summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/AjaxDispatcher.php25
-rw-r--r--includes/AjaxResponse.php74
-rw-r--r--includes/ArrayUtils.php69
-rw-r--r--includes/AuthPlugin.php65
-rw-r--r--includes/AutoLoader.php757
-rw-r--r--includes/Autopromote.php15
-rw-r--r--includes/Block.php370
-rw-r--r--includes/Category.php57
-rw-r--r--includes/CategoryFinder.php (renamed from includes/Categoryfinder.php)77
-rw-r--r--includes/CategoryViewer.php203
-rw-r--r--includes/ChangeTags.php147
-rw-r--r--includes/Collation.php175
-rw-r--r--includes/ConfEditor.php1109
-rw-r--r--includes/Cookie.php59
-rw-r--r--includes/DefaultSettings.php1382
-rw-r--r--includes/Defines.php65
-rw-r--r--includes/DeprecatedGlobal.php17
-rw-r--r--includes/EditPage.php1422
-rw-r--r--includes/Exception.php824
-rw-r--r--includes/Export.php373
-rw-r--r--includes/FakeTitle.php141
-rw-r--r--includes/Fallback.php50
-rw-r--r--includes/Feed.php111
-rw-r--r--includes/FeedUtils.php52
-rw-r--r--includes/FileDeleteForm.php68
-rw-r--r--includes/ForkController.php20
-rw-r--r--includes/FormOptions.php58
-rw-r--r--includes/GitInfo.php323
-rw-r--r--includes/GlobalFunctions.php1376
-rw-r--r--includes/HTMLForm.php2965
-rw-r--r--includes/HistoryBlob.php146
-rw-r--r--includes/Hooks.php24
-rw-r--r--includes/Html.php226
-rw-r--r--includes/HtmlFormatter.php97
-rw-r--r--includes/HttpFunctions.php127
-rw-r--r--includes/Import.php518
-rw-r--r--includes/Init.php136
-rw-r--r--includes/Licenses.php58
-rw-r--r--includes/LinkFilter.php115
-rw-r--r--includes/Linker.php991
-rw-r--r--includes/MWNamespace.php (renamed from includes/Namespace.php)122
-rw-r--r--includes/MWTimestamp.php (renamed from includes/Timestamp.php)80
-rw-r--r--includes/MagicWord.php175
-rw-r--r--includes/MediaWiki.php (renamed from includes/Wiki.php)331
-rw-r--r--includes/MediaWikiVersionFetcher.php31
-rw-r--r--includes/Message.php464
-rw-r--r--includes/MessageBlobStore.php106
-rw-r--r--includes/Metadata.php204
-rw-r--r--includes/MimeMagic.php360
-rw-r--r--includes/MovePage.php343
-rw-r--r--includes/OutputHandler.php15
-rw-r--r--includes/OutputPage.php1761
-rw-r--r--includes/PHPVersionError.php29
-rw-r--r--includes/PathRouter.php28
-rw-r--r--includes/PoolCounter.php329
-rw-r--r--includes/Preferences.php527
-rw-r--r--includes/PrefixSearch.php239
-rw-r--r--includes/ProtectionForm.php313
-rw-r--r--includes/ProxyTools.php86
-rw-r--r--includes/Revision.php517
-rw-r--r--includes/RevisionList.php60
-rw-r--r--includes/Sanitizer.php232
-rw-r--r--includes/Setup.php307
-rw-r--r--includes/SiteConfiguration.php74
-rw-r--r--includes/SiteStats.php340
-rw-r--r--includes/SpecialPage.php1446
-rw-r--r--includes/SpecialPageFactory.php591
-rw-r--r--includes/SquidPurgeClient.php65
-rw-r--r--includes/StatCounter.php34
-rw-r--r--includes/Status.php151
-rw-r--r--includes/StreamFile.php16
-rw-r--r--includes/StubObject.php115
-rw-r--r--includes/TimestampException.php7
-rw-r--r--includes/Title.php1932
-rw-r--r--includes/TitleArray.php67
-rw-r--r--includes/TitleArrayFromResult.php86
-rw-r--r--includes/User.php1160
-rw-r--r--includes/UserArray.php83
-rw-r--r--includes/UserArrayFromResult.php90
-rw-r--r--includes/UserMailer.php883
-rw-r--r--includes/UserRightsProxy.php75
-rw-r--r--includes/WatchedItem.php194
-rw-r--r--includes/WebRequest.php479
-rw-r--r--includes/WebResponse.php41
-rw-r--r--includes/WebStart.php62
-rw-r--r--includes/WikiError.php154
-rw-r--r--includes/WikiMap.php58
-rw-r--r--includes/Xml.php368
-rw-r--r--includes/ZhClient.php164
-rw-r--r--includes/ZhConversion.php3
-rw-r--r--includes/actions/Action.php (renamed from includes/Action.php)312
-rw-r--r--includes/actions/CachedAction.php23
-rw-r--r--includes/actions/CreditsAction.php51
-rw-r--r--includes/actions/DeleteAction.php10
-rw-r--r--includes/actions/EditAction.php38
-rw-r--r--includes/actions/FormAction.php123
-rw-r--r--includes/actions/FormlessAction.php45
-rw-r--r--includes/actions/HistoryAction.php218
-rw-r--r--includes/actions/InfoAction.php97
-rw-r--r--includes/actions/MarkpatrolledAction.php1
-rw-r--r--includes/actions/ProtectAction.php29
-rw-r--r--includes/actions/RawAction.php97
-rw-r--r--includes/actions/RenderAction.php3
-rw-r--r--includes/actions/RevertAction.php61
-rw-r--r--includes/actions/RevisiondeleteAction.php1
-rw-r--r--includes/actions/RollbackAction.php27
-rw-r--r--includes/actions/SubmitAction.php42
-rw-r--r--includes/actions/UnprotectAction.php43
-rw-r--r--includes/actions/UnwatchAction.php57
-rw-r--r--includes/actions/ViewAction.php1
-rw-r--r--includes/actions/WatchAction.php59
-rw-r--r--includes/api/ApiBase.php2267
-rw-r--r--includes/api/ApiBlock.php77
-rw-r--r--includes/api/ApiClearHasMsg.php58
-rw-r--r--includes/api/ApiComparePages.php55
-rw-r--r--includes/api/ApiCreateAccount.php106
-rw-r--r--includes/api/ApiDelete.php91
-rw-r--r--includes/api/ApiDisabled.php2
-rw-r--r--includes/api/ApiEditPage.php217
-rw-r--r--includes/api/ApiEmailUser.php43
-rw-r--r--includes/api/ApiExpandTemplates.php120
-rw-r--r--includes/api/ApiFeedContributions.php55
-rw-r--r--includes/api/ApiFeedRecentChanges.php207
-rw-r--r--includes/api/ApiFeedWatchlist.php64
-rw-r--r--includes/api/ApiFileRevert.php67
-rw-r--r--includes/api/ApiFormatBase.php146
-rw-r--r--includes/api/ApiFormatDbg.php4
-rw-r--r--includes/api/ApiFormatDump.php4
-rw-r--r--includes/api/ApiFormatFeedWrapper.php101
-rw-r--r--includes/api/ApiFormatJson.php13
-rw-r--r--includes/api/ApiFormatPhp.php3
-rw-r--r--includes/api/ApiFormatRaw.php8
-rw-r--r--includes/api/ApiFormatTxt.php4
-rw-r--r--includes/api/ApiFormatWddx.php17
-rw-r--r--includes/api/ApiFormatXml.php36
-rw-r--r--includes/api/ApiFormatYaml.php8
-rw-r--r--includes/api/ApiHelp.php32
-rw-r--r--includes/api/ApiImageRotate.php42
-rw-r--r--includes/api/ApiImport.php50
-rw-r--r--includes/api/ApiLogin.php105
-rw-r--r--includes/api/ApiLogout.php6
-rw-r--r--includes/api/ApiMain.php476
-rw-r--r--includes/api/ApiModuleManager.php176
-rw-r--r--includes/api/ApiMove.php105
-rw-r--r--includes/api/ApiOpenSearch.php34
-rw-r--r--includes/api/ApiOptions.php51
-rw-r--r--includes/api/ApiPageSet.php207
-rw-r--r--includes/api/ApiParamInfo.php93
-rw-r--r--includes/api/ApiParse.php278
-rw-r--r--includes/api/ApiPatrol.php39
-rw-r--r--includes/api/ApiProtect.php99
-rw-r--r--includes/api/ApiPurge.php95
-rw-r--r--includes/api/ApiQuery.php302
-rw-r--r--includes/api/ApiQueryAllCategories.php40
-rw-r--r--includes/api/ApiQueryAllImages.php136
-rw-r--r--includes/api/ApiQueryAllLinks.php124
-rw-r--r--includes/api/ApiQueryAllMessages.php40
-rw-r--r--includes/api/ApiQueryAllPages.php63
-rw-r--r--includes/api/ApiQueryAllUsers.php175
-rw-r--r--includes/api/ApiQueryBacklinks.php83
-rw-r--r--includes/api/ApiQueryBacklinksprop.php472
-rw-r--r--includes/api/ApiQueryBase.php490
-rw-r--r--includes/api/ApiQueryBlocks.php147
-rw-r--r--includes/api/ApiQueryCategories.php52
-rw-r--r--includes/api/ApiQueryCategoryInfo.php42
-rw-r--r--includes/api/ApiQueryCategoryMembers.php141
-rw-r--r--includes/api/ApiQueryContributors.php282
-rw-r--r--includes/api/ApiQueryDeletedrevs.php275
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php18
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php44
-rw-r--r--includes/api/ApiQueryExternalLinks.php25
-rw-r--r--includes/api/ApiQueryFileRepoInfo.php18
-rw-r--r--includes/api/ApiQueryFilearchive.php178
-rw-r--r--includes/api/ApiQueryIWBacklinks.php51
-rw-r--r--includes/api/ApiQueryIWLinks.php77
-rw-r--r--includes/api/ApiQueryImageInfo.php425
-rw-r--r--includes/api/ApiQueryImages.php30
-rw-r--r--includes/api/ApiQueryInfo.php166
-rw-r--r--includes/api/ApiQueryLangBacklinks.php48
-rw-r--r--includes/api/ApiQueryLangLinks.php73
-rw-r--r--includes/api/ApiQueryLinks.php30
-rw-r--r--includes/api/ApiQueryLogEvents.php289
-rw-r--r--includes/api/ApiQueryORM.php3
-rw-r--r--includes/api/ApiQueryPagePropNames.php7
-rw-r--r--includes/api/ApiQueryPageProps.php14
-rw-r--r--includes/api/ApiQueryPagesWithProp.php15
-rw-r--r--includes/api/ApiQueryPrefixSearch.php124
-rw-r--r--includes/api/ApiQueryProtectedTitles.php88
-rw-r--r--includes/api/ApiQueryQueryPage.php65
-rw-r--r--includes/api/ApiQueryRandom.php61
-rw-r--r--includes/api/ApiQueryRecentChanges.php446
-rw-r--r--includes/api/ApiQueryRevisions.php316
-rw-r--r--includes/api/ApiQuerySearch.php165
-rw-r--r--includes/api/ApiQuerySiteinfo.php340
-rw-r--r--includes/api/ApiQueryStashImageInfo.php20
-rw-r--r--includes/api/ApiQueryTags.php25
-rw-r--r--includes/api/ApiQueryTokens.php104
-rw-r--r--includes/api/ApiQueryUserContributions.php262
-rw-r--r--includes/api/ApiQueryUserInfo.php114
-rw-r--r--includes/api/ApiQueryUsers.php117
-rw-r--r--includes/api/ApiQueryWatchlist.php410
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php41
-rw-r--r--includes/api/ApiResult.php314
-rw-r--r--includes/api/ApiRevisionDelete.php236
-rw-r--r--includes/api/ApiRollback.php126
-rw-r--r--includes/api/ApiRsd.php10
-rw-r--r--includes/api/ApiSetNotificationTimestamp.php185
-rw-r--r--includes/api/ApiTokens.php21
-rw-r--r--includes/api/ApiUnblock.php50
-rw-r--r--includes/api/ApiUndelete.php58
-rw-r--r--includes/api/ApiUpload.php186
-rw-r--r--includes/api/ApiUserrights.php51
-rw-r--r--includes/api/ApiWatch.php155
-rw-r--r--includes/cache/BacklinkCache.php125
-rw-r--r--includes/cache/CacheDependency.php230
-rw-r--r--includes/cache/CacheHelper.php (renamed from includes/CacheHelper.php)64
-rw-r--r--includes/cache/FileCacheBase.php15
-rw-r--r--includes/cache/GenderCache.php28
-rw-r--r--includes/cache/HTMLFileCache.php47
-rw-r--r--includes/cache/LinkBatch.php47
-rw-r--r--includes/cache/LinkCache.php75
-rw-r--r--includes/cache/LocalisationCache.php571
-rw-r--r--includes/cache/MapCacheLRU.php123
-rw-r--r--includes/cache/MessageCache.php83
-rw-r--r--includes/cache/ObjectFileCache.php4
-rw-r--r--includes/cache/ResourceFileCache.php7
-rw-r--r--includes/cache/UserCache.php16
-rw-r--r--includes/cache/bloom/BloomCache.php323
-rw-r--r--includes/cache/bloom/BloomCacheRedis.php370
-rw-r--r--includes/cache/bloom/BloomFilters.php79
-rw-r--r--includes/changes/ChangesFeed.php (renamed from includes/ChangesFeed.php)71
-rw-r--r--includes/changes/ChangesList.php250
-rw-r--r--includes/changes/EnhancedChangesList.php336
-rw-r--r--includes/changes/OldChangesList.php95
-rw-r--r--includes/changes/RCCacheEntry.php16
-rw-r--r--includes/changes/RCCacheEntryFactory.php275
-rw-r--r--includes/changes/RecentChange.php523
-rw-r--r--includes/clientpool/RedisConnectionPool.php197
-rw-r--r--includes/composer/ComposerHookHandler.php37
-rw-r--r--includes/composer/ComposerPackageModifier.php62
-rw-r--r--includes/composer/ComposerVersionNormalizer.php66
-rw-r--r--includes/config/Config.php47
-rw-r--r--includes/config/ConfigException.php29
-rw-r--r--includes/config/ConfigFactory.php112
-rw-r--r--includes/config/GlobalVarConfig.php108
-rw-r--r--includes/config/HashConfig.php75
-rw-r--r--includes/config/MultiConfig.php72
-rw-r--r--includes/config/MutableConfig.php38
-rw-r--r--includes/content/AbstractContent.php277
-rw-r--r--includes/content/CodeContentHandler.php65
-rw-r--r--includes/content/Content.php163
-rw-r--r--includes/content/ContentHandler.php298
-rw-r--r--includes/content/CssContent.php28
-rw-r--r--includes/content/CssContentHandler.php37
-rw-r--r--includes/content/JavaScriptContent.php20
-rw-r--r--includes/content/JavaScriptContentHandler.php37
-rw-r--r--includes/content/JsonContent.php120
-rw-r--r--includes/content/JsonContentHandler.php26
-rw-r--r--includes/content/MessageContent.php54
-rw-r--r--includes/content/TextContent.php137
-rw-r--r--includes/content/TextContentHandler.php48
-rw-r--r--includes/content/WikitextContent.php175
-rw-r--r--includes/content/WikitextContentHandler.php44
-rw-r--r--includes/context/ContextSource.php34
-rw-r--r--includes/context/DerivativeContext.php58
-rw-r--r--includes/context/IContextSource.php20
-rw-r--r--includes/context/RequestContext.php187
-rw-r--r--includes/dao/DBAccessBase.php8
-rw-r--r--includes/dao/IDBAccessObject.php13
-rw-r--r--includes/db/ChronologyProtector.php13
-rw-r--r--includes/db/CloneDatabase.php65
-rw-r--r--includes/db/Database.php1325
-rw-r--r--includes/db/DatabaseError.php181
-rw-r--r--includes/db/DatabaseMssql.php1502
-rw-r--r--includes/db/DatabaseMysql.php35
-rw-r--r--includes/db/DatabaseMysqlBase.php451
-rw-r--r--includes/db/DatabaseMysqli.php138
-rw-r--r--includes/db/DatabaseOracle.php376
-rw-r--r--includes/db/DatabasePostgres.php426
-rw-r--r--includes/db/DatabaseSqlite.php289
-rw-r--r--includes/db/DatabaseUtility.php71
-rw-r--r--includes/db/IORMRow.php32
-rw-r--r--includes/db/IORMTable.php65
-rw-r--r--includes/db/LBFactory.php152
-rw-r--r--includes/db/LBFactoryMulti.php (renamed from includes/db/LBFactory_Multi.php)175
-rw-r--r--includes/db/LBFactorySingle.php (renamed from includes/db/LBFactory_Single.php)50
-rw-r--r--includes/db/LoadBalancer.php458
-rw-r--r--includes/db/LoadMonitor.php129
-rw-r--r--includes/db/ORMIterator.php1
-rw-r--r--includes/db/ORMResult.php14
-rw-r--r--includes/db/ORMRow.php55
-rw-r--r--includes/db/ORMTable.php123
-rw-r--r--includes/debug/MWDebug.php (renamed from includes/debug/Debug.php)121
-rw-r--r--includes/deferred/CallableUpdate.php (renamed from includes/CallableUpdate.php)5
-rw-r--r--includes/deferred/DataUpdate.php (renamed from includes/DataUpdate.php)13
-rw-r--r--includes/deferred/DeferredUpdates.php (renamed from includes/DeferredUpdates.php)43
-rw-r--r--includes/deferred/HTMLCacheUpdate.php (renamed from includes/cache/HTMLCacheUpdate.php)17
-rw-r--r--includes/deferred/LinksUpdate.php (renamed from includes/LinksUpdate.php)254
-rw-r--r--includes/deferred/SearchUpdate.php (renamed from includes/search/SearchUpdate.php)52
-rw-r--r--includes/deferred/SiteStatsUpdate.php254
-rw-r--r--includes/deferred/SqlDataUpdate.php (renamed from includes/SqlDataUpdate.php)45
-rw-r--r--includes/deferred/SquidUpdate.php (renamed from includes/cache/SquidUpdate.php)25
-rw-r--r--includes/deferred/ViewCountUpdate.php (renamed from includes/ViewCountUpdate.php)26
-rw-r--r--includes/diff/ArrayDiffFormatter.php82
-rw-r--r--includes/diff/DairikiDiff.php911
-rw-r--r--includes/diff/DiffFormatter.php247
-rw-r--r--includes/diff/DifferenceEngine.php502
-rw-r--r--includes/diff/TableDiffFormatter.php214
-rw-r--r--includes/diff/UnifiedDiffFormatter.php74
-rw-r--r--includes/diff/WikiDiff3.php89
-rw-r--r--includes/exception/BadTitleError.php51
-rw-r--r--includes/exception/ErrorPageError.php61
-rw-r--r--includes/exception/FatalError.php43
-rw-r--r--includes/exception/HttpError.php93
-rw-r--r--includes/exception/MWException.php262
-rw-r--r--includes/exception/MWExceptionHandler.php372
-rw-r--r--includes/exception/PermissionsError.php58
-rw-r--r--includes/exception/ReadOnlyError.php36
-rw-r--r--includes/exception/ThrottledError.php40
-rw-r--r--includes/exception/UserBlockedError.php33
-rw-r--r--includes/exception/UserNotLoggedIn.php102
-rw-r--r--includes/externalstore/ExternalStore.php10
-rw-r--r--includes/externalstore/ExternalStoreDB.php92
-rw-r--r--includes/externalstore/ExternalStoreMedium.php9
-rw-r--r--includes/externalstore/ExternalStoreMwstore.php11
-rw-r--r--includes/filebackend/FSFile.php38
-rw-r--r--includes/filebackend/FSFileBackend.php201
-rw-r--r--includes/filebackend/FileBackend.php300
-rw-r--r--includes/filebackend/FileBackendGroup.php46
-rw-r--r--includes/filebackend/FileBackendMultiWrite.php102
-rw-r--r--includes/filebackend/FileBackendStore.php383
-rw-r--r--includes/filebackend/FileOp.php183
-rw-r--r--includes/filebackend/FileOpBatch.php21
-rw-r--r--includes/filebackend/MemoryFileBackend.php274
-rw-r--r--includes/filebackend/README2
-rw-r--r--includes/filebackend/SwiftFileBackend.php2108
-rw-r--r--includes/filebackend/TempFSFile.php50
-rw-r--r--includes/filebackend/filejournal/DBFileJournal.php22
-rw-r--r--includes/filebackend/filejournal/FileJournal.php68
-rw-r--r--includes/filebackend/lockmanager/DBLockManager.php44
-rw-r--r--includes/filebackend/lockmanager/FSLockManager.php35
-rw-r--r--includes/filebackend/lockmanager/LSLockManager.php218
-rw-r--r--includes/filebackend/lockmanager/LockManager.php41
-rw-r--r--includes/filebackend/lockmanager/LockManagerGroup.php22
-rw-r--r--includes/filebackend/lockmanager/MemcLockManager.php50
-rw-r--r--includes/filebackend/lockmanager/QuorumLockManager.php22
-rw-r--r--includes/filebackend/lockmanager/RedisLockManager.php150
-rw-r--r--includes/filebackend/lockmanager/ScopedLock.php15
-rw-r--r--includes/filerepo/FSRepo.php6
-rw-r--r--includes/filerepo/FileRepo.php453
-rw-r--r--includes/filerepo/FileRepoStatus.php11
-rw-r--r--includes/filerepo/ForeignAPIRepo.php139
-rw-r--r--includes/filerepo/ForeignDBRepo.php46
-rw-r--r--includes/filerepo/ForeignDBViaLBRepo.php24
-rw-r--r--includes/filerepo/LocalRepo.php195
-rw-r--r--includes/filerepo/NullRepo.php6
-rw-r--r--includes/filerepo/RepoGroup.php186
-rw-r--r--includes/filerepo/file/ArchivedFile.php165
-rw-r--r--includes/filerepo/file/File.php827
-rw-r--r--includes/filerepo/file/ForeignAPIFile.php53
-rw-r--r--includes/filerepo/file/ForeignDBFile.php58
-rw-r--r--includes/filerepo/file/LocalFile.php994
-rw-r--r--includes/filerepo/file/OldLocalFile.php125
-rw-r--r--includes/filerepo/file/UnregisteredLocalFile.php63
-rw-r--r--includes/gallery/ImageGalleryBase.php165
-rw-r--r--includes/gallery/NolinesImageGallery.php1
-rw-r--r--includes/gallery/PackedImageGallery.php11
-rw-r--r--includes/gallery/PackedOverlayImageGallery.php22
-rw-r--r--includes/gallery/TraditionalImageGallery.php114
-rw-r--r--includes/htmlform/HTMLApiField.php19
-rw-r--r--includes/htmlform/HTMLAutoCompleteSelectField.php165
-rw-r--r--includes/htmlform/HTMLButtonField.php42
-rw-r--r--includes/htmlform/HTMLCheckField.php91
-rw-r--r--includes/htmlform/HTMLCheckMatrix.php250
-rw-r--r--includes/htmlform/HTMLEditTools.php51
-rw-r--r--includes/htmlform/HTMLFloatField.php46
-rw-r--r--includes/htmlform/HTMLForm.php1472
-rw-r--r--includes/htmlform/HTMLFormField.php893
-rw-r--r--includes/htmlform/HTMLFormFieldCloner.php382
-rw-r--r--includes/htmlform/HTMLFormFieldRequiredOptionsException.php9
-rw-r--r--includes/htmlform/HTMLHiddenField.php44
-rw-r--r--includes/htmlform/HTMLInfoField.php54
-rw-r--r--includes/htmlform/HTMLIntField.php27
-rw-r--r--includes/htmlform/HTMLMultiSelectField.php122
-rw-r--r--includes/htmlform/HTMLNestedFilterable.php11
-rw-r--r--includes/htmlform/HTMLRadioField.php71
-rw-r--r--includes/htmlform/HTMLSelectAndOtherField.php126
-rw-r--r--includes/htmlform/HTMLSelectField.php44
-rw-r--r--includes/htmlform/HTMLSelectLimitField.php35
-rw-r--r--includes/htmlform/HTMLSelectOrOtherField.php83
-rw-r--r--includes/htmlform/HTMLSubmitField.php9
-rw-r--r--includes/htmlform/HTMLTextAreaField.php38
-rw-r--r--includes/htmlform/HTMLTextField.php65
-rw-r--r--includes/installer/CliInstaller.php20
-rw-r--r--includes/installer/DatabaseInstaller.php99
-rw-r--r--includes/installer/DatabaseUpdater.php161
-rw-r--r--includes/installer/InstallDocFormatter.php2
-rw-r--r--includes/installer/Installer.i18n.php21554
-rw-r--r--includes/installer/Installer.php457
-rw-r--r--includes/installer/LocalSettingsGenerator.php87
-rw-r--r--includes/installer/MssqlInstaller.php737
-rw-r--r--includes/installer/MssqlUpdater.php138
-rw-r--r--includes/installer/MysqlInstaller.php33
-rw-r--r--includes/installer/MysqlUpdater.php81
-rw-r--r--includes/installer/OracleInstaller.php1
-rw-r--r--includes/installer/OracleUpdater.php23
-rw-r--r--includes/installer/PhpBugTests.php26
-rw-r--r--includes/installer/PostgresInstaller.php43
-rw-r--r--includes/installer/PostgresUpdater.php51
-rw-r--r--includes/installer/SqliteInstaller.php10
-rw-r--r--includes/installer/SqliteUpdater.php23
-rw-r--r--includes/installer/WebInstaller.php264
-rw-r--r--includes/installer/WebInstallerOutput.php173
-rw-r--r--includes/installer/WebInstallerPage.php366
-rw-r--r--includes/installer/i18n/af.json154
-rw-r--r--includes/installer/i18n/aln.json9
-rw-r--r--includes/installer/i18n/am.json5
-rw-r--r--includes/installer/i18n/an.json9
-rw-r--r--includes/installer/i18n/ang.json9
-rw-r--r--includes/installer/i18n/ar.json120
-rw-r--r--includes/installer/i18n/arc.json22
-rw-r--r--includes/installer/i18n/ary.json10
-rw-r--r--includes/installer/i18n/arz.json18
-rw-r--r--includes/installer/i18n/as.json10
-rw-r--r--includes/installer/i18n/ast.json71
-rw-r--r--includes/installer/i18n/av.json8
-rw-r--r--includes/installer/i18n/avk.json4
-rw-r--r--includes/installer/i18n/az.json33
-rw-r--r--includes/installer/i18n/ba.json10
-rw-r--r--includes/installer/i18n/bar.json13
-rw-r--r--includes/installer/i18n/bcc.json5
-rw-r--r--includes/installer/i18n/bcl.json37
-rw-r--r--includes/installer/i18n/be-tarask.json333
-rw-r--r--includes/installer/i18n/be.json20
-rw-r--r--includes/installer/i18n/bg.json293
-rw-r--r--includes/installer/i18n/bjn.json10
-rw-r--r--includes/installer/i18n/bn.json125
-rw-r--r--includes/installer/i18n/bpy.json5
-rw-r--r--includes/installer/i18n/br.json283
-rw-r--r--includes/installer/i18n/bs.json59
-rw-r--r--includes/installer/i18n/bto.json65
-rw-r--r--includes/installer/i18n/ca.json203
-rw-r--r--includes/installer/i18n/ce.json92
-rw-r--r--includes/installer/i18n/ceb.json5
-rw-r--r--includes/installer/i18n/ckb.json42
-rw-r--r--includes/installer/i18n/cps.json10
-rw-r--r--includes/installer/i18n/crh-cyrl.json5
-rw-r--r--includes/installer/i18n/crh-latn.json5
-rw-r--r--includes/installer/i18n/cs.json334
-rw-r--r--includes/installer/i18n/csb.json4
-rw-r--r--includes/installer/i18n/cu.json10
-rw-r--r--includes/installer/i18n/cv.json9
-rw-r--r--includes/installer/i18n/cy.json12
-rw-r--r--includes/installer/i18n/da.json38
-rw-r--r--includes/installer/i18n/de-ch.json8
-rw-r--r--includes/installer/i18n/de-formal.json12
-rw-r--r--includes/installer/i18n/de.json340
-rw-r--r--includes/installer/i18n/diq.json67
-rw-r--r--includes/installer/i18n/dsb.json9
-rw-r--r--includes/installer/i18n/dtp.json9
-rw-r--r--includes/installer/i18n/el.json111
-rw-r--r--includes/installer/i18n/en-gb.json22
-rw-r--r--includes/installer/i18n/en.json326
-rw-r--r--includes/installer/i18n/eo.json50
-rw-r--r--includes/installer/i18n/es-formal.json9
-rw-r--r--includes/installer/i18n/es.json352
-rw-r--r--includes/installer/i18n/et.json68
-rw-r--r--includes/installer/i18n/eu.json78
-rw-r--r--includes/installer/i18n/ext.json5
-rw-r--r--includes/installer/i18n/fa.json331
-rw-r--r--includes/installer/i18n/fi.json220
-rw-r--r--includes/installer/i18n/fo.json40
-rw-r--r--includes/installer/i18n/fr.json348
-rw-r--r--includes/installer/i18n/frc.json5
-rw-r--r--includes/installer/i18n/frp.json153
-rw-r--r--includes/installer/i18n/frr.json10
-rw-r--r--includes/installer/i18n/fur.json15
-rw-r--r--includes/installer/i18n/fy.json9
-rw-r--r--includes/installer/i18n/ga.json13
-rw-r--r--includes/installer/i18n/gag.json5
-rw-r--r--includes/installer/i18n/gan-hans.json5
-rw-r--r--includes/installer/i18n/gan-hant.json9
-rw-r--r--includes/installer/i18n/gd.json9
-rw-r--r--includes/installer/i18n/gl.json331
-rw-r--r--includes/installer/i18n/gom-latn.json9
-rw-r--r--includes/installer/i18n/grc.json11
-rw-r--r--includes/installer/i18n/gsw.json68
-rw-r--r--includes/installer/i18n/gu.json45
-rw-r--r--includes/installer/i18n/gv.json4
-rw-r--r--includes/installer/i18n/hak.json5
-rw-r--r--includes/installer/i18n/haw.json64
-rw-r--r--includes/installer/i18n/he.json330
-rw-r--r--includes/installer/i18n/hi.json49
-rw-r--r--includes/installer/i18n/hif-latn.json9
-rw-r--r--includes/installer/i18n/hil.json9
-rw-r--r--includes/installer/i18n/hr.json5
-rw-r--r--includes/installer/i18n/hrx.json314
-rw-r--r--includes/installer/i18n/hsb.json256
-rw-r--r--includes/installer/i18n/ht.json10
-rw-r--r--includes/installer/i18n/hu-formal.json33
-rw-r--r--includes/installer/i18n/hu.json304
-rw-r--r--includes/installer/i18n/hy.json5
-rw-r--r--includes/installer/i18n/ia.json318
-rw-r--r--includes/installer/i18n/id.json323
-rw-r--r--includes/installer/i18n/ie.json4
-rw-r--r--includes/installer/i18n/ig.json17
-rw-r--r--includes/installer/i18n/ilo.json4
-rw-r--r--includes/installer/i18n/io.json9
-rw-r--r--includes/installer/i18n/is.json5
-rw-r--r--includes/installer/i18n/it.json334
-rw-r--r--includes/installer/i18n/ja.json338
-rw-r--r--includes/installer/i18n/jam.json9
-rw-r--r--includes/installer/i18n/jut.json9
-rw-r--r--includes/installer/i18n/jv.json5
-rw-r--r--includes/installer/i18n/ka.json97
-rw-r--r--includes/installer/i18n/kaa.json5
-rw-r--r--includes/installer/i18n/kbd-cyrl.json10
-rw-r--r--includes/installer/i18n/khw.json8
-rw-r--r--includes/installer/i18n/kiu.json9
-rw-r--r--includes/installer/i18n/kk-arab.json5
-rw-r--r--includes/installer/i18n/kk-cyrl.json5
-rw-r--r--includes/installer/i18n/kk-latn.json5
-rw-r--r--includes/installer/i18n/km.json32
-rw-r--r--includes/installer/i18n/kn.json46
-rw-r--r--includes/installer/i18n/ko.json323
-rw-r--r--includes/installer/i18n/krc.json29
-rw-r--r--includes/installer/i18n/ksh.json319
-rw-r--r--includes/installer/i18n/ku-latn.json17
-rw-r--r--includes/installer/i18n/lad.json10
-rw-r--r--includes/installer/i18n/lb.json212
-rw-r--r--includes/installer/i18n/lez.json27
-rw-r--r--includes/installer/i18n/lfn.json5
-rw-r--r--includes/installer/i18n/lg.json9
-rw-r--r--includes/installer/i18n/li.json9
-rw-r--r--includes/installer/i18n/lo.json4
-rw-r--r--includes/installer/i18n/lrc.json24
-rw-r--r--includes/installer/i18n/lt.json89
-rw-r--r--includes/installer/i18n/lv.json45
-rw-r--r--includes/installer/i18n/lzh.json10
-rw-r--r--includes/installer/i18n/lzz.json9
-rw-r--r--includes/installer/i18n/mai.json9
-rw-r--r--includes/installer/i18n/mdf.json5
-rw-r--r--includes/installer/i18n/mg.json65
-rw-r--r--includes/installer/i18n/mhr.json4
-rw-r--r--includes/installer/i18n/min.json11
-rw-r--r--includes/installer/i18n/mk.json329
-rw-r--r--includes/installer/i18n/ml.json120
-rw-r--r--includes/installer/i18n/mn.json10
-rw-r--r--includes/installer/i18n/mr.json62
-rw-r--r--includes/installer/i18n/ms.json151
-rw-r--r--includes/installer/i18n/mt.json90
-rw-r--r--includes/installer/i18n/my.json8
-rw-r--r--includes/installer/i18n/myv.json16
-rw-r--r--includes/installer/i18n/mzn.json8
-rw-r--r--includes/installer/i18n/nah.json4
-rw-r--r--includes/installer/i18n/nan.json59
-rw-r--r--includes/installer/i18n/nap.json16
-rw-r--r--includes/installer/i18n/nb.json324
-rw-r--r--includes/installer/i18n/nds-nl.json9
-rw-r--r--includes/installer/i18n/nds.json14
-rw-r--r--includes/installer/i18n/ne.json26
-rw-r--r--includes/installer/i18n/nl-informal.json79
-rw-r--r--includes/installer/i18n/nl.json330
-rw-r--r--includes/installer/i18n/nn.json41
-rw-r--r--includes/installer/i18n/oc.json181
-rw-r--r--includes/installer/i18n/or.json33
-rw-r--r--includes/installer/i18n/os.json9
-rw-r--r--includes/installer/i18n/pa.json13
-rw-r--r--includes/installer/i18n/pam.json5
-rw-r--r--includes/installer/i18n/pcd.json4
-rw-r--r--includes/installer/i18n/pdc.json13
-rw-r--r--includes/installer/i18n/pl.json340
-rw-r--r--includes/installer/i18n/pms.json301
-rw-r--r--includes/installer/i18n/pnt.json8
-rw-r--r--includes/installer/i18n/prg.json9
-rw-r--r--includes/installer/i18n/ps.json65
-rw-r--r--includes/installer/i18n/pt-br.json244
-rw-r--r--includes/installer/i18n/pt.json335
-rw-r--r--includes/installer/i18n/qqq.json344
-rw-r--r--includes/installer/i18n/qu.json20
-rw-r--r--includes/installer/i18n/rgn.json4
-rw-r--r--includes/installer/i18n/rm.json9
-rw-r--r--includes/installer/i18n/ro.json140
-rw-r--r--includes/installer/i18n/roa-tara.json56
-rw-r--r--includes/installer/i18n/ru.json344
-rw-r--r--includes/installer/i18n/rue.json9
-rw-r--r--includes/installer/i18n/sa.json8
-rw-r--r--includes/installer/i18n/sah.json5
-rw-r--r--includes/installer/i18n/sc.json14
-rw-r--r--includes/installer/i18n/scn.json5
-rw-r--r--includes/installer/i18n/sco.json314
-rw-r--r--includes/installer/i18n/sdc.json19
-rw-r--r--includes/installer/i18n/sei.json4
-rw-r--r--includes/installer/i18n/sh.json10
-rw-r--r--includes/installer/i18n/shi.json10
-rw-r--r--includes/installer/i18n/si.json142
-rw-r--r--includes/installer/i18n/sk.json78
-rw-r--r--includes/installer/i18n/sl.json174
-rw-r--r--includes/installer/i18n/sli.json9
-rw-r--r--includes/installer/i18n/so.json9
-rw-r--r--includes/installer/i18n/sq.json5
-rw-r--r--includes/installer/i18n/sr-ec.json73
-rw-r--r--includes/installer/i18n/sr-el.json39
-rw-r--r--includes/installer/i18n/srn.json9
-rw-r--r--includes/installer/i18n/ss.json4
-rw-r--r--includes/installer/i18n/stq.json9
-rw-r--r--includes/installer/i18n/su.json5
-rw-r--r--includes/installer/i18n/sv.json330
-rw-r--r--includes/installer/i18n/sw.json9
-rw-r--r--includes/installer/i18n/szl.json9
-rw-r--r--includes/installer/i18n/ta.json90
-rw-r--r--includes/installer/i18n/tcy.json5
-rw-r--r--includes/installer/i18n/te.json240
-rw-r--r--includes/installer/i18n/tet.json9
-rw-r--r--includes/installer/i18n/tg-cyrl.json9
-rw-r--r--includes/installer/i18n/tg-latn.json9
-rw-r--r--includes/installer/i18n/th.json10
-rw-r--r--includes/installer/i18n/tk.json9
-rw-r--r--includes/installer/i18n/tl.json305
-rw-r--r--includes/installer/i18n/tly.json8
-rw-r--r--includes/installer/i18n/tr.json201
-rw-r--r--includes/installer/i18n/tt-cyrl.json10
-rw-r--r--includes/installer/i18n/tt-latn.json10
-rw-r--r--includes/installer/i18n/tyv.json8
-rw-r--r--includes/installer/i18n/udm.json8
-rw-r--r--includes/installer/i18n/ug-arab.json9
-rw-r--r--includes/installer/i18n/uk.json332
-rw-r--r--includes/installer/i18n/ur.json34
-rw-r--r--includes/installer/i18n/uz.json10
-rw-r--r--includes/installer/i18n/vec.json10
-rw-r--r--includes/installer/i18n/vep.json10
-rw-r--r--includes/installer/i18n/vi.json195
-rw-r--r--includes/installer/i18n/vo.json5
-rw-r--r--includes/installer/i18n/vro.json5
-rw-r--r--includes/installer/i18n/wa.json8
-rw-r--r--includes/installer/i18n/war.json72
-rw-r--r--includes/installer/i18n/wo.json9
-rw-r--r--includes/installer/i18n/wuu.json9
-rw-r--r--includes/installer/i18n/xal.json9
-rw-r--r--includes/installer/i18n/yi.json58
-rw-r--r--includes/installer/i18n/yo.json19
-rw-r--r--includes/installer/i18n/yue.json5
-rw-r--r--includes/installer/i18n/zea.json9
-rw-r--r--includes/installer/i18n/zh-hans.json345
-rw-r--r--includes/installer/i18n/zh-hant.json335
-rw-r--r--includes/installer/i18n/zh-hk.json8
-rw-r--r--includes/installer/i18n/zh-tw.json4
-rw-r--r--includes/interwiki/Interwiki.php195
-rw-r--r--includes/job/jobs/HTMLCacheUpdateJob.php263
-rw-r--r--includes/jobqueue/Job.php (renamed from includes/job/Job.php)129
-rw-r--r--includes/jobqueue/JobQueue.php (renamed from includes/job/JobQueue.php)156
-rw-r--r--includes/jobqueue/JobQueueDB.php (renamed from includes/job/JobQueueDB.php)143
-rw-r--r--includes/jobqueue/JobQueueFederated.php (renamed from includes/job/JobQueueFederated.php)218
-rw-r--r--includes/jobqueue/JobQueueGroup.php (renamed from includes/job/JobQueueGroup.php)143
-rw-r--r--includes/jobqueue/JobQueueRedis.php (renamed from includes/job/JobQueueRedis.php)357
-rw-r--r--includes/jobqueue/JobRunner.php350
-rw-r--r--includes/jobqueue/JobSpecification.php189
-rw-r--r--includes/jobqueue/README (renamed from includes/job/README)0
-rw-r--r--includes/jobqueue/aggregator/JobQueueAggregator.php (renamed from includes/job/aggregator/JobQueueAggregator.php)12
-rw-r--r--includes/jobqueue/aggregator/JobQueueAggregatorMemc.php (renamed from includes/job/aggregator/JobQueueAggregatorMemc.php)5
-rw-r--r--includes/jobqueue/aggregator/JobQueueAggregatorRedis.php (renamed from includes/job/aggregator/JobQueueAggregatorRedis.php)93
-rw-r--r--includes/jobqueue/jobs/AssembleUploadChunksJob.php (renamed from includes/job/jobs/AssembleUploadChunksJob.php)15
-rw-r--r--includes/jobqueue/jobs/DoubleRedirectJob.php (renamed from includes/job/jobs/DoubleRedirectJob.php)57
-rw-r--r--includes/jobqueue/jobs/DuplicateJob.php (renamed from includes/job/jobs/DuplicateJob.php)12
-rw-r--r--includes/jobqueue/jobs/EmaillingJob.php (renamed from includes/job/jobs/EmaillingJob.php)5
-rw-r--r--includes/jobqueue/jobs/EnotifNotifyJob.php (renamed from includes/job/jobs/EnotifNotifyJob.php)9
-rw-r--r--includes/jobqueue/jobs/HTMLCacheUpdateJob.php162
-rw-r--r--includes/jobqueue/jobs/NullJob.php (renamed from includes/job/jobs/NullJob.php)10
-rw-r--r--includes/jobqueue/jobs/PublishStashedFileJob.php (renamed from includes/job/jobs/PublishStashedFileJob.php)18
-rw-r--r--includes/jobqueue/jobs/RefreshLinksJob.php199
-rw-r--r--includes/jobqueue/jobs/RefreshLinksJob2.php (renamed from includes/job/jobs/RefreshLinksJob.php)101
-rw-r--r--includes/jobqueue/jobs/UploadFromUrlJob.php (renamed from includes/job/jobs/UploadFromUrlJob.php)31
-rw-r--r--includes/jobqueue/utils/BacklinkJobUtils.php122
-rw-r--r--includes/json/FormatJson.php150
-rw-r--r--includes/libs/CSSJanus.php246
-rw-r--r--includes/libs/CSSMin.php300
-rw-r--r--includes/libs/GenericArrayObject.php5
-rw-r--r--includes/libs/HashRing.php (renamed from includes/HashRing.php)109
-rw-r--r--includes/libs/HttpStatus.php8
-rw-r--r--includes/libs/IEContentAnalyzer.php7
-rw-r--r--includes/libs/IPSet.php277
-rw-r--r--includes/libs/JavaScriptMinifier.php1
-rw-r--r--includes/libs/MWMessagePack.php189
-rw-r--r--includes/libs/MappedIterator.php (renamed from includes/MappedIterator.php)7
-rw-r--r--includes/libs/MultiHttpClient.php389
-rw-r--r--includes/libs/ProcessCacheLRU.php (renamed from includes/cache/ProcessCacheLRU.php)27
-rw-r--r--includes/libs/RunningStat.php176
-rw-r--r--includes/libs/ScopedCallback.php (renamed from includes/ScopedCallback.php)4
-rw-r--r--includes/libs/ScopedPHPTimeout.php (renamed from includes/ScopedPHPTimeout.php)0
-rw-r--r--includes/libs/XmlTypeCheck.php (renamed from includes/XmlTypeCheck.php)0
-rw-r--r--includes/libs/jsminplus.php1
-rw-r--r--includes/libs/lessc.inc.php88
-rw-r--r--includes/libs/virtualrest/SwiftVirtualRESTService.php175
-rw-r--r--includes/libs/virtualrest/VirtualRESTService.php107
-rw-r--r--includes/libs/virtualrest/VirtualRESTServiceClient.php289
-rw-r--r--includes/limit.sh83
-rw-r--r--includes/logging/DeleteLogFormatter.php208
-rw-r--r--includes/logging/LogEntry.php126
-rw-r--r--includes/logging/LogEventsList.php235
-rw-r--r--includes/logging/LogFormatter.php126
-rw-r--r--includes/logging/LogPage.php178
-rw-r--r--includes/logging/LogPager.php112
-rw-r--r--includes/logging/MoveLogFormatter.php8
-rw-r--r--includes/logging/NewUsersLogFormatter.php3
-rw-r--r--includes/logging/PageLangLogFormatter.php61
-rw-r--r--includes/logging/PatrolLog.php15
-rw-r--r--includes/logging/PatrolLogFormatter.php2
-rw-r--r--includes/logging/RightsLogFormatter.php1
-rw-r--r--includes/mail/EmailNotification.php512
-rw-r--r--includes/mail/MailAddress.php91
-rw-r--r--includes/mail/UserMailer.php425
-rw-r--r--includes/media/BMP.php14
-rw-r--r--includes/media/Bitmap.php488
-rw-r--r--includes/media/BitmapMetadataHandler.php58
-rw-r--r--includes/media/Bitmap_ClientOnly.php15
-rw-r--r--includes/media/DjVu.php140
-rw-r--r--includes/media/DjVuImage.php82
-rw-r--r--includes/media/Exif.php511
-rw-r--r--includes/media/ExifBitmap.php74
-rw-r--r--includes/media/FormatMetadata.php2061
-rw-r--r--includes/media/GIF.php48
-rw-r--r--includes/media/GIFMetadataExtractor.php51
-rw-r--r--includes/media/IPTC.php47
-rw-r--r--includes/media/ImageHandler.php72
-rw-r--r--includes/media/Jpeg.php94
-rw-r--r--includes/media/JpegMetadataExtractor.php24
-rw-r--r--includes/media/MediaHandler.php415
-rw-r--r--includes/media/MediaTransformOutput.php98
-rw-r--r--includes/media/PNG.php50
-rw-r--r--includes/media/PNGMetadataExtractor.php92
-rw-r--r--includes/media/SVG.php202
-rw-r--r--includes/media/SVGMetadataExtractor.php92
-rw-r--r--includes/media/Tiff.php24
-rw-r--r--includes/media/TransformationalImageHandler.php593
-rw-r--r--includes/media/XCF.php141
-rw-r--r--includes/media/XMP.php213
-rw-r--r--includes/media/XMPInfo.php992
-rw-r--r--includes/media/XMPValidate.php82
-rw-r--r--includes/mime.info14
-rw-r--r--includes/mime.types8
-rw-r--r--includes/normal/Makefile5
-rw-r--r--includes/normal/RandomTest.php20
-rw-r--r--includes/normal/Utf8Case.php2109
-rw-r--r--includes/normal/Utf8CaseGenerate.php112
-rw-r--r--includes/normal/Utf8Test.php47
-rw-r--r--includes/normal/UtfNormal.php291
-rw-r--r--includes/normal/UtfNormalBench.php39
-rw-r--r--includes/normal/UtfNormalData.inc3
-rw-r--r--includes/normal/UtfNormalDataK.inc1
-rw-r--r--includes/normal/UtfNormalDefines.php1
-rw-r--r--includes/normal/UtfNormalGenerate.php103
-rw-r--r--includes/normal/UtfNormalMemStress.php49
-rw-r--r--includes/normal/UtfNormalTest.php70
-rw-r--r--includes/normal/UtfNormalTest2.php304
-rw-r--r--includes/normal/UtfNormalUtil.php42
-rw-r--r--includes/objectcache/APCBagOStuff.php30
-rw-r--r--includes/objectcache/BagOStuff.php180
-rw-r--r--includes/objectcache/DBABagOStuff.php307
-rw-r--r--includes/objectcache/EhcacheBagOStuff.php324
-rw-r--r--includes/objectcache/EmptyBagOStuff.php37
-rw-r--r--includes/objectcache/HashBagOStuff.php31
-rw-r--r--includes/objectcache/MemcachedBagOStuff.php58
-rw-r--r--includes/objectcache/MemcachedClient.php274
-rw-r--r--includes/objectcache/MemcachedPeclBagOStuff.php87
-rw-r--r--includes/objectcache/MemcachedPhpBagOStuff.php28
-rw-r--r--includes/objectcache/MultiWriteBagOStuff.php83
-rw-r--r--includes/objectcache/ObjectCache.php28
-rw-r--r--includes/objectcache/ObjectCacheSessionHandler.php31
-rw-r--r--includes/objectcache/RedisBagOStuff.php215
-rw-r--r--includes/objectcache/SqlBagOStuff.php222
-rw-r--r--includes/objectcache/WinCacheBagOStuff.php22
-rw-r--r--includes/objectcache/XCacheBagOStuff.php30
-rw-r--r--includes/page/Article.php (renamed from includes/Article.php)786
-rw-r--r--includes/page/CategoryPage.php (renamed from includes/CategoryPage.php)4
-rw-r--r--includes/page/ImagePage.php (renamed from includes/ImagePage.php)409
-rw-r--r--includes/page/WikiCategoryPage.php (renamed from includes/WikiCategoryPage.php)0
-rw-r--r--includes/page/WikiFilePage.php (renamed from includes/WikiFilePage.php)58
-rw-r--r--includes/page/WikiPage.php (renamed from includes/WikiPage.php)1315
-rw-r--r--includes/pager/AlphabeticPager.php108
-rw-r--r--includes/pager/IndexPager.php (renamed from includes/Pager.php)715
-rw-r--r--includes/pager/Pager.php35
-rw-r--r--includes/pager/ReverseChronologicalPager.php118
-rw-r--r--includes/pager/TablePager.php469
-rw-r--r--includes/parser/CacheTime.php98
-rw-r--r--includes/parser/CoreParserFunctions.php710
-rw-r--r--includes/parser/CoreTagHooks.php26
-rw-r--r--includes/parser/DateFormatter.php51
-rw-r--r--includes/parser/LinkHolderArray.php243
-rw-r--r--includes/parser/MWTidy.php (renamed from includes/parser/Tidy.php)37
-rw-r--r--includes/parser/Parser.php1364
-rw-r--r--includes/parser/ParserCache.php108
-rw-r--r--includes/parser/ParserDiffTest.php (renamed from includes/parser/Parser_DiffTest.php)26
-rw-r--r--includes/parser/ParserOptions.php517
-rw-r--r--includes/parser/ParserOutput.php449
-rw-r--r--includes/parser/Preprocessor.php176
-rw-r--r--includes/parser/Preprocessor_DOM.php485
-rw-r--r--includes/parser/Preprocessor_Hash.php620
-rw-r--r--includes/parser/StripState.php58
-rw-r--r--includes/password/BcryptPassword.php88
-rw-r--r--includes/password/EncryptedPassword.php98
-rw-r--r--includes/password/InvalidPassword.php47
-rw-r--r--includes/password/LayeredParameterizedPassword.php140
-rw-r--r--includes/password/MWOldPassword.php48
-rw-r--r--includes/password/MWSaltedPassword.php46
-rw-r--r--includes/password/ParameterizedPassword.php119
-rw-r--r--includes/password/Password.php186
-rw-r--r--includes/password/PasswordError.php28
-rw-r--r--includes/password/PasswordFactory.php178
-rw-r--r--includes/password/Pbkdf2Password.php85
-rw-r--r--includes/poolcounter/PoolCounter.php173
-rw-r--r--includes/poolcounter/PoolCounterRedis.php417
-rw-r--r--includes/poolcounter/PoolCounterWork.php160
-rw-r--r--includes/poolcounter/PoolCounterWorkViaCallback.php92
-rw-r--r--includes/poolcounter/PoolWorkArticleView.php208
-rw-r--r--includes/profiler/Profiler.php637
-rw-r--r--includes/profiler/ProfilerMwprof.php256
-rw-r--r--includes/profiler/ProfilerSimple.php133
-rw-r--r--includes/profiler/ProfilerSimpleDB.php111
-rw-r--r--includes/profiler/ProfilerSimpleText.php6
-rw-r--r--includes/profiler/ProfilerSimpleTrace.php59
-rw-r--r--includes/profiler/ProfilerSimpleUDP.php17
-rw-r--r--includes/profiler/ProfilerStandard.php559
-rw-r--r--includes/profiler/ProfilerStub.php38
-rw-r--r--includes/rcfeed/IRCColourfulRCFeedFormatter.php49
-rw-r--r--includes/rcfeed/JSONRCFeedFormatter.php112
-rw-r--r--includes/rcfeed/MachineReadableRCFeedFormatter.php130
-rw-r--r--includes/rcfeed/RCFeedEngine.php33
-rw-r--r--includes/rcfeed/RCFeedFormatter.php27
-rw-r--r--includes/rcfeed/RedisPubSubFeedEngine.php62
-rw-r--r--includes/rcfeed/UDPRCFeedEngine.php26
-rw-r--r--includes/rcfeed/XMLRCFeedFormatter.php29
-rw-r--r--includes/resourceloader/DerivativeResourceLoaderContext.php202
-rw-r--r--includes/resourceloader/ResourceLoader.php670
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php49
-rw-r--r--includes/resourceloader/ResourceLoaderEditToolbarModule.php102
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php473
-rw-r--r--includes/resourceloader/ResourceLoaderFilePageModule.php2
-rw-r--r--includes/resourceloader/ResourceLoaderFilePath.php74
-rw-r--r--includes/resourceloader/ResourceLoaderLESSFunctions.php67
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageDataModule.php84
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageNamesModule.php79
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php221
-rw-r--r--includes/resourceloader/ResourceLoaderNoscriptModule.php6
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php12
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php412
-rw-r--r--includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php31
-rw-r--r--includes/resourceloader/ResourceLoaderUserGroupsModule.php21
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php18
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php16
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php10
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php110
-rw-r--r--includes/revisiondelete/RevDelArchiveItem.php105
-rw-r--r--includes/revisiondelete/RevDelArchiveList.php66
-rw-r--r--includes/revisiondelete/RevDelArchivedFileItem.php129
-rw-r--r--includes/revisiondelete/RevDelArchivedFileList.php56
-rw-r--r--includes/revisiondelete/RevDelArchivedRevisionItem.php53
-rw-r--r--includes/revisiondelete/RevDelFileItem.php237
-rw-r--r--includes/revisiondelete/RevDelFileList.php128
-rw-r--r--includes/revisiondelete/RevDelItem.php62
-rw-r--r--includes/revisiondelete/RevDelList.php (renamed from includes/revisiondelete/RevisionDeleteAbstracts.php)75
-rw-r--r--includes/revisiondelete/RevDelLogItem.php151
-rw-r--r--includes/revisiondelete/RevDelLogList.php103
-rw-r--r--includes/revisiondelete/RevDelRevisionItem.php187
-rw-r--r--includes/revisiondelete/RevDelRevisionList.php143
-rw-r--r--includes/revisiondelete/RevisionDelete.php964
-rw-r--r--includes/revisiondelete/RevisionDeleteUser.php38
-rw-r--r--includes/revisiondelete/RevisionDeleter.php37
-rw-r--r--includes/search/SearchDatabase.php57
-rw-r--r--includes/search/SearchEngine.php1116
-rw-r--r--includes/search/SearchHighlighter.php575
-rw-r--r--includes/search/SearchMssql.php110
-rw-r--r--includes/search/SearchMySQL.php122
-rw-r--r--includes/search/SearchOracle.php77
-rw-r--r--includes/search/SearchPostgres.php89
-rw-r--r--includes/search/SearchResult.php237
-rw-r--r--includes/search/SearchResultSet.php211
-rw-r--r--includes/search/SearchSqlite.php116
-rw-r--r--includes/site/MediaWikiSite.php44
-rw-r--r--includes/site/Site.php32
-rw-r--r--includes/site/SiteList.php88
-rw-r--r--includes/site/SiteSQLStore.php52
-rw-r--r--includes/site/SiteStore.php8
-rw-r--r--includes/skins/Skin.php (renamed from includes/Skin.php)489
-rw-r--r--includes/skins/SkinException.php29
-rw-r--r--includes/skins/SkinFactory.php214
-rw-r--r--includes/skins/SkinFallback.php36
-rw-r--r--includes/skins/SkinFallbackTemplate.php109
-rw-r--r--includes/skins/SkinTemplate.php (renamed from includes/SkinTemplate.php)511
-rw-r--r--includes/specialpage/ChangesListSpecialPage.php468
-rw-r--r--includes/specialpage/FormSpecialPage.php193
-rw-r--r--includes/specialpage/ImageQueryPage.php (renamed from includes/ImageQueryPage.php)15
-rw-r--r--includes/specialpage/IncludableSpecialPage.php39
-rw-r--r--includes/specialpage/PageQueryPage.php (renamed from includes/PageQueryPage.php)24
-rw-r--r--includes/specialpage/QueryPage.php (renamed from includes/QueryPage.php)318
-rw-r--r--includes/specialpage/RedirectSpecialPage.php209
-rw-r--r--includes/specialpage/SpecialPage.php663
-rw-r--r--includes/specialpage/SpecialPageFactory.php702
-rw-r--r--includes/specialpage/UnlistedSpecialPage.php37
-rw-r--r--includes/specialpage/WantedQueryPage.php130
-rw-r--r--includes/specials/SpecialActiveusers.php237
-rw-r--r--includes/specials/SpecialAllMessages.php (renamed from includes/specials/SpecialAllmessages.php)105
-rw-r--r--includes/specials/SpecialAllPages.php384
-rw-r--r--includes/specials/SpecialAllpages.php573
-rw-r--r--includes/specials/SpecialBlock.php141
-rw-r--r--includes/specials/SpecialBlockList.php65
-rw-r--r--includes/specials/SpecialBooksources.php30
-rw-r--r--includes/specials/SpecialBrokenRedirects.php4
-rw-r--r--includes/specials/SpecialCachedPage.php24
-rw-r--r--includes/specials/SpecialCategories.php72
-rw-r--r--includes/specials/SpecialChangeEmail.php255
-rw-r--r--includes/specials/SpecialChangePassword.php385
-rw-r--r--includes/specials/SpecialComparePages.php4
-rw-r--r--includes/specials/SpecialConfirmemail.php44
-rw-r--r--includes/specials/SpecialContributions.php254
-rw-r--r--includes/specials/SpecialCreateAccount.php56
-rw-r--r--includes/specials/SpecialDeletedContributions.php42
-rw-r--r--includes/specials/SpecialDiff.php61
-rw-r--r--includes/specials/SpecialEditWatchlist.php222
-rw-r--r--includes/specials/SpecialEmailuser.php60
-rw-r--r--includes/specials/SpecialExpandTemplates.php286
-rw-r--r--includes/specials/SpecialExport.php87
-rw-r--r--includes/specials/SpecialFewestrevisions.php2
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php12
-rw-r--r--includes/specials/SpecialFilepath.php8
-rw-r--r--includes/specials/SpecialImport.php164
-rw-r--r--includes/specials/SpecialJavaScriptTest.php33
-rw-r--r--includes/specials/SpecialLinkSearch.php114
-rw-r--r--includes/specials/SpecialListDuplicatedFiles.php113
-rw-r--r--includes/specials/SpecialListfiles.php223
-rw-r--r--includes/specials/SpecialListgrouprights.php200
-rw-r--r--includes/specials/SpecialListredirects.php11
-rw-r--r--includes/specials/SpecialListusers.php65
-rw-r--r--includes/specials/SpecialLockdb.php10
-rw-r--r--includes/specials/SpecialLog.php33
-rw-r--r--includes/specials/SpecialLonelypages.php52
-rw-r--r--includes/specials/SpecialMIMEsearch.php58
-rw-r--r--includes/specials/SpecialMediaStatistics.php325
-rw-r--r--includes/specials/SpecialMergeHistory.php105
-rw-r--r--includes/specials/SpecialMostinterwikis.php4
-rw-r--r--includes/specials/SpecialMostlinked.php6
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php1
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php11
-rw-r--r--includes/specials/SpecialMostrevisions.php1
-rw-r--r--includes/specials/SpecialMovepage.php98
-rw-r--r--includes/specials/SpecialMyLanguage.php93
-rw-r--r--includes/specials/SpecialMyRedirectPages.php114
-rw-r--r--includes/specials/SpecialNewimages.php37
-rw-r--r--includes/specials/SpecialNewpages.php68
-rw-r--r--includes/specials/SpecialPageLanguage.php195
-rw-r--r--includes/specials/SpecialPagesWithProp.php45
-rw-r--r--includes/specials/SpecialPasswordReset.php43
-rw-r--r--includes/specials/SpecialPermanentLink.php45
-rw-r--r--includes/specials/SpecialPreferences.php16
-rw-r--r--includes/specials/SpecialPrefixindex.php46
-rw-r--r--includes/specials/SpecialProtectedpages.php406
-rw-r--r--includes/specials/SpecialProtectedtitles.php17
-rw-r--r--includes/specials/SpecialRandomInCategory.php77
-rw-r--r--includes/specials/SpecialRandompage.php9
-rw-r--r--includes/specials/SpecialRecentchanges.php562
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php61
-rw-r--r--includes/specials/SpecialRedirect.php72
-rw-r--r--includes/specials/SpecialResetTokens.php12
-rw-r--r--includes/specials/SpecialRevisiondelete.php286
-rw-r--r--includes/specials/SpecialRunJobs.php112
-rw-r--r--includes/specials/SpecialSearch.php661
-rw-r--r--includes/specials/SpecialShortpages.php21
-rw-r--r--includes/specials/SpecialSpecialpages.php27
-rw-r--r--includes/specials/SpecialStatistics.php168
-rw-r--r--includes/specials/SpecialTags.php10
-rw-r--r--includes/specials/SpecialTrackingCategories.php148
-rw-r--r--includes/specials/SpecialUnblock.php38
-rw-r--r--includes/specials/SpecialUncategorizedimages.php13
-rw-r--r--includes/specials/SpecialUncategorizedpages.php26
-rw-r--r--includes/specials/SpecialUndelete.php393
-rw-r--r--includes/specials/SpecialUnlockdb.php11
-rw-r--r--includes/specials/SpecialUnusedcategories.php27
-rw-r--r--includes/specials/SpecialUnusedimages.php25
-rw-r--r--includes/specials/SpecialUnusedtemplates.php18
-rw-r--r--includes/specials/SpecialUnwatchedpages.php28
-rw-r--r--includes/specials/SpecialUpload.php254
-rw-r--r--includes/specials/SpecialUploadStash.php121
-rw-r--r--includes/specials/SpecialUserlogin.php455
-rw-r--r--includes/specials/SpecialUserlogout.php1
-rw-r--r--includes/specials/SpecialUserrights.php165
-rw-r--r--includes/specials/SpecialVersion.php738
-rw-r--r--includes/specials/SpecialWantedcategories.php68
-rw-r--r--includes/specials/SpecialWantedfiles.php77
-rw-r--r--includes/specials/SpecialWantedpages.php7
-rw-r--r--includes/specials/SpecialWantedtemplates.php22
-rw-r--r--includes/specials/SpecialWatchlist.php652
-rw-r--r--includes/specials/SpecialWhatlinkshere.php216
-rw-r--r--includes/specials/SpecialWithoutinterwiki.php34
-rw-r--r--includes/templates/NoLocalSettings.php32
-rw-r--r--includes/templates/Usercreate.php80
-rw-r--r--includes/templates/Userlogin.php45
-rw-r--r--includes/title/MalformedTitleException.php32
-rw-r--r--includes/title/MediaWikiPageLinkRenderer.php131
-rw-r--r--includes/title/MediaWikiTitleCodec.php400
-rw-r--r--includes/title/PageLinkRenderer.php67
-rw-r--r--includes/title/TitleFormatter.php90
-rw-r--r--includes/title/TitleParser.php47
-rw-r--r--includes/title/TitleValue.php161
-rw-r--r--includes/upload/UploadBase.php462
-rw-r--r--includes/upload/UploadFromChunks.php110
-rw-r--r--includes/upload/UploadFromFile.php9
-rw-r--r--includes/upload/UploadFromStash.php46
-rw-r--r--includes/upload/UploadFromUrl.php72
-rw-r--r--includes/upload/UploadStash.php286
-rw-r--r--includes/utils/ArrayUtils.php187
-rw-r--r--includes/utils/Cdb.php (renamed from includes/Cdb.php)133
-rw-r--r--includes/utils/CdbDBA.php75
-rw-r--r--includes/utils/CdbPHP.php (renamed from includes/Cdb_PHP.php)141
-rw-r--r--includes/utils/IP.php (renamed from includes/IP.php)297
-rw-r--r--includes/utils/MWCryptHKDF.php332
-rw-r--r--includes/utils/MWCryptRand.php (renamed from includes/MWCryptRand.php)83
-rw-r--r--includes/utils/MWFunction.php (renamed from includes/MWFunction.php)14
-rw-r--r--includes/utils/README9
-rw-r--r--includes/utils/StringUtils.php (renamed from includes/StringUtils.php)84
-rw-r--r--includes/utils/UIDGenerator.php (renamed from includes/UIDGenerator.php)208
-rw-r--r--includes/utils/ZipDirectoryReader.php (renamed from includes/ZipDirectoryReader.php)94
1020 files changed, 100653 insertions, 77151 deletions
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php
index c9ca1283..9bc92be9 100644
--- a/includes/AjaxDispatcher.php
+++ b/includes/AjaxDispatcher.php
@@ -48,14 +48,21 @@ class AjaxDispatcher {
private $args;
/**
+ * @var Config
+ */
+ private $config;
+
+ /**
* Load up our object with user supplied data
*/
- function __construct() {
+ function __construct( Config $config ) {
wfProfileIn( __METHOD__ );
+ $this->config = $config;
+
$this->mode = "";
- if ( ! empty( $_GET["rs"] ) ) {
+ if ( !empty( $_GET["rs"] ) ) {
$this->mode = "get";
}
@@ -66,7 +73,7 @@ class AjaxDispatcher {
switch ( $this->mode ) {
case 'get':
$this->func_name = isset( $_GET["rs"] ) ? $_GET["rs"] : '';
- if ( ! empty( $_GET["rsargs"] ) ) {
+ if ( !empty( $_GET["rsargs"] ) ) {
$this->args = $_GET["rsargs"];
} else {
$this->args = array();
@@ -74,7 +81,7 @@ class AjaxDispatcher {
break;
case 'post':
$this->func_name = isset( $_POST["rs"] ) ? $_POST["rs"] : '';
- if ( ! empty( $_POST["rsargs"] ) ) {
+ if ( !empty( $_POST["rsargs"] ) ) {
$this->args = $_POST["rsargs"];
} else {
$this->args = array();
@@ -95,17 +102,17 @@ class AjaxDispatcher {
* BEWARE! Data are passed as they have been supplied by the user,
* they should be carefully handled in the function processing the
* request.
+ *
+ * @param User $user
*/
- function performAction() {
- global $wgAjaxExportList, $wgUser;
-
+ function performAction( User $user ) {
if ( empty( $this->mode ) ) {
return;
}
wfProfileIn( __METHOD__ );
- if ( ! in_array( $this->func_name, $wgAjaxExportList ) ) {
+ if ( !in_array( $this->func_name, $this->config->get( 'AjaxExportList' ) ) ) {
wfDebug( __METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n" );
wfHttpError(
@@ -113,7 +120,7 @@ class AjaxDispatcher {
'Bad Request',
"unknown function " . $this->func_name
);
- } elseif ( !User::isEveryoneAllowed( 'read' ) && !$wgUser->isAllowed( 'read' ) ) {
+ } elseif ( !User::isEveryoneAllowed( 'read' ) && !$user->isAllowed( 'read' ) ) {
wfHttpError(
403,
'Forbidden',
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index d5536529..8e9f490f 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -28,7 +28,6 @@
* @ingroup Ajax
*/
class AjaxResponse {
-
/**
* Number of seconds to get the response cached by a proxy
* @var int $mCacheDuration
@@ -49,7 +48,7 @@ class AjaxResponse {
/**
* Date for the HTTP header Last-modified
- * @var string|false $mLastModified
+ * @var string|bool $mLastModified
*/
private $mLastModified;
@@ -72,11 +71,18 @@ class AjaxResponse {
private $mText;
/**
- * @param $text string|null
+ * @var Config
*/
- function __construct( $text = null ) {
+ private $mConfig;
+
+ /**
+ * @param string|null $text
+ * @param Config|null $config
+ */
+ function __construct( $text = null, Config $config = null ) {
$this->mCacheDuration = null;
$this->mVary = null;
+ $this->mConfig = $config ?: ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
$this->mDisabled = false;
$this->mText = '';
@@ -91,7 +97,7 @@ class AjaxResponse {
/**
* Set the number of seconds to get the response cached by a proxy
- * @param $duration int
+ * @param int $duration
*/
function setCacheDuration( $duration ) {
$this->mCacheDuration = $duration;
@@ -99,7 +105,7 @@ class AjaxResponse {
/**
* Set the HTTP Vary header
- * @param $vary string
+ * @param string $vary
*/
function setVary( $vary ) {
$this->mVary = $vary;
@@ -107,7 +113,7 @@ class AjaxResponse {
/**
* Set the HTTP response code
- * @param $code string
+ * @param string $code
*/
function setResponseCode( $code ) {
$this->mResponseCode = $code;
@@ -115,7 +121,7 @@ class AjaxResponse {
/**
* Set the HTTP header Content-Type
- * @param $type string
+ * @param string $type
*/
function setContentType( $type ) {
$this->mContentType = $type;
@@ -130,10 +136,10 @@ class AjaxResponse {
/**
* Add content to the response
- * @param $text string
+ * @param string $text
*/
function addText( $text ) {
- if ( ! $this->mDisabled && $text ) {
+ if ( !$this->mDisabled && $text ) {
$this->mText .= $text;
}
}
@@ -142,7 +148,7 @@ class AjaxResponse {
* Output text
*/
function printText() {
- if ( ! $this->mDisabled ) {
+ if ( !$this->mDisabled ) {
print $this->mText;
}
}
@@ -151,8 +157,6 @@ class AjaxResponse {
* Construct the header and output it
*/
function sendHeaders() {
- global $wgUseSquid, $wgUseESI;
-
if ( $this->mResponseCode ) {
$n = preg_replace( '/^ *(\d+)/', '\1', $this->mResponseCode );
header( "Status: " . $this->mResponseCode, true, (int)$n );
@@ -171,12 +175,12 @@ class AjaxResponse {
# and tell the client to always check with the squid. Otherwise,
# tell the client to use a cached copy, without a way to purge it.
- if ( $wgUseSquid ) {
+ if ( $this->mConfig->get( 'UseSquid' ) ) {
# Expect explicit purge of the proxy cache, but require end user agents
# to revalidate against the proxy on each visit.
# Surrogate-Control controls our Squid, Cache-Control downstream caches
- if ( $wgUseESI ) {
+ if ( $this->mConfig->get( 'UseESI' ) ) {
header( 'Surrogate-Control: max-age=' . $this->mCacheDuration . ', content="ESI/1.0"' );
header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
} else {
@@ -184,10 +188,10 @@ class AjaxResponse {
}
} else {
-
# Let the client do the caching. Cache is not purged.
header ( "Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT" );
- header ( "Cache-Control: s-maxage={$this->mCacheDuration},public,max-age={$this->mCacheDuration}" );
+ header ( "Cache-Control: s-maxage={$this->mCacheDuration}," .
+ "public,max-age={$this->mCacheDuration}" );
}
} else {
@@ -207,7 +211,7 @@ class AjaxResponse {
* possible. If successful, the AjaxResponse is disabled so that
* any future call to AjaxResponse::printText() have no effect.
*
- * @param $timestamp string
+ * @param string $timestamp
* @return bool Returns true if the response code was set to 304 Not Modified.
*/
function checkLastModified( $timestamp ) {
@@ -215,17 +219,12 @@ class AjaxResponse {
$fname = 'AjaxResponse::checkLastModified';
if ( !$timestamp || $timestamp == '19700101000000' ) {
- wfDebug( "$fname: CACHE DISABLED, NO TIMESTAMP\n" );
+ wfDebug( "$fname: CACHE DISABLED, NO TIMESTAMP\n", 'log' );
return false;
}
if ( !$wgCachePages ) {
- wfDebug( "$fname: CACHE DISABLED\n", false );
- return false;
- }
-
- if ( $wgUser->getOption( 'nocache' ) ) {
- wfDebug( "$fname: USER DISABLED CACHE\n", false );
+ wfDebug( "$fname: CACHE DISABLED\n", 'log' );
return false;
}
@@ -239,32 +238,37 @@ class AjaxResponse {
$modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
$modsinceTime = strtotime( $modsince );
$ismodsince = wfTimestamp( TS_MW, $modsinceTime ? $modsinceTime : 1 );
- wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", false );
- wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", false );
+ wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", 'log' );
+ wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", 'log' );
- if ( ( $ismodsince >= $timestamp ) && $wgUser->validateCache( $ismodsince ) && $ismodsince >= $wgCacheEpoch ) {
+ if ( ( $ismodsince >= $timestamp )
+ && $wgUser->validateCache( $ismodsince ) &&
+ $ismodsince >= $wgCacheEpoch
+ ) {
ini_set( 'zlib.output_compression', 0 );
$this->setResponseCode( "304 Not Modified" );
$this->disable();
$this->mLastModified = $lastmod;
- wfDebug( "$fname: CACHED client: $ismodsince ; user: {$wgUser->getTouched()} ; page: $timestamp ; site $wgCacheEpoch\n", false );
+ wfDebug( "$fname: CACHED client: $ismodsince ; user: {$wgUser->getTouched()} ; " .
+ "page: $timestamp ; site $wgCacheEpoch\n", 'log' );
return true;
} else {
- wfDebug( "$fname: READY client: $ismodsince ; user: {$wgUser->getTouched()} ; page: $timestamp ; site $wgCacheEpoch\n", false );
+ wfDebug( "$fname: READY client: $ismodsince ; user: {$wgUser->getTouched()} ; " .
+ "page: $timestamp ; site $wgCacheEpoch\n", 'log' );
$this->mLastModified = $lastmod;
}
} else {
- wfDebug( "$fname: client did not send If-Modified-Since header\n", false );
+ wfDebug( "$fname: client did not send If-Modified-Since header\n", 'log' );
$this->mLastModified = $lastmod;
}
return false;
}
/**
- * @param $mckey string
- * @param $touched int
+ * @param string $mckey
+ * @param int $touched
* @return bool
*/
function loadFromMemcached( $mckey, $touched ) {
@@ -291,8 +295,8 @@ class AjaxResponse {
}
/**
- * @param $mckey string
- * @param $expiry int
+ * @param string $mckey
+ * @param int $expiry
* @return bool
*/
function storeInMemcached( $mckey, $expiry = 86400 ) {
diff --git a/includes/ArrayUtils.php b/includes/ArrayUtils.php
deleted file mode 100644
index 985271f7..00000000
--- a/includes/ArrayUtils.php
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-
-class ArrayUtils {
- /**
- * Sort the given array in a pseudo-random order which depends only on the
- * given key and each element value. This is typically used for load
- * balancing between servers each with a local cache.
- *
- * Keys are preserved. The input array is modified in place.
- *
- * Note: Benchmarking on PHP 5.3 and 5.4 indicates that for small
- * strings, md5() is only 10% slower than hash('joaat',...) etc.,
- * since the function call overhead dominates. So there's not much
- * justification for breaking compatibility with installations
- * compiled with ./configure --disable-hash.
- *
- * @param $array The array to sort
- * @param $key The string key
- * @param $separator A separator used to delimit the array elements and the
- * key. This can be chosen to provide backwards compatibility with
- * various consistent hash implementations that existed before this
- * function was introduced.
- */
- public static function consistentHashSort( &$array, $key, $separator = "\000" ) {
- $hashes = array();
- foreach ( $array as $elt ) {
- $hashes[$elt] = md5( $elt . $separator . $key );
- }
- uasort( $array, function ( $a, $b ) use ( $hashes ) {
- return strcmp( $hashes[$a], $hashes[$b] );
- } );
- }
-
- /**
- * Given an array of non-normalised probabilities, this function will select
- * an element and return the appropriate key
- *
- * @param $weights array
- *
- * @return bool|int|string
- */
- public static function pickRandom( $weights ) {
- if ( !is_array( $weights ) || count( $weights ) == 0 ) {
- return false;
- }
-
- $sum = array_sum( $weights );
- if ( $sum == 0 ) {
- # No loads on any of them
- # In previous versions, this triggered an unweighted random selection,
- # but this feature has been removed as of April 2006 to allow for strict
- # separation of query groups.
- return false;
- }
- $max = mt_getrandmax();
- $rand = mt_rand( 0, $max ) / $max * $sum;
-
- $sum = 0;
- foreach ( $weights as $i => $w ) {
- $sum += $w;
- # Do not return keys if they have 0 weight.
- # Note that the "all 0 weight" case is handed above
- if ( $w > 0 && $sum >= $rand ) {
- break;
- }
- }
- return $i;
- }
-}
diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php
index 84cf3d5e..45ad4d1b 100644
--- a/includes/AuthPlugin.php
+++ b/includes/AuthPlugin.php
@@ -3,7 +3,7 @@
* Authentication plugin interface
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -34,7 +34,6 @@
* someone logs in who can be authenticated externally.
*/
class AuthPlugin {
-
/**
* @var string
*/
@@ -46,7 +45,7 @@ class AuthPlugin {
* you might need to munge it (for instance, for lowercase initial
* letters).
*
- * @param string $username username.
+ * @param string $username Username.
* @return bool
*/
public function userExists( $username ) {
@@ -60,8 +59,8 @@ class AuthPlugin {
* you might need to munge it (for instance, for lowercase initial
* letters).
*
- * @param string $username username.
- * @param string $password user password.
+ * @param string $username Username.
+ * @param string $password User password.
* @return bool
*/
public function authenticate( $username, $password ) {
@@ -72,7 +71,7 @@ class AuthPlugin {
/**
* Modify options in the login template.
*
- * @param $template UserLoginTemplate object.
+ * @param UserLoginTemplate $template
* @param string $type 'signup' or 'login'. Added in 1.16.
*/
public function modifyUITemplate( &$template, &$type ) {
@@ -83,7 +82,7 @@ class AuthPlugin {
/**
* Set the domain this plugin is supposed to use when authenticating.
*
- * @param string $domain authentication domain.
+ * @param string $domain Authentication domain.
*/
public function setDomain( $domain ) {
$this->domain = $domain;
@@ -105,7 +104,7 @@ class AuthPlugin {
/**
* Check to see if the specific domain is a valid domain.
*
- * @param string $domain authentication domain.
+ * @param string $domain Authentication domain.
* @return bool
*/
public function validDomain( $domain ) {
@@ -121,7 +120,7 @@ class AuthPlugin {
* The User object is passed by reference so it can be modified; don't
* forget the & on your function declaration.
*
- * @param $user User object
+ * @param User $user
* @return bool
*/
public function updateUser( &$user ) {
@@ -140,7 +139,7 @@ class AuthPlugin {
*
* This is just a question, and shouldn't perform any actions.
*
- * @return Boolean
+ * @return bool
*/
public function autoCreate() {
return false;
@@ -151,9 +150,9 @@ class AuthPlugin {
* and use the same keys. 'Realname' 'Emailaddress' and 'Nickname'
* all reference this.
*
- * @param $prop string
+ * @param string $prop
*
- * @return Boolean
+ * @return bool
*/
public function allowPropChange( $prop = '' ) {
if ( $prop == 'realname' && is_callable( array( $this, 'allowRealNameChange' ) ) ) {
@@ -193,8 +192,8 @@ class AuthPlugin {
*
* Return true if successful.
*
- * @param $user User object.
- * @param string $password password.
+ * @param User $user
+ * @param string $password Password.
* @return bool
*/
public function setPassword( $user, $password ) {
@@ -205,8 +204,8 @@ class AuthPlugin {
* Update user information in the external authentication database.
* Return true if successful.
*
- * @param $user User object.
- * @return Boolean
+ * @param User $user
+ * @return bool
*/
public function updateExternalDB( $user ) {
return true;
@@ -216,10 +215,10 @@ class AuthPlugin {
* Update user groups in the external authentication database.
* Return true if successful.
*
- * @param $user User object.
- * @param $addgroups Groups to add.
- * @param $delgroups Groups to remove.
- * @return Boolean
+ * @param User $user
+ * @param array $addgroups Groups to add.
+ * @param array $delgroups Groups to remove.
+ * @return bool
*/
public function updateExternalDBGroups( $user, $addgroups, $delgroups = array() ) {
return true;
@@ -228,7 +227,7 @@ class AuthPlugin {
/**
* Check to see if external accounts can be created.
* Return true if external accounts can be created.
- * @return Boolean
+ * @return bool
*/
public function canCreateAccounts() {
return false;
@@ -238,11 +237,11 @@ class AuthPlugin {
* Add a user to the external authentication database.
* Return true if successful.
*
- * @param $user User: only the name should be assumed valid at this point
- * @param $password String
- * @param $email String
- * @param $realname String
- * @return Boolean
+ * @param User $user Only the name should be assumed valid at this point
+ * @param string $password
+ * @param string $email
+ * @param string $realname
+ * @return bool
*/
public function addUser( $user, $password, $email = '', $realname = '' ) {
return true;
@@ -254,7 +253,7 @@ class AuthPlugin {
*
* This is just a question, and shouldn't perform any actions.
*
- * @return Boolean
+ * @return bool
*/
public function strict() {
return false;
@@ -264,8 +263,8 @@ class AuthPlugin {
* Check if a user should authenticate locally if the global authentication fails.
* If either this or strict() returns true, local authentication is not used.
*
- * @param string $username username.
- * @return Boolean
+ * @param string $username Username.
+ * @return bool
*/
public function strictUserAuth( $username ) {
return false;
@@ -279,8 +278,8 @@ class AuthPlugin {
* The User object is passed by reference so it can be modified; don't
* forget the & on your function declaration.
*
- * @param $user User object.
- * @param $autocreate Boolean: True if user is being autocreated on login
+ * @param User $user
+ * @param bool $autocreate True if user is being autocreated on login
*/
public function initUser( &$user, $autocreate = false ) {
# Override this to do something.
@@ -289,7 +288,7 @@ class AuthPlugin {
/**
* If you want to munge the case of an account name before the final
* check, now is your chance.
- * @param $username string
+ * @param string $username
* @return string
*/
public function getCanonicalName( $username ) {
@@ -299,7 +298,7 @@ class AuthPlugin {
/**
* Get an instance of a User object
*
- * @param $user User
+ * @param User $user
*
* @return AuthPluginUser
*/
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 0706fe3f..6b0daa14 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -29,49 +29,32 @@ global $wgAutoloadLocalClasses;
$wgAutoloadLocalClasses = array(
# Includes
- 'Action' => 'includes/Action.php',
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxResponse' => 'includes/AjaxResponse.php',
- 'AlphabeticPager' => 'includes/Pager.php',
- 'ArrayUtils' => 'includes/ArrayUtils.php',
- 'Article' => 'includes/Article.php',
'AtomFeed' => 'includes/Feed.php',
'AuthPlugin' => 'includes/AuthPlugin.php',
'AuthPluginUser' => 'includes/AuthPlugin.php',
'Autopromote' => 'includes/Autopromote.php',
- 'BadTitleError' => 'includes/Exception.php',
- 'BaseTemplate' => 'includes/SkinTemplate.php',
'Block' => 'includes/Block.php',
+ 'BloomCache' => 'includes/cache/bloom/BloomCache.php',
+ 'BloomCacheRedis' => 'includes/cache/bloom/BloomCacheRedis.php',
+ 'BloomFilterTitleHasLogs' => 'includes/cache/bloom/BloomFilters.php',
'CacheHelper' => 'includes/CacheHelper.php',
'Category' => 'includes/Category.php',
- 'Categoryfinder' => 'includes/Categoryfinder.php',
- 'CategoryPage' => 'includes/CategoryPage.php',
+ 'CategoryFinder' => 'includes/CategoryFinder.php',
'CategoryViewer' => 'includes/CategoryViewer.php',
- 'CdbFunctions' => 'includes/Cdb_PHP.php',
- 'CdbReader' => 'includes/Cdb.php',
- 'CdbReader_DBA' => 'includes/Cdb.php',
- 'CdbReader_PHP' => 'includes/Cdb_PHP.php',
- 'CdbWriter' => 'includes/Cdb.php',
- 'CdbWriter_DBA' => 'includes/Cdb.php',
- 'CdbWriter_PHP' => 'includes/Cdb_PHP.php',
- 'ChangesFeed' => 'includes/ChangesFeed.php',
'ChangeTags' => 'includes/ChangeTags.php',
'ChannelFeed' => 'includes/Feed.php',
'Collation' => 'includes/Collation.php',
+ 'CollationCkb' => 'includes/Collation.php',
+ 'CollationEt' => 'includes/Collation.php',
'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
- 'ConfEditor' => 'includes/ConfEditor.php',
- 'ConfEditorParseError' => 'includes/ConfEditor.php',
- 'ConfEditorToken' => 'includes/ConfEditor.php',
'Cookie' => 'includes/Cookie.php',
'CookieJar' => 'includes/Cookie.php',
'CurlHttpRequest' => 'includes/HttpFunctions.php',
- 'DeferrableUpdate' => 'includes/DeferredUpdates.php',
- 'DeferredUpdates' => 'includes/DeferredUpdates.php',
- 'MWCallableUpdate' => 'includes/CallableUpdate.php',
'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php',
'DerivativeRequest' => 'includes/WebRequest.php',
'DiffHistoryBlob' => 'includes/HistoryBlob.php',
- 'DoubleReplacer' => 'includes/StringUtils.php',
'DummyLinker' => 'includes/Linker.php',
'Dump7ZipOutput' => 'includes/Export.php',
'DumpBZip2Output' => 'includes/Export.php',
@@ -85,126 +68,83 @@ $wgAutoloadLocalClasses = array(
'DumpOutput' => 'includes/Export.php',
'DumpPipeOutput' => 'includes/Export.php',
'EditPage' => 'includes/EditPage.php',
- 'EmailNotification' => 'includes/UserMailer.php',
- 'ErrorPageError' => 'includes/Exception.php',
- 'ExplodeIterator' => 'includes/StringUtils.php',
- 'FakeTitle' => 'includes/FakeTitle.php',
+ 'EmptyBloomCache' => 'includes/cache/bloom/BloomCache.php',
'Fallback' => 'includes/Fallback.php',
- 'FatalError' => 'includes/Exception.php',
'FauxRequest' => 'includes/WebRequest.php',
'FauxResponse' => 'includes/WebResponse.php',
'FeedItem' => 'includes/Feed.php',
'FeedUtils' => 'includes/FeedUtils.php',
'FileDeleteForm' => 'includes/FileDeleteForm.php',
'ForkController' => 'includes/ForkController.php',
- 'FormlessAction' => 'includes/Action.php',
- 'FormAction' => 'includes/Action.php',
'FormOptions' => 'includes/FormOptions.php',
- 'FormSpecialPage' => 'includes/SpecialPage.php',
'GitInfo' => 'includes/GitInfo.php',
- 'HashRing' => 'includes/HashRing.php',
- 'HashtableReplacer' => 'includes/StringUtils.php',
'HistoryBlob' => 'includes/HistoryBlob.php',
'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
'HistoryBlobStub' => 'includes/HistoryBlob.php',
'Hooks' => 'includes/Hooks.php',
'Html' => 'includes/Html.php',
'HtmlFormatter' => 'includes/HtmlFormatter.php',
- 'HTMLApiField' => 'includes/HTMLForm.php',
- 'HTMLButtonField' => 'includes/HTMLForm.php',
- 'HTMLCheckField' => 'includes/HTMLForm.php',
- 'HTMLCheckMatrix' => 'includes/HTMLForm.php',
- 'HTMLEditTools' => 'includes/HTMLForm.php',
- 'HTMLFloatField' => 'includes/HTMLForm.php',
- 'HTMLForm' => 'includes/HTMLForm.php',
- 'HTMLFormField' => 'includes/HTMLForm.php',
- 'HTMLFormFieldRequiredOptionsException' => 'includes/HTMLForm.php',
- 'HTMLHiddenField' => 'includes/HTMLForm.php',
- 'HTMLInfoField' => 'includes/HTMLForm.php',
- 'HTMLIntField' => 'includes/HTMLForm.php',
- 'HTMLNestedFilterable' => 'includes/HTMLForm.php',
- 'HTMLMultiSelectField' => 'includes/HTMLForm.php',
- 'HTMLRadioField' => 'includes/HTMLForm.php',
- 'HTMLSelectAndOtherField' => 'includes/HTMLForm.php',
- 'HTMLSelectField' => 'includes/HTMLForm.php',
- 'HTMLSelectOrOtherField' => 'includes/HTMLForm.php',
- 'HTMLSubmitField' => 'includes/HTMLForm.php',
- 'HTMLTextAreaField' => 'includes/HTMLForm.php',
- 'HTMLTextField' => 'includes/HTMLForm.php',
+ 'HTMLApiField' => 'includes/htmlform/HTMLApiField.php',
+ 'HTMLAutoCompleteSelectField' => 'includes/htmlform/HTMLAutoCompleteSelectField.php',
+ 'HTMLButtonField' => 'includes/htmlform/HTMLButtonField.php',
+ 'HTMLCheckField' => 'includes/htmlform/HTMLCheckField.php',
+ 'HTMLCheckMatrix' => 'includes/htmlform/HTMLCheckMatrix.php',
+ 'HTMLFormFieldCloner' => 'includes/htmlform/HTMLFormFieldCloner.php',
+ 'HTMLEditTools' => 'includes/htmlform/HTMLEditTools.php',
+ 'HTMLFloatField' => 'includes/htmlform/HTMLFloatField.php',
+ 'HTMLForm' => 'includes/htmlform/HTMLForm.php',
+ 'HTMLFormField' => 'includes/htmlform/HTMLFormField.php',
+ 'HTMLFormFieldRequiredOptionsException' =>
+ 'includes/htmlform/HTMLFormFieldRequiredOptionsException.php',
+ 'HTMLHiddenField' => 'includes/htmlform/HTMLHiddenField.php',
+ 'HTMLInfoField' => 'includes/htmlform/HTMLInfoField.php',
+ 'HTMLIntField' => 'includes/htmlform/HTMLIntField.php',
+ 'HTMLNestedFilterable' => 'includes/htmlform/HTMLNestedFilterable.php',
+ 'HTMLMultiSelectField' => 'includes/htmlform/HTMLMultiSelectField.php',
+ 'HTMLRadioField' => 'includes/htmlform/HTMLRadioField.php',
+ 'HTMLSelectAndOtherField' => 'includes/htmlform/HTMLSelectAndOtherField.php',
+ 'HTMLSelectField' => 'includes/htmlform/HTMLSelectField.php',
+ 'HTMLSelectLimitField' => 'includes/htmlform/HTMLSelectLimitField.php',
+ 'HTMLSelectOrOtherField' => 'includes/htmlform/HTMLSelectOrOtherField.php',
+ 'HTMLSubmitField' => 'includes/htmlform/HTMLSubmitField.php',
+ 'HTMLTextAreaField' => 'includes/htmlform/HTMLTextAreaField.php',
+ 'HTMLTextField' => 'includes/htmlform/HTMLTextField.php',
'Http' => 'includes/HttpFunctions.php',
- 'HttpError' => 'includes/Exception.php',
- 'ICacheHelper' => 'includes/CacheHelper.php',
'IcuCollation' => 'includes/Collation.php',
'IdentityCollation' => 'includes/Collation.php',
- 'ImageHistoryList' => 'includes/ImagePage.php',
- 'ImageHistoryPseudoPager' => 'includes/ImagePage.php',
- 'ImagePage' => 'includes/ImagePage.php',
- 'ImageQueryPage' => 'includes/ImageQueryPage.php',
'ImportStreamSource' => 'includes/Import.php',
'ImportStringSource' => 'includes/Import.php',
- 'IncludableSpecialPage' => 'includes/SpecialPage.php',
- 'IndexPager' => 'includes/Pager.php',
'Interwiki' => 'includes/interwiki/Interwiki.php',
- 'IP' => 'includes/IP.php',
- 'LCStore' => 'includes/cache/LocalisationCache.php',
- 'LCStore_Accel' => 'includes/cache/LocalisationCache.php',
- 'LCStore_CDB' => 'includes/cache/LocalisationCache.php',
- 'LCStore_DB' => 'includes/cache/LocalisationCache.php',
- 'LCStore_Null' => 'includes/cache/LocalisationCache.php',
'License' => 'includes/Licenses.php',
'Licenses' => 'includes/Licenses.php',
'Linker' => 'includes/Linker.php',
'LinkFilter' => 'includes/LinkFilter.php',
- 'LinksUpdate' => 'includes/LinksUpdate.php',
- 'LinksDeletionUpdate' => 'includes/LinksUpdate.php',
- 'LocalisationCache' => 'includes/cache/LocalisationCache.php',
- 'LocalisationCache_BulkLoad' => 'includes/cache/LocalisationCache.php',
'MagicWord' => 'includes/MagicWord.php',
'MagicWordArray' => 'includes/MagicWord.php',
- 'MailAddress' => 'includes/UserMailer.php',
- 'MappedIterator' => 'includes/MappedIterator.php',
- 'MediaWiki' => 'includes/Wiki.php',
- 'MediaWiki_I18N' => 'includes/SkinTemplate.php',
+ 'MediaWiki' => 'includes/MediaWiki.php',
+ 'MediaWikiVersionFetcher' => 'includes/MediaWikiVersionFetcher.php',
'Message' => 'includes/Message.php',
'MessageBlobStore' => 'includes/MessageBlobStore.php',
'MimeMagic' => 'includes/MimeMagic.php',
- 'MWCryptRand' => 'includes/MWCryptRand.php',
- 'MWException' => 'includes/Exception.php',
- 'MWExceptionHandler' => 'includes/Exception.php',
- 'MWFunction' => 'includes/MWFunction.php',
+ 'MovePage' => 'includes/MovePage.php',
'MWHookException' => 'includes/Hooks.php',
'MWHttpRequest' => 'includes/HttpFunctions.php',
- 'MWInit' => 'includes/Init.php',
- 'MWNamespace' => 'includes/Namespace.php',
+ 'MWNamespace' => 'includes/MWNamespace.php',
'OutputPage' => 'includes/OutputPage.php',
- 'Page' => 'includes/WikiPage.php',
- 'PageQueryPage' => 'includes/PageQueryPage.php',
- 'Pager' => 'includes/Pager.php',
- 'PasswordError' => 'includes/User.php',
'PathRouter' => 'includes/PathRouter.php',
'PathRouterPatternReplacer' => 'includes/PathRouter.php',
- 'PermissionsError' => 'includes/Exception.php',
'PhpHttpRequest' => 'includes/HttpFunctions.php',
- 'PoolCounter' => 'includes/PoolCounter.php',
- 'PoolCounter_Stub' => 'includes/PoolCounter.php',
- 'PoolCounterWork' => 'includes/PoolCounter.php',
- 'PoolCounterWorkViaCallback' => 'includes/PoolCounter.php',
- 'PoolWorkArticleView' => 'includes/WikiPage.php',
+ 'PoolCounter' => 'includes/poolcounter/PoolCounter.php',
+ 'PoolCounter_Stub' => 'includes/poolcounter/PoolCounter.php',
+ 'PoolCounterRedis' => 'includes/poolcounter/PoolCounterRedis.php',
+ 'PoolCounterWork' => 'includes/poolcounter/PoolCounterWork.php',
+ 'PoolCounterWorkViaCallback' => 'includes/poolcounter/PoolCounterWorkViaCallback.php',
+ 'PoolWorkArticleView' => 'includes/poolcounter/PoolWorkArticleView.php',
'Preferences' => 'includes/Preferences.php',
'PreferencesForm' => 'includes/Preferences.php',
'PrefixSearch' => 'includes/PrefixSearch.php',
'ProtectionForm' => 'includes/ProtectionForm.php',
- 'QueryPage' => 'includes/QueryPage.php',
- 'QuickTemplate' => 'includes/SkinTemplate.php',
'RawMessage' => 'includes/Message.php',
- 'RdfMetaData' => 'includes/Metadata.php',
- 'ReadOnlyError' => 'includes/Exception.php',
- 'RedirectSpecialArticle' => 'includes/SpecialPage.php',
- 'RedirectSpecialPage' => 'includes/SpecialPage.php',
- 'RegexlikeReplacer' => 'includes/StringUtils.php',
- 'ReplacementArray' => 'includes/StringUtils.php',
- 'Replacer' => 'includes/StringUtils.php',
- 'ReverseChronologicalPager' => 'includes/Pager.php',
'RevisionItem' => 'includes/RevisionList.php',
'RevisionItemBase' => 'includes/RevisionList.php',
'RevisionListBase' => 'includes/RevisionList.php',
@@ -212,125 +152,72 @@ $wgAutoloadLocalClasses = array(
'RevisionList' => 'includes/RevisionList.php',
'RSSFeed' => 'includes/Feed.php',
'Sanitizer' => 'includes/Sanitizer.php',
- 'DataUpdate' => 'includes/DataUpdate.php',
- 'SqlDataUpdate' => 'includes/SqlDataUpdate.php',
- 'ScopedCallback' => 'includes/ScopedCallback.php',
- 'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php',
'SiteConfiguration' => 'includes/SiteConfiguration.php',
'SiteStats' => 'includes/SiteStats.php',
'SiteStatsInit' => 'includes/SiteStats.php',
- 'SiteStatsUpdate' => 'includes/SiteStats.php',
- 'Skin' => 'includes/Skin.php',
- 'SkinTemplate' => 'includes/SkinTemplate.php',
- 'SpecialCreateAccount' => 'includes/SpecialPage.php',
- 'SpecialListAdmins' => 'includes/SpecialPage.php',
- 'SpecialListBots' => 'includes/SpecialPage.php',
- 'SpecialMycontributions' => 'includes/SpecialPage.php',
- 'SpecialMypage' => 'includes/SpecialPage.php',
- 'SpecialMytalk' => 'includes/SpecialPage.php',
- 'SpecialMyuploads' => 'includes/SpecialPage.php',
- 'SpecialAllMyUploads' => 'includes/SpecialPage.php',
- 'SpecialPage' => 'includes/SpecialPage.php',
- 'SpecialPageFactory' => 'includes/SpecialPageFactory.php',
- 'SpecialRedirectToSpecial' => 'includes/SpecialPage.php',
'SquidPurgeClient' => 'includes/SquidPurgeClient.php',
'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php',
'StatCounter' => 'includes/StatCounter.php',
'Status' => 'includes/Status.php',
'StreamFile' => 'includes/StreamFile.php',
- 'StringUtils' => 'includes/StringUtils.php',
- 'StubContLang' => 'includes/StubObject.php',
+ 'StringPrefixSearch' => 'includes/PrefixSearch.php',
'StubObject' => 'includes/StubObject.php',
'StubUserLang' => 'includes/StubObject.php',
- 'TablePager' => 'includes/Pager.php',
- 'MWTimestamp' => 'includes/Timestamp.php',
- 'TimestampException' => 'includes/Timestamp.php',
+ 'MWTimestamp' => 'includes/MWTimestamp.php',
+ 'TimestampException' => 'includes/TimestampException.php',
'Title' => 'includes/Title.php',
'TitleArray' => 'includes/TitleArray.php',
- 'TitleArrayFromResult' => 'includes/TitleArray.php',
- 'ThrottledError' => 'includes/Exception.php',
- 'UIDGenerator' => 'includes/UIDGenerator.php',
- 'UnlistedSpecialPage' => 'includes/SpecialPage.php',
+ 'TitleArrayFromResult' => 'includes/TitleArrayFromResult.php',
+ 'TitlePrefixSearch' => 'includes/PrefixSearch.php',
'UploadSourceAdapter' => 'includes/Import.php',
'UppercaseCollation' => 'includes/Collation.php',
'User' => 'includes/User.php',
'UserArray' => 'includes/UserArray.php',
- 'UserArrayFromResult' => 'includes/UserArray.php',
- 'UserBlockedError' => 'includes/Exception.php',
- 'UserNotLoggedIn' => 'includes/Exception.php',
- 'UserCache' => 'includes/cache/UserCache.php',
- 'UserMailer' => 'includes/UserMailer.php',
+ 'UserArrayFromResult' => 'includes/UserArrayFromResult.php',
'UserRightsProxy' => 'includes/UserRightsProxy.php',
- 'ViewCountUpdate' => 'includes/ViewCountUpdate.php',
- 'WantedQueryPage' => 'includes/QueryPage.php',
'WatchedItem' => 'includes/WatchedItem.php',
'WebRequest' => 'includes/WebRequest.php',
'WebRequestUpload' => 'includes/WebRequest.php',
'WebResponse' => 'includes/WebResponse.php',
- 'WikiCategoryPage' => 'includes/WikiCategoryPage.php',
- 'WikiError' => 'includes/WikiError.php',
- 'WikiErrorMsg' => 'includes/WikiError.php',
'WikiExporter' => 'includes/Export.php',
- 'WikiFilePage' => 'includes/WikiFilePage.php',
'WikiImporter' => 'includes/Import.php',
- 'WikiPage' => 'includes/WikiPage.php',
'WikiRevision' => 'includes/Import.php',
'WikiMap' => 'includes/WikiMap.php',
'WikiReference' => 'includes/WikiMap.php',
- 'WikiXmlError' => 'includes/WikiError.php',
'Xml' => 'includes/Xml.php',
'XmlDumpWriter' => 'includes/Export.php',
'XmlJsCode' => 'includes/Xml.php',
- 'XMLReader2' => 'includes/Import.php',
'XmlSelect' => 'includes/Xml.php',
- 'XmlTypeCheck' => 'includes/XmlTypeCheck.php',
- 'ZhClient' => 'includes/ZhClient.php',
- 'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
- 'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php',
-
- # content handler
- 'AbstractContent' => 'includes/content/AbstractContent.php',
- 'ContentHandler' => 'includes/content/ContentHandler.php',
- 'Content' => 'includes/content/Content.php',
- 'CssContentHandler' => 'includes/content/CssContentHandler.php',
- 'CssContent' => 'includes/content/CssContent.php',
- 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php',
- 'JavaScriptContent' => 'includes/content/JavaScriptContent.php',
- 'MessageContent' => 'includes/content/MessageContent.php',
- 'MWContentSerializationException' => 'includes/content/ContentHandler.php',
- 'TextContentHandler' => 'includes/content/TextContentHandler.php',
- 'TextContent' => 'includes/content/TextContent.php',
- 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php',
- 'WikitextContent' => 'includes/content/WikitextContent.php',
# includes/actions
+ 'Action' => 'includes/actions/Action.php',
'CachedAction' => 'includes/actions/CachedAction.php',
'CreditsAction' => 'includes/actions/CreditsAction.php',
'DeleteAction' => 'includes/actions/DeleteAction.php',
'EditAction' => 'includes/actions/EditAction.php',
+ 'FormlessAction' => 'includes/actions/FormlessAction.php',
+ 'FormAction' => 'includes/actions/FormAction.php',
'HistoryAction' => 'includes/actions/HistoryAction.php',
- 'HistoryPage' => 'includes/actions/HistoryAction.php',
'HistoryPager' => 'includes/actions/HistoryAction.php',
'InfoAction' => 'includes/actions/InfoAction.php',
'MarkpatrolledAction' => 'includes/actions/MarkpatrolledAction.php',
'ProtectAction' => 'includes/actions/ProtectAction.php',
'PurgeAction' => 'includes/actions/PurgeAction.php',
'RawAction' => 'includes/actions/RawAction.php',
- 'RawPage' => 'includes/actions/RawAction.php',
'RenderAction' => 'includes/actions/RenderAction.php',
'RevertAction' => 'includes/actions/RevertAction.php',
- 'RevertFileAction' => 'includes/actions/RevertAction.php',
'RevisiondeleteAction' => 'includes/actions/RevisiondeleteAction.php',
'RollbackAction' => 'includes/actions/RollbackAction.php',
- 'SubmitAction' => 'includes/actions/EditAction.php',
- 'UnprotectAction' => 'includes/actions/ProtectAction.php',
- 'UnwatchAction' => 'includes/actions/WatchAction.php',
+ 'SubmitAction' => 'includes/actions/SubmitAction.php',
+ 'UnprotectAction' => 'includes/actions/UnprotectAction.php',
+ 'UnwatchAction' => 'includes/actions/UnwatchAction.php',
'ViewAction' => 'includes/actions/ViewAction.php',
'WatchAction' => 'includes/actions/WatchAction.php',
# includes/api
'ApiBase' => 'includes/api/ApiBase.php',
'ApiBlock' => 'includes/api/ApiBlock.php',
+ 'ApiClearHasMsg' => 'includes/api/ApiClearHasMsg.php',
'ApiComparePages' => 'includes/api/ApiComparePages.php',
'ApiCreateAccount' => 'includes/api/ApiCreateAccount.php',
'ApiDelete' => 'includes/api/ApiDelete.php',
@@ -339,12 +226,13 @@ $wgAutoloadLocalClasses = array(
'ApiEmailUser' => 'includes/api/ApiEmailUser.php',
'ApiExpandTemplates' => 'includes/api/ApiExpandTemplates.php',
'ApiFeedContributions' => 'includes/api/ApiFeedContributions.php',
+ 'ApiFeedRecentChanges' => 'includes/api/ApiFeedRecentChanges.php',
'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php',
'ApiFileRevert' => 'includes/api/ApiFileRevert.php',
'ApiFormatBase' => 'includes/api/ApiFormatBase.php',
'ApiFormatDbg' => 'includes/api/ApiFormatDbg.php',
'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
- 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
+ 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatFeedWrapper.php',
'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
'ApiFormatNone' => 'includes/api/ApiFormatNone.php',
'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
@@ -379,12 +267,14 @@ $wgAutoloadLocalClasses = array(
'ApiQueryAllPages' => 'includes/api/ApiQueryAllPages.php',
'ApiQueryAllUsers' => 'includes/api/ApiQueryAllUsers.php',
'ApiQueryBacklinks' => 'includes/api/ApiQueryBacklinks.php',
+ 'ApiQueryBacklinksprop' => 'includes/api/ApiQueryBacklinksprop.php',
'ApiQueryBase' => 'includes/api/ApiQueryBase.php',
'ApiQueryBlocks' => 'includes/api/ApiQueryBlocks.php',
'ApiQueryCategories' => 'includes/api/ApiQueryCategories.php',
'ApiQueryCategoryInfo' => 'includes/api/ApiQueryCategoryInfo.php',
'ApiQueryCategoryMembers' => 'includes/api/ApiQueryCategoryMembers.php',
'ApiQueryContributions' => 'includes/api/ApiQueryUserContributions.php',
+ 'ApiQueryContributors' => 'includes/api/ApiQueryContributors.php',
'ApiQueryDeletedrevs' => 'includes/api/ApiQueryDeletedrevs.php',
'ApiQueryDisabled' => 'includes/api/ApiQueryDisabled.php',
'ApiQueryDuplicateFiles' => 'includes/api/ApiQueryDuplicateFiles.php',
@@ -405,6 +295,7 @@ $wgAutoloadLocalClasses = array(
'ApiQueryPageProps' => 'includes/api/ApiQueryPageProps.php',
'ApiQueryPagesWithProp' => 'includes/api/ApiQueryPagesWithProp.php',
'ApiQueryPagePropNames' => 'includes/api/ApiQueryPagePropNames.php',
+ 'ApiQueryPrefixSearch' => 'includes/api/ApiQueryPrefixSearch.php',
'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php',
'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php',
'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php',
@@ -415,11 +306,13 @@ $wgAutoloadLocalClasses = array(
'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
'ApiQueryStashImageInfo' => 'includes/api/ApiQueryStashImageInfo.php',
'ApiQueryTags' => 'includes/api/ApiQueryTags.php',
+ 'ApiQueryTokens' => 'includes/api/ApiQueryTokens.php',
'ApiQueryUserInfo' => 'includes/api/ApiQueryUserInfo.php',
'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php',
'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php',
'ApiQueryWatchlistRaw' => 'includes/api/ApiQueryWatchlistRaw.php',
'ApiResult' => 'includes/api/ApiResult.php',
+ 'ApiRevisionDelete' => 'includes/api/ApiRevisionDelete.php',
'ApiRollback' => 'includes/api/ApiRollback.php',
'ApiRsd' => 'includes/api/ApiRsd.php',
'ApiSetNotificationTimestamp' => 'includes/api/ApiSetNotificationTimestamp.php',
@@ -434,35 +327,73 @@ $wgAutoloadLocalClasses = array(
# includes/cache
'BacklinkCache' => 'includes/cache/BacklinkCache.php',
'CacheDependency' => 'includes/cache/CacheDependency.php',
+ 'CacheHelper' => 'includes/cache/CacheHelper.php',
'ConstantDependency' => 'includes/cache/CacheDependency.php',
'DependencyWrapper' => 'includes/cache/CacheDependency.php',
'FileCacheBase' => 'includes/cache/FileCacheBase.php',
'FileDependency' => 'includes/cache/CacheDependency.php',
'GenderCache' => 'includes/cache/GenderCache.php',
'GlobalDependency' => 'includes/cache/CacheDependency.php',
- 'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
+ 'ICacheHelper' => 'includes/cache/CacheHelper.php',
+ 'LCStore' => 'includes/cache/LocalisationCache.php',
+ 'LCStoreCDB' => 'includes/cache/LocalisationCache.php',
+ 'LCStoreDB' => 'includes/cache/LocalisationCache.php',
+ 'LCStoreNull' => 'includes/cache/LocalisationCache.php',
'LinkBatch' => 'includes/cache/LinkBatch.php',
'LinkCache' => 'includes/cache/LinkCache.php',
+ 'LocalisationCache' => 'includes/cache/LocalisationCache.php',
+ 'LocalisationCacheBulkLoad' => 'includes/cache/LocalisationCache.php',
+ 'MapCacheLRU' => 'includes/cache/MapCacheLRU.php',
'MessageCache' => 'includes/cache/MessageCache.php',
'ObjectFileCache' => 'includes/cache/ObjectFileCache.php',
- 'ProcessCacheLRU' => 'includes/cache/ProcessCacheLRU.php',
'ResourceFileCache' => 'includes/cache/ResourceFileCache.php',
- 'SquidUpdate' => 'includes/cache/SquidUpdate.php',
- 'TitleDependency' => 'includes/cache/CacheDependency.php',
- 'TitleListDependency' => 'includes/cache/CacheDependency.php',
+ 'UserCache' => 'includes/cache/UserCache.php',
# includes/changes
+ 'ChangesFeed' => 'includes/changes/ChangesFeed.php',
'ChangesList' => 'includes/changes/ChangesList.php',
'EnhancedChangesList' => 'includes/changes/EnhancedChangesList.php',
'OldChangesList' => 'includes/changes/OldChangesList.php',
'RCCacheEntry' => 'includes/changes/RCCacheEntry.php',
+ 'RCCacheEntryFactory' => 'includes/changes/RCCacheEntryFactory.php',
'RecentChange' => 'includes/changes/RecentChange.php',
# includes/clientpool
'RedisConnectionPool' => 'includes/clientpool/RedisConnectionPool.php',
'RedisConnRef' => 'includes/clientpool/RedisConnectionPool.php',
+ # includes/composer
+ 'ComposerPackageModifier' => 'includes/composer/ComposerPackageModifier.php',
+ 'ComposerVersionNormalizer' => 'includes/composer/ComposerVersionNormalizer.php',
+
+ # includes/config
+ 'Config' => 'includes/config/Config.php',
+ 'ConfigException' => 'includes/config/ConfigException.php',
+ 'ConfigFactory' => 'includes/config/ConfigFactory.php',
+ 'GlobalVarConfig' => 'includes/config/GlobalVarConfig.php',
+ 'HashConfig' => 'includes/config/HashConfig.php',
+ 'MultiConfig' => 'includes/config/MultiConfig.php',
+ 'MutableConfig' => 'includes/config/MutableConfig.php',
+
+ # includes/content
+ 'AbstractContent' => 'includes/content/AbstractContent.php',
+ 'CodeContentHandler' => 'includes/content/CodeContentHandler.php',
+ 'Content' => 'includes/content/Content.php',
+ 'ContentHandler' => 'includes/content/ContentHandler.php',
+ 'CssContent' => 'includes/content/CssContent.php',
+ 'CssContentHandler' => 'includes/content/CssContentHandler.php',
+ 'JavaScriptContent' => 'includes/content/JavaScriptContent.php',
+ 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php',
+ 'JsonContent' => 'includes/content/JsonContent.php',
+ 'JsonContentHandler' => 'includes/content/JsonContentHandler.php',
+ 'MessageContent' => 'includes/content/MessageContent.php',
+ 'MWContentSerializationException' => 'includes/content/ContentHandler.php',
+ 'TextContent' => 'includes/content/TextContent.php',
+ 'TextContentHandler' => 'includes/content/TextContentHandler.php',
+ 'WikitextContent' => 'includes/content/WikitextContent.php',
+ 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php',
+
# includes/context
'ContextSource' => 'includes/context/ContextSource.php',
'DerivativeContext' => 'includes/context/DerivativeContext.php',
@@ -491,6 +422,7 @@ $wgAutoloadLocalClasses = array(
'DBConnectionError' => 'includes/db/DatabaseError.php',
'DBConnRef' => 'includes/db/LoadBalancer.php',
'DBError' => 'includes/db/DatabaseError.php',
+ 'DBExpectedError' => 'includes/db/DatabaseError.php',
'DBObject' => 'includes/db/DatabaseUtility.php',
'IDatabase' => 'includes/db/Database.php',
'IORMRow' => 'includes/db/IORMRow.php',
@@ -501,18 +433,19 @@ $wgAutoloadLocalClasses = array(
'FakeResultWrapper' => 'includes/db/DatabaseUtility.php',
'Field' => 'includes/db/DatabaseUtility.php',
'LBFactory' => 'includes/db/LBFactory.php',
- 'LBFactory_Fake' => 'includes/db/LBFactory.php',
- 'LBFactory_Multi' => 'includes/db/LBFactory_Multi.php',
- 'LBFactory_Simple' => 'includes/db/LBFactory.php',
- 'LBFactory_Single' => 'includes/db/LBFactory_Single.php',
+ 'LBFactoryFake' => 'includes/db/LBFactory.php',
+ 'LBFactoryMulti' => 'includes/db/LBFactoryMulti.php',
+ 'LBFactorySimple' => 'includes/db/LBFactory.php',
+ 'LBFactorySingle' => 'includes/db/LBFactorySingle.php',
'LikeMatch' => 'includes/db/DatabaseUtility.php',
'LoadBalancer' => 'includes/db/LoadBalancer.php',
- 'LoadBalancer_Single' => 'includes/db/LBFactory_Single.php',
+ 'LoadBalancerSingle' => 'includes/db/LBFactorySingle.php',
'LoadMonitor' => 'includes/db/LoadMonitor.php',
- 'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php',
- 'LoadMonitor_Null' => 'includes/db/LoadMonitor.php',
+ 'LoadMonitorMySQL' => 'includes/db/LoadMonitor.php',
+ 'LoadMonitorNull' => 'includes/db/LoadMonitor.php',
'MssqlField' => 'includes/db/DatabaseMssql.php',
- 'MssqlResult' => 'includes/db/DatabaseMssql.php',
+ 'MssqlBlob' => 'includes/db/DatabaseMssql.php',
+ 'MssqlResultWrapper' => 'includes/db/DatabaseMssql.php',
'MySQLField' => 'includes/db/DatabaseMysqlBase.php',
'MySQLMasterPos' => 'includes/db/DatabaseMysqlBase.php',
'ORAField' => 'includes/db/DatabaseOracle.php',
@@ -528,27 +461,54 @@ $wgAutoloadLocalClasses = array(
'SQLiteField' => 'includes/db/DatabaseSqlite.php',
# includes/debug
- 'MWDebug' => 'includes/debug/Debug.php',
+ 'MWDebug' => 'includes/debug/MWDebug.php',
+
+ # includes/deferred
+ 'DataUpdate' => 'includes/deferred/DataUpdate.php',
+ 'DeferrableUpdate' => 'includes/deferred/DeferredUpdates.php',
+ 'DeferredUpdates' => 'includes/deferred/DeferredUpdates.php',
+ 'HTMLCacheUpdate' => 'includes/deferred/HTMLCacheUpdate.php',
+ 'LinksDeletionUpdate' => 'includes/deferred/LinksUpdate.php',
+ 'LinksUpdate' => 'includes/deferred/LinksUpdate.php',
+ 'MWCallableUpdate' => 'includes/deferred/CallableUpdate.php',
+ 'SearchUpdate' => 'includes/deferred/SearchUpdate.php',
+ 'SiteStatsUpdate' => 'includes/deferred/SiteStatsUpdate.php',
+ 'SqlDataUpdate' => 'includes/deferred/SqlDataUpdate.php',
+ 'SquidUpdate' => 'includes/deferred/SquidUpdate.php',
+ 'ViewCountUpdate' => 'includes/deferred/ViewCountUpdate.php',
# includes/diff
- '_DiffEngine' => 'includes/diff/DairikiDiff.php',
- '_DiffOp' => 'includes/diff/DairikiDiff.php',
- '_DiffOp_Add' => 'includes/diff/DairikiDiff.php',
- '_DiffOp_Change' => 'includes/diff/DairikiDiff.php',
- '_DiffOp_Copy' => 'includes/diff/DairikiDiff.php',
- '_DiffOp_Delete' => 'includes/diff/DairikiDiff.php',
- '_HWLDF_WordAccumulator' => 'includes/diff/DairikiDiff.php',
- 'ArrayDiffFormatter' => 'includes/diff/DairikiDiff.php',
+ 'DiffEngine' => 'includes/diff/DairikiDiff.php',
+ 'DiffOp' => 'includes/diff/DairikiDiff.php',
+ 'DiffOpAdd' => 'includes/diff/DairikiDiff.php',
+ 'DiffOpChange' => 'includes/diff/DairikiDiff.php',
+ 'DiffOpCopy' => 'includes/diff/DairikiDiff.php',
+ 'DiffOpDelete' => 'includes/diff/DairikiDiff.php',
+ 'HWLDFWordAccumulator' => 'includes/diff/DairikiDiff.php',
+ 'ArrayDiffFormatter' => 'includes/diff/ArrayDiffFormatter.php',
'Diff' => 'includes/diff/DairikiDiff.php',
'DifferenceEngine' => 'includes/diff/DifferenceEngine.php',
- 'DiffFormatter' => 'includes/diff/DairikiDiff.php',
+ 'DiffFormatter' => 'includes/diff/DiffFormatter.php',
'MappedDiff' => 'includes/diff/DairikiDiff.php',
'RangeDifference' => 'includes/diff/WikiDiff3.php',
- 'TableDiffFormatter' => 'includes/diff/DairikiDiff.php',
- 'UnifiedDiffFormatter' => 'includes/diff/DairikiDiff.php',
+ 'TableDiffFormatter' => 'includes/diff/TableDiffFormatter.php',
+ 'UnifiedDiffFormatter' => 'includes/diff/UnifiedDiffFormatter.php',
'WikiDiff3' => 'includes/diff/WikiDiff3.php',
'WordLevelDiff' => 'includes/diff/DairikiDiff.php',
+ # includes/exception
+ 'UserBlockedError' => 'includes/exception/UserBlockedError.php',
+ 'UserNotLoggedIn' => 'includes/exception/UserNotLoggedIn.php',
+ 'ThrottledError' => 'includes/exception/ThrottledError.php',
+ 'ReadOnlyError' => 'includes/exception/ReadOnlyError.php',
+ 'PermissionsError' => 'includes/exception/PermissionsError.php',
+ 'MWException' => 'includes/exception/MWException.php',
+ 'MWExceptionHandler' => 'includes/exception/MWExceptionHandler.php',
+ 'HttpError' => 'includes/exception/HttpError.php',
+ 'BadTitleError' => 'includes/exception/BadTitleError.php',
+ 'ErrorPageError' => 'includes/exception/ErrorPageError.php',
+ 'FatalError' => 'includes/exception/FatalError.php',
+
# includes/externalstore
'ExternalStore' => 'includes/externalstore/ExternalStore.php',
'ExternalStoreDB' => 'includes/externalstore/ExternalStoreDB.php',
@@ -560,6 +520,7 @@ $wgAutoloadLocalClasses = array(
'FileBackendGroup' => 'includes/filebackend/FileBackendGroup.php',
'FileBackend' => 'includes/filebackend/FileBackend.php',
'FileBackendError' => 'includes/filebackend/FileBackend.php',
+ 'FileBackendException' => 'includes/filebackend/FileBackend.php',
'FileBackendStore' => 'includes/filebackend/FileBackendStore.php',
'FileBackendStoreShardListIterator' => 'includes/filebackend/FileBackendStore.php',
'FileBackendStoreShardDirIterator' => 'includes/filebackend/FileBackendStore.php',
@@ -572,6 +533,7 @@ $wgAutoloadLocalClasses = array(
'FSFileBackendDirList' => 'includes/filebackend/FSFileBackend.php',
'FSFileBackendFileList' => 'includes/filebackend/FSFileBackend.php',
'FSFileOpHandle' => 'includes/filebackend/FSFileBackend.php',
+ 'MemoryFileBackend' => 'includes/filebackend/MemoryFileBackend.php',
'SwiftFileBackend' => 'includes/filebackend/SwiftFileBackend.php',
'SwiftFileBackendList' => 'includes/filebackend/SwiftFileBackend.php',
'SwiftFileBackendDirList' => 'includes/filebackend/SwiftFileBackend.php',
@@ -586,7 +548,6 @@ $wgAutoloadLocalClasses = array(
'ScopedLock' => 'includes/filebackend/lockmanager/ScopedLock.php',
'FSLockManager' => 'includes/filebackend/lockmanager/FSLockManager.php',
'DBLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
- 'LSLockManager' => 'includes/filebackend/lockmanager/LSLockManager.php',
'MemcLockManager' => 'includes/filebackend/lockmanager/MemcLockManager.php',
'QuorumLockManager' => 'includes/filebackend/lockmanager/QuorumLockManager.php',
'MySqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
@@ -634,73 +595,83 @@ $wgAutoloadLocalClasses = array(
'InstallDocFormatter' => 'includes/installer/InstallDocFormatter.php',
'Installer' => 'includes/installer/Installer.php',
'LocalSettingsGenerator' => 'includes/installer/LocalSettingsGenerator.php',
+ 'MssqlInstaller' => 'includes/installer/MssqlInstaller.php',
+ 'MssqlUpdater' => 'includes/installer/MssqlUpdater.php',
'MysqlInstaller' => 'includes/installer/MysqlInstaller.php',
'MysqlUpdater' => 'includes/installer/MysqlUpdater.php',
'OracleInstaller' => 'includes/installer/OracleInstaller.php',
'OracleUpdater' => 'includes/installer/OracleUpdater.php',
- 'PhpRefCallBugTester' => 'includes/installer/PhpBugTests.php',
'PhpXmlBugTester' => 'includes/installer/PhpBugTests.php',
'PostgresInstaller' => 'includes/installer/PostgresInstaller.php',
'PostgresUpdater' => 'includes/installer/PostgresUpdater.php',
'SqliteInstaller' => 'includes/installer/SqliteInstaller.php',
'SqliteUpdater' => 'includes/installer/SqliteUpdater.php',
'WebInstaller' => 'includes/installer/WebInstaller.php',
- 'WebInstaller_Complete' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Copying' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_DBConnect' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_DBSettings' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Document' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_ExistingWiki' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Install' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Language' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Name' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Options' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Readme' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_ReleaseNotes' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Restart' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Upgrade' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_UpgradeDoc' => 'includes/installer/WebInstallerPage.php',
- 'WebInstaller_Welcome' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerComplete' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerCopying' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerDBConnect' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerDBSettings' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerDocument' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerExistingWiki' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerInstall' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerLanguage' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerName' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerOptions' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerReadme' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerReleaseNotes' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerRestart' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerUpgrade' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerUpgradeDoc' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerWelcome' => 'includes/installer/WebInstallerPage.php',
'WebInstallerOutput' => 'includes/installer/WebInstallerOutput.php',
'WebInstallerPage' => 'includes/installer/WebInstallerPage.php',
# includes/job
- 'Job' => 'includes/job/Job.php',
- 'JobQueue' => 'includes/job/JobQueue.php',
- 'JobQueueAggregator' => 'includes/job/aggregator/JobQueueAggregator.php',
- 'JobQueueAggregatorMemc' => 'includes/job/aggregator/JobQueueAggregatorMemc.php',
- 'JobQueueAggregatorRedis' => 'includes/job/aggregator/JobQueueAggregatorRedis.php',
- 'JobQueueDB' => 'includes/job/JobQueueDB.php',
- 'JobQueueConnectionError' => 'includes/job/JobQueue.php',
- 'JobQueueError' => 'includes/job/JobQueue.php',
- 'JobQueueGroup' => 'includes/job/JobQueueGroup.php',
- 'JobQueueFederated' => 'includes/job/JobQueueFederated.php',
- 'JobQueueRedis' => 'includes/job/JobQueueRedis.php',
-
- # includes/job/jobs
- 'DoubleRedirectJob' => 'includes/job/jobs/DoubleRedirectJob.php',
- 'DuplicateJob' => 'includes/job/jobs/DuplicateJob.php',
- 'EmaillingJob' => 'includes/job/jobs/EmaillingJob.php',
- 'EnotifNotifyJob' => 'includes/job/jobs/EnotifNotifyJob.php',
- 'HTMLCacheUpdateJob' => 'includes/job/jobs/HTMLCacheUpdateJob.php',
- 'NullJob' => 'includes/job/jobs/NullJob.php',
- 'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php',
- 'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php',
- 'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php',
- 'AssembleUploadChunksJob' => 'includes/job/jobs/AssembleUploadChunksJob.php',
- 'PublishStashedFileJob' => 'includes/job/jobs/PublishStashedFileJob.php',
+ 'IJobSpecification' => 'includes/jobqueue/JobSpecification.php',
+ 'Job' => 'includes/jobqueue/Job.php',
+ 'JobQueue' => 'includes/jobqueue/JobQueue.php',
+ 'JobQueueAggregator' => 'includes/jobqueue/aggregator/JobQueueAggregator.php',
+ 'JobQueueAggregatorMemc' => 'includes/jobqueue/aggregator/JobQueueAggregatorMemc.php',
+ 'JobQueueAggregatorRedis' => 'includes/jobqueue/aggregator/JobQueueAggregatorRedis.php',
+ 'JobQueueDB' => 'includes/jobqueue/JobQueueDB.php',
+ 'JobQueueConnectionError' => 'includes/jobqueue/JobQueue.php',
+ 'JobQueueError' => 'includes/jobqueue/JobQueue.php',
+ 'JobQueueGroup' => 'includes/jobqueue/JobQueueGroup.php',
+ 'JobQueueFederated' => 'includes/jobqueue/JobQueueFederated.php',
+ 'JobQueueRedis' => 'includes/jobqueue/JobQueueRedis.php',
+ 'JobRunner' => 'includes/jobqueue/JobRunner.php',
+ 'JobSpecification' => 'includes/jobqueue/JobSpecification.php',
+
+ # includes/jobqueue/jobs
+ 'DoubleRedirectJob' => 'includes/jobqueue/jobs/DoubleRedirectJob.php',
+ 'DuplicateJob' => 'includes/jobqueue/jobs/DuplicateJob.php',
+ 'EmaillingJob' => 'includes/jobqueue/jobs/EmaillingJob.php',
+ 'EnotifNotifyJob' => 'includes/jobqueue/jobs/EnotifNotifyJob.php',
+ 'HTMLCacheUpdateJob' => 'includes/jobqueue/jobs/HTMLCacheUpdateJob.php',
+ 'NullJob' => 'includes/jobqueue/jobs/NullJob.php',
+ 'RefreshLinksJob' => 'includes/jobqueue/jobs/RefreshLinksJob.php',
+ 'RefreshLinksJob2' => 'includes/jobqueue/jobs/RefreshLinksJob2.php',
+ 'UploadFromUrlJob' => 'includes/jobqueue/jobs/UploadFromUrlJob.php',
+ 'AssembleUploadChunksJob' => 'includes/jobqueue/jobs/AssembleUploadChunksJob.php',
+ 'PublishStashedFileJob' => 'includes/jobqueue/jobs/PublishStashedFileJob.php',
+
+ # includes/jobqueue/utils
+ 'BacklinkJobUtils' => 'includes/jobqueue/utils/BacklinkJobUtils.php',
# includes/json
'FormatJson' => 'includes/json/FormatJson.php',
# includes/libs
'CSSJanus' => 'includes/libs/CSSJanus.php',
- 'CSSJanus_Tokenizer' => 'includes/libs/CSSJanus.php',
+ 'CSSJanusTokenizer' => 'includes/libs/CSSJanus.php',
'CSSMin' => 'includes/libs/CSSMin.php',
'GenericArrayObject' => 'includes/libs/GenericArrayObject.php',
+ 'HashRing' => 'includes/libs/HashRing.php',
'HttpStatus' => 'includes/libs/HttpStatus.php',
'IEContentAnalyzer' => 'includes/libs/IEContentAnalyzer.php',
'IEUrlExtension' => 'includes/libs/IEUrlExtension.php',
+ 'MappedIterator' => 'includes/libs/MappedIterator.php',
+ 'IPSet' => 'includes/libs/IPSet.php',
'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php',
'JSCompilerContext' => 'includes/libs/jsminplus.php',
'JSMinPlus' => 'includes/libs/jsminplus.php',
@@ -708,6 +679,16 @@ $wgAutoloadLocalClasses = array(
'JSParser' => 'includes/libs/jsminplus.php',
'JSToken' => 'includes/libs/jsminplus.php',
'JSTokenizer' => 'includes/libs/jsminplus.php',
+ 'MultiHttpClient' => 'includes/libs/MultiHttpClient.php',
+ 'MWMessagePack' => 'includes/libs/MWMessagePack.php',
+ 'ProcessCacheLRU' => 'includes/libs/ProcessCacheLRU.php',
+ 'RunningStat' => 'includes/libs/RunningStat.php',
+ 'ScopedCallback' => 'includes/libs/ScopedCallback.php',
+ 'ScopedPHPTimeout' => 'includes/libs/ScopedPHPTimeout.php',
+ 'SwiftVirtualRESTService' => 'includes/libs/virtualrest/SwiftVirtualRESTService.php',
+ 'VirtualRESTService' => 'includes/libs/virtualrest/VirtualRESTService.php',
+ 'VirtualRESTServiceClient' => 'includes/libs/virtualrest/VirtualRESTServiceClient.php',
+ 'XmlTypeCheck' => 'includes/libs/XmlTypeCheck.php',
# includes/libs/lessphp
'lessc' => 'includes/libs/lessc.inc.php',
@@ -729,6 +710,7 @@ $wgAutoloadLocalClasses = array(
'ManualLogEntry' => 'includes/logging/LogEntry.php',
'MoveLogFormatter' => 'includes/logging/MoveLogFormatter.php',
'NewUsersLogFormatter' => 'includes/logging/NewUsersLogFormatter.php',
+ 'PageLangLogFormatter' => 'includes/logging/PageLangLogFormatter.php',
'PatrolLog' => 'includes/logging/PatrolLog.php',
'PatrolLogFormatter' => 'includes/logging/PatrolLogFormatter.php',
'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php',
@@ -744,6 +726,11 @@ $wgAutoloadLocalClasses = array(
'PackedHoverImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php',
'PackedOverlayImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php',
+ # includes/mail
+ 'EmailNotification' => 'includes/mail/EmailNotification.php',
+ 'MailAddress' => 'includes/mail/MailAddress.php',
+ 'UserMailer' => 'includes/mail/UserMailer.php',
+
# includes/media
'BitmapHandler' => 'includes/media/Bitmap.php',
'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php',
@@ -753,7 +740,6 @@ $wgAutoloadLocalClasses = array(
'DjVuImage' => 'includes/media/DjVuImage.php',
'Exif' => 'includes/media/Exif.php',
'ExifBitmapHandler' => 'includes/media/ExifBitmap.php',
- 'FormatExif' => 'includes/media/FormatMetadata.php',
'FormatMetadata' => 'includes/media/FormatMetadata.php',
'GIFHandler' => 'includes/media/GIF.php',
'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php',
@@ -771,6 +757,7 @@ $wgAutoloadLocalClasses = array(
'SVGReader' => 'includes/media/SVGMetadataExtractor.php',
'ThumbnailImage' => 'includes/media/MediaTransformOutput.php',
'TiffHandler' => 'includes/media/Tiff.php',
+ 'TransformationalImageHandler' => 'includes/media/TransformationalImageHandler.php',
'TransformParameterError' => 'includes/media/MediaTransformOutput.php',
'XCFHandler' => 'includes/media/XCF.php',
'XMPInfo' => 'includes/media/XMPInfo.php',
@@ -783,10 +770,7 @@ $wgAutoloadLocalClasses = array(
# includes/objectcache
'APCBagOStuff' => 'includes/objectcache/APCBagOStuff.php',
'BagOStuff' => 'includes/objectcache/BagOStuff.php',
- 'DBABagOStuff' => 'includes/objectcache/DBABagOStuff.php',
- 'EhcacheBagOStuff' => 'includes/objectcache/EhcacheBagOStuff.php',
'EmptyBagOStuff' => 'includes/objectcache/EmptyBagOStuff.php',
- 'FakeMemCachedClient' => 'includes/objectcache/EmptyBagOStuff.php',
'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php',
'MediaWikiBagOStuff' => 'includes/objectcache/SqlBagOStuff.php',
'MemCachedClientforWiki' => 'includes/objectcache/MemcachedClient.php',
@@ -802,14 +786,32 @@ $wgAutoloadLocalClasses = array(
'WinCacheBagOStuff' => 'includes/objectcache/WinCacheBagOStuff.php',
'XCacheBagOStuff' => 'includes/objectcache/XCacheBagOStuff.php',
+ # includes/page
+ 'Article' => 'includes/page/Article.php',
+ 'CategoryPage' => 'includes/page/CategoryPage.php',
+ 'ImageHistoryList' => 'includes/page/ImagePage.php',
+ 'ImageHistoryPseudoPager' => 'includes/page/ImagePage.php',
+ 'ImagePage' => 'includes/page/ImagePage.php',
+ 'Page' => 'includes/page/WikiPage.php',
+ 'WikiCategoryPage' => 'includes/page/WikiCategoryPage.php',
+ 'WikiFilePage' => 'includes/page/WikiFilePage.php',
+ 'WikiPage' => 'includes/page/WikiPage.php',
+
+ # includes/pager
+ 'AlphabeticPager' => 'includes/pager/AlphabeticPager.php',
+ 'IndexPager' => 'includes/pager/IndexPager.php',
+ 'Pager' => 'includes/pager/Pager.php',
+ 'ReverseChronologicalPager' => 'includes/pager/ReverseChronologicalPager.php',
+ 'TablePager' => 'includes/pager/TablePager.php',
+
# includes/parser
'CacheTime' => 'includes/parser/CacheTime.php',
'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php',
'CoreTagHooks' => 'includes/parser/CoreTagHooks.php',
'DateFormatter' => 'includes/parser/DateFormatter.php',
'LinkHolderArray' => 'includes/parser/LinkHolderArray.php',
- 'MWTidy' => 'includes/parser/Tidy.php',
- 'MWTidyWrapper' => 'includes/parser/Tidy.php',
+ 'MWTidy' => 'includes/parser/MWTidy.php',
+ 'MWTidyWrapper' => 'includes/parser/MWTidy.php',
'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
'PPCustomFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php',
@@ -834,20 +836,36 @@ $wgAutoloadLocalClasses = array(
'ParserCache' => 'includes/parser/ParserCache.php',
'ParserOptions' => 'includes/parser/ParserOptions.php',
'ParserOutput' => 'includes/parser/ParserOutput.php',
- 'Parser_DiffTest' => 'includes/parser/Parser_DiffTest.php',
+ 'ParserDiffTest' => 'includes/parser/ParserDiffTest.php',
'Preprocessor' => 'includes/parser/Preprocessor.php',
'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php',
'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php',
'StripState' => 'includes/parser/StripState.php',
+ # includes/password
+ 'BcryptPassword' => 'includes/password/BcryptPassword.php',
+ 'InvalidPassword' => 'includes/password/InvalidPassword.php',
+ 'LayeredParameterizedPassword' => 'includes/password/LayeredParameterizedPassword.php',
+ 'MWSaltedPassword' => 'includes/password/MWSaltedPassword.php',
+ 'MWOldPassword' => 'includes/password/MWOldPassword.php',
+ 'ParameterizedPassword' => 'includes/password/ParameterizedPassword.php',
+ 'Password' => 'includes/password/Password.php',
+ 'PasswordError' => 'includes/password/PasswordError.php',
+ 'PasswordFactory' => 'includes/password/PasswordFactory.php',
+ 'Pbkdf2Password' => 'includes/password/Pbkdf2Password.php',
+ 'EncryptedPassword' => 'includes/password/EncryptedPassword.php',
+
# includes/profiler
'Profiler' => 'includes/profiler/Profiler.php',
- 'ProfilerSimple' => 'includes/profiler/ProfilerSimple.php',
+ 'ProfilerMwprof' => 'includes/profiler/ProfilerMwprof.php',
+ 'ProfilerSimpleDB' => 'includes/profiler/ProfilerSimpleDB.php',
'ProfilerSimpleText' => 'includes/profiler/ProfilerSimpleText.php',
'ProfilerSimpleTrace' => 'includes/profiler/ProfilerSimpleTrace.php',
'ProfilerSimpleUDP' => 'includes/profiler/ProfilerSimpleUDP.php',
+ 'ProfilerStandard' => 'includes/profiler/ProfilerStandard.php',
'ProfilerStub' => 'includes/profiler/ProfilerStub.php',
'ProfileSection' => 'includes/profiler/Profiler.php',
+ 'TransactionProfiler' => 'includes/profiler/Profiler.php',
# includes/rcfeed
'RCFeedEngine' => 'includes/rcfeed/RCFeedEngine.php',
@@ -856,62 +874,65 @@ $wgAutoloadLocalClasses = array(
'RCFeedFormatter' => 'includes/rcfeed/RCFeedFormatter.php',
'IRCColourfulRCFeedFormatter' => 'includes/rcfeed/IRCColourfulRCFeedFormatter.php',
'JSONRCFeedFormatter' => 'includes/rcfeed/JSONRCFeedFormatter.php',
+ 'XMLRCFeedFormatter' => 'includes/rcfeed/XMLRCFeedFormatter.php',
+ 'MachineReadableRCFeedFormatter' => 'includes/rcfeed/MachineReadableRCFeedFormatter.php',
# includes/resourceloader
+ 'DerivativeResourceLoaderContext' =>
+ 'includes/resourceloader/DerivativeResourceLoaderContext.php',
'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php',
'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php',
+ 'ResourceLoaderEditToolbarModule' => 'includes/resourceloader/ResourceLoaderEditToolbarModule.php',
'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php',
'ResourceLoaderFilePageModule' => 'includes/resourceloader/ResourceLoaderFilePageModule.php',
- 'ResourceLoaderLESSFunctions' => 'includes/resourceloader/ResourceLoaderLESSFunctions.php',
+ 'ResourceLoaderFilePath' => 'includes/resourceloader/ResourceLoaderFilePath.php',
'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php',
'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php',
'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
- 'ResourceLoaderUserCSSPrefsModule' => 'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
+ 'ResourceLoaderUserCSSPrefsModule' =>
+ 'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
'ResourceLoaderUserGroupsModule' => 'includes/resourceloader/ResourceLoaderUserGroupsModule.php',
'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php',
'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php',
'ResourceLoaderUserTokensModule' => 'includes/resourceloader/ResourceLoaderUserTokensModule.php',
- 'ResourceLoaderLanguageDataModule' => 'includes/resourceloader/ResourceLoaderLanguageDataModule.php',
+ 'ResourceLoaderLanguageDataModule' =>
+ 'includes/resourceloader/ResourceLoaderLanguageDataModule.php',
+ 'ResourceLoaderLanguageNamesModule' =>
+ 'includes/resourceloader/ResourceLoaderLanguageNamesModule.php',
'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php',
# includes/revisiondelete
- 'RevDel_ArchivedFileItem' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_ArchivedFileList' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_ArchivedRevisionItem' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_ArchiveItem' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_ArchiveList' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_FileItem' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_FileList' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_Item' => 'includes/revisiondelete/RevisionDeleteAbstracts.php',
- 'RevDel_List' => 'includes/revisiondelete/RevisionDeleteAbstracts.php',
- 'RevDel_LogItem' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_LogList' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_RevisionItem' => 'includes/revisiondelete/RevisionDelete.php',
- 'RevDel_RevisionList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDelArchivedFileItem' => 'includes/revisiondelete/RevDelArchivedFileItem.php',
+ 'RevDelArchivedFileList' => 'includes/revisiondelete/RevDelArchivedFileList.php',
+ 'RevDelArchivedRevisionItem' => 'includes/revisiondelete/RevDelArchivedRevisionItem.php',
+ 'RevDelArchiveItem' => 'includes/revisiondelete/RevDelArchiveItem.php',
+ 'RevDelArchiveList' => 'includes/revisiondelete/RevDelArchiveList.php',
+ 'RevDelFileItem' => 'includes/revisiondelete/RevDelFileItem.php',
+ 'RevDelFileList' => 'includes/revisiondelete/RevDelFileList.php',
+ 'RevDelItem' => 'includes/revisiondelete/RevDelItem.php',
+ 'RevDelList' => 'includes/revisiondelete/RevDelList.php',
+ 'RevDelLogItem' => 'includes/revisiondelete/RevDelLogItem.php',
+ 'RevDelLogList' => 'includes/revisiondelete/RevDelLogList.php',
+ 'RevDelRevisionItem' => 'includes/revisiondelete/RevDelRevisionItem.php',
+ 'RevDelRevisionList' => 'includes/revisiondelete/RevDelRevisionList.php',
'RevisionDeleter' => 'includes/revisiondelete/RevisionDeleter.php',
'RevisionDeleteUser' => 'includes/revisiondelete/RevisionDeleteUser.php',
# includes/search
- 'MssqlSearchResultSet' => 'includes/search/SearchMssql.php',
- 'MySQLSearchResultSet' => 'includes/search/SearchMySQL.php',
- 'PostgresSearchResult' => 'includes/search/SearchPostgres.php',
- 'PostgresSearchResultSet' => 'includes/search/SearchPostgres.php',
+ 'SearchDatabase' => 'includes/search/SearchDatabase.php',
'SearchEngine' => 'includes/search/SearchEngine.php',
'SearchEngineDummy' => 'includes/search/SearchEngine.php',
- 'SearchHighlighter' => 'includes/search/SearchEngine.php',
+ 'SearchHighlighter' => 'includes/search/SearchHighlighter.php',
'SearchMssql' => 'includes/search/SearchMssql.php',
'SearchMySQL' => 'includes/search/SearchMySQL.php',
- 'SearchNearMatchResultSet' => 'includes/search/SearchEngine.php',
+ 'SearchNearMatchResultSet' => 'includes/search/SearchResultSet.php',
'SearchOracle' => 'includes/search/SearchOracle.php',
'SearchPostgres' => 'includes/search/SearchPostgres.php',
- 'SearchResult' => 'includes/search/SearchEngine.php',
- 'SearchResultSet' => 'includes/search/SearchEngine.php',
- 'SearchResultTooMany' => 'includes/search/SearchEngine.php',
+ 'SearchResult' => 'includes/search/SearchResult.php',
+ 'SearchResultSet' => 'includes/search/SearchResultSet.php',
'SearchSqlite' => 'includes/search/SearchSqlite.php',
- 'SearchUpdate' => 'includes/search/SearchUpdate.php',
- 'SqliteSearchResultSet' => 'includes/search/SearchSqlite.php',
- 'SqlSearchResultSet' => 'includes/search/SearchEngine.php',
+ 'SqlSearchResultSet' => 'includes/search/SearchResultSet.php',
# includes/site
'MediaWikiSite' => 'includes/site/MediaWikiSite.php',
@@ -923,9 +944,35 @@ $wgAutoloadLocalClasses = array(
'Sites' => 'includes/site/SiteSQLStore.php',
'SiteStore' => 'includes/site/SiteStore.php',
+ # includes/skins
+ 'BaseTemplate' => 'includes/skins/SkinTemplate.php',
+ 'MediaWikiI18N' => 'includes/skins/SkinTemplate.php',
+ 'QuickTemplate' => 'includes/skins/SkinTemplate.php',
+ 'Skin' => 'includes/skins/Skin.php',
+ 'SkinException' => 'includes/skins/SkinException.php',
+ 'SkinFactory' => 'includes/skins/SkinFactory.php',
+ 'SkinFallback' => 'includes/skins/SkinFallback.php',
+ 'SkinFallbackTemplate' => 'includes/skins/SkinFallbackTemplate.php',
+ 'SkinTemplate' => 'includes/skins/SkinTemplate.php',
+
+ # includes/specialpage
+ 'ChangesListSpecialPage' => 'includes/specialpage/ChangesListSpecialPage.php',
+ 'FormSpecialPage' => 'includes/specialpage/FormSpecialPage.php',
+ 'ImageQueryPage' => 'includes/specialpage/ImageQueryPage.php',
+ 'IncludableSpecialPage' => 'includes/specialpage/IncludableSpecialPage.php',
+ 'PageQueryPage' => 'includes/specialpage/PageQueryPage.php',
+ 'QueryPage' => 'includes/specialpage/QueryPage.php',
+ 'RedirectSpecialArticle' => 'includes/specialpage/RedirectSpecialPage.php',
+ 'RedirectSpecialPage' => 'includes/specialpage/RedirectSpecialPage.php',
+ 'SpecialPage' => 'includes/specialpage/SpecialPage.php',
+ 'SpecialPageFactory' => 'includes/specialpage/SpecialPageFactory.php',
+ 'SpecialRedirectToSpecial' => 'includes/specialpage/RedirectSpecialPage.php',
+ 'UnlistedSpecialPage' => 'includes/specialpage/UnlistedSpecialPage.php',
+ 'WantedQueryPage' => 'includes/specialpage/WantedQueryPage.php',
+
# includes/specials
'ActiveUsersPager' => 'includes/specials/SpecialActiveusers.php',
- 'AllmessagesTablePager' => 'includes/specials/SpecialAllmessages.php',
+ 'AllMessagesTablePager' => 'includes/specials/SpecialAllMessages.php',
'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php',
'BlockListPager' => 'includes/specials/SpecialBlockList.php',
'BrokenRedirectsPage' => 'includes/specials/SpecialBrokenRedirects.php',
@@ -941,15 +988,15 @@ $wgAutoloadLocalClasses = array(
'EmailInvalidation' => 'includes/specials/SpecialConfirmemail.php',
'FewestrevisionsPage' => 'includes/specials/SpecialFewestrevisions.php',
'FileDuplicateSearchPage' => 'includes/specials/SpecialFileDuplicateSearch.php',
- 'HTMLBlockedUsersItemSelect' => 'includes/specials/SpecialBlockList.php',
'ImageListPager' => 'includes/specials/SpecialListfiles.php',
'ImportReporter' => 'includes/specials/SpecialImport.php',
- 'IPBlockForm' => 'includes/specials/SpecialBlock.php',
'LinkSearchPage' => 'includes/specials/SpecialLinkSearch.php',
'ListredirectsPage' => 'includes/specials/SpecialListredirects.php',
+ 'ListDuplicatedFilesPage' => 'includes/specials/SpecialListDuplicatedFiles.php',
'LoginForm' => 'includes/specials/SpecialUserlogin.php',
'LonelyPagesPage' => 'includes/specials/SpecialLonelypages.php',
'LongPagesPage' => 'includes/specials/SpecialLongpages.php',
+ 'MediaStatisticsPage' => 'includes/specials/SpecialMediaStatistics.php',
'MergeHistoryPager' => 'includes/specials/SpecialMergeHistory.php',
'MIMEsearchPage' => 'includes/specials/SpecialMIMEsearch.php',
'MostcategoriesPage' => 'includes/specials/SpecialMostcategories.php',
@@ -969,8 +1016,9 @@ $wgAutoloadLocalClasses = array(
'RandomPage' => 'includes/specials/SpecialRandompage.php',
'ShortPagesPage' => 'includes/specials/SpecialShortpages.php',
'SpecialActiveUsers' => 'includes/specials/SpecialActiveusers.php',
- 'SpecialAllmessages' => 'includes/specials/SpecialAllmessages.php',
- 'SpecialAllpages' => 'includes/specials/SpecialAllpages.php',
+ 'SpecialAllMessages' => 'includes/specials/SpecialAllMessages.php',
+ 'SpecialAllMyUploads' => 'includes/specials/SpecialMyRedirectPages.php',
+ 'SpecialAllPages' => 'includes/specials/SpecialAllPages.php',
'SpecialBlankpage' => 'includes/specials/SpecialBlankpage.php',
'SpecialBlock' => 'includes/specials/SpecialBlock.php',
'SpecialBlockList' => 'includes/specials/SpecialBlockList.php',
@@ -981,23 +1029,34 @@ $wgAutoloadLocalClasses = array(
'SpecialChangePassword' => 'includes/specials/SpecialChangePassword.php',
'SpecialComparePages' => 'includes/specials/SpecialComparePages.php',
'SpecialContributions' => 'includes/specials/SpecialContributions.php',
+ 'SpecialCreateAccount' => 'includes/specials/SpecialCreateAccount.php',
+ 'SpecialDiff' => 'includes/specials/SpecialDiff.php',
'SpecialEditWatchlist' => 'includes/specials/SpecialEditWatchlist.php',
'SpecialEmailUser' => 'includes/specials/SpecialEmailuser.php',
+ 'SpecialExpandTemplates' => 'includes/specials/SpecialExpandTemplates.php',
'SpecialExport' => 'includes/specials/SpecialExport.php',
'SpecialFilepath' => 'includes/specials/SpecialFilepath.php',
'SpecialImport' => 'includes/specials/SpecialImport.php',
'SpecialJavaScriptTest' => 'includes/specials/SpecialJavaScriptTest.php',
+ 'SpecialListAdmins' => 'includes/specials/SpecialListusers.php',
+ 'SpecialListBots' => 'includes/specials/SpecialListusers.php',
'SpecialListFiles' => 'includes/specials/SpecialListfiles.php',
'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php',
'SpecialListUsers' => 'includes/specials/SpecialListusers.php',
'SpecialLockdb' => 'includes/specials/SpecialLockdb.php',
'SpecialLog' => 'includes/specials/SpecialLog.php',
'SpecialMergeHistory' => 'includes/specials/SpecialMergeHistory.php',
+ 'SpecialMycontributions' => 'includes/specials/SpecialMyRedirectPages.php',
+ 'SpecialMyLanguage' => 'includes/specials/SpecialMyLanguage.php',
+ 'SpecialMypage' => 'includes/specials/SpecialMyRedirectPages.php',
+ 'SpecialMytalk' => 'includes/specials/SpecialMyRedirectPages.php',
+ 'SpecialMyuploads' => 'includes/specials/SpecialMyRedirectPages.php',
'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php',
'SpecialNewpages' => 'includes/specials/SpecialNewpages.php',
+ 'SpecialPageLanguage' => 'includes/specials/SpecialPageLanguage.php',
'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php',
'SpecialPagesWithProp' => 'includes/specials/SpecialPagesWithProp.php',
- 'SpecialPermanentLink' => 'includes/SpecialPage.php',
+ 'SpecialPermanentLink' => 'includes/specials/SpecialPermanentLink.php',
'SpecialPreferences' => 'includes/specials/SpecialPreferences.php',
'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php',
'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php',
@@ -1005,14 +1064,16 @@ $wgAutoloadLocalClasses = array(
'SpecialRandomInCategory' => 'includes/specials/SpecialRandomInCategory.php',
'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php',
'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
- 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php',
+ 'SpecialRecentChangesLinked' => 'includes/specials/SpecialRecentchangeslinked.php',
'SpecialRedirect' => 'includes/specials/SpecialRedirect.php',
'SpecialResetTokens' => 'includes/specials/SpecialResetTokens.php',
'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php',
+ 'SpecialRunJobs' => 'includes/specials/SpecialRunJobs.php',
'SpecialSearch' => 'includes/specials/SpecialSearch.php',
'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php',
'SpecialStatistics' => 'includes/specials/SpecialStatistics.php',
'SpecialTags' => 'includes/specials/SpecialTags.php',
+ 'SpecialTrackingCategories' => 'includes/specials/SpecialTrackingCategories.php',
'SpecialUnblock' => 'includes/specials/SpecialUnblock.php',
'SpecialUndelete' => 'includes/specials/SpecialUndelete.php',
'SpecialUnlockdb' => 'includes/specials/SpecialUnlockdb.php',
@@ -1042,13 +1103,21 @@ $wgAutoloadLocalClasses = array(
'WantedFilesPage' => 'includes/specials/SpecialWantedfiles.php',
'WantedPagesPage' => 'includes/specials/SpecialWantedpages.php',
'WantedTemplatesPage' => 'includes/specials/SpecialWantedtemplates.php',
- 'WatchlistEditor' => 'includes/specials/SpecialEditWatchlist.php',
'WithoutInterwikiPage' => 'includes/specials/SpecialWithoutinterwiki.php',
# includes/templates
'UserloginTemplate' => 'includes/templates/Userlogin.php',
'UsercreateTemplate' => 'includes/templates/Usercreate.php',
+ # includes/title
+ 'PageLinkRenderer' => 'includes/title/PageLinkRenderer.php',
+ 'TitleFormatter' => 'includes/title/TitleFormatter.php',
+ 'TitleParser' => 'includes/title/TitleParser.php',
+ 'TitleValue' => 'includes/title/TitleValue.php',
+ 'MalformedTitleException' => 'includes/title/MalformedTitleException.php',
+ 'MediaWikiPageLinkRenderer' => 'includes/title/MediaWikiPageLinkRenderer.php',
+ 'MediaWikiTitleCodec' => 'includes/title/MediaWikiTitleCodec.php',
+
# includes/upload
'UploadBase' => 'includes/upload/UploadBase.php',
'UploadFromFile' => 'includes/upload/UploadFromFile.php',
@@ -1067,18 +1136,43 @@ $wgAutoloadLocalClasses = array(
'UploadStashWrongOwnerException' => 'includes/upload/UploadStash.php',
'UploadStashNoSuchKeyException' => 'includes/upload/UploadStash.php',
+ # includes/utils
+ 'ArrayUtils' => 'includes/utils/ArrayUtils.php',
+ 'CdbException' => 'includes/utils/Cdb.php',
+ 'CdbFunctions' => 'includes/utils/CdbPHP.php',
+ 'CdbReader' => 'includes/utils/Cdb.php',
+ 'CdbReaderDBA' => 'includes/utils/CdbDBA.php',
+ 'CdbReaderPHP' => 'includes/utils/CdbPHP.php',
+ 'CdbWriter' => 'includes/utils/Cdb.php',
+ 'CdbWriterDBA' => 'includes/utils/CdbDBA.php',
+ 'CdbWriterPHP' => 'includes/utils/CdbPHP.php',
+ 'DoubleReplacer' => 'includes/utils/StringUtils.php',
+ 'ExplodeIterator' => 'includes/utils/StringUtils.php',
+ 'HashtableReplacer' => 'includes/utils/StringUtils.php',
+ 'IP' => 'includes/utils/IP.php',
+ 'MWCryptRand' => 'includes/utils/MWCryptRand.php',
+ 'MWCryptHKDF' => 'includes/utils/MWCryptHKDF.php',
+ 'MWFunction' => 'includes/utils/MWFunction.php',
+ 'RegexlikeReplacer' => 'includes/utils/StringUtils.php',
+ 'ReplacementArray' => 'includes/utils/StringUtils.php',
+ 'Replacer' => 'includes/utils/StringUtils.php',
+ 'StringUtils' => 'includes/utils/StringUtils.php',
+ 'UIDGenerator' => 'includes/utils/UIDGenerator.php',
+ 'ZipDirectoryReader' => 'includes/utils/ZipDirectoryReader.php',
+ 'ZipDirectoryReaderError' => 'includes/utils/ZipDirectoryReader.php',
+
# languages
- 'ConverterRule' => 'languages/LanguageConverter.php',
- 'FakeConverter' => 'languages/Language.php',
+ 'ConverterRule' => 'languages/ConverterRule.php',
+ 'FakeConverter' => 'languages/FakeConverter.php',
'Language' => 'languages/Language.php',
'LanguageConverter' => 'languages/LanguageConverter.php',
- 'CLDRPluralRuleConverter' => 'languages/utils/CLDRPluralRuleEvaluator.php',
- 'CLDRPluralRuleConverter_Expression' => 'languages/utils/CLDRPluralRuleEvaluator.php',
- 'CLDRPluralRuleConverter_Fragment' => 'languages/utils/CLDRPluralRuleEvaluator.php',
- 'CLDRPluralRuleConverter_Operator' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+ 'CLDRPluralRuleConverter' => 'languages/utils/CLDRPluralRuleConverter.php',
+ 'CLDRPluralRuleConverterExpression' => 'languages/utils/CLDRPluralRuleConverterExpression.php',
+ 'CLDRPluralRuleConverterFragment' => 'languages/utils/CLDRPluralRuleConverterFragment.php',
+ 'CLDRPluralRuleConverterOperator' => 'languages/utils/CLDRPluralRuleConverterOperator.php',
'CLDRPluralRuleEvaluator' => 'languages/utils/CLDRPluralRuleEvaluator.php',
- 'CLDRPluralRuleEvaluator_Range' => 'languages/utils/CLDRPluralRuleEvaluator.php',
- 'CLDRPluralRuleError' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+ 'CLDRPluralRuleEvaluatorRange' => 'languages/utils/CLDRPluralRuleEvaluatorRange.php',
+ 'CLDRPluralRuleError' => 'languages/utils/CLDRPluralRuleError.php',
# maintenance
'BackupDumper' => 'maintenance/backup.inc',
@@ -1092,6 +1186,7 @@ $wgAutoloadLocalClasses = array(
'FixExtLinksProtocolRelative' => 'maintenance/fixExtLinksProtocolRelative.php',
'LoggedUpdateMaintenance' => 'maintenance/Maintenance.php',
'Maintenance' => 'maintenance/Maintenance.php',
+ 'PopulateBacklinkNamespace' => 'maintenance/populateBacklinkNamespace.php',
'PopulateCategory' => 'maintenance/populateCategory.php',
'PopulateImageSha1' => 'maintenance/populateImageSha1.php',
'PopulateFilearchiveSha1' => 'maintenance/populateFilearchiveSha1.php',
@@ -1108,13 +1203,12 @@ $wgAutoloadLocalClasses = array(
'UserDupes' => 'maintenance/userDupes.inc',
# maintenance/language
- 'csvStatsOutput' => 'maintenance/language/StatOutputs.php',
- 'extensionLanguages' => 'maintenance/language/languages.inc',
- 'languages' => 'maintenance/language/languages.inc',
- 'MessageWriter' => 'maintenance/language/writeMessagesArray.inc',
- 'statsOutput' => 'maintenance/language/StatOutputs.php',
- 'textStatsOutput' => 'maintenance/language/StatOutputs.php',
- 'wikiStatsOutput' => 'maintenance/language/StatOutputs.php',
+ 'CsvStatsOutput' => 'maintenance/language/StatOutputs.php',
+ 'ExtensionLanguages' => 'maintenance/language/languages.inc',
+ 'Languages' => 'maintenance/language/languages.inc',
+ 'StatsOutput' => 'maintenance/language/StatOutputs.php',
+ 'TextStatsOutput' => 'maintenance/language/StatOutputs.php',
+ 'WikiStatsOutput' => 'maintenance/language/StatOutputs.php',
# maintenance/term
'AnsiTermColorer' => 'maintenance/term/MWTerm.php',
@@ -1123,29 +1217,19 @@ $wgAutoloadLocalClasses = array(
# mw-config
'InstallerOverrides' => 'mw-config/overrides.php',
'MyLocalSettingsGenerator' => 'mw-config/overrides.php',
-
- # skins
- 'CologneBlueTemplate' => 'skins/CologneBlue.php',
- 'ModernTemplate' => 'skins/Modern.php',
- 'MonoBookTemplate' => 'skins/MonoBook.php',
- 'SkinCologneBlue' => 'skins/CologneBlue.php',
- 'SkinModern' => 'skins/Modern.php',
- 'SkinMonoBook' => 'skins/MonoBook.php',
- 'SkinVector' => 'skins/Vector.php',
- 'VectorTemplate' => 'skins/Vector.php',
);
class AutoLoader {
+ static protected $autoloadLocalClassesLower = null;
+
/**
* autoload - take a class name and attempt to load it
*
- * @param string $className name of class we're looking for.
- * @return bool Returning false is important on failure as
- * it allows Zend to try and look in other registered autoloaders
- * as well.
+ * @param string $className Name of class we're looking for.
*/
static function autoload( $className ) {
- global $wgAutoloadClasses, $wgAutoloadLocalClasses;
+ global $wgAutoloadClasses, $wgAutoloadLocalClasses,
+ $wgAutoloadAttemptLowercase;
// Workaround for PHP bug <https://bugs.php.net/bug.php?id=49143> (5.3.2. is broken, it's
// fixed in 5.3.6). Strip leading backslashes from class names. When namespaces are used,
@@ -1156,41 +1240,46 @@ class AutoLoader {
// do not strip the leading backlash in this case, causing autoloading to fail.
$className = ltrim( $className, '\\' );
+ $filename = false;
+
if ( isset( $wgAutoloadLocalClasses[$className] ) ) {
$filename = $wgAutoloadLocalClasses[$className];
} elseif ( isset( $wgAutoloadClasses[$className] ) ) {
$filename = $wgAutoloadClasses[$className];
- } else {
- # Try a different capitalisation
- # The case can sometimes be wrong when unserializing PHP 4 objects
- $filename = false;
+ } elseif ( $wgAutoloadAttemptLowercase ) {
+ /*
+ * Try a different capitalisation.
+ *
+ * PHP 4 objects are always serialized with the classname coerced to lowercase,
+ * and we are plagued with several legacy uses created by MediaWiki < 1.5, see
+ * https://wikitech.wikimedia.org/wiki/Text_storage_data
+ */
$lowerClass = strtolower( $className );
- foreach ( $wgAutoloadLocalClasses as $class2 => $file2 ) {
- if ( strtolower( $class2 ) == $lowerClass ) {
- $filename = $file2;
- }
+ if ( self::$autoloadLocalClassesLower === null ) {
+ self::$autoloadLocalClassesLower = array_change_key_case( $wgAutoloadLocalClasses, CASE_LOWER );
}
- if ( !$filename ) {
- if ( function_exists( 'wfDebug' ) ) {
- wfDebug( "Class {$className} not found; skipped loading\n" );
+ if ( isset( self::$autoloadLocalClassesLower[$lowerClass] ) ) {
+ if ( function_exists( 'wfDebugLog' ) ) {
+ wfDebugLog( 'autoloader', "Class {$className} was loaded using incorrect case" );
}
-
- # Give up
- return false;
+ $filename = self::$autoloadLocalClassesLower[$lowerClass];
}
}
- # Make an absolute path, this improves performance by avoiding some stat calls
+ if ( !$filename ) {
+ // Class not found; let the next autoloader try to find it
+ return;
+ }
+
+ // Make an absolute path, this improves performance by avoiding some stat calls
if ( substr( $filename, 0, 1 ) != '/' && substr( $filename, 1, 1 ) != ':' ) {
global $IP;
$filename = "$IP/$filename";
}
require $filename;
-
- return true;
}
/**
@@ -1198,12 +1287,20 @@ class AutoLoader {
* Sanitizer that have define()s outside of their class definition. Of course
* this wouldn't be necessary if everything in MediaWiki was class-based. Sigh.
*
- * @param $class string
- * @return Boolean Return the results of class_exists() so we know if we were successful
+ * @param string $class
+ * @return bool Return the results of class_exists() so we know if we were successful
*/
static function loadClass( $class ) {
return class_exists( $class );
}
+
+ /**
+ * Method to clear the protected class property $autoloadLocalClassesLower.
+ * Used in tests.
+ */
+ static function resetAutoloadLocalClassesLower() {
+ self::$autoloadLocalClassesLower = null;
+ }
}
spl_autoload_register( array( 'AutoLoader', 'autoload' ) );
diff --git a/includes/Autopromote.php b/includes/Autopromote.php
index 170d7abf..81f3b7aa 100644
--- a/includes/Autopromote.php
+++ b/includes/Autopromote.php
@@ -29,7 +29,7 @@ class Autopromote {
/**
* Get the groups for the given user based on $wgAutopromote.
*
- * @param $user User The user to get the groups for
+ * @param User $user The user to get the groups for
* @return array Array of groups to promote to.
*/
public static function getAutopromoteGroups( User $user ) {
@@ -53,8 +53,8 @@ class Autopromote {
*
* Does not return groups the user already belongs to or has once belonged.
*
- * @param $user User The user to get the groups for
- * @param string $event key in $wgAutopromoteOnce (each one has groups/criteria)
+ * @param User $user The user to get the groups for
+ * @param string $event Key in $wgAutopromoteOnce (each one has groups/criteria)
*
* @return array Groups the user should be promoted to.
*
@@ -99,8 +99,8 @@ class Autopromote {
* This function evaluates the former type recursively, and passes off to
* self::checkCondition for evaluation of the latter type.
*
- * @param $cond Mixed: a condition, possibly containing other conditions
- * @param $user User The user to check the conditions against
+ * @param mixed $cond A condition, possibly containing other conditions
+ * @param User $user The user to check the conditions against
* @return bool Whether the condition is true
*/
private static function recCheckCondition( $cond, User $user ) {
@@ -156,7 +156,7 @@ class Autopromote {
* APCOND_AGE. Other types will throw an exception if no extension evaluates them.
*
* @param array $cond A condition, which must not contain other conditions
- * @param $user User The user to check the condition against
+ * @param User $user The user to check the condition against
* @throws MWException
* @return bool Whether the condition is true for the user
*/
@@ -197,7 +197,8 @@ class Autopromote {
return in_array( 'bot', User::getGroupPermissions( $user->getGroups() ) );
default:
$result = null;
- wfRunHooks( 'AutopromoteCondition', array( $cond[0], array_slice( $cond, 1 ), $user, &$result ) );
+ wfRunHooks( 'AutopromoteCondition', array( $cond[0],
+ array_slice( $cond, 1 ), $user, &$result ) );
if ( $result === null ) {
throw new MWException( "Unrecognized condition {$cond[0]} for autopromotion!" );
}
diff --git a/includes/Block.php b/includes/Block.php
index 34b89e73..6a29a056 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -20,33 +20,54 @@
* @file
*/
class Block {
- /* public*/ var $mReason, $mTimestamp, $mAuto, $mExpiry, $mHideName;
+ /** @var string */
+ public $mReason;
- protected
- $mId,
- $mFromMaster,
+ /** @var bool|string */
+ public $mTimestamp;
- $mBlockEmail,
- $mDisableUsertalk,
- $mCreateAccount,
- $mParentBlockId;
+ /** @var int */
+ public $mAuto;
- /// @var User|String
+ /** @var bool|string */
+ public $mExpiry;
+
+ public $mHideName;
+
+ /** @var int */
+ public $mParentBlockId;
+
+ /** @var int */
+ protected $mId;
+
+ /** @var bool */
+ protected $mFromMaster;
+
+ /** @var bool */
+ protected $mBlockEmail;
+
+ /** @var bool */
+ protected $mDisableUsertalk;
+
+ /** @var bool */
+ protected $mCreateAccount;
+
+ /** @var User|string */
protected $target;
- // @var Integer Hack for foreign blocking (CentralAuth)
+ /** @var int Hack for foreign blocking (CentralAuth) */
protected $forcedTargetID;
- /// @var Block::TYPE_ constant. Can only be USER, IP or RANGE internally
+ /** @var int Block::TYPE_ constant. Can only be USER, IP or RANGE internally */
protected $type;
- /// @var User
+ /** @var User */
protected $blocker;
- /// @var Bool
+ /** @var bool */
protected $isHardblock = true;
- /// @var Bool
+ /** @var bool */
protected $isAutoblocking = true;
# TYPE constants
@@ -57,14 +78,27 @@ class Block {
const TYPE_ID = 5;
/**
- * Constructor
- * @todo FIXME: Don't know what the best format to have for this constructor is, but fourteen
- * optional parameters certainly isn't it.
+ * @todo FIXME: Don't know what the best format to have for this constructor
+ * is, but fourteen optional parameters certainly isn't it.
+ * @param string $address
+ * @param int $user
+ * @param int $by
+ * @param string $reason
+ * @param mixed $timestamp
+ * @param int $auto
+ * @param string $expiry
+ * @param int $anonOnly
+ * @param int $createAccount
+ * @param int $enableAutoblock
+ * @param int $hideName
+ * @param int $blockEmail
+ * @param int $allowUsertalk
+ * @param string $byText
*/
function __construct( $address = '', $user = 0, $by = 0, $reason = '',
$timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0,
- $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = '' )
- {
+ $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = ''
+ ) {
if ( $timestamp === 0 ) {
$timestamp = wfTimestampNow();
}
@@ -102,25 +136,10 @@ class Block {
}
/**
- * Load a block from the database, using either the IP address or
- * user ID. Tries the user ID first, and if that doesn't work, tries
- * the address.
- *
- * @param string $address IP address of user/anon
- * @param $user Integer: user id of user
- * @return Block Object
- * @deprecated since 1.18
- */
- public static function newFromDB( $address, $user = 0 ) {
- wfDeprecated( __METHOD__, '1.18' );
- return self::newFromTarget( User::whoIs( $user ), $address );
- }
-
- /**
* Load a blocked user from their block id.
*
- * @param $id Integer: Block id to search for
- * @return Block object or null
+ * @param int $id Block id to search for
+ * @return Block|null
*/
public static function newFromID( $id ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -166,7 +185,7 @@ class Block {
* Check if two blocks are effectively equal. Doesn't check irrelevant things like
* the blocking user or the block timestamp, only things which affect the blocked user
*
- * @param $block Block
+ * @param Block $block
*
* @return bool
*/
@@ -187,52 +206,14 @@ class Block {
}
/**
- * Clear all member variables in the current object. Does not clear
- * the block from the DB.
- * @deprecated since 1.18
- */
- public function clear() {
- wfDeprecated( __METHOD__, '1.18' );
- # Noop
- }
-
- /**
- * Get a block from the DB, with either the given address or the given username
- *
- * @param string $address The IP address of the user, or blank to skip IP blocks
- * @param int $user The user ID, or zero for anonymous users
- * @return Boolean: the user is blocked from editing
- * @deprecated since 1.18
- */
- public function load( $address = '', $user = 0 ) {
- wfDeprecated( __METHOD__, '1.18' );
- if ( $user ) {
- $username = User::whoIs( $user );
- $block = self::newFromTarget( $username, $address );
- } else {
- $block = self::newFromTarget( null, $address );
- }
-
- if ( $block instanceof Block ) {
- # This is mildly evil, but hey, it's B/C :D
- foreach ( $block as $variable => $value ) {
- $this->$variable = $value;
- }
- return true;
- } else {
- return false;
- }
- }
-
- /**
* Load a block from the database which affects the already-set $this->target:
* 1) A block directly on the given user or IP
* 2) A rangeblock encompassing the given IP (smallest first)
* 3) An autoblock on the given IP
- * @param $vagueTarget User|String also search for blocks affecting this target. Doesn't
+ * @param User|string $vagueTarget Also search for blocks affecting this target. Doesn't
* make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
* @throws MWException
- * @return Bool whether a relevant block was found
+ * @return bool Whether a relevant block was found
*/
protected function newLoad( $vagueTarget = null ) {
$db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE );
@@ -333,7 +314,7 @@ class Block {
* Get a set of SQL conditions which will select rangeblocks encompassing a given range
* @param string $start Hexadecimal IP representation
* @param string $end Hexadecimal IP representation, or null to use $start = $end
- * @return String
+ * @return string
*/
public static function getRangeCond( $start, $end = null ) {
if ( $end === null ) {
@@ -365,8 +346,8 @@ class Block {
/**
* Get the component of an IP address which is certain to be the same between an IP
* address and a rangeblock containing that IP address.
- * @param $hex String Hexadecimal IP representation
- * @return String
+ * @param string $hex Hexadecimal IP representation
+ * @return string
*/
protected static function getIpFragment( $hex ) {
global $wgBlockCIDRLimit;
@@ -380,7 +361,7 @@ class Block {
/**
* Given a database row from the ipblocks table, initialize
* member variables
- * @param $row ResultWrapper: a row from the ipblocks table
+ * @param stdClass $row A row from the ipblocks table
*/
protected function initFromRow( $row ) {
$this->setTarget( $row->ipb_address );
@@ -415,7 +396,7 @@ class Block {
/**
* Create a new Block object from a database row
- * @param $row ResultWrapper row from the ipblocks table
+ * @param stdClass $row Row from the ipblocks table
* @return Block
*/
public static function newFromRow( $row ) {
@@ -428,7 +409,7 @@ class Block {
* Delete the row from the IP blocks table.
*
* @throws MWException
- * @return Boolean
+ * @return bool
*/
public function delete() {
if ( wfReadOnly() ) {
@@ -450,8 +431,8 @@ class Block {
* Insert a block into the block table. Will fail if there is a conflicting
* block (same name and options) already in the database.
*
- * @param $dbw DatabaseBase if you have one available
- * @return mixed: false on failure, assoc array on success:
+ * @param DatabaseBase $dbw If you have one available
+ * @return bool|array False on failure, assoc array on success:
* ('id' => block ID, 'autoIds' => array of autoblock IDs)
*/
public function insert( $dbw = null ) {
@@ -488,13 +469,15 @@ class Block {
* Update a block in the DB with new parameters.
* The ID field needs to be loaded first.
*
- * @return Int number of affected rows, which should probably be 1 or something has
- * gone slightly awry
+ * @return bool|array False on failure, array on success:
+ * ('id' => block ID, 'autoIds' => array of autoblock IDs)
*/
public function update() {
wfDebug( "Block::update; timestamp {$this->mTimestamp}\n" );
$dbw = wfGetDB( DB_MASTER );
+ $dbw->startAtomic( __METHOD__ );
+
$dbw->update(
'ipblocks',
$this->getDatabaseArray( $dbw ),
@@ -502,13 +485,39 @@ class Block {
__METHOD__
);
- return $dbw->affectedRows();
+ $affected = $dbw->affectedRows();
+
+ if ( $this->isAutoblocking() ) {
+ // update corresponding autoblock(s) (bug 48813)
+ $dbw->update(
+ 'ipblocks',
+ $this->getAutoblockUpdateArray(),
+ array( 'ipb_parent_block_id' => $this->getId() ),
+ __METHOD__
+ );
+ } else {
+ // autoblock no longer required, delete corresponding autoblock(s)
+ $dbw->delete(
+ 'ipblocks',
+ array( 'ipb_parent_block_id' => $this->getId() ),
+ __METHOD__
+ );
+ }
+
+ $dbw->endAtomic( __METHOD__ );
+
+ if ( $affected ) {
+ $auto_ipd_ids = $this->doRetroactiveAutoblock();
+ return array( 'id' => $this->mId, 'autoIds' => $auto_ipd_ids );
+ }
+
+ return false;
}
/**
* Get an array suitable for passing to $dbw->insert() or $dbw->update()
- * @param $db DatabaseBase
- * @return Array
+ * @param DatabaseBase $db
+ * @return array
*/
protected function getDatabaseArray( $db = null ) {
if ( !$db ) {
@@ -546,10 +555,24 @@ class Block {
}
/**
+ * @return array
+ */
+ protected function getAutoblockUpdateArray() {
+ return array(
+ 'ipb_by' => $this->getBy(),
+ 'ipb_by_text' => $this->getByName(),
+ 'ipb_reason' => $this->mReason,
+ 'ipb_create_account' => $this->prevents( 'createaccount' ),
+ 'ipb_deleted' => (int)$this->mHideName, // typecast required for SQLite
+ 'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ),
+ );
+ }
+
+ /**
* Retroactively autoblocks the last IP used by the user (if it is a user)
* blocked by this Block.
*
- * @return Array: block IDs of retroactive autoblocks made
+ * @return array Block IDs of retroactive autoblocks made
*/
protected function doRetroactiveAutoblock() {
$blockIds = array();
@@ -573,7 +596,6 @@ class Block {
*
* @param Block $block
* @param array &$blockIds
- * @return Array: block IDs of retroactive autoblocks made
*/
protected static function defaultRetroactiveAutoblock( Block $block, array &$blockIds ) {
global $wgPutIPinRC;
@@ -614,7 +636,7 @@ class Block {
* TODO: this probably belongs somewhere else, but not sure where...
*
* @param string $ip The IP to check
- * @return Boolean
+ * @return bool
*/
public static function isWhitelistedFromAutoblocks( $ip ) {
global $wgMemc;
@@ -656,8 +678,8 @@ class Block {
/**
* Autoblocks the given IP, referring to this Block.
*
- * @param string $autoblockIP the IP to autoblock.
- * @return mixed: block ID if an autoblock was inserted, false if not.
+ * @param string $autoblockIP The IP to autoblock.
+ * @return int|bool Block ID if an autoblock was inserted, false if not.
*/
public function doAutoblock( $autoblockIP ) {
# If autoblocks are disabled, go away.
@@ -698,7 +720,8 @@ class Block {
wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
$autoblock->setTarget( $autoblockIP );
$autoblock->setBlocker( $this->getBlocker() );
- $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason )->inContentLanguage()->plain();
+ $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason )
+ ->inContentLanguage()->plain();
$timestamp = wfTimestampNow();
$autoblock->mTimestamp = $timestamp;
$autoblock->mAuto = 1;
@@ -726,7 +749,7 @@ class Block {
/**
* Check if a block has expired. Delete it if it is.
- * @return Boolean
+ * @return bool
*/
public function deleteIfExpired() {
wfProfileIn( __METHOD__ );
@@ -746,7 +769,7 @@ class Block {
/**
* Has the block expired?
- * @return Boolean
+ * @return bool
*/
public function isExpired() {
$timestamp = wfTimestampNow();
@@ -761,7 +784,7 @@ class Block {
/**
* Is the block address valid (i.e. not a null string?)
- * @return Boolean
+ * @return bool
*/
public function isValid() {
return $this->getTarget() != null;
@@ -792,7 +815,7 @@ class Block {
/**
* Get the IP address at the start of the range in Hex form
* @throws MWException
- * @return String IP in Hex form
+ * @return string IP in Hex form
*/
public function getRangeStart() {
switch ( $this->type ) {
@@ -811,7 +834,7 @@ class Block {
/**
* Get the IP address at the end of the range in Hex form
* @throws MWException
- * @return String IP in Hex form
+ * @return string IP in Hex form
*/
public function getRangeEnd() {
switch ( $this->type ) {
@@ -830,7 +853,7 @@ class Block {
/**
* Get the user id of the blocking sysop
*
- * @return Integer (0 for foreign users)
+ * @return int (0 for foreign users)
*/
public function getBy() {
$blocker = $this->getBlocker();
@@ -842,7 +865,7 @@ class Block {
/**
* Get the username of the blocking sysop
*
- * @return String
+ * @return string
*/
public function getByName() {
$blocker = $this->getBlocker();
@@ -860,21 +883,10 @@ class Block {
}
/**
- * Get/set the SELECT ... FOR UPDATE flag
- * @deprecated since 1.18
- *
- * @param $x Bool
- */
- public function forUpdate( $x = null ) {
- wfDeprecated( __METHOD__, '1.18' );
- # noop
- }
-
- /**
* Get/set a flag determining whether the master is used for reads
*
- * @param $x Bool
- * @return Bool
+ * @param bool $x
+ * @return bool
*/
public function fromMaster( $x = null ) {
return wfSetVar( $this->mFromMaster, $x );
@@ -882,8 +894,8 @@ class Block {
/**
* Get/set whether the Block is a hardblock (affects logged-in users on a given IP/range
- * @param $x Bool
- * @return Bool
+ * @param bool $x
+ * @return bool
*/
public function isHardblock( $x = null ) {
wfSetVar( $this->isHardblock, $x );
@@ -906,9 +918,9 @@ class Block {
/**
* Get/set whether the Block prevents a given action
- * @param $action String
- * @param $x Bool
- * @return Bool
+ * @param string $action
+ * @param bool $x
+ * @return bool
*/
public function prevents( $action, $x = null ) {
switch ( $action ) {
@@ -932,7 +944,7 @@ class Block {
/**
* Get the block name, but with autoblocked IPs hidden as per standard privacy policy
- * @return String, text is escaped
+ * @return string Text is escaped
*/
public function getRedactedName() {
if ( $this->mAuto ) {
@@ -947,37 +959,10 @@ class Block {
}
/**
- * Encode expiry for DB
- *
- * @param string $expiry timestamp for expiry, or
- * @param $db DatabaseBase object
- * @return String
- * @deprecated since 1.18; use $dbw->encodeExpiry() instead
- */
- public static function encodeExpiry( $expiry, $db ) {
- wfDeprecated( __METHOD__, '1.18' );
- return $db->encodeExpiry( $expiry );
- }
-
- /**
- * Decode expiry which has come from the DB
- *
- * @param string $expiry Database expiry format
- * @param int $timestampType Requested timestamp format
- * @return String
- * @deprecated since 1.18; use $wgLang->formatExpiry() instead
- */
- public static function decodeExpiry( $expiry, $timestampType = TS_MW ) {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgContLang;
- return $wgContLang->formatExpiry( $expiry, $timestampType );
- }
-
- /**
* Get a timestamp of the expiry for autoblocks
*
- * @param $timestamp String|Int
- * @return String
+ * @param string|int $timestamp
+ * @return string
*/
public static function getAutoblockExpiry( $timestamp ) {
global $wgAutoblockExpiry;
@@ -986,18 +971,6 @@ class Block {
}
/**
- * Gets rid of unneeded numbers in quad-dotted/octet IP strings
- * For example, 127.111.113.151/24 -> 127.111.113.0/24
- * @param string $range IP address to normalize
- * @return string
- * @deprecated since 1.18, call IP::sanitizeRange() directly
- */
- public static function normaliseRange( $range ) {
- wfDeprecated( __METHOD__, '1.18' );
- return IP::sanitizeRange( $range );
- }
-
- /**
* Purge expired blocks from the ipblocks table
*/
public static function purgeExpired() {
@@ -1007,38 +980,15 @@ class Block {
$method = __METHOD__;
$dbw = wfGetDB( DB_MASTER );
- $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
+ $dbw->onTransactionIdle( function () use ( $dbw, $method ) {
$dbw->delete( 'ipblocks',
array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), $method );
} );
}
/**
- * Get a value to insert into expiry field of the database when infinite expiry
- * is desired
- * @deprecated since 1.18, call $dbr->getInfinity() directly
- * @return String
- */
- public static function infinity() {
- wfDeprecated( __METHOD__, '1.18' );
- return wfGetDB( DB_SLAVE )->getInfinity();
- }
-
- /**
- * Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
- * ("24 May 2034"), into an absolute timestamp we can put into the database.
- * @param string $expiry whatever was typed into the form
- * @return String: timestamp or "infinity" string for th DB implementation
- * @deprecated since 1.18 moved to SpecialBlock::parseExpiryInput()
- */
- public static function parseExpiryInput( $expiry ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialBlock::parseExpiryInput( $expiry );
- }
-
- /**
* Given a target and the target's type, get an existing Block object if possible.
- * @param $specificTarget String|User|Int a block target, which may be one of several types:
+ * @param string|User|int $specificTarget A block target, which may be one of several types:
* * A user to block, in which case $target will be a User
* * An IP to block, in which case $target will be a User generated by using
* User::newFromName( $ip, false ) to turn off name validation
@@ -1048,10 +998,10 @@ class Block {
* Calling this with a user, IP address or range will not select autoblocks, and will
* only select a block where the targets match exactly (so looking for blocks on
* 1.2.3.4 will not select 1.2.0.0/16 or even 1.2.3.4/32)
- * @param $vagueTarget String|User|Int as above, but we will search for *any* block which
+ * @param string|User|int $vagueTarget As above, but we will search for *any* block which
* affects that target (so for an IP address, get ranges containing that IP; and also
* get any relevant autoblocks). Leave empty or blank to skip IP-based lookups.
- * @param bool $fromMaster whether to use the DB_MASTER database
+ * @param bool $fromMaster Whether to use the DB_MASTER database
* @return Block|null (null if no relevant block could be found). The target and type
* of the returned Block will refer to the actual block which was found, which might
* not be the same as the target you gave if you used $vagueTarget!
@@ -1068,7 +1018,10 @@ class Block {
# passed by some callers (bug 29116)
return null;
- } elseif ( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) ) {
+ } elseif ( in_array(
+ $type,
+ array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) )
+ ) {
$block = new Block();
$block->fromMaster( $fromMaster );
@@ -1083,15 +1036,14 @@ class Block {
return null;
}
-
/**
* Get all blocks that match any IP from an array of IP addresses
*
- * @param Array $ipChain list of IPs (strings), usually retrieved from the
+ * @param array $ipChain List of IPs (strings), usually retrieved from the
* X-Forwarded-For header of the request
- * @param Bool $isAnon Exclude anonymous-only blocks if false
- * @param Bool $fromMaster Whether to query the master or slave database
- * @return Array of Blocks
+ * @param bool $isAnon Exclude anonymous-only blocks if false
+ * @param bool $fromMaster Whether to query the master or slave database
+ * @return array Array of Blocks
* @since 1.22
*/
public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) {
@@ -1111,7 +1063,7 @@ class Block {
continue;
}
# Don't check trusted IPs (includes local squids which will be in every request)
- if ( wfIsTrustedProxy( $ipaddr ) ) {
+ if ( IP::isTrustedProxy( $ipaddr ) ) {
continue;
}
# Check both the original IP (to check against single blocks), as well as build
@@ -1165,13 +1117,13 @@ class Block {
* - Other softblocks are chosen over autoblocks
* - If there are multiple exact or range blocks at the same level, the one chosen
* is random
-
- * @param Array $ipChain list of IPs (strings). This is used to determine how "close"
+ *
+ * @param array $blocks Array of blocks
+ * @param array $ipChain List of IPs (strings). This is used to determine how "close"
* a block is to the server, and if a block matches exactly, or is in a range.
* The order is furthest from the server to nearest e.g., (Browser, proxy1, proxy2,
* local-squid, ...)
- * @param Array $block Array of blocks
- * @return Block|null the "best" block from the list
+ * @return Block|null The "best" block from the list
*/
public static function chooseBlock( array $blocks, array $ipChain ) {
if ( !count( $blocks ) ) {
@@ -1184,7 +1136,7 @@ class Block {
// Sort hard blocks before soft ones and secondarily sort blocks
// that disable account creation before those that don't.
- usort( $blocks, function( Block $a, Block $b ) {
+ usort( $blocks, function ( Block $a, Block $b ) {
$aWeight = (int)$a->isHardblock() . (int)$a->prevents( 'createaccount' );
$bWeight = (int)$b->isHardblock() . (int)$b->prevents( 'createaccount' );
return strcmp( $bWeight, $aWeight ); // highest weight first
@@ -1275,7 +1227,7 @@ class Block {
* as a string; for User objects this will return User::__toString()
* which in turn gives User::getName().
*
- * @param $target String|Int|User|null
+ * @param string|int|User|null $target
* @return array( User|String|null, Block::TYPE_ constant|null )
*/
public static function parseTarget( $target ) {
@@ -1332,7 +1284,7 @@ class Block {
/**
* Get the type of target for this particular block
- * @return Block::TYPE_ constant, will never be TYPE_ID
+ * @return int Block::TYPE_ constant, will never be TYPE_ID
*/
public function getType() {
return $this->mAuto
@@ -1355,7 +1307,7 @@ class Block {
* Get the target for this particular Block. Note that for autoblocks,
* this returns the unredacted name; frontend functions need to call $block->getRedactedName()
* in this situation.
- * @return User|String
+ * @return User|string
*/
public function getTarget() {
return $this->target;
@@ -1364,7 +1316,7 @@ class Block {
/**
* @since 1.19
*
- * @return Mixed|string
+ * @return mixed|string
*/
public function getExpiry() {
return $this->mExpiry;
@@ -1372,7 +1324,7 @@ class Block {
/**
* Set the target for this block, and update $this->type accordingly
- * @param $target Mixed
+ * @param mixed $target
*/
public function setTarget( $target ) {
list( $this->target, $this->type ) = self::parseTarget( $target );
@@ -1388,7 +1340,7 @@ class Block {
/**
* Set the user who implemented (or will implement) this block
- * @param $user User|string Local User object or username string for foreign users
+ * @param User|string $user Local User object or username string for foreign users
*/
public function setBlocker( $user ) {
$this->blocker = $user;
@@ -1429,7 +1381,7 @@ class Block {
$this->getId(),
$lang->formatExpiry( $this->mExpiry ),
(string)$intended,
- $lang->timeanddate( wfTimestamp( TS_MW, $this->mTimestamp ), true ),
+ $lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ),
);
}
}
diff --git a/includes/Category.php b/includes/Category.php
index 126b8fee..322b0530 100644
--- a/includes/Category.php
+++ b/includes/Category.php
@@ -26,7 +26,7 @@
* 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.
+ * @todo Move some stuff from CategoryPage.php to here, and use that.
*/
class Category {
/** Name of the category, normalized to DB-key form */
@@ -75,7 +75,8 @@ class Category {
if ( !$row ) {
# Okay, there were no contents. Nothing to initialize.
if ( $this->mTitle ) {
- # If there is a title object but no record in the category table, treat this as an empty category
+ # If there is a title object but no record in the category table,
+ # treat this as an empty category.
$this->mID = false;
$this->mName = $this->mTitle->getDBkey();
$this->mPages = 0;
@@ -128,8 +129,8 @@ class Category {
/**
* Factory function.
*
- * @param $title Title for the category page
- * @return Category|bool on a totally invalid name
+ * @param Title $title Title for the category page
+ * @return Category|bool On a totally invalid name
*/
public static function newFromTitle( $title ) {
$cat = new self();
@@ -143,7 +144,7 @@ class Category {
/**
* Factory function.
*
- * @param $id Integer: a category id
+ * @param int $id A category id
* @return Category
*/
public static function newFromID( $id ) {
@@ -155,11 +156,13 @@ class Category {
/**
* Factory function, for constructing a Category object from a result set
*
- * @param $row result set row, must contain the cat_xxx fields. If the fields are null,
- * the resulting Category object will represent an empty category if a title object
- * was given. If the fields are null and no title was given, this method fails and returns false.
- * @param Title $title optional title object for the category represented by the given row.
- * May be provided if it is already known, to avoid having to re-create a title object later.
+ * @param object $row Result set row, must contain the cat_xxx fields. If the
+ * fields are null, the resulting Category object will represent an empty
+ * category if a title object was given. If the fields are null and no
+ * title was given, this method fails and returns false.
+ * @param Title $title Optional title object for the category represented by
+ * the given row. May be provided if it is already known, to avoid having
+ * to re-create a title object later.
* @return Category
*/
public static function newFromRow( $row, $title = null ) {
@@ -177,7 +180,8 @@ class Category {
# but we can't know that here...
return false;
} else {
- $cat->mName = $title->getDBkey(); # if we have a title object, fetch the category name from there
+ # if we have a title object, fetch the category name from there
+ $cat->mName = $title->getDBkey();
}
$cat->mID = false;
@@ -195,27 +199,37 @@ class Category {
return $cat;
}
- /** @return mixed DB key name, or false on failure */
+ /**
+ * @return mixed DB key name, or false on failure
+ */
public function getName() {
return $this->getX( 'mName' );
}
- /** @return mixed Category ID, or false on failure */
+ /**
+ * @return mixed Category ID, or false on failure
+ */
public function getID() {
return $this->getX( 'mID' );
}
- /** @return mixed Total number of member pages, or false on failure */
+ /**
+ * @return mixed Total number of member pages, or false on failure
+ */
public function getPageCount() {
return $this->getX( 'mPages' );
}
- /** @return mixed Number of subcategories, or false on failure */
+ /**
+ * @return mixed Number of subcategories, or false on failure
+ */
public function getSubcatCount() {
return $this->getX( 'mSubcats' );
}
- /** @return mixed Number of member files, or false on failure */
+ /**
+ * @return mixed Number of member files, or false on failure
+ */
public function getFileCount() {
return $this->getX( 'mFiles' );
}
@@ -239,9 +253,9 @@ class Category {
/**
* Fetch a TitleArray of up to $limit category members, beginning after the
* category sort key $offset.
- * @param $limit integer
- * @param $offset string
- * @return TitleArray object for category members.
+ * @param int $limit
+ * @param string $offset
+ * @return TitleArray TitleArray object for category members.
*/
public function getMembers( $limit = false, $offset = '' ) {
wfProfileIn( __METHOD__ );
@@ -277,6 +291,7 @@ class Category {
/**
* Generic accessor
+ * @param string $key
* @return bool
*/
private function getX( $key ) {
@@ -306,7 +321,7 @@ class Category {
wfProfileIn( __METHOD__ );
$dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
+ $dbw->startAtomic( __METHOD__ );
# Insert the row if it doesn't exist yet (e.g., this is being run via
# update.php from a pre-1.16 schema). TODO: This will cause lots and
@@ -346,7 +361,7 @@ class Category {
array( 'cat_title' => $this->mName ),
__METHOD__
);
- $dbw->commit( __METHOD__ );
+ $dbw->endAtomic( __METHOD__ );
wfProfileOut( __METHOD__ );
diff --git a/includes/Categoryfinder.php b/includes/CategoryFinder.php
index 6ef224b6..cf537e15 100644
--- a/includes/Categoryfinder.php
+++ b/includes/CategoryFinder.php
@@ -21,7 +21,7 @@
*/
/**
- * The "Categoryfinder" class takes a list of articles, creates an internal
+ * The "CategoryFinder" class takes a list of articles, creates an internal
* representation of all their parent categories (as well as parents of
* parents etc.). From this representation, it determines which of these
* articles are in one or all of a given subset of categories.
@@ -31,7 +31,7 @@
* # Determines whether the article with the page_id 12345 is in both
* # "Category 1" and "Category 2" or their subcategories, respectively
*
- * $cf = new Categoryfinder;
+ * $cf = new CategoryFinder;
* $cf->seed(
* array( 12345 ),
* array( 'Category 1', 'Category 2' ),
@@ -42,36 +42,41 @@
* </code>
*
*/
-class Categoryfinder {
- var $articles = array(); # The original article IDs passed to the seed function
- var $deadend = array(); # Array of DBKEY category names for categories that don't have a page
- var $parents = array(); # Array of [ID => array()]
- var $next = array(); # Array of article/category IDs
- var $targets = array(); # Array of DBKEY category names
- var $name2id = array();
- var $mode; # "AND" or "OR"
+class CategoryFinder {
+ /** @var int[] The original article IDs passed to the seed function */
+ protected $articles = array();
- /**
- * @var DatabaseBase
- */
- var $dbr; # Read-DB slave
+ /** @var array Array of DBKEY category names for categories that don't have a page */
+ protected $deadend = array();
- /**
- * Constructor (currently empty).
- */
- function __construct() {
- }
+ /** @var array Array of [ID => array()] */
+ protected $parents = array();
+
+ /** @var array Array of article/category IDs */
+ protected $next = array();
+
+ /** @var array Array of DBKEY category names */
+ protected $targets = array();
+
+ /** @var array */
+ protected $name2id = array();
+
+ /** @var string "AND" or "OR" */
+ protected $mode;
+
+ /** @var DatabaseBase Read-DB slave */
+ protected $dbr;
/**
* Initializes the instance. Do this prior to calling run().
- * @param $article_ids Array of article IDs
- * @param $categories FIXME
+ * @param array $articleIds Array of article IDs
+ * @param array $categories FIXME
* @param string $mode FIXME, default 'AND'.
* @todo FIXME: $categories/$mode
*/
- function seed( $article_ids, $categories, $mode = 'AND' ) {
- $this->articles = $article_ids;
- $this->next = $article_ids;
+ public function seed( $articleIds, $categories, $mode = 'AND' ) {
+ $this->articles = $articleIds;
+ $this->next = $articleIds;
$this->mode = $mode;
# Set the list of target categories; convert them to DBKEY form first
@@ -88,12 +93,12 @@ class Categoryfinder {
/**
* Iterates through the parent tree starting with the seed values,
* then checks the articles if they match the conditions
- * @return array of page_ids (those given to seed() that match the conditions)
+ * @return array Array of page_ids (those given to seed() that match the conditions)
*/
- function run() {
+ public function run() {
$this->dbr = wfGetDB( DB_SLAVE );
while ( count( $this->next ) > 0 ) {
- $this->scan_next_layer();
+ $this->scanNextLayer();
}
# Now check if this applies to the individual articles
@@ -110,13 +115,21 @@ class Categoryfinder {
}
/**
+ * Get the parents. Only really useful if run() has been called already
+ * @return array
+ */
+ public function getParents() {
+ return $this->parents;
+ }
+
+ /**
* This functions recurses through the parent representation, trying to match the conditions
* @param int $id The article/category to check
* @param array $conds The array of categories to match
- * @param array $path used to check for recursion loops
+ * @param array $path Used to check for recursion loops
* @return bool Does this match the conditions?
*/
- function check( $id, &$conds, $path = array() ) {
+ private function check( $id, &$conds, $path = array() ) {
// Check for loops and stop!
if ( in_array( $id, $path ) ) {
return false;
@@ -171,8 +184,8 @@ class Categoryfinder {
/**
* Scans a "parent layer" of the articles/categories in $this->next
*/
- function scan_next_layer() {
- wfProfileIn( __METHOD__ );
+ private function scanNextLayer() {
+ $profiler = new ProfileSection( __METHOD__ );
# Find all parents of the article currently in $this->next
$layer = array();
@@ -227,7 +240,5 @@ class Categoryfinder {
foreach ( $layer as $v ) {
$this->deadend[$v] = $v;
}
-
- wfProfileOut( __METHOD__ );
}
}
diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php
index 55d9c1e5..7581ae40 100644
--- a/includes/CategoryViewer.php
+++ b/includes/CategoryViewer.php
@@ -21,68 +21,77 @@
*/
class CategoryViewer extends ContextSource {
- var $limit, $from, $until,
- $articles, $articles_start_char,
- $children, $children_start_char,
- $showGallery, $imgsNoGalley,
- $imgsNoGallery_start_char,
- $imgsNoGallery;
+ /** @var int */
+ public $limit;
- /**
- * @var Array
- */
- var $nextPage;
+ /** @var array */
+ public $from;
- /**
- * @var Array
- */
- var $flip;
+ /** @var array */
+ public $until;
- /**
- * @var Title
- */
- var $title;
+ /** @var string[] */
+ public $articles;
- /**
- * @var Collation
- */
- var $collation;
+ /** @var array */
+ public $articles_start_char;
- /**
- * @var ImageGallery
- */
- var $gallery;
+ /** @var array */
+ public $children;
- /**
- * Category object for this page
- * @var Category
- */
+ /** @var array */
+ public $children_start_char;
+
+ /** @var bool */
+ public $showGallery;
+
+ /** @var array */
+ public $imgsNoGallery_start_char;
+
+ /** @var array */
+ public $imgsNoGallery;
+
+ /** @var array */
+ public $nextPage;
+
+ /** @var array */
+ protected $prevPage;
+
+ /** @var array */
+ public $flip;
+
+ /** @var Title */
+ public $title;
+
+ /** @var Collation */
+ public $collation;
+
+ /** @var ImageGallery */
+ public $gallery;
+
+ /** @var Category Category object for this page. */
private $cat;
- /**
- * The original query array, to be used in generating paging links.
- * @var array
- */
+ /** @var array The original query array, to be used in generating paging links. */
private $query;
/**
- * Constructor
- *
* @since 1.19 $context is a second, required parameter
- * @param $title Title
- * @param $context IContextSource
+ * @param Title $title
+ * @param IContextSource $context
* @param array $from An array with keys page, subcat,
* and file for offset of results of each section (since 1.17)
* @param array $until An array with 3 keys for until of each section (since 1.17)
- * @param $query Array
+ * @param array $query
*/
- function __construct( $title, IContextSource $context, $from = array(), $until = array(), $query = array() ) {
- global $wgCategoryPagingLimit;
+ function __construct( $title, IContextSource $context, $from = array(),
+ $until = array(), $query = array()
+ ) {
$this->title = $title;
$this->setContext( $context );
$this->from = $from;
$this->until = $until;
- $this->limit = $wgCategoryPagingLimit;
+ $this->limit = $context->getConfig()->get( 'CategoryPagingLimit' );
$this->cat = Category::newFromTitle( $title );
$this->query = $query;
$this->collation = Collation::singleton();
@@ -95,10 +104,10 @@ class CategoryViewer extends ContextSource {
* @return string HTML output
*/
public function getHTML() {
- global $wgCategoryMagicGallery;
wfProfileIn( __METHOD__ );
- $this->showGallery = $wgCategoryMagicGallery && !$this->getOutput()->mNoGallery;
+ $this->showGallery = $this->getConfig()->get( 'CategoryMagicGallery' )
+ && !$this->getOutput()->mNoGallery;
$this->clearCategoryState();
$this->doCategoryQuery();
@@ -144,14 +153,13 @@ class CategoryViewer extends ContextSource {
// Note that null for mode is taken to mean use default.
$mode = $this->getRequest()->getVal( 'gallerymode', null );
try {
- $this->gallery = ImageGalleryBase::factory( $mode );
+ $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
} catch ( MWException $e ) {
// User specified something invalid, fallback to default.
- $this->gallery = ImageGalleryBase::factory();
+ $this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
}
$this->gallery->setHideBadImages();
- $this->gallery->setContext( $this->getContext() );
} else {
$this->imgsNoGallery = array();
$this->imgsNoGallery_start_char = array();
@@ -160,9 +168,9 @@ class CategoryViewer extends ContextSource {
/**
* Add a subcategory to the internal lists, using a Category object
- * @param $cat Category
- * @param $sortkey
- * @param $pageLength
+ * @param Category $cat
+ * @param string $sortkey
+ * @param int $pageLength
*/
function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) {
// Subcategory; strip the 'Category' namespace from the link text.
@@ -182,15 +190,6 @@ class CategoryViewer extends ContextSource {
}
/**
- * Add a subcategory to the internal lists, using a title object
- * @deprecated since 1.17 kept for compatibility, use addSubcategoryObject instead
- */
- function addSubcategory( Title $title, $sortkey, $pageLength ) {
- wfDeprecated( __METHOD__, '1.17' );
- $this->addSubcategoryObject( Category::newFromTitle( $title ), $sortkey, $pageLength );
- }
-
- /**
* Get the character to be used for sorting subcategories.
* If there's a link from Category:A to Category:B, the sortkey of the resulting
* entry in the categorylinks table is Category:A, not A, which it SHOULD be.
@@ -217,10 +216,10 @@ class CategoryViewer extends ContextSource {
/**
* Add a page in the image namespace
- * @param $title Title
- * @param $sortkey
- * @param $pageLength
- * @param $isRedirect bool
+ * @param Title $title
+ * @param string $sortkey
+ * @param int $pageLength
+ * @param bool $isRedirect
*/
function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
global $wgContLang;
@@ -247,10 +246,10 @@ class CategoryViewer extends ContextSource {
/**
* Add a miscellaneous page
- * @param $title
- * @param $sortkey
- * @param $pageLength
- * @param $isRedirect bool
+ * @param Title $title
+ * @param string $sortkey
+ * @param int $pageLength
+ * @param bool $isRedirect
*/
function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
global $wgContLang;
@@ -290,6 +289,12 @@ class CategoryViewer extends ContextSource {
'subcat' => null,
'file' => null,
);
+ $this->prevPage = array(
+ 'page' => null,
+ 'subcat' => null,
+ 'file' => null,
+ );
+
$this->flip = array( 'page' => false, 'subcat' => false, 'file' => false );
foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
@@ -346,6 +351,9 @@ class CategoryViewer extends ContextSource {
$this->nextPage[$type] = $humanSortkey;
break;
}
+ if ( $count == $this->limit ) {
+ $this->prevPage[$type] = $humanSortkey;
+ }
if ( $title->getNamespace() == NS_CATEGORY ) {
$cat = Category::newFromRow( $row, $title );
@@ -432,7 +440,12 @@ class CategoryViewer extends ContextSource {
$countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
$r .= "<div id=\"mw-category-media\">\n";
- $r .= '<h2>' . $this->msg( 'category-media-header', wfEscapeWikiText( $this->title->getText() ) )->text() . "</h2>\n";
+ $r .= '<h2>' .
+ $this->msg(
+ 'category-media-header',
+ wfEscapeWikiText( $this->title->getText() )
+ )->text() .
+ "</h2>\n";
$r .= $countmsg;
$r .= $this->getSectionPagingLinks( 'file' );
if ( $this->showGallery ) {
@@ -451,12 +464,24 @@ class CategoryViewer extends ContextSource {
* of the output.
*
* @param string $type 'page', 'subcat', or 'file'
- * @return String: HTML output, possibly empty if there are no other pages
+ * @return string HTML output, possibly empty if there are no other pages
*/
private function getSectionPagingLinks( $type ) {
if ( isset( $this->until[$type] ) && $this->until[$type] !== null ) {
- return $this->pagingLinks( $this->nextPage[$type], $this->until[$type], $type );
- } elseif ( $this->nextPage[$type] !== null || ( isset( $this->from[$type] ) && $this->from[$type] !== null ) ) {
+ // The new value for the until parameter should be pointing to the first
+ // result displayed on the page which is the second last result retrieved
+ // from the database.The next link should have a from parameter pointing
+ // to the until parameter of the current page.
+ if ( $this->nextPage[$type] !== null ) {
+ return $this->pagingLinks( $this->prevPage[$type], $this->until[$type], $type );
+ } else {
+ // If the nextPage variable is null, it means that we have reached the first page
+ // and therefore the previous link should be disabled.
+ return $this->pagingLinks( null, $this->until[$type], $type );
+ }
+ } elseif ( $this->nextPage[$type] !== null
+ || ( isset( $this->from[$type] ) && $this->from[$type] !== null )
+ ) {
return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type );
} else {
return '';
@@ -474,10 +499,10 @@ class CategoryViewer extends ContextSource {
* Format a list of articles chunked by letter, either as a
* bullet list or a columnar format, depending on the length.
*
- * @param $articles Array
- * @param $articles_start_char Array
- * @param $cutoff Int
- * @return String
+ * @param array $articles
+ * @param array $articles_start_char
+ * @param int $cutoff
+ * @return string
* @private
*/
function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
@@ -507,9 +532,9 @@ class CategoryViewer extends ContextSource {
* More distant TODO: Scrap this and use CSS columns, whenever IE finally
* supports those.
*
- * @param $articles Array
- * @param $articles_start_char Array
- * @return String
+ * @param array $articles
+ * @param string[] $articles_start_char
+ * @return string
* @private
*/
static function columnList( $articles, $articles_start_char ) {
@@ -563,15 +588,16 @@ class CategoryViewer extends ContextSource {
/**
* Format a list of articles chunked by letter in a bullet list.
- * @param $articles Array
- * @param $articles_start_char Array
- * @return String
+ * @param array $articles
+ * @param string[] $articles_start_char
+ * @return string
* @private
*/
static function shortList( $articles, $articles_start_char ) {
$r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n";
$r .= '<ul><li>' . $articles[0] . '</li>';
- for ( $index = 1; $index < count( $articles ); $index++ ) {
+ $articleCount = count( $articles );
+ for ( $index = 1; $index < $articleCount; $index++ ) {
if ( $articles_start_char[$index] != $articles_start_char[$index - 1] ) {
$r .= "</ul><h3>" . htmlspecialchars( $articles_start_char[$index] ) . "</h3>\n<ul>";
}
@@ -589,7 +615,7 @@ class CategoryViewer extends ContextSource {
* @param string $last The 'from' parameter for the generated URL
* @param string $type A prefix for parameters, 'page' or 'subcat' or
* 'file'
- * @return String HTML
+ * @return string HTML
*/
private function pagingLinks( $first, $last, $type = '' ) {
$prevLink = $this->msg( 'prevn' )->numParams( $this->limit )->escaped();
@@ -627,8 +653,8 @@ class CategoryViewer extends ContextSource {
* Takes a title, and adds the fragment identifier that
* corresponds to the correct segment of the category.
*
- * @param Title $title: The title (usually $this->title)
- * @param string $section: Which section
+ * @param Title $title The title (usually $this->title)
+ * @param string $section Which section
* @throws MWException
* @return Title
*/
@@ -660,7 +686,7 @@ class CategoryViewer extends ContextSource {
* @param int $rescnt The number of items returned by our database query.
* @param int $dbcnt The number of items according to the category table.
* @param string $type 'subcat', 'article', or 'file'
- * @return string: A message giving the number of items, to output to HTML.
+ * @return string A message giving the number of items, to output to HTML.
*/
private function getCountMessage( $rescnt, $dbcnt, $type ) {
// There are three cases:
@@ -701,7 +727,10 @@ class CategoryViewer extends ContextSource {
// to refresh the incorrect category table entry -- which should be
// quick due to the small number of entries.
$totalcnt = $rescnt;
- $this->cat->refreshCounts();
+ $category = $this->cat;
+ wfGetDB( DB_MASTER )->onTransactionIdle( function () use ( $category ) {
+ $category->refreshCounts();
+ } );
} else {
// Case 3: hopeless. Don't give a total count at all.
// Messages: category-subcat-count-limited, category-article-count-limited,
diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php
index 3fc27f9a..94b7b7a9 100644
--- a/includes/ChangeTags.php
+++ b/includes/ChangeTags.php
@@ -21,18 +21,15 @@
*/
class ChangeTags {
-
/**
* Creates HTML for the given tags
*
* @param string $tags Comma-separated list of tags
* @param string $page A label for the type of action which is being displayed,
- * for example: 'history', 'contributions' or 'newpages'
- *
- * @return Array with two items: (html, classes)
- * - html: String: HTML for displaying the tags (empty string when param $tags is empty)
- * - classes: Array of strings: CSS classes used in the generated html, one class for each tag
- *
+ * for example: 'history', 'contributions' or 'newpages'
+ * @return array Array with two items: (html, classes)
+ * - html: String: HTML for displaying the tags (empty string when param $tags is empty)
+ * - classes: Array of strings: CSS classes used in the generated html, one class for each tag
*/
public static function formatSummaryRow( $tags, $page ) {
global $wgLang;
@@ -66,10 +63,10 @@ class ChangeTags {
/**
* Get a short description for a tag
*
- * @param string $tag tag
+ * @param string $tag Tag
*
- * @return String: Short description of the tag from "mediawiki:tag-$tag" if this message exists,
- * html-escaped version of $tag otherwise
+ * @return string Short description of the tag from "mediawiki:tag-$tag" if this message exists,
+ * html-escaped version of $tag otherwise
*/
public static function tagDescription( $tag ) {
$msg = wfMessage( "tag-$tag" );
@@ -80,17 +77,19 @@ class ChangeTags {
* Add tags to a change given its rc_id, rev_id and/or log_id
*
* @param string|array $tags Tags to add to the change
- * @param $rc_id int: rc_id of the change to add the tags to
- * @param $rev_id int: rev_id of the change to add the tags to
- * @param $log_id int: log_id of the change to add the tags to
- * @param string $params params to put in the ct_params field of table 'change_tag'
+ * @param int|null $rc_id The rc_id of the change to add the tags to
+ * @param int|null $rev_id The rev_id of the change to add the tags to
+ * @param int|null $log_id The log_id of the change to add the tags to
+ * @param string $params Params to put in the ct_params field of table 'change_tag'
*
* @throws MWException
- * @return bool: false if no changes are made, otherwise true
+ * @return bool False if no changes are made, otherwise true
*
- * @exception MWException when $rc_id, $rev_id and $log_id are all null
+ * @exception MWException When $rc_id, $rev_id and $log_id are all null
*/
- public static function addTags( $tags, $rc_id = null, $rev_id = null, $log_id = null, $params = null ) {
+ public static function addTags( $tags, $rc_id = null, $rev_id = null,
+ $log_id = null, $params = null
+ ) {
if ( !is_array( $tags ) ) {
$tags = array( $tags );
}
@@ -102,26 +101,52 @@ class ChangeTags {
'specified when adding a tag to a change!' );
}
- $dbr = wfGetDB( DB_SLAVE );
+ $dbw = wfGetDB( DB_MASTER );
// Might as well look for rcids and so on.
if ( !$rc_id ) {
- $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave.
+ // Info might be out of date, somewhat fractionally, on slave.
if ( $log_id ) {
- $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_logid' => $log_id ), __METHOD__ );
+ $rc_id = $dbw->selectField(
+ 'recentchanges',
+ 'rc_id',
+ array( 'rc_logid' => $log_id ),
+ __METHOD__
+ );
} elseif ( $rev_id ) {
- $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_this_oldid' => $rev_id ), __METHOD__ );
+ $rc_id = $dbw->selectField(
+ 'recentchanges',
+ 'rc_id',
+ array( 'rc_this_oldid' => $rev_id ),
+ __METHOD__
+ );
}
} elseif ( !$log_id && !$rev_id ) {
- $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave.
- $log_id = $dbr->selectField( 'recentchanges', 'rc_logid', array( 'rc_id' => $rc_id ), __METHOD__ );
- $rev_id = $dbr->selectField( 'recentchanges', 'rc_this_oldid', array( 'rc_id' => $rc_id ), __METHOD__ );
+ // Info might be out of date, somewhat fractionally, on slave.
+ $log_id = $dbw->selectField(
+ 'recentchanges',
+ 'rc_logid',
+ array( 'rc_id' => $rc_id ),
+ __METHOD__
+ );
+ $rev_id = $dbw->selectField(
+ 'recentchanges',
+ 'rc_this_oldid',
+ array( 'rc_id' => $rc_id ),
+ __METHOD__
+ );
}
- $tsConds = array_filter( array( 'ts_rc_id' => $rc_id, 'ts_rev_id' => $rev_id, 'ts_log_id' => $log_id ) );
+ $tsConds = array_filter( array(
+ 'ts_rc_id' => $rc_id,
+ 'ts_rev_id' => $rev_id,
+ 'ts_log_id' => $log_id )
+ );
- ## Update the summary row.
- $prevTags = $dbr->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ );
+ // Update the summary row.
+ // $prevTags can be out of date on slaves, especially when addTags is called consecutively,
+ // causing loss of tags added recently in tag_summary table.
+ $prevTags = $dbw->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ );
$prevTags = $prevTags ? $prevTags : '';
$prevTags = array_filter( explode( ',', $prevTags ) );
$newTags = array_unique( array_merge( $prevTags, $tags ) );
@@ -133,7 +158,6 @@ class ChangeTags {
return false;
}
- $dbw = wfGetDB( DB_MASTER );
$dbw->replace(
'tag_summary',
array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ),
@@ -167,9 +191,9 @@ class ChangeTags {
*
* @param string|array $tables Table names, see DatabaseBase::select
* @param string|array $fields Fields used in query, see DatabaseBase::select
- * @param string|array $conds conditions used in query, see DatabaseBase::select
- * @param $join_conds Array: join conditions, see DatabaseBase::select
- * @param array $options options, see Database::select
+ * @param string|array $conds Conditions used in query, see DatabaseBase::select
+ * @param array $join_conds Join conditions, see DatabaseBase::select
+ * @param array $options Options, see Database::select
* @param bool|string $filter_tag Tag to select on
*
* @throws MWException When unable to determine appropriate JOIN condition for tagging
@@ -184,29 +208,27 @@ class ChangeTags {
// Figure out which conditions can be done.
if ( in_array( 'recentchanges', $tables ) ) {
- $join_cond = 'rc_id';
+ $join_cond = 'ct_rc_id=rc_id';
} elseif ( in_array( 'logging', $tables ) ) {
- $join_cond = 'log_id';
+ $join_cond = 'ct_log_id=log_id';
} elseif ( in_array( 'revision', $tables ) ) {
- $join_cond = 'rev_id';
+ $join_cond = 'ct_rev_id=rev_id';
+ } elseif ( in_array( 'archive', $tables ) ) {
+ $join_cond = 'ct_rev_id=ar_rev_id';
} else {
throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
}
- // JOIN on tag_summary
- $tables[] = 'tag_summary';
- $join_conds['tag_summary'] = array( 'LEFT JOIN', "ts_$join_cond=$join_cond" );
- $fields[] = 'ts_tags';
+ $fields['ts_tags'] = wfGetDB( DB_SLAVE )->buildGroupConcatField(
+ ',', 'change_tag', 'ct_tag', $join_cond
+ );
if ( $wgUseTagFilter && $filter_tag ) {
// Somebody wants to filter on a tag.
// Add an INNER JOIN on change_tag
- // FORCE INDEX -- change_tags will almost ALWAYS be the correct query plan.
- $options['USE INDEX'] = array( 'change_tag' => 'change_tag_tag_id' );
- unset( $options['FORCE INDEX'] );
$tables[] = 'change_tag';
- $join_conds['change_tag'] = array( 'INNER JOIN', "ct_$join_cond=$join_cond" );
+ $join_conds['change_tag'] = array( 'INNER JOIN', $join_cond );
$conds['ct_tag'] = $filter_tag;
}
}
@@ -214,34 +236,55 @@ class ChangeTags {
/**
* Build a text box to select a change tag
*
- * @param string $selected tag to select by default
- * @param $fullForm Boolean:
+ * @param string $selected Tag to select by default
+ * @param bool $fullForm
* - if false, then it returns an array of (label, form).
* - if true, it returns an entire form around the selector.
- * @param $title Title object to send the form to.
+ * @param Title $title Title object to send the form to.
* Used when, and only when $fullForm is true.
- * @return String or array:
+ * @return string|array
* - if $fullForm is false: Array with
* - if $fullForm is true: String, html fragment
*/
- public static function buildTagFilterSelector( $selected = '', $fullForm = false, Title $title = null ) {
+ public static function buildTagFilterSelector( $selected = '',
+ $fullForm = false, Title $title = null
+ ) {
global $wgUseTagFilter;
if ( !$wgUseTagFilter || !count( self::listDefinedTags() ) ) {
return $fullForm ? '' : array();
}
- $data = array( Html::rawElement( 'label', array( 'for' => 'tagfilter' ), wfMessage( 'tag-filter' )->parse() ),
- Xml::input( 'tagfilter', 20, $selected, array( 'class' => 'mw-tagfilter-input' ) ) );
+ $data = array(
+ Html::rawElement(
+ 'label',
+ array( 'for' => 'tagfilter' ),
+ wfMessage( 'tag-filter' )->parse()
+ ),
+ Xml::input(
+ 'tagfilter',
+ 20,
+ $selected,
+ array( 'class' => 'mw-tagfilter-input', 'id' => 'tagfilter' )
+ )
+ );
if ( !$fullForm ) {
return $data;
}
$html = implode( '&#160;', $data );
- $html .= "\n" . Xml::element( 'input', array( 'type' => 'submit', 'value' => wfMessage( 'tag-filter-submit' )->text() ) );
+ $html .= "\n" .
+ Xml::element(
+ 'input',
+ array( 'type' => 'submit', 'value' => wfMessage( 'tag-filter-submit' )->text() )
+ );
$html .= "\n" . Html::hidden( 'title', $title->getPrefixedText() );
- $html = Xml::tags( 'form', array( 'action' => $title->getLocalURL(), 'class' => 'mw-tagfilter-form', 'method' => 'get' ), $html );
+ $html = Xml::tags(
+ 'form',
+ array( 'action' => $title->getLocalURL(), 'class' => 'mw-tagfilter-form', 'method' => 'get' ),
+ $html
+ );
return $html;
}
@@ -253,7 +296,7 @@ class ChangeTags {
*
* Tries memcached first.
*
- * @return Array of strings: tags
+ * @return string[] Array of strings: tags
*/
public static function listDefinedTags() {
// Caching...
diff --git a/includes/Collation.php b/includes/Collation.php
index b0252c70..1c2c2db3 100644
--- a/includes/Collation.php
+++ b/includes/Collation.php
@@ -21,7 +21,7 @@
*/
abstract class Collation {
- static $instance;
+ private static $instance;
/**
* @return Collation
@@ -36,7 +36,7 @@ abstract class Collation {
/**
* @throws MWException
- * @param $collationName string
+ * @param string $collationName
* @return Collation
*/
static function factory( $collationName ) {
@@ -47,6 +47,10 @@ abstract class Collation {
return new IdentityCollation;
case 'uca-default':
return new IcuCollation( 'root' );
+ case 'xx-uca-ckb':
+ return new CollationCkb;
+ case 'xx-uca-et':
+ return new CollationEt;
default:
$match = array();
if ( preg_match( '/^uca-([a-z@=-]+)$/', $collationName, $match ) ) {
@@ -106,7 +110,8 @@ abstract class Collation {
}
class UppercaseCollation extends Collation {
- var $lang;
+ private $lang;
+
function __construct() {
// Get a language object so that we can use the generic UTF-8 uppercase
// function there
@@ -149,10 +154,22 @@ class IdentityCollation extends Collation {
}
class IcuCollation extends Collation {
- const FIRST_LETTER_VERSION = 1;
+ const FIRST_LETTER_VERSION = 2;
+
+ /** @var Collator */
+ private $primaryCollator;
+
+ /** @var Collator */
+ private $mainCollator;
+
+ /** @var string */
+ private $locale;
- var $primaryCollator, $mainCollator, $locale;
- var $firstLetterData;
+ /** @var Language */
+ protected $digitTransformLanguage;
+
+ /** @var array */
+ private $firstLetterData;
/**
* Unified CJK blocks.
@@ -163,7 +180,7 @@ class IcuCollation extends Collation {
* is pretty useless for sorting Chinese text anyway. Japanese and Korean
* blocks are not included here, because they are smaller and more useful.
*/
- static $cjkBlocks = array(
+ private static $cjkBlocks = array(
array( 0x2E80, 0x2EFF ), // CJK Radicals Supplement
array( 0x2F00, 0x2FDF ), // Kangxi Radicals
array( 0x2FF0, 0x2FFF ), // Ideographic Description Characters
@@ -202,14 +219,19 @@ class IcuCollation extends Collation {
* Empty arrays are intended; this signifies that the data for the language is
* available and that there are, in fact, no additional letters to consider.
*/
- static $tailoringFirstLetters = array(
+ private static $tailoringFirstLetters = array(
// Verified by native speakers
'be' => array( "Ё" ),
'be-tarask' => array( "Ё" ),
+ 'cy' => array( "Ch", "Dd", "Ff", "Ng", "Ll", "Ph", "Rh", "Th" ),
'en' => array(),
+ 'fa' => array( "آ", "ء", "ه" ),
'fi' => array( "Å", "Ä", "Ö" ),
+ 'fr' => array(),
'hu' => array( "Cs", "Dz", "Dzs", "Gy", "Ly", "Ny", "Ö", "Sz", "Ty", "Ü", "Zs" ),
+ 'is' => array( "Á", "Ð", "É", "Í", "Ó", "Ú", "Ý", "Þ", "Æ", "Ö", "Å" ),
'it' => array(),
+ 'lv' => array( "Č", "Ģ", "Ķ", "Ļ", "Ņ", "Š", "Ž" ),
'pl' => array( "Ą", "Ć", "Ę", "Ł", "Ń", "Ó", "Ś", "Ź", "Ż" ),
'pt' => array(),
'ru' => array(),
@@ -227,18 +249,15 @@ class IcuCollation extends Collation {
'ca' => array(),
'co' => array(),
'cs' => array( "Č", "Ch", "Ř", "Š", "Ž" ),
- 'cy' => array( "Ch", "Dd", "Ff", "Ng", "Ll", "Ph", "Rh", "Th" ),
'da' => array( "Æ", "Ø", "Å" ),
'de' => array(),
'dsb' => array( "Č", "Ć", "Dź", "Ě", "Ch", "Ł", "Ń", "Ŕ", "Š", "Ś", "Ž", "Ź" ),
'el' => array(),
'eo' => array( "Ĉ", "Ĝ", "Ĥ", "Ĵ", "Ŝ", "Ŭ" ),
'es' => array( "Ñ" ),
- 'et' => array( "Š", "Ž", "Õ", "Ä", "Ö", "Ü" ),
+ 'et' => array( "Š", "Ž", "Õ", "Ä", "Ö", "Ü", "W" ), // added W for CollationEt (xx-uca-et)
'eu' => array( "Ñ" ),
- 'fa' => array( "آ", "ء", "ه" ),
'fo' => array( "Á", "Ð", "Í", "Ó", "Ú", "Ý", "Æ", "Ø", "Å" ),
- 'fr' => array(),
'fur' => array( "À", "Á", "Â", "È", "Ì", "Ò", "Ù" ),
'fy' => array(),
'ga' => array(),
@@ -246,7 +265,6 @@ class IcuCollation extends Collation {
'gl' => array( "Ch", "Ll", "Ñ" ),
'hr' => array( "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ),
'hsb' => array( "Č", "Dź", "Ě", "Ch", "Ł", "Ń", "Ř", "Š", "Ć", "Ž" ),
- 'is' => array( "Á", "Ð", "É", "Í", "Ó", "Ú", "Ý", "Þ", "Æ", "Ö", "Å" ),
'kk' => array( "Ү", "І" ),
'kl' => array( "Æ", "Ø", "Å" ),
'ku' => array( "Ç", "Ê", "Î", "Ş", "Û" ),
@@ -254,7 +272,6 @@ class IcuCollation extends Collation {
'la' => array(),
'lb' => array(),
'lt' => array( "Č", "Š", "Ž" ),
- 'lv' => array( "Č", "Ģ", "Ķ", "Ļ", "Ņ", "Š", "Ž" ),
'mk' => array(),
'mo' => array( "Ă", "Â", "Î", "Ş", "Ţ" ),
'mt' => array( "Ċ", "Ġ", "Għ", "Ħ", "Ż" ),
@@ -284,7 +301,12 @@ class IcuCollation extends Collation {
throw new MWException( 'An ICU collation was requested, ' .
'but the intl extension is not available.' );
}
+
$this->locale = $locale;
+ // Drop everything after the '@' in locale's name
+ $localeParts = explode( '@', $locale );
+ $this->digitTransformLanguage = Language::factory( $locale === 'root' ? 'en' : $localeParts[0] );
+
$this->mainCollator = Collator::create( $locale );
if ( !$this->mainCollator ) {
throw new MWException( "Invalid ICU locale specified for collation: $locale" );
@@ -319,16 +341,14 @@ class IcuCollation extends Collation {
// Check for CJK
$firstChar = mb_substr( $string, 0, 1, 'UTF-8' );
- if ( ord( $firstChar ) > 0x7f
- && self::isCjk( utf8ToCodepoint( $firstChar ) ) )
- {
+ if ( ord( $firstChar ) > 0x7f && self::isCjk( utf8ToCodepoint( $firstChar ) ) ) {
return $firstChar;
}
$sortKey = $this->getPrimarySortKey( $string );
// Do a binary search to find the correct letter to sort under
- $min = $this->findLowerBound(
+ $min = ArrayUtils::findLowerBound(
array( $this, 'getSortKeyByLetterIndex' ),
$this->getFirstLetterCount(),
'strcmp',
@@ -347,7 +367,12 @@ class IcuCollation extends Collation {
}
$cache = wfGetCache( CACHE_ANYTHING );
- $cacheKey = wfMemcKey( 'first-letters', $this->locale );
+ $cacheKey = wfMemcKey(
+ 'first-letters',
+ $this->locale,
+ $this->digitTransformLanguage->getCode(),
+ self::getICUVersion()
+ );
$cacheEntry = $cache->get( $cacheKey );
if ( $cacheEntry && isset( $cacheEntry['version'] )
@@ -367,6 +392,12 @@ class IcuCollation extends Collation {
if ( isset( self::$tailoringFirstLetters['-' . $this->locale] ) ) {
$letters = array_diff( $letters, self::$tailoringFirstLetters['-' . $this->locale] );
}
+ // Apply digit transforms
+ $digits = array( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' );
+ $letters = array_diff( $letters, $digits );
+ foreach ( $digits as $digit ) {
+ $letters[] = $this->digitTransformLanguage->formatNum( $digit, true );
+ }
} else {
$letters = wfGetPrecompiledData( "first-letters-{$this->locale}.ser" );
if ( $letters === false ) {
@@ -459,7 +490,7 @@ class IcuCollation extends Collation {
$prev = $trimmedKey;
}
foreach ( $duplicatePrefixes as $badKey ) {
- wfDebug( "Removing '{$letterMap[$badKey]}' from first letters." );
+ wfDebug( "Removing '{$letterMap[$badKey]}' from first letters.\n" );
unset( $letterMap[$badKey] );
// This code assumes that unsetting does not change sort order.
}
@@ -499,53 +530,6 @@ class IcuCollation extends Collation {
return count( $this->firstLetterData['chars'] );
}
- /**
- * Do a binary search, and return the index of the largest item that sorts
- * less than or equal to the target value.
- *
- * @param array $valueCallback A function to call to get the value with
- * a given array index.
- * @param int $valueCount The number of items accessible via $valueCallback,
- * indexed from 0 to $valueCount - 1
- * @param array $comparisonCallback A callback to compare two values, returning
- * -1, 0 or 1 in the style of strcmp().
- * @param string $target The target value to find.
- *
- * @return int|bool The item index of the lower bound, or false if the target value
- * sorts before all items.
- */
- function findLowerBound( $valueCallback, $valueCount, $comparisonCallback, $target ) {
- if ( $valueCount === 0 ) {
- return false;
- }
-
- $min = 0;
- $max = $valueCount;
- do {
- $mid = $min + ( ( $max - $min ) >> 1 );
- $item = call_user_func( $valueCallback, $mid );
- $comparison = call_user_func( $comparisonCallback, $target, $item );
- if ( $comparison > 0 ) {
- $min = $mid;
- } elseif ( $comparison == 0 ) {
- $min = $mid;
- break;
- } else {
- $max = $mid;
- }
- } while ( $min < $max - 1 );
-
- if ( $min == 0 ) {
- $item = call_user_func( $valueCallback, $min );
- $comparison = call_user_func( $comparisonCallback, $target, $item );
- if ( $comparison < 0 ) {
- // Before the first item
- return false;
- }
- }
- return $min;
- }
-
static function isCjk( $codepoint ) {
foreach ( self::$cjkBlocks as $block ) {
if ( $codepoint >= $block[0] && $codepoint <= $block[1] ) {
@@ -565,7 +549,7 @@ class IcuCollation extends Collation {
* This function will return false on older PHPs.
*
* @since 1.21
- * @return string|false
+ * @return string|bool
*/
static function getICUVersion() {
return defined( 'INTL_ICU_VERSION' ) ? INTL_ICU_VERSION : false;
@@ -576,7 +560,7 @@ class IcuCollation extends Collation {
* currently in use, or false when it can't be determined.
*
* @since 1.21
- * @return string|false
+ * @return string|bool
*/
static function getUnicodeVersionForICU() {
$icuVersion = IcuCollation::getICUVersion();
@@ -606,3 +590,56 @@ class IcuCollation extends Collation {
}
}
}
+
+/**
+ * Workaround for the lack of support of Sorani Kurdish / Central Kurdish language ('ckb') in ICU.
+ *
+ * Uses the same collation rules as Persian / Farsi ('fa'), but different characters for digits.
+ */
+class CollationCkb extends IcuCollation {
+ function __construct() {
+ // This will set $locale and collators, which affect the actual sorting order
+ parent::__construct( 'fa' );
+ // Override the 'fa' language set by parent constructor, which affects #getFirstLetterData()
+ $this->digitTransformLanguage = Language::factory( 'ckb' );
+ }
+}
+
+/**
+ * Workaround for incorrect collation of Estonian language ('et') in ICU (bug 54168).
+ *
+ * 'W' and 'V' should not be considered the same letter for the purposes of collation in modern
+ * Estonian. We work around this by replacing 'W' and 'w' with 'ᴡ' U+1D21 'LATIN LETTER SMALL
+ * CAPITAL W' for sortkey generation, which is collated like 'W' and is not tailored to have the
+ * same primary weight as 'V' in Estonian.
+ */
+class CollationEt extends IcuCollation {
+ function __construct() {
+ parent::__construct( 'et' );
+ }
+
+ private static function mangle( $string ) {
+ return str_replace(
+ array( 'w', 'W' ),
+ 'ᴡ', // U+1D21 'LATIN LETTER SMALL CAPITAL W'
+ $string
+ );
+ }
+
+ private static function unmangle( $string ) {
+ // Casing data is lost…
+ return str_replace(
+ 'ᴡ', // U+1D21 'LATIN LETTER SMALL CAPITAL W'
+ 'W',
+ $string
+ );
+ }
+
+ function getSortKey( $string ) {
+ return parent::getSortKey( self::mangle( $string ) );
+ }
+
+ function getFirstLetter( $string ) {
+ return self::unmangle( parent::getFirstLetter( self::mangle( $string ) ) );
+ }
+}
diff --git a/includes/ConfEditor.php b/includes/ConfEditor.php
deleted file mode 100644
index 67cb87db..00000000
--- a/includes/ConfEditor.php
+++ /dev/null
@@ -1,1109 +0,0 @@
-<?php
-/**
- * Configuration file editor.
- *
- * 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
- */
-
-/**
- * This is a state machine style parser with two internal stacks:
- * * A next state stack, which determines the state the machine will progress to next
- * * A path stack, which keeps track of the logical location in the file.
- *
- * Reference grammar:
- *
- * file = T_OPEN_TAG *statement
- * statement = T_VARIABLE "=" expression ";"
- * expression = array / scalar / T_VARIABLE
- * array = T_ARRAY "(" [ element *( "," element ) [ "," ] ] ")"
- * element = assoc-element / expression
- * assoc-element = scalar T_DOUBLE_ARROW expression
- * scalar = T_LNUMBER / T_DNUMBER / T_STRING / T_CONSTANT_ENCAPSED_STRING
- */
-class ConfEditor {
- /** The text to parse */
- var $text;
-
- /** The token array from token_get_all() */
- var $tokens;
-
- /** The current position in the token array */
- var $pos;
-
- /** The current 1-based line number */
- var $lineNum;
-
- /** The current 1-based column number */
- var $colNum;
-
- /** The current 0-based byte number */
- var $byteNum;
-
- /** The current ConfEditorToken object */
- var $currentToken;
-
- /** The previous ConfEditorToken object */
- var $prevToken;
-
- /**
- * The state machine stack. This is an array of strings where the topmost
- * element will be popped off and become the next parser state.
- */
- var $stateStack;
-
- /**
- * The path stack is a stack of associative arrays with the following elements:
- * name The name of top level of the path
- * level The level (number of elements) of the path
- * startByte The byte offset of the start of the path
- * startToken The token offset of the start
- * endByte The byte offset of thee
- * endToken The token offset of the end, plus one
- * valueStartToken The start token offset of the value part
- * valueStartByte The start byte offset of the value part
- * valueEndToken The end token offset of the value part, plus one
- * valueEndByte The end byte offset of the value part, plus one
- * nextArrayIndex The next numeric array index at this level
- * hasComma True if the array element ends with a comma
- * arrowByte The byte offset of the "=>", or false if there isn't one
- */
- var $pathStack;
-
- /**
- * The elements of the top of the pathStack for every path encountered, indexed
- * by slash-separated path.
- */
- var $pathInfo;
-
- /**
- * Next serial number for whitespace placeholder paths (\@extra-N)
- */
- var $serial;
-
- /**
- * Editor state. This consists of the internal copy/insert operations which
- * are applied to the source string to obtain the destination string.
- */
- var $edits;
-
- /**
- * Simple entry point for command-line testing
- *
- * @param $text string
- *
- * @return string
- */
- static function test( $text ) {
- try {
- $ce = new self( $text );
- $ce->parse();
- } catch ( ConfEditorParseError $e ) {
- return $e->getMessage() . "\n" . $e->highlight( $text );
- }
- return "OK";
- }
-
- /**
- * Construct a new parser
- */
- public function __construct( $text ) {
- $this->text = $text;
- }
-
- /**
- * Edit the text. Returns the edited text.
- * @param array $ops of operations.
- *
- * Operations are given as an associative array, with members:
- * type: One of delete, set, append or insert (required)
- * path: The path to operate on (required)
- * key: The array key to insert/append, with PHP quotes
- * value: The value, with PHP quotes
- *
- * delete
- * Deletes an array element or statement with the specified path.
- * e.g.
- * array('type' => 'delete', 'path' => '$foo/bar/baz' )
- * is equivalent to the runtime PHP code:
- * unset( $foo['bar']['baz'] );
- *
- * set
- * Sets the value of an array element. If the element doesn't exist, it
- * is appended to the array. If it does exist, the value is set, with
- * comments and indenting preserved.
- *
- * append
- * Appends a new element to the end of the array. Adds a trailing comma.
- * e.g.
- * array( 'type' => 'append', 'path', '$foo/bar',
- * 'key' => 'baz', 'value' => "'x'" )
- * is like the PHP code:
- * $foo['bar']['baz'] = 'x';
- *
- * insert
- * Insert a new element at the start of the array.
- *
- * @throws MWException
- * @return string
- */
- public function edit( $ops ) {
- $this->parse();
-
- $this->edits = array(
- array( 'copy', 0, strlen( $this->text ) )
- );
- foreach ( $ops as $op ) {
- $type = $op['type'];
- $path = $op['path'];
- $value = isset( $op['value'] ) ? $op['value'] : null;
- $key = isset( $op['key'] ) ? $op['key'] : null;
-
- switch ( $type ) {
- case 'delete':
- list( $start, $end ) = $this->findDeletionRegion( $path );
- $this->replaceSourceRegion( $start, $end, false );
- break;
- case 'set':
- if ( isset( $this->pathInfo[$path] ) ) {
- list( $start, $end ) = $this->findValueRegion( $path );
- $encValue = $value; // var_export( $value, true );
- $this->replaceSourceRegion( $start, $end, $encValue );
- break;
- }
- // No existing path, fall through to append
- $slashPos = strrpos( $path, '/' );
- $key = var_export( substr( $path, $slashPos + 1 ), true );
- $path = substr( $path, 0, $slashPos );
- // Fall through
- case 'append':
- // Find the last array element
- $lastEltPath = $this->findLastArrayElement( $path );
- if ( $lastEltPath === false ) {
- throw new MWException( "Can't find any element of array \"$path\"" );
- }
- $lastEltInfo = $this->pathInfo[$lastEltPath];
-
- // Has it got a comma already?
- if ( strpos( $lastEltPath, '@extra' ) === false && !$lastEltInfo['hasComma'] ) {
- // No comma, insert one after the value region
- list( , $end ) = $this->findValueRegion( $lastEltPath );
- $this->replaceSourceRegion( $end - 1, $end - 1, ',' );
- }
-
- // Make the text to insert
- list( $start, $end ) = $this->findDeletionRegion( $lastEltPath );
-
- if ( $key === null ) {
- list( $indent, ) = $this->getIndent( $start );
- $textToInsert = "$indent$value,";
- } else {
- list( $indent, $arrowIndent ) =
- $this->getIndent( $start, $key, $lastEltInfo['arrowByte'] );
- $textToInsert = "$indent$key$arrowIndent=> $value,";
- }
- $textToInsert .= ( $indent === false ? ' ' : "\n" );
-
- // Insert the item
- $this->replaceSourceRegion( $end, $end, $textToInsert );
- break;
- case 'insert':
- // Find first array element
- $firstEltPath = $this->findFirstArrayElement( $path );
- if ( $firstEltPath === false ) {
- throw new MWException( "Can't find array element of \"$path\"" );
- }
- list( $start, ) = $this->findDeletionRegion( $firstEltPath );
- $info = $this->pathInfo[$firstEltPath];
-
- // Make the text to insert
- if ( $key === null ) {
- list( $indent, ) = $this->getIndent( $start );
- $textToInsert = "$indent$value,";
- } else {
- list( $indent, $arrowIndent ) =
- $this->getIndent( $start, $key, $info['arrowByte'] );
- $textToInsert = "$indent$key$arrowIndent=> $value,";
- }
- $textToInsert .= ( $indent === false ? ' ' : "\n" );
-
- // Insert the item
- $this->replaceSourceRegion( $start, $start, $textToInsert );
- break;
- default:
- throw new MWException( "Unrecognised operation: \"$type\"" );
- }
- }
-
- // Do the edits
- $out = '';
- foreach ( $this->edits as $edit ) {
- if ( $edit[0] == 'copy' ) {
- $out .= substr( $this->text, $edit[1], $edit[2] - $edit[1] );
- } else { // if ( $edit[0] == 'insert' )
- $out .= $edit[1];
- }
- }
-
- // Do a second parse as a sanity check
- $this->text = $out;
- try {
- $this->parse();
- } catch ( ConfEditorParseError $e ) {
- throw new MWException(
- "Sorry, ConfEditor broke the file during editing and it won't parse anymore: " .
- $e->getMessage() );
- }
- return $out;
- }
-
- /**
- * Get the variables defined in the text
- * @return array( varname => value )
- */
- function getVars() {
- $vars = array();
- $this->parse();
- foreach ( $this->pathInfo as $path => $data ) {
- if ( $path[0] != '$' ) {
- continue;
- }
- $trimmedPath = substr( $path, 1 );
- $name = $data['name'];
- if ( $name[0] == '@' ) {
- continue;
- }
- if ( $name[0] == '$' ) {
- $name = substr( $name, 1 );
- }
- $parentPath = substr( $trimmedPath, 0,
- strlen( $trimmedPath ) - strlen( $name ) );
- if ( substr( $parentPath, -1 ) == '/' ) {
- $parentPath = substr( $parentPath, 0, -1 );
- }
-
- $value = substr( $this->text, $data['valueStartByte'],
- $data['valueEndByte'] - $data['valueStartByte']
- );
- $this->setVar( $vars, $parentPath, $name,
- $this->parseScalar( $value ) );
- }
- return $vars;
- }
-
- /**
- * Set a value in an array, unless it's set already. For instance,
- * setVar( $arr, 'foo/bar', 'baz', 3 ); will set
- * $arr['foo']['bar']['baz'] = 3;
- * @param $array array
- * @param string $path slash-delimited path
- * @param $key mixed Key
- * @param $value mixed Value
- */
- function setVar( &$array, $path, $key, $value ) {
- $pathArr = explode( '/', $path );
- $target =& $array;
- if ( $path !== '' ) {
- foreach ( $pathArr as $p ) {
- if ( !isset( $target[$p] ) ) {
- $target[$p] = array();
- }
- $target =& $target[$p];
- }
- }
- if ( !isset( $target[$key] ) ) {
- $target[$key] = $value;
- }
- }
-
- /**
- * Parse a scalar value in PHP
- * @return mixed Parsed value
- */
- function parseScalar( $str ) {
- if ( $str !== '' && $str[0] == '\'' ) {
- // Single-quoted string
- // @todo FIXME: trim() call is due to mystery bug where whitespace gets
- // appended to the token; without it we ended up reading in the
- // extra quote on the end!
- return strtr( substr( trim( $str ), 1, -1 ),
- array( '\\\'' => '\'', '\\\\' => '\\' ) );
- }
- if ( $str !== '' && $str[0] == '"' ) {
- // Double-quoted string
- // @todo FIXME: trim() call is due to mystery bug where whitespace gets
- // appended to the token; without it we ended up reading in the
- // extra quote on the end!
- return stripcslashes( substr( trim( $str ), 1, -1 ) );
- }
- if ( substr( $str, 0, 4 ) == 'true' ) {
- return true;
- }
- if ( substr( $str, 0, 5 ) == 'false' ) {
- return false;
- }
- if ( substr( $str, 0, 4 ) == 'null' ) {
- return null;
- }
- // Must be some kind of numeric value, so let PHP's weak typing
- // be useful for a change
- return $str;
- }
-
- /**
- * Replace the byte offset region of the source with $newText.
- * Works by adding elements to the $this->edits array.
- */
- function replaceSourceRegion( $start, $end, $newText = false ) {
- // Split all copy operations with a source corresponding to the region
- // in question.
- $newEdits = array();
- foreach ( $this->edits as $edit ) {
- if ( $edit[0] !== 'copy' ) {
- $newEdits[] = $edit;
- continue;
- }
- $copyStart = $edit[1];
- $copyEnd = $edit[2];
- if ( $start >= $copyEnd || $end <= $copyStart ) {
- // Outside this region
- $newEdits[] = $edit;
- continue;
- }
- if ( ( $start < $copyStart && $end > $copyStart )
- || ( $start < $copyEnd && $end > $copyEnd )
- ) {
- throw new MWException( "Overlapping regions found, can't do the edit" );
- }
- // Split the copy
- $newEdits[] = array( 'copy', $copyStart, $start );
- if ( $newText !== false ) {
- $newEdits[] = array( 'insert', $newText );
- }
- $newEdits[] = array( 'copy', $end, $copyEnd );
- }
- $this->edits = $newEdits;
- }
-
- /**
- * Finds the source byte region which you would want to delete, if $pathName
- * was to be deleted. Includes the leading spaces and tabs, the trailing line
- * break, and any comments in between.
- * @param $pathName
- * @throws MWException
- * @return array
- */
- function findDeletionRegion( $pathName ) {
- if ( !isset( $this->pathInfo[$pathName] ) ) {
- throw new MWException( "Can't find path \"$pathName\"" );
- }
- $path = $this->pathInfo[$pathName];
- // Find the start
- $this->firstToken();
- while ( $this->pos != $path['startToken'] ) {
- $this->nextToken();
- }
- $regionStart = $path['startByte'];
- for ( $offset = -1; $offset >= -$this->pos; $offset-- ) {
- $token = $this->getTokenAhead( $offset );
- if ( !$token->isSkip() ) {
- // If there is other content on the same line, don't move the start point
- // back, because that will cause the regions to overlap.
- $regionStart = $path['startByte'];
- break;
- }
- $lfPos = strrpos( $token->text, "\n" );
- if ( $lfPos === false ) {
- $regionStart -= strlen( $token->text );
- } else {
- // The line start does not include the LF
- $regionStart -= strlen( $token->text ) - $lfPos - 1;
- break;
- }
- }
- // Find the end
- while ( $this->pos != $path['endToken'] ) {
- $this->nextToken();
- }
- $regionEnd = $path['endByte']; // past the end
- for ( $offset = 0; $offset < count( $this->tokens ) - $this->pos; $offset++ ) {
- $token = $this->getTokenAhead( $offset );
- if ( !$token->isSkip() ) {
- break;
- }
- $lfPos = strpos( $token->text, "\n" );
- if ( $lfPos === false ) {
- $regionEnd += strlen( $token->text );
- } else {
- // This should point past the LF
- $regionEnd += $lfPos + 1;
- break;
- }
- }
- return array( $regionStart, $regionEnd );
- }
-
- /**
- * Find the byte region in the source corresponding to the value part.
- * This includes the quotes, but does not include the trailing comma
- * or semicolon.
- *
- * The end position is the past-the-end (end + 1) value as per convention.
- * @param $pathName
- * @throws MWException
- * @return array
- */
- function findValueRegion( $pathName ) {
- if ( !isset( $this->pathInfo[$pathName] ) ) {
- throw new MWException( "Can't find path \"$pathName\"" );
- }
- $path = $this->pathInfo[$pathName];
- if ( $path['valueStartByte'] === false || $path['valueEndByte'] === false ) {
- throw new MWException( "Can't find value region for path \"$pathName\"" );
- }
- return array( $path['valueStartByte'], $path['valueEndByte'] );
- }
-
- /**
- * Find the path name of the last element in the array.
- * If the array is empty, this will return the \@extra interstitial element.
- * If the specified path is not found or is not an array, it will return false.
- * @return bool|int|string
- */
- function findLastArrayElement( $path ) {
- // Try for a real element
- $lastEltPath = false;
- foreach ( $this->pathInfo as $candidatePath => $info ) {
- $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
- $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 );
- if ( $part2 == '@' ) {
- // Do nothing
- } elseif ( $part1 == "$path/" ) {
- $lastEltPath = $candidatePath;
- } elseif ( $lastEltPath !== false ) {
- break;
- }
- }
- if ( $lastEltPath !== false ) {
- return $lastEltPath;
- }
-
- // Try for an interstitial element
- $extraPath = false;
- foreach ( $this->pathInfo as $candidatePath => $info ) {
- $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
- if ( $part1 == "$path/" ) {
- $extraPath = $candidatePath;
- } elseif ( $extraPath !== false ) {
- break;
- }
- }
- return $extraPath;
- }
-
- /**
- * Find the path name of first element in the array.
- * If the array is empty, this will return the \@extra interstitial element.
- * If the specified path is not found or is not an array, it will return false.
- * @return bool|int|string
- */
- function findFirstArrayElement( $path ) {
- // Try for an ordinary element
- foreach ( $this->pathInfo as $candidatePath => $info ) {
- $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
- $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 );
- if ( $part1 == "$path/" && $part2 != '@' ) {
- return $candidatePath;
- }
- }
-
- // Try for an interstitial element
- foreach ( $this->pathInfo as $candidatePath => $info ) {
- $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
- if ( $part1 == "$path/" ) {
- return $candidatePath;
- }
- }
- return false;
- }
-
- /**
- * Get the indent string which sits after a given start position.
- * Returns false if the position is not at the start of the line.
- * @return array
- */
- function getIndent( $pos, $key = false, $arrowPos = false ) {
- $arrowIndent = ' ';
- if ( $pos == 0 || $this->text[$pos - 1] == "\n" ) {
- $indentLength = strspn( $this->text, " \t", $pos );
- $indent = substr( $this->text, $pos, $indentLength );
- } else {
- $indent = false;
- }
- if ( $indent !== false && $arrowPos !== false ) {
- $arrowIndentLength = $arrowPos - $pos - $indentLength - strlen( $key );
- if ( $arrowIndentLength > 0 ) {
- $arrowIndent = str_repeat( ' ', $arrowIndentLength );
- }
- }
- return array( $indent, $arrowIndent );
- }
-
- /**
- * Run the parser on the text. Throws an exception if the string does not
- * match our defined subset of PHP syntax.
- */
- public function parse() {
- $this->initParse();
- $this->pushState( 'file' );
- $this->pushPath( '@extra-' . ( $this->serial++ ) );
- $token = $this->firstToken();
-
- while ( !$token->isEnd() ) {
- $state = $this->popState();
- if ( !$state ) {
- $this->error( 'internal error: empty state stack' );
- }
-
- switch ( $state ) {
- case 'file':
- $this->expect( T_OPEN_TAG );
- $token = $this->skipSpace();
- if ( $token->isEnd() ) {
- break 2;
- }
- $this->pushState( 'statement', 'file 2' );
- break;
- case 'file 2':
- $token = $this->skipSpace();
- if ( $token->isEnd() ) {
- break 2;
- }
- $this->pushState( 'statement', 'file 2' );
- break;
- case 'statement':
- $token = $this->skipSpace();
- if ( !$this->validatePath( $token->text ) ) {
- $this->error( "Invalid variable name \"{$token->text}\"" );
- }
- $this->nextPath( $token->text );
- $this->expect( T_VARIABLE );
- $this->skipSpace();
- $arrayAssign = false;
- if ( $this->currentToken()->type == '[' ) {
- $this->nextToken();
- $token = $this->skipSpace();
- if ( !$token->isScalar() ) {
- $this->error( "expected a string or number for the array key" );
- }
- if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
- $text = $this->parseScalar( $token->text );
- } else {
- $text = $token->text;
- }
- if ( !$this->validatePath( $text ) ) {
- $this->error( "Invalid associative array name \"$text\"" );
- }
- $this->pushPath( $text );
- $this->nextToken();
- $this->skipSpace();
- $this->expect( ']' );
- $this->skipSpace();
- $arrayAssign = true;
- }
- $this->expect( '=' );
- $this->skipSpace();
- $this->startPathValue();
- if ( $arrayAssign ) {
- $this->pushState( 'expression', 'array assign end' );
- } else {
- $this->pushState( 'expression', 'statement end' );
- }
- break;
- case 'array assign end':
- case 'statement end':
- $this->endPathValue();
- if ( $state == 'array assign end' ) {
- $this->popPath();
- }
- $this->skipSpace();
- $this->expect( ';' );
- $this->nextPath( '@extra-' . ( $this->serial++ ) );
- break;
- case 'expression':
- $token = $this->skipSpace();
- if ( $token->type == T_ARRAY ) {
- $this->pushState( 'array' );
- } elseif ( $token->isScalar() ) {
- $this->nextToken();
- } elseif ( $token->type == T_VARIABLE ) {
- $this->nextToken();
- } else {
- $this->error( "expected simple expression" );
- }
- break;
- case 'array':
- $this->skipSpace();
- $this->expect( T_ARRAY );
- $this->skipSpace();
- $this->expect( '(' );
- $this->skipSpace();
- $this->pushPath( '@extra-' . ( $this->serial++ ) );
- if ( $this->isAhead( ')' ) ) {
- // Empty array
- $this->pushState( 'array end' );
- } else {
- $this->pushState( 'element', 'array end' );
- }
- break;
- case 'array end':
- $this->skipSpace();
- $this->popPath();
- $this->expect( ')' );
- break;
- case 'element':
- $token = $this->skipSpace();
- // Look ahead to find the double arrow
- if ( $token->isScalar() && $this->isAhead( T_DOUBLE_ARROW, 1 ) ) {
- // Found associative element
- $this->pushState( 'assoc-element', 'element end' );
- } else {
- // Not associative
- $this->nextPath( '@next' );
- $this->startPathValue();
- $this->pushState( 'expression', 'element end' );
- }
- break;
- case 'element end':
- $token = $this->skipSpace();
- if ( $token->type == ',' ) {
- $this->endPathValue();
- $this->markComma();
- $this->nextToken();
- $this->nextPath( '@extra-' . ( $this->serial++ ) );
- // Look ahead to find ending bracket
- if ( $this->isAhead( ")" ) ) {
- // Found ending bracket, no continuation
- $this->skipSpace();
- } else {
- // No ending bracket, continue to next element
- $this->pushState( 'element' );
- }
- } elseif ( $token->type == ')' ) {
- // End array
- $this->endPathValue();
- } else {
- $this->error( "expected the next array element or the end of the array" );
- }
- break;
- case 'assoc-element':
- $token = $this->skipSpace();
- if ( !$token->isScalar() ) {
- $this->error( "expected a string or number for the array key" );
- }
- if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
- $text = $this->parseScalar( $token->text );
- } else {
- $text = $token->text;
- }
- if ( !$this->validatePath( $text ) ) {
- $this->error( "Invalid associative array name \"$text\"" );
- }
- $this->nextPath( $text );
- $this->nextToken();
- $this->skipSpace();
- $this->markArrow();
- $this->expect( T_DOUBLE_ARROW );
- $this->skipSpace();
- $this->startPathValue();
- $this->pushState( 'expression' );
- break;
- }
- }
- if ( count( $this->stateStack ) ) {
- $this->error( 'unexpected end of file' );
- }
- $this->popPath();
- }
-
- /**
- * Initialise a parse.
- */
- protected function initParse() {
- $this->tokens = token_get_all( $this->text );
- $this->stateStack = array();
- $this->pathStack = array();
- $this->firstToken();
- $this->pathInfo = array();
- $this->serial = 1;
- }
-
- /**
- * Set the parse position. Do not call this except from firstToken() and
- * nextToken(), there is more to update than just the position.
- */
- protected function setPos( $pos ) {
- $this->pos = $pos;
- if ( $this->pos >= count( $this->tokens ) ) {
- $this->currentToken = ConfEditorToken::newEnd();
- } else {
- $this->currentToken = $this->newTokenObj( $this->tokens[$this->pos] );
- }
- return $this->currentToken;
- }
-
- /**
- * Create a ConfEditorToken from an element of token_get_all()
- * @return ConfEditorToken
- */
- function newTokenObj( $internalToken ) {
- if ( is_array( $internalToken ) ) {
- return new ConfEditorToken( $internalToken[0], $internalToken[1] );
- } else {
- return new ConfEditorToken( $internalToken, $internalToken );
- }
- }
-
- /**
- * Reset the parse position
- */
- function firstToken() {
- $this->setPos( 0 );
- $this->prevToken = ConfEditorToken::newEnd();
- $this->lineNum = 1;
- $this->colNum = 1;
- $this->byteNum = 0;
- return $this->currentToken;
- }
-
- /**
- * Get the current token
- */
- function currentToken() {
- return $this->currentToken;
- }
-
- /**
- * Advance the current position and return the resulting next token
- */
- function nextToken() {
- if ( $this->currentToken ) {
- $text = $this->currentToken->text;
- $lfCount = substr_count( $text, "\n" );
- if ( $lfCount ) {
- $this->lineNum += $lfCount;
- $this->colNum = strlen( $text ) - strrpos( $text, "\n" );
- } else {
- $this->colNum += strlen( $text );
- }
- $this->byteNum += strlen( $text );
- }
- $this->prevToken = $this->currentToken;
- $this->setPos( $this->pos + 1 );
- return $this->currentToken;
- }
-
- /**
- * Get the token $offset steps ahead of the current position.
- * $offset may be negative, to get tokens behind the current position.
- * @return ConfEditorToken
- */
- function getTokenAhead( $offset ) {
- $pos = $this->pos + $offset;
- if ( $pos >= count( $this->tokens ) || $pos < 0 ) {
- return ConfEditorToken::newEnd();
- } else {
- return $this->newTokenObj( $this->tokens[$pos] );
- }
- }
-
- /**
- * Advances the current position past any whitespace or comments
- */
- function skipSpace() {
- while ( $this->currentToken && $this->currentToken->isSkip() ) {
- $this->nextToken();
- }
- return $this->currentToken;
- }
-
- /**
- * Throws an error if the current token is not of the given type, and
- * then advances to the next position.
- */
- function expect( $type ) {
- if ( $this->currentToken && $this->currentToken->type == $type ) {
- return $this->nextToken();
- } else {
- $this->error( "expected " . $this->getTypeName( $type ) .
- ", got " . $this->getTypeName( $this->currentToken->type ) );
- }
- }
-
- /**
- * Push a state or two on to the state stack.
- */
- function pushState( $nextState, $stateAfterThat = null ) {
- if ( $stateAfterThat !== null ) {
- $this->stateStack[] = $stateAfterThat;
- }
- $this->stateStack[] = $nextState;
- }
-
- /**
- * Pop a state from the state stack.
- * @return mixed
- */
- function popState() {
- return array_pop( $this->stateStack );
- }
-
- /**
- * Returns true if the user input path is valid.
- * This exists to allow "/" and "@" to be reserved for string path keys
- * @return bool
- */
- function validatePath( $path ) {
- return strpos( $path, '/' ) === false && substr( $path, 0, 1 ) != '@';
- }
-
- /**
- * Internal function to update some things at the end of a path region. Do
- * not call except from popPath() or nextPath().
- */
- function endPath() {
- $key = '';
- foreach ( $this->pathStack as $pathInfo ) {
- if ( $key !== '' ) {
- $key .= '/';
- }
- $key .= $pathInfo['name'];
- }
- $pathInfo['endByte'] = $this->byteNum;
- $pathInfo['endToken'] = $this->pos;
- $this->pathInfo[$key] = $pathInfo;
- }
-
- /**
- * Go up to a new path level, for example at the start of an array.
- */
- function pushPath( $path ) {
- $this->pathStack[] = array(
- 'name' => $path,
- 'level' => count( $this->pathStack ) + 1,
- 'startByte' => $this->byteNum,
- 'startToken' => $this->pos,
- 'valueStartToken' => false,
- 'valueStartByte' => false,
- 'valueEndToken' => false,
- 'valueEndByte' => false,
- 'nextArrayIndex' => 0,
- 'hasComma' => false,
- 'arrowByte' => false
- );
- }
-
- /**
- * Go down a path level, for example at the end of an array.
- */
- function popPath() {
- $this->endPath();
- array_pop( $this->pathStack );
- }
-
- /**
- * Go to the next path on the same level. This ends the current path and
- * starts a new one. If $path is \@next, the new path is set to the next
- * numeric array element.
- */
- function nextPath( $path ) {
- $this->endPath();
- $i = count( $this->pathStack ) - 1;
- if ( $path == '@next' ) {
- $nextArrayIndex =& $this->pathStack[$i]['nextArrayIndex'];
- $this->pathStack[$i]['name'] = $nextArrayIndex;
- $nextArrayIndex++;
- } else {
- $this->pathStack[$i]['name'] = $path;
- }
- $this->pathStack[$i] =
- array(
- 'startByte' => $this->byteNum,
- 'startToken' => $this->pos,
- 'valueStartToken' => false,
- 'valueStartByte' => false,
- 'valueEndToken' => false,
- 'valueEndByte' => false,
- 'hasComma' => false,
- 'arrowByte' => false,
- ) + $this->pathStack[$i];
- }
-
- /**
- * Mark the start of the value part of a path.
- */
- function startPathValue() {
- $path =& $this->pathStack[count( $this->pathStack ) - 1];
- $path['valueStartToken'] = $this->pos;
- $path['valueStartByte'] = $this->byteNum;
- }
-
- /**
- * Mark the end of the value part of a path.
- */
- function endPathValue() {
- $path =& $this->pathStack[count( $this->pathStack ) - 1];
- $path['valueEndToken'] = $this->pos;
- $path['valueEndByte'] = $this->byteNum;
- }
-
- /**
- * Mark the comma separator in an array element
- */
- function markComma() {
- $path =& $this->pathStack[count( $this->pathStack ) - 1];
- $path['hasComma'] = true;
- }
-
- /**
- * Mark the arrow separator in an associative array element
- */
- function markArrow() {
- $path =& $this->pathStack[count( $this->pathStack ) - 1];
- $path['arrowByte'] = $this->byteNum;
- }
-
- /**
- * Generate a parse error
- */
- function error( $msg ) {
- throw new ConfEditorParseError( $this, $msg );
- }
-
- /**
- * Get a readable name for the given token type.
- * @return string
- */
- function getTypeName( $type ) {
- if ( is_int( $type ) ) {
- return token_name( $type );
- } else {
- return "\"$type\"";
- }
- }
-
- /**
- * Looks ahead to see if the given type is the next token type, starting
- * from the current position plus the given offset. Skips any intervening
- * whitespace.
- * @return bool
- */
- function isAhead( $type, $offset = 0 ) {
- $ahead = $offset;
- $token = $this->getTokenAhead( $offset );
- while ( !$token->isEnd() ) {
- if ( $token->isSkip() ) {
- $ahead++;
- $token = $this->getTokenAhead( $ahead );
- continue;
- } elseif ( $token->type == $type ) {
- // Found the type
- return true;
- } else {
- // Not found
- return false;
- }
- }
- return false;
- }
-
- /**
- * Get the previous token object
- */
- function prevToken() {
- return $this->prevToken;
- }
-
- /**
- * Echo a reasonably readable representation of the tokenizer array.
- */
- function dumpTokens() {
- $out = '';
- foreach ( $this->tokens as $token ) {
- $obj = $this->newTokenObj( $token );
- $out .= sprintf( "%-28s %s\n",
- $this->getTypeName( $obj->type ),
- addcslashes( $obj->text, "\0..\37" ) );
- }
- echo "<pre>" . htmlspecialchars( $out ) . "</pre>";
- }
-}
-
-/**
- * Exception class for parse errors
- */
-class ConfEditorParseError extends MWException {
- var $lineNum, $colNum;
- function __construct( $editor, $msg ) {
- $this->lineNum = $editor->lineNum;
- $this->colNum = $editor->colNum;
- parent::__construct( "Parse error on line {$editor->lineNum} " .
- "col {$editor->colNum}: $msg" );
- }
-
- function highlight( $text ) {
- $lines = StringUtils::explode( "\n", $text );
- foreach ( $lines as $lineNum => $line ) {
- if ( $lineNum == $this->lineNum - 1 ) {
- return "$line\n" . str_repeat( ' ', $this->colNum - 1 ) . "^\n";
- }
- }
- return '';
- }
-
-}
-
-/**
- * Class to wrap a token from the tokenizer.
- */
-class ConfEditorToken {
- var $type, $text;
-
- static $scalarTypes = array( T_LNUMBER, T_DNUMBER, T_STRING, T_CONSTANT_ENCAPSED_STRING );
- static $skipTypes = array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT );
-
- static function newEnd() {
- return new self( 'END', '' );
- }
-
- function __construct( $type, $text ) {
- $this->type = $type;
- $this->text = $text;
- }
-
- function isSkip() {
- return in_array( $this->type, self::$skipTypes );
- }
-
- function isScalar() {
- return in_array( $this->type, self::$scalarTypes );
- }
-
- function isEnd() {
- return $this->type == 'END';
- }
-}
diff --git a/includes/Cookie.php b/includes/Cookie.php
index ecf4667d..cb041904 100644
--- a/includes/Cookie.php
+++ b/includes/Cookie.php
@@ -43,8 +43,8 @@ class Cookie {
* cookies. Used internally after a request to parse the
* Set-Cookie headers.
*
- * @param string $value the value of the cookie
- * @param array $attr possible key/values:
+ * @param string $value The value of the cookie
+ * @param array $attr Possible key/values:
* expires A date string
* path The path this cookie is used on
* domain Domain this cookie is used on
@@ -85,18 +85,21 @@ class Cookie {
* @todo fixme fails to detect 2-letter top-level domains for single-domain use (probably
* not a big problem in practice, but there are test cases)
*
- * @param string $domain the domain to validate
+ * @param string $domain The domain to validate
* @param string $originDomain (optional) the domain the cookie originates from
- * @return Boolean
+ * @return bool
*/
public static function validateCookieDomain( $domain, $originDomain = null ) {
- // Don't allow a trailing dot
- if ( substr( $domain, -1 ) == '.' ) {
+ $dc = explode( ".", $domain );
+
+ // Don't allow a trailing dot or addresses without a or just a leading dot
+ if ( substr( $domain, -1 ) == '.' ||
+ count( $dc ) <= 1 ||
+ count( $dc ) == 2 && $dc[0] === ''
+ ) {
return false;
}
- $dc = explode( ".", $domain );
-
// Only allow full, valid IP addresses
if ( preg_match( '/^[0-9.]+$/', $domain ) ) {
if ( count( $dc ) != 4 ) {
@@ -131,8 +134,14 @@ class Cookie {
}
if ( substr( $domain, 0, 1 ) == '.'
- && substr_compare( $originDomain, $domain, -strlen( $domain ),
- strlen( $domain ), true ) != 0 ) {
+ && substr_compare(
+ $originDomain,
+ $domain,
+ -strlen( $domain ),
+ strlen( $domain ),
+ true
+ ) != 0
+ ) {
return false;
}
}
@@ -143,9 +152,9 @@ class Cookie {
/**
* Serialize the cookie jar into a format useful for HTTP Request headers.
*
- * @param string $path the path that will be used. Required.
- * @param string $domain the domain that will be used. Required.
- * @return String
+ * @param string $path The path that will be used. Required.
+ * @param string $domain The domain that will be used. Required.
+ * @return string
*/
public function serializeToHttpRequest( $path, $domain ) {
$ret = '';
@@ -160,15 +169,22 @@ class Cookie {
}
/**
- * @param $domain
+ * @param string $domain
* @return bool
*/
protected function canServeDomain( $domain ) {
if ( $domain == $this->domain
|| ( strlen( $domain ) > strlen( $this->domain )
&& substr( $this->domain, 0, 1 ) == '.'
- && substr_compare( $domain, $this->domain, -strlen( $this->domain ),
- strlen( $this->domain ), true ) == 0 ) ) {
+ && substr_compare(
+ $domain,
+ $this->domain,
+ -strlen( $this->domain ),
+ strlen( $this->domain ),
+ true
+ ) == 0
+ )
+ ) {
return true;
}
@@ -176,7 +192,7 @@ class Cookie {
}
/**
- * @param $path
+ * @param string $path
* @return bool
*/
protected function canServePath( $path ) {
@@ -197,6 +213,9 @@ class CookieJar {
/**
* Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
* @see Cookie::set()
+ * @param string $name
+ * @param string $value
+ * @param array $attr
*/
public function setCookie( $name, $value, $attr ) {
/* cookies: case insensitive, so this should work.
@@ -213,6 +232,8 @@ class CookieJar {
/**
* @see Cookie::serializeToHttpRequest
+ * @param string $path
+ * @param string $domain
* @return string
*/
public function serializeToHttpRequest( $path, $domain ) {
@@ -232,8 +253,8 @@ class CookieJar {
/**
* Parse the content of an Set-Cookie HTTP Response header.
*
- * @param $cookie String
- * @param string $domain cookie's domain
+ * @param string $cookie
+ * @param string $domain Cookie's domain
* @return null
*/
public function parseCookieResponseHeader( $cookie, $domain ) {
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 78568107..71268932 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -15,7 +15,7 @@
* performed in LocalSettings.php.
*
* Documentation is in the source and on:
- * http://www.mediawiki.org/wiki/Manual:Configuration_settings
+ * https://www.mediawiki.org/wiki/Manual:Configuration_settings
*
* @warning Note: this (and other things) will break if the autoloader is not
* enabled. Please include includes/AutoLoader.php before including this file.
@@ -60,10 +60,22 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$wgConf = new SiteConfiguration;
/**
+ * Registry of factory functions to create config objects:
+ * The 'main' key must be set, and the value should be a valid
+ * callable.
+ * @since 1.23
+ */
+$wgConfigRegistry = array(
+ 'main' => 'GlobalVarConfig::newInstance'
+);
+
+/**
* MediaWiki version number
+ * Note that MediaWikiVersionFetcher::fetchVersion() uses a regex to check this.
+ * Using single quotes is, therefore, important here.
* @since 1.2
*/
-$wgVersion = '1.22.15';
+$wgVersion = '1.24.1';
/**
* Name of the site. It must be changed in LocalSettings.php
@@ -97,6 +109,13 @@ $wgServer = WebRequest::detectServer();
*/
$wgCanonicalServer = false;
+/**
+ * Server name. This is automatically computed by parsing the bare
+ * hostname out of $wgCanonicalServer. It should not be customized.
+ * @since 1.24
+ */
+$wgServerName = false;
+
/************************************************************************//**
* @name Script path settings
* @{
@@ -236,7 +255,7 @@ $wgFileCacheDirectory = false;
/**
* The URL path of the wiki logo. The logo size should be 135x135 pixels.
- * Defaults to "{$wgStylePath}/common/images/wiki.png".
+ * Defaults to "$wgResourceBasePath/resources/assets/wiki.png".
*/
$wgLogo = false;
@@ -339,11 +358,6 @@ $wgEnableAsyncUploads = false;
$wgIllegalFileChars = ":";
/**
- * @deprecated since 1.17 use $wgDeletedDirectory
- */
-$wgFileStore = array();
-
-/**
* What directory to place deleted uploads in.
* Defaults to "{$wgUploadDirectory}/deleted".
*/
@@ -355,11 +369,20 @@ $wgDeletedDirectory = false;
$wgImgAuthDetails = false;
/**
- * If this is enabled, img_auth.php will not allow image access unless the wiki
- * is private. This improves security when image uploads are hosted on a
- * separate domain.
+ * Map of relative URL directories to match to internal mwstore:// base storage paths.
+ * For img_auth.php requests, everything after "img_auth.php/" is checked to see
+ * if starts with any of the prefixes defined here. The prefixes should not overlap.
+ * The prefix that matches has a corresponding storage path, which the rest of the URL
+ * is assumed to be relative to. The file at that path (or a 404) is send to the client.
+ *
+ * Example:
+ * $wgImgAuthUrlPathMap['/timeline/'] = 'mwstore://local-fs/timeline-render/';
+ * The above maps ".../img_auth.php/timeline/X" to "mwstore://local-fs/timeline-render/".
+ * The name "local-fs" should correspond by name to an entry in $wgFileBackends.
+ *
+ * @see $wgFileBackends
*/
-$wgImgAuthPublicTest = true;
+$wgImgAuthUrlPathMap = array();
/**
* File repository structures
@@ -384,8 +407,6 @@ $wgImgAuthPublicTest = true;
* url : base URL to the root of the zone
* urlsByExt : map of file extension types to base URLs
* (useful for using a different cache for videos)
- * handlerUrl : base script-handled URL to the root of the zone
- * (see FileRepo::getZoneHandlerUrl() function)
* Zones default to using "<repo name>-<zone name>" as the container name
* and default to using the container root as the zone's root directory.
* Nesting of zone locations within other zones should be avoided.
@@ -574,7 +595,7 @@ $wgCacheSharedUploads = true;
/**
* Allow for upload to be copied from an URL.
- * The timeout for copy uploads is set by $wgHTTPTimeout.
+ * The timeout for copy uploads is set by $wgCopyUploadTimeout.
* You have to assign the user right 'upload_by_url' to a user group, to use this.
*/
$wgAllowCopyUploads = false;
@@ -739,7 +760,7 @@ $wgFileBlacklist = array(
'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl' );
/**
- * Files with these mime types will never be allowed as uploads
+ * Files with these MIME types will never be allowed as uploads
* if $wgVerifyMimeType is enabled.
*/
$wgMimeTypeBlacklist = array(
@@ -791,7 +812,7 @@ $wgDisableUploadScriptChecks = false;
$wgUploadSizeWarning = false;
/**
- * list of trusted media-types and mime types.
+ * list of trusted media-types and MIME types.
* Use the MEDIATYPE_xxx constants to represent media types.
* This list is used by File::isSafeFile
*
@@ -839,13 +860,22 @@ $wgContentHandlers = array(
CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler',
// dumb version, no syntax highlighting
CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler',
+ // simple implementation, for use by extensions, etc.
+ CONTENT_MODEL_JSON => 'JsonContentHandler',
// dumb version, no syntax highlighting
CONTENT_MODEL_CSS => 'CssContentHandler',
- // plain text, for use by extensions etc
+ // plain text, for use by extensions, etc.
CONTENT_MODEL_TEXT => 'TextContentHandler',
);
/**
+ * Whether to enable server-side image thumbnailing. If false, images will
+ * always be sent to the client in full resolution, with appropriate width= and
+ * height= attributes on the <img> tag for the client to do its own scaling.
+ */
+$wgUseImageResize = true;
+
+/**
* Resizing can be done using PHP's internal image libraries or using
* ImageMagick or another third-party converter, e.g. GraphicMagick.
* These support more file formats than PHP, which only supports PNG,
@@ -861,11 +891,6 @@ $wgUseImageMagick = false;
$wgImageMagickConvertCommand = '/usr/bin/convert';
/**
- * The identify command shipped with ImageMagick
- */
-$wgImageMagickIdentifyCommand = '/usr/bin/identify';
-
-/**
* Sharpening parameter to ImageMagick
*/
$wgSharpenParameter = '0x0.4';
@@ -921,7 +946,8 @@ $wgSVGConverters = array(
'ImageMagick' => '$path/convert -background white -thumbnail $widthx$height\! $input PNG:$output',
'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output',
'inkscape' => '$path/inkscape -z -w $width -f $input -e $output',
- 'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input',
+ 'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d '
+ . '$output $input',
'rsvg' => '$path/rsvg -w $width -h $height $input $output',
'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output',
'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ),
@@ -1008,6 +1034,14 @@ $wgTiffThumbnailType = false;
$wgThumbnailEpoch = '20030516000000';
/**
+ * Certain operations are avoided if there were too many recent failures,
+ * for example, thumbnail generation. Bump this value to invalidate all
+ * memory of failed operations and thus allow further attempts to resume.
+ * This is useful when a cause for the failures has been found and fixed.
+ */
+$wgAttemptFailureEpoch = 1;
+
+/**
* If set, inline scaled images will still produce "<img>" tags ready for
* output instead of showing an error message.
*
@@ -1035,11 +1069,6 @@ $wgGenerateThumbnailOnParse = true;
$wgShowArchiveThumbnails = true;
/**
- * Obsolete, always true, kept for compatibility with extensions
- */
-$wgUseImageResize = true;
-
-/**
* If set to true, images that contain certain the exif orientation tag will
* be rotated accordingly. If set to null, try to auto-detect whether a scaler
* is available that can rotate.
@@ -1108,45 +1137,45 @@ $wgAntivirusSetup = array(
$wgAntivirusRequired = true;
/**
- * Determines if the mime type of uploaded files should be checked
+ * Determines if the MIME type of uploaded files should be checked
*/
$wgVerifyMimeType = true;
/**
- * Sets the mime type definition file to use by MimeMagic.php.
+ * Sets the MIME type definition file to use by MimeMagic.php.
* Set to null, to use built-in defaults only.
* example: $wgMimeTypeFile = '/etc/mime.types';
*/
$wgMimeTypeFile = 'includes/mime.types';
/**
- * Sets the mime type info file to use by MimeMagic.php.
+ * Sets the MIME type info file to use by MimeMagic.php.
* Set to null, to use built-in defaults only.
*/
$wgMimeInfoFile = 'includes/mime.info';
/**
- * Sets an external mime detector program. The command must print only
- * the mime type to standard output.
+ * Sets an external MIME detector program. The command must print only
+ * the MIME type to standard output.
* The name of the file to process will be appended to the command given here.
- * If not set or NULL, mime_content_type will be used if available.
+ * If not set or NULL, PHP's fileinfo extension will be used if available.
*
* @par Example:
* @code
- * #$wgMimeDetectorCommand = "file -bi"; # use external mime detector (Linux)
+ * #$wgMimeDetectorCommand = "file -bi"; # use external MIME detector (Linux)
* @endcode
*/
$wgMimeDetectorCommand = null;
/**
- * Switch for trivial mime detection. Used by thumb.php to disable all fancy
+ * Switch for trivial MIME detection. Used by thumb.php to disable all fancy
* things, because only a few types of images are needed and file extensions
* can be trusted.
*/
$wgTrivialMimeDetection = false;
/**
- * Additional XML types we can allow via mime-detection.
+ * Additional XML types we can allow via MIME-detection.
* array = ( 'rootElement' => 'associatedMimeType' )
*/
$wgXMLMimeTypes = array(
@@ -1188,6 +1217,34 @@ $wgThumbLimits = array(
);
/**
+ * When defined, is an array of image widths used as buckets for thumbnail generation.
+ * The goal is to save resources by generating thumbnails based on reference buckets instead of
+ * always using the original. This will incur a speed gain but cause a quality loss.
+ *
+ * The buckets generation is chained, with each bucket generated based on the above bucket
+ * when possible. File handlers have to opt into using that feature. For now only BitmapHandler
+ * supports it.
+ */
+$wgThumbnailBuckets = null;
+
+/**
+ * When using thumbnail buckets as defined above, this sets the minimum distance to the bucket
+ * above the requested size. The distance represents how many extra pixels of width the bucket
+ * needs in order to be used as the reference for a given thumbnail. For example, with the
+ * following buckets:
+ *
+ * $wgThumbnailBuckets = array ( 128, 256, 512 );
+ *
+ * and a distance of 50:
+ *
+ * $wgThumbnailMinimumBucketDistance = 50;
+ *
+ * If we want to render a thumbnail of width 220px, the 512px bucket will be used,
+ * because 220 + 50 = 270 and the closest bucket bigger than 270px is 512.
+ */
+$wgThumbnailMinimumBucketDistance = 50;
+
+/**
* Default parameters for the "<gallery>" tag
*/
$wgGalleryOptions = array(
@@ -1251,7 +1308,7 @@ $wgDjvuTxt = null;
* Path of the djvutoxml executable
* This works like djvudump except much, much slower as of version 3.5.
*
- * For now we recommend you use djvudump instead. The djvuxml output is
+ * For now we recommend you use djvudump instead. The djvuxml output is
* probably more stable, so we'll switch back to it as soon as they fix
* the efficiency problem.
* http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
@@ -1265,7 +1322,7 @@ $wgDjvuToXML = null;
/**
* Shell command for the DJVU post processor
- * Default: pnmtopng, since ddjvu generates ppm output
+ * Default: pnmtojpeg, since ddjvu generates ppm output
* Set this to false to output the ppm file directly.
*/
$wgDjvuPostProcessor = 'pnmtojpeg';
@@ -1284,24 +1341,27 @@ $wgDjvuOutputExtension = 'jpg';
* @{
*/
-$serverName = substr( $wgServer, strrpos( $wgServer, '/' ) + 1 );
/**
* Site admin email address.
+ *
+ * Defaults to "wikiadmin@{$wgServerName}".
*/
-$wgEmergencyContact = 'wikiadmin@' . $serverName;
+$wgEmergencyContact = false;
/**
* Password reminder email address.
*
* The address we should use as sender when a user is requesting his password.
+ *
+ * Defaults to "apache@{$wgServerName}".
*/
-$wgPasswordSender = 'apache@' . $serverName;
-
-unset( $serverName ); # Don't leak local variables to global scope
+$wgPasswordSender = false;
/**
* Password reminder name
+ *
+ * @deprecated since 1.23; use the system message 'emailsender' instead.
*/
$wgPasswordSenderName = 'MediaWiki Mail';
@@ -1352,6 +1412,18 @@ $wgNewPasswordExpiry = 3600 * 24 * 7;
$wgUserEmailConfirmationTokenExpiry = 7 * 24 * 60 * 60;
/**
+ * The number of days that a user's password is good for. After this number of days, the
+ * user will be asked to reset their password. Set to false to disable password expiration.
+ */
+$wgPasswordExpirationDays = false;
+
+/**
+ * If a user's password is expired, the number of seconds when they can still login,
+ * and cancel their password change, but are sent to the password change form on each login.
+ */
+$wgPasswordExpireGrace = 3600 * 24 * 7; // 7 days
+
+/**
* SMTP Mode.
*
* For using a direct (authenticated) SMTP server connection.
@@ -1469,7 +1541,7 @@ $wgUsersNotifiedOnAllChanges = array();
$wgDBserver = 'localhost';
/**
- * Database port number (for PostgreSQL)
+ * Database port number (for PostgreSQL and Microsoft SQL Server).
*/
$wgDBport = 5432;
@@ -1495,11 +1567,21 @@ $wgDBtype = 'mysql';
/**
* Whether to use SSL in DB connection.
+ *
+ * This setting is only used $wgLBFactoryConf['class'] is set to
+ * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise
+ * the DBO_SSL flag must be set in the 'flags' option of the database
+ * connection to achieve the same functionality.
*/
$wgDBssl = false;
/**
* Whether to use compression in DB connection.
+ *
+ * This setting is only used $wgLBFactoryConf['class'] is set to
+ * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise
+ * the DBO_COMPRESS flag must be set in the 'flags' option of the database
+ * connection to achieve the same functionality.
*/
$wgDBcompress = false;
@@ -1551,7 +1633,7 @@ $wgSQLMode = '';
/**
* Mediawiki schema
*/
-$wgDBmwschema = 'mediawiki';
+$wgDBmwschema = null;
/**
* To override default SQLite data directory ($docroot/../data)
@@ -1582,10 +1664,10 @@ $wgAllDBsAreLocalhost = false;
* $wgSharedPrefix is the table prefix for the shared database. It defaults to
* $wgDBprefix.
*
- * @deprecated In new code, use the $wiki parameter to wfGetLB() to access
- * remote databases. Using wfGetLB() allows the shared database to reside on
- * separate servers to the wiki's own database, with suitable configuration
- * of $wgLBFactoryConf.
+ * @deprecated since 1.21 In new code, use the $wiki parameter to wfGetLB() to
+ * access remote databases. Using wfGetLB() allows the shared database to
+ * reside on separate servers to the wiki's own database, with suitable
+ * configuration of $wgLBFactoryConf.
*/
$wgSharedDB = null;
@@ -1607,8 +1689,13 @@ $wgSharedTables = array( 'user', 'user_properties' );
* - dbname: Default database name
* - user: DB user
* - password: DB password
- * - type: "mysql" or "postgres"
- * - load: ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0
+ * - type: DB type
+ *
+ * - load: Ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0.
+ * If this is zero for any given server, no normal query traffic will be
+ * sent to it. It will be excluded from lag checks in maintenance scripts.
+ * The only way it can receive traffic is if groupLoads is used.
+ *
* - groupLoads: array of load ratios, the key is the query group name. A query may belong
* to several groups, the most specific group defined here is used.
*
@@ -1623,7 +1710,6 @@ $wgSharedTables = array( 'user', 'user_properties' );
* if available
*
* - max lag: (optional) Maximum replication lag before a slave will taken out of rotation
- * - max threads: (optional) Maximum number of running threads
*
* These and any other user-defined properties will be assigned to the mLBInfo member
* variable of the Database object.
@@ -1654,13 +1740,14 @@ $wgDBservers = false;
* The class identified here is responsible for reading $wgDBservers,
* $wgDBserver, etc., so overriding it may cause those globals to be ignored.
*
- * The LBFactory_Multi class is provided for this purpose, please see
- * includes/db/LBFactory_Multi.php for configuration information.
+ * The LBFactoryMulti class is provided for this purpose, please see
+ * includes/db/LBFactoryMulti.php for configuration information.
*/
-$wgLBFactoryConf = array( 'class' => 'LBFactory_Simple' );
+$wgLBFactoryConf = array( 'class' => 'LBFactorySimple' );
/**
* How long to wait for a slave to catch up to the master
+ * @deprecated since 1.24
*/
$wgMasterWaitTimeout = 10;
@@ -1690,11 +1777,6 @@ $wgDBerrorLog = false;
$wgDBerrorLogTZ = false;
/**
- * When to give an error message
- */
-$wgDBClusterTimeout = 10;
-
-/**
* Scale load balancer polling time so that under overload conditions, the
* database server receives a SHOW STATUS query at an average interval of this
* many microseconds
@@ -1767,6 +1849,11 @@ $wgSlaveLagWarning = 10;
*/
$wgSlaveLagCritical = 30;
+/**
+ * Use Windows Authentication instead of $wgDBuser / $wgDBpassword for MS SQL Server
+ */
+$wgDBWindowsAuthentication = false;
+
/**@}*/ # End of DB settings }
/************************************************************************//**
@@ -1793,7 +1880,7 @@ $wgCompressRevisions = false;
*
* CAUTION: Access to database might lead to code execution
*/
-$wgExternalStores = false;
+$wgExternalStores = array();
/**
* An array of external MySQL servers.
@@ -1806,7 +1893,7 @@ $wgExternalStores = false;
* );
* @endcode
*
- * Used by LBFactory_Simple, may be ignored if $wgLBFactoryConf is set to
+ * Used by LBFactorySimple, may be ignored if $wgLBFactoryConf is set to
* another class.
*/
$wgExternalServers = array();
@@ -1921,9 +2008,6 @@ $wgCacheDirectory = false;
* - CACHE_DB: Store cache objects in the DB
* - CACHE_MEMCACHED: MemCached, must specify servers in $wgMemCachedServers
* - CACHE_ACCEL: APC, XCache or WinCache
- * - CACHE_DBA: Use PHP's DBA extension to store in a DBM-style
- * database. This is slow, and is not recommended for
- * anything other than debugging.
* - (other): A string may be used which identifies a cache
* configuration in $wgObjectCaches.
*
@@ -1976,15 +2060,10 @@ $wgLanguageConverterCacheType = CACHE_ANYTHING;
* the value is an associative array of parameters. The "class" parameter is the
* class name which will be used. Alternatively, a "factory" parameter may be
* given, giving a callable function which will generate a suitable cache object.
- *
- * The other parameters are dependent on the class used.
- * - CACHE_DBA uses $wgTmpDirectory by default. The 'dir' parameter let you
- * overrides that.
*/
$wgObjectCaches = array(
CACHE_NONE => array( 'class' => 'EmptyBagOStuff' ),
CACHE_DB => array( 'class' => 'SqlBagOStuff', 'table' => 'objectcache' ),
- CACHE_DBA => array( 'class' => 'DBABagOStuff' ),
CACHE_ANYTHING => array( 'factory' => 'ObjectCache::newAnything' ),
CACHE_ACCEL => array( 'factory' => 'ObjectCache::newAccelerator' ),
@@ -1999,16 +2078,32 @@ $wgObjectCaches = array(
);
/**
- * The expiry time for the parser cache, in seconds.
- * The default is 86400 (one day).
+ * Map of bloom filter store names to configuration arrays.
+ *
+ * Example:
+ * $wgBloomFilterStores['main'] = array(
+ * 'cacheId' => 'main-v1',
+ * 'class' => 'BloomCacheRedis',
+ * 'redisServers' => array( '127.0.0.1:6379' ),
+ * 'redisConfig' => array( 'connectTimeout' => 2 )
+ * );
+ *
+ * A primary bloom filter must be created manually.
+ * Example in eval.php:
+ * <code>
+ * BloomCache::get( 'main' )->init( 'shared', 1000000000, .001 );
+ * </code>
+ * The size should be as large as practical given wiki size and resources.
+ *
+ * @since 1.24
*/
-$wgParserCacheExpireTime = 86400;
+$wgBloomFilterStores = array();
/**
- * Select which DBA handler <http://www.php.net/manual/en/dba.requirements.php>
- * to use as CACHE_DBA backend.
+ * The expiry time for the parser cache, in seconds.
+ * The default is 86400 (one day).
*/
-$wgDBAhandler = 'db3';
+$wgParserCacheExpireTime = 86400;
/**
* Deprecated alias for $wgSessionsInObjectCache.
@@ -2118,6 +2213,12 @@ $wgCachePages = true;
$wgCacheEpoch = '20030516000000';
/**
+ * Directory where GitInfo will look for pre-computed cache files. If false,
+ * $wgCacheDirectory/gitinfo will be used.
+ */
+$wgGitInfoCacheDirectory = false;
+
+/**
* Bump this number when changing the global style sheets and JavaScript.
*
* It should be appended in the query string of static CSS and JS includes,
@@ -2129,7 +2230,7 @@ $wgStyleVersion = '303';
/**
* This will cache static pages for non-logged-in users to reduce
* database traffic on public sites.
- * Must set $wgShowIPinHeader = false
+ * Automatically sets $wgShowIPinHeader = false
* ResourceLoader requests to default language and skins are cached
* as well as single module requests.
*/
@@ -2218,7 +2319,7 @@ $wgInvalidateCacheOnLocalSettingsChange = true;
* although they are referred to as Squid settings for historical reasons.
*
* Achieving a high hit ratio with an HTTP proxy requires special
- * configuration. See http://www.mediawiki.org/wiki/Manual:Squid_caching for
+ * configuration. See https://www.mediawiki.org/wiki/Manual:Squid_caching for
* more details.
*
* @{
@@ -2226,7 +2327,7 @@ $wgInvalidateCacheOnLocalSettingsChange = true;
/**
* Enable/disable Squid.
- * See http://www.mediawiki.org/wiki/Manual:Squid_caching
+ * See https://www.mediawiki.org/wiki/Manual:Squid_caching
*/
$wgUseSquid = false;
@@ -2285,7 +2386,9 @@ $wgSquidServers = array();
/**
* As above, except these servers aren't purged on page changes; use to set a
- * list of trusted proxies, etc.
+ * list of trusted proxies, etc. Supports both individual IP addresses and
+ * CIDR blocks.
+ * @since 1.23 Supports CIDR ranges
*/
$wgSquidServersNoPurge = array();
@@ -2369,42 +2472,6 @@ $wgSquidPurgeUseHostHeader = true;
$wgHTCPRouting = array();
/**
- * @deprecated since 1.22, please use $wgHTCPRouting instead.
- *
- * Whenever this is set and $wgHTCPRouting evaluates to false, $wgHTCPRouting
- * will be set to this value.
- * This is merely for back compatibility.
- *
- * @since 1.20
- */
-$wgHTCPMulticastRouting = null;
-
-/**
- * HTCP multicast address. Set this to a multicast IP address to enable HTCP.
- *
- * Note that MediaWiki uses the old non-RFC compliant HTCP format, which was
- * present in the earliest Squid implementations of the protocol.
- *
- * This setting is DEPRECATED in favor of $wgHTCPRouting , and kept for
- * backwards compatibility only. If $wgHTCPRouting is set, this setting is
- * ignored. If $wgHTCPRouting is not set and this setting is, it is used to
- * populate $wgHTCPRouting.
- *
- * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting and since 1.22 in
- * favor of $wgHTCPRouting.
- */
-$wgHTCPMulticastAddress = false;
-
-/**
- * HTCP multicast port.
- * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting and since 1.22 in
- * favor of $wgHTCPRouting.
- *
- * @see $wgHTCPMulticastAddress
- */
-$wgHTCPPort = 4827;
-
-/**
* HTCP multicast TTL.
* @see $wgHTCPRouting
*/
@@ -2467,6 +2534,21 @@ $wgInterwikiMagic = true;
$wgHideInterlanguageLinks = false;
/**
+ * List of additional interwiki prefixes that should be treated as
+ * interlanguage links (i.e. placed in the sidebar).
+ * Notes:
+ * - This will not do anything unless the prefixes are defined in the interwiki
+ * map.
+ * - The display text for these custom interlanguage links will be fetched from
+ * the system message "interlanguage-link-xyz" where xyz is the prefix in
+ * this array.
+ * - A friendly name for each site, used for tooltip text, may optionally be
+ * placed in the system message "interlanguage-link-sitename-xyz" where xyz is
+ * the prefix in this array.
+ */
+$wgExtraInterlanguageLinkPrefixes = array();
+
+/**
* List of language names or overrides for default names in Names.php
*/
$wgExtraLanguageNames = array();
@@ -2642,11 +2724,6 @@ $wgDisableLangConversion = false;
$wgDisableTitleConversion = false;
/**
- * Whether to enable canonical language links in meta data.
- */
-$wgCanonicalLanguageLinks = true;
-
-/**
* Default variant code, if false, the default will be the language code
*/
$wgDefaultLanguageVariant = false;
@@ -2794,6 +2871,23 @@ $wgHtml5 = true;
$wgHtml5Version = null;
/**
+ * Temporary variable that allows HTMLForms to be rendered as tables.
+ * Table based layouts cause various issues when designing for mobile.
+ * This global allows skins or extensions a means to force non-table based rendering.
+ * Setting to false forces form components to always render as div elements.
+ * @since 1.24
+ */
+$wgHTMLFormAllowTableFormat = true;
+
+/**
+ * Temporary variable that applies MediaWiki UI wherever it can be supported.
+ * Temporary variable that should be removed when mediawiki ui is more
+ * stable and change has been communicated.
+ * @since 1.24
+ */
+$wgUseMediaWikiUIEverywhere = false;
+
+/**
* Enabled RDFa attributes for use in wikitext.
* NOTE: Interaction with HTML5 is somewhat underspecified.
*/
@@ -2834,7 +2928,7 @@ $wgWellFormedXml = true;
* Normally we wouldn't have to define this in the root "<html>"
* element, but IE needs it there in some circumstances.
*
- * This is ignored if $wgMimeType is set to a non-XML mimetype.
+ * This is ignored if $wgMimeType is set to a non-XML MIME type.
*/
$wgXhtmlNamespaces = array();
@@ -2855,11 +2949,6 @@ $wgShowIPinHeader = true;
$wgSiteNotice = '';
/**
- * A subtitle to add to the tagline, for skins that have it/
- */
-$wgExtraSubtitle = '';
-
-/**
* If this is set, a "donate" link will appear in the sidebar. Set it to a URL.
*/
$wgSiteSupportPage = '';
@@ -2873,24 +2962,29 @@ $wgValidateAllHtml = false;
/**
* Default skin, for new users and anonymous visitors. Registered users may
* change this to any one of the other available skins in their preferences.
- * This has to be completely lowercase; see the "skins" directory for the list
- * of available skins.
*/
$wgDefaultSkin = 'vector';
/**
- * Specify the name of a skin that should not be presented in the list of
- * available skins. Use for blacklisting a skin which you do not want to
- * remove from the .../skins/ directory
+ * Fallback skin used when the skin defined by $wgDefaultSkin can't be found.
+ *
+ * @since 1.24
*/
-$wgSkipSkin = '';
+$wgFallbackSkin = 'fallback';
/**
- * Array for more like $wgSkipSkin.
+ * Specify the names of skins that should not be presented in the list of
+ * available skins in user preferences. If you want to remove a skin entirely,
+ * remove it from the skins/ directory and its entry from LocalSettings.php.
*/
$wgSkipSkins = array();
/**
+ * @deprecated since 1.23; use $wgSkipSkins instead
+ */
+$wgSkipSkin = '';
+
+/**
* Allow user Javascript page?
* This enables a lot of neat customizations, but may
* increase security risk to users and server load.
@@ -3010,7 +3104,8 @@ $wgFooterIcons = array(
),
"poweredby" => array(
"mediawiki" => array(
- "src" => null, // Defaults to "$wgStylePath/common/images/poweredby_mediawiki_88x31.png"
+ // src defaults to "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
+ "src" => null,
"url" => "//www.mediawiki.org/",
"alt" => "Powered by MediaWiki",
)
@@ -3026,33 +3121,11 @@ $wgFooterIcons = array(
$wgUseCombinedLoginLink = false;
/**
- * Search form look for Vector skin only.
- * - true = use an icon search button
- * - false = use Go & Search buttons
- */
-$wgVectorUseSimpleSearch = true;
-
-/**
- * Watch and unwatch as an icon rather than a link for Vector skin only.
- * - true = use an icon watch/unwatch button
- * - false = use watch/unwatch text link
- */
-$wgVectorUseIconWatch = true;
-
-/**
* Display user edit counts in various prominent places.
*/
$wgEdititis = false;
/**
- * Better directionality support (bug 6100 and related).
- * Removed in 1.18, still kept here for LiquidThreads backwards compatibility.
- *
- * @deprecated since 1.18
- */
-$wgBetterDirectionality = true;
-
-/**
* Some web hosts attempt to rewrite all responses with a 404 (not found)
* status code, mangling or hiding MediaWiki's output. If you are using such a
* host, you should start looking for a better one. While you're doing that,
@@ -3083,6 +3156,14 @@ $wgShowRollbackEditCount = 10;
*/
$wgEnableCanonicalServerLink = false;
+/**
+ * When OutputHandler is used, mangle any output that contains
+ * <cross-domain-policy>. Without this, an attacker can send their own
+ * cross-domain policy unless it is prevented by the crossdomain.xml file at
+ * the domain root.
+ */
+$wgMangleFlashPolicy = true;
+
/** @} */ # End of output format settings }
/*************************************************************************//**
@@ -3110,16 +3191,118 @@ $wgEnableCanonicalServerLink = false;
$wgResourceModules = array();
/**
+ * Skin-specific styles for resource modules.
+ *
+ * These are later added to the 'skinStyles' list of the existing module. The 'styles' list can
+ * not be modified or disabled.
+ *
+ * For example, here is a module "bar" and how skin Foo would provide additional styles for it.
+ *
+ * @par Example:
+ * @code
+ * $wgResourceModules['bar'] = array(
+ * 'scripts' => 'resources/bar/bar.js',
+ * 'styles' => 'resources/bar/main.css',
+ * );
+ *
+ * $wgResourceModuleSkinStyles['foo'] = array(
+ * 'bar' => 'skins/Foo/bar.css',
+ * );
+ * @endcode
+ *
+ * This is mostly equivalent to:
+ *
+ * @par Equivalent:
+ * @code
+ * $wgResourceModules['bar'] = array(
+ * 'scripts' => 'resources/bar/bar.js',
+ * 'styles' => 'resources/bar/main.css',
+ * 'skinStyles' => array(
+ * 'foo' => skins/Foo/bar.css',
+ * ),
+ * );
+ * @endcode
+ *
+ * If the module already defines its own entry in `skinStyles` for a given skin, then
+ * $wgResourceModuleSkinStyles is ignored.
+ *
+ * If a module defines a `skinStyles['default']` the skin may want to extend that instead
+ * of replacing them. This can be done using the `+` prefix.
+ *
+ * @par Example:
+ * @code
+ * $wgResourceModules['bar'] = array(
+ * 'scripts' => 'resources/bar/bar.js',
+ * 'styles' => 'resources/bar/basic.css',
+ * 'skinStyles' => array(
+ * 'default' => 'resources/bar/additional.css',
+ * ),
+ * );
+ * // Note the '+' character:
+ * $wgResourceModuleSkinStyles['+foo'] = array(
+ * 'bar' => 'skins/Foo/bar.css',
+ * );
+ * @endcode
+ *
+ * This is mostly equivalent to:
+ *
+ * @par Equivalent:
+ * @code
+ * $wgResourceModules['bar'] = array(
+ * 'scripts' => 'resources/bar/bar.js',
+ * 'styles' => 'resources/bar/basic.css',
+ * 'skinStyles' => array(
+ * 'default' => 'resources/bar/additional.css',
+ * 'foo' => array(
+ * 'resources/bar/additional.css',
+ * 'skins/Foo/bar.css',
+ * ),
+ * ),
+ * );
+ * @endcode
+ *
+ * In other words, as a module author, use the `styles` list for stylesheets that may not be
+ * disabled by a skin. To provide default styles that may be extended or replaced,
+ * use `skinStyles['default']`.
+ *
+ * As with $wgResourceModules, paths default to being relative to the MediaWiki root.
+ * You should always provide a localBasePath and remoteBasePath (or remoteExtPath/remoteSkinPath).
+ * Either for all skin styles at once (first example below) or for each module separately (second
+ * example).
+ *
+ * @par Example:
+ * @code
+ * $wgResourceModuleSkinStyles['foo'] = array(
+ * 'bar' => 'bar.css',
+ * 'quux' => 'quux.css',
+ * 'remoteSkinPath' => 'Foo',
+ * 'localBasePath' => __DIR__,
+ * );
+ *
+ * $wgResourceModuleSkinStyles['foo'] = array(
+ * 'bar' => array(
+ * 'bar.css',
+ * 'remoteSkinPath' => 'Foo',
+ * 'localBasePath' => __DIR__,
+ * ),
+ * 'quux' => array(
+ * 'quux.css',
+ * 'remoteSkinPath' => 'Foo',
+ * 'localBasePath' => __DIR__,
+ * ),
+ * );
+ * @endcode
+ */
+$wgResourceModuleSkinStyles = array();
+
+/**
* Extensions should register foreign module sources here. 'local' is a
* built-in source that is not in this array, but defined by
* ResourceLoader::__construct() so that it cannot be unset.
*
* @par Example:
* @code
- * $wgResourceLoaderSources['foo'] = array(
- * 'loadScript' => 'http://example.org/w/load.php',
- * 'apiScript' => 'http://example.org/w/api.php'
- * );
+ * $wgResourceLoaderSources['foo'] = 'http://example.org/w/load.php';
* @endcode
*/
$wgResourceLoaderSources = array();
@@ -3132,14 +3315,23 @@ $wgResourceBasePath = null;
/**
* Maximum time in seconds to cache resources served by the resource loader.
+ * Used to set last modified headers (max-age/s-maxage).
+ *
+ * Following options to distinguish:
+ * - versioned: Used for modules with a version, because changing version
+ * numbers causes cache misses. This normally has a long expiry time.
+ * - unversioned: Used for modules without a version to propagate changes
+ * quickly to clients. Also used for modules with errors to recover quickly.
+ * This normally has a short expiry time.
*
- * @todo Document array structure
+ * Expiry time for the options to distinguish:
+ * - server: Squid/Varnish but also any other public proxy cache between the
+ * client and MediaWiki.
+ * - client: On the client side (e.g. in the browser cache).
*/
$wgResourceLoaderMaxage = array(
'versioned' => array(
- // Squid/Varnish but also any other public proxy cache between the client and MediaWiki
'server' => 30 * 24 * 60 * 60, // 30 days
- // On the client side (e.g. in the browser cache).
'client' => 30 * 24 * 60 * 60, // 30 days
),
'unversioned' => array(
@@ -3182,6 +3374,15 @@ $wgResourceLoaderMinifierMaxLineLength = 1000;
$wgIncludeLegacyJavaScript = true;
/**
+ * Whether to include the jQuery Migrate library, which lets legacy JS that
+ * requires jQuery 1.8.x to work and breaks with 1.9.x+.
+ *
+ * @since 1.24
+ * @deprecated since 1.24, to be removed in 1.25
+ */
+$wgIncludejQueryMigrate = false;
+
+/**
* Whether to preload the mediawiki.util module as blocking module in the top
* queue.
*
@@ -3270,12 +3471,14 @@ $wgResourceLoaderValidateStaticJS = false;
$wgResourceLoaderExperimentalAsyncLoading = false;
/**
- * Global LESS variables. An associative array binding variable names to CSS
- * string values.
+ * Global LESS variables. An associative array binding variable names to
+ * LESS code snippets representing their values.
*
- * Because the hashed contents of this array are used to construct the cache key
- * that ResourceLoader uses to look up LESS compilation results, updating this
- * array can be used to deliberately invalidate the set of cached results.
+ * Adding an item here is equivalent to writing `@variable: value;`
+ * at the beginning of all your .less files, with all the consequences.
+ * In particular, string values must be escaped and quoted.
+ *
+ * Changes to LESS variables do not trigger cache invalidation.
*
* @par Example:
* @code
@@ -3293,17 +3496,13 @@ $wgResourceLoaderLESSVars = array();
* Custom LESS functions. An associative array mapping function name to PHP
* callable.
*
- * Changes to LESS functions do not trigger cache invalidation. If you update
- * the behavior of a LESS function and need to invalidate stale compilation
- * results, you can touch one of values in $wgResourceLoaderLESSVars, as
- * documented above.
+ * Changes to LESS functions do not trigger cache invalidation.
*
* @since 1.22
+ * @deprecated since 1.24 Questionable usefulness and problematic to support,
+ * will be removed in the future.
*/
-$wgResourceLoaderLESSFunctions = array(
- 'embeddable' => 'ResourceLoaderLESSFunctions::embeddable',
- 'embed' => 'ResourceLoaderLESSFunctions::embed',
-);
+$wgResourceLoaderLESSFunctions = array();
/**
* Default import paths for LESS modules. LESS files referenced in @import
@@ -3319,10 +3518,26 @@ $wgResourceLoaderLESSFunctions = array(
* @since 1.22
*/
$wgResourceLoaderLESSImportPaths = array(
- "$IP/resources/mediawiki.less/",
+ "$IP/resources/src/mediawiki.less/",
);
/**
+ * Whether ResourceLoader should attempt to persist modules in localStorage on
+ * browsers that support the Web Storage API.
+ *
+ * @since 1.23 - Client-side module persistence is experimental. Exercise care.
+ */
+$wgResourceLoaderStorageEnabled = false;
+
+/**
+ * Cache version for client-side ResourceLoader module storage. You can trigger
+ * invalidation of the contents of the module store by incrementing this value.
+ *
+ * @since 1.23
+ */
+$wgResourceLoaderStorageVersion = 1;
+
+/**
* Whether to allow site-wide CSS (MediaWiki:Common.css and friends) on
* restricted pages like Special:UserLogin or Special:Preferences where
* JavaScript is disabled for security reasons. As it is possible to
@@ -3335,14 +3550,6 @@ $wgResourceLoaderLESSImportPaths = array(
*/
$wgAllowSiteCSSOnRestrictedPages = false;
-/**
- * When OutputHandler is used, mangle any output that contains
- * <cross-domain-policy>. Without this, an attacker can send their own
- * cross-domain policy unless it is prevented by the crossdomain.xml file at
- * the domain root.
- */
-$wgMangleFlashPolicy = true;
-
/** @} */ # End of resource loader settings }
/*************************************************************************//**
@@ -3452,10 +3659,22 @@ $wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+";
/**
* The interwiki prefix of the current wiki, or false if it doesn't have one.
+ *
+ * @deprecated since 1.23; use $wgLocalInterwikis instead
*/
$wgLocalInterwiki = false;
/**
+ * Array for multiple $wgLocalInterwiki values, in case there are several
+ * interwiki prefixes that point to the current wiki. If $wgLocalInterwiki is
+ * set, its value is prepended to this array, for backwards compatibility.
+ *
+ * Note, recent changes feeds use only the first entry in this array (or
+ * $wgLocalInterwiki, if it is set). See $wgRCFeeds
+ */
+$wgLocalInterwikis = array();
+
+/**
* Expiry time for cache of interwiki table
*/
$wgInterwikiExpiry = 10800;
@@ -3557,6 +3776,30 @@ $wgNamespacesWithSubpages = array(
);
/**
+ * Array holding default tracking category names.
+ *
+ * Array contains the system messages for each tracking category.
+ * Tracking categories allow pages with certain characteristics to be tracked.
+ * It works by adding any such page to a category automatically.
+ *
+ * A message with the suffix '-desc' should be added as a description message
+ * to have extra information on Special:TrackingCategories.
+ *
+ * @since 1.23
+ */
+$wgTrackingCategories = array(
+ 'index-category',
+ 'noindex-category',
+ 'expensive-parserfunction-category',
+ 'post-expand-template-argument-category',
+ 'post-expand-template-inclusion-category',
+ 'hidden-category-category',
+ 'broken-file-category',
+ 'node-count-exceeded-category',
+ 'expansion-depth-exceeded-category',
+);
+
+/**
* Array of namespaces which can be deemed to contain valid "content", as far
* as the site statistics are concerned. Useful if additional namespaces also
* contain "content" which should be considered when generating a count of the
@@ -3653,36 +3896,20 @@ $wgMaxTemplateDepth = 40;
$wgMaxPPExpandDepth = 40;
/**
- * The external URL protocols
+ * URL schemes that should be recognized as valid by wfParseUrl().
+ *
+ * WARNING: Do not add 'file:' to this or internal file links will be broken.
+ * Instead, if you want to support file links, add 'file://'. The same applies
+ * to any other protocols with the same name as a namespace. See bug #44011 for
+ * more information.
+ *
+ * @see wfParseUrl
*/
$wgUrlProtocols = array(
- 'http://',
- 'https://',
- 'ftp://',
- 'ftps://', // If we allow ftp:// we should allow the secure version.
- 'ssh://',
- 'sftp://', // SFTP > FTP
- 'irc://',
- 'ircs://', // @bug 28503
- 'xmpp:', // Another open communication protocol
- 'sip:',
- 'sips:',
- 'gopher://',
- 'telnet://', // Well if we're going to support the above.. -ævar
- 'nntp://', // @bug 3808 RFC 1738
- 'worldwind://',
- 'mailto:',
- 'tel:', // If we can make emails linkable, why not phone numbers?
- 'sms:', // Likewise this is standardized too
- 'news:',
- 'svn://',
- 'git://',
- 'mms://',
- 'bitcoin:', // Even registerProtocolHandler whitelists this along with mailto:
- 'magnet:', // No reason to reject torrents over magnet: when they're allowed over http://
- 'urn:', // Allow URNs to be used in Microdata/RDFa <link ... href="urn:...">s
- 'geo:', // urls define geo locations, they're useful in Microdata/RDFa and for coordinates
- '//', // for protocol-relative URLs
+ 'bitcoin:', 'ftp://', 'ftps://', 'geo:', 'git://', 'gopher://', 'http://',
+ 'https://', 'irc://', 'ircs://', 'magnet:', 'mailto:', 'mms://', 'news:',
+ 'nntp://', 'redis://', 'sftp://', 'sip:', 'sips:', 'sms:', 'ssh://',
+ 'svn://', 'tel:', 'telnet://', 'urn:', 'worldwind://', 'xmpp:', '//'
);
/**
@@ -3809,13 +4036,16 @@ $wgNoFollowNsExceptions = array();
* (or any subdomains) will not be set to rel="nofollow" regardless of the
* value of $wgNoFollowLinks. For instance:
*
- * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org' );
+ * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org',
+ * 'mediawiki.org' );
*
* This would add rel="nofollow" to links to de.wikipedia.org, but not
* en.wikipedia.org, wiktionary.org, en.wiktionary.org, us.en.wikipedia.org,
* etc.
+ *
+ * Defaults to mediawiki.org for the links included in the software by default.
*/
-$wgNoFollowDomainExceptions = array();
+$wgNoFollowDomainExceptions = array( 'mediawiki.org' );
/**
* Allow DISPLAYTITLE to change title display
@@ -3869,23 +4099,14 @@ $wgTranscludeCacheExpiry = 3600;
* - 'any': all pages as considered as valid articles
* - 'comma': the page must contain a comma to be considered valid
* - 'link': the page must contain a [[wiki link]] to be considered valid
- * - null: the value will be set at run time depending on $wgUseCommaCount:
- * if $wgUseCommaCount is false, it will be 'link', if it is true
- * it will be 'comma'
*
- * See also See http://www.mediawiki.org/wiki/Manual:Article_count
+ * See also See https://www.mediawiki.org/wiki/Manual:Article_count
*
* Retroactively changing this variable will not affect the existing count,
* to update it, you will need to run the maintenance/updateArticleCount.php
* script.
*/
-$wgArticleCountMethod = null;
-
-/**
- * Backward compatibility setting, will set $wgArticleCountMethod if it is null.
- * @deprecated since 1.18; use $wgArticleCountMethod instead
- */
-$wgUseCommaCount = false;
+$wgArticleCountMethod = 'link';
/**
* wgHitcounterUpdateFreq sets how often page counters should be updated, higher
@@ -3914,6 +4135,7 @@ $wgActiveUserDays = 30;
/**
* For compatibility with old installations set to false
+ * @deprecated since 1.24 will be removed in future
*/
$wgPasswordSalt = true;
@@ -3924,6 +4146,72 @@ $wgPasswordSalt = true;
$wgMinimalPasswordLength = 1;
/**
+ * Specifies if users should be sent to a password-reset form on login, if their
+ * password doesn't meet the requirements of User::isValidPassword().
+ * @since 1.23
+ */
+$wgInvalidPasswordReset = true;
+
+/**
+ * Default password type to use when hashing user passwords
+ *
+ * @since 1.24
+ */
+$wgPasswordDefault = 'pbkdf2';
+
+/**
+ * Configuration for built-in password types. Maps the password type
+ * to an array of options. The 'class' option is the Password class to
+ * use. All other options are class-dependent.
+ *
+ * An advanced example:
+ * @code
+ * $wgPasswordConfig['bcrypt-peppered'] = array(
+ * 'class' => 'EncryptedPassword',
+ * 'underlying' => 'bcrypt',
+ * 'secrets' => array(),
+ * 'cipher' => MCRYPT_RIJNDAEL_256,
+ * 'mode' => MCRYPT_MODE_CBC,
+ * 'cost' => 5,
+ * );
+ * @endcode
+ *
+ * @since 1.24
+ */
+$wgPasswordConfig = array(
+ 'A' => array(
+ 'class' => 'MWOldPassword',
+ ),
+ 'B' => array(
+ 'class' => 'MWSaltedPassword',
+ ),
+ 'pbkdf2-legacyA' => array(
+ 'class' => 'LayeredParameterizedPassword',
+ 'types' => array(
+ 'A',
+ 'pbkdf2',
+ ),
+ ),
+ 'pbkdf2-legacyB' => array(
+ 'class' => 'LayeredParameterizedPassword',
+ 'types' => array(
+ 'B',
+ 'pbkdf2',
+ ),
+ ),
+ 'bcrypt' => array(
+ 'class' => 'BcryptPassword',
+ 'cost' => 9,
+ ),
+ 'pbkdf2' => array(
+ 'class' => 'Pbkdf2Password',
+ 'algo' => 'sha256',
+ 'cost' => '10000',
+ 'length' => '128',
+ ),
+);
+
+/**
* Whether to allow password resets ("enter some identifying data, and we'll send an email
* with a temporary password you can use to get back into the account") identified by
* various bits of data. Setting all of these to false (or the whole variable to false)
@@ -3963,8 +4251,8 @@ $wgReservedUsernames = array(
/**
* Settings added to this array will override the default globals for the user
* preferences used by anonymous visitors and newly created accounts.
- * For instance, to disable section editing links:
- * $wgDefaultUserOptions ['editsection'] = 0;
+ * For instance, to disable editing on double clicks:
+ * $wgDefaultUserOptions ['editondblclick'] = 0;
*/
$wgDefaultUserOptions = array(
'ccmeonemails' => 0,
@@ -3972,15 +4260,13 @@ $wgDefaultUserOptions = array(
'date' => 'default',
'diffonly' => 0,
'disablemail' => 0,
- 'disablesuggest' => 0,
'editfont' => 'default',
'editondblclick' => 0,
- 'editsection' => 1,
'editsectiononrightclick' => 0,
'enotifminoredits' => 0,
'enotifrevealaddr' => 0,
'enotifusertalkpages' => 1,
- 'enotifwatchlistpages' => 0,
+ 'enotifwatchlistpages' => 1,
'extendwatchlist' => 0,
'fancysig' => 0,
'forceeditsummary' => 0,
@@ -3988,34 +4274,28 @@ $wgDefaultUserOptions = array(
'hideminor' => 0,
'hidepatrolled' => 0,
'imagesize' => 2,
- 'justify' => 0,
'math' => 1,
'minordefault' => 0,
'newpageshidepatrolled' => 0,
- 'nocache' => 0,
- 'noconvertlink' => 0,
+ 'nickname' => '',
'norollbackdiff' => 0,
'numberheadings' => 0,
'previewonfirst' => 0,
'previewontop' => 1,
'rcdays' => 7,
'rclimit' => 50,
- 'rememberpassword' => 0,
'rows' => 25,
- 'searchlimit' => 20,
'showhiddencats' => 0,
'shownumberswatching' => 1,
- 'showtoc' => 1,
'showtoolbar' => 1,
'skin' => false,
'stubthreshold' => 0,
- 'thumbsize' => 2,
+ 'thumbsize' => 5,
'underline' => 2,
'uselivepreview' => 0,
'usenewrc' => 0,
- 'vector-simplesearch' => 1,
- 'watchcreations' => 0,
- 'watchdefault' => 0,
+ 'watchcreations' => 1,
+ 'watchdefault' => 1,
'watchdeletion' => 0,
'watchlistdays' => 3.0,
'watchlisthideanons' => 0,
@@ -4025,6 +4305,7 @@ $wgDefaultUserOptions = array(
'watchlisthideown' => 0,
'watchlisthidepatrolled' => 0,
'watchmoves' => 0,
+ 'watchrollback' => 0,
'wllimit' => 250,
'useeditwarning' => 1,
'prefershttps' => 1,
@@ -4126,7 +4407,7 @@ $wgBlockDisablesLogin = false;
*
* @note Also that this will only protect _pages in the wiki_. Uploaded files
* will remain readable. You can use img_auth.php to protect uploaded files,
- * see http://www.mediawiki.org/wiki/Manual:Image_Authorization
+ * see https://www.mediawiki.org/wiki/Manual:Image_Authorization
*/
$wgWhitelistRead = false;
@@ -4211,6 +4492,7 @@ $wgGroupPermissions['*']['editmyoptions'] = true;
$wgGroupPermissions['user']['move'] = true;
$wgGroupPermissions['user']['move-subpages'] = true;
$wgGroupPermissions['user']['move-rootuserpages'] = true; // can move root userpages
+$wgGroupPermissions['user']['move-categorypages'] = true;
$wgGroupPermissions['user']['movefile'] = true;
$wgGroupPermissions['user']['read'] = true;
$wgGroupPermissions['user']['edit'] = true;
@@ -4258,6 +4540,7 @@ $wgGroupPermissions['sysop']['importupload'] = true;
$wgGroupPermissions['sysop']['move'] = true;
$wgGroupPermissions['sysop']['move-subpages'] = true;
$wgGroupPermissions['sysop']['move-rootuserpages'] = true;
+$wgGroupPermissions['sysop']['move-categorypages'] = true;
$wgGroupPermissions['sysop']['patrol'] = true;
$wgGroupPermissions['sysop']['autopatrol'] = true;
$wgGroupPermissions['sysop']['protect'] = true;
@@ -4279,8 +4562,9 @@ $wgGroupPermissions['sysop']['noratelimit'] = true;
$wgGroupPermissions['sysop']['movefile'] = true;
$wgGroupPermissions['sysop']['unblockself'] = true;
$wgGroupPermissions['sysop']['suppressredirect'] = true;
+#$wgGroupPermissions['sysop']['pagelang'] = true;
#$wgGroupPermissions['sysop']['upload_by_url'] = true;
-#$wgGroupPermissions['sysop']['mergehistory'] = true;
+$wgGroupPermissions['sysop']['mergehistory'] = true;
// Permission to change users' group assignments
$wgGroupPermissions['bureaucrat']['userrights'] = true;
@@ -4296,6 +4580,8 @@ $wgGroupPermissions['bureaucrat']['noratelimit'] = true;
#$wgGroupPermissions['suppress']['hideuser'] = true;
// To hide revisions/log items from users and Sysops
#$wgGroupPermissions['suppress']['suppressrevision'] = true;
+// To view revisions/log items hidden from users and Sysops
+#$wgGroupPermissions['suppress']['viewsuppressed'] = true;
// For private suppression log access
#$wgGroupPermissions['suppress']['suppressionlog'] = true;
@@ -4555,6 +4841,15 @@ $wgAvailableRights = array();
$wgDeleteRevisionsLimit = 0;
/**
+ * The maximum number of edits a user can have and
+ * can still be hidden by users with the hideuser permission.
+ * This is limited for performance reason.
+ * Set to false to disable the limit.
+ * @since 1.23
+ */
+$wgHideUserContribLimit = 1000;
+
+/**
* Number of accounts each IP address may create, 0 to disable.
*
* @warning Requires memcached
@@ -4587,12 +4882,6 @@ $wgSummarySpamRegex = array();
$wgEnableDnsBlacklist = false;
/**
- * @deprecated since 1.17 Use $wgEnableDnsBlacklist instead, only kept for
- * backward compatibility.
- */
-$wgEnableSorbs = false;
-
-/**
* List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
*
* This is an array of either a URL or an array with the URL and a key (should
@@ -4618,12 +4907,6 @@ $wgEnableSorbs = false;
$wgDnsBlacklistUrls = array( 'http.dnsbl.sorbs.net.' );
/**
- * @deprecated since 1.17 Use $wgDnsBlacklistUrls instead, only kept for
- * backward compatibility.
- */
-$wgSorbsUrl = array();
-
-/**
* Proxy whitelist, list of addresses that are assumed to be non-proxy despite
* what the other methods might say.
*/
@@ -4690,10 +4973,19 @@ $wgRateLimits = array(
'ip' => null,
'subnet' => null,
),
+ 'renderfile-nonstandard' => array( // same as above but for non-standard thumbnails
+ 'anon' => null,
+ 'user' => null,
+ 'newbie' => null,
+ 'ip' => null,
+ 'subnet' => null,
+ ),
);
/**
* Set to a filename to log rate limiter hits.
+ *
+ * @deprecated since 1.23, use $wgDebugLogGroups['ratelimit'] instead
*/
$wgRateLimitLog = null;
@@ -4746,11 +5038,6 @@ $wgSecretKey = false;
*/
$wgProxyList = array();
-/**
- * @deprecated since 1.14
- */
-$wgProxyKey = false;
-
/** @} */ # end of proxy scanner settings
/************************************************************************//**
@@ -4759,7 +5046,7 @@ $wgProxyKey = false;
*/
/**
- * Default cookie expiration time. Setting to 0 makes all cookies session-only.
+ * Default cookie lifetime, in seconds. Setting to 0 makes all cookies session-only.
*/
$wgCookieExpiration = 180 * 86400;
@@ -4806,17 +5093,6 @@ $wgCookiePrefix = false;
$wgCookieHttpOnly = true;
/**
- * If the requesting browser matches a regex in this blacklist, we won't
- * send it cookies with HttpOnly mode, even if $wgCookieHttpOnly is on.
- */
-$wgHttpOnlyBlacklist = array(
- // Internet Explorer for Mac; sometimes the cookies work, sometimes
- // they don't. It's difficult to predict, as combinations of path
- // and expiration options affect its parsing.
- '/^Mozilla\/4\.0 \(compatible; MSIE \d+\.\d+; Mac_PowerPC\)/',
-);
-
-/**
* A list of cookies that vary the cache (for use by extensions)
*/
$wgCacheVaryCookies = array();
@@ -4852,7 +5128,7 @@ $wgUseTeX = false;
*/
/**
- * Filename for debug logging. See http://www.mediawiki.org/wiki/How_to_debug
+ * Filename for debug logging. See https://www.mediawiki.org/wiki/How_to_debug
* The debug log file should be not be publicly accessible if it is used, as it
* may contain private data.
*/
@@ -4895,15 +5171,48 @@ $wgDebugComments = false;
$wgDebugDBTransactions = false;
/**
- * Write SQL queries to the debug log
+ * Write SQL queries to the debug log.
+ *
+ * This setting is only used $wgLBFactoryConf['class'] is set to
+ * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise
+ * the DBO_DEBUG flag must be set in the 'flags' option of the database
+ * connection to achieve the same functionality.
*/
$wgDebugDumpSql = false;
/**
- * Set to an array of log group keys to filenames.
+ * Trim logged SQL queries to this many bytes. Set 0/false/null to do no
+ * trimming.
+ * @since 1.24
+ */
+$wgDebugDumpSqlLength = 500;
+
+/**
+ * Map of string log group names to log destinations.
+ *
* If set, wfDebugLog() output for that group will go to that file instead
* of the regular $wgDebugLogFile. Useful for enabling selective logging
* in production.
+ *
+ * Log destinations may be one of the following:
+ * - false to completely remove from the output, including from $wgDebugLogFile.
+ * - string values specifying a filename or URI.
+ * - associative array mapping 'destination' key to the desired filename or URI.
+ * The associative array may also contain a 'sample' key with an integer value,
+ * specifying a sampling factor.
+ *
+ * @par Example:
+ * @code
+ * $wgDebugLogGroups['redis'] = '/var/log/mediawiki/redis.log';
+ * @endcode
+ *
+ * @par Advanced example:
+ * @code
+ * $wgDebugLogGroups['memcached'] = (
+ * 'destination' => '/var/log/mediawiki/memcached.log',
+ * 'sample' => 1000, // log 1 message out of every 1,000.
+ * );
+ * @endcode
*/
$wgDebugLogGroups = array();
@@ -4947,6 +5256,11 @@ $wgShowExceptionDetails = false;
/**
* If true, show a backtrace for database errors
+ *
+ * @note This setting only applies when connection errors and query errors are
+ * reported in the normal manner. $wgShowExceptionDetails applies in other cases,
+ * including those in which an uncaught exception is thrown from within the
+ * exception handler.
*/
$wgShowDBErrorBacktrace = false;
@@ -4987,20 +5301,11 @@ $wgProfileLimit = 0.0;
/**
* Don't put non-profiling info into log file
- */
-$wgProfileOnly = false;
-
-/**
- * Log sums from profiling into "profiling" table in db.
*
- * You have to create a 'profiling' table in your database before using
- * this feature. Run set $wgProfileToDatabase to true in
- * LocalSettings.php and run maintenance/update.php or otherwise
- * manually add patch-profiling.sql to your database.
- *
- * To enable profiling, edit StartProfiler.php
+ * @deprecated since 1.23, set the log file in
+ * $wgDebugLogGroups['profileoutput'] instead.
*/
-$wgProfileToDatabase = false;
+$wgProfileOnly = false;
/**
* If true, print a raw call tree instead of per-function report
@@ -5016,7 +5321,8 @@ $wgProfilePerHost = false;
* Host for UDP profiler.
*
* The host should be running a daemon which can be obtained from MediaWiki
- * Subversion at: http://svn.wikimedia.org/svnroot/mediawiki/trunk/udpprofile
+ * Git at:
+ * http://git.wikimedia.org/tree/operations%2Fsoftware.git/master/udpprofile
*/
$wgUDPProfilerHost = '127.0.0.1';
@@ -5028,9 +5334,9 @@ $wgUDPProfilerPort = '3811';
/**
* Format string for the UDP profiler. The UDP profiler invokes sprintf() with
- * (profile id, count, cpu, cpu_sq, real, real_sq, entry name) as arguments.
- * You can use sprintf's argument numbering/swapping capability to repeat,
- * re-order or omit fields.
+ * (profile id, count, cpu, cpu_sq, real, real_sq, entry name, memory) as
+ * arguments. You can use sprintf's argument numbering/swapping capability to
+ * repeat, re-order or omit fields.
*
* @see $wgStatsFormatString
* @since 1.22
@@ -5038,11 +5344,6 @@ $wgUDPProfilerPort = '3811';
$wgUDPProfilerFormatString = "%s - %d %f %f %f %f %s\n";
/**
- * Detects non-matching wfProfileIn/wfProfileOut calls
- */
-$wgDebugProfiling = false;
-
-/**
* Output debug message on every wfProfileIn/wfProfileOut
*/
$wgDebugFunctionEntry = false;
@@ -5112,21 +5413,6 @@ $wgParserTestFiles = array(
);
/**
- * If configured, specifies target CodeReview installation to send test
- * result data from 'parserTests.php --upload'
- *
- * Something like this:
- * $wgParserTestRemote = array(
- * 'api-url' => 'http://www.mediawiki.org/w/api.php',
- * 'repo' => 'MediaWiki',
- * 'suite' => 'ParserTests',
- * 'path' => '/trunk/phase3', // not used client-side; for reference
- * 'secret' => 'qmoicj3mc4mcklmqw', // Shared secret used in HMAC validation
- * );
- */
-$wgParserTestRemote = false;
-
-/**
* Allow running of javascript test suites via [[Special:JavaScriptTest]] (such as QUnit).
*/
$wgEnableJavaScriptTest = false;
@@ -5190,18 +5476,6 @@ $wgAdvancedSearchHighlighting = false;
$wgSearchHighlightBoundaries = '[\p{Z}\p{P}\p{C}]';
/**
- * Set to true to have the search engine count total
- * search matches to present in the Special:Search UI.
- * Not supported by every search engine shipped with MW.
- *
- * This could however be slow on larger wikis, and is pretty flaky
- * with the current title vs content split. Recommend avoiding until
- * that's been worked out cleanly; but this may aid in testing the
- * search UI and API to confirm that the result count works.
- */
-$wgCountTotalSearchHits = false;
-
-/**
* Template for OpenSearch suggestions, defaults to API action=opensearch
*
* Sites with heavy load would typically have these point to a custom
@@ -5220,6 +5494,12 @@ $wgOpenSearchTemplate = false;
$wgEnableOpenSearchSuggest = true;
/**
+ * Integer defining default number of entries to show on
+ * OpenSearch call.
+ */
+$wgOpenSearchDefaultLimit = 10;
+
+/**
* Expiry time for search suggestion responses
*/
$wgSearchSuggestCacheExpiry = 1200;
@@ -5244,25 +5524,6 @@ $wgNamespacesToBeSearchedDefault = array(
);
/**
- * Namespaces to be searched when user clicks the "Help" tab
- * on Special:Search.
- *
- * Same format as $wgNamespacesToBeSearchedDefault.
- */
-$wgNamespacesToBeSearchedHelp = array(
- NS_PROJECT => true,
- NS_HELP => true,
-);
-
-/**
- * If set to true the 'searcheverything' preference will be effective only for
- * logged-in users.
- * Useful for big wikis to maintain different search profiles for anonymous and
- * logged-in users.
- */
-$wgSearchEverythingOnlyLoggedIn = false;
-
-/**
* Disable the internal MySQL-based search, to allow it to be
* implemented by an extension instead.
*/
@@ -5350,11 +5611,6 @@ $wgPreviewOnOpenNamespaces = array(
);
/**
- * Go button goes straight to the edit screen if the article doesn't exist.
- */
-$wgGoToEdit = false;
-
-/**
* Enable the UniversalEditButton for browsers that support it
* (currently only Firefox with an extension)
* See http://universaleditbutton.org for more background information
@@ -5391,13 +5647,6 @@ if ( !isset( $wgCommandLineMode ) ) {
$wgCommandLineDarkBg = false;
/**
- * Array for extensions to register their maintenance scripts with the
- * system. The key is the name of the class and the value is the full
- * path to the file
- */
-$wgMaintenanceScripts = array();
-
-/**
* Set this to a string to put the wiki into read-only mode. The text will be
* used as an explanation to users.
*
@@ -5445,9 +5694,10 @@ $wgGitBin = '/usr/bin/git';
* @since 1.20
*/
$wgGitRepositoryViewers = array(
- 'https://gerrit.wikimedia.org/r/p/(.*)' => 'https://git.wikimedia.org/commit/%r/%H',
- 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)'
- => 'https://git.wikimedia.org/commit/%r/%H',
+ 'https://(?:[a-z0-9_]+@)?gerrit.wikimedia.org/r/(?:p/)?(.*)' =>
+ 'https://git.wikimedia.org/tree/%r/%H',
+ 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' =>
+ 'https://git.wikimedia.org/tree/%r/%H',
);
/** @} */ # End of maintenance }
@@ -5474,74 +5724,44 @@ $wgRCMaxAge = 13 * 7 * 24 * 3600;
$wgRCFilterByAge = false;
/**
- * List of Days and Limits options to list in the Special:Recentchanges and
+ * List of Limits options to list in the Special:Recentchanges and
* Special:Recentchangeslinked pages.
*/
$wgRCLinkLimits = array( 50, 100, 250, 500 );
-$wgRCLinkDays = array( 1, 3, 7, 14, 30 );
-
-/**
- * Send recent changes updates via UDP. The updates will be formatted for IRC.
- * Set this to the IP address of the receiver.
- *
- * @deprecated since 1.22, use $wgRCFeeds
- */
-$wgRC2UDPAddress = false;
-
-/**
- * Port number for RC updates
- *
- * @deprecated since 1.22, use $wgRCFeeds
- */
-$wgRC2UDPPort = false;
-
-/**
- * Prefix to prepend to each UDP packet.
- * This can be used to identify the wiki. A script is available called
- * mxircecho.py which listens on a UDP port, and uses a prefix ending in a
- * tab to identify the IRC channel to send the log line to.
- *
- * @deprecated since 1.22, use $wgRCFeeds
- */
-$wgRC2UDPPrefix = '';
-
-/**
- * If this is set to true, $wgLocalInterwiki will be prepended to links in the
- * IRC feed. If this is set to a string, that string will be used as the prefix.
- *
- * @deprecated since 1.22, use $wgRCFeeds
- */
-$wgRC2UDPInterwikiPrefix = false;
/**
- * Set to true to omit "bot" edits (by users with the bot permission) from the
- * UDP feed.
- *
- * @deprecated since 1.22, use $wgRCFeeds
+ * List of Days options to list in the Special:Recentchanges and
+ * Special:Recentchangeslinked pages.
*/
-$wgRC2UDPOmitBots = false;
+$wgRCLinkDays = array( 1, 3, 7, 14, 30 );
/**
* Destinations to which notifications about recent changes
* should be sent.
*
- * As of MediaWiki 1.22, the only supported 'engine' parameter option in core
- * is 'UDPRCFeedEngine', which is used to send recent changes over UDP to the
- * specified server.
+ * As of MediaWiki 1.22, there are 2 supported 'engine' parameter option in core:
+ * * 'UDPRCFeedEngine', which is used to send recent changes over UDP to the
+ * specified server.
+ * * 'RedisPubSubFeedEngine', which is used to send recent changes to Redis.
+ *
* The common options are:
* * 'uri' -- the address to which the notices are to be sent.
* * 'formatter' -- the class name (implementing RCFeedFormatter) which will
- * produce the text to send.
+ * produce the text to send. This can also be an object of the class.
* * 'omit_bots' -- whether the bot edits should be in the feed
+ * * 'omit_anon' -- whether anonymous edits should be in the feed
+ * * 'omit_user' -- whether edits by registered users should be in the feed
+ * * 'omit_minor' -- whether minor edits should be in the feed
+ * * 'omit_patrolled' -- whether patrolled edits should be in the feed
+ *
* The IRC-specific options are:
* * 'add_interwiki_prefix' -- whether the titles should be prefixed with
- * $wgLocalInterwiki.
+ * the first entry in the $wgLocalInterwikis array (or the value of
+ * $wgLocalInterwiki, if set)
+ *
* The JSON-specific options are:
* * 'channel' -- if set, the 'channel' parameter is also set in JSON values.
*
- * To ensure backwards-compatability, whenever $wgRC2UDPAddress is set, a
- * 'default' feed will be created reusing the deprecated $wgRC2UDP* variables.
- *
* @example $wgRCFeeds['example'] = array(
* 'formatter' => 'JSONRCFeedFormatter',
* 'uri' => "udp://localhost:1336",
@@ -5568,13 +5788,6 @@ $wgRCEngines = array(
);
/**
- * Enable user search in Special:Newpages
- * This is really a temporary hack around an index install bug on some Wikipedias.
- * Kill it once fixed.
- */
-$wgEnableNewpagesUserFilter = true;
-
-/**
* Use RC Patrolling to check for vandalism
*/
$wgUseRCPatrol = true;
@@ -5707,24 +5920,42 @@ $wgUnwatchedPageThreshold = false;
* To register a new one:
* @code
* $wgRecentChangesFlags['flag'] => array(
+ * // message for the letter displayed next to rows on changes lists
* 'letter' => 'letter-msg',
- * 'title' => 'tooltip-msg'
+ * // message for the tooltip of the letter
+ * 'title' => 'tooltip-msg',
+ * // optional (defaults to 'tooltip-msg'), message to use in the legend box
+ * 'legend' => 'legend-msg',
+ * // optional (defaults to 'flag'), CSS class to put on changes lists rows
+ * 'class' => 'css-class',
* );
* @endcode
*
- * Optional 'class' allows to set a css class different than the flag name.
- *
* @since 1.22
*/
$wgRecentChangesFlags = array(
- 'newpage' => array( 'letter' => 'newpageletter',
- 'title' => 'recentchanges-label-newpage' ),
- 'minor' => array( 'letter' => 'minoreditletter',
- 'title' => 'recentchanges-label-minor', 'class' => 'minoredit' ),
- 'bot' => array( 'letter' => 'boteditletter',
- 'title' => 'recentchanges-label-bot', 'class' => 'botedit' ),
- 'unpatrolled' => array( 'letter' => 'unpatrolledletter',
- 'title' => 'recentchanges-label-unpatrolled' ),
+ 'newpage' => array(
+ 'letter' => 'newpageletter',
+ 'title' => 'recentchanges-label-newpage',
+ 'legend' => 'recentchanges-legend-newpage',
+ ),
+ 'minor' => array(
+ 'letter' => 'minoreditletter',
+ 'title' => 'recentchanges-label-minor',
+ 'legend' => 'recentchanges-legend-minor',
+ 'class' => 'minoredit',
+ ),
+ 'bot' => array(
+ 'letter' => 'boteditletter',
+ 'title' => 'recentchanges-label-bot',
+ 'legend' => 'recentchanges-legend-bot',
+ 'class' => 'botedit',
+ ),
+ 'unpatrolled' => array(
+ 'letter' => 'unpatrolledletter',
+ 'title' => 'recentchanges-label-unpatrolled',
+ 'legend' => 'recentchanges-legend-unpatrolled',
+ ),
);
/** @} */ # end RC/watchlist }
@@ -5764,11 +5995,6 @@ $wgRightsText = null;
$wgRightsIcon = null;
/**
- * Set to an array of metadata terms. Else they will be loaded based on $wgRightsUrl
- */
-$wgLicenseTerms = false;
-
-/**
* Set this to some HTML to override the rights icon with an arbitrary logo
* @deprecated since 1.18 Use $wgFooterIcons['copyright']['copyright']
*/
@@ -5806,6 +6032,17 @@ $wgShowCreditsIfMax = true;
* Special:Import (for sysops). Since complete page history can be imported,
* these should be 'trusted'.
*
+ * This can either be a regular array, or an associative map specifying
+ * subprojects on the interwiki map of the target wiki, or a mix of the two,
+ * e.g.
+ * @code
+ * $wgImportSources = array(
+ * 'wikipedia' => array( 'cs', 'en', 'fr', 'zh' ),
+ * 'wikispecies',
+ * 'wikia' => array( 'animanga', 'brickipedia', 'desserts' ),
+ * );
+ * @endcode
+ *
* If a user has the 'import' permission but not the 'importupload' permission,
* they will only be able to run imports through this transwiki interface.
*/
@@ -5886,6 +6123,16 @@ $wgExtensionFunctions = array();
* Variables defined in extensions will override conflicting variables defined
* in the core.
*
+ * Since MediaWiki 1.23, use of this variable to define messages is discouraged; instead, store
+ * messages in JSON format and use $wgMessagesDirs. For setting other variables than
+ * $messages, $wgExtensionMessagesFiles should still be used. Use a DIFFERENT key because
+ * any entry having a key that also exists in $wgMessagesDirs will be ignored.
+ *
+ * Extensions using the JSON message format can preserve backward compatibility with
+ * earlier versions of MediaWiki by using a compatibility shim, such as one generated
+ * by the generateJsonI18n.php maintenance script, listing it under the SAME key
+ * as for the $wgMessagesDirs entry.
+ *
* @par Example:
* @code
* $wgExtensionMessagesFiles['ConfirmEdit'] = __DIR__.'/ConfirmEdit.i18n.php';
@@ -5894,6 +6141,34 @@ $wgExtensionFunctions = array();
$wgExtensionMessagesFiles = array();
/**
+ * Extension messages directories.
+ *
+ * Associative array mapping extension name to the path of the directory where message files can
+ * be found. The message files are expected to be JSON files named for their language code, e.g.
+ * en.json, de.json, etc. Extensions with messages in multiple places may specify an array of
+ * message directories.
+ *
+ * @par Simple example:
+ * @code
+ * $wgMessagesDirs['Example'] = __DIR__ . '/i18n';
+ * @endcode
+ *
+ * @par Complex example:
+ * @code
+ * $wgMessagesDirs['Example'] = array(
+ * __DIR__ . '/lib/ve/i18n',
+ * __DIR__ . '/lib/oojs-ui/i18n',
+ * __DIR__ . '/i18n',
+ * )
+ * @endcode
+ * @since 1.23
+ */
+$wgMessagesDirs = array(
+ 'core' => "$IP/languages/i18n",
+ 'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
+);
+
+/**
* Array of files with list(s) of extension entry points to be used in
* maintenance/mergeMessageFileList.php
* @since 1.22
@@ -5922,19 +6197,20 @@ $wgParserOutputHooks = array();
$wgEnableParserLimitReporting = true;
/**
- * List of valid skin names.
+ * List of valid skin names
+ *
* The key should be the name in all lower case, the value should be a properly
- * cased name for the skin. This value will be prefixed with "Skin" to create the
- * class name of the skin to load, and if the skin's class cannot be found through
- * the autoloader it will be used to load a .php file by that name in the skins directory.
- * The default skins will be added later, by Skin::getSkinNames(). Use
- * Skin::getSkinNames() as an accessor if you wish to have access to the full list.
+ * cased name for the skin. This value will be prefixed with "Skin" to create
+ * the class name of the skin to load. Use Skin::getSkinNames() as an accessor
+ * if you wish to have access to the full list.
*/
$wgValidSkinNames = array();
/**
- * Special page list.
- * See the top of SpecialPage.php for documentation.
+ * Special page list. This is an associative array mapping the (canonical) names of
+ * special pages to either a class name to be instantiated, or a callback to use for
+ * creating the special page object. In both cases, the result must be an instance of
+ * SpecialPage.
*/
$wgSpecialPages = array();
@@ -5944,30 +6220,63 @@ $wgSpecialPages = array();
$wgAutoloadClasses = array();
/**
- * An array of extension types and inside that their names, versions, authors,
- * urls, descriptions and pointers to localized description msgs. Note that
- * the version, url, description and descriptionmsg key can be omitted.
+ * Switch controlling legacy case-insensitive classloading.
+ * Do not disable if your wiki must support data created by PHP4, or by
+ * MediaWiki 1.4 or earlier.
+ */
+$wgAutoloadAttemptLowercase = true;
+
+/**
+ * An array of information about installed extensions keyed by their type.
+ *
+ * All but 'name', 'path' and 'author' can be omitted.
*
* @code
* $wgExtensionCredits[$type][] = array(
- * 'name' => 'Example extension',
- * 'version' => 1.9,
* 'path' => __FILE__,
- * 'author' => 'Foo Barstein',
- * 'url' => 'http://www.example.com/Example%20Extension/',
- * 'description' => 'An example extension',
+ * 'name' => 'Example extension',
+ * 'namemsg' => 'exampleextension-name',
+ * 'author' => array(
+ * 'Foo Barstein',
+ * ),
+ * 'version' => '1.9.0',
+ * 'url' => 'http://example.org/example-extension/',
* 'descriptionmsg' => 'exampleextension-desc',
+ * 'license-name' => 'GPL-2.0',
* );
* @endcode
*
- * Where $type is 'specialpage', 'parserhook', 'variable', 'media' or 'other'.
- * Where 'descriptionmsg' can be an array with message key and parameters:
- * 'descriptionmsg' => array( 'exampleextension-desc', param1, param2, ... ),
+ * The extensions are listed on Special:Version. This page also looks for a file
+ * named COPYING or LICENSE (optional .txt extension) and provides a link to
+ * view said file. When the 'license-name' key is specified, this file is
+ * interpreted as wikitext.
+ *
+ * - $type: One of 'specialpage', 'parserhook', 'variable', 'media', 'antispam',
+ * 'skin', 'api', or 'other', or any additional types as specified through the
+ * ExtensionTypes hook as used in SpecialVersion::getExtensionTypes().
+ *
+ * - name: Name of extension as an inline string instead of localizable message.
+ * Do not omit this even if 'namemsg' is provided, as it is used to override
+ * the path Special:Version uses to find extension's license info, and is
+ * required for backwards-compatibility with MediaWiki 1.23 and older.
+ *
+ * - namemsg (since MW 1.24): A message key for a message containing the
+ * extension's name, if the name is localizable. (For example, skin names
+ * usually are.)
+ *
+ * - author: A string or an array of strings. Authors can be linked using
+ * the regular wikitext link syntax. To have an internationalized version of
+ * "and others" show, add an element "...". This element can also be linked,
+ * for instance "[http://example ...]".
+ *
+ * - descriptionmsg: A message key or an an array with message key and parameters:
+ * `'descriptionmsg' => 'exampleextension-desc',`
+ *
+ * - description: Description of extension as an inline string instead of
+ * localizable message (omit in favour of 'descriptionmsg').
*
- * author can be a string or an array of strings. Authors can be linked using
- * the regular wikitext link syntax. To have an internationalized version of
- * "and others" show, add an element "...". This element can also be linked,
- * for instance "[http://example ...]".
+ * - license-name: Short name of the license (used as label for the link), such
+ * as "GPL-2.0" or "MIT" (https://spdx.org/licenses/ for a list of identifiers).
*/
$wgExtensionCredits = array();
@@ -6019,7 +6328,7 @@ $wgHooks = array();
*/
$wgJobClasses = array(
'refreshLinks' => 'RefreshLinksJob',
- 'refreshLinks2' => 'RefreshLinksJob2',
+ 'refreshLinks2' => 'RefreshLinksJob2', // b/c
'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
'sendMail' => 'EmaillingJob',
'enotifNotify' => 'EnotifNotifyJob',
@@ -6039,10 +6348,22 @@ $wgJobClasses = array(
* - Jobs that you would never want to run as part of a page rendering request.
* - Jobs that you want to run on specialized machines ( like transcoding, or a particular
* machine on your cluster has 'outside' web access you could restrict uploadFromUrl )
+ * These settings should be global to all wikis.
*/
$wgJobTypesExcludedFromDefaultQueue = array( 'AssembleUploadChunks', 'PublishStashedFile' );
/**
+ * Map of job types to how many job "work items" should be run per second
+ * on each job runner process. The meaning of "work items" varies per job,
+ * but typically would be something like "pages to update". A single job
+ * may have a variable number of work items, as is the case with batch jobs.
+ * This is used by runJobs.php and not jobs run via $wgJobRunRate.
+ * These settings should be global to all wikis.
+ * @var float[]
+ */
+$wgJobBackoffThrottling = array();
+
+/**
* Map of job types to configuration arrays.
* This determines which queue class and storage system is used for each job type.
* Job types that do not have explicit configuration will use the 'default' config.
@@ -6065,7 +6386,8 @@ $wgJobQueueAggregator = array(
* Expensive Querypages are already updated.
*/
$wgSpecialPageCacheUpdates = array(
- 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' )
+ 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' ),
+ 'Activeusers' => array( 'SpecialActiveUsers', 'cacheUpdate' ),
);
/**
@@ -6262,9 +6584,6 @@ $wgLogActions = array(
'protect/modify' => 'modifiedarticleprotection',
'protect/unprotect' => 'unprotectedarticle',
'protect/move_prot' => 'movedarticleprotection',
- 'upload/upload' => 'uploadedimage',
- 'upload/overwrite' => 'overwroteimage',
- 'upload/revert' => 'uploadedimage',
'import/upload' => 'import-logentry-upload',
'import/interwiki' => 'import-logentry-interwiki',
'merge/merge' => 'pagemerge-logentry',
@@ -6291,6 +6610,9 @@ $wgLogActionsHandlers = array(
'patrol/patrol' => 'PatrolLogFormatter',
'rights/rights' => 'RightsLogFormatter',
'rights/autopromote' => 'RightsLogFormatter',
+ 'upload/upload' => 'LogFormatter',
+ 'upload/overwrite' => 'LogFormatter',
+ 'upload/revert' => 'LogFormatter',
);
/**
@@ -6325,11 +6647,6 @@ $wgDisableQueryPageUpdate = false;
$wgSpecialPageGroups = array();
/**
- * Whether or not to sort special pages in Special:Specialpages
- */
-$wgSortSpecialPages = true;
-
-/**
* On Special:Unusedimages, consider images "used", if they are put
* into a category. Default (false) is not to count those as used.
*/
@@ -6379,12 +6696,6 @@ $wgActions = array(
'watch' => true,
);
-/**
- * Array of disabled article actions, e.g. view, edit, delete, etc.
- * @deprecated since 1.18; just set $wgActions['action'] = false instead
- */
-$wgDisabledActions = array();
-
/** @} */ # end actions }
/*************************************************************************//**
@@ -6470,7 +6781,7 @@ $wgExemptFromUserRobotsControl = null;
* Enable the MediaWiki API for convenient access to
* machine-readable data via api.php
*
- * See http://www.mediawiki.org/wiki/API
+ * See https://www.mediawiki.org/wiki/API
*/
$wgEnableAPI = true;
@@ -6499,13 +6810,76 @@ $wgDebugAPI = false;
/**
* API module extensions.
- * Associative array mapping module name to class name.
+ *
+ * Associative array mapping module name to modules specs;
+ * Each module spec is an associative array containing at least
+ * the 'class' key for the module's class, and optionally a
+ * 'factory' key for the factory function to use for the module.
+ *
+ * That factory function will be called with two parameters,
+ * the parent module (an instance of ApiBase, usually ApiMain)
+ * and the name the module was registered under. The return
+ * value must be an instance of the class given in the 'class'
+ * field.
+ *
+ * For backward compatibility, the module spec may also be a
+ * simple string containing the module's class name. In that
+ * case, the class' constructor will be called with the parent
+ * module and module name as parameters, as described above.
+ *
+ * Examples for registering API modules:
+ *
+ * @code
+ * $wgAPIModules['foo'] = 'ApiFoo';
+ * $wgAPIModules['bar'] = array(
+ * 'class' => 'ApiBar',
+ * 'factory' => function( $main, $name ) { ... }
+ * );
+ * $wgAPIModules['xyzzy'] = array(
+ * 'class' => 'ApiXyzzy',
+ * 'factory' => array( 'XyzzyFactory', 'newApiModule' )
+ * );
+ * @endcode
+ *
* Extension modules may override the core modules.
- * @todo Describe each of the variables, group them and add examples
+ * See ApiMain::$Modules for a list of the core modules.
*/
$wgAPIModules = array();
+
+/**
+ * API format module extensions.
+ * Associative array mapping format module name to module specs (see $wgAPIModules).
+ * Extension modules may override the core modules.
+ *
+ * See ApiMain::$Formats for a list of the core format modules.
+ */
+$wgAPIFormatModules = array();
+
+/**
+ * API Query meta module extensions.
+ * Associative array mapping meta module name to module specs (see $wgAPIModules).
+ * Extension modules may override the core modules.
+ *
+ * See ApiQuery::$QueryMetaModules for a list of the core meta modules.
+ */
$wgAPIMetaModules = array();
+
+/**
+ * API Query prop module extensions.
+ * Associative array mapping prop module name to module specs (see $wgAPIModules).
+ * Extension modules may override the core modules.
+ *
+ * See ApiQuery::$QueryPropModules for a list of the core prop modules.
+ */
$wgAPIPropModules = array();
+
+/**
+ * API Query list module extensions.
+ * Associative array mapping list module name to module specs (see $wgAPIModules).
+ * Extension modules may override the core modules.
+ *
+ * See ApiQuery::$QueryListModules for a list of the core list modules.
+ */
$wgAPIListModules = array();
/**
@@ -6683,12 +7057,12 @@ $wgShellLocale = 'en_US.utf8';
*/
/**
- * Timeout for HTTP requests done internally
+ * Timeout for HTTP requests done internally, in seconds.
*/
$wgHTTPTimeout = 25;
/**
- * Timeout for Asynchronous (background) HTTP requests
+ * Timeout for Asynchronous (background) HTTP requests, in seconds.
*/
$wgAsyncHTTPTimeout = 25;
@@ -6720,6 +7094,14 @@ $wgHTTPConnectTimeout = 5e0;
$wgJobRunRate = 1;
/**
+ * When $wgJobRunRate > 0, try to run jobs asynchronously, spawning a new process
+ * to handle the job execution, instead of blocking the request until the job
+ * execution finishes.
+ * @since 1.23
+ */
+$wgRunJobsAsync = true;
+
+/**
* Number of rows to update per job
*/
$wgUpdateRowsPerJob = 500;
@@ -6729,15 +7111,6 @@ $wgUpdateRowsPerJob = 500;
*/
$wgUpdateRowsPerQuery = 100;
-/**
- * Do not purge all the pages that use a page when it is edited
- * if there are more than this many such pages. This is used to
- * avoid invalidating a large portion of the squid/parser cache.
- *
- * This setting should factor in any squid/parser cache expiry settings.
- */
-$wgMaxBacklinksInvalidate = false;
-
/** @} */ # End job queue }
/************************************************************************//**
@@ -6886,10 +7259,45 @@ $wgSiteTypes = array(
);
/**
- * Formerly a list of files for HipHop compilation
- * @deprecated since 1.22
+ * Whether the page_props table has a pp_sortkey column. Set to false in case
+ * the respective database schema change was not applied.
+ * @since 1.23
+ */
+$wgPagePropsHaveSortkey = true;
+
+/**
+ * Port where you have HTTPS running
+ * Supports HTTPS on non-standard ports
+ * @see bug 65184
+ * @since 1.24
+ */
+$wgHttpsPort = 443;
+
+/**
+ * Secret and algorithm for hmac-based key derivation function (fast,
+ * cryptographically secure random numbers).
+ * This should be set in LocalSettings.php, otherwise wgSecretKey will
+ * be used.
+ * @since 1.24
+ */
+$wgHKDFSecret = false;
+$wgHKDFAlgorithm = 'sha256';
+
+/**
+ * Enable page language feature
+ * Allows setting page language in database
+ * @var bool
+ * @since 1.24
+ */
+$wgPageLanguageUseDB = false;
+
+/**
+ * Enable use of the *_namespace fields of the pagelinks, redirect, and templatelinks tables.
+ * Set this only if the fields are fully populated. This may be removed in 1.25.
+ * @var bool
+ * @since 1.24
*/
-$wgCompiledFiles = array();
+$wgUseLinkNamespaceDBFields = true;
/**
* For really cool vim folding this needs to be at the end:
diff --git a/includes/Defines.php b/includes/Defines.php
index 86c5520b..017e9ea4 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -59,7 +59,6 @@ define( 'DB_MASTER', -2 ); # Write to master (or only server)
# Obsolete aliases
define( 'DB_READ', -1 );
define( 'DB_WRITE', -2 );
-define( 'DB_LAST', -3 ); # deprecated since 2008, usage throws exception
/**@{
* Virtual namespaces; don't appear in the page database
@@ -113,23 +112,33 @@ define( 'CACHE_NONE', 0 ); // Do not cache
define( 'CACHE_DB', 1 ); // Store cache objects in the DB
define( 'CACHE_MEMCACHED', 2 ); // MemCached, must specify servers in $wgMemCacheServers
define( 'CACHE_ACCEL', 3 ); // APC, XCache or WinCache
-define( 'CACHE_DBA', 4 ); // Use PHP's DBA extension to store in a DBM-style database
/**@}*/
/**@{
* Media types.
* This defines constants for the value returned by File::getMediaType()
*/
-define( 'MEDIATYPE_UNKNOWN', 'UNKNOWN' ); // unknown format
-define( 'MEDIATYPE_BITMAP', 'BITMAP' ); // some bitmap image or image source (like psd, etc). Can't scale up.
-define( 'MEDIATYPE_DRAWING', 'DRAWING' ); // some vector drawing (SVG, WMF, PS, ...) or image source (oo-draw, etc). Can scale up.
-define( 'MEDIATYPE_AUDIO', 'AUDIO' ); // simple audio file (ogg, mp3, wav, midi, whatever)
-define( 'MEDIATYPE_VIDEO', 'VIDEO' ); // simple video file (ogg, mpg, etc; no not include formats here that may contain executable sections or scripts!)
-define( 'MEDIATYPE_MULTIMEDIA', 'MULTIMEDIA' ); // Scriptable Multimedia (flash, advanced video container formats, etc)
-define( 'MEDIATYPE_OFFICE', 'OFFICE' ); // Office Documents, Spreadsheets (office formats possibly containing apples, scripts, etc)
-define( 'MEDIATYPE_TEXT', 'TEXT' ); // Plain text (possibly containing program code or scripts)
-define( 'MEDIATYPE_EXECUTABLE', 'EXECUTABLE' ); // binary executable
-define( 'MEDIATYPE_ARCHIVE', 'ARCHIVE' ); // archive file (zip, tar, etc)
+// unknown format
+define( 'MEDIATYPE_UNKNOWN', 'UNKNOWN' );
+// some bitmap image or image source (like psd, etc). Can't scale up.
+define( 'MEDIATYPE_BITMAP', 'BITMAP' );
+// some vector drawing (SVG, WMF, PS, ...) or image source (oo-draw, etc). Can scale up.
+define( 'MEDIATYPE_DRAWING', 'DRAWING' );
+// simple audio file (ogg, mp3, wav, midi, whatever)
+define( 'MEDIATYPE_AUDIO', 'AUDIO' );
+// simple video file (ogg, mpg, etc;
+// no not include formats here that may contain executable sections or scripts!)
+define( 'MEDIATYPE_VIDEO', 'VIDEO' );
+// Scriptable Multimedia (flash, advanced video container formats, etc)
+define( 'MEDIATYPE_MULTIMEDIA', 'MULTIMEDIA' );
+// Office Documents, Spreadsheets (office formats possibly containing apples, scripts, etc)
+define( 'MEDIATYPE_OFFICE', 'OFFICE' );
+// Plain text (possibly containing program code or scripts)
+define( 'MEDIATYPE_TEXT', 'TEXT' );
+// binary executable
+define( 'MEDIATYPE_EXECUTABLE', 'EXECUTABLE' );
+// archive file (zip, tar, etc)
+define( 'MEDIATYPE_ARCHIVE', 'ARCHIVE' );
/**@}*/
/**@{
@@ -155,11 +164,6 @@ define( 'ALF_NO_BLOCK_LOCK', 8 );
* Date format selectors; used in user preference storage and by
* Language::date() and co.
*/
-/*define( 'MW_DATE_DEFAULT', '0' );
-define( 'MW_DATE_MDY', '1' );
-define( 'MW_DATE_DMY', '2' );
-define( 'MW_DATE_YMD', '3' );
-define( 'MW_DATE_ISO', 'ISO 8601' );*/
define( 'MW_DATE_DEFAULT', 'default' );
define( 'MW_DATE_MDY', 'mdy' );
define( 'MW_DATE_DMY', 'dmy' );
@@ -172,9 +176,7 @@ define( 'MW_DATE_ISO', 'ISO 8601' );
*/
define( 'RC_EDIT', 0 );
define( 'RC_NEW', 1 );
-define( 'RC_MOVE', 2 ); // obsolete
define( 'RC_LOG', 3 );
-define( 'RC_MOVE_OVER_REDIRECT', 4 ); // obsolete
define( 'RC_EXTERNAL', 5 );
/**@}*/
@@ -279,6 +281,7 @@ define( 'CONTENT_MODEL_WIKITEXT', 'wikitext' );
define( 'CONTENT_MODEL_JAVASCRIPT', 'javascript' );
define( 'CONTENT_MODEL_CSS', 'css' );
define( 'CONTENT_MODEL_TEXT', 'text' );
+define( 'CONTENT_MODEL_JSON', 'json' );
/**@}*/
/**@{
@@ -288,12 +291,20 @@ define( 'CONTENT_MODEL_TEXT', 'text' );
* Extensions are free to use the below formats, or define their own.
* It is recommended to stick with the conventions for MIME types.
*/
-define( 'CONTENT_FORMAT_WIKITEXT', 'text/x-wiki' ); // wikitext
-define( 'CONTENT_FORMAT_JAVASCRIPT', 'text/javascript' ); // for js pages
-define( 'CONTENT_FORMAT_CSS', 'text/css' ); // for css pages
-define( 'CONTENT_FORMAT_TEXT', 'text/plain' ); // for future use, e.g. with some plain-html messages.
-define( 'CONTENT_FORMAT_HTML', 'text/html' ); // for future use, e.g. with some plain-html messages.
-define( 'CONTENT_FORMAT_SERIALIZED', 'application/vnd.php.serialized' ); // for future use with the api and for extensions
-define( 'CONTENT_FORMAT_JSON', 'application/json' ); // for future use with the api, and for use by extensions
-define( 'CONTENT_FORMAT_XML', 'application/xml' ); // for future use with the api, and for use by extensions
+// wikitext
+define( 'CONTENT_FORMAT_WIKITEXT', 'text/x-wiki' );
+// for js pages
+define( 'CONTENT_FORMAT_JAVASCRIPT', 'text/javascript' );
+// for css pages
+define( 'CONTENT_FORMAT_CSS', 'text/css' );
+// for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_TEXT', 'text/plain' );
+// for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_HTML', 'text/html' );
+// for future use with the api and for extensions
+define( 'CONTENT_FORMAT_SERIALIZED', 'application/vnd.php.serialized' );
+// for future use with the api, and for use by extensions
+define( 'CONTENT_FORMAT_JSON', 'application/json' );
+// for future use with the api, and for use by extensions
+define( 'CONTENT_FORMAT_XML', 'application/xml' );
/**@}*/
diff --git a/includes/DeprecatedGlobal.php b/includes/DeprecatedGlobal.php
index d48bd0b0..14329d32 100644
--- a/includes/DeprecatedGlobal.php
+++ b/includes/DeprecatedGlobal.php
@@ -23,20 +23,22 @@
/**
* Class to allow throwing wfDeprecated warnings
* when people use globals that we do not want them to.
- * (For example like $wgArticle)
*/
class DeprecatedGlobal extends StubObject {
- // The m's are to stay consistent with parent class.
- protected $mRealValue, $mVersion;
+ protected $realValue, $version;
function __construct( $name, $realValue, $version = false ) {
parent::__construct( $name );
- $this->mRealValue = $realValue;
- $this->mVersion = $version;
+ $this->realValue = $realValue;
+ $this->version = $version;
}
+ // @codingStandardsIgnoreStart
+ // PSR2.Methods.MethodDeclaration.Underscore
+ // PSR2.Classes.PropertyDeclaration.ScopeMissing
function _newObject() {
+
/* Put the caller offset for wfDeprecated as 6, as
* that gives the function that uses this object, since:
* 1 = this function ( _newObject )
@@ -49,7 +51,8 @@ class DeprecatedGlobal extends StubObject {
* sequences for this method, but that seems to be
* rather unlikely.
*/
- wfDeprecated( '$' . $this->mGlobal, $this->mVersion, false, 6 );
- return $this->mRealValue;
+ wfDeprecated( '$' . $this->global, $this->version, false, 6 );
+ return $this->realValue;
}
+ // @codingStandardsIgnoreEnd
}
diff --git a/includes/EditPage.php b/includes/EditPage.php
index 4dd83845..128244a8 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -1,6 +1,6 @@
<?php
/**
- * Page edition user interface.
+ * User interface for page editing.
*
* 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
@@ -36,7 +36,6 @@
* headaches, which may be fatal.
*/
class EditPage {
-
/**
* Status: Article successfully updated
*/
@@ -68,11 +67,6 @@ class EditPage {
const AS_CONTENT_TOO_BIG = 216;
/**
- * Status: User cannot edit? (not used)
- */
- const AS_USER_CANNOT_EDIT = 217;
-
- /**
* Status: this anonymous user is not allowed to edit this page
*/
const AS_READ_ONLY_PAGE_ANON = 218;
@@ -105,7 +99,7 @@ class EditPage {
const AS_NO_CREATE_PERMISSION = 223;
/**
- * Status: user tried to create a blank page
+ * Status: user tried to create a blank page and wpIgnoreBlankArticle == false
*/
const AS_BLANK_ARTICLE = 224;
@@ -131,11 +125,6 @@ class EditPage {
const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
/**
- * not used
- */
- const AS_OK = 230;
-
- /**
* Status: WikiPage::doEdit() was unsuccessful
*/
const AS_END = 231;
@@ -182,8 +171,9 @@ class EditPage {
* The cookie will be removed instantly if the JavaScript runs.
*
* Otherwise, though, we don't want the cookies to accumulate.
- * RFC 2109 ( https://www.ietf.org/rfc/rfc2109.txt ) specifies a possible limit of only 20 cookies per domain.
- * This still applies at least to some versions of IE without full updates:
+ * RFC 2109 ( https://www.ietf.org/rfc/rfc2109.txt ) specifies a possible
+ * limit of only 20 cookies per domain. This still applies at least to some
+ * versions of IE without full updates:
* https://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx
*
* A value of 20 minutes should be enough to take into account slow loads and minor
@@ -191,65 +181,166 @@ class EditPage {
*/
const POST_EDIT_COOKIE_DURATION = 1200;
- /**
- * @var Article
- */
- var $mArticle;
+ /** @var Article */
+ public $mArticle;
- /**
- * @var Title
- */
- var $mTitle;
+ /** @var Title */
+ public $mTitle;
+
+ /** @var null|Title */
private $mContextTitle = null;
- var $action = 'submit';
- var $isConflict = false;
- var $isCssJsSubpage = false;
- var $isCssSubpage = false;
- var $isJsSubpage = false;
- var $isWrongCaseCssJsPage = false;
- var $isNew = false; // new page or new section
- var $deletedSinceEdit;
- var $formtype;
- var $firsttime;
- var $lastDelete;
- var $mTokenOk = false;
- var $mTokenOkExceptSuffix = false;
- var $mTriedSave = false;
- var $incompleteForm = false;
- var $tooBig = false;
- var $kblength = false;
- var $missingComment = false;
- var $missingSummary = false;
- var $allowBlankSummary = false;
- var $autoSumm = '';
- var $hookError = '';
- #var $mPreviewTemplates;
-
- /**
- * @var ParserOutput
- */
- var $mParserOutput;
-
- /**
- * Has a summary been preset using GET parameter &summary= ?
- * @var Bool
- */
- var $hasPresetSummary = false;
-
- var $mBaseRevision = false;
- var $mShowSummaryField = true;
+
+ /** @var string */
+ public $action = 'submit';
+
+ /** @var bool */
+ public $isConflict = false;
+
+ /** @var bool */
+ public $isCssJsSubpage = false;
+
+ /** @var bool */
+ public $isCssSubpage = false;
+
+ /** @var bool */
+ public $isJsSubpage = false;
+
+ /** @var bool */
+ public $isWrongCaseCssJsPage = false;
+
+ /** @var bool New page or new section */
+ public $isNew = false;
+
+ /** @var bool */
+ public $deletedSinceEdit;
+
+ /** @var string */
+ public $formtype;
+
+ /** @var bool */
+ public $firsttime;
+
+ /** @var bool|stdClass */
+ public $lastDelete;
+
+ /** @var bool */
+ public $mTokenOk = false;
+
+ /** @var bool */
+ public $mTokenOkExceptSuffix = false;
+
+ /** @var bool */
+ public $mTriedSave = false;
+
+ /** @var bool */
+ public $incompleteForm = false;
+
+ /** @var bool */
+ public $tooBig = false;
+
+ /** @var bool */
+ public $kblength = false;
+
+ /** @var bool */
+ public $missingComment = false;
+
+ /** @var bool */
+ public $missingSummary = false;
+
+ /** @var bool */
+ public $allowBlankSummary = false;
+
+ /** @var bool */
+ protected $blankArticle = false;
+
+ /** @var bool */
+ protected $allowBlankArticle = false;
+
+ /** @var string */
+ public $autoSumm = '';
+
+ /** @var string */
+ public $hookError = '';
+
+ /** @var ParserOutput */
+ public $mParserOutput;
+
+ /** @var bool Has a summary been preset using GET parameter &summary= ? */
+ public $hasPresetSummary = false;
+
+ /** @var bool */
+ public $mBaseRevision = false;
+
+ /** @var bool */
+ public $mShowSummaryField = true;
# Form values
- var $save = false, $preview = false, $diff = false;
- var $minoredit = false, $watchthis = false, $recreate = false;
- var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
- var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
- var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
- var $contentModel = null, $contentFormat = null;
+
+ /** @var bool */
+ public $save = false;
+
+ /** @var bool */
+ public $preview = false;
+
+ /** @var bool */
+ public $diff = false;
+
+ /** @var bool */
+ public $minoredit = false;
+
+ /** @var bool */
+ public $watchthis = false;
+
+ /** @var bool */
+ public $recreate = false;
+
+ /** @var string */
+ public $textbox1 = '';
+
+ /** @var string */
+ public $textbox2 = '';
+
+ /** @var string */
+ public $summary = '';
+
+ /** @var bool */
+ public $nosummary = false;
+
+ /** @var string */
+ public $edittime = '';
+
+ /** @var string */
+ public $section = '';
+
+ /** @var string */
+ public $sectiontitle = '';
+
+ /** @var string */
+ public $starttime = '';
+
+ /** @var int */
+ public $oldid = 0;
+
+ /** @var string */
+ public $editintro = '';
+
+ /** @var null */
+ public $scrolltop = null;
+
+ /** @var bool */
+ public $bot = true;
+
+ /** @var null|string */
+ public $contentModel = null;
+
+ /** @var null|string */
+ public $contentFormat = null;
# Placeholders for text injection by hooks (must be HTML)
# extensions should take care to _append_ to the present value
- public $editFormPageTop = ''; // Before even the preview
+
+ /** @var string Before even the preview */
+ public $editFormPageTop = '';
public $editFormTextTop = '';
public $editFormTextBeforeContent = '';
public $editFormTextAfterWarn = '';
@@ -265,15 +356,17 @@ class EditPage {
public $suppressIntro = false;
- /**
- * Set to true to allow editing of non-text content types.
- *
- * @var bool
- */
+ /** @var bool Set to true to allow editing of non-text content types. */
public $allowNonTextContent = false;
+ /** @var bool */
+ protected $edit;
+
+ /** @var bool */
+ public $live;
+
/**
- * @param $article Article
+ * @param Article $article
*/
public function __construct( Article $article ) {
$this->mArticle = $article;
@@ -303,7 +396,7 @@ class EditPage {
/**
* Set the context Title object
*
- * @param $title Title object or null
+ * @param Title|null $title Title object or null
*/
public function setContextTitle( $title ) {
$this->mContextTitle = $title;
@@ -314,7 +407,7 @@ class EditPage {
* If not set, $wgTitle will be returned. This behavior might change in
* the future to return $this->mTitle instead.
*
- * @return Title object
+ * @return Title
*/
public function getContextTitle() {
if ( is_null( $this->mContextTitle ) ) {
@@ -325,6 +418,18 @@ class EditPage {
}
}
+ /**
+ * Returns if the given content model is editable.
+ *
+ * @param string $modelId The ID of the content model to test. Use CONTENT_MODEL_XXX constants.
+ * @return bool
+ * @throws MWException If $modelId has no known handler
+ */
+ public function isSupportedContentModel( $modelId ) {
+ return $this->allowNonTextContent ||
+ ContentHandler::getForModelID( $modelId ) instanceof TextContentHandler;
+ }
+
function submit() {
$this->edit();
}
@@ -406,6 +511,7 @@ class EditPage {
$this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
$this->isCssSubpage = $this->mTitle->isCssSubpage();
$this->isJsSubpage = $this->mTitle->isJsSubpage();
+ // @todo FIXME: Silly assignment.
$this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
# Show applicable editing introductions
@@ -463,9 +569,9 @@ class EditPage {
# Ignore some permissions errors when a user is just previewing/viewing diffs
$remove = array();
foreach ( $permErrors as $error ) {
- if ( ( $this->preview || $this->diff ) &&
- ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' ) )
- {
+ if ( ( $this->preview || $this->diff )
+ && ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' )
+ ) {
$remove[] = $error;
}
}
@@ -482,8 +588,8 @@ class EditPage {
* "View source for ..." page displaying the source code after the error message.
*
* @since 1.19
- * @param array $permErrors of permissions errors, as returned by
- * Title::getUserPermissionsErrors().
+ * @param array $permErrors Array of permissions errors, as returned by
+ * Title::getUserPermissionsErrors().
* @throws PermissionsError
*/
protected function displayPermissionsError( array $permErrors ) {
@@ -506,8 +612,13 @@ class EditPage {
throw new PermissionsError( $action, $permErrors );
}
+ wfRunHooks( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) );
+
$wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
+ $wgOut->setPageTitle( wfMessage(
+ 'viewsource-title',
+ $this->getContextTitle()->getPrefixedText()
+ ) );
$wgOut->addBacklinkSubtitle( $this->getContextTitle() );
$wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
$wgOut->addHTML( "<hr />\n" );
@@ -535,26 +646,6 @@ class EditPage {
}
/**
- * Show a read-only error
- * Parameters are the same as OutputPage:readOnlyPage()
- * Redirect to the article page if redlink=1
- * @deprecated in 1.19; use displayPermissionsError() instead
- */
- function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- global $wgRequest, $wgOut;
- if ( $wgRequest->getBool( 'redlink' ) ) {
- // The edit page was reached via a red link.
- // Redirect to the article page and let them click the edit tab if
- // they really want a permission error.
- $wgOut->redirect( $this->mTitle->getFullURL() );
- } else {
- $wgOut->readOnlyPage( $source, $protected, $reasons, $action );
- }
- }
-
- /**
* Should we show a preview when the edit form is first shown?
*
* @return bool
@@ -570,13 +661,15 @@ class EditPage {
} elseif ( $this->section == 'new' ) {
// Nothing *to* preview for new sections
return false;
- } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
+ } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() )
+ && $wgUser->getOption( 'previewonfirst' )
+ ) {
// Standard preference behavior
return true;
- } elseif ( !$this->mTitle->exists() &&
- isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) &&
- $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
- {
+ } elseif ( !$this->mTitle->exists()
+ && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
+ && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
+ ) {
// Categories are special
return true;
} else {
@@ -609,7 +702,7 @@ class EditPage {
* Subclasses may override this to replace the default behavior, which is
* to check ContentHandler::supportsSections.
*
- * @return bool true if this edit page supports sections, false otherwise.
+ * @return bool True if this edit page supports sections, false otherwise.
*/
protected function isSectionEditSupported() {
$contentHandler = ContentHandler::getForTitle( $this->mTitle );
@@ -618,7 +711,8 @@ class EditPage {
/**
* This function collects the form data and uses it to populate various member variables.
- * @param $request WebRequest
+ * @param WebRequest $request
+ * @throws ErrorPageError
*/
function importFormData( &$request ) {
global $wgContLang, $wgUser;
@@ -685,9 +779,13 @@ class EditPage {
// suhosin.request.max_value_length (d'oh)
$this->incompleteForm = true;
} else {
- // edittime should be one of our last fields; if it's missing,
- // the submission probably broke somewhere in the middle.
- $this->incompleteForm = is_null( $this->edittime );
+ // If we receive the last parameter of the request, we can fairly
+ // claim the POST request has not been truncated.
+
+ // TODO: softened the check for cutover. Once we determine
+ // that it is safe, we should complete the transition by
+ // removing the "edittime" clause.
+ $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' ) && is_null( $this->edittime ) );
}
if ( $this->incompleteForm ) {
# If the form is incomplete, force to preview.
@@ -734,15 +832,18 @@ class EditPage {
$this->watchthis = $request->getCheck( 'wpWatchthis' );
# Don't force edit summaries when a user is editing their own user or talk page
- if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK ) &&
- $this->mTitle->getText() == $wgUser->getName() )
- {
+ if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
+ && $this->mTitle->getText() == $wgUser->getName()
+ ) {
$this->allowBlankSummary = true;
} else {
- $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary' );
+ $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
+ || !$wgUser->getOption( 'forceeditsummary' );
}
$this->autoSumm = $request->getText( 'wpAutoSummary' );
+
+ $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
} else {
# Not a posted form? Start with nothing.
wfDebug( __METHOD__ . ": Not a posted form.\n" );
@@ -756,7 +857,8 @@ class EditPage {
$this->save = false;
$this->diff = false;
$this->minoredit = false;
- $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overridden by request parameters
+ // Watch may be overridden by request parameters
+ $this->watchthis = $request->getBool( 'watchthis', false );
$this->recreate = false;
// When creating a new section, we can preload a section title by passing it as the
@@ -765,8 +867,7 @@ class EditPage {
$this->sectiontitle = $request->getVal( 'preloadtitle' );
// Once wpSummary isn't being use for setting section titles, we should delete this.
$this->summary = $request->getVal( 'preloadtitle' );
- }
- elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
+ } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
$this->summary = $request->getText( 'summary' );
if ( $this->summary !== '' ) {
$this->hasPresetSummary = true;
@@ -783,12 +884,26 @@ class EditPage {
$this->bot = $request->getBool( 'bot', true );
$this->nosummary = $request->getBool( 'nosummary' );
- $content_handler = ContentHandler::getForTitle( $this->mTitle );
- $this->contentModel = $request->getText( 'model', $content_handler->getModelID() ); #may be overridden by revision
- $this->contentFormat = $request->getText( 'format', $content_handler->getDefaultFormat() ); #may be overridden by revision
+ // May be overridden by revision.
+ $this->contentModel = $request->getText( 'model', $this->contentModel );
+ // May be overridden by revision.
+ $this->contentFormat = $request->getText( 'format', $this->contentFormat );
- #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
- #TODO: check if the desired content model supports the given content format!
+ if ( !ContentHandler::getForModelID( $this->contentModel )
+ ->isSupportedFormat( $this->contentFormat )
+ ) {
+ throw new ErrorPageError(
+ 'editpage-notsupportedcontentformat-title',
+ 'editpage-notsupportedcontentformat-text',
+ array( $this->contentFormat, ContentHandler::getLocalizedName( $this->contentModel ) )
+ );
+ }
+
+ /**
+ * @todo Check if the desired model is allowed in this namespace, and if
+ * a transition from the page's current model to the new model is
+ * allowed.
+ */
$this->live = $request->getCheck( 'live' );
$this->editintro = $request->getText( 'editintro',
@@ -807,7 +922,7 @@ class EditPage {
* this method should be overridden and return the page text that will be used
* for saving, preview parsing and so on...
*
- * @param $request WebRequest
+ * @param WebRequest $request
*/
protected function importContentFormData( &$request ) {
return; // Don't do anything, EditPage already extracted wpTextbox1
@@ -816,7 +931,7 @@ class EditPage {
/**
* Initialise form fields in the object
* Called on the first invocation, e.g. when a user clicks an edit link
- * @return bool -- if the requested section is valid
+ * @return bool If the requested section is valid
*/
function initialiseForm() {
global $wgUser;
@@ -850,37 +965,14 @@ class EditPage {
}
/**
- * Fetch initial editing page content.
- *
- * @param $def_text string|bool
- * @return mixed string on success, $def_text for invalid sections
- * @private
- * @deprecated since 1.21, get WikiPage::getContent() instead.
- */
- function getContent( $def_text = false ) {
- ContentHandler::deprecated( __METHOD__, '1.21' );
-
- if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
- $def_content = $this->toEditContent( $def_text );
- } else {
- $def_content = false;
- }
-
- $content = $this->getContentObject( $def_content );
-
- // Note: EditPage should only be used with text based content anyway.
- return $this->toEditText( $content );
- }
-
- /**
* @param Content|null $def_content The default value to return
*
- * @return mixed Content on success, $def_content for invalid sections
+ * @return Content|null Content on success, $def_content for invalid sections
*
* @since 1.21
*/
protected function getContentObject( $def_content = null ) {
- global $wgOut, $wgRequest;
+ global $wgOut, $wgRequest, $wgUser, $wgContLang;
wfProfileIn( __METHOD__ );
@@ -900,14 +992,15 @@ class EditPage {
$preload = $wgRequest->getVal( 'preload',
// Custom preload text for new sections
$this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
+ $params = $wgRequest->getArray( 'preloadparams', array() );
- $content = $this->getPreloadedContent( $preload );
+ $content = $this->getPreloadedContent( $preload, $params );
}
// For existing pages, get text based on "undo" or section parameters.
} else {
if ( $this->section != '' ) {
// Get section edit text (returns $def_text for invalid sections)
- $orig = $this->getOriginalContent();
+ $orig = $this->getOriginalContent( $wgUser );
$content = $orig ? $orig->getSection( $this->section ) : null;
if ( !$content ) {
@@ -918,10 +1011,6 @@ class EditPage {
$undo = $wgRequest->getInt( 'undo' );
if ( $undo > 0 && $undoafter > 0 ) {
- if ( $undo < $undoafter ) {
- # If they got undoafter and undo round the wrong way, switch them
- list( $undo, $undoafter ) = array( $undoafter, $undo );
- }
$undorev = Revision::newFromId( $undo );
$oldrev = Revision::newFromId( $undoafter );
@@ -930,8 +1019,6 @@ class EditPage {
# the revisions exist and they were not deleted.
# Otherwise, $content will be left as-is.
if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
- $undorev->getPage() == $oldrev->getPage() &&
- $undorev->getPage() == $this->mTitle->getArticleID() &&
!$undorev->isDeleted( Revision::DELETED_TEXT ) &&
!$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
@@ -941,34 +1028,45 @@ class EditPage {
# Warn the user that something went wrong
$undoMsg = 'failure';
} else {
- # Inform the user of our success and set an automatic edit summary
- $undoMsg = 'success';
-
- # If we just undid one rev, use an autosummary
- $firstrev = $oldrev->getNext();
- if ( $firstrev && $firstrev->getId() == $undo ) {
- $userText = $undorev->getUserText();
- if ( $userText === '' ) {
- $undoSummary = wfMessage(
- 'undo-summary-username-hidden',
- $undo
- )->inContentLanguage()->text();
- } else {
- $undoSummary = wfMessage(
- 'undo-summary',
- $undo,
- $userText
- )->inContentLanguage()->text();
- }
- if ( $this->summary === '' ) {
- $this->summary = $undoSummary;
- } else {
- $this->summary = $undoSummary . wfMessage( 'colon-separator' )
- ->inContentLanguage()->text() . $this->summary;
+ $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW );
+ $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
+ $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
+
+ if ( $newContent->equals( $oldContent ) ) {
+ # Tell the user that the undo results in no change,
+ # i.e. the revisions were already undone.
+ $undoMsg = 'nochange';
+ $content = false;
+ } else {
+ # Inform the user of our success and set an automatic edit summary
+ $undoMsg = 'success';
+
+ # If we just undid one rev, use an autosummary
+ $firstrev = $oldrev->getNext();
+ if ( $firstrev && $firstrev->getId() == $undo ) {
+ $userText = $undorev->getUserText();
+ if ( $userText === '' ) {
+ $undoSummary = wfMessage(
+ 'undo-summary-username-hidden',
+ $undo
+ )->inContentLanguage()->text();
+ } else {
+ $undoSummary = wfMessage(
+ 'undo-summary',
+ $undo,
+ $userText
+ )->inContentLanguage()->text();
+ }
+ if ( $this->summary === '' ) {
+ $this->summary = $undoSummary;
+ } else {
+ $this->summary = $undoSummary . wfMessage( 'colon-separator' )
+ ->inContentLanguage()->text() . $this->summary;
+ }
+ $this->undidRev = $undo;
}
- $this->undidRev = $undo;
+ $this->formtype = 'diff';
}
- $this->formtype = 'diff';
}
} else {
// Failed basic sanity checks.
@@ -977,14 +1075,14 @@ class EditPage {
$undoMsg = 'norev';
}
- // Messages: undo-success, undo-failure, undo-norev
+ // Messages: undo-success, undo-failure, undo-norev, undo-nochange
$class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
$this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
}
if ( $content === false ) {
- $content = $this->getOriginalContent();
+ $content = $this->getOriginalContent( $wgUser );
}
}
}
@@ -1005,9 +1103,10 @@ class EditPage {
* 'missing-revision' message.
*
* @since 1.19
+ * @param User $user The user to get the revision for
* @return Content|null
*/
- private function getOriginalContent() {
+ private function getOriginalContent( User $user ) {
if ( $this->section == 'new' ) {
return $this->getCurrentContent();
}
@@ -1020,7 +1119,7 @@ class EditPage {
return $handler->makeEmptyContent();
}
- $content = $revision->getContent();
+ $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
return $content;
}
@@ -1053,23 +1152,9 @@ class EditPage {
}
/**
- * Use this method before edit() to preload some text into the edit box
- *
- * @param $text string
- * @deprecated since 1.21, use setPreloadedContent() instead.
- */
- public function setPreloadedText( $text ) {
- ContentHandler::deprecated( __METHOD__, "1.21" );
-
- $content = $this->toEditContent( $text );
-
- $this->setPreloadedContent( $content );
- }
-
- /**
* Use this method before edit() to preload some content into the edit box
*
- * @param $content Content
+ * @param Content $content
*
* @since 1.21
*/
@@ -1081,32 +1166,14 @@ class EditPage {
* Get the contents to be preloaded into the box, either set by
* an earlier setPreloadText() or by loading the given page.
*
- * @param string $preload representing the title to preload from.
- *
- * @return String
- *
- * @deprecated since 1.21, use getPreloadedContent() instead
- */
- protected function getPreloadedText( $preload ) {
- ContentHandler::deprecated( __METHOD__, "1.21" );
-
- $content = $this->getPreloadedContent( $preload );
- $text = $this->toEditText( $content );
-
- return $text;
- }
-
- /**
- * Get the contents to be preloaded into the box, either set by
- * an earlier setPreloadText() or by loading the given page.
- *
- * @param string $preload representing the title to preload from.
+ * @param string $preload Representing the title to preload from.
+ * @param array $params Parameters to use (interface-message style) in the preloaded text
*
* @return Content
*
* @since 1.21
*/
- protected function getPreloadedContent( $preload ) {
+ protected function getPreloadedContent( $preload, $params = array() ) {
global $wgUser;
if ( !empty( $this->mPreloadContent ) ) {
@@ -1160,13 +1227,13 @@ class EditPage {
$content = $converted;
}
- return $content->preloadTransform( $title, $parserOptions );
+ return $content->preloadTransform( $title, $parserOptions, $params );
}
/**
* Make sure the form isn't faking a user's credentials.
*
- * @param $request WebRequest
+ * @param WebRequest $request
* @return bool
* @private
*/
@@ -1189,18 +1256,24 @@ class EditPage {
* marked HttpOnly. The JavaScript code converts the cookie to a wgPostEdit config
* variable.
*
- * We use a path of '/' since wgCookiePath is not exposed to JS
- *
* If the variable were set on the server, it would be cached, which is unwanted
* since the post-edit state should only apply to the load right after the save.
+ *
+ * @param int $statusValue The status value (to check for new article status)
*/
- protected function setPostEditCookie() {
+ protected function setPostEditCookie( $statusValue ) {
$revisionId = $this->mArticle->getLatest();
$postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
+ $val = 'saved';
+ if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
+ $val = 'created';
+ } elseif ( $this->oldid ) {
+ $val = 'restored';
+ }
+
$response = RequestContext::getMain()->getRequest()->response();
- $response->setcookie( $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, array(
- 'path' => '/',
+ $response->setcookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, array(
'httpOnly' => false,
) );
}
@@ -1208,20 +1281,41 @@ class EditPage {
/**
* Attempt submission
* @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError
- * @return bool false if output is done, true if the rest of the form should be displayed
+ * @return bool False if output is done, true if the rest of the form should be displayed
*/
- function attemptSave() {
- global $wgUser, $wgOut;
+ public function attemptSave() {
+ global $wgUser;
$resultDetails = false;
# Allow bots to exempt some edits from bot flagging
$bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
$status = $this->internalAttemptSave( $resultDetails, $bot );
- // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
- if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
+
+ return $this->handleStatus( $status, $resultDetails );
+ }
+
+ /**
+ * Handle status, such as after attempt save
+ *
+ * @param Status $status
+ * @param array|bool $resultDetails
+ *
+ * @throws ErrorPageError
+ * @return bool False, if output is done, true if rest of the form should be displayed
+ */
+ private function handleStatus( Status $status, $resultDetails ) {
+ global $wgUser, $wgOut;
+
+ /**
+ * @todo FIXME: once the interface for internalAttemptSave() is made
+ * nicer, this should use the message in $status
+ */
+ if ( $status->value == self::AS_SUCCESS_UPDATE
+ || $status->value == self::AS_SUCCESS_NEW_ARTICLE
+ ) {
$this->didSave = true;
if ( !$resultDetails['nullEdit'] ) {
- $this->setPostEditCookie();
+ $this->setPostEditCookie( $status->value );
}
}
@@ -1234,6 +1328,7 @@ class EditPage {
case self::AS_TEXTBOX_EMPTY:
case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
case self::AS_END:
+ case self::AS_BLANK_ARTICLE:
return true;
case self::AS_HOOK_ERROR:
@@ -1254,7 +1349,10 @@ class EditPage {
$sectionanchor = $resultDetails['sectionanchor'];
// Give extensions a chance to modify URL query on update
- wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
+ wfRunHooks(
+ 'ArticleUpdateBeforeRedirect',
+ array( $this->mArticle, &$sectionanchor, &$extraQuery )
+ );
if ( $resultDetails['redirect'] ) {
if ( $extraQuery == '' ) {
@@ -1266,10 +1364,6 @@ class EditPage {
$wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
return false;
- case self::AS_BLANK_ARTICLE:
- $wgOut->redirect( $this->getContextTitle()->getFullURL() );
- return false;
-
case self::AS_SPAM_ERROR:
$this->spamPageWithContent( $resultDetails['spam'] );
return false;
@@ -1312,9 +1406,9 @@ class EditPage {
/**
* Run hooks that can filter edits just before they get saved.
*
- * @param Content $content the Content to filter.
- * @param Status $status for reporting the outcome to the caller
- * @param User $user the user performing the edit
+ * @param Content $content The Content to filter.
+ * @param Status $status For reporting the outcome to the caller
+ * @param User $user The user performing the edit
*
* @return bool
*/
@@ -1358,20 +1452,58 @@ class EditPage {
}
/**
+ * Return the summary to be used for a new section.
+ *
+ * @param string $sectionanchor Set to the section anchor text
+ * @return string
+ */
+ private function newSectionSummary( &$sectionanchor = null ) {
+ global $wgParser;
+
+ if ( $this->sectiontitle !== '' ) {
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+ // If no edit summary was specified, create one automatically from the section
+ // title and have it link to the new section. Otherwise, respect the summary as
+ // passed.
+ if ( $this->summary === '' ) {
+ $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+ return wfMessage( 'newsectionsummary' )
+ ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
+ }
+ } elseif ( $this->summary !== '' ) {
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+ # This is a new section, so create a link to the new section
+ # in the revision summary.
+ $cleanSummary = $wgParser->stripSectionName( $this->summary );
+ return wfMessage( 'newsectionsummary' )
+ ->rawParams( $cleanSummary )->inContentLanguage()->text();
+ }
+ return $this->summary;
+ }
+
+ /**
* Attempt submission (no UI)
*
- * @param array $result array to add statuses to, currently with the possible keys:
- * spam - string - Spam string from content if any spam is detected by matchSpamRegex
- * sectionanchor - string - Section anchor for a section save
- * nullEdit - boolean - Set if doEditContent is OK. True if null edit, false otherwise.
- * redirect - boolean - Set if doEditContent is OK. True if resulting revision is a redirect
+ * @param array $result Array to add statuses to, currently with the
+ * possible keys:
+ * - spam (string): Spam string from content if any spam is detected by
+ * matchSpamRegex.
+ * - sectionanchor (string): Section anchor for a section save.
+ * - nullEdit (boolean): Set if doEditContent is OK. True if null edit,
+ * false otherwise.
+ * - redirect (bool): Set if doEditContent is OK. True if resulting
+ * revision is a redirect.
* @param bool $bot True if edit is being made under the bot right.
*
- * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value,
+ * @return Status Status object, possibly with a message, but always with
+ * one of the AS_* constants in $status->value,
*
- * FIXME: This interface is TERRIBLE, but hard to get rid of due to various error display idiosyncrasies. There are
- * also lots of cases where error metadata is set in the object and retrieved later instead of being returned, e.g.
- * AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time.
+ * @todo FIXME: This interface is TERRIBLE, but hard to get rid of due to
+ * various error display idiosyncrasies. There are also lots of cases
+ * where error metadata is set in the object and retrieved later instead
+ * of being returned, e.g. AS_CONTENT_TOO_BIG and
+ * AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some
+ * time.
*/
function internalAttemptSave( &$result, $bot = false ) {
global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
@@ -1412,7 +1544,12 @@ class EditPage {
# Construct Content object
$textbox_content = $this->toEditContent( $this->textbox1 );
} catch ( MWContentSerializationException $ex ) {
- $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $status->fatal(
+ 'content-failed-to-parse',
+ $this->contentModel,
+ $this->contentFormat,
+ $ex->getMessage()
+ );
$status->value = self::AS_PARSE_ERROR;
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
@@ -1422,7 +1559,8 @@ class EditPage {
# Check image redirect
if ( $this->mTitle->getNamespace() == NS_FILE &&
$textbox_content->isRedirect() &&
- !$wgUser->isAllowed( 'upload' ) ) {
+ !$wgUser->isAllowed( 'upload' )
+ ) {
$code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
$status->setResult( false, $code );
@@ -1461,7 +1599,10 @@ class EditPage {
wfProfileOut( __METHOD__ );
return $status;
}
- if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
+ if ( !wfRunHooks(
+ 'EditFilter',
+ array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) )
+ ) {
# Error messages etc. could be handled within the hook...
$status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR;
@@ -1572,7 +1713,9 @@ class EditPage {
$defaultText = '';
}
- if ( $this->textbox1 === $defaultText ) {
+ if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
+ $this->blankArticle = true;
+ $status->fatal( 'blankarticle' );
$status->setResult( false, self::AS_BLANK_ARTICLE );
wfProfileOut( __METHOD__ );
return $status;
@@ -1590,30 +1733,11 @@ class EditPage {
if ( $this->sectiontitle !== '' ) {
// Insert the section title above the content.
$content = $content->addSectionHeader( $this->sectiontitle );
-
- // Jump to the new section
- $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
-
- // If no edit summary was specified, create one automatically from the section
- // title and have it link to the new section. Otherwise, respect the summary as
- // passed.
- if ( $this->summary === '' ) {
- $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
- }
} elseif ( $this->summary !== '' ) {
// Insert the section title above the content.
$content = $content->addSectionHeader( $this->summary );
-
- // Jump to the new section
- $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
-
- // Create a link to the new section from the edit summary.
- $cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSummary )->inContentLanguage()->text();
}
+ $this->summary = $this->newSectionSummary( $result['sectionanchor'] );
}
$status->value = self::AS_SUCCESS_NEW_ARTICLE;
@@ -1631,18 +1755,24 @@ class EditPage {
$this->isConflict = true;
if ( $this->section == 'new' ) {
if ( $this->mArticle->getUserText() == $wgUser->getName() &&
- $this->mArticle->getComment() == $this->summary ) {
+ $this->mArticle->getComment() == $this->newSectionSummary()
+ ) {
// Probably a duplicate submission of a new comment.
// This can happen when squid resends a request after
// a timeout but the first one actually went through.
- wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
+ wfDebug( __METHOD__
+ . ": duplicate new section submission; trigger edit conflict!\n" );
} else {
// New comment; suppress conflict.
$this->isConflict = false;
wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
}
- } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(),
- $wgUser->getId(), $this->edittime ) ) {
+ } elseif ( $this->section == ''
+ && Revision::userWasLastToEdit(
+ DB_MASTER, $this->mTitle->getArticleID(),
+ $wgUser->getId(), $this->edittime
+ )
+ ) {
# Suppress edit conflict with self, except for section edits where merging is required.
wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
$this->isConflict = false;
@@ -1659,13 +1789,23 @@ class EditPage {
$content = null;
if ( $this->isConflict ) {
- wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
- . " (article time '{$timestamp}')\n" );
-
- $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
+ wfDebug( __METHOD__
+ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
+ . " (article time '{$timestamp}')\n" );
+
+ $content = $this->mArticle->replaceSectionContent(
+ $this->section,
+ $textbox_content,
+ $sectionTitle,
+ $this->edittime
+ );
} else {
wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
- $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
+ $content = $this->mArticle->replaceSectionContent(
+ $this->section,
+ $textbox_content,
+ $sectionTitle
+ );
}
if ( is_null( $content ) ) {
@@ -1715,7 +1855,7 @@ class EditPage {
return $status;
}
} elseif ( !$this->allowBlankSummary
- && !$content->equals( $this->getOriginalContent() )
+ && !$content->equals( $this->getOriginalContent( $wgUser ) )
&& !$content->isRedirect()
&& md5( $this->summary ) == $this->autoSumm
) {
@@ -1730,31 +1870,15 @@ class EditPage {
wfProfileIn( __METHOD__ . '-sectionanchor' );
$sectionanchor = '';
if ( $this->section == 'new' ) {
- if ( $this->sectiontitle !== '' ) {
- $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
- // If no edit summary was specified, create one automatically from the section
- // title and have it link to the new section. Otherwise, respect the summary as
- // passed.
- if ( $this->summary === '' ) {
- $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
- }
- } elseif ( $this->summary !== '' ) {
- $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
- # This is a new section, so create a link to the new section
- # in the revision summary.
- $cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSummary )->inContentLanguage()->text();
- }
+ $this->summary = $this->newSectionSummary( $sectionanchor );
} elseif ( $this->section != '' ) {
- # Try to get a section anchor from the section source, redirect to edited section if header found
- # XXX: might be better to integrate this into Article::replaceSection
- # for duplicate heading checking and maybe parsing
+ # Try to get a section anchor from the section source, redirect
+ # to edited section if header found.
+ # XXX: Might be better to integrate this into Article::replaceSection
+ # for duplicate heading checking and maybe parsing.
$hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
- # we can't deal with anchors, includes, html etc in the header for now,
- # headline would need to be parsed to improve this
+ # We can't deal with anchors, includes, html etc in the header for now,
+ # headline would need to be parsed to improve this.
if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
$sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
}
@@ -1773,7 +1897,7 @@ class EditPage {
}
// Check for length errors again now that the section is merged in
- $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
+ $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
if ( $this->kblength > $wgMaxArticleSize ) {
$this->tooBig = true;
$status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
@@ -1786,17 +1910,23 @@ class EditPage {
( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
( $bot ? EDIT_FORCE_BOT : 0 );
- $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
- false, null, $this->contentFormat );
+ $doEditStatus = $this->mArticle->doEditContent(
+ $content,
+ $this->summary,
+ $flags,
+ false,
+ null,
+ $content->getDefaultFormat()
+ );
if ( !$doEditStatus->isOK() ) {
// Failure from doEdit()
// Show the edit conflict page for certain recognized errors from doEdit(),
// but don't show it for errors from extension hooks
$errors = $doEditStatus->getErrorsArray();
- if ( in_array( $errors[0][0], array( 'edit-gone-missing', 'edit-conflict',
- 'edit-already-exists' ) ) )
- {
+ if ( in_array( $errors[0][0],
+ array( 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ) )
+ ) {
$this->isConflict = true;
// Destroys data doEdit() put in $status->value but who cares
$doEditStatus->value = self::AS_END;
@@ -1831,44 +1961,20 @@ class EditPage {
// Do this in its own transaction to reduce contention...
$dbw = wfGetDB( DB_MASTER );
- $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
- $dbw->begin( $fname );
+ $dbw->onTransactionIdle( function () use ( $dbw, $title, $watch, $wgUser, $fname ) {
WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser );
- $dbw->commit( $fname );
} );
}
}
/**
- * Attempts to merge text content with base and current revisions
- *
- * @param $editText string
- *
- * @return bool
- * @deprecated since 1.21, use mergeChangesIntoContent() instead
- */
- function mergeChangesInto( &$editText ) {
- ContentHandler::deprecated( __METHOD__, "1.21" );
-
- $editContent = $this->toEditContent( $editText );
-
- $ok = $this->mergeChangesIntoContent( $editContent );
-
- if ( $ok ) {
- $editText = $this->toEditText( $editContent );
- return true;
- }
- return false;
- }
-
- /**
* Attempts to do 3-way merge of edit content with a base revision
* and current content, in case of edit conflict, in whichever way appropriate
* for the content type.
*
* @since 1.21
*
- * @param $editContent
+ * @param Content $editContent
*
* @return bool
*/
@@ -1915,20 +2021,18 @@ class EditPage {
function getBaseRevision() {
if ( !$this->mBaseRevision ) {
$db = wfGetDB( DB_MASTER );
- $baseRevision = Revision::loadFromTimestamp(
+ $this->mBaseRevision = Revision::loadFromTimestamp(
$db, $this->mTitle, $this->edittime );
- return $this->mBaseRevision = $baseRevision;
- } else {
- return $this->mBaseRevision;
}
+ return $this->mBaseRevision;
}
/**
* Check given input text against $wgSpamRegex, and return the text of the first match.
*
- * @param $text string
+ * @param string $text
*
- * @return string|bool matching string or false
+ * @return string|bool Matching string or false
*/
public static function matchSpamRegex( $text ) {
global $wgSpamRegex;
@@ -1940,9 +2044,9 @@ class EditPage {
/**
* Check given input text against $wgSummarySpamRegex, and return the text of the first match.
*
- * @param $text string
+ * @param string $text
*
- * @return string|bool matching string or false
+ * @return string|bool Matching string or false
*/
public static function matchSummarySpamRegex( $text ) {
global $wgSummarySpamRegex;
@@ -1951,8 +2055,8 @@ class EditPage {
}
/**
- * @param $text string
- * @param $regexes array
+ * @param string $text
+ * @param array $regexes
* @return bool|string
*/
protected static function matchSpamRegexInternal( $text, $regexes ) {
@@ -1979,9 +2083,6 @@ class EditPage {
$wgOut->addModules( 'mediawiki.action.edit.editWarning' );
}
- // Bug #19334: textarea jumps when editing articles in IE8
- $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
-
$wgOut->setRobotPolicy( 'noindex,nofollow' );
# Enabled article-related sidebar, toplinks, etc.
@@ -1993,9 +2094,14 @@ class EditPage {
} elseif ( $contextTitle->exists() && $this->section != '' ) {
$msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
} else {
- $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?
- 'editing' : 'creating';
+ $msg = $contextTitle->exists()
+ || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
+ && $contextTitle->getDefaultMessageText() !== false
+ )
+ ? 'editing'
+ : 'creating';
}
+
# Use the title defined by DISPLAYTITLE magic word when present
$displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
if ( $displayTitle === false ) {
@@ -2045,14 +2151,15 @@ class EditPage {
$username = $parts[0];
$user = User::newFromName( $username, false /* allow IP users*/ );
$ip = User::isIP( $username );
+ $block = Block::newFromTarget( $user, $user );
if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
$wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
- } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
+ } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked
LogEventsList::showLogExtract(
$wgOut,
'block',
- $user->getUserPage(),
+ MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
'',
array(
'lim' => 1,
@@ -2115,7 +2222,7 @@ class EditPage {
if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
global $wgOut;
// Added using template syntax, to take <noinclude>'s into account.
- $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
+ $wgOut->addWikiTextTitleTidy( '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>', $this->mTitle );
return true;
}
}
@@ -2129,14 +2236,16 @@ class EditPage {
*
* If $content is null or false or a string, $content is returned unchanged.
*
- * If the given Content object is not of a type that can be edited using the text base EditPage,
- * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+ * If the given Content object is not of a type that can be edited using
+ * the text base EditPage, an exception will be raised. Set
+ * $this->allowNonTextContent to true to allow editing of non-textual
* content.
*
* @param Content|null|bool|string $content
- * @return String the editable text form of the content.
+ * @return string The editable text form of the content.
*
- * @throws MWException if $content is not an instance of TextContent and $this->allowNonTextContent is not true.
+ * @throws MWException If $content is not an instance of TextContent and
+ * $this->allowNonTextContent is not true.
*/
protected function toEditText( $content ) {
if ( $content === null || $content === false ) {
@@ -2147,9 +2256,9 @@ class EditPage {
return $content;
}
- if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
- throw new MWException( "This content model can not be edited as text: "
- . ContentHandler::getLocalizedName( $content->getModel() ) );
+ if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
+ throw new MWException( 'This content model is not supported: '
+ . ContentHandler::getLocalizedName( $content->getModel() ) );
}
return $content->serialize( $this->contentFormat );
@@ -2158,16 +2267,18 @@ class EditPage {
/**
* Turns the given text into a Content object by unserializing it.
*
- * If the resulting Content object is not of a type that can be edited using the text base EditPage,
- * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+ * If the resulting Content object is not of a type that can be edited using
+ * the text base EditPage, an exception will be raised. Set
+ * $this->allowNonTextContent to true to allow editing of non-textual
* content.
*
* @param string|null|bool $text Text to unserialize
- * @return Content The content object created from $text. If $text was false or null, false resp. null will be
- * returned instead.
+ * @return Content The content object created from $text. If $text was false
+ * or null, false resp. null will be returned instead.
*
- * @throws MWException if unserializing the text results in a Content object that is not an instance of TextContent
- * and $this->allowNonTextContent is not true.
+ * @throws MWException If unserializing the text results in a Content
+ * object that is not an instance of TextContent and
+ * $this->allowNonTextContent is not true.
*/
protected function toEditContent( $text ) {
if ( $text === false || $text === null ) {
@@ -2177,8 +2288,8 @@ class EditPage {
$content = ContentHandler::makeContent( $text, $this->getTitle(),
$this->contentModel, $this->contentFormat );
- if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
- throw new MWException( "This content model can not be edited as text: "
+ if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
+ throw new MWException( 'This content model is not supported: '
. ContentHandler::getLocalizedName( $content->getModel() ) );
}
@@ -2187,7 +2298,7 @@ class EditPage {
/**
* Send the edit form and related headers to $wgOut
- * @param $formCallback Callback|null that takes an OutputPage parameter; will be called
+ * @param callable|null $formCallback That takes an OutputPage parameter; will be called
* during form output near the top, for captchas and the like.
*/
function showEditForm( $formCallback = null ) {
@@ -2235,9 +2346,16 @@ class EditPage {
// @todo add EditForm plugin interface and use it here!
// search for textarea1 and textares2, and allow EditForm to override all uses.
- $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
- 'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
- 'enctype' => 'multipart/form-data' ) ) );
+ $wgOut->addHTML( Html::openElement(
+ 'form',
+ array(
+ 'id' => self::EDITFORM_ID,
+ 'name' => self::EDITFORM_ID,
+ 'method' => 'post',
+ 'action' => $this->getActionURL( $this->getContextTitle() ),
+ 'enctype' => 'multipart/form-data'
+ )
+ ) );
if ( is_callable( $formCallback ) ) {
call_user_func_array( $formCallback, array( &$wgOut ) );
@@ -2246,8 +2364,20 @@ class EditPage {
// Add an empty field to trip up spambots
$wgOut->addHTML(
Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) )
- . Html::rawElement( 'label', array( 'for' => 'wpAntiSpam' ), wfMessage( 'simpleantispam-label' )->parse() )
- . Xml::element( 'input', array( 'type' => 'text', 'name' => 'wpAntispam', 'id' => 'wpAntispam', 'value' => '' ) )
+ . Html::rawElement(
+ 'label',
+ array( 'for' => 'wpAntiSpam' ),
+ wfMessage( 'simpleantispam-label' )->parse()
+ )
+ . Xml::element(
+ 'input',
+ array(
+ 'type' => 'text',
+ 'name' => 'wpAntispam',
+ 'id' => 'wpAntispam',
+ 'value' => ''
+ )
+ )
. Xml::closeElement( 'div' )
);
@@ -2321,6 +2451,10 @@ class EditPage {
$wgOut->addHTML( EditPage::getEditToolbar() );
}
+ if ( $this->blankArticle ) {
+ $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
+ }
+
if ( $this->isConflict ) {
// In an edit conflict bypass the overridable content form method
// and fallback to the raw wpTextbox1 since editconflicts can't be
@@ -2364,11 +2498,19 @@ class EditPage {
$this->showConflict();
} catch ( MWContentSerializationException $ex ) {
// this can't really happen, but be nice if it does.
- $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $msg = wfMessage(
+ 'content-failed-to-parse',
+ $this->contentModel,
+ $this->contentFormat,
+ $ex->getMessage()
+ );
$wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
}
}
+ // Marker for detecting truncated form data. This must be the last
+ // parameter sent in order to be of use, so do not move me.
+ $wgOut->addHTML( Html::hidden( 'wpUltimateParam', true ) );
$wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
if ( !$wgUser->getOption( 'previewontop' ) ) {
@@ -2382,7 +2524,7 @@ class EditPage {
* Extract the section title from current section text, if any.
*
* @param string $text
- * @return Mixed|string or false
+ * @return string|bool String or false
*/
public static function extractSectionTitle( $text ) {
preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
@@ -2394,8 +2536,12 @@ class EditPage {
}
}
+ /**
+ * @return bool
+ */
protected function showHeader() {
global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
+ global $wgAllowUserCss, $wgAllowUserJs;
if ( $this->mTitle->isTalkPage() ) {
$wgOut->addWikiMsg( 'talkpagetext' );
@@ -2437,6 +2583,10 @@ class EditPage {
$wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
}
+ if ( $this->blankArticle ) {
+ $wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' );
+ }
+
if ( $this->hookError !== '' ) {
$wgOut->addWikiText( $this->hookError );
}
@@ -2451,9 +2601,15 @@ class EditPage {
// Let sysop know that this will make private content public if saved
if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
+ $wgOut->wrapWikiMsg(
+ "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+ 'rev-deleted-text-permission'
+ );
} elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
+ $wgOut->wrapWikiMsg(
+ "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+ 'rev-deleted-text-view'
+ );
}
if ( !$revision->isCurrent() ) {
@@ -2470,32 +2626,55 @@ class EditPage {
}
if ( wfReadOnly() ) {
- $wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) );
+ $wgOut->wrapWikiMsg(
+ "<div id=\"mw-read-only-warning\">\n$1\n</div>",
+ array( 'readonlywarning', wfReadOnlyReason() )
+ );
} elseif ( $wgUser->isAnon() ) {
if ( $this->formtype != 'preview' ) {
- $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
+ $wgOut->wrapWikiMsg(
+ "<div id='mw-anon-edit-warning'>\n$1\n</div>",
+ array( 'anoneditwarning',
+ // Log-in link
+ '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}',
+ // Sign-up link
+ '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}' )
+ );
} else {
- $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' );
+ $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>",
+ 'anonpreviewwarning'
+ );
}
} else {
if ( $this->isCssJsSubpage ) {
# Check the skin exists
if ( $this->isWrongCaseCssJsPage ) {
- $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
+ $wgOut->wrapWikiMsg(
+ "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
+ array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() )
+ );
}
if ( $this->formtype !== 'preview' ) {
- if ( $this->isCssSubpage ) {
- $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
+ if ( $this->isCssSubpage && $wgAllowUserCss ) {
+ $wgOut->wrapWikiMsg(
+ "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
+ array( 'usercssyoucanpreview' )
+ );
}
- if ( $this->isJsSubpage ) {
- $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
+ if ( $this->isJsSubpage && $wgAllowUserJs ) {
+ $wgOut->wrapWikiMsg(
+ "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
+ array( 'userjsyoucanpreview' )
+ );
}
}
}
}
- if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+ if ( $this->mTitle->isProtected( 'edit' ) &&
+ MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
+ ) {
# Is the title semi-protected?
if ( $this->mTitle->isSemiProtected() ) {
$noticeMsg = 'semiprotectedpagewarning';
@@ -2534,16 +2713,27 @@ class EditPage {
if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
$wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
- array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
+ array(
+ 'longpageerror',
+ $wgLang->formatNum( $this->kblength ),
+ $wgLang->formatNum( $wgMaxArticleSize )
+ )
+ );
} else {
if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
$wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
- array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
+ array(
+ 'longpage-hint',
+ $wgLang->formatSize( strlen( $this->textbox1 ) ),
+ strlen( $this->textbox1 )
+ )
);
}
}
# Add header copyright warning
$this->showHeaderCopyrightWarning();
+
+ return true;
}
/**
@@ -2555,12 +2745,14 @@ class EditPage {
*
* @param string $summary The value of the summary input
* @param string $labelText The html to place inside the label
- * @param array $inputAttrs of attrs to use on the input
- * @param array $spanLabelAttrs of attrs to use on the span inside the label
+ * @param array $inputAttrs Array of attrs to use on the input
+ * @param array $spanLabelAttrs Array of attrs to use on the span inside the label
*
* @return array An array in the format array( $label, $input )
*/
- function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
+ function getSummaryInput( $summary = "", $labelText = null,
+ $inputAttrs = null, $spanLabelAttrs = null
+ ) {
// Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
$inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
'id' => 'wpSummary',
@@ -2577,7 +2769,11 @@ class EditPage {
$label = null;
if ( $labelText ) {
- $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
+ $label = Xml::tags(
+ 'label',
+ $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null,
+ $labelText
+ );
$label = Xml::tags( 'span', $spanLabelAttrs, $label );
}
@@ -2587,11 +2783,10 @@ class EditPage {
}
/**
- * @param $isSubjectPreview Boolean: true if this is the section subject/title
- * up top, or false if this is the comment summary
- * down below the textarea
+ * @param bool $isSubjectPreview True if this is the section subject/title
+ * up top, or false if this is the comment summary
+ * down below the textarea
* @param string $summary The text of the summary to display
- * @return String
*/
protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
global $wgOut, $wgContLang;
@@ -2608,16 +2803,21 @@ class EditPage {
}
$summary = $wgContLang->recodeForEdit( $summary );
$labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
- list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
+ list( $label, $input ) = $this->getSummaryInput(
+ $summary,
+ $labelText,
+ array( 'class' => $summaryClass ),
+ array()
+ );
$wgOut->addHTML( "{$label} {$input}" );
}
/**
- * @param $isSubjectPreview Boolean: true if this is the section subject/title
- * up top, or false if this is the comment summary
- * down below the textarea
- * @param string $summary the text of the summary to display
- * @return String
+ * @param bool $isSubjectPreview True if this is the section subject/title
+ * up top, or false if this is the comment summary
+ * down below the textarea
+ * @param string $summary The text of the summary to display
+ * @return string
*/
protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
// avoid spaces in preview, gets always trimmed on save
@@ -2635,7 +2835,8 @@ class EditPage {
$message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
- $summary = wfMessage( $message )->parse() . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
+ $summary = wfMessage( $message )->parse()
+ . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
}
@@ -2689,15 +2890,17 @@ HTML
* The $textoverride method can be used by subclasses overriding showContentForm
* to pass back to this method.
*
- * @param array $customAttribs of html attributes to use in the textarea
- * @param string $textoverride optional text to override $this->textarea1 with
+ * @param array $customAttribs Array of html attributes to use in the textarea
+ * @param string $textoverride Optional text to override $this->textarea1 with
*/
protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
$attribs = array( 'style' => 'display:none;' );
} else {
$classes = array(); // Textarea CSS
- if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+ if ( $this->mTitle->isProtected( 'edit' ) &&
+ MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
+ ) {
# Is the title semi-protected?
if ( $this->mTitle->isSemiProtected() ) {
$classes[] = 'mw-textarea-sprotected';
@@ -2725,7 +2928,11 @@ HTML
}
}
- $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
+ $this->showTextbox(
+ $textoverride !== null ? $textoverride : $this->textbox1,
+ 'wpTextbox1',
+ $attribs
+ );
}
protected function showTextbox2() {
@@ -2749,7 +2956,9 @@ HTML
'id' => $name,
'cols' => $wgUser->getIntOption( 'cols' ),
'rows' => $wgUser->getIntOption( 'rows' ),
- 'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
+ // Avoid PHP notices when appending preferences
+ // (appending allows customAttribs['style'] to still work).
+ 'style' => ''
);
$pageLang = $this->mTitle->getPageLanguage();
@@ -2784,7 +2993,12 @@ HTML
try {
$this->showDiff();
} catch ( MWContentSerializationException $ex ) {
- $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $msg = wfMessage(
+ 'content-failed-to-parse',
+ $this->contentModel,
+ $this->contentFormat,
+ $ex->getMessage()
+ );
$wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
}
}
@@ -2794,7 +3008,7 @@ HTML
* Append preview output to $wgOut.
* Includes category rendering if this is a category page.
*
- * @param string $text the HTML to be output for the preview.
+ * @param string $text The HTML to be output for the preview.
*/
protected function showPreview( $text ) {
global $wgOut;
@@ -2914,6 +3128,7 @@ HTML
* Get the copyright warning
*
* Renamed to getCopyrightWarning(), old name kept around for backwards compatibility
+ * @return string
*/
protected function getCopywarn() {
return self::getCopyrightWarning( $this->mTitle );
@@ -2923,8 +3138,7 @@ HTML
* Get the copyright warning, by default returns wikitext
*
* @param Title $title
- * @param string $format output format, valid values are any function of
- * a Message object
+ * @param string $format Output format, valid values are any function of a Message object
* @return string
*/
public static function getCopyrightWarning( $title, $format = 'plain' ) {
@@ -2972,7 +3186,7 @@ HTML
foreach ( $output->getLimitReportData() as $key => $value ) {
if ( wfRunHooks( 'ParserLimitReportFormat',
- array( $key, $value, &$limitReport, true, true )
+ array( $key, &$value, &$limitReport, true, true )
) ) {
$keyMsg = wfMessage( $key );
$valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
@@ -2998,7 +3212,7 @@ HTML
}
protected function showStandardInputs( &$tabindex = 2 ) {
- global $wgOut;
+ global $wgOut, $wgUseMediaWikiUIEverywhere;
$wgOut->addHTML( "<div class='editOptions'>\n" );
if ( $this->section != 'new' ) {
@@ -3023,14 +3237,26 @@ HTML
array( 'class' => 'mw-editButtons-pipe-separator' ),
wfMessage( 'pipe-separator' )->text() );
}
- $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() );
- $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
- wfMessage( 'edithelp' )->escaped() . '</a> ' .
+
+ $message = wfMessage( 'edithelppage' )->inContentLanguage()->text();
+ $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
+ $attrs = array(
+ 'target' => 'helpwindow',
+ 'href' => $edithelpurl,
+ );
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $attrs['class'] = 'mw-ui-button mw-ui-quiet';
+ }
+ $edithelp = Html::element( 'a', $attrs, wfMessage( 'edithelp' )->text() ) .
+ wfMessage( 'word-separator' )->escaped() .
wfMessage( 'newwindow' )->parse();
+
$wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
$wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
$wgOut->addHTML( "</div><!-- editButtons -->\n" );
+
wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
+
$wgOut->addHTML( "</div><!-- editOptions -->\n" );
}
@@ -3064,15 +3290,20 @@ HTML
* @return string
*/
public function getCancelLink() {
+ global $wgUseMediaWikiUIEverywhere;
$cancelParams = array();
if ( !$this->isConflict && $this->oldid > 0 ) {
$cancelParams['oldid'] = $this->oldid;
}
+ $attrs = array( 'id' => 'mw-editform-cancel' );
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $attrs['class'] = 'mw-ui-button mw-ui-quiet';
+ }
return Linker::linkKnown(
$this->getContextTitle(),
wfMessage( 'cancel' )->parse(),
- array( 'id' => 'mw-editform-cancel' ),
+ $attrs,
$cancelParams
);
}
@@ -3083,7 +3314,7 @@ HTML
* variable in the constructor is not enough. This can be used when the
* EditPage lives inside of a Special page rather than a custom page action.
*
- * @param $title Title object for which is being edited (where we go to for &action= links)
+ * @param Title $title Title object for which is being edited (where we go to for &action= links)
* @return string
*/
protected function getActionURL( Title $title ) {
@@ -3095,6 +3326,7 @@ HTML
* Note that we rely on the logging table, which hasn't been always there,
* but that doesn't matter, because this only applies to brand new
* deletes.
+ * @return bool
*/
protected function wasDeletedSinceLastEdit() {
if ( $this->deletedSinceEdit !== null ) {
@@ -3116,6 +3348,9 @@ HTML
return $this->deletedSinceEdit;
}
+ /**
+ * @return bool|stdClass
+ */
protected function getLastDelete() {
$dbr = wfGetDB( DB_SLAVE );
$data = $dbr->selectRow(
@@ -3151,6 +3386,7 @@ HTML
$data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
}
}
+
return $data;
}
@@ -3161,6 +3397,7 @@ HTML
*/
function getPreviewText() {
global $wgOut, $wgUser, $wgRawHtml, $wgLang;
+ global $wgAllowUserCss, $wgAllowUserJs;
wfProfileIn( __METHOD__ );
@@ -3185,23 +3422,28 @@ HTML
$content = $this->toEditContent( $this->textbox1 );
$previewHTML = '';
- if ( !wfRunHooks( 'AlternateEditPreview', array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) {
+ if ( !wfRunHooks(
+ 'AlternateEditPreview',
+ array( $this, &$content, &$previewHTML, &$this->mParserOutput ) )
+ ) {
wfProfileOut( __METHOD__ );
return $previewHTML;
}
+ # provide a anchor link to the editform
+ $continueEditing = '<span class="mw-continue-editing">' .
+ '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
+ wfMessage( 'continue-editing' )->text() . ']]</span>';
if ( $this->mTriedSave && !$this->mTokenOk ) {
if ( $this->mTokenOkExceptSuffix ) {
$note = wfMessage( 'token_suffix_mismatch' )->plain();
-
} else {
$note = wfMessage( 'session_fail_preview' )->plain();
}
} elseif ( $this->incompleteForm ) {
$note = wfMessage( 'edit_form_incomplete' )->plain();
} else {
- $note = wfMessage( 'previewnote' )->plain() .
- ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
+ $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
}
$parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
@@ -3221,8 +3463,14 @@ HTML
if ( $content->getModel() == CONTENT_MODEL_CSS ) {
$format = 'css';
+ if ( $level === 'user' && !$wgAllowUserCss ) {
+ $format = false;
+ }
} elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
$format = 'js';
+ if ( $level === 'user' && !$wgAllowUserJs ) {
+ $format = false;
+ }
} else {
$format = false;
}
@@ -3230,49 +3478,55 @@ HTML
# Used messages to make sure grep find them:
# Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
if ( $level && $format ) {
- $note = "<div id='mw-{$level}{$format}preview'>" . wfMessage( "{$level}{$format}preview" )->text() . "</div>";
+ $note = "<div id='mw-{$level}{$format}preview'>" .
+ wfMessage( "{$level}{$format}preview" )->text() .
+ ' ' . $continueEditing . "</div>";
}
}
- $rt = $content->getRedirectChain();
- if ( $rt ) {
- $previewHTML = $this->mArticle->viewRedirect( $rt, false );
- } else {
-
- # If we're adding a comment, we need to show the
- # summary as the headline
- if ( $this->section === "new" && $this->summary !== "" ) {
- $content = $content->addSectionHeader( $this->summary );
- }
+ # If we're adding a comment, we need to show the
+ # summary as the headline
+ if ( $this->section === "new" && $this->summary !== "" ) {
+ $content = $content->addSectionHeader( $this->summary );
+ }
- $hook_args = array( $this, &$content );
- ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
- wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
+ $hook_args = array( $this, &$content );
+ ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
+ wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
- $parserOptions->enableLimitReport();
+ $parserOptions->enableLimitReport();
- # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
- # But it's now deprecated, so never mind
+ # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
+ # But it's now deprecated, so never mind
- $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
- $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
+ $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+ $parserOutput = $content->getParserOutput(
+ $this->getArticle()->getTitle(),
+ null,
+ $parserOptions
+ );
- $previewHTML = $parserOutput->getText();
- $this->mParserOutput = $parserOutput;
- $wgOut->addParserOutputNoText( $parserOutput );
+ $previewHTML = $parserOutput->getText();
+ $this->mParserOutput = $parserOutput;
+ $wgOut->addParserOutputMetadata( $parserOutput );
- if ( count( $parserOutput->getWarnings() ) ) {
- $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
- }
+ if ( count( $parserOutput->getWarnings() ) ) {
+ $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
}
} catch ( MWContentSerializationException $ex ) {
- $m = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $m = wfMessage(
+ 'content-failed-to-parse',
+ $this->contentModel,
+ $this->contentFormat,
+ $ex->getMessage()
+ );
$note .= "\n\n" . $m->parse();
$previewHTML = '';
}
if ( $this->isConflict ) {
- $conflict = '<h2 id="mw-previewconflict">' . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
+ $conflict = '<h2 id="mw-previewconflict">'
+ . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
} else {
$conflict = '<hr />';
}
@@ -3291,7 +3545,7 @@ HTML
}
/**
- * @return Array
+ * @return array
*/
function getTemplates() {
if ( $this->preview || $this->section != '' ) {
@@ -3313,128 +3567,94 @@ HTML
/**
* Shows a bulletin board style toolbar for common editing functions.
* It can be disabled in the user preferences.
- * The necessary JavaScript code can be found in skins/common/edit.js.
*
* @return string
*/
static function getEditToolbar() {
- global $wgStylePath, $wgContLang, $wgLang, $wgOut;
- global $wgUseTeX, $wgEnableUploads, $wgForeignFileRepos;
+ global $wgContLang, $wgOut;
+ global $wgEnableUploads, $wgForeignFileRepos;
$imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
/**
* $toolarray is an array of arrays each of which includes the
- * filename of the button image (without path), the opening
- * tag, the closing tag, optionally a sample text that is
+ * opening tag, the closing tag, optionally a sample text that is
* inserted between the two when no selection is highlighted
* and. The tip text is shown when the user moves the mouse
* over the button.
*
- * Also here: accesskeys (key), which are not used yet until
- * someone can figure out a way to make them work in
- * IE. However, we should make sure these keys are not defined
- * on the edit page.
+ * Images are defined in ResourceLoaderEditToolbarModule.
*/
$toolarray = array(
array(
- 'image' => $wgLang->getImageFile( 'button-bold' ),
'id' => 'mw-editbutton-bold',
'open' => '\'\'\'',
'close' => '\'\'\'',
'sample' => wfMessage( 'bold_sample' )->text(),
'tip' => wfMessage( 'bold_tip' )->text(),
- 'key' => 'B'
),
array(
- 'image' => $wgLang->getImageFile( 'button-italic' ),
'id' => 'mw-editbutton-italic',
'open' => '\'\'',
'close' => '\'\'',
'sample' => wfMessage( 'italic_sample' )->text(),
'tip' => wfMessage( 'italic_tip' )->text(),
- 'key' => 'I'
),
array(
- 'image' => $wgLang->getImageFile( 'button-link' ),
'id' => 'mw-editbutton-link',
'open' => '[[',
'close' => ']]',
'sample' => wfMessage( 'link_sample' )->text(),
'tip' => wfMessage( 'link_tip' )->text(),
- 'key' => 'L'
),
array(
- 'image' => $wgLang->getImageFile( 'button-extlink' ),
'id' => 'mw-editbutton-extlink',
'open' => '[',
'close' => ']',
'sample' => wfMessage( 'extlink_sample' )->text(),
'tip' => wfMessage( 'extlink_tip' )->text(),
- 'key' => 'X'
),
array(
- 'image' => $wgLang->getImageFile( 'button-headline' ),
'id' => 'mw-editbutton-headline',
'open' => "\n== ",
'close' => " ==\n",
'sample' => wfMessage( 'headline_sample' )->text(),
'tip' => wfMessage( 'headline_tip' )->text(),
- 'key' => 'H'
),
$imagesAvailable ? array(
- 'image' => $wgLang->getImageFile( 'button-image' ),
'id' => 'mw-editbutton-image',
'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
'close' => ']]',
'sample' => wfMessage( 'image_sample' )->text(),
'tip' => wfMessage( 'image_tip' )->text(),
- 'key' => 'D',
) : false,
$imagesAvailable ? array(
- 'image' => $wgLang->getImageFile( 'button-media' ),
'id' => 'mw-editbutton-media',
'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
'close' => ']]',
'sample' => wfMessage( 'media_sample' )->text(),
'tip' => wfMessage( 'media_tip' )->text(),
- 'key' => 'M'
- ) : false,
- $wgUseTeX ? array(
- 'image' => $wgLang->getImageFile( 'button-math' ),
- 'id' => 'mw-editbutton-math',
- 'open' => "<math>",
- 'close' => "</math>",
- 'sample' => wfMessage( 'math_sample' )->text(),
- 'tip' => wfMessage( 'math_tip' )->text(),
- 'key' => 'C'
) : false,
array(
- 'image' => $wgLang->getImageFile( 'button-nowiki' ),
'id' => 'mw-editbutton-nowiki',
'open' => "<nowiki>",
'close' => "</nowiki>",
'sample' => wfMessage( 'nowiki_sample' )->text(),
'tip' => wfMessage( 'nowiki_tip' )->text(),
- 'key' => 'N'
),
array(
- 'image' => $wgLang->getImageFile( 'button-sig' ),
'id' => 'mw-editbutton-signature',
'open' => '--~~~~',
'close' => '',
'sample' => '',
'tip' => wfMessage( 'sig_tip' )->text(),
- 'key' => 'Y'
),
array(
- 'image' => $wgLang->getImageFile( 'button-hr' ),
'id' => 'mw-editbutton-hr',
'open' => "\n----\n",
'close' => '',
'sample' => '',
'tip' => wfMessage( 'hr_tip' )->text(),
- 'key' => 'R'
)
);
@@ -3445,16 +3665,17 @@ HTML
}
$params = array(
- $image = $wgStylePath . '/common/images/' . $tool['image'],
+ // Images are defined in ResourceLoaderEditToolbarModule
+ false,
// Note that we use the tip both for the ALT tag and the TITLE tag of the image.
// Older browsers show a "speedtip" type message only for ALT.
// Ideally these should be different, realistically they
// probably don't need to be.
- $tip = $tool['tip'],
- $open = $tool['open'],
- $close = $tool['close'],
- $sample = $tool['sample'],
- $cssId = $tool['id'],
+ $tool['tip'],
+ $tool['open'],
+ $tool['close'],
+ $tool['sample'],
+ $tool['id'],
);
$script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
@@ -3481,13 +3702,13 @@ HTML
* minor and watch
*
* @param int $tabindex Current tabindex
- * @param array $checked of checkbox => bool, where bool indicates the checked
+ * @param array $checked Array of checkbox => bool, where bool indicates the checked
* status of the checkbox
*
* @return array
*/
public function getCheckboxes( &$tabindex, $checked ) {
- global $wgUser;
+ global $wgUser, $wgUseMediaWikiUIEverywhere;
$checkboxes = array();
@@ -3501,11 +3722,19 @@ HTML
'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
'id' => 'wpMinoredit',
);
- $checkboxes['minor'] =
+ $minorEditHtml =
Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
"&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
">{$minorLabel}</label>";
+
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $checkboxes['minor'] = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
+ $minorEditHtml .
+ Html::closeElement( 'div' );
+ } else {
+ $checkboxes['minor'] = $minorEditHtml;
+ }
}
}
@@ -3517,11 +3746,18 @@ HTML
'accesskey' => wfMessage( 'accesskey-watch' )->text(),
'id' => 'wpWatchthis',
);
- $checkboxes['watch'] =
+ $watchThisHtml =
Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
"&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
">{$watchLabel}</label>";
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $checkboxes['watch'] = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
+ $watchThisHtml .
+ Html::closeElement( 'div' );
+ } else {
+ $checkboxes['watch'] = $watchThisHtml;
+ }
}
wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
return $checkboxes;
@@ -3536,42 +3772,47 @@ HTML
* @return array
*/
public function getEditButtons( &$tabindex ) {
+ global $wgUseMediaWikiUIEverywhere;
+
$buttons = array();
- $temp = array(
+ $attribs = array(
'id' => 'wpSave',
'name' => 'wpSave',
'type' => 'submit',
'tabindex' => ++$tabindex,
'value' => wfMessage( 'savearticle' )->text(),
- 'accesskey' => wfMessage( 'accesskey-save' )->text(),
- 'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
- );
- $buttons['save'] = Xml::element( 'input', $temp, '' );
+ ) + Linker::tooltipAndAccesskeyAttribs( 'save' );
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $attribs['class'] = 'mw-ui-button mw-ui-constructive';
+ }
+ $buttons['save'] = Xml::element( 'input', $attribs, '' );
++$tabindex; // use the same for preview and live preview
- $temp = array(
+ $attribs = array(
'id' => 'wpPreview',
'name' => 'wpPreview',
'type' => 'submit',
'tabindex' => $tabindex,
'value' => wfMessage( 'showpreview' )->text(),
- 'accesskey' => wfMessage( 'accesskey-preview' )->text(),
- 'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
- );
- $buttons['preview'] = Xml::element( 'input', $temp, '' );
+ ) + Linker::tooltipAndAccesskeyAttribs( 'preview' );
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $attribs['class'] = 'mw-ui-button mw-ui-progressive';
+ }
+ $buttons['preview'] = Xml::element( 'input', $attribs, '' );
$buttons['live'] = '';
- $temp = array(
+ $attribs = array(
'id' => 'wpDiff',
'name' => 'wpDiff',
'type' => 'submit',
'tabindex' => ++$tabindex,
'value' => wfMessage( 'showdiff' )->text(),
- 'accesskey' => wfMessage( 'accesskey-diff' )->text(),
- 'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
- );
- $buttons['diff'] = Xml::element( 'input', $temp, '' );
+ ) + Linker::tooltipAndAccesskeyAttribs( 'diff' );
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $attribs['class'] = 'mw-ui-button mw-ui-progressive';
+ }
+ $buttons['diff'] = Xml::element( 'input', $attribs, '' );
wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
return $buttons;
@@ -3599,49 +3840,15 @@ HTML
#$categories = $skin->getCategoryLinks();
$s =
- '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
- Xml::tags( 'livepreview', null,
- Xml::element( 'preview', null, $previewText )
- #. Xml::element( 'category', null, $categories )
- );
+ '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
+ Xml::tags( 'livepreview', null,
+ Xml::element( 'preview', null, $previewText )
+ #. Xml::element( 'category', null, $categories )
+ );
echo $s;
}
/**
- * Call the stock "user is blocked" page
- *
- * @deprecated in 1.19; throw an exception directly instead
- */
- function blockedPage() {
- wfDeprecated( __METHOD__, '1.19' );
- global $wgUser;
-
- throw new UserBlockedError( $wgUser->getBlock() );
- }
-
- /**
- * Produce the stock "please login to edit pages" page
- *
- * @deprecated in 1.19; throw an exception directly instead
- */
- function userNotLoggedInPage() {
- wfDeprecated( __METHOD__, '1.19' );
- throw new PermissionsError( 'edit' );
- }
-
- /**
- * Show an error page saying to the user that he has insufficient permissions
- * to create a new page
- *
- * @deprecated in 1.19; throw an exception directly instead
- */
- function noCreatePermission() {
- wfDeprecated( __METHOD__, '1.19' );
- $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
- throw new PermissionsError( $permission );
- }
-
- /**
* Creates a basic error page which informs the user that
* they have attempted to edit a nonexistent section.
*/
@@ -3658,32 +3865,9 @@ HTML
}
/**
- * Produce the stock "your edit contains spam" page
- *
- * @param string|bool $match Text which triggered one or more filters
- * @deprecated since 1.17 Use method spamPageWithContent() instead
- */
- static function spamPage( $match = false ) {
- wfDeprecated( __METHOD__, '1.17' );
-
- global $wgOut, $wgTitle;
-
- $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
-
- $wgOut->addHTML( '<div id="spamprotected">' );
- $wgOut->addWikiMsg( 'spamprotectiontext' );
- if ( $match ) {
- $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
- }
- $wgOut->addHTML( '</div>' );
-
- $wgOut->returnToMain( false, $wgTitle );
- }
-
- /**
* Show "your edit contains spam" page with your diff and text
*
- * @param $match string|Array|bool Text (or array of texts) which triggered one or more filters
+ * @param string|array|bool $match Text (or array of texts) which triggered one or more filters
*/
public function spamPageWithContent( $match = false ) {
global $wgOut, $wgLang;
@@ -3711,24 +3895,12 @@ HTML
}
/**
- * Format an anchor fragment as it would appear for a given section name
- * @param $text String
- * @return String
- * @private
- */
- function sectionAnchor( $text ) {
- global $wgParser;
- return $wgParser->guessSectionNameFromWikiText( $text );
- }
-
- /**
* Check if the browser is on a blacklist of user-agents known to
* mangle UTF-8 data on form submission. Returns true if Unicode
* should make it through, false if it's known to be a problem.
* @return bool
- * @private
*/
- function checkUnicodeCompliantBrowser() {
+ private function checkUnicodeCompliantBrowser() {
global $wgBrowserBlackList, $wgRequest;
$currentbrowser = $wgRequest->getHeader( 'User-Agent' );
@@ -3749,27 +3921,14 @@ HTML
* Filter an input field through a Unicode de-armoring process if it
* came from an old browser with known broken Unicode editing issues.
*
- * @param $request WebRequest
- * @param $field String
- * @return String
- * @private
- */
- function safeUnicodeInput( $request, $field ) {
- $text = rtrim( $request->getText( $field ) );
- return $request->getBool( 'safemode' )
- ? $this->unmakesafe( $text )
- : $text;
- }
-
- /**
- * @param $request WebRequest
- * @param $text string
+ * @param WebRequest $request
+ * @param string $field
* @return string
*/
- function safeUnicodeText( $request, $text ) {
- $text = rtrim( $text );
+ protected function safeUnicodeInput( $request, $field ) {
+ $text = rtrim( $request->getText( $field ) );
return $request->getBool( 'safemode' )
- ? $this->unmakesafe( $text )
+ ? $this->unmakeSafe( $text )
: $text;
}
@@ -3777,16 +3936,15 @@ HTML
* Filter an output field through a Unicode armoring process if it is
* going to an old browser with known broken Unicode editing issues.
*
- * @param $text String
- * @return String
- * @private
+ * @param string $text
+ * @return string
*/
- function safeUnicodeOutput( $text ) {
+ protected function safeUnicodeOutput( $text ) {
global $wgContLang;
$codedText = $wgContLang->recodeForEdit( $text );
return $this->checkUnicodeCompliantBrowser()
? $codedText
- : $this->makesafe( $codedText );
+ : $this->makeSafe( $codedText );
}
/**
@@ -3798,18 +3956,18 @@ HTML
* Preexisting such character references will have a 0 added to them
* to ensure that round-trips do not alter the original data.
*
- * @param $invalue String
- * @return String
- * @private
+ * @param string $invalue
+ * @return string
*/
- function makesafe( $invalue ) {
+ private function makeSafe( $invalue ) {
// Armor existing references for reversibility.
$invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
$bytesleft = 0;
$result = "";
$working = 0;
- for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
+ $valueLength = strlen( $invalue );
+ for ( $i = 0; $i < $valueLength; $i++ ) {
$bytevalue = ord( $invalue[$i] );
if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
$result .= chr( $bytevalue );
@@ -3840,13 +3998,13 @@ HTML
* back to UTF-8. Used to protect data from corruption by broken web browsers
* as listed in $wgBrowserBlackList.
*
- * @param $invalue String
- * @return String
- * @private
+ * @param string $invalue
+ * @return string
*/
- function unmakesafe( $invalue ) {
+ private function unmakeSafe( $invalue ) {
$result = "";
- for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
+ $valueLength = strlen( $invalue );
+ for ( $i = 0; $i < $valueLength; $i++ ) {
if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
$i += 3;
$hexstring = "";
diff --git a/includes/Exception.php b/includes/Exception.php
deleted file mode 100644
index 5bad88c2..00000000
--- a/includes/Exception.php
+++ /dev/null
@@ -1,824 +0,0 @@
-<?php
-/**
- * Exception class and handler.
- *
- * 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 Exception Exception
- */
-
-/**
- * MediaWiki exception
- *
- * @ingroup Exception
- */
-class MWException extends Exception {
-
- /**
- * Should the exception use $wgOut to output the error?
- *
- * @return bool
- */
- function useOutputPage() {
- return $this->useMessageCache() &&
- !empty( $GLOBALS['wgFullyInitialised'] ) &&
- !empty( $GLOBALS['wgOut'] ) &&
- !empty( $GLOBALS['wgTitle'] );
- }
-
- /**
- * Whether to log this exception in the exception debug log.
- *
- * @since 1.23
- * @return boolean
- */
- function isLoggable() {
- return true;
- }
-
- /**
- * Can the extension use the Message class/wfMessage to get i18n-ed messages?
- *
- * @return bool
- */
- function useMessageCache() {
- global $wgLang;
-
- foreach ( $this->getTrace() as $frame ) {
- if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
- return false;
- }
- }
-
- return $wgLang instanceof Language;
- }
-
- /**
- * Run hook to allow extensions to modify the text of the exception
- *
- * @param string $name class name of the exception
- * @param array $args arguments to pass to the callback functions
- * @return string|null string to output or null if any hook has been called
- */
- function runHooks( $name, $args = array() ) {
- global $wgExceptionHooks;
-
- if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
- return null; // Just silently ignore
- }
-
- if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[$name] ) ) {
- return null;
- }
-
- $hooks = $wgExceptionHooks[$name];
- $callargs = array_merge( array( $this ), $args );
-
- foreach ( $hooks as $hook ) {
- if ( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { // 'function' or array( 'class', hook' )
- $result = call_user_func_array( $hook, $callargs );
- } else {
- $result = null;
- }
-
- if ( is_string( $result ) ) {
- return $result;
- }
- }
- return null;
- }
-
- /**
- * Get a message from i18n
- *
- * @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
- * @return string message with arguments replaced
- */
- function msg( $key, $fallback /*[, params...] */ ) {
- $args = array_slice( func_get_args(), 2 );
-
- if ( $this->useMessageCache() ) {
- return wfMessage( $key, $args )->plain();
- } else {
- return wfMsgReplaceArgs( $fallback, $args );
- }
- }
-
- /**
- * If $wgShowExceptionDetails is true, return a HTML message with a
- * backtrace to the error, otherwise show a message to ask to set it to true
- * to show that information.
- *
- * @return string html to output
- */
- function getHTML() {
- global $wgShowExceptionDetails;
-
- if ( $wgShowExceptionDetails ) {
- return '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) .
- '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
- "</p>\n";
- } else {
- return "<div class=\"errorbox\">" .
- '[' . MWExceptionHandler::getLogId( $this ) . '] ' .
- gmdate( 'Y-m-d H:i:s' ) .
- ": Fatal exception of type " . get_class( $this ) . "</div>\n" .
- "<!-- Set \$wgShowExceptionDetails = true; " .
- "at the bottom of LocalSettings.php to show detailed " .
- "debugging information. -->";
- }
- }
-
- /**
- * Get the text to display when reporting the error on the command line.
- * If $wgShowExceptionDetails is true, return a text message with a
- * backtrace to the error.
- *
- * @return string
- */
- function getText() {
- global $wgShowExceptionDetails;
-
- if ( $wgShowExceptionDetails ) {
- return MWExceptionHandler::getLogMessage( $this ) .
- "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n";
- } else {
- return "Set \$wgShowExceptionDetails = true; " .
- "in LocalSettings.php to show detailed debugging information.\n";
- }
- }
-
- /**
- * Return the title of the page when reporting this error in a HTTP response.
- *
- * @return string
- */
- function getPageTitle() {
- return $this->msg( 'internalerror', "Internal error" );
- }
-
- /**
- * Get a the ID for this error.
- *
- * @since 1.20
- * @deprecated since 1.22 Use MWExceptionHandler::getLogId instead.
- * @return string
- */
- function getLogId() {
- wfDeprecated( __METHOD__, '1.22' );
- return MWExceptionHandler::getLogId( $this );
- }
-
- /**
- * Return the requested URL and point to file and line number from which the
- * exception occurred
- *
- * @since 1.8
- * @deprecated since 1.22 Use MWExceptionHandler::getLogMessage instead.
- * @return string
- */
- function getLogMessage() {
- wfDeprecated( __METHOD__, '1.22' );
- return MWExceptionHandler::getLogMessage( $this );
- }
-
- /**
- * Output the exception report using HTML.
- */
- function reportHTML() {
- global $wgOut;
- if ( $this->useOutputPage() ) {
- $wgOut->prepareErrorPage( $this->getPageTitle() );
-
- $hookResult = $this->runHooks( get_class( $this ) );
- if ( $hookResult ) {
- $wgOut->addHTML( $hookResult );
- } else {
- $wgOut->addHTML( $this->getHTML() );
- }
-
- $wgOut->output();
- } else {
- header( "Content-Type: text/html; charset=utf-8" );
- echo "<!doctype html>\n" .
- '<html><head>' .
- '<title>' . htmlspecialchars( $this->getPageTitle() ) . '</title>' .
- "</head><body>\n";
-
- $hookResult = $this->runHooks( get_class( $this ) . "Raw" );
- if ( $hookResult ) {
- echo $hookResult;
- } else {
- echo $this->getHTML();
- }
-
- echo "</body></html>\n";
- }
- }
-
- /**
- * Output a report about the exception and takes care of formatting.
- * It will be either HTML or plain text based on isCommandLine().
- */
- function report() {
- global $wgMimeType;
-
- MWExceptionHandler::logException( $this );
-
- if ( defined( 'MW_API' ) ) {
- // Unhandled API exception, we can't be sure that format printer is alive
- header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
- wfHttpError( 500, 'Internal Server Error', $this->getText() );
- } elseif ( self::isCommandLine() ) {
- MWExceptionHandler::printError( $this->getText() );
- } else {
- header( "HTTP/1.1 500 MediaWiki exception" );
- header( "Status: 500 MediaWiki exception", true );
- header( "Content-Type: $wgMimeType; charset=utf-8", true );
-
- $this->reportHTML();
- }
- }
-
- /**
- * Check whether we are in command line mode or not to report the exception
- * in the correct format.
- *
- * @return bool
- */
- static function isCommandLine() {
- return !empty( $GLOBALS['wgCommandLineMode'] );
- }
-}
-
-/**
- * Exception class which takes an HTML error message, and does not
- * produce a backtrace. Replacement for OutputPage::fatalError().
- *
- * @since 1.7
- * @ingroup Exception
- */
-class FatalError extends MWException {
-
- /**
- * @return string
- */
- function getHTML() {
- return $this->getMessage();
- }
-
- /**
- * @return string
- */
- function getText() {
- return $this->getMessage();
- }
-}
-
-/**
- * An error page which can definitely be safely rendered using the OutputPage.
- *
- * @since 1.7
- * @ingroup Exception
- */
-class ErrorPageError extends MWException {
- public $title, $msg, $params;
-
- /**
- * Note: these arguments are keys into wfMessage(), not text!
- *
- * @param string|Message $title Message key (string) for page title, or a Message object
- * @param string|Message $msg Message key (string) for error text, or a Message object
- * @param array $params with parameters to wfMessage()
- */
- function __construct( $title, $msg, $params = null ) {
- $this->title = $title;
- $this->msg = $msg;
- $this->params = $params;
-
- // Bug 44111: Messages in the log files should be in English and not
- // customized by the local wiki. So get the default English version for
- // passing to the parent constructor. Our overridden report() below
- // makes sure that the page shown to the user is not forced to English.
- if ( $msg instanceof Message ) {
- $enMsg = clone( $msg );
- } else {
- $enMsg = wfMessage( $msg, $params );
- }
- $enMsg->inLanguage( 'en' )->useDatabase( false );
- parent::__construct( $enMsg->text() );
- }
-
- function report() {
- global $wgOut;
-
- $wgOut->showErrorPage( $this->title, $this->msg, $this->params );
- $wgOut->output();
- }
-}
-
-/**
- * Show an error page on a badtitle.
- * Similar to ErrorPage, but emit a 400 HTTP error code to let mobile
- * browser it is not really a valid content.
- *
- * @since 1.19
- * @ingroup Exception
- */
-class BadTitleError extends ErrorPageError {
- /**
- * @param string|Message $msg A message key (default: 'badtitletext')
- * @param array $params parameter to wfMessage()
- */
- function __construct( $msg = 'badtitletext', $params = null ) {
- parent::__construct( 'badtitle', $msg, $params );
- }
-
- /**
- * Just like ErrorPageError::report() but additionally set
- * a 400 HTTP status code (bug 33646).
- */
- function report() {
- global $wgOut;
-
- // bug 33646: a badtitle error page need to return an error code
- // to let mobile browser now that it is not a normal page.
- $wgOut->setStatusCode( 400 );
- parent::report();
- }
-
-}
-
-/**
- * Show an error when a user tries to do something they do not have the necessary
- * permissions for.
- *
- * @since 1.18
- * @ingroup Exception
- */
-class PermissionsError extends ErrorPageError {
- public $permission, $errors;
-
- function __construct( $permission, $errors = array() ) {
- global $wgLang;
-
- $this->permission = $permission;
-
- if ( !count( $errors ) ) {
- $groups = array_map(
- array( 'User', 'makeGroupLinkWiki' ),
- User::getGroupsWithPermission( $this->permission )
- );
-
- if ( $groups ) {
- $errors[] = array( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
- } else {
- $errors[] = array( 'badaccess-group0' );
- }
- }
-
- $this->errors = $errors;
- }
-
- function report() {
- global $wgOut;
-
- $wgOut->showPermissionsErrorPage( $this->errors, $this->permission );
- $wgOut->output();
- }
-}
-
-/**
- * Show an error when the wiki is locked/read-only and the user tries to do
- * something that requires write access.
- *
- * @since 1.18
- * @ingroup Exception
- */
-class ReadOnlyError extends ErrorPageError {
- public function __construct() {
- parent::__construct(
- 'readonly',
- 'readonlytext',
- wfReadOnlyReason()
- );
- }
-}
-
-/**
- * Show an error when the user hits a rate limit.
- *
- * @since 1.18
- * @ingroup Exception
- */
-class ThrottledError extends ErrorPageError {
- public function __construct() {
- parent::__construct(
- 'actionthrottled',
- 'actionthrottledtext'
- );
- }
-
- public function report() {
- global $wgOut;
- $wgOut->setStatusCode( 503 );
- parent::report();
- }
-}
-
-/**
- * Show an error when the user tries to do something whilst blocked.
- *
- * @since 1.18
- * @ingroup Exception
- */
-class UserBlockedError extends ErrorPageError {
- public function __construct( Block $block ) {
- // @todo FIXME: Implement a more proper way to get context here.
- $params = $block->getPermissionsError( RequestContext::getMain() );
- parent::__construct( 'blockedtitle', array_shift( $params ), $params );
- }
-}
-
-/**
- * Shows a generic "user is not logged in" error page.
- *
- * This is essentially an ErrorPageError exception which by default uses the
- * 'exception-nologin' as a title and 'exception-nologin-text' for the message.
- * @see bug 37627
- * @since 1.20
- *
- * @par Example:
- * @code
- * if( $user->isAnon() ) {
- * throw new UserNotLoggedIn();
- * }
- * @endcode
- *
- * Note the parameter order differs from ErrorPageError, this allows you to
- * simply specify a reason without overriding the default title.
- *
- * @par Example:
- * @code
- * if( $user->isAnon() ) {
- * throw new UserNotLoggedIn( 'action-require-loggedin' );
- * }
- * @endcode
- *
- * @ingroup Exception
- */
-class UserNotLoggedIn extends ErrorPageError {
-
- /**
- * @param $reasonMsg A message key containing the reason for the error.
- * Optional, default: 'exception-nologin-text'
- * @param $titleMsg A message key to set the page title.
- * Optional, default: 'exception-nologin'
- * @param $params Parameters to wfMessage().
- * Optional, default: null
- */
- public function __construct(
- $reasonMsg = 'exception-nologin-text',
- $titleMsg = 'exception-nologin',
- $params = null
- ) {
- parent::__construct( $titleMsg, $reasonMsg, $params );
- }
-}
-
-/**
- * Show an error that looks like an HTTP server error.
- * Replacement for wfHttpError().
- *
- * @since 1.19
- * @ingroup Exception
- */
-class HttpError extends MWException {
- private $httpCode, $header, $content;
-
- /**
- * Constructor
- *
- * @param $httpCode Integer: HTTP status code to send to the client
- * @param string|Message $content content of the message
- * @param string|Message $header content of the header (\<title\> and \<h1\>)
- */
- public function __construct( $httpCode, $content, $header = null ) {
- parent::__construct( $content );
- $this->httpCode = (int)$httpCode;
- $this->header = $header;
- $this->content = $content;
- }
-
- /**
- * Returns the HTTP status code supplied to the constructor.
- *
- * @return int
- */
- public function getStatusCode() {
- return $this->httpCode;
- }
-
- /**
- * Report the HTTP error.
- * Sends the appropriate HTTP status code and outputs an
- * HTML page with an error message.
- */
- public function report() {
- $httpMessage = HttpStatus::getMessage( $this->httpCode );
-
- header( "Status: {$this->httpCode} {$httpMessage}", true, $this->httpCode );
- header( 'Content-type: text/html; charset=utf-8' );
-
- print $this->getHTML();
- }
-
- /**
- * Returns HTML for reporting the HTTP error.
- * This will be a minimal but complete HTML document.
- *
- * @return string HTML
- */
- public function getHTML() {
- if ( $this->header === null ) {
- $header = HttpStatus::getMessage( $this->httpCode );
- } elseif ( $this->header instanceof Message ) {
- $header = $this->header->escaped();
- } else {
- $header = htmlspecialchars( $this->header );
- }
-
- if ( $this->content instanceof Message ) {
- $content = $this->content->escaped();
- } else {
- $content = htmlspecialchars( $this->content );
- }
-
- return "<!DOCTYPE html>\n" .
- "<html><head><title>$header</title></head>\n" .
- "<body><h1>$header</h1><p>$content</p></body></html>\n";
- }
-}
-
-/**
- * Handler class for MWExceptions
- * @ingroup Exception
- */
-class MWExceptionHandler {
- /**
- * Install an exception handler for MediaWiki exception types.
- */
- public static function installHandler() {
- set_exception_handler( array( 'MWExceptionHandler', 'handle' ) );
- }
-
- /**
- * Report an exception to the user
- */
- protected static function report( Exception $e ) {
- global $wgShowExceptionDetails;
-
- $cmdLine = MWException::isCommandLine();
-
- if ( $e instanceof MWException ) {
- try {
- // Try and show the exception prettily, with the normal skin infrastructure
- $e->report();
- } catch ( Exception $e2 ) {
- // Exception occurred from within exception handler
- // Show a simpler error message for the original exception,
- // don't try to invoke report()
- $message = "MediaWiki internal error.\n\n";
-
- if ( $wgShowExceptionDetails ) {
- $message .= 'Original exception: ' . self::getLogMessage( $e ) .
- "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
- "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
- "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
- } else {
- $message .= "Exception caught inside exception handler.\n\n" .
- "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
- "to show detailed debugging information.";
- }
-
- $message .= "\n";
-
- if ( $cmdLine ) {
- self::printError( $message );
- } else {
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
- }
- }
- } else {
- $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"";
-
- if ( $wgShowExceptionDetails ) {
- $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
- self::getRedactedTraceAsString( $e ) . "\n";
- }
-
- if ( $cmdLine ) {
- self::printError( $message );
- } else {
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
- }
- }
- }
-
- /**
- * Print a message, if possible to STDERR.
- * Use this in command line mode only (see isCommandLine)
- *
- * @param string $message Failure text
- */
- public static function printError( $message ) {
- # NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
- # Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
- if ( defined( 'STDERR' ) ) {
- fwrite( STDERR, $message );
- } else {
- echo $message;
- }
- }
-
- /**
- * Exception handler which simulates the appropriate catch() handling:
- *
- * try {
- * ...
- * } catch ( MWException $e ) {
- * $e->report();
- * } catch ( Exception $e ) {
- * echo $e->__toString();
- * }
- */
- public static function handle( $e ) {
- global $wgFullyInitialised;
-
- self::report( $e );
-
- // Final cleanup
- if ( $wgFullyInitialised ) {
- try {
- // uses $wgRequest, hence the $wgFullyInitialised condition
- wfLogProfilingData();
- } catch ( Exception $e ) {
- }
- }
-
- // Exit value should be nonzero for the benefit of shell jobs
- exit( 1 );
- }
-
- /**
- * Generate a string representation of an exception's stack trace
- *
- * Like Exception::getTraceAsString, but replaces argument values with
- * argument type or class name.
- *
- * @param Exception $e
- * @return string
- */
- public static function getRedactedTraceAsString( Exception $e ) {
- $text = '';
-
- foreach ( self::getRedactedTrace( $e ) as $level => $frame ) {
- if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
- $text .= "#{$level} {$frame['file']}({$frame['line']}): ";
- } else {
- // 'file' and 'line' are unset for calls via call_user_func (bug 55634)
- // This matches behaviour of Exception::getTraceAsString to instead
- // display "[internal function]".
- $text .= "#{$level} [internal function]: ";
- }
-
- if ( isset( $frame['class'] ) ) {
- $text .= $frame['class'] . $frame['type'] . $frame['function'];
- } else {
- $text .= $frame['function'];
- }
-
- if ( isset( $frame['args'] ) ) {
- $text .= '(' . implode( ', ', $frame['args'] ) . ")\n";
- } else {
- $text .= "()\n";
- }
- }
-
- $level = $level + 1;
- $text .= "#{$level} {main}";
-
- return $text;
- }
-
- /**
- * Return a copy of an exception's backtrace as an array.
- *
- * Like Exception::getTrace, but replaces each element in each frame's
- * argument array with the name of its class (if the element is an object)
- * or its type (if the element is a PHP primitive).
- *
- * @since 1.22
- * @param Exception $e
- * @return array
- */
- public static function getRedactedTrace( Exception $e ) {
- return array_map( function ( $frame ) {
- if ( isset( $frame['args'] ) ) {
- $frame['args'] = array_map( function ( $arg ) {
- return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
- }, $frame['args'] );
- }
- return $frame;
- }, $e->getTrace() );
- }
-
-
- /**
- * Get the ID for this error.
- *
- * The ID is saved so that one can match the one output to the user (when
- * $wgShowExceptionDetails is set to false), to the entry in the debug log.
- *
- * @since 1.22
- * @param Exception $e
- * @return string
- */
- public static function getLogId( Exception $e ) {
- if ( !isset( $e->_mwLogId ) ) {
- $e->_mwLogId = wfRandomString( 8 );
- }
- return $e->_mwLogId;
- }
-
- /**
- * Return the requested URL and point to file and line number from which the
- * exception occurred.
- *
- * @since 1.22
- * @param Exception $e
- * @return string
- */
- public static function getLogMessage( Exception $e ) {
- global $wgRequest;
-
- $id = self::getLogId( $e );
- $file = $e->getFile();
- $line = $e->getLine();
- $message = $e->getMessage();
-
- if ( isset( $wgRequest ) && !$wgRequest instanceof FauxRequest ) {
- $url = $wgRequest->getRequestURL();
- if ( !$url ) {
- $url = '[no URL]';
- }
- } else {
- $url = '[no req]';
- }
-
- return "[$id] $url Exception from line $line of $file: $message";
- }
-
- /**
- * Log an exception to the exception log (if enabled).
- *
- * This method must not assume the exception is an MWException,
- * it is also used to handle PHP errors or errors from other libraries.
- *
- * @since 1.22
- * @param Exception $e
- */
- public static function logException( Exception $e ) {
- global $wgLogExceptionBacktrace;
-
- if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
- $log = self::getLogMessage( $e );
- if ( $wgLogExceptionBacktrace ) {
- wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() . "\n" );
- } else {
- wfDebugLog( 'exception', $log );
- }
- }
- }
-
-}
diff --git a/includes/Export.php b/includes/Export.php
index 98de4c00..84f5c60c 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -3,7 +3,7 @@
* Base classes for dumps and export
*
* Copyright © 2003, 2005, 2006 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -31,11 +31,17 @@
* @ingroup SpecialPage Dump
*/
class WikiExporter {
- var $list_authors = false; # Return distinct author list (when not returning full history)
- var $author_list = "";
+ /** @var bool Return distinct author list (when not returning full history) */
+ public $list_authors = false;
- var $dumpUploads = false;
- var $dumpUploadFileContents = false;
+ /** @var bool */
+ public $dumpUploads = false;
+
+ /** @var bool */
+ public $dumpUploadFileContents = false;
+
+ /** @var string */
+ public $author_list = "";
const FULL = 1;
const CURRENT = 2;
@@ -49,21 +55,21 @@ class WikiExporter {
const TEXT = 0;
const STUB = 1;
- var $buffer;
+ /** @var int */
+ public $buffer;
- var $text;
+ /** @var int */
+ public $text;
- /**
- * @var DumpOutput
- */
- var $sink;
+ /** @var DumpOutput */
+ public $sink;
/**
* Returns the export schema version.
* @return string
*/
public static function schemaVersion() {
- return "0.8";
+ return "0.9";
}
/**
@@ -73,15 +79,14 @@ class WikiExporter {
* make additional queries to pull source data while the
* main query is still running.
*
- * @param $db DatabaseBase
- * @param $history Mixed: one of WikiExporter::FULL, WikiExporter::CURRENT,
- * WikiExporter::RANGE or WikiExporter::STABLE,
- * or an associative array:
- * offset: non-inclusive offset at which to start the query
- * limit: maximum number of rows to return
- * dir: "asc" or "desc" timestamp order
- * @param int $buffer one of WikiExporter::BUFFER or WikiExporter::STREAM
- * @param int $text one of WikiExporter::TEXT or WikiExporter::STUB
+ * @param DatabaseBase $db
+ * @param int|array $history One of WikiExporter::FULL, WikiExporter::CURRENT,
+ * WikiExporter::RANGE or WikiExporter::STABLE, or an associative array:
+ * - offset: non-inclusive offset at which to start the query
+ * - limit: maximum number of rows to return
+ * - dir: "asc" or "desc" timestamp order
+ * @param int $buffer One of WikiExporter::BUFFER or WikiExporter::STREAM
+ * @param int $text One of WikiExporter::TEXT or WikiExporter::STUB
*/
function __construct( $db, $history = WikiExporter::CURRENT,
$buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT ) {
@@ -98,7 +103,7 @@ class WikiExporter {
* various row objects and XML output for filtering. Filters
* can be chained or used as callbacks.
*
- * @param $sink mixed
+ * @param DumpOutput $sink
*/
public function setOutputSink( &$sink ) {
$this->sink =& $sink;
@@ -126,9 +131,9 @@ class WikiExporter {
/**
* Dumps a series of page and revision records for those pages
* in the database falling within the page_id range given.
- * @param int $start inclusive lower limit (this id is included)
- * @param $end Int: Exclusive upper limit (this id is not included)
- * If 0, no upper limit.
+ * @param int $start Inclusive lower limit (this id is included)
+ * @param int $end Exclusive upper limit (this id is not included)
+ * If 0, no upper limit.
*/
public function pagesByRange( $start, $end ) {
$condition = 'page_id >= ' . intval( $start );
@@ -141,9 +146,9 @@ class WikiExporter {
/**
* Dumps a series of page and revision records for those pages
* in the database with revisions falling within the rev_id range given.
- * @param int $start inclusive lower limit (this id is included)
- * @param $end Int: Exclusive upper limit (this id is not included)
- * If 0, no upper limit.
+ * @param int $start Inclusive lower limit (this id is included)
+ * @param int $end Exclusive upper limit (this id is not included)
+ * If 0, no upper limit.
*/
public function revsByRange( $start, $end ) {
$condition = 'rev_id >= ' . intval( $start );
@@ -154,7 +159,7 @@ class WikiExporter {
}
/**
- * @param $title Title
+ * @param Title $title
*/
public function pageByTitle( $title ) {
$this->dumpFrom(
@@ -163,7 +168,7 @@ class WikiExporter {
}
/**
- * @param $name string
+ * @param string $name
* @throws MWException
*/
public function pageByName( $name ) {
@@ -176,7 +181,7 @@ class WikiExporter {
}
/**
- * @param $names array
+ * @param array $names
*/
public function pagesByName( $names ) {
foreach ( $names as $name ) {
@@ -189,8 +194,8 @@ class WikiExporter {
}
/**
- * @param $start int
- * @param $end int
+ * @param int $start
+ * @param int $end
*/
public function logsByRange( $start, $end ) {
$condition = 'log_id >= ' . intval( $start );
@@ -205,7 +210,7 @@ class WikiExporter {
* Not called by default (depends on $this->list_authors)
* Can be set by Special:Export when not exporting whole history
*
- * @param $cond
+ * @param array $cond
*/
protected function do_list_authors( $cond ) {
wfProfileIn( __METHOD__ );
@@ -238,7 +243,7 @@ class WikiExporter {
}
/**
- * @param $cond string
+ * @param string $cond
* @throws MWException
* @throws Exception
*/
@@ -262,7 +267,7 @@ class WikiExporter {
if ( $this->buffer == WikiExporter::STREAM ) {
$prev = $this->db->bufferResults( false );
}
- $wrapper = null; // Assuring $wrapper is not undefined, if exception occurs early
+ $result = null; // Assuring $result is not undefined, if exception occurs early
try {
$result = $this->db->select( array( 'logging', 'user' ),
array( "{$logging}.*", 'user_name' ), // grab the user name
@@ -270,8 +275,7 @@ class WikiExporter {
__METHOD__,
array( 'ORDER BY' => 'log_id', 'USE INDEX' => array( 'logging' => 'PRIMARY' ) )
);
- $wrapper = $this->db->resultObject( $result );
- $this->outputLogStream( $wrapper );
+ $this->outputLogStream( $result );
if ( $this->buffer == WikiExporter::STREAM ) {
$this->db->bufferResults( $prev );
}
@@ -281,8 +285,8 @@ class WikiExporter {
// Freeing result
try {
- if ( $wrapper ) {
- $wrapper->free();
+ if ( $result ) {
+ $result->free();
}
} catch ( Exception $e2 ) {
// Already in panic mode -> ignoring $e2 as $e has
@@ -372,16 +376,15 @@ class WikiExporter {
$prev = $this->db->bufferResults( false );
}
- $wrapper = null; // Assuring $wrapper is not undefined, if exception occurs early
+ $result = null; // Assuring $result is not undefined, if exception occurs early
try {
wfRunHooks( 'ModifyExportQuery',
array( $this->db, &$tables, &$cond, &$opts, &$join ) );
# Do the query!
$result = $this->db->select( $tables, '*', $cond, __METHOD__, $opts, $join );
- $wrapper = $this->db->resultObject( $result );
# Output dump results
- $this->outputPageStream( $wrapper );
+ $this->outputPageStream( $result );
if ( $this->buffer == WikiExporter::STREAM ) {
$this->db->bufferResults( $prev );
@@ -392,8 +395,8 @@ class WikiExporter {
// Freeing result
try {
- if ( $wrapper ) {
- $wrapper->free();
+ if ( $result ) {
+ $result->free();
}
} catch ( Exception $e2 ) {
// Already in panic mode -> ignoring $e2 as $e has
@@ -427,7 +430,7 @@ class WikiExporter {
* separate database connection not managed by LoadBalancer; some
* blob storage types will make queries to pull source data.
*
- * @param $resultset ResultWrapper
+ * @param ResultWrapper $resultset
*/
protected function outputPageStream( $resultset ) {
$last = null;
@@ -462,7 +465,7 @@ class WikiExporter {
}
/**
- * @param $resultset array
+ * @param ResultWrapper $resultset
*/
protected function outputLogStream( $resultset ) {
foreach ( $resultset as $row ) {
@@ -478,7 +481,7 @@ class WikiExporter {
class XmlDumpWriter {
/**
* Returns the export schema version.
- * @deprecated in 1.20; use WikiExporter::schemaVersion() instead
+ * @deprecated since 1.20; use WikiExporter::schemaVersion() instead
* @return string
*/
function schemaVersion() {
@@ -502,8 +505,18 @@ class XmlDumpWriter {
return Xml::element( 'mediawiki', array(
'xmlns' => "http://www.mediawiki.org/xml/export-$ver/",
'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
+ /*
+ * When a new version of the schema is created, it needs staging on mediawiki.org.
+ * This requires a change in the operations/mediawiki-config git repo.
+ *
+ * Create a changeset like https://gerrit.wikimedia.org/r/#/c/149643/ in which
+ * you copy in the new xsd file.
+ *
+ * After it is reviewed, merged and deployed (sync-docroot), the index.html needs purging.
+ * echo "http://www.mediawiki.org/xml/index.html" | mwscript purgeList.php --wiki=aawiki
+ */
'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
- "http://www.mediawiki.org/xml/export-$ver.xsd", #TODO: how do we get a new version up there?
+ "http://www.mediawiki.org/xml/export-$ver.xsd",
'version' => $ver,
'xml:lang' => $wgLanguageCode ),
null ) .
@@ -517,6 +530,7 @@ class XmlDumpWriter {
function siteInfo() {
$info = array(
$this->sitename(),
+ $this->dbname(),
$this->homelink(),
$this->generator(),
$this->caseSetting(),
@@ -537,6 +551,14 @@ class XmlDumpWriter {
/**
* @return string
*/
+ function dbname() {
+ global $wgDBname;
+ return Xml::element( 'dbname', array(), $wgDBname );
+ }
+
+ /**
+ * @return string
+ */
function generator() {
global $wgVersion;
return Xml::element( 'generator', array(), "MediaWiki $wgVersion" );
@@ -591,11 +613,10 @@ class XmlDumpWriter {
* Opens a "<page>" section on the output stream, with data
* from the given database row.
*
- * @param $row object
+ * @param object $row
* @return string
- * @access private
*/
- function openPage( $row ) {
+ public function openPage( $row ) {
$out = " <page>\n";
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
$out .= ' ' . Xml::elementClean( 'title', array(), self::canonicalTitle( $title ) ) . "\n";
@@ -604,8 +625,10 @@ class XmlDumpWriter {
if ( $row->page_is_redirect ) {
$page = WikiPage::factory( $title );
$redirect = $page->getRedirectTarget();
- if ( $redirect instanceOf Title && $redirect->isValidRedirectTarget() ) {
- $out .= ' ' . Xml::element( 'redirect', array( 'title' => self::canonicalTitle( $redirect ) ) ) . "\n";
+ if ( $redirect instanceof Title && $redirect->isValidRedirectTarget() ) {
+ $out .= ' ';
+ $out .= Xml::element( 'redirect', array( 'title' => self::canonicalTitle( $redirect ) ) );
+ $out .= "\n";
}
}
@@ -633,7 +656,7 @@ class XmlDumpWriter {
* Dumps a "<revision>" section on the output stream, with
* data filled in from the given database row.
*
- * @param $row object
+ * @param object $row
* @return string
* @access private
*/
@@ -663,12 +686,30 @@ class XmlDumpWriter {
$out .= " " . Xml::elementClean( 'comment', array(), strval( $row->rev_comment ) ) . "\n";
}
+ if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model ) ) {
+ $content_model = strval( $row->rev_content_model );
+ } else {
+ // probably using $wgContentHandlerUseDB = false;
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $content_model = ContentHandler::getDefaultModelFor( $title );
+ }
+
+ $content_handler = ContentHandler::getForModelID( $content_model );
+
+ if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
+ $content_format = strval( $row->rev_content_format );
+ } else {
+ // probably using $wgContentHandlerUseDB = false;
+ $content_format = $content_handler->getDefaultFormat();
+ }
+
$text = '';
if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
$out .= " " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
} elseif ( isset( $row->old_text ) ) {
// Raw text from the database may have invalid chars
$text = strval( Revision::getRevisionText( $row ) );
+ $text = $content_handler->exportTransform( $text, $content_format );
$out .= " " . Xml::elementClean( 'text',
array( 'xml:space' => 'preserve', 'bytes' => intval( $row->rev_len ) ),
strval( $text ) ) . "\n";
@@ -679,32 +720,16 @@ class XmlDumpWriter {
"" ) . "\n";
}
- if ( isset( $row->rev_sha1 ) && $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
+ if ( isset( $row->rev_sha1 )
+ && $row->rev_sha1
+ && !( $row->rev_deleted & Revision::DELETED_TEXT )
+ ) {
$out .= " " . Xml::element( 'sha1', null, strval( $row->rev_sha1 ) ) . "\n";
} else {
$out .= " <sha1/>\n";
}
- if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model ) ) {
- $content_model = strval( $row->rev_content_model );
- } else {
- // probably using $wgContentHandlerUseDB = false;
- // @todo test!
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $content_model = ContentHandler::getDefaultModelFor( $title );
- }
-
$out .= " " . Xml::element( 'model', null, strval( $content_model ) ) . "\n";
-
- if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
- $content_format = strval( $row->rev_content_format );
- } else {
- // probably using $wgContentHandlerUseDB = false;
- // @todo test!
- $content_handler = ContentHandler::getForModelID( $content_model );
- $content_format = $content_handler->getDefaultFormat();
- }
-
$out .= " " . Xml::element( 'format', null, strval( $content_format ) ) . "\n";
wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
@@ -719,7 +744,7 @@ class XmlDumpWriter {
* Dumps a "<logitem>" section on the output stream, with
* data filled in from the given database row.
*
- * @param $row object
+ * @param object $row
* @return string
* @access private
*/
@@ -763,7 +788,7 @@ class XmlDumpWriter {
}
/**
- * @param $timestamp string
+ * @param string $timestamp
* @param string $indent Default to six spaces
* @return string
*/
@@ -773,8 +798,8 @@ class XmlDumpWriter {
}
/**
- * @param $id
- * @param $text string
+ * @param int $id
+ * @param string $text
* @param string $indent Default to six spaces
* @return string
*/
@@ -792,8 +817,8 @@ class XmlDumpWriter {
/**
* Warning! This data is potentially inconsistent. :(
- * @param $row
- * @param $dumpContents bool
+ * @param object $row
+ * @param bool $dumpContents
* @return string
*/
function writeUploads( $row, $dumpContents = false ) {
@@ -812,8 +837,8 @@ class XmlDumpWriter {
}
/**
- * @param $file File
- * @param $dumpContents bool
+ * @param File $file
+ * @param bool $dumpContents
* @return string
*/
function writeUpload( $file, $dumpContents = false ) {
@@ -827,7 +852,7 @@ class XmlDumpWriter {
$be = $file->getRepo()->getBackend();
# Dump file as base64
# Uses only XML-safe characters, so does not need escaping
- # @TODO: too bad this loads the contents into memory (script might swap)
+ # @todo Too bad this loads the contents into memory (script might swap)
$contents = ' <contents encoding="base64">' .
chunk_split( base64_encode(
$be->getFileContents( array( 'src' => $file->getPath() ) ) ) ) .
@@ -865,7 +890,7 @@ class XmlDumpWriter {
* @since 1.18
*/
public static function canonicalTitle( Title $title ) {
- if ( $title->getInterwiki() ) {
+ if ( $title->isExternal() ) {
return $title->getPrefixedText();
}
@@ -887,45 +912,45 @@ class XmlDumpWriter {
class DumpOutput {
/**
- * @param $string string
+ * @param string $string
*/
function writeOpenStream( $string ) {
$this->write( $string );
}
/**
- * @param $string string
+ * @param string $string
*/
function writeCloseStream( $string ) {
$this->write( $string );
}
/**
- * @param $page
- * @param $string string
+ * @param object $page
+ * @param string $string
*/
function writeOpenPage( $page, $string ) {
$this->write( $string );
}
/**
- * @param $string string
+ * @param string $string
*/
function writeClosePage( $string ) {
$this->write( $string );
}
/**
- * @param $rev
- * @param $string string
+ * @param object $rev
+ * @param string $string
*/
function writeRevision( $rev, $string ) {
$this->write( $string );
}
/**
- * @param $rev
- * @param $string string
+ * @param object $rev
+ * @param string $string
*/
function writeLogItem( $rev, $string ) {
$this->write( $string );
@@ -933,7 +958,7 @@ class DumpOutput {
/**
* Override to write to a different stream type.
- * @param $string string
+ * @param string $string
* @return bool
*/
function write( $string ) {
@@ -945,7 +970,7 @@ class DumpOutput {
* and reopen new file with the old name. Use this
* for writing out a file in multiple pieces
* at specified checkpoints (e.g. every n hours).
- * @param $newname mixed File name. May be a string or an array with one element
+ * @param string|array $newname File name. May be a string or an array with one element
*/
function closeRenameAndReopen( $newname ) {
}
@@ -954,8 +979,9 @@ class DumpOutput {
* Close the old file, and move it to a specified name.
* Use this for the last piece of a file written out
* at specified checkpoints (e.g. every n hours).
- * @param $newname mixed File name. May be a string or an array with one element
- * @param bool $open If true, a new file with the old filename will be opened again for writing (default: false)
+ * @param string|array $newname File name. May be a string or an array with one element
+ * @param bool $open If true, a new file with the old filename will be opened
+ * again for writing (default: false)
*/
function closeAndRename( $newname, $open = false ) {
}
@@ -978,7 +1004,7 @@ class DumpFileOutput extends DumpOutput {
protected $handle = false, $filename;
/**
- * @param $file
+ * @param string $file
*/
function __construct( $file ) {
$this->handle = fopen( $file, "wt" );
@@ -986,7 +1012,7 @@ class DumpFileOutput extends DumpOutput {
}
/**
- * @param $string string
+ * @param string $string
*/
function writeCloseStream( $string ) {
parent::writeCloseStream( $string );
@@ -997,21 +1023,21 @@ class DumpFileOutput extends DumpOutput {
}
/**
- * @param $string string
+ * @param string $string
*/
function write( $string ) {
fputs( $this->handle, $string );
}
/**
- * @param $newname
+ * @param string $newname
*/
function closeRenameAndReopen( $newname ) {
$this->closeAndRename( $newname, true );
}
/**
- * @param $newname
+ * @param string $newname
* @throws MWException
*/
function renameOrException( $newname ) {
@@ -1021,8 +1047,8 @@ class DumpFileOutput extends DumpOutput {
}
/**
- * @param $newname array
- * @return mixed
+ * @param array $newname
+ * @return string
* @throws MWException
*/
function checkRenameArgCount( $newname ) {
@@ -1037,8 +1063,8 @@ class DumpFileOutput extends DumpOutput {
}
/**
- * @param $newname mixed
- * @param $open bool
+ * @param string $newname
+ * @param bool $open
*/
function closeAndRename( $newname, $open = false ) {
$newname = $this->checkRenameArgCount( $newname );
@@ -1073,8 +1099,8 @@ class DumpPipeOutput extends DumpFileOutput {
protected $procOpenResource = false;
/**
- * @param $command
- * @param $file null
+ * @param string $command
+ * @param string $file
*/
function __construct( $command, $file = null ) {
if ( !is_null( $file ) ) {
@@ -1087,7 +1113,7 @@ class DumpPipeOutput extends DumpFileOutput {
}
/**
- * @param $string string
+ * @param string $string
*/
function writeCloseStream( $string ) {
parent::writeCloseStream( $string );
@@ -1098,7 +1124,7 @@ class DumpPipeOutput extends DumpFileOutput {
}
/**
- * @param $command
+ * @param string $command
*/
function startCommand( $command ) {
$spec = array(
@@ -1110,15 +1136,15 @@ class DumpPipeOutput extends DumpFileOutput {
}
/**
- * @param mixed $newname
+ * @param string $newname
*/
function closeRenameAndReopen( $newname ) {
$this->closeAndRename( $newname, true );
}
/**
- * @param $newname mixed
- * @param $open bool
+ * @param string $newname
+ * @param bool $open
*/
function closeAndRename( $newname, $open = false ) {
$newname = $this->checkRenameArgCount( $newname );
@@ -1139,7 +1165,6 @@ class DumpPipeOutput extends DumpFileOutput {
}
}
}
-
}
/**
@@ -1147,9 +1172,8 @@ class DumpPipeOutput extends DumpFileOutput {
* @ingroup Dump
*/
class DumpGZipOutput extends DumpPipeOutput {
-
/**
- * @param $file string
+ * @param string $file
*/
function __construct( $file ) {
parent::__construct( "gzip", $file );
@@ -1161,9 +1185,8 @@ class DumpGZipOutput extends DumpPipeOutput {
* @ingroup Dump
*/
class DumpBZip2Output extends DumpPipeOutput {
-
/**
- * @param $file string
+ * @param string $file
*/
function __construct( $file ) {
parent::__construct( "bzip2", $file );
@@ -1175,9 +1198,8 @@ class DumpBZip2Output extends DumpPipeOutput {
* @ingroup Dump
*/
class Dump7ZipOutput extends DumpPipeOutput {
-
/**
- * @param $file string
+ * @param string $file
*/
function __construct( $file ) {
$command = $this->setup7zCommand( $file );
@@ -1186,7 +1208,7 @@ class Dump7ZipOutput extends DumpPipeOutput {
}
/**
- * @param $file string
+ * @param string $file
* @return string
*/
function setup7zCommand( $file ) {
@@ -1198,8 +1220,8 @@ class Dump7ZipOutput extends DumpPipeOutput {
}
/**
- * @param $newname string
- * @param $open bool
+ * @param string $newname
+ * @param bool $open
*/
function closeAndRename( $newname, $open = false ) {
$newname = $this->checkRenameArgCount( $newname );
@@ -1222,7 +1244,6 @@ class Dump7ZipOutput extends DumpPipeOutput {
* @ingroup Dump
*/
class DumpFilter {
-
/**
* @var DumpOutput
* FIXME will need to be made protected whenever legacy code
@@ -1236,29 +1257,29 @@ class DumpFilter {
protected $sendingThisPage;
/**
- * @param $sink DumpOutput
+ * @param DumpOutput $sink
*/
function __construct( &$sink ) {
$this->sink =& $sink;
}
/**
- * @param $string string
+ * @param string $string
*/
function writeOpenStream( $string ) {
$this->sink->writeOpenStream( $string );
}
/**
- * @param $string string
+ * @param string $string
*/
function writeCloseStream( $string ) {
$this->sink->writeCloseStream( $string );
}
/**
- * @param $page
- * @param $string string
+ * @param object $page
+ * @param string $string
*/
function writeOpenPage( $page, $string ) {
$this->sendingThisPage = $this->pass( $page, $string );
@@ -1268,7 +1289,7 @@ class DumpFilter {
}
/**
- * @param $string string
+ * @param string $string
*/
function writeClosePage( $string ) {
if ( $this->sendingThisPage ) {
@@ -1278,8 +1299,8 @@ class DumpFilter {
}
/**
- * @param $rev
- * @param $string string
+ * @param object $rev
+ * @param string $string
*/
function writeRevision( $rev, $string ) {
if ( $this->sendingThisPage ) {
@@ -1288,23 +1309,23 @@ class DumpFilter {
}
/**
- * @param $rev
- * @param $string string
+ * @param object $rev
+ * @param string $string
*/
function writeLogItem( $rev, $string ) {
$this->sink->writeRevision( $rev, $string );
}
/**
- * @param $newname string
+ * @param string $newname
*/
function closeRenameAndReopen( $newname ) {
$this->sink->closeRenameAndReopen( $newname );
}
/**
- * @param $newname string
- * @param $open bool
+ * @param string $newname
+ * @param bool $open
*/
function closeAndRename( $newname, $open = false ) {
$this->sink->closeAndRename( $newname, $open );
@@ -1319,7 +1340,7 @@ class DumpFilter {
/**
* Override for page-based filter types.
- * @param $page
+ * @param object $page
* @return bool
*/
function pass( $page ) {
@@ -1332,9 +1353,8 @@ class DumpFilter {
* @ingroup Dump
*/
class DumpNotalkFilter extends DumpFilter {
-
/**
- * @param $page
+ * @param object $page
* @return bool
*/
function pass( $page ) {
@@ -1347,12 +1367,15 @@ class DumpNotalkFilter extends DumpFilter {
* @ingroup Dump
*/
class DumpNamespaceFilter extends DumpFilter {
- var $invert = false;
- var $namespaces = array();
+ /** @var bool */
+ public $invert = false;
+
+ /** @var array */
+ public $namespaces = array();
/**
- * @param $sink DumpOutput
- * @param $param
+ * @param DumpOutput $sink
+ * @param array $param
* @throws MWException
*/
function __construct( &$sink, $param ) {
@@ -1398,7 +1421,7 @@ class DumpNamespaceFilter extends DumpFilter {
}
/**
- * @param $page
+ * @param object $page
* @return bool
*/
function pass( $page ) {
@@ -1412,11 +1435,17 @@ class DumpNamespaceFilter extends DumpFilter {
* @ingroup Dump
*/
class DumpLatestFilter extends DumpFilter {
- var $page, $pageString, $rev, $revString;
+ public $page;
+
+ public $pageString;
+
+ public $rev;
+
+ public $revString;
/**
- * @param $page
- * @param $string string
+ * @param object $page
+ * @param string $string
*/
function writeOpenPage( $page, $string ) {
$this->page = $page;
@@ -1424,7 +1453,7 @@ class DumpLatestFilter extends DumpFilter {
}
/**
- * @param $string string
+ * @param string $string
*/
function writeClosePage( $string ) {
if ( $this->rev ) {
@@ -1439,8 +1468,8 @@ class DumpLatestFilter extends DumpFilter {
}
/**
- * @param $rev
- * @param $string string
+ * @param object $rev
+ * @param string $string
*/
function writeRevision( $rev, $string ) {
if ( $rev->rev_id == $this->page->page_latest ) {
@@ -1457,7 +1486,7 @@ class DumpLatestFilter extends DumpFilter {
class DumpMultiWriter {
/**
- * @param $sinks
+ * @param array $sinks
*/
function __construct( $sinks ) {
$this->sinks = $sinks;
@@ -1465,7 +1494,7 @@ class DumpMultiWriter {
}
/**
- * @param $string string
+ * @param string $string
*/
function writeOpenStream( $string ) {
for ( $i = 0; $i < $this->count; $i++ ) {
@@ -1474,7 +1503,7 @@ class DumpMultiWriter {
}
/**
- * @param $string string
+ * @param string $string
*/
function writeCloseStream( $string ) {
for ( $i = 0; $i < $this->count; $i++ ) {
@@ -1483,8 +1512,8 @@ class DumpMultiWriter {
}
/**
- * @param $page
- * @param $string string
+ * @param object $page
+ * @param string $string
*/
function writeOpenPage( $page, $string ) {
for ( $i = 0; $i < $this->count; $i++ ) {
@@ -1493,7 +1522,7 @@ class DumpMultiWriter {
}
/**
- * @param $string
+ * @param string $string
*/
function writeClosePage( $string ) {
for ( $i = 0; $i < $this->count; $i++ ) {
@@ -1502,8 +1531,8 @@ class DumpMultiWriter {
}
/**
- * @param $rev
- * @param $string
+ * @param object $rev
+ * @param string $string
*/
function writeRevision( $rev, $string ) {
for ( $i = 0; $i < $this->count; $i++ ) {
@@ -1512,14 +1541,14 @@ class DumpMultiWriter {
}
/**
- * @param $newnames
+ * @param array $newnames
*/
function closeRenameAndReopen( $newnames ) {
$this->closeAndRename( $newnames, true );
}
/**
- * @param $newnames array
+ * @param array $newnames
* @param bool $open
*/
function closeAndRename( $newnames, $open = false ) {
@@ -1538,24 +1567,4 @@ class DumpMultiWriter {
}
return $filenames;
}
-
-}
-
-/**
- * @param $string string
- * @return string
- */
-function xmlsafe( $string ) {
- wfProfileIn( __FUNCTION__ );
-
- /**
- * The page may contain old data which has not been properly normalized.
- * Invalid UTF-8 sequences or forbidden control characters will make our
- * XML output invalid, so be sure to strip them out.
- */
- $string = UtfNormal::cleanUp( $string );
-
- $string = htmlspecialchars( $string );
- wfProfileOut( __FUNCTION__ );
- return $string;
}
diff --git a/includes/FakeTitle.php b/includes/FakeTitle.php
deleted file mode 100644
index efa213fb..00000000
--- a/includes/FakeTitle.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?php
-/**
- * Fake title class that triggers an error if any members are called.
- *
- * 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
- */
-
-/**
- * Fake title class that triggers an error if any members are called
- */
-class FakeTitle extends Title {
- function error() { throw new MWException( "Attempt to call member function of FakeTitle\n" ); }
-
- function isLocal() { $this->error(); }
- function isTrans() { $this->error(); }
- function getText() { $this->error(); }
- function getPartialURL() { $this->error(); }
- function getDBkey() { $this->error(); }
- function getNamespace() { $this->error(); }
- function getNsText() { $this->error(); }
- function getUserCaseDBKey() { $this->error(); }
- function getSubjectNsText() { $this->error(); }
- function getTalkNsText() { $this->error(); }
- function canTalk() { $this->error(); }
- function getInterwiki() { $this->error(); }
- function getFragment() { $this->error(); }
- function getFragmentForURL() { $this->error(); }
- function getDefaultNamespace() { $this->error(); }
- function getIndexTitle() { $this->error(); }
- function getPrefixedDBkey() { $this->error(); }
- function getPrefixedText() { $this->error(); }
- function getFullText() { $this->error(); }
- function getBaseText() { $this->error(); }
- function getSubpageText() { $this->error(); }
- function getSubpageUrlForm() { $this->error(); }
- function getPrefixedURL() { $this->error(); }
- function getFullURL( $query = '', $variant = false ) { $this->error(); }
- function getLocalURL( $query = '', $variant = false ) { $this->error(); }
- function getLinkURL( $query = array(), $variant = false ) { $this->error(); }
- function escapeLocalURL( $query = '', $query2 = false ) { $this->error(); }
- function escapeFullURL( $query = '', $query2 = false ) { $this->error(); }
- function getInternalURL( $query = '', $variant = false ) { $this->error(); }
- function getEditURL() { $this->error(); }
- function getEscapedText() { $this->error(); }
- function isExternal() { $this->error(); }
- function isSemiProtected( $action = 'edit' ) { $this->error(); }
- function isProtected( $action = '' ) { $this->error(); }
- function isConversionTable() { $this->error(); }
- function userIsWatching() { $this->error(); }
- function quickUserCan( $action, $user = null ) { $this->error(); }
- function isNamespaceProtected( User $user ) { $this->error(); }
- function userCan( $action, $user = null, $doExpensiveQueries = true ) { $this->error(); }
- function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) { $this->error(); }
- function updateTitleProtection( $create_perm, $reason, $expiry ) { $this->error(); }
- function deleteTitleProtection() { $this->error(); }
- function isMovable() { $this->error(); }
- function userCanRead() { $this->error(); }
- function isTalkPage() { $this->error(); }
- function isSubpage() { $this->error(); }
- function hasSubpages() { $this->error(); }
- function getSubpages( $limit = -1 ) { $this->error(); }
- function isCssJsSubpage() { $this->error(); }
- function isCssOrJsPage() { $this->error(); }
- function getSkinFromCssJsSubpage() { $this->error(); }
- function isCssSubpage() { $this->error(); }
- function isJsSubpage() { $this->error(); }
- function userCanEditCssSubpage() { $this->error(); }
- function userCanEditJsSubpage() { $this->error(); }
- function isCascadeProtected() { $this->error(); }
- function getCascadeProtectionSources( $get_pages = true ) { $this->error(); }
- function areRestrictionsCascading() { $this->error(); }
- function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) { $this->error(); }
- function loadRestrictions( $res = null ) { $this->error(); }
- function getRestrictions( $action ) { $this->error(); }
- function getRestrictionExpiry( $action ) { $this->error(); }
- function isDeleted() { $this->error(); }
- function isDeletedQuick() { $this->error(); }
- function getArticleID( $flags = 0 ) { $this->error(); }
- function isRedirect( $flags = 0 ) { $this->error(); }
- function getLength( $flags = 0 ) { $this->error(); }
- function getLatestRevID( $flags = 0 ) { $this->error(); }
- function resetArticleID( $newid ) { $this->error(); }
- function invalidateCache() { $this->error(); }
- function getTalkPage() { $this->error(); }
- function setFragment( $fragment ) { $this->error(); }
- function getSubjectPage() { $this->error(); }
- function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) { $this->error(); }
- function getTemplateLinksTo( $options = array() ) { $this->error(); }
- function getBrokenLinksFrom() { $this->error(); }
- function getSquidURLs() { $this->error(); }
- function purgeSquid() { $this->error(); }
- function moveNoAuth( &$nt ) { $this->error(); }
- function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) { $this->error(); }
- function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) { $this->error(); }
- function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) { $this->error(); }
- function isSingleRevRedirect() { $this->error(); }
- function isValidMoveTarget( $nt ) { $this->error(); }
- function isWatchable() { $this->error(); }
- function getParentCategories() { $this->error(); }
- function getParentCategoryTree( $children = array() ) { $this->error(); }
- function pageCond() { $this->error(); }
- function getPreviousRevisionID( $revId, $flags = 0 ) { $this->error(); }
- function getNextRevisionID( $revId, $flags = 0 ) { $this->error(); }
- function getFirstRevision( $flags = 0 ) { $this->error(); }
- function isNewPage() { $this->error(); }
- function getEarliestRevTime( $flags = 0 ) { $this->error(); }
- function countRevisionsBetween( $old, $new ) { $this->error(); }
- function equals( Title $title ) { $this->error(); }
- function exists() { $this->error(); }
- function isAlwaysKnown() { $this->error(); }
- function isKnown() { $this->error(); }
- function canExist() { $this->error(); }
- function touchLinks() { $this->error(); }
- function getTouched( $db = null ) { $this->error(); }
- function getNotificationTimestamp( $user = null ) { $this->error(); }
- function getNamespaceKey( $prepend = 'nstab-' ) { $this->error(); }
- function isSpecialPage() { $this->error(); }
- function isSpecial( $name ) { $this->error(); }
- function fixSpecialName() { $this->error(); }
- function isContentPage() { $this->error(); }
- function getRedirectsHere( $ns = null ) { $this->error(); }
- function isValidRedirectTarget() { $this->error(); }
- function getBacklinkCache() { $this->error(); }
- function canUseNoindex() { $this->error(); }
- function getRestrictionTypes() { $this->error(); }
-}
diff --git a/includes/Fallback.php b/includes/Fallback.php
index cdf6c88e..8e7f4b7e 100644
--- a/includes/Fallback.php
+++ b/includes/Fallback.php
@@ -26,28 +26,6 @@
class Fallback {
/**
- * @param $from
- * @param $to
- * @param $string
- * @return string
- */
- public static function iconv( $from, $to, $string ) {
- if ( substr( $to, -8 ) == '//IGNORE' ) {
- $to = substr( $to, 0, strlen( $to ) - 8 );
- }
- if ( strcasecmp( $from, $to ) == 0 ) {
- return $string;
- }
- if ( strcasecmp( $from, 'utf-8' ) == 0 ) {
- return utf8_decode( $string );
- }
- if ( strcasecmp( $to, 'utf-8' ) == 0 ) {
- return utf8_encode( $string );
- }
- return $string;
- }
-
- /**
* Fallback implementation for mb_substr, hardcoded to UTF-8.
* Attempts to be at least _moderately_ efficient; best optimized
* for relatively small offset and count values -- about 5x slower
@@ -57,9 +35,9 @@ class Fallback {
* can be up to 100x slower than native if the text is heavily
* multibyte and we have to slog through a few hundred kb.
*
- * @param $str
- * @param $start
- * @param $count string
+ * @param string $str
+ * @param int $start
+ * @param string $count
*
* @return string
*/
@@ -78,8 +56,8 @@ class Fallback {
}
/**
- * @param $str
- * @param $splitPos
+ * @param string $str
+ * @param int $splitPos
* @return int
*/
public static function mb_substr_split_unicode( $str, $splitPos ) {
@@ -130,7 +108,7 @@ class Fallback {
/**
* Fallback implementation of mb_strlen, hardcoded to UTF-8.
* @param string $str
- * @param string $enc optional encoding; ignored
+ * @param string $enc Optional encoding; ignored
* @return int
*/
public static function mb_strlen( $str, $enc = '' ) {
@@ -151,10 +129,10 @@ class Fallback {
/**
* Fallback implementation of mb_strpos, hardcoded to UTF-8.
- * @param $haystack String
- * @param $needle String
- * @param string $offset optional start position
- * @param string $encoding optional encoding; ignored
+ * @param string $haystack
+ * @param string $needle
+ * @param string $offset Optional start position
+ * @param string $encoding Optional encoding; ignored
* @return int
*/
public static function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
@@ -172,10 +150,10 @@ class Fallback {
/**
* Fallback implementation of mb_strrpos, hardcoded to UTF-8.
- * @param $haystack String
- * @param $needle String
- * @param string $offset optional start position
- * @param string $encoding optional encoding; ignored
+ * @param string $haystack
+ * @param string $needle
+ * @param string $offset Optional start position
+ * @param string $encoding Optional encoding; ignored
* @return int
*/
public static function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
diff --git a/includes/Feed.php b/includes/Feed.php
index 635b04e4..2fdfa424 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -6,7 +6,7 @@
* Available feeds are defined in Defines.php
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -36,28 +36,32 @@
* @ingroup Feed
*/
class FeedItem {
- /**
- * @var Title
- */
- var $title;
+ /** @var Title */
+ public $title;
+
+ public $description;
+
+ public $url;
+
+ public $date;
+
+ public $author;
- var $description;
- var $url;
- var $date;
- var $author;
- var $uniqueId;
- var $comments;
- var $rssIsPermalink = false;
+ public $uniqueId;
+
+ public $comments;
+
+ public $rssIsPermalink = false;
/**
* Constructor
*
* @param string|Title $title Item's title
- * @param $description String
+ * @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 $comments String
+ * @param string $comments
*/
function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) {
$this->title = $title;
@@ -72,8 +76,8 @@ class FeedItem {
/**
* Encode $string so that it can be safely embedded in a XML document
*
- * @param string $string string to encode
- * @return String
+ * @param string $string String to encode
+ * @return string
*/
public function xmlEncode( $string ) {
$string = str_replace( "\r\n", "\n", $string );
@@ -84,7 +88,7 @@ class FeedItem {
/**
* Get the unique id of this item
*
- * @return String
+ * @return string
*/
public function getUniqueId() {
if ( $this->uniqueId ) {
@@ -93,10 +97,10 @@ class FeedItem {
}
/**
- * set the unique id of an item
+ * Set the unique id of an item
*
- * @param string $uniqueId unique id for the item
- * @param $rssIsPermalink Boolean: set to true if the guid (unique id) is a permalink (RSS feeds only)
+ * @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;
@@ -106,7 +110,7 @@ class FeedItem {
/**
* Get the title of this item; already xml-encoded
*
- * @return String
+ * @return string
*/
public function getTitle() {
return $this->xmlEncode( $this->title );
@@ -115,7 +119,7 @@ class FeedItem {
/**
* Get the URL of this item; already xml-encoded
*
- * @return String
+ * @return string
*/
public function getUrl() {
return $this->xmlEncode( $this->url );
@@ -124,7 +128,7 @@ class FeedItem {
/**
* Get the description of this item; already xml-encoded
*
- * @return String
+ * @return string
*/
public function getDescription() {
return $this->xmlEncode( $this->description );
@@ -133,7 +137,7 @@ class FeedItem {
/**
* Get the language of this item
*
- * @return String
+ * @return string
*/
public function getLanguage() {
global $wgLanguageCode;
@@ -141,9 +145,9 @@ class FeedItem {
}
/**
- * Get the title of this item
+ * Get the date of this item
*
- * @return String
+ * @return string
*/
public function getDate() {
return $this->date;
@@ -152,7 +156,7 @@ class FeedItem {
/**
* Get the author of this item; already xml-encoded
*
- * @return String
+ * @return string
*/
public function getAuthor() {
return $this->xmlEncode( $this->author );
@@ -161,7 +165,7 @@ class FeedItem {
/**
* Get the comment of this item; already xml-encoded
*
- * @return String
+ * @return string
*/
public function getComments() {
return $this->xmlEncode( $this->comments );
@@ -170,8 +174,8 @@ class FeedItem {
/**
* Quickie hack... strip out wikilinks to more legible form from the comment.
*
- * @param string $text wikitext
- * @return String
+ * @param string $text Wikitext
+ * @return string
*/
public static function stripComment( $text ) {
return preg_replace( '/\[\[([^]]*\|)?([^]]+)\]\]/', '\2', $text );
@@ -190,7 +194,6 @@ abstract class ChannelFeed extends FeedItem {
* @code
* print "<feed>";
* @endcode
- * @param $item
*/
abstract public function outHeader();
@@ -200,7 +203,7 @@ abstract class ChannelFeed extends FeedItem {
* @code
* print "<item>...</item>";
* @endcode
- * @param $item
+ * @param FeedItem $item
*/
abstract public function outItem( $item );
@@ -239,28 +242,27 @@ abstract class ChannelFeed extends FeedItem {
* Return an internet media type to be sent in the headers.
*
* @return string
- * @private
*/
- function contentType() {
+ private function contentType() {
global $wgRequest;
+
$ctype = $wgRequest->getVal( 'ctype', 'application/xml' );
- $allowedctypes = array( 'application/xml', 'text/xml', 'application/rss+xml', 'application/atom+xml' );
+ $allowedctypes = array(
+ 'application/xml',
+ 'text/xml',
+ 'application/rss+xml',
+ 'application/atom+xml'
+ );
+
return ( in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml' );
}
/**
- * Output the initial XML headers with a stylesheet for legibility
- * if someone finds it in a browser.
- * @private
+ * Output the initial XML headers.
*/
- function outXmlHeader() {
- global $wgStylePath, $wgStyleVersion;
-
+ protected function outXmlHeader() {
$this->httpHeaders();
echo '<?xml version="1.0"?>' . "\n";
- echo '<?xml-stylesheet type="text/css" href="' .
- htmlspecialchars( wfExpandUrl( "$wgStylePath/common/feed.css?$wgStyleVersion", PROTO_CURRENT ) ) .
- '"?' . ">\n";
}
}
@@ -274,8 +276,8 @@ class RSSFeed extends ChannelFeed {
/**
* Format a date given a timestamp
*
- * @param $ts Integer: timestamp
- * @return String: date string
+ * @param int $ts Timestamp
+ * @return string Date string
*/
function formatTime( $ts ) {
return gmdate( 'D, d M Y H:i:s \G\M\T', wfTimestamp( TS_UNIX, $ts ) );
@@ -301,9 +303,10 @@ class RSSFeed extends ChannelFeed {
/**
* Output an RSS 2.0 item
- * @param $item FeedItem: item to be output
+ * @param FeedItem $item Item to be output
*/
function outItem( $item ) {
+ // @codingStandardsIgnoreStart Ignore long lines and formatting issues.
?>
<item>
<title><?php print $item->getTitle(); ?></title>
@@ -315,6 +318,7 @@ class RSSFeed extends ChannelFeed {
<?php if ( $item->getComments() ) { ?><comments><?php print wfExpandUrl( $item->getComments(), PROTO_CURRENT ); ?></comments><?php }?>
</item>
<?php
+ // @codingStandardsIgnoreEnd
}
/**
@@ -335,6 +339,7 @@ class RSSFeed extends ChannelFeed {
class AtomFeed extends ChannelFeed {
/**
* @todo document
+ * @param string|int $ts
* @return string
*/
function formatTime( $ts ) {
@@ -349,6 +354,7 @@ class AtomFeed extends ChannelFeed {
global $wgVersion;
$this->outXmlHeader();
+ // @codingStandardsIgnoreStart Ignore long lines and formatting issues.
?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="<?php print $this->getLanguage() ?>">
<id><?php print $this->getFeedId() ?></id>
<title><?php print $this->getTitle() ?></title>
@@ -359,6 +365,7 @@ class AtomFeed extends ChannelFeed {
<generator>MediaWiki <?php print $wgVersion ?></generator>
<?php
+ // @codingStandardsIgnoreEnd
}
/**
@@ -368,28 +375,27 @@ class AtomFeed extends ChannelFeed {
* have to change the id? Maybe? Maybe not.
*
* @return string
- * @private
*/
- function getFeedId() {
+ private function getFeedId() {
return $this->getSelfUrl();
}
/**
* Atom 1.0 requests a self-reference to the feed.
* @return string
- * @private
*/
- function getSelfUrl() {
+ private function getSelfUrl() {
global $wgRequest;
return htmlspecialchars( $wgRequest->getFullRequestURL() );
}
/**
* Output a given item.
- * @param $item
+ * @param FeedItem $item
*/
function outItem( $item ) {
global $wgMimeType;
+ // @codingStandardsIgnoreStart Ignore long lines and formatting issues.
?>
<entry>
<id><?php print $item->getUniqueId(); ?></id>
@@ -413,5 +419,6 @@ class AtomFeed extends ChannelFeed {
*/
function outFooter() {?>
</feed><?php
+ // @codingStandardsIgnoreEnd
}
}
diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php
index 22cb52be..6937c32d 100644
--- a/includes/FeedUtils.php
+++ b/includes/FeedUtils.php
@@ -33,8 +33,8 @@ class FeedUtils {
* If the feed should be purged; $timekey and $key will be removed from
* $messageMemc
*
- * @param string $timekey cache key of the timestamp of the last item
- * @param string $key cache key of feed's content
+ * @param string $timekey Cache key of the timestamp of the last item
+ * @param string $key Cache key of feed's content
*/
public static function checkPurge( $timekey, $key ) {
global $wgRequest, $wgUser, $messageMemc;
@@ -48,8 +48,8 @@ class FeedUtils {
/**
* Check whether feeds can be used and that $type is a valid feed type
*
- * @param string $type feed type, as requested by the user
- * @return Boolean
+ * @param string $type Feed type, as requested by the user
+ * @return bool
*/
public static function checkFeedOutput( $type ) {
global $wgOut, $wgFeed, $wgFeedClasses;
@@ -70,8 +70,8 @@ class FeedUtils {
/**
* Format a diff for the newsfeed
*
- * @param $row Object: row from the recentchanges table
- * @return String
+ * @param object $row Row from the recentchanges table
+ * @return string
*/
public static function formatDiff( $row ) {
$titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title );
@@ -94,15 +94,17 @@ class FeedUtils {
/**
* Really format a diff for the newsfeed
*
- * @param $title Title object
- * @param $oldid Integer: old revision's id
- * @param $newid Integer: new revision's id
- * @param $timestamp Integer: new revision's timestamp
- * @param string $comment new revision's comment
- * @param string $actiontext text of the action; in case of log event
- * @return String
+ * @param Title $title Title object
+ * @param int $oldid Old revision's id
+ * @param int $newid New revision's id
+ * @param int $timestamp New revision's timestamp
+ * @param string $comment New revision's comment
+ * @param string $actiontext Text of the action; in case of log event
+ * @return string
*/
- public static function formatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext = '' ) {
+ public static function formatDiffRow( $title, $oldid, $newid, $timestamp,
+ $comment, $actiontext = ''
+ ) {
global $wgFeedDiffCutoff, $wgLang;
wfProfileIn( __METHOD__ );
@@ -214,9 +216,9 @@ class FeedUtils {
* Generates a diff link. Used when the full diff is not wanted for example
* when $wgFeedDiffCutoff is 0.
*
- * @param $title Title object: used to generate the diff URL
- * @param $newid Integer newid for this diff
- * @param $oldid Integer|null oldid for the diff. Null means it is a new article
+ * @param Title $title Title object: used to generate the diff URL
+ * @param int $newid Newid for this diff
+ * @param int|null $oldid Oldid for the diff. Null means it is a new article
* @return string
*/
protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
@@ -237,17 +239,23 @@ class FeedUtils {
* Might be 'cleaner' to use DOM or XSLT or something,
* but *gack* it's a pain in the ass.
*
- * @param string $text diff's HTML output
- * @return String: modified HTML
+ * @param string $text Diff's HTML output
+ * @return string Modified HTML
*/
public static function applyDiffStyle( $text ) {
$styles = array(
'diff' => 'background-color: white; color:black;',
'diff-otitle' => 'background-color: white; color:black; text-align: center;',
'diff-ntitle' => 'background-color: white; color:black; text-align: center;',
- 'diff-addedline' => 'color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;',
- 'diff-deletedline' => 'color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;',
- 'diff-context' => 'background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;',
+ 'diff-addedline' => 'color:black; font-size: 88%; border-style: solid; '
+ . 'border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; '
+ . 'vertical-align: top; white-space: pre-wrap;',
+ 'diff-deletedline' => 'color:black; font-size: 88%; border-style: solid; '
+ . 'border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; '
+ . 'vertical-align: top; white-space: pre-wrap;',
+ 'diff-context' => 'background-color: #f9f9f9; color: #333333; font-size: 88%; '
+ . 'border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; '
+ . 'border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;',
'diffchange' => 'font-weight: bold; text-decoration: none;',
);
diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php
index 65d82b87..b4e24581 100644
--- a/includes/FileDeleteForm.php
+++ b/includes/FileDeleteForm.php
@@ -48,7 +48,7 @@ class FileDeleteForm {
/**
* Constructor
*
- * @param $file File object we're deleting
+ * @param File $file File object we're deleting
*/
public function __construct( $file ) {
$this->title = $file->getTitle();
@@ -83,7 +83,10 @@ class FileDeleteForm {
$suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
if ( $this->oldimage ) {
- $this->oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->title, $this->oldimage );
+ $this->oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName(
+ $this->title,
+ $this->oldimage
+ );
}
if ( !self::haveDeletableFile( $this->file, $this->oldfile, $this->oldimage ) ) {
@@ -107,11 +110,20 @@ class FileDeleteForm {
$reason = $deleteReasonList;
}
- $status = self::doDelete( $this->title, $this->file, $this->oldimage, $reason, $suppress, $wgUser );
+ $status = self::doDelete(
+ $this->title,
+ $this->file,
+ $this->oldimage,
+ $reason,
+ $suppress,
+ $wgUser
+ );
if ( !$status->isGood() ) {
$wgOut->addHTML( '<h2>' . $this->prepareMessage( 'filedeleteerror-short' ) . "</h2>\n" );
- $wgOut->addWikiText( '<div class="error">' . $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' ) . '</div>' );
+ $wgOut->addWikiText( '<div class="error">' .
+ $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' )
+ . '</div>' );
}
if ( $status->ok ) {
$wgOut->setPageTitle( wfMessage( 'actioncomplete' ) );
@@ -132,16 +144,18 @@ class FileDeleteForm {
/**
* Really delete the file
*
- * @param $title Title object
- * @param File $file: file object
- * @param string $oldimage archive name
- * @param string $reason reason of the deletion
- * @param $suppress Boolean: whether to mark all deleted versions as restricted
- * @param $user User object performing the request
+ * @param Title $title
+ * @param File $file
+ * @param string $oldimage Archive name
+ * @param string $reason Reason of the deletion
+ * @param bool $suppress Whether to mark all deleted versions as restricted
+ * @param User $user User object performing the request
* @throws MWException
* @return bool|Status
*/
- public static function doDelete( &$title, &$file, &$oldimage, $reason, $suppress, User $user = null ) {
+ public static function doDelete( &$title, &$file, &$oldimage, $reason,
+ $suppress, User $user = null
+ ) {
if ( $user === null ) {
global $wgUser;
$user = $wgUser;
@@ -149,7 +163,7 @@ class FileDeleteForm {
if ( $oldimage ) {
$page = null;
- $status = $file->deleteOld( $oldimage, $reason, $suppress );
+ $status = $file->deleteOld( $oldimage, $reason, $suppress, $user );
if ( $status->ok ) {
// Need to do a log item
$logComment = wfMessage( 'deletedrevision', $oldimage )->inContentLanguage()->text();
@@ -180,7 +194,7 @@ class FileDeleteForm {
// doDeleteArticleReal() returns a non-fatal error status if the page
// or revision is missing, so check for isOK() rather than isGood()
if ( $deleteStatus->isOK() ) {
- $status = $file->delete( $reason, $suppress );
+ $status = $file->delete( $reason, $suppress, $user );
if ( $status->isOK() ) {
$dbw->commit( __METHOD__ );
} else {
@@ -188,7 +202,8 @@ class FileDeleteForm {
}
}
} catch ( MWException $e ) {
- // rollback before returning to prevent UI from displaying incorrect "View or restore N deleted edits?"
+ // Rollback before returning to prevent UI from displaying
+ // incorrect "View or restore N deleted edits?"
$dbw->rollback( __METHOD__ );
throw $e;
}
@@ -266,8 +281,14 @@ class FileDeleteForm {
<tr>
<td></td>
<td class='mw-submit'>" .
- Xml::submitButton( wfMessage( 'filedelete-submit' )->text(),
- array( 'name' => 'mw-filedelete-submit', 'id' => 'mw-filedelete-submit', 'tabindex' => '4' ) ) .
+ Xml::submitButton(
+ wfMessage( 'filedelete-submit' )->text(),
+ array(
+ 'name' => 'mw-filedelete-submit',
+ 'id' => 'mw-filedelete-submit',
+ 'tabindex' => '4'
+ )
+ ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ) .
@@ -303,14 +324,16 @@ class FileDeleteForm {
* showing an appropriate message depending upon whether
* it's a current file or an old version
*
- * @param string $message message base
- * @return String
+ * @param string $message Message base
+ * @return string
*/
private function prepareMessage( $message ) {
global $wgLang;
if ( $this->oldimage ) {
+ # Message keys used:
+ # 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old'
return wfMessage(
- "{$message}-old", # To ensure grep will find them: 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old'
+ "{$message}-old",
wfEscapeWikiText( $this->title->getText() ),
$wgLang->date( $this->getTimestamp(), true ),
$wgLang->time( $this->getTimestamp(), true ),
@@ -336,6 +359,7 @@ class FileDeleteForm {
/**
* Is the provided `oldimage` value valid?
*
+ * @param string $oldimage
* @return bool
*/
public static function isValidOldSpec( $oldimage ) {
@@ -349,9 +373,9 @@ class FileDeleteForm {
* value was provided, does it correspond to an
* existing, local, old version of this file?
*
- * @param $file File
- * @param $oldfile File
- * @param $oldimage File
+ * @param File $file
+ * @param File $oldfile
+ * @param File $oldimage
* @return bool
*/
public static function haveDeletableFile( &$file, &$oldfile, $oldimage ) {
diff --git a/includes/ForkController.php b/includes/ForkController.php
index ced45af6..c1765e24 100644
--- a/includes/ForkController.php
+++ b/includes/ForkController.php
@@ -30,11 +30,11 @@
* @ingroup Maintenance
*/
class ForkController {
- var $children = array();
- var $termReceived = false;
- var $flags = 0, $procsToStart = 0;
+ protected $children = array(), $childNumber = 0;
+ protected $termReceived = false;
+ protected $flags = 0, $procsToStart = 0;
- static $restartableSignals = array(
+ protected static $restartableSignals = array(
SIGFPE,
SIGILL,
SIGSEGV,
@@ -137,6 +137,16 @@ class ForkController {
return 'done';
}
+ /**
+ * Get the number of the child currently running. Note, this
+ * is not the pid, but rather which of the total number of children
+ * we are
+ * @return int
+ */
+ public function getChildNumber() {
+ return $this->childNumber;
+ }
+
protected function prepareEnvironment() {
global $wgMemc;
// Don't share DB, storage, or memcached connections
@@ -150,6 +160,7 @@ class ForkController {
/**
* Fork a number of worker processes.
*
+ * @param int $numProcs
* @return string
*/
protected function forkWorkers( $numProcs ) {
@@ -166,6 +177,7 @@ class ForkController {
if ( !$pid ) {
$this->initChild();
+ $this->childNumber = $i;
return 'child';
} else {
// This is the parent process
diff --git a/includes/FormOptions.php b/includes/FormOptions.php
index 54822e32..c91c3367 100644
--- a/includes/FormOptions.php
+++ b/includes/FormOptions.php
@@ -4,6 +4,7 @@
*
* Copyright © 2008, Niklas Laxström
* Copyright © 2011, Antoine Musso
+ * Copyright © 2013, Bartosz Dziewoński
*
* 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
@@ -42,6 +43,9 @@ class FormOptions implements ArrayAccess {
const STRING = 0;
/** Integer type, maps guessType() to WebRequest::getInt() */
const INT = 1;
+ /** Float type, maps guessType() to WebRequest::getFloat()
+ * @since 1.23 */
+ const FLOAT = 4;
/** Boolean type, maps guessType() to WebRequest::getBool() */
const BOOL = 2;
/** Integer type or null, maps to WebRequest::getIntOrNull()
@@ -112,6 +116,8 @@ class FormOptions implements ArrayAccess {
return self::BOOL;
} elseif ( is_int( $data ) ) {
return self::INT;
+ } elseif ( is_float( $data ) ) {
+ return self::FLOAT;
} elseif ( is_string( $data ) ) {
return self::STRING;
} else {
@@ -234,19 +240,29 @@ class FormOptions implements ArrayAccess {
}
/**
- * Validate and set an option integer value
- * The value will be altered to fit in the range.
+ * @see validateBounds()
+ */
+ public function validateIntBounds( $name, $min, $max ) {
+ $this->validateBounds( $name, $min, $max );
+ }
+
+ /**
+ * Constrain a numeric value for a given option to a given range. The value will be altered to fit
+ * in the range.
*
- * @param string $name option name
- * @param int $min minimum value
- * @param int $max maximum value
+ * @since 1.23
+ *
+ * @param string $name Option name
+ * @param int|float $min Minimum value
+ * @param int|float $max Maximum value
* @throws MWException If option is not of type INT
*/
- public function validateIntBounds( $name, $min, $max ) {
+ public function validateBounds( $name, $min, $max ) {
$this->validateName( $name, true );
+ $type = $this->options[$name]['type'];
- if ( $this->options[$name]['type'] !== self::INT ) {
- throw new MWException( "Option $name is not of type int" );
+ if ( $type !== self::INT && $type !== self::FLOAT ) {
+ throw new MWException( "Option $name is not of type INT or FLOAT" );
}
$value = $this->getValueReal( $this->options[$name] );
@@ -333,6 +349,9 @@ class FormOptions implements ArrayAccess {
case self::INT:
$value = $r->getInt( $name, $default );
break;
+ case self::FLOAT:
+ $value = $r->getFloat( $name, $default );
+ break;
case self::STRING:
$value = $r->getText( $name, $default );
break;
@@ -354,22 +373,37 @@ class FormOptions implements ArrayAccess {
* @see http://php.net/manual/en/class.arrayaccess.php
*/
/* @{ */
- /** Whether the option exists. */
+ /**
+ * Whether the option exists.
+ * @param string $name
+ * @return bool
+ */
public function offsetExists( $name ) {
return isset( $this->options[$name] );
}
- /** Retrieve an option value. */
+ /**
+ * Retrieve an option value.
+ * @param string $name
+ * @return mixed
+ */
public function offsetGet( $name ) {
return $this->getValue( $name );
}
- /** Set an option to given value. */
+ /**
+ * Set an option to given value.
+ * @param string $name
+ * @param mixed $value
+ */
public function offsetSet( $name, $value ) {
$this->setValue( $name, $value );
}
- /** Delete the option. */
+ /**
+ * Delete the option.
+ * @param string $name
+ */
public function offsetUnset( $name ) {
$this->delete( $name );
}
diff --git a/includes/GitInfo.php b/includes/GitInfo.php
index f49f9be1..7052820e 100644
--- a/includes/GitInfo.php
+++ b/includes/GitInfo.php
@@ -36,32 +36,102 @@ class GitInfo {
protected $basedir;
/**
+ * Path to JSON cache file for pre-computed git information.
+ */
+ protected $cacheFile;
+
+ /**
+ * Cached git information.
+ */
+ protected $cache = array();
+
+ /**
* Map of repo URLs to viewer URLs. Access via static method getViewers().
*/
private static $viewers = false;
/**
- * @param string $dir The root directory of the repo where the .git dir can be found
+ * @param string $repoDir The root directory of the repo where .git can be found
+ * @param bool $usePrecomputed Use precomputed information if available
+ * @see precomputeValues
+ */
+ public function __construct( $repoDir, $usePrecomputed = true ) {
+ $this->cacheFile = self::getCacheFilePath( $repoDir );
+ wfDebugLog( 'gitinfo',
+ "Computed cacheFile={$this->cacheFile} for {$repoDir}"
+ );
+ if ( $usePrecomputed &&
+ $this->cacheFile !== null &&
+ is_readable( $this->cacheFile )
+ ) {
+ $this->cache = FormatJson::decode(
+ file_get_contents( $this->cacheFile ),
+ true
+ );
+ wfDebugLog( 'gitinfo', "Loaded git data from cache for {$repoDir}" );
+ }
+
+ if ( !$this->cacheIsComplete() ) {
+ wfDebugLog( 'gitinfo', "Cache incomplete for {$repoDir}" );
+ $this->basedir = $repoDir . DIRECTORY_SEPARATOR . '.git';
+ if ( is_readable( $this->basedir ) && !is_dir( $this->basedir ) ) {
+ $GITfile = file_get_contents( $this->basedir );
+ if ( strlen( $GITfile ) > 8 &&
+ substr( $GITfile, 0, 8 ) === 'gitdir: '
+ ) {
+ $path = rtrim( substr( $GITfile, 8 ), "\r\n" );
+ if ( $path[0] === '/' || substr( $path, 1, 1 ) === ':' ) {
+ // Path from GITfile is absolute
+ $this->basedir = $path;
+ } else {
+ $this->basedir = $repoDir . DIRECTORY_SEPARATOR . $path;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Compute the path to the cache file for a given directory.
+ *
+ * @param string $repoDir The root directory of the repo where .git can be found
+ * @return string Path to GitInfo cache file in $wgGitInfoCacheDirectory or
+ * null if $wgGitInfoCacheDirectory is false (cache disabled).
+ * @since 1.24
*/
- public function __construct( $dir ) {
- $this->basedir = "{$dir}/.git";
- if ( is_readable( $this->basedir ) && !is_dir( $this->basedir ) ) {
- $GITfile = file_get_contents( $this->basedir );
- if ( strlen( $GITfile ) > 8 && substr( $GITfile, 0, 8 ) === 'gitdir: ' ) {
- $path = rtrim( substr( $GITfile, 8 ), "\r\n" );
- $isAbsolute = $path[0] === '/' || substr( $path, 1, 1 ) === ':';
- $this->basedir = $isAbsolute ? $path : "{$dir}/{$path}";
+ protected static function getCacheFilePath( $repoDir ) {
+ global $IP, $wgGitInfoCacheDirectory;
+
+ if ( $wgGitInfoCacheDirectory ) {
+ // Convert both $IP and $repoDir to canonical paths to protect against
+ // $IP having changed between the settings files and runtime.
+ $realIP = realpath( $IP );
+ $repoName = realpath( $repoDir );
+ if ( $repoName === false ) {
+ // Unit tests use fake path names
+ $repoName = $repoDir;
+ }
+ if ( strpos( $repoName, $realIP ) === 0 ) {
+ // Strip $IP from path
+ $repoName = substr( $repoName, strlen( $realIP ) );
}
+ // Transform path to git repo to something we can safely embed in
+ // a filename
+ $repoName = strtr( $repoName, DIRECTORY_SEPARATOR, '-' );
+ $fileName = 'info' . $repoName . '.json';
+ return "{$wgGitInfoCacheDirectory}/{$fileName}";
}
+ return null;
}
/**
- * Return a singleton for the repo at $IP
+ * Get the singleton for the repo at $IP
+ *
* @return GitInfo
*/
public static function repo() {
- global $IP;
if ( is_null( self::$repo ) ) {
+ global $IP;
self::$repo = new self( $IP );
}
return self::$repo;
@@ -78,50 +148,56 @@ class GitInfo {
}
/**
- * Return the HEAD of the repo (without any opening "ref: ")
- * @return string The HEAD
+ * Get the HEAD of the repo (without any opening "ref: ")
+ *
+ * @return string|bool The HEAD (git reference or SHA1) or false
*/
public function getHead() {
- $HEADfile = "{$this->basedir}/HEAD";
-
- if ( !is_readable( $HEADfile ) ) {
- return false;
- }
+ if ( !isset( $this->cache['head'] ) ) {
+ $headFile = "{$this->basedir}/HEAD";
+ $head = false;
- $HEAD = file_get_contents( $HEADfile );
+ if ( is_readable( $headFile ) ) {
+ $head = file_get_contents( $headFile );
- if ( preg_match( "/ref: (.*)/", $HEAD, $m ) ) {
- return rtrim( $m[1] );
- } else {
- return rtrim( $HEAD );
+ if ( preg_match( "/ref: (.*)/", $head, $m ) ) {
+ $head = rtrim( $m[1] );
+ } else {
+ $head = rtrim( $head );
+ }
+ }
+ $this->cache['head'] = $head;
}
+ return $this->cache['head'];
}
/**
- * Return the SHA1 for the current HEAD of the repo
- * @return string A SHA1 or false
+ * Get the SHA1 for the current HEAD of the repo
+ *
+ * @return string|bool A SHA1 or false
*/
public function getHeadSHA1() {
- $HEAD = $this->getHead();
-
- // If detached HEAD may be a SHA1
- if ( self::isSHA1( $HEAD ) ) {
- return $HEAD;
- }
-
- // If not a SHA1 it may be a ref:
- $REFfile = "{$this->basedir}/{$HEAD}";
- if ( !is_readable( $REFfile ) ) {
- return false;
+ if ( !isset( $this->cache['headSHA1'] ) ) {
+ $head = $this->getHead();
+ $sha1 = false;
+
+ // If detached HEAD may be a SHA1
+ if ( self::isSHA1( $head ) ) {
+ $sha1 = $head;
+ } else {
+ // If not a SHA1 it may be a ref:
+ $refFile = "{$this->basedir}/{$head}";
+ if ( is_readable( $refFile ) ) {
+ $sha1 = rtrim( file_get_contents( $refFile ) );
+ }
+ }
+ $this->cache['headSHA1'] = $sha1;
}
-
- $sha1 = rtrim( file_get_contents( $REFfile ) );
-
- return $sha1;
+ return $this->cache['headSHA1'];
}
/**
- * Return the commit date of HEAD entry of the git code repository
+ * Get the commit date of HEAD entry of the git code repository
*
* @since 1.22
* @return int|bool Commit date (UNIX timestamp) or false
@@ -129,66 +205,54 @@ class GitInfo {
public function getHeadCommitDate() {
global $wgGitBin;
- if ( !is_file( $wgGitBin ) || !is_executable( $wgGitBin ) ) {
- return false;
- }
-
- $environment = array( "GIT_DIR" => $this->basedir );
- $cmd = wfEscapeShellArg( $wgGitBin ) . " show -s --format=format:%ct HEAD";
- $retc = false;
- $commitDate = wfShellExec( $cmd, $retc, $environment );
-
- if ( $retc !== 0 ) {
- return false;
- } else {
- return (int)$commitDate;
+ if ( !isset( $this->cache['headCommitDate'] ) ) {
+ $date = false;
+ if ( is_file( $wgGitBin ) &&
+ is_executable( $wgGitBin ) &&
+ $this->getHead() !== false
+ ) {
+ $environment = array( "GIT_DIR" => $this->basedir );
+ $cmd = wfEscapeShellArg( $wgGitBin ) .
+ " show -s --format=format:%ct HEAD";
+ $retc = false;
+ $commitDate = wfShellExec( $cmd, $retc, $environment );
+ if ( $retc === 0 ) {
+ $date = (int)$commitDate;
+ }
+ }
+ $this->cache['headCommitDate'] = $date;
}
-
- }
+ return $this->cache['headCommitDate'];
+ }
/**
- * Return the name of the current branch, or HEAD if not found
- * @return string The branch name, HEAD, or false
+ * Get the name of the current branch, or HEAD if not found
+ *
+ * @return string|bool The branch name, HEAD, or false
*/
public function getCurrentBranch() {
- $HEAD = $this->getHead();
- if ( $HEAD && preg_match( "#^refs/heads/(.*)$#", $HEAD, $m ) ) {
- return $m[1];
- } else {
- return $HEAD;
+ if ( !isset( $this->cache['branch'] ) ) {
+ $branch = $this->getHead();
+ if ( $branch &&
+ preg_match( "#^refs/heads/(.*)$#", $branch, $m )
+ ) {
+ $branch = $m[1];
+ }
+ $this->cache['branch'] = $branch;
}
+ return $this->cache['branch'];
}
/**
* Get an URL to a web viewer link to the HEAD revision.
*
- * @return string|bool string if a URL is available or false otherwise.
+ * @return string|bool String if a URL is available or false otherwise
*/
public function getHeadViewUrl() {
- $config = "{$this->basedir}/config";
- if ( !is_readable( $config ) ) {
- return false;
- }
-
- $configArray = parse_ini_file( $config, true );
- $remote = false;
-
- // Use the "origin" remote repo if available or any other repo if not.
- if ( isset( $configArray['remote origin'] ) ) {
- $remote = $configArray['remote origin'];
- } else {
- foreach ( $configArray as $sectionName => $sectionConf ) {
- if ( substr( $sectionName, 0, 6 ) == 'remote' ) {
- $remote = $sectionConf;
- }
- }
- }
-
- if ( $remote === false || !isset( $remote['url'] ) ) {
+ $url = $this->getRemoteUrl();
+ if ( $url === false ) {
return false;
}
-
- $url = $remote['url'];
if ( substr( $url, -4 ) !== '.git' ) {
$url .= '.git';
}
@@ -209,6 +273,93 @@ class GitInfo {
}
/**
+ * Get the URL of the remote origin.
+ * @return string|bool String if a URL is available or false otherwise.
+ */
+ protected function getRemoteUrl() {
+ if ( !isset( $this->cache['remoteURL'] ) ) {
+ $config = "{$this->basedir}/config";
+ $url = false;
+ if ( is_readable( $config ) ) {
+ wfSuppressWarnings();
+ $configArray = parse_ini_file( $config, true );
+ wfRestoreWarnings();
+ $remote = false;
+
+ // Use the "origin" remote repo if available or any other repo if not.
+ if ( isset( $configArray['remote origin'] ) ) {
+ $remote = $configArray['remote origin'];
+ } elseif ( is_array( $configArray ) ) {
+ foreach ( $configArray as $sectionName => $sectionConf ) {
+ if ( substr( $sectionName, 0, 6 ) == 'remote' ) {
+ $remote = $sectionConf;
+ }
+ }
+ }
+
+ if ( $remote !== false && isset( $remote['url'] ) ) {
+ $url = $remote['url'];
+ }
+ }
+ $this->cache['remoteURL'] = $url;
+ }
+ return $this->cache['remoteURL'];
+ }
+
+ /**
+ * Check to see if the current cache is fully populated.
+ *
+ * Note: This method is public only to make unit testing easier. There's
+ * really no strong reason that anything other than a test should want to
+ * call this method.
+ *
+ * @return bool True if all expected cache keys exist, false otherwise
+ */
+ public function cacheIsComplete() {
+ return isset( $this->cache['head'] ) &&
+ isset( $this->cache['headSHA1'] ) &&
+ isset( $this->cache['headCommitDate'] ) &&
+ isset( $this->cache['branch'] ) &&
+ isset( $this->cache['remoteURL'] );
+ }
+
+ /**
+ * Precompute and cache git information.
+ *
+ * Creates a JSON file in the cache directory associated with this
+ * GitInfo instance. This cache file will be used by subsequent GitInfo objects referencing
+ * the same directory to avoid needing to examine the .git directory again.
+ *
+ * @since 1.24
+ */
+ public function precomputeValues() {
+ if ( $this->cacheFile !== null ) {
+ // Try to completely populate the cache
+ $this->getHead();
+ $this->getHeadSHA1();
+ $this->getHeadCommitDate();
+ $this->getCurrentBranch();
+ $this->getRemoteUrl();
+
+ if ( !$this->cacheIsComplete() ) {
+ wfDebugLog( 'gitinfo',
+ "Failed to compute GitInfo for \"{$this->basedir}\""
+ );
+ return;
+ }
+
+ $cacheDir = dirname( $this->cacheFile );
+ if ( !file_exists( $cacheDir ) &&
+ !wfMkdirParents( $cacheDir, null, __METHOD__ )
+ ) {
+ throw new MWException( "Unable to create GitInfo cache \"{$cacheDir}\"" );
+ }
+
+ file_put_contents( $this->cacheFile, FormatJson::encode( $this->cache ) );
+ }
+ }
+
+ /**
* @see self::getHeadSHA1
* @return string
*/
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 77c09e53..27f7cacb 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -35,19 +35,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* PHP extensions may be included here.
*/
-if ( !function_exists( 'iconv' ) ) {
- /**
- * @codeCoverageIgnore
- * @return string
- */
- function iconv( $from, $to, $string ) {
- return Fallback::iconv( $from, $to, $string );
- }
-}
-
if ( !function_exists( 'mb_substr' ) ) {
/**
* @codeCoverageIgnore
+ * @see Fallback::mb_substr
* @return string
*/
function mb_substr( $str, $start, $count = 'end' ) {
@@ -56,6 +47,7 @@ if ( !function_exists( 'mb_substr' ) ) {
/**
* @codeCoverageIgnore
+ * @see Fallback::mb_substr_split_unicode
* @return int
*/
function mb_substr_split_unicode( $str, $splitPos ) {
@@ -66,6 +58,7 @@ if ( !function_exists( 'mb_substr' ) ) {
if ( !function_exists( 'mb_strlen' ) ) {
/**
* @codeCoverageIgnore
+ * @see Fallback::mb_strlen
* @return int
*/
function mb_strlen( $str, $enc = '' ) {
@@ -76,17 +69,18 @@ if ( !function_exists( 'mb_strlen' ) ) {
if ( !function_exists( 'mb_strpos' ) ) {
/**
* @codeCoverageIgnore
+ * @see Fallback::mb_strpos
* @return int
*/
function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
return Fallback::mb_strpos( $haystack, $needle, $offset, $encoding );
}
-
}
if ( !function_exists( 'mb_strrpos' ) ) {
/**
* @codeCoverageIgnore
+ * @see Fallback::mb_strrpos
* @return int
*/
function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
@@ -99,18 +93,77 @@ if ( !function_exists( 'mb_strrpos' ) ) {
if ( !function_exists( 'gzdecode' ) ) {
/**
* @codeCoverageIgnore
+ * @param string $data
* @return string
*/
function gzdecode( $data ) {
return gzinflate( substr( $data, 10, -8 ) );
}
}
+
+// hash_equals function only exists in PHP >= 5.6.0
+// http://php.net/hash_equals
+if ( !function_exists( 'hash_equals' ) ) {
+ /**
+ * Check whether a user-provided string is equal to a fixed-length secret string
+ * without revealing bytes of the secret string through timing differences.
+ *
+ * The usual way to compare strings (PHP's === operator or the underlying memcmp()
+ * function in C) is to compare corresponding bytes and stop at the first difference,
+ * which would take longer for a partial match than for a complete mismatch. This
+ * is not secure when one of the strings (e.g. an HMAC or token) must remain secret
+ * and the other may come from an attacker. Statistical analysis of timing measurements
+ * over many requests may allow the attacker to guess the string's bytes one at a time
+ * (and check his guesses) even if the timing differences are extremely small.
+ *
+ * When making such a security-sensitive comparison, it is essential that the sequence
+ * in which instructions are executed and memory locations are accessed not depend on
+ * the secret string's value. HOWEVER, for simplicity, we do not attempt to minimize
+ * the inevitable leakage of the string's length. That is generally known anyway as
+ * a chararacteristic of the hash function used to compute the secret value.
+ *
+ * Longer explanation: http://www.emerose.com/timing-attacks-explained
+ *
+ * @codeCoverageIgnore
+ * @param string $known_string Fixed-length secret string to compare against
+ * @param string $user_string User-provided string
+ * @return bool True if the strings are the same, false otherwise
+ */
+ function hash_equals( $known_string, $user_string ) {
+ // Strict type checking as in PHP's native implementation
+ if ( !is_string( $known_string ) ) {
+ trigger_error( 'hash_equals(): Expected known_string to be a string, ' .
+ gettype( $known_string ) . ' given', E_USER_WARNING );
+
+ return false;
+ }
+
+ if ( !is_string( $user_string ) ) {
+ trigger_error( 'hash_equals(): Expected user_string to be a string, ' .
+ gettype( $user_string ) . ' given', E_USER_WARNING );
+
+ return false;
+ }
+
+ $known_string_len = strlen( $known_string );
+ if ( $known_string_len !== strlen( $user_string ) ) {
+ return false;
+ }
+
+ $result = 0;
+ for ( $i = 0; $i < $known_string_len; $i++ ) {
+ $result |= ord( $known_string[$i] ) ^ ord( $user_string[$i] );
+ }
+
+ return ( $result === 0 );
+ }
+}
/// @endcond
/**
* Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
- * @param $a array
- * @param $b array
+ * @param array $a
+ * @param array $b
* @return array
*/
function wfArrayDiff2( $a, $b ) {
@@ -118,8 +171,8 @@ function wfArrayDiff2( $a, $b ) {
}
/**
- * @param $a array|string
- * @param $b array|string
+ * @param array|string $a
+ * @param array|string $b
* @return int
*/
function wfArrayDiff2_cmp( $a, $b ) {
@@ -141,27 +194,12 @@ function wfArrayDiff2_cmp( $a, $b ) {
}
/**
- * Array lookup
- * Returns an array where the values in array $b are replaced by the
- * values in array $a with the corresponding keys
- *
- * @deprecated since 1.22; use array_intersect_key()
- * @param $a Array
- * @param $b Array
- * @return array
- */
-function wfArrayLookup( $a, $b ) {
- wfDeprecated( __FUNCTION__, '1.22' );
- return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
-}
-
-/**
* Appends to second array if $value differs from that in $default
*
- * @param $key String|Int
- * @param $value Mixed
- * @param $default Mixed
- * @param array $changed to alter
+ * @param string|int $key
+ * @param mixed $value
+ * @param mixed $default
+ * @param array $changed Array to alter
* @throws MWException
*/
function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
@@ -174,26 +212,6 @@ function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
}
/**
- * Backwards array plus for people who haven't bothered to read the PHP manual
- * XXX: will not darn your socks for you.
- *
- * @deprecated since 1.22; use array_replace()
- * @param $array1 Array
- * @param [$array2, [...]] Arrays
- * @return Array
- */
-function wfArrayMerge( $array1/* ... */ ) {
- wfDeprecated( __FUNCTION__, '1.22' );
- $args = func_get_args();
- $args = array_reverse( $args, true );
- $out = array();
- foreach ( $args as $arg ) {
- $out += $arg;
- }
- return $out;
-}
-
-/**
* Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
* e.g.
* wfMergeErrorArrays(
@@ -208,8 +226,9 @@ function wfArrayMerge( $array1/* ... */ ) {
* array( 'x' ),
* array( 'y' )
* )
- * @param varargs
- * @return Array
+ *
+ * @param array $array1,...
+ * @return array
*/
function wfMergeErrorArrays( /*...*/ ) {
$args = func_get_args();
@@ -230,8 +249,8 @@ function wfMergeErrorArrays( /*...*/ ) {
*
* @param array $array The array.
* @param array $insert The array to insert.
- * @param $after Mixed: The key to insert after
- * @return Array
+ * @param mixed $after The key to insert after
+ * @return array
*/
function wfArrayInsertAfter( array $array, array $insert, $after ) {
// Find the offset of the element to insert after.
@@ -252,9 +271,9 @@ function wfArrayInsertAfter( array $array, array $insert, $after ) {
/**
* Recursively converts the parameter (an object) to an array with the same data
*
- * @param $objOrArray Object|Array
- * @param $recursive Bool
- * @return Array
+ * @param object|array $objOrArray
+ * @param bool $recursive
+ * @return array
*/
function wfObjectToArray( $objOrArray, $recursive = true ) {
$array = array();
@@ -283,8 +302,8 @@ function wfRandom() {
# The maximum random value is "only" 2^31-1, so get two random
# values to reduce the chance of dupes
$max = mt_getrandmax() + 1;
- $rand = number_format( ( mt_rand() * $max + mt_rand() )
- / $max / $max, 12, '.', '' );
+ $rand = number_format( ( mt_rand() * $max + mt_rand() ) / $max / $max, 12, '.', '' );
+
return $rand;
}
@@ -295,7 +314,7 @@ function wfRandom() {
* of token please use MWCryptRand instead.
*
* @param int $length The length of the string to generate
- * @return String
+ * @return string
* @since 1.20
*/
function wfRandomString( $length = 32 ) {
@@ -325,11 +344,12 @@ function wfRandomString( $length = 32 ) {
*
* %2F in the page titles seems to fatally break for some reason.
*
- * @param $s String:
+ * @param string $s
* @return string
*/
function wfUrlencode( $s ) {
static $needle;
+
if ( is_null( $s ) ) {
$needle = null;
return '';
@@ -337,7 +357,9 @@ function wfUrlencode( $s ) {
if ( is_null( $needle ) ) {
$needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F' );
- if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) || ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false ) ) {
+ if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) ||
+ ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false )
+ ) {
$needle[] = '%3A';
}
}
@@ -359,8 +381,8 @@ function wfUrlencode( $s ) {
*
* @param array $array1 ( String|Array )
* @param array $array2 ( String|Array )
- * @param $prefix String
- * @return String
+ * @param string $prefix
+ * @return string
*/
function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
if ( !is_null( $array2 ) ) {
@@ -404,8 +426,8 @@ function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
* with legacy functions that accept raw query strings instead of nice
* arrays. Of course, keys and values are urldecode()d.
*
- * @param string $query query string
- * @return array Array version of input
+ * @param string $query Query string
+ * @return string[] Array version of input
*/
function wfCgiToArray( $query ) {
if ( isset( $query[0] ) && $query[0] == '?' ) {
@@ -450,8 +472,8 @@ function wfCgiToArray( $query ) {
* Append a query string to an existing URL, which may or may not already
* have query string parameters already. If so, they will be combined.
*
- * @param $url String
- * @param $query Mixed: string or associative array
+ * @param string $url
+ * @param string|string[] $query String or associative array
* @return string
*/
function wfAppendQuery( $url, $query ) {
@@ -470,39 +492,41 @@ function wfAppendQuery( $url, $query ) {
}
/**
- * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
+ * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
* is correct.
*
* The meaning of the PROTO_* constants is as follows:
* PROTO_HTTP: Output a URL starting with http://
* PROTO_HTTPS: Output a URL starting with https://
* PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
- * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending on which protocol was used for the current incoming request
- * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer. For protocol-relative URLs, use the protocol of $wgCanonicalServer
+ * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending
+ * on which protocol was used for the current incoming request
+ * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer.
+ * For protocol-relative URLs, use the protocol of $wgCanonicalServer
* PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
*
* @todo this won't work with current-path-relative URLs
* like "subdir/foo.html", etc.
*
- * @param string $url either fully-qualified or a local path + query
- * @param $defaultProto Mixed: one of the PROTO_* constants. Determines the
- * protocol to use if $url or $wgServer is
- * protocol-relative
+ * @param string $url Either fully-qualified or a local path + query
+ * @param string $defaultProto One of the PROTO_* constants. Determines the
+ * protocol to use if $url or $wgServer is protocol-relative
* @return string Fully-qualified URL, current-path-relative URL or false if
- * no valid URL can be constructed
+ * no valid URL can be constructed
*/
function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
- global $wgServer, $wgCanonicalServer, $wgInternalServer;
- $serverUrl = $wgServer;
+ global $wgServer, $wgCanonicalServer, $wgInternalServer, $wgRequest,
+ $wgHttpsPort;
if ( $defaultProto === PROTO_CANONICAL ) {
$serverUrl = $wgCanonicalServer;
- }
- // Make $wgInternalServer fall back to $wgServer if not set
- if ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
+ } elseif ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
+ // Make $wgInternalServer fall back to $wgServer if not set
$serverUrl = $wgInternalServer;
- }
- if ( $defaultProto === PROTO_CURRENT ) {
- $defaultProto = WebRequest::detectProtocol() . '://';
+ } else {
+ $serverUrl = $wgServer;
+ if ( $defaultProto === PROTO_CURRENT ) {
+ $defaultProto = $wgRequest->getProtocol() . '://';
+ }
}
// Analyze $serverUrl to obtain its protocol
@@ -513,8 +537,9 @@ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
if ( $serverHasProto ) {
$defaultProto = $bits['scheme'] . '://';
} else {
- // $wgCanonicalServer or $wgInternalServer doesn't have a protocol. This really isn't supposed to happen
- // Fall back to HTTP in this ridiculous case
+ // $wgCanonicalServer or $wgInternalServer doesn't have a protocol.
+ // This really isn't supposed to happen. Fall back to HTTP in this
+ // ridiculous case.
$defaultProto = PROTO_HTTP;
}
}
@@ -524,11 +549,19 @@ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
if ( substr( $url, 0, 2 ) == '//' ) {
$url = $defaultProtoWithoutSlashes . $url;
} elseif ( substr( $url, 0, 1 ) == '/' ) {
- // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes, otherwise leave it alone
+ // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes,
+ // otherwise leave it alone.
$url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
}
$bits = wfParseUrl( $url );
+
+ // ensure proper port for HTTPS arrives in URL
+ // https://bugzilla.wikimedia.org/show_bug.cgi?id=65184
+ if ( $defaultProto === PROTO_HTTPS && $wgHttpsPort != 443 ) {
+ $bits['port'] = $wgHttpsPort;
+ }
+
if ( $bits && isset( $bits['path'] ) ) {
$bits['path'] = wfRemoveDotSegments( $bits['path'] );
return wfAssembleUrl( $bits );
@@ -685,7 +718,7 @@ function wfRemoveDotSegments( $urlPath ) {
*
* @param bool $includeProtocolRelative If false, remove '//' from the returned protocol list.
* DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
- * @return String
+ * @return string
*/
function wfUrlProtocols( $includeProtocolRelative = true ) {
global $wgUrlProtocols;
@@ -730,7 +763,7 @@ function wfUrlProtocols( $includeProtocolRelative = true ) {
* Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
* you need a regex that matches all URL protocols but does not match protocol-
* relative URLs
- * @return String
+ * @return string
*/
function wfUrlProtocolsWithoutProtRel() {
return wfUrlProtocols( false );
@@ -739,18 +772,20 @@ function wfUrlProtocolsWithoutProtRel() {
/**
* parse_url() work-alike, but non-broken. Differences:
*
- * 1) Does not raise warnings on bad URLs (just returns false)
- * 2) Handles protocols that don't use :// (e.g., mailto: and news: , as well as protocol-relative URLs) correctly
- * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2))
+ * 1) Does not raise warnings on bad URLs (just returns false).
+ * 2) Handles protocols that don't use :// (e.g., mailto: and news:, as well as
+ * protocol-relative URLs) correctly.
+ * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2)).
*
- * @param string $url a URL to parse
- * @return Array: bits of the URL in an associative array, per PHP docs
+ * @param string $url A URL to parse
+ * @return string[] Bits of the URL in an associative array, per PHP docs
*/
function wfParseUrl( $url ) {
global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
- // Protocol-relative URLs are handled really badly by parse_url(). It's so bad that the easiest
- // way to handle them is to just prepend 'http:' and strip the protocol out later
+ // Protocol-relative URLs are handled really badly by parse_url(). It's so
+ // bad that the easiest way to handle them is to just prepend 'http:' and
+ // strip the protocol out later.
$wasRelative = substr( $url, 0, 2 ) == '//';
if ( $wasRelative ) {
$url = "http:$url";
@@ -812,11 +847,15 @@ function wfParseUrl( $url ) {
*
* @todo handle punycode domains too
*
- * @param $url string
+ * @param string $url
* @return string
*/
function wfExpandIRI( $url ) {
- return preg_replace_callback( '/((?:%[89A-F][0-9A-F])+)/i', 'wfExpandIRI_callback', wfExpandUrl( $url ) );
+ return preg_replace_callback(
+ '/((?:%[89A-F][0-9A-F])+)/i',
+ 'wfExpandIRI_callback',
+ wfExpandUrl( $url )
+ );
}
/**
@@ -831,7 +870,7 @@ function wfExpandIRI_callback( $matches ) {
/**
* Make URL indexes, appropriate for the el_index field of externallinks.
*
- * @param $url String
+ * @param string $url
* @return array
*/
function wfMakeUrlIndexes( $url ) {
@@ -908,30 +947,41 @@ function wfMatchesDomainList( $url, $domains ) {
*
* Controlling globals:
* $wgDebugLogFile - points to the log file
- * $wgProfileOnly - if set, normal debug messages will not be recorded.
* $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
* $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
*
- * @param $text String
- * @param bool $logonly set true to avoid appearing in HTML when $wgDebugComments is set
+ * @param string $text
+ * @param string|bool $dest Destination of the message:
+ * - 'all': both to the log and HTML (debug toolbar or HTML comments)
+ * - 'log': only to the log and not in HTML
+ * For backward compatibility, it can also take a boolean:
+ * - true: same as 'all'
+ * - false: same as 'log'
*/
-function wfDebug( $text, $logonly = false ) {
- global $wgDebugLogFile, $wgProfileOnly, $wgDebugRawPage, $wgDebugLogPrefix;
+function wfDebug( $text, $dest = 'all' ) {
+ global $wgDebugLogFile, $wgDebugRawPage, $wgDebugLogPrefix;
if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
return;
}
+ // Turn $dest into a string if it's a boolean (for b/c)
+ if ( $dest === true ) {
+ $dest = 'all';
+ } elseif ( $dest === false ) {
+ $dest = 'log';
+ }
+
$timer = wfDebugTimer();
if ( $timer !== '' ) {
$text = preg_replace( '/[^\n]/', $timer . '\0', $text, 1 );
}
- if ( !$logonly ) {
+ if ( $dest === 'all' ) {
MWDebug::debugMsg( $text );
}
- if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
+ if ( $wgDebugLogFile != '' ) {
# Strip unprintables; they can switch terminal modes when binary data
# gets dumped, which is pretty annoying.
$text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
@@ -954,8 +1004,8 @@ function wfIsDebugRawPage() {
|| (
isset( $_SERVER['SCRIPT_NAME'] )
&& substr( $_SERVER['SCRIPT_NAME'], -8 ) == 'load.php'
- ) )
- {
+ )
+ ) {
$cache = true;
} else {
$cache = false;
@@ -983,44 +1033,84 @@ function wfDebugTimer() {
/**
* Send a line giving PHP memory usage.
*
- * @param bool $exact print exact values instead of kilobytes (default: false)
+ * @param bool $exact Print exact byte values instead of kibibytes (default: false)
*/
function wfDebugMem( $exact = false ) {
$mem = memory_get_usage();
if ( !$exact ) {
- $mem = floor( $mem / 1024 ) . ' kilobytes';
+ $mem = floor( $mem / 1024 ) . ' KiB';
} else {
- $mem .= ' bytes';
+ $mem .= ' B';
}
wfDebug( "Memory usage: $mem\n" );
}
/**
* Send a line to a supplementary debug log file, if configured, or main debug log if not.
- * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
- *
- * @param $logGroup String
- * @param $text String
- * @param bool $public whether to log the event in the public log if no private
- * log file is specified, (default true)
- */
-function wfDebugLog( $logGroup, $text, $public = true ) {
+ * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to a string
+ * filename or an associative array mapping 'destination' to the desired filename. The
+ * associative array may also contain a 'sample' key with an integer value, specifying
+ * a sampling factor.
+ *
+ * @since 1.23 support for sampling log messages via $wgDebugLogGroups.
+ *
+ * @param string $logGroup
+ * @param string $text
+ * @param string|bool $dest Destination of the message:
+ * - 'all': both to the log and HTML (debug toolbar or HTML comments)
+ * - 'log': only to the log and not in HTML
+ * - 'private': only to the specifc log if set in $wgDebugLogGroups and
+ * discarded otherwise
+ * For backward compatibility, it can also take a boolean:
+ * - true: same as 'all'
+ * - false: same as 'private'
+ */
+function wfDebugLog( $logGroup, $text, $dest = 'all' ) {
global $wgDebugLogGroups;
+
$text = trim( $text ) . "\n";
- if ( isset( $wgDebugLogGroups[$logGroup] ) ) {
- $time = wfTimestamp( TS_DB );
- $wiki = wfWikiID();
- $host = wfHostname();
- wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
- } elseif ( $public === true ) {
- wfDebug( "[$logGroup] $text", false );
+
+ // Turn $dest into a string if it's a boolean (for b/c)
+ if ( $dest === true ) {
+ $dest = 'all';
+ } elseif ( $dest === false ) {
+ $dest = 'private';
+ }
+
+ if ( !isset( $wgDebugLogGroups[$logGroup] ) ) {
+ if ( $dest !== 'private' ) {
+ wfDebug( "[$logGroup] $text", $dest );
+ }
+ return;
+ }
+
+ if ( $dest === 'all' ) {
+ MWDebug::debugMsg( "[$logGroup] $text" );
+ }
+
+ $logConfig = $wgDebugLogGroups[$logGroup];
+ if ( $logConfig === false ) {
+ return;
+ }
+ if ( is_array( $logConfig ) ) {
+ if ( isset( $logConfig['sample'] ) && mt_rand( 1, $logConfig['sample'] ) !== 1 ) {
+ return;
+ }
+ $destination = $logConfig['destination'];
+ } else {
+ $destination = strval( $logConfig );
}
+
+ $time = wfTimestamp( TS_DB );
+ $wiki = wfWikiID();
+ $host = wfHostname();
+ wfErrorLog( "$time $host $wiki: $text", $destination );
}
/**
* Log for database errors
*
- * @param string $text database error message.
+ * @param string $text Database error message.
*/
function wfLogDBError( $text ) {
global $wgDBerrorLog, $wgDBerrorLogTZ;
@@ -1044,7 +1134,7 @@ function wfLogDBError( $text ) {
$date = $d->format( 'D M j G:i:s T Y' );
- $text = "$date\t$host\t$wiki\t$text";
+ $text = "$date\t$host\t$wiki\t" . trim( $text ) . "\n";
wfErrorLog( $text, $wgDBerrorLog );
}
}
@@ -1052,10 +1142,11 @@ function wfLogDBError( $text ) {
/**
* Throws a warning that $function is deprecated
*
- * @param $function String
- * @param string|bool $version Version of MediaWiki that the function was deprecated in (Added in 1.19).
+ * @param string $function
+ * @param string|bool $version Version of MediaWiki that the function
+ * was deprecated in (Added in 1.19).
* @param string|bool $component Added in 1.19.
- * @param $callerOffset integer: How far up the call stack is the original
+ * @param int $callerOffset How far up the call stack is the original
* caller. 2 = function that called the function that called
* wfDeprecated (Added in 1.20)
*
@@ -1069,10 +1160,10 @@ function wfDeprecated( $function, $version = false, $component = false, $callerO
* Send a warning either to the debug log or in a PHP error depending on
* $wgDevelopmentWarnings. To log warnings in production, use wfLogWarning() instead.
*
- * @param string $msg message to send
- * @param $callerOffset Integer: number of items to go back in the backtrace to
+ * @param string $msg Message to send
+ * @param int $callerOffset Number of items to go back in the backtrace to
* find the correct caller (1 = function calling wfWarn, ...)
- * @param $level Integer: PHP error level; defaults to E_USER_NOTICE;
+ * @param int $level PHP error level; defaults to E_USER_NOTICE;
* only used when $wgDevelopmentWarnings is true
*/
function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
@@ -1083,10 +1174,10 @@ function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
* Send a warning as a PHP error and the debug log. This is intended for logging
* warnings in production. For logging development warnings, use WfWarn instead.
*
- * @param $msg String: message to send
- * @param $callerOffset Integer: number of items to go back in the backtrace to
+ * @param string $msg Message to send
+ * @param int $callerOffset Number of items to go back in the backtrace to
* find the correct caller (1 = function calling wfLogWarning, ...)
- * @param $level Integer: PHP error level; defaults to E_USER_WARNING
+ * @param int $level PHP error level; defaults to E_USER_WARNING
*/
function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
@@ -1098,8 +1189,8 @@ function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
* Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
* send lines to the specified port, prefixed by the specified prefix and a space.
*
- * @param $text String
- * @param string $file filename
+ * @param string $text
+ * @param string $file Filename
* @throws MWException
*/
function wfErrorLog( $text, $file ) {
@@ -1161,8 +1252,8 @@ function wfErrorLog( $text, $file ) {
* @todo document
*/
function wfLogProfilingData() {
- global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
- global $wgProfileLimit, $wgUser;
+ global $wgRequestTime, $wgDebugLogFile, $wgDebugLogGroups, $wgDebugRawPage;
+ global $wgProfileLimit, $wgUser, $wgRequest;
StatCounter::singleton()->flush();
@@ -1183,7 +1274,17 @@ function wfLogProfilingData() {
$profiler->logData();
// Check whether this should be logged in the debug file.
- if ( $wgDebugLogFile == '' || ( !$wgDebugRawPage && wfIsDebugRawPage() ) ) {
+ if ( isset( $wgDebugLogGroups['profileoutput'] )
+ && $wgDebugLogGroups['profileoutput'] === false
+ ) {
+ // Explicitely disabled
+ return;
+ }
+ if ( !isset( $wgDebugLogGroups['profileoutput'] ) && $wgDebugLogFile == '' ) {
+ // Logging not enabled; no point going further
+ return;
+ }
+ if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
return;
}
@@ -1218,14 +1319,14 @@ function wfLogProfilingData() {
gmdate( 'YmdHis' ), $elapsed,
urldecode( $requestUrl . $forward ) );
- wfErrorLog( $log . $profiler->getOutput(), $wgDebugLogFile );
+ wfDebugLog( 'profileoutput', $log . $profiler->getOutput() );
}
/**
* Increment a statistics counter
*
- * @param $key String
- * @param $count Int
+ * @param string $key
+ * @param int $count
* @return void
*/
function wfIncrStats( $key, $count = 1 ) {
@@ -1244,7 +1345,7 @@ function wfReadOnly() {
/**
* Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
*
- * @return string|bool: String when in read-only mode; false otherwise
+ * @return string|bool String when in read-only mode; false otherwise
*/
function wfReadOnlyReason() {
global $wgReadOnly, $wgReadOnlyFile;
@@ -1264,7 +1365,7 @@ function wfReadOnlyReason() {
/**
* Return a Language object from $langcode
*
- * @param $langcode Mixed: either:
+ * @param Language|string|bool $langcode Either:
* - a Language object
* - code of the language to get the message for, if it is
* a valid code create a language for that language, if
@@ -1274,7 +1375,7 @@ function wfReadOnlyReason() {
* the current user's language (as a fallback for the old parameter
* functionality), or if it is true then use global object
* for the wiki's content language.
- * @return Language object
+ * @return Language
*/
function wfGetLangObj( $langcode = false ) {
# Identify which language to get or create a language object for.
@@ -1310,19 +1411,6 @@ function wfGetLangObj( $langcode = false ) {
}
/**
- * Old function when $wgBetterDirectionality existed
- * All usage removed, wfUILang can be removed in near future
- *
- * @deprecated since 1.18
- * @return Language
- */
-function wfUILang() {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgLang;
- return $wgLang;
-}
-
-/**
* This is the function for getting translated interface messages.
*
* @see Message class for documentation how to use them.
@@ -1330,12 +1418,15 @@ function wfUILang() {
*
* This function replaces all old wfMsg* functions.
*
- * @param $key \string Message key.
- * Varargs: normal message parameters.
+ * @param string|string[] $key Message key, or array of keys
+ * @param mixed $params,... Normal message parameters
* @return Message
+ *
* @since 1.17
+ *
+ * @see Message::__construct
*/
-function wfMessage( $key /*...*/) {
+function wfMessage( $key /*...*/ ) {
$params = func_get_args();
array_shift( $params );
if ( isset( $params[0] ) && is_array( $params[0] ) ) {
@@ -1348,9 +1439,13 @@ function wfMessage( $key /*...*/) {
* This function accepts multiple message keys and returns a message instance
* for the first message which is non-empty. If all messages are empty then an
* instance of the first message key is returned.
- * @param varargs: message keys
+ *
+ * @param string|string[] $keys,... Message keys
* @return Message
+ *
* @since 1.18
+ *
+ * @see Message::newFallbackSequence
*/
function wfMessageFallback( /*...*/ ) {
$args = func_get_args();
@@ -1365,7 +1460,7 @@ function wfMessageFallback( /*...*/ ) {
*
* @deprecated since 1.18
*
- * @param string $key lookup key for the message, usually
+ * @param string $key Lookup key for the message, usually
* defined in languages/Language.php
*
* Parameters to the message, which can be used to insert variable text into
@@ -1374,7 +1469,7 @@ function wfMessageFallback( /*...*/ ) {
* - As an array in the second parameter
* These are not shown in the function definition.
*
- * @return String
+ * @return string
*/
function wfMsg( $key ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -1389,8 +1484,8 @@ function wfMsg( $key ) {
*
* @deprecated since 1.18
*
- * @param $key String
- * @return String
+ * @param string $key
+ * @return string
*/
function wfMsgNoTrans( $key ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -1421,9 +1516,9 @@ function wfMsgNoTrans( $key ) {
*
* @deprecated since 1.18
*
- * @param string $key lookup key for the message, usually
+ * @param string $key Lookup key for the message, usually
* defined in languages/Language.php
- * @return String
+ * @return string
*/
function wfMsgForContent( $key ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -1432,9 +1527,9 @@ function wfMsgForContent( $key ) {
$args = func_get_args();
array_shift( $args );
$forcontent = true;
- if ( is_array( $wgForceUIMsgAsContentMsg ) &&
- in_array( $key, $wgForceUIMsgAsContentMsg ) )
- {
+ if ( is_array( $wgForceUIMsgAsContentMsg )
+ && in_array( $key, $wgForceUIMsgAsContentMsg )
+ ) {
$forcontent = false;
}
return wfMsgReal( $key, $args, true, $forcontent );
@@ -1445,8 +1540,8 @@ function wfMsgForContent( $key ) {
*
* @deprecated since 1.18
*
- * @param $key String
- * @return String
+ * @param string $key
+ * @return string
*/
function wfMsgForContentNoTrans( $key ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -1455,9 +1550,9 @@ function wfMsgForContentNoTrans( $key ) {
$args = func_get_args();
array_shift( $args );
$forcontent = true;
- if ( is_array( $wgForceUIMsgAsContentMsg ) &&
- in_array( $key, $wgForceUIMsgAsContentMsg ) )
- {
+ if ( is_array( $wgForceUIMsgAsContentMsg )
+ && in_array( $key, $wgForceUIMsgAsContentMsg )
+ ) {
$forcontent = false;
}
return wfMsgReal( $key, $args, true, $forcontent, false );
@@ -1468,12 +1563,12 @@ function wfMsgForContentNoTrans( $key ) {
*
* @deprecated since 1.18
*
- * @param string $key key to get.
- * @param $args
- * @param $useDB Boolean
- * @param $forContent Mixed: Language code, or false for user lang, true for content lang.
- * @param $transform Boolean: Whether or not to transform the message.
- * @return String: the requested message.
+ * @param string $key Key to get.
+ * @param array $args
+ * @param bool $useDB
+ * @param string|bool $forContent Language code, or false for user lang, true for content lang.
+ * @param bool $transform Whether or not to transform the message.
+ * @return string The requested message.
*/
function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -1490,11 +1585,11 @@ function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform
*
* @deprecated since 1.18
*
- * @param $key String
- * @param $useDB Bool
- * @param string $langCode Code of the language to get the message for, or
- * behaves as a content language switch if it is a boolean.
- * @param $transform Boolean: whether to parse magic words, etc.
+ * @param string $key
+ * @param bool $useDB
+ * @param string|bool $langCode Code of the language to get the message for, or
+ * behaves as a content language switch if it is a boolean.
+ * @param bool $transform Whether to parse magic words, etc.
* @return string
*/
function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) {
@@ -1515,8 +1610,8 @@ function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true
/**
* Replace message parameter keys on the given formatted output.
*
- * @param $message String
- * @param $args Array
+ * @param string $message
+ * @param array $args
* @return string
* @private
*/
@@ -1549,8 +1644,8 @@ function wfMsgReplaceArgs( $message, $args ) {
*
* @deprecated since 1.18
*
- * @param $key String
- * @param string ... parameters
+ * @param string $key
+ * @param string $args,... Parameters
* @return string
*/
function wfMsgHtml( $key ) {
@@ -1570,8 +1665,8 @@ function wfMsgHtml( $key ) {
*
* @deprecated since 1.18
*
- * @param $key String
- * @param string ... parameters
+ * @param string $key
+ * @param string $args,... Parameters
* @return string
*/
function wfMsgWikiHtml( $key ) {
@@ -1590,22 +1685,23 @@ function wfMsgWikiHtml( $key ) {
*
* @deprecated since 1.18
*
- * @param string $key key of the message
- * @param array $options processing rules. Can take the following options:
- * <i>parse</i>: parses wikitext to HTML
- * <i>parseinline</i>: parses wikitext to HTML and removes the surrounding
+ * @param string $key Key of the message
+ * @param array $options Processing rules.
+ * Can take the following options:
+ * parse: parses wikitext to HTML
+ * parseinline: parses wikitext to HTML and removes the surrounding
* p's added by parser or tidy
- * <i>escape</i>: filters message through htmlspecialchars
- * <i>escapenoentities</i>: same, but allows entity references like &#160; through
- * <i>replaceafter</i>: parameters are substituted after parsing or escaping
- * <i>parsemag</i>: transform the message using magic phrases
- * <i>content</i>: fetch message for content language instead of interface
- * Also can accept a single associative argument, of the form 'language' => 'xx':
- * <i>language</i>: Language object or language code to fetch message for
- * (overridden by <i>content</i>).
+ * escape: filters message through htmlspecialchars
+ * escapenoentities: same, but allows entity references like &#160; through
+ * replaceafter: parameters are substituted after parsing or escaping
+ * parsemag: transform the message using magic phrases
+ * content: fetch message for content language instead of interface
+ * Also can accept a single associative argument, of the form 'language' => 'xx':
+ * language: Language object or language code to fetch message for
+ * (overridden by content).
* Behavior for conflicting options (e.g., parse+parseinline) is undefined.
*
- * @return String
+ * @return string
*/
function wfMsgExt( $key, $options ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -1656,10 +1752,7 @@ function wfMsgExt( $key, $options ) {
}
if ( $parseInline ) {
- $m = array();
- if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
- $string = $m[1];
- }
+ $string = Parser::stripOuterParagraph( $string );
}
} elseif ( in_array( 'parsemag', $options, true ) ) {
$string = $messageCache->transform( $string,
@@ -1686,8 +1779,8 @@ function wfMsgExt( $key, $options ) {
*
* @deprecated since 1.18. Use Message::isDisabled().
*
- * @param $key String: the message key looked up
- * @return Boolean True if the message *doesn't* exist.
+ * @param string $key The message key looked up
+ * @return bool True if the message *doesn't* exist.
*/
function wfEmptyMsg( $key ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -1696,19 +1789,6 @@ function wfEmptyMsg( $key ) {
}
/**
- * Throw a debugging exception. This function previously once exited the process,
- * but now throws an exception instead, with similar results.
- *
- * @deprecated since 1.22; just throw an MWException yourself
- * @param string $msg message shown when dying.
- * @throws MWException
- */
-function wfDebugDieBacktrace( $msg = '' ) {
- wfDeprecated( __FUNCTION__, '1.22' );
- throw new MWException( $msg );
-}
-
-/**
* Fetch server name for use in error reporting etc.
* Use real server name if available, so we know which machine
* in a server farm generated the current page.
@@ -1747,52 +1827,42 @@ function wfHostname() {
}
/**
- * Returns a HTML comment with the elapsed time since request.
- * This method has no side effects.
+ * Returns a script tag that stores the amount of time it took MediaWiki to
+ * handle the request in milliseconds as 'wgBackendResponseTime'.
+ *
+ * If $wgShowHostnames is true, the script will also set 'wgHostname' to the
+ * hostname of the server handling the request.
*
* @return string
*/
function wfReportTime() {
global $wgRequestTime, $wgShowHostnames;
- $elapsed = microtime( true ) - $wgRequestTime;
-
- return $wgShowHostnames
- ? sprintf( '<!-- Served by %s in %01.3f secs. -->', wfHostname(), $elapsed )
- : sprintf( '<!-- Served in %01.3f secs. -->', $elapsed );
+ $responseTime = round( ( microtime( true ) - $wgRequestTime ) * 1000 );
+ $reportVars = array( 'wgBackendResponseTime' => $responseTime );
+ if ( $wgShowHostnames ) {
+ $reportVars['wgHostname'] = wfHostname();
+ }
+ return Skin::makeVariablesScript( $reportVars );
}
/**
* Safety wrapper for debug_backtrace().
*
- * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
- * murky circumstances, which may be triggered in part by stub objects
- * or other fancy talking'.
- *
- * Will return an empty array if Zend Optimizer is detected or if
- * debug_backtrace is disabled, otherwise the output from
- * debug_backtrace() (trimmed).
+ * Will return an empty array if debug_backtrace is disabled, otherwise
+ * the output from debug_backtrace() (trimmed).
*
* @param int $limit This parameter can be used to limit the number of stack frames returned
*
- * @return array of backtrace information
+ * @return array Array of backtrace information
*/
function wfDebugBacktrace( $limit = 0 ) {
static $disabled = null;
- if ( extension_loaded( 'Zend Optimizer' ) ) {
- wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
- return array();
- }
-
if ( is_null( $disabled ) ) {
- $disabled = false;
- $functions = explode( ',', ini_get( 'disable_functions' ) );
- $functions = array_map( 'trim', $functions );
- $functions = array_map( 'strtolower', $functions );
- if ( in_array( 'debug_backtrace', $functions ) ) {
- wfDebug( "debug_backtrace is in disabled_functions\n" );
- $disabled = true;
+ $disabled = !function_exists( 'debug_backtrace' );
+ if ( $disabled ) {
+ wfDebug( "debug_backtrace() is disabled\n" );
}
}
if ( $disabled ) {
@@ -1863,7 +1933,7 @@ function wfBacktrace() {
* wfGetCaller( 2 ) [default] is the caller of the function running wfGetCaller()
* wfGetCaller( 3 ) is the parent of that.
*
- * @param $level Int
+ * @param int $level
* @return string
*/
function wfGetCaller( $level = 2 ) {
@@ -1879,9 +1949,8 @@ function wfGetCaller( $level = 2 ) {
* Return a string consisting of callers in the stack. Useful sometimes
* for profiling specific points.
*
- * @param int $limit The maximum depth of the stack frame to return, or false for
- * the entire stack.
- * @return String
+ * @param int $limit The maximum depth of the stack frame to return, or false for the entire stack.
+ * @return string
*/
function wfGetAllCallers( $limit = 3 ) {
$trace = array_reverse( wfDebugBacktrace() );
@@ -1895,7 +1964,7 @@ function wfGetAllCallers( $limit = 3 ) {
/**
* Return a string representation of frame
*
- * @param $frame Array
+ * @param array $frame
* @return string
*/
function wfFormatStackFrame( $frame ) {
@@ -1909,49 +1978,19 @@ function wfFormatStackFrame( $frame ) {
/**
* @todo document
*
- * @param $offset Int
- * @param $limit Int
- * @return String
+ * @param int $offset
+ * @param int $limit
+ * @return string
*/
function wfShowingResults( $offset, $limit ) {
return wfMessage( 'showingresults' )->numParams( $limit, $offset + 1 )->parse();
}
/**
- * Generate (prev x| next x) (20|50|100...) type links for paging
- *
- * @param $offset String
- * @param $limit Integer
- * @param $link String
- * @param string $query optional URL query parameter string
- * @param bool $atend optional param for specified if this is the last page
- * @return String
- * @deprecated in 1.19; use Language::viewPrevNext() instead
- */
-function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- global $wgLang;
-
- $query = wfCgiToArray( $query );
-
- if ( is_object( $link ) ) {
- $title = $link;
- } else {
- $title = Title::newFromText( $link );
- if ( is_null( $title ) ) {
- return false;
- }
- }
-
- return $wgLang->viewPrevNext( $title, $offset, $limit, $query, $atend );
-}
-
-/**
* @todo document
* @todo FIXME: We may want to blacklist some broken browsers
*
- * @param $force Bool
+ * @param bool $force
* @return bool Whereas client accept gzip compression
*/
function wfClientAcceptsGzip( $force = false ) {
@@ -1962,11 +2001,11 @@ function wfClientAcceptsGzip( $force = false ) {
# @todo FIXME: We may want to blacklist some broken browsers
$m = array();
if ( preg_match(
- '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
- $_SERVER['HTTP_ACCEPT_ENCODING'],
- $m )
- )
- {
+ '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
+ $_SERVER['HTTP_ACCEPT_ENCODING'],
+ $m
+ )
+ ) {
if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
$result = false;
return $result;
@@ -1983,13 +2022,14 @@ function wfClientAcceptsGzip( $force = false ) {
* Obtain the offset and limit values from the request string;
* used in special pages
*
- * @param int $deflimit default limit if none supplied
+ * @param int $deflimit Default limit if none supplied
* @param string $optionname Name of a user preference to check against
* @return array
- *
+ * @deprecated since 1.24, just call WebRequest::getLimitOffset() directly
*/
function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
global $wgRequest;
+ wfDeprecated( __METHOD__, '1.24' );
return $wgRequest->getLimitOffset( $deflimit, $optionname );
}
@@ -1999,8 +2039,8 @@ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
* is achieved by substituting certain characters with HTML entities.
* As required by the callers, "<nowiki>" is not used.
*
- * @param string $text text to be escaped
- * @return String
+ * @param string $text Text to be escaped
+ * @return string
*/
function wfEscapeWikiText( $text ) {
static $repl = null, $repl2 = null;
@@ -2045,24 +2085,14 @@ function wfEscapeWikiText( $text ) {
}
/**
- * Get the current unix timestamp with microseconds. Useful for profiling
- * @deprecated since 1.22; call microtime() directly
- * @return Float
- */
-function wfTime() {
- wfDeprecated( __FUNCTION__, '1.22' );
- return microtime( true );
-}
-
-/**
* Sets dest to source and returns the original value of dest
* If source is NULL, it just returns the value, it doesn't set the variable
* If force is true, it will set the value even if source is NULL
*
- * @param $dest Mixed
- * @param $source Mixed
- * @param $force Bool
- * @return Mixed
+ * @param mixed $dest
+ * @param mixed $source
+ * @param bool $force
+ * @return mixed
*/
function wfSetVar( &$dest, $source, $force = false ) {
$temp = $dest;
@@ -2075,9 +2105,9 @@ function wfSetVar( &$dest, $source, $force = false ) {
/**
* As for wfSetVar except setting a bit
*
- * @param $dest Int
- * @param $bit Int
- * @param $state Bool
+ * @param int $dest
+ * @param int $bit
+ * @param bool $state
*
* @return bool
*/
@@ -2097,7 +2127,7 @@ function wfSetBit( &$dest, $bit, $state = true ) {
* A wrapper around the PHP function var_export().
* Either print it or add it to the regular output ($wgOut).
*
- * @param $var mixed A PHP variable to dump.
+ * @param mixed $var A PHP variable to dump.
*/
function wfVarDump( $var ) {
global $wgOut;
@@ -2112,9 +2142,9 @@ function wfVarDump( $var ) {
/**
* Provide a simple HTTP error.
*
- * @param $code Int|String
- * @param $label String
- * @param $desc String
+ * @param int|string $code
+ * @param string $label
+ * @param string $desc
*/
function wfHttpError( $code, $label, $desc ) {
global $wgOut;
@@ -2149,7 +2179,7 @@ function wfHttpError( $code, $label, $desc ) {
* Note that some PHP configuration options may add output buffer
* layers which cannot be removed; these are left in place.
*
- * @param $resetGzipEncoding Bool
+ * @param bool $resetGzipEncoding
*/
function wfResetOutputBuffers( $resetGzipEncoding = true ) {
if ( $resetGzipEncoding ) {
@@ -2203,9 +2233,9 @@ function wfClearOutputBuffers() {
* Converts an Accept-* header into an array mapping string values to quality
* factors
*
- * @param $accept String
- * @param string $def default
- * @return Array
+ * @param string $accept
+ * @param string $def Default
+ * @return float[] Associative array of string => float pairs
*/
function wfAcceptToPrefs( $accept, $def = '*/*' ) {
# No arg means accept anything (per HTTP spec)
@@ -2238,8 +2268,8 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) {
* Returns the matching MIME type (or wildcard) if a match, otherwise
* NULL if no match.
*
- * @param $type String
- * @param $avail Array
+ * @param string $type
+ * @param array $avail
* @return string
* @private
*/
@@ -2264,8 +2294,8 @@ function mimeTypeMatch( $type, $avail ) {
* array of type to preference (preference is a float between 0.0 and 1.0).
* Wildcards in the types are acceptable.
*
- * @param array $cprefs client's acceptable type list
- * @param array $sprefs server's offered types
+ * @param array $cprefs Client's acceptable type list
+ * @param array $sprefs Server's offered types
* @return string
*
* @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
@@ -2310,7 +2340,7 @@ function wfNegotiateType( $cprefs, $sprefs ) {
/**
* Reference-counted warning suppression
*
- * @param $end Bool
+ * @param bool $end
*/
function wfSuppressWarnings( $end = false ) {
static $suppressCount = 0;
@@ -2325,7 +2355,15 @@ function wfSuppressWarnings( $end = false ) {
}
} else {
if ( !$suppressCount ) {
- $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT ) );
+ $originalLevel = error_reporting( E_ALL & ~(
+ E_WARNING |
+ E_NOTICE |
+ E_USER_WARNING |
+ E_USER_NOTICE |
+ E_DEPRECATED |
+ E_USER_DEPRECATED |
+ E_STRICT
+ ) );
}
++$suppressCount;
}
@@ -2394,11 +2432,10 @@ define( 'TS_ISO_8601_BASIC', 9 );
/**
* Get a timestamp string in one of various formats
*
- * @param $outputtype Mixed: A timestamp in one of the supported formats, the
- * function will autodetect which format is supplied and act
- * accordingly.
- * @param $ts Mixed: optional timestamp to convert, default 0 for the current time
- * @return Mixed: String / false The same date in the format specified in $outputtype or false
+ * @param mixed $outputtype A timestamp in one of the supported formats, the
+ * function will autodetect which format is supplied and act accordingly.
+ * @param mixed $ts Optional timestamp to convert, default 0 for the current time
+ * @return string|bool String / false The same date in the format specified in $outputtype or false
*/
function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
try {
@@ -2414,9 +2451,9 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
* Return a formatted timestamp, or null if input is null.
* For dealing with nullable timestamp columns in the database.
*
- * @param $outputtype Integer
- * @param $ts String
- * @return String
+ * @param int $outputtype
+ * @param string $ts
+ * @return string
*/
function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
if ( is_null( $ts ) ) {
@@ -2439,7 +2476,7 @@ function wfTimestampNow() {
/**
* Check if the operating system is Windows
*
- * @return Bool: true if it's Windows, False otherwise.
+ * @return bool True if it's Windows, false otherwise.
*/
function wfIsWindows() {
static $isWindows = null;
@@ -2450,21 +2487,23 @@ function wfIsWindows() {
}
/**
- * Check if we are running under HipHop
+ * Check if we are running under HHVM
*
- * @return Bool
+ * @return bool
*/
-function wfIsHipHop() {
- return defined( 'HPHP_VERSION' );
+function wfIsHHVM() {
+ return defined( 'HHVM_VERSION' );
}
/**
* Swap two variables
*
- * @param $x Mixed
- * @param $y Mixed
+ * @deprecated since 1.24
+ * @param mixed $x
+ * @param mixed $y
*/
function swap( &$x, &$y ) {
+ wfDeprecated( __FUNCTION__, '1.24' );
$z = $x;
$x = $y;
$y = $z;
@@ -2479,7 +2518,7 @@ function swap( &$x, &$y ) {
* NOTE: When possible, use instead the tmpfile() function to create
* temporary files to avoid race conditions on file creation, etc.
*
- * @return String
+ * @return string
*/
function wfTempDir() {
global $wgTmpDirectory;
@@ -2501,9 +2540,9 @@ function wfTempDir() {
/**
* Make directory, and make all parent directories if they don't exist
*
- * @param string $dir full path to directory to create
- * @param $mode Integer: chmod value to use, default is $wgDirectoryMode
- * @param string $caller optional caller param for debugging.
+ * @param string $dir Full path to directory to create
+ * @param int $mode Chmod value to use, default is $wgDirectoryMode
+ * @param string $caller Optional caller param for debugging.
* @throws MWException
* @return bool
*/
@@ -2548,6 +2587,7 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) {
/**
* Remove a directory and all its content.
* Does not hide error.
+ * @param string $dir
*/
function wfRecursiveRemoveDir( $dir ) {
wfDebug( __FUNCTION__ . "( $dir )\n" );
@@ -2569,10 +2609,10 @@ function wfRecursiveRemoveDir( $dir ) {
}
/**
- * @param $nr Mixed: the number to format
- * @param $acc Integer: the number of digits after the decimal point, default 2
- * @param $round Boolean: whether or not to round the value, default true
- * @return float
+ * @param int $nr The number to format
+ * @param int $acc The number of digits after the decimal point, default 2
+ * @param bool $round Whether or not to round the value, default true
+ * @return string
*/
function wfPercent( $nr, $acc = 2, $round = true ) {
$ret = sprintf( "%.${acc}f", $nr );
@@ -2580,25 +2620,6 @@ function wfPercent( $nr, $acc = 2, $round = true ) {
}
/**
- * Find out whether or not a mixed variable exists in a string
- *
- * @deprecated Just use str(i)pos
- * @param $needle String
- * @param $str String
- * @param $insensitive Boolean
- * @return Boolean
- */
-function in_string( $needle, $str, $insensitive = false ) {
- wfDeprecated( __METHOD__, '1.21' );
- $func = 'strpos';
- if ( $insensitive ) {
- $func = 'stripos';
- }
-
- return $func( $str, $needle ) !== false;
-}
-
-/**
* Safety wrapper around ini_get() for boolean settings.
* The values returned from ini_get() are pre-normalized for settings
* set via php.ini or php_flag/php_admin_flag... but *not*
@@ -2618,8 +2639,8 @@ function in_string( $needle, $str, $insensitive = false ) {
*
* I frickin' hate PHP... :P
*
- * @param $setting String
- * @return Bool
+ * @param string $setting
+ * @return bool
*/
function wfIniGetBool( $setting ) {
$val = strtolower( ini_get( $setting ) );
@@ -2638,10 +2659,10 @@ function wfIniGetBool( $setting ) {
* Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
* earlier distro releases of PHP)
*
- * @param varargs
- * @return String
+ * @param string $args,...
+ * @return string
*/
-function wfEscapeShellArg() {
+function wfEscapeShellArg( /*...*/ ) {
wfInitShellLocale();
$args = func_get_args();
@@ -2656,12 +2677,14 @@ function wfEscapeShellArg() {
if ( wfIsWindows() ) {
// Escaping for an MSVC-style command line parser and CMD.EXE
+ // @codingStandardsIgnoreStart For long URLs
// Refs:
// * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
// * http://technet.microsoft.com/en-us/library/cc723564.aspx
// * Bug #13518
// * CR r63214
// Double the backslashes before any double quotes. Escape the double quotes.
+ // @codingStandardsIgnoreEnd
$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
$arg = '';
$iteration = 0;
@@ -2696,24 +2719,21 @@ function wfEscapeShellArg() {
/**
* Check if wfShellExec() is effectively disabled via php.ini config
+ *
* @return bool|string False or one of (safemode,disabled)
* @since 1.22
*/
function wfShellExecDisabled() {
static $disabled = null;
if ( is_null( $disabled ) ) {
- $disabled = false;
if ( wfIniGetBool( 'safe_mode' ) ) {
wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
$disabled = 'safemode';
+ } elseif ( !function_exists( 'proc_open' ) ) {
+ wfDebug( "proc_open() is disabled\n" );
+ $disabled = 'disabled';
} else {
- $functions = explode( ',', ini_get( 'disable_functions' ) );
- $functions = array_map( 'trim', $functions );
- $functions = array_map( 'strtolower', $functions );
- if ( in_array( 'passthru', $functions ) ) {
- wfDebug( "passthru is in disabled_functions\n" );
- $disabled = 'passthru';
- }
+ $disabled = false;
}
}
return $disabled;
@@ -2722,18 +2742,26 @@ function wfShellExecDisabled() {
/**
* Execute a shell command, with time and memory limits mirrored from the PHP
* configuration if supported.
- * @param string $cmd Command line, properly escaped for shell.
- * @param &$retval null|Mixed optional, will receive the program's exit code.
- * (non-zero is usually failure)
- * @param array $environ optional environment variables which should be
- * added to the executed command environment.
- * @param array $limits optional array with limits(filesize, memory, time, walltime)
- * this overwrites the global wgShellMax* limits.
- * @param array $options Array of options. Only one is "duplicateStderr" => true, which
- * Which duplicates stderr to stdout, including errors from limit.sh
- * @return string collected stdout as a string
- */
-function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array(), $options = array() ) {
+ *
+ * @param string|string[] $cmd If string, a properly shell-escaped command line,
+ * or an array of unescaped arguments, in which case each value will be escaped
+ * Example: [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
+ * @param null|mixed &$retval Optional, will receive the program's exit code.
+ * (non-zero is usually failure). If there is an error from
+ * read, select, or proc_open(), this will be set to -1.
+ * @param array $environ Optional environment variables which should be
+ * added to the executed command environment.
+ * @param array $limits Optional array with limits(filesize, memory, time, walltime)
+ * this overwrites the global wgMaxShell* limits.
+ * @param array $options Array of options:
+ * - duplicateStderr: Set this to true to duplicate stderr to stdout,
+ * including errors from limit.sh
+ *
+ * @return string Collected stdout as a string
+ */
+function wfShellExec( $cmd, &$retval = null, $environ = array(),
+ $limits = array(), $options = array()
+) {
global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
$wgMaxShellWallClockTime, $wgShellCgroup;
@@ -2742,7 +2770,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
$retval = 1;
return $disabled == 'safemode' ?
'Unable to run external programs in safe mode.' :
- 'Unable to run external programs, passthru() is disabled.';
+ 'Unable to run external programs, proc_open() is disabled.';
}
$includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
@@ -2766,9 +2794,19 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
$envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
}
}
+ if ( is_array( $cmd ) ) {
+ // Command line may be given as an array, escape each value and glue them together with a space
+ $cmdVals = array();
+ foreach ( $cmd as $val ) {
+ $cmdVals[] = wfEscapeShellArg( $val );
+ }
+ $cmd = implode( ' ', $cmdVals );
+ }
+
$cmd = $envcmd . $cmd;
- if ( php_uname( 's' ) == 'Linux' ) {
+ $useLogPipe = false;
+ if ( is_executable( '/bin/bash' ) ) {
$time = intval ( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
if ( isset( $limits['walltime'] ) ) {
$wallTime = intval( $limits['walltime'] );
@@ -2789,8 +2827,10 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
"MW_MEM_LIMIT=$mem; " .
"MW_FILE_SIZE_LIMIT=$filesize; " .
- "MW_WALL_CLOCK_LIMIT=$wallTime"
+ "MW_WALL_CLOCK_LIMIT=$wallTime; " .
+ "MW_USE_LOG_PIPE=yes"
);
+ $useLogPipe = true;
} elseif ( $includeStderr ) {
$cmd .= ' 2>&1';
}
@@ -2799,19 +2839,146 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
}
wfDebug( "wfShellExec: $cmd\n" );
- // Default to an unusual value that shouldn't happen naturally,
- // so in the unlikely event of a weird php bug, it would be
- // more obvious what happened.
- $retval = 200;
- ob_start();
- passthru( $cmd, $retval );
- $output = ob_get_contents();
- ob_end_clean();
+ $desc = array(
+ 0 => array( 'file', 'php://stdin', 'r' ),
+ 1 => array( 'pipe', 'w' ),
+ 2 => array( 'file', 'php://stderr', 'w' ) );
+ if ( $useLogPipe ) {
+ $desc[3] = array( 'pipe', 'w' );
+ }
+ $pipes = null;
+ $proc = proc_open( $cmd, $desc, $pipes );
+ if ( !$proc ) {
+ wfDebugLog( 'exec', "proc_open() failed: $cmd" );
+ $retval = -1;
+ return '';
+ }
+ $outBuffer = $logBuffer = '';
+ $emptyArray = array();
+ $status = false;
+ $logMsg = false;
+
+ // According to the documentation, it is possible for stream_select()
+ // to fail due to EINTR. I haven't managed to induce this in testing
+ // despite sending various signals. If it did happen, the error
+ // message would take the form:
+ //
+ // stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
+ //
+ // where [4] is the value of the macro EINTR and "Interrupted system
+ // call" is string which according to the Linux manual is "possibly"
+ // localised according to LC_MESSAGES.
+ $eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
+ $eintrMessage = "stream_select(): unable to select [$eintr]";
+
+ // Build a table mapping resource IDs to pipe FDs to work around a
+ // PHP 5.3 issue in which stream_select() does not preserve array keys
+ // <https://bugs.php.net/bug.php?id=53427>.
+ $fds = array();
+ foreach ( $pipes as $fd => $pipe ) {
+ $fds[(int)$pipe] = $fd;
+ }
+
+ $running = true;
+ $timeout = null;
+ $numReadyPipes = 0;
+
+ while ( $running === true || $numReadyPipes !== 0 ) {
+ if ( $running ) {
+ $status = proc_get_status( $proc );
+ // If the process has terminated, switch to nonblocking selects
+ // for getting any data still waiting to be read.
+ if ( !$status['running'] ) {
+ $running = false;
+ $timeout = 0;
+ }
+ }
+
+ $readyPipes = $pipes;
- if ( $retval == 127 ) {
- wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
+ // Clear last error
+ // @codingStandardsIgnoreStart Generic.PHP.NoSilencedErrors.Discouraged
+ @trigger_error( '' );
+ $numReadyPipes = @stream_select( $readyPipes, $emptyArray, $emptyArray, $timeout );
+ if ( $numReadyPipes === false ) {
+ // @codingStandardsIgnoreEnd
+ $error = error_get_last();
+ if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
+ continue;
+ } else {
+ trigger_error( $error['message'], E_USER_WARNING );
+ $logMsg = $error['message'];
+ break;
+ }
+ }
+ foreach ( $readyPipes as $pipe ) {
+ $block = fread( $pipe, 65536 );
+ $fd = $fds[(int)$pipe];
+ if ( $block === '' ) {
+ // End of file
+ fclose( $pipes[$fd] );
+ unset( $pipes[$fd] );
+ if ( !$pipes ) {
+ break 2;
+ }
+ } elseif ( $block === false ) {
+ // Read error
+ $logMsg = "Error reading from pipe";
+ break 2;
+ } elseif ( $fd == 1 ) {
+ // From stdout
+ $outBuffer .= $block;
+ } elseif ( $fd == 3 ) {
+ // From log FD
+ $logBuffer .= $block;
+ if ( strpos( $block, "\n" ) !== false ) {
+ $lines = explode( "\n", $logBuffer );
+ $logBuffer = array_pop( $lines );
+ foreach ( $lines as $line ) {
+ wfDebugLog( 'exec', $line );
+ }
+ }
+ }
+ }
}
- return $output;
+
+ foreach ( $pipes as $pipe ) {
+ fclose( $pipe );
+ }
+
+ // Use the status previously collected if possible, since proc_get_status()
+ // just calls waitpid() which will not return anything useful the second time.
+ if ( $running ) {
+ $status = proc_get_status( $proc );
+ }
+
+ if ( $logMsg !== false ) {
+ // Read/select error
+ $retval = -1;
+ proc_close( $proc );
+ } elseif ( $status['signaled'] ) {
+ $logMsg = "Exited with signal {$status['termsig']}";
+ $retval = 128 + $status['termsig'];
+ proc_close( $proc );
+ } else {
+ if ( $status['running'] ) {
+ $retval = proc_close( $proc );
+ } else {
+ $retval = $status['exitcode'];
+ proc_close( $proc );
+ }
+ if ( $retval == 127 ) {
+ $logMsg = "Possibly missing executable file";
+ } elseif ( $retval >= 129 && $retval <= 192 ) {
+ $logMsg = "Probably exited with signal " . ( $retval - 128 );
+ }
+ }
+
+ if ( $logMsg !== false ) {
+ wfDebugLog( 'exec', "$logMsg: $cmd" );
+ }
+
+ return $outBuffer;
}
/**
@@ -2820,13 +2987,13 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
*
* @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
* @param string $cmd Command line, properly escaped for shell.
- * @param &$retval null|Mixed optional, will receive the program's exit code.
- * (non-zero is usually failure)
- * @param array $environ optional environment variables which should be
- * added to the executed command environment.
- * @param array $limits optional array with limits(filesize, memory, time, walltime)
- * this overwrites the global wgShellMax* limits.
- * @return string collected stdout and stderr as a string
+ * @param null|mixed &$retval Optional, will receive the program's exit code.
+ * (non-zero is usually failure)
+ * @param array $environ Optional environment variables which should be
+ * added to the executed command environment.
+ * @param array $limits Optional array with limits(filesize, memory, time, walltime)
+ * this overwrites the global wgMaxShell* limits.
+ * @return string Collected stdout and stderr as a string
*/
function wfShellExecWithStderr( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
return wfShellExec( $cmd, $retval, $environ, $limits, array( 'duplicateStderr' => true ) );
@@ -2851,6 +3018,7 @@ function wfInitShellLocale() {
/**
* Alias to wfShellWikiCmd()
+ *
* @see wfShellWikiCmd()
*/
function wfShellMaintenanceCmd( $script, array $parameters = array(), array $options = array() ) {
@@ -2861,12 +3029,13 @@ function wfShellMaintenanceCmd( $script, array $parameters = array(), array $opt
* Generate a shell-escaped command line string to run a MediaWiki cli script.
* Note that $parameters should be a flat array and an option with an argument
* should consist of two consecutive items in the array (do not use "--option value").
+ *
* @param string $script MediaWiki cli script path
* @param array $parameters Arguments and options to the script
* @param array $options Associative array of options:
* 'php': The path to the php executable
* 'wrapper': Path to a PHP wrapper to handle the maintenance script
- * @return Array
+ * @return string
*/
function wfShellWikiCmd( $script, array $parameters = array(), array $options = array() ) {
global $wgPhpCli;
@@ -2886,11 +3055,11 @@ function wfShellWikiCmd( $script, array $parameters = array(), array $options =
* wfMerge attempts to merge differences between three texts.
* Returns true for a clean merge and false for failure or a conflict.
*
- * @param $old String
- * @param $mine String
- * @param $yours String
- * @param $result String
- * @return Bool
+ * @param string $old
+ * @param string $mine
+ * @param string $yours
+ * @param string $result
+ * @return bool
*/
function wfMerge( $old, $mine, $yours, &$result ) {
global $wgDiff3;
@@ -2965,10 +3134,10 @@ function wfMerge( $old, $mine, $yours, &$result ) {
* Returns unified plain-text diff of two texts.
* Useful for machine processing of diffs.
*
- * @param string $before the text before the changes.
- * @param string $after the text after the changes.
- * @param string $params command-line options for the diff command.
- * @return String: unified diff of $before and $after
+ * @param string $before The text before the changes.
+ * @param string $after The text after the changes.
+ * @param string $params Command-line options for the diff command.
+ * @return string Unified diff of $before and $after
*/
function wfDiff( $before, $after, $params = '-u' ) {
if ( $before == $after ) {
@@ -3021,10 +3190,10 @@ function wfDiff( $before, $after, $params = '-u' ) {
// Kill the --- and +++ lines. They're not useful.
$diff_lines = explode( "\n", $diff );
- if ( strpos( $diff_lines[0], '---' ) === 0 ) {
+ if ( isset( $diff_lines[0] ) && strpos( $diff_lines[0], '---' ) === 0 ) {
unset( $diff_lines[0] );
}
- if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
+ if ( isset( $diff_lines[1] ) && strpos( $diff_lines[1], '+++' ) === 0 ) {
unset( $diff_lines[1] );
}
@@ -3045,8 +3214,7 @@ function wfDiff( $before, $after, $params = '-u' ) {
*
* @see perldoc -f use
*
- * @param $req_ver Mixed: the version to check, can be a string, an integer, or
- * a float
+ * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
* @throws MWException
*/
function wfUsePHP( $req_ver ) {
@@ -3065,10 +3233,17 @@ function wfUsePHP( $req_ver ) {
* This is useful for extensions which due to their nature are not kept in sync
* with releases
*
+ * Note: Due to the behavior of PHP's version_compare() which is used in this
+ * function, if you want to allow the 'wmf' development versions add a 'c' (or
+ * any single letter other than 'a', 'b' or 'p') as a post-fix to your
+ * targeted version number. For example if you wanted to allow any variation
+ * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will
+ * not result in the same comparison due to the internal logic of
+ * version_compare().
+ *
* @see perldoc -f use
*
- * @param $req_ver Mixed: the version to check, can be a string, an integer, or
- * a float
+ * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
* @throws MWException
*/
function wfUseMW( $req_ver ) {
@@ -3087,14 +3262,17 @@ function wfUseMW( $req_ver ) {
* PHP's basename() only considers '\' a pathchar on Windows and Netware.
* We'll consider it so always, as we don't want '\s' in our Unix paths either.
*
- * @param $path String
- * @param string $suffix to remove if present
- * @return String
+ * @param string $path
+ * @param string $suffix String to remove if present
+ * @return string
*/
function wfBaseName( $path, $suffix = '' ) {
- $encSuffix = ( $suffix == '' )
- ? ''
- : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
+ if ( $suffix == '' ) {
+ $encSuffix = '';
+ } else {
+ $encSuffix = '(?:' . preg_quote( $suffix, '#' ) . ')?';
+ }
+
$matches = array();
if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
return $matches[1];
@@ -3108,9 +3286,9 @@ function wfBaseName( $path, $suffix = '' ) {
* May explode on non-matching case-insensitive paths,
* funky symlinks, etc.
*
- * @param string $path absolute destination path including target filename
+ * @param string $path Absolute destination path including target filename
* @param string $from Absolute source path, directory only
- * @return String
+ * @return string
*/
function wfRelativePath( $path, $from ) {
// Normalize mixed input on Windows...
@@ -3149,18 +3327,6 @@ function wfRelativePath( $path, $from ) {
}
/**
- * Do any deferred updates and clear the list
- *
- * @deprecated since 1.19
- * @see DeferredUpdates::doUpdate()
- * @param $commit string
- */
-function wfDoUpdates( $commit = '' ) {
- wfDeprecated( __METHOD__, '1.19' );
- DeferredUpdates::doUpdates( $commit );
-}
-
-/**
* Convert an arbitrarily-long digit string from one numeric base
* to another, optionally zero-padding to a minimum column width.
*
@@ -3175,7 +3341,9 @@ function wfDoUpdates( $commit = '' ) {
* @param string $engine Either "gmp", "bcmath", or "php"
* @return string|bool The output number as a string, or false on error
*/
-function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true, $engine = 'auto' ) {
+function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1,
+ $lowercase = true, $engine = 'auto'
+) {
$input = (string)$input;
if (
$sourceBase < 2 ||
@@ -3185,7 +3353,10 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
$sourceBase != (int)$sourceBase ||
$destBase != (int)$destBase ||
$pad != (int)$pad ||
- !preg_match( "/^[" . substr( '0123456789abcdefghijklmnopqrstuvwxyz', 0, $sourceBase ) . "]+$/i", $input )
+ !preg_match(
+ "/^[" . substr( '0123456789abcdefghijklmnopqrstuvwxyz', 0, $sourceBase ) . "]+$/i",
+ $input
+ )
) {
return false;
}
@@ -3197,8 +3368,8 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
28 => 's', 29 => 't', 30 => 'u', 31 => 'v', 32 => 'w', 33 => 'x',
34 => 'y', 35 => 'z',
- '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5,
- '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'a' => 10, 'b' => 11,
+ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5,
+ '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'a' => 10, 'b' => 11,
'c' => 12, 'd' => 13, 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17,
'i' => 18, 'j' => 19, 'k' => 20, 'l' => 21, 'm' => 22, 'n' => 23,
'o' => 24, 'p' => 25, 'q' => 26, 'r' => 27, 's' => 28, 't' => 29,
@@ -3206,7 +3377,10 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
);
if ( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) {
- $result = gmp_strval( gmp_init( $input, $sourceBase ), $destBase );
+ // Removing leading zeros works around broken base detection code in
+ // some PHP versions (see <https://bugs.php.net/bug.php?id=50175> and
+ // <https://bugs.php.net/bug.php?id=55398>).
+ $result = gmp_strval( gmp_init( ltrim( $input, '0' ), $sourceBase ), $destBase );
} elseif ( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) {
$decimal = '0';
foreach ( str_split( strtolower( $input ) ) as $char ) {
@@ -3214,9 +3388,11 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
$decimal = bcadd( $decimal, $baseChars[$char] );
}
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
for ( $result = ''; bccomp( $decimal, 0 ); $decimal = bcdiv( $decimal, $destBase, 0 ) ) {
$result .= $baseChars[bcmod( $decimal, $destBase )];
}
+ // @codingStandardsIgnoreEnd
$result = strrev( $result );
} else {
@@ -3262,38 +3438,9 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
}
/**
- * Create an object with a given name and an array of construct parameters
- *
- * @param $name String
- * @param array $p parameters
- * @return object
- * @deprecated since 1.18, warnings in 1.18, removal in 1.20
- */
-function wfCreateObject( $name, $p ) {
- wfDeprecated( __FUNCTION__, '1.18' );
- return MWFunction::newObj( $name, $p );
-}
-
-/**
- * @return bool
- */
-function wfHttpOnlySafe() {
- global $wgHttpOnlyBlacklist;
-
- if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
- foreach ( $wgHttpOnlyBlacklist as $regex ) {
- if ( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
- return false;
- }
- }
- }
-
- return true;
-}
-
-/**
* Check if there is sufficient entropy in php's built-in session generation
- * @return bool true = there is sufficient entropy
+ *
+ * @return bool True = there is sufficient entropy
*/
function wfCheckEntropy() {
return (
@@ -3319,15 +3466,18 @@ function wfFixSessionID() {
// We treat it as disabled if it doesn't have an entropy length of at least 32
$entropyEnabled = wfCheckEntropy();
- // If built-in entropy is not enabled or not sufficient override php's built in session id generation code
+ // If built-in entropy is not enabled or not sufficient override PHP's
+ // built in session id generation code
if ( !$entropyEnabled ) {
- wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, overriding session id generation using our cryptrand source.\n" );
+ wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, " .
+ "overriding session id generation using our cryptrand source.\n" );
session_id( MWCryptRand::generateHex( 32 ) );
}
}
/**
* Reset the session_id
+ *
* @since 1.22
*/
function wfResetSessionID() {
@@ -3346,11 +3496,10 @@ function wfResetSessionID() {
wfRunHooks( 'ResetSessionID', array( $oldSessionId, $newSessionId ) );
}
-
/**
* Initialise php session
*
- * @param $sessionId Bool
+ * @param bool $sessionId
*/
function wfSetupSession( $sessionId = false ) {
global $wgSessionsInMemcached, $wgSessionsInObjectCache, $wgCookiePath, $wgCookieDomain,
@@ -3362,16 +3511,8 @@ function wfSetupSession( $sessionId = false ) {
# hasn't already been set to the desired value (that causes errors)
ini_set( 'session.save_handler', $wgSessionHandler );
}
- $httpOnlySafe = wfHttpOnlySafe() && $wgCookieHttpOnly;
- wfDebugLog( 'cookie',
- 'session_set_cookie_params: "' . implode( '", "',
- array(
- 0,
- $wgCookiePath,
- $wgCookieDomain,
- $wgCookieSecure,
- $httpOnlySafe ) ) . '"' );
- session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $httpOnlySafe );
+ session_set_cookie_params(
+ 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly );
session_cache_limiter( 'private, must-revalidate' );
if ( $sessionId ) {
session_id( $sessionId );
@@ -3386,8 +3527,8 @@ function wfSetupSession( $sessionId = false ) {
/**
* Get an object from the precompiled serialized directory
*
- * @param $name String
- * @return Mixed: the variable on success, false on failure
+ * @param string $name
+ * @return mixed The variable on success, false on failure
*/
function wfGetPrecompiledData( $name ) {
global $IP;
@@ -3405,10 +3546,10 @@ function wfGetPrecompiledData( $name ) {
/**
* Get a cache key
*
- * @param varargs
- * @return String
+ * @param string $args,...
+ * @return string
*/
-function wfMemcKey( /*... */ ) {
+function wfMemcKey( /*...*/ ) {
global $wgCachePrefix;
$prefix = $wgCachePrefix === false ? wfWikiID() : $wgCachePrefix;
$args = func_get_args();
@@ -3420,12 +3561,12 @@ function wfMemcKey( /*... */ ) {
/**
* Get a cache key for a foreign DB
*
- * @param $db String
- * @param $prefix String
- * @param varargs String
- * @return String
+ * @param string $db
+ * @param string $prefix
+ * @param string $args,...
+ * @return string
*/
-function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
+function wfForeignMemcKey( $db, $prefix /*...*/ ) {
$args = array_slice( func_get_args(), 2 );
if ( $prefix ) {
$key = "$db-$prefix:" . implode( ':', $args );
@@ -3439,7 +3580,7 @@ function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
* Get an ASCII string identifying this wiki
* This is used as a prefix in memcached keys
*
- * @return String
+ * @return string
*/
function wfWikiID() {
global $wgDBprefix, $wgDBname;
@@ -3453,7 +3594,7 @@ function wfWikiID() {
/**
* Split a wiki ID into DB name and table prefix
*
- * @param $wiki String
+ * @param string $wiki
*
* @return array
*/
@@ -3468,15 +3609,15 @@ function wfSplitWikiID( $wiki ) {
/**
* Get a Database object.
*
- * @param $db Integer: index of the connection to get. May be DB_MASTER for the
+ * @param int $db Index of the connection to get. May be DB_MASTER for the
* master (for write queries), DB_SLAVE for potentially lagged read
* queries, or an integer >= 0 for a particular server.
*
- * @param $groups Mixed: query groups. An array of group names that this query
+ * @param string|string[] $groups Query groups. An array of group names that this query
* belongs to. May contain a single string if the query is only
* in one group.
*
- * @param string $wiki the wiki ID, or false for the current wiki
+ * @param string|bool $wiki The wiki ID, or false for the current wiki
*
* Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
* will always return the same object, unless the underlying connection or load
@@ -3494,7 +3635,7 @@ function &wfGetDB( $db, $groups = array(), $wiki = false ) {
/**
* Get a load balancer object.
*
- * @param string $wiki wiki ID, or false for the current wiki
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
function wfGetLB( $wiki = false ) {
@@ -3514,7 +3655,7 @@ function &wfGetLBFactory() {
* Find a file.
* Shortcut for RepoGroup::singleton()->findFile()
*
- * @param string $title or Title object
+ * @param string $title String or Title object
* @param array $options Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
@@ -3528,7 +3669,7 @@ function &wfGetLBFactory() {
*
* bypassCache: If true, do not use the process-local cache of File objects
*
- * @return File, or false if the file does not exist
+ * @return File|bool File, or false if the file does not exist
*/
function wfFindFile( $title, $options = array() ) {
return RepoGroup::singleton()->findFile( $title, $options );
@@ -3538,7 +3679,7 @@ function wfFindFile( $title, $options = array() ) {
* Get an object referring to a locally registered file.
* Returns a valid placeholder object if the file does not exist.
*
- * @param $title Title|String
+ * @param Title|string $title
* @return LocalFile|null A File, or null if passed an invalid Title
*/
function wfLocalFile( $title ) {
@@ -3546,18 +3687,9 @@ function wfLocalFile( $title ) {
}
/**
- * Stream a file to the browser. Back-compat alias for StreamFile::stream()
- * @deprecated since 1.19
- */
-function wfStreamFile( $fname, $headers = array() ) {
- wfDeprecated( __FUNCTION__, '1.19' );
- StreamFile::stream( $fname, $headers );
-}
-
-/**
* Should low-performance queries be disabled?
*
- * @return Boolean
+ * @return bool
* @codeCoverageIgnore
*/
function wfQueriesMustScale() {
@@ -3573,8 +3705,8 @@ function wfQueriesMustScale() {
* extensions; this is a wrapper around $wgScriptExtension etc.
* except for 'index' and 'load' which use $wgScript/$wgLoadScript
*
- * @param string $script script filename, sans extension
- * @return String
+ * @param string $script Script filename, sans extension
+ * @return string
*/
function wfScript( $script = 'index' ) {
global $wgScriptPath, $wgScriptExtension, $wgScript, $wgLoadScript;
@@ -3590,7 +3722,7 @@ function wfScript( $script = 'index' ) {
/**
* Get the script URL.
*
- * @return string script URL
+ * @return string Script URL
*/
function wfGetScriptUrl() {
if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
@@ -3614,8 +3746,8 @@ function wfGetScriptUrl() {
* Convenience function converts boolean values into "true"
* or "false" (string) values
*
- * @param $value Boolean
- * @return String
+ * @param bool $value
+ * @return string
*/
function wfBoolToStr( $value ) {
return $value ? 'true' : 'false';
@@ -3627,9 +3759,7 @@ function wfBoolToStr( $value ) {
* @return string
*/
function wfGetNull() {
- return wfIsWindows()
- ? 'NUL'
- : '/dev/null';
+ return wfIsWindows() ? 'NUL' : '/dev/null';
}
/**
@@ -3639,51 +3769,52 @@ function wfGetNull() {
* in maintenance scripts, to avoid causing too much lag. Of course, this is
* a no-op if there are no slaves.
*
- * @param $maxLag Integer (deprecated)
- * @param $wiki mixed Wiki identifier accepted by wfGetLB
- * @param $cluster string cluster name accepted by LBFactory
+ * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
+ * @param string|bool $wiki Wiki identifier accepted by wfGetLB
+ * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
+ * @return bool Success (able to connect and no timeouts reached)
*/
-function wfWaitForSlaves( $maxLag = false, $wiki = false, $cluster = false ) {
- $lb = ( $cluster !== false )
- ? wfGetLBFactory()->getExternalLB( $cluster )
- : wfGetLB( $wiki );
+function wfWaitForSlaves( $ifWritesSince = false, $wiki = false, $cluster = false ) {
+ // B/C: first argument used to be "max seconds of lag"; ignore such values
+ $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : false;
+
+ if ( $cluster !== false ) {
+ $lb = wfGetLBFactory()->getExternalLB( $cluster );
+ } else {
+ $lb = wfGetLB( $wiki );
+ }
+
// bug 27975 - Don't try to wait for slaves if there are none
// Prevents permission error when getting master position
if ( $lb->getServerCount() > 1 ) {
+ if ( $ifWritesSince && !$lb->hasMasterConnection() ) {
+ return true; // assume no writes done
+ }
$dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
+ if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) {
+ return true; // no writes since the last wait
+ }
$pos = $dbw->getMasterPos();
// The DBMS may not support getMasterPos() or the whole
// load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
if ( $pos !== false ) {
- $lb->waitForAll( $pos );
+ return $lb->waitForAll( $pos, PHP_SAPI === 'cli' ? 86400 : null );
}
}
-}
-/**
- * Used to be used for outputting text in the installer/updater
- * @deprecated since 1.18, warnings in 1.18, remove in 1.20
- */
-function wfOut( $s ) {
- wfDeprecated( __FUNCTION__, '1.18' );
- global $wgCommandLineMode;
- if ( $wgCommandLineMode ) {
- echo $s;
- } else {
- echo htmlspecialchars( $s );
- }
- flush();
+ return true;
}
/**
- * Count down from $n to zero on the terminal, with a one-second pause
+ * Count down from $seconds to zero on the terminal, with a one-second pause
* between showing each number. For use in command-line scripts.
+ *
* @codeCoverageIgnore
- * @param $n int
+ * @param int $seconds
*/
-function wfCountDown( $n ) {
- for ( $i = $n; $i >= 0; $i-- ) {
- if ( $i != $n ) {
+function wfCountDown( $seconds ) {
+ for ( $i = $seconds; $i >= 0; $i-- ) {
+ if ( $i != $seconds ) {
echo str_repeat( "\x08", strlen( $i + 1 ) );
}
echo $i;
@@ -3696,27 +3827,12 @@ function wfCountDown( $n ) {
}
/**
- * Generate a random 32-character hexadecimal token.
- * @param $salt Mixed: some sort of salt, if necessary, to add to random
- * characters before hashing.
- * @return string
- * @codeCoverageIgnore
- * @deprecated since 1.20; Please use MWCryptRand for security purposes and wfRandomString for pseudo-random strings
- * @warning This method is NOT secure. Additionally it has many callers that use it for pseudo-random purposes.
- */
-function wfGenerateToken( $salt = '' ) {
- wfDeprecated( __METHOD__, '1.20' );
- $salt = serialize( $salt );
- return md5( mt_rand( 0, 0x7fffffff ) . $salt );
-}
-
-/**
* Replace all invalid characters with -
* Additional characters can be defined in $wgIllegalFileChars (see bug 20489)
* By default, $wgIllegalFileChars = ':'
*
- * @param $name Mixed: filename to process
- * @return String
+ * @param string $name Filename to process
+ * @return string
*/
function wfStripIllegalFilenameChars( $name ) {
global $wgIllegalFileChars;
@@ -3733,7 +3849,7 @@ function wfStripIllegalFilenameChars( $name ) {
/**
* Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit;
*
- * @return Integer value memory was set to.
+ * @return int Value the memory limit was set to.
*/
function wfMemoryLimit() {
global $wgMemoryLimit;
@@ -3760,8 +3876,8 @@ function wfMemoryLimit() {
/**
* Converts shorthand byte notation to integer form
*
- * @param $string String
- * @return Integer
+ * @param string $string
+ * @return int
*/
function wfShorthandToInteger( $string = '' ) {
$string = trim( $string );
@@ -3792,7 +3908,7 @@ function wfShorthandToInteger( $string = '' ) {
* See unit test for examples.
*
* @param string $code The language code.
- * @return String: The language code which complying with BCP 47 standards.
+ * @return string The language code which complying with BCP 47 standards.
*/
function wfBCP47( $code ) {
$codeSegment = explode( '-', $code );
@@ -3819,7 +3935,7 @@ function wfBCP47( $code ) {
/**
* Get a cache object.
*
- * @param $inputType integer Cache type, one the the CACHE_* constants.
+ * @param int $inputType Cache type, one the the CACHE_* constants.
* @return BagOStuff
*/
function wfGetCache( $inputType ) {
@@ -3869,26 +3985,28 @@ function wfGetLangConverterCacheStorage() {
/**
* Call hook functions defined in $wgHooks
*
- * @param string $event event name
- * @param array $args parameters passed to hook functions
- * @return Boolean True if no handler aborted the hook
+ * @param string $event Event name
+ * @param array $args Parameters passed to hook functions
+ * @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number
+ *
+ * @return bool True if no handler aborted the hook
*/
-function wfRunHooks( $event, array $args = array() ) {
- return Hooks::run( $event, $args );
+function wfRunHooks( $event, array $args = array(), $deprecatedVersion = null ) {
+ return Hooks::run( $event, $args, $deprecatedVersion );
}
/**
* Wrapper around php's unpack.
*
* @param string $format The format string (See php's docs)
- * @param $data: A binary string of binary data
- * @param $length integer or false: The minimum length of $data. This is to
+ * @param string $data A binary string of binary data
+ * @param int|bool $length The minimum length of $data or false. This is to
* prevent reading beyond the end of $data. false to disable the check.
*
* Also be careful when using this function to read unsigned 32 bit integer
* because php might make it negative.
*
- * @throws MWException if $data not long enough, or if unpack fails
+ * @throws MWException If $data not long enough, or if unpack fails
* @return array Associative array of the extracted data
*/
function wfUnpack( $format, $data, $length = false ) {
@@ -3922,9 +4040,9 @@ function wfUnpack( $format, $data, $length = false ) {
* * Any subsequent links on the same line are considered to be exceptions,
* i.e. articles where the image may occur inline.
*
- * @param string $name the image name to check
- * @param $contextTitle Title|bool the page on which the image occurs, if known
- * @param string $blacklist wikitext of a file blacklist
+ * @param string $name The image name to check
+ * @param Title|bool $contextTitle The page on which the image occurs, if known
+ * @param string $blacklist Wikitext of a file blacklist
* @return bool
*/
function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
@@ -3999,10 +4117,48 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
* access the wiki via HTTPS.
*
* @param string $ip The IPv4/6 address in the normal human-readable form
- * @return boolean
+ * @return bool
*/
function wfCanIPUseHTTPS( $ip ) {
$canDo = true;
wfRunHooks( 'CanIPUseHTTPS', array( $ip, &$canDo ) );
return !!$canDo;
}
+
+/**
+ * Work out the IP address based on various globals
+ * For trusted proxies, use the XFF client IP (first of the chain)
+ *
+ * @deprecated since 1.19; call $wgRequest->getIP() directly.
+ * @return string
+ */
+function wfGetIP() {
+ wfDeprecated( __METHOD__, '1.19' );
+ global $wgRequest;
+ return $wgRequest->getIP();
+}
+
+/**
+ * Checks if an IP is a trusted proxy provider.
+ * Useful to tell if X-Forwarded-For data is possibly bogus.
+ * Squid cache servers for the site are whitelisted.
+ * @deprecated Since 1.24, use IP::isTrustedProxy()
+ *
+ * @param string $ip
+ * @return bool
+ */
+function wfIsTrustedProxy( $ip ) {
+ return IP::isTrustedProxy( $ip );
+}
+
+/**
+ * Checks if an IP matches a proxy we've configured.
+ * @deprecated Since 1.24, use IP::isConfiguredProxy()
+ *
+ * @param string $ip
+ * @return bool
+ * @since 1.23 Supports CIDR ranges in $wgSquidServersNoPurge
+ */
+function wfIsConfiguredProxy( $ip ) {
+ return IP::isConfiguredProxy( $ip );
+}
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
deleted file mode 100644
index d260862c..00000000
--- a/includes/HTMLForm.php
+++ /dev/null
@@ -1,2965 +0,0 @@
-<?php
-/**
- * HTML form generation and submission handling.
- *
- * 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
- */
-
-/**
- * Object handling generic submission, CSRF protection, layout and
- * other logic for UI forms. in a reusable manner.
- *
- * In order to generate the form, the HTMLForm object takes an array
- * structure detailing the form fields available. Each element of the
- * array is a basic property-list, including the type of field, the
- * label it is to be given in the form, callbacks for validation and
- * 'filtering', and other pertinent information.
- *
- * Field types are implemented as subclasses of the generic HTMLFormField
- * object, and typically implement at least getInputHTML, which generates
- * the HTML for the input field to be placed in the table.
- *
- * You can find extensive documentation on the www.mediawiki.org wiki:
- * - http://www.mediawiki.org/wiki/HTMLForm
- * - http://www.mediawiki.org/wiki/HTMLForm/tutorial
- *
- * The constructor input is an associative array of $fieldname => $info,
- * where $info is an Associative Array with any of the following:
- *
- * 'class' -- the subclass of HTMLFormField that will be used
- * to create the object. *NOT* the CSS class!
- * 'type' -- roughly translates into the <select> type attribute.
- * if 'class' is not specified, this is used as a map
- * through HTMLForm::$typeMappings to get the class name.
- * 'default' -- default value when the form is displayed
- * 'id' -- HTML id attribute
- * 'cssclass' -- CSS class
- * 'options' -- varies according to the specific object.
- * 'label-message' -- message key for a message to use as the label.
- * can be an array of msg key and then parameters to
- * the message.
- * 'label' -- alternatively, a raw text message. Overridden by
- * label-message
- * 'help' -- message text for a message to use as a help text.
- * 'help-message' -- message key for a message to use as a help text.
- * can be an array of msg key and then parameters to
- * the message.
- * Overwrites 'help-messages' and 'help'.
- * 'help-messages' -- array of message key. As above, each item can
- * be an array of msg key and then parameters.
- * Overwrites 'help'.
- * 'required' -- passed through to the object, indicating that it
- * is a required field.
- * 'size' -- the length of text fields
- * 'filter-callback -- a function name to give you the chance to
- * massage the inputted value before it's processed.
- * @see HTMLForm::filter()
- * 'validation-callback' -- a function name to give you the chance
- * to impose extra validation on the field input.
- * @see HTMLForm::validate()
- * 'name' -- By default, the 'name' attribute of the input field
- * is "wp{$fieldname}". If you want a different name
- * (eg one without the "wp" prefix), specify it here and
- * it will be used without modification.
- *
- * Since 1.20, you can chain mutators to ease the form generation:
- * @par Example:
- * @code
- * $form = new HTMLForm( $someFields );
- * $form->setMethod( 'get' )
- * ->setWrapperLegendMsg( 'message-key' )
- * ->prepareForm()
- * ->displayForm( '' );
- * @endcode
- * Note that you will have prepareForm and displayForm at the end. Other
- * methods call done after that would simply not be part of the form :(
- *
- * TODO: Document 'section' / 'subsection' stuff
- */
-class HTMLForm extends ContextSource {
-
- // A mapping of 'type' inputs onto standard HTMLFormField subclasses
- public static $typeMappings = array(
- 'api' => 'HTMLApiField',
- 'text' => 'HTMLTextField',
- 'textarea' => 'HTMLTextAreaField',
- 'select' => 'HTMLSelectField',
- 'radio' => 'HTMLRadioField',
- 'multiselect' => 'HTMLMultiSelectField',
- 'check' => 'HTMLCheckField',
- 'toggle' => 'HTMLCheckField',
- 'int' => 'HTMLIntField',
- 'float' => 'HTMLFloatField',
- 'info' => 'HTMLInfoField',
- 'selectorother' => 'HTMLSelectOrOtherField',
- 'selectandother' => 'HTMLSelectAndOtherField',
- 'submit' => 'HTMLSubmitField',
- 'hidden' => 'HTMLHiddenField',
- 'edittools' => 'HTMLEditTools',
- 'checkmatrix' => 'HTMLCheckMatrix',
-
- // HTMLTextField will output the correct type="" attribute automagically.
- // There are about four zillion other HTML5 input types, like url, but
- // we don't use those at the moment, so no point in adding all of them.
- 'email' => 'HTMLTextField',
- 'password' => 'HTMLTextField',
- );
-
- protected $mMessagePrefix;
-
- /** @var HTMLFormField[] */
- protected $mFlatFields;
-
- protected $mFieldTree;
- protected $mShowReset = false;
- protected $mShowSubmit = true;
- public $mFieldData;
-
- protected $mSubmitCallback;
- protected $mValidationErrorMessage;
-
- protected $mPre = '';
- protected $mHeader = '';
- protected $mFooter = '';
- protected $mSectionHeaders = array();
- protected $mSectionFooters = array();
- protected $mPost = '';
- protected $mId;
- protected $mTableId = '';
-
- protected $mSubmitID;
- protected $mSubmitName;
- protected $mSubmitText;
- protected $mSubmitTooltip;
-
- protected $mTitle;
- protected $mMethod = 'post';
-
- /**
- * Form action URL. false means we will use the URL to set Title
- * @since 1.19
- * @var bool|string
- */
- protected $mAction = false;
-
- protected $mUseMultipart = false;
- protected $mHiddenFields = array();
- protected $mButtons = array();
-
- protected $mWrapperLegend = false;
-
- /**
- * If true, sections that contain both fields and subsections will
- * render their subsections before their fields.
- *
- * Subclasses may set this to false to render subsections after fields
- * instead.
- */
- protected $mSubSectionBeforeFields = true;
-
- /**
- * Format in which to display form. For viable options,
- * @see $availableDisplayFormats
- * @var String
- */
- protected $displayFormat = 'table';
-
- /**
- * Available formats in which to display the form
- * @var Array
- */
- protected $availableDisplayFormats = array(
- 'table',
- 'div',
- 'raw',
- 'vform',
- );
-
- /**
- * Build a new HTMLForm from an array of field attributes
- * @param array $descriptor of Field constructs, as described above
- * @param $context IContextSource available since 1.18, will become compulsory in 1.18.
- * Obviates the need to call $form->setTitle()
- * @param string $messagePrefix a prefix to go in front of default messages
- */
- public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) {
- if ( $context instanceof IContextSource ) {
- $this->setContext( $context );
- $this->mTitle = false; // We don't need them to set a title
- $this->mMessagePrefix = $messagePrefix;
- } elseif ( is_null( $context ) && $messagePrefix !== '' ) {
- $this->mMessagePrefix = $messagePrefix;
- } elseif ( is_string( $context ) && $messagePrefix === '' ) {
- // B/C since 1.18
- // it's actually $messagePrefix
- $this->mMessagePrefix = $context;
- }
-
- // Expand out into a tree.
- $loadedDescriptor = array();
- $this->mFlatFields = array();
-
- foreach ( $descriptor as $fieldname => $info ) {
- $section = isset( $info['section'] )
- ? $info['section']
- : '';
-
- if ( isset( $info['type'] ) && $info['type'] == 'file' ) {
- $this->mUseMultipart = true;
- }
-
- $field = self::loadInputFromParameters( $fieldname, $info );
- // FIXME During field's construct, the parent form isn't available!
- // could add a 'parent' name-value to $info, could add a third parameter.
- $field->mParent = $this;
-
- // vform gets too much space if empty labels generate HTML.
- if ( $this->isVForm() ) {
- $field->setShowEmptyLabel( false );
- }
-
- $setSection =& $loadedDescriptor;
- if ( $section ) {
- $sectionParts = explode( '/', $section );
-
- while ( count( $sectionParts ) ) {
- $newName = array_shift( $sectionParts );
-
- if ( !isset( $setSection[$newName] ) ) {
- $setSection[$newName] = array();
- }
-
- $setSection =& $setSection[$newName];
- }
- }
-
- $setSection[$fieldname] = $field;
- $this->mFlatFields[$fieldname] = $field;
- }
-
- $this->mFieldTree = $loadedDescriptor;
- }
-
- /**
- * Set format in which to display the form
- * @param string $format the name of the format to use, must be one of
- * $this->availableDisplayFormats
- * @throws MWException
- * @since 1.20
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setDisplayFormat( $format ) {
- if ( !in_array( $format, $this->availableDisplayFormats ) ) {
- throw new MWException( 'Display format must be one of ' . print_r( $this->availableDisplayFormats, true ) );
- }
- $this->displayFormat = $format;
- return $this;
- }
-
- /**
- * Getter for displayFormat
- * @since 1.20
- * @return String
- */
- public function getDisplayFormat() {
- return $this->displayFormat;
- }
-
- /**
- * Test if displayFormat is 'vform'
- * @since 1.22
- * @return Bool
- */
- public function isVForm() {
- return $this->displayFormat === 'vform';
- }
-
- /**
- * Add the HTMLForm-specific JavaScript, if it hasn't been
- * done already.
- * @deprecated since 1.18 load modules with ResourceLoader instead
- */
- static function addJS() {
- wfDeprecated( __METHOD__, '1.18' );
- }
-
- /**
- * Initialise a new Object for the field
- * @param $fieldname string
- * @param string $descriptor input Descriptor, as described above
- * @throws MWException
- * @return HTMLFormField subclass
- */
- static function loadInputFromParameters( $fieldname, $descriptor ) {
- if ( isset( $descriptor['class'] ) ) {
- $class = $descriptor['class'];
- } elseif ( isset( $descriptor['type'] ) ) {
- $class = self::$typeMappings[$descriptor['type']];
- $descriptor['class'] = $class;
- } else {
- $class = null;
- }
-
- if ( !$class ) {
- throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) );
- }
-
- $descriptor['fieldname'] = $fieldname;
-
- # TODO
- # This will throw a fatal error whenever someone try to use
- # 'class' to feed a CSS class instead of 'cssclass'. Would be
- # great to avoid the fatal error and show a nice error.
- $obj = new $class( $descriptor );
-
- return $obj;
- }
-
- /**
- * Prepare form for submission.
- *
- * @attention When doing method chaining, that should be the very last
- * method call before displayForm().
- *
- * @throws MWException
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function prepareForm() {
- # Check if we have the info we need
- if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
- throw new MWException( "You must call setTitle() on an HTMLForm" );
- }
-
- # Load data from the request.
- $this->loadData();
- return $this;
- }
-
- /**
- * Try submitting, with edit token check first
- * @return Status|boolean
- */
- function tryAuthorizedSubmit() {
- $result = false;
-
- $submit = false;
- if ( $this->getMethod() != 'post' ) {
- $submit = true; // no session check needed
- } elseif ( $this->getRequest()->wasPosted() ) {
- $editToken = $this->getRequest()->getVal( 'wpEditToken' );
- if ( $this->getUser()->isLoggedIn() || $editToken != null ) {
- // Session tokens for logged-out users have no security value.
- // However, if the user gave one, check it in order to give a nice
- // "session expired" error instead of "permission denied" or such.
- $submit = $this->getUser()->matchEditToken( $editToken );
- } else {
- $submit = true;
- }
- }
-
- if ( $submit ) {
- $result = $this->trySubmit();
- }
-
- return $result;
- }
-
- /**
- * The here's-one-I-made-earlier option: do the submission if
- * posted, or display the form with or without funky validation
- * errors
- * @return Bool or Status whether submission was successful.
- */
- function show() {
- $this->prepareForm();
-
- $result = $this->tryAuthorizedSubmit();
- if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
- return $result;
- }
-
- $this->displayForm( $result );
- return false;
- }
-
- /**
- * Validate all the fields, and call the submission callback
- * function if everything is kosher.
- * @throws MWException
- * @return Mixed Bool true == Successful submission, Bool false
- * == No submission attempted, anything else == Error to
- * display.
- */
- function trySubmit() {
- # Check for validation
- foreach ( $this->mFlatFields as $fieldname => $field ) {
- if ( !empty( $field->mParams['nodata'] ) ) {
- continue;
- }
- if ( $field->validate(
- $this->mFieldData[$fieldname],
- $this->mFieldData )
- !== true
- ) {
- return isset( $this->mValidationErrorMessage )
- ? $this->mValidationErrorMessage
- : array( 'htmlform-invalid-input' );
- }
- }
-
- $callback = $this->mSubmitCallback;
- if ( !is_callable( $callback ) ) {
- throw new MWException( 'HTMLForm: no submit callback provided. Use setSubmitCallback() to set one.' );
- }
-
- $data = $this->filterDataForSubmit( $this->mFieldData );
-
- $res = call_user_func( $callback, $data, $this );
-
- return $res;
- }
-
- /**
- * Set a callback to a function to do something with the form
- * once it's been successfully validated.
- * @param string $cb function name. The function will be passed
- * the output from HTMLForm::filterDataForSubmit, and must
- * return Bool true on success, Bool false if no submission
- * was attempted, or String HTML output to display on error.
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setSubmitCallback( $cb ) {
- $this->mSubmitCallback = $cb;
- return $this;
- }
-
- /**
- * Set a message to display on a validation error.
- * @param $msg Mixed String or Array of valid inputs to wfMessage()
- * (so each entry can be either a String or Array)
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setValidationErrorMessage( $msg ) {
- $this->mValidationErrorMessage = $msg;
- return $this;
- }
-
- /**
- * Set the introductory message, overwriting any existing message.
- * @param string $msg complete text of message to display
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setIntro( $msg ) {
- $this->setPreText( $msg );
- return $this;
- }
-
- /**
- * Set the introductory message, overwriting any existing message.
- * @since 1.19
- * @param string $msg complete text of message to display
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setPreText( $msg ) {
- $this->mPre = $msg;
- return $this;
- }
-
- /**
- * Add introductory text.
- * @param string $msg complete text of message to display
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function addPreText( $msg ) {
- $this->mPre .= $msg;
- return $this;
- }
-
- /**
- * Add header text, inside the form.
- * @param string $msg complete text of message to display
- * @param string $section The section to add the header to
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function addHeaderText( $msg, $section = null ) {
- if ( is_null( $section ) ) {
- $this->mHeader .= $msg;
- } else {
- if ( !isset( $this->mSectionHeaders[$section] ) ) {
- $this->mSectionHeaders[$section] = '';
- }
- $this->mSectionHeaders[$section] .= $msg;
- }
- return $this;
- }
-
- /**
- * Set header text, inside the form.
- * @since 1.19
- * @param string $msg complete text of message to display
- * @param $section The section to add the header to
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setHeaderText( $msg, $section = null ) {
- if ( is_null( $section ) ) {
- $this->mHeader = $msg;
- } else {
- $this->mSectionHeaders[$section] = $msg;
- }
- return $this;
- }
-
- /**
- * Add footer text, inside the form.
- * @param string $msg complete text of message to display
- * @param string $section The section to add the footer text to
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function addFooterText( $msg, $section = null ) {
- if ( is_null( $section ) ) {
- $this->mFooter .= $msg;
- } else {
- if ( !isset( $this->mSectionFooters[$section] ) ) {
- $this->mSectionFooters[$section] = '';
- }
- $this->mSectionFooters[$section] .= $msg;
- }
- return $this;
- }
-
- /**
- * Set footer text, inside the form.
- * @since 1.19
- * @param string $msg complete text of message to display
- * @param string $section The section to add the footer text to
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setFooterText( $msg, $section = null ) {
- if ( is_null( $section ) ) {
- $this->mFooter = $msg;
- } else {
- $this->mSectionFooters[$section] = $msg;
- }
- return $this;
- }
-
- /**
- * Add text to the end of the display.
- * @param string $msg complete text of message to display
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function addPostText( $msg ) {
- $this->mPost .= $msg;
- return $this;
- }
-
- /**
- * Set text at the end of the display.
- * @param string $msg complete text of message to display
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setPostText( $msg ) {
- $this->mPost = $msg;
- return $this;
- }
-
- /**
- * Add a hidden field to the output
- * @param string $name field name. This will be used exactly as entered
- * @param string $value field value
- * @param $attribs Array
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function addHiddenField( $name, $value, $attribs = array() ) {
- $attribs += array( 'name' => $name );
- $this->mHiddenFields[] = array( $value, $attribs );
- return $this;
- }
-
- /**
- * Add an array of hidden fields to the output
- *
- * @since 1.22
- * @param array $fields Associative array of fields to add;
- * mapping names to their values
- * @return HTMLForm $this for chaining calls
- */
- public function addHiddenFields( array $fields ) {
- foreach ( $fields as $name => $value ) {
- $this->mHiddenFields[] = array( $value, array( 'name' => $name ) );
- }
- return $this;
- }
-
- /**
- * Add a button to the form
- * @param string $name field name.
- * @param string $value field value
- * @param string $id DOM id for the button (default: null)
- * @param $attribs Array
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function addButton( $name, $value, $id = null, $attribs = null ) {
- $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
- return $this;
- }
-
- /**
- * Display the form (sending to the context's OutputPage object), with an
- * appropriate error message or stack of messages, and any validation errors, etc.
- *
- * @attention You should call prepareForm() before calling this function.
- * Moreover, when doing method chaining this should be the very last method
- * call just after prepareForm().
- *
- * @param $submitResult Mixed output from HTMLForm::trySubmit()
- * @return Nothing, should be last call
- */
- function displayForm( $submitResult ) {
- $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
- }
-
- /**
- * Returns the raw HTML generated by the form
- * @param $submitResult Mixed output from HTMLForm::trySubmit()
- * @return string
- */
- function getHTML( $submitResult ) {
- # For good measure (it is the default)
- $this->getOutput()->preventClickjacking();
- $this->getOutput()->addModules( 'mediawiki.htmlform' );
- if ( $this->isVForm() ) {
- $this->getOutput()->addModuleStyles( 'mediawiki.ui' );
- // TODO should vertical form set setWrapperLegend( false )
- // to hide ugly fieldsets?
- }
-
- $html = ''
- . $this->getErrors( $submitResult )
- . $this->mHeader
- . $this->getBody()
- . $this->getHiddenFields()
- . $this->getButtons()
- . $this->mFooter
- ;
-
- $html = $this->wrapForm( $html );
-
- return '' . $this->mPre . $html . $this->mPost;
- }
-
- /**
- * Wrap the form innards in an actual "<form>" element
- * @param string $html HTML contents to wrap.
- * @return String wrapped HTML.
- */
- function wrapForm( $html ) {
-
- # Include a <fieldset> wrapper for style, if requested.
- if ( $this->mWrapperLegend !== false ) {
- $html = Xml::fieldset( $this->mWrapperLegend, $html );
- }
- # Use multipart/form-data
- $encType = $this->mUseMultipart
- ? 'multipart/form-data'
- : 'application/x-www-form-urlencoded';
- # Attributes
- $attribs = array(
- 'action' => $this->getAction(),
- 'method' => $this->getMethod(),
- 'class' => array( 'visualClear' ),
- 'enctype' => $encType,
- );
- if ( !empty( $this->mId ) ) {
- $attribs['id'] = $this->mId;
- }
-
- if ( $this->isVForm() ) {
- array_push( $attribs['class'], 'mw-ui-vform', 'mw-ui-container' );
- }
- return Html::rawElement( 'form', $attribs, $html );
- }
-
- /**
- * Get the hidden fields that should go inside the form.
- * @return String HTML.
- */
- function getHiddenFields() {
- global $wgArticlePath;
-
- $html = '';
- if ( $this->getMethod() == 'post' ) {
- $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
- $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
- }
-
- if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() == 'get' ) {
- $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
- }
-
- foreach ( $this->mHiddenFields as $data ) {
- list( $value, $attribs ) = $data;
- $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
- }
-
- return $html;
- }
-
- /**
- * Get the submit and (potentially) reset buttons.
- * @return String HTML.
- */
- function getButtons() {
- $html = '<span class="mw-htmlform-submit-buttons">';
-
- if ( $this->mShowSubmit ) {
- $attribs = array();
-
- if ( isset( $this->mSubmitID ) ) {
- $attribs['id'] = $this->mSubmitID;
- }
-
- if ( isset( $this->mSubmitName ) ) {
- $attribs['name'] = $this->mSubmitName;
- }
-
- if ( isset( $this->mSubmitTooltip ) ) {
- $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
- }
-
- $attribs['class'] = array( 'mw-htmlform-submit' );
-
- if ( $this->isVForm() ) {
- // mw-ui-block is necessary because the buttons aren't necessarily in an
- // immediate child div of the vform.
- array_push( $attribs['class'], 'mw-ui-button', 'mw-ui-big', 'mw-ui-primary', 'mw-ui-block' );
- }
-
- $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
-
- // Buttons are top-level form elements in table and div layouts,
- // but vform wants all elements inside divs to get spaced-out block
- // styling.
- if ( $this->isVForm() ) {
- $html = Html::rawElement( 'div', null, "\n$html\n" );
- }
- }
-
- if ( $this->mShowReset ) {
- $html .= Html::element(
- 'input',
- array(
- 'type' => 'reset',
- 'value' => $this->msg( 'htmlform-reset' )->text()
- )
- ) . "\n";
- }
-
- foreach ( $this->mButtons as $button ) {
- $attrs = array(
- 'type' => 'submit',
- 'name' => $button['name'],
- 'value' => $button['value']
- );
-
- if ( $button['attribs'] ) {
- $attrs += $button['attribs'];
- }
-
- if ( isset( $button['id'] ) ) {
- $attrs['id'] = $button['id'];
- }
-
- $html .= Html::element( 'input', $attrs );
- }
-
- $html .= '</span>';
-
- return $html;
- }
-
- /**
- * Get the whole body of the form.
- * @return String
- */
- function getBody() {
- return $this->displaySection( $this->mFieldTree, $this->mTableId );
- }
-
- /**
- * Format and display an error message stack.
- * @param $errors String|Array|Status
- * @return String
- */
- function getErrors( $errors ) {
- if ( $errors instanceof Status ) {
- if ( $errors->isOK() ) {
- $errorstr = '';
- } else {
- $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
- }
- } elseif ( is_array( $errors ) ) {
- $errorstr = $this->formatErrors( $errors );
- } else {
- $errorstr = $errors;
- }
-
- return $errorstr
- ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr )
- : '';
- }
-
- /**
- * Format a stack of error messages into a single HTML string
- * @param array $errors of message keys/values
- * @return String HTML, a "<ul>" list of errors
- */
- public static function formatErrors( $errors ) {
- $errorstr = '';
-
- foreach ( $errors as $error ) {
- if ( is_array( $error ) ) {
- $msg = array_shift( $error );
- } else {
- $msg = $error;
- $error = array();
- }
-
- $errorstr .= Html::rawElement(
- 'li',
- array(),
- wfMessage( $msg, $error )->parse()
- );
- }
-
- $errorstr = Html::rawElement( 'ul', array(), $errorstr );
-
- return $errorstr;
- }
-
- /**
- * Set the text for the submit button
- * @param string $t plaintext.
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setSubmitText( $t ) {
- $this->mSubmitText = $t;
- return $this;
- }
-
- /**
- * Set the text for the submit button to a message
- * @since 1.19
- * @param string $msg message key
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setSubmitTextMsg( $msg ) {
- $this->setSubmitText( $this->msg( $msg )->text() );
- return $this;
- }
-
- /**
- * Get the text for the submit button, either customised or a default.
- * @return string
- */
- function getSubmitText() {
- return $this->mSubmitText
- ? $this->mSubmitText
- : $this->msg( 'htmlform-submit' )->text();
- }
-
- /**
- * @param string $name Submit button name
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setSubmitName( $name ) {
- $this->mSubmitName = $name;
- return $this;
- }
-
- /**
- * @param string $name Tooltip for the submit button
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setSubmitTooltip( $name ) {
- $this->mSubmitTooltip = $name;
- return $this;
- }
-
- /**
- * Set the id for the submit button.
- * @param $t String.
- * @todo FIXME: Integrity of $t is *not* validated
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setSubmitID( $t ) {
- $this->mSubmitID = $t;
- return $this;
- }
-
- /**
- * Stop a default submit button being shown for this form. This implies that an
- * alternate submit method must be provided manually.
- *
- * @since 1.22
- *
- * @param bool $suppressSubmit Set to false to re-enable the button again
- *
- * @return HTMLForm $this for chaining calls
- */
- function suppressDefaultSubmit( $suppressSubmit = true ) {
- $this->mShowSubmit = !$suppressSubmit;
- return $this;
- }
-
- /**
- * Set the id of the \<table\> or outermost \<div\> element.
- *
- * @since 1.22
- * @param string $id new value of the id attribute, or "" to remove
- * @return HTMLForm $this for chaining calls
- */
- public function setTableId( $id ) {
- $this->mTableId = $id;
- return $this;
- }
-
- /**
- * @param string $id DOM id for the form
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setId( $id ) {
- $this->mId = $id;
- return $this;
- }
-
- /**
- * Prompt the whole form to be wrapped in a "<fieldset>", with
- * this text as its "<legend>" element.
- * @param string|false $legend HTML to go inside the "<legend>" element, or
- * false for no <legend>
- * Will be escaped
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setWrapperLegend( $legend ) {
- $this->mWrapperLegend = $legend;
- return $this;
- }
-
- /**
- * Prompt the whole form to be wrapped in a "<fieldset>", with
- * this message as its "<legend>" element.
- * @since 1.19
- * @param string $msg message key
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setWrapperLegendMsg( $msg ) {
- $this->setWrapperLegend( $this->msg( $msg )->text() );
- return $this;
- }
-
- /**
- * Set the prefix for various default messages
- * @todo currently only used for the "<fieldset>" legend on forms
- * with multiple sections; should be used elsewhere?
- * @param $p String
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setMessagePrefix( $p ) {
- $this->mMessagePrefix = $p;
- return $this;
- }
-
- /**
- * Set the title for form submission
- * @param $t Title of page the form is on/should be posted to
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function setTitle( $t ) {
- $this->mTitle = $t;
- return $this;
- }
-
- /**
- * Get the title
- * @return Title
- */
- function getTitle() {
- return $this->mTitle === false
- ? $this->getContext()->getTitle()
- : $this->mTitle;
- }
-
- /**
- * Set the method used to submit the form
- * @param $method String
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setMethod( $method = 'post' ) {
- $this->mMethod = $method;
- return $this;
- }
-
- public function getMethod() {
- return $this->mMethod;
- }
-
- /**
- * @todo Document
- * @param array[]|HTMLFormField[] $fields array of fields (either arrays or objects)
- * @param string $sectionName ID attribute of the "<table>" tag for this section, ignored if empty
- * @param string $fieldsetIDPrefix ID prefix for the "<fieldset>" tag of each subsection, ignored if empty
- * @param boolean &$hasUserVisibleFields Whether the section had user-visible fields
- * @return String
- */
- public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '', &$hasUserVisibleFields = false ) {
- $displayFormat = $this->getDisplayFormat();
-
- $html = '';
- $subsectionHtml = '';
- $hasLabel = false;
-
- switch( $displayFormat ) {
- case 'table':
- $getFieldHtmlMethod = 'getTableRow';
- break;
- case 'vform':
- // Close enough to a div.
- $getFieldHtmlMethod = 'getDiv';
- break;
- default:
- $getFieldHtmlMethod = 'get' . ucfirst( $displayFormat );
- }
-
- foreach ( $fields as $key => $value ) {
- if ( $value instanceof HTMLFormField ) {
- $v = empty( $value->mParams['nodata'] )
- ? $this->mFieldData[$key]
- : $value->getDefault();
- $html .= $value->$getFieldHtmlMethod( $v );
-
- $labelValue = trim( $value->getLabel() );
- if ( $labelValue != '&#160;' && $labelValue !== '' ) {
- $hasLabel = true;
- }
-
- if ( get_class( $value ) !== 'HTMLHiddenField' &&
- get_class( $value ) !== 'HTMLApiField' ) {
- $hasUserVisibleFields = true;
- }
- } elseif ( is_array( $value ) ) {
- $subsectionHasVisibleFields = false;
- $section = $this->displaySection( $value, "mw-htmlform-$key", "$fieldsetIDPrefix$key-", $subsectionHasVisibleFields );
- $legend = null;
-
- if ( $subsectionHasVisibleFields === true ) {
- // Display the section with various niceties.
- $hasUserVisibleFields = true;
-
- $legend = $this->getLegend( $key );
-
- if ( isset( $this->mSectionHeaders[$key] ) ) {
- $section = $this->mSectionHeaders[$key] . $section;
- }
- if ( isset( $this->mSectionFooters[$key] ) ) {
- $section .= $this->mSectionFooters[$key];
- }
-
- $attributes = array();
- if ( $fieldsetIDPrefix ) {
- $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
- }
- $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
- } else {
- // Just return the inputs, nothing fancy.
- $subsectionHtml .= $section;
- }
- }
- }
-
- if ( $displayFormat !== 'raw' ) {
- $classes = array();
-
- if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
- $classes[] = 'mw-htmlform-nolabel';
- }
-
- $attribs = array(
- 'class' => implode( ' ', $classes ),
- );
-
- if ( $sectionName ) {
- $attribs['id'] = Sanitizer::escapeId( $sectionName );
- }
-
- if ( $displayFormat === 'table' ) {
- $html = Html::rawElement( 'table', $attribs,
- Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
- } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) {
- $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
- }
- }
-
- if ( $this->mSubSectionBeforeFields ) {
- return $subsectionHtml . "\n" . $html;
- } else {
- return $html . "\n" . $subsectionHtml;
- }
- }
-
- /**
- * Construct the form fields from the Descriptor array
- */
- function loadData() {
- $fieldData = array();
-
- foreach ( $this->mFlatFields as $fieldname => $field ) {
- if ( !empty( $field->mParams['nodata'] ) ) {
- continue;
- } elseif ( !empty( $field->mParams['disabled'] ) ) {
- $fieldData[$fieldname] = $field->getDefault();
- } else {
- $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() );
- }
- }
-
- # Filter data.
- foreach ( $fieldData as $name => &$value ) {
- $field = $this->mFlatFields[$name];
- $value = $field->filter( $value, $this->mFlatFields );
- }
-
- $this->mFieldData = $fieldData;
- }
-
- /**
- * Stop a reset button being shown for this form
- * @param bool $suppressReset set to false to re-enable the
- * button again
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- function suppressReset( $suppressReset = true ) {
- $this->mShowReset = !$suppressReset;
- return $this;
- }
-
- /**
- * Overload this if you want to apply special filtration routines
- * to the form as a whole, after it's submitted but before it's
- * processed.
- * @param $data
- * @return
- */
- function filterDataForSubmit( $data ) {
- return $data;
- }
-
- /**
- * Get a string to go in the "<legend>" of a section fieldset.
- * Override this if you want something more complicated.
- * @param $key String
- * @return String
- */
- public function getLegend( $key ) {
- return $this->msg( "{$this->mMessagePrefix}-$key" )->text();
- }
-
- /**
- * Set the value for the action attribute of the form.
- * When set to false (which is the default state), the set title is used.
- *
- * @since 1.19
- *
- * @param string|bool $action
- * @return HTMLForm $this for chaining calls (since 1.20)
- */
- public function setAction( $action ) {
- $this->mAction = $action;
- return $this;
- }
-
- /**
- * Get the value for the action attribute of the form.
- *
- * @since 1.22
- *
- * @return string
- */
- public function getAction() {
- global $wgScript, $wgArticlePath;
-
- // If an action is alredy provided, return it
- if ( $this->mAction !== false ) {
- return $this->mAction;
- }
-
- // Check whether we are in GET mode and $wgArticlePath contains a "?"
- // meaning that getLocalURL() would return something like "index.php?title=...".
- // As browser remove the query string before submitting GET forms,
- // it means that the title would be lost. In such case use $wgScript instead
- // and put title in an hidden field (see getHiddenFields()).
- if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
- return $wgScript;
- }
-
- return $this->getTitle()->getLocalURL();
- }
-}
-
-/**
- * The parent class to generate form fields. Any field type should
- * be a subclass of this.
- */
-abstract class HTMLFormField {
-
- protected $mValidationCallback;
- protected $mFilterCallback;
- protected $mName;
- public $mParams;
- protected $mLabel; # String label. Set on construction
- protected $mID;
- protected $mClass = '';
- protected $mDefault;
-
- /**
- * @var bool If true will generate an empty div element with no label
- * @since 1.22
- */
- protected $mShowEmptyLabels = true;
-
- /**
- * @var HTMLForm
- */
- public $mParent;
-
- /**
- * This function must be implemented to return the HTML to generate
- * the input object itself. It should not implement the surrounding
- * table cells/rows, or labels/help messages.
- * @param string $value the value to set the input to; eg a default
- * text for a text input.
- * @return String valid HTML.
- */
- abstract function getInputHTML( $value );
-
- /**
- * Get a translated interface message
- *
- * This is a wrapper around $this->mParent->msg() if $this->mParent is set
- * and wfMessage() otherwise.
- *
- * Parameters are the same as wfMessage().
- *
- * @return Message object
- */
- function msg() {
- $args = func_get_args();
-
- if ( $this->mParent ) {
- $callback = array( $this->mParent, 'msg' );
- } else {
- $callback = 'wfMessage';
- }
-
- return call_user_func_array( $callback, $args );
- }
-
- /**
- * Override this function to add specific validation checks on the
- * field input. Don't forget to call parent::validate() to ensure
- * that the user-defined callback mValidationCallback is still run
- * @param string $value the value the field was submitted with
- * @param array $alldata the data collected from the form
- * @return Mixed Bool true on success, or String error to display.
- */
- function validate( $value, $alldata ) {
- if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value === '' ) {
- return $this->msg( 'htmlform-required' )->parse();
- }
-
- if ( isset( $this->mValidationCallback ) ) {
- return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent );
- }
-
- return true;
- }
-
- function filter( $value, $alldata ) {
- if ( isset( $this->mFilterCallback ) ) {
- $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent );
- }
-
- return $value;
- }
-
- /**
- * Should this field have a label, or is there no input element with the
- * appropriate id for the label to point to?
- *
- * @return bool True to output a label, false to suppress
- */
- protected function needsLabel() {
- return true;
- }
-
- /**
- * Tell the field whether to generate a separate label element if its label
- * is blank.
- *
- * @since 1.22
- * @param bool $show Set to false to not generate a label.
- * @return void
- */
- public function setShowEmptyLabel( $show ) {
- $this->mShowEmptyLabels = $show;
- }
-
- /**
- * Get the value that this input has been set to from a posted form,
- * or the input's default value if it has not been set.
- * @param $request WebRequest
- * @return String the value
- */
- function loadDataFromRequest( $request ) {
- if ( $request->getCheck( $this->mName ) ) {
- return $request->getText( $this->mName );
- } else {
- return $this->getDefault();
- }
- }
-
- /**
- * Initialise the object
- * @param array $params Associative Array. See HTMLForm doc for syntax.
- *
- * @since 1.22 The 'label' attribute no longer accepts raw HTML, use 'label-raw' instead
- * @throws MWException
- */
- function __construct( $params ) {
- $this->mParams = $params;
-
- # Generate the label from a message, if possible
- if ( isset( $params['label-message'] ) ) {
- $msgInfo = $params['label-message'];
-
- if ( is_array( $msgInfo ) ) {
- $msg = array_shift( $msgInfo );
- } else {
- $msg = $msgInfo;
- $msgInfo = array();
- }
-
- $this->mLabel = wfMessage( $msg, $msgInfo )->parse();
- } elseif ( isset( $params['label'] ) ) {
- if ( $params['label'] === '&#160;' ) {
- // Apparently some things set &nbsp directly and in an odd format
- $this->mLabel = '&#160;';
- } else {
- $this->mLabel = htmlspecialchars( $params['label'] );
- }
- } elseif ( isset( $params['label-raw'] ) ) {
- $this->mLabel = $params['label-raw'];
- }
-
- $this->mName = "wp{$params['fieldname']}";
- if ( isset( $params['name'] ) ) {
- $this->mName = $params['name'];
- }
-
- $validName = Sanitizer::escapeId( $this->mName );
- if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
- throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
- }
-
- $this->mID = "mw-input-{$this->mName}";
-
- if ( isset( $params['default'] ) ) {
- $this->mDefault = $params['default'];
- }
-
- if ( isset( $params['id'] ) ) {
- $id = $params['id'];
- $validId = Sanitizer::escapeId( $id );
-
- if ( $id != $validId ) {
- throw new MWException( "Invalid id '$id' passed to " . __METHOD__ );
- }
-
- $this->mID = $id;
- }
-
- if ( isset( $params['cssclass'] ) ) {
- $this->mClass = $params['cssclass'];
- }
-
- if ( isset( $params['validation-callback'] ) ) {
- $this->mValidationCallback = $params['validation-callback'];
- }
-
- if ( isset( $params['filter-callback'] ) ) {
- $this->mFilterCallback = $params['filter-callback'];
- }
-
- if ( isset( $params['flatlist'] ) ) {
- $this->mClass .= ' mw-htmlform-flatlist';
- }
-
- if ( isset( $params['hidelabel'] ) ) {
- $this->mShowEmptyLabels = false;
- }
- }
-
- /**
- * Get the complete table row for the input, including help text,
- * labels, and whatever.
- * @param string $value the value to set the input to.
- * @return String complete HTML table row.
- */
- function getTableRow( $value ) {
- list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
- $inputHtml = $this->getInputHTML( $value );
- $fieldType = get_class( $this );
- $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
- $cellAttributes = array();
-
- if ( !empty( $this->mParams['vertical-label'] ) ) {
- $cellAttributes['colspan'] = 2;
- $verticalLabel = true;
- } else {
- $verticalLabel = false;
- }
-
- $label = $this->getLabelHtml( $cellAttributes );
-
- $field = Html::rawElement(
- 'td',
- array( 'class' => 'mw-input' ) + $cellAttributes,
- $inputHtml . "\n$errors"
- );
-
- if ( $verticalLabel ) {
- $html = Html::rawElement( 'tr',
- array( 'class' => 'mw-htmlform-vertical-label' ), $label );
- $html .= Html::rawElement( 'tr',
- array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
- $field );
- } else {
- $html = Html::rawElement( 'tr',
- array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
- $label . $field );
- }
-
- return $html . $helptext;
- }
-
- /**
- * Get the complete div for the input, including help text,
- * labels, and whatever.
- * @since 1.20
- * @param string $value the value to set the input to.
- * @return String complete HTML table row.
- */
- public function getDiv( $value ) {
- list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
- $inputHtml = $this->getInputHTML( $value );
- $fieldType = get_class( $this );
- $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
- $cellAttributes = array();
- $label = $this->getLabelHtml( $cellAttributes );
-
- $outerDivClass = array(
- 'mw-input',
- 'mw-htmlform-nolabel' => ( $label === '' )
- );
-
- $field = Html::rawElement(
- 'div',
- array( 'class' => $outerDivClass ) + $cellAttributes,
- $inputHtml . "\n$errors"
- );
- $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass, $errorClass );
- if ( $this->mParent->isVForm() ) {
- $divCssClasses[] = 'mw-ui-vform-div';
- }
- $html = Html::rawElement( 'div',
- array( 'class' => $divCssClasses ),
- $label . $field );
- $html .= $helptext;
- return $html;
- }
-
- /**
- * Get the complete raw fields for the input, including help text,
- * labels, and whatever.
- * @since 1.20
- * @param string $value the value to set the input to.
- * @return String complete HTML table row.
- */
- public function getRaw( $value ) {
- list( $errors, ) = $this->getErrorsAndErrorClass( $value );
- $inputHtml = $this->getInputHTML( $value );
- $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
- $cellAttributes = array();
- $label = $this->getLabelHtml( $cellAttributes );
-
- $html = "\n$errors";
- $html .= $label;
- $html .= $inputHtml;
- $html .= $helptext;
- return $html;
- }
-
- /**
- * Generate help text HTML in table format
- * @since 1.20
- * @param $helptext String|null
- * @return String
- */
- public function getHelpTextHtmlTable( $helptext ) {
- if ( is_null( $helptext ) ) {
- return '';
- }
-
- $row = Html::rawElement(
- 'td',
- array( 'colspan' => 2, 'class' => 'htmlform-tip' ),
- $helptext
- );
- $row = Html::rawElement( 'tr', array(), $row );
- return $row;
- }
-
- /**
- * Generate help text HTML in div format
- * @since 1.20
- * @param $helptext String|null
- * @return String
- */
- public function getHelpTextHtmlDiv( $helptext ) {
- if ( is_null( $helptext ) ) {
- return '';
- }
-
- $div = Html::rawElement( 'div', array( 'class' => 'htmlform-tip' ), $helptext );
- return $div;
- }
-
- /**
- * Generate help text HTML formatted for raw output
- * @since 1.20
- * @param $helptext String|null
- * @return String
- */
- public function getHelpTextHtmlRaw( $helptext ) {
- return $this->getHelpTextHtmlDiv( $helptext );
- }
-
- /**
- * Determine the help text to display
- * @since 1.20
- * @return String
- */
- public function getHelpText() {
- $helptext = null;
-
- if ( isset( $this->mParams['help-message'] ) ) {
- $this->mParams['help-messages'] = array( $this->mParams['help-message'] );
- }
-
- if ( isset( $this->mParams['help-messages'] ) ) {
- foreach ( $this->mParams['help-messages'] as $name ) {
- $helpMessage = (array)$name;
- $msg = $this->msg( array_shift( $helpMessage ), $helpMessage );
-
- if ( $msg->exists() ) {
- if ( is_null( $helptext ) ) {
- $helptext = '';
- } else {
- $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
- }
- $helptext .= $msg->parse(); // Append message
- }
- }
- }
- elseif ( isset( $this->mParams['help'] ) ) {
- $helptext = $this->mParams['help'];
- }
- return $helptext;
- }
-
- /**
- * Determine form errors to display and their classes
- * @since 1.20
- * @param string $value the value of the input
- * @return Array
- */
- public function getErrorsAndErrorClass( $value ) {
- $errors = $this->validate( $value, $this->mParent->mFieldData );
-
- if ( $errors === true || ( !$this->mParent->getRequest()->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) {
- $errors = '';
- $errorClass = '';
- } else {
- $errors = self::formatErrors( $errors );
- $errorClass = 'mw-htmlform-invalid-input';
- }
- return array( $errors, $errorClass );
- }
-
- function getLabel() {
- return is_null( $this->mLabel ) ? '' : $this->mLabel;
- }
-
- function getLabelHtml( $cellAttributes = array() ) {
- # Don't output a for= attribute for labels with no associated input.
- # Kind of hacky here, possibly we don't want these to be <label>s at all.
- $for = array();
-
- if ( $this->needsLabel() ) {
- $for['for'] = $this->mID;
- }
-
- $labelValue = trim( $this->getLabel() );
- $hasLabel = false;
- if ( $labelValue !== '&#160;' && $labelValue !== '' ) {
- $hasLabel = true;
- }
-
- $displayFormat = $this->mParent->getDisplayFormat();
- $html = '';
-
- if ( $displayFormat === 'table' ) {
- $html = Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
- Html::rawElement( 'label', $for, $labelValue )
- );
- } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
- if ( $displayFormat === 'div' ) {
- $html = Html::rawElement(
- 'div',
- array( 'class' => 'mw-label' ) + $cellAttributes,
- Html::rawElement( 'label', $for, $labelValue )
- );
- } else {
- $html = Html::rawElement( 'label', $for, $labelValue );
- }
- }
-
- return $html;
- }
-
- function getDefault() {
- if ( isset( $this->mDefault ) ) {
- return $this->mDefault;
- } else {
- return null;
- }
- }
-
- /**
- * Returns the attributes required for the tooltip and accesskey.
- *
- * @return array Attributes
- */
- public function getTooltipAndAccessKey() {
- if ( empty( $this->mParams['tooltip'] ) ) {
- return array();
- }
- return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
- }
-
- /**
- * flatten an array of options to a single array, for instance,
- * a set of "<options>" inside "<optgroups>".
- * @param array $options Associative Array with values either Strings
- * or Arrays
- * @return Array flattened input
- */
- public static function flattenOptions( $options ) {
- $flatOpts = array();
-
- foreach ( $options as $value ) {
- if ( is_array( $value ) ) {
- $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
- } else {
- $flatOpts[] = $value;
- }
- }
-
- return $flatOpts;
- }
-
- /**
- * Formats one or more errors as accepted by field validation-callback.
- * @param $errors String|Message|Array of strings or Message instances
- * @return String html
- * @since 1.18
- */
- protected static function formatErrors( $errors ) {
- if ( is_array( $errors ) && count( $errors ) === 1 ) {
- $errors = array_shift( $errors );
- }
-
- if ( is_array( $errors ) ) {
- $lines = array();
- foreach ( $errors as $error ) {
- if ( $error instanceof Message ) {
- $lines[] = Html::rawElement( 'li', array(), $error->parse() );
- } else {
- $lines[] = Html::rawElement( 'li', array(), $error );
- }
- }
- return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
- } else {
- if ( $errors instanceof Message ) {
- $errors = $errors->parse();
- }
- return Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
- }
- }
-}
-
-class HTMLTextField extends HTMLFormField {
- function getSize() {
- return isset( $this->mParams['size'] )
- ? $this->mParams['size']
- : 45;
- }
-
- function getInputHTML( $value ) {
- $attribs = array(
- 'id' => $this->mID,
- 'name' => $this->mName,
- 'size' => $this->getSize(),
- 'value' => $value,
- ) + $this->getTooltipAndAccessKey();
-
- if ( $this->mClass !== '' ) {
- $attribs['class'] = $this->mClass;
- }
-
- if ( !empty( $this->mParams['disabled'] ) ) {
- $attribs['disabled'] = 'disabled';
- }
-
- # TODO: Enforce pattern, step, required, readonly on the server side as
- # well
- $allowedParams = array( 'min', 'max', 'pattern', 'title', 'step',
- 'placeholder', 'list', 'maxlength' );
- foreach ( $allowedParams as $param ) {
- if ( isset( $this->mParams[$param] ) ) {
- $attribs[$param] = $this->mParams[$param];
- }
- }
-
- foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as $param ) {
- if ( isset( $this->mParams[$param] ) ) {
- $attribs[$param] = '';
- }
- }
-
- # Implement tiny differences between some field variants
- # here, rather than creating a new class for each one which
- # is essentially just a clone of this one.
- if ( isset( $this->mParams['type'] ) ) {
- switch ( $this->mParams['type'] ) {
- case 'email':
- $attribs['type'] = 'email';
- break;
- case 'int':
- $attribs['type'] = 'number';
- break;
- case 'float':
- $attribs['type'] = 'number';
- $attribs['step'] = 'any';
- break;
- # Pass through
- case 'password':
- case 'file':
- $attribs['type'] = $this->mParams['type'];
- break;
- }
- }
-
- return Html::element( 'input', $attribs );
- }
-}
-class HTMLTextAreaField extends HTMLFormField {
- const DEFAULT_COLS = 80;
- const DEFAULT_ROWS = 25;
-
- function getCols() {
- return isset( $this->mParams['cols'] )
- ? $this->mParams['cols']
- : static::DEFAULT_COLS;
- }
-
- function getRows() {
- return isset( $this->mParams['rows'] )
- ? $this->mParams['rows']
- : static::DEFAULT_ROWS;
- }
-
- function getInputHTML( $value ) {
- $attribs = array(
- 'id' => $this->mID,
- 'name' => $this->mName,
- 'cols' => $this->getCols(),
- 'rows' => $this->getRows(),
- ) + $this->getTooltipAndAccessKey();
-
- if ( $this->mClass !== '' ) {
- $attribs['class'] = $this->mClass;
- }
-
- if ( !empty( $this->mParams['disabled'] ) ) {
- $attribs['disabled'] = 'disabled';
- }
-
- if ( !empty( $this->mParams['readonly'] ) ) {
- $attribs['readonly'] = 'readonly';
- }
-
- if ( isset( $this->mParams['placeholder'] ) ) {
- $attribs['placeholder'] = $this->mParams['placeholder'];
- }
-
- foreach ( array( 'required', 'autofocus' ) as $param ) {
- if ( isset( $this->mParams[$param] ) ) {
- $attribs[$param] = '';
- }
- }
-
- return Html::element( 'textarea', $attribs, $value );
- }
-}
-
-/**
- * A field that will contain a numeric value
- */
-class HTMLFloatField extends HTMLTextField {
- function getSize() {
- return isset( $this->mParams['size'] )
- ? $this->mParams['size']
- : 20;
- }
-
- function validate( $value, $alldata ) {
- $p = parent::validate( $value, $alldata );
-
- if ( $p !== true ) {
- return $p;
- }
-
- $value = trim( $value );
-
- # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
- # with the addition that a leading '+' sign is ok.
- if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
- return $this->msg( 'htmlform-float-invalid' )->parseAsBlock();
- }
-
- # The "int" part of these message names is rather confusing.
- # They make equal sense for all numbers.
- if ( isset( $this->mParams['min'] ) ) {
- $min = $this->mParams['min'];
-
- if ( $min > $value ) {
- return $this->msg( 'htmlform-int-toolow', $min )->parseAsBlock();
- }
- }
-
- if ( isset( $this->mParams['max'] ) ) {
- $max = $this->mParams['max'];
-
- if ( $max < $value ) {
- return $this->msg( 'htmlform-int-toohigh', $max )->parseAsBlock();
- }
- }
-
- return true;
- }
-}
-
-/**
- * A field that must contain a number
- */
-class HTMLIntField extends HTMLFloatField {
- function validate( $value, $alldata ) {
- $p = parent::validate( $value, $alldata );
-
- if ( $p !== true ) {
- return $p;
- }
-
- # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers
- # with the addition that a leading '+' sign is ok. Note that leading zeros
- # are fine, and will be left in the input, which is useful for things like
- # phone numbers when you know that they are integers (the HTML5 type=tel
- # input does not require its value to be numeric). If you want a tidier
- # value to, eg, save in the DB, clean it up with intval().
- if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) )
- ) {
- return $this->msg( 'htmlform-int-invalid' )->parseAsBlock();
- }
-
- return true;
- }
-}
-
-/**
- * A checkbox field
- */
-class HTMLCheckField extends HTMLFormField {
- function getInputHTML( $value ) {
- if ( !empty( $this->mParams['invert'] ) ) {
- $value = !$value;
- }
-
- $attr = $this->getTooltipAndAccessKey();
- $attr['id'] = $this->mID;
-
- if ( !empty( $this->mParams['disabled'] ) ) {
- $attr['disabled'] = 'disabled';
- }
-
- if ( $this->mClass !== '' ) {
- $attr['class'] = $this->mClass;
- }
-
- if ( $this->mParent->isVForm() ) {
- // Nest checkbox inside label.
- return Html::rawElement(
- 'label',
- array(
- 'class' => 'mw-ui-checkbox-label'
- ),
- Xml::check(
- $this->mName,
- $value,
- $attr
- ) .
- // Html:rawElement doesn't escape contents.
- htmlspecialchars( $this->mLabel )
- );
- } else {
- return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
- Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
- }
- }
-
- /**
- * For a checkbox, the label goes on the right hand side, and is
- * added in getInputHTML(), rather than HTMLFormField::getRow()
- * @return String
- */
- function getLabel() {
- return '&#160;';
- }
-
- /**
- * checkboxes don't need a label.
- */
- protected function needsLabel() {
- return false;
- }
-
- /**
- * @param $request WebRequest
- * @return String
- */
- function loadDataFromRequest( $request ) {
- $invert = false;
- if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
- $invert = true;
- }
-
- // GetCheck won't work like we want for checks.
- // Fetch the value in either one of the two following case:
- // - we have a valid token (form got posted or GET forged by the user)
- // - checkbox name has a value (false or true), ie is not null
- if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName ) !== null ) {
- // XOR has the following truth table, which is what we want
- // INVERT VALUE | OUTPUT
- // true true | false
- // false true | true
- // false false | false
- // true false | true
- return $request->getBool( $this->mName ) xor $invert;
- } else {
- return $this->getDefault();
- }
- }
-}
-
-/**
- * A checkbox matrix
- * Operates similarly to HTMLMultiSelectField, but instead of using an array of
- * options, uses an array of rows and an array of columns to dynamically
- * construct a matrix of options. The tags used to identify a particular cell
- * are of the form "columnName-rowName"
- *
- * Options:
- * - columns
- * - Required list of columns in the matrix.
- * - rows
- * - Required list of rows in the matrix.
- * - force-options-on
- * - Accepts array of column-row tags to be displayed as enabled but unavailable to change
- * - force-options-off
- * - Accepts array of column-row tags to be displayed as disabled but unavailable to change.
- * - tooltips
- * - Optional array mapping row label to tooltip content
- * - tooltip-class
- * - Optional CSS class used on tooltip container span. Defaults to mw-icon-question.
- */
-class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
-
- static private $requiredParams = array(
- // Required by underlying HTMLFormField
- 'fieldname',
- // Required by HTMLCheckMatrix
- 'rows', 'columns'
- );
-
- public function __construct( $params ) {
- $missing = array_diff( self::$requiredParams, array_keys( $params ) );
- if ( $missing ) {
- throw new HTMLFormFieldRequiredOptionsException( $this, $missing );
- }
- parent::__construct( $params );
- }
-
- function validate( $value, $alldata ) {
- $rows = $this->mParams['rows'];
- $columns = $this->mParams['columns'];
-
- // Make sure user-defined validation callback is run
- $p = parent::validate( $value, $alldata );
- if ( $p !== true ) {
- return $p;
- }
-
- // Make sure submitted value is an array
- if ( !is_array( $value ) ) {
- return false;
- }
-
- // If all options are valid, array_intersect of the valid options
- // and the provided options will return the provided options.
- $validOptions = array();
- foreach ( $rows as $rowTag ) {
- foreach ( $columns as $columnTag ) {
- $validOptions[] = $columnTag . '-' . $rowTag;
- }
- }
- $validValues = array_intersect( $value, $validOptions );
- if ( count( $validValues ) == count( $value ) ) {
- return true;
- } else {
- return $this->msg( 'htmlform-select-badoption' )->parse();
- }
- }
-
- /**
- * Build a table containing a matrix of checkbox options.
- * The value of each option is a combination of the row tag and column tag.
- * mParams['rows'] is an array with row labels as keys and row tags as values.
- * mParams['columns'] is an array with column labels as keys and column tags as values.
- * @param array $value of the options that should be checked
- * @return String
- */
- function getInputHTML( $value ) {
- $html = '';
- $tableContents = '';
- $attribs = array();
- $rows = $this->mParams['rows'];
- $columns = $this->mParams['columns'];
-
- // If the disabled param is set, disable all the options
- if ( !empty( $this->mParams['disabled'] ) ) {
- $attribs['disabled'] = 'disabled';
- }
-
- // Build the column headers
- $headerContents = Html::rawElement( 'td', array(), '&#160;' );
- foreach ( $columns as $columnLabel => $columnTag ) {
- $headerContents .= Html::rawElement( 'td', array(), $columnLabel );
- }
- $tableContents .= Html::rawElement( 'tr', array(), "\n$headerContents\n" );
-
- $tooltipClass = 'mw-icon-question';
- if ( isset( $this->mParams['tooltip-class'] ) ) {
- $tooltipClass = $this->mParams['tooltip-class'];
- }
-
- // Build the options matrix
- foreach ( $rows as $rowLabel => $rowTag ) {
- // Append tooltip if configured
- if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) {
- $tooltipAttribs = array(
- 'class' => "mw-htmlform-tooltip $tooltipClass",
- 'title' => $this->mParams['tooltips'][$rowLabel],
- );
- $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' );
- }
- $rowContents = Html::rawElement( 'td', array(), $rowLabel );
- foreach ( $columns as $columnTag ) {
- $thisTag = "$columnTag-$rowTag";
- // Construct the checkbox
- $thisAttribs = array(
- 'id' => "{$this->mID}-$thisTag",
- 'value' => $thisTag,
- );
- $checked = in_array( $thisTag, (array)$value, true );
- if ( $this->isTagForcedOff( $thisTag ) ) {
- $checked = false;
- $thisAttribs['disabled'] = 1;
- } elseif ( $this->isTagForcedOn( $thisTag ) ) {
- $checked = true;
- $thisAttribs['disabled'] = 1;
- }
- $rowContents .= Html::rawElement(
- 'td',
- array(),
- Xml::check( "{$this->mName}[]", $checked, $attribs + $thisAttribs )
- );
- }
- $tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" );
- }
-
- // Put it all in a table
- $html .= Html::rawElement( 'table', array( 'class' => 'mw-htmlform-matrix' ),
- Html::rawElement( 'tbody', array(), "\n$tableContents\n" ) ) . "\n";
-
- return $html;
- }
-
- protected function isTagForcedOff( $tag ) {
- return isset( $this->mParams['force-options-off'] )
- && in_array( $tag, $this->mParams['force-options-off'] );
- }
-
- protected function isTagForcedOn( $tag ) {
- return isset( $this->mParams['force-options-on'] )
- && in_array( $tag, $this->mParams['force-options-on'] );
- }
-
- /**
- * Get the complete table row for the input, including help text,
- * labels, and whatever.
- * We override this function since the label should always be on a separate
- * line above the options in the case of a checkbox matrix, i.e. it's always
- * a "vertical-label".
- * @param string $value the value to set the input to
- * @return String complete HTML table row
- */
- function getTableRow( $value ) {
- list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
- $inputHtml = $this->getInputHTML( $value );
- $fieldType = get_class( $this );
- $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
- $cellAttributes = array( 'colspan' => 2 );
-
- $label = $this->getLabelHtml( $cellAttributes );
-
- $field = Html::rawElement(
- 'td',
- array( 'class' => 'mw-input' ) + $cellAttributes,
- $inputHtml . "\n$errors"
- );
-
- $html = Html::rawElement( 'tr',
- array( 'class' => 'mw-htmlform-vertical-label' ), $label );
- $html .= Html::rawElement( 'tr',
- array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
- $field );
-
- return $html . $helptext;
- }
-
- /**
- * @param $request WebRequest
- * @return Array
- */
- function loadDataFromRequest( $request ) {
- if ( $this->mParent->getMethod() == 'post' ) {
- if ( $request->wasPosted() ) {
- // Checkboxes are not added to the request arrays if they're not checked,
- // so it's perfectly possible for there not to be an entry at all
- return $request->getArray( $this->mName, array() );
- } else {
- // That's ok, the user has not yet submitted the form, so show the defaults
- return $this->getDefault();
- }
- } else {
- // This is the impossible case: if we look at $_GET and see no data for our
- // field, is it because the user has not yet submitted the form, or that they
- // have submitted it with all the options unchecked. We will have to assume the
- // latter, which basically means that you can't specify 'positive' defaults
- // for GET forms.
- return $request->getArray( $this->mName, array() );
- }
- }
-
- function getDefault() {
- if ( isset( $this->mDefault ) ) {
- return $this->mDefault;
- } else {
- return array();
- }
- }
-
- function filterDataForSubmit( $data ) {
- $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] );
- $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] );
- $res = array();
- foreach ( $columns as $column ) {
- foreach ( $rows as $row ) {
- // Make sure option hasn't been forced
- $thisTag = "$column-$row";
- if ( $this->isTagForcedOff( $thisTag ) ) {
- $res[$thisTag] = false;
- } elseif ( $this->isTagForcedOn( $thisTag ) ) {
- $res[$thisTag] = true;
- } else {
- $res[$thisTag] = in_array( $thisTag, $data );
- }
- }
- }
-
- return $res;
- }
-}
-
-/**
- * A select dropdown field. Basically a wrapper for Xmlselect class
- */
-class HTMLSelectField extends HTMLFormField {
- function validate( $value, $alldata ) {
- $p = parent::validate( $value, $alldata );
-
- if ( $p !== true ) {
- return $p;
- }
-
- $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
-
- if ( in_array( $value, $validOptions ) ) {
- return true;
- } else {
- return $this->msg( 'htmlform-select-badoption' )->parse();
- }
- }
-
- function getInputHTML( $value ) {
- $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
-
- # If one of the options' 'name' is int(0), it is automatically selected.
- # because PHP sucks and thinks int(0) == 'some string'.
- # Working around this by forcing all of them to strings.
- foreach ( $this->mParams['options'] as &$opt ) {
- if ( is_int( $opt ) ) {
- $opt = strval( $opt );
- }
- }
- unset( $opt ); # PHP keeps $opt around as a reference, which is a bit scary
-
- if ( !empty( $this->mParams['disabled'] ) ) {
- $select->setAttribute( 'disabled', 'disabled' );
- }
-
- if ( $this->mClass !== '' ) {
- $select->setAttribute( 'class', $this->mClass );
- }
-
- $select->addOptions( $this->mParams['options'] );
-
- return $select->getHTML();
- }
-}
-
-/**
- * Select dropdown field, with an additional "other" textbox.
- */
-class HTMLSelectOrOtherField extends HTMLTextField {
-
- function __construct( $params ) {
- if ( !in_array( 'other', $params['options'], true ) ) {
- $msg = isset( $params['other'] ) ?
- $params['other'] :
- wfMessage( 'htmlform-selectorother-other' )->text();
- $params['options'][$msg] = 'other';
- }
-
- parent::__construct( $params );
- }
-
- static function forceToStringRecursive( $array ) {
- if ( is_array( $array ) ) {
- return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array );
- } else {
- return strval( $array );
- }
- }
-
- function getInputHTML( $value ) {
- $valInSelect = false;
-
- if ( $value !== false ) {
- $valInSelect = in_array(
- $value,
- HTMLFormField::flattenOptions( $this->mParams['options'] )
- );
- }
-
- $selected = $valInSelect ? $value : 'other';
-
- $opts = self::forceToStringRecursive( $this->mParams['options'] );
-
- $select = new XmlSelect( $this->mName, $this->mID, $selected );
- $select->addOptions( $opts );
-
- $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
-
- $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() );
-
- if ( !empty( $this->mParams['disabled'] ) ) {
- $select->setAttribute( 'disabled', 'disabled' );
- $tbAttribs['disabled'] = 'disabled';
- }
-
- $select = $select->getHTML();
-
- if ( isset( $this->mParams['maxlength'] ) ) {
- $tbAttribs['maxlength'] = $this->mParams['maxlength'];
- }
-
- if ( $this->mClass !== '' ) {
- $tbAttribs['class'] = $this->mClass;
- }
-
- $textbox = Html::input(
- $this->mName . '-other',
- $valInSelect ? '' : $value,
- 'text',
- $tbAttribs
- );
-
- return "$select<br />\n$textbox";
- }
-
- /**
- * @param $request WebRequest
- * @return String
- */
- function loadDataFromRequest( $request ) {
- if ( $request->getCheck( $this->mName ) ) {
- $val = $request->getText( $this->mName );
-
- if ( $val == 'other' ) {
- $val = $request->getText( $this->mName . '-other' );
- }
-
- return $val;
- } else {
- return $this->getDefault();
- }
- }
-}
-
-/**
- * Multi-select field
- */
-class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable {
-
- function validate( $value, $alldata ) {
- $p = parent::validate( $value, $alldata );
-
- if ( $p !== true ) {
- return $p;
- }
-
- if ( !is_array( $value ) ) {
- return false;
- }
-
- # If all options are valid, array_intersect of the valid options
- # and the provided options will return the provided options.
- $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
-
- $validValues = array_intersect( $value, $validOptions );
- if ( count( $validValues ) == count( $value ) ) {
- return true;
- } else {
- return $this->msg( 'htmlform-select-badoption' )->parse();
- }
- }
-
- function getInputHTML( $value ) {
- $html = $this->formatOptions( $this->mParams['options'], $value );
-
- return $html;
- }
-
- function formatOptions( $options, $value ) {
- $html = '';
-
- $attribs = array();
-
- if ( !empty( $this->mParams['disabled'] ) ) {
- $attribs['disabled'] = 'disabled';
- }
-
- foreach ( $options as $label => $info ) {
- if ( is_array( $info ) ) {
- $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
- $html .= $this->formatOptions( $info, $value );
- } else {
- $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
-
- $checkbox = Xml::check(
- $this->mName . '[]',
- in_array( $info, $value, true ),
- $attribs + $thisAttribs );
- $checkbox .= '&#160;' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label );
-
- $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox );
- }
- }
-
- return $html;
- }
-
- /**
- * @param $request WebRequest
- * @return String
- */
- function loadDataFromRequest( $request ) {
- if ( $this->mParent->getMethod() == 'post' ) {
- if ( $request->wasPosted() ) {
- # Checkboxes are just not added to the request arrays if they're not checked,
- # so it's perfectly possible for there not to be an entry at all
- return $request->getArray( $this->mName, array() );
- } else {
- # That's ok, the user has not yet submitted the form, so show the defaults
- return $this->getDefault();
- }
- } else {
- # This is the impossible case: if we look at $_GET and see no data for our
- # field, is it because the user has not yet submitted the form, or that they
- # have submitted it with all the options unchecked? We will have to assume the
- # latter, which basically means that you can't specify 'positive' defaults
- # for GET forms.
- # @todo FIXME...
- return $request->getArray( $this->mName, array() );
- }
- }
-
- function getDefault() {
- if ( isset( $this->mDefault ) ) {
- return $this->mDefault;
- } else {
- return array();
- }
- }
-
- function filterDataForSubmit( $data ) {
- $options = HTMLFormField::flattenOptions( $this->mParams['options'] );
-
- $res = array();
- foreach ( $options as $opt ) {
- $res["$opt"] = in_array( $opt, $data );
- }
-
- return $res;
- }
-
- protected function needsLabel() {
- return false;
- }
-}
-
-/**
- * Double field with a dropdown list constructed from a system message in the format
- * * Optgroup header
- * ** <option value>
- * * New Optgroup header
- * Plus a text field underneath for an additional reason. The 'value' of the field is
- * "<select>: <extra reason>", or "<extra reason>" if nothing has been selected in the
- * select dropdown.
- * @todo FIXME: If made 'required', only the text field should be compulsory.
- */
-class HTMLSelectAndOtherField extends HTMLSelectField {
-
- function __construct( $params ) {
- if ( array_key_exists( 'other', $params ) ) {
- } elseif ( array_key_exists( 'other-message', $params ) ) {
- $params['other'] = wfMessage( $params['other-message'] )->plain();
- } else {
- $params['other'] = null;
- }
-
- if ( array_key_exists( 'options', $params ) ) {
- # Options array already specified
- } elseif ( array_key_exists( 'options-message', $params ) ) {
- # Generate options array from a system message
- $params['options'] = self::parseMessage(
- wfMessage( $params['options-message'] )->inContentLanguage()->plain(),
- $params['other']
- );
- } else {
- # Sulk
- throw new MWException( 'HTMLSelectAndOtherField called without any options' );
- }
- $this->mFlatOptions = self::flattenOptions( $params['options'] );
-
- parent::__construct( $params );
- }
-
- /**
- * Build a drop-down box from a textual list.
- * @param string $string message text
- * @param string $otherName name of "other reason" option
- * @return Array
- * TODO: this is copied from Xml::listDropDown(), deprecate/avoid duplication?
- */
- public static function parseMessage( $string, $otherName = null ) {
- if ( $otherName === null ) {
- $otherName = wfMessage( 'htmlform-selectorother-other' )->plain();
- }
-
- $optgroup = false;
- $options = array( $otherName => 'other' );
-
- foreach ( explode( "\n", $string ) as $option ) {
- $value = trim( $option );
- if ( $value == '' ) {
- continue;
- } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
- # A new group is starting...
- $value = trim( substr( $value, 1 ) );
- $optgroup = $value;
- } elseif ( substr( $value, 0, 2 ) == '**' ) {
- # groupmember
- $opt = trim( substr( $value, 2 ) );
- if ( $optgroup === false ) {
- $options[$opt] = $opt;
- } else {
- $options[$optgroup][$opt] = $opt;
- }
- } else {
- # groupless reason list
- $optgroup = false;
- $options[$option] = $option;
- }
- }
-
- return $options;
- }
-
- function getInputHTML( $value ) {
- $select = parent::getInputHTML( $value[1] );
-
- $textAttribs = array(
- 'id' => $this->mID . '-other',
- 'size' => $this->getSize(),
- );
-
- if ( $this->mClass !== '' ) {
- $textAttribs['class'] = $this->mClass;
- }
-
- foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) {
- if ( isset( $this->mParams[$param] ) ) {
- $textAttribs[$param] = '';
- }
- }
-
- $textbox = Html::input(
- $this->mName . '-other',
- $value[2],
- 'text',
- $textAttribs
- );
-
- return "$select<br />\n$textbox";
- }
-
- /**
- * @param $request WebRequest
- * @return Array("<overall message>","<select value>","<text field value>")
- */
- function loadDataFromRequest( $request ) {
- if ( $request->getCheck( $this->mName ) ) {
-
- $list = $request->getText( $this->mName );
- $text = $request->getText( $this->mName . '-other' );
-
- if ( $list == 'other' ) {
- $final = $text;
- } elseif ( !in_array( $list, $this->mFlatOptions ) ) {
- # User has spoofed the select form to give an option which wasn't
- # in the original offer. Sulk...
- $final = $text;
- } elseif ( $text == '' ) {
- $final = $list;
- } else {
- $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
- }
-
- } else {
- $final = $this->getDefault();
-
- $list = 'other';
- $text = $final;
- foreach ( $this->mFlatOptions as $option ) {
- $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
- if ( strpos( $text, $match ) === 0 ) {
- $list = $option;
- $text = substr( $text, strlen( $match ) );
- break;
- }
- }
- }
- return array( $final, $list, $text );
- }
-
- function getSize() {
- return isset( $this->mParams['size'] )
- ? $this->mParams['size']
- : 45;
- }
-
- function validate( $value, $alldata ) {
- # HTMLSelectField forces $value to be one of the options in the select
- # field, which is not useful here. But we do want the validation further up
- # the chain
- $p = parent::validate( $value[1], $alldata );
-
- if ( $p !== true ) {
- return $p;
- }
-
- if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value[1] === '' ) {
- return $this->msg( 'htmlform-required' )->parse();
- }
-
- return true;
- }
-}
-
-/**
- * Radio checkbox fields.
- */
-class HTMLRadioField extends HTMLFormField {
-
- function validate( $value, $alldata ) {
- $p = parent::validate( $value, $alldata );
-
- if ( $p !== true ) {
- return $p;
- }
-
- if ( !is_string( $value ) && !is_int( $value ) ) {
- return false;
- }
-
- $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
-
- if ( in_array( $value, $validOptions ) ) {
- return true;
- } else {
- return $this->msg( 'htmlform-select-badoption' )->parse();
- }
- }
-
- /**
- * This returns a block of all the radio options, in one cell.
- * @see includes/HTMLFormField#getInputHTML()
- * @param $value String
- * @return String
- */
- function getInputHTML( $value ) {
- $html = $this->formatOptions( $this->mParams['options'], $value );
-
- return $html;
- }
-
- function formatOptions( $options, $value ) {
- $html = '';
-
- $attribs = array();
- if ( !empty( $this->mParams['disabled'] ) ) {
- $attribs['disabled'] = 'disabled';
- }
-
- # TODO: should this produce an unordered list perhaps?
- foreach ( $options as $label => $info ) {
- if ( is_array( $info ) ) {
- $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
- $html .= $this->formatOptions( $info, $value );
- } else {
- $id = Sanitizer::escapeId( $this->mID . "-$info" );
- $radio = Xml::radio(
- $this->mName,
- $info,
- $info == $value,
- $attribs + array( 'id' => $id )
- );
- $radio .= '&#160;' .
- Html::rawElement( 'label', array( 'for' => $id ), $label );
-
- $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio );
- }
- }
-
- return $html;
- }
-
- protected function needsLabel() {
- return false;
- }
-}
-
-/**
- * An information field (text blob), not a proper input.
- */
-class HTMLInfoField extends HTMLFormField {
- public function __construct( $info ) {
- $info['nodata'] = true;
-
- parent::__construct( $info );
- }
-
- public function getInputHTML( $value ) {
- return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value );
- }
-
- public function getTableRow( $value ) {
- if ( !empty( $this->mParams['rawrow'] ) ) {
- return $value;
- }
-
- return parent::getTableRow( $value );
- }
-
- /**
- * @since 1.20
- */
- public function getDiv( $value ) {
- if ( !empty( $this->mParams['rawrow'] ) ) {
- return $value;
- }
-
- return parent::getDiv( $value );
- }
-
- /**
- * @since 1.20
- */
- public function getRaw( $value ) {
- if ( !empty( $this->mParams['rawrow'] ) ) {
- return $value;
- }
-
- return parent::getRaw( $value );
- }
-
- protected function needsLabel() {
- return false;
- }
-}
-
-class HTMLHiddenField extends HTMLFormField {
- public function __construct( $params ) {
- parent::__construct( $params );
-
- # Per HTML5 spec, hidden fields cannot be 'required'
- # http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state
- unset( $this->mParams['required'] );
- }
-
- public function getTableRow( $value ) {
- $params = array();
- if ( $this->mID ) {
- $params['id'] = $this->mID;
- }
-
- $this->mParent->addHiddenField(
- $this->mName,
- $this->mDefault,
- $params
- );
-
- return '';
- }
-
- /**
- * @since 1.20
- */
- public function getDiv( $value ) {
- return $this->getTableRow( $value );
- }
-
- /**
- * @since 1.20
- */
- public function getRaw( $value ) {
- return $this->getTableRow( $value );
- }
-
- public function getInputHTML( $value ) {
- return '';
- }
-}
-
-/**
- * Add a submit button inline in the form (as opposed to
- * HTMLForm::addButton(), which will add it at the end).
- */
-class HTMLSubmitField extends HTMLButtonField {
- protected $buttonType = 'submit';
-}
-
-/**
- * Adds a generic button inline to the form. Does not do anything, you must add
- * click handling code in JavaScript. Use a HTMLSubmitField if you merely
- * wish to add a submit button to a form.
- *
- * @since 1.22
- */
-class HTMLButtonField extends HTMLFormField {
- protected $buttonType = 'button';
-
- public function __construct( $info ) {
- $info['nodata'] = true;
- parent::__construct( $info );
- }
-
- public function getInputHTML( $value ) {
- $attr = array(
- 'class' => 'mw-htmlform-submit ' . $this->mClass,
- 'id' => $this->mID,
- );
-
- if ( !empty( $this->mParams['disabled'] ) ) {
- $attr['disabled'] = 'disabled';
- }
-
- return Html::input(
- $this->mName,
- $value,
- $this->buttonType,
- $attr
- );
- }
-
- protected function needsLabel() {
- return false;
- }
-
- /**
- * Button cannot be invalid
- * @param $value String
- * @param $alldata Array
- * @return Bool
- */
- public function validate( $value, $alldata ) {
- return true;
- }
-}
-
-class HTMLEditTools extends HTMLFormField {
- public function getInputHTML( $value ) {
- return '';
- }
-
- public function getTableRow( $value ) {
- $msg = $this->formatMsg();
-
- return '<tr><td></td><td class="mw-input">'
- . '<div class="mw-editTools">'
- . $msg->parseAsBlock()
- . "</div></td></tr>\n";
- }
-
- /**
- * @since 1.20
- */
- public function getDiv( $value ) {
- $msg = $this->formatMsg();
- return '<div class="mw-editTools">' . $msg->parseAsBlock() . '</div>';
- }
-
- /**
- * @since 1.20
- */
- public function getRaw( $value ) {
- return $this->getDiv( $value );
- }
-
- protected function formatMsg() {
- if ( empty( $this->mParams['message'] ) ) {
- $msg = $this->msg( 'edittools' );
- } else {
- $msg = $this->msg( $this->mParams['message'] );
- if ( $msg->isDisabled() ) {
- $msg = $this->msg( 'edittools' );
- }
- }
- $msg->inContentLanguage();
- return $msg;
- }
-}
-
-class HTMLApiField extends HTMLFormField {
- public function getTableRow( $value ) {
- return '';
- }
-
- public function getDiv( $value ) {
- return $this->getTableRow( $value );
- }
-
- public function getRaw( $value ) {
- return $this->getTableRow( $value );
- }
-
- public function getInputHTML( $value ) {
- return '';
- }
-}
-
-interface HTMLNestedFilterable {
- /**
- * Support for seperating multi-option preferences into multiple preferences
- * Due to lack of array support.
- * @param $data array
- */
- function filterDataForSubmit( $data );
-}
-
-class HTMLFormFieldRequiredOptionsException extends MWException {
- public function __construct( HTMLFormField $field, array $missing ) {
- parent::__construct( sprintf(
- "Form type `%s` expected the following parameters to be set: %s",
- get_class( $field ),
- implode( ', ', $missing )
- ) );
- }
-}
diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php
index 31aa0f87..69f1120d 100644
--- a/includes/HistoryBlob.php
+++ b/includes/HistoryBlob.php
@@ -25,25 +25,24 @@
* two-part external storage URLs. Used for represent efficient concatenated
* storage, and migration-related pointer objects.
*/
-interface HistoryBlob
-{
+interface HistoryBlob {
/**
* Adds an item of text, returns a stub object which points to the item.
* You must call setLocation() on the stub object before storing it to the
* database
*
- * @param $text string
+ * @param string $text
*
- * @return String: the key for getItem()
+ * @return string The key for getItem()
*/
function addItem( $text );
/**
* Get item by key, or false if the key is not present
*
- * @param $key string
+ * @param string $key
*
- * @return String or false
+ * @return string|bool
*/
function getItem( $key );
@@ -55,14 +54,14 @@ interface HistoryBlob
*
* Default text is not required for two-part external storage URLs.
*
- * @param $text string
+ * @param string $text
*/
function setText( $text );
/**
* Get default text. This is called from Revision::getRevisionText()
*
- * @return String
+ * @return string
*/
function getText();
}
@@ -71,22 +70,24 @@ interface HistoryBlob
* Concatenated gzip (CGZ) storage
* Improves compression ratio by concatenating like objects before gzipping
*/
-class ConcatenatedGzipHistoryBlob implements HistoryBlob
-{
+class ConcatenatedGzipHistoryBlob implements HistoryBlob {
public $mVersion = 0, $mCompressed = false, $mItems = array(), $mDefaultHash = '';
public $mSize = 0;
public $mMaxSize = 10000000;
public $mMaxCount = 100;
- /** Constructor */
+ /**
+ * Constructor
+ */
public function __construct() {
if ( !function_exists( 'gzdeflate' ) ) {
- throw new MWException( "Need zlib support to read or write this kind of history object (ConcatenatedGzipHistoryBlob)\n" );
+ throw new MWException( "Need zlib support to read or write this "
+ . "kind of history object (ConcatenatedGzipHistoryBlob)\n" );
}
}
/**
- * @param $text string
+ * @param string $text
* @return string
*/
public function addItem( $text ) {
@@ -100,7 +101,7 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
}
/**
- * @param $hash string
+ * @param string $hash
* @return array|bool
*/
public function getItem( $hash ) {
@@ -113,7 +114,7 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
}
/**
- * @param $text string
+ * @param string $text
* @return void
*/
public function setText( $text ) {
@@ -132,7 +133,7 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
/**
* Remove an item
*
- * @param $hash string
+ * @param string $hash
*/
public function removeItem( $hash ) {
$this->mSize -= strlen( $this->mItems[$hash] );
@@ -188,18 +189,25 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
*/
class HistoryBlobStub {
/**
- * One-step cache variable to hold base blobs; operations that
- * pull multiple revisions may often pull multiple times from
- * the same blob. By keeping the last-used one open, we avoid
- * redundant unserialization and decompression overhead.
+ * @var array One-step cache variable to hold base blobs; operations that
+ * pull multiple revisions may often pull multiple times from the same
+ * blob. By keeping the last-used one open, we avoid redundant
+ * unserialization and decompression overhead.
*/
protected static $blobCache = array();
- var $mOldId, $mHash, $mRef;
+ /** @var int */
+ public $mOldId;
+
+ /** @var string */
+ public $mHash;
+
+ /** @var string */
+ public $mRef;
/**
- * @param string $hash the content hash of the text
- * @param $oldid Integer the old_id for the CGZ object
+ * @param string $hash The content hash of the text
+ * @param int $oldid The old_id for the CGZ object
*/
function __construct( $hash = '', $oldid = 0 ) {
$this->mHash = $hash;
@@ -208,6 +216,7 @@ class HistoryBlobStub {
/**
* Sets the location (old_id) of the main object to which this object
* points
+ * @param int $id
*/
function setLocation( $id ) {
$this->mOldId = $id;
@@ -215,6 +224,7 @@ class HistoryBlobStub {
/**
* Sets the location (old_id) of the referring object
+ * @param string $id
*/
function setReferrer( $id ) {
$this->mRef = $id;
@@ -222,6 +232,7 @@ class HistoryBlobStub {
/**
* Gets the location of the referring object
+ * @return string
*/
function getReferrer() {
return $this->mRef;
@@ -235,10 +246,16 @@ class HistoryBlobStub {
$obj = self::$blobCache[$this->mOldId];
} else {
$dbr = wfGetDB( DB_SLAVE );
- $row = $dbr->selectRow( 'text', array( 'old_flags', 'old_text' ), array( 'old_id' => $this->mOldId ) );
+ $row = $dbr->selectRow(
+ 'text',
+ array( 'old_flags', 'old_text' ),
+ array( 'old_id' => $this->mOldId )
+ );
+
if ( !$row ) {
return false;
}
+
$flags = explode( ',', $row->old_flags );
if ( in_array( 'external', $flags ) ) {
$url = $row->old_text;
@@ -249,6 +266,7 @@ class HistoryBlobStub {
$row->old_text = ExternalStore::fetchFromUrl( $url );
}
+
if ( !in_array( 'object', $flags ) ) {
return false;
}
@@ -271,6 +289,7 @@ class HistoryBlobStub {
$obj->uncompress();
self::$blobCache = array( $this->mOldId => $obj );
}
+
return $obj->getItem( $this->mHash );
}
@@ -290,13 +309,14 @@ class HistoryBlobStub {
* of megabytes of data during the conversion downtime.
*
* Serialized HistoryBlobCurStub objects will be inserted into the text table
- * on conversion if $wgFastSchemaUpgrades is set to true.
+ * on conversion if $wgLegacySchemaConversion is set to true.
*/
class HistoryBlobCurStub {
- var $mCurId;
+ /** @var int */
+ public $mCurId;
/**
- * @param $curid Integer: the cur_id pointed to
+ * @param int $curid The cur_id pointed to
*/
function __construct( $curid = 0 ) {
$this->mCurId = $curid;
@@ -306,7 +326,7 @@ class HistoryBlobCurStub {
* Sets the location (cur_id) of the main object to which this object
* points
*
- * @param $id int
+ * @param int $id
*/
function setLocation( $id ) {
$this->mCurId = $id;
@@ -330,50 +350,43 @@ class HistoryBlobCurStub {
* Requires xdiff 1.5+ and zlib
*/
class DiffHistoryBlob implements HistoryBlob {
- /** Uncompressed item cache */
- var $mItems = array();
+ /** @var array Uncompressed item cache */
+ public $mItems = array();
- /** Total uncompressed size */
- var $mSize = 0;
+ /** @var int Total uncompressed size */
+ public $mSize = 0;
/**
- * Array of diffs. If a diff D from A to B is notated D = B - A, and Z is
- * an empty string:
+ * @var array Array of diffs. If a diff D from A to B is notated D = B - A,
+ * and Z is an empty string:
*
* { item[map[i]] - item[map[i-1]] where i > 0
* diff[i] = {
* { item[map[i]] - Z where i = 0
*/
- var $mDiffs;
+ public $mDiffs;
- /** The diff map, see above */
- var $mDiffMap;
+ /** @var array The diff map, see above */
+ public $mDiffMap;
- /**
- * The key for getText()
+ /** @var int The key for getText()
*/
- var $mDefaultKey;
+ public $mDefaultKey;
- /**
- * Compressed storage
- */
- var $mCompressed;
+ /** @var string Compressed storage */
+ public $mCompressed;
- /**
- * True if the object is locked against further writes
- */
- var $mFrozen = false;
+ /** @var bool True if the object is locked against further writes */
+ public $mFrozen = false;
/**
- * The maximum uncompressed size before the object becomes sad
+ * @var int The maximum uncompressed size before the object becomes sad
* Should be less than max_allowed_packet
*/
- var $mMaxSize = 10000000;
+ public $mMaxSize = 10000000;
- /**
- * The maximum number of text items before the object becomes sad
- */
- var $mMaxCount = 100;
+ /** @var int The maximum number of text items before the object becomes sad */
+ public $mMaxCount = 100;
/** Constants from xdiff.h */
const XDL_BDOP_INS = 1;
@@ -388,7 +401,7 @@ class DiffHistoryBlob implements HistoryBlob {
/**
* @throws MWException
- * @param $text string
+ * @param string $text
* @return int
*/
function addItem( $text ) {
@@ -403,7 +416,7 @@ class DiffHistoryBlob implements HistoryBlob {
}
/**
- * @param $key string
+ * @param string $key
* @return string
*/
function getItem( $key ) {
@@ -411,7 +424,7 @@ class DiffHistoryBlob implements HistoryBlob {
}
/**
- * @param $text string
+ * @param string $text
*/
function setText( $text ) {
$this->mDefaultKey = $this->addItem( $text );
@@ -455,7 +468,8 @@ class DiffHistoryBlob implements HistoryBlob {
);
$smallFactor = 0.5;
- for ( $i = 0; $i < count( $this->mItems ); $i++ ) {
+ $mItemsCount = count( $this->mItems );
+ for ( $i = 0; $i < $mItemsCount; $i++ ) {
$text = $this->mItems[$i];
if ( $i == 0 ) {
$seqName = 'main';
@@ -491,7 +505,8 @@ class DiffHistoryBlob implements HistoryBlob {
$this->mDiffs[] = $this->diff( $tail, $head );
}
$this->mDiffMap[] = $seq['map'][0];
- for ( $i = 1; $i < count( $seq['diffs'] ); $i++ ) {
+ $diffsCount = count( $seq['diffs'] );
+ for ( $i = 1; $i < $diffsCount; $i++ ) {
$this->mDiffs[] = $seq['diffs'][$i];
$this->mDiffMap[] = $seq['map'][$i];
}
@@ -500,8 +515,8 @@ class DiffHistoryBlob implements HistoryBlob {
}
/**
- * @param $t1
- * @param $t2
+ * @param string $t1
+ * @param string $t2
* @return string
*/
function diff( $t1, $t2 ) {
@@ -514,8 +529,8 @@ class DiffHistoryBlob implements HistoryBlob {
}
/**
- * @param $base
- * @param $diff
+ * @param string $base
+ * @param string $diff
* @return bool|string
*/
function patch( $base, $diff ) {
@@ -578,7 +593,7 @@ class DiffHistoryBlob implements HistoryBlob {
* the bytes backwards and initialised with 0 instead of 1. See bug 34428.
*
* @param string $s
- * @return string|bool: false if the hash extension is not available
+ * @return string|bool False if the hash extension is not available
*/
function xdiffAdler32( $s ) {
if ( !function_exists( 'hash' ) ) {
@@ -601,7 +616,8 @@ class DiffHistoryBlob implements HistoryBlob {
return;
}
$tail = '';
- for ( $diffKey = 0; $diffKey < count( $this->mDiffs ); $diffKey++ ) {
+ $mDiffsCount = count( $this->mDiffs );
+ for ( $diffKey = 0; $diffKey < $mDiffsCount; $diffKey++ ) {
$textKey = $this->mDiffMap[$diffKey];
$text = $this->patch( $tail, $this->mDiffs[$diffKey] );
$this->mItems[$textKey] = $text;
diff --git a/includes/Hooks.php b/includes/Hooks.php
index 396e360d..29287483 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -27,7 +27,8 @@
/**
* @since 1.18
*/
-class MWHookException extends MWException {}
+class MWHookException extends MWException {
+}
/**
* Hooks class.
@@ -37,7 +38,6 @@ class MWHookException extends MWException {}
* @since 1.18
*/
class Hooks {
-
/**
* Array of events mapped to an array of callbacks to be run
* when that event is triggered.
@@ -48,7 +48,7 @@ class Hooks {
* Attach an event handler to a given hook.
*
* @param string $name Name of hook
- * @param mixed $callback Callback function to attach
+ * @param callable $callback Callback function to attach
*
* @since 1.18
*/
@@ -64,10 +64,10 @@ class Hooks {
* Clears hooks registered via Hooks::register(). Does not touch $wgHooks.
* This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
*
- * @param string $name the name of the hook to clear.
+ * @param string $name The name of the hook to clear.
*
* @since 1.21
- * @throws MWException if not in testing mode.
+ * @throws MWException If not in testing mode.
*/
public static function clear( $name ) {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
@@ -123,7 +123,8 @@ class Hooks {
* Finally, process the return value and return/throw accordingly.
*
* @param string $event Event name
- * @param array $args Array of parameters passed to hook functions
+ * @param array $args Array of parameters passed to hook functions
+ * @param string|null $deprecatedVersion Optionally, mark hook as deprecated with version number
* @return bool True if no handler aborted the hook
*
* @since 1.22 A hook function is not required to return a value for
@@ -132,7 +133,7 @@ class Hooks {
* @throws MWException
* @throws FatalError
*/
- public static function run( $event, array $args = array() ) {
+ public static function run( $event, array $args = array(), $deprecatedVersion = null ) {
wfProfileIn( 'hook: ' . $event );
foreach ( self::getHandlers( $event ) as $hook ) {
// Turn non-array values into an array. (Can't use casting because of objects.)
@@ -195,10 +196,19 @@ class Hooks {
// Profile first in case the Profiler causes errors.
wfProfileIn( $func );
set_error_handler( 'Hooks::hookErrorHandler' );
+
+ // mark hook as deprecated, if deprecation version is specified
+ if ( $deprecatedVersion !== null ) {
+ wfDeprecated( "$event hook (used in $func)", $deprecatedVersion );
+ }
+
try {
$retval = call_user_func_array( $callback, $hook_args );
} catch ( MWHookException $e ) {
$badhookmsg = $e->getMessage();
+ } catch ( Exception $e ) {
+ restore_error_handler();
+ throw $e;
}
restore_error_handler();
wfProfileOut( $func );
diff --git a/includes/Html.php b/includes/Html.php
index 932f753e..1e16e394 100644
--- a/includes/Html.php
+++ b/includes/Html.php
@@ -36,7 +36,7 @@
*
* There are two important configuration options this class uses:
*
- * $wgMimeType: If this is set to an xml mimetype then output should be
+ * $wgMimeType: If this is set to an xml MIME type then output should be
* valid XHTML5.
* $wgWellFormedXml: If this is set to true, then all output should be
* well-formed XML (quotes on attributes, self-closing tags, etc.).
@@ -102,6 +102,35 @@ class Html {
);
/**
+ * Modifies a set of attributes meant for text input elements
+ * and apply a set of default attributes.
+ * Removes size attribute when $wgUseMediaWikiUIEverywhere enabled.
+ * @param array $attrs An attribute array.
+ * @return array $attrs A modified attribute array
+ */
+ public static function getTextInputAttributes( $attrs ) {
+ global $wgUseMediaWikiUIEverywhere;
+ if ( !$attrs ) {
+ $attrs = array();
+ }
+ if ( isset( $attrs['class'] ) ) {
+ if ( is_array( $attrs['class'] ) ) {
+ $attrs['class'][] = 'mw-ui-input';
+ } else {
+ $attrs['class'] .= ' mw-ui-input';
+ }
+ } else {
+ $attrs['class'] = 'mw-ui-input';
+ }
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ // Note that size can effect the desired width rendering of mw-ui-input elements
+ // so it is removed. Left intact when mediawiki ui not enabled.
+ unset( $attrs['size'] );
+ }
+ return $attrs;
+ }
+
+ /**
* Returns an HTML element in a string. The major advantage here over
* manually typing out the HTML is that it will escape all attribute
* values. If you're hardcoding all the attributes, or there are none, you
@@ -114,7 +143,7 @@ class Html {
* shaved off the HTML output as well.
*
* @param string $element The element's name, e.g., 'a'
- * @param array $attribs Associative array of attributes, e.g., array(
+ * @param array $attribs Associative array of attributes, e.g., array(
* 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
* further documentation.
* @param string $contents The raw HTML contents of the element: *not*
@@ -139,9 +168,9 @@ class Html {
* Identical to rawElement(), but HTML-escapes $contents (like
* Xml::element()).
*
- * @param $element string
- * @param $attribs array
- * @param $contents string
+ * @param string $element
+ * @param array $attribs
+ * @param string $contents
*
* @return string
*/
@@ -158,8 +187,8 @@ class Html {
* Identical to rawElement(), but has no third parameter and omits the end
* tag (and the self-closing '/' in XML mode for empty elements).
*
- * @param $element string
- * @param $attribs array
+ * @param string $element
+ * @param array $attribs
*
* @return string
*/
@@ -225,33 +254,15 @@ class Html {
}
/**
- * Returns "</$element>", except if $wgWellFormedXml is off, in which case
- * it returns the empty string when that's guaranteed to be safe.
+ * Returns "</$element>"
*
* @since 1.17
* @param string $element Name of the element, e.g., 'a'
- * @return string A closing tag, if required
+ * @return string A closing tag
*/
public static function closeElement( $element ) {
- global $wgWellFormedXml;
-
$element = strtolower( $element );
- // Reference:
- // http://www.whatwg.org/html/syntax.html#optional-tags
- if ( !$wgWellFormedXml && in_array( $element, array(
- 'html',
- 'head',
- 'body',
- 'li',
- 'dt',
- 'dd',
- 'tr',
- 'td',
- 'th',
- ) ) ) {
- return '';
- }
return "</$element>";
}
@@ -267,7 +278,7 @@ class Html {
* to the input array (currently per the HTML 5 draft as of 2009-09-06).
*
* @param string $element Name of the element, e.g., 'a'
- * @param array $attribs Associative array of attributes, e.g., array(
+ * @param array $attribs Associative array of attributes, e.g., array(
* 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
* further documentation.
* @return array An array of attributes functionally identical to $attribs
@@ -386,7 +397,7 @@ class Html {
* For instance, it will omit quotation marks if $wgWellFormedXml is false,
* and will treat boolean attributes specially.
*
- * Attributes that should contain space-separated lists (such as 'class') array
+ * Attributes that can contain space-separated lists ('class', 'accesskey' and 'rel') array
* values are allowed as well, which will automagically be normalized
* and converted to a space-separated string. In addition to a numerical
* array, the attribute value may also be an associative array. See the
@@ -413,6 +424,8 @@ class Html {
* A value of false means to omit the attribute. For boolean attributes,
* you can omit the key, e.g., array( 'checked' ) instead of
* array( 'checked' => 'checked' ) or such.
+ *
+ * @throws MWException If an attribute that doesn't allow lists is set to an array
* @return string HTML fragment that goes between element name and '>'
* (starting with a space if at least one attribute is output)
*/
@@ -449,8 +462,8 @@ class Html {
// numbers to be entered in 'type="number"' fields, allow
// the special case 'step="any"'.
- if ( in_array( $key, array( 'max', 'min', 'pattern', 'required' ) ) ||
- $key === 'step' && $value !== 'any' ) {
+ if ( in_array( $key, array( 'max', 'min', 'pattern', 'required' ) )
+ || $key === 'step' && $value !== 'any' ) {
continue;
}
@@ -500,6 +513,8 @@ class Html {
// Remove duplicates and create the string
$value = implode( ' ', array_unique( $value ) );
+ } elseif ( is_array( $value ) ) {
+ throw new MWException( "HTML attribute $key can not contain a list of values" );
}
// See the "Attributes" section in the HTML syntax part of HTML5,
@@ -583,7 +598,7 @@ class Html {
* Output a "<script>" tag linking to the given URL, e.g.,
* "<script src=foo.js></script>".
*
- * @param $url string
+ * @param string $url
* @return string Raw HTML
*/
public static function linkedScript( $url ) {
@@ -598,7 +613,7 @@ class Html {
* contains literal "</style>" (admittedly unlikely).
*
* @param string $contents CSS
- * @param $media mixed A media type string, like 'screen'
+ * @param string $media A media type string, like 'screen'
* @return string Raw HTML
*/
public static function inlineStyle( $contents, $media = 'all' ) {
@@ -618,8 +633,8 @@ class Html {
* Output a "<link rel=stylesheet>" linking to the given URL for the given
* media type (if any).
*
- * @param $url string
- * @param $media mixed A media type string, like 'screen'
+ * @param string $url
+ * @param string $media A media type string, like 'screen'
* @return string Raw HTML
*/
public static function linkedStyle( $url, $media = 'all' ) {
@@ -635,10 +650,10 @@ class Html {
* Convenience function to produce an "<input>" element. This supports the
* new HTML5 input types and attributes.
*
- * @param $name string name attribute
- * @param $value mixed value attribute
- * @param $type string type attribute
- * @param array $attribs Associative array of miscellaneous extra
+ * @param string $name Name attribute
+ * @param array $value Value attribute
+ * @param string $type Type attribute
+ * @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element()
* @return string Raw HTML
*/
@@ -646,16 +661,79 @@ class Html {
$attribs['type'] = $type;
$attribs['value'] = $value;
$attribs['name'] = $name;
-
+ if ( in_array( $type, array( 'text', 'search', 'email', 'password', 'number' ) ) ) {
+ $attribs = Html::getTextInputAttributes( $attribs );
+ }
return self::element( 'input', $attribs );
}
/**
+ * Convenience function to produce a checkbox (input element with type=checkbox)
+ *
+ * @param string $name Name attribute
+ * @param bool $checked Whether the checkbox is checked or not
+ * @param array $attribs Array of additional attributes
+ * @return string
+ */
+ public static function check( $name, $checked = false, array $attribs = array() ) {
+ if ( isset( $attribs['value'] ) ) {
+ $value = $attribs['value'];
+ unset( $attribs['value'] );
+ } else {
+ $value = 1;
+ }
+
+ if ( $checked ) {
+ $attribs[] = 'checked';
+ }
+
+ return self::input( $name, $value, 'checkbox', $attribs );
+ }
+
+ /**
+ * Convenience function to produce a checkbox (input element with type=checkbox)
+ *
+ * @param string $name Name attribute
+ * @param bool $checked Whether the checkbox is checked or not
+ * @param array $attribs Array of additional attributes
+ * @return string
+ */
+ public static function radio( $name, $checked = false, array $attribs = array() ) {
+ if ( isset( $attribs['value'] ) ) {
+ $value = $attribs['value'];
+ unset( $attribs['value'] );
+ } else {
+ $value = 1;
+ }
+
+ if ( $checked ) {
+ $attribs[] = 'checked';
+ }
+
+ return self::input( $name, $value, 'radio', $attribs );
+ }
+
+ /**
+ * Convenience function for generating a label for inputs.
+ *
+ * @param string $label Contents of the label
+ * @param string $id ID of the element being labeled
+ * @param array $attribs Additional attributes
+ * @return string
+ */
+ public static function label( $label, $id, array $attribs = array() ) {
+ $attribs += array(
+ 'for' => $id
+ );
+ return self::element( 'label', $attribs, $label );
+ }
+
+ /**
* Convenience function to produce an input element with type=hidden
*
- * @param $name string name attribute
- * @param $value string value attribute
- * @param array $attribs Associative array of miscellaneous extra
+ * @param string $name Name attribute
+ * @param string $value Value attribute
+ * @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element()
* @return string Raw HTML
*/
@@ -664,14 +742,14 @@ class Html {
}
/**
- * Convenience function to produce an "<input>" element.
+ * Convenience function to produce a <textarea> element.
*
* This supports leaving out the cols= and rows= which Xml requires and are
* required by HTML4/XHTML but not required by HTML5.
*
- * @param $name string name attribute
- * @param $value string value attribute
- * @param array $attribs Associative array of miscellaneous extra
+ * @param string $name Name attribute
+ * @param string $value Value attribute
+ * @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element()
* @return string Raw HTML
*/
@@ -687,23 +765,28 @@ class Html {
} else {
$spacedValue = $value;
}
- return self::element( 'textarea', $attribs, $spacedValue );
+ return self::element( 'textarea', Html::getTextInputAttributes( $attribs ), $spacedValue );
}
+
/**
* Build a drop-down box for selecting a namespace
*
- * @param $params array:
+ * @param array $params Params to set.
* - selected: [optional] Id of namespace which should be pre-selected
- * - all: [optional] Value of item for "all namespaces". If null or unset, no "<option>" is generated to select all namespaces
- * - label: text for label to add before the field
- * - exclude: [optional] Array of namespace ids to exclude
- * - disable: [optional] Array of namespace ids for which the option should be disabled in the selector
+ * - all: [optional] Value of item for "all namespaces". If null or unset,
+ * no "<option>" is generated to select all namespaces.
+ * - label: text for label to add before the field.
+ * - exclude: [optional] Array of namespace ids to exclude.
+ * - disable: [optional] Array of namespace ids for which the option should
+ * be disabled in the selector.
* @param array $selectAttribs HTML attributes for the generated select element.
- * - id: [optional], default: 'namespace'
- * - name: [optional], default: 'namespace'
+ * - id: [optional], default: 'namespace'.
+ * - name: [optional], default: 'namespace'.
* @return string HTML code to select a namespace.
*/
- public static function namespaceSelector( array $params = array(), array $selectAttribs = array() ) {
+ public static function namespaceSelector( array $params = array(),
+ array $selectAttribs = array()
+ ) {
global $wgContLang;
ksort( $selectAttribs );
@@ -793,9 +876,9 @@ class Html {
* Constructs the opening html-tag with necessary doctypes depending on
* global variables.
*
- * @param array $attribs Associative array of miscellaneous extra
+ * @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element() of html tag.
- * @return string Raw HTML
+ * @return string Raw HTML
*/
public static function htmlHeader( $attribs = array() ) {
$ret = '';
@@ -805,7 +888,7 @@ class Html {
$isXHTML = self::isXmlMimeType( $wgMimeType );
if ( $isXHTML ) { // XHTML5
- // XML mimetyped markup should have an xml header.
+ // XML MIME-typed markup should have an xml header.
// However a DOCTYPE is not needed.
$ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\n";
@@ -837,37 +920,30 @@ class Html {
}
/**
- * Determines if the given mime type is xml.
+ * Determines if the given MIME type is xml.
*
- * @param $mimetype string MimeType
- * @return Boolean
+ * @param string $mimetype MIME type
+ * @return bool
*/
public static function isXmlMimeType( $mimetype ) {
# http://www.whatwg.org/html/infrastructure.html#xml-mime-type
# * text/xml
# * application/xml
- # * Any mimetype with a subtype ending in +xml (this implicitly includes application/xhtml+xml)
+ # * Any MIME type with a subtype ending in +xml (this implicitly includes application/xhtml+xml)
return (bool)preg_match( '!^(text|application)/xml$|^.+/.+\+xml$!', $mimetype );
}
/**
* Get HTML for an info box with an icon.
*
- * @param string $text wikitext, get this with wfMessage()->plain()
- * @param string $icon icon name, file in skins/common/images
- * @param string $alt alternate text for the icon
- * @param string $class additional class name to add to the wrapper div
- * @param $useStylePath
+ * @param string $text Wikitext, get this with wfMessage()->plain()
+ * @param string $icon Path to icon file (used as 'src' attribute)
+ * @param string $alt Alternate text for the icon
+ * @param string $class Additional class name to add to the wrapper div
*
* @return string
*/
- static function infoBox( $text, $icon, $alt, $class = false, $useStylePath = true ) {
- global $wgStylePath;
-
- if ( $useStylePath ) {
- $icon = $wgStylePath . '/common/images/' . $icon;
- }
-
+ static function infoBox( $text, $icon, $alt, $class = false ) {
$s = Html::openElement( 'div', array( 'class' => "mw-infobox $class" ) );
$s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ) .
diff --git a/includes/HtmlFormatter.php b/includes/HtmlFormatter.php
index 248a76fe..ccbfba82 100644
--- a/includes/HtmlFormatter.php
+++ b/includes/HtmlFormatter.php
@@ -34,7 +34,7 @@ class HtmlFormatter {
/**
* Constructor
*
- * @param string $html: Text to process
+ * @param string $html Text to process
*/
public function __construct( $html ) {
$this->html = $html;
@@ -51,15 +51,15 @@ class HtmlFormatter {
/**
* Override this in descendant class to modify HTML after it has been converted from DOM tree
- * @param string $html: HTML to process
- * @return string: Processed HTML
+ * @param string $html HTML to process
+ * @return string Processed HTML
*/
protected function onHtmlReady( $html ) {
return $html;
}
/**
- * @return DOMDocument: DOM to manipulate
+ * @return DOMDocument DOM to manipulate
*/
public function getDoc() {
if ( !$this->doc ) {
@@ -101,7 +101,7 @@ class HtmlFormatter {
* .<class>
* #<id>
*
- * @param Array|string $selectors: Selector(s) of stuff to remove
+ * @param array|string $selectors Selector(s) of stuff to remove
*/
public function remove( $selectors ) {
$this->itemsToRemove = array_merge( $this->itemsToRemove, (array)$selectors );
@@ -114,7 +114,7 @@ class HtmlFormatter {
* Note this interface may fail in surprising unexpected ways due to usage of regexes,
* so should not be relied on for HTML markup security measures.
*
- * @param Array|string $elements: Name(s) of tag(s) to flatten
+ * @param array|string $elements Name(s) of tag(s) to flatten
*/
public function flatten( $elements ) {
$this->elementsToFlatten = array_merge( $this->elementsToFlatten, (array)$elements );
@@ -128,15 +128,23 @@ class HtmlFormatter {
}
/**
- * Removes content we've chosen to remove
+ * Removes content we've chosen to remove. The text of the removed elements can be
+ * extracted with the getText method.
+ * @return array Array of removed DOMElements
*/
public function filterContent() {
wfProfileIn( __METHOD__ );
$removals = $this->parseItemsToRemove();
- if ( !$removals ) {
+ // Bail out early if nothing to do
+ if ( array_reduce( $removals,
+ function ( $carry, $item ) {
+ return $carry && !$item;
+ },
+ true
+ ) ) {
wfProfileOut( __METHOD__ );
- return;
+ return array();
}
$doc = $this->getDoc();
@@ -156,8 +164,7 @@ class HtmlFormatter {
}
}
}
-
- $this->removeElements( $domElemsToRemove );
+ $removed = $this->removeElements( $domElemsToRemove );
// Elements with named IDs
$domElemsToRemove = array();
@@ -167,7 +174,7 @@ class HtmlFormatter {
$domElemsToRemove[] = $itemToRemoveNode;
}
}
- $this->removeElements( $domElemsToRemove );
+ $removed = array_merge( $removed, $this->removeElements( $domElemsToRemove ) );
// CSS Classes
$domElemsToRemove = array();
@@ -183,7 +190,7 @@ class HtmlFormatter {
}
}
}
- $this->removeElements( $domElemsToRemove );
+ $removed = array_merge( $removed, $this->removeElements( $domElemsToRemove ) );
// Tags with CSS Classes
foreach ( $removals['TAG_CLASS'] as $classToRemove ) {
@@ -192,16 +199,17 @@ class HtmlFormatter {
$elements = $xpath->query(
'//' . $parts[0] . '[@class="' . $parts[1] . '"]'
);
-
- $this->removeElements( $elements );
+ $removed = array_merge( $removed, $this->removeElements( $elements ) );
}
wfProfileOut( __METHOD__ );
+ return $removed;
}
/**
* Removes a list of elelments from DOMDocument
* @param array|DOMNodeList $elements
+ * @return array Array of removed elements
*/
private function removeElements( $elements ) {
$list = $elements;
@@ -217,6 +225,7 @@ class HtmlFormatter {
$element->parentNode->removeChild( $element );
}
}
+ return $list;
}
/**
@@ -228,7 +237,7 @@ class HtmlFormatter {
private function fixLibXML( $html ) {
wfProfileIn( __METHOD__ );
static $replacements;
- if ( ! $replacements ) {
+ if ( !$replacements ) {
// We don't include rules like '&#34;' => '&amp;quot;' because entities had already been
// normalized by libxml. Using this function with input not sanitized by libxml is UNSAFE!
$replacements = new ReplacementArray( array(
@@ -245,15 +254,20 @@ class HtmlFormatter {
}
/**
- * Performs final transformations and returns resulting HTML
+ * Performs final transformations and returns resulting HTML. Note that if you want to call this
+ * both without an element and with an element you should call it without an element first. If you
+ * specify the $element in the method it'll change the underlying dom and you won't be able to get
+ * it back.
*
- * @param DOMElement|string|null $element: ID of element to get HTML from or false to get it from the whole tree
- * @return string: Processed HTML
+ * @param DOMElement|string|null $element ID of element to get HTML from or
+ * false to get it from the whole tree
+ * @return string Processed HTML
*/
public function getText( $element = null ) {
wfProfileIn( __METHOD__ );
if ( $this->doc ) {
+ wfProfileIn( __METHOD__ . '-dom' );
if ( $element !== null && !( $element instanceof DOMElement ) ) {
$element = $this->doc->getElementById( $element );
}
@@ -269,35 +283,45 @@ class HtmlFormatter {
$body->appendChild( $element );
}
$html = $this->doc->saveHTML();
+ wfProfileOut( __METHOD__ . '-dom' );
+
+ wfProfileIn( __METHOD__ . '-fixes' );
$html = $this->fixLibXml( $html );
+ if ( wfIsWindows() ) {
+ // Cleanup for CRLF misprocessing of unknown origin on Windows.
+ //
+ // If this error continues in the future, please track it down in the
+ // XML code paths if possible and fix there.
+ $html = str_replace( '&#13;', '', $html );
+ }
+ wfProfileOut( __METHOD__ . '-fixes' );
} else {
$html = $this->html;
}
- if ( wfIsWindows() ) {
- // Appears to be cleanup for CRLF misprocessing of unknown origin
- // when running server on Windows platform.
- //
- // If this error continues in the future, please track it down in the
- // XML code paths if possible and fix there.
- $html = str_replace( '&#13;', '', $html );
- }
+ // Remove stuff added by wrapHTML()
$html = preg_replace( '/<!--.*?-->|^.*?<body>|<\/body>.*$/s', '', $html );
$html = $this->onHtmlReady( $html );
+ wfProfileIn( __METHOD__ . '-flatten' );
if ( $this->elementsToFlatten ) {
$elements = implode( '|', $this->elementsToFlatten );
$html = preg_replace( "#</?($elements)\\b[^>]*>#is", '', $html );
}
+ wfProfileOut( __METHOD__ . '-flatten' );
wfProfileOut( __METHOD__ );
return $html;
}
/**
- * @param $selector: CSS selector to parse
- * @param $type
- * @param $rawName
- * @return bool: Whether the selector was successfully recognised
+ * Helper function for parseItemsToRemove(). This function extracts the selector type
+ * and the raw name of a selector from a CSS-style selector string and assigns those
+ * values to parameters passed by reference. For example, if given '#toc' as the
+ * $selector parameter, it will assign 'ID' as the $type and 'toc' as the $rawName.
+ * @param string $selector CSS selector to parse
+ * @param string $type The type of selector (ID, CLASS, TAG_CLASS, or TAG)
+ * @param string $rawName The raw name of the selector
+ * @return bool Whether the selector was successfully recognised
*/
protected function parseSelector( $selector, &$type, &$rawName ) {
if ( strpos( $selector, '.' ) === 0 ) {
@@ -306,14 +330,10 @@ class HtmlFormatter {
} elseif ( strpos( $selector, '#' ) === 0 ) {
$type = 'ID';
$rawName = substr( $selector, 1 );
- } elseif ( strpos( $selector, '.' ) !== 0 &&
- strpos( $selector, '.' ) !== false )
- {
+ } elseif ( strpos( $selector, '.' ) !== 0 && strpos( $selector, '.' ) !== false ) {
$type = 'TAG_CLASS';
$rawName = $selector;
- } elseif ( strpos( $selector, '[' ) === false
- && strpos( $selector, ']' ) === false )
- {
+ } elseif ( strpos( $selector, '[' ) === false && strpos( $selector, ']' ) === false ) {
$type = 'TAG';
$rawName = $selector;
} else {
@@ -324,7 +344,8 @@ class HtmlFormatter {
}
/**
- * Transforms CSS selectors into an internal representation suitable for processing
+ * Transforms CSS-style selectors into an internal representation suitable for
+ * processing by filterContent()
* @return array
*/
protected function parseItemsToRemove() {
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
index b405ede2..83021245 100644
--- a/includes/HttpFunctions.php
+++ b/includes/HttpFunctions.php
@@ -30,14 +30,14 @@
* @ingroup HTTP
*/
class Http {
- static $httpEngine = false;
+ static public $httpEngine = false;
/**
* Perform an HTTP request
*
* @param string $method HTTP method. Usually GET/POST
- * @param string $url full URL to act on. If protocol-relative, will be expanded to an http:// URL
- * @param array $options options to pass to MWHttpRequest object.
+ * @param string $url Full URL to act on. If protocol-relative, will be expanded to an http:// URL
+ * @param array $options Options to pass to MWHttpRequest object.
* Possible keys for the array:
* - timeout Timeout length in seconds
* - connectTimeout Timeout for connection, in seconds (curl only)
@@ -55,7 +55,7 @@ class Http {
* to avoid attacks on intranet services accessible by HTTP.
* - userAgent A user agent, if you want to override the default
* MediaWiki/$wgVersion
- * @return Mixed: (bool)false on failure or a string on success
+ * @return string|bool (bool)false on failure or a string on success
*/
public static function request( $method, $url, $options = array() ) {
wfDebug( "HTTP: $method: $url\n" );
@@ -85,9 +85,9 @@ class Http {
* Simple wrapper for Http::request( 'GET' )
* @see Http::request()
*
- * @param $url
- * @param $timeout string
- * @param $options array
+ * @param string $url
+ * @param string $timeout
+ * @param array $options
* @return string
*/
public static function get( $url, $timeout = 'default', $options = array() ) {
@@ -99,8 +99,8 @@ class Http {
* Simple wrapper for Http::request( 'POST' )
* @see Http::request()
*
- * @param $url
- * @param $options array
+ * @param string $url
+ * @param array $options
* @return string
*/
public static function post( $url, $options = array() ) {
@@ -110,8 +110,8 @@ class Http {
/**
* Check if the URL can be served by localhost
*
- * @param string $url full url to check
- * @return Boolean
+ * @param string $url Full url to check
+ * @return bool
*/
public static function isLocalURL( $url ) {
global $wgCommandLineMode, $wgConf;
@@ -130,7 +130,8 @@ class Http {
$domainParts = array_reverse( $domainParts );
$domain = '';
- for ( $i = 0; $i < count( $domainParts ); $i++ ) {
+ $countParts = count( $domainParts );
+ for ( $i = 0; $i < $countParts; $i++ ) {
$domainPart = $domainParts[$i];
if ( $i == 0 ) {
$domain = $domainPart;
@@ -149,7 +150,7 @@ class Http {
/**
* A standard user-agent we can use for external requests.
- * @return String
+ * @return string
*/
public static function userAgent() {
global $wgVersion;
@@ -165,8 +166,8 @@ class Http {
*
* @todo FIXME this is wildly inaccurate and fails to actually check most stuff
*
- * @param $uri Mixed: URI to check for validity
- * @return Boolean
+ * @param string $uri URI to check for validity
+ * @return bool
*/
public static function isValidURI( $uri ) {
return preg_match(
@@ -204,7 +205,7 @@ class MWHttpRequest {
protected $followRedirects = false;
/**
- * @var CookieJar
+ * @var CookieJar
*/
protected $cookieJar;
@@ -216,7 +217,7 @@ class MWHttpRequest {
public $status;
/**
- * @param string $url url to use. If protocol-relative, will be expanded to an http:// URL
+ * @param string $url Url to use. If protocol-relative, will be expanded to an http:// URL
* @param array $options (optional) extra params to pass (see Http::request())
*/
protected function __construct( $url, $options = array() ) {
@@ -275,7 +276,7 @@ class MWHttpRequest {
/**
* Generate a new request object
- * @param string $url url to use
+ * @param string $url Url to use
* @param array $options (optional) extra params to pass (see Http::request())
* @throws MWException
* @return CurlHttpRequest|PhpHttpRequest
@@ -294,8 +295,11 @@ class MWHttpRequest {
return new CurlHttpRequest( $url, $options );
case 'php':
if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
- throw new MWException( __METHOD__ . ': allow_url_fopen needs to be enabled for pure PHP' .
- ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' );
+ throw new MWException( __METHOD__ . ': allow_url_fopen ' .
+ 'needs to be enabled for pure PHP http requests to ' .
+ 'work. If possible, curl should be used instead. See ' .
+ 'http://php.net/curl.'
+ );
}
return new PhpHttpRequest( $url, $options );
default:
@@ -306,7 +310,7 @@ class MWHttpRequest {
/**
* Get the body, or content, of the response to the request
*
- * @return String
+ * @return string
*/
public function getContent() {
return $this->content;
@@ -315,7 +319,7 @@ class MWHttpRequest {
/**
* Set the parameters of the request
*
- * @param $args Array
+ * @param array $args
* @todo overload the args param
*/
public function setData( $args ) {
@@ -347,15 +351,8 @@ class MWHttpRequest {
}
/**
- * Set the referrer header
- */
- public function setReferer( $url ) {
- $this->setHeader( 'Referer', $url );
- }
-
- /**
* Set the user agent
- * @param $UA string
+ * @param string $UA
*/
public function setUserAgent( $UA ) {
$this->setHeader( 'User-Agent', $UA );
@@ -363,8 +360,8 @@ class MWHttpRequest {
/**
* Set an arbitrary header
- * @param $name
- * @param $value
+ * @param string $name
+ * @param string $value
*/
public function setHeader( $name, $value ) {
// I feel like I should normalize the case here...
@@ -408,7 +405,7 @@ class MWHttpRequest {
* bytes are reported handled than were passed to you, the HTTP fetch
* will be aborted.
*
- * @param $callback Callback
+ * @param callable $callback
* @throws MWException
*/
public function setCallback( $callback ) {
@@ -422,8 +419,8 @@ class MWHttpRequest {
* A generic callback to read the body of the response from a remote
* server.
*
- * @param $fh handle
- * @param $content String
+ * @param resource $fh
+ * @param string $content
* @return int
*/
public function read( $fh, $content ) {
@@ -437,8 +434,6 @@ class MWHttpRequest {
* @return Status
*/
public function execute() {
- global $wgTitle;
-
wfProfileIn( __METHOD__ );
$this->content = "";
@@ -447,10 +442,6 @@ class MWHttpRequest {
$this->headersOnly = true;
}
- if ( is_object( $wgTitle ) && !isset( $this->reqHeaders['Referer'] ) ) {
- $this->setReferer( wfExpandUrl( $wgTitle->getFullURL(), PROTO_CURRENT ) );
- }
-
$this->proxySetup(); // set up any proxy as needed
if ( !$this->callback ) {
@@ -516,7 +507,7 @@ class MWHttpRequest {
* (see RFC2616, section 10, http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
* for a list of status codes.)
*
- * @return Integer
+ * @return int
*/
public function getStatus() {
if ( !$this->respHeaders ) {
@@ -529,7 +520,7 @@ class MWHttpRequest {
/**
* Returns true if the last status code was a redirect.
*
- * @return Boolean
+ * @return bool
*/
public function isRedirect() {
if ( !$this->respHeaders ) {
@@ -551,7 +542,7 @@ class MWHttpRequest {
* (e.g. Set-Cookie) can appear more than once the, each value of
* the associative array is an array of the values given.
*
- * @return Array
+ * @return array
*/
public function getResponseHeaders() {
if ( !$this->respHeaders ) {
@@ -564,8 +555,8 @@ class MWHttpRequest {
/**
* Returns the value of the given response header.
*
- * @param $header String
- * @return String
+ * @param string $header
+ * @return string
*/
public function getResponseHeader( $header ) {
if ( !$this->respHeaders ) {
@@ -583,7 +574,7 @@ class MWHttpRequest {
/**
* Tells the MWHttpRequest object to use this pre-loaded CookieJar.
*
- * @param $jar CookieJar
+ * @param CookieJar $jar
*/
public function setCookieJar( $jar ) {
$this->cookieJar = $jar;
@@ -607,9 +598,9 @@ class MWHttpRequest {
* cookies. Used internally after a request to parse the
* Set-Cookie headers.
* @see Cookie::set
- * @param $name
- * @param $value null
- * @param $attr null
+ * @param string $name
+ * @param mixed $value
+ * @param array $attr
*/
public function setCookie( $name, $value = null, $attr = null ) {
if ( !$this->cookieJar ) {
@@ -642,12 +633,16 @@ class MWHttpRequest {
/**
* Returns the final URL after all redirections.
*
- * Relative values of the "Location" header are incorrect as stated in RFC, however they do happen and modern browsers support them.
- * This function loops backwards through all locations in order to build the proper absolute URI - Marooned at wikia-inc.com
+ * Relative values of the "Location" header are incorrect as
+ * stated in RFC, however they do happen and modern browsers
+ * support them. This function loops backwards through all
+ * locations in order to build the proper absolute URI - Marooned
+ * at wikia-inc.com
*
- * Note that the multiple Location: headers are an artifact of CURL -- they
- * shouldn't actually get returned this way. Rewrite this when bug 29232 is
- * taken care of (high-level redirect handling rewrite).
+ * Note that the multiple Location: headers are an artifact of
+ * CURL -- they shouldn't actually get returned this way. Rewrite
+ * this when bug 29232 is taken care of (high-level redirect
+ * handling rewrite).
*
* @return string
*/
@@ -678,7 +673,8 @@ class MWHttpRequest {
} else {
$url = parse_url( $this->url );
if ( isset( $url['host'] ) ) {
- return $url['scheme'] . '://' . $url['host'] . $locations[$countLocations - 1];
+ return $url['scheme'] . '://' . $url['host'] .
+ $locations[$countLocations - 1];
}
}
} else {
@@ -709,8 +705,8 @@ class CurlHttpRequest extends MWHttpRequest {
protected $headerText = "";
/**
- * @param $fh
- * @param $content
+ * @param resource $fh
+ * @param string $content
* @return int
*/
protected function readHeader( $fh, $content ) {
@@ -742,10 +738,6 @@ class CurlHttpRequest extends MWHttpRequest {
$this->curlOptions[CURLOPT_MAXREDIRS] = $this->maxRedirects;
$this->curlOptions[CURLOPT_ENCODING] = ""; # Enable compression
- /* not sure these two are actually necessary */
- if ( isset( $this->reqHeaders['Referer'] ) ) {
- $this->curlOptions[CURLOPT_REFERER] = $this->reqHeaders['Referer'];
- }
$this->curlOptions[CURLOPT_USERAGENT] = $this->reqHeaders['User-Agent'];
$this->curlOptions[CURLOPT_SSL_VERIFYHOST] = $this->sslVerifyHost ? 2 : 0;
@@ -780,7 +772,7 @@ class CurlHttpRequest extends MWHttpRequest {
if ( $this->followRedirects && $this->canFollowRedirects() ) {
wfSuppressWarnings();
- if ( ! curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, true ) ) {
+ if ( !curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, true ) ) {
wfDebug( __METHOD__ . ": Couldn't set CURLOPT_FOLLOWLOCATION. " .
"Probably safe_mode or open_basedir is set.\n" );
// Continue the processing. If it were in curl_setopt_array,
@@ -817,7 +809,8 @@ class CurlHttpRequest extends MWHttpRequest {
return false;
}
- if ( !defined( 'CURLOPT_REDIR_PROTOCOLS' ) ) {
+ $curlVersionInfo = curl_version();
+ if ( $curlVersionInfo['version_number'] < 0x071304 ) {
wfDebug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" );
return false;
}
@@ -829,7 +822,7 @@ class CurlHttpRequest extends MWHttpRequest {
class PhpHttpRequest extends MWHttpRequest {
/**
- * @param $url string
+ * @param string $url
* @return string
*/
protected function urlToTcp( $url ) {
@@ -847,8 +840,8 @@ class PhpHttpRequest extends MWHttpRequest {
$this->postData = wfArrayToCgi( $this->postData );
}
- if ( $this->parsedUrl['scheme'] != 'http' &&
- $this->parsedUrl['scheme'] != 'https' ) {
+ if ( $this->parsedUrl['scheme'] != 'http'
+ && $this->parsedUrl['scheme'] != 'https' ) {
$this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
}
diff --git a/includes/Import.php b/includes/Import.php
index 8b7af02a..5319076e 100644
--- a/includes/Import.php
+++ b/includes/Import.php
@@ -3,7 +3,7 @@
* MediaWiki page data importer.
*
* Copyright © 2003,2005 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -40,12 +40,14 @@ class WikiImporter {
/**
* Creates an ImportXMLReader drawing from the source provided
- * @param $source
+ * @param ImportStreamSource $source
*/
- function __construct( $source ) {
+ function __construct( ImportStreamSource $source ) {
$this->reader = new XMLReader();
- stream_wrapper_register( 'uploadsource', 'UploadSourceAdapter' );
+ if ( !in_array( 'uploadsource', stream_get_wrappers() ) ) {
+ stream_wrapper_register( 'uploadsource', 'UploadSourceAdapter' );
+ }
$id = UploadSourceAdapter::registerSource( $source );
if ( defined( 'LIBXML_PARSEHUGE' ) ) {
$this->reader->open( "uploadsource://$id", null, LIBXML_PARSEHUGE );
@@ -60,22 +62,29 @@ class WikiImporter {
$this->setPageOutCallback( array( $this, 'finishImportPage' ) );
}
- private function throwXmlError( $err ) {
+ /**
+ * @return null|XMLReader
+ */
+ public function getReader() {
+ return $this->reader;
+ }
+
+ public function throwXmlError( $err ) {
$this->debug( "FAILURE: $err" );
wfDebug( "WikiImporter XML error: $err\n" );
}
- private function debug( $data ) {
+ public function debug( $data ) {
if ( $this->mDebug ) {
wfDebug( "IMPORT: $data\n" );
}
}
- private function warn( $data ) {
+ public function warn( $data ) {
wfDebug( "IMPORT: $data\n" );
}
- private function notice( $msg /*, $param, ...*/ ) {
+ public function notice( $msg /*, $param, ...*/ ) {
$params = func_get_args();
array_shift( $params );
@@ -88,7 +97,7 @@ class WikiImporter {
/**
* Set debug mode...
- * @param $debug bool
+ * @param bool $debug
*/
function setDebug( $debug ) {
$this->mDebug = $debug;
@@ -96,7 +105,7 @@ class WikiImporter {
/**
* Set 'no updates' mode. In this mode, the link tables will not be updated by the importer
- * @param $noupdates bool
+ * @param bool $noupdates
*/
function setNoUpdates( $noupdates ) {
$this->mNoUpdates = $noupdates;
@@ -105,8 +114,8 @@ class WikiImporter {
/**
* Set a callback that displays notice messages
*
- * @param $callback callback
- * @return callback
+ * @param callable $callback
+ * @return callable
*/
public function setNoticeCallback( $callback ) {
return wfSetVar( $this->mNoticeCallback, $callback );
@@ -114,8 +123,8 @@ class WikiImporter {
/**
* Sets the action to perform as each new page in the stream is reached.
- * @param $callback callback
- * @return callback
+ * @param callable $callback
+ * @return callable
*/
public function setPageCallback( $callback ) {
$previous = $this->mPageCallback;
@@ -129,8 +138,8 @@ class WikiImporter {
* with the original title form (in case it's been overridden into a
* local namespace), and a count of revisions.
*
- * @param $callback callback
- * @return callback
+ * @param callable $callback
+ * @return callable
*/
public function setPageOutCallback( $callback ) {
$previous = $this->mPageOutCallback;
@@ -140,8 +149,8 @@ class WikiImporter {
/**
* Sets the action to perform as each page revision is reached.
- * @param $callback callback
- * @return callback
+ * @param callable $callback
+ * @return callable
*/
public function setRevisionCallback( $callback ) {
$previous = $this->mRevisionCallback;
@@ -151,8 +160,8 @@ class WikiImporter {
/**
* Sets the action to perform as each file upload version is reached.
- * @param $callback callback
- * @return callback
+ * @param callable $callback
+ * @return callable
*/
public function setUploadCallback( $callback ) {
$previous = $this->mUploadCallback;
@@ -162,8 +171,8 @@ class WikiImporter {
/**
* Sets the action to perform as each log item reached.
- * @param $callback callback
- * @return callback
+ * @param callable $callback
+ * @return callable
*/
public function setLogItemCallback( $callback ) {
$previous = $this->mLogItemCallback;
@@ -173,8 +182,8 @@ class WikiImporter {
/**
* Sets the action to perform when site info is encountered
- * @param $callback callback
- * @return callback
+ * @param callable $callback
+ * @return callable
*/
public function setSiteInfoCallback( $callback ) {
$previous = $this->mSiteInfoCallback;
@@ -184,7 +193,7 @@ class WikiImporter {
/**
* Set a target namespace to override the defaults
- * @param $namespace
+ * @param null|int $namespace
* @return bool
*/
public function setTargetNamespace( $namespace ) {
@@ -201,8 +210,8 @@ class WikiImporter {
/**
* Set a target root page under which all pages are imported
- * @param $rootpage
- * @return status object
+ * @param null|string $rootpage
+ * @return Status
*/
public function setTargetRootPage( $rootpage ) {
$status = Status::newGood();
@@ -211,7 +220,11 @@ class WikiImporter {
$this->mTargetRootPage = null;
} elseif ( $rootpage !== '' ) {
$rootpage = rtrim( $rootpage, '/' ); //avoid double slashes
- $title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace ) ? $this->mTargetNamespace : NS_MAIN );
+ $title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace )
+ ? $this->mTargetNamespace
+ : NS_MAIN
+ );
+
if ( !$title || $title->isExternal() ) {
$status->fatal( 'import-rootpage-invalid' );
} else {
@@ -233,14 +246,14 @@ class WikiImporter {
}
/**
- * @param $dir
+ * @param string $dir
*/
public function setImageBasePath( $dir ) {
$this->mImageBasePath = $dir;
}
/**
- * @param $import
+ * @param bool $import
*/
public function setImportUploads( $import ) {
$this->mImportUploads = $import;
@@ -248,10 +261,20 @@ class WikiImporter {
/**
* Default per-revision callback, performs the import.
- * @param $revision WikiRevision
+ * @param WikiRevision $revision
* @return bool
*/
public function importRevision( $revision ) {
+ if ( !$revision->getContentHandler()->canBeUsedOn( $revision->getTitle() ) ) {
+ $this->notice( 'import-error-bad-location',
+ $revision->getTitle()->getPrefixedText(),
+ $revision->getID(),
+ $revision->getModel(),
+ $revision->getFormat() );
+
+ return false;
+ }
+
try {
$dbw = wfGetDB( DB_MASTER );
return $dbw->deadlockLoop( array( $revision, 'importOldRevision' ) );
@@ -262,21 +285,23 @@ class WikiImporter {
$revision->getModel(),
$revision->getFormat() );
}
+
+ return false;
}
/**
* Default per-revision callback, performs the import.
- * @param $rev WikiRevision
+ * @param WikiRevision $revision
* @return bool
*/
- public function importLogItem( $rev ) {
+ public function importLogItem( $revision ) {
$dbw = wfGetDB( DB_MASTER );
- return $dbw->deadlockLoop( array( $rev, 'importLogItem' ) );
+ return $dbw->deadlockLoop( array( $revision, 'importLogItem' ) );
}
/**
* Dummy for now...
- * @param $revision
+ * @param WikiRevision $revision
* @return bool
*/
public function importUpload( $revision ) {
@@ -286,12 +311,12 @@ class WikiImporter {
/**
* Mostly for hook use
- * @param $title
- * @param $origTitle
- * @param $revCount
- * @param $sRevCount
- * @param $pageInfo
- * @return
+ * @param Title $title
+ * @param string $origTitle
+ * @param int $revCount
+ * @param int $sRevCount
+ * @param array $pageInfo
+ * @return bool
*/
public function finishImportPage( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) {
$args = func_get_args();
@@ -300,7 +325,7 @@ class WikiImporter {
/**
* Alternate per-revision callback, for debugging.
- * @param $revision WikiRevision
+ * @param WikiRevision $revision
*/
public function debugRevisionHandler( &$revision ) {
$this->debug( "Got revision:" );
@@ -317,7 +342,7 @@ class WikiImporter {
/**
* Notify the callback function when a new "<page>" is reached.
- * @param $title Title
+ * @param Title $title
*/
function pageCallback( $title ) {
if ( isset( $this->mPageCallback ) ) {
@@ -327,11 +352,11 @@ class WikiImporter {
/**
* Notify the callback function when a "</page>" is closed.
- * @param $title Title
- * @param $origTitle Title
- * @param $revCount Integer
- * @param int $sucCount number of revisions for which callback returned true
- * @param array $pageInfo associative array of page information
+ * @param Title $title
+ * @param Title $origTitle
+ * @param int $revCount
+ * @param int $sucCount Number of revisions for which callback returned true
+ * @param array $pageInfo Associative array of page information
*/
private function pageOutCallback( $title, $origTitle, $revCount, $sucCount, $pageInfo ) {
if ( isset( $this->mPageOutCallback ) ) {
@@ -342,7 +367,7 @@ class WikiImporter {
/**
* Notify the callback function of a revision
- * @param $revision WikiRevision object
+ * @param WikiRevision $revision
* @return bool|mixed
*/
private function revisionCallback( $revision ) {
@@ -356,7 +381,7 @@ class WikiImporter {
/**
* Notify the callback function of a new log item
- * @param $revision WikiRevision object
+ * @param WikiRevision $revision
* @return bool|mixed
*/
private function logItemCallback( $revision ) {
@@ -369,13 +394,22 @@ class WikiImporter {
}
/**
+ * Retrieves the contents of the named attribute of the current element.
+ * @param string $attr The name of the attribute
+ * @return string The value of the attribute or an empty string if it is not set in the current element.
+ */
+ public function nodeAttribute( $attr ) {
+ return $this->reader->getAttribute( $attr );
+ }
+
+ /**
* Shouldn't something like this be built-in to XMLReader?
* Fetches text contents of the current element, assuming
* no sub-elements or such scary things.
* @return string
* @access private
*/
- private function nodeContents() {
+ public function nodeContents() {
if ( $this->reader->isEmptyElement ) {
return "";
}
@@ -395,53 +429,12 @@ class WikiImporter {
return '';
}
- # --------------
-
- /** Left in for debugging */
- private function dumpElement() {
- static $lookup = null;
- if ( !$lookup ) {
- $xmlReaderConstants = array(
- "NONE",
- "ELEMENT",
- "ATTRIBUTE",
- "TEXT",
- "CDATA",
- "ENTITY_REF",
- "ENTITY",
- "PI",
- "COMMENT",
- "DOC",
- "DOC_TYPE",
- "DOC_FRAGMENT",
- "NOTATION",
- "WHITESPACE",
- "SIGNIFICANT_WHITESPACE",
- "END_ELEMENT",
- "END_ENTITY",
- "XML_DECLARATION",
- );
- $lookup = array();
-
- foreach ( $xmlReaderConstants as $name ) {
- $lookup[constant( "XmlReader::$name" )] = $name;
- }
- }
-
- print var_dump(
- $lookup[$this->reader->nodeType],
- $this->reader->name,
- $this->reader->value
- ) . "\n\n";
- }
-
/**
* Primary entry point
* @throws MWException
* @return bool
*/
public function doImport() {
-
// Calls to reader->read need to be wrapped in calls to
// libxml_disable_entity_loader() to avoid local file
// inclusion attacks (bug 46932).
@@ -499,7 +492,7 @@ class WikiImporter {
private function handleSiteInfo() {
// Site info is useful, but not actually used for dump imports.
// Includes a quick short-circuit to save performance.
- if ( ! $this->mSiteInfoCallback ) {
+ if ( !$this->mSiteInfoCallback ) {
$this->reader->next();
return true;
}
@@ -539,7 +532,7 @@ class WikiImporter {
}
/**
- * @param $logInfo
+ * @param array $logInfo
* @return bool|mixed
*/
private function processLogItem( $logInfo ) {
@@ -593,17 +586,28 @@ class WikiImporter {
&$pageInfo ) ) ) {
// Do nothing
} elseif ( in_array( $tag, $normalFields ) ) {
- $pageInfo[$tag] = $this->nodeContents();
- if ( $tag == 'title' ) {
- $title = $this->processTitle( $pageInfo['title'] );
+ // An XML snippet:
+ // <page>
+ // <id>123</id>
+ // <title>Page</title>
+ // <redirect title="NewTitle"/>
+ // ...
+ // Because the redirect tag is built differently, we need special handling for that case.
+ if ( $tag == 'redirect' ) {
+ $pageInfo[$tag] = $this->nodeAttribute( 'title' );
+ } else {
+ $pageInfo[$tag] = $this->nodeContents();
+ if ( $tag == 'title' ) {
+ $title = $this->processTitle( $pageInfo['title'] );
- if ( !$title ) {
- $badTitle = true;
- $skip = true;
- }
+ if ( !$title ) {
+ $badTitle = true;
+ $skip = true;
+ }
- $this->pageCallback( $title );
- list( $pageInfo['_title'], $origTitle ) = $title;
+ $this->pageCallback( $title );
+ list( $pageInfo['_title'], $origTitle ) = $title;
+ }
}
} elseif ( $tag == 'revision' ) {
$this->handleRevision( $pageInfo );
@@ -622,7 +626,7 @@ class WikiImporter {
}
/**
- * @param $pageInfo array
+ * @param array $pageInfo
*/
private function handleRevision( &$pageInfo ) {
$this->debug( "Enter revision handler" );
@@ -661,8 +665,8 @@ class WikiImporter {
}
/**
- * @param $pageInfo
- * @param $revisionInfo
+ * @param array $pageInfo
+ * @param array $revisionInfo
* @return bool|mixed
*/
private function processRevision( $pageInfo, $revisionInfo ) {
@@ -671,9 +675,6 @@ class WikiImporter {
if ( isset( $revisionInfo['id'] ) ) {
$revision->setID( $revisionInfo['id'] );
}
- if ( isset( $revisionInfo['text'] ) ) {
- $revision->setText( $revisionInfo['text'] );
- }
if ( isset( $revisionInfo['model'] ) ) {
$revision->setModel( $revisionInfo['model'] );
}
@@ -682,6 +683,14 @@ class WikiImporter {
}
$revision->setTitle( $pageInfo['_title'] );
+ if ( isset( $revisionInfo['text'] ) ) {
+ $handler = $revision->getContentHandler();
+ $text = $handler->importTransform(
+ $revisionInfo['text'],
+ $revision->getFormat() );
+
+ $revision->setText( $text );
+ }
if ( isset( $revisionInfo['timestamp'] ) ) {
$revision->setTimestamp( $revisionInfo['timestamp'] );
} else {
@@ -707,7 +716,7 @@ class WikiImporter {
}
/**
- * @param $pageInfo
+ * @param array $pageInfo
* @return mixed
*/
private function handleUpload( &$pageInfo ) {
@@ -762,7 +771,7 @@ class WikiImporter {
}
/**
- * @param $contents
+ * @param string $contents
* @return string
*/
private function dumpTemp( $contents ) {
@@ -772,8 +781,8 @@ class WikiImporter {
}
/**
- * @param $pageInfo
- * @param $uploadInfo
+ * @param array $pageInfo
+ * @param array $uploadInfo
* @return mixed
*/
private function processUpload( $pageInfo, $uploadInfo ) {
@@ -834,8 +843,8 @@ class WikiImporter {
}
/**
- * @param $text string
- * @return Array or false
+ * @param string $text
+ * @return array|bool
*/
private function processTitle( $text ) {
global $wgCommandLineMode;
@@ -881,17 +890,23 @@ class WikiImporter {
/** This is a horrible hack used to keep source compatibility */
class UploadSourceAdapter {
- static $sourceRegistrations = array();
+ /** @var array */
+ public static $sourceRegistrations = array();
+ /** @var string */
private $mSource;
+
+ /** @var string */
private $mBuffer;
+
+ /** @var int */
private $mPosition;
/**
- * @param $source
+ * @param ImportStreamSource $source
* @return string
*/
- static function registerSource( $source ) {
+ static function registerSource( ImportStreamSource $source ) {
$id = wfRandomString();
self::$sourceRegistrations[$id] = $source;
@@ -900,10 +915,10 @@ class UploadSourceAdapter {
}
/**
- * @param $path
- * @param $mode
- * @param $options
- * @param $opened_path
+ * @param string $path
+ * @param string $mode
+ * @param array $options
+ * @param string $opened_path
* @return bool
*/
function stream_open( $path, $mode, $options, &$opened_path ) {
@@ -920,7 +935,7 @@ class UploadSourceAdapter {
}
/**
- * @param $count
+ * @param int $count
* @return string
*/
function stream_read( $count ) {
@@ -949,7 +964,7 @@ class UploadSourceAdapter {
}
/**
- * @param $data
+ * @param string $data
* @return bool
*/
function stream_write( $data ) {
@@ -994,84 +1009,114 @@ class UploadSourceAdapter {
}
}
-class XMLReader2 extends XMLReader {
-
- /**
- * @return bool|string
- */
- function nodeContents() {
- if ( $this->isEmptyElement ) {
- return "";
- }
- $buffer = "";
- while ( $this->read() ) {
- switch ( $this->nodeType ) {
- case XmlReader::TEXT:
- case XmlReader::SIGNIFICANT_WHITESPACE:
- $buffer .= $this->value;
- break;
- case XmlReader::END_ELEMENT:
- return $buffer;
- }
- }
- return $this->close();
- }
-}
-
/**
* @todo document (e.g. one-sentence class description).
* @ingroup SpecialPage
*/
class WikiRevision {
- var $importer = null;
-
- /**
- * @var Title
- */
- var $title = null;
- var $id = 0;
- var $timestamp = "20010115000000";
- var $user = 0;
- var $user_text = "";
- var $model = null;
- var $format = null;
- var $text = "";
- var $content = null;
- var $comment = "";
- var $minor = false;
- var $type = "";
- var $action = "";
- var $params = "";
- var $fileSrc = '';
- var $sha1base36 = false;
- var $isTemp = false;
- var $archiveName = '';
- var $fileIsTemp;
+ /** @todo Unused? */
+ public $importer = null;
+
+ /** @var Title */
+ public $title = null;
+
+ /** @var int */
+ public $id = 0;
+
+ /** @var string */
+ public $timestamp = "20010115000000";
+
+ /**
+ * @var int
+ * @todo Can't find any uses. Public, because that's suspicious. Get clarity. */
+ public $user = 0;
+
+ /** @var string */
+ public $user_text = "";
+
+ /** @var string */
+ public $model = null;
+
+ /** @var string */
+ public $format = null;
+
+ /** @var string */
+ public $text = "";
+
+ /** @var int */
+ protected $size;
+
+ /** @var Content */
+ public $content = null;
+
+ /** @var ContentHandler */
+ protected $contentHandler = null;
+
+ /** @var string */
+ public $comment = "";
+
+ /** @var bool */
+ public $minor = false;
+
+ /** @var string */
+ public $type = "";
+
+ /** @var string */
+ public $action = "";
+
+ /** @var string */
+ public $params = "";
+
+ /** @var string */
+ public $fileSrc = '';
+
+ /** @var bool|string */
+ public $sha1base36 = false;
+
+ /**
+ * @var bool
+ * @todo Unused?
+ */
+ public $isTemp = false;
+
+ /** @var string */
+ public $archiveName = '';
+
+ protected $filename;
+
+ /** @var mixed */
+ protected $src;
+
+ /** @todo Unused? */
+ public $fileIsTemp;
+
+ /** @var bool */
private $mNoUpdates = false;
/**
- * @param $title
+ * @param Title $title
* @throws MWException
*/
function setTitle( $title ) {
if ( is_object( $title ) ) {
$this->title = $title;
} elseif ( is_null( $title ) ) {
- throw new MWException( "WikiRevision given a null title in import. You may need to adjust \$wgLegalTitleChars." );
+ throw new MWException( "WikiRevision given a null title in import. "
+ . "You may need to adjust \$wgLegalTitleChars." );
} else {
throw new MWException( "WikiRevision given non-object title in import." );
}
}
/**
- * @param $id
+ * @param int $id
*/
function setID( $id ) {
$this->id = $id;
}
/**
- * @param $ts
+ * @param string $ts
*/
function setTimestamp( $ts ) {
# 2003-08-05T18:30:02Z
@@ -1079,64 +1124,64 @@ class WikiRevision {
}
/**
- * @param $user
+ * @param string $user
*/
function setUsername( $user ) {
$this->user_text = $user;
}
/**
- * @param $ip
+ * @param string $ip
*/
function setUserIP( $ip ) {
$this->user_text = $ip;
}
/**
- * @param $model
+ * @param string $model
*/
function setModel( $model ) {
$this->model = $model;
}
/**
- * @param $format
+ * @param string $format
*/
function setFormat( $format ) {
$this->format = $format;
}
/**
- * @param $text
+ * @param string $text
*/
function setText( $text ) {
$this->text = $text;
}
/**
- * @param $text
+ * @param string $text
*/
function setComment( $text ) {
$this->comment = $text;
}
/**
- * @param $minor
+ * @param bool $minor
*/
function setMinor( $minor ) {
$this->minor = (bool)$minor;
}
/**
- * @param $src
+ * @param mixed $src
*/
function setSrc( $src ) {
$this->src = $src;
}
/**
- * @param $src
- * @param $isTemp
+ * @param string $src
+ * @param bool $isTemp
*/
function setFileSrc( $src, $isTemp ) {
$this->fileSrc = $src;
@@ -1144,56 +1189,56 @@ class WikiRevision {
}
/**
- * @param $sha1base36
+ * @param string $sha1base36
*/
function setSha1Base36( $sha1base36 ) {
$this->sha1base36 = $sha1base36;
}
/**
- * @param $filename
+ * @param string $filename
*/
function setFilename( $filename ) {
$this->filename = $filename;
}
/**
- * @param $archiveName
+ * @param string $archiveName
*/
function setArchiveName( $archiveName ) {
$this->archiveName = $archiveName;
}
/**
- * @param $size
+ * @param int $size
*/
function setSize( $size ) {
$this->size = intval( $size );
}
/**
- * @param $type
+ * @param string $type
*/
function setType( $type ) {
$this->type = $type;
}
/**
- * @param $action
+ * @param string $action
*/
function setAction( $action ) {
$this->action = $action;
}
/**
- * @param $params
+ * @param array $params
*/
function setParams( $params ) {
$this->params = $params;
}
/**
- * @param $noupdates
+ * @param bool $noupdates
*/
public function setNoUpdates( $noupdates ) {
$this->mNoUpdates = $noupdates;
@@ -1239,24 +1284,30 @@ class WikiRevision {
}
/**
+ * @return ContentHandler
+ */
+ function getContentHandler() {
+ if ( is_null( $this->contentHandler ) ) {
+ $this->contentHandler = ContentHandler::getForModelID( $this->getModel() );
+ }
+
+ return $this->contentHandler;
+ }
+
+ /**
* @return Content
*/
function getContent() {
if ( is_null( $this->content ) ) {
- $this->content =
- ContentHandler::makeContent(
- $this->text,
- $this->getTitle(),
- $this->getModel(),
- $this->getFormat()
- );
+ $handler = $this->getContentHandler();
+ $this->content = $handler->unserializeContent( $this->text, $this->getFormat() );
}
return $this->content;
}
/**
- * @return String
+ * @return string
*/
function getModel() {
if ( is_null( $this->model ) ) {
@@ -1267,11 +1318,11 @@ class WikiRevision {
}
/**
- * @return String
+ * @return string
*/
function getFormat() {
- if ( is_null( $this->model ) ) {
- $this->format = ContentHandler::getForTitle( $this->getTitle() )->getDefaultFormat();
+ if ( is_null( $this->format ) ) {
+ $this->format = $this->getContentHandler()->getDefaultFormat();
}
return $this->format;
@@ -1299,7 +1350,7 @@ class WikiRevision {
}
/**
- * @return bool|String
+ * @return bool|string
*/
function getSha1() {
if ( $this->sha1base36 ) {
@@ -1387,6 +1438,7 @@ class WikiRevision {
$linkCache->clear();
$page = WikiPage::factory( $this->title );
+ $page->loadPageData( 'fromdbmaster' );
if ( !$page->exists() ) {
# must create the page...
$pageId = $page->insertOn( $dbw );
@@ -1419,7 +1471,8 @@ class WikiRevision {
'page' => $pageId,
'content_model' => $this->getModel(),
'content_format' => $this->getFormat(),
- 'text' => $this->getContent()->serialize( $this->getFormat() ), //XXX: just set 'content' => $this->getContent()?
+ //XXX: just set 'content' => $this->getContent()?
+ 'text' => $this->getContent()->serialize( $this->getFormat() ),
'comment' => $this->getComment(),
'user' => $userId,
'user_text' => $userText,
@@ -1431,15 +1484,16 @@ class WikiRevision {
if ( $changed !== false && !$this->mNoUpdates ) {
wfDebug( __METHOD__ . ": running updates\n" );
- $page->doEditUpdates( $revision, $userObj, array( 'created' => $created, 'oldcountable' => $oldcountable ) );
+ $page->doEditUpdates(
+ $revision,
+ $userObj,
+ array( 'created' => $created, 'oldcountable' => $oldcountable )
+ );
}
return true;
}
- /**
- * @return mixed
- */
function importLogItem() {
$dbw = wfGetDB( DB_MASTER );
# @todo FIXME: This will not record autoblocks
@@ -1463,8 +1517,9 @@ class WikiRevision {
);
// @todo FIXME: This could fail slightly for multiple matches :P
if ( $prior ) {
- wfDebug( __METHOD__ . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp " .
- $this->timestamp . "\n" );
+ wfDebug( __METHOD__
+ . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp "
+ . $this->timestamp . "\n" );
return;
}
$log_id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
@@ -1636,7 +1691,7 @@ class ImportStreamSource {
}
/**
- * @param $filename string
+ * @param string $filename
* @return Status
*/
static function newFromFile( $filename ) {
@@ -1650,7 +1705,7 @@ class ImportStreamSource {
}
/**
- * @param $fieldname string
+ * @param string $fieldname
* @return Status
*/
static function newFromUpload( $fieldname = "xmlimport" ) {
@@ -1661,13 +1716,18 @@ class ImportStreamSource {
}
if ( !empty( $upload['error'] ) ) {
switch ( $upload['error'] ) {
- case 1: # The uploaded file exceeds the upload_max_filesize directive in php.ini.
+ case 1:
+ # The uploaded file exceeds the upload_max_filesize directive in php.ini.
return Status::newFatal( 'importuploaderrorsize' );
- case 2: # The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.
+ case 2:
+ # The uploaded file exceeds the MAX_FILE_SIZE directive that
+ # was specified in the HTML form.
return Status::newFatal( 'importuploaderrorsize' );
- case 3: # The uploaded file was only partially uploaded
+ case 3:
+ # The uploaded file was only partially uploaded
return Status::newFatal( 'importuploaderrorpartial' );
- case 6: #Missing a temporary folder.
+ case 6:
+ # Missing a temporary folder.
return Status::newFatal( 'importuploaderrortemp' );
# case else: # Currently impossible
}
@@ -1682,8 +1742,8 @@ class ImportStreamSource {
}
/**
- * @param $url
- * @param $method string
+ * @param string $url
+ * @param string $method
* @return Status
*/
static function newFromURL( $url, $method = 'GET' ) {
@@ -1705,19 +1765,21 @@ class ImportStreamSource {
}
/**
- * @param $interwiki
- * @param $page
- * @param $history bool
- * @param $templates bool
- * @param $pageLinkDepth int
+ * @param string $interwiki
+ * @param string $page
+ * @param bool $history
+ * @param bool $templates
+ * @param int $pageLinkDepth
* @return Status
*/
- public static function newFromInterwiki( $interwiki, $page, $history = false, $templates = false, $pageLinkDepth = 0 ) {
+ public static function newFromInterwiki( $interwiki, $page, $history = false,
+ $templates = false, $pageLinkDepth = 0
+ ) {
if ( $page == '' ) {
return Status::newFatal( 'import-noarticle' );
}
$link = Title::newFromText( "$interwiki:Special:Export/$page" );
- if ( is_null( $link ) || $link->getInterwiki() == '' ) {
+ if ( is_null( $link ) || !$link->isExternal() ) {
return Status::newFatal( 'importbadinterwiki' );
} else {
$params = array();
diff --git a/includes/Init.php b/includes/Init.php
deleted file mode 100644
index 64431f09..00000000
--- a/includes/Init.php
+++ /dev/null
@@ -1,136 +0,0 @@
-<?php
-/**
- * Some functions that are useful during startup.
- *
- * This class previously contained some functionality related to a PHP compiler
- * called hphpc. That compiler has now been discontinued.
- *
- * 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
- */
-
-/**
- * Some functions that are useful during startup.
- *
- * This class previously contained some functionality related to a PHP compiler
- * called hphpc. That compiler has now been discontinued. All methods are now
- * deprecated.
- */
-class MWInit {
- static $compilerVersion;
-
- /**
- * @deprecated since 1.22
- */
- static function getCompilerVersion() {
- return false;
- }
-
- /**
- * Returns true if we are running under HipHop, whether in compiled or
- * interpreted mode.
- *
- * @deprecated since 1.22
- * @return bool
- */
- static function isHipHop() {
- return defined( 'HPHP_VERSION' );
- }
-
- /**
- * Get a fully-qualified path for a source file relative to $IP.
- * @deprecated since 1.22
- *
- * @param $file string
- *
- * @return string
- */
- static function interpretedPath( $file ) {
- global $IP;
- return "$IP/$file";
- }
-
- /**
- * @deprecated since 1.22
- * @param $file string
- * @return string
- */
- static function compiledPath( $file ) {
- global $IP;
- return "$IP/$file";
- }
-
- /**
- * @deprecated since 1.22
- * @param $file string
- * @return string
- */
- static function extCompiledPath( $file ) {
- return false;
- }
-
- /**
- * Deprecated wrapper for class_exists()
- * @deprecated since 1.22
- *
- * @param $class string
- *
- * @return bool
- */
- static function classExists( $class ) {
- return class_exists( $class );
- }
-
- /**
- * Deprecated wrapper for method_exists()
- * @deprecated since 1.22
- *
- * @param $class string
- * @param $method string
- *
- * @return bool
- */
- static function methodExists( $class, $method ) {
- return method_exists( $class, $method );
- }
-
- /**
- * Deprecated wrapper for function_exists()
- * @deprecated since 1.22
- *
- * @param $function string
- *
- * @return bool
- */
- static function functionExists( $function ) {
- return function_exists( $function );
- }
-
- /**
- * Deprecated wrapper for call_user_func_array()
- * @deprecated since 1.22
- *
- * @param $className string
- * @param $methodName string
- * @param $args array
- *
- * @return mixed
- */
- static function callStaticMethod( $className, $methodName, $args ) {
- return call_user_func_array( array( $className, $methodName ), $args );
- }
-}
diff --git a/includes/Licenses.php b/includes/Licenses.php
index 6c582aaf..0bcbc91b 100644
--- a/includes/Licenses.php
+++ b/includes/Licenses.php
@@ -28,31 +28,25 @@
* A License class for use on Special:Upload
*/
class Licenses extends HTMLFormField {
- /**
- * @var string
- */
+ /** @var string */
protected $msg;
- /**
- * @var array
- */
+ /** @var array */
protected $licenses = array();
- /**
- * @var string
- */
+ /** @var string */
protected $html;
/**#@-*/
/**
- * Constructor
- *
- * @param $params array
+ * @param array $params
*/
public function __construct( $params ) {
parent::__construct( $params );
- $this->msg = empty( $params['licenses'] ) ? wfMessage( 'licenses' )->inContentLanguage()->plain() : $params['licenses'];
+ $this->msg = empty( $params['licenses'] )
+ ? wfMessage( 'licenses' )->inContentLanguage()->plain()
+ : $params['licenses'];
$this->selected = null;
$this->makeLicenses();
@@ -89,7 +83,7 @@ class Licenses extends HTMLFormField {
}
/**
- * @param $str
+ * @param string $str
* @return array
*/
protected function trimStars( $str ) {
@@ -98,9 +92,9 @@ class Licenses extends HTMLFormField {
}
/**
- * @param $list
- * @param $path
- * @param $item
+ * @param array $list
+ * @param array $path
+ * @param mixed $item
*/
protected function stackItem( &$list, $path, $item ) {
$position =& $list;
@@ -113,8 +107,8 @@ class Licenses extends HTMLFormField {
}
/**
- * @param $tagset
- * @param $depth int
+ * @param array $tagset
+ * @param int $depth
*/
protected function makeHtml( $tagset, $depth = 0 ) {
foreach ( $tagset as $key => $val ) {
@@ -139,10 +133,10 @@ class Licenses extends HTMLFormField {
}
/**
- * @param $message
- * @param $value
- * @param $attribs null
- * @param $depth int
+ * @param string $message
+ * @param string $value
+ * @param null|array $attribs
+ * @param int $depth
* @return string
*/
protected function outputOption( $message, $value, $attribs = null, $depth = 0 ) {
@@ -171,7 +165,7 @@ class Licenses extends HTMLFormField {
/**
* Accessor for $this->html
*
- * @param $value bool
+ * @param bool $value
*
* @return string
*/
@@ -198,20 +192,14 @@ class Licenses extends HTMLFormField {
* A License class for use on Special:Upload (represents a single type of license).
*/
class License {
- /**
- * @var string
- */
- var $template;
+ /** @var string */
+ public $template;
- /**
- * @var string
- */
- var $text;
+ /** @var string */
+ public $text;
/**
- * Constructor
- *
- * @param string $str license name??
+ * @param string $str License name??
*/
function __construct( $str ) {
list( $text, $template ) = explode( '|', strrev( $str ), 2 );
diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php
index d552c696..340ae8f3 100644
--- a/includes/LinkFilter.php
+++ b/includes/LinkFilter.php
@@ -35,9 +35,9 @@ class LinkFilter {
/**
* Check whether $content contains a link to $filterEntry
*
- * @param $content Content: content to check
- * @param string $filterEntry domainparts, see makeRegex() for more details
- * @return Integer: 0 if no match or 1 if there's at least one match
+ * @param Content $content Content to check
+ * @param string $filterEntry Domainparts, see makeRegex() for more details
+ * @return int 0 if no match or 1 if there's at least one match
*/
static function matchEntry( Content $content, $filterEntry ) {
if ( !( $content instanceof TextContent ) ) {
@@ -58,7 +58,7 @@ class LinkFilter {
*
* @param string $filterEntry URL, if it begins with "*.", it'll be
* replaced to match any subdomain
- * @return String: regex pattern, for preg_match()
+ * @return string Regex pattern, for preg_match()
*/
private static function makeRegex( $filterEntry ) {
$regex = '!http://';
@@ -84,16 +84,28 @@ class LinkFilter {
*
* Asterisks in any other location are considered invalid.
*
- * @param string $filterEntry domainparts
- * @param $prot String: protocol
- * @return Array to be passed to DatabaseBase::buildLike() or false on error
+ * This function does the same as wfMakeUrlIndexes(), except it also takes care
+ * of adding wildcards
+ *
+ * @param string $filterEntry Domainparts
+ * @param string $protocol Protocol (default http://)
+ * @return array Array to be passed to DatabaseBase::buildLike() or false on error
*/
- public static function makeLikeArray( $filterEntry, $prot = 'http://' ) {
+ public static function makeLikeArray( $filterEntry, $protocol = 'http://' ) {
$db = wfGetDB( DB_MASTER );
- if ( substr( $filterEntry, 0, 2 ) == '*.' ) {
+
+ $target = $protocol . $filterEntry;
+ $bits = wfParseUrl( $target );
+
+ if ( $bits == false ) {
+ // Unknown protocol?
+ return false;
+ }
+
+ if ( substr( $bits['host'], 0, 2 ) == '*.' ) {
$subdomains = true;
- $filterEntry = substr( $filterEntry, 2 );
- if ( $filterEntry == '' ) {
+ $bits['host'] = substr( $bits['host'], 2 );
+ if ( $bits['host'] == '' ) {
// We don't want to make a clause that will match everything,
// that could be dangerous
return false;
@@ -101,55 +113,66 @@ class LinkFilter {
} else {
$subdomains = false;
}
- // No stray asterisks, that could cause confusion
- // It's not simple or efficient to handle it properly so we don't
- // handle it at all.
- if ( strpos( $filterEntry, '*' ) !== false ) {
- return false;
- }
- $slash = strpos( $filterEntry, '/' );
- if ( $slash !== false ) {
- $path = substr( $filterEntry, $slash );
- $host = substr( $filterEntry, 0, $slash );
- } else {
- $path = '/';
- $host = $filterEntry;
- }
+
// Reverse the labels in the hostname, convert to lower case
// For emails reverse domainpart only
- if ( $prot == 'mailto:' && strpos( $host, '@' ) ) {
+ if ( $bits['scheme'] === 'mailto' && strpos( $bits['host'], '@' ) ) {
// complete email address
- $mailparts = explode( '@', $host );
+ $mailparts = explode( '@', $bits['host'] );
$domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
- $host = $domainpart . '@' . $mailparts[0];
- $like = array( "$prot$host", $db->anyString() );
- } elseif ( $prot == 'mailto:' ) {
- // domainpart of email address only. do not add '.'
- $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
- $like = array( "$prot$host", $db->anyString() );
+ $bits['host'] = $domainpart . '@' . $mailparts[0];
+ } elseif ( $bits['scheme'] === 'mailto' ) {
+ // domainpart of email address only, do not add '.'
+ $bits['host'] = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
} else {
- $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
- if ( substr( $host, -1, 1 ) !== '.' ) {
- $host .= '.';
+ $bits['host'] = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
+ if ( substr( $bits['host'], -1, 1 ) !== '.' ) {
+ $bits['host'] .= '.';
}
- $like = array( "$prot$host" );
+ }
- if ( $subdomains ) {
- $like[] = $db->anyString();
- }
- if ( !$subdomains || $path !== '/' ) {
- $like[] = $path;
- $like[] = $db->anyString();
+ $like[] = $bits['scheme'] . $bits['delimiter'] . $bits['host'];
+
+ if ( $subdomains ) {
+ $like[] = $db->anyString();
+ }
+
+ if ( isset( $bits['port'] ) ) {
+ $like[] = ':' . $bits['port'];
+ }
+ if ( isset( $bits['path'] ) ) {
+ $like[] = $bits['path'];
+ } elseif ( !$subdomains ) {
+ $like[] = '/';
+ }
+ if ( isset( $bits['query'] ) ) {
+ $like[] = '?' . $bits['query'];
+ }
+ if ( isset( $bits['fragment'] ) ) {
+ $like[] = '#' . $bits['fragment'];
+ }
+
+ // Check for stray asterisks: asterisk only allowed at the start of the domain
+ foreach ( $like as $likepart ) {
+ if ( !( $likepart instanceof LikeMatch ) && strpos( $likepart, '*' ) !== false ) {
+ return false;
}
}
+
+ if ( !( $like[count( $like ) - 1] instanceof LikeMatch ) ) {
+ // Add wildcard at the end if there isn't one already
+ $like[] = $db->anyString();
+ }
+
return $like;
}
/**
- * Filters an array returned by makeLikeArray(), removing everything past first pattern placeholder.
+ * Filters an array returned by makeLikeArray(), removing everything past first
+ * pattern placeholder.
*
- * @param array $arr array to filter
- * @return array filtered array
+ * @param array $arr Array to filter
+ * @return array Filtered array
*/
public static function keepOneWildcard( $arr ) {
if ( !is_array( $arr ) ) {
diff --git a/includes/Linker.php b/includes/Linker.php
index 23ece751..be850d02 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -25,10 +25,11 @@
* for primarily page content: links, embedded images, table of contents. Links
* are also used in the skin.
*
+ * @todo turn this into a legacy interface for HtmlPageLinkRenderer and similar services.
+ *
* @ingroup Skins
*/
class Linker {
-
/**
* Flags for userToolLinks()
*/
@@ -39,10 +40,11 @@ class Linker {
* Get the appropriate HTML attributes to add to the "a" element of an
* external link, as created by [wikisyntax].
*
- * @param string $class the contents of the class attribute; if an empty
+ * @param string $class The contents of the class attribute; if an empty
* string is passed, which is the default value, defaults to 'external'.
* @return string
- * @deprecated since 1.18 Just pass the external class directly to something using Html::expandAttributes
+ * @deprecated since 1.18 Just pass the external class directly to something
+ * using Html::expandAttributes.
*/
static function getExternalLinkAttributes( $class = 'external' ) {
wfDeprecated( __METHOD__, '1.18' );
@@ -52,10 +54,10 @@ class Linker {
/**
* Get the appropriate HTML attributes to add to the "a" element of an interwiki link.
*
- * @param string $title the title text for the link, URL-encoded (???) but
+ * @param string $title The title text for the link, URL-encoded (???) but
* not HTML-escaped
- * @param string $unused unused
- * @param string $class the contents of the class attribute; if an empty
+ * @param string $unused Unused
+ * @param string $class The contents of the class attribute; if an empty
* string is passed, which is the default value, defaults to 'external'.
* @return string
*/
@@ -74,10 +76,10 @@ class Linker {
/**
* Get the appropriate HTML attributes to add to the "a" element of an internal link.
*
- * @param string $title the title text for the link, URL-encoded (???) but
+ * @param string $title The title text for the link, URL-encoded (???) but
* not HTML-escaped
- * @param string $unused unused
- * @param string $class the contents of the class attribute, default none
+ * @param string $unused Unused
+ * @param string $class The contents of the class attribute, default none
* @return string
*/
static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
@@ -90,10 +92,10 @@ class Linker {
* Get the appropriate HTML attributes to add to the "a" element of an internal
* link, given the Title object for the page we want to link to.
*
- * @param $nt Title
- * @param string $unused unused
- * @param string $class the contents of the class attribute, default none
- * @param $title Mixed: optional (unescaped) string to use in the title
+ * @param Title $nt
+ * @param string $unused Unused
+ * @param string $class The contents of the class attribute, default none
+ * @param string|bool $title Optional (unescaped) string to use in the title
* attribute; if false, default to the name of the page we're linking to
* @return string
*/
@@ -107,8 +109,8 @@ class Linker {
/**
* Common code for getLinkAttributesX functions
*
- * @param $title string
- * @param $class string
+ * @param string $title
+ * @param string $class
*
* @return string
*/
@@ -128,9 +130,9 @@ class Linker {
/**
* Return the CSS colour of a known link
*
- * @param $t Title object
- * @param $threshold Integer: user defined threshold
- * @return String: CSS class
+ * @param Title $t
+ * @param int $threshold User defined threshold
+ * @return string CSS class
*/
public static function getLinkColour( $t, $threshold ) {
$colour = '';
@@ -159,25 +161,22 @@ class Linker {
* link() replaces the old functions in the makeLink() family.
*
* @since 1.18 Method exists since 1.16 as non-static, made static in 1.18.
- * You can call it using this if you want to keep compat with these:
- * $linker = class_exists( 'DummyLinker' ) ? new DummyLinker() : new Linker();
- * $linker->link( ... );
*
- * @param $target Title Can currently only be a Title, but this may
+ * @param Title $target Can currently only be a Title, but this may
* change to support Images, literal URLs, etc.
- * @param $html string The HTML contents of the <a> element, i.e.,
+ * @param string $html The HTML contents of the <a> element, i.e.,
* the link text. This is raw HTML and will not be escaped. If null,
* defaults to the prefixed text of the Title; or if the Title is just a
* fragment, the contents of the fragment.
- * @param array $customAttribs A key => value array of extra HTML attributes,
+ * @param array $customAttribs A key => value array of extra HTML attributes,
* such as title and class. (href is ignored.) Classes will be
* merged with the default classes, while other attributes will replace
* default attributes. All passed attribute values will be HTML-escaped.
* A false attribute value means to suppress that attribute.
- * @param $query array The query string to append to the URL
+ * @param array $query The query string to append to the URL
* you're linking to, in key => value array form. Query keys and values
* will be URL-encoded.
- * @param string|array $options String or array of strings:
+ * @param string|array $options String or array of strings:
* 'known': Page is known to exist, so don't check if it does.
* 'broken': Page is known not to exist, so don't check if it does.
* 'noclasses': Don't add any classes automatically (includes "new",
@@ -193,11 +192,11 @@ class Linker {
public static function link(
$target, $html = null, $customAttribs = array(), $query = array(), $options = array()
) {
- wfProfileIn( __METHOD__ );
if ( !$target instanceof Title ) {
- wfProfileOut( __METHOD__ );
+ wfWarn( __METHOD__ . ': Requires $target to be a Title object.' );
return "<!-- ERROR -->$html";
}
+ wfProfileIn( __METHOD__ );
if ( is_string( $query ) ) {
// some functions withing core using this still hand over query strings
@@ -260,30 +259,31 @@ class Linker {
/**
* Identical to link(), except $options defaults to 'known'.
+ * @see Linker::link
* @return string
*/
public static function linkKnown(
$target, $html = null, $customAttribs = array(),
- $query = array(), $options = array( 'known', 'noclasses' ) )
- {
+ $query = array(), $options = array( 'known', 'noclasses' )
+ ) {
return self::link( $target, $html, $customAttribs, $query, $options );
}
/**
* Returns the Url used to link to a Title
*
- * @param $target Title
- * @param array $query query parameters
- * @param $options Array
- * @return String
+ * @param Title $target
+ * @param array $query Query parameters
+ * @param array $options
+ * @return string
*/
private static function linkUrl( $target, $query, $options ) {
wfProfileIn( __METHOD__ );
# We don't want to include fragments for broken links, because they
# generally make no sense.
- if ( in_array( 'broken', $options ) && $target->mFragment !== '' ) {
+ if ( in_array( 'broken', $options ) && $target->hasFragment() ) {
$target = clone $target;
- $target->mFragment = '';
+ $target->setFragment( '' );
}
# If it's a broken link, add the appropriate query pieces, unless
@@ -311,9 +311,9 @@ class Linker {
/**
* Returns the array of attributes used when linking to the Title $target
*
- * @param $target Title
- * @param $attribs
- * @param $options
+ * @param Title $target
+ * @param array $attribs
+ * @param array $options
*
* @return array
*/
@@ -375,62 +375,44 @@ class Linker {
/**
* Default text of the links to the Title $target
*
- * @param $target Title
+ * @param Title $target
*
* @return string
*/
private static function linkText( $target ) {
- // We might be passed a non-Title by make*LinkObj(). Fail gracefully.
if ( !$target instanceof Title ) {
+ wfWarn( __METHOD__ . ': Requires $target to be a Title object.' );
return '';
}
-
// If the target is just a fragment, with no title, we return the fragment
// text. Otherwise, we return the title text itself.
- if ( $target->getPrefixedText() === '' && $target->getFragment() !== '' ) {
+ if ( $target->getPrefixedText() === '' && $target->hasFragment() ) {
return htmlspecialchars( $target->getFragment() );
}
- return htmlspecialchars( $target->getPrefixedText() );
- }
-
- /**
- * Generate either a normal exists-style link or a stub link, depending
- * on the given page size.
- *
- * @param $size Integer
- * @param $nt Title object.
- * @param $text String
- * @param $query String
- * @param $trail String
- * @param $prefix String
- * @return string HTML of link
- * @deprecated since 1.17
- */
- static function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
- global $wgUser;
- wfDeprecated( __METHOD__, '1.17' );
- $threshold = $wgUser->getStubThreshold();
- $colour = ( $size < $threshold ) ? 'stub' : '';
- // @todo FIXME: Replace deprecated makeColouredLinkObj by link()
- return self::makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
+ return htmlspecialchars( $target->getPrefixedText() );
}
/**
- * Make appropriate markup for a link to the current article. This is currently rendered
- * as the bold link text. The calling sequence is the same as the other make*LinkObj static functions,
- * despite $query not being used.
+ * Make appropriate markup for a link to the current article. This is
+ * currently rendered as the bold link text. The calling sequence is the
+ * same as the other make*LinkObj static functions, despite $query not
+ * being used.
*
- * @param $nt Title
+ * @param Title $nt
* @param string $html [optional]
* @param string $query [optional]
* @param string $trail [optional]
* @param string $prefix [optional]
*
- *
* @return string
*/
public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
+ $ret = "<strong class=\"selflink\">{$prefix}{$html}</strong>{$trail}";
+ if ( !wfRunHooks( 'SelfLinkBegin', array( $nt, &$html, &$trail, &$prefix, &$ret ) ) ) {
+ return $ret;
+ }
+
if ( $html == '' ) {
$html = htmlspecialchars( $nt->getPrefixedText() );
}
@@ -443,7 +425,7 @@ class Linker {
* This should be called after a method like Title::makeTitleSafe() returned
* a value indicating that the title object is invalid.
*
- * @param $context IContextSource context to use to get the messages
+ * @param IContextSource $context Context to use to get the messages
* @param int $namespace Namespace number
* @param string $title Text of the title, without the namespace part
* @return string
@@ -465,7 +447,7 @@ class Linker {
}
/**
- * @param $title Title
+ * @param Title $title
* @return Title
*/
static function normaliseSpecialPage( Title $title ) {
@@ -474,8 +456,7 @@ class Linker {
if ( !$name ) {
return $title;
}
- $ret = SpecialPage::getTitleFor( $name, $subpage );
- $ret->mFragment = $title->getFragment();
+ $ret = SpecialPage::getTitleFor( $name, $subpage, $title->getFragment() );
return $ret;
} else {
return $title;
@@ -486,7 +467,7 @@ class Linker {
* Returns the filename part of an url.
* Used as alternative text for external images.
*
- * @param $url string
+ * @param string $url
*
* @return string
*/
@@ -504,8 +485,8 @@ class Linker {
* Return the code for images which were added via external links,
* via Parser::maybeMakeExternalImage().
*
- * @param $url
- * @param $alt
+ * @param string $url
+ * @param string $alt
*
* @return string
*/
@@ -516,7 +497,8 @@ class Linker {
$img = '';
$success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
if ( !$success ) {
- wfDebug( "Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true );
+ wfDebug( "Hook LinkerMakeExternalImage changed the output of external image "
+ . "with url {$url} and alt text {$alt} to {$img}\n", true );
return $img;
}
return Html::element( 'img',
@@ -529,10 +511,10 @@ class Linker {
* Given parameters derived from [[Image:Foo|options...]], generate the
* HTML that that syntax inserts in the page.
*
- * @param $parser Parser object
- * @param $title Title object of the file (not the currently viewed page)
- * @param $file File object, or false if it doesn't exist
- * @param array $frameParams associative array of parameters external to the media handler.
+ * @param Parser $parser
+ * @param Title $title Title object of the file (not the currently viewed page)
+ * @param File $file File object, or false if it doesn't exist
+ * @param array $frameParams Associative array of parameters external to the media handler.
* Boolean parameters are indicated by presence or absence, the value is arbitrary and
* will often be false.
* thumbnail If present, downscale and frame
@@ -553,17 +535,18 @@ class Linker {
* link-target Value for the target attribute, only with link-url
* no-link Boolean, suppress description link
*
- * @param array $handlerParams associative array of media handler parameters, to be passed
+ * @param array $handlerParams Associative array of media handler parameters, to be passed
* to transform(). Typical keys are "width" and "page".
- * @param string $time timestamp of the file, set as false for current
- * @param string $query query params for desc url
- * @param $widthOption: Used by the parser to remember the user preference thumbnailsize
+ * @param string|bool $time Timestamp of the file, set as false for current
+ * @param string $query Query params for desc url
+ * @param int|null $widthOption Used by the parser to remember the user preference thumbnailsize
* @since 1.20
- * @return String: HTML for an image, with links, wrappers, etc.
+ * @return string HTML for an image, with links, wrappers, etc.
*/
- public static function makeImageLink( /*Parser*/ $parser, Title $title, $file, $frameParams = array(),
- $handlerParams = array(), $time = false, $query = "", $widthOption = null )
- {
+ public static function makeImageLink( Parser $parser, Title $title,
+ $file, $frameParams = array(), $handlerParams = array(), $time = false,
+ $query = "", $widthOption = null
+ ) {
$res = null;
$dummy = new DummyLinker;
if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title,
@@ -612,8 +595,14 @@ class Linker {
$hp['width'] = $file->getWidth( $page );
}
- if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
+ if ( isset( $fp['thumbnail'] )
+ || isset( $fp['manualthumb'] )
+ || isset( $fp['framed'] )
+ || isset( $fp['frameless'] )
+ || !$hp['width']
+ ) {
global $wgThumbLimits, $wgThumbUpright;
+
if ( $widthOption === null || !isset( $wgThumbLimits[$widthOption] ) ) {
$widthOption = User::getDefaultOption( 'thumbsize' );
}
@@ -622,7 +611,10 @@ class Linker {
if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) {
$fp['upright'] = $wgThumbUpright;
}
- // For caching health: If width scaled down due to upright parameter, round to full __0 pixel to avoid the creation of a lot of odd thumbs
+
+ // For caching health: If width scaled down due to upright
+ // parameter, round to full __0 pixel to avoid the creation of a
+ // lot of odd thumbs.
$prefWidth = isset( $fp['upright'] ) ?
round( $wgThumbLimits[$widthOption] * $fp['upright'], -1 ) :
$wgThumbLimits[$widthOption];
@@ -644,21 +636,16 @@ class Linker {
# If a thumbnail width has not been provided, it is set
# to the default user option as specified in Language*.php
if ( $fp['align'] == '' ) {
- if ( $parser instanceof Parser ) {
- $fp['align'] = $parser->getTargetLanguage()->alignEnd();
- } else {
- # backwards compatibility, remove with makeImageLink2()
- global $wgContLang;
- $fp['align'] = $wgContLang->alignEnd();
- }
+ $fp['align'] = $parser->getTargetLanguage()->alignEnd();
}
return $prefix . self::makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
}
if ( $file && isset( $fp['frameless'] ) ) {
$srcWidth = $file->getWidth( $page );
- # For "frameless" option: do not present an image bigger than the source (for bitmap-style images)
- # This is the same behavior as the "thumb" option does it already.
+ # For "frameless" option: do not present an image bigger than the
+ # source (for bitmap-style images). This is the same behavior as the
+ # "thumb" option does it already.
if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
$hp['width'] = $srcWidth;
}
@@ -681,8 +668,7 @@ class Linker {
'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false,
'img-class' => $fp['class'] );
if ( isset( $fp['border'] ) ) {
- // TODO: BUG? Both values are identical
- $params['img-class'] .= ( $params['img-class'] !== '' ) ? ' thumbborder' : 'thumbborder';
+ $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
}
$params = self::getImageLinkMTOParams( $fp, $query, $parser ) + $params;
@@ -710,6 +696,7 @@ class Linker {
* frame parameters supplied by the Parser.
* @param array $frameParams The frame parameters
* @param string $query An optional query string to add to description page links
+ * @param Parser|null $parser
* @return array
*/
private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
@@ -739,19 +726,19 @@ class Linker {
/**
* Make HTML for a thumbnail including image, border and caption
- * @param $title Title object
- * @param $file File object or false if it doesn't exist
- * @param $label String
- * @param $alt String
- * @param $align String
- * @param $params Array
- * @param $framed Boolean
- * @param $manualthumb String
- * @return mixed
+ * @param Title $title
+ * @param File|bool $file File object or false if it doesn't exist
+ * @param string $label
+ * @param string $alt
+ * @param string $align
+ * @param array $params
+ * @param bool $framed
+ * @param string $manualthumb
+ * @return string
*/
public static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
- $align = 'right', $params = array(), $framed = false, $manualthumb = "" )
- {
+ $align = 'right', $params = array(), $framed = false, $manualthumb = ""
+ ) {
$frameParams = array(
'alt' => $alt,
'caption' => $label,
@@ -767,18 +754,17 @@ class Linker {
}
/**
- * @param $title Title
- * @param $file File
+ * @param Title $title
+ * @param File $file
* @param array $frameParams
* @param array $handlerParams
* @param bool $time
* @param string $query
- * @return mixed
+ * @return string
*/
public static function makeThumbLink2( Title $title, $file, $frameParams = array(),
- $handlerParams = array(), $time = false, $query = "" )
- {
- global $wgStylePath, $wgContLang;
+ $handlerParams = array(), $time = false, $query = ""
+ ) {
$exists = $file && $file->exists();
# Shortcuts
@@ -850,14 +836,16 @@ class Linker {
if ( $page ) {
$url = wfAppendQuery( $url, array( 'page' => $page ) );
}
- if ( $manualthumb &&
- !isset( $fp['link-title'] ) &&
- !isset( $fp['link-url'] ) &&
- !isset( $fp['no-link'] ) ) {
+ if ( $manualthumb
+ && !isset( $fp['link-title'] )
+ && !isset( $fp['link-url'] )
+ && !isset( $fp['no-link'] ) ) {
$fp['link-url'] = $url;
}
- $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
+ $s = "<div class=\"thumb t{$fp['align']}\">"
+ . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
+
if ( !$exists ) {
$s .= self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
$zoomIcon = '';
@@ -871,7 +859,9 @@ class Linker {
$params = array(
'alt' => $fp['alt'],
'title' => $fp['title'],
- 'img-class' => ( isset( $fp['class'] ) && $fp['class'] !== '' ) ? $fp['class'] . ' thumbimage' : 'thumbimage'
+ 'img-class' => ( isset( $fp['class'] ) && $fp['class'] !== ''
+ ? $fp['class'] . ' '
+ : '' ) . 'thumbimage'
);
$params = self::getImageLinkMTOParams( $fp, $query ) + $params;
$s .= $thumb->toHtml( $params );
@@ -883,11 +873,7 @@ class Linker {
'href' => $url,
'class' => 'internal',
'title' => wfMessage( 'thumbnail-more' )->text() ),
- Html::element( 'img', array(
- 'src' => $wgStylePath . '/common/images/magnify-clip' . ( $wgContLang->isRTL() ? '-rtl' : '' ) . '.png',
- 'width' => 15,
- 'height' => 11,
- 'alt' => "" ) ) ) );
+ "" ) );
}
}
$s .= ' <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
@@ -899,12 +885,12 @@ class Linker {
* applicable.
*
* @param File $file
- * @param MediaOutput $thumb
- * @param array $hp image parameters
+ * @param MediaTransformOutput $thumb
+ * @param array $hp Image parameters
*/
- protected static function processResponsiveImages( $file, $thumb, $hp ) {
+ public static function processResponsiveImages( $file, $thumb, $hp ) {
global $wgResponsiveImages;
- if ( $wgResponsiveImages ) {
+ if ( $wgResponsiveImages && $thumb && !$thumb->isError() ) {
$hp15 = $hp;
$hp15['width'] = round( $hp['width'] * 1.5 );
$hp20 = $hp;
@@ -916,11 +902,11 @@ class Linker {
$thumb15 = $file->transform( $hp15 );
$thumb20 = $file->transform( $hp20 );
- if ( $thumb15->url !== $thumb->url ) {
- $thumb->responsiveUrls['1.5'] = $thumb15->url;
+ if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
+ $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
}
- if ( $thumb20->url !== $thumb->url ) {
- $thumb->responsiveUrls['2'] = $thumb20->url;
+ if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
+ $thumb->responsiveUrls['2'] = $thumb20->getUrl();
}
}
}
@@ -928,19 +914,23 @@ class Linker {
/**
* Make a "broken" link to an image
*
- * @param $title Title object
- * @param string $label link label (plain text)
- * @param string $query query string
- * @param $unused1 Unused parameter kept for b/c
- * @param $unused2 Unused parameter kept for b/c
- * @param $time Boolean: a file of a certain timestamp was requested
- * @return String
+ * @param Title $title
+ * @param string $label Link label (plain text)
+ * @param string $query Query string
+ * @param string $unused1 Unused parameter kept for b/c
+ * @param string $unused2 Unused parameter kept for b/c
+ * @param bool $time A file of a certain timestamp was requested
+ * @return string
*/
- public static function makeBrokenImageLinkObj( $title, $label = '', $query = '', $unused1 = '', $unused2 = '', $time = false ) {
- global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
- if ( ! $title instanceof Title ) {
+ public static function makeBrokenImageLinkObj( $title, $label = '',
+ $query = '', $unused1 = '', $unused2 = '', $time = false
+ ) {
+ if ( !$title instanceof Title ) {
+ wfWarn( __METHOD__ . ': Requires $title to be a Title object.' );
return "<!-- ERROR -->" . htmlspecialchars( $label );
}
+
+ global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
wfProfileIn( __METHOD__ );
if ( $label == '' ) {
$label = $title->getPrefixedText();
@@ -948,7 +938,9 @@ class Linker {
$encLabel = htmlspecialchars( $label );
$currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
- if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) {
+ if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads )
+ && !$currentExists
+ ) {
$redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
if ( $redir ) {
@@ -971,9 +963,9 @@ class Linker {
/**
* Get the URL to upload a certain file
*
- * @param $destFile Title object of the file to upload
- * @param string $query urlencoded query string to prepend
- * @return String: urlencoded URL
+ * @param Title $destFile Title object of the file to upload
+ * @param string $query Urlencoded query string to prepend
+ * @return string Urlencoded URL
*/
protected static function getUploadUrl( $destFile, $query = '' ) {
global $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
@@ -995,10 +987,10 @@ class Linker {
/**
* Create a direct link to a given uploaded file.
*
- * @param $title Title object.
- * @param string $html pre-sanitized HTML
+ * @param Title $title
+ * @param string $html Pre-sanitized HTML
* @param string $time MW timestamp of file creation time
- * @return String: HTML
+ * @return string HTML
*/
public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
$img = wfFindFile( $title, array( 'time' => $time ) );
@@ -1009,10 +1001,10 @@ class Linker {
* Create a direct link to a given uploaded file.
* This will make a broken link if $file is false.
*
- * @param $title Title object.
- * @param $file File|bool mixed File object or false
- * @param string $html pre-sanitized HTML
- * @return String: HTML
+ * @param Title $title
+ * @param File|bool $file File object or false
+ * @param string $html Pre-sanitized HTML
+ * @return string HTML
*
* @todo Handle invalid or missing images better.
*/
@@ -1024,12 +1016,27 @@ class Linker {
$url = self::getUploadUrl( $title );
$class = 'new';
}
- $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
+
+ $alt = $title->getText();
if ( $html == '' ) {
$html = $alt;
}
- $u = htmlspecialchars( $url );
- return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$html}</a>";
+
+ $ret = '';
+ $attribs = array(
+ 'href' => $url,
+ 'class' => $class,
+ 'title' => $alt
+ );
+
+ if ( !wfRunHooks( 'LinkerMakeMediaLinkFile',
+ array( $title, $file, &$html, &$attribs, &$ret ) ) ) {
+ wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
+ . "with url {$url} and text {$html} to {$ret}\n", true );
+ return $ret;
+ }
+
+ return Html::rawElement( 'a', $attribs, $html );
}
/**
@@ -1037,6 +1044,8 @@ class Linker {
* a message key from the link text.
* Usage example: Linker::specialLink( 'Recentchanges' )
*
+ * @param string $name
+ * @param string $key
* @return string
*/
public static function specialLink( $name, $key = '' ) {
@@ -1050,14 +1059,16 @@ class Linker {
/**
* Make an external link
* @param string $url URL to link to
- * @param string $text text of link
- * @param $escape Boolean: do we escape the link text?
- * @param string $linktype type of external link. Gets added to the classes
- * @param array $attribs of extra attributes to <a>
- * @param $title Title|null Title object used for title specific link attributes
+ * @param string $text Text of link
+ * @param bool $escape Do we escape the link text?
+ * @param string $linktype Type of external link. Gets added to the classes
+ * @param array $attribs Array of extra attributes to <a>
+ * @param Title|null $title Title object used for title specific link attributes
* @return string
*/
- public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array(), $title = null ) {
+ public static function makeExternalLink( $url, $text, $escape = true,
+ $linktype = '', $attribs = array(), $title = null
+ ) {
global $wgTitle;
$class = "external";
if ( $linktype ) {
@@ -1080,7 +1091,8 @@ class Linker {
$success = wfRunHooks( 'LinkerMakeExternalLink',
array( &$url, &$text, &$link, &$attribs, $linktype ) );
if ( !$success ) {
- wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true );
+ wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
+ . "with url {$url} and text {$text} to {$link}\n", true );
return $link;
}
$attribs['href'] = $url;
@@ -1089,18 +1101,20 @@ class Linker {
/**
* Make user link (or user contributions for unregistered users)
- * @param $userId Integer: user id in database.
- * @param string $userName user name in database.
- * @param string $altUserName text to display instead of the user name (optional)
- * @return String: HTML fragment
+ * @param int $userId User id in database.
+ * @param string $userName User name in database.
+ * @param string $altUserName Text to display instead of the user name (optional)
+ * @return string HTML fragment
* @since 1.19 Method exists for a long time. $altUserName was added in 1.19.
*/
public static function userLink( $userId, $userName, $altUserName = false ) {
+ $classes = 'mw-userlink';
if ( $userId == 0 ) {
$page = SpecialPage::getTitleFor( 'Contributions', $userName );
if ( $altUserName === false ) {
$altUserName = IP::prettifyIP( $userName );
}
+ $classes .= ' mw-anonuserlink'; // Separate link class for anons (bug 43179)
} else {
$page = Title::makeTitle( NS_USER, $userName );
}
@@ -1108,20 +1122,21 @@ class Linker {
return self::link(
$page,
htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
- array( 'class' => 'mw-userlink' )
+ array( 'class' => $classes )
);
}
/**
* Generate standard user tool links (talk, contributions, block link, etc.)
*
- * @param $userId Integer: user identifier
- * @param string $userText user name or IP address
- * @param $redContribsWhenNoEdits Boolean: should the contributions link be
- * red if the user has no edits?
- * @param $flags Integer: customisation flags (e.g. Linker::TOOL_LINKS_NOBLOCK and Linker::TOOL_LINKS_EMAIL)
- * @param $edits Integer: user edit count (optional, for performance)
- * @return String: HTML fragment
+ * @param int $userId User identifier
+ * @param string $userText User name or IP address
+ * @param bool $redContribsWhenNoEdits Should the contributions link be
+ * red if the user has no edits?
+ * @param int $flags Customisation flags (e.g. Linker::TOOL_LINKS_NOBLOCK
+ * and Linker::TOOL_LINKS_EMAIL).
+ * @param int $edits User edit count (optional, for performance)
+ * @return string HTML fragment
*/
public static function userToolLinks(
$userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
@@ -1173,19 +1188,19 @@ class Linker {
/**
* Alias for userToolLinks( $userId, $userText, true );
- * @param $userId Integer: user identifier
- * @param string $userText user name or IP address
- * @param $edits Integer: user edit count (optional, for performance)
- * @return String
+ * @param int $userId User identifier
+ * @param string $userText User name or IP address
+ * @param int $edits User edit count (optional, for performance)
+ * @return string
*/
public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
return self::userToolLinks( $userId, $userText, true, 0, $edits );
}
/**
- * @param $userId Integer: user id in database.
- * @param string $userText user name in database.
- * @return String: HTML fragment with user talk link
+ * @param int $userId User id in database.
+ * @param string $userText User name in database.
+ * @return string HTML fragment with user talk link
*/
public static function userTalkLink( $userId, $userText ) {
$userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
@@ -1194,9 +1209,9 @@ class Linker {
}
/**
- * @param $userId Integer: userid
- * @param string $userText user name in database.
- * @return String: HTML fragment with block link
+ * @param int $userId Userid
+ * @param string $userText User name in database.
+ * @return string HTML fragment with block link
*/
public static function blockLink( $userId, $userText ) {
$blockPage = SpecialPage::getTitleFor( 'Block', $userText );
@@ -1205,9 +1220,9 @@ class Linker {
}
/**
- * @param $userId Integer: userid
- * @param string $userText user name in database.
- * @return String: HTML fragment with e-mail user link
+ * @param int $userId Userid
+ * @param string $userText User name in database.
+ * @return string HTML fragment with e-mail user link
*/
public static function emailLink( $userId, $userText ) {
$emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
@@ -1217,9 +1232,9 @@ class Linker {
/**
* Generate a user link if the current user is allowed to view it
- * @param $rev Revision object.
- * @param $isPublic Boolean: show only if all users can see it
- * @return String: HTML fragment
+ * @param Revision $rev
+ * @param bool $isPublic Show only if all users can see it
+ * @return string HTML fragment
*/
public static function revUserLink( $rev, $isPublic = false ) {
if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
@@ -1238,8 +1253,8 @@ class Linker {
/**
* Generate a user tool link cluster if the current user is allowed to view it
- * @param $rev Revision object.
- * @param $isPublic Boolean: show only if all users can see it
+ * @param Revision $rev
+ * @param bool $isPublic Show only if all users can see it
* @return string HTML
*/
public static function revUserTools( $rev, $isPublic = false ) {
@@ -1272,10 +1287,10 @@ class Linker {
* Since you can't set a default parameter for a reference, I've turned it
* temporarily to a value pass. Should be adjusted further. --brion
*
- * @param $comment String
- * @param $title Mixed: Title object (to generate link to the section in autocomment) or null
- * @param $local Boolean: whether section links should refer to local page
- * @return mixed|String
+ * @param string $comment
+ * @param Title|null $title Title object (to generate link to the section in autocomment) or null
+ * @param bool $local Whether section links should refer to local page
+ * @return mixed|string
*/
public static function formatComment( $comment, $title = null, $local = false ) {
wfProfileIn( __METHOD__ );
@@ -1294,12 +1309,6 @@ class Linker {
}
/**
- * @var Title
- */
- static $autocommentTitle;
- static $autocommentLocal;
-
- /**
* Converts autogenerated comments in edit summaries into section links.
* The pattern for autogen comments is / * foo * /, which makes for
* some nasty regex.
@@ -1307,99 +1316,77 @@ class Linker {
* add a separator where needed and format the comment itself with CSS
* Called by Linker::formatComment.
*
- * @param string $comment comment text
- * @param $title Title|null An optional title object used to links to sections
- * @param $local Boolean: whether section links should refer to local page
- * @return String: formatted comment
+ * @param string $comment Comment text
+ * @param Title|null $title An optional title object used to links to sections
+ * @param bool $local Whether section links should refer to local page
+ * @return string Formatted comment
*/
private static function formatAutocomments( $comment, $title = null, $local = false ) {
- // Bah!
- self::$autocommentTitle = $title;
- self::$autocommentLocal = $local;
- $comment = preg_replace_callback(
+ return preg_replace_callback(
'!(.*)/\*\s*(.*?)\s*\*/(.*)!',
- array( 'Linker', 'formatAutocommentsCallback' ),
- $comment );
- self::$autocommentTitle = null;
- self::$autocommentLocal = null;
- return $comment;
- }
-
- /**
- * Helper function for Linker::formatAutocomments
- * @param $match
- * @return string
- */
- private static function formatAutocommentsCallback( $match ) {
- global $wgLang;
- $title = self::$autocommentTitle;
- $local = self::$autocommentLocal;
-
- $pre = $match[1];
- $auto = $match[2];
- $post = $match[3];
- $comment = null;
- wfRunHooks( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) );
- if ( $comment === null ) {
- $link = '';
- if ( $title ) {
- $section = $auto;
-
- # Remove links that a user may have manually put in the autosummary
- # This could be improved by copying as much of Parser::stripSectionName as desired.
- $section = str_replace( '[[:', '', $section );
- $section = str_replace( '[[', '', $section );
- $section = str_replace( ']]', '', $section );
-
- $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
- if ( $local ) {
- $sectionTitle = Title::newFromText( '#' . $section );
- } else {
- $sectionTitle = Title::makeTitleSafe( $title->getNamespace(),
- $title->getDBkey(), $section );
- }
- if ( $sectionTitle ) {
- $link = self::link( $sectionTitle,
- $wgLang->getArrow(), array(), array(),
- 'noclasses' );
- } else {
+ function ( $match ) use ( $title, $local ) {
+ global $wgLang;
+
+ $pre = $match[1];
+ $auto = $match[2];
+ $post = $match[3];
+ $comment = null;
+ wfRunHooks( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) );
+ if ( $comment === null ) {
$link = '';
+ if ( $title ) {
+ $section = $auto;
+ # Remove links that a user may have manually put in the autosummary
+ # This could be improved by copying as much of Parser::stripSectionName as desired.
+ $section = str_replace( '[[:', '', $section );
+ $section = str_replace( '[[', '', $section );
+ $section = str_replace( ']]', '', $section );
+
+ $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
+ if ( $local ) {
+ $sectionTitle = Title::newFromText( '#' . $section );
+ } else {
+ $sectionTitle = Title::makeTitleSafe( $title->getNamespace(),
+ $title->getDBkey(), $section );
+ }
+ if ( $sectionTitle ) {
+ $link = Linker::link( $sectionTitle,
+ $wgLang->getArrow(), array(), array(),
+ 'noclasses' );
+ } else {
+ $link = '';
+ }
+ }
+ if ( $pre ) {
+ # written summary $presep autocomment (summary /* section */)
+ $pre .= wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
+ }
+ if ( $post ) {
+ # autocomment $postsep written summary (/* section */ summary)
+ $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
+ }
+ $auto = '<span class="autocomment">' . $auto . '</span>';
+ $comment = $pre . $link . $wgLang->getDirMark()
+ . '<span dir="auto">' . $auto . $post . '</span>';
}
- }
- if ( $pre ) {
- # written summary $presep autocomment (summary /* section */)
- $pre .= wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
- }
- if ( $post ) {
- # autocomment $postsep written summary (/* section */ summary)
- $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
- }
- $auto = '<span class="autocomment">' . $auto . '</span>';
- $comment = $pre . $link . $wgLang->getDirMark() . '<span dir="auto">' . $auto . $post . '</span>';
- }
- return $comment;
+ return $comment;
+ },
+ $comment
+ );
}
/**
- * @var Title
- */
- static $commentContextTitle;
- static $commentLocal;
-
- /**
* Formats wiki links and media links in text; all other wiki formatting
* is ignored
*
* @todo FIXME: Doesn't handle sub-links as in image thumb texts like the main parser
- * @param string $comment text to format links in
- * @param $title Title|null An optional title object used to links to sections
- * @param $local Boolean: whether section links should refer to local page
- * @return String
+ * @param string $comment Text to format links in
+ * @param Title|null $title An optional title object used to links to sections
+ * @param bool $local Whether section links should refer to local page
+ * @return string
*/
public static function formatLinksInComment( $comment, $title = null, $local = false ) {
- self::$commentContextTitle = $title;
- self::$commentLocal = $local;
- $html = preg_replace_callback(
+ return preg_replace_callback(
'/
\[\[
:? # ignore optional leading colon
@@ -1412,89 +1399,89 @@ class Linker {
\]\]
([^[]*) # 3. link trail (the text up until the next link)
/x',
- array( 'Linker', 'formatLinksInCommentCallback' ),
- $comment );
- self::$commentContextTitle = null;
- self::$commentLocal = null;
- return $html;
- }
-
- /**
- * @param $match
- * @return mixed
- */
- protected static function formatLinksInCommentCallback( $match ) {
- global $wgContLang;
+ function ( $match ) use ( $title, $local ) {
+ global $wgContLang;
- $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
- $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
+ $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
+ $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
- $comment = $match[0];
+ $comment = $match[0];
- # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
- if ( strpos( $match[1], '%' ) !== false ) {
- $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $match[1] ) );
- }
+ # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
+ if ( strpos( $match[1], '%' ) !== false ) {
+ $match[1] = str_replace(
+ array( '<', '>' ),
+ array( '&lt;', '&gt;' ),
+ rawurldecode( $match[1] )
+ );
+ }
- # Handle link renaming [[foo|text]] will show link as "text"
- if ( $match[2] != "" ) {
- $text = $match[2];
- } else {
- $text = $match[1];
- }
- $submatch = array();
- $thelink = null;
- if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
- # Media link; trail not supported.
- $linkRegexp = '/\[\[(.*?)\]\]/';
- $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
- if ( $title ) {
- $thelink = self::makeMediaLinkObj( $title, $text );
- }
- } else {
- # Other kind of link
- if ( preg_match( $wgContLang->linkTrail(), $match[3], $submatch ) ) {
- $trail = $submatch[1];
- } else {
- $trail = "";
- }
- $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
- if ( isset( $match[1][0] ) && $match[1][0] == ':' ) {
- $match[1] = substr( $match[1], 1 );
- }
- list( $inside, $trail ) = self::splitTrail( $trail );
-
- $linkText = $text;
- $linkTarget = self::normalizeSubpageLink( self::$commentContextTitle,
- $match[1], $linkText );
-
- $target = Title::newFromText( $linkTarget );
- if ( $target ) {
- if ( $target->getText() == '' && $target->getInterwiki() === ''
- && !self::$commentLocal && self::$commentContextTitle )
- {
- $newTarget = clone ( self::$commentContextTitle );
- $newTarget->setFragment( '#' . $target->getFragment() );
- $target = $newTarget;
+ # Handle link renaming [[foo|text]] will show link as "text"
+ if ( $match[2] != "" ) {
+ $text = $match[2];
+ } else {
+ $text = $match[1];
+ }
+ $submatch = array();
+ $thelink = null;
+ if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
+ # Media link; trail not supported.
+ $linkRegexp = '/\[\[(.*?)\]\]/';
+ $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
+ if ( $title ) {
+ $thelink = Linker::makeMediaLinkObj( $title, $text );
+ }
+ } else {
+ # Other kind of link
+ if ( preg_match( $wgContLang->linkTrail(), $match[3], $submatch ) ) {
+ $trail = $submatch[1];
+ } else {
+ $trail = "";
+ }
+ $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
+ if ( isset( $match[1][0] ) && $match[1][0] == ':' ) {
+ $match[1] = substr( $match[1], 1 );
+ }
+ list( $inside, $trail ) = Linker::splitTrail( $trail );
+
+ $linkText = $text;
+ $linkTarget = Linker::normalizeSubpageLink( $title, $match[1], $linkText );
+
+ $target = Title::newFromText( $linkTarget );
+ if ( $target ) {
+ if ( $target->getText() == '' && !$target->isExternal()
+ && !$local && $title
+ ) {
+ $newTarget = clone ( $title );
+ $newTarget->setFragment( '#' . $target->getFragment() );
+ $target = $newTarget;
+ }
+ $thelink = Linker::link(
+ $target,
+ $linkText . $inside
+ ) . $trail;
+ }
+ }
+ if ( $thelink ) {
+ // If the link is still valid, go ahead and replace it in!
+ $comment = preg_replace(
+ $linkRegexp,
+ StringUtils::escapeRegexReplacement( $thelink ),
+ $comment,
+ 1
+ );
}
- $thelink = self::link(
- $target,
- $linkText . $inside
- ) . $trail;
- }
- }
- if ( $thelink ) {
- // If the link is still valid, go ahead and replace it in!
- $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
- }
- return $comment;
+ return $comment;
+ },
+ $comment
+ );
}
/**
- * @param $contextTitle Title
- * @param $target
- * @param $text
+ * @param Title $contextTitle
+ * @param string $target
+ * @param string $text
* @return string
*/
public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
@@ -1573,9 +1560,9 @@ class Linker {
* Wrap a comment in standard punctuation and formatting if
* it's non-empty, otherwise return empty string.
*
- * @param $comment String
- * @param $title Mixed: Title object (to generate link to section in autocomment) or null
- * @param $local Boolean: whether section links should refer to local page
+ * @param string $comment
+ * @param Title|null $title Title object (to generate link to section in autocomment) or null
+ * @param bool $local Whether section links should refer to local page
*
* @return string
*/
@@ -1596,10 +1583,10 @@ class Linker {
* Wrap and format the given revision's comment block, if the current
* user is allowed to view it.
*
- * @param $rev Revision object
- * @param $local Boolean: whether section links should refer to local page
- * @param $isPublic Boolean: show only if all users can see it
- * @return String: HTML fragment
+ * @param Revision $rev
+ * @param bool $local Whether section links should refer to local page
+ * @param bool $isPublic Show only if all users can see it
+ * @return string HTML fragment
*/
public static function revComment( Revision $rev, $local = false, $isPublic = false ) {
if ( $rev->getRawComment() == "" ) {
@@ -1620,7 +1607,7 @@ class Linker {
}
/**
- * @param $size
+ * @param int $size
* @return string
*/
public static function formatRevisionSize( $size ) {
@@ -1645,6 +1632,7 @@ class Linker {
/**
* Finish one or more sublevels on the Table of Contents
*
+ * @param int $level
* @return string
*/
public static function tocUnindent( $level ) {
@@ -1654,6 +1642,11 @@ class Linker {
/**
* parameter level defines if we are on an indentation level
*
+ * @param string $anchor
+ * @param string $tocline
+ * @param string $tocnumber
+ * @param string $level
+ * @param string|bool $sectionIndex
* @return string
*/
public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
@@ -1680,9 +1673,9 @@ class Linker {
/**
* Wraps the TOC in a table and provides the hide/collapse javascript.
*
- * @param string $toc html of the Table Of Contents
- * @param $lang String|Language|false: Language for the toc title, defaults to user language
- * @return String: full html of the TOC
+ * @param string $toc Html of the Table Of Contents
+ * @param string|Language|bool $lang Language for the toc title, defaults to user language
+ * @return string Full html of the TOC
*/
public static function tocList( $toc, $lang = false ) {
$lang = wfGetLangObj( $lang );
@@ -1699,7 +1692,7 @@ class Linker {
* Currently unused.
*
* @param array $tree Return value of ParserOutput::getSections()
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
public static function generateTOC( $tree ) {
$toc = '';
@@ -1726,19 +1719,21 @@ class Linker {
/**
* Create a headline for content
*
- * @param $level Integer: the level of the headline (1-6)
- * @param string $attribs any attributes for the headline, starting with
- * a space and ending with '>'
- * This *must* be at least '>' for no attribs
- * @param string $anchor the anchor to give the headline (the bit after the #)
- * @param string $html html for the text of the header
+ * @param int $level The level of the headline (1-6)
+ * @param string $attribs Any attributes for the headline, starting with
+ * a space and ending with '>'
+ * This *must* be at least '>' for no attribs
+ * @param string $anchor The anchor to give the headline (the bit after the #)
+ * @param string $html Html for the text of the header
* @param string $link HTML to add for the section edit link
- * @param $legacyAnchor Mixed: a second, optional anchor to give for
+ * @param bool|string $legacyAnchor A second, optional anchor to give for
* backward compatibility (false to omit)
*
- * @return String: HTML headline
+ * @return string HTML headline
*/
- public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) {
+ public static function makeHeadline( $level, $attribs, $anchor, $html,
+ $link, $legacyAnchor = false
+ ) {
$ret = "<h$level$attribs"
. "<span class=\"mw-headline\" id=\"$anchor\">$html</span>"
. $link
@@ -1752,6 +1747,7 @@ class Linker {
/**
* Split a link trail, return the "inside" portion and the remainder of the trail
* as a two-element array
+ * @param string $trail
* @return array
*/
static function splitTrail( $trail ) {
@@ -1788,15 +1784,18 @@ class Linker {
*
* If the option noBrackets is set the rollback link wont be enclosed in []
*
- * @param $rev Revision object
- * @param $context IContextSource context to use or null for the main context.
- * @param $options array
+ * @param Revision $rev
+ * @param IContextSource $context Context to use or null for the main context.
+ * @param array $options
* @return string
*/
- public static function generateRollback( $rev, IContextSource $context = null, $options = array( 'verify' ) ) {
+ public static function generateRollback( $rev, IContextSource $context = null,
+ $options = array( 'verify' )
+ ) {
if ( $context === null ) {
$context = RequestContext::getMain();
}
+
$editCount = false;
if ( in_array( 'verify', $options ) ) {
$editCount = self::getRollbackEditCount( $rev, true );
@@ -1825,9 +1824,9 @@ class Linker {
* Returns null if $wgShowRollbackEditCount is disabled or false if $verify
* is set and the user is the only contributor of the page.
*
- * @param $rev Revision object
+ * @param Revision $rev
* @param bool $verify Try to verify that this revision can really be rolled back
- * @return integer|bool|null
+ * @return int|bool|null
*/
public static function getRollbackEditCount( $rev, $verify ) {
global $wgShowRollbackEditCount;
@@ -1856,9 +1855,13 @@ class Linker {
$moreRevs = false;
foreach ( $res as $row ) {
if ( $rev->getRawUserText() != $row->rev_user_text ) {
- if ( $verify && ( $row->rev_deleted & Revision::DELETED_TEXT || $row->rev_deleted & Revision::DELETED_USER ) ) {
- // If the user or the text of the revision we might rollback to is deleted in some way we can't rollback
- // Similar to the sanity checks in WikiPage::commitRollback
+ if ( $verify &&
+ ( $row->rev_deleted & Revision::DELETED_TEXT
+ || $row->rev_deleted & Revision::DELETED_USER
+ ) ) {
+ // If the user or the text of the revision we might rollback
+ // to is deleted in some way we can't rollback. Similar to
+ // the sanity checks in WikiPage::commitRollback.
return false;
}
$moreRevs = true;
@@ -1879,12 +1882,14 @@ class Linker {
/**
* Build a raw rollback link, useful for collections of "tool" links
*
- * @param $rev Revision object
- * @param $context IContextSource context to use or null for the main context.
- * @param $editCount integer Number of edits that would be reverted
- * @return String: HTML fragment
+ * @param Revision $rev
+ * @param IContextSource|null $context Context to use or null for the main context.
+ * @param int $editCount Number of edits that would be reverted
+ * @return string HTML fragment
*/
- public static function buildRollbackLink( $rev, IContextSource $context = null, $editCount = false ) {
+ public static function buildRollbackLink( $rev, IContextSource $context = null,
+ $editCount = false
+ ) {
global $wgShowRollbackEditCount, $wgMiserMode;
// To config which pages are effected by miser mode
@@ -1898,7 +1903,10 @@ class Linker {
$query = array(
'action' => 'rollback',
'from' => $rev->getUserText(),
- 'token' => $context->getUser()->getEditToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
+ 'token' => $context->getUser()->getEditToken( array(
+ $title->getPrefixedText(),
+ $rev->getUserText()
+ ) ),
);
if ( $context->getRequest()->getBool( 'bot' ) ) {
$query['bot'] = '1';
@@ -1915,13 +1923,17 @@ class Linker {
}
}
- if ( !$disableRollbackEditCount && is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
+ if ( !$disableRollbackEditCount
+ && is_int( $wgShowRollbackEditCount )
+ && $wgShowRollbackEditCount > 0
+ ) {
if ( !is_numeric( $editCount ) ) {
$editCount = self::getRollbackEditCount( $rev, false );
}
if ( $editCount > $wgShowRollbackEditCount ) {
- $editCount_output = $context->msg( 'rollbacklinkcount-morethan' )->numParams( $wgShowRollbackEditCount )->parse();
+ $editCount_output = $context->msg( 'rollbacklinkcount-morethan' )
+ ->numParams( $wgShowRollbackEditCount )->parse();
} else {
$editCount_output = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
}
@@ -1957,9 +1969,11 @@ class Linker {
* @param bool $preview Whether this is for a preview
* @param bool $section Whether this is for a section edit
* @param Title|Message|string|null $more An escaped link for "More..." of the templates
- * @return String: HTML output
+ * @return string HTML output
*/
- public static function formatTemplates( $templates, $preview = false, $section = false, $more = null ) {
+ public static function formatTemplates( $templates, $preview = false,
+ $section = false, $more = null
+ ) {
global $wgLang;
wfProfileIn( __METHOD__ );
@@ -2048,9 +2062,9 @@ class Linker {
/**
* Returns HTML for the "hidden categories on this page" list.
*
- * @param array $hiddencats of hidden categories from Article::getHiddenCategories
- * or similar
- * @return String: HTML output
+ * @param array $hiddencats Array of hidden categories from Article::getHiddenCategories
+ * or similar
+ * @return string HTML output
*/
public static function formatHiddenCategories( $hiddencats ) {
wfProfileIn( __METHOD__ );
@@ -2063,7 +2077,10 @@ class Linker {
$outText .= "</div><ul>\n";
foreach ( $hiddencats as $titleObj ) {
- $outText .= '<li>' . self::link( $titleObj, null, array(), array(), 'known' ) . "</li>\n"; # If it's hidden, it must exist - no need to check with a LinkBatch
+ # If it's hidden, it must exist - no need to check with a LinkBatch
+ $outText .= '<li>'
+ . self::link( $titleObj, null, array(), array(), 'known' )
+ . "</li>\n";
}
$outText .= '</ul>';
}
@@ -2076,7 +2093,7 @@ class Linker {
* unit (B, KB, MB or GB) according to the magnitude in question
*
* @param int $size Size to format
- * @return String
+ * @return string
*/
public static function formatSize( $size ) {
global $wgLang;
@@ -2089,10 +2106,10 @@ class Linker {
* isn't always, because sometimes the accesskey needs to go on a different
* element than the id, for reverse-compatibility, etc.)
*
- * @param string $name id of the element, minus prefixes.
- * @param $options Mixed: null or the string 'withaccess' to add an access-
+ * @param string $name Id of the element, minus prefixes.
+ * @param string|null $options Null or the string 'withaccess' to add an access-
* key hint
- * @return String: contents of the title attribute (which you must HTML-
+ * @return string Contents of the title attribute (which you must HTML-
* escape), or false for no title attribute
*/
public static function titleAttrib( $name, $options = null ) {
@@ -2115,10 +2132,12 @@ class Linker {
if ( $options == 'withaccess' ) {
$accesskey = self::accesskey( $name );
if ( $accesskey !== false ) {
+ // Should be build the same as in jquery.accessKeyLabel.js
if ( $tooltip === false || $tooltip === '' ) {
- $tooltip = "[$accesskey]";
+ $tooltip = wfMessage( 'brackets', $accesskey )->text();
} else {
- $tooltip .= " [$accesskey]";
+ $tooltip .= wfMessage( 'word-separator' )->text();
+ $tooltip .= wfMessage( 'brackets', $accesskey )->text();
}
}
}
@@ -2127,7 +2146,7 @@ class Linker {
return $tooltip;
}
- static $accesskeycache;
+ public static $accesskeycache;
/**
* Given the id of an interface element, constructs the appropriate
@@ -2135,8 +2154,8 @@ class Linker {
* the id but isn't always, because sometimes the accesskey needs to go on
* a different element than the id, for reverse-compatibility, etc.)
*
- * @param string $name id of the element, minus prefixes.
- * @return String: contents of the accesskey attribute (which you must HTML-
+ * @param string $name Id of the element, minus prefixes.
+ * @return string Contents of the accesskey attribute (which you must HTML-
* escape), or false for no accesskey attribute
*/
public static function accesskey( $name ) {
@@ -2160,7 +2179,8 @@ class Linker {
}
wfProfileOut( __METHOD__ );
- return self::$accesskeycache[$name] = $accesskey;
+ self::$accesskeycache[$name] = $accesskey;
+ return self::$accesskeycache[$name];
}
/**
@@ -2173,7 +2193,7 @@ class Linker {
*
* @param User $user
* @param Revision $rev
- * @param Revision $title
+ * @param Title $title
* @return string HTML fragment
*/
public static function getRevDeleteLink( User $user, Revision $rev, Title $title ) {
@@ -2210,11 +2230,11 @@ class Linker {
/**
* Creates a (show/hide) link for deleting revisions/log entries
*
- * @param array $query query parameters to be passed to link()
- * @param $restricted Boolean: set to true to use a "<strong>" instead of a "<span>"
- * @param $delete Boolean: set to true to use (show/hide) rather than (show)
+ * @param array $query Query parameters to be passed to link()
+ * @param bool $restricted Set to true to use a "<strong>" instead of a "<span>"
+ * @param bool $delete Set to true to use (show/hide) rather than (show)
*
- * @return String: HTML "<a>" link to Special:Revisiondelete, wrapped in a
+ * @return string HTML "<a>" link to Special:Revisiondelete, wrapped in a
* span to allow for customization of appearance with CSS
*/
public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
@@ -2223,13 +2243,17 @@ class Linker {
$html = wfMessage( $msgKey )->escaped();
$tag = $restricted ? 'strong' : 'span';
$link = self::link( $sp, $html, array(), $query, array( 'known', 'noclasses' ) );
- return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), wfMessage( 'parentheses' )->rawParams( $link )->escaped() );
+ return Xml::tags(
+ $tag,
+ array( 'class' => 'mw-revdelundel-link' ),
+ wfMessage( 'parentheses' )->rawParams( $link )->escaped()
+ );
}
/**
* Creates a dead (show/hide) link for deleting revisions/log entries
*
- * @param $delete Boolean: set to true to use (show/hide) rather than (show)
+ * @param bool $delete Set to true to use (show/hide) rather than (show)
*
* @return string HTML text wrapped in a span to allow for customization
* of appearance with CSS
@@ -2244,46 +2268,19 @@ class Linker {
/* Deprecated methods */
/**
- * @deprecated since 1.16 Use link()
- *
- * This function is a shortcut to makeBrokenLinkObj(Title::newFromText($title),...). Do not call
- * it if you already have a title object handy. See makeBrokenLinkObj for further documentation.
- *
- * @param string $title The text of the title
- * @param string $text Link text
- * @param string $query Optional query part
- * @param string $trail Optional trail. Alphabetic characters at the start of this string will
- * be included in the link text. Other characters will be appended after
- * the end of the link.
- * @return string
- */
- static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
- wfDeprecated( __METHOD__, '1.16' );
-
- $nt = Title::newFromText( $title );
- if ( $nt instanceof Title ) {
- return self::makeBrokenLinkObj( $nt, $text, $query, $trail );
- } else {
- wfDebug( 'Invalid title passed to self::makeBrokenLink(): "' . $title . "\"\n" );
- return $text == '' ? $title : $text;
- }
- }
-
- /**
* @deprecated since 1.16 Use link(); warnings since 1.21
*
* Make a link for a title which may or may not be in the database. If you need to
* call this lots of times, pre-fill the link cache with a LinkBatch, otherwise each
* call to this will result in a DB query.
*
- * @param $nt Title: the title object to make the link from, e.g. from
- * Title::newFromText.
- * @param $text String: link text
- * @param string $query optional query part
- * @param string $trail optional trail. Alphabetic characters at the start of this string will
- * be included in the link text. Other characters will be appended after
- * the end of the link.
- * @param string $prefix optional prefix. As trail, only before instead of after.
+ * @param Title $nt The title object to make the link from, e.g. from Title::newFromText.
+ * @param string $text Link text
+ * @param string $query Optional query part
+ * @param string $trail Optional trail. Alphabetic characters at the start of this string will
+ * be included in the link text. Other characters will be appended after
+ * the end of the link.
+ * @param string $prefix Optional prefix. As trail, only before instead of after.
* @return string
*/
static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
@@ -2309,14 +2306,14 @@ class Linker {
* it doesn't have to do a database query. It's also valid for interwiki titles and special
* pages.
*
- * @param $title Title object of target page
- * @param $text String: text to replace the title
- * @param $query String: link target
- * @param $trail String: text after link
- * @param string $prefix text before link text
- * @param string $aprops extra attributes to the a-element
- * @param $style String: style to apply - if empty, use getInternalLinkAttributesObj instead
- * @return string the a-element
+ * @param Title $title Title object of target page
+ * @param string $text Text to replace the title
+ * @param string $query Link target
+ * @param string $trail Text after link
+ * @param string $prefix Text before link text
+ * @param string $aprops Extra attributes to the a-element
+ * @param string $style Style to apply - if empty, use getInternalLinkAttributesObj instead
+ * @return string The a-element
*/
static function makeKnownLinkObj(
$title, $text = '', $query = '', $trail = '', $prefix = '', $aprops = '', $style = ''
@@ -2343,64 +2340,8 @@ class Linker {
}
/**
- * @deprecated since 1.16 Use link()
- *
- * Make a red link to the edit page of a given title.
- *
- * @param $title Title object of the target page
- * @param $text String: Link text
- * @param string $query Optional query part
- * @param string $trail Optional trail. Alphabetic characters at the start of this string will
- * be included in the link text. Other characters will be appended after
- * the end of the link.
- * @param string $prefix Optional prefix
- * @return string
- */
- static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
- wfDeprecated( __METHOD__, '1.16' );
-
- wfProfileIn( __METHOD__ );
-
- list( $inside, $trail ) = self::splitTrail( $trail );
- if ( $text === '' ) {
- $text = self::linkText( $title );
- }
-
- $ret = self::link( $title, "$prefix$text$inside", array(),
- wfCgiToArray( $query ), 'broken' ) . $trail;
-
- wfProfileOut( __METHOD__ );
- return $ret;
- }
-
- /**
- * @deprecated since 1.16 Use link()
- *
- * Make a coloured link.
- *
- * @param $nt Title object of the target page
- * @param $colour Integer: colour of the link
- * @param $text String: link text
- * @param $query String: optional query part
- * @param $trail String: optional trail. Alphabetic characters at the start of this string will
- * be included in the link text. Other characters will be appended after
- * the end of the link.
- * @param string $prefix Optional prefix
- * @return string
- */
- static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
- wfDeprecated( __METHOD__, '1.16' );
-
- if ( $colour != '' ) {
- $style = self::getInternalLinkAttributesObj( $nt, $text, $colour );
- } else {
- $style = '';
- }
- return self::makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
- }
-
- /**
* Returns the attributes for the tooltip and access key.
+ * @param string $name
* @return array
*/
public static function tooltipAndAccesskeyAttribs( $name ) {
@@ -2422,6 +2363,8 @@ class Linker {
/**
* Returns raw bits of HTML, use titleAttrib()
+ * @param string $name
+ * @param array|null $options
* @return null|string
*/
public static function tooltip( $name, $options = null ) {
diff --git a/includes/Namespace.php b/includes/MWNamespace.php
index 5c8e63b7..392f5582 100644
--- a/includes/Namespace.php
+++ b/includes/MWNamespace.php
@@ -45,8 +45,8 @@ class MWNamespace {
* Special namespaces are defined in includes/Defines.php and have
* a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2)
*
- * @param $index
- * @param $method
+ * @param int $index
+ * @param string $method
*
* @throws MWException
* @return bool
@@ -61,13 +61,13 @@ class MWNamespace {
/**
* Can pages in the given namespace be moved?
*
- * @param int $index namespace index
+ * @param int $index Namespace index
* @return bool
*/
public static function isMovable( $index ) {
global $wgAllowImageMoving;
- $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) || $index == NS_CATEGORY );
+ $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) );
/**
* @since 1.20
@@ -80,7 +80,7 @@ class MWNamespace {
/**
* Is the given namespace is a subject (non-talk) namespace?
*
- * @param int $index namespace index
+ * @param int $index Namespace index
* @return bool
* @since 1.19
*/
@@ -89,19 +89,9 @@ class MWNamespace {
}
/**
- * @see self::isSubject
- * @deprecated Please use the more consistently named isSubject (since 1.19)
- * @return bool
- */
- public static function isMain( $index ) {
- wfDeprecated( __METHOD__, '1.19' );
- return self::isSubject( $index );
- }
-
- /**
* Is the given namespace a talk namespace?
*
- * @param int $index namespace index
+ * @param int $index Namespace index
* @return bool
*/
public static function isTalk( $index ) {
@@ -112,7 +102,7 @@ class MWNamespace {
/**
* Get the talk namespace index for a given namespace
*
- * @param int $index namespace index
+ * @param int $index Namespace index
* @return int
*/
public static function getTalk( $index ) {
@@ -145,8 +135,8 @@ class MWNamespace {
* For talk namespaces, returns the subject (non-talk) namespace
* For subject (non-talk) namespaces, returns the talk namespace
*
- * @param int $index namespace index
- * @return int or null if no associated namespace could be found
+ * @param int $index Namespace index
+ * @return int|null If no associated namespace could be found
*/
public static function getAssociated( $index ) {
self::isMethodValidFor( $index, __METHOD__ );
@@ -163,7 +153,7 @@ class MWNamespace {
/**
* Returns whether the specified namespace exists
*
- * @param $index
+ * @param int $index
*
* @return bool
* @since 1.19
@@ -210,7 +200,7 @@ class MWNamespace {
* Returns array of all defined namespaces with their canonical
* (English) names.
*
- * @param bool $rebuild rebuild namespace list (default = false). Used for testing.
+ * @param bool $rebuild Rebuild namespace list (default = false). Used for testing.
*
* @return array
* @since 1.17
@@ -231,8 +221,8 @@ class MWNamespace {
/**
* Returns the canonical (English) name for a given index
*
- * @param int $index namespace index
- * @return string or false if no canonical definition.
+ * @param int $index Namespace index
+ * @return string|bool If no canonical definition.
*/
public static function getCanonicalName( $index ) {
$nslist = self::getCanonicalNamespaces();
@@ -247,7 +237,7 @@ class MWNamespace {
* Returns the index for a given canonical name, or NULL
* The input *must* be converted to lower case first
*
- * @param string $name namespace name
+ * @param string $name Namespace name
* @return int
*/
public static function getCanonicalIndex( $name ) {
@@ -287,7 +277,7 @@ class MWNamespace {
/**
* Can this namespace ever have a talk namespace?
*
- * @param int $index namespace index
+ * @param int $index Namespace index
* @return bool
*/
public static function canTalk( $index ) {
@@ -298,7 +288,7 @@ class MWNamespace {
* Does this namespace contain content, for the purposes of calculating
* statistics, etc?
*
- * @param int $index index to check
+ * @param int $index Index to check
* @return bool
*/
public static function isContent( $index ) {
@@ -309,7 +299,7 @@ class MWNamespace {
/**
* Can pages in a namespace be watched?
*
- * @param $index Int
+ * @param int $index
* @return bool
*/
public static function isWatchable( $index ) {
@@ -329,7 +319,7 @@ class MWNamespace {
/**
* Get a list of all namespace indices which are considered to contain content
- * @return array of namespace indices
+ * @return array Array of namespace indices
*/
public static function getContentNamespaces() {
global $wgContentNamespaces;
@@ -347,7 +337,7 @@ class MWNamespace {
* List all namespace indices which are considered subject, aka not a talk
* or special namespace. See also MWNamespace::isSubject
*
- * @return array of namespace indices
+ * @return array Array of namespace indices
*/
public static function getSubjectNamespaces() {
return array_filter(
@@ -360,7 +350,7 @@ class MWNamespace {
* List all namespace indices which are considered talks, aka not a subject
* or special namespace. See also MWNamespace::isTalk
*
- * @return array of namespace indices
+ * @return array Array of namespace indices
*/
public static function getTalkNamespaces() {
return array_filter(
@@ -425,7 +415,7 @@ class MWNamespace {
*
* @since 1.21
* @param int $index Index to check
- * @return null|string default model name for the given namespace, if set
+ * @return null|string Default model name for the given namespace, if set
*/
public static function getNamespaceContentModel( $index ) {
global $wgNamespaceContentModels;
@@ -433,4 +423,74 @@ class MWNamespace {
? $wgNamespaceContentModels[$index]
: null;
}
+
+ /**
+ * Determine which restriction levels it makes sense to use in a namespace,
+ * optionally filtered by a user's rights.
+ *
+ * @since 1.23
+ * @param int $index Index to check
+ * @param User $user User to check
+ * @return array
+ */
+ public static function getRestrictionLevels( $index, User $user = null ) {
+ global $wgNamespaceProtection, $wgRestrictionLevels;
+
+ if ( !isset( $wgNamespaceProtection[$index] ) ) {
+ // All levels are valid if there's no namespace restriction.
+ // But still filter by user, if necessary
+ $levels = $wgRestrictionLevels;
+ if ( $user ) {
+ $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
+ $right = $level;
+ if ( $right == 'sysop' ) {
+ $right = 'editprotected'; // BC
+ }
+ if ( $right == 'autoconfirmed' ) {
+ $right = 'editsemiprotected'; // BC
+ }
+ return ( $right == '' || $user->isAllowed( $right ) );
+ } ) );
+ }
+ return $levels;
+ }
+
+ // First, get the list of groups that can edit this namespace.
+ $namespaceGroups = array();
+ $combine = 'array_merge';
+ foreach ( (array)$wgNamespaceProtection[$index] as $right ) {
+ if ( $right == 'sysop' ) {
+ $right = 'editprotected'; // BC
+ }
+ if ( $right == 'autoconfirmed' ) {
+ $right = 'editsemiprotected'; // BC
+ }
+ if ( $right != '' ) {
+ $namespaceGroups = call_user_func( $combine, $namespaceGroups,
+ User::getGroupsWithPermission( $right ) );
+ $combine = 'array_intersect';
+ }
+ }
+
+ // Now, keep only those restriction levels where there is at least one
+ // group that can edit the namespace but would be blocked by the
+ // restriction.
+ $usableLevels = array( '' );
+ foreach ( $wgRestrictionLevels as $level ) {
+ $right = $level;
+ if ( $right == 'sysop' ) {
+ $right = 'editprotected'; // BC
+ }
+ if ( $right == 'autoconfirmed' ) {
+ $right = 'editsemiprotected'; // BC
+ }
+ if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) &&
+ array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) )
+ ) {
+ $usableLevels[] = $level;
+ }
+ }
+
+ return $usableLevels;
+ }
}
diff --git a/includes/Timestamp.php b/includes/MWTimestamp.php
index edcd6a88..26f5e543 100644
--- a/includes/Timestamp.php
+++ b/includes/MWTimestamp.php
@@ -77,7 +77,8 @@ class MWTimestamp {
$da = array();
$strtime = '';
- if ( !$ts || $ts === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ) { // We want to catch 0, '', null... but not date strings starting with a letter.
+ // We want to catch 0, '', null... but not date strings starting with a letter.
+ if ( !$ts || $ts === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ) {
$uts = time();
$strtime = "@$uts";
} elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
@@ -93,18 +94,41 @@ class MWTimestamp {
# TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
$strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
str_replace( '+00:00', 'UTC', $ts ) );
- } elseif ( preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
+ } elseif ( preg_match(
+ '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z?$/',
+ $ts,
+ $da
+ ) ) {
# TS_ISO_8601
- } elseif ( preg_match( '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
+ } elseif ( preg_match(
+ '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z?$/',
+ $ts,
+ $da
+ ) ) {
#TS_ISO_8601_BASIC
- } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/', $ts, $da ) ) {
+ } elseif ( preg_match(
+ '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',
+ $ts,
+ $da
+ ) ) {
# TS_POSTGRES
- } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/', $ts, $da ) ) {
+ } elseif ( preg_match(
+ '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',
+ $ts,
+ $da
+ ) ) {
# TS_POSTGRES
- } elseif ( preg_match( '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' . # Day of week
- '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' . # dd Mon yyyy
- '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S', $ts ) ) { # hh:mm:ss
- # TS_RFC2822, accepting a trailing comment. See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
+ } elseif ( preg_match(
+ # Day of week
+ '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' .
+ # dd Mon yyyy
+ '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' .
+ # hh:mm:ss
+ '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S',
+ $ts
+ ) ) {
+ # TS_RFC2822, accepting a trailing comment.
+ # See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
# The regex is a superset of rfc2822 for readability
$strtime = strtok( $ts, ';' );
} elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
@@ -171,12 +195,17 @@ class MWTimestamp {
* @since 1.20
* @since 1.22 Uses Language::getHumanTimestamp to produce the timestamp
*
- * @param MWTimestamp|null $relativeTo The base timestamp to compare to (defaults to now)
- * @param User|null $user User the timestamp is being generated for (or null to use main context's user)
- * @param Language|null $lang Language to use to make the human timestamp (or null to use main context's language)
+ * @param MWTimestamp|null $relativeTo The base timestamp to compare to
+ * (defaults to now).
+ * @param User|null $user User the timestamp is being generated for (or null
+ * to use main context's user).
+ * @param Language|null $lang Language to use to make the human timestamp
+ * (or null to use main context's language).
* @return string Formatted timestamp
*/
- public function getHumanTimestamp( MWTimestamp $relativeTo = null, User $user = null, Language $lang = null ) {
+ public function getHumanTimestamp( MWTimestamp $relativeTo = null,
+ User $user = null, Language $lang = null
+ ) {
if ( $relativeTo === null ) {
$relativeTo = new self();
}
@@ -209,7 +238,6 @@ class MWTimestamp {
* @since 1.22
*
* @param User $user User to take preferences from
- * @param[out] MWTimestamp $ts Timestamp to adjust
* @return DateInterval Offset that was applied to the timestamp
*/
public function offsetForUser( User $user ) {
@@ -239,7 +267,7 @@ class MWTimestamp {
// first value.
if ( $data[0] == 'System' ) {
// First value is System, so use the system offset.
- if ( isset( $wgLocalTZoffset ) ) {
+ if ( $wgLocalTZoffset !== null ) {
$diff = $wgLocalTZoffset;
}
} elseif ( $data[0] == 'Offset' ) {
@@ -298,11 +326,13 @@ class MWTimestamp {
$ts = '';
$diff = $this->diff( $relativeTo );
- if ( wfRunHooks( 'GetRelativeTimestamp', array( &$ts, &$diff, $this, $relativeTo, $user, $lang ) ) ) {
+ if ( wfRunHooks(
+ 'GetRelativeTimestamp',
+ array( &$ts, &$diff, $this, $relativeTo, $user, $lang )
+ ) ) {
$seconds = ( ( ( $diff->days * 24 + $diff->h ) * 60 + $diff->i ) * 60 + $diff->s );
$ts = wfMessage( 'ago', $lang->formatDuration( $seconds, $chosenIntervals ) )
- ->inLanguage( $lang )
- ->text();
+ ->inLanguage( $lang )->text();
}
return $ts;
@@ -322,7 +352,8 @@ class MWTimestamp {
*
* @since 1.22
* @param MWTimestamp $relativeTo Base time to calculate difference from
- * @return DateInterval|bool The DateInterval object representing the difference between the two dates or false on failure
+ * @return DateInterval|bool The DateInterval object representing the
+ * difference between the two dates or false on failure
*/
public function diff( MWTimestamp $relativeTo ) {
return $this->timestamp->diff( $relativeTo->timestamp );
@@ -332,7 +363,7 @@ class MWTimestamp {
* Set the timezone of this timestamp to the specified timezone.
*
* @since 1.22
- * @param String $timezone Timezone to set
+ * @param string $timezone Timezone to set
* @throws TimestampException
*/
public function setTimezone( $timezone ) {
@@ -369,7 +400,7 @@ class MWTimestamp {
*
* @since 1.22
* @param bool|string $ts Timestamp to set, or false for current time
- * @return MWTimestamp the local instance
+ * @return MWTimestamp The local instance
*/
public static function getLocalInstance( $ts = false ) {
global $wgLocaltimezone;
@@ -383,14 +414,9 @@ class MWTimestamp {
*
* @since 1.22
* @param bool|string $ts Timestamp to set, or false for current time
- * @return MWTimestamp the instance
+ * @return MWTimestamp The instance
*/
public static function getInstance( $ts = false ) {
return new self( $ts );
}
}
-
-/**
- * @since 1.20
- */
-class TimestampException extends MWException {}
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index 232f43e8..4d17298b 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -59,20 +59,44 @@
* @ingroup Parser
*/
class MagicWord {
- /**#@+
- * @private
- */
- var $mId, $mSynonyms, $mCaseSensitive;
- var $mRegex = '';
- var $mRegexStart = '';
- var $mBaseRegex = '';
- var $mVariableRegex = '';
- var $mVariableStartToEndRegex = '';
- var $mModified = false;
- var $mFound = false;
+ /**#@-*/
+
+ /** @var int */
+ public $mId;
+
+ /** @var array */
+ public $mSynonyms;
+
+ /** @var bool */
+ public $mCaseSensitive;
+
+ /** @var string */
+ private $mRegex = '';
+
+ /** @var string */
+ private $mRegexStart = '';
+
+ /** @var string */
+ private $mRegexStartToEnd = '';
+
+ /** @var string */
+ private $mBaseRegex = '';
+
+ /** @var string */
+ private $mVariableRegex = '';
+
+ /** @var string */
+ private $mVariableStartToEndRegex = '';
+
+ /** @var bool */
+ private $mModified = false;
+
+ /** @var bool */
+ private $mFound = false;
static public $mVariableIDsInitialised = false;
static public $mVariableIDs = array(
+ '!',
'currentmonth',
'currentmonth1',
'currentmonthname',
@@ -149,6 +173,7 @@ class MagicWord {
'contentlanguage',
'numberofadmins',
'numberofviews',
+ 'cascadingsources',
);
/* Array of caching hints for ParserCache */
@@ -192,7 +217,7 @@ class MagicWord {
'numberofadmins' => 3600,
'numberofviews' => 3600,
'numberingroup' => 3600,
- );
+ );
static public $mDoubleUnderscoreIDs = array(
'notoc',
@@ -229,7 +254,7 @@ class MagicWord {
/**
* Factory: creates an object representing an ID
*
- * @param $id
+ * @param int $id
*
* @return MagicWord
*/
@@ -267,8 +292,8 @@ class MagicWord {
/**
* Allow external reads of TTL array
*
- * @param $id int
- * @return array
+ * @param int $id
+ * @return int
*/
static function getCacheTTL( $id ) {
if ( array_key_exists( $id, self::$mCacheTTLs ) ) {
@@ -302,7 +327,7 @@ class MagicWord {
/**
* Initialises this object with an ID
*
- * @param $id
+ * @param int $id
* @throws MWException
*/
function load( $id ) {
@@ -338,6 +363,7 @@ class MagicWord {
$case = $this->mCaseSensitive ? '' : 'iu';
$this->mRegex = "/{$this->mBaseRegex}/{$case}";
$this->mRegexStart = "/^(?:{$this->mBaseRegex})/{$case}";
+ $this->mRegexStartToEnd = "/^(?:{$this->mBaseRegex})$/{$case}";
$this->mVariableRegex = str_replace( "\\$1", "(.*?)", $this->mRegex );
$this->mVariableStartToEndRegex = str_replace( "\\$1", "(.*?)",
"/^(?:{$this->mBaseRegex})$/{$case}" );
@@ -348,8 +374,8 @@ class MagicWord {
* first string is longer, the same length or shorter than the second
* string.
*
- * @param $s1 string
- * @param $s2 string
+ * @param string $s1
+ * @param string $s2
*
* @return int
*/
@@ -405,6 +431,19 @@ class MagicWord {
}
/**
+ * Gets a regex matching the word from start to end of a string
+ *
+ * @return string
+ * @since 1.23
+ */
+ function getRegexStartToEnd() {
+ if ( $this->mRegexStartToEnd == '' ) {
+ $this->initRegex();
+ }
+ return $this->mRegexStartToEnd;
+ }
+
+ /**
* regex without the slashes and what not
*
* @return string
@@ -419,7 +458,7 @@ class MagicWord {
/**
* Returns true if the text contains the word
*
- * @param $text string
+ * @param string $text
*
* @return bool
*/
@@ -430,7 +469,7 @@ class MagicWord {
/**
* Returns true if the text starts with the word
*
- * @param $text string
+ * @param string $text
*
* @return bool
*/
@@ -439,12 +478,24 @@ class MagicWord {
}
/**
+ * Returns true if the text matched the word
+ *
+ * @param string $text
+ *
+ * @return bool
+ * @since 1.23
+ */
+ function matchStartToEnd( $text ) {
+ return (bool)preg_match( $this->getRegexStartToEnd(), $text );
+ }
+
+ /**
* Returns NULL if there's no match, the value of $1 otherwise
* The return code is the matched string, if there's no variable
* part in the regex and the matched variable part ($1) if there
* is one.
*
- * @param $text string
+ * @param string $text
*
* @return string
*/
@@ -473,23 +524,33 @@ class MagicWord {
* Returns true if the text matches the word, and alters the
* input string, removing all instances of the word
*
- * @param $text string
+ * @param string $text
*
* @return bool
*/
function matchAndRemove( &$text ) {
$this->mFound = false;
- $text = preg_replace_callback( $this->getRegex(), array( &$this, 'pregRemoveAndRecord' ), $text );
+ $text = preg_replace_callback(
+ $this->getRegex(),
+ array( &$this, 'pregRemoveAndRecord' ),
+ $text
+ );
+
return $this->mFound;
}
/**
- * @param $text
+ * @param string $text
* @return bool
*/
function matchStartAndRemove( &$text ) {
$this->mFound = false;
- $text = preg_replace_callback( $this->getRegexStart(), array( &$this, 'pregRemoveAndRecord' ), $text );
+ $text = preg_replace_callback(
+ $this->getRegexStart(),
+ array( &$this, 'pregRemoveAndRecord' ),
+ $text
+ );
+
return $this->mFound;
}
@@ -506,14 +567,19 @@ class MagicWord {
/**
* Replaces the word with something else
*
- * @param $replacement
- * @param $subject
- * @param $limit int
+ * @param string $replacement
+ * @param string $subject
+ * @param int $limit
*
* @return string
*/
function replace( $replacement, $subject, $limit = -1 ) {
- $res = preg_replace( $this->getRegex(), StringUtils::escapeRegexReplacement( $replacement ), $subject, $limit );
+ $res = preg_replace(
+ $this->getRegex(),
+ StringUtils::escapeRegexReplacement( $replacement ),
+ $subject,
+ $limit
+ );
$this->mModified = $res !== $subject;
return $res;
}
@@ -523,8 +589,8 @@ class MagicWord {
* Calls back a function to determine what to replace xxx with
* Input word must contain $1
*
- * @param $text string
- * @param $callback
+ * @param string $text
+ * @param callable $callback
*
* @return string
*/
@@ -561,7 +627,7 @@ class MagicWord {
/**
* Accesses the synonym list directly
*
- * @param $i int
+ * @param int $i
*
* @return string
*/
@@ -593,9 +659,9 @@ class MagicWord {
* $result. The return value is true if something was replaced.
* @todo Should this be static? It doesn't seem to be used at all
*
- * @param $magicarr
- * @param $subject
- * @param $result
+ * @param array $magicarr
+ * @param string $subject
+ * @param string $result
*
* @return bool
*/
@@ -616,8 +682,8 @@ class MagicWord {
* Adds all the synonyms of this MagicWord to an array, to allow quick
* lookup in a list of magic words
*
- * @param $array
- * @param $value
+ * @param array $array
+ * @param string $value
*/
function addToArray( &$array, $value ) {
global $wgContLang;
@@ -646,13 +712,21 @@ class MagicWord {
* @ingroup Parser
*/
class MagicWordArray {
- var $names = array();
- var $hash;
- var $baseRegex, $regex;
- var $matches;
+ /** @var array */
+ public $names = array();
+
+ /** @var array */
+ private $hash;
+
+ private $baseRegex;
+
+ private $regex;
+
+ /** @todo Unused? */
+ private $matches;
/**
- * @param $names array
+ * @param array $names
*/
function __construct( $names = array() ) {
$this->names = $names;
@@ -661,7 +735,7 @@ class MagicWordArray {
/**
* Add a magic word by name
*
- * @param $name string
+ * @param string $name
*/
public function add( $name ) {
$this->names[] = $name;
@@ -671,7 +745,7 @@ class MagicWordArray {
/**
* Add a number of magic words by name
*
- * @param $names array
+ * @param array $names
*/
public function addArray( $names ) {
$this->names = array_merge( $this->names, array_values( $names ) );
@@ -680,6 +754,7 @@ class MagicWordArray {
/**
* Get a 2-d hashtable for this array
+ * @return array
*/
function getHash() {
if ( is_null( $this->hash ) ) {
@@ -701,6 +776,7 @@ class MagicWordArray {
/**
* Get the base regex
+ * @return array
*/
function getBaseRegex() {
if ( is_null( $this->baseRegex ) ) {
@@ -725,6 +801,7 @@ class MagicWordArray {
/**
* Get an unanchored regex that does not match parameters
+ * @return array
*/
function getRegex() {
if ( is_null( $this->regex ) ) {
@@ -796,7 +873,7 @@ class MagicWordArray {
* Returns array(magic word ID, parameter value)
* If there is no parameter value, that element will be false.
*
- * @param $m array
+ * @param array $m
*
* @throws MWException
* @return array
@@ -827,7 +904,7 @@ class MagicWordArray {
* parameter in the second element.
* Both elements are false if there was no match.
*
- * @param $text string
+ * @param string $text
*
* @return array
*/
@@ -848,7 +925,7 @@ class MagicWordArray {
* Match some text, without parameter capture
* Returns the magic word name, or false if there was no capture
*
- * @param $text string
+ * @param string $text
*
* @return string|bool False on failure
*/
@@ -869,7 +946,7 @@ class MagicWordArray {
* Returns an associative array, ID => param value, for all items that match
* Removes the matched items from the input string (passed by reference)
*
- * @param $text string
+ * @param string $text
*
* @return array
*/
@@ -896,7 +973,7 @@ class MagicWordArray {
* Return false if no match found and $text is not modified.
* Does not match parameters.
*
- * @param $text string
+ * @param string $text
*
* @return int|bool False on failure
*/
diff --git a/includes/Wiki.php b/includes/MediaWiki.php
index 074ec1ab..402494ec 100644
--- a/includes/Wiki.php
+++ b/includes/MediaWiki.php
@@ -26,32 +26,15 @@
* @internal documentation reviewed 15 Mar 2010
*/
class MediaWiki {
-
/**
- * TODO: fold $output, etc, into this
* @var IContextSource
*/
private $context;
/**
- * @param $x null|WebRequest
- * @return WebRequest
- */
- public function request( WebRequest $x = null ) {
- $old = $this->context->getRequest();
- $this->context->setRequest( $x );
- return $old;
- }
-
- /**
- * @param $x null|OutputPage
- * @return OutputPage
+ * @var Config
*/
- public function output( OutputPage $x = null ) {
- $old = $this->context->getOutput();
- $this->context->setOutput( $x );
- return $old;
- }
+ private $config;
/**
* @param IContextSource|null $context
@@ -62,12 +45,13 @@ class MediaWiki {
}
$this->context = $context;
+ $this->config = $context->getConfig();
}
/**
* Parse the request to get the Title object
*
- * @return Title object to be $wgTitle
+ * @return Title Title object to be $wgTitle
*/
private function parseTitle() {
global $wgContLang;
@@ -95,8 +79,8 @@ class MediaWiki {
// Check variant links so that interwiki links don't have to worry
// about the possible different language variants
if ( count( $wgContLang->getVariants() ) > 1
- && !is_null( $ret ) && $ret->getArticleID() == 0 )
- {
+ && !is_null( $ret ) && $ret->getArticleID() == 0
+ ) {
$wgContLang->findVariantLink( $title, $ret );
}
}
@@ -117,11 +101,15 @@ class MediaWiki {
}
// Use the main page as default title if nothing else has been provided
- if ( $ret === null && strval( $title ) === '' && $action !== 'delete' ) {
+ if ( $ret === null
+ && strval( $title ) === ''
+ && !$request->getCheck( 'curid' )
+ && $action !== 'delete'
+ ) {
$ret = Title::newMainPage();
}
- if ( $ret === null || ( $ret->getDBkey() == '' && $ret->getInterwiki() == '' ) ) {
+ if ( $ret === null || ( $ret->getDBkey() == '' && !$ret->isExternal() ) ) {
$ret = SpecialPage::getTitleFor( 'Badtitle' );
}
@@ -142,7 +130,7 @@ class MediaWiki {
/**
* Returns the name of the action that will be executed.
*
- * @return string: action
+ * @return string Action
*/
public function getAction() {
static $action = null;
@@ -155,19 +143,6 @@ class MediaWiki {
}
/**
- * Create an Article object of the appropriate class for the given page.
- *
- * @deprecated in 1.18; use Article::newFromTitle() instead
- * @param $title Title
- * @param $context IContextSource
- * @return Article object
- */
- public static function articleFromTitle( $title, IContextSource $context ) {
- wfDeprecated( __METHOD__, '1.18' );
- return Article::newFromTitle( $title, $context );
- }
-
- /**
* Performs the request.
* - bad titles
* - read restriction
@@ -180,7 +155,7 @@ class MediaWiki {
* @return void
*/
private function performRequest() {
- global $wgServer, $wgUsePathInfo, $wgTitle;
+ global $wgTitle;
wfProfileIn( __METHOD__ );
@@ -197,9 +172,9 @@ class MediaWiki {
wfRunHooks( 'BeforeInitialize', array( &$title, &$unused, &$output, &$user, $request, $this ) );
// Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty.
- if ( is_null( $title ) || ( $title->getDBkey() == '' && $title->getInterwiki() == '' ) ||
- $title->isSpecial( 'Badtitle' ) )
- {
+ if ( is_null( $title ) || ( $title->getDBkey() == '' && !$title->isExternal() )
+ || $title->isSpecial( 'Badtitle' )
+ ) {
$this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
wfProfileOut( __METHOD__ );
throw new BadTitleError();
@@ -208,7 +183,9 @@ class MediaWiki {
// Check user's permissions to read this page.
// We have to check here to catch special pages etc.
// We will check again in Article::view().
- $permErrors = $title->getUserPermissionsErrors( 'read', $user );
+ $permErrors = $title->isSpecial( 'RunJobs' )
+ ? array() // relies on HMAC key signature alone
+ : $title->getUserPermissionsErrors( 'read', $user );
if ( count( $permErrors ) ) {
// Bug 32276: allowing the skin to generate output with $wgTitle or
// $this->context->title set to the input title would allow anonymous users to
@@ -231,7 +208,7 @@ class MediaWiki {
$pageView = false; // was an article or special page viewed?
// Interwiki redirects
- if ( $title->getInterwiki() != '' ) {
+ if ( $title->isExternal() ) {
$rdfrom = $request->getVal( 'rdfrom' );
if ( $rdfrom ) {
$url = $title->getFullURL( array( 'rdfrom' => $rdfrom ) );
@@ -241,9 +218,9 @@ class MediaWiki {
$url = $title->getFullURL( $query );
}
// Check for a redirect loop
- if ( !preg_match( '/^' . preg_quote( $wgServer, '/' ) . '/', $url )
- && $title->isLocal() )
- {
+ if ( !preg_match( '/^' . preg_quote( $this->config->get( 'Server' ), '/' ) . '/', $url )
+ && $title->isLocal()
+ ) {
// 301 so google et al report the target as the actual url.
$output->redirect( $url, 301 );
} else {
@@ -253,11 +230,11 @@ class MediaWiki {
}
// Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
} elseif ( $request->getVal( 'action', 'view' ) == 'view' && !$request->wasPosted()
- && ( $request->getVal( 'title' ) === null ||
- $title->getPrefixedDBkey() != $request->getVal( 'title' ) )
+ && ( $request->getVal( 'title' ) === null
+ || $title->getPrefixedDBkey() != $request->getVal( 'title' ) )
&& !count( $request->getValueNames( array( 'action', 'title' ) ) )
- && wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) ) )
- {
+ && wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) )
+ ) {
if ( $title->isSpecialPage() ) {
list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
if ( $name ) {
@@ -272,7 +249,7 @@ class MediaWiki {
"requested; this sometimes happens when moving a wiki " .
"to a new server or changing the server configuration.\n\n";
- if ( $wgUsePathInfo ) {
+ if ( $this->config->get( 'UsePathInfo' ) ) {
$message .= "The wiki is trying to interpret the page " .
"title from the URL path portion (PATH_INFO), which " .
"sometimes fails depending on the web server. Try " .
@@ -302,13 +279,6 @@ class MediaWiki {
$article = $this->initializeArticle();
if ( is_object( $article ) ) {
$pageView = true;
- /**
- * $wgArticle is deprecated, do not use it.
- * @deprecated since 1.18
- */
- global $wgArticle;
- $wgArticle = new DeprecatedGlobal( 'wgArticle', $article, '1.18' );
-
$this->performAction( $article, $requestTitle );
} elseif ( is_string( $article ) ) {
$output->redirect( $article );
@@ -331,11 +301,9 @@ class MediaWiki {
* Initialize the main Article object for "standard" actions (view, etc)
* Create an Article object for the page, following redirects if needed.
*
- * @return mixed an Article, or a string to redirect to another URL
+ * @return mixed An Article, or a string to redirect to another URL
*/
private function initializeArticle() {
- global $wgDisableHardRedirects;
-
wfProfileIn( __METHOD__ );
$title = $this->context->getTitle();
@@ -365,12 +333,12 @@ class MediaWiki {
$action = $request->getVal( 'action', 'view' );
$file = ( $title->getNamespace() == NS_FILE ) ? $article->getFile() : null;
if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
- && !$request->getVal( 'oldid' ) && // ... and are not old revisions
- !$request->getVal( 'diff' ) && // ... and not when showing diff
- $request->getVal( 'redirect' ) != 'no' && // ... unless explicitly told not to
+ && !$request->getVal( 'oldid' ) // ... and are not old revisions
+ && !$request->getVal( 'diff' ) // ... and not when showing diff
+ && $request->getVal( 'redirect' ) != 'no' // ... unless explicitly told not to
// ... and the article is not a non-redirect image page with associated file
- !( is_object( $file ) && $file->exists() && !$file->getRedirected() ) )
- {
+ && !( is_object( $file ) && $file->exists() && !$file->getRedirected() )
+ ) {
// Give extensions a change to ignore/handle redirects as needed
$ignoreRedirect = $target = false;
@@ -383,7 +351,7 @@ class MediaWiki {
// Is the target already set by an extension?
$target = $target ? $target : $article->followRedirect();
if ( is_string( $target ) ) {
- if ( !$wgDisableHardRedirects ) {
+ if ( !$this->config->get( 'DisableHardRedirects' ) ) {
// we'll need to redirect
wfProfileOut( __METHOD__ );
return $target;
@@ -413,12 +381,10 @@ class MediaWiki {
/**
* Perform one of the "standard" actions
*
- * @param $page Page
- * @param $requestTitle The original title, before any redirects were applied
+ * @param Page $page
+ * @param Title $requestTitle The original title, before any redirects were applied
*/
private function performAction( Page $page, Title $requestTitle ) {
- global $wgUseSquid, $wgSquidMaxage;
-
wfProfileIn( __METHOD__ );
$request = $this->context->getRequest();
@@ -427,8 +393,8 @@ class MediaWiki {
$user = $this->context->getUser();
if ( !wfRunHooks( 'MediaWikiPerformAction',
- array( $output, $page, $title, $user, $request, $this ) ) )
- {
+ array( $output, $page, $title, $user, $request, $this ) )
+ ) {
wfProfileOut( __METHOD__ );
return;
}
@@ -439,10 +405,10 @@ class MediaWiki {
if ( $action instanceof Action ) {
# Let Squid cache things if we can purge them.
- if ( $wgUseSquid &&
+ if ( $this->config->get( 'UseSquid' ) &&
in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() )
) {
- $output->setSquidMaxage( $wgSquidMaxage );
+ $output->setSquidMaxage( $this->config->get( 'SquidMaxage' ) );
}
$action->show();
@@ -451,6 +417,7 @@ class MediaWiki {
}
if ( wfRunHooks( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) {
+ $output->setStatusCode( 404 );
$output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
@@ -464,7 +431,19 @@ class MediaWiki {
public function run() {
try {
$this->checkMaxLag();
- $this->main();
+ try {
+ $this->main();
+ } catch ( ErrorPageError $e ) {
+ // Bug 62091: while exceptions are convenient to bubble up GUI errors,
+ // they are not internal application faults. As with normal requests, this
+ // should commit, print the output, do deferred updates, jobs, and profiling.
+ wfGetLBFactory()->commitMasterChanges();
+ $e->report(); // display the GUI error
+ }
+ if ( function_exists( 'fastcgi_finish_request' ) ) {
+ fastcgi_finish_request();
+ }
+ $this->triggerJobs();
$this->restInPeace();
} catch ( Exception $e ) {
MWExceptionHandler::handle( $e );
@@ -477,8 +456,6 @@ class MediaWiki {
* @return bool
*/
private function checkMaxLag() {
- global $wgShowHostnames;
-
wfProfileIn( __METHOD__ );
$maxLag = $this->context->getRequest()->getVal( 'maxlag' );
if ( !is_null( $maxLag ) ) {
@@ -489,7 +466,7 @@ class MediaWiki {
$resp->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
$resp->header( 'X-Database-Lag: ' . intval( $lag ) );
$resp->header( 'Content-Type: text/plain' );
- if ( $wgShowHostnames ) {
+ if ( $this->config->get( 'ShowHostnames' ) ) {
echo "Waiting for $host: $lag seconds lagged\n";
} else {
echo "Waiting for a database server: $lag seconds lagged\n";
@@ -505,22 +482,22 @@ class MediaWiki {
}
private function main() {
- global $wgUseFileCache, $wgTitle, $wgUseAjax;
+ global $wgTitle;
wfProfileIn( __METHOD__ );
$request = $this->context->getRequest();
// Send Ajax requests to the Ajax dispatcher.
- if ( $wgUseAjax && $request->getVal( 'action', 'view' ) == 'ajax' ) {
+ if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action', 'view' ) == 'ajax' ) {
// Set a dummy title, because $wgTitle == null might break things
$title = Title::makeTitle( NS_MAIN, 'AJAX' );
$this->context->setTitle( $title );
$wgTitle = $title;
- $dispatcher = new AjaxDispatcher();
- $dispatcher->performAction();
+ $dispatcher = new AjaxDispatcher( $this->config );
+ $dispatcher->performAction( $this->context->getUser() );
wfProfileOut( __METHOD__ );
return;
}
@@ -537,6 +514,7 @@ class MediaWiki {
// Note: Do this after $wgTitle is setup, otherwise the hooks run from
// isLoggedIn() will do all sorts of weird stuff.
if (
+ $request->getProtocol() == 'http' &&
(
$request->getCookie( 'forceHTTPS', '' ) ||
// check for prefixed version for currently logged in users
@@ -546,41 +524,43 @@ class MediaWiki {
$this->context->getUser()->isLoggedIn()
&& $this->context->getUser()->requiresHTTPS()
)
- ) &&
- $request->detectProtocol() == 'http'
+ )
) {
$oldUrl = $request->getFullRequestURL();
- $redirUrl = str_replace( 'http://', 'https://', $oldUrl );
-
- if ( $request->wasPosted() ) {
- // This is weird and we'd hope it almost never happens. This
- // means that a POST came in via HTTP and policy requires us
- // redirecting to HTTPS. It's likely such a request is going
- // to fail due to post data being lost, but let's try anyway
- // and just log the instance.
- //
- // @todo @fixme See if we could issue a 307 or 308 here, need
- // to see how clients (automated & browser) behave when we do
- wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
+ $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl );
+
+ // ATTENTION: This hook is likely to be removed soon due to overall design of the system.
+ if ( wfRunHooks( 'BeforeHttpsRedirect', array( $this->context, &$redirUrl ) ) ) {
+
+ if ( $request->wasPosted() ) {
+ // This is weird and we'd hope it almost never happens. This
+ // means that a POST came in via HTTP and policy requires us
+ // redirecting to HTTPS. It's likely such a request is going
+ // to fail due to post data being lost, but let's try anyway
+ // and just log the instance.
+ //
+ // @todo FIXME: See if we could issue a 307 or 308 here, need
+ // to see how clients (automated & browser) behave when we do
+ wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
+ }
+ // Setup dummy Title, otherwise OutputPage::redirect will fail
+ $title = Title::newFromText( NS_MAIN, 'REDIR' );
+ $this->context->setTitle( $title );
+ $output = $this->context->getOutput();
+ // Since we only do this redir to change proto, always send a vary header
+ $output->addVaryHeader( 'X-Forwarded-Proto' );
+ $output->redirect( $redirUrl );
+ $output->output();
+ wfProfileOut( __METHOD__ );
+ return;
}
-
- // Setup dummy Title, otherwise OutputPage::redirect will fail
- $title = Title::newFromText( NS_MAIN, 'REDIR' );
- $this->context->setTitle( $title );
- $output = $this->context->getOutput();
- // Since we only do this redir to change proto, always send a vary header
- $output->addVaryHeader( 'X-Forwarded-Proto' );
- $output->redirect( $redirUrl );
- $output->output();
- wfProfileOut( __METHOD__ );
- return;
}
- if ( $wgUseFileCache && $title->getNamespace() >= 0 ) {
+ if ( $this->config->get( 'UseFileCache' ) && $title->getNamespace() >= 0 ) {
wfProfileIn( 'main-try-filecache' );
if ( HTMLFileCache::useFileCache( $this->context ) ) {
// Try low-level file cache hit
- $cache = HTMLFileCache::newFromTitle( $title, $action );
+ $cache = new HTMLFileCache( $title, $action );
if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
// Check incoming headers to see if client has this cached
$timestamp = $cache->cacheTimestamp();
@@ -588,6 +568,7 @@ class MediaWiki {
$cache->loadFromFileCache( $this->context );
}
// Do any stats increment/watchlist stuff
+ // Assume we're viewing the latest revision (this should always be the case with file cache)
$this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
// Tell OutputPage that output is taken care of
$this->context->getOutput()->disable();
@@ -599,8 +580,12 @@ class MediaWiki {
wfProfileOut( 'main-try-filecache' );
}
+ // Actually do the work of the request and build up any output
$this->performRequest();
+ // Either all DB and deferred updates should happen or none.
+ // The later should not be cancelled due to client disconnect.
+ ignore_user_abort( true );
// Now commit any transactions, so that unreported errors after
// output() don't roll back the whole DB transaction
wfGetLBFactory()->commitMasterChanges();
@@ -618,9 +603,6 @@ class MediaWiki {
// Do any deferred jobs
DeferredUpdates::doUpdates( 'commit' );
- // Execute a job from the queue
- $this->doJobs();
-
// Log profiling data, e.g. in the database or UDP
wfLogProfilingData();
@@ -633,69 +615,90 @@ class MediaWiki {
}
/**
- * Do a job from the job queue
+ * Potentially open a socket and sent an HTTP request back to the server
+ * to run a specified number of jobs. This registers a callback to cleanup
+ * the socket once it's done.
*/
- private function doJobs() {
- global $wgJobRunRate, $wgPhpCli, $IP;
-
- if ( $wgJobRunRate <= 0 || wfReadOnly() ) {
+ protected function triggerJobs() {
+ $jobRunRate = $this->config->get( 'JobRunRate' );
+ if ( $jobRunRate <= 0 || wfReadOnly() ) {
return;
+ } elseif ( $this->getTitle()->isSpecial( 'RunJobs' ) ) {
+ return; // recursion guard
}
- if ( $wgJobRunRate < 1 ) {
+ $section = new ProfileSection( __METHOD__ );
+
+ if ( $jobRunRate < 1 ) {
$max = mt_getrandmax();
- if ( mt_rand( 0, $max ) > $max * $wgJobRunRate ) {
- return; // the higher $wgJobRunRate, the less likely we return here
+ if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
+ return; // the higher the job run rate, the less likely we return here
}
$n = 1;
} else {
- $n = intval( $wgJobRunRate );
+ $n = intval( $jobRunRate );
}
- if ( !wfShellExecDisabled() && is_executable( $wgPhpCli ) ) {
- // Start a background process to run some of the jobs
- wfProfileIn( __METHOD__ . '-exec' );
- $retVal = 1;
- $cmd = wfShellWikiCmd( "$IP/maintenance/runJobs.php", array( '--maxjobs', $n ) );
- $cmd .= " >" . wfGetNull() . " 2>&1"; // don't hang PHP on pipes
- if ( wfIsWindows() ) {
- // Using START makes this async and also works around a bug where using
- // wfShellExec() with a quoted script name causes a filename syntax error.
- $cmd = "START /B \"bg\" $cmd";
- } else {
- $cmd = "$cmd &";
+ if ( !$this->config->get( 'RunJobsAsync' ) ) {
+ // Fall back to running the job here while the user waits
+ $runner = new JobRunner();
+ $runner->run( array( 'maxJobs' => $n ) );
+ return;
+ }
+
+ try {
+ if ( !JobQueueGroup::singleton()->queuesHaveJobs( JobQueueGroup::TYPE_DEFAULT ) ) {
+ return; // do not send request if there are probably no jobs
}
- wfShellExec( $cmd, $retVal );
- wfProfileOut( __METHOD__ . '-exec' );
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
+ return; // do not make the site unavailable
+ }
+
+ $query = array( 'title' => 'Special:RunJobs',
+ 'tasks' => 'jobs', 'maxjobs' => $n, 'sigexpiry' => time() + 5 );
+ $query['signature'] = SpecialRunJobs::getQuerySignature(
+ $query, $this->config->get( 'SecretKey' ) );
+
+ $errno = $errstr = null;
+ $info = wfParseUrl( $this->config->get( 'Server' ) );
+ wfSuppressWarnings();
+ $sock = fsockopen(
+ $info['host'],
+ isset( $info['port'] ) ? $info['port'] : 80,
+ $errno,
+ $errstr,
+ // If it takes more than 100ms to connect to ourselves there
+ // is a problem elsewhere.
+ 0.1
+ );
+ wfRestoreWarnings();
+ if ( !$sock ) {
+ wfDebugLog( 'runJobs', "Failed to start cron API (socket error $errno): $errstr\n" );
+ // Fall back to running the job here while the user waits
+ $runner = new JobRunner();
+ $runner->run( array( 'maxJobs' => $n ) );
+ return;
+ }
+
+ $url = wfAppendQuery( wfScript( 'index' ), $query );
+ $req = "POST $url HTTP/1.1\r\nHost: {$info['host']}\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n";
+
+ wfDebugLog( 'runJobs', "Running $n job(s) via '$url'\n" );
+ // Send a cron API request to be performed in the background.
+ // Give up if this takes too long to send (which should be rare).
+ stream_set_timeout( $sock, 1 );
+ $bytes = fwrite( $sock, $req );
+ if ( $bytes !== strlen( $req ) ) {
+ wfDebugLog( 'runJobs', "Failed to start cron API (socket write error)\n" );
} else {
- try {
- // Fallback to running the jobs here while the user waits
- $group = JobQueueGroup::singleton();
- do {
- $job = $group->pop( JobQueueGroup::USE_CACHE ); // job from any queue
- if ( $job ) {
- $output = $job->toString() . "\n";
- $t = - microtime( true );
- wfProfileIn( __METHOD__ . '-' . get_class( $job ) );
- $success = $job->run();
- wfProfileOut( __METHOD__ . '-' . get_class( $job ) );
- $group->ack( $job ); // done
- $t += microtime( true );
- $t = round( $t * 1000 );
- if ( $success === false ) {
- $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
- } else {
- $output .= "Success, Time: $t ms\n";
- }
- wfDebugLog( 'jobqueue', $output );
- }
- } while ( --$n && $job );
- } catch ( MWException $e ) {
- // We don't want exceptions thrown during job execution to
- // be reported to the user since the output is already sent.
- // Instead we just log them.
- MWExceptionHandler::logException( $e );
+ // Do not wait for the response (the script should handle client aborts).
+ // Make sure that we don't close before that script reaches ignore_user_abort().
+ $status = fgets( $sock );
+ if ( !preg_match( '#^HTTP/\d\.\d 202 #', $status ) ) {
+ wfDebugLog( 'runJobs', "Failed to start cron API: received '$status'\n" );
}
}
+ fclose( $sock );
}
}
diff --git a/includes/MediaWikiVersionFetcher.php b/includes/MediaWikiVersionFetcher.php
new file mode 100644
index 00000000..439e53f4
--- /dev/null
+++ b/includes/MediaWikiVersionFetcher.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Provides access to MediaWiki's version without requiring MediaWiki (or anything else)
+ * being loaded first.
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class MediaWikiVersionFetcher {
+
+ /**
+ * Returns the MediaWiki version, in the format used by MediaWiki's wgVersion global.
+ *
+ * @return string
+ * @throws RuntimeException
+ */
+ public function fetchVersion() {
+ $defaultSettings = file_get_contents( __DIR__ . '/DefaultSettings.php' );
+
+ $matches = array();
+ preg_match( "/wgVersion = '([-0-9a-zA-Z\.]+)';/", $defaultSettings, $matches );
+
+ if ( count( $matches ) !== 2 ) {
+ throw new RuntimeException( 'Could not extract the MediaWiki version from DefaultSettings.php' );
+ }
+
+ return $matches[1];
+ }
+
+}
diff --git a/includes/Message.php b/includes/Message.php
index 57c6264d..4df0d809 100644
--- a/includes/Message.php
+++ b/includes/Message.php
@@ -157,9 +157,12 @@
* @since 1.17
*/
class Message {
+
/**
* In which language to get this message. True, which is the default,
* means the current interface language, false content language.
+ *
+ * @var bool
*/
protected $interface = true;
@@ -172,12 +175,18 @@ class Message {
protected $language = null;
/**
- * The message key.
+ * @var string The message key. If $keysToTry has more than one element,
+ * this may change to one of the keys to try when fetching the message text.
*/
protected $key;
/**
- * List of parameters which will be substituted into the message.
+ * @var string[] List of keys to try when fetching the message.
+ */
+ protected $keysToTry;
+
+ /**
+ * @var array List of parameters which will be substituted into the message.
*/
protected $parameters = array();
@@ -189,21 +198,23 @@ class Message {
* * block-parse
* * parse (default)
* * plain
+ *
+ * @var string
*/
protected $format = 'parse';
/**
- * Whether database can be used.
+ * @var bool Whether database can be used.
*/
protected $useDatabase = true;
/**
- * Title object to use as context
+ * @var Title Title object to use as context.
*/
protected $title = null;
/**
- * Content object representing the message
+ * @var Content Content object representing the message.
*/
protected $content = null;
@@ -213,49 +224,82 @@ class Message {
protected $message;
/**
- * Constructor.
* @since 1.17
- * @param $key: message key, or array of message keys to try and use the first non-empty message for
- * @param array $params message parameters
- * @return Message: $this
+ *
+ * @param string|string[] $key Message key or array of message keys to try and use the first
+ * non-empty message for.
+ * @param array $params Message parameters.
+ * @param Language $language Optional language of the message, defaults to $wgLang.
+ *
+ * @throws InvalidArgumentException
*/
- public function __construct( $key, $params = array() ) {
+ public function __construct( $key, $params = array(), Language $language = null ) {
global $wgLang;
- $this->key = $key;
+
+ if ( !is_string( $key ) && !is_array( $key ) ) {
+ throw new InvalidArgumentException( '$key must be a string or an array' );
+ }
+
+ $this->keysToTry = (array)$key;
+
+ if ( empty( $this->keysToTry ) ) {
+ throw new InvalidArgumentException( '$key must not be an empty list' );
+ }
+
+ $this->key = reset( $this->keysToTry );
+
$this->parameters = array_values( $params );
- $this->language = $wgLang;
+ $this->language = $language ? $language : $wgLang;
+ }
+
+ /**
+ * @since 1.24
+ *
+ * @return bool True if this is a multi-key message, that is, if the key provided to the
+ * constructor was a fallback list of keys to try.
+ */
+ public function isMultiKey() {
+ return count( $this->keysToTry ) > 1;
+ }
+
+ /**
+ * @since 1.24
+ *
+ * @return string[] The list of keys to try when fetching the message text,
+ * in order of preference.
+ */
+ public function getKeysToTry() {
+ return $this->keysToTry;
}
/**
- * Returns the message key
+ * Returns the message key.
+ *
+ * If a list of multiple possible keys was supplied to the constructor, this method may
+ * return any of these keys. After the message ahs been fetched, this method will return
+ * the key that was actually used to fetch the message.
*
* @since 1.21
*
* @return string
*/
public function getKey() {
- if ( is_array( $this->key ) ) {
- // May happen if some kind of fallback is applied.
- // For now, just use the first key. We really need a better solution.
- return $this->key[0];
- } else {
- return $this->key;
- }
+ return $this->key;
}
/**
- * Returns the message parameters
+ * Returns the message parameters.
*
* @since 1.21
*
- * @return string[]
+ * @return array
*/
public function getParams() {
return $this->parameters;
}
/**
- * Returns the message format
+ * Returns the message format.
*
* @since 1.21
*
@@ -266,13 +310,27 @@ class Message {
}
/**
+ * Returns the Language of the Message.
+ *
+ * @since 1.23
+ *
+ * @return Language
+ */
+ public function getLanguage() {
+ return $this->language;
+ }
+
+ /**
* Factory function that is just wrapper for the real constructor. It is
* intended to be used instead of the real constructor, because it allows
* chaining method calls, while new objects don't.
+ *
* @since 1.17
- * @param string $key message key
- * @param Varargs: parameters as Strings
- * @return Message: $this
+ *
+ * @param string|string[] $key Message key or array of keys.
+ * @param mixed $param,... Parameters as strings.
+ *
+ * @return Message
*/
public static function newFromKey( $key /*...*/ ) {
$params = func_get_args();
@@ -284,9 +342,13 @@ class Message {
* Factory function accepting multiple message keys and returning a message instance
* for the first message which is non-empty. If all messages are empty then an
* instance of the first message key is returned.
+ *
* @since 1.18
- * @param Varargs: message keys (or first arg as an array of all the message keys)
- * @return Message: $this
+ *
+ * @param string|string[] $keys,... Message keys, or first argument as an array of all the
+ * message keys.
+ *
+ * @return Message
*/
public static function newFallbackSequence( /*...*/ ) {
$keys = func_get_args();
@@ -304,9 +366,13 @@ class Message {
/**
* Adds parameters to the parameter list of this message.
+ *
* @since 1.17
- * @param Varargs: parameters as Strings, or a single argument that is an array of Strings
- * @return Message: $this
+ *
+ * @param mixed $params,... Parameters as strings, or a single argument that is
+ * an array of strings.
+ *
+ * @return Message $this
*/
public function params( /*...*/ ) {
$args = func_get_args();
@@ -323,9 +389,13 @@ class Message {
* In other words the parsing process cannot access the contents
* of this type of parameter, and you need to make sure it is
* sanitized beforehand. The parser will see "$n", instead.
+ *
* @since 1.17
- * @param Varargs: raw parameters as Strings (or single argument that is an array of raw parameters)
- * @return Message: $this
+ *
+ * @param mixed $params,... Raw parameters as strings, or a single argument that is
+ * an array of raw parameters.
+ *
+ * @return Message $this
*/
public function rawParams( /*...*/ ) {
$params = func_get_args();
@@ -341,9 +411,13 @@ class Message {
/**
* Add parameters that are numeric and will be passed through
* Language::formatNum before substitution
+ *
* @since 1.18
- * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
- * @return Message: $this
+ *
+ * @param mixed $param,... Numeric parameters, or a single argument that is
+ * an array of numeric parameters.
+ *
+ * @return Message $this
*/
public function numParams( /*...*/ ) {
$params = func_get_args();
@@ -359,9 +433,13 @@ class Message {
/**
* Add parameters that are durations of time and will be passed through
* Language::formatDuration before substitution
+ *
* @since 1.22
- * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
- * @return Message: $this
+ *
+ * @param int|int[] $param,... Duration parameters, or a single argument that is
+ * an array of duration parameters.
+ *
+ * @return Message $this
*/
public function durationParams( /*...*/ ) {
$params = func_get_args();
@@ -377,9 +455,13 @@ class Message {
/**
* Add parameters that are expiration times and will be passed through
* Language::formatExpiry before substitution
+ *
* @since 1.22
- * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
- * @return Message: $this
+ *
+ * @param string|string[] $param,... Expiry parameters, or a single argument that is
+ * an array of expiry parameters.
+ *
+ * @return Message $this
*/
public function expiryParams( /*...*/ ) {
$params = func_get_args();
@@ -395,9 +477,13 @@ class Message {
/**
* Add parameters that are time periods and will be passed through
* Language::formatTimePeriod before substitution
+ *
* @since 1.22
- * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
- * @return Message: $this
+ *
+ * @param int|int[] $param,... Time period parameters, or a single argument that is
+ * an array of time period parameters.
+ *
+ * @return Message $this
*/
public function timeperiodParams( /*...*/ ) {
$params = func_get_args();
@@ -413,9 +499,13 @@ class Message {
/**
* Add parameters that are file sizes and will be passed through
* Language::formatSize before substitution
+ *
* @since 1.22
- * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
- * @return Message: $this
+ *
+ * @param int|int[] $param,... Size parameters, or a single argument that is
+ * an array of size parameters.
+ *
+ * @return Message $this
*/
public function sizeParams( /*...*/ ) {
$params = func_get_args();
@@ -431,9 +521,13 @@ class Message {
/**
* Add parameters that are bitrates and will be passed through
* Language::formatBitrate before substitution
+ *
* @since 1.22
- * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
- * @return Message: $this
+ *
+ * @param int|int[] $param,... Bit rate parameters, or a single argument that is
+ * an array of bit rate parameters.
+ *
+ * @return Message $this
*/
public function bitrateParams( /*...*/ ) {
$params = func_get_args();
@@ -448,9 +542,12 @@ class Message {
/**
* Set the language and the title from a context object
+ *
* @since 1.19
- * @param $context IContextSource
- * @return Message: $this
+ *
+ * @param IContextSource $context
+ *
+ * @return Message $this
*/
public function setContext( IContextSource $context ) {
$this->inLanguage( $context->getLanguage() );
@@ -464,10 +561,13 @@ class Message {
* Request the message in any language that is supported.
* As a side effect interface message status is unconditionally
* turned off.
+ *
* @since 1.17
- * @param $lang Mixed: language code or Language object.
+ *
+ * @param Language|string $lang Language code or Language object.
+ *
+ * @return Message $this
* @throws MWException
- * @return Message: $this
*/
public function inLanguage( $lang ) {
if ( $lang instanceof Language || $lang instanceof StubUserLang ) {
@@ -482,6 +582,7 @@ class Message {
. "passed a String or Language object; $type given"
);
}
+ $this->message = null;
$this->interface = false;
return $this;
}
@@ -489,9 +590,11 @@ class Message {
/**
* Request the message in the wiki's content language,
* unless it is disabled for this message.
+ *
* @since 1.17
* @see $wgForceUIMsgAsContentMsg
- * @return Message: $this
+ *
+ * @return Message $this
*/
public function inContentLanguage() {
global $wgForceUIMsgAsContentMsg;
@@ -500,39 +603,47 @@ class Message {
}
global $wgContLang;
- $this->interface = false;
- $this->language = $wgContLang;
+ $this->inLanguage( $wgContLang );
return $this;
}
/**
* Allows manipulating the interface message flag directly.
* Can be used to restore the flag after setting a language.
- * @param $value bool
- * @return Message: $this
+ *
* @since 1.20
+ *
+ * @param bool $interface
+ *
+ * @return Message $this
*/
- public function setInterfaceMessageFlag( $value ) {
- $this->interface = (bool)$value;
+ public function setInterfaceMessageFlag( $interface ) {
+ $this->interface = (bool)$interface;
return $this;
}
/**
* Enable or disable database use.
+ *
* @since 1.17
- * @param $value Boolean
- * @return Message: $this
+ *
+ * @param bool $useDatabase
+ *
+ * @return Message $this
*/
- public function useDatabase( $value ) {
- $this->useDatabase = (bool)$value;
+ public function useDatabase( $useDatabase ) {
+ $this->useDatabase = (bool)$useDatabase;
return $this;
}
/**
* Set the Title object to use as context when transforming the message
+ *
* @since 1.18
- * @param $title Title object
- * @return Message: $this
+ *
+ * @param Title $title
+ *
+ * @return Message $this
*/
public function title( $title ) {
$this->title = $title;
@@ -541,6 +652,7 @@ class Message {
/**
* Returns the message as a Content object.
+ *
* @return Content
*/
public function content() {
@@ -553,14 +665,16 @@ class Message {
/**
* Returns the message parsed from wikitext to HTML.
+ *
* @since 1.17
- * @return String: HTML
+ *
+ * @return string HTML
*/
public function toString() {
$string = $this->fetchMessage();
if ( $string === false ) {
- $key = htmlspecialchars( is_array( $this->key ) ? $this->key[0] : $this->key );
+ $key = htmlspecialchars( $this->key );
if ( $this->format === 'plain' ) {
return '<' . $key . '>';
}
@@ -582,10 +696,7 @@ class Message {
# Maybe transform using the full parser
if ( $this->format === 'parse' ) {
$string = $this->parseText( $string );
- $m = array();
- if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
- $string = $m[1];
- }
+ $string = Parser::stripOuterParagraph( $string );
} elseif ( $this->format === 'block-parse' ) {
$string = $this->parseText( $string );
} elseif ( $this->format === 'text' ) {
@@ -605,8 +716,10 @@ class Message {
* Magic method implementation of the above (for PHP >= 5.2.0), so we can do, eg:
* $foo = Message::get( $key );
* $string = "<abbr>$foo</abbr>";
+ *
* @since 1.18
- * @return String
+ *
+ * @return string
*/
public function __toString() {
// PHP doesn't allow __toString to throw exceptions and will
@@ -630,9 +743,11 @@ class Message {
}
/**
- * Fully parse the text from wikitext to HTML
+ * Fully parse the text from wikitext to HTML.
+ *
* @since 1.17
- * @return String parsed HTML
+ *
+ * @return string Parsed HTML.
*/
public function parse() {
$this->format = 'parse';
@@ -641,8 +756,10 @@ class Message {
/**
* Returns the message text. {{-transformation is done.
+ *
* @since 1.17
- * @return String: Unescaped message text.
+ *
+ * @return string Unescaped message text.
*/
public function text() {
$this->format = 'text';
@@ -651,8 +768,10 @@ class Message {
/**
* Returns the message text as-is, only parameters are substituted.
+ *
* @since 1.17
- * @return String: Unescaped untransformed message text.
+ *
+ * @return string Unescaped untransformed message text.
*/
public function plain() {
$this->format = 'plain';
@@ -661,8 +780,10 @@ class Message {
/**
* Returns the parsed message text which is always surrounded by a block element.
+ *
* @since 1.17
- * @return String: HTML
+ *
+ * @return string HTML
*/
public function parseAsBlock() {
$this->format = 'block-parse';
@@ -672,8 +793,10 @@ class Message {
/**
* Returns the message text. {{-transformation is done and the result
* is escaped excluding any raw parameters.
+ *
* @since 1.17
- * @return String: Escaped message text.
+ *
+ * @return string Escaped message text.
*/
public function escaped() {
$this->format = 'escaped';
@@ -682,8 +805,10 @@ class Message {
/**
* Check whether a message key has been defined currently.
+ *
* @since 1.17
- * @return Bool: true if it is and false if not.
+ *
+ * @return bool
*/
public function exists() {
return $this->fetchMessage() !== false;
@@ -691,9 +816,11 @@ class Message {
/**
* Check whether a message does not exist, or is an empty string
+ *
* @since 1.18
- * @return Bool: true if is is and false if not
* @todo FIXME: Merge with isDisabled()?
+ *
+ * @return bool
*/
public function isBlank() {
$message = $this->fetchMessage();
@@ -701,9 +828,11 @@ class Message {
}
/**
- * Check whether a message does not exist, is an empty string, or is "-"
+ * Check whether a message does not exist, is an empty string, or is "-".
+ *
* @since 1.18
- * @return Bool: true if it is and false if not
+ *
+ * @return bool
*/
public function isDisabled() {
$message = $this->fetchMessage();
@@ -712,73 +841,90 @@ class Message {
/**
* @since 1.17
- * @param $value
- * @return array
+ *
+ * @param mixed $raw
+ *
+ * @return array Array with a single "raw" key.
*/
- public static function rawParam( $value ) {
- return array( 'raw' => $value );
+ public static function rawParam( $raw ) {
+ return array( 'raw' => $raw );
}
/**
* @since 1.18
- * @param $value
- * @return array
+ *
+ * @param mixed $num
+ *
+ * @return array Array with a single "num" key.
*/
- public static function numParam( $value ) {
- return array( 'num' => $value );
+ public static function numParam( $num ) {
+ return array( 'num' => $num );
}
/**
* @since 1.22
- * @param $value
- * @return array
+ *
+ * @param int $duration
+ *
+ * @return int[] Array with a single "duration" key.
*/
- public static function durationParam( $value ) {
- return array( 'duration' => $value );
+ public static function durationParam( $duration ) {
+ return array( 'duration' => $duration );
}
/**
* @since 1.22
- * @param $value
- * @return array
+ *
+ * @param string $expiry
+ *
+ * @return string[] Array with a single "expiry" key.
*/
- public static function expiryParam( $value ) {
- return array( 'expiry' => $value );
+ public static function expiryParam( $expiry ) {
+ return array( 'expiry' => $expiry );
}
/**
* @since 1.22
- * @param $value
- * @return array
+ *
+ * @param number $period
+ *
+ * @return number[] Array with a single "period" key.
*/
- public static function timeperiodParam( $value ) {
- return array( 'period' => $value );
+ public static function timeperiodParam( $period ) {
+ return array( 'period' => $period );
}
/**
* @since 1.22
- * @param $value
- * @return array
+ *
+ * @param int $size
+ *
+ * @return int[] Array with a single "size" key.
*/
- public static function sizeParam( $value ) {
- return array( 'size' => $value );
+ public static function sizeParam( $size ) {
+ return array( 'size' => $size );
}
/**
* @since 1.22
- * @param $value
- * @return array
+ *
+ * @param int $bitrate
+ *
+ * @return int[] Array with a single "bitrate" key.
*/
- public static function bitrateParam( $value ) {
- return array( 'bitrate' => $value );
+ public static function bitrateParam( $bitrate ) {
+ return array( 'bitrate' => $bitrate );
}
/**
* Substitutes any parameters into the message text.
+ *
* @since 1.17
- * @param string $message the message text
- * @param string $type either before or after
- * @return String
+ *
+ * @param string $message The message text.
+ * @param string $type Either "before" or "after".
+ *
+ * @return string
*/
protected function replaceParameters( $message, $type = 'before' ) {
$replacementKeys = array();
@@ -794,9 +940,12 @@ class Message {
/**
* Extracts the parameter type and preprocessed the value if needed.
+ *
* @since 1.18
- * @param string|array $param Parameter as defined in this class.
- * @return Tuple(type, value)
+ *
+ * @param mixed $param Parameter as defined in this class.
+ *
+ * @return array Array with the parameter type (either "before" or "after") and the value.
*/
protected function extractParam( $param ) {
if ( is_array( $param ) ) {
@@ -817,10 +966,12 @@ class Message {
} elseif ( isset( $param['bitrate'] ) ) {
return array( 'before', $this->language->formatBitrate( $param['bitrate'] ) );
} else {
- trigger_error(
- "Invalid message parameter: " . htmlspecialchars( serialize( $param ) ),
- E_USER_WARNING
- );
+ $warning = 'Invalid parameter for message "' . $this->getKey() . '": ' .
+ htmlspecialchars( serialize( $param ) );
+ trigger_error( $warning, E_USER_WARNING );
+ $e = new Exception;
+ wfDebugLog( 'Bug58676', $warning . "\n" . $e->getTraceAsString() );
+
return array( 'before', '[INVALID]' );
}
} elseif ( $param instanceof Message ) {
@@ -835,48 +986,66 @@ class Message {
/**
* Wrapper for what ever method we use to parse wikitext.
+ *
* @since 1.17
- * @param string $string Wikitext message contents
- * @return string Wikitext parsed into HTML
+ *
+ * @param string $string Wikitext message contents.
+ *
+ * @return string Wikitext parsed into HTML.
*/
protected function parseText( $string ) {
- $out = MessageCache::singleton()->parse( $string, $this->title, /*linestart*/true, $this->interface, $this->language );
- return is_object( $out ) ? $out->getText() : $out;
+ $out = MessageCache::singleton()->parse(
+ $string,
+ $this->title,
+ /*linestart*/true,
+ $this->interface,
+ $this->language
+ );
+
+ return $out instanceof ParserOutput ? $out->getText() : $out;
}
/**
* Wrapper for what ever method we use to {{-transform wikitext.
+ *
* @since 1.17
- * @param string $string Wikitext message contents
+ *
+ * @param string $string Wikitext message contents.
+ *
* @return string Wikitext with {{-constructs replaced with their values.
*/
protected function transformText( $string ) {
- return MessageCache::singleton()->transform( $string, $this->interface, $this->language, $this->title );
+ return MessageCache::singleton()->transform(
+ $string,
+ $this->interface,
+ $this->language,
+ $this->title
+ );
}
/**
- * Wrapper for what ever method we use to get message contents
+ * Wrapper for what ever method we use to get message contents.
+ *
* @since 1.17
- * @throws MWException
+ *
* @return string
+ * @throws MWException If message key array is empty.
*/
protected function fetchMessage() {
- if ( !isset( $this->message ) ) {
+ if ( $this->message === null ) {
$cache = MessageCache::singleton();
- if ( is_array( $this->key ) ) {
- if ( !count( $this->key ) ) {
- throw new MWException( "Given empty message key array." );
- }
- foreach ( $this->key as $key ) {
- $message = $cache->get( $key, $this->useDatabase, $this->language );
- if ( $message !== false && $message !== '' ) {
- break;
- }
+
+ foreach ( $this->keysToTry as $key ) {
+ $message = $cache->get( $key, $this->useDatabase, $this->language );
+ if ( $message !== false && $message !== '' ) {
+ break;
}
- $this->message = $message;
- } else {
- $this->message = $cache->get( $this->key, $this->useDatabase, $this->language );
}
+
+ // NOTE: The constructor makes sure keysToTry isn't empty,
+ // so we know that $key and $message are initialized.
+ $this->key = $key;
+ $this->message = $message;
}
return $this->message;
}
@@ -897,18 +1066,27 @@ class Message {
* @since 1.21
*/
class RawMessage extends Message {
+
/**
* Call the parent constructor, then store the key as
* the message.
*
- * @param string $key Message to use
- * @param array $params Parameters for the message
* @see Message::__construct
+ *
+ * @param string $text Message to use.
+ * @param array $params Parameters for the message.
+ *
+ * @throws InvalidArgumentException
*/
- public function __construct( $key, $params = array() ) {
- parent::__construct( $key, $params );
+ public function __construct( $text, $params = array() ) {
+ if ( !is_string( $text ) ) {
+ throw new InvalidArgumentException( '$text must be a string' );
+ }
+
+ parent::__construct( $text, $params );
+
// The key is the message.
- $this->message = $key;
+ $this->message = $text;
}
/**
@@ -918,9 +1096,11 @@ class RawMessage extends Message {
*/
public function fetchMessage() {
// Just in case the message is unset somewhere.
- if ( !isset( $this->message ) ) {
+ if ( $this->message === null ) {
$this->message = $this->key;
}
+
return $this->message;
}
+
}
diff --git a/includes/MessageBlobStore.php b/includes/MessageBlobStore.php
index 8a8142b7..e3b4dbe8 100644
--- a/includes/MessageBlobStore.php
+++ b/includes/MessageBlobStore.php
@@ -32,28 +32,42 @@
* constituent messages or the resource itself is changed.
*/
class MessageBlobStore {
+ /**
+ * Get the singleton instance
+ *
+ * @since 1.24
+ * @return MessageBlobStore
+ */
+ public static function getInstance() {
+ static $instance = null;
+ if ( $instance === null ) {
+ $instance = new self;
+ }
+
+ return $instance;
+ }
/**
* Get the message blobs for a set of modules
*
- * @param $resourceLoader ResourceLoader object
+ * @param ResourceLoader $resourceLoader
* @param array $modules Array of module objects keyed by module name
* @param string $lang Language code
* @return array An array mapping module names to message blobs
*/
- public static function get( ResourceLoader $resourceLoader, $modules, $lang ) {
+ public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
wfProfileIn( __METHOD__ );
if ( !count( $modules ) ) {
wfProfileOut( __METHOD__ );
return array();
}
// Try getting from the DB first
- $blobs = self::getFromDB( $resourceLoader, array_keys( $modules ), $lang );
+ $blobs = $this->getFromDB( $resourceLoader, array_keys( $modules ), $lang );
// Generate blobs for any missing modules and store them in the DB
$missing = array_diff( array_keys( $modules ), array_keys( $blobs ) );
foreach ( $missing as $name ) {
- $blob = self::insertMessageBlob( $name, $modules[$name], $lang );
+ $blob = $this->insertMessageBlob( $name, $modules[$name], $lang );
if ( $blob ) {
$blobs[$name] = $blob;
}
@@ -68,13 +82,13 @@ class MessageBlobStore {
* present, it is not regenerated; instead, the preexisting blob
* is fetched and returned.
*
- * @param string $name module name
- * @param $module ResourceLoaderModule object
- * @param string $lang language code
+ * @param string $name Module name
+ * @param ResourceLoaderModule $module
+ * @param string $lang Language code
* @return mixed Message blob or false if the module has no messages
*/
- public static function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
- $blob = self::generateMessageBlob( $module, $lang );
+ public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
+ $blob = $this->generateMessageBlob( $module, $lang );
if ( !$blob ) {
return false;
@@ -125,12 +139,13 @@ class MessageBlobStore {
/**
* Update the message blob for a given module in a given language
*
- * @param string $name module name
- * @param $module ResourceLoaderModule object
- * @param string $lang language code
- * @return String Regenerated message blob, or null if there was no blob for the given module/language pair
+ * @param string $name Module name
+ * @param ResourceLoaderModule $module
+ * @param string $lang Language code
+ * @return string Regenerated message blob, or null if there was no blob for
+ * the given module/language pair.
*/
- public static function updateModule( $name, ResourceLoaderModule $module, $lang ) {
+ public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
$dbw = wfGetDB( DB_MASTER );
$row = $dbw->selectRow( 'msg_resource', 'mr_blob',
array( 'mr_resource' => $name, 'mr_lang' => $lang ),
@@ -142,7 +157,7 @@ class MessageBlobStore {
// Save the old and new blobs for later
$oldBlob = $row->mr_blob;
- $newBlob = self::generateMessageBlob( $module, $lang );
+ $newBlob = $this->generateMessageBlob( $module, $lang );
try {
$newRow = array(
@@ -195,9 +210,9 @@ class MessageBlobStore {
/**
* Update a single message in all message blobs it occurs in.
*
- * @param string $key message key
+ * @param string $key Message key
*/
- public static function updateMessage( $key ) {
+ public function updateMessage( $key ) {
try {
$dbw = wfGetDB( DB_MASTER );
@@ -206,7 +221,7 @@ class MessageBlobStore {
// in one iteration.
$updates = null;
do {
- $updates = self::getUpdatesForMessage( $key, $updates );
+ $updates = $this->getUpdatesForMessage( $key, $updates );
foreach ( $updates as $k => $update ) {
// Update the row on the condition that it
@@ -240,10 +255,11 @@ class MessageBlobStore {
}
}
- public static function clear() {
+ public function clear() {
// TODO: Give this some more thought
- // TODO: Is TRUNCATE better?
try {
+ // Not using TRUNCATE, because that needs extra permissions,
+ // which maybe not granted to the database user.
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'msg_resource', '*', __METHOD__ );
$dbw->delete( 'msg_resource_links', '*', __METHOD__ );
@@ -255,11 +271,11 @@ class MessageBlobStore {
/**
* Create an update queue for updateMessage()
*
- * @param string $key message key
- * @param array $prevUpdates updates queue to refresh or null to build a fresh update queue
- * @return Array: updates queue
+ * @param string $key Message key
+ * @param array $prevUpdates Updates queue to refresh or null to build a fresh update queue
+ * @return array Updates queue
*/
- private static function getUpdatesForMessage( $key, $prevUpdates = null ) {
+ private function getUpdatesForMessage( $key, $prevUpdates = null ) {
$dbw = wfGetDB( DB_MASTER );
if ( is_null( $prevUpdates ) ) {
@@ -296,7 +312,7 @@ class MessageBlobStore {
'resource' => $row->mr_resource,
'lang' => $row->mr_lang,
'timestamp' => $row->mr_timestamp,
- 'newBlob' => self::reencodeBlob( $row->mr_blob, $key, $row->mr_lang )
+ 'newBlob' => $this->reencodeBlob( $row->mr_blob, $key, $row->mr_lang )
);
}
@@ -306,12 +322,12 @@ class MessageBlobStore {
/**
* Reencode a message blob with the updated value for a message
*
- * @param string $blob message blob (JSON object)
- * @param string $key message key
- * @param string $lang language code
- * @return Message blob with $key replaced with its new value
+ * @param string $blob Message blob (JSON object)
+ * @param string $key Message key
+ * @param string $lang Language code
+ * @return string Message blob with $key replaced with its new value
*/
- private static function reencodeBlob( $blob, $key, $lang ) {
+ private function reencodeBlob( $blob, $key, $lang ) {
$decoded = FormatJson::decode( $blob, true );
$decoded[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
@@ -322,14 +338,14 @@ class MessageBlobStore {
* Get the message blobs for a set of modules from the database.
* Modules whose blobs are not in the database are silently dropped.
*
- * @param $resourceLoader ResourceLoader object
- * @param array $modules of module names
- * @param string $lang language code
+ * @param ResourceLoader $resourceLoader
+ * @param array $modules Array of module names
+ * @param string $lang Language code
* @throws MWException
* @return array Array mapping module names to blobs
*/
- private static function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
- global $wgCacheEpoch;
+ private function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
+ $config = $resourceLoader->getConfig();
$retval = array();
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'msg_resource',
@@ -344,11 +360,15 @@ class MessageBlobStore {
// This shouldn't be possible
throw new MWException( __METHOD__ . ' passed an invalid module name' );
}
+
// Update the module's blobs if the set of messages changed or if the blob is
- // older than $wgCacheEpoch
- if ( array_keys( FormatJson::decode( $row->mr_blob, true ) ) !== array_values( array_unique( $module->getMessages() ) ) ||
- wfTimestamp( TS_MW, $row->mr_timestamp ) <= $wgCacheEpoch ) {
- $retval[$row->mr_resource] = self::updateModule( $row->mr_resource, $module, $lang );
+ // older than the CacheEpoch setting
+ $keys = array_keys( FormatJson::decode( $row->mr_blob, true ) );
+ $values = array_values( array_unique( $module->getMessages() ) );
+ if ( $keys !== $values
+ || wfTimestamp( TS_MW, $row->mr_timestamp ) <= $config->get( 'CacheEpoch' )
+ ) {
+ $retval[$row->mr_resource] = $this->updateModule( $row->mr_resource, $module, $lang );
} else {
$retval[$row->mr_resource] = $row->mr_blob;
}
@@ -360,11 +380,11 @@ class MessageBlobStore {
/**
* Generate the message blob for a given module in a given language.
*
- * @param $module ResourceLoaderModule object
- * @param string $lang language code
- * @return String: JSON object
+ * @param ResourceLoaderModule $module
+ * @param string $lang Language code
+ * @return string JSON object
*/
- private static function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
+ private function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
$messages = array();
foreach ( $module->getMessages() as $key ) {
diff --git a/includes/Metadata.php b/includes/Metadata.php
deleted file mode 100644
index 37df489c..00000000
--- a/includes/Metadata.php
+++ /dev/null
@@ -1,204 +0,0 @@
-<?php
-/**
- * Base code to format metadata.
- *
- * Copyright 2004, Evan Prodromou <evan@wikitravel.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
- *
- * @author Evan Prodromou <evan@wikitravel.org>
- * @file
- */
-
-abstract class RdfMetaData {
- const RDF_TYPE_PREFS = 'application/rdf+xml,text/xml;q=0.7,application/xml;q=0.5,text/rdf;q=0.1';
-
- /**
- * Constructor
- * @param $article Article object
- */
- public function __construct( Page $article ) {
- $this->mArticle = $article;
- }
-
- abstract public function show();
-
- protected function setup() {
- global $wgOut, $wgRequest;
-
- $httpaccept = isset( $_SERVER['HTTP_ACCEPT'] ) ? $_SERVER['HTTP_ACCEPT'] : null;
- $rdftype = wfNegotiateType( wfAcceptToPrefs( $httpaccept ), wfAcceptToPrefs( self::RDF_TYPE_PREFS ) );
-
- if ( !$rdftype ) {
- throw new HttpError( 406, wfMessage( 'notacceptable' ) );
- }
-
- $wgOut->disable();
- $wgRequest->response()->header( "Content-type: {$rdftype}; charset=utf-8" );
- $wgOut->sendCacheControl();
- return true;
- }
-
- protected function reallyFullUrl() {
- return $this->mArticle->getTitle()->getFullURL();
- }
-
- protected function basics() {
- global $wgLanguageCode, $wgSitename;
-
- $this->element( 'title', $this->mArticle->getTitle()->getText() );
- $this->pageOrString( 'publisher', wfMessage( 'aboutpage' )->text(), $wgSitename );
- $this->element( 'language', $wgLanguageCode );
- $this->element( 'type', 'Text' );
- $this->element( 'format', 'text/html' );
- $this->element( 'identifier', $this->reallyFullUrl() );
- $this->element( 'date', $this->date( $this->mArticle->getTimestamp() ) );
-
- $lastEditor = User::newFromId( $this->mArticle->getUser() );
- $this->person( 'creator', $lastEditor );
-
- foreach ( $this->mArticle->getContributors() as $user ) {
- $this->person( 'contributor', $user );
- }
-
- $this->rights();
- }
-
- protected function element( $name, $value ) {
- $value = htmlspecialchars( $value );
- print "\t\t<dc:{$name}>{$value}</dc:{$name}>\n";
- }
-
- protected function date( $timestamp ) {
- return substr( $timestamp, 0, 4 ) . '-'
- . substr( $timestamp, 4, 2 ) . '-'
- . substr( $timestamp, 6, 2 );
- }
-
- protected function pageOrString( $name, $page, $str ) {
- if ( $page instanceof Title ) {
- $nt = $page;
- } else {
- $nt = Title::newFromText( $page );
- }
-
- if ( !$nt || $nt->getArticleID() == 0 ) {
- $this->element( $name, $str );
- } else {
- $this->page( $name, $nt );
- }
- }
-
- /**
- * @param $name string
- * @param $title Title
- */
- protected function page( $name, $title ) {
- $this->url( $name, $title->getFullURL() );
- }
-
- protected function url( $name, $url ) {
- $url = htmlspecialchars( $url );
- print "\t\t<dc:{$name} rdf:resource=\"{$url}\" />\n";
- }
-
- protected function person( $name, User $user ) {
- global $wgHiddenPrefs;
-
- if ( $user->isAnon() ) {
- $this->element( $name, wfMessage( 'anonymous' )->numParams( 1 )->text() );
- } else {
- $real = $user->getRealName();
- if ( $real && !in_array( 'realname', $wgHiddenPrefs ) ) {
- $this->element( $name, $real );
- } else {
- $userName = $user->getName();
- $this->pageOrString(
- $name,
- $user->getUserPage(),
- wfMessage( 'siteuser', $userName, $userName )->text()
- );
- }
- }
- }
-
- /**
- * Takes an arg, for future enhancement with different rights for
- * different pages.
- */
- protected function rights() {
- global $wgRightsPage, $wgRightsUrl, $wgRightsText;
-
- if ( $wgRightsPage && ( $nt = Title::newFromText( $wgRightsPage ) )
- && ( $nt->getArticleID() != 0 ) ) {
- $this->page( 'rights', $nt );
- } elseif ( $wgRightsUrl ) {
- $this->url( 'rights', $wgRightsUrl );
- } elseif ( $wgRightsText ) {
- $this->element( 'rights', $wgRightsText );
- }
- }
-
- protected function getTerms( $url ) {
- global $wgLicenseTerms;
-
- if ( $wgLicenseTerms ) {
- return $wgLicenseTerms;
- } else {
- $known = $this->getKnownLicenses();
- if ( isset( $known[$url] ) ) {
- return $known[$url];
- } else {
- return array();
- }
- }
- }
-
- protected function getKnownLicenses() {
- $ccLicenses = array( 'by', 'by-nd', 'by-nd-nc', 'by-nc',
- 'by-nc-sa', 'by-sa' );
- $ccVersions = array( '1.0', '2.0' );
- $knownLicenses = array();
-
- foreach ( $ccVersions as $version ) {
- foreach ( $ccLicenses as $license ) {
- if ( $version == '2.0' && substr( $license, 0, 2 ) != 'by' ) {
- # 2.0 dropped the non-attribs licenses
- continue;
- }
- $lurl = "http://creativecommons.org/licenses/{$license}/{$version}/";
- $knownLicenses[$lurl] = explode( '-', $license );
- $knownLicenses[$lurl][] = 're';
- $knownLicenses[$lurl][] = 'di';
- $knownLicenses[$lurl][] = 'no';
- if ( !in_array( 'nd', $knownLicenses[$lurl] ) ) {
- $knownLicenses[$lurl][] = 'de';
- }
- }
- }
-
- /* Handle the GPL and LGPL, too. */
-
- $knownLicenses['http://creativecommons.org/licenses/GPL/2.0/'] =
- array( 'de', 're', 'di', 'no', 'sa', 'sc' );
- $knownLicenses['http://creativecommons.org/licenses/LGPL/2.1/'] =
- array( 'de', 're', 'di', 'no', 'sa', 'sc' );
- $knownLicenses['http://www.gnu.org/copyleft/fdl.html'] =
- array( 'de', 're', 'di', 'no', 'sa', 'sc' );
-
- return $knownLicenses;
- }
-}
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index 9180218b..bfd60111 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -1,6 +1,6 @@
<?php
/**
- * Module defining helper functions for detecting and dealing with mime types.
+ * Module defining helper functions for detecting and dealing with MIME types.
*
* 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
@@ -21,18 +21,18 @@
*/
/**
- * Defines a set of well known mime types
+ * Defines a set of well known MIME types
* This is used as a fallback to mime.types files.
- * An extensive list of well known mime types is provided by
+ * An extensive list of well known MIME types is provided by
* the file mime.types in the includes directory.
*
- * This list concatenated with mime.types is used to create a mime <-> ext
- * map. Each line contains a mime type followed by a space separated list of
- * extensions. If multiple extensions for a single mime type exist or if
- * multiple mime types exist for a single extension then in most cases
- * MediaWiki assumes that the first extension following the mime type is the
- * canonical extension, and the first time a mime type appears for a certain
- * extension is considered the canonical mime type.
+ * This list concatenated with mime.types is used to create a MIME <-> ext
+ * map. Each line contains a MIME type followed by a space separated list of
+ * extensions. If multiple extensions for a single MIME type exist or if
+ * multiple MIME types exist for a single extension then in most cases
+ * MediaWiki assumes that the first extension following the MIME type is the
+ * canonical extension, and the first time a MIME type appears for a certain
+ * extension is considered the canonical MIME type.
*
* (Note that appending $wgMimeTypeFile to the end of MM_WELL_KNOWN_MIME_TYPES
* sucks because you can't redefine canonical types. This could be fixed by
@@ -86,9 +86,9 @@ END_STRING
);
/**
- * Defines a set of well known mime info entries
+ * Defines a set of well known MIME info entries
* This is used as a fallback to mime.info files.
- * An extensive list of well known mime types is provided by
+ * An extensive list of well known MIME types is provided by
* the file mime.info in the includes directory.
*/
define( 'MM_WELL_KNOWN_MIME_INFO', <<<END_STRING
@@ -135,74 +135,99 @@ END_STRING
);
/**
- * Implements functions related to mime types such as detection and mapping to
+ * Implements functions related to MIME types such as detection and mapping to
* file extension.
*
* Instances of this class are stateless, there only needs to be one global instance
* of MimeMagic. Please use MimeMagic::singleton() to get that instance.
*/
class MimeMagic {
-
/**
- * Mapping of media types to arrays of mime types.
+ * @var array Mapping of media types to arrays of MIME types.
* This is used by findMediaType and getMediaType, respectively
*/
- var $mMediaTypes = null;
+ protected $mMediaTypes = null;
+
+ /** @var array Map of MIME type aliases
+ */
+ protected $mMimeTypeAliases = null;
- /** Map of mime type aliases
+ /** @var array Map of MIME types to file extensions (as a space separated list)
*/
- var $mMimeTypeAliases = null;
+ protected $mMimeToExt = null;
- /** map of mime types to file extensions (as a space separated list)
+ /** @var array Map of file extensions types to MIME types (as a space separated list)
*/
- var $mMimeToExt = null;
+ public $mExtToMime = null;
- /** map of file extensions types to mime types (as a space separated list)
+ /** @var IEContentAnalyzer
*/
- var $mExtToMime = null;
+ protected $mIEAnalyzer;
- /** IEContentAnalyzer instance
+ /** @var string Extra MIME types, set for example by media handling extensions
*/
- var $mIEAnalyzer;
+ private $mExtraTypes = '';
- /** The singleton instance
+ /** @var string Extra MIME info, set for example by media handling extensions
*/
- private static $instance;
+ private $mExtraInfo = '';
+
+ /** @var Config */
+ private $mConfig;
+
+ /** @var MimeMagic The singleton instance
+ */
+ private static $instance = null;
/** Initializes the MimeMagic object. This is called by MimeMagic::singleton().
*
* This constructor parses the mime.types and mime.info files and build internal mappings.
+ *
+ * @todo Make this constructor private once everything uses the singleton instance
+ * @param Config $config
*/
- function __construct() {
+ function __construct( Config $config = null ) {
+ if ( !$config ) {
+ wfDebug( __METHOD__ . ' called with no Config instance passed to it' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+ $this->mConfig = $config;
+
/**
* --- load mime.types ---
*/
- global $wgMimeTypeFile, $IP;
+ global $IP;
+
+ # Allow media handling extensions adding MIME-types and MIME-info
+ wfRunHooks( 'MimeMagicInit', array( $this ) );
$types = MM_WELL_KNOWN_MIME_TYPES;
- if ( $wgMimeTypeFile == 'includes/mime.types' ) {
- $wgMimeTypeFile = "$IP/$wgMimeTypeFile";
+ $mimeTypeFile = $this->mConfig->get( 'MimeTypeFile' );
+ if ( $mimeTypeFile == 'includes/mime.types' ) {
+ $mimeTypeFile = "$IP/$mimeTypeFile";
}
- if ( $wgMimeTypeFile ) {
- if ( is_file( $wgMimeTypeFile ) and is_readable( $wgMimeTypeFile ) ) {
- wfDebug( __METHOD__ . ": loading mime types from $wgMimeTypeFile\n" );
+ if ( $mimeTypeFile ) {
+ if ( is_file( $mimeTypeFile ) and is_readable( $mimeTypeFile ) ) {
+ wfDebug( __METHOD__ . ": loading mime types from $mimeTypeFile\n" );
$types .= "\n";
- $types .= file_get_contents( $wgMimeTypeFile );
+ $types .= file_get_contents( $mimeTypeFile );
} else {
- wfDebug( __METHOD__ . ": can't load mime types from $wgMimeTypeFile\n" );
+ wfDebug( __METHOD__ . ": can't load mime types from $mimeTypeFile\n" );
}
} else {
wfDebug( __METHOD__ . ": no mime types file defined, using build-ins only.\n" );
}
+ $types .= "\n" . $this->mExtraTypes;
+
$types = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $types );
$types = str_replace( "\t", " ", $types );
$this->mMimeToExt = array();
- $this->mToMime = array();
+ $this->mExtToMime = array();
$lines = explode( "\n", $types );
foreach ( $lines as $s ) {
@@ -254,25 +279,27 @@ class MimeMagic {
* --- load mime.info ---
*/
- global $wgMimeInfoFile;
- if ( $wgMimeInfoFile == 'includes/mime.info' ) {
- $wgMimeInfoFile = "$IP/$wgMimeInfoFile";
+ $mimeInfoFile = $this->mConfig->get( 'MimeInfoFile' );
+ if ( $mimeInfoFile == 'includes/mime.info' ) {
+ $mimeInfoFile = "$IP/$mimeInfoFile";
}
$info = MM_WELL_KNOWN_MIME_INFO;
- if ( $wgMimeInfoFile ) {
- if ( is_file( $wgMimeInfoFile ) and is_readable( $wgMimeInfoFile ) ) {
- wfDebug( __METHOD__ . ": loading mime info from $wgMimeInfoFile\n" );
+ if ( $mimeInfoFile ) {
+ if ( is_file( $mimeInfoFile ) and is_readable( $mimeInfoFile ) ) {
+ wfDebug( __METHOD__ . ": loading mime info from $mimeInfoFile\n" );
$info .= "\n";
- $info .= file_get_contents( $wgMimeInfoFile );
+ $info .= file_get_contents( $mimeInfoFile );
} else {
- wfDebug( __METHOD__ . ": can't load mime info from $wgMimeInfoFile\n" );
+ wfDebug( __METHOD__ . ": can't load mime info from $mimeInfoFile\n" );
}
} else {
wfDebug( __METHOD__ . ": no mime info file defined, using build-ins only.\n" );
}
+ $info .= "\n" . $this->mExtraInfo;
+
$info = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $info );
$info = str_replace( "\t", " ", $info );
@@ -323,32 +350,54 @@ class MimeMagic {
if ( count( $m ) > 1 ) {
$main = $m[0];
- for ( $i = 1; $i < count( $m ); $i += 1 ) {
+ $mCount = count( $m );
+ for ( $i = 1; $i < $mCount; $i += 1 ) {
$mime = $m[$i];
$this->mMimeTypeAliases[$mime] = $main;
}
}
}
-
}
/**
* Get an instance of this class
* @return MimeMagic
*/
- public static function &singleton() {
+ public static function singleton() {
if ( self::$instance === null ) {
- self::$instance = new MimeMagic;
+ self::$instance = new MimeMagic(
+ ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+ );
}
return self::$instance;
}
/**
- * Returns a list of file extensions for a given mime type as a space
- * separated string or null if the mime type was unrecognized. Resolves
- * mime type aliases.
+ * Adds to the list mapping MIME to file extensions.
+ * As an extension author, you are encouraged to submit patches to
+ * MediaWiki's core to add new MIME types to mime.types.
+ * @param string $types
+ */
+ public function addExtraTypes( $types ) {
+ $this->mExtraTypes .= "\n" . $types;
+ }
+
+ /**
+ * Adds to the list mapping MIME to media type.
+ * As an extension author, you are encouraged to submit patches to
+ * MediaWiki's core to add new MIME info to mime.info.
+ * @param string $info
+ */
+ public function addExtraInfo( $info ) {
+ $this->mExtraInfo .= "\n" . $info;
+ }
+
+ /**
+ * Returns a list of file extensions for a given MIME type as a space
+ * separated string or null if the MIME type was unrecognized. Resolves
+ * MIME type aliases.
*
- * @param $mime string
+ * @param string $mime
* @return string|null
*/
public function getExtensionsForType( $mime ) {
@@ -359,7 +408,7 @@ class MimeMagic {
return $this->mMimeToExt[$mime];
}
- // Resolve the mime type to the canonical type
+ // Resolve the MIME type to the canonical type
if ( isset( $this->mMimeTypeAliases[$mime] ) ) {
$mime = $this->mMimeTypeAliases[$mime];
if ( isset( $this->mMimeToExt[$mime] ) ) {
@@ -371,10 +420,10 @@ class MimeMagic {
}
/**
- * Returns a list of mime types for a given file extension as a space
+ * Returns a list of MIME types for a given file extension as a space
* separated string or null if the extension was unrecognized.
*
- * @param $ext string
+ * @param string $ext
* @return string|null
*/
public function getTypesForExtension( $ext ) {
@@ -385,10 +434,10 @@ class MimeMagic {
}
/**
- * Returns a single mime type for a given file extension or null if unknown.
+ * Returns a single MIME type for a given file extension or null if unknown.
* This is always the first type from the list returned by getTypesForExtension($ext).
*
- * @param $ext string
+ * @param string $ext
* @return string|null
*/
public function guessTypesForExtension( $ext ) {
@@ -405,19 +454,19 @@ class MimeMagic {
}
/**
- * Tests if the extension matches the given mime type. Returns true if a
- * match was found, null if the mime type is unknown, and false if the
- * mime type is known but no matches where found.
+ * Tests if the extension matches the given MIME type. Returns true if a
+ * match was found, null if the MIME type is unknown, and false if the
+ * MIME type is known but no matches where found.
*
- * @param $extension string
- * @param $mime string
+ * @param string $extension
+ * @param string $mime
* @return bool|null
*/
public function isMatchingExtension( $extension, $mime ) {
$ext = $this->getExtensionsForType( $mime );
if ( !$ext ) {
- return null; // Unknown mime type
+ return null; // Unknown MIME type
}
$ext = explode( ' ', $ext );
@@ -427,10 +476,10 @@ class MimeMagic {
}
/**
- * Returns true if the mime type is known to represent an image format
+ * Returns true if the MIME type is known to represent an image format
* supported by the PHP GD library.
*
- * @param $mime string
+ * @param string $mime
*
* @return bool
*/
@@ -456,8 +505,9 @@ class MimeMagic {
* invalid uploads; if we can't identify the type we won't
* be able to say if it's invalid.
*
- * @todo Be more accurate when using fancy mime detector plugins;
+ * @todo Be more accurate when using fancy MIME detector plugins;
* right now this is the bare minimum getimagesize() list.
+ * @param string $extension
* @return bool
*/
function isRecognizableExtension( $extension ) {
@@ -480,23 +530,15 @@ class MimeMagic {
}
/**
- * Improves a mime type using the file extension. Some file formats are very generic,
- * so their mime type is not very meaningful. A more useful mime type can be derived
+ * Improves a MIME type using the file extension. Some file formats are very generic,
+ * so their MIME type is not very meaningful. A more useful MIME type can be derived
* by looking at the file extension. Typically, this method would be called on the
* result of guessMimeType().
*
- * Currently, this method does the following:
+ * @param string $mime The MIME type, typically guessed from a file's content.
+ * @param string $ext The file extension, as taken from the file name
*
- * If $mime is "unknown/unknown" and isRecognizableExtension( $ext ) returns false,
- * return the result of guessTypesForExtension($ext).
- *
- * If $mime is "application/x-opc+zip" and isMatchingExtension( $ext, $mime )
- * gives true, return the result of guessTypesForExtension($ext).
- *
- * @param string $mime the mime type, typically guessed from a file's content.
- * @param string $ext the file extension, as taken from the file name
- *
- * @return string the mime type
+ * @return string The MIME type
*/
public function improveTypeFromExtension( $mime, $ext ) {
if ( $mime === 'unknown/unknown' ) {
@@ -508,19 +550,27 @@ class MimeMagic {
// trust the file extension
$mime = $this->guessTypesForExtension( $ext );
}
- }
- elseif ( $mime === 'application/x-opc+zip' ) {
+ } elseif ( $mime === 'application/x-opc+zip' ) {
if ( $this->isMatchingExtension( $ext, $mime ) ) {
// A known file extension for an OPC file,
- // find the proper mime type for that file extension
+ // find the proper MIME type for that file extension
$mime = $this->guessTypesForExtension( $ext );
} else {
wfDebug( __METHOD__ . ": refusing to guess better type for $mime file, " .
".$ext is not a known OPC extension.\n" );
$mime = 'application/zip';
}
+ } elseif ( $mime === 'text/plain' && $this->findMediaType( ".$ext" ) === MEDIATYPE_TEXT ) {
+ // Textual types are sometimes not recognized properly.
+ // If detected as text/plain, and has an extension which is textual
+ // improve to the extension's type. For example, csv and json are often
+ // misdetected as text/plain.
+ $mime = $this->guessTypesForExtension( $ext );
}
+ # Media handling extensions can improve the MIME detected
+ wfRunHooks( 'MimeMagicImproveFromExtension', array( $this, $ext, &$mime ) );
+
if ( isset( $this->mMimeTypeAliases[$mime] ) ) {
$mime = $this->mMimeTypeAliases[$mime];
}
@@ -530,18 +580,18 @@ class MimeMagic {
}
/**
- * Mime type detection. This uses detectMimeType to detect the mime type
+ * MIME type detection. This uses detectMimeType to detect the MIME type
* of the file, but applies additional checks to determine some well known
- * file formats that may be missed or misinterpreted by the default mime
+ * file formats that may be missed or misinterpreted by the default MIME
* detection (namely XML based formats like XHTML or SVG, as well as ZIP
* based formats like OPC/ODF files).
*
- * @param string $file the file to check
- * @param $ext Mixed: the file extension, or true (default) to extract it from the filename.
- * Set it to false to ignore the extension. DEPRECATED! Set to false, use
- * improveTypeFromExtension($mime, $ext) later to improve mime type.
+ * @param string $file The file to check
+ * @param string|bool $ext The file extension, or true (default) to extract it from the filename.
+ * Set it to false to ignore the extension. DEPRECATED! Set to false, use
+ * improveTypeFromExtension($mime, $ext) later to improve MIME type.
*
- * @return string the mime type of $file
+ * @return string The MIME type of $file
*/
public function guessMimeType( $file, $ext = true ) {
if ( $ext ) { // TODO: make $ext default to false. Or better, remove it.
@@ -565,7 +615,7 @@ class MimeMagic {
}
/**
- * Guess the mime type from the file contents.
+ * Guess the MIME type from the file contents.
*
* @param string $file
* @param mixed $ext
@@ -676,9 +726,9 @@ class MimeMagic {
*/
$xml = new XmlTypeCheck( $file );
if ( $xml->wellFormed ) {
- global $wgXMLMimeTypes;
- if ( isset( $wgXMLMimeTypes[$xml->getRootElement()] ) ) {
- return $wgXMLMimeTypes[$xml->getRootElement()];
+ $xmlMimeTypes = $this->mConfig->get( 'XMLMimeTypes' );
+ if ( isset( $xmlMimeTypes[$xml->getRootElement()] ) ) {
+ return $xmlMimeTypes[$xml->getRootElement()];
} else {
return 'application/xml';
}
@@ -747,7 +797,17 @@ class MimeMagic {
return 'image/vnd.djvu';
}
- return false;
+ # Media handling extensions can guess the MIME by content
+ # It's intentionally here so that if core is wrong about a type (false positive),
+ # people will hopefully nag and submit patches :)
+ $mime = false;
+ # Some strings by reference for performance - assuming well-behaved hooks
+ wfRunHooks(
+ 'MimeMagicGuessFromContent',
+ array( $this, &$head, &$tail, $file, &$mime )
+ );
+
+ return $mime;
}
/**
@@ -755,11 +815,11 @@ class MimeMagic {
* header data. Currently works for OpenDocument and OpenXML types...
* If can't tell, returns 'application/zip'.
*
- * @param string $header some reasonably-sized chunk of file header
- * @param $tail String: the tail of the file
- * @param $ext Mixed: the file extension, or true to extract it from the filename.
- * Set it to false (default) to ignore the extension. DEPRECATED! Set to false,
- * use improveTypeFromExtension($mime, $ext) later to improve mime type.
+ * @param string $header Some reasonably-sized chunk of file header
+ * @param string|null $tail The tail of the file
+ * @param string|bool $ext The file extension, or true to extract it from the filename.
+ * Set it to false (default) to ignore the extension. DEPRECATED! Set to false,
+ * use improveTypeFromExtension($mime, $ext) later to improve MIME type.
*
* @return string
*/
@@ -802,7 +862,7 @@ class MimeMagic {
# TODO: remove the block below, as soon as improveTypeFromExtension is used everywhere
if ( $ext !== true && $ext !== false ) {
/** This is the mode used by getPropsFromPath
- * These mime's are stored in the database, where we don't really want
+ * These MIME's are stored in the database, where we don't really want
* x-opc+zip, because we use it only for internal purposes
*/
if ( $this->isMatchingExtension( $ext, $mime ) ) {
@@ -851,47 +911,37 @@ class MimeMagic {
}
/**
- * Internal mime type detection. Detection is done using an external
+ * Internal MIME type detection. Detection is done using an external
* program, if $wgMimeDetectorCommand is set. Otherwise, the fileinfo
- * extension and mime_content_type are tried (in this order), if they
- * are available. If the detections fails and $ext is not false, the mime
- * type is guessed from the file extension, using guessTypesForExtension.
+ * extension is tried if it is available. If detection fails and $ext
+ * is not false, the MIME type is guessed from the file extension,
+ * using guessTypesForExtension.
*
- * If the mime type is still unknown, getimagesize is used to detect the
- * mime type if the file is an image. If no mime type can be determined,
+ * If the MIME type is still unknown, getimagesize is used to detect the
+ * MIME type if the file is an image. If no MIME type can be determined,
* this function returns 'unknown/unknown'.
*
- * @param string $file the file to check
- * @param $ext Mixed: the file extension, or true (default) to extract it from the filename.
- * Set it to false to ignore the extension. DEPRECATED! Set to false, use
- * improveTypeFromExtension($mime, $ext) later to improve mime type.
+ * @param string $file The file to check
+ * @param string|bool $ext The file extension, or true (default) to extract it from the filename.
+ * Set it to false to ignore the extension. DEPRECATED! Set to false, use
+ * improveTypeFromExtension($mime, $ext) later to improve MIME type.
*
- * @return string the mime type of $file
+ * @return string The MIME type of $file
*/
private function detectMimeType( $file, $ext = true ) {
- global $wgMimeDetectorCommand;
-
- if ( $ext ) { # TODO: make $ext default to false. Or better, remove it.
- wfDebug( __METHOD__ . ": WARNING: use of the \$ext parameter is deprecated. Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
+ /** @todo Make $ext default to false. Or better, remove it. */
+ if ( $ext ) {
+ wfDebug( __METHOD__ . ": WARNING: use of the \$ext parameter is deprecated. "
+ . "Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
}
+ $mimeDetectorCommand = $this->mConfig->get( 'MimeDetectorCommand' );
$m = null;
- if ( $wgMimeDetectorCommand ) {
+ if ( $mimeDetectorCommand ) {
$args = wfEscapeShellArg( $file );
- $m = wfShellExec( "$wgMimeDetectorCommand $args" );
+ $m = wfShellExec( "$mimeDetectorCommand $args" );
} elseif ( function_exists( "finfo_open" ) && function_exists( "finfo_file" ) ) {
-
- # This required the fileinfo extension by PECL,
- # see http://pecl.php.net/package/fileinfo
- # This must be compiled into PHP
- #
- # finfo is the official replacement for the deprecated
- # mime_content_type function, see below.
- #
- # If you may need to load the fileinfo extension at runtime, set
- # $wgLoadFileinfoExtension in LocalSettings.php
-
- $mime_magic_resource = finfo_open( FILEINFO_MIME ); /* return mime type ala mimetype extension */
+ $mime_magic_resource = finfo_open( FILEINFO_MIME );
if ( $mime_magic_resource ) {
$m = finfo_file( $mime_magic_resource, $file );
@@ -899,18 +949,6 @@ class MimeMagic {
} else {
wfDebug( __METHOD__ . ": finfo_open failed on " . FILEINFO_MIME . "!\n" );
}
- } elseif ( function_exists( "mime_content_type" ) ) {
-
- # NOTE: this function is available since PHP 4.3.0, but only if
- # PHP was compiled with --with-mime-magic or, before 4.3.2, with --enable-mime-magic.
- #
- # On Windows, you must set mime_magic.magicfile in php.ini to point to the mime.magic file bundled with PHP;
- # sometimes, this may even be needed under linus/unix.
- #
- # Also note that this has been DEPRECATED in favor of the fileinfo extension by PECL, see above.
- # see http://www.php.net/manual/en/ref.mime-magic.php for details.
-
- $m = mime_content_type( $file );
} else {
wfDebug( __METHOD__ . ": no magic mime detector found!\n" );
}
@@ -936,7 +974,8 @@ class MimeMagic {
}
if ( $ext ) {
if ( $this->isRecognizableExtension( $ext ) ) {
- wfDebug( __METHOD__ . ": refusing to guess mime type for .$ext file, we should have recognized it\n" );
+ wfDebug( __METHOD__ . ": refusing to guess mime type for .$ext file, "
+ . "we should have recognized it\n" );
} else {
$m = $this->guessTypesForExtension( $ext );
if ( $m ) {
@@ -952,27 +991,27 @@ class MimeMagic {
}
/**
- * Determine the media type code for a file, using its mime type, name and
+ * Determine the media type code for a file, using its MIME type, name and
* possibly its contents.
*
- * This function relies on the findMediaType(), mapping extensions and mime
+ * This function relies on the findMediaType(), mapping extensions and MIME
* types to media types.
*
* @todo analyse file if need be
* @todo look at multiple extension, separately and together.
*
- * @param string $path full path to the image file, in case we have to look at the contents
- * (if null, only the mime type is used to determine the media type code).
- * @param string $mime mime type. If null it will be guessed using guessMimeType.
+ * @param string $path Full path to the image file, in case we have to look at the contents
+ * (if null, only the MIME type is used to determine the media type code).
+ * @param string $mime MIME type. If null it will be guessed using guessMimeType.
*
- * @return (int?string?) a value to be used with the MEDIATYPE_xxx constants.
+ * @return string A value to be used with the MEDIATYPE_xxx constants.
*/
function getMediaType( $path = null, $mime = null ) {
if ( !$mime && !$path ) {
return MEDIATYPE_UNKNOWN;
}
- // If mime type is unknown, guess it
+ // If MIME type is unknown, guess it
if ( !$mime ) {
$mime = $this->guessMimeType( $path, false );
}
@@ -989,7 +1028,7 @@ class MimeMagic {
$head = fread( $f, 256 );
fclose( $f );
- $head = strtolower( $head );
+ $head = str_replace( 'ffmpeg2theora', '', strtolower( $head ) );
// This is an UGLY HACK, file should be parsed correctly
if ( strpos( $head, 'theora' ) !== false ) {
@@ -1005,7 +1044,7 @@ class MimeMagic {
}
}
- // Check for entry for full mime type
+ // Check for entry for full MIME type
if ( $mime ) {
$type = $this->findMediaType( $mime );
if ( $type !== MEDIATYPE_UNKNOWN ) {
@@ -1025,7 +1064,7 @@ class MimeMagic {
}
}
- // Check major mime type
+ // Check major MIME type
if ( $mime ) {
$i = strpos( $mime, '/' );
if ( $i !== false ) {
@@ -1045,17 +1084,18 @@ class MimeMagic {
}
/**
- * Returns a media code matching the given mime type or file extension.
+ * Returns a media code matching the given MIME type or file extension.
* File extensions are represented by a string starting with a dot (.) to
- * distinguish them from mime types.
+ * distinguish them from MIME types.
*
* This function relies on the mapping defined by $this->mMediaTypes
* @access private
+ * @param string $extMime
* @return int|string
*/
function findMediaType( $extMime ) {
if ( strpos( $extMime, '.' ) === 0 ) {
- // If it's an extension, look up the mime types
+ // If it's an extension, look up the MIME types
$m = $this->getTypesForExtension( substr( $extMime, 1 ) );
if ( !$m ) {
return MEDIATYPE_UNKNOWN;
@@ -1063,7 +1103,7 @@ class MimeMagic {
$m = explode( ' ', $m );
} else {
- // Normalize mime type
+ // Normalize MIME type
if ( isset( $this->mMimeTypeAliases[$extMime] ) ) {
$extMime = $this->mMimeTypeAliases[$extMime];
}
@@ -1086,10 +1126,10 @@ class MimeMagic {
* Get the MIME types that various versions of Internet Explorer would
* detect from a chunk of the content.
*
- * @param string $fileName the file name (unused at present)
- * @param string $chunk the first 256 bytes of the file
- * @param string $proposed the MIME type proposed by the server
- * @return Array
+ * @param string $fileName The file name (unused at present)
+ * @param string $chunk The first 256 bytes of the file
+ * @param string $proposed The MIME type proposed by the server
+ * @return array
*/
public function getIEMimeTypes( $fileName, $chunk, $proposed ) {
$ca = $this->getIEContentAnalyzer();
diff --git a/includes/MovePage.php b/includes/MovePage.php
new file mode 100644
index 00000000..fdece8d5
--- /dev/null
+++ b/includes/MovePage.php
@@ -0,0 +1,343 @@
+<?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
+ */
+
+/**
+ * Handles the backend logic of moving a page from one title
+ * to another.
+ *
+ * @since 1.24
+ */
+class MovePage {
+
+ /**
+ * @var Title
+ */
+ protected $oldTitle;
+
+ /**
+ * @var Title
+ */
+ protected $newTitle;
+
+ public function __construct( Title $oldTitle, Title $newTitle ) {
+ $this->oldTitle = $oldTitle;
+ $this->newTitle = $newTitle;
+ }
+
+ /**
+ * @param User $user
+ * @param string $reason
+ * @param bool $createRedirect
+ * @return Status
+ */
+ public function move( User $user, $reason, $createRedirect ) {
+ global $wgCategoryCollation;
+
+ // If it is a file, move it first.
+ // It is done before all other moving stuff is done because it's hard to revert.
+ $dbw = wfGetDB( DB_MASTER );
+ if ( $this->oldTitle->getNamespace() == NS_FILE ) {
+ $file = wfLocalFile( $this->oldTitle );
+ if ( $file->exists() ) {
+ $status = $file->move( $this->newTitle );
+ if ( !$status->isOk() ) {
+ return $status;
+ }
+ }
+ // Clear RepoGroup process cache
+ RepoGroup::singleton()->clearCache( $this->oldTitle );
+ RepoGroup::singleton()->clearCache( $this->newTitle ); # clear false negative cache
+ }
+
+ $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
+ $pageid = $this->oldTitle->getArticleID( Title::GAID_FOR_UPDATE );
+ $protected = $this->oldTitle->isProtected();
+
+ // Do the actual move
+ $this->moveToInternal( $user, $this->newTitle, $reason, $createRedirect );
+
+ // Refresh the sortkey for this row. Be careful to avoid resetting
+ // cl_timestamp, which may disturb time-based lists on some sites.
+ // @todo This block should be killed, it's duplicating code
+ // from LinksUpdate::getCategoryInsertions() and friends.
+ $prefixes = $dbw->select(
+ 'categorylinks',
+ array( 'cl_sortkey_prefix', 'cl_to' ),
+ array( 'cl_from' => $pageid ),
+ __METHOD__
+ );
+ if ( $this->newTitle->getNamespace() == NS_CATEGORY ) {
+ $type = 'subcat';
+ } elseif ( $this->newTitle->getNamespace() == NS_FILE ) {
+ $type = 'file';
+ } else {
+ $type = 'page';
+ }
+ foreach ( $prefixes as $prefixRow ) {
+ $prefix = $prefixRow->cl_sortkey_prefix;
+ $catTo = $prefixRow->cl_to;
+ $dbw->update( 'categorylinks',
+ array(
+ 'cl_sortkey' => Collation::singleton()->getSortKey(
+ $this->newTitle->getCategorySortkey( $prefix ) ),
+ 'cl_collation' => $wgCategoryCollation,
+ 'cl_type' => $type,
+ 'cl_timestamp=cl_timestamp' ),
+ array(
+ 'cl_from' => $pageid,
+ 'cl_to' => $catTo ),
+ __METHOD__
+ );
+ }
+
+ $redirid = $this->oldTitle->getArticleID();
+
+ if ( $protected ) {
+ # Protect the redirect title as the title used to be...
+ $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
+ array(
+ 'pr_page' => $redirid,
+ 'pr_type' => 'pr_type',
+ 'pr_level' => 'pr_level',
+ 'pr_cascade' => 'pr_cascade',
+ 'pr_user' => 'pr_user',
+ 'pr_expiry' => 'pr_expiry'
+ ),
+ array( 'pr_page' => $pageid ),
+ __METHOD__,
+ array( 'IGNORE' )
+ );
+ # Update the protection log
+ $log = new LogPage( 'protect' );
+ $comment = wfMessage(
+ 'prot_1movedto2',
+ $this->oldTitle->getPrefixedText(),
+ $this->newTitle->getPrefixedText()
+ )->inContentLanguage()->text();
+ if ( $reason ) {
+ $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
+ }
+ // @todo FIXME: $params?
+ $logId = $log->addEntry(
+ 'move_prot',
+ $this->newTitle,
+ $comment,
+ array( $this->oldTitle->getPrefixedText() ),
+ $user
+ );
+
+ // reread inserted pr_ids for log relation
+ $insertedPrIds = $dbw->select(
+ 'page_restrictions',
+ 'pr_id',
+ array( 'pr_page' => $redirid ),
+ __METHOD__
+ );
+ $logRelationsValues = array();
+ foreach ( $insertedPrIds as $prid ) {
+ $logRelationsValues[] = $prid->pr_id;
+ }
+ $log->addRelations( 'pr_id', $logRelationsValues, $logId );
+ }
+
+ // Update *_from_namespace fields as needed
+ if ( $this->oldTitle->getNamespace() != $this->newTitle->getNamespace() ) {
+ $dbw->update( 'pagelinks',
+ array( 'pl_from_namespace' => $this->newTitle->getNamespace() ),
+ array( 'pl_from' => $pageid ),
+ __METHOD__
+ );
+ $dbw->update( 'templatelinks',
+ array( 'tl_from_namespace' => $this->newTitle->getNamespace() ),
+ array( 'tl_from' => $pageid ),
+ __METHOD__
+ );
+ $dbw->update( 'imagelinks',
+ array( 'il_from_namespace' => $this->newTitle->getNamespace() ),
+ array( 'il_from' => $pageid ),
+ __METHOD__
+ );
+ }
+
+ # Update watchlists
+ $oldtitle = $this->oldTitle->getDBkey();
+ $newtitle = $this->newTitle->getDBkey();
+ $oldsnamespace = MWNamespace::getSubject( $this->oldTitle->getNamespace() );
+ $newsnamespace = MWNamespace::getSubject( $this->newTitle->getNamespace() );
+ if ( $oldsnamespace != $newsnamespace || $oldtitle != $newtitle ) {
+ WatchedItem::duplicateEntries( $this->oldTitle, $this->newTitle );
+ }
+
+ $dbw->commit( __METHOD__ );
+
+ wfRunHooks( 'TitleMoveComplete', array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) );
+ return Status::newGood();
+
+ }
+
+ /**
+ * Move page to a title which is either a redirect to the
+ * source page or nonexistent
+ *
+ * @fixme This was basically directly moved from Title, it should be split into smaller functions
+ * @param User $user the User doing the move
+ * @param Title $nt The page to move to, which should be a redirect or nonexistent
+ * @param string $reason The reason for the move
+ * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check
+ * if the user has the suppressredirect right
+ * @throws MWException
+ */
+ private function moveToInternal( User $user, &$nt, $reason = '', $createRedirect = true ) {
+ global $wgContLang;
+
+ if ( $nt->exists() ) {
+ $moveOverRedirect = true;
+ $logType = 'move_redir';
+ } else {
+ $moveOverRedirect = false;
+ $logType = 'move';
+ }
+
+ if ( $createRedirect ) {
+ if ( $this->oldTitle->getNamespace() == NS_CATEGORY
+ && !wfMessage( 'category-move-redirect-override' )->inContentLanguage()->isDisabled()
+ ) {
+ $redirectContent = new WikitextContent(
+ wfMessage( 'category-move-redirect-override' )
+ ->params( $nt->getPrefixedText() )->inContentLanguage()->plain() );
+ } else {
+ $contentHandler = ContentHandler::getForTitle( $this->oldTitle );
+ $redirectContent = $contentHandler->makeRedirectContent( $nt,
+ wfMessage( 'move-redirect-text' )->inContentLanguage()->plain() );
+ }
+
+ // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
+ } else {
+ $redirectContent = null;
+ }
+
+ // bug 57084: log_page should be the ID of the *moved* page
+ $oldid = $this->oldTitle->getArticleID();
+ $logTitle = clone $this->oldTitle;
+
+ $logEntry = new ManualLogEntry( 'move', $logType );
+ $logEntry->setPerformer( $user );
+ $logEntry->setTarget( $logTitle );
+ $logEntry->setComment( $reason );
+ $logEntry->setParameters( array(
+ '4::target' => $nt->getPrefixedText(),
+ '5::noredir' => $redirectContent ? '0': '1',
+ ) );
+
+ $formatter = LogFormatter::newFromEntry( $logEntry );
+ $formatter->setContext( RequestContext::newExtraneousContext( $this->oldTitle ) );
+ $comment = $formatter->getPlainActionText();
+ if ( $reason ) {
+ $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
+ }
+ # Truncate for whole multibyte characters.
+ $comment = $wgContLang->truncate( $comment, 255 );
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $newpage = WikiPage::factory( $nt );
+
+ if ( $moveOverRedirect ) {
+ $newid = $nt->getArticleID();
+ $newcontent = $newpage->getContent();
+
+ # Delete the old redirect. We don't save it to history since
+ # by definition if we've got here it's rather uninteresting.
+ # We have to remove it so that the next step doesn't trigger
+ # a conflict on the unique namespace+title index...
+ $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
+
+ $newpage->doDeleteUpdates( $newid, $newcontent );
+ }
+
+ # Save a null revision in the page's history notifying of the move
+ $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $user );
+ if ( !is_object( $nullRevision ) ) {
+ throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
+ }
+
+ $nullRevision->insertOn( $dbw );
+
+ # Change the name of the target page:
+ $dbw->update( 'page',
+ /* SET */ array(
+ 'page_namespace' => $nt->getNamespace(),
+ 'page_title' => $nt->getDBkey(),
+ ),
+ /* WHERE */ array( 'page_id' => $oldid ),
+ __METHOD__
+ );
+
+ // clean up the old title before reset article id - bug 45348
+ if ( !$redirectContent ) {
+ WikiPage::onArticleDelete( $this->oldTitle );
+ }
+
+ $this->oldTitle->resetArticleID( 0 ); // 0 == non existing
+ $nt->resetArticleID( $oldid );
+ $newpage->loadPageData( WikiPage::READ_LOCKING ); // bug 46397
+
+ $newpage->updateRevisionOn( $dbw, $nullRevision );
+
+ wfRunHooks( 'NewRevisionFromEditComplete',
+ array( $newpage, $nullRevision, $nullRevision->getParentId(), $user ) );
+
+ $newpage->doEditUpdates( $nullRevision, $user, array( 'changed' => false ) );
+
+ if ( !$moveOverRedirect ) {
+ WikiPage::onArticleCreate( $nt );
+ }
+
+ # Recreate the redirect, this time in the other direction.
+ if ( $redirectContent ) {
+ $redirectArticle = WikiPage::factory( $this->oldTitle );
+ $redirectArticle->loadFromRow( false, WikiPage::READ_LOCKING ); // bug 46397
+ $newid = $redirectArticle->insertOn( $dbw );
+ if ( $newid ) { // sanity
+ $this->oldTitle->resetArticleID( $newid );
+ $redirectRevision = new Revision( array(
+ 'title' => $this->oldTitle, // for determining the default content model
+ 'page' => $newid,
+ 'user_text' => $user->getName(),
+ 'user' => $user->getId(),
+ 'comment' => $comment,
+ 'content' => $redirectContent ) );
+ $redirectRevision->insertOn( $dbw );
+ $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
+
+ wfRunHooks( 'NewRevisionFromEditComplete',
+ array( $redirectArticle, $redirectRevision, false, $user ) );
+
+ $redirectArticle->doEditUpdates( $redirectRevision, $user, array( 'created' => true ) );
+ }
+ }
+
+ # Log the move
+ $logid = $logEntry->insert();
+ $logEntry->publish( $logid );
+ }
+
+} \ No newline at end of file
diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php
index 65bb86e7..b0bbcddb 100644
--- a/includes/OutputHandler.php
+++ b/includes/OutputHandler.php
@@ -23,7 +23,7 @@
/**
* Standard output handler for use with ob_start
*
- * @param $s string
+ * @param string $s
*
* @return string
*/
@@ -96,7 +96,7 @@ function wfRequestExtension() {
* Handler that compresses data with gzip if allowed by the Accept header.
* Unlike ob_gzhandler, it works for HEAD requests too.
*
- * @param $s string
+ * @param string $s
*
* @return string
*/
@@ -147,7 +147,7 @@ function wfGzipHandler( $s ) {
/**
* Mangle flash policy tags which open up the site to XSS attacks.
*
- * @param $s string
+ * @param string $s
*
* @return string
*/
@@ -163,10 +163,13 @@ function wfMangleFlashPolicy( $s ) {
/**
* Add a Content-Length header if possible. This makes it cooperate with squid better.
*
- * @param $length int
+ * @param int $length
*/
function wfDoContentLength( $length ) {
- if ( !headers_sent() && isset( $_SERVER['SERVER_PROTOCOL'] ) && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0' ) {
+ if ( !headers_sent()
+ && isset( $_SERVER['SERVER_PROTOCOL'] )
+ && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0'
+ ) {
header( "Content-Length: $length" );
}
}
@@ -174,7 +177,7 @@ function wfDoContentLength( $length ) {
/**
* Replace the output with an error if the HTML is not valid
*
- * @param $s string
+ * @param string $s
*
* @return string
*/
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index e6d4339f..2f8094ab 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -36,20 +36,31 @@
* @todo document
*/
class OutputPage extends ContextSource {
- /// Should be private. Used with addMeta() which adds "<meta>"
- var $mMetatags = array();
+ /** @var array Should be private. Used with addMeta() which adds "<meta>" */
+ protected $mMetatags = array();
- var $mLinktags = array();
- var $mCanonicalUrl = false;
+ /** @var array */
+ protected $mLinktags = array();
- /// Additional stylesheets. Looks like this is for extensions. Might be replaced by resource loader.
- var $mExtStyles = array();
+ /** @var bool */
+ protected $mCanonicalUrl = false;
- /// Should be private - has getter and setter. Contains the HTML title
- var $mPagetitle = '';
+ /**
+ * @var array Additional stylesheets. Looks like this is for extensions.
+ * Might be replaced by resource loader.
+ */
+ protected $mExtStyles = array();
- /// Contains all of the "<body>" content. Should be private we got set/get accessors and the append() method.
- var $mBodytext = '';
+ /**
+ * @var string Should be private - has getter and setter. Contains
+ * the HTML title */
+ public $mPagetitle = '';
+
+ /**
+ * @var string Contains all of the "<body>" content. Should be private we
+ * got set/get accessors and the append() method.
+ */
+ public $mBodytext = '';
/**
* Holds the debug lines that will be output as comments in page source if
@@ -58,43 +69,43 @@ class OutputPage extends ContextSource {
*/
public $mDebugtext = '';
- /// Should be private. Stores contents of "<title>" tag
- var $mHTMLtitle = '';
-
- /// Should be private. Is the displayed content related to the source of the corresponding wiki article.
- var $mIsarticle = false;
+ /** @var string Stores contents of "<title>" tag */
+ private $mHTMLtitle = '';
/**
- * Should be private. Has get/set methods properly documented.
- * Stores "article flag" toggle.
+ * @var bool Is the displayed content related to the source of the
+ * corresponding wiki article.
*/
- var $mIsArticleRelated = true;
+ private $mIsarticle = false;
+
+ /** @var bool Stores "article flag" toggle. */
+ private $mIsArticleRelated = true;
/**
- * Should be private. We have to set isPrintable(). Some pages should
+ * @var bool We have to set isPrintable(). Some pages should
* never be printed (ex: redirections).
*/
- var $mPrintable = false;
+ private $mPrintable = false;
/**
- * Should be private. We have set/get/append methods.
- *
- * Contains the page subtitle. Special pages usually have some links here.
- * Don't confuse with site subtitle added by skins.
+ * @var array Contains the page subtitle. Special pages usually have some
+ * links here. Don't confuse with site subtitle added by skins.
*/
private $mSubtitle = array();
- var $mRedirect = '';
- var $mStatusCode;
+ /** @var string */
+ public $mRedirect = '';
+
+ /** @var int */
+ protected $mStatusCode;
/**
- * mLastModified and mEtag are used for sending cache control.
- * The whole caching system should probably be moved into its own class.
+ * @var string Variable mLastModified and mEtag are used for sending cache control.
+ * The whole caching system should probably be moved into its own class.
*/
- var $mLastModified = '';
+ protected $mLastModified = '';
/**
- * Should be private. No getter but used in sendCacheControl();
* Contains an HTTP Entity Tags (see RFC 2616 section 3.13) which is used
* as a unique identifier for the content. It is later used by the client
* to compare its cached version with the server version. Client sends
@@ -103,53 +114,70 @@ class OutputPage extends ContextSource {
* To get more information, you will have to look at HTTP/1.1 protocol which
* is properly described in RFC 2616 : http://tools.ietf.org/html/rfc2616
*/
- var $mETag = false;
+ private $mETag = false;
- var $mCategoryLinks = array();
- var $mCategories = array();
+ /** @var array */
+ protected $mCategoryLinks = array();
- /// Should be private. Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page')
- var $mLanguageLinks = array();
+ /** @var array */
+ protected $mCategories = array();
+
+ /** @var array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page') */
+ private $mLanguageLinks = array();
/**
- * Should be private. Used for JavaScript (pre resource loader)
- * We should split js / css.
+ * Used for JavaScript (pre resource loader)
+ * @todo We should split JS / CSS.
* mScripts content is inserted as is in "<head>" by Skin. This might
- * contains either a link to a stylesheet or inline css.
+ * contain either a link to a stylesheet or inline CSS.
*/
- var $mScripts = '';
+ private $mScripts = '';
- /**
- * Inline CSS styles. Use addInlineStyle() sparingly
- */
- var $mInlineStyles = '';
+ /** @var string Inline CSS styles. Use addInlineStyle() sparingly */
+ protected $mInlineStyles = '';
- //
- var $mLinkColours;
+ /** @todo Unused? */
+ private $mLinkColours;
/**
- * Used by skin template.
+ * @var string Used by skin template.
* Example: $tpl->set( 'displaytitle', $out->mPageLinkTitle );
*/
- var $mPageLinkTitle = '';
+ public $mPageLinkTitle = '';
+
+ /** @var array Array of elements in "<head>". Parser might add its own headers! */
+ protected $mHeadItems = array();
+
+ // @todo FIXME: Next 5 variables probably come from the resource loader
+
+ /** @var array */
+ protected $mModules = array();
+
+ /** @var array */
+ protected $mModuleScripts = array();
+
+ /** @var array */
+ protected $mModuleStyles = array();
+
+ /** @var array */
+ protected $mModuleMessages = array();
- /// Array of elements in "<head>". Parser might add its own headers!
- var $mHeadItems = array();
+ /** @var ResourceLoader */
+ protected $mResourceLoader;
- // @todo FIXME: Next variables probably comes from the resource loader
- var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array();
- var $mResourceLoader;
- var $mJsConfigVars = array();
+ /** @var array */
+ protected $mJsConfigVars = array();
- /** @todo FIXME: Is this still used ?*/
- var $mInlineMsg = array();
+ /** @var array */
+ protected $mTemplateIds = array();
- var $mTemplateIds = array();
- var $mImageTimeKeys = array();
+ /** @var array */
+ protected $mImageTimeKeys = array();
- var $mRedirectCode = '';
+ /** @var string */
+ public $mRedirectCode = '';
- var $mFeedLinksAppendQuery = null;
+ protected $mFeedLinksAppendQuery = null;
/** @var array
* What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
@@ -160,14 +188,19 @@ class OutputPage extends ContextSource {
ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL,
);
+ /** @var bool Whether output is disabled. If this is true, the 'output' method will do nothing. */
+ protected $mDoNothing = false;
+
+ // Parser related.
+
/**
- * @EasterEgg I just love the name for this self documenting variable.
- * @todo document
+ * @var int
+ * @todo Unused?
*/
- var $mDoNothing = false;
+ private $mContainsOldMagic = 0;
- // Parser related.
- var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
+ /** @var int */
+ protected $mContainsNewMagic = 0;
/**
* lazy initialised, use parserOptions()
@@ -176,57 +209,64 @@ class OutputPage extends ContextSource {
protected $mParserOptions = null;
/**
- * Handles the atom / rss links.
- * We probably only support atom in 2011.
- * Looks like a private variable.
+ * Handles the Atom / RSS links.
+ * We probably only support Atom in 2011.
* @see $wgAdvertisedFeedTypes
*/
- var $mFeedLinks = array();
+ private $mFeedLinks = array();
// Gwicke work on squid caching? Roughly from 2003.
- var $mEnableClientCache = true;
+ protected $mEnableClientCache = true;
- /**
- * Flag if output should only contain the body of the article.
- * Should be private.
- */
- var $mArticleBodyOnly = false;
+ /** @var bool Flag if output should only contain the body of the article. */
+ private $mArticleBodyOnly = false;
+
+ /** @var bool */
+ protected $mNewSectionLink = false;
- var $mNewSectionLink = false;
- var $mHideNewSectionLink = false;
+ /** @var bool */
+ protected $mHideNewSectionLink = false;
/**
- * Comes from the parser. This was probably made to load CSS/JS only
- * if we had "<gallery>". Used directly in CategoryPage.php
+ * @var bool Comes from the parser. This was probably made to load CSS/JS
+ * only if we had "<gallery>". Used directly in CategoryPage.php.
* Looks like resource loader can replace this.
*/
- var $mNoGallery = false;
+ public $mNoGallery = false;
+
+ /** @var string */
+ private $mPageTitleActionText = '';
+
+ /** @var array */
+ private $mParseWarnings = array();
- // should be private.
- var $mPageTitleActionText = '';
- var $mParseWarnings = array();
+ /** @var int Cache stuff. Looks like mEnableClientCache */
+ protected $mSquidMaxage = 0;
- // Cache stuff. Looks like mEnableClientCache
- var $mSquidMaxage = 0;
+ /**
+ * @var bool
+ * @todo Document
+ */
+ protected $mPreventClickjacking = true;
- // @todo document
- var $mPreventClickjacking = true;
+ /** @var int To include the variable {{REVISIONID}} */
+ private $mRevisionId = null;
- /// should be private. To include the variable {{REVISIONID}}
- var $mRevisionId = null;
+ /** @var string */
private $mRevisionTimestamp = null;
- var $mFileVersion = null;
+ /** @var array */
+ protected $mFileVersion = null;
/**
- * An array of stylesheet filenames (relative from skins path), with options
- * for CSS media, IE conditions, and RTL/LTR direction.
+ * @var array An array of stylesheet filenames (relative from skins path),
+ * with options for CSS media, IE conditions, and RTL/LTR direction.
* For internal use; add settings in the skin via $this->addStyle()
*
* Style again! This seems like a code duplication since we already have
- * mStyles. This is what makes OpenSource amazing.
+ * mStyles. This is what makes Open Source amazing.
*/
- var $styles = array();
+ protected $styles = array();
/**
* Whether jQuery is already handled.
@@ -253,19 +293,25 @@ class OutputPage extends ContextSource {
private $mProperties = array();
/**
- * @var string|null: ResourceLoader target for load.php links. If null, will be omitted
+ * @var string|null ResourceLoader target for load.php links. If null, will be omitted
*/
private $mTarget = null;
/**
- * @var bool: Whether output should contain table of contents
+ * @var bool Whether parser output should contain table of contents
*/
private $mEnableTOC = true;
/**
+ * @var bool Whether parser output should contain section edit links
+ */
+ private $mEnableSectionEditLinks = true;
+
+ /**
* Constructor for OutputPage. This should not be called directly.
* Instead a new RequestContext should be created and it will implicitly create
* a OutputPage tied to that context.
+ * @param IContextSource|null $context
*/
function __construct( IContextSource $context = null ) {
if ( $context === null ) {
@@ -291,7 +337,7 @@ class OutputPage extends ContextSource {
/**
* Get the URL to redirect to, or an empty string if not redirect URL set
*
- * @return String
+ * @return string
*/
public function getRedirect() {
return $this->mRedirect;
@@ -300,7 +346,7 @@ class OutputPage extends ContextSource {
/**
* Set the HTTP status code to send with the output.
*
- * @param $statusCode Integer
+ * @param int $statusCode
*/
public function setStatusCode( $statusCode ) {
$this->mStatusCode = $statusCode;
@@ -310,32 +356,52 @@ class OutputPage extends ContextSource {
* Add a new "<meta>" tag
* To add an http-equiv meta tag, precede the name with "http:"
*
- * @param string $name tag name
- * @param string $val tag value
+ * @param string $name Tag name
+ * @param string $val Tag value
*/
function addMeta( $name, $val ) {
array_push( $this->mMetatags, array( $name, $val ) );
}
/**
+ * Returns the current <meta> tags
+ *
+ * @since 1.25
+ * @return array
+ */
+ public function getMetaTags() {
+ return $this->mMetatags;
+ }
+
+ /**
* Add a new \<link\> tag to the page header.
*
* Note: use setCanonicalUrl() for rel=canonical.
*
- * @param array $linkarr associative array of attributes.
+ * @param array $linkarr Associative array of attributes.
*/
- function addLink( $linkarr ) {
+ function addLink( array $linkarr ) {
array_push( $this->mLinktags, $linkarr );
}
/**
+ * Returns the current <link> tags
+ *
+ * @since 1.25
+ * @return array
+ */
+ public function getLinkTags() {
+ return $this->mLinktags;
+ }
+
+ /**
* Add a new \<link\> with "rel" attribute set to "meta"
*
- * @param array $linkarr associative array mapping attribute names to their
+ * @param array $linkarr Associative array mapping attribute names to their
* values, both keys and values will be escaped, and the
* "rel" attribute will be automatically added
*/
- function addMetadataLink( $linkarr ) {
+ function addMetadataLink( array $linkarr ) {
$linkarr['rel'] = $this->getMetadataAttribute();
$this->addLink( $linkarr );
}
@@ -343,15 +409,27 @@ class OutputPage extends ContextSource {
/**
* Set the URL to be used for the <link rel=canonical>. This should be used
* in preference to addLink(), to avoid duplicate link tags.
+ * @param string $url
*/
function setCanonicalUrl( $url ) {
$this->mCanonicalUrl = $url;
}
/**
+ * Returns the URL to be used for the <link rel=canonical> if
+ * one is set.
+ *
+ * @since 1.25
+ * @return bool|string
+ */
+ public function getCanonicalUrl() {
+ return $this->mCanonicalUrl;
+ }
+
+ /**
* Get the value of the "rel" attribute for metadata links
*
- * @return String
+ * @return string
*/
public function getMetadataAttribute() {
# note: buggy CC software only reads first "meta" link
@@ -367,7 +445,7 @@ class OutputPage extends ContextSource {
/**
* Add raw HTML to the list of scripts (including \<script\> tag, etc.)
*
- * @param string $script raw HTML
+ * @param string $script Raw HTML
*/
function addScript( $script ) {
$this->mScripts .= $script . "\n";
@@ -376,7 +454,7 @@ class OutputPage extends ContextSource {
/**
* Register and add a stylesheet from an extension directory.
*
- * @param string $url path to sheet. Provide either a full url (beginning
+ * @param string $url Path to sheet. Provide either a full url (beginning
* with 'http', etc) or a relative path from the document root
* (beginning with '/'). Otherwise it behaves identically to
* addStyle() and draws from the /skins folder.
@@ -388,7 +466,7 @@ class OutputPage extends ContextSource {
/**
* Get all styles added by extensions
*
- * @return Array
+ * @return array
*/
function getExtStyle() {
return $this->mExtStyles;
@@ -397,20 +475,19 @@ class OutputPage extends ContextSource {
/**
* Add a JavaScript file out of skins/common, or a given relative path.
*
- * @param string $file filename in skins/common or complete on-server path
+ * @param string $file Filename in skins/common or complete on-server path
* (/foo/bar.js)
- * @param string $version style version of the file. Defaults to $wgStyleVersion
+ * @param string $version Style version of the file. Defaults to $wgStyleVersion
*/
public function addScriptFile( $file, $version = null ) {
- global $wgStylePath, $wgStyleVersion;
// See if $file parameter is an absolute URL or begins with a slash
if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
$path = $file;
} else {
- $path = "{$wgStylePath}/common/{$file}";
+ $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}";
}
if ( is_null( $version ) ) {
- $version = $wgStyleVersion;
+ $version = $this->getConfig()->get( 'StyleVersion' );
}
$this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
}
@@ -427,21 +504,25 @@ class OutputPage extends ContextSource {
/**
* Get all registered JS and CSS tags for the header.
*
- * @return String
+ * @return string
+ * @deprecated since 1.24 Use OutputPage::headElement to build the full header.
*/
function getScript() {
+ wfDeprecated( __METHOD__, '1.24' );
return $this->mScripts . $this->getHeadItems();
}
/**
* Filter an array of modules to remove insufficiently trustworthy members, and modules
* which are no longer registered (eg a page is cached before an extension is disabled)
- * @param $modules Array
- * @param string $position if not null, only return modules with this position
- * @param $type string
- * @return Array
+ * @param array $modules
+ * @param string|null $position If not null, only return modules with this position
+ * @param string $type
+ * @return array
*/
- protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ) {
+ protected function filterModules( array $modules, $position = null,
+ $type = ResourceLoaderModule::TYPE_COMBINED
+ ) {
$resourceLoader = $this->getResourceLoader();
$filteredModules = array();
foreach ( $modules as $val ) {
@@ -449,8 +530,8 @@ class OutputPage extends ContextSource {
if ( $module instanceof ResourceLoaderModule
&& $module->getOrigin() <= $this->getAllowedModules( $type )
&& ( is_null( $position ) || $module->getPosition() == $position )
- && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) ) )
- {
+ && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) )
+ ) {
$filteredModules[] = $val;
}
}
@@ -460,10 +541,10 @@ class OutputPage extends ContextSource {
/**
* Get the list of modules to include on this page
*
- * @param bool $filter whether to filter out insufficiently trustworthy modules
- * @param string $position if not null, only return modules with this position
- * @param $param string
- * @return Array of module names
+ * @param bool $filter Whether to filter out insufficiently trustworthy modules
+ * @param string|null $position If not null, only return modules with this position
+ * @param string $param
+ * @return array Array of module names
*/
public function getModules( $filter = false, $position = null, $param = 'mModules' ) {
$modules = array_values( array_unique( $this->$param ) );
@@ -477,7 +558,7 @@ class OutputPage extends ContextSource {
* through this function will be loaded by the resource loader when the
* page loads.
*
- * @param $modules Mixed: module name (string) or array of module names
+ * @param string|array $modules Module name (string) or array of module names
*/
public function addModules( $modules ) {
$this->mModules = array_merge( $this->mModules, (array)$modules );
@@ -486,10 +567,10 @@ class OutputPage extends ContextSource {
/**
* Get the list of module JS to include on this page
*
- * @param $filter
- * @param $position
+ * @param bool $filter
+ * @param string|null $position
*
- * @return array of module names
+ * @return array Array of module names
*/
public function getModuleScripts( $filter = false, $position = null ) {
return $this->getModules( $filter, $position, 'mModuleScripts' );
@@ -500,7 +581,7 @@ class OutputPage extends ContextSource {
* scripts added through this function will be loaded by the resource loader when
* the page loads.
*
- * @param $modules Mixed: module name (string) or array of module names
+ * @param string|array $modules Module name (string) or array of module names
*/
public function addModuleScripts( $modules ) {
$this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
@@ -509,10 +590,10 @@ class OutputPage extends ContextSource {
/**
* Get the list of module CSS to include on this page
*
- * @param $filter
- * @param $position
+ * @param bool $filter
+ * @param string|null $position
*
- * @return Array of module names
+ * @return array Array of module names
*/
public function getModuleStyles( $filter = false, $position = null ) {
return $this->getModules( $filter, $position, 'mModuleStyles' );
@@ -525,7 +606,7 @@ class OutputPage extends ContextSource {
* tags, rather than as a combined Javascript and CSS package. Thus, they will
* load when JavaScript is disabled (unless CSS also happens to be disabled).
*
- * @param $modules Mixed: module name (string) or array of module names
+ * @param string|array $modules Module name (string) or array of module names
*/
public function addModuleStyles( $modules ) {
$this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
@@ -534,10 +615,10 @@ class OutputPage extends ContextSource {
/**
* Get the list of module messages to include on this page
*
- * @param $filter
- * @param $position
+ * @param bool $filter
+ * @param string|null $position
*
- * @return Array of module names
+ * @return array Array of module names
*/
public function getModuleMessages( $filter = false, $position = null ) {
return $this->getModules( $filter, $position, 'mModuleMessages' );
@@ -548,14 +629,14 @@ class OutputPage extends ContextSource {
* Module messages added through this function will be loaded by the resource
* loader when the page loads.
*
- * @param $modules Mixed: module name (string) or array of module names
+ * @param string|array $modules Module name (string) or array of module names
*/
public function addModuleMessages( $modules ) {
$this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
}
/**
- * @return null|string: ResourceLoader target
+ * @return null|string ResourceLoader target
*/
public function getTarget() {
return $this->mTarget;
@@ -564,7 +645,7 @@ class OutputPage extends ContextSource {
/**
* Sets ResourceLoader target for load.php links. If null, will be omitted
*
- * @param $target string|null
+ * @param string|null $target
*/
public function setTarget( $target ) {
$this->mTarget = $target;
@@ -573,7 +654,7 @@ class OutputPage extends ContextSource {
/**
* Get an array of head items
*
- * @return Array
+ * @return array
*/
function getHeadItemsArray() {
return $this->mHeadItems;
@@ -582,9 +663,12 @@ class OutputPage extends ContextSource {
/**
* Get all header items in a string
*
- * @return String
+ * @return string
+ * @deprecated since 1.24 Use OutputPage::headElement or
+ * if absolutely necessary use OutputPage::getHeadItemsArray
*/
function getHeadItems() {
+ wfDeprecated( __METHOD__, '1.24' );
$s = '';
foreach ( $this->mHeadItems as $item ) {
$s .= $item;
@@ -595,8 +679,8 @@ class OutputPage extends ContextSource {
/**
* Add or replace an header item to the output
*
- * @param string $name item name
- * @param string $value raw HTML
+ * @param string $name Item name
+ * @param string $value Raw HTML
*/
public function addHeadItem( $name, $value ) {
$this->mHeadItems[$name] = $value;
@@ -605,8 +689,8 @@ class OutputPage extends ContextSource {
/**
* Check if the header item $name is already set
*
- * @param string $name item name
- * @return Boolean
+ * @param string $name Item name
+ * @return bool
*/
public function hasHeadItem( $name ) {
return isset( $this->mHeadItems[$name] );
@@ -615,7 +699,7 @@ class OutputPage extends ContextSource {
/**
* Set the value of the ETag HTTP header, only used if $wgUseETag is true
*
- * @param string $tag value of "ETag" header
+ * @param string $tag Value of "ETag" header
*/
function setETag( $tag ) {
$this->mETag = $tag;
@@ -626,7 +710,7 @@ class OutputPage extends ContextSource {
* without any skin, sidebar, etc.
* Used e.g. when calling with "action=render".
*
- * @param $only Boolean: whether to output only the body of the article
+ * @param bool $only Whether to output only the body of the article
*/
public function setArticleBodyOnly( $only ) {
$this->mArticleBodyOnly = $only;
@@ -635,7 +719,7 @@ class OutputPage extends ContextSource {
/**
* Return whether the output will contain only the body of the article
*
- * @return Boolean
+ * @return bool
*/
public function getArticleBodyOnly() {
return $this->mArticleBodyOnly;
@@ -656,8 +740,8 @@ class OutputPage extends ContextSource {
* Get an additional output property
* @since 1.21
*
- * @param $name
- * @return mixed: Property value or null if not found
+ * @param string $name
+ * @return mixed Property value or null if not found
*/
public function getProperty( $name ) {
if ( isset( $this->mProperties[$name] ) ) {
@@ -674,23 +758,18 @@ class OutputPage extends ContextSource {
*
* Side effect: sets mLastModified for Last-Modified header
*
- * @param $timestamp string
+ * @param string $timestamp
*
- * @return Boolean: true if cache-ok headers was sent.
+ * @return bool True if cache-ok headers was sent.
*/
public function checkLastModified( $timestamp ) {
- global $wgCachePages, $wgCacheEpoch, $wgUseSquid, $wgSquidMaxage;
-
if ( !$timestamp || $timestamp == '19700101000000' ) {
wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
return false;
}
- if ( !$wgCachePages ) {
- wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
- return false;
- }
- if ( $this->getUser()->getOption( 'nocache' ) ) {
- wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
+ $config = $this->getConfig();
+ if ( !$config->get( 'CachePages' ) ) {
+ wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
return false;
}
@@ -698,11 +777,11 @@ class OutputPage extends ContextSource {
$modifiedTimes = array(
'page' => $timestamp,
'user' => $this->getUser()->getTouched(),
- 'epoch' => $wgCacheEpoch
+ 'epoch' => $config->get( 'CacheEpoch' )
);
- if ( $wgUseSquid ) {
+ if ( $config->get( 'UseSquid' ) ) {
// bug 44570: the core page itself may not change, but resources might
- $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $wgSquidMaxage );
+ $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
}
wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
@@ -711,7 +790,7 @@ class OutputPage extends ContextSource {
$clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
if ( $clientHeader === false ) {
- wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false );
+ wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", 'log' );
return false;
}
@@ -724,7 +803,8 @@ class OutputPage extends ContextSource {
$clientHeaderTime = strtotime( $clientHeader );
wfRestoreWarnings();
if ( !$clientHeaderTime ) {
- wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
+ wfDebug( __METHOD__
+ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
return false;
}
$clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
@@ -739,17 +819,17 @@ class OutputPage extends ContextSource {
}
wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
- wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
+ wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", 'log' );
wfDebug( __METHOD__ . ": effective Last-Modified: " .
- wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
+ wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", 'log' );
if ( $clientHeaderTime < $maxModified ) {
- wfDebug( __METHOD__ . ": STALE, $info\n", false );
+ wfDebug( __METHOD__ . ": STALE, $info\n", 'log' );
return false;
}
# Not modified
# Give a 304 response code and disable body output
- wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
+ wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", 'log' );
ini_set( 'zlib.output_compression', 0 );
$this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" );
$this->sendCacheControl();
@@ -766,7 +846,7 @@ class OutputPage extends ContextSource {
/**
* Override the last modified timestamp
*
- * @param string $timestamp new timestamp, in a format readable by
+ * @param string $timestamp New timestamp, in a format readable by
* wfTimestamp()
*/
public function setLastModified( $timestamp ) {
@@ -776,7 +856,7 @@ class OutputPage extends ContextSource {
/**
* Set the robot policy for the page: <http://www.robotstxt.org/meta.html>
*
- * @param string $policy the literal string to output as the contents of
+ * @param string $policy The literal string to output as the contents of
* the meta tag. Will be parsed according to the spec and output in
* standardized form.
* @return null
@@ -810,7 +890,7 @@ class OutputPage extends ContextSource {
* Set the follow policy for the page, but leave the index policy un-
* touched.
*
- * @param string $policy either 'follow' or 'nofollow'.
+ * @param string $policy Either 'follow' or 'nofollow'.
* @return null
*/
public function setFollowPolicy( $policy ) {
@@ -824,7 +904,7 @@ class OutputPage extends ContextSource {
* Set the new value of the "action text", this will be added to the
* "HTML title", separated from it with " - ".
*
- * @param string $text new value of the "action text"
+ * @param string $text New value of the "action text"
*/
public function setPageTitleActionText( $text ) {
$this->mPageTitleActionText = $text;
@@ -833,20 +913,17 @@ class OutputPage extends ContextSource {
/**
* Get the value of the "action text"
*
- * @return String
+ * @return string
*/
public function getPageTitleActionText() {
- if ( isset( $this->mPageTitleActionText ) ) {
- return $this->mPageTitleActionText;
- }
- return '';
+ return $this->mPageTitleActionText;
}
/**
* "HTML title" means the contents of "<title>".
* It is stored as plain, unescaped text and will be run through htmlspecialchars in the skin file.
*
- * @param $name string
+ * @param string|Message $name
*/
public function setHTMLTitle( $name ) {
if ( $name instanceof Message ) {
@@ -859,7 +936,7 @@ class OutputPage extends ContextSource {
/**
* Return the "HTML title", i.e. the content of the "<title>" tag.
*
- * @return String
+ * @return string
*/
public function getHTMLTitle() {
return $this->mHTMLtitle;
@@ -868,19 +945,21 @@ class OutputPage extends ContextSource {
/**
* Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
*
- * @param $t Title
+ * @param Title $t
*/
public function setRedirectedFrom( $t ) {
$this->mRedirectedFrom = $t;
}
/**
- * "Page title" means the contents of \<h1\>. It is stored as a valid HTML fragment.
- * This function allows good tags like \<sup\> in the \<h1\> tag, but not bad tags like \<script\>.
- * This function automatically sets \<title\> to the same content as \<h1\> but with all tags removed.
- * Bad tags that were escaped in \<h1\> will still be escaped in \<title\>, and good tags like \<i\> will be dropped entirely.
+ * "Page title" means the contents of \<h1\>. It is stored as a valid HTML
+ * fragment. This function allows good tags like \<sup\> in the \<h1\> tag,
+ * but not bad tags like \<script\>. This function automatically sets
+ * \<title\> to the same content as \<h1\> but with all tags removed. Bad
+ * tags that were escaped in \<h1\> will still be escaped in \<title\>, and
+ * good tags like \<i\> will be dropped entirely.
*
- * @param $name string|Message
+ * @param string|Message $name
*/
public function setPageTitle( $name ) {
if ( $name instanceof Message ) {
@@ -893,13 +972,16 @@ class OutputPage extends ContextSource {
$this->mPagetitle = $nameWithTags;
# change "<i>foo&amp;bar</i>" to "foo&bar"
- $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) );
+ $this->setHTMLTitle(
+ $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) )
+ ->inContentLanguage()
+ );
}
/**
* Return the "page title", i.e. the content of the \<h1\> tag.
*
- * @return String
+ * @return string
*/
public function getPageTitle() {
return $this->mPagetitle;
@@ -908,7 +990,7 @@ class OutputPage extends ContextSource {
/**
* Set the Title object to use
*
- * @param $t Title object
+ * @param Title $t
*/
public function setTitle( Title $t ) {
$this->getContext()->setTitle( $t );
@@ -917,7 +999,7 @@ class OutputPage extends ContextSource {
/**
* Replace the subtitle with $str
*
- * @param string|Message $str new value of the subtitle. String should be safe HTML.
+ * @param string|Message $str New value of the subtitle. String should be safe HTML.
*/
public function setSubtitle( $str ) {
$this->clearSubtitle();
@@ -927,8 +1009,8 @@ class OutputPage extends ContextSource {
/**
* Add $str to the subtitle
*
- * @deprecated in 1.19; use addSubtitle() instead
- * @param string|Message $str to add to the subtitle
+ * @deprecated since 1.19; use addSubtitle() instead
+ * @param string|Message $str String or Message to add to the subtitle
*/
public function appendSubtitle( $str ) {
$this->addSubtitle( $str );
@@ -937,7 +1019,7 @@ class OutputPage extends ContextSource {
/**
* Add $str to the subtitle
*
- * @param string|Message $str to add to the subtitle. String should be safe HTML.
+ * @param string|Message $str String or Message to add to the subtitle. String should be safe HTML.
*/
public function addSubtitle( $str ) {
if ( $str instanceof Message ) {
@@ -950,14 +1032,15 @@ class OutputPage extends ContextSource {
/**
* Add a subtitle containing a backlink to a page
*
- * @param $title Title to link to
+ * @param Title $title Title to link to
+ * @param array $query Array of additional parameters to include in the link
*/
- public function addBacklinkSubtitle( Title $title ) {
- $query = array();
+ public function addBacklinkSubtitle( Title $title, $query = array() ) {
if ( $title->isRedirect() ) {
$query['redirect'] = 'no';
}
- $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) );
+ $this->addSubtitle( $this->msg( 'backlinksubtitle' )
+ ->rawParams( Linker::link( $title, null, array(), $query ) ) );
}
/**
@@ -970,7 +1053,7 @@ class OutputPage extends ContextSource {
/**
* Get the subtitle
*
- * @return String
+ * @return string
*/
public function getSubtitle() {
return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
@@ -987,7 +1070,7 @@ class OutputPage extends ContextSource {
/**
* Return whether the page is "printable"
*
- * @return Boolean
+ * @return bool
*/
public function isPrintable() {
return $this->mPrintable;
@@ -1003,7 +1086,7 @@ class OutputPage extends ContextSource {
/**
* Return whether the output will be completely disabled
*
- * @return Boolean
+ * @return bool
*/
public function isDisabled() {
return $this->mDoNothing;
@@ -1012,7 +1095,7 @@ class OutputPage extends ContextSource {
/**
* Show an "add new section" link?
*
- * @return Boolean
+ * @return bool
*/
public function showNewSectionLink() {
return $this->mNewSectionLink;
@@ -1021,7 +1104,7 @@ class OutputPage extends ContextSource {
/**
* Forcibly hide the new section link?
*
- * @return Boolean
+ * @return bool
*/
public function forceHideNewSectionLink() {
return $this->mHideNewSectionLink;
@@ -1033,7 +1116,7 @@ class OutputPage extends ContextSource {
* for the new version
* @see addFeedLink()
*
- * @param $show Boolean: true: add default feeds, false: remove all feeds
+ * @param bool $show True: add default feeds, false: remove all feeds
*/
public function setSyndicated( $show = true ) {
if ( $show ) {
@@ -1049,15 +1132,13 @@ class OutputPage extends ContextSource {
* for the new version
* @see addFeedLink()
*
- * @param string $val query to append to feed links or false to output
+ * @param string $val Query to append to feed links or false to output
* default links
*/
public function setFeedAppendQuery( $val ) {
- global $wgAdvertisedFeedTypes;
-
$this->mFeedLinks = array();
- foreach ( $wgAdvertisedFeedTypes as $type ) {
+ foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) {
$query = "feed=$type";
if ( is_string( $val ) ) {
$query .= '&' . $val;
@@ -1069,20 +1150,18 @@ class OutputPage extends ContextSource {
/**
* Add a feed link to the page header
*
- * @param string $format feed type, should be a key of $wgFeedClasses
+ * @param string $format Feed type, should be a key of $wgFeedClasses
* @param string $href URL
*/
public function addFeedLink( $format, $href ) {
- global $wgAdvertisedFeedTypes;
-
- if ( in_array( $format, $wgAdvertisedFeedTypes ) ) {
+ if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) {
$this->mFeedLinks[$format] = $href;
}
}
/**
* Should we output feed links for this page?
- * @return Boolean
+ * @return bool
*/
public function isSyndicated() {
return count( $this->mFeedLinks ) > 0;
@@ -1090,7 +1169,7 @@ class OutputPage extends ContextSource {
/**
* Return URLs for each supported syndication format for this page.
- * @return array associating format keys with URLs
+ * @return array Associating format keys with URLs
*/
public function getSyndicationLinks() {
return $this->mFeedLinks;
@@ -1110,7 +1189,7 @@ class OutputPage extends ContextSource {
* corresponding article on the wiki
* Setting true will cause the change "article related" toggle to true
*
- * @param $v Boolean
+ * @param bool $v
*/
public function setArticleFlag( $v ) {
$this->mIsarticle = $v;
@@ -1123,7 +1202,7 @@ class OutputPage extends ContextSource {
* Return whether the content displayed page is related to the source of
* the corresponding article on the wiki
*
- * @return Boolean
+ * @return bool
*/
public function isArticle() {
return $this->mIsarticle;
@@ -1133,7 +1212,7 @@ class OutputPage extends ContextSource {
* Set whether this page is related an article on the wiki
* Setting false will cause the change of "article flag" toggle to false
*
- * @param $v Boolean
+ * @param bool $v
*/
public function setArticleRelated( $v ) {
$this->mIsArticleRelated = $v;
@@ -1145,7 +1224,7 @@ class OutputPage extends ContextSource {
/**
* Return whether this page is related an article on the wiki
*
- * @return Boolean
+ * @return bool
*/
public function isArticleRelated() {
return $this->mIsArticleRelated;
@@ -1157,7 +1236,7 @@ class OutputPage extends ContextSource {
* @param array $newLinkArray Associative array mapping language code to the page
* name
*/
- public function addLanguageLinks( $newLinkArray ) {
+ public function addLanguageLinks( array $newLinkArray ) {
$this->mLanguageLinks += $newLinkArray;
}
@@ -1167,14 +1246,14 @@ class OutputPage extends ContextSource {
* @param array $newLinkArray Associative array mapping language code to the page
* name
*/
- public function setLanguageLinks( $newLinkArray ) {
+ public function setLanguageLinks( array $newLinkArray ) {
$this->mLanguageLinks = $newLinkArray;
}
/**
* Get the list of language links
*
- * @return Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page')
+ * @return array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page')
*/
public function getLanguageLinks() {
return $this->mLanguageLinks;
@@ -1183,9 +1262,9 @@ class OutputPage extends ContextSource {
/**
* Add an array of categories, with names in the keys
*
- * @param array $categories mapping category name => sort key
+ * @param array $categories Mapping category name => sort key
*/
- public function addCategoryLinks( $categories ) {
+ public function addCategoryLinks( array $categories ) {
global $wgContLang;
if ( !is_array( $categories ) || count( $categories ) == 0 ) {
@@ -1199,22 +1278,29 @@ class OutputPage extends ContextSource {
# Fetch existence plus the hiddencat property
$dbr = wfGetDB( DB_SLAVE );
+ $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_len',
+ 'page_is_redirect', 'page_latest', 'pp_value' );
+
+ if ( $this->getConfig()->get( 'ContentHandlerUseDB' ) ) {
+ $fields[] = 'page_content_model';
+ }
+
$res = $dbr->select( array( 'page', 'page_props' ),
- array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ),
+ $fields,
$lb->constructSet( 'page', $dbr ),
__METHOD__,
array(),
- array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) )
+ array( 'page_props' => array( 'LEFT JOIN', array(
+ 'pp_propname' => 'hiddencat',
+ 'pp_page = page_id'
+ ) ) )
);
# Add the results to the link cache
$lb->addResultToCache( LinkCache::singleton(), $res );
- # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
- $categories = array_combine(
- array_keys( $categories ),
- array_fill( 0, count( $categories ), 'normal' )
- );
+ # Set all the values to 'normal'.
+ $categories = array_fill_keys( array_keys( $categories ), 'normal' );
# Mark hidden categories
foreach ( $res as $row ) {
@@ -1224,15 +1310,19 @@ class OutputPage extends ContextSource {
}
# Add the remaining categories to the skin
- if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
+ if ( wfRunHooks(
+ 'OutputPageMakeCategoryLinks',
+ array( &$this, $categories, &$this->mCategoryLinks ) )
+ ) {
foreach ( $categories as $category => $type ) {
$origcategory = $category;
$title = Title::makeTitleSafe( NS_CATEGORY, $category );
+ if ( !$title ) {
+ continue;
+ }
$wgContLang->findVariantLink( $category, $title, true );
- if ( $category != $origcategory ) {
- if ( array_key_exists( $category, $categories ) ) {
- continue;
- }
+ if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
+ continue;
}
$text = $wgContLang->convertHtml( $title->getText() );
$this->mCategories[] = $title->getText();
@@ -1244,9 +1334,9 @@ class OutputPage extends ContextSource {
/**
* Reset the category links (but not the category list) and add $categories
*
- * @param array $categories mapping category name => sort key
+ * @param array $categories Mapping category name => sort key
*/
- public function setCategoryLinks( $categories ) {
+ public function setCategoryLinks( array $categories ) {
$this->mCategoryLinks = array();
$this->addCategoryLinks( $categories );
}
@@ -1257,7 +1347,7 @@ class OutputPage extends ContextSource {
* hidden categories) and $link a HTML fragment with a link to the category
* page
*
- * @return Array
+ * @return array
*/
public function getCategoryLinks() {
return $this->mCategoryLinks;
@@ -1266,7 +1356,7 @@ class OutputPage extends ContextSource {
/**
* Get the list of category names this page belongs to
*
- * @return Array of strings
+ * @return array Array of strings
*/
public function getCategories() {
return $this->mCategories;
@@ -1281,7 +1371,6 @@ class OutputPage extends ContextSource {
* @todo this should be given a more accurate name
*/
public function disallowUserJs() {
- global $wgAllowSiteCSSOnRestrictedPages;
$this->reduceAllowedModules(
ResourceLoaderModule::TYPE_SCRIPTS,
ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
@@ -1289,7 +1378,7 @@ class OutputPage extends ContextSource {
// Site-wide styles are controlled by a config setting, see bug 71621
// for background on why. User styles are never allowed.
- if ( $wgAllowSiteCSSOnRestrictedPages ) {
+ if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
$styleOrigin = ResourceLoaderModule::ORIGIN_USER_SITEWIDE;
} else {
$styleOrigin = ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL;
@@ -1301,19 +1390,7 @@ class OutputPage extends ContextSource {
}
/**
- * Return whether user JavaScript is allowed for this page
- * @deprecated since 1.18 Load modules with ResourceLoader, and origin and
- * trustworthiness is identified and enforced automagically.
- * @return Boolean
- */
- public function isUserJsAllowed() {
- wfDeprecated( __METHOD__, '1.18' );
- return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
- }
-
- /**
- * Get the level of JavaScript / CSS untrustworthiness allowed on this page.
- *
+ * Show what level of JavaScript / CSS untrustworthiness is allowed on this page
* @see ResourceLoaderModule::$origin
* @param string $type ResourceLoaderModule TYPE_ constant
* @return int ResourceLoaderModule ORIGIN_ class constant
@@ -1333,7 +1410,6 @@ class OutputPage extends ContextSource {
*
* @deprecated since 1.24 Raising level of allowed untrusted content is no longer supported.
* Use reduceAllowedModules() instead
- *
* @param string $type ResourceLoaderModule TYPE_ constant
* @param int $level ResourceLoaderModule class constant
*/
@@ -1378,11 +1454,11 @@ class OutputPage extends ContextSource {
*
* @since 1.19
*
- * @param $element string
- * @param $attribs array
- * @param $contents string
+ * @param string $element
+ * @param array $attribs
+ * @param string $contents
*/
- public function addElement( $element, $attribs = array(), $contents = '' ) {
+ public function addElement( $element, array $attribs = array(), $contents = '' ) {
$this->addHTML( Html::element( $element, $attribs, $contents ) );
}
@@ -1396,7 +1472,7 @@ class OutputPage extends ContextSource {
/**
* Get the body HTML
*
- * @return String: HTML
+ * @return string HTML
*/
public function getHTML() {
return $this->mBodytext;
@@ -1405,9 +1481,9 @@ class OutputPage extends ContextSource {
/**
* Get/set the ParserOptions object to use for wikitext parsing
*
- * @param $options ParserOptions|null either the ParserOption to use or null to only get the
- * current ParserOption object
- * @return ParserOptions object
+ * @param ParserOptions|null $options Either the ParserOption to use or null to only get the
+ * current ParserOption object
+ * @return ParserOptions
*/
public function parserOptions( $options = null ) {
if ( !$this->mParserOptions ) {
@@ -1421,8 +1497,8 @@ class OutputPage extends ContextSource {
* Set the revision ID which will be seen by the wiki text parser
* for things such as embedded {{REVISIONID}} variable use.
*
- * @param $revid Mixed: an positive integer, or null
- * @return Mixed: previous value
+ * @param int|null $revid An positive integer, or null
+ * @return mixed Previous value
*/
public function setRevisionId( $revid ) {
$val = is_null( $revid ) ? null : intval( $revid );
@@ -1432,7 +1508,7 @@ class OutputPage extends ContextSource {
/**
* Get the displayed revision ID
*
- * @return Integer
+ * @return int
*/
public function getRevisionId() {
return $this->mRevisionId;
@@ -1442,8 +1518,8 @@ class OutputPage extends ContextSource {
* Set the timestamp of the revision which will be displayed. This is used
* to avoid a extra DB call in Skin::lastModified().
*
- * @param $timestamp Mixed: string, or null
- * @return Mixed: previous value
+ * @param string|null $timestamp
+ * @return mixed Previous value
*/
public function setRevisionTimestamp( $timestamp ) {
return wfSetVar( $this->mRevisionTimestamp, $timestamp );
@@ -1453,7 +1529,7 @@ class OutputPage extends ContextSource {
* Get the timestamp of displayed revision.
* This will be null if not filled by setRevisionTimestamp().
*
- * @return String or null
+ * @return string|null
*/
public function getRevisionTimestamp() {
return $this->mRevisionTimestamp;
@@ -1462,8 +1538,8 @@ class OutputPage extends ContextSource {
/**
* Set the displayed file version
*
- * @param $file File|bool
- * @return Mixed: previous value
+ * @param File|bool $file
+ * @return mixed Previous value
*/
public function setFileVersion( $file ) {
$val = null;
@@ -1476,7 +1552,7 @@ class OutputPage extends ContextSource {
/**
* Get the displayed file version
*
- * @return Array|null ('time' => MW timestamp, 'sha1' => sha1)
+ * @return array|null ('time' => MW timestamp, 'sha1' => sha1)
*/
public function getFileVersion() {
return $this->mFileVersion;
@@ -1485,7 +1561,7 @@ class OutputPage extends ContextSource {
/**
* Get the templates used on this page
*
- * @return Array (namespace => dbKey => revId)
+ * @return array (namespace => dbKey => revId)
* @since 1.18
*/
public function getTemplateIds() {
@@ -1495,7 +1571,7 @@ class OutputPage extends ContextSource {
/**
* Get the files used on this page
*
- * @return Array (dbKey => array('time' => MW timestamp or null, 'sha1' => sha1 or ''))
+ * @return array (dbKey => array('time' => MW timestamp or null, 'sha1' => sha1 or ''))
* @since 1.18
*/
public function getFileSearchOptions() {
@@ -1506,9 +1582,9 @@ class OutputPage extends ContextSource {
* Convert wikitext to HTML and add it to the buffer
* Default assumes that the current page title will be used.
*
- * @param $text String
- * @param $linestart Boolean: is this the start of a line?
- * @param $interface Boolean: is this text in the user interface language?
+ * @param string $text
+ * @param bool $linestart Is this the start of a line?
+ * @param bool $interface Is this text in the user interface language?
*/
public function addWikiText( $text, $linestart = true, $interface = true ) {
$title = $this->getTitle(); // Work around E_STRICT
@@ -1521,9 +1597,9 @@ class OutputPage extends ContextSource {
/**
* Add wikitext with a custom Title object
*
- * @param string $text wikitext
- * @param $title Title object
- * @param $linestart Boolean: is this the start of a line?
+ * @param string $text Wikitext
+ * @param Title $title
+ * @param bool $linestart Is this the start of a line?
*/
public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
$this->addWikiTextTitle( $text, $title, $linestart );
@@ -1532,9 +1608,9 @@ class OutputPage extends ContextSource {
/**
* Add wikitext with a custom Title object and tidy enabled.
*
- * @param string $text wikitext
- * @param $title Title object
- * @param $linestart Boolean: is this the start of a line?
+ * @param string $text Wikitext
+ * @param Title $title
+ * @param bool $linestart Is this the start of a line?
*/
function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
$this->addWikiTextTitle( $text, $title, $linestart, true );
@@ -1543,8 +1619,8 @@ class OutputPage extends ContextSource {
/**
* Add wikitext with tidy enabled
*
- * @param string $text wikitext
- * @param $linestart Boolean: is this the start of a line?
+ * @param string $text Wikitext
+ * @param bool $linestart Is this the start of a line?
*/
public function addWikiTextTidy( $text, $linestart = true ) {
$title = $this->getTitle();
@@ -1554,14 +1630,16 @@ class OutputPage extends ContextSource {
/**
* Add wikitext with a custom Title object
*
- * @param string $text wikitext
- * @param $title Title object
- * @param $linestart Boolean: is this the start of a line?
- * @param $tidy Boolean: whether to use tidy
- * @param $interface Boolean: whether it is an interface message
- * (for example disables conversion)
+ * @param string $text Wikitext
+ * @param Title $title
+ * @param bool $linestart Is this the start of a line?
+ * @param bool $tidy Whether to use tidy
+ * @param bool $interface Whether it is an interface message
+ * (for example disables conversion)
*/
- public function addWikiTextTitle( $text, Title $title, $linestart, $tidy = false, $interface = false ) {
+ public function addWikiTextTitle( $text, Title $title, $linestart,
+ $tidy = false, $interface = false
+ ) {
global $wgParser;
wfProfileIn( __METHOD__ );
@@ -1570,7 +1648,7 @@ class OutputPage extends ContextSource {
$oldTidy = $popts->setTidy( $tidy );
$popts->setInterfaceMessage( (bool)$interface );
- $parserOutput = $wgParser->parse(
+ $parserOutput = $wgParser->getFreshParser()->parse(
$text, $title, $popts,
$linestart, true, $this->mRevisionId
);
@@ -1583,11 +1661,24 @@ class OutputPage extends ContextSource {
}
/**
- * Add a ParserOutput object, but without Html
+ * Add a ParserOutput object, but without Html.
+ *
+ * @deprecated since 1.24, use addParserOutputMetadata() instead.
+ * @param ParserOutput $parserOutput
+ */
+ public function addParserOutputNoText( $parserOutput ) {
+ $this->addParserOutputMetadata( $parserOutput );
+ }
+
+ /**
+ * Add all metadata associated with a ParserOutput object, but without the actual HTML. This
+ * includes categories, language links, ResourceLoader modules, effects of certain magic words,
+ * and so on.
*
- * @param $parserOutput ParserOutput object
+ * @since 1.24
+ * @param ParserOutput $parserOutput
*/
- public function addParserOutputNoText( &$parserOutput ) {
+ public function addParserOutputMetadata( $parserOutput ) {
$this->mLanguageLinks += $parserOutput->getLanguageLinks();
$this->addCategoryLinks( $parserOutput->getCategories() );
$this->mNewSectionLink = $parserOutput->getNewSection();
@@ -1603,6 +1694,7 @@ class OutputPage extends ContextSource {
$this->addModuleScripts( $parserOutput->getModuleScripts() );
$this->addModuleStyles( $parserOutput->getModuleStyles() );
$this->addModuleMessages( $parserOutput->getModuleMessages() );
+ $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
$this->mPreventClickjacking = $this->mPreventClickjacking
|| $parserOutput->preventClickjacking();
@@ -1620,11 +1712,11 @@ class OutputPage extends ContextSource {
}
// Hooks registered in the object
- global $wgParserOutputHooks;
+ $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
list( $hookName, $data ) = $hookInfo;
- if ( isset( $wgParserOutputHooks[$hookName] ) ) {
- call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data );
+ if ( isset( $parserOutputHooks[$hookName] ) ) {
+ call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data );
}
}
@@ -1636,43 +1728,72 @@ class OutputPage extends ContextSource {
}
/**
- * Add a ParserOutput object
+ * Add the HTML and enhancements for it (like ResourceLoader modules) associated with a
+ * ParserOutput object, without any other metadata.
*
- * @param $parserOutput ParserOutput
+ * @since 1.24
+ * @param ParserOutput $parserOutput
*/
- function addParserOutput( &$parserOutput ) {
- $this->addParserOutputNoText( $parserOutput );
- $parserOutput->setTOCEnabled( $this->mEnableTOC );
+ public function addParserOutputContent( $parserOutput ) {
+ $this->addParserOutputText( $parserOutput );
+
+ $this->addModules( $parserOutput->getModules() );
+ $this->addModuleScripts( $parserOutput->getModuleScripts() );
+ $this->addModuleStyles( $parserOutput->getModuleStyles() );
+ $this->addModuleMessages( $parserOutput->getModuleMessages() );
+
+ $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
+ }
+
+ /**
+ * Add the HTML associated with a ParserOutput object, without any metadata.
+ *
+ * @since 1.24
+ * @param ParserOutput $parserOutput
+ */
+ public function addParserOutputText( $parserOutput ) {
$text = $parserOutput->getText();
wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
$this->addHTML( $text );
}
/**
+ * Add everything from a ParserOutput object.
+ *
+ * @param ParserOutput $parserOutput
+ */
+ function addParserOutput( $parserOutput ) {
+ $this->addParserOutputMetadata( $parserOutput );
+ $parserOutput->setTOCEnabled( $this->mEnableTOC );
+
+ // Touch section edit links only if not previously disabled
+ if ( $parserOutput->getEditSectionTokens() ) {
+ $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks );
+ }
+
+ $this->addParserOutputText( $parserOutput );
+ }
+
+ /**
* Add the output of a QuickTemplate to the output buffer
*
- * @param $template QuickTemplate
+ * @param QuickTemplate $template
*/
public function addTemplate( &$template ) {
- ob_start();
- $template->execute();
- $this->addHTML( ob_get_contents() );
- ob_end_clean();
+ $this->addHTML( $template->getHTML() );
}
/**
* Parse wikitext and return the HTML.
*
- * @param $text String
- * @param $linestart Boolean: is this the start of a line?
- * @param $interface Boolean: use interface language ($wgLang instead of
- * $wgContLang) while parsing language sensitive magic
- * words like GRAMMAR and PLURAL. This also disables
- * LanguageConverter.
- * @param $language Language object: target language object, will override
- * $interface
+ * @param string $text
+ * @param bool $linestart Is this the start of a line?
+ * @param bool $interface Use interface language ($wgLang instead of
+ * $wgContLang) while parsing language sensitive magic words like GRAMMAR and PLURAL.
+ * This also disables LanguageConverter.
+ * @param Language $language Target language object, will override $interface
* @throws MWException
- * @return String: HTML
+ * @return string HTML
*/
public function parse( $text, $linestart = true, $interface = false, $language = null ) {
global $wgParser;
@@ -1689,7 +1810,7 @@ class OutputPage extends ContextSource {
$oldLang = $popts->setTargetLanguage( $language );
}
- $parserOutput = $wgParser->parse(
+ $parserOutput = $wgParser->getFreshParser()->parse(
$text, $this->getTitle(), $popts,
$linestart, true, $this->mRevisionId
);
@@ -1707,28 +1828,22 @@ class OutputPage extends ContextSource {
/**
* Parse wikitext, strip paragraphs, and return the HTML.
*
- * @param $text String
- * @param $linestart Boolean: is this the start of a line?
- * @param $interface Boolean: use interface language ($wgLang instead of
- * $wgContLang) while parsing language sensitive magic
- * words like GRAMMAR and PLURAL
- * @return String: HTML
+ * @param string $text
+ * @param bool $linestart Is this the start of a line?
+ * @param bool $interface Use interface language ($wgLang instead of
+ * $wgContLang) while parsing language sensitive magic
+ * words like GRAMMAR and PLURAL
+ * @return string HTML
*/
public function parseInline( $text, $linestart = true, $interface = false ) {
$parsed = $this->parse( $text, $linestart, $interface );
-
- $m = array();
- if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) {
- $parsed = $m[1];
- }
-
- return $parsed;
+ return Parser::stripOuterParagraph( $parsed );
}
/**
* Set the value of the "s-maxage" part of the "Cache-control" HTTP header
*
- * @param $maxage Integer: maximum cache time on the Squid, in seconds.
+ * @param int $maxage Maximum cache time on the Squid, in seconds.
*/
public function setSquidMaxage( $maxage ) {
$this->mSquidMaxage = $maxage;
@@ -1737,7 +1852,7 @@ class OutputPage extends ContextSource {
/**
* Use enableClientCache(false) to force it to send nocache headers
*
- * @param $state bool
+ * @param bool $state
*
* @return bool
*/
@@ -1748,20 +1863,20 @@ class OutputPage extends ContextSource {
/**
* Get the list of cookies that will influence on the cache
*
- * @return Array
+ * @return array
*/
function getCacheVaryCookies() {
- global $wgCookiePrefix, $wgCacheVaryCookies;
static $cookies;
if ( $cookies === null ) {
+ $config = $this->getConfig();
$cookies = array_merge(
array(
- "{$wgCookiePrefix}Token",
- "{$wgCookiePrefix}LoggedOut",
+ $config->get( 'CookiePrefix' ) . 'Token',
+ $config->get( 'CookiePrefix' ) . 'LoggedOut',
"forceHTTPS",
session_name()
),
- $wgCacheVaryCookies
+ $config->get( 'CacheVaryCookies' )
);
wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) );
}
@@ -1772,7 +1887,7 @@ class OutputPage extends ContextSource {
* Check if the request has a cache-varying cookie header
* If it does, it's very important that we don't allow public caching
*
- * @return Boolean
+ * @return bool
*/
function haveCacheVaryCookies() {
$cookieHeader = $this->getRequest()->getHeader( 'cookie' );
@@ -1794,8 +1909,8 @@ class OutputPage extends ContextSource {
/**
* Add an HTTP header that will influence on the cache
*
- * @param string $header header name
- * @param $option Array|null
+ * @param string $header Header name
+ * @param array|null $option
* @todo FIXME: Document the $option parameter; it appears to be for
* X-Vary-Options but what format is acceptable?
*/
@@ -1816,7 +1931,7 @@ class OutputPage extends ContextSource {
* Return a Vary: header on which to vary caches. Based on the keys of $mVaryHeader,
* such as Accept-Encoding or Cookie
*
- * @return String
+ * @return string
*/
public function getVaryHeader() {
return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) );
@@ -1825,7 +1940,7 @@ class OutputPage extends ContextSource {
/**
* Get a complete X-Vary-Options header
*
- * @return String
+ * @return string
*/
public function getXVO() {
$cvCookies = $this->getCacheVaryCookies();
@@ -1858,7 +1973,12 @@ class OutputPage extends ContextSource {
* /w/index.php?title=Main_page&variant=zh-cn should never be served.
*/
function addAcceptLanguage() {
- $lang = $this->getTitle()->getPageLanguage();
+ $title = $this->getTitle();
+ if ( !$title instanceof Title ) {
+ return;
+ }
+
+ $lang = $title->getPageLanguage();
if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
$variants = $lang->getVariants();
$aloption = array();
@@ -1889,7 +2009,7 @@ class OutputPage extends ContextSource {
* This is the default for special pages. If you display a CSRF-protected
* form on an ordinary view page, then you need to call this function.
*
- * @param $enable bool
+ * @param bool $enable
*/
public function preventClickjacking( $enable = true ) {
$this->mPreventClickjacking = $enable;
@@ -1908,7 +2028,7 @@ class OutputPage extends ContextSource {
* Get the prevent-clickjacking flag
*
* @since 1.24
- * @return boolean
+ * @return bool
*/
public function getPreventClickjacking() {
return $this->mPreventClickjacking;
@@ -1922,11 +2042,11 @@ class OutputPage extends ContextSource {
* @return string
*/
public function getFrameOptions() {
- global $wgBreakFrames, $wgEditPageFrameOptions;
- if ( $wgBreakFrames ) {
+ $config = $this->getConfig();
+ if ( $config->get( 'BreakFrames' ) ) {
return 'DENY';
- } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) {
- return $wgEditPageFrameOptions;
+ } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
+ return $config->get( 'EditPageFrameOptions' );
}
return false;
}
@@ -1935,10 +2055,9 @@ class OutputPage extends ContextSource {
* Send cache control HTTP headers
*/
public function sendCacheControl() {
- global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO;
-
$response = $this->getRequest()->response();
- if ( $wgUseETag && $this->mETag ) {
+ $config = $this->getConfig();
+ if ( $config->get( 'UseETag' ) && $this->mETag ) {
$response->header( "ETag: $this->mETag" );
}
@@ -1949,39 +2068,41 @@ class OutputPage extends ContextSource {
# maintain different caches for logged-in users and non-logged in ones
$response->header( $this->getVaryHeader() );
- if ( $wgUseXVO ) {
+ if ( $config->get( 'UseXVO' ) ) {
# Add an X-Vary-Options header for Squid with Wikimedia patches
$response->header( $this->getXVO() );
}
if ( $this->mEnableClientCache ) {
if (
- $wgUseSquid && session_id() == '' && !$this->isPrintable() &&
+ $config->get( 'UseSquid' ) && session_id() == '' && !$this->isPrintable() &&
$this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies()
) {
- if ( $wgUseESI ) {
+ if ( $config->get( 'UseESI' ) ) {
# We'll purge the proxy cache explicitly, but require end user agents
# to revalidate against the proxy on each visit.
# Surrogate-Control controls our Squid, Cache-Control downstream caches
- wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false );
+ wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", 'log' );
# start with a shorter timeout for initial testing
# header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
- $response->header( 'Surrogate-Control: max-age=' . $wgSquidMaxage . '+' . $this->mSquidMaxage . ', content="ESI/1.0"' );
+ $response->header( 'Surrogate-Control: max-age=' . $config->get( 'SquidMaxage' )
+ . '+' . $this->mSquidMaxage . ', content="ESI/1.0"' );
$response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
} else {
# We'll purge the proxy cache for anons explicitly, but require end user agents
# to revalidate against the proxy on each visit.
# IMPORTANT! The Squid needs to replace the Cache-Control header with
# Cache-Control: s-maxage=0, must-revalidate, max-age=0
- wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false );
+ wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", 'log' );
# start with a shorter timeout for initial testing
# header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
- $response->header( 'Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0' );
+ $response->header( 'Cache-Control: s-maxage=' . $this->mSquidMaxage
+ . ', must-revalidate, max-age=0' );
}
} else {
# We do want clients to cache if they can, but they *must* check for updates
# on revisiting the page.
- wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false );
+ wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", 'log' );
$response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
$response->header( "Cache-Control: private, must-revalidate, max-age=0" );
}
@@ -1989,7 +2110,7 @@ class OutputPage extends ContextSource {
$response->header( "Last-Modified: {$this->mLastModified}" );
}
} else {
- wfDebug( __METHOD__ . ": no caching **\n", false );
+ wfDebug( __METHOD__ . ": no caching **\n", 'log' );
# In general, the absence of a last modified header should be enough to prevent
# the client from using its cache. We send a few other things just to make sure.
@@ -2000,26 +2121,11 @@ class OutputPage extends ContextSource {
}
/**
- * Get the message associated with the HTTP response code $code
- *
- * @param $code Integer: status code
- * @return String or null: message or null if $code is not in the list of
- * messages
- *
- * @deprecated since 1.18 Use HttpStatus::getMessage() instead.
- */
- public static function getStatusMessage( $code ) {
- wfDeprecated( __METHOD__, '1.18' );
- return HttpStatus::getMessage( $code );
- }
-
- /**
* Finally, all the text has been munged and accumulated into
* the object, let's actually output it:
*/
public function output() {
- global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP,
- $wgUseAjax, $wgResponsiveImages;
+ global $wgLanguageCode;
if ( $this->mDoNothing ) {
return;
@@ -2028,6 +2134,7 @@ class OutputPage extends ContextSource {
wfProfileIn( __METHOD__ );
$response = $this->getRequest()->response();
+ $config = $this->getConfig();
if ( $this->mRedirect != '' ) {
# Standards require redirect URLs to be absolute
@@ -2038,19 +2145,19 @@ class OutputPage extends ContextSource {
if ( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
if ( $code == '301' || $code == '303' ) {
- if ( !$wgDebugRedirects ) {
+ if ( !$config->get( 'DebugRedirects' ) ) {
$message = HttpStatus::getMessage( $code );
$response->header( "HTTP/1.1 $code $message" );
}
$this->mLastModified = wfTimestamp( TS_RFC2822 );
}
- if ( $wgVaryOnXFP ) {
+ if ( $config->get( 'VaryOnXFP' ) ) {
$this->addVaryHeader( 'X-Forwarded-Proto' );
}
$this->sendCacheControl();
$response->header( "Content-Type: text/html; charset=utf-8" );
- if ( $wgDebugRedirects ) {
+ if ( $config->get( 'DebugRedirects' ) ) {
$url = htmlspecialchars( $redirect );
print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
print "<p>Location: <a href=\"$url\">$url</a></p>\n";
@@ -2072,9 +2179,13 @@ class OutputPage extends ContextSource {
# Buffer output; final headers may depend on later processing
ob_start();
- $response->header( "Content-type: $wgMimeType; charset=UTF-8" );
+ $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
$response->header( 'Content-language: ' . $wgLanguageCode );
+ // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
+ // jQuery etc. can work correctly.
+ $response->header( 'X-UA-Compatible: IE=Edge' );
+
// Prevent framing, if requested
$frameOptions = $this->getFrameOptions();
if ( $frameOptions ) {
@@ -2097,7 +2208,7 @@ class OutputPage extends ContextSource {
);
// Support for high-density display images if enabled
- if ( $wgResponsiveImages ) {
+ if ( $config->get( 'ResponsiveImages' ) ) {
$coreModules[] = 'mediawiki.hidpi';
}
@@ -2106,10 +2217,6 @@ class OutputPage extends ContextSource {
$this->addModules( $group );
}
MWDebug::addModules( $this );
- if ( $wgUseAjax ) {
- // FIXME: deprecate? - not clear why this is useful
- wfRunHooks( 'AjaxAddScript', array( &$this ) );
- }
// Hook that allows last minute changes to the output page, e.g.
// adding of CSS or Javascript by extensions.
@@ -2133,7 +2240,7 @@ class OutputPage extends ContextSource {
/**
* Actually output something with print.
*
- * @param string $ins the string to output
+ * @param string $ins The string to output
* @deprecated since 1.22 Use echo yourself.
*/
public function out( $ins ) {
@@ -2154,8 +2261,8 @@ class OutputPage extends ContextSource {
* indexing, clear the current text and redirect, set the page's title
* and optionally an custom HTML title (content of the "<title>" tag).
*
- * @param string|Message $pageTitle will be passed directly to setPageTitle()
- * @param string|Message $htmlTitle will be passed directly to setHTMLTitle();
+ * @param string|Message $pageTitle Will be passed directly to setPageTitle()
+ * @param string|Message $htmlTitle Will be passed directly to setHTMLTitle();
* optional, if not passed the "<title>" attribute will be
* based on $pageTitle
*/
@@ -2175,13 +2282,14 @@ class OutputPage extends ContextSource {
/**
* Output a standard error page
*
+ * showErrorPage( 'titlemsg', 'pagetextmsg' );
* showErrorPage( 'titlemsg', 'pagetextmsg', array( 'param1', 'param2' ) );
* showErrorPage( 'titlemsg', $messageObject );
- * showErrorPage( $titleMessageObj, $messageObject );
+ * showErrorPage( $titleMessageObject, $messageObject );
*
- * @param $title Mixed: message key (string) for page title, or a Message object
- * @param $msg Mixed: message key (string) for page text, or a Message object
- * @param array $params message parameters; ignored if $msg is a Message object
+ * @param string|Message $title Message key (string) for page title, or a Message object
+ * @param string|Message $msg Message key (string) for page text, or a Message object
+ * @param array $params Message parameters; ignored if $msg is a Message object
*/
public function showErrorPage( $title, $msg, $params = array() ) {
if ( !$title instanceof Message ) {
@@ -2191,6 +2299,11 @@ class OutputPage extends ContextSource {
$this->prepareErrorPage( $title );
if ( $msg instanceof Message ) {
+ if ( $params !== array() ) {
+ trigger_error( 'Argument ignored: $params. The message parameters argument '
+ . 'is discarded when the $msg argument is a Message object instead of '
+ . 'a string.', E_USER_NOTICE );
+ }
$this->addHTML( $msg->parseAsBlock() );
} else {
$this->addWikiMsgArray( $msg, $params );
@@ -2202,10 +2315,10 @@ class OutputPage extends ContextSource {
/**
* Output a standard permission error page
*
- * @param array $errors error message keys
- * @param string $action action that was denied or null if unknown
+ * @param array $errors Error message keys
+ * @param string $action Action that was denied or null if unknown
*/
- public function showPermissionsErrorPage( $errors, $action = null ) {
+ public function showPermissionsErrorPage( array $errors, $action = null ) {
// For some action (read, edit, create and upload), display a "login to do this action"
// error if all of the following conditions are met:
// 1. the user is not logged in
@@ -2275,7 +2388,7 @@ class OutputPage extends ContextSource {
* Display an error page indicating that a given version of MediaWiki is
* required to use it
*
- * @param $version Mixed: the version of MediaWiki needed to use the page
+ * @param mixed $version The version of MediaWiki needed to use the page
*/
public function versionRequired( $version ) {
$this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
@@ -2287,7 +2400,7 @@ class OutputPage extends ContextSource {
/**
* Display an error page noting that a given permission bit is required.
* @deprecated since 1.18, just throw the exception directly
- * @param string $permission key required
+ * @param string $permission Key required
* @throws PermissionsError
*/
public function permissionRequired( $permission ) {
@@ -2297,7 +2410,7 @@ class OutputPage extends ContextSource {
/**
* Produce the stock "please login to use the wiki" page
*
- * @deprecated in 1.19; throw the exception directly
+ * @deprecated since 1.19; throw the exception directly
*/
public function loginToUse() {
throw new PermissionsError( 'read' );
@@ -2306,11 +2419,11 @@ class OutputPage extends ContextSource {
/**
* Format a list of error messages
*
- * @param array $errors of arrays returned by Title::getUserPermissionsErrors
- * @param string $action action that was denied or null if unknown
- * @return String: the wikitext error-messages, formatted into a list.
+ * @param array $errors Array of arrays returned by Title::getUserPermissionsErrors
+ * @param string $action Action that was denied or null if unknown
+ * @return string The wikitext error-messages, formatted into a list.
*/
- public function formatPermissionsErrorMessage( $errors, $action = null ) {
+ public function formatPermissionsErrorMessage( array $errors, $action = null ) {
if ( $action == null ) {
$text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
} else {
@@ -2355,13 +2468,16 @@ class OutputPage extends ContextSource {
*
* @todo Needs to be split into multiple functions.
*
- * @param $source String: source code to show (or null).
- * @param $protected Boolean: is this a permissions error?
- * @param $reasons Array: list of reasons for this error, as returned by Title::getUserPermissionsErrors().
- * @param $action String: action that was denied or null if unknown
+ * @param string $source Source code to show (or null).
+ * @param bool $protected Is this a permissions error?
+ * @param array $reasons List of reasons for this error, as returned by
+ * Title::getUserPermissionsErrors().
+ * @param string $action Action that was denied or null if unknown
* @throws ReadOnlyError
*/
- public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
+ public function readOnlyPage( $source = null, $protected = false,
+ array $reasons = array(), $action = null
+ ) {
$this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
@@ -2432,12 +2548,12 @@ $templates
* then the warning is a bit more obvious. If the lag is
* lower than $wgSlaveLagWarning, then no warning is shown.
*
- * @param $lag Integer: slave lag
+ * @param int $lag Slave lag
*/
public function showLagWarning( $lag ) {
- global $wgSlaveLagWarning, $wgSlaveLagCritical;
- if ( $lag >= $wgSlaveLagWarning ) {
- $message = $lag < $wgSlaveLagCritical
+ $config = $this->getConfig();
+ if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
+ $message = $lag < $config->get( 'SlaveLagCritical' )
? 'lag-warn-normal'
: 'lag-warn-high';
$wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
@@ -2474,12 +2590,12 @@ $templates
/**
* Add a "return to" link pointing to a specified title
*
- * @param $title Title to link
- * @param array $query query string parameters
- * @param string $text text of the link (input is not escaped)
- * @param $options Options array to pass to Linker
+ * @param Title $title Title to link
+ * @param array $query Query string parameters
+ * @param string $text Text of the link (input is not escaped)
+ * @param array $options Options array to pass to Linker
*/
- public function addReturnTo( $title, $query = array(), $text = null, $options = array() ) {
+ public function addReturnTo( $title, array $query = array(), $text = null, $options = array() ) {
$link = $this->msg( 'returnto' )->rawParams(
Linker::link( $title, $text, array(), $query, $options ) )->escaped();
$this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
@@ -2489,9 +2605,9 @@ $templates
* Add a "return to" link pointing to a specified title,
* or the title indicated in the request, or else the main page
*
- * @param $unused
- * @param $returnto Title or String to return to
- * @param string $returntoquery query string for the return to link
+ * @param mixed $unused
+ * @param Title|string $returnto Title or String to return to
+ * @param string $returntoquery Query string for the return to link
*/
public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
if ( $returnto == null ) {
@@ -2519,20 +2635,20 @@ $templates
}
/**
- * @param $sk Skin The given Skin
- * @param $includeStyle Boolean: unused
- * @return String: The doctype, opening "<html>", and head element.
+ * @param Skin $sk The given Skin
+ * @param bool $includeStyle Unused
+ * @return string The doctype, opening "<html>", and head element.
*/
public function headElement( Skin $sk, $includeStyle = true ) {
- global $wgContLang, $wgMimeType;
+ global $wgContLang;
$userdir = $this->getLanguage()->getDir();
$sitedir = $wgContLang->getDir();
- $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
+ $ret = Html::htmlHeader( $sk->getHtmlElementAttributes() );
if ( $this->getHTMLTitle() == '' ) {
- $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) );
+ $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
}
$openHead = Html::openElement( 'head' );
@@ -2541,7 +2657,7 @@ $templates
$ret .= "$openHead\n";
}
- if ( !Html::isXmlMimeType( $wgMimeType ) ) {
+ if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
// Add <meta charset="UTF-8">
// This should be before <title> since it defines the charset used by
// text including the text inside <title>.
@@ -2550,17 +2666,23 @@ $templates
// Our XML declaration is output by Html::htmlHeader.
// http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type
// http://www.whatwg.org/html/semantics.html#charset
- $ret .= Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
+ $ret .= Html::element( 'meta', array( 'charset' => 'UTF-8' ) ) . "\n";
}
$ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
- $ret .= implode( "\n", array(
- $this->getHeadLinks(),
- $this->buildCssLinks(),
- $this->getHeadScripts(),
- $this->getHeadItems()
- ) );
+ foreach ( $this->getHeadLinksArray() as $item ) {
+ $ret .= $item . "\n";
+ }
+
+ // No newline after buildCssLinks since makeResourceLoaderLink did that already
+ $ret .= $this->buildCssLinks();
+
+ $ret .= $this->getHeadScripts() . "\n";
+
+ foreach ( $this->mHeadItems as $item ) {
+ $ret .= $item . "\n";
+ }
$closeHead = Html::closeElement( 'head' );
if ( $closeHead ) {
@@ -2581,7 +2703,8 @@ $templates
$bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
$bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
- $bodyClasses[] = 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
+ $bodyClasses[] =
+ 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
$bodyAttrs = array();
// While the implode() is not strictly needed, it's used for backwards compatibility
@@ -2604,27 +2727,34 @@ $templates
*/
public function getResourceLoader() {
if ( is_null( $this->mResourceLoader ) ) {
- $this->mResourceLoader = new ResourceLoader();
+ $this->mResourceLoader = new ResourceLoader( $this->getConfig() );
}
return $this->mResourceLoader;
}
/**
- * TODO: Document
- * @param $modules Array/string with the module name(s)
+ * @todo Document
+ * @param array|string $modules One or more module names
* @param string $only ResourceLoaderModule TYPE_ class constant
- * @param $useESI boolean
- * @param array $extraQuery with extra query parameters to add to each request. array( param => value )
- * @param $loadCall boolean If true, output an (asynchronous) mw.loader.load() call rather than a "<script src='...'>" tag
- * @return string html "<script>" and "<style>" tags
- */
- protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
- global $wgResourceLoaderUseESI;
-
+ * @param bool $useESI
+ * @param array $extraQuery Array with extra query parameters to add to each
+ * request. array( param => value ).
+ * @param bool $loadCall If true, output an (asynchronous) mw.loader.load()
+ * call rather than a "<script src='...'>" tag.
+ * @return string The html "<script>", "<link>" and "<style>" tags
+ */
+ protected function makeResourceLoaderLink( $modules, $only, $useESI = false,
+ array $extraQuery = array(), $loadCall = false
+ ) {
$modules = (array)$modules;
+ $links = array(
+ 'html' => '',
+ 'states' => array(),
+ );
+
if ( !count( $modules ) ) {
- return '';
+ return $links;
}
if ( count( $modules ) > 1 ) {
@@ -2635,20 +2765,23 @@ $templates
if ( ResourceLoader::inDebugMode() ) {
// Recursively call us for every item
- $links = '';
foreach ( $modules as $name ) {
- $links .= $this->makeResourceLoaderLink( $name, $only, $useESI );
+ $link = $this->makeResourceLoaderLink( $name, $only, $useESI );
+ $links['html'] .= $link['html'];
+ $links['states'] += $link['states'];
}
return $links;
}
}
+
if ( !is_null( $this->mTarget ) ) {
$extraQuery['target'] = $this->mTarget;
}
- // Create keyed-by-group list of module objects from modules list
- $groups = array();
+ // Create keyed-by-source and then keyed-by-group list of module objects from modules list
+ $sortedModules = array();
$resourceLoader = $this->getResourceLoader();
+ $resourceLoaderUseESI = $this->getConfig()->get( 'ResourceLoaderUseESI' );
foreach ( $modules as $name ) {
$module = $resourceLoader->getModule( $name );
# Check that we're allowed to include this module on this page
@@ -2657,156 +2790,196 @@ $templates
&& $only == ResourceLoaderModule::TYPE_SCRIPTS )
|| ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
&& $only == ResourceLoaderModule::TYPE_STYLES )
+ || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
+ && $only == ResourceLoaderModule::TYPE_COMBINED )
|| ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) )
) {
continue;
}
- $group = $module->getGroup();
- if ( !isset( $groups[$group] ) ) {
- $groups[$group] = array();
- }
- $groups[$group][$name] = $module;
+ $sortedModules[$module->getSource()][$module->getGroup()][$name] = $module;
}
- $links = '';
- foreach ( $groups as $group => $grpModules ) {
- // Special handling for user-specific groups
- $user = null;
- if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
- $user = $this->getUser()->getName();
- }
+ foreach ( $sortedModules as $source => $groups ) {
+ foreach ( $groups as $group => $grpModules ) {
+ // Special handling for user-specific groups
+ $user = null;
+ if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
+ $user = $this->getUser()->getName();
+ }
- // Create a fake request based on the one we are about to make so modules return
- // correct timestamp and emptiness data
- $query = ResourceLoader::makeLoaderQuery(
- array(), // modules; not determined yet
- $this->getLanguage()->getCode(),
- $this->getSkin()->getSkinName(),
- $user,
- null, // version; not determined yet
- ResourceLoader::inDebugMode(),
- $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
- $this->isPrintable(),
- $this->getRequest()->getBool( 'handheld' ),
- $extraQuery
- );
- $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
- // Extract modules that know they're empty
- $emptyModules = array();
- foreach ( $grpModules as $key => $module ) {
- if ( $module->isKnownEmpty( $context ) ) {
- $emptyModules[$key] = 'ready';
- unset( $grpModules[$key] );
+ // Create a fake request based on the one we are about to make so modules return
+ // correct timestamp and emptiness data
+ $query = ResourceLoader::makeLoaderQuery(
+ array(), // modules; not determined yet
+ $this->getLanguage()->getCode(),
+ $this->getSkin()->getSkinName(),
+ $user,
+ null, // version; not determined yet
+ ResourceLoader::inDebugMode(),
+ $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
+ $this->isPrintable(),
+ $this->getRequest()->getBool( 'handheld' ),
+ $extraQuery
+ );
+ $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
+
+
+ // Extract modules that know they're empty and see if we have one or more
+ // raw modules
+ $isRaw = false;
+ foreach ( $grpModules as $key => $module ) {
+ // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857)
+ // If we're only getting the styles, we don't need to do anything for empty modules.
+ if ( $module->isKnownEmpty( $context ) ) {
+ unset( $grpModules[$key] );
+ if ( $only !== ResourceLoaderModule::TYPE_STYLES ) {
+ $links['states'][$key] = 'ready';
+ }
+ }
+
+ $isRaw |= $module->isRaw();
}
- }
- // Inline empty modules: since they're empty, just mark them as 'ready'
- if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) {
- // If we're only getting the styles, we don't need to do anything for empty modules.
- $links .= Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- ResourceLoader::makeLoaderStateScript( $emptyModules )
- )
- ) . "\n";
- }
- // If there are no modules left, skip this group
- if ( count( $grpModules ) === 0 ) {
- continue;
- }
+ // If there are no non-empty modules, skip this group
+ if ( count( $grpModules ) === 0 ) {
+ continue;
+ }
- // Inline private modules. These can't be loaded through load.php for security
- // reasons, see bug 34907. Note that these modules should be loaded from
- // getHeadScripts() before the first loader call. Otherwise other modules can't
- // properly use them as dependencies (bug 30914)
- if ( $group === 'private' ) {
- if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
- $links .= Html::inlineStyle(
- $resourceLoader->makeModuleResponse( $context, $grpModules )
- );
- } else {
- $links .= Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
+ // Inline private modules. These can't be loaded through load.php for security
+ // reasons, see bug 34907. Note that these modules should be loaded from
+ // getHeadScripts() before the first loader call. Otherwise other modules can't
+ // properly use them as dependencies (bug 30914)
+ if ( $group === 'private' ) {
+ if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
+ $links['html'] .= Html::inlineStyle(
$resourceLoader->makeModuleResponse( $context, $grpModules )
- )
- );
+ );
+ } else {
+ $links['html'] .= Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ $resourceLoader->makeModuleResponse( $context, $grpModules )
+ )
+ );
+ }
+ $links['html'] .= "\n";
+ continue;
}
- $links .= "\n";
- continue;
- }
- // Special handling for the user group; because users might change their stuff
- // on-wiki like user pages, or user preferences; we need to find the highest
- // timestamp of these user-changeable modules so we can ensure cache misses on change
- // This should NOT be done for the site group (bug 27564) because anons get that too
- // and we shouldn't be putting timestamps in Squid-cached HTML
- $version = null;
- if ( $group === 'user' ) {
- // Get the maximum timestamp
- $timestamp = 1;
- foreach ( $grpModules as $module ) {
- $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
+
+ // Special handling for the user group; because users might change their stuff
+ // on-wiki like user pages, or user preferences; we need to find the highest
+ // timestamp of these user-changeable modules so we can ensure cache misses on change
+ // This should NOT be done for the site group (bug 27564) because anons get that too
+ // and we shouldn't be putting timestamps in Squid-cached HTML
+ $version = null;
+ if ( $group === 'user' ) {
+ // Get the maximum timestamp
+ $timestamp = 1;
+ foreach ( $grpModules as $module ) {
+ $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
+ }
+ // Add a version parameter so cache will break when things change
+ $query['version'] = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
}
- // Add a version parameter so cache will break when things change
- $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
- }
- $url = ResourceLoader::makeLoaderURL(
- array_keys( $grpModules ),
- $this->getLanguage()->getCode(),
- $this->getSkin()->getSkinName(),
- $user,
- $version,
- ResourceLoader::inDebugMode(),
- $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
- $this->isPrintable(),
- $this->getRequest()->getBool( 'handheld' ),
- $extraQuery
- );
- if ( $useESI && $wgResourceLoaderUseESI ) {
- $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
- if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
- $link = Html::inlineStyle( $esi );
+ $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $grpModules ) );
+ $moduleContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
+ $url = $resourceLoader->createLoaderURL( $source, $moduleContext, $extraQuery );
+
+ if ( $useESI && $resourceLoaderUseESI ) {
+ $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
+ if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
+ $link = Html::inlineStyle( $esi );
+ } else {
+ $link = Html::inlineScript( $esi );
+ }
} else {
- $link = Html::inlineScript( $esi );
+ // Automatically select style/script elements
+ if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
+ $link = Html::linkedStyle( $url );
+ } elseif ( $loadCall ) {
+ $link = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
+ )
+ );
+ } else {
+ $link = Html::linkedScript( $url );
+ if ( $context->getOnly() === 'scripts' && !$context->getRaw() && !$isRaw ) {
+ // Wrap only=script requests in a conditional as browsers not supported
+ // by the startup module would unconditionally execute this module.
+ // Otherwise users will get "ReferenceError: mw is undefined" or
+ // "jQuery is undefined" from e.g. a "site" module.
+ $link = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'document.write', array( $link ) )
+ )
+ );
+ }
+
+ // For modules requested directly in the html via <link> or <script>,
+ // tell mw.loader they are being loading to prevent duplicate requests.
+ foreach ( $grpModules as $key => $module ) {
+ // Don't output state=loading for the startup module..
+ if ( $key !== 'startup' ) {
+ $links['states'][$key] = 'loading';
+ }
+ }
+ }
}
- } else {
- // Automatically select style/script elements
- if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
- $link = Html::linkedStyle( $url );
- } elseif ( $loadCall ) {
- $link = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
- )
- );
+
+ if ( $group == 'noscript' ) {
+ $links['html'] .= Html::rawElement( 'noscript', array(), $link ) . "\n";
} else {
- $link = Html::linkedScript( $url );
+ $links['html'] .= $link . "\n";
}
}
+ }
- if ( $group == 'noscript' ) {
- $links .= Html::rawElement( 'noscript', array(), $link ) . "\n";
+ return $links;
+ }
+
+ /**
+ * Build html output from an array of links from makeResourceLoaderLink.
+ * @param array $links
+ * @return string HTML
+ */
+ protected static function getHtmlFromLoaderLinks( array $links ) {
+ $html = '';
+ $states = array();
+ foreach ( $links as $link ) {
+ if ( !is_array( $link ) ) {
+ $html .= $link;
} else {
- $links .= $link . "\n";
+ $html .= $link['html'];
+ $states += $link['states'];
}
}
- return $links;
+
+ if ( count( $states ) ) {
+ $html = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ ResourceLoader::makeLoaderStateScript( $states )
+ )
+ ) . "\n" . $html;
+ }
+
+ return $html;
}
/**
* JS stuff to put in the "<head>". This is the startup module, config
* vars and modules marked with position 'top'
*
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
function getHeadScripts() {
- global $wgResourceLoaderExperimentalAsyncLoading;
-
// Startup - this will immediately load jquery and mediawiki modules
- $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
+ $links = array();
+ $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
// Load config before anything else
- $scripts .= Html::inlineScript(
+ $links[] = Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
ResourceLoader::makeConfigSetScript( $this->getJSVars() )
)
@@ -2816,51 +2989,58 @@ $templates
// This needs to be TYPE_COMBINED so these modules are properly wrapped
// in mw.loader.implement() calls and deferred until mw.user is available
$embedScripts = array( 'user.options', 'user.tokens' );
- $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
+ $links[] = $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
- // Script and Messages "only" requests marked for top inclusion
+ // Scripts and messages "only" requests marked for top inclusion
// Messages should go first
- $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
- $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS );
+ $links[] = $this->makeResourceLoaderLink(
+ $this->getModuleMessages( true, 'top' ),
+ ResourceLoaderModule::TYPE_MESSAGES
+ );
+ $links[] = $this->makeResourceLoaderLink(
+ $this->getModuleScripts( true, 'top' ),
+ ResourceLoaderModule::TYPE_SCRIPTS
+ );
// Modules requests - let the client calculate dependencies and batch requests as it likes
// Only load modules that have marked themselves for loading at the top
$modules = $this->getModules( true, 'top' );
if ( $modules ) {
- $scripts .= Html::inlineScript(
+ $links[] = Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
)
);
}
- if ( $wgResourceLoaderExperimentalAsyncLoading ) {
- $scripts .= $this->getScriptsForBottomQueue( true );
+ if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
+ $links[] = $this->getScriptsForBottomQueue( true );
}
- return $scripts;
+ return self::getHtmlFromLoaderLinks( $links );
}
/**
- * JS stuff to put at the 'bottom', which can either be the bottom of the "<body>"
- * or the bottom of the "<head>" depending on $wgResourceLoaderExperimentalAsyncLoading:
- * modules marked with position 'bottom', legacy scripts ($this->mScripts),
- * user preferences, site JS and user JS
+ * JS stuff to put at the 'bottom', which can either be the bottom of the
+ * "<body>" or the bottom of the "<head>" depending on
+ * $wgResourceLoaderExperimentalAsyncLoading: modules marked with position
+ * 'bottom', legacy scripts ($this->mScripts), user preferences, site JS
+ * and user JS.
*
- * @param $inHead boolean If true, this HTML goes into the "<head>", if false it goes into the "<body>"
+ * @param bool $inHead If true, this HTML goes into the "<head>",
+ * if false it goes into the "<body>".
* @return string
*/
function getScriptsForBottomQueue( $inHead ) {
- global $wgUseSiteJs, $wgAllowUserJs;
-
- // Script and Messages "only" requests marked for bottom inclusion
+ // Scripts and messages "only" requests marked for bottom inclusion
// If we're in the <head>, use load() calls rather than <script src="..."> tags
// Messages should go first
- $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
+ $links = array();
+ $links[] = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
/* $loadCall = */ $inHead
);
- $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
+ $links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
/* $loadCall = */ $inHead
);
@@ -2869,7 +3049,7 @@ $templates
// Only load modules that have marked themselves for loading at the bottom
$modules = $this->getModules( true, 'bottom' );
if ( $modules ) {
- $scripts .= Html::inlineScript(
+ $links[] = Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
)
@@ -2877,88 +3057,46 @@ $templates
}
// Legacy Scripts
- $scripts .= "\n" . $this->mScripts;
-
- $defaultModules = array();
+ $links[] = "\n" . $this->mScripts;
// Add site JS if enabled
- if ( $wgUseSiteJs ) {
- $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
- /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
- $defaultModules['site'] = 'loading';
- } else {
- // Site module is empty, save request by marking ready in advance (bug 46857)
- $defaultModules['site'] = 'ready';
- }
+ $links[] = $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
// Add user JS if enabled
- if ( $wgAllowUserJs ) {
- if ( $this->getUser()->isLoggedIn() ) {
- if ( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
- # XXX: additional security check/prompt?
- // We're on a preview of a JS subpage
- // Exclude this page from the user module in case it's in there (bug 26283)
- $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
- array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
- );
- // Load the previewed JS
- $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
- // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
- // asynchronously and may arrive *after* the inline script here. So the previewed code
- // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
- } else {
- // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
- $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
- /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
- }
- $defaultModules['user'] = 'loading';
- } else {
- // Non-logged-in users have an empty user module.
- // Save request by marking ready in advance (bug 46857)
- $defaultModules['user'] = 'ready';
- }
+ if ( $this->getConfig()->get( 'AllowUserJs' )
+ && $this->getUser()->isLoggedIn()
+ && $this->getTitle()
+ && $this->getTitle()->isJsSubpage()
+ && $this->userCanPreview()
+ ) {
+ # XXX: additional security check/prompt?
+ // We're on a preview of a JS subpage
+ // Exclude this page from the user module in case it's in there (bug 26283)
+ $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
+ );
+ // Load the previewed JS
+ $links[] = Html::inlineScript( "\n"
+ . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
+
+ // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
+ // asynchronously and may arrive *after* the inline script here. So the previewed code
+ // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
} else {
- // User modules are disabled on this wiki.
- // Save request by marking ready in advance (bug 46857)
- $defaultModules['user'] = 'ready';
+ // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
+ $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
}
// Group JS is only enabled if site JS is enabled.
- if ( $wgUseSiteJs ) {
- if ( $this->getUser()->isLoggedIn() ) {
- $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
- /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
- $defaultModules['user.groups'] = 'loading';
- } else {
- // Non-logged-in users have no user.groups module.
- // Save request by marking ready in advance (bug 46857)
- $defaultModules['user.groups'] = 'ready';
- }
- } else {
- // Site (and group JS) disabled
- $defaultModules['user.groups'] = 'ready';
- }
+ $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
- $loaderInit = '';
- if ( $inHead ) {
- // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'.
- foreach ( $defaultModules as $m => $state ) {
- if ( $state == 'loading' ) {
- unset( $defaultModules[$m] );
- }
- }
- }
- if ( count( $defaultModules ) > 0 ) {
- $loaderInit = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- ResourceLoader::makeLoaderStateScript( $defaultModules )
- )
- ) . "\n";
- }
- return $loaderInit . $scripts;
+ return self::getHtmlFromLoaderLinks( $links );
}
/**
@@ -2966,15 +3104,13 @@ $templates
* @return string
*/
function getBottomScripts() {
- global $wgResourceLoaderExperimentalAsyncLoading;
-
// Optimise jQuery ready event cross-browser.
// This also enforces $.isReady to be true at </body> which fixes the
// mw.loader bug in Firefox with using document.write between </body>
// and the DOMContentReady event (bug 47457).
$html = Html::inlineScript( 'window.jQuery && jQuery.ready();' );
- if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
+ if ( !$this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
$html .= $this->getScriptsForBottomQueue( false );
}
@@ -2982,10 +3118,20 @@ $templates
}
/**
- * Add one or more variables to be set in mw.config in JavaScript.
+ * Get the javascript config vars to include on this page
+ *
+ * @return array Array of javascript config vars
+ * @since 1.23
+ */
+ public function getJsConfigVars() {
+ return $this->mJsConfigVars;
+ }
+
+ /**
+ * Add one or more variables to be set in mw.config in JavaScript
*
- * @param $keys {String|Array} Key or array of key/value pairs.
- * @param $value {Mixed} [optional] Value of the configuration variable.
+ * @param string|array $keys Key or array of key/value pairs
+ * @param mixed $value [optional] Value of the configuration variable
*/
public function addJsConfigVars( $keys, $value = null ) {
if ( is_array( $keys ) ) {
@@ -3001,16 +3147,13 @@ $templates
/**
* Get an array containing the variables to be set in mw.config in JavaScript.
*
- * DO NOT CALL THIS FROM OUTSIDE OF THIS CLASS OR Skin::makeGlobalVariablesScript().
- * This is only public until that function is removed. You have been warned.
- *
* Do not add things here which can be evaluated in ResourceLoaderStartUpModule
* - in other words, page-independent/site-wide variables (without state).
* You will only be adding bloat to the html page and causing page caches to
* have to be purged on configuration changes.
* @return array
*/
- public function getJSVars() {
+ private function getJSVars() {
global $wgContLang;
$curRevisionId = 0;
@@ -3019,14 +3162,19 @@ $templates
$title = $this->getTitle();
$ns = $title->getNamespace();
- $canonicalNamespace = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
+ $canonicalNamespace = MWNamespace::exists( $ns )
+ ? MWNamespace::getCanonicalName( $ns )
+ : $title->getNsText();
+ $sk = $this->getSkin();
// Get the relevant title so that AJAX features can use the correct page name
// when making API requests from certain special pages (bug 34972).
- $relevantTitle = $this->getSkin()->getRelevantTitle();
+ $relevantTitle = $sk->getRelevantTitle();
+ $relevantUser = $sk->getRelevantUser();
if ( $ns == NS_SPECIAL ) {
- list( $canonicalSpecialPageName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
+ list( $canonicalSpecialPageName, /*...*/ ) =
+ SpecialPageFactory::resolveAlias( $title->getDBkey() );
} elseif ( $this->canUseWikiPage() ) {
$wikiPage = $this->getWikiPage();
$curRevisionId = $wikiPage->getLatest();
@@ -3076,6 +3224,7 @@ $templates
'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
);
+
if ( $user->isLoggedIn() ) {
$vars['wgUserId'] = $user->getId();
$vars['wgUserEditCount'] = $user->getEditCount();
@@ -3086,21 +3235,30 @@ $templates
// the client side.
$vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
}
+
if ( $wgContLang->hasVariants() ) {
$vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
}
// Same test as SkinTemplate
- $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user ) && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
+ $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
+ && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
+
foreach ( $title->getRestrictionTypes() as $type ) {
$vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
}
+
if ( $title->isMainPage() ) {
$vars['wgIsMainPage'] = true;
}
+
if ( $this->mRedirectedFrom ) {
$vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
}
+ if ( $relevantUser ) {
+ $vars['wgRelevantUserName'] = $relevantUser->getName();
+ }
+
// Allow extensions to add their custom variables to the mw.config map.
// Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
// page-dependant but site-wide (without state).
@@ -3108,7 +3266,7 @@ $templates
wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
// Merge in variables from addJsConfigVars last
- return array_merge( $vars, $this->mJsConfigVars );
+ return array_merge( $vars, $this->getJsConfigVars() );
}
/**
@@ -3136,16 +3294,13 @@ $templates
}
/**
- * @return array in format "link name or number => 'link html'".
+ * @return array Array in format "link name or number => 'link html'".
*/
public function getHeadLinksArray() {
- global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
- $wgSitename, $wgVersion,
- $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
- $wgDisableLangConversion, $wgCanonicalLanguageLinks,
- $wgRightsPage, $wgRightsUrl;
+ global $wgVersion;
$tags = array();
+ $config = $this->getConfig();
$canonicalUrl = $this->mCanonicalUrl;
@@ -3188,7 +3343,7 @@ $templates
}
# Universal edit button
- if ( $wgUniversalEditButton && $this->isArticleRelated() ) {
+ if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
$user = $this->getUser();
if ( $this->getTitle()->quickUserCan( 'edit', $user )
&& ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
@@ -3198,13 +3353,13 @@ $templates
'rel' => 'alternate',
'type' => 'application/x-wiki',
'title' => $msg,
- 'href' => $this->getTitle()->getLocalURL( 'action=edit' )
+ 'href' => $this->getTitle()->getEditURL(),
) );
// Alternate edit link
$tags['alternative-edit'] = Html::element( 'link', array(
'rel' => 'edit',
'title' => $msg,
- 'href' => $this->getTitle()->getLocalURL( 'action=edit' )
+ 'href' => $this->getTitle()->getEditURL(),
) );
}
}
@@ -3213,12 +3368,18 @@ $templates
# should not matter, but Konqueror (3.5.9 at least) incorrectly
# uses whichever one appears later in the HTML source. Make sure
# apple-touch-icon is specified first to avoid this.
- if ( $wgAppleTouchIcon !== false ) {
- $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
+ if ( $config->get( 'AppleTouchIcon' ) !== false ) {
+ $tags['apple-touch-icon'] = Html::element( 'link', array(
+ 'rel' => 'apple-touch-icon',
+ 'href' => $config->get( 'AppleTouchIcon' )
+ ) );
}
- if ( $wgFavicon !== false ) {
- $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
+ if ( $config->get( 'Favicon' ) !== false ) {
+ $tags['favicon'] = Html::element( 'link', array(
+ 'rel' => 'shortcut icon',
+ 'href' => $config->get( 'Favicon' )
+ ) );
}
# OpenSearch description link
@@ -3229,7 +3390,7 @@ $templates
'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
) );
- if ( $wgEnableAPI ) {
+ if ( $config->get( 'EnableAPI' ) ) {
# Real Simple Discovery link, provides auto-discovery information
# for the MediaWiki API (and potentially additional custom API
# support such as WordPress or Twitter-compatible APIs for a
@@ -3239,44 +3400,46 @@ $templates
'type' => 'application/rsd+xml',
// Output a protocol-relative URL here if $wgServer is protocol-relative
// Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though
- 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ),
+ 'href' => wfExpandUrl( wfAppendQuery(
+ wfScript( 'api' ),
+ array( 'action' => 'rsd' ) ),
+ PROTO_RELATIVE
+ ),
) );
}
# Language variants
- if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) {
+ if ( !$config->get( 'DisableLangConversion' ) ) {
$lang = $this->getTitle()->getPageLanguage();
if ( $lang->hasVariants() ) {
-
- $urlvar = $lang->getURLVariant();
-
- if ( !$urlvar ) {
- $variants = $lang->getVariants();
- foreach ( $variants as $_v ) {
- $tags["variant-$_v"] = Html::element( 'link', array(
- 'rel' => 'alternate',
- 'hreflang' => wfBCP47( $_v ),
- 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
- );
- }
- } else {
- $canonicalUrl = $this->getTitle()->getLocalURL();
+ $variants = $lang->getVariants();
+ foreach ( $variants as $_v ) {
+ $tags["variant-$_v"] = Html::element( 'link', array(
+ 'rel' => 'alternate',
+ 'hreflang' => wfBCP47( $_v ),
+ 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
+ );
}
}
+ # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
+ $tags["variant-x-default"] = Html::element( 'link', array(
+ 'rel' => 'alternate',
+ 'hreflang' => 'x-default',
+ 'href' => $this->getTitle()->getLocalURL() ) );
}
# Copyright
$copyright = '';
- if ( $wgRightsPage ) {
- $copy = Title::newFromText( $wgRightsPage );
+ if ( $config->get( 'RightsPage' ) ) {
+ $copy = Title::newFromText( $config->get( 'RightsPage' ) );
if ( $copy ) {
$copyright = $copy->getLocalURL();
}
}
- if ( !$copyright && $wgRightsUrl ) {
- $copyright = $wgRightsUrl;
+ if ( !$copyright && $config->get( 'RightsUrl' ) ) {
+ $copyright = $config->get( 'RightsUrl' );
}
if ( $copyright ) {
@@ -3287,7 +3450,7 @@ $templates
}
# Feeds
- if ( $wgFeed ) {
+ if ( $config->get( 'Feed' ) ) {
foreach ( $this->getSyndicationLinks() as $format => $link ) {
# Use the page name for the title. In principle, this could
# lead to issues with having the same name for different feeds
@@ -3309,30 +3472,31 @@ $templates
# like to promote instead of the RC feed (maybe like a "Recent New Articles"
# or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
# If so, use it instead.
- if ( $wgOverrideSiteFeed ) {
- foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
+ $sitename = $config->get( 'Sitename' );
+ if ( $config->get( 'OverrideSiteFeed' ) ) {
+ foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) {
// Note, this->feedLink escapes the url.
$tags[] = $this->feedLink(
$type,
$feedUrl,
- $this->msg( "site-{$type}-feed", $wgSitename )->text()
+ $this->msg( "site-{$type}-feed", $sitename )->text()
);
}
} elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
$rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
- foreach ( $wgAdvertisedFeedTypes as $format ) {
+ foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) {
$tags[] = $this->feedLink(
$format,
$rctitle->getLocalURL( array( 'feed' => $format ) ),
- $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'.
+ # For grep: 'site-rss-feed', 'site-atom-feed'
+ $this->msg( "site-{$format}-feed", $sitename )->text()
);
}
}
}
# Canonical URL
- global $wgEnableCanonicalServerLink;
- if ( $wgEnableCanonicalServerLink ) {
+ if ( $config->get( 'EnableCanonicalServerLink' ) ) {
if ( $canonicalUrl !== false ) {
$canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
} else {
@@ -3352,18 +3516,21 @@ $templates
/**
* @return string HTML tag links to be put in the header.
+ * @deprecated since 1.24 Use OutputPage::headElement or if you have to,
+ * OutputPage::getHeadLinksArray directly.
*/
public function getHeadLinks() {
+ wfDeprecated( __METHOD__, '1.24' );
return implode( "\n", $this->getHeadLinksArray() );
}
/**
* Generate a "<link rel/>" for a feed.
*
- * @param string $type feed type
+ * @param string $type Feed type
* @param string $url URL to the feed
- * @param string $text value of the "title" attribute
- * @return String: HTML fragment
+ * @param string $text Value of the "title" attribute
+ * @return string HTML fragment
*/
private function feedLink( $type, $url, $text ) {
return Html::element( 'link', array(
@@ -3379,9 +3546,9 @@ $templates
* Meant primarily for internal use...
*
* @param string $style URL to the file
- * @param string $media to specify a media type, 'screen', 'printable', 'handheld' or any.
- * @param string $condition for IE conditional comments, specifying an IE version
- * @param string $dir set to 'rtl' or 'ltr' for direction-specific sheets
+ * @param string $media To specify a media type, 'screen', 'printable', 'handheld' or any.
+ * @param string $condition For IE conditional comments, specifying an IE version
+ * @param string $dir Set to 'rtl' or 'ltr' for direction-specific sheets
*/
public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
$options = array();
@@ -3401,7 +3568,7 @@ $templates
/**
* Adds inline CSS styles
- * @param $style_css Mixed: inline CSS
+ * @param mixed $style_css Inline CSS
* @param string $flip Set to 'flip' to flip the CSS if needed
*/
public function addInlineStyle( $style_css, $flip = 'noflip' ) {
@@ -3409,7 +3576,7 @@ $templates
# If wanted, and the interface is right-to-left, flip the CSS
$style_css = CSSJanus::transform( $style_css, true, false );
}
- $this->mInlineStyles .= Html::inlineStyle( $style_css );
+ $this->mInlineStyles .= Html::inlineStyle( $style_css ) . "\n";
}
/**
@@ -3419,54 +3586,54 @@ $templates
* @return string
*/
public function buildCssLinks() {
- global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, $wgContLang;
+ global $wgContLang;
$this->getSkin()->setupSkinUserCss( $this );
// Add ResourceLoader styles
- // Split the styles into four groups
- $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() );
+ // Split the styles into these groups
+ $styles = array(
+ 'other' => array(),
+ 'user' => array(),
+ 'site' => array(),
+ 'private' => array(),
+ 'noscript' => array()
+ );
+ $links = array();
$otherTags = ''; // Tags to append after the normal <link> tags
$resourceLoader = $this->getResourceLoader();
$moduleStyles = $this->getModuleStyles();
// Per-site custom styles
- if ( $wgUseSiteCss ) {
- $moduleStyles[] = 'site';
- $moduleStyles[] = 'noscript';
- if ( $this->getUser()->isLoggedIn() ) {
- $moduleStyles[] = 'user.groups';
- }
- }
+ $moduleStyles[] = 'site';
+ $moduleStyles[] = 'noscript';
+ $moduleStyles[] = 'user.groups';
// Per-user custom styles
- if ( $wgAllowUserCss ) {
- if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
- // We're on a preview of a CSS subpage
- // Exclude this page from the user module in case it's in there (bug 26283)
- $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
- array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
- );
-
- // Load the previewed CSS
- // If needed, Janus it first. This is user-supplied CSS, so it's
- // assumed to be right for the content language directionality.
- $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
- if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
- $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
- }
- $otherTags .= Html::inlineStyle( $previewedCSS );
- } else {
- // Load the user styles normally
- $moduleStyles[] = 'user';
+ if ( $this->getConfig()->get( 'AllowUserCss' ) && $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
+ // We're on a preview of a CSS subpage
+ // Exclude this page from the user module in case it's in there (bug 26283)
+ $link = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
+ );
+ $otherTags .= $link['html'];
+
+ // Load the previewed CSS
+ // If needed, Janus it first. This is user-supplied CSS, so it's
+ // assumed to be right for the content language directionality.
+ $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
+ if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
+ $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
}
+ $otherTags .= Html::inlineStyle( $previewedCSS ) . "\n";
+ } else {
+ // Load the user styles normally
+ $moduleStyles[] = 'user';
}
// Per-user preference styles
- if ( $wgAllowUserCssPrefs ) {
- $moduleStyles[] = 'user.cssprefs';
- }
+ $moduleStyles[] = 'user.cssprefs';
foreach ( $moduleStyles as $name ) {
$module = $resourceLoader->getModule( $name );
@@ -3474,38 +3641,41 @@ $templates
continue;
}
$group = $module->getGroup();
- // Modules in groups named "other" or anything different than "user", "site" or "private"
+ // Modules in groups different than the ones listed on top (see $styles assignment)
// will be placed in the "other" group
$styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
}
- // We want site, private and user styles to override dynamically added styles from modules, but we want
- // dynamically added styles to override statically added styles from other modules. So the order
- // has to be other, dynamic, site, private, user
- // Add statically added styles for other modules
- $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES );
+ // We want site, private and user styles to override dynamically added
+ // styles from modules, but we want dynamically added styles to override
+ // statically added styles from other modules. So the order has to be
+ // other, dynamic, site, private, user. Add statically added styles for
+ // other modules
+ $links[] = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES );
// Add normal styles added through addStyle()/addInlineStyle() here
- $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
+ $links[] = implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
// Add marker tag to mark the place where the client-side loader should inject dynamic styles
// We use a <meta> tag with a made-up name for this because that's valid HTML
- $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n";
+ $links[] = Html::element(
+ 'meta',
+ array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' )
+ ) . "\n";
// Add site, private and user styles
// 'private' at present only contains user.options, so put that before 'user'
// Any future private modules will likely have a similar user-specific character
foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
- $ret .= $this->makeResourceLoaderLink( $styles[$group],
- ResourceLoaderModule::TYPE_STYLES
+ $links[] = $this->makeResourceLoaderLink( $styles[$group],
+ ResourceLoaderModule::TYPE_STYLES
);
}
// Add stuff in $otherTags (previewed user CSS if applicable)
- $ret .= $otherTags;
- return $ret;
+ return self::getHtmlFromLoaderLinks( $links ) . $otherTags;
}
/**
- * @return Array
+ * @return array
*/
public function buildCssLinksArray() {
$links = array();
@@ -3529,11 +3699,10 @@ $templates
* Generate \<link\> tags for stylesheets
*
* @param string $style URL to the file
- * @param array $options option, can contain 'condition', 'dir', 'media'
- * keys
- * @return String: HTML fragment
+ * @param array $options Option, can contain 'condition', 'dir', 'media' keys
+ * @return string HTML fragment
*/
- protected function styleLink( $style, $options ) {
+ protected function styleLink( $style, array $options ) {
if ( isset( $options['dir'] ) ) {
if ( $this->getLanguage()->getDir() != $options['dir'] ) {
return '';
@@ -3554,8 +3723,8 @@ $templates
substr( $style, 0, 6 ) == 'https:' ) {
$url = $style;
} else {
- global $wgStylePath, $wgStyleVersion;
- $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
+ $config = $this->getConfig();
+ $url = $config->get( 'StylePath' ) . '/' . $style . '?' . $config->get( 'StyleVersion' );
}
$link = Html::linkedStyle( $url, $media );
@@ -3570,8 +3739,8 @@ $templates
/**
* Transform "media" attribute based on request parameters
*
- * @param string $media current value of the "media" attribute
- * @return String: modified value of the "media" attribute, or null to skip
+ * @param string $media Current value of the "media" attribute
+ * @return string Modified value of the "media" attribute, or null to skip
* this stylesheet
*/
public static function transformCssMedia( $media ) {
@@ -3592,8 +3761,10 @@ $templates
} elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
// This regex will not attempt to understand a comma-separated media_query_list
//
- // Example supported values for $media: 'screen', 'only screen', 'screen and (min-width: 982px)' ),
- // Example NOT supported value for $media: '3d-glasses, screen, print and resolution > 90dpi'
+ // Example supported values for $media:
+ // 'screen', 'only screen', 'screen and (min-width: 982px)' ),
+ // Example NOT supported value for $media:
+ // '3d-glasses, screen, print and resolution > 90dpi'
//
// If it's a print request, we never want any kind of screen stylesheets
// If it's a handheld request (currently the only other choice with a switch),
@@ -3627,8 +3798,8 @@ $templates
* Like addWikiMsg() except the parameters are taken as an array
* instead of a variable argument list.
*
- * @param $name string
- * @param $args array
+ * @param string $name
+ * @param array $args
*/
public function addWikiMsgArray( $name, $args ) {
$this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
@@ -3651,11 +3822,12 @@ $templates
*
* Is equivalent to:
*
- * $wgOut->addWikiText( "<div class='error'>\n" . wfMessage( 'some-error' )->plain() . "\n</div>" );
+ * $wgOut->addWikiText( "<div class='error'>\n"
+ * . wfMessage( 'some-error' )->plain() . "\n</div>" );
*
* The newline after opening div is needed in some wikitext. See bug 19226.
*
- * @param $wrap string
+ * @param string $wrap
*/
public function wrapWikiMsg( $wrap /*, ...*/ ) {
$msgSpecs = func_get_args();
@@ -3686,12 +3858,12 @@ $templates
* Include jQuery core. Use this to avoid loading it multiple times
* before we get a usable script loader.
*
- * @param array $modules list of jQuery modules which should be loaded
- * @return Array: the list of modules which were not loaded.
+ * @param array $modules List of jQuery modules which should be loaded
+ * @return array The list of modules which were not loaded.
* @since 1.16
* @deprecated since 1.17
*/
- public function includeJQuery( $modules = array() ) {
+ public function includeJQuery( array $modules = array() ) {
return array();
}
@@ -3711,4 +3883,21 @@ $templates
public function isTOCEnabled() {
return $this->mEnableTOC;
}
+
+ /**
+ * Enables/disables section edit links, doesn't override __NOEDITSECTION__
+ * @param bool $flag
+ * @since 1.23
+ */
+ public function enableSectionEditLinks( $flag = true ) {
+ $this->mEnableSectionEditLinks = $flag;
+ }
+
+ /**
+ * @return bool
+ * @since 1.23
+ */
+ public function sectionEditLinksEnabled() {
+ return $this->mEnableSectionEditLinks;
+ }
}
diff --git a/includes/PHPVersionError.php b/includes/PHPVersionError.php
index 02d3546f..f481650c 100644
--- a/includes/PHPVersionError.php
+++ b/includes/PHPVersionError.php
@@ -32,26 +32,35 @@
* - index.php
* - load.php
* - api.php
+ * - mw-config/index.php
* - cli
*
* @note Since we can't rely on anything, the minimum PHP versions and MW current
* version are hardcoded here
*/
function wfPHPVersionError( $type ) {
- $mwVersion = '1.22';
+ $mwVersion = '1.24';
$minimumVersionPHP = '5.3.2';
- $phpVersion = phpversion();
+ $phpVersion = PHP_VERSION;
$protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';
- $message = "MediaWiki $mwVersion requires at least PHP version $minimumVersionPHP, you are using PHP $phpVersion.";
+ $message = "MediaWiki $mwVersion requires at least "
+ . "PHP version $minimumVersionPHP, you are using PHP $phpVersion.";
+
if ( $type == 'cli' ) {
- $finalOutput = "You are using PHP version $phpVersion but MediaWiki $mwVersion needs PHP $minimumVersionPHP or higher. ABORTING.\n" .
- "Check if you have a newer php executable with a different name, such as php5.\n";
- } elseif ( $type == 'index.php' ) {
+ $finalOutput = "You are using PHP version $phpVersion "
+ . "but MediaWiki $mwVersion needs PHP $minimumVersionPHP or higher. ABORTING.\n"
+ . "Check if you have a newer php executable with a different name, such as php5.\n";
+ } elseif ( $type == 'index.php' || $type == 'mw-config/index.php' ) {
$pathinfo = pathinfo( $_SERVER['SCRIPT_NAME'] );
+ if ( $type == 'mw-config/index.php' ) {
+ $dirname = dirname( $pathinfo['dirname'] );
+ } else {
+ $dirname = $pathinfo['dirname'];
+ }
$encLogo = htmlspecialchars(
- str_replace( '//', '/', $pathinfo['dirname'] . '/' ) .
- 'skins/common/images/mediawiki.png'
+ str_replace( '//', '/', $dirname . '/' ) .
+ 'resources/assets/mediawiki.png'
);
header( "$protocol 500 MediaWiki configuration Error" );
@@ -97,9 +106,9 @@ function wfPHPVersionError( $type ) {
</p>
<p>
If for some reason you are unable to upgrade your PHP version, you will need to
- <a href="http://www.mediawiki.org/wiki/Download">download</a> an older version
+ <a href="https://www.mediawiki.org/wiki/Download">download</a> an older version
of MediaWiki from our website. See our
- <a href="http://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a>
+ <a href="https://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a>
for details of which versions are compatible with prior versions of PHP.
</p>
</div>
diff --git a/includes/PathRouter.php b/includes/PathRouter.php
index 435e09ef..e5979b8d 100644
--- a/includes/PathRouter.php
+++ b/includes/PathRouter.php
@@ -27,7 +27,7 @@
*
* $router->add( "/wiki/$1" );
* - Matches /wiki/Foo style urls and extracts the title
- * $router->add( array( 'edit' => "/edit/$1" ), array( 'action' => '$key' ) );
+ * $router->add( array( 'edit' => "/edit/$key" ), array( 'action' => '$key' ) );
* - Matches /edit/Foo style urls and sets action=edit
* $router->add( '/$2/$1',
* array( 'variant' => '$2' ),
@@ -82,10 +82,10 @@ class PathRouter {
* This is in a separate method so that add() can handle the difference between
* a single string $path and an array() $path that contains multiple path
* patterns each with an associated $key to pass on.
- * @param $path string
- * @param $params array
- * @param $options array
- * @param $key null|string
+ * @param string $path
+ * @param array $params
+ * @param array $options
+ * @param null|string $key
*/
protected function doAdd( $path, $params, $options, $key = null ) {
// Make sure all paths start with a /
@@ -170,9 +170,9 @@ class PathRouter {
/**
* Add a new path pattern to the path router with the strict option on
* @see self::add
- * @param $path string|array
- * @param $params array
- * @param $options array
+ * @param string|array $path
+ * @param array $params
+ * @param array $options
*/
public function addStrict( $path, $params = array(), $options = array() ) {
$options['strict'] = true;
@@ -192,7 +192,7 @@ class PathRouter {
}
/**
- * @param $pattern object
+ * @param object $pattern
* @return float|int
*/
protected static function makeWeight( $pattern ) {
@@ -233,7 +233,7 @@ class PathRouter {
* Parse a path and return the query matches for the path
*
* @param string $path The path to parse
- * @return Array The array of matches for the path
+ * @return array The array of matches for the path
*/
public function parse( $path ) {
// Make sure our patterns are sorted by weight so the most specific
@@ -257,8 +257,8 @@ class PathRouter {
}
/**
- * @param $path string
- * @param $pattern string
+ * @param string $path
+ * @param string $pattern
* @return array|null
*/
protected static function extractTitle( $path, $pattern ) {
@@ -363,7 +363,7 @@ class PathRouterPatternReplacer {
* We do this inside of a replacement callback because after replacement we can't tell the
* difference between a $1 that was not replaced and a $1 that was part of
* the content a $1 was replaced with.
- * @param $value string
+ * @param string $value
* @return string
*/
public function replace( $value ) {
@@ -376,7 +376,7 @@ class PathRouterPatternReplacer {
}
/**
- * @param $m array
+ * @param array $m
* @return string
*/
protected function callback( $m ) {
diff --git a/includes/PoolCounter.php b/includes/PoolCounter.php
deleted file mode 100644
index 2dac9388..00000000
--- a/includes/PoolCounter.php
+++ /dev/null
@@ -1,329 +0,0 @@
-<?php
-/**
- * Provides of semaphore semantics for restricting the number
- * of workers that may be concurrently performing the same task.
- *
- * 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
- */
-
-/**
- * When you have many workers (threads/servers) giving service, and a
- * cached item expensive to produce expires, you may get several workers
- * doing the job at the same time.
- *
- * Given enough requests and the item expiring fast (non-cacheable,
- * lots of edits...) that single work can end up unfairly using most (all)
- * of the cpu of the pool. This is also known as 'Michael Jackson effect'
- * since this effect triggered on the english wikipedia on the day Michael
- * Jackson died, the biographical article got hit with several edits per
- * minutes and hundreds of read hits.
- *
- * The PoolCounter provides semaphore semantics for restricting the number
- * of workers that may be concurrently performing such single task.
- *
- * By default PoolCounter_Stub is used, which provides no locking. You
- * can get a useful one in the PoolCounter extension.
- */
-abstract class PoolCounter {
- /* Return codes */
- const LOCKED = 1; /* Lock acquired */
- const RELEASED = 2; /* Lock released */
- const DONE = 3; /* Another worker did the work for you */
-
- const ERROR = -1; /* Indeterminate error */
- const NOT_LOCKED = -2; /* Called release() with no lock held */
- const QUEUE_FULL = -3; /* There are already maxqueue workers on this lock */
- const TIMEOUT = -4; /* Timeout exceeded */
- const LOCK_HELD = -5; /* Cannot acquire another lock while you have one lock held */
-
- /** @var string All workers with the same key share the lock */
- protected $key;
- /** @var integer Maximum number of workers doing the task simultaneously */
- protected $workers;
- /** @var integer If this number of workers are already working/waiting, fail instead of wait */
- protected $maxqueue;
- /** @var float Maximum time in seconds to wait for the lock */
- protected $timeout;
-
- /**
- * @param array $conf
- * @param string $type
- * @param string $key
- */
- protected function __construct( $conf, $type, $key ) {
- $this->key = $key;
- $this->workers = $conf['workers'];
- $this->maxqueue = $conf['maxqueue'];
- $this->timeout = $conf['timeout'];
- }
-
- /**
- * Create a Pool counter. This should only be called from the PoolWorks.
- *
- * @param $type
- * @param $key
- *
- * @return PoolCounter
- */
- public static function factory( $type, $key ) {
- global $wgPoolCounterConf;
- if ( !isset( $wgPoolCounterConf[$type] ) ) {
- return new PoolCounter_Stub;
- }
- $conf = $wgPoolCounterConf[$type];
- $class = $conf['class'];
-
- return new $class( $conf, $type, $key );
- }
-
- /**
- * I want to do this task and I need to do it myself.
- *
- * @return Status Value is one of Locked/Error
- */
- abstract public function acquireForMe();
-
- /**
- * I want to do this task, but if anyone else does it
- * instead, it's also fine for me. I will read its cached data.
- *
- * @return Status Value is one of Locked/Done/Error
- */
- abstract public function acquireForAnyone();
-
- /**
- * I have successfully finished my task.
- * Lets another one grab the lock, and returns the workers
- * waiting on acquireForAnyone()
- *
- * @return Status value is one of Released/NotLocked/Error
- */
- abstract public function release();
-}
-
-class PoolCounter_Stub extends PoolCounter {
- public function __construct() {
- /* No parameters needed */
- }
-
- public function acquireForMe() {
- return Status::newGood( PoolCounter::LOCKED );
- }
-
- public function acquireForAnyone() {
- return Status::newGood( PoolCounter::LOCKED );
- }
-
- public function release() {
- return Status::newGood( PoolCounter::RELEASED );
- }
-}
-
-/**
- * Class for dealing with PoolCounters using class members
- */
-abstract class PoolCounterWork {
- protected $cacheable = false; //Does this override getCachedWork() ?
-
- /**
- * @param string $type The type of PoolCounter to use
- * @param string $key Key that identifies the queue this work is placed on
- */
- public function __construct( $type, $key ) {
- $this->poolCounter = PoolCounter::factory( $type, $key );
- }
-
- /**
- * Actually perform the work, caching it if needed
- * @return mixed work result or false
- */
- abstract public function doWork();
-
- /**
- * Retrieve the work from cache
- * @return mixed work result or false
- */
- public function getCachedWork() {
- return false;
- }
-
- /**
- * A work not so good (eg. expired one) but better than an error
- * message.
- * @return mixed work result or false
- */
- public function fallback() {
- return false;
- }
-
- /**
- * Do something with the error, like showing it to the user.
- * @return bool
- */
- function error( $status ) {
- return false;
- }
-
- /**
- * Log an error
- *
- * @param $status Status
- * @return void
- */
- function logError( $status ) {
- wfDebugLog( 'poolcounter', $status->getWikiText() );
- }
-
- /**
- * Get the result of the work (whatever it is), or the result of the error() function.
- * This returns the result of the first applicable method that returns a non-false value,
- * where the methods are checked in the following order:
- * - a) doWork() : Applies if the work is exclusive or no another process
- * is doing it, and on the condition that either this process
- * successfully entered the pool or the pool counter is down.
- * - b) doCachedWork() : Applies if the work is cacheable and this blocked on another
- * process which finished the work.
- * - c) fallback() : Applies for all remaining cases.
- * If these all fall through (by returning false), then the result of error() is returned.
- *
- * @param $skipcache bool
- * @return mixed
- */
- public function execute( $skipcache = false ) {
- if ( $this->cacheable && !$skipcache ) {
- $status = $this->poolCounter->acquireForAnyone();
- } else {
- $status = $this->poolCounter->acquireForMe();
- }
-
- if ( !$status->isOK() ) {
- // Respond gracefully to complete server breakage: just log it and do the work
- $this->logError( $status );
- return $this->doWork();
- }
-
- switch ( $status->value ) {
- case PoolCounter::LOCKED:
- $result = $this->doWork();
- $this->poolCounter->release();
- return $result;
-
- case PoolCounter::DONE:
- $result = $this->getCachedWork();
- if ( $result === false ) {
- /* That someone else work didn't serve us.
- * Acquire the lock for me
- */
- return $this->execute( true );
- }
- return $result;
-
- case PoolCounter::QUEUE_FULL:
- case PoolCounter::TIMEOUT:
- $result = $this->fallback();
-
- if ( $result !== false ) {
- return $result;
- }
- /* no break */
-
- /* These two cases should never be hit... */
- case PoolCounter::ERROR:
- default:
- $errors = array(
- PoolCounter::QUEUE_FULL => 'pool-queuefull',
- PoolCounter::TIMEOUT => 'pool-timeout' );
-
- $status = Status::newFatal( isset( $errors[$status->value] )
- ? $errors[$status->value]
- : 'pool-errorunknown' );
- $this->logError( $status );
- return $this->error( $status );
- }
- }
-}
-
-/**
- * Convenience class for dealing with PoolCounters using callbacks
- * @since 1.22
- */
-class PoolCounterWorkViaCallback extends PoolCounterWork {
- /** @var callable */
- protected $doWork;
- /** @var callable|null */
- protected $doCachedWork;
- /** @var callable|null */
- protected $fallback;
- /** @var callable|null */
- protected $error;
-
- /**
- * Build a PoolCounterWork class from a type, key, and callback map.
- *
- * The callback map must at least have a callback for the 'doWork' method.
- * Additionally, callbacks can be provided for the 'doCachedWork', 'fallback',
- * and 'error' methods. Methods without callbacks will be no-ops that return false.
- * If a 'doCachedWork' callback is provided, then execute() may wait for any prior
- * process in the pool to finish and reuse its cached result.
- *
- * @param string $type
- * @param string $key
- * @param array $callbacks Map of callbacks
- * @throws MWException
- */
- public function __construct( $type, $key, array $callbacks ) {
- parent::__construct( $type, $key );
- foreach ( array( 'doWork', 'doCachedWork', 'fallback', 'error' ) as $name ) {
- if ( isset( $callbacks[$name] ) ) {
- if ( !is_callable( $callbacks[$name] ) ) {
- throw new MWException( "Invalid callback provided for '$name' function." );
- }
- $this->$name = $callbacks[$name];
- }
- }
- if ( !isset( $this->doWork ) ) {
- throw new MWException( "No callback provided for 'doWork' function." );
- }
- $this->cacheable = isset( $this->doCachedWork );
- }
-
- public function doWork() {
- return call_user_func_array( $this->doWork, array() );
- }
-
- public function getCachedWork() {
- if ( $this->doCachedWork ) {
- return call_user_func_array( $this->doCachedWork, array() );
- }
- return false;
- }
-
- function fallback() {
- if ( $this->fallback ) {
- return call_user_func_array( $this->fallback, array() );
- }
- return false;
- }
-
- function error( $status ) {
- if ( $this->error ) {
- return call_user_func_array( $this->error, array( $status ) );
- }
- return false;
- }
-}
diff --git a/includes/Preferences.php b/includes/Preferences.php
index c9caf4f7..84cf5af0 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -46,14 +46,17 @@
* over to the tryUISubmit static method of this class.
*/
class Preferences {
- static $defaultPreferences = null;
- static $saveFilters = array(
- 'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ),
- 'cols' => array( 'Preferences', 'filterIntval' ),
- 'rows' => array( 'Preferences', 'filterIntval' ),
- 'rclimit' => array( 'Preferences', 'filterIntval' ),
- 'wllimit' => array( 'Preferences', 'filterIntval' ),
- 'searchlimit' => array( 'Preferences', 'filterIntval' ),
+ /** @var array */
+ protected static $defaultPreferences = null;
+
+ /** @var array */
+ protected static $saveFilters = array(
+ 'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ),
+ 'cols' => array( 'Preferences', 'filterIntval' ),
+ 'rows' => array( 'Preferences', 'filterIntval' ),
+ 'rclimit' => array( 'Preferences', 'filterIntval' ),
+ 'wllimit' => array( 'Preferences', 'filterIntval' ),
+ 'searchlimit' => array( 'Preferences', 'filterIntval' ),
);
// Stuff that shouldn't be saved as a preference.
@@ -63,9 +66,16 @@ class Preferences {
);
/**
+ * @return array
+ */
+ static function getSaveBlacklist() {
+ return self::$saveBlacklist;
+ }
+
+ /**
* @throws MWException
- * @param $user User
- * @param $context IContextSource
+ * @param User $user
+ * @param IContextSource $context
* @return array|null
*/
static function getPreferences( $user, IContextSource $context ) {
@@ -77,8 +87,8 @@ class Preferences {
self::profilePreferences( $user, $context, $defaultPreferences );
self::skinPreferences( $user, $context, $defaultPreferences );
- self::filesPreferences( $user, $context, $defaultPreferences );
self::datetimePreferences( $user, $context, $defaultPreferences );
+ self::filesPreferences( $user, $context, $defaultPreferences );
self::renderingPreferences( $user, $context, $defaultPreferences );
self::editingPreferences( $user, $context, $defaultPreferences );
self::rcPreferences( $user, $context, $defaultPreferences );
@@ -88,9 +98,22 @@ class Preferences {
wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) );
+ self::loadPreferenceValues( $user, $context, $defaultPreferences );
+ self::$defaultPreferences = $defaultPreferences;
+ return $defaultPreferences;
+ }
+
+ /**
+ * 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
+ */
+ static function loadPreferenceValues( $user, $context, &$defaultPreferences ) {
## Remove preferences that wikis don't want to use
- global $wgHiddenPrefs;
- foreach ( $wgHiddenPrefs as $pref ) {
+ foreach ( $context->getConfig()->get( 'HiddenPrefs' ) as $pref ) {
if ( isset( $defaultPreferences[$pref] ) ) {
unset( $defaultPreferences[$pref] );
}
@@ -128,18 +151,16 @@ class Preferences {
}
}
- self::$defaultPreferences = $defaultPreferences;
-
return $defaultPreferences;
}
/**
* Pull option from a user account. Handles stuff like array-type preferences.
*
- * @param $name
- * @param $info
- * @param $user User
- * @return array|String
+ * @param string $name
+ * @param array $info
+ * @param User $user
+ * @return array|string
*/
static function getOptionFromUser( $name, $info, $user ) {
$val = $user->getOption( $name );
@@ -179,18 +200,15 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
* @return void
*/
static function profilePreferences( $user, IContextSource $context, &$defaultPreferences ) {
- global $wgAuth, $wgContLang, $wgParser, $wgCookieExpiration, $wgLanguageCode,
- $wgDisableTitleConversion, $wgDisableLangConversion, $wgMaxSigChars,
- $wgEnableEmail, $wgEmailConfirmToEdit, $wgEnableUserEmail, $wgEmailAuthentication,
- $wgEnotifWatchlist, $wgEnotifUserTalk, $wgEnotifRevealEditorAddress,
- $wgSecureLogin;
+ global $wgAuth, $wgContLang, $wgParser;
+ $config = $context->getConfig();
// retrieving user name for GENDER and misc.
$userName = $user->getName();
@@ -203,13 +221,6 @@ class Preferences {
'section' => 'personal/info',
);
- $defaultPreferences['userid'] = array(
- 'type' => 'info',
- 'label-message' => array( 'uid', $userName ),
- 'default' => $user->getId(),
- 'section' => 'personal/info',
- );
-
# Get groups to which the user belongs
$userEffectiveGroups = $user->getEffectiveGroups();
$userGroups = $userMembers = array();
@@ -294,16 +305,8 @@ class Preferences {
'section' => 'personal/info',
);
}
- if ( $wgCookieExpiration > 0 ) {
- $defaultPreferences['rememberpassword'] = array(
- 'type' => 'toggle',
- 'label' => $context->msg( 'tog-rememberpassword' )->numParams(
- ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(),
- 'section' => 'personal/info',
- );
- }
- // Only show preferhttps if secure login is turned on
- if ( $wgSecureLogin && wfCanIPUseHTTPS( $context->getRequest()->getIP() ) ) {
+ // Only show prefershttps if secure login is turned on
+ if ( $config->get( 'SecureLogin' ) && wfCanIPUseHTTPS( $context->getRequest()->getIP() ) ) {
$defaultPreferences['prefershttps'] = array(
'type' => 'toggle',
'label-message' => 'tog-prefershttps',
@@ -314,8 +317,9 @@ class Preferences {
// Language
$languages = Language::fetchLanguageNames( null, 'mw' );
- if ( !array_key_exists( $wgLanguageCode, $languages ) ) {
- $languages[$wgLanguageCode] = $wgLanguageCode;
+ $languageCode = $config->get( 'LanguageCode' );
+ if ( !array_key_exists( $languageCode, $languages ) ) {
+ $languages[$languageCode] = $languageCode;
}
ksort( $languages );
@@ -346,7 +350,7 @@ class Preferences {
);
// see if there are multiple language variants to choose from
- if ( !$wgDisableLangConversion ) {
+ if ( !$config->get( 'DisableLangConversion' ) ) {
foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
if ( $langCode == $wgContLang->getCode() ) {
$variants = $wgContLang->getVariants();
@@ -374,14 +378,6 @@ class Preferences {
'section' => 'personal/i18n',
'help-message' => 'prefs-help-variant',
);
-
- if ( !$wgDisableTitleConversion ) {
- $defaultPreferences['noconvertlink'] = array(
- 'type' => 'toggle',
- 'section' => 'personal/i18n',
- 'label-message' => 'tog-noconvertlink',
- );
- }
} else {
$defaultPreferences["variant-$langCode"] = array(
'type' => 'api',
@@ -403,7 +399,12 @@ class Preferences {
}
// show a preview of the old signature first
- $oldsigWikiText = $wgParser->preSaveTransform( "~~~", $context->getTitle(), $user, ParserOptions::newFromContext( $context ) );
+ $oldsigWikiText = $wgParser->preSaveTransform(
+ '~~~',
+ $context->getTitle(),
+ $user,
+ ParserOptions::newFromContext( $context )
+ );
$oldsigHTML = $context->getOutput()->parseInline( $oldsigWikiText, true, true );
$defaultPreferences['oldsig'] = array(
'type' => 'info',
@@ -414,7 +415,7 @@ class Preferences {
);
$defaultPreferences['nickname'] = array(
'type' => $wgAuth->allowPropChange( 'nickname' ) ? 'text' : 'info',
- 'maxlength' => $wgMaxSigChars,
+ 'maxlength' => $config->get( 'MaxSigChars' ),
'label-message' => 'yournick',
'validation-callback' => array( 'Preferences', 'validateSignature' ),
'section' => 'personal/signature',
@@ -423,19 +424,20 @@ class Preferences {
$defaultPreferences['fancysig'] = array(
'type' => 'toggle',
'label-message' => 'tog-fancysig',
- 'help-message' => 'prefs-help-signature', // show general help about signature at the bottom of the section
+ // show general help about signature at the bottom of the section
+ 'help-message' => 'prefs-help-signature',
'section' => 'personal/signature'
);
## Email stuff
- if ( $wgEnableEmail ) {
+ if ( $config->get( 'EnableEmail' ) ) {
if ( $canViewPrivateInfo ) {
- $helpMessages[] = $wgEmailConfirmToEdit
+ $helpMessages[] = $config->get( 'EmailConfirmToEdit' )
? 'prefs-help-email-required'
: 'prefs-help-email';
- if ( $wgEnableUserEmail ) {
+ if ( $config->get( 'EnableUserEmail' ) ) {
// additional messages when users can send email to each other
$helpMessages[] = 'prefs-help-email-others';
}
@@ -467,7 +469,7 @@ class Preferences {
$disableEmailPrefs = false;
- if ( $wgEmailAuthentication ) {
+ if ( $config->get( 'EmailAuthentication' ) ) {
$emailauthenticationclass = 'mw-email-not-authenticated';
if ( $user->getEmail() ) {
if ( $user->getEmailAuthenticationTimestamp() ) {
@@ -512,7 +514,7 @@ class Preferences {
}
}
- if ( $wgEnableUserEmail && $user->isAllowed( 'sendemail' ) ) {
+ if ( $config->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
$defaultPreferences['disablemail'] = array(
'type' => 'toggle',
'invert' => true,
@@ -528,7 +530,7 @@ class Preferences {
);
}
- if ( $wgEnotifWatchlist ) {
+ if ( $config->get( 'EnotifWatchlist' ) ) {
$defaultPreferences['enotifwatchlistpages'] = array(
'type' => 'toggle',
'section' => 'personal/email',
@@ -536,7 +538,7 @@ class Preferences {
'disabled' => $disableEmailPrefs,
);
}
- if ( $wgEnotifUserTalk ) {
+ if ( $config->get( 'EnotifUserTalk' ) ) {
$defaultPreferences['enotifusertalkpages'] = array(
'type' => 'toggle',
'section' => 'personal/email',
@@ -544,7 +546,7 @@ class Preferences {
'disabled' => $disableEmailPrefs,
);
}
- if ( $wgEnotifUserTalk || $wgEnotifWatchlist ) {
+ if ( $config->get( 'EnotifUserTalk' ) || $config->get( 'EnotifWatchlist' ) ) {
$defaultPreferences['enotifminoredits'] = array(
'type' => 'toggle',
'section' => 'personal/email',
@@ -552,7 +554,7 @@ class Preferences {
'disabled' => $disableEmailPrefs,
);
- if ( $wgEnotifRevealEditorAddress ) {
+ if ( $config->get( 'EnotifRevealEditorAddress' ) ) {
$defaultPreferences['enotifrevealaddr'] = array(
'type' => 'toggle',
'section' => 'personal/email',
@@ -565,35 +567,41 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
* @return void
*/
static function skinPreferences( $user, IContextSource $context, &$defaultPreferences ) {
## Skin #####################################
- global $wgAllowUserCss, $wgAllowUserJs;
- $defaultPreferences['skin'] = array(
- 'type' => 'radio',
- 'options' => self::generateSkinOptions( $user, $context ),
- 'label' => '&#160;',
- 'section' => 'rendering/skin',
- );
+ // Skin selector, if there is at least one valid skin
+ $skinOptions = self::generateSkinOptions( $user, $context );
+ if ( $skinOptions ) {
+ $defaultPreferences['skin'] = array(
+ 'type' => 'radio',
+ 'options' => $skinOptions,
+ 'label' => '&#160;',
+ 'section' => 'rendering/skin',
+ );
+ }
+ $config = $context->getConfig();
+ $allowUserCss = $config->get( 'AllowUserCss' );
+ $allowUserJs = $config->get( 'AllowUserJs' );
# Create links to user CSS/JS pages for all skins
# This code is basically copied from generateSkinOptions(). It'd
# be nice to somehow merge this back in there to avoid redundancy.
- if ( $wgAllowUserCss || $wgAllowUserJs ) {
+ if ( $allowUserCss || $allowUserJs ) {
$linkTools = array();
$userName = $user->getName();
- if ( $wgAllowUserCss ) {
+ if ( $allowUserCss ) {
$cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
$linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
}
- if ( $wgAllowUserJs ) {
+ if ( $allowUserJs ) {
$jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
$linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
}
@@ -609,9 +617,9 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences Array
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
*/
static function filesPreferences( $user, IContextSource $context, &$defaultPreferences ) {
## Files #####################################
@@ -630,9 +638,9 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
* @return void
*/
static function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) {
@@ -643,7 +651,7 @@ class Preferences {
'type' => 'radio',
'options' => $dateOptions,
'label' => '&#160;',
- 'section' => 'datetime/dateformat',
+ 'section' => 'rendering/dateformat',
);
}
@@ -660,7 +668,7 @@ class Preferences {
'raw' => 1,
'label-message' => 'servertime',
'default' => $nowserver,
- 'section' => 'datetime/timeoffset',
+ 'section' => 'rendering/timeoffset',
);
$defaultPreferences['nowlocal'] = array(
@@ -668,7 +676,7 @@ class Preferences {
'raw' => 1,
'label-message' => 'localtime',
'default' => $nowlocal,
- 'section' => 'datetime/timeoffset',
+ 'section' => 'rendering/timeoffset',
);
// Grab existing pref.
@@ -682,8 +690,8 @@ class Preferences {
$minDiff = $tz[1];
$tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
} elseif ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
- !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) ) )
- {
+ !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
+ ) {
# Timezone offset can vary with DST
$userTZ = timezone_open( $tz[2] );
if ( $userTZ !== false ) {
@@ -698,14 +706,14 @@ class Preferences {
'options' => $tzOptions,
'default' => $tzSetting,
'size' => 20,
- 'section' => 'datetime/timeoffset',
+ 'section' => 'rendering/timeoffset',
);
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences Array
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
*/
static function renderingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
## Diffs ####################################
@@ -721,8 +729,7 @@ class Preferences {
);
## Page Rendering ##############################
- global $wgAllowUserCssPrefs;
- if ( $wgAllowUserCssPrefs ) {
+ if ( $context->getConfig()->get( 'AllowUserCssPrefs' ) ) {
$defaultPreferences['underline'] = array(
'type' => 'select',
'options' => array(
@@ -742,39 +749,18 @@ class Preferences {
}
$defaultPreferences['stubthreshold'] = array(
- 'type' => 'selectorother',
+ 'type' => 'select',
'section' => 'rendering/advancedrendering',
'options' => $stubThresholdOptions,
- 'size' => 20,
'label-raw' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
);
- if ( $wgAllowUserCssPrefs ) {
- $defaultPreferences['showtoc'] = array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-showtoc',
- );
- }
- $defaultPreferences['nocache'] = array(
- 'type' => 'toggle',
- 'label-message' => 'tog-nocache',
- 'section' => 'rendering/advancedrendering',
- );
$defaultPreferences['showhiddencats'] = array(
'type' => 'toggle',
'section' => 'rendering/advancedrendering',
'label-message' => 'tog-showhiddencats'
);
- if ( $wgAllowUserCssPrefs ) {
- $defaultPreferences['justify'] = array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-justify',
- );
- }
-
$defaultPreferences['numberheadings'] = array(
'type' => 'toggle',
'section' => 'rendering/advancedrendering',
@@ -783,21 +769,12 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences Array
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
*/
static function editingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- global $wgAllowUserCssPrefs;
-
## Editing #####################################
- if ( $wgAllowUserCssPrefs ) {
- $defaultPreferences['editsection'] = array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editsection',
- );
- }
$defaultPreferences['editsectiononrightclick'] = array(
'type' => 'toggle',
'section' => 'editing/advancedediting',
@@ -809,7 +786,7 @@ class Preferences {
'label-message' => 'tog-editondblclick',
);
- if ( $wgAllowUserCssPrefs ) {
+ if ( $context->getConfig()->get( 'AllowUserCssPrefs' ) ) {
$defaultPreferences['editfont'] = array(
'type' => 'select',
'section' => 'editing/editor',
@@ -878,22 +855,22 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences Array
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
*/
static function rcPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- global $wgRCMaxAge, $wgRCShowWatchingUsers;
-
+ $config = $context->getConfig();
+ $rcMaxAge = $config->get( 'RCMaxAge' );
## RecentChanges #####################################
$defaultPreferences['rcdays'] = array(
'type' => 'float',
'label-message' => 'recentchangesdays',
'section' => 'rc/displayrc',
'min' => 1,
- 'max' => ceil( $wgRCMaxAge / ( 3600 * 24 ) ),
+ 'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
'help' => $context->msg( 'recentchangesdays-max' )->numParams(
- ceil( $wgRCMaxAge / ( 3600 * 24 ) ) )->text()
+ ceil( $rcMaxAge / ( 3600 * 24 ) ) )->text()
);
$defaultPreferences['rclimit'] = array(
'type' => 'int',
@@ -925,7 +902,7 @@ class Preferences {
);
}
- if ( $wgRCShowWatchingUsers ) {
+ if ( $config->get( 'RCShowWatchingUsers' ) ) {
$defaultPreferences['shownumberswatching'] = array(
'type' => 'toggle',
'section' => 'rc/advancedrc',
@@ -935,14 +912,13 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
*/
static function watchlistPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- global $wgUseRCPatrol, $wgEnableAPI, $wgRCMaxAge;
-
- $watchlistdaysMax = ceil( $wgRCMaxAge / ( 3600 * 24 ) );
+ $config = $context->getConfig();
+ $watchlistdaysMax = ceil( $config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
## Watchlist #####################################
$defaultPreferences['watchlistdays'] = array(
@@ -993,7 +969,7 @@ class Preferences {
'label-message' => 'tog-watchlisthideliu',
);
- if ( $wgUseRCPatrol ) {
+ if ( $context->getConfig()->get( 'UseRCPatrol' ) ) {
$defaultPreferences['watchlisthidepatrolled'] = array(
'type' => 'toggle',
'section' => 'watchlist/advancedwatchlist',
@@ -1012,10 +988,15 @@ class Preferences {
$watchTypes['read'] = 'watchcreations';
}
+ if ( $user->isAllowed( 'rollback' ) ) {
+ $watchTypes['rollback'] = 'watchrollback';
+ }
+
foreach ( $watchTypes as $action => $pref ) {
if ( $user->isAllowed( $action ) ) {
// Messages:
// tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations
+ // tog-watchrollback
$defaultPreferences[$pref] = array(
'type' => 'toggle',
'section' => 'watchlist/advancedwatchlist',
@@ -1024,7 +1005,7 @@ class Preferences {
}
}
- if ( $wgEnableAPI ) {
+ if ( $config->get( 'EnableAPI' ) ) {
$defaultPreferences['watchlisttoken'] = array(
'type' => 'api',
);
@@ -1039,56 +1020,16 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences Array
+ * @param User $user
+ * @param IContextSource $context
+ * @param array $defaultPreferences
*/
static function searchPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- global $wgContLang, $wgVectorUseSimpleSearch;
-
- ## Search #####################################
- $defaultPreferences['searchlimit'] = array(
- 'type' => 'int',
- 'label-message' => 'resultsperpage',
- 'section' => 'searchoptions/displaysearchoptions',
- 'min' => 0,
- );
-
- if ( $wgVectorUseSimpleSearch ) {
- $defaultPreferences['vector-simplesearch'] = array(
- 'type' => 'toggle',
- 'label-message' => 'vector-simplesearch-preference',
- 'section' => 'searchoptions/displaysearchoptions',
+ foreach ( MWNamespace::getValidNamespaces() as $n ) {
+ $defaultPreferences['searchNs' . $n] = array(
+ 'type' => 'api',
);
}
-
- $defaultPreferences['disablesuggest'] = array(
- 'type' => 'toggle',
- 'label-message' => 'mwsuggest-disable',
- 'section' => 'searchoptions/displaysearchoptions',
- );
-
- $defaultPreferences['searcheverything'] = array(
- 'type' => 'toggle',
- 'label-message' => 'searcheverything-enable',
- 'section' => 'searchoptions/advancedsearchoptions',
- );
-
- $nsOptions = $wgContLang->getFormattedNamespaces();
- $nsOptions[0] = $context->msg( 'blanknamespace' )->text();
- foreach ( $nsOptions as $ns => $name ) {
- if ( $ns < 0 ) {
- unset( $nsOptions[$ns] );
- }
- }
-
- $defaultPreferences['searchnamespaces'] = array(
- 'type' => 'multiselect',
- 'label-message' => 'defaultns',
- 'options' => array_flip( $nsOptions ),
- 'section' => 'searchoptions/advancedsearchoptions',
- 'prefix' => 'searchNs',
- );
}
/**
@@ -1098,20 +1039,18 @@ class Preferences {
}
/**
- * @param $user User The User object
- * @param $context IContextSource
- * @return Array: text/links to display as key; $skinkey as value
+ * @param User $user The User object
+ * @param IContextSource $context
+ * @return array Text/links to display as key; $skinkey as value
*/
static function generateSkinOptions( $user, IContextSource $context ) {
- global $wgDefaultSkin, $wgAllowUserCss, $wgAllowUserJs;
$ret = array();
$mptitle = Title::newMainPage();
$previewtext = $context->msg( 'skin-preview' )->text();
- # Only show members of Skin::getSkinNames() rather than
- # $skinNames (skins is all skin names from Language.php)
- $validSkinNames = Skin::getUsableSkins();
+ # Only show skins that aren't disabled in $wgSkipSkins
+ $validSkinNames = Skin::getAllowedSkins();
# Sort by UI skin name. First though need to update validSkinNames as sometimes
# the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
@@ -1123,12 +1062,19 @@ class Preferences {
}
asort( $validSkinNames );
+ $config = $context->getConfig();
+ $defaultSkin = $config->get( 'DefaultSkin' );
+ $allowUserCss = $config->get( 'AllowUserCss' );
+ $allowUserJs = $config->get( 'AllowUserJs' );
+
+ $foundDefault = false;
foreach ( $validSkinNames as $skinkey => $sn ) {
$linkTools = array();
# Mark the default skin
- if ( $skinkey == $wgDefaultSkin ) {
+ if ( $skinkey == $defaultSkin ) {
$linkTools[] = $context->msg( 'default' )->escaped();
+ $foundDefault = true;
}
# Create preview link
@@ -1136,25 +1082,34 @@ class Preferences {
$linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
# Create links to user CSS/JS pages
- if ( $wgAllowUserCss ) {
+ if ( $allowUserCss ) {
$cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
$linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
}
- if ( $wgAllowUserJs ) {
+ if ( $allowUserJs ) {
$jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
$linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
}
- $display = $sn . ' ' . $context->msg( 'parentheses', $context->getLanguage()->pipeList( $linkTools ) )->text();
+ $display = $sn . ' ' . $context->msg(
+ 'parentheses',
+ $context->getLanguage()->pipeList( $linkTools )
+ )->text();
$ret[$display] = $skinkey;
}
+ if ( !$foundDefault ) {
+ // If the default skin is not available, things are going to break horribly because the
+ // default value for skin selector will not be a valid value. Let's just not show it then.
+ return array();
+ }
+
return $ret;
}
/**
- * @param $context IContextSource
+ * @param IContextSource $context
* @return array
*/
static function getDateOptions( IContextSource $context ) {
@@ -1169,7 +1124,7 @@ class Preferences {
// Bug 19237
}
- // KLUGE: site default might not be valid for user language
+ // FIXME KLUGE: site default might not be valid for user language
global $wgDefaultUserOptions;
if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
$wgDefaultUserOptions['date'] = 'default';
@@ -1189,16 +1144,14 @@ class Preferences {
}
/**
- * @param $context IContextSource
+ * @param IContextSource $context
* @return array
*/
static function getImageSizes( IContextSource $context ) {
- global $wgImageLimits;
-
$ret = array();
$pixels = $context->msg( 'unit-pixel' )->text();
- foreach ( $wgImageLimits as $index => $limits ) {
+ foreach ( $context->getConfig()->get( 'ImageLimits' ) as $index => $limits ) {
$display = "{$limits[0]}×{$limits[1]}" . $pixels;
$ret[$display] = $index;
}
@@ -1207,16 +1160,14 @@ class Preferences {
}
/**
- * @param $context IContextSource
+ * @param IContextSource $context
* @return array
*/
static function getThumbSizes( IContextSource $context ) {
- global $wgThumbLimits;
-
$ret = array();
$pixels = $context->msg( 'unit-pixel' )->text();
- foreach ( $wgThumbLimits as $index => $size ) {
+ foreach ( $context->getConfig()->get( 'ThumbLimits' ) as $index => $size ) {
$display = $size . $pixels;
$ret[$display] = $index;
}
@@ -1225,29 +1176,35 @@ class Preferences {
}
/**
- * @param $signature string
- * @param $alldata array
- * @param $form HTMLForm
+ * @param string $signature
+ * @param array $alldata
+ * @param HTMLForm $form
* @return bool|string
*/
static function validateSignature( $signature, $alldata, $form ) {
- global $wgParser, $wgMaxSigChars;
- if ( mb_strlen( $signature ) > $wgMaxSigChars ) {
+ global $wgParser;
+ $maxSigChars = $form->getConfig()->get( 'MaxSigChars' );
+ if ( mb_strlen( $signature ) > $maxSigChars ) {
return Xml::element( 'span', array( 'class' => 'error' ),
- $form->msg( 'badsiglength' )->numParams( $wgMaxSigChars )->text() );
+ $form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() );
} elseif ( isset( $alldata['fancysig'] ) &&
$alldata['fancysig'] &&
- false === $wgParser->validateSig( $signature ) ) {
- return Xml::element( 'span', array( 'class' => 'error' ), $form->msg( 'badsig' )->text() );
+ $wgParser->validateSig( $signature ) === false
+ ) {
+ return Xml::element(
+ 'span',
+ array( 'class' => 'error' ),
+ $form->msg( 'badsig' )->text()
+ );
} else {
return true;
}
}
/**
- * @param $signature string
- * @param $alldata array
- * @param $form HTMLForm
+ * @param string $signature
+ * @param array $alldata
+ * @param HTMLForm $form
* @return string
*/
static function cleanSignature( $signature, $alldata, $form ) {
@@ -1263,13 +1220,18 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $formClass string
- * @param array $remove array of items to remove
+ * @param User $user
+ * @param IContextSource $context
+ * @param string $formClass
+ * @param array $remove Array of items to remove
* @return HtmlForm
*/
- static function getFormObject( $user, IContextSource $context, $formClass = 'PreferencesForm', array $remove = array() ) {
+ static function getFormObject(
+ $user,
+ IContextSource $context,
+ $formClass = 'PreferencesForm',
+ array $remove = array()
+ ) {
$formDescriptor = Preferences::getPreferences( $user, $context );
if ( count( $remove ) ) {
$removeKeys = array_flip( $remove );
@@ -1300,22 +1262,29 @@ class Preferences {
}
/**
- * @param $context IContextSource
+ * @param IContextSource $context
* @return array
*/
static function getTimezoneOptions( IContextSource $context ) {
$opt = array();
- global $wgLocalTZoffset;
+ $localTZoffset = $context->getConfig()->get( 'LocalTZoffset' );
$timestamp = MWTimestamp::getLocalInstance();
- // Check that $wgLocalTZoffset is the same as the local time zone offset
- if ( $wgLocalTZoffset == $timestamp->format( 'Z' ) / 60 ) {
- $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $timestamp->getTimezone()->getName() )->text();
+ // Check that the LocalTZoffset is the same as the local time zone offset
+ if ( $localTZoffset == $timestamp->format( 'Z' ) / 60 ) {
+ $server_tz_msg = $context->msg(
+ 'timezoneuseserverdefault',
+ $timestamp->getTimezone()->getName()
+ )->text();
} else {
- $tzstring = sprintf( '%+03d:%02d', floor( $wgLocalTZoffset / 60 ), abs( $wgLocalTZoffset ) % 60 );
+ $tzstring = sprintf(
+ '%+03d:%02d',
+ floor( $localTZoffset / 60 ),
+ abs( $localTZoffset ) % 60
+ );
$server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
}
- $opt[$server_tz_msg] = "System|$wgLocalTZoffset";
+ $opt[$server_tz_msg] = "System|$localTZoffset";
$opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
$opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
@@ -1367,8 +1336,8 @@ class Preferences {
}
/**
- * @param $value
- * @param $alldata
+ * @param string $value
+ * @param array $alldata
* @return int
*/
static function filterIntval( $value, $alldata ) {
@@ -1376,8 +1345,8 @@ class Preferences {
}
/**
- * @param $tz
- * @param $alldata
+ * @param string $tz
+ * @param array $alldata
* @return string
*/
static function filterTimezoneInput( $tz, $alldata ) {
@@ -1410,15 +1379,15 @@ class Preferences {
/**
* Handle the form submission if everything validated properly
*
- * @param $formData
- * @param $form PreferencesForm
- * @param $entryPoint string
+ * @param array $formData
+ * @param PreferencesForm $form
* @return bool|Status|string
*/
- static function tryFormSubmit( $formData, $form, $entryPoint = 'internal' ) {
- global $wgHiddenPrefs, $wgAuth;
+ static function tryFormSubmit( $formData, $form ) {
+ global $wgAuth;
$user = $form->getModifiedUser();
+ $hiddenPrefs = $form->getConfig()->get( 'HiddenPrefs' );
$result = true;
if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
@@ -1435,7 +1404,10 @@ class Preferences {
// Fortunately, the realname field is MUCH simpler
// (not really "private", but still shouldn't be edited without permission)
- if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->isAllowed( 'editmyprivateinfo' ) ) {
+ if ( !in_array( 'realname', $hiddenPrefs )
+ && $user->isAllowed( 'editmyprivateinfo' )
+ && array_key_exists( 'realname', $formData )
+ ) {
$realName = $formData['realname'];
$user->setRealName( $realName );
}
@@ -1448,8 +1420,7 @@ class Preferences {
# If users have saved a value for a preference which has subsequently been disabled
# via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
# is subsequently re-enabled
- # TODO: maintenance script to actually delete these
- foreach ( $wgHiddenPrefs as $pref ) {
+ foreach ( $hiddenPrefs as $pref ) {
# If the user has not set a non-default value here, the default will be returned
# and subsequently discarded
$formData[$pref] = $user->getOption( $pref, null, true );
@@ -1462,6 +1433,7 @@ class Preferences {
$user->setOption( $key, $value );
}
+ wfRunHooks( 'PreferencesFormPreSave', array( $formData, $form, $user, &$result ) );
$user->saveSettings();
}
@@ -1471,12 +1443,12 @@ class Preferences {
}
/**
- * @param $formData
- * @param $form PreferencesForm
+ * @param array $formData
+ * @param PreferencesForm $form
* @return Status
*/
public static function tryUISubmit( $formData, $form ) {
- $res = self::tryFormSubmit( $formData, $form, 'ui' );
+ $res = self::tryFormSubmit( $formData, $form );
if ( $res ) {
$urlOptions = array( 'success' => 1 );
@@ -1501,10 +1473,10 @@ class Preferences {
* Caller is responsible for checking $wgAuth and 'editmyprivateinfo'
* right.
*
- * @deprecated in 1.20; use User::setEmailWithConfirmation() instead.
- * @param $user User
+ * @deprecated since 1.20; use User::setEmailWithConfirmation() instead.
+ * @param User $user
* @param string $newaddr New email address
- * @return Array (true on success or Status on failure, info string)
+ * @return array (true on success or Status on failure, info string)
*/
public static function trySetUserEmail( User $user, $newaddr ) {
wfDeprecated( __METHOD__, '1.20' );
@@ -1516,27 +1488,6 @@ class Preferences {
return array( $result, 'mailerror' );
}
}
-
- /**
- * @deprecated in 1.19
- * @param $user User
- * @return array
- */
- public static function loadOldSearchNs( $user ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- $searchableNamespaces = SearchEngine::searchableNamespaces();
- // Back compat with old format
- $arr = array();
-
- foreach ( $searchableNamespaces as $ns => $name ) {
- if ( $user->getOption( 'searchNs' . $ns ) ) {
- $arr[] = $ns;
- }
- }
-
- return $arr;
- }
}
/** Some tweaks to allow js prefs to work */
@@ -1547,7 +1498,7 @@ class PreferencesForm extends HTMLForm {
private $modifiedUser;
/**
- * @param $user User
+ * @param User $user
*/
public function setModifiedUser( $user ) {
$this->modifiedUser = $user;
@@ -1575,8 +1526,8 @@ class PreferencesForm extends HTMLForm {
}
/**
- * @param $html string
- * @return String
+ * @param string $html
+ * @return string
*/
function wrapForm( $html ) {
$html = Xml::tags( 'div', array( 'id' => 'preferences' ), $html );
@@ -1585,9 +1536,16 @@ class PreferencesForm extends HTMLForm {
}
/**
- * @return String
+ * @return string
*/
function getButtons() {
+ global $wgUseMediaWikiUIEverywhere;
+
+ $attrs = array( 'id' => 'mw-prefs-restoreprefs' );
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $attrs['class'] = 'mw-ui-button mw-ui-quiet';
+ }
+
if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
return '';
}
@@ -1597,7 +1555,8 @@ class PreferencesForm extends HTMLForm {
if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
$t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
- $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped() );
+ $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped(),
+ $attrs );
$html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
}
@@ -1608,7 +1567,7 @@ class PreferencesForm extends HTMLForm {
/**
* Separate multi-option preferences into multiple preferences, since we
* have to store them separately
- * @param $data array
+ * @param array $data
* @return array
*/
function filterDataForSubmit( $data ) {
@@ -1637,7 +1596,7 @@ class PreferencesForm extends HTMLForm {
/**
* Get the "<legend>" for a given section key. Normally this is the
* prefs-$key message but we'll allow extensions to override it.
- * @param $key string
+ * @param string $key
* @return string
*/
function getLegend( $key ) {
diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php
index 3c464c58..718750f5 100644
--- a/includes/PrefixSearch.php
+++ b/includes/PrefixSearch.php
@@ -26,30 +26,44 @@
*
* @ingroup Search
*/
-class PrefixSearch {
+abstract class PrefixSearch {
/**
* Do a prefix search of titles and return a list of matching page names.
+ * @deprecated Since 1.23, use TitlePrefixSearch or StringPrefixSearch classes
*
- * @param $search String
- * @param $limit Integer
- * @param array $namespaces used if query is not explicitly prefixed
- * @return Array of strings
+ * @param string $search
+ * @param int $limit
+ * @param array $namespaces Used if query is not explicitly prefixed
+ * @return array Array of strings
*/
public static function titleSearch( $search, $limit, $namespaces = array() ) {
+ $prefixSearch = new StringPrefixSearch;
+ return $prefixSearch->search( $search, $limit, $namespaces );
+ }
+
+ /**
+ * Do a prefix search of titles and return a list of matching page names.
+ *
+ * @param string $search
+ * @param int $limit
+ * @param array $namespaces Used if query is not explicitly prefixed
+ * @return array Array of strings or Title objects
+ */
+ public function search( $search, $limit, $namespaces = array() ) {
$search = trim( $search );
if ( $search == '' ) {
return array(); // Return empty result
}
- $namespaces = self::validateNamespaces( $namespaces );
+ $namespaces = $this->validateNamespaces( $namespaces );
// Find a Title which is not an interwiki and is in NS_MAIN
$title = Title::newFromText( $search );
- if ( $title && $title->getInterwiki() == '' ) {
+ if ( $title && !$title->isExternal() ) {
$ns = array( $title->getNamespace() );
if ( $ns[0] == NS_MAIN ) {
$ns = $namespaces; // no explicit prefix, use default namespaces
}
- return self::searchBackend(
+ return $this->searchBackend(
$ns, $title->getText(), $limit );
}
@@ -57,61 +71,136 @@ class PrefixSearch {
$title = Title::newFromText( $search . 'Dummy' );
if ( $title && $title->getText() == 'Dummy'
&& $title->getNamespace() != NS_MAIN
- && $title->getInterwiki() == '' ) {
- return self::searchBackend(
- array( $title->getNamespace() ), '', $limit );
+ && !$title->isExternal() )
+ {
+ $namespaces = array( $title->getNamespace() );
+ $search = '';
}
- return self::searchBackend( $namespaces, $search, $limit );
+ return $this->searchBackend( $namespaces, $search, $limit );
+ }
+
+ /**
+ * Do a prefix search for all possible variants of the prefix
+ * @param string $search
+ * @param int $limit
+ * @param array $namespaces
+ *
+ * @return array
+ */
+ public function searchWithVariants( $search, $limit, array $namespaces ) {
+ wfProfileIn( __METHOD__ );
+ $searches = $this->search( $search, $limit, $namespaces );
+
+ // if the content language has variants, try to retrieve fallback results
+ $fallbackLimit = $limit - count( $searches );
+ if ( $fallbackLimit > 0 ) {
+ global $wgContLang;
+
+ $fallbackSearches = $wgContLang->autoConvertToAllVariants( $search );
+ $fallbackSearches = array_diff( array_unique( $fallbackSearches ), array( $search ) );
+
+ foreach ( $fallbackSearches as $fbs ) {
+ $fallbackSearchResult = $this->search( $fbs, $fallbackLimit, $namespaces );
+ $searches = array_merge( $searches, $fallbackSearchResult );
+ $fallbackLimit -= count( $fallbackSearchResult );
+
+ if ( $fallbackLimit == 0 ) {
+ break;
+ }
+ }
+ }
+ wfProfileOut( __METHOD__ );
+ return $searches;
}
/**
+ * When implemented in a descendant class, receives an array of Title objects and returns
+ * either an unmodified array or an array of strings corresponding to titles passed to it.
+ *
+ * @param array $titles
+ * @return array
+ */
+ abstract protected function titles( array $titles );
+
+ /**
+ * When implemented in a descendant class, receives an array of titles as strings and returns
+ * either an unmodified array or an array of Title objects corresponding to strings received.
+ *
+ * @param array $strings
+ *
+ * @return array
+ */
+ abstract protected function strings( array $strings );
+
+ /**
* Do a prefix search of titles and return a list of matching page names.
- * @param $namespaces Array
- * @param $search String
- * @param $limit Integer
- * @return Array of strings
+ * @param array $namespaces
+ * @param string $search
+ * @param int $limit
+ * @return array Array of strings
*/
- protected static function searchBackend( $namespaces, $search, $limit ) {
+ protected function searchBackend( $namespaces, $search, $limit ) {
if ( count( $namespaces ) == 1 ) {
$ns = $namespaces[0];
if ( $ns == NS_MEDIA ) {
$namespaces = array( NS_FILE );
} elseif ( $ns == NS_SPECIAL ) {
- return self::specialSearch( $search, $limit );
+ return $this->titles( $this->specialSearch( $search, $limit ) );
}
}
$srchres = array();
if ( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres ) ) ) {
- return self::defaultSearchBackend( $namespaces, $search, $limit );
+ return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit ) );
}
- return $srchres;
+ return $this->strings( $srchres );
}
/**
* Prefix search special-case for Special: namespace.
*
- * @param string $search term
- * @param $limit Integer: max number of items to return
- * @return Array
+ * @param string $search Term
+ * @param int $limit Max number of items to return
+ * @return array
*/
- protected static function specialSearch( $search, $limit ) {
+ protected function specialSearch( $search, $limit ) {
global $wgContLang;
- # normalize searchKey, so aliases with spaces can be found - bug 25675
- $search = str_replace( ' ', '_', $search );
+ $searchParts = explode( '/', $search, 2 );
+ $searchKey = $searchParts[0];
+ $subpageSearch = isset( $searchParts[1] ) ? $searchParts[1] : null;
- $searchKey = $wgContLang->caseFold( $search );
+ // Handle subpage search separately.
+ if ( $subpageSearch !== null ) {
+ // Try matching the full search string as a page name
+ $specialTitle = Title::makeTitleSafe( NS_SPECIAL, $searchKey );
+ if ( !$specialTitle ) {
+ return array();
+ }
+ $special = SpecialPageFactory::getPage( $specialTitle->getText() );
+ if ( $special ) {
+ $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit );
+ return array_map( function ( $sub ) use ( $specialTitle ) {
+ return $specialTitle->getSubpage( $sub );
+ }, $subpages );
+ } else {
+ return array();
+ }
+ }
+
+ # normalize searchKey, so aliases with spaces can be found - bug 25675
+ $searchKey = str_replace( ' ', '_', $searchKey );
+ $searchKey = $wgContLang->caseFold( $searchKey );
// Unlike SpecialPage itself, we want the canonical forms of both
// canonical and alias title forms...
$keys = array();
- foreach ( SpecialPageFactory::getList() as $page => $class ) {
+ foreach ( SpecialPageFactory::getNames() as $page ) {
$keys[$wgContLang->caseFold( $page )] = $page;
}
foreach ( $wgContLang->getSpecialPageAliases() as $page => $aliases ) {
- if ( !array_key_exists( $page, SpecialPageFactory::getList() ) ) {# bug 20885
+ if ( !in_array( $page, SpecialPageFactory::getNames() ) ) {# bug 20885
continue;
}
@@ -124,13 +213,11 @@ class PrefixSearch {
$srchres = array();
foreach ( $keys as $pageKey => $page ) {
if ( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
- wfSuppressWarnings();
// bug 27671: Don't use SpecialPage::getTitleFor() here because it
// localizes its input leading to searches for e.g. Special:All
// returning Spezial:MediaWiki-Systemnachrichten and returning
// Spezial:Alle_Seiten twice when $wgLanguageCode == 'de'
- $srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page )->getPrefixedText();
- wfRestoreWarnings();
+ $srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page );
}
if ( count( $srchres ) >= $limit ) {
@@ -147,51 +234,43 @@ class PrefixSearch {
* be automatically capitalized by Title::secureAndSpit()
* later on depending on $wgCapitalLinks)
*
- * @param array $namespaces namespaces to search in
- * @param string $search term
- * @param $limit Integer: max number of items to return
- * @return Array of title strings
+ * @param array $namespaces Namespaces to search in
+ * @param string $search Term
+ * @param int $limit Max number of items to return
+ * @return array Array of Title objects
*/
- protected static function defaultSearchBackend( $namespaces, $search, $limit ) {
+ protected function defaultSearchBackend( $namespaces, $search, $limit ) {
$ns = array_shift( $namespaces ); // support only one namespace
if ( in_array( NS_MAIN, $namespaces ) ) {
$ns = NS_MAIN; // if searching on many always default to main
}
- // Prepare nested request
- $req = new FauxRequest( array(
- 'action' => 'query',
- 'list' => 'allpages',
- 'apnamespace' => $ns,
- 'aplimit' => $limit,
- 'apprefix' => $search
- ));
-
- // Execute
- $module = new ApiMain( $req );
- $module->execute();
-
- // Get resulting data
- $data = $module->getResultData();
-
- // Reformat useful data for future printing by JSON engine
+ $t = Title::newFromText( $search, $ns );
+ $prefix = $t ? $t->getDBkey() : '';
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'page',
+ array( 'page_id', 'page_namespace', 'page_title' ),
+ array(
+ 'page_namespace' => $ns,
+ 'page_title ' . $dbr->buildLike( $prefix, $dbr->anyString() )
+ ),
+ __METHOD__,
+ array( 'LIMIT' => $limit, 'ORDER BY' => 'page_title' )
+ );
$srchres = array();
- foreach ( (array)$data['query']['allpages'] as $pageinfo ) {
- // Note: this data will no be printable by the xml engine
- // because it does not support lists of unnamed items
- $srchres[] = $pageinfo['title'];
+ foreach ( $res as $row ) {
+ $srchres[] = Title::newFromRow( $row );
}
-
return $srchres;
}
/**
* Validate an array of numerical namespace indexes
*
- * @param $namespaces Array
- * @return Array (default: contains only NS_MAIN)
+ * @param array $namespaces
+ * @return array (default: contains only NS_MAIN)
*/
- protected static function validateNamespaces( $namespaces ) {
+ protected function validateNamespaces( $namespaces ) {
global $wgContLang;
// We will look at each given namespace against wgContLang namespaces
@@ -211,3 +290,39 @@ class PrefixSearch {
return array( NS_MAIN );
}
}
+
+/**
+ * Performs prefix search, returning Title objects
+ * @ingroup Search
+ */
+class TitlePrefixSearch extends PrefixSearch {
+
+ protected function titles( array $titles ) {
+ return $titles;
+ }
+
+ protected function strings( array $strings ) {
+ $titles = array_map( 'Title::newFromText', $strings );
+ $lb = new LinkBatch( $titles );
+ $lb->setCaller( __METHOD__ );
+ $lb->execute();
+ return $titles;
+ }
+}
+
+/**
+ * Performs prefix search, returning strings
+ * @ingroup Search
+ */
+class StringPrefixSearch extends PrefixSearch {
+
+ protected function titles( array $titles ) {
+ return array_map( function ( Title $t ) {
+ return $t->getPrefixedText();
+ }, $titles );
+ }
+
+ protected function strings( array $strings ) {
+ return $strings;
+ }
+}
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index f10317a9..7bad8b57 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -3,7 +3,7 @@
* Page protection
*
* Copyright © 2005 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -27,46 +27,51 @@
* Handles the page protection UI and backend
*/
class ProtectionForm {
- /** A map of action to restriction level, from request or default */
- var $mRestrictions = array();
+ /** @var array A map of action to restriction level, from request or default */
+ protected $mRestrictions = array();
- /** The custom/additional protection reason */
- var $mReason = '';
+ /** @var string The custom/additional protection reason */
+ protected $mReason = '';
- /** The reason selected from the list, blank for other/additional */
- var $mReasonSelection = '';
+ /** @var string The reason selected from the list, blank for other/additional */
+ protected $mReasonSelection = '';
- /** True if the restrictions are cascading, from request or existing protection */
- var $mCascade = false;
+ /** @var bool True if the restrictions are cascading, from request or existing protection */
+ protected $mCascade = false;
- /** Map of action to "other" expiry time. Used in preference to mExpirySelection. */
- var $mExpiry = array();
+ /** @var array Map of action to "other" expiry time. Used in preference to mExpirySelection. */
+ protected $mExpiry = array();
/**
- * Map of action to value selected in expiry drop-down list.
+ * @var array Map of action to value selected in expiry drop-down list.
* Will be set to 'othertime' whenever mExpiry is set.
*/
- var $mExpirySelection = array();
+ protected $mExpirySelection = array();
- /** Permissions errors for the protect action */
- var $mPermErrors = array();
+ /** @var array Permissions errors for the protect action */
+ protected $mPermErrors = array();
- /** Types (i.e. actions) for which levels can be selected */
- var $mApplicableTypes = array();
+ /** @var array Types (i.e. actions) for which levels can be selected */
+ protected $mApplicableTypes = array();
- /** Map of action to the expiry time of the existing protection */
- var $mExistingExpiry = array();
+ /** @var array Map of action to the expiry time of the existing protection */
+ protected $mExistingExpiry = array();
- function __construct( Page $article ) {
- global $wgUser;
+ /** @var IContextSource */
+ private $mContext;
+
+ function __construct( Article $article ) {
// Set instance variables.
$this->mArticle = $article;
$this->mTitle = $article->getTitle();
$this->mApplicableTypes = $this->mTitle->getRestrictionTypes();
+ $this->mContext = $article->getContext();
// Check if the form should be disabled.
// If it is, the form will be available in read-only to show levels.
- $this->mPermErrors = $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser );
+ $this->mPermErrors = $this->mTitle->getUserPermissionsErrors(
+ 'protect', $this->mContext->getUser()
+ );
if ( wfReadOnly() ) {
$this->mPermErrors[] = array( 'readonlytext', wfReadOnlyReason() );
}
@@ -82,14 +87,15 @@ class ProtectionForm {
* Loads the current state of protection into the object.
*/
function loadData() {
- global $wgRequest, $wgUser;
- global $wgRestrictionLevels;
-
+ $levels = MWNamespace::getRestrictionLevels(
+ $this->mTitle->getNamespace(), $this->mContext->getUser()
+ );
$this->mCascade = $this->mTitle->areRestrictionsCascading();
- $this->mReason = $wgRequest->getText( 'mwProtect-reason' );
- $this->mReasonSelection = $wgRequest->getText( 'wpProtectReasonSelection' );
- $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade', $this->mCascade );
+ $request = $this->mContext->getRequest();
+ $this->mReason = $request->getText( 'mwProtect-reason' );
+ $this->mReasonSelection = $request->getText( 'wpProtectReasonSelection' );
+ $this->mCascade = $request->getBool( 'mwProtect-cascade', $this->mCascade );
foreach ( $this->mApplicableTypes as $action ) {
// @todo FIXME: This form currently requires individual selections,
@@ -106,8 +112,8 @@ class ProtectionForm {
}
$this->mExistingExpiry[$action] = $existingExpiry;
- $requestExpiry = $wgRequest->getText( "mwProtect-expiry-$action" );
- $requestExpirySelection = $wgRequest->getVal( "wpProtectExpirySelection-$action" );
+ $requestExpiry = $request->getText( "mwProtect-expiry-$action" );
+ $requestExpirySelection = $request->getVal( "wpProtectExpirySelection-$action" );
if ( $requestExpiry ) {
// Custom expiry takes precedence
@@ -117,36 +123,19 @@ class ProtectionForm {
// Expiry selected from list
$this->mExpiry[$action] = '';
$this->mExpirySelection[$action] = $requestExpirySelection;
- } elseif ( $existingExpiry == 'infinity' ) {
- // Existing expiry is infinite, use "infinite" in drop-down
- $this->mExpiry[$action] = '';
- $this->mExpirySelection[$action] = 'infinite';
} elseif ( $existingExpiry ) {
// Use existing expiry in its own list item
$this->mExpiry[$action] = '';
$this->mExpirySelection[$action] = $existingExpiry;
} else {
+ // Catches 'infinity' - Existing expiry is infinite, use "infinite" in drop-down
// Final default: infinite
$this->mExpiry[$action] = '';
$this->mExpirySelection[$action] = 'infinite';
}
- $val = $wgRequest->getVal( "mwProtect-level-$action" );
- if ( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
- // Prevent users from setting levels that they cannot later unset
- if ( $val == 'sysop' ) {
- // Special case, rewrite sysop to editprotected
- if ( !$wgUser->isAllowed( 'editprotected' ) ) {
- continue;
- }
- } elseif ( $val == 'autoconfirmed' ) {
- // Special case, rewrite autoconfirmed to editsemiprotected
- if ( !$wgUser->isAllowed( 'editsemiprotected' ) ) {
- continue;
- }
- } elseif ( !$wgUser->isAllowed( $val ) ) {
- continue;
- }
+ $val = $request->getVal( "mwProtect-level-$action" );
+ if ( isset( $val ) && in_array( $val, $levels ) ) {
$this->mRestrictions[$action] = $val;
}
}
@@ -155,7 +144,7 @@ class ProtectionForm {
/**
* Get the expiry time for a given action, by combining the relevant inputs.
*
- * @param $action string
+ * @param string $action
*
* @return string 14-char timestamp or "infinity", or false if the input was invalid
*/
@@ -187,16 +176,14 @@ class ProtectionForm {
* Main entry point for action=protect and action=unprotect
*/
function execute() {
- global $wgRequest, $wgOut;
-
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ if ( MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) === array( '' ) ) {
throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
}
- if ( $wgRequest->wasPosted() ) {
+ if ( $this->mContext->getRequest()->wasPosted() ) {
if ( $this->save() ) {
$q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
- $wgOut->redirect( $this->mTitle->getFullURL( $q ) );
+ $this->mContext->getOutput()->redirect( $this->mTitle->getFullURL( $q ) );
}
} else {
$this->show();
@@ -206,28 +193,30 @@ class ProtectionForm {
/**
* Show the input form with optional error message
*
- * @param string $err error message or null if there's no error
+ * @param string $err Error message or null if there's no error
*/
function show( $err = null ) {
- global $wgOut;
-
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->addBacklinkSubtitle( $this->mTitle );
+ $out = $this->mContext->getOutput();
+ $out->setRobotPolicy( 'noindex,nofollow' );
+ $out->addBacklinkSubtitle( $this->mTitle );
if ( is_array( $err ) ) {
- $wgOut->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
+ $out->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
} elseif ( is_string( $err ) ) {
- $wgOut->addHTML( "<p class='error'>{$err}</p>\n" );
+ $out->addHTML( "<p class='error'>{$err}</p>\n" );
}
if ( $this->mTitle->getRestrictionTypes() === array() ) {
// No restriction types available for the current title
// this might happen if an extension alters the available types
- $wgOut->setPageTitle( wfMessage( 'protect-norestrictiontypes-title', $this->mTitle->getPrefixedText() ) );
- $wgOut->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() );
+ $out->setPageTitle( wfMessage(
+ 'protect-norestrictiontypes-title',
+ $this->mTitle->getPrefixedText()
+ ) );
+ $out->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() );
// Show the log in case protection was possible once
- $this->showLogExtract( $wgOut );
+ $this->showLogExtract( $out );
// return as there isn't anything else we can do
return;
}
@@ -240,40 +229,48 @@ class ProtectionForm {
$titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
}
- $wgOut->wrapWikiMsg( "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>", array( 'protect-cascadeon', count( $cascadeSources ) ) );
+ /** @todo FIXME: i18n issue, should use formatted number. */
+ $out->wrapWikiMsg(
+ "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>",
+ array( 'protect-cascadeon', count( $cascadeSources ) )
+ );
}
# Show an appropriate message if the user isn't allowed or able to change
# the protection settings at this time
if ( $this->disabled ) {
- $wgOut->setPageTitle( wfMessage( 'protect-title-notallowed', $this->mTitle->getPrefixedText() ) );
- $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
+ $out->setPageTitle(
+ wfMessage( 'protect-title-notallowed',
+ $this->mTitle->getPrefixedText() )
+ );
+ $out->addWikiText( $out->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
} else {
- $wgOut->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) );
- $wgOut->addWikiMsg( 'protect-text',
+ $out->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) );
+ $out->addWikiMsg( 'protect-text',
wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
}
- $wgOut->addHTML( $this->buildForm() );
- $this->showLogExtract( $wgOut );
+ $out->addHTML( $this->buildForm() );
+ $this->showLogExtract( $out );
}
/**
* Save submitted protection form
*
- * @return Boolean: success
+ * @return bool Success
*/
function save() {
- global $wgRequest, $wgUser, $wgOut;
-
# Permission check!
if ( $this->disabled ) {
$this->show();
return false;
}
- $token = $wgRequest->getVal( 'wpEditToken' );
- if ( !$wgUser->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) {
+ $request = $this->mContext->getRequest();
+ $user = $this->mContext->getUser();
+ $out = $this->mContext->getOutput();
+ $token = $request->getVal( 'wpEditToken' );
+ if ( !$user->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) {
$this->show( array( 'sessionfailure' ) );
return false;
}
@@ -302,12 +299,18 @@ class ProtectionForm {
}
}
- $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
+ $this->mCascade = $request->getBool( 'mwProtect-cascade' );
- $status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, $wgUser );
+ $status = $this->mArticle->doUpdateRestrictions(
+ $this->mRestrictions,
+ $expiry,
+ $this->mCascade,
+ $reasonstr,
+ $user
+ );
if ( !$status->isOK() ) {
- $this->show( $wgOut->parseInline( $status->getWikiText() ) );
+ $this->show( $out->parseInline( $status->getWikiText() ) );
return false;
}
@@ -328,7 +331,7 @@ class ProtectionForm {
return false;
}
- WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'mwProtectWatch' ), $this->mTitle, $wgUser );
+ WatchAction::doWatchOrUnwatch( $request->getCheck( 'mwProtectWatch' ), $this->mTitle, $user );
return true;
}
@@ -336,26 +339,20 @@ class ProtectionForm {
/**
* Build the input form
*
- * @return String: HTML form
+ * @return string HTML form
*/
function buildForm() {
- global $wgUser, $wgLang, $wgOut;
-
- $mProtectreasonother = Xml::label(
- wfMessage( 'protectcomment' )->text(),
- 'wpProtectReasonSelection'
- );
- $mProtectreason = Xml::label(
- wfMessage( 'protect-otherreason' )->text(),
- 'mwProtect-reason'
- );
-
+ $user = $this->mContext->getUser();
+ $output = $this->mContext->getOutput();
+ $lang = $this->mContext->getLanguage();
+ $cascadingRestrictionLevels = $this->mContext->getConfig()->get( 'CascadingRestrictionLevels' );
$out = '';
if ( !$this->disabled ) {
- $wgOut->addModules( 'mediawiki.legacy.protect' );
+ $output->addModules( 'mediawiki.legacy.protect' );
+ $output->addJsConfigVars( 'wgCascadeableLevels', $cascadingRestrictionLevels );
$out .= Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->mTitle->getLocalURL( 'action=protect' ),
- 'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
+ 'id' => 'mw-Protect-Form' ) );
}
$out .= Xml::openElement( 'fieldset' ) .
@@ -363,6 +360,9 @@ class ProtectionForm {
Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
Xml::openElement( 'tbody' );
+ $scExpiryOptions = wfMessage( 'protect-expiry-options' )->inContentLanguage()->text();
+ $showProtectOptions = $scExpiryOptions !== '-' && !$this->disabled;
+
// Not all languages have V_x <-> N_x relation
foreach ( $this->mRestrictions as $action => $selected ) {
// Messages:
@@ -374,15 +374,6 @@ class ProtectionForm {
Xml::openElement( 'table', array( 'id' => "mw-protect-table-$action" ) ) .
"<tr><td>" . $this->buildSelector( $action, $selected ) . "</td></tr><tr><td>";
- $reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
- wfMessage( 'protect-dropdown' )->inContentLanguage()->text(),
- wfMessage( 'protect-otherreason-op' )->inContentLanguage()->text(),
- $this->mReasonSelection,
- 'mwProtect-reason', 4 );
- $scExpiryOptions = wfMessage( 'protect-expiry-options' )->inContentLanguage()->text();
-
- $showProtectOptions = $scExpiryOptions !== '-' && !$this->disabled;
-
$mProtectexpiry = Xml::label(
wfMessage( 'protectexpiry' )->text(),
"mwProtectExpirySelection-$action"
@@ -393,13 +384,18 @@ class ProtectionForm {
);
$expiryFormOptions = '';
- if ( $this->mExistingExpiry[$action] && $this->mExistingExpiry[$action] != 'infinity' ) {
- $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action], true );
- $d = $wgLang->date( $this->mExistingExpiry[$action], true );
- $t = $wgLang->time( $this->mExistingExpiry[$action], true );
+ if ( $this->mExistingExpiry[$action] ) {
+ if ( $this->mExistingExpiry[$action] == 'infinity' ) {
+ $existingExpiryMessage = wfMessage( 'protect-existing-expiry-infinity' );
+ } else {
+ $timestamp = $lang->timeanddate( $this->mExistingExpiry[$action], true );
+ $d = $lang->date( $this->mExistingExpiry[$action], true );
+ $t = $lang->time( $this->mExistingExpiry[$action], true );
+ $existingExpiryMessage = wfMessage( 'protect-existing-expiry', $timestamp, $d, $t );
+ }
$expiryFormOptions .=
Xml::option(
- wfMessage( 'protect-existing-expiry', $timestamp, $d, $t )->text(),
+ $existingExpiryMessage->text(),
'existing',
$this->mExpirySelection[$action] == 'existing'
) . "\n";
@@ -417,7 +413,11 @@ class ProtectionForm {
}
$show = htmlspecialchars( $show );
$value = htmlspecialchars( $value );
- $expiryFormOptions .= Xml::option( $show, $value, $this->mExpirySelection[$action] === $value ) . "\n";
+ $expiryFormOptions .= Xml::option(
+ $show,
+ $value,
+ $this->mExpirySelection[$action] === $value
+ ) . "\n";
}
# Add expiry dropdown
if ( $showProtectOptions && !$this->disabled ) {
@@ -431,16 +431,13 @@ class ProtectionForm {
array(
'id' => "mwProtectExpirySelection-$action",
'name' => "wpProtectExpirySelection-$action",
- 'onchange' => "ProtectionForm.updateExpiryList(this)",
'tabindex' => '2' ) + $this->disabledAttrib,
$expiryFormOptions ) .
"</td>
</tr></table>";
}
# Add custom expiry field
- $attribs = array( 'id' => "mwProtect-$action-expires",
- 'onkeyup' => 'ProtectionForm.updateExpiry(this)',
- 'onchange' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib;
+ $attribs = array( 'id' => "mwProtect-$action-expires" ) + $this->disabledAttrib;
$out .= "<table><tr>
<td class='mw-label'>" .
$mProtectother .
@@ -479,6 +476,22 @@ class ProtectionForm {
# Add manual and custom reason field/selects as well as submit
if ( !$this->disabled ) {
+ $mProtectreasonother = Xml::label(
+ wfMessage( 'protectcomment' )->text(),
+ 'wpProtectReasonSelection'
+ );
+
+ $mProtectreason = Xml::label(
+ wfMessage( 'protect-otherreason' )->text(),
+ 'mwProtect-reason'
+ );
+
+ $reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
+ wfMessage( 'protect-dropdown' )->inContentLanguage()->text(),
+ wfMessage( 'protect-otherreason-op' )->inContentLanguage()->text(),
+ $this->mReasonSelection,
+ 'mwProtect-reason', 4 );
+
$out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
Xml::openElement( 'tbody' );
$out .= "
@@ -503,14 +516,14 @@ class ProtectionForm {
"</td>
</tr>";
# Disallow watching is user is not logged in
- if ( $wgUser->isLoggedIn() ) {
+ if ( $user->isLoggedIn() ) {
$out .= "
<tr>
<td></td>
<td class='mw-input'>" .
Xml::checkLabel( wfMessage( 'watchthis' )->text(),
'mwProtectWatch', 'mwProtectWatch',
- $wgUser->isWatched( $this->mTitle ) || $wgUser->getOption( 'watchdefault' ) ) .
+ $user->isWatched( $this->mTitle ) || $user->getOption( 'watchdefault' ) ) .
"</td>
</tr>";
}
@@ -528,7 +541,7 @@ class ProtectionForm {
}
$out .= Xml::closeElement( 'fieldset' );
- if ( $wgUser->isAllowed( 'editinterface' ) ) {
+ if ( $user->isAllowed( 'editinterface' ) ) {
$title = Title::makeTitle( NS_MEDIAWIKI, 'Protect-dropdown' );
$link = Linker::link(
$title,
@@ -540,9 +553,11 @@ class ProtectionForm {
}
if ( !$this->disabled ) {
- $out .= Html::hidden( 'wpEditToken', $wgUser->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) );
+ $out .= Html::hidden(
+ 'wpEditToken',
+ $user->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) )
+ );
$out .= Xml::closeElement( 'form' );
- $wgOut->addScript( $this->buildCleanupScript() );
}
return $out;
@@ -551,41 +566,23 @@ class ProtectionForm {
/**
* Build protection level selector
*
- * @param string $action action to protect
- * @param string $selected current protection level
- * @return String: HTML fragment
+ * @param string $action Action to protect
+ * @param string $selected Current protection level
+ * @return string HTML fragment
*/
function buildSelector( $action, $selected ) {
- global $wgRestrictionLevels, $wgUser;
-
- $levels = array();
- foreach ( $wgRestrictionLevels as $key ) {
- //don't let them choose levels above their own (aka so they can still unprotect and edit the page). but only when the form isn't disabled
- if ( $key == 'sysop' ) {
- //special case, rewrite sysop to editprotected
- if ( !$wgUser->isAllowed( 'editprotected' ) && !$this->disabled ) {
- continue;
- }
- } elseif ( $key == 'autoconfirmed' ) {
- //special case, rewrite autoconfirmed to editsemiprotected
- if ( !$wgUser->isAllowed( 'editsemiprotected' ) && !$this->disabled ) {
- continue;
- }
- } else {
- if ( !$wgUser->isAllowed( $key ) && !$this->disabled ) {
- continue;
- }
- }
- $levels[] = $key;
- }
+ // If the form is disabled, display all relevant levels. Otherwise,
+ // just show the ones this user can use.
+ $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(),
+ $this->disabled ? null : $this->mContext->getUser()
+ );
$id = 'mwProtect-level-' . $action;
$attribs = array(
'id' => $id,
'name' => $id,
'size' => count( $levels ),
- 'onchange' => 'ProtectionForm.updateLevels(this)',
- ) + $this->disabledAttrib;
+ ) + $this->disabledAttrib;
$out = Xml::openElement( 'select', $attribs );
foreach ( $levels as $key ) {
@@ -598,8 +595,8 @@ class ProtectionForm {
/**
* Prepare the label for a protection selector option
*
- * @param string $permission permission required
- * @return String
+ * @param string $permission Permission required
+ * @return string
*/
private function getOptionLabel( $permission ) {
if ( $permission == '' ) {
@@ -614,26 +611,10 @@ class ProtectionForm {
}
}
- function buildCleanupScript() {
- global $wgCascadingRestrictionLevels, $wgOut;
-
- $cascadeableLevels = $wgCascadingRestrictionLevels;
- $options = array(
- 'tableId' => 'mwProtectSet',
- 'labelText' => wfMessage( 'protect-unchain-permissions' )->plain(),
- 'numTypes' => count( $this->mApplicableTypes ),
- 'existingMatch' => count( array_unique( $this->mExistingExpiry ) ) === 1,
- );
-
- $wgOut->addJsConfigVars( 'wgCascadeableLevels', $cascadeableLevels );
- $script = Xml::encodeJsCall( 'ProtectionForm.init', array( $options ) );
- return Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) );
- }
-
/**
* Show protection long extracts for this page
*
- * @param $out OutputPage
+ * @param OutputPage $out
* @access private
*/
function showLogExtract( &$out ) {
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
deleted file mode 100644
index bf1c4059..00000000
--- a/includes/ProxyTools.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-/**
- * Functions for dealing with proxies.
- *
- * 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
- */
-
-/**
- * Extracts the XFF string from the request header
- * Note: headers are spoofable
- *
- * @deprecated in 1.19; use $wgRequest->getHeader( 'X-Forwarded-For' ) instead.
- * @return string
- */
-function wfGetForwardedFor() {
- wfDeprecated( __METHOD__, '1.19' );
- global $wgRequest;
- return $wgRequest->getHeader( 'X-Forwarded-For' );
-}
-
-/**
- * Returns the browser/OS data from the request header
- * Note: headers are spoofable
- *
- * @deprecated in 1.18; use $wgRequest->getHeader( 'User-Agent' ) instead.
- * @return string
- */
-function wfGetAgent() {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgRequest;
- return $wgRequest->getHeader( 'User-Agent' );
-}
-
-/**
- * Work out the IP address based on various globals
- * For trusted proxies, use the XFF client IP (first of the chain)
- *
- * @deprecated in 1.19; call $wgRequest->getIP() directly.
- * @return string
- */
-function wfGetIP() {
- wfDeprecated( __METHOD__, '1.19' );
- global $wgRequest;
- return $wgRequest->getIP();
-}
-
-/**
- * Checks if an IP is a trusted proxy provider.
- * Useful to tell if X-Forwarded-For data is possibly bogus.
- * Squid cache servers for the site are whitelisted.
- *
- * @param $ip String
- * @return bool
- */
-function wfIsTrustedProxy( $ip ) {
- $trusted = wfIsConfiguredProxy( $ip );
- wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) );
- return $trusted;
-}
-
-/**
- * Checks if an IP matches a proxy we've configured.
- * @param $ip String
- * @return bool
- */
-function wfIsConfiguredProxy( $ip ) {
- global $wgSquidServers, $wgSquidServersNoPurge;
- $trusted = in_array( $ip, $wgSquidServers ) ||
- in_array( $ip, $wgSquidServersNoPurge );
- return $trusted;
-}
diff --git a/includes/Revision.php b/includes/Revision.php
index 233eac01..28a825d0 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -41,6 +41,11 @@ class Revision implements IDBAccessObject {
protected $mParentId;
protected $mComment;
protected $mText;
+ protected $mTextId;
+
+ /**
+ * @var stdClass|null
+ */
protected $mTextRow;
/**
@@ -86,9 +91,9 @@ class Revision implements IDBAccessObject {
* Revision::READ_LATEST : Select the data from the master
* Revision::READ_LOCKING : Select & lock the data from the master
*
- * @param $id Integer
- * @param $flags Integer (optional)
- * @return Revision or null
+ * @param int $id
+ * @param int $flags (optional)
+ * @return Revision|null
*/
public static function newFromId( $id, $flags = 0 ) {
return self::newFromConds( array( 'rev_id' => intval( $id ) ), $flags );
@@ -103,10 +108,10 @@ class Revision implements IDBAccessObject {
* Revision::READ_LATEST : Select the data from the master
* Revision::READ_LOCKING : Select & lock the data from the master
*
- * @param $title Title
- * @param $id Integer (optional)
- * @param $flags Integer Bitfield (optional)
- * @return Revision or null
+ * @param Title $title
+ * @param int $id (optional)
+ * @param int $flags Bitfield (optional)
+ * @return Revision|null
*/
public static function newFromTitle( $title, $id = 0, $flags = 0 ) {
$conds = array(
@@ -134,10 +139,10 @@ class Revision implements IDBAccessObject {
* Revision::READ_LATEST : Select the data from the master (since 1.20)
* Revision::READ_LOCKING : Select & lock the data from the master
*
- * @param $revId Integer
- * @param $pageId Integer (optional)
- * @param $flags Integer Bitfield (optional)
- * @return Revision or null
+ * @param int $pageId
+ * @param int $revId (optional)
+ * @param int $flags Bitfield (optional)
+ * @return Revision|null
*/
public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
$conds = array( 'page_id' => $pageId );
@@ -155,8 +160,8 @@ class Revision implements IDBAccessObject {
* for permissions or even inserted (as in Special:Undelete)
* @todo FIXME: Should be a subclass for RevisionDelete. [TS]
*
- * @param $row
- * @param $overrides array
+ * @param object $row
+ * @param array $overrides
*
* @throws MWException
* @return Revision
@@ -205,7 +210,7 @@ class Revision implements IDBAccessObject {
/**
* @since 1.19
*
- * @param $row
+ * @param object $row
* @return Revision
*/
public static function newFromRow( $row ) {
@@ -216,9 +221,9 @@ class Revision implements IDBAccessObject {
* Load a page revision from a given revision ID number.
* Returns null if no such revision can be found.
*
- * @param $db DatabaseBase
- * @param $id Integer
- * @return Revision or null
+ * @param DatabaseBase $db
+ * @param int $id
+ * @return Revision|null
*/
public static function loadFromId( $db, $id ) {
return self::loadFromConds( $db, array( 'rev_id' => intval( $id ) ) );
@@ -229,10 +234,10 @@ class Revision implements IDBAccessObject {
* that's attached to a given page. If not attached
* to that page, will return null.
*
- * @param $db DatabaseBase
- * @param $pageid Integer
- * @param $id Integer
- * @return Revision or null
+ * @param DatabaseBase $db
+ * @param int $pageid
+ * @param int $id
+ * @return Revision|null
*/
public static function loadFromPageId( $db, $pageid, $id = 0 ) {
$conds = array( 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) );
@@ -249,10 +254,10 @@ class Revision implements IDBAccessObject {
* that's attached to a given page. If not attached
* to that page, will return null.
*
- * @param $db DatabaseBase
- * @param $title Title
- * @param $id Integer
- * @return Revision or null
+ * @param DatabaseBase $db
+ * @param Title $title
+ * @param int $id
+ * @return Revision|null
*/
public static function loadFromTitle( $db, $title, $id = 0 ) {
if ( $id ) {
@@ -274,10 +279,10 @@ class Revision implements IDBAccessObject {
* WARNING: Timestamps may in some circumstances not be unique,
* so this isn't the best key to use.
*
- * @param $db DatabaseBase
- * @param $title Title
- * @param $timestamp String
- * @return Revision or null
+ * @param DatabaseBase $db
+ * @param Title $title
+ * @param string $timestamp
+ * @return Revision|null
*/
public static function loadFromTimestamp( $db, $title, $timestamp ) {
return self::loadFromConds( $db,
@@ -292,14 +297,14 @@ class Revision implements IDBAccessObject {
/**
* Given a set of conditions, fetch a revision.
*
- * @param $conditions Array
- * @param $flags integer (optional)
- * @return Revision or null
+ * @param array $conditions
+ * @param int $flags (optional)
+ * @return Revision|null
*/
private static function newFromConds( $conditions, $flags = 0 ) {
$db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
$rev = self::loadFromConds( $db, $conditions, $flags );
- if ( is_null( $rev ) && wfGetLB()->getServerCount() > 1 ) {
+ if ( $rev === null && wfGetLB()->getServerCount() > 1 ) {
if ( !( $flags & self::READ_LATEST ) ) {
$dbw = wfGetDB( DB_MASTER );
$rev = self::loadFromConds( $dbw, $conditions, $flags );
@@ -315,10 +320,10 @@ class Revision implements IDBAccessObject {
* Given a set of conditions, fetch a revision from
* the given database connection.
*
- * @param $db DatabaseBase
- * @param $conditions Array
- * @param $flags integer (optional)
- * @return Revision or null
+ * @param DatabaseBase $db
+ * @param array $conditions
+ * @param int $flags (optional)
+ * @return Revision|null
*/
private static function loadFromConds( $db, $conditions, $flags = 0 ) {
$res = self::fetchFromConds( $db, $conditions, $flags );
@@ -338,7 +343,7 @@ class Revision implements IDBAccessObject {
* fetch all of a given page's revisions in turn.
* Each row can be fed to the constructor to get objects.
*
- * @param $title Title
+ * @param Title $title
* @return ResultWrapper
*/
public static function fetchRevision( $title ) {
@@ -357,9 +362,9 @@ class Revision implements IDBAccessObject {
* which will return matching database rows with the
* fields necessary to build Revision objects.
*
- * @param $db DatabaseBase
- * @param $conditions Array
- * @param $flags integer (optional)
+ * @param DatabaseBase $db
+ * @param array $conditions
+ * @param int $flags (optional)
* @return ResultWrapper
*/
private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
@@ -386,7 +391,7 @@ class Revision implements IDBAccessObject {
* Return the value of a select() JOIN conds array for the user table.
* This will get user table rows for logged-in users.
* @since 1.19
- * @return Array
+ * @return array
*/
public static function userJoinCond() {
return array( 'LEFT JOIN', array( 'rev_user != 0', 'user_id = rev_user' ) );
@@ -396,7 +401,7 @@ class Revision implements IDBAccessObject {
* Return the value of a select() page conds array for the page table.
* This will assure that the revision(s) are not orphaned from live pages.
* @since 1.19
- * @return Array
+ * @return array
*/
public static function pageJoinCond() {
return array( 'INNER JOIN', array( 'page_id = rev_page' ) );
@@ -434,6 +439,37 @@ class Revision implements IDBAccessObject {
}
/**
+ * Return the list of revision fields that should be selected to create
+ * a new revision from an archive row.
+ * @return array
+ */
+ public static function selectArchiveFields() {
+ global $wgContentHandlerUseDB;
+ $fields = array(
+ 'ar_id',
+ 'ar_page_id',
+ 'ar_rev_id',
+ 'ar_text',
+ 'ar_text_id',
+ 'ar_timestamp',
+ 'ar_comment',
+ 'ar_user_text',
+ 'ar_user',
+ 'ar_minor_edit',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_parent_id',
+ 'ar_sha1',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+ return $fields;
+ }
+
+ /**
* Return the list of text fields that should be selected to read the
* revision text
* @return array
@@ -470,8 +506,8 @@ class Revision implements IDBAccessObject {
/**
* Do a batched query to get the parent revision lengths
- * @param $db DatabaseBase
- * @param $revIds Array
+ * @param DatabaseBase $db
+ * @param array $revIds
* @return array
*/
public static function getParentLengths( $db, array $revIds ) {
@@ -494,20 +530,20 @@ class Revision implements IDBAccessObject {
/**
* Constructor
*
- * @param $row Mixed: either a database row or an array
+ * @param object|array $row Either a database row or an array
* @throws MWException
* @access private
*/
function __construct( $row ) {
if ( is_object( $row ) ) {
- $this->mId = intval( $row->rev_id );
- $this->mPage = intval( $row->rev_page );
- $this->mTextId = intval( $row->rev_text_id );
- $this->mComment = $row->rev_comment;
- $this->mUser = intval( $row->rev_user );
+ $this->mId = intval( $row->rev_id );
+ $this->mPage = intval( $row->rev_page );
+ $this->mTextId = intval( $row->rev_text_id );
+ $this->mComment = $row->rev_comment;
+ $this->mUser = intval( $row->rev_user );
$this->mMinorEdit = intval( $row->rev_minor_edit );
- $this->mTimestamp = $row->rev_timestamp;
- $this->mDeleted = intval( $row->rev_deleted );
+ $this->mTimestamp = $row->rev_timestamp;
+ $this->mDeleted = intval( $row->rev_deleted );
if ( !isset( $row->rev_parent_id ) ) {
$this->mParentId = null;
@@ -535,13 +571,13 @@ class Revision implements IDBAccessObject {
$this->mTitle = null;
}
- if ( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) {
+ if ( !isset( $row->rev_content_model ) ) {
$this->mContentModel = null; # determine on demand if needed
} else {
$this->mContentModel = strval( $row->rev_content_model );
}
- if ( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
+ if ( !isset( $row->rev_content_format ) ) {
$this->mContentFormat = null; # determine on demand if needed
} else {
$this->mContentFormat = strval( $row->rev_content_format );
@@ -582,27 +618,31 @@ class Revision implements IDBAccessObject {
# also set text to null?
}
- $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
- $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
- $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
- $this->mUserText = isset( $row['user_text'] ) ? strval( $row['user_text'] ) : $wgUser->getName();
- $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
+ $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
+ $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
+ $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
+ $this->mUserText = isset( $row['user_text'] )
+ ? strval( $row['user_text'] ) : $wgUser->getName();
+ $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
$this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
- $this->mTimestamp = isset( $row['timestamp'] ) ? strval( $row['timestamp'] ) : wfTimestampNow();
- $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
- $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
- $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
- $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
-
- $this->mContentModel = isset( $row['content_model'] ) ? strval( $row['content_model'] ) : null;
- $this->mContentFormat = isset( $row['content_format'] ) ? strval( $row['content_format'] ) : null;
+ $this->mTimestamp = isset( $row['timestamp'] )
+ ? strval( $row['timestamp'] ) : wfTimestampNow();
+ $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
+ $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
+ $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
+ $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
+
+ $this->mContentModel = isset( $row['content_model'] )
+ ? strval( $row['content_model'] ) : null;
+ $this->mContentFormat = isset( $row['content_format'] )
+ ? strval( $row['content_format'] ) : null;
// Enforce spacing trimming on supplied text
- $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
- $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
- $this->mTextRow = null;
+ $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
+ $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
+ $this->mTextRow = null;
- $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
+ $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
// if we have a Content object, override mText and mContentModel
if ( !empty( $row['content'] ) ) {
@@ -617,7 +657,7 @@ class Revision implements IDBAccessObject {
$this->mContentHandler = null;
$this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
- } elseif ( !is_null( $this->mText ) ) {
+ } elseif ( $this->mText !== null ) {
$handler = $this->getContentHandler();
$this->mContent = $handler->unserializeContent( $this->mText );
}
@@ -639,7 +679,7 @@ class Revision implements IDBAccessObject {
// If we still have no length, see it we have the text to figure it out
if ( !$this->mSize ) {
- if ( !is_null( $this->mContent ) ) {
+ if ( $this->mContent !== null ) {
$this->mSize = $this->mContent->getSize();
} else {
#NOTE: this should never happen if we have either text or content object!
@@ -649,7 +689,7 @@ class Revision implements IDBAccessObject {
// Same for sha1
if ( $this->mSha1 === null ) {
- $this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
+ $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
}
// force lazy init
@@ -664,7 +704,7 @@ class Revision implements IDBAccessObject {
/**
* Get revision ID
*
- * @return Integer|null
+ * @return int|null
*/
public function getId() {
return $this->mId;
@@ -674,7 +714,7 @@ class Revision implements IDBAccessObject {
* Set the revision ID
*
* @since 1.19
- * @param $id Integer
+ * @param int $id
*/
public function setId( $id ) {
$this->mId = $id;
@@ -683,7 +723,7 @@ class Revision implements IDBAccessObject {
/**
* Get text row ID
*
- * @return Integer|null
+ * @return int|null
*/
public function getTextId() {
return $this->mTextId;
@@ -692,7 +732,7 @@ class Revision implements IDBAccessObject {
/**
* Get parent revision ID (the original previous page revision)
*
- * @return Integer|null
+ * @return int|null
*/
public function getParentId() {
return $this->mParentId;
@@ -701,7 +741,7 @@ class Revision implements IDBAccessObject {
/**
* Returns the length of the text in this revision, or null if unknown.
*
- * @return Integer|null
+ * @return int|null
*/
public function getSize() {
return $this->mSize;
@@ -710,7 +750,7 @@ class Revision implements IDBAccessObject {
/**
* Returns the base36 sha1 of the text in this revision, or null if unknown.
*
- * @return String|null
+ * @return string|null
*/
public function getSha1() {
return $this->mSha1;
@@ -724,10 +764,11 @@ class Revision implements IDBAccessObject {
* @return Title|null
*/
public function getTitle() {
- if ( isset( $this->mTitle ) ) {
+ if ( $this->mTitle !== null ) {
return $this->mTitle;
}
- if ( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
+ //rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
+ if ( $this->mId !== null ) {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
array( 'page', 'revision' ),
@@ -740,7 +781,7 @@ class Revision implements IDBAccessObject {
}
}
- if ( !$this->mTitle && !is_null( $this->mPage ) && $this->mPage > 0 ) {
+ if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
$this->mTitle = Title::newFromID( $this->mPage );
}
@@ -750,7 +791,7 @@ class Revision implements IDBAccessObject {
/**
* Set the title of the revision
*
- * @param $title Title
+ * @param Title $title
*/
public function setTitle( $title ) {
$this->mTitle = $title;
@@ -759,7 +800,7 @@ class Revision implements IDBAccessObject {
/**
* Get the page ID
*
- * @return Integer|null
+ * @return int|null
*/
public function getPage() {
return $this->mPage;
@@ -770,13 +811,13 @@ class Revision implements IDBAccessObject {
* If the specified audience does not have access to it, zero will be
* returned.
*
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the ID regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
- * @return Integer
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the ID regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return int
*/
public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
@@ -791,7 +832,7 @@ class Revision implements IDBAccessObject {
/**
* Fetch revision's user id without regard for the current user's permissions
*
- * @return String
+ * @return string
*/
public function getRawUser() {
return $this->mUser;
@@ -802,12 +843,12 @@ class Revision implements IDBAccessObject {
* If the specified audience does not have access to the username, an
* empty string will be returned.
*
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return string
*/
public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
@@ -823,7 +864,7 @@ class Revision implements IDBAccessObject {
/**
* Fetch revision's username without regard for view restrictions
*
- * @return String
+ * @return string
*/
public function getRawUserText() {
if ( $this->mUserText === null ) {
@@ -842,13 +883,13 @@ class Revision implements IDBAccessObject {
* If the specified audience does not have access to the comment, an
* empty string will be returned.
*
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
- * @return String
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return string
*/
function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
@@ -863,21 +904,21 @@ class Revision implements IDBAccessObject {
/**
* Fetch revision comment without regard for the current user's permissions
*
- * @return String
+ * @return string
*/
public function getRawComment() {
return $this->mComment;
}
/**
- * @return Boolean
+ * @return bool
*/
public function isMinor() {
return (bool)$this->mMinorEdit;
}
/**
- * @return integer rcid of the unpatrolled row, zero if there isn't one
+ * @return int Rcid of the unpatrolled row, zero if there isn't one
*/
public function isUnpatrolled() {
if ( $this->mUnpatrolled !== null ) {
@@ -911,9 +952,9 @@ class Revision implements IDBAccessObject {
}
/**
- * @param int $field one of DELETED_* bitfield constants
+ * @param int $field One of DELETED_* bitfield constants
*
- * @return Boolean
+ * @return bool
*/
public function isDeleted( $field ) {
return ( $this->mDeleted & $field ) == $field;
@@ -933,16 +974,16 @@ class Revision implements IDBAccessObject {
* If the specified audience does not have the ability to view this
* revision, an empty string will be returned.
*
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
*
- * @deprecated in 1.21, use getContent() instead
+ * @deprecated since 1.21, use getContent() instead
* @todo Replace usage in core
- * @return String
+ * @return string
*/
public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
ContentHandler::deprecated( __METHOD__, '1.21' );
@@ -956,12 +997,12 @@ class Revision implements IDBAccessObject {
* If the specified audience does not have the ability to view this
* revision, null will be returned.
*
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @since 1.21
* @return Content|null
*/
@@ -976,20 +1017,9 @@ class Revision implements IDBAccessObject {
}
/**
- * Alias for getText(Revision::FOR_THIS_USER)
- *
- * @deprecated since 1.17
- * @return String
- */
- public function revText() {
- wfDeprecated( __METHOD__, '1.17' );
- return $this->getText( self::FOR_THIS_USER );
- }
-
- /**
* Fetch revision text without regard for view restrictions
*
- * @return String
+ * @return string
*
* @deprecated since 1.21. Instead, use Revision::getContent( Revision::RAW )
* or Revision::getSerializedData() as appropriate.
@@ -1003,10 +1033,10 @@ class Revision implements IDBAccessObject {
* Fetch original serialized data without regard for view restrictions
*
* @since 1.21
- * @return String
+ * @return string
*/
public function getSerializedData() {
- if ( is_null( $this->mText ) ) {
+ if ( $this->mText === null ) {
$this->mText = $this->loadText();
}
@@ -1020,12 +1050,12 @@ class Revision implements IDBAccessObject {
* fresh clone.
*
* @since 1.21
- * @return Content|null the Revision's content, or null on failure.
+ * @return Content|null The Revision's content, or null on failure.
*/
protected function getContentInternal() {
- if ( is_null( $this->mContent ) ) {
+ if ( $this->mContent === null ) {
// Revision is immutable. Load on demand:
- if ( is_null( $this->mText ) ) {
+ if ( $this->mText === null ) {
$this->mText = $this->loadText();
}
@@ -1051,7 +1081,8 @@ class Revision implements IDBAccessObject {
* used to determine the content model to use. If no title is know, CONTENT_MODEL_WIKITEXT
* is used as a last resort.
*
- * @return String the content model id associated with this revision, see the CONTENT_MODEL_XXX constants.
+ * @return string The content model id associated with this revision,
+ * see the CONTENT_MODEL_XXX constants.
**/
public function getContentModel() {
if ( !$this->mContentModel ) {
@@ -1070,7 +1101,8 @@ class Revision implements IDBAccessObject {
* If no content format was stored in the database, the default format for this
* revision's content model is returned.
*
- * @return String the content format id associated with this revision, see the CONTENT_FORMAT_XXX constants.
+ * @return string The content format id associated with this revision,
+ * see the CONTENT_FORMAT_XXX constants.
**/
public function getContentFormat() {
if ( !$this->mContentFormat ) {
@@ -1097,7 +1129,8 @@ class Revision implements IDBAccessObject {
$format = $this->getContentFormat();
if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
- throw new MWException( "Oops, the content format $format is not supported for this content model, $model" );
+ throw new MWException( "Oops, the content format $format is not supported for "
+ . "this content model, $model" );
}
}
@@ -1105,14 +1138,14 @@ class Revision implements IDBAccessObject {
}
/**
- * @return String
+ * @return string
*/
public function getTimestamp() {
return wfTimestamp( TS_MW, $this->mTimestamp );
}
/**
- * @return Boolean
+ * @return bool
*/
public function isCurrent() {
return $this->mCurrent;
@@ -1136,7 +1169,7 @@ class Revision implements IDBAccessObject {
/**
* Get next revision for this title
*
- * @return Revision or null
+ * @return Revision|null
*/
public function getNext() {
if ( $this->getTitle() ) {
@@ -1152,11 +1185,11 @@ class Revision implements IDBAccessObject {
* Get previous revision Id for this page_id
* This is used to populate rev_parent_id on save
*
- * @param $db DatabaseBase
- * @return Integer
+ * @param DatabaseBase $db
+ * @return int
*/
private function getPreviousRevisionId( $db ) {
- if ( is_null( $this->mPage ) ) {
+ if ( $this->mPage === null ) {
return 0;
}
# Use page_latest if ID is not given
@@ -1174,18 +1207,18 @@ class Revision implements IDBAccessObject {
}
/**
- * Get revision text associated with an old or archive row
- * $row is usually an object from wfFetchRow(), both the flags and the text
- * field must be included
- *
- * @param $row Object: the text data
- * @param string $prefix table prefix (default 'old_')
- * @param string|false $wiki the name of the wiki to load the revision text from
- * (same as the the wiki $row was loaded from) or false to indicate the local
- * wiki (this is the default). Otherwise, it must be a symbolic wiki database
- * identifier as understood by the LoadBalancer class.
- * @return String: text the text requested or false on failure
- */
+ * Get revision text associated with an old or archive row
+ * $row is usually an object from wfFetchRow(), both the flags and the text
+ * field must be included.
+ *
+ * @param stdClass $row The text data
+ * @param string $prefix Table prefix (default 'old_')
+ * @param string|bool $wiki The name of the wiki to load the revision text from
+ * (same as the the wiki $row was loaded from) or false to indicate the local
+ * wiki (this is the default). Otherwise, it must be a symbolic wiki database
+ * identifier as understood by the LoadBalancer class.
+ * @return string Text the text requested or false on failure
+ */
public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
wfProfileIn( __METHOD__ );
@@ -1232,8 +1265,8 @@ class Revision implements IDBAccessObject {
* data is compressed, and 'utf-8' if we're saving in UTF-8
* mode.
*
- * @param $text Mixed: reference to a text
- * @return String
+ * @param mixed $text Reference to a text
+ * @return string
*/
public static function compressRevisionText( &$text ) {
global $wgCompressRevisions;
@@ -1257,9 +1290,9 @@ class Revision implements IDBAccessObject {
/**
* Re-converts revision text according to it's flags.
*
- * @param $text Mixed: reference to a text
- * @param $flags array: compression flags
- * @return String|bool decompressed text, or false on failure
+ * @param mixed $text Reference to a text
+ * @param array $flags Compression flags
+ * @return string|bool Decompressed text, or false on failure
*/
public static function decompressRevisionText( $text, $flags ) {
if ( in_array( 'gzip', $flags ) ) {
@@ -1281,8 +1314,8 @@ class Revision implements IDBAccessObject {
global $wgLegacyEncoding;
if ( $text !== false && $wgLegacyEncoding
- && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags ) )
- {
+ && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags )
+ ) {
# Old revisions kept around in a legacy encoding?
# Upconvert on demand.
# ("utf8" checked for compatibility with some broken
@@ -1298,9 +1331,9 @@ class Revision implements IDBAccessObject {
* Insert a new revision into the database, returning the new revision ID
* number on success and dies horribly on failure.
*
- * @param $dbw DatabaseBase: (master connection)
+ * @param DatabaseBase $dbw (master connection)
* @throws MWException
- * @return Integer
+ * @return int
*/
public function insertOn( $dbw ) {
global $wgDefaultExternalStore, $wgContentHandlerUseDB;
@@ -1327,7 +1360,7 @@ class Revision implements IDBAccessObject {
}
# Record the text (or external storage URL) to the text table
- if ( !isset( $this->mTextId ) ) {
+ if ( $this->mTextId === null ) {
$old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
$dbw->insert( 'text',
array(
@@ -1344,7 +1377,7 @@ class Revision implements IDBAccessObject {
}
# Record the edit in revisions
- $rev_id = isset( $this->mId )
+ $rev_id = $this->mId !== null
? $this->mId
: $dbw->nextSequenceValue( 'revision_rev_id_seq' );
$row = array(
@@ -1358,17 +1391,18 @@ class Revision implements IDBAccessObject {
'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
'rev_deleted' => $this->mDeleted,
'rev_len' => $this->mSize,
- 'rev_parent_id' => is_null( $this->mParentId )
+ 'rev_parent_id' => $this->mParentId === null
? $this->getPreviousRevisionId( $dbw )
: $this->mParentId,
- 'rev_sha1' => is_null( $this->mSha1 )
+ 'rev_sha1' => $this->mSha1 === null
? Revision::base36Sha1( $this->mText )
: $this->mSha1,
);
if ( $wgContentHandlerUseDB ) {
//NOTE: Store null for the default model and format, to save space.
- //XXX: Makes the DB sensitive to changed defaults. Make this behavior optional? Only in miser mode?
+ //XXX: Makes the DB sensitive to changed defaults.
+ // Make this behavior optional? Only in miser mode?
$model = $this->getContentModel();
$format = $this->getContentFormat();
@@ -1377,7 +1411,8 @@ class Revision implements IDBAccessObject {
if ( $title === null ) {
wfProfileOut( __METHOD__ );
- throw new MWException( "Insufficient information to determine the title of the revision's page!" );
+ throw new MWException( "Insufficient information to determine the title of the "
+ . "revision's page!" );
}
$defaultModel = ContentHandler::getDefaultModelFor( $title );
@@ -1389,7 +1424,7 @@ class Revision implements IDBAccessObject {
$dbw->insert( 'revision', $row, __METHOD__ );
- $this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
+ $this->mId = $rev_id !== null ? $rev_id : $dbw->insertId();
wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
@@ -1413,7 +1448,8 @@ class Revision implements IDBAccessObject {
}
if ( !$wgContentHandlerUseDB && $title ) {
- // if $wgContentHandlerUseDB is not set, all revisions must use the default content model and format.
+ // if $wgContentHandlerUseDB is not set,
+ // all revisions must use the default content model and format.
$defaultModel = ContentHandler::getDefaultModelFor( $title );
$defaultHandler = ContentHandler::getForModelID( $defaultModel );
@@ -1422,15 +1458,17 @@ class Revision implements IDBAccessObject {
if ( $this->getContentModel() != $defaultModel ) {
$t = $title->getPrefixedDBkey();
- throw new MWException( "Can't save non-default content model with \$wgContentHandlerUseDB disabled: "
- . "model is $model , default for $t is $defaultModel" );
+ throw new MWException( "Can't save non-default content model with "
+ . "\$wgContentHandlerUseDB disabled: model is $model, "
+ . "default for $t is $defaultModel" );
}
if ( $this->getContentFormat() != $defaultFormat ) {
$t = $title->getPrefixedDBkey();
- throw new MWException( "Can't use non-default content format with \$wgContentHandlerUseDB disabled: "
- . "format is $format, default for $t is $defaultFormat" );
+ throw new MWException( "Can't use non-default content format with "
+ . "\$wgContentHandlerUseDB disabled: format is $format, "
+ . "default for $t is $defaultFormat" );
}
}
@@ -1445,8 +1483,8 @@ class Revision implements IDBAccessObject {
/**
* Get the base 36 SHA-1 value for a string of text
- * @param $text String
- * @return String
+ * @param string $text
+ * @return string
*/
public static function base36Sha1( $text ) {
return wfBaseConvert( sha1( $text ), 16, 36, 31 );
@@ -1456,7 +1494,7 @@ class Revision implements IDBAccessObject {
* Lazy-load the revision's text.
* Currently hardcoded to the 'text' table storage engine.
*
- * @return String|bool the revision's text, or false on failure
+ * @return string|bool The revision's text, or false on failure
*/
protected function loadText() {
wfProfileIn( __METHOD__ );
@@ -1475,7 +1513,7 @@ class Revision implements IDBAccessObject {
}
// If we kept data for lazy extraction, use it now...
- if ( isset( $this->mTextRow ) ) {
+ if ( $this->mTextRow !== null ) {
$row = $this->mTextRow;
$this->mTextRow = null;
} else {
@@ -1530,13 +1568,14 @@ class Revision implements IDBAccessObject {
* Such revisions can for instance identify page rename
* operations and other such meta-modifications.
*
- * @param $dbw DatabaseBase
- * @param $pageId Integer: ID number of the page to read from
- * @param string $summary revision's summary
- * @param $minor Boolean: whether the revision should be considered as minor
- * @return Revision|null on error
+ * @param DatabaseBase $dbw
+ * @param int $pageId ID number of the page to read from
+ * @param string $summary Revision's summary
+ * @param bool $minor Whether the revision should be considered as minor
+ * @param User|null $user User object to use or null for $wgUser
+ * @return Revision|null Revision or null on error
*/
- public static function newNullRevision( $dbw, $pageId, $summary, $minor ) {
+ public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
global $wgContentHandlerUseDB;
wfProfileIn( __METHOD__ );
@@ -1559,8 +1598,15 @@ class Revision implements IDBAccessObject {
__METHOD__ );
if ( $current ) {
+ if ( !$user ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
+
$row = array(
'page' => $pageId,
+ 'user_text' => $user->getName(),
+ 'user' => $user->getId(),
'comment' => $summary,
'minor_edit' => $minor,
'text_id' => $current->rev_text_id,
@@ -1588,11 +1634,11 @@ class Revision implements IDBAccessObject {
* Determine if the current user is allowed to view a particular
* field of this revision, if it's marked as deleted.
*
- * @param $field Integer:one of self::DELETED_TEXT,
+ * @param int $field One of self::DELETED_TEXT,
* self::DELETED_COMMENT,
* self::DELETED_USER
- * @param $user User object to check, or null to use $wgUser
- * @return Boolean
+ * @param User|null $user User object to check, or null to use $wgUser
+ * @return bool
*/
public function userCan( $field, User $user = null ) {
return self::userCanBitfield( $this->mDeleted, $field, $user );
@@ -1603,28 +1649,44 @@ class Revision implements IDBAccessObject {
* field of this revision, if it's marked as deleted. This is used
* by various classes to avoid duplication.
*
- * @param $bitfield Integer: current field
- * @param $field Integer: one of self::DELETED_TEXT = File::DELETED_FILE,
+ * @param int $bitfield Current field
+ * @param int $field One of self::DELETED_TEXT = File::DELETED_FILE,
* self::DELETED_COMMENT = File::DELETED_COMMENT,
* self::DELETED_USER = File::DELETED_USER
- * @param $user User object to check, or null to use $wgUser
- * @return Boolean
- */
- public static function userCanBitfield( $bitfield, $field, User $user = null ) {
+ * @param User|null $user User object to check, or null to use $wgUser
+ * @param Title|null $title A Title object to check for per-page restrictions on,
+ * instead of just plain userrights
+ * @return bool
+ */
+ public static function userCanBitfield( $bitfield, $field, User $user = null,
+ Title $title = null
+ ) {
if ( $bitfield & $field ) { // aspect is deleted
+ if ( $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
if ( $bitfield & self::DELETED_RESTRICTED ) {
- $permission = 'suppressrevision';
+ $permissions = array( 'suppressrevision', 'viewsuppressed' );
} elseif ( $field & self::DELETED_TEXT ) {
- $permission = 'deletedtext';
+ $permissions = array( 'deletedtext' );
} else {
- $permission = 'deletedhistory';
+ $permissions = array( 'deletedhistory' );
}
- wfDebug( "Checking for $permission due to $field match on $bitfield\n" );
- if ( $user === null ) {
- global $wgUser;
- $user = $wgUser;
+ $permissionlist = implode( ', ', $permissions );
+ if ( $title === null ) {
+ wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
+ return call_user_func_array( array( $user, 'isAllowedAny' ), $permissions );
+ } else {
+ $text = $title->getPrefixedText();
+ wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
+ foreach ( $permissions as $perm ) {
+ if ( $title->userCan( $perm, $user ) ) {
+ return true;
+ }
+ }
+ return false;
}
- return $user->isAllowed( $permission );
} else {
return true;
}
@@ -1633,9 +1695,9 @@ class Revision implements IDBAccessObject {
/**
* Get rev_timestamp from rev_id, without loading the rest of the row
*
- * @param $title Title
- * @param $id Integer
- * @return String
+ * @param Title $title
+ * @param int $id
+ * @return string
*/
static function getTimestampFromId( $title, $id ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -1657,9 +1719,9 @@ class Revision implements IDBAccessObject {
/**
* Get count of revisions per page...not very efficient
*
- * @param $db DatabaseBase
- * @param $id Integer: page id
- * @return Integer
+ * @param DatabaseBase $db
+ * @param int $id Page id
+ * @return int
*/
static function countByPageId( $db, $id ) {
$row = $db->selectRow( 'revision', array( 'revCount' => 'COUNT(*)' ),
@@ -1673,9 +1735,9 @@ class Revision implements IDBAccessObject {
/**
* Get count of revisions per page...not very efficient
*
- * @param $db DatabaseBase
- * @param $title Title
- * @return Integer
+ * @param DatabaseBase $db
+ * @param Title $title
+ * @return int
*/
static function countByTitle( $db, $title ) {
$id = $title->getArticleID();
@@ -1691,12 +1753,13 @@ class Revision implements IDBAccessObject {
* 50 revisions for the sake of performance.
*
* @since 1.20
+ * @deprecated since 1.24
*
- * @param DatabaseBase|int $db the Database to perform the check on. May be given as a Database object or
- * a database identifier usable with wfGetDB.
- * @param int $pageId the ID of the page in question
- * @param int $userId the ID of the user in question
- * @param string $since look at edits since this time
+ * @param DatabaseBase|int $db The Database to perform the check on. May be given as a
+ * Database object or a database identifier usable with wfGetDB.
+ * @param int $pageId The ID of the page in question
+ * @param int $userId The ID of the user in question
+ * @param string $since Look at edits since this time
*
* @return bool True if the given user was the only one to edit since the given timestamp
*/
diff --git a/includes/RevisionList.php b/includes/RevisionList.php
index 1b865bb0..d10b5412 100644
--- a/includes/RevisionList.php
+++ b/includes/RevisionList.php
@@ -24,17 +24,21 @@
* List for revision table items for a single page
*/
abstract class RevisionListBase extends ContextSource {
- /**
- * @var Title
- */
- var $title;
+ /** @var Title */
+ public $title;
+
+ /** @var array */
+ protected $ids;
- var $ids, $res, $current;
+ protected $res;
+
+ /** @var bool|object */
+ protected $current;
/**
* Construct a revision list for a given title
- * @param $context IContextSource
- * @param $title Title
+ * @param IContextSource $context
+ * @param Title $title
*/
function __construct( IContextSource $context, Title $title ) {
$this->setContext( $context );
@@ -43,7 +47,7 @@ abstract class RevisionListBase extends ContextSource {
/**
* Select items only where the ID is any of the specified values
- * @param $ids Array
+ * @param array $ids
*/
function filterByIds( array $ids ) {
$this->ids = $ids;
@@ -72,7 +76,7 @@ abstract class RevisionListBase extends ContextSource {
/**
* Start iteration. This must be called before current() or next().
- * @return First list item
+ * @return Revision First list item
*/
public function reset() {
if ( !$this->res ) {
@@ -86,6 +90,7 @@ abstract class RevisionListBase extends ContextSource {
/**
* Get the current list item, or false if we are at the end
+ * @return Revision
*/
public function current() {
return $this->current;
@@ -93,6 +98,7 @@ abstract class RevisionListBase extends ContextSource {
/**
* Move the iteration pointer to the next list item, and return it.
+ * @return Revision
*/
public function next() {
$this->res->next();
@@ -114,13 +120,13 @@ abstract class RevisionListBase extends ContextSource {
/**
* Do the DB query to iterate through the objects.
- * @param $db DatabaseBase object to use for the query
+ * @param DatabaseBase $db DatabaseBase object to use for the query
*/
abstract public function doQuery( $db );
/**
* Create an item object from a DB result row
- * @param $row stdclass
+ * @param object $row
*/
abstract public function newItem( $row );
}
@@ -129,15 +135,15 @@ abstract class RevisionListBase extends ContextSource {
* Abstract base class for revision items
*/
abstract class RevisionItemBase {
- /** The parent RevisionListBase */
- var $list;
+ /** @var RevisionListBase The parent */
+ protected $list;
- /** The DB result row */
- var $row;
+ /** The database result row */
+ protected $row;
/**
- * @param $list RevisionListBase
- * @param $row DB result row
+ * @param RevisionListBase $list
+ * @param object $row DB result row
*/
public function __construct( $list, $row ) {
$this->list = $list;
@@ -182,7 +188,7 @@ abstract class RevisionItemBase {
/**
* Get the ID, as it would appear in the ids URL parameter
- * @return
+ * @return int
*/
public function getId() {
$field = $this->getIdField();
@@ -191,7 +197,7 @@ abstract class RevisionItemBase {
/**
* Get the date, formatted in user's language
- * @return String
+ * @return string
*/
public function formatDate() {
return $this->list->getLanguage()->userDate( $this->getTimestamp(),
@@ -200,7 +206,7 @@ abstract class RevisionItemBase {
/**
* Get the time, formatted in user's language
- * @return String
+ * @return string
*/
public function formatTime() {
return $this->list->getLanguage()->userTime( $this->getTimestamp(),
@@ -209,7 +215,7 @@ abstract class RevisionItemBase {
/**
* Get the timestamp in MW 14-char form
- * @return Mixed
+ * @return mixed
*/
public function getTimestamp() {
$field = $this->getTimestampField();
@@ -257,7 +263,7 @@ class RevisionList extends RevisionListBase {
}
/**
- * @param $db DatabaseBase
+ * @param DatabaseBase $db
* @return mixed
*/
public function doQuery( $db ) {
@@ -286,7 +292,11 @@ class RevisionList extends RevisionListBase {
* Item class for a live revision table row
*/
class RevisionItem extends RevisionItemBase {
- var $revision, $context;
+ /** @var Revision */
+ protected $revision;
+
+ /** @var RequestContext */
+ protected $context;
public function __construct( $list, $row ) {
parent::__construct( $list, $row );
@@ -324,7 +334,7 @@ class RevisionItem extends RevisionItemBase {
/**
* Get the HTML link to the revision text.
- * Overridden by RevDel_ArchiveItem.
+ * Overridden by RevDelArchiveItem.
* @return string
*/
protected function getRevisionLink() {
@@ -345,7 +355,7 @@ class RevisionItem extends RevisionItemBase {
/**
* Get the HTML link to the diff.
- * Overridden by RevDel_ArchiveItem
+ * Overridden by RevDelArchiveItem
* @return string
*/
protected function getDiffLink() {
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index 3ca66443..bca2f67e 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -3,7 +3,7 @@
* HTML sanitizer for %MediaWiki.
*
* Copyright © 2002-2005 Brion Vibber <brion@pobox.com> et al
- * http://www.mediawiki.org/
+ * 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
@@ -328,6 +328,7 @@ class Sanitizer {
* Regular expression to match HTML/XML attribute pairs within a tag.
* Allows some... latitude.
* Used in Sanitizer::fixTagAttributes and Sanitizer::decodeTagAttributes
+ * @return string
*/
static function getAttribsRegex() {
if ( self::$attribsRegex === null ) {
@@ -355,12 +356,12 @@ class Sanitizer {
* Cleans up HTML, removes dangerous tags and attributes, and
* removes HTML comments
* @private
- * @param $text String
- * @param $processCallback Callback to do any variable or parameter
- * replacements in HTML attribute values
- * @param array $args for the processing callback
- * @param array $extratags for any extra tags to include
- * @param array $removetags for any tags (default or extra) to exclude
+ * @param string $text
+ * @param callable $processCallback Callback to do any variable or parameter
+ * replacements in HTML attribute values
+ * @param array|bool $args Arguments for the processing callback
+ * @param array $extratags For any extra tags to include
+ * @param array $removetags For any tags (default or extra) to exclude
* @return string
*/
static function removeHTMLtags( $text, $processCallback = null,
@@ -383,7 +384,7 @@ class Sanitizer {
'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
'strike', 'strong', 'tt', 'var', 'div', 'center',
'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
- 'ruby', 'rt', 'rb', 'rp', 'p', 'span', 'abbr', 'dfn',
+ 'ruby', 'rb', 'rp', 'rt', 'rtc', 'p', 'span', 'abbr', 'dfn',
'kbd', 'samp', 'data', 'time', 'mark'
);
$htmlsingle = array(
@@ -459,7 +460,10 @@ class Sanitizer {
$badtag = true;
} elseif ( $slash ) {
# Closing a tag... is it the one we just opened?
- $ot = @array_pop( $tagstack );
+ wfSuppressWarnings();
+ $ot = array_pop( $tagstack );
+ wfRestoreWarnings();
+
if ( $ot != $t ) {
if ( isset( $htmlsingleallowed[$ot] ) ) {
# Pop all elements with an optional close tag
@@ -489,7 +493,10 @@ class Sanitizer {
}
}
} else {
- @array_push( $tagstack, $ot );
+ wfSuppressWarnings();
+ array_push( $tagstack, $ot );
+ wfRestoreWarnings();
+
# <li> can be nested in <ul> or <ol>, skip those cases:
if ( !isset( $htmllist[$ot] ) || !isset( $listtags[$t] ) ) {
$badtag = true;
@@ -567,9 +574,16 @@ class Sanitizer {
} else {
# this might be possible using tidy itself
foreach ( $bits as $x ) {
- preg_match( '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/',
- $x, $regs );
- @list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
+ preg_match(
+ '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/',
+ $x,
+ $regs
+ );
+
+ wfSuppressWarnings();
+ list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
+ wfRestoreWarnings();
+
$badtag = false;
if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
if ( is_callable( $processCallback ) ) {
@@ -601,7 +615,7 @@ class Sanitizer {
* trailing spaces and one of the newlines.
*
* @private
- * @param $text String
+ * @param string $text
* @return string
*/
static function removeHTMLcomments( $text ) {
@@ -631,8 +645,7 @@ class Sanitizer {
# Remove the comment, leading and trailing
# spaces, and leave only one newline.
$text = substr_replace( $text, "\n", $spaceStart, $spaceLen + 1 );
- }
- else {
+ } else {
# Remove just the comment.
$text = substr_replace( $text, '', $start, $end - $start );
}
@@ -649,8 +662,8 @@ class Sanitizer {
* where we may want to allow a tag within content but ONLY when it has
* specific attributes set.
*
- * @param $params
- * @param $element
+ * @param string $params
+ * @param string $element
* @return bool
*/
static function validateTag( $params, $element ) {
@@ -682,9 +695,9 @@ class Sanitizer {
* - Unsafe style attributes are discarded
* - Invalid id attributes are re-encoded
*
- * @param $attribs Array
- * @param $element String
- * @return Array
+ * @param array $attribs
+ * @param string $element
+ * @return array
*
* @todo Check for legal values where the DTD limits things.
* @todo Check for unique id attribute :P
@@ -702,9 +715,9 @@ class Sanitizer {
* - Unsafe style attributes are discarded
* - Invalid id attributes are re-encoded
*
- * @param $attribs Array
- * @param array $whitelist list of allowed attribute names
- * @return Array
+ * @param array $attribs
+ * @param array $whitelist List of allowed attribute names
+ * @return array
*
* @todo Check for legal values where the DTD limits things.
* @todo Check for unique id attribute :P
@@ -801,8 +814,8 @@ class Sanitizer {
* will be combined (if they're both strings).
*
* @todo implement merging for other attributes such as style
- * @param $a Array
- * @param $b Array
+ * @param array $a
+ * @param array $b
* @return array
*/
static function mergeAttributes( $a, $b ) {
@@ -959,8 +972,8 @@ class Sanitizer {
}
/**
- * @param $matches array
- * @return String
+ * @param array $matches
+ * @return string
*/
static function cssDecodeCallback( $matches ) {
if ( $matches[1] !== '' ) {
@@ -998,9 +1011,9 @@ class Sanitizer {
* - Unsafe style attributes are discarded
* - Prepends space if there are attributes.
*
- * @param $text String
- * @param $element String
- * @return String
+ * @param string $text
+ * @param string $element
+ * @return string
*/
static function fixTagAttributes( $text, $element ) {
if ( trim( $text ) == '' ) {
@@ -1015,8 +1028,8 @@ class Sanitizer {
/**
* Encode an attribute value for HTML output.
- * @param $text String
- * @return HTML-encoded text fragment
+ * @param string $text
+ * @return string HTML-encoded text fragment
*/
static function encodeAttribute( $text ) {
$encValue = htmlspecialchars( $text, ENT_QUOTES );
@@ -1036,8 +1049,8 @@ class Sanitizer {
/**
* Encode an attribute value for HTML tags, with extra armoring
* against further wiki processing.
- * @param $text String
- * @return HTML-encoded text fragment
+ * @param string $text
+ * @return string HTML-encoded text fragment
*/
static function safeEncodeAttribute( $text ) {
$encValue = Sanitizer::encodeAttribute( $text );
@@ -1080,14 +1093,14 @@ class Sanitizer {
* (which don't work reliably in fragments cross-browser).
*
* @see http://www.w3.org/TR/html401/types.html#type-name Valid characters
- * in the id and
- * name attributes
- * @see http://www.w3.org/TR/html401/struct/links.html#h-12.2.3 Anchors with the id attribute
+ * in the id and name attributes
+ * @see http://www.w3.org/TR/html401/struct/links.html#h-12.2.3 Anchors with
+ * the id attribute
* @see http://www.whatwg.org/html/elements.html#the-id-attribute
* HTML5 definition of id attribute
*
- * @param string $id id to escape
- * @param $options Mixed: string or array of strings (default is array()):
+ * @param string $id Id to escape
+ * @param string|array $options String or array of strings (default is array()):
* 'noninitial': This is a non-initial fragment of an id, not a full id,
* so don't pay attention if the first character isn't valid at the
* beginning of an id. Only matters if $wgExperimentalHtmlIds is
@@ -1095,14 +1108,15 @@ class Sanitizer {
* 'legacy': Behave the way the old HTML 4-based ID escaping worked even
* if $wgExperimentalHtmlIds is used, so we can generate extra
* anchors and links won't break.
- * @return String
+ * @return string
*/
static function escapeId( $id, $options = array() ) {
global $wgExperimentalHtmlIds;
$options = (array)$options;
+ $id = Sanitizer::decodeCharReferences( $id );
+
if ( $wgExperimentalHtmlIds && !in_array( 'legacy', $options ) ) {
- $id = Sanitizer::decodeCharReferences( $id );
$id = preg_replace( '/[ \t\n\r\f_\'"&#%]+/', '_', $id );
$id = trim( $id, '_' );
if ( $id === '' ) {
@@ -1119,7 +1133,7 @@ class Sanitizer {
'%' => '.'
);
- $id = urlencode( Sanitizer::decodeCharReferences( strtr( $id, ' ', '_' ) ) );
+ $id = urlencode( strtr( $id, ' ', '_' ) );
$id = str_replace( array_keys( $replace ), array_values( $replace ), $id );
if ( !preg_match( '/^[a-zA-Z]/', $id )
@@ -1138,8 +1152,8 @@ class Sanitizer {
*
* @see http://www.w3.org/TR/CSS21/syndata.html Valid characters/format
*
- * @param $class String
- * @return String
+ * @param string $class
+ * @return string
*/
static function escapeClass( $class ) {
// Convert ugly stuff to underscores and kill underscores in ugly places
@@ -1153,8 +1167,8 @@ class Sanitizer {
* Given HTML input, escape with htmlspecialchars but un-escape entities.
* This allows (generally harmless) entities like &#160; to survive.
*
- * @param string $html to escape
- * @return String: escaped input
+ * @param string $html HTML to escape
+ * @return string Escaped input
*/
static function escapeHtmlAllowEntities( $html ) {
$html = Sanitizer::decodeCharReferences( $html );
@@ -1166,7 +1180,7 @@ class Sanitizer {
/**
* Regex replace callback for armoring links against further processing.
- * @param $matches Array
+ * @param array $matches
* @return string
*/
private static function armorLinksCallback( $matches ) {
@@ -1178,8 +1192,8 @@ class Sanitizer {
* a partial tag string. Attribute names are forces to lowercase,
* character references are decoded to UTF-8 text.
*
- * @param $text String
- * @return Array
+ * @param string $text
+ * @return array
*/
public static function decodeTagAttributes( $text ) {
if ( trim( $text ) == '' ) {
@@ -1214,8 +1228,8 @@ class Sanitizer {
* Build a partial tag string from an associative array of attribute
* names and values as returned by decodeTagAttributes.
*
- * @param $assoc_array Array
- * @return String
+ * @param array $assoc_array
+ * @return string
*/
public static function safeEncodeTagAttributes( $assoc_array ) {
$attribs = array();
@@ -1232,9 +1246,9 @@ class Sanitizer {
* Pick the appropriate attribute value from a match set from the
* attribs regex matches.
*
- * @param $set Array
- * @throws MWException
- * @return String
+ * @param array $set
+ * @throws MWException When tag conditions are not met.
+ * @return string
*/
private static function getTagAttributeCallback( $set ) {
if ( isset( $set[6] ) ) {
@@ -1266,8 +1280,9 @@ class Sanitizer {
* but note that we're not returning the value, but are returning
* XML source fragments that will be slapped into output.
*
- * @param $text String
- * @return String
+ * @param string $text
+ * @return string
+ * @todo Remove, unused?
*/
private static function normalizeAttributeValue( $text ) {
return str_replace( '"', '&quot;',
@@ -1276,8 +1291,8 @@ class Sanitizer {
}
/**
- * @param $text string
- * @return mixed
+ * @param string $text
+ * @return string
*/
private static function normalizeWhitespace( $text ) {
return preg_replace(
@@ -1291,8 +1306,8 @@ class Sanitizer {
* by Parser::stripSectionName(), for use in the id's that are used for
* section links.
*
- * @param $section String
- * @return String
+ * @param string $section
+ * @return string
*/
static function normalizeSectionNameWhitespace( $section ) {
return trim( preg_replace( '/[ _]+/', ' ', $section ) );
@@ -1309,8 +1324,8 @@ class Sanitizer {
* c. use lower cased "&#x", not "&#X"
* d. fix or reject non-valid attributes
*
- * @param $text String
- * @return String
+ * @param string $text
+ * @return string
* @private
*/
static function normalizeCharReferences( $text ) {
@@ -1319,9 +1334,10 @@ class Sanitizer {
array( 'Sanitizer', 'normalizeCharReferencesCallback' ),
$text );
}
+
/**
- * @param $matches String
- * @return String
+ * @param string $matches
+ * @return string
*/
static function normalizeCharReferencesCallback( $matches ) {
$ret = null;
@@ -1346,8 +1362,8 @@ class Sanitizer {
* the HTML equivalent. Otherwise, returns HTML-escaped text of
* pseudo-entity source (eg &amp;foo;)
*
- * @param $name String
- * @return String
+ * @param string $name
+ * @return string
*/
static function normalizeEntity( $name ) {
if ( isset( self::$htmlEntityAliases[$name] ) ) {
@@ -1363,7 +1379,7 @@ class Sanitizer {
}
/**
- * @param $codepoint
+ * @param int $codepoint
* @return null|string
*/
static function decCharReference( $codepoint ) {
@@ -1376,7 +1392,7 @@ class Sanitizer {
}
/**
- * @param $codepoint
+ * @param int $codepoint
* @return null|string
*/
static function hexCharReference( $codepoint ) {
@@ -1390,8 +1406,8 @@ class Sanitizer {
/**
* Returns true if a given Unicode codepoint is a valid character in XML.
- * @param $codepoint Integer
- * @return Boolean
+ * @param int $codepoint
+ * @return bool
*/
private static function validateCodepoint( $codepoint ) {
return $codepoint == 0x09
@@ -1406,8 +1422,8 @@ class Sanitizer {
* Decode any character references, numeric or named entities,
* in the text and return a UTF-8 string.
*
- * @param $text String
- * @return String
+ * @param string $text
+ * @return string
*/
public static function decodeCharReferences( $text ) {
return preg_replace_callback(
@@ -1423,8 +1439,8 @@ class Sanitizer {
* This is useful for page titles, not for text to be displayed,
* MediaWiki allows HTML entities to escape normalization as a feature.
*
- * @param string $text (already normalized, containing entities)
- * @return String (still normalized, without entities)
+ * @param string $text Already normalized, containing entities
+ * @return string Still normalized, without entities
*/
public static function decodeCharReferencesAndNormalize( $text ) {
global $wgContLang;
@@ -1441,8 +1457,8 @@ class Sanitizer {
}
/**
- * @param $matches String
- * @return String
+ * @param string $matches
+ * @return string
*/
static function decodeCharReferencesCallback( $matches ) {
if ( $matches[1] != '' ) {
@@ -1459,8 +1475,8 @@ class Sanitizer {
/**
* Return UTF-8 string for a codepoint if that is a valid
* character reference, otherwise U+FFFD REPLACEMENT CHARACTER.
- * @param $codepoint Integer
- * @return String
+ * @param int $codepoint
+ * @return string
* @private
*/
static function decodeChar( $codepoint ) {
@@ -1476,8 +1492,8 @@ class Sanitizer {
* return the UTF-8 encoding of that character. Otherwise, returns
* pseudo-entity source (eg "&foo;")
*
- * @param $name String
- * @return String
+ * @param string $name
+ * @return string
*/
static function decodeEntity( $name ) {
if ( isset( self::$htmlEntityAliases[$name] ) ) {
@@ -1493,8 +1509,8 @@ class Sanitizer {
/**
* Fetch the whitelist of acceptable attributes for a given element name.
*
- * @param $element String
- * @return Array
+ * @param string $element
+ * @return array
*/
static function attributeWhitelist( $element ) {
$list = Sanitizer::setupAttributeWhitelist();
@@ -1506,15 +1522,15 @@ class Sanitizer {
/**
* Foreach array key (an allowed HTML element), return an array
* of allowed attributes
- * @return Array
+ * @return array
*/
static function setupAttributeWhitelist() {
global $wgAllowRdfaAttributes, $wgAllowMicrodataAttributes;
-
static $whitelist, $staticInitialised;
+
$globalContext = implode( '-', compact( 'wgAllowRdfaAttributes', 'wgAllowMicrodataAttributes' ) );
- if ( isset( $whitelist ) && $staticInitialised == $globalContext ) {
+ if ( $whitelist !== null && $staticInitialised == $globalContext ) {
return $whitelist;
}
@@ -1548,7 +1564,7 @@ class Sanitizer {
}
$block = array_merge( $common, array( 'align' ) );
- $tablealign = array( 'align', 'char', 'charoff', 'valign' );
+ $tablealign = array( 'align', 'valign' );
$tablecell = array(
'abbr',
'axis',
@@ -1568,7 +1584,7 @@ class Sanitizer {
# 7.5.4
'div' => $block,
'center' => $common, # deprecated
- 'span' => $block, # ??
+ 'span' => $common,
# 7.5.5
'h1' => $block,
@@ -1582,7 +1598,7 @@ class Sanitizer {
# address
# 8.2.4
- # bdo
+ 'bdo' => $common,
# 9.2.1
'em' => $common,
@@ -1598,7 +1614,7 @@ class Sanitizer {
# 9.2.2
'blockquote' => array_merge( $common, array( 'cite' ) ),
- # q
+ 'q' => array_merge( $common, array( 'cite' ) ),
# 9.2.3
'sub' => $common,
@@ -1608,10 +1624,10 @@ class Sanitizer {
'p' => $block,
# 9.3.2
- 'br' => array( 'id', 'class', 'title', 'style', 'clear' ),
+ 'br' => array_merge( $common, array( 'clear' ) ),
# http://www.whatwg.org/html/text-level-semantics.html#the-wbr-element
- 'wbr' => array( 'id', 'class', 'title', 'style' ),
+ 'wbr' => $common,
# 9.3.4
'pre' => array_merge( $common, array( 'width' ) ),
@@ -1638,16 +1654,16 @@ class Sanitizer {
) ),
# 11.2.2
- 'caption' => array_merge( $common, array( 'align' ) ),
+ 'caption' => $block,
# 11.2.3
- 'thead' => array_merge( $common, $tablealign ),
- 'tfoot' => array_merge( $common, $tablealign ),
- 'tbody' => array_merge( $common, $tablealign ),
+ 'thead' => $common,
+ 'tfoot' => $common,
+ 'tbody' => $common,
# 11.2.4
- 'colgroup' => array_merge( $common, array( 'span', 'width' ), $tablealign ),
- 'col' => array_merge( $common, array( 'span', 'width' ), $tablealign ),
+ 'colgroup' => array_merge( $common, array( 'span' ) ),
+ 'col' => array_merge( $common, array( 'span' ) ),
# 11.2.5
'tr' => array_merge( $common, array( 'bgcolor' ), $tablealign ),
@@ -1682,16 +1698,16 @@ class Sanitizer {
# basefont
# 15.3
- 'hr' => array_merge( $common, array( 'noshade', 'size', 'width' ) ),
+ 'hr' => array_merge( $common, array( 'width' ) ),
# HTML Ruby annotation text module, simple ruby only.
# http://www.whatwg.org/html/text-level-semantics.html#the-ruby-element
'ruby' => $common,
# rbc
- # rtc
'rb' => $common,
- 'rt' => $common, #array_merge( $common, array( 'rbspan' ) ),
'rp' => $common,
+ 'rt' => $common, #array_merge( $common, array( 'rbspan' ) ),
+ 'rtc' => $common,
# MathML root element, where used for extensions
# 'title' may not be 100% valid here; it's XHTML
@@ -1729,7 +1745,7 @@ class Sanitizer {
* inclusion in HTML output as of 1.10!
*
* @param string $text HTML fragment
- * @return String
+ * @return string
*/
static function stripAllTags( $text ) {
# Actual <tags>
@@ -1749,7 +1765,7 @@ class Sanitizer {
*
* Use for passing XHTML fragments to PHP's XML parsing functions
*
- * @return String
+ * @return string
*/
static function hackDocType() {
$out = "<!DOCTYPE html [\n";
@@ -1761,7 +1777,7 @@ class Sanitizer {
}
/**
- * @param $url string
+ * @param string $url
* @return mixed|string
*/
static function cleanUrl( $url ) {
@@ -1808,7 +1824,7 @@ class Sanitizer {
}
/**
- * @param $matches array
+ * @param array $matches
* @return string
*/
static function cleanUrlCallback( $matches ) {
@@ -1841,7 +1857,7 @@ class Sanitizer {
* @since 1.18
*
* @param string $addr E-mail address
- * @return Bool
+ * @return bool
*/
public static function validateEmail( $addr ) {
$result = null;
@@ -1855,7 +1871,7 @@ class Sanitizer {
$rfc5322_atext = "a-z0-9!#$%&'*+\\-\/=?^_`{|}~";
$rfc1034_ldh_str = "a-z0-9\\-";
- $HTML5_email_regexp = "/
+ $html5_email_regexp = "/
^ # start of string
[$rfc5322_atext\\.]+ # user part which is liberal :p
@ # 'apostrophe'
@@ -1864,6 +1880,6 @@ class Sanitizer {
$ # End of string
/ix"; // case Insensitive, eXtended
- return (bool)preg_match( $HTML5_email_regexp, $addr );
+ return (bool)preg_match( $html5_email_regexp, $addr );
}
}
diff --git a/includes/Setup.php b/includes/Setup.php
index 2e083d83..7a89c7a3 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -1,6 +1,10 @@
<?php
/**
- * Include most things that's need to customize the site.
+ * Include most things that are needed to make %MediaWiki work.
+ *
+ * This file is included by WebStart.php and doMaintenance.php so that both
+ * web and maintenance scripts share a final set up phase to include necessary
+ * files and create global object variables.
*
* 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
@@ -28,14 +32,9 @@ if ( !defined( 'MEDIAWIKI' ) ) {
exit( 1 );
}
-# The main wiki script and things like database
-# conversion and maintenance scripts all share a
-# common setup of including lots of classes and
-# setting up a few globals.
-#
-
$fname = 'Setup.php';
wfProfileIn( $fname );
+wfProfileIn( $fname . '-defaults' );
// Check to see if we are at the file scope
if ( !isset( $wgVersion ) ) {
@@ -44,6 +43,7 @@ if ( !isset( $wgVersion ) ) {
}
// Set various default paths sensibly...
+
if ( $wgScript === false ) {
$wgScript = "$wgScriptPath/index$wgScriptExtension";
}
@@ -60,8 +60,8 @@ if ( $wgArticlePath === false ) {
}
if ( !empty( $wgActionPaths ) && !isset( $wgActionPaths['view'] ) ) {
- # 'view' is assumed the default action path everywhere in the code
- # but is rarely filled in $wgActionPaths
+ // 'view' is assumed the default action path everywhere in the code
+ // but is rarely filled in $wgActionPaths
$wgActionPaths['view'] = $wgArticlePath;
}
@@ -77,9 +77,12 @@ if ( $wgStyleDirectory === false ) {
if ( $wgExtensionAssetsPath === false ) {
$wgExtensionAssetsPath = "$wgScriptPath/extensions";
}
+if ( $wgResourceBasePath === null ) {
+ $wgResourceBasePath = $wgScriptPath;
+}
if ( $wgLogo === false ) {
- $wgLogo = "$wgStylePath/common/images/wiki.png";
+ $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
}
if ( $wgUploadPath === false ) {
@@ -98,15 +101,24 @@ if ( $wgDeletedDirectory === false ) {
$wgDeletedDirectory = "{$wgUploadDirectory}/deleted";
}
-if ( isset( $wgFileStore['deleted']['directory'] ) ) {
- $wgDeletedDirectory = $wgFileStore['deleted']['directory'];
+if ( $wgGitInfoCacheDirectory === false && $wgCacheDirectory !== false ) {
+ $wgGitInfoCacheDirectory = "{$wgCacheDirectory}/gitinfo";
}
-if ( isset( $wgFooterIcons['copyright'] ) &&
- isset( $wgFooterIcons['copyright']['copyright'] ) &&
- $wgFooterIcons['copyright']['copyright'] === array() )
-{
- if ( isset( $wgCopyrightIcon ) && $wgCopyrightIcon ) {
+// Fix path to icon images after they were moved in 1.24
+if ( $wgRightsIcon ) {
+ $wgRightsIcon = str_replace(
+ "{$wgStylePath}/common/images/",
+ "{$wgResourceBasePath}/resources/assets/licenses/",
+ $wgRightsIcon
+ );
+}
+
+if ( isset( $wgFooterIcons['copyright'] )
+ && isset( $wgFooterIcons['copyright']['copyright'] )
+ && $wgFooterIcons['copyright']['copyright'] === array()
+) {
+ if ( $wgCopyrightIcon ) {
$wgFooterIcons['copyright']['copyright'] = $wgCopyrightIcon;
} elseif ( $wgRightsIcon || $wgRightsText ) {
$wgFooterIcons['copyright']['copyright'] = array(
@@ -119,11 +131,12 @@ if ( isset( $wgFooterIcons['copyright'] ) &&
}
}
-if ( isset( $wgFooterIcons['poweredby'] ) &&
- isset( $wgFooterIcons['poweredby']['mediawiki'] ) &&
- $wgFooterIcons['poweredby']['mediawiki']['src'] === null )
-{
- $wgFooterIcons['poweredby']['mediawiki']['src'] = "$wgStylePath/common/images/poweredby_mediawiki_88x31.png";
+if ( isset( $wgFooterIcons['poweredby'] )
+ && isset( $wgFooterIcons['poweredby']['mediawiki'] )
+ && $wgFooterIcons['poweredby']['mediawiki']['src'] === null
+) {
+ $wgFooterIcons['poweredby']['mediawiki']['src'] =
+ "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png";
}
/**
@@ -160,11 +173,6 @@ $wgLockManagers[] = array(
* Initialise $wgLocalFileRepo from backwards-compatible settings
*/
if ( !$wgLocalFileRepo ) {
- if ( isset( $wgFileStore['deleted']['hash'] ) ) {
- $deletedHashLevel = $wgFileStore['deleted']['hash'];
- } else {
- $deletedHashLevel = $wgHashedUploadDirectory ? 3 : 0;
- }
$wgLocalFileRepo = array(
'class' => 'LocalRepo',
'name' => 'local',
@@ -176,7 +184,7 @@ if ( !$wgLocalFileRepo ) {
'thumbScriptUrl' => $wgThumbnailScriptPath,
'transformVia404' => !$wgGenerateThumbnailOnParse,
'deletedDir' => $wgDeletedDirectory,
- 'deletedHashLevels' => $deletedHashLevel
+ 'deletedHashLevels' => $wgHashedUploadDirectory ? 3 : 0
);
}
/**
@@ -247,17 +255,15 @@ foreach ( $wgForeignFileRepos as &$repo ) {
}
unset( $repo ); // no global pollution; destroy reference
-if ( is_null( $wgEnableAutoRotation ) ) {
- // Only enable auto-rotation when the bitmap handler can rotate
- $wgEnableAutoRotation = BitmapHandler::canRotate();
-}
-
if ( $wgRCFilterByAge ) {
- # # Trim down $wgRCLinkDays so that it only lists links which are valid
- # # as determined by $wgRCMaxAge.
- # # Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
+ // Trim down $wgRCLinkDays so that it only lists links which are valid
+ // as determined by $wgRCMaxAge.
+ // Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
sort( $wgRCLinkDays );
+
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
for ( $i = 0; $i < count( $wgRCLinkDays ); $i++ ) {
+ // @codingStandardsIgnoreEnd
if ( $wgRCLinkDays[$i] >= $wgRCMaxAge / ( 3600 * 24 ) ) {
$wgRCLinkDays = array_slice( $wgRCLinkDays, 0, $i + 1, false );
break;
@@ -269,7 +275,28 @@ if ( $wgSkipSkin ) {
$wgSkipSkins[] = $wgSkipSkin;
}
-# Set default shared prefix
+// Register skins
+// Use a closure to avoid leaking into global state
+call_user_func( function () use ( $wgValidSkinNames ) {
+ $factory = SkinFactory::getDefaultInstance();
+ foreach ( $wgValidSkinNames as $name => $skin ) {
+ $factory->register( $name, $skin, function () use ( $name, $skin ) {
+ $class = "Skin$skin";
+ return new $class( $name );
+ } );
+ }
+ // Register a hidden "fallback" skin
+ $factory->register( 'fallback', 'Fallback', function () {
+ return new SkinFallback;
+ } );
+} );
+$wgSkipSkins[] = 'fallback';
+
+if ( $wgLocalInterwiki ) {
+ array_unshift( $wgLocalInterwikis, $wgLocalInterwiki );
+}
+
+// Set default shared prefix
if ( $wgSharedPrefix === false ) {
$wgSharedPrefix = $wgDBprefix;
}
@@ -287,13 +314,38 @@ if ( !$wgCookiePrefix ) {
}
$wgCookiePrefix = strtr( $wgCookiePrefix, '=,; +."\'\\[', '__________' );
-$wgUseEnotif = $wgEnotifUserTalk || $wgEnotifWatchlist;
+if ( $wgEnableEmail ) {
+ $wgUseEnotif = $wgEnotifUserTalk || $wgEnotifWatchlist;
+} else {
+ // Disable all other email settings automatically if $wgEnableEmail
+ // is set to false. - bug 63678
+ $wgAllowHTMLEmail = false;
+ $wgEmailAuthentication = false; // do not require auth if you're not sending email anyway
+ $wgEnableUserEmail = false;
+ $wgEnotifFromEditor = false;
+ $wgEnotifImpersonal = false;
+ $wgEnotifMaxRecips = 0;
+ $wgEnotifMinorEdits = false;
+ $wgEnotifRevealEditorAddress = false;
+ $wgEnotifUseJobQ = false;
+ $wgEnotifUseRealName = false;
+ $wgEnotifUserTalk = false;
+ $wgEnotifWatchlist = false;
+ unset( $wgGroupPermissions['user']['sendemail'] );
+ $wgUseEnotif = false;
+ $wgUserEmailUseReplyTo = false;
+ $wgUsersNotifiedOnAllChanges = array();
+}
+
+// Doesn't make sense to have if disabled.
+if ( !$wgEnotifMinorEdits ) {
+ $wgHiddenPrefs[] = 'enotifminoredits';
+}
if ( $wgMetaNamespace === false ) {
$wgMetaNamespace = str_replace( ' ', '_', $wgSitename );
}
-
// Default value is either the suhosin limit or -1 for unlimited
if ( $wgResourceLoaderMaxQueryLength === false ) {
$maxValueLength = ini_get( 'suhosin.get.max_value_length' );
@@ -329,36 +381,26 @@ if ( is_array( $wgExtraNamespaces ) ) {
$wgCanonicalNamespaceNames = $wgCanonicalNamespaceNames + $wgExtraNamespaces;
}
-# These are now the same, always
-# To determine the user language, use $wgLang->getCode()
+// These are now the same, always
+// To determine the user language, use $wgLang->getCode()
$wgContLanguageCode = $wgLanguageCode;
-# Easy to forget to falsify $wgShowIPinHeader for static caches.
-# If file cache or squid cache is on, just disable this (DWIMD).
-# Do the same for $wgDebugToolbar.
+// Easy to forget to falsify $wgShowIPinHeader for static caches.
+// If file cache or squid cache is on, just disable this (DWIMD).
+// Do the same for $wgDebugToolbar.
if ( $wgUseFileCache || $wgUseSquid ) {
$wgShowIPinHeader = false;
$wgDebugToolbar = false;
}
-# Doesn't make sense to have if disabled.
-if ( !$wgEnotifMinorEdits ) {
- $wgHiddenPrefs[] = 'enotifminoredits';
-}
-
-# $wgDisabledActions is deprecated as of 1.18
-foreach ( $wgDisabledActions as $action ) {
- $wgActions[$action] = false;
-}
-
-# We always output HTML5 since 1.22, overriding these is no longer supported
-# we set them here for extensions that depend on its value.
+// We always output HTML5 since 1.22, overriding these is no longer supported
+// we set them here for extensions that depend on its value.
$wgHtml5 = true;
$wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml';
$wgJsMimeType = 'text/javascript';
if ( !$wgHtml5Version && $wgAllowRdfaAttributes ) {
- # see http://www.w3.org/TR/rdfa-in-html/#document-conformance
+ // see http://www.w3.org/TR/rdfa-in-html/#document-conformance
if ( $wgMimeType == 'application/xhtml+xml' ) {
$wgHtml5Version = 'XHTML+RDFa 1.0';
} else {
@@ -366,19 +408,17 @@ if ( !$wgHtml5Version && $wgAllowRdfaAttributes ) {
}
}
-# Blacklisted file extensions shouldn't appear on the "allowed" list
+// Blacklisted file extensions shouldn't appear on the "allowed" list
$wgFileExtensions = array_values( array_diff ( $wgFileExtensions, $wgFileBlacklist ) );
-if ( $wgArticleCountMethod === null ) {
- $wgArticleCountMethod = $wgUseCommaCount ? 'comma' : 'link';
-}
-
if ( $wgInvalidateCacheOnLocalSettingsChange ) {
+ // @codingStandardsIgnoreStart Generic.PHP.NoSilencedErrors.Discouraged - No GlobalFunction here yet.
$wgCacheEpoch = max( $wgCacheEpoch, gmdate( 'YmdHis', @filemtime( "$IP/LocalSettings.php" ) ) );
+ // @codingStandardsIgnoreEnd
}
if ( $wgNewUserLog ) {
- # Add a new log type
+ // Add a new log type
$wgLogTypes[] = 'newusers';
$wgLogNames['newusers'] = 'newuserlogpage';
$wgLogHeaders['newusers'] = 'newuserlogpagetext';
@@ -389,24 +429,34 @@ if ( $wgNewUserLog ) {
$wgLogActionsHandlers['newusers/autocreate'] = 'NewUsersLogFormatter';
}
+if ( $wgPageLanguageUseDB ) {
+ $wgLogTypes[] = 'pagelang';
+ $wgLogActionsHandlers['pagelang/pagelang'] = 'PageLangLogFormatter';
+}
+
if ( $wgCookieSecure === 'detect' ) {
$wgCookieSecure = ( WebRequest::detectProtocol() === 'https' );
}
-if ( $wgRC2UDPAddress ) {
- $wgRCFeeds['default'] = array(
- 'formatter' => 'IRCColourfulRCFeedFormatter',
- 'uri' => "udp://$wgRC2UDPAddress:$wgRC2UDPPort/$wgRC2UDPPrefix",
- 'add_interwiki_prefix' => &$wgRC2UDPInterwikiPrefix,
- 'omit_bots' => &$wgRC2UDPOmitBots,
- );
+// Back compatibility for $wgRateLimitLog deprecated with 1.23
+if ( $wgRateLimitLog && !array_key_exists( 'ratelimit', $wgDebugLogGroups ) ) {
+ $wgDebugLogGroups['ratelimit'] = $wgRateLimitLog;
}
+if ( $wgProfileOnly ) {
+ $wgDebugLogGroups['profileoutput'] = $wgDebugLogFile;
+ $wgDebugLogFile = '';
+}
+
+wfProfileOut( $fname . '-defaults' );
+
// Disable MWDebug for command line mode, this prevents MWDebug from eating up
// all the memory from logging SQL queries on maintenance scripts
global $wgCommandLineMode;
if ( $wgDebugToolbar && !$wgCommandLineMode ) {
+ wfProfileIn( $fname . '-debugtoolbar' );
MWDebug::init();
+ wfProfileOut( $fname . '-debugtoolbar' );
}
if ( !class_exists( 'AutoLoader' ) ) {
@@ -420,45 +470,52 @@ wfProfileOut( $fname . '-exception' );
wfProfileIn( $fname . '-includes' );
require_once "$IP/includes/normal/UtfNormalUtil.php";
require_once "$IP/includes/GlobalFunctions.php";
-require_once "$IP/includes/ProxyTools.php";
require_once "$IP/includes/normal/UtfNormalDefines.php";
wfProfileOut( $fname . '-includes' );
-if ( $wgSecureLogin && substr( $wgServer, 0, 2 ) !== '//' ) {
- $wgSecureLogin = false;
- wfWarn( 'Secure login was enabled on a server that only supports HTTP or HTTPS. Disabling secure login.' );
+wfProfileIn( $fname . '-defaults2' );
+
+if ( $wgCanonicalServer === false ) {
+ $wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
}
-# Now that GlobalFunctions is loaded, set defaults that depend
-# on it.
-if ( $wgTmpDirectory === false ) {
- $wgTmpDirectory = wfTempDir();
+// Set server name
+$serverParts = wfParseUrl( $wgCanonicalServer );
+if ( $wgServerName !== false ) {
+ wfWarn( '$wgServerName should be derived from $wgCanonicalServer, '
+ . 'not customized. Overwriting $wgServerName.' );
}
+$wgServerName = $serverParts['host'];
+unset( $serverParts );
-if ( $wgCanonicalServer === false ) {
- $wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
+// Set defaults for configuration variables
+// that are derived from the server name by default
+if ( $wgEmergencyContact === false ) {
+ $wgEmergencyContact = 'wikiadmin@' . $wgServerName;
}
-// $wgHTCPMulticastRouting got renamed to $wgHTCPRouting in MediaWiki 1.22
-// ensure back compatibility.
-if ( !$wgHTCPRouting && $wgHTCPMulticastRouting ) {
- $wgHTCPRouting = $wgHTCPMulticastRouting;
+if ( $wgPasswordSender === false ) {
+ $wgPasswordSender = 'apache@' . $wgServerName;
}
-// Initialize $wgHTCPRouting from backwards-compatible settings that
-// comes from pre 1.20 version.
-if ( !$wgHTCPRouting && $wgHTCPMulticastAddress ) {
- $wgHTCPRouting = array(
- '' => array(
- 'host' => $wgHTCPMulticastAddress,
- 'port' => $wgHTCPPort,
- )
- );
+if ( $wgSecureLogin && substr( $wgServer, 0, 2 ) !== '//' ) {
+ $wgSecureLogin = false;
+ wfWarn( 'Secure login was enabled on a server that only supports '
+ . 'HTTP or HTTPS. Disabling secure login.' );
+}
+
+// Now that GlobalFunctions is loaded, set defaults that depend
+// on it.
+if ( $wgTmpDirectory === false ) {
+ wfProfileIn( $fname . '-tempDir' );
+ $wgTmpDirectory = wfTempDir();
+ wfProfileOut( $fname . '-tempDir' );
}
+wfProfileOut( $fname . '-defaults2' );
wfProfileIn( $fname . '-misc1' );
-# Raise the memory limit if it's too low
+// Raise the memory limit if it's too low
wfMemoryLimit();
/**
@@ -477,13 +534,13 @@ if ( is_null( $wgLocalTZoffset ) ) {
$wgLocalTZoffset = date( 'Z' ) / 60;
}
-# Useful debug output
+// Useful debug output
if ( $wgCommandLineMode ) {
$wgRequest = new FauxRequest( array() );
wfDebug( "\n\nStart command line script $self\n" );
} else {
- # Can't stub this one, it sets up $_GET and $_REQUEST in its constructor
+ // Can't stub this one, it sets up $_GET and $_REQUEST in its constructor
$wgRequest = new WebRequest;
$debug = "\n\nStart request {$wgRequest->getMethod()} {$wgRequest->getRequestURL()}\n";
@@ -506,76 +563,86 @@ $messageMemc = wfGetMessageCacheStorage();
$parserMemc = wfGetParserCacheStorage();
$wgLangConvMemc = wfGetLangConverterCacheStorage();
-wfDebug( 'CACHES: ' . get_class( $wgMemc ) . '[main] ' .
- get_class( $messageMemc ) . '[message] ' .
- get_class( $parserMemc ) . "[parser]\n" );
+wfDebugLog( 'caches', 'main: ' . get_class( $wgMemc ) .
+ ', message: ' . get_class( $messageMemc ) .
+ ', parser: ' . get_class( $parserMemc ) );
wfProfileOut( $fname . '-memcached' );
-# # Most of the config is out, some might want to run hooks here.
+// Most of the config is out, some might want to run hooks here.
wfRunHooks( 'SetupAfterCache' );
wfProfileIn( $fname . '-session' );
-# If session.auto_start is there, we can't touch session name
-if ( !wfIniGetBool( 'session.auto_start' ) ) {
- session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' );
-}
-
if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
+ // If session.auto_start is there, we can't touch session name
+ if ( !wfIniGetBool( 'session.auto_start' ) ) {
+ session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' );
+ }
+
if ( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix . 'Token'] ) ) {
wfSetupSession();
- $wgSessionStarted = true;
- } else {
- $wgSessionStarted = false;
}
}
wfProfileOut( $fname . '-session' );
wfProfileIn( $fname . '-globals' );
+/**
+ * @var Language $wgContLang
+ */
$wgContLang = Language::factory( $wgLanguageCode );
$wgContLang->initEncoding();
$wgContLang->initContLang();
// Now that variant lists may be available...
$wgRequest->interpolateTitle();
-$wgUser = RequestContext::getMain()->getUser(); # BackCompat
/**
- * @var $wgLang Language
+ * @var User $wgUser
+ */
+$wgUser = RequestContext::getMain()->getUser(); // BackCompat
+
+/**
+ * @var Language $wgLang
*/
$wgLang = new StubUserLang;
/**
- * @var OutputPage
+ * @var OutputPage $wgOut
*/
-$wgOut = RequestContext::getMain()->getOutput(); # BackCompat
+$wgOut = RequestContext::getMain()->getOutput(); // BackCompat
/**
- * @var $wgParser Parser
+ * @var Parser $wgParser
*/
$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
if ( !is_object( $wgAuth ) ) {
- $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' );
+ $wgAuth = new AuthPlugin;
wfRunHooks( 'AuthPluginSetup', array( &$wgAuth ) );
}
-# Placeholders in case of DB error
+/**
+ * @var Title $wgTitle
+ */
$wgTitle = null;
+/**
+ * @deprecated since 1.24 Use DeferredUpdates::addUpdate instead
+ * @var array
+ */
$wgDeferredUpdateList = array();
wfProfileOut( $fname . '-globals' );
wfProfileIn( $fname . '-extensions' );
-# Extension setup functions for extensions other than skins
-# Entries should be added to this variable during the inclusion
-# of the extension file. This allows the extension to perform
-# any necessary initialisation in the fully initialised environment
+// Extension setup functions for extensions other than skins
+// Entries should be added to this variable during the inclusion
+// of the extension file. This allows the extension to perform
+// any necessary initialisation in the fully initialised environment
foreach ( $wgExtensionFunctions as $func ) {
- # Allow closures in PHP 5.3+
+ // Allow closures in PHP 5.3+
if ( is_object( $func ) && $func instanceof Closure ) {
$profName = $fname . '-extensions-closure';
} elseif ( is_array( $func ) ) {
diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php
index fa871fe0..8c1f26b8 100644
--- a/includes/SiteConfiguration.php
+++ b/includes/SiteConfiguration.php
@@ -108,7 +108,7 @@
* extract( $globals );
* @endcode
*
- * TODO: give examples for,
+ * @todo Give examples for,
* suffixes:
* $conf->suffixes = array( 'wiki' );
* localVHosts
@@ -174,9 +174,11 @@ class SiteConfiguration {
* @param string $suffix The suffix of the wiki in question.
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
- * @return Mixed the value of the setting requested.
+ * @return mixed The value of the setting requested.
*/
- public function get( $settingName, $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
+ public function get( $settingName, $wiki, $suffix = null, $params = array(),
+ $wikiTags = array()
+ ) {
$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
return $this->getSetting( $settingName, $wiki, $params );
}
@@ -186,10 +188,10 @@ class SiteConfiguration {
*
* @param string $settingName ID of the setting name to retrieve.
* @param string $wiki Wiki ID of the wiki in question.
- * @param array $params array of parameters.
- * @return Mixed the value of the setting requested.
+ * @param array $params Array of parameters.
+ * @return mixed The value of the setting requested.
*/
- protected function getSetting( $settingName, $wiki, /*array*/ $params ) {
+ protected function getSetting( $settingName, $wiki, array $params ) {
$retval = null;
if ( array_key_exists( $settingName, $this->settings ) ) {
$thisSetting =& $this->settings[$settingName];
@@ -205,14 +207,14 @@ class SiteConfiguration {
// Do tag settings
foreach ( $params['tags'] as $tag ) {
if ( array_key_exists( $tag, $thisSetting ) ) {
- if ( isset( $retval ) && is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
+ if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
$retval = self::arrayMerge( $retval, $thisSetting[$tag] );
} else {
$retval = $thisSetting[$tag];
}
break 2;
} elseif ( array_key_exists( "+$tag", $thisSetting ) && is_array( $thisSetting["+$tag"] ) ) {
- if ( !isset( $retval ) ) {
+ if ( $retval === null ) {
$retval = array();
}
$retval = self::arrayMerge( $retval, $thisSetting["+$tag"] );
@@ -222,14 +224,16 @@ class SiteConfiguration {
$suffix = $params['suffix'];
if ( !is_null( $suffix ) ) {
if ( array_key_exists( $suffix, $thisSetting ) ) {
- if ( isset( $retval ) && is_array( $retval ) && is_array( $thisSetting[$suffix] ) ) {
+ if ( is_array( $retval ) && is_array( $thisSetting[$suffix] ) ) {
$retval = self::arrayMerge( $retval, $thisSetting[$suffix] );
} else {
$retval = $thisSetting[$suffix];
}
break;
- } elseif ( array_key_exists( "+$suffix", $thisSetting ) && is_array( $thisSetting["+$suffix"] ) ) {
- if ( !isset( $retval ) ) {
+ } elseif ( array_key_exists( "+$suffix", $thisSetting )
+ && is_array( $thisSetting["+$suffix"] )
+ ) {
+ if ( $retval === null ) {
$retval = array();
}
$retval = self::arrayMerge( $retval, $thisSetting["+$suffix"] );
@@ -260,9 +264,9 @@ class SiteConfiguration {
* Type-safe string replace; won't do replacements on non-strings
* private?
*
- * @param $from
- * @param $to
- * @param $in
+ * @param string $from
+ * @param string $to
+ * @param string|array $in
* @return string
*/
function doReplace( $from, $to, $in ) {
@@ -284,7 +288,7 @@ class SiteConfiguration {
* @param string $suffix The suffix of the wiki in question.
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
- * @return Array Array of settings requested.
+ * @return array Array of settings requested.
*/
public function getAll( $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
@@ -338,7 +342,9 @@ class SiteConfiguration {
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
*/
- public function extractVar( $setting, $wiki, $suffix, &$var, $params = array(), $wikiTags = array() ) {
+ public function extractVar( $setting, $wiki, $suffix, &$var,
+ $params = array(), $wikiTags = array()
+ ) {
$value = $this->get( $setting, $wiki, $suffix, $params, $wikiTags );
if ( !is_null( $value ) ) {
$var = $value;
@@ -353,15 +359,17 @@ class SiteConfiguration {
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
*/
- public function extractGlobal( $setting, $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
+ public function extractGlobal( $setting, $wiki, $suffix = null,
+ $params = array(), $wikiTags = array()
+ ) {
$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
$this->extractGlobalSetting( $setting, $wiki, $params );
}
/**
- * @param $setting string
- * @param $wiki string
- * @param $params array
+ * @param string $setting
+ * @param string $wiki
+ * @param array $params
*/
public function extractGlobalSetting( $setting, $wiki, $params ) {
$value = $this->getSetting( $setting, $wiki, $params );
@@ -386,7 +394,9 @@ class SiteConfiguration {
* @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
* @param array $wikiTags The tags assigned to the wiki.
*/
- public function extractAllGlobals( $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
+ public function extractAllGlobals( $wiki, $suffix = null, $params = array(),
+ $wikiTags = array()
+ ) {
$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
foreach ( $this->settings as $varName => $setting ) {
$this->extractGlobalSetting( $varName, $wiki, $params );
@@ -398,7 +408,7 @@ class SiteConfiguration {
* See the documentation of self::$siteParamsCallback for more in-depth
* documentation about this function
*
- * @param $wiki String
+ * @param string $wiki
* @return array
*/
protected function getWikiParams( $wiki ) {
@@ -436,11 +446,11 @@ class SiteConfiguration {
* @param string $wiki Wiki ID of the wiki in question.
* @param string $suffix The suffix of the wiki in question.
* @param array $params List of parameters. $.'key' is replaced by $value in
- * all returned data.
+ * all returned data.
* @param array $wikiTags The tags assigned to the wiki.
* @return array
*/
- protected function mergeParams( $wiki, $suffix, /*array*/ $params, /*array*/ $wikiTags ) {
+ protected function mergeParams( $wiki, $suffix, array $params, array $wikiTags ) {
$ret = $this->getWikiParams( $wiki );
if ( is_null( $ret['suffix'] ) ) {
@@ -464,7 +474,7 @@ class SiteConfiguration {
/**
* Work out the site and language name from a database name
- * @param $db
+ * @param string $db
*
* @return array
*/
@@ -499,7 +509,7 @@ class SiteConfiguration {
*
* @param string $wiki
* @param array|string $settings A setting name or array of setting names
- * @return Array|mixed Array if $settings is an array, otherwise the value
+ * @return mixed|mixed[] Array if $settings is an array, otherwise the value
* @throws MWException
* @since 1.21
*/
@@ -522,7 +532,7 @@ class SiteConfiguration {
if ( isset( $this->cfgCache[$wiki] ) ) {
$res = array_intersect_key( $this->cfgCache[$wiki], array_flip( $settings ) );
if ( count( $res ) == count( $settings ) ) {
- return $res; // cache hit
+ return $multi ? $res : current( $res ); // cache hit
}
} elseif ( !in_array( $wiki, $this->wikis ) ) {
throw new MWException( "No such wiki '$wiki'." );
@@ -555,7 +565,7 @@ class SiteConfiguration {
/**
* Returns true if the given vhost is handled locally.
- * @param $vhost String
+ * @param string $vhost
* @return bool
*/
public function isLocalVHost( $vhost ) {
@@ -568,18 +578,20 @@ class SiteConfiguration {
* PHP's array_merge_recursive() merges ANY duplicate values into arrays,
* which is not fun
*
- * @param $array1 array
+ * @param array $array1
*
* @return array
*/
static function arrayMerge( $array1/* ... */ ) {
$out = $array1;
- for ( $i = 1; $i < func_num_args(); $i++ ) {
+ $argsCount = func_num_args();
+ for ( $i = 1; $i < $argsCount; $i++ ) {
foreach ( func_get_arg( $i ) 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 ) ) {
- // Values that evaluate to true given precedence, for the primary purpose of merging permissions arrays.
+ // Values that evaluate to true given precedence, for the
+ // primary purpose of merging permissions arrays.
$out[$key] = $value;
} elseif ( is_numeric( $key ) ) {
$out[] = $value;
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
index 355993c6..3dc17933 100644
--- a/includes/SiteStats.php
+++ b/includes/SiteStats.php
@@ -24,17 +24,27 @@
* Static accessor class for site_stats and related things
*/
class SiteStats {
- static $row, $loaded = false;
- static $jobs;
- static $pageCount = array();
- static $groupMemberCounts = array();
+ /** @var bool|ResultWrapper */
+ private static $row;
+
+ /** @var bool */
+ private static $loaded = false;
+
+ /** @var int */
+ private static $jobs;
+
+ /** @var int[] */
+ private static $pageCount = array();
+
+ /** @var int[] */
+ private static $groupMemberCounts = array();
static function recache() {
self::load( true );
}
/**
- * @param $recache bool
+ * @param bool $recache
*/
static function load( $recache = false ) {
if ( self::$loaded && !$recache ) {
@@ -55,7 +65,7 @@ class SiteStats {
}
/**
- * @return Bool|ResultWrapper
+ * @return bool|ResultWrapper
*/
static function loadAndLazyInit() {
wfDebug( __METHOD__ . ": reading site_stats from slave\n" );
@@ -86,8 +96,8 @@ class SiteStats {
}
/**
- * @param $db DatabaseBase
- * @return Bool|ResultWrapper
+ * @param DatabaseBase $db
+ * @return bool|ResultWrapper
*/
static function doLoad( $db ) {
return $db->selectRow( 'site_stats', array(
@@ -160,8 +170,8 @@ class SiteStats {
/**
* Find the number of users in a given user group.
- * @param string $group name of group
- * @return Integer
+ * @param string $group Name of group
+ * @return int
*/
static function numberingroup( $group ) {
if ( !isset( self::$groupMemberCounts[$group] ) ) {
@@ -190,7 +200,10 @@ class SiteStats {
if ( !isset( self::$jobs ) ) {
$dbr = wfGetDB( DB_SLAVE );
self::$jobs = array_sum( JobQueueGroup::singleton()->getQueueSizes() );
- /* Zero rows still do single row read for row that doesn't exist, but people are annoyed by that */
+ /**
+ * Zero rows still do single row read for row that doesn't exist,
+ * but people are annoyed by that
+ */
if ( self::$jobs == 1 ) {
self::$jobs = 0;
}
@@ -199,7 +212,7 @@ class SiteStats {
}
/**
- * @param $ns int
+ * @param int $ns
*
* @return int
*/
@@ -221,7 +234,9 @@ class SiteStats {
/**
* Is the provided row of site stats sane, or should it be regenerated?
*
- * @param $row
+ * Checks only fields which are filled by SiteStatsInit::refresh.
+ *
+ * @param bool|object $row
*
* @return bool
*/
@@ -229,7 +244,6 @@ class SiteStats {
if ( $row === false
|| $row->ss_total_pages < $row->ss_good_articles
|| $row->ss_total_edits < $row->ss_total_pages
- || $row->ss_users < $row->ss_active_users
) {
return false;
}
@@ -240,7 +254,6 @@ class SiteStats {
'ss_good_articles',
'ss_total_pages',
'ss_users',
- 'ss_active_users',
'ss_images',
) as $member ) {
if ( $row->$member > 2000000000 || $row->$member < 0 ) {
@@ -252,231 +265,6 @@ class SiteStats {
}
/**
- * Class for handling updates to the site_stats table
- */
-class SiteStatsUpdate implements DeferrableUpdate {
- protected $views = 0;
- protected $edits = 0;
- protected $pages = 0;
- protected $articles = 0;
- protected $users = 0;
- protected $images = 0;
-
- // @todo deprecate this constructor
- function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
- $this->views = $views;
- $this->edits = $edits;
- $this->articles = $good;
- $this->pages = $pages;
- $this->users = $users;
- }
-
- /**
- * @param $deltas Array
- * @return SiteStatsUpdate
- */
- public static function factory( array $deltas ) {
- $update = new self( 0, 0, 0 );
-
- $fields = array( 'views', 'edits', 'pages', 'articles', 'users', 'images' );
- foreach ( $fields as $field ) {
- if ( isset( $deltas[$field] ) && $deltas[$field] ) {
- $update->$field = $deltas[$field];
- }
- }
-
- return $update;
- }
-
- public function doUpdate() {
- global $wgSiteStatsAsyncFactor;
-
- $rate = $wgSiteStatsAsyncFactor; // convenience
- // If set to do so, only do actual DB updates 1 every $rate times.
- // The other times, just update "pending delta" values in memcached.
- if ( $rate && ( $rate < 0 || mt_rand( 0, $rate - 1 ) != 0 ) ) {
- $this->doUpdatePendingDeltas();
- } else {
- // Need a separate transaction because this a global lock
- wfGetDB( DB_MASTER )->onTransactionIdle( array( $this, 'tryDBUpdateInternal' ) );
- }
- }
-
- /**
- * Do not call this outside of SiteStatsUpdate
- *
- * @return void
- */
- public function tryDBUpdateInternal() {
- global $wgSiteStatsAsyncFactor;
-
- $dbw = wfGetDB( DB_MASTER );
- $lockKey = wfMemcKey( 'site_stats' ); // prepend wiki ID
- if ( $wgSiteStatsAsyncFactor ) {
- // Lock the table so we don't have double DB/memcached updates
- if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
- || !$dbw->lock( $lockKey, __METHOD__, 1 ) // 1 sec timeout
- ) {
- $this->doUpdatePendingDeltas();
- return;
- }
- $pd = $this->getPendingDeltas();
- // Piggy-back the async deltas onto those of this stats update....
- $this->views += ( $pd['ss_total_views']['+'] - $pd['ss_total_views']['-'] );
- $this->edits += ( $pd['ss_total_edits']['+'] - $pd['ss_total_edits']['-'] );
- $this->articles += ( $pd['ss_good_articles']['+'] - $pd['ss_good_articles']['-'] );
- $this->pages += ( $pd['ss_total_pages']['+'] - $pd['ss_total_pages']['-'] );
- $this->users += ( $pd['ss_users']['+'] - $pd['ss_users']['-'] );
- $this->images += ( $pd['ss_images']['+'] - $pd['ss_images']['-'] );
- }
-
- // Build up an SQL query of deltas and apply them...
- $updates = '';
- $this->appendUpdate( $updates, 'ss_total_views', $this->views );
- $this->appendUpdate( $updates, 'ss_total_edits', $this->edits );
- $this->appendUpdate( $updates, 'ss_good_articles', $this->articles );
- $this->appendUpdate( $updates, 'ss_total_pages', $this->pages );
- $this->appendUpdate( $updates, 'ss_users', $this->users );
- $this->appendUpdate( $updates, 'ss_images', $this->images );
- if ( $updates != '' ) {
- $dbw->update( 'site_stats', array( $updates ), array(), __METHOD__ );
- }
-
- if ( $wgSiteStatsAsyncFactor ) {
- // Decrement the async deltas now that we applied them
- $this->removePendingDeltas( $pd );
- // Commit the updates and unlock the table
- $dbw->unlock( $lockKey, __METHOD__ );
- }
- }
-
- /**
- * @param $dbw DatabaseBase
- * @return bool|mixed
- */
- public static function cacheUpdate( $dbw ) {
- global $wgActiveUserDays;
- $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow' ) );
- # Get non-bot users than did some recent action other than making accounts.
- # If account creation is included, the number gets inflated ~20+ fold on enwiki.
- $activeUsers = $dbr->selectField(
- 'recentchanges',
- 'COUNT( DISTINCT rc_user_text )',
- array(
- 'rc_user != 0',
- 'rc_bot' => 0,
- 'rc_log_type != ' . $dbr->addQuotes( 'newusers' ) . ' OR rc_log_type IS NULL',
- 'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays * 24 * 3600 ) ),
- ),
- __METHOD__
- );
- $dbw->update(
- 'site_stats',
- array( 'ss_active_users' => intval( $activeUsers ) ),
- array( 'ss_row_id' => 1 ),
- __METHOD__
- );
- return $activeUsers;
- }
-
- protected function doUpdatePendingDeltas() {
- $this->adjustPending( 'ss_total_views', $this->views );
- $this->adjustPending( 'ss_total_edits', $this->edits );
- $this->adjustPending( 'ss_good_articles', $this->articles );
- $this->adjustPending( 'ss_total_pages', $this->pages );
- $this->adjustPending( 'ss_users', $this->users );
- $this->adjustPending( 'ss_images', $this->images );
- }
-
- /**
- * @param $sql string
- * @param $field string
- * @param $delta integer
- */
- protected function appendUpdate( &$sql, $field, $delta ) {
- if ( $delta ) {
- if ( $sql ) {
- $sql .= ',';
- }
- if ( $delta < 0 ) {
- $sql .= "$field=$field-" . abs( $delta );
- } else {
- $sql .= "$field=$field+" . abs( $delta );
- }
- }
- }
-
- /**
- * @param $type string
- * @param string $sign ('+' or '-')
- * @return string
- */
- private function getTypeCacheKey( $type, $sign ) {
- return wfMemcKey( 'sitestatsupdate', 'pendingdelta', $type, $sign );
- }
-
- /**
- * Adjust the pending deltas for a stat type.
- * Each stat type has two pending counters, one for increments and decrements
- * @param $type string
- * @param $delta integer Delta (positive or negative)
- * @return void
- */
- protected function adjustPending( $type, $delta ) {
- global $wgMemc;
-
- if ( $delta < 0 ) { // decrement
- $key = $this->getTypeCacheKey( $type, '-' );
- } else { // increment
- $key = $this->getTypeCacheKey( $type, '+' );
- }
-
- $magnitude = abs( $delta );
- if ( !$wgMemc->incr( $key, $magnitude ) ) { // not there?
- if ( !$wgMemc->add( $key, $magnitude ) ) { // race?
- $wgMemc->incr( $key, $magnitude );
- }
- }
- }
-
- /**
- * Get pending delta counters for each stat type
- * @return Array Positive and negative deltas for each type
- * @return void
- */
- protected function getPendingDeltas() {
- global $wgMemc;
-
- $pending = array();
- foreach ( array( 'ss_total_views', 'ss_total_edits',
- 'ss_good_articles', 'ss_total_pages', 'ss_users', 'ss_images' ) as $type )
- {
- // Get pending increments and pending decrements
- $pending[$type]['+'] = (int)$wgMemc->get( $this->getTypeCacheKey( $type, '+' ) );
- $pending[$type]['-'] = (int)$wgMemc->get( $this->getTypeCacheKey( $type, '-' ) );
- }
-
- return $pending;
- }
-
- /**
- * Reduce pending delta counters after updates have been applied
- * @param array $pd Result of getPendingDeltas(), used for DB update
- * @return void
- */
- protected function removePendingDeltas( array $pd ) {
- global $wgMemc;
-
- foreach ( $pd as $type => $deltas ) {
- foreach ( $deltas as $sign => $magnitude ) {
- // Lower the pending counter now that we applied these changes
- $wgMemc->decr( $this->getTypeCacheKey( $type, $sign ), $magnitude );
- }
- }
- }
-}
-
-/**
* Class designed for counting of stats.
*/
class SiteStatsInit {
@@ -485,11 +273,12 @@ class SiteStatsInit {
private $db;
// Various stats
- private $mEdits, $mArticles, $mPages, $mUsers, $mViews, $mFiles = 0;
+ private $mEdits = null, $mArticles = null, $mPages = null;
+ private $mUsers = null, $mViews = null, $mFiles = null;
/**
* Constructor
- * @param $database Boolean or DatabaseBase:
+ * @param bool|DatabaseBase $database
* - Boolean: whether to use the master DB
* - DatabaseBase: database connection to use
*/
@@ -503,7 +292,7 @@ class SiteStatsInit {
/**
* Count the total number of edits
- * @return Integer
+ * @return int
*/
public function edits() {
$this->mEdits = $this->db->selectField( 'revision', 'COUNT(*)', '', __METHOD__ );
@@ -513,7 +302,7 @@ class SiteStatsInit {
/**
* Count pages in article space(s)
- * @return Integer
+ * @return int
*/
public function articles() {
global $wgArticleCountMethod;
@@ -543,7 +332,7 @@ class SiteStatsInit {
/**
* Count total pages
- * @return Integer
+ * @return int
*/
public function pages() {
$this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ );
@@ -552,7 +341,7 @@ class SiteStatsInit {
/**
* Count total users
- * @return Integer
+ * @return int
*/
public function users() {
$this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ );
@@ -561,7 +350,7 @@ class SiteStatsInit {
/**
* Count views
- * @return Integer
+ * @return int
*/
public function views() {
$this->mViews = $this->db->selectField( 'page', 'SUM(page_counter)', '', __METHOD__ );
@@ -570,7 +359,7 @@ class SiteStatsInit {
/**
* Count total files
- * @return Integer
+ * @return int
*/
public function files() {
$this->mFiles = $this->db->selectField( 'image', 'COUNT(*)', '', __METHOD__ );
@@ -581,11 +370,10 @@ class SiteStatsInit {
* Do all updates and commit them. More or less a replacement
* for the original initStats, but without output.
*
- * @param $database DatabaseBase|bool
+ * @param DatabaseBase|bool $database
* - Boolean: whether to use the master DB
* - DatabaseBase: database connection to use
- * @param array $options of options, may contain the following values
- * - update Boolean: whether to update the current stats (true) or write fresh (false) (default: false)
+ * @param array $options Array of options, may contain the following values
* - views Boolean: when true, do not update the number of page views (default: true)
* - activeUsers Boolean: whether to update the number of active users (default: false)
*/
@@ -606,12 +394,7 @@ class SiteStatsInit {
$counter->views();
}
- // Update/refresh
- if ( $options['update'] ) {
- $counter->update();
- } else {
- $counter->refresh();
- }
+ $counter->refresh();
// Count active users if need be
if ( $options['activeUsers'] ) {
@@ -620,39 +403,22 @@ class SiteStatsInit {
}
/**
- * Update the current row with the selected values
- */
- public function update() {
- list( $values, $conds ) = $this->getDbParams();
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'site_stats', $values, $conds, __METHOD__ );
- }
-
- /**
- * Refresh site_stats. Erase the current record and save all
- * the new values.
+ * Refresh site_stats. If you want ss_total_views to be updated, be sure to
+ * call views() first.
*/
public function refresh() {
- list( $values, $conds, $views ) = $this->getDbParams();
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'site_stats', $conds, __METHOD__ );
- $dbw->insert( 'site_stats', array_merge( $values, $conds, $views ), __METHOD__ );
- }
-
- /**
- * Return three arrays of params for the db queries
- * @return Array
- */
- private function getDbParams() {
$values = array(
- 'ss_total_edits' => $this->mEdits,
- 'ss_good_articles' => $this->mArticles,
- 'ss_total_pages' => $this->mPages,
- 'ss_users' => $this->mUsers,
- 'ss_images' => $this->mFiles
+ 'ss_row_id' => 1,
+ 'ss_total_edits' => ( $this->mEdits === null ? $this->edits() : $this->mEdits ),
+ 'ss_good_articles' => ( $this->mArticles === null ? $this->articles() : $this->mArticles ),
+ 'ss_total_pages' => ( $this->mPages === null ? $this->pages() : $this->mPages ),
+ 'ss_users' => ( $this->mUsers === null ? $this->users() : $this->mUsers ),
+ 'ss_images' => ( $this->mFiles === null ? $this->files() : $this->mFiles ),
+ ) + (
+ $this->mViews ? array( 'ss_total_views' => $this->mViews ) : array()
);
- $conds = array( 'ss_row_id' => 1 );
- $views = array( 'ss_total_views' => $this->mViews );
- return array( $values, $conds, $views );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->upsert( 'site_stats', $values, array( 'ss_row_id' ), $values, __METHOD__ );
}
}
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
deleted file mode 100644
index a6195fc4..00000000
--- a/includes/SpecialPage.php
+++ /dev/null
@@ -1,1446 +0,0 @@
-<?php
-/**
- * Parent class for all special pages.
- *
- * 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
- */
-
-/**
- * Parent special page class, also static functions for handling the special
- * page list.
- * @ingroup SpecialPage
- */
-class SpecialPage {
- // The canonical name of this special page
- // Also used for the default <h1> heading, @see getDescription()
- protected $mName;
-
- // The local name of this special page
- private $mLocalName;
-
- // Minimum user level required to access this page, or "" for anyone.
- // Also used to categorise the pages in Special:Specialpages
- private $mRestriction;
-
- // Listed in Special:Specialpages?
- private $mListed;
-
- // Function name called by the default execute()
- private $mFunction;
-
- // File which needs to be included before the function above can be called
- private $mFile;
-
- // Whether or not this special page is being included from an article
- protected $mIncluding;
-
- // Whether the special page can be included in an article
- protected $mIncludable;
-
- /**
- * Current request context
- * @var IContextSource
- */
- protected $mContext;
-
- /**
- * Initialise the special page list
- * This must be called before accessing SpecialPage::$mList
- * @deprecated since 1.18
- */
- static function initList() {
- wfDeprecated( __METHOD__, '1.18' );
- // Noop
- }
-
- /**
- * @deprecated since 1.18
- */
- static function initAliasList() {
- wfDeprecated( __METHOD__, '1.18' );
- // Noop
- }
-
- /**
- * Given a special page alias, return the special page name.
- * Returns false if there is no such alias.
- *
- * @param $alias String
- * @return String or false
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function resolveAlias( $alias ) {
- wfDeprecated( __METHOD__, '1.18' );
- list( $name, /*...*/ ) = SpecialPageFactory::resolveAlias( $alias );
- return $name;
- }
-
- /**
- * Given a special page name with a possible subpage, return an array
- * where the first element is the special page name and the second is the
- * subpage.
- *
- * @param $alias String
- * @return Array
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function resolveAliasWithSubpage( $alias ) {
- return SpecialPageFactory::resolveAlias( $alias );
- }
-
- /**
- * Add a page to a certain display group for Special:SpecialPages
- *
- * @param $page Mixed: SpecialPage or string
- * @param $group String
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function setGroup( $page, $group ) {
- wfDeprecated( __METHOD__, '1.18' );
- SpecialPageFactory::setGroup( $page, $group );
- }
-
- /**
- * Get the group that the special page belongs in on Special:SpecialPage
- *
- * @param $page SpecialPage
- * @return string
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function getGroup( &$page ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::getGroup( $page );
- }
-
- /**
- * Remove a special page from the list
- * Formerly used to disable expensive or dangerous special pages. The
- * preferred method is now to add a SpecialPage_initList hook.
- * @deprecated since 1.18
- *
- * @param string $name the page to remove
- */
- static function removePage( $name ) {
- wfDeprecated( __METHOD__, '1.18' );
- unset( SpecialPageFactory::getList()->$name );
- }
-
- /**
- * Check if a given name exist as a special page or as a special page alias
- *
- * @param string $name name of a special page
- * @return Boolean: true if a special page exists with this name
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function exists( $name ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::exists( $name );
- }
-
- /**
- * Find the object with a given name and return it (or NULL)
- *
- * @param $name String
- * @return SpecialPage object or null if the page doesn't exist
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function getPage( $name ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::getPage( $name );
- }
-
- /**
- * Get a special page with a given localised name, or NULL if there
- * is no such special page.
- *
- * @param $alias String
- * @return SpecialPage object or null if the page doesn't exist
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function getPageByAlias( $alias ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::getPage( $alias );
- }
-
- /**
- * Return categorised listable special pages which are available
- * for the current user, and everyone.
- *
- * @param $user User object to check permissions, $wgUser will be used
- * if not provided
- * @return array Associative array mapping page's name to its SpecialPage object
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function getUsablePages( User $user = null ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::getUsablePages( $user );
- }
-
- /**
- * Return categorised listable special pages for all users
- *
- * @return array Associative array mapping page's name to its SpecialPage object
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function getRegularPages() {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::getRegularPages();
- }
-
- /**
- * Return categorised listable special pages which are available
- * for the current user, but not for everyone
- *
- * @return array Associative array mapping page's name to its SpecialPage object
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function getRestrictedPages() {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::getRestrictedPages();
- }
-
- /**
- * Execute a special page path.
- * The path may contain parameters, e.g. Special:Name/Params
- * Extracts the special page name and call the execute method, passing the parameters
- *
- * Returns a title object if the page is redirected, false if there was no such special
- * page, and true if it was successful.
- *
- * @param $title Title object
- * @param $context IContextSource
- * @param $including Bool output is being captured for use in {{special:whatever}}
- * @return Bool
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- public static function executePath( &$title, IContextSource &$context, $including = false ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::executePath( $title, $context, $including );
- }
-
- /**
- * Get the local name for a specified canonical name
- *
- * @param $name String
- * @param $subpage Mixed: boolean false, or string
- *
- * @return String
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function getLocalNameFor( $name, $subpage = false ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::getLocalNameFor( $name, $subpage );
- }
-
- /**
- * Get a localised Title object for a specified special page name
- *
- * @param $name String
- * @param string|Bool $subpage subpage string, or false to not use a subpage
- * @param string $fragment the link fragment (after the "#")
- * @throws MWException
- * @return Title object
- */
- public static function getTitleFor( $name, $subpage = false, $fragment = '' ) {
- $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
- if ( $name ) {
- return Title::makeTitle( NS_SPECIAL, $name, $fragment );
- } else {
- throw new MWException( "Invalid special page name \"$name\"" );
- }
- }
-
- /**
- * Get a localised Title object for a page name with a possibly unvalidated subpage
- *
- * @param $name String
- * @param string|Bool $subpage subpage string, or false to not use a subpage
- * @return Title object or null if the page doesn't exist
- */
- public static function getSafeTitleFor( $name, $subpage = false ) {
- $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
- if ( $name ) {
- return Title::makeTitleSafe( NS_SPECIAL, $name );
- } else {
- return null;
- }
- }
-
- /**
- * Get a title for a given alias
- *
- * @param $alias String
- * @return Title or null if there is no such alias
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function getTitleForAlias( $alias ) {
- wfDeprecated( __METHOD__, '1.18' );
- return SpecialPageFactory::getTitleForAlias( $alias );
- }
-
- /**
- * Default constructor for special pages
- * Derivative classes should call this from their constructor
- * Note that if the user does not have the required level, an error message will
- * be displayed by the default execute() method, without the global function ever
- * being called.
- *
- * If you override execute(), you can recover the default behavior with userCanExecute()
- * and displayRestrictionError()
- *
- * @param string $name Name of the special page, as seen in links and URLs
- * @param string $restriction User right required, e.g. "block" or "delete"
- * @param bool $listed Whether the page is listed in Special:Specialpages
- * @param Callback|Bool $function Function called by execute(). By default
- * it is constructed from $name
- * @param string $file File which is included by execute(). It is also
- * constructed from $name by default
- * @param bool $includable Whether the page can be included in normal pages
- */
- public function __construct(
- $name = '', $restriction = '', $listed = true,
- $function = false, $file = 'default', $includable = false
- ) {
- $this->init( $name, $restriction, $listed, $function, $file, $includable );
- }
-
- /**
- * Do the real work for the constructor, mainly so __call() can intercept
- * calls to SpecialPage()
- * @param string $name Name of the special page, as seen in links and URLs
- * @param string $restriction User right required, e.g. "block" or "delete"
- * @param bool $listed Whether the page is listed in Special:Specialpages
- * @param Callback|Bool $function Function called by execute(). By default
- * it is constructed from $name
- * @param string $file File which is included by execute(). It is also
- * constructed from $name by default
- * @param bool $includable Whether the page can be included in normal pages
- */
- private function init( $name, $restriction, $listed, $function, $file, $includable ) {
- $this->mName = $name;
- $this->mRestriction = $restriction;
- $this->mListed = $listed;
- $this->mIncludable = $includable;
- if ( !$function ) {
- $this->mFunction = 'wfSpecial' . $name;
- } else {
- $this->mFunction = $function;
- }
- if ( $file === 'default' ) {
- $this->mFile = __DIR__ . "/specials/Special$name.php";
- } else {
- $this->mFile = $file;
- }
- }
-
- /**
- * Use PHP's magic __call handler to get calls to the old PHP4 constructor
- * because PHP E_STRICT yells at you for having __construct() and SpecialPage()
- *
- * @param string $fName Name of called method
- * @param array $a Arguments to the method
- * @throws MWException
- * @deprecated since 1.17, call parent::__construct()
- */
- public function __call( $fName, $a ) {
- // Deprecated messages now, remove in 1.19 or 1.20?
- wfDeprecated( __METHOD__, '1.17' );
-
- // Sometimes $fName is SpecialPage, sometimes it's specialpage. <3 PHP
- if ( strtolower( $fName ) == 'specialpage' ) {
- $name = isset( $a[0] ) ? $a[0] : '';
- $restriction = isset( $a[1] ) ? $a[1] : '';
- $listed = isset( $a[2] ) ? $a[2] : true;
- $function = isset( $a[3] ) ? $a[3] : false;
- $file = isset( $a[4] ) ? $a[4] : 'default';
- $includable = isset( $a[5] ) ? $a[5] : false;
- $this->init( $name, $restriction, $listed, $function, $file, $includable );
- } else {
- $className = get_class( $this );
- throw new MWException( "Call to undefined method $className::$fName" );
- }
- }
-
- /**
- * Get the name of this Special Page.
- * @return String
- */
- function getName() {
- return $this->mName;
- }
-
- /**
- * Get the permission that a user must have to execute this page
- * @return String
- */
- function getRestriction() {
- return $this->mRestriction;
- }
-
- /**
- * Get the file which will be included by SpecialPage::execute() if your extension is
- * still stuck in the past and hasn't overridden the execute() method. No modern code
- * should want or need to know this.
- * @return String
- * @deprecated since 1.18
- */
- function getFile() {
- wfDeprecated( __METHOD__, '1.18' );
- return $this->mFile;
- }
-
- // @todo FIXME: Decide which syntax to use for this, and stick to it
- /**
- * Whether this special page is listed in Special:SpecialPages
- * @since r3583 (v1.3)
- * @return Bool
- */
- function isListed() {
- return $this->mListed;
- }
- /**
- * Set whether this page is listed in Special:Specialpages, at run-time
- * @since r3583 (v1.3)
- * @param $listed Bool
- * @return Bool
- */
- function setListed( $listed ) {
- return wfSetVar( $this->mListed, $listed );
- }
- /**
- * Get or set whether this special page is listed in Special:SpecialPages
- * @since r11308 (v1.6)
- * @param $x Bool
- * @return Bool
- */
- function listed( $x = null ) {
- return wfSetVar( $this->mListed, $x );
- }
-
- /**
- * Whether it's allowed to transclude the special page via {{Special:Foo/params}}
- * @return Bool
- */
- public function isIncludable() {
- return $this->mIncludable;
- }
-
- /**
- * These mutators are very evil, as the relevant variables should not mutate. So
- * don't use them.
- * @param $x Mixed
- * @return Mixed
- * @deprecated since 1.18
- */
- function name( $x = null ) {
- wfDeprecated( __METHOD__, '1.18' );
- return wfSetVar( $this->mName, $x );
- }
-
- /**
- * These mutators are very evil, as the relevant variables should not mutate. So
- * don't use them.
- * @param $x Mixed
- * @return Mixed
- * @deprecated since 1.18
- */
- function restriction( $x = null ) {
- wfDeprecated( __METHOD__, '1.18' );
- return wfSetVar( $this->mRestriction, $x );
- }
-
- /**
- * These mutators are very evil, as the relevant variables should not mutate. So
- * don't use them.
- * @param $x Mixed
- * @return Mixed
- * @deprecated since 1.18
- */
- function func( $x = null ) {
- wfDeprecated( __METHOD__, '1.18' );
- return wfSetVar( $this->mFunction, $x );
- }
-
- /**
- * These mutators are very evil, as the relevant variables should not mutate. So
- * don't use them.
- * @param $x Mixed
- * @return Mixed
- * @deprecated since 1.18
- */
- function file( $x = null ) {
- wfDeprecated( __METHOD__, '1.18' );
- return wfSetVar( $this->mFile, $x );
- }
-
- /**
- * These mutators are very evil, as the relevant variables should not mutate. So
- * don't use them.
- * @param $x Mixed
- * @return Mixed
- * @deprecated since 1.18
- */
- function includable( $x = null ) {
- wfDeprecated( __METHOD__, '1.18' );
- return wfSetVar( $this->mIncludable, $x );
- }
-
- /**
- * Whether the special page is being evaluated via transclusion
- * @param $x Bool
- * @return Bool
- */
- function including( $x = null ) {
- return wfSetVar( $this->mIncluding, $x );
- }
-
- /**
- * Get the localised name of the special page
- */
- function getLocalName() {
- if ( !isset( $this->mLocalName ) ) {
- $this->mLocalName = SpecialPageFactory::getLocalNameFor( $this->mName );
- }
- return $this->mLocalName;
- }
-
- /**
- * Is this page expensive (for some definition of expensive)?
- * Expensive pages are disabled or cached in miser mode. Originally used
- * (and still overridden) by QueryPage and subclasses, moved here so that
- * Special:SpecialPages can safely call it for all special pages.
- *
- * @return Boolean
- */
- public function isExpensive() {
- return false;
- }
-
- /**
- * Is this page cached?
- * Expensive pages are cached or disabled in miser mode.
- * Used by QueryPage and subclasses, moved here so that
- * Special:SpecialPages can safely call it for all special pages.
- *
- * @return Boolean
- * @since 1.21
- */
- public function isCached() {
- return false;
- }
-
- /**
- * Can be overridden by subclasses with more complicated permissions
- * schemes.
- *
- * @return Boolean: should the page be displayed with the restricted-access
- * pages?
- */
- public function isRestricted() {
- // DWIM: If anons can do something, then it is not restricted
- return $this->mRestriction != '' && !User::groupHasPermission( '*', $this->mRestriction );
- }
-
- /**
- * Checks if the given user (identified by an object) can execute this
- * special page (as defined by $mRestriction). Can be overridden by sub-
- * classes with more complicated permissions schemes.
- *
- * @param $user User: the user to check
- * @return Boolean: does the user have permission to view the page?
- */
- public function userCanExecute( User $user ) {
- return $user->isAllowed( $this->mRestriction );
- }
-
- /**
- * Output an error message telling the user what access level they have to have
- */
- function displayRestrictionError() {
- throw new PermissionsError( $this->mRestriction );
- }
-
- /**
- * Checks if userCanExecute, and if not throws a PermissionsError
- *
- * @since 1.19
- */
- public function checkPermissions() {
- if ( !$this->userCanExecute( $this->getUser() ) ) {
- $this->displayRestrictionError();
- }
- }
-
- /**
- * If the wiki is currently in readonly mode, throws a ReadOnlyError
- *
- * @since 1.19
- * @throws ReadOnlyError
- */
- public function checkReadOnly() {
- if ( wfReadOnly() ) {
- throw new ReadOnlyError;
- }
- }
-
- /**
- * Sets headers - this should be called from the execute() method of all derived classes!
- */
- function setHeaders() {
- $out = $this->getOutput();
- $out->setArticleRelated( false );
- $out->setRobotPolicy( "noindex,nofollow" );
- $out->setPageTitle( $this->getDescription() );
- }
-
- /**
- * Entry point.
- *
- * @since 1.20
- *
- * @param $subPage string|null
- */
- final public function run( $subPage ) {
- /**
- * Gets called before @see SpecialPage::execute.
- *
- * @since 1.20
- *
- * @param $special SpecialPage
- * @param $subPage string|null
- */
- wfRunHooks( 'SpecialPageBeforeExecute', array( $this, $subPage ) );
-
- $this->beforeExecute( $subPage );
- $this->execute( $subPage );
- $this->afterExecute( $subPage );
-
- /**
- * Gets called after @see SpecialPage::execute.
- *
- * @since 1.20
- *
- * @param $special SpecialPage
- * @param $subPage string|null
- */
- wfRunHooks( 'SpecialPageAfterExecute', array( $this, $subPage ) );
- }
-
- /**
- * Gets called before @see SpecialPage::execute.
- *
- * @since 1.20
- *
- * @param $subPage string|null
- */
- protected function beforeExecute( $subPage ) {
- // No-op
- }
-
- /**
- * Gets called after @see SpecialPage::execute.
- *
- * @since 1.20
- *
- * @param $subPage string|null
- */
- protected function afterExecute( $subPage ) {
- // No-op
- }
-
- /**
- * Default execute method
- * Checks user permissions, calls the function given in mFunction
- *
- * This must be overridden by subclasses; it will be made abstract in a future version
- *
- * @param $subPage string|null
- */
- public function execute( $subPage ) {
- $this->setHeaders();
- $this->checkPermissions();
-
- $func = $this->mFunction;
- // only load file if the function does not exist
- if ( !is_callable( $func ) && $this->mFile ) {
- require_once $this->mFile;
- }
- $this->outputHeader();
- call_user_func( $func, $subPage, $this );
- }
-
- /**
- * Outputs a summary message on top of special pages
- * Per default the message key is the canonical name of the special page
- * May be overridden, i.e. by extensions to stick with the naming conventions
- * for message keys: 'extensionname-xxx'
- *
- * @param string $summaryMessageKey message key of the summary
- */
- function outputHeader( $summaryMessageKey = '' ) {
- global $wgContLang;
-
- if ( $summaryMessageKey == '' ) {
- $msg = $wgContLang->lc( $this->getName() ) . '-summary';
- } else {
- $msg = $summaryMessageKey;
- }
- if ( !$this->msg( $msg )->isDisabled() && !$this->including() ) {
- $this->getOutput()->wrapWikiMsg(
- "<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
- }
-
- }
-
- /**
- * Returns the name that goes in the \<h1\> in the special page itself, and
- * also the name that will be listed in Special:Specialpages
- *
- * Derived classes can override this, but usually it is easier to keep the
- * default behavior. Messages can be added at run-time, see
- * MessageCache.php.
- *
- * @return String
- */
- function getDescription() {
- return $this->msg( strtolower( $this->mName ) )->text();
- }
-
- /**
- * Get a self-referential title object
- *
- * @param $subpage String|Bool
- * @return Title object
- */
- function getTitle( $subpage = false ) {
- return self::getTitleFor( $this->mName, $subpage );
- }
-
- /**
- * Sets the context this SpecialPage is executed in
- *
- * @param $context IContextSource
- * @since 1.18
- */
- public function setContext( $context ) {
- $this->mContext = $context;
- }
-
- /**
- * Gets the context this SpecialPage is executed in
- *
- * @return IContextSource|RequestContext
- * @since 1.18
- */
- public function getContext() {
- if ( $this->mContext instanceof IContextSource ) {
- return $this->mContext;
- } else {
- wfDebug( __METHOD__ . " called and \$mContext is null. " .
- "Return RequestContext::getMain(); for sanity\n" );
- return RequestContext::getMain();
- }
- }
-
- /**
- * Get the WebRequest being used for this instance
- *
- * @return WebRequest
- * @since 1.18
- */
- public function getRequest() {
- return $this->getContext()->getRequest();
- }
-
- /**
- * Get the OutputPage being used for this instance
- *
- * @return OutputPage
- * @since 1.18
- */
- public function getOutput() {
- return $this->getContext()->getOutput();
- }
-
- /**
- * Shortcut to get the User executing this instance
- *
- * @return User
- * @since 1.18
- */
- public function getUser() {
- return $this->getContext()->getUser();
- }
-
- /**
- * Shortcut to get the skin being used for this instance
- *
- * @return Skin
- * @since 1.18
- */
- public function getSkin() {
- return $this->getContext()->getSkin();
- }
-
- /**
- * Shortcut to get user's language
- *
- * @deprecated since 1.19 Use getLanguage instead
- * @return Language
- * @since 1.18
- */
- public function getLang() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->getLanguage();
- }
-
- /**
- * Shortcut to get user's language
- *
- * @return Language
- * @since 1.19
- */
- public function getLanguage() {
- return $this->getContext()->getLanguage();
- }
-
- /**
- * Return the full title, including $par
- *
- * @return Title
- * @since 1.18
- */
- public function getFullTitle() {
- return $this->getContext()->getTitle();
- }
-
- /**
- * Wrapper around wfMessage that sets the current context.
- *
- * @return Message
- * @see wfMessage
- */
- public function msg( /* $args */ ) {
- // Note: can't use func_get_args() directly as second or later item in
- // a parameter list until PHP 5.3 or you get a fatal error.
- // Works fine as the first parameter, which appears elsewhere in the
- // code base. Sighhhh.
- $args = func_get_args();
- $message = call_user_func_array( array( $this->getContext(), 'msg' ), $args );
- // RequestContext passes context to wfMessage, and the language is set from
- // the context, but setting the language for Message class removes the
- // interface message status, which breaks for example usernameless gender
- // invocations. Restore the flag when not including special page in content.
- if ( $this->including() ) {
- $message->setInterfaceMessageFlag( false );
- }
- return $message;
- }
-
- /**
- * Adds RSS/atom links
- *
- * @param $params array
- */
- protected function addFeedLinks( $params ) {
- global $wgFeedClasses;
-
- $feedTemplate = wfScript( 'api' );
-
- foreach ( $wgFeedClasses as $format => $class ) {
- $theseParams = $params + array( 'feedformat' => $format );
- $url = wfAppendQuery( $feedTemplate, $theseParams );
- $this->getOutput()->addFeedLink( $format, $url );
- }
- }
-
- /**
- * Get the group that the special page belongs in on Special:SpecialPage
- * Use this method, instead of getGroupName to allow customization
- * of the group name from the wiki side
- *
- * @return string Group of this special page
- * @since 1.21
- */
- public function getFinalGroupName() {
- global $wgSpecialPageGroups;
- $name = $this->getName();
- $group = '-';
-
- // Allow overbidding the group from the wiki side
- $msg = $this->msg( 'specialpages-specialpagegroup-' . strtolower( $name ) )->inContentLanguage();
- if ( !$msg->isBlank() ) {
- $group = $msg->text();
- } else {
- // Than use the group from this object
- $group = $this->getGroupName();
-
- // Group '-' is used as default to have the chance to determine,
- // if the special pages overrides this method,
- // if not overridden, $wgSpecialPageGroups is checked for b/c
- if ( $group === '-' && isset( $wgSpecialPageGroups[$name] ) ) {
- $group = $wgSpecialPageGroups[$name];
- }
- }
-
- // never give '-' back, change to 'other'
- if ( $group === '-' ) {
- $group = 'other';
- }
-
- return $group;
- }
-
- /**
- * Under which header this special page is listed in Special:SpecialPages
- * See messages 'specialpages-group-*' for valid names
- * This method defaults to group 'other'
- *
- * @return string
- * @since 1.21
- */
- protected function getGroupName() {
- // '-' used here to determine, if this group is overridden or has a hardcoded 'other'
- // Needed for b/c in getFinalGroupName
- return '-';
- }
-}
-
-/**
- * Special page which uses an HTMLForm to handle processing. This is mostly a
- * clone of FormAction. More special pages should be built this way; maybe this could be
- * a new structure for SpecialPages
- */
-abstract class FormSpecialPage extends SpecialPage {
- /**
- * The sub-page of the special page.
- * @var string
- */
- protected $par = null;
-
- /**
- * Get an HTMLForm descriptor array
- * @return Array
- */
- abstract protected function getFormFields();
-
- /**
- * Add pre-text to the form
- * @return String HTML which will be sent to $form->addPreText()
- */
- protected function preText() {
- return '';
- }
-
- /**
- * Add post-text to the form
- * @return String HTML which will be sent to $form->addPostText()
- */
- protected function postText() {
- return '';
- }
-
- /**
- * Play with the HTMLForm if you need to more substantially
- * @param $form HTMLForm
- */
- protected function alterForm( HTMLForm $form ) {
- }
-
- /**
- * Get message prefix for HTMLForm
- *
- * @since 1.21
- * @return string
- */
- protected function getMessagePrefix() {
- return strtolower( $this->getName() );
- }
-
- /**
- * Get the HTMLForm to control behavior
- * @return HTMLForm|null
- */
- protected function getForm() {
- $this->fields = $this->getFormFields();
-
- $form = new HTMLForm( $this->fields, $this->getContext(), $this->getMessagePrefix() );
- $form->setSubmitCallback( array( $this, 'onSubmit' ) );
- // If the form is a compact vertical form, then don't output this ugly
- // fieldset surrounding it.
- // XXX Special pages can setDisplayFormat to 'vform' in alterForm(), but that
- // is called after this.
- if ( !$form->isVForm() ) {
- $form->setWrapperLegendMsg( $this->getMessagePrefix() . '-legend' );
- }
-
- $headerMsg = $this->msg( $this->getMessagePrefix() . '-text' );
- if ( !$headerMsg->isDisabled() ) {
- $form->addHeaderText( $headerMsg->parseAsBlock() );
- }
-
- // Retain query parameters (uselang etc)
- $params = array_diff_key(
- $this->getRequest()->getQueryValues(), array( 'title' => null ) );
- $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
-
- $form->addPreText( $this->preText() );
- $form->addPostText( $this->postText() );
- $this->alterForm( $form );
-
- // Give hooks a chance to alter the form, adding extra fields or text etc
- wfRunHooks( "Special{$this->getName()}BeforeFormDisplay", array( &$form ) );
-
- return $form;
- }
-
- /**
- * Process the form on POST submission.
- * @param $data Array
- * @return Bool|Array true for success, false for didn't-try, array of errors on failure
- */
- abstract public function onSubmit( array $data );
-
- /**
- * Do something exciting on successful processing of the form, most likely to show a
- * confirmation message
- * @since 1.22 Default is to do nothing
- */
- public function onSuccess() {
- }
-
- /**
- * Basic SpecialPage workflow: get a form, send it to the user; get some data back,
- *
- * @param string $par Subpage string if one was specified
- */
- public function execute( $par ) {
- $this->setParameter( $par );
- $this->setHeaders();
-
- // This will throw exceptions if there's a problem
- $this->checkExecutePermissions( $this->getUser() );
-
- $form = $this->getForm();
- if ( $form->show() ) {
- $this->onSuccess();
- }
- }
-
- /**
- * Maybe do something interesting with the subpage parameter
- * @param string $par
- */
- protected function setParameter( $par ) {
- $this->par = $par;
- }
-
- /**
- * Called from execute() to check if the given user can perform this action.
- * Failures here must throw subclasses of ErrorPageError.
- * @param $user User
- * @throws UserBlockedError
- * @return Bool true
- */
- protected function checkExecutePermissions( User $user ) {
- $this->checkPermissions();
-
- if ( $this->requiresUnblock() && $user->isBlocked() ) {
- $block = $user->getBlock();
- throw new UserBlockedError( $block );
- }
-
- if ( $this->requiresWrite() ) {
- $this->checkReadOnly();
- }
-
- return true;
- }
-
- /**
- * Whether this action requires the wiki not to be locked
- * @return Bool
- */
- public function requiresWrite() {
- return true;
- }
-
- /**
- * Whether this action cannot be executed by a blocked user
- * @return Bool
- */
- public function requiresUnblock() {
- return true;
- }
-}
-
-/**
- * Shortcut to construct a special page which is unlisted by default
- * @ingroup SpecialPage
- */
-class UnlistedSpecialPage extends SpecialPage {
- function __construct( $name, $restriction = '', $function = false, $file = 'default' ) {
- parent::__construct( $name, $restriction, false, $function, $file );
- }
-
- public function isListed() {
- return false;
- }
-}
-
-/**
- * Shortcut to construct an includable special page
- * @ingroup SpecialPage
- */
-class IncludableSpecialPage extends SpecialPage {
- function __construct(
- $name, $restriction = '', $listed = true, $function = false, $file = 'default'
- ) {
- parent::__construct( $name, $restriction, $listed, $function, $file, true );
- }
-
- public function isIncludable() {
- return true;
- }
-}
-
-/**
- * Shortcut to construct a special page alias.
- * @ingroup SpecialPage
- */
-abstract class RedirectSpecialPage extends UnlistedSpecialPage {
-
- // Query parameters that can be passed through redirects
- protected $mAllowedRedirectParams = array();
-
- // Query parameters added by redirects
- protected $mAddedRedirectParams = array();
-
- public function execute( $par ) {
- $redirect = $this->getRedirect( $par );
- $query = $this->getRedirectQuery();
- // Redirect to a page title with possible query parameters
- if ( $redirect instanceof Title ) {
- $url = $redirect->getFullURL( $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 {
- $class = get_class( $this );
- throw new MWException( "RedirectSpecialPage $class doesn't redirect!" );
- }
- }
-
- /**
- * If the special page is a redirect, then get the Title object it redirects to.
- * False otherwise.
- *
- * @param string $par Subpage string
- * @return Title|bool
- */
- abstract public function getRedirect( $par );
-
- /**
- * Return part of the request string for a special redirect page
- * This allows passing, e.g. action=history to Special:Mypage, etc.
- *
- * @return String
- */
- public function getRedirectQuery() {
- $params = array();
-
- foreach ( $this->mAllowedRedirectParams as $arg ) {
- if ( $this->getRequest()->getVal( $arg, null ) !== null ) {
- $params[$arg] = $this->getRequest()->getVal( $arg );
- }
- }
-
- foreach ( $this->mAddedRedirectParams as $arg => $val ) {
- $params[$arg] = $val;
- }
-
- return count( $params )
- ? $params
- : false;
- }
-}
-
-abstract class SpecialRedirectToSpecial extends RedirectSpecialPage {
- // @todo FIXME: Visibility must be declared
- var $redirName, $redirSubpage;
-
- function __construct(
- $name, $redirName, $redirSubpage = false,
- $allowedRedirectParams = array(), $addedRedirectParams = array()
- ) {
- parent::__construct( $name );
- $this->redirName = $redirName;
- $this->redirSubpage = $redirSubpage;
- $this->mAllowedRedirectParams = $allowedRedirectParams;
- $this->mAddedRedirectParams = $addedRedirectParams;
- }
-
- public function getRedirect( $subpage ) {
- if ( $this->redirSubpage === false ) {
- return SpecialPage::getTitleFor( $this->redirName, $subpage );
- } else {
- return SpecialPage::getTitleFor( $this->redirName, $this->redirSubpage );
- }
- }
-}
-
-/**
- * ListAdmins --> ListUsers/sysop
- */
-class SpecialListAdmins extends SpecialRedirectToSpecial {
- function __construct() {
- parent::__construct( 'Listadmins', 'Listusers', 'sysop' );
- }
-}
-
-/**
- * ListBots --> ListUsers/bot
- */
-class SpecialListBots extends SpecialRedirectToSpecial {
- function __construct() {
- parent::__construct( 'Listbots', 'Listusers', 'bot' );
- }
-}
-
-/**
- * CreateAccount --> UserLogin/signup
- * @todo FIXME: This (and the rest of the login frontend) needs to die a horrible painful death
- */
-class SpecialCreateAccount extends SpecialRedirectToSpecial {
- function __construct() {
- parent::__construct( 'CreateAccount', 'Userlogin', 'signup', array( 'returnto', 'returntoquery', 'uselang' ) );
- }
-
- // No reason to hide this link on Special:Specialpages
- public function isListed() {
- return true;
- }
-
- protected function getGroupName() {
- return 'login';
- }
-}
-/**
- * SpecialMypage, SpecialMytalk and SpecialMycontributions special pages
- * are used to get user independent links pointing to the user page, talk
- * page and list of contributions.
- * This can let us cache a single copy of any generated content for all
- * users.
- */
-
-/**
- * Superclass for any RedirectSpecialPage which redirects the user
- * to a particular article (as opposed to user contributions, logs, etc.).
- *
- * For security reasons these special pages are restricted to pass on
- * the following subset of GET parameters to the target page while
- * removing all others:
- *
- * - useskin, uselang, printable: to alter the appearance of the resulting page
- *
- * - redirect: allows viewing one's user page or talk page even if it is a
- * redirect.
- *
- * - rdfrom: allows redirecting to one's user page or talk page from an
- * external wiki with the "Redirect from..." notice.
- *
- * - limit, offset: Useful for linking to history of one's own user page or
- * user talk page. For example, this would be a link to "the last edit to your
- * user talk page in the year 2010":
- * http://en.wikipedia.org/wiki/Special:MyPage?offset=20110000000000&limit=1&action=history
- *
- * - feed: would allow linking to the current user's RSS feed for their user
- * talk page:
- * http://en.wikipedia.org/w/index.php?title=Special:MyTalk&action=history&feed=rss
- *
- * - preloadtitle: Can be used to provide a default section title for a
- * preloaded new comment on one's own talk page.
- *
- * - summary : Can be used to provide a default edit summary for a preloaded
- * edit to one's own user page or talk page.
- *
- * - preview: Allows showing/hiding preview on first edit regardless of user
- * preference, useful for preloaded edits where you know preview wouldn't be
- * useful.
- *
- * - internaledit, externaledit, mode: Allows forcing the use of the
- * internal/external editor, e.g. to force the internal editor for
- * short/simple preloaded edits.
- *
- * - redlink: Affects the message the user sees if their talk page/user talk
- * page does not currently exist. Avoids confusion for newbies with no user
- * pages over why they got a "permission error" following this link:
- * http://en.wikipedia.org/w/index.php?title=Special:MyPage&redlink=1
- *
- * - debug: determines whether the debug parameter is passed to load.php,
- * which disables reformatting and allows scripts to be debugged. Useful
- * when debugging scripts that manipulate one's own user page or talk page.
- *
- * @par Hook extension:
- * Extensions can add to the redirect parameters list by using the hook
- * RedirectSpecialArticleRedirectParams
- *
- * This hook allows extensions which add GET parameters like FlaggedRevs to
- * retain those parameters when redirecting using special pages.
- *
- * @par Hook extension example:
- * @code
- * $wgHooks['RedirectSpecialArticleRedirectParams'][] =
- * 'MyExtensionHooks::onRedirectSpecialArticleRedirectParams';
- * public static function onRedirectSpecialArticleRedirectParams( &$redirectParams ) {
- * $redirectParams[] = 'stable';
- * return true;
- * }
- * @endcode
- * @ingroup SpecialPage
- */
-abstract class RedirectSpecialArticle extends RedirectSpecialPage {
- function __construct( $name ) {
- parent::__construct( $name );
- $redirectParams = array(
- 'action',
- 'redirect', 'rdfrom',
- # Options for preloaded edits
- 'preload', 'editintro', 'preloadtitle', 'summary', 'nosummary',
- # Options for overriding user settings
- 'preview', 'internaledit', 'externaledit', 'mode', 'minor', 'watchthis',
- # Options for history/diffs
- 'section', 'oldid', 'diff', 'dir',
- 'limit', 'offset', 'feed',
- # Misc options
- 'redlink', 'debug',
- # Options for action=raw; missing ctype can break JS or CSS in some browsers
- 'ctype', 'maxage', 'smaxage',
- );
-
- wfRunHooks( "RedirectSpecialArticleRedirectParams", array( &$redirectParams ) );
- $this->mAllowedRedirectParams = $redirectParams;
- }
-}
-
-/**
- * Shortcut to construct a special page pointing to current user user's page.
- * @ingroup SpecialPage
- */
-class SpecialMypage extends RedirectSpecialArticle {
- function __construct() {
- parent::__construct( 'Mypage' );
- }
-
- function getRedirect( $subpage ) {
- if ( strval( $subpage ) !== '' ) {
- return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
- } else {
- return Title::makeTitle( NS_USER, $this->getUser()->getName() );
- }
- }
-}
-
-/**
- * Shortcut to construct a special page pointing to current user talk page.
- * @ingroup SpecialPage
- */
-class SpecialMytalk extends RedirectSpecialArticle {
- function __construct() {
- parent::__construct( 'Mytalk' );
- }
-
- function getRedirect( $subpage ) {
- if ( strval( $subpage ) !== '' ) {
- return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
- } else {
- return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() );
- }
- }
-}
-
-/**
- * Shortcut to construct a special page pointing to current user contributions.
- * @ingroup SpecialPage
- */
-class SpecialMycontributions extends RedirectSpecialPage {
- function __construct() {
- parent::__construct( 'Mycontributions' );
- $this->mAllowedRedirectParams = array( 'limit', 'namespace', 'tagfilter',
- 'offset', 'dir', 'year', 'month', 'feed' );
- }
-
- function getRedirect( $subpage ) {
- return SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() );
- }
-}
-
-/**
- * Redirect to Special:Listfiles?user=$wgUser
- */
-class SpecialMyuploads extends RedirectSpecialPage {
- function __construct() {
- parent::__construct( 'Myuploads' );
- $this->mAllowedRedirectParams = array( 'limit', 'ilshowall', 'ilsearch' );
- }
-
- function getRedirect( $subpage ) {
- return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
- }
-}
-
-/**
- * Redirect Special:Listfiles?user=$wgUser&ilshowall=true
- */
-class SpecialAllMyUploads extends RedirectSpecialPage {
- function __construct() {
- parent::__construct( 'AllMyUploads' );
- $this->mAllowedRedirectParams = array( 'limit', 'ilsearch' );
- }
-
- function getRedirect( $subpage ) {
- $this->mAddedRedirectParams['ilshowall'] = 1;
- return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
- }
-}
-
-
-/**
- * Redirect from Special:PermanentLink/### to index.php?oldid=###
- */
-class SpecialPermanentLink extends RedirectSpecialPage {
- function __construct() {
- parent::__construct( 'PermanentLink' );
- $this->mAllowedRedirectParams = array();
- }
-
- function getRedirect( $subpage ) {
- $subpage = intval( $subpage );
- if ( $subpage === 0 ) {
- # throw an error page when no subpage was given
- throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
- }
- $this->mAddedRedirectParams['oldid'] = $subpage;
- return true;
- }
-}
diff --git a/includes/SpecialPageFactory.php b/includes/SpecialPageFactory.php
deleted file mode 100644
index 11edc8ac..00000000
--- a/includes/SpecialPageFactory.php
+++ /dev/null
@@ -1,591 +0,0 @@
-<?php
-/**
- * Factory for handling the special page list and generating SpecialPage objects.
- *
- * 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
- * @defgroup SpecialPage SpecialPage
- */
-
-/**
- * Factory for handling the special page list and generating SpecialPage objects.
- *
- * To add a special page in an extension, add to $wgSpecialPages either
- * an object instance or an array containing the name and constructor
- * parameters. The latter is preferred for performance reasons.
- *
- * The object instantiated must be either an instance of SpecialPage or a
- * sub-class thereof. It must have an execute() method, which sends the HTML
- * for the special page to $wgOut. The parent class has an execute() method
- * which distributes the call to the historical global functions. Additionally,
- * execute() also checks if the user has the necessary access privileges
- * and bails out if not.
- *
- * To add a core special page, use the similar static list in
- * SpecialPage::$mList. To remove a core static special page at runtime, use
- * a SpecialPage_initList hook.
- *
- * @ingroup SpecialPage
- * @since 1.17
- */
-class SpecialPageFactory {
-
- /**
- * List of special page names to the subclass of SpecialPage which handles them.
- */
- private static $mList = array(
- // Maintenance Reports
- 'BrokenRedirects' => 'BrokenRedirectsPage',
- 'Deadendpages' => 'DeadendpagesPage',
- 'DoubleRedirects' => 'DoubleRedirectsPage',
- 'Longpages' => 'LongpagesPage',
- 'Ancientpages' => 'AncientpagesPage',
- 'Lonelypages' => 'LonelypagesPage',
- 'Fewestrevisions' => 'FewestrevisionsPage',
- 'Withoutinterwiki' => 'WithoutinterwikiPage',
- 'Protectedpages' => 'SpecialProtectedpages',
- 'Protectedtitles' => 'SpecialProtectedtitles',
- 'Shortpages' => 'ShortpagesPage',
- 'Uncategorizedcategories' => 'UncategorizedcategoriesPage',
- 'Uncategorizedimages' => 'UncategorizedimagesPage',
- 'Uncategorizedpages' => 'UncategorizedpagesPage',
- 'Uncategorizedtemplates' => 'UncategorizedtemplatesPage',
- 'Unusedcategories' => 'UnusedcategoriesPage',
- 'Unusedimages' => 'UnusedimagesPage',
- 'Unusedtemplates' => 'UnusedtemplatesPage',
- 'Unwatchedpages' => 'UnwatchedpagesPage',
- 'Wantedcategories' => 'WantedcategoriesPage',
- 'Wantedfiles' => 'WantedfilesPage',
- 'Wantedpages' => 'WantedpagesPage',
- 'Wantedtemplates' => 'WantedtemplatesPage',
-
- // List of pages
- 'Allpages' => 'SpecialAllpages',
- 'Prefixindex' => 'SpecialPrefixindex',
- 'Categories' => 'SpecialCategories',
- 'Listredirects' => 'ListredirectsPage',
- 'PagesWithProp' => 'SpecialPagesWithProp',
-
- // Login/create account
- 'Userlogin' => 'LoginForm',
- 'CreateAccount' => 'SpecialCreateAccount',
-
- // Users and rights
- 'Block' => 'SpecialBlock',
- 'Unblock' => 'SpecialUnblock',
- 'BlockList' => 'SpecialBlockList',
- 'ChangePassword' => 'SpecialChangePassword',
- 'PasswordReset' => 'SpecialPasswordReset',
- 'DeletedContributions' => 'DeletedContributionsPage',
- 'Preferences' => 'SpecialPreferences',
- 'ResetTokens' => 'SpecialResetTokens',
- 'Contributions' => 'SpecialContributions',
- 'Listgrouprights' => 'SpecialListGroupRights',
- 'Listusers' => 'SpecialListUsers',
- 'Listadmins' => 'SpecialListAdmins',
- 'Listbots' => 'SpecialListBots',
- 'Activeusers' => 'SpecialActiveUsers',
- 'Userrights' => 'UserrightsPage',
- 'EditWatchlist' => 'SpecialEditWatchlist',
-
- // Recent changes and logs
- 'Newimages' => 'SpecialNewFiles',
- 'Log' => 'SpecialLog',
- 'Watchlist' => 'SpecialWatchlist',
- 'Newpages' => 'SpecialNewpages',
- 'Recentchanges' => 'SpecialRecentchanges',
- 'Recentchangeslinked' => 'SpecialRecentchangeslinked',
- 'Tags' => 'SpecialTags',
-
- // Media reports and uploads
- 'Listfiles' => 'SpecialListFiles',
- 'Filepath' => 'SpecialFilepath',
- 'MIMEsearch' => 'MIMEsearchPage',
- 'FileDuplicateSearch' => 'FileDuplicateSearchPage',
- 'Upload' => 'SpecialUpload',
- 'UploadStash' => 'SpecialUploadStash',
-
- // Data and tools
- 'Statistics' => 'SpecialStatistics',
- 'Allmessages' => 'SpecialAllmessages',
- 'Version' => 'SpecialVersion',
- 'Lockdb' => 'SpecialLockdb',
- 'Unlockdb' => 'SpecialUnlockdb',
-
- // Redirecting special pages
- 'LinkSearch' => 'LinkSearchPage',
- 'Randompage' => 'Randompage',
- 'RandomInCategory' => 'SpecialRandomInCategory',
- 'Randomredirect' => 'SpecialRandomredirect',
-
- // High use pages
- 'Mostlinkedcategories' => 'MostlinkedCategoriesPage',
- 'Mostimages' => 'MostimagesPage',
- 'Mostinterwikis' => 'MostinterwikisPage',
- 'Mostlinked' => 'MostlinkedPage',
- 'Mostlinkedtemplates' => 'MostlinkedTemplatesPage',
- 'Mostcategories' => 'MostcategoriesPage',
- 'Mostrevisions' => 'MostrevisionsPage',
-
- // Page tools
- 'ComparePages' => 'SpecialComparePages',
- 'Export' => 'SpecialExport',
- 'Import' => 'SpecialImport',
- 'Undelete' => 'SpecialUndelete',
- 'Whatlinkshere' => 'SpecialWhatlinkshere',
- 'MergeHistory' => 'SpecialMergeHistory',
-
- // Other
- 'Booksources' => 'SpecialBookSources',
-
- // Unlisted / redirects
- 'Blankpage' => 'SpecialBlankpage',
- 'Emailuser' => 'SpecialEmailUser',
- 'Movepage' => 'MovePageForm',
- 'Mycontributions' => 'SpecialMycontributions',
- 'Mypage' => 'SpecialMypage',
- 'Mytalk' => 'SpecialMytalk',
- 'Myuploads' => 'SpecialMyuploads',
- 'AllMyUploads' => 'SpecialAllMyUploads',
- 'PermanentLink' => 'SpecialPermanentLink',
- 'Redirect' => 'SpecialRedirect',
- 'Revisiondelete' => 'SpecialRevisionDelete',
- 'Specialpages' => 'SpecialSpecialpages',
- 'Userlogout' => 'SpecialUserlogout',
- );
-
- private static $mAliases;
-
- /**
- * Initialise the special page list
- * This must be called before accessing SpecialPage::$mList
- *
- * @return array
- */
- static function getList() {
- global $wgSpecialPages;
- global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
- global $wgEnableEmail, $wgEnableJavaScriptTest;
-
- if ( !is_object( self::$mList ) ) {
- wfProfileIn( __METHOD__ );
-
- if ( !$wgDisableCounters ) {
- self::$mList['Popularpages'] = 'PopularpagesPage';
- }
-
- if ( !$wgDisableInternalSearch ) {
- self::$mList['Search'] = 'SpecialSearch';
- }
-
- if ( $wgEmailAuthentication ) {
- self::$mList['Confirmemail'] = 'EmailConfirmation';
- self::$mList['Invalidateemail'] = 'EmailInvalidation';
- }
-
- if ( $wgEnableEmail ) {
- self::$mList['ChangeEmail'] = 'SpecialChangeEmail';
- }
-
- if ( $wgEnableJavaScriptTest ) {
- self::$mList['JavaScriptTest'] = 'SpecialJavaScriptTest';
- }
-
- // Add extension special pages
- self::$mList = array_merge( self::$mList, $wgSpecialPages );
-
- // Run hooks
- // This hook can be used to remove undesired built-in special pages
- wfRunHooks( 'SpecialPage_initList', array( &self::$mList ) );
-
- // Cast to object: func()[$key] doesn't work, but func()->$key does
- settype( self::$mList, 'object' );
-
- wfProfileOut( __METHOD__ );
- }
- return self::$mList;
- }
-
- /**
- * Initialise and return the list of special page aliases. Returns an object with
- * properties which can be accessed $obj->pagename - each property is an array of
- * aliases; the first in the array is the canonical alias. All registered special
- * pages are guaranteed to have a property entry, and for that property array to
- * contain at least one entry (English fallbacks will be added if necessary).
- * @return Object
- */
- static function getAliasList() {
- if ( !is_object( self::$mAliases ) ) {
- global $wgContLang;
- $aliases = $wgContLang->getSpecialPageAliases();
-
- // Objects are passed by reference by default, need to create a copy
- $missingPages = clone self::getList();
-
- self::$mAliases = array();
- foreach ( $aliases as $realName => $aliasList ) {
- foreach ( $aliasList as $alias ) {
- self::$mAliases[$wgContLang->caseFold( $alias )] = $realName;
- }
- unset( $missingPages->$realName );
- }
- foreach ( $missingPages as $name => $stuff ) {
- self::$mAliases[$wgContLang->caseFold( $name )] = $name;
- }
-
- // Cast to object: func()[$key] doesn't work, but func()->$key does
- self::$mAliases = (object)self::$mAliases;
- }
- return self::$mAliases;
- }
-
- /**
- * Given a special page name with a possible subpage, return an array
- * where the first element is the special page name and the second is the
- * subpage.
- *
- * @param $alias String
- * @return Array( String, String|null ), or array( null, null ) if the page is invalid
- */
- public static function resolveAlias( $alias ) {
- global $wgContLang;
- $bits = explode( '/', $alias, 2 );
-
- $caseFoldedAlias = $wgContLang->caseFold( $bits[0] );
- $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
- if ( isset( self::getAliasList()->$caseFoldedAlias ) ) {
- $name = self::getAliasList()->$caseFoldedAlias;
- } else {
- return array( null, null );
- }
-
- if ( !isset( $bits[1] ) ) { // bug 2087
- $par = null;
- } else {
- $par = $bits[1];
- }
-
- return array( $name, $par );
- }
-
- /**
- * Add a page to a certain display group for Special:SpecialPages
- *
- * @param $page Mixed: SpecialPage or string
- * @param $group String
- * @deprecated since 1.21 Override SpecialPage::getGroupName
- */
- public static function setGroup( $page, $group ) {
- wfDeprecated( __METHOD__, '1.21' );
-
- global $wgSpecialPageGroups;
- $name = is_object( $page ) ? $page->getName() : $page;
- $wgSpecialPageGroups[$name] = $group;
- }
-
- /**
- * Get the group that the special page belongs in on Special:SpecialPage
- *
- * @param $page SpecialPage
- * @return String
- * @deprecated since 1.21 Use SpecialPage::getFinalGroupName
- */
- public static function getGroup( &$page ) {
- wfDeprecated( __METHOD__, '1.21' );
-
- return $page->getFinalGroupName();
- }
-
- /**
- * Check if a given name exist as a special page or as a special page alias
- *
- * @param string $name name of a special page
- * @return Boolean: true if a special page exists with this name
- */
- public static function exists( $name ) {
- list( $title, /*...*/ ) = self::resolveAlias( $name );
- return property_exists( self::getList(), $title );
- }
-
- /**
- * Find the object with a given name and return it (or NULL)
- *
- * @param string $name Special page name, may be localised and/or an alias
- * @return SpecialPage|null SpecialPage object or null if the page doesn't exist
- */
- public static function getPage( $name ) {
- list( $realName, /*...*/ ) = self::resolveAlias( $name );
- if ( property_exists( self::getList(), $realName ) ) {
- $rec = self::getList()->$realName;
- if ( is_string( $rec ) ) {
- $className = $rec;
- return new $className;
- } elseif ( is_array( $rec ) ) {
- // @deprecated, officially since 1.18, unofficially since forever
- wfDebug( "Array syntax for \$wgSpecialPages is deprecated, define a subclass of SpecialPage instead." );
- $className = array_shift( $rec );
- self::getList()->$realName = MWFunction::newObj( $className, $rec );
- }
- return self::getList()->$realName;
- } else {
- return null;
- }
- }
-
- /**
- * Return categorised listable special pages which are available
- * for the current user, and everyone.
- *
- * @param $user User object to check permissions, $wgUser will be used
- * if not provided
- * @return Array( String => Specialpage )
- */
- public static function getUsablePages( User $user = null ) {
- $pages = array();
- if ( $user === null ) {
- global $wgUser;
- $user = $wgUser;
- }
- foreach ( self::getList() as $name => $rec ) {
- $page = self::getPage( $name );
- if ( $page ) { // not null
- $page->setContext( RequestContext::getMain() );
- if ( $page->isListed()
- && ( !$page->isRestricted() || $page->userCanExecute( $user ) )
- ) {
- $pages[$name] = $page;
- }
- }
- }
- return $pages;
- }
-
- /**
- * Return categorised listable special pages for all users
- *
- * @return Array( String => Specialpage )
- */
- public static function getRegularPages() {
- $pages = array();
- foreach ( self::getList() as $name => $rec ) {
- $page = self::getPage( $name );
- if ( $page->isListed() && !$page->isRestricted() ) {
- $pages[$name] = $page;
- }
- }
- return $pages;
- }
-
- /**
- * Return categorised listable special pages which are available
- * for the current user, but not for everyone
- *
- * @return Array( String => Specialpage )
- */
- public static function getRestrictedPages() {
- global $wgUser;
- $pages = array();
- foreach ( self::getList() as $name => $rec ) {
- $page = self::getPage( $name );
- if (
- $page->isListed()
- && $page->isRestricted()
- && $page->userCanExecute( $wgUser )
- ) {
- $pages[$name] = $page;
- }
- }
- return $pages;
- }
-
- /**
- * Execute a special page path.
- * The path may contain parameters, e.g. Special:Name/Params
- * Extracts the special page name and call the execute method, passing the parameters
- *
- * Returns a title object if the page is redirected, false if there was no such special
- * page, and true if it was successful.
- *
- * @param $title Title object
- * @param $context IContextSource
- * @param $including Bool output is being captured for use in {{special:whatever}}
- *
- * @return bool
- */
- public static function executePath( Title &$title, IContextSource &$context, $including = false ) {
- wfProfileIn( __METHOD__ );
-
- // @todo FIXME: Redirects broken due to this call
- $bits = explode( '/', $title->getDBkey(), 2 );
- $name = $bits[0];
- if ( !isset( $bits[1] ) ) { // bug 2087
- $par = null;
- } else {
- $par = $bits[1];
- }
- $page = self::getPage( $name );
- // Nonexistent?
- if ( !$page ) {
- $context->getOutput()->setArticleRelated( false );
- $context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
-
- global $wgSend404Code;
- if ( $wgSend404Code ) {
- $context->getOutput()->setStatusCode( 404 );
- }
-
- $context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- // Page exists, set the context
- $page->setContext( $context );
-
- if ( !$including ) {
- // Redirect to canonical alias for GET commands
- // Not for POST, we'd lose the post data, so it's best to just distribute
- // the request. Such POST requests are possible for old extensions that
- // generate self-links without being aware that their default name has
- // changed.
- if ( $name != $page->getLocalName() && !$context->getRequest()->wasPosted() ) {
- $query = $context->getRequest()->getQueryValues();
- unset( $query['title'] );
- $title = $page->getTitle( $par );
- $url = $title->getFullURL( $query );
- $context->getOutput()->redirect( $url );
- wfProfileOut( __METHOD__ );
- return $title;
- } else {
- $context->setTitle( $page->getTitle( $par ) );
- }
-
- } elseif ( !$page->isIncludable() ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $page->including( $including );
-
- // Execute special page
- $profName = 'Special:' . $page->getName();
- wfProfileIn( $profName );
- $page->run( $par );
- wfProfileOut( $profName );
- wfProfileOut( __METHOD__ );
- return true;
- }
-
- /**
- * Just like executePath() but will override global variables and execute
- * the page in "inclusion" mode. Returns true if the execution was
- * successful or false if there was no such special page, or a title object
- * if it was a redirect.
- *
- * Also saves the current $wgTitle, $wgOut, $wgRequest, $wgUser and $wgLang
- * variables so that the special page will get the context it'd expect on a
- * normal request, and then restores them to their previous values after.
- *
- * @param $title Title
- * @param $context IContextSource
- *
- * @return String: HTML fragment
- */
- static function capturePath( Title $title, IContextSource $context ) {
- global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgLang;
-
- // Save current globals
- $oldTitle = $wgTitle;
- $oldOut = $wgOut;
- $oldRequest = $wgRequest;
- $oldUser = $wgUser;
- $oldLang = $wgLang;
-
- // Set the globals to the current context
- $wgTitle = $title;
- $wgOut = $context->getOutput();
- $wgRequest = $context->getRequest();
- $wgUser = $context->getUser();
- $wgLang = $context->getLanguage();
-
- // The useful part
- $ret = self::executePath( $title, $context, true );
-
- // And restore the old globals
- $wgTitle = $oldTitle;
- $wgOut = $oldOut;
- $wgRequest = $oldRequest;
- $wgUser = $oldUser;
- $wgLang = $oldLang;
-
- return $ret;
- }
-
- /**
- * Get the local name for a specified canonical name
- *
- * @param $name String
- * @param $subpage String|Bool
- *
- * @return String
- */
- static function getLocalNameFor( $name, $subpage = false ) {
- global $wgContLang;
- $aliases = $wgContLang->getSpecialPageAliases();
-
- if ( isset( $aliases[$name][0] ) ) {
- $name = $aliases[$name][0];
- } else {
- // Try harder in case someone misspelled the correct casing
- $found = false;
- foreach ( $aliases as $n => $values ) {
- if ( strcasecmp( $name, $n ) === 0 ) {
- wfWarn( "Found alias defined for $n when searching for " .
- "special page aliases for $name. Case mismatch?" );
- $name = $values[0];
- $found = true;
- break;
- }
- }
- if ( !$found ) {
- wfWarn( "Did not find alias for special page '$name'. " .
- "Perhaps no aliases are defined for it?" );
- }
- }
- if ( $subpage !== false && !is_null( $subpage ) ) {
- $name = "$name/$subpage";
- }
- return $wgContLang->ucfirst( $name );
- }
-
- /**
- * Get a title for a given alias
- *
- * @param $alias String
- *
- * @return Title or null if there is no such alias
- */
- static function getTitleForAlias( $alias ) {
- $name = self::resolveAlias( $alias );
- if ( $name ) {
- return SpecialPage::getTitleFor( $name );
- } else {
- return null;
- }
- }
-}
diff --git a/includes/SquidPurgeClient.php b/includes/SquidPurgeClient.php
index f5fd1950..824dd06b 100644
--- a/includes/SquidPurgeClient.php
+++ b/includes/SquidPurgeClient.php
@@ -28,12 +28,26 @@
* Could be replaced by curl_multi_exec() or some such.
*/
class SquidPurgeClient {
- var $host, $port, $ip;
+ /** @var string */
+ protected $host;
- var $readState = 'idle';
- var $writeBuffer = '';
- var $requests = array();
- var $currentRequestIndex;
+ /** @var int */
+ protected $port;
+
+ /** @var string|bool */
+ protected $ip;
+
+ /** @var string */
+ protected $readState = 'idle';
+
+ /** @var string */
+ protected $writeBuffer = '';
+
+ /** @var array */
+ protected $requests = array();
+
+ /** @var mixed */
+ protected $currentRequestIndex;
const EINTR = 4;
const EAGAIN = 11;
@@ -41,17 +55,20 @@ class SquidPurgeClient {
const BUFFER_SIZE = 8192;
/**
- * The socket resource, or null for unconnected, or false for disabled due to error
+ * @var resource|null The socket resource, or null for unconnected, or false
+ * for disabled due to error.
*/
- var $socket;
+ protected $socket;
- var $readBuffer;
+ /** @var string */
+ protected $readBuffer;
- var $bodyRemaining;
+ /** @var int */
+ protected $bodyRemaining;
/**
- * @param $server string
- * @param $options array
+ * @param string $server
+ * @param array $options
*/
public function __construct( $server, $options = array() ) {
$parts = explode( ':', $server, 2 );
@@ -126,6 +143,8 @@ class SquidPurgeClient {
/**
* Get the host's IP address.
* Does not support IPv6 at present due to the lack of a convenient interface in PHP.
+ * @throws MWException
+ * @return string
*/
protected function getIP() {
if ( $this->ip === null ) {
@@ -173,7 +192,7 @@ class SquidPurgeClient {
/**
* Queue a purge operation
*
- * @param $url string
+ * @param string $url
*/
public function queuePurge( $url ) {
global $wgSquidPurgeUseHostHeader;
@@ -323,8 +342,7 @@ class SquidPurgeClient {
}
/**
- * @param $line
- * @return
+ * @param string $line
*/
protected function processStatusLine( $line ) {
if ( !preg_match( '!^HTTP/(\d+)\.(\d+) (\d{3}) (.*)$!', $line, $m ) ) {
@@ -343,7 +361,7 @@ class SquidPurgeClient {
}
/**
- * @param $line string
+ * @param string $line
*/
protected function processHeaderLine( $line ) {
if ( preg_match( '/^Content-Length: (\d+)$/i', $line, $m ) ) {
@@ -370,23 +388,22 @@ class SquidPurgeClient {
}
/**
- * @param $msg string
+ * @param string $msg
*/
protected function log( $msg ) {
- wfDebugLog( 'squid', __CLASS__ . " ($this->host): $msg\n" );
+ wfDebugLog( 'squid', __CLASS__ . " ($this->host): $msg" );
}
}
class SquidPurgeClientPool {
+ /** @var array Array of SquidPurgeClient */
+ protected $clients = array();
- /**
- * @var array of SquidPurgeClient
- */
- var $clients = array();
- var $timeout = 5;
+ /** @var int */
+ protected $timeout = 5;
/**
- * @param $options array
+ * @param array $options
*/
function __construct( $options = array() ) {
if ( isset( $options['timeout'] ) ) {
@@ -395,7 +412,7 @@ class SquidPurgeClientPool {
}
/**
- * @param $client SquidPurgeClient
+ * @param SquidPurgeClient $client
* @return void
*/
public function addClient( $client ) {
diff --git a/includes/StatCounter.php b/includes/StatCounter.php
index 1373f3d5..5fc8f2f5 100644
--- a/includes/StatCounter.php
+++ b/includes/StatCounter.php
@@ -36,10 +36,15 @@
* @ingroup StatCounter
*/
class StatCounter {
- /** @var Array */
+ /** @var array */
protected $deltas = array(); // (key => count)
- protected function __construct() {}
+ /** @var Config */
+ protected $config;
+
+ protected function __construct( Config $config ) {
+ $this->config = $config;
+ }
/**
* @return StatCounter
@@ -47,7 +52,9 @@ class StatCounter {
public static function singleton() {
static $instance = null;
if ( !$instance ) {
- $instance = new self();
+ $instance = new self(
+ ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+ );
}
return $instance;
}
@@ -56,7 +63,7 @@ class StatCounter {
* Increment a key by delta $count
*
* @param string $key
- * @param integer $count
+ * @param int $count
* @return void
*/
public function incr( $key, $count = 1 ) {
@@ -73,12 +80,11 @@ class StatCounter {
* @return void
*/
public function flush() {
- global $wgStatsMethod;
-
+ $statsMethod = $this->config->get( 'StatsMethod' );
$deltas = array_filter( $this->deltas ); // remove 0 valued entries
- if ( $wgStatsMethod === 'udp' ) {
+ if ( $statsMethod === 'udp' ) {
$this->sendDeltasUDP( $deltas );
- } elseif ( $wgStatsMethod === 'cache' ) {
+ } elseif ( $statsMethod === 'cache' ) {
$this->sendDeltasMemc( $deltas );
} else {
// disabled
@@ -91,14 +97,12 @@ class StatCounter {
* @return void
*/
protected function sendDeltasUDP( array $deltas ) {
- global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgAggregateStatsID,
- $wgStatsFormatString;
-
- $id = strlen( $wgAggregateStatsID ) ? $wgAggregateStatsID : wfWikiID();
+ $aggregateStatsID = $this->config->get( 'AggregateStatsID' );
+ $id = strlen( $aggregateStatsID ) ? $aggregateStatsID : wfWikiID();
$lines = array();
foreach ( $deltas as $key => $count ) {
- $lines[] = sprintf( $wgStatsFormatString, $id, $count, $key );
+ $lines[] = sprintf( $this->config->get( 'StatsFormatString' ), $id, $count, $key );
}
if ( count( $lines ) ) {
@@ -125,8 +129,8 @@ class StatCounter {
$packet,
strlen( $packet ),
0,
- $wgUDPProfilerHost,
- $wgUDPProfilerPort
+ $this->config->get( 'UDPProfilerHost' ),
+ $this->config->get( 'UDPProfilerPort' )
);
wfRestoreWarnings();
}
diff --git a/includes/Status.php b/includes/Status.php
index 928f8ebd..1a72968b 100644
--- a/includes/Status.php
+++ b/includes/Status.php
@@ -38,21 +38,33 @@
* so that a lack of error-handling will be explicit.
*/
class Status {
- var $ok = true;
- var $value;
+ /** @var bool */
+ public $ok = true;
+
+ /** @var mixed */
+ public $value;
/** Counters for batch operations */
- public $successCount = 0, $failCount = 0;
+ /** @var int */
+ public $successCount = 0;
+
+ /** @var int */
+ public $failCount = 0;
+
/** Array to indicate which items of the batch operations were successful */
+ /** @var array */
public $success = array();
- /*semi-private*/ var $errors = array();
- /*semi-private*/ var $cleanCallback = false;
+ /** @var array */
+ public $errors = array();
+
+ /** @var callable */
+ public $cleanCallback = false;
/**
* Factory function for fatal errors
*
- * @param string|Message $message message name or object
+ * @param string|Message $message Message name or object
* @return Status
*/
static function newFatal( $message /*, parameters...*/ ) {
@@ -66,7 +78,7 @@ class Status {
/**
* Factory function for good results
*
- * @param $value Mixed
+ * @param mixed $value
* @return Status
*/
static function newGood( $value = null ) {
@@ -78,10 +90,10 @@ class Status {
/**
* Change operation result
*
- * @param $ok Boolean: whether the operation completed
- * @param $value Mixed
+ * @param bool $ok Whether the operation completed
+ * @param mixed $value
*/
- function setResult( $ok, $value = null ) {
+ public function setResult( $ok, $value = null ) {
$this->ok = $ok;
$this->value = $value;
}
@@ -90,27 +102,27 @@ class Status {
* Returns whether the operation completed and didn't have any error or
* warnings
*
- * @return Boolean
+ * @return bool
*/
- function isGood() {
+ public function isGood() {
return $this->ok && !$this->errors;
}
/**
* Returns whether the operation completed
*
- * @return Boolean
+ * @return bool
*/
- function isOK() {
+ public function isOK() {
return $this->ok;
}
/**
* Add a new warning
*
- * @param string|Message $message message name or object
+ * @param string|Message $message Message name or object
*/
- function warning( $message /*, parameters... */ ) {
+ public function warning( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
$this->errors[] = array(
'type' => 'warning',
@@ -122,9 +134,9 @@ class Status {
* Add an error, do not set fatal flag
* This can be used for non-fatal errors
*
- * @param string|Message $message message name or object
+ * @param string|Message $message Message name or object
*/
- function error( $message /*, parameters... */ ) {
+ public function error( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
$this->errors[] = array(
'type' => 'error',
@@ -136,9 +148,9 @@ class Status {
* Add an error and set OK to false, indicating that the operation
* as a whole was fatal
*
- * @param string|Message $message message name or object
+ * @param string|Message $message Message name or object
*/
- function fatal( $message /*, parameters... */ ) {
+ public function fatal( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
$this->errors[] = array(
'type' => 'error',
@@ -150,12 +162,12 @@ class Status {
/**
* Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
*/
- function __wakeup() {
+ public function __wakeup() {
$this->cleanCallback = false;
}
/**
- * @param $params array
+ * @param array $params
* @return array
*/
protected function cleanParams( $params ) {
@@ -172,12 +184,12 @@ class Status {
/**
* Get the error list as a wikitext formatted list
*
- * @param string $shortContext a short enclosing context message name, to
+ * @param string $shortContext A short enclosing context message name, to
* be used when there is a single error
- * @param string $longContext a long enclosing context message name, for a list
- * @return String
+ * @param string $longContext A long enclosing context message name, for a list
+ * @return string
*/
- function getWikiText( $shortContext = false, $longContext = false ) {
+ public function getWikiText( $shortContext = false, $longContext = false ) {
if ( count( $this->errors ) == 0 ) {
if ( $this->ok ) {
$this->fatal( 'internalerror_info',
@@ -212,12 +224,14 @@ class Status {
/**
* Get the error list as a Message object
*
- * @param string $shortContext a short enclosing context message name, to
- * be used when there is a single error
- * @param string $longContext a long enclosing context message name, for a list
+ * @param string|string[] $shortContext A short enclosing context message name (or an array of
+ * message names), to be used when there is a single error.
+ * @param string|string[] $longContext A long enclosing context message name (or an array of
+ * message names), for a list.
+ *
* @return Message
*/
- function getMessage( $shortContext = false, $longContext = false ) {
+ public function getMessage( $shortContext = false, $longContext = false ) {
if ( count( $this->errors ) == 0 ) {
if ( $this->ok ) {
$this->fatal( 'internalerror_info',
@@ -237,20 +251,20 @@ class Status {
$s = wfMessage( $longContext, $wrapper );
}
} else {
- $msgs = $this->getErrorMessageArray( $this->errors );
+ $msgs = $this->getErrorMessageArray( $this->errors );
$msgCount = count( $msgs );
if ( $shortContext ) {
$msgCount++;
}
- $wrapper = new RawMessage( '* $' . implode( "\n* \$", range( 1, $msgCount ) ) );
- $s = $wrapper->params( $msgs )->parse();
+ $s = new RawMessage( '* $' . implode( "\n* \$", range( 1, $msgCount ) ) );
+ $s->params( $msgs )->parse();
if ( $longContext ) {
- $s = wfMessage( $longContext, $wrapper );
+ $s = wfMessage( $longContext, $s );
} elseif ( $shortContext ) {
- $wrapper = new RawMessage( "\n\$1\n", $wrapper );
+ $wrapper = new RawMessage( "\n\$1\n", $s );
$wrapper->parse();
$s = wfMessage( $shortContext, $wrapper );
}
@@ -261,12 +275,12 @@ class Status {
/**
* Return the message for a single error.
- * @param $error Mixed With an array & two values keyed by
+ * @param mixed $error With an array & two values keyed by
* 'message' and 'params', use those keys-value pairs.
* Otherwise, if its an array, just use the first value as the
* message and the remaining items as the params.
*
- * @return String
+ * @return string
*/
protected function getErrorMessage( $error ) {
if ( is_array( $error ) ) {
@@ -289,20 +303,21 @@ class Status {
/**
* Get the error message as HTML. This is done by parsing the wikitext error
* message.
- *
- * @note: this does not perform a full wikitext to HTML conversion, it merely applies
- * a message transformation.
- * @todo figure out whether that is actually The Right Thing.
+ * @param string $shortContext A short enclosing context message name, to
+ * be used when there is a single error
+ * @param string $longContext A long enclosing context message name, for a list
+ * @return string
*/
public function getHTML( $shortContext = false, $longContext = false ) {
$text = $this->getWikiText( $shortContext, $longContext );
- return MessageCache::singleton()->transform( $text, true );
+ $out = MessageCache::singleton()->parse( $text, null, true, true );
+ return $out instanceof ParserOutput ? $out->getText() : $out;
}
/**
* Return an array with the wikitext for each item in the array.
- * @param $errors Array
- * @return Array
+ * @param array $errors
+ * @return array
*/
protected function getErrorMessageArray( $errors ) {
return array_map( array( $this, 'getErrorMessage' ), $errors );
@@ -311,10 +326,10 @@ class Status {
/**
* Merge another status object into this one
*
- * @param $other Status Other Status object
- * @param $overwriteValue Boolean: whether to override the "value" member
+ * @param Status $other Other Status object
+ * @param bool $overwriteValue Whether to override the "value" member
*/
- function merge( $other, $overwriteValue = false ) {
+ public function merge( $other, $overwriteValue = false ) {
$this->errors = array_merge( $this->errors, $other->errors );
$this->ok = $this->ok && $other->ok;
if ( $overwriteValue ) {
@@ -330,7 +345,7 @@ class Status {
* @return array A list in which each entry is an array with a message key as its first element.
* The remaining array elements are the message parameters.
*/
- function getErrorsArray() {
+ public function getErrorsArray() {
return $this->getStatusArray( "error" );
}
@@ -340,22 +355,24 @@ class Status {
* @return array A list in which each entry is an array with a message key as its first element.
* The remaining array elements are the message parameters.
*/
- function getWarningsArray() {
+ public function getWarningsArray() {
return $this->getStatusArray( "warning" );
}
/**
* Returns a list of status messages of the given type
- * @param $type String
- *
- * @return Array
+ * @param string $type
+ * @return array
*/
protected function getStatusArray( $type ) {
$result = array();
foreach ( $this->errors as $error ) {
if ( $error['type'] === $type ) {
if ( $error['message'] instanceof Message ) {
- $result[] = array_merge( array( $error['message']->getKey() ), $error['message']->getParams() );
+ $result[] = array_merge(
+ array( $error['message']->getKey() ),
+ $error['message']->getParams()
+ );
} elseif ( $error['params'] ) {
$result[] = array_merge( array( $error['message'] ), $error['params'] );
} else {
@@ -363,6 +380,7 @@ class Status {
}
}
}
+
return $result;
}
@@ -370,9 +388,9 @@ class Status {
* Returns a list of status messages of the given type, with message and
* params left untouched, like a sane version of getStatusArray
*
- * @param $type String
+ * @param string $type
*
- * @return Array
+ * @return array
*/
public function getErrorsByType( $type ) {
$result = array();
@@ -387,15 +405,20 @@ class Status {
/**
* Returns true if the specified message is present as a warning or error
*
- * Note, due to the lack of tools for comparing Message objects, this
- * function will not work when using a Message object as a parameter.
+ * @param string|Message $message Message key or object to search for
*
- * @param string $msg message name
- * @return Boolean
+ * @return bool
*/
- function hasMessage( $msg ) {
+ public function hasMessage( $message ) {
+ if ( $message instanceof Message ) {
+ $message = $message->getKey();
+ }
foreach ( $this->errors as $error ) {
- if ( $error['message'] === $msg ) {
+ if ( $error['message'] instanceof Message
+ && $error['message']->getKey() === $message
+ ) {
+ return true;
+ } elseif ( $error['message'] === $message ) {
return true;
}
}
@@ -409,11 +432,11 @@ class Status {
* Note, due to the lack of tools for comparing Message objects, this
* function will not work when using a Message object as the search parameter.
*
- * @param $source Message|String: Message key or object to search for
- * @param $dest Message|String: Replacement message key or object
+ * @param Message|string $source Message key or object to search for
+ * @param Message|string $dest Replacement message key or object
* @return bool Return true if the replacement was done, false otherwise.
*/
- function replaceMessage( $source, $dest ) {
+ public function replaceMessage( $source, $dest ) {
$replaced = false;
foreach ( $this->errors as $index => $error ) {
if ( $error['message'] === $source ) {
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index 1ad643ac..25031501 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -158,10 +158,14 @@ class StreamFile {
# used for thumbnails (thumb.php)
if ( $wgTrivialMimeDetection ) {
switch ( $ext ) {
- case 'gif': return 'image/gif';
- case 'png': return 'image/png';
- case 'jpg': return 'image/jpeg';
- case 'jpeg': return 'image/jpeg';
+ case 'gif':
+ return 'image/gif';
+ case 'png':
+ return 'image/png';
+ case 'jpg':
+ return 'image/jpeg';
+ case 'jpeg':
+ return 'image/jpeg';
}
return 'unknown/unknown';
@@ -185,8 +189,8 @@ class StreamFile {
return 'unknown/unknown';
}
if ( $wgCheckFileExtensions && $wgStrictFileExtensions
- && !UploadBase::checkFileExtensionList( $extList, $wgFileExtensions ) )
- {
+ && !UploadBase::checkFileExtensionList( $extList, $wgFileExtensions )
+ ) {
return 'unknown/unknown';
}
if ( $wgVerifyMimeType && in_array( strtolower( $type ), $wgMimeTypeBlacklist ) ) {
diff --git a/includes/StubObject.php b/includes/StubObject.php
index a3970f34..8878660b 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -42,30 +42,36 @@
* which refers to it should be kept to a minimum.
*/
class StubObject {
- var $mGlobal, $mClass, $mParams;
+ /** @var null|string */
+ protected $global;
+
+ /** @var null|string */
+ protected $class;
+
+ /** @var array */
+ protected $params;
/**
* Constructor.
*
- * @param string $global name of the global variable.
- * @param string $class name of the class of the real object.
- * @param array $params parameters to pass to constructor of the real
- * object.
+ * @param string $global Name of the global variable.
+ * @param string $class Name of the class of the real object.
+ * @param array $params Parameters to pass to constructor of the real object.
*/
- function __construct( $global = null, $class = null, $params = array() ) {
- $this->mGlobal = $global;
- $this->mClass = $class;
- $this->mParams = $params;
+ public function __construct( $global = null, $class = null, $params = array() ) {
+ $this->global = $global;
+ $this->class = $class;
+ $this->params = $params;
}
/**
* Returns a bool value whenever $obj is a stub object. Can be used to break
* a infinite loop when unstubbing an object.
*
- * @param $obj Object to check.
- * @return Boolean: true if $obj is not an instance of StubObject class.
+ * @param object $obj Object to check.
+ * @return bool True if $obj is not an instance of StubObject class.
*/
- static function isRealObject( $obj ) {
+ public static function isRealObject( $obj ) {
return is_object( $obj ) && !$obj instanceof StubObject;
}
@@ -74,12 +80,12 @@ class StubObject {
* infinite loop when unstubbing an object or to avoid reference parameter
* breakage.
*
- * @param $obj Object to check.
+ * @param object $obj Object to check.
* @return void
*/
- static function unstub( $obj ) {
+ public static function unstub( &$obj ) {
if ( $obj instanceof StubObject ) {
- $obj->_unstub( 'unstub', 3 );
+ $obj = $obj->_unstub( 'unstub', 3 );
}
}
@@ -90,32 +96,32 @@ class StubObject {
* This function will also call the function with the same name in the real
* object.
*
- * @param string $name name of the function called
- * @param array $args arguments
+ * @param string $name Name of the function called
+ * @param array $args Arguments
* @return mixed
*/
- function _call( $name, $args ) {
+ public function _call( $name, $args ) {
$this->_unstub( $name, 5 );
- return call_user_func_array( array( $GLOBALS[$this->mGlobal], $name ), $args );
+ return call_user_func_array( array( $GLOBALS[$this->global], $name ), $args );
}
/**
* Create a new object to replace this stub object.
* @return object
*/
- function _newObject() {
- return MWFunction::newObj( $this->mClass, $this->mParams );
+ public function _newObject() {
+ return MWFunction::newObj( $this->class, $this->params );
}
/**
* Function called by PHP if no function with that name exists in this
* object.
*
- * @param string $name name of the function called
- * @param array $args arguments
+ * @param string $name Name of the function called
+ * @param array $args Arguments
* @return mixed
*/
- function __call( $name, $args ) {
+ public function __call( $name, $args ) {
return $this->_call( $name, $args );
}
@@ -125,82 +131,57 @@ class StubObject {
* This is public, for the convenience of external callers wishing to access
* properties, e.g. eval.php
*
- * @param string $name name of the method called in this object.
- * @param $level Integer: level to go in the stack trace to get the function
- * who called this function.
+ * @param string $name Name of the method called in this object.
+ * @param int $level Level to go in the stack trace to get the function
+ * who called this function.
+ * @return object The unstubbed version of itself
* @throws MWException
*/
- function _unstub( $name = '_unstub', $level = 2 ) {
+ public function _unstub( $name = '_unstub', $level = 2 ) {
static $recursionLevel = 0;
- if ( !$GLOBALS[$this->mGlobal] instanceof StubObject ) {
- return $GLOBALS[$this->mGlobal]; // already unstubbed.
+ if ( !$GLOBALS[$this->global] instanceof StubObject ) {
+ return $GLOBALS[$this->global]; // already unstubbed.
}
- if ( get_class( $GLOBALS[$this->mGlobal] ) != $this->mClass ) {
- $fname = __METHOD__ . '-' . $this->mGlobal;
+ if ( get_class( $GLOBALS[$this->global] ) != $this->class ) {
+ $fname = __METHOD__ . '-' . $this->global;
wfProfileIn( $fname );
$caller = wfGetCaller( $level );
if ( ++$recursionLevel > 2 ) {
wfProfileOut( $fname );
- throw new MWException( "Unstub loop detected on call of \${$this->mGlobal}->$name from $caller\n" );
+ throw new MWException( "Unstub loop detected on call of "
+ . "\${$this->global}->$name from $caller\n" );
}
- wfDebug( "Unstubbing \${$this->mGlobal} on call of \${$this->mGlobal}::$name from $caller\n" );
- $GLOBALS[$this->mGlobal] = $this->_newObject();
+ wfDebug( "Unstubbing \${$this->global} on call of "
+ . "\${$this->global}::$name from $caller\n" );
+ $GLOBALS[$this->global] = $this->_newObject();
--$recursionLevel;
wfProfileOut( $fname );
+ return $GLOBALS[$this->global];
}
}
}
/**
- * Stub object for the content language of this wiki. This object have to be in
- * $wgContLang global.
- *
- * @deprecated since 1.18
- */
-class StubContLang extends StubObject {
-
- function __construct() {
- wfDeprecated( __CLASS__, '1.18' );
- parent::__construct( 'wgContLang' );
- }
-
- function __call( $name, $args ) {
- return $this->_call( $name, $args );
- }
-
- /**
- * @return Language
- */
- function _newObject() {
- global $wgLanguageCode;
- $obj = Language::factory( $wgLanguageCode );
- $obj->initEncoding();
- $obj->initContLang();
- return $obj;
- }
-}
-
-/**
* Stub object for the user language. It depends of the user preferences and
* "uselang" parameter that can be passed to index.php. This object have to be
* in $wgLang global.
*/
class StubUserLang extends StubObject {
- function __construct() {
+ public function __construct() {
parent::__construct( 'wgLang' );
}
- function __call( $name, $args ) {
+ public function __call( $name, $args ) {
return $this->_call( $name, $args );
}
/**
* @return Language
*/
- function _newObject() {
+ public function _newObject() {
return RequestContext::getMain()->getLanguage();
}
}
diff --git a/includes/TimestampException.php b/includes/TimestampException.php
new file mode 100644
index 00000000..b9c0c35c
--- /dev/null
+++ b/includes/TimestampException.php
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * @since 1.20
+ */
+class TimestampException extends MWException {
+}
diff --git a/includes/Title.php b/includes/Title.php
index 21872e45..74d78ba5 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -1,6 +1,6 @@
<?php
/**
- * Representation a title within %MediaWiki.
+ * Representation of a title within %MediaWiki.
*
* See title.txt
*
@@ -27,14 +27,14 @@
* Optionally may contain an interwiki designation or namespace.
* @note This class can fetch various kinds of data from the database;
* however, it does so inefficiently.
+ * @note Consider using a TitleValue object instead. TitleValue is more lightweight
+ * and does not rely on global state or the database.
*
* @internal documentation reviewed 15 Mar 2010
*/
class Title {
- /** @name Static cache variables */
- // @{
- static private $titleCache = array();
- // @}
+ /** @var MapCacheLRU */
+ static private $titleCache = null;
/**
* Title::newFromText maintains a cache to avoid expensive re-normalization of
@@ -56,50 +56,173 @@ class Title {
*/
// @{
- var $mTextform = ''; // /< Text form (spaces not underscores) of the main part
- var $mUrlform = ''; // /< URL-encoded form of the main part
- var $mDbkeyform = ''; // /< Main part with underscores
- var $mUserCaseDBKey; // /< DB key with the initial letter in the case specified by the user
- var $mNamespace = NS_MAIN; // /< Namespace index, i.e. one of the NS_xxxx constants
- var $mInterwiki = ''; // /< Interwiki prefix (or null string)
- var $mFragment; // /< Title fragment (i.e. the bit after the #)
- var $mArticleID = -1; // /< Article ID, fetched from the link cache on demand
- var $mLatestID = false; // /< ID of most recent revision
- var $mContentModel = false; // /< ID of the page's content model, i.e. one of the CONTENT_MODEL_XXX constants
- private $mEstimateRevisions; // /< Estimated number of revisions; null of not loaded
- var $mRestrictions = array(); // /< Array of groups allowed to edit this article
- var $mOldRestrictions = false;
- var $mCascadeRestriction; ///< Cascade restrictions on this page to included templates and images?
- var $mCascadingRestrictions; // Caching the results of getCascadeProtectionSources
- var $mRestrictionsExpiry = array(); ///< When do the restrictions on this page expire?
- var $mHasCascadingRestrictions; ///< Are cascading restrictions in effect on this page?
- var $mCascadeSources; ///< Where are the cascading restrictions coming from on this page?
- var $mRestrictionsLoaded = false; ///< Boolean for initialisation on demand
- var $mPrefixedText; ///< Text form including namespace/interwiki, initialised on demand
- var $mTitleProtection; ///< Cached value for getTitleProtection (create protection)
- # Don't change the following default, NS_MAIN is hardcoded in several
- # places. See bug 696.
- var $mDefaultNamespace = NS_MAIN; // /< Namespace index when there is no namespace
- # Zero except in {{transclusion}} tags
- var $mWatched = null; // /< Is $wgUser watching this page? null if unfilled, accessed through userIsWatching()
- var $mLength = -1; // /< The page length, 0 for special pages
- var $mRedirect = null; // /< Is the article at this title a redirect?
- var $mNotificationTimestamp = array(); // /< Associative array of user ID -> timestamp/false
- var $mHasSubpage; // /< Whether a page has any subpages
+ /** @var string Text form (spaces not underscores) of the main part */
+ public $mTextform = '';
+
+ /** @var string URL-encoded form of the main part */
+ public $mUrlform = '';
+
+ /** @var string Main part with underscores */
+ public $mDbkeyform = '';
+
+ /** @var string Database key with the initial letter in the case specified by the user */
+ protected $mUserCaseDBKey;
+
+ /** @var int Namespace index, i.e. one of the NS_xxxx constants */
+ public $mNamespace = NS_MAIN;
+
+ /** @var string Interwiki prefix */
+ public $mInterwiki = '';
+
+ /** @var bool Was this Title created from a string with a local interwiki prefix? */
+ private $mLocalInterwiki = false;
+
+ /** @var string Title fragment (i.e. the bit after the #) */
+ public $mFragment = '';
+
+ /** @var int Article ID, fetched from the link cache on demand */
+ public $mArticleID = -1;
+
+ /** @var bool|int ID of most recent revision */
+ protected $mLatestID = false;
+
+ /**
+ * @var bool|string ID of the page's content model, i.e. one of the
+ * CONTENT_MODEL_XXX constants
+ */
+ public $mContentModel = false;
+
+ /** @var int Estimated number of revisions; null of not loaded */
+ private $mEstimateRevisions;
+
+ /** @var array Array of groups allowed to edit this article */
+ public $mRestrictions = array();
+
+ /** @var bool */
+ protected $mOldRestrictions = false;
+
+ /** @var bool Cascade restrictions on this page to included templates and images? */
+ public $mCascadeRestriction;
+
+ /** Caching the results of getCascadeProtectionSources */
+ public $mCascadingRestrictions;
+
+ /** @var array When do the restrictions on this page expire? */
+ protected $mRestrictionsExpiry = array();
+
+ /** @var bool Are cascading restrictions in effect on this page? */
+ protected $mHasCascadingRestrictions;
+
+ /** @var array Where are the cascading restrictions coming from on this page? */
+ public $mCascadeSources;
+
+ /** @var bool Boolean for initialisation on demand */
+ public $mRestrictionsLoaded = false;
+
+ /** @var string Text form including namespace/interwiki, initialised on demand */
+ protected $mPrefixedText = null;
+
+ /** @var mixed Cached value for getTitleProtection (create protection) */
+ public $mTitleProtection;
+
+ /**
+ * @var int Namespace index when there is no namespace. Don't change the
+ * following default, NS_MAIN is hardcoded in several places. See bug 696.
+ * Zero except in {{transclusion}} tags.
+ */
+ public $mDefaultNamespace = NS_MAIN;
+
+ /**
+ * @var bool Is $wgUser watching this page? null if unfilled, accessed
+ * through userIsWatching()
+ */
+ protected $mWatched = null;
+
+ /** @var int The page length, 0 for special pages */
+ protected $mLength = -1;
+
+ /** @var null Is the article at this title a redirect? */
+ public $mRedirect = null;
+
+ /** @var array Associative array of user ID -> timestamp/false */
+ private $mNotificationTimestamp = array();
+
+ /** @var bool Whether a page has any subpages */
+ private $mHasSubpages;
+
+ /** @var bool The (string) language code of the page's language and content code. */
+ private $mPageLanguage = false;
+
+ /** @var string The page language code from the database */
+ private $mDbPageLanguage = null;
+
+ /** @var TitleValue A corresponding TitleValue object */
+ private $mTitleValue = null;
+
+ /** @var bool Would deleting this page be a big deletion? */
+ private $mIsBigDeletion = null;
// @}
/**
- * Constructor
+ * B/C kludge: provide a TitleParser for use by Title.
+ * Ideally, Title would have no methods that need this.
+ * Avoid usage of this singleton by using TitleValue
+ * and the associated services when possible.
+ *
+ * @return TitleParser
*/
- /*protected*/ function __construct() { }
+ private static function getTitleParser() {
+ global $wgContLang, $wgLocalInterwikis;
+
+ static $titleCodec = null;
+ static $titleCodecFingerprint = null;
+
+ // $wgContLang and $wgLocalInterwikis may change (especially while testing),
+ // make sure we are using the right one. To detect changes over the course
+ // of a request, we remember a fingerprint of the config used to create the
+ // codec singleton, and re-create it if the fingerprint doesn't match.
+ $fingerprint = spl_object_hash( $wgContLang ) . '|' . join( '+', $wgLocalInterwikis );
+
+ if ( $fingerprint !== $titleCodecFingerprint ) {
+ $titleCodec = null;
+ }
+
+ if ( !$titleCodec ) {
+ $titleCodec = new MediaWikiTitleCodec(
+ $wgContLang,
+ GenderCache::singleton(),
+ $wgLocalInterwikis
+ );
+ $titleCodecFingerprint = $fingerprint;
+ }
+
+ return $titleCodec;
+ }
+
+ /**
+ * B/C kludge: provide a TitleParser for use by Title.
+ * Ideally, Title would have no methods that need this.
+ * Avoid usage of this singleton by using TitleValue
+ * and the associated services when possible.
+ *
+ * @return TitleFormatter
+ */
+ private static function getTitleFormatter() {
+ //NOTE: we know that getTitleParser() returns a MediaWikiTitleCodec,
+ // which implements TitleFormatter.
+ return self::getTitleParser();
+ }
+
+ function __construct() {
+ }
/**
* Create a new Title from a prefixed DB key
*
- * @param string $key the database key, which has underscores
+ * @param string $key The database key, which has underscores
* instead of spaces, possibly including namespace and
* interwiki prefixes
- * @return Title, or NULL on an error
+ * @return Title|null Title, or null on an error
*/
public static function newFromDBkey( $key ) {
$t = new Title();
@@ -112,23 +235,39 @@ class Title {
}
/**
+ * Create a new Title from a TitleValue
+ *
+ * @param TitleValue $titleValue Assumed to be safe.
+ *
+ * @return Title
+ */
+ public static function newFromTitleValue( TitleValue $titleValue ) {
+ return self::makeTitle(
+ $titleValue->getNamespace(),
+ $titleValue->getText(),
+ $titleValue->getFragment() );
+ }
+
+ /**
* Create a new Title from text, such as what one would find in a link. De-
* codes any HTML entities in the text.
*
- * @param string $text the link text; spaces, prefixes, and an
+ * @param string $text The link text; spaces, prefixes, and an
* initial ':' indicating the main namespace are accepted.
- * @param int $defaultNamespace the namespace to use if none is specified
+ * @param int $defaultNamespace The namespace to use if none is specified
* by a prefix. If you want to force a specific namespace even if
* $text might begin with a namespace prefix, use makeTitle() or
* makeTitleSafe().
* @throws MWException
- * @return Title|null - Title or null on an error.
+ * @return Title|null Title or null on an error.
*/
public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
if ( is_object( $text ) ) {
throw new MWException( 'Title::newFromText given an object' );
}
+ $cache = self::getTitleCache();
+
/**
* Wiki pages often contain multiple links to the same page.
* Title normalization and parsing can become expensive on
@@ -137,8 +276,8 @@ class Title {
*
* In theory these are value objects and won't get changed...
*/
- if ( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
- return Title::$titleCache[$text];
+ if ( $defaultNamespace == NS_MAIN && $cache->has( $text ) ) {
+ return $cache->get( $text );
}
# Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
@@ -146,23 +285,15 @@ class Title {
$t = new Title();
$t->mDbkeyform = str_replace( ' ', '_', $filteredText );
- $t->mDefaultNamespace = $defaultNamespace;
+ $t->mDefaultNamespace = intval( $defaultNamespace );
- static $cachedcount = 0;
if ( $t->secureAndSplit() ) {
if ( $defaultNamespace == NS_MAIN ) {
- if ( $cachedcount >= self::CACHE_MAX ) {
- # Avoid memory leaks on mass operations...
- Title::$titleCache = array();
- $cachedcount = 0;
- }
- $cachedcount++;
- Title::$titleCache[$text] =& $t;
+ $cache->set( $text, $t );
}
return $t;
} else {
- $ret = null;
- return $ret;
+ return null;
}
}
@@ -178,8 +309,8 @@ class Title {
* Create a new Title from URL-encoded text. Ensures that
* the given title's length does not exceed the maximum.
*
- * @param string $url the title, as might be taken from a URL
- * @return Title the new object, or NULL on an error
+ * @param string $url The title, as might be taken from a URL
+ * @return Title|null The new object, or null on an error
*/
public static function newFromURL( $url ) {
$t = new Title();
@@ -200,8 +331,19 @@ class Title {
}
/**
- * Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
- * Uses $wgContentHandlerUseDB to determine whether to include page_content_model.
+ * @return MapCacheLRU
+ */
+ private static function getTitleCache() {
+ if ( self::$titleCache == null ) {
+ self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
+ }
+ return self::$titleCache;
+ }
+
+ /**
+ * Returns a list of fields that are to be selected for initializing Title
+ * objects or LinkCache entries. Uses $wgContentHandlerUseDB to determine
+ * whether to include page_content_model.
*
* @return array
*/
@@ -223,9 +365,9 @@ class Title {
/**
* Create a new Title from an article ID
*
- * @param int $id the page_id corresponding to the Title to create
- * @param int $flags use Title::GAID_FOR_UPDATE to use master
- * @return Title|null the new object, or NULL on an error
+ * @param int $id The page_id corresponding to the Title to create
+ * @param int $flags Use Title::GAID_FOR_UPDATE to use master
+ * @return Title|null The new object, or null on an error
*/
public static function newFromID( $id, $flags = 0 ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
@@ -246,8 +388,8 @@ class Title {
/**
* Make an array of titles from an array of IDs
*
- * @param array $ids of Int Array of IDs
- * @return Array of Titles
+ * @param int[] $ids Array of IDs
+ * @return Title[] Array of Titles
*/
public static function newFromIDs( $ids ) {
if ( !count( $ids ) ) {
@@ -272,8 +414,8 @@ class Title {
/**
* Make a Title object from a DB row
*
- * @param $row Object database row (needs at least page_title,page_namespace)
- * @return Title corresponding Title
+ * @param stdClass $row Object database row (needs at least page_title,page_namespace)
+ * @return Title Corresponding Title
*/
public static function newFromRow( $row ) {
$t = self::makeTitle( $row->page_namespace, $row->page_title );
@@ -285,7 +427,7 @@ class Title {
* Load Title object fields from a DB row.
* If false is given, the title will be treated as non-existing.
*
- * @param $row Object|bool database row
+ * @param stdClass|bool $row Database row
*/
public function loadFromRow( $row ) {
if ( $row ) { // page found
@@ -306,6 +448,9 @@ class Title {
} else {
$this->mContentModel = false; # initialized lazily in getContentModel()
}
+ if ( isset( $row->page_lang ) ) {
+ $this->mDbPageLanguage = (string)$row->page_lang;
+ }
} else { // page not found
$this->mArticleID = 0;
$this->mLength = 0;
@@ -322,11 +467,11 @@ class Title {
* For convenience, spaces are converted to underscores so that
* eg user_text fields can be used directly.
*
- * @param int $ns the namespace of the article
- * @param string $title the unprefixed database key form
- * @param string $fragment the link fragment (after the "#")
- * @param string $interwiki the interwiki prefix
- * @return Title the new object
+ * @param int $ns The namespace of the article
+ * @param string $title The unprefixed database key form
+ * @param string $fragment The link fragment (after the "#")
+ * @param string $interwiki The interwiki prefix
+ * @return Title The new object
*/
public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
$t = new Title();
@@ -346,11 +491,11 @@ class Title {
* The parameters will be checked for validity, which is a bit slower
* than makeTitle() but safer for user-provided data.
*
- * @param int $ns the namespace of the article
- * @param string $title database key form
- * @param string $fragment the link fragment (after the "#")
- * @param string $interwiki interwiki prefix
- * @return Title the new object, or NULL on an error
+ * @param int $ns The namespace of the article
+ * @param string $title Database key form
+ * @param string $fragment The link fragment (after the "#")
+ * @param string $interwiki Interwiki prefix
+ * @return Title The new object, or null on an error
*/
public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
if ( !MWNamespace::exists( $ns ) ) {
@@ -369,7 +514,7 @@ class Title {
/**
* Create a new Title for the Main Page
*
- * @return Title the new object
+ * @return Title The new object
*/
public static function newMainPage() {
$title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
@@ -387,7 +532,7 @@ class Title {
* the redirect table and other checks that don't need full recursion
*
* @param string $text Text with possible redirect
- * @return Title: The corresponding Title
+ * @return Title The corresponding Title
* @deprecated since 1.21, use Content::getRedirectTarget instead.
*/
public static function newFromRedirect( $text ) {
@@ -421,7 +566,7 @@ class Title {
* have been resolved (up to $wgMaxRedirects times)
*
* @param string $text Text with possible redirect
- * @return Array of Titles, with the destination last
+ * @return Title[] Array of Titles, with the destination last
* @deprecated since 1.21, use Content::getRedirectChain instead.
*/
public static function newFromRedirectArray( $text ) {
@@ -434,8 +579,8 @@ class Title {
/**
* Get the prefixed DB key associated with an ID
*
- * @param int $id the page_id of the article
- * @return Title an object representing the article, or NULL if no such article was found
+ * @param int $id The page_id of the article
+ * @return Title|null An object representing the article, or null if no such article was found
*/
public static function nameOf( $id ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -457,7 +602,7 @@ class Title {
/**
* Get a regex character class describing the legal characters in a link
*
- * @return String the list of characters, not delimited
+ * @return string The list of characters, not delimited
*/
public static function legalChars() {
global $wgLegalTitleChars;
@@ -469,7 +614,9 @@ class Title {
* Note that this doesn't pick up many things that could be wrong with titles, but that
* replacing this regex with something valid will make many titles valid.
*
- * @return String regex string
+ * @todo move this into MediaWikiTitleCodec
+ *
+ * @return string Regex string
*/
static function getTitleInvalidRegex() {
static $rxTc = false;
@@ -594,41 +741,13 @@ class Title {
}
/**
- * Get a string representation of a title suitable for
- * including in a search index
- *
- * @param int $ns a namespace index
- * @param string $title text-form main part
- * @return String a stripped-down title string ready for the search index
- */
- public static function indexTitle( $ns, $title ) {
- global $wgContLang;
-
- $lc = SearchEngine::legalSearchChars() . '&#;';
- $t = $wgContLang->normalizeForSearch( $title );
- $t = preg_replace( "/[^{$lc}]+/", ' ', $t );
- $t = $wgContLang->lc( $t );
-
- # Handle 's, s'
- $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t );
- $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t );
-
- $t = preg_replace( "/\\s+/", ' ', $t );
-
- if ( $ns == NS_FILE ) {
- $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
- }
- return trim( $t );
- }
-
- /**
* Make a prefixed DB key from a DB key and a namespace index
*
- * @param int $ns numerical representation of the namespace
- * @param string $title the DB key form the title
+ * @param int $ns Numerical representation of the namespace
+ * @param string $title The DB key form the title
* @param string $fragment The link fragment (after the "#")
* @param string $interwiki The interwiki prefix
- * @return String the prefixed form of the title
+ * @return string The prefixed form of the title
*/
public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
global $wgContLang;
@@ -647,8 +766,8 @@ class Title {
/**
* Escape a text fragment, say from a link, for a URL
*
- * @param string $fragment containing a URL or link fragment (after the "#")
- * @return String: escaped string
+ * @param string $fragment Containing a URL or link fragment (after the "#")
+ * @return string Escaped string
*/
static function escapeFragmentForURL( $fragment ) {
# Note that we don't urlencode the fragment. urlencoded Unicode
@@ -661,10 +780,10 @@ class Title {
/**
* Callback for usort() to do title sorts by (namespace, title)
*
- * @param $a Title
- * @param $b Title
+ * @param Title $a
+ * @param Title $b
*
- * @return Integer: result of string comparison, or namespace comparison
+ * @return int Result of string comparison, or namespace comparison
*/
public static function compare( $a, $b ) {
if ( $a->getNamespace() == $b->getNamespace() ) {
@@ -678,10 +797,10 @@ class Title {
* Determine whether the object refers to a page within
* this project.
*
- * @return Bool TRUE if this is an in-project interwiki link or a wikilink, FALSE otherwise
+ * @return bool True if this is an in-project interwiki link or a wikilink, false otherwise
*/
public function isLocal() {
- if ( $this->mInterwiki != '' ) {
+ if ( $this->isExternal() ) {
$iw = Interwiki::fetch( $this->mInterwiki );
if ( $iw ) {
return $iw->isLocal();
@@ -693,29 +812,40 @@ class Title {
/**
* Is this Title interwiki?
*
- * @return Bool
+ * @return bool
*/
public function isExternal() {
- return ( $this->mInterwiki != '' );
+ return $this->mInterwiki !== '';
}
/**
- * Get the interwiki prefix (or null string)
+ * Get the interwiki prefix
*
- * @return String Interwiki prefix
+ * Use Title::isExternal to check if a interwiki is set
+ *
+ * @return string Interwiki prefix
*/
public function getInterwiki() {
return $this->mInterwiki;
}
/**
+ * Was this a local interwiki link?
+ *
+ * @return bool
+ */
+ public function wasLocalInterwiki() {
+ return $this->mLocalInterwiki;
+ }
+
+ /**
* Determine whether the object refers to a page within
* this project and is transcludable.
*
- * @return Bool TRUE if this is transcludable
+ * @return bool True if this is transcludable
*/
public function isTrans() {
- if ( $this->mInterwiki == '' ) {
+ if ( !$this->isExternal() ) {
return false;
}
@@ -725,10 +855,10 @@ class Title {
/**
* Returns the DB name of the distant wiki which owns the object.
*
- * @return String the DB name
+ * @return string The DB name
*/
public function getTransWikiID() {
- if ( $this->mInterwiki == '' ) {
+ if ( !$this->isExternal() ) {
return false;
}
@@ -736,9 +866,34 @@ class Title {
}
/**
+ * Get a TitleValue object representing this Title.
+ *
+ * @note Not all valid Titles have a corresponding valid TitleValue
+ * (e.g. TitleValues cannot represent page-local links that have a
+ * fragment but no title text).
+ *
+ * @return TitleValue|null
+ */
+ public function getTitleValue() {
+ if ( $this->mTitleValue === null ) {
+ try {
+ $this->mTitleValue = new TitleValue(
+ $this->getNamespace(),
+ $this->getDBkey(),
+ $this->getFragment() );
+ } catch ( InvalidArgumentException $ex ) {
+ wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
+ $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
+ }
+ }
+
+ return $this->mTitleValue;
+ }
+
+ /**
* Get the text form (spaces not underscores) of the main part
*
- * @return String Main part of the title
+ * @return string Main part of the title
*/
public function getText() {
return $this->mTextform;
@@ -747,7 +902,7 @@ class Title {
/**
* Get the URL-encoded form of the main part
*
- * @return String Main part of the title, URL-encoded
+ * @return string Main part of the title, URL-encoded
*/
public function getPartialURL() {
return $this->mUrlform;
@@ -756,7 +911,7 @@ class Title {
/**
* Get the main part with underscores
*
- * @return String: Main part of the title, with underscores
+ * @return string Main part of the title, with underscores
*/
public function getDBkey() {
return $this->mDbkeyform;
@@ -765,16 +920,21 @@ class Title {
/**
* Get the DB key with the initial letter case as specified by the user
*
- * @return String DB key
+ * @return string DB key
*/
function getUserCaseDBKey() {
- return $this->mUserCaseDBKey;
+ if ( !is_null( $this->mUserCaseDBKey ) ) {
+ return $this->mUserCaseDBKey;
+ } else {
+ // If created via makeTitle(), $this->mUserCaseDBKey is not set.
+ return $this->mDbkeyform;
+ }
}
/**
* Get the namespace index, i.e. one of the NS_xxxx constants.
*
- * @return Integer: Namespace index
+ * @return int Namespace index
*/
public function getNamespace() {
return $this->mNamespace;
@@ -784,10 +944,12 @@ class Title {
* Get the page's content model id, see the CONTENT_MODEL_XXX constants.
*
* @throws MWException
- * @return String: Content model id
+ * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @return string Content model id
*/
- public function getContentModel() {
- if ( !$this->mContentModel ) {
+ public function getContentModel( $flags = 0 ) {
+ # Calling getArticleID() loads the field from cache as needed
+ if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
$linkCache = LinkCache::singleton();
$this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
}
@@ -807,7 +969,7 @@ class Title {
* Convenience method for checking a title's content model name
*
* @param string $id The content model ID (use the CONTENT_MODEL_XXX constants).
- * @return Boolean true if $this->getContentModel() == $id
+ * @return bool True if $this->getContentModel() == $id
*/
public function hasContentModel( $id ) {
return $this->getContentModel() == $id;
@@ -816,12 +978,10 @@ class Title {
/**
* Get the namespace text
*
- * @return String: Namespace text
+ * @return string Namespace text
*/
public function getNsText() {
- global $wgContLang;
-
- if ( $this->mInterwiki != '' ) {
+ if ( $this->isExternal() ) {
// This probably shouldn't even happen. ohh man, oh yuck.
// But for interwiki transclusion it sometimes does.
// Shit. Shit shit shit.
@@ -833,19 +993,19 @@ class Title {
}
}
- if ( $wgContLang->needsGenderDistinction() &&
- MWNamespace::hasGenderDistinction( $this->mNamespace ) ) {
- $gender = GenderCache::singleton()->getGenderOf( $this->getText(), __METHOD__ );
- return $wgContLang->getGenderNsText( $this->mNamespace, $gender );
+ try {
+ $formatter = self::getTitleFormatter();
+ return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
+ } catch ( InvalidArgumentException $ex ) {
+ wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
+ return false;
}
-
- return $wgContLang->getNsText( $this->mNamespace );
}
/**
* Get the namespace text of the subject (rather than talk) page
*
- * @return String Namespace text
+ * @return string Namespace text
*/
public function getSubjectNsText() {
global $wgContLang;
@@ -855,7 +1015,7 @@ class Title {
/**
* Get the namespace text of the talk page
*
- * @return String Namespace text
+ * @return string Namespace text
*/
public function getTalkNsText() {
global $wgContLang;
@@ -865,7 +1025,7 @@ class Title {
/**
* Could this title have a corresponding talk page?
*
- * @return Bool TRUE or FALSE
+ * @return bool
*/
public function canTalk() {
return MWNamespace::canTalk( $this->mNamespace );
@@ -874,7 +1034,7 @@ class Title {
/**
* Is this in a namespace that allows actual pages?
*
- * @return Bool
+ * @return bool
* @internal note -- uses hardcoded namespace index instead of constants
*/
public function canExist() {
@@ -884,7 +1044,7 @@ class Title {
/**
* Can this title be added to a user's watchlist?
*
- * @return Bool TRUE or FALSE
+ * @return bool
*/
public function isWatchable() {
return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
@@ -893,7 +1053,7 @@ class Title {
/**
* Returns true if this is a special page.
*
- * @return boolean
+ * @return bool
*/
public function isSpecialPage() {
return $this->getNamespace() == NS_SPECIAL;
@@ -903,7 +1063,7 @@ class Title {
* Returns true if this title resolves to the named special page
*
* @param string $name The special page name
- * @return boolean
+ * @return bool
*/
public function isSpecial( $name ) {
if ( $this->isSpecialPage() ) {
@@ -951,7 +1111,7 @@ class Title {
/**
* Returns true if the title is inside one of the specified namespaces.
*
- * @param ...$namespaces The namespaces to check for
+ * @param int $namespaces,... The namespaces to check for
* @return bool
* @since 1.19
*/
@@ -980,7 +1140,7 @@ class Title {
* This is MUCH simpler than individually testing for equivalence
* against both NS_USER and NS_USER_TALK, and is also forward compatible.
* @since 1.19
- * @param $ns int
+ * @param int $ns
* @return bool
*/
public function hasSubjectNamespace( $ns ) {
@@ -992,7 +1152,7 @@ class Title {
* In other words, is this a content page, for the purposes of calculating
* statistics, etc?
*
- * @return Boolean
+ * @return bool
*/
public function isContentPage() {
return MWNamespace::isContent( $this->getNamespace() );
@@ -1002,10 +1162,10 @@ class Title {
* Would anybody with sufficient privileges be able to move this page?
* Some pages just aren't movable.
*
- * @return Bool TRUE or FALSE
+ * @return bool
*/
public function isMovable() {
- if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) {
+ if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
// Interwiki title or immovable namespace. Hooks don't get to override here
return false;
}
@@ -1023,7 +1183,7 @@ class Title {
* ends up reporting something differently than $title->isMainPage();
*
* @since 1.18
- * @return Bool
+ * @return bool
*/
public function isMainPage() {
return $this->equals( Title::newMainPage() );
@@ -1032,7 +1192,7 @@ class Title {
/**
* Is this a subpage?
*
- * @return Bool
+ * @return bool
*/
public function isSubpage() {
return MWNamespace::hasSubpages( $this->mNamespace )
@@ -1043,7 +1203,7 @@ class Title {
/**
* Is this a conversion table for the LanguageConverter?
*
- * @return Bool
+ * @return bool
*/
public function isConversionTable() {
// @todo ConversionTable should become a separate content model.
@@ -1055,7 +1215,7 @@ class Title {
/**
* Does that page contain wikitext, or it is JS, CSS or whatever?
*
- * @return Bool
+ * @return bool
*/
public function isWikitextPage() {
return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
@@ -1066,19 +1226,22 @@ class Title {
* This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS
* or CONTENT_MODEL_JAVASCRIPT.
*
- * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage() for that!
+ * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage()
+ * for that!
*
- * Note that this method should not return true for pages that contain and show "inactive" CSS or JS.
+ * Note that this method should not return true for pages that contain and
+ * show "inactive" CSS or JS.
*
- * @return Bool
+ * @return bool
*/
public function isCssOrJsPage() {
$isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
&& ( $this->hasContentModel( CONTENT_MODEL_CSS )
|| $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
- #NOTE: this hook is also called in ContentHandler::getDefaultModel. It's called here again to make sure
- # hook functions can force this method to return true even outside the mediawiki namespace.
+ # @note This hook is also called in ContentHandler::getDefaultModel.
+ # It's called here again to make sure hook functions can force this
+ # method to return true even outside the mediawiki namespace.
wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
@@ -1087,7 +1250,7 @@ class Title {
/**
* Is this a .css or .js subpage of a user page?
- * @return Bool
+ * @return bool
*/
public function isCssJsSubpage() {
return ( NS_USER == $this->mNamespace && $this->isSubpage()
@@ -1098,7 +1261,7 @@ class Title {
/**
* Trim down a .css or .js subpage title to get the corresponding skin name
*
- * @return string containing skin name from .css or .js subpage title
+ * @return string Containing skin name from .css or .js subpage title
*/
public function getSkinFromCssJsSubpage() {
$subpage = explode( '/', $this->mTextform );
@@ -1113,7 +1276,7 @@ class Title {
/**
* Is this a .css subpage of a user page?
*
- * @return Bool
+ * @return bool
*/
public function isCssSubpage() {
return ( NS_USER == $this->mNamespace && $this->isSubpage()
@@ -1123,7 +1286,7 @@ class Title {
/**
* Is this a .js subpage of a user page?
*
- * @return Bool
+ * @return bool
*/
public function isJsSubpage() {
return ( NS_USER == $this->mNamespace && $this->isSubpage()
@@ -1133,7 +1296,7 @@ class Title {
/**
* Is this a talk page of some sort?
*
- * @return Bool
+ * @return bool
*/
public function isTalkPage() {
return MWNamespace::isTalk( $this->getNamespace() );
@@ -1142,7 +1305,7 @@ class Title {
/**
* Get a Title object associated with the talk page of this article
*
- * @return Title the object for the talk page
+ * @return Title The object for the talk page
*/
public function getTalkPage() {
return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
@@ -1152,7 +1315,7 @@ class Title {
* Get a title object associated with the subject page of this
* talk page
*
- * @return Title the object for the subject page
+ * @return Title The object for the subject page
*/
public function getSubjectPage() {
// Is this the same title?
@@ -1166,40 +1329,42 @@ class Title {
/**
* Get the default namespace index, for when there is no namespace
*
- * @return Int Default namespace index
+ * @return int Default namespace index
*/
public function getDefaultNamespace() {
return $this->mDefaultNamespace;
}
/**
- * Get title for search index
+ * Get the Title fragment (i.e.\ the bit after the #) in text form
*
- * @return String a stripped-down title string ready for the
- * search index
+ * Use Title::hasFragment to check for a fragment
+ *
+ * @return string Title fragment
*/
- public function getIndexTitle() {
- return Title::indexTitle( $this->mNamespace, $this->mTextform );
+ public function getFragment() {
+ return $this->mFragment;
}
/**
- * Get the Title fragment (i.e.\ the bit after the #) in text form
+ * Check if a Title fragment is set
*
- * @return String Title fragment
+ * @return bool
+ * @since 1.23
*/
- public function getFragment() {
- return $this->mFragment;
+ public function hasFragment() {
+ return $this->mFragment !== '';
}
/**
* Get the fragment in URL form, including the "#" character if there is one
- * @return String Fragment in URL form
+ * @return string Fragment in URL form
*/
public function getFragmentForURL() {
- if ( $this->mFragment == '' ) {
+ if ( !$this->hasFragment() ) {
return '';
} else {
- return '#' . Title::escapeFragmentForURL( $this->mFragment );
+ return '#' . Title::escapeFragmentForURL( $this->getFragment() );
}
}
@@ -1211,7 +1376,7 @@ class Title {
* Deprecated for public use, use Title::makeTitle() with fragment parameter.
* Still in active use privately.
*
- * @param string $fragment text
+ * @param string $fragment Text
*/
public function setFragment( $fragment ) {
$this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
@@ -1221,13 +1386,12 @@ class Title {
* Prefix some arbitrary text with the namespace or interwiki prefix
* of this object
*
- * @param string $name the text
- * @return String the prefixed text
- * @private
+ * @param string $name The text
+ * @return string The prefixed text
*/
private function prefix( $name ) {
$p = '';
- if ( $this->mInterwiki != '' ) {
+ if ( $this->isExternal() ) {
$p = $this->mInterwiki . ':';
}
@@ -1240,7 +1404,7 @@ class Title {
/**
* Get the prefixed database key form
*
- * @return String the prefixed title, with underscores and
+ * @return string The prefixed title, with underscores and
* any interwiki and namespace prefixes
*/
public function getPrefixedDBkey() {
@@ -1253,11 +1417,10 @@ class Title {
* Get the prefixed title with spaces.
* This is the form usually used for display
*
- * @return String the prefixed title, with spaces
+ * @return string The prefixed title, with spaces
*/
public function getPrefixedText() {
- // @todo FIXME: Bad usage of empty() ?
- if ( empty( $this->mPrefixedText ) ) {
+ if ( $this->mPrefixedText === null ) {
$s = $this->prefix( $this->mTextform );
$s = str_replace( '_', ' ', $s );
$this->mPrefixedText = $s;
@@ -1268,7 +1431,7 @@ class Title {
/**
* Return a string representation of this title
*
- * @return String representation of this title
+ * @return string Representation of this title
*/
public function __toString() {
return $this->getPrefixedText();
@@ -1278,12 +1441,12 @@ class Title {
* Get the prefixed title with spaces, plus any fragment
* (part beginning with '#')
*
- * @return String the prefixed title, with spaces and the fragment, including '#'
+ * @return string The prefixed title, with spaces and the fragment, including '#'
*/
public function getFullText() {
$text = $this->getPrefixedText();
- if ( $this->mFragment != '' ) {
- $text .= '#' . $this->mFragment;
+ if ( $this->hasFragment() ) {
+ $text .= '#' . $this->getFragment();
}
return $text;
}
@@ -1297,7 +1460,7 @@ class Title {
* # returns: 'Foo'
* @endcode
*
- * @return String Root name
+ * @return string Root name
* @since 1.20
*/
public function getRootText() {
@@ -1333,7 +1496,7 @@ class Title {
* # returns: 'Foo/Bar'
* @endcode
*
- * @return String Base name
+ * @return string Base name
*/
public function getBaseText() {
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
@@ -1373,7 +1536,7 @@ class Title {
* # returns: "Baz"
* @endcode
*
- * @return String Subpage name
+ * @return string Subpage name
*/
public function getSubpageText() {
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
@@ -1401,21 +1564,9 @@ class Title {
}
/**
- * Get the HTML-escaped displayable text form.
- * Used for the title field in <a> tags.
- *
- * @return String the text, including any prefixes
- * @deprecated since 1.19
- */
- public function getEscapedText() {
- wfDeprecated( __METHOD__, '1.19' );
- return htmlspecialchars( $this->getPrefixedText() );
- }
-
- /**
* Get a URL-encoded form of the subpage text
*
- * @return String URL-encoded subpage name
+ * @return string URL-encoded subpage name
*/
public function getSubpageUrlForm() {
$text = $this->getSubpageText();
@@ -1426,7 +1577,7 @@ class Title {
/**
* Get a URL-encoded title (not an actual URL) including interwiki
*
- * @return String the URL-encoded form
+ * @return string The URL-encoded form
*/
public function getPrefixedURL() {
$s = $this->prefix( $this->mDbkeyform );
@@ -1443,9 +1594,9 @@ class Title {
* and the wfArrayToCgi moved to getLocalURL();
*
* @since 1.19 (r105919)
- * @param $query
- * @param $query2 bool
- * @return String
+ * @param array|string $query
+ * @param bool $query2
+ * @return string
*/
private static function fixUrlQueryArgs( $query, $query2 = false ) {
if ( $query2 !== false ) {
@@ -1478,14 +1629,12 @@ class Title {
* Get a real URL referring to this title, with interwiki link and
* fragment
*
- * See getLocalURL for the arguments.
- *
- * @see self::getLocalURL
+ * @see self::getLocalURL for the arguments.
* @see wfExpandUrl
- * @param $query
- * @param $query2 bool
- * @param $proto Protocol type to use in URL
- * @return String the URL
+ * @param array|string $query
+ * @param bool $query2
+ * @param string $proto Protocol type to use in URL
+ * @return string The URL
*/
public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
$query = self::fixUrlQueryArgs( $query, $query2 );
@@ -1506,21 +1655,27 @@ class Title {
}
/**
- * Get a URL with no fragment or server name. If this page is generated
- * with action=render, $wgServer is prepended.
+ * Get a URL with no fragment or server name (relative URL) from a Title object.
+ * If this page is generated with action=render, however,
+ * $wgServer is prepended to make an absolute URL.
+ *
+ * @see self::getFullURL to always get an absolute URL.
+ * @see self::getLinkURL to always get a URL that's the simplest URL that will be
+ * valid to link, locally, to the current Title.
+ * @see self::newFromText to produce a Title object.
*
- * @param string|array $query an optional query string,
+ * @param string|array $query An optional query string,
* not used for interwiki links. Can be specified as an associative array as well,
* e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped).
* Some query patterns will trigger various shorturl path replacements.
- * @param $query2 Mixed: An optional secondary query array. This one MUST
+ * @param array $query2 An optional secondary query array. This one MUST
* be an array. If a string is passed it will be interpreted as a deprecated
* variant argument and urlencoded into a variant= argument.
* This second query argument will be added to the $query
* The second parameter is deprecated since 1.19. Pass it as a key,value
* pair in the first parameter array instead.
*
- * @return String the URL
+ * @return string String of the URL.
*/
public function getLocalURL( $query = '', $query2 = false ) {
global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
@@ -1547,9 +1702,9 @@ class Title {
$url = false;
$matches = array();
- if ( !empty( $wgActionPaths ) &&
- preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
- {
+ if ( !empty( $wgActionPaths )
+ && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
+ ) {
$action = urldecode( $matches[2] );
if ( isset( $wgActionPaths[$action] ) ) {
$query = $matches[1];
@@ -1563,12 +1718,12 @@ class Title {
}
}
- if ( $url === false &&
- $wgVariantArticlePath &&
- $wgContLang->getCode() === $this->getPageLanguage()->getCode() &&
- $this->getPageLanguage()->hasVariants() &&
- preg_match( '/^variant=([^&]*)$/', $query, $matches ) )
- {
+ if ( $url === false
+ && $wgVariantArticlePath
+ && $wgContLang->getCode() === $this->getPageLanguage()->getCode()
+ && $this->getPageLanguage()->hasVariants()
+ && preg_match( '/^variant=([^&]*)$/', $query, $matches )
+ ) {
$variant = urldecode( $matches[1] );
if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
// Only do the variant replacement if the given variant is a valid
@@ -1608,19 +1763,17 @@ class Title {
* The result obviously should not be URL-escaped, but does need to be
* HTML-escaped if it's being output in HTML.
*
- * See getLocalURL for the arguments.
- *
- * @param $query
- * @param $query2 bool
- * @param $proto Protocol to use; setting this will cause a full URL to be used
- * @see self::getLocalURL
- * @return String the URL
+ * @param array $query
+ * @param bool $query2
+ * @param string $proto Protocol to use; setting this will cause a full URL to be used
+ * @see self::getLocalURL for the arguments.
+ * @return string The URL
*/
public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
wfProfileIn( __METHOD__ );
if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
$ret = $this->getFullURL( $query, $query2, $proto );
- } elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
+ } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
$ret = $this->getFragmentForURL();
} else {
$ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
@@ -1630,38 +1783,6 @@ class Title {
}
/**
- * Get an HTML-escaped version of the URL form, suitable for
- * using in a link, without a server name or fragment
- *
- * See getLocalURL for the arguments.
- *
- * @see self::getLocalURL
- * @param $query string
- * @param $query2 bool|string
- * @return String the URL
- * @deprecated since 1.19
- */
- public function escapeLocalURL( $query = '', $query2 = false ) {
- wfDeprecated( __METHOD__, '1.19' );
- return htmlspecialchars( $this->getLocalURL( $query, $query2 ) );
- }
-
- /**
- * Get an HTML-escaped version of the URL form, suitable for
- * using in a link, including the server name and fragment
- *
- * See getLocalURL for the arguments.
- *
- * @see self::getLocalURL
- * @return String the URL
- * @deprecated since 1.19
- */
- public function escapeFullURL( $query = '', $query2 = false ) {
- wfDeprecated( __METHOD__, '1.19' );
- return htmlspecialchars( $this->getFullURL( $query, $query2 ) );
- }
-
- /**
* Get the URL form for an internal link.
* - Used in various Squid-related code, in case we have a different
* internal hostname for the server from the exposed one.
@@ -1670,10 +1791,8 @@ class Title {
* if $wgInternalServer is not set. If the server variable used is
* protocol-relative, the URL will be expanded to http://
*
- * See getLocalURL for the arguments.
- *
- * @see self::getLocalURL
- * @return String the URL
+ * @see self::getLocalURL for the arguments.
+ * @return string The URL
*/
public function getInternalURL( $query = '', $query2 = false ) {
global $wgInternalServer, $wgServer;
@@ -1691,9 +1810,7 @@ class Title {
*
* NOTE: Unlike getInternalURL(), the canonical URL includes the fragment
*
- * See getLocalURL for the arguments.
- *
- * @see self::getLocalURL
+ * @see self::getLocalURL for the arguments.
* @return string The URL
* @since 1.18
*/
@@ -1705,28 +1822,12 @@ class Title {
}
/**
- * HTML-escaped version of getCanonicalURL()
- *
- * See getLocalURL for the arguments.
- *
- * @see self::getLocalURL
- * @since 1.18
- * @return string
- * @deprecated since 1.19
- */
- public function escapeCanonicalURL( $query = '', $query2 = false ) {
- wfDeprecated( __METHOD__, '1.19' );
- return htmlspecialchars( $this->getCanonicalURL( $query, $query2 ) );
- }
-
- /**
* Get the edit URL for this Title
*
- * @return String the URL, or a null string if this is an
- * interwiki link
+ * @return string The URL, or a null string if this is an interwiki link
*/
public function getEditURL() {
- if ( $this->mInterwiki != '' ) {
+ if ( $this->isExternal() ) {
return '';
}
$s = $this->getLocalURL( 'action=edit' );
@@ -1737,8 +1838,8 @@ class Title {
/**
* Is $wgUser watching this page?
*
- * @deprecated in 1.20; use User::isWatched() instead.
- * @return Bool
+ * @deprecated since 1.20; use User::isWatched() instead.
+ * @return bool
*/
public function userIsWatching() {
global $wgUser;
@@ -1754,18 +1855,6 @@ class Title {
}
/**
- * Can $wgUser read this page?
- *
- * @deprecated in 1.19; use userCan(), quickUserCan() or getUserPermissionsErrors() instead
- * @return Bool
- * @todo fold these checks into userCan()
- */
- public function userCanRead() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->userCan( 'read' );
- }
-
- /**
* Can $user perform $action on this page?
* This skips potentially expensive cascading permission checks
* as well as avoids expensive error formatting
@@ -1775,10 +1864,9 @@ class Title {
*
* May provide false positives, but should never provide a false negative.
*
- * @param string $action action that permission needs to be checked for
- * @param $user User to check (since 1.19); $wgUser will be used if not
- * provided.
- * @return Bool
+ * @param string $action Action that permission needs to be checked for
+ * @param User $user User to check (since 1.19); $wgUser will be used if not provided.
+ * @return bool
*/
public function quickUserCan( $action, $user = null ) {
return $this->userCan( $action, $user, false );
@@ -1787,19 +1875,21 @@ class Title {
/**
* Can $user perform $action on this page?
*
- * @param string $action action that permission needs to be checked for
- * @param $user User to check (since 1.19); $wgUser will be used if not
+ * @param string $action Action that permission needs to be checked for
+ * @param User $user User to check (since 1.19); $wgUser will be used if not
* provided.
* @param bool $doExpensiveQueries Set this to false to avoid doing
* unnecessary queries.
- * @return Bool
+ * @return bool
*/
public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
if ( !$user instanceof User ) {
global $wgUser;
$user = $wgUser;
}
- return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries, true ) );
+
+ return !count( $this->getUserPermissionsErrorsInternal(
+ $action, $user, $doExpensiveQueries, true ) );
}
/**
@@ -1807,15 +1897,17 @@ class Title {
*
* @todo FIXME: This *does not* check throttles (User::pingLimiter()).
*
- * @param string $action action that permission needs to be checked for
- * @param $user User to check
+ * @param string $action Action that permission needs to be checked for
+ * @param User $user User to check
* @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary
* queries by skipping checks for cascading protections and user blocks.
- * @param array $ignoreErrors of Strings Set this to a list of message keys
+ * @param array $ignoreErrors Array of Strings Set this to a list of message keys
* whose corresponding errors may be ignored.
- * @return Array of arguments to wfMessage to explain permissions problems.
+ * @return array Array of arguments to wfMessage to explain permissions problems.
*/
- public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
+ public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true,
+ $ignoreErrors = array()
+ ) {
$errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
// Remove the errors being ignored.
@@ -1833,16 +1925,20 @@ class Title {
/**
* Permissions checks that fail most often, and which are easiest to test.
*
- * @param string $action the action to check
- * @param $user User user to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
- private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
- if ( !wfRunHooks( 'TitleQuickPermissions', array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) ) ) {
+ private function checkQuickPermissions( $action, $user, $errors,
+ $doExpensiveQueries, $short
+ ) {
+ if ( !wfRunHooks( 'TitleQuickPermissions',
+ array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) )
+ ) {
return $errors;
}
@@ -1865,6 +1961,11 @@ class Title {
$errors[] = array( 'movenotallowedfile' );
}
+ // Check if user is allowed to move category pages if it's a category page
+ if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
+ $errors[] = array( 'cant-move-category-page' );
+ }
+
if ( !$user->isAllowed( 'move' ) ) {
// User can't move anything
$userCanMove = User::groupHasPermission( 'user', 'move' );
@@ -1884,6 +1985,10 @@ class Title {
&& $this->mNamespace == NS_USER && !$this->isSubpage() ) {
// Show user page-specific message only if the user can move other pages
$errors[] = array( 'cant-move-to-user-page' );
+ } elseif ( !$user->isAllowed( 'move-categorypages' )
+ && $this->mNamespace == NS_CATEGORY ) {
+ // Show category page-specific message only if the user can move other pages
+ $errors[] = array( 'cant-move-to-category-page' );
}
} elseif ( !$user->isAllowed( $action ) ) {
$errors[] = $this->missingPermissionError( $action, $short );
@@ -1895,10 +2000,10 @@ class Title {
/**
* Add the resulting error code to the errors array
*
- * @param array $errors list of current errors
- * @param $result Mixed result of errors
+ * @param array $errors List of current errors
+ * @param array $result Result of errors
*
- * @return Array list of errors
+ * @return array List of errors
*/
private function resultToError( $errors, $result ) {
if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
@@ -1920,13 +2025,13 @@ class Title {
/**
* Check various permission hooks
*
- * @param string $action the action to check
- * @param $user User user to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
// Use getUserPermissionsErrors instead
@@ -1953,15 +2058,17 @@ class Title {
/**
* Check permissions on special pages & namespaces
*
- * @param string $action the action to check
- * @param $user User user to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
- private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkSpecialsAndNSPermissions( $action, $user, $errors,
+ $doExpensiveQueries, $short
+ ) {
# Only 'createaccount' can be performed on special pages,
# which don't actually exist in the DB.
if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
@@ -1973,7 +2080,7 @@ class Title {
$ns = $this->mNamespace == NS_MAIN ?
wfMessage( 'nstab-main' )->text() : $this->getNsText();
$errors[] = $this->mNamespace == NS_MEDIAWIKI ?
- array( 'protectedinterface' ) : array( 'namespaceprotected', $ns );
+ array( 'protectedinterface', $action ) : array( 'namespaceprotected', $ns, $action );
}
return $errors;
@@ -1982,13 +2089,13 @@ class Title {
/**
* Check CSS/JS sub-page permissions
*
- * @param string $action the action to check
- * @param $user User user to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
# Protect css/js subpages of user pages
@@ -1997,15 +2104,15 @@ class Title {
if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
- $errors[] = array( 'mycustomcssprotected' );
+ $errors[] = array( 'mycustomcssprotected', $action );
} elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
- $errors[] = array( 'mycustomjsprotected' );
+ $errors[] = array( 'mycustomjsprotected', $action );
}
} else {
if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
- $errors[] = array( 'customcssprotected' );
+ $errors[] = array( 'customcssprotected', $action );
} elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
- $errors[] = array( 'customjsprotected' );
+ $errors[] = array( 'customjsprotected', $action );
}
}
}
@@ -2018,13 +2125,13 @@ class Title {
* page. The user must possess all required rights for this
* action.
*
- * @param string $action the action to check
- * @param $user User user to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
foreach ( $this->getRestrictions( $action ) as $right ) {
@@ -2040,9 +2147,9 @@ class Title {
continue;
}
if ( !$user->isAllowed( $right ) ) {
- $errors[] = array( 'protectedpagetext', $right );
+ $errors[] = array( 'protectedpagetext', $right, $action );
} elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
- $errors[] = array( 'protectedpagetext', 'protect' );
+ $errors[] = array( 'protectedpagetext', 'protect', $action );
}
}
@@ -2052,15 +2159,17 @@ class Title {
/**
* Check restrictions on cascading pages.
*
- * @param string $action the action to check
- * @param $user User to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
- private function checkCascadingSourcesRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkCascadingSourcesRestrictions( $action, $user, $errors,
+ $doExpensiveQueries, $short
+ ) {
if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
# We /could/ use the protection level on the source page, but it's
# fairly ugly as we have to establish a precedence hierarchy for pages
@@ -2087,7 +2196,7 @@ class Title {
foreach ( $cascadingSources as $page ) {
$pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
}
- $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
+ $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages, $action );
}
}
}
@@ -2099,19 +2208,23 @@ class Title {
/**
* Check action permissions not already checked in checkQuickPermissions
*
- * @param string $action the action to check
- * @param $user User to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
- private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkActionPermissions( $action, $user, $errors,
+ $doExpensiveQueries, $short
+ ) {
global $wgDeleteRevisionsLimit, $wgLang;
if ( $action == 'protect' ) {
- if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $doExpensiveQueries, true ) ) ) {
+ if ( count( $this->getUserPermissionsErrorsInternal( 'edit',
+ $user, $doExpensiveQueries, true ) )
+ ) {
// If they can't edit, they shouldn't protect.
$errors[] = array( 'protect-cantedit' );
}
@@ -2124,10 +2237,14 @@ class Title {
if ( $title_protection['pt_create_perm'] == 'autoconfirmed' ) {
$title_protection['pt_create_perm'] = 'editsemiprotected'; // B/C
}
- if ( $title_protection['pt_create_perm'] == '' ||
- !$user->isAllowed( $title_protection['pt_create_perm'] ) )
- {
- $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
+ if ( $title_protection['pt_create_perm'] == ''
+ || !$user->isAllowed( $title_protection['pt_create_perm'] )
+ ) {
+ $errors[] = array(
+ 'titleprotected',
+ User::whoIs( $title_protection['pt_user'] ),
+ $title_protection['pt_reason']
+ );
}
}
} elseif ( $action == 'move' ) {
@@ -2146,9 +2263,19 @@ class Title {
$errors[] = array( 'immobile-target-page' );
}
} elseif ( $action == 'delete' ) {
+ $tempErrors = $this->checkPageRestrictions( 'edit',
+ $user, array(), $doExpensiveQueries, true );
+ if ( !$tempErrors ) {
+ $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
+ $user, $tempErrors, $doExpensiveQueries, true );
+ }
+ if ( $tempErrors ) {
+ // If protection keeps them from editing, they shouldn't be able to delete.
+ $errors[] = array( 'deleteprotected' );
+ }
if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
- && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion() )
- {
+ && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
+ ) {
$errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
}
}
@@ -2158,13 +2285,13 @@ class Title {
/**
* Check that the user isn't blocked from editing.
*
- * @param string $action the action to check
- * @param $user User to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
// Account creation blocks handled at userlogin.
@@ -2193,13 +2320,13 @@ class Title {
/**
* Check that the user is allowed to read this page.
*
- * @param string $action the action to check
- * @param $user User to check
- * @param array $errors list of current errors
- * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
- * @param $short Boolean short circuit on first error
+ * @param string $action The action to check
+ * @param User $user User to check
+ * @param array $errors List of current errors
+ * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param bool $short Short circuit on first error
*
- * @return Array list of errors
+ * @return array List of errors
*/
private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
global $wgWhitelistRead, $wgWhitelistReadRegexp;
@@ -2272,9 +2399,9 @@ class Title {
* Get a description array when the user doesn't have the right to perform
* $action (i.e. when User::isAllowed() returns false)
*
- * @param string $action the action to check
- * @param $short Boolean short circuit on first error
- * @return Array list of errors
+ * @param string $action The action to check
+ * @param bool $short Short circuit on first error
+ * @return array List of errors
*/
private function missingPermissionError( $action, $short ) {
// We avoid expensive display logic for quickUserCan's and such
@@ -2302,13 +2429,15 @@ class Title {
* which checks ONLY that previously checked by userCan (i.e. it leaves out
* checks on wfReadOnly() and blocks)
*
- * @param string $action action that permission needs to be checked for
- * @param $user User to check
+ * @param string $action Action that permission needs to be checked for
+ * @param User $user User to check
* @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary queries.
* @param bool $short Set this to true to stop after the first permission error.
- * @return Array of arrays of the arguments to wfMessage to explain permissions problems.
+ * @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
*/
- protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
+ protected function getUserPermissionsErrorsInternal( $action, $user,
+ $doExpensiveQueries = true, $short = false
+ ) {
wfProfileIn( __METHOD__ );
# Read has special handling
@@ -2317,6 +2446,19 @@ class Title {
'checkPermissionHooks',
'checkReadPermissions',
);
+ # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
+ # here as it will lead to duplicate error messages. This is okay to do
+ # since anywhere that checks for create will also check for edit, and
+ # those checks are called for edit.
+ } elseif ( $action == 'create' ) {
+ $checks = array(
+ 'checkQuickPermissions',
+ 'checkPermissionHooks',
+ 'checkPageRestrictions',
+ 'checkCascadingSourcesRestrictions',
+ 'checkActionPermissions',
+ 'checkUserBlock'
+ );
} else {
$checks = array(
'checkQuickPermissions',
@@ -2364,7 +2506,7 @@ class Title {
/**
* Returns restriction types for the current Title
*
- * @return array applicable restriction types
+ * @return array Applicable restriction types
*/
public function getRestrictionTypes() {
if ( $this->isSpecialPage() ) {
@@ -2390,7 +2532,7 @@ class Title {
* Is this title subject to title protection?
* Title protection is the one applied against creation of such title.
*
- * @return Mixed An associative array representing any existent title
+ * @return array|bool An associative array representing any existent title
* protection, or false if there's none.
*/
private function getTitleProtection() {
@@ -2404,7 +2546,7 @@ class Title {
return false;
}
- if ( !isset( $this->mTitleProtection ) ) {
+ if ( $this->mTitleProtection === null ) {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
'protected_titles',
@@ -2420,30 +2562,6 @@ class Title {
}
/**
- * Update the title protection status
- *
- * @deprecated in 1.19; use WikiPage::doUpdateRestrictions() instead.
- * @param $create_perm String Permission required for creation
- * @param string $reason Reason for protection
- * @param string $expiry Expiry timestamp
- * @return boolean true
- */
- public function updateTitleProtection( $create_perm, $reason, $expiry ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- global $wgUser;
-
- $limit = array( 'create' => $create_perm );
- $expiry = array( 'create' => $expiry );
-
- $page = WikiPage::factory( $this );
- $cascade = false;
- $status = $page->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $wgUser );
-
- return $status->isOK();
- }
-
- /**
* Remove any title protection due to page existing
*/
public function deleteTitleProtection() {
@@ -2462,7 +2580,7 @@ class Title {
* in $wgSemiprotectedRestrictionLevels?
*
* @param string $action Action to check (default: edit)
- * @return Bool
+ * @return bool
*/
public function isSemiProtected( $action = 'edit' ) {
global $wgSemiprotectedRestrictionLevels;
@@ -2488,9 +2606,9 @@ class Title {
/**
* Does the title correspond to a protected article?
*
- * @param string $action the action the page is protected from,
+ * @param string $action The action the page is protected from,
* by default checks all actions.
- * @return Bool
+ * @return bool
*/
public function isProtected( $action = '' ) {
global $wgRestrictionLevels;
@@ -2521,8 +2639,8 @@ class Title {
* Determines if $user is unable to edit this page because it has been protected
* by $wgNamespaceProtection.
*
- * @param $user User object to check permissions
- * @return Bool
+ * @param User $user User object to check permissions
+ * @return bool
*/
public function isNamespaceProtected( User $user ) {
global $wgNamespaceProtection;
@@ -2540,7 +2658,7 @@ class Title {
/**
* Cascading protection: Return true if cascading restrictions apply to this page, false if not.
*
- * @return Bool If the page is subject to cascading restrictions.
+ * @return bool If the page is subject to cascading restrictions.
*/
public function isCascadeProtected() {
list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
@@ -2548,22 +2666,38 @@ class Title {
}
/**
+ * Determines whether cascading protection sources have already been loaded from
+ * the database.
+ *
+ * @param bool $getPages True to check if the pages are loaded, or false to check
+ * if the status is loaded.
+ * @return bool Whether or not the specified information has been loaded
+ * @since 1.23
+ */
+ public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
+ return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
+ }
+
+ /**
* Cascading protection: Get the source of any cascading restrictions on this page.
*
* @param bool $getPages Whether or not to retrieve the actual pages
- * that the restrictions have come from.
- * @return Mixed Array of Title objects of the pages from which cascading restrictions
- * have come, false for none, or true if such restrictions exist, but $getPages
- * was not set. The restriction array is an array of each type, each of which
- * contains a array of unique groups.
+ * that the restrictions have come from and the actual restrictions
+ * themselves.
+ * @return array Two elements: First is an array of Title objects of the
+ * pages from which cascading restrictions have come, false for
+ * none, or true if such restrictions exist but $getPages was not
+ * set. Second is an array like that returned by
+ * Title::getAllRestrictions(), or an empty array if $getPages is
+ * false.
*/
public function getCascadeProtectionSources( $getPages = true ) {
global $wgContLang;
$pagerestrictions = array();
- if ( isset( $this->mCascadeSources ) && $getPages ) {
+ if ( $this->mCascadeSources !== null && $getPages ) {
return array( $this->mCascadeSources, $this->mCascadingRestrictions );
- } elseif ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) {
+ } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
return array( $this->mHasCascadingRestrictions, $pagerestrictions );
}
@@ -2648,10 +2782,22 @@ class Title {
}
/**
+ * Accessor for mRestrictionsLoaded
+ *
+ * @return bool Whether or not the page's restrictions have already been
+ * loaded from the database
+ * @since 1.23
+ */
+ public function areRestrictionsLoaded() {
+ return $this->mRestrictionsLoaded;
+ }
+
+ /**
* Accessor/initialisation for mRestrictions
*
- * @param string $action action that permission needs to be checked for
- * @return Array of Strings the array of groups allowed to edit this article
+ * @param string $action Action that permission needs to be checked for
+ * @return array Restriction levels needed to take the action. All levels
+ * are required.
*/
public function getRestrictions( $action ) {
if ( !$this->mRestrictionsLoaded ) {
@@ -2663,10 +2809,24 @@ class Title {
}
/**
+ * Accessor/initialisation for mRestrictions
+ *
+ * @return array Keys are actions, values are arrays as returned by
+ * Title::getRestrictions()
+ * @since 1.23
+ */
+ public function getAllRestrictions() {
+ if ( !$this->mRestrictionsLoaded ) {
+ $this->loadRestrictions();
+ }
+ return $this->mRestrictions;
+ }
+
+ /**
* Get the expiry time for the restriction against a given action
*
- * @param $action
- * @return String|Bool 14-char timestamp, or 'infinity' if the page is protected forever
+ * @param string $action
+ * @return string|bool 14-char timestamp, or 'infinity' if the page is protected forever
* or not protected at all, or false if the action is not recognised.
*/
public function getRestrictionExpiry( $action ) {
@@ -2679,7 +2839,7 @@ class Title {
/**
* Returns cascading restrictions for the current article
*
- * @return Boolean
+ * @return bool
*/
function areRestrictionsCascading() {
if ( !$this->mRestrictionsLoaded ) {
@@ -2692,8 +2852,8 @@ class Title {
/**
* Loads a string into mRestrictions array
*
- * @param $res Resource restrictions as an SQL result.
- * @param string $oldFashionedRestrictions comma-separated list of page
+ * @param ResultWrapper $res Resource restrictions as an SQL result.
+ * @param string $oldFashionedRestrictions Comma-separated list of page
* restrictions from page table (pre 1.10)
*/
private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
@@ -2711,9 +2871,9 @@ class Title {
* and page_restrictions table for this existing page.
* Public for usage by LiquidThreads.
*
- * @param array $rows of db result objects
- * @param string $oldFashionedRestrictions comma-separated list of page
- * restrictions from page table (pre 1.10)
+ * @param array $rows Array of db result objects
+ * @param string $oldFashionedRestrictions Comma-separated list of page
+ * restrictions from page table (pre 1.10)
*/
public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
global $wgContLang;
@@ -2795,8 +2955,8 @@ class Title {
/**
* Load restrictions from the page_restrictions table
*
- * @param string $oldFashionedRestrictions comma-separated list of page
- * restrictions from page table (pre 1.10)
+ * @param string $oldFashionedRestrictions Comma-separated list of page
+ * restrictions from page table (pre 1.10)
*/
public function loadRestrictions( $oldFashionedRestrictions = null ) {
global $wgContLang;
@@ -2854,7 +3014,7 @@ class Title {
$method = __METHOD__;
$dbw = wfGetDB( DB_MASTER );
- $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
+ $dbw->onTransactionIdle( function () use ( $dbw, $method ) {
$dbw->delete(
'page_restrictions',
array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
@@ -2871,7 +3031,7 @@ class Title {
/**
* Does this have subpages? (Warning, usually requires an extra DB query.)
*
- * @return Bool
+ * @return bool
*/
public function hasSubpages() {
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
@@ -2883,22 +3043,22 @@ class Title {
# alone to cache the result. There's no point in having it hanging
# around uninitialized in every Title object; therefore we only add it
# if needed and don't declare it statically.
- if ( isset( $this->mHasSubpages ) ) {
- return $this->mHasSubpages;
+ if ( $this->mHasSubpages === null ) {
+ $this->mHasSubpages = false;
+ $subpages = $this->getSubpages( 1 );
+ if ( $subpages instanceof TitleArray ) {
+ $this->mHasSubpages = (bool)$subpages->count();
+ }
}
- $subpages = $this->getSubpages( 1 );
- if ( $subpages instanceof TitleArray ) {
- return $this->mHasSubpages = (bool)$subpages->count();
- }
- return $this->mHasSubpages = false;
+ return $this->mHasSubpages;
}
/**
* Get all subpages of this page.
*
- * @param int $limit maximum number of subpages to fetch; -1 for no limit
- * @return mixed TitleArray, or empty array if this page's namespace
+ * @param int $limit Maximum number of subpages to fetch; -1 for no limit
+ * @return TitleArray|array TitleArray, or empty array if this page's namespace
* doesn't allow subpages
*/
public function getSubpages( $limit = -1 ) {
@@ -2913,7 +3073,7 @@ class Title {
if ( $limit > -1 ) {
$options['LIMIT'] = $limit;
}
- return $this->mSubpages = TitleArray::newFromResult(
+ $this->mSubpages = TitleArray::newFromResult(
$dbr->select( 'page',
array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
$conds,
@@ -2921,12 +3081,13 @@ class Title {
$options
)
);
+ return $this->mSubpages;
}
/**
* Is there a version of this page in the deletion archive?
*
- * @return Int the number of archived revisions
+ * @return int The number of archived revisions
*/
public function isDeleted() {
if ( $this->getNamespace() < 0 ) {
@@ -2951,7 +3112,7 @@ class Title {
/**
* Is there a version of this page in the deletion archive?
*
- * @return Boolean
+ * @return bool
*/
public function isDeletedQuick() {
if ( $this->getNamespace() < 0 ) {
@@ -2975,13 +3136,14 @@ class Title {
* Get the article ID for this Title from the link cache,
* adding it if necessary
*
- * @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select
+ * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select
* for update
- * @return Int the ID
+ * @return int The ID
*/
public function getArticleID( $flags = 0 ) {
if ( $this->getNamespace() < 0 ) {
- return $this->mArticleID = 0;
+ $this->mArticleID = 0;
+ return $this->mArticleID;
}
$linkCache = LinkCache::singleton();
if ( $flags & self::GAID_FOR_UPDATE ) {
@@ -3001,8 +3163,8 @@ class Title {
* Is this an article that is a redirect page?
* Uses link cache, adding it if necessary
*
- * @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select for update
- * @return Bool
+ * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @return bool
*/
public function isRedirect( $flags = 0 ) {
if ( !is_null( $this->mRedirect ) ) {
@@ -3010,7 +3172,8 @@ class Title {
}
# Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
- return $this->mRedirect = false;
+ $this->mRedirect = false;
+ return $this->mRedirect;
}
$linkCache = LinkCache::singleton();
@@ -3022,7 +3185,8 @@ class Title {
# LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
# set, then LinkCache will definitely be up to date here, since getArticleID() forces
# LinkCache to refresh its data from the master.
- return $this->mRedirect = false;
+ $this->mRedirect = false;
+ return $this->mRedirect;
}
$this->mRedirect = (bool)$cached;
@@ -3034,8 +3198,8 @@ class Title {
* What is the length of this page?
* Uses link cache, adding it if necessary
*
- * @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select for update
- * @return Int
+ * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @return int
*/
public function getLength( $flags = 0 ) {
if ( $this->mLength != -1 ) {
@@ -3043,13 +3207,15 @@ class Title {
}
# Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
- return $this->mLength = 0;
+ $this->mLength = 0;
+ return $this->mLength;
}
$linkCache = LinkCache::singleton();
$cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
if ( $cached === null ) {
# Trust LinkCache's state over our own, as for isRedirect()
- return $this->mLength = 0;
+ $this->mLength = 0;
+ return $this->mLength;
}
$this->mLength = intval( $cached );
@@ -3060,8 +3226,8 @@ class Title {
/**
* What is the page_latest field for this page?
*
- * @param int $flags a bit field; may be Title::GAID_FOR_UPDATE to select for update
- * @return Int or 0 if the page doesn't exist
+ * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @return int Int or 0 if the page doesn't exist
*/
public function getLatestRevID( $flags = 0 ) {
if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
@@ -3069,14 +3235,16 @@ class Title {
}
# Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
- return $this->mLatestID = 0;
+ $this->mLatestID = 0;
+ return $this->mLatestID;
}
$linkCache = LinkCache::singleton();
$linkCache->addLinkObj( $this );
$cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
if ( $cached === null ) {
# Trust LinkCache's state over our own, as for isRedirect()
- return $this->mLatestID = 0;
+ $this->mLatestID = 0;
+ return $this->mLatestID;
}
$this->mLatestID = intval( $cached );
@@ -3092,7 +3260,7 @@ class Title {
* loading of the new page_id. It's also called from
* WikiPage::doDeleteArticleReal()
*
- * @param int $newid the new Article ID
+ * @param int $newid The new Article ID
*/
public function resetArticleID( $newid ) {
$linkCache = LinkCache::singleton();
@@ -3110,14 +3278,17 @@ class Title {
$this->mLatestID = false;
$this->mContentModel = false;
$this->mEstimateRevisions = null;
+ $this->mPageLanguage = false;
+ $this->mDbPageLanguage = null;
+ $this->mIsBigDeletion = null;
}
/**
* Capitalize a text string for a title if it belongs to a namespace that capitalizes
*
- * @param string $text containing title to capitalize
- * @param int $ns namespace index, defaults to NS_MAIN
- * @return String containing capitalized title
+ * @param string $text Containing title to capitalize
+ * @param int $ns Namespace index, defaults to NS_MAIN
+ * @return string Containing capitalized title
*/
public static function capitalize( $text, $ns = NS_MAIN ) {
global $wgContLang;
@@ -3138,195 +3309,41 @@ class Title {
* namespace prefixes, sets the other forms, and canonicalizes
* everything.
*
- * @return Bool true on success
+ * @return bool True on success
*/
private function secureAndSplit() {
- global $wgContLang, $wgLocalInterwiki;
-
# Initialisation
- $this->mInterwiki = $this->mFragment = '';
+ $this->mInterwiki = '';
+ $this->mFragment = '';
$this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
$dbkey = $this->mDbkeyform;
- # Strip Unicode bidi override characters.
- # Sometimes they slip into cut-n-pasted page titles, where the
- # override chars get included in list displays.
- $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
-
- # Clean up whitespace
- # Note: use of the /u option on preg_replace here will cause
- # input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
- # conveniently disabling them.
- $dbkey = preg_replace( '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u', '_', $dbkey );
- $dbkey = trim( $dbkey, '_' );
-
- if ( $dbkey == '' ) {
- return false;
- }
-
- if ( strpos( $dbkey, UTF8_REPLACEMENT ) !== false ) {
- # Contained illegal UTF-8 sequences or forbidden Unicode chars.
+ try {
+ // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
+ // the parsing code with Title, while avoiding massive refactoring.
+ // @todo: get rid of secureAndSplit, refactor parsing code.
+ $titleParser = self::getTitleParser();
+ $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
+ } catch ( MalformedTitleException $ex ) {
return false;
}
- $this->mDbkeyform = $dbkey;
-
- # Initial colon indicates main namespace rather than specified default
- # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
- if ( ':' == $dbkey[0] ) {
- $this->mNamespace = NS_MAIN;
- $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
- $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
- }
-
- # Namespace or interwiki prefix
- $firstPass = true;
- $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
- do {
- $m = array();
- if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
- $p = $m[1];
- if ( ( $ns = $wgContLang->getNsIndex( $p ) ) !== false ) {
- # Ordinary namespace
- $dbkey = $m[2];
- $this->mNamespace = $ns;
- # For Talk:X pages, check if X has a "namespace" prefix
- if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
- if ( $wgContLang->getNsIndex( $x[1] ) ) {
- # Disallow Talk:File:x type titles...
- return false;
- } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
- # Disallow Talk:Interwiki:x type titles...
- return false;
- }
- }
- } elseif ( Interwiki::isValidInterwiki( $p ) ) {
- if ( !$firstPass ) {
- # Can't make a local interwiki link to an interwiki link.
- # That's just crazy!
- return false;
- }
+ # Fill fields
+ $this->setFragment( '#' . $parts['fragment'] );
+ $this->mInterwiki = $parts['interwiki'];
+ $this->mLocalInterwiki = $parts['local_interwiki'];
+ $this->mNamespace = $parts['namespace'];
+ $this->mUserCaseDBKey = $parts['user_case_dbkey'];
- # Interwiki link
- $dbkey = $m[2];
- $this->mInterwiki = $wgContLang->lc( $p );
-
- # Redundant interwiki prefix to the local wiki
- if ( $wgLocalInterwiki !== false
- && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) )
- {
- if ( $dbkey == '' ) {
- # Can't have an empty self-link
- return false;
- }
- $this->mInterwiki = '';
- $firstPass = false;
- # Do another namespace split...
- continue;
- }
-
- # If there's an initial colon after the interwiki, that also
- # resets the default namespace
- if ( $dbkey !== '' && $dbkey[0] == ':' ) {
- $this->mNamespace = NS_MAIN;
- $dbkey = substr( $dbkey, 1 );
- }
- }
- # If there's no recognized interwiki or namespace,
- # then let the colon expression be part of the title.
- }
- break;
- } while ( true );
+ $this->mDbkeyform = $parts['dbkey'];
+ $this->mUrlform = wfUrlencode( $this->mDbkeyform );
+ $this->mTextform = str_replace( '_', ' ', $this->mDbkeyform );
# We already know that some pages won't be in the database!
- if ( $this->mInterwiki != '' || NS_SPECIAL == $this->mNamespace ) {
+ if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
$this->mArticleID = 0;
}
- $fragment = strstr( $dbkey, '#' );
- if ( false !== $fragment ) {
- $this->setFragment( $fragment );
- $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
- # remove whitespace again: prevents "Foo_bar_#"
- # becoming "Foo_bar_"
- $dbkey = preg_replace( '/_*$/', '', $dbkey );
- }
-
- # Reject illegal characters.
- $rxTc = self::getTitleInvalidRegex();
- if ( preg_match( $rxTc, $dbkey ) ) {
- return false;
- }
-
- # Pages with "/./" or "/../" appearing in the URLs will often be un-
- # reachable due to the way web browsers deal with 'relative' URLs.
- # Also, they conflict with subpage syntax. Forbid them explicitly.
- if (
- strpos( $dbkey, '.' ) !== false &&
- (
- $dbkey === '.' || $dbkey === '..' ||
- strpos( $dbkey, './' ) === 0 ||
- strpos( $dbkey, '../' ) === 0 ||
- strpos( $dbkey, '/./' ) !== false ||
- strpos( $dbkey, '/../' ) !== false ||
- substr( $dbkey, -2 ) == '/.' ||
- substr( $dbkey, -3 ) == '/..'
- )
- ) {
- return false;
- }
-
- # Magic tilde sequences? Nu-uh!
- if ( strpos( $dbkey, '~~~' ) !== false ) {
- return false;
- }
-
- # Limit the size of titles to 255 bytes. This is typically the size of the
- # underlying database field. We make an exception for special pages, which
- # don't need to be stored in the database, and may edge over 255 bytes due
- # to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
- if (
- ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 )
- || strlen( $dbkey ) > 512
- ) {
- return false;
- }
-
- # Normally, all wiki links are forced to have an initial capital letter so [[foo]]
- # and [[Foo]] point to the same place. Don't force it for interwikis, since the
- # other site might be case-sensitive.
- $this->mUserCaseDBKey = $dbkey;
- if ( $this->mInterwiki == '' ) {
- $dbkey = self::capitalize( $dbkey, $this->mNamespace );
- }
-
- # Can't make a link to a namespace alone... "empty" local links can only be
- # self-links with a fragment identifier.
- # TODO: Why do we exclude NS_MAIN (bug 54044)
- if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) {
- return false;
- }
-
- // Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles.
- // IP names are not allowed for accounts, and can only be referring to
- // edits from the IP. Given '::' abbreviations and caps/lowercaps,
- // there are numerous ways to present the same IP. Having sp:contribs scan
- // them all is silly and having some show the edits and others not is
- // inconsistent. Same for talk/userpages. Keep them normalized instead.
- $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK )
- ? IP::sanitizeIP( $dbkey )
- : $dbkey;
-
- // Any remaining initial :s are illegal.
- if ( $dbkey !== '' && ':' == $dbkey[0] ) {
- return false;
- }
-
- # Fill fields
- $this->mDbkeyform = $dbkey;
- $this->mUrlform = wfUrlencode( $dbkey );
-
- $this->mTextform = str_replace( '_', ' ', $dbkey );
return true;
}
@@ -3338,10 +3355,10 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param array $options may be FOR UPDATE
- * @param string $table table name
- * @param string $prefix fields prefix
- * @return Array of Title objects linking here
+ * @param array $options May be FOR UPDATE
+ * @param string $table Table name
+ * @param string $prefix Fields prefix
+ * @return Title[] Array of Title objects linking here
*/
public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
if ( count( $options ) > 0 ) {
@@ -3382,8 +3399,8 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param array $options may be FOR UPDATE
- * @return Array of Title the Title objects linking here
+ * @param array $options May be FOR UPDATE
+ * @return Title[] Array of Title the Title objects linking here
*/
public function getTemplateLinksTo( $options = array() ) {
return $this->getLinksTo( $options, 'templatelinks', 'tl' );
@@ -3396,10 +3413,10 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param array $options may be FOR UPDATE
- * @param string $table table name
- * @param string $prefix fields prefix
- * @return Array of Title objects linking here
+ * @param array $options May be FOR UPDATE
+ * @param string $table Table name
+ * @param string $prefix Fields prefix
+ * @return array Array of Title objects linking here
*/
public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
global $wgContentHandlerUseDB;
@@ -3420,7 +3437,15 @@ class Title {
$namespaceFiled = "{$prefix}_namespace";
$titleField = "{$prefix}_title";
- $fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
+ $fields = array(
+ $namespaceFiled,
+ $titleField,
+ 'page_id',
+ 'page_len',
+ 'page_is_redirect',
+ 'page_latest'
+ );
+
if ( $wgContentHandlerUseDB ) {
$fields[] = 'page_content_model';
}
@@ -3431,7 +3456,10 @@ class Title {
array( "{$prefix}_from" => $id ),
__METHOD__,
$options,
- array( 'page' => array( 'LEFT JOIN', array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) ) )
+ array( 'page' => array(
+ 'LEFT JOIN',
+ array( "page_namespace=$namespaceFiled", "page_title=$titleField" )
+ ) )
);
$retVal = array();
@@ -3459,18 +3487,20 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param array $options may be FOR UPDATE
- * @return Array of Title the Title objects used here
+ * @param array $options May be FOR UPDATE
+ * @return Title[] Array of Title the Title objects used here
*/
public function getTemplateLinksFrom( $options = array() ) {
return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
}
/**
- * Get an array of Title objects referring to non-existent articles linked from this page
+ * Get an array of Title objects referring to non-existent articles linked
+ * from this page.
*
- * @todo check if needed (used only in SpecialBrokenRedirects.php, and should use redirect table in this case)
- * @return Array of Title the Title objects
+ * @todo check if needed (used only in SpecialBrokenRedirects.php, and
+ * should use redirect table in this case).
+ * @return Title[] Array of Title the Title objects
*/
public function getBrokenLinksFrom() {
if ( $this->getArticleID() == 0 ) {
@@ -3506,7 +3536,7 @@ class Title {
* Get a list of URLs to purge from the Squid cache when this
* page changes
*
- * @return Array of String the URLs
+ * @return string[] Array of String the URLs
*/
public function getSquidURLs() {
$urls = array(
@@ -3522,6 +3552,13 @@ class Title {
}
}
+ // If we are looking at a css/js user subpage, purge the action=raw.
+ if ( $this->isJsSubpage() ) {
+ $urls[] = $this->getInternalUrl( 'action=raw&ctype=text/javascript' );
+ } elseif ( $this->isCssSubpage() ) {
+ $urls[] = $this->getInternalUrl( 'action=raw&ctype=text/css' );
+ }
+
wfRunHooks( 'TitleSquidURLs', array( $this, &$urls ) );
return $urls;
}
@@ -3541,8 +3578,8 @@ class Title {
/**
* Move this page without authentication
*
- * @param $nt Title the new page Title
- * @return Mixed true on success, getUserPermissionsErrors()-like array on failure
+ * @param Title $nt The new page Title
+ * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
*/
public function moveNoAuth( &$nt ) {
return $this->moveTo( $nt, false );
@@ -3552,11 +3589,12 @@ class Title {
* Check whether a given move operation would be valid.
* Returns true if ok, or a getUserPermissionsErrors()-like array otherwise
*
- * @param $nt Title the new title
- * @param bool $auth indicates whether $wgUser's permissions
+ * @todo move this into MovePage
+ * @param Title $nt The new title
+ * @param bool $auth Indicates whether $wgUser's permissions
* should be checked
- * @param string $reason is the log summary of the move, used for spam checking
- * @return Mixed True on success, getUserPermissionsErrors()-like array on failure
+ * @param string $reason Is the log summary of the move, used for spam checking
+ * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
*/
public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
global $wgUser, $wgContentHandlerUseDB;
@@ -3573,7 +3611,7 @@ class Title {
if ( !$this->isMovable() ) {
$errors[] = array( 'immobile-source-namespace', $this->getNsText() );
}
- if ( $nt->getInterwiki() != '' ) {
+ if ( $nt->isExternal() ) {
$errors[] = array( 'immobile-target-namespace-iw' );
}
if ( !$nt->isMovable() ) {
@@ -3702,13 +3740,14 @@ class Title {
/**
* Move a title to a new location
*
- * @param $nt Title the new title
- * @param bool $auth indicates whether $wgUser's permissions
+ * @todo Deprecate this in favor of MovePage
+ * @param Title $nt The new title
+ * @param bool $auth Indicates whether $wgUser's permissions
* should be checked
- * @param string $reason the reason for the move
+ * @param string $reason The reason for the move
* @param bool $createRedirect Whether to create a redirect from the old title to the new title.
* Ignored if the user doesn't have the suppressredirect right.
- * @return Mixed true on success, getUserPermissionsErrors()-like array on failure
+ * @return array|bool True on success, getUserPermissionsErrors()-like array on failure
*/
public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
global $wgUser;
@@ -3725,240 +3764,24 @@ class Title {
wfRunHooks( 'TitleMove', array( $this, $nt, $wgUser ) );
- // If it is a file, move it first.
- // It is done before all other moving stuff is done because it's hard to revert.
- $dbw = wfGetDB( DB_MASTER );
- if ( $this->getNamespace() == NS_FILE ) {
- $file = wfLocalFile( $this );
- if ( $file->exists() ) {
- $status = $file->move( $nt );
- if ( !$status->isOk() ) {
- return $status->getErrorsArray();
- }
- }
- // Clear RepoGroup process cache
- RepoGroup::singleton()->clearCache( $this );
- RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
- }
-
- $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
- $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
- $protected = $this->isProtected();
-
- // Do the actual move
- $this->moveToInternal( $nt, $reason, $createRedirect );
-
- // Refresh the sortkey for this row. Be careful to avoid resetting
- // cl_timestamp, which may disturb time-based lists on some sites.
- $prefixes = $dbw->select(
- 'categorylinks',
- array( 'cl_sortkey_prefix', 'cl_to' ),
- array( 'cl_from' => $pageid ),
- __METHOD__
- );
- foreach ( $prefixes as $prefixRow ) {
- $prefix = $prefixRow->cl_sortkey_prefix;
- $catTo = $prefixRow->cl_to;
- $dbw->update( 'categorylinks',
- array(
- 'cl_sortkey' => Collation::singleton()->getSortKey(
- $nt->getCategorySortkey( $prefix ) ),
- 'cl_timestamp=cl_timestamp' ),
- array(
- 'cl_from' => $pageid,
- 'cl_to' => $catTo ),
- __METHOD__
- );
- }
-
- $redirid = $this->getArticleID();
-
- if ( $protected ) {
- # Protect the redirect title as the title used to be...
- $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
- array(
- 'pr_page' => $redirid,
- 'pr_type' => 'pr_type',
- 'pr_level' => 'pr_level',
- 'pr_cascade' => 'pr_cascade',
- 'pr_user' => 'pr_user',
- 'pr_expiry' => 'pr_expiry'
- ),
- array( 'pr_page' => $pageid ),
- __METHOD__,
- array( 'IGNORE' )
- );
- # Update the protection log
- $log = new LogPage( 'protect' );
- $comment = wfMessage(
- 'prot_1movedto2',
- $this->getPrefixedText(),
- $nt->getPrefixedText()
- )->inContentLanguage()->text();
- if ( $reason ) {
- $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
- }
- // @todo FIXME: $params?
- $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ), $wgUser );
- }
-
- # Update watchlists
- $oldnamespace = MWNamespace::getSubject( $this->getNamespace() );
- $newnamespace = MWNamespace::getSubject( $nt->getNamespace() );
- $oldtitle = $this->getDBkey();
- $newtitle = $nt->getDBkey();
-
- if ( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
- WatchedItem::duplicateEntries( $this, $nt );
- }
-
- $dbw->commit( __METHOD__ );
-
- wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
- return true;
- }
-
- /**
- * Move page to a title which is either a redirect to the
- * source page or nonexistent
- *
- * @param $nt Title the page to move to, which should be a redirect or nonexistent
- * @param string $reason The reason for the move
- * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check
- * if the user has the suppressredirect right
- * @throws MWException
- */
- private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
- global $wgUser, $wgContLang;
-
- if ( $nt->exists() ) {
- $moveOverRedirect = true;
- $logType = 'move_redir';
- } else {
- $moveOverRedirect = false;
- $logType = 'move';
- }
-
- if ( $createRedirect ) {
- $contentHandler = ContentHandler::getForTitle( $this );
- $redirectContent = $contentHandler->makeRedirectContent( $nt,
- wfMessage( 'move-redirect-text' )->inContentLanguage()->plain() );
-
- // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
+ $mp = new MovePage( $this, $nt );
+ $status = $mp->move( $wgUser, $reason, $createRedirect );
+ if ( $status->isOK() ) {
+ return true;
} else {
- $redirectContent = null;
- }
-
- $logEntry = new ManualLogEntry( 'move', $logType );
- $logEntry->setPerformer( $wgUser );
- $logEntry->setTarget( $this );
- $logEntry->setComment( $reason );
- $logEntry->setParameters( array(
- '4::target' => $nt->getPrefixedText(),
- '5::noredir' => $redirectContent ? '0': '1',
- ) );
-
- $formatter = LogFormatter::newFromEntry( $logEntry );
- $formatter->setContext( RequestContext::newExtraneousContext( $this ) );
- $comment = $formatter->getPlainActionText();
- if ( $reason ) {
- $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
- }
- # Truncate for whole multibyte characters.
- $comment = $wgContLang->truncate( $comment, 255 );
-
- $oldid = $this->getArticleID();
-
- $dbw = wfGetDB( DB_MASTER );
-
- $newpage = WikiPage::factory( $nt );
-
- if ( $moveOverRedirect ) {
- $newid = $nt->getArticleID();
-
- # Delete the old redirect. We don't save it to history since
- # by definition if we've got here it's rather uninteresting.
- # We have to remove it so that the next step doesn't trigger
- # a conflict on the unique namespace+title index...
- $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
-
- $newpage->doDeleteUpdates( $newid );
- }
-
- # Save a null revision in the page's history notifying of the move
- $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
- if ( !is_object( $nullRevision ) ) {
- throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
- }
-
- $nullRevision->insertOn( $dbw );
-
- # Change the name of the target page:
- $dbw->update( 'page',
- /* SET */ array(
- 'page_namespace' => $nt->getNamespace(),
- 'page_title' => $nt->getDBkey(),
- ),
- /* WHERE */ array( 'page_id' => $oldid ),
- __METHOD__
- );
-
- // clean up the old title before reset article id - bug 45348
- if ( !$redirectContent ) {
- WikiPage::onArticleDelete( $this );
- }
-
- $this->resetArticleID( 0 ); // 0 == non existing
- $nt->resetArticleID( $oldid );
- $newpage->loadPageData( WikiPage::READ_LOCKING ); // bug 46397
-
- $newpage->updateRevisionOn( $dbw, $nullRevision );
-
- wfRunHooks( 'NewRevisionFromEditComplete',
- array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
-
- $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
-
- if ( !$moveOverRedirect ) {
- WikiPage::onArticleCreate( $nt );
+ return $status->getErrorsArray();
}
-
- # Recreate the redirect, this time in the other direction.
- if ( $redirectContent ) {
- $redirectArticle = WikiPage::factory( $this );
- $redirectArticle->loadFromRow( false, WikiPage::READ_LOCKING ); // bug 46397
- $newid = $redirectArticle->insertOn( $dbw );
- if ( $newid ) { // sanity
- $this->resetArticleID( $newid );
- $redirectRevision = new Revision( array(
- 'title' => $this, // for determining the default content model
- 'page' => $newid,
- 'comment' => $comment,
- 'content' => $redirectContent ) );
- $redirectRevision->insertOn( $dbw );
- $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
-
- wfRunHooks( 'NewRevisionFromEditComplete',
- array( $redirectArticle, $redirectRevision, false, $wgUser ) );
-
- $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
- }
- }
-
- # Log the move
- $logid = $logEntry->insert();
- $logEntry->publish( $logid );
}
/**
* Move this page's subpages to be subpages of $nt
*
- * @param $nt Title Move target
+ * @param Title $nt Move target
* @param bool $auth Whether $wgUser's permissions should be checked
* @param string $reason The reason for the move
* @param bool $createRedirect Whether to create redirects from the old subpages to
* the new ones Ignored if the user doesn't have the 'suppressredirect' right
- * @return mixed array with old page titles as keys, and strings (new page titles) or
+ * @return array Array with old page titles as keys, and strings (new page titles) or
* arrays (errors) as values, or an error array with numeric indices if no pages
* were moved
*/
@@ -3984,7 +3807,7 @@ class Title {
foreach ( $subpages as $oldSubpage ) {
$count++;
if ( $count > $wgMaximumMovedPages ) {
- $retval[$oldSubpage->getPrefixedTitle()] =
+ $retval[$oldSubpage->getPrefixedText()] =
array( 'movepage-max-pages',
$wgMaximumMovedPages );
break;
@@ -3993,9 +3816,9 @@ class Title {
// We don't know whether this function was called before
// or after moving the root page, so check both
// $this and $nt
- if ( $oldSubpage->getArticleID() == $this->getArticleID() ||
- $oldSubpage->getArticleID() == $nt->getArticleID() )
- {
+ if ( $oldSubpage->getArticleID() == $this->getArticleID()
+ || $oldSubpage->getArticleID() == $nt->getArticleID()
+ ) {
// When moving a page to a subpage of itself,
// don't move it twice
continue;
@@ -4027,7 +3850,7 @@ class Title {
* Checks if this page is just a one-rev redirect.
* Adds lock, so don't use just for light purposes.
*
- * @return Bool
+ * @return bool
*/
public function isSingleRevRedirect() {
global $wgContentHandlerUseDB;
@@ -4050,7 +3873,10 @@ class Title {
$this->mArticleID = $row ? intval( $row->page_id ) : 0;
$this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
$this->mLatestID = $row ? intval( $row->page_latest ) : false;
- $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
+ $this->mContentModel = $row && isset( $row->page_content_model )
+ ? strval( $row->page_content_model )
+ : false;
+
if ( !$this->mRedirect ) {
return false;
}
@@ -4073,8 +3899,8 @@ class Title {
* Checks if $this can be moved to a given Title
* - Selects for update, so don't call it unless you mean business
*
- * @param $nt Title the new title to check
- * @return Bool
+ * @param Title $nt The new title to check
+ * @return bool
*/
public function isValidMoveTarget( $nt ) {
# Is it an existing file?
@@ -4120,7 +3946,7 @@ class Title {
* Get categories to which this Title belongs and return an array of
* categories' names.
*
- * @return Array of parents in the form:
+ * @return array Array of parents in the form:
* $parent => $currentarticle
*/
public function getParentCategories() {
@@ -4155,8 +3981,8 @@ class Title {
/**
* Get a tree of parent categories
*
- * @param array $children with the children in the keys, to check for circular refs
- * @return Array Tree of parent categories
+ * @param array $children Array with the children in the keys, to check for circular refs
+ * @return array Tree of parent categories
*/
public function getParentCategoryTree( $children = array() ) {
$stack = array();
@@ -4183,7 +4009,7 @@ class Title {
* Get an associative array for selecting this title from
* the "page" table
*
- * @return Array suitable for the $where parameter of DB::select()
+ * @return array Array suitable for the $where parameter of DB::select()
*/
public function pageCond() {
if ( $this->mArticleID > 0 ) {
@@ -4199,7 +4025,7 @@ class Title {
*
* @param int $revId Revision ID. Get the revision that was before this one.
* @param int $flags Title::GAID_FOR_UPDATE
- * @return Int|Bool Old revision ID, or FALSE if none exists
+ * @return int|bool Old revision ID, or false if none exists
*/
public function getPreviousRevisionID( $revId, $flags = 0 ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
@@ -4224,7 +4050,7 @@ class Title {
*
* @param int $revId Revision ID. Get the revision that was after this one.
* @param int $flags Title::GAID_FOR_UPDATE
- * @return Int|Bool Next revision ID, or FALSE if none exists
+ * @return int|bool Next revision ID, or false if none exists
*/
public function getNextRevisionID( $revId, $flags = 0 ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
@@ -4248,7 +4074,7 @@ class Title {
* Get the first revision of the page
*
* @param int $flags Title::GAID_FOR_UPDATE
- * @return Revision|Null if page doesn't exist
+ * @return Revision|null If page doesn't exist
*/
public function getFirstRevision( $flags = 0 ) {
$pageId = $this->getArticleID( $flags );
@@ -4270,7 +4096,7 @@ class Title {
* Get the oldest revision timestamp of this page
*
* @param int $flags Title::GAID_FOR_UPDATE
- * @return String: MW timestamp
+ * @return string MW timestamp
*/
public function getEarliestRevTime( $flags = 0 ) {
$rev = $this->getFirstRevision( $flags );
@@ -4299,12 +4125,32 @@ class Title {
return false;
}
- $revCount = $this->estimateRevisionCount();
- return $revCount > $wgDeleteRevisionsLimit;
+ if ( $this->mIsBigDeletion === null ) {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $innerQuery = $dbr->selectSQLText(
+ 'revision',
+ '1',
+ array( 'rev_page' => $this->getArticleID() ),
+ __METHOD__,
+ array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
+ );
+
+ $revCount = $dbr->query(
+ 'SELECT COUNT(*) FROM (' . $innerQuery . ') AS innerQuery',
+ __METHOD__
+ );
+ $revCount = $revCount->fetchRow();
+ $revCount = $revCount['COUNT(*)'];
+
+ $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
+ }
+
+ return $this->mIsBigDeletion;
}
/**
- * Get the approximate revision count of this page.
+ * Get the approximate revision count of this page.
*
* @return int
*/
@@ -4328,9 +4174,10 @@ class Title {
*
* @param int|Revision $old Old revision or rev ID (first before range)
* @param int|Revision $new New revision or rev ID (first after range)
- * @return Int Number of revisions between these revisions.
+ * @param int|null $max Limit of Revisions to count, will be incremented to detect truncations
+ * @return int Number of revisions between these revisions.
*/
- public function countRevisionsBetween( $old, $new ) {
+ public function countRevisionsBetween( $old, $new, $max = null ) {
if ( !( $old instanceof Revision ) ) {
$old = Revision::newFromTitle( $this, (int)$old );
}
@@ -4341,20 +4188,29 @@ class Title {
return 0; // nothing to compare
}
$dbr = wfGetDB( DB_SLAVE );
- return (int)$dbr->selectField( 'revision', 'count(*)',
- array(
- 'rev_page' => $this->getArticleID(),
- 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
- 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
- ),
- __METHOD__
+ $conds = array(
+ 'rev_page' => $this->getArticleID(),
+ 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
+ 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
);
+ if ( $max !== null ) {
+ $res = $dbr->select( 'revision', '1',
+ $conds,
+ __METHOD__,
+ array( 'LIMIT' => $max + 1 ) // extra to detect truncation
+ );
+ return $res->numRows();
+ } else {
+ return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
+ }
}
/**
- * Get the number of authors between the given revisions or revision IDs.
+ * Get the authors between the given revisions or revision IDs.
* Used for diffs and other things that really need it.
*
+ * @since 1.23
+ *
* @param int|Revision $old Old revision or rev ID (first before range by default)
* @param int|Revision $new New revision or rev ID (first after range by default)
* @param int $limit Maximum number of authors
@@ -4363,9 +4219,9 @@ class Title {
* 'include_new' Include $new in the range; $old is excluded.
* 'include_both' Include both $old and $new in the range.
* Unknown option values are ignored.
- * @return int Number of revision authors in the range; zero if not both revisions exist
+ * @return array|null Names of revision authors in the range; null if not both revisions exist
*/
- public function countAuthorsBetween( $old, $new, $limit, $options = array() ) {
+ public function getAuthorsBetween( $old, $new, $limit, $options = array() ) {
if ( !( $old instanceof Revision ) ) {
$old = Revision::newFromTitle( $this, (int)$old );
}
@@ -4376,8 +4232,9 @@ class Title {
// Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
// in the sanity check below?
if ( !$old || !$new ) {
- return 0; // nothing to compare
+ return null; // nothing to compare
}
+ $authors = array();
$old_cmp = '>';
$new_cmp = '<';
$options = (array)$options;
@@ -4393,12 +4250,19 @@ class Title {
}
// No DB query needed if $old and $new are the same or successive revisions:
if ( $old->getId() === $new->getId() ) {
- return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
+ return ( $old_cmp === '>' && $new_cmp === '<' ) ? array() : array( $old->getRawUserText() );
} elseif ( $old->getId() === $new->getParentId() ) {
- if ( $old_cmp === '>' || $new_cmp === '<' ) {
- return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
+ if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
+ $authors[] = $old->getRawUserText();
+ if ( $old->getRawUserText() != $new->getRawUserText() ) {
+ $authors[] = $new->getRawUserText();
+ }
+ } elseif ( $old_cmp === '>=' ) {
+ $authors[] = $old->getRawUserText();
+ } elseif ( $new_cmp === '<=' ) {
+ $authors[] = $new->getRawUserText();
}
- return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2;
+ return $authors;
}
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
@@ -4409,14 +4273,36 @@ class Title {
), __METHOD__,
array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
);
- return (int)$dbr->numRows( $res );
+ foreach ( $res as $row ) {
+ $authors[] = $row->rev_user_text;
+ }
+ return $authors;
+ }
+
+ /**
+ * Get the number of authors between the given revisions or revision IDs.
+ * Used for diffs and other things that really need it.
+ *
+ * @param int|Revision $old Old revision or rev ID (first before range by default)
+ * @param int|Revision $new New revision or rev ID (first after range by default)
+ * @param int $limit Maximum number of authors
+ * @param string|array $options (Optional): Single option, or an array of options:
+ * 'include_old' Include $old in the range; $new is excluded.
+ * 'include_new' Include $new in the range; $old is excluded.
+ * 'include_both' Include both $old and $new in the range.
+ * Unknown option values are ignored.
+ * @return int Number of revision authors in the range; zero if not both revisions exist
+ */
+ public function countAuthorsBetween( $old, $new, $limit, $options = array() ) {
+ $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
+ return $authors ? count( $authors ) : 0;
}
/**
* Compare with another title.
*
- * @param $title Title
- * @return Bool
+ * @param Title $title
+ * @return bool
*/
public function equals( Title $title ) {
// Note: === is necessary for proper matching of number-like titles.
@@ -4428,8 +4314,8 @@ class Title {
/**
* Check if this title is a subpage of another title
*
- * @param $title Title
- * @return Bool
+ * @param Title $title
+ * @return bool
*/
public function isSubpageOf( Title $title ) {
return $this->getInterwiki() === $title->getInterwiki()
@@ -4444,10 +4330,12 @@ class Title {
* If you want to know if a title can be meaningfully viewed, you should
* probably call the isKnown() method instead.
*
- * @return Bool
+ * @return bool
*/
public function exists() {
- return $this->getArticleID() != 0;
+ $exists = $this->getArticleID() != 0;
+ wfRunHooks( 'TitleExists', array( $this, &$exists ) );
+ return $exists;
}
/**
@@ -4464,7 +4352,7 @@ class Title {
* existing code, but we might want to add an optional parameter to skip
* it and any other expensive checks.)
*
- * @return Bool
+ * @return bool
*/
public function isAlwaysKnown() {
$isKnown = null;
@@ -4477,7 +4365,7 @@ class Title {
* @since 1.20
*
* @param Title $title
- * @param boolean|null $isKnown
+ * @param bool|null $isKnown
*/
wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
@@ -4485,7 +4373,7 @@ class Title {
return $isKnown;
}
- if ( $this->mInterwiki != '' ) {
+ if ( $this->isExternal() ) {
return true; // any interwiki link might be viewable, for all we know
}
@@ -4517,7 +4405,7 @@ class Title {
* since LinkHolderArray calls isAlwaysKnown() and does its own
* page existence check.
*
- * @return Bool
+ * @return bool
*/
public function isKnown() {
return $this->isAlwaysKnown() || $this->exists();
@@ -4526,7 +4414,7 @@ class Title {
/**
* Does this page have source text?
*
- * @return Boolean
+ * @return bool
*/
public function hasSourceText() {
if ( $this->exists() ) {
@@ -4539,7 +4427,9 @@ class Title {
// Use always content language to avoid loading hundreds of languages
// to get the link color.
global $wgContLang;
- list( $name, ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
+ list( $name, ) = MessageCache::singleton()->figureMessage(
+ $wgContLang->lcfirst( $this->getText() )
+ );
$message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
return $message->exists();
}
@@ -4550,7 +4440,7 @@ class Title {
/**
* Get the default message text or false if the message doesn't exist
*
- * @return String or false
+ * @return string|bool
*/
public function getDefaultMessageText() {
global $wgContLang;
@@ -4559,7 +4449,9 @@ class Title {
return false;
}
- list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
+ list( $name, $lang ) = MessageCache::singleton()->figureMessage(
+ $wgContLang->lcfirst( $this->getText() )
+ );
$message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
if ( $message->exists() ) {
@@ -4572,17 +4464,21 @@ class Title {
/**
* Updates page_touched for this page; called from LinksUpdate.php
*
- * @return Bool true if the update succeeded
+ * @return bool True if the update succeeded
*/
public function invalidateCache() {
if ( wfReadOnly() ) {
return false;
}
+ if ( $this->mArticleID === 0 ) {
+ return true; // avoid gap locking if we know it's not there
+ }
+
$method = __METHOD__;
$dbw = wfGetDB( DB_MASTER );
$conds = $this->pageCond();
- $dbw->onTransactionIdle( function() use ( $dbw, $conds, $method ) {
+ $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method ) {
$dbw->update(
'page',
array( 'page_touched' => $dbw->timestamp() ),
@@ -4612,11 +4508,13 @@ class Title {
/**
* Get the last touched timestamp
*
- * @param $db DatabaseBase: optional db
- * @return String last-touched timestamp
+ * @param DatabaseBase $db Optional db
+ * @return string Last-touched timestamp
*/
public function getTouched( $db = null ) {
- $db = isset( $db ) ? $db : wfGetDB( DB_SLAVE );
+ if ( $db === null ) {
+ $db = wfGetDB( DB_SLAVE );
+ }
$touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
return $touched;
}
@@ -4624,8 +4522,8 @@ class Title {
/**
* Get the timestamp when this page was updated since the user last saw it.
*
- * @param $user User
- * @return String|Null
+ * @param User $user
+ * @return string|null
*/
public function getNotificationTimestamp( $user = null ) {
global $wgUser, $wgShowUpdatedMarker;
@@ -4640,7 +4538,8 @@ class Title {
return $this->mNotificationTimestamp[$uid];
}
if ( !$uid || !$wgShowUpdatedMarker || !$user->isAllowed( 'viewmywatchlist' ) ) {
- return $this->mNotificationTimestamp[$uid] = false;
+ $this->mNotificationTimestamp[$uid] = false;
+ return $this->mNotificationTimestamp[$uid];
}
// Don't cache too much!
if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
@@ -4662,8 +4561,8 @@ class Title {
/**
* Generate strings used for xml 'id' names in monobook tabs
*
- * @param string $prepend defaults to 'nstab-'
- * @return String XML 'id' name
+ * @param string $prepend Defaults to 'nstab-'
+ * @return string XML 'id' name
*/
public function getNamespaceKey( $prepend = 'nstab-' ) {
global $wgContLang;
@@ -4693,8 +4592,8 @@ class Title {
/**
* Get all extant redirects to this Title
*
- * @param int|Null $ns Single namespace to consider; NULL to consider all namespaces
- * @return Array of Title redirects to this title
+ * @param int|null $ns Single namespace to consider; null to consider all namespaces
+ * @return Title[] Array of Title redirects to this title
*/
public function getRedirectsHere( $ns = null ) {
$redirs = array();
@@ -4730,7 +4629,7 @@ class Title {
/**
* Check if this Title is a valid redirect target
*
- * @return Bool
+ * @return bool
*/
public function isValidRedirectTarget() {
global $wgInvalidRedirectTargets;
@@ -4761,7 +4660,7 @@ class Title {
/**
* Whether the magic words __INDEX__ and __NOINDEX__ function for this page.
*
- * @return Boolean
+ * @return bool
*/
public function canUseNoindex() {
global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
@@ -4812,18 +4711,36 @@ class Title {
* @return Language
*/
public function getPageLanguage() {
- global $wgLang;
+ global $wgLang, $wgLanguageCode;
+ wfProfileIn( __METHOD__ );
if ( $this->isSpecialPage() ) {
// special pages are in the user language
+ wfProfileOut( __METHOD__ );
return $wgLang;
}
- //TODO: use the LinkCache to cache this! Note that this may depend on user settings, so the cache should be only per-request.
- //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
- $contentHandler = ContentHandler::getForTitle( $this );
- $pageLang = $contentHandler->getPageLanguage( $this );
+ // Checking if DB language is set
+ if ( $this->mDbPageLanguage ) {
+ wfProfileOut( __METHOD__ );
+ return wfGetLangObj( $this->mDbPageLanguage );
+ }
- return wfGetLangObj( $pageLang );
+ if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
+ // Note that this may depend on user settings, so the cache should
+ // be only per-request.
+ // NOTE: ContentHandler::getPageLanguage() may need to load the
+ // content to determine the page language!
+ // Checking $wgLanguageCode hasn't changed for the benefit of unit
+ // tests.
+ $contentHandler = ContentHandler::getForTitle( $this );
+ $langObj = wfGetLangObj( $contentHandler->getPageLanguage( $this ) );
+ $this->mPageLanguage = array( $langObj->getCode(), $wgLanguageCode );
+ } else {
+ $langObj = wfGetLangObj( $this->mPageLanguage[0] );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $langObj;
}
/**
@@ -4848,8 +4765,9 @@ class Title {
return $wgLang;
}
- //NOTE: can't be cached persistently, depends on user settings
- //NOTE: ContentHandler::getPageViewLanguage() may need to load the content to determine the page language!
+ // @note Can't be cached persistently, depends on user settings.
+ // @note ContentHandler::getPageViewLanguage() may need to load the
+ // content to determine the page language!
$contentHandler = ContentHandler::getForTitle( $this );
$pageLang = $contentHandler->getPageViewLanguage( $this );
return $pageLang;
@@ -4862,8 +4780,8 @@ class Title {
* they will already be wrapped in paragraphs.
*
* @since 1.21
- * @param int oldid Revision ID that's being edited
- * @return Array
+ * @param int $oldid Revision ID that's being edited
+ * @return array
*/
public function getEditNotices( $oldid = 0 ) {
$notices = array();
diff --git a/includes/TitleArray.php b/includes/TitleArray.php
index 90fb861a..b67d9f4d 100644
--- a/includes/TitleArray.php
+++ b/includes/TitleArray.php
@@ -1,6 +1,6 @@
<?php
/**
- * Classes to walk into a list of Title objects.
+ * Class to walk into a list of Title objects.
*
* Note: this entire file is a byte-for-byte copy of UserArray.php with
* s/User/Title/. If anyone can figure out how to do this nicely with
@@ -30,7 +30,7 @@
*/
abstract class TitleArray implements Iterator {
/**
- * @param $res ResultWrapper A SQL result including at least page_namespace and
+ * @param ResultWrapper $res A SQL result including at least page_namespace and
* page_title -- also can have page_id, page_len, page_is_redirect,
* page_latest (if those will be used). See Title::newFromRow.
* @return TitleArrayFromResult
@@ -47,7 +47,7 @@ abstract class TitleArray implements Iterator {
}
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper $res
* @return TitleArrayFromResult
*/
protected static function newFromResult_internal( $res ) {
@@ -55,64 +55,3 @@ abstract class TitleArray implements Iterator {
return $array;
}
}
-
-class TitleArrayFromResult extends TitleArray {
-
- /**
- * @var ResultWrapper
- */
- var $res;
- var $key, $current;
-
- function __construct( $res ) {
- $this->res = $res;
- $this->key = 0;
- $this->setCurrent( $this->res->current() );
- }
-
- /**
- * @param $row ResultWrapper
- * @return void
- */
- protected function setCurrent( $row ) {
- if ( $row === false ) {
- $this->current = false;
- } else {
- $this->current = Title::newFromRow( $row );
- }
- }
-
- /**
- * @return int
- */
- public function count() {
- return $this->res->numRows();
- }
-
- function current() {
- return $this->current;
- }
-
- function key() {
- return $this->key;
- }
-
- function next() {
- $row = $this->res->next();
- $this->setCurrent( $row );
- $this->key++;
- }
-
- function rewind() {
- $this->res->rewind();
- $this->key = 0;
- $this->setCurrent( $this->res->current() );
- }
-
- /**
- * @return bool
- */
- function valid() {
- return $this->current !== false;
- }
-}
diff --git a/includes/TitleArrayFromResult.php b/includes/TitleArrayFromResult.php
new file mode 100644
index 00000000..668ea54b
--- /dev/null
+++ b/includes/TitleArrayFromResult.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Class to walk into a list of Title objects.
+ *
+ * Note: this entire file is a byte-for-byte copy of UserArrayFromResult.php
+ * with s/User/Title/. If anyone can figure out how to do this nicely
+ * with inheritance or something, please do so.
+ *
+ * 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 TitleArrayFromResult extends TitleArray implements Countable {
+ /** @var ResultWrapper */
+ public $res;
+
+ public $key;
+
+ public $current;
+
+ function __construct( $res ) {
+ $this->res = $res;
+ $this->key = 0;
+ $this->setCurrent( $this->res->current() );
+ }
+
+ /**
+ * @param bool|ResultWrapper $row
+ * @return void
+ */
+ protected function setCurrent( $row ) {
+ if ( $row === false ) {
+ $this->current = false;
+ } else {
+ $this->current = Title::newFromRow( $row );
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function count() {
+ return $this->res->numRows();
+ }
+
+ function current() {
+ return $this->current;
+ }
+
+ function key() {
+ return $this->key;
+ }
+
+ function next() {
+ $row = $this->res->next();
+ $this->setCurrent( $row );
+ $this->key++;
+ }
+
+ function rewind() {
+ $this->res->rewind();
+ $this->key = 0;
+ $this->setCurrent( $this->res->current() );
+ }
+
+ /**
+ * @return bool
+ */
+ function valid() {
+ return $this->current !== false;
+ }
+}
diff --git a/includes/User.php b/includes/User.php
index 4c7a39df..5e5d3eed 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -21,32 +21,12 @@
*/
/**
- * Int Number of characters in user_token field.
- * @ingroup Constants
- */
-define( 'USER_TOKEN_LENGTH', 32 );
-
-/**
- * Int Serialized record version.
- * @ingroup Constants
- */
-define( 'MW_USER_VERSION', 8 );
-
-/**
* String Some punctuation to prevent editing from broken text-mangling proxies.
* @ingroup Constants
*/
define( 'EDIT_TOKEN_SUFFIX', '+\\' );
/**
- * Thrown by User::setPassword() on error.
- * @ingroup Exception
- */
-class PasswordError extends MWException {
- // NOP
-}
-
-/**
* The User object encapsulates all of the user-specific settings (user_id,
* name, rights, password, email address, options, last login time). Client
* classes use the getXXX() functions to access these fields. These functions
@@ -56,34 +36,44 @@ class PasswordError extends MWException {
* for rendering normal pages are set in the cookie to minimize use
* of the database.
*/
-class User {
+class User implements IDBAccessObject {
/**
- * Global constants made accessible as class constants so that autoloader
+ * @const int Number of characters in user_token field.
+ */
+ const TOKEN_LENGTH = 32;
+
+ /**
+ * Global constant made accessible as class constants so that autoloader
* magic can be used.
*/
- const USER_TOKEN_LENGTH = USER_TOKEN_LENGTH;
- const MW_USER_VERSION = MW_USER_VERSION;
const EDIT_TOKEN_SUFFIX = EDIT_TOKEN_SUFFIX;
/**
+ * @const int Serialized record version.
+ */
+ const VERSION = 10;
+
+ /**
* Maximum items in $mWatchedItems
*/
const MAX_WATCHED_ITEMS_CACHE = 100;
/**
+ * @var PasswordFactory Lazily loaded factory object for passwords
+ */
+ private static $mPasswordFactory = null;
+
+ /**
* Array of Strings List of member variables which are saved to the
* shared cache (memcached). Any operation which changes the
* corresponding database fields must call a cache-clearing function.
* @showinitializer
*/
- static $mCacheVars = array(
+ protected static $mCacheVars = array(
// user table
'mId',
'mName',
'mRealName',
- 'mPassword',
- 'mNewpassword',
- 'mNewpassTime',
'mEmail',
'mTouched',
'mToken',
@@ -104,7 +94,7 @@ class User {
* "right-$right".
* @showinitializer
*/
- static $mCoreRights = array(
+ protected static $mCoreRights = array(
'apihighlimits',
'autoconfirmed',
'autopatrol',
@@ -143,11 +133,13 @@ class User {
'minoredit',
'move',
'movefile',
+ 'move-categorypages',
'move-rootuserpages',
'move-subpages',
'nominornewtalk',
'noratelimit',
'override-export-depth',
+ 'pagelang',
'passwordreset',
'patrol',
'patrolmarks',
@@ -173,31 +165,70 @@ class User {
'userrights-interwiki',
'viewmyprivateinfo',
'viewmywatchlist',
+ 'viewsuppressed',
'writeapi',
);
+
/**
* String Cached results of getAllRights()
*/
- static $mAllRights = false;
+ protected static $mAllRights = false;
/** @name Cache variables */
//@{
- var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime,
- $mEmail, $mTouched, $mToken, $mEmailAuthenticated,
- $mEmailToken, $mEmailTokenExpires, $mRegistration, $mEditCount,
- $mGroups, $mOptionOverrides;
+ public $mId;
+
+ public $mName;
+
+ public $mRealName;
+
+ /**
+ * @todo Make this actually private
+ * @private
+ */
+ public $mPassword;
+
+ /**
+ * @todo Make this actually private
+ * @private
+ */
+ public $mNewpassword;
+
+ public $mNewpassTime;
+
+ public $mEmail;
+
+ public $mTouched;
+
+ protected $mToken;
+
+ public $mEmailAuthenticated;
+
+ protected $mEmailToken;
+
+ protected $mEmailTokenExpires;
+
+ protected $mRegistration;
+
+ protected $mEditCount;
+
+ public $mGroups;
+
+ protected $mOptionOverrides;
+
+ protected $mPasswordExpires;
//@}
/**
* Bool Whether the cache variables have been loaded.
*/
//@{
- var $mOptionsLoaded;
+ public $mOptionsLoaded;
/**
* Array with already loaded items or true if all items have been loaded.
*/
- private $mLoadedItems = array();
+ protected $mLoadedItems = array();
//@}
/**
@@ -209,41 +240,55 @@ class User {
*
* Use the User::newFrom*() family of functions to set this.
*/
- var $mFrom;
+ public $mFrom;
/**
* Lazy-initialized variables, invalidated with clearInstanceCache
*/
- var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mRights,
- $mBlockreason, $mEffectiveGroups, $mImplicitGroups, $mFormerGroups, $mBlockedGlobally,
- $mLocked, $mHideName, $mOptions;
+ protected $mNewtalk;
+
+ protected $mDatePreference;
+
+ public $mBlockedby;
+
+ protected $mHash;
+
+ public $mRights;
+
+ protected $mBlockreason;
+
+ protected $mEffectiveGroups;
+
+ protected $mImplicitGroups;
+
+ protected $mFormerGroups;
+
+ protected $mBlockedGlobally;
+
+ protected $mLocked;
+
+ public $mHideName;
+
+ public $mOptions;
/**
* @var WebRequest
*/
private $mRequest;
- /**
- * @var Block
- */
- var $mBlock;
+ /** @var Block */
+ public $mBlock;
- /**
- * @var bool
- */
- var $mAllowUsertalk;
+ /** @var bool */
+ protected $mAllowUsertalk;
- /**
- * @var Block
- */
+ /** @var Block */
private $mBlockedFromCreateAccount = false;
- /**
- * @var Array
- */
+ /** @var array */
private $mWatchedItems = array();
- static $idCacheByName = array();
+ public static $idCacheByName = array();
/**
* Lightweight constructor for an anonymous user.
@@ -255,14 +300,14 @@ class User {
* @see newFromSession()
* @see newFromRow()
*/
- function __construct() {
+ public function __construct() {
$this->clearInstanceCache( 'defaults' );
}
/**
* @return string
*/
- function __toString() {
+ public function __toString() {
return $this->getName();
}
@@ -310,7 +355,7 @@ class User {
/**
* Load user table data, given mId has already been set.
- * @return bool false if the ID does not exist, true otherwise
+ * @return bool False if the ID does not exist, true otherwise
*/
public function loadFromId() {
global $wgMemc;
@@ -322,7 +367,7 @@ class User {
// Try cache
$key = wfMemcKey( 'user', 'id', $this->mId );
$data = $wgMemc->get( $key );
- if ( !is_array( $data ) || $data['mVersion'] < MW_USER_VERSION ) {
+ if ( !is_array( $data ) || $data['mVersion'] != self::VERSION ) {
// Object is expired, load from DB
$data = false;
}
@@ -363,7 +408,7 @@ class User {
foreach ( self::$mCacheVars as $name ) {
$data[$name] = $this->$name;
}
- $data['mVersion'] = MW_USER_VERSION;
+ $data['mVersion'] = self::VERSION;
$key = wfMemcKey( 'user', 'id', $this->mId );
global $wgMemc;
$wgMemc->set( $key, $data );
@@ -446,8 +491,8 @@ class User {
* Create a new user object using data from session or cookies. If the
* login credentials are invalid, the result is an anonymous user.
*
- * @param WebRequest $request Object to use; $wgRequest will be used if omitted.
- * @return User object
+ * @param WebRequest|null $request Object to use; $wgRequest will be used if omitted.
+ * @return User
*/
public static function newFromSession( WebRequest $request = null ) {
$user = new User;
@@ -466,7 +511,7 @@ class User {
* user_name and user_real_name are not provided because the whole row
* will be loaded once more from the database when accessing them.
*
- * @param array $row A row from the user table
+ * @param stdClass $row A row from the user table
* @param array $data Further data to load into the object (see User::loadFromRow for valid keys)
* @return User
*/
@@ -514,7 +559,12 @@ class User {
}
$dbr = wfGetDB( DB_SLAVE );
- $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), __METHOD__ );
+ $s = $dbr->selectRow(
+ 'user',
+ array( 'user_id' ),
+ array( 'user_name' => $nt->getText() ),
+ __METHOD__
+ );
if ( $s === false ) {
$result = null;
@@ -555,7 +605,8 @@ class User {
* @return bool
*/
public static function isIP( $name ) {
- return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name ) || IP::isIPv6( $name );
+ return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
+ || IP::isIPv6( $name );
}
/**
@@ -657,7 +708,7 @@ class User {
* Additional blacklisting may be added here rather than in
* isValidUserName() to avoid disrupting existing accounts.
*
- * @param string $name to match
+ * @param string $name String to match
* @return bool
*/
public static function isCreatableName( $name ) {
@@ -695,13 +746,41 @@ class User {
return $this->getPasswordValidity( $password ) === true;
}
+
/**
* Given unvalidated password input, return error message on failure.
*
* @param string $password Desired password
- * @return mixed: true on success, string or array of error message on failure
+ * @return bool|string|array True on success, string or array of error message on failure
*/
public function getPasswordValidity( $password ) {
+ $result = $this->checkPasswordValidity( $password );
+ if ( $result->isGood() ) {
+ return true;
+ } else {
+ $messages = array();
+ foreach ( $result->getErrorsByType( 'error' ) as $error ) {
+ $messages[] = $error['message'];
+ }
+ foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
+ $messages[] = $warning['message'];
+ }
+ if ( count( $messages ) === 1 ) {
+ return $messages[0];
+ }
+ return $messages;
+ }
+ }
+
+ /**
+ * Check if this is a valid password for this user. Status will be good if
+ * the password is valid, or have an array of error messages if not.
+ *
+ * @param string $password Desired password
+ * @return Status
+ * @since 1.23
+ */
+ public function checkPasswordValidity( $password ) {
global $wgMinimalPasswordLength, $wgContLang;
static $blockedLogins = array(
@@ -709,74 +788,118 @@ class User {
'Apitestsysop' => 'testpass', 'Apitestuser' => 'testpass' # r75605
);
+ $status = Status::newGood();
+
$result = false; //init $result to false for the internal checks
if ( !wfRunHooks( 'isValidPassword', array( $password, &$result, $this ) ) ) {
- return $result;
+ $status->error( $result );
+ return $status;
}
if ( $result === false ) {
if ( strlen( $password ) < $wgMinimalPasswordLength ) {
- return 'passwordtooshort';
+ $status->error( 'passwordtooshort', $wgMinimalPasswordLength );
+ return $status;
} elseif ( $wgContLang->lc( $password ) == $wgContLang->lc( $this->mName ) ) {
- return 'password-name-match';
- } elseif ( isset( $blockedLogins[$this->getName()] ) && $password == $blockedLogins[$this->getName()] ) {
- return 'password-login-forbidden';
+ $status->error( 'password-name-match' );
+ return $status;
+ } elseif ( isset( $blockedLogins[$this->getName()] )
+ && $password == $blockedLogins[$this->getName()]
+ ) {
+ $status->error( 'password-login-forbidden' );
+ return $status;
} else {
- //it seems weird returning true here, but this is because of the
+ //it seems weird returning a Good status here, but this is because of the
//initialization of $result to false above. If the hook is never run or it
//doesn't modify $result, then we will likely get down into this if with
//a valid password.
- return true;
+ return $status;
}
} elseif ( $result === true ) {
- return true;
+ return $status;
} else {
- return $result; //the isValidPassword hook set a string $result and returned true
+ $status->error( $result );
+ return $status; //the isValidPassword hook set a string $result and returned true
}
}
/**
- * Does a string look like an e-mail address?
- *
- * This validates an email address using an HTML5 specification found at:
- * http://www.whatwg.org/html/states-of-the-type-attribute.html#valid-e-mail-address
- * Which as of 2011-01-24 says:
- *
- * A valid e-mail address is a string that matches the ABNF production
- * 1*( atext / "." ) "@" ldh-str *( "." ldh-str ) where atext is defined
- * in RFC 5322 section 3.2.3, and ldh-str is defined in RFC 1034 section
- * 3.5.
- *
- * This function is an implementation of the specification as requested in
- * bug 22449.
- *
- * Client-side forms will use the same standard validation rules via JS or
- * HTML 5 validation; additional restrictions can be enforced server-side
- * by extensions via the 'isValidEmailAddr' hook.
- *
- * Note that this validation doesn't 100% match RFC 2822, but is believed
- * to be liberal enough for wide use. Some invalid addresses will still
- * pass validation here.
- *
- * @param string $addr E-mail address
- * @return bool
- * @deprecated since 1.18 call Sanitizer::isValidEmail() directly
+ * Expire a user's password
+ * @since 1.23
+ * @param int $ts Optional timestamp to convert, default 0 for the current time
*/
- public static function isValidEmailAddr( $addr ) {
- wfDeprecated( __METHOD__, '1.18' );
- return Sanitizer::validateEmail( $addr );
+ public function expirePassword( $ts = 0 ) {
+ $this->loadPasswords();
+ $timestamp = wfTimestamp( TS_MW, $ts );
+ $this->mPasswordExpires = $timestamp;
+ $this->saveSettings();
+ }
+
+ /**
+ * Clear the password expiration for a user
+ * @since 1.23
+ * @param bool $load Ensure user object is loaded first
+ */
+ public function resetPasswordExpiration( $load = true ) {
+ global $wgPasswordExpirationDays;
+ if ( $load ) {
+ $this->load();
+ }
+ $newExpire = null;
+ if ( $wgPasswordExpirationDays ) {
+ $newExpire = wfTimestamp(
+ TS_MW,
+ time() + ( $wgPasswordExpirationDays * 24 * 3600 )
+ );
+ }
+ // Give extensions a chance to force an expiration
+ wfRunHooks( 'ResetPasswordExpiration', array( $this, &$newExpire ) );
+ $this->mPasswordExpires = $newExpire;
+ }
+
+ /**
+ * Check if the user's password is expired.
+ * TODO: Put this and password length into a PasswordPolicy object
+ * @since 1.23
+ * @return string|bool The expiration type, or false if not expired
+ * hard: A password change is required to login
+ * soft: Allow login, but encourage password change
+ * false: Password is not expired
+ */
+ public function getPasswordExpired() {
+ global $wgPasswordExpireGrace;
+ $expired = false;
+ $now = wfTimestamp();
+ $expiration = $this->getPasswordExpireDate();
+ $expUnix = wfTimestamp( TS_UNIX, $expiration );
+ if ( $expiration !== null && $expUnix < $now ) {
+ $expired = ( $expUnix + $wgPasswordExpireGrace < $now ) ? 'hard' : 'soft';
+ }
+ return $expired;
+ }
+
+ /**
+ * Get this user's password expiration date. Since this may be using
+ * the cached User object, we assume that whatever mechanism is setting
+ * the expiration date is also expiring the User cache.
+ * @since 1.23
+ * @return string|bool The datestamp of the expiration, or null if not set
+ */
+ public function getPasswordExpireDate() {
+ $this->load();
+ return $this->mPasswordExpires;
}
/**
* Given unvalidated user input, return a canonical username, or false if
* the username is invalid.
* @param string $name User input
- * @param string|bool $validate type of validation to use:
- * - false No validation
- * - 'valid' Valid for batch processes
- * - 'usable' Valid for batch processes and login
- * - 'creatable' Valid for batch processes, login and account creation
+ * @param string|bool $validate Type of validation to use:
+ * - false No validation
+ * - 'valid' Valid for batch processes
+ * - 'usable' Valid for batch processes and login
+ * - 'creatable' Valid for batch processes, login and account creation
*
* @throws MWException
* @return bool|string
@@ -793,8 +916,9 @@ class User {
return false;
}
- // Clean up name according to title rules
- $t = ( $validate === 'valid' ) ?
+ // Clean up name according to title rules,
+ // but only when validation is requested (bug 12654)
+ $t = ( $validate !== false ) ?
Title::newFromText( $name ) : Title::makeTitle( NS_USER, $name );
// Check for invalid titles
if ( is_null( $t ) ) {
@@ -850,7 +974,8 @@ class User {
*/
public static function randomPassword() {
global $wgMinimalPasswordLength;
- // Decide the final password length based on our min password length, stopping at a minimum of 10 chars
+ // Decide the final password length based on our min password length,
+ // stopping at a minimum of 10 chars.
$length = max( 10, $wgMinimalPasswordLength );
// Multiply by 1.25 to get the number of hex characters we need
$length = $length * 1.25;
@@ -866,15 +991,18 @@ class User {
* @note This no longer clears uncached lazy-initialised properties;
* the constructor does that instead.
*
- * @param $name string|bool
+ * @param string|bool $name
*/
public function loadDefaults( $name = false ) {
wfProfileIn( __METHOD__ );
+ $passwordFactory = self::getPasswordFactory();
+
$this->mId = 0;
$this->mName = $name;
$this->mRealName = '';
- $this->mPassword = $this->mNewpassword = '';
+ $this->mPassword = $passwordFactory->newFromCiphertext( null );
+ $this->mNewpassword = $passwordFactory->newFromCiphertext( null );
$this->mNewpassTime = null;
$this->mEmail = '';
$this->mOptionOverrides = null;
@@ -891,6 +1019,8 @@ class User {
$this->mEmailAuthenticated = null;
$this->mEmailToken = '';
$this->mEmailTokenExpires = null;
+ $this->mPasswordExpires = null;
+ $this->resetPasswordExpiration( false );
$this->mRegistration = wfTimestamp( TS_MW );
$this->mGroups = array();
@@ -902,14 +1032,14 @@ class User {
/**
* Return whether an item has been loaded.
*
- * @param string $item item to check. Current possibilities:
- * - id
- * - name
- * - realname
+ * @param string $item Item to check. Current possibilities:
+ * - id
+ * - name
+ * - realname
* @param string $all 'all' to check if the whole object has been loaded
- * or any other string to check if only the item is available (e.g.
- * for optimisation)
- * @return boolean
+ * or any other string to check if only the item is available (e.g.
+ * for optimisation)
+ * @return bool
*/
public function isItemLoaded( $item, $all = 'all' ) {
return ( $this->mLoadedItems === true && $all === 'all' ) ||
@@ -979,14 +1109,16 @@ class User {
}
if ( $request->getSessionData( 'wsToken' ) ) {
- $passwordCorrect = ( $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' ) );
+ $passwordCorrect =
+ ( $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' ) );
$from = 'session';
} elseif ( $request->getCookie( 'Token' ) ) {
# Get the token from DB/cache and clean it up to remove garbage padding.
# This deals with historical problems with bugs and the default column value.
$token = rtrim( $proposedUser->getToken( false ) ); // correct token
// Make comparison in constant time (bug 61346)
- $passwordCorrect = strlen( $token ) && $this->compareSecrets( $token, $request->getCookie( 'Token' ) );
+ $passwordCorrect = strlen( $token )
+ && hash_equals( $token, $request->getCookie( 'Token' ) );
$from = 'cookie';
} else {
// No session or persistent login cookie
@@ -1006,31 +1138,13 @@ class User {
}
/**
- * A comparison of two strings, not vulnerable to timing attacks
- * @param string $answer the secret string that you are comparing against.
- * @param string $test compare this string to the $answer.
- * @return bool True if the strings are the same, false otherwise
- */
- protected function compareSecrets( $answer, $test ) {
- if ( strlen( $answer ) !== strlen( $test ) ) {
- $passwordCorrect = false;
- } else {
- $result = 0;
- for ( $i = 0; $i < strlen( $answer ); $i++ ) {
- $result |= ord( $answer{$i} ) ^ ord( $test{$i} );
- }
- $passwordCorrect = ( $result == 0 );
- }
- return $passwordCorrect;
- }
-
- /**
* Load user and user_group data from the database.
* $this->mId must be set, this is how the user is identified.
*
+ * @param int $flags Supports User::READ_LOCKING
* @return bool True if the user exists, false if the user is anonymous
*/
- public function loadFromDatabase() {
+ public function loadFromDatabase( $flags = 0 ) {
// Paranoia
$this->mId = intval( $this->mId );
@@ -1041,7 +1155,15 @@ class User {
}
$dbr = wfGetDB( DB_MASTER );
- $s = $dbr->selectRow( 'user', self::selectFields(), array( 'user_id' => $this->mId ), __METHOD__ );
+ $s = $dbr->selectRow(
+ 'user',
+ self::selectFields(),
+ array( 'user_id' => $this->mId ),
+ __METHOD__,
+ ( $flags & self::READ_LOCKING == self::READ_LOCKING )
+ ? array( 'LOCK IN SHARE MODE' )
+ : array()
+ );
wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
@@ -1062,7 +1184,7 @@ class User {
/**
* Initialize this object from a row from the user table.
*
- * @param array $row Row from the user table to load.
+ * @param stdClass $row Row from the user table to load.
* @param array $data Further user data to load into the object
*
* user_groups Array with groups out of the user_groups table
@@ -1070,6 +1192,7 @@ class User {
*/
public function loadFromRow( $row, $data = null ) {
$all = true;
+ $passwordFactory = self::getPasswordFactory();
$this->mGroups = null; // deferred
@@ -1103,13 +1226,32 @@ class User {
}
if ( isset( $row->user_password ) ) {
- $this->mPassword = $row->user_password;
- $this->mNewpassword = $row->user_newpassword;
+ // Check for *really* old password hashes that don't even have a type
+ // The old hash format was just an md5 hex hash, with no type information
+ if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) {
+ $row->user_password = ":A:{$this->mId}:{$row->user_password}";
+ }
+
+ try {
+ $this->mPassword = $passwordFactory->newFromCiphertext( $row->user_password );
+ } catch ( PasswordError $e ) {
+ wfDebug( 'Invalid password hash found in database.' );
+ $this->mPassword = $passwordFactory->newFromCiphertext( null );
+ }
+
+ try {
+ $this->mNewpassword = $passwordFactory->newFromCiphertext( $row->user_newpassword );
+ } catch ( PasswordError $e ) {
+ wfDebug( 'Invalid password hash found in database.' );
+ $this->mNewpassword = $passwordFactory->newFromCiphertext( null );
+ }
+
$this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time );
+ $this->mPasswordExpires = wfTimestampOrNull( TS_MW, $row->user_password_expires );
+ }
+
+ if ( isset( $row->user_email ) ) {
$this->mEmail = $row->user_email;
- if ( isset( $row->user_options ) ) {
- $this->decodeOptions( $row->user_options );
- }
$this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
$this->mToken = $row->user_token;
if ( $this->mToken == '' ) {
@@ -1140,7 +1282,7 @@ class User {
/**
* Load the data for this user object from another user object.
*
- * @param $user User
+ * @param User $user
*/
protected function loadFromUserObject( $user ) {
$user->load();
@@ -1169,6 +1311,26 @@ class User {
}
/**
+ * Load the user's password hashes from the database
+ *
+ * This is usually called in a scenario where the actual User object was
+ * loaded from the cache, and then password comparison needs to be performed.
+ * Password hashes are not stored in memcached.
+ *
+ * @since 1.24
+ */
+ private function loadPasswords() {
+ if ( $this->getId() !== 0 && ( $this->mPassword === null || $this->mNewpassword === null ) ) {
+ $this->loadFromRow( wfGetDB( DB_MASTER )->selectRow(
+ 'user',
+ array( 'user_password', 'user_newpassword', 'user_newpass_time', 'user_password_expires' ),
+ array( 'user_id' => $this->getId() ),
+ __METHOD__
+ ) );
+ }
+ }
+
+ /**
* Add the user to the group if he/she meets given criteria.
*
* Contrary to autopromotion by \ref $wgAutopromote, the group will be
@@ -1176,7 +1338,7 @@ class User {
* will not be re-added automatically. The user will also not lose the
* group if they no longer meet the criteria.
*
- * @param string $event key in $wgAutopromoteOnce (each one has groups/criteria)
+ * @param string $event Key in $wgAutopromoteOnce (each one has groups/criteria)
*
* @return array Array of groups the user has been promoted to.
*
@@ -1220,8 +1382,7 @@ class User {
* data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given.
*
* @param bool|string $reloadFrom Reload user and user_groups table data from a
- * given source. May be "name", "id", "defaults", "session", or false for
- * no reload.
+ * given source. May be "name", "id", "defaults", "session", or false for no reload.
*/
public function clearInstanceCache( $reloadFrom = false ) {
$this->mNewtalk = -1;
@@ -1246,7 +1407,7 @@ class User {
* Combine the language default options with any site-specific options
* and add the default language variants.
*
- * @return Array of String options
+ * @return array Array of String options
*/
public static function getDefaultOptions() {
global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
@@ -1268,7 +1429,7 @@ class User {
foreach ( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
$defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
}
- $defOpt['skin'] = $wgDefaultSkin;
+ $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
@@ -1292,10 +1453,9 @@ class User {
/**
* Get blocking information
- * @param bool $bFromSlave Whether to check the slave database first. To
- * improve performance, non-critical checks are done
- * against slaves. Check when actually saving should be
- * done against master.
+ * @param bool $bFromSlave Whether to check the slave database first.
+ * To improve performance, non-critical checks are done against slaves.
+ * Check when actually saving should be done against master.
*/
private function getBlockedStatus( $bFromSlave = true ) {
global $wgProxyWhitelist, $wgUser, $wgApplyIpBlocksToXff;
@@ -1328,8 +1488,8 @@ class User {
// Proxy blocking
if ( !$block instanceof Block && $ip !== null && !$this->isAllowed( 'proxyunbannable' )
- && !in_array( $ip, $wgProxyWhitelist ) )
- {
+ && !in_array( $ip, $wgProxyWhitelist )
+ ) {
// Local list
if ( self::isLocallyBlockedProxy( $ip ) ) {
$block = new Block;
@@ -1386,14 +1546,13 @@ class User {
* Whether the given IP is in a DNS blacklist.
*
* @param string $ip IP to check
- * @param bool $checkWhitelist whether to check the whitelist first
+ * @param bool $checkWhitelist Whether to check the whitelist first
* @return bool True if blacklisted.
*/
public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
- global $wgEnableSorbs, $wgEnableDnsBlacklist,
- $wgSorbsUrl, $wgDnsBlacklistUrls, $wgProxyWhitelist;
+ global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist;
- if ( !$wgEnableDnsBlacklist && !$wgEnableSorbs ) {
+ if ( !$wgEnableDnsBlacklist ) {
return false;
}
@@ -1401,15 +1560,14 @@ class User {
return false;
}
- $urls = array_merge( $wgDnsBlacklistUrls, (array)$wgSorbsUrl );
- return $this->inDnsBlacklist( $ip, $urls );
+ return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
}
/**
* Whether the given IP is in a given DNS blacklist.
*
* @param string $ip IP to check
- * @param string|array $bases of Strings: URL of the DNS blacklist
+ * @param string|array $bases Array of Strings: URL of the DNS blacklist
* @return bool True if blacklisted.
*/
public function inDnsBlacklist( $ip, $bases ) {
@@ -1439,11 +1597,11 @@ class User {
$ipList = gethostbynamel( $host );
if ( $ipList ) {
- wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
+ wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $base!" );
$found = true;
break;
} else {
- wfDebugLog( 'dnsblacklist', "Requested $host, not found in $base.\n" );
+ wfDebugLog( 'dnsblacklist', "Requested $host, not found in $base." );
}
}
}
@@ -1455,7 +1613,7 @@ class User {
/**
* Check if an IP address is in the local proxy list
*
- * @param $ip string
+ * @param string $ip
*
* @return bool
*/
@@ -1506,11 +1664,14 @@ class User {
* Primitive rate limits: enforce maximum actions per time period
* to put a brake on flooding.
*
+ * The method generates both a generic profiling point and a per action one
+ * (suffix being "-$action".
+ *
* @note When using a shared cache like memcached, IP-address
* last-hit counters will be shared across wikis.
*
* @param string $action Action to enforce; 'edit' if unspecified
- * @param integer $incrBy Positive amount to increment counter by [defaults to 1]
+ * @param int $incrBy Positive amount to increment counter by [defaults to 1]
* @return bool True if a rate limiter was tripped
*/
public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
@@ -1530,8 +1691,9 @@ class User {
return false;
}
- global $wgMemc, $wgRateLimitLog;
+ global $wgMemc;
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $action );
$limits = $wgRateLimits[$action];
$keys = array();
@@ -1593,12 +1755,8 @@ class User {
// Already pinged?
if ( $count ) {
if ( $count >= $max ) {
- wfDebug( __METHOD__ . ": tripped! $key at $count $summary\n" );
- if ( $wgRateLimitLog ) {
- wfSuppressWarnings();
- file_put_contents( $wgRateLimitLog, wfTimestamp( TS_MW ) . ' ' . wfWikiID() . ': ' . $this->getName() . " tripped $key at $count $summary\n", FILE_APPEND );
- wfRestoreWarnings();
- }
+ wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
+ "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
$triggered = true;
} else {
wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
@@ -1614,6 +1772,7 @@ class User {
}
}
+ wfProfileOut( __METHOD__ . '-' . $action );
wfProfileOut( __METHOD__ );
return $triggered;
}
@@ -1621,10 +1780,11 @@ class User {
/**
* Check if user is blocked
*
- * @param bool $bFromSlave Whether to check the slave database instead of the master
+ * @param bool $bFromSlave Whether to check the slave database instead of
+ * the master. Hacked from false due to horrible probs on site.
* @return bool True if blocked, false otherwise
*/
- public function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
+ public function isBlocked( $bFromSlave = true ) {
return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
}
@@ -1643,18 +1803,18 @@ class User {
* Check if user is blocked from editing a particular article
*
* @param Title $title Title to check
- * @param bool $bFromSlave whether to check the slave database instead of the master
+ * @param bool $bFromSlave Whether to check the slave database instead of the master
* @return bool
*/
- function isBlockedFrom( $title, $bFromSlave = false ) {
+ public function isBlockedFrom( $title, $bFromSlave = false ) {
global $wgBlockAllowsUTEdit;
wfProfileIn( __METHOD__ );
$blocked = $this->isBlocked( $bFromSlave );
$allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
// If a user's name is suppressed, they cannot make edits anywhere
- if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName() &&
- $title->getNamespace() == NS_USER_TALK ) {
+ if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
+ && $title->getNamespace() == NS_USER_TALK ) {
$blocked = false;
wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
}
@@ -1726,7 +1886,6 @@ class User {
return $this->mLocked;
}
global $wgAuth;
- StubObject::unstub( $wgAuth );
$authUser = $wgAuth->getUserInstance( $this );
$this->mLocked = (bool)$authUser->isLocked();
return $this->mLocked;
@@ -1744,7 +1903,6 @@ class User {
$this->getBlockedStatus();
if ( !$this->mHideName ) {
global $wgAuth;
- StubObject::unstub( $wgAuth );
$authUser = $wgAuth->getUserInstance( $this );
$this->mHideName = (bool)$authUser->isHidden();
}
@@ -1869,7 +2027,7 @@ class User {
* If there are no new messages, it returns an empty array.
* @note This function was designed to accomodate multiple talk pages, but
* currently only returns a single link and revision.
- * @return Array
+ * @return array
*/
public function getNewMessageLinks() {
$talks = array();
@@ -1918,7 +2076,7 @@ class User {
* @see getNewtalk()
* @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
* @param string|int $id User's IP address for anonymous users, User ID otherwise
- * @param bool $fromMaster true to fetch from the master, false for a slave
+ * @param bool $fromMaster True to fetch from the master, false for a slave
* @return bool True if the user has new messages
*/
protected function checkNewtalk( $field, $id, $fromMaster = false ) {
@@ -1936,7 +2094,7 @@ class User {
* Add or update the new messages flag
* @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
* @param string|int $id User's IP address for anonymous users, User ID otherwise
- * @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null.
+ * @param Revision|null $curRev New, as yet unseen revision of the user talk page. Ignored if null.
* @return bool True if successful, false otherwise
*/
protected function updateNewtalk( $field, $id, $curRev = null ) {
@@ -1981,7 +2139,8 @@ class User {
/**
* Update the 'You have new messages!' status.
* @param bool $val Whether the user has new messages
- * @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null or !$val.
+ * @param Revision $curRev New, as yet unseen revision of the user talk
+ * page. Ignored if null or !$val.
*/
public function setNewtalk( $val, $curRev = null ) {
if ( wfReadOnly() ) {
@@ -2034,7 +2193,7 @@ class User {
*
* Called implicitly from invalidateCache() and saveSettings().
*/
- private function clearSharedCache() {
+ public function clearSharedCache() {
$this->load();
if ( $this->mId ) {
global $wgMemc;
@@ -2059,7 +2218,7 @@ class User {
$userid = $this->mId;
$touched = $this->mTouched;
$method = __METHOD__;
- $dbw->onTransactionIdle( function() use ( $dbw, $userid, $touched, $method ) {
+ $dbw->onTransactionIdle( function () use ( $dbw, $userid, $touched, $method ) {
// Prevent contention slams by checking user_touched first
$encTouched = $dbw->addQuotes( $dbw->timestamp( $touched ) );
$needsPurge = $dbw->selectField( 'user', '1',
@@ -2088,7 +2247,7 @@ class User {
/**
* Get the user touched timestamp
- * @return string timestamp
+ * @return string Timestamp
*/
public function getTouched() {
$this->load();
@@ -2096,6 +2255,26 @@ class User {
}
/**
+ * @return Password
+ * @since 1.24
+ */
+ public function getPassword() {
+ $this->loadPasswords();
+
+ return $this->mPassword;
+ }
+
+ /**
+ * @return Password
+ * @since 1.24
+ */
+ public function getTemporaryPassword() {
+ $this->loadPasswords();
+
+ return $this->mNewpassword;
+ }
+
+ /**
* Set the password and reset the random token.
* Calls through to authentication plugin if necessary;
* will have no effect if the auth plugin refuses to
@@ -2107,13 +2286,15 @@ class User {
* a new password is set, for instance via e-mail.
*
* @param string $str New password to set
- * @throws PasswordError on failure
+ * @throws PasswordError On failure
*
* @return bool
*/
public function setPassword( $str ) {
global $wgAuth;
+ $this->loadPasswords();
+
if ( $str !== null ) {
if ( !$wgAuth->allowPasswordChange() ) {
throw new PasswordError( wfMessage( 'password-change-forbidden' )->text() );
@@ -2150,22 +2331,23 @@ class User {
* through the web interface.
*/
public function setInternalPassword( $str ) {
- $this->load();
$this->setToken();
+ $passwordFactory = self::getPasswordFactory();
if ( $str === null ) {
- // Save an invalid hash...
- $this->mPassword = '';
+ $this->mPassword = $passwordFactory->newFromCiphertext( null );
} else {
- $this->mPassword = self::crypt( $str );
+ $this->mPassword = $passwordFactory->newFromPlaintext( $str );
}
- $this->mNewpassword = '';
+
+ $this->mNewpassword = $passwordFactory->newFromCiphertext( null );
$this->mNewpassTime = null;
}
/**
* Get the user's current token.
- * @param bool $forceCreation Force the generation of a new token if the user doesn't have one (default=true for backwards compatibility)
+ * @param bool $forceCreation Force the generation of a new token if the
+ * user doesn't have one (default=true for backwards compatibility).
* @return string Token
*/
public function getToken( $forceCreation = true ) {
@@ -2185,7 +2367,7 @@ class User {
public function setToken( $token = false ) {
$this->load();
if ( !$token ) {
- $this->mToken = MWCryptRand::generateHex( USER_TOKEN_LENGTH );
+ $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
} else {
$this->mToken = $token;
}
@@ -2194,14 +2376,21 @@ class User {
/**
* Set the password for a password reminder or new account email
*
- * @param string $str New password to set
+ * @param string $str New password to set or null to set an invalid
+ * password hash meaning that the user will not be able to use it
* @param bool $throttle If true, reset the throttle timestamp to the present
*/
public function setNewpassword( $str, $throttle = true ) {
- $this->load();
- $this->mNewpassword = self::crypt( $str );
- if ( $throttle ) {
- $this->mNewpassTime = wfTimestampNow();
+ $this->loadPasswords();
+
+ if ( $str === null ) {
+ $this->mNewpassword = '';
+ $this->mNewpassTime = null;
+ } else {
+ $this->mNewpassword = self::getPasswordFactory()->newFromPlaintext( $str );
+ if ( $throttle ) {
+ $this->mNewpassTime = wfTimestampNow();
+ }
}
}
@@ -2249,8 +2438,8 @@ class User {
if ( $str == $this->mEmail ) {
return;
}
- $this->mEmail = $str;
$this->invalidateEmail();
+ $this->mEmail = $str;
wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
}
@@ -2396,6 +2585,8 @@ class User {
/**
* Set the given option for a user.
*
+ * You need to call saveSettings() to actually write to the database.
+ *
* @param string $oname The option to set
* @param mixed $val New value to set
*/
@@ -2464,6 +2655,8 @@ class User {
* - 'registered-checkmatrix' - as above, using the 'checkmatrix' type.
* - 'userjs' - preferences with names starting with 'userjs-', intended to
* be used by user scripts.
+ * - 'special' - "preferences" that are not accessible via User::getOptions
+ * or User::setOptions.
* - 'unused' - preferences about which MediaWiki doesn't know anything.
* These are usually legacy options, removed in newer versions.
*
@@ -2480,6 +2673,7 @@ class User {
'registered-multiselect',
'registered-checkmatrix',
'userjs',
+ 'special',
'unused'
);
}
@@ -2491,9 +2685,10 @@ class User {
* See User::listOptionKinds for the list of valid option types that can be provided.
*
* @see User::listOptionKinds
- * @param $context IContextSource
- * @param array $options assoc. array with options keys to check as keys. Defaults to $this->mOptions.
- * @return array the key => kind mapping data
+ * @param IContextSource $context
+ * @param array $options Assoc. array with options keys to check as keys.
+ * Defaults to $this->mOptions.
+ * @return array The key => kind mapping data
*/
public function getOptionKinds( IContextSource $context, $options = null ) {
$this->loadOptions();
@@ -2504,6 +2699,13 @@ class User {
$prefs = Preferences::getPreferences( $this, $context );
$mapping = array();
+ // Pull out the "special" options, so they don't get converted as
+ // multiselect or checkmatrix.
+ $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
+ foreach ( $specialOptions as $name => $value ) {
+ unset( $prefs[$name] );
+ }
+
// Multiselect and checkmatrix options are stored in the database with
// one key per option, each having a boolean value. Extract those keys.
$multiselectOptions = array();
@@ -2530,7 +2732,7 @@ class User {
foreach ( $columns as $column ) {
foreach ( $rows as $row ) {
- $checkmatrixOptions["$prefix-$column-$row"] = true;
+ $checkmatrixOptions["$prefix$column-$row"] = true;
}
}
@@ -2546,6 +2748,8 @@ class User {
$mapping[$key] = 'registered-multiselect';
} elseif ( isset( $checkmatrixOptions[$key] ) ) {
$mapping[$key] = 'registered-checkmatrix';
+ } elseif ( isset( $specialOptions[$key] ) ) {
+ $mapping[$key] = 'special';
} elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
$mapping[$key] = 'userjs';
} else {
@@ -2563,10 +2767,10 @@ class User {
* Supported values are everything that can be reported by getOptionKinds()
* and 'all', which forces a reset of *all* preferences and overrides everything else.
*
- * @param array|string $resetKinds which kinds of preferences to reset. Defaults to
+ * @param array|string $resetKinds Which kinds of preferences to reset. Defaults to
* array( 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' )
* for backwards-compatibility.
- * @param $context IContextSource|null context source used when $resetKinds
+ * @param IContextSource|null $context Context source used when $resetKinds
* does not contain 'all', passed to getOptionKinds().
* Defaults to RequestContext::getMain() when null.
*/
@@ -2605,6 +2809,8 @@ class User {
}
}
+ wfRunHooks( 'UserResetAllOptions', array( $this, &$newOptions, $this->mOptions, $resetKinds ) );
+
$this->mOptions = $newOptions;
$this->mOptionsLoaded = true;
}
@@ -2665,7 +2871,7 @@ class User {
/**
* Get the permissions this user has.
- * @return Array of String permission names
+ * @return array Array of String permission names
*/
public function getRights() {
if ( is_null( $this->mRights ) ) {
@@ -2680,7 +2886,7 @@ class User {
/**
* Get the list of explicit group memberships this user has.
* The implicit * and user groups are not included.
- * @return Array of String internal group names
+ * @return array Array of String internal group names
*/
public function getGroups() {
$this->load();
@@ -2693,7 +2899,7 @@ class User {
* This includes all explicit groups, plus 'user' if logged in,
* '*' for all accounts, and autopromoted groups
* @param bool $recache Whether to avoid the cache
- * @return Array of String internal group names
+ * @return array Array of String internal group names
*/
public function getEffectiveGroups( $recache = false ) {
if ( $recache || is_null( $this->mEffectiveGroups ) ) {
@@ -2716,7 +2922,7 @@ class User {
* This includes 'user' if logged in, '*' for all accounts,
* and autopromoted groups
* @param bool $recache Whether to avoid the cache
- * @return Array of String internal group names
+ * @return array Array of String internal group names
*/
public function getAutomaticGroups( $recache = false ) {
if ( $recache || is_null( $this->mImplicitGroups ) ) {
@@ -2766,14 +2972,14 @@ class User {
/**
* Get the user's edit count.
- * @return int, null for anonymous users
+ * @return int|null Null for anonymous users
*/
public function getEditCount() {
if ( !$this->getId() ) {
return null;
}
- if ( !isset( $this->mEditCount ) ) {
+ if ( $this->mEditCount === null ) {
/* Populate the count, if it has not been populated yet */
wfProfileIn( __METHOD__ );
$dbr = wfGetDB( DB_SLAVE );
@@ -2879,10 +3085,8 @@ class User {
/**
* Check if user is allowed to access a feature / make an action
*
- * @internal param \String $varargs permissions to test
- * @return boolean: True if user is allowed to perform *any* of the given actions
- *
- * @return bool
+ * @param string $permissions,... Permissions to test
+ * @return bool True if user is allowed to perform *any* of the given actions
*/
public function isAllowedAny( /*...*/ ) {
$permissions = func_get_args();
@@ -2896,7 +3100,7 @@ class User {
/**
*
- * @internal param $varargs string
+ * @param string $permissions,... Permissions to test
* @return bool True if the user is allowed to perform *all* of the given actions
*/
public function isAllowedAll( /*...*/ ) {
@@ -2932,7 +3136,7 @@ class User {
/**
* Check whether to enable recent changes patrol features for this user
- * @return boolean: True or false
+ * @return bool True or false
*/
public function useRCPatrol() {
global $wgUseRCPatrol;
@@ -2980,8 +3184,8 @@ class User {
* Get a WatchedItem for this user and $title.
*
* @since 1.22 $checkRights parameter added
- * @param $title Title
- * @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+ * @param Title $title
+ * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
* Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
* @return WatchedItem
*/
@@ -3003,8 +3207,8 @@ class User {
/**
* Check the watched status of an article.
* @since 1.22 $checkRights parameter added
- * @param $title Title of the article to look at
- * @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+ * @param Title $title Title of the article to look at
+ * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
* Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
* @return bool
*/
@@ -3015,8 +3219,8 @@ class User {
/**
* Watch an article.
* @since 1.22 $checkRights parameter added
- * @param $title Title of the article to look at
- * @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+ * @param Title $title Title of the article to look at
+ * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
* Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
*/
public function addWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
@@ -3027,8 +3231,8 @@ class User {
/**
* Stop watching an article.
* @since 1.22 $checkRights parameter added
- * @param $title Title of the article to look at
- * @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+ * @param Title $title Title of the article to look at
+ * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
* Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
*/
public function removeWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
@@ -3041,9 +3245,10 @@ class User {
* If e-notif e-mails are on, they will receive notification mails on
* the next change of the page if it's watched etc.
* @note If the user doesn't have 'editmywatchlist', this will do nothing.
- * @param $title Title of the article to look at
+ * @param Title $title Title of the article to look at
+ * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
*/
- public function clearNotification( &$title ) {
+ public function clearNotification( &$title, $oldid = 0 ) {
global $wgUseEnotif, $wgShowUpdatedMarker;
// Do nothing if the database is locked to writes
@@ -3056,12 +3261,25 @@ class User {
return;
}
- if ( $title->getNamespace() == NS_USER_TALK &&
- $title->getText() == $this->getName() ) {
- if ( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this ) ) ) {
+ // If we're working on user's talk page, we should update the talk page message indicator
+ if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
+ if ( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this, $oldid ) ) ) {
return;
}
- $this->setNewtalk( false );
+
+ $nextid = $oldid ? $title->getNextRevisionID( $oldid ) : null;
+
+ if ( !$oldid || !$nextid ) {
+ // If we're looking at the latest revision, we should definitely clear it
+ $this->setNewtalk( false );
+ } else {
+ // Otherwise we should update its revision, if it's present
+ if ( $this->getNewtalk() ) {
+ // Naturally the other one won't clear by itself
+ $this->setNewtalk( false );
+ $this->setNewtalk( true, Revision::newFromId( $nextid ) );
+ }
+ }
}
if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
@@ -3078,13 +3296,11 @@ class User {
// and when it does have to be executed, it can be on a slave
// If this is the user's newtalk page, we always update the timestamp
$force = '';
- if ( $title->getNamespace() == NS_USER_TALK &&
- $title->getText() == $this->getName() )
- {
+ if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
$force = 'force';
}
- $this->getWatchedItem( $title )->resetNotificationTimestamp( $force );
+ $this->getWatchedItem( $title )->resetNotificationTimestamp( $force, $oldid );
}
/**
@@ -3112,42 +3328,12 @@ class User {
if ( $id != 0 ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'watchlist',
- array( /* SET */
- 'wl_notificationtimestamp' => null
- ), array( /* WHERE */
- 'wl_user' => $id
- ), __METHOD__
+ array( /* SET */ 'wl_notificationtimestamp' => null ),
+ array( /* WHERE */ 'wl_user' => $id ),
+ __METHOD__
);
- # We also need to clear here the "you have new message" notification for the own user_talk page
- # This is cleared one page view later in Article::viewUpdates();
- }
- }
-
- /**
- * Set this user's options from an encoded string
- * @param string $str Encoded options to import
- *
- * @deprecated in 1.19 due to removal of user_options from the user table
- */
- private function decodeOptions( $str ) {
- wfDeprecated( __METHOD__, '1.19' );
- if ( !$str ) {
- return;
- }
-
- $this->mOptionsLoaded = true;
- $this->mOptionOverrides = array();
-
- // If an option is not set in $str, use the default value
- $this->mOptions = self::getDefaultOptions();
-
- $a = explode( "\n", $str );
- foreach ( $a as $s ) {
- $m = array();
- if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
- $this->mOptions[$m[1]] = $m[2];
- $this->mOptionOverrides[$m[1]] = $m[2];
- }
+ // We also need to clear here the "you have new message" notification for the own user_talk page;
+ // it's cleared one page view later in WikiPage::doViewUpdates().
}
}
@@ -3185,11 +3371,12 @@ class User {
/**
* Set the default cookies for this session on the user's client.
*
- * @param $request WebRequest object to use; $wgRequest will be used if null
+ * @param WebRequest|null $request WebRequest object to use; $wgRequest will be used if null
* is passed.
* @param bool $secure Whether to force secure/insecure cookies or use default
+ * @param bool $rememberMe Whether to add a Token cookie for elongated sessions
*/
- public function setCookies( $request = null, $secure = null ) {
+ public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
if ( $request === null ) {
$request = $this->getRequest();
}
@@ -3215,7 +3402,7 @@ class User {
'UserID' => $this->mId,
'UserName' => $this->getName(),
);
- if ( 1 == $this->getOption( 'rememberpassword' ) ) {
+ if ( $rememberMe ) {
$cookies['Token'] = $this->mToken;
} else {
$cookies['Token'] = false;
@@ -3242,14 +3429,10 @@ class User {
* standard time setting, based on if rememberme was set.
*/
if ( $request->getCheck( 'wpStickHTTPS' ) || $this->requiresHTTPS() ) {
- $time = null;
- if ( ( 1 == $this->getOption( 'rememberpassword' ) ) ) {
- $time = 0; // set to $wgCookieExpiration
- }
$this->setCookie(
'forceHTTPS',
'true',
- $time,
+ $rememberMe ? 0 : null,
false,
array( 'prefix' => '' ) // no prefix
);
@@ -3290,6 +3473,7 @@ class User {
global $wgAuth;
$this->load();
+ $this->loadPasswords();
if ( wfReadOnly() ) {
return;
}
@@ -3299,15 +3483,15 @@ class User {
$this->mTouched = self::newTouchedTimestamp();
if ( !$wgAuth->allowSetLocalPassword() ) {
- $this->mPassword = '';
+ $this->mPassword = self::getPasswordFactory()->newFromCiphertext( null );
}
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'user',
array( /* SET */
'user_name' => $this->mName,
- 'user_password' => $this->mPassword,
- 'user_newpassword' => $this->mNewpassword,
+ 'user_password' => $this->mPassword->toString(),
+ 'user_newpassword' => $this->mNewpassword->toString(),
'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ),
'user_real_name' => $this->mRealName,
'user_email' => $this->mEmail,
@@ -3316,6 +3500,7 @@ class User {
'user_token' => strval( $this->mToken ),
'user_email_token' => $this->mEmailToken,
'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
+ 'user_password_expires' => $dbw->timestampOrNull( $this->mPasswordExpires ),
), array( /* WHERE */
'user_id' => $this->mId
), __METHOD__
@@ -3350,21 +3535,25 @@ class User {
* Add a user to the database, return the user object
*
* @param string $name Username to add
- * @param array $params of Strings Non-default parameters to save to the database as user_* fields:
- * - password The user's password hash. Password logins will be disabled if this is omitted.
- * - newpassword Hash for a temporary password that has been mailed to the user
- * - email The user's email address
- * - email_authenticated The email authentication timestamp
- * - real_name The user's real name
- * - options An associative array of non-default options
- * - token Random authentication token. Do not set.
- * - registration Registration timestamp. Do not set.
- *
- * @return User object, or null if the username already exists
+ * @param array $params Array of Strings Non-default parameters to save to
+ * the database as user_* fields:
+ * - password: The user's password hash. Password logins will be disabled
+ * if this is omitted.
+ * - newpassword: Hash for a temporary password that has been mailed to
+ * the user.
+ * - email: The user's email address.
+ * - email_authenticated: The email authentication timestamp.
+ * - real_name: The user's real name.
+ * - options: An associative array of non-default options.
+ * - token: Random authentication token. Do not set.
+ * - registration: Registration timestamp. Do not set.
+ *
+ * @return User|null User object, or null if the username already exists.
*/
public static function createNew( $name, $params = array() ) {
$user = new User;
$user->load();
+ $user->loadPasswords();
$user->setToken(); // init token
if ( isset( $params['options'] ) ) {
$user->mOptions = $params['options'] + (array)$user->mOptions;
@@ -3376,8 +3565,8 @@ class User {
$fields = array(
'user_id' => $seqVal,
'user_name' => $name,
- 'user_password' => $user->mPassword,
- 'user_newpassword' => $user->mNewpassword,
+ 'user_password' => $user->mPassword->toString(),
+ 'user_newpassword' => $user->mNewpassword->toString(),
'user_newpass_time' => $dbw->timestampOrNull( $user->mNewpassTime ),
'user_email' => $user->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
@@ -3427,6 +3616,7 @@ class User {
*/
public function addToDatabase() {
$this->load();
+ $this->loadPasswords();
if ( !$this->mToken ) {
$this->setToken(); // init token
}
@@ -3440,8 +3630,8 @@ class User {
array(
'user_id' => $seqVal,
'user_name' => $this->mName,
- 'user_password' => $this->mPassword,
- 'user_newpassword' => $this->mNewpassword,
+ 'user_password' => $this->mPassword->toString(),
+ 'user_newpassword' => $this->mNewpassword->toString(),
'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ),
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
@@ -3454,17 +3644,25 @@ class User {
array( 'IGNORE' )
);
if ( !$dbw->affectedRows() ) {
- if ( !$inWrite ) {
- // XXX: Get out of REPEATABLE-READ so the SELECT below works.
- // Often this case happens early in views before any writes.
- // This shows up at least with CentralAuth.
+ // The queries below cannot happen in the same REPEATABLE-READ snapshot.
+ // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise.
+ if ( $inWrite ) {
+ // Can't commit due to pending writes that may need atomicity.
+ // This may cause some lock contention unlike the case below.
+ $options = array( 'LOCK IN SHARE MODE' );
+ $flags = self::READ_LOCKING;
+ } else {
+ // Often, this case happens early in views before any writes when
+ // using CentralAuth. It's should be OK to commit and break the snapshot.
$dbw->commit( __METHOD__, 'flush' );
+ $options = array();
+ $flags = 0;
}
$this->mId = $dbw->selectField( 'user', 'user_id',
- array( 'user_name' => $this->mName ), __METHOD__ );
+ array( 'user_name' => $this->mName ), __METHOD__, $options );
$loaded = false;
if ( $this->mId ) {
- if ( $this->loadFromDatabase() ) {
+ if ( $this->loadFromDatabase( $flags ) ) {
$loaded = true;
}
}
@@ -3516,56 +3714,6 @@ class User {
}
/**
- * Generate a string which will be different for any combination of
- * user options which would produce different parser output.
- * This will be used as part of the hash key for the parser cache,
- * so users with the same options can share the same cached data
- * safely.
- *
- * Extensions which require it should install 'PageRenderingHash' hook,
- * which will give them a chance to modify this key based on their own
- * settings.
- *
- * @deprecated since 1.17 use the ParserOptions object to get the relevant options
- * @return string Page rendering hash
- */
- public function getPageRenderingHash() {
- wfDeprecated( __METHOD__, '1.17' );
-
- global $wgRenderHashAppend, $wgLang, $wgContLang;
- if ( $this->mHash ) {
- return $this->mHash;
- }
-
- // stubthreshold is only included below for completeness,
- // since it disables the parser cache, its value will always
- // be 0 when this function is called by parsercache.
-
- $confstr = $this->getOption( 'math' );
- $confstr .= '!' . $this->getStubThreshold();
- $confstr .= '!' . ( $this->getOption( 'numberheadings' ) ? '1' : '' );
- $confstr .= '!' . $wgLang->getCode();
- $confstr .= '!' . $this->getOption( 'thumbsize' );
- // add in language specific options, if any
- $extra = $wgContLang->getExtraHashOptions();
- $confstr .= $extra;
-
- // Since the skin could be overloading link(), it should be
- // included here but in practice, none of our skins do that.
-
- $confstr .= $wgRenderHashAppend;
-
- // Give a chance for extensions to modify the hash, if they have
- // extra options or other effects on the parser cache.
- wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
-
- // Make it a valid memcached key fragment
- $confstr = str_replace( ' ', '_', $confstr );
- $this->mHash = $confstr;
- return $confstr;
- }
-
- /**
* Get whether the user is explicitly blocked from account creation.
* @return bool|Block
*/
@@ -3581,7 +3729,8 @@ class User {
if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
$this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
}
- return $this->mBlockedFromCreateAccount instanceof Block && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
+ return $this->mBlockedFromCreateAccount instanceof Block
+ && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
? $this->mBlockedFromCreateAccount
: false;
}
@@ -3599,14 +3748,14 @@ class User {
* Get whether the user is allowed to create an account.
* @return bool
*/
- function isAllowedToCreateAccount() {
+ public function isAllowedToCreateAccount() {
return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
}
/**
* Get this user's personal page title.
*
- * @return Title: User's personal page title
+ * @return Title User's personal page title
*/
public function getUserPage() {
return Title::makeTitle( NS_USER, $this->getName() );
@@ -3615,7 +3764,7 @@ class User {
/**
* Get this user's talk page title.
*
- * @return Title: User's talk page title
+ * @return Title User's talk page title
*/
public function getTalkPage() {
$title = $this->getUserPage();
@@ -3633,22 +3782,19 @@ class User {
/**
* Check to see if the given clear-text password is one of the accepted passwords
- * @param string $password user password.
- * @return boolean: True if the given password is correct, otherwise False.
+ * @param string $password User password
+ * @return bool True if the given password is correct, otherwise False
*/
public function checkPassword( $password ) {
global $wgAuth, $wgLegacyEncoding;
- $this->load();
- // Even though we stop people from creating passwords that
- // are shorter than this, doesn't mean people wont be able
- // to. Certain authentication plugins do NOT want to save
+ $section = new ProfileSection( __METHOD__ );
+
+ $this->loadPasswords();
+
+ // Certain authentication plugins do NOT want to save
// domain passwords in a mysql database, so we should
// check this (in case $wgAuth->strict() is false).
- if ( !$this->isValidPassword( $password ) ) {
- return false;
- }
-
if ( $wgAuth->authenticate( $this->getName(), $password ) ) {
return true;
} elseif ( $wgAuth->strict() ) {
@@ -3658,34 +3804,43 @@ class User {
// Auth plugin doesn't allow local authentication for this user name
return false;
}
- if ( self::comparePasswords( $this->mPassword, $password, $this->mId ) ) {
- return true;
- } elseif ( $wgLegacyEncoding ) {
- // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
- // Check for this with iconv
- $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password );
- if ( $cp1252Password != $password &&
- self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId ) )
- {
- return true;
+
+ $passwordFactory = self::getPasswordFactory();
+ if ( !$this->mPassword->equals( $password ) ) {
+ if ( $wgLegacyEncoding ) {
+ // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
+ // Check for this with iconv
+ $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password );
+ if ( $cp1252Password === $password || !$this->mPassword->equals( $cp1252Password ) ) {
+ return false;
+ }
+ } else {
+ return false;
}
}
- return false;
+
+ if ( $passwordFactory->needsUpdate( $this->mPassword ) ) {
+ $this->mPassword = $passwordFactory->newFromPlaintext( $password );
+ $this->saveSettings();
+ }
+
+ return true;
}
/**
* Check if the given clear-text password matches the temporary password
* sent by e-mail for password reset operations.
*
- * @param $plaintext string
+ * @param string $plaintext
*
- * @return boolean: True if matches, false otherwise
+ * @return bool True if matches, false otherwise
*/
public function checkTemporaryPassword( $plaintext ) {
global $wgNewPasswordExpiry;
$this->load();
- if ( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) {
+ $this->loadPasswords();
+ if ( $this->mNewpassword->equals( $plaintext ) ) {
if ( is_null( $this->mNewpassTime ) ) {
return true;
}
@@ -3700,8 +3855,8 @@ class User {
* Alias for getEditToken.
* @deprecated since 1.19, use getEditToken instead.
*
- * @param string|array $salt of Strings Optional function-specific data for hashing
- * @param $request WebRequest object to use or null to use $wgRequest
+ * @param string|array $salt Array of Strings Optional function-specific data for hashing
+ * @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
* @return string The new edit token
*/
public function editToken( $salt = '', $request = null ) {
@@ -3717,8 +3872,8 @@ class User {
*
* @since 1.19
*
- * @param string|array $salt of Strings Optional function-specific data for hashing
- * @param $request WebRequest object to use or null to use $wgRequest
+ * @param string|array $salt Array of Strings Optional function-specific data for hashing
+ * @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
* @return string The new edit token
*/
public function getEditToken( $salt = '', $request = null ) {
@@ -3727,7 +3882,7 @@ class User {
}
if ( $this->isAnon() ) {
- return EDIT_TOKEN_SUFFIX;
+ return self::EDIT_TOKEN_SUFFIX;
} else {
$token = $request->getSessionData( 'wsEditToken' );
if ( $token === null ) {
@@ -3737,7 +3892,7 @@ class User {
if ( is_array( $salt ) ) {
$salt = implode( '|', $salt );
}
- return md5( $token . $salt ) . EDIT_TOKEN_SUFFIX;
+ return md5( $token . $salt ) . self::EDIT_TOKEN_SUFFIX;
}
}
@@ -3745,7 +3900,8 @@ class User {
* Generate a looking random token for various uses.
*
* @return string The new random token
- * @deprecated since 1.20: Use MWCryptRand for secure purposes or wfRandomString for pseudo-randomness
+ * @deprecated since 1.20: Use MWCryptRand for secure purposes or
+ * wfRandomString for pseudo-randomness.
*/
public static function generateToken() {
return MWCryptRand::generateHex( 32 );
@@ -3759,14 +3915,15 @@ class User {
*
* @param string $val Input value to compare
* @param string $salt Optional function-specific data for hashing
- * @param WebRequest $request Object to use or null to use $wgRequest
- * @return boolean: Whether the token matches
+ * @param WebRequest|null $request Object to use or null to use $wgRequest
+ * @return bool Whether the token matches
*/
public function matchEditToken( $val, $salt = '', $request = null ) {
$sessionToken = $this->getEditToken( $salt, $request );
if ( $val != $sessionToken ) {
wfDebug( "User::matchEditToken: broken session data\n" );
}
+
return $val == $sessionToken;
}
@@ -3776,8 +3933,8 @@ class User {
*
* @param string $val Input value to compare
* @param string $salt Optional function-specific data for hashing
- * @param WebRequest $request object to use or null to use $wgRequest
- * @return boolean: Whether the token matches
+ * @param WebRequest|null $request Object to use or null to use $wgRequest
+ * @return bool Whether the token matches
*/
public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) {
$sessionToken = $this->getEditToken( $salt, $request );
@@ -3788,8 +3945,8 @@ class User {
* Generate a new e-mail confirmation token and send a confirmation/invalidation
* mail to the user's given address.
*
- * @param string $type message to send, either "created", "changed" or "set"
- * @return Status object
+ * @param string $type Message to send, either "created", "changed" or "set"
+ * @return Status
*/
public function sendConfirmationMail( $type = 'created' ) {
global $wgLang;
@@ -3825,19 +3982,21 @@ class User {
*
* @param string $subject Message subject
* @param string $body Message body
- * @param string $from Optional From address; if unspecified, default $wgPasswordSender will be used
+ * @param string $from Optional From address; if unspecified, default
+ * $wgPasswordSender will be used.
* @param string $replyto Reply-To address
* @return Status
*/
public function sendMail( $subject, $body, $from = null, $replyto = null ) {
if ( is_null( $from ) ) {
- global $wgPasswordSender, $wgPasswordSenderName;
- $sender = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
+ global $wgPasswordSender;
+ $sender = new MailAddress( $wgPasswordSender,
+ wfMessage( 'emailsender' )->inContentLanguage()->text() );
} else {
- $sender = new MailAddress( $from );
+ $sender = MailAddress::newFromUser( $from );
}
- $to = new MailAddress( $this );
+ $to = MailAddress::newFromUser( $this );
return UserMailer::send( $to, $sender, $subject, $body, $replyto );
}
@@ -3848,7 +4007,7 @@ class User {
* @note Call saveSettings() after calling this function to commit
* this change to the database.
*
- * @param &$expiration \mixed Accepts the expiration time
+ * @param string &$expiration Accepts the expiration time
* @return string New token
*/
protected function confirmationToken( &$expiration ) {
@@ -3926,11 +4085,12 @@ class User {
* @note Call saveSettings() after calling this function to commit the change.
* @return bool Returns true
*/
- function invalidateEmail() {
+ public function invalidateEmail() {
$this->load();
$this->mEmailToken = null;
$this->mEmailTokenExpires = null;
$this->setEmailAuthenticationTimestamp( null );
+ $this->mEmail = '';
wfRunHooks( 'InvalidateEmailComplete', array( $this ) );
return true;
}
@@ -3939,7 +4099,7 @@ class User {
* Set the e-mail authentication timestamp.
* @param string $timestamp TS_MW timestamp
*/
- function setEmailAuthenticationTimestamp( $timestamp ) {
+ public function setEmailAuthenticationTimestamp( $timestamp ) {
$this->load();
$this->mEmailAuthenticated = $timestamp;
wfRunHooks( 'UserSetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
@@ -4051,8 +4211,8 @@ class User {
/**
* Get the permissions associated with a given list of groups
*
- * @param array $groups of Strings List of internal group names
- * @return Array of Strings List of permission key names for given groups combined
+ * @param array $groups Array of Strings List of internal group names
+ * @return array Array of Strings List of permission key names for given groups combined
*/
public static function getGroupPermissions( $groups ) {
global $wgGroupPermissions, $wgRevokePermissions;
@@ -4079,7 +4239,7 @@ class User {
* Get all the groups who have a given permission
*
* @param string $role Role to check
- * @return Array of Strings List of internal group names with the given permission
+ * @return array Array of Strings List of internal group names with the given permission
*/
public static function getGroupsWithPermission( $role ) {
global $wgGroupPermissions;
@@ -4177,7 +4337,7 @@ class User {
* Return the set of defined explicit groups.
* The implicit groups (by default *, 'user' and 'autoconfirmed')
* are not included, as they are defined automatically, not in the database.
- * @return Array of internal group names
+ * @return array Array of internal group names
*/
public static function getAllGroups() {
global $wgGroupPermissions, $wgRevokePermissions;
@@ -4189,7 +4349,7 @@ class User {
/**
* Get a list of all available permissions.
- * @return Array of permission names
+ * @return array Array of permission names
*/
public static function getAllRights() {
if ( self::$mAllRights === false ) {
@@ -4206,12 +4366,15 @@ class User {
/**
* Get a list of implicit groups
- * @return Array of Strings Array of internal group names
+ * @return array Array of Strings Array of internal group names
*/
public static function getImplicitGroups() {
global $wgImplicitGroups;
+
$groups = $wgImplicitGroups;
- wfRunHooks( 'UserGetImplicitGroups', array( &$groups ) ); #deprecated, use $wgImplictGroups instead
+ # Deprecated, use $wgImplictGroups instead
+ wfRunHooks( 'UserGetImplicitGroups', array( &$groups ) );
+
return $groups;
}
@@ -4276,8 +4439,8 @@ class User {
/**
* Returns an array of the groups that a particular group can add/remove.
*
- * @param string $group the group to check for whether it can add/remove
- * @return Array array( 'add' => array( addablegroups ),
+ * @param string $group The group to check for whether it can add/remove
+ * @return array Array( 'add' => array( addablegroups ),
* 'remove' => array( removablegroups ),
* 'add-self' => array( addablegroups to self),
* 'remove-self' => array( removable groups from self) )
@@ -4285,7 +4448,13 @@ class User {
public static function changeableByGroup( $group ) {
global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
- $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() );
+ $groups = array(
+ 'add' => array(),
+ 'remove' => array(),
+ 'add-self' => array(),
+ 'remove-self' => array()
+ );
+
if ( empty( $wgAddGroups[$group] ) ) {
// Don't add anything to $groups
} elseif ( $wgAddGroups[$group] === true ) {
@@ -4341,7 +4510,7 @@ class User {
/**
* Returns an array of groups that this user can add and remove
- * @return Array array( 'add' => array( addablegroups ),
+ * @return array Array( 'add' => array( addablegroups ),
* 'remove' => array( removablegroups ),
* 'add-self' => array( addablegroups to self),
* 'remove-self' => array( removable groups from self) )
@@ -4420,8 +4589,8 @@ class User {
/**
* Initialize user_editcount from data out of the revision table
*
- * @param $add Integer Edits to add to the count from the revision table
- * @return integer Number of edits
+ * @param int $add Edits to add to the count from the revision table
+ * @return int Number of edits
*/
protected function initEditCount( $add = 0 ) {
// Pull from a slave to be less cruel to servers
@@ -4459,45 +4628,18 @@ class User {
}
/**
- * Make an old-style password hash
- *
- * @param string $password Plain-text password
- * @param string $userId User ID
- * @return string Password hash
- */
- public static function oldCrypt( $password, $userId ) {
- global $wgPasswordSalt;
- if ( $wgPasswordSalt ) {
- return md5( $userId . '-' . md5( $password ) );
- } else {
- return md5( $password );
- }
- }
-
- /**
* Make a new-style password hash
*
* @param string $password Plain-text password
* @param bool|string $salt Optional salt, may be random or the user ID.
* If unspecified or false, will generate one automatically
* @return string Password hash
+ * @deprecated since 1.24, use Password class
*/
public static function crypt( $password, $salt = false ) {
- global $wgPasswordSalt;
-
- $hash = '';
- if ( !wfRunHooks( 'UserCryptPassword', array( &$password, &$salt, &$wgPasswordSalt, &$hash ) ) ) {
- return $hash;
- }
-
- if ( $wgPasswordSalt ) {
- if ( $salt === false ) {
- $salt = MWCryptRand::generateHex( 8 );
- }
- return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) );
- } else {
- return ':A:' . md5( $password );
- }
+ wfDeprecated( __METHOD__, '1.24' );
+ $hash = self::getPasswordFactory()->newFromPlaintext( $password );
+ return $hash->toString();
}
/**
@@ -4508,34 +4650,32 @@ class User {
* @param string $password Plain-text password to compare
* @param string|bool $userId User ID for old-style password salt
*
- * @return boolean
+ * @return bool
+ * @deprecated since 1.24, use Password class
*/
public static function comparePasswords( $hash, $password, $userId = false ) {
- $type = substr( $hash, 0, 3 );
-
- $result = false;
- if ( !wfRunHooks( 'UserComparePasswords', array( &$hash, &$password, &$userId, &$result ) ) ) {
- return $result;
+ wfDeprecated( __METHOD__, '1.24' );
+
+ // Check for *really* old password hashes that don't even have a type
+ // The old hash format was just an md5 hex hash, with no type information
+ if ( preg_match( '/^[0-9a-f]{32}$/', $hash ) ) {
+ global $wgPasswordSalt;
+ if ( $wgPasswordSalt ) {
+ $password = ":B:{$userId}:{$hash}";
+ } else {
+ $password = ":A:{$hash}";
+ }
}
- if ( $type == ':A:' ) {
- // Unsalted
- return md5( $password ) === substr( $hash, 3 );
- } elseif ( $type == ':B:' ) {
- // Salted
- list( $salt, $realHash ) = explode( ':', substr( $hash, 3 ), 2 );
- return md5( $salt . '-' . md5( $password ) ) === $realHash;
- } else {
- // Old-style
- return self::oldCrypt( $password, $userId ) === $hash;
- }
+ $hash = self::getPasswordFactory()->newFromCiphertext( $hash );
+ return $hash->equals( $password );
}
/**
* Add a newuser log entry for this user.
* Before 1.19 the return value was always true.
*
- * @param string|bool $action account creation type.
+ * @param string|bool $action Account creation type.
* - String, one of the following values:
* - 'create' for an anonymous user creating an account for himself.
* This will force the action's performer to be the created user itself,
@@ -4548,7 +4688,7 @@ class User {
* - false will be converted to 'create' if this object is the same as
* $wgUser and to 'create2' otherwise
*
- * @param string $reason user supplied reason
+ * @param string $reason User supplied reason
*
* @return int|bool True if not $wgNewUserLog; otherwise ID of log item or 0 on failure
*/
@@ -4668,7 +4808,9 @@ class User {
}
/**
- * @todo document
+ * Saves the non-default options for this user, as previously set e.g. via
+ * setOption(), in the database's "user_properties" (preferences) table.
+ * Usually used via saveSettings().
*/
protected function saveOptions() {
$this->loadOptions();
@@ -4683,37 +4825,68 @@ class User {
}
$userId = $this->getId();
- $insert_rows = array();
+
+ $insert_rows = array(); // all the new preference rows
foreach ( $saveOptions as $key => $value ) {
// Don't bother storing default values
$defaultOption = self::getDefaultOption( $key );
- if ( ( is_null( $defaultOption ) &&
- !( $value === false || is_null( $value ) ) ) ||
- $value != $defaultOption ) {
+ if ( ( $defaultOption === null && $value !== false && $value !== null )
+ || $value != $defaultOption
+ ) {
$insert_rows[] = array(
- 'up_user' => $userId,
- 'up_property' => $key,
- 'up_value' => $value,
- );
+ 'up_user' => $userId,
+ 'up_property' => $key,
+ 'up_value' => $value,
+ );
}
}
$dbw = wfGetDB( DB_MASTER );
- $hasRows = $dbw->selectField( 'user_properties', '1',
- array( 'up_user' => $userId ), __METHOD__ );
- if ( $hasRows ) {
- // Only do this delete if there is something there. A very large portion of
- // calls to this function are for setting 'rememberpassword' for new accounts.
- // Doing this delete for new accounts with no rows in the table rougly causes
- // gap locks on [max user ID,+infinity) which causes high contention since many
- // updates will pile up on each other since they are for higher (newer) user IDs.
- $dbw->delete( 'user_properties', array( 'up_user' => $userId ), __METHOD__ );
+ $res = $dbw->select( 'user_properties',
+ array( 'up_property', 'up_value' ), array( 'up_user' => $userId ), __METHOD__ );
+
+ // Find prior rows that need to be removed or updated. These rows will
+ // all be deleted (the later so that INSERT IGNORE applies the new values).
+ $keysDelete = array();
+ foreach ( $res as $row ) {
+ if ( !isset( $saveOptions[$row->up_property] )
+ || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
+ ) {
+ $keysDelete[] = $row->up_property;
+ }
}
+
+ if ( count( $keysDelete ) ) {
+ // Do the DELETE by PRIMARY KEY for prior rows.
+ // In the past a very large portion of calls to this function are for setting
+ // 'rememberpassword' for new accounts (a preference that has since been removed).
+ // Doing a blanket per-user DELETE for new accounts with no rows in the table
+ // caused gap locks on [max user ID,+infinity) which caused high contention since
+ // updates would pile up on each other as they are for higher (newer) user IDs.
+ // It might not be necessary these days, but it shouldn't hurt either.
+ $dbw->delete( 'user_properties',
+ array( 'up_user' => $userId, 'up_property' => $keysDelete ), __METHOD__ );
+ }
+ // Insert the new preference rows
$dbw->insert( 'user_properties', $insert_rows, __METHOD__, array( 'IGNORE' ) );
}
/**
+ * Lazily instantiate and return a factory object for making passwords
+ *
+ * @return PasswordFactory
+ */
+ public static function getPasswordFactory() {
+ if ( self::$mPasswordFactory === null ) {
+ self::$mPasswordFactory = new PasswordFactory();
+ self::$mPasswordFactory->init( RequestContext::getMain()->getConfig() );
+ }
+
+ return self::$mPasswordFactory;
+ }
+
+ /**
* Provide an array of HTML5 attributes to put on an input element
* intended for the user to enter a new password. This may include
* required, title, and/or pattern, depending on $wgMinimalPasswordLength.
@@ -4781,9 +4954,6 @@ class User {
'user_id',
'user_name',
'user_real_name',
- 'user_password',
- 'user_newpassword',
- 'user_newpass_time',
'user_email',
'user_touched',
'user_token',
diff --git a/includes/UserArray.php b/includes/UserArray.php
index 1f55ef35..7da65827 100644
--- a/includes/UserArray.php
+++ b/includes/UserArray.php
@@ -1,6 +1,6 @@
<?php
/**
- * Classes to walk into a list of User objects.
+ * Class to walk into a list of User objects.
*
* 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
@@ -22,7 +22,7 @@
abstract class UserArray implements Iterator {
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper $res
* @return UserArrayFromResult
*/
static function newFromResult( $res ) {
@@ -37,7 +37,7 @@ abstract class UserArray implements Iterator {
}
/**
- * @param $ids array
+ * @param array $ids
* @return UserArrayFromResult
*/
static function newFromIDs( $ids ) {
@@ -47,83 +47,20 @@ abstract class UserArray implements Iterator {
return new ArrayIterator( array() );
}
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'user', '*', array( 'user_id' => $ids ),
- __METHOD__ );
+ $res = $dbr->select(
+ 'user',
+ User::selectFields(),
+ array( 'user_id' => array_unique( $ids ) ),
+ __METHOD__
+ );
return self::newFromResult( $res );
}
/**
- * @param $res
+ * @param ResultWrapper $res
* @return UserArrayFromResult
*/
protected static function newFromResult_internal( $res ) {
return new UserArrayFromResult( $res );
}
}
-
-class UserArrayFromResult extends UserArray {
-
- /**
- * @var ResultWrapper
- */
- var $res;
- var $key, $current;
-
- /**
- * @param $res ResultWrapper
- */
- function __construct( $res ) {
- $this->res = $res;
- $this->key = 0;
- $this->setCurrent( $this->res->current() );
- }
-
- /**
- * @param $row
- * @return void
- */
- protected function setCurrent( $row ) {
- if ( $row === false ) {
- $this->current = false;
- } else {
- $this->current = User::newFromRow( $row );
- }
- }
-
- /**
- * @return int
- */
- public function count() {
- return $this->res->numRows();
- }
-
- /**
- * @return User
- */
- function current() {
- return $this->current;
- }
-
- function key() {
- return $this->key;
- }
-
- function next() {
- $row = $this->res->next();
- $this->setCurrent( $row );
- $this->key++;
- }
-
- function rewind() {
- $this->res->rewind();
- $this->key = 0;
- $this->setCurrent( $this->res->current() );
- }
-
- /**
- * @return bool
- */
- function valid() {
- return $this->current !== false;
- }
-}
diff --git a/includes/UserArrayFromResult.php b/includes/UserArrayFromResult.php
new file mode 100644
index 00000000..fb533d08
--- /dev/null
+++ b/includes/UserArrayFromResult.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Class to walk into a list of User objects.
+ *
+ * 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 UserArrayFromResult extends UserArray implements Countable {
+ /** @var ResultWrapper */
+ public $res;
+
+ /** @var int */
+ public $key;
+
+ /** @var bool|stdClass */
+ public $current;
+
+ /**
+ * @param ResultWrapper $res
+ */
+ function __construct( $res ) {
+ $this->res = $res;
+ $this->key = 0;
+ $this->setCurrent( $this->res->current() );
+ }
+
+ /**
+ * @param bool|stdClass $row
+ * @return void
+ */
+ protected function setCurrent( $row ) {
+ if ( $row === false ) {
+ $this->current = false;
+ } else {
+ $this->current = User::newFromRow( $row );
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function count() {
+ return $this->res->numRows();
+ }
+
+ /**
+ * @return User
+ */
+ function current() {
+ return $this->current;
+ }
+
+ function key() {
+ return $this->key;
+ }
+
+ function next() {
+ $row = $this->res->next();
+ $this->setCurrent( $row );
+ $this->key++;
+ }
+
+ function rewind() {
+ $this->res->rewind();
+ $this->key = 0;
+ $this->setCurrent( $this->res->current() );
+ }
+
+ /**
+ * @return bool
+ */
+ function valid() {
+ return $this->current !== false;
+ }
+}
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
deleted file mode 100644
index 163f8361..00000000
--- a/includes/UserMailer.php
+++ /dev/null
@@ -1,883 +0,0 @@
-<?php
-/**
- * Classes used to send e-mails
- *
- * 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 <brion@pobox.com>
- * @author <mail@tgries.de>
- * @author Tim Starling
- * @author Luke Welling lwelling@wikimedia.org
- */
-
-/**
- * Stores a single person's name and email address.
- * These are passed in via the constructor, and will be returned in SMTP
- * header format when requested.
- */
-class MailAddress {
- /**
- * @param string|User $address string with an email address, or a User object
- * @param string $name human-readable name if a string address is given
- * @param string $realName human-readable real name if a string address is given
- */
- function __construct( $address, $name = null, $realName = null ) {
- if ( is_object( $address ) && $address instanceof User ) {
- $this->address = $address->getEmail();
- $this->name = $address->getName();
- $this->realName = $address->getRealName();
- } else {
- $this->address = strval( $address );
- $this->name = strval( $name );
- $this->realName = strval( $realName );
- }
- }
-
- /**
- * Return formatted and quoted address to insert into SMTP headers
- * @return string
- */
- function toString() {
- # PHP's mail() implementation under Windows is somewhat shite, and
- # can't handle "Joe Bloggs <joe@bloggs.com>" format email addresses,
- # so don't bother generating them
- if ( $this->address ) {
- if ( $this->name != '' && !wfIsWindows() ) {
- global $wgEnotifUseRealName;
- $name = ( $wgEnotifUseRealName && $this->realName ) ? $this->realName : $this->name;
- $quoted = UserMailer::quotedPrintable( $name );
- if ( strpos( $quoted, '.' ) !== false || strpos( $quoted, ',' ) !== false ) {
- $quoted = '"' . $quoted . '"';
- }
- return "$quoted <{$this->address}>";
- } else {
- return $this->address;
- }
- } else {
- return "";
- }
- }
-
- function __toString() {
- return $this->toString();
- }
-}
-
-/**
- * Collection of static functions for sending mail
- */
-class UserMailer {
- static $mErrorString;
-
- /**
- * Send mail using a PEAR mailer
- *
- * @param $mailer
- * @param $dest
- * @param $headers
- * @param $body
- *
- * @return Status
- */
- protected static function sendWithPear( $mailer, $dest, $headers, $body ) {
- $mailResult = $mailer->send( $dest, $headers, $body );
-
- # Based on the result return an error string,
- if ( PEAR::isError( $mailResult ) ) {
- wfDebug( "PEAR::Mail failed: " . $mailResult->getMessage() . "\n" );
- return Status::newFatal( 'pear-mail-error', $mailResult->getMessage() );
- } else {
- return Status::newGood();
- }
- }
-
- /**
- * Creates a single string from an associative array
- *
- * @param array $headers Associative Array: keys are header field names,
- * values are ... values.
- * @param string $endl The end of line character. Defaults to "\n"
- *
- * Note RFC2822 says newlines must be CRLF (\r\n)
- * but php mail naively "corrects" it and requires \n for the "correction" to work
- *
- * @return String
- */
- static function arrayToHeaderString( $headers, $endl = "\n" ) {
- $strings = array();
- foreach ( $headers as $name => $value ) {
- $strings[] = "$name: $value";
- }
- return implode( $endl, $strings );
- }
-
- /**
- * Create a value suitable for the MessageId Header
- *
- * @return String
- */
- static function makeMsgId() {
- global $wgSMTP, $wgServer;
-
- $msgid = uniqid( wfWikiID() . ".", true ); /* true required for cygwin */
- if ( is_array( $wgSMTP ) && isset( $wgSMTP['IDHost'] ) && $wgSMTP['IDHost'] ) {
- $domain = $wgSMTP['IDHost'];
- } else {
- $url = wfParseUrl( $wgServer );
- $domain = $url['host'];
- }
- return "<$msgid@$domain>";
- }
-
- /**
- * This function will perform a direct (authenticated) login to
- * a SMTP Server to use for mail relaying if 'wgSMTP' specifies an
- * array of parameters. It requires PEAR:Mail to do that.
- * Otherwise it just uses the standard PHP 'mail' function.
- *
- * @param $to MailAddress: recipient's email (or an array of them)
- * @param $from MailAddress: sender's email
- * @param string $subject email's subject.
- * @param string $body email's text or Array of two strings to be the text and html bodies
- * @param $replyto MailAddress: optional reply-to email (default: null).
- * @param string $contentType optional custom Content-Type (default: text/plain; charset=UTF-8)
- * @throws MWException
- * @return Status object
- */
- public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = 'text/plain; charset=UTF-8' ) {
- global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams, $wgAllowHTMLEmail;
- $mime = null;
- if ( !is_array( $to ) ) {
- $to = array( $to );
- }
-
- // mail body must have some content
- $minBodyLen = 10;
- // arbitrary but longer than Array or Object to detect casting error
-
- // body must either be a string or an array with text and body
- if (
- !(
- !is_array( $body ) &&
- strlen( $body ) >= $minBodyLen
- )
- &&
- !(
- is_array( $body ) &&
- isset( $body['text'] ) &&
- isset( $body['html'] ) &&
- strlen( $body['text'] ) >= $minBodyLen &&
- strlen( $body['html'] ) >= $minBodyLen
- )
- ) {
- // if it is neither we have a problem
- return Status::newFatal( 'user-mail-no-body' );
- }
-
- if ( !$wgAllowHTMLEmail && is_array( $body ) ) {
- // HTML not wanted. Dump it.
- $body = $body['text'];
- }
-
- wfDebug( __METHOD__ . ': sending mail to ' . implode( ', ', $to ) . "\n" );
-
- # Make sure we have at least one address
- $has_address = false;
- foreach ( $to as $u ) {
- if ( $u->address ) {
- $has_address = true;
- break;
- }
- }
- if ( !$has_address ) {
- return Status::newFatal( 'user-mail-no-addy' );
- }
-
- # Forge email headers
- # -------------------
- #
- # WARNING
- #
- # DO NOT add To: or Subject: headers at this step. They need to be
- # handled differently depending upon the mailer we are going to use.
- #
- # To:
- # PHP mail() first argument is the mail receiver. The argument is
- # used as a recipient destination and as a To header.
- #
- # PEAR mailer has a recipient argument which is only used to
- # send the mail. If no To header is given, PEAR will set it to
- # to 'undisclosed-recipients:'.
- #
- # NOTE: To: is for presentation, the actual recipient is specified
- # by the mailer using the Rcpt-To: header.
- #
- # Subject:
- # PHP mail() second argument to pass the subject, passing a Subject
- # as an additional header will result in a duplicate header.
- #
- # PEAR mailer should be passed a Subject header.
- #
- # -- hashar 20120218
-
- $headers['From'] = $from->toString();
- $headers['Return-Path'] = $from->address;
-
- if ( $replyto ) {
- $headers['Reply-To'] = $replyto->toString();
- }
-
- $headers['Date'] = MWTimestamp::getLocalInstance()->format( 'r' );
- $headers['Message-ID'] = self::makeMsgId();
- $headers['X-Mailer'] = 'MediaWiki mailer';
-
- # Line endings need to be different on Unix and Windows due to
- # the bug described at http://trac.wordpress.org/ticket/2603
- if ( wfIsWindows() ) {
- $endl = "\r\n";
- } else {
- $endl = "\n";
- }
-
- if ( is_array( $body ) ) {
- // we are sending a multipart message
- wfDebug( "Assembling multipart mime email\n" );
- if ( !stream_resolve_include_path( 'Mail/mime.php' ) ) {
- wfDebug( "PEAR Mail_Mime package is not installed. Falling back to text email.\n" );
- // remove the html body for text email fall back
- $body = $body['text'];
- }
- else {
- require_once 'Mail/mime.php';
- if ( wfIsWindows() ) {
- $body['text'] = str_replace( "\n", "\r\n", $body['text'] );
- $body['html'] = str_replace( "\n", "\r\n", $body['html'] );
- }
- $mime = new Mail_mime( array( 'eol' => $endl, 'text_charset' => 'UTF-8', 'html_charset' => 'UTF-8' ) );
- $mime->setTXTBody( $body['text'] );
- $mime->setHTMLBody( $body['html'] );
- $body = $mime->get(); // must call get() before headers()
- $headers = $mime->headers( $headers );
- }
- }
- if ( !isset( $mime ) ) {
- // sending text only, either deliberately or as a fallback
- if ( wfIsWindows() ) {
- $body = str_replace( "\n", "\r\n", $body );
- }
- $headers['MIME-Version'] = '1.0';
- $headers['Content-type'] = ( is_null( $contentType ) ?
- 'text/plain; charset=UTF-8' : $contentType );
- $headers['Content-transfer-encoding'] = '8bit';
- }
-
- $ret = wfRunHooks( 'AlternateUserMailer', array( $headers, $to, $from, $subject, $body ) );
- if ( $ret === false ) {
- // the hook implementation will return false to skip regular mail sending
- return Status::newGood();
- } elseif ( $ret !== true ) {
- // the hook implementation will return a string to pass an error message
- return Status::newFatal( 'php-mail-error', $ret );
- }
-
- if ( is_array( $wgSMTP ) ) {
- #
- # PEAR MAILER
- #
-
- if ( !stream_resolve_include_path( 'Mail.php' ) ) {
- throw new MWException( 'PEAR mail package is not installed' );
- }
- require_once 'Mail.php';
-
- wfSuppressWarnings();
-
- // Create the mail object using the Mail::factory method
- $mail_object =& Mail::factory( 'smtp', $wgSMTP );
- if ( PEAR::isError( $mail_object ) ) {
- wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" );
- wfRestoreWarnings();
- return Status::newFatal( 'pear-mail-error', $mail_object->getMessage() );
- }
-
- wfDebug( "Sending mail via PEAR::Mail\n" );
-
- $headers['Subject'] = self::quotedPrintable( $subject );
-
- # When sending only to one recipient, shows it its email using To:
- if ( count( $to ) == 1 ) {
- $headers['To'] = $to[0]->toString();
- }
-
- # Split jobs since SMTP servers tends to limit the maximum
- # number of possible recipients.
- $chunks = array_chunk( $to, $wgEnotifMaxRecips );
- foreach ( $chunks as $chunk ) {
- $status = self::sendWithPear( $mail_object, $chunk, $headers, $body );
- # FIXME : some chunks might be sent while others are not!
- if ( !$status->isOK() ) {
- wfRestoreWarnings();
- return $status;
- }
- }
- wfRestoreWarnings();
- return Status::newGood();
- } else {
- #
- # PHP mail()
- #
- if ( count( $to ) > 1 ) {
- $headers['To'] = 'undisclosed-recipients:;';
- }
- $headers = self::arrayToHeaderString( $headers, $endl );
-
- wfDebug( "Sending mail via internal mail() function\n" );
-
- self::$mErrorString = '';
- $html_errors = ini_get( 'html_errors' );
- ini_set( 'html_errors', '0' );
- set_error_handler( 'UserMailer::errorHandler' );
-
- $safeMode = wfIniGetBool( 'safe_mode' );
-
- foreach ( $to as $recip ) {
- if ( $safeMode ) {
- $sent = mail( $recip, self::quotedPrintable( $subject ), $body, $headers );
- } else {
- $sent = mail( $recip, self::quotedPrintable( $subject ), $body, $headers, $wgAdditionalMailParams );
- }
- }
-
- restore_error_handler();
- ini_set( 'html_errors', $html_errors );
-
- if ( self::$mErrorString ) {
- wfDebug( "Error sending mail: " . self::$mErrorString . "\n" );
- return Status::newFatal( 'php-mail-error', self::$mErrorString );
- } elseif ( ! $sent ) {
- // mail function only tells if there's an error
- wfDebug( "Unknown error sending mail\n" );
- return Status::newFatal( 'php-mail-error-unknown' );
- } else {
- return Status::newGood();
- }
- }
- }
-
- /**
- * Set the mail error message in self::$mErrorString
- *
- * @param $code Integer: error number
- * @param string $string error message
- */
- static function errorHandler( $code, $string ) {
- self::$mErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
- }
-
- /**
- * Converts a string into a valid RFC 822 "phrase", such as is used for the sender name
- * @param $phrase string
- * @return string
- */
- public static function rfc822Phrase( $phrase ) {
- $phrase = strtr( $phrase, array( "\r" => '', "\n" => '', '"' => '' ) );
- return '"' . $phrase . '"';
- }
-
- /**
- * Converts a string into quoted-printable format
- * @since 1.17
- *
- * From PHP5.3 there is a built in function quoted_printable_encode()
- * This method does not duplicate that.
- * This method is doing Q encoding inside encoded-words as defined by RFC 2047
- * This is for email headers.
- * The built in quoted_printable_encode() is for email bodies
- * @return string
- */
- public static function quotedPrintable( $string, $charset = '' ) {
- # Probably incomplete; see RFC 2045
- if ( empty( $charset ) ) {
- $charset = 'UTF-8';
- }
- $charset = strtoupper( $charset );
- $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
-
- $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
- $replace = $illegal . '\t ?_';
- if ( !preg_match( "/[$illegal]/", $string ) ) {
- return $string;
- }
- $out = "=?$charset?Q?";
- $out .= preg_replace_callback( "/([$replace])/",
- array( __CLASS__, 'quotedPrintableCallback' ), $string );
- $out .= '?=';
- return $out;
- }
-
- protected static function quotedPrintableCallback( $matches ) {
- return sprintf( "=%02X", ord( $matches[1] ) );
- }
-}
-
-/**
- * This module processes the email notifications when the current page is
- * changed. It looks up the table watchlist to find out which users are watching
- * that page.
- *
- * The current implementation sends independent emails to each watching user for
- * the following reason:
- *
- * - Each watching user will be notified about the page edit time expressed in
- * his/her local time (UTC is shown additionally). To achieve this, we need to
- * find the individual timeoffset of each watching user from the preferences..
- *
- * Suggested improvement to slack down the number of sent emails: We could think
- * of sending out bulk mails (bcc:user1,user2...) for all these users having the
- * same timeoffset in their preferences.
- *
- * Visit the documentation pages under http://meta.wikipedia.com/Enotif
- *
- *
- */
-class EmailNotification {
- protected $subject, $body, $replyto, $from;
- protected $timestamp, $summary, $minorEdit, $oldid, $composed_common, $pageStatus;
- protected $mailTargets = array();
-
- /**
- * @var Title
- */
- protected $title;
-
- /**
- * @var User
- */
- protected $editor;
-
- /**
- * Send emails corresponding to the user $editor editing the page $title.
- * Also updates wl_notificationtimestamp.
- *
- * May be deferred via the job queue.
- *
- * @param $editor User object
- * @param $title Title object
- * @param $timestamp
- * @param $summary
- * @param $minorEdit
- * @param $oldid (default: false)
- * @param $pageStatus (default: 'changed')
- */
- public function notifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid = false, $pageStatus = 'changed' ) {
- global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker, $wgEnotifMinorEdits,
- $wgUsersNotifiedOnAllChanges, $wgEnotifUserTalk;
-
- if ( $title->getNamespace() < 0 ) {
- return;
- }
-
- // Build a list of users to notify
- $watchers = array();
- if ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) {
- $dbw = wfGetDB( DB_MASTER );
- $res = $dbw->select( array( 'watchlist' ),
- array( 'wl_user' ),
- array(
- 'wl_user != ' . intval( $editor->getID() ),
- 'wl_namespace' => $title->getNamespace(),
- 'wl_title' => $title->getDBkey(),
- 'wl_notificationtimestamp IS NULL',
- ), __METHOD__
- );
- foreach ( $res as $row ) {
- $watchers[] = intval( $row->wl_user );
- }
- if ( $watchers ) {
- // Update wl_notificationtimestamp for all watching users except the editor
- $fname = __METHOD__;
- $dbw->onTransactionIdle(
- function() use ( $dbw, $timestamp, $watchers, $title, $fname ) {
- $dbw->begin( $fname );
- $dbw->update( 'watchlist',
- array( /* SET */
- 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
- ), array( /* WHERE */
- 'wl_user' => $watchers,
- 'wl_namespace' => $title->getNamespace(),
- 'wl_title' => $title->getDBkey(),
- ), $fname
- );
- $dbw->commit( $fname );
- }
- );
- }
- }
-
- $sendEmail = true;
- // If nobody is watching the page, and there are no users notified on all changes
- // don't bother creating a job/trying to send emails
- // $watchers deals with $wgEnotifWatchlist
- if ( !count( $watchers ) && !count( $wgUsersNotifiedOnAllChanges ) ) {
- $sendEmail = false;
- // Only send notification for non minor edits, unless $wgEnotifMinorEdits
- if ( !$minorEdit || ( $wgEnotifMinorEdits && !$editor->isAllowed( 'nominornewtalk' ) ) ) {
- $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
- if ( $wgEnotifUserTalk && $isUserTalkPage && $this->canSendUserTalkEmail( $editor, $title, $minorEdit ) ) {
- $sendEmail = true;
- }
- }
- }
-
- if ( !$sendEmail ) {
- return;
- }
- if ( $wgEnotifUseJobQ ) {
- $params = array(
- 'editor' => $editor->getName(),
- 'editorID' => $editor->getID(),
- 'timestamp' => $timestamp,
- 'summary' => $summary,
- 'minorEdit' => $minorEdit,
- 'oldid' => $oldid,
- 'watchers' => $watchers,
- 'pageStatus' => $pageStatus
- );
- $job = new EnotifNotifyJob( $title, $params );
- JobQueueGroup::singleton()->push( $job );
- } else {
- $this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers, $pageStatus );
- }
- }
-
- /**
- * Immediate version of notifyOnPageChange().
- *
- * Send emails corresponding to the user $editor editing the page $title.
- * Also updates wl_notificationtimestamp.
- *
- * @param $editor User object
- * @param $title Title object
- * @param string $timestamp Edit timestamp
- * @param string $summary Edit summary
- * @param $minorEdit bool
- * @param int $oldid Revision ID
- * @param array $watchers of user IDs
- * @param string $pageStatus
- * @throws MWException
- */
- public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit,
- $oldid, $watchers, $pageStatus = 'changed' ) {
- # we use $wgPasswordSender as sender's address
- global $wgEnotifWatchlist;
- global $wgEnotifMinorEdits, $wgEnotifUserTalk;
-
- wfProfileIn( __METHOD__ );
-
- # The following code is only run, if several conditions are met:
- # 1. EmailNotification for pages (other than user_talk pages) must be enabled
- # 2. minor edits (changes) are only regarded if the global flag indicates so
-
- $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
-
- $this->title = $title;
- $this->timestamp = $timestamp;
- $this->summary = $summary;
- $this->minorEdit = $minorEdit;
- $this->oldid = $oldid;
- $this->editor = $editor;
- $this->composed_common = false;
- $this->pageStatus = $pageStatus;
-
- $formattedPageStatus = array( 'deleted', 'created', 'moved', 'restored', 'changed' );
-
- wfRunHooks( 'UpdateUserMailerFormattedPageStatus', array( &$formattedPageStatus ) );
- if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) {
- wfProfileOut( __METHOD__ );
- throw new MWException( 'Not a valid page status!' );
- }
-
- $userTalkId = false;
-
- if ( !$minorEdit || ( $wgEnotifMinorEdits && !$editor->isAllowed( 'nominornewtalk' ) ) ) {
-
- if ( $wgEnotifUserTalk && $isUserTalkPage && $this->canSendUserTalkEmail( $editor, $title, $minorEdit ) ) {
- $targetUser = User::newFromName( $title->getText() );
- $this->compose( $targetUser );
- $userTalkId = $targetUser->getId();
- }
-
- if ( $wgEnotifWatchlist ) {
- // Send updates to watchers other than the current editor
- $userArray = UserArray::newFromIDs( $watchers );
- foreach ( $userArray as $watchingUser ) {
- if ( $watchingUser->getOption( 'enotifwatchlistpages' ) &&
- ( !$minorEdit || $watchingUser->getOption( 'enotifminoredits' ) ) &&
- $watchingUser->isEmailConfirmed() &&
- $watchingUser->getID() != $userTalkId )
- {
- $this->compose( $watchingUser );
- }
- }
- }
- }
-
- global $wgUsersNotifiedOnAllChanges;
- foreach ( $wgUsersNotifiedOnAllChanges as $name ) {
- if ( $editor->getName() == $name ) {
- // No point notifying the user that actually made the change!
- continue;
- }
- $user = User::newFromName( $name );
- $this->compose( $user );
- }
-
- $this->sendMails();
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * @param $editor User
- * @param $title Title bool
- * @param $minorEdit
- * @return bool
- */
- private function canSendUserTalkEmail( $editor, $title, $minorEdit ) {
- global $wgEnotifUserTalk;
- $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
-
- if ( $wgEnotifUserTalk && $isUserTalkPage ) {
- $targetUser = User::newFromName( $title->getText() );
-
- if ( !$targetUser || $targetUser->isAnon() ) {
- wfDebug( __METHOD__ . ": user talk page edited, but user does not exist\n" );
- } elseif ( $targetUser->getId() == $editor->getId() ) {
- wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent\n" );
- } elseif ( $targetUser->getOption( 'enotifusertalkpages' ) &&
- ( !$minorEdit || $targetUser->getOption( 'enotifminoredits' ) ) )
- {
- if ( !$targetUser->isEmailConfirmed() ) {
- wfDebug( __METHOD__ . ": talk page owner doesn't have validated email\n" );
- } elseif ( !wfRunHooks( 'AbortTalkPageEmailNotification', array( $targetUser, $title ) ) ) {
- wfDebug( __METHOD__ . ": talk page update notification is aborted for this user\n" );
- } else {
- wfDebug( __METHOD__ . ": sending talk page update notification\n" );
- return true;
- }
- } else {
- wfDebug( __METHOD__ . ": talk page owner doesn't want notifications\n" );
- }
- }
- return false;
- }
-
- /**
- * Generate the generic "this page has been changed" e-mail text.
- */
- private function composeCommonMailtext() {
- global $wgPasswordSender, $wgPasswordSenderName, $wgNoReplyAddress;
- global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
- global $wgEnotifImpersonal, $wgEnotifUseRealName;
-
- $this->composed_common = true;
-
- # You as the WikiAdmin and Sysops can make use of plenty of
- # named variables when composing your notification emails while
- # simply editing the Meta pages
-
- $keys = array();
- $postTransformKeys = array();
- $pageTitleUrl = $this->title->getCanonicalURL();
- $pageTitle = $this->title->getPrefixedText();
-
- if ( $this->oldid ) {
- // Always show a link to the diff which triggered the mail. See bug 32210.
- $keys['$NEWPAGE'] = "\n\n" . wfMessage( 'enotif_lastdiff',
- $this->title->getCanonicalURL( array( 'diff' => 'next', 'oldid' => $this->oldid ) ) )
- ->inContentLanguage()->text();
-
- if ( !$wgEnotifImpersonal ) {
- // For personal mail, also show a link to the diff of all changes
- // since last visited.
- $keys['$NEWPAGE'] .= "\n\n" . wfMessage( 'enotif_lastvisited',
- $this->title->getCanonicalURL( array( 'diff' => '0', 'oldid' => $this->oldid ) ) )
- ->inContentLanguage()->text();
- }
- $keys['$OLDID'] = $this->oldid;
- // @deprecated Remove in MediaWiki 1.23.
- $keys['$CHANGEDORCREATED'] = wfMessage( 'changed' )->inContentLanguage()->text();
- } else {
- # clear $OLDID placeholder in the message template
- $keys['$OLDID'] = '';
- $keys['$NEWPAGE'] = '';
- // @deprecated Remove in MediaWiki 1.23.
- $keys['$CHANGEDORCREATED'] = wfMessage( 'created' )->inContentLanguage()->text();
- }
-
- $keys['$PAGETITLE'] = $this->title->getPrefixedText();
- $keys['$PAGETITLE_URL'] = $this->title->getCanonicalURL();
- $keys['$PAGEMINOREDIT'] = $this->minorEdit ?
- wfMessage( 'minoredit' )->inContentLanguage()->text() : '';
- $keys['$UNWATCHURL'] = $this->title->getCanonicalURL( 'action=unwatch' );
-
- if ( $this->editor->isAnon() ) {
- # real anon (user:xxx.xxx.xxx.xxx)
- $keys['$PAGEEDITOR'] = wfMessage( 'enotif_anon_editor', $this->editor->getName() )
- ->inContentLanguage()->text();
- $keys['$PAGEEDITOR_EMAIL'] = wfMessage( 'noemailtitle' )->inContentLanguage()->text();
-
- } else {
- $keys['$PAGEEDITOR'] = $wgEnotifUseRealName ? $this->editor->getRealName() : $this->editor->getName();
- $emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $this->editor->getName() );
- $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getCanonicalURL();
- }
-
- $keys['$PAGEEDITOR_WIKI'] = $this->editor->getUserPage()->getCanonicalURL();
- $keys['$HELPPAGE'] = wfExpandUrl( Skin::makeInternalOrExternalUrl( wfMessage( 'helppage' )->inContentLanguage()->text() ) );
-
- # Replace this after transforming the message, bug 35019
- $postTransformKeys['$PAGESUMMARY'] = $this->summary == '' ? ' - ' : $this->summary;
-
- // Now build message's subject and body
-
- // Messages:
- // enotif_subject_deleted, enotif_subject_created, enotif_subject_moved,
- // enotif_subject_restored, enotif_subject_changed
- $this->subject = wfMessage( 'enotif_subject_' . $this->pageStatus )->inContentLanguage()
- ->params( $pageTitle, $keys['$PAGEEDITOR'] )->text();
-
- // Messages:
- // enotif_body_intro_deleted, enotif_body_intro_created, enotif_body_intro_moved,
- // enotif_body_intro_restored, enotif_body_intro_changed
- $keys['$PAGEINTRO'] = wfMessage( 'enotif_body_intro_' . $this->pageStatus )
- ->inContentLanguage()->params( $pageTitle, $keys['$PAGEEDITOR'], $pageTitleUrl )
- ->text();
-
- $body = wfMessage( 'enotif_body' )->inContentLanguage()->plain();
- $body = strtr( $body, $keys );
- $body = MessageCache::singleton()->transform( $body, false, null, $this->title );
- $this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
-
- # Reveal the page editor's address as REPLY-TO address only if
- # the user has not opted-out and the option is enabled at the
- # global configuration level.
- $adminAddress = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
- if ( $wgEnotifRevealEditorAddress
- && ( $this->editor->getEmail() != '' )
- && $this->editor->getOption( 'enotifrevealaddr' ) )
- {
- $editorAddress = new MailAddress( $this->editor );
- if ( $wgEnotifFromEditor ) {
- $this->from = $editorAddress;
- } else {
- $this->from = $adminAddress;
- $this->replyto = $editorAddress;
- }
- } else {
- $this->from = $adminAddress;
- $this->replyto = new MailAddress( $wgNoReplyAddress );
- }
- }
-
- /**
- * Compose a mail to a given user and either queue it for sending, or send it now,
- * depending on settings.
- *
- * Call sendMails() to send any mails that were queued.
- * @param $user User
- */
- function compose( $user ) {
- global $wgEnotifImpersonal;
-
- if ( !$this->composed_common ) {
- $this->composeCommonMailtext();
- }
-
- if ( $wgEnotifImpersonal ) {
- $this->mailTargets[] = new MailAddress( $user );
- } else {
- $this->sendPersonalised( $user );
- }
- }
-
- /**
- * Send any queued mails
- */
- function sendMails() {
- global $wgEnotifImpersonal;
- if ( $wgEnotifImpersonal ) {
- $this->sendImpersonal( $this->mailTargets );
- }
- }
-
- /**
- * Does the per-user customizations to a notification e-mail (name,
- * timestamp in proper timezone, etc) and sends it out.
- * Returns true if the mail was sent successfully.
- *
- * @param $watchingUser User object
- * @return Boolean
- * @private
- */
- function sendPersonalised( $watchingUser ) {
- global $wgContLang, $wgEnotifUseRealName;
- // From the PHP manual:
- // Note: The to parameter cannot be an address in the form of "Something <someone@example.com>".
- // The mail command will not parse this properly while talking with the MTA.
- $to = new MailAddress( $watchingUser );
-
- # $PAGEEDITDATE is the time and date of the page change
- # expressed in terms of individual local time of the notification
- # recipient, i.e. watching user
- $body = str_replace(
- array( '$WATCHINGUSERNAME',
- '$PAGEEDITDATE',
- '$PAGEEDITTIME' ),
- array( $wgEnotifUseRealName ? $watchingUser->getRealName() : $watchingUser->getName(),
- $wgContLang->userDate( $this->timestamp, $watchingUser ),
- $wgContLang->userTime( $this->timestamp, $watchingUser ) ),
- $this->body );
-
- return UserMailer::send( $to, $this->from, $this->subject, $body, $this->replyto );
- }
-
- /**
- * Same as sendPersonalised but does impersonal mail suitable for bulk
- * mailing. Takes an array of MailAddress objects.
- * @param $addresses array
- * @return Status|null
- */
- function sendImpersonal( $addresses ) {
- global $wgContLang;
-
- if ( empty( $addresses ) ) {
- return null;
- }
-
- $body = str_replace(
- array( '$WATCHINGUSERNAME',
- '$PAGEEDITDATE',
- '$PAGEEDITTIME' ),
- array( wfMessage( 'enotif_impersonal_salutation' )->inContentLanguage()->text(),
- $wgContLang->date( $this->timestamp, false, false ),
- $wgContLang->time( $this->timestamp, false, false ) ),
- $this->body );
-
- return UserMailer::send( $addresses, $this->from, $this->subject, $body, $this->replyto );
- }
-
-} # end of class EmailNotification
diff --git a/includes/UserRightsProxy.php b/includes/UserRightsProxy.php
index a8a22be7..53c69d81 100644
--- a/includes/UserRightsProxy.php
+++ b/includes/UserRightsProxy.php
@@ -31,10 +31,10 @@ class UserRightsProxy {
*
* @see newFromId()
* @see newFromName()
- * @param $db DatabaseBase: db connection
- * @param string $database database name
- * @param string $name user name
- * @param $id Integer: user ID
+ * @param DatabaseBase $db Db connection
+ * @param string $database Database name
+ * @param string $name User name
+ * @param int $id User ID
*/
private function __construct( $db, $database, $name, $id ) {
$this->db = $db;
@@ -47,7 +47,7 @@ class UserRightsProxy {
/**
* Accessor for $this->database
*
- * @return String: database name
+ * @return string Database name
*/
public function getDBName() {
return $this->database;
@@ -56,8 +56,8 @@ class UserRightsProxy {
/**
* Confirm the selected database name is a valid local interwiki database name.
*
- * @param string $database database name
- * @return Boolean
+ * @param string $database Database name
+ * @return bool
*/
public static function validDatabase( $database ) {
global $wgLocalDatabases;
@@ -67,10 +67,10 @@ class UserRightsProxy {
/**
* Same as User::whoIs()
*
- * @param string $database database name
- * @param $id Integer: user ID
- * @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
- * @return String: user name or false if the user doesn't exist
+ * @param string $database Database name
+ * @param int $id User ID
+ * @param bool $ignoreInvalidDB If true, don't check if $database is in $wgLocalDatabases
+ * @return string User name or false if the user doesn't exist
*/
public static function whoIs( $database, $id, $ignoreInvalidDB = false ) {
$user = self::newFromId( $database, $id, $ignoreInvalidDB );
@@ -84,10 +84,10 @@ class UserRightsProxy {
/**
* Factory function; get a remote user entry by ID number.
*
- * @param string $database database name
- * @param $id Integer: user ID
- * @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
- * @return UserRightsProxy or null if doesn't exist
+ * @param string $database Database name
+ * @param int $id User ID
+ * @param bool $ignoreInvalidDB If true, don't check if $database is in $wgLocalDatabases
+ * @return UserRightsProxy|null If doesn't exist
*/
public static function newFromId( $database, $id, $ignoreInvalidDB = false ) {
return self::newFromLookup( $database, 'user_id', intval( $id ), $ignoreInvalidDB );
@@ -96,29 +96,40 @@ class UserRightsProxy {
/**
* Factory function; get a remote user entry by name.
*
- * @param string $database database name
- * @param string $name user name
- * @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
- * @return UserRightsProxy or null if doesn't exist
+ * @param string $database Database name
+ * @param string $name User name
+ * @param bool $ignoreInvalidDB If true, don't check if $database is in $wgLocalDatabases
+ * @return UserRightsProxy|null If doesn't exist
*/
public static function newFromName( $database, $name, $ignoreInvalidDB = false ) {
return self::newFromLookup( $database, 'user_name', $name, $ignoreInvalidDB );
}
/**
- * @param $database
- * @param $field
- * @param $value
- * @param $ignoreInvalidDB bool
+ * @param string $database
+ * @param string $field
+ * @param string $value
+ * @param bool $ignoreInvalidDB
* @return null|UserRightsProxy
*/
private static function newFromLookup( $database, $field, $value, $ignoreInvalidDB = false ) {
+ global $wgSharedDB, $wgSharedTables;
+ // If the user table is shared, perform the user query on it, but don't pass it to the UserRightsProxy,
+ // as user rights are normally not shared.
+ if ( $wgSharedDB && in_array( 'user', $wgSharedTables ) ) {
+ $userdb = self::getDB( $wgSharedDB, $ignoreInvalidDB );
+ } else {
+ $userdb = self::getDB( $database, $ignoreInvalidDB );
+ }
+
$db = self::getDB( $database, $ignoreInvalidDB );
- if ( $db ) {
- $row = $db->selectRow( 'user',
+
+ if ( $db && $userdb ) {
+ $row = $userdb->selectRow( 'user',
array( 'user_id', 'user_name' ),
array( $field => $value ),
__METHOD__ );
+
if ( $row !== false ) {
return new UserRightsProxy( $db, $database,
$row->user_name,
@@ -132,9 +143,9 @@ class UserRightsProxy {
* Open a database connection to work on for the requested user.
* This may be a new connection to another database for remote users.
*
- * @param $database String
- * @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
- * @return DatabaseBase or null if invalid selection
+ * @param string $database
+ * @param bool $ignoreInvalidDB If true, don't check if $database is in $wgLocalDatabases
+ * @return DatabaseBase|null If invalid selection
*/
public static function getDB( $database, $ignoreInvalidDB = false ) {
global $wgDBname;
@@ -166,7 +177,7 @@ class UserRightsProxy {
/**
* Same as User::getName()
*
- * @return String
+ * @return string
*/
public function getName() {
return $this->name . '@' . $this->database;
@@ -175,7 +186,7 @@ class UserRightsProxy {
/**
* Same as User::getUserPage()
*
- * @return Title object
+ * @return Title
*/
public function getUserPage() {
return Title::makeTitle( NS_USER, $this->getName() );
@@ -199,6 +210,7 @@ class UserRightsProxy {
/**
* Replaces User::addUserGroup()
+ * @param string $group
*/
function addGroup( $group ) {
$this->db->insert( 'user_groups',
@@ -212,6 +224,7 @@ class UserRightsProxy {
/**
* Replaces User::removeUserGroup()
+ * @param string $group
*/
function removeGroup( $group ) {
$this->db->delete( 'user_groups',
@@ -224,6 +237,8 @@ class UserRightsProxy {
/**
* Replaces User::setOption()
+ * @param string $option
+ * @param mixed $value
*/
public function setOption( $option, $value ) {
$this->newOptions[$option] = $value;
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php
index 1e07e7c7..ab136b89 100644
--- a/includes/WatchedItem.php
+++ b/includes/WatchedItem.php
@@ -41,19 +41,36 @@ class WatchedItem {
*/
const CHECK_USER_RIGHTS = 1;
- var $mTitle, $mUser, $mCheckRights;
- private $loaded = false, $watched, $timestamp;
+ /** @var Title */
+ public $mTitle;
+
+ /** @var User */
+ public $mUser;
+
+ /** @var int */
+ public $mCheckRights;
+
+ /** @var bool */
+ private $loaded = false;
+
+ /** @var bool */
+ private $watched;
+
+ /** @var string */
+ private $timestamp;
/**
* Create a WatchedItem object with the given user and title
* @since 1.22 $checkRights parameter added
- * @param $user User: the user to use for (un)watching
- * @param $title Title: the title we're going to (un)watch
- * @param $checkRights int: Whether to check the 'viewmywatchlist' and 'editmywatchlist' rights.
+ * @param User $user The user to use for (un)watching
+ * @param Title $title The title we're going to (un)watch
+ * @param int $checkRights Whether to check the 'viewmywatchlist' and 'editmywatchlist' rights.
* Pass either WatchedItem::IGNORE_USER_RIGHTS or WatchedItem::CHECK_USER_RIGHTS.
- * @return WatchedItem object
+ * @return WatchedItem
*/
- public static function fromUserTitle( $user, $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
+ public static function fromUserTitle( $user, $title,
+ $checkRights = WatchedItem::CHECK_USER_RIGHTS
+ ) {
$wl = new WatchedItem;
$wl->mUser = $user;
$wl->mTitle = $title;
@@ -70,16 +87,26 @@ class WatchedItem {
return $this->mTitle;
}
- /** Helper to retrieve the title namespace */
+ /**
+ * Helper to retrieve the title namespace
+ * @return int
+ */
protected function getTitleNs() {
return $this->getTitle()->getNamespace();
}
- /** Helper to retrieve the title DBkey */
+ /**
+ * Helper to retrieve the title DBkey
+ * @return string
+ */
protected function getTitleDBkey() {
return $this->getTitle()->getDBkey();
}
- /** Helper to retrieve the user id */
+
+ /**
+ * Helper to retrieve the user id
+ * @return int
+ */
protected function getUserId() {
return $this->mUser->getId();
}
@@ -113,6 +140,12 @@ class WatchedItem {
return;
}
+ // some pages cannot be watched
+ if ( !$this->getTitle()->isWatchable() ) {
+ $this->watched = false;
+ return;
+ }
+
# Pages and their talk pages are considered equivalent for watching;
# remember that talk namespaces are numbered as page namespace+1.
@@ -130,7 +163,8 @@ class WatchedItem {
/**
* Check permissions
- * @param $what string: 'viewmywatchlist' or 'editmywatchlist'
+ * @param string $what 'viewmywatchlist' or 'editmywatchlist'
+ * @return bool
*/
private function isAllowed( $what ) {
return !$this->mCheckRights || $this->mUser->isAllowed( $what );
@@ -152,8 +186,8 @@ class WatchedItem {
/**
* Get the notification timestamp of this entry.
*
- * @return false|null|string: false if the page is not watched, the value of
- * the wl_notificationtimestamp field otherwise
+ * @return bool|null|string False if the page is not watched, the value of
+ * the wl_notificationtimestamp field otherwise
*/
public function getNotificationTimestamp() {
if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
@@ -171,10 +205,11 @@ class WatchedItem {
/**
* Reset the notification timestamp of this entry
*
- * @param $force Whether to force the write query to be executed even if the
- * page is not watched or the notification timestamp is already NULL.
+ * @param bool $force Whether to force the write query to be executed even if the
+ * page is not watched or the notification timestamp is already NULL.
+ * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
*/
- public function resetNotificationTimestamp( $force = '' ) {
+ public function resetNotificationTimestamp( $force = '', $oldid = 0 ) {
// Only loggedin user can have a watchlist
if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
return;
@@ -187,56 +222,111 @@ class WatchedItem {
}
}
+ $title = $this->getTitle();
+ if ( !$oldid ) {
+ // No oldid given, assuming latest revision; clear the timestamp.
+ $notificationTimestamp = null;
+ } elseif ( !$title->getNextRevisionID( $oldid ) ) {
+ // Oldid given and is the latest revision for this title; clear the timestamp.
+ $notificationTimestamp = null;
+ } else {
+ // See if the version marked as read is more recent than the one we're viewing.
+ // Call load() if it wasn't called before due to $force.
+ $this->load();
+
+ if ( $this->timestamp === null ) {
+ // This can only happen if $force is enabled.
+ $notificationTimestamp = null;
+ } else {
+ // Oldid given and isn't the latest; update the timestamp.
+ // This will result in no further notification emails being sent!
+ $dbr = wfGetDB( DB_SLAVE );
+ $notificationTimestamp = $dbr->selectField(
+ 'revision', 'rev_timestamp',
+ array( 'rev_page' => $title->getArticleID(), 'rev_id' => $oldid )
+ );
+ // We need to go one second to the future because of various strict comparisons
+ // throughout the codebase
+ $ts = new MWTimestamp( $notificationTimestamp );
+ $ts->timestamp->add( new DateInterval( 'PT1S' ) );
+ $notificationTimestamp = $ts->getTimestamp( TS_MW );
+
+ if ( $notificationTimestamp < $this->timestamp ) {
+ if ( $force != 'force' ) {
+ return;
+ } else {
+ // This is a little silly…
+ $notificationTimestamp = $this->timestamp;
+ }
+ }
+ }
+ }
+
// If the page is watched by the user (or may be watched), update the timestamp on any
// any matching rows
$dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => null ),
+ $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $notificationTimestamp ),
$this->dbCond(), __METHOD__ );
$this->timestamp = null;
}
/**
- * Given a title and user (assumes the object is setup), add the watch to the
- * database.
+ * @param WatchedItem[] $items
* @return bool
*/
- public function addWatch() {
- wfProfileIn( __METHOD__ );
+ public static function batchAddWatch( array $items ) {
+ $section = new ProfileSection( __METHOD__ );
- // Only loggedin user can have a watchlist
- if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
- wfProfileOut( __METHOD__ );
+ if ( wfReadOnly() ) {
return false;
}
- // Use INSERT IGNORE to avoid overwriting the notification timestamp
- // if there's already an entry for this page
- $dbw = wfGetDB( DB_MASTER );
- $dbw->insert( 'watchlist',
- array(
- 'wl_user' => $this->getUserId(),
- 'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ),
- 'wl_title' => $this->getTitleDBkey(),
+ $rows = array();
+ foreach ( $items as $item ) {
+ // Only loggedin user can have a watchlist
+ if ( $item->mUser->isAnon() || !$item->isAllowed( 'editmywatchlist' ) ) {
+ continue;
+ }
+ $rows[] = array(
+ 'wl_user' => $item->getUserId(),
+ 'wl_namespace' => MWNamespace::getSubject( $item->getTitleNs() ),
+ 'wl_title' => $item->getTitleDBkey(),
+ 'wl_notificationtimestamp' => null,
+ );
+ // Every single watched page needs now to be listed in watchlist;
+ // namespace:page and namespace_talk:page need separate entries:
+ $rows[] = array(
+ 'wl_user' => $item->getUserId(),
+ 'wl_namespace' => MWNamespace::getTalk( $item->getTitleNs() ),
+ 'wl_title' => $item->getTitleDBkey(),
'wl_notificationtimestamp' => null
- ), __METHOD__, 'IGNORE' );
+ );
+ $item->watched = true;
+ }
- // Every single watched page needs now to be listed in watchlist;
- // namespace:page and namespace_talk:page need separate entries:
- $dbw->insert( 'watchlist',
- array(
- 'wl_user' => $this->getUserId(),
- 'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ),
- 'wl_title' => $this->getTitleDBkey(),
- 'wl_notificationtimestamp' => null
- ), __METHOD__, 'IGNORE' );
+ if ( !$rows ) {
+ return false;
+ }
- $this->watched = true;
+ $dbw = wfGetDB( DB_MASTER );
+ foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
+ // Use INSERT IGNORE to avoid overwriting the notification timestamp
+ // if there's already an entry for this page
+ $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' );
+ }
- wfProfileOut( __METHOD__ );
return true;
}
/**
+ * Given a title and user (assumes the object is setup), add the watch to the database.
+ * @return bool
+ */
+ public function addWatch() {
+ return self::batchAddWatch( array( $this ) );
+ }
+
+ /**
* Same as addWatch, only the opposite.
* @return bool
*/
@@ -288,8 +378,8 @@ class WatchedItem {
* Check if the given title already is watched by the user, and if so
* add watches on a new title. To be used for page renames and such.
*
- * @param $ot Title: page title to duplicate entries from, if present
- * @param $nt Title: page title to add watches on
+ * @param Title $ot Page title to duplicate entries from, if present
+ * @param Title $nt Page title to add watches on
*/
public static function duplicateEntries( $ot, $nt ) {
WatchedItem::doDuplicateEntries( $ot->getSubjectPage(), $nt->getSubjectPage() );
@@ -299,8 +389,8 @@ class WatchedItem {
/**
* Handle duplicate entries. Backend for duplicateEntries().
*
- * @param $ot Title
- * @param $nt Title
+ * @param Title $ot
+ * @param Title $nt
*
* @return bool
*/
@@ -333,7 +423,13 @@ class WatchedItem {
# Perform replace
# Note that multi-row replace is very efficient for MySQL but may be inefficient for
# some other DBMSes, mostly due to poor simulation by us
- $dbw->replace( 'watchlist', array( array( 'wl_user', 'wl_namespace', 'wl_title' ) ), $values, __METHOD__ );
+ $dbw->replace(
+ 'watchlist',
+ array( array( 'wl_user', 'wl_namespace', 'wl_title' ) ),
+ $values,
+ __METHOD__
+ );
+
return true;
}
}
diff --git a/includes/WebRequest.php b/includes/WebRequest.php
index b17cb9ec..b187c4ac 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -3,7 +3,7 @@
* Deal with importing all those nasty globals and things
*
* Copyright © 2003 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -25,8 +25,8 @@
/**
* The WebRequest class encapsulates getting at data passed in the
- * URL or via a POSTed form, handling remove of "magic quotes" slashes,
- * stripping illegal input characters and normalizing Unicode sequences.
+ * URL or via a POSTed form stripping illegal input characters and
+ * normalizing Unicode sequences.
*
* Usually this is used via a global singleton, $wgRequest. You should
* not create a second WebRequest object; make a FauxRequest object if
@@ -46,15 +46,20 @@ class WebRequest {
/**
* Cached client IP address
- * @var String
+ * @var string
*/
private $ip;
+ /**
+ * Cached URL protocol
+ * @var string
+ */
+ protected $protocol;
+
public function __construct() {
- /// @todo FIXME: This preemptive de-quoting can interfere with other web libraries
- /// and increases our memory footprint. It would be cleaner to do on
- /// demand; but currently we have no wrapper for $_SERVER etc.
- $this->checkMagicQuotes();
+ if ( function_exists( 'get_magic_quotes_gpc' ) && get_magic_quotes_gpc() ) {
+ throw new MWException( "MediaWiki does not function when magic quotes are enabled." );
+ }
// POST overrides GET data
// We don't use $_REQUEST here to avoid interference from cookies...
@@ -74,7 +79,7 @@ class WebRequest {
* will return an empty array if it determines that the URL is
* inside a rewrite path.
*
- * @return Array: Any query arguments found in path matches.
+ * @return array Any query arguments found in path matches.
*/
public static function getPathInfo( $want = 'all' ) {
global $wgUsePathInfo;
@@ -107,8 +112,8 @@ class WebRequest {
$router->add( "$wgScript/$1" );
if ( isset( $_SERVER['SCRIPT_NAME'] )
- && preg_match( '/\.php5?/', $_SERVER['SCRIPT_NAME'] ) )
- {
+ && preg_match( '/\.php5?/', $_SERVER['SCRIPT_NAME'] )
+ ) {
# Check for SCRIPT_NAME, we handle index.php explicitly
# But we do have some other .php files such as img_auth.php
# Don't let root article paths clober the parsing for them
@@ -160,7 +165,8 @@ class WebRequest {
* @return string
*/
public static function detectServer() {
- list( $proto, $stdPort ) = self::detectProtocolAndStdPort();
+ $proto = self::detectProtocol();
+ $stdPort = $proto === 'https' ? 443 : 80;
$varNames = array( 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' );
$host = 'localhost';
@@ -175,7 +181,12 @@ class WebRequest {
continue;
}
$host = $parts[0];
- if ( $parts[1] === false ) {
+ if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
+ // Bug 70021: Assume that upstream proxy is running on the default
+ // port based on the protocol. We have no reliable way to determine
+ // the actual port in use upstream.
+ $port = $stdPort;
+ } elseif ( $parts[1] === false ) {
if ( isset( $_SERVER['SERVER_PORT'] ) ) {
$port = $_SERVER['SERVER_PORT'];
} // else leave it as $stdPort
@@ -189,25 +200,31 @@ class WebRequest {
}
/**
+ * Detect the protocol from $_SERVER.
+ * This is for use prior to Setup.php, when no WebRequest object is available.
+ * At other times, use the non-static function getProtocol().
+ *
* @return array
*/
- public static function detectProtocolAndStdPort() {
+ public static function detectProtocol() {
if ( ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ||
( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
$_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) ) {
- $arr = array( 'https', 443 );
+ return 'https';
} else {
- $arr = array( 'http', 80 );
+ return 'http';
}
- return $arr;
}
/**
+ * Get the current URL protocol (http or https)
* @return string
*/
- public static function detectProtocol() {
- list( $proto, ) = self::detectProtocolAndStdPort();
- return $proto;
+ public function getProtocol() {
+ if ( $this->protocol === null ) {
+ $this->protocol = self::detectProtocol();
+ }
+ return $this->protocol;
}
/**
@@ -233,11 +250,11 @@ class WebRequest {
* URL rewriting function; tries to extract page title and,
* optionally, one other fixed parameter value from a URL path.
*
- * @param string $path the URL path given from the client
- * @param array $bases one or more URLs, optionally with $1 at the end
- * @param string $key if provided, the matching key in $bases will be
- * passed on as the value of this URL parameter
- * @return array of URL variables to interpolate; empty if no match
+ * @param string $path The URL path given from the client
+ * @param array $bases One or more URLs, optionally with $1 at the end
+ * @param string $key If provided, the matching key in $bases will be
+ * passed on as the value of this URL parameter
+ * @return array Array of URL variables to interpolate; empty if no match
*/
static function extractTitle( $path, $bases, $key = false ) {
foreach ( (array)$bases as $keyValue => $base ) {
@@ -259,55 +276,10 @@ class WebRequest {
}
/**
- * Recursively strips slashes from the given array;
- * used for undoing the evil that is magic_quotes_gpc.
- *
- * @param array $arr will be modified
- * @param bool $topLevel Specifies if the array passed is from the top
- * level of the source. In PHP5 magic_quotes only escapes the first level
- * of keys that belong to an array.
- * @return array the original array
- * @see http://www.php.net/manual/en/function.get-magic-quotes-gpc.php#49612
- */
- private function &fix_magic_quotes( &$arr, $topLevel = true ) {
- $clean = array();
- foreach ( $arr as $key => $val ) {
- if ( is_array( $val ) ) {
- $cleanKey = $topLevel ? stripslashes( $key ) : $key;
- $clean[$cleanKey] = $this->fix_magic_quotes( $arr[$key], false );
- } else {
- $cleanKey = stripslashes( $key );
- $clean[$cleanKey] = stripslashes( $val );
- }
- }
- $arr = $clean;
- return $arr;
- }
-
- /**
- * If magic_quotes_gpc option is on, run the global arrays
- * through fix_magic_quotes to strip out the stupid slashes.
- * WARNING: This should only be done once! Running a second
- * time could damage the values.
- */
- private function checkMagicQuotes() {
- $mustFixQuotes = function_exists( 'get_magic_quotes_gpc' )
- && get_magic_quotes_gpc();
- if ( $mustFixQuotes ) {
- $this->fix_magic_quotes( $_COOKIE );
- $this->fix_magic_quotes( $_ENV );
- $this->fix_magic_quotes( $_GET );
- $this->fix_magic_quotes( $_POST );
- $this->fix_magic_quotes( $_REQUEST );
- $this->fix_magic_quotes( $_SERVER );
- }
- }
-
- /**
* Recursively normalizes UTF-8 strings in the given array.
*
- * @param $data string|array
- * @return array|string cleaned-up version of the given
+ * @param string|array $data
+ * @return array|string Cleaned-up version of the given
* @private
*/
function normalizeUnicode( $data ) {
@@ -325,9 +297,9 @@ class WebRequest {
/**
* Fetch a value from the given array or return $default if it's not set.
*
- * @param $arr Array
- * @param $name String
- * @param $default Mixed
+ * @param array $arr
+ * @param string $name
+ * @param mixed $default
* @return mixed
*/
private function getGPCVal( $arr, $name, $default ) {
@@ -357,9 +329,9 @@ class WebRequest {
* non-freeform text inputs (e.g. predefined internal text keys
* selected by a drop-down menu). For freeform input, see getText().
*
- * @param $name String
- * @param string $default optional default (or NULL)
- * @return String
+ * @param string $name
+ * @param string $default Optional default (or null)
+ * @return string
*/
public function getVal( $name, $default = null ) {
$val = $this->getGPCVal( $this->data, $name, $default );
@@ -376,9 +348,9 @@ class WebRequest {
/**
* Set an arbitrary value into our get/post data.
*
- * @param string $key key name to use
- * @param $value Mixed: value to set
- * @return Mixed: old value if one was present, null otherwise
+ * @param string $key Key name to use
+ * @param mixed $value Value to set
+ * @return mixed Old value if one was present, null otherwise
*/
public function setVal( $key, $value ) {
$ret = isset( $this->data[$key] ) ? $this->data[$key] : null;
@@ -389,8 +361,8 @@ class WebRequest {
/**
* Unset an arbitrary value from our get/post data.
*
- * @param string $key key name to use
- * @return Mixed: old value if one was present, null otherwise
+ * @param string $key Key name to use
+ * @return mixed Old value if one was present, null otherwise
*/
public function unsetVal( $key ) {
if ( !isset( $this->data[$key] ) ) {
@@ -405,11 +377,11 @@ class WebRequest {
/**
* Fetch an array from the input or return $default if it's not set.
* If source was scalar, will return an array with a single element.
- * If no source and no default, returns NULL.
+ * If no source and no default, returns null.
*
- * @param $name String
- * @param array $default optional default (or NULL)
- * @return Array
+ * @param string $name
+ * @param array $default Optional default (or null)
+ * @return array
*/
public function getArray( $name, $default = null ) {
$val = $this->getGPCVal( $this->data, $name, $default );
@@ -423,12 +395,12 @@ class WebRequest {
/**
* Fetch an array of integers, or return $default if it's not set.
* If source was scalar, will return an array with a single element.
- * If no source and no default, returns NULL.
+ * If no source and no default, returns null.
* If an array is returned, contents are guaranteed to be integers.
*
- * @param $name String
- * @param array $default option default (or NULL)
- * @return Array of ints
+ * @param string $name
+ * @param array $default Option default (or null)
+ * @return array Array of ints
*/
public function getIntArray( $name, $default = null ) {
$val = $this->getArray( $name, $default );
@@ -443,9 +415,9 @@ class WebRequest {
* Guaranteed to return an integer; non-numeric input will typically
* return 0.
*
- * @param $name String
- * @param $default Integer
- * @return Integer
+ * @param string $name
+ * @param int $default
+ * @return int
*/
public function getInt( $name, $default = 0 ) {
return intval( $this->getVal( $name, $default ) );
@@ -456,8 +428,8 @@ class WebRequest {
* Guaranteed to return an integer or null; non-numeric input will
* typically return null.
*
- * @param $name String
- * @return Integer
+ * @param string $name
+ * @return int|null
*/
public function getIntOrNull( $name ) {
$val = $this->getVal( $name );
@@ -467,13 +439,27 @@ class WebRequest {
}
/**
+ * Fetch a floating point value from the input or return $default if not set.
+ * Guaranteed to return a float; non-numeric input will typically
+ * return 0.
+ *
+ * @since 1.23
+ * @param string $name
+ * @param float $default
+ * @return float
+ */
+ public function getFloat( $name, $default = 0.0 ) {
+ return floatval( $this->getVal( $name, $default ) );
+ }
+
+ /**
* Fetch a boolean value from the input or return $default if not set.
* Guaranteed to return true or false, with normal PHP semantics for
* boolean interpretation of strings.
*
- * @param $name String
- * @param $default Boolean
- * @return Boolean
+ * @param string $name
+ * @param bool $default
+ * @return bool
*/
public function getBool( $name, $default = false ) {
return (bool)$this->getVal( $name, $default );
@@ -484,9 +470,9 @@ class WebRequest {
* Unlike getBool, the string "false" will result in boolean false, which is
* useful when interpreting information sent from JavaScript.
*
- * @param $name String
- * @param $default Boolean
- * @return Boolean
+ * @param string $name
+ * @param bool $default
+ * @return bool
*/
public function getFuzzyBool( $name, $default = false ) {
return $this->getBool( $name, $default ) && strcasecmp( $this->getVal( $name ), 'false' ) !== 0;
@@ -497,8 +483,8 @@ class WebRequest {
* value is (even "0"). Return false if the named value is not set.
* Example use is checking for the presence of check boxes in forms.
*
- * @param $name String
- * @return Boolean
+ * @param string $name
+ * @return bool
*/
public function getCheck( $name ) {
# Checkboxes and buttons are only present when clicked
@@ -514,9 +500,9 @@ class WebRequest {
* user-supplied freeform text input (for which input transformations may
* be required - e.g. Esperanto x-coding).
*
- * @param $name String
- * @param string $default optional
- * @return String
+ * @param string $name
+ * @param string $default Optional
+ * @return string
*/
public function getText( $name, $default = '' ) {
global $wgContLang;
@@ -551,7 +537,7 @@ class WebRequest {
/**
* Returns the names of all input values excluding those in $exclude.
*
- * @param $exclude Array
+ * @param array $exclude
* @return array
*/
public function getValueNames( $exclude = array() ) {
@@ -562,7 +548,7 @@ class WebRequest {
* Get the values passed in the query string.
* No transformation is performed on the values.
*
- * @return Array
+ * @return array
*/
public function getQueryValues() {
return $_GET;
@@ -572,7 +558,7 @@ class WebRequest {
* Return the contents of the Query with no decoding. Use when you need to
* know exactly what was sent, e.g. for an OAuth signature over the elements.
*
- * @return String
+ * @return string
*/
public function getRawQueryString() {
return $_SERVER['QUERY_STRING'];
@@ -582,7 +568,7 @@ class WebRequest {
* Return the contents of the POST with no decoding. Use when you need to
* know exactly what was sent, e.g. for an OAuth signature over the elements.
*
- * @return String
+ * @return string
*/
public function getRawPostString() {
if ( !$this->wasPosted() ) {
@@ -596,11 +582,11 @@ class WebRequest {
* disallow reading the stream more than once. As stated in the php docs, this
* does not work with enctype="multipart/form-data".
*
- * @return String
+ * @return string
*/
public function getRawInput() {
- static $input = false;
- if ( $input === false ) {
+ static $input = null;
+ if ( $input === null ) {
$input = file_get_contents( 'php://input' );
}
return $input;
@@ -609,7 +595,7 @@ class WebRequest {
/**
* Get the HTTP method used for this request.
*
- * @return String
+ * @return string
*/
public function getMethod() {
return isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : 'GET';
@@ -622,7 +608,7 @@ class WebRequest {
* Note that values retrieved by the object may come from the
* GET URL etc even on a POST request.
*
- * @return Boolean
+ * @return bool
*/
public function wasPosted() {
return $this->getMethod() == 'POST';
@@ -637,7 +623,7 @@ class WebRequest {
* during the current request (in which case the cookie will
* be sent back to the client at the end of the script run).
*
- * @return Boolean
+ * @return bool
*/
public function checkSessionCookie() {
return isset( $_COOKIE[session_name()] );
@@ -646,10 +632,10 @@ class WebRequest {
/**
* Get a cookie from the $_COOKIE jar
*
- * @param string $key the name of the cookie
- * @param string $prefix a prefix to use for the cookie name, if not $wgCookiePrefix
- * @param $default Mixed: what to return if the value isn't found
- * @return Mixed: cookie value or $default if the cookie not set
+ * @param string $key The name of the cookie
+ * @param string $prefix A prefix to use for the cookie name, if not $wgCookiePrefix
+ * @param mixed $default What to return if the value isn't found
+ * @return mixed Cookie value or $default if the cookie not set
*/
public function getCookie( $key, $prefix = null, $default = null ) {
if ( $prefix === null ) {
@@ -664,12 +650,14 @@ class WebRequest {
* This will be suitable for use as a relative link in HTML output.
*
* @throws MWException
- * @return String
+ * @return string
*/
public function getRequestURL() {
if ( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
$base = $_SERVER['REQUEST_URI'];
- } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
+ } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] )
+ && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] )
+ ) {
// Probably IIS; doesn't set REQUEST_URI
$base = $_SERVER['HTTP_X_ORIGINAL_URL'];
} elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
@@ -709,7 +697,7 @@ class WebRequest {
* If $wgServer is protocol-relative, this will return a fully
* qualified URL with the protocol that was used for this request.
*
- * @return String
+ * @return string
*/
public function getFullRequestURL() {
return wfExpandUrl( $this->getRequestURL(), PROTO_CURRENT );
@@ -717,30 +705,19 @@ class WebRequest {
/**
* Take an arbitrary query and rewrite the present URL to include it
- * @param string $query query string fragment; do not include initial '?'
+ * @param string $query Query string fragment; do not include initial '?'
*
- * @return String
+ * @return string
*/
public function appendQuery( $query ) {
return $this->appendQueryArray( wfCgiToArray( $query ) );
}
/**
- * HTML-safe version of appendQuery().
- * @deprecated: Deprecated in 1.20, warnings in 1.21, remove in 1.22.
- *
- * @param string $query query string fragment; do not include initial '?'
- * @return String
- */
- public function escapeAppendQuery( $query ) {
- return htmlspecialchars( $this->appendQuery( $query ) );
- }
-
- /**
- * @param $key
- * @param $value
- * @param $onlyquery bool
- * @return String
+ * @param string $key
+ * @param string $value
+ * @param bool $onlyquery
+ * @return string
*/
public function appendQueryValue( $key, $value, $onlyquery = false ) {
return $this->appendQueryArray( array( $key => $value ), $onlyquery );
@@ -749,10 +726,9 @@ class WebRequest {
/**
* Appends or replaces value of query variables.
*
- * @param array $array of values to replace/add to query
- * @param bool $onlyquery whether to only return the query string and not
- * the complete URL
- * @return String
+ * @param array $array Array of values to replace/add to query
+ * @param bool $onlyquery Whether to only return the query string and not the complete URL
+ * @return string
*/
public function appendQueryArray( $array, $onlyquery = false ) {
global $wgTitle;
@@ -768,9 +744,9 @@ class WebRequest {
* defaults if not given. The limit must be positive and is capped at 5000.
* Offset must be positive but is not capped.
*
- * @param $deflimit Integer: limit to use if no input and the user hasn't set the option.
- * @param string $optionname to specify an option other than rclimit to pull from.
- * @return array first element is limit, second is offset
+ * @param int $deflimit Limit to use if no input and the user hasn't set the option.
+ * @param string $optionname To specify an option other than rclimit to pull from.
+ * @return array First element is limit, second is offset
*/
public function getLimitOffset( $deflimit = 50, $optionname = 'rclimit' ) {
global $wgUser;
@@ -800,8 +776,8 @@ class WebRequest {
/**
* Return the path to the temporary file where PHP has stored the upload.
*
- * @param $key String:
- * @return string or NULL if no such file.
+ * @param string $key
+ * @return string|null String or null if no such file.
*/
public function getFileTempname( $key ) {
$file = new WebRequestUpload( $this, $key );
@@ -809,23 +785,10 @@ class WebRequest {
}
/**
- * Return the size of the upload, or 0.
- *
- * @deprecated since 1.17
- * @param $key String:
- * @return integer
- */
- public function getFileSize( $key ) {
- wfDeprecated( __METHOD__, '1.17' );
- $file = new WebRequestUpload( $this, $key );
- return $file->getSize();
- }
-
- /**
* Return the upload error or 0
*
- * @param $key String:
- * @return integer
+ * @param string $key
+ * @return int
*/
public function getUploadError( $key ) {
$file = new WebRequestUpload( $this, $key );
@@ -840,8 +803,8 @@ class WebRequest {
*
* Other than this the name is not verified for being a safe filename.
*
- * @param $key String:
- * @return string or NULL if no such file.
+ * @param string $key
+ * @return string|null String or null if no such file.
*/
public function getFileName( $key ) {
$file = new WebRequestUpload( $this, $key );
@@ -851,7 +814,7 @@ class WebRequest {
/**
* Return a WebRequestUpload object corresponding to the key
*
- * @param $key string
+ * @param string $key
* @return WebRequestUpload
*/
public function getUpload( $key ) {
@@ -901,7 +864,7 @@ class WebRequest {
/**
* Get an array containing all request headers
*
- * @return Array mapping header name to its value
+ * @return array Mapping header name to its value
*/
public function getAllHeaders() {
$this->initHeaders();
@@ -910,7 +873,7 @@ class WebRequest {
/**
* Get a request header, or false if it isn't set
- * @param string $name case-insensitive header name
+ * @param string $name Case-insensitive header name
*
* @return string|bool False on failure
*/
@@ -927,8 +890,8 @@ class WebRequest {
/**
* Get data from $_SESSION
*
- * @param string $key name of key in $_SESSION
- * @return Mixed
+ * @param string $key Name of key in $_SESSION
+ * @return mixed
*/
public function getSessionData( $key ) {
if ( !isset( $_SESSION[$key] ) ) {
@@ -940,8 +903,8 @@ class WebRequest {
/**
* Set session data
*
- * @param string $key name of key in $_SESSION
- * @param $data Mixed
+ * @param string $key Name of key in $_SESSION
+ * @param mixed $data
*/
public function setSessionData( $key, $data ) {
$_SESSION[$key] = $data;
@@ -953,7 +916,7 @@ class WebRequest {
* message or redirect to a safer URL. Returns true if the URL is OK, and
* false if an error message has been shown and the request should be aborted.
*
- * @param $extWhitelist array
+ * @param array $extWhitelist
* @throws HttpError
* @return bool
*/
@@ -979,7 +942,7 @@ class WebRequest {
* Attempt to redirect to a URL with a QUERY_STRING that's not dangerous in
* IE 6. Returns true if it was successful, false otherwise.
*
- * @param $url string
+ * @param string $url
* @return bool
*/
protected function doSecurityRedirect( $url ) {
@@ -997,9 +960,9 @@ class WebRequest {
We can't serve non-HTML content from the URL you have requested, because
Internet Explorer would interpret it as an incorrect and potentially dangerous
content type.</p>
-<p>Instead, please use <a href="$encUrl">this URL</a>, which is the same as the URL you have requested, except that
-"&amp;*" is appended. This prevents Internet Explorer from seeing a bogus file
-extension.
+<p>Instead, please use <a href="$encUrl">this URL</a>, which is the same as the
+URL you have requested, except that "&amp;*" is appended. This prevents Internet
+Explorer from seeing a bogus file extension.
</p>
</body>
</html>
@@ -1009,44 +972,17 @@ HTML;
}
/**
- * Returns true if the PATH_INFO ends with an extension other than a script
- * extension. This could confuse IE for scripts that send arbitrary data which
- * is not HTML but may be detected as such.
- *
- * Various past attempts to use the URL to make this check have generally
- * run up against the fact that CGI does not provide a standard method to
- * determine the URL. PATH_INFO may be mangled (e.g. if cgi.fix_pathinfo=0),
- * but only by prefixing it with the script name and maybe some other stuff,
- * the extension is not mangled. So this should be a reasonably portable
- * way to perform this security check.
- *
- * Also checks for anything that looks like a file extension at the end of
- * QUERY_STRING, since IE 6 and earlier will use this to get the file type
- * if there was no dot before the question mark (bug 28235).
- *
- * @deprecated Use checkUrlExtension().
- *
- * @param $extWhitelist array
- *
- * @return bool
- */
- public function isPathInfoBad( $extWhitelist = array() ) {
- wfDeprecated( __METHOD__, '1.17' );
- global $wgScriptExtension;
- $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
- return IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist );
- }
-
- /**
* Parse the Accept-Language header sent by the client into an array
- * @return array array( languageCode => q-value ) sorted by q-value in descending order then
- * appearing time in the header in ascending order.
+ *
+ * @return array Array( languageCode => q-value ) sorted by q-value in
+ * descending order then appearing time in the header in ascending order.
* May contain the "language" '*', which applies to languages other than those explicitly listed.
* This is aligned with rfc2616 section 14.4
* Preference for earlier languages appears in rfc3282 as an extension to HTTP/1.1.
*/
public function getAcceptLang() {
- // Modified version of code found at http://www.thefutureoftheweb.com/blog/use-accept-language-header
+ // Modified version of code found at
+ // http://www.thefutureoftheweb.com/blog/use-accept-language-header
$acceptLang = $this->getHeader( 'Accept-Language' );
if ( !$acceptLang ) {
return array();
@@ -1057,8 +993,11 @@ HTML;
// Break up string into pieces (languages and q factors)
$lang_parse = null;
- preg_match_all( '/([a-z]{1,8}(-[a-z]{1,8})*|\*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})?)?)?/',
- $acceptLang, $lang_parse );
+ preg_match_all(
+ '/([a-z]{1,8}(-[a-z]{1,8})*|\*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})?)?)?/',
+ $acceptLang,
+ $lang_parse
+ );
if ( !count( $lang_parse[1] ) ) {
return array();
@@ -1092,7 +1031,7 @@ HTML;
* @since 1.19
*
* @throws MWException
- * @return String
+ * @return string
*/
protected function getRawIP() {
if ( !isset( $_SERVER['REMOTE_ADDR'] ) ) {
@@ -1100,7 +1039,8 @@ HTML;
}
if ( is_array( $_SERVER['REMOTE_ADDR'] ) || strpos( $_SERVER['REMOTE_ADDR'], ',' ) !== false ) {
- throw new MWException( __METHOD__ . " : Could not determine the remote IP address due to multiple values." );
+ throw new MWException( __METHOD__
+ . " : Could not determine the remote IP address due to multiple values." );
} else {
$ipchain = $_SERVER['REMOTE_ADDR'];
}
@@ -1127,36 +1067,45 @@ HTML;
# collect the originating ips
$ip = $this->getRawIP();
+ if ( !$ip ) {
+ throw new MWException( 'Unable to determine IP.' );
+ }
# Append XFF
$forwardedFor = $this->getHeader( 'X-Forwarded-For' );
if ( $forwardedFor !== false ) {
+ $isConfigured = IP::isConfiguredProxy( $ip );
$ipchain = array_map( 'trim', explode( ',', $forwardedFor ) );
$ipchain = array_reverse( $ipchain );
- if ( $ip ) {
- array_unshift( $ipchain, $ip );
- }
+ array_unshift( $ipchain, $ip );
# Step through XFF list and find the last address in the list which is a
# trusted server. Set $ip to the IP address given by that trusted server,
# unless the address is not sensible (e.g. private). However, prefer private
# IP addresses over proxy servers controlled by this site (more sensible).
+ # Note that some XFF values might be "unknown" with Squid/Varnish.
foreach ( $ipchain as $i => $curIP ) {
$curIP = IP::sanitizeIP( IP::canonicalize( $curIP ) );
- if ( wfIsTrustedProxy( $curIP ) && isset( $ipchain[$i + 1] ) ) {
- if ( wfIsConfiguredProxy( $curIP ) || // bug 48919; treat IP as sane
- IP::isPublic( $ipchain[$i + 1] ) ||
- $wgUsePrivateIPs
- ) {
- $nextIP = IP::canonicalize( $ipchain[$i + 1] );
- if ( !$nextIP && wfIsConfiguredProxy( $ip ) ) {
- // We have not yet made it past CDN/proxy servers of this site,
- // so either they are misconfigured or there is some IP spoofing.
- throw new MWException( "Invalid IP given in XFF '$forwardedFor'." );
- }
- $ip = $nextIP;
- continue;
+ if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] === 'unknown'
+ || !IP::isTrustedProxy( $curIP )
+ ) {
+ break; // IP is not valid/trusted or does not point to anything
+ }
+ if (
+ IP::isPublic( $ipchain[$i + 1] ) ||
+ $wgUsePrivateIPs ||
+ IP::isConfiguredProxy( $curIP ) // bug 48919; treat IP as sane
+ ) {
+ // Follow the next IP according to the proxy
+ $nextIP = IP::canonicalize( $ipchain[$i + 1] );
+ if ( !$nextIP && $isConfigured ) {
+ // We have not yet made it past CDN/proxy servers of this site,
+ // so either they are misconfigured or there is some IP spoofing.
+ throw new MWException( "Invalid IP given in XFF '$forwardedFor'." );
}
+ $ip = $nextIP;
+ // keep traversing the chain
+ continue;
}
break;
}
@@ -1195,7 +1144,7 @@ class WebRequestUpload {
/**
* Constructor. Should only be called by WebRequest
*
- * @param $request WebRequest The associated request
+ * @param WebRequest $request The associated request
* @param string $key Key in $_FILES array (name of form field)
*/
public function __construct( $request, $key ) {
@@ -1218,7 +1167,7 @@ class WebRequestUpload {
/**
* Return the original filename of the uploaded file
*
- * @return mixed Filename or null if non-existent
+ * @return string|null Filename or null if non-existent
*/
public function getName() {
if ( !$this->exists() ) {
@@ -1252,7 +1201,7 @@ class WebRequestUpload {
/**
* Return the path to the temporary file
*
- * @return mixed Path or null if non-existent
+ * @return string|null Path or null if non-existent
*/
public function getTempName() {
if ( !$this->exists() ) {
@@ -1308,13 +1257,16 @@ class FauxRequest extends WebRequest {
private $session = array();
/**
- * @param array $data of *non*-urlencoded key => value pairs, the
+ * @param array $data Array of *non*-urlencoded key => value pairs, the
* fake GET/POST values
- * @param bool $wasPosted whether to treat the data as POST
- * @param $session Mixed: session array or null
+ * @param bool $wasPosted Whether to treat the data as POST
+ * @param array|null $session Session array or null
+ * @param string $protocol 'http' or 'https'
* @throws MWException
*/
- public function __construct( $data = array(), $wasPosted = false, $session = null ) {
+ public function __construct( $data = array(), $wasPosted = false,
+ $session = null, $protocol = 'http'
+ ) {
if ( is_array( $data ) ) {
$this->data = $data;
} else {
@@ -1324,10 +1276,11 @@ class FauxRequest extends WebRequest {
if ( $session ) {
$this->session = $session;
}
+ $this->protocol = $protocol;
}
/**
- * @param $method string
+ * @param string $method
* @throws MWException
*/
private function notImplemented( $method ) {
@@ -1335,8 +1288,8 @@ class FauxRequest extends WebRequest {
}
/**
- * @param $name string
- * @param $default string
+ * @param string $name
+ * @param string $default
* @return string
*/
public function getText( $name, $default = '' ) {
@@ -1345,7 +1298,7 @@ class FauxRequest extends WebRequest {
}
/**
- * @return Array
+ * @return array
*/
public function getValues() {
return $this->data;
@@ -1385,6 +1338,10 @@ class FauxRequest extends WebRequest {
$this->notImplemented( __METHOD__ );
}
+ public function getProtocol() {
+ return $this->protocol;
+ }
+
/**
* @param string $name The name of the header to get (case insensitive).
* @return bool|string
@@ -1395,8 +1352,8 @@ class FauxRequest extends WebRequest {
}
/**
- * @param $name string
- * @param $val string
+ * @param string $name
+ * @param string $val
*/
public function setHeader( $name, $val ) {
$name = strtoupper( $name );
@@ -1404,8 +1361,8 @@ class FauxRequest extends WebRequest {
}
/**
- * @param $key
- * @return mixed
+ * @param string $key
+ * @return array|null
*/
public function getSessionData( $key ) {
if ( isset( $this->session[$key] ) ) {
@@ -1415,31 +1372,23 @@ class FauxRequest extends WebRequest {
}
/**
- * @param $key
- * @param $data
+ * @param string $key
+ * @param array $data
*/
public function setSessionData( $key, $data ) {
$this->session[$key] = $data;
}
/**
- * @return array|Mixed|null
+ * @return array|mixed|null
*/
public function getSessionArray() {
return $this->session;
}
/**
- * @param array $extWhitelist
- * @return bool
- */
- public function isPathInfoBad( $extWhitelist = array() ) {
- return false;
- }
-
- /**
* FauxRequests shouldn't depend on raw request data (but that could be implemented here)
- * @return String
+ * @return string
*/
public function getRawQueryString() {
return '';
@@ -1447,7 +1396,7 @@ class FauxRequest extends WebRequest {
/**
* FauxRequests shouldn't depend on raw request data (but that could be implemented here)
- * @return String
+ * @return string
*/
public function getRawPostString() {
return '';
@@ -1455,7 +1404,7 @@ class FauxRequest extends WebRequest {
/**
* FauxRequests shouldn't depend on raw request data (but that could be implemented here)
- * @return String
+ * @return string
*/
public function getRawInput() {
return '';
@@ -1488,6 +1437,12 @@ class FauxRequest extends WebRequest {
class DerivativeRequest extends FauxRequest {
private $base;
+ /**
+ * @param WebRequest $base
+ * @param array $data Array of *non*-urlencoded key => value pairs, the
+ * fake GET/POST values
+ * @param bool $wasPosted Whether to treat the data as POST
+ */
public function __construct( WebRequest $base, $data, $wasPosted = false ) {
$this->base = $base;
parent::__construct( $data, $wasPosted );
@@ -1524,4 +1479,8 @@ class DerivativeRequest extends FauxRequest {
public function getIP() {
return $this->base->getIP();
}
+
+ public function getProtocol() {
+ return $this->base->getProtocol();
+ }
}
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
index ab7524c2..ad9f4e66 100644
--- a/includes/WebResponse.php
+++ b/includes/WebResponse.php
@@ -28,11 +28,10 @@
class WebResponse {
/**
- * Output a HTTP header, wrapper for PHP's
- * header()
- * @param string $string header to output
- * @param bool $replace replace current similar header
- * @param $http_response_code null|int Forces the HTTP response code to the specified value.
+ * Output a HTTP header, wrapper for PHP's header()
+ * @param string $string Header to output
+ * @param bool $replace Replace current similar header
+ * @param null|int $http_response_code Forces the HTTP response code to the specified value.
*/
public function header( $string, $replace = true, $http_response_code = null ) {
header( $string, $replace, $http_response_code );
@@ -40,8 +39,8 @@ class WebResponse {
/**
* Set the browser cookie
- * @param string $name name of cookie
- * @param string $value value to give cookie
+ * @param string $name Name of cookie
+ * @param string $value Value to give cookie
* @param int|null $expire Unix timestamp (in seconds) when the cookie should expire.
* 0 (the default) causes it to expire $wgCookieExpiration seconds from now.
* null causes it to be a session cookie.
@@ -52,7 +51,7 @@ class WebResponse {
* secure: bool, secure attribute ($wgCookieSecure)
* httpOnly: bool, httpOnly attribute ($wgCookieHttpOnly)
* raw: bool, if true uses PHP's setrawcookie() instead of setcookie()
- * For backwards compatability, if $options is not an array then it and
+ * For backwards compatibility, if $options is not an array then it and
* the following two parameters will be interpreted as values for
* 'prefix', 'domain', and 'secure'
* @since 1.22 Replaced $prefix, $domain, and $forceSecure with $options
@@ -62,7 +61,7 @@ class WebResponse {
global $wgCookieSecure, $wgCookieExpiration, $wgCookieHttpOnly;
if ( !is_array( $options ) ) {
- // Backwards compatability
+ // Backwards compatibility
$options = array( 'prefix' => $options );
if ( func_num_args() >= 5 ) {
$options['domain'] = func_get_arg( 4 );
@@ -88,12 +87,6 @@ class WebResponse {
$expire = time() + $wgCookieExpiration;
}
- // Don't mark the cookie as httpOnly if the requesting user-agent is
- // known to have trouble with httpOnly cookies.
- if ( !wfHttpOnlySafe() ) {
- $options['httpOnly'] = false;
- }
-
$func = $options['raw'] ? 'setrawcookie' : 'setcookie';
if ( wfRunHooks( 'WebResponseSetCookie', array( &$name, &$value, &$expire, $options ) ) ) {
@@ -130,9 +123,9 @@ class FauxResponse extends WebResponse {
/**
* Stores a HTTP header
- * @param string $string header to output
- * @param bool $replace replace current similar header
- * @param $http_response_code null|int Forces the HTTP response code to the specified value.
+ * @param string $string Header to output
+ * @param bool $replace Replace current similar header
+ * @param null|int $http_response_code Forces the HTTP response code to the specified value.
*/
public function header( $string, $replace = true, $http_response_code = null ) {
if ( substr( $string, 0, 5 ) == 'HTTP/' ) {
@@ -169,7 +162,7 @@ class FauxResponse extends WebResponse {
/**
* Get the HTTP response code, null if not set
*
- * @return Int or null
+ * @return int|null
*/
public function getStatusCode() {
return $this->code;
@@ -178,17 +171,17 @@ class FauxResponse extends WebResponse {
/**
* @todo document. It just ignore optional parameters.
*
- * @param string $name name of cookie
- * @param string $value value to give cookie
- * @param int $expire number of seconds til cookie expires (Default: 0)
- * @param array $options ignored
+ * @param string $name Name of cookie
+ * @param string $value Value to give cookie
+ * @param int $expire Number of seconds til cookie expires (Default: 0)
+ * @param array $options Ignored
*/
public function setcookie( $name, $value, $expire = 0, $options = null ) {
$this->cookies[$name] = $value;
}
/**
- * @param $name string
+ * @param string $name
* @return string
*/
public function getcookie( $name ) {
diff --git a/includes/WebStart.php b/includes/WebStart.php
index 58c953af..2ae72dcc 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -1,10 +1,13 @@
<?php
/**
- * This does the initial setup for a web request.
+ * This does the initial set up for a web request.
* It does some security checks, starts the profiler and loads the
* configuration, and optionally loads Setup.php depending on whether
* MW_NO_SETUP is defined.
*
+ * Setup.php (if loaded) then sets up GlobalFunctions, the AutoLoader,
+ * and the configuration globals (though not $wgTitle).
+ *
* 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
@@ -23,38 +26,12 @@
* @file
*/
-# Protect against register_globals
+# Die if register_globals is enabled (PHP <=5.3)
# This must be done before any globals are set by the code
if ( ini_get( 'register_globals' ) ) {
- if ( isset( $_REQUEST['GLOBALS'] ) || isset( $_FILES['GLOBALS'] ) ) {
- die( '<a href="http://www.hardened-php.net/globals-problem">$GLOBALS overwrite vulnerability</a>' );
- }
- $verboten = array(
- 'GLOBALS',
- '_SERVER',
- 'HTTP_SERVER_VARS',
- '_GET',
- 'HTTP_GET_VARS',
- '_POST',
- 'HTTP_POST_VARS',
- '_COOKIE',
- 'HTTP_COOKIE_VARS',
- '_FILES',
- 'HTTP_POST_FILES',
- '_ENV',
- 'HTTP_ENV_VARS',
- '_REQUEST',
- '_SESSION',
- 'HTTP_SESSION_VARS'
- );
- foreach ( $_REQUEST as $name => $value ) {
- if ( in_array( $name, $verboten ) ) {
- header( "HTTP/1.1 500 Internal Server Error" );
- echo "register_globals security paranoia: trying to overwrite superglobals, aborting.";
- die( -1 );
- }
- unset( $GLOBALS[$name] );
- }
+ die( 'MediaWiki does not support installations where register_globals is enabled. '
+ . 'Please see <a href="https://www.mediawiki.org/wiki/register_globals">mediawiki.org</a> '
+ . 'for help on how to disable it.' );
}
# bug 15461: Make IE8 turn off content sniffing. Everybody else should ignore this
@@ -63,12 +40,6 @@ if ( ini_get( 'register_globals' ) ) {
header( 'X-Content-Type-Options: nosniff' );
$wgRequestTime = microtime( true );
-# getrusage() does not exist on the Microsoft Windows platforms, catching this
-if ( function_exists ( 'getrusage' ) ) {
- $wgRUstart = getrusage();
-} else {
- $wgRUstart = array();
-}
unset( $IP );
# Valid web server entry point, enable includes.
@@ -84,18 +55,15 @@ define( 'MEDIAWIKI', true );
# if we don't have permissions on parent directories.
$IP = getenv( 'MW_INSTALL_PATH' );
if ( $IP === false ) {
- if ( realpath( '.' ) ) {
- $IP = realpath( '.' );
- } else {
- $IP = dirname( __DIR__ );
- }
+ $IP = realpath( '.' ) ?: dirname( __DIR__ );
}
-# Start the autoloader, so that extensions can derive classes from core files
-require_once "$IP/includes/AutoLoader.php";
-
# Load the profiler
require_once "$IP/includes/profiler/Profiler.php";
+$wgRUstart = wfGetRusage() ?: array();
+
+# Start the autoloader, so that extensions can derive classes from core files
+require_once "$IP/includes/AutoLoader.php";
# Load up some global defines.
require_once "$IP/includes/Defines.php";
@@ -126,8 +94,8 @@ if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
# LocalSettings.php is the per site customization file. If it does not exist
# the wiki installer needs to be launched or the generated file uploaded to
- # the root wiki directory
- if ( !file_exists( MW_CONFIG_FILE ) ) {
+ # the root wiki directory. Give a hint, if it is not readable by the server.
+ if ( !is_readable( MW_CONFIG_FILE ) ) {
require_once "$IP/includes/templates/NoLocalSettings.php";
die();
}
diff --git a/includes/WikiError.php b/includes/WikiError.php
deleted file mode 100644
index 08eb8004..00000000
--- a/includes/WikiError.php
+++ /dev/null
@@ -1,154 +0,0 @@
-<?php
-/**
- * MediaWiki error classes
- *
- * Copyright © 2005 Brion Vibber <brion@pobox.com>
- * http://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
- */
-
-/**
- * Since PHP4 doesn't have exceptions, here's some error objects
- * loosely modeled on the standard PEAR_Error model...
- * @ingroup Exception
- */
-class WikiError {
- /**
- * @param $message string
- *
- * @deprecated since 1.17
- */
- function __construct( $message ) {
- wfDeprecated( __METHOD__, '1.17' );
- $this->mMessage = $message;
- }
-
- /**
- * @return string Plaintext error message to display
- */
- function getMessage() {
- return $this->mMessage;
- }
-
- /**
- * In following PEAR_Error model this could be formatted differently,
- * but so far it's not.
- * @return string
- */
- function toString() {
- return $this->getMessage();
- }
-
- /**
- * Returns true if the given object is a WikiError-descended
- * error object, false otherwise.
- *
- * @param $object mixed
- * @return bool
- *
- * @deprecated since 1.17
- */
- public static function isError( $object ) {
- wfDeprecated( __METHOD__, '1.17' );
- if ( $object instanceof WikiError ) {
- return true;
- } elseif ( $object instanceof Status ) {
- return !$object->isOK();
- } else {
- return false;
- }
- }
-}
-
-/**
- * Localized error message object
- * @ingroup Exception
- */
-class WikiErrorMsg extends WikiError {
- /**
- * @param string $message wiki message name
- * @param ... parameters to pass to wfMsg()
- *
- * @deprecated since 1.17
- */
- function __construct( $message/*, ... */ ) {
- wfDeprecated( __METHOD__, '1.17' );
- $args = func_get_args();
- array_shift( $args );
- $this->mMessage = wfMessage( $message )->rawParams( $args )->text();
- $this->mMsgKey = $message;
- $this->mMsgArgs = $args;
- }
-
- function getMessageKey() {
- return $this->mMsgKey;
- }
-
- function getMessageArgs() {
- return $this->mMsgArgs;
- }
-}
-
-/**
- * Error class designed to handle errors involved with
- * XML parsing
- * @ingroup Exception
- */
-class WikiXmlError extends WikiError {
- /**
- * @param $parser resource
- * @param $message string
- * @param $context
- * @param $offset Int
- *
- * @deprecated since 1.17
- */
- function __construct( $parser, $message = 'XML parsing error', $context = null, $offset = 0 ) {
- wfDeprecated( __METHOD__, '1.17' );
- $this->mXmlError = xml_get_error_code( $parser );
- $this->mColumn = xml_get_current_column_number( $parser );
- $this->mLine = xml_get_current_line_number( $parser );
- $this->mByte = xml_get_current_byte_index( $parser );
- $this->mContext = $this->_extractContext( $context, $offset );
- $this->mMessage = $message;
- xml_parser_free( $parser );
- wfDebug( "WikiXmlError: " . $this->getMessage() . "\n" );
- }
-
- /** @return string */
- function getMessage() {
- // '$1 at line $2, col $3 (byte $4): $5',
- return wfMessage( 'xml-error-string',
- $this->mMessage,
- $this->mLine,
- $this->mColumn,
- $this->mByte . $this->mContext,
- xml_error_string( $this->mXmlError ) )->escaped();
- }
-
- function _extractContext( $context, $offset ) {
- if ( is_null( $context ) ) {
- return null;
- } else {
- // Hopefully integer overflow will be handled transparently here
- $inlineOffset = $this->mByte - $offset;
- return '; "' . substr( $context, $inlineOffset, 16 ) . '"';
- }
- }
-}
diff --git a/includes/WikiMap.php b/includes/WikiMap.php
index da4416de..34cd48da 100644
--- a/includes/WikiMap.php
+++ b/includes/WikiMap.php
@@ -28,8 +28,8 @@ class WikiMap {
/**
* Get a WikiReference object for $wikiID
*
- * @param string $wikiID wiki'd id (generally database name)
- * @return WikiReference object or null if the wiki was not found
+ * @param string $wikiID Wiki'd id (generally database name)
+ * @return WikiReference|null WikiReference object or null if the wiki was not found
*/
public static function getWiki( $wikiID ) {
global $wgConf;
@@ -58,7 +58,7 @@ class WikiMap {
* Convenience to get the wiki's display name
*
* @todo We can give more info than just the wiki id!
- * @param string $wikiID wiki'd id (generally database name)
+ * @param string $wikiID Wiki'd id (generally database name)
* @return string|int Wiki's name or $wiki_id if the wiki was not found
*/
public static function getWikiName( $wikiID ) {
@@ -73,10 +73,10 @@ class WikiMap {
/**
* Convenience to get a link to a user page on a foreign wiki
*
- * @param string $wikiID wiki'd id (generally database name)
- * @param string $user user name (must be normalised before calling this function!)
- * @param string $text link's text; optional, default to "User:$user"
- * @return String: HTML link or false if the wiki was not found
+ * @param string $wikiID Wiki'd id (generally database name)
+ * @param string $user User name (must be normalised before calling this function!)
+ * @param string $text Link's text; optional, default to "User:$user"
+ * @return string HTML link or false if the wiki was not found
*/
public static function foreignUserLink( $wikiID, $user, $text = null ) {
return self::makeForeignLink( $wikiID, "User:$user", $text );
@@ -85,10 +85,10 @@ class WikiMap {
/**
* Convenience to get a link to a page on a foreign wiki
*
- * @param string $wikiID wiki'd id (generally database name)
- * @param string $page page name (must be normalised before calling this function!)
- * @param string $text link's text; optional, default to $page
- * @return String: HTML link or false if the wiki was not found
+ * @param string $wikiID Wiki'd id (generally database name)
+ * @param string $page Page name (must be normalised before calling this function!)
+ * @param string $text Link's text; optional, default to $page
+ * @return string HTML link or false if the wiki was not found
*/
public static function makeForeignLink( $wikiID, $page, $text = null ) {
if ( !$text ) {
@@ -106,9 +106,9 @@ class WikiMap {
/**
* Convenience to get a url to a page on a foreign wiki
*
- * @param string $wikiID wiki'd id (generally database name)
- * @param string $page page name (must be normalised before calling this function!)
- * @return String: URL or false if the wiki was not found
+ * @param string $wikiID Wiki'd id (generally database name)
+ * @param string $page Page name (must be normalised before calling this function!)
+ * @return string|bool URL or false if the wiki was not found
*/
public static function getForeignURL( $wikiID, $page ) {
$wiki = WikiMap::getWiki( $wikiID );
@@ -127,16 +127,16 @@ class WikiMap {
class WikiReference {
private $mMinor; ///< 'en', 'meta', 'mediawiki', etc
private $mMajor; ///< 'wiki', 'wiktionary', etc
- private $mCanonicalServer; ///< canonical server URL, e.g. 'http://www.mediawiki.org'
+ private $mCanonicalServer; ///< canonical server URL, e.g. 'https://www.mediawiki.org'
private $mServer; ///< server URL, may be protocol-relative, e.g. '//www.mediawiki.org'
private $mPath; ///< path, '/wiki/$1'
/**
- * @param $major string
- * @param $minor string
- * @param $canonicalServer string
- * @param $path string
- * @param $server null|string
+ * @param string $major
+ * @param string $minor
+ * @param string $canonicalServer
+ * @param string $path
+ * @param null|string $server
*/
public function __construct( $major, $minor, $canonicalServer, $path, $server = null ) {
$this->mMajor = $major;
@@ -164,7 +164,7 @@ class WikiReference {
* Get the the URL in a way to de displayed to the user
* More or less Wikimedia specific
*
- * @return String
+ * @return string
*/
public function getDisplayName() {
$url = $this->getUrl( '' );
@@ -181,8 +181,8 @@ class WikiReference {
* Helper function for getUrl()
*
* @todo FIXME: This may be generalized...
- * @param string $page page name (must be normalised before calling this function!)
- * @return String: Url fragment
+ * @param string $page Page name (must be normalised before calling this function!)
+ * @return string Url fragment
*/
private function getLocalUrl( $page ) {
return str_replace( '$1', wfUrlEncode( str_replace( ' ', '_', $page ) ), $this->mPath );
@@ -191,8 +191,8 @@ class WikiReference {
/**
* Get a canonical (i.e. based on $wgCanonicalServer) URL to a page on this foreign wiki
*
- * @param string $page page name (must be normalised before calling this function!)
- * @return String: Url
+ * @param string $page Page name (must be normalised before calling this function!)
+ * @return string Url
*/
public function getCanonicalUrl( $page ) {
return $this->mCanonicalServer . $this->getLocalUrl( $page );
@@ -208,8 +208,8 @@ class WikiReference {
/**
* Alias for getCanonicalUrl(), for backwards compatibility.
- * @param $page string
- * @return String
+ * @param string $page
+ * @return string
*/
public function getUrl( $page ) {
return $this->getCanonicalUrl( $page );
@@ -219,8 +219,8 @@ class WikiReference {
* Get a URL based on $wgServer, like Title::getFullURL() would produce
* when called locally on the wiki.
*
- * @param string $page page name (must be normalized before calling this function!)
- * @return String: URL
+ * @param string $page Page name (must be normalized before calling this function!)
+ * @return string URL
*/
public function getFullUrl( $page ) {
return $this->mServer .
diff --git a/includes/Xml.php b/includes/Xml.php
index ac0539d1..159f7114 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -30,13 +30,15 @@ class Xml {
* Strings are assumed to not contain XML-illegal characters; special
* characters (<, >, &) are escaped but illegals are not touched.
*
- * @param string $element element name
+ * @param string $element Element name
* @param array $attribs Name=>value pairs. Values will be escaped.
- * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
- * @param bool $allowShortTag whether '' in $contents will result in a contentless closed tag
+ * @param string $contents Null to make an open tag only; '' for a contentless closed tag (default)
+ * @param bool $allowShortTag Whether '' in $contents will result in a contentless closed tag
* @return string
*/
- public static function element( $element, $attribs = null, $contents = '', $allowShortTag = true ) {
+ public static function element( $element, $attribs = null, $contents = '',
+ $allowShortTag = true
+ ) {
$out = '<' . $element;
if ( !is_null( $attribs ) ) {
$out .= self::expandAttributes( $attribs );
@@ -58,7 +60,7 @@ class Xml {
* to set the XML attributes : attributename="value".
* The values are passed to Sanitizer::encodeAttribute.
* Return null if no attributes given.
- * @param array $attribs of attributes for an XML element
+ * @param array $attribs Array of attributes for an XML element
* @throws MWException
* @return null|string
*/
@@ -81,9 +83,9 @@ class Xml {
* $wgContLang->normalize() validator first to ensure that no invalid UTF-8
* is passed.
*
- * @param $element String:
+ * @param string $element
* @param array $attribs Name=>value pairs. Values will be escaped.
- * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
+ * @param string $contents Null to make an open tag only; '' for a contentless closed tag (default)
* @return string
*/
public static function elementClean( $element, $attribs = array(), $contents = '' ) {
@@ -102,8 +104,8 @@ class Xml {
/**
* This opens an XML element
*
- * @param string $element name of the element
- * @param array $attribs of attributes, see Xml::expandAttributes()
+ * @param string $element Name of the element
+ * @param array $attribs Array of attributes, see Xml::expandAttributes()
* @return string
*/
public static function openElement( $element, $attribs = null ) {
@@ -112,7 +114,7 @@ class Xml {
/**
* Shortcut to close an XML element
- * @param string $element element name
+ * @param string $element Element name
* @return string
*/
public static function closeElement( $element ) {
@@ -123,9 +125,9 @@ class Xml {
* Same as Xml::element(), but does not escape contents. Handy when the
* content you have is already valid xml.
*
- * @param string $element element name
- * @param array $attribs of attributes
- * @param string $contents content of the element
+ * @param string $element Element name
+ * @param array $attribs Array of attributes
+ * @param string $contents Content of the element
* @return string
*/
public static function tags( $element, $attribs = null, $contents ) {
@@ -133,35 +135,13 @@ class Xml {
}
/**
- * Build a drop-down box for selecting a namespace
- *
- * @param $selected Mixed: Namespace which should be pre-selected
- * @param $all Mixed: Value of an item denoting all namespaces, or null to omit
- * @param $element_name String: value of the "name" attribute of the select tag
- * @param string $label optional label to add to the field
- * @return string
- * @deprecated since 1.19
- */
- public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
- wfDeprecated( __METHOD__, '1.19' );
- return Html::namespaceSelector( array(
- 'selected' => $selected,
- 'all' => $all,
- 'label' => $label,
- ), array(
- 'name' => $element_name,
- 'id' => 'namespace',
- 'class' => 'namespaceselector',
- ) );
- }
-
- /**
* Create a date selector
*
- * @param $selected Mixed: the month which should be selected, default ''
- * @param string $allmonths value of a special item denoting all month. Null to not include (default)
+ * @param string $selected The month which should be selected, default ''.
+ * @param string $allmonths Value of a special item denoting all month.
+ * Null to not include (default).
* @param string $id Element identifier
- * @return String: Html string containing the month selector
+ * @return string Html string containing the month selector
*/
public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
global $wgLang;
@@ -170,19 +150,27 @@ class Xml {
$selected = '';
}
if ( !is_null( $allmonths ) ) {
- $options[] = self::option( wfMessage( 'monthsall' )->text(), $allmonths, $selected === $allmonths );
+ $options[] = self::option(
+ wfMessage( 'monthsall' )->text(),
+ $allmonths,
+ $selected === $allmonths
+ );
}
for ( $i = 1; $i < 13; $i++ ) {
$options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
}
- return self::openElement( 'select', array( 'id' => $id, 'name' => 'month', 'class' => 'mw-month-selector' ) )
+ return self::openElement( 'select', array(
+ 'id' => $id,
+ 'name' => 'month',
+ 'class' => 'mw-month-selector'
+ ) )
. implode( "\n", $options )
. self::closeElement( 'select' );
}
/**
- * @param $year Integer
- * @param $month Integer
+ * @param int $year
+ * @param int $month
* @return string Formatted HTML
*/
public static function dateMenu( $year, $month ) {
@@ -216,13 +204,15 @@ class Xml {
* Construct a language selector appropriate for use in a form or preferences
*
* @param string $selected The language code of the selected language
- * @param boolean $customisedOnly If true only languages which have some content are listed
+ * @param bool $customisedOnly If true only languages which have some content are listed
* @param string $inLanguage The ISO code of the language to display the select list in (optional)
* @param array $overrideAttrs Override the attributes of the select tag (since 1.20)
* @param Message|null $msg Label message key (since 1.20)
- * @return array containing 2 items: label HTML and select list HTML
+ * @return array Array containing 2 items: label HTML and select list HTML
*/
- public static function languageSelector( $selected, $customisedOnly = true, $inLanguage = null, $overrideAttrs = array(), Message $msg = null ) {
+ public static function languageSelector( $selected, $customisedOnly = true,
+ $inLanguage = null, $overrideAttrs = array(), Message $msg = null
+ ) {
global $wgLanguageCode;
$include = $customisedOnly ? 'mwfile' : 'mw';
@@ -262,9 +252,9 @@ class Xml {
/**
* Shortcut to make a span element
- * @param string $text content of the element, will be escaped
- * @param string $class class name of the span element
- * @param array $attribs other attributes
+ * @param string $text Content of the element, will be escaped
+ * @param string $class Class name of the span element
+ * @param array $attribs Other attributes
* @return string
*/
public static function span( $text, $class, $attribs = array() ) {
@@ -273,10 +263,10 @@ class Xml {
/**
* Shortcut to make a specific element with a class attribute
- * @param string $text content of the element, will be escaped
- * @param string $class class name of the span element
- * @param string $tag element name
- * @param array $attribs other attributes
+ * @param string $text Content of the element, will be escaped
+ * @param string $class Class name of the span element
+ * @param string $tag Element name
+ * @param array $attribs Other attributes
* @return string
*/
public static function wrapClass( $text, $class, $tag = 'span', $attribs = array() ) {
@@ -285,10 +275,10 @@ class Xml {
/**
* Convenience function to build an HTML text input field
- * @param string $name value of the name attribute
- * @param int $size value of the size attribute
- * @param $value mixed value of the value attribute
- * @param array $attribs other attributes
+ * @param string $name Value of the name attribute
+ * @param int $size Value of the size attribute
+ * @param mixed $value Value of the value attribute
+ * @param array $attribs Other attributes
* @return string HTML
*/
public static function input( $name, $size = false, $value = false, $attribs = array() ) {
@@ -302,26 +292,30 @@ class Xml {
$attributes['value'] = $value;
}
- return self::element( 'input', $attributes + $attribs );
+ return self::element( 'input',
+ Html::getTextInputAttributes( $attributes + $attribs ) );
}
/**
* Convenience function to build an HTML password input field
- * @param string $name value of the name attribute
- * @param int $size value of the size attribute
- * @param $value mixed value of the value attribute
- * @param array $attribs other attributes
+ * @param string $name Value of the name attribute
+ * @param int $size Value of the size attribute
+ * @param mixed $value Value of the value attribute
+ * @param array $attribs Other attributes
* @return string HTML
*/
- public static function password( $name, $size = false, $value = false, $attribs = array() ) {
- return self::input( $name, $size, $value, array_merge( $attribs, array( 'type' => 'password' ) ) );
+ public static function password( $name, $size = false, $value = false,
+ $attribs = array()
+ ) {
+ return self::input( $name, $size, $value,
+ array_merge( $attribs, array( 'type' => 'password' ) ) );
}
/**
* Internal function for use in checkboxes and radio buttons and such.
*
- * @param $name string
- * @param $present bool
+ * @param string $name
+ * @param bool $present
*
* @return array
*/
@@ -331,9 +325,9 @@ class Xml {
/**
* Convenience function to build an HTML checkbox
- * @param string $name value of the name attribute
+ * @param string $name Value of the name attribute
* @param bool $checked Whether the checkbox is checked or not
- * @param array $attribs other attributes
+ * @param array $attribs Array other attributes
* @return string HTML
*/
public static function check( $name, $checked = false, $attribs = array() ) {
@@ -348,10 +342,10 @@ class Xml {
/**
* Convenience function to build an HTML radio button
- * @param string $name value of the name attribute
- * @param string $value value of the value attribute
+ * @param string $name Value of the name attribute
+ * @param string $value Value of the value attribute
* @param bool $checked Whether the checkbox is checked or not
- * @param array $attribs other attributes
+ * @param array $attribs Other attributes
* @return string HTML
*/
public static function radio( $name, $value, $checked = false, $attribs = array() ) {
@@ -363,9 +357,9 @@ class Xml {
/**
* Convenience function to build an HTML form label
- * @param string $label text of the label
- * @param $id
- * @param array $attribs an attribute array. This will usually be
+ * @param string $label Text of the label
+ * @param string $id
+ * @param array $attribs An attribute array. This will usually be
* the same array as is passed to the corresponding input element,
* so this function will cherry-pick appropriate attributes to
* apply to the label as well; only class and title are applied.
@@ -387,15 +381,17 @@ class Xml {
/**
* Convenience function to build an HTML text input field with a label
- * @param string $label text of the label
- * @param string $name value of the name attribute
- * @param string $id id of the input
- * @param int|Bool $size value of the size attribute
- * @param string|Bool $value value of the value attribute
- * @param array $attribs other attributes
+ * @param string $label Text of the label
+ * @param string $name Value of the name attribute
+ * @param string $id Id of the input
+ * @param int|bool $size Value of the size attribute
+ * @param string|bool $value Value of the value attribute
+ * @param array $attribs Other attributes
* @return string HTML
*/
- public static function inputLabel( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
+ public static function inputLabel( $label, $name, $id, $size = false,
+ $value = false, $attribs = array()
+ ) {
list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
return $label . '&#160;' . $input;
}
@@ -403,16 +399,18 @@ class Xml {
/**
* Same as Xml::inputLabel() but return input and label in an array
*
- * @param $label String
- * @param $name String
- * @param $id String
- * @param $size Int|Bool
- * @param $value String|Bool
- * @param $attribs array
+ * @param string $label
+ * @param string $name
+ * @param string $id
+ * @param int|bool $size
+ * @param string|bool $value
+ * @param array $attribs
*
* @return array
*/
- public static function inputLabelSep( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
+ public static function inputLabelSep( $label, $name, $id, $size = false,
+ $value = false, $attribs = array()
+ ) {
return array(
Xml::label( $label, $id, $attribs ),
self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
@@ -422,33 +420,42 @@ class Xml {
/**
* Convenience function to build an HTML checkbox with a label
*
- * @param $label
- * @param $name
- * @param $id
- * @param $checked bool
- * @param $attribs array
+ * @param string $label
+ * @param string $name
+ * @param string $id
+ * @param bool $checked
+ * @param array $attribs
*
* @return string HTML
*/
public static function checkLabel( $label, $name, $id, $checked = false, $attribs = array() ) {
- return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
+ global $wgUseMediaWikiUIEverywhere;
+ $chkLabel = self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
'&#160;' .
self::label( $label, $id, $attribs );
+
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $chkLabel = self::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
+ $chkLabel . self::closeElement( 'div' );
+ }
+ return $chkLabel;
}
/**
* Convenience function to build an HTML radio button with a label
*
- * @param $label
- * @param $name
- * @param $value
- * @param $id
- * @param $checked bool
- * @param $attribs array
+ * @param string $label
+ * @param string $name
+ * @param string $value
+ * @param string $id
+ * @param bool $checked
+ * @param array $attribs
*
* @return string HTML
*/
- public static function radioLabel( $label, $name, $value, $id, $checked = false, $attribs = array() ) {
+ public static function radioLabel( $label, $name, $value, $id,
+ $checked = false, $attribs = array()
+ ) {
return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
'&#160;' .
self::label( $label, $id, $attribs );
@@ -456,20 +463,34 @@ class Xml {
/**
* Convenience function to build an HTML submit button
- * @param string $value label text for the button
- * @param array $attribs optional custom attributes
+ * When $wgUseMediaWikiUIEverywhere is true it will default to a constructive button
+ * @param string $value Label text for the button
+ * @param array $attribs Optional custom attributes
* @return string HTML
*/
public static function submitButton( $value, $attribs = array() ) {
- return Html::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
+ global $wgUseMediaWikiUIEverywhere;
+ $baseAttrs = array(
+ 'type' => 'submit',
+ 'value' => $value,
+ );
+ // Done conditionally for time being as it is possible
+ // some submit forms
+ // might need to be mw-ui-destructive (e.g. delete a page)
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $baseAttrs['class'] = 'mw-ui-button mw-ui-constructive';
+ }
+ // Any custom attributes will take precendence of anything in baseAttrs e.g. override the class
+ $attribs = $attribs + $baseAttrs;
+ return Html::element( 'input', $attribs );
}
/**
* Convenience function to build an HTML drop-down list item.
- * @param string $text text for this item. Will be HTML escaped
- * @param string $value form submission value; if empty, use text
- * @param $selected boolean: if true, will be the default selected item
- * @param array $attribs optional additional HTML attributes
+ * @param string $text Text for this item. Will be HTML escaped
+ * @param string $value Form submission value; if empty, use text
+ * @param bool $selected If true, will be the default selected item
+ * @param array $attribs Optional additional HTML attributes
* @return string HTML
*/
public static function option( $text, $value = null, $selected = false,
@@ -486,48 +507,51 @@ class Xml {
/**
* Build a drop-down box from a textual list.
*
- * @param $name Mixed: Name and id for the drop-down
- * @param $list Mixed: Correctly formatted text (newline delimited) to be used to generate the options
- * @param $other Mixed: Text for the "Other reasons" option
- * @param $selected Mixed: Option which should be pre-selected
- * @param $class Mixed: CSS classes for the drop-down
- * @param $tabindex Mixed: Value of the tabindex attribute
+ * @param string $name Name and id for the drop-down
+ * @param string $list Correctly formatted text (newline delimited) to be
+ * used to generate the options.
+ * @param string $other Text for the "Other reasons" option
+ * @param string $selected Option which should be pre-selected
+ * @param string $class CSS classes for the drop-down
+ * @param int $tabindex Value of the tabindex attribute
* @return string
*/
- public static function listDropDown( $name = '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
+ public static function listDropDown( $name = '', $list = '', $other = '',
+ $selected = '', $class = '', $tabindex = null
+ ) {
$optgroup = false;
$options = self::option( $other, 'other', $selected === 'other' );
foreach ( explode( "\n", $list ) as $option ) {
- $value = trim( $option );
- if ( $value == '' ) {
- continue;
- } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
- // A new group is starting ...
- $value = trim( substr( $value, 1 ) );
- if ( $optgroup ) {
- $options .= self::closeElement( 'optgroup' );
- }
- $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
- $optgroup = true;
- } elseif ( substr( $value, 0, 2 ) == '**' ) {
- // groupmember
- $value = trim( substr( $value, 2 ) );
- $options .= self::option( $value, $value, $selected === $value );
- } else {
- // groupless reason list
- if ( $optgroup ) {
- $options .= self::closeElement( 'optgroup' );
- }
- $options .= self::option( $value, $value, $selected === $value );
- $optgroup = false;
+ $value = trim( $option );
+ if ( $value == '' ) {
+ continue;
+ } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
+ // A new group is starting ...
+ $value = trim( substr( $value, 1 ) );
+ if ( $optgroup ) {
+ $options .= self::closeElement( 'optgroup' );
+ }
+ $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
+ $optgroup = true;
+ } elseif ( substr( $value, 0, 2 ) == '**' ) {
+ // groupmember
+ $value = trim( substr( $value, 2 ) );
+ $options .= self::option( $value, $value, $selected === $value );
+ } else {
+ // groupless reason list
+ if ( $optgroup ) {
+ $options .= self::closeElement( 'optgroup' );
}
+ $options .= self::option( $value, $value, $selected === $value );
+ $optgroup = false;
}
+ }
- if ( $optgroup ) {
- $options .= self::closeElement( 'optgroup' );
- }
+ if ( $optgroup ) {
+ $options .= self::closeElement( 'optgroup' );
+ }
$attribs = array();
@@ -554,8 +578,10 @@ class Xml {
/**
* Shortcut for creating fieldsets.
*
- * @param string|bool $legend Legend of the fieldset. If evaluates to false, legend is not added.
- * @param string $content Pre-escaped content for the fieldset. If false, only open fieldset is returned.
+ * @param string|bool $legend Legend of the fieldset. If evaluates to false,
+ * legend is not added.
+ * @param string $content Pre-escaped content for the fieldset. If false,
+ * only open fieldset is returned.
* @param array $attribs Any attributes to fieldset-element.
*
* @return string
@@ -588,12 +614,14 @@ class Xml {
*/
public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
return self::element( 'textarea',
- array(
- 'name' => $name,
- 'id' => $name,
- 'cols' => $cols,
- 'rows' => $rows
- ) + $attribs,
+ Html::getTextInputAttributes(
+ array(
+ 'name' => $name,
+ 'id' => $name,
+ 'cols' => $cols,
+ 'rows' => $rows
+ ) + $attribs
+ ),
$content, false );
}
@@ -603,8 +631,8 @@ class Xml {
* Illegal control characters are assumed not to be present.
*
* @deprecated since 1.21; use Xml::encodeJsVar() or Xml::encodeJsCall() instead
- * @param string $string to escape
- * @return String
+ * @param string $string String to escape
+ * @return string
*/
public static function escapeJsString( $string ) {
// See ECMA 262 section 7.8.4 for string literal format
@@ -642,7 +670,7 @@ class Xml {
*
* @param mixed $value The value being encoded. Can be any type except a resource.
* @param bool $pretty If true, add non-significant whitespace to improve readability.
- * @return string|bool: String if successful; false upon failure
+ * @return string|bool String if successful; false upon failure
*/
public static function encodeJsVar( $value, $pretty = false ) {
if ( $value instanceof XmlJsCode ) {
@@ -660,7 +688,7 @@ class Xml {
* which evaluates to a function object which is called.
* @param array $args The arguments to pass to the function.
* @param bool $pretty If true, add non-significant whitespace to improve readability.
- * @return string|bool: String if successful; false upon failure
+ * @return string|bool String if successful; false upon failure
*/
public static function encodeJsCall( $name, $args, $pretty = false ) {
foreach ( $args as &$arg ) {
@@ -680,7 +708,7 @@ class Xml {
* Check if a string is well-formed XML.
* Must include the surrounding tag.
*
- * @param string $text string to test.
+ * @param string $text String to test.
* @return bool
*
* @todo Error position reporting return
@@ -710,7 +738,7 @@ class Xml {
* Wraps fragment in an \<html\> bit and doctype, so it can be a fragment
* and can use HTML named entities.
*
- * @param $text String:
+ * @param string $text
* @return bool
*/
public static function isWellFormedXmlFragment( $text ) {
@@ -727,7 +755,7 @@ class Xml {
* Replace " > and < with their respective HTML entities ( &quot;,
* &gt;, &lt;)
*
- * @param string $in text that might contain HTML tags.
+ * @param string $in Text that might contain HTML tags.
* @return string Escaped string
*/
public static function escapeTagsOnly( $in ) {
@@ -740,8 +768,11 @@ class Xml {
/**
* Generate a form (without the opening form element).
* Output optionally includes a submit button.
- * @param array $fields Associative array, key is the name of a message that contains a description for the field, value is an HTML string containing the appropriate input.
- * @param string $submitLabel The name of a message containing a label for the submit button.
+ * @param array $fields Associative array, key is the name of a message that
+ * contains a description for the field, value is an HTML string
+ * containing the appropriate input.
+ * @param string $submitLabel The name of a message containing a label for
+ * the submit button.
* @param array $submitAttribs The attributes to add to the submit button
* @return string HTML form.
*/
@@ -758,14 +789,17 @@ class Xml {
// the input somehow.
$form .= Xml::tags( 'td', array( 'class' => 'mw-label' ), wfMessage( $labelmsg )->parse() );
- $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
+ $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) )
+ . $input . Xml::closeElement( 'td' );
$form .= Xml::closeElement( 'tr' );
}
if ( $submitLabel ) {
$form .= Xml::openElement( 'tr' );
$form .= Xml::tags( 'td', array(), '' );
- $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs ) . Xml::closeElement( 'td' );
+ $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) )
+ . Xml::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs )
+ . Xml::closeElement( 'td' );
$form .= Xml::closeElement( 'tr' );
}
@@ -859,22 +893,22 @@ class XmlSelect {
}
/**
- * @param $default
+ * @param string $default
*/
public function setDefault( $default ) {
$this->default = $default;
}
/**
- * @param $name string
- * @param $value
+ * @param string $name
+ * @param array $value
*/
public function setAttribute( $name, $value ) {
$this->attributes[$name] = $value;
}
/**
- * @param $name
+ * @param string $name
* @return array|null
*/
public function getAttribute( $name ) {
@@ -886,8 +920,8 @@ class XmlSelect {
}
/**
- * @param $name
- * @param $value bool
+ * @param string $name
+ * @param bool $value
*/
public function addOption( $name, $value = false ) {
// Stab stab stab
@@ -901,7 +935,7 @@ class XmlSelect {
* label => value
* label => ( label => value, label => value )
*
- * @param $options
+ * @param array $options
*/
public function addOptions( $options ) {
$this->options[] = $options;
@@ -912,7 +946,7 @@ class XmlSelect {
* label => value
* label => ( label => value, label => value )
*
- * @param $options
+ * @param array $options
* @param bool $default
* @return string
*/
diff --git a/includes/ZhClient.php b/includes/ZhClient.php
deleted file mode 100644
index c5955aec..00000000
--- a/includes/ZhClient.php
+++ /dev/null
@@ -1,164 +0,0 @@
-<?php
-/**
- * Client for querying zhdaemon.
- *
- * 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
- */
-
-/**
- * Client for querying zhdaemon
- */
-class ZhClient {
- var $mHost, $mPort, $mFP, $mConnected;
-
- /**
- * Constructor
- *
- * @param $host
- * @param $port
- *
- * @return ZhClient
- */
- function __construct( $host, $port ) {
- $this->mHost = $host;
- $this->mPort = $port;
- $this->mConnected = $this->connect();
- }
-
- /**
- * Check if connection to zhdaemon is successful
- *
- * @return bool
- */
- function isconnected() {
- return $this->mConnected;
- }
-
- /**
- * Establish connection
- *
- * @access private
- *
- * @return bool
- */
- function connect() {
- wfSuppressWarnings();
- $errno = $errstr = '';
- $this->mFP = fsockopen( $this->mHost, $this->mPort, $errno, $errstr, 30 );
- wfRestoreWarnings();
- return !$this->mFP;
- }
-
- /**
- * Query the daemon and return the result
- *
- * @access private
- *
- * @return string
- */
- function query( $request ) {
- if ( !$this->mConnected ) {
- return false;
- }
-
- fwrite( $this->mFP, $request );
-
- $result = fgets( $this->mFP, 1024 );
-
- list( $status, $len ) = explode( ' ', $result );
- if ( $status == 'ERROR' ) {
- // $len is actually the error code...
- print "zhdaemon error $len<br />\n";
- return false;
- }
- $bytesread = 0;
- $data = '';
- while ( !feof( $this->mFP ) && $bytesread < $len ) {
- $str = fread( $this->mFP, $len - $bytesread );
- $bytesread += strlen( $str );
- $data .= $str;
- }
- // data should be of length $len. otherwise something is wrong
- return strlen( $data ) == $len;
- }
-
- /**
- * Convert the input to a different language variant
- *
- * @param string $text input text
- * @param string $tolang language variant
- * @return string the converted text
- */
- function convert( $text, $tolang ) {
- $len = strlen( $text );
- $q = "CONV $tolang $len\n$text";
- $result = $this->query( $q );
- if ( !$result ) {
- $result = $text;
- }
- return $result;
- }
-
- /**
- * Convert the input to all possible variants
- *
- * @param string $text input text
- * @return array langcode => converted_string
- */
- function convertToAllVariants( $text ) {
- $len = strlen( $text );
- $q = "CONV ALL $len\n$text";
- $result = $this->query( $q );
- if ( !$result ) {
- return false;
- }
- list( $infoline, $data ) = explode( '|', $result, 2 );
- $info = explode( ';', $infoline );
- $ret = array();
- $i = 0;
- foreach ( $info as $variant ) {
- list( $code, $len ) = explode( ' ', $variant );
- $ret[strtolower( $code )] = substr( $data, $i, $len );
- $i += $len;
- }
- return $ret;
- }
-
- /**
- * Perform word segmentation
- *
- * @param string $text input text
- * @return string segmented text
- */
- function segment( $text ) {
- $len = strlen( $text );
- $q = "SEG $len\n$text";
- $result = $this->query( $q );
- if ( !$result ) { // fallback to character based segmentation
- $result = $this->segment( $text );
- }
- return $result;
- }
-
- /**
- * Close the connection
- */
- function close() {
- fclose( $this->mFP );
- }
-}
diff --git a/includes/ZhConversion.php b/includes/ZhConversion.php
index df98836f..651b1140 100644
--- a/includes/ZhConversion.php
+++ b/includes/ZhConversion.php
@@ -2,7 +2,7 @@
/**
* Simplified / Traditional Chinese conversion tables
*
- * Automatically generated using code and data in includes/zhtable/
+ * Automatically generated using code and data in maintenance/language/zhtable/
* Do not modify directly!
*
* @file
@@ -7578,6 +7578,7 @@ $zh2Hant = array(
'管人吊脚儿事' => '管人弔腳兒事',
'管制法' => '管制法',
'管干' => '管幹',
+'箱里' => '箱裡',
'节欲' => '節慾',
'节余' => '節餘',
'范例' => '範例',
diff --git a/includes/Action.php b/includes/actions/Action.php
index 4b6e4468..8d11d901 100644
--- a/includes/Action.php
+++ b/includes/actions/Action.php
@@ -38,27 +38,30 @@ abstract class Action {
/**
* Page on which we're performing the action
+ * @since 1.17
* @var WikiPage|Article|ImagePage|CategoryPage|Page $page
*/
protected $page;
/**
* IContextSource if specified; otherwise we'll use the Context from the Page
+ * @since 1.17
* @var IContextSource $context
*/
protected $context;
/**
* The fields used to create the HTMLForm
- * @var Array $fields
+ * @since 1.17
+ * @var array $fields
*/
protected $fields;
/**
* Get the Action subclass which should be used to handle this action, false if
* the action is disabled, or null if it's not recognised
- * @param $action String
- * @param $overrides Array
+ * @param string $action
+ * @param array $overrides
* @return bool|null|string|callable
*/
final private static function getClass( $action, array $overrides ) {
@@ -82,10 +85,11 @@ abstract class Action {
/**
* Get an appropriate Action subclass for the given action
- * @param $action String
- * @param $page Page
- * @param $context IContextSource
- * @return Action|bool|null false if the action is disabled, null
+ * @since 1.17
+ * @param string $action
+ * @param Page $page
+ * @param IContextSource $context
+ * @return Action|bool|null False if the action is disabled, null
* if it is not recognised
*/
final public static function factory( $action, Page $page, IContextSource $context = null ) {
@@ -109,8 +113,8 @@ abstract class Action {
* $wgActions will be replaced by "nosuchaction".
*
* @since 1.19
- * @param $context IContextSource
- * @return string: action name
+ * @param IContextSource $context
+ * @return string Action name
*/
final public static function getActionName( IContextSource $context ) {
global $wgActions;
@@ -152,9 +156,10 @@ abstract class Action {
/**
* Check if a given action is recognised, even if it's disabled
+ * @since 1.17
*
- * @param string $name name of an action
- * @return Bool
+ * @param string $name Name of an action
+ * @return bool
*/
final public static function exists( $name ) {
return self::getClass( $name, array() ) !== null;
@@ -162,14 +167,15 @@ abstract class Action {
/**
* Get the IContextSource in use here
+ * @since 1.17
* @return IContextSource
*/
final public function getContext() {
if ( $this->context instanceof IContextSource ) {
return $this->context;
- } else if ( $this->page instanceof Article ) {
+ } elseif ( $this->page instanceof Article ) {
// NOTE: $this->page can be a WikiPage, which does not have a context.
- wfDebug( __METHOD__ . ': no context known, falling back to Article\'s context.' );
+ wfDebug( __METHOD__ . ": no context known, falling back to Article's context.\n" );
return $this->page->getContext();
}
@@ -179,6 +185,7 @@ abstract class Action {
/**
* Get the WebRequest being used for this instance
+ * @since 1.17
*
* @return WebRequest
*/
@@ -188,6 +195,7 @@ abstract class Action {
/**
* Get the OutputPage being used for this instance
+ * @since 1.17
*
* @return OutputPage
*/
@@ -197,6 +205,7 @@ abstract class Action {
/**
* Shortcut to get the User being used for this instance
+ * @since 1.17
*
* @return User
*/
@@ -206,6 +215,7 @@ abstract class Action {
/**
* Shortcut to get the Skin being used for this instance
+ * @since 1.17
*
* @return Skin
*/
@@ -223,18 +233,9 @@ abstract class Action {
}
/**
- * Shortcut to get the user Language being used for this instance
- *
- * @deprecated since 1.19 Use getLanguage instead
- * @return Language
- */
- final public function getLang() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->getLanguage();
- }
-
- /**
* Shortcut to get the Title object from the page
+ * @since 1.17
+ *
* @return Title
*/
final public function getTitle() {
@@ -245,7 +246,7 @@ abstract class Action {
* Get a Message object with context set
* Parameters are the same as wfMessage()
*
- * @return Message object
+ * @return Message
*/
final public function msg() {
$params = func_get_args();
@@ -257,8 +258,8 @@ abstract class Action {
*
* Only public since 1.21
*
- * @param $page Page
- * @param $context IContextSource
+ * @param Page $page
+ * @param IContextSource $context
*/
public function __construct( Page $page, IContextSource $context = null ) {
if ( $context === null ) {
@@ -273,14 +274,18 @@ abstract class Action {
/**
* Return the name of the action this object responds to
- * @return String lowercase
+ * @since 1.17
+ *
+ * @return string Lowercase name
*/
abstract public function getName();
/**
* Get the permission required to perform this action. Often, but not always,
* the same as the action name
- * @return String|null
+ * @since 1.17
+ *
+ * @return string|null
*/
public function getRestriction() {
return null;
@@ -290,10 +295,10 @@ abstract class Action {
* Checks if the given user (identified by an object) can perform this action. Can be
* overridden by sub-classes with more complicated permissions schemes. Failures here
* must throw subclasses of ErrorPageError
+ * @since 1.17
*
- * @param $user User: the user to check, or null to use the context user
+ * @param User $user The user to check, or null to use the context user
* @throws UserBlockedError|ReadOnlyError|PermissionsError
- * @return bool True on success
*/
protected function checkCanExecute( User $user ) {
$right = $this->getRestriction();
@@ -315,12 +320,13 @@ abstract class Action {
if ( $this->requiresWrite() && wfReadOnly() ) {
throw new ReadOnlyError();
}
- return true;
}
/**
* Whether this action requires the wiki not to be locked
- * @return Bool
+ * @since 1.17
+ *
+ * @return bool
*/
public function requiresWrite() {
return true;
@@ -328,7 +334,9 @@ abstract class Action {
/**
* Whether this action can still be executed by a blocked user
- * @return Bool
+ * @since 1.17
+ *
+ * @return bool
*/
public function requiresUnblock() {
return true;
@@ -337,19 +345,20 @@ abstract class Action {
/**
* Set output headers for noindexing etc. This function will not be called through
* the execute() entry point, so only put UI-related stuff in here.
+ * @since 1.17
*/
protected function setHeaders() {
$out = $this->getOutput();
$out->setRobotPolicy( "noindex,nofollow" );
$out->setPageTitle( $this->getPageTitle() );
- $this->getOutput()->setSubtitle( $this->getDescription() );
+ $out->setSubtitle( $this->getDescription() );
$out->setArticleRelated( true );
}
/**
* Returns the name that goes in the \<h1\> page title
*
- * @return String
+ * @return string
*/
protected function getPageTitle() {
return $this->getTitle()->getPrefixedText();
@@ -357,8 +366,9 @@ abstract class Action {
/**
* Returns the description that goes below the \<h1\> tag
+ * @since 1.17
*
- * @return String
+ * @return string
*/
protected function getDescription() {
return $this->msg( strtolower( $this->getName() ) )->escaped();
@@ -368,233 +378,9 @@ abstract class Action {
* The main action entry point. Do all output for display and send it to the context
* output. Do not use globals $wgOut, $wgRequest, etc, in implementations; use
* $this->getOutput(), etc.
+ * @since 1.17
+ *
* @throws ErrorPageError
*/
abstract public function show();
-
- /**
- * Execute the action in a silent fashion: do not display anything or release any errors.
- * @return Bool whether execution was successful
- */
- abstract public function execute();
-}
-
-/**
- * An action which shows a form and does something based on the input from the form
- */
-abstract class FormAction extends Action {
-
- /**
- * Get an HTMLForm descriptor array
- * @return Array
- */
- abstract protected function getFormFields();
-
- /**
- * Add pre- or post-text to the form
- * @return String HTML which will be sent to $form->addPreText()
- */
- protected function preText() {
- return '';
- }
-
- /**
- * @return string
- */
- protected function postText() {
- return '';
- }
-
- /**
- * Play with the HTMLForm if you need to more substantially
- * @param $form HTMLForm
- */
- protected function alterForm( HTMLForm $form ) {
- }
-
- /**
- * Get the HTMLForm to control behavior
- * @return HTMLForm|null
- */
- protected function getForm() {
- $this->fields = $this->getFormFields();
-
- // Give hooks a chance to alter the form, adding extra fields or text etc
- wfRunHooks( 'ActionModifyFormFields', array( $this->getName(), &$this->fields, $this->page ) );
-
- $form = new HTMLForm( $this->fields, $this->getContext(), $this->getName() );
- $form->setSubmitCallback( array( $this, 'onSubmit' ) );
-
- // Retain query parameters (uselang etc)
- $form->addHiddenField( 'action', $this->getName() ); // Might not be the same as the query string
- $params = array_diff_key(
- $this->getRequest()->getQueryValues(),
- array( 'action' => null, 'title' => null )
- );
- $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
-
- $form->addPreText( $this->preText() );
- $form->addPostText( $this->postText() );
- $this->alterForm( $form );
-
- // Give hooks a chance to alter the form, adding extra fields or text etc
- wfRunHooks( 'ActionBeforeFormDisplay', array( $this->getName(), &$form, $this->page ) );
-
- return $form;
- }
-
- /**
- * Process the form on POST submission. If you return false from getFormFields(),
- * this will obviously never be reached. If you don't want to do anything with the
- * form, just return false here
- * @param $data Array
- * @return Bool|Array true for success, false for didn't-try, array of errors on failure
- */
- abstract public function onSubmit( $data );
-
- /**
- * Do something exciting on successful processing of the form. This might be to show
- * a confirmation message (watch, rollback, etc) or to redirect somewhere else (edit,
- * protect, etc).
- */
- abstract public function onSuccess();
-
- /**
- * The basic pattern for actions is to display some sort of HTMLForm UI, maybe with
- * some stuff underneath (history etc); to do some processing on submission of that
- * form (delete, protect, etc) and to do something exciting on 'success', be that
- * display something new or redirect to somewhere. Some actions have more exotic
- * behavior, but that's what subclassing is for :D
- */
- public function show() {
- $this->setHeaders();
-
- // This will throw exceptions if there's a problem
- $this->checkCanExecute( $this->getUser() );
-
- $form = $this->getForm();
- if ( $form->show() ) {
- $this->onSuccess();
- }
- }
-
- /**
- * @see Action::execute()
- *
- * @param $data array|null
- * @param $captureErrors bool
- * @throws ErrorPageError|Exception
- * @return bool
- */
- public function execute( array $data = null, $captureErrors = true ) {
- try {
- // Set a new context so output doesn't leak.
- $this->context = clone $this->getContext();
-
- // This will throw exceptions if there's a problem
- $this->checkCanExecute( $this->getUser() );
-
- $fields = array();
- foreach ( $this->fields as $key => $params ) {
- if ( isset( $data[$key] ) ) {
- $fields[$key] = $data[$key];
- } elseif ( isset( $params['default'] ) ) {
- $fields[$key] = $params['default'];
- } else {
- $fields[$key] = null;
- }
- }
- $status = $this->onSubmit( $fields );
- if ( $status === true ) {
- // This might do permanent stuff
- $this->onSuccess();
- return true;
- } else {
- return false;
- }
- }
- catch ( ErrorPageError $e ) {
- if ( $captureErrors ) {
- return false;
- } else {
- throw $e;
- }
- }
- }
-}
-
-/**
- * An action which just does something, without showing a form first.
- */
-abstract class FormlessAction extends Action {
-
- /**
- * Show something on GET request.
- * @return String|null will be added to the HTMLForm if present, or just added to the
- * output if not. Return null to not add anything
- */
- abstract public function onView();
-
- /**
- * We don't want an HTMLForm
- * @return bool
- */
- protected function getFormFields() {
- return false;
- }
-
- /**
- * @param $data Array
- * @return bool
- */
- public function onSubmit( $data ) {
- return false;
- }
-
- /**
- * @return bool
- */
- public function onSuccess() {
- return false;
- }
-
- public function show() {
- $this->setHeaders();
-
- // This will throw exceptions if there's a problem
- $this->checkCanExecute( $this->getUser() );
-
- $this->getOutput()->addHTML( $this->onView() );
- }
-
- /**
- * Execute the action silently, not giving any output. Since these actions don't have
- * forms, they probably won't have any data, but some (eg rollback) may do
- * @param array $data values that would normally be in the GET request
- * @param bool $captureErrors whether to catch exceptions and just return false
- * @throws ErrorPageError|Exception
- * @return Bool whether execution was successful
- */
- public function execute( array $data = null, $captureErrors = true ) {
- try {
- // Set a new context so output doesn't leak.
- $this->context = clone $this->getContext();
- if ( is_array( $data ) ) {
- $this->context->setRequest( new FauxRequest( $data, false ) );
- }
-
- // This will throw exceptions if there's a problem
- $this->checkCanExecute( $this->getUser() );
-
- $this->onView();
- return true;
- }
- catch ( ErrorPageError $e ) {
- if ( $captureErrors ) {
- return false;
- } else {
- throw $e;
- }
- }
- }
}
diff --git a/includes/actions/CachedAction.php b/includes/actions/CachedAction.php
index bfdda7b9..bc4df349 100644
--- a/includes/actions/CachedAction.php
+++ b/includes/actions/CachedAction.php
@@ -58,7 +58,7 @@ abstract class CachedAction extends FormlessAction implements ICacheHelper {
* If the cache is enabled or not.
*
* @since 1.20
- * @var boolean
+ * @var bool
*/
protected $cacheEnabled = true;
@@ -66,7 +66,7 @@ abstract class CachedAction extends FormlessAction implements ICacheHelper {
* Sets if the cache should be enabled or not.
*
* @since 1.20
- * @param boolean $cacheEnabled
+ * @param bool $cacheEnabled
*/
public function setCacheEnabled( $cacheEnabled ) {
$this->cacheHelper->setCacheEnabled( $cacheEnabled );
@@ -78,8 +78,8 @@ abstract class CachedAction extends FormlessAction implements ICacheHelper {
*
* @since 1.20
*
- * @param integer|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
- * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
+ * @param int|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
+ * @param bool|null $cacheEnabled Sets if the cache should be enabled or not.
*/
public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
$this->cacheHelper = new CacheHelper();
@@ -110,7 +110,7 @@ abstract class CachedAction extends FormlessAction implements ICacheHelper {
*
* @since 1.20
*
- * @param {function} $computeFunction
+ * @param callable $computeFunction
* @param array|mixed $args
* @param string|null $key
*
@@ -128,12 +128,13 @@ abstract class CachedAction extends FormlessAction implements ICacheHelper {
*
* @since 1.20
*
- * @param {function} $computeFunction
+ * @param callable $computeFunction
* @param array $args
* @param string|null $key
*/
public function addCachedHTML( $computeFunction, $args = array(), $key = null ) {
- $this->getOutput()->addHTML( $this->cacheHelper->getCachedValue( $computeFunction, $args, $key ) );
+ $html = $this->cacheHelper->getCachedValue( $computeFunction, $args, $key );
+ $this->getOutput()->addHTML( $html );
}
/**
@@ -147,11 +148,12 @@ abstract class CachedAction extends FormlessAction implements ICacheHelper {
}
/**
- * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
+ * Sets the time to live for the cache, in seconds or a unix timestamp
+ * indicating the point of expiry.
*
* @since 1.20
*
- * @param integer $cacheExpiry
+ * @param int $cacheExpiry
*/
public function setExpiry( $cacheExpiry ) {
$this->cacheHelper->setExpiry( $cacheExpiry );
@@ -177,12 +179,11 @@ abstract class CachedAction extends FormlessAction implements ICacheHelper {
*
* @since 1.20
*
- * @param boolean $hasCached
+ * @param bool $hasCached
*/
public function onCacheInitialized( $hasCached ) {
if ( $hasCached ) {
$this->getOutput()->setSubtitle( $this->cacheHelper->getCachedNotice( $this->getContext() ) );
}
}
-
}
diff --git a/includes/actions/CreditsAction.php b/includes/actions/CreditsAction.php
index 0a2bf306..e064aab4 100644
--- a/includes/actions/CreditsAction.php
+++ b/includes/actions/CreditsAction.php
@@ -39,7 +39,7 @@ class CreditsAction extends FormlessAction {
/**
* This is largely cadged from PageHistory::history
*
- * @return String HTML
+ * @return string HTML
*/
public function onView() {
wfProfileIn( __METHOD__ );
@@ -58,9 +58,9 @@ class CreditsAction extends FormlessAction {
/**
* Get a list of contributors
*
- * @param int $cnt maximum list of contributors to show
- * @param bool $showIfMax whether to contributors if there more than $cnt
- * @return String: html
+ * @param int $cnt Maximum list of contributors to show
+ * @param bool $showIfMax Whether to contributors if there more than $cnt
+ * @return string Html
*/
public function getCredits( $cnt, $showIfMax = true ) {
wfProfileIn( __METHOD__ );
@@ -74,13 +74,14 @@ class CreditsAction extends FormlessAction {
}
wfProfileOut( __METHOD__ );
+
return $s;
}
/**
* Get the last author with the last modification time
* @param Page $page
- * @return String HTML
+ * @return string HTML
*/
protected function getAuthor( Page $page ) {
$user = User::newFromName( $page->getUserText(), false );
@@ -94,19 +95,29 @@ class CreditsAction extends FormlessAction {
$d = '';
$t = '';
}
+
return $this->msg( 'lastmodifiedatby', $d, $t )->rawParams(
$this->userLink( $user ) )->params( $user->getName() )->escaped();
}
/**
+ * Whether we can display the user's real name (not a hidden pref)
+ *
+ * @since 1.24
+ * @return bool
+ */
+ protected function canShowRealUserName() {
+ $hiddenPrefs = $this->context->getConfig()->get( 'HiddenPrefs' );
+ return !in_array( 'realname', $hiddenPrefs );
+ }
+
+ /**
* Get a list of contributors of $article
- * @param int $cnt maximum list of contributors to show
- * @param bool $showIfMax whether to contributors if there more than $cnt
- * @return String: html
+ * @param int $cnt Maximum list of contributors to show
+ * @param bool $showIfMax Whether to contributors if there more than $cnt
+ * @return string Html
*/
protected function getContributors( $cnt, $showIfMax ) {
- global $wgHiddenPrefs;
-
$contributors = $this->page->getContributors();
$others_link = false;
@@ -125,11 +136,12 @@ class CreditsAction extends FormlessAction {
$anon_ips = array();
# Sift for real versus user names
+ /** @var $user User */
foreach ( $contributors as $user ) {
$cnt--;
if ( $user->isLoggedIn() ) {
$link = $this->link( $user );
- if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
+ if ( $this->canShowRealUserName() && $user->getRealName() ) {
$real_names[] = $link;
} else {
$user_names[] = $link;
@@ -175,6 +187,7 @@ class CreditsAction extends FormlessAction {
}
$count = count( $fulllist );
+
# "Based on work by ..."
return $count
? $this->msg( 'othercontribs' )->rawParams(
@@ -184,12 +197,11 @@ class CreditsAction extends FormlessAction {
/**
* Get a link to $user's user page
- * @param $user User object
- * @return String: html
+ * @param User $user
+ * @return string Html
*/
protected function link( User $user ) {
- global $wgHiddenPrefs;
- if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) {
+ if ( $this->canShowRealUserName() && !$user->isAnon() ) {
$real = $user->getRealName();
} else {
$real = false;
@@ -204,16 +216,15 @@ class CreditsAction extends FormlessAction {
/**
* Get a link to $user's user page
- * @param $user User object
- * @return String: html
+ * @param User $user
+ * @return string Html
*/
protected function userLink( User $user ) {
$link = $this->link( $user );
if ( $user->isAnon() ) {
return $this->msg( 'anonuser' )->rawParams( $link )->parse();
} else {
- global $wgHiddenPrefs;
- if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
+ if ( $this->canShowRealUserName() && $user->getRealName() ) {
return $link;
} else {
return $this->msg( 'siteuser' )->rawParams( $link )->params( $user->getName() )->escaped();
@@ -223,7 +234,7 @@ class CreditsAction extends FormlessAction {
/**
* Get a link to action=credits of $article page
- * @return String: HTML link
+ * @return string HTML link
*/
protected function othersLink() {
return Linker::linkKnown(
diff --git a/includes/actions/DeleteAction.php b/includes/actions/DeleteAction.php
index db7123db..12f0dff0 100644
--- a/includes/actions/DeleteAction.php
+++ b/includes/actions/DeleteAction.php
@@ -41,9 +41,13 @@ class DeleteAction extends FormlessAction {
}
public function show() {
-
+ if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $out = $this->getOutput();
+ $out->addModuleStyles( array(
+ 'mediawiki.ui.input',
+ 'mediawiki.ui.checkbox',
+ ) );
+ }
$this->page->delete();
-
}
-
}
diff --git a/includes/actions/EditAction.php b/includes/actions/EditAction.php
index 3dd4c483..88767244 100644
--- a/includes/actions/EditAction.php
+++ b/includes/actions/EditAction.php
@@ -1,6 +1,6 @@
<?php
/**
- * action=edit / action=submit handler
+ * action=edit handler
*
* Copyright © 2012 Timo Tijhof
*
@@ -26,8 +26,7 @@
/**
* Page edition handler
*
- * This is a wrapper that will call the EditPage class, or ExternalEdit
- * if $wgUseExternalEditor is set to true and requested by the user.
+ * This is a wrapper that will call the EditPage class or a custom editor from an extension.
*
* @ingroup Actions
*/
@@ -42,6 +41,13 @@ class EditAction extends FormlessAction {
}
public function show() {
+ if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $out = $this->getOutput();
+ $out->addModuleStyles( array(
+ 'mediawiki.ui.input',
+ 'mediawiki.ui.checkbox',
+ ) );
+ }
$page = $this->page;
$user = $this->getUser();
@@ -49,31 +55,5 @@ class EditAction extends FormlessAction {
$editor = new EditPage( $page );
$editor->edit();
}
-
}
-
-}
-
-/**
- * Edit submission handler
- *
- * This is the same as EditAction; except that it sets the session cookie.
- *
- * @ingroup Actions
- */
-class SubmitAction extends EditAction {
-
- public function getName() {
- return 'submit';
- }
-
- public function show() {
- if ( session_id() == '' ) {
- // Send a cookie so anons get talk message notifications
- wfSetupSession();
- }
-
- parent::show();
- }
-
}
diff --git a/includes/actions/FormAction.php b/includes/actions/FormAction.php
new file mode 100644
index 00000000..4c9e85dd
--- /dev/null
+++ b/includes/actions/FormAction.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Base classes for actions done on pages.
+ *
+ * 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
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+/**
+ * An action which shows a form and does something based on the input from the form
+ *
+ * @ingroup Actions
+ */
+abstract class FormAction extends Action {
+
+ /**
+ * Get an HTMLForm descriptor array
+ * @return array
+ */
+ abstract protected function getFormFields();
+
+ /**
+ * Add pre- or post-text to the form
+ * @return string HTML which will be sent to $form->addPreText()
+ */
+ protected function preText() {
+ return '';
+ }
+
+ /**
+ * @return string
+ */
+ protected function postText() {
+ return '';
+ }
+
+ /**
+ * Play with the HTMLForm if you need to more substantially
+ * @param HTMLForm $form
+ */
+ protected function alterForm( HTMLForm $form ) {
+ }
+
+ /**
+ * Get the HTMLForm to control behavior
+ * @return HTMLForm|null
+ */
+ protected function getForm() {
+ $this->fields = $this->getFormFields();
+
+ // Give hooks a chance to alter the form, adding extra fields or text etc
+ wfRunHooks( 'ActionModifyFormFields', array( $this->getName(), &$this->fields, $this->page ) );
+
+ $form = new HTMLForm( $this->fields, $this->getContext(), $this->getName() );
+ $form->setSubmitCallback( array( $this, 'onSubmit' ) );
+
+ // Retain query parameters (uselang etc)
+ $form->addHiddenField( 'action', $this->getName() ); // Might not be the same as the query string
+ $params = array_diff_key(
+ $this->getRequest()->getQueryValues(),
+ array( 'action' => null, 'title' => null )
+ );
+ $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
+
+ $form->addPreText( $this->preText() );
+ $form->addPostText( $this->postText() );
+ $this->alterForm( $form );
+
+ // Give hooks a chance to alter the form, adding extra fields or text etc
+ wfRunHooks( 'ActionBeforeFormDisplay', array( $this->getName(), &$form, $this->page ) );
+
+ return $form;
+ }
+
+ /**
+ * Process the form on POST submission. If you return false from getFormFields(),
+ * this will obviously never be reached. If you don't want to do anything with the
+ * form, just return false here
+ * @param array $data
+ * @return bool|array True for success, false for didn't-try, array of errors on failure
+ */
+ abstract public function onSubmit( $data );
+
+ /**
+ * Do something exciting on successful processing of the form. This might be to show
+ * a confirmation message (watch, rollback, etc) or to redirect somewhere else (edit,
+ * protect, etc).
+ */
+ abstract public function onSuccess();
+
+ /**
+ * The basic pattern for actions is to display some sort of HTMLForm UI, maybe with
+ * some stuff underneath (history etc); to do some processing on submission of that
+ * form (delete, protect, etc) and to do something exciting on 'success', be that
+ * display something new or redirect to somewhere. Some actions have more exotic
+ * behavior, but that's what subclassing is for :D
+ */
+ public function show() {
+ $this->setHeaders();
+
+ // This will throw exceptions if there's a problem
+ $this->checkCanExecute( $this->getUser() );
+
+ $form = $this->getForm();
+ if ( $form->show() ) {
+ $this->onSuccess();
+ }
+ }
+}
diff --git a/includes/actions/FormlessAction.php b/includes/actions/FormlessAction.php
new file mode 100644
index 00000000..a6f1e295
--- /dev/null
+++ b/includes/actions/FormlessAction.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Base classes for actions done on pages.
+ *
+ * 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
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+/**
+ * An action which just does something, without showing a form first.
+ *
+ * @ingroup Actions
+ */
+abstract class FormlessAction extends Action {
+
+ /**
+ * Show something on GET request.
+ * @return string|null Will be added to the HTMLForm if present, or just added to the
+ * output if not. Return null to not add anything
+ */
+ abstract public function onView();
+
+ public function show() {
+ $this->setHeaders();
+
+ // This will throw exceptions if there's a problem
+ $this->checkCanExecute( $this->getUser() );
+
+ $this->getOutput()->addHTML( $this->onView() );
+ }
+}
diff --git a/includes/actions/HistoryAction.php b/includes/actions/HistoryAction.php
index e58791ea..8522e560 100644
--- a/includes/actions/HistoryAction.php
+++ b/includes/actions/HistoryAction.php
@@ -37,6 +37,9 @@ class HistoryAction extends FormlessAction {
const DIR_PREV = 0;
const DIR_NEXT = 1;
+ /** @var array Array of message keys and strings */
+ public $message;
+
public function getName() {
return 'history';
}
@@ -89,8 +92,6 @@ class HistoryAction extends FormlessAction {
* Print the history page for an article.
*/
function onView() {
- global $wgScript, $wgUseFileCache;
-
$out = $this->getOutput();
$request = $this->getRequest();
@@ -104,10 +105,12 @@ class HistoryAction extends FormlessAction {
wfProfileIn( __METHOD__ );
$this->preCacheMessages();
+ $config = $this->context->getConfig();
# Fill in the file cache if not set already
- if ( $wgUseFileCache && HTMLFileCache::useFileCache( $this->getContext() ) ) {
- $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'history' );
+ $useFileCache = $config->get( 'UseFileCache' );
+ if ( $useFileCache && HTMLFileCache::useFileCache( $this->getContext() ) ) {
+ $cache = new HTMLFileCache( $this->getTitle(), 'history' );
if ( !$cache->isCacheGood( /* Assume up to date */ ) ) {
ob_start( array( &$cache, 'saveToFileCache' ) );
}
@@ -116,12 +119,20 @@ class HistoryAction extends FormlessAction {
// Setup page variables.
$out->setFeedAppendQuery( 'action=history' );
$out->addModules( 'mediawiki.action.history' );
+ if ( $config->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $out = $this->getOutput();
+ $out->addModuleStyles( array(
+ 'mediawiki.ui.input',
+ 'mediawiki.ui.checkbox',
+ ) );
+ }
// Handle atom/RSS feeds.
$feedType = $request->getVal( 'feed' );
if ( $feedType ) {
$this->feed( $feedType );
wfProfileOut( __METHOD__ );
+
return;
}
@@ -141,6 +152,7 @@ class HistoryAction extends FormlessAction {
)
);
wfProfileOut( __METHOD__ );
+
return;
}
@@ -162,13 +174,13 @@ class HistoryAction extends FormlessAction {
}
if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
$checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(),
- 'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n";
+ 'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n";
} else {
$checkDeleted = '';
}
// Add the general form
- $action = htmlspecialchars( $wgScript );
+ $action = htmlspecialchars( wfScript() );
$out->addHTML(
"<form action=\"$action\" method=\"get\" id=\"mw-history-searchform\">" .
Xml::fieldset(
@@ -178,7 +190,10 @@ class HistoryAction extends FormlessAction {
) .
Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
Html::hidden( 'action', 'history' ) . "\n" .
- Xml::dateMenu( ( $year == null ? MWTimestamp::getLocalInstance()->format( 'Y' ) : $year ), $month ) . '&#160;' .
+ Xml::dateMenu(
+ ( $year == null ? MWTimestamp::getLocalInstance()->format( 'Y' ) : $year ),
+ $month
+ ) . '&#160;' .
( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
$checkDeleted .
Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n" .
@@ -204,9 +219,9 @@ class HistoryAction extends FormlessAction {
* direction. This is now only used by the feeds. It was previously
* used by the main UI but that's now handled by the pager.
*
- * @param $limit Integer: the limit number of revisions to get
- * @param $offset Integer
- * @param $direction Integer: either HistoryPage::DIR_PREV or HistoryPage::DIR_NEXT
+ * @param int $limit The limit number of revisions to get
+ * @param int $offset
+ * @param int $direction Either self::DIR_PREV or self::DIR_NEXT
* @return ResultWrapper
*/
function fetchRevisions( $limit, $offset, $direction ) {
@@ -217,9 +232,9 @@ class HistoryAction extends FormlessAction {
$dbr = wfGetDB( DB_SLAVE );
- if ( $direction == HistoryPage::DIR_PREV ) {
+ if ( $direction === self::DIR_PREV ) {
list( $dirs, $oper ) = array( "ASC", ">=" );
- } else { /* $direction == HistoryPage::DIR_NEXT */
+ } else { /* $direction === self::DIR_NEXT */
list( $dirs, $oper ) = array( "DESC", "<=" );
}
@@ -243,16 +258,17 @@ class HistoryAction extends FormlessAction {
/**
* Output a subscription feed listing recent edits to this page.
*
- * @param string $type feed type
+ * @param string $type Feed type
*/
function feed( $type ) {
- global $wgFeedClasses, $wgFeedLimit;
if ( !FeedUtils::checkFeedOutput( $type ) ) {
return;
}
$request = $this->getRequest();
- $feed = new $wgFeedClasses[$type](
+ $feedClasses = $this->context->getConfig()->get( 'FeedClasses' );
+ /** @var RSSFeed|AtomFeed $feed */
+ $feed = new $feedClasses[$type](
$this->getTitle()->getPrefixedText() . ' - ' .
$this->msg( 'history-feed-title' )->inContentLanguage()->text(),
$this->msg( 'history-feed-description' )->inContentLanguage()->text(),
@@ -262,9 +278,12 @@ class HistoryAction extends FormlessAction {
// Get a limit on number of feed entries. Provide a sane default
// of 10 if none is defined (but limit to $wgFeedLimit max)
$limit = $request->getInt( 'limit', 10 );
- $limit = min( max( $limit, 1 ), $wgFeedLimit );
+ $limit = min(
+ max( $limit, 1 ),
+ $this->context->getConfig()->get( 'FeedLimit' )
+ );
- $items = $this->fetchRevisions( $limit, 0, HistoryPage::DIR_NEXT );
+ $items = $this->fetchRevisions( $limit, 0, self::DIR_NEXT );
// Generate feed elements enclosed between header and footer.
$feed->outHeader();
@@ -294,7 +313,7 @@ class HistoryAction extends FormlessAction {
* Borrows Recent Changes' feed generation functions for formatting;
* includes a diff to the previous revision (if any).
*
- * @param $row Object: database row
+ * @param stdClass|array $row Database row
* @return FeedItem
*/
function feedItem( $row ) {
@@ -316,9 +335,10 @@ class HistoryAction extends FormlessAction {
$wgContLang->time( $rev->getTimestamp() ) )->inContentLanguage()->text();
} else {
$title = $rev->getUserText() .
- $this->msg( 'colon-separator' )->inContentLanguage()->text() .
- FeedItem::stripComment( $rev->getComment() );
+ $this->msg( 'colon-separator' )->inContentLanguage()->text() .
+ FeedItem::stripComment( $rev->getComment() );
}
+
return new FeedItem(
$title,
$text,
@@ -335,14 +355,28 @@ class HistoryAction extends FormlessAction {
* @ingroup Actions
*/
class HistoryPager extends ReverseChronologicalPager {
- public $lastRow = false, $counter, $historyPage, $buttons, $conds;
+ /**
+ * @var bool|stdClass
+ */
+ public $lastRow = false;
+
+ public $counter, $historyPage, $buttons, $conds;
+
protected $oldIdChecked;
+
protected $preventClickjacking = false;
/**
* @var array
*/
protected $parentLens;
+ /**
+ * @param HistoryAction $historyPage
+ * @param string $year
+ * @param string $month
+ * @param string $tagFilter
+ * @param array $conds
+ */
function __construct( $historyPage, $year = '', $month = '', $tagFilter = '', $conds = array() ) {
parent::__construct( $historyPage->getContext() );
$this->historyPage = $historyPage;
@@ -383,6 +417,7 @@ class HistoryPager extends ReverseChronologicalPager {
$this->tagFilter
);
wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
+
return $queryInfo;
}
@@ -390,6 +425,10 @@ class HistoryPager extends ReverseChronologicalPager {
return 'rev_timestamp';
}
+ /**
+ * @param stdClass $row
+ * @return string
+ */
function formatRow( $row ) {
if ( $this->lastRow ) {
$latest = ( $this->counter == 1 && $this->mIsFirst );
@@ -401,6 +440,7 @@ class HistoryPager extends ReverseChronologicalPager {
$s = '';
}
$this->lastRow = $row;
+
return $s;
}
@@ -432,21 +472,24 @@ class HistoryPager extends ReverseChronologicalPager {
* @return string HTML output
*/
function getStartBody() {
- global $wgScript;
$this->lastRow = false;
$this->counter = 1;
$this->oldIdChecked = 0;
$this->getOutput()->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
- $s = Html::openElement( 'form', array( 'action' => $wgScript,
+ $s = Html::openElement( 'form', array( 'action' => wfScript(),
'id' => 'mw-history-compare' ) ) . "\n";
$s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n";
$s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
// Button container stored in $this->buttons for re-use in getEndBody()
$this->buttons = '<div>';
+ $className = 'historysubmit mw-history-compareselectedversions-button';
+ if ( $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $className .= ' mw-ui-button mw-ui-constructive';
+ }
$this->buttons .= $this->submitButton( $this->msg( 'compareselectedversions' )->text(),
- array( 'class' => 'historysubmit mw-history-compareselectedversions-button' )
+ array( 'class' => $className )
+ Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' )
) . "\n";
@@ -457,13 +500,15 @@ class HistoryPager extends ReverseChronologicalPager {
$s .= $this->buttons;
$s .= '<ul id="pagehistory">' . "\n";
+
return $s;
}
private function getRevisionButton( $name, $msg ) {
$this->preventClickjacking();
# Note bug #20966, <button> is non-standard in IE<8
- $element = Html::element( 'button',
+ $element = Html::element(
+ 'button',
array(
'type' => 'submit',
'name' => $name,
@@ -502,15 +547,16 @@ class HistoryPager extends ReverseChronologicalPager {
$s .= $this->buttons;
}
$s .= '</form>';
+
return $s;
}
/**
* Creates a submit button
*
- * @param string $message text of the submit button, will be escaped
- * @param array $attributes attributes
- * @return String: HTML output for the submit button
+ * @param string $message Text of the submit button, will be escaped
+ * @param array $attributes Attributes
+ * @return string HTML output for the submit button
*/
function submitButton( $message, $attributes = array() ) {
# Disable submit button if history has 1 revision only
@@ -526,16 +572,17 @@ class HistoryPager extends ReverseChronologicalPager {
*
* @todo document some more, and maybe clean up the code (some params redundant?)
*
- * @param $row Object: the database row corresponding to the previous line.
- * @param $next Mixed: the database row corresponding to the next line. (chronologically previous)
- * @param $notificationtimestamp
- * @param $latest Boolean: whether this row corresponds to the page's latest revision.
- * @param $firstInList Boolean: whether this row corresponds to the first displayed on this history page.
- * @return String: HTML output for the row
+ * @param stdClass $row The database row corresponding to the previous line.
+ * @param mixed $next The database row corresponding to the next line
+ * (chronologically previous)
+ * @param bool|string $notificationtimestamp
+ * @param bool $latest Whether this row corresponds to the page's latest revision.
+ * @param bool $firstInList Whether this row corresponds to the first
+ * displayed on this history page.
+ * @return string HTML output for the row
*/
function historyLine( $row, $next, $notificationtimestamp = false,
- $latest = false, $firstInList = false )
- {
+ $latest = false, $firstInList = false ) {
$rev = new Revision( $row );
$rev->setTitle( $this->getTitle() );
@@ -548,12 +595,14 @@ class HistoryPager extends ReverseChronologicalPager {
$curlink = $this->curLink( $rev, $latest );
$lastlink = $this->lastLink( $rev, $next );
- $diffButtons = $this->diffButtons( $rev, $firstInList );
+ $curLastlinks = $curlink . $this->historyPage->message['pipe-separator'] . $lastlink;
$histLinks = Html::rawElement(
- 'span',
- array( 'class' => 'mw-history-histlinks' ),
- $this->msg( 'parentheses' )->rawParams( $curlink . $this->historyPage->message['pipe-separator'] . $lastlink )->escaped()
+ 'span',
+ array( 'class' => 'mw-history-histlinks' ),
+ $this->msg( 'parentheses' )->rawParams( $curLastlinks )->escaped()
);
+
+ $diffButtons = $this->diffButtons( $rev, $firstInList );
$s = $histLinks . $diffButtons;
$link = $this->revLink( $rev );
@@ -627,7 +676,11 @@ class HistoryPager extends ReverseChronologicalPager {
if ( $prevRev && $this->getTitle()->quickUserCan( 'edit', $user ) ) {
if ( $latest && $this->getTitle()->quickUserCan( 'rollback', $user ) ) {
// Get a rollback link without the brackets
- $rollbackLink = Linker::generateRollback( $rev, $this->getContext(), array( 'verify', 'noBrackets' ) );
+ $rollbackLink = Linker::generateRollback(
+ $rev,
+ $this->getContext(),
+ array( 'verify', 'noBrackets' )
+ );
if ( $rollbackLink ) {
$this->preventClickjacking();
$tools[] = $rollbackLink;
@@ -635,8 +688,8 @@ class HistoryPager extends ReverseChronologicalPager {
}
if ( !$rev->isDeleted( Revision::DELETED_TEXT )
- && !$prevRev->isDeleted( Revision::DELETED_TEXT ) )
- {
+ && !$prevRev->isDeleted( Revision::DELETED_TEXT )
+ ) {
# Create undo tooltip for the first (=latest) line only
$undoTooltip = $latest
? array( 'title' => $this->msg( 'tooltip-undo' )->text() )
@@ -686,8 +739,8 @@ class HistoryPager extends ReverseChronologicalPager {
/**
* Create a link to view this revision of the page
*
- * @param $rev Revision
- * @return String
+ * @param Revision $rev
+ * @return string
*/
function revLink( $rev ) {
$date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $this->getUser() );
@@ -705,15 +758,16 @@ class HistoryPager extends ReverseChronologicalPager {
if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$link = "<span class=\"history-deleted\">$link</span>";
}
+
return $link;
}
/**
* Create a diff-to-current link for this revision for this page
*
- * @param $rev Revision
- * @param $latest Boolean: this is the latest revision of the page?
- * @return String
+ * @param Revision $rev
+ * @param bool $latest This is the latest revision of the page?
+ * @return string
*/
function curLink( $rev, $latest ) {
$cur = $this->historyPage->message['cur'];
@@ -735,18 +789,21 @@ class HistoryPager extends ReverseChronologicalPager {
/**
* Create a diff-to-previous link for this revision for this page.
*
- * @param $prevRev Revision: the previous revision
- * @param $next Mixed: the newer revision
- * @return String
+ * @param Revision $prevRev The revision being displayed
+ * @param stdClass|string|null $next The next revision in list (that is
+ * the previous one in chronological order).
+ * May either be a row, "unknown" or null.
+ * @return string
*/
function lastLink( $prevRev, $next ) {
$last = $this->historyPage->message['last'];
- # $next may either be a Row, null, or "unkown"
- $nextRev = is_object( $next ) ? new Revision( $next ) : $next;
- if ( is_null( $next ) ) {
+
+ if ( $next === null ) {
# Probably no next row
return $last;
- } elseif ( $next === 'unknown' ) {
+ }
+
+ if ( $next === 'unknown' ) {
# Next row probably exists but is unknown, use an oldid=prev link
return Linker::linkKnown(
$this->getTitle(),
@@ -757,30 +814,34 @@ class HistoryPager extends ReverseChronologicalPager {
'oldid' => 'prev'
)
);
- } elseif ( !$prevRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
- || !$nextRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) )
- {
+ }
+
+ $nextRev = new Revision( $next );
+
+ if ( !$prevRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ || !$nextRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ ) {
return $last;
- } else {
- return Linker::linkKnown(
- $this->getTitle(),
- $last,
- array(),
- array(
- 'diff' => $prevRev->getId(),
- 'oldid' => $next->rev_id
- )
- );
}
+
+ return Linker::linkKnown(
+ $this->getTitle(),
+ $last,
+ array(),
+ array(
+ 'diff' => $prevRev->getId(),
+ 'oldid' => $next->rev_id
+ )
+ );
}
/**
* Create radio buttons for page history
*
- * @param $rev Revision object
- * @param $firstInList Boolean: is this version the first one?
+ * @param Revision $rev
+ * @param bool $firstInList Is this version the first one?
*
- * @return String: HTML output for the radio buttons
+ * @return string HTML output for the radio buttons
*/
function diffButtons( $rev, $firstInList ) {
if ( $this->getNumRows() > 1 ) {
@@ -816,6 +877,7 @@ class HistoryPager extends ReverseChronologicalPager {
array_merge( $radio, $checkmark, array(
'name' => 'diff',
'id' => "mw-diff-$id" ) ) );
+
return $first . $second;
} else {
return '';
@@ -824,6 +886,7 @@ class HistoryPager extends ReverseChronologicalPager {
/**
* This is called if a write operation is possible from the generated HTML
+ * @param bool $enable
*/
function preventClickjacking( $enable = true ) {
$this->preventClickjacking = $enable;
@@ -837,16 +900,3 @@ class HistoryPager extends ReverseChronologicalPager {
return $this->preventClickjacking;
}
}
-
-/**
- * Backwards-compatibility alias
- */
-class HistoryPage extends HistoryAction {
- public function __construct( Page $article ) { # Just to make it public
- parent::__construct( $article );
- }
-
- public function history() {
- $this->onView();
- }
-}
diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php
index 0a16f4a8..f932a405 100644
--- a/includes/actions/InfoAction.php
+++ b/includes/actions/InfoAction.php
@@ -33,7 +33,7 @@ class InfoAction extends FormlessAction {
/**
* Returns the name of the action this object responds to.
*
- * @return string lowercase
+ * @return string Lowercase name
*/
public function getName() {
return 'info';
@@ -153,6 +153,7 @@ class InfoAction extends FormlessAction {
*/
protected function makeHeader( $header ) {
$spanAttribs = array( 'class' => 'mw-headline', 'id' => Sanitizer::escapeId( $header ) );
+
return Html::rawElement( 'h2', array(), Html::element( 'span', $spanAttribs, $header ) );
}
@@ -192,13 +193,13 @@ class InfoAction extends FormlessAction {
* @return array
*/
protected function pageInfo() {
- global $wgContLang, $wgRCMaxAge, $wgMemc,
- $wgUnwatchedPageThreshold, $wgPageInfoTransclusionLimit;
+ global $wgContLang, $wgMemc;
$user = $this->getUser();
$lang = $this->getLanguage();
$title = $this->getTitle();
$id = $title->getArticleID();
+ $config = $this->context->getConfig();
$memcKey = wfMemcKey( 'infoaction',
sha1( $title->getPrefixedText() ), $this->page->getLatest() );
@@ -206,7 +207,7 @@ class InfoAction extends FormlessAction {
$version = isset( $pageCounts['cacheversion'] ) ? $pageCounts['cacheversion'] : false;
if ( $pageCounts === false || $version !== self::CACHE_VERSION ) {
// Get page information that would be too "expensive" to retrieve by normal means
- $pageCounts = self::pageCounts( $title );
+ $pageCounts = $this->pageCounts( $title );
$pageCounts['cacheversion'] = self::CACHE_VERSION;
$wgMemc->set( $memcKey, $pageCounts );
@@ -274,15 +275,37 @@ class InfoAction extends FormlessAction {
// Language in which the page content is (supposed to be) written
$pageLang = $title->getPageLanguage()->getCode();
- $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-language' ),
+
+ if ( $config->get( 'PageLanguageUseDB' ) && $this->getTitle()->userCan( 'pagelang' ) ) {
+ // Link to Special:PageLanguage with pre-filled page title if user has permissions
+ $titleObj = SpecialPage::getTitleFor( 'PageLanguage', $title->getPrefixedText() );
+ $langDisp = Linker::link(
+ $titleObj,
+ $this->msg( 'pageinfo-language' )->escaped()
+ );
+ } else {
+ // Display just the message
+ $langDisp = $this->msg( 'pageinfo-language' )->escaped();
+ }
+
+ $pageInfo['header-basic'][] = array( $langDisp,
Language::fetchLanguageName( $pageLang, $lang->getCode() )
. ' ' . $this->msg( 'parentheses', $pageLang ) );
+ // Content model of the page
+ $pageInfo['header-basic'][] = array(
+ $this->msg( 'pageinfo-content-model' ),
+ ContentHandler::getLocalizedName( $title->getContentModel() )
+ );
+
// Search engine status
$pOutput = new ParserOutput();
if ( isset( $pageProperties['noindex'] ) ) {
$pOutput->setIndexPolicy( 'noindex' );
}
+ if ( isset( $pageProperties['index'] ) ) {
+ $pOutput->setIndexPolicy( 'index' );
+ }
// Use robot policy logic
$policy = $this->page->getRobotPolicy( 'view', $pOutput );
@@ -298,19 +321,20 @@ class InfoAction extends FormlessAction {
);
}
+ $unwatchedPageThreshold = $config->get( 'UnwatchedPageThreshold' );
if (
$user->isAllowed( 'unwatchedpages' ) ||
- ( $wgUnwatchedPageThreshold !== false &&
- $pageCounts['watchers'] >= $wgUnwatchedPageThreshold )
+ ( $unwatchedPageThreshold !== false &&
+ $pageCounts['watchers'] >= $unwatchedPageThreshold )
) {
// Number of page watchers
$pageInfo['header-basic'][] = array(
$this->msg( 'pageinfo-watchers' ), $lang->formatNum( $pageCounts['watchers'] )
);
- } elseif ( $wgUnwatchedPageThreshold !== false ) {
+ } elseif ( $unwatchedPageThreshold !== false ) {
$pageInfo['header-basic'][] = array(
$this->msg( 'pageinfo-watchers' ),
- $this->msg( 'pageinfo-few-watchers' )->numParams( $wgUnwatchedPageThreshold )
+ $this->msg( 'pageinfo-few-watchers' )->numParams( $unwatchedPageThreshold )
);
}
@@ -498,7 +522,7 @@ class InfoAction extends FormlessAction {
// Recent number of edits (within past 30 days)
$pageInfo['header-edits'][] = array(
- $this->msg( 'pageinfo-recent-edits', $lang->formatDuration( $wgRCMaxAge ) ),
+ $this->msg( 'pageinfo-recent-edits', $lang->formatDuration( $config->get( 'RCMaxAge' ) ) ),
$lang->formatNum( $pageCounts['recent_edits'] )
);
@@ -532,9 +556,13 @@ class InfoAction extends FormlessAction {
$pageCounts['transclusion']['from'] > 0 ||
$pageCounts['transclusion']['to'] > 0
) {
- $options = array( 'LIMIT' => $wgPageInfoTransclusionLimit );
+ $options = array( 'LIMIT' => $config->get( 'PageInfoTransclusionLimit' ) );
$transcludedTemplates = $title->getTemplateLinksFrom( $options );
- $transcludedTargets = $title->getTemplateLinksTo( $options );
+ if ( $config->get( 'MiserMode' ) ) {
+ $transcludedTargets = array();
+ } else {
+ $transcludedTargets = $title->getTemplateLinksTo( $options );
+ }
// Page properties
$pageInfo['header-properties'] = array();
@@ -575,7 +603,7 @@ class InfoAction extends FormlessAction {
);
}
- if ( $pageCounts['transclusion']['to'] > 0 ) {
+ if ( !$config->get( 'MiserMode' ) && $pageCounts['transclusion']['to'] > 0 ) {
if ( $pageCounts['transclusion']['to'] > count( $transcludedTargets ) ) {
$more = Linker::link(
$whatLinksHere,
@@ -608,16 +636,15 @@ class InfoAction extends FormlessAction {
* @param Title $title Title to get counts for
* @return array
*/
- protected static function pageCounts( Title $title ) {
- global $wgRCMaxAge, $wgDisableCounters;
-
+ protected function pageCounts( Title $title ) {
wfProfileIn( __METHOD__ );
$id = $title->getArticleID();
+ $config = $this->context->getConfig();
$dbr = wfGetDB( DB_SLAVE );
$result = array();
- if ( !$wgDisableCounters ) {
+ if ( !$config->get( 'DisableCounters' ) ) {
// Number of views
$views = (int)$dbr->selectField(
'page',
@@ -658,8 +685,8 @@ class InfoAction extends FormlessAction {
);
$result['authors'] = $authors;
- // "Recent" threshold defined by $wgRCMaxAge
- $threshold = $dbr->timestamp( time() - $wgRCMaxAge );
+ // "Recent" threshold defined by RCMaxAge setting
+ $threshold = $dbr->timestamp( time() - $config->get( 'RCMaxAge' ) );
// Recent number of edits
$edits = (int)$dbr->selectField(
@@ -713,15 +740,19 @@ class InfoAction extends FormlessAction {
}
// Counts for the number of transclusion links (to/from)
- $result['transclusion']['to'] = (int)$dbr->selectField(
- 'templatelinks',
- 'COUNT(tl_from)',
- array(
- 'tl_namespace' => $title->getNamespace(),
- 'tl_title' => $title->getDBkey()
- ),
- __METHOD__
- );
+ if ( $config->get( 'MiserMode' ) ) {
+ $result['transclusion']['to'] = 0;
+ } else {
+ $result['transclusion']['to'] = (int)$dbr->selectField(
+ 'templatelinks',
+ 'COUNT(tl_from)',
+ array(
+ 'tl_namespace' => $title->getNamespace(),
+ 'tl_title' => $title->getDBkey()
+ ),
+ __METHOD__
+ );
+ }
$result['transclusion']['from'] = (int)$dbr->selectField(
'templatelinks',
@@ -731,6 +762,7 @@ class InfoAction extends FormlessAction {
);
wfProfileOut( __METHOD__ );
+
return $result;
}
@@ -745,25 +777,25 @@ class InfoAction extends FormlessAction {
/**
* Get a list of contributors of $article
- * @return string: html
+ * @return string Html
*/
protected function getContributors() {
- global $wgHiddenPrefs;
-
$contributors = $this->page->getContributors();
$real_names = array();
$user_names = array();
$anon_ips = array();
# Sift for real versus user names
+ /** @var $user User */
foreach ( $contributors as $user ) {
$page = $user->isAnon()
? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
: $user->getUserPage();
+ $hiddenPrefs = $this->context->getConfig()->get( 'HiddenPrefs' );
if ( $user->getID() == 0 ) {
$anon_ips[] = Linker::link( $page, htmlspecialchars( $user->getName() ) );
- } elseif ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
+ } elseif ( !in_array( 'realname', $hiddenPrefs ) && $user->getRealName() ) {
$real_names[] = Linker::link( $page, htmlspecialchars( $user->getRealName() ) );
} else {
$user_names[] = Linker::link( $page, htmlspecialchars( $user->getName() ) );
@@ -798,6 +830,7 @@ class InfoAction extends FormlessAction {
}
$count = count( $fulllist );
+
# "Based on work by ..."
return $count
? $this->msg( 'othercontribs' )->rawParams(
diff --git a/includes/actions/MarkpatrolledAction.php b/includes/actions/MarkpatrolledAction.php
index ff6cf13a..4016f672 100644
--- a/includes/actions/MarkpatrolledAction.php
+++ b/includes/actions/MarkpatrolledAction.php
@@ -70,6 +70,7 @@ class MarkpatrolledAction extends FormlessAction {
$this->getOutput()->setPageTitle( $this->msg( 'markedaspatrollederror' ) );
$this->getOutput()->addWikiMsg( 'markedaspatrollederror-noautopatrol' );
$this->getOutput()->returnToMain( null, $return );
+
return;
}
diff --git a/includes/actions/ProtectAction.php b/includes/actions/ProtectAction.php
index ec6648e2..a7f1ac34 100644
--- a/includes/actions/ProtectAction.php
+++ b/includes/actions/ProtectAction.php
@@ -41,30 +41,15 @@ class ProtectAction extends FormlessAction {
}
public function show() {
+ if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $out = $this->getOutput();
+ $out->addModuleStyles( array(
+ 'mediawiki.ui.input',
+ 'mediawiki.ui.checkbox',
+ ) );
+ }
$this->page->protect();
-
}
-
}
-/**
- * Handle page unprotection
- *
- * This is a wrapper that will call Article::unprotect().
- *
- * @ingroup Actions
- */
-class UnprotectAction extends ProtectAction {
-
- public function getName() {
- return 'unprotect';
- }
-
- public function show() {
-
- $this->page->unprotect();
-
- }
-
-}
diff --git a/includes/actions/RawAction.php b/includes/actions/RawAction.php
index 1a2b3cb1..d0d956ec 100644
--- a/includes/actions/RawAction.php
+++ b/includes/actions/RawAction.php
@@ -5,7 +5,7 @@
* Copyright © 2004 Gabriel Wicke <wicke@wikidev.net>
* http://wikidev.net/
*
- * Based on HistoryPage and SpecialExport
+ * Based on HistoryAction and SpecialExport
*
* 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
@@ -33,7 +33,13 @@
* @ingroup Actions
*/
class RawAction extends FormlessAction {
- private $mGen;
+ /**
+ * @var bool Does the request include a gen=css|javascript parameter
+ * @deprecated This used to be a string for "css" or "javascript" but
+ * it is no longer used. Setting this parameter results in empty content
+ * being served
+ */
+ private $gen = false;
public function getName() {
return 'raw';
@@ -48,10 +54,9 @@ class RawAction extends FormlessAction {
}
function onView() {
- global $wgSquidMaxage, $wgForcedRawSMaxage;
-
$this->getOutput()->disable();
$request = $this->getRequest();
+ $config = $this->context->getConfig();
if ( !$request->checkUrlExtension() ) {
return;
@@ -67,26 +72,25 @@ class RawAction extends FormlessAction {
$smaxage = $request->getIntOrNull( 'smaxage' );
if ( $gen == 'css' || $gen == 'js' ) {
- $this->mGen = $gen;
+ $this->gen = true;
if ( $smaxage === null ) {
- $smaxage = $wgSquidMaxage;
+ $smaxage = $config->get( 'SquidMaxage' );
}
- } else {
- $this->mGen = false;
}
$contentType = $this->getContentType();
- # Force caching for CSS and JS raw content, default: 5 minutes
+ # Force caching for CSS and JS raw content, default: 5 minutes.
+ # Note: If using a canonical url for userpage css/js, we send an HTCP purge.
if ( $smaxage === null ) {
if ( $contentType == 'text/css' || $contentType == 'text/javascript' ) {
- $smaxage = intval( $wgForcedRawSMaxage );
+ $smaxage = intval( $config->get( 'ForcedRawSMaxage' ) );
} else {
$smaxage = 0;
}
}
- $maxage = $request->getInt( 'maxage', $wgSquidMaxage );
+ $maxage = $request->getInt( 'maxage', $config->get( 'SquidMaxage' ) );
$response = $request->response();
@@ -99,7 +103,9 @@ class RawAction extends FormlessAction {
$privateCache = $privateCache ?: $this->getUser()->isLoggedIn();
# allow the client to cache this for 24 hours
$mode = $privateCache ? 'private' : 'public';
- $response->header( 'Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage );
+ $response->header(
+ 'Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage
+ );
$text = $this->getRawText();
@@ -122,13 +128,13 @@ class RawAction extends FormlessAction {
* Get the text that should be returned, or false if the page or revision
* was not found.
*
- * @return String|Bool
+ * @return string|bool
*/
public function getRawText() {
global $wgParser;
# No longer used
- if ( $this->mGen ) {
+ if ( $this->gen ) {
return '';
}
@@ -138,8 +144,9 @@ class RawAction extends FormlessAction {
// If it's a MediaWiki message we can just hit the message cache
if ( $request->getBool( 'usemsgcache' ) && $title->getNamespace() == NS_MEDIAWIKI ) {
- // The first "true" is to use the database, the second is to use the content langue
- // and the last one is to specify the message key already contains the language in it ("/de", etc.)
+ // The first "true" is to use the database, the second is to use
+ // the content langue and the last one is to specify the message
+ // key already contains the language in it ("/de", etc.).
$text = MessageCache::singleton()->get( $title->getDBkey(), true, true, true );
// If the message doesn't exist, return a blank
if ( $text === false ) {
@@ -161,7 +168,7 @@ class RawAction extends FormlessAction {
} elseif ( !$content instanceof TextContent ) {
// non-text content
wfHttpError( 415, "Unsupported Media Type", "The requested page uses the content model `"
- . $content->getModel() . "` which is not supported via this interface." );
+ . $content->getModel() . "` which is not supported via this interface." );
die();
} else {
// want a section?
@@ -181,7 +188,11 @@ class RawAction extends FormlessAction {
}
if ( $text !== false && $text !== '' && $request->getVal( 'templates' ) === 'expand' ) {
- $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ $text = $wgParser->preprocess(
+ $text,
+ $title,
+ ParserOptions::newFromContext( $this->getContext() )
+ );
}
return $text;
@@ -190,17 +201,18 @@ class RawAction extends FormlessAction {
/**
* Get the ID of the revision that should used to get the text.
*
- * @return Integer
+ * @return int
*/
public function getOldId() {
$oldid = $this->getRequest()->getInt( 'oldid' );
switch ( $this->getRequest()->getText( 'direction' ) ) {
case 'next':
# output next revision, or nothing if there isn't one
+ $nextid = 0;
if ( $oldid ) {
- $oldid = $this->getTitle()->getNextRevisionID( $oldid );
+ $nextid = $this->getTitle()->getNextRevisionID( $oldid );
}
- $oldid = $oldid ? $oldid : -1;
+ $oldid = $nextid ?: -1;
break;
case 'prev':
# output previous revision, or nothing if there isn't one
@@ -208,20 +220,21 @@ class RawAction extends FormlessAction {
# get the current revision so we can get the penultimate one
$oldid = $this->page->getLatest();
}
- $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
- $oldid = $prev ? $prev : -1;
+ $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
+ $oldid = $previd ?: -1;
break;
case 'cur':
$oldid = 0;
break;
}
+
return $oldid;
}
/**
* Get the content type to use for the response
*
- * @return String
+ * @return string
*/
public function getContentType() {
$ctype = $this->getRequest()->getVal( 'ctype' );
@@ -243,39 +256,3 @@ class RawAction extends FormlessAction {
return $ctype;
}
}
-
-/**
- * Backward compatibility for extensions
- *
- * @deprecated in 1.19
- */
-class RawPage extends RawAction {
- public $mOldId;
-
- /**
- * @param Page $page
- * @param WebRequest|bool $request The WebRequest (default: false).
- */
- function __construct( Page $page, $request = false ) {
- wfDeprecated( __CLASS__, '1.19' );
- parent::__construct( $page );
-
- if ( $request !== false ) {
- $context = new DerivativeContext( $this->getContext() );
- $context->setRequest( $request );
- $this->context = $context;
- }
- }
-
- public function view() {
- $this->onView();
- }
-
- public function getOldId() {
- # Some extensions like to set $mOldId
- if ( $this->mOldId !== null ) {
- return $this->mOldId;
- }
- return parent::getOldId();
- }
-}
diff --git a/includes/actions/RenderAction.php b/includes/actions/RenderAction.php
index 3d244fb3..16e407f4 100644
--- a/includes/actions/RenderAction.php
+++ b/includes/actions/RenderAction.php
@@ -41,9 +41,6 @@ class RenderAction extends FormlessAction {
}
public function show() {
-
$this->page->render();
-
}
-
}
diff --git a/includes/actions/RevertAction.php b/includes/actions/RevertAction.php
index a5fc4e17..6481630e 100644
--- a/includes/actions/RevertAction.php
+++ b/includes/actions/RevertAction.php
@@ -24,29 +24,14 @@
*/
/**
- * Dummy class for pages not in NS_FILE
- *
- * @ingroup Actions
- */
-class RevertAction extends Action {
-
- public function getName() {
- return 'revert';
- }
-
- public function show() {
- $this->getOutput()->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
- }
-
- public function execute() {}
-}
-
-/**
- * Class for pages in NS_FILE
+ * File reversion user interface
*
* @ingroup Actions
*/
-class RevertFileAction extends FormAction {
+class RevertAction extends FormAction {
+ /**
+ * @var OldLocalFile
+ */
protected $oldFile;
public function getName() {
@@ -58,17 +43,24 @@ class RevertFileAction extends FormAction {
}
protected function checkCanExecute( User $user ) {
+ if ( $this->getTitle()->getNamespace() !== NS_FILE ) {
+ throw new ErrorPageError( $this->msg( 'nosuchaction' ), $this->msg( 'nosuchactiontext' ) );
+ }
parent::checkCanExecute( $user );
$oldimage = $this->getRequest()->getText( 'oldimage' );
if ( strlen( $oldimage ) < 16
|| strpos( $oldimage, '/' ) !== false
- || strpos( $oldimage, '\\' ) !== false )
- {
+ || strpos( $oldimage, '\\' ) !== false
+ ) {
throw new ErrorPageError( 'internalerror', 'unexpected', array( 'oldimage', $oldimage ) );
}
- $this->oldFile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->getTitle(), $oldimage );
+ $this->oldFile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName(
+ $this->getTitle(),
+ $oldimage
+ );
+
if ( !$this->oldFile->exists() ) {
throw new ErrorPageError( '', 'filerevert-badversion' );
}
@@ -78,6 +70,7 @@ class RevertFileAction extends FormAction {
$form->setWrapperLegendMsg( 'filerevert-legend' );
$form->setSubmitTextMsg( 'filerevert-submit' );
$form->addHiddenField( 'oldimage', $this->getRequest()->getText( 'oldimage' ) );
+ $form->setTokenSalt( array( 'revert', $this->getTitle()->getPrefixedDBkey() ) );
}
protected function getFormFields() {
@@ -99,8 +92,10 @@ class RevertFileAction extends FormAction {
'raw' => true,
'default' => $this->msg( 'filerevert-intro',
$this->getTitle()->getText(), $userDate, $userTime,
- wfExpandUrl( $this->page->getFile()->getArchiveUrl( $this->getRequest()->getText( 'oldimage' ) ),
- PROTO_CURRENT ) )->parseAsBlock()
+ wfExpandUrl(
+ $this->page->getFile()->getArchiveUrl( $this->getRequest()->getText( 'oldimage' ) ),
+ PROTO_CURRENT
+ ) )->parseAsBlock()
),
'comment' => array(
'type' => 'text',
@@ -112,10 +107,21 @@ class RevertFileAction extends FormAction {
}
public function onSubmit( $data ) {
- $source = $this->page->getFile()->getArchiveVirtualUrl( $this->getRequest()->getText( 'oldimage' ) );
+ $source = $this->page->getFile()->getArchiveVirtualUrl(
+ $this->getRequest()->getText( 'oldimage' )
+ );
$comment = $data['comment'];
+
// TODO: Preserve file properties from database instead of reloading from file
- return $this->page->getFile()->upload( $source, $comment, $comment, 0, false, false, $this->getUser() );
+ return $this->page->getFile()->upload(
+ $source,
+ $comment,
+ $comment,
+ 0,
+ false,
+ false,
+ $this->getUser()
+ );
}
public function onSuccess() {
@@ -139,6 +145,7 @@ class RevertFileAction extends FormAction {
protected function getDescription() {
$this->getOutput()->addBacklinkSubtitle( $this->getTitle() );
+
return '';
}
}
diff --git a/includes/actions/RevisiondeleteAction.php b/includes/actions/RevisiondeleteAction.php
index 2949fa95..b6eeb7b4 100644
--- a/includes/actions/RevisiondeleteAction.php
+++ b/includes/actions/RevisiondeleteAction.php
@@ -49,6 +49,7 @@ class RevisiondeleteAction extends FormlessAction {
public function show() {
$special = SpecialPageFactory::getPage( 'Revisiondelete' );
$special->setContext( $this->getContext() );
+ $special->getContext()->setTitle( $special->getPageTitle() );
$special->run( '' );
}
}
diff --git a/includes/actions/RollbackAction.php b/includes/actions/RollbackAction.php
index 81bad9da..76d70d70 100644
--- a/includes/actions/RollbackAction.php
+++ b/includes/actions/RollbackAction.php
@@ -39,6 +39,7 @@ class RollbackAction extends FormlessAction {
$details = null;
$request = $this->getRequest();
+ $user = $this->getUser();
$result = $this->page->doRollback(
$request->getVal( 'from' ),
@@ -53,13 +54,16 @@ class RollbackAction extends FormlessAction {
throw new ThrottledError;
}
- if ( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ) {
+ if ( isset( $result[0][0] ) &&
+ ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' )
+ ) {
$this->getOutput()->setPageTitle( $this->msg( 'rollbackfailed' ) );
$errArray = $result[0];
$errMsg = array_shift( $errArray );
$this->getOutput()->addWikiMsgArray( $errMsg, $errArray );
if ( isset( $details['current'] ) ) {
+ /** @var Revision $current */
$current = $details['current'];
if ( $current->getComment() != '' ) {
@@ -83,6 +87,7 @@ class RollbackAction extends FormlessAction {
throw new ErrorPageError( 'rollbackfailed', $error[0], array_slice( $error, 1 ) );
}
+ /** @var Revision $current */
$current = $details['current'];
$target = $details['target'];
$newId = $details['newid'];
@@ -91,12 +96,26 @@ class RollbackAction extends FormlessAction {
$old = Linker::revUserTools( $current );
$new = Linker::revUserTools( $target );
- $this->getOutput()->addHTML( $this->msg( 'rollback-success' )->rawParams( $old, $new )->parseAsBlock() );
+ $this->getOutput()->addHTML( $this->msg( 'rollback-success' )->rawParams( $old, $new )
+ ->parseAsBlock() );
+
+ if ( $user->getBoolOption( 'watchrollback' ) ) {
+ $user->addWatch( $this->page->getTitle(), WatchedItem::IGNORE_USER_RIGHTS );
+ }
+
$this->getOutput()->returnToMain( false, $this->getTitle() );
- if ( !$request->getBool( 'hidediff', false ) && !$this->getUser()->getBoolOption( 'norollbackdiff', false ) ) {
+ if ( !$request->getBool( 'hidediff', false ) &&
+ !$this->getUser()->getBoolOption( 'norollbackdiff', false )
+ ) {
$contentHandler = $current->getContentHandler();
- $de = $contentHandler->createDifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
+ $de = $contentHandler->createDifferenceEngine(
+ $this->getContext(),
+ $current->getId(),
+ $newId,
+ false,
+ true
+ );
$de->showDiff( '', '' );
}
}
diff --git a/includes/actions/SubmitAction.php b/includes/actions/SubmitAction.php
new file mode 100644
index 00000000..fae49f61
--- /dev/null
+++ b/includes/actions/SubmitAction.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Wrapper for EditAction; sets the session cookie.
+ *
+ * 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
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+/**
+ * This is the same as EditAction; except that it sets the session cookie.
+ *
+ * @ingroup Actions
+ */
+class SubmitAction extends EditAction {
+
+ public function getName() {
+ return 'submit';
+ }
+
+ public function show() {
+ if ( session_id() === '' ) {
+ // Send a cookie so anons get talk message notifications
+ wfSetupSession();
+ }
+
+ parent::show();
+ }
+}
diff --git a/includes/actions/UnprotectAction.php b/includes/actions/UnprotectAction.php
new file mode 100644
index 00000000..bc28c8ed
--- /dev/null
+++ b/includes/actions/UnprotectAction.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * action=unprotect handler
+ *
+ * Copyright © 2012 Timo Tijhof
+ *
+ * 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
+ *
+ * @file
+ * @ingroup Actions
+ * @author Timo Tijhof
+ */
+
+/**
+ * Handle page unprotection
+ *
+ * This is a wrapper that will call Article::unprotect().
+ *
+ * @ingroup Actions
+ */
+class UnprotectAction extends ProtectAction {
+
+ public function getName() {
+ return 'unprotect';
+ }
+
+ public function show() {
+
+ $this->page->unprotect();
+ }
+}
diff --git a/includes/actions/UnwatchAction.php b/includes/actions/UnwatchAction.php
new file mode 100644
index 00000000..e2e5a1d8
--- /dev/null
+++ b/includes/actions/UnwatchAction.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Performs the unwatch actions on a page
+ *
+ * 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
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+/**
+ * Page removal from a user's watchlist
+ *
+ * @ingroup Actions
+ */
+class UnwatchAction extends WatchAction {
+
+ public function getName() {
+ return 'unwatch';
+ }
+
+ protected function getDescription() {
+ return $this->msg( 'removewatch' )->escaped();
+ }
+
+ public function onSubmit( $data ) {
+ wfProfileIn( __METHOD__ );
+ self::doUnwatch( $this->getTitle(), $this->getUser() );
+ wfProfileOut( __METHOD__ );
+
+ return true;
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setSubmitTextMsg( 'confirm-unwatch-button' );
+ }
+
+ protected function preText() {
+ return $this->msg( 'confirm-unwatch-top' )->parse();
+ }
+
+ public function onSuccess() {
+ $this->getOutput()->addWikiMsg( 'removedwatchtext', $this->getTitle()->getPrefixedText() );
+ }
+}
diff --git a/includes/actions/ViewAction.php b/includes/actions/ViewAction.php
index e227197d..3a24565c 100644
--- a/includes/actions/ViewAction.php
+++ b/includes/actions/ViewAction.php
@@ -43,5 +43,4 @@ class ViewAction extends FormlessAction {
public function show() {
$this->page->view();
}
-
}
diff --git a/includes/actions/WatchAction.php b/includes/actions/WatchAction.php
index 929c1b5f..8c9a46a5 100644
--- a/includes/actions/WatchAction.php
+++ b/includes/actions/WatchAction.php
@@ -1,6 +1,6 @@
<?php
/**
- * Performs the watch and unwatch actions on a page
+ * Performs the watch actions on a page
*
* 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
@@ -51,6 +51,7 @@ class WatchAction extends FormAction {
wfProfileIn( __METHOD__ );
self::doWatch( $this->getTitle(), $this->getUser() );
wfProfileOut( __METHOD__ );
+
return true;
}
@@ -81,10 +82,10 @@ class WatchAction extends FormAction {
protected function checkCanExecute( User $user ) {
// Must be logged in
if ( $user->isAnon() ) {
- throw new ErrorPageError( 'watchnologin', 'watchnologintext' );
+ throw new UserNotLoggedIn( 'watchlistanontext', 'watchnologin' );
}
- return parent::checkCanExecute( $user );
+ parent::checkCanExecute( $user );
}
/**
@@ -96,7 +97,9 @@ class WatchAction extends FormAction {
* @return Status
*/
public static function doWatchOrUnwatch( $watch, Title $title, User $user ) {
- if ( $user->isLoggedIn() && $user->isWatched( $title, WatchedItem::IGNORE_USER_RIGHTS ) != $watch ) {
+ if ( $user->isLoggedIn() &&
+ $user->isWatched( $title, WatchedItem::IGNORE_USER_RIGHTS ) != $watch
+ ) {
// If the user doesn't have 'editmywatchlist', we still want to
// allow them to add but not remove items via edits and such.
if ( $watch ) {
@@ -105,6 +108,7 @@ class WatchAction extends FormAction {
return self::doUnwatch( $title, $user );
}
}
+
return Status::newGood();
}
@@ -116,8 +120,12 @@ class WatchAction extends FormAction {
* @param int $checkRights Passed through to $user->addWatch()
* @return Status
*/
- public static function doWatch( Title $title, User $user, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
- if ( $checkRights !== WatchedItem::IGNORE_USER_RIGHTS && !$user->isAllowed( 'editmywatchlist' ) ) {
+ public static function doWatch( Title $title, User $user,
+ $checkRights = WatchedItem::CHECK_USER_RIGHTS
+ ) {
+ if ( $checkRights !== WatchedItem::IGNORE_USER_RIGHTS &&
+ !$user->isAllowed( 'editmywatchlist' )
+ ) {
return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
}
@@ -129,6 +137,7 @@ class WatchAction extends FormAction {
$user->addWatch( $title, $checkRights );
wfRunHooks( 'WatchArticleComplete', array( &$user, &$page ) );
}
+
return $status;
}
@@ -152,6 +161,7 @@ class WatchAction extends FormAction {
$user->removeWatch( $title );
wfRunHooks( 'UnwatchArticleComplete', array( &$user, &$page ) );
}
+
return $status;
}
@@ -168,7 +178,7 @@ class WatchAction extends FormAction {
if ( $action != 'unwatch' ) {
$action = 'watch';
}
- $salt = array( $action, $title->getDBkey() );
+ $salt = array( $action, $title->getPrefixedDBkey() );
// This token stronger salted and not compatible with ApiWatch
// It's title/action specific because index.php is GET and API is POST
@@ -200,38 +210,3 @@ class WatchAction extends FormAction {
$this->getOutput()->addWikiMsg( 'addedwatchtext', $this->getTitle()->getPrefixedText() );
}
}
-
-/**
- * Page removal from a user's watchlist
- *
- * @ingroup Actions
- */
-class UnwatchAction extends WatchAction {
-
- public function getName() {
- return 'unwatch';
- }
-
- protected function getDescription() {
- return $this->msg( 'removewatch' )->escaped();
- }
-
- public function onSubmit( $data ) {
- wfProfileIn( __METHOD__ );
- self::doUnwatch( $this->getTitle(), $this->getUser() );
- wfProfileOut( __METHOD__ );
- return true;
- }
-
- protected function alterForm( HTMLForm $form ) {
- $form->setSubmitTextMsg( 'confirm-unwatch-button' );
- }
-
- protected function preText() {
- return $this->msg( 'confirm-unwatch-top' )->parse();
- }
-
- public function onSuccess() {
- $this->getOutput()->addWikiMsg( 'removedwatchtext', $this->getTitle()->getPrefixedText() );
- }
-}
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index c1454e76..944e4895 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -30,36 +30,40 @@
* The class functions are divided into several areas of functionality:
*
* Module parameters: Derived classes can define getAllowedParams() to specify
- * which parameters to expect, how to parse and validate them.
+ * which parameters to expect, how to parse and validate them.
*
* Profiling: various methods to allow keeping tabs on various tasks and their
- * time costs
+ * time costs
*
* Self-documentation: code to allow the API to document its own state
*
* @ingroup API
*/
abstract class ApiBase extends ContextSource {
-
// These constants allow modules to specify exactly how to treat incoming parameters.
- const PARAM_DFLT = 0; // Default value of the parameter
- const PARAM_ISMULTI = 1; // Boolean, do we accept more than one item for this parameter (e.g.: titles)?
- const PARAM_TYPE = 2; // Can be either a string type (e.g.: 'integer') or an array of allowed values
- const PARAM_MAX = 3; // Max value allowed for a parameter. Only applies if TYPE='integer'
- const PARAM_MAX2 = 4; // Max value allowed for a parameter for bots and sysops. Only applies if TYPE='integer'
- const PARAM_MIN = 5; // Lowest value allowed for a parameter. Only applies if TYPE='integer'
- const PARAM_ALLOW_DUPLICATES = 6; // Boolean, do we allow the same value to be set more than once when ISMULTI=true
- const PARAM_DEPRECATED = 7; // Boolean, is the parameter deprecated (will show a warning)
+ // Default value of the parameter
+ const PARAM_DFLT = 0;
+ // Boolean, do we accept more than one item for this parameter (e.g.: titles)?
+ const PARAM_ISMULTI = 1;
+ // Can be either a string type (e.g.: 'integer') or an array of allowed values
+ const PARAM_TYPE = 2;
+ // Max value allowed for a parameter. Only applies if TYPE='integer'
+ const PARAM_MAX = 3;
+ // Max value allowed for a parameter for bots and sysops. Only applies if TYPE='integer'
+ const PARAM_MAX2 = 4;
+ // Lowest value allowed for a parameter. Only applies if TYPE='integer'
+ const PARAM_MIN = 5;
+ // Boolean, do we allow the same value to be set more than once when ISMULTI=true
+ const PARAM_ALLOW_DUPLICATES = 6;
+ // Boolean, is the parameter deprecated (will show a warning)
+ const PARAM_DEPRECATED = 7;
/// @since 1.17
const PARAM_REQUIRED = 8; // Boolean, is the parameter required?
/// @since 1.17
- const PARAM_RANGE_ENFORCE = 9; // Boolean, if MIN/MAX are set, enforce (die) these? Only applies if TYPE='integer' Use with extreme caution
-
- const PROP_ROOT = 'ROOT'; // Name of property group that is on the root element of the result, i.e. not part of a list
- const PROP_LIST = 'LIST'; // Boolean, is the result multiple items? Defaults to true for query modules, to false for other modules
- const PROP_TYPE = 0; // Type of the property, uses same format as PARAM_TYPE
- const PROP_NULLABLE = 1; // Boolean, can the property be not included in the result? Defaults to false
+ // Boolean, if MIN/MAX are set, enforce (die) these?
+ // Only applies if TYPE='integer' Use with extreme caution
+ const PARAM_RANGE_ENFORCE = 9;
const LIMIT_BIG1 = 500; // Fast query, std user limit
const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
@@ -73,17 +77,19 @@ abstract class ApiBase extends ContextSource {
*/
const GET_VALUES_FOR_HELP = 1;
- private $mMainModule, $mModuleName, $mModulePrefix;
+ /** @var ApiMain */
+ private $mMainModule;
+ /** @var string */
+ private $mModuleName, $mModulePrefix;
private $mSlaveDB = null;
private $mParamCache = array();
/**
- * Constructor
- * @param $mainModule ApiMain object
+ * @param ApiMain $mainModule
* @param string $moduleName Name of this module
* @param string $modulePrefix Prefix to use for parameter names
*/
- public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) {
+ public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
$this->mMainModule = $mainModule;
$this->mModuleName = $moduleName;
$this->mModulePrefix = $modulePrefix;
@@ -93,9 +99,11 @@ abstract class ApiBase extends ContextSource {
}
}
- /*****************************************************************************
- * ABSTRACT METHODS *
- *****************************************************************************/
+
+ /************************************************************************//**
+ * @name Methods to implement
+ * @{
+ */
/**
* Evaluates the parameters, performs the requested query, and sets up
@@ -116,432 +124,232 @@ abstract class ApiBase extends ContextSource {
abstract public function execute();
/**
- * Returns a string that identifies the version of the extending class.
- * Typically includes the class name, the svn revision, timestamp, and
- * last author. Usually done with SVN's Id keyword
- * @return string
- * @deprecated since 1.21, version string is no longer supported
+ * Get the module manager, or null if this module has no sub-modules
+ * @since 1.21
+ * @return ApiModuleManager
*/
- public function getVersion() {
- wfDeprecated( __METHOD__, '1.21' );
- return '';
+ public function getModuleManager() {
+ return null;
}
/**
- * Get the name of the module being executed by this instance
- * @return string
+ * If the module may only be used with a certain format module,
+ * it should override this method to return an instance of that formatter.
+ * A value of null means the default format will be used.
+ * @return mixed Instance of a derived class of ApiFormatBase, or null
*/
- public function getModuleName() {
- return $this->mModuleName;
+ public function getCustomPrinter() {
+ return null;
}
/**
- * Get the module manager, or null if this module has no sub-modules
- * @since 1.21
- * @return ApiModuleManager
+ * Returns the description string for this module
+ * @return string|array
*/
- public function getModuleManager() {
- return null;
+ protected function getDescription() {
+ return false;
}
/**
- * Get parameter prefix (usually two letters or an empty string).
- * @return string
+ * Returns usage examples for this module. Return false if no examples are available.
+ * @return bool|string|array
*/
- public function getModulePrefix() {
- return $this->mModulePrefix;
+ protected function getExamples() {
+ return false;
}
/**
- * Get the name of the module as shown in the profiler log
+ * @return bool|string|array Returns a false if the module has no help URL,
+ * else returns a (array of) string
+ */
+ public function getHelpUrls() {
+ return false;
+ }
+
+ /**
+ * Returns an array of allowed parameters (parameter name) => (default
+ * value) or (parameter name) => (array with PARAM_* constants as keys)
+ * Don't call this function directly: use getFinalParams() to allow
+ * hooks to modify parameters as needed.
*
- * @param $db DatabaseBase|bool
+ * Some derived classes may choose to handle an integer $flags parameter
+ * in the overriding methods. Callers of this method can pass zero or
+ * more OR-ed flags like GET_VALUES_FOR_HELP.
*
- * @return string
+ * @return array|bool
*/
- public function getModuleProfileName( $db = false ) {
- if ( $db ) {
- return 'API:' . $this->mModuleName . '-DB';
- } else {
- return 'API:' . $this->mModuleName;
- }
+ protected function getAllowedParams( /* $flags = 0 */ ) {
+ // int $flags is not declared because it causes "Strict standards"
+ // warning. Most derived classes do not implement it.
+ return false;
}
/**
- * Get the main module
- * @return ApiMain object
+ * Returns an array of parameter descriptions.
+ * Don't call this function directly: use getFinalParamDescription() to
+ * allow hooks to modify descriptions as needed.
+ * @return array|bool False on no parameter descriptions
*/
- public function getMain() {
- return $this->mMainModule;
+ protected function getParamDescription() {
+ return false;
}
/**
- * Returns true if this module is the main module ($this === $this->mMainModule),
- * false otherwise.
+ * Indicates if this module needs maxlag to be checked
* @return bool
*/
- public function isMain() {
- return $this === $this->mMainModule;
+ public function shouldCheckMaxlag() {
+ return true;
}
/**
- * Get the result object
- * @return ApiResult
+ * Indicates whether this module requires read rights
+ * @return bool
*/
- public function getResult() {
- // Main module has getResult() method overridden
- // Safety - avoid infinite loop:
- if ( $this->isMain() ) {
- ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
- }
- return $this->getMain()->getResult();
+ public function isReadMode() {
+ return true;
}
/**
- * Get the result data array (read-only)
- * @return array
+ * Indicates whether this module requires write mode
+ * @return bool
*/
- public function getResultData() {
- return $this->getResult()->getData();
+ public function isWriteMode() {
+ return false;
}
/**
- * Create a new RequestContext object to use e.g. for calls to other parts
- * the software.
- * The object will have the WebRequest and the User object set to the ones
- * used in this instance.
- *
- * @deprecated since 1.19 use getContext to get the current context
- * @return DerivativeContext
+ * Indicates whether this module must be called with a POST request
+ * @return bool
*/
- public function createContext() {
- wfDeprecated( __METHOD__, '1.19' );
- return new DerivativeContext( $this->getContext() );
+ public function mustBePosted() {
+ return $this->needsToken() !== false;
}
/**
- * Set warning section for this module. Users should monitor this
- * section to notice any changes in API. Multiple calls to this
- * function will result in the warning messages being separated by
- * newlines
- * @param string $warning Warning message
+ * Returns the token type this module requires in order to execute.
+ *
+ * Modules are strongly encouraged to use the core 'csrf' type unless they
+ * have specialized security needs. If the token type is not one of the
+ * core types, you must use the ApiQueryTokensRegisterTypes hook to
+ * register it.
+ *
+ * Returning a non-falsey value here will cause self::getFinalParams() to
+ * return a required string 'token' parameter and
+ * self::getFinalParamDescription() to ensure there is standardized
+ * documentation for it. Also, self::mustBePosted() must return true when
+ * tokens are used.
+ *
+ * In previous versions of MediaWiki, true was a valid return value.
+ * Returning true will generate errors indicating that the API module needs
+ * updating.
+ *
+ * @return string|false
*/
- public function setWarning( $warning ) {
- $result = $this->getResult();
- $data = $result->getData();
- $moduleName = $this->getModuleName();
- if ( isset( $data['warnings'][$moduleName] ) ) {
- // Don't add duplicate warnings
- $oldWarning = $data['warnings'][$moduleName]['*'];
- $warnPos = strpos( $oldWarning, $warning );
- // If $warning was found in $oldWarning, check if it starts at 0 or after "\n"
- if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
- // Check if $warning is followed by "\n" or the end of the $oldWarning
- $warnPos += strlen( $warning );
- if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
- return;
- }
- }
- // If there is a warning already, append it to the existing one
- $warning = "$oldWarning\n$warning";
- }
- $msg = array();
- ApiResult::setContent( $msg, $warning );
- $result->disableSizeCheck();
- $result->addValue( 'warnings', $moduleName,
- $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP );
- $result->enableSizeCheck();
+ public function needsToken() {
+ return false;
}
/**
- * If the module may only be used with a certain format module,
- * it should override this method to return an instance of that formatter.
- * A value of null means the default format will be used.
- * @return mixed instance of a derived class of ApiFormatBase, or null
+ * Fetch the salt used in the Web UI corresponding to this module.
+ *
+ * Only override this if the Web UI uses a token with a non-constant salt.
+ *
+ * @since 1.24
+ * @param array $params All supplied parameters for the module
+ * @return string|array|null
*/
- public function getCustomPrinter() {
+ protected function getWebUITokenSalt( array $params ) {
return null;
}
- /**
- * Generates help message for this module, or false if there is no description
- * @return mixed string or false
- */
- public function makeHelpMsg() {
- static $lnPrfx = "\n ";
-
- $msg = $this->getFinalDescription();
-
- if ( $msg !== false ) {
-
- if ( !is_array( $msg ) ) {
- $msg = array(
- $msg
- );
- }
- $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
+ /**@}*/
- $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
-
- if ( $this->isReadMode() ) {
- $msg .= "\nThis module requires read rights";
- }
- if ( $this->isWriteMode() ) {
- $msg .= "\nThis module requires write rights";
- }
- if ( $this->mustBePosted() ) {
- $msg .= "\nThis module only accepts POST requests";
- }
- if ( $this->isReadMode() || $this->isWriteMode() ||
- $this->mustBePosted() ) {
- $msg .= "\n";
- }
-
- // Parameters
- $paramsMsg = $this->makeHelpMsgParameters();
- if ( $paramsMsg !== false ) {
- $msg .= "Parameters:\n$paramsMsg";
- }
-
- $examples = $this->getExamples();
- if ( $examples ) {
- if ( !is_array( $examples ) ) {
- $examples = array(
- $examples
- );
- }
- $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
- foreach ( $examples as $k => $v ) {
- if ( is_numeric( $k ) ) {
- $msg .= " $v\n";
- } else {
- if ( is_array( $v ) ) {
- $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
- } else {
- $msgExample = " $v";
- }
- $msgExample .= ":";
- $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n $k\n";
- }
- }
- }
- }
-
- return $msg;
- }
+ /************************************************************************//**
+ * @name Data access methods
+ * @{
+ */
/**
- * @param $item string
+ * Get the name of the module being executed by this instance
* @return string
*/
- private function indentExampleText( $item ) {
- return " " . $item;
+ public function getModuleName() {
+ return $this->mModuleName;
}
/**
- * @param string $prefix Text to split output items
- * @param string $title What is being output
- * @param $input string|array
+ * Get parameter prefix (usually two letters or an empty string).
* @return string
*/
- protected function makeHelpArrayToString( $prefix, $title, $input ) {
- if ( $input === false ) {
- return '';
- }
- if ( !is_array( $input ) ) {
- $input = array( $input );
- }
-
- if ( count( $input ) > 0 ) {
- if ( $title ) {
- $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
- } else {
- $msg = ' ';
- }
- $msg .= implode( $prefix, $input ) . "\n";
- return $msg;
- }
- return '';
+ public function getModulePrefix() {
+ return $this->mModulePrefix;
}
/**
- * Generates the parameter descriptions for this module, to be displayed in the
- * module's help.
- * @return string or false
+ * Get the main module
+ * @return ApiMain
*/
- public function makeHelpMsgParameters() {
- $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
- if ( $params ) {
-
- $paramsDescription = $this->getFinalParamDescription();
- $msg = '';
- $paramPrefix = "\n" . str_repeat( ' ', 24 );
- $descWordwrap = "\n" . str_repeat( ' ', 28 );
- foreach ( $params as $paramName => $paramSettings ) {
- $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
- if ( is_array( $desc ) ) {
- $desc = implode( $paramPrefix, $desc );
- }
-
- //handle shorthand
- if ( !is_array( $paramSettings ) ) {
- $paramSettings = array(
- self::PARAM_DFLT => $paramSettings,
- );
- }
-
- //handle missing type
- if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
- $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] ) ? $paramSettings[ApiBase::PARAM_DFLT] : null;
- if ( is_bool( $dflt ) ) {
- $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
- } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
- $paramSettings[ApiBase::PARAM_TYPE] = 'string';
- } elseif ( is_int( $dflt ) ) {
- $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
- }
- }
-
- if ( isset( $paramSettings[self::PARAM_DEPRECATED] ) && $paramSettings[self::PARAM_DEPRECATED] ) {
- $desc = "DEPRECATED! $desc";
- }
-
- if ( isset( $paramSettings[self::PARAM_REQUIRED] ) && $paramSettings[self::PARAM_REQUIRED] ) {
- $desc .= $paramPrefix . "This parameter is required";
- }
-
- $type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
- if ( isset( $type ) ) {
- $hintPipeSeparated = true;
- $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false;
- if ( $multi ) {
- $prompt = 'Values (separate with \'|\'): ';
- } else {
- $prompt = 'One value: ';
- }
-
- if ( is_array( $type ) ) {
- $choices = array();
- $nothingPrompt = '';
- foreach ( $type as $t ) {
- if ( $t === '' ) {
- $nothingPrompt = 'Can be empty, or ';
- } else {
- $choices[] = $t;
- }
- }
- $desc .= $paramPrefix . $nothingPrompt . $prompt;
- $choicesstring = implode( ', ', $choices );
- $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
- $hintPipeSeparated = false;
- } else {
- switch ( $type ) {
- case 'namespace':
- // Special handling because namespaces are type-limited, yet they are not given
- $desc .= $paramPrefix . $prompt;
- $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
- 100, $descWordwrap );
- $hintPipeSeparated = false;
- break;
- case 'limit':
- $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
- if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
- $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
- }
- $desc .= ' allowed';
- break;
- case 'integer':
- $s = $multi ? 's' : '';
- $hasMin = isset( $paramSettings[self::PARAM_MIN] );
- $hasMax = isset( $paramSettings[self::PARAM_MAX] );
- if ( $hasMin || $hasMax ) {
- if ( !$hasMax ) {
- $intRangeStr = "The value$s must be no less than {$paramSettings[self::PARAM_MIN]}";
- } elseif ( !$hasMin ) {
- $intRangeStr = "The value$s must be no more than {$paramSettings[self::PARAM_MAX]}";
- } else {
- $intRangeStr = "The value$s must be between {$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
- }
-
- $desc .= $paramPrefix . $intRangeStr;
- }
- break;
- case 'upload':
- $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
- break;
- }
- }
-
- if ( $multi ) {
- if ( $hintPipeSeparated ) {
- $desc .= $paramPrefix . "Separate values with '|'";
- }
-
- $isArray = is_array( $type );
- if ( !$isArray
- || $isArray && count( $type ) > self::LIMIT_SML1 ) {
- $desc .= $paramPrefix . "Maximum number of values " .
- self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
- }
- }
- }
-
- $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
- if ( !is_null( $default ) && $default !== false ) {
- $desc .= $paramPrefix . "Default: $default";
- }
-
- $msg .= sprintf( " %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
- }
- return $msg;
+ public function getMain() {
+ return $this->mMainModule;
+ }
- } else {
- return false;
- }
+ /**
+ * Returns true if this module is the main module ($this === $this->mMainModule),
+ * false otherwise.
+ * @return bool
+ */
+ public function isMain() {
+ return $this === $this->mMainModule;
}
/**
- * Returns the description string for this module
- * @return mixed string or array of strings
+ * Get the result object
+ * @return ApiResult
*/
- protected function getDescription() {
- return false;
+ public function getResult() {
+ // Main module has getResult() method overridden
+ // Safety - avoid infinite loop:
+ if ( $this->isMain() ) {
+ ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+ }
+
+ return $this->getMain()->getResult();
}
/**
- * Returns usage examples for this module. Return false if no examples are available.
- * @return bool|string|array
+ * Get the result data array (read-only)
+ * @return array
*/
- protected function getExamples() {
- return false;
+ public function getResultData() {
+ return $this->getResult()->getData();
}
/**
- * Returns an array of allowed parameters (parameter name) => (default
- * value) or (parameter name) => (array with PARAM_* constants as keys)
- * Don't call this function directly: use getFinalParams() to allow
- * hooks to modify parameters as needed.
- *
- * Some derived classes may choose to handle an integer $flags parameter
- * in the overriding methods. Callers of this method can pass zero or
- * more OR-ed flags like GET_VALUES_FOR_HELP.
- *
- * @return array|bool
+ * Gets a default slave database connection object
+ * @return DatabaseBase
*/
- protected function getAllowedParams( /* $flags = 0 */ ) {
- // int $flags is not declared because it causes "Strict standards"
- // warning. Most derived classes do not implement it.
- return false;
+ protected function getDB() {
+ if ( !isset( $this->mSlaveDB ) ) {
+ $this->profileDBIn();
+ $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
+ $this->profileDBOut();
+ }
+
+ return $this->mSlaveDB;
}
/**
- * Returns an array of parameter descriptions.
- * Don't call this function directly: use getFinalParamDescription() to
- * allow hooks to modify descriptions as needed.
- * @return array|bool False on no parameter descriptions
+ * Get final module description, after hooks have had a chance to tweak it as
+ * needed.
+ *
+ * @return array|bool False on no parameters
*/
- protected function getParamDescription() {
- return false;
+ public function getFinalDescription() {
+ $desc = $this->getDescription();
+ wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
+
+ return $desc;
}
/**
@@ -549,12 +357,21 @@ abstract class ApiBase extends ContextSource {
* tweak it as needed.
*
* @param int $flags Zero or more flags like GET_VALUES_FOR_HELP
- * @return array|Bool False on no parameters
+ * @return array|bool False on no parameters
* @since 1.21 $flags param added
*/
public function getFinalParams( $flags = 0 ) {
$params = $this->getAllowedParams( $flags );
+
+ if ( $this->needsToken() ) {
+ $params['token'] = array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ );
+ }
+
wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
+
return $params;
}
@@ -566,67 +383,33 @@ abstract class ApiBase extends ContextSource {
*/
public function getFinalParamDescription() {
$desc = $this->getParamDescription();
- wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
- return $desc;
- }
- /**
- * Returns possible properties in the result, grouped by the value of the prop parameter
- * that shows them.
- *
- * Properties that are shown always are in a group with empty string as a key.
- * Properties that can be shown by several values of prop are included multiple times.
- * If some properties are part of a list and some are on the root object (see ApiQueryQueryPage),
- * those on the root object are under the key PROP_ROOT.
- * The array can also contain a boolean under the key PROP_LIST,
- * indicating whether the result is a list.
- *
- * Don't call this function directly: use getFinalResultProperties() to
- * allow hooks to modify descriptions as needed.
- *
- * @return array|bool False on no properties
- */
- protected function getResultProperties() {
- return false;
- }
-
- /**
- * Get final possible result properties, after hooks have had a chance to tweak it as
- * needed.
- *
- * @return array
- */
- public function getFinalResultProperties() {
- $properties = $this->getResultProperties();
- wfRunHooks( 'APIGetResultProperties', array( $this, &$properties ) );
- return $properties;
- }
-
- /**
- * Add token properties to the array used by getResultProperties,
- * based on a token functions mapping.
- */
- protected static function addTokenProperties( &$props, $tokenFunctions ) {
- foreach ( array_keys( $tokenFunctions ) as $token ) {
- $props[''][$token . 'token'] = array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
+ $tokenType = $this->needsToken();
+ if ( $tokenType ) {
+ if ( !isset( $desc['token'] ) ) {
+ $desc['token'] = array();
+ } elseif ( !is_array( $desc['token'] ) ) {
+ // We ignore a plain-string token, because it's probably an
+ // extension that is supplying the string for BC.
+ $desc['token'] = array();
+ }
+ array_unshift( $desc['token'],
+ "A '$tokenType' token retrieved from action=query&meta=tokens"
);
}
- }
- /**
- * Get final module description, after hooks have had a chance to tweak it as
- * needed.
- *
- * @return array|bool False on no parameters
- */
- public function getFinalDescription() {
- $desc = $this->getDescription();
- wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
+ wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
+
return $desc;
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Parameter handling
+ * @{
+ */
+
/**
* This method mangles parameter name based on the prefix supplied to the constructor.
* Override this method to change parameter name during runtime
@@ -643,7 +426,7 @@ abstract class ApiBase extends ContextSource {
* value - validated value from user or default. limits will not be
* parsed if $parseLimit is set to false; use this when the max
* limit is not definitive yet, e.g. when getting revisions.
- * @param $parseLimit Boolean: true by default
+ * @param bool $parseLimit True by default
* @return array
*/
public function extractRequestParams( $parseLimit = true ) {
@@ -660,26 +443,30 @@ abstract class ApiBase extends ContextSource {
}
$this->mParamCache[$parseLimit] = $results;
}
+
return $this->mParamCache[$parseLimit];
}
/**
* Get a value for the given parameter
* @param string $paramName Parameter name
- * @param bool $parseLimit see extractRequestParams()
+ * @param bool $parseLimit See extractRequestParams()
* @return mixed Parameter value
*/
protected function getParameter( $paramName, $parseLimit = true ) {
$params = $this->getFinalParams();
$paramSettings = $params[$paramName];
+
return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
}
/**
* Die if none or more than one of a certain set of parameters is set and not false.
- * @param array $params of parameter names
+ *
+ * @param array $params User provided set of parameters, as from $this->extractRequestParams()
+ * @param string $required,... Names of parameters of which exactly one must be set
*/
- public function requireOnlyOneParameter( $params ) {
+ public function requireOnlyOneParameter( $params, $required /*...*/ ) {
$required = func_get_args();
array_shift( $required );
$p = $this->getModulePrefix();
@@ -688,34 +475,24 @@ abstract class ApiBase extends ContextSource {
array( $this, "parameterNotEmpty" ) ) ), $required );
if ( count( $intersection ) > 1 ) {
- $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', 'invalidparammix' );
+ $this->dieUsage(
+ "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
+ 'invalidparammix' );
} elseif ( count( $intersection ) == 0 ) {
- $this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', 'missingparam' );
+ $this->dieUsage(
+ "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required',
+ 'missingparam'
+ );
}
}
/**
- * Generates the possible errors requireOnlyOneParameter() can die with
- *
- * @param $params array
- * @return array
- */
- public function getRequireOnlyOneParameterErrorMessages( $params ) {
- $p = $this->getModulePrefix();
- $params = implode( ", {$p}", $params );
-
- return array(
- array( 'code' => "{$p}missingparam", 'info' => "One of the parameters {$p}{$params} is required" ),
- array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" )
- );
- }
-
- /**
* Die if more than one of a certain set of parameters is set and not false.
*
- * @param $params array
+ * @param array $params User provided set of parameters, as from $this->extractRequestParams()
+ * @param string $required,... Names of parameters of which at most one must be set
*/
- public function requireMaxOneParameter( $params ) {
+ public function requireMaxOneParameter( $params, $required /*...*/ ) {
$required = func_get_args();
array_shift( $required );
$p = $this->getModulePrefix();
@@ -724,27 +501,51 @@ abstract class ApiBase extends ContextSource {
array( $this, "parameterNotEmpty" ) ) ), $required );
if ( count( $intersection ) > 1 ) {
- $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', 'invalidparammix' );
+ $this->dieUsage(
+ "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
+ 'invalidparammix'
+ );
}
}
/**
- * Generates the possible error requireMaxOneParameter() can die with
+ * Die if none of a certain set of parameters is set and not false.
*
- * @param $params array
- * @return array
+ * @since 1.23
+ * @param array $params User provided set of parameters, as from $this->extractRequestParams()
+ * @param string $required,... Names of parameters of which at least one must be set
*/
- public function getRequireMaxOneParameterErrorMessages( $params ) {
+ public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
+ $required = func_get_args();
+ array_shift( $required );
$p = $this->getModulePrefix();
- $params = implode( ", {$p}", $params );
- return array(
- array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" )
+ $intersection = array_intersect(
+ array_keys( array_filter( $params, array( $this, "parameterNotEmpty" ) ) ),
+ $required
);
+
+ if ( count( $intersection ) == 0 ) {
+ $this->dieUsage( "At least one of the parameters {$p}" .
+ implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
+ }
+ }
+
+ /**
+ * Callback function used in requireOnlyOneParameter to check whether required parameters are set
+ *
+ * @param object $x Parameter to check is not null/false
+ * @return bool
+ */
+ private function parameterNotEmpty( $x ) {
+ return !is_null( $x ) && $x !== false;
}
/**
- * @param $params array
+ * Get a WikiPage object from a title or pageid param, if possible.
+ * Can die, if no param is set or if the title or page id is not valid.
+ *
+ * @param array $params
* @param bool|string $load Whether load the object's state from the database:
* - false: don't load (if the pageid is given, it will still be loaded)
* - 'fromdb': load from a slave database
@@ -781,44 +582,11 @@ abstract class ApiBase extends ContextSource {
}
/**
- * @return array
- */
- public function getTitleOrPageIdErrorMessage() {
- return array_merge(
- $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
- array(
- array( 'invalidtitle', 'title' ),
- array( 'nosuchpageid', 'pageid' ),
- )
- );
- }
-
- /**
- * Callback function used in requireOnlyOneParameter to check whether required parameters are set
- *
- * @param $x object Parameter to check is not null/false
- * @return bool
- */
- private function parameterNotEmpty( $x ) {
- return !is_null( $x ) && $x !== false;
- }
-
- /**
- * @deprecated since 1.17 use MWNamespace::getValidNamespaces()
- *
- * @return array
- */
- public static function getValidNamespaces() {
- wfDeprecated( __METHOD__, '1.17' );
- return MWNamespace::getValidNamespaces();
- }
-
- /**
* Return true if we're to watch the page, false if not, null if no change.
* @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
- * @param $titleObj Title the page under consideration
+ * @param Title $titleObj The page under consideration
* @param string $userOption The user option to consider when $watchlist=preferences.
- * If not set will magically default to either watchdefault or watchcreations
+ * If not set will use watchdefault always and watchcreations if $titleObj doesn't exist.
* @return bool
*/
protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
@@ -837,11 +605,12 @@ abstract class ApiBase extends ContextSource {
if ( $userWatching ) {
return true;
}
- # If no user option was passed, use watchdefault or watchcreations
+ # If no user option was passed, use watchdefault and watchcreations
if ( is_null( $userOption ) ) {
- $userOption = $titleObj->exists()
- ? 'watchdefault' : 'watchcreations';
+ return $this->getUser()->getBoolOption( 'watchdefault' ) ||
+ $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
}
+
# Watch the article based on the user preference
return $this->getUser()->getBoolOption( $userOption );
@@ -854,27 +623,12 @@ abstract class ApiBase extends ContextSource {
}
/**
- * Set a watch (or unwatch) based the based on a watchlist parameter.
- * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
- * @param $titleObj Title the article's title to change
- * @param string $userOption The user option to consider when $watch=preferences
- */
- protected function setWatch( $watch, $titleObj, $userOption = null ) {
- $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
- if ( $value === null ) {
- return;
- }
-
- WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
- }
-
- /**
* Using the settings determine the value for the given parameter
*
- * @param string $paramName parameter name
- * @param array|mixed $paramSettings default value or an array of settings
+ * @param string $paramName Parameter name
+ * @param array|mixed $paramSettings Default value or an array of settings
* using PARAM_* constants.
- * @param $parseLimit Boolean: parse limit?
+ * @param bool $parseLimit Parse limit?
* @return mixed Parameter value
*/
protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
@@ -889,12 +643,24 @@ abstract class ApiBase extends ContextSource {
$deprecated = false;
$required = false;
} else {
- $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
- $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false;
- $type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
- $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] ) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false;
- $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ? $paramSettings[self::PARAM_DEPRECATED] : false;
- $required = isset( $paramSettings[self::PARAM_REQUIRED] ) ? $paramSettings[self::PARAM_REQUIRED] : false;
+ $default = isset( $paramSettings[self::PARAM_DFLT] )
+ ? $paramSettings[self::PARAM_DFLT]
+ : null;
+ $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
+ ? $paramSettings[self::PARAM_ISMULTI]
+ : false;
+ $type = isset( $paramSettings[self::PARAM_TYPE] )
+ ? $paramSettings[self::PARAM_TYPE]
+ : null;
+ $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
+ ? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
+ : false;
+ $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
+ ? $paramSettings[self::PARAM_DEPRECATED]
+ : false;
+ $required = isset( $paramSettings[self::PARAM_REQUIRED] )
+ ? $paramSettings[self::PARAM_REQUIRED]
+ : false;
// When type is not given, and no choices, the type is the same as $default
if ( !isset( $type ) ) {
@@ -909,14 +675,21 @@ abstract class ApiBase extends ContextSource {
if ( $type == 'boolean' ) {
if ( isset( $default ) && $default !== false ) {
// Having a default value of anything other than 'false' is not allowed
- ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'. Boolean parameters must default to false." );
+ ApiBase::dieDebug(
+ __METHOD__,
+ "Boolean param $encParamName's default is set to '$default'. " .
+ "Boolean parameters must default to false."
+ );
}
$value = $this->getMain()->getCheck( $encParamName );
} elseif ( $type == 'upload' ) {
if ( isset( $default ) ) {
// Having a default value is not allowed
- ApiBase::dieDebug( __METHOD__, "File upload param $encParamName's default is set to '$default'. File upload parameters may not have a default." );
+ ApiBase::dieDebug(
+ __METHOD__,
+ "File upload param $encParamName's default is set to " .
+ "'$default'. File upload parameters may not have a default." );
}
if ( $multi ) {
ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
@@ -930,8 +703,8 @@ abstract class ApiBase extends ContextSource {
if ( $value !== null ) {
$this->dieUsage(
"File upload param $encParamName is not a file upload; " .
- "be sure to use multipart/form-data for your POST and include " .
- "a filename in the Content-Disposition header.",
+ "be sure to use multipart/form-data for your POST and include " .
+ "a filename in the Content-Disposition header.",
"badupload_{$encParamName}"
);
}
@@ -942,10 +715,18 @@ abstract class ApiBase extends ContextSource {
if ( isset( $value ) && $type == 'namespace' ) {
$type = MWNamespace::getValidNamespaces();
}
+ if ( isset( $value ) && $type == 'submodule' ) {
+ $type = $this->getModuleManager()->getNames( $paramName );
+ }
}
if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
- $value = $this->parseMultiValue( $encParamName, $value, $multi, is_array( $type ) ? $type : null );
+ $value = $this->parseMultiValue(
+ $encParamName,
+ $value,
+ $multi,
+ is_array( $type ) ? $type : null
+ );
}
// More validation only when choices were not given
@@ -964,7 +745,7 @@ abstract class ApiBase extends ContextSource {
$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
$max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
$enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
- ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
+ ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
if ( is_array( $value ) ) {
$value = array_map( 'intval', $value );
@@ -985,19 +766,32 @@ abstract class ApiBase extends ContextSource {
// Don't do any validation whatsoever
break;
}
- if ( !isset( $paramSettings[self::PARAM_MAX] ) || !isset( $paramSettings[self::PARAM_MAX2] ) ) {
- ApiBase::dieDebug( __METHOD__, "MAX1 or MAX2 are not defined for the limit $encParamName" );
+ if ( !isset( $paramSettings[self::PARAM_MAX] )
+ || !isset( $paramSettings[self::PARAM_MAX2] )
+ ) {
+ ApiBase::dieDebug(
+ __METHOD__,
+ "MAX1 or MAX2 are not defined for the limit $encParamName"
+ );
}
if ( $multi ) {
ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
}
$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
if ( $value == 'max' ) {
- $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX];
+ $value = $this->getMain()->canApiHighLimits()
+ ? $paramSettings[self::PARAM_MAX2]
+ : $paramSettings[self::PARAM_MAX];
$this->getResult()->setParsedLimit( $this->getModuleName(), $value );
} else {
$value = intval( $value );
- $this->validateLimit( $paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2] );
+ $this->validateLimit(
+ $paramName,
+ $value,
+ $min,
+ $paramSettings[self::PARAM_MAX],
+ $paramSettings[self::PARAM_MAX2]
+ );
}
break;
case 'boolean':
@@ -1052,25 +846,28 @@ abstract class ApiBase extends ContextSource {
*
* @param string $valueName The name of the parameter (for error
* reporting)
- * @param $value mixed The value being parsed
+ * @param mixed $value The value being parsed
* @param bool $allowMultiple Can $value contain more than one value
* separated by '|'?
- * @param $allowedValues mixed An array of values to check against. If
+ * @param string[]|null $allowedValues An array of values to check against. If
* null, all values are accepted.
- * @return mixed (allowMultiple ? an_array_of_values : a_single_value)
+ * @return string|string[] (allowMultiple ? an_array_of_values : a_single_value)
*/
protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
if ( trim( $value ) === '' && $allowMultiple ) {
return array();
}
- // This is a bit awkward, but we want to avoid calling canApiHighLimits() because it unstubs $wgUser
+ // This is a bit awkward, but we want to avoid calling canApiHighLimits()
+ // because it unstubs $wgUser
$valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
- $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits() ?
- self::LIMIT_SML2 : self::LIMIT_SML1;
+ $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits()
+ ? self::LIMIT_SML2
+ : self::LIMIT_SML1;
if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
- $this->setWarning( "Too many values supplied for parameter '$valueName': the limit is $sizeLimit" );
+ $this->setWarning( "Too many values supplied for parameter '$valueName': " .
+ "the limit is $sizeLimit" );
}
if ( !$allowMultiple && count( $valuesList ) != 1 ) {
@@ -1079,8 +876,13 @@ abstract class ApiBase extends ContextSource {
return $value;
}
- $possibleValues = is_array( $allowedValues ) ? "of '" . implode( "', '", $allowedValues ) . "'" : '';
- $this->dieUsage( "Only one $possibleValues is allowed for parameter '$valueName'", "multival_$valueName" );
+ $possibleValues = is_array( $allowedValues )
+ ? "of '" . implode( "', '", $allowedValues ) . "'"
+ : '';
+ $this->dieUsage(
+ "Only one $possibleValues is allowed for parameter '$valueName'",
+ "multival_$valueName"
+ );
}
if ( is_array( $allowedValues ) ) {
@@ -1092,7 +894,10 @@ abstract class ApiBase extends ContextSource {
$vals = implode( ", ", $unknown );
$this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
} else {
- $this->dieUsage( "Unrecognized value for parameter '$valueName': {$valuesList[0]}", "unknown_$valueName" );
+ $this->dieUsage(
+ "Unrecognized value for parameter '$valueName': {$valuesList[0]}",
+ "unknown_$valueName"
+ );
}
}
// Now throw them out
@@ -1110,9 +915,9 @@ abstract class ApiBase extends ContextSource {
* @param int|null $min Minimum value
* @param int|null $max Maximum value for users
* @param int $botMax Maximum value for sysops/bots
- * @param $enforceLimits Boolean Whether to enforce (die) if value is outside limits
+ * @param bool $enforceLimits Whether to enforce (die) if value is outside limits
*/
- function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
+ protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
if ( !is_null( $min ) && $value < $min ) {
$msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
@@ -1120,7 +925,8 @@ abstract class ApiBase extends ContextSource {
$value = $min;
}
- // Minimum is always validated, whereas maximum is checked only if not running in internal call mode
+ // Minimum is always validated, whereas maximum is checked only if not
+ // running in internal call mode
if ( $this->getMain()->isInternalMode() ) {
return;
}
@@ -1130,7 +936,8 @@ abstract class ApiBase extends ContextSource {
if ( !is_null( $max ) && $value > $max ) {
if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
if ( $value > $botMax ) {
- $msg = $this->encodeParamName( $paramName ) . " may not be over $botMax (set to $value) for bots or sysops";
+ $msg = $this->encodeParamName( $paramName ) .
+ " may not be over $botMax (set to $value) for bots or sysops";
$this->warnOrDie( $msg, $enforceLimits );
$value = $botMax;
}
@@ -1148,15 +955,57 @@ abstract class ApiBase extends ContextSource {
* @param string $encParamName Parameter name
* @return string Validated and normalized parameter
*/
- function validateTimestamp( $value, $encParamName ) {
+ protected function validateTimestamp( $value, $encParamName ) {
$unixTimestamp = wfTimestamp( TS_UNIX, $value );
if ( $unixTimestamp === false ) {
- $this->dieUsage( "Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}" );
+ $this->dieUsage(
+ "Invalid value '$value' for timestamp parameter $encParamName",
+ "badtimestamp_{$encParamName}"
+ );
}
+
return wfTimestamp( TS_MW, $unixTimestamp );
}
/**
+ * Validate the supplied token.
+ *
+ * @since 1.24
+ * @param string $token Supplied token
+ * @param array $params All supplied parameters for the module
+ * @return bool
+ */
+ public final function validateToken( $token, array $params ) {
+ $tokenType = $this->needsToken();
+ $salts = ApiQueryTokens::getTokenTypeSalts();
+ if ( !isset( $salts[$tokenType] ) ) {
+ throw new MWException(
+ "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
+ 'without registering it'
+ );
+ }
+
+ if ( $this->getUser()->matchEditToken(
+ $token,
+ $salts[$tokenType],
+ $this->getRequest()
+ ) ) {
+ return true;
+ }
+
+ $webUiSalt = $this->getWebUITokenSalt( $params );
+ if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
+ $token,
+ $webUiSalt,
+ $this->getRequest()
+ ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
* Validate and normalize of parameters of type 'user'
* @param string $value Parameter value
* @param string $encParamName Parameter name
@@ -1165,23 +1014,35 @@ abstract class ApiBase extends ContextSource {
private function validateUser( $value, $encParamName ) {
$title = Title::makeTitleSafe( NS_USER, $value );
if ( $title === null ) {
- $this->dieUsage( "Invalid value '$value' for user parameter $encParamName", "baduser_{$encParamName}" );
+ $this->dieUsage(
+ "Invalid value '$value' for user parameter $encParamName",
+ "baduser_{$encParamName}"
+ );
}
+
return $title->getText();
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Utility methods
+ * @{
+ */
+
/**
- * Adds a warning to the output, else dies
- *
- * @param $msg String Message to show as a warning, or error message if dying
- * @param $enforceLimits Boolean Whether this is an enforce (die)
+ * Set a watch (or unwatch) based the based on a watchlist parameter.
+ * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
+ * @param Title $titleObj The article's title to change
+ * @param string $userOption The user option to consider when $watch=preferences
*/
- private function warnOrDie( $msg, $enforceLimits = false ) {
- if ( $enforceLimits ) {
- $this->dieUsage( $msg, 'integeroutofrange' );
- } else {
- $this->setWarning( $msg );
+ protected function setWatch( $watch, $titleObj, $userOption = null ) {
+ $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
+ if ( $value === null ) {
+ return;
}
+
+ WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
}
/**
@@ -1196,10 +1057,96 @@ abstract class ApiBase extends ContextSource {
array_pop( $arr );
$modified = true;
}
+
return $modified;
}
/**
+ * Gets the user for whom to get the watchlist
+ *
+ * @param array $params
+ * @return User
+ */
+ public function getWatchlistUser( $params ) {
+ if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
+ $user = User::newFromName( $params['owner'], false );
+ if ( !( $user && $user->getId() ) ) {
+ $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
+ }
+ $token = $user->getOption( 'watchlisttoken' );
+ if ( $token == '' || $token != $params['token'] ) {
+ $this->dieUsage(
+ 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
+ 'bad_wltoken'
+ );
+ }
+ } else {
+ if ( !$this->getUser()->isLoggedIn() ) {
+ $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
+ }
+ if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
+ $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
+ }
+ $user = $this->getUser();
+ }
+
+ return $user;
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Warning and error reporting
+ * @{
+ */
+
+ /**
+ * Set warning section for this module. Users should monitor this
+ * section to notice any changes in API. Multiple calls to this
+ * function will result in the warning messages being separated by
+ * newlines
+ * @param string $warning Warning message
+ */
+ public function setWarning( $warning ) {
+ $result = $this->getResult();
+ $data = $result->getData();
+ $moduleName = $this->getModuleName();
+ if ( isset( $data['warnings'][$moduleName] ) ) {
+ // Don't add duplicate warnings
+ $oldWarning = $data['warnings'][$moduleName]['*'];
+ $warnPos = strpos( $oldWarning, $warning );
+ // If $warning was found in $oldWarning, check if it starts at 0 or after "\n"
+ if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
+ // Check if $warning is followed by "\n" or the end of the $oldWarning
+ $warnPos += strlen( $warning );
+ if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
+ return;
+ }
+ }
+ // If there is a warning already, append it to the existing one
+ $warning = "$oldWarning\n$warning";
+ }
+ $msg = array();
+ ApiResult::setContent( $msg, $warning );
+ $result->addValue( 'warnings', $moduleName,
+ $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+
+ /**
+ * Adds a warning to the output, else dies
+ *
+ * @param string $msg Message to show as a warning, or error message if dying
+ * @param bool $enforceLimits Whether this is an enforce (die)
+ */
+ private function warnOrDie( $msg, $enforceLimits = false ) {
+ if ( $enforceLimits ) {
+ $this->dieUsage( $msg, 'integeroutofrange' );
+ }
+
+ $this->setWarning( $msg );
+ }
+
+ /**
* Throw a UsageException, which will (if uncaught) call the main module's
* error handler and die with an error message.
*
@@ -1213,17 +1160,22 @@ abstract class ApiBase extends ContextSource {
*/
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
Profiler::instance()->close();
- throw new UsageException( $description, $this->encodeParamName( $errorCode ), $httpRespCode, $extradata );
+ throw new UsageException(
+ $description,
+ $this->encodeParamName( $errorCode ),
+ $httpRespCode,
+ $extradata
+ );
}
/**
- * Throw a UsageException based on the errors in the Status object.
+ * Get error (as code, string) from a Status object.
*
- * @since 1.22
- * @param Status $status Status object
- * @throws UsageException
+ * @since 1.23
+ * @param Status $status
+ * @return array Array of code and error string
*/
- public function dieStatus( $status ) {
+ public function getErrorFromStatus( $status ) {
if ( $status->isGood() ) {
throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
}
@@ -1248,13 +1200,28 @@ abstract class ApiBase extends ContextSource {
$msg = wfMessage( $code, $errors[0] );
}
if ( isset( ApiBase::$messageMap[$code] ) ) {
- // Translate message to code, for backwards compatability
+ // Translate message to code, for backwards compatibility
$code = ApiBase::$messageMap[$code]['code'];
}
- $this->dieUsage( $msg->inLanguage( 'en' )->useDatabase( false )->plain(), $code );
+
+ return array( $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() );
}
/**
+ * Throw a UsageException based on the errors in the Status object.
+ *
+ * @since 1.22
+ * @param Status $status
+ * @throws MWException
+ */
+ public function dieStatus( $status ) {
+
+ list( $code, $msg ) = $this->getErrorFromStatus( $status );
+ $this->dieUsage( $msg, $code );
+ }
+
+ // @codingStandardsIgnoreStart Allow long lines. Cannot split these.
+ /**
* Array that maps message keys to error messages. $1 and friends are replaced.
*/
public static $messageMap = array(
@@ -1263,74 +1230,243 @@ abstract class ApiBase extends ContextSource {
'unknownerror-nocode' => array( 'code' => 'unknownerror', 'info' => 'Unknown error' ),
// Messages from Title::getUserPermissionsErrors()
- 'ns-specialprotected' => array( 'code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited" ),
- 'protectedinterface' => array( 'code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages" ),
- 'namespaceprotected' => array( 'code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the \"\$1\" namespace" ),
- 'customcssprotected' => array( 'code' => 'customcssprotected', 'info' => "You're not allowed to edit custom CSS pages" ),
- 'customjsprotected' => array( 'code' => 'customjsprotected', 'info' => "You're not allowed to edit custom JavaScript pages" ),
- 'cascadeprotected' => array( 'code' => 'cascadeprotected', 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page" ),
- 'protectedpagetext' => array( 'code' => 'protectedpage', 'info' => "The \"\$1\" right is required to edit this page" ),
- 'protect-cantedit' => array( 'code' => 'cantedit', 'info' => "You can't protect this page because you can't edit it" ),
- 'badaccess-group0' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ), // Generic permission denied message
- 'badaccess-groups' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ),
- 'titleprotected' => array( 'code' => 'protectedtitle', 'info' => "This title has been protected from creation" ),
- 'nocreate-loggedin' => array( 'code' => 'cantcreate', 'info' => "You don't have permission to create new pages" ),
- 'nocreatetext' => array( 'code' => 'cantcreate-anon', 'info' => "Anonymous users can't create new pages" ),
- 'movenologintext' => array( 'code' => 'cantmove-anon', 'info' => "Anonymous users can't move pages" ),
- 'movenotallowed' => array( 'code' => 'cantmove', 'info' => "You don't have permission to move pages" ),
- 'confirmedittext' => array( 'code' => 'confirmemail', 'info' => "You must confirm your email address before you can edit" ),
- 'blockedtext' => array( 'code' => 'blocked', 'info' => "You have been blocked from editing" ),
- 'autoblockedtext' => array( 'code' => 'autoblocked', 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user" ),
+ 'ns-specialprotected' => array(
+ 'code' => 'unsupportednamespace',
+ 'info' => "Pages in the Special namespace can't be edited"
+ ),
+ 'protectedinterface' => array(
+ 'code' => 'protectednamespace-interface',
+ 'info' => "You're not allowed to edit interface messages"
+ ),
+ 'namespaceprotected' => array(
+ 'code' => 'protectednamespace',
+ 'info' => "You're not allowed to edit pages in the \"\$1\" namespace"
+ ),
+ 'customcssprotected' => array(
+ 'code' => 'customcssprotected',
+ 'info' => "You're not allowed to edit custom CSS pages"
+ ),
+ 'customjsprotected' => array(
+ 'code' => 'customjsprotected',
+ 'info' => "You're not allowed to edit custom JavaScript pages"
+ ),
+ 'cascadeprotected' => array(
+ 'code' => 'cascadeprotected',
+ 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page"
+ ),
+ 'protectedpagetext' => array(
+ 'code' => 'protectedpage',
+ 'info' => "The \"\$1\" right is required to edit this page"
+ ),
+ 'protect-cantedit' => array(
+ 'code' => 'cantedit',
+ 'info' => "You can't protect this page because you can't edit it"
+ ),
+ 'deleteprotected' => array(
+ 'code' => 'cantedit',
+ 'info' => "You can't delete this page because it has been protected"
+ ),
+ 'badaccess-group0' => array(
+ 'code' => 'permissiondenied',
+ 'info' => "Permission denied"
+ ), // Generic permission denied message
+ 'badaccess-groups' => array(
+ 'code' => 'permissiondenied',
+ 'info' => "Permission denied"
+ ),
+ 'titleprotected' => array(
+ 'code' => 'protectedtitle',
+ 'info' => "This title has been protected from creation"
+ ),
+ 'nocreate-loggedin' => array(
+ 'code' => 'cantcreate',
+ 'info' => "You don't have permission to create new pages"
+ ),
+ 'nocreatetext' => array(
+ 'code' => 'cantcreate-anon',
+ 'info' => "Anonymous users can't create new pages"
+ ),
+ 'movenologintext' => array(
+ 'code' => 'cantmove-anon',
+ 'info' => "Anonymous users can't move pages"
+ ),
+ 'movenotallowed' => array(
+ 'code' => 'cantmove',
+ 'info' => "You don't have permission to move pages"
+ ),
+ 'confirmedittext' => array(
+ 'code' => 'confirmemail',
+ 'info' => "You must confirm your email address before you can edit"
+ ),
+ 'blockedtext' => array(
+ 'code' => 'blocked',
+ 'info' => "You have been blocked from editing"
+ ),
+ 'autoblockedtext' => array(
+ 'code' => 'autoblocked',
+ 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user"
+ ),
// Miscellaneous interface messages
- 'actionthrottledtext' => array( 'code' => 'ratelimited', 'info' => "You've exceeded your rate limit. Please wait some time and try again" ),
- 'alreadyrolled' => array( 'code' => 'alreadyrolled', 'info' => "The page you tried to rollback was already rolled back" ),
- 'cantrollback' => array( 'code' => 'onlyauthor', 'info' => "The page you tried to rollback only has one author" ),
- 'readonlytext' => array( 'code' => 'readonly', 'info' => "The wiki is currently in read-only mode" ),
- 'sessionfailure' => array( 'code' => 'badtoken', 'info' => "Invalid token" ),
- 'cannotdelete' => array( 'code' => 'cantdelete', 'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else" ),
- 'notanarticle' => array( 'code' => 'missingtitle', 'info' => "The page you requested doesn't exist" ),
- 'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself" ),
- 'immobile_namespace' => array( 'code' => 'immobilenamespace', 'info' => "You tried to move pages from or to a namespace that is protected from moving" ),
- 'articleexists' => array( 'code' => 'articleexists', 'info' => "The destination article already exists and is not a redirect to the source article" ),
- 'protectedpage' => array( 'code' => 'protectedpage', 'info' => "You don't have permission to perform this move" ),
- 'hookaborted' => array( 'code' => 'hookaborted', 'info' => "The modification you tried to make was aborted by an extension hook" ),
- 'cantmove-titleprotected' => array( 'code' => 'protectedtitle', 'info' => "The destination article has been protected from creation" ),
- 'imagenocrossnamespace' => array( 'code' => 'nonfilenamespace', 'info' => "Can't move a file to a non-file namespace" ),
- 'imagetypemismatch' => array( 'code' => 'filetypemismatch', 'info' => "The new file extension doesn't match its type" ),
+ 'actionthrottledtext' => array(
+ 'code' => 'ratelimited',
+ 'info' => "You've exceeded your rate limit. Please wait some time and try again"
+ ),
+ 'alreadyrolled' => array(
+ 'code' => 'alreadyrolled',
+ 'info' => "The page you tried to rollback was already rolled back"
+ ),
+ 'cantrollback' => array(
+ 'code' => 'onlyauthor',
+ 'info' => "The page you tried to rollback only has one author"
+ ),
+ 'readonlytext' => array(
+ 'code' => 'readonly',
+ 'info' => "The wiki is currently in read-only mode"
+ ),
+ 'sessionfailure' => array(
+ 'code' => 'badtoken',
+ 'info' => "Invalid token" ),
+ 'cannotdelete' => array(
+ 'code' => 'cantdelete',
+ 'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else"
+ ),
+ 'notanarticle' => array(
+ 'code' => 'missingtitle',
+ 'info' => "The page you requested doesn't exist"
+ ),
+ 'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself"
+ ),
+ 'immobile_namespace' => array(
+ 'code' => 'immobilenamespace',
+ 'info' => "You tried to move pages from or to a namespace that is protected from moving"
+ ),
+ 'articleexists' => array(
+ 'code' => 'articleexists',
+ 'info' => "The destination article already exists and is not a redirect to the source article"
+ ),
+ 'protectedpage' => array(
+ 'code' => 'protectedpage',
+ 'info' => "You don't have permission to perform this move"
+ ),
+ 'hookaborted' => array(
+ 'code' => 'hookaborted',
+ 'info' => "The modification you tried to make was aborted by an extension hook"
+ ),
+ 'cantmove-titleprotected' => array(
+ 'code' => 'protectedtitle',
+ 'info' => "The destination article has been protected from creation"
+ ),
+ 'imagenocrossnamespace' => array(
+ 'code' => 'nonfilenamespace',
+ 'info' => "Can't move a file to a non-file namespace"
+ ),
+ 'imagetypemismatch' => array(
+ 'code' => 'filetypemismatch',
+ 'info' => "The new file extension doesn't match its type"
+ ),
// 'badarticleerror' => shouldn't happen
// 'badtitletext' => shouldn't happen
'ip_range_invalid' => array( 'code' => 'invalidrange', 'info' => "Invalid IP range" ),
- 'range_block_disabled' => array( 'code' => 'rangedisabled', 'info' => "Blocking IP ranges has been disabled" ),
- 'nosuchusershort' => array( 'code' => 'nosuchuser', 'info' => "The user you specified doesn't exist" ),
+ 'range_block_disabled' => array(
+ 'code' => 'rangedisabled',
+ 'info' => "Blocking IP ranges has been disabled"
+ ),
+ 'nosuchusershort' => array(
+ 'code' => 'nosuchuser',
+ 'info' => "The user you specified doesn't exist"
+ ),
'badipaddress' => array( 'code' => 'invalidip', 'info' => "Invalid IP address specified" ),
'ipb_expiry_invalid' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time" ),
- 'ipb_already_blocked' => array( 'code' => 'alreadyblocked', 'info' => "The user you tried to block was already blocked" ),
- 'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole." ),
- 'ipb_cant_unblock' => array( 'code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already" ),
- 'mailnologin' => array( 'code' => 'cantsend', 'info' => "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email" ),
- 'ipbblocked' => array( 'code' => 'ipbblocked', 'info' => 'You cannot block or unblock users while you are yourself blocked' ),
- 'ipbnounblockself' => array( 'code' => 'ipbnounblockself', 'info' => 'You are not allowed to unblock yourself' ),
- 'usermaildisabled' => array( 'code' => 'usermaildisabled', 'info' => "User email has been disabled" ),
- 'blockedemailuser' => array( 'code' => 'blockedfrommail', 'info' => "You have been blocked from sending email" ),
- 'notarget' => array( 'code' => 'notarget', 'info' => "You have not specified a valid target for this action" ),
- 'noemail' => array( 'code' => 'noemail', 'info' => "The user has not specified a valid email address, or has chosen not to receive email from other users" ),
- 'rcpatroldisabled' => array( 'code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki" ),
- 'markedaspatrollederror-noautopatrol' => array( 'code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes" ),
- 'delete-toobig' => array( 'code' => 'bigdelete', 'info' => "You can't delete this page because it has more than \$1 revisions" ),
- 'movenotallowedfile' => array( 'code' => 'cantmovefile', 'info' => "You don't have permission to move files" ),
- 'userrights-no-interwiki' => array( 'code' => 'nointerwikiuserrights', 'info' => "You don't have permission to change user rights on other wikis" ),
- 'userrights-nodatabase' => array( 'code' => 'nosuchdatabase', 'info' => "Database \"\$1\" does not exist or is not local" ),
+ 'ipb_already_blocked' => array(
+ 'code' => 'alreadyblocked',
+ 'info' => "The user you tried to block was already blocked"
+ ),
+ 'ipb_blocked_as_range' => array(
+ 'code' => 'blockedasrange',
+ 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole."
+ ),
+ 'ipb_cant_unblock' => array(
+ 'code' => 'cantunblock',
+ 'info' => "The block you specified was not found. It may have been unblocked already"
+ ),
+ 'mailnologin' => array(
+ 'code' => 'cantsend',
+ 'info' => "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email"
+ ),
+ 'ipbblocked' => array(
+ 'code' => 'ipbblocked',
+ 'info' => 'You cannot block or unblock users while you are yourself blocked'
+ ),
+ 'ipbnounblockself' => array(
+ 'code' => 'ipbnounblockself',
+ 'info' => 'You are not allowed to unblock yourself'
+ ),
+ 'usermaildisabled' => array(
+ 'code' => 'usermaildisabled',
+ 'info' => "User email has been disabled"
+ ),
+ 'blockedemailuser' => array(
+ 'code' => 'blockedfrommail',
+ 'info' => "You have been blocked from sending email"
+ ),
+ 'notarget' => array(
+ 'code' => 'notarget',
+ 'info' => "You have not specified a valid target for this action"
+ ),
+ 'noemail' => array(
+ 'code' => 'noemail',
+ 'info' => "The user has not specified a valid email address, or has chosen not to receive email from other users"
+ ),
+ 'rcpatroldisabled' => array(
+ 'code' => 'patroldisabled',
+ 'info' => "Patrolling is disabled on this wiki"
+ ),
+ 'markedaspatrollederror-noautopatrol' => array(
+ 'code' => 'noautopatrol',
+ 'info' => "You don't have permission to patrol your own changes"
+ ),
+ 'delete-toobig' => array(
+ 'code' => 'bigdelete',
+ 'info' => "You can't delete this page because it has more than \$1 revisions"
+ ),
+ 'movenotallowedfile' => array(
+ 'code' => 'cantmovefile',
+ 'info' => "You don't have permission to move files"
+ ),
+ 'userrights-no-interwiki' => array(
+ 'code' => 'nointerwikiuserrights',
+ 'info' => "You don't have permission to change user rights on other wikis"
+ ),
+ 'userrights-nodatabase' => array(
+ 'code' => 'nosuchdatabase',
+ 'info' => "Database \"\$1\" does not exist or is not local"
+ ),
'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
'summaryrequired' => array( 'code' => 'summaryrequired', 'info' => 'Summary required' ),
- 'import-rootpage-invalid' => array( 'code' => 'import-rootpage-invalid', 'info' => 'Root page is an invalid title' ),
- 'import-rootpage-nosubpage' => array( 'code' => 'import-rootpage-nosubpage', 'info' => 'Namespace "$1" of the root page does not allow subpages' ),
+ 'import-rootpage-invalid' => array(
+ 'code' => 'import-rootpage-invalid',
+ 'info' => 'Root page is an invalid title'
+ ),
+ 'import-rootpage-nosubpage' => array(
+ 'code' => 'import-rootpage-nosubpage',
+ 'info' => 'Namespace "$1" of the root page does not allow subpages'
+ ),
// API-specific messages
- 'readrequired' => array( 'code' => 'readapidenied', 'info' => "You need read permission to use this module" ),
- 'writedisabled' => array( 'code' => 'noapiwrite', 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file" ),
- 'writerequired' => array( 'code' => 'writeapidenied', 'info' => "You're not allowed to edit this wiki through the API" ),
+ 'readrequired' => array(
+ 'code' => 'readapidenied',
+ 'info' => "You need read permission to use this module"
+ ),
+ 'writedisabled' => array(
+ 'code' => 'noapiwrite',
+ 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file"
+ ),
+ 'writerequired' => array(
+ 'code' => 'writeapidenied',
+ 'info' => "You're not allowed to edit this wiki through the API"
+ ),
'missingparam' => array( 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ),
'invalidtitle' => array( 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ),
'nosuchpageid' => array( 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ),
@@ -1339,81 +1475,253 @@ abstract class ApiBase extends ContextSource {
'invaliduser' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
'invalidexpiry' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ),
'pastexpiry' => array( 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ),
- 'create-titleexists' => array( 'code' => 'create-titleexists', 'info' => "Existing titles can't be protected with 'create'" ),
- 'missingtitle-createonly' => array( 'code' => 'missingtitle-createonly', 'info' => "Missing titles can only be protected with 'create'" ),
- 'cantblock' => array( 'code' => 'cantblock', 'info' => "You don't have permission to block users" ),
- 'canthide' => array( 'code' => 'canthide', 'info' => "You don't have permission to hide user names from the block log" ),
- 'cantblock-email' => array( 'code' => 'cantblock-email', 'info' => "You don't have permission to block users from sending email through the wiki" ),
- 'unblock-notarget' => array( 'code' => 'notarget', 'info' => "Either the id or the user parameter must be set" ),
- 'unblock-idanduser' => array( 'code' => 'idanduser', 'info' => "The id and user parameters can't be used together" ),
- 'cantunblock' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to unblock users" ),
- 'cannotundelete' => array( 'code' => 'cantundelete', 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already" ),
- 'permdenied-undelete' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions" ),
- 'createonly-exists' => array( 'code' => 'articleexists', 'info' => "The article you tried to create has been created already" ),
- 'nocreate-missing' => array( 'code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist" ),
- 'cantchangecontentmodel' => array( 'code' => 'cantchangecontentmodel', 'info' => "You don't have permission to change the content model of a page" ),
- 'nosuchrcid' => array( 'code' => 'nosuchrcid', 'info' => "There is no change with rcid \"\$1\"" ),
- 'protect-invalidaction' => array( 'code' => 'protect-invalidaction', 'info' => "Invalid protection type \"\$1\"" ),
- 'protect-invalidlevel' => array( 'code' => 'protect-invalidlevel', 'info' => "Invalid protection level \"\$1\"" ),
- 'toofewexpiries' => array( 'code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed" ),
- 'cantimport' => array( 'code' => 'cantimport', 'info' => "You don't have permission to import pages" ),
- 'cantimport-upload' => array( 'code' => 'cantimport-upload', 'info' => "You don't have permission to import uploaded pages" ),
+ 'create-titleexists' => array(
+ 'code' => 'create-titleexists',
+ 'info' => "Existing titles can't be protected with 'create'"
+ ),
+ 'missingtitle-createonly' => array(
+ 'code' => 'missingtitle-createonly',
+ 'info' => "Missing titles can only be protected with 'create'"
+ ),
+ 'cantblock' => array( 'code' => 'cantblock',
+ 'info' => "You don't have permission to block users"
+ ),
+ 'canthide' => array(
+ 'code' => 'canthide',
+ 'info' => "You don't have permission to hide user names from the block log"
+ ),
+ 'cantblock-email' => array(
+ 'code' => 'cantblock-email',
+ 'info' => "You don't have permission to block users from sending email through the wiki"
+ ),
+ 'unblock-notarget' => array(
+ 'code' => 'notarget',
+ 'info' => "Either the id or the user parameter must be set"
+ ),
+ 'unblock-idanduser' => array(
+ 'code' => 'idanduser',
+ 'info' => "The id and user parameters can't be used together"
+ ),
+ 'cantunblock' => array(
+ 'code' => 'permissiondenied',
+ 'info' => "You don't have permission to unblock users"
+ ),
+ 'cannotundelete' => array(
+ 'code' => 'cantundelete',
+ 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"
+ ),
+ 'permdenied-undelete' => array(
+ 'code' => 'permissiondenied',
+ 'info' => "You don't have permission to restore deleted revisions"
+ ),
+ 'createonly-exists' => array(
+ 'code' => 'articleexists',
+ 'info' => "The article you tried to create has been created already"
+ ),
+ 'nocreate-missing' => array(
+ 'code' => 'missingtitle',
+ 'info' => "The article you tried to edit doesn't exist"
+ ),
+ 'cantchangecontentmodel' => array(
+ 'code' => 'cantchangecontentmodel',
+ 'info' => "You don't have permission to change the content model of a page"
+ ),
+ 'nosuchrcid' => array(
+ 'code' => 'nosuchrcid',
+ 'info' => "There is no change with rcid \"\$1\""
+ ),
+ 'protect-invalidaction' => array(
+ 'code' => 'protect-invalidaction',
+ 'info' => "Invalid protection type \"\$1\""
+ ),
+ 'protect-invalidlevel' => array(
+ 'code' => 'protect-invalidlevel',
+ 'info' => "Invalid protection level \"\$1\""
+ ),
+ 'toofewexpiries' => array(
+ 'code' => 'toofewexpiries',
+ 'info' => "\$1 expiry timestamps were provided where \$2 were needed"
+ ),
+ 'cantimport' => array(
+ 'code' => 'cantimport',
+ 'info' => "You don't have permission to import pages"
+ ),
+ 'cantimport-upload' => array(
+ 'code' => 'cantimport-upload',
+ 'info' => "You don't have permission to import uploaded pages"
+ ),
'importnofile' => array( 'code' => 'nofile', 'info' => "You didn't upload a file" ),
- 'importuploaderrorsize' => array( 'code' => 'filetoobig', 'info' => 'The file you uploaded is bigger than the maximum upload size' ),
- 'importuploaderrorpartial' => array( 'code' => 'partialupload', 'info' => 'The file was only partially uploaded' ),
- 'importuploaderrortemp' => array( 'code' => 'notempdir', 'info' => 'The temporary upload directory is missing' ),
- 'importcantopen' => array( 'code' => 'cantopenfile', 'info' => "Couldn't open the uploaded file" ),
- 'import-noarticle' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
- 'importbadinterwiki' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
- 'import-unknownerror' => array( 'code' => 'import-unknownerror', 'info' => "Unknown error on import: \"\$1\"" ),
- 'cantoverwrite-sharedfile' => array( 'code' => 'cantoverwrite-sharedfile', 'info' => 'The target file exists on a shared repository and you do not have permission to override it' ),
- 'sharedfile-exists' => array( 'code' => 'fileexists-sharedrepo-perm', 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.' ),
- 'mustbeposted' => array( 'code' => 'mustbeposted', 'info' => "The \$1 module requires a POST request" ),
- 'show' => array( 'code' => 'show', 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied' ),
- 'specialpage-cantexecute' => array( 'code' => 'specialpage-cantexecute', 'info' => "You don't have permission to view the results of this special page" ),
- 'invalidoldimage' => array( 'code' => 'invalidoldimage', 'info' => 'The oldimage parameter has invalid format' ),
- 'nodeleteablefile' => array( 'code' => 'nodeleteablefile', 'info' => 'No such old version of the file' ),
- 'fileexists-forbidden' => array( 'code' => 'fileexists-forbidden', 'info' => 'A file with name "$1" already exists, and cannot be overwritten.' ),
- 'fileexists-shared-forbidden' => array( 'code' => 'fileexists-shared-forbidden', 'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.' ),
- 'filerevert-badversion' => array( 'code' => 'filerevert-badversion', 'info' => 'There is no previous local version of this file with the provided timestamp.' ),
+ 'importuploaderrorsize' => array(
+ 'code' => 'filetoobig',
+ 'info' => 'The file you uploaded is bigger than the maximum upload size'
+ ),
+ 'importuploaderrorpartial' => array(
+ 'code' => 'partialupload',
+ 'info' => 'The file was only partially uploaded'
+ ),
+ 'importuploaderrortemp' => array(
+ 'code' => 'notempdir',
+ 'info' => 'The temporary upload directory is missing'
+ ),
+ 'importcantopen' => array(
+ 'code' => 'cantopenfile',
+ 'info' => "Couldn't open the uploaded file"
+ ),
+ 'import-noarticle' => array(
+ 'code' => 'badinterwiki',
+ 'info' => 'Invalid interwiki title specified'
+ ),
+ 'importbadinterwiki' => array(
+ 'code' => 'badinterwiki',
+ 'info' => 'Invalid interwiki title specified'
+ ),
+ 'import-unknownerror' => array(
+ 'code' => 'import-unknownerror',
+ 'info' => "Unknown error on import: \"\$1\""
+ ),
+ 'cantoverwrite-sharedfile' => array(
+ 'code' => 'cantoverwrite-sharedfile',
+ 'info' => 'The target file exists on a shared repository and you do not have permission to override it'
+ ),
+ 'sharedfile-exists' => array(
+ 'code' => 'fileexists-sharedrepo-perm',
+ 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.'
+ ),
+ 'mustbeposted' => array(
+ 'code' => 'mustbeposted',
+ 'info' => "The \$1 module requires a POST request"
+ ),
+ 'show' => array(
+ 'code' => 'show',
+ 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied'
+ ),
+ 'specialpage-cantexecute' => array(
+ 'code' => 'specialpage-cantexecute',
+ 'info' => "You don't have permission to view the results of this special page"
+ ),
+ 'invalidoldimage' => array(
+ 'code' => 'invalidoldimage',
+ 'info' => 'The oldimage parameter has invalid format'
+ ),
+ 'nodeleteablefile' => array(
+ 'code' => 'nodeleteablefile',
+ 'info' => 'No such old version of the file'
+ ),
+ 'fileexists-forbidden' => array(
+ 'code' => 'fileexists-forbidden',
+ 'info' => 'A file with name "$1" already exists, and cannot be overwritten.'
+ ),
+ 'fileexists-shared-forbidden' => array(
+ 'code' => 'fileexists-shared-forbidden',
+ 'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.'
+ ),
+ 'filerevert-badversion' => array(
+ 'code' => 'filerevert-badversion',
+ 'info' => 'There is no previous local version of this file with the provided timestamp.'
+ ),
// ApiEditPage messages
- 'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ),
- 'noimageredirect-logged' => array( 'code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects" ),
- 'spamdetected' => array( 'code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\"" ),
- 'contenttoobig' => array( 'code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes" ),
+ 'noimageredirect-anon' => array(
+ 'code' => 'noimageredirect-anon',
+ 'info' => "Anonymous users can't create image redirects"
+ ),
+ 'noimageredirect-logged' => array(
+ 'code' => 'noimageredirect',
+ 'info' => "You don't have permission to create image redirects"
+ ),
+ 'spamdetected' => array(
+ 'code' => 'spamdetected',
+ 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\""
+ ),
+ 'contenttoobig' => array(
+ 'code' => 'contenttoobig',
+ 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"
+ ),
'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ),
'noedit' => array( 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ),
- 'wasdeleted' => array( 'code' => 'pagedeleted', 'info' => "The page has been deleted since you fetched its timestamp" ),
- 'blankpage' => array( 'code' => 'emptypage', 'info' => "Creating new, empty pages is not allowed" ),
+ 'wasdeleted' => array(
+ 'code' => 'pagedeleted',
+ 'info' => "The page has been deleted since you fetched its timestamp"
+ ),
+ 'blankpage' => array(
+ 'code' => 'emptypage',
+ 'info' => "Creating new, empty pages is not allowed"
+ ),
'editconflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
'hashcheckfailed' => array( 'code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect" ),
- 'missingtext' => array( 'code' => 'notext', 'info' => "One of the text, appendtext, prependtext and undo parameters must be set" ),
- 'emptynewsection' => array( 'code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.' ),
- 'revwrongpage' => array( 'code' => 'revwrongpage', 'info' => "r\$1 is not a revision of \"\$2\"" ),
- 'undo-failure' => array( 'code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits' ),
+ 'missingtext' => array(
+ 'code' => 'notext',
+ 'info' => "One of the text, appendtext, prependtext and undo parameters must be set"
+ ),
+ 'emptynewsection' => array(
+ 'code' => 'emptynewsection',
+ 'info' => 'Creating empty new sections is not possible.'
+ ),
+ 'revwrongpage' => array(
+ 'code' => 'revwrongpage',
+ 'info' => "r\$1 is not a revision of \"\$2\""
+ ),
+ 'undo-failure' => array(
+ 'code' => 'undofailure',
+ 'info' => 'Undo failed due to conflicting intermediate edits'
+ ),
+ 'content-not-allowed-here' => array(
+ 'code' => 'contentnotallowedhere',
+ 'info' => 'Content model "$1" is not allowed at title "$2"'
+ ),
// Messages from WikiPage::doEit()
- 'edit-hook-aborted' => array( 'code' => 'edit-hook-aborted', 'info' => "Your edit was aborted by an ArticleSave hook" ),
- 'edit-gone-missing' => array( 'code' => 'edit-gone-missing', 'info' => "The page you tried to edit doesn't seem to exist anymore" ),
+ 'edit-hook-aborted' => array(
+ 'code' => 'edit-hook-aborted',
+ 'info' => "Your edit was aborted by an ArticleSave hook"
+ ),
+ 'edit-gone-missing' => array(
+ 'code' => 'edit-gone-missing',
+ 'info' => "The page you tried to edit doesn't seem to exist anymore"
+ ),
'edit-conflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
- 'edit-already-exists' => array( 'code' => 'edit-already-exists', 'info' => "It seems the page you tried to create already exist" ),
+ 'edit-already-exists' => array(
+ 'code' => 'edit-already-exists',
+ 'info' => 'It seems the page you tried to create already exist'
+ ),
// uploadMsgs
'invalid-file-key' => array( 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ),
'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
- 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
- 'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ),
- 'copyuploadbaddomain' => array( 'code' => 'copyuploadbaddomain', 'info' => 'Uploads by URL are not allowed from this domain.' ),
- 'copyuploadbadurl' => array( 'code' => 'copyuploadbadurl', 'info' => 'Upload not allowed from this URL.' ),
-
- 'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
+ 'uploaddisabled' => array(
+ 'code' => 'uploaddisabled',
+ 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true'
+ ),
+ 'copyuploaddisabled' => array(
+ 'code' => 'copyuploaddisabled',
+ 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.'
+ ),
+ 'copyuploadbaddomain' => array(
+ 'code' => 'copyuploadbaddomain',
+ 'info' => 'Uploads by URL are not allowed from this domain.'
+ ),
+ 'copyuploadbadurl' => array(
+ 'code' => 'copyuploadbadurl',
+ 'info' => 'Upload not allowed from this URL.'
+ ),
+
+ 'filename-tooshort' => array(
+ 'code' => 'filename-tooshort',
+ 'info' => 'The filename is too short'
+ ),
'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ),
- 'illegal-filename' => array( 'code' => 'illegal-filename', 'info' => 'The filename is not allowed' ),
- 'filetype-missing' => array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
+ 'illegal-filename' => array(
+ 'code' => 'illegal-filename',
+ 'info' => 'The filename is not allowed'
+ ),
+ 'filetype-missing' => array(
+ 'code' => 'filetype-missing',
+ 'info' => 'The file is missing an extension'
+ ),
'mustbeloggedin' => array( 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' )
);
+ // @codingStandardsIgnoreEnd
/**
* Helper function for readonly errors
@@ -1426,7 +1734,7 @@ abstract class ApiBase extends ContextSource {
/**
* Output the error message related to a certain array
- * @param $error (array|string) Element of a getUserPermissionsErrors()-style array
+ * @param array|string $error Element of a getUserPermissionsErrors()-style array
*/
public function dieUsageMsg( $error ) {
# most of the time we send a 1 element, so we might as well send it as
@@ -1441,26 +1749,25 @@ abstract class ApiBase extends ContextSource {
/**
* Will only set a warning instead of failing if the global $wgDebugAPI
* is set to true. Otherwise behaves exactly as dieUsageMsg().
- * @param $error (array|string) Element of a getUserPermissionsErrors()-style array
+ * @param array|string $error Element of a getUserPermissionsErrors()-style array
* @since 1.21
*/
public function dieUsageMsgOrDebug( $error ) {
- global $wgDebugAPI;
- if ( $wgDebugAPI !== true ) {
+ if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
$this->dieUsageMsg( $error );
- } else {
- if ( is_string( $error ) ) {
- $error = array( $error );
- }
- $parsed = $this->parseMsg( $error );
- $this->setWarning( '$wgDebugAPI: ' . $parsed['code']
- . ' - ' . $parsed['info'] );
}
+
+ if ( is_string( $error ) ) {
+ $error = array( $error );
+ }
+ $parsed = $this->parseMsg( $error );
+ $this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] );
}
/**
- * Die with the $prefix.'badcontinue' error. This call is common enough to make it into the base method.
- * @param $condition boolean will only die if this value is true
+ * Die with the $prefix.'badcontinue' error. This call is common enough to
+ * make it into the base method.
+ * @param bool $condition Will only die if this value is true
* @since 1.21
*/
protected function dieContinueUsageIf( $condition ) {
@@ -1502,193 +1809,311 @@ abstract class ApiBase extends ContextSource {
* Internal code errors should be reported with this method
* @param string $method Method or function name
* @param string $message Error message
+ * @throws MWException
*/
protected static function dieDebug( $method, $message ) {
throw new MWException( "Internal error in $method: $message" );
}
- /**
- * Indicates if this module needs maxlag to be checked
- * @return bool
- */
- public function shouldCheckMaxlag() {
- return true;
- }
+ /**@}*/
- /**
- * Indicates whether this module requires read rights
- * @return bool
+ /************************************************************************//**
+ * @name Help message generation
+ * @{
*/
- public function isReadMode() {
- return true;
- }
- /**
- * Indicates whether this module requires write mode
- * @return bool
- */
- public function isWriteMode() {
- return false;
- }
/**
- * Indicates whether this module must be called with a POST request
- * @return bool
+ * Generates help message for this module, or false if there is no description
+ * @return string|bool
*/
- public function mustBePosted() {
- return false;
- }
+ public function makeHelpMsg() {
+ static $lnPrfx = "\n ";
- /**
- * Returns whether this module requires a token to execute
- * It is used to show possible errors in action=paraminfo
- * see bug 25248
- * @return bool
- */
- public function needsToken() {
- return false;
- }
+ $msg = $this->getFinalDescription();
- /**
- * Returns the token salt if there is one,
- * '' if the module doesn't require a salt,
- * else false if the module doesn't need a token
- * You have also to override needsToken()
- * Value is passed to User::getEditToken
- * @return bool|string|array
- */
- public function getTokenSalt() {
- return false;
- }
+ if ( $msg !== false ) {
- /**
- * Gets the user for whom to get the watchlist
- *
- * @param $params array
- * @return User
- */
- public function getWatchlistUser( $params ) {
- if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
- $user = User::newFromName( $params['owner'], false );
- if ( !( $user && $user->getId() ) ) {
- $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
+ if ( !is_array( $msg ) ) {
+ $msg = array(
+ $msg
+ );
}
- $token = $user->getOption( 'watchlisttoken' );
- if ( $token == '' || $token != $params['token'] ) {
- $this->dieUsage( 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', 'bad_wltoken' );
+ $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
+
+ $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
+
+ if ( $this->isReadMode() ) {
+ $msg .= "\nThis module requires read rights";
}
- } else {
- if ( !$this->getUser()->isLoggedIn() ) {
- $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
+ if ( $this->isWriteMode() ) {
+ $msg .= "\nThis module requires write rights";
}
- if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
- $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
+ if ( $this->mustBePosted() ) {
+ $msg .= "\nThis module only accepts POST requests";
+ }
+ if ( $this->isReadMode() || $this->isWriteMode() ||
+ $this->mustBePosted()
+ ) {
+ $msg .= "\n";
+ }
+
+ // Parameters
+ $paramsMsg = $this->makeHelpMsgParameters();
+ if ( $paramsMsg !== false ) {
+ $msg .= "Parameters:\n$paramsMsg";
+ }
+
+ $examples = $this->getExamples();
+ if ( $examples ) {
+ if ( !is_array( $examples ) ) {
+ $examples = array(
+ $examples
+ );
+ }
+ $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
+ foreach ( $examples as $k => $v ) {
+ if ( is_numeric( $k ) ) {
+ $msg .= " $v\n";
+ } else {
+ if ( is_array( $v ) ) {
+ $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
+ } else {
+ $msgExample = " $v";
+ }
+ $msgExample .= ":";
+ $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n $k\n";
+ }
+ }
}
- $user = $this->getUser();
}
- return $user;
+
+ return $msg;
}
/**
- * @return bool|string|array Returns a false if the module has no help url, else returns a (array of) string
+ * @param string $item
+ * @return string
*/
- public function getHelpUrls() {
- return false;
+ private function indentExampleText( $item ) {
+ return " " . $item;
}
/**
- * Returns a list of all possible errors returned by the module
- *
- * Don't call this function directly: use getFinalPossibleErrors() to allow
- * hooks to modify parameters as needed.
- *
- * @return array in the format of array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
+ * @param string $prefix Text to split output items
+ * @param string $title What is being output
+ * @param string|array $input
+ * @return string
*/
- public function getPossibleErrors() {
- $ret = array();
-
- $params = $this->getFinalParams();
- if ( $params ) {
- foreach ( $params as $paramName => $paramSettings ) {
- if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] ) && $paramSettings[ApiBase::PARAM_REQUIRED] ) {
- $ret[] = array( 'missingparam', $paramName );
- }
- }
- if ( array_key_exists( 'continue', $params ) ) {
- $ret[] = array(
- 'code' => 'badcontinue',
- 'info' => 'Invalid continue param. You should pass the original value returned by the previous query'
- );
- }
- }
-
- if ( $this->mustBePosted() ) {
- $ret[] = array( 'mustbeposted', $this->getModuleName() );
- }
-
- if ( $this->isReadMode() ) {
- $ret[] = array( 'readrequired' );
+ protected function makeHelpArrayToString( $prefix, $title, $input ) {
+ if ( $input === false ) {
+ return '';
}
-
- if ( $this->isWriteMode() ) {
- $ret[] = array( 'writerequired' );
- $ret[] = array( 'writedisabled' );
+ if ( !is_array( $input ) ) {
+ $input = array( $input );
}
- if ( $this->needsToken() ) {
- if ( !isset( $params['token'][ApiBase::PARAM_REQUIRED] )
- || !$params['token'][ApiBase::PARAM_REQUIRED]
- ) {
- // Add token as possible missing parameter, if not already done
- $ret[] = array( 'missingparam', 'token' );
+ if ( count( $input ) > 0 ) {
+ if ( $title ) {
+ $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
+ } else {
+ $msg = ' ';
}
- $ret[] = array( 'sessionfailure' );
+ $msg .= implode( $prefix, $input ) . "\n";
+
+ return $msg;
}
- return $ret;
+ return '';
}
/**
- * Get final list of possible errors, after hooks have had a chance to
- * tweak it as needed.
- *
- * @return array
- * @since 1.22
+ * Generates the parameter descriptions for this module, to be displayed in the
+ * module's help.
+ * @return string|bool
*/
- public function getFinalPossibleErrors() {
- $possibleErrors = $this->getPossibleErrors();
- wfRunHooks( 'APIGetPossibleErrors', array( $this, &$possibleErrors ) );
- return $possibleErrors;
- }
+ public function makeHelpMsgParameters() {
+ $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+ if ( $params ) {
- /**
- * Parses a list of errors into a standardised format
- * @param array $errors List of errors. Items can be in the for array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
- * @return array Parsed list of errors with items in the form array( 'code' => ..., 'info' => ... )
- */
- public function parseErrors( $errors ) {
- $ret = array();
+ $paramsDescription = $this->getFinalParamDescription();
+ $msg = '';
+ $paramPrefix = "\n" . str_repeat( ' ', 24 );
+ $descWordwrap = "\n" . str_repeat( ' ', 28 );
+ foreach ( $params as $paramName => $paramSettings ) {
+ $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
+ if ( is_array( $desc ) ) {
+ $desc = implode( $paramPrefix, $desc );
+ }
- foreach ( $errors as $row ) {
- if ( isset( $row['code'] ) && isset( $row['info'] ) ) {
- $ret[] = $row;
- } else {
- $ret[] = $this->parseMsg( $row );
+ //handle shorthand
+ if ( !is_array( $paramSettings ) ) {
+ $paramSettings = array(
+ self::PARAM_DFLT => $paramSettings,
+ );
+ }
+
+ //handle missing type
+ if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
+ $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
+ ? $paramSettings[ApiBase::PARAM_DFLT]
+ : null;
+ if ( is_bool( $dflt ) ) {
+ $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
+ } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
+ $paramSettings[ApiBase::PARAM_TYPE] = 'string';
+ } elseif ( is_int( $dflt ) ) {
+ $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
+ }
+ }
+
+ if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
+ && $paramSettings[self::PARAM_DEPRECATED]
+ ) {
+ $desc = "DEPRECATED! $desc";
+ }
+
+ if ( isset( $paramSettings[self::PARAM_REQUIRED] )
+ && $paramSettings[self::PARAM_REQUIRED]
+ ) {
+ $desc .= $paramPrefix . "This parameter is required";
+ }
+
+ $type = isset( $paramSettings[self::PARAM_TYPE] )
+ ? $paramSettings[self::PARAM_TYPE]
+ : null;
+ if ( isset( $type ) ) {
+ $hintPipeSeparated = true;
+ $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
+ ? $paramSettings[self::PARAM_ISMULTI]
+ : false;
+ if ( $multi ) {
+ $prompt = 'Values (separate with \'|\'): ';
+ } else {
+ $prompt = 'One value: ';
+ }
+
+ if ( $type === 'submodule' ) {
+ $type = $this->getModuleManager()->getNames( $paramName );
+ sort( $type );
+ }
+ if ( is_array( $type ) ) {
+ $choices = array();
+ $nothingPrompt = '';
+ foreach ( $type as $t ) {
+ if ( $t === '' ) {
+ $nothingPrompt = 'Can be empty, or ';
+ } else {
+ $choices[] = $t;
+ }
+ }
+ $desc .= $paramPrefix . $nothingPrompt . $prompt;
+ $choicesstring = implode( ', ', $choices );
+ $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
+ $hintPipeSeparated = false;
+ } else {
+ switch ( $type ) {
+ case 'namespace':
+ // Special handling because namespaces are
+ // type-limited, yet they are not given
+ $desc .= $paramPrefix . $prompt;
+ $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
+ 100, $descWordwrap );
+ $hintPipeSeparated = false;
+ break;
+ case 'limit':
+ $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
+ if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
+ $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
+ }
+ $desc .= ' allowed';
+ break;
+ case 'integer':
+ $s = $multi ? 's' : '';
+ $hasMin = isset( $paramSettings[self::PARAM_MIN] );
+ $hasMax = isset( $paramSettings[self::PARAM_MAX] );
+ if ( $hasMin || $hasMax ) {
+ if ( !$hasMax ) {
+ $intRangeStr = "The value$s must be no less than " .
+ "{$paramSettings[self::PARAM_MIN]}";
+ } elseif ( !$hasMin ) {
+ $intRangeStr = "The value$s must be no more than " .
+ "{$paramSettings[self::PARAM_MAX]}";
+ } else {
+ $intRangeStr = "The value$s must be between " .
+ "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
+ }
+
+ $desc .= $paramPrefix . $intRangeStr;
+ }
+ break;
+ case 'upload':
+ $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
+ break;
+ }
+ }
+
+ if ( $multi ) {
+ if ( $hintPipeSeparated ) {
+ $desc .= $paramPrefix . "Separate values with '|'";
+ }
+
+ $isArray = is_array( $type );
+ if ( !$isArray
+ || $isArray && count( $type ) > self::LIMIT_SML1
+ ) {
+ $desc .= $paramPrefix . "Maximum number of values " .
+ self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
+ }
+ }
+ }
+
+ $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
+ if ( !is_null( $default ) && $default !== false ) {
+ $desc .= $paramPrefix . "Default: $default";
+ }
+
+ $msg .= sprintf( " %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
}
+
+ return $msg;
}
- return $ret;
+
+ return false;
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Profiling
+ * @{
+ */
+
/**
* Profiling: total module execution time
*/
private $mTimeIn = 0, $mModuleTime = 0;
/**
+ * Get the name of the module as shown in the profiler log
+ *
+ * @param DatabaseBase|bool $db
+ *
+ * @return string
+ */
+ public function getModuleProfileName( $db = false ) {
+ if ( $db ) {
+ return 'API:' . $this->mModuleName . '-DB';
+ }
+
+ return 'API:' . $this->mModuleName;
+ }
+
+ /**
* Start module profiling
*/
public function profileIn() {
if ( $this->mTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called twice without calling profileOut()' );
+ ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileOut()' );
}
$this->mTimeIn = microtime( true );
wfProfileIn( $this->getModuleProfileName() );
@@ -1699,10 +2124,13 @@ abstract class ApiBase extends ContextSource {
*/
public function profileOut() {
if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called without calling profileIn() first' );
+ ApiBase::dieDebug( __METHOD__, 'Called without calling profileIn() first' );
}
if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'must be called after database profiling is done with profileDBOut()' );
+ ApiBase::dieDebug(
+ __METHOD__,
+ 'Must be called after database profiling is done with profileDBOut()'
+ );
}
$this->mModuleTime += microtime( true ) - $this->mTimeIn;
@@ -1729,8 +2157,9 @@ abstract class ApiBase extends ContextSource {
*/
public function getProfileTime() {
if ( $this->mTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called without calling profileOut() first' );
+ ApiBase::dieDebug( __METHOD__, 'Called without calling profileOut() first' );
}
+
return $this->mModuleTime;
}
@@ -1744,10 +2173,13 @@ abstract class ApiBase extends ContextSource {
*/
public function profileDBIn() {
if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'must be called while profiling the entire module with profileIn()' );
+ ApiBase::dieDebug(
+ __METHOD__,
+ 'Must be called while profiling the entire module with profileIn()'
+ );
}
if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called twice without calling profileDBOut()' );
+ ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileDBOut()' );
}
$this->mDBTimeIn = microtime( true );
wfProfileIn( $this->getModuleProfileName( true ) );
@@ -1758,10 +2190,11 @@ abstract class ApiBase extends ContextSource {
*/
public function profileDBOut() {
if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'must be called while profiling the entire module with profileIn()' );
+ ApiBase::dieDebug( __METHOD__, 'Must be called while profiling ' .
+ 'the entire module with profileIn()' );
}
if ( $this->mDBTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called without calling profileDBIn() first' );
+ ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBIn() first' );
}
$time = microtime( true ) - $this->mDBTimeIn;
@@ -1778,36 +2211,168 @@ abstract class ApiBase extends ContextSource {
*/
public function getProfileDBTime() {
if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called without calling profileDBOut() first' );
+ ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBOut() first' );
}
+
return $this->mDBTime;
}
/**
- * Gets a default slave database connection object
- * @return DatabaseBase
+ * Write logging information for API features to a debug log, for usage
+ * analysis.
+ * @param string $feature Feature being used.
*/
- protected function getDB() {
- if ( !isset( $this->mSlaveDB ) ) {
- $this->profileDBIn();
- $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
- $this->profileDBOut();
- }
- return $this->mSlaveDB;
+ protected function logFeatureUsage( $feature ) {
+ $request = $this->getRequest();
+ $s = '"' . addslashes( $feature ) . '"' .
+ ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
+ ' "' . $request->getIP() . '"' .
+ ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
+ ' "' . addslashes( $request->getHeader( 'User-agent' ) ) . '"';
+ wfDebugLog( 'api-feature-usage', $s, 'private' );
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Deprecated
+ * @{
+ */
+
+ /// @deprecated since 1.24
+ const PROP_ROOT = 'ROOT';
+ /// @deprecated since 1.24
+ const PROP_LIST = 'LIST';
+ /// @deprecated since 1.24
+ const PROP_TYPE = 0;
+ /// @deprecated since 1.24
+ const PROP_NULLABLE = 1;
+
/**
- * Debugging function that prints a value and an optional backtrace
- * @param $value mixed Value to print
- * @param string $name Description of the printed value
- * @param bool $backtrace If true, print a backtrace
+ * Formerly returned a string that identifies the version of the extending
+ * class. Typically included the class name, the svn revision, timestamp,
+ * and last author. Usually done with SVN's Id keyword
+ *
+ * @deprecated since 1.21, version string is no longer supported
+ * @return string
*/
- public static function debugPrint( $value, $name = 'unknown', $backtrace = false ) {
- print "\n\n<pre><b>Debugging value '$name':</b>\n\n";
- var_export( $value );
- if ( $backtrace ) {
- print "\n" . wfBacktrace();
- }
- print "\n</pre>\n";
+ public function getVersion() {
+ wfDeprecated( __METHOD__, '1.21' );
+ return '';
+ }
+
+ /**
+ * Formerly used to fetch a list of possible properites in the result,
+ * somehow organized with respect to the prop parameter that causes them to
+ * be returned. The specific semantics of the return value was never
+ * specified. Since this was never possible to be accurately updated, it
+ * has been removed.
+ *
+ * @deprecated since 1.24
+ * @return array|bool
+ */
+ protected function getResultProperties() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return false;
+ }
+
+ /**
+ * @see self::getResultProperties()
+ * @deprecated since 1.24
+ * @return array|bool
+ */
+ public function getFinalResultProperties() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getResultProperties()
+ * @deprecated since 1.24
+ */
+ protected static function addTokenProperties( &$props, $tokenFunctions ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getRequireOnlyOneParameterErrorMessages( $params ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getRequireMaxOneParameterErrorMessages( $params ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getRequireAtLeastOneParameterErrorMessages( $params ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getTitleOrPageIdErrorMessage() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * This formerly attempted to return a list of all possible errors returned
+ * by the module. However, this was impossible to maintain in many cases
+ * since errors could come from other areas of MediaWiki and in some cases
+ * from arbitrary extension hooks. Since a partial list claiming to be
+ * comprehensive is unlikely to be useful, it was removed.
+ *
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getPossibleErrors() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
}
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getFinalPossibleErrors() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function parseErrors( $errors ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**@}*/
}
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index 975153ac..07f62c66 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -55,8 +55,11 @@ class ApiBlock extends ApiBase {
}
$target = User::newFromName( $params['user'] );
- // Bug 38633 - if the target is a user (not an IP address), but it doesn't exist or is unusable, error.
- if ( $target instanceof User && ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) ) ) {
+ // Bug 38633 - if the target is a user (not an IP address), but it
+ // doesn't exist or is unusable, error.
+ if ( $target instanceof User &&
+ ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) )
+ ) {
$this->dieUsageMsg( array( 'nosuchuser', $params['user'] ) );
}
@@ -149,7 +152,6 @@ class ApiBlock extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => null,
'expiry' => 'never',
'reason' => '',
'anononly' => false,
@@ -166,80 +168,35 @@ class ApiBlock extends ApiBase {
public function getParamDescription() {
return array(
'user' => 'Username, IP address or IP range you want to block',
- 'token' => 'A block token previously obtained through prop=info',
- 'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.',
+ 'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. ' .
+ 'If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.',
'reason' => 'Reason for block',
'anononly' => 'Block anonymous users only (i.e. disable anonymous edits for this IP)',
'nocreate' => 'Prevent account creation',
- 'autoblock' => 'Automatically block the last used IP address, and any subsequent IP addresses they try to login from',
- 'noemail' => 'Prevent user from sending email through the wiki. (Requires the "blockemail" right.)',
+ 'autoblock' => 'Automatically block the last used IP address, and ' .
+ 'any subsequent IP addresses they try to login from',
+ 'noemail'
+ => 'Prevent user from sending email through the wiki. (Requires the "blockemail" right.)',
'hidename' => 'Hide the username from the block log. (Requires the "hideuser" right.)',
- 'allowusertalk' => 'Allow the user to edit their own talk page (depends on $wgBlockAllowsUTEdit)',
+ 'allowusertalk'
+ => 'Allow the user to edit their own talk page (depends on $wgBlockAllowsUTEdit)',
'reblock' => 'If the user is already blocked, overwrite the existing block',
'watchuser' => 'Watch the user/IP\'s user and talk pages',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'user' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'userID' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'expiry' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'id' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'reason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'anononly' => 'boolean',
- 'nocreate' => 'boolean',
- 'autoblock' => 'boolean',
- 'noemail' => 'boolean',
- 'hidename' => 'boolean',
- 'allowusertalk' => 'boolean',
- 'watchuser' => 'boolean'
- )
- );
- }
-
public function getDescription() {
- return 'Block a user';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'cantblock' ),
- array( 'canthide' ),
- array( 'cantblock-email' ),
- array( 'ipbblocked' ),
- array( 'ipbnounblockself' ),
- ) );
+ return 'Block a user.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike',
- 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail='
+ 'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike&token=123ABC',
+ 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC'
);
}
diff --git a/includes/api/ApiClearHasMsg.php b/includes/api/ApiClearHasMsg.php
new file mode 100644
index 00000000..32e20e80
--- /dev/null
+++ b/includes/api/ApiClearHasMsg.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Created on August 26, 2014
+ *
+ * Copyright © 2014 Petr Bena (benapetr@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
+ */
+
+/**
+ * API module that clears the hasmsg flag for current user
+ * @ingroup API
+ */
+class ApiClearHasMsg extends ApiBase {
+ public function execute() {
+ $user = $this->getUser();
+ $user->setNewtalk( false );
+ $this->getResult()->addValue( null, $this->getModuleName(), 'success' );
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function mustBePosted() {
+ return false;
+ }
+
+ public function getDescription() {
+ return array( 'Clears the hasmsg flag for current user.' );
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=clearhasmsg' => 'Clears the hasmsg flag for current user',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:ClearHasMsg';
+ }
+}
diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php
index 1e35c349..48559268 100644
--- a/includes/api/ApiComparePages.php
+++ b/includes/api/ApiComparePages.php
@@ -65,19 +65,22 @@ class ApiComparePages extends ApiBase {
$difftext = $de->getDiffBody();
if ( $difftext === false ) {
- $this->dieUsage( 'The diff cannot be retrieved. ' .
- 'Maybe one or both revisions do not exist or you do not have permission to view them.', 'baddiff' );
- } else {
- ApiResult::setContent( $vals, $difftext );
+ $this->dieUsage(
+ 'The diff cannot be retrieved. Maybe one or both revisions do ' .
+ 'not exist or you do not have permission to view them.',
+ 'baddiff'
+ );
}
+ ApiResult::setContent( $vals, $difftext );
+
$this->getResult()->addValue( null, $this->getModuleName(), $vals );
}
/**
- * @param $revision int
- * @param $titleText string
- * @param $titleId int
+ * @param int $revision
+ * @param string $titleText
+ * @param int $titleId
* @return int
*/
private function revisionOrTitleOrId( $revision, $titleText, $titleId ) {
@@ -88,15 +91,20 @@ class ApiComparePages extends ApiBase {
if ( !$title || $title->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $titleText ) );
}
+
return $title->getLatestRevID();
} elseif ( $titleId ) {
$title = Title::newFromID( $titleId );
if ( !$title ) {
$this->dieUsageMsg( array( 'nosuchpageid', $titleId ) );
}
+
return $title->getLatestRevID();
}
- $this->dieUsage( 'inputneeded', 'A title, a page ID, or a revision number is needed for both the from and the to parameters' );
+ $this->dieUsage(
+ 'A title, a page ID, or a revision number is needed for both the from and the to parameters',
+ 'inputneeded'
+ );
}
public function getAllowedParams() {
@@ -129,40 +137,13 @@ class ApiComparePages extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'fromtitle' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'fromrevid' => 'integer',
- 'totitle' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'torevid' => 'integer',
- '*' => 'string'
- )
- );
- }
-
public function getDescription() {
return array(
- 'Get the difference between 2 pages',
- 'You must pass a revision number or a page title or a page ID id for each part (1 and 2)'
+ 'Get the difference between 2 pages.',
+ 'You must pass a revision number or a page title or a page ID id for each part (1 and 2).'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'inputneeded', 'info' => 'A title or a revision is needed' ),
- array( 'invalidtitle', 'title' ),
- array( 'nosuchpageid', 'pageid' ),
- array( 'code' => 'baddiff', 'info' => 'The diff cannot be retrieved. Maybe one or both revisions do not exist or you do not have permission to view them.' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=compare&fromrev=1&torev=2' => 'Create a diff between revision 1 and 2',
diff --git a/includes/api/ApiCreateAccount.php b/includes/api/ApiCreateAccount.php
index 0e752c56..2ce532b9 100644
--- a/includes/api/ApiCreateAccount.php
+++ b/includes/api/ApiCreateAccount.php
@@ -39,7 +39,10 @@ class ApiCreateAccount extends ApiBase {
// Use userCan in order to hit GlobalBlock checks (according to Special:userlogin)
$loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
if ( !$loginTitle->userCan( 'createaccount', $this->getUser() ) ) {
- $this->dieUsage( 'You do not have the right to create a new account', 'permdenied-createaccount' );
+ $this->dieUsage(
+ 'You do not have the right to create a new account',
+ 'permdenied-createaccount'
+ );
}
if ( $this->getUser()->isBlockedFromCreateAccount() ) {
$this->dieUsage( 'You cannot create a new account because you are blocked', 'blocked' );
@@ -80,13 +83,13 @@ class ApiCreateAccount extends ApiBase {
$loginForm = new LoginForm();
$loginForm->setContext( $context );
+ wfRunHooks( 'AddNewAccountApiForm', array( $this, $loginForm ) );
$loginForm->load();
$status = $loginForm->addNewaccountInternal();
$result = array();
if ( $status->isGood() ) {
// Success!
- global $wgEmailAuthentication;
$user = $status->getValue();
if ( $params['language'] ) {
@@ -96,8 +99,13 @@ class ApiCreateAccount extends ApiBase {
if ( $params['mailpassword'] ) {
// If mailpassword was set, disable the password and send an email.
$user->setPassword( null );
- $status->merge( $loginForm->mailPasswordInternal( $user, false, 'createaccount-title', 'createaccount-text' ) );
- } elseif ( $wgEmailAuthentication && Sanitizer::validateEmail( $user->getEmail() ) ) {
+ $status->merge( $loginForm->mailPasswordInternal(
+ $user,
+ false,
+ 'createaccount-title',
+ 'createaccount-text'
+ ) );
+ } elseif ( $this->getConfig()->get( 'EmailAuthentication' ) && Sanitizer::validateEmail( $user->getEmail() ) ) {
// Send out an email authentication message if needed
$status->merge( $user->sendConfirmationMail() );
}
@@ -129,13 +137,13 @@ class ApiCreateAccount extends ApiBase {
// since not having the correct token is part of the normal
// flow of events.
$result['token'] = LoginForm::getCreateaccountToken();
- $result['result'] = 'needtoken';
+ $result['result'] = 'NeedToken';
} elseif ( !$status->isOK() ) {
// There was an error. Die now.
$this->dieStatus( $status );
} elseif ( !$status->isGood() ) {
// Status is not good, but OK. This means warnings.
- $result['result'] = 'warning';
+ $result['result'] = 'Warning';
// Add any warnings to the result
$warnings = $status->getErrorsByType( 'warning' );
@@ -148,9 +156,12 @@ class ApiCreateAccount extends ApiBase {
}
} else {
// Everything was fine.
- $result['result'] = 'success';
+ $result['result'] = 'Success';
}
+ // Give extensions a chance to modify the API result data
+ wfRunHooks( 'AddNewAccountApiResult', array( $this, $loginForm, &$result ) );
+
$apiResult->addValue( null, 'createaccount', $result );
}
@@ -171,7 +182,6 @@ class ApiCreateAccount extends ApiBase {
}
public function getAllowedParams() {
- global $wgEmailConfirmToEdit;
return array(
'name' => array(
ApiBase::PARAM_TYPE => 'user',
@@ -182,7 +192,7 @@ class ApiCreateAccount extends ApiBase {
'token' => null,
'email' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => $wgEmailConfirmToEdit
+ ApiBase::PARAM_REQUIRED => $this->getConfig()->get( 'EmailConfirmToEdit' ),
),
'realname' => null,
'mailpassword' => array(
@@ -196,6 +206,7 @@ class ApiCreateAccount extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'name' => 'Username',
'password' => "Password (ignored if {$p}mailpassword is set)",
@@ -205,82 +216,9 @@ class ApiCreateAccount extends ApiBase {
'realname' => 'Real name of user (optional)',
'mailpassword' => 'If set to any value, a random password will be emailed to the user',
'reason' => 'Optional reason for creating the account to be put in the logs',
- 'language' => 'Language code to set as default for the user (optional, defaults to content language)'
- );
- }
-
- public function getResultProperties() {
- return array(
- 'createaccount' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'success',
- 'warning',
- 'needtoken'
- )
- ),
- 'username' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'userid' => array(
- ApiBase::PROP_TYPE => 'int',
- ApiBase::PROP_NULLABLE => true
- ),
- 'token' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- )
- );
- }
-
- public function getPossibleErrors() {
- // Note the following errors aren't possible and don't need to be listed:
- // sessionfailure, nocookiesfornew, badretype
- $localErrors = array(
- 'wrongpassword', // Actually caused by wrong domain field. Riddle me that...
- 'sorbs_create_account_reason',
- 'noname',
- 'userexists',
- 'password-name-match', // from User::getPasswordValidity
- 'password-login-forbidden', // from User::getPasswordValidity
- 'noemailtitle',
- 'invalidemailaddress',
- 'externaldberror',
- 'acct_creation_throttle_hit',
- );
-
- $errors = parent::getPossibleErrors();
- // All local errors are from LoginForm, which means they're actually message keys.
- foreach ( $localErrors as $error ) {
- $errors[] = array( 'code' => $error, 'info' => wfMessage( $error )->inLanguage( 'en' )->useDatabase( false )->parse() );
- }
-
- $errors[] = array(
- 'code' => 'permdenied-createaccount',
- 'info' => 'You do not have the right to create a new account'
- );
- $errors[] = array(
- 'code' => 'blocked',
- 'info' => 'You cannot create a new account because you are blocked'
- );
- $errors[] = array(
- 'code' => 'aborted',
- 'info' => 'Account creation aborted by hook (info may vary)'
- );
- $errors[] = array(
- 'code' => 'langinvalid',
- 'info' => 'Invalid language parameter'
- );
-
- // 'passwordtooshort' has parameters. :(
- global $wgMinimalPasswordLength;
- $errors[] = array(
- 'code' => 'passwordtooshort',
- 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->inLanguage( 'en' )->useDatabase( false )->parse()
+ 'language'
+ => 'Language code to set as default for the user (optional, defaults to content language)'
);
- return $errors;
}
public function getExamples() {
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index aea10482..abca8245 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -31,7 +31,6 @@
* @ingroup API
*/
class ApiDelete extends ApiBase {
-
/**
* Extracts the title, token, and reason from the request parameters and invokes
* the local delete() function with these as arguments. It does not make use of
@@ -52,7 +51,14 @@ class ApiDelete extends ApiBase {
$user = $this->getUser();
if ( $titleObj->getNamespace() == NS_FILE ) {
- $status = self::deleteFile( $pageObj, $user, $params['token'], $params['oldimage'], $reason, false );
+ $status = self::deleteFile(
+ $pageObj,
+ $user,
+ $params['token'],
+ $params['oldimage'],
+ $reason,
+ false
+ );
} else {
$status = self::delete( $pageObj, $user, $params['token'], $reason );
}
@@ -66,8 +72,10 @@ class ApiDelete extends ApiBase {
// Deprecated parameters
if ( $params['watch'] ) {
+ $this->logFeatureUsage( 'action=delete&watch' );
$watch = 'watch';
} elseif ( $params['unwatch'] ) {
+ $this->logFeatureUsage( 'action=delete&unwatch' );
$watch = 'unwatch';
} else {
$watch = $params['watchlist'];
@@ -83,9 +91,9 @@ class ApiDelete extends ApiBase {
}
/**
- * @param $title Title
- * @param $user User doing the action
- * @param $token String
+ * @param Title $title
+ * @param User $user User doing the action
+ * @param string $token
* @return array
*/
private static function getPermissionsError( $title, $user, $token ) {
@@ -96,10 +104,10 @@ class ApiDelete extends ApiBase {
/**
* We have our own delete() function, since Article.php's implementation is split in two phases
*
- * @param $page Page|WikiPage object to work on
- * @param $user User doing the action
- * @param string $token delete token (same as edit token)
- * @param string|null $reason reason for the deletion. Autogenerated if NULL
+ * @param Page|WikiPage $page Page or WikiPage object to work on
+ * @param User $user User doing the action
+ * @param string $token Delete token (same as edit token)
+ * @param string|null $reason Reason for the deletion. Autogenerated if null
* @return Status|array
*/
public static function delete( Page $page, User $user, $token, &$reason = null ) {
@@ -121,20 +129,23 @@ class ApiDelete extends ApiBase {
}
$error = '';
+
// Luckily, Article.php provides a reusable delete function that does the hard work for us
return $page->doDeleteArticleReal( $reason, false, 0, true, $error );
}
/**
- * @param $page WikiPage|Page object to work on
- * @param $user User doing the action
- * @param $token
- * @param $oldimage
- * @param $reason
- * @param $suppress bool
+ * @param Page $page Object to work on
+ * @param User $user User doing the action
+ * @param string $token Delete token (same as edit token)
+ * @param string $oldimage Archive name
+ * @param string $reason Reason for the deletion. Autogenerated if null.
+ * @param bool $suppress Whether to mark all deleted versions as restricted
* @return Status|array
*/
- public static function deleteFile( Page $page, User $user, $token, $oldimage, &$reason = null, $suppress = false ) {
+ public static function deleteFile( Page $page, User $user, $token, $oldimage,
+ &$reason = null, $suppress = false
+ ) {
$title = $page->getTitle();
$errors = self::getPermissionsError( $title, $user, $token );
if ( count( $errors ) ) {
@@ -159,6 +170,7 @@ class ApiDelete extends ApiBase {
if ( is_null( $reason ) ) { // Log and RC don't like null reasons
$reason = '';
}
+
return FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress, $user );
}
@@ -176,10 +188,6 @@ class ApiDelete extends ApiBase {
'pageid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => null,
'watch' => array(
ApiBase::PARAM_DFLT => false,
@@ -204,58 +212,33 @@ class ApiDelete extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'title' => "Title of the page you want to delete. Cannot be used together with {$p}pageid",
'pageid' => "Page ID of the page you want to delete. Cannot be used together with {$p}title",
- 'token' => 'A delete token previously retrieved through prop=info',
- 'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used',
+ 'reason'
+ => 'Reason for the deletion. If not set, an automatically generated reason will be used',
'watch' => 'Add the page to your watchlist',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
'unwatch' => 'Remove the page from your watchlist',
'oldimage' => 'The name of the old image to delete as provided by iiprop=archivename'
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'title' => 'string',
- 'reason' => 'string',
- 'logid' => 'integer'
- )
- );
- }
-
public function getDescription() {
- return 'Delete a page';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'notanarticle' ),
- array( 'hookaborted', 'error' ),
- array( 'delete-toobig', 'limit' ),
- array( 'cannotdelete', 'title' ),
- array( 'invalidoldimage' ),
- array( 'nodeleteablefile' ),
- )
- );
+ return 'Delete a page.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
'api.php?action=delete&title=Main%20Page&token=123ABC' => 'Delete the Main Page',
- 'api.php?action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move' => 'Delete the Main Page with the reason "Preparing for move"',
+ 'api.php?action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move'
+ => 'Delete the Main Page with the reason "Preparing for move"',
);
}
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index e5ef3b7e..6ea5d202 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -53,7 +53,7 @@ class ApiDisabled extends ApiBase {
}
public function getDescription() {
- return 'This module has been disabled';
+ return 'This module has been disabled.';
}
public function getExamples() {
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index 51c9efc6..a423b560 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -32,15 +32,14 @@
* @ingroup API
*/
class ApiEditPage extends ApiBase {
-
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
if ( is_null( $params['text'] ) && is_null( $params['appendtext'] ) &&
- is_null( $params['prependtext'] ) &&
- $params['undo'] == 0 )
- {
+ is_null( $params['prependtext'] ) &&
+ $params['undo'] == 0
+ ) {
$this->dieUsageMsg( 'missingtext' );
}
@@ -49,12 +48,15 @@ class ApiEditPage extends ApiBase {
$apiResult = $this->getResult();
if ( $params['redirect'] ) {
+ if ( $params['prependtext'] === null && $params['appendtext'] === null && $params['section'] !== 'new' ) {
+ $this->dieUsage( 'You have attempted to edit using the "redirect"-following mode, which must be used in conjuction with section=new, prependtext, or appendtext.', 'redirect-appendonly' );
+ }
if ( $titleObj->isRedirect() ) {
$oldTitle = $titleObj;
$titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
- ->getContent( Revision::FOR_THIS_USER, $user )
- ->getRedirectChain();
+ ->getContent( Revision::FOR_THIS_USER, $user )
+ ->getRedirectChain();
// array_shift( $titles );
$redirValues = array();
@@ -88,7 +90,8 @@ class ApiEditPage extends ApiBase {
$contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
}
- // @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such
+ // @todo Ask handler whether direct editing is supported at all! make
+ // allowFlatEdit() method or some such
if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
$params['contentformat'] = $contentHandler->getDefaultFormat();
@@ -101,7 +104,7 @@ class ApiEditPage extends ApiBase {
$model = $contentHandler->getModelID();
$this->dieUsage( "The requested format $contentFormat is not supported for content model " .
- " $model used by $name", 'badformat' );
+ " $model used by $name", 'badformat' );
}
if ( $params['createonly'] && $titleObj->exists() ) {
@@ -121,8 +124,7 @@ class ApiEditPage extends ApiBase {
}
$toMD5 = $params['text'];
- if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
- {
+ if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) ) {
$content = $pageObj->getContent();
if ( !$content ) {
@@ -138,6 +140,7 @@ class ApiEditPage extends ApiBase {
$content = ContentHandler::makeContent( $text, $this->getTitle() );
} catch ( MWContentSerializationException $ex ) {
$this->dieUsage( $ex->getMessage(), 'parseerror' );
+
return;
}
} else {
@@ -156,7 +159,10 @@ class ApiEditPage extends ApiBase {
if ( !is_null( $params['section'] ) ) {
if ( !$contentHandler->supportsSections() ) {
$modelName = $contentHandler->getModelID();
- $this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' );
+ $this->dieUsage(
+ "Sections are not supported for this content model: $modelName.",
+ 'sectionsnotsupported'
+ );
}
if ( $params['section'] == 'new' ) {
@@ -164,7 +170,7 @@ class ApiEditPage extends ApiBase {
$content = null;
} else {
// Process the content for section edits
- $section = intval( $params['section'] );
+ $section = $params['section'];
$content = $content->getSection( $section );
if ( !$content ) {
@@ -187,7 +193,7 @@ class ApiEditPage extends ApiBase {
if ( $params['undoafter'] > 0 ) {
if ( $params['undo'] < $params['undoafter'] ) {
list( $params['undo'], $params['undoafter'] ) =
- array( $params['undoafter'], $params['undo'] );
+ array( $params['undoafter'], $params['undo'] );
}
$undoafterRev = Revision::newFromID( $params['undoafter'] );
}
@@ -204,13 +210,19 @@ class ApiEditPage extends ApiBase {
}
if ( $undoRev->getPage() != $pageObj->getID() ) {
- $this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
+ $this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(),
+ $titleObj->getPrefixedText() ) );
}
if ( $undoafterRev->getPage() != $pageObj->getID() ) {
- $this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
+ $this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(),
+ $titleObj->getPrefixedText() ) );
}
- $newContent = $contentHandler->getUndoContent( $pageObj->getRevision(), $undoRev, $undoafterRev );
+ $newContent = $contentHandler->getUndoContent(
+ $pageObj->getRevision(),
+ $undoRev,
+ $undoafterRev
+ );
if ( !$newContent ) {
$this->dieUsageMsg( 'undo-failure' );
@@ -220,8 +232,11 @@ class ApiEditPage extends ApiBase {
// If no summary was given and we only undid one rev,
// use an autosummary
- if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] ) {
- $params['summary'] = wfMessage( 'undo-summary', $params['undo'], $undoRev->getUserText() )->inContentLanguage()->text();
+ if ( is_null( $params['summary'] ) &&
+ $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo']
+ ) {
+ $params['summary'] = wfMessage( 'undo-summary' )
+ ->params ( $params['undo'], $undoRev->getUserText() )->inContentLanguage()->text();
}
}
@@ -237,7 +252,8 @@ class ApiEditPage extends ApiBase {
'format' => $contentFormat,
'model' => $contentHandler->getModelID(),
'wpEditToken' => $params['token'],
- 'wpIgnoreBlankSummary' => ''
+ 'wpIgnoreBlankSummary' => '',
+ 'wpIgnoreBlankArticle' => true
);
if ( !is_null( $params['summary'] ) ) {
@@ -276,12 +292,12 @@ class ApiEditPage extends ApiBase {
}
if ( !is_null( $params['section'] ) ) {
- $section = intval( $params['section'] );
- if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' ) {
- $this->dieUsage( "The section parameter must be set to an integer or 'new'", "invalidsection" );
+ $section = $params['section'];
+ if ( !preg_match( '/^((T-)?\d+|new)$/', $section ) ) {
+ $this->dieUsage( "The section parameter must be a valid section id or 'new'", "invalidsection" );
}
$content = $pageObj->getContent();
- if ( $section !== 0 && ( !$content || !$content->getSection( $section ) ) ) {
+ if ( $section !== '0' && $section != 'new' && ( !$content || !$content->getSection( $section ) ) ) {
$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
}
$requestArray['wpSection'] = $params['section'];
@@ -293,8 +309,10 @@ class ApiEditPage extends ApiBase {
// Deprecated parameters
if ( $params['watch'] ) {
+ $this->logFeatureUsage( 'action=edit&watch' );
$watch = true;
} elseif ( $params['unwatch'] ) {
+ $this->logFeatureUsage( 'action=edit&unwatch' );
$watch = false;
}
@@ -333,9 +351,9 @@ class ApiEditPage extends ApiBase {
// The following is needed to give the hook the full content of the
// new revision rather than just the current section. (Bug 52077)
- if ( !is_null( $params['section'] ) && $contentHandler->supportsSections() && $titleObj->exists() ) {
-
- $sectionTitle = '';
+ if ( !is_null( $params['section'] ) &&
+ $contentHandler->supportsSections() && $titleObj->exists()
+ ) {
// If sectiontitle is set, use it, otherwise use the summary as the section title (for
// backwards compatibility with old forms/bots).
if ( $ep->sectiontitle !== '' ) {
@@ -346,7 +364,11 @@ class ApiEditPage extends ApiBase {
$contentObj = $contentHandler->unserializeContent( $content, $contentFormat );
- $fullContentObj = $articleObject->replaceSectionContent( $params['section'], $contentObj, $sectionTitle );
+ $fullContentObj = $articleObject->replaceSectionContent(
+ $params['section'],
+ $contentObj,
+ $sectionTitle
+ );
if ( $fullContentObj ) {
$content = $fullContentObj->serialize( $contentFormat );
} else {
@@ -363,10 +385,11 @@ class ApiEditPage extends ApiBase {
if ( count( $r ) ) {
$r['result'] = 'Failure';
$apiResult->addValue( null, $this->getModuleName(), $r );
+
return;
- } else {
- $this->dieUsageMsg( 'hookaborted' );
}
+
+ $this->dieUsageMsg( 'hookaborted' );
}
// Do the actual save
@@ -379,7 +402,6 @@ class ApiEditPage extends ApiBase {
$status = $ep->internalAttemptSave( $result, $user->isAllowed( 'bot' ) && $params['bot'] );
$wgRequest = $oldRequest;
- global $wgMaxArticleSize;
switch ( $status->value ) {
case EditPage::AS_HOOK_ERROR:
@@ -403,7 +425,7 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
case EditPage::AS_CONTENT_TOO_BIG:
- $this->dieUsageMsg( array( 'contenttoobig', $wgMaxArticleSize ) );
+ $this->dieUsageMsg( array( 'contenttoobig', $this->getConfig()->get( 'MaxArticleSize' ) ) );
case EditPage::AS_READ_ONLY_PAGE_ANON:
$this->dieUsageMsg( 'noedit-anon' );
@@ -481,52 +503,6 @@ class ApiEditPage extends ApiBase {
return 'Create and edit pages.';
}
- public function getPossibleErrors() {
- global $wgMaxArticleSize;
-
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'missingtext' ),
- array( 'createonly-exists' ),
- array( 'nocreate-missing' ),
- array( 'nosuchrevid', 'undo' ),
- array( 'nosuchrevid', 'undoafter' ),
- array( 'revwrongpage', 'id', 'text' ),
- array( 'undo-failure' ),
- array( 'hashcheckfailed' ),
- array( 'hookaborted' ),
- array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
- array( 'noimageredirect-anon' ),
- array( 'noimageredirect-logged' ),
- array( 'spamdetected', 'spam' ),
- array( 'summaryrequired' ),
- array( 'blockedtext' ),
- array( 'contenttoobig', $wgMaxArticleSize ),
- array( 'noedit-anon' ),
- array( 'noedit' ),
- array( 'actionthrottledtext' ),
- array( 'wasdeleted' ),
- array( 'nocreate-loggedin' ),
- array( 'blankpage' ),
- array( 'editconflict' ),
- array( 'emptynewsection' ),
- array( 'unknownerror', 'retval' ),
- array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
- array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
- array( 'code' => 'sectionsnotsupported', 'info' => 'Sections are not supported for this type of page.' ),
- array( 'code' => 'editnotsupported', 'info' => 'Editing of this type of page is not supported using '
- . 'the text based edit API.' ),
- array( 'code' => 'appendnotsupported', 'info' => 'This type of page can not be edited by appending '
- . 'or prepending text.' ),
- array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied to '
- . 'the page\'s content model' ),
- array( 'customcssprotected' ),
- array( 'customjsprotected' ),
- )
- );
- }
-
public function getAllowedParams() {
return array(
'title' => array(
@@ -540,10 +516,6 @@ class ApiEditPage extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
),
'text' => null,
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'summary' => null,
'minor' => false,
'notminor' => false,
@@ -594,36 +566,47 @@ class ApiEditPage extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'title' => "Title of the page you want to edit. Cannot be used together with {$p}pageid",
'pageid' => "Page ID of the page you want to edit. Cannot be used together with {$p}title",
'section' => 'Section number. 0 for the top section, \'new\' for a new section',
'sectiontitle' => 'The title for a new section',
'text' => 'Page content',
- 'token' => array( 'Edit token. You can get one of these through prop=info.',
- "The token should always be sent as the last parameter, or at least, after the {$p}text parameter"
+ 'token' => array(
+ /* Standard description is automatically prepended */
+ 'The token should always be sent as the last parameter, or at ' .
+ "least, after the {$p}text parameter"
),
- 'summary' => "Edit summary. Also section title when {$p}section=new and {$p}sectiontitle is not set",
+ 'summary'
+ => "Edit summary. Also section title when {$p}section=new and {$p}sectiontitle is not set",
'minor' => 'Minor edit',
'notminor' => 'Non-minor edit',
'bot' => 'Mark this edit as bot',
- 'basetimestamp' => array( 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).',
- 'Used to detect edit conflicts; leave unset to ignore conflicts'
+ 'basetimestamp' => array(
+ 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).',
+ 'Used to detect edit conflicts; leave unset to ignore conflicts'
),
- 'starttimestamp' => array( 'Timestamp when you obtained the edit token.',
- 'Used to detect edit conflicts; leave unset to ignore conflicts'
+ 'starttimestamp' => array(
+ 'Timestamp when you began the editing process, e.g. when the current page content ' .
+ 'was loaded for editing.',
+ 'Used to detect edit conflicts; leave unset to ignore conflicts'
),
'recreate' => 'Override any errors about the article having been deleted in the meantime',
'createonly' => 'Don\'t edit the page if it exists already',
'nocreate' => 'Throw an error if the page doesn\'t exist',
'watch' => 'Add the page to your watchlist',
'unwatch' => 'Remove the page from your watchlist',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
- 'md5' => array( "The MD5 hash of the {$p}text parameter, or the {$p}prependtext and {$p}appendtext parameters concatenated.",
- 'If set, the edit won\'t be done unless the hash is correct' ),
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
+ 'md5' => array(
+ "The MD5 hash of the {$p}text parameter, or the {$p}prependtext " .
+ "and {$p}appendtext parameters concatenated.",
+ 'If set, the edit won\'t be done unless the hash is correct'
+ ),
'prependtext' => "Add this text to the beginning of the page. Overrides {$p}text",
'appendtext' => array( "Add this text to the end of the page. Overrides {$p}text.",
- "Use {$p}section=new to append a new section" ),
+ "Use {$p}section=new to append a new section" ),
'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
'redirect' => 'Automatically resolve redirects',
@@ -632,56 +615,20 @@ class ApiEditPage extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'new' => 'boolean',
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'Failure'
- ),
- ),
- 'pageid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'title' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'nochange' => 'boolean',
- 'oldrevid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'newrevid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'newtimestamp' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\'
+ 'api.php?action=edit&title=Test&summary=test%20summary&' .
+ 'text=article%20content&basetimestamp=20070824123454&token=%2B\\'
=> 'Edit a page (anonymous user)',
- 'api.php?action=edit&title=Test&summary=NOTOC&minor=&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\'
+ 'api.php?action=edit&title=Test&summary=NOTOC&minor=&' .
+ 'prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\'
=> 'Prepend __NOTOC__ to a page (anonymous user)',
- 'api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\'
+ 'api.php?action=edit&title=Test&undo=13585&undoafter=13579&' .
+ 'basetimestamp=20070824123454&token=%2B\\'
=> 'Undo r13579 through r13585 with autosummary (anonymous user)',
);
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index cd0d0cba..9870b2de 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -40,7 +40,11 @@ class ApiEmailUser extends ApiBase {
}
// Check permissions and errors
- $error = SpecialEmailUser::getPermissionsError( $this->getUser(), $params['token'] );
+ $error = SpecialEmailUser::getPermissionsError(
+ $this->getUser(),
+ $params['token'],
+ $this->getConfig()
+ );
if ( $error ) {
$this->dieUsageMsg( array( $error ) );
}
@@ -94,10 +98,6 @@ class ApiEmailUser extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'ccme' => false,
);
}
@@ -107,49 +107,22 @@ class ApiEmailUser extends ApiBase {
'target' => 'User to send email to',
'subject' => 'Subject header',
'text' => 'Mail body',
- 'token' => 'A token previously acquired via prop=info',
'ccme' => 'Send a copy of this mail to me',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'Failure'
- ),
- ),
- 'message' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
return 'Email a user.';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'usermaildisabled' ),
- ) );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=emailuser&target=WikiSysop&text=Content' => 'Send an email to the User "WikiSysop" with the text "Content"',
+ 'api.php?action=emailuser&target=WikiSysop&text=Content&token=123ABC'
+ => 'Send an email to the User "WikiSysop" with the text "Content"',
);
}
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index d5c789c3..8a3b534d 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -39,6 +39,18 @@ class ApiExpandTemplates extends ApiBase {
// Get parameters
$params = $this->extractRequestParams();
+ $this->requireMaxOneParameter( $params, 'prop', 'generatexml' );
+
+ if ( $params['prop'] === null ) {
+ $this->logFeatureUsage( 'action=expandtemplates&!prop' );
+ $this->setWarning( 'Because no values have been specified for the prop parameter, a ' .
+ 'legacy format has been used for the output. This format is deprecated, and in ' .
+ 'the future, a default value will be set for the prop parameter, causing the new' .
+ 'format to always be used.' );
+ $prop = array();
+ } else {
+ $prop = array_flip( $params['prop'] );
+ }
// Create title for parser
$title_obj = Title::newFromText( $params['title'] );
@@ -56,7 +68,13 @@ class ApiExpandTemplates extends ApiBase {
$options->setRemoveComments( false );
}
- if ( $params['generatexml'] ) {
+ $retval = array();
+
+ if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
+ if ( !isset( $prop['parsetree'] ) ) {
+ $this->logFeatureUsage( 'action=expandtemplates&generatexml' );
+ }
+
$wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $params['text'] );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
@@ -64,16 +82,54 @@ class ApiExpandTemplates extends ApiBase {
} else {
$xml = $dom->__toString();
}
- $xml_result = array();
- ApiResult::setContent( $xml_result, $xml );
- $result->addValue( null, 'parsetree', $xml_result );
+ if ( isset( $prop['parsetree'] ) ) {
+ unset( $prop['parsetree'] );
+ $retval['parsetree'] = $xml;
+ } else {
+ // the old way
+ $xml_result = array();
+ ApiResult::setContent( $xml_result, $xml );
+ $result->addValue( null, 'parsetree', $xml_result );
+ }
}
- $retval = $wgParser->preprocess( $params['text'], $title_obj, $options );
- // Return result
- $retval_array = array();
- ApiResult::setContent( $retval_array, $retval );
- $result->addValue( null, $this->getModuleName(), $retval_array );
+ // if they didn't want any output except (probably) the parse tree,
+ // then don't bother actually fully expanding it
+ if ( $prop || $params['prop'] === null ) {
+ $wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS );
+ $frame = $wgParser->getPreprocessor()->newFrame();
+ $wikitext = $wgParser->preprocess( $params['text'], $title_obj, $options, null, $frame );
+ if ( $params['prop'] === null ) {
+ // the old way
+ ApiResult::setContent( $retval, $wikitext );
+ } else {
+ if ( isset( $prop['categories'] ) ) {
+ $categories = $wgParser->getOutput()->getCategories();
+ if ( !empty( $categories ) ) {
+ $categories_result = array();
+ foreach ( $categories as $category => $sortkey ) {
+ $entry = array();
+ $entry['sortkey'] = $sortkey;
+ ApiResult::setContent( $entry, $category );
+ $categories_result[] = $entry;
+ }
+ $result->setIndexedTagName( $categories_result, 'category' );
+ $retval['categories'] = $categories_result;
+ }
+ }
+ if ( isset( $prop['volatile'] ) && $frame->isVolatile() ) {
+ $retval['volatile'] = '';
+ }
+ if ( isset( $prop['ttl'] ) && $frame->getTTL() !== null ) {
+ $retval['ttl'] = $frame->getTTL();
+ }
+ if ( isset( $prop['wikitext'] ) ) {
+ $retval['wikitext'] = $wikitext;
+ }
+ }
+ }
+ $result->setSubelements( $retval, array( 'wikitext', 'parsetree' ) );
+ $result->addValue( null, $this->getModuleName(), $retval );
}
public function getAllowedParams() {
@@ -85,8 +141,21 @@ class ApiExpandTemplates extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
),
- 'generatexml' => false,
+ 'prop' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'wikitext',
+ 'categories',
+ 'volatile',
+ 'ttl',
+ 'parsetree',
+ ),
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'includecomments' => false,
+ 'generatexml' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
);
}
@@ -94,27 +163,26 @@ class ApiExpandTemplates extends ApiBase {
return array(
'text' => 'Wikitext to convert',
'title' => 'Title of page',
- 'generatexml' => 'Generate XML parse tree',
+ 'prop' => array(
+ 'Which pieces of information to get',
+ ' wikitext - The expanded wikitext',
+ ' categories - Any categories present in the input that are not represented in ' .
+ 'the wikitext output',
+ ' volatile - Whether the output is volatile and should not be reused ' .
+ 'elsewhere within the page',
+ ' ttl - The maximum time after which caches of the result should be ' .
+ 'invalidated',
+ ' parsetree - The XML parse tree of the input',
+ 'Note that if no values are selected, the result will contain the wikitext,',
+ 'but the output will be in a deprecated format.',
+ ),
'includecomments' => 'Whether to include HTML comments in the output',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- '*' => 'string'
- )
+ 'generatexml' => 'Generate XML parse tree (replaced by prop=parsetree)',
);
}
public function getDescription() {
- return 'Expands all templates in wikitext';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'invalidtitle', 'title' ),
- ) );
+ return 'Expands all templates in wikitext.';
}
public function getExamples() {
diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php
index 05691093..374203eb 100644
--- a/includes/api/ApiFeedContributions.php
+++ b/includes/api/ApiFeedContributions.php
@@ -41,30 +41,29 @@ class ApiFeedContributions extends ApiBase {
public function execute() {
$params = $this->extractRequestParams();
- global $wgFeed, $wgFeedClasses, $wgSitename, $wgLanguageCode;
-
- if ( !$wgFeed ) {
+ $config = $this->getConfig();
+ if ( !$config->get( 'Feed' ) ) {
$this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
}
- if ( !isset( $wgFeedClasses[$params['feedformat']] ) ) {
+ $feedClasses = $config->get( 'FeedClasses' );
+ if ( !isset( $feedClasses[$params['feedformat']] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
- global $wgMiserMode;
- if ( $params['showsizediff'] && $wgMiserMode ) {
+ if ( $params['showsizediff'] && $this->getConfig()->get( 'MiserMode' ) ) {
$this->dieUsage( 'Size difference is disabled in Miser Mode', 'sizediffdisabled' );
}
$msg = wfMessage( 'Contributions' )->inContentLanguage()->text();
- $feedTitle = $wgSitename . ' - ' . $msg . ' [' . $wgLanguageCode . ']';
+ $feedTitle = $config->get( 'Sitename' ) . ' - ' . $msg . ' [' . $config->get( 'LanguageCode' ) . ']';
$feedUrl = SpecialPage::getTitleFor( 'Contributions', $params['user'] )->getFullURL();
$target = $params['user'] == 'newbies'
- ? 'newbies'
- : Title::makeTitleSafe( NS_USER, $params['user'] )->getText();
+ ? 'newbies'
+ : Title::makeTitleSafe( NS_USER, $params['user'] )->getText();
- $feed = new $wgFeedClasses[$params['feedformat']] (
+ $feed = new $feedClasses[$params['feedformat']] (
$feedTitle,
htmlspecialchars( $msg ),
$feedUrl
@@ -78,12 +77,24 @@ class ApiFeedContributions extends ApiBase {
'tagFilter' => $params['tagfilter'],
'deletedOnly' => $params['deletedonly'],
'topOnly' => $params['toponly'],
+ 'newOnly' => $params['newonly'],
'showSizeDiff' => $params['showsizediff'],
) );
+ $feedLimit = $this->getConfig()->get( 'FeedLimit' );
+ if ( $pager->getLimit() > $feedLimit ) {
+ $pager->setLimit( $feedLimit );
+ }
+
$feedItems = array();
if ( $pager->getNumRows() > 0 ) {
+ $count = 0;
+ $limit = $pager->getLimit();
foreach ( $pager->mResult as $row ) {
+ // ContribsPager selects one more row for navigation, skip that row
+ if ( ++$count > $limit ) {
+ break;
+ }
$feedItems[] = $this->feedItem( $row );
}
}
@@ -101,17 +112,18 @@ class ApiFeedContributions extends ApiBase {
return new FeedItem(
$title->getPrefixedText(),
$this->feedItemDesc( $revision ),
- $title->getFullURL(),
+ $title->getFullURL( array( 'diff' => $revision->getId() ) ),
$date,
$this->feedItemAuthor( $revision ),
$comments
);
}
+
return null;
}
/**
- * @param $revision Revision
+ * @param Revision $revision
* @return string
*/
protected function feedItemAuthor( $revision ) {
@@ -119,7 +131,7 @@ class ApiFeedContributions extends ApiBase {
}
/**
- * @param $revision Revision
+ * @param Revision $revision
* @return string
*/
protected function feedItemDesc( $revision ) {
@@ -142,12 +154,13 @@ class ApiFeedContributions extends ApiBase {
htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
"</p>\n<hr />\n<div>" . $html . "</div>";
}
+
return '';
}
public function getAllowedParams() {
- global $wgFeedClasses;
- $feedFormatNames = array_keys( $wgFeedClasses );
+ $feedFormatNames = array_keys( $this->getConfig()->get( 'FeedClasses' ) );
+
return array(
'feedformat' => array(
ApiBase::PARAM_DFLT => 'rss',
@@ -173,6 +186,7 @@ class ApiFeedContributions extends ApiBase {
),
'deletedonly' => false,
'toponly' => false,
+ 'newonly' => false,
'showsizediff' => false,
);
}
@@ -187,20 +201,13 @@ class ApiFeedContributions extends ApiBase {
'tagfilter' => 'Filter contributions that have these tags',
'deletedonly' => 'Show only deleted contributions',
'toponly' => 'Only show edits that are latest revisions',
+ 'newonly' => 'Only show edits that are page creations',
'showsizediff' => 'Show the size difference between revisions. Disabled in Miser Mode',
);
}
public function getDescription() {
- return 'Returns a user contributions feed';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'feed-unavailable', 'info' => 'Syndication feeds are not available' ),
- array( 'code' => 'feed-invalid', 'info' => 'Invalid subscription feed type' ),
- array( 'code' => 'sizediffdisabled', 'info' => 'Size difference is disabled in Miser Mode' ),
- ) );
+ return 'Returns a user contributions feed.';
}
public function getExamples() {
diff --git a/includes/api/ApiFeedRecentChanges.php b/includes/api/ApiFeedRecentChanges.php
new file mode 100644
index 00000000..7239a296
--- /dev/null
+++ b/includes/api/ApiFeedRecentChanges.php
@@ -0,0 +1,207 @@
+<?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
+ * @since 1.23
+ */
+
+/**
+ * Recent changes feed.
+ *
+ * @ingroup API
+ */
+class ApiFeedRecentChanges extends ApiBase {
+
+ /**
+ * This module uses a custom feed wrapper printer.
+ *
+ * @return ApiFormatFeedWrapper
+ */
+ public function getCustomPrinter() {
+ return new ApiFormatFeedWrapper( $this->getMain() );
+ }
+
+ /**
+ * Format the rows (generated by SpecialRecentchanges or SpecialRecentchangeslinked)
+ * as an RSS/Atom feed.
+ */
+ public function execute() {
+ $config = $this->getConfig();
+
+ $this->params = $this->extractRequestParams();
+
+ if ( !$config->get( 'Feed' ) ) {
+ $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
+ }
+
+ $feedClasses = $config->get( 'FeedClasses' );
+ if ( !isset( $feedClasses[$this->params['feedformat']] ) ) {
+ $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
+ }
+
+ $this->getMain()->setCacheMode( 'public' );
+ if ( !$this->getMain()->getParameter( 'smaxage' ) ) {
+ // bug 63249: This page gets hit a lot, cache at least 15 seconds.
+ $this->getMain()->setCacheMaxAge( 15 );
+ }
+
+ $feedFormat = $this->params['feedformat'];
+ $specialClass = $this->params['target'] !== null
+ ? 'SpecialRecentchangeslinked'
+ : 'SpecialRecentchanges';
+
+ $formatter = $this->getFeedObject( $feedFormat, $specialClass );
+
+ // Everything is passed implicitly via $wgRequest… :(
+ // The row-getting functionality should maybe be factored out of ChangesListSpecialPage too…
+ $rc = new $specialClass();
+ $rows = $rc->getRows();
+
+ $feedItems = $rows ? ChangesFeed::buildItems( $rows ) : array();
+
+ ApiFormatFeedWrapper::setResult( $this->getResult(), $formatter, $feedItems );
+ }
+
+ /**
+ * Return a ChannelFeed object.
+ *
+ * @param string $feedFormat Feed's format (either 'rss' or 'atom')
+ * @param string $specialClass Relevant special page name (either 'SpecialRecentchanges' or
+ * 'SpecialRecentchangeslinked')
+ * @return ChannelFeed
+ */
+ public function getFeedObject( $feedFormat, $specialClass ) {
+ if ( $specialClass === 'SpecialRecentchangeslinked' ) {
+ $title = Title::newFromText( $this->params['target'] );
+ if ( !$title ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $this->params['target'] ) );
+ }
+
+ $feed = new ChangesFeed( $feedFormat, false );
+ $feedObj = $feed->getFeedObject(
+ $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() )
+ ->inContentLanguage()->text(),
+ $this->msg( 'recentchangeslinked-feed' )->inContentLanguage()->text(),
+ SpecialPage::getTitleFor( 'Recentchangeslinked' )->getFullURL()
+ );
+ } else {
+ $feed = new ChangesFeed( $feedFormat, 'rcfeed' );
+ $feedObj = $feed->getFeedObject(
+ $this->msg( 'recentchanges' )->inContentLanguage()->text(),
+ $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(),
+ SpecialPage::getTitleFor( 'Recentchanges' )->getFullURL()
+ );
+ }
+
+ return $feedObj;
+ }
+
+ public function getAllowedParams() {
+ $config = $this->getConfig();
+ $feedFormatNames = array_keys( $config->get( 'FeedClasses' ) );
+
+ $ret = array(
+ 'feedformat' => array(
+ ApiBase::PARAM_DFLT => 'rss',
+ ApiBase::PARAM_TYPE => $feedFormatNames,
+ ),
+
+ 'namespace' => array(
+ ApiBase::PARAM_TYPE => 'namespace',
+ ),
+ 'invert' => false,
+ 'associated' => false,
+
+ 'days' => array(
+ ApiBase::PARAM_DFLT => 7,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 50,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => $config->get( 'FeedLimit' ),
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
+ 'from' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ),
+
+ 'hideminor' => false,
+ 'hidebots' => false,
+ 'hideanons' => false,
+ 'hideliu' => false,
+ 'hidepatrolled' => false,
+ 'hidemyself' => false,
+
+ 'tagfilter' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ),
+
+ 'target' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ),
+ 'showlinkedto' => false,
+ );
+
+ if ( $config->get( 'AllowCategorizedRecentChanges' ) ) {
+ $ret += array(
+ 'categories' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'categories_any' => false,
+ );
+ }
+
+ return $ret;
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'feedformat' => 'The format of the feed',
+ 'namespace' => 'Namespace to limit the results to',
+ 'invert' => 'All namespaces but the selected one',
+ 'associated' => 'Include associated (talk or main) namespace',
+ 'days' => 'Days to limit the results to',
+ 'limit' => 'Maximum number of results to return',
+ 'from' => 'Show changes since then',
+ 'hideminor' => 'Hide minor changes',
+ 'hidebots' => 'Hide changes made by bots',
+ 'hideanons' => 'Hide changes made by anonymous users',
+ 'hideliu' => 'Hide changes made by registered users',
+ 'hidepatrolled' => 'Hide patrolled changes',
+ 'hidemyself' => 'Hide changes made by yourself',
+ 'tagfilter' => 'Filter by tag',
+ 'target' => 'Show only changes on pages linked from this page',
+ 'showlinkedto' => 'Show changes on pages linked to the selected page instead',
+ 'categories' => 'Show only changes on pages in all of these categories',
+ 'categories_any' => 'Show only changes on pages in any of the categories instead',
+ );
+ }
+
+ public function getDescription() {
+ return 'Returns a recent changes feed';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=feedrecentchanges',
+ 'api.php?action=feedrecentchanges&days=30'
+ );
+ }
+}
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index fbb70fbc..6aef8fc2 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -34,7 +34,6 @@
class ApiFeedWatchlist extends ApiBase {
private $watchlistModule = null;
- private $linkToDiffs = false;
private $linkToSections = false;
/**
@@ -51,16 +50,16 @@ class ApiFeedWatchlist extends ApiBase {
* Wrap the result as an RSS/Atom feed.
*/
public function execute() {
- global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgSitename, $wgLanguageCode;
-
+ $config = $this->getConfig();
+ $feedClasses = $config->get( 'FeedClasses' );
try {
$params = $this->extractRequestParams();
- if ( !$wgFeed ) {
+ if ( !$config->get( 'Feed' ) ) {
$this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
}
- if ( !isset( $wgFeedClasses[$params['feedformat']] ) ) {
+ if ( !isset( $feedClasses[$params['feedformat']] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
@@ -73,10 +72,10 @@ class ApiFeedWatchlist extends ApiBase {
'meta' => 'siteinfo',
'siprop' => 'general',
'list' => 'watchlist',
- 'wlprop' => 'title|user|comment|timestamp',
+ 'wlprop' => 'title|user|comment|timestamp|ids',
'wldir' => 'older', // reverse order - from newest to oldest
'wlend' => $endTime, // stop at this time
- 'wllimit' => min( 50, $wgFeedLimit )
+ 'wllimit' => min( 50, $this->getConfig()->get( 'FeedLimit' ) )
);
if ( $params['wlowner'] !== null ) {
@@ -95,12 +94,6 @@ class ApiFeedWatchlist extends ApiBase {
$fauxReqArr['wltype'] = $params['wltype'];
}
- // Support linking to diffs instead of article
- if ( $params['linktodiffs'] ) {
- $this->linkToDiffs = true;
- $fauxReqArr['wlprop'] .= '|ids';
- }
-
// Support linking directly to sections when possible
// (possible only if section name is present in comment)
if ( $params['linktosections'] ) {
@@ -129,24 +122,29 @@ class ApiFeedWatchlist extends ApiBase {
$msg = wfMessage( 'watchlist' )->inContentLanguage()->text();
- $feedTitle = $wgSitename . ' - ' . $msg . ' [' . $wgLanguageCode . ']';
+ $feedTitle = $this->getConfig()->get( 'Sitename' ) . ' - ' . $msg . ' [' . $this->getConfig()->get( 'LanguageCode' ) . ']';
$feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
- $feed = new $wgFeedClasses[$params['feedformat']] ( $feedTitle, htmlspecialchars( $msg ), $feedUrl );
+ $feed = new $feedClasses[$params['feedformat']] (
+ $feedTitle,
+ htmlspecialchars( $msg ),
+ $feedUrl
+ );
ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
-
} catch ( Exception $e ) {
-
// Error results should not be cached
$this->getMain()->setCacheMaxAge( 0 );
- $feedTitle = $wgSitename . ' - Error - ' . wfMessage( 'watchlist' )->inContentLanguage()->text() . ' [' . $wgLanguageCode . ']';
+ // @todo FIXME: Localise brackets
+ $feedTitle = $this->getConfig()->get( 'Sitename' ) . ' - Error - ' .
+ wfMessage( 'watchlist' )->inContentLanguage()->text() .
+ ' [' . $this->getConfig()->get( 'LanguageCode' ) . ']';
$feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
$feedFormat = isset( $params['feedformat'] ) ? $params['feedformat'] : 'rss';
$msg = wfMessage( 'watchlist' )->inContentLanguage()->escaped();
- $feed = new $wgFeedClasses[$feedFormat] ( $feedTitle, $msg, $feedUrl );
+ $feed = new $feedClasses[$feedFormat] ( $feedTitle, $msg, $feedUrl );
if ( $e instanceof UsageException ) {
$errorCode = $e->getCodeString();
@@ -162,13 +160,13 @@ class ApiFeedWatchlist extends ApiBase {
}
/**
- * @param $info array
+ * @param array $info
* @return FeedItem
*/
private function createFeedItem( $info ) {
$titleStr = $info['title'];
$title = Title::newFromText( $titleStr );
- if ( $this->linkToDiffs && isset( $info['revid'] ) ) {
+ if ( isset( $info['revid'] ) ) {
$titleUrl = $title->getFullURL( array( 'diff' => $info['revid'] ) );
} else {
$titleUrl = $title->getFullURL();
@@ -179,8 +177,11 @@ class ApiFeedWatchlist extends ApiBase {
// The anchor won't work for sections that have dupes on page
// as there's no way to strip that info from ApiWatchlist (apparently?).
// RegExp in the line below is equal to Linker::formatAutocomments().
- if ( $this->linkToSections && $comment !== null && preg_match( '!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment, $matches ) ) {
+ if ( $this->linkToSections && $comment !== null &&
+ preg_match( '!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment, $matches )
+ ) {
global $wgParser;
+
$sectionTitle = $wgParser->stripSectionName( $matches[2] );
$sectionTitle = Sanitizer::normalizeSectionNameWhitespace( $sectionTitle );
$titleUrl .= Title::newFromText( '#' . $sectionTitle )->getFragmentForURL();
@@ -199,12 +200,12 @@ class ApiFeedWatchlist extends ApiBase {
$this->watchlistModule = $this->getMain()->getModuleManager()->getModule( 'query' )
->getModuleManager()->getModule( 'watchlist' );
}
+
return $this->watchlistModule;
}
public function getAllowedParams( $flags = 0 ) {
- global $wgFeedClasses;
- $feedFormatNames = array_keys( $wgFeedClasses );
+ $feedFormatNames = array_keys( $this->getConfig()->get( 'FeedClasses' ) );
$ret = array(
'feedformat' => array(
ApiBase::PARAM_DFLT => 'rss',
@@ -216,7 +217,6 @@ class ApiFeedWatchlist extends ApiBase {
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => 72,
),
- 'linktodiffs' => false,
'linktosections' => false,
);
if ( $flags ) {
@@ -235,15 +235,16 @@ class ApiFeedWatchlist extends ApiBase {
$ret['wltype'] = null;
$ret['wlexcludeuser'] = null;
}
+
return $ret;
}
public function getParamDescription() {
$wldescr = $this->getWatchlistModule()->getParamDescription();
+
return array(
'feedformat' => 'The format of the feed',
'hours' => 'List pages modified within this many hours from now',
- 'linktodiffs' => 'Link to change differences instead of article pages',
'linktosections' => 'Link directly to changed sections if possible',
'allrev' => $wldescr['allrev'],
'wlowner' => $wldescr['owner'],
@@ -255,20 +256,13 @@ class ApiFeedWatchlist extends ApiBase {
}
public function getDescription() {
- return 'Returns a watchlist feed';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'feed-unavailable', 'info' => 'Syndication feeds are not available' ),
- array( 'code' => 'feed-invalid', 'info' => 'Invalid subscription feed type' ),
- ) );
+ return 'Returns a watchlist feed.';
}
public function getExamples() {
return array(
'api.php?action=feedwatchlist',
- 'api.php?action=feedwatchlist&allrev=&linktodiffs=&hours=6'
+ 'api.php?action=feedwatchlist&allrev=&hours=6'
);
}
diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php
index cbb2ba6a..f518e172 100644
--- a/includes/api/ApiFileRevert.php
+++ b/includes/api/ApiFileRevert.php
@@ -28,13 +28,13 @@
* @ingroup API
*/
class ApiFileRevert extends ApiBase {
-
- /**
- * @var File
- */
+ /** @var LocalFile */
protected $file;
+
+ /** @var string */
protected $archiveName;
+ /** @var array */
protected $params;
public function execute() {
@@ -46,7 +46,15 @@ class ApiFileRevert extends ApiBase {
$this->checkPermissions( $this->getUser() );
$sourceUrl = $this->file->getArchiveVirtualUrl( $this->archiveName );
- $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'], 0, false, false, $this->getUser() );
+ $status = $this->file->upload(
+ $sourceUrl,
+ $this->params['comment'],
+ $this->params['comment'],
+ 0,
+ false,
+ false,
+ $this->getUser()
+ );
if ( $status->isGood() ) {
$result = array( 'result' => 'Success' );
@@ -58,13 +66,12 @@ class ApiFileRevert extends ApiBase {
}
$this->getResult()->addValue( null, $this->getModuleName(), $result );
-
}
/**
* Checks that the user has permissions to perform this revert.
* Dies with usage message on inadequate permissions.
- * @param $user User The user to check.
+ * @param User $user The user to check.
*/
protected function checkPermissions( $user ) {
$title = $this->file->getTitle();
@@ -125,69 +132,31 @@ class ApiFileRevert extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
);
-
}
public function getParamDescription() {
return array(
'filename' => 'Target filename without the File: prefix',
- 'token' => 'Edit token. You can get one of these through prop=info',
'comment' => 'Upload comment',
'archivename' => 'Archive name of the revision to revert to',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'Failure'
- )
- ),
- 'errors' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
return array(
- 'Revert a file to an old version'
- );
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- array(
- array( 'mustbeloggedin', 'upload' ),
- array( 'badaccess-groups' ),
- array( 'invalidtitle', 'title' ),
- array( 'notanarticle' ),
- array( 'filerevert-badversion' ),
- )
+ 'Revert a file to an old version.'
);
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=123ABC'
+ 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&' .
+ 'archivename=20110305152740!Wiki.png&token=123ABC'
=> 'Revert Wiki.png to the version of 20110305152740',
);
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 70495439..9165ce88 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -30,22 +30,20 @@
* @ingroup API
*/
abstract class ApiFormatBase extends ApiBase {
-
private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp, $mCleared;
private $mBufferResult = false, $mBuffer, $mDisabled = false;
/**
- * Constructor
* If $format ends with 'fm', pretty-print the output in HTML.
- * @param $main ApiMain
+ * @param ApiMain $main
* @param string $format Format name
*/
- public function __construct( $main, $format ) {
+ public function __construct( ApiMain $main, $format ) {
parent::__construct( $main, $format );
- $this->mIsHtml = ( substr( $format, - 2, 2 ) === 'fm' ); // ends with 'fm'
+ $this->mIsHtml = ( substr( $format, -2, 2 ) === 'fm' ); // ends with 'fm'
if ( $this->mIsHtml ) {
- $this->mFormat = substr( $format, 0, - 2 ); // remove ending 'fm'
+ $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
} else {
$this->mFormat = $format;
}
@@ -54,7 +52,7 @@ abstract class ApiFormatBase extends ApiBase {
}
/**
- * Overriding class returns the mime type that should be sent to the client.
+ * Overriding class returns the MIME type that should be sent to the client.
* This method is not called if getIsHtml() returns true.
* @return string
*/
@@ -122,6 +120,16 @@ abstract class ApiFormatBase extends ApiBase {
}
/**
+ * Whether this formatter can handle printing API errors. If this returns
+ * false, then on API errors the default printer will be instantiated.
+ * @since 1.23
+ * @return bool
+ */
+ public function canPrintErrors() {
+ return true;
+ }
+
+ /**
* Initialize the printer function and prepare the output headers, etc.
* This method must be the first outputting method during execution.
* A human-targeted notice about available formats is printed for the HTML-based output,
@@ -146,9 +154,9 @@ abstract class ApiFormatBase extends ApiBase {
$this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
//Set X-Frame-Options API results (bug 39180)
- global $wgApiFrameOptions;
- if ( $wgApiFrameOptions ) {
- $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $wgApiFrameOptions" );
+ $apiFrameOptions = $this->getConfig()->get( 'ApiFrameOptions' );
+ if ( $apiFrameOptions ) {
+ $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
}
if ( $isHtml ) {
@@ -156,17 +164,20 @@ abstract class ApiFormatBase extends ApiBase {
<!DOCTYPE HTML>
<html>
<head>
-<?php if ( $this->mUnescapeAmps ) {
+<?php
+ if ( $this->mUnescapeAmps ) {
?> <title>MediaWiki API</title>
-<?php } else {
+<?php
+ } else {
?> <title>MediaWiki API Result</title>
-<?php } ?>
+<?php
+ }
+?>
</head>
<body>
<?php
-
-
if ( !$isHelpScreen ) {
+// @codingStandardsIgnoreStart Exclude long line from CodeSniffer checks
?>
<br />
<small>
@@ -179,15 +190,14 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
</small>
<pre style='white-space: pre-wrap;'>
<?php
-
-
- } else { // don't wrap the contents of the <pre> for help screens
- // because these are actually formatted to rely on
- // the monospaced font for layout purposes
+// @codingStandardsIgnoreEnd
+ // don't wrap the contents of the <pre> for help screens
+ // because these are actually formatted to rely on
+ // the monospaced font for layout purposes
+ } else {
?>
<pre>
<?php
-
}
}
}
@@ -206,8 +216,6 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
</body>
</html>
<?php
-
-
}
}
@@ -215,7 +223,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
* The main format printing function. Call it to output the result
* string to the user. This function will automatically output HTML
* when format name ends in 'fm'.
- * @param $text string
+ * @param string $text
*/
public function printText( $text ) {
if ( $this->mDisabled ) {
@@ -239,6 +247,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
/**
* Get the contents of the buffer.
+ * @return string
*/
public function getBuffer() {
return $this->mBuffer;
@@ -246,7 +255,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
/**
* Set the flag to buffer the result instead of printing it.
- * @param $value bool
+ * @param bool $value
*/
public function setBufferResult( $value ) {
$this->mBufferResult = $value;
@@ -254,7 +263,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
/**
* Sets whether the pretty-printer should format *bold*
- * @param $help bool
+ * @param bool $help
*/
public function setHelp( $help = true ) {
$this->mHelp = $help;
@@ -263,7 +272,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
/**
* Pretty-print various elements in HTML format, such as xml tags and
* URLs. This method also escapes characters like <
- * @param $text string
+ * @param string $text
* @return string
*/
protected function formatHTML( $text ) {
@@ -276,8 +285,8 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
// identify requests to api.php
$text = preg_replace( '#^(\s*)(api\.php\?[^ <\n\t]+)$#m', '\1<a href="\2">\2</a>', $text );
if ( $this->mHelp ) {
- // make strings inside * bold
- $text = preg_replace( "#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text );
+ // make lines inside * bold
+ $text = preg_replace( '#^(\s*)(\*[^<>\n]+\*)(\s*)$#m', '$1<b>$2</b>$3', $text );
}
// Armor links (bug 61362)
@@ -291,7 +300,11 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
// identify URLs
$protos = wfUrlProtocolsWithoutProtRel();
// This regex hacks around bug 13218 (&quot; included in the URL)
- $text = preg_replace( "#(((?i)$protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#", '<a href="\\1">\\1</a>\\3\\4', $text );
+ $text = preg_replace(
+ "#(((?i)$protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#",
+ '<a href="\\1">\\1</a>\\3\\4',
+ $text
+ );
// Unarmor links
$text = preg_replace_callback( '#<([0-9a-f]{40})>#', function ( $matches ) use ( &$masked ) {
@@ -326,73 +339,16 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
public function getDescription() {
return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
}
-}
-
-/**
- * This printer is used to wrap an instance of the Feed class
- * @ingroup API
- */
-class ApiFormatFeedWrapper extends ApiFormatBase {
-
- public function __construct( $main ) {
- parent::__construct( $main, 'feed' );
- }
-
- /**
- * Call this method to initialize output data. See execute()
- * @param $result ApiResult
- * @param $feed object an instance of one of the $wgFeedClasses classes
- * @param array $feedItems of FeedItem objects
- */
- public static function setResult( $result, $feed, $feedItems ) {
- // Store output in the Result data.
- // This way we can check during execution if any error has occurred
- // Disable size checking for this because we can't continue
- // cleanly; size checking would cause more problems than it'd
- // solve
- $result->disableSizeCheck();
- $result->addValue( null, '_feed', $feed );
- $result->addValue( null, '_feeditems', $feedItems );
- $result->enableSizeCheck();
- }
-
- /**
- * Feed does its own headers
- *
- * @return null
- */
- public function getMimeType() {
- return null;
- }
-
- /**
- * Optimization - no need to sanitize data that will not be needed
- *
- * @return bool
- */
- public function getNeedsRawData() {
- return true;
- }
/**
- * This class expects the result data to be in a custom format set by self::setResult()
- * $result['_feed'] - an instance of one of the $wgFeedClasses classes
- * $result['_feeditems'] - an array of FeedItem instances
+ * To avoid code duplication with the deprecation of dbg, dump, txt, wddx,
+ * and yaml, this method is added to do the necessary work. It should be
+ * removed when those deprecated formats are removed.
*/
- public function execute() {
- $data = $this->getResultData();
- if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
- $feed = $data['_feed'];
- $items = $data['_feeditems'];
-
- $feed->outHeader();
- foreach ( $items as & $item ) {
- $feed->outItem( $item );
- }
- $feed->outFooter();
- } else {
- // Error has occurred, print something useful
- ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
- }
+ protected function markDeprecated() {
+ $fm = $this->getIsHtml() ? 'fm' : '';
+ $name = $this->getModuleName();
+ $this->logFeatureUsage( "format=$name" );
+ $this->setWarning( "format=$name has been deprecated. Please use format=json$fm instead." );
}
}
diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php
index 1b2e02c9..5ec518b3 100644
--- a/includes/api/ApiFormatDbg.php
+++ b/includes/api/ApiFormatDbg.php
@@ -26,6 +26,7 @@
/**
* API PHP's var_export() output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatDbg extends ApiFormatBase {
@@ -38,10 +39,11 @@ class ApiFormatDbg extends ApiFormatBase {
}
public function execute() {
+ $this->markDeprecated();
$this->printText( var_export( $this->getResultData(), true ) );
}
public function getDescription() {
- return 'Output data in PHP\'s var_export() format' . parent::getDescription();
+ return 'DEPRECATED! Output data in PHP\'s var_export() format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php
index 62253e14..d4c7cab4 100644
--- a/includes/api/ApiFormatDump.php
+++ b/includes/api/ApiFormatDump.php
@@ -26,6 +26,7 @@
/**
* API PHP's var_dump() output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatDump extends ApiFormatBase {
@@ -38,6 +39,7 @@ class ApiFormatDump extends ApiFormatBase {
}
public function execute() {
+ $this->markDeprecated();
ob_start();
var_dump( $this->getResultData() );
$result = ob_get_contents();
@@ -46,6 +48,6 @@ class ApiFormatDump extends ApiFormatBase {
}
public function getDescription() {
- return 'Output data in PHP\'s var_dump() format' . parent::getDescription();
+ return 'DEPRECATED! Output data in PHP\'s var_dump() format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatFeedWrapper.php b/includes/api/ApiFormatFeedWrapper.php
new file mode 100644
index 00000000..92600067
--- /dev/null
+++ b/includes/api/ApiFormatFeedWrapper.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ *
+ *
+ * Created on Sep 19, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan "<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
+ */
+
+/**
+ * This printer is used to wrap an instance of the Feed class
+ * @ingroup API
+ */
+class ApiFormatFeedWrapper extends ApiFormatBase {
+
+ public function __construct( ApiMain $main ) {
+ parent::__construct( $main, 'feed' );
+ }
+
+ /**
+ * Call this method to initialize output data. See execute()
+ * @param ApiResult $result
+ * @param object $feed An instance of one of the $wgFeedClasses classes
+ * @param array $feedItems Array of FeedItem objects
+ */
+ public static function setResult( $result, $feed, $feedItems ) {
+ // Store output in the Result data.
+ // This way we can check during execution if any error has occurred
+ // Disable size checking for this because we can't continue
+ // cleanly; size checking would cause more problems than it'd
+ // solve
+ $result->addValue( null, '_feed', $feed, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( null, '_feeditems', $feedItems, ApiResult::NO_SIZE_CHECK );
+ }
+
+ /**
+ * Feed does its own headers
+ *
+ * @return null
+ */
+ public function getMimeType() {
+ return null;
+ }
+
+ /**
+ * Optimization - no need to sanitize data that will not be needed
+ *
+ * @return bool
+ */
+ public function getNeedsRawData() {
+ return true;
+ }
+
+ /**
+ * ChannelFeed doesn't give us a method to print errors in a friendly
+ * manner, so just punt errors to the default printer.
+ * @return bool
+ */
+ public function canPrintErrors() {
+ return false;
+ }
+
+ /**
+ * This class expects the result data to be in a custom format set by self::setResult()
+ * $result['_feed'] - an instance of one of the $wgFeedClasses classes
+ * $result['_feeditems'] - an array of FeedItem instances
+ */
+ public function execute() {
+ $data = $this->getResultData();
+ if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
+ $feed = $data['_feed'];
+ $items = $data['_feeditems'];
+
+ $feed->outHeader();
+ foreach ( $items as & $item ) {
+ $feed->outItem( $item );
+ }
+ $feed->outFooter();
+ } else {
+ // Error has occurred, print something useful
+ ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
+ }
+ }
+}
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index 47d82124..d9f9d46a 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -32,7 +32,7 @@ class ApiFormatJson extends ApiFormatBase {
private $mIsRaw;
- public function __construct( $main, $format ) {
+ public function __construct( ApiMain $main, $format ) {
parent::__construct( $main, $format );
$this->mIsRaw = ( $format === 'rawfm' );
}
@@ -43,6 +43,7 @@ class ApiFormatJson extends ApiFormatBase {
if ( $params['callback'] ) {
return 'text/javascript';
}
+
return 'application/json';
}
@@ -92,16 +93,18 @@ class ApiFormatJson extends ApiFormatBase {
public function getParamDescription() {
return array(
- 'callback' => 'If specified, wraps the output into a given function call. For safety, all user-specific data will be restricted.',
- 'utf8' => 'If specified, encodes most (but not all) non-ASCII characters as UTF-8 instead of replacing them with hexadecimal escape sequences.',
+ 'callback' => 'If specified, wraps the output into a given function ' .
+ 'call. For safety, all user-specific data will be restricted.',
+ 'utf8' => 'If specified, encodes most (but not all) non-ASCII ' .
+ 'characters as UTF-8 instead of replacing them with hexadecimal escape sequences.',
);
}
public function getDescription() {
if ( $this->mIsRaw ) {
return 'Output data with the debugging elements in JSON format' . parent::getDescription();
- } else {
- return 'Output data in JSON format' . parent::getDescription();
}
+
+ return 'Output data in JSON format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
index bda1c180..73ce80ef 100644
--- a/includes/api/ApiFormatPhp.php
+++ b/includes/api/ApiFormatPhp.php
@@ -35,14 +35,13 @@ class ApiFormatPhp extends ApiFormatBase {
}
public function execute() {
- global $wgMangleFlashPolicy;
$text = serialize( $this->getResultData() );
// Bug 66776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
// Flash, but what it does isn't friendly for the API. There's nothing
// we can do here that isn't actively broken in some manner, so let's
// just be broken in a useful manner.
- if ( $wgMangleFlashPolicy &&
+ if ( $this->getConfig()->get( 'MangleFlashPolicy' ) &&
in_array( 'wfOutputHandler', ob_list_handlers(), true ) &&
preg_match( '/\<\s*cross-domain-policy\s*\>/i', $text )
) {
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
index d278efa0..3f5c8b73 100644
--- a/includes/api/ApiFormatRaw.php
+++ b/includes/api/ApiFormatRaw.php
@@ -31,11 +31,10 @@
class ApiFormatRaw extends ApiFormatBase {
/**
- * Constructor
- * @param $main ApiMain object
- * @param $errorFallback ApiFormatBase object to fall back on for errors
+ * @param ApiMain $main
+ * @param ApiFormatBase $errorFallback Object to fall back on for errors
*/
- public function __construct( $main, $errorFallback ) {
+ public function __construct( ApiMain $main, ApiFormatBase $errorFallback ) {
parent::__construct( $main, 'raw' );
$this->mErrorFallback = $errorFallback;
}
@@ -58,6 +57,7 @@ class ApiFormatRaw extends ApiFormatBase {
$data = $this->getResultData();
if ( isset( $data['error'] ) ) {
$this->mErrorFallback->execute();
+
return;
}
diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php
index 4130e70c..c451ed77 100644
--- a/includes/api/ApiFormatTxt.php
+++ b/includes/api/ApiFormatTxt.php
@@ -26,6 +26,7 @@
/**
* API Text output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatTxt extends ApiFormatBase {
@@ -38,10 +39,11 @@ class ApiFormatTxt extends ApiFormatBase {
}
public function execute() {
+ $this->markDeprecated();
$this->printText( print_r( $this->getResultData(), true ) );
}
public function getDescription() {
- return 'Output data in PHP\'s print_r() format' . parent::getDescription();
+ return 'DEPRECATED! Output data in PHP\'s print_r() format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index 5685d937..ba90c260 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -26,6 +26,7 @@
/**
* API WDDX output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatWddx extends ApiFormatBase {
@@ -35,13 +36,17 @@ class ApiFormatWddx extends ApiFormatBase {
}
public function execute() {
+ $this->markDeprecated();
+
// Some versions of PHP have a broken wddx_serialize_value, see
// PHP bug 45314. Test encoding an affected character (U+00A0)
// to avoid this.
- $expected = "<wddxPacket version='1.0'><header/><data><string>\xc2\xa0</string></data></wddxPacket>";
+ $expected =
+ "<wddxPacket version='1.0'><header/><data><string>\xc2\xa0</string></data></wddxPacket>";
if ( function_exists( 'wddx_serialize_value' )
- && !$this->getIsHtml()
- && wddx_serialize_value( "\xc2\xa0" ) == $expected ) {
+ && !$this->getIsHtml()
+ && wddx_serialize_value( "\xc2\xa0" ) == $expected
+ ) {
$this->printText( wddx_serialize_value( $this->getResultData() ) );
} else {
// Don't do newlines and indentation if we weren't asked
@@ -60,8 +65,8 @@ class ApiFormatWddx extends ApiFormatBase {
/**
* Recursively go through the object and output its data in WDDX format.
- * @param $elemValue
- * @param $indent int
+ * @param mixed $elemValue
+ * @param int $indent
*/
function slowWddxPrinter( $elemValue, $indent = 0 ) {
$indstr = ( $this->getIsHtml() ? str_repeat( ' ', $indent ) : '' );
@@ -105,6 +110,6 @@ class ApiFormatWddx extends ApiFormatBase {
}
public function getDescription() {
- return 'Output data in WDDX format' . parent::getDescription();
+ return 'DEPRECATED! Output data in WDDX format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index 4ec149c0..b3d59379 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -69,7 +69,7 @@ class ApiFormatXml extends ApiFormatBase {
$this->printText(
self::recXmlPrint( $this->mRootElemName,
$data,
- $this->getIsHtml() ? - 2 : null
+ $this->getIsHtml() ? -2 : null
)
);
}
@@ -111,9 +111,9 @@ class ApiFormatXml extends ApiFormatBase {
* @note The method is recursive, so the same rules apply to any
* sub-arrays.
*
- * @param $elemName
- * @param $elemValue
- * @param $indent
+ * @param string $elemName
+ * @param mixed $elemValue
+ * @param int $indent
*
* @return string
*/
@@ -147,6 +147,15 @@ class ApiFormatXml extends ApiFormatBase {
$subElemIndName = null;
}
+ if ( isset( $elemValue['_subelements'] ) ) {
+ foreach ( $elemValue['_subelements'] as $subElemId ) {
+ if ( isset( $elemValue[$subElemId] ) && !is_array( $elemValue[$subElemId] ) ) {
+ $elemValue[$subElemId] = array( '*' => $elemValue[$subElemId] );
+ }
+ }
+ unset( $elemValue['_subelements'] );
+ }
+
$indElements = array();
$subElements = array();
foreach ( $elemValue as $subElemId => & $subElemValue ) {
@@ -156,11 +165,19 @@ class ApiFormatXml extends ApiFormatBase {
} elseif ( is_array( $subElemValue ) ) {
$subElements[$subElemId] = $subElemValue;
unset( $elemValue[$subElemId] );
+ } elseif ( is_bool( $subElemValue ) ) {
+ // treat true as empty string, skip false in xml format
+ if ( $subElemValue === true ) {
+ $subElemValue = '';
+ } else {
+ unset( $elemValue[$subElemId] );
+ }
}
}
if ( is_null( $subElemIndName ) && count( $indElements ) ) {
- ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()." );
+ ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys " .
+ "without _element value. Use ApiResult::setIndexedTagName()." );
}
if ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) {
@@ -193,6 +210,7 @@ class ApiFormatXml extends ApiFormatBase {
$retval .= $indstr . Xml::element( $elemName, null, $elemValue );
}
}
+
return $retval;
}
@@ -200,17 +218,21 @@ class ApiFormatXml extends ApiFormatBase {
$nt = Title::newFromText( $this->mXslt );
if ( is_null( $nt ) || !$nt->exists() ) {
$this->setWarning( 'Invalid or non-existent stylesheet specified' );
+
return;
}
if ( $nt->getNamespace() != NS_MEDIAWIKI ) {
$this->setWarning( 'Stylesheet should be in the MediaWiki namespace.' );
+
return;
}
- if ( substr( $nt->getText(), - 4 ) !== '.xsl' ) {
+ if ( substr( $nt->getText(), -4 ) !== '.xsl' ) {
$this->setWarning( 'Stylesheet should have .xsl extension.' );
+
return;
}
- $this->printText( '<?xml-stylesheet href="' . htmlspecialchars( $nt->getLocalURL( 'action=raw' ) ) . '" type="text/xsl" ?>' );
+ $this->printText( '<?xml-stylesheet href="' .
+ htmlspecialchars( $nt->getLocalURL( 'action=raw' ) ) . '" type="text/xsl" ?>' );
}
public function getAllowedParams() {
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index 700d4a5e..3798f894 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -26,6 +26,7 @@
/**
* API YAML output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatYaml extends ApiFormatJson {
@@ -34,7 +35,12 @@ class ApiFormatYaml extends ApiFormatJson {
return 'application/yaml';
}
+ public function execute() {
+ $this->markDeprecated();
+ parent::execute();
+ }
+
public function getDescription() {
- return 'Output data in YAML format' . ApiFormatBase::getDescription();
+ return 'DEPRECATED! Output data in YAML format' . ApiFormatBase::getDescription();
}
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index 9cafc5bb..bcd6c12e 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -30,7 +30,6 @@
* @ingroup API
*/
class ApiHelp extends ApiBase {
-
/**
* Module for displaying help
*/
@@ -52,6 +51,7 @@ class ApiHelp extends ApiBase {
}
if ( is_array( $params['querymodules'] ) ) {
+ $this->logFeatureUsage( 'action=help&querymodules' );
$queryModules = $params['querymodules'];
foreach ( $queryModules as $m ) {
$modules[] = 'query+' . $m;
@@ -68,19 +68,22 @@ class ApiHelp extends ApiBase {
// In case the '+' was typed into URL, it resolves as a space
$subNames = explode( ' ', $m );
}
+
$module = $this->getMain();
- for ( $i = 0; $i < count( $subNames ); $i++ ) {
+ $subNamesCount = count( $subNames );
+ for ( $i = 0; $i < $subNamesCount; $i++ ) {
$subs = $module->getModuleManager();
if ( $subs === null ) {
$module = null;
} else {
$module = $subs->getModule( $subNames[$i] );
}
+
if ( $module === null ) {
if ( count( $subNames ) === 2
- && $i === 1
- && $subNames[0] === 'query'
- && in_array( $subNames[1], $queryModules )
+ && $i === 1
+ && $subNames[0] === 'query'
+ && in_array( $subNames[1], $queryModules )
) {
// Legacy: This is one of the renamed 'querymodule=...' parameters,
// do not use '+' notation in the output, use submodule's name instead.
@@ -94,6 +97,7 @@ class ApiHelp extends ApiBase {
$type = $subs->getModuleGroup( $subNames[$i] );
}
}
+
if ( $module !== null ) {
$r[] = $this->buildModuleHelp( $module, $type );
}
@@ -104,8 +108,8 @@ class ApiHelp extends ApiBase {
}
/**
- * @param $module ApiBase
- * @param $type String What type of request is this? e.g. action, query, list, prop, meta, format
+ * @param ApiBase $module
+ * @param string $type What type of request is this? e.g. action, query, list, prop, meta, format
* @return string
*/
private function buildModuleHelp( $module, $type ) {
@@ -141,21 +145,25 @@ class ApiHelp extends ApiBase {
public function getParamDescription() {
return array(
- 'modules' => 'List of module names (value of the action= parameter). Can specify submodules with a \'+\'',
- 'querymodules' => 'Use modules=query+value instead. List of query module names (value of prop=, meta= or list= parameter)',
+ 'modules' => 'List of module names (value of the action= parameter). ' .
+ 'Can specify submodules with a \'+\'',
+ 'querymodules' => 'Use modules=query+value instead. List of query ' .
+ 'module names (value of prop=, meta= or list= parameter)',
);
}
public function getDescription() {
- return 'Display this help screen. Or the help screen for the specified module';
+ return 'Display this help screen. Or the help screen for the specified module.';
}
public function getExamples() {
return array(
'api.php?action=help' => 'Whole help page',
'api.php?action=help&modules=protect' => 'Module (action) help page',
- 'api.php?action=help&modules=query+categorymembers' => 'Help for the query/categorymembers module',
- 'api.php?action=help&modules=login|query+info' => 'Help for the login and query/info modules',
+ 'api.php?action=help&modules=query+categorymembers'
+ => 'Help for the query/categorymembers module',
+ 'api.php?action=help&modules=login|query+info'
+ => 'Help for the login and query/info modules',
);
}
diff --git a/includes/api/ApiImageRotate.php b/includes/api/ApiImageRotate.php
index 7a60e831..20396dd7 100644
--- a/includes/api/ApiImageRotate.php
+++ b/includes/api/ApiImageRotate.php
@@ -24,16 +24,12 @@
class ApiImageRotate extends ApiBase {
private $mPageSet = null;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Add all items from $values into the result
- * @param array $result output
- * @param array $values values to add
- * @param string $flag the name of the boolean flag to mark this element
- * @param string $name if given, name of the value
+ * @param array $result Output
+ * @param array $values Values to add
+ * @param string $flag The name of the boolean flag to mark this element
+ * @param string $name If given, name of the value
*/
private static function addValues( array &$result, $values, $flag = null, $name = null ) {
foreach ( $values as $val ) {
@@ -56,6 +52,8 @@ class ApiImageRotate extends ApiBase {
$params = $this->extractRequestParams();
$rotation = $params['rotation'];
+ $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+
$pageSet = $this->getPageSet();
$pageSet->execute();
@@ -135,6 +133,7 @@ class ApiImageRotate extends ApiBase {
$apiResult = $this->getResult();
$apiResult->setIndexedTagName( $result, 'page' );
$apiResult->addValue( null, $this->getModuleName(), $result );
+ $apiResult->endContinuation();
}
/**
@@ -145,6 +144,7 @@ class ApiImageRotate extends ApiBase {
if ( $this->mPageSet === null ) {
$this->mPageSet = new ApiPageSet( $this, 0, NS_FILE );
}
+
return $this->mPageSet;
}
@@ -163,6 +163,7 @@ class ApiImageRotate extends ApiBase {
if ( $permissionErrors ) {
// Just return the first error
$msg = $this->parseMsg( $permissionErrors[0] );
+
return $msg['info'];
}
@@ -183,43 +184,30 @@ class ApiImageRotate extends ApiBase {
ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
+ 'continue' => '',
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
}
+
return $result;
}
public function getParamDescription() {
$pageSet = $this->getPageSet();
+
return $pageSet->getFinalParamDescription() + array(
'rotation' => 'Degrees to rotate image clockwise',
- 'token' => 'Edit token. You can get one of these through action=tokens',
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return 'Rotate one or more images';
+ return 'Rotate one or more images.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
- }
-
- public function getPossibleErrors() {
- $pageSet = $this->getPageSet();
- return array_merge(
- parent::getPossibleErrors(),
- $pageSet->getFinalPossibleErrors()
- );
+ return 'csrf';
}
public function getExamples() {
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index f48a822e..b11348e5 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -98,18 +98,13 @@ class ApiImport extends ApiBase {
}
public function getAllowedParams() {
- global $wgImportSources;
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'summary' => null,
'xml' => array(
ApiBase::PARAM_TYPE => 'upload',
),
'interwikisource' => array(
- ApiBase::PARAM_TYPE => $wgImportSources
+ ApiBase::PARAM_TYPE => $this->getConfig()->get( 'ImportSources' ),
),
'interwikipage' => null,
'fullhistory' => false,
@@ -123,7 +118,6 @@ class ApiImport extends ApiBase {
public function getParamDescription() {
return array(
- 'token' => 'Import token obtained through prop=info',
'summary' => 'Import summary',
'xml' => 'Uploaded XML file',
'interwikisource' => 'For interwiki imports: wiki to import from',
@@ -135,17 +129,6 @@ class ApiImport extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- ApiBase::PROP_LIST => true,
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string',
- 'revisions' => 'integer'
- )
- );
- }
-
public function getDescription() {
return array(
'Import a page from another wiki, or an XML file.',
@@ -154,29 +137,14 @@ class ApiImport extends ApiBase {
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'cantimport' ),
- array( 'missingparam', 'interwikipage' ),
- array( 'cantimport-upload' ),
- array( 'import-unknownerror', 'source' ),
- array( 'import-unknownerror', 'result' ),
- array( 'import-rootpage-nosubpage', 'namespace' ),
- array( 'import-rootpage-invalid' ),
- ) );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&namespace=100&fullhistory=&token=123ABC'
+ 'api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&' .
+ 'namespace=100&fullhistory=&token=123ABC'
=> 'Import [[meta:Help:Parserfunctions]] to namespace 100 with full history',
);
}
@@ -194,11 +162,11 @@ class ApiImportReporter extends ImportReporter {
private $mResultArr = array();
/**
- * @param $title Title
- * @param $origTitle Title
- * @param $revisionCount int
- * @param $successCount int
- * @param $pageInfo
+ * @param Title $title
+ * @param Title $origTitle
+ * @param int $revisionCount
+ * @param int $successCount
+ * @param array $pageInfo
* @return void
*/
function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index b51d441d..976f4c12 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -32,7 +32,7 @@
*/
class ApiLogin extends ApiBase {
- public function __construct( $main, $action ) {
+ public function __construct( ApiMain $main, $action ) {
parent::__construct( $main, $action, 'lg' );
}
@@ -52,6 +52,7 @@ class ApiLogin extends ApiBase {
'result' => 'Aborted',
'reason' => 'Cannot log in when using a callback',
) );
+
return;
}
@@ -78,15 +79,12 @@ class ApiLogin extends ApiBase {
$loginForm = new LoginForm();
$loginForm->setContext( $context );
- global $wgCookiePrefix, $wgPasswordAttemptThrottle;
-
$authRes = $loginForm->authenticateUserData();
switch ( $authRes ) {
case LoginForm::SUCCESS:
$user = $context->getUser();
$this->getContext()->setUser( $user );
- $user->setOption( 'rememberpassword', 1 );
- $user->setCookies( $this->getRequest() );
+ $user->setCookies( $this->getRequest(), null, true );
ApiQueryInfo::resetTokenCache();
@@ -100,14 +98,14 @@ class ApiLogin extends ApiBase {
$result['lguserid'] = intval( $user->getId() );
$result['lgusername'] = $user->getName();
$result['lgtoken'] = $user->getToken();
- $result['cookieprefix'] = $wgCookiePrefix;
+ $result['cookieprefix'] = $this->getConfig()->get( 'CookiePrefix' );
$result['sessionid'] = session_id();
break;
case LoginForm::NEED_TOKEN:
$result['result'] = 'NeedToken';
$result['token'] = $loginForm->getLoginToken();
- $result['cookieprefix'] = $wgCookiePrefix;
+ $result['cookieprefix'] = $this->getConfig()->get( 'CookiePrefix' );
$result['sessionid'] = session_id();
break;
@@ -131,7 +129,9 @@ class ApiLogin extends ApiBase {
$result['result'] = 'NotExists';
break;
- case LoginForm::RESET_PASS: // bug 20223 - Treat a temporary password as wrong. Per SpecialUserLogin - "The e-mailed temporary password should not be used for actual logins;"
+ // bug 20223 - Treat a temporary password as wrong. Per SpecialUserLogin:
+ // The e-mailed temporary password should not be used for actual logins.
+ case LoginForm::RESET_PASS:
case LoginForm::WRONG_PASS:
$result['result'] = 'WrongPass';
break;
@@ -147,7 +147,8 @@ class ApiLogin extends ApiBase {
case LoginForm::THROTTLED:
$result['result'] = 'Throttled';
- $result['wait'] = intval( $wgPasswordAttemptThrottle['seconds'] );
+ $throttle = $this->getConfig()->get( 'PasswordAttemptThrottle' );
+ $result['wait'] = intval( $throttle['seconds'] );
break;
case LoginForm::USER_BLOCKED:
@@ -192,92 +193,16 @@ class ApiLogin extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'NeedToken',
- 'WrongToken',
- 'NoName',
- 'Illegal',
- 'WrongPluginPass',
- 'NotExists',
- 'WrongPass',
- 'EmptyPass',
- 'CreateBlocked',
- 'Throttled',
- 'Blocked',
- 'Aborted'
- )
- ),
- 'lguserid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'lgusername' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'lgtoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'cookieprefix' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'sessionid' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'token' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'details' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'wait' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'reason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
return array(
- 'Log in and get the authentication tokens. ',
- 'In the event of a successful log-in, a cookie will be attached',
- 'to your session. In the event of a failed log-in, you will not ',
- 'be able to attempt another log-in through this method for 5 seconds.',
- 'This is to prevent password guessing by automated password crackers'
+ 'Log in and get the authentication tokens.',
+ 'In the event of a successful log-in, a cookie will be attached to your session.',
+ 'In the event of a failed log-in, you will not be able to attempt another log-in',
+ 'through this method for 5 seconds. This is to prevent password guessing by',
+ 'automated password crackers.'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'NeedToken', 'info' => 'You need to resubmit your login with the specified token. See https://bugzilla.wikimedia.org/show_bug.cgi?id=23076' ),
- array( 'code' => 'WrongToken', 'info' => 'You specified an invalid token' ),
- array( 'code' => 'NoName', 'info' => 'You didn\'t set the lgname parameter' ),
- array( 'code' => 'Illegal', 'info' => ' You provided an illegal username' ),
- array( 'code' => 'NotExists', 'info' => ' The username you provided doesn\'t exist' ),
- array( 'code' => 'EmptyPass', 'info' => ' You didn\'t set the lgpassword parameter or you left it empty' ),
- array( 'code' => 'WrongPass', 'info' => ' The password you provided is incorrect' ),
- array( 'code' => 'WrongPluginPass', 'info' => 'Same as "WrongPass", returned when an authentication plugin rather than MediaWiki itself rejected the password' ),
- array( 'code' => 'CreateBlocked', 'info' => 'The wiki tried to automatically create a new account for you, but your IP address has been blocked from account creation' ),
- array( 'code' => 'Throttled', 'info' => 'You\'ve logged in too many times in a short time' ),
- array( 'code' => 'Blocked', 'info' => 'User is blocked' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=login&lgname=user&lgpassword=password'
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index 2ba92a63..324f4b2f 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -50,16 +50,12 @@ class ApiLogout extends ApiBase {
return array();
}
- public function getResultProperties() {
- return array();
- }
-
public function getParamDescription() {
return array();
}
public function getDescription() {
- return 'Log out and clear session data';
+ return 'Log out and clear session data.';
}
public function getExamples() {
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index ea2fcc78..119d7b48 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -39,7 +39,6 @@
* @ingroup API
*/
class ApiMain extends ApiBase {
-
/**
* When no format parameter is given, this format will be used
*/
@@ -57,6 +56,7 @@ class ApiMain extends ApiBase {
'parse' => 'ApiParse',
'opensearch' => 'ApiOpenSearch',
'feedcontributions' => 'ApiFeedContributions',
+ 'feedrecentchanges' => 'ApiFeedRecentChanges',
'feedwatchlist' => 'ApiFeedWatchlist',
'help' => 'ApiHelp',
'paraminfo' => 'ApiParamInfo',
@@ -81,9 +81,11 @@ class ApiMain extends ApiBase {
'watch' => 'ApiWatch',
'patrol' => 'ApiPatrol',
'import' => 'ApiImport',
+ 'clearhasmsg' => 'ApiClearHasMsg',
'userrights' => 'ApiUserrights',
'options' => 'ApiOptions',
'imagerotate' => 'ApiImageRotate',
+ 'revisiondelete' => 'ApiRevisionDelete',
);
/**
@@ -110,6 +112,7 @@ class ApiMain extends ApiBase {
'none' => 'ApiFormatNone',
);
+ // @codingStandardsIgnoreStart String contenation on "msg" not allowed to break long line
/**
* List of user roles that are specifically relevant to the API.
* array( 'right' => array ( 'msg' => 'Some message with a $1',
@@ -126,6 +129,7 @@ class ApiMain extends ApiBase {
'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 )
)
);
+ // @codingStandardsIgnoreEnd
/**
* @var ApiFormatBase
@@ -144,8 +148,9 @@ class ApiMain extends ApiBase {
/**
* Constructs an instance of ApiMain that utilizes the module and format specified by $request.
*
- * @param $context IContextSource|WebRequest - if this is an instance of FauxRequest, errors are thrown and no printing occurs
- * @param bool $enableWrite should be set to true if the api may modify data
+ * @param IContextSource|WebRequest $context If this is an instance of
+ * FauxRequest, errors are thrown and no printing occurs
+ * @param bool $enableWrite Should be set to true if the api may modify data
*/
public function __construct( $context = null, $enableWrite = false ) {
if ( $context === null ) {
@@ -182,16 +187,17 @@ class ApiMain extends ApiBase {
}
}
- global $wgAPIModules;
+ $config = $this->getConfig();
$this->mModuleMgr = new ApiModuleManager( $this );
$this->mModuleMgr->addModules( self::$Modules, 'action' );
- $this->mModuleMgr->addModules( $wgAPIModules, 'action' );
+ $this->mModuleMgr->addModules( $config->get( 'APIModules' ), 'action' );
$this->mModuleMgr->addModules( self::$Formats, 'format' );
+ $this->mModuleMgr->addModules( $config->get( 'APIFormatModules' ), 'format' );
$this->mResult = new ApiResult( $this );
$this->mEnableWrite = $enableWrite;
- $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
+ $this->mSquidMaxage = -1; // flag for executeActionWithErrorHandling()
$this->mCommit = false;
}
@@ -233,7 +239,7 @@ class ApiMain extends ApiBase {
/**
* Set how long the response should be cached.
*
- * @param $maxage
+ * @param int $maxage
*/
public function setCacheMaxAge( $maxage ) {
$this->setCacheControl( array(
@@ -270,6 +276,7 @@ class ApiMain extends ApiBase {
public function setCacheMode( $mode ) {
if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) {
wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"\n" );
+
// Ignore for forwards-compatibility
return;
}
@@ -278,6 +285,7 @@ class ApiMain extends ApiBase {
// Private wiki, only private headers
if ( $mode !== 'private' ) {
wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" );
+
return;
}
}
@@ -287,16 +295,6 @@ class ApiMain extends ApiBase {
}
/**
- * @deprecated since 1.17 Private caching is now the default, so there is usually no
- * need to call this function. If there is a need, you can use
- * $this->setCacheMode('private')
- */
- public function setCachePrivate() {
- wfDeprecated( __METHOD__, '1.17' );
- $this->setCacheMode( 'private' );
- }
-
- /**
* Set directives (key/value pairs) for the Cache-Control header.
* Boolean values will be formatted as such, by including or omitting
* without an equals sign.
@@ -304,31 +302,16 @@ class ApiMain extends ApiBase {
* Cache control values set here will only be used if the cache mode is not
* private, see setCacheMode().
*
- * @param $directives array
+ * @param array $directives
*/
public function setCacheControl( $directives ) {
$this->mCacheControl = $directives + $this->mCacheControl;
}
/**
- * Make sure Vary: Cookie and friends are set. Use this when the output of a request
- * may be cached for anons but may not be cached for logged-in users.
- *
- * WARNING: This function must be called CONSISTENTLY for a given URL. This means that a
- * given URL must either always or never call this function; if it sometimes does and
- * sometimes doesn't, stuff will break.
- *
- * @deprecated since 1.17 Use setCacheMode( 'anon-public-user-private' )
- */
- public function setVaryCookie() {
- wfDeprecated( __METHOD__, '1.17' );
- $this->setCacheMode( 'anon-public-user-private' );
- }
-
- /**
* Create an instance of an output formatter by its name
*
- * @param $format string
+ * @param string $format
*
* @return ApiFormatBase
*/
@@ -337,6 +320,7 @@ class ApiMain extends ApiBase {
if ( $printer === null ) {
$this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
}
+
return $printer;
}
@@ -379,37 +363,7 @@ class ApiMain extends ApiBase {
try {
$this->executeAction();
} catch ( Exception $e ) {
- // Allow extra cleanup and logging
- wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
-
- // Log it
- if ( !( $e instanceof UsageException ) ) {
- MWExceptionHandler::logException( $e );
- }
-
- // Handle any kind of exception by outputting properly formatted error message.
- // If this fails, an unhandled exception should be thrown so that global error
- // handler will process and log it.
-
- $errCode = $this->substituteResultWithError( $e );
-
- // Error results should not be cached
- $this->setCacheMode( 'private' );
-
- $response = $this->getRequest()->response();
- $headerStr = 'MediaWiki-API-Error: ' . $errCode;
- if ( $e->getCode() === 0 ) {
- $response->header( $headerStr );
- } else {
- $response->header( $headerStr, true, $e->getCode() );
- }
-
- // Reset and print just the error message
- ob_clean();
-
- // If the error occurred during printing, do a printer->profileOut()
- $this->mPrinter->safeProfileOut();
- $this->printResult( true );
+ $this->handleException( $e );
}
// Log the request whether or not there was an error
@@ -427,6 +381,79 @@ class ApiMain extends ApiBase {
}
/**
+ * Handle an exception as an API response
+ *
+ * @since 1.23
+ * @param Exception $e
+ */
+ protected function handleException( Exception $e ) {
+ // Bug 63145: Rollback any open database transactions
+ if ( !( $e instanceof UsageException ) ) {
+ // UsageExceptions are intentional, so don't rollback if that's the case
+ MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+ }
+
+ // Allow extra cleanup and logging
+ wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
+
+ // Log it
+ if ( !( $e instanceof UsageException ) ) {
+ MWExceptionHandler::logException( $e );
+ }
+
+ // Handle any kind of exception by outputting properly formatted error message.
+ // If this fails, an unhandled exception should be thrown so that global error
+ // handler will process and log it.
+
+ $errCode = $this->substituteResultWithError( $e );
+
+ // Error results should not be cached
+ $this->setCacheMode( 'private' );
+
+ $response = $this->getRequest()->response();
+ $headerStr = 'MediaWiki-API-Error: ' . $errCode;
+ if ( $e->getCode() === 0 ) {
+ $response->header( $headerStr );
+ } else {
+ $response->header( $headerStr, true, $e->getCode() );
+ }
+
+ // Reset and print just the error message
+ ob_clean();
+
+ // If the error occurred during printing, do a printer->profileOut()
+ $this->mPrinter->safeProfileOut();
+ $this->printResult( true );
+ }
+
+ /**
+ * Handle an exception from the ApiBeforeMain hook.
+ *
+ * This tries to print the exception as an API response, to be more
+ * friendly to clients. If it fails, it will rethrow the exception.
+ *
+ * @since 1.23
+ * @param Exception $e
+ */
+ public static function handleApiBeforeMainException( Exception $e ) {
+ ob_start();
+
+ try {
+ $main = new self( RequestContext::getMain(), false );
+ $main->handleException( $e );
+ } catch ( Exception $e2 ) {
+ // Nope, even that didn't work. Punt.
+ throw $e;
+ }
+
+ // Log the request and reset cache headers
+ $main->logRequest( 0 );
+ $main->sendCacheHeaders();
+
+ ob_end_flush();
+ }
+
+ /**
* Check the &origin= query parameter against the Origin: HTTP header and respond appropriately.
*
* If no origin parameter is present, nothing happens.
@@ -439,8 +466,6 @@ class ApiMain extends ApiBase {
* @return bool False if the caller should abort (403 case), true otherwise (all other cases)
*/
protected function handleCORS() {
- global $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions;
-
$originParam = $this->getParameter( 'origin' ); // defaults to null
if ( $originParam === null ) {
// No origin parameter, nothing to do
@@ -456,6 +481,7 @@ class ApiMain extends ApiBase {
} else {
$origins = explode( ' ', $originHeader );
}
+
if ( !in_array( $originParam, $origins ) ) {
// origin parameter set but incorrect
// Send a 403 response
@@ -463,13 +489,23 @@ class ApiMain extends ApiBase {
$response->header( "HTTP/1.1 403 $message", true, 403 );
$response->header( 'Cache-Control: no-cache' );
echo "'origin' parameter does not match Origin header\n";
+
return false;
}
- if ( self::matchOrigin( $originParam, $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions ) ) {
+
+ $config = $this->getConfig();
+ $matchOrigin = self::matchOrigin(
+ $originParam,
+ $config->get( 'CrossSiteAJAXdomains' ),
+ $config->get( 'CrossSiteAJAXdomainExceptions' )
+ );
+
+ if ( $matchOrigin ) {
$response->header( "Access-Control-Allow-Origin: $originParam" );
$response->header( 'Access-Control-Allow-Credentials: true' );
$this->getOutput()->addVaryHeader( 'Origin' );
}
+
return true;
}
@@ -478,7 +514,8 @@ class ApiMain extends ApiBase {
* @param string $value Origin header
* @param array $rules Set of wildcard rules
* @param array $exceptions Set of wildcard rules
- * @return bool True if $value matches a rule in $rules and doesn't match any rules in $exceptions, false otherwise
+ * @return bool True if $value matches a rule in $rules and doesn't match
+ * any rules in $exceptions, false otherwise
*/
protected static function matchOrigin( $value, $rules, $exceptions ) {
foreach ( $rules as $rule ) {
@@ -489,9 +526,11 @@ class ApiMain extends ApiBase {
return false;
}
}
+
return true;
}
}
+
return false;
}
@@ -510,15 +549,17 @@ class ApiMain extends ApiBase {
array( '.*?', '.' ),
$wildcard
);
+
return "/^https?:\/\/$wildcard$/";
}
protected function sendCacheHeaders() {
- global $wgUseXVO, $wgVaryOnXFP;
$response = $this->getRequest()->response();
$out = $this->getOutput();
- if ( $wgVaryOnXFP ) {
+ $config = $this->getConfig();
+
+ if ( $config->get( 'VaryOnXFP' ) ) {
$out->addVaryHeader( 'X-Forwarded-Proto' );
}
@@ -527,10 +568,11 @@ class ApiMain extends ApiBase {
return;
}
+ $useXVO = $config->get( 'UseXVO' );
if ( $this->mCacheMode == 'anon-public-user-private' ) {
$out->addVaryHeader( 'Cookie' );
$response->header( $out->getVaryHeader() );
- if ( $wgUseXVO ) {
+ if ( $useXVO ) {
$response->header( $out->getXVO() );
if ( $out->haveCacheVaryCookies() ) {
// Logged in, mark this request private
@@ -542,13 +584,14 @@ class ApiMain extends ApiBase {
// Logged in or otherwise has session (e.g. anonymous users who have edited)
// Mark request private
$response->header( 'Cache-Control: private' );
+
return;
} // else no XVO and anonymous, send public headers below
}
// Send public headers
$response->header( $out->getVaryHeader() );
- if ( $wgUseXVO ) {
+ if ( $useXVO ) {
$response->header( $out->getXVO() );
}
@@ -565,6 +608,7 @@ class ApiMain extends ApiBase {
// Sending a Vary header in this case is harmless, and protects us
// against conditional calls of setCacheMaxAge().
$response->header( 'Cache-Control: private' );
+
return;
}
@@ -596,13 +640,12 @@ class ApiMain extends ApiBase {
/**
* Replace the result data with the information about an exception.
* Returns the error code
- * @param $e Exception
+ * @param Exception $e
* @return string
*/
protected function substituteResultWithError( $e ) {
- global $wgShowHostnames;
-
$result = $this->getResult();
+
// Printer may not be initialized if the extractRequestParams() fails for the main module
if ( !isset( $this->mPrinter ) ) {
// The printer has not been created yet. Try to manually get formatter value.
@@ -612,11 +655,20 @@ class ApiMain extends ApiBase {
}
$this->mPrinter = $this->createPrinterByName( $value );
- if ( $this->mPrinter->getNeedsRawData() ) {
- $result->setRawMode();
- }
}
+ // Printer may not be able to handle errors. This is particularly
+ // likely if the module returns something for getCustomPrinter().
+ if ( !$this->mPrinter->canPrintErrors() ) {
+ $this->mPrinter->safeProfileOut();
+ $this->mPrinter = $this->createPrinterByName( self::API_DEFAULT_FORMAT );
+ }
+
+ // Update raw mode flag for the selected printer.
+ $result->setRawMode( $this->mPrinter->getNeedsRawData() );
+
+ $config = $this->getConfig();
+
if ( $e instanceof UsageException ) {
// User entered incorrect parameters - print usage screen
$errMessage = $e->getMessageArray();
@@ -626,9 +678,8 @@ class ApiMain extends ApiBase {
ApiResult::setContent( $errMessage, $this->makeHelpMsg() );
}
} else {
- global $wgShowSQLErrors, $wgShowExceptionDetails;
// Something is seriously wrong
- if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
+ if ( ( $e instanceof DBQueryError ) && !$config->get( 'ShowSQLErrors' ) ) {
$info = 'Database query error';
} else {
$info = "Exception Caught: {$e->getMessage()}";
@@ -638,7 +689,10 @@ class ApiMain extends ApiBase {
'code' => 'internal_api_error_' . get_class( $e ),
'info' => $info,
);
- ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' );
+ ApiResult::setContent(
+ $errMessage,
+ $config->get( 'ShowExceptionDetails' ) ? "\n\n{$e->getTraceAsString()}\n\n" : ''
+ );
}
// Remember all the warnings to re-add them later
@@ -646,21 +700,20 @@ class ApiMain extends ApiBase {
$warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null;
$result->reset();
- $result->disableSizeCheck();
// Re-add the id
$requestid = $this->getParameter( 'requestid' );
if ( !is_null( $requestid ) ) {
- $result->addValue( null, 'requestid', $requestid );
+ $result->addValue( null, 'requestid', $requestid, ApiResult::NO_SIZE_CHECK );
}
- if ( $wgShowHostnames ) {
+ if ( $config->get( 'ShowHostnames' ) ) {
// servedby is especially useful when debugging errors
- $result->addValue( null, 'servedby', wfHostName() );
+ $result->addValue( null, 'servedby', wfHostName(), ApiResult::NO_SIZE_CHECK );
}
if ( $warnings !== null ) {
- $result->addValue( null, 'warnings', $warnings );
+ $result->addValue( null, 'warnings', $warnings, ApiResult::NO_SIZE_CHECK );
}
- $result->addValue( null, 'error', $errMessage );
+ $result->addValue( null, 'error', $errMessage, ApiResult::NO_SIZE_CHECK );
return $errMessage['code'];
}
@@ -670,8 +723,6 @@ class ApiMain extends ApiBase {
* @return array
*/
protected function setupExecuteAction() {
- global $wgShowHostnames;
-
// First add the id to the top element
$result = $this->getResult();
$requestid = $this->getParameter( 'requestid' );
@@ -679,13 +730,18 @@ class ApiMain extends ApiBase {
$result->addValue( null, 'requestid', $requestid );
}
- if ( $wgShowHostnames ) {
+ if ( $this->getConfig()->get( 'ShowHostnames' ) ) {
$servedby = $this->getParameter( 'servedby' );
if ( $servedby ) {
$result->addValue( null, 'servedby', wfHostName() );
}
}
+ if ( $this->getParameter( 'curtimestamp' ) ) {
+ $result->addValue( null, 'curtimestamp', wfTimestamp( TS_ISO_8601, time() ),
+ ApiResult::NO_SIZE_CHECK );
+ }
+
$params = $this->extractRequestParams();
$this->mAction = $params['action'];
@@ -709,30 +765,53 @@ class ApiMain extends ApiBase {
}
$moduleParams = $module->extractRequestParams();
- // Die if token required, but not provided
- $salt = $module->getTokenSalt();
- if ( $salt !== false ) {
+ // Check token, if necessary
+ if ( $module->needsToken() === true ) {
+ throw new MWException(
+ "Module '{$module->getModuleName()}' must be updated for the new token handling. " .
+ "See documentation for ApiBase::needsToken for details."
+ );
+ }
+ if ( $module->needsToken() ) {
+ if ( !$module->mustBePosted() ) {
+ throw new MWException(
+ "Module '{$module->getModuleName()}' must require POST to use tokens."
+ );
+ }
+
if ( !isset( $moduleParams['token'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'token' ) );
- } else {
- if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getContext()->getRequest() ) ) {
- $this->dieUsageMsg( 'sessionfailure' );
- }
+ }
+
+ if ( !$this->getConfig()->get( 'DebugAPI' ) &&
+ array_key_exists(
+ $module->encodeParamName( 'token' ),
+ $this->getRequest()->getQueryValues()
+ )
+ ) {
+ $this->dieUsage(
+ "The '{$module->encodeParamName( 'token' )}' parameter was found in the query string, but must be in the POST body",
+ 'mustposttoken'
+ );
+ }
+
+ if ( !$module->validateToken( $moduleParams['token'], $moduleParams ) ) {
+ $this->dieUsageMsg( 'sessionfailure' );
}
}
+
return $module;
}
/**
* Check the max lag if necessary
- * @param $module ApiBase object: Api module being used
- * @param array $params an array containing the request parameters.
- * @return boolean True on success, false should exit immediately
+ * @param ApiBase $module Api module being used
+ * @param array $params Array an array containing the request parameters.
+ * @return bool True on success, false should exit immediately
*/
protected function checkMaxLag( $module, $params ) {
if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
// Check for maxlag
- global $wgShowHostnames;
$maxLag = $params['maxlag'];
list( $host, $lag ) = wfGetLB()->getMaxLag();
if ( $lag > $maxLag ) {
@@ -741,26 +820,26 @@ class ApiMain extends ApiBase {
$response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
$response->header( 'X-Database-Lag: ' . intval( $lag ) );
- if ( $wgShowHostnames ) {
+ if ( $this->getConfig()->get( 'ShowHostnames' ) ) {
$this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
- } else {
- $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
}
- return false;
+
+ $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
}
}
+
return true;
}
/**
* Check for sufficient permissions to execute
- * @param $module ApiBase An Api module
+ * @param ApiBase $module An Api module
*/
protected function checkExecutePermissions( $module ) {
$user = $this->getUser();
if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) &&
- !$user->isAllowed( 'read' ) )
- {
+ !$user->isAllowed( 'read' )
+ ) {
$this->dieUsageMsg( 'readrequired' );
}
if ( $module->isWriteMode() ) {
@@ -783,9 +862,31 @@ class ApiMain extends ApiBase {
}
/**
+ * Check asserts of the user's rights
+ * @param array $params
+ */
+ protected function checkAsserts( $params ) {
+ if ( isset( $params['assert'] ) ) {
+ $user = $this->getUser();
+ switch ( $params['assert'] ) {
+ case 'user':
+ if ( $user->isAnon() ) {
+ $this->dieUsage( 'Assertion that the user is logged in failed', 'assertuserfailed' );
+ }
+ break;
+ case 'bot':
+ if ( !$user->isAllowed( 'bot' ) ) {
+ $this->dieUsage( 'Assertion that the user has the bot right failed', 'assertbotfailed' );
+ }
+ break;
+ }
+ }
+ }
+
+ /**
* Check POST for external response and setup result printer
- * @param $module ApiBase An Api module
- * @param array $params an array with the request parameters
+ * @param ApiBase $module An Api module
+ * @param array $params An array with the request parameters
*/
protected function setupExternalResponse( $module, $params ) {
if ( !$this->getRequest()->wasPosted() && $module->mustBePosted() ) {
@@ -824,6 +925,8 @@ class ApiMain extends ApiBase {
$this->setupExternalResponse( $module, $params );
}
+ $this->checkAsserts( $params );
+
// Execute
$module->profileIn();
$module->execute();
@@ -843,7 +946,7 @@ class ApiMain extends ApiBase {
/**
* Log the preceding request
- * @param $time Time in seconds
+ * @param int $time Time in seconds
*/
protected function logRequest( $time ) {
$request = $this->getRequest();
@@ -867,25 +970,30 @@ class ApiMain extends ApiBase {
}
}
$s .= "\n";
- wfDebugLog( 'api', $s, false );
+ wfDebugLog( 'api', $s, 'private' );
}
/**
* Encode a value in a format suitable for a space-separated log line.
+ * @param string $s
+ * @return string
*/
protected function encodeRequestLogValue( $s ) {
static $table;
if ( !$table ) {
$chars = ';@$!*(),/:';
- for ( $i = 0; $i < strlen( $chars ); $i++ ) {
+ $numChars = strlen( $chars );
+ for ( $i = 0; $i < $numChars; $i++ ) {
$table[rawurlencode( $chars[$i] )] = $chars[$i];
}
}
+
return strtr( rawurlencode( $s ), $table );
}
/**
* Get the request parameters used in the course of the preceding execute() request
+ * @return array
*/
protected function getParamsUsed() {
return array_keys( $this->mParamsUsed );
@@ -893,19 +1001,35 @@ class ApiMain extends ApiBase {
/**
* Get a request value, and register the fact that it was used, for logging.
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
*/
public function getVal( $name, $default = null ) {
$this->mParamsUsed[$name] = true;
- return $this->getRequest()->getVal( $name, $default );
+
+ $ret = $this->getRequest()->getVal( $name );
+ if ( $ret === null ) {
+ if ( $this->getRequest()->getArray( $name ) !== null ) {
+ // See bug 10262 for why we don't just join( '|', ... ) the
+ // array.
+ $this->setWarning(
+ "Parameter '$name' uses unsupported PHP array syntax"
+ );
+ }
+ $ret = $default;
+ }
+ return $ret;
}
/**
* Get a boolean request value, and register the fact that the parameter
* was used, for logging.
+ * @param string $name
+ * @return bool
*/
public function getCheck( $name ) {
- $this->mParamsUsed[$name] = true;
- return $this->getRequest()->getCheck( $name );
+ return $this->getVal( $name, null ) !== null;
}
/**
@@ -917,6 +1041,7 @@ class ApiMain extends ApiBase {
*/
public function getUpload( $name ) {
$this->mParamsUsed[$name] = true;
+
return $this->getRequest()->getUpload( $name );
}
@@ -948,11 +1073,10 @@ class ApiMain extends ApiBase {
/**
* Print results using the current printer
*
- * @param $isError bool
+ * @param bool $isError
*/
protected function printResult( $isError ) {
- global $wgDebugAPI;
- if ( $wgDebugAPI !== false ) {
+ if ( $this->getConfig()->get( 'DebugAPI' ) !== false ) {
$this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
}
@@ -991,11 +1115,11 @@ class ApiMain extends ApiBase {
return array(
'format' => array(
ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'format' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'action' => array(
ApiBase::PARAM_DFLT => 'help',
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'maxlag' => array(
ApiBase::PARAM_TYPE => 'integer'
@@ -1008,8 +1132,12 @@ class ApiMain extends ApiBase {
ApiBase::PARAM_TYPE => 'integer',
ApiBase::PARAM_DFLT => 0
),
+ 'assert' => array(
+ ApiBase::PARAM_TYPE => array( 'user', 'bot' )
+ ),
'requestid' => null,
'servedby' => false,
+ 'curtimestamp' => false,
'origin' => null,
);
}
@@ -1033,14 +1161,20 @@ class ApiMain extends ApiBase {
),
'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
+ 'assert' => 'Verify the user is logged in if set to "user", or has the bot userright if "bot"',
'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
- 'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error',
+ 'servedby' => 'Include the hostname that served the request in the ' .
+ 'results. Unconditionally shown on error',
+ 'curtimestamp' => 'Include the current timestamp in the result.',
'origin' => array(
- 'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.',
- 'This must be included in any pre-flight request, and therefore must be part of the request URI (not the POST body).',
- 'This must match one of the origins in the Origin: header exactly, so it has to be set to something like http://en.wikipedia.org or https://meta.wikimedia.org .',
- 'If this parameter does not match the Origin: header, a 403 response will be returned.',
- 'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.',
+ 'When accessing the API using a cross-domain AJAX request (CORS), set this to the',
+ 'originating domain. This must be included in any pre-flight request, and',
+ 'therefore must be part of the request URI (not the POST body). This must match',
+ 'one of the origins in the Origin: header exactly, so it has to be set to ',
+ 'something like http://en.wikipedia.org or https://meta.wikimedia.org . If this',
+ 'parameter does not match the Origin: header, a 403 response will be returned. If',
+ 'this parameter matches the Origin: header and the origin is whitelisted, an',
+ 'Access-Control-Allow-Origin header will be set.',
),
);
}
@@ -1054,33 +1188,35 @@ class ApiMain extends ApiBase {
return array(
'',
'',
- '**********************************************************************************************************',
- '** **',
- '** This is an auto-generated MediaWiki API documentation page **',
- '** **',
- '** Documentation and Examples: **',
- '** https://www.mediawiki.org/wiki/API **',
- '** **',
- '**********************************************************************************************************',
+ '**********************************************************************************************',
+ '** **',
+ '** This is an auto-generated MediaWiki API documentation page **',
+ '** **',
+ '** Documentation and Examples: **',
+ '** https://www.mediawiki.org/wiki/API **',
+ '** **',
+ '**********************************************************************************************',
'',
'Status: All features shown on this page should be working, but the API',
' is still in active development, and may change at any time.',
- ' Make sure to monitor our mailing list for any updates',
+ ' Make sure to monitor our mailing list for any updates.',
'',
'Erroneous requests: When erroneous requests are sent to the API, a HTTP header will be sent',
' with the key "MediaWiki-API-Error" and then both the value of the',
- ' header and the error code sent back will be set to the same value',
+ ' header and the error code sent back will be set to the same value.',
'',
' In the case of an invalid action being passed, these will have a value',
- ' of "unknown_action"',
+ ' of "unknown_action".',
'',
- ' For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings',
+ ' For more information see https://www.mediawiki.org' .
+ '/wiki/API:Errors_and_warnings',
'',
'Documentation: https://www.mediawiki.org/wiki/API:Main_page',
'FAQ https://www.mediawiki.org/wiki/API:FAQ',
'Mailing list: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
'Api Announcements: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce',
- 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
+ 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&' .
+ 'bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
'',
'',
'',
@@ -1090,30 +1226,18 @@ class ApiMain extends ApiBase {
}
/**
- * @return array
- */
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'readonlytext' ),
- array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ),
- array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ),
- array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ),
- array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ),
- ) );
- }
-
- /**
* Returns an array of strings with credits for the API
* @return array
*/
protected function getCredits() {
return array(
'API developers:',
- ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-2009)',
- ' Victor Vasiliev - vasilvv @ gmail . com',
- ' Bryan Tong Minh - bryan . tongminh @ gmail . com',
- ' Sam Reed - sam @ reedyboy . net',
- ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012-present)',
+ ' Roan Kattouw (lead developer Sep 2007-2009)',
+ ' Victor Vasiliev',
+ ' Bryan Tong Minh',
+ ' Sam Reed',
+ ' Yuri Astrakhan (creator, lead developer Sep 2006-Sep 2007, 2012-2013)',
+ ' Brad Jorsch (lead developer 2013-now)',
'',
'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
'or file a bug report at https://bugzilla.wikimedia.org/'
@@ -1123,7 +1247,7 @@ class ApiMain extends ApiBase {
/**
* Sets whether the pretty-printer should format *bold* and $italics$
*
- * @param $help bool
+ * @param bool $help
*/
public function setHelp( $help = true ) {
$this->mPrinter->setHelp( $help );
@@ -1135,21 +1259,24 @@ class ApiMain extends ApiBase {
* @return string
*/
public function makeHelpMsg() {
- global $wgMemc, $wgAPICacheHelpTimeout;
+ global $wgMemc;
$this->setHelp();
// Get help text from cache if present
$key = wfMemcKey( 'apihelp', $this->getModuleName(),
str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) );
- if ( $wgAPICacheHelpTimeout > 0 ) {
+
+ $cacheHelpTimeout = $this->getConfig()->get( 'APICacheHelpTimeout' );
+ if ( $cacheHelpTimeout > 0 ) {
$cached = $wgMemc->get( $key );
if ( $cached ) {
return $cached;
}
}
$retval = $this->reallyMakeHelpMsg();
- if ( $wgAPICacheHelpTimeout > 0 ) {
- $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout );
+ if ( $cacheHelpTimeout > 0 ) {
+ $wgMemc->set( $key, $retval, $cacheHelpTimeout );
}
+
return $retval;
}
@@ -1180,7 +1307,7 @@ class ApiMain extends ApiBase {
foreach ( self::$mRights as $right => $rightMsg ) {
$groups = User::getGroupsWithPermission( $right );
$msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg['msg'], $rightMsg['params'] ) .
- "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
+ "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
}
$msg .= "\n$astriks Formats $astriks\n\n";
@@ -1200,8 +1327,9 @@ class ApiMain extends ApiBase {
}
/**
- * @param $module ApiBase
- * @param string $paramName What type of request is this? e.g. action, query, list, prop, meta, format
+ * @param ApiBase $module
+ * @param string $paramName What type of request is this? e.g. action,
+ * query, list, prop, meta, format
* @return string
*/
public static function makeHelpMsgHeader( $module, $paramName ) {
@@ -1234,6 +1362,7 @@ class ApiMain extends ApiBase {
*/
public function getShowVersions() {
wfDeprecated( __METHOD__, '1.21' );
+
return false;
}
@@ -1252,7 +1381,7 @@ class ApiMain extends ApiBase {
*
* @deprecated since 1.21, Use getModuleManager()->addModule() instead.
* @param string $name The identifier for this module.
- * @param $class ApiBase The class where this module is implemented.
+ * @param ApiBase $class The class where this module is implemented.
*/
protected function addModule( $name, $class ) {
$this->getModuleManager()->addModule( $name, 'action', $class );
@@ -1264,7 +1393,7 @@ class ApiMain extends ApiBase {
*
* @deprecated since 1.21, Use getModuleManager()->addModule() instead.
* @param string $name The identifier for this format.
- * @param $class ApiFormatBase The class implementing this format.
+ * @param ApiFormatBase $class The class implementing this format.
*/
protected function addFormat( $name, $class ) {
$this->getModuleManager()->addModule( $name, 'format', $class );
@@ -1307,10 +1436,10 @@ class UsageException extends MWException {
private $mExtraData;
/**
- * @param $message string
- * @param $codestr string
- * @param $code int
- * @param $extradata array|null
+ * @param string $message
+ * @param string $codestr
+ * @param int $code
+ * @param array|null $extradata
*/
public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
parent::__construct( $message, $code );
@@ -1336,6 +1465,7 @@ class UsageException extends MWException {
if ( is_array( $this->mExtraData ) ) {
$result = array_merge( $result, $this->mExtraData );
}
+
return $result;
}
diff --git a/includes/api/ApiModuleManager.php b/includes/api/ApiModuleManager.php
index 100392bf..a0300ab5 100644
--- a/includes/api/ApiModuleManager.php
+++ b/includes/api/ApiModuleManager.php
@@ -33,9 +33,21 @@
*/
class ApiModuleManager extends ContextSource {
+ /**
+ * @var ApiBase
+ */
private $mParent;
+ /**
+ * @var ApiBase[]
+ */
private $mInstances = array();
+ /**
+ * @var null[]
+ */
private $mGroups = array();
+ /**
+ * @var array[]
+ */
private $mModules = array();
/**
@@ -47,13 +59,55 @@ class ApiModuleManager extends ContextSource {
}
/**
- * Add a list of modules to the manager
- * @param array $modules A map of ModuleName => ModuleClass
+ * Add a list of modules to the manager. Each module is described
+ * by a module spec.
+ *
+ * Each module spec is an associative array containing at least
+ * the 'class' key for the module's class, and optionally a
+ * 'factory' key for the factory function to use for the module.
+ *
+ * That factory function will be called with two parameters,
+ * the parent module (an instance of ApiBase, usually ApiMain)
+ * and the name the module was registered under. The return
+ * value must be an instance of the class given in the 'class'
+ * field.
+ *
+ * For backward compatibility, the module spec may also be a
+ * simple string containing the module's class name. In that
+ * case, the class' constructor will be called with the parent
+ * module and module name as parameters, as described above.
+ *
+ * Examples for defining module specs:
+ *
+ * @code
+ * $modules['foo'] = 'ApiFoo';
+ * $modules['bar'] = array(
+ * 'class' => 'ApiBar',
+ * 'factory' => function( $main, $name ) { ... }
+ * );
+ * $modules['xyzzy'] = array(
+ * 'class' => 'ApiXyzzy',
+ * 'factory' => array( 'XyzzyFactory', 'newApiModule' )
+ * );
+ * @endcode
+ *
+ * @param array $modules A map of ModuleName => ModuleSpec; The ModuleSpec
+ * is either a string containing the module's class name, or an associative
+ * array (see above for details).
* @param string $group Which group modules belong to (action,format,...)
*/
public function addModules( array $modules, $group ) {
- foreach ( $modules as $name => $class ) {
- $this->addModule( $name, $group, $class );
+
+ foreach ( $modules as $name => $moduleSpec ) {
+ if ( is_array( $moduleSpec ) ) {
+ $class = $moduleSpec['class'];
+ $factory = ( isset( $moduleSpec['factory'] ) ? $moduleSpec['factory'] : null );
+ } else {
+ $class = $moduleSpec;
+ $factory = null;
+ }
+
+ $this->addModule( $name, $group, $class, $factory );
}
}
@@ -62,49 +116,100 @@ class ApiModuleManager extends ContextSource {
* classes who wish to add their own modules to their lexicon or override the
* behavior of inherent ones.
*
- * @param string $group Name of the module group
* @param string $name The identifier for this module.
+ * @param string $group Name of the module group
* @param string $class The class where this module is implemented.
+ * @param callable|null $factory Callback for instantiating the module.
+ *
+ * @throws InvalidArgumentException
*/
- public function addModule( $name, $group, $class ) {
+ public function addModule( $name, $group, $class, $factory = null ) {
+ if ( !is_string( $name ) ) {
+ throw new InvalidArgumentException( '$name must be a string' );
+ }
+
+ if ( !is_string( $group ) ) {
+ throw new InvalidArgumentException( '$group must be a string' );
+ }
+
+ if ( !is_string( $class ) ) {
+ throw new InvalidArgumentException( '$class must be a string' );
+ }
+
+ if ( $factory !== null && !is_callable( $factory ) ) {
+ throw new InvalidArgumentException( '$factory must be a callable (or null)' );
+ }
+
$this->mGroups[$group] = null;
- $this->mModules[$name] = array( $group, $class );
+ $this->mModules[$name] = array( $group, $class, $factory );
}
/**
* Get module instance by name, or instantiate it if it does not exist
- * @param string $moduleName module name
- * @param string $group optionally validate that the module is in a specific group
- * @param bool $ignoreCache if true, force-creates a new instance and does not cache it
- * @return mixed the new module instance, or null if failed
+ *
+ * @param string $moduleName Module name
+ * @param string $group Optionally validate that the module is in a specific group
+ * @param bool $ignoreCache If true, force-creates a new instance and does not cache it
+ *
+ * @return ApiBase|null The new module instance, or null if failed
*/
public function getModule( $moduleName, $group = null, $ignoreCache = false ) {
if ( !isset( $this->mModules[$moduleName] ) ) {
return null;
}
- $grpCls = $this->mModules[$moduleName];
- if ( $group !== null && $grpCls[0] !== $group ) {
+
+ list( $moduleGroup, $moduleClass, $moduleFactory ) = $this->mModules[$moduleName];
+
+ if ( $group !== null && $moduleGroup !== $group ) {
return null;
}
+
if ( !$ignoreCache && isset( $this->mInstances[$moduleName] ) ) {
// already exists
return $this->mInstances[$moduleName];
} else {
// new instance
- $class = $grpCls[1];
- $instance = new $class ( $this->mParent, $moduleName );
+ $instance = $this->instantiateModule( $moduleName, $moduleClass, $moduleFactory );
+
if ( !$ignoreCache ) {
// cache this instance in case it is needed later
$this->mInstances[$moduleName] = $instance;
}
+
return $instance;
}
}
/**
+ * Instantiate the module using the given class or factory function.
+ *
+ * @param string $name The identifier for this module.
+ * @param string $class The class where this module is implemented.
+ * @param callable|null $factory Callback for instantiating the module.
+ *
+ * @throws MWException
+ * @return ApiBase
+ */
+ private function instantiateModule( $name, $class, $factory = null ) {
+ if ( $factory !== null ) {
+ // create instance from factory
+ $instance = call_user_func( $factory, $this->mParent, $name );
+
+ if ( !$instance instanceof $class ) {
+ throw new MWException( "The factory function for module $name did not return an instance of $class!" );
+ }
+ } else {
+ // create instance from class name
+ $instance = new $class( $this->mParent, $name );
+ }
+
+ return $instance;
+ }
+
+ /**
* Get an array of modules in a specific group or all if no group is set.
- * @param string $group optional group filter
- * @return array list of module names
+ * @param string $group Optional group filter
+ * @return array List of module names
*/
public function getNames( $group = null ) {
if ( $group === null ) {
@@ -116,13 +221,14 @@ class ApiModuleManager extends ContextSource {
$result[] = $name;
}
}
+
return $result;
}
/**
* Create an array of (moduleName => moduleClass) for a specific group or for all.
- * @param string $group name of the group to get or null for all
- * @return array name=>class map
+ * @param string $group Name of the group to get or null for all
+ * @return array Name=>class map
*/
public function getNamesWithClasses( $group = null ) {
$result = array();
@@ -131,34 +237,50 @@ class ApiModuleManager extends ContextSource {
$result[$name] = $grpCls[1];
}
}
+
return $result;
}
/**
+ * Returns the class name of the given module
+ *
+ * @param string $module Module name
+ * @return string|bool class name or false if the module does not exist
+ * @since 1.24
+ */
+ public function getClassName( $module ) {
+ if ( isset( $this->mModules[$module] ) ) {
+ return $this->mModules[$module][1];
+ }
+
+ return false;
+ }
+
+ /**
* Returns true if the specific module is defined at all or in a specific group.
- * @param string $moduleName module name
- * @param string $group group name to check against, or null to check all groups,
- * @return boolean true if defined
+ * @param string $moduleName Module name
+ * @param string $group Group name to check against, or null to check all groups,
+ * @return bool True if defined
*/
public function isDefined( $moduleName, $group = null ) {
if ( isset( $this->mModules[$moduleName] ) ) {
return $group === null || $this->mModules[$moduleName][0] === $group;
- } else {
- return false;
}
+
+ return false;
}
/**
* Returns the group name for the given module
* @param string $moduleName
- * @return string group name or null if missing
+ * @return string Group name or null if missing
*/
public function getModuleGroup( $moduleName ) {
if ( isset( $this->mModules[$moduleName] ) ) {
return $this->mModules[$moduleName][0];
- } else {
- return null;
}
+
+ return null;
}
/**
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index c18036cf..04e931d2 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -61,8 +61,8 @@ class ApiMove extends ApiBase {
if ( $toTitle->getNamespace() == NS_FILE
&& !RepoGroup::singleton()->getLocalRepo()->findFile( $toTitle )
- && wfFindFile( $toTitle ) )
- {
+ && wfFindFile( $toTitle )
+ ) {
if ( !$params['ignorewarnings'] && $user->isAllowed( 'reupload-shared' ) ) {
$this->dieUsageMsg( 'sharedfile-exists' );
} elseif ( !$user->isAllowed( 'reupload-shared' ) ) {
@@ -77,7 +77,11 @@ class ApiMove extends ApiBase {
$this->dieUsageMsg( reset( $retval ) );
}
- $r = array( 'from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason'] );
+ $r = array(
+ 'from' => $fromTitle->getPrefixedText(),
+ 'to' => $toTitle->getPrefixedText(),
+ 'reason' => $params['reason']
+ );
if ( $fromTitle->exists() ) {
//NOTE: we assume that if the old title exists, it's because it was re-created as
@@ -115,7 +119,7 @@ class ApiMove extends ApiBase {
// Move subpages
if ( $params['movesubpages'] ) {
$r['subpages'] = $this->moveSubpages( $fromTitle, $toTitle,
- $params['reason'], $params['noredirect'] );
+ $params['reason'], $params['noredirect'] );
$result->setIndexedTagName( $r['subpages'], 'subpage' );
if ( $params['movetalk'] ) {
@@ -130,8 +134,10 @@ class ApiMove extends ApiBase {
$watch = $params['watchlist'];
} elseif ( $params['watch'] ) {
$watch = 'watch';
+ $this->logFeatureUsage( 'action=move&watch' );
} elseif ( $params['unwatch'] ) {
$watch = 'unwatch';
+ $this->logFeatureUsage( 'action=move&unwatch' );
}
// Watch pages
@@ -144,8 +150,8 @@ class ApiMove extends ApiBase {
/**
* @param Title $fromTitle
* @param Title $toTitle
- * @param $reason
- * @param $noredirect
+ * @param string $reason
+ * @param bool $noredirect
* @return array
*/
public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect ) {
@@ -153,20 +159,21 @@ class ApiMove extends ApiBase {
$success = $fromTitle->moveSubpages( $toTitle, true, $reason, !$noredirect );
if ( isset( $success[0] ) ) {
return array( 'error' => $this->parseMsg( $success ) );
- } else {
- // At least some pages could be moved
- // Report each of them separately
- foreach ( $success as $oldTitle => $newTitle ) {
- $r = array( 'from' => $oldTitle );
- if ( is_array( $newTitle ) ) {
- $r['error'] = $this->parseMsg( reset( $newTitle ) );
- } else {
- // Success
- $r['to'] = $newTitle;
- }
- $retval[] = $r;
+ }
+
+ // At least some pages could be moved
+ // Report each of them separately
+ foreach ( $success as $oldTitle => $newTitle ) {
+ $r = array( 'from' => $oldTitle );
+ if ( is_array( $newTitle ) ) {
+ $r['error'] = $this->parseMsg( reset( $newTitle ) );
+ } else {
+ // Success
+ $r['to'] = $newTitle;
}
+ $retval[] = $r;
}
+
return $retval;
}
@@ -188,10 +195,6 @@ class ApiMove extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => '',
'movetalk' => false,
'movesubpages' => false,
@@ -219,79 +222,35 @@ class ApiMove extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'from' => "Title of the page you want to move. Cannot be used together with {$p}fromid",
'fromid' => "Page ID of the page you want to move. Cannot be used together with {$p}from",
'to' => 'Title you want to rename the page to',
- 'token' => 'A move token previously retrieved through prop=info',
'reason' => 'Reason for the move',
'movetalk' => 'Move the talk page, if it exists',
'movesubpages' => 'Move subpages, if applicable',
'noredirect' => 'Don\'t create a redirect',
'watch' => 'Add the page and the redirect to your watchlist',
'unwatch' => 'Remove the page and the redirect from your watchlist',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
'ignorewarnings' => 'Ignore any warnings'
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'from' => 'string',
- 'to' => 'string',
- 'reason' => 'string',
- 'redirectcreated' => 'boolean',
- 'moveoverredirect' => 'boolean',
- 'talkfrom' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'talkto' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'talkmoveoverredirect' => 'boolean',
- 'talkmove-error-code' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'talkmove-error-info' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
- return 'Move a page';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'from', 'fromid' ) ),
- array(
- array( 'invalidtitle', 'from' ),
- array( 'nosuchpageid', 'fromid' ),
- array( 'notanarticle' ),
- array( 'invalidtitle', 'to' ),
- array( 'sharedfile-exists' ),
- )
- );
+ return 'Move a page.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=move&from=Badtitle&to=Goodtitle&token=123ABC&reason=Misspelled%20title&movetalk=&noredirect='
+ 'api.php?action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
+ 'reason=Misspelled%20title&movetalk=&noredirect='
);
}
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index 315ace37..7fb045e3 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -40,11 +40,11 @@ class ApiOpenSearch extends ApiBase {
if ( in_array( $format, $allowed ) ) {
return $this->getMain()->createPrinterByName( $format );
}
+
return $this->getMain()->createPrinterByName( $allowed[0] );
}
public function execute() {
- global $wgEnableOpenSearchSuggest, $wgSearchSuggestCacheExpiry;
$params = $this->extractRequestParams();
$search = $params['search'];
$limit = $params['limit'];
@@ -52,35 +52,15 @@ class ApiOpenSearch extends ApiBase {
$suggest = $params['suggest'];
// Some script that was loaded regardless of wgEnableOpenSearchSuggest, likely cached.
- if ( $suggest && !$wgEnableOpenSearchSuggest ) {
+ if ( $suggest && !$this->getConfig()->get( 'EnableOpenSearchSuggest' ) ) {
$searches = array();
} else {
// Open search results may be stored for a very long time
- $this->getMain()->setCacheMaxAge( $wgSearchSuggestCacheExpiry );
+ $this->getMain()->setCacheMaxAge( $this->getConfig()->get( 'SearchSuggestCacheExpiry' ) );
$this->getMain()->setCacheMode( 'public' );
- $searches = PrefixSearch::titleSearch( $search, $limit,
- $namespaces );
-
- // if the content language has variants, try to retrieve fallback results
- $fallbackLimit = $limit - count( $searches );
- if ( $fallbackLimit > 0 ) {
- global $wgContLang;
-
- $fallbackSearches = $wgContLang->autoConvertToAllVariants( $search );
- $fallbackSearches = array_diff( array_unique( $fallbackSearches ), array( $search ) );
-
- foreach ( $fallbackSearches as $fbs ) {
- $fallbackSearchResult = PrefixSearch::titleSearch( $fbs, $fallbackLimit,
- $namespaces );
- $searches = array_merge( $searches, $fallbackSearchResult );
- $fallbackLimit -= count( $fallbackSearchResult );
-
- if ( $fallbackLimit == 0 ) {
- break;
- }
- }
- }
+ $searcher = new StringPrefixSearch;
+ $searches = $searcher->searchWithVariants( $search, $limit, $namespaces );
}
// Set top level elements
$result = $this->getResult();
@@ -92,7 +72,7 @@ class ApiOpenSearch extends ApiBase {
return array(
'search' => null,
'limit' => array(
- ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_DFLT => $this->getConfig()->get( 'OpenSearchDefaultLimit' ),
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => 100,
@@ -122,7 +102,7 @@ class ApiOpenSearch extends ApiBase {
}
public function getDescription() {
- return 'Search the wiki using the OpenSearch protocol';
+ return 'Search the wiki using the OpenSearch protocol.';
}
public function getExamples() {
diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php
index 7256066d..b01dc3e2 100644
--- a/includes/api/ApiOptions.php
+++ b/includes/api/ApiOptions.php
@@ -31,7 +31,6 @@
* @ingroup API
*/
class ApiOptions extends ApiBase {
-
/**
* Changes preferences of the current user.
*/
@@ -99,6 +98,9 @@ class ApiOptions extends ApiBase {
$validation = true;
}
break;
+ case 'special':
+ $validation = "cannot be set by this module";
+ break;
case 'unused':
default:
$validation = "not a valid preference";
@@ -133,10 +135,6 @@ class ApiOptions extends ApiBase {
$optionKinds[] = 'all';
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reset' => false,
'resetkinds' => array(
ApiBase::PARAM_TYPE => $optionKinds,
@@ -155,50 +153,32 @@ class ApiOptions extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- '*' => array(
- ApiBase::PROP_TYPE => array(
- 'success'
- )
- )
- )
- );
- }
-
public function getParamDescription() {
return array(
- 'token' => 'An options token previously obtained through the action=tokens',
'reset' => 'Resets preferences to the site defaults',
'resetkinds' => 'List of types of options to reset when the "reset" option is set',
- 'change' => 'List of changes, formatted name=value (e.g. skin=vector), value cannot contain pipe characters. If no value is given (not even an equals sign), e.g., optionname|otheroption|..., the option will be reset to its default value',
+ 'change' => array( 'List of changes, formatted name=value (e.g. skin=vector), ' .
+ 'value cannot contain pipe characters. If no value is given (not ',
+ 'even an equals sign), e.g., optionname|otheroption|..., the ' .
+ 'option will be reset to its default value'
+ ),
'optionname' => 'A name of a option which should have an optionvalue set',
- 'optionvalue' => 'A value of the option specified by the optionname, can contain pipe characters',
+ 'optionvalue' => 'A value of the option specified by the optionname, ' .
+ 'can contain pipe characters',
);
}
public function getDescription() {
return array(
- 'Change preferences of the current user',
+ 'Change preferences of the current user.',
'Only options which are registered in core or in one of installed extensions,',
- 'or as options with keys prefixed with \'userjs-\' (intended to be used by user scripts), can be set.'
+ 'or as options with keys prefixed with \'userjs-\' (intended to be used by user',
+ 'scripts), can be set.'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'notloggedin', 'info' => 'Anonymous users cannot change preferences' ),
- array( 'code' => 'nochanges', 'info' => 'No changes were requested' ),
- ) );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getHelpUrls() {
@@ -209,7 +189,8 @@ class ApiOptions extends ApiBase {
return array(
'api.php?action=options&reset=&token=123ABC',
'api.php?action=options&change=skin=vector|hideminor=1&token=123ABC',
- 'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
+ 'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&' .
+ 'optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
);
}
}
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index b05cb2b6..0f264675 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -39,7 +39,6 @@
* @since 1.21 derives from ApiBase instead of ApiQueryBase
*/
class ApiPageSet extends ApiBase {
-
/**
* Constructor flag: The new instance of ApiPageSet will ignore the 'generator=' parameter
* @since 1.21
@@ -62,6 +61,7 @@ class ApiPageSet extends ApiBase {
private $mSpecialTitles = array();
private $mNormalizedTitles = array();
private $mInterwikiTitles = array();
+ /** @var Title[] */
private $mPendingRedirectIDs = array();
private $mConvertedTitles = array();
private $mGoodRevIDs = array();
@@ -69,17 +69,38 @@ class ApiPageSet extends ApiBase {
private $mFakePageId = -1;
private $mCacheMode = 'public';
private $mRequestedPageFields = array();
+ /** @var int */
+ private $mDefaultNamespace = NS_MAIN;
+
/**
- * @var int
+ * Add all items from $values into the result
+ * @param array $result Output
+ * @param array $values Values to add
+ * @param string $flag The name of the boolean flag to mark this element
+ * @param string $name If given, name of the value
*/
- private $mDefaultNamespace = NS_MAIN;
+ private static function addValues( array &$result, $values, $flag = null, $name = null ) {
+ foreach ( $values as $val ) {
+ if ( $val instanceof Title ) {
+ $v = array();
+ ApiQueryBase::addTitleInfo( $v, $val );
+ } elseif ( $name !== null ) {
+ $v = array( $name => $val );
+ } else {
+ $v = $val;
+ }
+ if ( $flag !== null ) {
+ $v[$flag] = '';
+ }
+ $result[] = $v;
+ }
+ }
/**
- * Constructor
- * @param $dbSource ApiBase Module implementing getDB().
+ * @param ApiBase $dbSource Module implementing getDB().
* Allows PageSet to reuse existing db connection from the shared state like ApiQuery.
* @param int $flags Zero or more flags like DISABLE_GENERATORS
- * @param int $defaultNamespace the namespace to use if none is specified by a prefix.
+ * @param int $defaultNamespace The namespace to use if none is specified by a prefix.
* @since 1.21 accepts $flags instead of two boolean values
*/
public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) {
@@ -112,7 +133,8 @@ class ApiPageSet extends ApiBase {
/**
* Populate the PageSet from the request parameters.
- * @param bool $isDryRun If true, instantiates generator, but only to mark relevant parameters as used
+ * @param bool $isDryRun If true, instantiates generator, but only to mark
+ * relevant parameters as used
*/
private function executeInternal( $isDryRun ) {
$this->profileIn();
@@ -200,8 +222,9 @@ class ApiPageSet extends ApiBase {
break;
case 'revids':
if ( $this->mResolveRedirects ) {
- $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' .
- 'Any redirects the revids= point to have not been resolved.' );
+ $this->setWarning( 'Redirect resolution cannot be used ' .
+ 'together with the revids= parameter. Any redirects ' .
+ 'the revids= point to have not been resolved.' );
}
$this->mResolveRedirects = false;
$this->initFromRevIDs( $this->mParams['revids'] );
@@ -244,6 +267,7 @@ class ApiPageSet extends ApiBase {
if ( isset( $this->mParams['revids'] ) ) {
return 'revids';
}
+
return null;
}
@@ -270,7 +294,7 @@ class ApiPageSet extends ApiBase {
* Get the fields that have to be queried from the page table:
* the ones requested through requestField() and a few basic ones
* we always need
- * @return array of field names
+ * @return array Array of field names
*/
public function getPageTableFields() {
// Ensure we get minimum required fields
@@ -289,6 +313,7 @@ class ApiPageSet extends ApiBase {
$this->mRequestedPageFields = array_diff_key( $this->mRequestedPageFields, $pageFlds );
$pageFlds = array_merge( $pageFlds, $this->mRequestedPageFields );
+
return array_keys( $pageFlds );
}
@@ -304,7 +329,7 @@ class ApiPageSet extends ApiBase {
/**
* All Title objects provided.
- * @return array of Title objects
+ * @return Title[]
*/
public function getTitles() {
return $this->mTitles;
@@ -320,7 +345,7 @@ class ApiPageSet extends ApiBase {
/**
* Title objects that were found in the database.
- * @return array page_id (int) => Title (obj)
+ * @return Title[] Array page_id (int) => Title (obj)
*/
public function getGoodTitles() {
return $this->mGoodTitles;
@@ -337,7 +362,7 @@ class ApiPageSet extends ApiBase {
/**
* Title objects that were NOT found in the database.
* The array's index will be negative for each item
- * @return array of Title objects
+ * @return Title[]
*/
public function getMissingTitles() {
return $this->mMissingTitles;
@@ -346,7 +371,7 @@ class ApiPageSet extends ApiBase {
/**
* Titles that were deemed invalid by Title::newFromText()
* The array's index will be unique and negative for each item
- * @return array of strings (not Title objects)
+ * @return string[] Array of strings (not Title objects)
*/
public function getInvalidTitles() {
return $this->mInvalidTitles;
@@ -354,7 +379,7 @@ class ApiPageSet extends ApiBase {
/**
* Page IDs that were not found in the database
- * @return array of page IDs
+ * @return array Array of page IDs
*/
public function getMissingPageIDs() {
return $this->mMissingPageIDs;
@@ -363,7 +388,7 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of redirect resolutions - maps a title to its redirect
* target, as an array of output-ready arrays
- * @return array
+ * @return Title[]
*/
public function getRedirectTitles() {
return $this->mRedirectTitles;
@@ -372,8 +397,8 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of redirect resolutions - maps a title to its redirect
* target.
- * @param $result ApiResult
- * @return array of prefixed_title (string) => Title object
+ * @param ApiResult $result
+ * @return array Array of prefixed_title (string) => Title object
* @since 1.21
*/
public function getRedirectTitlesAsResult( $result = null ) {
@@ -383,7 +408,7 @@ class ApiPageSet extends ApiBase {
'from' => strval( $titleStrFrom ),
'to' => $titleTo->getPrefixedText(),
);
- if ( $titleTo->getFragment() !== '' ) {
+ if ( $titleTo->hasFragment() ) {
$r['tofragment'] = $titleTo->getFragment();
}
$values[] = $r;
@@ -391,13 +416,14 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'r' );
}
+
return $values;
}
/**
* Get a list of title normalizations - maps a title to its normalized
* version.
- * @return array raw_prefixed_title (string) => prefixed_title (string)
+ * @return array Array of raw_prefixed_title (string) => prefixed_title (string)
*/
public function getNormalizedTitles() {
return $this->mNormalizedTitles;
@@ -406,8 +432,8 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of title normalizations - maps a title to its normalized
* version in the form of result array.
- * @param $result ApiResult
- * @return array of raw_prefixed_title (string) => prefixed_title (string)
+ * @param ApiResult $result
+ * @return array Array of raw_prefixed_title (string) => prefixed_title (string)
* @since 1.21
*/
public function getNormalizedTitlesAsResult( $result = null ) {
@@ -421,13 +447,14 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'n' );
}
+
return $values;
}
/**
* Get a list of title conversions - maps a title to its converted
* version.
- * @return array raw_prefixed_title (string) => prefixed_title (string)
+ * @return array Array of raw_prefixed_title (string) => prefixed_title (string)
*/
public function getConvertedTitles() {
return $this->mConvertedTitles;
@@ -436,8 +463,8 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of title conversions - maps a title to its converted
* version as a result array.
- * @param $result ApiResult
- * @return array of (from, to) strings
+ * @param ApiResult $result
+ * @return array Array of (from, to) strings
* @since 1.21
*/
public function getConvertedTitlesAsResult( $result = null ) {
@@ -451,13 +478,14 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'c' );
}
+
return $values;
}
/**
* Get a list of interwiki titles - maps a title to its interwiki
* prefix.
- * @return array raw_prefixed_title (string) => interwiki_prefix (string)
+ * @return array Array of raw_prefixed_title (string) => interwiki_prefix (string)
*/
public function getInterwikiTitles() {
return $this->mInterwikiTitles;
@@ -466,9 +494,9 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of interwiki titles - maps a title to its interwiki
* prefix as result.
- * @param $result ApiResult
- * @param $iwUrl boolean
- * @return array raw_prefixed_title (string) => interwiki_prefix (string)
+ * @param ApiResult $result
+ * @param bool $iwUrl
+ * @return array Array of raw_prefixed_title (string) => interwiki_prefix (string)
* @since 1.21
*/
public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) {
@@ -487,12 +515,53 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'i' );
}
+
return $values;
}
/**
+ * Get an array of invalid/special/missing titles.
+ *
+ * @param array $invalidChecks List of types of invalid titles to include.
+ * Recognized values are:
+ * - invalidTitles: Titles from $this->getInvalidTitles()
+ * - special: Titles from $this->getSpecialTitles()
+ * - missingIds: ids from $this->getMissingPageIDs()
+ * - missingRevIds: ids from $this->getMissingRevisionIDs()
+ * - missingTitles: Titles from $this->getMissingTitles()
+ * - interwikiTitles: Titles from $this->getInterwikiTitlesAsResult()
+ * @return array Array suitable for inclusion in the response
+ * @since 1.23
+ */
+ public function getInvalidTitlesAndRevisions( $invalidChecks = array( 'invalidTitles',
+ 'special', 'missingIds', 'missingRevIds', 'missingTitles', 'interwikiTitles' )
+ ) {
+ $result = array();
+ if ( in_array( "invalidTitles", $invalidChecks ) ) {
+ self::addValues( $result, $this->getInvalidTitles(), 'invalid', 'title' );
+ }
+ if ( in_array( "special", $invalidChecks ) ) {
+ self::addValues( $result, $this->getSpecialTitles(), 'special', 'title' );
+ }
+ if ( in_array( "missingIds", $invalidChecks ) ) {
+ self::addValues( $result, $this->getMissingPageIDs(), 'missing', 'pageid' );
+ }
+ if ( in_array( "missingRevIds", $invalidChecks ) ) {
+ self::addValues( $result, $this->getMissingRevisionIDs(), 'missing', 'revid' );
+ }
+ if ( in_array( "missingTitles", $invalidChecks ) ) {
+ self::addValues( $result, $this->getMissingTitles(), 'missing' );
+ }
+ if ( in_array( "interwikiTitles", $invalidChecks ) ) {
+ self::addValues( $result, $this->getInterwikiTitlesAsResult() );
+ }
+
+ return $result;
+ }
+
+ /**
* Get the list of revision IDs (requested with the revids= parameter)
- * @return array revID (int) => pageID (int)
+ * @return array Array of revID (int) => pageID (int)
*/
public function getRevisionIDs() {
return $this->mGoodRevIDs;
@@ -500,7 +569,7 @@ class ApiPageSet extends ApiBase {
/**
* Revision IDs that were not found in the database
- * @return array of revision IDs
+ * @return array Array of revision IDs
*/
public function getMissingRevisionIDs() {
return $this->mMissingRevIDs;
@@ -508,8 +577,8 @@ class ApiPageSet extends ApiBase {
/**
* Revision IDs that were not found in the database as result array.
- * @param $result ApiResult
- * @return array of revision IDs
+ * @param ApiResult $result
+ * @return array Array of revision IDs
* @since 1.21
*/
public function getMissingRevisionIDsAsResult( $result = null ) {
@@ -522,12 +591,13 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'rev' );
}
+
return $values;
}
/**
* Get the list of titles with negative namespace
- * @return array Title
+ * @return Title[]
*/
public function getSpecialTitles() {
return $this->mSpecialTitles;
@@ -543,7 +613,7 @@ class ApiPageSet extends ApiBase {
/**
* Populate this PageSet from a list of Titles
- * @param array $titles of Title objects
+ * @param array $titles Array of Title objects
*/
public function populateFromTitles( $titles ) {
$this->profileIn();
@@ -553,7 +623,7 @@ class ApiPageSet extends ApiBase {
/**
* Populate this PageSet from a list of page IDs
- * @param array $pageIDs of page IDs
+ * @param array $pageIDs Array of page IDs
*/
public function populateFromPageIDs( $pageIDs ) {
$this->profileIn();
@@ -563,8 +633,8 @@ class ApiPageSet extends ApiBase {
/**
* Populate this PageSet from a rowset returned from the database
- * @param $db DatabaseBase object
- * @param $queryResult ResultWrapper Query result object
+ * @param DatabaseBase $db
+ * @param ResultWrapper $queryResult Query result object
*/
public function populateFromQueryResult( $db, $queryResult ) {
$this->profileIn();
@@ -574,7 +644,7 @@ class ApiPageSet extends ApiBase {
/**
* Populate this PageSet from a list of revision IDs
- * @param array $revIDs of revision IDs
+ * @param array $revIDs Array of revision IDs
*/
public function populateFromRevisionIDs( $revIDs ) {
$this->profileIn();
@@ -584,7 +654,7 @@ class ApiPageSet extends ApiBase {
/**
* Extract all requested fields from the row received from the database
- * @param $row Result row
+ * @param stdClass $row Result row
*/
public function processDbRow( $row ) {
// Store Title object in various data structures
@@ -627,7 +697,7 @@ class ApiPageSet extends ApiBase {
* #5 Substitute the original LinkBatch object with the new list
* #6 Repeat from step #1
*
- * @param array $titles of Title objects or strings
+ * @param array $titles Array of Title objects or strings
*/
private function initFromTitles( $titles ) {
// Get validated and normalized title objects
@@ -642,7 +712,7 @@ class ApiPageSet extends ApiBase {
// Get pageIDs data from the `page` table
$this->profileDBIn();
$res = $db->select( 'page', $this->getPageTableFields(), $set,
- __METHOD__ );
+ __METHOD__ );
$this->profileDBOut();
// Hack: get the ns:titles stored in array(ns => array(titles)) format
@@ -654,7 +724,7 @@ class ApiPageSet extends ApiBase {
/**
* Does the same as initFromTitles(), but is based on page IDs instead
- * @param array $pageids of page IDs
+ * @param array $pageids Array of page IDs
*/
private function initFromPageIds( $pageids ) {
if ( !$pageids ) {
@@ -676,7 +746,7 @@ class ApiPageSet extends ApiBase {
// Get pageIDs data from the `page` table
$this->profileDBIn();
$res = $db->select( 'page', $this->getPageTableFields(), $set,
- __METHOD__ );
+ __METHOD__ );
$this->profileDBOut();
}
@@ -689,8 +759,8 @@ class ApiPageSet extends ApiBase {
/**
* Iterate through the result of the query on 'page' table,
* and for each row create and store title object and save any extra fields requested.
- * @param $res ResultWrapper DB Query result
- * @param array $remaining of either pageID or ns/title elements (optional).
+ * @param ResultWrapper $res DB Query result
+ * @param array $remaining Array of either pageID or ns/title elements (optional).
* If given, any missing items will go to $mMissingPageIDs and $mMissingTitles
* @param bool $processTitles Must be provided together with $remaining.
* If true, treat $remaining as an array of [ns][title]
@@ -761,7 +831,7 @@ class ApiPageSet extends ApiBase {
/**
* Does the same as initFromTitles(), but is based on revision IDs
* instead
- * @param array $revids of revision IDs
+ * @param array $revids Array of revision IDs
*/
private function initFromRevIDs( $revids ) {
if ( !$revids ) {
@@ -863,7 +933,12 @@ class ApiPageSet extends ApiBase {
foreach ( $res as $row ) {
$rdfrom = intval( $row->rd_from );
$from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
- $to = Title::makeTitle( $row->rd_namespace, $row->rd_title, $row->rd_fragment, $row->rd_interwiki );
+ $to = Title::makeTitle(
+ $row->rd_namespace,
+ $row->rd_title,
+ $row->rd_fragment,
+ $row->rd_interwiki
+ );
unset( $this->mPendingRedirectIDs[$rdfrom] );
if ( !$to->isExternal() && !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
$lb->add( $row->rd_namespace, $row->rd_title );
@@ -886,6 +961,7 @@ class ApiPageSet extends ApiBase {
unset( $this->mPendingRedirectIDs[$id] );
}
}
+
return $lb;
}
@@ -898,7 +974,7 @@ class ApiPageSet extends ApiBase {
* Public caching will only be allowed if *all* the modules that supply
* data for a given request return a cache mode of public.
*
- * @param $params
+ * @param array|null $params
* @return string
* @since 1.21
*/
@@ -912,7 +988,7 @@ class ApiPageSet extends ApiBase {
* This method validates access rights for the title,
* and appends normalization values to the output.
*
- * @param array $titles of Title objects or strings
+ * @param array $titles Array of Title objects or strings
* @return LinkBatch
*/
private function processTitlesArray( $titles ) {
@@ -941,8 +1017,9 @@ class ApiPageSet extends ApiBase {
// Variants checking
global $wgContLang;
if ( $this->mConvertTitles &&
- count( $wgContLang->getVariants() ) > 1 &&
- !$titleObj->exists() ) {
+ count( $wgContLang->getVariants() ) > 1 &&
+ !$titleObj->exists()
+ ) {
// Language::findVariantLink will modify titleText and titleObj into
// the canonical variant if possible
$titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText();
@@ -999,7 +1076,7 @@ class ApiPageSet extends ApiBase {
/**
* Returns the input array of integers with all values < 0 removed
*
- * @param $array array
+ * @param array $array
* @return array
*/
private static function getPositiveIntegers( $array ) {
@@ -1040,6 +1117,7 @@ class ApiPageSet extends ApiBase {
$result['generator'] = null;
}
}
+
return $result;
}
@@ -1067,6 +1145,7 @@ class ApiPageSet extends ApiBase {
sort( $gens );
self::$generators = $gens;
}
+
return self::$generators;
}
@@ -1075,19 +1154,17 @@ class ApiPageSet extends ApiBase {
'titles' => 'A list of titles to work on',
'pageids' => 'A list of page IDs to work on',
'revids' => 'A list of revision IDs to work on',
- 'generator' => array( 'Get the list of pages to work on by executing the specified query module.',
- 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' ),
+ 'generator' => array(
+ 'Get the list of pages to work on by executing the specified query module.',
+ 'NOTE: generator parameter names must be prefixed with a \'g\', see examples'
+ ),
'redirects' => 'Automatically resolve redirects',
- 'converttitles' => array( 'Convert titles to other variants if necessary. Only works if the wiki\'s content language supports variant conversion.',
- 'Languages that support variant conversion include ' . implode( ', ', LanguageConverter::$languagesWithVariants ) ),
+ 'converttitles' => array(
+ 'Convert titles to other variants if necessary. Only works if ' .
+ 'the wiki\'s content language supports variant conversion.',
+ 'Languages that support variant conversion include ' .
+ implode( ', ', LanguageConverter::$languagesWithVariants )
+ ),
);
}
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'multisource', 'info' => "Cannot use 'pageids' at the same time as 'dataSource'" ),
- array( 'code' => 'multisource', 'info' => "Cannot use 'revids' at the same time as 'dataSource'" ),
- array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ),
- ) );
- }
}
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index 3e1a7531..067b2f59 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -34,7 +34,7 @@ class ApiParamInfo extends ApiBase {
*/
protected $queryObj;
- public function __construct( $main, $action ) {
+ public function __construct( ApiMain $main, $action ) {
parent::__construct( $main, $action );
$this->queryObj = new ApiQuery( $this->getMain(), 'query' );
}
@@ -66,10 +66,10 @@ class ApiParamInfo extends ApiBase {
/**
* If the type is requested in parameters, adds a section to res with module info.
- * @param array $params user parameters array
- * @param string $type parameter name
- * @param array $res store results in this array
- * @param ApiResult $resultObj results object to set indexed tag.
+ * @param array $params User parameters array
+ * @param string $type Parameter name
+ * @param array $res Store results in this array
+ * @param ApiResult $resultObj Results object to set indexed tag.
*/
private function addModulesInfo( $params, $type, &$res, $resultObj ) {
if ( !is_array( $params[$type] ) ) {
@@ -99,7 +99,7 @@ class ApiParamInfo extends ApiBase {
}
/**
- * @param $obj ApiBase
+ * @param ApiBase $obj
* @return ApiResult
*/
private function getClassInfo( $obj ) {
@@ -198,6 +198,10 @@ class ApiParamInfo extends ApiBase {
$a['required'] = '';
}
+ if ( $n === 'token' && $obj->needsToken() ) {
+ $a['tokentype'] = $obj->needsToken();
+ }
+
if ( isset( $p[ApiBase::PARAM_DFLT] ) ) {
$type = $p[ApiBase::PARAM_TYPE];
if ( $type === 'boolean' ) {
@@ -224,9 +228,16 @@ class ApiParamInfo extends ApiBase {
}
if ( isset( $p[ApiBase::PARAM_TYPE] ) ) {
- $a['type'] = $p[ApiBase::PARAM_TYPE];
+ if ( $p[ApiBase::PARAM_TYPE] === 'submodule' ) {
+ $a['type'] = $obj->getModuleManager()->getNames( $n );
+ sort( $a['type'] );
+ $a['submodules'] = '';
+ } else {
+ $a['type'] = $p[ApiBase::PARAM_TYPE];
+ }
if ( is_array( $a['type'] ) ) {
- $a['type'] = array_values( $a['type'] ); // to prevent sparse arrays from being serialized to JSON as objects
+ // To prevent sparse arrays from being serialized to JSON as objects
+ $a['type'] = array_values( $a['type'] );
$result->setIndexedTagName( $a['type'], 't' );
}
}
@@ -243,66 +254,6 @@ class ApiParamInfo extends ApiBase {
}
$result->setIndexedTagName( $retval['parameters'], 'param' );
- $props = $obj->getFinalResultProperties();
- $listResult = null;
- if ( $props !== false ) {
- $retval['props'] = array();
-
- foreach ( $props as $prop => $properties ) {
- $propResult = array();
- if ( $prop == ApiBase::PROP_LIST ) {
- $listResult = $properties;
- continue;
- }
- if ( $prop != ApiBase::PROP_ROOT ) {
- $propResult['name'] = $prop;
- }
- $propResult['properties'] = array();
-
- foreach ( $properties as $name => $p ) {
- $propertyResult = array();
-
- $propertyResult['name'] = $name;
-
- if ( !is_array( $p ) ) {
- $p = array( ApiBase::PROP_TYPE => $p );
- }
-
- $propertyResult['type'] = $p[ApiBase::PROP_TYPE];
-
- if ( is_array( $propertyResult['type'] ) ) {
- $propertyResult['type'] = array_values( $propertyResult['type'] );
- $result->setIndexedTagName( $propertyResult['type'], 't' );
- }
-
- $nullable = null;
- if ( isset( $p[ApiBase::PROP_NULLABLE] ) ) {
- $nullable = $p[ApiBase::PROP_NULLABLE];
- }
-
- if ( $nullable === true ) {
- $propertyResult['nullable'] = '';
- }
-
- $propResult['properties'][] = $propertyResult;
- }
-
- $result->setIndexedTagName( $propResult['properties'], 'property' );
- $retval['props'][] = $propResult;
- }
-
- // default is true for query modules, false for other modules, overridden by ApiBase::PROP_LIST
- if ( $listResult === true || ( $listResult !== false && $obj instanceof ApiQueryBase ) ) {
- $retval['listresult'] = '';
- }
-
- $result->setIndexedTagName( $retval['props'], 'prop' );
- }
-
- // Errors
- $retval['errors'] = $this->parseErrors( $obj->getFinalPossibleErrors() );
- $result->setIndexedTagName( $retval['errors'], 'error' );
-
return $retval;
}
@@ -317,6 +268,7 @@ class ApiParamInfo extends ApiBase {
sort( $querymodules );
$formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' );
sort( $formatmodules );
+
return array(
'modules' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -340,13 +292,14 @@ class ApiParamInfo extends ApiBase {
'modules' => 'List of module names (value of the action= parameter)',
'querymodules' => 'List of query module names (value of prop=, meta= or list= parameter)',
'mainmodule' => 'Get information about the main (top-level) module as well',
- 'pagesetmodule' => 'Get information about the pageset module (providing titles= and friends) as well',
+ 'pagesetmodule' => 'Get information about the pageset module ' .
+ '(providing titles= and friends) as well',
'formatmodules' => 'List of format module names (value of format= parameter)',
);
}
public function getDescription() {
- return 'Obtain information about certain API parameters and errors';
+ return 'Obtain information about certain API parameters and errors.';
}
public function getExamples() {
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index a369994b..06fdf85b 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -27,7 +27,7 @@
*/
class ApiParse extends ApiBase {
- /** @var String $section */
+ /** @var string $section */
private $section = null;
/** @var Content $content */
@@ -60,7 +60,10 @@ class ApiParse extends ApiBase {
$format = $params['contentformat'];
if ( !is_null( $page ) && ( !is_null( $text ) || $titleProvided ) ) {
- $this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
+ $this->dieUsage(
+ 'The page parameter cannot be used together with the text and title parameters',
+ 'params'
+ );
}
$prop = array_flip( $params['prop'] );
@@ -76,9 +79,12 @@ class ApiParse extends ApiBase {
// TODO: Does this still need $wgTitle?
global $wgParser, $wgTitle;
- // Currently unnecessary, code to act as a safeguard against any change in current behavior of uselang
+ // Currently unnecessary, code to act as a safeguard against any change
+ // in current behavior of uselang
$oldLang = null;
- if ( isset( $params['uselang'] ) && $params['uselang'] != $this->getContext()->getLanguage()->getCode() ) {
+ if ( isset( $params['uselang'] )
+ && $params['uselang'] != $this->getContext()->getLanguage()->getCode()
+ ) {
$oldLang = $this->getContext()->getLanguage(); // Backup language
$this->getContext()->setLanguage( Language::factory( $params['uselang'] ) );
}
@@ -125,7 +131,7 @@ class ApiParse extends ApiBase {
'action' => 'query',
'redirects' => '',
);
- if ( !is_null ( $pageid ) ) {
+ if ( !is_null( $pageid ) ) {
$reqParams['pageids'] = $pageid;
} else { // $page
$reqParams['titles'] = $page;
@@ -170,15 +176,19 @@ class ApiParse extends ApiBase {
if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
- if ( !$titleObj->canExist() ) {
- $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
- }
$wgTitle = $titleObj;
- $pageObj = WikiPage::factory( $titleObj );
+ if ( $titleObj->canExist() ) {
+ $pageObj = WikiPage::factory( $titleObj );
+ } else {
+ // Do like MediaWiki::initializeArticle()
+ $article = Article::newFromTitle( $titleObj, $this->getContext() );
+ $pageObj = $article->getPage();
+ }
$popts = $this->makeParserOptions( $pageObj, $params );
+ $textProvided = !is_null( $text );
- if ( is_null( $text ) ) {
+ if ( !$textProvided ) {
if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
$this->setWarning(
"'title' used without 'text', and parsed page properties were requested " .
@@ -191,7 +201,7 @@ class ApiParse extends ApiBase {
// If we are parsing text, do not use the content model of the default
// API title, but default to wikitext to keep BC.
- if ( !$titleProvided && is_null( $model ) ) {
+ if ( $textProvided && !$titleProvided && is_null( $model ) ) {
$model = CONTENT_MODEL_WIKITEXT;
$this->setWarning( "No 'title' or 'contentmodel' was given, assuming $model." );
}
@@ -203,7 +213,7 @@ class ApiParse extends ApiBase {
}
if ( $this->section !== false ) {
- $this->content = $this->getSectionContent( $this->content, $titleObj->getText() );
+ $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
}
if ( $params['pst'] || $params['onlypst'] ) {
@@ -219,6 +229,7 @@ class ApiParse extends ApiBase {
ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
}
$result->addValue( null, $this->getModuleName(), $result_array );
+
return;
}
@@ -242,6 +253,10 @@ class ApiParse extends ApiBase {
$result_array['redirects'] = $redirValues;
}
+ if ( $params['disabletoc'] ) {
+ $p_result->setTOCEnabled( false );
+ }
+
if ( isset( $prop['text'] ) ) {
$result_array['text'] = array();
ApiResult::setContent( $result_array['text'], $p_result->getText() );
@@ -249,10 +264,13 @@ class ApiParse extends ApiBase {
if ( !is_null( $params['summary'] ) ) {
$result_array['parsedsummary'] = array();
- ApiResult::setContent( $result_array['parsedsummary'], Linker::formatComment( $params['summary'], $titleObj ) );
+ ApiResult::setContent(
+ $result_array['parsedsummary'],
+ Linker::formatComment( $params['summary'], $titleObj )
+ );
}
- if ( isset( $prop['langlinks'] ) || isset( $prop['languageshtml'] ) ) {
+ if ( isset( $prop['langlinks'] ) ) {
$langlinks = $p_result->getLanguageLinks();
if ( $params['effectivelanglinks'] ) {
@@ -268,12 +286,6 @@ class ApiParse extends ApiBase {
if ( isset( $prop['langlinks'] ) ) {
$result_array['langlinks'] = $this->formatLangLinks( $langlinks );
}
- if ( isset( $prop['languageshtml'] ) ) {
- $languagesHtml = $this->languagesHtml( $langlinks );
-
- $result_array['languageshtml'] = array();
- ApiResult::setContent( $result_array['languageshtml'], $languagesHtml );
- }
if ( isset( $prop['categories'] ) ) {
$result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
}
@@ -300,14 +312,14 @@ class ApiParse extends ApiBase {
if ( isset( $prop['displaytitle'] ) ) {
$result_array['displaytitle'] = $p_result->getDisplayTitle() ?
- $p_result->getDisplayTitle() :
- $titleObj->getPrefixedText();
+ $p_result->getDisplayTitle() :
+ $titleObj->getPrefixedText();
}
if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) {
$context = $this->getContext();
$context->setTitle( $titleObj );
- $context->getOutput()->addParserOutputNoText( $p_result );
+ $context->getOutput()->addParserOutputMetadata( $p_result );
if ( isset( $prop['headitems'] ) ) {
$headItems = $this->formatHeadItems( $p_result->getHeadItems() );
@@ -321,10 +333,20 @@ class ApiParse extends ApiBase {
if ( isset( $prop['headhtml'] ) ) {
$result_array['headhtml'] = array();
- ApiResult::setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) );
+ ApiResult::setContent(
+ $result_array['headhtml'],
+ $context->getOutput()->headElement( $context->getSkin() )
+ );
}
}
+ if ( isset( $prop['modules'] ) ) {
+ $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
+ $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
+ $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
+ $result_array['modulemessages'] = array_values( array_unique( $p_result->getModuleMessages() ) );
+ }
+
if ( isset( $prop['iwlinks'] ) ) {
$result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
}
@@ -341,6 +363,16 @@ class ApiParse extends ApiBase {
$result_array['properties'] = $this->formatProperties( $p_result->getProperties() );
}
+ if ( isset( $prop['limitreportdata'] ) ) {
+ $result_array['limitreportdata'] =
+ $this->formatLimitReportData( $p_result->getLimitReportData() );
+ }
+ if ( isset( $prop['limitreporthtml'] ) ) {
+ $limitreportHtml = EditPage::getPreviewLimitReport( $p_result );
+ $result_array['limitreporthtml'] = array();
+ ApiResult::setContent( $result_array['limitreporthtml'], $limitreportHtml );
+ }
+
if ( $params['generatexml'] ) {
if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
$this->dieUsage( "generatexml is only supported for wikitext content", "notwikitext" );
@@ -368,7 +400,12 @@ class ApiParse extends ApiBase {
'iwlinks' => 'iw',
'sections' => 's',
'headitems' => 'hi',
+ 'modules' => 'm',
+ 'modulescripts' => 'm',
+ 'modulestyles' => 'm',
+ 'modulemessages' => 'm',
'properties' => 'pp',
+ 'limitreportdata' => 'lr',
);
$this->setIndexedTagNames( $result_array, $result_mapping );
$result->addValue( null, $this->getModuleName(), $result_array );
@@ -393,16 +430,18 @@ class ApiParse extends ApiBase {
$popts->enableLimitReport( !$params['disablepp'] );
$popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
$popts->setIsSectionPreview( $params['sectionpreview'] );
+ $popts->setEditSection( !$params['disableeditsection'] );
wfProfileOut( __METHOD__ );
+
return $popts;
}
/**
- * @param $page WikiPage
- * @param $popts ParserOptions
- * @param $pageId Int
- * @param $getWikitext Bool
+ * @param WikiPage $page
+ * @param ParserOptions $popts
+ * @param int $pageId
+ * @param bool $getWikitext
* @return ParserOutput
*/
private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
@@ -411,24 +450,31 @@ class ApiParse extends ApiBase {
if ( $this->section !== false && $this->content !== null ) {
$this->content = $this->getSectionContent(
$this->content,
- !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getText() );
+ !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText()
+ );
// Not cached (save or load)
return $this->content->getParserOutput( $page->getTitle(), null, $popts );
- } else {
- // Try the parser cache first
- // getParserOutput will save to Parser cache if able
- $pout = $page->getParserOutput( $popts );
- if ( !$pout ) {
- $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
- }
- if ( $getWikitext ) {
- $this->content = $page->getContent( Revision::RAW );
- }
- return $pout;
}
+
+ // Try the parser cache first
+ // getParserOutput will save to Parser cache if able
+ $pout = $page->getParserOutput( $popts );
+ if ( !$pout ) {
+ $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
+ }
+ if ( $getWikitext ) {
+ $this->content = $page->getContent( Revision::RAW );
+ }
+
+ return $pout;
}
+ /**
+ * @param Content $content
+ * @param string $what Identifies the content in error messages, e.g. page title.
+ * @return Content|bool
+ */
private function getSectionContent( Content $content, $what ) {
// Not cached (save or load)
$section = $content->getSection( $this->section );
@@ -439,6 +485,7 @@ class ApiParse extends ApiBase {
$this->dieUsage( "Sections are not supported by " . $what, 'nosuchsection' );
$section = false;
}
+
return $section;
}
@@ -452,64 +499,67 @@ class ApiParse extends ApiBase {
$entry['lang'] = $bits[0];
if ( $title ) {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
+ // localised language name in user language (maybe set by uselang=)
+ $entry['langname'] = Language::fetchLanguageName(
+ $title->getInterwiki(),
+ $this->getLanguage()->getCode()
+ );
+
+ // native language name
+ $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
}
ApiResult::setContent( $entry, $bits[1] );
$result[] = $entry;
}
+
return $result;
}
private function formatCategoryLinks( $links ) {
$result = array();
+
+ if ( !$links ) {
+ return $result;
+ }
+
+ // Fetch hiddencat property
+ $lb = new LinkBatch;
+ $lb->setArray( array( NS_CATEGORY => $links ) );
+ $db = $this->getDB();
+ $res = $db->select( array( 'page', 'page_props' ),
+ array( 'page_title', 'pp_propname' ),
+ $lb->constructSet( 'page', $db ),
+ __METHOD__,
+ array(),
+ array( 'page_props' => array(
+ 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' )
+ ) )
+ );
+ $hiddencats = array();
+ foreach ( $res as $row ) {
+ $hiddencats[$row->page_title] = isset( $row->pp_propname );
+ }
+
foreach ( $links as $link => $sortkey ) {
$entry = array();
$entry['sortkey'] = $sortkey;
ApiResult::setContent( $entry, $link );
+ if ( !isset( $hiddencats[$link] ) ) {
+ $entry['missing'] = '';
+ } elseif ( $hiddencats[$link] ) {
+ $entry['hidden'] = '';
+ }
$result[] = $entry;
}
+
return $result;
}
private function categoriesHtml( $categories ) {
$context = $this->getContext();
$context->getOutput()->addCategoryLinks( $categories );
- return $context->getSkin()->getCategories();
- }
- /**
- * @deprecated since 1.18 No modern skin generates language links this way, please use language links
- * data to generate your own HTML.
- * @param $languages array
- * @return string
- */
- private function languagesHtml( $languages ) {
- wfDeprecated( __METHOD__, '1.18' );
-
- global $wgContLang, $wgHideInterlanguageLinks;
-
- if ( $wgHideInterlanguageLinks || count( $languages ) == 0 ) {
- return '';
- }
-
- $s = htmlspecialchars( wfMessage( 'otherlanguages' )->text() . wfMessage( 'colon-separator' )->text() );
-
- $langs = array();
- foreach ( $languages as $l ) {
- $nt = Title::newFromText( $l );
- $text = Language::fetchLanguageName( $nt->getInterwiki() );
-
- $langs[] = Html::element( 'a',
- array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => 'external' ),
- $text == '' ? $l : $text );
- }
-
- $s .= implode( wfMessage( 'pipe-separator' )->escaped(), $langs );
-
- if ( $wgContLang->isRTL() ) {
- $s = Html::rawElement( 'span', array( 'dir' => 'LTR' ), $s );
- }
-
- return $s;
+ return $context->getSkin()->getCategories();
}
private function formatLinks( $links ) {
@@ -525,6 +575,7 @@ class ApiParse extends ApiBase {
$result[] = $entry;
}
}
+
return $result;
}
@@ -544,6 +595,7 @@ class ApiParse extends ApiBase {
$result[] = $entry;
}
}
+
return $result;
}
@@ -555,6 +607,7 @@ class ApiParse extends ApiBase {
ApiResult::setContent( $entry, $content );
$result[] = $entry;
}
+
return $result;
}
@@ -566,6 +619,7 @@ class ApiParse extends ApiBase {
ApiResult::setContent( $entry, $value );
$result[] = $entry;
}
+
return $result;
}
@@ -577,6 +631,26 @@ class ApiParse extends ApiBase {
ApiResult::setContent( $entry, $link );
$result[] = $entry;
}
+
+ return $result;
+ }
+
+ private function formatLimitReportData( $limitReportData ) {
+ $result = array();
+ $apiResult = $this->getResult();
+
+ foreach ( $limitReportData as $name => $value ) {
+ $entry = array();
+ $entry['name'] = $name;
+ if ( !is_array( $value ) ) {
+ $value = array( $value );
+ }
+ $apiResult->setIndexedTagName( $value, 'param' );
+ $apiResult->setIndexedTagName_recursive( $value, 'param' );
+ $entry = array_merge( $entry, $value );
+ $result[] = $entry;
+ }
+
return $result;
}
@@ -602,12 +676,12 @@ class ApiParse extends ApiBase {
ApiBase::PARAM_TYPE => 'integer',
),
'prop' => array(
- ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle|iwlinks|properties',
+ ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
+ 'images|externallinks|sections|revid|displaytitle|iwlinks|properties',
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array(
'text',
'langlinks',
- 'languageshtml',
'categories',
'categorieshtml',
'links',
@@ -619,9 +693,12 @@ class ApiParse extends ApiBase {
'displaytitle',
'headitems',
'headhtml',
+ 'modules',
'iwlinks',
'wikitext',
'properties',
+ 'limitreportdata',
+ 'limitreporthtml',
)
),
'pst' => false,
@@ -630,9 +707,11 @@ class ApiParse extends ApiBase {
'uselang' => null,
'section' => null,
'disablepp' => false,
+ 'disableeditsection' => false,
'generatexml' => false,
'preview' => false,
'sectionpreview' => false,
+ 'disabletoc' => false,
'contentformat' => array(
ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
),
@@ -645,12 +724,13 @@ class ApiParse extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
$wikitext = CONTENT_MODEL_WIKITEXT;
+
return array(
'text' => "Text to parse. Use {$p}title or {$p}contentmodel to control the content model",
'summary' => 'Summary to parse',
'redirects' => "If the {$p}page or the {$p}pageid parameter is set to a redirect, resolve it",
'title' => "Title of page the text belongs to. " .
- "If omitted, \"API\" is used as the title with content model $wikitext",
+ "If omitted, {$p}contentmodel must be specified, and \"API\" will be used as the title",
'page' => "Parse the content of this page. Cannot be used together with {$p}text and {$p}title",
'pageid' => "Parse the content of this page. Overrides {$p}page",
'oldid' => "Parse the content of this revision. Overrides {$p}page and {$p}pageid",
@@ -660,7 +740,6 @@ class ApiParse extends ApiBase {
' langlinks - Gives the language links in the parsed wikitext',
' categories - Gives the categories in the parsed wikitext',
' categorieshtml - Gives the HTML version of the categories',
- ' languageshtml - Gives the HTML version of the language links',
' links - Gives the internal links in the parsed wikitext',
' templates - Gives the templates in the parsed wikitext',
' images - Gives the images in the parsed wikitext',
@@ -670,13 +749,18 @@ class ApiParse extends ApiBase {
' displaytitle - Adds the title of the parsed wikitext',
' headitems - Gives items to put in the <head> of the page',
' headhtml - Gives parsed <head> of the page',
+ ' modules - Gives the ResourceLoader modules used on the page',
' iwlinks - Gives interwiki links in the parsed wikitext',
' wikitext - Gives the original wikitext that was parsed',
' properties - Gives various properties defined in the parsed wikitext',
+ ' limitreportdata - Gives the limit report in a structured way.',
+ " Gives no data, when {$p}disablepp is set.",
+ ' limitreporthtml - Gives the HTML version of the limit report.',
+ " Gives no data, when {$p}disablepp is set.",
),
'effectivelanglinks' => array(
'Includes language links supplied by extensions',
- '(for use with prop=langlinks|languageshtml)',
+ '(for use with prop=langlinks)',
),
'pst' => array(
'Do a pre-save transform on the input before parsing it',
@@ -690,16 +774,18 @@ class ApiParse extends ApiBase {
'uselang' => 'Which language to parse the request in',
'section' => 'Only retrieve the content of this section number',
'disablepp' => 'Disable the PP Report from the parser output',
+ 'disableeditsection' => 'Disable edit section links from the parser output',
'generatexml' => "Generate XML parse tree (requires contentmodel=$wikitext)",
'preview' => 'Parse in preview mode',
'sectionpreview' => 'Parse in section preview mode (enables preview mode too)',
+ 'disabletoc' => 'Disable table of contents in output',
'contentformat' => array(
'Content serialization format used for the input text',
"Only valid when used with {$p}text",
),
'contentmodel' => array(
- "Content model of the input text. Default is the model of the " .
- "specified ${p}title, or $wikitext if ${p}title is not specified",
+ "Content model of the input text. If omitted, ${p}title must be specified, " .
+ "and default will be the model of the specified ${p}title",
"Only valid when used with {$p}text",
),
);
@@ -707,9 +793,11 @@ class ApiParse extends ApiBase {
public function getDescription() {
$p = $this->getModulePrefix();
+
return array(
- 'Parses content and returns parser output',
- 'See the various prop-Modules of action=query to get information from the current version of a page',
+ 'Parses content and returns parser output.',
+ 'See the various prop-Modules of action=query to get information from the current' .
+ 'version of a page.',
'There are several ways to specify the text to parse:',
"1) Specify a page or revision, using {$p}page, {$p}pageid, or {$p}oldid.",
"2) Specify content explicitly, using {$p}text, {$p}title, and {$p}contentmodel.",
@@ -717,26 +805,12 @@ class ApiParse extends ApiBase {
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => 'The page parameter cannot be used together with the text and title parameters' ),
- array( 'code' => 'missingrev', 'info' => 'There is no revision ID oldid' ),
- array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revisions' ),
- array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ),
- array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
- array( 'nosuchpageid' ),
- array( 'invalidtitle', 'title' ),
- array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
- array( 'code' => 'notwikitext', 'info' => 'The requested operation is only supported on wikitext content.' ),
- array( 'code' => 'pagecannotexist', 'info' => "Namespace doesn't allow actual pages" ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=parse&page=Project:Sandbox' => 'Parse a page',
- 'api.php?action=parse&text={{Project:Sandbox}}' => 'Parse wikitext',
- 'api.php?action=parse&text={{PAGENAME}}&title=Test' => 'Parse wikitext, specifying the page title',
+ 'api.php?action=parse&text={{Project:Sandbox}}&contentmodel=wikitext' => 'Parse wikitext',
+ 'api.php?action=parse&text={{PAGENAME}}&title=Test'
+ => 'Parse wikitext, specifying the page title',
'api.php?action=parse&summary=Some+[[link]]&prop=' => 'Parse a summary',
);
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index bd2fde2b..8b66781a 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -77,10 +77,6 @@ class ApiPatrol extends ApiBase {
public function getAllowedParams() {
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'rcid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
@@ -92,52 +88,23 @@ class ApiPatrol extends ApiBase {
public function getParamDescription() {
return array(
- 'token' => 'Patrol token obtained from list=recentchanges',
'rcid' => 'Recentchanges ID to patrol',
'revid' => 'Revision ID to patrol',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'rcid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Patrol a page or revision';
- }
-
- public function getPossibleErrors() {
- return array_merge(
- parent::getPossibleErrors(),
- parent::getRequireOnlyOneParameterErrorMessages( array( 'rcid', 'revid' ) ),
- array(
- array( 'nosuchrcid', 'rcid' ),
- array( 'nosuchrevid', 'revid' ),
- array(
- 'code' => 'notpatrollable',
- 'info' => "The revision can't be patrolled as it's too old"
- )
- ) );
+ return 'Patrol a page or revision.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
return 'patrol';
}
public function getExamples() {
return array(
- 'api.php?action=patrol&token=123abc&rcid=230672766',
- 'api.php?action=patrol&token=123abc&revid=230672766'
+ 'api.php?action=patrol&token=123ABC&rcid=230672766',
+ 'api.php?action=patrol&token=123ABC&revid=230672766'
);
}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index 7830c8b4..a3d12b7f 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -28,9 +28,7 @@
* @ingroup API
*/
class ApiProtect extends ApiBase {
-
public function execute() {
- global $wgRestrictionLevels;
$params = $this->extractRequestParams();
$pageObj = $this->getTitleOrPageId( $params, 'fromdbmaster' );
@@ -47,7 +45,11 @@ class ApiProtect extends ApiBase {
if ( count( $expiry ) == 1 ) {
$expiry = array_fill( 0, count( $params['protections'] ), $expiry[0] );
} else {
- $this->dieUsageMsg( array( 'toofewexpiries', count( $expiry ), count( $params['protections'] ) ) );
+ $this->dieUsageMsg( array(
+ 'toofewexpiries',
+ count( $expiry ),
+ count( $params['protections'] )
+ ) );
}
}
@@ -71,11 +73,11 @@ class ApiProtect extends ApiBase {
if ( !in_array( $p[0], $restrictionTypes ) && $p[0] != 'create' ) {
$this->dieUsageMsg( array( 'protect-invalidaction', $p[0] ) );
}
- if ( !in_array( $p[1], $wgRestrictionLevels ) && $p[1] != 'all' ) {
+ if ( !in_array( $p[1], $this->getConfig()->get( 'RestrictionLevels' ) ) && $p[1] != 'all' ) {
$this->dieUsageMsg( array( 'protect-invalidlevel', $p[1] ) );
}
- if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'never' ) ) ) {
+ if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'infinity', 'never' ) ) ) {
$expiryarray[$p[0]] = $db->getInfinity();
} else {
$exp = strtotime( $expiry[$i] );
@@ -89,18 +91,30 @@ class ApiProtect extends ApiBase {
}
$expiryarray[$p[0]] = $exp;
}
- $resultProtections[] = array( $p[0] => $protections[$p[0]],
- 'expiry' => ( $expiryarray[$p[0]] == $db->getInfinity() ?
- 'infinite' :
- wfTimestamp( TS_ISO_8601, $expiryarray[$p[0]] ) ) );
+ $resultProtections[] = array(
+ $p[0] => $protections[$p[0]],
+ 'expiry' => ( $expiryarray[$p[0]] == $db->getInfinity()
+ ? 'infinite'
+ : wfTimestamp( TS_ISO_8601, $expiryarray[$p[0]] )
+ )
+ );
}
$cascade = $params['cascade'];
+ if ( $params['watch'] ) {
+ $this->logFeatureUsage( 'action=protect&watch' );
+ }
$watch = $params['watch'] ? 'watch' : $params['watchlist'];
- $this->setWatch( $watch, $titleObj );
-
- $status = $pageObj->doUpdateRestrictions( $protections, $expiryarray, $cascade, $params['reason'], $this->getUser() );
+ $this->setWatch( $watch, $titleObj, 'watchdefault' );
+
+ $status = $pageObj->doUpdateRestrictions(
+ $protections,
+ $expiryarray,
+ $cascade,
+ $params['reason'],
+ $this->getUser()
+ );
if ( !$status->isOK() ) {
$this->dieStatus( $status );
@@ -134,10 +148,6 @@ class ApiProtect extends ApiBase {
'pageid' => array(
ApiBase::PARAM_TYPE => 'integer',
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'protections' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_REQUIRED => true,
@@ -167,62 +177,41 @@ class ApiProtect extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'title' => "Title of the page you want to (un)protect. Cannot be used together with {$p}pageid",
'pageid' => "ID of the page you want to (un)protect. Cannot be used together with {$p}title",
- 'token' => 'A protect token previously retrieved through prop=info',
'protections' => 'List of protection levels, formatted action=group (e.g. edit=sysop)',
- 'expiry' => array( 'Expiry timestamps. If only one timestamp is set, it\'ll be used for all protections.',
- 'Use \'infinite\', \'indefinite\' or \'never\', for a never-expiring protection.' ),
+ 'expiry' => array(
+ 'Expiry timestamps. If only one timestamp is ' .
+ 'set, it\'ll be used for all protections.',
+ 'Use \'infinite\', \'indefinite\', \'infinity\' or \'never\', for a never-expiring protection.'
+ ),
'reason' => 'Reason for (un)protecting',
- 'cascade' => array( 'Enable cascading protection (i.e. protect pages included in this page)',
- 'Ignored if not all protection levels are \'sysop\' or \'protect\'' ),
+ 'cascade' => array(
+ 'Enable cascading protection (i.e. protect pages included in this page)',
+ 'Ignored if not all protection levels are \'sysop\' or \'protect\''
+ ),
'watch' => 'If set, add the page being (un)protected to your watchlist',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'title' => 'string',
- 'reason' => 'string',
- 'cascade' => 'boolean'
- )
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
);
}
public function getDescription() {
- return 'Change the protection level of a page';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'toofewexpiries', 'noofexpiries', 'noofprotections' ),
- array( 'create-titleexists' ),
- array( 'missingtitle-createonly' ),
- array( 'protect-invalidaction', 'action' ),
- array( 'protect-invalidlevel', 'level' ),
- array( 'invalidexpiry', 'expiry' ),
- array( 'pastexpiry', 'expiry' ),
- )
- );
+ return 'Change the protection level of a page.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never',
- 'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=all|move=all&reason=Lifting%20restrictions'
+ 'api.php?action=protect&title=Main%20Page&token=123ABC&' .
+ 'protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never',
+ 'api.php?action=protect&title=Main%20Page&token=123ABC&' .
+ 'protections=edit=all|move=all&reason=Lifting%20restrictions'
);
}
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index 0812ba51..7667b235 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -30,51 +30,22 @@
* @ingroup API
*/
class ApiPurge extends ApiBase {
-
private $mPageSet;
/**
- * Add all items from $values into the result
- * @param array $result output
- * @param array $values values to add
- * @param string $flag the name of the boolean flag to mark this element
- * @param string $name if given, name of the value
- */
- private static function addValues( array &$result, $values, $flag = null, $name = null ) {
- foreach ( $values as $val ) {
- if ( $val instanceof Title ) {
- $v = array();
- ApiQueryBase::addTitleInfo( $v, $val );
- } elseif ( $name !== null ) {
- $v = array( $name => $val );
- } else {
- $v = $val;
- }
- if ( $flag !== null ) {
- $v[$flag] = '';
- }
- $result[] = $v;
- }
- }
-
- /**
* Purges the cache of a page
*/
public function execute() {
$params = $this->extractRequestParams();
+ $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+
$forceLinkUpdate = $params['forcelinkupdate'];
$forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate'];
$pageSet = $this->getPageSet();
$pageSet->execute();
- $result = array();
- self::addValues( $result, $pageSet->getInvalidTitles(), 'invalid', 'title' );
- self::addValues( $result, $pageSet->getSpecialTitles(), 'special', 'title' );
- self::addValues( $result, $pageSet->getMissingPageIDs(), 'missing', 'pageid' );
- self::addValues( $result, $pageSet->getMissingRevisionIDs(), 'missing', 'revid' );
- self::addValues( $result, $pageSet->getMissingTitles(), 'missing' );
- self::addValues( $result, $pageSet->getInterwikiTitlesAsResult() );
+ $result = $pageSet->getInvalidTitlesAndRevisions();
foreach ( $pageSet->getGoodTitles() as $title ) {
$r = array();
@@ -85,13 +56,17 @@ class ApiPurge extends ApiBase {
if ( $forceLinkUpdate || $forceRecursiveLinkUpdate ) {
if ( !$this->getUser()->pingLimiter( 'linkpurge' ) ) {
- global $wgEnableParserCache;
-
$popts = $page->makeParserOptions( 'canonical' );
# Parse content; note that HTML generation is only needed if we want to cache the result.
$content = $page->getContent( Revision::RAW );
- $p_result = $content->getParserOutput( $title, $page->getLatest(), $popts, $wgEnableParserCache );
+ $enableParserCache = $this->getConfig()->get( 'EnableParserCache' );
+ $p_result = $content->getParserOutput(
+ $title,
+ $page->getLatest(),
+ $popts,
+ $enableParserCache
+ );
# Update the links tables
$updates = $content->getSecondaryDataUpdates(
@@ -100,7 +75,7 @@ class ApiPurge extends ApiBase {
$r['linkupdate'] = '';
- if ( $wgEnableParserCache ) {
+ if ( $enableParserCache ) {
$pcache = ParserCache::singleton();
$pcache->save( $p_result, $page, $popts );
}
@@ -129,6 +104,8 @@ class ApiPurge extends ApiBase {
if ( $values ) {
$apiResult->addValue( null, 'redirects', $values );
}
+
+ $apiResult->endContinuation();
}
/**
@@ -139,6 +116,7 @@ class ApiPurge extends ApiBase {
if ( !isset( $this->mPageSet ) ) {
$this->mPageSet = new ApiPageSet( $this );
}
+
return $this->mPageSet;
}
@@ -154,11 +132,13 @@ class ApiPurge extends ApiBase {
public function getAllowedParams( $flags = 0 ) {
$result = array(
'forcelinkupdate' => false,
- 'forcerecursivelinkupdate' => false
+ 'forcerecursivelinkupdate' => false,
+ 'continue' => '',
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
}
+
return $result;
}
@@ -168,55 +148,16 @@ class ApiPurge extends ApiBase {
'forcelinkupdate' => 'Update the links tables',
'forcerecursivelinkupdate' => 'Update the links table, and update ' .
'the links tables for any page that uses this page as a template',
+ 'continue' => 'When more results are available, use this to continue',
);
}
- public function getResultProperties() {
- return array(
- ApiBase::PROP_LIST => true,
- '' => array(
- 'ns' => array(
- ApiBase::PROP_TYPE => 'namespace',
- ApiBase::PROP_NULLABLE => true
- ),
- 'title' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'pageid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'revid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'invalid' => 'boolean',
- 'special' => 'boolean',
- 'missing' => 'boolean',
- 'purged' => 'boolean',
- 'linkupdate' => 'boolean',
- 'iw' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- )
- );
- }
-
public function getDescription() {
return array( 'Purge the cache for the given titles.',
'Requires a POST request if the user is not logged in.'
);
}
- public function getPossibleErrors() {
- return array_merge(
- parent::getPossibleErrors(),
- $this->getPageSet()->getFinalPossibleErrors()
- );
- }
-
public function getExamples() {
return array(
'api.php?action=purge&titles=Main_Page|API' => 'Purge the "Main Page" and the "API" page',
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index e03837fc..7c750e41 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -44,18 +44,23 @@ class ApiQuery extends ApiBase {
private static $QueryPropModules = array(
'categories' => 'ApiQueryCategories',
'categoryinfo' => 'ApiQueryCategoryInfo',
+ 'contributors' => 'ApiQueryContributors',
'duplicatefiles' => 'ApiQueryDuplicateFiles',
'extlinks' => 'ApiQueryExternalLinks',
+ 'fileusage' => 'ApiQueryBacklinksprop',
'images' => 'ApiQueryImages',
'imageinfo' => 'ApiQueryImageInfo',
'info' => 'ApiQueryInfo',
'links' => 'ApiQueryLinks',
+ 'linkshere' => 'ApiQueryBacklinksprop',
'iwlinks' => 'ApiQueryIWLinks',
'langlinks' => 'ApiQueryLangLinks',
'pageprops' => 'ApiQueryPageProps',
+ 'redirects' => 'ApiQueryBacklinksprop',
'revisions' => 'ApiQueryRevisions',
'stashimageinfo' => 'ApiQueryStashImageInfo',
'templates' => 'ApiQueryLinks',
+ 'transcludedin' => 'ApiQueryBacklinksprop',
);
/**
@@ -68,6 +73,7 @@ class ApiQuery extends ApiBase {
'allimages' => 'ApiQueryAllImages',
'alllinks' => 'ApiQueryAllLinks',
'allpages' => 'ApiQueryAllPages',
+ 'allredirects' => 'ApiQueryAllLinks',
'alltransclusions' => 'ApiQueryAllLinks',
'allusers' => 'ApiQueryAllUsers',
'backlinks' => 'ApiQueryBacklinks',
@@ -83,6 +89,7 @@ class ApiQuery extends ApiBase {
'logevents' => 'ApiQueryLogEvents',
'pageswithprop' => 'ApiQueryPagesWithProp',
'pagepropnames' => 'ApiQueryPagePropNames',
+ 'prefixsearch' => 'ApiQueryPrefixSearch',
'protectedtitles' => 'ApiQueryProtectedTitles',
'querypage' => 'ApiQueryQueryPage',
'random' => 'ApiQueryRandom',
@@ -104,6 +111,7 @@ class ApiQuery extends ApiBase {
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
'filerepoinfo' => 'ApiQueryFileRepoInfo',
+ 'tokens' => 'ApiQueryTokens',
);
/**
@@ -114,26 +122,24 @@ class ApiQuery extends ApiBase {
private $mParams;
private $mNamedDB = array();
private $mModuleMgr;
- private $mGeneratorContinue;
- private $mUseLegacyContinue;
/**
- * @param $main ApiMain
- * @param $action string
+ * @param ApiMain $main
+ * @param string $action
*/
- public function __construct( $main, $action ) {
+ public function __construct( ApiMain $main, $action ) {
parent::__construct( $main, $action );
$this->mModuleMgr = new ApiModuleManager( $this );
// Allow custom modules to be added in LocalSettings.php
- global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules;
+ $config = $this->getConfig();
$this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
- $this->mModuleMgr->addModules( $wgAPIPropModules, 'prop' );
+ $this->mModuleMgr->addModules( $config->get( 'APIPropModules' ), 'prop' );
$this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
- $this->mModuleMgr->addModules( $wgAPIListModules, 'list' );
+ $this->mModuleMgr->addModules( $config->get( 'APIListModules' ), 'list' );
$this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
- $this->mModuleMgr->addModules( $wgAPIMetaModules, 'meta' );
+ $this->mModuleMgr->addModules( $config->get( 'APIMetaModules' ), 'meta' );
// Create PageSet that will process titles/pageids/revids/generator
$this->mPageSet = new ApiPageSet( $this );
@@ -163,6 +169,7 @@ class ApiQuery extends ApiBase {
$this->mNamedDB[$name] = wfGetDB( $db, $groups );
$this->profileDBOut();
}
+
return $this->mNamedDB[$name];
}
@@ -177,17 +184,18 @@ class ApiQuery extends ApiBase {
/**
* Get the array mapping module names to class names
* @deprecated since 1.21, use getModuleManager()'s methods instead
- * @return array array(modulename => classname)
+ * @return array Array(modulename => classname)
*/
public function getModules() {
wfDeprecated( __METHOD__, '1.21' );
+
return $this->getModuleManager()->getNamesWithClasses();
}
/**
* Get the generators array mapping module names to class names
* @deprecated since 1.21, list of generators is maintained by ApiPageSet
- * @return array array(modulename => classname)
+ * @return array Array(modulename => classname)
*/
public function getGenerators() {
wfDeprecated( __METHOD__, '1.21' );
@@ -197,6 +205,7 @@ class ApiQuery extends ApiBase {
$gens[$name] = $class;
}
}
+
return $gens;
}
@@ -204,7 +213,7 @@ class ApiQuery extends ApiBase {
* Get whether the specified module is a prop, list or a meta query module
* @deprecated since 1.21, use getModuleManager()->getModuleGroup()
* @param string $moduleName Name of the module to find type for
- * @return mixed string or null
+ * @return string|null
*/
function getModuleType( $moduleName ) {
return $this->getModuleManager()->getModuleGroup( $moduleName );
@@ -216,8 +225,8 @@ class ApiQuery extends ApiBase {
public function getCustomPrinter() {
// If &exportnowrap is set, use the raw formatter
if ( $this->getParameter( 'export' ) &&
- $this->getParameter( 'exportnowrap' ) )
- {
+ $this->getParameter( 'exportnowrap' )
+ ) {
return new ApiFormatRaw( $this->getMain(),
$this->getMain()->createPrinterByName( 'xml' ) );
} else {
@@ -238,23 +247,24 @@ class ApiQuery extends ApiBase {
public function execute() {
$this->mParams = $this->extractRequestParams();
- // $pagesetParams is a array of parameter names used by the pageset generator
- // or null if pageset has already finished and is no longer needed
- // $completeModules is a set of complete modules with the name as key
- $this->initContinue( $pagesetParams, $completeModules );
-
// Instantiate requested modules
$allModules = array();
$this->instantiateModules( $allModules, 'prop' );
- $propModules = $allModules; // Keep a copy
+ $propModules = array_keys( $allModules );
$this->instantiateModules( $allModules, 'list' );
$this->instantiateModules( $allModules, 'meta' );
// Filter modules based on continue parameter
- $modules = $this->initModules( $allModules, $completeModules, $pagesetParams !== null );
+ list( $generatorDone, $modules ) = $this->getResult()->beginContinuation(
+ $this->mParams['continue'], $allModules, $propModules
+ );
- // Execute pageset if in legacy mode or if pageset is not done
- if ( $completeModules === null || $pagesetParams !== null ) {
+ if ( !$generatorDone ) {
+ // Query modules may optimize data requests through the $this->getPageSet()
+ // object by adding extra fields from the page table.
+ foreach ( $modules as $module ) {
+ $module->requestExtraData( $this->mPageSet );
+ }
// Populate page/revision information
$this->mPageSet->execute();
// Record page information (title, namespace, if exists, etc)
@@ -280,134 +290,10 @@ class ApiQuery extends ApiBase {
// Set the cache mode
$this->getMain()->setCacheMode( $cacheMode );
- if ( $completeModules === null ) {
- return; // Legacy continue, we are done
- }
-
- // Reformat query-continue result section
- $result = $this->getResult();
- $qc = $result->getData();
- if ( isset( $qc['query-continue'] ) ) {
- $qc = $qc['query-continue'];
- $result->unsetValue( null, 'query-continue' );
- } elseif ( $this->mGeneratorContinue !== null ) {
- $qc = array();
- } else {
- // no more "continue"s, we are done!
- return;
- }
-
- // we are done with all the modules that do not have result in query-continue
- $completeModules = array_merge( $completeModules, array_diff_key( $modules, $qc ) );
- if ( $pagesetParams !== null ) {
- // The pageset is still in use, check if all props have finished
- $incompleteProps = array_intersect_key( $propModules, $qc );
- if ( count( $incompleteProps ) > 0 ) {
- // Properties are not done, continue with the same pageset state - copy current parameters
- $main = $this->getMain();
- $contValues = array();
- foreach ( $pagesetParams as $param ) {
- // The param name is already prefix-encoded
- $contValues[$param] = $main->getVal( $param );
- }
- } elseif ( $this->mGeneratorContinue !== null ) {
- // Move to the next set of pages produced by pageset, properties need to be restarted
- $contValues = $this->mGeneratorContinue;
- $pagesetParams = array_keys( $contValues );
- $completeModules = array_diff_key( $completeModules, $propModules );
- } else {
- // Done with the pageset, finish up with the the lists and meta modules
- $pagesetParams = null;
- }
- }
-
- $continue = '||' . implode( '|', array_keys( $completeModules ) );
- if ( $pagesetParams !== null ) {
- // list of all pageset parameters to use in the next request
- $continue = implode( '|', $pagesetParams ) . $continue;
- } else {
- // we are done with the pageset
- $contValues = array();
- $continue = '-' . $continue;
- }
- $contValues['continue'] = $continue;
- foreach ( $qc as $qcModule ) {
- foreach ( $qcModule as $qcKey => $qcValue ) {
- $contValues[$qcKey] = $qcValue;
- }
- }
- $this->getResult()->addValue( null, 'continue', $contValues );
- }
-
- /**
- * Parse 'continue' parameter into the list of complete modules and a list of generator parameters
- * @param array|null $pagesetParams returns list of generator params or null if pageset is done
- * @param array|null $completeModules returns list of finished modules (as keys), or null if legacy
- */
- private function initContinue( &$pagesetParams, &$completeModules ) {
- $pagesetParams = array();
- $continue = $this->mParams['continue'];
- if ( $continue !== null ) {
- $this->mUseLegacyContinue = false;
- if ( $continue !== '' ) {
- // Format: ' pagesetParam1 | pagesetParam2 || module1 | module2 | module3 | ...
- // If pageset is done, use '-'
- $continue = explode( '||', $continue );
- $this->dieContinueUsageIf( count( $continue ) !== 2 );
- if ( $continue[0] === '-' ) {
- $pagesetParams = null; // No need to execute pageset
- } elseif ( $continue[0] !== '' ) {
- // list of pageset params that might need to be repeated
- $pagesetParams = explode( '|', $continue[0] );
- }
- $continue = $continue[1];
- }
- if ( $continue !== '' ) {
- $completeModules = array_flip( explode( '|', $continue ) );
- } else {
- $completeModules = array();
- }
- } else {
- $this->mUseLegacyContinue = true;
- $completeModules = null;
- }
- }
-
- /**
- * Validate sub-modules, filter out completed ones, and do requestExtraData()
- * @param array $allModules An dict of name=>instance of all modules requested by the client
- * @param array|null $completeModules list of finished modules, or null if legacy continue
- * @param bool $usePageset True if pageset will be executed
- * @return array of modules to be processed during this execution
- */
- private function initModules( $allModules, $completeModules, $usePageset ) {
- $modules = $allModules;
- $tmp = $completeModules;
- $wasPosted = $this->getRequest()->wasPosted();
-
- /** @var $module ApiQueryBase */
- foreach ( $allModules as $moduleName => $module ) {
- if ( !$wasPosted && $module->mustBePosted() ) {
- $this->dieUsageMsgOrDebug( array( 'mustbeposted', $moduleName ) );
- }
- if ( $completeModules !== null && array_key_exists( $moduleName, $completeModules ) ) {
- // If this module is done, mark all its params as used
- $module->extractRequestParams();
- // Make sure this module is not used during execution
- unset( $modules[$moduleName] );
- unset( $tmp[$moduleName] );
- } elseif ( $completeModules === null || $usePageset ) {
- // Query modules may optimize data requests through the $this->getPageSet()
- // object by adding extra fields from the page table.
- // This function will gather all the extra request fields from the modules.
- $module->requestExtraData( $this->mPageSet );
- } else {
- // Error - this prop module must have finished before generator is done
- $this->dieContinueUsageIf( $this->mModuleMgr->getModuleGroup( $moduleName ) === 'prop' );
- }
- }
- $this->dieContinueUsageIf( $completeModules !== null && count( $tmp ) !== 0 );
- return $modules;
+ // Write the continuation data into the result
+ $this->getResult()->endContinuation(
+ $this->mParams['continue'] === null ? 'raw' : 'standard'
+ );
}
/**
@@ -415,8 +301,8 @@ class ApiQuery extends ApiBase {
* The cache mode may increase in the level of privacy, but public modules
* added to private data do not decrease the level of privacy.
*
- * @param $cacheMode string
- * @param $modCacheMode string
+ * @param string $cacheMode
+ * @param string $modCacheMode
* @return string
*/
protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
@@ -429,21 +315,26 @@ class ApiQuery extends ApiBase {
} else { // private
$cacheMode = 'private';
}
+
return $cacheMode;
}
/**
* Create instances of all modules requested by the client
- * @param array $modules to append instantiated modules to
+ * @param array $modules To append instantiated modules to
* @param string $param Parameter name to read modules from
*/
private function instantiateModules( &$modules, $param ) {
+ $wasPosted = $this->getRequest()->wasPosted();
if ( isset( $this->mParams[$param] ) ) {
foreach ( $this->mParams[$param] as $moduleName ) {
$instance = $this->mModuleMgr->getModule( $moduleName, $param );
if ( $instance === null ) {
ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
}
+ if ( !$wasPosted && $instance->mustBePosted() ) {
+ $this->dieUsageMsgOrDebug( array( 'mustbeposted', $moduleName ) );
+ }
// Ignore duplicates. TODO 2.0: die()?
if ( !array_key_exists( $moduleName, $modules ) ) {
$modules[$moduleName] = $instance;
@@ -461,29 +352,29 @@ class ApiQuery extends ApiBase {
$pageSet = $this->getPageSet();
$result = $this->getResult();
- // We don't check for a full result set here because we can't be adding
- // more than 380K. The maximum revision size is in the megabyte range,
- // and the maximum result size must be even higher than that.
+ // We can't really handle max-result-size failure here, but we need to
+ // check anyway in case someone set the limit stupidly low.
+ $fit = true;
$values = $pageSet->getNormalizedTitlesAsResult( $result );
if ( $values ) {
- $result->addValue( 'query', 'normalized', $values );
+ $fit = $fit && $result->addValue( 'query', 'normalized', $values );
}
$values = $pageSet->getConvertedTitlesAsResult( $result );
if ( $values ) {
- $result->addValue( 'query', 'converted', $values );
+ $fit = $fit && $result->addValue( 'query', 'converted', $values );
}
$values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
if ( $values ) {
- $result->addValue( 'query', 'interwiki', $values );
+ $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
}
$values = $pageSet->getRedirectTitlesAsResult( $result );
if ( $values ) {
- $result->addValue( 'query', 'redirects', $values );
+ $fit = $fit && $result->addValue( 'query', 'redirects', $values );
}
$values = $pageSet->getMissingRevisionIDsAsResult( $result );
if ( $values ) {
- $result->addValue( 'query', 'badrevids', $values );
+ $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
}
// Page elements
@@ -514,10 +405,12 @@ class ApiQuery extends ApiBase {
ApiQueryBase::addTitleInfo( $vals, $title );
$vals['special'] = '';
if ( $title->isSpecialPage() &&
- !SpecialPageFactory::exists( $title->getDBkey() ) ) {
+ !SpecialPageFactory::exists( $title->getDBkey() )
+ ) {
$vals['missing'] = '';
} elseif ( $title->getNamespace() == NS_MEDIA &&
- !wfFindFile( $title ) ) {
+ !wfFindFile( $title )
+ ) {
$vals['missing'] = '';
}
$pages[$fakeId] = $vals;
@@ -537,12 +430,21 @@ class ApiQuery extends ApiBase {
// json treats all map keys as strings - converting to match
$pageIDs = array_map( 'strval', $pageIDs );
$result->setIndexedTagName( $pageIDs, 'id' );
- $result->addValue( 'query', 'pageids', $pageIDs );
+ $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
}
$result->setIndexedTagName( $pages, 'page' );
- $result->addValue( 'query', 'pages', $pages );
+ $fit = $fit && $result->addValue( 'query', 'pages', $pages );
}
+
+ if ( !$fit ) {
+ $this->dieUsage(
+ 'The value of $wgAPIMaxResultSize on this wiki is ' .
+ 'too small to hold basic result information',
+ 'badconfig'
+ );
+ }
+
if ( $this->mParams['export'] ) {
$this->doExport( $pageSet, $result );
}
@@ -552,26 +454,21 @@ class ApiQuery extends ApiBase {
* This method is called by the generator base when generator in the smart-continue
* mode tries to set 'query-continue' value. ApiQuery stores those values separately
* until the post-processing when it is known if the generation should continue or repeat.
- * @param ApiQueryGeneratorBase $module generator module
+ * @deprecated since 1.24
+ * @param ApiQueryGeneratorBase $module Generator module
* @param string $paramName
* @param mixed $paramValue
- * @return bool true if processed, false if this is a legacy continue
+ * @return bool True if processed, false if this is a legacy continue
*/
public function setGeneratorContinue( $module, $paramName, $paramValue ) {
- if ( $this->mUseLegacyContinue ) {
- return false;
- }
- $paramName = $module->encodeParamName( $paramName );
- if ( $this->mGeneratorContinue === null ) {
- $this->mGeneratorContinue = array();
- }
- $this->mGeneratorContinue[$paramName] = $paramValue;
- return true;
+ wfDeprecated( __METHOD__, '1.24' );
+ $this->getResult()->setGeneratorContinueParam( $module, $paramName, $paramValue );
+ return $this->getParameter( 'continue' ) !== null;
}
/**
- * @param $pageSet ApiPageSet Pages to be exported
- * @param $result ApiResult Result to output to
+ * @param ApiPageSet $pageSet Pages to be exported
+ * @param ApiResult $result Result to output to
*/
private function doExport( $pageSet, $result ) {
$exportTitles = array();
@@ -601,43 +498,43 @@ class ApiQuery extends ApiBase {
// Don't check the size of exported stuff
// It's not continuable, so it would cause more
// problems than it'd solve
- $result->disableSizeCheck();
if ( $this->mParams['exportnowrap'] ) {
$result->reset();
// Raw formatter will handle this
- $result->addValue( null, 'text', $exportxml );
- $result->addValue( null, 'mime', 'text/xml' );
+ $result->addValue( null, 'text', $exportxml, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
} else {
$r = array();
ApiResult::setContent( $r, $exportxml );
- $result->addValue( 'query', 'export', $r );
+ $result->addValue( 'query', 'export', $r, ApiResult::NO_SIZE_CHECK );
}
- $result->enableSizeCheck();
}
public function getAllowedParams( $flags = 0 ) {
$result = array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'prop' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'list' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'list' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'meta' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'meta' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'indexpageids' => false,
'export' => false,
'exportnowrap' => false,
'iwurl' => false,
'continue' => null,
+ 'rawcontinue' => false,
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
}
+
return $result;
}
@@ -699,38 +596,41 @@ class ApiQuery extends ApiBase {
public function getParamDescription() {
return $this->getPageSet()->getFinalParamDescription() + array(
- 'prop' => 'Which properties to get for the titles/revisions/pageids. Module help is available below',
+ 'prop' => 'Which properties to get for the titles/revisions/pageids. ' .
+ 'Module help is available below',
'list' => 'Which lists to get. Module help is available below',
'meta' => 'Which metadata to get about the site. Module help is available below',
'indexpageids' => 'Include an additional pageids section listing all returned page IDs',
'export' => 'Export the current revisions of all given or generated pages',
- 'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export',
+ 'exportnowrap' => 'Return the export XML without wrapping it in an ' .
+ 'XML result (same format as Special:Export). Can only be used with export',
'iwurl' => 'Whether to get the full URL if the title is an interwiki link',
'continue' => array(
- 'When present, formats query-continue as key-value pairs that should simply be merged into the original request.',
+ 'When present, formats query-continue as key-value pairs that ' .
+ 'should simply be merged into the original request.',
'This parameter must be set to an empty string in the initial query.',
- 'This parameter is recommended for all new development, and will be made default in the next API version.' ),
+ 'This parameter is recommended for all new development, and ' .
+ 'will be made default in the next API version.'
+ ),
+ 'rawcontinue' => 'Currently ignored. In the future, \'continue=\' will become the ' .
+ 'default and this will be needed to receive the raw query-continue data.',
);
}
public function getDescription() {
return array(
- 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',
+ 'Query API module allows applications to get needed pieces of data ' .
+ 'from the MediaWiki databases,',
'and is loosely based on the old query.php interface.',
- 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites'
- );
- }
-
- public function getPossibleErrors() {
- return array_merge(
- parent::getPossibleErrors(),
- $this->getPageSet()->getFinalPossibleErrors()
+ 'All data modifications will first have to use query to acquire a ' .
+ 'token to prevent abuse from malicious sites.'
);
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment&continue=',
+ 'api.php?action=query&prop=revisions&meta=siteinfo&' .
+ 'titles=Main%20Page&rvprop=user|comment&continue=',
'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions&continue=',
);
}
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index 3f5c6ee7..79fab727 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -32,7 +32,7 @@
*/
class ApiQueryAllCategories extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ac' );
}
@@ -49,7 +49,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
$db = $this->getDB();
@@ -67,8 +67,12 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $params['from'] === null
+ ? null
+ : $this->titlePartToKey( $params['from'], NS_CATEGORY ) );
+ $to = ( $params['to'] === null
+ ? null
+ : $this->titlePartToKey( $params['to'], NS_CATEGORY ) );
$this->addWhereRange( 'cat_title', $dir, $from, $to );
$min = $params['min'];
@@ -80,7 +84,9 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'cat_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'cat_title' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], NS_CATEGORY ),
+ $db->anyString() ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -109,8 +115,9 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$result = $this->getResult();
$count = 0;
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional cats to be had. Stop here...
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional cats to be had. Stop here...
$this->setContinueEnumParameter( 'continue', $row->cat_title );
break;
}
@@ -200,25 +207,8 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- '*' => 'string'
- ),
- 'size' => array(
- 'size' => 'integer',
- 'pages' => 'integer',
- 'files' => 'integer',
- 'subcats' => 'integer'
- ),
- 'hidden' => array(
- 'hidden' => 'boolean'
- )
- );
- }
-
public function getDescription() {
- return 'Enumerate all categories';
+ return 'Enumerate all categories.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php
index ccc7a3a2..9dc5f69a 100644
--- a/includes/api/ApiQueryAllImages.php
+++ b/includes/api/ApiQueryAllImages.php
@@ -32,10 +32,9 @@
* @ingroup API
*/
class ApiQueryAllImages extends ApiQueryGeneratorBase {
-
protected $mRepo;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ai' );
$this->mRepo = RepoGroup::singleton()->getLocalRepo();
}
@@ -60,25 +59,32 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function executeGenerator( $resultPageSet ) {
if ( $resultPageSet->isResolvingRedirects() ) {
- $this->dieUsage( 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator', 'params' );
+ $this->dieUsage(
+ 'Use "gaifilterredir=nonredirects" option instead of "redirects" ' .
+ 'when using allimages as a generator',
+ 'params'
+ );
}
$this->run( $resultPageSet );
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
$repo = $this->mRepo;
if ( !$repo instanceof LocalRepo ) {
- $this->dieUsage( 'Local file repository does not support querying all images', 'unsupportedrepo' );
+ $this->dieUsage(
+ 'Local file repository does not support querying all images',
+ 'unsupportedrepo'
+ );
}
$prefix = $this->getModulePrefix();
@@ -103,11 +109,17 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$disallowed = array( 'start', 'end', 'user' );
foreach ( $disallowed as $pname ) {
if ( isset( $params[$pname] ) ) {
- $this->dieUsage( "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=timestamp", 'badparams' );
+ $this->dieUsage(
+ "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=timestamp",
+ 'badparams'
+ );
}
}
if ( $params['filterbots'] != 'all' ) {
- $this->dieUsage( "Parameter '{$prefix}filterbots' can only be used with {$prefix}sort=timestamp", 'badparams' );
+ $this->dieUsage(
+ "Parameter '{$prefix}filterbots' can only be used with {$prefix}sort=timestamp",
+ 'badparams'
+ );
}
// Pagination
@@ -120,28 +132,56 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
// Image filters
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $params['from'] === null ? null : $this->titlePartToKey( $params['from'], NS_FILE ) );
+ $to = ( $params['to'] === null ? null : $this->titlePartToKey( $params['to'], NS_FILE ) );
$this->addWhereRange( 'img_name', ( $ascendingOrder ? 'newer' : 'older' ), $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'img_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'img_name' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], NS_FILE ),
+ $db->anyString() ) );
}
} else {
// Check mutually exclusive params
$disallowed = array( 'from', 'to', 'prefix' );
foreach ( $disallowed as $pname ) {
if ( isset( $params[$pname] ) ) {
- $this->dieUsage( "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name", 'badparams' );
+ $this->dieUsage(
+ "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name",
+ 'badparams'
+ );
}
}
if ( !is_null( $params['user'] ) && $params['filterbots'] != 'all' ) {
- // Since filterbots checks if each user has the bot right, it doesn't make sense to use it with user
- $this->dieUsage( "Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together", 'badparams' );
+ // Since filterbots checks if each user has the bot right, it
+ // doesn't make sense to use it with user
+ $this->dieUsage(
+ "Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together",
+ 'badparams'
+ );
}
// Pagination
- $this->addTimestampWhereRange( 'img_timestamp', ( $ascendingOrder ? 'newer' : 'older' ), $params['start'], $params['end'] );
+ $this->addTimestampWhereRange(
+ 'img_timestamp',
+ $ascendingOrder ? 'newer' : 'older',
+ $params['start'],
+ $params['end']
+ );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'img_name', $ascendingOrder ? 'newer' : 'older', null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $op = ( $ascendingOrder ? '>' : '<' );
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueName = $db->addQuotes( $cont[1] );
+ $this->addWhere( "img_timestamp $op $continueTimestamp OR " .
+ "(img_timestamp = $continueTimestamp AND " .
+ "img_name $op= $continueName)"
+ );
+ }
// Image filters
if ( !is_null( $params['user'] ) ) {
@@ -156,7 +196,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'ug_user = img_user'
)
) ) );
- $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL': 'NOT NULL' );
+ $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL' : 'NOT NULL' );
$this->addWhere( "ug_group IS $groupCond" );
}
}
@@ -188,8 +228,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
if ( !is_null( $params['mime'] ) ) {
- global $wgMiserMode;
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' );
}
@@ -222,12 +261,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
if ( $params['sort'] == 'name' ) {
$this->setContinueEnumParameter( 'continue', $row->img_name );
} else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->img_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->img_timestamp|$row->img_name" );
}
break;
}
@@ -243,7 +283,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
if ( $params['sort'] == 'name' ) {
$this->setContinueEnumParameter( 'continue', $row->img_name );
} else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->img_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->img_timestamp|$row->img_name" );
}
break;
}
@@ -326,6 +366,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'sort' => 'Property to sort by',
'dir' => 'The direction in which to list',
@@ -335,54 +376,25 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'start' => "The timestamp to start enumerating from. Can only be used with {$p}sort=timestamp",
'end' => "The timestamp to end enumerating. Can only be used with {$p}sort=timestamp",
'prop' => ApiQueryImageInfo::getPropertyDescriptions( $this->propertyFilter ),
- 'prefix' => "Search for all image titles that begin with this value. Can only be used with {$p}sort=name",
+ 'prefix' => "Search for all image titles that begin with this " .
+ "value. Can only be used with {$p}sort=name",
'minsize' => 'Limit to images with at least this many bytes',
'maxsize' => 'Limit to images with at most this many bytes',
'sha1' => "SHA1 hash of image. Overrides {$p}sha1base36",
'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)',
- 'user' => "Only return files uploaded by this user. Can only be used with {$p}sort=timestamp. Cannot be used together with {$p}filterbots",
- 'filterbots' => "How to filter files uploaded by bots. Can only be used with {$p}sort=timestamp. Cannot be used together with {$p}user",
+ 'user' => "Only return files uploaded by this user. Can only be used " .
+ "with {$p}sort=timestamp. Cannot be used together with {$p}filterbots",
+ 'filterbots' => "How to filter files uploaded by bots. Can only be " .
+ "used with {$p}sort=timestamp. Cannot be used together with {$p}user",
'mime' => 'What MIME type to search for. e.g. image/jpeg. Disabled in Miser Mode',
'limit' => 'How many images in total to return',
);
}
- private $propertyFilter = array( 'archivename', 'thumbmime' );
-
- public function getResultProperties() {
- return array_merge(
- array(
- '' => array(
- 'name' => 'string',
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- ),
- ApiQueryImageInfo::getResultPropertiesFiltered( $this->propertyFilter )
- );
- }
+ private $propertyFilter = array( 'archivename', 'thumbmime', 'uploadwarning' );
public function getDescription() {
- return 'Enumerate all images sequentially';
- }
-
- public function getPossibleErrors() {
- $p = $this->getModulePrefix();
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator' ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}start' can only be used with {$p}sort=timestamp" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}end' can only be used with {$p}sort=timestamp" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}user' can only be used with {$p}sort=timestamp" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}filterbots' can only be used with {$p}sort=timestamp" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}from' can only be used with {$p}sort=name" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}to' can only be used with {$p}sort=name" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}prefix' can only be used with {$p}sort=name" ),
- array( 'code' => 'badparams', 'info' => "Parameters '{$p}user' and '{$p}filterbots' cannot be used together" ),
- array( 'code' => 'unsupportedrepo', 'info' => 'Local file repository does not support querying all images' ),
- array( 'code' => 'mimesearchdisabled', 'info' => 'MIME search disabled in Miser Mode' ),
- array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
- array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
- ) );
+ return 'Enumerate all images sequentially.';
}
public function getExamples() {
@@ -391,11 +403,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'Simple Use',
'Show a list of files starting at the letter "B"',
),
- 'api.php?action=query&list=allimages&aiprop=user|timestamp|url&aisort=timestamp&aidir=older' => array(
+ 'api.php?action=query&list=allimages&aiprop=user|timestamp|url&' .
+ 'aisort=timestamp&aidir=older' => array(
'Simple Use',
'Show a list of recently uploaded files similar to Special:NewFiles',
),
- 'api.php?action=query&generator=allimages&gailimit=4&gaifrom=T&prop=imageinfo' => array(
+ 'api.php?action=query&generator=allimages&gailimit=4&' .
+ 'gaifrom=T&prop=imageinfo' => array(
'Using as Generator',
'Show info about 4 files starting at the letter "T"',
),
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index 47d1bcef..903dee42 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -31,15 +31,21 @@
*/
class ApiQueryAllLinks extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ private $table, $tablePrefix, $indexTag,
+ $description, $descriptionWhat, $descriptionTargets, $descriptionLinking;
+ private $fieldTitle = 'title';
+ private $dfltNamespace = NS_MAIN;
+ private $hasNamespace = true;
+ private $useIndex = null;
+ private $props = array(), $propHelp = array();
+
+ public function __construct( ApiQuery $query, $moduleName ) {
switch ( $moduleName ) {
case 'alllinks':
$prefix = 'al';
$this->table = 'pagelinks';
$this->tablePrefix = 'pl_';
- $this->fieldTitle = 'title';
- $this->dfltNamespace = NS_MAIN;
- $this->hasNamespace = true;
+ $this->useIndex = 'pl_namespace';
$this->indexTag = 'l';
$this->description = 'Enumerate all links that point to a given namespace';
$this->descriptionWhat = 'link';
@@ -50,11 +56,11 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$prefix = 'at';
$this->table = 'templatelinks';
$this->tablePrefix = 'tl_';
- $this->fieldTitle = 'title';
$this->dfltNamespace = NS_TEMPLATE;
- $this->hasNamespace = true;
+ $this->useIndex = 'tl_namespace';
$this->indexTag = 't';
- $this->description = 'List all transclusions (pages embedded using {{x}}), including non-existing';
+ $this->description =
+ 'List all transclusions (pages embedded using {{x}}), including non-existing';
$this->descriptionWhat = 'transclusion';
$this->descriptionTargets = 'transcluded titles';
$this->descriptionLinking = 'transcluding';
@@ -72,6 +78,24 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$this->descriptionTargets = 'file titles';
$this->descriptionLinking = 'using';
break;
+ case 'allredirects':
+ $prefix = 'ar';
+ $this->table = 'redirect';
+ $this->tablePrefix = 'rd_';
+ $this->indexTag = 'r';
+ $this->description = 'List all redirects to a namespace';
+ $this->descriptionWhat = 'redirect';
+ $this->descriptionTargets = 'target pages';
+ $this->descriptionLinking = 'redirecting';
+ $this->props = array(
+ 'fragment' => 'rd_fragment',
+ 'interwiki' => 'rd_interwiki',
+ );
+ $this->propHelp = array(
+ ' fragment - Adds the fragment from the redirect, if any',
+ ' interwiki - Adds the interwiki prefix from the redirect, if any',
+ );
+ break;
default:
ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
}
@@ -92,7 +116,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -111,10 +135,13 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
if ( $params['unique'] ) {
- if ( $fld_ids ) {
+ $matches = array_intersect_key( $prop, $this->props + array( 'ids' => 1 ) );
+ if ( $matches ) {
+ $p = $this->getModulePrefix();
$this->dieUsage(
- "{$this->getModuleName()} cannot return corresponding page ids in unique {$this->descriptionWhat}s mode",
- 'params' );
+ "Cannot use {$p}prop=" . join( '|', array_keys( $matches ) ) . " with {$p}unique",
+ 'params'
+ );
}
$this->addOption( 'DISTINCT' );
}
@@ -145,19 +172,25 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
// 'continue' always overrides 'from'
- $from = ( $continue || is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $continue || $params['from'] === null ? null :
+ $this->titlePartToKey( $params['from'], $namespace ) );
+ $to = ( $params['to'] === null ? null :
+ $this->titlePartToKey( $params['to'], $namespace ) );
$this->addWhereRange( $pfx . $fieldTitle, 'newer', $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( $pfx . $fieldTitle . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( $pfx . $fieldTitle . $db->buildLike( $this->titlePartToKey(
+ $params['prefix'], $namespace ), $db->anyString() ) );
}
$this->addFields( array( 'pl_title' => $pfx . $fieldTitle ) );
$this->addFieldsIf( array( 'pl_from' => $pfx . 'from' ), !$params['unique'] );
+ foreach ( $this->props as $name => $field ) {
+ $this->addFieldsIf( $field, isset( $prop[$name] ) );
+ }
- if ( $this->hasNamespace ) {
- $this->addOption( 'USE INDEX', $pfx . 'namespace' );
+ if ( $this->useIndex ) {
+ $this->addOption( 'USE INDEX', $this->useIndex );
}
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
@@ -177,8 +210,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
if ( $params['unique'] ) {
$this->setContinueEnumParameter( 'continue', $row->pl_title );
} else {
@@ -196,6 +230,11 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$title = Title::makeTitle( $namespace, $row->pl_title );
ApiQueryBase::addTitleInfo( $vals, $title );
}
+ foreach ( $this->props as $name => $field ) {
+ if ( isset( $prop[$name] ) && $row->$field !== null && $row->$field !== '' ) {
+ $vals[$name] = $row->$field;
+ }
+ }
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
if ( $params['unique'] ) {
@@ -231,10 +270,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'title',
- ApiBase::PARAM_TYPE => array(
- 'ids',
- 'title'
- )
+ ApiBase::PARAM_TYPE => array_merge(
+ array( 'ids', 'title' ), array_keys( $this->props )
+ ),
),
'namespace' => array(
ApiBase::PARAM_DFLT => $this->dfltNamespace,
@@ -258,6 +296,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
if ( !$this->hasNamespace ) {
unset( $allowedParams['namespace'] );
}
+
return $allowedParams;
}
@@ -271,68 +310,55 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
'to' => "The title of the $what to stop enumerating at",
'prefix' => "Search for all $targets that begin with this value",
'unique' => array(
- "Only show distinct $targets. Cannot be used with {$p}prop=ids.",
+ "Only show distinct $targets. Cannot be used with {$p}prop=" .
+ join( '|', array_keys( array( 'ids' => 1 ) + $this->props ) ) . '.',
'When used as a generator, yields target pages instead of source pages.',
),
'prop' => array(
'What pieces of information to include',
- " ids - Adds the pageid of the $linking page (Cannot be used with {$p}unique)",
- " title - Adds the title of the $what",
+ " ids - Adds the pageid of the $linking page (Cannot be used with {$p}unique)",
+ " title - Adds the title of the $what",
),
'namespace' => 'The namespace to enumerate',
'limit' => 'How many total items to return',
'continue' => 'When more results are available, use this to continue',
'dir' => 'The direction in which to list',
);
+ foreach ( $this->propHelp as $help ) {
+ $paramDescription['prop'][] = "$help (Cannot be used with {$p}unique)";
+ }
if ( !$this->hasNamespace ) {
unset( $paramDescription['namespace'] );
}
- return $paramDescription;
- }
- public function getResultProperties() {
- return array(
- 'ids' => array(
- 'fromid' => 'integer'
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
+ return $paramDescription;
}
public function getDescription() {
return $this->description;
}
- public function getPossibleErrors() {
- $m = $this->getModuleName();
- $what = $this->descriptionWhat;
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique {$what}s mode" ),
- ) );
- }
-
public function getExamples() {
$p = $this->getModulePrefix();
$name = $this->getModuleName();
$what = $this->descriptionWhat;
$targets = $this->descriptionTargets;
+
return array(
"api.php?action=query&list={$name}&{$p}from=B&{$p}prop=ids|title"
- => "List $targets with page ids they are from, including missing ones. Start at B",
+ => "List $targets with page ids they are from, including missing ones. Start at B",
"api.php?action=query&list={$name}&{$p}unique=&{$p}from=B"
- => "List unique $targets",
+ => "List unique $targets",
"api.php?action=query&generator={$name}&g{$p}unique=&g{$p}from=B"
- => "Gets all $targets, marking the missing ones",
+ => "Gets all $targets, marking the missing ones",
"api.php?action=query&generator={$name}&g{$p}from=B"
- => "Gets pages containing the {$what}s",
+ => "Gets pages containing the {$what}s",
);
}
public function getHelpUrls() {
$name = ucfirst( $this->getModuleName() );
+
return "https://www.mediawiki.org/wiki/API:{$name}";
}
}
diff --git a/includes/api/ApiQueryAllMessages.php b/includes/api/ApiQueryAllMessages.php
index d47c7b76..a75a16fc 100644
--- a/includes/api/ApiQueryAllMessages.php
+++ b/includes/api/ApiQueryAllMessages.php
@@ -31,7 +31,7 @@
*/
class ApiQueryAllMessages extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'am' );
}
@@ -63,14 +63,13 @@ class ApiQueryAllMessages extends ApiQueryBase {
if ( in_array( '*', $params['messages'] ) ) {
$message_names = Language::getMessageKeysFor( $langObj->getCode() );
if ( $params['includelocal'] ) {
- global $wgLanguageCode;
$message_names = array_unique( array_merge(
$message_names,
// Pass in the content language code so we get local messages that have a
// MediaWiki:msgkey page. We might theoretically miss messages that have no
// MediaWiki:msgkey page but do have a MediaWiki:msgkey/lang page, but that's
// just a stupid case.
- MessageCache::singleton()->getAllMessageKeys( $wgLanguageCode )
+ MessageCache::singleton()->getAllMessageKeys( $this->getConfig()->get( 'LanguageCode' ) )
) );
}
sort( $message_names );
@@ -116,7 +115,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
global $wgContLang;
$lang = $langObj->getCode();
- $customisedMessages = AllmessagesTablePager::getCustomisedStatuses(
+ $customisedMessages = AllMessagesTablePager::getCustomisedStatuses(
array_map( array( $langObj, 'ucfirst' ), $messages_target ), $lang, $lang != $wgContLang->getCode() );
$customised = $params['customised'] === 'modified';
@@ -241,10 +240,10 @@ class ApiQueryAllMessages extends ApiQueryBase {
'messages' => 'Which messages to output. "*" (default) means all messages',
'prop' => 'Which properties to get',
'enableparser' => array( 'Set to enable parser, will preprocess the wikitext of message',
- 'Will substitute magic words, handle templates etc.' ),
+ 'Will substitute magic words, handle templates etc.' ),
'nocontent' => 'If set, do not include the content of the messages in the output.',
'includelocal' => array( "Also include local messages, i.e. messages that don't exist in the software but do exist as a MediaWiki: page.",
- "This lists all MediaWiki: pages, so it will also list those that aren't 'really' messages such as Common.js",
+ "This lists all MediaWiki: pages, so it will also list those that aren't 'really' messages such as Common.js",
),
'title' => 'Page name to use as context when parsing message (for enableparser option)',
'args' => 'Arguments to be substituted into message',
@@ -257,35 +256,8 @@ class ApiQueryAllMessages extends ApiQueryBase {
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'invalidlang', 'info' => 'Invalid language code for parameter lang' ),
- ) );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'name' => 'string',
- 'customised' => 'boolean',
- 'missing' => 'boolean',
- '*' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'default' => array(
- 'defaultmissing' => 'boolean',
- 'default' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
- return 'Return messages from this site';
+ return 'Return messages from this site.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryAllPages.php b/includes/api/ApiQueryAllPages.php
index d95980c2..b7bd65a5 100644
--- a/includes/api/ApiQueryAllPages.php
+++ b/includes/api/ApiQueryAllPages.php
@@ -31,7 +31,7 @@
*/
class ApiQueryAllPages extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ap' );
}
@@ -44,19 +44,23 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function executeGenerator( $resultPageSet ) {
if ( $resultPageSet->isResolvingRedirects() ) {
- $this->dieUsage( 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params' );
+ $this->dieUsage(
+ 'Use "gapfilterredir=nonredirects" option instead of "redirects" ' .
+ 'when using allpages as a generator',
+ 'params'
+ );
}
$this->run( $resultPageSet );
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -83,12 +87,18 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
$this->addWhereFld( 'page_namespace', $params['namespace'] );
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $params['from'] === null
+ ? null
+ : $this->titlePartToKey( $params['from'], $params['namespace'] ) );
+ $to = ( $params['to'] === null
+ ? null
+ : $this->titlePartToKey( $params['to'], $params['namespace'] ) );
$this->addWhereRange( 'page_title', $dir, $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'page_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'page_title' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], $params['namespace'] ),
+ $db->anyString() ) );
}
if ( is_null( $resultPageSet ) ) {
@@ -145,7 +155,6 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
}
$this->addOption( 'DISTINCT' );
-
} elseif ( isset( $params['prlevel'] ) ) {
$this->dieUsage( 'prlevel may not be used without prtype', 'params' );
}
@@ -186,8 +195,9 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue', $row->page_title );
break;
}
@@ -215,8 +225,6 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- global $wgRestrictionLevels;
-
return array(
'from' => null,
'continue' => null,
@@ -245,7 +253,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
ApiBase::PARAM_ISMULTI => true
),
'prlevel' => array(
- ApiBase::PARAM_TYPE => $wgRestrictionLevels,
+ ApiBase::PARAM_TYPE => $this->getConfig()->get( 'RestrictionLevels' ),
ApiBase::PARAM_ISMULTI => true
),
'prfiltercascade' => array(
@@ -291,6 +299,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'from' => 'The page title to start enumerating from',
'continue' => 'When more results are available, use this to continue',
@@ -303,7 +312,8 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'maxsize' => 'Limit to pages with at most this many bytes',
'prtype' => 'Limit to protected pages only',
'prlevel' => "The protection level (must be used with {$p}prtype= parameter)",
- 'prfiltercascade' => "Filter protections based on cascadingness (ignored when {$p}prtype isn't set)",
+ 'prfiltercascade'
+ => "Filter protections based on cascadingness (ignored when {$p}prtype isn't set)",
'filterlanglinks' => array(
'Filter based on whether a page has langlinks',
'Note that this may not consider langlinks added by extensions.',
@@ -318,25 +328,8 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'pageid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Enumerate all pages sequentially in a given namespace';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator' ),
- array( 'code' => 'params', 'info' => 'prlevel may not be used without prtype' ),
- ) );
+ return 'Enumerate all pages sequentially in a given namespace.';
}
public function getExamples() {
@@ -349,9 +342,9 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'Using as Generator',
'Show info about 4 pages starting at the letter "T"',
),
- 'api.php?action=query&generator=allpages&gaplimit=2&gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content' => array(
- 'Show content of first 2 non-redirect pages beginning at "Re"',
- )
+ 'api.php?action=query&generator=allpages&gaplimit=2&' .
+ 'gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content'
+ => array( 'Show content of first 2 non-redirect pages beginning at "Re"' )
);
}
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 1948a51a..affddda7 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -30,7 +30,7 @@
* @ingroup API
*/
class ApiQueryAllUsers extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'au' );
}
@@ -38,15 +38,22 @@ class ApiQueryAllUsers extends ApiQueryBase {
* This function converts the user name to a canonical form
* which is stored in the database.
* @param string $name
- * @return String
+ * @return string
*/
private function getCanonicalUserName( $name ) {
return str_replace( '_', ' ', $name );
}
public function execute() {
- $db = $this->getDB();
$params = $this->extractRequestParams();
+ $activeUserDays = $this->getConfig()->get( 'ActiveUserDays' );
+
+ if ( $params['activeusers'] ) {
+ // Update active user cache
+ SpecialActiveUsers::mergeActiveUsers( 600, $activeUserDays );
+ }
+
+ $db = $this->getDB();
$prop = $params['prop'];
if ( !is_null( $prop ) ) {
@@ -58,7 +65,8 @@ class ApiQueryAllUsers extends ApiQueryBase {
$fld_registration = isset( $prop['registration'] );
$fld_implicitgroups = isset( $prop['implicitgroups'] );
} else {
- $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration = $fld_rights = $fld_implicitgroups = false;
+ $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration =
+ $fld_rights = $fld_implicitgroups = false;
}
$limit = $params['limit'];
@@ -70,9 +78,9 @@ class ApiQueryAllUsers extends ApiQueryBase {
$from = is_null( $params['from'] ) ? null : $this->getCanonicalUserName( $params['from'] );
$to = is_null( $params['to'] ) ? null : $this->getCanonicalUserName( $params['to'] );
- # MySQL doesn't seem to use 'equality propagation' here, so like the
- # ActiveUsers special page, we have to use rc_user_text for some cases.
- $userFieldToSort = $params['activeusers'] ? 'rc_user_text' : 'user_name';
+ # MySQL can't figure out that 'user_name' and 'qcc_title' are the same
+ # despite the JOIN condition, so manually sort on the correct one.
+ $userFieldToSort = $params['activeusers'] ? 'qcc_title' : 'user_name';
$this->addWhereRange( $userFieldToSort, $dir, $from, $to );
@@ -90,6 +98,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
// no group with the given right(s) exists, no need for a query
if ( !count( $groups ) ) {
$this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), '' );
+
return;
}
@@ -111,7 +120,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
// Filter only users that belong to a given group
$this->addTables( 'user_groups', 'ug1' );
$this->addJoinConds( array( 'ug1' => array( 'INNER JOIN', array( 'ug1.ug_user=user_id',
- 'ug1.ug_group' => $params['group'] ) ) ) );
+ 'ug1.ug_group' => $params['group'] ) ) ) );
}
if ( !is_null( $params['excludegroup'] ) && count( $params['excludegroup'] ) ) {
@@ -122,12 +131,14 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( count( $params['excludegroup'] ) == 1 ) {
$exclude = array( 'ug1.ug_group' => $params['excludegroup'][0] );
} else {
- $exclude = array( $db->makeList( array( 'ug1.ug_group' => $params['excludegroup'] ), LIST_OR ) );
+ $exclude = array( $db->makeList(
+ array( 'ug1.ug_group' => $params['excludegroup'] ),
+ LIST_OR
+ ) );
}
$this->addJoinConds( array( 'ug1' => array( 'LEFT OUTER JOIN',
array_merge( array( 'ug1.ug_user=user_id' ), $exclude )
- )
- ) );
+ ) ) );
$this->addWhere( 'ug1.ug_user IS NULL' );
}
@@ -145,26 +156,38 @@ class ApiQueryAllUsers extends ApiQueryBase {
$this->addTables( 'user_groups', 'ug2' );
$this->addJoinConds( array( 'ug2' => array( 'LEFT JOIN', 'ug2.ug_user=user_id' ) ) );
- $this->addFields( 'ug2.ug_group ug_group2' );
+ $this->addFields( array( 'ug_group2' => 'ug2.ug_group' ) );
} else {
$sqlLimit = $limit + 1;
}
if ( $params['activeusers'] ) {
- global $wgActiveUserDays;
- $this->addTables( 'recentchanges' );
-
- $this->addJoinConds( array( 'recentchanges' => array(
- 'INNER JOIN', 'rc_user_text=user_name'
+ $activeUserSeconds = $activeUserDays * 86400;
+
+ // Filter query to only include users in the active users cache
+ $this->addTables( 'querycachetwo' );
+ $this->addJoinConds( array( 'querycachetwo' => array(
+ 'INNER JOIN', array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_namespace' => NS_USER,
+ 'qcc_title=user_name',
+ ),
) ) );
- $this->addFields( array( 'recentedits' => 'COUNT(*)' ) );
-
- $this->addWhere( 'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ) );
- $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays * 24 * 3600 );
- $this->addWhere( 'rc_timestamp >= ' . $db->addQuotes( $timestamp ) );
-
- $this->addOption( 'GROUP BY', $userFieldToSort );
+ // Actually count the actions using a subquery (bug 64505 and bug 64507)
+ $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
+ $this->addFields( array(
+ 'recentactions' => '(' . $db->selectSQLText(
+ 'recentchanges',
+ 'COUNT(*)',
+ array(
+ 'rc_user_text = user_name',
+ 'rc_type != ' . $db->addQuotes( RC_EXTERNAL ), // no wikidata
+ 'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ),
+ 'rc_timestamp >= ' . $db->addQuotes( $timestamp ),
+ )
+ ) . ')'
+ ) );
}
$this->addOption( 'LIMIT', $sqlLimit );
@@ -187,12 +210,12 @@ class ApiQueryAllUsers extends ApiQueryBase {
$lastUser = false;
$result = $this->getResult();
- //
- // This loop keeps track of the last entry.
- // For each new row, if the new row is for different user then the last, the last entry is added to results.
- // Otherwise, the group of the new row is appended to the last entry.
- // The setContinue... is more complex because of this, and takes into account the higher sql limit
- // to make sure all rows that belong to the same user are received.
+ // This loop keeps track of the last entry. For each new row, if the
+ // new row is for different user then the last, the last entry is added
+ // to results. Otherwise, the group of the new row is appended to the
+ // last entry. The setContinue... is more complex because of this, and
+ // takes into account the higher sql limit to make sure all rows that
+ // belong to the same user are received.
foreach ( $res as $row ) {
$count++;
@@ -200,8 +223,13 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( $lastUser !== $row->user_name ) {
// Save the last pass's user data
if ( is_array( $lastUserData ) ) {
- $fit = $result->addValue( array( 'query', $this->getModuleName() ),
+ if ( $params['activeusers'] && $lastUserData['recentactions'] === 0 ) {
+ // activeusers cache was out of date
+ $fit = true;
+ } else {
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $lastUserData );
+ }
$lastUserData = null;
@@ -212,7 +240,8 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
if ( $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'from', $row->user_name );
break;
}
@@ -227,6 +256,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
$lastUserData['blockid'] = $row->ipb_id;
$lastUserData['blockedby'] = $row->ipb_by_text;
$lastUserData['blockedbyid'] = $row->ipb_by;
+ $lastUserData['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
$lastUserData['blockreason'] = $row->ipb_reason;
$lastUserData['blockexpiry'] = $row->ipb_expiry;
}
@@ -237,7 +267,9 @@ class ApiQueryAllUsers extends ApiQueryBase {
$lastUserData['editcount'] = intval( $row->user_editcount );
}
if ( $params['activeusers'] ) {
- $lastUserData['recenteditcount'] = intval( $row->recentedits );
+ $lastUserData['recentactions'] = intval( $row->recentactions );
+ // @todo 'recenteditcount' is set for BC, remove in 1.25
+ $lastUserData['recenteditcount'] = $lastUserData['recentactions'];
}
if ( $fld_registration ) {
$lastUserData['registration'] = $row->user_registration ?
@@ -246,10 +278,13 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
if ( $sqlLimit == $count ) {
- // BUG! database contains group name that User::getAllGroups() does not return
- // TODO: should handle this more gracefully
- ApiBase::dieDebug( __METHOD__,
- 'MediaWiki configuration error: the database contains more user groups than known to User::getAllGroups() function' );
+ // @todo BUG! database contains group name that User::getAllGroups() does not return
+ // Should handle this more gracefully
+ ApiBase::dieDebug(
+ __METHOD__,
+ 'MediaWiki configuration error: The database contains more ' .
+ 'user groups than known to User::getAllGroups() function'
+ );
}
$lastUserObj = User::newFromId( $row->user_id );
@@ -295,7 +330,9 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
}
- if ( is_array( $lastUserData ) ) {
+ if ( is_array( $lastUserData ) &&
+ !( $params['activeusers'] && $lastUserData['recentactions'] === 0 )
+ ) {
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $lastUserData );
if ( !$fit ) {
@@ -312,6 +349,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
public function getAllowedParams() {
$userGroups = User::getAllGroups();
+
return array(
'from' => null,
'to' => null,
@@ -359,7 +397,6 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
public function getParamDescription() {
- global $wgActiveUserDays;
return array(
'from' => 'The user name to start enumerating from',
'to' => 'The user name to stop enumerating at',
@@ -367,72 +404,26 @@ class ApiQueryAllUsers extends ApiQueryBase {
'dir' => 'Direction to sort in',
'group' => 'Limit users to given group name(s)',
'excludegroup' => 'Exclude users in given group name(s)',
- 'rights' => 'Limit users to given right(s) (does not include rights granted by implicit or auto-promoted groups like *, user, or autoconfirmed)',
+ 'rights' => 'Limit users to given right(s) (does not include rights ' .
+ 'granted by implicit or auto-promoted groups like *, user, or autoconfirmed)',
'prop' => array(
'What pieces of information to include.',
' blockinfo - Adds the information about a current block on the user',
- ' groups - Lists groups that the user is in. This uses more server resources and may return fewer results than the limit',
+ ' groups - Lists groups that the user is in. This uses ' .
+ 'more server resources and may return fewer results than the limit',
' implicitgroups - Lists all the groups the user is automatically in',
' rights - Lists rights that the user has',
' editcount - Adds the edit count of the user',
' registration - Adds the timestamp of when the user registered if available (may be blank)',
- ),
+ ),
'limit' => 'How many total user names to return',
'witheditsonly' => 'Only list users who have made edits',
- 'activeusers' => "Only list users active in the last {$wgActiveUserDays} days(s)"
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'userid' => 'integer',
- 'name' => 'string',
- 'recenteditcount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'blockinfo' => array(
- 'blockid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedby' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedbyid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedreason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedexpiry' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'hidden' => 'boolean'
- ),
- 'editcount' => array(
- 'editcount' => 'integer'
- ),
- 'registration' => array(
- 'registration' => 'string'
- )
+ 'activeusers' => "Only list users active in the last {$this->getConfig()->get( 'ActiveUserDays' )} days(s)"
);
}
public function getDescription() {
- return 'Enumerate all registered users';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'group-excludegroup', 'info' => 'group and excludegroup cannot be used together' ),
- ) );
+ return 'Enumerate all registered users.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index 2d1089a7..c141246d 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -75,7 +75,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
)
);
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
$settings = $this->backlinksSettings[$moduleName];
$prefix = $settings['prefix'];
$code = $settings['code'];
@@ -116,7 +116,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function prepareFirstQuery( $resultPageSet = null ) {
@@ -149,7 +149,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
if ( $this->params['filterredir'] == 'redirects' ) {
$this->addWhereFld( 'page_is_redirect', 1 );
} elseif ( $this->params['filterredir'] == 'nonredirects' && !$this->redirect ) {
- // bug 22245 - Check for !redirect, as filtering nonredirects, when getting what links to them is contradictory
+ // bug 22245 - Check for !redirect, as filtering nonredirects, when
+ // getting what links to them is contradictory
$this->addWhereFld( 'page_is_redirect', 0 );
}
@@ -160,7 +161,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function prepareSecondQuery( $resultPageSet = null ) {
@@ -193,7 +194,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$redirNs = $t->getNamespace();
$redirDBkey = $t->getDBkey();
$titleWhere[] = "{$this->bl_title} = " . $db->addQuotes( $redirDBkey ) .
- ( $this->hasNS ? " AND {$this->bl_ns} = {$redirNs}" : '' );
+ ( $this->hasNS ? " AND {$this->bl_ns} = {$redirNs}" : '' );
$allRedirNs[] = $redirNs;
$allRedirDBkey[] = $redirDBkey;
}
@@ -209,14 +210,14 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$from = $this->redirID;
if ( $this->hasNS ) {
$this->addWhere( "{$this->bl_ns} $op $ns OR " .
- "({$this->bl_ns} = $ns AND " .
- "({$this->bl_title} $op $title OR " .
- "({$this->bl_title} = $title AND " .
- "{$this->bl_from} $op= $from)))" );
+ "({$this->bl_ns} = $ns AND " .
+ "({$this->bl_title} $op $title OR " .
+ "({$this->bl_title} = $title AND " .
+ "{$this->bl_from} $op= $from)))" );
} else {
$this->addWhere( "{$this->bl_title} $op $title OR " .
- "({$this->bl_title} = $title AND " .
- "{$this->bl_from} $op= $from)" );
+ "({$this->bl_title} = $title AND " .
+ "{$this->bl_from} $op= $from)" );
}
}
if ( $this->params['filterredir'] == 'redirects' ) {
@@ -241,7 +242,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -268,8 +269,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$count = 0;
foreach ( $res as $row ) {
- if ( ++ $count > $this->params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $this->params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
// Continue string preserved in case the redirect query doesn't pass the limit
$this->continueStr = $this->getContinueStr( $row->page_id );
break;
@@ -294,7 +296,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$count = 0;
foreach ( $res as $row ) {
if ( ++$count > $this->params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
// We need to keep the parent page of this redir in
if ( $this->hasNS ) {
$parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}];
@@ -384,7 +387,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$parentID = $this->pageMap[$ns][$row->{$this->bl_title}];
// Put all the results in an array first
$this->resultArr[$parentID]['redirlinks'][] = $a;
- $this->getResult()->setIndexedTagName( $this->resultArr[$parentID]['redirlinks'], $this->bl_code );
+ $this->getResult()->setIndexedTagName(
+ $this->resultArr[$parentID]['redirlinks'],
+ $this->bl_code
+ );
}
protected function processContinue() {
@@ -396,7 +402,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
// only image titles are allowed for the root in imageinfo mode
if ( !$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE ) {
- $this->dieUsage( "The title for {$this->getModuleName()} query must be an image", 'bad_image_title' );
+ $this->dieUsage(
+ "The title for {$this->getModuleName()} query must be an image",
+ 'bad_image_title'
+ );
}
}
@@ -428,7 +437,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
return;
}
$this->redirID = $redirID;
-
}
protected function getContinueStr( $lastPageID ) {
@@ -481,6 +489,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
return $retval;
}
$retval['redirect'] = false;
+
return $retval;
}
@@ -494,50 +503,36 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
);
if ( $this->getModuleName() != 'embeddedin' ) {
return array_merge( $retval, array(
- 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.',
- 'filterredir' => "How to filter for redirects. If set to nonredirects when {$this->bl_code}redirect is enabled, this is only applied to the second level",
- 'limit' => "How many total pages to return. If {$this->bl_code}redirect is enabled, limit applies to each level separately (which means you may get up to 2 * limit results)."
+ 'redirect' => 'If linking page is a redirect, find all pages ' .
+ 'that link to that redirect as well. Maximum limit is halved.',
+ 'filterredir' => 'How to filter for redirects. If set to ' .
+ "nonredirects when {$this->bl_code}redirect is enabled, " .
+ 'this is only applied to the second level',
+ 'limit' => 'How many total pages to return. If ' .
+ "{$this->bl_code}redirect is enabled, limit applies to each " .
+ 'level separately (which means you may get up to 2 * limit results).'
) );
}
+
return array_merge( $retval, array(
'filterredir' => 'How to filter for redirects',
'limit' => 'How many total pages to return'
) );
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'pageid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string',
- 'redirect' => 'boolean'
- )
- );
- }
-
public function getDescription() {
switch ( $this->getModuleName() ) {
case 'backlinks':
- return 'Find all pages that link to the given page';
+ return 'Find all pages that link to the given page.';
case 'embeddedin':
- return 'Find all pages that embed (transclude) the given title';
+ return 'Find all pages that embed (transclude) the given title.';
case 'imageusage':
return 'Find all pages that use the given image title.';
default:
- ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
+ ApiBase::dieDebug( __METHOD__, 'Unknown module name.' );
}
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'code' => 'bad_image_title', 'info' => "The title for {$this->getModuleName()} query must be an image" ),
- )
- );
- }
-
public function getExamples() {
static $examples = array(
'backlinks' => array(
diff --git a/includes/api/ApiQueryBacklinksprop.php b/includes/api/ApiQueryBacklinksprop.php
new file mode 100644
index 00000000..cd682612
--- /dev/null
+++ b/includes/api/ApiQueryBacklinksprop.php
@@ -0,0 +1,472 @@
+<?php
+/**
+ * API module to handle links table back-queries
+ *
+ * Created on Aug 19, 2014
+ *
+ * Copyright © 2014 Brad Jorsch <bjorsch@wikimedia.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
+ * @since 1.24
+ */
+
+/**
+ * This implements prop=redirects, prop=linkshere, prop=catmembers,
+ * prop=transcludedin, and prop=fileusage
+ *
+ * @ingroup API
+ * @since 1.24
+ */
+class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
+
+ // Data for the various modules implemented by this class
+ private static $settings = array(
+ 'redirects' => array(
+ 'code' => 'rd',
+ 'prefix' => 'rd',
+ 'linktable' => 'redirect',
+ 'what' => 'redirects to',
+ 'description' => 'Returns all redirects to the given pages.',
+ 'props' => array(
+ 'fragment' => 'Fragment of each redirect, if any',
+ ),
+ 'showredirects' => false,
+ 'show' => array(
+ 'fragment' => 'Only show redirects with a fragment',
+ '!fragment' => 'Only show redirects without a fragment',
+ ),
+ ),
+ 'linkshere' => array(
+ 'code' => 'lh',
+ 'prefix' => 'pl',
+ 'linktable' => 'pagelinks',
+ 'from_namespace' => true,
+ 'what' => 'pages linking to',
+ 'description' => 'Find all pages that link to the given pages.',
+ 'showredirects' => true,
+ ),
+ 'transcludedin' => array(
+ 'code' => 'ti',
+ 'prefix' => 'tl',
+ 'linktable' => 'templatelinks',
+ 'from_namespace' => true,
+ 'what' => 'pages transcluding',
+ 'description' => 'Find all pages that transclude the given pages.',
+ 'showredirects' => true,
+ ),
+ 'fileusage' => array(
+ 'code' => 'fu',
+ 'prefix' => 'il',
+ 'linktable' => 'imagelinks',
+ 'from_namespace' => true,
+ 'to_namespace' => NS_FILE,
+ 'what' => 'pages using',
+ 'exampletitle' => 'File:Example.jpg',
+ 'description' => 'Find all pages that use the given files.',
+ 'showredirects' => true,
+ ),
+ );
+
+ public function __construct( ApiQuery $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, self::$settings[$moduleName]['code'] );
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param ApiPageSet $resultPageSet
+ */
+ private function run( ApiPageSet $resultPageSet = null ) {
+ $settings = self::$settings[$this->getModuleName()];
+
+ $db = $this->getDB();
+ $params = $this->extractRequestParams();
+ $prop = array_flip( $params['prop'] );
+ $emptyString = $db->addQuotes( '' );
+
+ $pageSet = $this->getPageSet();
+ $titles = $pageSet->getGoodTitles() + $pageSet->getMissingTitles();
+ $map = $pageSet->getAllTitlesByNamespace();
+
+ // Determine our fields to query on
+ $p = $settings['prefix'];
+ $hasNS = !isset( $settings['to_namespace'] );
+ if ( $hasNS ) {
+ $bl_namespace = "{$p}_namespace";
+ $bl_title = "{$p}_title";
+ } else {
+ $bl_namespace = $settings['to_namespace'];
+ $bl_title = "{$p}_to";
+
+ $titles = array_filter( $titles, function ( $t ) use ( $bl_namespace ) {
+ return $t->getNamespace() === $bl_namespace;
+ } );
+ $map = array_intersect_key( $map, array( $bl_namespace => true ) );
+ }
+ $bl_from = "{$p}_from";
+
+ if ( !$titles ) {
+ return; // nothing to do
+ }
+
+ // Figure out what we're sorting by, and add associated WHERE clauses.
+ // MySQL's query planner screws up if we include a field in ORDER BY
+ // when it's constant in WHERE, so we have to test that for each field.
+ $sortby = array();
+ if ( $hasNS && count( $map ) > 1 ) {
+ $sortby[$bl_namespace] = 'ns';
+ }
+ $theTitle = null;
+ foreach ( $map as $nsTitles ) {
+ reset( $nsTitles );
+ $key = key( $nsTitles );
+ if ( $theTitle === null ) {
+ $theTitle = $key;
+ }
+ if ( count( $nsTitles ) > 1 || $key !== $theTitle ) {
+ $sortby[$bl_title] = 'title';
+ break;
+ }
+ }
+ $miser_ns = null;
+ if ( $params['namespace'] !== null ) {
+ if ( empty( $settings['from_namespace'] ) && $this->getConfig()->get( 'MiserMode' ) ) {
+ $miser_ns = $params['namespace'];
+ } else {
+ $this->addWhereFld( "{$p}_from_namespace", $params['namespace'] );
+ if ( !empty( $settings['from_namespace'] ) && count( $params['namespace'] ) > 1 ) {
+ $sortby["{$p}_from_namespace"] = 'int';
+ }
+ }
+ }
+ $sortby[$bl_from] = 'int';
+
+ // Now use the $sortby to figure out the continuation
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != count( $sortby ) );
+ $where = '';
+ $i = count( $sortby ) - 1;
+ $cont_ns = 0;
+ $cont_title = '';
+ foreach ( array_reverse( $sortby, true ) as $field => $type ) {
+ $v = $cont[$i];
+ switch ( $type ) {
+ case 'ns':
+ $cont_ns = (int)$v;
+ /* fall through */
+ case 'int':
+ $v = (int)$v;
+ $this->dieContinueUsageIf( $v != $cont[$i] );
+ break;
+
+ case 'title':
+ $cont_title = $v;
+ /* fall through */
+ default:
+ $v = $db->addQuotes( $v );
+ break;
+ }
+
+ if ( $where === '' ) {
+ $where = "$field >= $v";
+ } else {
+ $where = "$field > $v OR ($field = $v AND ($where))";
+ }
+
+ $i--;
+ }
+ $this->addWhere( $where );
+ }
+
+ // Populate the rest of the query
+ $this->addTables( array( $settings['linktable'], 'page' ) );
+ $this->addWhere( "$bl_from = page_id" );
+
+ if ( $this->getModuleName() === 'redirects' ) {
+ $this->addWhere( "rd_interwiki = $emptyString OR rd_interwiki IS NULL" );
+ }
+
+ $this->addFields( array_keys( $sortby ) );
+ $this->addFields( array( 'bl_namespace' => $bl_namespace, 'bl_title' => $bl_title ) );
+ if ( is_null( $resultPageSet ) ) {
+ $fld_pageid = isset( $prop['pageid'] );
+ $fld_title = isset( $prop['title'] );
+ $fld_redirect = isset( $prop['redirect'] );
+
+ $this->addFieldsIf( 'page_id', $fld_pageid );
+ $this->addFieldsIf( array( 'page_title', 'page_namespace' ), $fld_title );
+ $this->addFieldsIf( 'page_is_redirect', $fld_redirect );
+
+ // prop=redirects
+ $fld_fragment = isset( $prop['fragment'] );
+ $this->addFieldsIf( 'rd_fragment', $fld_fragment );
+ } else {
+ $this->addFields( $resultPageSet->getPageTableFields() );
+ }
+
+ $this->addFieldsIf( 'page_namespace', $miser_ns !== null );
+
+ if ( $hasNS ) {
+ $lb = new LinkBatch( $titles );
+ $this->addWhere( $lb->constructSet( $p, $db ) );
+ } else {
+ $where = array();
+ foreach ( $titles as $t ) {
+ if ( $t->getNamespace() == $bl_namespace ) {
+ $where[] = "$bl_title = " . $db->addQuotes( $t->getDBkey() );
+ }
+ }
+ $this->addWhere( $db->makeList( $where, LIST_OR ) );
+ }
+
+ if ( $params['show'] !== null ) {
+ // prop=redirects only
+ $show = array_flip( $params['show'] );
+ if ( isset( $show['fragment'] ) && isset( $show['!fragment'] ) ||
+ isset( $show['redirect'] ) && isset( $show['!redirect'] )
+ ) {
+ $this->dieUsageMsg( 'show' );
+ }
+ $this->addWhereIf( "rd_fragment != $emptyString", isset( $show['fragment'] ) );
+ $this->addWhereIf(
+ "rd_fragment = $emptyString OR rd_fragment IS NULL",
+ isset( $show['!fragment'] )
+ );
+ $this->addWhereIf( array( 'page_is_redirect' => 1 ), isset( $show['redirect'] ) );
+ $this->addWhereIf( array( 'page_is_redirect' => 0 ), isset( $show['!redirect'] ) );
+ }
+
+ // Override any ORDER BY from above with what we calculated earlier.
+ $this->addOption( 'ORDER BY', array_keys( $sortby ) );
+
+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
+
+ $res = $this->select( __METHOD__ );
+
+ if ( is_null( $resultPageSet ) ) {
+ $count = 0;
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinue( $row, $sortby );
+ break;
+ }
+
+ if ( $miser_ns !== null && !in_array( $row->page_namespace, $miser_ns ) ) {
+ // Miser mode namespace check
+ continue;
+ }
+
+ // Get the ID of the current page
+ $id = $map[$row->bl_namespace][$row->bl_title];
+
+ $vals = array();
+ if ( $fld_pageid ) {
+ $vals['pageid'] = $row->page_id;
+ }
+ if ( $fld_title ) {
+ ApiQueryBase::addTitleInfo( $vals,
+ Title::makeTitle( $row->page_namespace, $row->page_title )
+ );
+ }
+ if ( $fld_fragment && $row->rd_fragment !== null && $row->rd_fragment !== '' ) {
+ $vals['fragment'] = $row->rd_fragment;
+ }
+ if ( $fld_redirect && $row->page_is_redirect ) {
+ $vals['redirect'] = '';
+ }
+ $fit = $this->addPageSubItem( $id, $vals );
+ if ( !$fit ) {
+ $this->setContinue( $row, $sortby );
+ break;
+ }
+ }
+ } else {
+ $titles = array();
+ $count = 0;
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinue( $row, $sortby );
+ break;
+ }
+ $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
+ }
+ $resultPageSet->populateFromTitles( $titles );
+ }
+ }
+
+ private function setContinue( $row, $sortby ) {
+ $cont = array();
+ foreach ( $sortby as $field => $v ) {
+ $cont[] = $row->$field;
+ }
+ $this->setContinueEnumParameter( 'continue', join( '|', $cont ) );
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ $settings = self::$settings[$this->getModuleName()];
+
+ $ret = array(
+ 'prop' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'pageid',
+ 'title',
+ ),
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'pageid|title',
+ ),
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
+ );
+
+ if ( !empty( $settings['showredirects'] ) ) {
+ $ret['prop'][ApiBase::PARAM_TYPE][] = 'redirect';
+ $ret['prop'][ApiBase::PARAM_DFLT] .= '|redirect';
+ }
+ if ( isset( $settings['props'] ) ) {
+ $ret['prop'][ApiBase::PARAM_TYPE] = array_merge(
+ $ret['prop'][ApiBase::PARAM_TYPE], array_keys( $settings['props'] )
+ );
+ }
+
+ $show = array();
+ if ( !empty( $settings['showredirects'] ) ) {
+ $show[] = 'redirect';
+ $show[] = '!redirect';
+ }
+ if ( isset( $settings['show'] ) ) {
+ $show = array_merge( $show, array_keys( $settings['show'] ) );
+ }
+ if ( $show ) {
+ $ret['show'] = array(
+ ApiBase::PARAM_TYPE => $show,
+ ApiBase::PARAM_ISMULTI => true,
+ );
+ }
+
+ return $ret;
+ }
+
+ public function getParamDescription() {
+ $settings = self::$settings[$this->getModuleName()];
+ $p = $this->getModulePrefix();
+
+ $ret = array(
+ 'prop' => array(
+ 'Which properties to get:',
+ ),
+ 'show' => array(
+ 'Show only items that meet this criteria.',
+ ),
+ 'namespace' => 'Only include pages in these namespaces',
+ 'limit' => 'How many to return',
+ 'continue' => 'When more results are available, use this to continue',
+ );
+
+ if ( empty( $settings['from_namespace'] ) && $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['namespace'] = array(
+ $ret['namespace'],
+ "NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
+ 'returned before continuing; in extreme cases, zero results may be returned.',
+ );
+ if ( isset( $ret['type'] ) ) {
+ $ret['namespace'][] = "Note that you can use {$p}type=subcat or {$p}type=file " .
+ "instead of {$p}namespace=14 or 6.";
+ }
+ }
+
+ $props = array(
+ 'pageid' => 'Adds the ID of page',
+ 'title' => 'Adds the title and namespace ID of the page',
+ );
+ if ( !empty( $settings['showredirects'] ) ) {
+ $props['redirect'] = 'Indicate if the page is a redirect';
+ }
+ if ( isset( $settings['props'] ) ) {
+ $props += $settings['props'];
+ }
+ foreach ( $props as $k => $v ) {
+ $ret['props'][] = sprintf( "%-9s - %s", $k, $v );
+ }
+
+ $show = array();
+ if ( !empty( $settings['showredirects'] ) ) {
+ $show += array(
+ 'redirect' => 'Only show redirects',
+ '!redirect' => 'Only show non-redirects',
+ );
+ }
+ if ( isset( $settings['show'] ) ) {
+ $show += $settings['show'];
+ }
+ foreach ( $show as $k => $v ) {
+ $ret['show'][] = sprintf( "%-9s - %s", $k, $v );
+ }
+
+ return $ret;
+ }
+
+ public function getDescription() {
+ return self::$settings[$this->getModuleName()]['description'];
+ }
+
+ public function getExamples() {
+ $settings = self::$settings[$this->getModuleName()];
+ $name = $this->getModuleName();
+ $what = $settings['what'];
+ $title = isset( $settings['exampletitle'] ) ? $settings['exampletitle'] : 'Main Page';
+ $etitle = rawurlencode( $title );
+
+ return array(
+ "api.php?action=query&prop={$name}&titles={$etitle}"
+ => "Get a list of $what [[$title]]",
+ "api.php?action=query&generator={$name}&titles={$etitle}&prop=info"
+ => "Get information about $what [[$title]]",
+ );
+ }
+
+ public function getHelpUrls() {
+ $name = $this->getModuleName();
+ $prefix = $this->getModulePrefix();
+ return "https://www.mediawiki.org/wiki/API:Properties#{$name}_.2F_{$prefix}";
+ }
+}
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 8668e04b..65e10ab7 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -36,17 +36,22 @@ abstract class ApiQueryBase extends ApiBase {
private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds;
/**
- * @param $query ApiBase
- * @param $moduleName string
- * @param $paramPrefix string
+ * @param ApiQuery $queryModule
+ * @param string $moduleName
+ * @param string $paramPrefix
*/
- public function __construct( ApiBase $query, $moduleName, $paramPrefix = '' ) {
- parent::__construct( $query->getMain(), $moduleName, $paramPrefix );
- $this->mQueryModule = $query;
+ public function __construct( ApiQuery $queryModule, $moduleName, $paramPrefix = '' ) {
+ parent::__construct( $queryModule->getMain(), $moduleName, $paramPrefix );
+ $this->mQueryModule = $queryModule;
$this->mDb = null;
$this->resetQueryParams();
}
+ /************************************************************************//**
+ * @name Methods to implement
+ * @{
+ */
+
/**
* Get the cache mode for the data generated by this module. Override
* this in the module subclass. For possible return values and other
@@ -55,7 +60,7 @@ abstract class ApiQueryBase extends ApiBase {
* Public caching will only be allowed if *all* the modules that supply
* data for a given request return a cache mode of public.
*
- * @param $params
+ * @param array $params
* @return string
*/
public function getCacheMode( $params ) {
@@ -63,6 +68,68 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
+ * Override this method to request extra fields from the pageSet
+ * using $pageSet->requestField('fieldName')
+ * @param ApiPageSet $pageSet
+ */
+ public function requestExtraData( $pageSet ) {
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Data access
+ * @{
+ */
+
+ /**
+ * Get the main Query module
+ * @return ApiQuery
+ */
+ public function getQuery() {
+ return $this->mQueryModule;
+ }
+
+ /**
+ * Get the Query database connection (read-only)
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ if ( is_null( $this->mDb ) ) {
+ $this->mDb = $this->getQuery()->getDB();
+ }
+
+ return $this->mDb;
+ }
+
+ /**
+ * Selects the query database connection with the given name.
+ * See ApiQuery::getNamedDB() for more information
+ * @param string $name Name to assign to the database connection
+ * @param int $db One of the DB_* constants
+ * @param array $groups Query groups
+ * @return DatabaseBase
+ */
+ public function selectNamedDB( $name, $db, $groups ) {
+ $this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups );
+ }
+
+ /**
+ * Get the PageSet object to work on
+ * @return ApiPageSet
+ */
+ protected function getPageSet() {
+ return $this->getQuery()->getPageSet();
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Querying
+ * @{
+ */
+
+ /**
* Blank the internal arrays with query parameters
*/
protected function resetQueryParams() {
@@ -75,8 +142,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a set of tables to the internal array
- * @param $tables mixed Table name or array of table names
- * @param $alias mixed Table alias, or null for no alias. Cannot be
+ * @param string|string[] $tables Table name or array of table names
+ * @param string|null $alias Table alias, or null for no alias. Cannot be
* used with multiple tables
*/
protected function addTables( $tables, $alias = null ) {
@@ -101,7 +168,7 @@ abstract class ApiQueryBase extends ApiBase {
* conditions) e.g. array('page' => array('LEFT JOIN',
* 'page_id=rev_page')) . conditions may be a string or an
* addWhere()-style array
- * @param $join_conds array JOIN conditions
+ * @param array $join_conds JOIN conditions
*/
protected function addJoinConds( $join_conds ) {
if ( !is_array( $join_conds ) ) {
@@ -131,8 +198,10 @@ abstract class ApiQueryBase extends ApiBase {
protected function addFieldsIf( $value, $condition ) {
if ( $condition ) {
$this->addFields( $value );
+
return true;
}
+
return false;
}
@@ -145,7 +214,7 @@ abstract class ApiQueryBase extends ApiBase {
*
* For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates
* to "foo=bar AND baz='3' AND bla='foo'"
- * @param $value mixed String or array
+ * @param string|array $value
*/
protected function addWhere( $value ) {
if ( is_array( $value ) ) {
@@ -161,15 +230,17 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addWhere(), but add the WHERE clauses only if a condition is met
- * @param $value mixed See addWhere()
+ * @param string|array $value
* @param bool $condition If false, do nothing
* @return bool $condition
*/
protected function addWhereIf( $value, $condition ) {
if ( $condition ) {
$this->addWhere( $value );
+
return true;
}
+
return false;
}
@@ -215,7 +286,9 @@ abstract class ApiQueryBase extends ApiBase {
if ( $sort ) {
$order = $field . ( $isDirNewer ? '' : ' DESC' );
// Append ORDER BY
- $optionOrderBy = isset( $this->options['ORDER BY'] ) ? (array)$this->options['ORDER BY'] : array();
+ $optionOrderBy = isset( $this->options['ORDER BY'] )
+ ? (array)$this->options['ORDER BY']
+ : array();
$optionOrderBy[] = $order;
$this->addOption( 'ORDER BY', $optionOrderBy );
}
@@ -225,11 +298,11 @@ abstract class ApiQueryBase extends ApiBase {
* Add a WHERE clause corresponding to a range, similar to addWhereRange,
* but converts $start and $end to database timestamps.
* @see addWhereRange
- * @param $field
- * @param $dir
- * @param $start
- * @param $end
- * @param $sort bool
+ * @param string $field
+ * @param string $dir
+ * @param string $start
+ * @param string $end
+ * @param bool $sort
*/
protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) {
$db = $this->getDb();
@@ -256,16 +329,37 @@ abstract class ApiQueryBase extends ApiBase {
* @param string $method Function the query should be attributed to.
* You should usually use __METHOD__ here
* @param array $extraQuery Query data to add but not store in the object
- * Format is array( 'tables' => ..., 'fields' => ..., 'where' => ..., 'options' => ..., 'join_conds' => ... )
+ * Format is array(
+ * 'tables' => ...,
+ * 'fields' => ...,
+ * 'where' => ...,
+ * 'options' => ...,
+ * 'join_conds' => ...
+ * )
* @return ResultWrapper
*/
protected function select( $method, $extraQuery = array() ) {
- $tables = array_merge( $this->tables, isset( $extraQuery['tables'] ) ? (array)$extraQuery['tables'] : array() );
- $fields = array_merge( $this->fields, isset( $extraQuery['fields'] ) ? (array)$extraQuery['fields'] : array() );
- $where = array_merge( $this->where, isset( $extraQuery['where'] ) ? (array)$extraQuery['where'] : array() );
- $options = array_merge( $this->options, isset( $extraQuery['options'] ) ? (array)$extraQuery['options'] : array() );
- $join_conds = array_merge( $this->join_conds, isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : array() );
+ $tables = array_merge(
+ $this->tables,
+ isset( $extraQuery['tables'] ) ? (array)$extraQuery['tables'] : array()
+ );
+ $fields = array_merge(
+ $this->fields,
+ isset( $extraQuery['fields'] ) ? (array)$extraQuery['fields'] : array()
+ );
+ $where = array_merge(
+ $this->where,
+ isset( $extraQuery['where'] ) ? (array)$extraQuery['where'] : array()
+ );
+ $options = array_merge(
+ $this->options,
+ isset( $extraQuery['options'] ) ? (array)$extraQuery['options'] : array()
+ );
+ $join_conds = array_merge(
+ $this->join_conds,
+ isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : array()
+ );
// getDB has its own profileDBIn/Out calls
$db = $this->getDB();
@@ -278,28 +372,69 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
- * Estimate the row count for the SELECT query that would be run if we
- * called select() right now, and check if it's acceptable.
- * @return bool true if acceptable, false otherwise
+ * @param string $query
+ * @param string $protocol
+ * @return null|string
*/
- protected function checkRowCount() {
- $db = $this->getDB();
- $this->profileDBIn();
- $rowcount = $db->estimateRowCount( $this->tables, $this->fields, $this->where, __METHOD__, $this->options );
- $this->profileDBOut();
+ public function prepareUrlQuerySearchString( $query = null, $protocol = null ) {
+ $db = $this->getDb();
+ if ( !is_null( $query ) || $query != '' ) {
+ if ( is_null( $protocol ) ) {
+ $protocol = 'http://';
+ }
- global $wgAPIMaxDBRows;
- if ( $rowcount > $wgAPIMaxDBRows ) {
- return false;
+ $likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
+ if ( !$likeQuery ) {
+ $this->dieUsage( 'Invalid query', 'bad_query' );
+ }
+
+ $likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
+
+ return 'el_index ' . $db->buildLike( $likeQuery );
+ } elseif ( !is_null( $protocol ) ) {
+ return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() );
+ }
+
+ return null;
+ }
+
+ /**
+ * Filters hidden users (where the user doesn't have the right to view them)
+ * Also adds relevant block information
+ *
+ * @param bool $showBlockInfo
+ * @return void
+ */
+ public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
+ $this->addTables( 'ipblocks' );
+ $this->addJoinConds( array(
+ 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ),
+ ) );
+
+ $this->addFields( 'ipb_deleted' );
+
+ if ( $showBlockInfo ) {
+ $this->addFields( array( 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_reason', 'ipb_expiry', 'ipb_timestamp' ) );
+ }
+
+ // Don't show hidden names
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
+ $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
}
- return true;
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Utility methods
+ * @{
+ */
+
/**
* Add information (title and namespace) about a Title object to a
* result array
* @param array $arr Result array à la ApiResult
- * @param $title Title
+ * @param Title $title
* @param string $prefix Module prefix
*/
public static function addTitleInfo( &$arr, $title, $prefix = '' ) {
@@ -308,22 +443,6 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
- * Override this method to request extra fields from the pageSet
- * using $pageSet->requestField('fieldName')
- * @param $pageSet ApiPageSet
- */
- public function requestExtraData( $pageSet ) {
- }
-
- /**
- * Get the main Query module
- * @return ApiQuery
- */
- public function getQuery() {
- return $this->mQueryModule;
- }
-
- /**
* Add a sub-element under the page element with the given page ID
* @param int $pageId Page ID
* @param array $data Data array à la ApiResult
@@ -332,6 +451,7 @@ abstract class ApiQueryBase extends ApiBase {
protected function addPageSubItems( $pageId, $data ) {
$result = $this->getResult();
$result->setIndexedTagName( $data, $this->getModulePrefix() );
+
return $result->addValue( array( 'query', 'pages', intval( $pageId ) ),
$this->getModuleName(),
$data );
@@ -356,61 +476,134 @@ abstract class ApiQueryBase extends ApiBase {
return false;
}
$result->setIndexedTagName_internal( array( 'query', 'pages', $pageId,
- $this->getModuleName() ), $elemname );
+ $this->getModuleName() ), $elemname );
+
return true;
}
/**
* Set a query-continue value
* @param string $paramName Parameter name
- * @param string $paramValue Parameter value
+ * @param string|array $paramValue Parameter value
*/
protected function setContinueEnumParameter( $paramName, $paramValue ) {
- $paramName = $this->encodeParamName( $paramName );
- $msg = array( $paramName => $paramValue );
- $result = $this->getResult();
- $result->disableSizeCheck();
- $result->addValue( 'query-continue', $this->getModuleName(), $msg, ApiResult::ADD_ON_TOP );
- $result->enableSizeCheck();
+ $this->getResult()->setContinueParam( $this, $paramName, $paramValue );
}
/**
- * Get the Query database connection (read-only)
- * @return DatabaseBase
+ * Convert an input title or title prefix into a dbkey.
+ *
+ * $namespace should always be specified in order to handle per-namespace
+ * capitalization settings.
+ *
+ * @param string $titlePart Title part
+ * @param int $defaultNamespace Namespace of the title
+ * @return string DBkey (no namespace prefix)
*/
- protected function getDB() {
- if ( is_null( $this->mDb ) ) {
- $this->mDb = $this->getQuery()->getDB();
+ public function titlePartToKey( $titlePart, $namespace = NS_MAIN ) {
+ $t = Title::makeTitleSafe( $namespace, $titlePart . 'x' );
+ if ( !$t ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) );
}
- return $this->mDb;
+ if ( $namespace != $t->getNamespace() || $t->isExternal() ) {
+ // This can happen in two cases. First, if you call titlePartToKey with a title part
+ // that looks like a namespace, but with $defaultNamespace = NS_MAIN. It would be very
+ // difficult to handle such a case. Such cases cannot exist and are therefore treated
+ // as invalid user input. The second case is when somebody specifies a title interwiki
+ // prefix.
+ $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) );
+ }
+
+ return substr( $t->getDbKey(), 0, -1 );
}
/**
- * Selects the query database connection with the given name.
- * See ApiQuery::getNamedDB() for more information
- * @param string $name Name to assign to the database connection
- * @param int $db One of the DB_* constants
- * @param array $groups Query groups
- * @return DatabaseBase
+ * Gets the personalised direction parameter description
+ *
+ * @param string $p ModulePrefix
+ * @param string $extraDirText Any extra text to be appended on the description
+ * @return array
*/
- public function selectNamedDB( $name, $db, $groups ) {
- $this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups );
+ public function getDirectionDescription( $p = '', $extraDirText = '' ) {
+ return array(
+ "In which direction to enumerate{$extraDirText}",
+ " newer - List oldest first. Note: {$p}start has to be before {$p}end.",
+ " older - List newest first (default). Note: {$p}start has to be later than {$p}end.",
+ );
}
/**
- * Get the PageSet object to work on
- * @return ApiPageSet
+ * @param string $hash
+ * @return bool
*/
- protected function getPageSet() {
- return $this->getQuery()->getPageSet();
+ public function validateSha1Hash( $hash ) {
+ return preg_match( '/^[a-f0-9]{40}$/', $hash );
+ }
+
+ /**
+ * @param string $hash
+ * @return bool
+ */
+ public function validateSha1Base36Hash( $hash ) {
+ return preg_match( '/^[a-z0-9]{31}$/', $hash );
+ }
+
+ /**
+ * Check whether the current user has permission to view revision-deleted
+ * fields.
+ * @return bool
+ */
+ public function userCanSeeRevDel() {
+ return $this->getUser()->isAllowedAny(
+ 'deletedhistory',
+ 'deletedtext',
+ 'suppressrevision',
+ 'viewsuppressed'
+ );
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Deprecated
+ * @{
+ */
+
+ /**
+ * Estimate the row count for the SELECT query that would be run if we
+ * called select() right now, and check if it's acceptable.
+ * @deprecated since 1.24
+ * @return bool True if acceptable, false otherwise
+ */
+ protected function checkRowCount() {
+ wfDeprecated( __METHOD__, '1.24' );
+ $db = $this->getDB();
+ $this->profileDBIn();
+ $rowcount = $db->estimateRowCount(
+ $this->tables,
+ $this->fields,
+ $this->where,
+ __METHOD__,
+ $this->options
+ );
+ $this->profileDBOut();
+
+ if ( $rowcount > $this->getConfig()->get( 'APIMaxDBRows' ) ) {
+ return false;
+ }
+
+ return true;
}
/**
* Convert a title to a DB key
+ * @deprecated since 1.24, past uses of this were always incorrect and should
+ * have used self::titlePartToKey() instead
* @param string $title Page title with spaces
* @return string Page title with underscores
*/
public function titleToKey( $title ) {
+ wfDeprecated( __METHOD__, '1.24' );
// Don't throw an error if we got an empty string
if ( trim( $title ) == '' ) {
return '';
@@ -419,15 +612,18 @@ abstract class ApiQueryBase extends ApiBase {
if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
+
return $t->getPrefixedDBkey();
}
/**
* The inverse of titleToKey()
+ * @deprecated since 1.24, unused and probably never needed
* @param string $key Page title with underscores
* @return string Page title with spaces
*/
public function keyToTitle( $key ) {
+ wfDeprecated( __METHOD__, '1.24' );
// Don't throw an error if we got an empty string
if ( trim( $key ) == '' ) {
return '';
@@ -437,124 +633,22 @@ abstract class ApiQueryBase extends ApiBase {
if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $key ) );
}
- return $t->getPrefixedText();
- }
- /**
- * An alternative to titleToKey() that doesn't trim trailing spaces
- * @param string $titlePart Title part with spaces
- * @return string Title part with underscores
- */
- public function titlePartToKey( $titlePart ) {
- return substr( $this->titleToKey( $titlePart . 'x' ), 0, - 1 );
+ return $t->getPrefixedText();
}
/**
- * An alternative to keyToTitle() that doesn't trim trailing spaces
- * @param string $keyPart Key part with spaces
+ * Inverse of titlePartToKey()
+ * @deprecated since 1.24, unused and probably never needed
+ * @param string $keyPart DBkey, with prefix
* @return string Key part with underscores
*/
public function keyPartToTitle( $keyPart ) {
- return substr( $this->keyToTitle( $keyPart . 'x' ), 0, - 1 );
+ wfDeprecated( __METHOD__, '1.24' );
+ return substr( $this->keyToTitle( $keyPart . 'x' ), 0, -1 );
}
- /**
- * Gets the personalised direction parameter description
- *
- * @param string $p ModulePrefix
- * @param string $extraDirText Any extra text to be appended on the description
- * @return array
- */
- public function getDirectionDescription( $p = '', $extraDirText = '' ) {
- return array(
- "In which direction to enumerate{$extraDirText}",
- " newer - List oldest first. Note: {$p}start has to be before {$p}end.",
- " older - List newest first (default). Note: {$p}start has to be later than {$p}end.",
- );
- }
-
- /**
- * @param $query String
- * @param $protocol String
- * @return null|string
- */
- public function prepareUrlQuerySearchString( $query = null, $protocol = null ) {
- $db = $this->getDb();
- if ( !is_null( $query ) || $query != '' ) {
- if ( is_null( $protocol ) ) {
- $protocol = 'http://';
- }
-
- $likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
- if ( !$likeQuery ) {
- $this->dieUsage( 'Invalid query', 'bad_query' );
- }
-
- $likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
- return 'el_index ' . $db->buildLike( $likeQuery );
- } elseif ( !is_null( $protocol ) ) {
- return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() );
- }
-
- return null;
- }
-
- /**
- * Filters hidden users (where the user doesn't have the right to view them)
- * Also adds relevant block information
- *
- * @param bool $showBlockInfo
- * @return void
- */
- public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
- $userCanViewHiddenUsers = $this->getUser()->isAllowed( 'hideuser' );
-
- if ( $showBlockInfo || !$userCanViewHiddenUsers ) {
- $this->addTables( 'ipblocks' );
- $this->addJoinConds( array(
- 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ),
- ) );
-
- $this->addFields( 'ipb_deleted' );
-
- if ( $showBlockInfo ) {
- $this->addFields( array( 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_reason', 'ipb_expiry' ) );
- }
-
- // Don't show hidden names
- if ( !$userCanViewHiddenUsers ) {
- $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
- }
- }
- }
-
- /**
- * @param $hash string
- * @return bool
- */
- public function validateSha1Hash( $hash ) {
- return preg_match( '/^[a-f0-9]{40}$/', $hash );
- }
-
- /**
- * @param $hash string
- * @return bool
- */
- public function validateSha1Base36Hash( $hash ) {
- return preg_match( '/^[a-z0-9]{31}$/', $hash );
- }
-
- /**
- * @return array
- */
- public function getPossibleErrors() {
- $errors = parent::getPossibleErrors();
- $errors = array_merge( $errors, array(
- array( 'invalidtitle', 'title' ),
- array( 'invalidtitle', 'key' ),
- ) );
- return $errors;
- }
+ /**@}*/
}
/**
@@ -568,7 +662,7 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
* Switch this module to generator mode. By default, generator mode is
* switched off and the module acts like a normal query module.
* @since 1.21 requires pageset parameter
- * @param $generatorPageSet ApiPageSet object that the module will get
+ * @param ApiPageSet $generatorPageSet ApiPageSet object that the module will get
* by calling getPageSet() when in generator mode.
*/
public function setGeneratorMode( ApiPageSet $generatorPageSet ) {
@@ -587,11 +681,12 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
if ( $this->mGeneratorPageSet !== null ) {
return $this->mGeneratorPageSet;
}
+
return parent::getPageSet();
}
/**
- * Overrides base class to prepend 'g' to every generator parameter
+ * Overrides ApiBase to prepend 'g' to every generator parameter
* @param string $paramName Parameter name
* @return string Prefixed parameter name
*/
@@ -604,24 +699,21 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
}
/**
- * Overrides base in case of generator & smart continue to
- * notify ApiQueryMain instead of adding them to the result right away.
+ * Overridden to set the generator param if in generator mode
* @param string $paramName Parameter name
- * @param string $paramValue Parameter value
+ * @param string|array $paramValue Parameter value
*/
protected function setContinueEnumParameter( $paramName, $paramValue ) {
- // If this is a generator and query->setGeneratorContinue() returns false, treat as before
- if ( $this->mGeneratorPageSet === null
- || !$this->getQuery()->setGeneratorContinue( $this, $paramName, $paramValue )
- ) {
+ if ( $this->mGeneratorPageSet !== null ) {
+ $this->getResult()->setGeneratorContinueParam( $this, $paramName, $paramValue );
+ } else {
parent::setContinueEnumParameter( $paramName, $paramValue );
}
}
/**
* Execute this module as a generator
- * @param $resultPageSet ApiPageSet: All output should be appended to
- * this object
+ * @param ApiPageSet $resultPageSet All output should be appended to this object
*/
abstract public function executeGenerator( $resultPageSet );
}
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index e3c27f5e..33b25fd9 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -32,17 +32,18 @@
class ApiQueryBlocks extends ApiQueryBase {
/**
- * @var Array
+ * @var array
*/
protected $usernames;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'bk' );
}
public function execute() {
global $wgContLang;
+ $db = $this->getDB();
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'users', 'ip' );
@@ -61,9 +62,8 @@ class ApiQueryBlocks extends ApiQueryBase {
$result = $this->getResult();
$this->addTables( 'ipblocks' );
- $this->addFields( 'ipb_auto' );
+ $this->addFields( array( 'ipb_auto', 'ipb_id' ) );
- $this->addFieldsIf( 'ipb_id', $fld_id );
$this->addFieldsIf( array( 'ipb_address', 'ipb_user' ), $fld_user || $fld_userid );
$this->addFieldsIf( 'ipb_by_text', $fld_by );
$this->addFieldsIf( 'ipb_by', $fld_byid );
@@ -72,13 +72,31 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addFieldsIf( 'ipb_reason', $fld_reason );
$this->addFieldsIf( array( 'ipb_range_start', 'ipb_range_end' ), $fld_range );
$this->addFieldsIf( array( 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock',
- 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ),
- $fld_flags );
+ 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ),
+ $fld_flags );
$this->addOption( 'LIMIT', $params['limit'] + 1 );
- $this->addTimestampWhereRange( 'ipb_timestamp', $params['dir'], $params['start'], $params['end'] );
-
- $db = $this->getDB();
+ $this->addTimestampWhereRange(
+ 'ipb_timestamp',
+ $params['dir'],
+ $params['start'],
+ $params['end']
+ );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'ipb_id', $params['dir'], null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $op = ( $params['dir'] == 'newer' ? '>' : '<' );
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueId = (int)$cont[1];
+ $this->dieContinueUsageIf( $continueId != $cont[1] );
+ $this->addWhere( "ipb_timestamp $op $continueTimestamp OR " .
+ "(ipb_timestamp = $continueTimestamp AND " .
+ "ipb_id $op= $continueId)"
+ );
+ }
if ( isset( $params['ids'] ) ) {
$this->addWhereFld( 'ipb_id', $params['ids'] );
@@ -91,14 +109,14 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addWhereFld( 'ipb_auto', 0 );
}
if ( isset( $params['ip'] ) ) {
- global $wgBlockCIDRLimit;
+ $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
if ( IP::isIPv4( $params['ip'] ) ) {
$type = 'IPv4';
- $cidrLimit = $wgBlockCIDRLimit['IPv4'];
+ $cidrLimit = $blockCIDRLimit['IPv4'];
$prefixLen = 0;
} elseif ( IP::isIPv6( $params['ip'] ) ) {
$type = 'IPv6';
- $cidrLimit = $wgBlockCIDRLimit['IPv6'];
+ $cidrLimit = $blockCIDRLimit['IPv6'];
$prefixLen = 3; // IP::toHex output is prefixed with "v6-"
} else {
$this->dieUsage( 'IP parameter is not valid', 'param_ip' );
@@ -107,7 +125,10 @@ class ApiQueryBlocks extends ApiQueryBase {
# Check range validity, if it's a CIDR
list( $ip, $range ) = IP::parseCIDR( $params['ip'] );
if ( $ip !== false && $range !== false && $range < $cidrLimit ) {
- $this->dieUsage( "$type CIDR ranges broader than /$cidrLimit are not accepted", 'cidrtoobroad' );
+ $this->dieUsage(
+ "$type CIDR ranges broader than /$cidrLimit are not accepted",
+ 'cidrtoobroad'
+ );
}
# Let IP::parseRange handle calculating $upper, instead of duplicating the logic here.
@@ -134,9 +155,9 @@ class ApiQueryBlocks extends ApiQueryBase {
/* Check for conflicting parameters. */
if ( ( isset( $show['account'] ) && isset( $show['!account'] ) )
- || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
- || ( isset( $show['range'] ) && isset( $show['!range'] ) )
- || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
+ || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
+ || ( isset( $show['range'] ) && isset( $show['!range'] ) )
+ || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
) {
$this->dieUsageMsg( 'show' );
}
@@ -145,8 +166,10 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addWhereIf( 'ipb_user != 0', isset( $show['account'] ) );
$this->addWhereIf( 'ipb_user != 0 OR ipb_range_end > ipb_range_start', isset( $show['!ip'] ) );
$this->addWhereIf( 'ipb_user = 0 AND ipb_range_end = ipb_range_start', isset( $show['ip'] ) );
- $this->addWhereIf( 'ipb_expiry = ' . $db->addQuotes( $db->getInfinity() ), isset( $show['!temp'] ) );
- $this->addWhereIf( 'ipb_expiry != ' . $db->addQuotes( $db->getInfinity() ), isset( $show['temp'] ) );
+ $this->addWhereIf( 'ipb_expiry = ' .
+ $db->addQuotes( $db->getInfinity() ), isset( $show['!temp'] ) );
+ $this->addWhereIf( 'ipb_expiry != ' .
+ $db->addQuotes( $db->getInfinity() ), isset( $show['temp'] ) );
$this->addWhereIf( 'ipb_range_end = ipb_range_start', isset( $show['!range'] ) );
$this->addWhereIf( 'ipb_range_end > ipb_range_start', isset( $show['range'] ) );
}
@@ -166,7 +189,7 @@ class ApiQueryBlocks extends ApiQueryBase {
foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've had enough
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
break;
}
$block = array();
@@ -224,7 +247,7 @@ class ApiQueryBlocks extends ApiQueryBase {
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $block );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
break;
}
}
@@ -303,12 +326,14 @@ class ApiQueryBlocks extends ApiQueryBase {
),
ApiBase::PARAM_ISMULTI => true
),
+ 'continue' => null,
);
}
public function getParamDescription() {
- global $wgBlockCIDRLimit;
+ $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
$p = $this->getModulePrefix();
+
return array(
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to stop enumerating at',
@@ -318,7 +343,7 @@ class ApiQueryBlocks extends ApiQueryBase {
'ip' => array(
'Get all blocks applying to this IP or CIDR range, including range blocks.',
"Cannot be used together with bkusers. CIDR ranges broader than " .
- "IPv4/{$wgBlockCIDRLimit['IPv4']} or IPv6/{$wgBlockCIDRLimit['IPv6']} " .
+ "IPv4/{$blockCIDRLimit['IPv4']} or IPv6/{$blockCIDRLimit['IPv6']} " .
"are not accepted"
),
'limit' => 'The maximum amount of blocks to list',
@@ -339,86 +364,12 @@ class ApiQueryBlocks extends ApiQueryBase {
'Show only items that meet this criteria.',
"For example, to see only indefinite blocks on IPs, set {$p}show=ip|!temp"
),
- );
- }
-
- public function getResultProperties() {
- return array(
- 'id' => array(
- 'id' => 'integer'
- ),
- 'user' => array(
- 'user' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'userid' => array(
- 'userid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'by' => array(
- 'by' => 'string'
- ),
- 'byid' => array(
- 'byid' => 'integer'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'expiry' => array(
- 'expiry' => 'timestamp'
- ),
- 'reason' => array(
- 'reason' => 'string'
- ),
- 'range' => array(
- 'rangestart' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'rangeend' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'flags' => array(
- 'automatic' => 'boolean',
- 'anononly' => 'boolean',
- 'nocreate' => 'boolean',
- 'autoblock' => 'boolean',
- 'noemail' => 'boolean',
- 'hidden' => 'boolean',
- 'allowusertalk' => 'boolean'
- )
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return 'List all blocked users and IP addresses';
- }
-
- public function getPossibleErrors() {
- global $wgBlockCIDRLimit;
- return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'users', 'ip' ) ),
- array(
- array(
- 'code' => 'cidrtoobroad',
- 'info' => "IPv4 CIDR ranges broader than /{$wgBlockCIDRLimit['IPv4']} are not accepted"
- ),
- array(
- 'code' => 'cidrtoobroad',
- 'info' => "IPv6 CIDR ranges broader than /{$wgBlockCIDRLimit['IPv6']} are not accepted"
- ),
- array( 'code' => 'param_ip', 'info' => 'IP parameter is not valid' ),
- array( 'code' => 'param_user', 'info' => 'User parameter may not be empty' ),
- array( 'code' => 'param_user', 'info' => 'User name user is not valid' ),
- array( 'show' ),
- )
- );
+ return 'List all blocked users and IP addresses.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index 5d714f57..1926dd09 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -31,7 +31,7 @@
*/
class ApiQueryCategories extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'cl' );
}
@@ -48,7 +48,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
@@ -98,8 +98,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
if ( isset( $show['hidden'] ) && isset( $show['!hidden'] ) ) {
$this->dieUsageMsg( 'show' );
}
- if ( isset( $show['hidden'] ) || isset( $show['!hidden'] ) || isset( $prop['hidden'] ) )
- {
+ if ( isset( $show['hidden'] ) || isset( $show['!hidden'] ) || isset( $prop['hidden'] ) ) {
$this->addOption( 'STRAIGHT_JOIN' );
$this->addTables( array( 'page', 'page_props' ) );
$this->addFieldsIf( 'pp_propname', isset( $prop['hidden'] ) );
@@ -126,9 +125,9 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', 'cl_to' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'cl_from' . $sort,
- 'cl_to' . $sort
- ));
+ 'cl_from' . $sort,
+ 'cl_to' . $sort
+ ) );
}
$res = $this->select( __METHOD__ );
@@ -221,51 +220,30 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
return array(
'prop' => array(
'Which additional properties to get for each category',
- ' sortkey - Adds the sortkey (hexadecimal string) and sortkey prefix (human-readable part) for the category',
+ ' sortkey - Adds the sortkey (hexadecimal string) and sortkey prefix',
+ ' (human-readable part) for the category',
' timestamp - Adds timestamp of when the category was added',
' hidden - Tags categories that are hidden with __HIDDENCAT__',
),
'limit' => 'How many categories to return',
'show' => 'Which kind of categories to show',
'continue' => 'When more results are available, use this to continue',
- 'categories' => 'Only list these categories. Useful for checking whether a certain page is in a certain category',
+ 'categories' => 'Only list these categories. Useful for checking ' .
+ 'whether a certain page is in a certain category',
'dir' => 'The direction in which to list',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'sortkey' => array(
- 'sortkey' => 'string',
- 'sortkeyprefix' => 'string'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'hidden' => array(
- 'hidden' => 'boolean'
- )
- );
- }
-
public function getDescription() {
- return 'List all categories the page(s) belong to';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'show' ),
- ) );
+ return 'List all categories the page(s) belong to.';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=categories&titles=Albert%20Einstein' => 'Get a list of categories [[Albert Einstein]] belongs to',
- 'api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info' => 'Get information about all categories used in the [[Albert Einstein]]',
+ 'api.php?action=query&prop=categories&titles=Albert%20Einstein'
+ => 'Get a list of categories [[Albert Einstein]] belongs to',
+ 'api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info'
+ => 'Get information about all categories used in the [[Albert Einstein]]',
);
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index a889272e..6e9f33c1 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -32,7 +32,7 @@
*/
class ApiQueryCategoryInfo extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ci' );
}
@@ -45,7 +45,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
$categories = $alltitles[NS_CATEGORY];
$titles = $this->getPageSet()->getGoodTitles() +
- $this->getPageSet()->getMissingTitles();
+ $this->getPageSet()->getMissingTitles();
$cattitles = array();
foreach ( $categories as $c ) {
/** @var $t Title */
@@ -63,7 +63,13 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
'pp_propname' => 'hiddencat' ) ),
) );
- $this->addFields( array( 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'cat_hidden' => 'pp_propname' ) );
+ $this->addFields( array(
+ 'cat_title',
+ 'cat_pages',
+ 'cat_subcats',
+ 'cat_files',
+ 'cat_hidden' => 'pp_propname'
+ ) );
$this->addWhere( array( 'cat_title' => $cattitles ) );
if ( !is_null( $params['continue'] ) ) {
@@ -108,36 +114,8 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
);
}
- public function getResultProperties() {
- return array(
- ApiBase::PROP_LIST => false,
- '' => array(
- 'size' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => false
- ),
- 'pages' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => false
- ),
- 'files' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => false
- ),
- 'subcats' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => false
- ),
- 'hidden' => array(
- ApiBase::PROP_TYPE => 'boolean',
- ApiBase::PROP_NULLABLE => false
- )
- )
- );
- }
-
public function getDescription() {
- return 'Returns information about the given categories';
+ return 'Returns information about the given categories.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index 704d108a..a88a9cb1 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -31,7 +31,7 @@
*/
class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'cm' );
}
@@ -48,7 +48,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -86,9 +86,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
// Scanning large datasets for rare categories sucks, and I already told
// how to have efficient subcategory access :-) ~~~~ (oh well, domas)
- global $wgMiserMode;
$miser_ns = array();
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$miser_ns = $params['namespace'];
} else {
$this->addWhereFld( 'page_namespace', $params['namespace'] );
@@ -101,6 +100,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$dir,
$params['start'],
$params['end'] );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'cl_from', $dir, null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $op = ( $dir === 'newer' ? '>' : '<' );
+ $db = $this->getDB();
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueFrom = (int)$cont[1];
+ $this->dieContinueUsageIf( $continueFrom != $cont[1] );
+ $this->addWhere( "cl_timestamp $op $continueTimestamp OR " .
+ "(cl_timestamp = $continueTimestamp AND " .
+ "cl_from $op= $continueFrom)"
+ );
+ }
$this->addOption( 'USE INDEX', 'cl_timestamp' );
} else {
@@ -125,12 +140,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$this->addWhereRange( 'cl_sortkey', $dir, null, null );
$this->addWhereRange( 'cl_from', $dir, null, null );
} else {
- $startsortkey = $params['startsortkeyprefix'] !== null ?
- Collation::singleton()->getSortkey( $params['startsortkeyprefix'] ) :
- $params['startsortkey'];
- $endsortkey = $params['endsortkeyprefix'] !== null ?
- Collation::singleton()->getSortkey( $params['endsortkeyprefix'] ) :
- $params['endsortkey'];
+ if ( $params['startsortkeyprefix'] !== null ) {
+ $startsortkey = Collation::singleton()->getSortkey( $params['startsortkeyprefix'] );
+ } elseif ( $params['starthexsortkey'] !== null ) {
+ $startsortkey = pack( 'H*', $params['starthexsortkey'] );
+ } else {
+ $this->logFeatureUsage( 'list=categorymembers&cmstartsortkey' );
+ $startsortkey = $params['startsortkey'];
+ }
+ if ( $params['endsortkeyprefix'] !== null ) {
+ $endsortkey = Collation::singleton()->getSortkey( $params['endsortkeyprefix'] );
+ } elseif ( $params['endhexsortkey'] !== null ) {
+ $endsortkey = pack( 'H*', $params['endhexsortkey'] );
+ } else {
+ $this->logFeatureUsage( 'list=categorymembers&cmendsortkey' );
+ $endsortkey = $params['endsortkey'];
+ }
// The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them
$this->addWhereRange( 'cl_sortkey',
@@ -180,11 +205,13 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$result = $this->getResult();
$count = 0;
foreach ( $rows as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // TODO: Security issue - if the user has no right to view next title, it will still be shown
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ // @todo Security issue - if the user has no right to view next
+ // title, it will still be shown
if ( $params['sort'] == 'timestamp' ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->cl_timestamp|$row->cl_from" );
} else {
$sortkey = bin2hex( $row->cl_sortkey );
$this->setContinueEnumParameter( 'continue',
@@ -224,10 +251,10 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cl_timestamp );
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
- null, $vals );
+ null, $vals );
if ( !$fit ) {
if ( $params['sort'] == 'timestamp' ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->cl_timestamp|$row->cl_from" );
} else {
$sortkey = bin2hex( $row->cl_sortkey );
$this->setContinueEnumParameter( 'continue',
@@ -313,25 +340,32 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'end' => array(
ApiBase::PARAM_TYPE => 'timestamp'
),
- 'startsortkey' => null,
- 'endsortkey' => null,
+ 'starthexsortkey' => null,
+ 'endhexsortkey' => null,
'startsortkeyprefix' => null,
'endsortkeyprefix' => null,
+ 'startsortkey' => array(
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'endsortkey' => array(
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
);
}
public function getParamDescription() {
- global $wgMiserMode;
$p = $this->getModulePrefix();
$desc = array(
- 'title' => "Which category to enumerate (required). Must include Category: prefix. Cannot be used together with {$p}pageid",
+ 'title' => "Which category to enumerate (required). Must include " .
+ "'Category:' prefix. Cannot be used together with {$p}pageid",
'pageid' => "Page ID of the category to enumerate. Cannot be used together with {$p}title",
'prop' => array(
'What pieces of information to include',
' ids - Adds the page ID',
' title - Adds the title and namespace ID of the page',
' sortkey - Adds the sortkey used for sorting in the category (hexadecimal string)',
- ' sortkeyprefix - Adds the sortkey prefix used for sorting in the category (human-readable part of the sortkey)',
+ ' sortkeyprefix - Adds the sortkey prefix used for sorting in the ' .
+ 'category (human-readable part of the sortkey)',
' type - Adds the type that the page has been categorised as (page, subcat or file)',
' timestamp - Adds the timestamp of when the page was included',
),
@@ -341,15 +375,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'dir' => 'In which direction to sort',
'start' => "Timestamp to start listing from. Can only be used with {$p}sort=timestamp",
'end' => "Timestamp to end listing at. Can only be used with {$p}sort=timestamp",
- 'startsortkey' => "Sortkey to start listing from. Must be given in binary format. Can only be used with {$p}sort=sortkey",
- 'endsortkey' => "Sortkey to end listing at. Must be given in binary format. Can only be used with {$p}sort=sortkey",
- 'startsortkeyprefix' => "Sortkey prefix to start listing from. Can only be used with {$p}sort=sortkey. Overrides {$p}startsortkey",
- 'endsortkeyprefix' => "Sortkey prefix to end listing BEFORE (not at, if this value occurs it will not be included!). Can only be used with {$p}sort=sortkey. Overrides {$p}endsortkey",
+ 'starthexsortkey' => "Sortkey to start listing from, as returned by prop=sortkey. " .
+ "Can only be used with {$p}sort=sortkey",
+ 'endhexsortkey' => "Sortkey to end listing from, as returned by prop=sortkey. " .
+ "Can only be used with {$p}sort=sortkey",
+ 'startsortkeyprefix' => "Sortkey prefix to start listing from. Can " .
+ "only be used with {$p}sort=sortkey. Overrides {$p}starthexsortkey",
+ 'endsortkeyprefix' => "Sortkey prefix to end listing BEFORE (not at, " .
+ "if this value occurs it will not be included!). Can only be used with " .
+ "{$p}sort=sortkey. Overrides {$p}endhexsortkey",
+ 'startsortkey' => "Use starthexsortkey instead",
+ 'endsortkey' => "Use endhexsortkey instead",
'continue' => 'For large categories, give the value returned from previous query',
'limit' => 'The maximum number of pages to return.',
);
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$desc['namespace'] = array(
$desc['namespace'],
"NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
@@ -357,56 +398,20 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
"Note that you can use {$p}type=subcat or {$p}type=file instead of {$p}namespace=14 or 6.",
);
}
- return $desc;
- }
- public function getResultProperties() {
- return array(
- 'ids' => array(
- 'pageid' => 'integer'
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'sortkey' => array(
- 'sortkey' => 'string'
- ),
- 'sortkeyprefix' => array(
- 'sortkeyprefix' => 'string'
- ),
- 'type' => array(
- 'type' => array(
- ApiBase::PROP_TYPE => array(
- 'page',
- 'subcat',
- 'file'
- )
- )
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- )
- );
+ return $desc;
}
public function getDescription() {
- return 'List all pages in a given category';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ),
- )
- );
+ return 'List all pages in a given category.';
}
public function getExamples() {
return array(
- 'api.php?action=query&list=categorymembers&cmtitle=Category:Physics' => 'Get first 10 pages in [[Category:Physics]]',
- 'api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info' => 'Get page info about first 10 pages in [[Category:Physics]]',
+ 'api.php?action=query&list=categorymembers&cmtitle=Category:Physics'
+ => 'Get first 10 pages in [[Category:Physics]]',
+ 'api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info'
+ => 'Get page info about first 10 pages in [[Category:Physics]]',
);
}
diff --git a/includes/api/ApiQueryContributors.php b/includes/api/ApiQueryContributors.php
new file mode 100644
index 00000000..55ea4702
--- /dev/null
+++ b/includes/api/ApiQueryContributors.php
@@ -0,0 +1,282 @@
+<?php
+/**
+ * Query the list of contributors to a page
+ *
+ * Created on Nov 14, 2013
+ *
+ * Copyright © 2013 Brad Jorsch
+ *
+ * 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
+ * @since 1.23
+ */
+
+/**
+ * A query module to show contributors to a page
+ *
+ * @ingroup API
+ * @since 1.23
+ */
+class ApiQueryContributors extends ApiQueryBase {
+ /** We don't want to process too many pages at once (it hits cold
+ * database pages too heavily), so only do the first MAX_PAGES input pages
+ * in each API call (leaving the rest for continuation).
+ */
+ const MAX_PAGES = 100;
+
+ public function __construct( ApiQuery $query, $moduleName ) {
+ // "pc" is short for "page contributors", "co" was already taken by the
+ // GeoData extension's prop=coordinates.
+ parent::__construct( $query, $moduleName, 'pc' );
+ }
+
+ public function execute() {
+ $db = $this->getDB();
+ $params = $this->extractRequestParams();
+ $this->requireMaxOneParameter( $params, 'group', 'excludegroup', 'rights', 'excluderights' );
+
+ // Only operate on existing pages
+ $pages = array_keys( $this->getPageSet()->getGoodTitles() );
+
+ // Filter out already-processed pages
+ if ( $params['continue'] !== null ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $cont_page = (int)$cont[0];
+ $pages = array_filter( $pages, function ( $v ) use ( $cont_page ) {
+ return $v >= $cont_page;
+ } );
+ }
+ if ( !count( $pages ) ) {
+ // Nothing to do
+ return;
+ }
+
+ // Apply MAX_PAGES, leaving any over the limit for a continue.
+ sort( $pages );
+ $continuePages = null;
+ if ( count( $pages ) > self::MAX_PAGES ) {
+ $continuePages = $pages[self::MAX_PAGES] . '|0';
+ $pages = array_slice( $pages, 0, self::MAX_PAGES );
+ }
+
+ $result = $this->getResult();
+
+ // First, count anons
+ $this->addTables( 'revision' );
+ $this->addFields( array(
+ 'page' => 'rev_page',
+ 'anons' => 'COUNT(DISTINCT rev_user_text)',
+ ) );
+ $this->addWhereFld( 'rev_page', $pages );
+ $this->addWhere( 'rev_user = 0' );
+ $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ $this->addOption( 'GROUP BY', 'rev_page' );
+ $res = $this->select( __METHOD__ );
+ foreach ( $res as $row ) {
+ $fit = $result->addValue( array( 'query', 'pages', $row->page ),
+ 'anoncontributors', $row->anons
+ );
+ if ( !$fit ) {
+ // This not fitting isn't reasonable, so it probably means that
+ // some other module used up all the space. Just set a dummy
+ // continue and hope it works next time.
+ $this->setContinueEnumParameter( 'continue',
+ $params['continue'] !== null ? $params['continue'] : '0|0'
+ );
+
+ return;
+ }
+ }
+
+ // Next, add logged-in users
+ $this->resetQueryParams();
+ $this->addTables( 'revision' );
+ $this->addFields( array(
+ 'page' => 'rev_page',
+ 'user' => 'rev_user',
+ 'username' => 'MAX(rev_user_text)', // Non-MySQL databases don't like partial group-by
+ ) );
+ $this->addWhereFld( 'rev_page', $pages );
+ $this->addWhere( 'rev_user != 0' );
+ $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ $this->addOption( 'GROUP BY', 'rev_page, rev_user' );
+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
+
+ // Force a sort order to ensure that properties are grouped by page
+ // But only if pp_page is not constant in the WHERE clause.
+ if ( count( $pages ) > 1 ) {
+ $this->addOption( 'ORDER BY', 'rev_page, rev_user' );
+ } else {
+ $this->addOption( 'ORDER BY', 'rev_user' );
+ }
+
+ $limitGroups = array();
+ if ( $params['group'] ) {
+ $excludeGroups = false;
+ $limitGroups = $params['group'];
+ } elseif ( $params['excludegroup'] ) {
+ $excludeGroups = true;
+ $limitGroups = $params['excludegroup'];
+ } elseif ( $params['rights'] ) {
+ $excludeGroups = false;
+ foreach ( $params['rights'] as $r ) {
+ $limitGroups = array_merge( $limitGroups, User::getGroupsWithPermission( $r ) );
+ }
+
+ // If no group has the rights requested, no need to query
+ if ( !$limitGroups ) {
+ if ( $continuePages !== null ) {
+ // But we still need to continue for the next page's worth
+ // of anoncontributors
+ $this->setContinueEnumParameter( 'continue', $continuePages );
+ }
+
+ return;
+ }
+ } elseif ( $params['excluderights'] ) {
+ $excludeGroups = true;
+ foreach ( $params['excluderights'] as $r ) {
+ $limitGroups = array_merge( $limitGroups, User::getGroupsWithPermission( $r ) );
+ }
+ }
+
+ if ( $limitGroups ) {
+ $limitGroups = array_unique( $limitGroups );
+ $this->addTables( 'user_groups' );
+ $this->addJoinConds( array( 'user_groups' => array(
+ $excludeGroups ? 'LEFT OUTER JOIN' : 'INNER JOIN',
+ array( 'ug_user=rev_user', 'ug_group' => $limitGroups )
+ ) ) );
+ $this->addWhereIf( 'ug_user IS NULL', $excludeGroups );
+ }
+
+ if ( $params['continue'] !== null ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $cont_page = (int)$cont[0];
+ $cont_user = (int)$cont[1];
+ $this->addWhere(
+ "rev_page > $cont_page OR " .
+ "(rev_page = $cont_page AND " .
+ "rev_user >= $cont_user)"
+ );
+ }
+
+ $res = $this->select( __METHOD__ );
+ $count = 0;
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->user );
+
+ return;
+ }
+
+ $fit = $this->addPageSubItem( $row->page,
+ array( 'userid' => $row->user, 'name' => $row->username ),
+ 'user'
+ );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->user );
+
+ return;
+ }
+ }
+
+ if ( $continuePages !== null ) {
+ $this->setContinueEnumParameter( 'continue', $continuePages );
+ }
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ $userGroups = User::getAllGroups();
+ $userRights = User::getAllRights();
+
+ return array(
+ 'group' => array(
+ ApiBase::PARAM_TYPE => $userGroups,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'excludegroup' => array(
+ ApiBase::PARAM_TYPE => $userGroups,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'rights' => array(
+ ApiBase::PARAM_TYPE => $userRights,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'excluderights' => array(
+ ApiBase::PARAM_TYPE => $userRights,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'group' => array(
+ 'Limit users to given group name(s)',
+ 'Does not include implicit or auto-promoted groups like *, user, or autoconfirmed'
+ ),
+ 'excludegroup' => array(
+ 'Exclude users in given group name(s)',
+ 'Does not include implicit or auto-promoted groups like *, user, or autoconfirmed'
+ ),
+ 'rights' => array(
+ 'Limit users to those having given right(s)',
+ 'Does not include rights granted by implicit or auto-promoted groups ' .
+ 'like *, user, or autoconfirmed'
+ ),
+ 'excluderights' => array(
+ 'Limit users to those not having given right(s)',
+ 'Does not include rights granted by implicit or auto-promoted groups ' .
+ 'like *, user, or autoconfirmed'
+ ),
+ 'limit' => 'How many contributors to return',
+ 'continue' => 'When more results are available, use this to continue',
+ );
+ }
+
+ public function getDescription() {
+ return 'Get the list of logged-in contributors and ' .
+ 'the count of anonymous contributors to a page.';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&prop=contributors&titles=Main_Page',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#contributors_.2F_pc';
+ }
+}
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index 82733133..9042696b 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -31,7 +31,7 @@
*/
class ApiQueryDeletedrevs extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'dr' );
}
@@ -39,7 +39,10 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$user = $this->getUser();
// Before doing anything at all, let's check permissions
if ( !$user->isAllowed( 'deletedhistory' ) ) {
- $this->dieUsage( 'You don\'t have permission to view deleted revision information', 'permissiondenied' );
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision information',
+ 'permissiondenied'
+ );
}
$db = $this->getDB();
@@ -56,12 +59,25 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$fld_sha1 = isset( $prop['sha1'] );
$fld_content = isset( $prop['content'] );
$fld_token = isset( $prop['token'] );
+ $fld_tags = isset( $prop['tags'] );
+
+ if ( isset( $prop['token'] ) ) {
+ $p = $this->getModulePrefix();
+ $this->setWarning(
+ "{$p}prop=token has been deprecated. Please use action=query&meta=tokens instead."
+ );
+ }
// If we're in JSON callback mode, no tokens can be obtained
if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
$fld_token = false;
}
+ // If user can't undelete, no tokens
+ if ( !$user->isAllowed( 'undelete' ) ) {
+ $fld_token = false;
+ }
+
$result = $this->getResult();
$pageSet = $this->getPageSet();
$titles = $pageSet->getTitles();
@@ -97,8 +113,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
$this->addTables( 'archive' );
- $this->addWhere( 'ar_deleted = 0' );
- $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp' ) );
+ $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_deleted', 'ar_id' ) );
$this->addFieldsIf( 'ar_parent_id', $fld_parentid );
$this->addFieldsIf( 'ar_rev_id', $fld_revid );
@@ -109,14 +124,41 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addFieldsIf( 'ar_len', $fld_len );
$this->addFieldsIf( 'ar_sha1', $fld_sha1 );
+ if ( $fld_tags ) {
+ $this->addTables( 'tag_summary' );
+ $this->addJoinConds(
+ array( 'tag_summary' => array( 'LEFT JOIN', array( 'ar_rev_id=ts_rev_id' ) ) )
+ );
+ $this->addFields( 'ts_tags' );
+ }
+
+ if ( !is_null( $params['tag'] ) ) {
+ $this->addTables( 'change_tag' );
+ $this->addJoinConds(
+ array( 'change_tag' => array( 'INNER JOIN', array( 'ar_rev_id=ct_rev_id' ) ) )
+ );
+ $this->addWhereFld( 'ct_tag', $params['tag'] );
+ }
+
if ( $fld_content ) {
+ // Modern MediaWiki has the content for deleted revs in the 'text'
+ // table using fields old_text and old_flags. But revisions deleted
+ // pre-1.5 store the content in the 'archive' table directly using
+ // fields ar_text and ar_flags, and no corresponding 'text' row. So
+ // we have to LEFT JOIN and fetch all four fields, plus ar_text_id
+ // to be able to tell the difference.
$this->addTables( 'text' );
- $this->addFields( array( 'ar_text', 'ar_text_id', 'old_text', 'old_flags' ) );
- $this->addWhere( 'ar_text_id = old_id' );
+ $this->addJoinConds(
+ array( 'text' => array( 'LEFT JOIN', array( 'ar_text_id=old_id' ) ) )
+ );
+ $this->addFields( array( 'ar_text', 'ar_flags', 'ar_text_id', 'old_text', 'old_flags' ) );
// This also means stricter restrictions
- if ( !$user->isAllowed( 'undelete' ) ) {
- $this->dieUsage( 'You don\'t have permission to view deleted revision content', 'permissiondenied' );
+ if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision content',
+ 'permissiondenied'
+ );
}
}
// Check limits
@@ -147,12 +189,18 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
} elseif ( $mode == 'all' ) {
$this->addWhereFld( 'ar_namespace', $params['namespace'] );
- $from = is_null( $params['from'] ) ? null : $this->titleToKey( $params['from'] );
- $to = is_null( $params['to'] ) ? null : $this->titleToKey( $params['to'] );
+ $from = $params['from'] === null
+ ? null
+ : $this->titlePartToKey( $params['from'], $params['namespace'] );
+ $to = $params['to'] === null
+ ? null
+ : $this->titlePartToKey( $params['to'], $params['namespace'] );
$this->addWhereRange( 'ar_title', $dir, $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'ar_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'ar_title' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], $params['namespace'] ),
+ $db->anyString() ) );
}
}
@@ -163,32 +211,67 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$db->addQuotes( $params['excludeuser'] ) );
}
- if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) ) {
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ // Paranoia: avoid brute force searches (bug 17342)
+ // (shouldn't be able to get here without 'deletedhistory', but
+ // check it again just in case)
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+
+ if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- $this->dieContinueUsageIf( count( $cont ) != 3 );
- $ns = intval( $cont[0] );
- $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
- $title = $db->addQuotes( $cont[1] );
- $ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
$op = ( $dir == 'newer' ? '>' : '<' );
- $this->addWhere( "ar_namespace $op $ns OR " .
+ if ( $mode == 'all' || $mode == 'revs' ) {
+ $this->dieContinueUsageIf( count( $cont ) != 4 );
+ $ns = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
+ $title = $db->addQuotes( $cont[1] );
+ $ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
+ $ar_id = (int)$cont[3];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[3] );
+ $this->addWhere( "ar_namespace $op $ns OR " .
"(ar_namespace = $ns AND " .
"(ar_title $op $title OR " .
"(ar_title = $title AND " .
- "ar_timestamp $op= $ts)))" );
+ "(ar_timestamp $op $ts OR " .
+ "(ar_timestamp = $ts AND " .
+ "ar_id $op= $ar_id)))))" );
+ } else {
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $ts = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $ar_id = (int)$cont[1];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[1] );
+ $this->addWhere( "ar_timestamp $op $ts OR " .
+ "(ar_timestamp = $ts AND " .
+ "ar_id $op= $ar_id)" );
+ }
}
$this->addOption( 'LIMIT', $limit + 1 );
- $this->addOption( 'USE INDEX', array( 'archive' => ( $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp' ) ) );
+ $this->addOption(
+ 'USE INDEX',
+ array( 'archive' => ( $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp' ) )
+ );
if ( $mode == 'all' ) {
if ( $params['unique'] ) {
+ // @todo Does this work on non-MySQL?
$this->addOption( 'GROUP BY', 'ar_title' );
} else {
$sort = ( $dir == 'newer' ? '' : ' DESC' );
$this->addOption( 'ORDER BY', array(
'ar_title' . $sort,
- 'ar_timestamp' . $sort
- ));
+ 'ar_timestamp' . $sort,
+ 'ar_id' . $sort,
+ ) );
}
} else {
if ( $mode == 'revs' ) {
@@ -197,6 +280,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addWhereRange( 'ar_title', $dir, null, null );
}
$this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'ar_id', $dir, null, null );
}
$res = $this->select( __METHOD__ );
$pageMap = array(); // Maps ns&title to (fake) pageid
@@ -206,15 +291,18 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( ++$count > $limit ) {
// We've had enough
if ( $mode == 'all' || $mode == 'revs' ) {
- $this->setContinueEnumParameter( 'continue', intval( $row->ar_namespace ) . '|' .
- $row->ar_title . '|' . $row->ar_timestamp );
+ $this->setContinueEnumParameter( 'continue',
+ "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
} else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ar_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
}
break;
}
$rev = array();
+ $anyHidden = false;
+
$rev['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ar_timestamp );
if ( $fld_revid ) {
$rev['revid'] = intval( $row->ar_rev_id );
@@ -222,21 +310,37 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_parentid && !is_null( $row->ar_parent_id ) ) {
$rev['parentid'] = intval( $row->ar_parent_id );
}
- if ( $fld_user ) {
- $rev['user'] = $row->ar_user_text;
- }
- if ( $fld_userid ) {
- $rev['userid'] = $row->ar_user;
- }
- if ( $fld_comment ) {
- $rev['comment'] = $row->ar_comment;
+ if ( $fld_user || $fld_userid ) {
+ if ( $row->ar_deleted & Revision::DELETED_USER ) {
+ $rev['userhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_USER, $user ) ) {
+ if ( $fld_user ) {
+ $rev['user'] = $row->ar_user_text;
+ }
+ if ( $fld_userid ) {
+ $rev['userid'] = $row->ar_user;
+ }
+ }
}
- $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
-
- if ( $fld_parsedcomment ) {
- $rev['parsedcomment'] = Linker::formatComment( $row->ar_comment, $title );
+ if ( $fld_comment || $fld_parsedcomment ) {
+ if ( $row->ar_deleted & Revision::DELETED_COMMENT ) {
+ $rev['commenthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_COMMENT, $user ) ) {
+ if ( $fld_comment ) {
+ $rev['comment'] = $row->ar_comment;
+ }
+ if ( $fld_parsedcomment ) {
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ $rev['parsedcomment'] = Linker::formatComment( $row->ar_comment, $title );
+ }
+ }
}
+
if ( $fld_minor && $row->ar_minor_edit == 1 ) {
$rev['minor'] = '';
}
@@ -244,14 +348,45 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$rev['len'] = $row->ar_len;
}
if ( $fld_sha1 ) {
- if ( $row->ar_sha1 != '' ) {
- $rev['sha1'] = wfBaseConvert( $row->ar_sha1, 36, 16, 40 );
- } else {
- $rev['sha1'] = '';
+ if ( $row->ar_deleted & Revision::DELETED_TEXT ) {
+ $rev['sha1hidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
+ if ( $row->ar_sha1 != '' ) {
+ $rev['sha1'] = wfBaseConvert( $row->ar_sha1, 36, 16, 40 );
+ } else {
+ $rev['sha1'] = '';
+ }
}
}
if ( $fld_content ) {
- ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+ if ( $row->ar_deleted & Revision::DELETED_TEXT ) {
+ $rev['texthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
+ if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
+ // Pre-1.5 ar_text row (if condition from Revision::newFromArchiveRow)
+ ApiResult::setContent( $rev, Revision::getRevisionText( $row, 'ar_' ) );
+ } else {
+ ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+ }
+ }
+ }
+
+ if ( $fld_tags ) {
+ if ( $row->ts_tags ) {
+ $tags = explode( ',', $row->ts_tags );
+ $this->getResult()->setIndexedTagName( $tags, 'tag' );
+ $rev['tags'] = $tags;
+ } else {
+ $rev['tags'] = array();
+ }
+ }
+
+ if ( $anyHidden && ( $row->ar_deleted & Revision::DELETED_RESTRICTED ) ) {
+ $rev['suppressed'] = '';
}
if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
@@ -259,6 +394,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
$a['revisions'] = array( $rev );
$result->setIndexedTagName( $a['revisions'], 'rev' );
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
ApiQueryBase::addTitleInfo( $a, $title );
if ( $fld_token ) {
$a['token'] = $token;
@@ -272,10 +408,11 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
if ( !$fit ) {
if ( $mode == 'all' || $mode == 'revs' ) {
- $this->setContinueEnumParameter( 'continue', intval( $row->ar_namespace ) . '|' .
- $row->ar_title . '|' . $row->ar_timestamp );
+ $this->setContinueEnumParameter( 'continue',
+ "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
} else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ar_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
}
break;
}
@@ -303,6 +440,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'prefix' => null,
'continue' => null,
'unique' => false,
+ 'tag' => null,
'user' => array(
ApiBase::PARAM_TYPE => 'user'
),
@@ -333,7 +471,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'len',
'sha1',
'content',
- 'token'
+ 'token',
+ 'tags'
),
ApiBase::PARAM_ISMULTI => true
),
@@ -361,57 +500,37 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
' len - Adds the length (bytes) of the revision',
' sha1 - Adds the SHA-1 (base 16) of the revision',
' content - Adds the content of the revision',
- ' token - Gives the edit token',
+ ' token - DEPRECATED! Gives the edit token',
+ ' tags - Tags for the revision',
),
'namespace' => 'Only list pages in this namespace (3)',
'user' => 'Only list revisions by this user',
'excludeuser' => 'Don\'t list revisions by this user',
- 'continue' => 'When more results are available, use this to continue (1, 3)',
+ 'continue' => 'When more results are available, use this to continue',
'unique' => 'List only one revision for each page (3)',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'token' => array(
- 'token' => 'string'
- )
+ 'tag' => 'Only list revisions tagged with this tag',
);
}
public function getDescription() {
$p = $this->getModulePrefix();
+
return array(
'List deleted revisions.',
'Operates in three modes:',
- ' 1) List deleted revisions for the given title(s), sorted by timestamp',
- ' 2) List deleted contributions for the given user, sorted by timestamp (no titles specified)',
- " 3) List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, {$p}user not set)",
+ ' 1) List deleted revisions for the given title(s), sorted by timestamp.',
+ ' 2) List deleted contributions for the given user, sorted by timestamp (no titles specified).',
+ ' 3) List all deleted revisions in the given namespace, sorted by title and timestamp',
+ " (no titles specified, {$p}user not set).",
'Certain parameters only apply to some modes and are ignored in others.',
- 'For instance, a parameter marked (1) only applies to mode 1 and is ignored in modes 2 and 3',
+ 'For instance, a parameter marked (1) only applies to mode 1 and is ignored in modes 2 and 3.',
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision information' ),
- array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
- array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision content' ),
- array( 'code' => 'badparams', 'info' => "The 'from' parameter cannot be used in modes 1 or 2" ),
- array( 'code' => 'badparams', 'info' => "The 'to' parameter cannot be used in modes 1 or 2" ),
- array( 'code' => 'badparams', 'info' => "The 'prefix' parameter cannot be used in modes 1 or 2" ),
- array( 'code' => 'badparams', 'info' => "The 'start' parameter cannot be used in mode 3" ),
- array( 'code' => 'badparams', 'info' => "The 'end' parameter cannot be used in mode 3" ),
- ) );
- }
-
public function getExamples() {
return array(
- 'api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content'
+ 'api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&' .
+ 'drprop=user|comment|content'
=> 'List the last deleted revisions of Main Page and Talk:Main Page, with content (mode 1)',
'api.php?action=query&list=deletedrevs&druser=Bob&drlimit=50'
=> 'List the last 50 deleted contributions by Bob (mode 2)',
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index 0311fa7f..6d836cd5 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -31,7 +31,7 @@
*/
class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'df' );
}
@@ -95,7 +95,8 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$sha1s[$file->getName()] = $file->getSha1();
}
- // find all files with the hashes, result format is: array( hash => array( dup1, dup2 ), hash1 => ... )
+ // find all files with the hashes, result format is:
+ // array( hash => array( dup1, dup2 ), hash1 => ... )
$filesToFindBySha1s = array_unique( array_values( $sha1s ) );
if ( $params['localonly'] ) {
$filesBySha1s = RepoGroup::singleton()->getLocalRepo()->findBySha1s( $filesToFindBySha1s );
@@ -187,19 +188,8 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'name' => 'string',
- 'user' => 'string',
- 'timestamp' => 'timestamp',
- 'shared' => 'boolean',
- )
- );
- }
-
public function getDescription() {
- return 'List all files that are duplicates of the given file(s) based on hash values';
+ return 'List all files that are duplicates of the given file(s) based on hash values.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index 456e87ba..faabb920 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -29,7 +29,7 @@
*/
class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'eu' );
}
@@ -46,7 +46,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -59,9 +59,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$this->addOption( 'USE INDEX', 'el_index' );
$this->addWhere( 'page_id=el_from' );
- global $wgMiserMode;
$miser_ns = array();
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$miser_ns = $params['namespace'];
} else {
$this->addWhereFld( 'page_namespace', $params['namespace'] );
@@ -101,8 +100,9 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$result = $this->getResult();
$count = 0;
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'offset', $offset + $limit );
break;
}
@@ -140,7 +140,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ),
- $this->getModulePrefix() );
+ $this->getModulePrefix() );
}
}
@@ -186,6 +186,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$protocols[] = substr( $p, 0, strpos( $p, ':' ) );
}
}
+
return $protocols;
}
@@ -207,7 +208,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
public function getParamDescription() {
- global $wgMiserMode;
$p = $this->getModulePrefix();
$desc = array(
'prop' => array(
@@ -221,13 +221,14 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
"Protocol of the URL. If empty and {$p}query set, the protocol is http.",
"Leave both this and {$p}query empty to list all external links"
),
- 'query' => 'Search string without protocol. See [[Special:LinkSearch]]. Leave empty to list all external links',
+ 'query' => 'Search string without protocol. See [[Special:LinkSearch]]. ' .
+ 'Leave empty to list all external links',
'namespace' => 'The page namespace(s) to enumerate.',
'limit' => 'How many pages to return.',
'expandurl' => 'Expand protocol-relative URLs with the canonical protocol',
);
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$desc['namespace'] = array(
$desc['namespace'],
"NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
@@ -238,29 +239,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
return $desc;
}
- public function getResultProperties() {
- return array(
- 'ids' => array(
- 'pageid' => 'integer'
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'url' => array(
- 'url' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Enumerate pages that contain a given URL';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'bad_query', 'info' => 'Invalid query' ),
- ) );
+ return 'Enumerate pages that contain a given URL.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index 583ef697..95666354 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -31,7 +31,7 @@
*/
class ApiQueryExternalLinks extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'el' );
}
@@ -127,6 +127,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'limit' => 'How many links to return',
'offset' => 'When more results are available, use this to continue',
@@ -134,32 +135,20 @@ class ApiQueryExternalLinks extends ApiQueryBase {
"Protocol of the URL. If empty and {$p}query set, the protocol is http.",
"Leave both this and {$p}query empty to list all external links"
),
- 'query' => 'Search string without protocol. Useful for checking whether a certain page contains a certain external url',
+ 'query' => 'Search string without protocol. Useful for checking ' .
+ 'whether a certain page contains a certain external url',
'expandurl' => 'Expand protocol-relative URLs with the canonical protocol',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- '*' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Returns all external URLs (not interwikis) from the given page(s)';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'bad_query', 'info' => 'Invalid query' ),
- ) );
+ return 'Returns all external URLs (not interwikis) from the given page(s).';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=extlinks&titles=Main%20Page' => 'Get a list of external links on the [[Main Page]]',
+ 'api.php?action=query&prop=extlinks&titles=Main%20Page'
+ => 'Get a list of external links on the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryFileRepoInfo.php b/includes/api/ApiQueryFileRepoInfo.php
index 3a353533..d1600efe 100644
--- a/includes/api/ApiQueryFileRepoInfo.php
+++ b/includes/api/ApiQueryFileRepoInfo.php
@@ -29,16 +29,13 @@
*/
class ApiQueryFileRepoInfo extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'fri' );
}
protected function getInitialisedRepoGroup() {
$repoGroup = RepoGroup::singleton();
-
- if ( !$repoGroup->reposInitialised ) {
- $repoGroup->initialiseRepos();
- }
+ $repoGroup->initialiseRepos();
return $repoGroup;
}
@@ -55,7 +52,7 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
$repos[] = array_intersect_key( $repo->getInfo(), $props );
} );
- $repos[] = array_intersect_key( $repoGroup->localRepo->getInfo(), $props );
+ $repos[] = array_intersect_key( $repoGroup->getLocalRepo()->getInfo(), $props );
$result = $this->getResult();
$result->setIndexedTagName( $repos, 'repo' );
@@ -86,16 +83,19 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
$props = array_merge( $props, array_keys( $repo->getInfo() ) );
} );
- return array_values( array_unique( array_merge( $props, array_keys( $repoGroup->localRepo->getInfo() ) ) ) );
+ return array_values( array_unique( array_merge(
+ $props,
+ array_keys( $repoGroup->getLocalRepo()->getInfo() )
+ ) ) );
}
public function getParamDescription() {
- $p = $this->getModulePrefix();
return array(
'prop' => array(
'Which repository properties to get (there may be more available on some wikis):',
' apiurl - URL to the repository API - helpful for getting image info from the host.',
- ' name - The key of the repository - used in e.g. $wgForeignFileRepos and imageinfo return values.',
+ ' name - The key of the repository - used in e.g. ' .
+ '$wgForeignFileRepos and imageinfo return values.',
' displayname - The human-readable name of the repository wiki.',
' rooturl - Root URL for image paths.',
' local - Whether that repository is the local one or not.',
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
index f53cd386..f047d8d4 100644
--- a/includes/api/ApiQueryFilearchive.php
+++ b/includes/api/ApiQueryFilearchive.php
@@ -33,7 +33,7 @@
*/
class ApiQueryFilearchive extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'fa' );
}
@@ -41,7 +41,10 @@ class ApiQueryFilearchive extends ApiQueryBase {
$user = $this->getUser();
// Before doing anything at all, let's check permissions
if ( !$user->isAllowed( 'deletedhistory' ) ) {
- $this->dieUsage( 'You don\'t have permission to view deleted file information', 'permissiondenied' );
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted file information',
+ 'permissiondenied'
+ );
}
$db = $this->getDB();
@@ -63,9 +66,9 @@ class ApiQueryFilearchive extends ApiQueryBase {
$this->addTables( 'filearchive' );
- $this->addFields( array( 'fa_name', 'fa_deleted' ) );
+ $this->addFields( ArchivedFile::selectFields() );
+ $this->addFields( array( 'fa_id', 'fa_name', 'fa_timestamp', 'fa_deleted' ) );
$this->addFieldsIf( 'fa_sha1', $fld_sha1 );
- $this->addFieldsIf( 'fa_timestamp', $fld_timestamp );
$this->addFieldsIf( array( 'fa_user', 'fa_user_text' ), $fld_user );
$this->addFieldsIf( array( 'fa_height', 'fa_width', 'fa_size' ), $fld_dimensions || $fld_size );
$this->addFieldsIf( 'fa_description', $fld_description );
@@ -77,22 +80,29 @@ class ApiQueryFilearchive extends ApiQueryBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- $this->dieContinueUsageIf( count( $cont ) != 1 );
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$cont_from = $db->addQuotes( $cont[0] );
- $this->addWhere( "fa_name $op= $cont_from" );
+ $cont_timestamp = $db->addQuotes( $db->timestamp( $cont[1] ) );
+ $cont_id = (int)$cont[2];
+ $this->dieContinueUsageIf( $cont[2] !== (string)$cont_id );
+ $this->addWhere( "fa_name $op $cont_from OR " .
+ "(fa_name = $cont_from AND " .
+ "(fa_timestamp $op $cont_timestamp OR " .
+ "(fa_timestamp = $cont_timestamp AND " .
+ "fa_id $op= $cont_id )))"
+ );
}
// Image filters
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- if ( !is_null( $params['continue'] ) ) {
- $from = $params['continue'];
- }
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $params['from'] === null ? null : $this->titlePartToKey( $params['from'], NS_FILE ) );
+ $to = ( $params['to'] === null ? null : $this->titlePartToKey( $params['to'], NS_FILE ) );
$this->addWhereRange( 'fa_name', $dir, $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'fa_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'fa_name' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], NS_FILE ),
+ $db->anyString() ) );
}
$sha1Set = isset( $params['sha1'] );
@@ -116,20 +126,26 @@ class ApiQueryFilearchive extends ApiQueryBase {
}
}
- if ( !$user->isAllowed( 'suppressrevision' ) ) {
- // Filter out revisions that the user is not allowed to see. There
- // is no way to indicate that we have skipped stuff because the
- // continuation parameter is fa_name
-
- // Note that this field is unindexed. This should however not be
- // a big problem as files with fa_deleted are rare
- $this->addWhereFld( 'fa_deleted', 0 );
+ // Exclude files this user can't view.
+ if ( !$user->isAllowed( 'deletedtext' ) ) {
+ $bitmask = File::DELETED_FILE;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" );
}
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
$sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
- $this->addOption( 'ORDER BY', 'fa_name' . $sort );
+ $this->addOption( 'ORDER BY', array(
+ 'fa_name' . $sort,
+ 'fa_timestamp' . $sort,
+ 'fa_id' . $sort,
+ ) );
$res = $this->select( __METHOD__ );
@@ -137,26 +153,41 @@ class ApiQueryFilearchive extends ApiQueryBase {
$result = $this->getResult();
foreach ( $res as $row ) {
if ( ++$count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', $row->fa_name );
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ $this->setContinueEnumParameter(
+ 'continue', "$row->fa_name|$row->fa_timestamp|$row->fa_id"
+ );
break;
}
$file = array();
+ $file['id'] = $row->fa_id;
$file['name'] = $row->fa_name;
$title = Title::makeTitle( NS_FILE, $row->fa_name );
self::addTitleInfo( $file, $title );
+ if ( $fld_description &&
+ Revision::userCanBitfield( $row->fa_deleted, File::DELETED_COMMENT, $user )
+ ) {
+ $file['description'] = $row->fa_description;
+ if ( isset( $prop['parseddescription'] ) ) {
+ $file['parseddescription'] = Linker::formatComment(
+ $row->fa_description, $title );
+ }
+ }
+ if ( $fld_user &&
+ Revision::userCanBitfield( $row->fa_deleted, File::DELETED_USER, $user )
+ ) {
+ $file['userid'] = $row->fa_user;
+ $file['user'] = $row->fa_user_text;
+ }
if ( $fld_sha1 ) {
$file['sha1'] = wfBaseConvert( $row->fa_sha1, 36, 16, 40 );
}
if ( $fld_timestamp ) {
$file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
}
- if ( $fld_user ) {
- $file['userid'] = $row->fa_user;
- $file['user'] = $row->fa_user_text;
- }
if ( $fld_size || $fld_dimensions ) {
$file['size'] = $row->fa_size;
@@ -168,20 +199,13 @@ class ApiQueryFilearchive extends ApiQueryBase {
$file['height'] = $row->fa_height;
$file['width'] = $row->fa_width;
}
- if ( $fld_description ) {
- $file['description'] = $row->fa_description;
- if ( isset( $prop['parseddescription'] ) ) {
- $file['parseddescription'] = Linker::formatComment(
- $row->fa_description, $title );
- }
- }
if ( $fld_mediatype ) {
$file['mediatype'] = $row->fa_media_type;
}
if ( $fld_metadata ) {
$file['metadata'] = $row->fa_metadata
- ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
- : null;
+ ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
+ : null;
}
if ( $fld_bitdepth ) {
$file['bitdepth'] = $row->fa_bits;
@@ -209,7 +233,9 @@ class ApiQueryFilearchive extends ApiQueryBase {
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', $row->fa_name );
+ $this->setContinueEnumParameter(
+ 'continue', "$row->fa_name|$row->fa_timestamp|$row->fa_id"
+ );
break;
}
}
@@ -275,7 +301,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
' sha1 - Adds SHA-1 hash for the image',
' timestamp - Adds timestamp for the uploaded version',
' user - Adds user who uploaded the image version',
- ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)',
+ ' size - Adds the size of the image in bytes and the height, ' .
+ 'width and page count (if applicable)',
' dimensions - Alias for size',
' description - Adds description the image version',
' parseddescription - Parse the description on the version',
@@ -288,81 +315,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'name' => 'string',
- 'ns' => 'namespace',
- 'title' => 'string',
- 'filehidden' => 'boolean',
- 'commenthidden' => 'boolean',
- 'userhidden' => 'boolean',
- 'suppressed' => 'boolean'
- ),
- 'sha1' => array(
- 'sha1' => 'string'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'user' => array(
- 'userid' => 'integer',
- 'user' => 'string'
- ),
- 'size' => array(
- 'size' => 'integer',
- 'pagecount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'height' => 'integer',
- 'width' => 'integer'
- ),
- 'dimensions' => array(
- 'size' => 'integer',
- 'pagecount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'height' => 'integer',
- 'width' => 'integer'
- ),
- 'description' => array(
- 'description' => 'string'
- ),
- 'parseddescription' => array(
- 'description' => 'string',
- 'parseddescription' => 'string'
- ),
- 'metadata' => array(
- 'metadata' => 'string'
- ),
- 'bitdepth' => array(
- 'bitdepth' => 'integer'
- ),
- 'mime' => array(
- 'mime' => 'string'
- ),
- 'mediatype' => array(
- 'mediatype' => 'string'
- ),
- 'archivename' => array(
- 'archivename' => 'string'
- ),
- );
- }
-
public function getDescription() {
- return 'Enumerate all deleted files sequentially';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted file information' ),
- array( 'code' => 'hashsearchdisabled', 'info' => 'Search by hash disabled in Miser Mode' ),
- array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
- array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
- ) );
+ return 'Enumerate all deleted files sequentially.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
index ebae3e76..b5aa45bf 100644
--- a/includes/api/ApiQueryIWBacklinks.php
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -31,7 +31,7 @@
*/
class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'iwbl' );
}
@@ -44,7 +44,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function run( $resultPageSet = null ) {
@@ -92,14 +92,14 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', array(
'iwl_title' . $sort,
'iwl_from' . $sort
- ));
+ ) );
}
} else {
$this->addOption( 'ORDER BY', array(
'iwl_prefix' . $sort,
'iwl_title' . $sort,
'iwl_from' . $sort
- ));
+ ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -111,10 +111,15 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // Continue string preserved in case the redirect query doesn't pass the limit
- $this->setContinueEnumParameter( 'continue', "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}" );
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ // Continue string preserved in case the redirect query doesn't
+ // pass the limit
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}"
+ );
break;
}
@@ -140,7 +145,10 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $entry );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}" );
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}"
+ );
break;
}
}
@@ -202,37 +210,14 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'pageid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string',
- 'redirect' => 'boolean'
- ),
- 'iwprefix' => array(
- 'iwprefix' => 'string'
- ),
- 'iwtitle' => array(
- 'iwtitle' => 'string'
- )
- );
- }
-
public function getDescription() {
return array( 'Find all pages that link to the given interwiki link.',
'Can be used to find all links with a prefix, or',
'all links to a title (with a given prefix).',
- 'Using neither parameter is effectively "All IW Links"',
+ 'Using neither parameter is effectively "All IW Links".',
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'prefix' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&list=iwbacklinks&iwbltitle=Test&iwblprefix=wikibooks',
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
index be539311..a185ee24 100644
--- a/includes/api/ApiQueryIWLinks.php
+++ b/includes/api/ApiQueryIWLinks.php
@@ -32,7 +32,7 @@
*/
class ApiQueryIWLinks extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'iw' );
}
@@ -42,11 +42,19 @@ class ApiQueryIWLinks extends ApiQueryBase {
}
$params = $this->extractRequestParams();
+ $prop = array_flip( (array)$params['prop'] );
if ( isset( $params['title'] ) && !isset( $params['prefix'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'prefix' ) );
}
+ // Handle deprecated param
+ $this->requireMaxOneParameter( $params, 'url', 'prop' );
+ if ( $params['url'] ) {
+ $this->logFeatureUsage( 'prop=iwlinks&iwurl' );
+ $prop = array( 'url' => 1 );
+ }
+
$this->addFields( array(
'iwl_from',
'iwl_prefix',
@@ -81,9 +89,9 @@ class ApiQueryIWLinks extends ApiQueryBase {
$this->addOption( 'ORDER BY', 'iwl_from' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'iwl_from' . $sort,
- 'iwl_title' . $sort
- ));
+ 'iwl_from' . $sort,
+ 'iwl_title' . $sort
+ ) );
}
} else {
// Don't order by iwl_from if it's constant in the WHERE clause
@@ -91,10 +99,10 @@ class ApiQueryIWLinks extends ApiQueryBase {
$this->addOption( 'ORDER BY', 'iwl_prefix' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'iwl_from' . $sort,
- 'iwl_prefix' . $sort,
- 'iwl_title' . $sort
- ));
+ 'iwl_from' . $sort,
+ 'iwl_prefix' . $sort,
+ 'iwl_title' . $sort
+ ) );
}
}
@@ -106,12 +114,15 @@ class ApiQueryIWLinks extends ApiQueryBase {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}" );
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}"
+ );
break;
}
$entry = array( 'prefix' => $row->iwl_prefix );
- if ( $params['url'] ) {
+ if ( isset( $prop['url'] ) ) {
$title = Title::newFromText( "{$row->iwl_prefix}:{$row->iwl_title}" );
if ( $title ) {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
@@ -121,7 +132,10 @@ class ApiQueryIWLinks extends ApiQueryBase {
ApiResult::setContent( $entry, $row->iwl_title );
$fit = $this->addPageSubItem( $row->iwl_from, $entry );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}" );
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}"
+ );
break;
}
}
@@ -133,7 +147,16 @@ class ApiQueryIWLinks extends ApiQueryBase {
public function getAllowedParams() {
return array(
- 'url' => false,
+ 'url' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
+ 'url',
+ )
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -156,7 +179,11 @@ class ApiQueryIWLinks extends ApiQueryBase {
public function getParamDescription() {
return array(
- 'url' => 'Whether to get the full URL',
+ 'prop' => array(
+ 'Which additional properties to get for each interlanguage link',
+ ' url - Adds the full URL',
+ ),
+ 'url' => "Whether to get the full URL (Cannot be used with {$this->getModulePrefix()}prop)",
'limit' => 'How many interwiki links to return',
'continue' => 'When more results are available, use this to continue',
'prefix' => 'Prefix for the interwiki',
@@ -165,32 +192,14 @@ class ApiQueryIWLinks extends ApiQueryBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'prefix' => 'string',
- 'url' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- '*' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Returns all interwiki links from the given page(s)';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'prefix' ),
- ) );
+ return 'Returns all interwiki links from the given page(s).';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=iwlinks&titles=Main%20Page' => 'Get interwiki links from the [[Main Page]]',
+ 'api.php?action=query&prop=iwlinks&titles=Main%20Page'
+ => 'Get interwiki links from the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index 0ea28684..945374b1 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -33,9 +33,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
const TRANSFORM_LIMIT = 50;
private static $transformCount = 0;
- public function __construct( $query, $moduleName, $prefix = 'ii' ) {
- // We allow a subclass to override the prefix, to create a related API module.
- // Some other parts of MediaWiki construct this with a null $prefix, which used to be ignored when this only took two arguments
+ public function __construct( ApiQuery $query, $moduleName, $prefix = 'ii' ) {
+ // We allow a subclass to override the prefix, to create a related API
+ // module. Some other parts of MediaWiki construct this with a null
+ // $prefix, which used to be ignored when this only took two arguments
if ( is_null( $prefix ) ) {
$prefix = 'ii';
}
@@ -49,6 +50,14 @@ class ApiQueryImageInfo extends ApiQueryBase {
$scale = $this->getScale( $params );
+ $opts = array(
+ 'version' => $params['metadataversion'],
+ 'language' => $params['extmetadatalanguage'],
+ 'multilang' => $params['extmetadatamultilang'],
+ 'extmetadatafilter' => $params['extmetadatafilter'],
+ 'revdelUser' => $this->getUser(),
+ );
+
$pageIds = $this->getPageSet()->getAllTitlesByNamespace();
if ( !empty( $pageIds[NS_FILE] ) ) {
$titles = array_keys( $pageIds[NS_FILE] );
@@ -70,13 +79,21 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
}
- $result = $this->getResult();
- //search only inside the local repo
+ $user = $this->getUser();
+ $findTitles = array_map( function ( $title ) use ( $user ) {
+ return array(
+ 'title' => $title,
+ 'private' => $user,
+ );
+ }, $titles );
+
if ( $params['localonly'] ) {
- $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $titles );
+ $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $findTitles );
} else {
- $images = RepoGroup::singleton()->findFiles( $titles );
+ $images = RepoGroup::singleton()->findFiles( $findTitles );
}
+
+ $result = $this->getResult();
foreach ( $titles as $title ) {
$pageId = $pageIds[NS_FILE][$title];
$start = $title === $fromTitle ? $fromTimestamp : $params['start'];
@@ -146,7 +163,9 @@ class ApiQueryImageInfo extends ApiQueryBase {
$fit = $this->addPageSubItem( $pageId,
self::getInfo( $img, $prop, $result,
- $finalThumbParams, $params['metadataversion'] ) );
+ $finalThumbParams, $opts
+ )
+ );
if ( !$fit ) {
if ( count( $pageIds[NS_FILE] ) == 1 ) {
// See the 'the user is screwed' comment above
@@ -167,7 +186,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
/** @var $oldie File */
foreach ( $oldies as $oldie ) {
if ( ++$count > $params['limit'] ) {
- // We've reached the extra one which shows that there are additional pages to be had. Stop here...
+ // We've reached the extra one which shows that there are
+ // additional pages to be had. Stop here...
// Only set a query-continue if there was only one title
if ( count( $pageIds[NS_FILE] ) == 1 ) {
$this->setContinueEnumParameter( 'start',
@@ -178,7 +198,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$fit = self::getTransformCount() < self::TRANSFORM_LIMIT &&
$this->addPageSubItem( $pageId,
self::getInfo( $oldie, $prop, $result,
- $finalThumbParams, $params['metadataversion']
+ $finalThumbParams, $opts
)
);
if ( !$fit ) {
@@ -202,7 +222,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* From parameters, construct a 'scale' array
* @param array $params Parameters passed to api.
- * @return Array or Null: key-val array of 'width' and 'height', or null
+ * @return array|null Key-val array of 'width' and 'height', or null
*/
public function getScale( $params ) {
$p = $this->getModulePrefix();
@@ -217,9 +237,11 @@ class ApiQueryImageInfo extends ApiQueryBase {
$scale = array();
$scale['height'] = $params['urlheight'];
} else {
- $scale = null;
if ( $params['urlparam'] ) {
- $this->dieUsage( "{$p}urlparam requires {$p}urlwidth", "urlparam_no_width" );
+ // Audio files might not have a width/height.
+ $scale = array();
+ } else {
+ $scale = null;
}
}
@@ -231,26 +253,29 @@ class ApiQueryImageInfo extends ApiQueryBase {
* We do this later than getScale, since we need the image
* to know which handler, since handlers can make their own parameters.
* @param File $image Image that params are for.
- * @param array $thumbParams thumbnail parameters from getScale
- * @param string $otherParams of otherParams (iiurlparam).
- * @return Array of parameters for transform.
+ * @param array $thumbParams Thumbnail parameters from getScale
+ * @param string $otherParams String of otherParams (iiurlparam).
+ * @return array Array of parameters for transform.
*/
protected function mergeThumbParams( $image, $thumbParams, $otherParams ) {
- global $wgThumbLimits;
-
+ if ( $thumbParams === null ) {
+ // No scaling requested
+ return null;
+ }
if ( !isset( $thumbParams['width'] ) && isset( $thumbParams['height'] ) ) {
// We want to limit only by height in this situation, so pass the
// image's full width as the limiting width. But some file types
// don't have a width of their own, so pick something arbitrary so
// thumbnailing the default icon works.
if ( $image->getWidth() <= 0 ) {
- $thumbParams['width'] = max( $wgThumbLimits );
+ $thumbParams['width'] = max( $this->getConfig()->get( 'ThumbLimits' ) );
} else {
$thumbParams['width'] = $image->getWidth();
}
}
if ( !$otherParams ) {
+ $this->checkParameterNormalise( $image, $thumbParams );
return $thumbParams;
}
$p = $this->getModulePrefix();
@@ -259,6 +284,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( !$h ) {
$this->setWarning( 'Could not create thumbnail because ' .
$image->getName() . ' does not have an associated image handler' );
+
return $thumbParams;
}
@@ -270,13 +296,15 @@ class ApiQueryImageInfo extends ApiQueryBase {
// handlers.
$this->setWarning( "Could not parse {$p}urlparam for " . $image->getName()
. '. Using only width and height' );
+ $this->checkParameterNormalise( $image, $thumbParams );
return $thumbParams;
}
- if ( isset( $paramList['width'] ) ) {
+ if ( isset( $paramList['width'] ) && isset( $thumbParams['width'] ) ) {
if ( intval( $paramList['width'] ) != intval( $thumbParams['width'] ) ) {
$this->setWarning( "Ignoring width value set in {$p}urlparam ({$paramList['width']}) "
- . "in favor of width value derived from {$p}urlwidth/{$p}urlheight ({$thumbParams['width']})" );
+ . "in favor of width value derived from {$p}urlwidth/{$p}urlheight "
+ . "({$thumbParams['width']})" );
}
}
@@ -286,20 +314,65 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
}
- return $thumbParams + $paramList;
+ $finalParams = $thumbParams + $paramList;
+ $this->checkParameterNormalise( $image, $finalParams );
+ return $finalParams;
+ }
+
+ /**
+ * Verify that the final image parameters can be normalised.
+ *
+ * This doesn't use the normalised parameters, since $file->transform
+ * expects the pre-normalised parameters, but doing the normalisation
+ * allows us to catch certain error conditions early (such as missing
+ * required parameter).
+ *
+ * @param $image File
+ * @param $finalParams array List of parameters to transform image with
+ */
+ protected function checkParameterNormalise( $image, $finalParams ) {
+ $h = $image->getHandler();
+ if ( !$h ) {
+ return;
+ }
+ // Note: normaliseParams modifies the array in place, but we aren't interested
+ // in the actual normalised version, only if we can actually normalise them,
+ // so we use the functions scope to throw away the normalisations.
+ if ( !$h->normaliseParams( $image, $finalParams ) ) {
+ $this->dieUsage( "Could not normalise image parameters for " . $image->getName(), "urlparamnormal" );
+ }
}
/**
* Get result information for an image revision
*
- * @param $file File object
- * @param array $prop of properties to get (in the keys)
- * @param $result ApiResult object
- * @param array $thumbParams containing 'width' and 'height' items, or null
- * @param string $version Version of image metadata (for things like jpeg which have different versions).
- * @return Array: result array
+ * @param File $file
+ * @param array $prop Array of properties to get (in the keys)
+ * @param ApiResult $result
+ * @param array $thumbParams Containing 'width' and 'height' items, or null
+ * @param array|bool|string $opts Options for data fetching.
+ * This is an array consisting of the keys:
+ * 'version': The metadata version for the metadata option
+ * 'language': The language for extmetadata property
+ * 'multilang': Return all translations in extmetadata property
+ * 'revdelUser': User to use when checking whether to show revision-deleted fields.
+ * @return array Result array
*/
- static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) {
+ static function getInfo( $file, $prop, $result, $thumbParams = null, $opts = false ) {
+ global $wgContLang;
+
+ $anyHidden = false;
+
+ if ( !$opts || is_string( $opts ) ) {
+ $opts = array(
+ 'version' => $opts ?: 'latest',
+ 'language' => $wgContLang,
+ 'multilang' => false,
+ 'extmetadatafilter' => array(),
+ 'revdelUser' => null,
+ );
+ }
+ $version = $opts['version'];
$vals = array();
// Timestamp is shown even if the file is revdelete'd in interface
// so do same here.
@@ -307,13 +380,27 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
}
+ // Handle external callers who don't pass revdelUser
+ if ( isset( $opts['revdelUser'] ) && $opts['revdelUser'] ) {
+ $revdelUser = $opts['revdelUser'];
+ $canShowField = function ( $field ) use ( $file, $revdelUser ) {
+ return $file->userCan( $field, $revdelUser );
+ };
+ } else {
+ $canShowField = function ( $field ) use ( $file ) {
+ return !$file->isDeleted( $field );
+ };
+ }
+
$user = isset( $prop['user'] );
$userid = isset( $prop['userid'] );
if ( $user || $userid ) {
if ( $file->isDeleted( File::DELETED_USER ) ) {
$vals['userhidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( $canShowField( File::DELETED_USER ) ) {
if ( $user ) {
$vals['user'] = $file->getUser();
}
@@ -337,6 +424,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $pageCount !== false ) {
$vals['pagecount'] = $pageCount;
}
+
+ // length as in how many seconds long a video is.
+ $length = $file->getLength();
+ if ( $length ) {
+ // Call it duration, because "length" can be ambiguous.
+ $vals['duration'] = (float)$length;
+ }
}
$pcomment = isset( $prop['parsedcomment'] );
@@ -345,34 +439,53 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $pcomment || $comment ) {
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( $canShowField( File::DELETED_COMMENT ) ) {
if ( $pcomment ) {
$vals['parsedcomment'] = Linker::formatComment(
- $file->getDescription(), $file->getTitle() );
+ $file->getDescription( File::RAW ), $file->getTitle() );
}
if ( $comment ) {
- $vals['comment'] = $file->getDescription();
+ $vals['comment'] = $file->getDescription( File::RAW );
}
}
}
+ $canonicaltitle = isset( $prop['canonicaltitle'] );
$url = isset( $prop['url'] );
$sha1 = isset( $prop['sha1'] );
$meta = isset( $prop['metadata'] );
+ $extmetadata = isset( $prop['extmetadata'] );
+ $commonmeta = isset( $prop['commonmetadata'] );
$mime = isset( $prop['mime'] );
$mediatype = isset( $prop['mediatype'] );
$archive = isset( $prop['archivename'] );
$bitdepth = isset( $prop['bitdepth'] );
$uploadwarning = isset( $prop['uploadwarning'] );
- if ( ( $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth )
- && $file->isDeleted( File::DELETED_FILE ) ) {
+ if ( $uploadwarning ) {
+ $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) );
+ }
+
+ if ( $file->isDeleted( File::DELETED_FILE ) ) {
$vals['filehidden'] = '';
+ $anyHidden = true;
+ }
+
+ if ( $anyHidden && $file->isDeleted( File::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = true;
+ }
+ if ( !$canShowField( File::DELETED_FILE ) ) {
//Early return, tidier than indenting all following things one level
return $vals;
}
+ if ( $canonicaltitle ) {
+ $vals['canonicaltitle'] = $file->getTitle()->getPrefixedText();
+ }
+
if ( $url ) {
if ( !is_null( $thumbParams ) ) {
$mto = $file->transform( $thumbParams );
@@ -416,6 +529,26 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
$vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
}
+ if ( $commonmeta ) {
+ $metaArray = $file->getCommonMetaArray();
+ $vals['commonmetadata'] = $metaArray ? self::processMetaData( $metaArray, $result ) : array();
+ }
+
+ if ( $extmetadata ) {
+ // Note, this should return an array where all the keys
+ // start with a letter, and all the values are strings.
+ // Thus there should be no issue with format=xml.
+ $format = new FormatMetadata;
+ $format->setSingleLanguage( !$opts['multilang'] );
+ $format->getContext()->setLanguage( $opts['language'] );
+ $extmetaArray = $format->fetchExtendedMetadata( $file );
+ if ( $opts['extmetadatafilter'] ) {
+ $extmetaArray = array_intersect_key(
+ $extmetaArray, array_flip( $opts['extmetadatafilter'] )
+ );
+ }
+ $vals['extmetadata'] = $extmetaArray;
+ }
if ( $mime ) {
$vals['mime'] = $file->getMimeType();
@@ -433,10 +566,6 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['bitdepth'] = $file->getBitDepth();
}
- if ( $uploadwarning ) {
- $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) );
- }
-
return $vals;
}
@@ -445,7 +574,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
*
* If this is >= TRANSFORM_LIMIT, you should probably stop processing images.
*
- * @return integer count
+ * @return int Count
*/
static function getTransformCount() {
return self::$transformCount;
@@ -453,9 +582,9 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
*
- * @param $metadata Array
- * @param $result ApiResult
- * @return Array
+ * @param array $metadata
+ * @param ApiResult $result
+ * @return array
*/
public static function processMetaData( $metadata, $result ) {
$retval = array();
@@ -471,15 +600,20 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
}
$result->setIndexedTagName( $retval, 'metadata' );
+
return $retval;
}
public function getCacheMode( $params ) {
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
+
return 'public';
}
/**
- * @param $img File
+ * @param File $img
* @param null|string $start
* @return string
*/
@@ -487,10 +621,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $start === null ) {
$start = $img->getTimestamp();
}
+
return $img->getOriginalTitle()->getDBkey() . '|' . $start;
}
public function getAllowedParams() {
+ global $wgContLang;
+
return array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -522,6 +659,18 @@ class ApiQueryImageInfo extends ApiQueryBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_DFLT => '1',
),
+ 'extmetadatalanguage' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => $wgContLang->getCode(),
+ ),
+ 'extmetadatamultilang' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => false,
+ ),
+ 'extmetadatafilter' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'urlparam' => array(
ApiBase::PARAM_DFLT => '',
ApiBase::PARAM_TYPE => 'string',
@@ -536,7 +685,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
*
* @param array $filter List of properties to filter out
*
- * @return Array
+ * @return array
*/
public static function getPropertyNames( $filter = array() ) {
return array_diff( array_keys( self::getProperties() ), $filter );
@@ -555,18 +704,26 @@ class ApiQueryImageInfo extends ApiQueryBase {
'userid' => ' userid - Add the user ID that uploaded the image version',
'comment' => ' comment - Comment on the version',
'parsedcomment' => ' parsedcomment - Parse the comment on the version',
+ 'canonicaltitle' => ' canonicaltitle - Adds the canonical title of the image file',
'url' => ' url - Gives URL to the image and the description page',
- 'size' => ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)',
- 'dimensions' => ' dimensions - Alias for size', // For backwards compatibility with Allimages
+ 'size' => ' size - Adds the size of the image in bytes, ' .
+ 'its height and its width. Page count and duration are added if applicable',
+ 'dimensions' => ' dimensions - Alias for size', // B/C with Allimages
'sha1' => ' sha1 - Adds SHA-1 hash for the image',
'mime' => ' mime - Adds MIME type of the image',
'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail' .
' (requires url and param ' . $modulePrefix . 'urlwidth)',
'mediatype' => ' mediatype - Adds the media type of the image',
'metadata' => ' metadata - Lists Exif metadata for the version of the image',
- 'archivename' => ' archivename - Adds the file name of the archive version for non-latest versions',
+ 'commonmetadata' => ' commonmetadata - Lists file format generic metadata ' .
+ 'for the version of the image',
+ 'extmetadata' => ' extmetadata - Lists formatted metadata combined ' .
+ 'from multiple sources. Results are HTML formatted.',
+ 'archivename' => ' archivename - Adds the file name of the archive ' .
+ 'version for non-latest versions',
'bitdepth' => ' bitdepth - Adds the bit depth of the version',
- 'uploadwarning' => ' uploadwarning - Used by the Special:Upload page to get information about an existing file. Not intended for use outside MediaWiki core',
+ 'uploadwarning' => ' uploadwarning - Used by the Special:Upload page to ' .
+ 'get information about an existing file. Not intended for use outside MediaWiki core',
);
}
@@ -586,169 +743,53 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* Return the API documentation for the parameters.
- * @return Array parameter documentation.
+ * @return array Parameter documentation.
*/
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'prop' => self::getPropertyDescriptions( array(), $p ),
- 'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
+ 'urlwidth' => array(
+ "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
'For performance reasons if this option is used, ' .
- 'no more than ' . self::TRANSFORM_LIMIT . ' scaled images will be returned.' ),
+ 'no more than ' . self::TRANSFORM_LIMIT . ' scaled images will be returned.'
+ ),
'urlheight' => "Similar to {$p}urlwidth.",
- 'urlparam' => array( "A handler specific parameter string. For example, pdf's ",
- "might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ),
+ 'urlparam' => array(
+ "A handler specific parameter string. For example, pdf's ",
+ "might use 'page15-100px'."
+ ),
'limit' => 'How many image revisions to return per image',
'start' => 'Timestamp to start listing from',
'end' => 'Timestamp to stop listing at',
- 'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
- "Defaults to '1' for backwards compatibility" ),
- 'continue' => 'If the query response includes a continue value, use it here to get another page of results',
- 'localonly' => 'Look only for files in the local repository',
- );
- }
-
- public static function getResultPropertiesFiltered( $filter = array() ) {
- $props = array(
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'user' => array(
- 'userhidden' => 'boolean',
- 'user' => 'string',
- 'anon' => 'boolean'
- ),
- 'userid' => array(
- 'userhidden' => 'boolean',
- 'userid' => 'integer',
- 'anon' => 'boolean'
- ),
- 'size' => array(
- 'size' => 'integer',
- 'width' => 'integer',
- 'height' => 'integer',
- 'pagecount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'dimensions' => array(
- 'size' => 'integer',
- 'width' => 'integer',
- 'height' => 'integer',
- 'pagecount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'comment' => array(
- 'commenthidden' => 'boolean',
- 'comment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'parsedcomment' => array(
- 'commenthidden' => 'boolean',
- 'parsedcomment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'url' => array(
- 'filehidden' => 'boolean',
- 'thumburl' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'thumbwidth' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'thumbheight' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'thumberror' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'url' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'descriptionurl' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'sha1' => array(
- 'filehidden' => 'boolean',
- 'sha1' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'mime' => array(
- 'filehidden' => 'boolean',
- 'mime' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'thumbmime' => array(
- 'filehidden' => 'boolean',
- 'thumbmime' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'mediatype' => array(
- 'filehidden' => 'boolean',
- 'mediatype' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'archivename' => array(
- 'filehidden' => 'boolean',
- 'archivename' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'bitdepth' => array(
- 'filehidden' => 'boolean',
- 'bitdepth' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
+ 'metadataversion'
+ => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
+ "Defaults to '1' for backwards compatibility" ),
+ 'extmetadatalanguage' => array(
+ 'What language to fetch extmetadata in. This affects both which',
+ 'translation to fetch, if multiple are available, as well as how things',
+ 'like numbers and various values are formatted.'
),
+ 'extmetadatamultilang'
+ =>'If translations for extmetadata property are available, fetch all of them.',
+ 'extmetadatafilter'
+ => "If specified and non-empty, only these keys will be returned for {$p}prop=extmetadata",
+ 'continue' => 'If the query response includes a continue value, ' .
+ 'use it here to get another page of results',
+ 'localonly' => 'Look only for files in the local repository',
);
- return array_diff_key( $props, array_flip( $filter ) );
- }
-
- public function getResultProperties() {
- return self::getResultPropertiesFiltered();
}
public function getDescription() {
- return 'Returns image information and upload history';
- }
-
- public function getPossibleErrors() {
- $p = $this->getModulePrefix();
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => "{$p}urlwidth", 'info' => "{$p}urlheight cannot be used without {$p}urlwidth" ),
- array( 'code' => 'urlparam', 'info' => "Invalid value for {$p}urlparam" ),
- array( 'code' => 'urlparam_no_width', 'info' => "{$p}urlparam requires {$p}urlwidth" ),
- ) );
+ return 'Returns image information and upload history.';
}
public function getExamples() {
return array(
'api.php?action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo',
- 'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url',
+ 'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&' .
+ 'iiend=20071231235959&iiprop=timestamp|user|url',
);
}
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index f2bf0a7b..9bc3abed 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -32,7 +32,7 @@
*/
class ApiQueryImages extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'im' );
}
@@ -45,7 +45,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
@@ -79,9 +79,9 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', 'il_to' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'il_from' . $sort,
- 'il_to' . $sort
- ));
+ 'il_from' . $sort,
+ 'il_to' . $sort
+ ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -164,28 +164,22 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
return array(
'limit' => 'How many images to return',
'continue' => 'When more results are available, use this to continue',
- 'images' => 'Only list these images. Useful for checking whether a certain page has a certain Image.',
+ 'images' => 'Only list these images. Useful for checking whether a ' .
+ 'certain page has a certain Image.',
'dir' => 'The direction in which to list',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Returns all images contained on the given page(s)';
+ return 'Returns all images contained on the given page(s).';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=images&titles=Main%20Page' => 'Get a list of images used in the [[Main Page]]',
- 'api.php?action=query&generator=images&titles=Main%20Page&prop=info' => 'Get information about all images used in the [[Main Page]]',
+ 'api.php?action=query&prop=images&titles=Main%20Page'
+ => 'Get a list of images used in the [[Main Page]]',
+ 'api.php?action=query&generator=images&titles=Main%20Page&prop=info'
+ => 'Get information about all images used in the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index 017684ed..d7037e3a 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -42,35 +42,35 @@ class ApiQueryInfo extends ApiQueryBase {
private $pageRestrictions, $pageIsRedir, $pageIsNew, $pageTouched,
$pageLatest, $pageLength;
- private $protections, $watched, $watchers, $notificationtimestamps, $talkids, $subjectids, $displaytitles;
+ private $protections, $watched, $watchers, $notificationtimestamps,
+ $talkids, $subjectids, $displaytitles;
private $showZeroWatchers = false;
private $tokenFunctions;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'in' );
}
/**
- * @param $pageSet ApiPageSet
+ * @param ApiPageSet $pageSet
* @return void
*/
public function requestExtraData( $pageSet ) {
- global $wgDisableCounters, $wgContentHandlerUseDB;
-
$pageSet->requestField( 'page_restrictions' );
// when resolving redirects, no page will have this field
if ( !$pageSet->isResolvingRedirects() ) {
$pageSet->requestField( 'page_is_redirect' );
}
$pageSet->requestField( 'page_is_new' );
- if ( !$wgDisableCounters ) {
+ $config = $this->getConfig();
+ if ( !$config->get( 'DisableCounters' ) ) {
$pageSet->requestField( 'page_counter' );
}
$pageSet->requestField( 'page_touched' );
$pageSet->requestField( 'page_latest' );
$pageSet->requestField( 'page_len' );
- if ( $wgContentHandlerUseDB ) {
+ if ( $config->get( 'ContentHandlerUseDB' ) ) {
$pageSet->requestField( 'page_content_model' );
}
}
@@ -79,7 +79,8 @@ class ApiQueryInfo extends ApiQueryBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title)
* it should return a token or false (permission denied)
- * @return array array(tokenname => function)
+ * @deprecated since 1.24
+ * @return array Array(tokenname => function)
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
@@ -104,15 +105,22 @@ class ApiQueryInfo extends ApiQueryBase {
'watch' => array( 'ApiQueryInfo', 'getWatchToken' ),
);
wfRunHooks( 'APIQueryInfoTokens', array( &$this->tokenFunctions ) );
+
return $this->tokenFunctions;
}
- static $cachedTokens = array();
+ static protected $cachedTokens = array();
+ /**
+ * @deprecated since 1.24
+ */
public static function resetTokenCache() {
ApiQueryInfo::$cachedTokens = array();
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getEditToken( $pageid, $title ) {
// We could check for $title->userCan('edit') here,
// but that's too expensive for this purpose
@@ -130,6 +138,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['edit'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getDeleteToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'delete' ) ) {
@@ -144,6 +155,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['delete'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getProtectToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'protect' ) ) {
@@ -158,6 +172,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['protect'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getMoveToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'move' ) ) {
@@ -172,6 +189,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['move'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getBlockToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'block' ) ) {
@@ -186,11 +206,17 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['block'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getUnblockToken( $pageid, $title ) {
// Currently, this is exactly the same as the block token
return self::getBlockToken( $pageid, $title );
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getEmailToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser() ) {
@@ -205,6 +231,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['email'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getImportToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
@@ -219,6 +248,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['import'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getWatchToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isLoggedIn() ) {
@@ -233,6 +265,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['watch'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getOptionsToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isLoggedIn() ) {
@@ -293,9 +328,7 @@ class ApiQueryInfo extends ApiQueryBase {
: array();
$this->pageIsNew = $pageSet->getCustomField( 'page_is_new' );
- global $wgDisableCounters;
-
- if ( !$wgDisableCounters ) {
+ if ( !$this->getConfig()->get( 'DisableCounters' ) ) {
$this->pageCounter = $pageSet->getCustomField( 'page_counter' );
}
$this->pageTouched = $pageSet->getCustomField( 'page_touched' );
@@ -333,8 +366,8 @@ class ApiQueryInfo extends ApiQueryBase {
), $pageid, $pageInfo );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue',
- $title->getNamespace() . '|' .
- $title->getText() );
+ $title->getNamespace() . '|' .
+ $title->getText() );
break;
}
}
@@ -343,12 +376,13 @@ class ApiQueryInfo extends ApiQueryBase {
/**
* Get a result array with information about a title
* @param int $pageid Page ID (negative for missing titles)
- * @param $title Title object
+ * @param Title $title
* @return array
*/
private function extractPageInfo( $pageid, $title ) {
$pageInfo = array();
- $titleExists = $pageid > 0; //$title->exists() needs pageid, which is not set for all title objects
+ // $title->exists() needs pageid, which is not set for all title objects
+ $titleExists = $pageid > 0;
$ns = $title->getNamespace();
$dbkey = $title->getDBkey();
@@ -356,11 +390,9 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['pagelanguage'] = $title->getPageLanguage()->getCode();
if ( $titleExists ) {
- global $wgDisableCounters;
-
$pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
$pageInfo['lastrevid'] = intval( $this->pageLatest[$pageid] );
- $pageInfo['counter'] = $wgDisableCounters
+ $pageInfo['counter'] = $this->getConfig()->get( 'DisableCounters' )
? ''
: intval( $this->pageCounter[$pageid] );
$pageInfo['length'] = intval( $this->pageLength[$pageid] );
@@ -410,7 +442,8 @@ class ApiQueryInfo extends ApiQueryBase {
if ( $this->fld_notificationtimestamp ) {
$pageInfo['notificationtimestamp'] = '';
if ( isset( $this->notificationtimestamps[$ns][$dbkey] ) ) {
- $pageInfo['notificationtimestamp'] = wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
+ $pageInfo['notificationtimestamp'] =
+ wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
}
}
@@ -425,6 +458,7 @@ class ApiQueryInfo extends ApiQueryBase {
if ( $this->fld_url ) {
$pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
$pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT );
+ $pageInfo['canonicalurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CANONICAL );
}
if ( $this->fld_readable && $title->userCan( 'read', $this->getUser() ) ) {
$pageInfo['readable'] = '';
@@ -465,7 +499,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->resetQueryParams();
$this->addTables( 'page_restrictions' );
$this->addFields( array( 'pr_page', 'pr_type', 'pr_level',
- 'pr_expiry', 'pr_cascade' ) );
+ 'pr_expiry', 'pr_cascade' ) );
$this->addWhereFld( 'pr_page', array_keys( $this->titles ) );
$res = $this->select( __METHOD__ );
@@ -556,8 +590,8 @@ class ApiQueryInfo extends ApiQueryBase {
$this->resetQueryParams();
$this->addTables( array( 'page_restrictions', 'page', 'templatelinks' ) );
$this->addFields( array( 'pr_type', 'pr_level', 'pr_expiry',
- 'page_title', 'page_namespace',
- 'tl_title', 'tl_namespace' ) );
+ 'page_title', 'page_namespace',
+ 'tl_title', 'tl_namespace' ) );
$this->addWhere( $lb->constructSet( 'tl', $db ) );
$this->addWhere( 'pr_page = page_id' );
$this->addWhere( 'pr_page = tl_from' );
@@ -580,7 +614,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->resetQueryParams();
$this->addTables( array( 'page_restrictions', 'page', 'imagelinks' ) );
$this->addFields( array( 'pr_type', 'pr_level', 'pr_expiry',
- 'page_title', 'page_namespace', 'il_to' ) );
+ 'page_title', 'page_namespace', 'il_to' ) );
$this->addWhere( 'pr_page = page_id' );
$this->addWhere( 'pr_page = il_from' );
$this->addWhereFld( 'pr_cascade', 1 );
@@ -633,10 +667,10 @@ class ApiQueryInfo extends ApiQueryBase {
foreach ( $res as $row ) {
if ( MWNamespace::isTalk( $row->page_namespace ) ) {
$this->talkids[MWNamespace::getSubject( $row->page_namespace )][$row->page_title] =
- intval( $row->page_id );
+ intval( $row->page_id );
} else {
$this->subjectids[MWNamespace::getTalk( $row->page_namespace )][$row->page_title] =
- intval( $row->page_id );
+ intval( $row->page_id );
}
}
}
@@ -697,7 +731,8 @@ class ApiQueryInfo extends ApiQueryBase {
$this->watched[$row->wl_namespace][$row->wl_title] = true;
}
if ( $this->fld_notificationtimestamp ) {
- $this->notificationtimestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
+ $this->notificationtimestamps[$row->wl_namespace][$row->wl_title] =
+ $row->wl_notificationtimestamp;
}
}
}
@@ -706,15 +741,14 @@ class ApiQueryInfo extends ApiQueryBase {
* Get the count of watchers and put it in $this->watchers
*/
private function getWatcherInfo() {
- global $wgUnwatchedPageThreshold;
-
if ( count( $this->everything ) == 0 ) {
return;
}
$user = $this->getUser();
$canUnwatchedpages = $user->isAllowed( 'unwatchedpages' );
- if ( !$canUnwatchedpages && !is_int( $wgUnwatchedPageThreshold ) ) {
+ $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' );
+ if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
return;
}
@@ -732,7 +766,7 @@ class ApiQueryInfo extends ApiQueryBase {
) );
$this->addOption( 'GROUP BY', array( 'wl_namespace', 'wl_title' ) );
if ( !$canUnwatchedpages ) {
- $this->addOption( 'HAVING', "COUNT(*) >= $wgUnwatchedPageThreshold" );
+ $this->addOption( 'HAVING', "COUNT(*) >= $unwatchedPageThreshold" );
}
$res = $this->select( __METHOD__ );
@@ -761,6 +795,7 @@ class ApiQueryInfo extends ApiQueryBase {
if ( !is_null( $params['token'] ) ) {
return 'private';
}
+
return 'public';
}
@@ -784,6 +819,7 @@ class ApiQueryInfo extends ApiQueryBase {
// need to be added to getCacheMode()
) ),
'token' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_DFLT => null,
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() )
@@ -802,7 +838,7 @@ class ApiQueryInfo extends ApiQueryBase {
' watchers - The number of watchers, if allowed',
' notificationtimestamp - The watchlist notification timestamp of each page',
' subjectid - The page ID of the parent page for each talk page',
- ' url - Gives a full URL to the page, and also an edit URL',
+ ' url - Gives a full URL, an edit URL, and the canonical URL for each page',
' readable - Whether the user can read this page',
' preload - Gives the text returned by EditFormPreloadText',
' displaytitle - Gives the way the page title is actually displayed',
@@ -812,72 +848,6 @@ class ApiQueryInfo extends ApiQueryBase {
);
}
- public function getResultProperties() {
- $props = array(
- ApiBase::PROP_LIST => false,
- '' => array(
- 'touched' => 'timestamp',
- 'lastrevid' => 'integer',
- 'counter' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'length' => 'integer',
- 'redirect' => 'boolean',
- 'new' => 'boolean',
- 'starttimestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- ),
- 'contentmodel' => 'string',
- ),
- 'watched' => array(
- 'watched' => 'boolean'
- ),
- 'watchers' => array(
- 'watchers' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'notificationtimestamp' => array(
- 'notificationtimestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'talkid' => array(
- 'talkid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'subjectid' => array(
- 'subjectid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'url' => array(
- 'fullurl' => 'string',
- 'editurl' => 'string'
- ),
- 'readable' => array(
- 'readable' => 'boolean'
- ),
- 'preload' => array(
- 'preload' => 'string'
- ),
- 'displaytitle' => array(
- 'displaytitle' => 'string'
- )
- );
-
- self::addTokenProperties( $props, $this->getTokenFunctions() );
-
- return $props;
- }
-
public function getDescription() {
return 'Get basic page information such as namespace, title, last touched date, ...';
}
diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php
index 5bd451b6..34842c63 100644
--- a/includes/api/ApiQueryLangBacklinks.php
+++ b/includes/api/ApiQueryLangBacklinks.php
@@ -31,7 +31,7 @@
*/
class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'lbl' );
}
@@ -44,7 +44,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function run( $resultPageSet = null ) {
@@ -92,14 +92,14 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', array(
'll_title' . $sort,
'll_from' . $sort
- ));
+ ) );
}
} else {
$this->addOption( 'ORDER BY', array(
'll_lang' . $sort,
'll_title' . $sort,
'll_from' . $sort
- ));
+ ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -111,10 +111,14 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // Continue string preserved in case the redirect query doesn't pass the limit
- $this->setContinueEnumParameter( 'continue', "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}" );
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here... Continue string
+ // preserved in case the redirect query doesn't pass the limit.
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}"
+ );
break;
}
@@ -140,7 +144,10 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $entry );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}" );
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}"
+ );
break;
}
}
@@ -202,23 +209,6 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'pageid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string',
- 'redirect' => 'boolean'
- ),
- 'lllang' => array(
- 'lllang' => 'string'
- ),
- 'lltitle' => array(
- 'lltitle' => 'string'
- )
- );
- }
-
public function getDescription() {
return array( 'Find all pages that link to the given language link.',
'Can be used to find all links with a language code, or',
@@ -228,12 +218,6 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'lang' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&list=langbacklinks&lbltitle=Test&lbllang=fr',
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index aa796e31..da05f273 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -31,7 +31,7 @@
*/
class ApiQueryLangLinks extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'll' );
}
@@ -41,11 +41,19 @@ class ApiQueryLangLinks extends ApiQueryBase {
}
$params = $this->extractRequestParams();
+ $prop = array_flip( (array)$params['prop'] );
if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'lang' ) );
}
+ // Handle deprecated param
+ $this->requireMaxOneParameter( $params, 'url', 'prop' );
+ if ( $params['url'] ) {
+ $this->logFeatureUsage( 'prop=langlinks&llurl' );
+ $prop = array( 'url' => 1 );
+ }
+
$this->addFields( array(
'll_from',
'll_lang',
@@ -86,9 +94,9 @@ class ApiQueryLangLinks extends ApiQueryBase {
$this->addOption( 'ORDER BY', 'll_lang' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'll_from' . $sort,
- 'll_lang' . $sort
- ));
+ 'll_from' . $sort,
+ 'll_lang' . $sort
+ ) );
}
}
@@ -104,12 +112,18 @@ class ApiQueryLangLinks extends ApiQueryBase {
break;
}
$entry = array( 'lang' => $row->ll_lang );
- if ( $params['url'] ) {
+ if ( isset( $prop['url'] ) ) {
$title = Title::newFromText( "{$row->ll_lang}:{$row->ll_title}" );
if ( $title ) {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
}
+ if ( isset( $prop['langname'] ) ) {
+ $entry['langname'] = Language::fetchLanguageName( $row->ll_lang, $params['inlanguagecode'] );
+ }
+ if ( isset( $prop['autonym'] ) ) {
+ $entry['autonym'] = Language::fetchLanguageName( $row->ll_lang );
+ }
ApiResult::setContent( $entry, $row->ll_title );
$fit = $this->addPageSubItem( $row->ll_from, $entry );
if ( !$fit ) {
@@ -124,6 +138,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
}
public function getAllowedParams() {
+ global $wgContLang;
return array(
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -133,7 +148,18 @@ class ApiQueryLangLinks extends ApiQueryBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'continue' => null,
- 'url' => false,
+ 'url' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
+ 'url',
+ 'langname',
+ 'autonym',
+ )
+ ),
'lang' => null,
'title' => null,
'dir' => array(
@@ -143,6 +169,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
'descending'
)
),
+ 'inlanguagecode' => $wgContLang->getCode(),
);
}
@@ -150,39 +177,29 @@ class ApiQueryLangLinks extends ApiQueryBase {
return array(
'limit' => 'How many langlinks to return',
'continue' => 'When more results are available, use this to continue',
- 'url' => 'Whether to get the full URL',
+ 'url' => "Whether to get the full URL (Cannot be used with {$this->getModulePrefix()}prop)",
+ 'prop' => array(
+ 'Which additional properties to get for each interlanguage link',
+ ' url - Adds the full URL',
+ ' langname - Adds the localised language name (best effort, use CLDR extension)',
+ " Use {$this->getModulePrefix()}inlanguagecode to control the language",
+ ' autonym - Adds the native language name',
+ ),
'lang' => 'Language code',
'title' => "Link to search for. Must be used with {$this->getModulePrefix()}lang",
'dir' => 'The direction in which to list',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'lang' => 'string',
- 'url' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- '*' => 'string'
- )
+ 'inlanguagecode' => 'Language code for localised language names',
);
}
public function getDescription() {
- return 'Returns all interlanguage links from the given page(s)';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'lang' ),
- ) );
+ return 'Returns all interlanguage links from the given page(s).';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=langlinks&titles=Main%20Page&redirects=' => 'Get interlanguage links from the [[Main Page]]',
+ 'api.php?action=query&prop=langlinks&titles=Main%20Page&redirects='
+ => 'Get interlanguage links from the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 937f4f13..71329c4d 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -36,14 +36,15 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
private $table, $prefix, $description, $helpUrl;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
switch ( $moduleName ) {
case self::LINKS:
$this->table = 'pagelinks';
$this->prefix = 'pl';
$this->description = 'link';
$this->titlesParam = 'titles';
- $this->titlesParamDescription = 'Only list links to these titles. Useful for checking whether a certain page links to a certain title.';
+ $this->titlesParamDescription = 'Only list links to these titles. Useful ' .
+ 'for checking whether a certain page links to a certain title.';
$this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#links_.2F_pl';
break;
case self::TEMPLATES:
@@ -51,7 +52,8 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->prefix = 'tl';
$this->description = 'template';
$this->titlesParam = 'templates';
- $this->titlesParamDescription = 'Only list these templates. Useful for checking whether a certain page uses a certain template.';
+ $this->titlesParamDescription = 'Only list these templates. Useful ' .
+ 'for checking whether a certain page uses a certain template.';
$this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#templates_.2F_tl';
break;
default:
@@ -74,8 +76,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
- * @return
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
@@ -212,6 +213,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
public function getParamDescription() {
$desc = $this->description;
+
return array(
'namespace' => "Show {$desc}s in this namespace(s) only",
'limit' => "How many {$desc}s to return",
@@ -221,26 +223,20 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return "Returns all {$this->description}s from the given page(s)";
+ return "Returns all {$this->description}s from the given page(s).";
}
public function getExamples() {
$desc = $this->description;
$name = $this->getModuleName();
+
return array(
"api.php?action=query&prop={$name}&titles=Main%20Page" => "Get {$desc}s from the [[Main Page]]",
- "api.php?action=query&generator={$name}&titles=Main%20Page&prop=info" => "Get information about the {$desc} pages in the [[Main Page]]",
- "api.php?action=query&prop={$name}&titles=Main%20Page&{$this->prefix}namespace=2|10" => "Get {$desc}s from the Main Page in the User and Template namespaces",
+ "api.php?action=query&generator={$name}&titles=Main%20Page&prop=info"
+ => "Get information about the {$desc} pages in the [[Main Page]]",
+ "api.php?action=query&prop={$name}&titles=Main%20Page&{$this->prefix}namespace=2|10"
+ => "Get {$desc}s from the Main Page in the User and Template namespaces",
);
}
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index ecd117e4..d9dbb5e6 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -31,7 +31,7 @@
*/
class ApiQueryLogEvents extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'le' );
}
@@ -43,6 +43,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
public function execute() {
$params = $this->extractRequestParams();
$db = $this->getDB();
+ $this->requireMaxOneParameter( $params, 'title', 'prefix', 'namespace' );
$prop = array_flip( $params['prop'] );
@@ -64,26 +65,32 @@ class ApiQueryLogEvents extends ApiQueryBase {
// Order is significant here
$this->addTables( array( 'logging', 'user', 'page' ) );
- $this->addOption( 'STRAIGHT_JOIN' );
$this->addJoinConds( array(
'user' => array( 'LEFT JOIN',
'user_id=log_user' ),
'page' => array( 'LEFT JOIN',
array( 'log_namespace=page_namespace',
'log_title=page_title' ) ) ) );
- $index = array( 'logging' => 'times' ); // default, may change
$this->addFields( array(
+ 'log_id',
'log_type',
'log_action',
'log_timestamp',
'log_deleted',
) );
- $this->addFieldsIf( array( 'log_id', 'page_id' ), $this->fld_ids );
+ $this->addFieldsIf( 'page_id', $this->fld_ids );
+ // log_page is the page_id saved at log time, whereas page_id is from a
+ // join at query time. This leads to different results in various
+ // scenarios, e.g. deletion, recreation.
+ $this->addFieldsIf( 'log_page', $this->fld_ids );
$this->addFieldsIf( array( 'log_user', 'log_user_text', 'user_name' ), $this->fld_user );
$this->addFieldsIf( 'log_user', $this->fld_userid );
- $this->addFieldsIf( array( 'log_namespace', 'log_title' ), $this->fld_title || $this->fld_parsedcomment );
+ $this->addFieldsIf(
+ array( 'log_namespace', 'log_title' ),
+ $this->fld_title || $this->fld_parsedcomment
+ );
$this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'log_params', $this->fld_details );
@@ -95,21 +102,59 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
- $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'log_id=ct_log_id' ) ) ) );
+ $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN',
+ array( 'log_id=ct_log_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $params['tag'] );
- $index['change_tag'] = 'change_tag_tag_id';
}
if ( !is_null( $params['action'] ) ) {
- list( $type, $action ) = explode( '/', $params['action'] );
+ // Do validation of action param, list of allowed actions can contains wildcards
+ // Allow the param, when the actions is in the list or a wildcard version is listed.
+ $logAction = $params['action'];
+ if ( strpos( $logAction, '/' ) === false ) {
+ // all items in the list have a slash
+ $valid = false;
+ } else {
+ $logActions = array_flip( $this->getAllowedLogActions() );
+ list( $type, $action ) = explode( '/', $logAction, 2 );
+ $valid = isset( $logActions[$logAction] ) || isset( $logActions[$type . '/*'] );
+ }
+
+ if ( !$valid ) {
+ $valueName = $this->encodeParamName( 'action' );
+ $this->dieUsage(
+ "Unrecognized value for parameter '$valueName': {$logAction}",
+ "unknown_$valueName"
+ );
+ }
+
$this->addWhereFld( 'log_type', $type );
$this->addWhereFld( 'log_action', $action );
} elseif ( !is_null( $params['type'] ) ) {
$this->addWhereFld( 'log_type', $params['type'] );
- $index['logging'] = 'type_time';
}
- $this->addTimestampWhereRange( 'log_timestamp', $params['dir'], $params['start'], $params['end'] );
+ $this->addTimestampWhereRange(
+ 'log_timestamp',
+ $params['dir'],
+ $params['start'],
+ $params['end']
+ );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'log_id', $params['dir'], null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $op = ( $params['dir'] === 'newer' ? '>' : '<' );
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueId = (int)$cont[1];
+ $this->dieContinueUsageIf( $continueId != $cont[1] );
+ $this->addWhere( "log_timestamp $op $continueTimestamp OR " .
+ "(log_timestamp = $continueTimestamp AND " .
+ "log_id $op= $continueId)"
+ );
+ }
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
@@ -117,11 +162,11 @@ class ApiQueryLogEvents extends ApiQueryBase {
$user = $params['user'];
if ( !is_null( $user ) ) {
$userid = User::idFromName( $user );
- if ( !$userid ) {
- $this->dieUsage( "User name $user not found", 'param_user' );
+ if ( $userid ) {
+ $this->addWhereFld( 'log_user', $userid );
+ } else {
+ $this->addWhereFld( 'log_user_text', IP::sanitizeIP( $user ) );
}
- $this->addWhereFld( 'log_user', $userid );
- $index['logging'] = 'user_time';
}
$title = $params['title'];
@@ -132,16 +177,16 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
$this->addWhereFld( 'log_namespace', $titleObj->getNamespace() );
$this->addWhereFld( 'log_title', $titleObj->getDBkey() );
+ }
- // Use the title index in preference to the user index if there is a conflict
- $index['logging'] = is_null( $user ) ? 'page_time' : array( 'page_time', 'user_time' );
+ if ( $params['namespace'] !== null ) {
+ $this->addWhereFld( 'log_namespace', $params['namespace'] );
}
$prefix = $params['prefix'];
if ( !is_null( $prefix ) ) {
- global $wgMiserMode;
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$this->dieUsage( 'Prefix search disabled in Miser Mode', 'prefixsearchdisabled' );
}
@@ -153,23 +198,34 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->addWhere( 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ) );
}
- $this->addOption( 'USE INDEX', $index );
-
// Paranoia: avoid brute force searches (bug 17342)
- if ( !is_null( $title ) ) {
- $this->addWhere( $db->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) . ' = 0' );
- }
- if ( !is_null( $user ) ) {
- $this->addWhere( $db->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0' );
+ if ( $params['namespace'] !== null || !is_null( $title ) || !is_null( $user ) ) {
+ if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $titleBits = LogPage::DELETED_ACTION;
+ $userBits = LogPage::DELETED_USER;
+ } elseif ( !$this->getUser()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $titleBits = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
+ $userBits = LogPage::DELETED_USER | LogPage::DELETED_RESTRICTED;
+ } else {
+ $titleBits = 0;
+ $userBits = 0;
+ }
+ if ( ( $params['namespace'] !== null || !is_null( $title ) ) && $titleBits ) {
+ $this->addWhere( $db->bitAnd( 'log_deleted', $titleBits ) . " != $titleBits" );
+ }
+ if ( !is_null( $user ) && $userBits ) {
+ $this->addWhere( $db->bitAnd( 'log_deleted', $userBits ) . " != $userBits" );
+ }
}
$count = 0;
$res = $this->select( __METHOD__ );
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->log_timestamp ) );
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
break;
}
@@ -179,7 +235,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->log_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
break;
}
}
@@ -187,16 +243,18 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
/**
- * @param $result ApiResult
- * @param $vals array
- * @param $params string
- * @param $type string
- * @param $action string
- * @param $ts
- * @param $legacy bool
+ * @param ApiResult $result
+ * @param array $vals
+ * @param string $params
+ * @param string $type
+ * @param string $action
+ * @param string $ts
+ * @param bool $legacy
* @return array
*/
- public static function addLogParams( $result, &$vals, $params, $type, $action, $ts, $legacy = false ) {
+ public static function addLogParams( $result, &$vals, $params, $type,
+ $action, $ts, $legacy = false
+ ) {
switch ( $type ) {
case 'move':
if ( $legacy ) {
@@ -284,12 +342,15 @@ class ApiQueryLogEvents extends ApiQueryBase {
$result->setIndexedTagName_recursive( $logParams, 'param' );
$vals = array_merge( $vals, $logParams );
}
+
return $vals;
}
private function extractRowInfo( $row ) {
$logEntry = DatabaseLogEntry::newFromRow( $row );
$vals = array();
+ $anyHidden = false;
+ $user = $this->getUser();
if ( $this->fld_ids ) {
$vals['logid'] = intval( $row->log_id );
@@ -299,18 +360,29 @@ class ApiQueryLogEvents extends ApiQueryBase {
$title = Title::makeTitle( $row->log_namespace, $row->log_title );
}
- if ( $this->fld_title || $this->fld_ids ) {
+ if ( $this->fld_title || $this->fld_ids || $this->fld_details && $row->log_params !== '' ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
$vals['actionhidden'] = '';
- } else {
- if ( $this->fld_type ) {
- $vals['action'] = $row->log_action;
- }
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCan( $row, LogPage::DELETED_ACTION, $user ) ) {
if ( $this->fld_title ) {
ApiQueryBase::addTitleInfo( $vals, $title );
}
if ( $this->fld_ids ) {
$vals['pageid'] = intval( $row->page_id );
+ $vals['logpage'] = intval( $row->log_page );
+ }
+ if ( $this->fld_details && $row->log_params !== '' ) {
+ self::addLogParams(
+ $this->getResult(),
+ $vals,
+ $logEntry->getParameters(),
+ $logEntry->getType(),
+ $logEntry->getSubtype(),
+ $logEntry->getTimestamp(),
+ $logEntry->isLegacy()
+ );
}
}
}
@@ -320,31 +392,17 @@ class ApiQueryLogEvents extends ApiQueryBase {
$vals['action'] = $row->log_action;
}
- if ( $this->fld_details && $row->log_params !== '' ) {
- if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
- $vals['actionhidden'] = '';
- } else {
- self::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp(),
- $logEntry->isLegacy()
- );
- }
- }
-
if ( $this->fld_user || $this->fld_userid ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
$vals['userhidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCan( $row, LogPage::DELETED_USER, $user ) ) {
if ( $this->fld_user ) {
$vals['user'] = $row->user_name === null ? $row->log_user_text : $row->user_name;
}
if ( $this->fld_userid ) {
- $vals['userid'] = $row->log_user;
+ $vals['userid'] = intval( $row->log_user );
}
if ( !$row->log_user ) {
@@ -359,7 +417,9 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->log_comment ) ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCan( $row, LogPage::DELETED_COMMENT, $user ) ) {
if ( $this->fld_comment ) {
$vals['comment'] = $row->log_comment;
}
@@ -380,10 +440,25 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
}
+ if ( $anyHidden && LogEventsList::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
+ }
+
return $vals;
}
+ /**
+ * @return array
+ */
+ private function getAllowedLogActions() {
+ $config = $this->getConfig();
+ return array_keys( array_merge( $config->get( 'LogActions' ), $config->get( 'LogActionsHandlers' ) ) );
+ }
+
public function getCacheMode( $params ) {
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMessage() among other things
return 'anon-public-user-private';
@@ -396,8 +471,8 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
}
- public function getAllowedParams() {
- global $wgLogTypes, $wgLogActions, $wgLogActionsHandlers;
+ public function getAllowedParams( $flags = 0 ) {
+ $config = $this->getConfig();
return array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -416,10 +491,13 @@ class ApiQueryLogEvents extends ApiQueryBase {
)
),
'type' => array(
- ApiBase::PARAM_TYPE => $wgLogTypes
+ ApiBase::PARAM_TYPE => $config->get( 'LogTypes' )
),
'action' => array(
- ApiBase::PARAM_TYPE => array_keys( array_merge( $wgLogActions, $wgLogActionsHandlers ) )
+ // validation on request is done in execute()
+ ApiBase::PARAM_TYPE => ( $flags & ApiBase::GET_VALUES_FOR_HELP )
+ ? $this->getAllowedLogActions()
+ : null
),
'start' => array(
ApiBase::PARAM_TYPE => 'timestamp'
@@ -436,6 +514,9 @@ class ApiQueryLogEvents extends ApiQueryBase {
),
'user' => null,
'title' => null,
+ 'namespace' => array(
+ ApiBase::PARAM_TYPE => 'namespace'
+ ),
'prefix' => null,
'tag' => null,
'limit' => array(
@@ -444,12 +525,14 @@ class ApiQueryLogEvents extends ApiQueryBase {
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
- )
+ ),
+ 'continue' => null,
);
}
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'prop' => array(
'Which properties to get',
@@ -465,85 +548,25 @@ class ApiQueryLogEvents extends ApiQueryBase {
' tags - Lists tags for the event',
),
'type' => 'Filter log entries to only this type',
- 'action' => "Filter log actions to only this type. Overrides {$p}type",
+ 'action' => array(
+ "Filter log actions to only this action. Overrides {$p}type",
+ "Wildcard actions like 'action/*' allows to specify any string for the asterisk"
+ ),
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to end enumerating',
'dir' => $this->getDirectionDescription( $p ),
'user' => 'Filter entries to those made by the given user',
'title' => 'Filter entries to those related to a page',
+ 'namespace' => 'Filter entries to those in the given namespace',
'prefix' => 'Filter entries that start with this prefix. Disabled in Miser Mode',
'limit' => 'How many total event entries to return',
'tag' => 'Only list event entries tagged with this tag',
- );
- }
-
- public function getResultProperties() {
- global $wgLogTypes;
- return array(
- 'ids' => array(
- 'logid' => 'integer',
- 'pageid' => 'integer'
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'type' => array(
- 'type' => array(
- ApiBase::PROP_TYPE => $wgLogTypes
- ),
- 'action' => 'string'
- ),
- 'details' => array(
- 'actionhidden' => 'boolean'
- ),
- 'user' => array(
- 'userhidden' => 'boolean',
- 'user' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'anon' => 'boolean'
- ),
- 'userid' => array(
- 'userhidden' => 'boolean',
- 'userid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'anon' => 'boolean'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'comment' => array(
- 'commenthidden' => 'boolean',
- 'comment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'parsedcomment' => array(
- 'commenthidden' => 'boolean',
- 'parsedcomment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return 'Get events from logs';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'param_user', 'info' => 'User name $user not found' ),
- array( 'code' => 'param_title', 'info' => 'Bad title value \'title\'' ),
- array( 'code' => 'param_prefix', 'info' => 'Bad title value \'prefix\'' ),
- array( 'code' => 'prefixsearchdisabled', 'info' => 'Prefix search disabled in Miser Mode' ),
- ) );
+ return 'Get events from logs.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryORM.php b/includes/api/ApiQueryORM.php
index a23ff06b..469b2972 100644
--- a/includes/api/ApiQueryORM.php
+++ b/includes/api/ApiQueryORM.php
@@ -104,7 +104,7 @@ abstract class ApiQueryORM extends ApiQueryBase {
protected function getParams() {
return array_filter(
$this->extractRequestParams(),
- function( $prop ) {
+ function ( $prop ) {
return isset( $prop );
}
);
@@ -260,5 +260,4 @@ abstract class ApiQueryORM extends ApiQueryBase {
return array_merge( $this->getTable()->getFieldDescriptions(), $descriptions );
}
-
}
diff --git a/includes/api/ApiQueryPagePropNames.php b/includes/api/ApiQueryPagePropNames.php
index 08c883d8..8cd9c6cf 100644
--- a/includes/api/ApiQueryPagePropNames.php
+++ b/includes/api/ApiQueryPagePropNames.php
@@ -32,7 +32,7 @@
*/
class ApiQueryPagePropNames extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ppn' );
}
@@ -63,7 +63,8 @@ class ApiQueryPagePropNames extends ApiQueryBase {
$count = 0;
foreach ( $this->select( __METHOD__ ) as $row ) {
if ( ++$count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue', $row->pp_propname );
break;
}
@@ -101,7 +102,7 @@ class ApiQueryPagePropNames extends ApiQueryBase {
}
public function getDescription() {
- return 'List all page prop names in use on the wiki';
+ return 'List all page prop names in use on the wiki.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryPageProps.php b/includes/api/ApiQueryPageProps.php
index 2de57106..e370c39f 100644
--- a/includes/api/ApiQueryPageProps.php
+++ b/includes/api/ApiQueryPageProps.php
@@ -33,7 +33,7 @@ class ApiQueryPageProps extends ApiQueryBase {
private $params;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'pp' );
}
@@ -104,9 +104,9 @@ class ApiQueryPageProps extends ApiQueryBase {
* Add page properties to an ApiResult, adding a continue
* parameter if it doesn't fit.
*
- * @param $result ApiResult
- * @param $page int
- * @param $props array
+ * @param ApiResult $result
+ * @param int $page
+ * @param array $props
* @return bool True if it fits in the result
*/
private function addPageProps( $result, $page, $props ) {
@@ -115,6 +115,7 @@ class ApiQueryPageProps extends ApiQueryBase {
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $page );
}
+
return $fit;
}
@@ -134,12 +135,13 @@ class ApiQueryPageProps extends ApiQueryBase {
public function getParamDescription() {
return array(
'continue' => 'When more results are available, use this to continue',
- 'prop' => 'Only list these props. Useful for checking whether a certain page uses a certain page prop',
+ 'prop' => 'Only list these props. Useful for checking whether a ' .
+ 'certain page uses a certain page prop',
);
}
public function getDescription() {
- return 'Get various properties defined in the page content';
+ return 'Get various properties defined in the page content.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryPagesWithProp.php b/includes/api/ApiQueryPagesWithProp.php
index 6f2f02e4..b6c85253 100644
--- a/includes/api/ApiQueryPagesWithProp.php
+++ b/includes/api/ApiQueryPagesWithProp.php
@@ -32,7 +32,7 @@
*/
class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'pwp' );
}
@@ -49,7 +49,7 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -92,7 +92,8 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
$count = 0;
foreach ( $this->select( __METHOD__ ) as $row ) {
if ( ++$count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue', $row->page_id );
break;
}
@@ -173,13 +174,15 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
}
public function getDescription() {
- return 'List all pages using a given page prop';
+ return 'List all pages using a given page prop.';
}
public function getExamples() {
return array(
- 'api.php?action=query&list=pageswithprop&pwppropname=displaytitle&pwpprop=ids|title|value' => 'Get first 10 pages using {{DISPLAYTITLE:}}',
- 'api.php?action=query&generator=pageswithprop&gpwppropname=notoc&prop=info' => 'Get page info about first 10 pages using __NOTOC__',
+ 'api.php?action=query&list=pageswithprop&pwppropname=displaytitle&pwpprop=ids|title|value'
+ => 'Get first 10 pages using {{DISPLAYTITLE:}}',
+ 'api.php?action=query&generator=pageswithprop&gpwppropname=notoc&prop=info'
+ => 'Get page info about first 10 pages using __NOTOC__',
);
}
diff --git a/includes/api/ApiQueryPrefixSearch.php b/includes/api/ApiQueryPrefixSearch.php
new file mode 100644
index 00000000..4abd7f0e
--- /dev/null
+++ b/includes/api/ApiQueryPrefixSearch.php
@@ -0,0 +1,124 @@
+<?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
+ * @since 1.23
+ */
+
+/**
+ * @ingroup API
+ */
+class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'ps' );
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param ApiPageSet $resultPageSet
+ */
+ private function run( $resultPageSet = null ) {
+ $params = $this->extractRequestParams();
+ $search = $params['search'];
+ $limit = $params['limit'];
+ $namespaces = $params['namespace'];
+
+ $searcher = new TitlePrefixSearch;
+ $titles = $searcher->searchWithVariants( $search, $limit, $namespaces );
+ if ( $resultPageSet ) {
+ $resultPageSet->populateFromTitles( $titles );
+ } else {
+ $result = $this->getResult();
+ foreach ( $titles as $title ) {
+ if ( !$limit-- ) {
+ break;
+ }
+ $vals = array(
+ 'ns' => intval( $title->getNamespace() ),
+ 'title' => $title->getPrefixedText(),
+ );
+ if ( $title->isSpecialPage() ) {
+ $vals['special'] = '';
+ } else {
+ $vals['pageid'] = intval( $title->getArticleId() );
+ }
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
+ if ( !$fit ) {
+ break;
+ }
+ }
+ $result->setIndexedTagName_internal(
+ array( 'query', $this->getModuleName() ), $this->getModulePrefix()
+ );
+ }
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'search' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'namespace' => array(
+ ApiBase::PARAM_DFLT => NS_MAIN,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ // Non-standard value for compatibility with action=opensearch
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 200,
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'search' => 'Search string',
+ 'limit' => 'Maximum amount of results to return',
+ 'namespace' => 'Namespaces to search',
+ );
+ }
+
+ public function getDescription() {
+ return 'Perform a prefix search for page titles';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&list=prefixsearch&pssearch=meaning',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Prefixsearch';
+ }
+}
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
index 222ad074..4c88be7a 100644
--- a/includes/api/ApiQueryProtectedTitles.php
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -31,7 +31,7 @@
*/
class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'pt' );
}
@@ -44,7 +44,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -63,6 +63,27 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$this->addWhereFld( 'pt_namespace', $params['namespace'] );
$this->addWhereFld( 'pt_create_perm', $params['level'] );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'pt_namespace', $params['dir'], null, null );
+ $this->addWhereRange( 'pt_title', $params['dir'], null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
+ $op = ( $params['dir'] === 'newer' ? '>' : '<' );
+ $db = $this->getDB();
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueNs = (int)$cont[1];
+ $this->dieContinueUsageIf( $continueNs != $cont[1] );
+ $continueTitle = $db->addQuotes( $cont[2] );
+ $this->addWhere( "pt_timestamp $op $continueTimestamp OR " .
+ "(pt_timestamp = $continueTimestamp AND " .
+ "(pt_namespace $op $continueNs OR " .
+ "(pt_namespace = $continueNs AND " .
+ "pt_title $op= $continueTitle)))"
+ );
+ }
+
if ( isset( $prop['user'] ) ) {
$this->addTables( 'user' );
$this->addFields( 'user_name' );
@@ -80,9 +101,12 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$titles = array();
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->pt_timestamp ) );
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue',
+ "$row->pt_timestamp|$row->pt_namespace|$row->pt_title"
+ );
break;
}
@@ -121,8 +145,9 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'start',
- wfTimestamp( TS_ISO_8601, $row->pt_timestamp ) );
+ $this->setContinueEnumParameter( 'continue',
+ "$row->pt_timestamp|$row->pt_namespace|$row->pt_title"
+ );
break;
}
} else {
@@ -131,7 +156,10 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $this->getModulePrefix() );
+ $result->setIndexedTagName_internal(
+ array( 'query', $this->getModuleName() ),
+ $this->getModulePrefix()
+ );
} else {
$resultPageSet->populateFromTitles( $titles );
}
@@ -147,7 +175,6 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- global $wgRestrictionLevels;
return array(
'namespace' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -155,7 +182,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
),
'level' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => array_diff( $wgRestrictionLevels, array( '' ) )
+ ApiBase::PARAM_TYPE => array_diff( $this->getConfig()->get( 'RestrictionLevels' ), array( '' ) )
),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -190,6 +217,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
'level'
)
),
+ 'continue' => null,
);
}
@@ -211,48 +239,12 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
' level - Adds the protection level',
),
'level' => 'Only list titles with these protection levels',
- );
- }
-
- public function getResultProperties() {
- global $wgRestrictionLevels;
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'user' => array(
- 'user' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'userid' => 'integer'
- ),
- 'userid' => array(
- 'userid' => 'integer'
- ),
- 'comment' => array(
- 'comment' => 'string'
- ),
- 'parsedcomment' => array(
- 'parsedcomment' => 'string'
- ),
- 'expiry' => array(
- 'expiry' => 'timestamp'
- ),
- 'level' => array(
- 'level' => array(
- ApiBase::PROP_TYPE => array_diff( $wgRestrictionLevels, array( '' ) )
- )
- )
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return 'List all titles protected from creation';
+ return 'List all titles protected from creation.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php
index 79fe0498..5ddd9450 100644
--- a/includes/api/ApiQueryQueryPage.php
+++ b/includes/api/ApiQueryQueryPage.php
@@ -32,18 +32,13 @@
class ApiQueryQueryPage extends ApiQueryGeneratorBase {
private $qpMap;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'qp' );
- // We need to do this to make sure $wgQueryPages is set up
- // This SUCKS
- global $IP;
- require_once "$IP/includes/QueryPage.php";
-
// Build mapping from special page names to QueryPage classes
- global $wgQueryPages, $wgAPIUselessQueryPages;
+ $uselessQueryPages = $this->getConfig()->get( 'APIUselessQueryPages' );
$this->qpMap = array();
- foreach ( $wgQueryPages as $page ) {
- if ( !in_array( $page[1], $wgAPIUselessQueryPages ) ) {
+ foreach ( QueryPage::getPages() as $page ) {
+ if ( !in_array( $page[1], $uselessQueryPages ) ) {
$this->qpMap[$page[1]] = $page[0];
}
}
@@ -58,11 +53,9 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
*/
public function run( $resultPageSet = null ) {
- global $wgQueryCacheLimit;
-
$params = $this->extractRequestParams();
$result = $this->getResult();
@@ -82,7 +75,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
if ( $ts ) {
$r['cachedtimestamp'] = wfTimestamp( TS_ISO_8601, $ts );
}
- $r['maxresults'] = $wgQueryCacheLimit;
+ $r['maxresults'] = $this->getConfig()->get( 'QueryCacheLimit' );
}
}
$result->addValue( array( 'query' ), $this->getModuleName(), $r );
@@ -126,7 +119,10 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
}
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName(), 'results' ), 'page' );
+ $result->setIndexedTagName_internal(
+ array( 'query', $this->getModuleName(), 'results' ),
+ 'page'
+ );
} else {
$resultPageSet->populateFromTitles( $titles );
}
@@ -138,6 +134,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
if ( $qp->getRestriction() != '' ) {
return 'private';
}
+
return 'public';
}
@@ -166,46 +163,8 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- ApiBase::PROP_ROOT => array(
- 'name' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => false
- ),
- 'disabled' => array(
- ApiBase::PROP_TYPE => 'boolean',
- ApiBase::PROP_NULLABLE => false
- ),
- 'cached' => array(
- ApiBase::PROP_TYPE => 'boolean',
- ApiBase::PROP_NULLABLE => false
- ),
- 'cachedtimestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- '' => array(
- 'value' => 'string',
- 'timestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- ),
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Get a list provided by a QueryPage-based special page';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'specialpage-cantexecute' )
- ) );
+ return 'Get a list provided by a QueryPage-based special page.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index 2754bdae..530557e6 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -30,12 +30,10 @@
*
* @ingroup API
*/
-
class ApiQueryRandom extends ApiQueryGeneratorBase {
-
private $pageIDs;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rn' );
}
@@ -48,11 +46,11 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
}
/**
- * @param $randstr
- * @param $limit
- * @param $namespace
- * @param $resultPageSet ApiPageSet
- * @param $redirect
+ * @param string $randstr
+ * @param int $limit
+ * @param int $namespace
+ * @param ApiPageSet $resultPageSet
+ * @param bool $redirect
* @return void
*/
protected function prepareQuery( $randstr, $limit, $namespace, &$resultPageSet, $redirect ) {
@@ -62,7 +60,6 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
$this->addWhereFld( 'page_namespace', $namespace );
$this->addWhereRange( 'page_random', 'newer', $randstr, null );
$this->addWhereFld( 'page_is_redirect', $redirect );
- $this->addOption( 'USE INDEX', 'page_random' );
if ( is_null( $resultPageSet ) ) {
$this->addFields( array( 'page_id', 'page_title', 'page_namespace' ) );
} else {
@@ -71,7 +68,7 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return int
*/
protected function runQuery( $resultPageSet = null ) {
@@ -83,8 +80,8 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
// Prevent duplicates
if ( !in_array( $row->page_id, $this->pageIDs ) ) {
$fit = $this->getResult()->addValue(
- array( 'query', $this->getModuleName() ),
- null, $this->extractRowInfo( $row ) );
+ array( 'query', $this->getModuleName() ),
+ null, $this->extractRowInfo( $row ) );
if ( !$fit ) {
// We can't really query-continue a random list.
// Return an insanely high value so
@@ -102,7 +99,7 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function run( $resultPageSet = null ) {
@@ -110,14 +107,26 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
$result = $this->getResult();
$this->pageIDs = array();
- $this->prepareQuery( wfRandom(), $params['limit'], $params['namespace'], $resultPageSet, $params['redirect'] );
+ $this->prepareQuery(
+ wfRandom(),
+ $params['limit'],
+ $params['namespace'],
+ $resultPageSet,
+ $params['redirect']
+ );
$count = $this->runQuery( $resultPageSet );
if ( $count < $params['limit'] ) {
/* We got too few pages, we probably picked a high value
* for page_random. We'll just take the lowest ones, see
* also the comment in Title::getRandomTitle()
*/
- $this->prepareQuery( 0, $params['limit'] - $count, $params['namespace'], $resultPageSet, $params['redirect'] );
+ $this->prepareQuery(
+ 0,
+ $params['limit'] - $count,
+ $params['namespace'],
+ $resultPageSet,
+ $params['redirect']
+ );
$this->runQuery( $resultPageSet );
}
@@ -131,6 +140,7 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
$vals = array();
$vals['id'] = intval( $row->page_id );
ApiQueryBase::addTitleInfo( $vals, $title );
+
return $vals;
}
@@ -163,22 +173,15 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'id' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
return array(
- 'Get a set of random pages',
- 'NOTE: Pages are listed in a fixed sequence, only the starting point is random. This means that if, for example, "Main Page" is the first ',
- ' random page on your list, "List of fictional monkeys" will *always* be second, "List of people on stamps of Vanuatu" third, etc',
- 'NOTE: If the number of pages in the namespace is lower than rnlimit, you will get fewer pages. You will not get the same page twice'
+ 'Get a set of random pages.',
+ 'NOTE: Pages are listed in a fixed sequence, only the starting point is random.',
+ ' This means that if, for example, "Main Page" is the first random page on',
+ ' your list, "List of fictional monkeys" will *always* be second, "List of',
+ ' people on stamps of Vanuatu" third, etc.',
+ 'NOTE: If the number of pages in the namespace is lower than rnlimit, you will',
+ ' get fewer pages. You will not get the same page twice.'
);
}
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index 6b10bdc6..6f0c5d34 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -32,14 +32,14 @@
*/
class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rc' );
}
private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
- $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
- $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
- $fld_tags = false, $fld_sha1 = false, $token = array();
+ $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
+ $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
+ $fld_tags = false, $fld_sha1 = false, $token = array();
private $tokenFunctions;
@@ -47,7 +47,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title, $rc)
* it should return a token or false (permission denied)
- * @return array array(tokenname => function)
+ * @deprecated since 1.24
+ * @return array Array(tokenname => function)
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
@@ -64,14 +65,16 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' )
);
wfRunHooks( 'APIQueryRecentChangesTokens', array( &$this->tokenFunctions ) );
+
return $this->tokenFunctions;
}
/**
- * @param $pageid
- * @param $title
- * @param $rc RecentChange (optional)
- * @return bool|String
+ * @deprecated since 1.24
+ * @param int $pageid
+ * @param Title $title
+ * @param RecentChange|null $rc
+ * @return bool|string
*/
public static function getPatrolToken( $pageid, $title, $rc = null ) {
global $wgUser;
@@ -80,32 +83,31 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( $rc ) {
if ( ( $wgUser->useRCPatrol() && $rc->getAttribute( 'rc_type' ) == RC_EDIT ) ||
- ( $wgUser->useNPPatrol() && $rc->getAttribute( 'rc_type' ) == RC_NEW ) )
- {
- $validTokenUser = true;
- }
- } else {
- if ( $wgUser->useRCPatrol() || $wgUser->useNPPatrol() ) {
+ ( $wgUser->useNPPatrol() && $rc->getAttribute( 'rc_type' ) == RC_NEW )
+ ) {
$validTokenUser = true;
}
+ } elseif ( $wgUser->useRCPatrol() || $wgUser->useNPPatrol() ) {
+ $validTokenUser = true;
}
if ( $validTokenUser ) {
// The patrol token is always the same, let's exploit that
static $cachedPatrolToken = null;
+
if ( is_null( $cachedPatrolToken ) ) {
$cachedPatrolToken = $wgUser->getEditToken( 'patrol' );
}
+
return $cachedPatrolToken;
- } else {
- return false;
}
+ return false;
}
/**
* Sets internal state to include the desired properties in the output.
- * @param array $prop associative array of properties, only keys are used here
+ * @param array $prop Associative array of properties, only keys are used here
*/
public function initProperties( $prop ) {
$this->fld_comment = isset( $prop['comment'] );
@@ -135,7 +137,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/**
* Generates and outputs the result of this query based upon the provided parameters.
*
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
*/
public function run( $resultPageSet = null ) {
$user = $this->getUser();
@@ -145,7 +147,6 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Build our basic query. Namely, something along the lines of:
* SELECT * FROM recentchanges WHERE rc_timestamp > $start
* AND rc_timestamp < $end AND rc_namespace = $namespace
- * AND rc_deleted = 0
*/
$this->addTables( 'recentchanges' );
$index = array( 'recentchanges' => 'rc_timestamp' ); // May change
@@ -153,15 +154,12 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
-
- $timestamp = $this->getDB()->addQuotes( wfTimestamp( TS_MW, $cont[0] ) );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $db = $this->getDB();
+ $timestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
$id = intval( $cont[1] );
+ $this->dieContinueUsageIf( $id != $cont[1] );
$op = $params['dir'] === 'older' ? '<' : '>';
-
$this->addWhere(
"rc_timestamp $op $timestamp OR " .
"(rc_timestamp = $timestamp AND " .
@@ -176,10 +174,13 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
) );
$this->addWhereFld( 'rc_namespace', $params['namespace'] );
- $this->addWhereFld( 'rc_deleted', 0 );
if ( !is_null( $params['type'] ) ) {
- $this->addWhereFld( 'rc_type', $this->parseRCType( $params['type'] ) );
+ try {
+ $this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
+ } catch ( MWException $e ) {
+ ApiBase::dieDebug( __METHOD__, $e->getMessage() );
+ }
}
if ( !is_null( $params['show'] ) ) {
@@ -187,18 +188,26 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Check for conflicting parameters. */
if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
- || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
- || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
- || ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
- || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
+ || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
+ || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
+ || ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
+ || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
+ || ( isset( $show['patrolled'] ) && isset( $show['unpatrolled'] ) )
+ || ( isset( $show['!patrolled'] ) && isset( $show['unpatrolled'] ) )
) {
$this->dieUsageMsg( 'show' );
}
// Check permissions
- if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
+ if ( isset( $show['patrolled'] )
+ || isset( $show['!patrolled'] )
+ || isset( $show['unpatrolled'] )
+ ) {
if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
- $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
+ $this->dieUsage(
+ 'You need the patrol right to request the patrolled flag',
+ 'permissiondenied'
+ );
}
}
@@ -213,8 +222,21 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
$this->addWhereIf( 'page_is_redirect = 1', isset( $show['redirect'] ) );
+ if ( isset( $show['unpatrolled'] ) ) {
+ // See ChangesList:isUnpatrolled
+ if ( $user->useRCPatrol() ) {
+ $this->addWhere( 'rc_patrolled = 0' );
+ } elseif ( $user->useNPPatrol() ) {
+ $this->addWhere( 'rc_patrolled = 0' );
+ $this->addWhereFld( 'rc_type', RC_NEW );
+ }
+ }
+
// Don't throw log entries out the window here
- $this->addWhereIf( 'page_is_redirect = 0 OR page_is_redirect IS NULL', isset( $show['!redirect'] ) );
+ $this->addWhereIf(
+ 'page_is_redirect = 0 OR page_is_redirect IS NULL',
+ isset( $show['!redirect'] )
+ );
}
if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
@@ -235,6 +257,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Add the fields we're concerned with to our query. */
$this->addFields( array(
+ 'rc_id',
'rc_timestamp',
'rc_namespace',
'rc_title',
@@ -252,20 +275,26 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->initProperties( $prop );
if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
- $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
+ $this->dieUsage(
+ 'You need the patrol right to request the patrolled flag',
+ 'permissiondenied'
+ );
}
- $this->addFields( 'rc_id' );
/* Add fields to our query if they are specified as a needed parameter. */
$this->addFieldsIf( array( 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids );
$this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
- $this->addFieldsIf( 'rc_user', $this->fld_user );
- $this->addFieldsIf( 'rc_user_text', $this->fld_user || $this->fld_userid );
+ $this->addFieldsIf( 'rc_user', $this->fld_user || $this->fld_userid );
+ $this->addFieldsIf( 'rc_user_text', $this->fld_user );
$this->addFieldsIf( array( 'rc_minor', 'rc_type', 'rc_bot' ), $this->fld_flags );
$this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
- $this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo );
- $showRedirects = $this->fld_redirect || isset( $show['redirect'] ) || isset( $show['!redirect'] );
+ $this->addFieldsIf(
+ array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ),
+ $this->fld_loginfo
+ );
+ $showRedirects = $this->fld_redirect || isset( $show['redirect'] )
+ || isset( $show['!redirect'] );
}
if ( $this->fld_tags ) {
@@ -276,13 +305,15 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( $this->fld_sha1 ) {
$this->addTables( 'revision' );
- $this->addJoinConds( array( 'revision' => array( 'LEFT JOIN', array( 'rc_this_oldid=rev_id' ) ) ) );
+ $this->addJoinConds( array( 'revision' => array( 'LEFT JOIN',
+ array( 'rc_this_oldid=rev_id' ) ) ) );
$this->addFields( array( 'rev_sha1', 'rev_deleted' ) );
}
if ( $params['toponly'] || $showRedirects ) {
$this->addTables( 'page' );
- $this->addJoinConds( array( 'page' => array( 'LEFT JOIN', array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) );
+ $this->addJoinConds( array( 'page' => array( 'LEFT JOIN',
+ array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) );
$this->addFields( 'page_is_redirect' );
if ( $params['toponly'] ) {
@@ -294,7 +325,36 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $params['tag'] );
- $index['change_tag'] = 'change_tag_tag_id';
+ }
+
+ // Paranoia: avoid brute force searches (bug 17342)
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+ if ( $this->getRequest()->getCheck( 'namespace' ) ) {
+ // LogPage::DELETED_ACTION hides the affected page, too.
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = LogPage::DELETED_ACTION;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->makeList( array(
+ 'rc_type != ' . RC_LOG,
+ $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
+ ), LIST_OR ) );
+ }
}
$this->token = $params['token'];
@@ -311,9 +371,10 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Iterate through the rows, adding data extracted from them to our query result. */
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) . '|' . $row->rc_id );
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
break;
}
@@ -327,7 +388,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) . '|' . $row->rc_id );
+ $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
break;
}
} else {
@@ -346,69 +407,65 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/**
* Extracts from a single sql row the data needed to describe one recent change.
*
- * @param mixed $row The row from which to extract the data.
+ * @param stdClass $row The row from which to extract the data.
* @return array An array mapping strings (descriptors) to their respective string values.
* @access public
*/
public function extractRowInfo( $row ) {
/* Determine the title of the page that has been changed. */
$title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
+ $user = $this->getUser();
/* Our output data. */
$vals = array();
$type = intval( $row->rc_type );
+ $vals['type'] = RecentChange::parseFromRCType( $type );
- /* Determine what kind of change this was. */
- switch ( $type ) {
- case RC_EDIT:
- $vals['type'] = 'edit';
- break;
- case RC_NEW:
- $vals['type'] = 'new';
- break;
- case RC_MOVE:
- $vals['type'] = 'move';
- break;
- case RC_LOG:
- $vals['type'] = 'log';
- break;
- case RC_EXTERNAL:
- $vals['type'] = 'external';
- break;
- case RC_MOVE_OVER_REDIRECT:
- $vals['type'] = 'move over redirect';
- break;
- default:
- $vals['type'] = $type;
- }
+ $anyHidden = false;
/* Create a new entry in the result for the title. */
- if ( $this->fld_title ) {
- ApiQueryBase::addTitleInfo( $vals, $title );
+ if ( $this->fld_title || $this->fld_ids ) {
+ if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
+ $vals['actionhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $type !== RC_LOG ||
+ LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
+ ) {
+ if ( $this->fld_title ) {
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ }
+ if ( $this->fld_ids ) {
+ $vals['pageid'] = intval( $row->rc_cur_id );
+ $vals['revid'] = intval( $row->rc_this_oldid );
+ $vals['old_revid'] = intval( $row->rc_last_oldid );
+ }
+ }
}
- /* Add ids, such as rcid, pageid, revid, and oldid to the change's info. */
if ( $this->fld_ids ) {
$vals['rcid'] = intval( $row->rc_id );
- $vals['pageid'] = intval( $row->rc_cur_id );
- $vals['revid'] = intval( $row->rc_this_oldid );
- $vals['old_revid'] = intval( $row->rc_last_oldid );
}
- /* Add user data and 'anon' flag, if use is anonymous. */
+ /* Add user data and 'anon' flag, if user is anonymous. */
if ( $this->fld_user || $this->fld_userid ) {
-
- if ( $this->fld_user ) {
- $vals['user'] = $row->rc_user_text;
+ if ( $row->rc_deleted & Revision::DELETED_USER ) {
+ $vals['userhidden'] = '';
+ $anyHidden = true;
}
+ if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->rc_user_text;
+ }
- if ( $this->fld_userid ) {
- $vals['userid'] = $row->rc_user;
- }
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $row->rc_user;
+ }
- if ( !$row->rc_user ) {
- $vals['anon'] = '';
+ if ( !$row->rc_user ) {
+ $vals['anon'] = '';
+ }
}
}
@@ -437,12 +494,20 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
/* Add edit summary / log summary. */
- if ( $this->fld_comment && isset( $row->rc_comment ) ) {
- $vals['comment'] = $row->rc_comment;
- }
+ if ( $this->fld_comment || $this->fld_parsedcomment ) {
+ if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
+ $vals['commenthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
+ if ( $this->fld_comment && isset( $row->rc_comment ) ) {
+ $vals['comment'] = $row->rc_comment;
+ }
- if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
- $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
+ $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ }
+ }
}
if ( $this->fld_redirect ) {
@@ -456,19 +521,29 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$vals['patrolled'] = '';
}
+ if ( $this->fld_patrolled && ChangesList::isUnpatrolled( $row, $user ) ) {
+ $vals['unpatrolled'] = '';
+ }
+
if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
- $vals['logid'] = intval( $row->rc_logid );
- $vals['logtype'] = $row->rc_log_type;
- $vals['logaction'] = $row->rc_log_action;
- $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
- ApiQueryLogEvents::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp()
- );
+ if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
+ $vals['actionhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
+ $vals['logid'] = intval( $row->rc_logid );
+ $vals['logtype'] = $row->rc_log_type;
+ $vals['logaction'] = $row->rc_log_action;
+ $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
+ ApiQueryLogEvents::addLogParams(
+ $this->getResult(),
+ $vals,
+ $logEntry->getParameters(),
+ $logEntry->getType(),
+ $logEntry->getSubtype(),
+ $logEntry->getTimestamp()
+ );
+ }
}
if ( $this->fld_tags ) {
@@ -482,15 +557,16 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
- // The RevDel check should currently never pass due to the
- // rc_deleted = 0 condition in the WHERE clause, but in case that
- // ever changes we check it here too.
if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
$vals['sha1hidden'] = '';
- } elseif ( $row->rev_sha1 !== '' ) {
- $vals['sha1'] = wfBaseConvert( $row->rev_sha1, 36, 16, 40 );
- } else {
- $vals['sha1'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->rev_deleted, Revision::DELETED_TEXT, $user ) ) {
+ if ( $row->rev_sha1 !== '' ) {
+ $vals['sha1'] = wfBaseConvert( $row->rev_sha1, 36, 16, 40 );
+ } else {
+ $vals['sha1'] = '';
+ }
}
}
@@ -507,27 +583,11 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
}
- return $vals;
- }
-
- private function parseRCType( $type ) {
- if ( is_array( $type ) ) {
- $retval = array();
- foreach ( $type as $t ) {
- $retval[] = $this->parseRCType( $t );
- }
- return $retval;
- }
- switch ( $type ) {
- case 'edit':
- return RC_EDIT;
- case 'new':
- return RC_NEW;
- case 'log':
- return RC_LOG;
- case 'external':
- return RC_EXTERNAL;
+ if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
}
+
+ return $vals;
}
public function getCacheMode( $params ) {
@@ -541,10 +601,14 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( isset( $params['token'] ) ) {
return 'private';
}
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMessage() among other things
return 'anon-public-user-private';
}
+
return 'public';
}
@@ -595,6 +659,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
)
),
'token' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),
@@ -610,7 +675,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
'redirect',
'!redirect',
'patrolled',
- '!patrolled'
+ '!patrolled',
+ 'unpatrolled'
)
),
'limit' => array(
@@ -636,6 +702,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to end enumerating',
@@ -655,7 +722,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
' ids - Adds the page ID, recent changes ID and the new and old revision ID',
' sizes - Adds the new and old page length in bytes',
' redirect - Tags edit if page is a redirect',
- ' patrolled - Tags edits that have been patrolled',
+ ' patrolled - Tags patrollable edits as being patrolled or unpatrolled',
' loginfo - Adds log information (logid, logtype, etc) to log entries',
' tags - Lists tags for the entry',
' sha1 - Adds the content checksum for entries associated with a revision',
@@ -673,117 +740,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- global $wgLogTypes;
- $props = array(
- '' => array(
- 'type' => array(
- ApiBase::PROP_TYPE => array(
- 'edit',
- 'new',
- 'move',
- 'log',
- 'move over redirect'
- )
- )
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string',
- 'new_ns' => array(
- ApiBase::PROP_TYPE => 'namespace',
- ApiBase::PROP_NULLABLE => true
- ),
- 'new_title' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'ids' => array(
- 'rcid' => 'integer',
- 'pageid' => 'integer',
- 'revid' => 'integer',
- 'old_revid' => 'integer'
- ),
- 'user' => array(
- 'user' => 'string',
- 'anon' => 'boolean'
- ),
- 'userid' => array(
- 'userid' => 'integer',
- 'anon' => 'boolean'
- ),
- 'flags' => array(
- 'bot' => 'boolean',
- 'new' => 'boolean',
- 'minor' => 'boolean'
- ),
- 'sizes' => array(
- 'oldlen' => 'integer',
- 'newlen' => 'integer'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'comment' => array(
- 'comment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'parsedcomment' => array(
- 'parsedcomment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'redirect' => array(
- 'redirect' => 'boolean'
- ),
- 'patrolled' => array(
- 'patrolled' => 'boolean'
- ),
- 'loginfo' => array(
- 'logid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'logtype' => array(
- ApiBase::PROP_TYPE => $wgLogTypes,
- ApiBase::PROP_NULLABLE => true
- ),
- 'logaction' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'sha1' => array(
- 'sha1' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'sha1hidden' => array(
- ApiBase::PROP_TYPE => 'boolean',
- ApiBase::PROP_NULLABLE => true
- ),
- ),
- );
-
- self::addTokenProperties( $props, $this->getTokenFunctions() );
-
- return $props;
- }
-
public function getDescription() {
- return 'Enumerate recent changes';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'show' ),
- array( 'code' => 'permissiondenied', 'info' => 'You need the patrol right to request the patrolled flag' ),
- array( 'code' => 'user-excludeuser', 'info' => 'user and excludeuser cannot be used together' ),
- ) );
+ return 'Enumerate recent changes.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index 415288ef..da4ec195 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -25,9 +25,10 @@
*/
/**
- * A query action to enumerate revisions of a given page, or show top revisions of multiple pages.
- * Various pieces of information may be shown - flags, comments, and the actual wiki markup of the rev.
- * In the enumeration mode, ranges of revisions may be requested and filtered.
+ * A query action to enumerate revisions of a given page, or show top revisions
+ * of multiple pages. Various pieces of information may be shown - flags,
+ * comments, and the actual wiki markup of the rev. In the enumeration mode,
+ * ranges of revisions may be requested and filtered.
*
* @ingroup API
*/
@@ -36,16 +37,18 @@ class ApiQueryRevisions extends ApiQueryBase {
private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
$token, $parseContent, $contentFormat;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rv' );
}
- private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false, $fld_sha1 = false,
- $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
- $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
+ private $fld_ids = false, $fld_flags = false, $fld_timestamp = false,
+ $fld_size = false, $fld_sha1 = false, $fld_comment = false,
+ $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
+ $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
private $tokenFunctions;
+ /** @deprecated since 1.24 */
protected function getTokenFunctions() {
// tokenname => function
// function prototype is func($pageid, $title, $rev)
@@ -65,20 +68,23 @@ class ApiQueryRevisions extends ApiQueryBase {
'rollback' => array( 'ApiQueryRevisions', 'getRollbackToken' )
);
wfRunHooks( 'APIQueryRevisionsTokens', array( &$this->tokenFunctions ) );
+
return $this->tokenFunctions;
}
/**
- * @param $pageid
- * @param $title Title
- * @param $rev Revision
- * @return bool|String
+ * @deprecated since 1.24
+ * @param int $pageid
+ * @param Title $title
+ * @param Revision $rev
+ * @return bool|string
*/
public static function getRollbackToken( $pageid, $title, $rev ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'rollback' ) ) {
return false;
}
+
return $wgUser->getEditToken(
array( $title->getPrefixedText(), $rev->getUserText() ) );
}
@@ -91,9 +97,9 @@ class ApiQueryRevisions extends ApiQueryBase {
// Enumerating revisions on multiple pages make it extremely
// difficult to manage continuations and require additional SQL indexes
$enumRevMode = ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ||
- !is_null( $params['limit'] ) || !is_null( $params['startid'] ) ||
- !is_null( $params['endid'] ) || $params['dir'] === 'newer' ||
- !is_null( $params['start'] ) || !is_null( $params['end'] ) );
+ !is_null( $params['limit'] ) || !is_null( $params['startid'] ) ||
+ !is_null( $params['endid'] ) || $params['dir'] === 'newer' ||
+ !is_null( $params['start'] ) || !is_null( $params['end'] ) );
$pageSet = $this->getPageSet();
$pageCount = $pageSet->getGoodTitleCount();
@@ -105,11 +111,20 @@ class ApiQueryRevisions extends ApiQueryBase {
}
if ( $revCount > 0 && $enumRevMode ) {
- $this->dieUsage( 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids' );
+ $this->dieUsage(
+ 'The revids= parameter may not be used with the list options ' .
+ '(limit, startid, endid, dirNewer, start, end).',
+ 'revids'
+ );
}
if ( $pageCount > 1 && $enumRevMode ) {
- $this->dieUsage( 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.', 'multpages' );
+ $this->dieUsage(
+ 'titles, pageids or a generator was used to supply multiple pages, ' .
+ 'but the limit, startid, endid, dirNewer, user, excludeuser, start ' .
+ 'and end parameters may only be used on a single page.',
+ 'multpages'
+ );
}
if ( !is_null( $params['difftotext'] ) ) {
@@ -119,8 +134,12 @@ class ApiQueryRevisions extends ApiQueryBase {
$params['diffto'] = 0;
}
if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 )
- && $params['diffto'] != 'prev' && $params['diffto'] != 'next' ) {
- $this->dieUsage( 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"', 'diffto' );
+ && $params['diffto'] != 'prev' && $params['diffto'] != 'next'
+ ) {
+ $this->dieUsage(
+ 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"',
+ 'diffto'
+ );
}
// Check whether the revision exists and is readable,
// DifferenceEngine returns a rather ambiguous empty
@@ -130,7 +149,7 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( !$difftoRev ) {
$this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
}
- if ( $difftoRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( !$difftoRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
$this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
$params['diffto'] = null;
}
@@ -163,9 +182,6 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->contentFormat = $params['contentformat'];
}
- // Possible indexes used
- $index = array();
-
$userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
$botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
$limit = $params['limit'];
@@ -181,18 +197,21 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( isset( $prop['tags'] ) ) {
$this->fld_tags = true;
$this->addTables( 'tag_summary' );
- $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) );
+ $this->addJoinConds(
+ array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) )
+ );
$this->addFields( 'ts_tags' );
}
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
- $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
+ $this->addJoinConds(
+ array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) )
+ );
$this->addWhereFld( 'ct_tag', $params['tag'] );
- $index['change_tag'] = 'change_tag_tag_id';
}
- if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
+ if ( isset( $prop['content'] ) || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
// For each page we will request, the user must have read rights for that page
$user = $this->getUser();
/** @var $title Title */
@@ -299,7 +318,16 @@ class ApiQueryRevisions extends ApiQueryBase {
}
if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
// Paranoia: avoid brute force searches (bug 17342)
- $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$this->getUser()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
+ }
}
} elseif ( $revCount > 0 ) {
$max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
@@ -349,7 +377,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->addOption( 'ORDER BY', array(
'rev_page',
'rev_id'
- ));
+ ) );
// assumption testing -- we should never get more then $pageCount rows.
$limit = $pageCount;
@@ -358,14 +386,14 @@ class ApiQueryRevisions extends ApiQueryBase {
}
$this->addOption( 'LIMIT', $limit + 1 );
- $this->addOption( 'USE INDEX', $index );
$count = 0;
$res = $this->select( __METHOD__ );
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
if ( !$enumRevMode ) {
ApiBase::dieDebug( __METHOD__, 'Got more rows then expected' ); // bug report
}
@@ -391,7 +419,9 @@ class ApiQueryRevisions extends ApiQueryBase {
private function extractRowInfo( $row ) {
$revision = new Revision( $row );
$title = $revision->getTitle();
+ $user = $this->getUser();
$vals = array();
+ $anyHidden = false;
if ( $this->fld_ids ) {
$vals['revid'] = intval( $revision->getId() );
@@ -408,11 +438,13 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( $this->fld_user || $this->fld_userid ) {
if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
$vals['userhidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_USER, $user ) ) {
if ( $this->fld_user ) {
- $vals['user'] = $revision->getUserText();
+ $vals['user'] = $revision->getRawUserText();
}
- $userid = $revision->getUser();
+ $userid = $revision->getRawUser();
if ( !$userid ) {
$vals['anon'] = '';
}
@@ -435,14 +467,18 @@ class ApiQueryRevisions extends ApiQueryBase {
}
}
- if ( $this->fld_sha1 && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
- if ( $revision->getSha1() != '' ) {
- $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
- } else {
- $vals['sha1'] = '';
+ if ( $this->fld_sha1 ) {
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['sha1hidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_TEXT, $user ) ) {
+ if ( $revision->getSha1() != '' ) {
+ $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
+ } else {
+ $vals['sha1'] = '';
+ }
}
- } elseif ( $this->fld_sha1 ) {
- $vals['sha1hidden'] = '';
}
if ( $this->fld_contentmodel ) {
@@ -452,8 +488,10 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( $this->fld_comment || $this->fld_parsedcomment ) {
if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
- } else {
- $comment = $revision->getComment();
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $comment = $revision->getRawComment();
if ( $this->fld_comment ) {
$vals['comment'] = $comment;
@@ -490,25 +528,38 @@ class ApiQueryRevisions extends ApiQueryBase {
$content = null;
global $wgParser;
if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
- $content = $revision->getContent();
+ $content = $revision->getContent( Revision::FOR_THIS_USER, $this->getUser() );
// Expand templates after getting section content because
// template-added sections don't count and Parser::preprocess()
// will have less input
if ( $content && $this->section !== false ) {
$content = $content->getSection( $this->section, false );
if ( !$content ) {
- $this->dieUsage( "There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection' );
+ $this->dieUsage(
+ "There is no section {$this->section} in r" . $revision->getId(),
+ 'nosuchsection'
+ );
}
}
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['texthidden'] = '';
+ $anyHidden = true;
+ } elseif ( !$content ) {
+ $vals['textmissing'] = '';
+ }
}
- if ( $this->fld_content && $content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $this->fld_content && $content ) {
$text = null;
if ( $this->generateXML ) {
if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
$t = $content->getNativeData(); # note: don't set $text
- $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
+ $wgParser->startExternalParse(
+ $title,
+ ParserOptions::newFromContext( $this->getContext() ),
+ OT_PREPROCESS
+ );
$dom = $wgParser->preprocessToDom( $t );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
$xml = $dom->saveXML();
@@ -518,8 +569,8 @@ class ApiQueryRevisions extends ApiQueryBase {
$vals['parsetree'] = $xml;
} else {
$this->setWarning( "Conversion to XML is supported for wikitext only, " .
- $title->getPrefixedDBkey() .
- " uses content model " . $content->getModel() );
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() );
}
}
@@ -528,7 +579,11 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
$text = $content->getNativeData();
- $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ $text = $wgParser->preprocess(
+ $text,
+ $title,
+ ParserOptions::newFromContext( $this->getContext() )
+ );
} else {
$this->setWarning( "Template expansion is supported for wikitext only, " .
$title->getPrefixedDBkey() .
@@ -538,7 +593,11 @@ class ApiQueryRevisions extends ApiQueryBase {
}
}
if ( $this->parseContent ) {
- $po = $content->getParserOutput( $title, $revision->getId(), ParserOptions::newFromContext( $this->getContext() ) );
+ $po = $content->getParserOutput(
+ $title,
+ $revision->getId(),
+ ParserOptions::newFromContext( $this->getContext() )
+ );
$text = $po->getText();
}
@@ -550,7 +609,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$name = $title->getPrefixedDBkey();
$this->dieUsage( "The requested format {$this->contentFormat} is not supported " .
- "for content model $model used by $name", 'badformat' );
+ "for content model $model used by $name", 'badformat' );
}
$text = $content->serialize( $format );
@@ -564,21 +623,12 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( $text !== false ) {
ApiResult::setContent( $vals, $text );
}
- } elseif ( $this->fld_content ) {
- if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
- $vals['texthidden'] = '';
- } else {
- $vals['textmissing'] = '';
- }
}
- if ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
- global $wgAPIMaxUncachedDiffs;
+ if ( $content && ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) ) {
static $n = 0; // Number of uncached diffs we've had
- if ( is_null( $content ) ) {
- $vals['textmissing'] = '';
- } elseif ( $n < $wgAPIMaxUncachedDiffs ) {
+ if ( $n < $this->getConfig()->get( 'APIMaxUncachedDiffs' ) ) {
$vals['diff'] = array();
$context = new DerivativeContext( $this->getContext() );
$context->setTitle( $title );
@@ -588,15 +638,21 @@ class ApiQueryRevisions extends ApiQueryBase {
$model = $title->getContentModel();
if ( $this->contentFormat
- && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat ) ) {
+ && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat )
+ ) {
$name = $title->getPrefixedDBkey();
$this->dieUsage( "The requested format {$this->contentFormat} is not supported for " .
- "content model $model used by $name", 'badformat' );
+ "content model $model used by $name", 'badformat' );
}
- $difftocontent = ContentHandler::makeContent( $this->difftotext, $title, $model, $this->contentFormat );
+ $difftocontent = ContentHandler::makeContent(
+ $this->difftotext,
+ $title,
+ $model,
+ $this->contentFormat
+ );
$engine = $handler->createDifferenceEngine( $context );
$engine->setContent( $content, $difftocontent );
@@ -614,6 +670,11 @@ class ApiQueryRevisions extends ApiQueryBase {
$vals['diff']['notcached'] = '';
}
}
+
+ if ( $anyHidden && $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
+ }
+
return $vals;
}
@@ -621,10 +682,14 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( isset( $params['token'] ) ) {
return 'private';
}
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMessage() among other things
return 'anon-public-user-private';
}
+
return 'public';
}
@@ -685,6 +750,7 @@ class ApiQueryRevisions extends ApiQueryBase {
'parse' => false,
'section' => null,
'token' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),
@@ -700,6 +766,7 @@ class ApiQueryRevisions extends ApiQueryBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'prop' => array(
'Which properties to get for each revision:',
@@ -733,120 +800,47 @@ class ApiQueryRevisions extends ApiQueryBase {
'continue' => 'When more results are available, use this to continue',
'diffto' => array( 'Revision ID to diff each revision to.',
'Use "prev", "next" and "cur" for the previous, next and current revision respectively' ),
- 'difftotext' => array( 'Text to diff each revision to. Only diffs a limited number of revisions.',
- "Overrides {$p}diffto. If {$p}section is set, only that section will be diffed against this text" ),
+ 'difftotext' => array(
+ 'Text to diff each revision to. Only diffs a limited number of revisions.',
+ "Overrides {$p}diffto. If {$p}section is set, only that section will be",
+ 'diffed against this text',
+ ),
'tag' => 'Only list revisions tagged with this tag',
'contentformat' => 'Serialization format used for difftotext and expected for output of content',
);
}
- public function getResultProperties() {
- $props = array(
- '' => array(),
- 'ids' => array(
- 'revid' => 'integer',
- 'parentid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'flags' => array(
- 'minor' => 'boolean'
- ),
- 'user' => array(
- 'userhidden' => 'boolean',
- 'user' => 'string',
- 'anon' => 'boolean'
- ),
- 'userid' => array(
- 'userhidden' => 'boolean',
- 'userid' => 'integer',
- 'anon' => 'boolean'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'size' => array(
- 'size' => 'integer'
- ),
- 'sha1' => array(
- 'sha1' => 'string'
- ),
- 'comment' => array(
- 'commenthidden' => 'boolean',
- 'comment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'parsedcomment' => array(
- 'commenthidden' => 'boolean',
- 'parsedcomment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'content' => array(
- '*' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'texthidden' => 'boolean',
- 'textmissing' => 'boolean',
- ),
- 'contentmodel' => array(
- 'contentmodel' => 'string'
- ),
- );
-
- self::addTokenProperties( $props, $this->getTokenFunctions() );
-
- return $props;
- }
-
public function getDescription() {
return array(
- 'Get revision information',
+ 'Get revision information.',
'May be used in several ways:',
- ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter',
- ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params',
- ' 3) Get data about a set of revisions by setting their IDs with revids parameter',
- 'All parameters marked as (enum) may only be used with a single page (#2)'
+ ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter.',
+ ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params.',
+ ' 3) Get data about a set of revisions by setting their IDs with revids parameter.',
+ 'All parameters marked as (enum) may only be used with a single page (#2).'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'nosuchrevid', 'diffto' ),
- array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options '
- . '(limit, startid, endid, dirNewer, start, end).' ),
- array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, '
- . ' but the limit, startid, endid, dirNewer, user, excludeuser, '
- . 'start and end parameters may only be used on a single page.' ),
- array( 'code' => 'diffto', 'info' => 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"' ),
- array( 'code' => 'badparams', 'info' => 'start and startid cannot be used together' ),
- array( 'code' => 'badparams', 'info' => 'end and endid cannot be used together' ),
- array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
- array( 'code' => 'nosuchsection', 'info' => 'There is no section section in rID' ),
- array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied '
- . ' to the page\'s content model' ),
- ) );
- }
-
public function getExamples() {
return array(
'Get data with content for the last revision of titles "API" and "Main Page"',
- ' api.php?action=query&prop=revisions&titles=API|Main%20Page&rvprop=timestamp|user|comment|content',
+ ' api.php?action=query&prop=revisions&titles=API|Main%20Page&' .
+ 'rvprop=timestamp|user|comment|content',
'Get last 5 revisions of the "Main Page"',
- ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment',
'Get first 5 revisions of the "Main Page"',
- ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment&rvdir=newer',
'Get first 5 revisions of the "Main Page" made after 2006-05-01',
- ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000',
'Get first 5 revisions of the "Main Page" that were not made made by anonymous user "127.0.0.1"',
- ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1',
'Get first 5 revisions of the "Main Page" that were made by the user "MediaWiki default"',
- ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvuser=MediaWiki%20default',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment&rvuser=MediaWiki%20default',
);
}
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index 36b55979..b7dcd0ed 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -39,7 +39,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
*/
const BACKEND_NULL_PARAM = 'database-backed';
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'sr' );
}
@@ -52,7 +52,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -63,15 +63,25 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$limit = $params['limit'];
$query = $params['search'];
$what = $params['what'];
+ $interwiki = $params['interwiki'];
$searchInfo = array_flip( $params['info'] );
$prop = array_flip( $params['prop'] );
+ // Deprecated parameters
+ if ( isset( $prop['hasrelated'] ) ) {
+ $this->logFeatureUsage( 'action=search&srprop=hasrelated' );
+ $this->setWarning( 'srprop=hasrelated has been deprecated' );
+ }
+ if ( isset( $prop['score'] ) ) {
+ $this->logFeatureUsage( 'action=search&srprop=score' );
+ $this->setWarning( 'srprop=score has been deprecated' );
+ }
+
// Create search engine instance and set options
$search = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ?
SearchEngine::create( $params['backend'] ) : SearchEngine::create();
$search->setLimitOffset( $limit + 1, $params['offset'] );
$search->setNamespaces( $params['namespace'] );
- $search->showRedirects = $params['redirects'];
$query = $search->transformSearchTerm( $query );
$query = $search->replacePrefixes( $query );
@@ -112,12 +122,12 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$totalhits = $matches->getTotalHits();
if ( $totalhits !== null ) {
$apiResult->addValue( array( 'query', 'searchinfo' ),
- 'totalhits', $totalhits );
+ 'totalhits', $totalhits );
}
}
if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) {
$apiResult->addValue( array( 'query', 'searchinfo' ),
- 'suggestion', $matches->getSuggestionQuery() );
+ 'suggestion', $matches->getSuggestionQuery() );
}
// Add the search results to the result
@@ -127,8 +137,9 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$result = $matches->next();
while ( $result ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional items to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional items to be had. Stop here...
$this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] );
break;
}
@@ -156,9 +167,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
if ( isset( $prop['timestamp'] ) ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $result->getTimestamp() );
}
- if ( !is_null( $result->getScore() ) && isset( $prop['score'] ) ) {
- $vals['score'] = $result->getScore();
- }
if ( isset( $prop['titlesnippet'] ) ) {
$vals['titlesnippet'] = $result->getTitleSnippet( $terms );
}
@@ -178,13 +186,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$vals['sectionsnippet'] = $result->getSectionSnippet();
}
}
- if ( isset( $prop['hasrelated'] ) && $result->hasRelated() ) {
- $vals['hasrelated'] = '';
- }
// Add item to results and see whether it fits
$fit = $apiResult->addValue( array( 'query', $this->getModuleName() ),
- null, $vals );
+ null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 );
break;
@@ -196,10 +201,55 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$result = $matches->next();
}
+ $hasInterwikiResults = false;
+ if ( $interwiki && $resultPageSet === null && $matches->hasInterwikiResults() ) {
+ $matches = $matches->getInterwikiResults();
+ $hasInterwikiResults = true;
+
+ // Include number of results if requested
+ if ( isset( $searchInfo['totalhits'] ) ) {
+ $totalhits = $matches->getTotalHits();
+ if ( $totalhits !== null ) {
+ $apiResult->addValue( array( 'query', 'interwikisearchinfo' ),
+ 'totalhits', $totalhits );
+ }
+ }
+
+ $result = $matches->next();
+ while ( $result ) {
+ $title = $result->getTitle();
+ $vals = array(
+ 'namespace' => $result->getInterwikiNamespaceText(),
+ 'title' => $title->getText(),
+ 'url' => $title->getFullUrl(),
+ );
+
+ // Add item to results and see whether it fits
+ $fit = $apiResult->addValue(
+ array( 'query', 'interwiki' . $this->getModuleName(), $result->getInterwikiPrefix() ),
+ null,
+ $vals
+ );
+
+ if ( !$fit ) {
+ // We hit the limit. We can't really provide any meaningful
+ // pagination info so just bail out
+ break;
+ }
+
+ $result = $matches->next();
+ }
+ }
+
if ( is_null( $resultPageSet ) ) {
$apiResult->setIndexedTagName_internal( array(
- 'query', $this->getModuleName()
- ), 'p' );
+ 'query', $this->getModuleName()
+ ), 'p' );
+ if ( $hasInterwikiResults ) {
+ $apiResult->setIndexedTagName_internal( array(
+ 'query', 'interwiki' . $this->getModuleName()
+ ), 'p' );
+ }
} else {
$resultPageSet->populateFromTitles( $titles );
}
@@ -210,8 +260,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- global $wgSearchType;
-
$params = array(
'search' => array(
ApiBase::PARAM_TYPE => 'string',
@@ -255,7 +303,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
),
ApiBase::PARAM_ISMULTI => true,
),
- 'redirects' => false,
'offset' => 0,
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -263,7 +310,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => ApiBase::LIMIT_SML1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_SML2
- )
+ ),
+ 'interwiki' => false,
);
$alternatives = SearchEngine::getSearchTypes();
@@ -272,7 +320,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$alternatives[0] = self::BACKEND_NULL_PARAM;
}
$params['backend'] = array(
- ApiBase::PARAM_DFLT => $wgSearchType,
+ ApiBase::PARAM_DFLT => $this->getConfig()->get( 'SearchType' ),
ApiBase::PARAM_TYPE => $alternatives,
);
}
@@ -291,18 +339,18 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
' size - Adds the size of the page in bytes',
' wordcount - Adds the word count of the page',
' timestamp - Adds the timestamp of when the page was last edited',
- ' score - Adds the score (if any) from the search engine',
+ ' score - DEPRECATED and IGNORED',
' snippet - Adds a parsed snippet of the page',
' titlesnippet - Adds a parsed snippet of the page title',
' redirectsnippet - Adds a parsed snippet of the redirect title',
' redirecttitle - Adds the title of the matching redirect',
' sectionsnippet - Adds a parsed snippet of the matching section title',
' sectiontitle - Adds the title of the matching section',
- ' hasrelated - Indicates whether a related search is available',
+ ' hasrelated - DEPRECATED and IGNORED',
),
- 'redirects' => 'Include redirect pages in the search',
'offset' => 'Use this value to continue paging (return by query)',
- 'limit' => 'How many total pages to return'
+ 'limit' => 'How many total pages to return',
+ 'interwiki' => 'Include interwiki results in the search, if available'
);
if ( count( SearchEngine::getSearchTypes() ) > 1 ) {
@@ -312,73 +360,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
return $descriptions;
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'snippet' => array(
- 'snippet' => 'string'
- ),
- 'size' => array(
- 'size' => 'integer'
- ),
- 'wordcount' => array(
- 'wordcount' => 'integer'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'score' => array(
- 'score' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'titlesnippet' => array(
- 'titlesnippet' => 'string'
- ),
- 'redirecttitle' => array(
- 'redirecttitle' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'redirectsnippet' => array(
- 'redirectsnippet' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'sectiontitle' => array(
- 'sectiontitle' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'sectionsnippet' => array(
- 'sectionsnippet' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'hasrelated' => array(
- 'hasrelated' => 'boolean'
- )
- );
- }
-
public function getDescription() {
- return 'Perform a full text search';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'search-text-disabled', 'info' => 'text search is disabled' ),
- array( 'code' => 'search-title-disabled', 'info' => 'title search is disabled' ),
- array( 'code' => 'search-error', 'info' => 'search error has occurred' ),
- ) );
+ return 'Perform a full text search.';
}
public function getExamples() {
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index a7767062..311438fd 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -31,7 +31,7 @@
*/
class ApiQuerySiteinfo extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'si' );
}
@@ -78,6 +78,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
case 'rightsinfo':
$fit = $this->appendRightsInfo( $p );
break;
+ case 'restrictions':
+ $fit = $this->appendRestrictions( $p );
+ break;
case 'languages':
$fit = $this->appendLanguages( $p );
break;
@@ -99,6 +102,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
case 'protocols':
$fit = $this->appendProtocols( $p );
break;
+ case 'defaultoptions':
+ $fit = $this->appendDefaultOptions( $p );
+ break;
default:
ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
}
@@ -106,7 +112,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
// Abuse siprop as a query-continue parameter
// and set it to all unprocessed props
$this->setContinueEnumParameter( 'prop', implode( '|',
- array_diff( $params['prop'], $done ) ) );
+ array_diff( $params['prop'], $done ) ) );
break;
}
$done[] = $p;
@@ -114,29 +120,37 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
protected function appendGeneralInfo( $property ) {
- global $wgContLang,
- $wgDisableLangConversion,
- $wgDisableTitleConversion;
+ global $wgContLang;
+
+ $config = $this->getConfig();
$data = array();
$mainPage = Title::newMainPage();
$data['mainpage'] = $mainPage->getPrefixedText();
$data['base'] = wfExpandUrl( $mainPage->getFullURL(), PROTO_CURRENT );
- $data['sitename'] = $GLOBALS['wgSitename'];
- $data['logo'] = $GLOBALS['wgLogo'];
- $data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}";
- $data['phpversion'] = phpversion();
+ $data['sitename'] = $config->get( 'Sitename' );
+
+ // wgLogo can either be a relative or an absolute path
+ // make sure we always return an absolute path
+ $data['logo'] = wfExpandUrl( $config->get( 'Logo' ), PROTO_RELATIVE );
+
+ $data['generator'] = "MediaWiki {$config->get( 'Version' )}";
+
+ $data['phpversion'] = PHP_VERSION;
$data['phpsapi'] = PHP_SAPI;
- $data['dbtype'] = $GLOBALS['wgDBtype'];
+ if ( defined( 'HHVM_VERSION' ) ) {
+ $data['hhvmversion'] = HHVM_VERSION;
+ }
+ $data['dbtype'] = $config->get( 'DBtype' );
$data['dbversion'] = $this->getDB()->getServerVersion();
$allowFrom = array( '' );
$allowException = true;
- if ( !$GLOBALS['wgAllowExternalImages'] ) {
- if ( $GLOBALS['wgEnableImageWhitelist'] ) {
+ if ( !$config->get( 'AllowExternalImages' ) ) {
+ if ( $config->get( 'EnableImageWhitelist' ) ) {
$data['imagewhitelistenabled'] = '';
}
- $allowFrom = $GLOBALS['wgAllowExternalImagesFrom'];
+ $allowFrom = $config->get( 'AllowExternalImagesFrom' );
$allowException = !empty( $allowFrom );
}
if ( $allowException ) {
@@ -144,17 +158,21 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$this->getResult()->setIndexedTagName( $data['externalimages'], 'prefix' );
}
- if ( !$wgDisableLangConversion ) {
+ if ( !$config->get( 'DisableLangConversion' ) ) {
$data['langconversion'] = '';
}
- if ( !$wgDisableTitleConversion ) {
+ if ( !$config->get( 'DisableTitleConversion' ) ) {
$data['titleconversion'] = '';
}
if ( $wgContLang->linkPrefixExtension() ) {
- $data['linkprefix'] = wfMessage( 'linkprefix' )->inContentLanguage()->text();
+ $linkPrefixCharset = $wgContLang->linkPrefixCharset();
+ $data['linkprefixcharset'] = $linkPrefixCharset;
+ // For backwards compatibility
+ $data['linkprefix'] = "/^((?>.*[^$linkPrefixCharset]|))(.+)$/sDu";
} else {
+ $data['linkprefixcharset'] = '';
$data['linkprefix'] = '';
}
@@ -165,24 +183,22 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['linktrail'] = '';
}
- $git = SpecialVersion::getGitHeadSha1( $GLOBALS['IP'] );
+ global $IP;
+ $git = SpecialVersion::getGitHeadSha1( $IP );
if ( $git ) {
$data['git-hash'] = $git;
+ $data['git-branch'] =
+ SpecialVersion::getGitCurrentBranch( $GLOBALS['IP'] );
} else {
- $svn = SpecialVersion::getSvnRevision( $GLOBALS['IP'] );
+ $svn = SpecialVersion::getSvnRevision( $IP );
if ( $svn ) {
$data['rev'] = $svn;
}
}
// 'case-insensitive' option is reserved for future
- $data['case'] = $GLOBALS['wgCapitalLinks'] ? 'first-letter' : 'case-sensitive';
-
- if ( isset( $GLOBALS['wgRightsCode'] ) ) {
- $data['rightscode'] = $GLOBALS['wgRightsCode'];
- }
- $data['rights'] = $GLOBALS['wgRightsText'];
- $data['lang'] = $GLOBALS['wgLanguageCode'];
+ $data['case'] = $config->get( 'CapitalLinks' ) ? 'first-letter' : 'case-sensitive';
+ $data['lang'] = $config->get( 'LanguageCode' );
$fallbacks = array();
foreach ( $wgContLang->getFallbackLanguages() as $code ) {
@@ -194,7 +210,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
if ( $wgContLang->hasVariants() ) {
$variants = array();
foreach ( $wgContLang->getVariants() as $code ) {
- $variants[] = array( 'code' => $code );
+ $variants[] = array(
+ 'code' => $code,
+ 'name' => $wgContLang->getVariantname( $code ),
+ );
}
$data['variants'] = $variants;
$this->getResult()->setIndexedTagName( $data['variants'], 'lang' );
@@ -209,12 +228,12 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['readonly'] = '';
$data['readonlyreason'] = wfReadOnlyReason();
}
- if ( $GLOBALS['wgEnableWriteAPI'] ) {
+ if ( $config->get( 'EnableWriteAPI' ) ) {
$data['writeapi'] = '';
}
- $tz = $GLOBALS['wgLocaltimezone'];
- $offset = $GLOBALS['wgLocalTZoffset'];
+ $tz = $config->get( 'Localtimezone' );
+ $offset = $config->get( 'LocalTZoffset' );
if ( is_null( $tz ) ) {
$tz = 'UTC';
$offset = 0;
@@ -223,20 +242,36 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$data['timezone'] = $tz;
$data['timeoffset'] = intval( $offset );
- $data['articlepath'] = $GLOBALS['wgArticlePath'];
- $data['scriptpath'] = $GLOBALS['wgScriptPath'];
- $data['script'] = $GLOBALS['wgScript'];
- $data['variantarticlepath'] = $GLOBALS['wgVariantArticlePath'];
- $data['server'] = $GLOBALS['wgServer'];
+ $data['articlepath'] = $config->get( 'ArticlePath' );
+ $data['scriptpath'] = $config->get( 'ScriptPath' );
+ $data['script'] = $config->get( 'Script' );
+ $data['variantarticlepath'] = $config->get( 'VariantArticlePath' );
+ $data['server'] = $config->get( 'Server' );
+ $data['servername'] = $config->get( 'ServerName' );
$data['wikiid'] = wfWikiID();
$data['time'] = wfTimestamp( TS_ISO_8601, time() );
- if ( $GLOBALS['wgMiserMode'] ) {
+ if ( $config->get( 'MiserMode' ) ) {
$data['misermode'] = '';
}
$data['maxuploadsize'] = UploadBase::getMaxUploadSize();
+ $data['thumblimits'] = $config->get( 'ThumbLimits' );
+ $this->getResult()->setIndexedTagName( $data['thumblimits'], 'limit' );
+ $data['imagelimits'] = array();
+ $this->getResult()->setIndexedTagName( $data['imagelimits'], 'limit' );
+ foreach ( $config->get( 'ImageLimits' ) as $k => $limit ) {
+ $data['imagelimits'][$k] = array( 'width' => $limit[0], 'height' => $limit[1] );
+ }
+
+ $favicon = $config->get( 'Favicon' );
+ if ( !empty( $favicon ) ) {
+ // wgFavicon can either be a relative or an absolute path
+ // make sure we always return an absolute path
+ $data['favicon'] = wfExpandUrl( $favicon, PROTO_RELATIVE );
+ }
+
wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
return $this->getResult()->addValue( 'query', $property, $data );
@@ -276,12 +311,14 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$this->getResult()->setIndexedTagName( $data, 'ns' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendNamespaceAliases( $property ) {
- global $wgNamespaceAliases, $wgContLang;
- $aliases = array_merge( $wgNamespaceAliases, $wgContLang->getNamespaceAliases() );
+ global $wgContLang;
+ $aliases = array_merge( $this->getConfig()->get( 'NamespaceAliases' ),
+ $wgContLang->getNamespaceAliases() );
$namespaces = $wgContLang->getNamespaces();
$data = array();
foreach ( $aliases as $title => $ns ) {
@@ -299,6 +336,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
sort( $data );
$this->getResult()->setIndexedTagName( $data, 'ns' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -306,7 +344,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
global $wgContLang;
$data = array();
$aliases = $wgContLang->getSpecialPageAliases();
- foreach ( SpecialPageFactory::getList() as $specialpage => $stuff ) {
+ foreach ( SpecialPageFactory::getNames() as $specialpage ) {
if ( isset( $aliases[$specialpage] ) ) {
$arr = array( 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] );
$this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
@@ -314,6 +352,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
}
$this->getResult()->setIndexedTagName( $data, 'specialpage' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -330,6 +369,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data[] = $arr;
}
$this->getResult()->setIndexedTagName( $data, 'magicword' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -348,20 +388,45 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$langNames = Language::fetchLanguageNames( $langCode );
$getPrefixes = Interwiki::getAllPrefixes( $local );
+ $extraLangPrefixes = $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' );
+ $localInterwikis = $this->getConfig()->get( 'LocalInterwikis' );
$data = array();
foreach ( $getPrefixes as $row ) {
$prefix = $row['iw_prefix'];
$val = array();
$val['prefix'] = $prefix;
- if ( $row['iw_local'] == '1' ) {
+ if ( isset( $row['iw_local'] ) && $row['iw_local'] == '1' ) {
$val['local'] = '';
}
- // $val['trans'] = intval( $row['iw_trans'] ); // should this be exposed?
+ if ( isset( $row['iw_trans'] ) && $row['iw_trans'] == '1' ) {
+ $val['trans'] = '';
+ }
+
if ( isset( $langNames[$prefix] ) ) {
$val['language'] = $langNames[$prefix];
}
+ if ( in_array( $prefix, $localInterwikis ) ) {
+ $val['localinterwiki'] = '';
+ }
+ if ( in_array( $prefix, $extraLangPrefixes ) ) {
+ $val['extralanglink'] = '';
+
+ $linktext = wfMessage( "interlanguage-link-$prefix" );
+ if ( !$linktext->isDisabled() ) {
+ $val['linktext'] = $linktext->text();
+ }
+
+ $sitename = wfMessage( "interlanguage-link-sitename-$prefix" );
+ if ( !$sitename->isDisabled() ) {
+ $val['sitename'] = $sitename->text();
+ }
+ }
+
$val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
+ if ( substr( $row['iw_url'], 0, 2 ) == '//' ) {
+ $val['protorel'] = '';
+ }
if ( isset( $row['iw_wikiid'] ) ) {
$val['wikiid'] = $row['iw_wikiid'];
}
@@ -373,16 +438,20 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$this->getResult()->setIndexedTagName( $data, 'iw' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendDbReplLagInfo( $property, $includeAll ) {
- global $wgShowHostnames;
$data = array();
$lb = wfGetLB();
+ $showHostnames = $this->getConfig()->get( 'ShowHostnames' );
if ( $includeAll ) {
- if ( !$wgShowHostnames ) {
- $this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' );
+ if ( !$showHostnames ) {
+ $this->dieUsage(
+ 'Cannot view all servers info unless $wgShowHostnames is true',
+ 'includeAllDenied'
+ );
}
$lags = $lb->getLagTimes();
@@ -395,7 +464,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
} else {
list( , $lag, $index ) = $lb->getMaxLag();
$data[] = array(
- 'host' => $wgShowHostnames
+ 'host' => $showHostnames
? $lb->getServerName( $index )
: '',
'lag' => intval( $lag )
@@ -404,15 +473,15 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$result = $this->getResult();
$result->setIndexedTagName( $data, 'db' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendStatistics( $property ) {
- global $wgDisableCounters;
$data = array();
$data['pages'] = intval( SiteStats::pages() );
$data['articles'] = intval( SiteStats::articles() );
- if ( !$wgDisableCounters ) {
+ if ( !$this->getConfig()->get( 'DisableCounters' ) ) {
$data['views'] = intval( SiteStats::views() );
}
$data['edits'] = intval( SiteStats::edits() );
@@ -428,40 +497,42 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
protected function appendUserGroups( $property, $numberInGroup ) {
- global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups;
- global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
+ $config = $this->getConfig();
$data = array();
$result = $this->getResult();
- foreach ( $wgGroupPermissions as $group => $permissions ) {
+ $allGroups = User::getAllGroups();
+ foreach ( $config->get( 'GroupPermissions' ) as $group => $permissions ) {
$arr = array(
'name' => $group,
'rights' => array_keys( $permissions, true ),
);
if ( $numberInGroup ) {
- global $wgAutopromote;
+ $autopromote = $config->get( 'Autopromote' );
if ( $group == 'user' ) {
$arr['number'] = SiteStats::users();
-
// '*' and autopromote groups have no size
- } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) {
+ } elseif ( $group !== '*' && !isset( $autopromote[$group] ) ) {
$arr['number'] = SiteStats::numberInGroup( $group );
}
}
$groupArr = array(
- 'add' => $wgAddGroups,
- 'remove' => $wgRemoveGroups,
- 'add-self' => $wgGroupsAddToSelf,
- 'remove-self' => $wgGroupsRemoveFromSelf
+ 'add' => $config->get( 'AddGroups' ),
+ 'remove' => $config->get( 'RemoveGroups' ),
+ 'add-self' => $config->get( 'GroupsAddToSelf' ),
+ 'remove-self' => $config->get( 'GroupsRemoveFromSelf' )
);
foreach ( $groupArr as $type => $rights ) {
if ( isset( $rights[$group] ) ) {
- $arr[$type] = $rights[$group];
- $result->setIndexedTagName( $arr[$type], 'group' );
+ $groups = array_intersect( $rights[$group], $allGroups );
+ if ( $groups ) {
+ $arr[$type] = $groups;
+ $result->setIndexedTagName( $arr[$type], 'group' );
+ }
}
}
@@ -470,30 +541,32 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$result->setIndexedTagName( $data, 'group' );
+
return $result->addValue( 'query', $property, $data );
}
protected function appendFileExtensions( $property ) {
- global $wgFileExtensions;
-
$data = array();
- foreach ( array_unique( $wgFileExtensions ) as $ext ) {
+ foreach ( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) as $ext ) {
$data[] = array( 'ext' => $ext );
}
$this->getResult()->setIndexedTagName( $data, 'fe' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendExtensions( $property ) {
- global $wgExtensionCredits;
$data = array();
- foreach ( $wgExtensionCredits as $type => $extensions ) {
+ foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $type => $extensions ) {
foreach ( $extensions as $ext ) {
$ret = array();
$ret['type'] = $type;
if ( isset( $ext['name'] ) ) {
$ret['name'] = $ext['name'];
}
+ if ( isset( $ext['namemsg'] ) ) {
+ $ret['namemsg'] = $ext['namemsg'];
+ }
if ( isset( $ext['description'] ) ) {
$ret['description'] = $ext['description'];
}
@@ -515,26 +588,63 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$ret['url'] = $ext['url'];
}
if ( isset( $ext['version'] ) ) {
- $ret['version'] = $ext['version'];
+ $ret['version'] = $ext['version'];
} elseif ( isset( $ext['svn-revision'] ) &&
preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/',
- $ext['svn-revision'], $m ) )
- {
- $ret['version'] = 'r' . $m[1];
+ $ext['svn-revision'], $m )
+ ) {
+ $ret['version'] = 'r' . $m[1];
+ }
+ if ( isset( $ext['path'] ) ) {
+ $extensionPath = dirname( $ext['path'] );
+ $gitInfo = new GitInfo( $extensionPath );
+ $vcsVersion = $gitInfo->getHeadSHA1();
+ if ( $vcsVersion !== false ) {
+ $ret['vcs-system'] = 'git';
+ $ret['vcs-version'] = $vcsVersion;
+ $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
+ $vcsDate = $gitInfo->getHeadCommitDate();
+ if ( $vcsDate !== false ) {
+ $ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $vcsDate );
+ }
+ } else {
+ $svnInfo = SpecialVersion::getSvnInfo( $extensionPath );
+ if ( $svnInfo !== false ) {
+ $ret['vcs-system'] = 'svn';
+ $ret['vcs-version'] = $svnInfo['checkout-rev'];
+ $ret['vcs-url'] = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : '';
+ }
+ }
+
+ if ( SpecialVersion::getExtLicenseFileName( $extensionPath ) ) {
+ $ret['license-name'] = isset( $ext['license-name'] ) ? $ext['license-name'] : '';
+ $ret['license'] = SpecialPage::getTitleFor(
+ 'Version',
+ "License/{$ext['name']}"
+ )->getLinkURL();
+ }
+
+ if ( SpecialVersion::getExtAuthorsFileName( $extensionPath ) ) {
+ $ret['credits'] = SpecialPage::getTitleFor(
+ 'Version',
+ "Credits/{$ext['name']}"
+ )->getLinkURL();
+ }
}
$data[] = $ret;
}
}
$this->getResult()->setIndexedTagName( $data, 'ext' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendRightsInfo( $property ) {
- global $wgRightsPage, $wgRightsUrl, $wgRightsText;
- $title = Title::newFromText( $wgRightsPage );
- $url = $title ? wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ) : $wgRightsUrl;
- $text = $wgRightsText;
+ $config = $this->getConfig();
+ $title = Title::newFromText( $config->get( 'RightsPage' ) );
+ $url = $title ? wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ) : $config->get( 'RightsUrl' );
+ $text = $config->get( 'RightsText' );
if ( !$text && $title ) {
$text = $title->getPrefixedText();
}
@@ -547,6 +657,23 @@ class ApiQuerySiteinfo extends ApiQueryBase {
return $this->getResult()->addValue( 'query', $property, $data );
}
+ protected function appendRestrictions( $property ) {
+ $config = $this->getConfig();
+ $data = array(
+ 'types' => $config->get( 'RestrictionTypes' ),
+ 'levels' => $config->get( 'RestrictionLevels' ),
+ 'cascadinglevels' => $config->get( 'CascadingRestrictionLevels' ),
+ 'semiprotectedlevels' => $config->get( 'SemiprotectedRestrictionLevels' ),
+ );
+
+ $this->getResult()->setIndexedTagName( $data['types'], 'type' );
+ $this->getResult()->setIndexedTagName( $data['levels'], 'level' );
+ $this->getResult()->setIndexedTagName( $data['cascadinglevels'], 'level' );
+ $this->getResult()->setIndexedTagName( $data['semiprotectedlevels'], 'level' );
+
+ return $this->getResult()->addValue( 'query', $property, $data );
+ }
+
public function appendLanguages( $property ) {
$params = $this->extractRequestParams();
$langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
@@ -560,17 +687,28 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data[] = $lang;
}
$this->getResult()->setIndexedTagName( $data, 'lang' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
public function appendSkins( $property ) {
$data = array();
- $usable = Skin::getUsableSkins();
+ $allowed = Skin::getAllowedSkins();
$default = Skin::normalizeKey( 'default' );
foreach ( Skin::getSkinNames() as $name => $displayName ) {
+ $msg = $this->msg( "skinname-{$name}" );
+ $code = $this->getParameter( 'inlanguagecode' );
+ if ( $code && Language::isValidCode( $code ) ) {
+ $msg->inLanguage( $code );
+ } else {
+ $msg->inContentLanguage();
+ }
+ if ( $msg->exists() ) {
+ $displayName = $msg->text();
+ }
$skin = array( 'code' => $name );
ApiResult::setContent( $skin, $displayName );
- if ( !isset( $usable[$name] ) ) {
+ if ( !isset( $allowed[$name] ) ) {
$skin['unusable'] = '';
}
if ( $name === $default ) {
@@ -579,6 +717,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data[] = $skin;
}
$this->getResult()->setIndexedTagName( $data, 'skin' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -587,6 +726,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$wgParser->firstCallInit();
$tags = array_map( array( $this, 'formatParserTags' ), $wgParser->getTags() );
$this->getResult()->setIndexedTagName( $tags, 't' );
+
return $this->getResult()->addValue( 'query', $property, $tags );
}
@@ -595,37 +735,43 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$wgParser->firstCallInit();
$hooks = $wgParser->getFunctionHooks();
$this->getResult()->setIndexedTagName( $hooks, 'h' );
+
return $this->getResult()->addValue( 'query', $property, $hooks );
}
public function appendVariables( $property ) {
$variables = MagicWord::getVariableIDs();
$this->getResult()->setIndexedTagName( $variables, 'v' );
+
return $this->getResult()->addValue( 'query', $property, $variables );
}
public function appendProtocols( $property ) {
- global $wgUrlProtocols;
// Make a copy of the global so we don't try to set the _element key of it - bug 45130
- $protocols = array_values( $wgUrlProtocols );
+ $protocols = array_values( $this->getConfig()->get( 'UrlProtocols' ) );
$this->getResult()->setIndexedTagName( $protocols, 'p' );
+
return $this->getResult()->addValue( 'query', $property, $protocols );
}
+ public function appendDefaultOptions( $property ) {
+ return $this->getResult()->addValue( 'query', $property, User::getDefaultOptions() );
+ }
+
private function formatParserTags( $item ) {
return "<{$item}>";
}
public function appendSubscribedHooks( $property ) {
- global $wgHooks;
- $myWgHooks = $wgHooks;
+ $hooks = $this->getConfig()->get( 'Hooks' );
+ $myWgHooks = $hooks;
ksort( $myWgHooks );
$data = array();
- foreach ( $myWgHooks as $hook => $hooks ) {
+ foreach ( $myWgHooks as $name => $subscribers ) {
$arr = array(
- 'name' => $hook,
- 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
+ 'name' => $name,
+ 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $subscribers ),
);
$this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
@@ -633,10 +779,20 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$this->getResult()->setIndexedTagName( $data, 'hook' );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
public function getCacheMode( $params ) {
+ // Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
+ if (
+ count( $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' ) ) &&
+ !is_null( $params['prop'] ) &&
+ in_array( 'interwikimap', $params['prop'] )
+ ) {
+ return 'anon-public-user-private';
+ }
+
return 'public';
}
@@ -658,6 +814,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'extensions',
'fileextensions',
'rightsinfo',
+ 'restrictions',
'languages',
'skins',
'extensiontags',
@@ -665,6 +822,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'showhooks',
'variables',
'protocols',
+ 'defaultoptions',
)
),
'filteriw' => array(
@@ -681,6 +839,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'prop' => array(
'Which sysinfo properties to get:',
@@ -690,38 +849,35 @@ class ApiQuerySiteinfo extends ApiQueryBase {
' specialpagealiases - List of special page aliases',
' magicwords - List of magic words and their aliases',
' statistics - Returns site statistics',
- " interwikimap - Returns interwiki map " .
+ ' interwikimap - Returns interwiki map ' .
"(optionally filtered, (optionally localised by using {$p}inlanguagecode))",
' dbrepllag - Returns database server with the highest replication lag',
' usergroups - Returns user groups and the associated permissions',
' extensions - Returns extensions installed on the wiki',
' fileextensions - Returns list of file extensions allowed to be uploaded',
' rightsinfo - Returns wiki rights (license) information if available',
- " languages - Returns a list of languages MediaWiki supports" .
+ ' restrictions - Returns information on available restriction (protection) types',
+ ' languages - Returns a list of languages MediaWiki supports ' .
"(optionally localised by using {$p}inlanguagecode)",
- ' skins - Returns a list of all enabled skins',
+ ' skins - Returns a list of all enabled skins ' .
+ "(optionally localised by using {$p}inlanguagecode, otherwise in content language)",
' extensiontags - Returns a list of parser extension tags',
' functionhooks - Returns a list of parser function hooks',
' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)',
' variables - Returns a list of variable IDs',
' protocols - Returns a list of protocols that are allowed in external links.',
+ ' defaultoptions - Returns the default values for user preferences.',
),
'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
'showalldb' => 'List all database servers, not just the one lagging the most',
'numberingroup' => 'Lists the number of users in user groups',
- 'inlanguagecode' => 'Language code for localised language names (best effort, use CLDR extension)',
+ 'inlanguagecode' => 'Language code for localised language names ' .
+ '(best effort, use CLDR extension) and skin names',
);
}
public function getDescription() {
- return 'Return general information about the site';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array( array(
- 'code' => 'includeAllDenied',
- 'info' => 'Cannot view all servers info unless $wgShowHostnames is true'
- ), ) );
+ return 'Return general information about the site.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryStashImageInfo.php b/includes/api/ApiQueryStashImageInfo.php
index 6899375a..db928560 100644
--- a/includes/api/ApiQueryStashImageInfo.php
+++ b/includes/api/ApiQueryStashImageInfo.php
@@ -27,7 +27,7 @@
*/
class ApiQueryStashImageInfo extends ApiQueryImageInfo {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'sii' );
}
@@ -47,6 +47,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
// Alias sessionkey to filekey, but give an existing filekey precedence.
if ( !$params['filekey'] && $params['sessionkey'] ) {
+ $this->logFeatureUsage( 'prop=stashimageinfo&siisessionkey' );
$params['filekey'] = $params['sessionkey'];
}
@@ -60,7 +61,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
$result->addValue( array( 'query', $this->getModuleName() ), null, $imageInfo );
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $modulePrefix );
}
- //TODO: update exception handling here to understand current getFile exceptions
+ // @todo Update exception handling here to understand current getFile exceptions
} catch ( UploadStashNotAvailableException $e ) {
$this->dieUsage( "Session not available: " . $e->getMessage(), "nosession" );
} catch ( UploadStashFileNotFoundException $e ) {
@@ -72,7 +73,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
private $propertyFilter = array(
'user', 'userid', 'comment', 'parsedcomment',
- 'mediatype', 'archivename',
+ 'mediatype', 'archivename', 'uploadwarning',
);
public function getAllowedParams() {
@@ -108,10 +109,11 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
/**
* Return the API documentation for the parameters.
- * @return Array parameter documentation.
+ * @return array Parameter documentation.
*/
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'prop' => self::getPropertyDescriptions( $this->propertyFilter, $p ),
'filekey' => 'Key that identifies a previous upload that was stashed temporarily.',
@@ -123,19 +125,15 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
);
}
- public function getResultProperties() {
- return ApiQueryImageInfo::getResultPropertiesFiltered( $this->propertyFilter );
- }
-
public function getDescription() {
- return 'Returns image information for stashed images';
+ return 'Returns image information for stashed images.';
}
public function getExamples() {
return array(
'api.php?action=query&prop=stashimageinfo&siifilekey=124sd34rsdf567',
- 'api.php?action=query&prop=stashimageinfo&siifilekey=b34edoe3|bceffd4&siiurlwidth=120&siiprop=url',
+ 'api.php?action=query&prop=stashimageinfo&siifilekey=b34edoe3|bceffd4&' .
+ 'siiurlwidth=120&siiprop=url',
);
}
-
}
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
index 732df9a4..31845648 100644
--- a/includes/api/ApiQueryTags.php
+++ b/includes/api/ApiQueryTags.php
@@ -38,9 +38,9 @@ class ApiQueryTags extends ApiQueryBase {
private $limit;
private $fld_displayname = false, $fld_description = false,
- $fld_hitcount = false;
+ $fld_hitcount = false;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'tg' );
}
@@ -97,6 +97,7 @@ class ApiQueryTags extends ApiQueryBase {
if ( ++$count > $this->limit ) {
$this->setContinueEnumParameter( 'continue', $tagName );
+
return false;
}
@@ -121,6 +122,7 @@ class ApiQueryTags extends ApiQueryBase {
$fit = $this->result->addValue( array( 'query', $this->getModuleName() ), null, $tag );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $tagName );
+
return false;
}
@@ -168,25 +170,8 @@ class ApiQueryTags extends ApiQueryBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'name' => 'string'
- ),
- 'displayname' => array(
- 'displayname' => 'string'
- ),
- 'description' => array(
- 'description' => 'string'
- ),
- 'hitcount' => array(
- 'hitcount' => 'integer'
- )
- );
- }
-
public function getDescription() {
- return 'List change tags';
+ return 'List change tags.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryTokens.php b/includes/api/ApiQueryTokens.php
new file mode 100644
index 00000000..ba9c9377
--- /dev/null
+++ b/includes/api/ApiQueryTokens.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Module to fetch tokens via action=query&meta=tokens
+ *
+ * Created on August 8, 2014
+ *
+ * Copyright © 2014 Brad Jorsch bjorsch@wikimedia.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
+ * @since 1.24
+ */
+
+/**
+ * Module to fetch tokens via action=query&meta=tokens
+ *
+ * @ingroup API
+ * @since 1.24
+ */
+class ApiQueryTokens extends ApiQueryBase {
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $res = array();
+
+ if ( $this->getMain()->getRequest()->getVal( 'callback' ) !== null ) {
+ $this->setWarning( 'Tokens may not be obtained when using a callback' );
+ return;
+ }
+
+ $salts = self::getTokenTypeSalts();
+ foreach ( $params['type'] as $type ) {
+ $salt = $salts[$type];
+ $val = $this->getUser()->getEditToken( $salt, $this->getRequest() );
+ $res[$type . 'token'] = $val;
+ }
+
+ $this->getResult()->addValue( 'query', $this->getModuleName(), $res );
+ }
+
+ public static function getTokenTypeSalts() {
+ static $salts = null;
+ if ( !$salts ) {
+ wfProfileIn( __METHOD__ );
+ $salts = array(
+ 'csrf' => '',
+ 'watch' => 'watch',
+ 'patrol' => 'patrol',
+ 'rollback' => 'rollback',
+ 'userrights' => 'userrights',
+ );
+ wfRunHooks( 'ApiQueryTokensRegisterTypes', array( &$salts ) );
+ ksort( $salts );
+ wfProfileOut( __METHOD__ );
+ }
+
+ return $salts;
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'type' => array(
+ ApiBase::PARAM_DFLT => 'csrf',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array_keys( self::getTokenTypeSalts() ),
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'type' => 'Type of token(s) to request'
+ );
+ }
+
+ public function getDescription() {
+ return 'Gets tokens for data-modifying actions.';
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=query&meta=tokens' => 'Retrieve a csrf token (the default)',
+ 'api.php?action=query&meta=tokens&type=watch|patrol' => 'Retrieve a watch token and a patrol token'
+ );
+ }
+
+ public function getCacheMode( $params ) {
+ return 'private';
+ }
+}
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index 9a9be7b2..4b167b8b 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -31,14 +31,14 @@
*/
class ApiQueryContributions extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'uc' );
}
private $params, $prefixMode, $userprefix, $multiUserMode, $usernames, $parentLens;
private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
- $fld_comment = false, $fld_parsedcomment = false, $fld_flags = false,
- $fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
+ $fld_comment = false, $fld_parsedcomment = false, $fld_flags = false,
+ $fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
public function execute() {
// Parse some parameters
@@ -56,6 +56,11 @@ class ApiQueryContributions extends ApiQueryBase {
$this->fld_patrolled = isset( $prop['patrolled'] );
$this->fld_tags = isset( $prop['tags'] );
+ // Most of this code will use the 'contributions' group DB, which can map to slaves
+ // with extra user based indexes or partioning by user. The additional metadata
+ // queries should use a regular slave since the lookup pattern is not all by user.
+ $dbSecondary = $this->getDB(); // any random slave
+
// TODO: if the query is going only against the revision table, should this be done?
$this->selectNamedDB( 'contributions', DB_SLAVE, 'contributions' );
@@ -90,7 +95,7 @@ class ApiQueryContributions extends ApiQueryBase {
$revIds[] = $row->rev_parent_id;
}
}
- $this->parentLens = Revision::getParentLengths( $this->getDB(), $revIds );
+ $this->parentLens = Revision::getParentLengths( $dbSecondary, $revIds );
$res->rewind(); // reset
}
@@ -100,36 +105,32 @@ class ApiQueryContributions extends ApiQueryBase {
// Fetch each row
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- if ( $this->multiUserMode ) {
- $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
- } else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rev_timestamp ) );
- }
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
break;
}
$vals = $this->extractRowInfo( $row );
$fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
- if ( $this->multiUserMode ) {
- $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
- } else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rev_timestamp ) );
- }
+ $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
break;
}
}
- $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
+ $this->getResult()->setIndexedTagName_internal(
+ array( 'query', $this->getModuleName() ),
+ 'item'
+ );
}
/**
* Validate the 'user' parameter and set the value to compare
* against `revision`.`rev_user_text`
*
- * @param $user string
+ * @param string $user
*/
private function prepareUsername( $user ) {
if ( !is_null( $user ) && $user !== '' ) {
@@ -158,26 +159,53 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addWhere( 'page_id=rev_page' );
// Handle continue parameter
- if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) {
+ if ( !is_null( $this->params['continue'] ) ) {
$continue = explode( '|', $this->params['continue'] );
- $this->dieContinueUsageIf( count( $continue ) != 2 );
$db = $this->getDB();
- $encUser = $db->addQuotes( $continue[0] );
- $encTS = $db->addQuotes( $db->timestamp( $continue[1] ) );
+ if ( $this->multiUserMode ) {
+ $this->dieContinueUsageIf( count( $continue ) != 3 );
+ $encUser = $db->addQuotes( array_shift( $continue ) );
+ } else {
+ $this->dieContinueUsageIf( count( $continue ) != 2 );
+ }
+ $encTS = $db->addQuotes( $db->timestamp( $continue[0] ) );
+ $encId = (int)$continue[1];
+ $this->dieContinueUsageIf( $encId != $continue[1] );
$op = ( $this->params['dir'] == 'older' ? '<' : '>' );
- $this->addWhere(
- "rev_user_text $op $encUser OR " .
- "(rev_user_text = $encUser AND " .
- "rev_timestamp $op= $encTS)"
- );
+ if ( $this->multiUserMode ) {
+ $this->addWhere(
+ "rev_user_text $op $encUser OR " .
+ "(rev_user_text = $encUser AND " .
+ "(rev_timestamp $op $encTS OR " .
+ "(rev_timestamp = $encTS AND " .
+ "rev_id $op= $encId)))"
+ );
+ } else {
+ $this->addWhere(
+ "rev_timestamp $op $encTS OR " .
+ "(rev_timestamp = $encTS AND " .
+ "rev_id $op= $encId)"
+ );
+ }
}
- if ( !$user->isAllowed( 'hideuser' ) ) {
- $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ // Don't include any revisions where we're not supposed to be able to
+ // see the username.
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
}
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
+ }
+
// We only want pages by the specified users.
if ( $this->prefixMode ) {
- $this->addWhere( 'rev_user_text' . $this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) );
+ $this->addWhere( 'rev_user_text' .
+ $this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) );
} else {
$this->addWhereFld( 'rev_user_text', $this->usernames );
}
@@ -189,13 +217,24 @@ class ApiQueryContributions extends ApiQueryBase {
}
$this->addTimestampWhereRange( 'rev_timestamp',
$this->params['dir'], $this->params['start'], $this->params['end'] );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'rev_id', $this->params['dir'], null, null );
+
$this->addWhereFld( 'page_namespace', $this->params['namespace'] );
$show = $this->params['show'];
+ if ( $this->params['toponly'] ) { // deprecated/old param
+ $this->logFeatureUsage( 'list=usercontribs&uctoponly' );
+ $show[] = 'top';
+ }
if ( !is_null( $show ) ) {
$show = array_flip( $show );
+
if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
- || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) ) {
+ || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
+ || ( isset( $show['top'] ) && isset( $show['!top'] ) )
+ || ( isset( $show['new'] ) && isset( $show['!new'] ) )
+ ) {
$this->dieUsageMsg( 'show' );
}
@@ -203,6 +242,10 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) );
$this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
$this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
+ $this->addWhereIf( 'rev_id != page_latest', isset( $show['!top'] ) );
+ $this->addWhereIf( 'rev_id = page_latest', isset( $show['top'] ) );
+ $this->addWhereIf( 'rev_parent_id != 0', isset( $show['!new'] ) );
+ $this->addWhereIf( 'rev_parent_id = 0', isset( $show['new'] ) );
}
$this->addOption( 'LIMIT', $this->params['limit'] + 1 );
$index = array( 'revision' => 'usertext_timestamp' );
@@ -211,6 +254,7 @@ class ApiQueryContributions extends ApiQueryBase {
// ns+title checks if the user has access rights for this page
// user_text is necessary if multiple users were specified
$this->addFields( array(
+ 'rev_id',
'rev_timestamp',
'page_namespace',
'page_title',
@@ -220,9 +264,13 @@ class ApiQueryContributions extends ApiQueryBase {
) );
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
- $this->fld_patrolled ) {
+ $this->fld_patrolled
+ ) {
if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
- $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
+ $this->dieUsage(
+ 'You need the patrol right to request the patrolled flag',
+ 'permissiondenied'
+ );
}
// Use a redundant join condition on both
@@ -249,7 +297,6 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addTables( $tables );
$this->addFieldsIf( 'rev_page', $this->fld_ids );
- $this->addFieldsIf( 'rev_id', $this->fld_ids || $this->fld_flags );
$this->addFieldsIf( 'page_latest', $this->fld_flags );
// $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed?
$this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment );
@@ -260,19 +307,18 @@ class ApiQueryContributions extends ApiQueryBase {
if ( $this->fld_tags ) {
$this->addTables( 'tag_summary' );
- $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) );
+ $this->addJoinConds(
+ array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) )
+ );
$this->addFields( 'ts_tags' );
}
if ( isset( $this->params['tag'] ) ) {
$this->addTables( 'change_tag' );
- $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
+ $this->addJoinConds(
+ array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) )
+ );
$this->addWhereFld( 'ct_tag', $this->params['tag'] );
- $index['change_tag'] = 'change_tag_tag_id';
- }
-
- if ( $this->params['toponly'] ) {
- $this->addWhere( 'rev_id = page_latest' );
}
$this->addOption( 'USE INDEX', $index );
@@ -281,16 +327,24 @@ class ApiQueryContributions extends ApiQueryBase {
/**
* Extract fields from the database row and append them to a result array
*
- * @param $row
+ * @param stdClass $row
* @return array
*/
private function extractRowInfo( $row ) {
$vals = array();
+ $anyHidden = false;
+ if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
+ $vals['texthidden'] = '';
+ $anyHidden = true;
+ }
+
+ // Any rows where we can't view the user were filtered out in the query.
$vals['userid'] = $row->rev_user;
$vals['user'] = $row->rev_user_text;
if ( $row->rev_deleted & Revision::DELETED_USER ) {
$vals['userhidden'] = '';
+ $anyHidden = true;
}
if ( $this->fld_ids ) {
$vals['pageid'] = intval( $row->rev_page );
@@ -327,7 +381,15 @@ class ApiQueryContributions extends ApiQueryBase {
if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) {
if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
$vals['commenthidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+
+ $userCanView = Revision::userCanBitfield(
+ $row->rev_deleted,
+ Revision::DELETED_COMMENT, $this->getUser()
+ );
+
+ if ( $userCanView ) {
if ( $this->fld_comment ) {
$vals['comment'] = $row->rev_comment;
}
@@ -346,8 +408,13 @@ class ApiQueryContributions extends ApiQueryBase {
$vals['size'] = intval( $row->rev_len );
}
- if ( $this->fld_sizediff && !is_null( $row->rev_len ) && !is_null( $row->rev_parent_id ) ) {
- $parentLen = isset( $this->parentLens[$row->rev_parent_id] ) ? $this->parentLens[$row->rev_parent_id] : 0;
+ if ( $this->fld_sizediff
+ && !is_null( $row->rev_len )
+ && !is_null( $row->rev_parent_id )
+ ) {
+ $parentLen = isset( $this->parentLens[$row->rev_parent_id] )
+ ? $this->parentLens[$row->rev_parent_id]
+ : 0;
$vals['sizediff'] = intval( $row->rev_len - $parentLen );
}
@@ -361,12 +428,19 @@ class ApiQueryContributions extends ApiQueryBase {
}
}
+ if ( $anyHidden && $row->rev_deleted & Revision::DELETED_RESTRICTED ) {
+ $vals['suppressed'] = '';
+ }
+
return $vals;
}
private function continueStr( $row ) {
- return $row->rev_user_text . '|' .
- wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
+ if ( $this->multiUserMode ) {
+ return "$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
+ } else {
+ return "$row->rev_timestamp|$row->rev_id";
+ }
}
public function getCacheMode( $params ) {
@@ -429,23 +503,34 @@ class ApiQueryContributions extends ApiQueryBase {
'!minor',
'patrolled',
'!patrolled',
+ 'top',
+ '!top',
+ 'new',
+ '!new',
)
),
'tag' => null,
- 'toponly' => false,
+ 'toponly' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
);
}
public function getParamDescription() {
- global $wgRCMaxAge;
$p = $this->getModulePrefix();
+ $RCMaxAge = $this->getConfig()->get( 'RCMaxAge' );
+
return array(
'limit' => 'The maximum number of contributions to return',
'start' => 'The start timestamp to return from',
'end' => 'The end timestamp to return to',
'continue' => 'When more results are available, use this to continue',
'user' => 'The users to retrieve contributions for',
- 'userprefix' => "Retrieve contributions for all users whose names begin with this value. Overrides {$p}user",
+ 'userprefix' => array(
+ "Retrieve contributions for all users whose names begin with this value.",
+ "Overrides {$p}user",
+ ),
'dir' => $this->getDirectionDescription( $p ),
'namespace' => 'Only list contributions in these namespaces',
'prop' => array(
@@ -461,83 +546,18 @@ class ApiQueryContributions extends ApiQueryBase {
' patrolled - Tags patrolled edits',
' tags - Lists tags for the edit',
),
- 'show' => array( "Show only items that meet this criteria, e.g. non minor edits only: {$p}show=!minor",
- "NOTE: if {$p}show=patrolled or {$p}show=!patrolled is set, revisions older than \$wgRCMaxAge ($wgRCMaxAge) won't be shown", ),
+ 'show' => array(
+ "Show only items that meet thse criteria, e.g. non minor edits only: {$p}show=!minor",
+ "NOTE: If {$p}show=patrolled or {$p}show=!patrolled is set, revisions older than",
+ "\$wgRCMaxAge ($RCMaxAge) won't be shown",
+ ),
'tag' => 'Only list revisions tagged with this tag',
'toponly' => 'Only list changes which are the latest revision',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'userid' => 'integer',
- 'user' => 'string',
- 'userhidden' => 'boolean'
- ),
- 'ids' => array(
- 'pageid' => 'integer',
- 'revid' => 'integer',
- 'parentid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'flags' => array(
- 'new' => 'boolean',
- 'minor' => 'boolean',
- 'top' => 'boolean'
- ),
- 'comment' => array(
- 'commenthidden' => 'boolean',
- 'comment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'parsedcomment' => array(
- 'commenthidden' => 'boolean',
- 'parsedcomment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'patrolled' => array(
- 'patrolled' => 'boolean'
- ),
- 'size' => array(
- 'size' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'sizediff' => array(
- 'sizediff' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
- return 'Get all edits by a user';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'param_user', 'info' => 'User parameter may not be empty.' ),
- array( 'code' => 'param_user', 'info' => 'User name user is not valid' ),
- array( 'show' ),
- array( 'code' => 'permissiondenied', 'info' => 'You need the patrol right to request the patrolled flag' ),
- ) );
+ return 'Get all edits by a user.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index 3c85ea69..fd5f47b4 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -31,9 +31,11 @@
*/
class ApiQueryUserInfo extends ApiQueryBase {
+ const WL_UNREAD_LIMIT = 1000;
+
private $prop = array();
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ui' );
}
@@ -50,7 +52,6 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
protected function getCurrentUserInfo() {
- global $wgHiddenPrefs;
$user = $this->getUser();
$result = $this->getResult();
$vals = array();
@@ -68,6 +69,10 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['blockedby'] = $block->getByName();
$vals['blockedbyid'] = $block->getBy();
$vals['blockreason'] = $user->blockedFor();
+ $vals['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $block->mTimestamp );
+ $vals['blockexpiry'] = $block->getExpiry() === 'infinity'
+ ? 'infinite'
+ : wfTimestamp( TS_ISO_8601, $block->getExpiry() );
}
}
@@ -103,6 +108,12 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['options'] = $user->getOptions();
}
+ if ( isset( $this->prop['preferencestoken'] ) ) {
+ $p = $this->getModulePrefix();
+ $this->setWarning(
+ "{$p}prop=preferencestoken has been deprecated. Please use action=query&meta=tokens instead."
+ );
+ }
if ( isset( $this->prop['preferencestoken'] ) &&
is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) &&
$user->isAllowed( 'editmyoptions' )
@@ -120,7 +131,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['ratelimits'] = $this->getRateLimits();
}
- if ( isset( $this->prop['realname'] ) && !in_array( 'realname', $wgHiddenPrefs ) ) {
+ if ( isset( $this->prop['realname'] ) && !in_array( 'realname', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
$vals['realname'] = $user->getRealName();
}
@@ -152,11 +163,33 @@ class ApiQueryUserInfo extends ApiQueryBase {
$result->setIndexedTagName( $acceptLang, 'lang' );
$vals['acceptlang'] = $acceptLang;
}
+
+ if ( isset( $this->prop['unreadcount'] ) ) {
+ $dbr = $this->getQuery()->getNamedDB( 'watchlist', DB_SLAVE, 'watchlist' );
+
+ $sql = $dbr->selectSQLText(
+ 'watchlist',
+ array( 'dummy' => 1 ),
+ array(
+ 'wl_user' => $user->getId(),
+ 'wl_notificationtimestamp IS NOT NULL',
+ ),
+ __METHOD__,
+ array( 'LIMIT' => self::WL_UNREAD_LIMIT )
+ );
+ $count = $dbr->selectField( array( 'c' => "($sql)" ), 'COUNT(*)' );
+
+ if ( $count >= self::WL_UNREAD_LIMIT ) {
+ $vals['unreadcount'] = self::WL_UNREAD_LIMIT . '+';
+ } else {
+ $vals['unreadcount'] = (int)$count;
+ }
+ }
+
return $vals;
}
protected function getRateLimits() {
- global $wgRateLimits;
$user = $this->getUser();
if ( !$user->isPingLimitable() ) {
return array(); // No limits
@@ -180,7 +213,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
// Now get the actual limits
$retval = array();
- foreach ( $wgRateLimits as $action => $limits ) {
+ foreach ( $this->getConfig()->get( 'RateLimits' ) as $action => $limits ) {
foreach ( $categories as $cat ) {
if ( isset( $limits[$cat] ) && !is_null( $limits[$cat] ) ) {
$retval[$action][$cat]['hits'] = intval( $limits[$cat][0] );
@@ -188,6 +221,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
}
}
+
return $retval;
}
@@ -210,7 +244,8 @@ class ApiQueryUserInfo extends ApiQueryBase {
'email',
'realname',
'acceptlang',
- 'registrationdate'
+ 'registrationdate',
+ 'unreadcount',
)
)
);
@@ -227,76 +262,23 @@ class ApiQueryUserInfo extends ApiQueryBase {
' rights - Lists all the rights the current user has',
' changeablegroups - Lists the groups the current user can add to and remove from',
' options - Lists all preferences the current user has set',
- ' preferencestoken - Get a token to change current user\'s preferences',
+ ' preferencestoken - DEPRECATED! Get a token to change current user\'s preferences',
' editcount - Adds the current user\'s edit count',
' ratelimits - Lists all rate limits applying to the current user',
' realname - Adds the user\'s real name',
' email - Adds the user\'s email address and email authentication date',
- ' acceptlang - Echoes the Accept-Language header sent by the client in a structured format',
+ ' acceptlang - Echoes the Accept-Language header sent by ' .
+ 'the client in a structured format',
' registrationdate - Adds the user\'s registration date',
- )
- );
- }
-
- public function getResultProperties() {
- return array(
- ApiBase::PROP_LIST => false,
- '' => array(
- 'id' => 'integer',
- 'name' => 'string',
- 'anon' => 'boolean'
- ),
- 'blockinfo' => array(
- 'blockid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedby' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedbyid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedreason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'hasmsg' => array(
- 'messages' => 'boolean'
- ),
- 'preferencestoken' => array(
- 'preferencestoken' => 'string'
- ),
- 'editcount' => array(
- 'editcount' => 'integer'
- ),
- 'realname' => array(
- 'realname' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'email' => array(
- 'email' => 'string',
- 'emailauthenticated' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'registrationdate' => array(
- 'registrationdate' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
+ ' unreadcount - Adds the count of unread pages on the user\'s watchlist ' .
+ '(maximum ' . ( self::WL_UNREAD_LIMIT - 1 ) . '; returns "' .
+ self::WL_UNREAD_LIMIT . '+" if more)',
)
);
}
public function getDescription() {
- return 'Get information about the current user';
+ return 'Get information about the current user.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index dccfee67..2f5e4b4b 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -33,7 +33,24 @@ class ApiQueryUsers extends ApiQueryBase {
private $tokenFunctions, $prop;
- public function __construct( $query, $moduleName ) {
+ /**
+ * Properties whose contents does not depend on who is looking at them. If the usprops field
+ * contains anything not listed here, the cache mode will never be public for logged-in users.
+ * @var array
+ */
+ protected static $publicProps = array(
+ // everything except 'blockinfo' which might show hidden records if the user
+ // making the request has the appropriate permissions
+ 'groups',
+ 'implicitgroups',
+ 'rights',
+ 'editcount',
+ 'registration',
+ 'emailable',
+ 'gender',
+ );
+
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'us' );
}
@@ -41,7 +58,8 @@ class ApiQueryUsers extends ApiQueryBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($user)
* it should return a token or false (permission denied)
- * @return Array tokenname => function
+ * @deprecated since 1.24
+ * @return array Array of tokenname => function
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
@@ -58,15 +76,18 @@ class ApiQueryUsers extends ApiQueryBase {
'userrights' => array( 'ApiQueryUsers', 'getUserrightsToken' ),
);
wfRunHooks( 'APIQueryUsersTokens', array( &$this->tokenFunctions ) );
+
return $this->tokenFunctions;
}
/**
- * @param $user User
- * @return String
+ * @deprecated since 1.24
+ * @param User $user
+ * @return string
*/
public static function getUserrightsToken( $user ) {
global $wgUser;
+
// Since the permissions check for userrights is non-trivial,
// don't bother with it here
return $wgUser->getEditToken( $user->getName() );
@@ -90,10 +111,10 @@ class ApiQueryUsers extends ApiQueryBase {
if ( $n === false || $n === '' ) {
$vals = array( 'name' => $u, 'invalid' => '' );
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
- null, $vals );
+ null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'users',
- implode( '|', array_diff( $users, $done ) ) );
+ implode( '|', array_diff( $users, $done ) ) );
$goodNames = array();
break;
}
@@ -174,6 +195,7 @@ class ApiQueryUsers extends ApiQueryBase {
$data[$name]['blockid'] = $row->ipb_id;
$data[$name]['blockedby'] = $row->ipb_by_text;
$data[$name]['blockedbyid'] = $row->ipb_by;
+ $data[$name]['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
$data[$name]['blockreason'] = $row->ipb_reason;
$data[$name]['blockexpiry'] = $row->ipb_expiry;
}
@@ -244,10 +266,10 @@ class ApiQueryUsers extends ApiQueryBase {
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
- null, $data[$u] );
+ null, $data[$u] );
if ( !$fit ) {
$this->setContinueEnumParameter( 'users',
- implode( '|', array_diff( $users, $done ) ) );
+ implode( '|', array_diff( $users, $done ) ) );
break;
}
$done[] = $u;
@@ -259,7 +281,7 @@ class ApiQueryUsers extends ApiQueryBase {
* Gets all the groups that a user is automatically a member of (implicit groups)
*
* @deprecated since 1.20; call User::getAutomaticGroups() directly.
- * @param $user User
+ * @param User $user
* @return array
*/
public static function getAutoGroups( $user ) {
@@ -271,8 +293,10 @@ class ApiQueryUsers extends ApiQueryBase {
public function getCacheMode( $params ) {
if ( isset( $params['token'] ) ) {
return 'private';
- } else {
+ } elseif ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
return 'anon-public-user-private';
+ } else {
+ return 'public';
}
}
@@ -296,6 +320,7 @@ class ApiQueryUsers extends ApiQueryBase {
ApiBase::PARAM_ISMULTI => true
),
'token' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),
@@ -312,7 +337,8 @@ class ApiQueryUsers extends ApiQueryBase {
' rights - Lists all the rights the user(s) has',
' editcount - Adds the user\'s edit count',
' registration - Adds the user\'s registration timestamp',
- ' emailable - Tags if the user can and wants to receive email through [[Special:Emailuser]]',
+ ' emailable - Tags if the user can and wants to receive ' .
+ 'email through [[Special:Emailuser]]',
' gender - Tags the gender of the user. Returns "male", "female", or "unknown"',
),
'users' => 'A list of users to obtain the same information for',
@@ -320,75 +346,8 @@ class ApiQueryUsers extends ApiQueryBase {
);
}
- public function getResultProperties() {
- $props = array(
- '' => array(
- 'userid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'name' => 'string',
- 'invalid' => 'boolean',
- 'hidden' => 'boolean',
- 'interwiki' => 'boolean',
- 'missing' => 'boolean'
- ),
- 'editcount' => array(
- 'editcount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'registration' => array(
- 'registration' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'blockinfo' => array(
- 'blockid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedby' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedbyid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedreason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedexpiry' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'emailable' => array(
- 'emailable' => 'boolean'
- ),
- 'gender' => array(
- 'gender' => array(
- ApiBase::PROP_TYPE => array(
- 'male',
- 'female',
- 'unknown'
- ),
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
-
- self::addTokenProperties( $props, $this->getTokenFunctions() );
-
- return $props;
- }
-
public function getDescription() {
- return 'Get information about a list of users';
+ return 'Get information about a list of users.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index 22843f50..efbe05ee 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -32,7 +32,7 @@
*/
class ApiQueryWatchlist extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'wl' );
}
@@ -44,12 +44,14 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
- private $fld_ids = false, $fld_title = false, $fld_patrol = false, $fld_flags = false,
- $fld_timestamp = false, $fld_user = false, $fld_comment = false, $fld_parsedcomment = false, $fld_sizes = false,
- $fld_notificationtimestamp = false, $fld_userid = false, $fld_loginfo = false;
+ private $fld_ids = false, $fld_title = false, $fld_patrol = false,
+ $fld_flags = false, $fld_timestamp = false, $fld_user = false,
+ $fld_comment = false, $fld_parsedcomment = false, $fld_sizes = false,
+ $fld_notificationtimestamp = false, $fld_userid = false,
+ $fld_loginfo = false;
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -57,7 +59,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$params = $this->extractRequestParams();
- $user = $this->getWatchlistUser( $params );
+ $user = $this->getUser();
+ $wlowner = $this->getWatchlistUser( $params );
if ( !is_null( $params['prop'] ) && is_null( $resultPageSet ) ) {
$prop = array_flip( $params['prop'] );
@@ -83,10 +86,12 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
$this->addFields( array(
+ 'rc_id',
'rc_namespace',
'rc_title',
'rc_timestamp',
'rc_type',
+ 'rc_deleted',
) );
if ( is_null( $resultPageSet ) ) {
@@ -103,7 +108,10 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrol );
$this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes );
$this->addFieldsIf( 'wl_notificationtimestamp', $this->fld_notificationtimestamp );
- $this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo );
+ $this->addFieldsIf(
+ array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ),
+ $this->fld_loginfo
+ );
} elseif ( $params['allrev'] ) {
$this->addFields( 'rc_this_oldid' );
} else {
@@ -115,22 +123,35 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'watchlist',
) );
- $userId = $user->getId();
+ $userId = $wlowner->getId();
$this->addJoinConds( array( 'watchlist' => array( 'INNER JOIN',
array(
'wl_user' => $userId,
'wl_namespace=rc_namespace',
'wl_title=rc_title'
- ) ) ) );
-
- $this->addWhere( array(
- 'rc_deleted' => 0,
- ) );
+ )
+ ) ) );
$db = $this->getDB();
$this->addTimestampWhereRange( 'rc_timestamp', $params['dir'],
$params['start'], $params['end'] );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'rc_id', $params['dir'], null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $op = ( $params['dir'] === 'newer' ? '>' : '<' );
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueId = (int)$cont[1];
+ $this->dieContinueUsageIf( $continueId != $cont[1] );
+ $this->addWhere( "rc_timestamp $op $continueTimestamp OR " .
+ "(rc_timestamp = $continueTimestamp AND " .
+ "rc_id $op= $continueId)"
+ );
+ }
+
$this->addWhereFld( 'wl_namespace', $params['namespace'] );
if ( !$params['allrev'] ) {
@@ -144,18 +165,21 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
/* Check for conflicting parameters. */
if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
- || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
- || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
- || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
+ || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
+ || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
+ || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
+ || ( isset( $show['unread'] ) && isset( $show['!unread'] ) )
) {
$this->dieUsageMsg( 'show' );
}
// Check permissions.
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
- $user = $this->getUser();
if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
- $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
+ $this->dieUsage(
+ 'You need the patrol right to request the patrolled flag',
+ 'permissiondenied'
+ );
}
}
@@ -168,10 +192,16 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addWhereIf( 'rc_user != 0', isset( $show['!anon'] ) );
$this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
$this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
+ $this->addWhereIf( 'wl_notificationtimestamp IS NOT NULL', isset( $show['unread'] ) );
+ $this->addWhereIf( 'wl_notificationtimestamp IS NULL', isset( $show['!unread'] ) );
}
if ( !is_null( $params['type'] ) ) {
- $this->addWhereFld( 'rc_type', $this->parseRCType( $params['type'] ) );
+ try {
+ $this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
+ } catch ( MWException $e ) {
+ ApiBase::dieDebug( __METHOD__, $e->getMessage() );
+ }
}
if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
@@ -185,7 +215,40 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
// This is an index optimization for mysql, as done in the Special:Watchlist page
- $this->addWhereIf( "rc_timestamp > ''", !isset( $params['start'] ) && !isset( $params['end'] ) && $db->getType() == 'mysql' );
+ $this->addWhereIf(
+ "rc_timestamp > ''",
+ !isset( $params['start'] ) && !isset( $params['end'] ) && $db->getType() == 'mysql'
+ );
+
+ // Paranoia: avoid brute force searches (bug 17342)
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+
+ // LogPage::DELETED_ACTION hides the affected page, too. So hide those
+ // entirely from the watchlist, or someone could guess the title.
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = LogPage::DELETED_ACTION;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->makeList( array(
+ 'rc_type != ' . RC_LOG,
+ $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
+ ), LIST_OR ) );
+ }
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -194,9 +257,10 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$res = $this->select( __METHOD__ );
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
break;
}
@@ -204,8 +268,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$vals = $this->extractRowInfo( $row );
$fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'start',
- wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
break;
}
} else {
@@ -218,7 +281,10 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
+ $this->getResult()->setIndexedTagName_internal(
+ array( 'query', $this->getModuleName() ),
+ 'item'
+ );
} elseif ( $params['allrev'] ) {
$resultPageSet->populateFromRevisionIDs( $ids );
} else {
@@ -227,139 +293,142 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
private function extractRowInfo( $row ) {
- $vals = array();
-
- $type = intval( $row->rc_type );
-
- /* Determine what kind of change this was. */
- switch ( $type ) {
- case RC_EDIT:
- $vals['type'] = 'edit';
- break;
- case RC_NEW:
- $vals['type'] = 'new';
- break;
- case RC_MOVE:
- $vals['type'] = 'move';
- break;
- case RC_LOG:
- $vals['type'] = 'log';
- break;
- case RC_EXTERNAL:
- $vals['type'] = 'external';
- break;
- case RC_MOVE_OVER_REDIRECT:
- $vals['type'] = 'move over redirect';
- break;
- default:
- $vals['type'] = $type;
- }
-
- if ( $this->fld_ids ) {
- $vals['pageid'] = intval( $row->rc_cur_id );
- $vals['revid'] = intval( $row->rc_this_oldid );
- $vals['old_revid'] = intval( $row->rc_last_oldid );
- }
-
+ /* Determine the title of the page that has been changed. */
$title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
+ $user = $this->getUser();
- if ( $this->fld_title ) {
- ApiQueryBase::addTitleInfo( $vals, $title );
+ /* Our output data. */
+ $vals = array();
+ $type = intval( $row->rc_type );
+ $vals['type'] = RecentChange::parseFromRCType( $type );
+ $anyHidden = false;
+
+ /* Create a new entry in the result for the title. */
+ if ( $this->fld_title || $this->fld_ids ) {
+ // These should already have been filtered out of the query, but just in case.
+ if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
+ $vals['actionhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( $type !== RC_LOG ||
+ LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
+ ) {
+ if ( $this->fld_title ) {
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ }
+ if ( $this->fld_ids ) {
+ $vals['pageid'] = intval( $row->rc_cur_id );
+ $vals['revid'] = intval( $row->rc_this_oldid );
+ $vals['old_revid'] = intval( $row->rc_last_oldid );
+ }
+ }
}
+ /* Add user data and 'anon' flag, if user is anonymous. */
if ( $this->fld_user || $this->fld_userid ) {
-
- if ( $this->fld_userid ) {
- $vals['userid'] = $row->rc_user;
- // for backwards compatibility
- $vals['user'] = $row->rc_user;
+ if ( $row->rc_deleted & Revision::DELETED_USER ) {
+ $vals['userhidden'] = '';
+ $anyHidden = true;
}
+ if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $row->rc_user;
+ // for backwards compatibility
+ $vals['user'] = $row->rc_user;
+ }
- if ( $this->fld_user ) {
- $vals['user'] = $row->rc_user_text;
- }
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->rc_user_text;
+ }
- if ( !$row->rc_user ) {
- $vals['anon'] = '';
+ if ( !$row->rc_user ) {
+ $vals['anon'] = '';
+ }
}
}
+ /* Add flags, such as new, minor, bot. */
if ( $this->fld_flags ) {
+ if ( $row->rc_bot ) {
+ $vals['bot'] = '';
+ }
if ( $row->rc_type == RC_NEW ) {
$vals['new'] = '';
}
if ( $row->rc_minor ) {
$vals['minor'] = '';
}
- if ( $row->rc_bot ) {
- $vals['bot'] = '';
- }
}
- if ( $this->fld_patrol && isset( $row->rc_patrolled ) ) {
- $vals['patrolled'] = '';
+ /* Add sizes of each revision. (Only available on 1.10+) */
+ if ( $this->fld_sizes ) {
+ $vals['oldlen'] = intval( $row->rc_old_len );
+ $vals['newlen'] = intval( $row->rc_new_len );
}
+ /* Add the timestamp. */
if ( $this->fld_timestamp ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
}
- if ( $this->fld_sizes ) {
- $vals['oldlen'] = intval( $row->rc_old_len );
- $vals['newlen'] = intval( $row->rc_new_len );
- }
-
if ( $this->fld_notificationtimestamp ) {
$vals['notificationtimestamp'] = ( $row->wl_notificationtimestamp == null )
? ''
: wfTimestamp( TS_ISO_8601, $row->wl_notificationtimestamp );
}
- if ( $this->fld_comment && isset( $row->rc_comment ) ) {
- $vals['comment'] = $row->rc_comment;
- }
+ /* Add edit summary / log summary. */
+ if ( $this->fld_comment || $this->fld_parsedcomment ) {
+ if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
+ $vals['commenthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
+ if ( $this->fld_comment && isset( $row->rc_comment ) ) {
+ $vals['comment'] = $row->rc_comment;
+ }
- if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
- $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
+ $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ }
+ }
}
- if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
- $vals['logid'] = intval( $row->rc_logid );
- $vals['logtype'] = $row->rc_log_type;
- $vals['logaction'] = $row->rc_log_action;
- $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
- ApiQueryLogEvents::addLogParams(
- $this->getResult(),
- $vals,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp()
- );
+ /* Add the patrolled flag */
+ if ( $this->fld_patrol && $row->rc_patrolled == 1 ) {
+ $vals['patrolled'] = '';
}
- return $vals;
- }
+ if ( $this->fld_patrol && ChangesList::isUnpatrolled( $row, $user ) ) {
+ $vals['unpatrolled'] = '';
+ }
- /* Copied from ApiQueryRecentChanges. */
- private function parseRCType( $type ) {
- if ( is_array( $type ) ) {
- $retval = array();
- foreach ( $type as $t ) {
- $retval[] = $this->parseRCType( $t );
+ if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
+ if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
+ $vals['actionhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
+ $vals['logid'] = intval( $row->rc_logid );
+ $vals['logtype'] = $row->rc_log_type;
+ $vals['logaction'] = $row->rc_log_action;
+ $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
+ ApiQueryLogEvents::addLogParams(
+ $this->getResult(),
+ $vals,
+ $logEntry->getParameters(),
+ $logEntry->getType(),
+ $logEntry->getSubtype(),
+ $logEntry->getTimestamp()
+ );
}
- return $retval;
}
- switch ( $type ) {
- case 'edit':
- return RC_EDIT;
- case 'new':
- return RC_NEW;
- case 'log':
- return RC_LOG;
- case 'external':
- return RC_EXTERNAL;
+
+ if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = '';
}
+
+ return $vals;
}
public function getAllowedParams() {
@@ -424,6 +493,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'!anon',
'patrolled',
'!patrolled',
+ 'unread',
+ '!unread',
)
),
'type' => array(
@@ -440,12 +511,14 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
),
'token' => array(
ApiBase::PARAM_TYPE => 'string'
- )
+ ),
+ 'continue' => null,
);
}
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'allrev' => 'Include multiple revisions of the same page within given timeframe',
'start' => 'The timestamp to start enumerating from',
@@ -482,105 +555,14 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
' log - Log entries',
),
'owner' => 'The name of the user whose watchlist you\'d like to access',
- 'token' => 'Give a security token (settable in preferences) to allow access to another user\'s watchlist'
- );
- }
-
- public function getResultProperties() {
- global $wgLogTypes;
- return array(
- '' => array(
- 'type' => array(
- ApiBase::PROP_TYPE => array(
- 'edit',
- 'new',
- 'move',
- 'log',
- 'move over redirect'
- )
- )
- ),
- 'ids' => array(
- 'pageid' => 'integer',
- 'revid' => 'integer',
- 'old_revid' => 'integer'
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'user' => array(
- 'user' => 'string',
- 'anon' => 'boolean'
- ),
- 'userid' => array(
- 'userid' => 'integer',
- 'anon' => 'boolean'
- ),
- 'flags' => array(
- 'new' => 'boolean',
- 'minor' => 'boolean',
- 'bot' => 'boolean'
- ),
- 'patrol' => array(
- 'patrolled' => 'boolean'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'sizes' => array(
- 'oldlen' => 'integer',
- 'newlen' => 'integer'
- ),
- 'notificationtimestamp' => array(
- 'notificationtimestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'comment' => array(
- 'comment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'parsedcomment' => array(
- 'parsedcomment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'loginfo' => array(
- 'logid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'logtype' => array(
- ApiBase::PROP_TYPE => $wgLogTypes,
- ApiBase::PROP_NULLABLE => true
- ),
- 'logaction' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
+ 'token' => 'Give a security token (settable in preferences) to ' .
+ 'allow access to another user\'s watchlist',
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return "Get all recent changes to pages in the logged in user's watchlist";
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'bad_wlowner', 'info' => 'Specified user does not exist' ),
- array( 'code' => 'bad_wltoken', 'info' => 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences' ),
- array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to have a watchlist' ),
- array( 'code' => 'patrol', 'info' => 'patrol property is not available' ),
- array( 'show' ),
- array( 'code' => 'permissiondenied', 'info' => 'You need the patrol right to request the patrolled flag' ),
- array( 'code' => 'user-excludeuser', 'info' => 'user and excludeuser cannot be used together' ),
- ) );
+ return "Get all recent changes to pages in the logged in user's watchlist.";
}
public function getExamples() {
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index ea4e724a..6b2223ac 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -32,7 +32,7 @@
*/
class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'wr' );
}
@@ -45,7 +45,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -91,7 +91,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', array(
'wl_namespace' . $sort,
'wl_title' . $sort
- ));
+ ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
@@ -100,7 +100,8 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
$count = 0;
foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue', $row->wl_namespace . '|' . $row->wl_title );
break;
}
@@ -109,8 +110,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $t );
- if ( isset( $prop['changed'] ) && !is_null( $row->wl_notificationtimestamp ) )
- {
+ if ( isset( $prop['changed'] ) && !is_null( $row->wl_notificationtimestamp ) ) {
$vals['changed'] = wfTimestamp( TS_ISO_8601, $row->wl_notificationtimestamp );
}
$fit = $this->getResult()->addValue( $this->getModuleName(), null, $vals );
@@ -183,37 +183,14 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
),
'show' => 'Only list items that meet these criteria',
'owner' => 'The name of the user whose watchlist you\'d like to access',
- 'token' => 'Give a security token (settable in preferences) to allow access to another user\'s watchlist',
+ 'token' => 'Give a security token (settable in preferences) to allow ' .
+ 'access to another user\'s watchlist',
'dir' => 'Direction to sort the titles and namespaces in',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'changed' => array(
- 'changed' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
- return "Get all pages on the logged in user's watchlist";
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to have a watchlist' ),
- array( 'show' ),
- array( 'code' => 'bad_wlowner', 'info' => 'Specified user does not exist' ),
- array( 'code' => 'bad_wltoken', 'info' => 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences' ),
- ) );
+ return "Get all pages on the logged in user's watchlist.";
}
public function getExamples() {
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index 39c114b8..2e80447e 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -26,18 +26,20 @@
/**
* This class represents the result of the API operations.
- * It simply wraps a nested array() structure, adding some functions to simplify array's modifications.
- * As various modules execute, they add different pieces of information to this result,
- * structuring it as it will be given to the client.
+ * It simply wraps a nested array() structure, adding some functions to simplify
+ * array's modifications. As various modules execute, they add different pieces
+ * of information to this result, structuring it as it will be given to the client.
*
* Each subarray may either be a dictionary - key-value pairs with unique keys,
* or lists, where the items are added using $data[] = $value notation.
*
- * There are two special key values that change how XML output is generated:
- * '_element' This key sets the tag name for the rest of the elements in the current array.
- * It is only inserted if the formatter returned true for getNeedsRawData()
- * '*' This key has special meaning only to the XML formatter, and is outputted as is
- * for all others. In XML it becomes the content of the current element.
+ * There are three special key values that change how XML output is generated:
+ * '_element' This key sets the tag name for the rest of the elements in the current array.
+ * It is only inserted if the formatter returned true for getNeedsRawData()
+ * '_subelements' This key causes the specified elements to be returned as subelements rather than attributes.
+ * It is only inserted if the formatter returned true for getNeedsRawData()
+ * '*' This key has special meaning only to the XML formatter, and is outputted as is
+ * for all others. In XML it becomes the content of the current element.
*
* @ingroup API
*/
@@ -56,13 +58,27 @@ class ApiResult extends ApiBase {
*/
const ADD_ON_TOP = 2;
+ /**
+ * For addValue() and setElement(), do not check size while adding a value
+ * Don't use this unless you REALLY know what you're doing.
+ * Values added while the size checking was disabled will never be counted
+ * @since 1.24
+ */
+ const NO_SIZE_CHECK = 4;
+
private $mData, $mIsRawMode, $mSize, $mCheckingSize;
+ private $continueAllModules = array();
+ private $continueGeneratedModules = array();
+ private $continuationData = array();
+ private $generatorContinuationData = array();
+ private $generatorParams = array();
+ private $generatorDone = false;
+
/**
- * Constructor
- * @param $main ApiMain object
+ * @param ApiMain $main
*/
- public function __construct( $main ) {
+ public function __construct( ApiMain $main ) {
parent::__construct( $main, 'result' );
$this->mIsRawMode = false;
$this->mCheckingSize = true;
@@ -80,9 +96,11 @@ class ApiResult extends ApiBase {
/**
* Call this function when special elements such as '_element'
* are needed by the formatter, for example in XML printing.
+ * @since 1.23 $flag parameter added
+ * @param bool $flag Set the raw mode flag to this state
*/
- public function setRawMode() {
- $this->mIsRawMode = true;
+ public function setRawMode( $flag = true ) {
+ $this->mIsRawMode = $flag;
}
/**
@@ -104,7 +122,7 @@ class ApiResult extends ApiBase {
/**
* Get the 'real' size of a result item. This means the strlen() of the item,
* or the sum of the strlen()s of the elements if the item is an array.
- * @param $value mixed
+ * @param mixed $value
* @return int
*/
public static function size( $value ) {
@@ -117,6 +135,7 @@ class ApiResult extends ApiBase {
// Objects can't always be cast to string
$s = strlen( $value );
}
+
return $s;
}
@@ -132,6 +151,7 @@ class ApiResult extends ApiBase {
* Disable size checking in addValue(). Don't use this unless you
* REALLY know what you're doing. Values added while size checking
* was disabled will not be counted (ever)
+ * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
*/
public function disableSizeCheck() {
$this->mCheckingSize = false;
@@ -139,6 +159,7 @@ class ApiResult extends ApiBase {
/**
* Re-enable size checking in addValue()
+ * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
*/
public function enableSizeCheck() {
$this->mCheckingSize = true;
@@ -147,17 +168,20 @@ class ApiResult extends ApiBase {
/**
* Add an output value to the array by name.
* Verifies that value with the same name has not been added before.
- * @param array $arr to add $value to
+ * @param array $arr To add $value to
* @param string $name Index of $arr to add $value at
- * @param $value mixed
- * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP. This parameter used to be
- * boolean, and the value of OVERRIDE=1 was specifically chosen so that it would be backwards
- * compatible with the new method signature.
+ * @param mixed $value
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
+ * This parameter used to be boolean, and the value of OVERRIDE=1 was
+ * specifically chosen so that it would be backwards compatible with the
+ * new method signature.
*
* @since 1.21 int $flags replaced boolean $override
*/
public static function setElement( &$arr, $name, $value, $flags = 0 ) {
- if ( $arr === null || $name === null || $value === null || !is_array( $arr ) || is_array( $name ) ) {
+ if ( $arr === null || $name === null || $value === null
+ || !is_array( $arr ) || is_array( $name )
+ ) {
ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
}
@@ -176,16 +200,19 @@ class ApiResult extends ApiBase {
ApiBase::dieDebug( __METHOD__, "Attempting to merge element $name" );
}
} else {
- ApiBase::dieDebug( __METHOD__, "Attempting to add element $name=$value, existing value is {$arr[$name]}" );
+ ApiBase::dieDebug(
+ __METHOD__,
+ "Attempting to add element $name=$value, existing value is {$arr[$name]}"
+ );
}
}
/**
* Adds a content element to an array.
* Use this function instead of hardcoding the '*' element.
- * @param array $arr to add the content element to
- * @param $value Mixed
- * @param string $subElemName when present, content element is created
+ * @param array $arr To add the content element to
+ * @param mixed $value
+ * @param string $subElemName When present, content element is created
* as a sub item of $arr. Use this parameter to create elements in
* format "<elem>text</elem>" without attributes.
*/
@@ -204,10 +231,34 @@ class ApiResult extends ApiBase {
}
/**
+ * Causes the elements with the specified names to be output as
+ * subelements rather than attributes.
+ * @param array $arr
+ * @param array|string $names The element name(s) to be output as subelements
+ */
+ public function setSubelements( &$arr, $names ) {
+ // In raw mode, add the '_subelements', otherwise just ignore
+ if ( !$this->getIsRawMode() ) {
+ return;
+ }
+ if ( $arr === null || $names === null || !is_array( $arr ) ) {
+ ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+ }
+ if ( !is_array( $names ) ) {
+ $names = array( $names );
+ }
+ if ( !isset( $arr['_subelements'] ) ) {
+ $arr['_subelements'] = $names;
+ } else {
+ $arr['_subelements'] = array_merge( $arr['_subelements'], $names );
+ }
+ }
+
+ /**
* In case the array contains indexed values (in addition to named),
* give all indexed values the given tag name. This function MUST be
* called on every array that has numerical indexes.
- * @param $arr array
+ * @param array $arr
* @param string $tag Tag name
*/
public function setIndexedTagName( &$arr, $tag ) {
@@ -224,7 +275,7 @@ class ApiResult extends ApiBase {
/**
* Calls setIndexedTagName() on each sub-array of $arr
- * @param $arr array
+ * @param array $arr
* @param string $tag Tag name
*/
public function setIndexedTagName_recursive( &$arr, $tag ) {
@@ -245,7 +296,7 @@ class ApiResult extends ApiBase {
* Don't specify a path to a value that's not in the result, or
* you'll get nasty errors.
* @param array $path Path to the array, like addValue()'s $path
- * @param $tag string
+ * @param string $tag
*/
public function setIndexedTagName_internal( $path, $tag ) {
$data = &$this->mData;
@@ -268,26 +319,26 @@ class ApiResult extends ApiBase {
* If $path is null, the value will be inserted at the data root.
* If $name is empty, the $value is added as a next list element data[] = $value.
*
- * @param $path array|string|null
- * @param $name string
- * @param $value mixed
- * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP. This parameter used to be
- * boolean, and the value of OVERRIDE=1 was specifically chosen so that it would be backwards
- * compatible with the new method signature.
+ * @param array|string|null $path
+ * @param string $name
+ * @param mixed $value
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
+ * This parameter used to be boolean, and the value of OVERRIDE=1 was specifically
+ * chosen so that it would be backwards compatible with the new method signature.
* @return bool True if $value fits in the result, false if not
*
* @since 1.21 int $flags replaced boolean $override
*/
public function addValue( $path, $name, $value, $flags = 0 ) {
- global $wgAPIMaxResultSize;
-
$data = &$this->mData;
- if ( $this->mCheckingSize ) {
+ if ( $this->mCheckingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
$newsize = $this->mSize + self::size( $value );
- if ( $newsize > $wgAPIMaxResultSize ) {
+ $maxResultSize = $this->getConfig()->get( 'APIMaxResultSize' );
+ if ( $newsize > $maxResultSize ) {
$this->setWarning(
"This result was truncated because it would otherwise be larger than the " .
- "limit of {$wgAPIMaxResultSize} bytes" );
+ "limit of {$maxResultSize} bytes" );
+
return false;
}
$this->mSize = $newsize;
@@ -322,14 +373,15 @@ class ApiResult extends ApiBase {
// Add named element
self::setElement( $data, $name, $value, $flags );
}
+
return true;
}
/**
* Add a parsed limit=max to the result.
*
- * @param $moduleName string
- * @param $limit int
+ * @param string $moduleName
+ * @param int $limit
*/
public function setParsedLimit( $moduleName, $limit ) {
// Add value, allowing overwriting
@@ -340,8 +392,8 @@ class ApiResult extends ApiBase {
* Unset a value previously added to the result set.
* Fails silently if the value isn't found.
* For parameters, see addValue()
- * @param $path array|null
- * @param $name string
+ * @param array|null $path
+ * @param string $name
*/
public function unsetValue( $path, $name ) {
$data = &$this->mData;
@@ -367,7 +419,7 @@ class ApiResult extends ApiBase {
/**
* Callback function for cleanUpUTF8()
*
- * @param $s string
+ * @param string $s
*/
private static function cleanUp_helper( &$s ) {
if ( !is_string( $s ) ) {
@@ -394,10 +446,186 @@ class ApiResult extends ApiBase {
$result[] = $error;
}
$this->setIndexedTagName( $result, $errorType );
+
return $result;
}
public function execute() {
ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' );
}
+
+ /**
+ * Parse a 'continue' parameter and return status information.
+ *
+ * This must be balanced by a call to endContinuation().
+ *
+ * @since 1.24
+ * @param string|null $continue The "continue" parameter, if any
+ * @param ApiBase[] $allModules Contains ApiBase instances that will be executed
+ * @param array $generatedModules Names of modules that depend on the generator
+ * @return array Two elements: a boolean indicating if the generator is done,
+ * and an array of modules to actually execute.
+ */
+ public function beginContinuation(
+ $continue, array $allModules = array(), array $generatedModules = array()
+ ) {
+ $this->continueGeneratedModules = $generatedModules
+ ? array_combine( $generatedModules, $generatedModules )
+ : array();
+ $this->continuationData = array();
+ $this->generatorContinuationData = array();
+ $this->generatorParams = array();
+
+ $skip = array();
+ if ( is_string( $continue ) && $continue !== '' ) {
+ $continue = explode( '||', $continue );
+ $this->dieContinueUsageIf( count( $continue ) !== 2 );
+ $this->generatorDone = ( $continue[0] === '-' );
+ if ( !$this->generatorDone ) {
+ $this->generatorParams = explode( '|', $continue[0] );
+ }
+ $skip = explode( '|', $continue[1] );
+ }
+
+ $this->continueAllModules = array();
+ $runModules = array();
+ foreach ( $allModules as $module ) {
+ $name = $module->getModuleName();
+ if ( in_array( $name, $skip ) ) {
+ $this->continueAllModules[$name] = false;
+ // Prevent spurious "unused parameter" warnings
+ $module->extractRequestParams();
+ } else {
+ $this->continueAllModules[$name] = true;
+ $runModules[] = $module;
+ }
+ }
+
+ return array(
+ $this->generatorDone,
+ $runModules,
+ );
+ }
+
+ /**
+ * Set the continuation parameter for a module
+ *
+ * @since 1.24
+ * @param ApiBase $module
+ * @param string $paramName
+ * @param string|array $paramValue
+ */
+ public function setContinueParam( ApiBase $module, $paramName, $paramValue ) {
+ $name = $module->getModuleName();
+ if ( !isset( $this->continueAllModules[$name] ) ) {
+ throw new MWException(
+ "Module '$name' called ApiResult::setContinueParam but was not " .
+ 'passed to ApiResult::beginContinuation'
+ );
+ }
+ if ( !$this->continueAllModules[$name] ) {
+ throw new MWException(
+ "Module '$name' was not supposed to have been executed, but " .
+ 'it was executed anyway'
+ );
+ }
+ $paramName = $module->encodeParamName( $paramName );
+ if ( is_array( $paramValue ) ) {
+ $paramValue = join( '|', $paramValue );
+ }
+ $this->continuationData[$name][$paramName] = $paramValue;
+ }
+
+ /**
+ * Set the continuation parameter for the generator module
+ *
+ * @since 1.24
+ * @param ApiBase $module
+ * @param string $paramName
+ * @param string|array $paramValue
+ */
+ public function setGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
+ $name = $module->getModuleName();
+ $paramName = $module->encodeParamName( $paramName );
+ if ( is_array( $paramValue ) ) {
+ $paramValue = join( '|', $paramValue );
+ }
+ $this->generatorContinuationData[$name][$paramName] = $paramValue;
+ }
+
+ /**
+ * Close continuation, writing the data into the result
+ *
+ * @since 1.24
+ * @param string $style 'standard' for the new style since 1.21, 'raw' for
+ * the style used in 1.20 and earlier.
+ */
+ public function endContinuation( $style = 'standard' ) {
+ if ( $style === 'raw' ) {
+ $key = 'query-continue';
+ $data = array_merge_recursive(
+ $this->continuationData, $this->generatorContinuationData
+ );
+ } else {
+ $key = 'continue';
+ $data = array();
+
+ $finishedModules = array_diff(
+ array_keys( $this->continueAllModules ),
+ array_keys( $this->continuationData )
+ );
+
+ // First, grab the non-generator-using continuation data
+ $continuationData = array_diff_key(
+ $this->continuationData, $this->continueGeneratedModules
+ );
+ foreach ( $continuationData as $module => $kvp ) {
+ $data += $kvp;
+ }
+
+ // Next, handle the generator-using continuation data
+ $continuationData = array_intersect_key(
+ $this->continuationData, $this->continueGeneratedModules
+ );
+ if ( $continuationData ) {
+ // Some modules are unfinished: include those params, and copy
+ // the generator params.
+ foreach ( $continuationData as $module => $kvp ) {
+ $data += $kvp;
+ }
+ $data += array_intersect_key(
+ $this->getMain()->getRequest()->getValues(),
+ array_flip( $this->generatorParams )
+ );
+ } elseif ( $this->generatorContinuationData ) {
+ // All the generator-using modules are complete, but the
+ // generator isn't. Continue the generator and restart the
+ // generator-using modules
+ $this->generatorParams = array();
+ foreach ( $this->generatorContinuationData as $kvp ) {
+ $this->generatorParams = array_merge(
+ $this->generatorParams, array_keys( $kvp )
+ );
+ $data += $kvp;
+ }
+ $finishedModules = array_diff(
+ $finishedModules, $this->continueGeneratedModules
+ );
+ } else {
+ // Generator and prop modules are all done. Mark it so.
+ $this->generatorDone = true;
+ }
+
+ // Set 'continue' if any continuation data is set or if the generator
+ // still needs to run
+ if ( $data || !$this->generatorDone ) {
+ $data['continue'] =
+ ( $this->generatorDone ? '-' : join( '|', $this->generatorParams ) ) .
+ '||' . join( '|', $finishedModules );
+ }
+ }
+ if ( $data ) {
+ $this->addValue( null, $key, $data, ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+ }
}
diff --git a/includes/api/ApiRevisionDelete.php b/includes/api/ApiRevisionDelete.php
new file mode 100644
index 00000000..cbc30704
--- /dev/null
+++ b/includes/api/ApiRevisionDelete.php
@@ -0,0 +1,236 @@
+<?php
+/**
+ * Created on Jun 25, 2013
+ *
+ * Copyright © 2013 Brad Jorsch <bjorsch@wikimedia.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
+ * @since 1.23
+ */
+
+/**
+ * API interface to RevDel. The API equivalent of Special:RevisionDelete.
+ * Requires API write mode to be enabled.
+ *
+ * @ingroup API
+ */
+class ApiRevisionDelete extends ApiBase {
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $user = $this->getUser();
+
+ if ( !$user->isAllowed( RevisionDeleter::getRestriction( $params['type'] ) ) ) {
+ $this->dieUsageMsg( 'badaccess-group0' );
+ }
+
+ if ( !$params['ids'] ) {
+ $this->dieUsage( "At least one value is required for 'ids'", 'badparams' );
+ }
+
+ $hide = $params['hide'] ?: array();
+ $show = $params['show'] ?: array();
+ if ( array_intersect( $hide, $show ) ) {
+ $this->dieUsage( "Mutually exclusive values for 'hide' and 'show'", 'badparams' );
+ } elseif ( !$hide && !$show ) {
+ $this->dieUsage( "At least one value is required for 'hide' or 'show'", 'badparams' );
+ }
+ $bits = array(
+ 'content' => RevisionDeleter::getRevdelConstant( $params['type'] ),
+ 'comment' => Revision::DELETED_COMMENT,
+ 'user' => Revision::DELETED_USER,
+ );
+ $bitfield = array();
+ foreach ( $bits as $key => $bit ) {
+ if ( in_array( $key, $hide ) ) {
+ $bitfield[$bit] = 1;
+ } elseif ( in_array( $key, $show ) ) {
+ $bitfield[$bit] = 0;
+ } else {
+ $bitfield[$bit] = -1;
+ }
+ }
+
+ if ( $params['suppress'] === 'yes' ) {
+ if ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $this->dieUsageMsg( 'badaccess-group0' );
+ }
+ $bitfield[Revision::DELETED_RESTRICTED] = 1;
+ } elseif ( $params['suppress'] === 'no' ) {
+ $bitfield[Revision::DELETED_RESTRICTED] = 0;
+ } else {
+ $bitfield[Revision::DELETED_RESTRICTED] = -1;
+ }
+
+ $targetObj = null;
+ if ( $params['target'] ) {
+ $targetObj = Title::newFromText( $params['target'] );
+ }
+ $targetObj = RevisionDeleter::suggestTarget( $params['type'], $targetObj, $params['ids'] );
+ if ( $targetObj === null ) {
+ $this->dieUsage( 'A target title is required for this RevDel type', 'needtarget' );
+ }
+
+ $list = RevisionDeleter::createList(
+ $params['type'], $this->getContext(), $targetObj, $params['ids']
+ );
+ $status = $list->setVisibility(
+ array( 'value' => $bitfield, 'comment' => $params['reason'], 'perItemStatus' => true )
+ );
+
+ $result = $this->getResult();
+ $data = $this->extractStatusInfo( $status );
+ $data['target'] = $targetObj->getFullText();
+ $data['items'] = array();
+
+ foreach ( $status->itemStatuses as $id => $s ) {
+ $data['items'][$id] = $this->extractStatusInfo( $s );
+ $data['items'][$id]['id'] = $id;
+ }
+
+ $list->reloadFromMaster();
+ // @codingStandardsIgnoreStart Avoid function calls in a FOR loop test part
+ for ( $item = $list->reset(); $list->current(); $item = $list->next() ) {
+ $data['items'][$item->getId()] += $item->getApiData( $this->getResult() );
+ }
+ // @codingStandardsIgnoreEnd
+
+ $data['items'] = array_values( $data['items'] );
+ $result->setIndexedTagName( $data['items'], 'i' );
+ $result->addValue( null, $this->getModuleName(), $data );
+ }
+
+ private function extractStatusInfo( $status ) {
+ $ret = array(
+ 'status' => $status->isOK() ? 'Success' : 'Fail',
+ );
+ $errors = $this->formatStatusMessages( $status->getErrorsByType( 'error' ) );
+ if ( $errors ) {
+ $this->getResult()->setIndexedTagName( $errors, 'e' );
+ $ret['errors'] = $errors;
+ }
+ $warnings = $this->formatStatusMessages( $status->getErrorsByType( 'warning' ) );
+ if ( $warnings ) {
+ $this->getResult()->setIndexedTagName( $warnings, 'w' );
+ $ret['warnings'] = $warnings;
+ }
+
+ return $ret;
+ }
+
+ private function formatStatusMessages( $messages ) {
+ if ( !$messages ) {
+ return array();
+ }
+ $result = $this->getResult();
+ $ret = array();
+ foreach ( $messages as $m ) {
+ $message = array();
+ if ( $m['message'] instanceof Message ) {
+ $msg = $m['message'];
+ $message = array( 'message' => $msg->getKey() );
+ if ( $msg->getParams() ) {
+ $message['params'] = $msg->getParams();
+ $result->setIndexedTagName( $message['params'], 'p' );
+ }
+ } else {
+ $message = array( 'message' => $m['message'] );
+ $msg = wfMessage( $m['message'] );
+ if ( isset( $m['params'] ) ) {
+ $message['params'] = $m['params'];
+ $result->setIndexedTagName( $message['params'], 'p' );
+ $msg->params( $m['params'] );
+ }
+ }
+ $message['rendered'] = $msg->useDatabase( false )->inLanguage( 'en' )->plain();
+ $ret[] = $message;
+ }
+
+ return $ret;
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'type' => array(
+ ApiBase::PARAM_TYPE => RevisionDeleter::getTypes(),
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'target' => null,
+ 'ids' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'hide' => array(
+ ApiBase::PARAM_TYPE => array( 'content', 'comment', 'user' ),
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'show' => array(
+ ApiBase::PARAM_TYPE => array( 'content', 'comment', 'user' ),
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'suppress' => array(
+ ApiBase::PARAM_TYPE => array( 'yes', 'no', 'nochange' ),
+ ApiBase::PARAM_DFLT => 'nochange',
+ ),
+ 'reason' => null,
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'type' => 'Type of revision deletion being performed',
+ 'target' => 'Page title for the revision deletion, if required for the type',
+ 'ids' => 'Identifiers for the revisions to be deleted',
+ 'hide' => 'What to hide for each revision',
+ 'show' => 'What to unhide for each revision',
+ 'suppress' => 'Whether to suppress data from administrators as well as others',
+ 'reason' => 'Reason for the deletion/undeletion',
+ );
+ }
+
+ public function getDescription() {
+ return 'Delete/undelete revisions.';
+ }
+
+ public function needsToken() {
+ return 'csrf';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=revisiondelete&target=Main%20Page&type=revision&ids=12345&' .
+ 'hide=content&token=123ABC'
+ => 'Hide content for revision 12345 on the Main Page',
+ 'api.php?action=revisiondelete&type=logging&ids=67890&hide=content|comment|user&' .
+ 'reason=BLP%20violation&token=123ABC'
+ => 'Hide all data on log entry 67890 with the reason "BLP violation"',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Revisiondelete';
+ }
+}
diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php
index b9873f49..f4d3c541 100644
--- a/includes/api/ApiRollback.php
+++ b/includes/api/ApiRollback.php
@@ -40,21 +40,44 @@ class ApiRollback extends ApiBase {
private $mUser = null;
public function execute() {
+ $user = $this->getUser();
$params = $this->extractRequestParams();
- // User and title already validated in call to getTokenSalt from Main
- $titleObj = $this->getRbTitle();
+ // WikiPage::doRollback needs a Web UI token, so get one of those if we
+ // validated based on an API rollback token.
+ $token = $params['token'];
+ if ( $user->matchEditToken( $token, 'rollback', $this->getRequest() ) ) {
+ $token = $this->getUser()->getEditToken(
+ $this->getWebUITokenSalt( $params ),
+ $this->getRequest()
+ );
+ }
+
+ $titleObj = $this->getRbTitle( $params );
$pageObj = WikiPage::factory( $titleObj );
$summary = $params['summary'];
$details = array();
- $retval = $pageObj->doRollback( $this->getRbUser(), $summary, $params['token'], $params['markbot'], $details, $this->getUser() );
+ $retval = $pageObj->doRollback(
+ $this->getRbUser( $params ),
+ $summary,
+ $token,
+ $params['markbot'],
+ $details,
+ $user
+ );
if ( $retval ) {
// We don't care about multiple errors, just report one of them
$this->dieUsageMsg( reset( $retval ) );
}
- $this->setWatch( $params['watchlist'], $titleObj );
+ $watch = 'preferences';
+ if ( isset( $params['watchlist'] ) ) {
+ $watch = $params['watchlist'];
+ }
+
+ // Watch pages
+ $this->setWatch( $watch, $titleObj, 'watchrollback' );
$info = array(
'title' => $titleObj->getPrefixedText(),
@@ -78,18 +101,14 @@ class ApiRollback extends ApiBase {
public function getAllowedParams() {
return array(
- 'title' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
+ 'title' => null,
+ 'pageid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
),
'user' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'summary' => '',
'markbot' => false,
'watchlist' => array(
@@ -105,59 +124,51 @@ class ApiRollback extends ApiBase {
}
public function getParamDescription() {
+ $p = $this->getModulePrefix();
+
return array(
- 'title' => 'Title of the page you want to rollback.',
- 'user' => 'Name of the user whose edits are to be rolled back. If set incorrectly, you\'ll get a badtoken error.',
- 'token' => "A rollback token previously retrieved through {$this->getModulePrefix()}prop=revisions",
+ 'title' => "Title of the page you want to roll back. Cannot be used together with {$p}pageid",
+ 'pageid' => "Page ID of the page you want to roll back. Cannot be used together with {$p}title",
+ 'user' => 'Name of the user whose edits are to be rolled back.',
+ 'token' => array(
+ /* Standard description automatically prepended */
+ 'For compatibility, the token used in the web UI is also accepted.'
+ ),
'summary' => 'Custom edit summary. If empty, default summary will be used',
'markbot' => 'Mark the reverted edits and the revert as bot edits',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'title' => 'string',
- 'pageid' => 'integer',
- 'summary' => 'string',
- 'revid' => 'integer',
- 'old_revid' => 'integer',
- 'last_revid' => 'integer'
- )
+ 'watchlist' => 'Unconditionally add or remove the page from your watchlist, ' .
+ 'use preferences or do not change watch',
);
}
public function getDescription() {
return array(
- 'Undo the last edit to the page. If the last user who edited the page made multiple edits in a row,',
- 'they will all be rolled back'
+ 'Undo the last edit to the page. If the last user who edited the page made',
+ 'multiple edits in a row, they will all be rolled back.'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'invalidtitle', 'title' ),
- array( 'notanarticle' ),
- array( 'invaliduser', 'user' ),
- ) );
- }
-
public function needsToken() {
- return true;
+ return 'rollback';
}
- public function getTokenSalt() {
- return array( $this->getRbTitle()->getPrefixedText(), $this->getRbUser() );
+ protected function getWebUITokenSalt( array $params ) {
+ return array(
+ $this->getRbTitle( $params )->getPrefixedText(),
+ $this->getRbUser( $params )
+ );
}
- private function getRbUser() {
+ /**
+ * @param array $params
+ *
+ * @return string
+ */
+ private function getRbUser( array $params ) {
if ( $this->mUser !== null ) {
return $this->mUser;
}
- $params = $this->extractRequestParams();
-
// We need to be able to revert IPs, but getCanonicalName rejects them
$this->mUser = User::isIP( $params['user'] )
? $params['user']
@@ -170,20 +181,29 @@ class ApiRollback extends ApiBase {
}
/**
+ * @param array $params
+ *
* @return Title
*/
- private function getRbTitle() {
+ private function getRbTitle( array $params ) {
if ( $this->mTitleObj !== null ) {
return $this->mTitleObj;
}
- $params = $this->extractRequestParams();
-
- $this->mTitleObj = Title::newFromText( $params['title'] );
-
- if ( !$this->mTitleObj || $this->mTitleObj->isExternal() ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
+
+ if ( isset( $params['title'] ) ) {
+ $this->mTitleObj = Title::newFromText( $params['title'] );
+ if ( !$this->mTitleObj || $this->mTitleObj->isExternal() ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
+ } elseif ( isset( $params['pageid'] ) ) {
+ $this->mTitleObj = Title::newFromID( $params['pageid'] );
+ if ( !$this->mTitleObj ) {
+ $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
+ }
}
+
if ( !$this->mTitleObj->exists() ) {
$this->dieUsageMsg( 'notanarticle' );
}
@@ -194,7 +214,9 @@ class ApiRollback extends ApiBase {
public function getExamples() {
return array(
'api.php?action=rollback&title=Main%20Page&user=Catrope&token=123ABC',
- 'api.php?action=rollback&title=Main%20Page&user=217.121.114.116&token=123ABC&summary=Reverting%20vandalism&markbot=1'
+ 'api.php?action=rollback&pageid=122&user=Catrope&token=123ABC',
+ 'api.php?action=rollback&title=Main%20Page&user=217.121.114.116&' .
+ 'token=123ABC&summary=Reverting%20vandalism&markbot=1'
);
}
diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php
index d219c91c..a2771a0c 100644
--- a/includes/api/ApiRsd.php
+++ b/includes/api/ApiRsd.php
@@ -60,7 +60,7 @@ class ApiRsd extends ApiBase {
}
public function getDescription() {
- return 'Export an RSD (Really Simple Discovery) schema';
+ return 'Export an RSD (Really Simple Discovery) schema.';
}
public function getExamples() {
@@ -69,6 +69,10 @@ class ApiRsd extends ApiBase {
);
}
+ public function isReadMode() {
+ return false;
+ }
+
/**
* Builds an internal list of APIs to expose information about.
* Normally this only lists the MediaWiki API, with its base URL,
@@ -107,6 +111,7 @@ class ApiRsd extends ApiBase {
),
);
wfRunHooks( 'ApiRsdServiceApis', array( &$apis ) );
+
return $apis;
}
@@ -149,12 +154,13 @@ class ApiRsd extends ApiBase {
}
$outputData[] = $data;
}
+
return $outputData;
}
}
class ApiFormatXmlRsd extends ApiFormatXml {
- public function __construct( $main, $format ) {
+ public function __construct( ApiMain $main, $format ) {
parent::__construct( $main, $format );
$this->setRootElement( 'rsd' );
}
diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php
index 53a68fde..5d527fc7 100644
--- a/includes/api/ApiSetNotificationTimestamp.php
+++ b/includes/api/ApiSetNotificationTimestamp.php
@@ -46,9 +46,14 @@ class ApiSetNotificationTimestamp extends ApiBase {
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' );
+ $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+
$pageSet = $this->getPageSet();
if ( $params['entirewatchlist'] && $pageSet->getDataSource() !== null ) {
- $this->dieUsage( "Cannot use 'entirewatchlist' at the same time as '{$pageSet->getDataSource()}'", 'multisource' );
+ $this->dieUsage(
+ "Cannot use 'entirewatchlist' at the same time as '{$pageSet->getDataSource()}'",
+ 'multisource'
+ );
}
$dbw = wfGetDB( DB_MASTER, 'api' );
@@ -67,22 +72,26 @@ class ApiSetNotificationTimestamp extends ApiBase {
$this->dieUsage( 'torevid may only be used with a single page', 'multpages' );
}
$title = reset( $pageSet->getGoodTitles() );
- $timestamp = Revision::getTimestampFromId( $title, $params['torevid'] );
- if ( $timestamp ) {
- $timestamp = $dbw->timestamp( $timestamp );
- } else {
- $timestamp = null;
+ if ( $title ) {
+ $timestamp = Revision::getTimestampFromId( $title, $params['torevid'] );
+ if ( $timestamp ) {
+ $timestamp = $dbw->timestamp( $timestamp );
+ } else {
+ $timestamp = null;
+ }
}
} elseif ( isset( $params['newerthanrevid'] ) ) {
if ( $params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1 ) {
$this->dieUsage( 'newerthanrevid may only be used with a single page', 'multpages' );
}
$title = reset( $pageSet->getGoodTitles() );
- $revid = $title->getNextRevisionID( $params['newerthanrevid'] );
- if ( $revid ) {
- $timestamp = $dbw->timestamp( Revision::getTimestampFromId( $title, $revid ) );
- } else {
- $timestamp = null;
+ if ( $title ) {
+ $revid = $title->getNextRevisionID( $params['newerthanrevid'] );
+ if ( $revid ) {
+ $timestamp = $dbw->timestamp( Revision::getTimestampFromId( $title, $revid ) );
+ } else {
+ $timestamp = null;
+ }
}
}
@@ -95,7 +104,9 @@ class ApiSetNotificationTimestamp extends ApiBase {
__METHOD__
);
- $result['notificationtimestamp'] = ( is_null( $timestamp ) ? '' : wfTimestamp( TS_ISO_8601, $timestamp ) );
+ $result['notificationtimestamp'] = is_null( $timestamp )
+ ? ''
+ : wfTimestamp( TS_ISO_8601, $timestamp );
} else {
// First, log the invalid titles
foreach ( $pageSet->getInvalidTitles() as $title ) {
@@ -119,49 +130,55 @@ class ApiSetNotificationTimestamp extends ApiBase {
$result[] = $rev;
}
- // Now process the valid titles
- $lb = new LinkBatch( $pageSet->getTitles() );
- $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $timestamp ),
- array( 'wl_user' => $user->getID(), $lb->constructSet( 'wl', $dbw ) ),
- __METHOD__
- );
-
- // Query the results of our update
- $timestamps = array();
- $res = $dbw->select( 'watchlist', array( 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ),
- array( 'wl_user' => $user->getID(), $lb->constructSet( 'wl', $dbw ) ),
- __METHOD__
- );
- foreach ( $res as $row ) {
- $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
- }
+ if ( $pageSet->getTitles() ) {
+ // Now process the valid titles
+ $lb = new LinkBatch( $pageSet->getTitles() );
+ $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $timestamp ),
+ array( 'wl_user' => $user->getID(), $lb->constructSet( 'wl', $dbw ) ),
+ __METHOD__
+ );
- // Now, put the valid titles into the result
- /** @var $title Title */
- foreach ( $pageSet->getTitles() as $title ) {
- $ns = $title->getNamespace();
- $dbkey = $title->getDBkey();
- $r = array(
- 'ns' => intval( $ns ),
- 'title' => $title->getPrefixedText(),
+ // Query the results of our update
+ $timestamps = array();
+ $res = $dbw->select(
+ 'watchlist',
+ array( 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ),
+ array( 'wl_user' => $user->getID(), $lb->constructSet( 'wl', $dbw ) ),
+ __METHOD__
);
- if ( !$title->exists() ) {
- $r['missing'] = '';
+ foreach ( $res as $row ) {
+ $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
}
- if ( isset( $timestamps[$ns] ) && array_key_exists( $dbkey, $timestamps[$ns] ) ) {
- $r['notificationtimestamp'] = '';
- if ( $timestamps[$ns][$dbkey] !== null ) {
- $r['notificationtimestamp'] = wfTimestamp( TS_ISO_8601, $timestamps[$ns][$dbkey] );
+
+ // Now, put the valid titles into the result
+ /** @var $title Title */
+ foreach ( $pageSet->getTitles() as $title ) {
+ $ns = $title->getNamespace();
+ $dbkey = $title->getDBkey();
+ $r = array(
+ 'ns' => intval( $ns ),
+ 'title' => $title->getPrefixedText(),
+ );
+ if ( !$title->exists() ) {
+ $r['missing'] = '';
}
- } else {
- $r['notwatched'] = '';
+ if ( isset( $timestamps[$ns] ) && array_key_exists( $dbkey, $timestamps[$ns] ) ) {
+ $r['notificationtimestamp'] = '';
+ if ( $timestamps[$ns][$dbkey] !== null ) {
+ $r['notificationtimestamp'] = wfTimestamp( TS_ISO_8601, $timestamps[$ns][$dbkey] );
+ }
+ } else {
+ $r['notwatched'] = '';
+ }
+ $result[] = $r;
}
- $result[] = $r;
}
$apiResult->setIndexedTagName( $result, 'page' );
}
$apiResult->addValue( null, $this->getModuleName(), $result );
+
+ $apiResult->endContinuation();
}
/**
@@ -172,6 +189,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
if ( !isset( $this->mPageSet ) ) {
$this->mPageSet = new ApiPageSet( $this );
}
+
return $this->mPageSet;
}
@@ -184,11 +202,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getAllowedParams( $flags = 0 ) {
@@ -196,7 +210,6 @@ class ApiSetNotificationTimestamp extends ApiBase {
'entirewatchlist' => array(
ApiBase::PARAM_TYPE => 'boolean'
),
- 'token' => null,
'timestamp' => array(
ApiBase::PARAM_TYPE => 'timestamp'
),
@@ -206,12 +219,13 @@ class ApiSetNotificationTimestamp extends ApiBase {
'newerthanrevid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
+ 'continue' => '',
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
}
- return $result;
+ return $result;
}
public function getParamDescription() {
@@ -220,44 +234,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
'timestamp' => 'Timestamp to which to set the notification timestamp',
'torevid' => 'Revision to set the notification timestamp to (one page only)',
'newerthanrevid' => 'Revision to set the notification timestamp newer than (one page only)',
- 'token' => 'A token previously acquired via prop=info',
- );
- }
-
- public function getResultProperties() {
- return array(
- ApiBase::PROP_LIST => true,
- ApiBase::PROP_ROOT => array(
- 'notificationtimestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- '' => array(
- 'ns' => array(
- ApiBase::PROP_TYPE => 'namespace',
- ApiBase::PROP_NULLABLE => true
- ),
- 'title' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'pageid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'revid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'invalid' => 'boolean',
- 'missing' => 'boolean',
- 'notwatched' => 'boolean',
- 'notificationtimestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- )
+ 'continue' => 'When more results are available, use this to continue',
);
}
@@ -269,28 +246,16 @@ class ApiSetNotificationTimestamp extends ApiBase {
);
}
- public function getPossibleErrors() {
- $ps = $this->getPageSet();
- return array_merge(
- parent::getPossibleErrors(),
- $ps->getFinalPossibleErrors(),
- $this->getRequireMaxOneParameterErrorMessages(
- array( 'timestamp', 'torevid', 'newerthanrevid' ) ),
- $this->getRequireOnlyOneParameterErrorMessages(
- array_merge( array( 'entirewatchlist' ), array_keys( $ps->getFinalParams() ) ) ),
- array(
- array( 'code' => 'notloggedin', 'info' => 'Anonymous users cannot use watchlist change notifications' ),
- array( 'code' => 'multpages', 'info' => 'torevid may only be used with a single page' ),
- array( 'code' => 'multpages', 'info' => 'newerthanrevid may only be used with a single page' ),
- )
- );
- }
-
public function getExamples() {
return array(
- 'api.php?action=setnotificationtimestamp&entirewatchlist=&token=123ABC' => 'Reset the notification status for the entire watchlist',
- 'api.php?action=setnotificationtimestamp&titles=Main_page&token=123ABC' => 'Reset the notification status for "Main page"',
- 'api.php?action=setnotificationtimestamp&titles=Main_page&timestamp=2012-01-01T00:00:00Z&token=123ABC' => 'Set the notification timestamp for "Main page" so all edits since 1 January 2012 are unviewed',
+ 'api.php?action=setnotificationtimestamp&entirewatchlist=&token=123ABC'
+ => 'Reset the notification status for the entire watchlist',
+ 'api.php?action=setnotificationtimestamp&titles=Main_page&token=123ABC'
+ => 'Reset the notification status for "Main page"',
+ 'api.php?action=setnotificationtimestamp&titles=Main_page&' .
+ 'timestamp=2012-01-01T00:00:00Z&token=123ABC'
+ => 'Set the notification timestamp for "Main page" so all edits ' .
+ 'since 1 January 2012 are unviewed',
);
}
diff --git a/includes/api/ApiTokens.php b/includes/api/ApiTokens.php
index d220a5e6..9287fe6e 100644
--- a/includes/api/ApiTokens.php
+++ b/includes/api/ApiTokens.php
@@ -25,11 +25,16 @@
*/
/**
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiTokens extends ApiBase {
public function execute() {
+ $this->setWarning(
+ "action=tokens has been deprecated. Please use action=query&meta=tokens instead."
+ );
+
$params = $this->extractRequestParams();
$res = array();
@@ -67,6 +72,7 @@ class ApiTokens extends ApiBase {
wfRunHooks( 'ApiTokensGetTokenTypes', array( &$types ) );
ksort( $types );
wfProfileOut( __METHOD__ );
+
return $types;
}
@@ -80,16 +86,6 @@ class ApiTokens extends ApiBase {
);
}
- public function getResultProperties() {
- $props = array(
- '' => array(),
- );
-
- self::addTokenProperties( $props, $this->getTokenTypes() );
-
- return $props;
- }
-
public function getParamDescription() {
return array(
'type' => 'Type of token(s) to request'
@@ -97,7 +93,10 @@ class ApiTokens extends ApiBase {
}
public function getDescription() {
- return 'Gets tokens for data-modifying actions';
+ return array(
+ 'This module is deprecated in favor of action=query&meta=tokens.',
+ 'Gets tokens for data-modifying actions.'
+ );
}
protected function getExamples() {
diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php
index 6a739a2f..2854a825 100644
--- a/includes/api/ApiUnblock.php
+++ b/includes/api/ApiUnblock.php
@@ -89,64 +89,28 @@ class ApiUnblock extends ApiBase {
ApiBase::PARAM_TYPE => 'integer',
),
'user' => null,
- 'token' => null,
'reason' => '',
);
}
public function getParamDescription() {
$p = $this->getModulePrefix();
- return array(
- 'id' => "ID of the block you want to unblock (obtained through list=blocks). Cannot be used together with {$p}user",
- 'user' => "Username, IP address or IP range you want to unblock. Cannot be used together with {$p}id",
- 'token' => "An unblock token previously obtained through prop=info",
- 'reason' => 'Reason for unblock',
- );
- }
- public function getResultProperties() {
return array(
- '' => array(
- 'id' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'user' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'userid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'reason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
+ 'id' => "ID of the block you want to unblock (obtained through list=blocks). " .
+ "Cannot be used together with {$p}user",
+ 'user' => "Username, IP address or IP range you want to unblock. " .
+ "Cannot be used together with {$p}id",
+ 'reason' => 'Reason for unblock',
);
}
public function getDescription() {
- return 'Unblock a user';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'unblock-notarget' ),
- array( 'unblock-idanduser' ),
- array( 'cantunblock' ),
- array( 'ipbblocked' ),
- array( 'ipbnounblockself' ),
- ) );
+ return 'Unblock a user.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php
index 4bbe568d..07aad9f5 100644
--- a/includes/api/ApiUndelete.php
+++ b/includes/api/ApiUndelete.php
@@ -56,11 +56,11 @@ class ApiUndelete extends ApiBase {
$params['timestamps'][$i] = wfTimestamp( TS_MW, $ts );
}
- $pa = new PageArchive( $titleObj );
+ $pa = new PageArchive( $titleObj, $this->getConfig() );
$retval = $pa->undelete(
( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ),
$params['reason'],
- array(),
+ $params['fileids'],
false,
$this->getUser()
);
@@ -70,7 +70,7 @@ class ApiUndelete extends ApiBase {
if ( $retval[1] ) {
wfRunHooks( 'FileUndeleteComplete',
- array( $titleObj, array(), $this->getUser(), $params['reason'] ) );
+ array( $titleObj, $params['fileids'], $this->getUser(), $params['reason'] ) );
}
$this->setWatch( $params['watchlist'], $titleObj );
@@ -96,15 +96,15 @@ class ApiUndelete extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => '',
'timestamps' => array(
ApiBase::PARAM_TYPE => 'timestamp',
ApiBase::PARAM_ISMULTI => true,
),
+ 'fileids' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'watchlist' => array(
ApiBase::PARAM_DFLT => 'preferences',
ApiBase::PARAM_TYPE => array(
@@ -120,46 +120,30 @@ class ApiUndelete extends ApiBase {
public function getParamDescription() {
return array(
'title' => 'Title of the page you want to restore',
- 'token' => 'An undelete token previously retrieved through list=deletedrevs',
'reason' => 'Reason for restoring',
- 'timestamps' => 'Timestamps of the revisions to restore. If not set, all revisions will be restored.',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'title' => 'string',
- 'revisions' => 'integer',
- 'filerevisions' => 'integer',
- 'reason' => 'string'
- )
+ 'timestamps' => array(
+ 'Timestamps of the revisions to restore.',
+ 'If both timestamps and fileids are empty, all will be restored.',
+ ),
+ 'fileids' => array(
+ 'IDs of the file revisions to restore.',
+ 'If both timestamps and fileids are empty, all will be restored.',
+ ),
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
);
}
public function getDescription() {
return array(
- 'Restore certain revisions of a deleted page. A list of deleted revisions (including timestamps) can be',
- 'retrieved through list=deletedrevs'
+ 'Restore certain revisions of a deleted page. A list of deleted revisions ',
+ '(including timestamps) can be retrieved through list=deletedrevs, and a list',
+ 'of deleted file ids can be retrieved through list=filearchive.'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'permdenied-undelete' ),
- array( 'blockedtext' ),
- array( 'invalidtitle', 'title' ),
- array( 'cannotundelete' ),
- ) );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index 467eccf8..657181b7 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -28,17 +28,12 @@
* @ingroup API
*/
class ApiUpload extends ApiBase {
-
- /**
- * @var UploadBase
- */
+ /** @var UploadBase|UploadFromChunks */
protected $mUpload = null;
protected $mParams;
public function execute() {
- global $wgEnableAsyncUploads;
-
// Check whether upload is enabled
if ( !UploadBase::isEnabled() ) {
$this->dieUsageMsg( 'uploaddisabled' );
@@ -50,13 +45,14 @@ class ApiUpload extends ApiBase {
$this->mParams = $this->extractRequestParams();
$request = $this->getMain()->getRequest();
// Check if async mode is actually supported (jobs done in cli mode)
- $this->mParams['async'] = ( $this->mParams['async'] && $wgEnableAsyncUploads );
+ $this->mParams['async'] = ( $this->mParams['async'] && $this->getConfig()->get( 'EnableAsyncUploads' ) );
// Add the uploaded file to the params array
$this->mParams['file'] = $request->getFileName( 'file' );
$this->mParams['chunk'] = $request->getFileName( 'chunk' );
// Copy the session key to the file key, for backward compatibility.
if ( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) {
+ $this->logFeatureUsage( 'action=upload&sessionkey' );
$this->mParams['filekey'] = $this->mParams['sessionkey'];
}
@@ -95,7 +91,7 @@ class ApiUpload extends ApiBase {
} elseif ( $this->mParams['async'] && $this->mParams['filekey'] ) {
// defer verification to background process
} else {
- wfDebug( __METHOD__ . 'about to verify' );
+ wfDebug( __METHOD__ . " about to verify\n" );
$this->verifyUpload();
}
@@ -141,6 +137,7 @@ class ApiUpload extends ApiBase {
// Stash the file and get stash result
return $this->getStashResult( $warnings );
}
+
// This is the most common case -- a normal upload with no warnings
// performUpload will return a formatted properly for the API with status
return $this->performUpload( $warnings );
@@ -165,6 +162,7 @@ class ApiUpload extends ApiBase {
} catch ( MWException $e ) {
$this->dieUsage( $e->getMessage(), 'stashfailed' );
}
+
return $result;
}
@@ -185,6 +183,7 @@ class ApiUpload extends ApiBase {
} catch ( MWException $e ) {
$result['warnings']['stashfailed'] = $e->getMessage();
}
+
return $result;
}
@@ -212,11 +211,11 @@ class ApiUpload extends ApiBase {
}
} else {
$filekey = $this->mParams['filekey'];
- /** @var $status Status */
$status = $this->mUpload->addChunk(
$chunkPath, $chunkSize, $this->mParams['offset'] );
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
+
return array();
}
}
@@ -233,7 +232,7 @@ class ApiUpload extends ApiBase {
array( 'result' => 'Poll',
'stage' => 'queued', 'status' => Status::newGood() )
);
- $ok = JobQueueGroup::singleton()->push( new AssembleUploadChunksJob(
+ JobQueueGroup::singleton()->push( new AssembleUploadChunksJob(
Title::makeTitle( NS_FILE, $filekey ),
array(
'filename' => $this->mParams['filename'],
@@ -241,17 +240,13 @@ class ApiUpload extends ApiBase {
'session' => $this->getContext()->exportSession()
)
) );
- if ( $ok ) {
- $result['result'] = 'Poll';
- } else {
- UploadBase::setSessionStatus( $filekey, false );
- $this->dieUsage(
- "Failed to start AssembleUploadChunks.php", 'stashfailed' );
- }
+ $result['result'] = 'Poll';
+ $result['stage'] = 'queued';
} else {
$status = $this->mUpload->concatenateChunks();
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
+
return array();
}
@@ -265,6 +260,7 @@ class ApiUpload extends ApiBase {
}
$result['filekey'] = $filekey;
$result['offset'] = $this->mParams['offset'] + $chunkSize;
+
return $result;
}
@@ -272,7 +268,7 @@ class ApiUpload extends ApiBase {
* Stash the file and return the file key
* Also re-raises exceptions with slightly more informative message strings (useful for API)
* @throws MWException
- * @return String file key
+ * @return string File key
*/
private function performStash() {
try {
@@ -287,6 +283,7 @@ class ApiUpload extends ApiBase {
wfDebug( __METHOD__ . ' ' . $message . "\n" );
throw new MWException( $message );
}
+
return $fileKey;
}
@@ -341,6 +338,7 @@ class ApiUpload extends ApiBase {
}
unset( $progress['status'] ); // remove Status object
$this->getResult()->addValue( null, $this->getModuleName(), $progress );
+
return false;
}
@@ -357,6 +355,7 @@ class ApiUpload extends ApiBase {
$sessionData['sessionkey'] = $this->mParams['statuskey'];
}
$this->getResult()->addValue( null, $this->getModuleName(), $sessionData );
+
return false;
}
@@ -440,7 +439,7 @@ class ApiUpload extends ApiBase {
/**
* Checks that the user has permissions to perform this upload.
* Dies with usage message on inadequate permissions.
- * @param $user User The user to check.
+ * @param User $user The user to check.
*/
protected function checkPermissions( $user ) {
// Check whether the user has the appropriate permissions to upload anyway
@@ -449,9 +448,9 @@ class ApiUpload extends ApiBase {
if ( $permission !== true ) {
if ( !$user->isLoggedIn() ) {
$this->dieUsageMsg( array( 'mustbeloggedin', 'upload' ) );
- } else {
- $this->dieUsageMsg( 'badaccess-groups' );
}
+
+ $this->dieUsageMsg( 'badaccess-groups' );
}
}
@@ -469,10 +468,9 @@ class ApiUpload extends ApiBase {
/**
* Performs file verification, dies on error.
+ * @param array $verification
*/
protected function checkVerification( array $verification ) {
- global $wgFileExtensions;
-
// @todo Move them to ApiBase's message map
switch ( $verification['status'] ) {
// Recoverable errors
@@ -481,7 +479,7 @@ class ApiUpload extends ApiBase {
break;
case UploadBase::ILLEGAL_FILENAME:
$this->dieRecoverableError( 'illegal-filename', 'filename',
- array( 'filename' => $verification['filtered'] ) );
+ array( 'filename' => $verification['filtered'] ) );
break;
case UploadBase::FILENAME_TOO_LONG:
$this->dieRecoverableError( 'filename-toolong', 'filename' );
@@ -504,7 +502,7 @@ class ApiUpload extends ApiBase {
case UploadBase::FILETYPE_BADTYPE:
$extradata = array(
'filetype' => $verification['finalExt'],
- 'allowed' => array_values( array_unique( $wgFileExtensions ) )
+ 'allowed' => array_values( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) )
);
$this->getResult()->setIndexedTagName( $extradata['allowed'], 'ext' );
@@ -521,15 +519,15 @@ class ApiUpload extends ApiBase {
case UploadBase::VERIFICATION_ERROR:
$this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
$this->dieUsage( 'This file did not pass file verification', 'verification-error',
- 0, array( 'details' => $verification['details'] ) );
+ 0, array( 'details' => $verification['details'] ) );
break;
case UploadBase::HOOK_ABORTED:
$this->dieUsage( "The modification you tried to make was aborted by an extension hook",
- 'hookaborted', 0, array( 'error' => $verification['error'] ) );
+ 'hookaborted', 0, array( 'error' => $verification['error'] ) );
break;
default:
$this->dieUsage( 'An unknown error occurred', 'unknown-error',
- 0, array( 'code' => $verification['status'] ) );
+ 0, array( 'code' => $verification['status'] ) );
break;
}
}
@@ -555,6 +553,7 @@ class ApiUpload extends ApiBase {
if ( isset( $warnings['duplicate'] ) ) {
$dupes = array();
+ /** @var File $dupe */
foreach ( $warnings['duplicate'] as $dupe ) {
$dupes[] = $dupe->getName();
}
@@ -565,10 +564,14 @@ class ApiUpload extends ApiBase {
if ( isset( $warnings['exists'] ) ) {
$warning = $warnings['exists'];
unset( $warnings['exists'] );
- $localFile = isset( $warning['normalizedFile'] ) ? $warning['normalizedFile'] : $warning['file'];
+ /** @var LocalFile $localFile */
+ $localFile = isset( $warning['normalizedFile'] )
+ ? $warning['normalizedFile']
+ : $warning['file'];
$warnings[$warning['warning']] = $localFile->getName();
}
}
+
return $warnings;
}
@@ -587,10 +590,23 @@ class ApiUpload extends ApiBase {
/** @var $file File */
$file = $this->mUpload->getLocalFile();
- $watch = $this->getWatchlistValue( $this->mParams['watchlist'], $file->getTitle() );
+
+ // For preferences mode, we want to watch if 'watchdefault' is set or
+ // if the *file* doesn't exist and 'watchcreations' is set. But
+ // getWatchlistValue()'s automatic handling checks if the *title*
+ // exists or not, so we need to check both prefs manually.
+ $watch = $this->getWatchlistValue(
+ $this->mParams['watchlist'], $file->getTitle(), 'watchdefault'
+ );
+ if ( !$watch && $this->mParams['watchlist'] == 'preferences' && !$file->exists() ) {
+ $watch = $this->getWatchlistValue(
+ $this->mParams['watchlist'], $file->getTitle(), 'watchcreations'
+ );
+ }
// Deprecated parameters
if ( $this->mParams['watch'] ) {
+ $this->logFeatureUsage( 'action=upload&watch' );
$watch = true;
}
@@ -604,7 +620,7 @@ class ApiUpload extends ApiBase {
$this->mParams['filekey'],
array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() )
);
- $ok = JobQueueGroup::singleton()->push( new PublishStashedFileJob(
+ JobQueueGroup::singleton()->push( new PublishStashedFileJob(
Title::makeTitle( NS_FILE, $this->mParams['filename'] ),
array(
'filename' => $this->mParams['filename'],
@@ -615,13 +631,8 @@ class ApiUpload extends ApiBase {
'session' => $this->getContext()->exportSession()
)
) );
- if ( $ok ) {
- $result['result'] = 'Poll';
- } else {
- UploadBase::setSessionStatus( $this->mParams['filekey'], false );
- $this->dieUsage(
- "Failed to start PublishStashedFile.php", 'publishfailed' );
- }
+ $result['result'] = 'Poll';
+ $result['stage'] = 'queued';
} else {
/** @var $status Status */
$status = $this->mUpload->performUpload( $this->mParams['comment'],
@@ -637,11 +648,10 @@ class ApiUpload extends ApiBase {
'result' => 'Queued',
'statuskey' => $error[0][1],
);
- } else {
- $this->getResult()->setIndexedTagName( $error, 'error' );
-
- $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
}
+
+ $this->getResult()->setIndexedTagName( $error, 'error' );
+ $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
}
$result['result'] = 'Success';
}
@@ -658,8 +668,7 @@ class ApiUpload extends ApiBase {
* Checks if asynchronous copy uploads are enabled and throws an error if they are not.
*/
protected function checkAsyncDownloadEnabled() {
- global $wgAllowAsyncCopyUploads;
- if ( !$wgAllowAsyncCopyUploads ) {
+ if ( !$this->getConfig()->get( 'AllowAsyncCopyUploads' ) ) {
$this->dieUsage( 'Asynchronous copy uploads disabled', 'asynccopyuploaddisabled' );
}
}
@@ -681,10 +690,6 @@ class ApiUpload extends ApiBase {
ApiBase::PARAM_DFLT => ''
),
'text' => null,
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'watch' => array(
ApiBase::PARAM_DFLT => false,
ApiBase::PARAM_DEPRECATED => true,
@@ -728,17 +733,19 @@ class ApiUpload extends ApiBase {
public function getParamDescription() {
$params = array(
'filename' => 'Target filename',
- 'token' => 'Edit token. You can get one of these through prop=info',
- 'comment' => 'Upload comment. Also used as the initial page text for new files if "text" is not specified',
+ 'comment' => 'Upload comment. Also used as the initial page text for new ' .
+ 'files if "text" is not specified',
'text' => 'Initial page text for new files',
'watch' => 'Watch the page',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
+ 'watchlist' => 'Unconditionally add or remove the page from your watchlist, ' .
+ 'use preferences or do not change watch',
'ignorewarnings' => 'Ignore any warnings',
'file' => 'File contents',
'url' => 'URL to fetch the file from',
'filekey' => 'Key that identifies a previous upload that was stashed temporarily.',
'sessionkey' => 'Same as filekey, maintained for backward compatibility.',
- 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.',
+ 'stash' => 'If set, the server will not add the file to the repository ' .
+ 'and stash it temporarily.',
'chunk' => 'Chunk contents',
'offset' => 'Offset of chunk in bytes',
@@ -752,42 +759,6 @@ class ApiUpload extends ApiBase {
);
return $params;
-
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'Warning',
- 'Continue',
- 'Queued'
- ),
- ),
- 'filekey' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'sessionkey' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'offset' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'statuskey' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'filename' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
}
public function getDescription() {
@@ -797,49 +768,20 @@ class ApiUpload extends ApiBase {
' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter',
' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter',
'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
- 'sending the "file". Also you must get and send an edit token before doing any upload stuff'
- );
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'filekey', 'file', 'url', 'statuskey' ) ),
- array(
- array( 'uploaddisabled' ),
- array( 'invalid-file-key' ),
- array( 'uploaddisabled' ),
- array( 'mustbeloggedin', 'upload' ),
- array( 'badaccess-groups' ),
- array( 'code' => 'fetchfileerror', 'info' => '' ),
- array( 'code' => 'nomodule', 'info' => 'No upload module set' ),
- array( 'code' => 'empty-file', 'info' => 'The file you submitted was empty' ),
- array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
- array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
- array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ),
- array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ),
- array( 'code' => 'publishfailed', 'info' => 'Publishing of stashed file failed' ),
- array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ),
- array( 'code' => 'asynccopyuploaddisabled', 'info' => 'Asynchronous copy uploads disabled' ),
- array( 'code' => 'stasherror', 'info' => 'An upload stash error occurred' ),
- array( 'fileexists-forbidden' ),
- array( 'fileexists-shared-forbidden' ),
- )
+ 'sending the "file".',
);
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=upload&filename=Wiki.png&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png'
+ 'api.php?action=upload&filename=Wiki.png' .
+ '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
=> 'Upload from a URL',
- 'api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1'
+ 'api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC'
=> 'Complete an upload that failed due to warnings',
);
}
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
index 7d308285..c3ceb345 100644
--- a/includes/api/ApiUserrights.php
+++ b/includes/api/ApiUserrights.php
@@ -35,16 +35,16 @@ class ApiUserrights extends ApiBase {
public function execute() {
$params = $this->extractRequestParams();
- $user = $this->getUrUser();
+ $user = $this->getUrUser( $params );
$form = new UserrightsPage;
$form->setContext( $this->getContext() );
$r['user'] = $user->getName();
$r['userid'] = $user->getId();
- list( $r['added'], $r['removed'] ) =
- $form->doSaveUserGroups(
- $user, (array)$params['add'],
- (array)$params['remove'], $params['reason'] );
+ list( $r['added'], $r['removed'] ) = $form->doSaveUserGroups(
+ $user, (array)$params['add'],
+ (array)$params['remove'], $params['reason']
+ );
$result = $this->getResult();
$result->setIndexedTagName( $r['added'], 'group' );
@@ -53,26 +53,28 @@ class ApiUserrights extends ApiBase {
}
/**
+ * @param array $params
* @return User
*/
- private function getUrUser() {
+ private function getUrUser( array $params ) {
if ( $this->mUser !== null ) {
return $this->mUser;
}
- $params = $this->extractRequestParams();
+ $this->requireOnlyOneParameter( $params, 'user', 'userid' );
+
+ $user = isset( $params['user'] ) ? $params['user'] : '#' . $params['userid'];
$form = new UserrightsPage;
$form->setContext( $this->getContext() );
- $status = $form->fetchUser( $params['user'] );
+ $status = $form->fetchUser( $user );
if ( !$status->isOK() ) {
$this->dieStatus( $status );
- } else {
- $user = $status->value;
}
- $this->mUser = $user;
- return $user;
+ $this->mUser = $status->value;
+
+ return $status->value;
}
public function mustBePosted() {
@@ -87,7 +89,9 @@ class ApiUserrights extends ApiBase {
return array(
'user' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
+ ),
+ 'userid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
),
'add' => array(
ApiBase::PARAM_TYPE => User::getAllGroups(),
@@ -97,10 +101,6 @@ class ApiUserrights extends ApiBase {
ApiBase::PARAM_TYPE => User::getAllGroups(),
ApiBase::PARAM_ISMULTI => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => array(
ApiBase::PARAM_DFLT => ''
)
@@ -110,28 +110,33 @@ class ApiUserrights extends ApiBase {
public function getParamDescription() {
return array(
'user' => 'User name',
+ 'userid' => 'User id',
'add' => 'Add the user to these groups',
'remove' => 'Remove the user from these groups',
- 'token' => 'A userrights token previously retrieved through list=users',
+ 'token' => array(
+ /* Standard description automatically prepended */
+ 'For compatibility, the token used in the web UI is also accepted.'
+ ),
'reason' => 'Reason for the change',
);
}
public function getDescription() {
- return 'Add/remove a user to/from groups';
+ return 'Add/remove a user to/from groups.';
}
public function needsToken() {
- return true;
+ return 'userrights';
}
- public function getTokenSalt() {
- return $this->getUrUser()->getName();
+ protected function getWebUITokenSalt( array $params ) {
+ return $this->getUrUser( $params )->getName();
}
public function getExamples() {
return array(
- 'api.php?action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
+ 'api.php?action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC',
+ 'api.php?action=userrights&userid=123&add=bot&remove=sysop|bureaucrat&token=123ABC'
);
}
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index c7d636a1..e6a660b3 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -30,41 +30,105 @@
* @ingroup API
*/
class ApiWatch extends ApiBase {
+ private $mPageSet = null;
public function execute() {
$user = $this->getUser();
if ( !$user->isLoggedIn() ) {
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
}
+
if ( !$user->isAllowed( 'editmywatchlist' ) ) {
$this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' );
}
$params = $this->extractRequestParams();
- $title = Title::newFromText( $params['title'] );
- if ( !$title || $title->isExternal() || !$title->canExist() ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+
+ $pageSet = $this->getPageSet();
+ // by default we use pageset to extract the page to work on.
+ // title is still supported for backward compatibility
+ if ( !isset( $params['title'] ) ) {
+ $pageSet->execute();
+ $res = $pageSet->getInvalidTitlesAndRevisions( array(
+ 'invalidTitles',
+ 'special',
+ 'missingIds',
+ 'missingRevIds',
+ 'interwikiTitles'
+ ) );
+
+ foreach ( $pageSet->getMissingTitles() as $title ) {
+ $r = $this->watchTitle( $title, $user, $params );
+ $r['missing'] = 1;
+ $res[] = $r;
+ }
+
+ foreach ( $pageSet->getGoodTitles() as $title ) {
+ $r = $this->watchTitle( $title, $user, $params );
+ $res[] = $r;
+ }
+ $this->getResult()->setIndexedTagName( $res, 'w' );
+ } else {
+ // dont allow use of old title parameter with new pageset parameters.
+ $extraParams = array_keys( array_filter( $pageSet->extractRequestParams(), function ( $x ) {
+ return $x !== null && $x !== false;
+ } ) );
+
+ if ( $extraParams ) {
+ $p = $this->getModulePrefix();
+ $this->dieUsage(
+ "The parameter {$p}title can not be used with " . implode( ", ", $extraParams ),
+ 'invalidparammix'
+ );
+ }
+
+ $this->logFeatureUsage( 'action=watch&title' );
+ $title = Title::newFromText( $params['title'] );
+ if ( !$title || !$title->isWatchable() ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
+ $res = $this->watchTitle( $title, $user, $params, true );
+ }
+ $this->getResult()->addValue( null, $this->getModuleName(), $res );
+ $this->getResult()->endContinuation();
+ }
+
+ private function watchTitle( Title $title, User $user, array $params,
+ $compatibilityMode = false
+ ) {
+ if ( !$title->isWatchable() ) {
+ return array( 'title' => $title->getPrefixedText(), 'watchable' => 0 );
}
$res = array( 'title' => $title->getPrefixedText() );
- // Currently unnecessary, code to act as a safeguard against any change in current behavior of uselang
+ // Currently unnecessary, code to act as a safeguard against any change
+ // in current behavior of uselang.
// Copy from ApiParse
$oldLang = null;
- if ( isset( $params['uselang'] ) && $params['uselang'] != $this->getContext()->getLanguage()->getCode() ) {
+ if ( isset( $params['uselang'] ) &&
+ $params['uselang'] != $this->getContext()->getLanguage()->getCode()
+ ) {
$oldLang = $this->getContext()->getLanguage(); // Backup language
$this->getContext()->setLanguage( Language::factory( $params['uselang'] ) );
}
if ( $params['unwatch'] ) {
- $res['unwatched'] = '';
- $res['message'] = $this->msg( 'removedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
$status = UnwatchAction::doUnwatch( $title, $user );
+ if ( $status->isOK() ) {
+ $res['unwatched'] = '';
+ $res['message'] = $this->msg( 'removedwatchtext', $title->getPrefixedText() )
+ ->title( $title )->parseAsBlock();
+ }
} else {
- $res['watched'] = '';
- $res['message'] = $this->msg( 'addedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
$status = WatchAction::doWatch( $title, $user );
+ if ( $status->isOK() ) {
+ $res['watched'] = '';
+ $res['message'] = $this->msg( 'addedwatchtext', $title->getPrefixedText() )
+ ->title( $title )->parseAsBlock();
+ }
}
if ( !is_null( $oldLang ) ) {
@@ -72,9 +136,25 @@ class ApiWatch extends ApiBase {
}
if ( !$status->isOK() ) {
- $this->dieStatus( $status );
+ if ( $compatibilityMode ) {
+ $this->dieStatus( $status );
+ }
+ $res['error'] = $this->getErrorFromStatus( $status );
}
- $this->getResult()->addValue( null, $this->getModuleName(), $res );
+
+ return $res;
+ }
+
+ /**
+ * Get a cached instance of an ApiPageSet object
+ * @return ApiPageSet
+ */
+ private function getPageSet() {
+ if ( $this->mPageSet === null ) {
+ $this->mPageSet = new ApiPageSet( $this );
+ }
+
+ return $this->mPageSet;
}
public function mustBePosted() {
@@ -86,64 +166,45 @@ class ApiWatch extends ApiBase {
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
return 'watch';
}
- public function getAllowedParams() {
- return array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array(
'title' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
+ ApiBase::PARAM_DEPRECATED => true
),
'unwatch' => false,
'uselang' => null,
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
+ 'continue' => '',
);
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+
+ return $result;
}
public function getParamDescription() {
- return array(
- 'title' => 'The page to (un)watch',
+ $psModule = $this->getPageSet();
+
+ return $psModule->getParamDescription() + array(
+ 'title' => 'The page to (un)watch. use titles instead',
'unwatch' => 'If set the page will be unwatched rather than watched',
'uselang' => 'Language to show the message in',
- 'token' => 'A token previously acquired via prop=info',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'title' => 'string',
- 'unwatched' => 'boolean',
- 'watched' => 'boolean',
- 'message' => 'string'
- )
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return 'Add or remove a page from/to the current user\'s watchlist';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to have a watchlist' ),
- array( 'invalidtitle', 'title' ),
- array( 'hookaborted' ),
- ) );
+ return 'Add or remove pages from/to the current user\'s watchlist.';
}
public function getExamples() {
return array(
- 'api.php?action=watch&title=Main_Page' => 'Watch the page "Main Page"',
- 'api.php?action=watch&title=Main_Page&unwatch=' => 'Unwatch the page "Main Page"',
+ 'api.php?action=watch&titles=Main_Page' => 'Watch the page "Main Page"',
+ 'api.php?action=watch&titles=Main_Page&unwatch=' => 'Unwatch the page "Main Page"',
);
}
diff --git a/includes/cache/BacklinkCache.php b/includes/cache/BacklinkCache.php
index 193f20fe..ed62bba0 100644
--- a/includes/cache/BacklinkCache.php
+++ b/includes/cache/BacklinkCache.php
@@ -48,6 +48,7 @@ class BacklinkCache {
/**
* Multi dimensions array representing batches. Keys are:
* > (string) links table name
+ * > (int) batch size
* > 'numRows' : Number of rows for this link table
* > 'batches' : array( $start, $end )
*
@@ -96,7 +97,7 @@ class BacklinkCache {
* Currently, only one cache instance can exist; callers that
* need multiple backlink cache objects should keep them in scope.
*
- * @param Title $title : Title object to get a backlink cache for
+ * @param Title $title Title object to get a backlink cache for
* @return BacklinkCache
*/
public static function get( Title $title ) {
@@ -107,6 +108,7 @@ class BacklinkCache {
if ( !self::$cache->has( $dbKey, 'obj', 3600 ) ) {
self::$cache->set( $dbKey, 'obj', new self( $title ) );
}
+
return self::$cache->get( $dbKey, 'obj' );
}
@@ -133,7 +135,7 @@ class BacklinkCache {
/**
* Set the Database object to use
*
- * @param $db DatabaseBase
+ * @param DatabaseBase $db
*/
public function setDB( $db ) {
$this->db = $db;
@@ -142,21 +144,22 @@ class BacklinkCache {
/**
* Get the slave connection to the database
* When non existing, will initialize the connection.
- * @return DatabaseBase object
+ * @return DatabaseBase
*/
protected function getDB() {
if ( !isset( $this->db ) ) {
$this->db = wfGetDB( DB_SLAVE );
}
+
return $this->db;
}
/**
* Get the backlinks for a given table. Cached in process memory only.
- * @param $table String
- * @param $startId Integer|false
- * @param $endId Integer|false
- * @param $max Integer|INF
+ * @param string $table
+ * @param int|bool $startId
+ * @param int|bool $endId
+ * @param int|INF $max
* @return TitleArrayFromResult
*/
public function getLinks( $table, $startId = false, $endId = false, $max = INF ) {
@@ -165,20 +168,21 @@ class BacklinkCache {
/**
* Get the backlinks for a given table. Cached in process memory only.
- * @param $table String
- * @param $startId Integer|false
- * @param $endId Integer|false
- * @param $max Integer|INF
+ * @param string $table
+ * @param int|bool $startId
+ * @param int|bool $endId
+ * @param int|INF $max
+ * @param string $select 'all' or 'ids'
* @return ResultWrapper
*/
- protected function queryLinks( $table, $startId, $endId, $max ) {
+ protected function queryLinks( $table, $startId, $endId, $max, $select = 'all' ) {
wfProfileIn( __METHOD__ );
$fromField = $this->getPrefix( $table ) . '_from';
if ( !$startId && !$endId && is_infinite( $max )
- && isset( $this->fullResultCache[$table] ) )
- {
+ && isset( $this->fullResultCache[$table] )
+ ) {
wfDebug( __METHOD__ . ": got results from cache\n" );
$res = $this->fullResultCache[$table];
} else {
@@ -192,20 +196,34 @@ class BacklinkCache {
if ( $endId ) {
$conds[] = "$fromField <= " . intval( $endId );
}
- $options = array( 'STRAIGHT_JOIN', 'ORDER BY' => $fromField );
+ $options = array( 'ORDER BY' => $fromField );
if ( is_finite( $max ) && $max > 0 ) {
$options['LIMIT'] = $max;
}
- $res = $this->getDB()->select(
- array( $table, 'page' ),
- array( 'page_namespace', 'page_title', 'page_id' ),
- $conds,
- __METHOD__,
- $options
- );
+ if ( $select === 'ids' ) {
+ // Just select from the backlink table and ignore the page JOIN
+ $res = $this->getDB()->select(
+ $table,
+ array( $this->getPrefix( $table ) . '_from AS page_id' ),
+ array_filter( $conds, function ( $clause ) { // kind of janky
+ return !preg_match( '/(\b|=)page_id(\b|=)/', $clause );
+ } ),
+ __METHOD__,
+ $options
+ );
+ } else {
+ // Select from the backlink table and JOIN with page title information
+ $res = $this->getDB()->select(
+ array( $table, 'page' ),
+ array( 'page_namespace', 'page_title', 'page_id' ),
+ $conds,
+ __METHOD__,
+ array_merge( array( 'STRAIGHT_JOIN' ), $options )
+ );
+ }
- if ( !$startId && !$endId && $res->numRows() < $max ) {
+ if ( $select === 'all' && !$startId && !$endId && $res->numRows() < $max ) {
// The full results fit within the limit, so cache them
$this->fullResultCache[$table] = $res;
} else {
@@ -214,12 +232,13 @@ class BacklinkCache {
}
wfProfileOut( __METHOD__ );
+
return $res;
}
/**
* Get the field name prefix for a given table
- * @param $table String
+ * @param string $table
* @throws MWException
* @return null|string
*/
@@ -248,15 +267,13 @@ class BacklinkCache {
/**
* Get the SQL condition array for selecting backlinks, with a join
* on the page table.
- * @param $table String
+ * @param string $table
* @throws MWException
* @return array|null
*/
protected function getConditions( $table ) {
$prefix = $this->getPrefix( $table );
- // @todo FIXME: imagelinks and categorylinks do not rely on getNamespace,
- // they could be moved up for nicer case statements
switch ( $table ) {
case 'pagelinks':
case 'templatelinks':
@@ -278,15 +295,10 @@ class BacklinkCache {
);
break;
case 'imagelinks':
- $conds = array(
- 'il_to' => $this->title->getDBkey(),
- 'page_id=il_from'
- );
- break;
case 'categorylinks':
$conds = array(
- 'cl_to' => $this->title->getDBkey(),
- 'page_id=cl_from',
+ "{$prefix}_to" => $this->title->getDBkey(),
+ "page_id={$prefix}_from"
);
break;
default:
@@ -302,7 +314,7 @@ class BacklinkCache {
/**
* Check if there are any backlinks
- * @param $table String
+ * @param string $table
* @return bool
*/
public function hasLinks( $table ) {
@@ -311,16 +323,17 @@ class BacklinkCache {
/**
* Get the approximate number of backlinks
- * @param $table String
- * @param $max integer|INF Only count up to this many backlinks
- * @return integer
+ * @param string $table
+ * @param int|INF $max Only count up to this many backlinks
+ * @return int
*/
public function getNumLinks( $table, $max = INF ) {
- global $wgMemc;
+ global $wgMemc, $wgUpdateRowsPerJob;
// 1) try partition cache ...
if ( isset( $this->partitionCache[$table] ) ) {
$entry = reset( $this->partitionCache[$table] );
+
return min( $max, $entry['numRows'] );
}
@@ -338,9 +351,17 @@ class BacklinkCache {
}
// 4) fetch from the database ...
- $count = $this->getLinks( $table, false, false, $max )->count();
- if ( $count < $max ) { // full count
- $wgMemc->set( $memcKey, $count, self::CACHE_EXPIRY );
+ if ( is_infinite( $max ) ) { // no limit at all
+ // Use partition() since it will batch the query and skip the JOIN.
+ // Use $wgUpdateRowsPerJob just to encourage cache reuse for jobs.
+ $this->partition( $table, $wgUpdateRowsPerJob ); // updates $this->partitionCache
+ return $this->partitionCache[$table][$wgUpdateRowsPerJob]['numRows'];
+ } else { // probably some sane limit
+ // Fetch the full title info, since the caller will likely need it next
+ $count = $this->getLinks( $table, false, false, $max )->count();
+ if ( $count < $max ) { // full count
+ $wgMemc->set( $memcKey, $count, self::CACHE_EXPIRY );
+ }
}
return min( $max, $count );
@@ -351,9 +372,9 @@ class BacklinkCache {
* Returns an array giving the start and end of each range. The first
* batch has a start of false, and the last batch has an end of false.
*
- * @param string $table the links table name
- * @param $batchSize Integer
- * @return Array
+ * @param string $table The links table name
+ * @param int $batchSize
+ * @return array
*/
public function partition( $table, $batchSize ) {
global $wgMemc;
@@ -361,6 +382,7 @@ class BacklinkCache {
// 1) try partition cache ...
if ( isset( $this->partitionCache[$table][$batchSize] ) ) {
wfDebug( __METHOD__ . ": got from partition cache\n" );
+
return $this->partitionCache[$table][$batchSize]['batches'];
}
@@ -371,6 +393,7 @@ class BacklinkCache {
if ( isset( $this->fullResultCache[$table] ) ) {
$cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
wfDebug( __METHOD__ . ": got from full result cache\n" );
+
return $cacheEntry['batches'];
}
@@ -386,6 +409,7 @@ class BacklinkCache {
if ( is_array( $memcValue ) ) {
$cacheEntry = $memcValue;
wfDebug( __METHOD__ . ": got from memcached $memcKey\n" );
+
return $cacheEntry['batches'];
}
@@ -396,13 +420,13 @@ class BacklinkCache {
$selectSize = max( $batchSize, 200000 - ( 200000 % $batchSize ) );
$start = false;
do {
- $res = $this->queryLinks( $table, $start, false, $selectSize );
+ $res = $this->queryLinks( $table, $start, false, $selectSize, 'ids' );
$partitions = $this->partitionResult( $res, $batchSize, false );
// Merge the link count and range partitions for this chunk
$cacheEntry['numRows'] += $partitions['numRows'];
$cacheEntry['batches'] = array_merge( $cacheEntry['batches'], $partitions['batches'] );
if ( count( $partitions['batches'] ) ) {
- list( $lStart, $lEnd ) = end( $partitions['batches'] );
+ list( , $lEnd ) = end( $partitions['batches'] );
$start = $lEnd + 1; // pick up after this inclusive range
}
} while ( $partitions['numRows'] >= $selectSize );
@@ -420,14 +444,15 @@ class BacklinkCache {
$wgMemc->set( $memcKey, $cacheEntry['numRows'], self::CACHE_EXPIRY );
wfDebug( __METHOD__ . ": got from database\n" );
+
return $cacheEntry['batches'];
}
/**
* Partition a DB result with backlinks in it into batches
- * @param $res ResultWrapper database result
- * @param $batchSize integer
- * @param $isComplete bool Whether $res includes all the backlinks
+ * @param ResultWrapper $res Database result
+ * @param int $batchSize
+ * @param bool $isComplete Whether $res includes all the backlinks
* @throws MWException
* @return array
*/
diff --git a/includes/cache/CacheDependency.php b/includes/cache/CacheDependency.php
index 32bcdf7f..9b48ecb7 100644
--- a/includes/cache/CacheDependency.php
+++ b/includes/cache/CacheDependency.php
@@ -28,14 +28,15 @@
* @ingroup Cache
*/
class DependencyWrapper {
- var $value;
- var $deps;
+ private $value;
+ /** @var CacheDependency[] */
+ private $deps;
/**
* Create an instance.
- * @param $value Mixed: the user-supplied value
- * @param $deps Mixed: a dependency or dependency array. All dependencies
- * must be objects implementing CacheDependency.
+ * @param mixed $value The user-supplied value
+ * @param CacheDependency|CacheDependency[] $deps A dependency or dependency
+ * array. All dependencies must be objects implementing CacheDependency.
*/
function __construct( $value = false, $deps = array() ) {
$this->value = $value;
@@ -74,7 +75,7 @@ class DependencyWrapper {
/**
* Get the user-defined value
- * @return bool|Mixed
+ * @return bool|mixed
*/
function getValue() {
return $this->value;
@@ -83,9 +84,9 @@ class DependencyWrapper {
/**
* Store the wrapper to a cache
*
- * @param $cache BagOStuff
- * @param $key
- * @param $expiry
+ * @param BagOStuff $cache
+ * @param string $key
+ * @param int $expiry
*/
function storeToCache( $cache, $key, $expiry = 0 ) {
$this->initialiseDeps();
@@ -97,12 +98,12 @@ class DependencyWrapper {
* it will be generated with the callback function (if present), and the newly
* calculated value will be stored to the cache in a wrapper.
*
- * @param $cache BagOStuff a cache object such as $wgMemc
- * @param string $key the cache key
- * @param $expiry Integer: the expiry timestamp or interval in seconds
- * @param $callback Mixed: the callback for generating the value, or false
- * @param array $callbackParams the function parameters for the callback
- * @param array $deps the dependencies to store on a cache miss. Note: these
+ * @param BagOStuff $cache A cache object such as $wgMemc
+ * @param string $key The cache key
+ * @param int $expiry The expiry timestamp or interval in seconds
+ * @param bool|callable $callback The callback for generating the value, or false
+ * @param array $callbackParams The function parameters for the callback
+ * @param array $deps The dependencies to store on a cache miss. Note: these
* are not the dependencies used on a cache hit! Cache hits use the stored
* dependency array.
*
@@ -110,8 +111,8 @@ class DependencyWrapper {
* callback was defined.
*/
static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false,
- $callbackParams = array(), $deps = array() )
- {
+ $callbackParams = array(), $deps = array()
+ ) {
$obj = $cache->get( $key );
if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) {
@@ -141,20 +142,22 @@ abstract class CacheDependency {
/**
* Hook to perform any expensive pre-serialize loading of dependency values.
*/
- function loadDependencyValues() { }
+ function loadDependencyValues() {
+ }
}
/**
* @ingroup Cache
*/
class FileDependency extends CacheDependency {
- var $filename, $timestamp;
+ private $filename;
+ private $timestamp;
/**
* Create a file dependency
*
- * @param string $filename the name of the file, preferably fully qualified
- * @param $timestamp Mixed: the unix last modified timestamp, or false if the
+ * @param string $filename The name of the file, preferably fully qualified
+ * @param null|bool|int $timestamp The unix last modified timestamp, or false if the
* file does not exist. If omitted, the timestamp will be loaded from
* the file.
*
@@ -172,6 +175,7 @@ class FileDependency extends CacheDependency {
*/
function __sleep() {
$this->loadDependencyValues();
+
return array( 'filename', 'timestamp' );
}
@@ -198,6 +202,7 @@ class FileDependency extends CacheDependency {
} else {
# Deleted
wfDebug( "Dependency triggered: {$this->filename} deleted.\n" );
+
return true;
}
} else {
@@ -205,6 +210,7 @@ class FileDependency extends CacheDependency {
if ( $lastmod > $this->timestamp ) {
# Modified or created
wfDebug( "Dependency triggered: {$this->filename} changed.\n" );
+
return true;
} else {
# Not modified
@@ -217,183 +223,9 @@ class FileDependency extends CacheDependency {
/**
* @ingroup Cache
*/
-class TitleDependency extends CacheDependency {
- var $titleObj;
- var $ns, $dbk;
- var $touched;
-
- /**
- * Construct a title dependency
- * @param $title Title
- */
- function __construct( Title $title ) {
- $this->titleObj = $title;
- $this->ns = $title->getNamespace();
- $this->dbk = $title->getDBkey();
- }
-
- function loadDependencyValues() {
- $this->touched = $this->getTitle()->getTouched();
- }
-
- /**
- * Get rid of bulky Title object for sleep
- *
- * @return array
- */
- function __sleep() {
- return array( 'ns', 'dbk', 'touched' );
- }
-
- /**
- * @return Title
- */
- function getTitle() {
- if ( !isset( $this->titleObj ) ) {
- $this->titleObj = Title::makeTitle( $this->ns, $this->dbk );
- }
-
- return $this->titleObj;
- }
-
- /**
- * @return bool
- */
- function isExpired() {
- $touched = $this->getTitle()->getTouched();
-
- if ( $this->touched === false ) {
- if ( $touched === false ) {
- # Still missing
- return false;
- } else {
- # Created
- return true;
- }
- } elseif ( $touched === false ) {
- # Deleted
- return true;
- } elseif ( $touched > $this->touched ) {
- # Updated
- return true;
- } else {
- # Unmodified
- return false;
- }
- }
-}
-
-/**
- * @ingroup Cache
- */
-class TitleListDependency extends CacheDependency {
- var $linkBatch;
- var $timestamps;
-
- /**
- * Construct a dependency on a list of titles
- * @param $linkBatch LinkBatch
- */
- function __construct( LinkBatch $linkBatch ) {
- $this->linkBatch = $linkBatch;
- }
-
- /**
- * @return array
- */
- function calculateTimestamps() {
- # Initialise values to false
- $timestamps = array();
-
- foreach ( $this->getLinkBatch()->data as $ns => $dbks ) {
- if ( count( $dbks ) > 0 ) {
- $timestamps[$ns] = array();
-
- foreach ( $dbks as $dbk => $value ) {
- $timestamps[$ns][$dbk] = false;
- }
- }
- }
-
- # Do the query
- if ( count( $timestamps ) ) {
- $dbr = wfGetDB( DB_SLAVE );
- $where = $this->getLinkBatch()->constructSet( 'page', $dbr );
- $res = $dbr->select(
- 'page',
- array( 'page_namespace', 'page_title', 'page_touched' ),
- $where,
- __METHOD__
- );
-
- foreach ( $res as $row ) {
- $timestamps[$row->page_namespace][$row->page_title] = $row->page_touched;
- }
- }
-
- return $timestamps;
- }
-
- function loadDependencyValues() {
- $this->timestamps = $this->calculateTimestamps();
- }
-
- /**
- * @return array
- */
- function __sleep() {
- return array( 'timestamps' );
- }
-
- /**
- * @return LinkBatch
- */
- function getLinkBatch() {
- if ( !isset( $this->linkBatch ) ) {
- $this->linkBatch = new LinkBatch;
- $this->linkBatch->setArray( $this->timestamps );
- }
- return $this->linkBatch;
- }
-
- /**
- * @return bool
- */
- function isExpired() {
- $newTimestamps = $this->calculateTimestamps();
-
- foreach ( $this->timestamps as $ns => $dbks ) {
- foreach ( $dbks as $dbk => $oldTimestamp ) {
- $newTimestamp = $newTimestamps[$ns][$dbk];
-
- if ( $oldTimestamp === false ) {
- if ( $newTimestamp === false ) {
- # Still missing
- } else {
- # Created
- return true;
- }
- } elseif ( $newTimestamp === false ) {
- # Deleted
- return true;
- } elseif ( $newTimestamp > $oldTimestamp ) {
- # Updated
- return true;
- } else {
- # Unmodified
- }
- }
- }
-
- return false;
- }
-}
-
-/**
- * @ingroup Cache
- */
class GlobalDependency extends CacheDependency {
- var $name, $value;
+ private $name;
+ private $value;
function __construct( $name ) {
$this->name = $name;
@@ -407,6 +239,7 @@ class GlobalDependency extends CacheDependency {
if ( !isset( $GLOBALS[$this->name] ) ) {
return true;
}
+
return $GLOBALS[$this->name] != $this->value;
}
}
@@ -415,7 +248,8 @@ class GlobalDependency extends CacheDependency {
* @ingroup Cache
*/
class ConstantDependency extends CacheDependency {
- var $name, $value;
+ private $name;
+ private $value;
function __construct( $name ) {
$this->name = $name;
diff --git a/includes/CacheHelper.php b/includes/cache/CacheHelper.php
index f0ae5a31..401c2b96 100644
--- a/includes/CacheHelper.php
+++ b/includes/cache/CacheHelper.php
@@ -28,12 +28,11 @@
* @since 1.20
*/
interface ICacheHelper {
-
/**
* Sets if the cache should be enabled or not.
*
* @since 1.20
- * @param boolean $cacheEnabled
+ * @param bool $cacheEnabled
*/
function setCacheEnabled( $cacheEnabled );
@@ -43,8 +42,8 @@ interface ICacheHelper {
*
* @since 1.20
*
- * @param integer|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
- * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
+ * @param int|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
+ * @param bool|null $cacheEnabled Sets if the cache should be enabled or not.
*/
function startCache( $cacheExpiry = null, $cacheEnabled = null );
@@ -56,7 +55,7 @@ interface ICacheHelper {
*
* @since 1.20
*
- * @param {function} $computeFunction
+ * @param callable $computeFunction
* @param array|mixed $args
* @param string|null $key
*
@@ -78,10 +77,9 @@ interface ICacheHelper {
*
* @since 1.20
*
- * @param integer $cacheExpiry
+ * @param int $cacheExpiry
*/
function setExpiry( $cacheExpiry );
-
}
/**
@@ -103,12 +101,11 @@ interface ICacheHelper {
* @since 1.20
*/
class CacheHelper implements ICacheHelper {
-
/**
* The time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
*
* @since 1.20
- * @var integer
+ * @var int
*/
protected $cacheExpiry = 3600;
@@ -127,7 +124,7 @@ class CacheHelper implements ICacheHelper {
* Null if this information is not available yet.
*
* @since 1.20
- * @var boolean|null
+ * @var bool|null
*/
protected $hasCached = null;
@@ -135,7 +132,7 @@ class CacheHelper implements ICacheHelper {
* If the cache is enabled or not.
*
* @since 1.20
- * @var boolean
+ * @var bool
*/
protected $cacheEnabled = true;
@@ -159,7 +156,7 @@ class CacheHelper implements ICacheHelper {
* Sets if the cache should be enabled or not.
*
* @since 1.20
- * @param boolean $cacheEnabled
+ * @param bool $cacheEnabled
*/
public function setCacheEnabled( $cacheEnabled ) {
$this->cacheEnabled = $cacheEnabled;
@@ -171,8 +168,8 @@ class CacheHelper implements ICacheHelper {
*
* @since 1.20
*
- * @param integer|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
- * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
+ * @param int|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
+ * @param bool|null $cacheEnabled Sets if the cache should be enabled or not.
*/
public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
if ( is_null( $this->hasCached ) ) {
@@ -195,7 +192,7 @@ class CacheHelper implements ICacheHelper {
* @since 1.20
*
* @param IContextSource $context
- * @param boolean $includePurgeLink
+ * @param bool $includePurgeLink
*
* @return string
*/
@@ -205,8 +202,7 @@ class CacheHelper implements ICacheHelper {
'cachedspecial-viewing-cached-ttl',
$context->getLanguage()->formatDuration( $this->cacheExpiry )
)->escaped();
- }
- else {
+ } else {
$message = $context->msg(
'cachedspecial-viewing-cached-ts'
)->escaped();
@@ -259,7 +255,7 @@ class CacheHelper implements ICacheHelper {
*
* @since 1.20
*
- * @param {function} $computeFunction
+ * @param callable $computeFunction
* @param array|mixed $args
* @param string|null $key
*
@@ -276,26 +272,22 @@ class CacheHelper implements ICacheHelper {
$itemKey = array_shift( $itemKey );
if ( !is_integer( $itemKey ) ) {
- wfWarn( "Attempted to get item with non-numeric key while the next item in the queue has a key ($itemKey) in " . __METHOD__ );
- }
- elseif ( is_null( $itemKey ) ) {
+ wfWarn( "Attempted to get item with non-numeric key while " .
+ "the next item in the queue has a key ($itemKey) in " . __METHOD__ );
+ } elseif ( is_null( $itemKey ) ) {
wfWarn( "Attempted to get an item while the queue is empty in " . __METHOD__ );
- }
- else {
+ } else {
$value = array_shift( $this->cachedChunks );
}
- }
- else {
+ } else {
if ( array_key_exists( $key, $this->cachedChunks ) ) {
$value = $this->cachedChunks[$key];
unset( $this->cachedChunks[$key] );
- }
- else {
+ } else {
wfWarn( "There is no item with key '$key' in this->cachedChunks in " . __METHOD__ );
}
}
- }
- else {
+ } else {
if ( !is_array( $args ) ) {
$args = array( $args );
}
@@ -305,8 +297,7 @@ class CacheHelper implements ICacheHelper {
if ( $this->cacheEnabled ) {
if ( is_null( $key ) ) {
$this->cachedChunks[] = $value;
- }
- else {
+ } else {
$this->cachedChunks[$key] = $value;
}
}
@@ -323,7 +314,11 @@ class CacheHelper implements ICacheHelper {
*/
public function saveCache() {
if ( $this->cacheEnabled && $this->hasCached === false && !empty( $this->cachedChunks ) ) {
- wfGetCache( CACHE_ANYTHING )->set( $this->getCacheKeyString(), $this->cachedChunks, $this->cacheExpiry );
+ wfGetCache( CACHE_ANYTHING )->set(
+ $this->getCacheKeyString(),
+ $this->cachedChunks,
+ $this->cacheExpiry
+ );
}
}
@@ -333,7 +328,7 @@ class CacheHelper implements ICacheHelper {
*
* @since 1.20
*
- * @param integer $cacheExpiry
+ * @param int $cacheExpiry
*/
public function setExpiry( $cacheExpiry ) {
$this->cacheExpiry = $cacheExpiry;
@@ -383,10 +378,9 @@ class CacheHelper implements ICacheHelper {
*
* @since 1.20
*
- * @param $handlerFunction
+ * @param callable $handlerFunction
*/
public function setOnInitializedHandler( $handlerFunction ) {
$this->onInitHandler = $handlerFunction;
}
-
}
diff --git a/includes/cache/FileCacheBase.php b/includes/cache/FileCacheBase.php
index d4bf5ee6..4bf36114 100644
--- a/includes/cache/FileCacheBase.php
+++ b/includes/cache/FileCacheBase.php
@@ -51,6 +51,7 @@ abstract class FileCacheBase {
*/
final protected function baseCacheDirectory() {
global $wgFileCacheDirectory;
+
return $wgFileCacheDirectory;
}
@@ -91,6 +92,7 @@ abstract class FileCacheBase {
if ( $this->mCached === null ) {
$this->mCached = file_exists( $this->cachePath() );
}
+
return $this->mCached;
}
@@ -100,6 +102,7 @@ abstract class FileCacheBase {
*/
public function cacheTimestamp() {
$timestamp = filemtime( $this->cachePath() );
+
return ( $timestamp !== false )
? wfTimestamp( TS_MW, $timestamp )
: false;
@@ -120,7 +123,8 @@ abstract class FileCacheBase {
$cachetime = $this->cacheTimestamp();
$good = ( $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime );
- wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n" );
+ wfDebug( __METHOD__ .
+ ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n" );
return $good;
}
@@ -140,6 +144,7 @@ abstract class FileCacheBase {
public function fetchText() {
if ( $this->useGzip() ) {
$fh = gzopen( $this->cachePath(), 'rb' );
+
return stream_get_contents( $fh );
} else {
return file_get_contents( $this->cachePath() );
@@ -148,7 +153,8 @@ abstract class FileCacheBase {
/**
* Save and compress text to the cache
- * @return string compressed text
+ * @param string $text
+ * @return string Compressed text
*/
public function saveText( $text ) {
global $wgUseFileCache;
@@ -165,10 +171,12 @@ abstract class FileCacheBase {
if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() . "\n" );
$this->mCached = null;
+
return false;
}
$this->mCached = true;
+
return $text;
}
@@ -223,7 +231,7 @@ abstract class FileCacheBase {
/**
* Roughly increments the cache misses in the last hour by unique visitors
- * @param $request WebRequest
+ * @param WebRequest $request
* @return void
*/
public function incrMissesRecent( WebRequest $request ) {
@@ -262,6 +270,7 @@ abstract class FileCacheBase {
*/
public function getMissesRecent() {
global $wgMemc;
+
return self::MISS_FACTOR * $wgMemc->get( $this->cacheMissKey() );
}
diff --git a/includes/cache/GenderCache.php b/includes/cache/GenderCache.php
index a933527a..63e7bfd7 100644
--- a/includes/cache/GenderCache.php
+++ b/includes/cache/GenderCache.php
@@ -41,27 +41,30 @@ class GenderCache {
if ( $that === null ) {
$that = new self();
}
+
return $that;
}
- protected function __construct() {}
+ protected function __construct() {
+ }
/**
* Returns the default gender option in this wiki.
- * @return String
+ * @return string
*/
protected function getDefault() {
if ( $this->default === null ) {
$this->default = User::getDefaultOption( 'gender' );
}
+
return $this->default;
}
/**
* Returns the gender for given username.
- * @param string $username or User: username
- * @param string $caller the calling method
- * @return String
+ * @param string|User $username Username
+ * @param string $caller The calling method
+ * @return string
*/
public function getGenderOf( $username, $caller = '' ) {
global $wgUser;
@@ -77,8 +80,8 @@ class GenderCache {
$this->misses++;
wfDebug( __METHOD__ . ": too many misses, returning default onwards\n" );
}
- return $this->getDefault();
+ return $this->getDefault();
} else {
$this->misses++;
$this->doQuery( $username, $caller );
@@ -94,8 +97,8 @@ class GenderCache {
/**
* Wrapper for doQuery that processes raw LinkBatch data.
*
- * @param $data
- * @param $caller
+ * @param array $data
+ * @param string $caller
*/
public function doLinkBatch( $data, $caller = '' ) {
$users = array();
@@ -115,8 +118,8 @@ class GenderCache {
* Wrapper for doQuery that processes a title or string array.
*
* @since 1.20
- * @param $titles List: array of Title objects or strings
- * @param string $caller the calling method
+ * @param array $titles Array of Title objects or strings
+ * @param string $caller The calling method
*/
public function doTitlesArray( $titles, $caller = '' ) {
$users = array();
@@ -136,8 +139,8 @@ class GenderCache {
/**
* Preloads genders for given list of users.
- * @param $users List|String: usernames
- * @param string $caller the calling method
+ * @param array|string $users Usernames
+ * @param string $caller The calling method
*/
public function doQuery( $users, $caller = '' ) {
$default = $this->getDefault();
@@ -184,6 +187,7 @@ class GenderCache {
if ( $indexSlash !== false ) {
$username = substr( $username, 0, $indexSlash );
}
+
// normalize underscore/spaces
return strtr( $username, '_', ' ' );
}
diff --git a/includes/cache/HTMLFileCache.php b/includes/cache/HTMLFileCache.php
index ab379116..58ca2dcd 100644
--- a/includes/cache/HTMLFileCache.php
+++ b/includes/cache/HTMLFileCache.php
@@ -31,25 +31,32 @@
class HTMLFileCache extends FileCacheBase {
/**
* Construct an ObjectFileCache from a Title and an action
- * @param $title Title|string Title object or prefixed DB key string
- * @param $action string
+ * @param Title|string $title Title object or prefixed DB key string
+ * @param string $action
* @throws MWException
* @return HTMLFileCache
+ *
+ * @deprecated Since 1.24, instantiate this class directly
*/
public static function newFromTitle( $title, $action ) {
- $cache = new self();
+ return new self( $title, $action );
+ }
+ /**
+ * @param Title|string $title Title object or prefixed DB key string
+ * @param string $action
+ * @throws MWException
+ */
+ public function __construct( $title, $action ) {
$allowedTypes = self::cacheablePageActions();
if ( !in_array( $action, $allowedTypes ) ) {
- throw new MWException( "Invalid filecache type given." );
+ throw new MWException( 'Invalid file cache type given.' );
}
- $cache->mKey = ( $title instanceof Title )
+ $this->mKey = ( $title instanceof Title )
? $title->getPrefixedDBkey()
: (string)$title;
- $cache->mType = (string)$action;
- $cache->mExt = 'html';
-
- return $cache;
+ $this->mType = (string)$action;
+ $this->mExt = 'html';
}
/**
@@ -84,7 +91,7 @@ class HTMLFileCache extends FileCacheBase {
/**
* Check if pages can be cached for this request/user
- * @param $context IContextSource
+ * @param IContextSource $context
* @return bool
*/
public static function useFileCache( IContextSource $context ) {
@@ -94,6 +101,7 @@ class HTMLFileCache extends FileCacheBase {
}
if ( $wgShowIPinHeader || $wgDebugToolbar ) {
wfDebug( "HTML file cache skipped. Either \$wgShowIPinHeader and/or \$wgDebugToolbar on\n" );
+
return false;
}
@@ -109,6 +117,7 @@ class HTMLFileCache extends FileCacheBase {
} elseif ( $query === 'maxage' || $query === 'smaxage' ) {
continue;
}
+
return false;
}
$user = $context->getUser();
@@ -116,13 +125,18 @@ class HTMLFileCache extends FileCacheBase {
// and extensions for auto-detecting user language.
$ulang = $context->getLanguage()->getCode();
$clang = $wgContLang->getCode();
+
// Check that there are no other sources of variation
- return !$user->getId() && !$user->getNewtalk() && $ulang == $clang;
+ if ( $user->getId() || $user->getNewtalk() || $ulang != $clang ) {
+ return false;
+ }
+ // Allow extensions to disable caching
+ return wfRunHooks( 'HTMLFileCache::useFileCache', array( $context ) );
}
/**
* Read from cache to context output
- * @param $context IContextSource
+ * @param IContextSource $context
* @return void
*/
public function loadFromFileCache( IContextSource $context ) {
@@ -152,7 +166,7 @@ class HTMLFileCache extends FileCacheBase {
/**
* Save this cache object with the given text.
* Use this as an ob_start() handler.
- * @param $text string
+ * @param string $text
* @return bool Whether $wgUseFileCache is enabled
*/
public function saveToFileCache( $text ) {
@@ -163,7 +177,7 @@ class HTMLFileCache extends FileCacheBase {
return $text;
}
- wfDebug( __METHOD__ . "()\n", false );
+ wfDebug( __METHOD__ . "()\n", 'log' );
$now = wfTimestampNow();
if ( $this->useGzip() ) {
@@ -185,6 +199,7 @@ class HTMLFileCache extends FileCacheBase {
// @todo Ugly wfClientAcceptsGzip() function - use context!
if ( wfClientAcceptsGzip() ) {
header( 'Content-Encoding: gzip' );
+
return $compressed;
} else {
return $text;
@@ -196,7 +211,7 @@ class HTMLFileCache extends FileCacheBase {
/**
* Clear the file caches for a page for all actions
- * @param $title Title
+ * @param Title $title
* @return bool Whether $wgUseFileCache is enabled
*/
public static function clearFileCache( Title $title ) {
@@ -207,7 +222,7 @@ class HTMLFileCache extends FileCacheBase {
}
foreach ( self::cacheablePageActions() as $type ) {
- $fc = self::newFromTitle( $title, $type );
+ $fc = new self( $title, $type );
$fc->clearCache();
}
diff --git a/includes/cache/LinkBatch.php b/includes/cache/LinkBatch.php
index 48b60aa9..48c063f4 100644
--- a/includes/cache/LinkBatch.php
+++ b/includes/cache/LinkBatch.php
@@ -31,7 +31,7 @@ class LinkBatch {
/**
* 2-d array, first index namespace, second index dbkey, value arbitrary
*/
- var $data = array();
+ public $data = array();
/**
* For debugging which method is using this class.
@@ -49,14 +49,14 @@ class LinkBatch {
* class. Only used in debugging output.
* @since 1.17
*
- * @param $caller
+ * @param string $caller
*/
public function setCaller( $caller ) {
$this->caller = $caller;
}
/**
- * @param $title Title
+ * @param Title $title
*/
public function addObj( $title ) {
if ( is_object( $title ) ) {
@@ -67,9 +67,8 @@ class LinkBatch {
}
/**
- * @param $ns int
- * @param $dbkey string
- * @return mixed
+ * @param int $ns
+ * @param string $dbkey
*/
public function add( $ns, $dbkey ) {
if ( $ns < 0 ) {
@@ -86,7 +85,7 @@ class LinkBatch {
* Set the link list to a given 2-d array
* First key is the namespace, second is the DB key, value arbitrary
*
- * @param $array array
+ * @param array $array
*/
public function setArray( $array ) {
$this->data = $array;
@@ -113,10 +112,11 @@ class LinkBatch {
/**
* Do the query and add the results to the LinkCache object
*
- * @return Array mapping PDBK to ID
+ * @return array Mapping PDBK to ID
*/
public function execute() {
$linkCache = LinkCache::singleton();
+
return $this->executeInto( $linkCache );
}
@@ -124,8 +124,8 @@ class LinkBatch {
* Do the query and add the results to a given LinkCache object
* Return an array mapping PDBK to ID
*
- * @param $cache LinkCache
- * @return Array remaining IDs
+ * @param LinkCache $cache
+ * @return array Remaining IDs
*/
protected function executeInto( &$cache ) {
wfProfileIn( __METHOD__ );
@@ -133,6 +133,7 @@ class LinkBatch {
$this->doGenderQuery();
$ids = $this->addResultToCache( $cache, $res );
wfProfileOut( __METHOD__ );
+
return $ids;
}
@@ -142,9 +143,9 @@ class LinkBatch {
* This function *also* stores extra fields of the title used for link
* parsing to avoid extra DB queries.
*
- * @param $cache LinkCache
- * @param $res
- * @return Array of remaining titles
+ * @param LinkCache $cache
+ * @param ResultWrapper $res
+ * @return array Array of remaining titles
*/
public function addResultToCache( $cache, $res ) {
if ( !$res ) {
@@ -170,14 +171,17 @@ class LinkBatch {
$ids[$title->getPrefixedDBkey()] = 0;
}
}
+
return $ids;
}
/**
* Perform the existence test query, return a ResultWrapper with page_id fields
- * @return Bool|ResultWrapper
+ * @return bool|ResultWrapper
*/
public function doQuery() {
+ global $wgContentHandlerUseDB;
+
if ( $this->isEmpty() ) {
return false;
}
@@ -188,6 +192,11 @@ class LinkBatch {
$table = 'page';
$fields = array( 'page_id', 'page_namespace', 'page_title', 'page_len',
'page_is_redirect', 'page_latest' );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
+
$conds = $this->constructSet( 'page', $dbr );
// Do query
@@ -197,13 +206,14 @@ class LinkBatch {
}
$res = $dbr->select( $table, $fields, $conds, $caller );
wfProfileOut( __METHOD__ );
+
return $res;
}
/**
* Do (and cache) {{GENDER:...}} information for userpages in this LinkBatch
*
- * @return bool whether the query was successful
+ * @return bool Whether the query was successful
*/
public function doGenderQuery() {
if ( $this->isEmpty() ) {
@@ -217,15 +227,16 @@ class LinkBatch {
$genderCache = GenderCache::singleton();
$genderCache->doLinkBatch( $this->data, $this->caller );
+
return true;
}
/**
* Construct a WHERE clause which will match all the given titles.
*
- * @param string $prefix the appropriate table's field name prefix ('page', 'pl', etc)
- * @param $db DatabaseBase object to use
- * @return mixed string with SQL where clause fragment, or false if no items.
+ * @param string $prefix The appropriate table's field name prefix ('page', 'pl', etc)
+ * @param DatabaseBase $db DatabaseBase object to use
+ * @return string|bool String with SQL where clause fragment, or false if no items.
*/
public function constructSet( $prefix, $db ) {
return $db->makeWhereFrom2d( $this->data, "{$prefix}_namespace", "{$prefix}_title" );
diff --git a/includes/cache/LinkCache.php b/includes/cache/LinkCache.php
index 54de1989..6925df90 100644
--- a/includes/cache/LinkCache.php
+++ b/includes/cache/LinkCache.php
@@ -35,7 +35,6 @@ class LinkCache {
private $mGoodLinkFields = array();
private $mBadLinks = array();
private $mForUpdate = false;
- private $useDatabase = true;
/**
* @var LinkCache
@@ -52,6 +51,7 @@ class LinkCache {
return self::$instance;
}
self::$instance = new LinkCache;
+
return self::$instance;
}
@@ -78,6 +78,7 @@ class LinkCache {
/**
* General accessor to get/set whether SELECT FOR UPDATE should be used
*
+ * @param bool $update
* @return bool
*/
public function forUpdate( $update = null ) {
@@ -85,8 +86,8 @@ class LinkCache {
}
/**
- * @param $title
- * @return array|int
+ * @param string $title
+ * @return int
*/
public function getGoodLinkID( $title ) {
if ( array_key_exists( $title, $this->mGoodLinks ) ) {
@@ -99,9 +100,9 @@ class LinkCache {
/**
* Get a field of a title object from cache.
* If this link is not good, it will return NULL.
- * @param $title Title
+ * @param Title $title
* @param string $field ('length','redirect','revision','model')
- * @return mixed
+ * @return string|null
*/
public function getGoodLinkFieldObj( $title, $field ) {
$dbkey = $title->getPrefixedDBkey();
@@ -113,7 +114,7 @@ class LinkCache {
}
/**
- * @param $title
+ * @param string $title
* @return bool
*/
public function isBadLink( $title ) {
@@ -123,28 +124,31 @@ class LinkCache {
/**
* Add a link for the title to the link cache
*
- * @param $id Integer: page's ID
- * @param $title Title object
- * @param $len Integer: text's length
- * @param $redir Integer: whether the page is a redirect
- * @param $revision Integer: latest revision's ID
- * @param $model Integer: latest revision's content model ID
+ * @param int $id Page's ID
+ * @param Title $title
+ * @param int $len Text's length
+ * @param int $redir Whether the page is a redirect
+ * @param int $revision Latest revision's ID
+ * @param string|null $model Latest revision's content model ID
*/
- public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false, $model = false ) {
+ public function addGoodLinkObj( $id, $title, $len = -1, $redir = null,
+ $revision = 0, $model = null
+ ) {
$dbkey = $title->getPrefixedDBkey();
- $this->mGoodLinks[$dbkey] = intval( $id );
+ $this->mGoodLinks[$dbkey] = (int)$id;
$this->mGoodLinkFields[$dbkey] = array(
- 'length' => intval( $len ),
- 'redirect' => intval( $redir ),
- 'revision' => intval( $revision ),
- 'model' => intval( $model ) );
+ 'length' => (int)$len,
+ 'redirect' => (int)$redir,
+ 'revision' => (int)$revision,
+ 'model' => $model ? (string)$model : null,
+ );
}
/**
* Same as above with better interface.
* @since 1.19
- * @param $title Title
- * @param $row object which has the fields page_id, page_is_redirect,
+ * @param Title $title
+ * @param stdClass $row Object which has the fields page_id, page_is_redirect,
* page_latest and page_content_model
*/
public function addGoodLinkObjFromRow( $title, $row ) {
@@ -159,7 +163,7 @@ class LinkCache {
}
/**
- * @param $title Title
+ * @param Title $title
*/
public function addBadLinkObj( $title ) {
$dbkey = $title->getPrefixedDBkey();
@@ -173,7 +177,7 @@ class LinkCache {
}
/**
- * @param $title Title
+ * @param Title $title
*/
public function clearLink( $title ) {
$dbkey = $title->getPrefixedDBkey();
@@ -193,8 +197,8 @@ class LinkCache {
/**
* Add a title to the link cache, return the page_id or zero if non-existent
*
- * @param string $title title to add
- * @return Integer
+ * @param string $title Title to add
+ * @return int
*/
public function addLink( $title ) {
$nt = Title::newFromDBkey( $title );
@@ -206,23 +210,10 @@ class LinkCache {
}
/**
- * Enable or disable database use.
- * @since 1.22
- * @param $value Boolean
- * @return Boolean
- */
- public function useDatabase( $value = null ) {
- if ( $value !== null ) {
- $this->useDatabase = (bool)$value;
- }
- return $this->useDatabase;
- }
-
- /**
* Add a title to the link cache, return the page_id or zero if non-existent
*
- * @param $nt Title object to add
- * @return Integer
+ * @param Title $nt Title object to add
+ * @return int
*/
public function addLinkObj( $nt ) {
global $wgAntiLockFlags, $wgContentHandlerUseDB;
@@ -232,20 +223,19 @@ class LinkCache {
$key = $nt->getPrefixedDBkey();
if ( $this->isBadLink( $key ) || $nt->isExternal() ) {
wfProfileOut( __METHOD__ );
+
return 0;
}
$id = $this->getGoodLinkID( $key );
if ( $id != 0 ) {
wfProfileOut( __METHOD__ );
+
return $id;
}
if ( $key === '' ) {
wfProfileOut( __METHOD__ );
- return 0;
- }
- if( !$this->useDatabase ) {
return 0;
}
@@ -280,6 +270,7 @@ class LinkCache {
}
wfProfileOut( __METHOD__ );
+
return $id;
}
diff --git a/includes/cache/LocalisationCache.php b/includes/cache/LocalisationCache.php
index 25a1e196..ae27fba3 100644
--- a/includes/cache/LocalisationCache.php
+++ b/includes/cache/LocalisationCache.php
@@ -20,8 +20,6 @@
* @file
*/
-define( 'MW_LC_VERSION', 2 );
-
/**
* Class for caching the contents of localisation files, Messages*.php
* and *.i18n.php.
@@ -35,20 +33,22 @@ define( 'MW_LC_VERSION', 2 );
* as grammatical transformation, is done by the caller.
*/
class LocalisationCache {
+ const VERSION = 2;
+
/** Configuration associative array */
- var $conf;
+ private $conf;
/**
* True if recaching should only be done on an explicit call to recache().
* Setting this reduces the overhead of cache freshness checking, which
* requires doing a stat() for every extension i18n file.
*/
- var $manualRecache = false;
+ private $manualRecache = false;
/**
* True to treat all files as expired until they are regenerated by this object.
*/
- var $forceRecache = false;
+ private $forceRecache = false;
/**
* The cache data. 3-d array, where the first key is the language code,
@@ -56,14 +56,14 @@ class LocalisationCache {
* an item specific subkey index. Some items are not arrays and so for those
* items, there are no subkeys.
*/
- var $data = array();
+ protected $data = array();
/**
* The persistent store object. An instance of LCStore.
*
* @var LCStore
*/
- var $store;
+ private $store;
/**
* A 2-d associative array, code/key, where presence indicates that the item
@@ -72,32 +72,32 @@ class LocalisationCache {
* For split items, if set, this indicates that all of the subitems have been
* loaded.
*/
- var $loadedItems = array();
+ private $loadedItems = array();
/**
* A 3-d associative array, code/key/subkey, where presence indicates that
* the subitem is loaded. Only used for the split items, i.e. messages.
*/
- var $loadedSubitems = array();
+ private $loadedSubitems = array();
/**
* An array where presence of a key indicates that that language has been
* initialised. Initialisation includes checking for cache expiry and doing
* any necessary updates.
*/
- var $initialisedLangs = array();
+ private $initialisedLangs = array();
/**
* An array mapping non-existent pseudo-languages to fallback languages. This
* is filled by initShallowFallback() when data is requested from a language
* that lacks a Messages*.php file.
*/
- var $shallowFallbacks = array();
+ private $shallowFallbacks = array();
/**
* An array where the keys are codes that have been recached by this instance.
*/
- var $recachedLangs = array();
+ private $recachedLangs = array();
/**
* All item keys
@@ -106,7 +106,7 @@ class LocalisationCache {
'fallback', 'namespaceNames', 'bookstoreList',
'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
- 'linkTrail', 'namespaceAliases',
+ 'linkTrail', 'linkPrefixCharset', 'namespaceAliases',
'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
@@ -158,7 +158,7 @@ class LocalisationCache {
* Associative array of cached plural rules. The key is the language code,
* the value is an array of plural rules for that language.
*/
- var $pluralRules = null;
+ private $pluralRules = null;
/**
* Associative array of cached plural rule types. The key is the language
@@ -172,16 +172,16 @@ class LocalisationCache {
* example, {{plural:count|wordform1|wordform2|wordform3}}, rather than
* {{plural:count|one=wordform1|two=wordform2|many=wordform3}}.
*/
- var $pluralRuleTypes = null;
+ private $pluralRuleTypes = null;
- var $mergeableKeys = null;
+ private $mergeableKeys = null;
/**
* Constructor.
* For constructor parameters, see the documentation in DefaultSettings.php
* for $wgLocalisationCacheConf.
*
- * @param $conf Array
+ * @param array $conf
* @throws MWException
*/
function __construct( $conf ) {
@@ -195,16 +195,13 @@ class LocalisationCache {
switch ( $conf['store'] ) {
case 'files':
case 'file':
- $storeClass = 'LCStore_CDB';
+ $storeClass = 'LCStoreCDB';
break;
case 'db':
- $storeClass = 'LCStore_DB';
- break;
- case 'accel':
- $storeClass = 'LCStore_Accel';
+ $storeClass = 'LCStoreDB';
break;
case 'detect':
- $storeClass = $wgCacheDirectory ? 'LCStore_CDB' : 'LCStore_DB';
+ $storeClass = $wgCacheDirectory ? 'LCStoreCDB' : 'LCStoreDB';
break;
default:
throw new MWException(
@@ -212,7 +209,7 @@ class LocalisationCache {
}
}
- wfDebug( get_class( $this ) . ": using store $storeClass\n" );
+ wfDebugLog( 'caches', get_class( $this ) . ": using store $storeClass" );
if ( !empty( $conf['storeDirectory'] ) ) {
$storeConf['directory'] = $conf['storeDirectory'];
}
@@ -228,7 +225,7 @@ class LocalisationCache {
/**
* Returns true if the given key is mergeable, that is, if it is an associative
* array which can be merged through a fallback sequence.
- * @param $key
+ * @param string $key
* @return bool
*/
public function isMergeableKey( $key ) {
@@ -241,6 +238,7 @@ class LocalisationCache {
self::$magicWordKeys
) );
}
+
return isset( $this->mergeableKeys[$key] );
}
@@ -249,8 +247,8 @@ class LocalisationCache {
*
* Warning: this may be slow for split items (messages), since it will
* need to fetch all of the subitems from the cache individually.
- * @param $code
- * @param $key
+ * @param string $code
+ * @param string $key
* @return mixed
*/
public function getItem( $code, $key ) {
@@ -269,14 +267,15 @@ class LocalisationCache {
/**
* Get a subitem, for instance a single message for a given language.
- * @param $code
- * @param $key
- * @param $subkey
- * @return null
+ * @param string $code
+ * @param string $key
+ * @param string $subkey
+ * @return mixed|null
*/
public function getSubitem( $code, $key, $subkey ) {
if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
- !isset( $this->loadedItems[$code][$key] ) ) {
+ !isset( $this->loadedItems[$code][$key] )
+ ) {
wfProfileIn( __METHOD__ . '-load' );
$this->loadSubitem( $code, $key, $subkey );
wfProfileOut( __METHOD__ . '-load' );
@@ -297,8 +296,8 @@ class LocalisationCache {
*
* Will return null if the item is not found, or false if the item is not an
* array.
- * @param $code
- * @param $key
+ * @param string $code
+ * @param string $key
* @return bool|null|string
*/
public function getSubitemList( $code, $key ) {
@@ -316,8 +315,8 @@ class LocalisationCache {
/**
* Load an item into the cache.
- * @param $code
- * @param $key
+ * @param string $code
+ * @param string $key
*/
protected function loadItem( $code, $key ) {
if ( !isset( $this->initialisedLangs[$code] ) ) {
@@ -331,6 +330,7 @@ class LocalisationCache {
if ( isset( $this->shallowFallbacks[$code] ) ) {
$this->loadItem( $this->shallowFallbacks[$code], $key );
+
return;
}
@@ -351,13 +351,14 @@ class LocalisationCache {
/**
* Load a subitem into the cache
- * @param $code
- * @param $key
- * @param $subkey
+ * @param string $code
+ * @param string $key
+ * @param string $subkey
*/
protected function loadSubitem( $code, $key, $subkey ) {
if ( !in_array( $key, self::$splitKeys ) ) {
$this->loadItem( $code, $key );
+
return;
}
@@ -367,12 +368,14 @@ class LocalisationCache {
// Check to see if initLanguage() loaded it for us
if ( isset( $this->loadedItems[$code][$key] ) ||
- isset( $this->loadedSubitems[$code][$key][$subkey] ) ) {
+ isset( $this->loadedSubitems[$code][$key][$subkey] )
+ ) {
return;
}
if ( isset( $this->shallowFallbacks[$code] ) ) {
$this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
+
return;
}
@@ -391,15 +394,17 @@ class LocalisationCache {
public function isExpired( $code ) {
if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
wfDebug( __METHOD__ . "($code): forced reload\n" );
+
return true;
}
$deps = $this->store->get( $code, 'deps' );
$keys = $this->store->get( $code, 'list' );
$preload = $this->store->get( $code, 'preload' );
- // Different keys may expire separately, at least in LCStore_Accel
+ // Different keys may expire separately for some stores
if ( $deps === null || $keys === null || $preload === null ) {
wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
+
return true;
}
@@ -411,6 +416,7 @@ class LocalisationCache {
if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
get_class( $dep ) . "\n" );
+
return true;
}
}
@@ -420,7 +426,7 @@ class LocalisationCache {
/**
* Initialise a language in this object. Rebuild the cache if necessary.
- * @param $code
+ * @param string $code
* @throws MWException
*/
protected function initLanguage( $code ) {
@@ -433,18 +439,20 @@ class LocalisationCache {
# If the code is of the wrong form for a Messages*.php file, do a shallow fallback
if ( !Language::isValidBuiltInCode( $code ) ) {
$this->initShallowFallback( $code, 'en' );
+
return;
}
# Recache the data if necessary
if ( !$this->manualRecache && $this->isExpired( $code ) ) {
- if ( file_exists( Language::getMessagesFileName( $code ) ) ) {
+ if ( Language::isSupportedLanguage( $code ) ) {
$this->recache( $code );
} elseif ( $code === 'en' ) {
throw new MWException( 'MessagesEn.php is missing.' );
} else {
$this->initShallowFallback( $code, 'en' );
}
+
return;
}
@@ -458,6 +466,7 @@ class LocalisationCache {
'Please run maintenance/rebuildLocalisationCache.php.' );
}
$this->initShallowFallback( $code, 'en' );
+
return;
} else {
throw new MWException( 'Invalid or missing localisation cache.' );
@@ -478,8 +487,8 @@ class LocalisationCache {
/**
* Create a fallback from one language to another, without creating a
* complete persistent cache.
- * @param $primaryCode
- * @param $fallbackCode
+ * @param string $primaryCode
+ * @param string $fallbackCode
*/
public function initShallowFallback( $primaryCode, $fallbackCode ) {
$this->data[$primaryCode] =& $this->data[$fallbackCode];
@@ -490,17 +499,23 @@ class LocalisationCache {
/**
* Read a PHP file containing localisation data.
- * @param $_fileName
- * @param $_fileType
+ * @param string $_fileName
+ * @param string $_fileType
* @throws MWException
* @return array
*/
protected function readPHPFile( $_fileName, $_fileType ) {
wfProfileIn( __METHOD__ );
// Disable APC caching
+ wfSuppressWarnings();
$_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
+ wfRestoreWarnings();
+
include $_fileName;
+
+ wfSuppressWarnings();
ini_set( 'apc.cache_by_default', $_apcEnabled );
+ wfRestoreWarnings();
if ( $_fileType == 'core' || $_fileType == 'extension' ) {
$data = compact( self::$allKeys );
@@ -511,12 +526,57 @@ class LocalisationCache {
throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
}
wfProfileOut( __METHOD__ );
+
return $data;
}
/**
+ * Read a JSON file containing localisation messages.
+ * @param string $fileName Name of file to read
+ * @throws MWException If there is a syntax error in the JSON file
+ * @return array Array with a 'messages' key, or empty array if the file doesn't exist
+ */
+ public function readJSONFile( $fileName ) {
+ wfProfileIn( __METHOD__ );
+
+ if ( !is_readable( $fileName ) ) {
+ wfProfileOut( __METHOD__ );
+
+ return array();
+ }
+
+ $json = file_get_contents( $fileName );
+ if ( $json === false ) {
+ wfProfileOut( __METHOD__ );
+
+ return array();
+ }
+
+ $data = FormatJson::decode( $json, true );
+ if ( $data === null ) {
+ wfProfileOut( __METHOD__ );
+
+ throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
+ }
+
+ // Remove keys starting with '@', they're reserved for metadata and non-message data
+ foreach ( $data as $key => $unused ) {
+ if ( $key === '' || $key[0] === '@' ) {
+ unset( $data[$key] );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ // The JSON format only supports messages, none of the other variables, so wrap the data
+ return array( 'messages' => $data );
+ }
+
+ /**
* Get the compiled plural rules for a given language from the XML files.
* @since 1.20
+ * @param string $code
+ * @return array|null
*/
public function getCompiledPluralRules( $code ) {
$rules = $this->getPluralRules( $code );
@@ -526,9 +586,11 @@ class LocalisationCache {
try {
$compiledRules = CLDRPluralRuleEvaluator::compile( $rules );
} catch ( CLDRPluralRuleError $e ) {
- wfDebugLog( 'l10n', $e->getMessage() . "\n" );
+ wfDebugLog( 'l10n', $e->getMessage() );
+
return array();
}
+
return $compiledRules;
}
@@ -536,6 +598,8 @@ class LocalisationCache {
* Get the plural rules for a given language from the XML files.
* Cached.
* @since 1.20
+ * @param string $code
+ * @return array|null
*/
public function getPluralRules( $code ) {
if ( $this->pluralRules === null ) {
@@ -552,6 +616,8 @@ class LocalisationCache {
* Get the plural rule types for a given language from the XML files.
* Cached.
* @since 1.22
+ * @param string $code
+ * @return array|null
*/
public function getPluralRuleTypes( $code ) {
if ( $this->pluralRuleTypes === null ) {
@@ -582,6 +648,8 @@ class LocalisationCache {
/**
* Load a plural XML file with the given filename, compile the relevant
* rules, and save the compiled rules in a process-local cache.
+ *
+ * @param string $fileName
*/
protected function loadPluralFile( $fileName ) {
$doc = new DOMDocument;
@@ -612,20 +680,24 @@ class LocalisationCache {
* Read the data from the source files for a given language, and register
* the relevant dependencies in the $deps array. If the localisation
* exists, the data array is returned, otherwise false is returned.
+ *
+ * @param string $code
+ * @param array $deps
+ * @return array
*/
protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
global $IP;
wfProfileIn( __METHOD__ );
+ // This reads in the PHP i18n file with non-messages l10n data
$fileName = Language::getMessagesFileName( $code );
if ( !file_exists( $fileName ) ) {
- wfProfileOut( __METHOD__ );
- return false;
+ $data = array();
+ } else {
+ $deps[] = new FileDependency( $fileName );
+ $data = $this->readPHPFile( $fileName, 'core' );
}
- $deps[] = new FileDependency( $fileName );
- $data = $this->readPHPFile( $fileName, 'core' );
-
# Load CLDR plural rules for JavaScript
$data['pluralRules'] = $this->getPluralRules( $code );
# And for PHP
@@ -637,15 +709,16 @@ class LocalisationCache {
$deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
wfProfileOut( __METHOD__ );
+
return $data;
}
/**
* Merge two localisation values, a primary and a fallback, overwriting the
* primary value in place.
- * @param $key
- * @param $value
- * @param $fallbackValue
+ * @param string $key
+ * @param mixed $value
+ * @param mixed $fallbackValue
*/
protected function mergeItem( $key, &$value, $fallbackValue ) {
if ( !is_null( $value ) ) {
@@ -674,8 +747,8 @@ class LocalisationCache {
}
/**
- * @param $value
- * @param $fallbackValue
+ * @param mixed $value
+ * @param mixed $fallbackValue
*/
protected function mergeMagicWords( &$value, $fallbackValue ) {
foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
@@ -698,10 +771,10 @@ class LocalisationCache {
*
* Returns true if any data from the extension array was used, false
* otherwise.
- * @param $codeSequence
- * @param $key
- * @param $value
- * @param $fallbackValue
+ * @param array $codeSequence
+ * @param string $key
+ * @param mixed $value
+ * @param mixed $fallbackValue
* @return bool
*/
protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
@@ -719,11 +792,11 @@ class LocalisationCache {
/**
* Load localisation data for a given language for both core and extensions
* and save it to the persistent cache store and the process cache
- * @param $code
+ * @param string $code
* @throws MWException
*/
public function recache( $code ) {
- global $wgExtensionMessagesFiles;
+ global $wgExtensionMessagesFiles, $wgMessagesDirs;
wfProfileIn( __METHOD__ );
if ( !$code ) {
@@ -751,7 +824,6 @@ class LocalisationCache {
foreach ( $data as $key => $value ) {
$this->mergeItem( $key, $coreData[$key], $value );
}
-
}
# Fill in the fallback if it's not there already
@@ -768,43 +840,31 @@ class LocalisationCache {
if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
$coreData['fallbackSequence'][] = 'en';
}
+ }
- # Load the fallback localisation item by item and merge it
- foreach ( $coreData['fallbackSequence'] as $fbCode ) {
- # Load the secondary localisation from the source file to
- # avoid infinite cycles on cyclic fallbacks
- $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
- if ( $fbData === false ) {
- continue;
- }
+ $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] );
- foreach ( self::$allKeys as $key ) {
- if ( !isset( $fbData[$key] ) ) {
- continue;
- }
+ wfProfileIn( __METHOD__ . '-fallbacks' );
- if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
- $this->mergeItem( $key, $coreData[$key], $fbData[$key] );
- }
- }
+ # Load non-JSON localisation data for extensions
+ $extensionData = array_combine(
+ $codeSequence,
+ array_fill( 0, count( $codeSequence ), $initialData ) );
+ foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
+ if ( isset( $wgMessagesDirs[$extension] ) ) {
+ # This extension has JSON message data; skip the PHP shim
+ continue;
}
- }
- $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] );
-
- # Load the extension localisations
- # This is done after the core because we know the fallback sequence now.
- # But it has a higher precedence for merging so that we can support things
- # like site-specific message overrides.
- wfProfileIn( __METHOD__ . '-extensions' );
- $allData = $initialData;
- foreach ( $wgExtensionMessagesFiles as $fileName ) {
$data = $this->readPHPFile( $fileName, 'extension' );
$used = false;
foreach ( $data as $key => $item ) {
- if ( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
- $used = true;
+ foreach ( $codeSequence as $csCode ) {
+ if ( isset( $item[$csCode] ) ) {
+ $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
+ $used = true;
+ }
}
}
@@ -813,15 +873,81 @@ class LocalisationCache {
}
}
- # Merge core data into extension data
- foreach ( $coreData as $key => $item ) {
- $this->mergeItem( $key, $allData[$key], $item );
+ # Load the localisation data for each fallback, then merge it into the full array
+ $allData = $initialData;
+ foreach ( $codeSequence as $csCode ) {
+ $csData = $initialData;
+
+ # Load core messages and the extension localisations.
+ foreach ( $wgMessagesDirs as $dirs ) {
+ foreach ( (array)$dirs as $dir ) {
+ $fileName = "$dir/$csCode.json";
+ $data = $this->readJSONFile( $fileName );
+
+ foreach ( $data as $key => $item ) {
+ $this->mergeItem( $key, $csData[$key], $item );
+ }
+
+ $deps[] = new FileDependency( $fileName );
+ }
+ }
+
+ # Merge non-JSON extension data
+ if ( isset( $extensionData[$csCode] ) ) {
+ foreach ( $extensionData[$csCode] as $key => $item ) {
+ $this->mergeItem( $key, $csData[$key], $item );
+ }
+ }
+
+ if ( $csCode === $code ) {
+ # Merge core data into extension data
+ foreach ( $coreData as $key => $item ) {
+ $this->mergeItem( $key, $csData[$key], $item );
+ }
+ } else {
+ # Load the secondary localisation from the source file to
+ # avoid infinite cycles on cyclic fallbacks
+ $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
+ if ( $fbData !== false ) {
+ # Only merge the keys that make sense to merge
+ foreach ( self::$allKeys as $key ) {
+ if ( !isset( $fbData[$key] ) ) {
+ continue;
+ }
+
+ if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
+ $this->mergeItem( $key, $csData[$key], $fbData[$key] );
+ }
+ }
+ }
+ }
+
+ # Allow extensions an opportunity to adjust the data for this
+ # fallback
+ wfRunHooks( 'LocalisationCacheRecacheFallback', array( $this, $csCode, &$csData ) );
+
+ # Merge the data for this fallback into the final array
+ if ( $csCode === $code ) {
+ $allData = $csData;
+ } else {
+ foreach ( self::$allKeys as $key ) {
+ if ( !isset( $csData[$key] ) ) {
+ continue;
+ }
+
+ if ( is_null( $allData[$key] ) || $this->isMergeableKey( $key ) ) {
+ $this->mergeItem( $key, $allData[$key], $csData[$key] );
+ }
+ }
+ }
}
- wfProfileOut( __METHOD__ . '-extensions' );
+
+ wfProfileOut( __METHOD__ . '-fallbacks' );
# Add cache dependencies for any referenced globals
$deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
- $deps['version'] = new ConstantDependency( 'MW_LC_VERSION' );
+ $deps['wgMessagesDirs'] = new GlobalDependency( 'wgMessagesDirs' );
+ $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
# Add dependencies to the cache entry
$allData['deps'] = $deps;
@@ -854,7 +980,8 @@ class LocalisationCache {
$allData['list'][$key] = array_keys( $allData[$key] );
}
# Run hooks
- wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) );
+ $purgeBlobs = true;
+ wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData, &$purgeBlobs ) );
if ( is_null( $allData['namespaceNames'] ) ) {
wfProfileOut( __METHOD__ );
@@ -889,8 +1016,8 @@ class LocalisationCache {
# Clear out the MessageBlobStore
# HACK: If using a null (i.e. disabled) storage backend, we
# can't write to the MessageBlobStore either
- if ( !$this->store instanceof LCStore_Null ) {
- MessageBlobStore::clear();
+ if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
+ MessageBlobStore::getInstance()->clear();
}
wfProfileOut( __METHOD__ );
@@ -901,7 +1028,7 @@ class LocalisationCache {
*
* The preload item will be loaded automatically, improving performance
* for the commonly-requested items it contains.
- * @param $data
+ * @param array $data
* @return array
*/
protected function buildPreload( $data ) {
@@ -925,7 +1052,7 @@ class LocalisationCache {
/**
* Unload the data for a given language from the object cache.
* Reduces memory usage.
- * @param $code
+ * @param string $code
*/
public function unload( $code ) {
unset( $this->data[$code] );
@@ -954,28 +1081,9 @@ class LocalisationCache {
* Disable the storage backend
*/
public function disableBackend() {
- $this->store = new LCStore_Null;
+ $this->store = new LCStoreNull;
$this->manualRecache = false;
}
-
- /**
- * Return an array with initialised languages.
- *
- * @return array
- */
- public function getInitialisedLanguages() {
- return $this->initialisedLangs;
- }
-
- /**
- * Set initialised languages.
- *
- * @param array $languages Optional array of initialised languages.
- */
- public function setInitialisedLanguages( $languages = array() ) {
- $this->initialisedLangs = $languages;
- }
-
}
/**
@@ -1024,68 +1132,19 @@ interface LCStore {
}
/**
- * LCStore implementation which uses PHP accelerator to store data.
- * This will work if one of XCache, WinCache or APC cacher is configured.
- * (See ObjectCache.php)
- */
-class LCStore_Accel implements LCStore {
- var $currentLang;
- var $keys;
-
- public function __construct() {
- $this->cache = wfGetCache( CACHE_ACCEL );
- }
-
- public function get( $code, $key ) {
- $k = wfMemcKey( 'l10n', $code, 'k', $key );
- $r = $this->cache->get( $k );
- return $r === false ? null : $r;
- }
-
- public function startWrite( $code ) {
- $k = wfMemcKey( 'l10n', $code, 'l' );
- $keys = $this->cache->get( $k );
- if ( $keys ) {
- foreach ( $keys as $k ) {
- $this->cache->delete( $k );
- }
- }
- $this->currentLang = $code;
- $this->keys = array();
- }
-
- public function finishWrite() {
- if ( $this->currentLang ) {
- $k = wfMemcKey( 'l10n', $this->currentLang, 'l' );
- $this->cache->set( $k, array_keys( $this->keys ) );
- }
- $this->currentLang = null;
- $this->keys = array();
- }
-
- public function set( $key, $value ) {
- if ( $this->currentLang ) {
- $k = wfMemcKey( 'l10n', $this->currentLang, 'k', $key );
- $this->keys[$k] = true;
- $this->cache->set( $k, $value );
- }
- }
-}
-
-/**
* LCStore implementation which uses the standard DB functions to store data.
* This will work on any MediaWiki installation.
*/
-class LCStore_DB implements LCStore {
- var $currentLang;
- var $writesDone = false;
+class LCStoreDB implements LCStore {
+ private $currentLang;
+ private $writesDone = false;
- /**
- * @var DatabaseBase
- */
- var $dbw;
- var $batch;
- var $readOnly = false;
+ /** @var DatabaseBase */
+ private $dbw;
+ /** @var array */
+ private $batch = array();
+
+ private $readOnly = false;
public function get( $code, $key ) {
if ( $this->writesDone ) {
@@ -1096,7 +1155,7 @@ class LCStore_DB implements LCStore {
$row = $db->selectRow( 'l10n_cache', array( 'lc_value' ),
array( 'lc_lang' => $code, 'lc_key' => $key ), __METHOD__ );
if ( $row ) {
- return unserialize( $row->lc_value );
+ return unserialize( $db->decodeBlob( $row->lc_value ) );
} else {
return null;
}
@@ -1105,25 +1164,11 @@ class LCStore_DB implements LCStore {
public function startWrite( $code ) {
if ( $this->readOnly ) {
return;
- }
-
- if ( !$code ) {
+ } elseif ( !$code ) {
throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
$this->dbw = wfGetDB( DB_MASTER );
- try {
- $this->dbw->begin( __METHOD__ );
- $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ );
- } catch ( DBQueryError $e ) {
- if ( $this->dbw->wasReadOnlyError() ) {
- $this->readOnly = true;
- $this->dbw->rollback( __METHOD__ );
- return;
- } else {
- throw $e;
- }
- }
$this->currentLang = $code;
$this->batch = array();
@@ -1132,37 +1177,42 @@ class LCStore_DB implements LCStore {
public function finishWrite() {
if ( $this->readOnly ) {
return;
+ } elseif ( is_null( $this->currentLang ) ) {
+ throw new MWException( __CLASS__ . ': must call startWrite() before finishWrite()' );
}
- if ( $this->batch ) {
- $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
+ $this->dbw->begin( __METHOD__ );
+ try {
+ $this->dbw->delete( 'l10n_cache',
+ array( 'lc_lang' => $this->currentLang ), __METHOD__ );
+ foreach ( array_chunk( $this->batch, 500 ) as $rows ) {
+ $this->dbw->insert( 'l10n_cache', $rows, __METHOD__ );
+ }
+ $this->writesDone = true;
+ } catch ( DBQueryError $e ) {
+ if ( $this->dbw->wasReadOnlyError() ) {
+ $this->readOnly = true; // just avoid site down time
+ } else {
+ throw $e;
+ }
}
-
$this->dbw->commit( __METHOD__ );
+
$this->currentLang = null;
- $this->dbw = null;
$this->batch = array();
- $this->writesDone = true;
}
public function set( $key, $value ) {
if ( $this->readOnly ) {
return;
- }
-
- if ( is_null( $this->currentLang ) ) {
- throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
+ } elseif ( is_null( $this->currentLang ) ) {
+ throw new MWException( __CLASS__ . ': must call startWrite() before set()' );
}
$this->batch[] = array(
'lc_lang' => $this->currentLang,
'lc_key' => $key,
- 'lc_value' => serialize( $value ) );
-
- if ( count( $this->batch ) >= 100 ) {
- $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
- $this->batch = array();
- }
+ 'lc_value' => $this->dbw->encodeBlob( serialize( $value ) ) );
}
}
@@ -1178,8 +1228,18 @@ class LCStore_DB implements LCStore {
*
* See Cdb.php and http://cr.yp.to/cdb.html
*/
-class LCStore_CDB implements LCStore {
- var $readers, $writer, $currentLang, $directory;
+class LCStoreCDB implements LCStore {
+ /** @var CdbReader[] */
+ private $readers;
+
+ /** @var CdbWriter */
+ private $writer;
+
+ /** @var string Current language code */
+ private $currentLang;
+
+ /** @var bool|string Cache directory. False if not set */
+ private $directory;
function __construct( $conf = array() ) {
global $wgCacheDirectory;
@@ -1195,21 +1255,30 @@ class LCStore_CDB implements LCStore {
if ( !isset( $this->readers[$code] ) ) {
$fileName = $this->getFileName( $code );
- if ( !file_exists( $fileName ) ) {
- $this->readers[$code] = false;
- } else {
- $this->readers[$code] = CdbReader::open( $fileName );
+ $this->readers[$code] = false;
+ if ( file_exists( $fileName ) ) {
+ try {
+ $this->readers[$code] = CdbReader::open( $fileName );
+ } catch ( CdbException $e ) {
+ wfDebug( __METHOD__ . ": unable to open cdb file for reading\n" );
+ }
}
}
if ( !$this->readers[$code] ) {
return null;
} else {
- $value = $this->readers[$code]->get( $key );
-
+ $value = false;
+ try {
+ $value = $this->readers[$code]->get( $key );
+ } catch ( CdbException $e ) {
+ wfDebug( __METHOD__ . ": CdbException caught, error message was "
+ . $e->getMessage() . "\n" );
+ }
if ( $value === false ) {
return null;
}
+
return unserialize( $value );
}
}
@@ -1227,13 +1296,21 @@ class LCStore_CDB implements LCStore {
$this->readers[$code]->close();
}
- $this->writer = CdbWriter::open( $this->getFileName( $code ) );
+ try {
+ $this->writer = CdbWriter::open( $this->getFileName( $code ) );
+ } catch ( CdbException $e ) {
+ throw new MWException( $e->getMessage() );
+ }
$this->currentLang = $code;
}
public function finishWrite() {
// Close the writer
- $this->writer->close();
+ try {
+ $this->writer->close();
+ } catch ( CdbException $e ) {
+ throw new MWException( $e->getMessage() );
+ }
$this->writer = null;
unset( $this->readers[$this->currentLang] );
$this->currentLang = null;
@@ -1243,13 +1320,18 @@ class LCStore_CDB implements LCStore {
if ( is_null( $this->writer ) ) {
throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
}
- $this->writer->set( $key, serialize( $value ) );
+ try {
+ $this->writer->set( $key, serialize( $value ) );
+ } catch ( CdbException $e ) {
+ throw new MWException( $e->getMessage() );
+ }
}
protected function getFileName( $code ) {
if ( strval( $code ) === '' || strpos( $code, '/' ) !== false ) {
throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
+
return "{$this->directory}/l10n_cache-$code.cdb";
}
}
@@ -1257,42 +1339,47 @@ class LCStore_CDB implements LCStore {
/**
* Null store backend, used to avoid DB errors during install
*/
-class LCStore_Null implements LCStore {
+class LCStoreNull implements LCStore {
public function get( $code, $key ) {
return null;
}
- public function startWrite( $code ) {}
- public function finishWrite() {}
- public function set( $key, $value ) {}
+ public function startWrite( $code ) {
+ }
+
+ public function finishWrite() {
+ }
+
+ public function set( $key, $value ) {
+ }
}
/**
* A localisation cache optimised for loading large amounts of data for many
* languages. Used by rebuildLocalisationCache.php.
*/
-class LocalisationCache_BulkLoad extends LocalisationCache {
+class LocalisationCacheBulkLoad extends LocalisationCache {
/**
* A cache of the contents of data files.
* Core files are serialized to avoid using ~1GB of RAM during a recache.
*/
- var $fileCache = array();
+ private $fileCache = array();
/**
* Most recently used languages. Uses the linked-list aspect of PHP hashtables
* to keep the most recently used language codes at the end of the array, and
* the language codes that are ready to be deleted at the beginning.
*/
- var $mruLangs = array();
+ private $mruLangs = array();
/**
* Maximum number of languages that may be loaded into $this->data
*/
- var $maxLoadedLangs = 10;
+ private $maxLoadedLangs = 10;
/**
- * @param $fileName
- * @param $fileType
+ * @param string $fileName
+ * @param string $fileType
* @return array|mixed
*/
protected function readPHPFile( $fileName, $fileType ) {
@@ -1317,30 +1404,32 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
}
/**
- * @param $code
- * @param $key
+ * @param string $code
+ * @param string $key
* @return mixed
*/
public function getItem( $code, $key ) {
unset( $this->mruLangs[$code] );
$this->mruLangs[$code] = true;
+
return parent::getItem( $code, $key );
}
/**
- * @param $code
- * @param $key
- * @param $subkey
- * @return
+ * @param string $code
+ * @param string $key
+ * @param string $subkey
+ * @return mixed
*/
public function getSubitem( $code, $key, $subkey ) {
unset( $this->mruLangs[$code] );
$this->mruLangs[$code] = true;
+
return parent::getSubitem( $code, $key, $subkey );
}
/**
- * @param $code
+ * @param string $code
*/
public function recache( $code ) {
parent::recache( $code );
@@ -1350,7 +1439,7 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
}
/**
- * @param $code
+ * @param string $code
*/
public function unload( $code ) {
unset( $this->mruLangs[$code] );
diff --git a/includes/cache/MapCacheLRU.php b/includes/cache/MapCacheLRU.php
new file mode 100644
index 00000000..95e3af76
--- /dev/null
+++ b/includes/cache/MapCacheLRU.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Per-process memory cache for storing items.
+ *
+ * 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 Cache
+ */
+
+/**
+ * Handles a simple LRU key/value map with a maximum number of entries
+ *
+ * Use ProcessCacheLRU if hierarchical purging is needed or objects can become stale
+ *
+ * @see ProcessCacheLRU
+ * @ingroup Cache
+ * @since 1.23
+ */
+class MapCacheLRU {
+ /** @var array */
+ protected $cache = array(); // (key => value)
+
+ protected $maxCacheKeys; // integer; max entries
+
+ /**
+ * @param int $maxKeys Maximum number of entries allowed (min 1).
+ * @throws MWException When $maxCacheKeys is not an int or =< 0.
+ */
+ public function __construct( $maxKeys ) {
+ if ( !is_int( $maxKeys ) || $maxKeys < 1 ) {
+ throw new MWException( __METHOD__ . " must be given an integer and >= 1" );
+ }
+ $this->maxCacheKeys = $maxKeys;
+ }
+
+ /**
+ * Set a key/value pair.
+ * This will prune the cache if it gets too large based on LRU.
+ * If the item is already set, it will be pushed to the top of the cache.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function set( $key, $value ) {
+ if ( array_key_exists( $key, $this->cache ) ) {
+ $this->ping( $key ); // push to top
+ } elseif ( count( $this->cache ) >= $this->maxCacheKeys ) {
+ reset( $this->cache );
+ $evictKey = key( $this->cache );
+ unset( $this->cache[$evictKey] );
+ }
+ $this->cache[$key] = $value;
+ }
+
+ /**
+ * Check if a key exists
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function has( $key ) {
+ return array_key_exists( $key, $this->cache );
+ }
+
+ /**
+ * Get the value for a key.
+ * This returns null if the key is not set.
+ * If the item is already set, it will be pushed to the top of the cache.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function get( $key ) {
+ if ( array_key_exists( $key, $this->cache ) ) {
+ $this->ping( $key ); // push to top
+ return $this->cache[$key];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Clear one or several cache entries, or all cache entries
+ *
+ * @param string|array $keys
+ * @return void
+ */
+ public function clear( $keys = null ) {
+ if ( $keys === null ) {
+ $this->cache = array();
+ } else {
+ foreach ( (array)$keys as $key ) {
+ unset( $this->cache[$key] );
+ }
+ }
+ }
+
+ /**
+ * Push an entry to the top of the cache
+ *
+ * @param string $key
+ */
+ protected function ping( $key ) {
+ $item = $this->cache[$key];
+ unset( $this->cache[$key] );
+ $this->cache[$key] = $item;
+ }
+}
diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php
index a92c87f4..1ef7cc58 100644
--- a/includes/cache/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -110,6 +110,7 @@ class MessageCache {
$wgMsgCacheExpiry
);
}
+
return self::$instance;
}
@@ -123,7 +124,7 @@ class MessageCache {
}
/**
- * @param ObjectCache $memCached A cache instance. If none, fall back to CACHE_NONE.
+ * @param BagOStuff $memCached A cache instance. If none, fall back to CACHE_NONE.
* @param bool $useDB
* @param int $expiry Lifetime for cache. @see $mExpiry.
*/
@@ -147,14 +148,15 @@ class MessageCache {
$this->mParserOptions = new ParserOptions;
$this->mParserOptions->setEditSection( false );
}
+
return $this->mParserOptions;
}
/**
* Try to load the cache from a local file.
*
- * @param string $hash the hash of contents, to check validity.
- * @param Mixed $code Optional language code, see documenation of load().
+ * @param string $hash The hash of contents, to check validity.
+ * @param string $code Optional language code, see documenation of load().
* @return array The cache array
*/
function getLocalCache( $hash, $code ) {
@@ -179,15 +181,20 @@ class MessageCache {
$serialized .= fread( $file, 100000 );
}
fclose( $file );
+
return unserialize( $serialized );
} else {
fclose( $file );
+
return false; // Wrong hash
}
}
/**
* Save the cache to a local file.
+ * @param string $serialized
+ * @param string $hash
+ * @param string $code
*/
function saveToLocal( $serialized, $hash, $code ) {
global $wgCacheDirectory;
@@ -201,6 +208,7 @@ class MessageCache {
if ( !$file ) {
wfDebug( "Unable to open local cache file for writing\n" );
+
return;
}
@@ -227,7 +235,7 @@ class MessageCache {
* or false if populating empty cache fails. Also returns true if MessageCache
* is disabled.
*
- * @param bool|String $code Language to which load messages
+ * @param bool|string $code Language to which load messages
* @throws MWException
* @return bool
*/
@@ -253,6 +261,7 @@ class MessageCache {
wfDebug( __METHOD__ . ": disabled\n" );
$shownDisabled = true;
}
+
return true;
}
@@ -415,6 +424,7 @@ class MessageCache {
$info = implode( ', ', $where );
wfDebug( __METHOD__ . ": Loading $code... $info\n" );
wfProfileOut( __METHOD__ );
+
return $success;
}
@@ -502,6 +512,7 @@ class MessageCache {
$cache['VERSION'] = MSG_CACHE_VERSION;
$cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
wfProfileOut( __METHOD__ );
+
return $cache;
}
@@ -517,6 +528,7 @@ class MessageCache {
if ( $this->mDisable ) {
wfProfileOut( __METHOD__ );
+
return;
}
@@ -561,7 +573,7 @@ class MessageCache {
// Update the message in the message blob store
global $wgContLang;
- MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
+ MessageBlobStore::getInstance()->updateMessage( $wgContLang->lcfirst( $msg ) );
wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
@@ -571,7 +583,7 @@ class MessageCache {
/**
* Is the given cache array expired due to time passing or a version change?
*
- * @param $cache
+ * @param array $cache
* @return bool
*/
protected function isCacheExpired( $cache ) {
@@ -584,6 +596,7 @@ class MessageCache {
if ( wfTimestampNow() >= $cache['EXPIRY'] ) {
return true;
}
+
return false;
}
@@ -617,6 +630,7 @@ class MessageCache {
}
wfProfileOut( __METHOD__ );
+
return $success;
}
@@ -627,7 +641,7 @@ class MessageCache {
* a timeout of MessageCache::MSG_LOCK_TIMEOUT.
*
* @param string $key
- * @return Boolean: success
+ * @return bool Success
*/
function lock( $key ) {
$lockKey = $key . ':lock';
@@ -676,20 +690,20 @@ class MessageCache {
* * Fallbacks will be just that: fallbacks. A fallback language will never be reached if
* the message is available *anywhere* in the language for which it is a fallback.
*
- * @param string $key the message key
+ * @param string $key The message key
* @param bool $useDB If true, look for the message in the DB, false
- * to use only the compiled l10n cache.
+ * to use only the compiled l10n cache.
* @param bool|string|object $langcode Code of the language to get the message for.
- * - If string and a valid code, will create a standard language object
- * - If string but not a valid code, will create a basic language object
- * - If boolean and false, create object from the current users language
- * - If boolean and true, create object from the wikis content language
- * - If language object, use it as given
- * @param bool $isFullKey specifies whether $key is a two part key
- * "msg/lang".
+ * - If string and a valid code, will create a standard language object
+ * - If string but not a valid code, will create a basic language object
+ * - If boolean and false, create object from the current users language
+ * - If boolean and true, create object from the wikis content language
+ * - If language object, use it as given
+ * @param bool $isFullKey Specifies whether $key is a two part key "msg/lang".
*
- * @throws MWException when given an invalid key
- * @return string|bool False if the message doesn't exist, otherwise the message (which can be empty)
+ * @throws MWException When given an invalid key
+ * @return string|bool False if the message doesn't exist, otherwise the
+ * message (which can be empty)
*/
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
global $wgContLang;
@@ -716,17 +730,28 @@ class MessageCache {
// Normalise title-case input (with some inlining)
$lckey = strtr( $key, ' ', '_' );
- if ( ord( $key ) < 128 ) {
+ if ( ord( $lckey ) < 128 ) {
$lckey[0] = strtolower( $lckey[0] );
- $uckey = ucfirst( $lckey );
} else {
$lckey = $wgContLang->lcfirst( $lckey );
+ }
+
+ wfRunHooks( 'MessageCache::get', array( &$lckey ) );
+
+ if ( ord( $lckey ) < 128 ) {
+ $uckey = ucfirst( $lckey );
+ } else {
$uckey = $wgContLang->ucfirst( $lckey );
}
// Loop through each language in the fallback list until we find something useful
$lang = wfGetLangObj( $langcode );
- $message = $this->getMessageFromFallbackChain( $lang, $lckey, $uckey, !$this->mDisable && $useDB );
+ $message = $this->getMessageFromFallbackChain(
+ $lang,
+ $lckey,
+ $uckey,
+ !$this->mDisable && $useDB
+ );
// If we still have no message, maybe the key was in fact a full key so try that
if ( $message === false ) {
@@ -804,7 +829,8 @@ class MessageCache {
return $message;
}
- list( $fallbackChain, $siteFallbackChain ) = Language::getFallbacksIncludingSiteLanguage( $langcode );
+ list( $fallbackChain, $siteFallbackChain ) =
+ Language::getFallbacksIncludingSiteLanguage( $langcode );
// Next try checking the database for all of the fallback languages of the requested language.
if ( $useDB ) {
@@ -897,11 +923,13 @@ class MessageCache {
if ( $entry ) {
if ( substr( $entry, 0, 1 ) === ' ' ) {
$this->mCache[$code][$title] = $entry;
+
// The message exists, so make sure a string
// is returned.
return (string)substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
$this->mCache[$code][$title] = '!NONEXISTENT';
+
return false;
} else {
# Corrupt/obsolete entry, delete it
@@ -983,6 +1011,7 @@ class MessageCache {
$this->mInParser = false;
$popts->setUserLang( $userlang );
}
+
return $message;
}
@@ -996,13 +1025,14 @@ class MessageCache {
$wgParser->firstCallInit();
# Clone it and store it
$class = $wgParserConf['class'];
- if ( $class == 'Parser_DiffTest' ) {
+ if ( $class == 'ParserDiffTest' ) {
# Uncloneable
$this->mParser = new $class( $wgParserConf );
} else {
$this->mParser = clone $wgParser;
}
}
+
return $this->mParser;
}
@@ -1043,6 +1073,7 @@ class MessageCache {
$this->mInParser = false;
wfProfileOut( __METHOD__ );
+
return $res;
}
@@ -1069,7 +1100,7 @@ class MessageCache {
}
/**
- * @param $key
+ * @param string $key
* @return array
*/
public function figureMessage( $key ) {
@@ -1085,6 +1116,7 @@ class MessageCache {
}
$message = implode( '/', $pieces );
+
return array( $message, $lang );
}
@@ -1094,7 +1126,7 @@ class MessageCache {
* for which MediaWiki:msgkey exists. If $code is another language code, this
* will ONLY return message keys for which MediaWiki:msgkey/$code exists.
* @param string $code Language code
- * @return array of message keys (strings)
+ * @return array Array of message keys (strings)
*/
public function getAllMessageKeys( $code ) {
global $wgContLang;
@@ -1109,6 +1141,7 @@ class MessageCache {
unset( $cache['EXPIRY'] );
// Remove any !NONEXISTENT keys
$cache = array_diff( $cache, array( '!NONEXISTENT' ) );
+
// Keys may appear with a capital first letter. lcfirst them.
return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
}
diff --git a/includes/cache/ObjectFileCache.php b/includes/cache/ObjectFileCache.php
index ed1e49a6..c7ef0443 100644
--- a/includes/cache/ObjectFileCache.php
+++ b/includes/cache/ObjectFileCache.php
@@ -29,8 +29,8 @@
class ObjectFileCache extends FileCacheBase {
/**
* Construct an ObjectFileCache from a key and a type
- * @param $key string
- * @param $type string
+ * @param string $key
+ * @param string $type
* @return ObjectFileCache
*/
public static function newFromKey( $key, $type ) {
diff --git a/includes/cache/ResourceFileCache.php b/includes/cache/ResourceFileCache.php
index 2ad7b853..55da52c5 100644
--- a/includes/cache/ResourceFileCache.php
+++ b/includes/cache/ResourceFileCache.php
@@ -34,7 +34,7 @@ class ResourceFileCache extends FileCacheBase {
/**
* Construct an ResourceFileCache from a context
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return ResourceFileCache
*/
public static function newFromContext( ResourceLoaderContext $context ) {
@@ -58,7 +58,7 @@ class ResourceFileCache extends FileCacheBase {
/**
* Check if an RL request can be cached.
* Caller is responsible for checking if any modules are private.
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return bool
*/
public static function useFileCache( ResourceLoaderContext $context ) {
@@ -80,8 +80,10 @@ class ResourceFileCache extends FileCacheBase {
} elseif ( $query === 'debug' && $val === 'false' ) {
continue;
}
+
return false;
}
+
return true; // cacheable
}
@@ -104,6 +106,7 @@ class ResourceFileCache extends FileCacheBase {
$this->getMissesRecent() >= self::MISS_THRESHOLD // many misses
);
}
+
return $this->mCacheWorthy;
}
}
diff --git a/includes/cache/UserCache.php b/includes/cache/UserCache.php
index 6085f586..7f36f5a6 100644
--- a/includes/cache/UserCache.php
+++ b/includes/cache/UserCache.php
@@ -36,23 +36,26 @@ class UserCache {
if ( $instance === null ) {
$instance = new self();
}
+
return $instance;
}
- protected function __construct() {}
+ protected function __construct() {
+ }
/**
* Get a property of a user based on their user ID
*
- * @param $userId integer User ID
+ * @param int $userId User ID
* @param string $prop User property
- * @return mixed The property or false if the user does not exist
+ * @return mixed|bool The property or false if the user does not exist
*/
public function getProp( $userId, $prop ) {
if ( !isset( $this->cache[$userId][$prop] ) ) {
wfDebug( __METHOD__ . ": querying DB for prop '$prop' for user ID '$userId'.\n" );
$this->doQuery( array( $userId ) ); // cache miss
}
+
return isset( $this->cache[$userId][$prop] )
? $this->cache[$userId][$prop]
: false; // user does not exist?
@@ -61,8 +64,9 @@ class UserCache {
/**
* Get the name of a user or return $ip if the user ID is 0
*
- * @param integer $userId
+ * @param int $userId
* @param string $ip
+ * @return string
* @since 1.22
*/
public function getUserName( $userId, $ip ) {
@@ -73,7 +77,7 @@ class UserCache {
* Preloads user names for given list of users.
* @param array $userIds List of user IDs
* @param array $options Option flags; include 'userpage' and 'usertalk'
- * @param string $caller the calling method
+ * @param string $caller The calling method
*/
public function doQuery( array $userIds, $options = array(), $caller = '' ) {
wfProfileIn( __METHOD__ );
@@ -136,7 +140,7 @@ class UserCache {
/**
* Check if a cache type is in $options and was not loaded for this user
*
- * @param $uid integer user ID
+ * @param int $uid User ID
* @param string $type Cache type
* @param array $options Requested cache types
* @return bool
diff --git a/includes/cache/bloom/BloomCache.php b/includes/cache/bloom/BloomCache.php
new file mode 100644
index 00000000..236db954
--- /dev/null
+++ b/includes/cache/bloom/BloomCache.php
@@ -0,0 +1,323 @@
+<?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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Persistent bloom filter used to avoid expensive lookups
+ *
+ * @since 1.24
+ */
+abstract class BloomCache {
+ /** @var string Unique ID for key namespacing */
+ protected $cacheID;
+
+ /** @var array Map of (id => BloomCache) */
+ protected static $instances = array();
+
+ /**
+ * @param string $id
+ * @return BloomCache
+ */
+ final public static function get( $id ) {
+ global $wgBloomFilterStores;
+
+ if ( !isset( self::$instances[$id] ) ) {
+ if ( isset( $wgBloomFilterStores[$id] ) ) {
+ $class = $wgBloomFilterStores[$id]['class'];
+ self::$instances[$id] = new $class( $wgBloomFilterStores[$id] );
+ } else {
+ wfDebug( "No bloom filter store '$id'; using EmptyBloomCache." );
+ return new EmptyBloomCache( array() );
+ }
+ }
+
+ return self::$instances[$id];
+ }
+
+ /**
+ * Create a new bloom cache instance from configuration.
+ * This should only be called from within BloomCache.
+ *
+ * @param array $config Parameters include:
+ * - cacheID : Prefix to all bloom filter names that is unique to this cache.
+ * It should only consist of alphanumberic, '-', and '_' characters.
+ * This ID is what avoids collisions if multiple logical caches
+ * use the same storage system, so this should be set carefully.
+ */
+ public function __construct( array $config ) {
+ $this->cacheID = $config['cacheId'];
+ if ( !preg_match( '!^[a-zA-Z0-9-_]{1,32}$!', $this->cacheID ) ) {
+ throw new MWException( "Cache ID '{$this->cacheID}' is invalid." );
+ }
+ }
+
+ /**
+ * Check if a member is set in the bloom filter
+ *
+ * A member being set means that it *might* have been added.
+ * A member not being set means it *could not* have been added.
+ *
+ * This abstracts over isHit() to deal with filter updates and readiness.
+ * A class must exist with the name BloomFilter<type> and a static public
+ * mergeAndCheck() method. The later takes the following arguments:
+ * (BloomCache $bcache, $domain, $virtualKey, array $status)
+ * The method should return a bool indicating whether to use the filter.
+ *
+ * The 'shared' bloom key must be used for any updates and will be used
+ * for the membership check if the method returns true. Since the key is shared,
+ * the method should never use delete(). The filter cannot be used in cases where
+ * membership in the filter needs to be taken away. In such cases, code *cannot*
+ * use this method - instead, it can directly use the other BloomCache methods
+ * to manage custom filters with their own keys (e.g. not 'shared').
+ *
+ * @param string $domain
+ * @param string $type
+ * @param string $member
+ * @return bool True if set, false if not (also returns true on error)
+ */
+ final public function check( $domain, $type, $member ) {
+ $section = new ProfileSection( get_class( $this ) . '::' . __FUNCTION__ );
+
+ if ( method_exists( "BloomFilter{$type}", 'mergeAndCheck' ) ) {
+ try {
+ $virtualKey = "$domain:$type";
+
+ $status = $this->getStatus( $virtualKey );
+ if ( $status == false ) {
+ wfDebug( "Could not query virtual bloom filter '$virtualKey'." );
+ return null;
+ }
+
+ $useFilter = call_user_func_array(
+ array( "BloomFilter{$type}", 'mergeAndCheck' ),
+ array( $this, $domain, $virtualKey, $status )
+ );
+
+ if ( $useFilter ) {
+ return ( $this->isHit( 'shared', "$virtualKey:$member" ) !== false );
+ }
+ } catch ( MWException $e ) {
+ MWExceptionHandler::logException( $e );
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Inform the bloom filter of a new member in order to keep it up to date
+ *
+ * @param string $domain
+ * @param string $type
+ * @param string|array $members
+ * @return bool Success
+ */
+ final public function insert( $domain, $type, $members ) {
+ $section = new ProfileSection( get_class( $this ) . '::' . __FUNCTION__ );
+
+ if ( method_exists( "BloomFilter{$type}", 'mergeAndCheck' ) ) {
+ try {
+ $virtualKey = "$domain:$type";
+ $prefixedMembers = array();
+ foreach ( (array)$members as $member ) {
+ $prefixedMembers[] = "$virtualKey:$member";
+ }
+
+ return $this->add( 'shared', $prefixedMembers );
+ } catch ( MWException $e ) {
+ MWExceptionHandler::logException( $e );
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a new bloom filter at $key (if one does not exist yet)
+ *
+ * @param string $key
+ * @param integer $size Bit length [default: 1000000]
+ * @param float $precision [default: .001]
+ * @return bool Success
+ */
+ final public function init( $key, $size = 1000000, $precision = .001 ) {
+ $section = new ProfileSection( get_class( $this ) . '::' . __FUNCTION__ );
+
+ return $this->doInit( "{$this->cacheID}:$key", $size, min( .1, $precision ) );
+ }
+
+ /**
+ * Add a member to the bloom filter at $key
+ *
+ * @param string $key
+ * @param string|array $members
+ * @return bool Success
+ */
+ final public function add( $key, $members ) {
+ $section = new ProfileSection( get_class( $this ) . '::' . __FUNCTION__ );
+
+ return $this->doAdd( "{$this->cacheID}:$key", (array)$members );
+ }
+
+ /**
+ * Check if a member is set in the bloom filter.
+ *
+ * A member being set means that it *might* have been added.
+ * A member not being set means it *could not* have been added.
+ *
+ * If this returns true, then the caller usually should do the
+ * expensive check (whatever that may be). It can be avoided otherwise.
+ *
+ * @param string $key
+ * @param string $member
+ * @return bool|null True if set, false if not, null on error
+ */
+ final public function isHit( $key, $member ) {
+ $section = new ProfileSection( get_class( $this ) . '::' . __FUNCTION__ );
+
+ return $this->doIsHit( "{$this->cacheID}:$key", $member );
+ }
+
+ /**
+ * Destroy a bloom filter at $key
+ *
+ * @param string $key
+ * @return bool Success
+ */
+ final public function delete( $key ) {
+ $section = new ProfileSection( get_class( $this ) . '::' . __FUNCTION__ );
+
+ return $this->doDelete( "{$this->cacheID}:$key" );
+ }
+
+ /**
+ * Set the status map of the virtual bloom filter at $key
+ *
+ * @param string $virtualKey
+ * @param array $values Map including some of (lastID, asOfTime, epoch)
+ * @return bool Success
+ */
+ final public function setStatus( $virtualKey, array $values ) {
+ $section = new ProfileSection( get_class( $this ) . '::' . __FUNCTION__ );
+
+ return $this->doSetStatus( "{$this->cacheID}:$virtualKey", $values );
+ }
+
+ /**
+ * Get the status map of the virtual bloom filter at $key
+ *
+ * The map includes:
+ * - lastID : the highest ID of the items merged in
+ * - asOfTime : UNIX timestamp that the filter is up-to-date as of
+ * - epoch : UNIX timestamp that filter started being populated
+ * Unset fields will have a null value.
+ *
+ * @param string $virtualKey
+ * @return array|bool False on failure
+ */
+ final public function getStatus( $virtualKey ) {
+ $section = new ProfileSection( get_class( $this ) . '::' . __FUNCTION__ );
+
+ return $this->doGetStatus( "{$this->cacheID}:$virtualKey" );
+ }
+
+ /**
+ * Get an exclusive lock on a filter for updates
+ *
+ * @param string $virtualKey
+ * @return ScopedCallback|ScopedLock|null Returns null if acquisition failed
+ */
+ public function getScopedLock( $virtualKey ) {
+ return null;
+ }
+
+ /**
+ * @param string $key
+ * @param integer $size Bit length
+ * @param float $precision
+ * @return bool Success
+ */
+ abstract protected function doInit( $key, $size, $precision );
+
+ /**
+ * @param string $key
+ * @param array $members
+ * @return bool Success
+ */
+ abstract protected function doAdd( $key, array $members );
+
+ /**
+ * @param string $key
+ * @param string $member
+ * @return bool|null
+ */
+ abstract protected function doIsHit( $key, $member );
+
+ /**
+ * @param string $key
+ * @return bool Success
+ */
+ abstract protected function doDelete( $key );
+
+ /**
+ * @param string $virtualKey
+ * @param array $values
+ * @return bool Success
+ */
+ abstract protected function doSetStatus( $virtualKey, array $values );
+
+ /**
+ * @param string $key
+ * @return array|bool
+ */
+ abstract protected function doGetStatus( $key );
+}
+
+class EmptyBloomCache extends BloomCache {
+ public function __construct( array $config ) {
+ parent::__construct( array( 'cacheId' => 'none' ) );
+ }
+
+ protected function doInit( $key, $size, $precision ) {
+ return true;
+ }
+
+ protected function doAdd( $key, array $members ) {
+ return true;
+ }
+
+ protected function doIsHit( $key, $member ) {
+ return true;
+ }
+
+ protected function doDelete( $key ) {
+ return true;
+ }
+
+ protected function doSetStatus( $virtualKey, array $values ) {
+ return true;
+ }
+
+ protected function doGetStatus( $virtualKey ) {
+ return array( 'lastID' => null, 'asOfTime' => null, 'epoch' => null ) ;
+ }
+}
diff --git a/includes/cache/bloom/BloomCacheRedis.php b/includes/cache/bloom/BloomCacheRedis.php
new file mode 100644
index 00000000..212e5e8b
--- /dev/null
+++ b/includes/cache/bloom/BloomCacheRedis.php
@@ -0,0 +1,370 @@
+<?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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Bloom filter implemented using Redis
+ *
+ * The Redis server must be >= 2.6 and should have volatile-lru or volatile-ttl
+ * if there is any eviction policy. It should not be allkeys-* in any case. Also,
+ * this can be used in a simple master/slave setup or with Redis Sentinel preferably.
+ *
+ * Some bits are based on https://github.com/ErikDubbelboer/redis-lua-scaling-bloom-filter
+ * but are simplified to use a single filter instead of up to 32 filters.
+ *
+ * @since 1.24
+ */
+class BloomCacheRedis extends BloomCache {
+ /** @var RedisConnectionPool */
+ protected $redisPool;
+ /** @var RedisLockManager */
+ protected $lockMgr;
+ /** @var array */
+ protected $servers;
+ /** @var integer Federate each filter into this many redis bitfield objects */
+ protected $segments = 128;
+
+ /**
+ * @params include:
+ * - redisServers : list of servers (address:<port>) (the first is the master)
+ * - redisConf : additional redis configuration
+ *
+ * @param array $config
+ */
+ public function __construct( array $config ) {
+ parent::__construct( $config );
+
+ $redisConf = $config['redisConfig'];
+ $redisConf['serializer'] = 'none'; // manage that in this class
+ $this->redisPool = RedisConnectionPool::singleton( $redisConf );
+ $this->servers = $config['redisServers'];
+ $this->lockMgr = new RedisLockManager( array(
+ 'lockServers' => array( 'srv1' => $this->servers[0] ),
+ 'srvsByBucket' => array( 0 => array( 'srv1' ) ),
+ 'redisConfig' => $config['redisConfig']
+ ) );
+ }
+
+ protected function doInit( $key, $size, $precision ) {
+ $conn = $this->getConnection( 'master' );
+ if ( !$conn ) {
+ return false;
+ }
+
+ // 80000000 items at p = .001 take up 500MB and fit into one value.
+ // Do not hit the 512MB redis value limit by reducing the demands.
+ $size = min( $size, 80000000 * $this->segments );
+ $precision = max( round( $precision, 3 ), .001 );
+ $epoch = microtime( true );
+
+ static $script =
+<<<LUA
+ local kMetadata, kData = unpack(KEYS)
+ local aEntries, aPrec, aEpoch = unpack(ARGV)
+ if redis.call('EXISTS',kMetadata) == 0 or redis.call('EXISTS',kData) == 0 then
+ redis.call('DEL',kMetadata)
+ redis.call('HSET',kMetadata,'entries',aEntries)
+ redis.call('HSET',kMetadata,'precision',aPrec)
+ redis.call('HSET',kMetadata,'epoch',aEpoch)
+ redis.call('SET',kData,'')
+ return 1
+ end
+ return 0
+LUA;
+
+ $res = false;
+ try {
+ $conn->script( 'load', $script );
+ $conn->multi( Redis::MULTI );
+ for ( $i = 0; $i < $this->segments; ++$i ) {
+ $res = $conn->luaEval( $script,
+ array(
+ "$key:$i:bloom-metadata", # KEYS[1]
+ "$key:$i:bloom-data", # KEYS[2]
+ ceil( $size / $this->segments ), # ARGV[1]
+ $precision, # ARGV[2]
+ $epoch # ARGV[3]
+ ),
+ 2 # number of first argument(s) that are keys
+ );
+ }
+ $results = $conn->exec();
+ $res = $results && !in_array( false, $results, true );
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ }
+
+ return ( $res !== false );
+ }
+
+ protected function doAdd( $key, array $members ) {
+ $conn = $this->getConnection( 'master' );
+ if ( !$conn ) {
+ return false;
+ }
+
+ static $script =
+<<<LUA
+ local kMetadata, kData = unpack(KEYS)
+ local aMember = unpack(ARGV)
+
+ -- Check if the filter was initialized
+ if redis.call('EXISTS',kMetadata) == 0 or redis.call('EXISTS',kData) == 0 then
+ return false
+ end
+
+ -- Initial expected entries and desired precision
+ local entries = 1*redis.call('HGET',kMetadata,'entries')
+ local precision = 1*redis.call('HGET',kMetadata,'precision')
+ local hash = redis.sha1hex(aMember)
+
+ -- Based on the math from: http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives
+ -- 0.480453013 = ln(2)^2
+ local bits = math.ceil((entries * math.log(precision)) / -0.480453013)
+
+ -- 0.693147180 = ln(2)
+ local k = math.floor(0.693147180 * bits / entries)
+
+ -- This uses a variation on:
+ -- 'Less Hashing, Same Performance: Building a Better Bloom Filter'
+ -- http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
+ local h = { }
+ h[0] = tonumber(string.sub(hash, 1, 8 ), 16)
+ h[1] = tonumber(string.sub(hash, 9, 16), 16)
+ h[2] = tonumber(string.sub(hash, 17, 24), 16)
+ h[3] = tonumber(string.sub(hash, 25, 32), 16)
+
+ for i=1, k do
+ local pos = (h[i % 2] + i * h[2 + (((i + (i % 2)) % 4) / 2)]) % bits
+ redis.call('SETBIT', kData, pos, 1)
+ end
+
+ return 1
+LUA;
+
+ $res = false;
+ try {
+ $conn->script( 'load', $script );
+ $conn->multi( Redis::PIPELINE );
+ foreach ( $members as $member ) {
+ $i = $this->getSegment( $member );
+ $conn->luaEval( $script,
+ array(
+ "$key:$i:bloom-metadata", # KEYS[1],
+ "$key:$i:bloom-data", # KEYS[2]
+ $member # ARGV[1]
+ ),
+ 2 # number of first argument(s) that are keys
+ );
+ }
+ $results = $conn->exec();
+ $res = $results && !in_array( false, $results, true );
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ }
+
+ if ( $res === false ) {
+ wfDebug( "Could not add to the '$key' bloom filter; it may be missing." );
+ }
+
+ return ( $res !== false );
+ }
+
+ protected function doSetStatus( $virtualKey, array $values ) {
+ $conn = $this->getConnection( 'master' );
+ if ( !$conn ) {
+ return null;
+ }
+
+ $res = false;
+ try {
+ $res = $conn->hMSet( "$virtualKey:filter-metadata", $values );
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ }
+
+ return ( $res !== false );
+ }
+
+ protected function doGetStatus( $virtualKey ) {
+ $conn = $this->getConnection( 'slave' );
+ if ( !$conn ) {
+ return false;
+ }
+
+ $res = false;
+ try {
+ $res = $conn->hGetAll( "$virtualKey:filter-metadata" );
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ }
+
+ if ( is_array( $res ) ) {
+ $res['lastID'] = isset( $res['lastID'] ) ? $res['lastID'] : null;
+ $res['asOfTime'] = isset( $res['asOfTime'] ) ? $res['asOfTime'] : null;
+ $res['epoch'] = isset( $res['epoch'] ) ? $res['epoch'] : null;
+ }
+
+ return $res;
+ }
+
+ protected function doIsHit( $key, $member ) {
+ $conn = $this->getConnection( 'slave' );
+ if ( !$conn ) {
+ return null;
+ }
+
+ static $script =
+<<<LUA
+ local kMetadata, kData = unpack(KEYS)
+ local aMember = unpack(ARGV)
+
+ -- Check if the filter was initialized
+ if redis.call('EXISTS',kMetadata) == 0 or redis.call('EXISTS',kData) == 0 then
+ return false
+ end
+
+ -- Initial expected entries and desired precision.
+ -- This determines the size of the first and subsequent filters.
+ local entries = redis.call('HGET',kMetadata,'entries')
+ local precision = redis.call('HGET',kMetadata,'precision')
+ local hash = redis.sha1hex(aMember)
+
+ -- This uses a variation on:
+ -- 'Less Hashing, Same Performance: Building a Better Bloom Filter'
+ -- http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
+ local h = { }
+ h[0] = tonumber(string.sub(hash, 1, 8 ), 16)
+ h[1] = tonumber(string.sub(hash, 9, 16), 16)
+ h[2] = tonumber(string.sub(hash, 17, 24), 16)
+ h[3] = tonumber(string.sub(hash, 25, 32), 16)
+
+ -- 0.480453013 = ln(2)^2
+ local bits = math.ceil((entries * math.log(precision)) / -0.480453013)
+
+ -- 0.693147180 = ln(2)
+ local k = math.floor(0.693147180 * bits / entries)
+
+ local found = 1
+ for i=1, k do
+ local pos = (h[i % 2] + i * h[2 + (((i + (i % 2)) % 4) / 2)]) % bits
+ if redis.call('GETBIT', kData, pos) == 0 then
+ found = 0
+ break
+ end
+ end
+
+ return found
+LUA;
+
+ $res = null;
+ try {
+ $i = $this->getSegment( $member );
+ $res = $conn->luaEval( $script,
+ array(
+ "$key:$i:bloom-metadata", # KEYS[1],
+ "$key:$i:bloom-data", # KEYS[2]
+ $member # ARGV[1]
+ ),
+ 2 # number of first argument(s) that are keys
+ );
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ }
+
+ return is_int( $res ) ? (bool)$res : null;
+ }
+
+ protected function doDelete( $key ) {
+ $conn = $this->getConnection( 'master' );
+ if ( !$conn ) {
+ return false;
+ }
+
+ $res = false;
+ try {
+ $keys = array();
+ for ( $i = 0; $i < $this->segments; ++$i ) {
+ $keys[] = "$key:$i:bloom-metadata";
+ $keys[] = "$key:$i:bloom-data";
+ }
+ $res = $conn->delete( $keys );
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ }
+
+ return ( $res !== false );
+ }
+
+ public function getScopedLock( $virtualKey ) {
+ $status = Status::newGood();
+ return ScopedLock::factory( $this->lockMgr,
+ array( $virtualKey ), LockManager::LOCK_EX, $status );
+ }
+
+ /**
+ * @param string $member
+ * @return integer
+ */
+ protected function getSegment( $member ) {
+ return hexdec( substr( md5( $member ), 0, 2 ) ) % $this->segments;
+ }
+
+ /**
+ * $param string $to (master/slave)
+ * @return RedisConnRef|bool Returns false on failure
+ */
+ protected function getConnection( $to ) {
+ if ( $to === 'master' ) {
+ $conn = $this->redisPool->getConnection( $this->servers[0] );
+ } else {
+ static $lastServer = null;
+
+ $conn = false;
+ if ( $lastServer ) {
+ $conn = $this->redisPool->getConnection( $lastServer );
+ if ( $conn ) {
+ return $conn; // reuse connection
+ }
+ }
+ $servers = $this->servers;
+ $attempts = min( 3, count( $servers ) );
+ for ( $i = 1; $i <= $attempts; ++$i ) {
+ $index = mt_rand( 0, count( $servers ) - 1 );
+ $conn = $this->redisPool->getConnection( $servers[$index] );
+ if ( $conn ) {
+ $lastServer = $servers[$index];
+ return $conn;
+ }
+ unset( $servers[$index] ); // skip next time
+ }
+ }
+
+ return $conn;
+ }
+
+ /**
+ * @param RedisConnRef $conn
+ * @param Exception $e
+ */
+ protected function handleException( RedisConnRef $conn, $e ) {
+ $this->redisPool->handleError( $conn, $e );
+ }
+}
diff --git a/includes/cache/bloom/BloomFilters.php b/includes/cache/bloom/BloomFilters.php
new file mode 100644
index 00000000..9b710d79
--- /dev/null
+++ b/includes/cache/bloom/BloomFilters.php
@@ -0,0 +1,79 @@
+<?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
+ * @author Aaron Schulz
+ */
+
+/**
+ * @since 1.24
+ */
+class BloomFilterTitleHasLogs {
+ public static function mergeAndCheck(
+ BloomCache $bcache, $domain, $virtualKey, array $status
+ ) {
+ $age = microtime( true ) - $status['asOfTime']; // seconds
+ $scopedLock = ( mt_rand( 1, (int)pow( 3, max( 0, 5 - $age ) ) ) == 1 )
+ ? $bcache->getScopedLock( $virtualKey )
+ : false;
+
+ if ( $scopedLock ) {
+ $updates = self::merge( $bcache, $domain, $virtualKey, $status );
+ if ( isset( $updates['asOfTime'] ) ) {
+ $age = ( microtime( true ) - $updates['asOfTime'] );
+ }
+ }
+
+ return ( $age < 30 );
+ }
+
+ public static function merge(
+ BloomCache $bcache, $domain, $virtualKey, array $status
+ ) {
+ $limit = 1000;
+ $dbr = wfGetDB( DB_SLAVE, array(), $domain );
+ $res = $dbr->select( 'logging',
+ array( 'log_namespace', 'log_title', 'log_id', 'log_timestamp' ),
+ array( 'log_id > ' . $dbr->addQuotes( (int)$status['lastID'] ) ),
+ __METHOD__,
+ array( 'ORDER BY' => 'log_id', 'LIMIT' => $limit )
+ );
+
+ $updates = array();
+ if ( $res->numRows() > 0 ) {
+ $members = array();
+ foreach ( $res as $row ) {
+ $members[] = "$virtualKey:{$row->log_namespace}:{$row->log_title}";
+ }
+ $lastID = $row->log_id;
+ $lastTime = $row->log_timestamp;
+ if ( !$bcache->add( 'shared', $members ) ) {
+ return false;
+ }
+ $updates['lastID'] = $lastID;
+ $updates['asOfTime'] = wfTimestamp( TS_UNIX, $lastTime );
+ } else {
+ $updates['asOfTime'] = microtime( true );
+ }
+
+ $updates['epoch'] = $status['epoch'] ?: microtime( true );
+
+ $bcache->setStatus( $virtualKey, $updates );
+
+ return $updates;
+ }
+}
diff --git a/includes/ChangesFeed.php b/includes/changes/ChangesFeed.php
index 0736c507..2d3b919d 100644
--- a/includes/ChangesFeed.php
+++ b/includes/changes/ChangesFeed.php
@@ -31,8 +31,8 @@ class ChangesFeed {
/**
* Constructor
*
- * @param string $format feed's format (either 'rss' or 'atom')
- * @param string $type type of feed (for cache keys)
+ * @param string $format Feed's format (either 'rss' or 'atom')
+ * @param string $type Type of feed (for cache keys)
*/
public function __construct( $format, $type ) {
$this->format = $format;
@@ -42,10 +42,10 @@ class ChangesFeed {
/**
* Get a ChannelFeed subclass object to use
*
- * @param string $title feed's title
- * @param string $description feed's description
- * @param string $url url of origin page
- * @return ChannelFeed subclass or false on failure
+ * @param string $title Feed's title
+ * @param string $description Feed's description
+ * @param string $url Url of origin page
+ * @return ChannelFeed|bool ChannelFeed subclass or false on failure
*/
public function getFeedObject( $title, $description, $url ) {
global $wgSitename, $wgLanguageCode, $wgFeedClasses;
@@ -67,10 +67,12 @@ class ChangesFeed {
/**
* Generates feed's content
*
- * @param $feed ChannelFeed subclass object (generally the one returned by getFeedObject())
- * @param $rows ResultWrapper object with rows in recentchanges table
- * @param $lastmod Integer: timestamp of the last item in the recentchanges table (only used for the cache key)
- * @param $opts FormOptions as in SpecialRecentChanges::getDefaultOptions()
+ * @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 ) {
@@ -110,9 +112,9 @@ class ChangesFeed {
/**
* Save to feed result to $messageMemc
*
- * @param string $feed feed's content
- * @param string $timekey memcached key of the last modification
- * @param string $key memcached key of the content
+ * @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 ) {
global $messageMemc;
@@ -124,10 +126,10 @@ class ChangesFeed {
/**
* Try to load the feed result from $messageMemc
*
- * @param $lastmod Integer: 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
+ * @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, $messageMemc;
@@ -160,14 +162,29 @@ class ChangesFeed {
}
/**
- * Generate the feed items given a row from the database.
- * @param $rows DatabaseBase resource with recentchanges rows
- * @param $feed Feed object
+ * Generate the feed items given a row from the database, printing the feed.
+ * @param object $rows DatabaseBase resource with recentchanges rows
+ * @param Feed $feed
*/
public static function generateFeed( $rows, &$feed ) {
wfProfileIn( __METHOD__ );
-
+ $items = self::buildItems( $rows );
$feed->outHeader();
+ foreach ( $items as $item ) {
+ $feed->outItem( $item );
+ }
+ $feed->outFooter();
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Generate the feed items given a row from the database.
+ * @param object $rows DatabaseBase resource with recentchanges rows
+ * @return array
+ */
+ public static function buildItems( $rows ) {
+ wfProfileIn( __METHOD__ );
+ $items = array();
# Merge adjacent edits by one user
$sorted = array();
@@ -187,7 +204,10 @@ class ChangesFeed {
foreach ( $sorted as $obj ) {
$title = Title::makeTitle( $obj->rc_namespace, $obj->rc_title );
- $talkpage = MWNamespace::canTalk( $obj->rc_namespace ) ? $title->getTalkPage()->getFullURL() : '';
+ $talkpage = MWNamespace::canTalk( $obj->rc_namespace )
+ ? $title->getTalkPage()->getFullURL()
+ : '';
+
// Skip items with deleted content (avoids partially complete/inconsistent output)
if ( $obj->rc_deleted ) {
continue;
@@ -203,7 +223,7 @@ class ChangesFeed {
$url = $title->getFullURL();
}
- $item = new FeedItem(
+ $items[] = new FeedItem(
$title->getPrefixedText(),
FeedUtils::formatDiff( $obj ),
$url,
@@ -212,10 +232,9 @@ class ChangesFeed {
? wfMessage( 'rev-deleted-user' )->escaped() : $obj->rc_user_text,
$talkpage
);
- $feed->outItem( $item );
}
- $feed->outFooter();
+
wfProfileOut( __METHOD__ );
+ return $items;
}
-
}
diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php
index bf800c46..03d1289f 100644
--- a/includes/changes/ChangesList.php
+++ b/includes/changes/ChangesList.php
@@ -23,20 +23,26 @@
*/
class ChangesList extends ContextSource {
-
/**
* @var Skin
*/
public $skin;
protected $watchlist = false;
-
+ protected $lastdate;
protected $message;
+ protected $rc_cache;
+ protected $rcCacheIndex;
+ protected $rclistOpen;
+ protected $rcMoveIndex;
+
+ /** @var MapCacheLRU */
+ protected $watchingCache;
/**
* Changeslist constructor
*
- * @param $obj Skin or IContextSource
+ * @param Skin|IContextSource $obj
*/
public function __construct( $obj ) {
if ( $obj instanceof IContextSource ) {
@@ -47,27 +53,15 @@ class ChangesList extends ContextSource {
$this->skin = $obj;
}
$this->preCacheMessages();
- }
-
- /**
- * Fetch an appropriate changes list class for the main context
- * This first argument used to be an User object.
- *
- * @deprecated in 1.18; use newFromContext() instead
- * @param string|User $unused Unused
- * @return ChangesList|EnhancedChangesList|OldChangesList derivative
- */
- public static function newFromUser( $unused ) {
- wfDeprecated( __METHOD__, '1.18' );
- return self::newFromContext( RequestContext::getMain() );
+ $this->watchingCache = new MapCacheLRU( 50 );
}
/**
* Fetch an appropriate changes list class for the specified context
* Some users might want to use an enhanced list format, for instance
*
- * @param $context IContextSource to use
- * @return ChangesList|EnhancedChangesList|OldChangesList derivative
+ * @param IContextSource $context
+ * @return ChangesList
*/
public static function newFromContext( IContextSource $context ) {
$user = $context->getUser();
@@ -75,6 +69,7 @@ class ChangesList extends ContextSource {
$list = null;
if ( wfRunHooks( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) {
$new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
+
return $new ? new EnhancedChangesList( $context ) : new OldChangesList( $context );
} else {
return $list;
@@ -83,13 +78,21 @@ class ChangesList extends ContextSource {
/**
* Sets the list to use a "<li class='watchlist-(namespace)-(page)'>" tag
- * @param $value Boolean
+ * @param bool $value
*/
public function setWatchlistDivs( $value = true ) {
$this->watchlist = $value;
}
/**
+ * @return bool True when setWatchlistDivs has been called
+ * @since 1.23
+ */
+ public function isWatchlist() {
+ return (bool)$this->watchlist;
+ }
+
+ /**
* As we use the same small set of messages in various methods and that
* they are called often, we call them once and save them in $this->message
*/
@@ -107,17 +110,17 @@ class ChangesList extends ContextSource {
/**
* Returns the appropriate flags for new page, minor change and patrolling
* @param array $flags Associative array of 'flag' => Bool
- * @param string $nothing to use for empty space
- * @return String
+ * @param string $nothing To use for empty space
+ * @return string
*/
public function recentChangesFlags( $flags, $nothing = '&#160;' ) {
- global $wgRecentChangesFlags;
$f = '';
- foreach ( array_keys( $wgRecentChangesFlags ) as $flag ) {
+ foreach ( array_keys( $this->getConfig()->get( 'RecentChangesFlags' ) ) as $flag ) {
$f .= isset( $flags[$flag] ) && $flags[$flag]
? self::flag( $flag )
: $nothing;
}
+
return $f;
}
@@ -128,7 +131,7 @@ class ChangesList extends ContextSource {
* "!" respectively, plus it will have an appropriate title and class.
*
* @param string $flag One key of $wgRecentChangesFlags
- * @return String: Raw HTML
+ * @return string Raw HTML
*/
public static function flag( $flag ) {
static $flagInfos = null;
@@ -153,14 +156,14 @@ class ChangesList extends ContextSource {
$flag = $map[$flag];
}
- return "<abbr class='" . $flagInfos[$flag]['class'] . "' title='" . $flagInfos[$flag]['title'] . "'>" .
- $flagInfos[$flag]['letter'] .
+ return "<abbr class='" . $flagInfos[$flag]['class'] . "' title='" .
+ $flagInfos[$flag]['title'] . "'>" . $flagInfos[$flag]['letter'] .
'</abbr>';
}
/**
* Returns text for the start of the tabular part of RC
- * @return String
+ * @return string
*/
public function beginRecentChangesList() {
$this->rc_cache = array();
@@ -169,19 +172,25 @@ class ChangesList extends ContextSource {
$this->lastdate = '';
$this->rclistOpen = false;
$this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' );
- return '';
+
+ return '<div class="mw-changeslist">';
+ }
+
+ /**
+ * @param ResultWrapper|array $rows
+ */
+ public function initChangesListRows( $rows ) {
+ wfRunHooks( 'ChangesListInitRows', array( $this, $rows ) );
}
/**
* Show formatted char difference
- * @param $old Integer: bytes
- * @param $new Integer: bytes
- * @param $context IContextSource context to use
- * @return String
+ * @param int $old Number of bytes
+ * @param int $new Number of bytes
+ * @param IContextSource $context
+ * @return string
*/
public static function showCharacterDifference( $old, $new, IContextSource $context = null ) {
- global $wgRCChangedSizeThreshold, $wgMiserMode;
-
if ( !$context ) {
$context = RequestContext::getMain();
}
@@ -191,10 +200,11 @@ class ChangesList extends ContextSource {
$szdiff = $new - $old;
$lang = $context->getLanguage();
+ $config = $context->getConfig();
$code = $lang->getCode();
static $fastCharDiff = array();
if ( !isset( $fastCharDiff[$code] ) ) {
- $fastCharDiff[$code] = $wgMiserMode || $context->msg( 'rc-change-size' )->plain() === '$1';
+ $fastCharDiff[$code] = $config->get( 'MiserMode' ) || $context->msg( 'rc-change-size' )->plain() === '$1';
}
$formattedSize = $lang->formatNum( $szdiff );
@@ -203,7 +213,7 @@ class ChangesList extends ContextSource {
$formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text();
}
- if ( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) {
+ if ( abs( $szdiff ) > abs( $config->get( 'RCChangedSizeThreshold' ) ) ) {
$tag = 'strong';
} else {
$tag = 'span';
@@ -211,12 +221,10 @@ class ChangesList extends ContextSource {
if ( $szdiff === 0 ) {
$formattedSizeClass = 'mw-plusminus-null';
- }
- if ( $szdiff > 0 ) {
+ } elseif ( $szdiff > 0 ) {
$formattedSize = '+' . $formattedSize;
$formattedSizeClass = 'mw-plusminus-pos';
- }
- if ( $szdiff < 0 ) {
+ } else {
$formattedSizeClass = 'mw-plusminus-neg';
}
@@ -230,8 +238,8 @@ class ChangesList extends ContextSource {
/**
* Format the character difference of one or several changes.
*
- * @param $old RecentChange
- * @param $new RecentChange last change to use, if not provided, $old will be used
+ * @param RecentChange $old
+ * @param RecentChange $new Last change to use, if not provided, $old will be used
* @return string HTML fragment
*/
public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) {
@@ -252,19 +260,18 @@ class ChangesList extends ContextSource {
/**
* Returns text for the end of RC
- * @return String
+ * @return string
*/
public function endRecentChangesList() {
- if ( $this->rclistOpen ) {
- return "</ul>\n";
- } else {
- return '';
- }
+ $out = $this->rclistOpen ? "</ul>\n" : '';
+ $out .= '</div>';
+
+ return $out;
}
/**
* @param string $s HTML to update
- * @param $rc_timestamp mixed
+ * @param mixed $rc_timestamp
*/
public function insertDateHeader( &$s, $rc_timestamp ) {
# Make date header if necessary
@@ -281,8 +288,8 @@ class ChangesList extends ContextSource {
/**
* @param string $s HTML to update
- * @param $title Title
- * @param $logtype string
+ * @param Title $title
+ * @param string $logtype
*/
public function insertLog( &$s, $title, $logtype ) {
$page = new LogPage( $logtype );
@@ -292,8 +299,8 @@ class ChangesList extends ContextSource {
/**
* @param string $s HTML to update
- * @param $rc RecentChange
- * @param $unpatrolled
+ * @param RecentChange $rc
+ * @param bool $unpatrolled
*/
public function insertDiffHist( &$s, &$rc, $unpatrolled ) {
# Diff link
@@ -326,17 +333,22 @@ class ChangesList extends ContextSource {
'action' => 'history'
)
);
- $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() . ' <span class="mw-changeslist-separator">. .</span> ';
+ // @todo FIXME: Hard coded ". .". Is there a message for this? Should there be?
+ $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() .
+ ' <span class="mw-changeslist-separator">. .</span> ';
}
/**
* @param string $s HTML to update
- * @param $rc RecentChange
- * @param $unpatrolled
- * @param $watched
+ * @param RecentChange $rc
+ * @param bool $unpatrolled
+ * @param bool $watched
*/
public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) {
$params = array();
+ if ( $rc->getTitle()->isRedirect() ) {
+ $params = array( 'redirect' => 'no' );
+ }
$articlelink = Linker::linkKnown(
$rc->getTitle(),
@@ -362,19 +374,23 @@ class ChangesList extends ContextSource {
* Get the timestamp from $rc formatted with current user's settings
* and a separator
*
- * @param $rc RecentChange
+ * @param RecentChange $rc
* @return string HTML fragment
*/
public function getTimestamp( $rc ) {
+ // @todo FIXME: Hard coded ". .". Is there a message for this? Should there be?
return $this->message['semicolon-separator'] . '<span class="mw-changeslist-date">' .
- $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() ) . '</span> <span class="mw-changeslist-separator">. .</span> ';
+ $this->getLanguage()->userTime(
+ $rc->mAttribs['rc_timestamp'],
+ $this->getUser()
+ ) . '</span> <span class="mw-changeslist-separator">. .</span> ';
}
/**
* Insert time timestamp string from $rc into $s
*
* @param string $s HTML to update
- * @param $rc RecentChange
+ * @param RecentChange $rc
*/
public function insertTimestamp( &$s, $rc ) {
$s .= $this->getTimestamp( $rc );
@@ -383,12 +399,13 @@ class ChangesList extends ContextSource {
/**
* Insert links to user page, user talk page and eventually a blocking link
*
- * @param &$s String HTML to update
- * @param &$rc RecentChange
+ * @param string &$s HTML to update
+ * @param RecentChange &$rc
*/
public function insertUserRelatedLinks( &$s, &$rc ) {
if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
- $s .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
+ $s .= ' <span class="history-deleted">' .
+ $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
} else {
$s .= $this->getLanguage()->getDirMark() . Linker::userLink( $rc->mAttribs['rc_user'],
$rc->mAttribs['rc_user_text'] );
@@ -399,7 +416,7 @@ class ChangesList extends ContextSource {
/**
* Insert a formatted action
*
- * @param $rc RecentChange
+ * @param RecentChange $rc
* @return string
*/
public function insertLogEntry( $rc ) {
@@ -407,30 +424,29 @@ class ChangesList extends ContextSource {
$formatter->setContext( $this->getContext() );
$formatter->setShowUserToolLinks( true );
$mark = $this->getLanguage()->getDirMark();
+
return $formatter->getActionText() . " $mark" . $formatter->getComment();
}
/**
* Insert a formatted comment
- * @param $rc RecentChange
+ * @param RecentChange $rc
* @return string
*/
public function insertComment( $rc ) {
- if ( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
- if ( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
- return ' <span class="history-deleted">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
- } else {
- return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
- }
+ if ( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
+ return ' <span class="history-deleted">' .
+ $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
+ } else {
+ return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
}
- return '';
}
/**
* Check whether to enable recent changes patrol features
*
* @deprecated since 1.22
- * @return Boolean
+ * @return bool
*/
public static function usePatrol() {
global $wgUser;
@@ -442,15 +458,18 @@ class ChangesList extends ContextSource {
/**
* Returns the string which indicates the number of watching users
+ * @param int $count Number of user watching a page
* @return string
*/
protected function numberofWatchingusers( $count ) {
- static $cache = array();
+ $cache = $this->watchingCache;
if ( $count > 0 ) {
- if ( !isset( $cache[$count] ) ) {
- $cache[$count] = $this->msg( 'number_of_watching_users_RCview' )->numParams( $count )->escaped();
+ if ( !$cache->has( $count ) ) {
+ $cache->set( $count, $this->msg( 'number_of_watching_users_RCview' )
+ ->numParams( $count )->escaped() );
}
- return $cache[$count];
+
+ return $cache->get( $count );
} else {
return '';
}
@@ -458,9 +477,9 @@ class ChangesList extends ContextSource {
/**
* Determine if said field of a revision is hidden
- * @param $rc RCCacheEntry
- * @param $field Integer: one of DELETED_* bitfield constants
- * @return Boolean
+ * @param RCCacheEntry|RecentChange $rc
+ * @param int $field One of DELETED_* bitfield constants
+ * @return bool
*/
public static function isDeleted( $rc, $field ) {
return ( $rc->mAttribs['rc_deleted'] & $field ) == $field;
@@ -469,10 +488,10 @@ class ChangesList extends ContextSource {
/**
* Determine if the current user is allowed to view a particular
* field of this revision, if it's marked as deleted.
- * @param $rc RCCacheEntry
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
- * @return Boolean
+ * @param RCCacheEntry|RecentChange $rc
+ * @param int $field
+ * @param User $user User object to check, or null to use $wgUser
+ * @return bool
*/
public static function userCan( $rc, $field, User $user = null ) {
if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
@@ -483,8 +502,8 @@ class ChangesList extends ContextSource {
}
/**
- * @param $link string
- * @param $watched bool
+ * @param string $link
+ * @param bool $watched
* @return string
*/
protected function maybeWatchedLink( $link, $watched = false ) {
@@ -497,16 +516,20 @@ class ChangesList extends ContextSource {
/** Inserts a rollback link
*
- * @param $s string
- * @param $rc RecentChange
+ * @param string $s
+ * @param RecentChange $rc
*/
public function insertRollback( &$s, &$rc ) {
- if ( $rc->mAttribs['rc_type'] == RC_EDIT && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) {
+ if ( $rc->mAttribs['rc_type'] == RC_EDIT
+ && $rc->mAttribs['rc_this_oldid']
+ && $rc->mAttribs['rc_cur_id']
+ ) {
$page = $rc->getTitle();
/** Check for rollback and edit permissions, disallow special pages, and only
- * show a link on the top-most revision */
- if ( $this->getUser()->isAllowed( 'rollback' ) && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] )
- {
+ * show a link on the top-most revision */
+ if ( $this->getUser()->isAllowed( 'rollback' )
+ && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid']
+ ) {
$rev = new Revision( array(
'title' => $page,
'id' => $rc->mAttribs['rc_this_oldid'],
@@ -520,16 +543,19 @@ class ChangesList extends ContextSource {
}
/**
- * @param $s string
- * @param $rc RecentChange
- * @param $classes
+ * @param string $s
+ * @param RecentChange $rc
+ * @param array $classes
*/
public function insertTags( &$s, &$rc, &$classes ) {
if ( empty( $rc->mAttribs['ts_tags'] ) ) {
return;
}
- list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' );
+ list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
+ $rc->mAttribs['ts_tags'],
+ 'changeslist'
+ );
$classes = array_merge( $classes, $newClasses );
$s .= ' ' . $tagSummary;
}
@@ -539,14 +565,32 @@ class ChangesList extends ContextSource {
}
protected function showAsUnpatrolled( RecentChange $rc ) {
- $unpatrolled = false;
- if ( !$rc->mAttribs['rc_patrolled'] ) {
- if ( $this->getUser()->useRCPatrol() ) {
- $unpatrolled = true;
- } elseif ( $this->getUser()->useNPPatrol() && $rc->mAttribs['rc_type'] == RC_NEW ) {
- $unpatrolled = true;
+ return self::isUnpatrolled( $rc, $this->getUser() );
+ }
+
+ /**
+ * @param object|RecentChange $rc Database row from recentchanges or a RecentChange object
+ * @param User $user
+ * @return bool
+ */
+ public static function isUnpatrolled( $rc, User $user ) {
+ if ( $rc instanceof RecentChange ) {
+ $isPatrolled = $rc->mAttribs['rc_patrolled'];
+ $rcType = $rc->mAttribs['rc_type'];
+ } else {
+ $isPatrolled = $rc->rc_patrolled;
+ $rcType = $rc->rc_type;
+ }
+
+ if ( !$isPatrolled ) {
+ if ( $user->useRCPatrol() ) {
+ return true;
+ }
+ if ( $user->useNPPatrol() && $rcType == RC_NEW ) {
+ return true;
}
}
- return $unpatrolled;
+
+ return false;
}
}
diff --git a/includes/changes/EnhancedChangesList.php b/includes/changes/EnhancedChangesList.php
index 4c8aa451..4ab77297 100644
--- a/includes/changes/EnhancedChangesList.php
+++ b/includes/changes/EnhancedChangesList.php
@@ -22,11 +22,44 @@
class EnhancedChangesList extends ChangesList {
+ /**
+ * @var RCCacheEntryFactory
+ */
+ protected $cacheEntryFactory;
+
+ /**
+ * @var array Array of array of RCCacheEntry
+ */
protected $rc_cache;
/**
+ * @param IContextSource|Skin $obj
+ */
+ public function __construct( $obj ) {
+ if ( $obj instanceof Skin ) {
+ // @todo: deprecate constructing with Skin
+ $context = $obj->getContext();
+ } else {
+ if ( !$obj instanceof IContextSource ) {
+ throw new MWException( 'EnhancedChangesList must be constructed with a '
+ . 'context source or skin.' );
+ }
+
+ $context = $obj;
+ }
+
+ parent::__construct( $context );
+
+ // message is set by the parent ChangesList class
+ $this->cacheEntryFactory = new RCCacheEntryFactory(
+ $context,
+ $this->message
+ );
+ }
+
+ /**
* Add the JavaScript file for enhanced changeslist
- * @return String
+ * @return string
*/
public function beginRecentChangesList() {
$this->rc_cache = array();
@@ -42,27 +75,29 @@ class EnhancedChangesList extends ChangesList {
'jquery.makeCollapsible',
'mediawiki.icon',
) );
- return '';
+
+ return '<div class="mw-changeslist">';
}
+
/**
* Format a line for enhanced recentchange (aka with javascript and block of lines).
*
- * @param $baseRC RecentChange
- * @param $watched bool
+ * @param RecentChange $baseRC
+ * @param bool $watched
*
* @return string
*/
public function recentChangesLine( &$baseRC, $watched = false ) {
wfProfileIn( __METHOD__ );
- # Create a specialised object
- $rc = RCCacheEntry::newFromParent( $baseRC );
+ $date = $this->getLanguage()->userDate(
+ $baseRC->mAttribs['rc_timestamp'],
+ $this->getUser()
+ );
- $curIdEq = array( 'curid' => $rc->mAttribs['rc_cur_id'] );
+ $ret = '';
# If it's a new day, add the headline and flush the cache
- $date = $this->getLanguage()->userDate( $rc->mAttribs['rc_timestamp'], $this->getUser() );
- $ret = '';
if ( $date != $this->lastdate ) {
# Process current cache
$ret = $this->recentChangesBlock();
@@ -71,128 +106,60 @@ class EnhancedChangesList extends ChangesList {
$this->lastdate = $date;
}
- # Should patrol-related stuff be shown?
- $rc->unpatrolled = $this->showAsUnpatrolled( $rc );
-
- $showdifflinks = true;
- # Make article link
- $type = $rc->mAttribs['rc_type'];
- $logType = $rc->mAttribs['rc_log_type'];
- // Page moves, very old style, not supported anymore
- if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
- // New unpatrolled pages
- } elseif ( $rc->unpatrolled && $type == RC_NEW ) {
- $clink = Linker::linkKnown( $rc->getTitle() );
- // Log entries
- } elseif ( $type == RC_LOG ) {
- if ( $logType ) {
- $logtitle = SpecialPage::getTitleFor( 'Log', $logType );
- $logpage = new LogPage( $logType );
- $logname = $logpage->getName()->escaped();
- $clink = $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logtitle, $logname ) )->escaped();
- } else {
- $clink = Linker::link( $rc->getTitle() );
- }
- $watched = false;
- // Log entries (old format) and special pages
- } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
- wfDebug( "Unexpected special page in recentchanges\n" );
- $clink = '';
- // Edits
- } else {
- $clink = Linker::linkKnown( $rc->getTitle() );
- }
+ $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $baseRC, $watched );
+ $this->addCacheEntry( $cacheEntry );
- # Don't show unusable diff links
- if ( !ChangesList::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
- $showdifflinks = false;
- }
+ wfProfileOut( __METHOD__ );
- $time = $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() );
- $rc->watched = $watched;
- $rc->link = $clink;
- $rc->timestamp = $time;
- $rc->numberofWatchingusers = $baseRC->numberofWatchingusers;
-
- # Make "cur" and "diff" links. Do not use link(), it is too slow if
- # called too many times (50% of CPU time on RecentChanges!).
- $thisOldid = $rc->mAttribs['rc_this_oldid'];
- $lastOldid = $rc->mAttribs['rc_last_oldid'];
-
- $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $thisOldid );
- $querydiff = $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid );
-
- if ( !$showdifflinks ) {
- $curLink = $this->message['cur'];
- $diffLink = $this->message['diff'];
- } elseif ( in_array( $type, array( RC_NEW, RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
- if ( $type != RC_NEW ) {
- $curLink = $this->message['cur'];
- } else {
- $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
- $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
- }
- $diffLink = $this->message['diff'];
- } else {
- $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querydiff ) );
- $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
- $diffLink = "<a href=\"$diffUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['diff']}</a>";
- $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
- }
+ return $ret;
+ }
- # Make "last" link
- if ( !$showdifflinks || !$lastOldid ) {
- $lastLink = $this->message['last'];
- } elseif ( in_array( $type, array( RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
- $lastLink = $this->message['last'];
- } else {
- $lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'],
- array(), $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid ) );
- }
+ /**
+ * Put accumulated information into the cache, for later display.
+ * Page moves go on their own line.
+ *
+ * @param RCCacheEntry $cacheEntry
+ */
+ protected function addCacheEntry( RCCacheEntry $cacheEntry ) {
+ $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry );
- # Make user links
- if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
- $rc->userlink = ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
- } else {
- $rc->userlink = Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
- $rc->usertalklink = Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ if ( !isset( $this->rc_cache[$cacheGroupingKey] ) ) {
+ $this->rc_cache[$cacheGroupingKey] = array();
}
- $rc->lastlink = $lastLink;
- $rc->curlink = $curLink;
- $rc->difflink = $diffLink;
-
- # Put accumulated information into the cache, for later display
- # Page moves go on their own line
- $title = $rc->getTitle();
- $secureName = $title->getPrefixedDBkey();
- if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
- # Use an @ character to prevent collision with page names
- $this->rc_cache['@@' . ( $this->rcMoveIndex++ )] = array( $rc );
- } else {
- # Logs are grouped by type
- if ( $type == RC_LOG ) {
- $secureName = SpecialPage::getTitleFor( 'Log', $logType )->getPrefixedDBkey();
- }
- if ( !isset( $this->rc_cache[$secureName] ) ) {
- $this->rc_cache[$secureName] = array();
- }
+ array_push( $this->rc_cache[$cacheGroupingKey], $cacheEntry );
+ }
- array_push( $this->rc_cache[$secureName], $rc );
- }
+ /**
+ * @todo use rc_source to group, if set; fallback to rc_type
+ *
+ * @param RCCacheEntry $cacheEntry
+ *
+ * @return string
+ */
+ protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) {
+ $title = $cacheEntry->getTitle();
+ $cacheGroupingKey = $title->getPrefixedDBkey();
- wfProfileOut( __METHOD__ );
+ $type = $cacheEntry->mAttribs['rc_type'];
- return $ret;
+ if ( $type == RC_LOG ) {
+ // Group by log type
+ $cacheGroupingKey = SpecialPage::getTitleFor(
+ 'Log',
+ $cacheEntry->mAttribs['rc_log_type']
+ )->getPrefixedDBkey();
+ }
+
+ return $cacheGroupingKey;
}
/**
* Enhanced RC group
+ * @param RCCacheEntry[] $block
* @return string
*/
protected function recentChangesBlockGroup( $block ) {
- global $wgRCShowChangedSize;
-
wfProfileIn( __METHOD__ );
# Add the namespace and title of the block as part of the class
@@ -203,7 +170,7 @@ class EnhancedChangesList extends ChangesList {
. $block[0]->mAttribs['rc_log_type'] );
} else {
$classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
- . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
+ . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
}
$classes[] = $block[0]->watched && $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched
? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
@@ -221,6 +188,8 @@ class EnhancedChangesList extends ChangesList {
# Some catalyst variables...
$namehidden = true;
$allLogs = true;
+ $oldid = '';
+ $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
foreach ( $block as $rcObj ) {
$oldid = $rcObj->mAttribs['rc_last_oldid'];
if ( $rcObj->mAttribs['rc_type'] == RC_NEW ) {
@@ -268,7 +237,9 @@ class EnhancedChangesList extends ChangesList {
$text = $userlink;
$text .= $this->getLanguage()->getDirMark();
if ( $count > 1 ) {
- $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->formatNum( $count ) . '×' )->escaped();
+ // @todo FIXME: Hardcoded '×'. Should be a message.
+ $formattedCount = $this->getLanguage()->formatNum( $count ) . '×';
+ $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
}
array_push( $users, $text );
}
@@ -278,7 +249,8 @@ class EnhancedChangesList extends ChangesList {
implode( $this->message['semicolon-separator'], $users )
)->escaped() . '</span>';
- $tl = '<span class="mw-collapsible-toggle mw-collapsible-arrow mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>';
+ $tl = '<span class="mw-collapsible-toggle mw-collapsible-arrow ' .
+ 'mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>';
$r .= "<td>$tl</td>";
# Main line
@@ -294,7 +266,8 @@ class EnhancedChangesList extends ChangesList {
# Article link
if ( $namehidden ) {
- $r .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-event' )->escaped() . '</span>';
+ $r .= ' <span class="history-deleted">' .
+ $this->msg( 'rev-deleted-event' )->escaped() . '</span>';
} elseif ( $allLogs ) {
$r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
} else {
@@ -316,6 +289,7 @@ class EnhancedChangesList extends ChangesList {
$sinceLast = 0;
$unvisitedOldid = null;
+ /** @var $rcObj RCCacheEntry */
foreach ( $block as $rcObj ) {
// Same logic as below inside main foreach
if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) {
@@ -331,6 +305,8 @@ class EnhancedChangesList extends ChangesList {
# Total change link
$r .= ' ';
$logtext = '';
+ /** @var $block0 RecentChange */
+ $block0 = $block[0];
if ( !$allLogs ) {
if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
$logtext .= $nchanges[$n];
@@ -338,7 +314,7 @@ class EnhancedChangesList extends ChangesList {
$logtext .= $nchanges[$n];
} else {
$logtext .= Linker::link(
- $block[0]->getTitle(),
+ $block0->getTitle(),
$nchanges[$n],
array(),
$queryParams + array(
@@ -349,7 +325,7 @@ class EnhancedChangesList extends ChangesList {
);
if ( $sinceLast > 0 && $sinceLast < $n ) {
$logtext .= $this->message['pipe-separator'] . Linker::link(
- $block[0]->getTitle(),
+ $block0->getTitle(),
$sinceLastVisitMsg[$sinceLast],
array(),
$queryParams + array(
@@ -365,7 +341,7 @@ class EnhancedChangesList extends ChangesList {
# History
if ( $allLogs ) {
// don't show history link for logs
- } elseif ( $namehidden || !$block[0]->getTitle()->exists() ) {
+ } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
$logtext .= $this->message['pipe-separator'] . $this->message['enhancedrc-history'];
} else {
$params = $queryParams;
@@ -373,7 +349,7 @@ class EnhancedChangesList extends ChangesList {
$logtext .= $this->message['pipe-separator'] .
Linker::linkKnown(
- $block[0]->getTitle(),
+ $block0->getTitle(),
$this->message['enhancedrc-history'],
array(),
$params
@@ -387,7 +363,7 @@ class EnhancedChangesList extends ChangesList {
$r .= ' <span class="mw-changeslist-separator">. .</span> ';
# Character difference (does not apply if only log items)
- if ( $wgRCShowChangedSize && !$allLogs ) {
+ if ( $RCShowChangedSize && !$allLogs ) {
$last = 0;
$first = count( $block ) - 1;
# Some events (like logs) have an "empty" size, so we need to skip those...
@@ -408,7 +384,8 @@ class EnhancedChangesList extends ChangesList {
}
$r .= $users;
- $r .= $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
+ $r .= $this->numberofWatchingusers( $block0->numberofWatchingusers );
+ $r .= '</td></tr>';
# Sub-entries
foreach ( $block as $rcObj ) {
@@ -443,11 +420,11 @@ class EnhancedChangesList extends ChangesList {
} else {
$link = Linker::linkKnown(
- $rcObj->getTitle(),
- $rcObj->timestamp,
- array(),
- $params
- );
+ $rcObj->getTitle(),
+ $rcObj->timestamp,
+ array(),
+ $params
+ );
if ( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) {
$link = '<span class="history-deleted">' . $link . '</span> ';
}
@@ -455,12 +432,16 @@ class EnhancedChangesList extends ChangesList {
$r .= $link . '</span>';
if ( !$type == RC_LOG || $type == RC_NEW ) {
- $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->curlink . $this->message['pipe-separator'] . $rcObj->lastlink )->escaped();
+ $r .= ' ' . $this->msg( 'parentheses' )->rawParams(
+ $rcObj->curlink .
+ $this->message['pipe-separator'] .
+ $rcObj->lastlink
+ )->escaped();
}
$r .= ' <span class="mw-changeslist-separator">. .</span> ';
# Character diff
- if ( $wgRCShowChangedSize ) {
+ if ( $RCShowChangedSize ) {
$cd = $this->formatCharacterDifference( $rcObj );
if ( $cd !== '' ) {
$r .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
@@ -493,56 +474,12 @@ class EnhancedChangesList extends ChangesList {
}
/**
- * Generate HTML for an arrow or placeholder graphic
- * @param string $dir one of '', 'd', 'l', 'r'
- * @param string $alt text
- * @param string $title text
- * @return String: HTML "<img>" tag
- */
- protected function arrow( $dir, $alt = '', $title = '' ) {
- global $wgStylePath;
- $encUrl = htmlspecialchars( $wgStylePath . '/common/images/Arr_' . $dir . '.png' );
- $encAlt = htmlspecialchars( $alt );
- $encTitle = htmlspecialchars( $title );
- return "<img src=\"$encUrl\" width=\"12\" height=\"12\" alt=\"$encAlt\" title=\"$encTitle\" />";
- }
-
- /**
- * Generate HTML for a right- or left-facing arrow,
- * depending on language direction.
- * @return String: HTML "<img>" tag
- */
- protected function sideArrow() {
- $dir = $this->getLanguage()->isRTL() ? 'l' : 'r';
- return $this->arrow( $dir, '+', $this->msg( 'rc-enhanced-expand' )->text() );
- }
-
- /**
- * Generate HTML for a down-facing arrow
- * depending on language direction.
- * @return String: HTML "<img>" tag
- */
- protected function downArrow() {
- return $this->arrow( 'd', '-', $this->msg( 'rc-enhanced-hide' )->text() );
- }
-
- /**
- * Generate HTML for a spacer image
- * @return String: HTML "<img>" tag
- */
- protected function spacerArrow() {
- return $this->arrow( '', codepointToUtf8( 0xa0 ) ); // non-breaking space
- }
-
- /**
* Enhanced RC ungrouped line.
*
- * @param $rcObj RecentChange
- * @return String: a HTML formatted line (generated using $r)
+ * @param RecentChange|RCCacheEntry $rcObj
+ * @return string A HTML formatted line (generated using $r)
*/
protected function recentChangesBlockLine( $rcObj ) {
- global $wgRCShowChangedSize;
-
wfProfileIn( __METHOD__ );
$query['curid'] = $rcObj->mAttribs['rc_cur_id'];
@@ -551,10 +488,10 @@ class EnhancedChangesList extends ChangesList {
$classes = array( 'mw-enhanced-rc' );
if ( $logType ) {
# Log entry
-+ $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
+ $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
} else {
$classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
- $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
+ $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
}
$classes[] = $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
@@ -563,39 +500,37 @@ class EnhancedChangesList extends ChangesList {
$r .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>';
# Flag and Timestamp
- if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
- $r .= $this->recentChangesFlags( array() ); // no flags, but need the placeholders
- } else {
- $r .= $this->recentChangesFlags( array(
- 'newpage' => $type == RC_NEW,
- 'minor' => $rcObj->mAttribs['rc_minor'],
- 'unpatrolled' => $rcObj->unpatrolled,
- 'bot' => $rcObj->mAttribs['rc_bot'],
- ) );
- }
+ $r .= $this->recentChangesFlags( array(
+ 'newpage' => $type == RC_NEW,
+ 'minor' => $rcObj->mAttribs['rc_minor'],
+ 'unpatrolled' => $rcObj->unpatrolled,
+ 'bot' => $rcObj->mAttribs['rc_bot'],
+ ) );
$r .= '&#160;' . $rcObj->timestamp . '&#160;</td><td>';
# Article or log link
if ( $logType ) {
$logPage = new LogPage( $logType );
$logTitle = SpecialPage::getTitleFor( 'Log', $logType );
$logName = $logPage->getName()->escaped();
- $r .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped();
+ $r .= $this->msg( 'parentheses' )
+ ->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped();
} else {
$this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched );
}
# Diff and hist links
if ( $type != RC_LOG ) {
$query['action'] = 'history';
- $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown(
- $rcObj->getTitle(),
- $this->message['hist'],
- array(),
- $query
- ) )->escaped();
+ $r .= ' ' . $this->msg( 'parentheses' )
+ ->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown(
+ $rcObj->getTitle(),
+ $this->message['hist'],
+ array(),
+ $query
+ ) )->escaped();
}
$r .= ' <span class="mw-changeslist-separator">. .</span> ';
# Character diff
- if ( $wgRCShowChangedSize ) {
+ if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
$cd = $this->formatCharacterDifference( $rcObj );
if ( $cd !== '' ) {
$r .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
@@ -629,7 +564,7 @@ class EnhancedChangesList extends ChangesList {
* @return string
*/
protected function recentChangesBlock() {
- if ( count ( $this->rc_cache ) == 0 ) {
+ if ( count( $this->rc_cache ) == 0 ) {
return '';
}
@@ -655,7 +590,6 @@ class EnhancedChangesList extends ChangesList {
* @return string
*/
public function endRecentChangesList() {
- return $this->recentChangesBlock() . parent::endRecentChangesList();
+ return $this->recentChangesBlock() . '</div>';
}
-
}
diff --git a/includes/changes/OldChangesList.php b/includes/changes/OldChangesList.php
index a7fe9342..4eed9262 100644
--- a/includes/changes/OldChangesList.php
+++ b/includes/changes/OldChangesList.php
@@ -19,28 +19,21 @@
*
* @file
*/
+
class OldChangesList extends ChangesList {
/**
* Format a line using the old system (aka without any javascript).
*
- * @param $rc RecentChange, passed by reference
+ * @param RecentChange $rc Passed by reference
* @param bool $watched (default false)
* @param int $linenumber (default null)
*
* @return string|bool
*/
public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
- global $wgRCShowChangedSize;
wfProfileIn( __METHOD__ );
- # Should patrol-related stuff be shown?
- $unpatrolled = $this->showAsUnpatrolled( $rc );
-
- $dateheader = ''; // $s now contains only <li>...</li>, for hooks' convenience.
- $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
-
- $s = '';
$classes = array();
// use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468)
if ( $linenumber ) {
@@ -56,23 +49,53 @@ class OldChangesList extends ChangesList {
$classes[] = $watched && $rc->mAttribs['rc_timestamp'] >= $watched
? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
- // Moved pages (very very old, not supported anymore)
- if ( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) {
- // Log entries
- } elseif ( $rc->mAttribs['rc_log_type'] ) {
+ $html = $this->formatChangeLine( $rc, $classes, $watched );
+
+ if ( $this->watchlist ) {
+ $classes[] = Sanitizer::escapeClass( 'watchlist-' .
+ $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
+ }
+
+ if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$html, $rc, &$classes ) ) ) {
+ wfProfileOut( __METHOD__ );
+
+ return false;
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ $dateheader = ''; // $html now contains only <li>...</li>, for hooks' convenience.
+ $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
+
+ return "$dateheader<li class=\"" . implode( ' ', $classes ) . "\">" . $html . "</li>\n";
+ }
+
+ /**
+ * @param RecentChange $rc
+ * @param string[] &$classes
+ * @param boolean $watched
+ *
+ * @return string
+ */
+ private function formatChangeLine( RecentChange $rc, array &$classes, $watched ) {
+ $html = '';
+
+ if ( $rc->mAttribs['rc_log_type'] ) {
$logtitle = SpecialPage::getTitleFor( 'Log', $rc->mAttribs['rc_log_type'] );
- $this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] );
+ $this->insertLog( $html, $logtitle, $rc->mAttribs['rc_log_type'] );
// Log entries (old format) or log targets, and special pages
} elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
- list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] );
+ list( $name, $htmlubpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] );
if ( $name == 'Log' ) {
- $this->insertLog( $s, $rc->getTitle(), $subpage );
+ $this->insertLog( $html, $rc->getTitle(), $htmlubpage );
}
// Regular entries
} else {
- $this->insertDiffHist( $s, $rc, $unpatrolled );
+ $unpatrolled = $this->showAsUnpatrolled( $rc );
+
+ $this->insertDiffHist( $html, $rc, $unpatrolled );
# M, N, b and ! (minor, new, bot and unpatrolled)
- $s .= $this->recentChangesFlags(
+ $html .= $this->recentChangesFlags(
array(
'newpage' => $rc->mAttribs['rc_type'] == RC_NEW,
'minor' => $rc->mAttribs['rc_minor'],
@@ -81,50 +104,40 @@ class OldChangesList extends ChangesList {
),
''
);
- $this->insertArticleLink( $s, $rc, $unpatrolled, $watched );
+ $this->insertArticleLink( $html, $rc, $unpatrolled, $watched );
}
# Edit/log timestamp
- $this->insertTimestamp( $s, $rc );
+ $this->insertTimestamp( $html, $rc );
# Bytes added or removed
- if ( $wgRCShowChangedSize ) {
+ if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
$cd = $this->formatCharacterDifference( $rc );
if ( $cd !== '' ) {
- $s .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
+ $html .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
}
}
if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
- $s .= $this->insertLogEntry( $rc );
+ $html .= $this->insertLogEntry( $rc );
} else {
# User tool links
- $this->insertUserRelatedLinks( $s, $rc );
+ $this->insertUserRelatedLinks( $html, $rc );
# LTR/RTL direction mark
- $s .= $this->getLanguage()->getDirMark();
- $s .= $this->insertComment( $rc );
+ $html .= $this->getLanguage()->getDirMark();
+ $html .= $this->insertComment( $rc );
}
# Tags
- $this->insertTags( $s, $rc, $classes );
+ $this->insertTags( $html, $rc, $classes );
# Rollback
- $this->insertRollback( $s, $rc );
+ $this->insertRollback( $html, $rc );
# For subclasses
- $this->insertExtra( $s, $rc, $classes );
+ $this->insertExtra( $html, $rc, $classes );
# How many users watch this page
if ( $rc->numberofWatchingusers > 0 ) {
- $s .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers );
+ $html .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers );
}
- if ( $this->watchlist ) {
- $classes[] = Sanitizer::escapeClass( 'watchlist-' . $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
- }
-
- if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc, &$classes ) ) ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- wfProfileOut( __METHOD__ );
- return "$dateheader<li class=\"" . implode( ' ', $classes ) . "\">" . $s . "</li>\n";
+ return $html;
}
}
diff --git a/includes/changes/RCCacheEntry.php b/includes/changes/RCCacheEntry.php
index 9aef3d30..d9cafbc3 100644
--- a/includes/changes/RCCacheEntry.php
+++ b/includes/changes/RCCacheEntry.php
@@ -17,19 +17,27 @@
*
* @file
*/
+
class RCCacheEntry extends RecentChange {
- var $secureName, $link;
- var $curlink, $difflink, $lastlink, $usertalklink, $versionlink;
- var $userlink, $timestamp, $watched;
+ public $curlink;
+ public $difflink;
+ public $lastlink;
+ public $link;
+ public $timestamp;
+ public $unpatrolled;
+ public $userlink;
+ public $usertalklink;
+ public $watched;
/**
- * @param $rc RecentChange
+ * @param RecentChange $rc
* @return RCCacheEntry
*/
static function newFromParent( $rc ) {
$rc2 = new RCCacheEntry;
$rc2->mAttribs = $rc->mAttribs;
$rc2->mExtra = $rc->mExtra;
+
return $rc2;
}
}
diff --git a/includes/changes/RCCacheEntryFactory.php b/includes/changes/RCCacheEntryFactory.php
new file mode 100644
index 00000000..c3fe183e
--- /dev/null
+++ b/includes/changes/RCCacheEntryFactory.php
@@ -0,0 +1,275 @@
+<?php
+/**
+ * Creates a RCCacheEntry from a RecentChange to use in EnhancedChangesList
+ *
+ * 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 RCCacheEntryFactory {
+
+ /* @var IContextSource */
+ private $context;
+
+ /* @var string[] */
+ private $messages;
+
+ /**
+ * @param IContextSource $context
+ * @param string[] $messages
+ */
+ public function __construct( IContextSource $context, $messages ) {
+ $this->context = $context;
+ $this->messages = $messages;
+ }
+
+ /**
+ * @param RecentChange $baseRC
+ * @param bool $watched
+ *
+ * @return RCCacheEntry
+ */
+ public function newFromRecentChange( RecentChange $baseRC, $watched ) {
+ $user = $this->context->getUser();
+ $counter = $baseRC->counter;
+
+ $cacheEntry = RCCacheEntry::newFromParent( $baseRC );
+
+ // Should patrol-related stuff be shown?
+ $cacheEntry->unpatrolled = ChangesList::isUnpatrolled( $baseRC, $user );
+
+ $cacheEntry->watched = $cacheEntry->mAttribs['rc_type'] == RC_LOG ? false : $watched;
+ $cacheEntry->numberofWatchingusers = $baseRC->numberofWatchingusers;
+
+ $cacheEntry->link = $this->buildCLink( $cacheEntry );
+ $cacheEntry->timestamp = $this->buildTimestamp( $cacheEntry );
+
+ // Make "cur" and "diff" links. Do not use link(), it is too slow if
+ // called too many times (50% of CPU time on RecentChanges!).
+ $showDiffLinks = $this->showDiffLinks( $cacheEntry, $user );
+
+ $cacheEntry->difflink = $this->buildDiffLink( $cacheEntry, $showDiffLinks, $counter );
+ $cacheEntry->curlink = $this->buildCurLink( $cacheEntry, $showDiffLinks, $counter );
+ $cacheEntry->lastlink = $this->buildLastLink( $cacheEntry, $showDiffLinks );
+
+ // Make user links
+ $cacheEntry->userlink = $this->getUserLink( $cacheEntry );
+
+ if ( !ChangesList::isDeleted( $cacheEntry, Revision::DELETED_USER ) ) {
+ $cacheEntry->usertalklink = Linker::userToolLinks(
+ $cacheEntry->mAttribs['rc_user'],
+ $cacheEntry->mAttribs['rc_user_text']
+ );
+ }
+
+ return $cacheEntry;
+ }
+
+ /**
+ * @param RecentChange $cacheEntry
+ * @param User $user
+ *
+ * @return bool
+ */
+ private function showDiffLinks( RecentChange $cacheEntry, User $user ) {
+ return ChangesList::userCan( $cacheEntry, Revision::DELETED_TEXT, $user );
+ }
+
+ /**
+ * @param RecentChange $cacheEntry
+ *
+ * @return string
+ */
+ private function buildCLink( RecentChange $cacheEntry ) {
+ $type = $cacheEntry->mAttribs['rc_type'];
+
+ // New unpatrolled pages
+ if ( $cacheEntry->unpatrolled && $type == RC_NEW ) {
+ $clink = Linker::linkKnown( $cacheEntry->getTitle() );
+ // Log entries
+ } elseif ( $type == RC_LOG ) {
+ $logType = $cacheEntry->mAttribs['rc_log_type'];
+
+ if ( $logType ) {
+ $clink = $this->getLogLink( $logType );
+ } else {
+ wfDebugLog( 'recentchanges', 'Unexpected log entry with no log type in recent changes' );
+ $clink = Linker::link( $cacheEntry->getTitle() );
+ }
+ // Log entries (old format) and special pages
+ } elseif ( $cacheEntry->mAttribs['rc_namespace'] == NS_SPECIAL ) {
+ wfDebugLog( 'recentchanges', 'Unexpected special page in recentchanges' );
+ $clink = '';
+ // Edits
+ } else {
+ $clink = Linker::linkKnown( $cacheEntry->getTitle() );
+ }
+
+ return $clink;
+ }
+
+ private function getLogLink( $logType ) {
+ $logtitle = SpecialPage::getTitleFor( 'Log', $logType );
+ $logpage = new LogPage( $logType );
+ $logname = $logpage->getName()->escaped();
+
+ $logLink = $this->context->msg( 'parentheses' )
+ ->rawParams( Linker::linkKnown( $logtitle, $logname ) )->escaped();
+
+ return $logLink;
+ }
+
+ /**
+ * @param RecentChange $cacheEntry
+ *
+ * @return string
+ */
+ private function buildTimestamp( RecentChange $cacheEntry ) {
+ return $this->context->getLanguage()->userTime(
+ $cacheEntry->mAttribs['rc_timestamp'],
+ $this->context->getUser()
+ );
+ }
+
+ /**
+ * @param RecentChange $recentChange
+ *
+ * @return array
+ */
+ private function buildCurQueryParams( RecentChange $recentChange ) {
+ return array(
+ 'curid' => $recentChange->mAttribs['rc_cur_id'],
+ 'diff' => 0,
+ 'oldid' => $recentChange->mAttribs['rc_this_oldid']
+ );
+ }
+
+ /**
+ * @param RecentChange $cacheEntry
+ * @param bool $showDiffLinks
+ * @param int $counter
+ *
+ * @return string
+ */
+ private function buildCurLink( RecentChange $cacheEntry, $showDiffLinks, $counter ) {
+ $queryParams = $this->buildCurQueryParams( $cacheEntry );
+ $curMessage = $this->getMessage( 'cur' );
+ $logTypes = array( RC_LOG );
+
+ if ( !$showDiffLinks || in_array( $cacheEntry->mAttribs['rc_type'], $logTypes ) ) {
+ $curLink = $curMessage;
+ } else {
+ $curUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $queryParams ) );
+ $curLink = "<a href=\"$curUrl\" tabindex=\"$counter\">$curMessage</a>";
+ }
+
+ return $curLink;
+ }
+
+ /**
+ * @param RecentChange $recentChange
+ *
+ * @return array
+ */
+ private function buildDiffQueryParams( RecentChange $recentChange ) {
+ return array(
+ 'curid' => $recentChange->mAttribs['rc_cur_id'],
+ 'diff' => $recentChange->mAttribs['rc_this_oldid'],
+ 'oldid' => $recentChange->mAttribs['rc_last_oldid']
+ );
+ }
+
+ /**
+ * @param RecentChange $cacheEntry
+ * @param bool $showDiffLinks
+ * @param int $counter
+ *
+ * @return string
+ */
+ private function buildDiffLink( RecentChange $cacheEntry, $showDiffLinks, $counter ) {
+ $queryParams = $this->buildDiffQueryParams( $cacheEntry );
+ $diffMessage = $this->getMessage( 'diff' );
+ $logTypes = array( RC_NEW, RC_LOG );
+
+ if ( !$showDiffLinks ) {
+ $diffLink = $diffMessage;
+ } elseif ( in_array( $cacheEntry->mAttribs['rc_type'], $logTypes ) ) {
+ $diffLink = $diffMessage;
+ } else {
+ $diffUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $queryParams ) );
+ $diffLink = "<a href=\"$diffUrl\" tabindex=\"$counter\">$diffMessage</a>";
+ }
+
+ return $diffLink;
+ }
+
+ /**
+ * @param RecentChange $cacheEntry
+ * @param bool $showDiffLinks
+ *
+ * @return string
+ */
+ private function buildLastLink( RecentChange $cacheEntry, $showDiffLinks ) {
+ $lastOldid = $cacheEntry->mAttribs['rc_last_oldid'];
+ $lastMessage = $this->getMessage( 'last' );
+ $type = $cacheEntry->mAttribs['rc_type'];
+ $logTypes = array( RC_LOG );
+
+ // Make "last" link
+ if ( !$showDiffLinks || !$lastOldid || in_array( $type, $logTypes ) ) {
+ $lastLink = $lastMessage;
+ } else {
+ $lastLink = Linker::linkKnown(
+ $cacheEntry->getTitle(),
+ $lastMessage,
+ array(),
+ $this->buildDiffQueryParams( $cacheEntry )
+ );
+ }
+
+ return $lastLink;
+ }
+
+ /**
+ * @param RecentChange $cacheEntry
+ *
+ * @return string
+ */
+ private function getUserLink( RecentChange $cacheEntry ) {
+ if ( ChangesList::isDeleted( $cacheEntry, Revision::DELETED_USER ) ) {
+ $userLink = ' <span class="history-deleted">' .
+ $this->context->msg( 'rev-deleted-user' )->escaped() . '</span>';
+ } else {
+ $userLink = Linker::userLink(
+ $cacheEntry->mAttribs['rc_user'],
+ $cacheEntry->mAttribs['rc_user_text']
+ );
+ }
+
+ return $userLink;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return string
+ */
+ private function getMessage( $key ) {
+ return $this->messages[$key];
+ }
+
+}
diff --git a/includes/changes/RecentChange.php b/includes/changes/RecentChange.php
index 980bd0a0..e33274e8 100644
--- a/includes/changes/RecentChange.php
+++ b/includes/changes/RecentChange.php
@@ -26,10 +26,10 @@
* mAttribs:
* rc_id id of the row in the recentchanges table
* rc_timestamp time the entry was made
- * rc_cur_time timestamp on the cur row
* rc_namespace namespace #
* rc_title non-prefixed db key
* rc_type is new entry, used to determine whether updating is necessary
+ * rc_source string representation of change source
* rc_minor is minor
* rc_cur_id page_id of associated page entry
* rc_user user id who made the entry
@@ -52,7 +52,6 @@
* mExtra:
* prefixedDBkey prefixed db key, used by external app via msg queue
* lastTimestamp timestamp of previous entry, used in WHERE clause during update
- * lang the interwiki prefix, automatically set in save()
* oldSize text size before the change
* newSize text size after the change
* pageStatus status of the page: created, deleted, moved, restored, changed
@@ -60,59 +59,111 @@
* temporary: not stored in the database
* notificationtimestamp
* numberofWatchingusers
- *
- * @todo document functions and variables
*/
class RecentChange {
- var $mAttribs = array(), $mExtra = array();
+ // Constants for the rc_source field. Extensions may also have
+ // their own source constants.
+ const SRC_EDIT = 'mw.edit';
+ const SRC_NEW = 'mw.new';
+ const SRC_LOG = 'mw.log';
+ const SRC_EXTERNAL = 'mw.external'; // obsolete
+
+ public $mAttribs = array();
+ public $mExtra = array();
/**
* @var Title
*/
- var $mTitle = false;
+ public $mTitle = false;
/**
* @var User
*/
private $mPerformer = false;
+ public $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentChangesLinked
+ public $notificationtimestamp;
+
/**
- * @var Title
+ * @var int Line number of recent change. Default -1.
*/
- var $mMovedToTitle = false;
- var $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentchangeslinked
- var $notificationtimestamp;
+ public $counter = -1;
# Factory methods
/**
- * @param $row
+ * @param mixed $row
* @return RecentChange
*/
public static function newFromRow( $row ) {
$rc = new RecentChange;
$rc->loadFromRow( $row );
+
return $rc;
}
/**
- * @deprecated in 1.22
- * @param $row
- * @return RecentChange
+ * Parsing text to RC_* constants
+ * @since 1.24
+ * @param string|array $type
+ * @throws MWException
+ * @return int|array RC_TYPE
*/
- public static function newFromCurRow( $row ) {
- wfDeprecated( __METHOD__, '1.22' );
- $rc = new RecentChange;
- $rc->loadFromCurRow( $row );
- $rc->notificationtimestamp = false;
- $rc->numberofWatchingusers = false;
- return $rc;
+ public static function parseToRCType( $type ) {
+ if ( is_array( $type ) ) {
+ $retval = array();
+ foreach ( $type as $t ) {
+ $retval[] = RecentChange::parseToRCType( $t );
+ }
+
+ return $retval;
+ }
+
+ switch ( $type ) {
+ case 'edit':
+ return RC_EDIT;
+ case 'new':
+ return RC_NEW;
+ case 'log':
+ return RC_LOG;
+ case 'external':
+ return RC_EXTERNAL;
+ default:
+ throw new MWException( "Unknown type '$type'" );
+ }
+ }
+
+ /**
+ * Parsing RC_* constants to human-readable test
+ * @since 1.24
+ * @param int $rcType
+ * @return string $type
+ */
+ public static function parseFromRCType( $rcType ) {
+ switch ( $rcType ) {
+ case RC_EDIT:
+ $type = 'edit';
+ break;
+ case RC_NEW:
+ $type = 'new';
+ break;
+ case RC_LOG:
+ $type = 'log';
+ break;
+ case RC_EXTERNAL:
+ $type = 'external';
+ break;
+ default:
+ $type = "$rcType";
+ }
+
+ return $type;
}
/**
* Obtain the recent change with a given rc_id value
*
- * @param int $rcid rc_id value to retrieve
+ * @param int $rcid The rc_id value to retrieve
* @return RecentChange
*/
public static function newFromId( $rcid ) {
@@ -122,9 +173,9 @@ class RecentChange {
/**
* Find the first recent change matching some specific conditions
*
- * @param array $conds of conditions
- * @param $fname Mixed: override the method name in profiling/logs
- * @param $options Array Query options
+ * @param array $conds Array of conditions
+ * @param mixed $fname Override the method name in profiling/logs
+ * @param array $options Query options
* @return RecentChange
*/
public static function newFromConds( $conds, $fname = __METHOD__, $options = array() ) {
@@ -146,7 +197,6 @@ class RecentChange {
return array(
'rc_id',
'rc_timestamp',
- 'rc_cur_time',
'rc_user',
'rc_user_text',
'rc_namespace',
@@ -159,6 +209,7 @@ class RecentChange {
'rc_this_oldid',
'rc_last_oldid',
'rc_type',
+ 'rc_source',
'rc_patrolled',
'rc_ip',
'rc_old_len',
@@ -174,27 +225,27 @@ class RecentChange {
# Accessors
/**
- * @param $attribs array
+ * @param array $attribs
*/
public function setAttribs( $attribs ) {
$this->mAttribs = $attribs;
}
/**
- * @param $extra array
+ * @param array $extra
*/
public function setExtra( $extra ) {
$this->mExtra = $extra;
}
/**
- *
* @return Title
*/
public function &getTitle() {
if ( $this->mTitle === false ) {
$this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
}
+
return $this->mTitle;
}
@@ -211,21 +262,21 @@ class RecentChange {
$this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false );
}
}
+
return $this->mPerformer;
}
/**
* Writes the data in this object to the database
- * @param $noudp bool
+ * @param bool $noudp
*/
public function save( $noudp = false ) {
- global $wgLocalInterwiki, $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang;
+ global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang;
$dbw = wfGetDB( DB_MASTER );
if ( !is_array( $this->mExtra ) ) {
$this->mExtra = array();
}
- $this->mExtra['lang'] = $wgLocalInterwiki;
if ( !$wgPutIPinRC ) {
$this->mAttribs['rc_ip'] = '';
@@ -244,7 +295,6 @@ class RecentChange {
# Fixup database timestamps
$this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] );
- $this->mAttribs['rc_cur_time'] = $dbw->timestamp( $this->mAttribs['rc_cur_time'] );
$this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'recentchanges_rc_id_seq' );
## If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
@@ -271,7 +321,7 @@ class RecentChange {
$editor = $this->getPerformer();
$title = $this->getTitle();
- if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title ) ) ) {
+ if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title, $this ) ) ) {
# @todo FIXME: This would be better as an extension hook
$enotif = new EmailNotification();
$enotif->notifyOnPageChange( $editor, $title,
@@ -285,44 +335,37 @@ class RecentChange {
}
/**
- * @deprecated since 1.22, use notifyRCFeeds instead.
- */
- public function notifyRC2UDP() {
- wfDeprecated( __METHOD__, '1.22' );
- $this->notifyRCFeeds();
- }
-
- /**
- * Send some text to UDP.
- * @deprecated since 1.22
+ * Notify all the feeds about the change.
+ * @param array $feeds Optional feeds to send to, defaults to $wgRCFeeds
*/
- public static function sendToUDP( $line, $address = '', $prefix = '', $port = '' ) {
- global $wgRC2UDPAddress, $wgRC2UDPInterwikiPrefix, $wgRC2UDPPort, $wgRC2UDPPrefix;
-
- wfDeprecated( __METHOD__, '1.22' );
-
- # Assume default for standard RC case
- $address = $address ? $address : $wgRC2UDPAddress;
- $prefix = $prefix ? $prefix : $wgRC2UDPPrefix;
- $port = $port ? $port : $wgRC2UDPPort;
+ public function notifyRCFeeds( array $feeds = null ) {
+ global $wgRCFeeds;
+ if ( $feeds === null ) {
+ $feeds = $wgRCFeeds;
+ }
- $engine = new UDPRCFeedEngine();
- $feed = array(
- 'uri' => "udp://$address:$port/$prefix",
- 'formatter' => 'IRCColourfulRCFeedFormatter',
- 'add_interwiki_prefix' => $wgRC2UDPInterwikiPrefix,
- );
+ $performer = $this->getPerformer();
- return $engine->send( $feed, $line );
- }
+ foreach ( $feeds as $feed ) {
+ $feed += array(
+ 'omit_bots' => false,
+ 'omit_anon' => false,
+ 'omit_user' => false,
+ 'omit_minor' => false,
+ 'omit_patrolled' => false,
+ );
- /**
- * Notify all the feeds about the change.
- */
- public function notifyRCFeeds() {
- global $wgRCFeeds;
+ if (
+ ( $feed['omit_bots'] && $this->mAttribs['rc_bot'] ) ||
+ ( $feed['omit_anon'] && $performer->isAnon() ) ||
+ ( $feed['omit_user'] && !$performer->isAnon() ) ||
+ ( $feed['omit_minor'] && $this->mAttribs['rc_minor'] ) ||
+ ( $feed['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) ||
+ $this->mAttribs['rc_type'] == RC_EXTERNAL
+ ) {
+ continue;
+ }
- foreach ( $wgRCFeeds as $feed ) {
$engine = self::getEngine( $feed['uri'] );
if ( isset( $this->mExtra['actionCommentIRC'] ) ) {
@@ -331,16 +374,8 @@ class RecentChange {
$actionComment = null;
}
- $omitBots = isset( $feed['omit_bots'] ) ? $feed['omit_bots'] : false;
-
- if (
- ( $omitBots && $this->mAttribs['rc_bot'] ) ||
- $this->mAttribs['rc_type'] == RC_EXTERNAL
- ) {
- continue;
- }
-
- $formatter = new $feed['formatter']();
+ /** @var $formatter RCFeedFormatter */
+ $formatter = is_object( $feed['formatter'] ) ? $feed['formatter'] : new $feed['formatter']();
$line = $formatter->getLine( $feed, $this, $actionComment );
$engine->send( $feed, $line );
@@ -350,10 +385,11 @@ class RecentChange {
/**
* Gets the stream engine object for a given URI from $wgRCEngines
*
- * @param $uri string URI to get the engine object for
- * @return object The engine object
+ * @param string $uri URI to get the engine object for
+ * @throws MWException
+ * @return RCFeedEngine The engine object
*/
- private static function getEngine( $uri ) {
+ public static function getEngine( $uri ) {
global $wgRCEngines;
$scheme = parse_url( $uri, PHP_URL_SCHEME );
@@ -369,19 +405,11 @@ class RecentChange {
}
/**
- * @deprecated since 1.22, moved to IRCColourfulRCFeedFormatter
- */
- public static function cleanupForIRC( $text ) {
- wfDeprecated( __METHOD__, '1.22' );
- return IRCColourfulRCFeedFormatter::cleanupForIRC( $text );
- }
-
- /**
* Mark a given change as patrolled
*
- * @param $change Mixed: RecentChange or corresponding rc_id
- * @param $auto Boolean: for automatic patrol
- * @return Array See doMarkPatrolled(), or null if $change is not an existing rc_id
+ * @param RecentChange|int $change RecentChange or corresponding rc_id
+ * @param bool $auto For automatic patrol
+ * @return array See doMarkPatrolled(), or null if $change is not an existing rc_id
*/
public static function markPatrolled( $change, $auto = false ) {
global $wgUser;
@@ -393,16 +421,18 @@ class RecentChange {
if ( !$change instanceof RecentChange ) {
return null;
}
+
return $change->doMarkPatrolled( $wgUser, $auto );
}
/**
* Mark this RecentChange as patrolled
*
- * NOTE: Can also return 'rcpatroldisabled', 'hookaborted' and 'markedaspatrollederror-noautopatrol' as errors
- * @param $user User object doing the action
- * @param $auto Boolean: for automatic patrol
- * @return array of permissions errors, see Title::getUserPermissionsErrors()
+ * NOTE: Can also return 'rcpatroldisabled', 'hookaborted' and
+ * 'markedaspatrollederror-noautopatrol' as errors
+ * @param User $user User object doing the action
+ * @param bool $auto For automatic patrol
+ * @return array Array of permissions errors, see Title::getUserPermissionsErrors()
*/
public function doMarkPatrolled( User $user, $auto = false ) {
global $wgUseRCPatrol, $wgUseNPPatrol;
@@ -420,7 +450,9 @@ class RecentChange {
}
// Users without the 'autopatrol' right can't patrol their
// own revisions
- if ( $user->getName() == $this->getAttribute( 'rc_user_text' ) && !$user->isAllowed( 'autopatrol' ) ) {
+ if ( $user->getName() == $this->getAttribute( 'rc_user_text' )
+ && !$user->isAllowed( 'autopatrol' )
+ ) {
$errors[] = array( 'markedaspatrollederror-noautopatrol' );
}
if ( $errors ) {
@@ -435,12 +467,13 @@ class RecentChange {
// Log this patrol event
PatrolLog::record( $this, $auto, $user );
wfRunHooks( 'MarkPatrolledComplete', array( $this->getAttribute( 'rc_id' ), &$user, false ) );
+
return array();
}
/**
* Mark this RecentChange patrolled, without error checking
- * @return Integer: number of affected rows
+ * @return int Number of affected rows
*/
public function reallyMarkPatrolled() {
$dbw = wfGetDB( DB_MASTER );
@@ -457,25 +490,26 @@ class RecentChange {
// Invalidate the page cache after the page has been patrolled
// to make sure that the Patrol link isn't visible any longer!
$this->getTitle()->invalidateCache();
+
return $dbw->affectedRows();
}
/**
* Makes an entry in the database corresponding to an edit
*
- * @param $timestamp
- * @param $title Title
- * @param $minor
- * @param $user User
- * @param $comment
- * @param $oldId
- * @param $lastTimestamp
- * @param $bot
- * @param $ip string
- * @param $oldSize int
- * @param $newSize int
- * @param $newId int
- * @param $patrol int
+ * @param string $timestamp
+ * @param Title $title
+ * @param bool $minor
+ * @param User $user
+ * @param string $comment
+ * @param int $oldId
+ * @param string $lastTimestamp
+ * @param bool $bot
+ * @param string $ip
+ * @param int $oldSize
+ * @param int $newSize
+ * @param int $newId
+ * @param int $patrol
* @return RecentChange
*/
public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId,
@@ -484,57 +518,57 @@ class RecentChange {
$rc->mTitle = $title;
$rc->mPerformer = $user;
$rc->mAttribs = array(
- 'rc_timestamp' => $timestamp,
- 'rc_cur_time' => $timestamp,
- 'rc_namespace' => $title->getNamespace(),
- 'rc_title' => $title->getDBkey(),
- 'rc_type' => RC_EDIT,
- 'rc_minor' => $minor ? 1 : 0,
- 'rc_cur_id' => $title->getArticleID(),
- 'rc_user' => $user->getId(),
- 'rc_user_text' => $user->getName(),
- 'rc_comment' => $comment,
+ 'rc_timestamp' => $timestamp,
+ 'rc_namespace' => $title->getNamespace(),
+ 'rc_title' => $title->getDBkey(),
+ 'rc_type' => RC_EDIT,
+ 'rc_source' => self::SRC_EDIT,
+ 'rc_minor' => $minor ? 1 : 0,
+ 'rc_cur_id' => $title->getArticleID(),
+ 'rc_user' => $user->getId(),
+ 'rc_user_text' => $user->getName(),
+ 'rc_comment' => $comment,
'rc_this_oldid' => $newId,
'rc_last_oldid' => $oldId,
- 'rc_bot' => $bot ? 1 : 0,
- 'rc_ip' => self::checkIPAddress( $ip ),
- 'rc_patrolled' => intval( $patrol ),
- 'rc_new' => 0, # obsolete
- 'rc_old_len' => $oldSize,
- 'rc_new_len' => $newSize,
- 'rc_deleted' => 0,
- 'rc_logid' => 0,
- 'rc_log_type' => null,
+ 'rc_bot' => $bot ? 1 : 0,
+ 'rc_ip' => self::checkIPAddress( $ip ),
+ 'rc_patrolled' => intval( $patrol ),
+ 'rc_new' => 0, # obsolete
+ 'rc_old_len' => $oldSize,
+ 'rc_new_len' => $newSize,
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
'rc_log_action' => '',
- 'rc_params' => ''
+ 'rc_params' => ''
);
$rc->mExtra = array(
'prefixedDBkey' => $title->getPrefixedDBkey(),
'lastTimestamp' => $lastTimestamp,
- 'oldSize' => $oldSize,
- 'newSize' => $newSize,
- 'pageStatus' => 'changed'
+ 'oldSize' => $oldSize,
+ 'newSize' => $newSize,
+ 'pageStatus' => 'changed'
);
$rc->save();
+
return $rc;
}
/**
* Makes an entry in the database corresponding to page creation
* Note: the title object must be loaded with the new id using resetArticleID()
- * @todo Document parameters and return
*
- * @param $timestamp
- * @param $title Title
- * @param $minor
- * @param $user User
- * @param $comment
- * @param $bot
- * @param $ip string
- * @param $size int
- * @param $newId int
- * @param $patrol int
+ * @param string $timestamp
+ * @param Title $title
+ * @param bool $minor
+ * @param User $user
+ * @param string $comment
+ * @param bool $bot
+ * @param string $ip
+ * @param int $size
+ * @param int $newId
+ * @param int $patrol
* @return RecentChange
*/
public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot,
@@ -543,29 +577,29 @@ class RecentChange {
$rc->mTitle = $title;
$rc->mPerformer = $user;
$rc->mAttribs = array(
- 'rc_timestamp' => $timestamp,
- 'rc_cur_time' => $timestamp,
- 'rc_namespace' => $title->getNamespace(),
- 'rc_title' => $title->getDBkey(),
- 'rc_type' => RC_NEW,
- 'rc_minor' => $minor ? 1 : 0,
- 'rc_cur_id' => $title->getArticleID(),
- 'rc_user' => $user->getId(),
- 'rc_user_text' => $user->getName(),
- 'rc_comment' => $comment,
- 'rc_this_oldid' => $newId,
- 'rc_last_oldid' => 0,
- 'rc_bot' => $bot ? 1 : 0,
- 'rc_ip' => self::checkIPAddress( $ip ),
- 'rc_patrolled' => intval( $patrol ),
- 'rc_new' => 1, # obsolete
- 'rc_old_len' => 0,
- 'rc_new_len' => $size,
- 'rc_deleted' => 0,
- 'rc_logid' => 0,
- 'rc_log_type' => null,
- 'rc_log_action' => '',
- 'rc_params' => ''
+ 'rc_timestamp' => $timestamp,
+ 'rc_namespace' => $title->getNamespace(),
+ 'rc_title' => $title->getDBkey(),
+ 'rc_type' => RC_NEW,
+ 'rc_source' => self::SRC_NEW,
+ 'rc_minor' => $minor ? 1 : 0,
+ 'rc_cur_id' => $title->getArticleID(),
+ 'rc_user' => $user->getId(),
+ 'rc_user_text' => $user->getName(),
+ 'rc_comment' => $comment,
+ 'rc_this_oldid' => $newId,
+ 'rc_last_oldid' => 0,
+ 'rc_bot' => $bot ? 1 : 0,
+ 'rc_ip' => self::checkIPAddress( $ip ),
+ 'rc_patrolled' => intval( $patrol ),
+ 'rc_new' => 1, # obsolete
+ 'rc_old_len' => 0,
+ 'rc_new_len' => $size,
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => ''
);
$rc->mExtra = array(
@@ -576,28 +610,30 @@ class RecentChange {
'pageStatus' => 'created'
);
$rc->save();
+
return $rc;
}
/**
- * @param $timestamp
- * @param $title
- * @param $user
- * @param $actionComment
- * @param $ip string
- * @param $type
- * @param $action
- * @param $target
- * @param $logComment
- * @param $params
- * @param $newId int
- * @param $actionCommentIRC string
+ * @param string $timestamp
+ * @param Title $title
+ * @param User $user
+ * @param string $actionComment
+ * @param string $ip
+ * @param string $type
+ * @param string $action
+ * @param Title $target
+ * @param string $logComment
+ * @param string $params
+ * @param int $newId
+ * @param string $actionCommentIRC
* @return bool
*/
public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip, $type,
- $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' )
- {
+ $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = ''
+ ) {
global $wgLogRestrictions;
+
# Don't add private logs to RC!
if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) {
return false;
@@ -605,22 +641,23 @@ class RecentChange {
$rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action,
$target, $logComment, $params, $newId, $actionCommentIRC );
$rc->save();
+
return true;
}
/**
- * @param $timestamp
- * @param $title Title
- * @param $user User
- * @param $actionComment
- * @param $ip string
- * @param $type
- * @param $action
- * @param $target Title
- * @param $logComment
- * @param $params
- * @param $newId int
- * @param $actionCommentIRC string
+ * @param string $timestamp
+ * @param Title $title
+ * @param User $user
+ * @param string $actionComment
+ * @param string $ip
+ * @param string $type
+ * @param string $action
+ * @param Title $target
+ * @param string $logComment
+ * @param string $params
+ * @param int $newId
+ * @param string $actionCommentIRC
* @return RecentChange
*/
public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip,
@@ -652,45 +689,46 @@ class RecentChange {
$rc->mTitle = $target;
$rc->mPerformer = $user;
$rc->mAttribs = array(
- 'rc_timestamp' => $timestamp,
- 'rc_cur_time' => $timestamp,
- 'rc_namespace' => $target->getNamespace(),
- 'rc_title' => $target->getDBkey(),
- 'rc_type' => RC_LOG,
- 'rc_minor' => 0,
- 'rc_cur_id' => $target->getArticleID(),
- 'rc_user' => $user->getId(),
- 'rc_user_text' => $user->getName(),
- 'rc_comment' => $logComment,
+ 'rc_timestamp' => $timestamp,
+ 'rc_namespace' => $target->getNamespace(),
+ 'rc_title' => $target->getDBkey(),
+ 'rc_type' => RC_LOG,
+ 'rc_source' => self::SRC_LOG,
+ 'rc_minor' => 0,
+ 'rc_cur_id' => $target->getArticleID(),
+ 'rc_user' => $user->getId(),
+ 'rc_user_text' => $user->getName(),
+ 'rc_comment' => $logComment,
'rc_this_oldid' => 0,
'rc_last_oldid' => 0,
- 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0,
- 'rc_ip' => self::checkIPAddress( $ip ),
- 'rc_patrolled' => 1,
- 'rc_new' => 0, # obsolete
- 'rc_old_len' => null,
- 'rc_new_len' => null,
- 'rc_deleted' => 0,
- 'rc_logid' => $newId,
- 'rc_log_type' => $type,
+ 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0,
+ 'rc_ip' => self::checkIPAddress( $ip ),
+ 'rc_patrolled' => 1,
+ 'rc_new' => 0, # obsolete
+ 'rc_old_len' => null,
+ 'rc_new_len' => null,
+ 'rc_deleted' => 0,
+ 'rc_logid' => $newId,
+ 'rc_log_type' => $type,
'rc_log_action' => $action,
- 'rc_params' => $params
+ 'rc_params' => $params
);
$rc->mExtra = array(
'prefixedDBkey' => $title->getPrefixedDBkey(),
'lastTimestamp' => 0,
'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
- 'pageStatus' => $pageStatus,
+ 'pageStatus' => $pageStatus,
'actionCommentIRC' => $actionCommentIRC
);
+
return $rc;
}
/**
* Initialises the members of this object from a mysql row object
*
- * @param $row
+ * @param mixed $row
*/
public function loadFromRow( $row ) {
$this->mAttribs = get_object_vars( $row );
@@ -699,42 +737,6 @@ class RecentChange {
}
/**
- * Makes a pseudo-RC entry from a cur row
- *
- * @deprected in 1.22
- * @param $row
- */
- public function loadFromCurRow( $row ) {
- wfDeprecated( __METHOD__, '1.22' );
- $this->mAttribs = array(
- 'rc_timestamp' => wfTimestamp( TS_MW, $row->rev_timestamp ),
- 'rc_cur_time' => $row->rev_timestamp,
- 'rc_user' => $row->rev_user,
- 'rc_user_text' => $row->rev_user_text,
- 'rc_namespace' => $row->page_namespace,
- 'rc_title' => $row->page_title,
- 'rc_comment' => $row->rev_comment,
- 'rc_minor' => $row->rev_minor_edit ? 1 : 0,
- 'rc_type' => $row->page_is_new ? RC_NEW : RC_EDIT,
- 'rc_cur_id' => $row->page_id,
- 'rc_this_oldid' => $row->rev_id,
- 'rc_last_oldid' => isset( $row->rc_last_oldid ) ? $row->rc_last_oldid : 0,
- 'rc_bot' => 0,
- 'rc_ip' => '',
- 'rc_id' => $row->rc_id,
- 'rc_patrolled' => $row->rc_patrolled,
- 'rc_new' => $row->page_is_new, # obsolete
- 'rc_old_len' => $row->rc_old_len,
- 'rc_new_len' => $row->rc_new_len,
- 'rc_params' => isset( $row->rc_params ) ? $row->rc_params : '',
- 'rc_log_type' => isset( $row->rc_log_type ) ? $row->rc_log_type : null,
- 'rc_log_action' => isset( $row->rc_log_action ) ? $row->rc_log_action : null,
- 'rc_logid' => isset( $row->rc_logid ) ? $row->rc_logid : 0,
- 'rc_deleted' => $row->rc_deleted // MUST be set
- );
- }
-
- /**
* Get an attribute value
*
* @param string $name Attribute name
@@ -754,7 +756,7 @@ class RecentChange {
/**
* Gets the end part of the diff URL associated with this object
* Blank if no diff link should be displayed
- * @param $forceCur
+ * @param bool $forceCur
* @return string
*/
public function diffLinkTrail( $forceCur ) {
@@ -769,14 +771,15 @@ class RecentChange {
} else {
$trail = '';
}
+
return $trail;
}
/**
* Returns the change size (HTML).
* The lengths can be given optionally.
- * @param $old int
- * @param $new int
+ * @param int $old
+ * @param int $new
* @return string
*/
public function getCharacterDifference( $old = 0, $new = 0 ) {
@@ -789,6 +792,7 @@ class RecentChange {
if ( $old === null || $new === null ) {
return '';
}
+
return ChangesList::showCharacterDifference( $old, $new );
}
@@ -803,7 +807,7 @@ class RecentChange {
$method = __METHOD__;
$dbw = wfGetDB( DB_MASTER );
- $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
+ $dbw->onTransactionIdle( function () use ( $dbw, $method ) {
global $wgRCMaxAge;
$cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
@@ -819,7 +823,8 @@ class RecentChange {
global $wgRequest;
if ( $ip ) {
if ( !IP::isIPAddress( $ip ) ) {
- throw new MWException( "Attempt to write \"" . $ip . "\" as an IP address into recent changes" );
+ throw new MWException( "Attempt to write \"" . $ip .
+ "\" as an IP address into recent changes" );
}
} else {
$ip = $wgRequest->getIP();
@@ -827,6 +832,7 @@ class RecentChange {
$ip = '';
}
}
+
return $ip;
}
@@ -835,12 +841,13 @@ class RecentChange {
* as the recentchanges table might not be cleared out regularly (so older entries might exist)
* or rows which will be deleted soon shouldn't be included.
*
- * @param $timestamp mixed MWTimestamp compatible timestamp
- * @param $tolerance integer Tolerance in seconds
+ * @param mixed $timestamp MWTimestamp compatible timestamp
+ * @param int $tolerance Tolerance in seconds
* @return bool
*/
public static function isInRCLifespan( $timestamp, $tolerance = 0 ) {
global $wgRCMaxAge;
+
return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge;
}
}
diff --git a/includes/clientpool/RedisConnectionPool.php b/includes/clientpool/RedisConnectionPool.php
index ef71b182..dc95727d 100644
--- a/includes/clientpool/RedisConnectionPool.php
+++ b/includes/clientpool/RedisConnectionPool.php
@@ -44,23 +44,25 @@ class RedisConnectionPool {
*/
/** @var string Connection timeout in seconds */
protected $connectTimeout;
+ /** @var string Read timeout in seconds */
+ protected $readTimeout;
/** @var string Plaintext auth password */
protected $password;
/** @var bool Whether connections persist */
protected $persistent;
- /** @var integer Serializer to use (Redis::SERIALIZER_*) */
+ /** @var int Serializer to use (Redis::SERIALIZER_*) */
protected $serializer;
/** @} */
- /** @var integer Current idle pool size */
+ /** @var int Current idle pool size */
protected $idlePoolSize = 0;
- /** @var Array (server name => ((connection info array),...) */
+ /** @var array (server name => ((connection info array),...) */
protected $connections = array();
- /** @var Array (server name => UNIX timestamp) */
+ /** @var array (server name => UNIX timestamp) */
protected $downServers = array();
- /** @var Array (pool ID => RedisConnectionPool) */
+ /** @var array (pool ID => RedisConnectionPool) */
protected static $instances = array();
/** integer; seconds to cache servers as "down". */
@@ -68,6 +70,7 @@ class RedisConnectionPool {
/**
* @param array $options
+ * @throws MWException
*/
protected function __construct( array $options ) {
if ( !class_exists( 'Redis' ) ) {
@@ -75,6 +78,7 @@ class RedisConnectionPool {
'See https://www.mediawiki.org/wiki/Redis#Setup' );
}
$this->connectTimeout = $options['connectTimeout'];
+ $this->readTimeout = $options['readTimeout'];
$this->persistent = $options['persistent'];
$this->password = $options['password'];
if ( !isset( $options['serializer'] ) || $options['serializer'] === 'php' ) {
@@ -89,27 +93,34 @@ class RedisConnectionPool {
}
/**
- * @param $options Array
- * @return Array
+ * @param array $options
+ * @return array
*/
protected static function applyDefaultConfig( array $options ) {
if ( !isset( $options['connectTimeout'] ) ) {
$options['connectTimeout'] = 1;
}
+ if ( !isset( $options['readTimeout'] ) ) {
+ $options['readTimeout'] = 1;
+ }
if ( !isset( $options['persistent'] ) ) {
$options['persistent'] = false;
}
if ( !isset( $options['password'] ) ) {
$options['password'] = null;
}
+
return $options;
}
/**
- * @param $options Array
+ * @param array $options
* $options include:
* - connectTimeout : The timeout for new connections, in seconds.
* Optional, default is 1 second.
+ * - readTimeout : The timeout for operation reads, in seconds.
+ * Commands like BLPOP can fail if told to wait longer than this.
+ * Optional, default is 1 second.
* - persistent : Set this to true to allow connections to persist across
* multiple web requests. False by default.
* - password : The authentication password, will be sent to Redis in clear text.
@@ -125,8 +136,9 @@ class RedisConnectionPool {
// Initialize the object at the hash as needed...
if ( !isset( self::$instances[$id] ) ) {
self::$instances[$id] = new self( $options );
- wfDebug( "Creating a new " . __CLASS__ . " instance with id $id." );
+ wfDebug( "Creating a new " . __CLASS__ . " instance with id $id.\n" );
}
+
return self::$instances[$id];
}
@@ -150,7 +162,8 @@ class RedisConnectionPool {
} else {
// Server is dead
wfDebug( "server $server is marked down for another " .
- ( $this->downServers[$server] - $now ) . " seconds, can't get connection" );
+ ( $this->downServers[$server] - $now ) . " seconds, can't get connection\n" );
+
return false;
}
}
@@ -161,6 +174,7 @@ class RedisConnectionPool {
if ( $connection['free'] ) {
$connection['free'] = false;
--$this->idlePoolSize;
+
return new RedisConnRef( $this, $server, $connection['conn'] );
}
}
@@ -195,6 +209,7 @@ class RedisConnectionPool {
wfDebugLog( 'redis', "Could not connect to server $server" );
// Mark server down for some time to avoid further timeouts
$this->downServers[$server] = time() + self::SERVER_DOWN_TTL;
+
return false;
}
if ( $this->password !== null ) {
@@ -204,13 +219,16 @@ class RedisConnectionPool {
}
} catch ( RedisException $e ) {
$this->downServers[$server] = time() + self::SERVER_DOWN_TTL;
- wfDebugLog( 'redis', "Redis exception: " . $e->getMessage() . "\n" );
+ wfDebugLog( 'redis', "Redis exception connecting to $server: " . $e->getMessage() );
+
return false;
}
if ( $conn ) {
+ $conn->setOption( Redis::OPT_READ_TIMEOUT, $this->readTimeout );
$conn->setOption( Redis::OPT_SERIALIZER, $this->serializer );
$this->connections[$server][] = array( 'conn' => $conn, 'free' => false );
+
return new RedisConnRef( $this, $server, $conn );
} else {
return false;
@@ -220,9 +238,9 @@ class RedisConnectionPool {
/**
* Mark a connection to a server as free to return to the pool
*
- * @param $server string
- * @param $conn Redis
- * @return boolean
+ * @param string $server
+ * @param Redis $conn
+ * @return bool
*/
public function freeConnection( $server, Redis $conn ) {
$found = false;
@@ -242,15 +260,13 @@ class RedisConnectionPool {
/**
* Close any extra idle connections if there are more than the limit
- *
- * @return void
*/
protected function closeExcessIdleConections() {
if ( $this->idlePoolSize <= count( $this->connections ) ) {
return; // nothing to do (no more connections than servers)
}
- foreach ( $this->connections as $server => &$serverConnections ) {
+ foreach ( $this->connections as &$serverConnections ) {
foreach ( $serverConnections as $key => &$connection ) {
if ( $connection['free'] ) {
unset( $serverConnections[$key] );
@@ -268,12 +284,26 @@ class RedisConnectionPool {
* not. The safest response for us is to explicitly destroy the connection
* object and let it be reopened during the next request.
*
- * @param $server string
- * @param $cref RedisConnRef
- * @param $e RedisException
- * @return void
+ * @param string $server
+ * @param RedisConnRef $cref
+ * @param RedisException $e
+ * @deprecated since 1.23
*/
public function handleException( $server, RedisConnRef $cref, RedisException $e ) {
+ return $this->handleError( $cref, $e );
+ }
+
+ /**
+ * The redis extension throws an exception in response to various read, write
+ * and protocol errors. Sometimes it also closes the connection, sometimes
+ * not. The safest response for us is to explicitly destroy the connection
+ * object and let it be reopened during the next request.
+ *
+ * @param RedisConnRef $cref
+ * @param RedisException $e
+ */
+ public function handleError( RedisConnRef $cref, RedisException $e ) {
+ $server = $cref->getServer();
wfDebugLog( 'redis', "Redis exception on server $server: " . $e->getMessage() . "\n" );
foreach ( $this->connections[$server] as $key => $connection ) {
if ( $cref->isConnIdentical( $connection['conn'] ) ) {
@@ -283,11 +313,62 @@ class RedisConnectionPool {
}
}
}
+
+ /**
+ * Re-send an AUTH request to the redis server (useful after disconnects).
+ *
+ * This works around an upstream bug in phpredis. phpredis hides disconnects by transparently
+ * reconnecting, but it neglects to re-authenticate the new connection. To the user of the
+ * phpredis client API this manifests as a seemingly random tendency of connections to lose
+ * their authentication status.
+ *
+ * This method is for internal use only.
+ *
+ * @see https://github.com/nicolasff/phpredis/issues/403
+ *
+ * @param string $server
+ * @param Redis $conn
+ * @return bool Success
+ */
+ public function reauthenticateConnection( $server, Redis $conn ) {
+ if ( $this->password !== null ) {
+ if ( !$conn->auth( $this->password ) ) {
+ wfDebugLog( 'redis', "Authentication error connecting to $server" );
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Adjust or reset the connection handle read timeout value
+ *
+ * @param Redis $conn
+ * @param int $timeout Optional
+ */
+ public function resetTimeout( Redis $conn, $timeout = null ) {
+ $conn->setOption( Redis::OPT_READ_TIMEOUT, $timeout ?: $this->readTimeout );
+ }
+
+ /**
+ * Make sure connections are closed for sanity
+ */
+ function __destruct() {
+ foreach ( $this->connections as $server => &$serverConnections ) {
+ foreach ( $serverConnections as $key => &$connection ) {
+ $connection['conn']->close();
+ }
+ }
+ }
}
/**
* Helper class to handle automatically marking connectons as reusable (via RAII pattern)
*
+ * This class simply wraps the Redis class and can be used the same way
+ *
* @ingroup Redis
* @since 1.21
*/
@@ -298,11 +379,12 @@ class RedisConnRef {
protected $conn;
protected $server; // string
+ protected $lastError; // string
/**
- * @param $pool RedisConnectionPool
- * @param $server string
- * @param $conn Redis
+ * @param RedisConnectionPool $pool
+ * @param string $server
+ * @param Redis $conn
*/
public function __construct( RedisConnectionPool $pool, $server, Redis $conn ) {
$this->pool = $pool;
@@ -310,24 +392,81 @@ class RedisConnRef {
$this->conn = $conn;
}
+ /**
+ * @return string
+ * @since 1.23
+ */
+ public function getServer() {
+ return $this->server;
+ }
+
+ public function getLastError() {
+ return $this->lastError;
+ }
+
+ public function clearLastError() {
+ $this->lastError = null;
+ }
+
public function __call( $name, $arguments ) {
- return call_user_func_array( array( $this->conn, $name ), $arguments );
+ $conn = $this->conn; // convenience
+
+ // Work around https://github.com/nicolasff/phpredis/issues/70
+ $lname = strtolower( $name );
+ if ( ( $lname === 'blpop' || $lname == 'brpop' )
+ && is_array( $arguments[0] ) && isset( $arguments[1] )
+ ) {
+ $this->pool->resetTimeout( $conn, $arguments[1] + 1 );
+ } elseif ( $lname === 'brpoplpush' && isset( $arguments[2] ) ) {
+ $this->pool->resetTimeout( $conn, $arguments[2] + 1 );
+ }
+
+ $conn->clearLastError();
+ try {
+ $res = call_user_func_array( array( $conn, $name ), $arguments );
+ if ( preg_match( '/^ERR operation not permitted\b/', $conn->getLastError() ) ) {
+ $this->pool->reauthenticateConnection( $this->server, $conn );
+ $conn->clearLastError();
+ $res = call_user_func_array( array( $conn, $name ), $arguments );
+ wfDebugLog( 'redis', "Used automatic re-authentication for method '$name'." );
+ }
+ } catch ( RedisException $e ) {
+ $this->pool->resetTimeout( $conn ); // restore
+ throw $e;
+ }
+
+ $this->lastError = $conn->getLastError() ?: $this->lastError;
+
+ $this->pool->resetTimeout( $conn ); // restore
+
+ return $res;
}
/**
* @param string $script
* @param array $params
- * @param integer $numKeys
+ * @param int $numKeys
* @return mixed
* @throws RedisException
*/
public function luaEval( $script, array $params, $numKeys ) {
$sha1 = sha1( $script ); // 40 char hex
$conn = $this->conn; // convenience
+ $server = $this->server; // convenience
// Try to run the server-side cached copy of the script
$conn->clearLastError();
$res = $conn->evalSha( $sha1, $params, $numKeys );
+ // If we got a permission error reply that means that (a) we are not in
+ // multi()/pipeline() and (b) some connection problem likely occurred. If
+ // the password the client gave was just wrong, an exception should have
+ // been thrown back in getConnection() previously.
+ if ( preg_match( '/^ERR operation not permitted\b/', $conn->getLastError() ) ) {
+ $this->pool->reauthenticateConnection( $server, $conn );
+ $conn->clearLastError();
+ $res = $conn->eval( $script, $params, $numKeys );
+ wfDebugLog( 'redis', "Used automatic re-authentication for Lua script $sha1." );
+ }
// If the script is not in cache, use eval() to retry and cache it
if ( preg_match( '/^NOSCRIPT/', $conn->getLastError() ) ) {
$conn->clearLastError();
@@ -336,14 +475,16 @@ class RedisConnRef {
}
if ( $conn->getLastError() ) { // script bug?
- wfDebugLog( 'redis', "Lua script error: " . $conn->getLastError() );
+ wfDebugLog( 'redis', "Lua script error on server $server: " . $conn->getLastError() );
}
+ $this->lastError = $conn->getLastError() ?: $this->lastError;
+
return $res;
}
/**
- * @param RedisConnRef $conn
+ * @param Redis $conn
* @return bool
*/
public function isConnIdentical( Redis $conn ) {
diff --git a/includes/composer/ComposerHookHandler.php b/includes/composer/ComposerHookHandler.php
new file mode 100644
index 00000000..2587b1d8
--- /dev/null
+++ b/includes/composer/ComposerHookHandler.php
@@ -0,0 +1,37 @@
+<?php
+
+use Composer\Package\Package;
+use Composer\Script\Event;
+
+$GLOBALS['IP'] = __DIR__ . '/../../';
+require_once __DIR__ . '/../AutoLoader.php';
+
+/**
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class ComposerHookHandler {
+
+ public static function onPreUpdate( Event $event ) {
+ self::handleChangeEvent( $event );
+ }
+
+ public static function onPreInstall( Event $event ) {
+ self::handleChangeEvent( $event );
+ }
+
+ private static function handleChangeEvent( Event $event ) {
+ $package = $event->getComposer()->getPackage();
+
+ if ( $package instanceof Package ) {
+ $packageModifier = new ComposerPackageModifier(
+ $package,
+ new ComposerVersionNormalizer(),
+ new MediaWikiVersionFetcher()
+ );
+
+ $packageModifier->setProvidesMediaWiki();
+ }
+ }
+
+}
diff --git a/includes/composer/ComposerPackageModifier.php b/includes/composer/ComposerPackageModifier.php
new file mode 100644
index 00000000..64c69ef5
--- /dev/null
+++ b/includes/composer/ComposerPackageModifier.php
@@ -0,0 +1,62 @@
+<?php
+
+use Composer\Package\Link;
+use Composer\Package\LinkConstraint\VersionConstraint;
+use Composer\Package\Package;
+
+/**
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class ComposerPackageModifier {
+
+ const MEDIAWIKI_PACKAGE_NAME = 'mediawiki/mediawiki';
+
+ protected $package;
+ protected $versionNormalizer;
+ protected $versionFetcher;
+
+ public function __construct( Package $package,
+ ComposerVersionNormalizer $versionNormalizer, MediaWikiVersionFetcher $versionFetcher
+ ) {
+ $this->package = $package;
+ $this->versionNormalizer = $versionNormalizer;
+ $this->versionFetcher = $versionFetcher;
+ }
+
+ public function setProvidesMediaWiki() {
+ $this->setLinkAsProvides( $this->newMediaWikiLink() );
+ }
+
+ private function setLinkAsProvides( Link $link ) {
+ $this->package->setProvides( array( $link ) );
+ }
+
+ private function newMediaWikiLink() {
+ $version = $this->getMediaWikiVersionConstraint();
+
+ $link = new Link(
+ '__root__',
+ self::MEDIAWIKI_PACKAGE_NAME,
+ $version,
+ 'provides',
+ $version->getPrettyString()
+ );
+
+ return $link;
+ }
+
+ private function getMediaWikiVersionConstraint() {
+ $mvVersion = $this->versionFetcher->fetchVersion();
+ $mvVersion = $this->versionNormalizer->normalizeSuffix( $mvVersion );
+
+ $version = new VersionConstraint(
+ '==',
+ $this->versionNormalizer->normalizeLevelCount( $mvVersion )
+ );
+ $version->setPrettyString( $mvVersion );
+
+ return $version;
+ }
+
+}
diff --git a/includes/composer/ComposerVersionNormalizer.php b/includes/composer/ComposerVersionNormalizer.php
new file mode 100644
index 00000000..a0d31cf2
--- /dev/null
+++ b/includes/composer/ComposerVersionNormalizer.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class ComposerVersionNormalizer {
+
+ /**
+ * Ensures there is a dash in between the version and the stability suffix.
+ *
+ * Examples:
+ * - 1.23RC => 1.23-RC
+ * - 1.23alpha => 1.23-alpha
+ * - 1.23alpha3 => 1.23-alpha3
+ * - 1.23-beta => 1.23-beta
+ *
+ * @param string $version
+ *
+ * @return string
+ * @throws InvalidArgumentException
+ */
+ public function normalizeSuffix( $version ) {
+ if ( !is_string( $version ) ) {
+ throw new InvalidArgumentException( '$version must be a string' );
+ }
+
+ return preg_replace( '/^(\d[\d\.]*)([a-zA-Z]+)(\d*)$/', '$1-$2$3', $version, 1 );
+ }
+
+ /**
+ * Ensures the version has four levels.
+ * Version suffixes are supported, as long as they start with a dash.
+ *
+ * Examples:
+ * - 1.19 => 1.19.0.0
+ * - 1.19.2.3 => 1.19.2.3
+ * - 1.19-alpha => 1.19.0.0-alpha
+ * - 1337 => 1337.0.0.0
+ *
+ * @param string $version
+ *
+ * @return string
+ * @throws InvalidArgumentException
+ */
+ public function normalizeLevelCount( $version ) {
+ if ( !is_string( $version ) ) {
+ throw new InvalidArgumentException( '$version must be a string' );
+ }
+
+ $dashPosition = strpos( $version, '-' );
+
+ if ( $dashPosition !== false ) {
+ $suffix = substr( $version, $dashPosition );
+ $version = substr( $version, 0, $dashPosition );
+ }
+
+ $version = implode( '.', array_pad( explode( '.', $version ), 4, '0' ) );
+
+ if ( $dashPosition !== false ) {
+ $version .= $suffix;
+ }
+
+ return $version;
+ }
+}
diff --git a/includes/config/Config.php b/includes/config/Config.php
new file mode 100644
index 00000000..38f589dc
--- /dev/null
+++ b/includes/config/Config.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Copyright 2014
+ *
+ * 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 configuration instances
+ *
+ * @since 1.23
+ */
+interface Config {
+
+ /**
+ * Get a configuration variable such as "Sitename" or "UploadMaintenance."
+ *
+ * @param string $name Name of configuration option
+ * @return mixed Value configured
+ * @throws ConfigException
+ */
+ public function get( $name );
+
+ /**
+ * Check whether a configuration option is set for the given name
+ *
+ * @param string $name Name of configuration option
+ * @return bool
+ * @since 1.24
+ */
+ public function has( $name );
+}
diff --git a/includes/config/ConfigException.php b/includes/config/ConfigException.php
new file mode 100644
index 00000000..75cd5eeb
--- /dev/null
+++ b/includes/config/ConfigException.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright 2014
+ *
+ * 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
+ */
+
+/**
+ * Exceptions for config failures
+ *
+ * @since 1.23
+ */
+class ConfigException extends MWException {
+}
diff --git a/includes/config/ConfigFactory.php b/includes/config/ConfigFactory.php
new file mode 100644
index 00000000..12b0c399
--- /dev/null
+++ b/includes/config/ConfigFactory.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * Copyright 2014
+ *
+ * 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
+ */
+
+/**
+ * Factory class to create Config objects
+ *
+ * @since 1.23
+ */
+class ConfigFactory {
+
+ /**
+ * Map of config name => callback
+ * @var array
+ */
+ protected $factoryFunctions = array();
+
+ /**
+ * Config objects that have already been created
+ * name => Config object
+ * @var array
+ */
+ protected $configs = array();
+
+ /**
+ * @var ConfigFactory
+ */
+ private static $self;
+
+ public static function getDefaultInstance() {
+ if ( !self::$self ) {
+ self::$self = new self;
+ global $wgConfigRegistry;
+ foreach ( $wgConfigRegistry as $name => $callback ) {
+ self::$self->register( $name, $callback );
+ }
+ }
+ return self::$self;
+ }
+
+ /**
+ * Destroy the default instance
+ * Should only be called inside unit tests
+ * @throws MWException
+ * @codeCoverageIgnore
+ */
+ public static function destroyDefaultInstance() {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new MWException( __METHOD__ . ' was called outside of unit tests' );
+ }
+
+ self::$self = null;
+ }
+
+ /**
+ * Register a new config factory function
+ * Will override if it's already registered
+ * @param string $name
+ * @param callable $callback That takes this ConfigFactory as an argument
+ * @throws InvalidArgumentException If an invalid callback is provided
+ */
+ public function register( $name, $callback ) {
+ if ( !is_callable( $callback ) ) {
+ throw new InvalidArgumentException( 'Invalid callback provided' );
+ }
+ $this->factoryFunctions[$name] = $callback;
+ }
+
+ /**
+ * Create a given Config using the registered callback for $name.
+ * If an object was already created, the same Config object is returned.
+ * @param string $name Name of the extension/component you want a Config object for
+ * 'main' is used for core
+ * @throws ConfigException If a factory function isn't registered for $name
+ * @throws UnexpectedValueException If the factory function returns a non-Config object
+ * @return Config
+ */
+ public function makeConfig( $name ) {
+ if ( !isset( $this->configs[$name] ) ) {
+ if ( !isset( $this->factoryFunctions[$name] ) ) {
+ throw new ConfigException( "No registered builder available for $name." );
+ }
+ $conf = call_user_func( $this->factoryFunctions[$name], $this );
+ if ( $conf instanceof Config ) {
+ $this->configs[$name] = $conf;
+ } else {
+ throw new UnexpectedValueException( "The builder for $name returned a non-Config object." );
+ }
+ }
+
+ return $this->configs[$name];
+ }
+}
diff --git a/includes/config/GlobalVarConfig.php b/includes/config/GlobalVarConfig.php
new file mode 100644
index 00000000..39d6e8e1
--- /dev/null
+++ b/includes/config/GlobalVarConfig.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Copyright 2014
+ *
+ * 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
+ */
+
+/**
+ * Accesses configuration settings from $GLOBALS
+ *
+ * @since 1.23
+ */
+class GlobalVarConfig implements Config {
+
+ /**
+ * Prefix to use for configuration variables
+ * @var string
+ */
+ private $prefix;
+
+ /**
+ * Default builder function
+ * @return GlobalVarConfig
+ */
+ public static function newInstance() {
+ return new GlobalVarConfig();
+ }
+
+ public function __construct( $prefix = 'wg' ) {
+ $this->prefix = $prefix;
+ }
+
+ /**
+ * @see Config::get
+ */
+ public function get( $name ) {
+ if ( !$this->has( $name ) ) {
+ throw new ConfigException( __METHOD__ . ": undefined option: '$name'" );
+ }
+ return $this->getWithPrefix( $this->prefix, $name );
+ }
+
+ /**
+ * @see Config::has
+ */
+ public function has( $name ) {
+ return $this->hasWithPrefix( $this->prefix, $name );
+ }
+
+ /**
+ * @see MutableConfig::set
+ * @deprecated since 1.24
+ */
+ public function set( $name, $value ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ $this->setWithPrefix( $this->prefix, $name, $value );
+ }
+
+ /**
+ * Get a variable with a given prefix, if not the defaults.
+ *
+ * @param string $prefix Prefix to use on the variable, if one.
+ * @param string $name Variable name without prefix
+ * @return mixed
+ */
+ protected function getWithPrefix( $prefix, $name ) {
+ return $GLOBALS[$prefix . $name];
+ }
+
+ /**
+ * Check if a variable with a given prefix is set
+ *
+ * @param string $prefix Prefix to use on the variable
+ * @param string $name Variable name without prefix
+ * @return bool
+ */
+ protected function hasWithPrefix( $prefix, $name ) {
+ $var = $prefix . $name;
+ return array_key_exists( $var, $GLOBALS );
+ }
+
+ /**
+ * Get a variable with a given prefix, if not the defaults.
+ *
+ * @param string $prefix Prefix to use on the variable
+ * @param string $name Variable name without prefix
+ * @param mixed $value Value to set
+ * @deprecated since 1.24
+ */
+ protected function setWithPrefix( $prefix, $name, $value ) {
+ $GLOBALS[$prefix . $name] = $value;
+ }
+}
diff --git a/includes/config/HashConfig.php b/includes/config/HashConfig.php
new file mode 100644
index 00000000..a09a0a43
--- /dev/null
+++ b/includes/config/HashConfig.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Copyright 2014
+ *
+ * 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
+ */
+
+/**
+ * A Config instance which stores all settings as a member variable
+ *
+ * @since 1.24
+ */
+class HashConfig implements Config, MutableConfig {
+
+ /**
+ * Array of config settings
+ *
+ * @var array
+ */
+ private $settings;
+
+ /**
+ * @return HashConfig
+ */
+ public static function newInstance() {
+ return new HashConfig;
+ }
+
+ /**
+ * @param array $settings Any current settings to pre-load
+ */
+ public function __construct( array $settings = array() ) {
+ $this->settings = $settings;
+ }
+
+ /**
+ * @see Config::get
+ */
+ public function get( $name ) {
+ if ( !$this->has( $name ) ) {
+ throw new ConfigException( __METHOD__ . ": undefined option: '$name'" );
+ }
+
+ return $this->settings[$name];
+ }
+
+ /**
+ * @see Config::has
+ */
+ public function has( $name ) {
+ return array_key_exists( $name, $this->settings );
+ }
+
+ /**
+ * @see Config::set
+ */
+ public function set( $name, $value ) {
+ $this->settings[$name] = $value;
+ }
+}
diff --git a/includes/config/MultiConfig.php b/includes/config/MultiConfig.php
new file mode 100644
index 00000000..cbb65aa6
--- /dev/null
+++ b/includes/config/MultiConfig.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Copyright 2014
+ *
+ * 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
+ */
+
+/**
+ * Provides a fallback sequence for Config objects
+ *
+ * @since 1.24
+ */
+class MultiConfig implements Config {
+
+ /**
+ * Array of Config objects to use
+ * Order matters, the Config objects
+ * will be checked in order to see
+ * whether they have the requested setting
+ *
+ * @var Config[]
+ */
+ private $configs;
+
+ /**
+ * @param Config[] $configs
+ */
+ public function __construct( array $configs ) {
+ $this->configs = $configs;
+ }
+
+ /**
+ * @see Config::get
+ */
+ public function get( $name ) {
+ foreach ( $this->configs as $config ) {
+ if ( $config->has( $name ) ) {
+ return $config->get( $name );
+ }
+ }
+
+ throw new ConfigException( __METHOD__ . ": undefined option: '$name'" );
+ }
+
+ /**
+ * @see Config::has
+ */
+ public function has( $name ) {
+ foreach ( $this->configs as $config ) {
+ if ( $config->has( $name ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/includes/config/MutableConfig.php b/includes/config/MutableConfig.php
new file mode 100644
index 00000000..e765e3bc
--- /dev/null
+++ b/includes/config/MutableConfig.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Copyright 2014
+ *
+ * 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 mutable configuration instances
+ *
+ * @since 1.24
+ */
+interface MutableConfig {
+
+ /**
+ * Set a configuration variable such a "Sitename" to something like "My Wiki"
+ *
+ * @param string $name Name of configuration option
+ * @param mixed $value Value to set
+ * @throws ConfigException
+ */
+ public function set( $name, $value );
+}
diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php
index 137efb8a..9d257a6a 100644
--- a/includes/content/AbstractContent.php
+++ b/includes/content/AbstractContent.php
@@ -32,7 +32,6 @@
* @ingroup Content
*/
abstract class AbstractContent implements Content {
-
/**
* Name of the content model this Content object represents.
* Use with CONTENT_MODEL_XXX constants
@@ -44,7 +43,7 @@ abstract class AbstractContent implements Content {
protected $model_id;
/**
- * @param string|null $modelId
+ * @param string $modelId
*
* @since 1.21
*/
@@ -53,23 +52,21 @@ abstract class AbstractContent implements Content {
}
/**
- * @see Content::getModel
- *
* @since 1.21
+ *
+ * @see Content::getModel
*/
public function getModel() {
return $this->model_id;
}
/**
- * Throws an MWException if $model_id is not the id of the content model
- * supported by this Content object.
- *
* @since 1.21
*
* @param string $modelId The model to check
*
- * @throws MWException
+ * @throws MWException If the provided ID is not the ID of the content model supported by this
+ * Content object.
*/
protected function checkModelID( $modelId ) {
if ( $modelId !== $this->model_id ) {
@@ -82,40 +79,40 @@ abstract class AbstractContent implements Content {
}
/**
- * @see Content::getContentHandler
- *
* @since 1.21
+ *
+ * @see Content::getContentHandler
*/
public function getContentHandler() {
return ContentHandler::getForContent( $this );
}
/**
- * @see Content::getDefaultFormat
- *
* @since 1.21
+ *
+ * @see Content::getDefaultFormat
*/
public function getDefaultFormat() {
return $this->getContentHandler()->getDefaultFormat();
}
/**
- * @see Content::getSupportedFormats
- *
* @since 1.21
+ *
+ * @see Content::getSupportedFormats
*/
public function getSupportedFormats() {
return $this->getContentHandler()->getSupportedFormats();
}
/**
- * @see Content::isSupportedFormat
+ * @since 1.21
*
* @param string $format
*
- * @since 1.21
+ * @return bool
*
- * @return boolean
+ * @see Content::isSupportedFormat
*/
public function isSupportedFormat( $format ) {
if ( !$format ) {
@@ -126,13 +123,11 @@ abstract class AbstractContent implements Content {
}
/**
- * Throws an MWException if $this->isSupportedFormat( $format ) does not
- * return true.
- *
* @since 1.21
*
- * @param string $format
- * @throws MWException
+ * @param string $format The serialization format to check.
+ *
+ * @throws MWException If the format is not supported by this content handler.
*/
protected function checkFormat( $format ) {
if ( !$this->isSupportedFormat( $format ) ) {
@@ -144,48 +139,50 @@ abstract class AbstractContent implements Content {
}
/**
- * @see Content::serialize
- *
- * @param string|null $format
- *
* @since 1.21
*
+ * @param string $format
+ *
* @return string
+ *
+ * @see Content::serialize
*/
public function serialize( $format = null ) {
return $this->getContentHandler()->serializeContent( $this, $format );
}
/**
- * @see Content::isEmpty
- *
* @since 1.21
*
- * @return boolean
+ * @return bool
+ *
+ * @see Content::isEmpty
*/
public function isEmpty() {
return $this->getSize() === 0;
}
/**
- * @see Content::isValid
+ * Subclasses may override this to implement (light weight) validation.
*
* @since 1.21
*
- * @return boolean
+ * @return bool Always true.
+ *
+ * @see Content::isValid
*/
public function isValid() {
return true;
}
/**
- * @see Content::equals
- *
* @since 1.21
*
- * @param Content|null $that
+ * @param Content $that
+ *
+ * @return bool
*
- * @return boolean
+ * @see Content::equals
*/
public function equals( Content $that = null ) {
if ( is_null( $that ) ) {
@@ -215,26 +212,19 @@ abstract class AbstractContent implements Content {
* Subclasses may override this to determine the secondary data updates more
* efficiently, preferably without the need to generate a parser output object.
*
- * @see Content::getSecondaryDataUpdates()
+ * @since 1.21
*
- * @param $title Title The context for determining the necessary updates
- * @param $old Content|null An optional Content object representing the
- * previous content, i.e. the content being replaced by this Content
- * object.
- * @param $recursive boolean Whether to include recursive updates (default:
- * false).
- * @param $parserOutput ParserOutput|null Optional ParserOutput object.
- * Provide if you have one handy, to avoid re-parsing of the content.
+ * @param Title $title
+ * @param Content $old
+ * @param bool $recursive
+ * @param ParserOutput $parserOutput
*
- * @return Array. A list of DataUpdate objects for putting information
- * about this content object somewhere.
+ * @return DataUpdate[]
*
- * @since 1.21
+ * @see Content::getSecondaryDataUpdates()
*/
- public function getSecondaryDataUpdates( Title $title,
- Content $old = null,
- $recursive = true, ParserOutput $parserOutput = null
- ) {
+ public function getSecondaryDataUpdates( Title $title, Content $old = null,
+ $recursive = true, ParserOutput $parserOutput = null ) {
if ( $parserOutput === null ) {
$parserOutput = $this->getParserOutput( $title, null, null, false );
}
@@ -243,9 +233,11 @@ abstract class AbstractContent implements Content {
}
/**
- * @see Content::getRedirectChain
- *
* @since 1.21
+ *
+ * @return Title[]|null
+ *
+ * @see Content::getRedirectChain
*/
public function getRedirectChain() {
global $wgMaxRedirects;
@@ -264,7 +256,7 @@ abstract class AbstractContent implements Content {
break;
}
// Redirects to some special pages are not permitted
- if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
+ if ( $newtitle instanceof Title && $newtitle->isValidRedirectTarget() ) {
// The new title passes the checks, so make that our current
// title so that further recursion can be checked
$title = $newtitle;
@@ -273,104 +265,126 @@ abstract class AbstractContent implements Content {
break;
}
}
+
return $titles;
}
/**
- * @see Content::getRedirectTarget
+ * Subclasses that implement redirects should override this.
*
* @since 1.21
+ *
+ * @return null
+ *
+ * @see Content::getRedirectTarget
*/
public function getRedirectTarget() {
return null;
}
/**
- * @see Content::getUltimateRedirectTarget
- * @note: migrated here from Title::newFromRedirectRecurse
+ * @note Migrated here from Title::newFromRedirectRecurse.
*
* @since 1.21
+ *
+ * @return Title|null
+ *
+ * @see Content::getUltimateRedirectTarget
*/
public function getUltimateRedirectTarget() {
$titles = $this->getRedirectChain();
+
return $titles ? array_pop( $titles ) : null;
}
/**
- * @see Content::isRedirect
- *
* @since 1.21
*
* @return bool
+ *
+ * @see Content::isRedirect
*/
public function isRedirect() {
return $this->getRedirectTarget() !== null;
}
/**
- * @see Content::updateRedirect
- *
* This default implementation always returns $this.
- *
- * @param Title $target
+ * Subclasses that implement redirects should override this.
*
* @since 1.21
*
+ * @param Title $target
+ *
* @return Content $this
+ *
+ * @see Content::updateRedirect
*/
public function updateRedirect( Title $target ) {
return $this;
}
/**
- * @see Content::getSection
- *
* @since 1.21
+ *
+ * @return null
+ *
+ * @see Content::getSection
*/
public function getSection( $sectionId ) {
return null;
}
/**
- * @see Content::replaceSection
- *
* @since 1.21
+ *
+ * @return null
+ *
+ * @see Content::replaceSection
*/
- public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
+ public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) {
return null;
}
/**
- * @see Content::preSaveTransform
- *
* @since 1.21
+ *
+ * @return Content $this
+ *
+ * @see Content::preSaveTransform
*/
public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
return $this;
}
/**
- * @see Content::addSectionHeader
- *
* @since 1.21
+ *
+ * @return Content $this
+ *
+ * @see Content::addSectionHeader
*/
public function addSectionHeader( $header ) {
return $this;
}
/**
- * @see Content::preloadTransform
- *
* @since 1.21
+ *
+ * @return Content $this
+ *
+ * @see Content::preloadTransform
*/
- public function preloadTransform( Title $title, ParserOptions $popts ) {
+ public function preloadTransform( Title $title, ParserOptions $popts, $params = array() ) {
return $this;
}
/**
- * @see Content::prepareSave
- *
* @since 1.21
+ *
+ * @return Status
+ *
+ * @see Content::prepareSave
*/
public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) {
if ( $this->isValid() ) {
@@ -381,53 +395,47 @@ abstract class AbstractContent implements Content {
}
/**
- * @see Content::getDeletionUpdates
- *
* @since 1.21
*
- * @param $page WikiPage the deleted page
- * @param $parserOutput null|ParserOutput optional parser output object
- * for efficient access to meta-information about the content object.
- * Provide if you have one handy.
+ * @param WikiPage $page
+ * @param ParserOutput $parserOutput
+ *
+ * @return LinksDeletionUpdate[]
*
- * @return array A list of DataUpdate instances that will clean up the
- * database after deletion.
+ * @see Content::getDeletionUpdates
*/
- public function getDeletionUpdates( WikiPage $page,
- ParserOutput $parserOutput = null )
- {
+ public function getDeletionUpdates( WikiPage $page, ParserOutput $parserOutput = null ) {
return array(
new LinksDeletionUpdate( $page ),
);
}
/**
- * This default implementation always returns false. Subclasses may override this to supply matching logic.
- *
- * @see Content::matchMagicWord
+ * This default implementation always returns false. Subclasses may override
+ * this to supply matching logic.
*
* @since 1.21
*
* @param MagicWord $word
*
- * @return bool
+ * @return bool Always false.
+ *
+ * @see Content::matchMagicWord
*/
public function matchMagicWord( MagicWord $word ) {
return false;
}
/**
- * @see Content::convert()
- *
* This base implementation calls the hook ConvertContent to enable custom conversions.
* Subclasses may override this to implement conversion for "their" content model.
*
- * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags.
- * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is
- * not allowed, full round-trip conversion is expected to work without losing information.
+ * @param string $toModel
+ * @param string $lossy
*
- * @return Content|bool A content object with the content model $toModel, or false if
- * that conversion is not supported.
+ * @return Content|bool
+ *
+ * @see Content::convert()
*/
public function convert( $toModel, $lossy = '' ) {
if ( $this->getModel() === $toModel ) {
@@ -439,6 +447,77 @@ abstract class AbstractContent implements Content {
$result = false;
wfRunHooks( 'ConvertContent', array( $this, $toModel, $lossy, &$result ) );
+
return $result;
}
+
+ /**
+ * Returns a ParserOutput object containing information derived from this content.
+ * Most importantly, unless $generateHtml was false, the return value contains an
+ * HTML representation of the content.
+ *
+ * Subclasses that want to control the parser output may override this, but it is
+ * preferred to override fillParserOutput() instead.
+ *
+ * Subclasses that override getParserOutput() itself should take care to call the
+ * ContentGetParserOutput hook.
+ *
+ * @since 1.24
+ *
+ * @param Title $title Context title for parsing
+ * @param int|null $revId Revision ID (for {{REVISIONID}})
+ * @param ParserOptions|null $options Parser options
+ * @param bool $generateHtml Whether or not to generate HTML
+ *
+ * @return ParserOutput Containing information derived from this content.
+ */
+ public function getParserOutput( Title $title, $revId = null,
+ ParserOptions $options = null, $generateHtml = true
+ ) {
+ if ( $options === null ) {
+ $options = $this->getContentHandler()->makeParserOptions( 'canonical' );
+ }
+
+ $po = new ParserOutput();
+
+ if ( wfRunHooks( 'ContentGetParserOutput',
+ array( $this, $title, $revId, $options, $generateHtml, &$po ) ) ) {
+
+ // Save and restore the old value, just in case something is reusing
+ // the ParserOptions object in some weird way.
+ $oldRedir = $options->getRedirectTarget();
+ $options->setRedirectTarget( $this->getRedirectTarget() );
+ $this->fillParserOutput( $title, $revId, $options, $generateHtml, $po );
+ $options->setRedirectTarget( $oldRedir );
+ }
+
+ return $po;
+ }
+
+ /**
+ * Fills the provided ParserOutput with information derived from the content.
+ * Unless $generateHtml was false, this includes an HTML representation of the content.
+ *
+ * This is called by getParserOutput() after consulting the ContentGetParserOutput hook.
+ * Subclasses are expected to override this method (or getParserOutput(), if need be).
+ * Subclasses of TextContent should generally override getHtml() instead.
+ *
+ * This placeholder implementation always throws an exception.
+ *
+ * @since 1.24
+ *
+ * @param Title $title Context title for parsing
+ * @param int|null $revId Revision ID (for {{REVISIONID}})
+ * @param ParserOptions $options Parser options
+ * @param bool $generateHtml Whether or not to generate HTML
+ * @param ParserOutput &$output The output object to fill (reference).
+ *
+ * @throws MWException
+ */
+ protected function fillParserOutput( Title $title, $revId,
+ ParserOptions $options, $generateHtml, ParserOutput &$output
+ ) {
+ // Don't make abstract, so subclasses that override getParserOutput() directly don't fail.
+ throw new MWException( 'Subclasses of AbstractContent must override fillParserOutput!' );
+ }
}
diff --git a/includes/content/CodeContentHandler.php b/includes/content/CodeContentHandler.php
new file mode 100644
index 00000000..447a2a73
--- /dev/null
+++ b/includes/content/CodeContentHandler.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Content handler for the pages with code, such as CSS, JavaScript, JSON.
+ *
+ * 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 Content
+ */
+
+/**
+ * Content handler for code content such as CSS, JavaScript, JSON, etc
+ * @since 1.24
+ * @ingroup Content
+ */
+abstract class CodeContentHandler extends TextContentHandler {
+
+ /**
+ * Returns the english language, because code is english, and should be handled as such.
+ *
+ * @param Title $title
+ * @param Content $content
+ *
+ * @return Language Return of wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageLanguage()
+ */
+ public function getPageLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+
+ /**
+ * Returns the english language, because code is english, and should be handled as such.
+ *
+ * @param Title $title
+ * @param Content $content
+ *
+ * @return Language Return of wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageViewLanguage()
+ */
+ public function getPageViewLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+
+ /**
+ * @return string
+ */
+ protected function getContentClass() {
+ throw new MWException( 'Subclass must override' );
+ }
+}
diff --git a/includes/content/Content.php b/includes/content/Content.php
index 5a90e092..61b9254f 100644
--- a/includes/content/Content.php
+++ b/includes/content/Content.php
@@ -48,7 +48,7 @@ interface Content {
/**
* @since 1.21
*
- * @return string|false The wikitext to include when another page includes this
+ * @return string|bool The wikitext to include when another page includes this
* content, or false if the content is not includable in a wikitext page.
*
* @todo Allow native handling, bypassing wikitext representation, like
@@ -65,8 +65,9 @@ interface Content {
*
* @since 1.21
*
- * @param int $maxLength Maximum length of the summary text
- * @return string The summary text
+ * @param int $maxLength Maximum length of the summary text.
+ *
+ * @return string The summary text.
*/
public function getTextForSummary( $maxLength = 250 );
@@ -85,7 +86,7 @@ interface Content {
public function getNativeData();
/**
- * Returns the content's nominal size in bogo-bytes.
+ * Returns the content's nominal size in "bogo-bytes".
*
* @return int
*/
@@ -97,7 +98,7 @@ interface Content {
*
* @since 1.21
*
- * @return String The model id
+ * @return string The model id
*/
public function getModel();
@@ -121,7 +122,7 @@ interface Content {
*
* @since 1.21
*
- * @return String
+ * @return string
*/
public function getDefaultFormat();
@@ -133,7 +134,7 @@ interface Content {
*
* @since 1.21
*
- * @return Array of supported serialization formats
+ * @return string[] List of supported serialization formats
*/
public function getSupportedFormats();
@@ -148,7 +149,8 @@ interface Content {
*
* @since 1.21
*
- * @param string $format The format to check
+ * @param string $format The serialization format to check.
+ *
* @return bool Whether the format is supported
*/
public function isSupportedFormat( $format );
@@ -160,9 +162,9 @@ interface Content {
*
* @since 1.21
*
- * @param $format null|string The desired serialization format (or null for
- * the default format).
- * @return string Serialized form of this Content object
+ * @param string $format The desired serialization format, or null for the default format.
+ *
+ * @return string Serialized form of this Content object.
*/
public function serialize( $format = null );
@@ -185,7 +187,7 @@ interface Content {
*
* @since 1.21
*
- * @return boolean
+ * @return bool
*/
public function isValid();
@@ -208,7 +210,8 @@ interface Content {
*
* @since 1.21
*
- * @param $that Content The Content object to compare to
+ * @param Content $that The Content object to compare to.
+ *
* @return bool True if this Content object is equal to $that, false otherwise.
*/
public function equals( Content $that = null );
@@ -229,7 +232,7 @@ interface Content {
*
* @since 1.21
*
- * @return Content. A copy of this object
+ * @return Content A copy of this object
*/
public function copy();
@@ -243,7 +246,8 @@ interface Content {
* @param bool $hasLinks If it is known whether this content contains
* links, provide this information here, to avoid redundant parsing to
* find out.
- * @return boolean
+ *
+ * @return bool
*/
public function isCountable( $hasLinks = null );
@@ -253,10 +257,14 @@ interface Content {
* is needed, $generateHtml can be set to false; in that case,
* $result->getText() may return null.
*
- * @param $title Title The page title to use as a context for rendering
- * @param $revId null|int The revision being rendered (optional)
- * @param $options null|ParserOptions Any parser options
- * @param $generateHtml Boolean Whether to generate HTML (default: true). If false,
+ * @note To control which options are used in the cache key for the
+ * generated parser output, implementations of this method
+ * may call ParserOutput::recordOption() on the output object.
+ *
+ * @param Title $title The page title to use as a context for rendering.
+ * @param int $revId Optional revision ID being rendered.
+ * @param ParserOptions $options Any parser options.
+ * @param bool $generateHtml Whether to generate HTML (default: true). If false,
* the result of calling getText() on the ParserOutput object returned by
* this method is undefined.
*
@@ -264,9 +272,9 @@ interface Content {
*
* @return ParserOutput
*/
- public function getParserOutput( Title $title,
- $revId = null,
+ public function getParserOutput( Title $title, $revId = null,
ParserOptions $options = null, $generateHtml = true );
+
// TODO: make RenderOutput and RenderOptions base classes
/**
@@ -284,24 +292,22 @@ interface Content {
* Subclasses may implement this to determine the necessary updates more
* efficiently, or make use of information about the old content.
*
- * @param $title Title The context for determining the necessary updates
- * @param $old Content|null An optional Content object representing the
+ * @param Title $title The context for determining the necessary updates
+ * @param Content $old An optional Content object representing the
* previous content, i.e. the content being replaced by this Content
* object.
- * @param $recursive boolean Whether to include recursive updates (default:
+ * @param bool $recursive Whether to include recursive updates (default:
* false).
- * @param $parserOutput ParserOutput|null Optional ParserOutput object.
+ * @param ParserOutput $parserOutput Optional ParserOutput object.
* Provide if you have one handy, to avoid re-parsing of the content.
*
- * @return Array. A list of DataUpdate objects for putting information
+ * @return DataUpdate[] A list of DataUpdate objects for putting information
* about this content object somewhere.
*
* @since 1.21
*/
- public function getSecondaryDataUpdates( Title $title,
- Content $old = null,
- $recursive = true, ParserOutput $parserOutput = null
- );
+ public function getSecondaryDataUpdates( Title $title, Content $old = null,
+ $recursive = true, ParserOutput $parserOutput = null );
/**
* Construct the redirect destination from this content and return an
@@ -311,7 +317,7 @@ interface Content {
*
* @since 1.21
*
- * @return Array of Titles, with the destination last
+ * @return Title[]|null List of Titles, with the destination last.
*/
public function getRedirectChain();
@@ -323,7 +329,7 @@ interface Content {
*
* @since 1.21
*
- * @return Title: The corresponding Title
+ * @return Title|null The corresponding Title.
*/
public function getRedirectTarget();
@@ -340,7 +346,7 @@ interface Content {
*
* @since 1.21
*
- * @return Title
+ * @return Title|null
*/
public function getUltimateRedirectTarget();
@@ -360,9 +366,10 @@ interface Content {
*
* @since 1.21
*
- * @param Title $target the new redirect target
+ * @param Title $target The new redirect target
*
- * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect)
+ * @return Content A new Content object with the updated redirect (or $this
+ * if this Content object isn't a redirect)
*/
public function updateRedirect( Title $target );
@@ -371,11 +378,11 @@ interface Content {
*
* @since 1.21
*
- * @param string $sectionId The section's ID, given as a numeric string.
- * The ID "0" retrieves the section before the first heading, "1" the
- * text between the first heading (included) and the second heading
- * (excluded), etc.
- * @return Content|Boolean|null The section, or false if no such section
+ * @param string|number $sectionId Section identifier as a number or string
+ * (e.g. 0, 1 or 'T-1'). The ID "0" retrieves the section before the first heading, "1" the
+ * text between the first heading (included) and the second heading (excluded), etc.
+ *
+ * @return Content|bool|null The section, or false if no such section
* exist, or null if sections are not supported.
*/
public function getSection( $sectionId );
@@ -386,12 +393,15 @@ interface Content {
*
* @since 1.21
*
- * @param $section null/false or a section number (0, 1, 2, T1, T2...), or "new"
- * @param $with Content: new content of the section
- * @param string $sectionTitle new section's subject, only if $section is 'new'
- * @return string Complete article text, or null if error
+ * @param string|number|null|bool $sectionId Section identifier as a number or string
+ * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
+ * or 'new' for a new section.
+ * @param Content $with New content of the section
+ * @param string $sectionTitle New section's subject, only if $section is 'new'
+ *
+ * @return string|null Complete article text, or null if error
*/
- public function replaceSection( $section, Content $with, $sectionTitle = '' );
+ public function replaceSection( $sectionId, Content $with, $sectionTitle = '' );
/**
* Returns a Content object with pre-save transformations applied (or this
@@ -399,9 +409,10 @@ interface Content {
*
* @since 1.21
*
- * @param $title Title
- * @param $user User
- * @param $parserOptions null|ParserOptions
+ * @param Title $title
+ * @param User $user
+ * @param ParserOptions $parserOptions
+ *
* @return Content
*/
public function preSaveTransform( Title $title, User $user, ParserOptions $parserOptions );
@@ -413,7 +424,8 @@ interface Content {
*
* @since 1.21
*
- * @param $header string
+ * @param string $header
+ *
* @return Content
*/
public function addSectionHeader( $header );
@@ -424,11 +436,13 @@ interface Content {
*
* @since 1.21
*
- * @param $title Title
- * @param $parserOptions null|ParserOptions
+ * @param Title $title
+ * @param ParserOptions $parserOptions
+ * @param array $params
+ *
* @return Content
*/
- public function preloadTransform( Title $title, ParserOptions $parserOptions );
+ public function preloadTransform( Title $title, ParserOptions $parserOptions, $params = array() );
/**
* Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in
@@ -437,24 +451,24 @@ interface Content {
* This may be used to check the content's consistency with global state. This function should
* NOT write any information to the database.
*
- * Note that this method will usually be called inside the same transaction bracket that will be used
- * to save the new revision.
+ * Note that this method will usually be called inside the same transaction
+ * bracket that will be used to save the new revision.
*
- * Note that this method is called before any update to the page table is performed. This means that
- * $page may not yet know a page ID.
+ * Note that this method is called before any update to the page table is
+ * performed. This means that $page may not yet know a page ID.
*
* @since 1.21
*
* @param WikiPage $page The page to be saved.
- * @param int $flags bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent()
- * @param int $baseRevId the ID of the current revision
- * @param User $user
+ * @param int $flags Bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent()
+ * @param int $baseRevId The ID of the current revision
+ * @param User $user
*
- * @return Status A status object indicating whether the content was successfully prepared for saving.
- * If the returned status indicates an error, a rollback will be performed and the
- * transaction aborted.
+ * @return Status A status object indicating whether the content was
+ * successfully prepared for saving. If the returned status indicates
+ * an error, a rollback will be performed and the transaction aborted.
*
- * @see see WikiPage::doEditContent()
+ * @see WikiPage::doEditContent()
*/
public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user );
@@ -465,12 +479,12 @@ interface Content {
*
* @since 1.21
*
- * @param $page WikiPage the deleted page
- * @param $parserOutput null|ParserOutput optional parser output object
+ * @param WikiPage $page The deleted page
+ * @param ParserOutput $parserOutput Optional parser output object
* for efficient access to meta-information about the content object.
* Provide if you have one handy.
*
- * @return array A list of DataUpdate instances that will clean up the
+ * @return DataUpdate[] A list of DataUpdate instances that will clean up the
* database after deletion.
*/
public function getDeletionUpdates( WikiPage $page,
@@ -481,9 +495,9 @@ interface Content {
*
* @since 1.21
*
- * @param MagicWord $word the magic word to match
+ * @param MagicWord $word The magic word to match
*
- * @return bool whether this Content object matches the given magic word.
+ * @return bool Whether this Content object matches the given magic word.
*/
public function matchMagicWord( MagicWord $word );
@@ -491,18 +505,19 @@ interface Content {
* Converts this content object into another content object with the given content model,
* if that is possible.
*
- * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags.
- * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is
- * not allowed, full round-trip conversion is expected to work without losing information.
+ * @param string $toModel The desired content model, use the CONTENT_MODEL_XXX flags.
+ * @param string $lossy Optional flag, set to "lossy" to allow lossy conversion. If lossy
+ * conversion is not allowed, full round-trip conversion is expected to work without losing
+ * information.
*
* @return Content|bool A content object with the content model $toModel, or false if
* that conversion is not supported.
*/
public function convert( $toModel, $lossy = '' );
-
- // TODO: ImagePage and CategoryPage interfere with per-content action handlers
- // TODO: nice&sane integration of GeSHi syntax highlighting
+ // @todo ImagePage and CategoryPage interfere with per-content action handlers
+ // @todo nice&sane integration of GeSHi syntax highlighting
// [11:59] <vvv> Hooks are ugly; make CodeHighlighter interface and a
// config to set the class which handles syntax highlighting
// [12:00] <vvv> And default it to a DummyHighlighter
+
}
diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php
index 2a92e233..ac417223 100644
--- a/includes/content/ContentHandler.php
+++ b/includes/content/ContentHandler.php
@@ -31,7 +31,6 @@
* @ingroup Content
*/
class MWContentSerializationException extends MWException {
-
}
/**
@@ -54,7 +53,6 @@ class MWContentSerializationException extends MWException {
* @ingroup Content
*/
abstract class ContentHandler {
-
/**
* Switch for enabling deprecation warnings. Used by ContentHandler::deprecated()
* and ContentHandler::runLegacyHooks().
@@ -87,10 +85,11 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $content Content|null
- * @return null|string the textual form of $content, if available
- * @throws MWException if $content is not an instance of TextContent and
- * $wgContentHandlerTextFallback was set to 'fail'.
+ * @param Content $content
+ *
+ * @throws MWException If the content is not an instance of TextContent and
+ * wgContentHandlerTextFallback was set to 'fail'.
+ * @return string|null Textual form of the content, if available.
*/
public static function getContentText( Content $content = null ) {
global $wgContentHandlerTextFallback;
@@ -129,24 +128,21 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param string $text the textual representation, will be
+ * @param string $text The textual representation, will be
* unserialized to create the Content object
- * @param $title null|Title the title of the page this text belongs to.
+ * @param Title $title The title of the page this text belongs to.
* Required if $modelId is not provided.
- * @param $modelId null|string the model to deserialize to. If not provided,
+ * @param string $modelId The model to deserialize to. If not provided,
* $title->getContentModel() is used.
- * @param $format null|string the format to use for deserialization. If not
+ * @param string $format The format to use for deserialization. If not
* given, the model's default format is used.
*
- * @throws MWException
- * @return Content a Content object representing $text
- *
- * @throws MWException if $model or $format is not supported or if $text can
- * not be unserialized using $format.
+ * @throws MWException If model ID or format is not supported or if the text can not be
+ * unserialized using the format.
+ * @return Content A Content object representing the text.
*/
public static function makeContent( $text, Title $title = null,
- $modelId = null, $format = null )
- {
+ $modelId = null, $format = null ) {
if ( is_null( $modelId ) ) {
if ( is_null( $title ) ) {
throw new MWException( "Must provide a Title object or a content model ID." );
@@ -156,6 +152,7 @@ abstract class ContentHandler {
}
$handler = ContentHandler::getForModelID( $modelId );
+
return $handler->unserializeContent( $text, $format );
}
@@ -189,8 +186,9 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $title Title
- * @return null|string default model name for the page given by $title
+ * @param Title $title
+ *
+ * @return string Default model name for the page given by $title
*/
public static function getDefaultModelFor( Title $title ) {
// NOTE: this method must not rely on $title->getContentModel() directly or indirectly,
@@ -254,11 +252,13 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $title Title
+ * @param Title $title
+ *
* @return ContentHandler
*/
public static function getForTitle( Title $title ) {
$modelId = $title->getContentModel();
+
return ContentHandler::getForModelID( $modelId );
}
@@ -268,18 +268,20 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $content Content
+ * @param Content $content
+ *
* @return ContentHandler
*/
public static function getForContent( Content $content ) {
$modelId = $content->getModel();
+
return ContentHandler::getForModelID( $modelId );
}
/**
- * @var Array A Cache of ContentHandler instances by model id
+ * @var array A Cache of ContentHandler instances by model id
*/
- static $handlers;
+ protected static $handlers;
/**
* Returns the ContentHandler singleton for the given model ID. Use the
@@ -302,9 +304,9 @@ abstract class ContentHandler {
*
* @param string $modelId The ID of the content model for which to get a
* handler. Use CONTENT_MODEL_XXX constants.
- * @return ContentHandler The ContentHandler singleton for handling the
- * model given by $modelId
- * @throws MWException if no handler is known for $modelId.
+ *
+ * @throws MWException If no handler is known for the model ID.
+ * @return ContentHandler The ContentHandler singleton for handling the model given by the ID.
*/
public static function getForModelID( $modelId ) {
global $wgContentHandlers;
@@ -330,14 +332,16 @@ abstract class ContentHandler {
$handler = new $class( $modelId );
if ( !( $handler instanceof ContentHandler ) ) {
- throw new MWException( "$class from \$wgContentHandlers is not compatible with ContentHandler" );
+ throw new MWException( "$class from \$wgContentHandlers is not " .
+ "compatible with ContentHandler" );
}
}
wfDebugLog( 'ContentHandler', 'Created handler for ' . $modelId
- . ': ' . get_class( $handler ) );
+ . ': ' . get_class( $handler ) );
ContentHandler::$handlers[$modelId] = $handler;
+
return ContentHandler::$handlers[$modelId];
}
@@ -350,10 +354,12 @@ abstract class ContentHandler {
* @param string $name The content model ID, as given by a CONTENT_MODEL_XXX
* constant or returned by Revision::getContentModel().
*
- * @return string The content format's localized name.
- * @throws MWException if the model id isn't known.
+ * @throws MWException If the model ID isn't known.
+ * @return string The content model's localized name.
*/
public static function getLocalizedName( $name ) {
+ // Messages: content-model-wikitext, content-model-text,
+ // content-model-javascript, content-model-css
$key = "content-model-$name";
$msg = wfMessage( $key );
@@ -378,12 +384,20 @@ abstract class ContentHandler {
}
$formats = array_unique( $formats );
+
return $formats;
}
// ------------------------------------------------------------------------
+ /**
+ * @var string
+ */
protected $mModelID;
+
+ /**
+ * @var string[]
+ */
protected $mSupportedFormats;
/**
@@ -392,7 +406,7 @@ abstract class ContentHandler {
* provided as literals by subclass's constructors.
*
* @param string $modelId (use CONTENT_MODEL_XXX constants).
- * @param array $formats List for supported serialization formats
+ * @param string[] $formats List for supported serialization formats
* (typically as MIME types)
*/
public function __construct( $modelId, $formats ) {
@@ -409,24 +423,55 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $content Content The Content object to serialize
- * @param $format null|String The desired serialization format
+ * @param Content $content The Content object to serialize
+ * @param string $format The desired serialization format
+ *
* @return string Serialized form of the content
*/
abstract public function serializeContent( Content $content, $format = null );
/**
+ * Applies transformations on export (returns the blob unchanged per default).
+ * Subclasses may override this to perform transformations such as conversion
+ * of legacy formats or filtering of internal meta-data.
+ *
+ * @param string $blob The blob to be exported
+ * @param string|null $format The blob's serialization format
+ *
+ * @return string
+ */
+ public function exportTransform( $blob, $format = null ) {
+ return $blob;
+ }
+
+ /**
* Unserializes a Content object of the type supported by this ContentHandler.
*
* @since 1.21
*
- * @param string $blob serialized form of the content
- * @param $format null|String the format used for serialization
- * @return Content the Content object created by deserializing $blob
+ * @param string $blob Serialized form of the content
+ * @param string $format The format used for serialization
+ *
+ * @return Content The Content object created by deserializing $blob
*/
abstract public function unserializeContent( $blob, $format = null );
/**
+ * Apply import transformation (per default, returns $blob unchanged).
+ * This gives subclasses an opportunity to transform data blobs on import.
+ *
+ * @since 1.24
+ *
+ * @param string $blob
+ * @param string|null $format
+ *
+ * @return string
+ */
+ public function importTransform( $blob, $format = null ) {
+ return $blob;
+ }
+
+ /**
* Creates an empty Content object of the type supported by this
* ContentHandler.
*
@@ -438,7 +483,7 @@ abstract class ContentHandler {
/**
* Creates a new Content object that acts as a redirect to the given page,
- * or null of redirects are not supported by this content model.
+ * or null if redirects are not supported by this content model.
*
* This default implementation always returns null. Subclasses supporting redirects
* must override this method.
@@ -448,10 +493,10 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param Title $destination the page to redirect to.
- * @param string $text text to include in the redirect, if possible.
+ * @param Title $destination The page to redirect to.
+ * @param string $text Text to include in the redirect, if possible.
*
- * @return Content
+ * @return Content Always null.
*/
public function makeRedirectContent( Title $destination, $text = '' ) {
return null;
@@ -463,21 +508,19 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @return String The model ID
+ * @return string The model ID
*/
public function getModelID() {
return $this->mModelID;
}
/**
- * Throws an MWException if $model_id is not the ID of the content model
- * supported by this ContentHandler.
- *
* @since 1.21
*
* @param string $model_id The model to check
*
- * @throws MWException
+ * @throws MWException If the model ID is not the ID of the content model supported by this
+ * ContentHandler.
*/
protected function checkModelID( $model_id ) {
if ( $model_id !== $this->mModelID ) {
@@ -494,7 +537,7 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @return array of serialization formats as MIME type like strings
+ * @return string[] List of serialization formats as MIME type like strings
*/
public function getSupportedFormats() {
return $this->mSupportedFormats;
@@ -509,7 +552,7 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @return string the name of the default serialization format as a MIME type
+ * @return string The name of the default serialization format as a MIME type
*/
public function getDefaultFormat() {
return $this->mSupportedFormats[0];
@@ -524,11 +567,11 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param string $format the serialization format to check
+ * @param string $format The serialization format to check
+ *
* @return bool
*/
public function isSupportedFormat( $format ) {
-
if ( !$format ) {
return true; // this means "use the default"
}
@@ -537,13 +580,11 @@ abstract class ContentHandler {
}
/**
- * Throws an MWException if isSupportedFormat( $format ) is not true.
- * Convenient for checking whether a format provided as a parameter is
- * actually supported.
+ * Convenient for checking whether a format provided as a parameter is actually supported.
*
- * @param string $format the serialization format to check
+ * @param string $format The serialization format to check
*
- * @throws MWException
+ * @throws MWException If the format is not supported by this content handler.
*/
protected function checkFormat( $format ) {
if ( !$this->isSupportedFormat( $format ) ) {
@@ -562,7 +603,7 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @return Array
+ * @return array Always an empty array.
*/
public function getActionOverrides() {
return array();
@@ -573,21 +614,18 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $context IContextSource context to use, anything else will be
- * ignored
- * @param $old Integer Old ID we want to show and diff with.
- * @param int|string $new String either 'prev' or 'next'.
- * @param $rcid Integer ??? FIXME (default 0)
- * @param $refreshCache boolean If set, refreshes the diff cache
- * @param $unhide boolean If set, allow viewing deleted revs
+ * @param IContextSource $context Context to use, anything else will be ignored.
+ * @param int $old Revision ID we want to show and diff with.
+ * @param int|string $new Either a revision ID or one of the strings 'cur', 'prev' or 'next'.
+ * @param int $rcid FIXME: Deprecated, no longer used. Defaults to 0.
+ * @param bool $refreshCache If set, refreshes the diff cache. Defaults to false.
+ * @param bool $unhide If set, allow viewing deleted revs. Defaults to false.
*
* @return DifferenceEngine
*/
- public function createDifferenceEngine( IContextSource $context,
- $old = 0, $new = 0,
- $rcid = 0, # FIXME: use everywhere!
- $refreshCache = false, $unhide = false
- ) {
+ public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0,
+ $rcid = 0, //FIXME: Deprecated, no longer used
+ $refreshCache = false, $unhide = false ) {
$diffEngineClass = $this->getDiffEngineClass();
return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
@@ -596,19 +634,21 @@ abstract class ContentHandler {
/**
* Get the language in which the content of the given page is written.
*
- * This default implementation just returns $wgContLang (except for pages in the MediaWiki namespace)
+ * This default implementation just returns $wgContLang (except for pages
+ * in the MediaWiki namespace)
*
- * Note that the pages language is not cacheable, since it may in some cases depend on user settings.
+ * Note that the pages language is not cacheable, since it may in some
+ * cases depend on user settings.
*
* Also note that the page language may or may not depend on the actual content of the page,
* that is, this method may load the content in order to determine the language.
*
* @since 1.21
*
- * @param Title $title the page to determine the language for.
- * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+ * @param Title $title The page to determine the language for.
+ * @param Content $content The page's content, if you have it handy, to avoid reloading it.
*
- * @return Language the page's language
+ * @return Language The page's language
*/
public function getPageLanguage( Title $title, Content $content = null ) {
global $wgContLang, $wgLang;
@@ -621,6 +661,7 @@ abstract class ContentHandler {
}
wfRunHooks( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) );
+
return wfGetLangObj( $pageLang );
}
@@ -639,10 +680,10 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param Title $title the page to determine the language for.
- * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+ * @param Title $title The page to determine the language for.
+ * @param Content $content The page's content, if you have it handy, to avoid reloading it.
*
- * @return Language the page's language for viewing
+ * @return Language The page's language for viewing
*/
public function getPageViewLanguage( Title $title, Content $content = null ) {
$pageLang = $this->getPageLanguage( $title, $content );
@@ -668,12 +709,19 @@ abstract class ContentHandler {
* typically based on the namespace or some other aspect of the title, such as a special suffix
* (e.g. ".svg" for SVG content).
*
- * @param Title $title the page's title.
+ * @note this calls the ContentHandlerCanBeUsedOn hook which may be used to override which
+ * content model can be used where.
*
- * @return bool true if content of this kind can be used on the given page, false otherwise.
+ * @param Title $title The page's title.
+ *
+ * @return bool True if content of this kind can be used on the given page, false otherwise.
*/
public function canBeUsedOn( Title $title ) {
- return true;
+ $ok = true;
+
+ wfRunHooks( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) );
+
+ return $ok;
}
/**
@@ -688,19 +736,18 @@ abstract class ContentHandler {
}
/**
- * Attempts to merge differences between three versions.
- * Returns a new Content object for a clean merge and false for failure or
- * a conflict.
+ * Attempts to merge differences between three versions. Returns a new
+ * Content object for a clean merge and false for failure or a conflict.
*
* This default implementation always returns false.
*
* @since 1.21
*
- * @param $oldContent Content|string String
- * @param $myContent Content|string String
- * @param $yourContent Content|string String
+ * @param Content $oldContent The page's previous content.
+ * @param Content $myContent One of the page's conflicting contents.
+ * @param Content $yourContent One of the page's conflicting contents.
*
- * @return Content|Bool
+ * @return Content|bool Always false.
*/
public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
return false;
@@ -711,13 +758,14 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $oldContent Content|null: the previous text of the page.
- * @param $newContent Content|null: The submitted text of the page.
+ * @param Content $oldContent The previous text of the page.
+ * @param Content $newContent The submitted text of the page.
* @param int $flags Bit mask: a bit mask of flags submitted for the edit.
*
* @return string An appropriate auto-summary, or an empty string.
*/
- public function getAutosummary( Content $oldContent = null, Content $newContent = null, $flags ) {
+ public function getAutosummary( Content $oldContent = null, Content $newContent = null,
+ $flags ) {
// Decide what kind of auto-summary is needed.
// Redirect auto-summaries
@@ -733,15 +781,15 @@ abstract class ContentHandler {
if ( is_object( $rt ) ) {
if ( !is_object( $ot )
|| !$rt->equals( $ot )
- || $ot->getFragment() != $rt->getFragment() )
- {
+ || $ot->getFragment() != $rt->getFragment()
+ ) {
$truncatedtext = $newContent->getTextForSummary(
250
- - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
- - strlen( $rt->getFullText() ) );
+ - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
+ - strlen( $rt->getFullText() ) );
return wfMessage( 'autoredircomment', $rt->getFullText() )
- ->rawParams( $truncatedtext )->inContentLanguage()->text();
+ ->rawParams( $truncatedtext )->inContentLanguage()->text();
}
}
@@ -754,7 +802,7 @@ abstract class ContentHandler {
200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
- ->inContentLanguage()->text();
+ ->inContentLanguage()->text();
}
// Blanking auto-summaries
@@ -762,15 +810,20 @@ abstract class ContentHandler {
return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
} elseif ( !empty( $oldContent )
&& $oldContent->getSize() > 10 * $newContent->getSize()
- && $newContent->getSize() < 500 )
- {
+ && $newContent->getSize() < 500
+ ) {
// Removing more than 90% of the article
$truncatedtext = $newContent->getTextForSummary(
200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
- ->inContentLanguage()->text();
+ ->inContentLanguage()->text();
+ }
+
+ // New blank article auto-summary
+ if ( $flags & EDIT_NEW && $newContent->isEmpty() ) {
+ return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text();
}
// If we reach this point, there's no applicable auto-summary for our
@@ -783,12 +836,13 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $title Title: the page's title
- * @param &$hasHistory Boolean: whether the page has a history
+ * @param Title $title The page's title
+ * @param bool &$hasHistory Whether the page has a history
+ *
* @return mixed String containing deletion reason or empty string, or
* boolean false if no revision occurred
*
- * @XXX &$hasHistory is extremely ugly, it's here because
+ * @todo &$hasHistory is extremely ugly, it's here because
* WikiPage::getAutoDeleteReason() and Article::generateReason()
* have it / want it.
*/
@@ -891,9 +945,9 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @param $current Revision The current text
- * @param $undo Revision The revision to undo
- * @param $undoafter Revision Must be an earlier revision than $undo
+ * @param Revision $current The current text
+ * @param Revision $undo The revision to undo
+ * @param Revision $undoafter Must be an earlier revision than $undo
*
* @return mixed String on success, false on failure
*/
@@ -907,6 +961,10 @@ abstract class ContentHandler {
$undo_content = $undo->getContent();
$undoafter_content = $undoafter->getContent();
+ if ( !$undo_content || !$undoafter_content ) {
+ return false; // no content to undo
+ }
+
$this->checkModelID( $cur_content->getModel() );
$this->checkModelID( $undo_content->getModel() );
$this->checkModelID( $undoafter_content->getModel() );
@@ -922,7 +980,7 @@ abstract class ContentHandler {
}
/**
- * Get parser options suitable for rendering the primary article wikitext
+ * Get parser options suitable for rendering and caching the article
*
* @param IContextSource|User|string $context One of the following:
* - IContextSource: Use the User and the Language of the provided
@@ -932,8 +990,6 @@ abstract class ContentHandler {
* - 'canonical': Canonical options (anonymous user with default
* preferences and content language).
*
- * @param IContextSource|User|string $context
- *
* @throws MWException
* @return ParserOptions
*/
@@ -962,7 +1018,7 @@ abstract class ContentHandler {
*
* @since 1.21
*
- * @return bool
+ * @return bool Always false.
*/
public function isParserCacheSupported() {
return false;
@@ -975,7 +1031,7 @@ abstract class ContentHandler {
* Content models that return true here should also implement
* Content::getSection, Content::replaceSection, etc. to handle sections..
*
- * @return boolean whether sections are supported.
+ * @return bool Always false.
*/
public function supportsSections() {
return false;
@@ -988,7 +1044,7 @@ abstract class ContentHandler {
* Content models that return true here should also implement
* ContentHandler::makeRedirectContent to return a Content object.
*
- * @return boolean whether redirects are supported.
+ * @return bool Always false.
*/
public function supportsRedirects() {
return false;
@@ -998,11 +1054,11 @@ abstract class ContentHandler {
* Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if
* self::$enableDeprecationWarnings is set to true.
*
- * @param string $func The name of the deprecated function
- * @param string $version The version since the method is deprecated. Usually 1.21
- * for ContentHandler related stuff.
- * @param string|bool $component: Component to which the function belongs.
- * If false, it is assumed the function is in MediaWiki core.
+ * @param string $func The name of the deprecated function
+ * @param string $version The version since the method is deprecated. Usually 1.21
+ * for ContentHandler related stuff.
+ * @param string|bool $component : Component to which the function belongs.
+ * If false, it is assumed the function is in MediaWiki core.
*
* @see ContentHandler::$enableDeprecationWarnings
* @see wfDeprecated
@@ -1020,18 +1076,19 @@ abstract class ContentHandler {
* hook function, a new Content object is constructed from the new
* text.
*
- * @param string $event event name
- * @param array $args parameters passed to hook functions
- * @param bool $warn whether to log a warning.
+ * @param string $event Event name
+ * @param array $args Parameters passed to hook functions
+ * @param bool $warn Whether to log a warning.
* Default to self::$enableDeprecationWarnings.
* May be set to false for testing.
*
- * @return Boolean True if no handler aborted the hook
+ * @return bool True if no handler aborted the hook
*
* @see ContentHandler::$enableDeprecationWarnings
*/
public static function runLegacyHooks( $event, $args = array(),
- $warn = null ) {
+ $warn = null
+ ) {
if ( $warn === null ) {
$warn = self::$enableDeprecationWarnings;
@@ -1073,7 +1130,8 @@ abstract class ContentHandler {
wfRestoreWarnings();
- wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " . implode( ', ', $handlerInfo ), 2 );
+ wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " .
+ implode( ', ', $handlerInfo ), 2 );
}
// convert Content objects to text
diff --git a/includes/content/CssContent.php b/includes/content/CssContent.php
index 03cc2d00..8290603c 100644
--- a/includes/content/CssContent.php
+++ b/includes/content/CssContent.php
@@ -31,18 +31,26 @@
* @ingroup Content
*/
class CssContent extends TextContent {
- public function __construct( $text ) {
- parent::__construct( $text, CONTENT_MODEL_CSS );
+
+ /**
+ * @param string $text CSS code.
+ * @param string $modelId the content content model
+ */
+ public function __construct( $text, $modelId = CONTENT_MODEL_CSS ) {
+ parent::__construct( $text, $modelId );
}
/**
* Returns a Content object with pre-save transformations applied using
* Parser::preSaveTransform().
*
- * @param $title Title
- * @param $user User
- * @param $popts ParserOptions
- * @return Content
+ * @param Title $title
+ * @param User $user
+ * @param ParserOptions $popts
+ *
+ * @return CssContent
+ *
+ * @see TextContent::preSaveTransform
*/
public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
global $wgParser;
@@ -51,15 +59,19 @@ class CssContent extends TextContent {
$text = $this->getNativeData();
$pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
- return new CssContent( $pst );
+ return new static( $pst );
}
+ /**
+ * @return string CSS wrapped in a <pre> tag.
+ */
protected function getHtml() {
$html = "";
$html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
- $html .= $this->getHighlightHtml();
+ $html .= htmlspecialchars( $this->getNativeData() );
$html .= "\n</pre>\n";
return $html;
}
+
}
diff --git a/includes/content/CssContentHandler.php b/includes/content/CssContentHandler.php
index cb5a349d..b2a8676b 100644
--- a/includes/content/CssContentHandler.php
+++ b/includes/content/CssContentHandler.php
@@ -27,41 +27,16 @@
* @since 1.21
* @ingroup Content
*/
-class CssContentHandler extends TextContentHandler {
-
- public function __construct( $modelId = CONTENT_MODEL_CSS ) {
- parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) );
- }
-
- public function unserializeContent( $text, $format = null ) {
- $this->checkFormat( $format );
-
- return new CssContent( $text );
- }
-
- public function makeEmptyContent() {
- return new CssContent( '' );
- }
+class CssContentHandler extends CodeContentHandler {
/**
- * Returns the english language, because CSS is english, and should be handled as such.
- *
- * @return Language wfGetLangObj( 'en' )
- *
- * @see ContentHandler::getPageLanguage()
+ * @param string $modelId
*/
- public function getPageLanguage( Title $title, Content $content = null ) {
- return wfGetLangObj( 'en' );
+ public function __construct( $modelId = CONTENT_MODEL_CSS ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) );
}
- /**
- * Returns the english language, because CSS is english, and should be handled as such.
- *
- * @return Language wfGetLangObj( 'en' )
- *
- * @see ContentHandler::getPageViewLanguage()
- */
- public function getPageViewLanguage( Title $title, Content $content = null ) {
- return wfGetLangObj( 'en' );
+ protected function getContentClass() {
+ return 'CssContent';
}
}
diff --git a/includes/content/JavaScriptContent.php b/includes/content/JavaScriptContent.php
index 2ae572be..c0194c2e 100644
--- a/includes/content/JavaScriptContent.php
+++ b/includes/content/JavaScriptContent.php
@@ -31,8 +31,13 @@
* @ingroup Content
*/
class JavaScriptContent extends TextContent {
- public function __construct( $text ) {
- parent::__construct( $text, CONTENT_MODEL_JAVASCRIPT );
+
+ /**
+ * @param string $text JavaScript code.
+ * @param string $modelId the content model name
+ */
+ public function __construct( $text, $modelId = CONTENT_MODEL_JAVASCRIPT ) {
+ parent::__construct( $text, $modelId );
}
/**
@@ -42,7 +47,8 @@ class JavaScriptContent extends TextContent {
* @param Title $title
* @param User $user
* @param ParserOptions $popts
- * @return Content
+ *
+ * @return JavaScriptContent
*/
public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
global $wgParser;
@@ -52,15 +58,19 @@ class JavaScriptContent extends TextContent {
$text = $this->getNativeData();
$pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
- return new JavaScriptContent( $pst );
+ return new static( $pst );
}
+ /**
+ * @return string JavaScript wrapped in a <pre> tag.
+ */
protected function getHtml() {
$html = "";
$html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
- $html .= $this->getHighlightHtml();
+ $html .= htmlspecialchars( $this->getNativeData() );
$html .= "\n</pre>\n";
return $html;
}
+
}
diff --git a/includes/content/JavaScriptContentHandler.php b/includes/content/JavaScriptContentHandler.php
index 33fa9172..457b83d7 100644
--- a/includes/content/JavaScriptContentHandler.php
+++ b/includes/content/JavaScriptContentHandler.php
@@ -27,41 +27,16 @@
* @ingroup Content
* @todo make ScriptContentHandler base class, do highlighting stuff there?
*/
-class JavaScriptContentHandler extends TextContentHandler {
-
- public function __construct( $modelId = CONTENT_MODEL_JAVASCRIPT ) {
- parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) );
- }
-
- public function unserializeContent( $text, $format = null ) {
- $this->checkFormat( $format );
-
- return new JavaScriptContent( $text );
- }
-
- public function makeEmptyContent() {
- return new JavaScriptContent( '' );
- }
+class JavaScriptContentHandler extends CodeContentHandler {
/**
- * Returns the english language, because JS is english, and should be handled as such.
- *
- * @return Language wfGetLangObj( 'en' )
- *
- * @see ContentHandler::getPageLanguage()
+ * @param string $modelId
*/
- public function getPageLanguage( Title $title, Content $content = null ) {
- return wfGetLangObj( 'en' );
+ public function __construct( $modelId = CONTENT_MODEL_JAVASCRIPT ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) );
}
- /**
- * Returns the english language, because JS is english, and should be handled as such.
- *
- * @return Language wfGetLangObj( 'en' )
- *
- * @see ContentHandler::getPageViewLanguage()
- */
- public function getPageViewLanguage( Title $title, Content $content = null ) {
- return wfGetLangObj( 'en' );
+ protected function getContentClass() {
+ return 'JavaScriptContent';
}
}
diff --git a/includes/content/JsonContent.php b/includes/content/JsonContent.php
new file mode 100644
index 00000000..b36827c5
--- /dev/null
+++ b/includes/content/JsonContent.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * JSON Content Model
+ *
+ * @file
+ *
+ * @author Ori Livneh <ori@wikimedia.org>
+ * @author Kunal Mehta <legoktm@gmail.com>
+ */
+
+/**
+ * Represents the content of a JSON content.
+ * @since 1.24
+ */
+class JsonContent extends TextContent {
+
+ public function __construct( $text, $modelId = CONTENT_MODEL_JSON ) {
+ parent::__construct( $text, $modelId );
+ }
+
+ /**
+ * Decodes the JSON into a PHP associative array.
+ * @return array
+ */
+ public function getJsonData() {
+ return FormatJson::decode( $this->getNativeData(), true );
+ }
+
+ /**
+ * @return bool Whether content is valid JSON.
+ */
+ public function isValid() {
+ return $this->getJsonData() !== null;
+ }
+
+ /**
+ * Pretty-print JSON
+ *
+ * @return bool|null|string
+ */
+ public function beautifyJSON() {
+ $decoded = FormatJson::decode( $this->getNativeData(), true );
+ if ( !is_array( $decoded ) ) {
+ return null;
+ }
+ return FormatJson::encode( $decoded, true );
+
+ }
+
+ /**
+ * Beautifies JSON prior to save.
+ * @param Title $title Title
+ * @param User $user User
+ * @param ParserOptions $popts
+ * @return JsonContent
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ return new static( $this->beautifyJSON() );
+ }
+
+ /**
+ * Set the HTML and add the appropriate styles
+ *
+ *
+ * @param Title $title
+ * @param int $revId
+ * @param ParserOptions $options
+ * @param bool $generateHtml
+ * @param ParserOutput $output
+ */
+ protected function fillParserOutput( Title $title, $revId,
+ ParserOptions $options, $generateHtml, ParserOutput &$output
+ ) {
+ if ( $generateHtml ) {
+ $output->setText( $this->objectTable( $this->getJsonData() ) );
+ $output->addModuleStyles( 'mediawiki.content.json' );
+ } else {
+ $output->setText( '' );
+ }
+ }
+ /**
+ * Constructs an HTML representation of a JSON object.
+ * @param array $mapping
+ * @return string HTML
+ */
+ protected function objectTable( $mapping ) {
+ $rows = array();
+
+ foreach ( $mapping as $key => $val ) {
+ $rows[] = $this->objectRow( $key, $val );
+ }
+ return Xml::tags( 'table', array( 'class' => 'mw-json' ),
+ Xml::tags( 'tbody', array(), join( "\n", $rows ) )
+ );
+ }
+
+ /**
+ * Constructs HTML representation of a single key-value pair.
+ * @param string $key
+ * @param mixed $val
+ * @return string HTML.
+ */
+ protected function objectRow( $key, $val ) {
+ $th = Xml::elementClean( 'th', array(), $key );
+ if ( is_array( $val ) ) {
+ $td = Xml::tags( 'td', array(), self::objectTable( $val ) );
+ } else {
+ if ( is_string( $val ) ) {
+ $val = '"' . $val . '"';
+ } else {
+ $val = FormatJson::encode( $val );
+ }
+
+ $td = Xml::elementClean( 'td', array( 'class' => 'value' ), $val );
+ }
+
+ return Xml::tags( 'tr', array(), $th . $td );
+ }
+
+}
diff --git a/includes/content/JsonContentHandler.php b/includes/content/JsonContentHandler.php
new file mode 100644
index 00000000..392ce37b
--- /dev/null
+++ b/includes/content/JsonContentHandler.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * JSON Schema Content Handler
+ *
+ * @file
+ *
+ * @author Ori Livneh <ori@wikimedia.org>
+ * @author Kunal Mehta <legoktm@gmail.com>
+ */
+
+/**
+ * @since 1.24
+ */
+class JsonContentHandler extends CodeContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_JSON ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_JSON ) );
+ }
+
+ /**
+ * @return string
+ */
+ protected function getContentClass() {
+ return 'JsonContent';
+ }
+}
diff --git a/includes/content/MessageContent.php b/includes/content/MessageContent.php
index b36b670c..5b84657f 100644
--- a/includes/content/MessageContent.php
+++ b/includes/content/MessageContent.php
@@ -29,7 +29,7 @@
* Wrapper allowing us to handle a system message as a Content object.
* Note that this is generally *not* used to represent content from the
* MediaWiki namespace, and that there is no MessageContentHandler.
- * MessageContent is just intended as glue for wrapping a message programatically.
+ * MessageContent is just intended as glue for wrapping a message programmatically.
*
* @ingroup Content
*/
@@ -41,8 +41,8 @@ class MessageContent extends AbstractContent {
protected $mMessage;
/**
- * @param Message|String $msg A Message object, or a message key
- * @param array|null $params An optional array of message parameters
+ * @param Message|string $msg A Message object, or a message key.
+ * @param string[] $params An optional array of message parameters.
*/
public function __construct( $msg, $params = null ) {
# XXX: messages may be wikitext, html or plain text! and maybe even something else entirely.
@@ -60,18 +60,18 @@ class MessageContent extends AbstractContent {
}
/**
- * Returns the message as rendered HTML
+ * Fully parse the text from wikitext to HTML.
*
- * @return string The message text, parsed into html
+ * @return string Parsed HTML.
*/
public function getHtml() {
return $this->mMessage->parse();
}
/**
- * Returns the message as rendered HTML
+ * Returns the message text. {{-transformation is done.
*
- * @return string The message text, parsed into html
+ * @return string Unescaped message text.
*/
public function getWikitext() {
return $this->mMessage->text();
@@ -88,6 +88,8 @@ class MessageContent extends AbstractContent {
}
/**
+ * @return string
+ *
* @see Content::getTextForSearchIndex
*/
public function getTextForSearchIndex() {
@@ -95,6 +97,8 @@ class MessageContent extends AbstractContent {
}
/**
+ * @return string
+ *
* @see Content::getWikitextForTransclusion
*/
public function getWikitextForTransclusion() {
@@ -102,6 +106,10 @@ class MessageContent extends AbstractContent {
}
/**
+ * @param int $maxlength Maximum length of the summary text, defaults to 250.
+ *
+ * @return string The summary text.
+ *
* @see Content::getTextForSummary
*/
public function getTextForSummary( $maxlength = 250 ) {
@@ -109,18 +117,18 @@ class MessageContent extends AbstractContent {
}
/**
- * @see Content::getSize
- *
* @return int
+ *
+ * @see Content::getSize
*/
public function getSize() {
return strlen( $this->mMessage->plain() );
}
/**
- * @see Content::copy
+ * @return Content A copy of this object
*
- * @return Content. A copy of this object
+ * @see Content::copy
*/
public function copy() {
// MessageContent is immutable (because getNativeData() returns a clone of the Message object)
@@ -128,24 +136,28 @@ class MessageContent extends AbstractContent {
}
/**
- * @see Content::isCountable
+ * @param bool $hasLinks
+ *
+ * @return bool Always false.
*
- * @return bool false
+ * @see Content::isCountable
*/
public function isCountable( $hasLinks = null ) {
return false;
}
/**
- * @see Content::getParserOutput
+ * @param Title $title Unused.
+ * @param int $revId Unused.
+ * @param ParserOptions $options Unused.
+ * @param bool $generateHtml Whether to generate HTML (default: true).
*
* @return ParserOutput
+ *
+ * @see Content::getParserOutput
*/
- public function getParserOutput(
- Title $title, $revId = null,
- ParserOptions $options = null, $generateHtml = true
- ) {
-
+ public function getParserOutput( Title $title, $revId = null,
+ ParserOptions $options = null, $generateHtml = true ) {
if ( $generateHtml ) {
$html = $this->getHtml();
} else {
@@ -153,6 +165,10 @@ class MessageContent extends AbstractContent {
}
$po = new ParserOutput( $html );
+ // Message objects are in the user language.
+ $po->recordOption( 'userlang' );
+
return $po;
}
+
}
diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php
index f66dacd7..c479f202 100644
--- a/includes/content/TextContent.php
+++ b/includes/content/TextContent.php
@@ -34,12 +34,16 @@
*/
class TextContent extends AbstractContent {
+ /**
+ * @param string $text
+ * @param string $model_id
+ */
public function __construct( $text, $model_id = CONTENT_MODEL_TEXT ) {
parent::__construct( $model_id );
if ( $text === null || $text === false ) {
wfWarn( "TextContent constructed with \$text = " . var_export( $text, true ) . "! "
- . "This may indicate an error in the caller's scope." );
+ . "This may indicate an error in the caller's scope.", 2 );
$text = '';
}
@@ -51,6 +55,11 @@ class TextContent extends AbstractContent {
$this->mText = $text;
}
+ /**
+ * @note Mutable subclasses MUST override this to return a copy!
+ *
+ * @return Content $this
+ */
public function copy() {
return $this; # NOTE: this is ok since TextContent are immutable.
}
@@ -68,12 +77,13 @@ class TextContent extends AbstractContent {
}
/**
- * returns the text's size in bytes.
+ * Returns the text's size in bytes.
*
- * @return int The size
+ * @return int
*/
public function getSize() {
$text = $this->getNativeData();
+
return strlen( $text );
}
@@ -81,10 +91,10 @@ class TextContent extends AbstractContent {
* Returns true if this content is not a redirect, and $wgArticleCountMethod
* is "any".
*
- * @param bool $hasLinks if it is known whether this content contains links,
+ * @param bool $hasLinks If it is known whether this content contains links,
* provide this information here, to avoid redundant parsing to find out.
*
- * @return bool True if the content is countable
+ * @return bool
*/
public function isCountable( $hasLinks = null ) {
global $wgArticleCountMethod;
@@ -103,17 +113,16 @@ class TextContent extends AbstractContent {
/**
* Returns the text represented by this Content object, as a string.
*
- * @return string: the raw text
+ * @return string The raw text.
*/
public function getNativeData() {
- $text = $this->mText;
- return $text;
+ return $this->mText;
}
/**
* Returns the text represented by this Content object, as a string.
*
- * @return string: the raw text
+ * @return string The raw text.
*/
public function getTextForSearchIndex() {
return $this->getNativeData();
@@ -123,9 +132,9 @@ class TextContent extends AbstractContent {
* Returns attempts to convert this content object to wikitext,
* and then returns the text string. The conversion may be lossy.
*
- * @note: this allows any text-based content to be transcluded as if it was wikitext.
+ * @note this allows any text-based content to be transcluded as if it was wikitext.
*
- * @return string|false: the raw text, or null if the conversion failed
+ * @return string|bool The raw text, or false if the conversion failed.
*/
public function getWikitextForTransclusion() {
$wikitext = $this->convert( CONTENT_MODEL_WIKITEXT, 'lossy' );
@@ -141,29 +150,29 @@ class TextContent extends AbstractContent {
* Returns a Content object with pre-save transformations applied.
* This implementation just trims trailing whitespace.
*
- * @param $title Title
- * @param $user User
- * @param $popts ParserOptions
+ * @param Title $title
+ * @param User $user
+ * @param ParserOptions $popts
+ *
* @return Content
*/
public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
$text = $this->getNativeData();
$pst = rtrim( $text );
- return ( $text === $pst ) ? $this : new WikitextContent( $pst );
+ return ( $text === $pst ) ? $this : new static( $pst, $this->getModel() );
}
/**
* Diff this content object with another content object.
*
- * @since 1.21diff
+ * @since 1.21
*
- * @param $that Content: The other content object to compare this content
- * object to.
- * @param $lang Language: The language object to use for text segmentation.
+ * @param Content $that The other content object to compare this content object to.
+ * @param Language $lang The language object to use for text segmentation.
* If not given, $wgContentLang is used.
*
- * @return DiffResult: A diff representing the changes that would have to be
+ * @return Diff A diff representing the changes that would have to be
* made to this content object to make it equal to $that.
*/
public function diff( Content $that, Language $lang = null ) {
@@ -178,43 +187,42 @@ class TextContent extends AbstractContent {
}
$otext = $this->getNativeData();
- $ntext = $this->getNativeData();
+ $ntext = $that->getNativeData();
# Note: Use native PHP diff, external engines don't give us abstract output
$ota = explode( "\n", $lang->segmentForDiff( $otext ) );
$nta = explode( "\n", $lang->segmentForDiff( $ntext ) );
$diff = new Diff( $ota, $nta );
+
return $diff;
}
/**
- * Returns a generic ParserOutput object, wrapping the HTML returned by
- * getHtml().
+ * Fills the provided ParserOutput object with information derived from the content.
+ * Unless $generateHtml was false, this includes an HTML representation of the content
+ * provided by getHtml().
*
- * @param $title Title Context title for parsing
- * @param int|null $revId Revision ID (for {{REVISIONID}})
- * @param $options ParserOptions|null Parser options
- * @param bool $generateHtml Whether or not to generate HTML
+ * For content models listed in $wgTextModelsToParse, this method will call the MediaWiki
+ * wikitext parser on the text to extract any (wikitext) links, magic words, etc.
*
- * @return ParserOutput representing the HTML form of the text
+ * Subclasses may override this to provide custom content processing.
+ * For custom HTML generation alone, it is sufficient to override getHtml().
+ *
+ * @param Title $title Context title for parsing
+ * @param int $revId Revision ID (for {{REVISIONID}})
+ * @param ParserOptions $options Parser options
+ * @param bool $generateHtml Whether or not to generate HTML
+ * @param ParserOutput $output The output object to fill (reference).
*/
- public function getParserOutput( Title $title,
- $revId = null,
- ParserOptions $options = null, $generateHtml = true
+ protected function fillParserOutput( Title $title, $revId,
+ ParserOptions $options, $generateHtml, ParserOutput &$output
) {
global $wgParser, $wgTextModelsToParse;
- if ( !$options ) {
- //NOTE: use canonical options per default to produce cacheable output
- $options = $this->getContentHandler()->makeParserOptions( 'canonical' );
- }
-
if ( in_array( $this->getModel(), $wgTextModelsToParse ) ) {
- // parse just to get links etc into the database
- $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
- } else {
- $po = new ParserOutput();
+ // parse just to get links etc into the database, HTML is replaced below.
+ $output = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
}
if ( $generateHtml ) {
@@ -223,18 +231,19 @@ class TextContent extends AbstractContent {
$html = '';
}
- $po->setText( $html );
- return $po;
+ $output->setText( $html );
}
/**
* Generates an HTML version of the content, for display. Used by
- * getParserOutput() to construct a ParserOutput object.
+ * fillParserOutput() to provide HTML for the ParserOutput object.
+ *
+ * Subclasses may override this to provide a custom HTML rendering.
+ * If further information is to be derived from the content (such as
+ * categories), the fillParserOutput() method can be overridden instead.
*
- * This default implementation just calls getHighlightHtml(). Content
- * models that have another mapping to HTML (as is the case for markup
- * languages like wikitext) should override this method to generate the
- * appropriate HTML.
+ * For backwards-compatibility, this default implementation just calls
+ * getHighlightHtml().
*
* @return string An HTML representation of the content
*/
@@ -243,28 +252,37 @@ class TextContent extends AbstractContent {
}
/**
- * Generates a syntax-highlighted version of the content, as HTML.
- * Used by the default implementation of getHtml().
+ * Generates an HTML version of the content, for display.
+ *
+ * This default implementation returns an HTML-escaped version
+ * of the raw text content.
+ *
+ * @note The functionality of this method should really be implemented
+ * in getHtml(), and subclasses should override getHtml() if needed.
+ * getHighlightHtml() is kept around for backward compatibility with
+ * extensions that already override it.
+ *
+ * @deprecated since 1.24. Use getHtml() instead. In particular, subclasses overriding
+ * getHighlightHtml() should override getHtml() instead.
*
- * @return string an HTML representation of the content's markup
+ * @return string An HTML representation of the content
*/
protected function getHighlightHtml() {
- # TODO: make Highlighter interface, use highlighter here, if available
return htmlspecialchars( $this->getNativeData() );
}
/**
- * @see Content::convert()
- *
* This implementation provides lossless conversion between content models based
* on TextContent.
*
- * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags.
- * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is
- * not allowed, full round-trip conversion is expected to work without losing information.
+ * @param string $toModel The desired content model, use the CONTENT_MODEL_XXX flags.
+ * @param string $lossy Flag, set to "lossy" to allow lossy conversion. If lossy conversion is not
+ * allowed, full round-trip conversion is expected to work without losing information.
*
- * @return Content|bool A content object with the content model $toModel, or false if
- * that conversion is not supported.
+ * @return Content|bool A content object with the content model $toModel, or false if that
+ * conversion is not supported.
+ *
+ * @see Content::convert()
*/
public function convert( $toModel, $lossy = '' ) {
$converted = parent::convert( $toModel, $lossy );
@@ -276,11 +294,12 @@ class TextContent extends AbstractContent {
$toHandler = ContentHandler::getForModelID( $toModel );
if ( $toHandler instanceof TextContentHandler ) {
- //NOTE: ignore content serialization format - it's just text anyway.
+ // NOTE: ignore content serialization format - it's just text anyway.
$text = $this->getNativeData();
$converted = $toHandler->unserializeContent( $text );
}
return $converted;
}
+
}
diff --git a/includes/content/TextContentHandler.php b/includes/content/TextContentHandler.php
index e7f41e18..ffe1acbd 100644
--- a/includes/content/TextContentHandler.php
+++ b/includes/content/TextContentHandler.php
@@ -30,19 +30,24 @@
*/
class TextContentHandler extends ContentHandler {
- public function __construct( $modelId = CONTENT_MODEL_TEXT, $formats = array( CONTENT_FORMAT_TEXT ) ) {
+ // @codingStandardsIgnoreStart bug 57585
+ public function __construct( $modelId = CONTENT_MODEL_TEXT,
+ $formats = array( CONTENT_FORMAT_TEXT ) ) {
parent::__construct( $modelId, $formats );
}
+ // @codingStandardsIgnoreEnd
/**
* Returns the content's text as-is.
*
- * @param $content Content
- * @param $format string|null
+ * @param Content $content
+ * @param string $format The serialization format to check
+ *
* @return mixed
*/
public function serializeContent( Content $content, $format = null ) {
$this->checkFormat( $format );
+
return $content->getNativeData();
}
@@ -55,11 +60,11 @@ class TextContentHandler extends ContentHandler {
*
* This text-based implementation uses wfMerge().
*
- * @param $oldContent Content|string String
- * @param $myContent Content|string String
- * @param $yourContent Content|string String
+ * @param Content $oldContent The page's previous content.
+ * @param Content $myContent One of the page's conflicting contents.
+ * @param Content $yourContent One of the page's conflicting contents.
*
- * @return Content|Bool
+ * @return Content|bool
*/
public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
$this->checkModelID( $oldContent->getModel() );
@@ -83,23 +88,38 @@ class TextContentHandler extends ContentHandler {
}
$mergedContent = $this->unserializeContent( $result, $format );
+
return $mergedContent;
}
/**
+ * Returns the name of the associated Content class, to
+ * be used when creating new objects. Override expected
+ * by subclasses.
+ *
+ * @since 1.24
+ *
+ * @return string
+ */
+ protected function getContentClass() {
+ return 'TextContent';
+ }
+
+ /**
* Unserializes a Content object of the type supported by this ContentHandler.
*
* @since 1.21
*
- * @param $text string serialized form of the content
- * @param $format null|String the format used for serialization
+ * @param string $text Serialized form of the content
+ * @param string $format The format used for serialization
*
- * @return Content the TextContent object wrapping $text
+ * @return Content The TextContent object wrapping $text
*/
public function unserializeContent( $text, $format = null ) {
$this->checkFormat( $format );
- return new TextContent( $text );
+ $class = $this->getContentClass();
+ return new $class( $text );
}
/**
@@ -107,9 +127,11 @@ class TextContentHandler extends ContentHandler {
*
* @since 1.21
*
- * @return Content
+ * @return Content A new TextContent object with empty text.
*/
public function makeEmptyContent() {
- return new TextContent( '' );
+ $class = $this->getContentClass();
+ return new $class( '' );
}
+
}
diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php
index 26337db9..9a8ab3a6 100644
--- a/includes/content/WikitextContent.php
+++ b/includes/content/WikitextContent.php
@@ -31,31 +31,43 @@
* @ingroup Content
*/
class WikitextContent extends TextContent {
+ private $redirectTargetAndText = null;
public function __construct( $text ) {
parent::__construct( $text, CONTENT_MODEL_WIKITEXT );
}
/**
+ * @param string|number $sectionId
+ *
+ * @return Content|bool|null
+ *
* @see Content::getSection()
*/
- public function getSection( $section ) {
+ public function getSection( $sectionId ) {
global $wgParser;
$text = $this->getNativeData();
- $sect = $wgParser->getSection( $text, $section, false );
+ $sect = $wgParser->getSection( $text, $sectionId, false );
if ( $sect === false ) {
return false;
} else {
- return new WikitextContent( $sect );
+ return new static( $sect );
}
}
/**
+ * @param string|number|null|bool $sectionId
+ * @param Content $with
+ * @param string $sectionTitle
+ *
+ * @throws MWException
+ * @return Content
+ *
* @see Content::replaceSection()
*/
- public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
+ public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) {
wfProfileIn( __METHOD__ );
$myModelId = $this->getModel();
@@ -71,13 +83,16 @@ class WikitextContent extends TextContent {
$oldtext = $this->getNativeData();
$text = $with->getNativeData();
- if ( $section === '' ) {
+ if ( strval( $sectionId ) === '' ) {
wfProfileOut( __METHOD__ );
+
return $with; # XXX: copy first?
- } if ( $section == 'new' ) {
+ }
+
+ if ( $sectionId === 'new' ) {
# Inserting a new section
$subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
- ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
+ ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
$text = strlen( trim( $oldtext ) ) > 0
? "{$oldtext}\n\n{$subject}{$text}"
@@ -87,12 +102,13 @@ class WikitextContent extends TextContent {
# Replacing an existing section; roll out the big guns
global $wgParser;
- $text = $wgParser->replaceSection( $oldtext, $section, $text );
+ $text = $wgParser->replaceSection( $oldtext, $sectionId, $text );
}
- $newContent = new WikitextContent( $text );
+ $newContent = new static( $text );
wfProfileOut( __METHOD__ );
+
return $newContent;
}
@@ -100,7 +116,8 @@ class WikitextContent extends TextContent {
* Returns a new WikitextContent object with the given section heading
* prepended.
*
- * @param $header string
+ * @param string $header
+ *
* @return Content
*/
public function addSectionHeader( $header ) {
@@ -109,16 +126,17 @@ class WikitextContent extends TextContent {
$text .= "\n\n";
$text .= $this->getNativeData();
- return new WikitextContent( $text );
+ return new static( $text );
}
/**
* Returns a Content object with pre-save transformations applied using
* Parser::preSaveTransform().
*
- * @param $title Title
- * @param $user User
- * @param $popts ParserOptions
+ * @param Title $title
+ * @param User $user
+ * @param ParserOptions $popts
+ *
* @return Content
*/
public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
@@ -128,50 +146,58 @@ class WikitextContent extends TextContent {
$pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
rtrim( $pst );
- return ( $text === $pst ) ? $this : new WikitextContent( $pst );
+ return ( $text === $pst ) ? $this : new static( $pst );
}
/**
* Returns a Content object with preload transformations applied (or this
* object if no transformations apply).
*
- * @param $title Title
- * @param $popts ParserOptions
+ * @param Title $title
+ * @param ParserOptions $popts
+ * @param array $params
+ *
* @return Content
*/
- public function preloadTransform( Title $title, ParserOptions $popts ) {
+ public function preloadTransform( Title $title, ParserOptions $popts, $params = array() ) {
global $wgParser;
$text = $this->getNativeData();
- $plt = $wgParser->getPreloadText( $text, $title, $popts );
+ $plt = $wgParser->getPreloadText( $text, $title, $popts, $params );
- return new WikitextContent( $plt );
+ return new static( $plt );
}
/**
- * Implement redirect extraction for wikitext.
+ * Extract the redirect target and the remaining text on the page.
*
- * @return null|Title
+ * @note migrated here from Title::newFromRedirectInternal()
*
- * @note: migrated here from Title::newFromRedirectInternal()
+ * @since 1.23
*
- * @see Content::getRedirectTarget
- * @see AbstractContent::getRedirectTarget
+ * @return array List of two elements: Title|null and string.
*/
- public function getRedirectTarget() {
+ protected function getRedirectTargetAndText() {
global $wgMaxRedirects;
+
+ if ( $this->redirectTargetAndText !== null ) {
+ return $this->redirectTargetAndText;
+ }
+
if ( $wgMaxRedirects < 1 ) {
// redirects are disabled, so quit early
- return null;
+ $this->redirectTargetAndText = array( null, $this->getNativeData() );
+ return $this->redirectTargetAndText;
}
+
$redir = MagicWord::get( 'redirect' );
- $text = trim( $this->getNativeData() );
+ $text = ltrim( $this->getNativeData() );
if ( $redir->matchStartAndRemove( $text ) ) {
// Extract the first link and see if it's usable
// Ensure that it really does come directly after #REDIRECT
// Some older redirects included a colon, so don't freak about that!
$m = array();
- if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
+ if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}\s*!', $text, $m ) ) {
// Strip preceding colon used to "escape" categories, etc.
// and URL-decode links
if ( strpos( $m[1], '%' ) !== false ) {
@@ -181,17 +207,33 @@ class WikitextContent extends TextContent {
$title = Title::newFromText( $m[1] );
// If the title is a redirect to bad special pages or is invalid, return null
if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
- return null;
+ $this->redirectTargetAndText = array( null, $this->getNativeData() );
+ return $this->redirectTargetAndText;
}
- return $title;
+
+ $this->redirectTargetAndText = array( $title, substr( $text, strlen( $m[0] ) ) );
+ return $this->redirectTargetAndText;
}
}
- return null;
+
+ $this->redirectTargetAndText = array( null, $this->getNativeData() );
+ return $this->redirectTargetAndText;
}
/**
- * @see Content::updateRedirect()
+ * Implement redirect extraction for wikitext.
*
+ * @return Title|null
+ *
+ * @see Content::getRedirectTarget
+ */
+ public function getRedirectTarget() {
+ list( $title, ) = $this->getRedirectTargetAndText();
+
+ return $title;
+ }
+
+ /**
* This implementation replaces the first link on the page with the given new target
* if this Content object is a redirect. Otherwise, this method returns $this.
*
@@ -199,7 +241,9 @@ class WikitextContent extends TextContent {
*
* @param Title $target
*
- * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect)
+ * @return Content
+ *
+ * @see Content::updateRedirect()
*/
public function updateRedirect( Title $target ) {
if ( !$this->isRedirect() ) {
@@ -213,21 +257,19 @@ class WikitextContent extends TextContent {
'[[' . $target->getFullText() . ']]',
$this->getNativeData(), 1 );
- return new WikitextContent( $newText );
+ return new static( $newText );
}
/**
* Returns true if this content is not a redirect, and this content's text
* is countable according to the criteria defined by $wgArticleCountMethod.
*
- * @param bool $hasLinks if it is known whether this content contains
+ * @param bool $hasLinks If it is known whether this content contains
* links, provide this information here, to avoid redundant parsing to
* find out (default: null).
- * @param $title Title: (default: null)
- *
- * @internal param \IContextSource $context context for parsing if necessary
+ * @param Title $title Optional title, defaults to the title from the current main request.
*
- * @return bool True if the content is countable
+ * @return bool
*/
public function isCountable( $hasLinks = null, Title $title = null ) {
global $wgArticleCountMethod;
@@ -261,6 +303,10 @@ class WikitextContent extends TextContent {
return false;
}
+ /**
+ * @param int $maxlength
+ * @return string
+ */
public function getTextForSummary( $maxlength = 250 ) {
$truncatedtext = parent::getTextForSummary( $maxlength );
@@ -276,31 +322,39 @@ class WikitextContent extends TextContent {
* Returns a ParserOutput object resulting from parsing the content's text
* using $wgParser.
*
- * @since 1.21
- *
- * @param $title Title
+ * @param Title $title
* @param int $revId Revision to pass to the parser (default: null)
- * @param $options ParserOptions (default: null)
- * @param bool $generateHtml (default: false)
- *
- * @internal param \IContextSource|null $context
- * @return ParserOutput representing the HTML form of the text
+ * @param ParserOptions $options (default: null)
+ * @param bool $generateHtml (default: true)
+ * @param ParserOutput &$output ParserOutput representing the HTML form of the text,
+ * may be manipulated or replaced.
*/
- public function getParserOutput( Title $title,
- $revId = null,
- ParserOptions $options = null, $generateHtml = true
+ protected function fillParserOutput( Title $title, $revId,
+ ParserOptions $options, $generateHtml, ParserOutput &$output
) {
global $wgParser;
- if ( !$options ) {
- //NOTE: use canonical options per default to produce cacheable output
- $options = $this->getContentHandler()->makeParserOptions( 'canonical' );
+ list( $redir, $text ) = $this->getRedirectTargetAndText();
+ $output = $wgParser->parse( $text, $title, $options, true, true, $revId );
+
+ // Add redirect indicator at the top
+ if ( $redir ) {
+ // Make sure to include the redirect link in pagelinks
+ $output->addLink( $redir );
+ if ( $generateHtml ) {
+ $chain = $this->getRedirectChain();
+ $output->setText(
+ Article::getRedirectHeaderHtml( $title->getPageLanguage(), $chain, false ) .
+ $output->getText()
+ );
+ $output->addModuleStyles( 'mediawiki.action.view.redirectPage' );
+ }
}
-
- $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
- return $po;
}
+ /**
+ * @throws MWException
+ */
protected function getHtml() {
throw new MWException(
"getHtml() not implemented for wikitext. "
@@ -309,15 +363,16 @@ class WikitextContent extends TextContent {
}
/**
- * @see Content::matchMagicWord()
- *
* This implementation calls $word->match() on the this TextContent object's text.
*
* @param MagicWord $word
*
- * @return bool whether this Content object matches the given magic word.
+ * @return bool
+ *
+ * @see Content::matchMagicWord()
*/
public function matchMagicWord( MagicWord $word ) {
return $word->match( $this->getNativeData() );
}
+
}
diff --git a/includes/content/WikitextContentHandler.php b/includes/content/WikitextContentHandler.php
index b1b461fb..c1db1de6 100644
--- a/includes/content/WikitextContentHandler.php
+++ b/includes/content/WikitextContentHandler.php
@@ -34,30 +34,19 @@ class WikitextContentHandler extends TextContentHandler {
parent::__construct( $modelId, array( CONTENT_FORMAT_WIKITEXT ) );
}
- public function unserializeContent( $text, $format = null ) {
- $this->checkFormat( $format );
-
- return new WikitextContent( $text );
- }
-
- /**
- * @see ContentHandler::makeEmptyContent
- *
- * @return Content
- */
- public function makeEmptyContent() {
- return new WikitextContent( '' );
+ protected function getContentClass() {
+ return 'WikitextContent';
}
/**
* Returns a WikitextContent object representing a redirect to the given destination page.
*
- * @see ContentHandler::makeRedirectContent
- *
- * @param Title $destination the page to redirect to.
- * @param string $text text to include in the redirect, if possible.
+ * @param Title $destination The page to redirect to.
+ * @param string $text Text to include in the redirect, if possible.
*
* @return Content
+ *
+ * @see ContentHandler::makeRedirectContent
*/
public function makeRedirectContent( Title $destination, $text = '' ) {
$optionalColon = '';
@@ -72,20 +61,23 @@ class WikitextContentHandler extends TextContentHandler {
}
$mwRedir = MagicWord::get( 'redirect' );
- $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $optionalColon . $destination->getFullText() . ']]';
+ $redirectText = $mwRedir->getSynonym( 0 ) .
+ ' [[' . $optionalColon . $destination->getFullText() . ']]';
+
if ( $text != '' ) {
$redirectText .= "\n" . $text;
}
- return new WikitextContent( $redirectText );
+ $class = $this->getContentClass();
+ return new $class( $redirectText );
}
/**
* Returns true because wikitext supports redirects.
*
- * @see ContentHandler::supportsRedirects
+ * @return bool Always true.
*
- * @return boolean whether redirects are supported.
+ * @see ContentHandler::supportsRedirects
*/
public function supportsRedirects() {
return true;
@@ -94,7 +86,9 @@ class WikitextContentHandler extends TextContentHandler {
/**
* Returns true because wikitext supports sections.
*
- * @return boolean whether sections are supported.
+ * @return bool Always true.
+ *
+ * @see ContentHandler::supportsSections
*/
public function supportsSections() {
return true;
@@ -105,9 +99,13 @@ class WikitextContentHandler extends TextContentHandler {
* ParserCache mechanism.
*
* @since 1.21
- * @return bool
+ *
+ * @return bool Always true.
+ *
+ * @see ContentHandler::isParserCacheSupported
*/
public function isParserCacheSupported() {
return true;
}
+
}
diff --git a/includes/context/ContextSource.php b/includes/context/ContextSource.php
index e13cfa88..076504ec 100644
--- a/includes/context/ContextSource.php
+++ b/includes/context/ContextSource.php
@@ -34,16 +34,18 @@ abstract class ContextSource implements IContextSource {
private $context;
/**
- * Get the RequestContext object
+ * Get the base IContextSource object
* @since 1.18
- * @return RequestContext
+ * @return IContextSource
*/
public function getContext() {
if ( $this->context === null ) {
$class = get_class( $this );
- wfDebug( __METHOD__ . " ($class): called and \$context is null. Using RequestContext::getMain() for sanity\n" );
+ wfDebug( __METHOD__ . " ($class): called and \$context is null. " .
+ "Using RequestContext::getMain() for sanity\n" );
$this->context = RequestContext::getMain();
}
+
return $this->context;
}
@@ -58,6 +60,16 @@ abstract class ContextSource implements IContextSource {
}
/**
+ * Get the Config object
+ *
+ * @since 1.23
+ * @return Config
+ */
+ public function getConfig() {
+ return $this->getContext()->getConfig();
+ }
+
+ /**
* Get the WebRequest object
*
* @since 1.18
@@ -71,7 +83,7 @@ abstract class ContextSource implements IContextSource {
* Get the Title object
*
* @since 1.18
- * @return Title
+ * @return Title|null
*/
public function getTitle() {
return $this->getContext()->getTitle();
@@ -125,17 +137,6 @@ abstract class ContextSource implements IContextSource {
/**
* Get the Language object
*
- * @deprecated since 1.19 Use getLanguage instead
- * @return Language
- */
- public function getLang() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->getLanguage();
- }
-
- /**
- * Get the Language object
- *
* @since 1.19
* @return Language
*/
@@ -162,6 +163,7 @@ abstract class ContextSource implements IContextSource {
*/
public function msg( /* $args */ ) {
$args = func_get_args();
+
return call_user_func_array( array( $this->getContext(), 'msg' ), $args );
}
@@ -169,7 +171,7 @@ abstract class ContextSource implements IContextSource {
* Export the resolved user IP, HTTP headers, user ID, and session ID.
* The result will be reasonably sized to allow for serialization.
*
- * @return Array
+ * @return array
* @since 1.21
*/
public function exportSession() {
diff --git a/includes/context/DerivativeContext.php b/includes/context/DerivativeContext.php
index fd9bf963..b8966f0c 100644
--- a/includes/context/DerivativeContext.php
+++ b/includes/context/DerivativeContext.php
@@ -66,6 +66,11 @@ class DerivativeContext extends ContextSource {
private $skin;
/**
+ * @var Config
+ */
+ private $config;
+
+ /**
* Constructor
* @param IContextSource $context Context to inherit from
*/
@@ -74,6 +79,28 @@ class DerivativeContext extends ContextSource {
}
/**
+ * Set the SiteConfiguration object
+ *
+ * @param Config $s
+ */
+ public function setConfig( Config $s ) {
+ $this->config = $s;
+ }
+
+ /**
+ * Get the Config object
+ *
+ * @return Config
+ */
+ public function getConfig() {
+ if ( !is_null( $this->config ) ) {
+ return $this->config;
+ } else {
+ return $this->getContext()->getConfig();
+ }
+ }
+
+ /**
* Set the WebRequest object
*
* @param WebRequest $r
@@ -100,17 +127,14 @@ class DerivativeContext extends ContextSource {
*
* @param Title $t
*/
- public function setTitle( $t ) {
- if ( $t !== null && !$t instanceof Title ) {
- throw new MWException( __METHOD__ . " expects an instance of Title" );
- }
+ public function setTitle( Title $t ) {
$this->title = $t;
}
/**
* Get the Title object
*
- * @return Title
+ * @return Title|null
*/
public function getTitle() {
if ( !is_null( $this->title ) ) {
@@ -212,17 +236,6 @@ class DerivativeContext extends ContextSource {
/**
* Set the Language object
*
- * @deprecated since 1.19 Use setLanguage instead
- * @param Language|string $l Language instance or language code
- */
- public function setLang( $l ) {
- wfDeprecated( __METHOD__, '1.19' );
- $this->setLanguage( $l );
- }
-
- /**
- * Set the Language object
- *
* @param Language|string $l Language instance or language code
* @throws MWException
* @since 1.19
@@ -240,15 +253,6 @@ class DerivativeContext extends ContextSource {
}
/**
- * @deprecated since 1.19 Use getLanguage instead
- * @return Language
- */
- public function getLang() {
- wfDeprecated( __METHOD__, '1.19' );
- $this->getLanguage();
- }
-
- /**
* Get the Language object
*
* @return Language
@@ -292,12 +296,12 @@ class DerivativeContext extends ContextSource {
* it would set only the original context, and not take
* into account any changes.
*
- * @param String Message name
- * @param Variable number of message arguments
+ * @param mixed $args,... Arguments to wfMessage
* @return Message
*/
public function msg() {
$args = func_get_args();
+
return call_user_func_array( 'wfMessage', $args )->setContext( $this );
}
}
diff --git a/includes/context/IContextSource.php b/includes/context/IContextSource.php
index 35d5aed6..f718103d 100644
--- a/includes/context/IContextSource.php
+++ b/includes/context/IContextSource.php
@@ -37,7 +37,7 @@ interface IContextSource {
/**
* Get the Title object
*
- * @return Title
+ * @return Title|null
*/
public function getTitle();
@@ -79,14 +79,6 @@ interface IContextSource {
/**
* Get the Language object
*
- * @deprecated since 1.19 Use getLanguage instead
- * @return Language
- */
- public function getLang();
-
- /**
- * Get the Language object
- *
* @return Language
* @since 1.19
*/
@@ -100,6 +92,14 @@ interface IContextSource {
public function getSkin();
/**
+ * Get the site configuration
+ *
+ * @since 1.23
+ * @return Config
+ */
+ public function getConfig();
+
+ /**
* Get a Message object with context set
*
* @return Message
@@ -110,7 +110,7 @@ interface IContextSource {
* Export the resolved user IP, HTTP headers, user ID, and session ID.
* The result will be reasonably sized to allow for serialization.
*
- * @return Array
+ * @return array
* @since 1.21
*/
public function exportSession();
diff --git a/includes/context/RequestContext.php b/includes/context/RequestContext.php
index 01ec57c0..ede10fe9 100644
--- a/includes/context/RequestContext.php
+++ b/includes/context/RequestContext.php
@@ -64,6 +64,40 @@ class RequestContext implements IContextSource {
private $skin;
/**
+ * @var Config
+ */
+ private $config;
+
+ /**
+ * @var RequestContext
+ */
+ private static $instance = null;
+
+ /**
+ * Set the Config object
+ *
+ * @param Config $c
+ */
+ public function setConfig( Config $c ) {
+ $this->config = $c;
+ }
+
+ /**
+ * Get the Config object
+ *
+ * @return Config
+ */
+ public function getConfig() {
+ if ( $this->config === null ) {
+ // @todo In the future, we could move this to WebStart.php so
+ // the Config object is ready for when initialization happens
+ $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+
+ return $this->config;
+ }
+
+ /**
* Set the WebRequest object
*
* @param WebRequest $r
@@ -82,19 +116,17 @@ class RequestContext implements IContextSource {
global $wgRequest; # fallback to $wg till we can improve this
$this->request = $wgRequest;
}
+
return $this->request;
}
/**
* Set the Title object
*
- * @param Title $t
+ * @param Title $title
*/
- public function setTitle( $t ) {
- if ( $t !== null && !$t instanceof Title ) {
- throw new MWException( __METHOD__ . " expects an instance of Title" );
- }
- $this->title = $t;
+ public function setTitle( Title $title ) {
+ $this->title = $title;
// Erase the WikiPage so a new one with the new title gets created.
$this->wikipage = null;
}
@@ -102,13 +134,14 @@ class RequestContext implements IContextSource {
/**
* Get the Title object
*
- * @return Title
+ * @return Title|null
*/
public function getTitle() {
if ( $this->title === null ) {
global $wgTitle; # fallback to $wg till we can improve this
$this->title = $wgTitle;
}
+
return $this->title;
}
@@ -121,18 +154,14 @@ class RequestContext implements IContextSource {
* @return bool
*/
public function canUseWikiPage() {
- if ( $this->wikipage !== null ) {
- # If there's a WikiPage object set, we can for sure get it
+ if ( $this->wikipage ) {
+ // If there's a WikiPage object set, we can for sure get it
return true;
}
+ // Only pages with legitimate titles can have WikiPages.
+ // That usually means pages in non-virtual namespaces.
$title = $this->getTitle();
- if ( $title === null ) {
- # No Title, no WikiPage
- return false;
- } else {
- # Only namespaces whose pages are stored in the database can have WikiPage
- return $title->canExist();
- }
+ return $title ? $title->canExist() : false;
}
/**
@@ -169,11 +198,12 @@ class RequestContext implements IContextSource {
}
$this->wikipage = WikiPage::factory( $title );
}
+
return $this->wikipage;
}
/**
- * @param $o OutputPage
+ * @param OutputPage $o
*/
public function setOutput( OutputPage $o ) {
$this->output = $o;
@@ -188,6 +218,7 @@ class RequestContext implements IContextSource {
if ( $this->output === null ) {
$this->output = new OutputPage( $this );
}
+
return $this->output;
}
@@ -209,6 +240,7 @@ class RequestContext implements IContextSource {
if ( $this->user === null ) {
$this->user = User::newFromSession( $this->getRequest() );
}
+
return $this->user;
}
@@ -225,7 +257,7 @@ class RequestContext implements IContextSource {
$code = strtolower( $code );
# Validate $code
- if ( empty( $code ) || !Language::isValidCode( $code ) || ( $code === 'qqq' ) ) {
+ if ( !$code || !Language::isValidCode( $code ) || $code === 'qqq' ) {
wfDebug( "Invalid user language code\n" );
$code = $wgLanguageCode;
}
@@ -236,17 +268,6 @@ class RequestContext implements IContextSource {
/**
* Set the Language object
*
- * @deprecated since 1.19 Use setLanguage instead
- * @param Language|string $l Language instance or language code
- */
- public function setLang( $l ) {
- wfDeprecated( __METHOD__, '1.19' );
- $this->setLanguage( $l );
- }
-
- /**
- * Set the Language object
- *
* @param Language|string $l Language instance or language code
* @throws MWException
* @since 1.19
@@ -264,15 +285,6 @@ class RequestContext implements IContextSource {
}
/**
- * @deprecated since 1.19 Use getLanguage instead
- * @return Language
- */
- public function getLang() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->getLanguage();
- }
-
- /**
* Get the Language object.
* Initialization of user or request objects can depend on this.
*
@@ -285,30 +297,35 @@ class RequestContext implements IContextSource {
$e = new Exception;
wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() );
- global $wgLanguageCode;
- $code = ( $wgLanguageCode ) ? $wgLanguageCode : 'en';
+ $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en';
$this->lang = Language::factory( $code );
} elseif ( $this->lang === null ) {
$this->recursion = true;
- global $wgLanguageCode, $wgContLang;
+ global $wgContLang;
- $request = $this->getRequest();
- $user = $this->getUser();
+ try {
+ $request = $this->getRequest();
+ $user = $this->getUser();
- $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
- $code = self::sanitizeLangCode( $code );
+ $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
+ $code = self::sanitizeLangCode( $code );
- wfRunHooks( 'UserGetLanguageObject', array( $user, &$code, $this ) );
+ wfRunHooks( 'UserGetLanguageObject', array( $user, &$code, $this ) );
- if ( $code === $wgLanguageCode ) {
- $this->lang = $wgContLang;
- } else {
- $obj = Language::factory( $code );
- $this->lang = $obj;
- }
+ if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) {
+ $this->lang = $wgContLang;
+ } else {
+ $obj = Language::factory( $code );
+ $this->lang = $obj;
+ }
- unset( $this->recursion );
+ unset( $this->recursion );
+ }
+ catch ( Exception $ex ) {
+ unset( $this->recursion );
+ throw $ex;
+ }
}
return $this->lang;
@@ -335,35 +352,43 @@ class RequestContext implements IContextSource {
$skin = null;
wfRunHooks( 'RequestContextCreateSkin', array( $this, &$skin ) );
+ $factory = SkinFactory::getDefaultInstance();
// If the hook worked try to set a skin from it
if ( $skin instanceof Skin ) {
$this->skin = $skin;
} elseif ( is_string( $skin ) ) {
- $this->skin = Skin::newFromKey( $skin );
+ // Normalize the key, just in case the hook did something weird.
+ $normalized = Skin::normalizeKey( $skin );
+ $this->skin = $factory->makeSkin( $normalized );
}
// If this is still null (the hook didn't run or didn't work)
// then go through the normal processing to load a skin
if ( $this->skin === null ) {
- global $wgHiddenPrefs;
- if ( !in_array( 'skin', $wgHiddenPrefs ) ) {
+ if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
# get the user skin
$userSkin = $this->getUser()->getOption( 'skin' );
$userSkin = $this->getRequest()->getVal( 'useskin', $userSkin );
} else {
# if we're not allowing users to override, then use the default
- global $wgDefaultSkin;
- $userSkin = $wgDefaultSkin;
+ $userSkin = $this->getConfig()->get( 'DefaultSkin' );
}
- $this->skin = Skin::newFromKey( $userSkin );
+ // Normalize the key in case the user is passing gibberish
+ // or has old preferences (bug 69566).
+ $normalized = Skin::normalizeKey( $userSkin );
+
+ // Skin::normalizeKey will also validate it, so
+ // this won't throw an exception
+ $this->skin = $factory->makeSkin( $normalized );
}
// After all that set a context on whatever skin got created
$this->skin->setContext( $this );
wfProfileOut( __METHOD__ . '-createskin' );
}
+
return $this->skin;
}
@@ -377,6 +402,7 @@ class RequestContext implements IContextSource {
*/
public function msg() {
$args = func_get_args();
+
return call_user_func_array( 'wfMessage', $args )->setContext( $this );
}
@@ -388,18 +414,43 @@ class RequestContext implements IContextSource {
* @return RequestContext
*/
public static function getMain() {
- static $instance = null;
- if ( $instance === null ) {
- $instance = new self;
+ if ( self::$instance === null ) {
+ self::$instance = new self;
}
- return $instance;
+
+ return self::$instance;
+ }
+
+ /**
+ * Get the RequestContext object associated with the main request
+ * and gives a warning to the log, to find places, where a context maybe is missing.
+ *
+ * @param string $func
+ * @return RequestContext
+ * @since 1.24
+ */
+ public static function getMainAndWarn( $func = __METHOD__ ) {
+ wfDebug( $func . ' called without context. ' .
+ "Using RequestContext::getMain() for sanity\n" );
+
+ return self::getMain();
+ }
+
+ /**
+ * Resets singleton returned by getMain(). Should be called only from unit tests.
+ */
+ public static function resetMain() {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new MWException( __METHOD__ . '() should be called only from unit tests!' );
+ }
+ self::$instance = null;
}
/**
* Export the resolved user IP, HTTP headers, user ID, and session ID.
* The result will be reasonably sized to allow for serialization.
*
- * @return Array
+ * @return array
* @since 1.21
*/
public function exportSession() {
@@ -437,7 +488,8 @@ class RequestContext implements IContextSource {
if ( $params['userId'] ) { // logged-in user
$user = User::newFromId( $params['userId'] );
- if ( !$user ) {
+ $user->load();
+ if ( !$user->getId() ) {
throw new MWException( "No user with ID '{$params['userId']}'." );
}
} elseif ( !IP::isValid( $params['ip'] ) ) {
@@ -446,7 +498,7 @@ class RequestContext implements IContextSource {
$user = User::newFromName( $params['ip'], false );
}
- $importSessionFunction = function( User $user, array $params ) {
+ $importSessionFunction = function ( User $user, array $params ) {
global $wgRequest, $wgUser;
$context = RequestContext::getMain();
@@ -482,7 +534,7 @@ class RequestContext implements IContextSource {
$importSessionFunction( $user, $params );
// Set callback to save and close the new session and reload the old one
- return new ScopedCallback( function() use ( $importSessionFunction, $oUser, $oParams ) {
+ return new ScopedCallback( function () use ( $importSessionFunction, $oUser, $oParams ) {
$importSessionFunction( $oUser, $oParams );
} );
}
@@ -510,6 +562,7 @@ class RequestContext implements IContextSource {
$context->setRequest( new FauxRequest( $request ) );
}
$context->user = User::newFromName( '127.0.0.1', false );
+
return $context;
}
}
diff --git a/includes/dao/DBAccessBase.php b/includes/dao/DBAccessBase.php
index 6c009dee..3909faa7 100644
--- a/includes/dao/DBAccessBase.php
+++ b/includes/dao/DBAccessBase.php
@@ -2,7 +2,7 @@
/**
* Base class for objects that allow access to other wiki's databases using
- * the foreign database access mechanism implemented by LBFactory_multi.
+ * the foreign database access mechanism implemented by LBFactoryMulti.
*
* 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
@@ -28,9 +28,8 @@
* @author Daniel Kinzler
*/
abstract class DBAccessBase implements IDBAccessObject {
-
/**
- * @var String|bool $wiki The target wiki's name. This must be an ID
+ * @var string|bool $wiki The target wiki's name. This must be an ID
* that LBFactory can understand.
*/
protected $wiki = false;
@@ -58,6 +57,7 @@ abstract class DBAccessBase implements IDBAccessObject {
*/
protected function getConnection( $id, $groups = array() ) {
$loadBalancer = wfGetLB( $this->wiki );
+
return $loadBalancer->getConnection( $id, $groups, $this->wiki );
}
@@ -68,7 +68,7 @@ abstract class DBAccessBase implements IDBAccessObject {
*
* @since 1.21
*
- * @param DatabaseBase $db the database connection to release.
+ * @param DatabaseBase $db The database connection to release.
*/
protected function releaseConnection( DatabaseBase $db ) {
if ( $this->wiki !== false ) {
diff --git a/includes/dao/IDBAccessObject.php b/includes/dao/IDBAccessObject.php
index 4eb6ff3e..3690735e 100644
--- a/includes/dao/IDBAccessObject.php
+++ b/includes/dao/IDBAccessObject.php
@@ -28,10 +28,12 @@
* functions. In general, objects should assume READ_NORMAL if no flags are explicitly given,
* though certain objects may assume READ_LATEST for common use case or legacy reasons.
*
- * There are three types of reads:
- * - READ_NORMAL : Potentially cached read of data (e.g. from a slave or stale replica)
- * - READ_LATEST : Up-to-date read as of transaction start (e.g. from master or a quorum read)
- * - READ_LOCKING : Up-to-date read as of now, that locks the records for the transaction
+ * There are four types of reads:
+ * - READ_NORMAL : Potentially cached read of data (e.g. from a slave or stale replica)
+ * - READ_LATEST : Up-to-date read as of transaction start (e.g. from master or a quorum read)
+ * - READ_LOCKING : Up-to-date read as of now, that locks (shared) the records
+ * - READ_EXCLUSIVE : Up-to-date read as of now, that locks (exclusive) the records
+ * All record locks persist for the duration of the transaction.
*
* Callers should use READ_NORMAL (or pass in no flags) unless the read determines a write.
* In theory, such cases may require READ_LOCKING, though to avoid contention, READ_LATEST is
@@ -47,7 +49,8 @@
interface IDBAccessObject {
// Constants for object loading bitfield flags (higher => higher QoS)
const READ_LATEST = 1; // read from the master
- const READ_LOCKING = 3; // READ_LATEST and "FOR UPDATE"
+ const READ_LOCKING = 3; // READ_LATEST (1) and "LOCK IN SHARE MODE" (2)
+ const READ_EXCLUSIVE = 7; // READ_LOCKING (3) and "FOR UPDATE" (4)
// Convenience constant for callers to explicitly request slave data
const READ_NORMAL = 0; // read from the slave
diff --git a/includes/db/ChronologyProtector.php b/includes/db/ChronologyProtector.php
index de5e72c3..0c7b612e 100644
--- a/includes/db/ChronologyProtector.php
+++ b/includes/db/ChronologyProtector.php
@@ -26,12 +26,14 @@
* Kind of like Hawking's [[Chronology Protection Agency]].
*/
class ChronologyProtector {
- /** @var Array (DB master name => position) */
+ /** @var array (DB master name => position) */
protected $startupPositions = array();
- /** @var Array (DB master name => position) */
+
+ /** @var array (DB master name => position) */
protected $shutdownPositions = array();
- protected $initialized = false; // bool; whether the session data was loaded
+ /** @var bool Whether the session data was loaded */
+ protected $initialized = false;
/**
* Initialise a LoadBalancer to give it appropriate chronology protection.
@@ -41,7 +43,7 @@ class ChronologyProtector {
* to that position by delaying execution. The delay may timeout and allow stale
* data if no non-lagged slaves are available.
*
- * @param $lb LoadBalancer
+ * @param LoadBalancer $lb
* @return void
*/
public function initLB( LoadBalancer $lb ) {
@@ -67,7 +69,7 @@ class ChronologyProtector {
* Notify the ChronologyProtector that the LoadBalancer is about to shut
* down. Saves replication positions.
*
- * @param $lb LoadBalancer
+ * @param LoadBalancer $lb
* @return void
*/
public function shutdownLB( LoadBalancer $lb ) {
@@ -83,6 +85,7 @@ class ChronologyProtector {
$info = $lb->parentInfo();
if ( !$db || !$db->doneWrites() ) {
wfDebug( __METHOD__ . ": LB {$info['id']}, no writes done\n" );
+
return;
}
$pos = $db->getMasterPos();
diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index 819925cb..9eb3e2fa 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -3,7 +3,7 @@
* Helper class for making a copy of the database, mostly for unit testing.
*
* Copyright © 2010 Chad Horohoe <chad@anyonecanedit.org>
- * http://www.mediawiki.org/
+ * 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
@@ -25,49 +25,33 @@
*/
class CloneDatabase {
-
- /**
- * Table prefix for cloning
- * @var String
- */
+ /** @var string Table prefix for cloning */
private $newTablePrefix = '';
- /**
- * Current table prefix
- * @var String
- */
+ /** @var string Current table prefix */
private $oldTablePrefix = '';
- /**
- * List of tables to be cloned
- * @var Array
- */
+ /** @var array List of tables to be cloned */
private $tablesToClone = array();
- /**
- * Should we DROP tables containing the new names?
- * @var Bool
- */
+ /** @var bool Should we DROP tables containing the new names? */
private $dropCurrentTables = true;
- /**
- * Whether to use temporary tables or not
- * @var Bool
- */
+ /** @var bool Whether to use temporary tables or not */
private $useTemporaryTables = true;
/**
* Constructor
*
- * @param $db DatabaseBase A database subclass
+ * @param DatabaseBase $db A database subclass
* @param array $tablesToClone An array of tables to clone, unprefixed
* @param string $newTablePrefix Prefix to assign to the tables
* @param string $oldTablePrefix Prefix on current tables, if not $wgDBprefix
- * @param $dropCurrentTables bool
+ * @param bool $dropCurrentTables
*/
public function __construct( DatabaseBase $db, array $tablesToClone,
- $newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true )
- {
+ $newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true
+ ) {
$this->db = $db;
$this->tablesToClone = $tablesToClone;
$this->newTablePrefix = $newTablePrefix;
@@ -87,7 +71,13 @@ class CloneDatabase {
* Clone the table structure
*/
public function cloneTableStructure() {
+ global $wgSharedTables, $wgSharedDB;
foreach ( $this->tablesToClone as $tbl ) {
+ if ( $wgSharedDB && in_array( $tbl, $wgSharedTables, true ) ) {
+ // Shared tables don't work properly when cloning due to
+ // how prefixes are handled (bug 65654)
+ throw new MWException( "Cannot clone shared table $tbl." );
+ }
# Clean up from previous aborted run. So that table escaping
# works correctly across DB engines, we need to change the pre-
# fix back and forth so tableName() works right.
@@ -98,14 +88,21 @@ class CloneDatabase {
self::changePrefix( $this->newTablePrefix );
$newTableName = $this->db->tableName( $tbl, 'raw' );
- if ( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) {
+ if ( $this->dropCurrentTables
+ && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) )
+ ) {
+ if ( $oldTableName === $newTableName ) {
+ // Last ditch check to avoid data loss
+ throw new MWException( "Not dropping new table, as '$newTableName'"
+ . " is name of both the old and the new table." );
+ }
$this->db->dropTable( $tbl, __METHOD__ );
- wfDebug( __METHOD__ . " dropping {$newTableName}\n", true );
+ wfDebug( __METHOD__ . " dropping {$newTableName}\n" );
//Dropping the oldTable because the prefix was changed
}
# Create new table
- wfDebug( __METHOD__ . " duplicating $oldTableName to $newTableName\n", true );
+ wfDebug( __METHOD__ . " duplicating $oldTableName to $newTableName\n" );
$this->db->duplicateTableStructure( $oldTableName, $newTableName, $this->useTemporaryTables );
}
}
@@ -127,7 +124,7 @@ class CloneDatabase {
/**
* Change the table prefix on all open DB connections/
*
- * @param $prefix
+ * @param string $prefix
* @return void
*/
public static function changePrefix( $prefix ) {
@@ -137,8 +134,8 @@ class CloneDatabase {
}
/**
- * @param $lb LoadBalancer
- * @param $prefix
+ * @param LoadBalancer $lb
+ * @param string $prefix
* @return void
*/
public static function changeLBPrefix( $lb, $prefix ) {
@@ -146,8 +143,8 @@ class CloneDatabase {
}
/**
- * @param $db DatabaseBase
- * @param $prefix
+ * @param DatabaseBase $db
+ * @param string $prefix
* @return void
*/
public static function changeDBPrefix( $db, $prefix ) {
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 10645608..9b783a99 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -1,4 +1,5 @@
<?php
+
/**
* @defgroup Database Database
*
@@ -42,10 +43,10 @@ interface DatabaseType {
/**
* Open a connection to the database. Usually aborts on failure
*
- * @param string $server database server host
- * @param string $user database user name
- * @param string $password database user password
- * @param string $dbName database name
+ * @param string $server Database server host
+ * @param string $user Database user name
+ * @param string $password Database user password
+ * @param string $dbName Database name
* @return bool
* @throws DBConnectionError
*/
@@ -57,18 +58,18 @@ interface DatabaseType {
* member variables.
* If no more rows are available, false is returned.
*
- * @param $res ResultWrapper|object as returned from DatabaseBase::query(), etc.
- * @return object|bool
+ * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc.
+ * @return stdClass|bool
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchObject( $res );
/**
* Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
+ * form. Fields are retrieved with $row['fieldname'].
* If no more rows are available, false is returned.
*
- * @param $res ResultWrapper result object as returned from DatabaseBase::query(), etc.
+ * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
* @return array|bool
* @throws DBUnexpectedError Thrown if the database returns an error
*/
@@ -77,7 +78,7 @@ interface DatabaseType {
/**
* Get the number of rows in a result object
*
- * @param $res Mixed: A SQL result
+ * @param mixed $res A SQL result
* @return int
*/
function numRows( $res );
@@ -86,7 +87,7 @@ interface DatabaseType {
* Get the number of fields in a result object
* @see http://www.php.net/mysql_num_fields
*
- * @param $res Mixed: A SQL result
+ * @param mixed $res A SQL result
* @return int
*/
function numFields( $res );
@@ -95,8 +96,8 @@ interface DatabaseType {
* Get a field name in a result object
* @see http://www.php.net/mysql_field_name
*
- * @param $res Mixed: A SQL result
- * @param $n Integer
+ * @param mixed $res A SQL result
+ * @param int $n
* @return string
*/
function fieldName( $res, $n );
@@ -119,8 +120,8 @@ interface DatabaseType {
* Change the position of the cursor in a result object
* @see http://www.php.net/mysql_data_seek
*
- * @param $res Mixed: A SQL result
- * @param $row Mixed: Either MySQL row or ResultWrapper
+ * @param mixed $res A SQL result
+ * @param int $row
*/
function dataSeek( $res, $row );
@@ -144,8 +145,8 @@ interface DatabaseType {
* mysql_fetch_field() wrapper
* Returns false if the field doesn't exist
*
- * @param string $table table name
- * @param string $field field name
+ * @param string $table Table name
+ * @param string $field Field name
*
* @return Field
*/
@@ -156,7 +157,7 @@ interface DatabaseType {
* @param string $table Table name
* @param string $index Index name
* @param string $fname Calling function name
- * @return Mixed: Database-specific index description class or false if the index does not exist
+ * @return mixed Database-specific index description class or false if the index does not exist
*/
function indexInfo( $table, $index, $fname = __METHOD__ );
@@ -171,18 +172,18 @@ interface DatabaseType {
/**
* Wrapper for addslashes()
*
- * @param string $s to be slashed.
- * @return string: slashed string.
+ * @param string $s String to be slashed.
+ * @return string Slashed string.
*/
function strencode( $s );
/**
* Returns a wikitext link to the DB's website, e.g.,
- * return "[http://www.mysql.com/ MySQL]";
+ * return "[http://www.mysql.com/ MySQL]";
* Should at least contain plain text, if for some reason
* your database has no website.
*
- * @return string: wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
function getSoftwareLink();
@@ -190,16 +191,16 @@ interface DatabaseType {
* A string describing the current software version, like from
* mysql_get_server_info().
*
- * @return string: Version information from the database server.
+ * @return string Version information from the database server.
*/
function getServerVersion();
/**
* A string describing the current software version, and possibly
- * other details in a user-friendly way. Will be listed on Special:Version, etc.
+ * other details in a user-friendly way. Will be listed on Special:Version, etc.
* Use getServerVersion() to get machine-friendly information.
*
- * @return string: Version information from the database server
+ * @return string Version information from the database server
*/
function getServerInfo();
}
@@ -208,7 +209,8 @@ interface DatabaseType {
* Interface for classes that implement or wrap DatabaseBase
* @ingroup Database
*/
-interface IDatabase {}
+interface IDatabase {
+}
/**
* Database abstraction object
@@ -217,8 +219,10 @@ interface IDatabase {}
abstract class DatabaseBase implements IDatabase, DatabaseType {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
+
/** Minimum time to wait before retry, in microseconds */
const DEADLOCK_DELAY_MIN = 500000;
+
/** Maximum time to wait before retry */
const DEADLOCK_DELAY_MAX = 1500000;
@@ -232,6 +236,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
protected $mServer, $mUser, $mPassword, $mDBname;
+ /** @var resource Database connection */
protected $mConn = null;
protected $mOpened = false;
@@ -241,12 +246,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
protected $mTrxPreCommitCallbacks = array();
protected $mTablePrefix;
+ protected $mSchema;
protected $mFlags;
protected $mForeign;
- protected $mTrxLevel = 0;
protected $mErrorCount = 0;
protected $mLBInfo = array();
- protected $mFakeSlaveLag = null, $mFakeMaster = false;
protected $mDefaultBigSelects = null;
protected $mSchemaVars = false;
@@ -257,10 +261,25 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
protected $delimiter = ';';
/**
+ * Either 1 if a transaction is active or 0 otherwise.
+ * The other Trx fields may not be meaningfull if this is 0.
+ *
+ * @var int
+ */
+ protected $mTrxLevel = 0;
+
+ /**
+ * Either a short hexidecimal string if a transaction is active or ""
+ *
+ * @var string
+ */
+ protected $mTrxShortId = '';
+
+ /**
* Remembers the function name given for starting the most recent transaction via begin().
* Used to provide additional context for error reporting.
*
- * @var String
+ * @var string
* @see DatabaseBase::mTrxLevel
*/
private $mTrxFname = null;
@@ -268,7 +287,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Record if possible write queries were done in the last transaction started
*
- * @var Bool
+ * @var bool
* @see DatabaseBase::mTrxLevel
*/
private $mTrxDoneWrites = false;
@@ -276,20 +295,34 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Record if the current transaction was started implicitly due to DBO_TRX being set.
*
- * @var Bool
+ * @var bool
* @see DatabaseBase::mTrxLevel
*/
private $mTrxAutomatic = false;
/**
+ * Array of levels of atomicity within transactions
+ *
+ * @var SplStack
+ */
+ private $mTrxAtomicLevels;
+
+ /**
+ * Record if the current transaction was started implicitly by DatabaseBase::startAtomic
+ *
+ * @var bool
+ */
+ private $mTrxAutomaticAtomic = false;
+
+ /**
* @since 1.21
- * @var file handle for upgrade
+ * @var resource File handle for upgrade
*/
protected $fileHandle = null;
/**
* @since 1.22
- * @var Process cache of VIEWs names in the database
+ * @var string[] Process cache of VIEWs names in the database
*/
protected $allViews = null;
@@ -300,17 +333,17 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* A string describing the current software version, and possibly
- * other details in a user-friendly way. Will be listed on Special:Version, etc.
+ * other details in a user-friendly way. Will be listed on Special:Version, etc.
* Use getServerVersion() to get machine-friendly information.
*
- * @return string: Version information from the database server
+ * @return string Version information from the database server
*/
public function getServerInfo() {
return $this->getServerVersion();
}
/**
- * @return string: command delimiter used by this database engine
+ * @return string Command delimiter used by this database engine
*/
public function getDelimiter() {
return $this->delimiter;
@@ -318,12 +351,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Boolean, controls output of large amounts of debug information.
- * @param $debug bool|null
+ * @param bool|null $debug
* - true to enable debugging
* - false to disable debugging
* - omitted or null to do nothing
*
- * @return bool|null previous value of the flag
+ * @return bool|null Previous value of the flag
*/
public function debug( $debug = null ) {
return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
@@ -347,8 +380,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* split up queries into batches using a LIMIT clause than to switch off
* buffering.
*
- * @param $buffer null|bool
- *
+ * @param null|bool $buffer
* @return null|bool The previous value of the flag
*/
public function bufferResults( $buffer = null ) {
@@ -368,8 +400,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* Do not use this function outside of the Database classes.
*
- * @param $ignoreErrors bool|null
- *
+ * @param null|bool $ignoreErrors
* @return bool The previous value of the flag.
*/
public function ignoreErrors( $ignoreErrors = null ) {
@@ -377,16 +408,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Gets or sets the current transaction level.
+ * Gets the current transaction level.
*
* Historically, transactions were allowed to be "nested". This is no
* longer supported, so this function really only returns a boolean.
*
- * @param int $level An integer (0 or 1), or omitted to leave it unchanged.
* @return int The previous value
*/
- public function trxLevel( $level = null ) {
- return wfSetVar( $this->mTrxLevel, $level );
+ public function trxLevel() {
+ return $this->mTrxLevel;
}
/**
@@ -408,9 +438,18 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Get/set the db schema.
+ * @param string $schema The database schema to set, or omitted to leave it unchanged.
+ * @return string The previous db schema.
+ */
+ public function dbSchema( $schema = null ) {
+ return wfSetVar( $this->mSchema, $schema );
+ }
+
+ /**
* Set the filehandle to copy write statements to.
*
- * @param $fh filehandle
+ * @param resource $fh File handle
*/
public function setFileHandle( $fh ) {
$this->fileHandle = $fh;
@@ -423,7 +462,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param string $name The entry of the info array to get, or null to get the
* whole array
*
- * @return LoadBalancer|null
+ * @return array|mixed|null
*/
public function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
@@ -442,8 +481,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* the LB info array is set to that parameter. If it is called with two
* parameters, the member with the given name is set to the given value.
*
- * @param $name
- * @param $value
+ * @param string $name
+ * @param array $value
*/
public function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
@@ -456,19 +495,19 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Set lag time in seconds for a fake slave
*
- * @param $lag int
+ * @param mixed $lag Valid values for this parameter are determined by the
+ * subclass, but should be a PHP scalar or array that would be sensible
+ * as part of $wgLBFactoryConf.
*/
public function setFakeSlaveLag( $lag ) {
- $this->mFakeSlaveLag = $lag;
}
/**
* Make this connection a fake master
*
- * @param $enabled bool
+ * @param bool $enabled
*/
public function setFakeMaster( $enabled = true ) {
- $this->mFakeMaster = $enabled;
}
/**
@@ -548,7 +587,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Return the last query that went through DatabaseBase::query()
- * @return String
+ * @return string
*/
public function lastQuery() {
return $this->mLastQuery;
@@ -561,7 +600,18 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @return bool
*/
public function doneWrites() {
- return $this->mDoneWrites;
+ return (bool)$this->mDoneWrites;
+ }
+
+ /**
+ * Returns the last time the connection may have been used for write queries.
+ * Should return a timestamp if unsure.
+ *
+ * @return int|float UNIX timestamp or false
+ * @since 1.24
+ */
+ public function lastDoneWrites() {
+ return $this->mDoneWrites ?: false;
}
/**
@@ -578,7 +628,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Is a connection to the database open?
- * @return Boolean
+ * @return bool
*/
public function isOpen() {
return $this->mOpened;
@@ -587,7 +637,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Set a flag for this connection
*
- * @param $flag Integer: DBO_* constants from Defines.php:
+ * @param int $flag DBO_* constants from Defines.php:
* - DBO_DEBUG: output some debug info (same as debug())
* - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
* - DBO_TRX: automatically start transactions
@@ -598,15 +648,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
public function setFlag( $flag ) {
global $wgDebugDBTransactions;
$this->mFlags |= $flag;
- if ( ( $flag & DBO_TRX ) & $wgDebugDBTransactions ) {
- wfDebug( "Implicit transactions are now disabled.\n" );
+ if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
+ wfDebug( "Implicit transactions are now enabled.\n" );
}
}
/**
* Clear a flag for this connection
*
- * @param $flag: same as setFlag()'s $flag param
+ * @param int $flag DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_TRX: automatically start transactions
+ * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
+ * and removes it in command line mode
+ * - DBO_PERSISTENT: use persistant database connection
*/
public function clearFlag( $flag ) {
global $wgDebugDBTransactions;
@@ -619,8 +675,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Returns a boolean whether the flag $flag is set for this connection
*
- * @param $flag: same as setFlag()'s $flag param
- * @return Boolean
+ * @param int $flag DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_TRX: automatically start transactions
+ * - DBO_PERSISTENT: use persistant database connection
+ * @return bool
*/
public function getFlag( $flag ) {
return !!( $this->mFlags & $flag );
@@ -629,8 +689,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* General read-only accessor
*
- * @param $name string
- *
+ * @param string $name
* @return string
*/
public function getProperty( $name ) {
@@ -649,19 +708,42 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Return a path to the DBMS-specific schema file, otherwise default to tables.sql
+ * Return a path to the DBMS-specific SQL file if it exists,
+ * otherwise default SQL file
*
+ * @param string $filename
* @return string
*/
- public function getSchemaPath() {
+ private function getSqlFilePath( $filename ) {
global $IP;
- if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
- return "$IP/maintenance/" . $this->getType() . "/tables.sql";
+ $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename";
+ if ( file_exists( $dbmsSpecificFilePath ) ) {
+ return $dbmsSpecificFilePath;
} else {
- return "$IP/maintenance/tables.sql";
+ return "$IP/maintenance/$filename";
}
}
+ /**
+ * Return a path to the DBMS-specific schema file,
+ * otherwise default to tables.sql
+ *
+ * @return string
+ */
+ public function getSchemaPath() {
+ return $this->getSqlFilePath( 'tables.sql' );
+ }
+
+ /**
+ * Return a path to the DBMS-specific update key file,
+ * otherwise default to update-keys.sql
+ *
+ * @return string
+ */
+ public function getUpdateKeysPath() {
+ return $this->getSqlFilePath( 'update-keys.sql' );
+ }
+
# ------------------------------------------------------------------------------
# Other functions
# ------------------------------------------------------------------------------
@@ -673,28 +755,39 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* connection object, by specifying no parameters to __construct(). This
* feature is deprecated and should be removed.
*
- * FIXME: The long list of formal parameters here is not really appropriate
- * for MySQL, and not at all appropriate for any other DBMS. It should be
- * replaced by named parameters as in DatabaseBase::factory().
- *
* DatabaseBase subclasses should not be constructed directly in external
* code. DatabaseBase::factory() should be used instead.
*
- * @param string $server database server host
- * @param string $user database user name
- * @param string $password database user password
- * @param string $dbName database name
- * @param $flags
- * @param string $tablePrefix database table prefixes. By default use the prefix gave in LocalSettings.php
- * @param bool $foreign disable some operations specific to local databases
+ * @param array $params Parameters passed from DatabaseBase::factory()
*/
- function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $flags = 0, $tablePrefix = 'get from global', $foreign = false
- ) {
- global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
+ function __construct( $params = null ) {
+ global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions;
- $this->mFlags = $flags;
+ $this->mTrxAtomicLevels = new SplStack;
+
+ if ( is_array( $params ) ) { // MW 1.22
+ $server = $params['host'];
+ $user = $params['user'];
+ $password = $params['password'];
+ $dbName = $params['dbname'];
+ $flags = $params['flags'];
+ $tablePrefix = $params['tablePrefix'];
+ $schema = $params['schema'];
+ $foreign = $params['foreign'];
+ } else { // legacy calling pattern
+ wfDeprecated( __METHOD__ . " method called without parameter array.", "1.23" );
+ $args = func_get_args();
+ $server = isset( $args[0] ) ? $args[0] : false;
+ $user = isset( $args[1] ) ? $args[1] : false;
+ $password = isset( $args[2] ) ? $args[2] : false;
+ $dbName = isset( $args[3] ) ? $args[3] : false;
+ $flags = isset( $args[4] ) ? $args[4] : 0;
+ $tablePrefix = isset( $args[5] ) ? $args[5] : 'get from global';
+ $schema = 'get from global';
+ $foreign = isset( $args[6] ) ? $args[6] : false;
+ }
+ $this->mFlags = $flags;
if ( $this->mFlags & DBO_DEFAULT ) {
if ( $wgCommandLineMode ) {
$this->mFlags &= ~DBO_TRX;
@@ -716,6 +809,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->mTablePrefix = $tablePrefix;
}
+ /** Get the database schema*/
+ if ( $schema == 'get from global' ) {
+ $this->mSchema = $wgDBmwschema;
+ } else {
+ $this->mSchema = $schema;
+ }
+
$this->mForeign = $foreign;
if ( $user ) {
@@ -729,13 +829,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* not restored on unserialize.
*/
public function __sleep() {
- throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
+ throw new MWException( 'Database serialization may cause problems, since ' .
+ 'the connection is not restored on wakeup.' );
}
/**
* Given a DB type, construct the name of the appropriate child class of
* DatabaseBase. This is designed to replace all of the manual stuff like:
- * $class = 'Database' . ucfirst( strtolower( $dbType ) );
+ * $class = 'Database' . ucfirst( strtolower( $dbType ) );
* as well as validate against the canonical list of DB types we have
*
* This factory function is mostly useful for when you need to connect to a
@@ -744,22 +845,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* database. Example uses in core:
* @see LoadBalancer::reallyOpenConnection()
* @see ForeignDBRepo::getMasterDB()
- * @see WebInstaller_DBConnect::execute()
+ * @see WebInstallerDBConnect::execute()
*
* @since 1.18
*
* @param string $dbType A possible DB type
* @param array $p An array of options to pass to the constructor.
- * Valid options are: host, user, password, dbname, flags, tablePrefix, driver
- * @return DatabaseBase subclass or null
+ * Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver
+ * @throws MWException If the database driver or extension cannot be found
+ * @return DatabaseBase|null DatabaseBase subclass or null
*/
final public static function factory( $dbType, $p = array() ) {
$canonicalDBTypes = array(
- 'mysql' => array( 'mysqli', 'mysql' ),
+ 'mysql' => array( 'mysqli', 'mysql' ),
'postgres' => array(),
- 'sqlite' => array(),
- 'oracle' => array(),
- 'mssql' => array(),
+ 'sqlite' => array(),
+ 'oracle' => array(),
+ 'mssql' => array(),
);
$driver = false;
@@ -789,17 +891,32 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
" no viable database extension found for type '$dbType'" );
}
+ // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
+ // and everything else doesn't use a schema (e.g. null)
+ // Although postgres and oracle support schemas, we don't use them (yet)
+ // to maintain backwards compatibility
+ $defaultSchemas = array(
+ 'mysql' => null,
+ 'postgres' => null,
+ 'sqlite' => null,
+ 'oracle' => null,
+ 'mssql' => 'get from global',
+ );
+
$class = 'Database' . ucfirst( $driver );
if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
- return new $class(
- isset( $p['host'] ) ? $p['host'] : false,
- isset( $p['user'] ) ? $p['user'] : false,
- isset( $p['password'] ) ? $p['password'] : false,
- isset( $p['dbname'] ) ? $p['dbname'] : false,
- isset( $p['flags'] ) ? $p['flags'] : 0,
- isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
- isset( $p['foreign'] ) ? $p['foreign'] : false
+ $params = array(
+ 'host' => isset( $p['host'] ) ? $p['host'] : false,
+ 'user' => isset( $p['user'] ) ? $p['user'] : false,
+ 'password' => isset( $p['password'] ) ? $p['password'] : false,
+ 'dbname' => isset( $p['dbname'] ) ? $p['dbname'] : false,
+ 'flags' => isset( $p['flags'] ) ? $p['flags'] : 0,
+ 'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
+ 'schema' => isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType],
+ 'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false
);
+
+ return new $class( $params );
} else {
return null;
}
@@ -822,6 +939,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $this->mPHPError ) {
$error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
$error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
+
return $error;
} else {
return false;
@@ -829,9 +947,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * @param $errno
- * @param $errstr
- * @access private
+ * @param int $errno
+ * @param string $errstr
*/
public function connectionErrorHandler( $errno, $errstr ) {
$this->mPHPError = $errstr;
@@ -842,13 +959,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* if it is open : commits any open transactions
*
* @throws MWException
- * @return Bool operation success. true if already closed.
+ * @return bool Operation success. true if already closed.
*/
public function close() {
if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
throw new MWException( "Transaction idle callbacks still pending." );
}
- $this->mOpened = false;
if ( $this->mConn ) {
if ( $this->trxLevel() ) {
if ( !$this->mTrxAutomatic ) {
@@ -859,23 +975,25 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->commit( __METHOD__, 'flush' );
}
- $ret = $this->closeConnection();
+ $closed = $this->closeConnection();
$this->mConn = false;
- return $ret;
} else {
- return true;
+ $closed = true;
}
+ $this->mOpened = false;
+
+ return $closed;
}
/**
* Closes underlying database connection
* @since 1.20
- * @return bool: Whether connection was closed successfully
+ * @return bool Whether connection was closed successfully
*/
abstract protected function closeConnection();
/**
- * @param string $error fallback error message, used if none is given by DB
+ * @param string $error Fallback error message, used if none is given by DB
* @throws DBConnectionError
*/
function reportConnectionError( $error = 'Unknown error' ) {
@@ -891,8 +1009,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* The DBMS-dependent part of query()
*
- * @param $sql String: SQL query.
- * @return ResultWrapper Result object to feed to fetchObject, fetchRow, ...; or false on failure
+ * @param string $sql SQL query.
+ * @return ResultWrapper|bool Result object to feed to fetchObject,
+ * fetchRow, ...; or false on failure
*/
abstract protected function doQuery( $sql );
@@ -900,8 +1019,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Determine whether a query writes to the DB.
* Should return true if unsure.
*
- * @param $sql string
- *
+ * @param string $sql
* @return bool
*/
public function isWriteQuery( $sql ) {
@@ -921,23 +1039,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* However, the query wrappers themselves should call this function.
*
- * @param $sql String: SQL query
- * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST
+ * @param string $sql SQL query
+ * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST
* comment (you can use __METHOD__ or add some extra info)
- * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
+ * @param bool $tempIgnore Whether to avoid throwing an exception on errors...
* maybe best to catch the exception instead?
* @throws MWException
- * @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object
+ * @return bool|ResultWrapper True for a successful write query, ResultWrapper object
* for a successful read query, or false on failure if $tempIgnore set
*/
public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
- global $wgUser, $wgDebugDBTransactions;
+ global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
$this->mLastQuery = $sql;
- if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
+ if ( $this->isWriteQuery( $sql ) ) {
# Set a flag indicating that writes have been done
- wfDebug( __METHOD__ . ": Writes done: $sql\n" );
- $this->mDoneWrites = true;
+ wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" );
+ $this->mDoneWrites = microtime( true );
}
# Add a comment for easy SHOW PROCESSLIST interpretation
@@ -957,8 +1075,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# If DBO_TRX is set, start a transaction
if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' )
- {
+ $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
+ ) {
# Avoid establishing transactions for SHOW and SET statements too -
# that would delay transaction initializations to once connection
# is really used by application
@@ -975,10 +1093,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# Keep track of whether the transaction has write queries pending
if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
$this->mTrxDoneWrites = true;
- Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname );
+ Profiler::instance()->transactionWritingIn(
+ $this->mServer, $this->mDBname, $this->mTrxShortId );
}
+ $queryProf = '';
+ $totalProf = '';
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
+
if ( !Profiler::instance()->isStub() ) {
# generalizeSQL will probably cut down the query to reasonable
# logging size most of the time. The substr is really just a sanity check.
@@ -989,6 +1111,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
$totalProf = 'DatabaseBase::query';
}
+ # Include query transaction state
+ $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
+
+ $trx = $this->mTrxLevel ? 'TRX=yes' : 'TRX=no';
wfProfileIn( $totalProf );
wfProfileIn( $queryProf );
}
@@ -997,7 +1123,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
static $cnt = 0;
$cnt++;
- $sqlx = substr( $commentedSql, 0, 500 );
+ $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
+ : $commentedSql;
$sqlx = strtr( $sqlx, "\t\n", ' ' );
$master = $isMaster ? 'master' : 'slave';
@@ -1006,6 +1133,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$queryId = MWDebug::query( $sql, $fname, $isMaster );
+ # Avoid fatals if close() was called
+ if ( !$this->isOpen() ) {
+ throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ }
+
# Do the query and handle errors
$ret = $this->doQuery( $commentedSql );
@@ -1014,22 +1146,33 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# Try reconnecting if the connection was lost
if ( false === $ret && $this->wasErrorReissuable() ) {
# Transaction is gone, like it or not
+ $hadTrx = $this->mTrxLevel; // possible lost transaction
$this->mTrxLevel = 0;
- $this->mTrxIdleCallbacks = array(); // cancel
- $this->mTrxPreCommitCallbacks = array(); // cancel
+ $this->mTrxIdleCallbacks = array(); // bug 65263
+ $this->mTrxPreCommitCallbacks = array(); // bug 65263
wfDebug( "Connection lost, reconnecting...\n" );
-
+ # Stash the last error values since ping() might clear them
+ $lastError = $this->lastError();
+ $lastErrno = $this->lastErrno();
if ( $this->ping() ) {
+ global $wgRequestTime;
wfDebug( "Reconnected\n" );
- $sqlx = substr( $commentedSql, 0, 500 );
+ $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
+ : $commentedSql;
$sqlx = strtr( $sqlx, "\t\n", ' ' );
- global $wgRequestTime;
$elapsed = round( microtime( true ) - $wgRequestTime, 3 );
if ( $elapsed < 300 ) {
# Not a database error to lose a transaction after a minute or two
- wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
+ wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" );
+ }
+ if ( $hadTrx ) {
+ # Leave $ret as false and let an error be reported.
+ # Callers may catch the exception and continue to use the DB.
+ $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore );
+ } else {
+ # Should be safe to silently retry (no trx and thus no callbacks)
+ $ret = $this->doQuery( $commentedSql );
}
- $ret = $this->doQuery( $commentedSql );
} else {
wfDebug( "Failed\n" );
}
@@ -1051,11 +1194,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Report a query error. Log the error, and if neither the object ignore
* flag nor the $tempIgnore flag is set, throw a DBQueryError.
*
- * @param $error String
- * @param $errno Integer
- * @param $sql String
- * @param $fname String
- * @param $tempIgnore Boolean
+ * @param string $error
+ * @param int $errno
+ * @param string $sql
+ * @param string $fname
+ * @param bool $tempIgnore
* @throws DBQueryError
*/
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
@@ -1067,8 +1210,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
wfDebug( "SQL ERROR (ignored): $error\n" );
$this->ignoreErrors( $ignore );
} else {
- $sql1line = str_replace( "\n", "\\n", $sql );
- wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
+ $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
+ wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" );
wfDebug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
@@ -1083,21 +1226,22 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* & = filename; reads the file and inserts as a blob
* (we don't use this though...)
*
- * @param $sql string
- * @param $func string
+ * @param string $sql
+ * @param string $func
*
* @return array
*/
protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
/* MySQL doesn't support prepared statements (yet), so just
- pack up the query for reference. We'll manually replace
- the bits later. */
+ * pack up the query for reference. We'll manually replace
+ * the bits later.
+ */
return array( 'query' => $sql, 'func' => $func );
}
/**
* Free a prepared query, generated by prepare().
- * @param $prepared
+ * @param string $prepared
*/
protected function freePrepared( $prepared ) {
/* No-op by default */
@@ -1105,8 +1249,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Execute a prepared query with the various arguments
- * @param string $prepared the prepared sql
- * @param $args Mixed: Either an array here, or put scalars as varargs
+ * @param string $prepared The prepared sql
+ * @param mixed $args Either an array here, or put scalars as varargs
*
* @return ResultWrapper
*/
@@ -1125,9 +1269,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* For faking prepared SQL statements on DBs that don't support it directly.
*
- * @param string $preparedQuery a 'preparable' SQL statement
- * @param array $args of arguments to fill it with
- * @return string executable SQL
+ * @param string $preparedQuery A 'preparable' SQL statement
+ * @param array $args Array of Arguments to fill it with
+ * @return string Executable SQL
*/
public function fillPrepared( $preparedQuery, $args ) {
reset( $args );
@@ -1142,9 +1286,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* The arguments should be in $this->preparedArgs and must not be touched
* while we're doing this.
*
- * @param $matches Array
+ * @param array $matches
* @throws DBUnexpectedError
- * @return String
+ * @return string
*/
protected function fillPreparedArg( $matches ) {
switch ( $matches[1] ) {
@@ -1165,9 +1309,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
return $arg;
case '&':
# return $this->addQuotes( file_get_contents( $arg ) );
- throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
+ throw new DBUnexpectedError(
+ $this,
+ '& mode is not implemented. If it\'s really needed, uncomment the line above.'
+ );
default:
- throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
+ throw new DBUnexpectedError(
+ $this,
+ 'Received invalid match. This should never happen!'
+ );
}
}
@@ -1176,7 +1326,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* necessary to call this, just use unset() or let the variable holding
* the result object go out of scope.
*
- * @param $res Mixed: A SQL result
+ * @param mixed $res A SQL result
*/
public function freeResult( $res ) {
}
@@ -1226,9 +1376,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query.
*
- * @param array $options associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return Array
+ * @param array $options Associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
+ * @return array
* @see DatabaseBase::select()
*/
public function makeSelectOptions( $options ) {
@@ -1310,7 +1460,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Returns an optional GROUP BY with an optional HAVING
*
- * @param array $options associative array of options
+ * @param array $options Associative array of options
* @return string
* @see DatabaseBase::select()
* @since 1.21
@@ -1329,13 +1479,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
: $options['HAVING'];
$sql .= ' HAVING ' . $having;
}
+
return $sql;
}
/**
* Returns an optional ORDER BY
*
- * @param array $options associative array of options
+ * @param array $options Associative array of options
* @return string
* @see DatabaseBase::select()
* @since 1.21
@@ -1345,8 +1496,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$ob = is_array( $options['ORDER BY'] )
? implode( ',', $options['ORDER BY'] )
: $options['ORDER BY'];
+
return ' ORDER BY ' . $ob;
}
+
return '';
}
@@ -1359,9 +1512,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param string|array $conds Conditions
* @param string $fname Caller function name
* @param array $options Query options
- * @param $join_conds Array Join conditions
+ * @param array $join_conds Join conditions
*
- * @param $table string|array
+ *
+ * @param string|array $table
*
* May be either an array of table names, or a single string holding a table
* name. If an array is given, table aliases can be specified, for example:
@@ -1376,7 +1530,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* added, and various other table name mappings to be performed.
*
*
- * @param $vars string|array
+ * @param string|array $vars
*
* May be either a field name or an array of field names. The field names
* can be complete fragments of SQL, for direct inclusion into the SELECT
@@ -1390,7 +1544,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* DBMS-independent.
*
*
- * @param $conds string|array
+ * @param string|array $conds
*
* May be either a string containing a single condition, or an array of
* conditions. If an array is given, the conditions constructed from each
@@ -1415,7 +1569,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* - DatabaseBase::conditional()
*
*
- * @param $options string|array
+ * @param string|array $options
*
* Optional: Array of query options. Boolean options are specified by
* including them in the array as a string value with a numeric key, for
@@ -1471,7 +1625,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* - SQL_NO_CACHE
*
*
- * @param $join_conds string|array
+ * @param string|array $join_conds
*
* Optional associative array of table-specific join conditions. In the
* most common case, this is unnecessary, since the join condition can be
@@ -1484,7 +1638,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* array( 'page' => array( 'LEFT JOIN', 'page_latest=rev_id' ) )
*
- * @return ResultWrapper. If the query returned no rows, a ResultWrapper
+ * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper
* with no rows in it will be returned. If there was a query error, a
* DBQueryError exception will be thrown, except if the "ignore errors"
* option was set, in which case false will be returned.
@@ -1507,14 +1661,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param string|array $conds Conditions
* @param string $fname Caller function name
* @param string|array $options Query options
- * @param $join_conds string|array Join conditions
+ * @param string|array $join_conds Join conditions
*
* @return string SQL query string.
* @see DatabaseBase::select()
*/
public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
- $options = array(), $join_conds = array() )
- {
+ $options = array(), $join_conds = array()
+ ) {
if ( is_array( $vars ) ) {
$vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
}
@@ -1573,13 +1727,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param array $conds Conditions
* @param string $fname Caller function name
* @param string|array $options Query options
- * @param $join_conds array|string Join conditions
+ * @param array|string $join_conds Join conditions
*
- * @return object|bool
+ * @return stdClass|bool
*/
public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
- $options = array(), $join_conds = array() )
- {
+ $options = array(), $join_conds = array()
+ ) {
$options = (array)$options;
$options['LIMIT'] = 1;
$res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1598,7 +1752,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Estimate rows in dataset.
+ * Estimate the number of rows in dataset
*
* MySQL allows you to estimate the number of rows that would be returned
* by a SELECT query, using EXPLAIN SELECT. The estimate is provided using
@@ -1610,16 +1764,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* Takes the same arguments as DatabaseBase::select().
*
- * @param string $table table name
- * @param array|string $vars : unused
- * @param array|string $conds : filters on the table
- * @param string $fname function name for profiling
- * @param array $options options for select
- * @return Integer: row count
- */
- public function estimateRowCount( $table, $vars = '*', $conds = '',
- $fname = __METHOD__, $options = array() )
- {
+ * @param string $table Table name
+ * @param string $vars Unused
+ * @param array|string $conds Filters on the table
+ * @param string $fname Function name for profiling
+ * @param array $options Options for select
+ * @return int Row count
+ */
+ public function estimateRowCount(
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array()
+ ) {
$rows = 0;
$res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
@@ -1632,6 +1786,36 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Get the number of rows in dataset
+ *
+ * This is useful when trying to do COUNT(*) but with a LIMIT for performance.
+ *
+ * Takes the same arguments as DatabaseBase::select().
+ *
+ * @param string $table Table name
+ * @param string $vars Unused
+ * @param array|string $conds Filters on the table
+ * @param string $fname Function name for profiling
+ * @param array $options Options for select
+ * @return int Row count
+ * @since 1.24
+ */
+ public function selectRowCount(
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array()
+ ) {
+ $rows = 0;
+ $sql = $this->selectSQLText( $table, '1', $conds, $fname, $options );
+ $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count" );
+
+ if ( $res ) {
+ $row = $this->fetchRow( $res );
+ $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
+ }
+
+ return $rows;
+ }
+
+ /**
* Removes most variables from an SQL query and replaces them with X or N for numbers.
* It's only slightly flawed. Don't use for anything important.
*
@@ -1653,9 +1837,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# All newlines, tabs, etc replaced by single space
$sql = preg_replace( '/\s+/', ' ', $sql );
- # All numbers => N
+ # All numbers => N,
+ # except the ones surrounded by characters, e.g. l10n
$sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
- $sql = preg_replace( '/-?\d+/s', 'N', $sql );
+ $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
return $sql;
}
@@ -1663,10 +1848,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Determines whether a field exists in a table
*
- * @param string $table table name
- * @param string $field filed to check on that table
- * @param string $fname calling function name (optional)
- * @return Boolean: whether $table has filed $field
+ * @param string $table Table name
+ * @param string $field Filed to check on that table
+ * @param string $fname Calling function name (optional)
+ * @return bool Whether $table has filed $field
*/
public function fieldExists( $table, $field, $fname = __METHOD__ ) {
$info = $this->fieldInfo( $table, $field );
@@ -1679,10 +1864,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Usually throws a DBQueryError on failure
* If errors are explicitly ignored, returns NULL on failure
*
- * @param $table
- * @param $index
- * @param $fname string
- *
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool|null
*/
public function indexExists( $table, $index, $fname = __METHOD__ ) {
@@ -1701,9 +1885,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Query whether a given table exists
*
- * @param $table string
- * @param $fname string
- *
+ * @param string $table
+ * @param string $fname
* @return bool
*/
public function tableExists( $table, $fname = __METHOD__ ) {
@@ -1716,24 +1899,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * mysql_field_type() wrapper
- * @param $res
- * @param $index
- * @return string
- */
- public function fieldType( $res, $index ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return mysql_field_type( $res, $index );
- }
-
- /**
* Determines if a given index is unique
*
- * @param $table string
- * @param $index string
+ * @param string $table
+ * @param string $index
*
* @return bool
*/
@@ -1750,7 +1919,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Helper for DatabaseBase::insert().
*
- * @param $options array
+ * @param array $options
* @return string
*/
protected function makeInsertOptions( $options ) {
@@ -1782,11 +1951,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* possible to determine how many rows were successfully inserted using
* DatabaseBase::affectedRows().
*
- * @param $table String Table name. This will be passed through
- * DatabaseBase::tableName().
- * @param $a Array of rows to insert
- * @param $fname String Calling function name (use __METHOD__) for logs/profiling
- * @param array $options of options
+ * @param string $table Table name. This will be passed through
+ * DatabaseBase::tableName().
+ * @param array $a Array of rows to insert
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options Array of options
*
* @return bool
*/
@@ -1843,12 +2012,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Make UPDATE options for the DatabaseBase::update function
+ * Make UPDATE options array for DatabaseBase::makeUpdateOptions
*
- * @param array $options The options passed to DatabaseBase::update
- * @return string
+ * @param array $options
+ * @return array
*/
- protected function makeUpdateOptions( $options ) {
+ protected function makeUpdateOptionsArray( $options ) {
if ( !is_array( $options ) ) {
$options = array( $options );
}
@@ -1863,31 +2032,38 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$opts[] = 'IGNORE';
}
+ return $opts;
+ }
+
+ /**
+ * Make UPDATE options for the DatabaseBase::update function
+ *
+ * @param array $options The options passed to DatabaseBase::update
+ * @return string
+ */
+ protected function makeUpdateOptions( $options ) {
+ $opts = $this->makeUpdateOptionsArray( $options );
+
return implode( ' ', $opts );
}
/**
* UPDATE wrapper. Takes a condition array and a SET array.
*
- * @param $table String name of the table to UPDATE. This will be passed through
- * DatabaseBase::tableName().
- *
- * @param array $values An array of values to SET. For each array element,
- * the key gives the field name, and the value gives the data
- * to set that field to. The data will be quoted by
- * DatabaseBase::addQuotes().
- *
- * @param $conds Array: An array of conditions (WHERE). See
- * DatabaseBase::select() for the details of the format of
- * condition arrays. Use '*' to update all rows.
- *
- * @param $fname String: The function name of the caller (from __METHOD__),
- * for logging and profiling.
- *
+ * @param string $table Name of the table to UPDATE. This will be passed through
+ * DatabaseBase::tableName().
+ * @param array $values An array of values to SET. For each array element,
+ * the key gives the field name, and the value gives the data to set
+ * that field to. The data will be quoted by DatabaseBase::addQuotes().
+ * @param array $conds An array of conditions (WHERE). See
+ * DatabaseBase::select() for the details of the format of condition
+ * arrays. Use '*' to update all rows.
+ * @param string $fname The function name of the caller (from __METHOD__),
+ * for logging and profiling.
* @param array $options An array of UPDATE options, can be:
- * - IGNORE: Ignore unique key conflicts
- * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
- * @return Boolean
+ * - IGNORE: Ignore unique key conflicts
+ * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
+ * @return bool
*/
function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
$table = $this->tableName( $table );
@@ -1903,15 +2079,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Makes an encoded list of strings from an array
- * @param array $a containing the data
- * @param int $mode Constant
- * - LIST_COMMA: comma separated, no field names
- * - LIST_AND: ANDed WHERE clause (without the WHERE). See
- * the documentation for $conds in DatabaseBase::select().
- * - LIST_OR: ORed WHERE clause (without the WHERE)
- * - LIST_SET: comma separated with field names, like a SET clause
- * - LIST_NAMES: comma separated field names
*
+ * @param array $a Containing the data
+ * @param int $mode Constant
+ * - LIST_COMMA: Comma separated, no field names
+ * - LIST_AND: ANDed WHERE clause (without the WHERE). See the
+ * documentation for $conds in DatabaseBase::select().
+ * - LIST_OR: ORed WHERE clause (without the WHERE)
+ * - LIST_SET: Comma separated with field names, like a SET clause
+ * - LIST_NAMES: Comma separated field names
* @throws MWException|DBUnexpectedError
* @return string
*/
@@ -1974,11 +2150,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Build a partial where clause from a 2-d array such as used for LinkBatch.
* The keys on each level may be either integers or strings.
*
- * @param array $data organized as 2-d
- * array(baseKeyVal => array(subKeyVal => [ignored], ...), ...)
- * @param string $baseKey field name to match the base-level keys to (eg 'pl_namespace')
- * @param string $subKey field name to match the sub-level keys to (eg 'pl_title')
- * @return Mixed: string SQL fragment, or false if no items in array.
+ * @param array $data Organized as 2-d
+ * array(baseKeyVal => array(subKeyVal => [ignored], ...), ...)
+ * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace')
+ * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title')
+ * @return string|bool SQL fragment, or false if no items in array
*/
public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
$conds = array();
@@ -2002,8 +2178,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Return aggregated value alias
*
- * @param $valuedata
- * @param $valuename string
+ * @param array $valuedata
+ * @param string $valuename
*
* @return string
*/
@@ -2012,7 +2188,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * @param $field
+ * @param string $field
* @return string
*/
public function bitNot( $field ) {
@@ -2020,8 +2196,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * @param $fieldLeft
- * @param $fieldRight
+ * @param string $fieldLeft
+ * @param string $fieldRight
* @return string
*/
public function bitAnd( $fieldLeft, $fieldRight ) {
@@ -2029,8 +2205,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * @param $fieldLeft
- * @param $fieldRight
+ * @param string $fieldLeft
+ * @param string $fieldRight
* @return string
*/
public function bitOr( $fieldLeft, $fieldRight ) {
@@ -2039,32 +2215,59 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Build a concatenation list to feed into a SQL query
- * @param array $stringList list of raw SQL expressions; caller is responsible for any quoting
- * @return String
+ * @param array $stringList List of raw SQL expressions; caller is
+ * responsible for any quoting
+ * @return string
*/
public function buildConcat( $stringList ) {
return 'CONCAT(' . implode( ',', $stringList ) . ')';
}
/**
+ * Build a GROUP_CONCAT or equivalent statement for a query.
+ *
+ * This is useful for combining a field for several rows into a single string.
+ * NULL values will not appear in the output, duplicated values will appear,
+ * and the resulting delimiter-separated values have no defined sort order.
+ * Code using the results may need to use the PHP unique() or sort() methods.
+ *
+ * @param string $delim Glue to bind the results together
+ * @param string|array $table Table name
+ * @param string $field Field name
+ * @param string|array $conds Conditions
+ * @param string|array $join_conds Join conditions
+ * @return string SQL text
+ * @since 1.23
+ */
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = array()
+ ) {
+ $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
+ /**
* Change the current database
*
* @todo Explain what exactly will fail if this is not overridden.
*
- * @param $db
+ * @param string $db
*
* @return bool Success or failure
*/
public function selectDB( $db ) {
- # Stub. Shouldn't cause serious problems if it's not overridden, but
+ # Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
# databases you may as well.
$this->mDBname = $db;
+
return true;
}
/**
* Get the current DB name
+ * @return string
*/
public function getDBname() {
return $this->mDBname;
@@ -2072,6 +2275,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Get the server hostname or IP address
+ * @return string
*/
public function getServer() {
return $this->mServer;
@@ -2087,15 +2291,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* themselves. Pass the canonical name to such functions. This is only needed
* when calling query() directly.
*
- * @param string $name database table name
+ * @param string $name Database table name
* @param string $format One of:
* quoted - Automatically pass the table name through addIdentifierQuotes()
* so that it can be used in a query.
* raw - Do not add identifier quotes to the table name
- * @return String: full database name
+ * @return string Full database name
*/
public function tableName( $name, $format = 'quoted' ) {
- global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
+ global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema;
# Skip the entire process when we have a string quoted on both ends.
# Note that we check the end so that we will still quote any use of
# use of `database`.table. But won't break things if someone wants
@@ -2119,31 +2323,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# We reverse the explode so that database.table and table both output
# the correct table.
$dbDetails = explode( '.', $name, 2 );
- if ( count( $dbDetails ) == 2 ) {
+ if ( count( $dbDetails ) == 3 ) {
+ list( $database, $schema, $table ) = $dbDetails;
+ # We don't want any prefix added in this case
+ $prefix = '';
+ } elseif ( count( $dbDetails ) == 2 ) {
list( $database, $table ) = $dbDetails;
# We don't want any prefix added in this case
+ # In dbs that support it, $database may actually be the schema
+ # but that doesn't affect any of the functionality here
$prefix = '';
+ $schema = null;
} else {
list( $table ) = $dbDetails;
if ( $wgSharedDB !== null # We have a shared database
&& $this->mForeign == false # We're not working on a foreign database
- && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
+ && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
&& in_array( $table, $wgSharedTables ) # A shared table is selected
) {
$database = $wgSharedDB;
+ $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema;
$prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
} else {
$database = null;
+ $schema = $this->mSchema; # Default schema
$prefix = $this->mTablePrefix; # Default prefix
}
}
# Quote $table and apply the prefix if not quoted.
+ # $tableName might be empty if this is called from Database::replaceVars()
$tableName = "{$prefix}{$table}";
- if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) {
+ if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
$tableName = $this->addIdentifierQuotes( $tableName );
}
+ # Quote $schema and merge it with the table name if needed
+ if ( $schema !== null ) {
+ if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
+ $schema = $this->addIdentifierQuotes( $schema );
+ }
+ $tableName = $schema . '.' . $tableName;
+ }
+
# Quote $database and merge it with the table name if needed
if ( $database !== null ) {
if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
@@ -2218,8 +2440,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Gets an array of aliased table names
*
- * @param $tables array( [alias] => table )
- * @return array of strings, see tableNameWithAlias()
+ * @param array $tables Array( [alias] => table )
+ * @return string[] See tableNameWithAlias()
*/
public function tableNamesWithAlias( $tables ) {
$retval = array();
@@ -2229,6 +2451,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
$retval[] = $this->tableNameWithAlias( $table, $alias );
}
+
return $retval;
}
@@ -2251,8 +2474,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Gets an array of aliased field names
*
- * @param $fields array( [alias] => field )
- * @return array of strings, see fieldNameWithAlias()
+ * @param array $fields Array( [alias] => field )
+ * @return string[] See fieldNameWithAlias()
*/
public function fieldNamesWithAlias( $fields ) {
$retval = array();
@@ -2262,6 +2485,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
$retval[] = $this->fieldNameWithAlias( $field, $alias );
}
+
return $retval;
}
@@ -2270,8 +2494,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* which might have a JOIN and/or USE INDEX clause
*
* @param array $tables ( [alias] => table )
- * @param $use_index array Same as for select()
- * @param $join_conds array Same as for select()
+ * @param array $use_index Same as for select()
+ * @param array $join_conds Same as for select()
* @return string
*/
protected function tableNamesWithUseIndexOrJOIN(
@@ -2304,11 +2528,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
$retJOIN[] = $tableClause;
- // Is there an INDEX clause for this table?
} elseif ( isset( $use_index[$alias] ) ) {
+ // Is there an INDEX clause for this table?
$tableClause = $this->tableNameWithAlias( $table, $alias );
$tableClause .= ' ' . $this->useIndexClause(
- implode( ',', (array)$use_index[$alias] ) );
+ implode( ',', (array)$use_index[$alias] )
+ );
$ret[] = $tableClause;
} else {
@@ -2329,8 +2554,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Get the name of an index in a given table
*
- * @param $index
- *
+ * @param string $index
* @return string
*/
protected function indexName( $index ) {
@@ -2351,8 +2575,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Adds quotes and backslashes.
*
- * @param $s string
- *
+ * @param string $s
* @return string
*/
public function addQuotes( $s ) {
@@ -2373,8 +2596,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Since MySQL is the odd one out here the double quotes are our generic
* and we implement backticks in DatabaseMysql.
*
- * @param $s string
- *
+ * @param string $s
* @return string
*/
public function addIdentifierQuotes( $s ) {
@@ -2385,37 +2607,36 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Returns if the given identifier looks quoted or not according to
* the database convention for quoting identifiers .
*
- * @param $name string
- *
- * @return boolean
+ * @param string $name
+ * @return bool
*/
public function isQuotedIdentifier( $name ) {
return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
}
/**
- * @param $s string
+ * @param string $s
* @return string
*/
protected function escapeLikeInternal( $s ) {
- $s = str_replace( '\\', '\\\\', $s );
- $s = $this->strencode( $s );
- $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
-
- return $s;
+ return addcslashes( $s, '\%_' );
}
/**
- * LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match
- * containing either string literals that will be escaped or tokens returned by anyChar() or anyString().
- * Alternatively, the function could be provided with an array of aforementioned parameters.
+ * LIKE statement wrapper, receives a variable-length argument list with
+ * parts of pattern to match containing either string literals that will be
+ * escaped or tokens returned by anyChar() or anyString(). Alternatively,
+ * the function could be provided with an array of aforementioned
+ * parameters.
*
- * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches
- * for subpages of 'My page title'.
- * Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern );
+ * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
+ * a LIKE clause that searches for subpages of 'My page title'.
+ * Alternatively:
+ * $pattern = array( 'My_page_title/', $dbr->anyString() );
+ * $query .= $dbr->buildLike( $pattern );
*
* @since 1.16
- * @return String: fully built LIKE statement
+ * @return string Fully built LIKE statement
*/
public function buildLike() {
$params = func_get_args();
@@ -2434,7 +2655,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
}
- return " LIKE '" . $s . "' ";
+ return " LIKE {$this->addQuotes( $s )} ";
}
/**
@@ -2463,21 +2684,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Any implementation of this function should *not* involve reusing
* sequence numbers created for rolled-back transactions.
* See http://bugs.mysql.com/bug.php?id=30767 for details.
- * @param $seqName string
- * @return null
+ * @param string $seqName
+ * @return null|int
*/
public function nextSequenceValue( $seqName ) {
return null;
}
/**
- * USE INDEX clause. Unlikely to be useful for anything but MySQL. This
+ * USE INDEX clause. Unlikely to be useful for anything but MySQL. This
* is only needed because a) MySQL must be as efficient as possible due to
* its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about
- * which index to pick. Anyway, other databases might have different
- * indexes on a given table. So don't bother overriding this unless you're
+ * which index to pick. Anyway, other databases might have different
+ * indexes on a given table. So don't bother overriding this unless you're
* MySQL.
- * @param $index
+ * @param string $index
* @return string
*/
public function useIndexClause( $index ) {
@@ -2500,10 +2721,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* errors which wouldn't have occurred in MySQL.
*
* @param string $table The table to replace the row(s) in.
+ * @param array $uniqueIndexes Is an array of indexes. Each element may be either
+ * a field name or an array of field names
* @param array $rows Can be either a single row to insert, or multiple rows,
* in the same format as for DatabaseBase::insert()
- * @param array $uniqueIndexes is an array of indexes. Each element may be either
- * a field name or an array of field names
* @param string $fname Calling function name (use __METHOD__) for logs/profiling
*/
public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
@@ -2558,7 +2779,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* statement.
*
* @param string $table Table name
- * @param array $rows Rows to insert
+ * @param array|string $rows Row(s) to insert
* @param string $fname Caller function name
*
* @return ResultWrapper
@@ -2609,26 +2830,28 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Usually throws a DBQueryError on failure. If errors are explicitly ignored,
* returns success.
*
+ * @since 1.22
+ *
* @param string $table Table name. This will be passed through DatabaseBase::tableName().
* @param array $rows A single row or list of rows to insert
* @param array $uniqueIndexes List of single field names or field name tuples
- * @param array $set An array of values to SET. For each array element,
- * the key gives the field name, and the value gives the data
- * to set that field to. The data will be quoted by
- * DatabaseBase::addQuotes().
+ * @param array $set An array of values to SET. For each array element, the
+ * key gives the field name, and the value gives the data to set that
+ * field to. The data will be quoted by DatabaseBase::addQuotes().
* @param string $fname Calling function name (use __METHOD__) for logs/profiling
- * @param array $options of options
- *
+ * @throws Exception
* @return bool
- * @since 1.22
*/
- public function upsert(
- $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
+ $fname = __METHOD__
) {
if ( !count( $rows ) ) {
return true; // nothing to do
}
- $rows = is_array( reset( $rows ) ) ? $rows : array( $rows );
+
+ if ( !is_array( reset( $rows ) ) ) {
+ $rows = array( $rows );
+ }
if ( count( $uniqueIndexes ) ) {
$clauses = array(); // list WHERE clauses that each identify a single row
@@ -2684,19 +2907,18 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* DO NOT put the join condition in $conds.
*
- * @param $delTable String: The table to delete from.
- * @param $joinTable String: The other table.
- * @param $delVar String: The variable to join on, in the first table.
- * @param $joinVar String: The variable to join on, in the second table.
- * @param $conds Array: Condition array of field names mapped to variables,
- * ANDed together in the WHERE clause
- * @param $fname String: Calling function name (use __METHOD__) for
- * logs/profiling
+ * @param string $delTable The table to delete from.
+ * @param string $joinTable The other table.
+ * @param string $delVar The variable to join on, in the first table.
+ * @param string $joinVar The variable to join on, in the second table.
+ * @param array $conds Condition array of field names mapped to variables,
+ * ANDed together in the WHERE clause
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
* @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
- $fname = __METHOD__ )
- {
+ $fname = __METHOD__
+ ) {
if ( !$conds ) {
throw new DBUnexpectedError( $this,
'DatabaseBase::deleteJoin() called with empty $conds' );
@@ -2716,9 +2938,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Returns the size of a text field, or -1 for "unlimited"
*
- * @param $table string
- * @param $field string
- *
+ * @param string $table
+ * @param string $field
* @return int
*/
public function textFieldSize( $table, $field ) {
@@ -2740,7 +2961,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* A string to insert into queries to show that they're low-priority, like
- * MySQL's LOW_PRIORITY. If no such feature exists, return an empty
+ * MySQL's LOW_PRIORITY. If no such feature exists, return an empty
* string and nothing bad should happen.
*
* @return string Returns the text of the low priority option if it is
@@ -2754,10 +2975,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* DELETE query wrapper.
*
* @param array $table Table name
- * @param string|array $conds of conditions. See $conds in DatabaseBase::select() for
- * the format. Use $conds == "*" to delete all rows
- * @param string $fname name of the calling function
- *
+ * @param string|array $conds Array of conditions. See $conds in DatabaseBase::select()
+ * for the format. Use $conds == "*" to delete all rows
+ * @param string $fname Name of the calling function
* @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
@@ -2787,7 +3007,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param string|array $srcTable May be either a table name, or an array of table names
* to include in a join.
*
- * @param array $varMap must be an associative array of the form
+ * @param array $varMap Must be an associative array of the form
* array( 'dest1' => 'source1', ...). Source items may be literals
* rather than field names, but strings should be quoted with
* DatabaseBase::addQuotes()
@@ -2807,14 +3027,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*/
public function insertSelect( $destTable, $srcTable, $varMap, $conds,
$fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() )
- {
+ $insertOptions = array(), $selectOptions = array()
+ ) {
$destTable = $this->tableName( $destTable );
- if ( is_array( $insertOptions ) ) {
- $insertOptions = implode( ' ', $insertOptions );
+ if ( !is_array( $insertOptions ) ) {
+ $insertOptions = array( $insertOptions );
}
+ $insertOptions = $this->makeInsertOptions( $insertOptions );
+
if ( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
}
@@ -2844,22 +3066,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * 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
+ * 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
+ * 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 $limit Integer the SQL limit
- * @param $offset Integer|bool the SQL offset (default false)
- *
+ * @param int $limit The SQL limit
+ * @param int|bool $offset The SQL offset (default false)
* @throws DBUnexpectedError
* @return string
*/
@@ -2867,6 +3088,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( !is_numeric( $limit ) ) {
throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
}
+
return "$sql LIMIT "
. ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
. "{$limit} ";
@@ -2875,7 +3097,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
* within the UNION construct.
- * @return Boolean
+ * @return bool
*/
public function unionSupportsOrderAndLimit() {
return true; // True for almost every DB supported
@@ -2886,27 +3108,29 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* This is used for providing overload point for other DB abstractions
* not compatible with the MySQL syntax.
* @param array $sqls SQL statements to combine
- * @param $all Boolean: use UNION ALL
- * @return String: SQL fragment
+ * @param bool $all Use UNION ALL
+ * @return string SQL fragment
*/
public function unionQueries( $sqls, $all ) {
$glue = $all ? ') UNION ALL (' : ') UNION (';
+
return '(' . implode( $glue, $sqls ) . ')';
}
/**
- * Returns an SQL expression for a simple conditional. This doesn't need
+ * Returns an SQL expression for a simple conditional. This doesn't need
* to be overridden unless CASE isn't supported in your DBMS.
*
* @param string|array $cond SQL expression which will result in a boolean value
* @param string $trueVal SQL expression to return if true
* @param string $falseVal SQL expression to return if false
- * @return String: SQL fragment
+ * @return string SQL fragment
*/
public function conditional( $cond, $trueVal, $falseVal ) {
if ( is_array( $cond ) ) {
$cond = $this->makeList( $cond, LIST_AND );
}
+
return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
@@ -2914,9 +3138,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Returns a comand for str_replace function in SQL query.
* Uses REPLACE() in MySQL
*
- * @param string $orig column to modify
- * @param string $old column to seek
- * @param string $new column to replace with
+ * @param string $orig Column to modify
+ * @param string $old Column to seek
+ * @param string $new Column to replace with
*
* @return string
*/
@@ -3027,9 +3251,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $tries <= 0 ) {
$this->rollback( __METHOD__ );
$this->reportQueryError( $error, $errno, $sql, $fname );
+
return false;
} else {
$this->commit( __METHOD__ );
+
return $retVal;
}
}
@@ -3037,38 +3263,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Wait for the slave to catch up to a given master position.
*
- * @param $pos DBMasterPos object
- * @param $timeout Integer: the maximum number of seconds to wait for
+ * @param DBMasterPos $pos
+ * @param int $timeout The maximum number of seconds to wait for
* synchronisation
- *
- * @return integer: zero if the slave was past that position already,
+ * @return int Zero if the slave was past that position already,
* greater than zero if we waited for some period of time, less than
* zero if we timed out.
*/
public function masterPosWait( DBMasterPos $pos, $timeout ) {
- wfProfileIn( __METHOD__ );
-
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
-
- if ( $wait > $timeout * 1e6 ) {
- wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
- wfProfileOut( __METHOD__ );
- return -1;
- } elseif ( $wait > 0 ) {
- wfDebug( "Fake slave waiting $wait us\n" );
- usleep( $wait );
- wfProfileOut( __METHOD__ );
- return 1;
- } else {
- wfDebug( "Fake slave up to date ($wait us)\n" );
- wfProfileOut( __METHOD__ );
- return 0;
- }
- }
-
- wfProfileOut( __METHOD__ );
-
# Real waits are implemented in the subclass.
return 0;
}
@@ -3076,30 +3278,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Get the replication position of this slave
*
- * @return DBMasterPos, or false if this is not a slave.
+ * @return DBMasterPos|bool False if this is not a slave.
*/
public function getSlavePos() {
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
- wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
- return $pos;
- } else {
- # Stub
- return false;
- }
+ # Stub
+ return false;
}
/**
* Get the position of this master
*
- * @return DBMasterPos, or false if this is not a master
+ * @return DBMasterPos|bool False if this is not a master
*/
public function getMasterPos() {
- if ( $this->mFakeMaster ) {
- return new MySQLMasterPos( 'fake', microtime( true ) );
- } else {
- return false;
- }
+ # Stub
+ return false;
}
/**
@@ -3150,7 +3343,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
protected function runOnTransactionIdleCallbacks() {
$autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
- $e = null; // last exception
+ $e = $ePrior = null; // last exception
do { // callbacks may add callbacks :)
$callbacks = $this->mTrxIdleCallbacks;
$this->mTrxIdleCallbacks = array(); // recursion guard
@@ -3160,7 +3353,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->clearFlag( DBO_TRX ); // make each query its own transaction
call_user_func( $phpCallback );
$this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
- } catch ( Exception $e ) {}
+ } catch ( Exception $e ) {
+ if ( $ePrior ) {
+ MWExceptionHandler::logException( $ePrior );
+ }
+ $ePrior = $e;
+ }
}
} while ( count( $this->mTrxIdleCallbacks ) );
@@ -3175,7 +3373,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @since 1.22
*/
protected function runOnTransactionPreCommitCallbacks() {
- $e = null; // last exception
+ $e = $ePrior = null; // last exception
do { // callbacks may add callbacks :)
$callbacks = $this->mTrxPreCommitCallbacks;
$this->mTrxPreCommitCallbacks = array(); // recursion guard
@@ -3183,7 +3381,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
try {
list( $phpCallback ) = $callback;
call_user_func( $phpCallback );
- } catch ( Exception $e ) {}
+ } catch ( Exception $e ) {
+ if ( $ePrior ) {
+ MWExceptionHandler::logException( $ePrior );
+ }
+ $ePrior = $e;
+ }
}
} while ( count( $this->mTrxPreCommitCallbacks ) );
@@ -3193,22 +3396,91 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Begin a transaction. If a transaction is already in progress, that transaction will be committed before the
- * new transaction is started.
+ * Begin an atomic section of statements
+ *
+ * If a transaction has been started already, just keep track of the given
+ * section name to make sure the transaction is not committed pre-maturely.
+ * This function can be used in layers (with sub-sections), so use a stack
+ * to keep track of the different atomic sections. If there is no transaction,
+ * start one implicitly.
+ *
+ * The goal of this function is to create an atomic section of SQL queries
+ * without having to start a new transaction if it already exists.
*
- * Note that when the DBO_TRX flag is set (which is usually the case for web requests, but not for maintenance scripts),
- * any previous database query will have started a transaction automatically.
+ * Atomic sections are more strict than transactions. With transactions,
+ * attempting to begin a new transaction when one is already running results
+ * in MediaWiki issuing a brief warning and doing an implicit commit. All
+ * atomic levels *must* be explicitly closed using DatabaseBase::endAtomic(),
+ * and any database transactions cannot be began or committed until all atomic
+ * levels are closed. There is no such thing as implicitly opening or closing
+ * an atomic section.
+ *
+ * @since 1.23
+ * @param string $fname
+ * @throws DBError
+ */
+ final public function startAtomic( $fname = __METHOD__ ) {
+ if ( !$this->mTrxLevel ) {
+ $this->begin( $fname );
+ $this->mTrxAutomatic = true;
+ $this->mTrxAutomaticAtomic = true;
+ }
+
+ $this->mTrxAtomicLevels->push( $fname );
+ }
+
+ /**
+ * Ends an atomic section of SQL statements
*
- * Nesting of transactions is not supported. Attempts to nest transactions will cause a warning, unless the current
- * transaction was started automatically because of the DBO_TRX flag.
+ * Ends the next section of atomic SQL statements and commits the transaction
+ * if necessary.
*
- * @param $fname string
+ * @since 1.23
+ * @see DatabaseBase::startAtomic
+ * @param string $fname
+ * @throws DBError
+ */
+ final public function endAtomic( $fname = __METHOD__ ) {
+ if ( !$this->mTrxLevel ) {
+ throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
+ }
+ if ( $this->mTrxAtomicLevels->isEmpty() ||
+ $this->mTrxAtomicLevels->pop() !== $fname
+ ) {
+ throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
+ }
+
+ if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) {
+ $this->commit( $fname, 'flush' );
+ }
+ }
+
+ /**
+ * Begin a transaction. If a transaction is already in progress,
+ * that transaction will be committed before the new transaction is started.
+ *
+ * Note that when the DBO_TRX flag is set (which is usually the case for web
+ * requests, but not for maintenance scripts), any previous database query
+ * will have started a transaction automatically.
+ *
+ * Nesting of transactions is not supported. Attempts to nest transactions
+ * will cause a warning, unless the current transaction was started
+ * automatically because of the DBO_TRX flag.
+ *
+ * @param string $fname
+ * @throws DBError
*/
final public function begin( $fname = __METHOD__ ) {
global $wgDebugDBTransactions;
if ( $this->mTrxLevel ) { // implicit commit
- if ( !$this->mTrxAutomatic ) {
+ if ( !$this->mTrxAtomicLevels->isEmpty() ) {
+ // If the current transaction was an automatic atomic one, then we definitely have
+ // a problem. Same if there is any unclosed atomic level.
+ throw new DBUnexpectedError( $this,
+ "Attempted to start explicit transaction when atomic levels are still open."
+ );
+ } elseif ( !$this->mTrxAutomatic ) {
// We want to warn about inadvertently nested begin/commit pairs, but not about
// auto-committing implicit transactions that were started by query() via DBO_TRX
$msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
@@ -3228,22 +3500,33 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ Profiler::instance()->transactionWritingOut(
+ $this->mServer, $this->mDBname, $this->mTrxShortId );
}
$this->runOnTransactionIdleCallbacks();
}
+ # Avoid fatals if close() was called
+ if ( !$this->isOpen() ) {
+ throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ }
+
$this->doBegin( $fname );
$this->mTrxFname = $fname;
$this->mTrxDoneWrites = false;
$this->mTrxAutomatic = false;
+ $this->mTrxAutomaticAtomic = false;
+ $this->mTrxAtomicLevels = new SplStack;
+ $this->mTrxIdleCallbacks = array();
+ $this->mTrxPreCommitCallbacks = array();
+ $this->mTrxShortId = wfRandomString( 12 );
}
/**
* Issues the BEGIN command to the database server.
*
* @see DatabaseBase::begin()
- * @param type $fname
+ * @param string $fname
*/
protected function doBegin( $fname ) {
$this->query( 'BEGIN', $fname );
@@ -3256,33 +3539,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* Nesting of transactions is not supported.
*
- * @param $fname string
- * @param string $flush Flush flag, set to 'flush' to disable warnings about explicitly committing implicit
- * transactions, or calling commit when no transaction is in progress.
- * This will silently break any ongoing explicit transaction. Only set the flush flag if you are sure
- * that it is safe to ignore these warnings in your context.
+ * @param string $fname
+ * @param string $flush Flush flag, set to 'flush' to disable warnings about
+ * explicitly committing implicit transactions, or calling commit when no
+ * transaction is in progress. This will silently break any ongoing
+ * explicit transaction. Only set the flush flag if you are sure that it
+ * is safe to ignore these warnings in your context.
+ * @throws DBUnexpectedError
*/
final public function commit( $fname = __METHOD__, $flush = '' ) {
- if ( $flush != 'flush' ) {
+ if ( !$this->mTrxAtomicLevels->isEmpty() ) {
+ // There are still atomic sections open. This cannot be ignored
+ throw new DBUnexpectedError(
+ $this,
+ "Attempted to commit transaction while atomic sections are still open"
+ );
+ }
+
+ if ( $flush === 'flush' ) {
if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to commit, something got out of sync!" );
- } elseif ( $this->mTrxAutomatic ) {
- wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
+ return; // nothing to do
+ } elseif ( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
}
} else {
if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to commit, something got out of sync!" );
return; // nothing to do
- } elseif ( !$this->mTrxAutomatic ) {
- wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ } elseif ( $this->mTrxAutomatic ) {
+ wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
}
}
+ # Avoid fatals if close() was called
+ if ( !$this->isOpen() ) {
+ throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ }
+
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ Profiler::instance()->transactionWritingOut(
+ $this->mServer, $this->mDBname, $this->mTrxShortId );
}
- $this->mTrxDoneWrites = false;
$this->runOnTransactionIdleCallbacks();
}
@@ -3290,7 +3589,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Issues the COMMIT command to the database server.
*
* @see DatabaseBase::commit()
- * @param type $fname
+ * @param string $fname
*/
protected function doCommit( $fname ) {
if ( $this->mTrxLevel ) {
@@ -3305,26 +3604,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* No-op on non-transactional databases.
*
- * @param $fname string
+ * @param string $fname
+ * @param string $flush Flush flag, set to 'flush' to disable warnings about
+ * calling rollback when no transaction is in progress. This will silently
+ * break any ongoing explicit transaction. Only set the flush flag if you
+ * are sure that it is safe to ignore these warnings in your context.
+ * @since 1.23 Added $flush parameter
*/
- final public function rollback( $fname = __METHOD__ ) {
- if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
+ final public function rollback( $fname = __METHOD__, $flush = '' ) {
+ if ( $flush !== 'flush' ) {
+ if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
+ return; // nothing to do
+ } elseif ( $this->mTrxAutomatic ) {
+ wfWarn( "$fname: Explicit rollback of implicit transaction. Something may be out of sync!" );
+ }
+ } else {
+ if ( !$this->mTrxLevel ) {
+ return; // nothing to do
+ } elseif ( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ }
}
+
+ # Avoid fatals if close() was called
+ if ( !$this->isOpen() ) {
+ throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ }
+
$this->doRollback( $fname );
$this->mTrxIdleCallbacks = array(); // cancel
$this->mTrxPreCommitCallbacks = array(); // cancel
+ $this->mTrxAtomicLevels = new SplStack;
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ Profiler::instance()->transactionWritingOut(
+ $this->mServer, $this->mDBname, $this->mTrxShortId );
}
- $this->mTrxDoneWrites = false;
}
/**
* Issues the ROLLBACK command to the database server.
*
* @see DatabaseBase::rollback()
- * @param type $fname
+ * @param string $fname
*/
protected function doRollback( $fname ) {
if ( $this->mTrxLevel ) {
@@ -3341,12 +3663,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* The table names passed to this function shall not be quoted (this
* function calls addIdentifierQuotes when needed).
*
- * @param string $oldName name of table whose structure should be copied
- * @param string $newName name of table to be created
- * @param $temporary Boolean: whether the new table should be temporary
- * @param string $fname calling function name
+ * @param string $oldName Name of table whose structure should be copied
+ * @param string $newName Name of table to be created
+ * @param bool $temporary Whether the new table should be temporary
+ * @param string $fname Calling function name
* @throws MWException
- * @return Boolean: true if operation was successful
+ * @return bool True if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
$fname = __METHOD__
@@ -3359,7 +3681,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* List all tables on the database
*
* @param string $prefix Only show tables with this prefix, e.g. mw_
- * @param string $fname calling function name
+ * @param string $fname Calling function name
* @throws MWException
*/
function listTables( $prefix = null, $fname = __METHOD__ ) {
@@ -3380,8 +3702,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* For caching purposes the list of all views should be stored in
* $this->allViews. The process cache can be cleared with clearViewsCache()
*
- * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
- * @param string $fname Name of calling function
+ * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
+ * @param string $fname Name of calling function
* @throws MWException
* @since 1.22
*/
@@ -3392,7 +3714,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Differentiates between a TABLE and a VIEW
*
- * @param $name string: Name of the database-structure to test.
+ * @param string $name Name of the database-structure to test.
* @throws MWException
* @since 1.22
*/
@@ -3407,7 +3729,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* The result is unquoted, and needs to be passed through addQuotes()
* before it can be included in raw SQL.
*
- * @param $ts string|int
+ * @param string|int $ts
*
* @return string
*/
@@ -3424,7 +3746,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* The result is unquoted, and needs to be passed through addQuotes()
* before it can be included in raw SQL.
*
- * @param $ts string|int
+ * @param string|int $ts
*
* @return string
*/
@@ -3447,8 +3769,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* callers, so this is unnecessary in external code. For compatibility with
* old code, ResultWrapper objects are passed through unaltered.
*
- * @param $result bool|ResultWrapper
- *
+ * @param bool|ResultWrapper|resource $result
* @return bool|ResultWrapper
*/
public function resultObject( $result ) {
@@ -3470,7 +3791,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @return bool Success or failure
*/
public function ping() {
- # Stub. Not essential to override.
+ # Stub. Not essential to override.
return true;
}
@@ -3484,7 +3805,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @return int Database replication lag in seconds
*/
public function getLag() {
- return intval( $this->mFakeSlaveLag );
+ return 0;
}
/**
@@ -3501,7 +3822,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* don't allow simple quoted strings to be inserted. To insert into such
* a field, pass the data through this function before passing it to
* DatabaseBase::insert().
- * @param $b string
+ *
+ * @param string $b
* @return string
*/
public function encodeBlob( $b ) {
@@ -3512,7 +3834,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Some DBMSs return a special placeholder object representing blob fields
* in result objects. Pass the object through this function to return the
* original string.
- * @param $b string
+ *
+ * @param string $b
* @return string
*/
public function decodeBlob( $b ) {
@@ -3526,7 +3849,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* full-wiki dumps, where a single query reads out over
* hours or days.
*
- * @param $options Array
+ * @param array $options
* @return void
*/
public function setSessionOptions( array $options ) {
@@ -3542,9 +3865,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @param bool|callable $lineCallback Optional function called before reading each line
* @param bool|callable $resultCallback Optional function called for each MySQL result
* @param bool|string $fname Calling function name or false if name should be
- * generated dynamically using $filename
- * @param bool|callable $inputCallback Callback: Optional function called for each complete line sent
- * @throws MWException
+ * generated dynamically using $filename
+ * @param bool|callable $inputCallback Optional function called for each
+ * complete line sent
* @throws Exception|MWException
* @return bool|string
*/
@@ -3565,8 +3888,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
try {
$error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
- }
- catch ( MWException $e ) {
+ } catch ( MWException $e ) {
fclose( $fp );
throw $e;
}
@@ -3582,7 +3904,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* it fails back to MySQL if no DB-specific patch can be found
*
* @param string $patch The name of the patch, like patch-something.sql
- * @return String Full path to patch file
+ * @return string Full path to patch file
*/
public function patchPath( $patch ) {
global $IP;
@@ -3600,7 +3922,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
* all. If it's set to false, $GLOBALS will be used.
*
- * @param bool|array $vars mapping variable name to value.
+ * @param bool|array $vars Mapping variable name to value.
*/
public function setSchemaVars( $vars ) {
$this->mSchemaVars = $vars;
@@ -3612,16 +3934,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Returns true on success, error string or exception on failure (depending
* on object's error ignore settings).
*
- * @param $fp Resource: File handle
- * @param $lineCallback Callback: Optional function called before reading each query
- * @param $resultCallback Callback: Optional function called for each MySQL result
+ * @param resource $fp File handle
+ * @param bool|callable $lineCallback Optional function called before reading each query
+ * @param bool|callable $resultCallback Optional function called for each MySQL result
* @param string $fname Calling function name
- * @param $inputCallback Callback: Optional function called for each complete query sent
+ * @param bool|callable $inputCallback Optional function called for each complete query sent
* @return bool|string
*/
public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = __METHOD__, $inputCallback = false )
- {
+ $fname = __METHOD__, $inputCallback = false
+ ) {
$cmd = '';
while ( !feof( $fp ) ) {
@@ -3659,6 +3981,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( false === $res ) {
$err = $this->lastError();
+
return "Query \"{$cmd}\" failed with error code \"$err\".\n";
}
}
@@ -3674,7 +3997,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* @param string $sql SQL assembled so far
* @param string $newLine New line about to be added to $sql
- * @return Bool Whether $newLine contains end of the statement
+ * @return bool Whether $newLine contains end of the statement
*/
public function streamStatementEnd( &$sql, &$newLine ) {
if ( $this->delimiter ) {
@@ -3684,6 +4007,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
return true;
}
}
+
return false;
}
@@ -3702,7 +4026,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* table options its use should be avoided.
*
* @param string $ins SQL statement to replace variables in
- * @return String The new SQL statement with variables replaced
+ * @return string The new SQL statement with variables replaced
*/
protected function replaceSchemaVars( $ins ) {
$vars = $this->getSchemaVars();
@@ -3714,14 +4038,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
// replace /*$var*/
$ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
}
+
return $ins;
}
/**
* Replace variables in sourced SQL
*
- * @param $ins string
- *
+ * @param string $ins
* @return string
*/
protected function replaceVars( $ins ) {
@@ -3767,8 +4091,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Table name callback
*
- * @param $matches array
- *
+ * @param array $matches
* @return string
*/
protected function tableNameCallback( $matches ) {
@@ -3778,8 +4101,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Index name callback
*
- * @param $matches array
- *
+ * @param array $matches
* @return string
*/
protected function indexNameCallback( $matches ) {
@@ -3789,9 +4111,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Check to see if a named lock is available. This is non-blocking.
*
- * @param string $lockName name of lock to poll
- * @param string $method name of method calling us
- * @return Boolean
+ * @param string $lockName Name of lock to poll
+ * @param string $method Name of method calling us
+ * @return bool
* @since 1.20
*/
public function lockIsFree( $lockName, $method ) {
@@ -3804,10 +4126,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Abstracted from Filestore::lock() so child classes can implement for
* their own needs.
*
- * @param string $lockName name of lock to aquire
- * @param string $method name of method calling us
- * @param $timeout Integer: timeout
- * @return Boolean
+ * @param string $lockName Name of lock to aquire
+ * @param string $method Name of method calling us
+ * @param int $timeout
+ * @return bool
*/
public function lock( $lockName, $method, $timeout = 5 ) {
return true;
@@ -3830,11 +4152,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Lock specific tables
*
- * @param array $read of tables to lock for read access
- * @param array $write of tables to lock for write access
- * @param string $method name of caller
+ * @param array $read Array of tables to lock for read access
+ * @param array $write Array of tables to lock for write access
+ * @param string $method Name of caller
* @param bool $lowPriority Whether to indicate writes to be LOW PRIORITY
- *
* @return bool
*/
public function lockTables( $read, $write, $method, $lowPriority = true ) {
@@ -3844,8 +4165,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Unlock specific tables
*
- * @param string $method the caller
- *
+ * @param string $method The caller
* @return bool
*/
public function unlockTables( $method ) {
@@ -3854,8 +4174,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Delete a table
- * @param $tableName string
- * @param $fName string
+ * @param string $tableName
+ * @param string $fName
* @return bool|ResultWrapper
* @since 1.18
*/
@@ -3867,6 +4187,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $this->cascadingDeletes() ) {
$sql .= " CASCADE";
}
+
return $this->query( $sql, $fName );
}
@@ -3874,7 +4195,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Get search engine class. All subclasses of this need to implement this
* if they wish to use searching.
*
- * @return String
+ * @return string
*/
public function getSearchEngine() {
return 'SearchEngineDummy';
@@ -3885,7 +4206,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* keyword for timestamps in PostgreSQL, and works with CHAR(14) as well
* because "i" sorts after all numbers.
*
- * @return String
+ * @return string
*/
public function getInfinity() {
return 'infinity';
@@ -3894,8 +4215,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Encode an expiry time into the DBMS dependent format
*
- * @param string $expiry timestamp for expiry, or the 'infinity' string
- * @return String
+ * @param string $expiry Timestamp for expiry, or the 'infinity' string
+ * @return string
*/
public function encodeExpiry( $expiry ) {
return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
@@ -3907,8 +4228,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Decode an expiry time into a DBMS independent format
*
* @param string $expiry DB timestamp field value for expiry
- * @param $format integer: TS_* constant, defaults to TS_MW
- * @return String
+ * @param int $format TS_* constant, defaults to TS_MW
+ * @return string
*/
public function decodeExpiry( $expiry, $format = TS_MW ) {
return ( $expiry == '' || $expiry == $this->getInfinity() )
@@ -3922,7 +4243,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* This is a MySQL-specific feature.
*
- * @param $value Mixed: true for allow, false for deny, or "default" to
+ * @param bool|string $value True for allow, false for deny, or "default" to
* restore the initial value
*/
public function setBigSelects( $value = true ) {
@@ -3931,6 +4252,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* @since 1.19
+ * @return string
*/
public function __toString() {
return (string)$this->mConn;
@@ -3947,10 +4269,9 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$callers = array();
foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
$callers[] = $callbackInfo[1];
-
}
$callers = implode( ', ', $callers );
- trigger_error( "DB transaction callbacks still pending (from $callers)." );
+ trigger_error( "DB transaction callbacks still pending (from $callers)." );
}
}
}
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index 0875695f..2dfec41d 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -26,22 +26,28 @@
* @ingroup Database
*/
class DBError extends MWException {
-
- /**
- * @var DatabaseBase
- */
+ /** @var DatabaseBase */
public $db;
/**
* Construct a database error
- * @param $db DatabaseBase object which threw the error
+ * @param DatabaseBase $db Object which threw the error
* @param string $error A simple error message to be used for debugging
*/
function __construct( DatabaseBase $db = null, $error ) {
$this->db = $db;
parent::__construct( $error );
}
+}
+/**
+ * Base class for the more common types of database errors. These are known to occur
+ * frequently, so we try to give friendly error messages for them.
+ *
+ * @ingroup Database
+ * @since 1.23
+ */
+class DBExpectedError extends DBError {
/**
* @return string
*/
@@ -66,8 +72,7 @@ class DBError extends MWException {
$s = $this->getHTMLContent();
if ( $wgShowDBErrorBacktrace ) {
- $s .= '<p>Backtrace:</p><p>' .
- nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . '</p>';
+ $s .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
}
return $s;
@@ -84,16 +89,21 @@ class DBError extends MWException {
* @return string
*/
protected function getHTMLContent() {
- return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) . '</p>';
+ return '<p>' . nl2br( htmlspecialchars( $this->getTextContent() ) ) . '</p>';
}
}
/**
* @ingroup Database
*/
-class DBConnectionError extends DBError {
+class DBConnectionError extends DBExpectedError {
+ /** @var string Error text */
public $error;
+ /**
+ * @param DatabaseBase $db Object throwing the error
+ * @param string $error Error text
+ */
function __construct( DatabaseBase $db = null, $error = 'unknown error' ) {
$msg = 'DB connection error';
@@ -116,25 +126,24 @@ class DBConnectionError extends DBError {
}
/**
- * @param $key
- * @param $fallback
- * @return string
+ * @param string $key
+ * @param string $fallback Unescaped alternative error text in case the
+ * message cache cannot be used. Can contain parameters as in regular
+ * messages, that should be passed as additional parameters.
+ * @return string Unprocessed plain error text with parameters replaced
*/
function msg( $key, $fallback /*[, params...] */ ) {
- global $wgLang;
-
$args = array_slice( func_get_args(), 2 );
if ( $this->useMessageCache() ) {
- $message = $wgLang->getMessage( $key );
+ return wfMessage( $key, $args )->useDatabase( false )->text();
} else {
- $message = $fallback;
+ return wfMsgReplaceArgs( $fallback, $args );
}
- return wfMsgReplaceArgs( $message, $args );
}
/**
- * @return boolean
+ * @return bool
*/
function isLoggable() {
// Don't send to the exception log, already in dberror log
@@ -142,20 +151,19 @@ class DBConnectionError extends DBError {
}
/**
- * @return string
- */
- function getPageTitle() {
- return $this->msg( 'dberr-header', 'This wiki has a problem' );
- }
-
- /**
- * @return string
+ * @return string Safe HTML
*/
function getHTML() {
global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
- $sorry = htmlspecialchars( $this->msg( 'dberr-problems', "Sorry!\nThis site is experiencing technical difficulties." ) );
- $again = htmlspecialchars( $this->msg( 'dberr-again', 'Try waiting a few minutes and reloading.' ) );
+ $sorry = htmlspecialchars( $this->msg(
+ 'dberr-problems',
+ 'Sorry! This site is experiencing technical difficulties.'
+ ) );
+ $again = htmlspecialchars( $this->msg(
+ 'dberr-again',
+ 'Try waiting a few minutes and reloading.'
+ ) );
if ( $wgShowHostnames || $wgShowSQLErrors ) {
$info = str_replace(
@@ -163,23 +171,25 @@ class DBConnectionError extends DBError {
htmlspecialchars( $this->msg( 'dberr-info', '(Cannot contact the database server: $1)' ) )
);
} else {
- $info = htmlspecialchars( $this->msg( 'dberr-info-hidden', '(Cannot contact the database server)' ) );
+ $info = htmlspecialchars( $this->msg(
+ 'dberr-info-hidden',
+ '(Cannot contact the database server)'
+ ) );
}
# No database access
MessageCache::singleton()->disable();
- $text = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
+ $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
if ( $wgShowDBErrorBacktrace ) {
- $text .= '<p>Backtrace:</p><p>' .
- nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . '</p>';
+ $html .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
}
- $text .= '<hr />';
- $text .= $this->searchForm();
+ $html .= '<hr />';
+ $html .= $this->searchForm();
- return $text;
+ return $html;
}
protected function getTextContent() {
@@ -192,25 +202,31 @@ class DBConnectionError extends DBError {
}
}
+ /**
+ * Output the exception report using HTML.
+ *
+ * @return void
+ */
public function reportHTML() {
global $wgUseFileCache;
- # Check whether we can serve a file-cached copy of the page with the error underneath
+ // Check whether we can serve a file-cached copy of the page with the error underneath
if ( $wgUseFileCache ) {
try {
$cache = $this->fileCachedPage();
- # Cached version on file system?
+ // Cached version on file system?
if ( $cache !== null ) {
- # Hack: extend the body for error messages
+ // Hack: extend the body for error messages
$cache = str_replace( array( '</html>', '</body>' ), '', $cache );
- # Add cache notice...
- $cache .= '<div style="color:red;font-size:150%;font-weight:bold;">' .
+ // Add cache notice...
+ $cache .= '<div style="border:1px solid #ffd0d0;padding:1em;">' .
htmlspecialchars( $this->msg( 'dberr-cachederror',
- 'This is a cached copy of the requested page, and may not be up to date. ' ) ) .
+ 'This is a cached copy of the requested page, and may not be up to date.' ) ) .
'</div>';
- # Output cached page with notices on bottom and re-close body
+ // Output cached page with notices on bottom and re-close body
echo "{$cache}<hr />{$this->getHTML()}</body></html>";
+
return;
}
} catch ( MWException $e ) {
@@ -218,7 +234,7 @@ class DBConnectionError extends DBError {
}
}
- # We can't, cough and die in the usual fashion
+ // We can't, cough and die in the usual fashion
parent::reportHTML();
}
@@ -228,8 +244,14 @@ class DBConnectionError extends DBError {
function searchForm() {
global $wgSitename, $wgCanonicalServer, $wgRequest;
- $usegoogle = htmlspecialchars( $this->msg( 'dberr-usegoogle', 'You can try searching via Google in the meantime.' ) );
- $outofdate = htmlspecialchars( $this->msg( 'dberr-outofdate', 'Note that their indexes of our content may be out of date.' ) );
+ $usegoogle = htmlspecialchars( $this->msg(
+ 'dberr-usegoogle',
+ 'You can try searching via Google in the meantime.'
+ ) );
+ $outofdate = htmlspecialchars( $this->msg(
+ 'dberr-outofdate',
+ 'Note that their indexes of our content may be out of date.'
+ ) );
$googlesearch = htmlspecialchars( $this->msg( 'searchbutton', 'Search' ) );
$search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
@@ -239,8 +261,8 @@ class DBConnectionError extends DBError {
$trygoogle = <<<EOT
<div style="margin: 1.5em">$usegoogle<br />
-<small>$outofdate</small></div>
-<!-- SiteSearch Google -->
+<small>$outofdate</small>
+</div>
<form method="get" action="//www.google.com/search" id="googlesearch">
<input type="hidden" name="domains" value="$server" />
<input type="hidden" name="num" value="50" />
@@ -249,13 +271,13 @@ class DBConnectionError extends DBError {
<input type="text" name="q" size="31" maxlength="255" value="$search" />
<input type="submit" name="btnG" value="$googlesearch" />
- <div>
- <input type="radio" name="sitesearch" id="gwiki" value="$server" checked="checked" /><label for="gwiki">$sitename</label>
- <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
- </div>
+ <p>
+ <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
+ <label><input type="radio" name="sitesearch" value="" />WWW</label>
+ </p>
</form>
-<!-- SiteSearch Google -->
EOT;
+
return $trygoogle;
}
@@ -263,26 +285,28 @@ EOT;
* @return string
*/
private function fileCachedPage() {
- global $wgTitle, $wgOut, $wgRequest;
+ $context = RequestContext::getMain();
- if ( $wgOut->isDisabled() ) {
- return ''; // Done already?
+ if ( $context->getOutput()->isDisabled() ) {
+ // Done already?
+ return '';
}
- if ( $wgTitle ) { // use $wgTitle if we managed to set it
- $t = $wgTitle->getPrefixedDBkey();
+ if ( $context->getTitle() ) {
+ // Use the main context's title if we managed to set it
+ $t = $context->getTitle()->getPrefixedDBkey();
} else {
- # Fallback to the raw title URL param. We can't use the Title
- # class is it may hit the interwiki table and give a DB error.
- # We may get a cache miss due to not sanitizing the title though.
- $t = str_replace( ' ', '_', $wgRequest->getVal( 'title' ) );
+ // Fallback to the raw title URL param. We can't use the Title
+ // class is it may hit the interwiki table and give a DB error.
+ // We may get a cache miss due to not sanitizing the title though.
+ $t = str_replace( ' ', '_', $context->getRequest()->getVal( 'title' ) );
if ( $t == '' ) { // fallback to main page
$t = Title::newFromText(
$this->msg( 'mainpage', 'Main Page' ) )->getPrefixedDBkey();
}
}
- $cache = HTMLFileCache::newFromTitle( $t, 'view' );
+ $cache = new HTMLFileCache( $t, 'view' );
if ( $cache->isCached() ) {
return $cache->fetchText();
} else {
@@ -294,18 +318,20 @@ EOT;
/**
* @ingroup Database
*/
-class DBQueryError extends DBError {
+class DBQueryError extends DBExpectedError {
public $error, $errno, $sql, $fname;
/**
- * @param $db DatabaseBase
- * @param $error string
- * @param $errno int|string
- * @param $sql string
- * @param $fname string
+ * @param DatabaseBase $db
+ * @param string $error
+ * @param int|string $errno
+ * @param string $sql
+ * @param string $fname
*/
function __construct( DatabaseBase $db, $error, $errno, $sql, $fname ) {
- $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
+ $message = "A database error has occurred. Did you forget to run " .
+ "maintenance/update.php after upgrading? See: " .
+ "https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
"Query: $sql\n" .
"Function: $fname\n" .
"Error: $errno $error\n";
@@ -318,14 +344,6 @@ class DBQueryError extends DBError {
}
/**
- * @return boolean
- */
- function isLoggable() {
- // Don't send to the exception log, already in dberror log
- return false;
- }
-
- /**
* @return string
*/
function getPageTitle() {
@@ -380,7 +398,7 @@ class DBQueryError extends DBError {
* sites using this option probably don't care much about "security by obscurity". Of course,
* if $wgShowSQLErrors is true, the SQL query *is* shown.
*
- * @return array: Keys are message keys; values are arrays of arguments for Html::element().
+ * @return array Keys are message keys; values are arrays of arguments for Html::element().
* Array will be empty if users are not allowed to see any of these details at all.
*/
protected function getTechnicalDetails() {
@@ -405,7 +423,7 @@ class DBQueryError extends DBError {
/**
* @param string $key Message key
- * @return string: English message text
+ * @return string English message text
*/
private function getFallbackMessage( $key ) {
$messages = array(
@@ -416,12 +434,13 @@ This may indicate a bug in the software.',
'databaseerror-function' => 'Function: $1',
'databaseerror-error' => 'Error: $1',
);
+
return $messages[$key];
}
-
}
/**
* @ingroup Database
*/
-class DBUnexpectedError extends DBError {}
+class DBUnexpectedError extends DBError {
+}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 37f5372e..af3cc72d 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -22,47 +22,54 @@
* @author Joel Penner <a-joelpe at microsoft dot com>
* @author Chris Pucci <a-cpucci at microsoft dot com>
* @author Ryan Biesemeyer <v-ryanbi at microsoft dot com>
+ * @author Ryan Schmidt <skizzerz at gmail dot com>
*/
/**
* @ingroup Database
*/
class DatabaseMssql extends DatabaseBase {
- var $mInsertId = null;
- var $mLastResult = null;
- var $mAffectedRows = null;
-
- var $mPort;
-
- function cascadingDeletes() {
+ protected $mInsertId = null;
+ protected $mLastResult = null;
+ protected $mAffectedRows = null;
+ protected $mSubqueryId = 0;
+ protected $mScrollableCursor = true;
+ protected $mPrepareStatements = true;
+ protected $mBinaryColumnCache = null;
+ protected $mBitColumnCache = null;
+ protected $mIgnoreDupKeyErrors = false;
+
+ protected $mPort;
+
+ public function cascadingDeletes() {
return true;
}
- function cleanupTriggers() {
- return true;
+ public function cleanupTriggers() {
+ return false;
}
- function strictIPs() {
- return true;
+ public function strictIPs() {
+ return false;
}
- function realTimestamps() {
- return true;
+ public function realTimestamps() {
+ return false;
}
- function implicitGroupby() {
+ public function implicitGroupby() {
return false;
}
- function implicitOrderby() {
+ public function implicitOrderby() {
return false;
}
- function functionalIndexes() {
+ public function functionalIndexes() {
return true;
}
- function unionSupportsOrderAndLimit() {
+ public function unionSupportsOrderAndLimit() {
return false;
}
@@ -75,16 +82,21 @@ class DatabaseMssql extends DatabaseBase {
* @throws DBConnectionError
* @return bool|DatabaseBase|null
*/
- function open( $server, $user, $password, $dbName ) {
+ public function open( $server, $user, $password, $dbName ) {
# Test for driver support, to avoid suppressed fatal error
if ( !function_exists( 'sqlsrv_connect' ) ) {
- throw new DBConnectionError( $this, "MS Sql Server Native (sqlsrv) functions missing. You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n" );
+ throw new DBConnectionError(
+ $this,
+ "Microsoft SQL Server Native (sqlsrv) functions missing.
+ You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
+ );
}
- global $wgDBport;
+ global $wgDBport, $wgDBWindowsAuthentication;
- if ( !strlen( $user ) ) { # e.g. the class is being loaded
- return;
+ # e.g. the class is being loaded
+ if ( !strlen( $user ) ) {
+ return null;
}
$this->close();
@@ -100,35 +112,23 @@ class DatabaseMssql extends DatabaseBase {
$connectionInfo['Database'] = $dbName;
}
- // Start NT Auth Hack
- // Quick and dirty work around to provide NT Auth designation support.
- // Current solution requires installer to know to input 'ntauth' for both username and password
- // to trigger connection via NT Auth. - ugly, ugly, ugly
- // TO-DO: Make this better and add NT Auth choice to MW installer when SQL Server option is chosen.
- $ntAuthUserTest = strtolower( $user );
- $ntAuthPassTest = strtolower( $password );
-
// Decide which auth scenerio to use
- if ( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) {
- // Don't add credentials to $connectionInfo
- } else {
+ // if we are using Windows auth, don't add credentials to $connectionInfo
+ if ( !$wgDBWindowsAuthentication ) {
$connectionInfo['UID'] = $user;
$connectionInfo['PWD'] = $password;
}
- // End NT Auth Hack
wfSuppressWarnings();
$this->mConn = sqlsrv_connect( $server, $connectionInfo );
wfRestoreWarnings();
if ( $this->mConn === false ) {
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError() . "\n" );
- return false;
+ throw new DBConnectionError( $this, $this->lastError() );
}
$this->mOpened = true;
+
return $this->mConn;
}
@@ -141,14 +141,39 @@ class DatabaseMssql extends DatabaseBase {
return sqlsrv_close( $this->mConn );
}
+ /**
+ * @param bool|MssqlResultWrapper|resource $result
+ * @return bool|MssqlResultWrapper
+ */
+ public function resultObject( $result ) {
+ if ( empty( $result ) ) {
+ return false;
+ } elseif ( $result instanceof MssqlResultWrapper ) {
+ return $result;
+ } elseif ( $result === true ) {
+ // Successful write query
+ return $result;
+ } else {
+ return new MssqlResultWrapper( $this, $result );
+ }
+ }
+
+ /**
+ * @param string $sql
+ * @return bool|MssqlResult
+ * @throws DBUnexpectedError
+ */
protected function doQuery( $sql ) {
- wfDebug( "SQL: [$sql]\n" );
+ if ( $this->debug() ) {
+ wfDebug( "SQL: [$sql]\n" );
+ }
$this->offset = 0;
- // several extensions seem to think that all databases support limits via LIMIT N after the WHERE clause
- // well, MSSQL uses SELECT TOP N, so to catch any of those extensions we'll do a quick check for a LIMIT
- // clause and pass $sql through $this->LimitToTopN() which parses the limit clause and passes the result to
- // $this->limitResult();
+ // several extensions seem to think that all databases support limits
+ // via LIMIT N after the WHERE clause well, MSSQL uses SELECT TOP N,
+ // so to catch any of those extensions we'll do a quick check for a
+ // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
+ // the limit clause and passes the result to $this->limitResult();
if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
// massage LIMIT -> TopN
$sql = $this->LimitToTopN( $sql );
@@ -161,149 +186,235 @@ class DatabaseMssql extends DatabaseBase {
}
// perform query
- $stmt = sqlsrv_query( $this->mConn, $sql );
- if ( $stmt == false ) {
- $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
- "Query: " . htmlentities( $sql ) . "\n" .
- "Function: " . __METHOD__ . "\n";
- // process each error (our driver will give us an array of errors unlike other providers)
- foreach ( sqlsrv_errors() as $error ) {
- $message .= $message . "ERROR[" . $error['code'] . "] " . $error['message'] . "\n";
- }
- throw new DBUnexpectedError( $this, $message );
+ // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
+ // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
+ // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
+ // strings make php throw a fatal error "Severe error translating Unicode"
+ if ( $this->mScrollableCursor ) {
+ $scrollArr = array( 'Scrollable' => SQLSRV_CURSOR_STATIC );
+ } else {
+ $scrollArr = array();
}
- // remember number of rows affected
- $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
- // if it is a SELECT statement, or an insert with a request to output something we want to return a row.
- if ( ( preg_match( '#\bSELECT\s#i', $sql ) ) ||
- ( preg_match( '#\bINSERT\s#i', $sql ) && preg_match( '#\bOUTPUT\s+INSERTED\b#i', $sql ) ) ) {
- // this is essentially a rowset, but Mediawiki calls these 'result'
- // the rowset owns freeing the statement
- $res = new MssqlResult( $stmt );
+ if ( $this->mPrepareStatements ) {
+ // we do prepare + execute so we can get its field metadata for later usage if desired
+ $stmt = sqlsrv_prepare( $this->mConn, $sql, array(), $scrollArr );
+ $success = sqlsrv_execute( $stmt );
} else {
- // otherwise we simply return it was successful, failure throws an exception
- $res = true;
+ $stmt = sqlsrv_query( $this->mConn, $sql, array(), $scrollArr );
+ $success = (bool)$stmt;
}
- return $res;
- }
- function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
+ if ( $this->mIgnoreDupKeyErrors ) {
+ // ignore duplicate key errors, but nothing else
+ // this emulates INSERT IGNORE in MySQL
+ if ( $success === false ) {
+ $errors = sqlsrv_errors( SQLSRV_ERR_ERRORS );
+ $success = true;
+
+ foreach ( $errors as $err ) {
+ if ( $err['SQLSTATE'] == '23000' && $err['code'] == '2601' ) {
+ continue; // duplicate key error caused by unique index
+ } elseif ( $err['SQLSTATE'] == '23000' && $err['code'] == '2627' ) {
+ continue; // duplicate key error caused by primary key
+ } elseif ( $err['SQLSTATE'] == '01000' && $err['code'] == '3621' ) {
+ continue; // generic "the statement has been terminated" error
+ }
+
+ $success = false; // getting here means we got an error we weren't expecting
+ break;
+ }
+
+ if ( $success ) {
+ $this->mAffectedRows = 0;
+ return $stmt;
+ }
+ }
+ }
+
+ if ( $success === false ) {
+ return false;
}
- $res->free();
+ // remember number of rows affected
+ $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
+
+ return $stmt;
}
- function fetchObject( $res ) {
+ public function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- $row = $res->fetch( 'OBJECT' );
- return $row;
+
+ sqlsrv_free_stmt( $res );
}
- function getErrors() {
- $strRet = '';
- $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
- if ( $retErrors != null ) {
- foreach ( $retErrors as $arrError ) {
- $strRet .= "SQLState: " . $arrError['SQLSTATE'] . "\n";
- $strRet .= "Error Code: " . $arrError['code'] . "\n";
- $strRet .= "Message: " . $arrError['message'] . "\n";
- }
- } else {
- $strRet = "No errors found";
- }
- return $strRet;
+ /**
+ * @param MssqlResultWrapper $res
+ * @return stdClass
+ */
+ public function fetchObject( $res ) {
+ // $res is expected to be an instance of MssqlResultWrapper here
+ return $res->fetchObject();
}
- function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- $row = $res->fetch( SQLSRV_FETCH_BOTH );
- return $row;
+ /**
+ * @param MssqlResultWrapper $res
+ * @return array
+ */
+ public function fetchRow( $res ) {
+ return $res->fetchRow();
}
- function numRows( $res ) {
+ /**
+ * @param mixed $res
+ * @return int
+ */
+ public function numRows( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return ( $res ) ? $res->numrows() : 0;
+
+ return sqlsrv_num_rows( $res );
}
- function numFields( $res ) {
+ /**
+ * @param mixed $res
+ * @return int
+ */
+ public function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return ( $res ) ? $res->numfields() : 0;
+
+ return sqlsrv_num_fields( $res );
}
- function fieldName( $res, $n ) {
+ /**
+ * @param mixed $res
+ * @param int $n
+ * @return int
+ */
+ public function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return ( $res ) ? $res->fieldname( $n ) : 0;
+
+ $metadata = sqlsrv_field_metadata( $res );
+ return $metadata[$n]['Name'];
}
/**
* This must be called after nextSequenceVal
- * @return null
+ * @return int|null
*/
- function insertId() {
+ public function insertId() {
return $this->mInsertId;
}
- function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return ( $res ) ? $res->seek( $row ) : false;
+ /**
+ * @param MssqlResultWrapper $res
+ * @param int $row
+ * @return bool
+ */
+ public function dataSeek( $res, $row ) {
+ return $res->seek( $row );
}
- function lastError() {
- if ( $this->mConn ) {
- return $this->getErrors();
+ /**
+ * @return string
+ */
+ public function lastError() {
+ $strRet = '';
+ $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
+ if ( $retErrors != null ) {
+ foreach ( $retErrors as $arrError ) {
+ $strRet .= $this->formatError( $arrError ) . "\n";
+ }
} else {
- return "No database connection";
+ $strRet = "No errors found";
}
+
+ return $strRet;
+ }
+
+ /**
+ * @param array $err
+ * @return string
+ */
+ private function formatError( $err ) {
+ return '[SQLSTATE ' . $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
}
- function lastErrno() {
+ /**
+ * @return string
+ */
+ public function lastErrno() {
$err = sqlsrv_errors( SQLSRV_ERR_ALL );
- if ( $err[0] ) {
+ if ( $err !== null && isset( $err[0] ) ) {
return $err[0]['code'];
} else {
return 0;
}
}
- function affectedRows() {
+ /**
+ * @return int
+ */
+ public function affectedRows() {
return $this->mAffectedRows;
}
/**
* SELECT wrapper
*
- * @param $table Mixed: array or string, table name(s) (prefix auto-added)
- * @param $vars Mixed: array or string, field name(s) to be retrieved
- * @param $conds Mixed: array or string, condition(s) for WHERE
- * @param $fname String: calling function name (use __METHOD__) for logs/profiling
- * @param array $options associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @param $join_conds Array: Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * @param mixed $table Array or string, table name(s) (prefix auto-added)
+ * @param mixed $vars Array or string, field name(s) to be retrieved
+ * @param mixed $conds Array or string, condition(s) for WHERE
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options Associative array of options (e.g.
+ * array('GROUP BY' => 'page_title')), see Database::makeSelectOptions
+ * code for list of supported stuff
+ * @param array $join_conds Associative array of table join conditions
+ * (optional) (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return mixed Database result resource (feed to Database::fetchObject
+ * or whatever), or false on failure
*/
- function select( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() )
- {
+ public function select( $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = array(), $join_conds = array()
+ ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
if ( isset( $options['EXPLAIN'] ) ) {
- sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL ON;" );
- $ret = $this->query( $sql, $fname );
- sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL OFF;" );
+ try {
+ $this->mScrollableCursor = false;
+ $this->mPrepareStatements = false;
+ $this->query( "SET SHOWPLAN_ALL ON" );
+ $ret = $this->query( $sql, $fname );
+ $this->query( "SET SHOWPLAN_ALL OFF" );
+ } catch ( DBQueryError $dqe ) {
+ if ( isset( $options['FOR COUNT'] ) ) {
+ // likely don't have privs for SHOWPLAN, so run a select count instead
+ $this->query( "SET SHOWPLAN_ALL OFF" );
+ unset( $options['EXPLAIN'] );
+ $ret = $this->select(
+ $table,
+ 'COUNT(*) AS EstimateRows',
+ $conds,
+ $fname,
+ $options,
+ $join_conds
+ );
+ } else {
+ // someone actually wanted the query plan instead of an est row count
+ // let them know of the error
+ $this->mScrollableCursor = true;
+ $this->mPrepareStatements = true;
+ throw $dqe;
+ }
+ }
+ $this->mScrollableCursor = true;
+ $this->mPrepareStatements = true;
return $ret;
}
return $this->query( $sql, $fname );
@@ -312,21 +423,70 @@ class DatabaseMssql extends DatabaseBase {
/**
* SELECT wrapper
*
- * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
- * @param $vars Mixed: Array or string, field name(s) to be retrieved
- * @param $conds Mixed: Array or string, condition(s) for WHERE
- * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
- * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @param $join_conds Array: Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return string, the SQL text
+ * @param mixed $table Array or string, table name(s) (prefix auto-added)
+ * @param mixed $vars Array or string, field name(s) to be retrieved
+ * @param mixed $conds Array or string, condition(s) for WHERE
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of supported stuff
+ * @param array $join_conds Associative array of table join conditions (optional)
+ * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return string The SQL text
*/
- function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() ) {
+ public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = array(), $join_conds = array()
+ ) {
if ( isset( $options['EXPLAIN'] ) ) {
unset( $options['EXPLAIN'] );
}
- return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+
+ $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+
+ // try to rewrite aggregations of bit columns (currently MAX and MIN)
+ if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
+ $bitColumns = array();
+ if ( is_array( $table ) ) {
+ foreach ( $table as $t ) {
+ $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
+ }
+ } else {
+ $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
+ }
+
+ foreach ( $bitColumns as $col => $info ) {
+ $replace = array(
+ "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
+ "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
+ );
+ $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
+ }
+ }
+
+ return $sql;
+ }
+
+ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+ $fname = __METHOD__
+ ) {
+ $this->mScrollableCursor = false;
+ try {
+ parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ throw $e;
+ }
+ $this->mScrollableCursor = true;
+ }
+
+ public function delete( $table, $conds, $fname = __METHOD__ ) {
+ $this->mScrollableCursor = false;
+ try {
+ parent::delete( $table, $conds, $fname );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ throw $e;
+ }
+ $this->mScrollableCursor = true;
}
/**
@@ -335,30 +495,45 @@ class DatabaseMssql extends DatabaseBase {
* This is not necessarily an accurate estimate, so use sparingly
* Returns -1 if count cannot be found
* Takes same arguments as Database::select()
+ * @param string $table
+ * @param string $vars
+ * @param string $conds
+ * @param string $fname
+ * @param array $options
* @return int
*/
- function estimateRowCount( $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() ) {
- $options['EXPLAIN'] = true;// http://msdn2.microsoft.com/en-us/library/aa259203.aspx
+ public function estimateRowCount( $table, $vars = '*', $conds = '',
+ $fname = __METHOD__, $options = array()
+ ) {
+ // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
+ $options['EXPLAIN'] = true;
+ $options['FOR COUNT'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
if ( $res ) {
$row = $this->fetchRow( $res );
+
if ( isset( $row['EstimateRows'] ) ) {
$rows = $row['EstimateRows'];
}
}
+
return $rows;
}
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return array|bool|null
*/
- function indexInfo( $table, $index, $fname = __METHOD__ ) {
- # This does not return the same info as MYSQL would, but that's OK because MediaWiki never uses the
- # returned value except to check for the existance of indexes.
+ public function indexInfo( $table, $index, $fname = __METHOD__ ) {
+ # This does not return the same info as MYSQL would, but that's OK
+ # because MediaWiki never uses the returned value except to check for
+ # the existance of indexes.
$sql = "sp_helpindex '" . $table . "'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
@@ -383,6 +558,7 @@ class DatabaseMssql extends DatabaseBase {
}
}
}
+
return empty( $result ) ? false : $result;
}
@@ -401,7 +577,7 @@ class DatabaseMssql extends DatabaseBase {
* @throws DBQueryError
* @return bool
*/
- function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
+ public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $arrToInsert ) ) {
return true;
@@ -413,24 +589,39 @@ class DatabaseMssql extends DatabaseBase {
$table = $this->tableName( $table );
- if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) {// Not multi row
- $arrToInsert = array( 0 => $arrToInsert );// make everything multi row compatible
+ if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
+ $arrToInsert = array( 0 => $arrToInsert ); // make everything multi row compatible
}
- $allOk = true;
-
// We know the table we're inserting into, get its identity column
$identity = null;
- $tableRaw = preg_replace( '#\[([^\]]*)\]#', '$1', $table ); // strip matching square brackets from table name
- $res = $this->doQuery( "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'" );
- if ( $res && $res->numrows() ) {
+ // strip matching square brackets and the db/schema from table name
+ $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+ $tableRaw = array_pop( $tableRawArr );
+ $res = $this->doQuery(
+ "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
+ "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
+ );
+ if ( $res && sqlsrv_has_rows( $res ) ) {
// There is an identity for this table.
- $identity = array_pop( $res->fetch( SQLSRV_FETCH_ASSOC ) );
+ $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
+ $identity = array_pop( $identityArr );
+ }
+ sqlsrv_free_stmt( $res );
+
+ // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
+ $binaryColumns = $this->getBinaryColumns( $table );
+
+ // INSERT IGNORE is not supported by SQL Server
+ // remove IGNORE from options list and set ignore flag to true
+ if ( in_array( 'IGNORE', $options ) ) {
+ $options = array_diff( $options, array( 'IGNORE' ) );
+ $this->mIgnoreDupKeyErrors = true;
}
- unset( $res );
foreach ( $arrToInsert as $a ) {
- // start out with empty identity column, this is so we can return it as a result of the insert logic
+ // start out with empty identity column, this is so we can return
+ // it as a result of the insert logic
$sqlPre = '';
$sqlPost = '';
$identityClause = '';
@@ -441,152 +632,246 @@ class DatabaseMssql extends DatabaseBase {
foreach ( $a as $k => $v ) {
if ( $k == $identity ) {
if ( !is_null( $v ) ) {
- // there is a value being passed to us, we need to turn on and off inserted identity
+ // there is a value being passed to us,
+ // we need to turn on and off inserted identity
$sqlPre = "SET IDENTITY_INSERT $table ON;";
$sqlPost = ";SET IDENTITY_INSERT $table OFF;";
-
} else {
- // we can't insert NULL into an identity column, so remove the column from the insert.
+ // we can't insert NULL into an identity column,
+ // so remove the column from the insert.
unset( $a[$k] );
}
}
}
- $identityClause = "OUTPUT INSERTED.$identity "; // we want to output an identity column as result
- }
-
- $keys = array_keys( $a );
- // INSERT IGNORE is not supported by SQL Server
- // remove IGNORE from options list and set ignore flag to true
- $ignoreClause = false;
- foreach ( $options as $k => $v ) {
- if ( strtoupper( $v ) == "IGNORE" ) {
- unset( $options[$k] );
- $ignoreClause = true;
- }
+ // we want to output an identity column as result
+ $identityClause = "OUTPUT INSERTED.$identity ";
}
- // translate MySQL INSERT IGNORE to something SQL Server can use
- // example:
- // MySQL: INSERT IGNORE INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
- // MSSQL: IF NOT EXISTS (SELECT * FROM user_groups WHERE ug_user = '1') INSERT INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
- if ( $ignoreClause ) {
- $prival = $a[$keys[0]];
- $sqlPre .= "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival')";
- }
+ $keys = array_keys( $a );
// Build the actual query
$sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
" INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
$first = true;
- foreach ( $a as $value ) {
+ foreach ( $a as $key => $value ) {
+ if ( isset( $binaryColumns[$key] ) ) {
+ $value = new MssqlBlob( $value );
+ }
if ( $first ) {
$first = false;
} else {
$sql .= ',';
}
- if ( is_string( $value ) ) {
- $sql .= $this->addQuotes( $value );
- } elseif ( is_null( $value ) ) {
+ if ( is_null( $value ) ) {
$sql .= 'null';
} elseif ( is_array( $value ) || is_object( $value ) ) {
- if ( is_object( $value ) && strtolower( get_class( $value ) ) == 'blob' ) {
+ if ( is_object( $value ) && $value instanceof Blob ) {
$sql .= $this->addQuotes( $value );
} else {
$sql .= $this->addQuotes( serialize( $value ) );
}
} else {
- $sql .= $value;
+ $sql .= $this->addQuotes( $value );
}
}
$sql .= ')' . $sqlPost;
// Run the query
- $ret = sqlsrv_query( $this->mConn, $sql );
-
- if ( $ret === false ) {
- throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname );
- } elseif ( $ret != null ) {
- // remember number of rows affected
- $this->mAffectedRows = sqlsrv_rows_affected( $ret );
- if ( !is_null( $identity ) ) {
- // then we want to get the identity column value we were assigned and save it off
- $row = sqlsrv_fetch_object( $ret );
+ $this->mScrollableCursor = false;
+ try {
+ $ret = $this->query( $sql );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ $this->mIgnoreDupKeyErrors = false;
+ throw $e;
+ }
+ $this->mScrollableCursor = true;
+
+ if ( !is_null( $identity ) ) {
+ // then we want to get the identity column value we were assigned and save it off
+ $row = $ret->fetchObject();
+ if( is_object( $row ) ){
$this->mInsertId = $row->$identity;
}
- sqlsrv_free_stmt( $ret );
- continue;
}
- $allOk = false;
}
- return $allOk;
+ $this->mIgnoreDupKeyErrors = false;
+ return $ret;
}
/**
* INSERT SELECT wrapper
* $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
- * $conds may be "*" to copy the whole table
- * srcTable may be an array of tables.
+ * Source items may be literals rather than field names, but strings should
+ * be quoted with Database::addQuotes().
* @param string $destTable
- * @param array|string $srcTable
+ * @param array|string $srcTable May be an array of tables.
* @param array $varMap
- * @param array $conds
+ * @param array $conds May be "*" to copy the whole table.
* @param string $fname
* @param array $insertOptions
* @param array $selectOptions
* @throws DBQueryError
* @return null|ResultWrapper
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() ) {
- $ret = parent::insertSelect( $destTable, $srcTable, $varMap, $conds, $fname, $insertOptions, $selectOptions );
-
- if ( $ret === false ) {
- throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), /*$sql*/ '', $fname );
- } elseif ( $ret != null ) {
- // remember number of rows affected
- $this->mAffectedRows = sqlsrv_rows_affected( $ret );
- return $ret;
+ public function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
+ $insertOptions = array(), $selectOptions = array()
+ ) {
+ $this->mScrollableCursor = false;
+ try {
+ $ret = parent::insertSelect(
+ $destTable,
+ $srcTable,
+ $varMap,
+ $conds,
+ $fname,
+ $insertOptions,
+ $selectOptions
+ );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ throw $e;
}
- return null;
+ $this->mScrollableCursor = true;
+
+ return $ret;
}
/**
- * Return the next in a sequence, save the value for retrieval via insertId()
- * @return
+ * UPDATE wrapper. Takes a condition array and a SET array.
+ *
+ * @param string $table Name of the table to UPDATE. This will be passed through
+ * DatabaseBase::tableName().
+ *
+ * @param array $values An array of values to SET. For each array element,
+ * the key gives the field name, and the value gives the data
+ * to set that field to. The data will be quoted by
+ * DatabaseBase::addQuotes().
+ *
+ * @param array $conds An array of conditions (WHERE). See
+ * DatabaseBase::select() for the details of the format of
+ * condition arrays. Use '*' to update all rows.
+ *
+ * @param string $fname The function name of the caller (from __METHOD__),
+ * for logging and profiling.
+ *
+ * @param array $options An array of UPDATE options, can be:
+ * - IGNORE: Ignore unique key conflicts
+ * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
+ * @return bool
*/
- function nextSequenceValue( $seqName ) {
- if ( !$this->tableExists( 'sequence_' . $seqName ) ) {
- sqlsrv_query( $this->mConn, "CREATE TABLE [sequence_$seqName] (id INT NOT NULL IDENTITY PRIMARY KEY, junk varchar(10) NULL)" );
+ function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
+ $table = $this->tableName( $table );
+ $binaryColumns = $this->getBinaryColumns( $table );
+
+ $opts = $this->makeUpdateOptions( $options );
+ $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
+
+ if ( $conds !== array() && $conds !== '*' ) {
+ $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
}
- sqlsrv_query( $this->mConn, "INSERT INTO [sequence_$seqName] (junk) VALUES ('')" );
- $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
- $row = sqlsrv_fetch_array( $ret, SQLSRV_FETCH_ASSOC );// KEEP ASSOC THERE, weird weird bug dealing with the return value if you don't
- sqlsrv_free_stmt( $ret );
- $this->mInsertId = $row['id'];
- return $row['id'];
+ $this->mScrollableCursor = false;
+ try {
+ $ret = $this->query( $sql );
+ } catch ( Exception $e ) {
+ $this->mScrollableCursor = true;
+ throw $e;
+ }
+ $this->mScrollableCursor = true;
+ return true;
}
/**
- * Return the current value of a sequence. Assumes it has ben nextval'ed in this session.
- * @return
+ * Makes an encoded list of strings from an array
+ * @param array $a Containing the data
+ * @param int $mode Constant
+ * - LIST_COMMA: comma separated, no field names
+ * - LIST_AND: ANDed WHERE clause (without the WHERE). See
+ * the documentation for $conds in DatabaseBase::select().
+ * - LIST_OR: ORed WHERE clause (without the WHERE)
+ * - LIST_SET: comma separated with field names, like a SET clause
+ * - LIST_NAMES: comma separated field names
+ * @param array $binaryColumns Contains a list of column names that are binary types
+ * This is a custom parameter only present for MS SQL.
+ *
+ * @throws MWException|DBUnexpectedError
+ * @return string
*/
- function currentSequenceValue( $seqName ) {
- $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
- if ( $ret !== false ) {
- $row = sqlsrv_fetch_array( $ret );
- sqlsrv_free_stmt( $ret );
- return $row['id'];
- } else {
- return $this->nextSequenceValue( $seqName );
+ public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = array() ) {
+ if ( !is_array( $a ) ) {
+ throw new DBUnexpectedError( $this,
+ 'DatabaseBase::makeList called with incorrect parameters' );
}
+
+ $first = true;
+ $list = '';
+
+ foreach ( $a as $field => $value ) {
+ if ( $mode != LIST_NAMES && isset( $binaryColumns[$field] ) ) {
+ if ( is_array( $value ) ) {
+ foreach ( $value as &$v ) {
+ $v = new MssqlBlob( $v );
+ }
+ } else {
+ $value = new MssqlBlob( $value );
+ }
+ }
+
+ if ( !$first ) {
+ if ( $mode == LIST_AND ) {
+ $list .= ' AND ';
+ } elseif ( $mode == LIST_OR ) {
+ $list .= ' OR ';
+ } else {
+ $list .= ',';
+ }
+ } else {
+ $first = false;
+ }
+
+ if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
+ $list .= "($value)";
+ } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
+ $list .= "$value";
+ } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
+ if ( count( $value ) == 0 ) {
+ throw new MWException( __METHOD__ . ": empty input for field $field" );
+ } elseif ( count( $value ) == 1 ) {
+ // Special-case single values, as IN isn't terribly efficient
+ // Don't necessarily assume the single key is 0; we don't
+ // enforce linear numeric ordering on other arrays here.
+ $value = array_values( $value );
+ $list .= $field . " = " . $this->addQuotes( $value[0] );
+ } else {
+ $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+ }
+ } elseif ( $value === null ) {
+ if ( $mode == LIST_AND || $mode == LIST_OR ) {
+ $list .= "$field IS ";
+ } elseif ( $mode == LIST_SET ) {
+ $list .= "$field = ";
+ }
+ $list .= 'NULL';
+ } else {
+ if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
+ $list .= "$field = ";
+ }
+ $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
+ }
+ }
+
+ return $list;
}
- # Returns the size of a text field, or -1 for "unlimited"
- function textFieldSize( $table, $field ) {
+ /**
+ * @param string $table
+ * @param string $field
+ * @return int Returns the size of a text field, or -1 for "unlimited"
+ */
+ public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
@@ -596,40 +881,75 @@ class DatabaseMssql extends DatabaseBase {
if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
$size = $row['CHARACTER_MAXIMUM_LENGTH'];
}
+
return $size;
}
/**
* Construct a LIMIT query with optional offset
* This is used for query pages
- * $sql string SQL query we will append the limit too
- * $limit integer the SQL limit
- * $offset integer the SQL offset (default false)
- * @return mixed|string
+ *
+ * @param string $sql SQL query we will append the limit too
+ * @param int $limit The SQL limit
+ * @param bool|int $offset The SQL offset (default false)
+ * @return array|string
*/
- function limitResult( $sql, $limit, $offset = false ) {
+ public function limitResult( $sql, $limit, $offset = false ) {
if ( $offset === false || $offset == 0 ) {
if ( strpos( $sql, "SELECT" ) === false ) {
return "TOP {$limit} " . $sql;
} else {
- return preg_replace( '/\bSELECT(\s*DISTINCT)?\b/Dsi', 'SELECT$1 TOP ' . $limit, $sql, 1 );
+ return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
+ 'SELECT$1 TOP ' . $limit, $sql, 1 );
}
} else {
- $sql = '
- SELECT * FROM (
- SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM (
- SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1
- ) as sub2
- ) AS sub3
- WHERE line3 BETWEEN ' . ( $offset + 1 ) . ' AND ' . ( $offset + $limit );
+ // This one is fun, we need to pull out the select list as well as any ORDER BY clause
+ $select = $orderby = array();
+ $s1 = preg_match( '#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
+ $s2 = preg_match( '#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
+ $overOrder = $postOrder = '';
+ $first = $offset + 1;
+ $last = $offset + $limit;
+ $sub1 = 'sub_' . $this->mSubqueryId;
+ $sub2 = 'sub_' . ( $this->mSubqueryId + 1 );
+ $this->mSubqueryId += 2;
+ if ( !$s1 ) {
+ // wat
+ throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
+ }
+ if ( !$s2 ) {
+ // no ORDER BY
+ $overOrder = 'ORDER BY (SELECT 1)';
+ } else {
+ if ( !isset( $orderby[2] ) || !$orderby[2] ) {
+ // don't need to strip it out if we're using a FOR XML clause
+ $sql = str_replace( $orderby[1], '', $sql );
+ }
+ $overOrder = $orderby[1];
+ $postOrder = ' ' . $overOrder;
+ }
+ $sql = "SELECT {$select[1]}
+ FROM (
+ SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
+ FROM ({$sql}) {$sub1}
+ ) {$sub2}
+ WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
+
return $sql;
}
}
- // If there is a limit clause, parse it, strip it, and pass the remaining sql through limitResult()
- // with the appropriate parameters. Not the prettiest solution, but better than building a whole new parser.
- // This exists becase there are still too many extensions that don't use dynamic sql generation.
- function LimitToTopN( $sql ) {
+ /**
+ * If there is a limit clause, parse it, strip it, and pass the remaining
+ * SQL through limitResult() with the appropriate parameters. Not the
+ * prettiest solution, but better than building a whole new parser. This
+ * exists becase there are still too many extensions that don't use dynamic
+ * sql generation.
+ *
+ * @param string $sql
+ * @return array|mixed|string
+ */
+ public function LimitToTopN( $sql ) {
// Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
$pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
if ( preg_match( $pattern, $sql, $matches ) ) {
@@ -637,22 +957,20 @@ class DatabaseMssql extends DatabaseBase {
$row_count = $matches[4];
// offset = $matches[3] OR $matches[6]
$offset = $matches[3] or
- $offset = $matches[6] or
- $offset = false;
+ $offset = $matches[6] or
+ $offset = false;
// strip the matching LIMIT clause out
$sql = str_replace( $matches[0], '', $sql );
+
return $this->limitResult( $sql, $row_count, $offset );
}
- return $sql;
- }
- function timestamp( $ts = 0 ) {
- return wfTimestamp( TS_ISO_8601, $ts );
+ return $sql;
}
/**
- * @return string wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
public function getSoftwareLink() {
return "[{{int:version-db-mssql-url}} MS SQL Server]";
@@ -661,23 +979,40 @@ class DatabaseMssql extends DatabaseBase {
/**
* @return string Version information from the database
*/
- function getServerVersion() {
+ public function getServerVersion() {
$server_info = sqlsrv_server_info( $this->mConn );
$version = 'Error';
if ( isset( $server_info['SQLServerVersion'] ) ) {
$version = $server_info['SQLServerVersion'];
}
+
return $version;
}
- function tableExists( $table, $fname = __METHOD__, $schema = false ) {
- $res = sqlsrv_query( $this->mConn, "SELECT * FROM information_schema.tables
- WHERE table_type='BASE TABLE' AND table_name = '$table'" );
- if ( $res === false ) {
- print "Error in tableExists query: " . $this->getErrors();
+ /**
+ * @param string $table
+ * @param string $fname
+ * @return bool
+ */
+ public function tableExists( $table, $fname = __METHOD__ ) {
+ list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+ if ( $db !== false ) {
+ // remote database
+ wfDebug( "Attempting to call tableExists on a remote table" );
return false;
}
- if ( sqlsrv_fetch( $res ) ) {
+
+ if ( $schema === false ) {
+ global $wgDBmwschema;
+ $schema = $wgDBmwschema;
+ }
+
+ $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_TYPE = 'BASE TABLE'
+ AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
+
+ if ( $res->numRows() ) {
return true;
} else {
return false;
@@ -686,40 +1021,53 @@ class DatabaseMssql extends DatabaseBase {
/**
* Query whether a given column exists in the mediawiki schema
+ * @param string $table
+ * @param string $field
+ * @param string $fname
* @return bool
*/
- function fieldExists( $table, $field, $fname = __METHOD__ ) {
- $table = $this->tableName( $table );
- $res = sqlsrv_query( $this->mConn, "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.Columns
- WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
- if ( $res === false ) {
- print "Error in fieldExists query: " . $this->getErrors();
+ public function fieldExists( $table, $field, $fname = __METHOD__ ) {
+ list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+ if ( $db !== false ) {
+ // remote database
+ wfDebug( "Attempting to call fieldExists on a remote table" );
return false;
}
- if ( sqlsrv_fetch( $res ) ) {
+
+ $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+
+ if ( $res->numRows() ) {
return true;
} else {
return false;
}
}
- function fieldInfo( $table, $field ) {
- $table = $this->tableName( $table );
- $res = sqlsrv_query( $this->mConn, "SELECT * FROM INFORMATION_SCHEMA.Columns
- WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
- if ( $res === false ) {
- print "Error in fieldInfo query: " . $this->getErrors();
+ public function fieldInfo( $table, $field ) {
+ list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+ if ( $db !== false ) {
+ // remote database
+ wfDebug( "Attempting to call fieldInfo on a remote table" );
return false;
}
- $meta = $this->fetchRow( $res );
+
+ $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+
+ $meta = $res->fetchRow();
if ( $meta ) {
return new MssqlField( $meta );
}
+
return false;
}
/**
* Begin a transaction, committing any previously open transaction
+ * @param string $fname
*/
protected function doBegin( $fname = __METHOD__ ) {
sqlsrv_begin_transaction( $this->mConn );
@@ -728,6 +1076,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* End a transaction
+ * @param string $fname
*/
protected function doCommit( $fname = __METHOD__ ) {
sqlsrv_commit( $this->mConn );
@@ -737,6 +1086,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Rollback a transaction.
* No-op on non-transactional databases.
+ * @param string $fname
*/
protected function doRollback( $fname = __METHOD__ ) {
sqlsrv_rollback( $this->mConn );
@@ -747,7 +1097,7 @@ class DatabaseMssql extends DatabaseBase {
* Escapes a identifier for use inm SQL.
* Throws an exception if it is invalid.
* Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
- * @param $identifier
+ * @param string $identifier
* @throws MWException
* @return string
*/
@@ -758,153 +1108,82 @@ class DatabaseMssql extends DatabaseBase {
if ( strlen( $identifier ) > 128 ) {
throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
}
- if ( ( strpos( $identifier, '[' ) !== false ) || ( strpos( $identifier, ']' ) !== false ) ) {
- // It may be allowed if you quoted with double quotation marks, but that would break if QUOTED_IDENTIFIER is OFF
- throw new MWException( "You can't use square brackers in the identifier '$identifier'" );
+ if ( ( strpos( $identifier, '[' ) !== false )
+ || ( strpos( $identifier, ']' ) !== false )
+ ) {
+ // It may be allowed if you quoted with double quotation marks, but
+ // that would break if QUOTED_IDENTIFIER is OFF
+ throw new MWException( "Square brackets are not allowed in '$identifier'" );
}
+
return "[$identifier]";
}
/**
- * Initial setup.
- * Precondition: This object is connected as the superuser.
- * Creates the database, schema, user and login.
+ * @param string $s
+ * @return string
*/
- function initial_setup( $dbName, $newUser, $loginPassword ) {
- $dbName = $this->escapeIdentifier( $dbName );
-
- // It is not clear what can be used as a login,
- // From http://msdn.microsoft.com/en-us/library/ms173463.aspx
- // a sysname may be the same as an identifier.
- $newUser = $this->escapeIdentifier( $newUser );
- $loginPassword = $this->addQuotes( $loginPassword );
-
- $this->doQuery( "CREATE DATABASE $dbName;" );
- $this->doQuery( "USE $dbName;" );
- $this->doQuery( "CREATE SCHEMA $dbName;" );
- $this->doQuery( "
- CREATE
- LOGIN $newUser
- WITH
- PASSWORD=$loginPassword
- ;
- " );
- $this->doQuery( "
- CREATE
- USER $newUser
- FOR
- LOGIN $newUser
- WITH
- DEFAULT_SCHEMA=$dbName
- ;
- " );
- $this->doQuery( "
- GRANT
- BACKUP DATABASE,
- BACKUP LOG,
- CREATE DEFAULT,
- CREATE FUNCTION,
- CREATE PROCEDURE,
- CREATE RULE,
- CREATE TABLE,
- CREATE VIEW,
- CREATE FULLTEXT CATALOG
- ON
- DATABASE::$dbName
- TO $newUser
- ;
- " );
- $this->doQuery( "
- GRANT
- CONTROL
- ON
- SCHEMA::$dbName
- TO $newUser
- ;
- " );
- }
-
- function encodeBlob( $b ) {
- // we can't have zero's and such, this is a simple encoding to make sure we don't barf
- return base64_encode( $b );
- }
-
- function decodeBlob( $b ) {
- // we can't have zero's and such, this is a simple encoding to make sure we don't barf
- return base64_decode( $b );
+ public function strencode( $s ) { # Should not be called by us
+ return str_replace( "'", "''", $s );
}
/**
- * @private
+ * @param string $s
* @return string
*/
- function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
- $ret = array();
- $retJOIN = array();
- $use_index_safe = is_array( $use_index ) ? $use_index : array();
- $join_conds_safe = is_array( $join_conds ) ? $join_conds : array();
- foreach ( $tables as $table ) {
- // Is there a JOIN and INDEX clause for this table?
- if ( isset( $join_conds_safe[$table] ) && isset( $use_index_safe[$table] ) ) {
- $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
- $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
- $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
- $retJOIN[] = $tableClause;
- // Is there an INDEX clause?
- } elseif ( isset( $use_index_safe[$table] ) ) {
- $tableClause = $this->tableName( $table );
- $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
- $ret[] = $tableClause;
- // Is there a JOIN clause?
- } elseif ( isset( $join_conds_safe[$table] ) ) {
- $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
- $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
- $retJOIN[] = $tableClause;
- } else {
- $tableClause = $this->tableName( $table );
- $ret[] = $tableClause;
- }
- }
- // We can't separate explicit JOIN clauses with ',', use ' ' for those
- $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
- $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
- // Compile our final table clause
- return implode( ' ', array( $straightJoins, $otherJoins ) );
- }
-
- function strencode( $s ) { # Should not be called by us
- return str_replace( "'", "''", $s );
- }
-
- function addQuotes( $s ) {
- if ( $s instanceof Blob ) {
- return "'" . $s->fetch( $s ) . "'";
+ public function addQuotes( $s ) {
+ if ( $s instanceof MssqlBlob ) {
+ return $s->fetch();
+ } elseif ( $s instanceof Blob ) {
+ // this shouldn't really ever be called, but it's here if needed
+ // (and will quite possibly make the SQL error out)
+ $blob = new MssqlBlob( $s->fetch() );
+ return $blob->fetch();
} else {
+ if ( is_bool( $s ) ) {
+ $s = $s ? 1 : 0;
+ }
return parent::addQuotes( $s );
}
}
+ /**
+ * @param string $s
+ * @return string
+ */
public function addIdentifierQuotes( $s ) {
// http://msdn.microsoft.com/en-us/library/aa223962.aspx
return '[' . $s . ']';
}
+ /**
+ * @param string $name
+ * @return bool
+ */
public function isQuotedIdentifier( $name ) {
- return $name[0] == '[' && substr( $name, -1, 1 ) == ']';
+ return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
}
- function selectDB( $db ) {
- return ( $this->query( "SET DATABASE $db" ) !== false );
+ /**
+ * @param string $db
+ * @return bool
+ */
+ public function selectDB( $db ) {
+ try {
+ $this->mDBname = $db;
+ $this->query( "USE $db" );
+ return true;
+ } catch ( Exception $e ) {
+ return false;
+ }
}
/**
- * @private
- *
- * @param array $options an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return Array
+ * @param array $options An associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
+ * @return array
*/
- function makeSelectOptions( $options ) {
+ public function makeSelectOptions( $options ) {
$tailOpts = '';
$startOpts = '';
@@ -919,10 +1198,15 @@ class DatabaseMssql extends DatabaseBase {
$tailOpts .= $this->makeOrderBy( $options );
- if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) {
+ if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
$startOpts .= 'DISTINCT';
}
+ if ( isset( $noKeyOptions['FOR XML'] ) ) {
+ // used in group concat field emulation
+ $tailOpts .= " FOR XML PATH('')";
+ }
+
// we want this to be compatible with the output of parent::makeSelectOptions()
return array( $startOpts, '', $tailOpts, '' );
}
@@ -931,27 +1215,165 @@ class DatabaseMssql extends DatabaseBase {
* Get the type of the DBMS, as it appears in $wgDBtype.
* @return string
*/
- function getType() {
+ public function getType() {
return 'mssql';
}
- function buildConcat( $stringList ) {
+ /**
+ * @param array $stringList
+ * @return string
+ */
+ public function buildConcat( $stringList ) {
return implode( ' + ', $stringList );
}
+ /**
+ * Build a GROUP_CONCAT or equivalent statement for a query.
+ * MS SQL doesn't have GROUP_CONCAT so we emulate it with other stuff (and boy is it nasty)
+ *
+ * This is useful for combining a field for several rows into a single string.
+ * NULL values will not appear in the output, duplicated values will appear,
+ * and the resulting delimiter-separated values have no defined sort order.
+ * Code using the results may need to use the PHP unique() or sort() methods.
+ *
+ * @param string $delim Glue to bind the results together
+ * @param string|array $table Table name
+ * @param string $field Field name
+ * @param string|array $conds Conditions
+ * @param string|array $join_conds Join conditions
+ * @return string SQL text
+ * @since 1.23
+ */
+ public function buildGroupConcatField( $delim, $table, $field, $conds = '',
+ $join_conds = array()
+ ) {
+ $gcsq = 'gcsq_' . $this->mSubqueryId;
+ $this->mSubqueryId++;
+
+ $delimLen = strlen( $delim );
+ $fld = "{$field} + {$this->addQuotes( $delim )}";
+ $sql = "(SELECT LEFT({$field}, LEN({$field}) - {$delimLen}) FROM ("
+ . $this->selectSQLText( $table, $fld, $conds, null, array( 'FOR XML' ), $join_conds )
+ . ") {$gcsq} ({$field}))";
+
+ return $sql;
+ }
+
+ /**
+ * @return string
+ */
public function getSearchEngine() {
return "SearchMssql";
}
/**
- * Since MSSQL doesn't recognize the infinity keyword, set date manually.
- * @todo Remove magic date
+ * Returns an associative array for fields that are of type varbinary, binary, or image
+ * $table can be either a raw table name or passed through tableName() first
+ * @param string $table
+ * @return array
+ */
+ private function getBinaryColumns( $table ) {
+ $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+ $tableRaw = array_pop( $tableRawArr );
+
+ if ( $this->mBinaryColumnCache === null ) {
+ $this->populateColumnCaches();
+ }
+
+ return isset( $this->mBinaryColumnCache[$tableRaw] )
+ ? $this->mBinaryColumnCache[$tableRaw]
+ : array();
+ }
+
+ /**
+ * @param string $table
+ * @return array
+ */
+ private function getBitColumns( $table ) {
+ $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+ $tableRaw = array_pop( $tableRawArr );
+
+ if ( $this->mBitColumnCache === null ) {
+ $this->populateColumnCaches();
+ }
+
+ return isset( $this->mBitColumnCache[$tableRaw] )
+ ? $this->mBitColumnCache[$tableRaw]
+ : array();
+ }
+
+ private function populateColumnCaches() {
+ $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
+ array(
+ 'TABLE_CATALOG' => $this->mDBname,
+ 'TABLE_SCHEMA' => $this->mSchema,
+ 'DATA_TYPE' => array( 'varbinary', 'binary', 'image', 'bit' )
+ ) );
+
+ $this->mBinaryColumnCache = array();
+ $this->mBitColumnCache = array();
+ foreach ( $res as $row ) {
+ if ( $row->DATA_TYPE == 'bit' ) {
+ $this->mBitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
+ } else {
+ $this->mBinaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
+ }
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param string $format
+ * @return string
+ */
+ function tableName( $name, $format = 'quoted' ) {
+ # Replace reserved words with better ones
+ switch ( $name ) {
+ case 'user':
+ return $this->realTableName( 'mwuser', $format );
+ default:
+ return $this->realTableName( $name, $format );
+ }
+ }
+
+ /**
+ * call this instead of tableName() in the updater when renaming tables
+ * @param string $name
+ * @param string $format One of quoted, raw, or split
* @return string
*/
- public function getInfinity() {
- return '3000-01-31 00:00:00.000';
+ function realTableName( $name, $format = 'quoted' ) {
+ $table = parent::tableName( $name, $format );
+ if ( $format == 'split' ) {
+ // Used internally, we want the schema split off from the table name and returned
+ // as a list with 3 elements (database, schema, table)
+ $table = explode( '.', $table );
+ while ( count( $table ) < 3 ) {
+ array_unshift( $table, false );
+ }
+ }
+ return $table;
}
+ /**
+ * Called in the installer and updater.
+ * Probably doesn't need to be called anywhere else in the codebase.
+ * @param bool|null $value
+ * @return bool|null
+ */
+ public function prepareStatements( $value = null ) {
+ return wfSetVar( $this->mPrepareStatements, $value );
+ }
+
+ /**
+ * Called in the installer and updater.
+ * Probably doesn't need to be called anywhere else in the codebase.
+ * @param bool|null $value
+ * @return bool|null
+ */
+ public function scrollableCursor( $value = null ) {
+ return wfSetVar( $this->mScrollableCursor, $value );
+ }
} // end DatabaseMssql class
/**
@@ -960,10 +1382,11 @@ class DatabaseMssql extends DatabaseBase {
* @ingroup Database
*/
class MssqlField implements Field {
- private $name, $tablename, $default, $max_length, $nullable, $type;
+ private $name, $tableName, $default, $max_length, $nullable, $type;
+
function __construct( $info ) {
$this->name = $info['COLUMN_NAME'];
- $this->tablename = $info['TABLE_NAME'];
+ $this->tableName = $info['TABLE_NAME'];
$this->default = $info['COLUMN_DEFAULT'];
$this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
$this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
@@ -995,208 +1418,105 @@ class MssqlField implements Field {
}
}
-/**
- * The MSSQL PHP driver doesn't support sqlsrv_num_rows, so we recall all rows into an array and maintain our
- * own cursor index into that array...This is similar to the way the Oracle driver handles this same issue
- *
- * @ingroup Database
- */
-class MssqlResult {
-
- public function __construct( $queryresult = false ) {
- $this->mCursor = 0;
- $this->mRows = array();
- $this->mNumFields = sqlsrv_num_fields( $queryresult );
- $this->mFieldMeta = sqlsrv_field_metadata( $queryresult );
+class MssqlBlob extends Blob {
+ public function __construct( $data ) {
+ if ( $data instanceof MssqlBlob ) {
+ return $data;
+ } elseif ( $data instanceof Blob ) {
+ $this->mData = $data->fetch();
+ } elseif ( is_array( $data ) && is_object( $data ) ) {
+ $this->mData = serialize( $data );
+ } else {
+ $this->mData = $data;
+ }
+ }
- $rows = sqlsrv_fetch_array( $queryresult, SQLSRV_FETCH_ASSOC );
+ /**
+ * Returns an unquoted hex representation of a binary string
+ * for insertion into varbinary-type fields
+ * @return string
+ */
+ public function fetch() {
+ if ( $this->mData === null ) {
+ return 'null';
+ }
- foreach ( $rows as $row ) {
- if ( $row !== null ) {
- foreach ( $row as $k => $v ) {
- if ( is_object( $v ) && method_exists( $v, 'format' ) ) {// DateTime Object
- $row[$k] = $v->format( "Y-m-d\TH:i:s\Z" );
- }
- }
- $this->mRows[] = $row;// read results into memory, cursors are not supported
- }
+ $ret = '0x';
+ $dataLength = strlen( $this->mData );
+ for ( $i = 0; $i < $dataLength; $i++ ) {
+ $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
}
- $this->mRowCount = count( $this->mRows );
- sqlsrv_free_stmt( $queryresult );
+
+ return $ret;
}
+}
- private function array_to_obj( $array, &$obj ) {
- foreach ( $array as $key => $value ) {
- if ( is_array( $value ) ) {
- $obj->$key = new stdClass();
- $this->array_to_obj( $value, $obj->$key );
- } else {
- if ( !empty( $key ) ) {
- $obj->$key = $value;
- }
- }
+class MssqlResultWrapper extends ResultWrapper {
+ private $mSeekTo = null;
+
+ /**
+ * @return stdClass|bool
+ */
+ public function fetchObject() {
+ $res = $this->result;
+
+ if ( $this->mSeekTo !== null ) {
+ $result = sqlsrv_fetch_object( $res, 'stdClass', array(),
+ SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+ $this->mSeekTo = null;
+ } else {
+ $result = sqlsrv_fetch_object( $res );
}
- return $obj;
- }
- public function fetch( $mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass' ) {
- if ( $this->mCursor >= $this->mRowCount || $this->mRowCount == 0 ) {
+ // MediaWiki expects us to return boolean false when there are no more rows instead of null
+ if ( $result === null ) {
return false;
}
- $arrNum = array();
- if ( $mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH ) {
- foreach ( $this->mRows[$this->mCursor] as $value ) {
- $arrNum[] = $value;
- }
- }
- switch ( $mode ) {
- case SQLSRV_FETCH_ASSOC:
- $ret = $this->mRows[$this->mCursor];
- break;
- case SQLSRV_FETCH_NUMERIC:
- $ret = $arrNum;
- break;
- case 'OBJECT':
- $o = new $object_class;
- $ret = $this->array_to_obj( $this->mRows[$this->mCursor], $o );
- break;
- case SQLSRV_FETCH_BOTH:
- default:
- $ret = $this->mRows[$this->mCursor] + $arrNum;
- break;
- }
- $this->mCursor++;
- return $ret;
+ return $result;
}
- public function get( $pos, $fld ) {
- return $this->mRows[$pos][$fld];
- }
+ /**
+ * @return array|bool
+ */
+ public function fetchRow() {
+ $res = $this->result;
- public function numrows() {
- return $this->mRowCount;
- }
+ if ( $this->mSeekTo !== null ) {
+ $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
+ SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+ $this->mSeekTo = null;
+ } else {
+ $result = sqlsrv_fetch_array( $res );
+ }
- public function seek( $iRow ) {
- $this->mCursor = min( $iRow, $this->mRowCount );
- }
+ // MediaWiki expects us to return boolean false when there are no more rows instead of null
+ if ( $result === null ) {
+ return false;
+ }
- public function numfields() {
- return $this->mNumFields;
+ return $result;
}
- public function fieldname( $nr ) {
- $arrKeys = array_keys( $this->mRows[0] );
- return $arrKeys[$nr];
- }
+ /**
+ * @param int $row
+ * @return bool
+ */
+ public function seek( $row ) {
+ $res = $this->result;
- public function fieldtype( $nr ) {
- $i = 0;
- $intType = -1;
- foreach ( $this->mFieldMeta as $meta ) {
- if ( $nr == $i ) {
- $intType = $meta['Type'];
- break;
- }
- $i++;
- }
- // http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table
- switch ( $intType ) {
- case SQLSRV_SQLTYPE_BIGINT:
- $strType = 'bigint';
- break;
- case SQLSRV_SQLTYPE_BINARY:
- $strType = 'binary';
- break;
- case SQLSRV_SQLTYPE_BIT:
- $strType = 'bit';
- break;
- case SQLSRV_SQLTYPE_CHAR:
- $strType = 'char';
- break;
- case SQLSRV_SQLTYPE_DATETIME:
- $strType = 'datetime';
- break;
- case SQLSRV_SQLTYPE_DECIMAL: // ($precision, $scale)
- $strType = 'decimal';
- break;
- case SQLSRV_SQLTYPE_FLOAT:
- $strType = 'float';
- break;
- case SQLSRV_SQLTYPE_IMAGE:
- $strType = 'image';
- break;
- case SQLSRV_SQLTYPE_INT:
- $strType = 'int';
- break;
- case SQLSRV_SQLTYPE_MONEY:
- $strType = 'money';
- break;
- case SQLSRV_SQLTYPE_NCHAR: // ($charCount):
- $strType = 'nchar';
- break;
- case SQLSRV_SQLTYPE_NUMERIC: // ($precision, $scale):
- $strType = 'numeric';
- break;
- case SQLSRV_SQLTYPE_NVARCHAR: // ($charCount)
- $strType = 'nvarchar';
- break;
- // case SQLSRV_SQLTYPE_NVARCHAR('max'):
- // $strType = 'nvarchar(MAX)';
- // break;
- case SQLSRV_SQLTYPE_NTEXT:
- $strType = 'ntext';
- break;
- case SQLSRV_SQLTYPE_REAL:
- $strType = 'real';
- break;
- case SQLSRV_SQLTYPE_SMALLDATETIME:
- $strType = 'smalldatetime';
- break;
- case SQLSRV_SQLTYPE_SMALLINT:
- $strType = 'smallint';
- break;
- case SQLSRV_SQLTYPE_SMALLMONEY:
- $strType = 'smallmoney';
- break;
- case SQLSRV_SQLTYPE_TEXT:
- $strType = 'text';
- break;
- case SQLSRV_SQLTYPE_TIMESTAMP:
- $strType = 'timestamp';
- break;
- case SQLSRV_SQLTYPE_TINYINT:
- $strType = 'tinyint';
- break;
- case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER:
- $strType = 'uniqueidentifier';
- break;
- case SQLSRV_SQLTYPE_UDT:
- $strType = 'UDT';
- break;
- case SQLSRV_SQLTYPE_VARBINARY: // ($byteCount)
- $strType = 'varbinary';
- break;
- // case SQLSRV_SQLTYPE_VARBINARY('max'):
- // $strType = 'varbinary(MAX)';
- // break;
- case SQLSRV_SQLTYPE_VARCHAR: // ($charCount)
- $strType = 'varchar';
- break;
- // case SQLSRV_SQLTYPE_VARCHAR('max'):
- // $strType = 'varchar(MAX)';
- // break;
- case SQLSRV_SQLTYPE_XML:
- $strType = 'xml';
- break;
- default:
- $strType = $intType;
+ // check bounds
+ $numRows = $this->db->numRows( $res );
+ $row = intval( $row );
+
+ if ( $numRows === 0 ) {
+ return false;
+ } elseif ( $row < 0 || $row > $numRows - 1 ) {
+ return false;
}
- return $strType;
- }
- public function free() {
- unset( $this->mRows );
+ // Unlike MySQL, the seek actually happens on the next access
+ $this->mSeekTo = $row;
+ return true;
}
}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index 956bb694..dc4a67df 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -28,10 +28,9 @@
* @see Database
*/
class DatabaseMysql extends DatabaseMysqlBase {
-
/**
- * @param $sql string
- * @return resource
+ * @param string $sql
+ * @return resource False on error
*/
protected function doQuery( $sql ) {
if ( $this->bufferResults() ) {
@@ -39,14 +38,23 @@ class DatabaseMysql extends DatabaseMysqlBase {
} else {
$ret = mysql_unbuffered_query( $sql, $this->mConn );
}
+
return $ret;
}
+ /**
+ * @param string $realServer
+ * @return bool|resource MySQL Database connection or false on failure to connect
+ * @throws DBConnectionError
+ */
protected function mysqlConnect( $realServer ) {
# Fail now
# Otherwise we get a suppressed fatal error, which is very hard to track down
if ( !extension_loaded( 'mysql' ) ) {
- throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
+ throw new DBConnectionError(
+ $this,
+ "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n"
+ );
}
$connFlags = 0;
@@ -81,6 +89,18 @@ class DatabaseMysql extends DatabaseMysqlBase {
}
/**
+ * @param string $charset
+ * @return bool
+ */
+ protected function mysqlSetCharset( $charset ) {
+ if ( function_exists( 'mysql_set_charset' ) ) {
+ return mysql_set_charset( $charset, $this->mConn );
+ } else {
+ return $this->query( 'SET NAMES ' . $charset, __METHOD__ );
+ }
+ }
+
+ /**
* @return bool
*/
protected function closeConnection() {
@@ -113,11 +133,12 @@ class DatabaseMysql extends DatabaseMysqlBase {
}
/**
- * @param $db
+ * @param string $db
* @return bool
*/
function selectDB( $db ) {
$this->mDBname = $db;
+
return mysql_select_db( $db, $this->mConn );
}
@@ -156,6 +177,10 @@ class DatabaseMysql extends DatabaseMysqlBase {
return mysql_field_name( $res, $n );
}
+ protected function mysqlFieldType( $res, $n ) {
+ return mysql_field_type( $res, $n );
+ }
+
protected function mysqlDataSeek( $res, $row ) {
return mysql_data_seek( $res, $row );
}
diff --git a/includes/db/DatabaseMysqlBase.php b/includes/db/DatabaseMysqlBase.php
index 8f12b92d..ba0f39ff 100644
--- a/includes/db/DatabaseMysqlBase.php
+++ b/includes/db/DatabaseMysqlBase.php
@@ -33,6 +33,11 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/** @var MysqlMasterPos */
protected $lastKnownSlavePos;
+ /** @var null|int */
+ protected $mFakeSlaveLag = null;
+
+ protected $mFakeMaster = false;
+
/**
* @return string
*/
@@ -41,15 +46,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @param $server string
- * @param $user string
- * @param $password string
- * @param $dbName string
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws Exception|DBConnectionError
* @return bool
- * @throws DBConnectionError
*/
function open( $server, $user, $password, $dbName ) {
- global $wgAllDBsAreLocalhost, $wgDBmysql5, $wgSQLMode;
+ global $wgAllDBsAreLocalhost, $wgSQLMode;
wfProfileIn( __METHOD__ );
# Debugging hack -- fake cluster
@@ -76,6 +81,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
} catch ( Exception $ex ) {
wfProfileOut( "dbconnect-$server" );
wfProfileOut( __METHOD__ );
+ $this->restoreErrorHandler();
throw $ex;
}
$error = $this->restoreErrorHandler();
@@ -87,13 +93,14 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
if ( !$error ) {
$error = $this->lastError();
}
- wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
+ wfLogDBError( "Error connecting to {$this->mServer}: $error" );
wfDebug( "DB connection error\n" .
"Server: $server, User: $user, Password: " .
substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
wfProfileOut( __METHOD__ );
- return $this->reportConnectionError( $error );
+
+ $this->reportConnectionError( $error );
}
if ( $dbName != '' ) {
@@ -101,44 +108,74 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$success = $this->selectDB( $dbName );
wfRestoreWarnings();
if ( !$success ) {
- wfLogDBError( "Error selecting database $dbName on server {$this->mServer}\n" );
+ wfLogDBError( "Error selecting database $dbName on server {$this->mServer}" );
wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
"from client host " . wfHostname() . "\n" );
wfProfileOut( __METHOD__ );
- return $this->reportConnectionError( "Error selecting database $dbName" );
+
+ $this->reportConnectionError( "Error selecting database $dbName" );
}
}
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- if ( $wgDBmysql5 ) {
- $this->query( 'SET NAMES utf8', __METHOD__ );
- } else {
- $this->query( 'SET NAMES binary', __METHOD__ );
+ // Tell the server what we're communicating with
+ if ( !$this->connectInitCharset() ) {
+ $this->reportConnectionError( "Error setting character set" );
}
+
// Set SQL mode, default is turning them all off, can be overridden or skipped with null
if ( is_string( $wgSQLMode ) ) {
$mode = $this->addQuotes( $wgSQLMode );
- $this->query( "SET sql_mode = $mode", __METHOD__ );
+ // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
+ $success = $this->doQuery( "SET sql_mode = $mode", __METHOD__ );
+ if ( !$success ) {
+ wfLogDBError( "Error setting sql_mode to $mode on server {$this->mServer}" );
+ wfProfileOut( __METHOD__ );
+ $this->reportConnectionError( "Error setting sql_mode to $mode" );
+ }
}
$this->mOpened = true;
wfProfileOut( __METHOD__ );
+
return true;
}
/**
+ * Set the character set information right after connection
+ * @return bool
+ */
+ protected function connectInitCharset() {
+ global $wgDBmysql5;
+
+ if ( $wgDBmysql5 ) {
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ return $this->mysqlSetCharset( 'utf8' );
+ } else {
+ return $this->mysqlSetCharset( 'binary' );
+ }
+ }
+
+ /**
* Open a connection to a MySQL server
*
- * @param $realServer string
+ * @param string $realServer
* @return mixed Raw connection
* @throws DBConnectionError
*/
abstract protected function mysqlConnect( $realServer );
/**
- * @param $res ResultWrapper
+ * Set the character set of the MySQL link
+ *
+ * @param string $charset
+ * @return bool
+ */
+ abstract protected function mysqlSetCharset( $charset );
+
+ /**
+ * @param ResultWrapper|resource $res
* @throws DBUnexpectedError
*/
function freeResult( $res ) {
@@ -156,14 +193,14 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* Free result memory
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return bool
*/
abstract protected function mysqlFreeResult( $res );
/**
- * @param $res ResultWrapper
- * @return object|bool
+ * @param ResultWrapper|resource $res
+ * @return stdClass|bool
* @throws DBUnexpectedError
*/
function fetchObject( $res ) {
@@ -180,21 +217,25 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
// these are the only errors mysql_fetch_object can cause.
// See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
if ( $errno == 2000 || $errno == 2013 ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
+ );
}
+
return $row;
}
/**
* Fetch a result row as an object
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return stdClass
*/
abstract protected function mysqlFetchObject( $res );
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper|resource $res
* @return array|bool
* @throws DBUnexpectedError
*/
@@ -212,22 +253,26 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
// these are the only errors mysql_fetch_array can cause.
// See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
if ( $errno == 2000 || $errno == 2013 ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
+ );
}
+
return $row;
}
/**
* Fetch a result row as an associative and numeric array
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return array
*/
abstract protected function mysqlFetchArray( $res );
/**
* @throws DBUnexpectedError
- * @param $res ResultWrapper
+ * @param ResultWrapper|resource $res
* @return int
*/
function numRows( $res ) {
@@ -237,6 +282,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
wfSuppressWarnings();
$n = $this->mysqlNumRows( $res );
wfRestoreWarnings();
+
// Unfortunately, mysql_num_rows does not reset the last errno.
// We are not checking for any errors here, since
// these are no errors mysql_num_rows can cause.
@@ -248,68 +294,94 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* Get number of rows in result
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return int
*/
abstract protected function mysqlNumRows( $res );
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper|resource $res
* @return int
*/
function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return $this->mysqlNumFields( $res );
}
/**
* Get number of fields in result
*
- * @param $res Raw result
+ * @param resource $res Raw result
* @return int
*/
abstract protected function mysqlNumFields( $res );
/**
- * @param $res ResultWrapper
- * @param $n string
+ * @param ResultWrapper|resource $res
+ * @param int $n
* @return string
*/
function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return $this->mysqlFieldName( $res, $n );
}
/**
* Get the name of the specified field in a result
*
- * @param $res Raw result
- * @param $n int
+ * @param ResultWrapper|resource $res
+ * @param int $n
* @return string
*/
abstract protected function mysqlFieldName( $res, $n );
/**
- * @param $res ResultWrapper
- * @param $row
+ * mysql_field_type() wrapper
+ * @param ResultWrapper|resource $res
+ * @param int $n
+ * @return string
+ */
+ public function fieldType( $res, $n ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+
+ return $this->mysqlFieldType( $res, $n );
+ }
+
+ /**
+ * Get the type of the specified field in a result
+ *
+ * @param ResultWrapper|resource $res
+ * @param int $n
+ * @return string
+ */
+ abstract protected function mysqlFieldType( $res, $n );
+
+ /**
+ * @param ResultWrapper|resource $res
+ * @param int $row
* @return bool
*/
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return $this->mysqlDataSeek( $res, $row );
}
/**
* Move internal result pointer
*
- * @param $res Raw result
- * @param $row int
+ * @param ResultWrapper|resource $res
+ * @param int $row
* @return bool
*/
abstract protected function mysqlDataSeek( $res, $row );
@@ -332,22 +404,23 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
if ( $error ) {
$error .= ' (' . $this->mServer . ')';
}
+
return $error;
}
/**
* Returns the text of the error message from previous MySQL operation
*
- * @param $conn Raw connection
+ * @param resource $conn Raw connection
* @return string
*/
abstract protected function mysqlError( $conn = null );
/**
- * @param $table string
- * @param $uniqueIndexes
- * @param $rows array
- * @param $fname string
+ * @param string $table
+ * @param array $uniqueIndexes
+ * @param array $rows
+ * @param string $fname
* @return ResultWrapper
*/
function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
@@ -359,14 +432,16 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* Returns estimated count, based on EXPLAIN output
* Takes same arguments as Database::select()
*
- * @param $table string|array
- * @param $vars string|array
- * @param $conds string|array
- * @param $fname string
- * @param $options string|array
- * @return int
+ * @param string|array $table
+ * @param string|array $vars
+ * @param string|array $conds
+ * @param string $fname
+ * @param string|array $options
+ * @return bool|int
*/
- public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() ) {
+ public function estimateRowCount( $table, $vars = '*', $conds = '',
+ $fname = __METHOD__, $options = array()
+ ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
if ( $res === false ) {
@@ -380,12 +455,13 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
foreach ( $res as $plan ) {
$rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
}
+
return $rows;
}
/**
- * @param $table string
- * @param $field string
+ * @param string $table
+ * @param string $field
* @return bool|MySQLField
*/
function fieldInfo( $table, $field ) {
@@ -401,14 +477,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
return new MySQLField( $meta );
}
}
+
return false;
}
/**
* Get column information from a result
*
- * @param $res Raw result
- * @param $n int
+ * @param resource $res Raw result
+ * @param int $n
* @return stdClass
*/
abstract protected function mysqlFetchField( $res, $n );
@@ -417,9 +494,9 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* Get information about an index into an object
* Returns false if the index does not exist
*
- * @param $table string
- * @param $index string
- * @param $fname string
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool|array|null False or null on failure
*/
function indexInfo( $table, $index, $fname = __METHOD__ ) {
@@ -443,12 +520,12 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$result[] = $row;
}
}
+
return empty( $result ) ? false : $result;
}
/**
- * @param $s string
- *
+ * @param string $s
* @return string
*/
function strencode( $s ) {
@@ -458,24 +535,24 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->ping();
$sQuoted = $this->mysqlRealEscapeString( $s );
}
+
return $sQuoted;
}
/**
* MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
*
- * @param $s string
- *
+ * @param string $s
* @return string
*/
public function addIdentifierQuotes( $s ) {
// Characters in the range \u0001-\uFFFF are valid in a quoted identifier
// Remove NUL bytes and escape backticks by doubling
- return '`' . str_replace( array( "\0", '`' ), array( '', '``' ), $s ) . '`';
+ return '`' . str_replace( array( "\0", '`' ), array( '', '``' ), $s ) . '`';
}
/**
- * @param $name string
+ * @param string $name
* @return bool
*/
public function isQuotedIdentifier( $name ) {
@@ -495,6 +572,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->mOpened = false;
$this->mConn = false;
$this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+
return true;
}
@@ -506,6 +584,24 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
abstract protected function mysqlPing();
/**
+ * Set lag time in seconds for a fake slave
+ *
+ * @param int $lag
+ */
+ public function setFakeSlaveLag( $lag ) {
+ $this->mFakeSlaveLag = $lag;
+ }
+
+ /**
+ * Make this connection a fake master
+ *
+ * @param bool $enabled
+ */
+ public function setFakeMaster( $enabled = true ) {
+ $this->mFakeMaster = $enabled;
+ }
+
+ /**
* Returns slave lag.
*
* This will do a SHOW SLAVE STATUS
@@ -515,6 +611,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
function getLag() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
+
return $this->mFakeSlaveLag;
}
@@ -541,52 +638,14 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @deprecated in 1.19, use getLagFromSlaveStatus
- *
- * @return bool|int
- */
- function getLagFromProcesslist() {
- wfDeprecated( __METHOD__, '1.19' );
- $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
- if ( !$res ) {
- return false;
- }
- # Find slave SQL thread
- foreach ( $res as $row ) {
- /* This should work for most situations - when default db
- * for thread is not specified, it had no events executed,
- * and therefore it doesn't know yet how lagged it is.
- *
- * Relay log I/O thread does not select databases.
- */
- if ( $row->User == 'system user' &&
- $row->State != 'Waiting for master to send event' &&
- $row->State != 'Connecting to master' &&
- $row->State != 'Queueing master event to the relay log' &&
- $row->State != 'Waiting for master update' &&
- $row->State != 'Requesting binlog dump' &&
- $row->State != 'Waiting to reconnect after a failed master event read' &&
- $row->State != 'Reconnecting after a failed master event read' &&
- $row->State != 'Registering slave on master'
- ) {
- # This is it, return the time (except -ve)
- if ( $row->Time > 0x7fffffff ) {
- return false;
- } else {
- return $row->Time;
- }
- }
- }
- return false;
- }
-
- /**
* Wait for the slave to catch up to a given master position.
- * @TODO: return values for this and base class are rubbish
+ * @todo Return values for this and base class are rubbish
*
- * @param $pos DBMasterPos object
- * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
- * @return bool|string
+ * @param DBMasterPos|MySQLMasterPos $pos
+ * @param int $timeout The maximum number of seconds to wait for synchronisation
+ * @return int Zero if the slave was past that position already,
+ * greater than zero if we waited for some period of time, less than
+ * zero if we timed out.
*/
function masterPosWait( DBMasterPos $pos, $timeout ) {
if ( $this->lastKnownSlavePos && $this->lastKnownSlavePos->hasReached( $pos ) ) {
@@ -598,9 +657,25 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->commit( __METHOD__, 'flush' );
if ( !is_null( $this->mFakeSlaveLag ) ) {
- $status = parent::masterPosWait( $pos, $timeout );
- wfProfileOut( __METHOD__ );
- return $status;
+ $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
+
+ if ( $wait > $timeout * 1e6 ) {
+ wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
+ wfProfileOut( __METHOD__ );
+
+ return -1;
+ } elseif ( $wait > 0 ) {
+ wfDebug( "Fake slave waiting $wait us\n" );
+ usleep( $wait );
+ wfProfileOut( __METHOD__ );
+
+ return 1;
+ } else {
+ wfDebug( "Fake slave up to date ($wait us)\n" );
+ wfProfileOut( __METHOD__ );
+
+ return 0;
+ }
}
# Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
@@ -618,6 +693,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
wfProfileOut( __METHOD__ );
+
return $status;
}
@@ -628,14 +704,20 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
function getSlavePos() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
- return parent::getSlavePos();
+ $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
+ wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
+
+ return $pos;
}
$res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
$row = $this->fetchObject( $res );
if ( $row ) {
- $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
+ $pos = isset( $row->Exec_master_log_pos )
+ ? $row->Exec_master_log_pos
+ : $row->Exec_Master_Log_Pos;
+
return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
} else {
return false;
@@ -649,7 +731,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
function getMasterPos() {
if ( $this->mFakeMaster ) {
- return parent::getMasterPos();
+ return new MySQLMasterPos( 'fake', microtime( true ) );
}
$res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
@@ -663,7 +745,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @param $index
+ * @param string $index
* @return string
*/
function useIndexClause( $index ) {
@@ -681,18 +763,22 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* @return string
*/
public function getSoftwareLink() {
+ // MariaDB includes its name in its version string (sent when the connection is opened),
+ // and this is how MariaDB's version of the mysql command-line client identifies MariaDB
+ // servers (see the mariadb_connection() function in libmysql/libmysql.c).
$version = $this->getServerVersion();
- if ( strpos( $version, 'MariaDB' ) !== false ) {
+ if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
return '[{{int:version-db-mariadb-url}} MariaDB]';
- } elseif ( strpos( $version, 'percona' ) !== false ) {
- return '[{{int:version-db-percona-url}} Percona Server]';
- } else {
- return '[{{int:version-db-mysql-url}} MySQL]';
}
+
+ // Percona Server's version suffix is not very distinctive, and @@version_comment
+ // doesn't give the necessary info for source builds, so assume the server is MySQL.
+ // (Even Percona's version of mysql doesn't try to make the distinction.)
+ return '[{{int:version-db-mysql-url}} MySQL]';
}
/**
- * @param $options array
+ * @param array $options
*/
public function setSessionOptions( array $options ) {
if ( isset( $options['connTimeout'] ) ) {
@@ -702,34 +788,41 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
}
+ /**
+ * @param string $sql
+ * @param string $newLine
+ * @return bool
+ */
public function streamStatementEnd( &$sql, &$newLine ) {
if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
$this->delimiter = $m[1];
$newLine = '';
}
+
return parent::streamStatementEnd( $sql, $newLine );
}
/**
* Check to see if a named lock is available. This is non-blocking.
*
- * @param string $lockName name of lock to poll
- * @param string $method name of method calling us
- * @return Boolean
+ * @param string $lockName Name of lock to poll
+ * @param string $method Name of method calling us
+ * @return bool
* @since 1.20
*/
public function lockIsFree( $lockName, $method ) {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
$row = $this->fetchObject( $result );
+
return ( $row->lockstatus == 1 );
}
/**
- * @param $lockName string
- * @param $method string
- * @param $timeout int
+ * @param string $lockName
+ * @param string $method
+ * @param int $timeout
* @return bool
*/
public function lock( $lockName, $method, $timeout = 5 ) {
@@ -741,28 +834,31 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
return true;
} else {
wfDebug( __METHOD__ . " failed to acquire lock\n" );
+
return false;
}
}
/**
- * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
- * @param $lockName string
- * @param $method string
+ * FROM MYSQL DOCS:
+ * http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
+ * @param string $lockName
+ * @param string $method
* @return bool
*/
public function unlock( $lockName, $method ) {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
$row = $this->fetchObject( $result );
+
return ( $row->lockstatus == 1 );
}
/**
- * @param $read array
- * @param $write array
- * @param $method string
- * @param $lowPriority bool
+ * @param array $read
+ * @param array $write
+ * @param string $method
+ * @param bool $lowPriority
* @return bool
*/
public function lockTables( $read, $write, $method, $lowPriority = true ) {
@@ -770,8 +866,8 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
foreach ( $write as $table ) {
$tbl = $this->tableName( $table ) .
- ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
- ' WRITE';
+ ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
+ ' WRITE';
$items[] = $tbl;
}
foreach ( $read as $table ) {
@@ -779,15 +875,17 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
$sql = "LOCK TABLES " . implode( ',', $items );
$this->query( $sql, $method );
+
return true;
}
/**
- * @param $method string
+ * @param string $method
* @return bool
*/
public function unlockTables( $method ) {
$this->query( "UNLOCK TABLES", $method );
+
return true;
}
@@ -795,7 +893,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* Get search engine class. All subclasses of this
* need to implement this if they wish to use searching.
*
- * @return String
+ * @return string
*/
public function getSearchEngine() {
return 'SearchMySQL';
@@ -803,7 +901,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* @param bool $value
- * @return mixed
*/
public function setBigSelects( $value = true ) {
if ( $value === 'default' ) {
@@ -822,12 +919,12 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* DELETE where the condition is a join. MySql uses multi-table deletes.
- * @param $delTable string
- * @param $joinTable string
- * @param $delVar string
- * @param $joinVar string
- * @param $conds array|string
- * @param bool|string $fname bool
+ * @param string $delTable
+ * @param string $joinTable
+ * @param string $delVar
+ * @param string $joinVar
+ * @param array|string $conds
+ * @param bool|string $fname
* @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
@@ -853,16 +950,18 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
* @param array $uniqueIndexes
* @param array $set
* @param string $fname
- * @param array $options
* @return bool
*/
- public function upsert(
- $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ public function upsert( $table, array $rows, array $uniqueIndexes,
+ array $set, $fname = __METHOD__
) {
if ( !count( $rows ) ) {
return true; // nothing to do
}
- $rows = is_array( reset( $rows ) ) ? $rows : array( $rows );
+
+ if ( !is_array( reset( $rows ) ) ) {
+ $rows = array( $rows );
+ }
$table = $this->tableName( $table );
$columns = array_keys( $rows[0] );
@@ -885,6 +984,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
function getServerUptime() {
$vars = $this->getMysqlStatus( 'Uptime' );
+
return (int)$vars['Uptime'];
}
@@ -927,24 +1027,26 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @param $oldName
- * @param $newName
- * @param $temporary bool
- * @param $fname string
+ * @param string $oldName
+ * @param string $newName
+ * @param bool $temporary
+ * @param string $fname
+ * @return bool
*/
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
$tmp = $temporary ? 'TEMPORARY ' : '';
$newName = $this->addIdentifierQuotes( $newName );
$oldName = $this->addIdentifierQuotes( $oldName );
$query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
- $this->query( $query, $fname );
+
+ return $this->query( $query, $fname );
}
/**
* List all tables on the database
*
* @param string $prefix Only show tables with this prefix, e.g. mw_
- * @param string $fname calling function name
+ * @param string $fname Calling function name
* @return array
*/
function listTables( $prefix = null, $fname = __METHOD__ ) {
@@ -965,14 +1067,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
- * @param $tableName
- * @param $fName string
+ * @param string $tableName
+ * @param string $fName
* @return bool|ResultWrapper
*/
public function dropTable( $tableName, $fName = __METHOD__ ) {
if ( !$this->tableExists( $tableName, $fName ) ) {
return false;
}
+
return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
}
@@ -982,14 +1085,19 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
protected function getDefaultSchemaVars() {
$vars = parent::getDefaultSchemaVars();
$vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
- $vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $vars['wgDBTableOptions'] );
+ $vars['wgDBTableOptions'] = str_replace(
+ 'CHARSET=mysql4',
+ 'CHARSET=binary',
+ $vars['wgDBTableOptions']
+ );
+
return $vars;
}
/**
* Get status information from SHOW STATUS in an associative array
*
- * @param $which string
+ * @param string $which
* @return array
*/
function getMysqlStatus( $which = "%" ) {
@@ -1006,9 +1114,9 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
/**
* Lists VIEWs in the database
*
- * @param string $prefix Only show VIEWs with this prefix, eg.
+ * @param string $prefix Only show VIEWs with this prefix, eg.
* unit_test_, or $wgDBprefix. Default: null, would return all views.
- * @param string $fname Name of calling function
+ * @param string $fname Name of calling function
* @return array
* @since 1.22
*/
@@ -1022,12 +1130,12 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
// Query for the VIEWS
$result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
$this->allViews = array();
- while ( ($row = $this->fetchRow($result)) !== false ) {
+ while ( ( $row = $this->fetchRow( $result ) ) !== false ) {
array_push( $this->allViews, $row[$propertyName] );
}
}
- if ( is_null($prefix) || $prefix === '' ) {
+ if ( is_null( $prefix ) || $prefix === '' ) {
return $this->allViews;
}
@@ -1038,24 +1146,23 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
array_push( $filteredViews, $viewName );
}
}
+
return $filteredViews;
}
/**
* Differentiates between a TABLE and a VIEW.
*
- * @param $name string: Name of the TABLE/VIEW to test
+ * @param string $name Name of the TABLE/VIEW to test
+ * @param string $prefix
* @return bool
* @since 1.22
*/
public function isView( $name, $prefix = null ) {
return in_array( $name, $this->listViews( $prefix ) );
}
-
}
-
-
/**
* Utility class.
* @ingroup Database
@@ -1130,7 +1237,11 @@ class MySQLField implements Field {
}
class MySQLMasterPos implements DBMasterPos {
- var $file, $pos;
+ /** @var string */
+ public $file;
+
+ /** @var int Timestamp */
+ public $pos;
function __construct( $file, $pos ) {
$this->file = $file;
@@ -1143,19 +1254,21 @@ class MySQLMasterPos implements DBMasterPos {
}
/**
- * @return array|false (int, int)
+ * @return array|bool (int, int)
*/
protected function getCoordinates() {
$m = array();
if ( preg_match( '!\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
return array( (int)$m[1], (int)$m[2] );
}
+
return false;
}
function hasReached( MySQLMasterPos $pos ) {
$thisPos = $this->getCoordinates();
$thatPos = $pos->getCoordinates();
+
return ( $thisPos && $thatPos && $thisPos >= $thatPos );
}
}
diff --git a/includes/db/DatabaseMysqli.php b/includes/db/DatabaseMysqli.php
index 0ec54314..a03c9aaf 100644
--- a/includes/db/DatabaseMysqli.php
+++ b/includes/db/DatabaseMysqli.php
@@ -29,9 +29,8 @@
* @see Database
*/
class DatabaseMysqli extends DatabaseMysqlBase {
-
/**
- * @param $sql string
+ * @param string $sql
* @return resource
*/
protected function doQuery( $sql ) {
@@ -40,10 +39,17 @@ class DatabaseMysqli extends DatabaseMysqlBase {
} else {
$ret = $this->mConn->query( $sql, MYSQLI_USE_RESULT );
}
+
return $ret;
}
+ /**
+ * @param string $realServer
+ * @return bool|mysqli
+ * @throws DBConnectionError
+ */
protected function mysqlConnect( $realServer ) {
+ global $wgDBmysql5;
# Fail now
# Otherwise we get a suppressed fatal error, which is very hard to track down
if ( !function_exists( 'mysqli_init' ) ) {
@@ -52,14 +58,22 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
// Other than mysql_connect, mysqli_real_connect expects an explicit port
- // parameter. So we need to parse the port out of $realServer
+ // and socket parameters. So we need to parse the port and socket out of
+ // $realServer
$port = null;
+ $socket = null;
$hostAndPort = IP::splitHostAndPort( $realServer );
if ( $hostAndPort ) {
$realServer = $hostAndPort[0];
if ( $hostAndPort[1] ) {
$port = $hostAndPort[1];
}
+ } elseif ( substr_count( $realServer, ':' ) == 1 ) {
+ // If we have a colon and something that's not a port number
+ // inside the hostname, assume it's the socket location
+ $hostAndSocket = explode( ':', $realServer );
+ $realServer = $hostAndSocket[0];
+ $socket = $hostAndSocket[1];
}
$connFlags = 0;
@@ -74,22 +88,41 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
$mysqli = mysqli_init();
- $numAttempts = 2;
+ if ( $wgDBmysql5 ) {
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'utf8' );
+ } else {
+ $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'binary' );
+ }
+ $mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
- for ( $i = 0; $i < $numAttempts; $i++ ) {
- if ( $i > 1 ) {
- usleep( 1000 );
- }
- if ( $mysqli->real_connect( $realServer, $this->mUser,
- $this->mPassword, $this->mDBname, $port, null, $connFlags ) )
- {
- return $mysqli;
- }
+ if ( $mysqli->real_connect( $realServer, $this->mUser,
+ $this->mPassword, $this->mDBname, $port, $socket, $connFlags )
+ ) {
+ return $mysqli;
}
return false;
}
+ protected function connectInitCharset() {
+ // already done in mysqlConnect()
+ return true;
+ }
+
+ /**
+ * @param string $charset
+ * @return bool
+ */
+ protected function mysqlSetCharset( $charset ) {
+ if ( method_exists( $this->mConn, 'set_charset' ) ) {
+ return $this->mConn->set_charset( $charset );
+ } else {
+ return $this->query( 'SET NAMES ' . $charset, __METHOD__ );
+ }
+ }
+
/**
* @return bool
*/
@@ -123,11 +156,12 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
/**
- * @param $db
+ * @param string $db
* @return bool
*/
function selectDB( $db ) {
$this->mDBname = $db;
+
return $this->mConn->select_db( $db );
}
@@ -138,35 +172,63 @@ class DatabaseMysqli extends DatabaseMysqlBase {
return $this->mConn->server_info;
}
+ /**
+ * @param mysqli $res
+ * @return bool
+ */
protected function mysqlFreeResult( $res ) {
$res->free_result();
+
return true;
}
+ /**
+ * @param mysqli $res
+ * @return bool
+ */
protected function mysqlFetchObject( $res ) {
$object = $res->fetch_object();
if ( $object === null ) {
return false;
}
+
return $object;
}
+ /**
+ * @param mysqli $res
+ * @return bool
+ */
protected function mysqlFetchArray( $res ) {
$array = $res->fetch_array();
if ( $array === null ) {
return false;
}
+
return $array;
}
+ /**
+ * @param mysqli $res
+ * @return mixed
+ */
protected function mysqlNumRows( $res ) {
return $res->num_rows;
}
+ /**
+ * @param mysqli $res
+ * @return mixed
+ */
protected function mysqlNumFields( $res ) {
return $res->field_count;
}
+ /**
+ * @param mysqli $res
+ * @param int $n
+ * @return mixed
+ */
protected function mysqlFetchField( $res, $n ) {
$field = $res->fetch_field_direct( $n );
$field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
@@ -174,26 +236,58 @@ class DatabaseMysqli extends DatabaseMysqlBase {
$field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
$field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
$field->binary = $field->flags & MYSQLI_BINARY_FLAG;
+
return $field;
}
+ /**
+ * @param resource|ResultWrapper $res
+ * @param int $n
+ * @return mixed
+ */
protected function mysqlFieldName( $res, $n ) {
$field = $res->fetch_field_direct( $n );
+
return $field->name;
}
+ /**
+ * @param resource|ResultWrapper $res
+ * @param int $n
+ * @return mixed
+ */
+ protected function mysqlFieldType( $res, $n ) {
+ $field = $res->fetch_field_direct( $n );
+
+ return $field->type;
+ }
+
+ /**
+ * @param resource|ResultWrapper $res
+ * @param int $row
+ * @return mixed
+ */
protected function mysqlDataSeek( $res, $row ) {
return $res->data_seek( $row );
}
+ /**
+ * @param mysqli $conn Optional connection object
+ * @return string
+ */
protected function mysqlError( $conn = null ) {
- if ($conn === null) {
+ if ( $conn === null ) {
return mysqli_connect_error();
} else {
return $conn->error;
}
}
+ /**
+ * Escapes special characters in a string for use in an SQL statement
+ * @param string $s
+ * @return string
+ */
protected function mysqlRealEscapeString( $s ) {
return $this->mConn->real_escape_string( $s );
}
@@ -202,4 +296,18 @@ class DatabaseMysqli extends DatabaseMysqlBase {
return $this->mConn->ping();
}
+ /**
+ * Give an id for the connection
+ *
+ * mysql driver used resource id, but mysqli objects cannot be cast to string.
+ * @return string
+ */
+ public function __toString() {
+ if ( $this->mConn instanceof Mysqli ) {
+ return (string)$this->mConn->thread_id;
+ } else {
+ // mConn might be false or something.
+ return (string)$this->mConn;
+ }
+ }
}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 9009b328..f031f780 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -50,17 +50,19 @@ class ORAResult {
}
/**
- * @param $db DatabaseBase
- * @param $stmt
+ * @param DatabaseBase $db
+ * @param resource $stmt A valid OCI statement identifier
* @param bool $unique
*/
function __construct( &$db, $stmt, $unique = false ) {
$this->db =& $db;
- if ( ( $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, - 1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM ) ) === false ) {
+ $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM );
+ if ( $this->nrows === false ) {
$e = oci_error( $stmt );
$db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
$this->free();
+
return;
}
@@ -121,6 +123,7 @@ class ORAResult {
$ret[$lc] = $v;
$ret[$k] = $v;
}
+
return $ret;
}
}
@@ -183,25 +186,49 @@ class ORAField implements Field {
* @ingroup Database
*/
class DatabaseOracle extends DatabaseBase {
- var $mInsertId = null;
- var $mLastResult = null;
- var $lastResult = null;
- var $cursor = 0;
- var $mAffectedRows;
+ /** @var resource */
+ protected $mLastResult = null;
+
+ /** @var int The number of rows affected as an integer */
+ protected $mAffectedRows;
- var $ignore_DUP_VAL_ON_INDEX = false;
- var $sequenceData = null;
+ /** @var int */
+ private $mInsertId = null;
- var $defaultCharset = 'AL32UTF8';
+ /** @var bool */
+ private $ignoreDupValOnIndex = false;
- var $mFieldInfoCache = array();
+ /** @var bool|array */
+ private $sequenceData = null;
- function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $flags = 0, $tablePrefix = 'get from global' )
- {
+ /** @var string Character set for Oracle database */
+ private $defaultCharset = 'AL32UTF8';
+
+ /** @var array */
+ private $mFieldInfoCache = array();
+
+ function __construct( $p = null ) {
global $wgDBprefix;
- $tablePrefix = $tablePrefix == 'get from global' ? strtoupper( $wgDBprefix ) : strtoupper( $tablePrefix );
- parent::__construct( $server, $user, $password, $dbName, $flags, $tablePrefix );
+
+ if ( !is_array( $p ) ) { // legacy calling pattern
+ wfDeprecated( __METHOD__ . " method called without parameter array.", "1.22" );
+ $args = func_get_args();
+ $p = array(
+ 'host' => isset( $args[0] ) ? $args[0] : false,
+ 'user' => isset( $args[1] ) ? $args[1] : false,
+ 'password' => isset( $args[2] ) ? $args[2] : false,
+ 'dbname' => isset( $args[3] ) ? $args[3] : false,
+ 'flags' => isset( $args[4] ) ? $args[4] : 0,
+ 'tablePrefix' => isset( $args[5] ) ? $args[5] : 'get from global',
+ 'schema' => 'get from global',
+ 'foreign' => isset( $args[6] ) ? $args[6] : false
+ );
+ }
+ if ( $p['tablePrefix'] == 'get from global' ) {
+ $p['tablePrefix'] = $wgDBprefix;
+ }
+ $p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
+ parent::__construct( $p );
wfRunHooks( 'DatabaseOraclePostInit', array( $this ) );
}
@@ -220,21 +247,27 @@ class DatabaseOracle extends DatabaseBase {
function cascadingDeletes() {
return true;
}
+
function cleanupTriggers() {
return true;
}
+
function strictIPs() {
return true;
}
+
function realTimestamps() {
return true;
}
+
function implicitGroupby() {
return false;
}
+
function implicitOrderby() {
return false;
}
+
function searchableIPs() {
return true;
}
@@ -251,7 +284,11 @@ class DatabaseOracle extends DatabaseBase {
function open( $server, $user, $password, $dbName ) {
global $wgDBOracleDRCP;
if ( !function_exists( 'oci_connect' ) ) {
- throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
+ throw new DBConnectionError(
+ $this,
+ "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n " .
+ "(Note: if you recently installed PHP, you may need to restart your webserver\n " .
+ "and database)\n" );
}
$this->close();
@@ -274,7 +311,7 @@ class DatabaseOracle extends DatabaseBase {
}
if ( !strlen( $user ) ) { # e.g. the class is being loaded
- return;
+ return null;
}
if ( $wgDBOracleDRCP ) {
@@ -285,11 +322,29 @@ class DatabaseOracle extends DatabaseBase {
wfSuppressWarnings();
if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = oci_pconnect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_pconnect(
+ $this->mUser,
+ $this->mPassword,
+ $this->mServer,
+ $this->defaultCharset,
+ $session_mode
+ );
} elseif ( $this->mFlags & DBO_DEFAULT ) {
- $this->mConn = oci_new_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_new_connect(
+ $this->mUser,
+ $this->mPassword,
+ $this->mServer,
+ $this->defaultCharset,
+ $session_mode
+ );
} else {
- $this->mConn = oci_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_connect(
+ $this->mUser,
+ $this->mPassword,
+ $this->mServer,
+ $this->defaultCharset,
+ $session_mode
+ );
}
wfRestoreWarnings();
@@ -308,6 +363,7 @@ class DatabaseOracle extends DatabaseBase {
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
+
return $this->mConn;
}
@@ -343,20 +399,28 @@ class DatabaseOracle extends DatabaseBase {
// you have to select data from plan table after explain
$explain_id = MWTimestamp::getLocalInstance()->format( 'dmYHis' );
- $sql = preg_replace( '/^EXPLAIN /', 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR', $sql, 1, $explain_count );
+ $sql = preg_replace(
+ '/^EXPLAIN /',
+ 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR',
+ $sql,
+ 1,
+ $explain_count
+ );
wfSuppressWarnings();
if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
$e = oci_error( $this->mConn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
if ( !oci_execute( $stmt, $this->execFlags() ) ) {
$e = oci_error( $stmt );
- if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
+ if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
}
@@ -364,11 +428,13 @@ class DatabaseOracle extends DatabaseBase {
wfRestoreWarnings();
if ( $explain_count > 0 ) {
- return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table WHERE statement_id = \'' . $explain_id . '\'' );
+ return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table ' .
+ 'WHERE statement_id = \'' . $explain_id . '\'' );
} elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
return new ORAResult( $this, $stmt, $union_unique );
} else {
$this->mAffectedRows = oci_num_rows( $stmt );
+
return true;
}
}
@@ -377,6 +443,10 @@ class DatabaseOracle extends DatabaseBase {
return $this->query( $sql, $fname, true );
}
+ /**
+ * Frees resources associated with the LOB descriptor
+ * @param ResultWrapper|resource $res
+ */
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -385,6 +455,10 @@ class DatabaseOracle extends DatabaseBase {
$res->free();
}
+ /**
+ * @param ResultWrapper|stdClass $res
+ * @return mixed
+ */
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -423,12 +497,16 @@ class DatabaseOracle extends DatabaseBase {
/**
* This must be called after nextSequenceVal
- * @return null
+ * @return null|int
*/
function insertId() {
return $this->mInsertId;
}
+ /**
+ * @param mixed $res
+ * @param int $row
+ */
function dataSeek( $res, $row ) {
if ( $res instanceof ORAResult ) {
$res->seek( $row );
@@ -443,6 +521,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$e = oci_error( $this->mConn );
}
+
return $e['message'];
}
@@ -452,6 +531,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$e = oci_error( $this->mConn );
}
+
return $e['code'];
}
@@ -462,6 +542,9 @@ class DatabaseOracle extends DatabaseBase {
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool
*/
function indexInfo( $table, $index, $fname = __METHOD__ ) {
@@ -482,7 +565,7 @@ class DatabaseOracle extends DatabaseBase {
}
if ( in_array( 'IGNORE', $options ) ) {
- $this->ignore_DUP_VAL_ON_INDEX = true;
+ $this->ignoreDupValOnIndex = true;
}
if ( !is_array( reset( $a ) ) ) {
@@ -495,7 +578,7 @@ class DatabaseOracle extends DatabaseBase {
$retVal = true;
if ( in_array( 'IGNORE', $options ) ) {
- $this->ignore_DUP_VAL_ON_INDEX = false;
+ $this->ignoreDupValOnIndex = false;
}
return $retVal;
@@ -509,6 +592,7 @@ class DatabaseOracle extends DatabaseBase {
if ( is_numeric( $col ) ) {
$bind = $val;
$val = null;
+
return $bind;
} elseif ( $includeCol ) {
$bind = "$col = ";
@@ -535,6 +619,13 @@ class DatabaseOracle extends DatabaseBase {
return $bind;
}
+ /**
+ * @param string $table
+ * @param array $row
+ * @param string $fname
+ * @return bool
+ * @throws DBUnexpectedError
+ */
private function insertOneRow( $table, $row, $fname ) {
global $wgContLang;
@@ -563,6 +654,7 @@ class DatabaseOracle extends DatabaseBase {
if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
$e = oci_error( $this->mConn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
foreach ( $row as $col => &$val ) {
@@ -585,9 +677,11 @@ class DatabaseOracle extends DatabaseBase {
if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
$e = oci_error( $stmt );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
} else {
+ /** @var OCI_Lob[] $lob */
if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
$e = oci_error( $stmt );
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
@@ -599,10 +693,10 @@ class DatabaseOracle extends DatabaseBase {
if ( $col_type == 'BLOB' ) {
$lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_BLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_BLOB );
} else {
$lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
}
}
}
@@ -611,8 +705,9 @@ class DatabaseOracle extends DatabaseBase {
if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
$e = oci_error( $stmt );
- if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
+ if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
} else {
$this->mAffectedRows = oci_num_rows( $stmt );
@@ -633,12 +728,12 @@ class DatabaseOracle extends DatabaseBase {
oci_commit( $this->mConn );
}
- oci_free_statement( $stmt );
+ return oci_free_statement( $stmt );
}
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() )
- {
+ $insertOptions = array(), $selectOptions = array()
+ ) {
$destTable = $this->tableName( $destTable );
if ( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
@@ -651,8 +746,8 @@ class DatabaseOracle extends DatabaseBase {
}
if ( ( $sequenceData = $this->getSequenceData( $destTable ) ) !== false &&
- !isset( $varMap[$sequenceData['column']] ) )
- {
+ !isset( $varMap[$sequenceData['column']] )
+ ) {
$varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
}
@@ -671,13 +766,13 @@ class DatabaseOracle extends DatabaseBase {
$sql .= " $tailOpts";
if ( in_array( 'IGNORE', $insertOptions ) ) {
- $this->ignore_DUP_VAL_ON_INDEX = true;
+ $this->ignoreDupValOnIndex = true;
}
$retval = $this->query( $sql, $fname );
if ( in_array( 'IGNORE', $insertOptions ) ) {
- $this->ignore_DUP_VAL_ON_INDEX = false;
+ $this->ignoreDupValOnIndex = false;
}
return $retval;
@@ -699,7 +794,9 @@ class DatabaseOracle extends DatabaseBase {
// add sequence column to each list of columns, when not set
foreach ( $rows as &$row ) {
if ( !isset( $row[$sequenceData['column']] ) ) {
- $row[$sequenceData['column']] = $this->addIdentifierQuotes('GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')');
+ $row[$sequenceData['column']] =
+ $this->addIdentifierQuotes( 'GET_SEQUENCE_VALUE(\'' .
+ $sequenceData['sequence'] . '\')' );
}
}
}
@@ -727,33 +824,45 @@ class DatabaseOracle extends DatabaseBase {
function tableNameInternal( $name ) {
$name = $this->tableName( $name );
+
return preg_replace( '/.*\.(.*)/', '$1', $name );
}
+
/**
* Return the next in a sequence, save the value for retrieval via insertId()
- * @return null
+ *
+ * @param string $seqName
+ * @return null|int
*/
function nextSequenceValue( $seqName ) {
$res = $this->query( "SELECT $seqName.nextval FROM dual" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
+
return $this->mInsertId;
}
/**
* Return sequence_name if table has a sequence
+ *
+ * @param string $table
* @return 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->mTablePrefix}MWUSER', '{$this->mTablePrefix}USER', atc.table_name) || '_' ||
- atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
- AND asq.sequence_owner = upper('{$this->mDBname}')
- AND atc.owner = upper('{$this->mDBname}')" );
+ lower(atc.table_name),
+ lower(atc.column_name)
+ FROM all_sequences asq, all_tab_columns atc
+ WHERE decode(
+ atc.table_name,
+ '{$this->mTablePrefix}MWUSER',
+ '{$this->mTablePrefix}USER',
+ atc.table_name
+ ) || '_' ||
+ atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
+ AND asq.sequence_owner = upper('{$this->mDBname}')
+ AND atc.owner = upper('{$this->mDBname}')" );
while ( ( $row = $result->fetchRow() ) !== false ) {
$this->sequenceData[$row[1]] = array(
@@ -763,12 +872,20 @@ class DatabaseOracle extends DatabaseBase {
}
}
$table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
+
return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
}
- # Returns the size of a text field, or -1 for "unlimited"
+ /**
+ * Returns the size of a text field, or -1 for "unlimited"
+ *
+ * @param string $table
+ * @param string $field
+ * @return mixed
+ */
function textFieldSize( $table, $field ) {
$fieldInfoData = $this->fieldInfo( $table, $field );
+
return $fieldInfoData->maxLength();
}
@@ -776,6 +893,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $offset === false ) {
$offset = 0;
}
+
return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
}
@@ -787,19 +905,24 @@ class DatabaseOracle extends DatabaseBase {
if ( $b instanceof Blob ) {
$b = $b->fetch();
}
+
return $b;
}
function unionQueries( $sqls, $all ) {
$glue = ' UNION ALL ';
- return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')';
+
+ return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) .
+ 'FROM (' . implode( $glue, $sqls ) . ')';
}
function wasDeadlock() {
return $this->lastErrno() == 'OCI-00060';
}
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
+ function duplicateTableStructure( $oldName, $newName, $temporary = false,
+ $fname = __METHOD__
+ ) {
$temporary = $temporary ? 'TRUE' : 'FALSE';
$newName = strtoupper( $newName );
@@ -809,7 +932,8 @@ class DatabaseOracle extends DatabaseBase {
$oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
$newPrefix = strtoupper( $this->mTablePrefix );
- return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', '$oldPrefix', '$newPrefix', $temporary ); END;" );
+ return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
+ "'$oldPrefix', '$newPrefix', $temporary ); END;" );
}
function listTables( $prefix = null, $fname = __METHOD__ ) {
@@ -819,7 +943,8 @@ class DatabaseOracle extends DatabaseBase {
}
$owner = strtoupper( $this->mDBname );
- $result = $this->doQuery( "SELECT table_name FROM all_tables WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
+ $result = $this->doQuery( "SELECT table_name FROM all_tables " .
+ "WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
// dirty code ... i know
$endArray = array();
@@ -851,6 +976,10 @@ class DatabaseOracle extends DatabaseBase {
/**
* Return aggregated value function call
+ *
+ * @param array $valuedata
+ * @param string $valuename
+ * @return mixed
*/
public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
@@ -871,7 +1000,7 @@ class DatabaseOracle extends DatabaseBase {
}
/**
- * @return string wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
public function getSoftwareLink() {
return '[{{int:version-db-oracle-url}} Oracle]';
@@ -882,15 +1011,22 @@ class DatabaseOracle extends DatabaseBase {
*/
function getServerVersion() {
//better version number, fallback on driver
- $rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' );
+ $rset = $this->doQuery(
+ 'SELECT version FROM product_component_version ' .
+ 'WHERE UPPER(product) LIKE \'ORACLE DATABASE%\''
+ );
if ( !( $row = $rset->fetchRow() ) ) {
return oci_server_version( $this->mConn );
}
+
return $row['version'];
}
/**
* Query whether a given index exists
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool
*/
function indexExists( $table, $index, $fname = __METHOD__ ) {
@@ -898,27 +1034,30 @@ class DatabaseOracle extends DatabaseBase {
$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
$index = strtoupper( $index );
$owner = strtoupper( $this->mDBname );
- $SQL = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
- $res = $this->doQuery( $SQL );
+ $sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
+ $res = $this->doQuery( $sql );
if ( $res ) {
$count = $res->numRows();
$res->free();
} else {
$count = 0;
}
+
return $count != 0;
}
/**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
+ * @param string $table
+ * @param string $fname
* @return bool
*/
function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
$owner = $this->addQuotes( strtoupper( $this->mDBname ) );
- $SQL = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
- $res = $this->doQuery( $SQL );
+ $sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
+ $res = $this->doQuery( $sql );
if ( $res && $res->numRows() > 0 ) {
$exists = true;
} else {
@@ -926,6 +1065,7 @@ class DatabaseOracle extends DatabaseBase {
}
$res->free();
+
return $exists;
}
@@ -935,8 +1075,8 @@ class DatabaseOracle extends DatabaseBase {
* For internal calls. Use fieldInfo for normal usage.
* Returns false if the field doesn't exist
*
- * @param $table Array
- * @param $field String
+ * @param array|string $table
+ * @param string $field
* @return ORAField|ORAResult
*/
private function fieldInfoMulti( $table, $field ) {
@@ -960,10 +1100,15 @@ class DatabaseOracle extends DatabaseBase {
$tableWhere = '= \'' . $table . '\'';
}
- $fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name ' . $tableWhere . ' and column_name = \'' . $field . '\'' );
+ $fieldInfoStmt = oci_parse(
+ $this->mConn,
+ 'SELECT * FROM wiki_field_info_full WHERE table_name ' .
+ $tableWhere . ' and column_name = \'' . $field . '\''
+ );
if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
$e = oci_error( $fieldInfoStmt );
$this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
+
return false;
}
$res = new ORAResult( $this, $fieldInfoStmt );
@@ -982,19 +1127,21 @@ class DatabaseOracle extends DatabaseBase {
$this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
}
$res->free();
+
return $fieldInfoTemp;
}
/**
* @throws DBUnexpectedError
- * @param $table
- * @param $field
+ * @param string $table
+ * @param string $field
* @return ORAField
*/
function fieldInfo( $table, $field ) {
if ( is_array( $table ) ) {
throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
}
+
return $this->fieldInfoMulti( $table, $field );
}
@@ -1022,7 +1169,16 @@ class DatabaseOracle extends DatabaseBase {
}
}
- /* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
+ /**
+ * defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
+ *
+ * @param resource $fp
+ * @param bool|string $lineCallback
+ * @param bool|callable $resultCallback
+ * @param string $fname
+ * @param bool|callable $inputCallback
+ * @return bool|string
+ */
function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
$fname = __METHOD__, $inputCallback = false ) {
$cmd = '';
@@ -1031,7 +1187,7 @@ class DatabaseOracle extends DatabaseBase {
$replacements = array();
- while ( ! feof( $fp ) ) {
+ while ( !feof( $fp ) ) {
if ( $lineCallback ) {
call_user_func( $lineCallback );
}
@@ -1041,7 +1197,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $sl < 0 ) {
continue;
}
- if ( '-' == $line { 0 } && '-' == $line { 1 } ) {
+ if ( '-' == $line[0] && '-' == $line[1] ) {
continue;
}
@@ -1055,7 +1211,7 @@ class DatabaseOracle extends DatabaseBase {
$dollarquote = true;
}
} elseif ( !$dollarquote ) {
- if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) {
+ if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
$done = true;
$line = substr( $line, 0, $sl );
}
@@ -1088,6 +1244,7 @@ class DatabaseOracle extends DatabaseBase {
if ( false === $res ) {
$err = $this->lastError();
+
return "Query \"{$cmd}\" failed with error code \"$err\".\n";
}
}
@@ -1096,6 +1253,7 @@ class DatabaseOracle extends DatabaseBase {
$done = false;
}
}
+
return true;
}
@@ -1114,8 +1272,10 @@ class DatabaseOracle extends DatabaseBase {
if ( $e['code'] != '1435' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
}
+
return false;
}
+
return true;
}
@@ -1128,6 +1288,7 @@ class DatabaseOracle extends DatabaseBase {
if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
$s = $wgContLang->checkTitleEncoding( $s );
}
+
return "'" . $this->strencode( $s ) . "'";
}
@@ -1135,6 +1296,7 @@ class DatabaseOracle extends DatabaseBase {
if ( !$this->getFlag( DBO_DDLMODE ) ) {
$s = '/*Q*/' . $s;
}
+
return $s;
}
@@ -1173,13 +1335,17 @@ class DatabaseOracle extends DatabaseBase {
$conds2[$col] = $val;
}
}
+
return $conds2;
}
- function selectRow( $table, $vars, $conds, $fname = __METHOD__, $options = array(), $join_conds = array() ) {
+ function selectRow( $table, $vars, $conds, $fname = __METHOD__,
+ $options = array(), $join_conds = array()
+ ) {
if ( is_array( $conds ) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
+
return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
}
@@ -1187,10 +1353,8 @@ class DatabaseOracle extends DatabaseBase {
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query
*
- * @private
- *
- * @param array $options an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
+ * @param array $options An associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
* @return array
*/
function makeSelectOptions( $options ) {
@@ -1216,7 +1380,7 @@ class DatabaseOracle extends DatabaseBase {
$startOpts .= 'DISTINCT';
}
- if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
+ if ( isset( $options['USE INDEX'] ) && !is_array( $options['USE INDEX'] ) ) {
$useIndex = $this->useIndexClause( $options['USE INDEX'] );
} else {
$useIndex = '';
@@ -1233,21 +1397,41 @@ class DatabaseOracle extends DatabaseBase {
// all deletions on these tables have transactions so final failure rollbacks these updates
$table = $this->tableName( $table );
if ( $table == $this->tableName( 'user' ) ) {
- $this->update( 'archive', array( 'ar_user' => 0 ), array( 'ar_user' => $conds['user_id'] ), $fname );
- $this->update( 'ipblocks', array( 'ipb_user' => 0 ), array( 'ipb_user' => $conds['user_id'] ), $fname );
- $this->update( 'image', array( 'img_user' => 0 ), array( 'img_user' => $conds['user_id'] ), $fname );
- $this->update( 'oldimage', array( 'oi_user' => 0 ), array( 'oi_user' => $conds['user_id'] ), $fname );
- $this->update( 'filearchive', array( 'fa_deleted_user' => 0 ), array( 'fa_deleted_user' => $conds['user_id'] ), $fname );
- $this->update( 'filearchive', array( 'fa_user' => 0 ), array( 'fa_user' => $conds['user_id'] ), $fname );
- $this->update( 'uploadstash', array( 'us_user' => 0 ), array( 'us_user' => $conds['user_id'] ), $fname );
- $this->update( 'recentchanges', array( 'rc_user' => 0 ), array( 'rc_user' => $conds['user_id'] ), $fname );
- $this->update( 'logging', array( 'log_user' => 0 ), array( 'log_user' => $conds['user_id'] ), $fname );
+ $this->update( 'archive', array( 'ar_user' => 0 ),
+ array( 'ar_user' => $conds['user_id'] ), $fname );
+ $this->update( 'ipblocks', array( 'ipb_user' => 0 ),
+ array( 'ipb_user' => $conds['user_id'] ), $fname );
+ $this->update( 'image', array( 'img_user' => 0 ),
+ array( 'img_user' => $conds['user_id'] ), $fname );
+ $this->update( 'oldimage', array( 'oi_user' => 0 ),
+ array( 'oi_user' => $conds['user_id'] ), $fname );
+ $this->update( 'filearchive', array( 'fa_deleted_user' => 0 ),
+ array( 'fa_deleted_user' => $conds['user_id'] ), $fname );
+ $this->update( 'filearchive', array( 'fa_user' => 0 ),
+ array( 'fa_user' => $conds['user_id'] ), $fname );
+ $this->update( 'uploadstash', array( 'us_user' => 0 ),
+ array( 'us_user' => $conds['user_id'] ), $fname );
+ $this->update( 'recentchanges', array( 'rc_user' => 0 ),
+ array( 'rc_user' => $conds['user_id'] ), $fname );
+ $this->update( 'logging', array( 'log_user' => 0 ),
+ array( 'log_user' => $conds['user_id'] ), $fname );
} elseif ( $table == $this->tableName( 'image' ) ) {
- $this->update( 'oldimage', array( 'oi_name' => 0 ), array( 'oi_name' => $conds['img_name'] ), $fname );
+ $this->update( 'oldimage', array( 'oi_name' => 0 ),
+ array( 'oi_name' => $conds['img_name'] ), $fname );
}
+
return parent::delete( $table, $conds, $fname );
}
+ /**
+ * @param string $table
+ * @param array $values
+ * @param array $conds
+ * @param string $fname
+ * @param array $options
+ * @return bool
+ * @throws DBUnexpectedError
+ */
function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
global $wgContLang;
@@ -1275,6 +1459,7 @@ class DatabaseOracle extends DatabaseBase {
if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
$e = oci_error( $this->mConn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
foreach ( $values as $col => &$val ) {
@@ -1296,9 +1481,11 @@ class DatabaseOracle extends DatabaseBase {
if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
$e = oci_error( $stmt );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
}
} else {
+ /** @var OCI_Lob[] $lob */
if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
$e = oci_error( $stmt );
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
@@ -1310,10 +1497,10 @@ class DatabaseOracle extends DatabaseBase {
if ( $col_type == 'BLOB' ) {
$lob[$col]->writeTemporary( $val );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, SQLT_BLOB );
} else {
$lob[$col]->writeTemporary( $val );
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
}
}
}
@@ -1322,8 +1509,9 @@ class DatabaseOracle extends DatabaseBase {
if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
$e = oci_error( $stmt );
- if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
+ if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+
return false;
} else {
$this->mAffectedRows = oci_num_rows( $stmt );
@@ -1344,7 +1532,7 @@ class DatabaseOracle extends DatabaseBase {
oci_commit( $this->mConn );
}
- oci_free_statement( $stmt );
+ return oci_free_statement( $stmt );
}
function bitNot( $field ) {
@@ -1360,9 +1548,6 @@ class DatabaseOracle extends DatabaseBase {
return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
}
- function setFakeMaster( $enabled = true ) {
- }
-
function getDBname() {
return $this->mDBname;
}
@@ -1371,6 +1556,14 @@ class DatabaseOracle extends DatabaseBase {
return $this->mServer;
}
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = array()
+ ) {
+ $fld = "LISTAGG($field," . $this->addQuotes( $delim ) . ") WITHIN GROUP (ORDER BY $field)";
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
public function getSearchEngine() {
return 'SearchOracle';
}
@@ -1378,5 +1571,4 @@ class DatabaseOracle extends DatabaseBase {
public function getInfinity() {
return '31-12-2030 12:00:00.000000';
}
-
-} // end DatabaseOracle class
+}
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index 0bd966ba..ce14d7a9 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -26,9 +26,9 @@ class PostgresField implements Field {
$has_default, $default;
/**
- * @param $db DatabaseBase
- * @param $table
- * @param $field
+ * @param DatabaseBase $db
+ * @param string $table
+ * @param string $field
* @return null|PostgresField
*/
static function fromText( $db, $table, $field ) {
@@ -79,6 +79,7 @@ SQL;
$n->conname = $row->conname;
$n->has_default = ( $row->atthasdef === 't' );
$n->default = $row->adsrc;
+
return $n;
}
@@ -113,8 +114,10 @@ SQL;
function conname() {
return $this->conname;
}
+
/**
* @since 1.19
+ * @return bool|mixed
*/
function defaultValue() {
if ( $this->has_default ) {
@@ -123,7 +126,6 @@ SQL;
return false;
}
}
-
}
/**
@@ -134,8 +136,7 @@ SQL;
* @ingroup Database
*/
class PostgresTransactionState {
-
- static $WATCHED = array(
+ private static $WATCHED = array(
array(
"desc" => "%s: Connection state changed from %s -> %s\n",
"states" => array(
@@ -155,6 +156,12 @@ class PostgresTransactionState {
)
);
+ /** @var array */
+ private $mNewState;
+
+ /** @var array */
+ private $mCurrentState;
+
public function __construct( $conn ) {
$this->mConn = $conn;
$this->update();
@@ -181,7 +188,6 @@ class PostgresTransactionState {
}
$old = next( $this->mCurrentState );
$new = next( $this->mNewState );
-
}
}
}
@@ -211,21 +217,23 @@ class PostgresTransactionState {
* @since 1.19
*/
class SavepointPostgres {
- /**
- * Establish a savepoint within a transaction
- */
+ /** @var DatabaseBase Establish a savepoint within a transaction */
protected $dbw;
protected $id;
protected $didbegin;
+ /**
+ * @param DatabaseBase $dbw
+ * @param int $id
+ */
public function __construct( $dbw, $id ) {
$this->dbw = $dbw;
$this->id = $id;
$this->didbegin = false;
/* If we are not in a transaction, we need to be for savepoint trickery */
if ( !$dbw->trxLevel() ) {
- $dbw->begin( "FOR SAVEPOINT" );
- $this->didbegin = true;
+ $dbw->begin( "FOR SAVEPOINT" );
+ $this->didbegin = true;
}
}
@@ -247,10 +255,10 @@ class SavepointPostgres {
global $wgDebugDBTransactions;
if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
if ( $wgDebugDBTransactions ) {
- wfDebug( sprintf ( $msg_ok, $this->id ) );
+ wfDebug( sprintf( $msg_ok, $this->id ) );
}
} else {
- wfDebug( sprintf ( $msg_failed, $this->id ) );
+ wfDebug( sprintf( $msg_failed, $this->id ) );
}
}
@@ -284,10 +292,26 @@ class SavepointPostgres {
* @ingroup Database
*/
class DatabasePostgres extends DatabaseBase {
- var $mInsertId = null;
- var $mLastResult = null;
- var $numeric_version = null;
- var $mAffectedRows = null;
+ /** @var resource */
+ protected $mLastResult = null;
+
+ /** @var int The number of rows affected as an integer */
+ protected $mAffectedRows = null;
+
+ /** @var int */
+ private $mInsertId = null;
+
+ /** @var float|string */
+ private $numericVersion = null;
+
+ /** @var string Connect string to open a PostgreSQL connection */
+ private $connectString;
+
+ /** @var PostgresTransactionState */
+ private $mTransactionState;
+
+ /** @var string */
+ private $mCoreSchema;
function getType() {
return 'postgres';
@@ -296,32 +320,42 @@ class DatabasePostgres extends DatabaseBase {
function cascadingDeletes() {
return true;
}
+
function cleanupTriggers() {
return true;
}
+
function strictIPs() {
return true;
}
+
function realTimestamps() {
return true;
}
+
function implicitGroupby() {
return false;
}
+
function implicitOrderby() {
return false;
}
+
function searchableIPs() {
return true;
}
+
function functionalIndexes() {
return true;
}
function hasConstraint( $name ) {
- $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" .
- pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
- $res = $this->doQuery( $SQL );
+ $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
+ "WHERE c.connamespace = n.oid AND conname = '" .
+ pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" .
+ pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
+ $res = $this->doQuery( $sql );
+
return $this->numRows( $res );
}
@@ -331,19 +365,24 @@ class DatabasePostgres extends DatabaseBase {
* @param string $user
* @param string $password
* @param string $dbName
- * @throws DBConnectionError
+ * @throws DBConnectionError|Exception
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
# Test for Postgres support, to avoid suppressed fatal error
if ( !function_exists( 'pg_connect' ) ) {
- throw new DBConnectionError( $this, "Postgres functions missing, have you compiled PHP with the --with-pgsql option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
+ throw new DBConnectionError(
+ $this,
+ "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
+ "option? (Note: if you recently installed PHP, you may need to restart your\n" .
+ "webserver and database)\n"
+ );
}
global $wgDBport;
if ( !strlen( $user ) ) { # e.g. the class is being loaded
- return;
+ return null;
}
$this->mServer = $server;
@@ -370,12 +409,20 @@ class DatabasePostgres extends DatabaseBase {
$this->connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
$this->close();
$this->installErrorHandler();
- $this->mConn = pg_connect( $this->connectString );
+
+ try {
+ $this->mConn = pg_connect( $this->connectString );
+ } catch ( Exception $ex ) {
+ $this->restoreErrorHandler();
+ throw $ex;
+ }
+
$phpError = $this->restoreErrorHandler();
if ( !$this->mConn ) {
wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
+ wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " .
+ substr( $password, 0, 3 ) . "...\n" );
wfDebug( $this->lastError() . "\n" );
throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
}
@@ -406,7 +453,8 @@ class DatabasePostgres extends DatabaseBase {
/**
* Postgres doesn't support selectDB in the same way MySQL does. So if the
* DB name doesn't match the open connection, open a new one
- * @return
+ * @param string $db
+ * @return bool
*/
function selectDB( $db ) {
if ( $this->mDBname !== $db ) {
@@ -421,6 +469,7 @@ class DatabasePostgres extends DatabaseBase {
foreach ( $vars as $name => $value ) {
$s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
}
+
return $s;
}
@@ -447,38 +496,44 @@ class DatabasePostgres extends DatabaseBase {
if ( pg_result_error( $this->mLastResult ) ) {
return false;
}
+
return $this->mLastResult;
}
protected function dumpError() {
- $diags = array( PGSQL_DIAG_SEVERITY,
- PGSQL_DIAG_SQLSTATE,
- PGSQL_DIAG_MESSAGE_PRIMARY,
- PGSQL_DIAG_MESSAGE_DETAIL,
- PGSQL_DIAG_MESSAGE_HINT,
- PGSQL_DIAG_STATEMENT_POSITION,
- PGSQL_DIAG_INTERNAL_POSITION,
- PGSQL_DIAG_INTERNAL_QUERY,
- PGSQL_DIAG_CONTEXT,
- PGSQL_DIAG_SOURCE_FILE,
- PGSQL_DIAG_SOURCE_LINE,
- PGSQL_DIAG_SOURCE_FUNCTION );
+ $diags = array(
+ PGSQL_DIAG_SEVERITY,
+ PGSQL_DIAG_SQLSTATE,
+ PGSQL_DIAG_MESSAGE_PRIMARY,
+ PGSQL_DIAG_MESSAGE_DETAIL,
+ PGSQL_DIAG_MESSAGE_HINT,
+ PGSQL_DIAG_STATEMENT_POSITION,
+ PGSQL_DIAG_INTERNAL_POSITION,
+ PGSQL_DIAG_INTERNAL_QUERY,
+ PGSQL_DIAG_CONTEXT,
+ PGSQL_DIAG_SOURCE_FILE,
+ PGSQL_DIAG_SOURCE_LINE,
+ PGSQL_DIAG_SOURCE_FUNCTION
+ );
foreach ( $diags as $d ) {
- wfDebug( sprintf( "PgSQL ERROR(%d): %s\n", $d, pg_result_error_field( $this->mLastResult, $d ) ) );
+ wfDebug( sprintf( "PgSQL ERROR(%d): %s\n",
+ $d, pg_result_error_field( $this->mLastResult, $d ) ) );
}
}
function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- /* Transaction stays in the ERROR state until rolledback */
if ( $tempIgnore ) {
/* Check for constraint violation */
if ( $errno === '23505' ) {
parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
+
return;
}
}
- /* Don't ignore serious errors */
- $this->rollback( __METHOD__ );
+ /* Transaction stays in the ERROR state until rolledback */
+ if ( $this->mTrxLevel ) {
+ $this->rollback( __METHOD__ );
+ };
parent::reportQueryError( $error, $errno, $sql, $fname, false );
}
@@ -486,6 +541,10 @@ class DatabasePostgres extends DatabaseBase {
return $this->query( $sql, $fname, true );
}
+ /**
+ * @param stdClass|ResultWrapper $res
+ * @throws DBUnexpectedError
+ */
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -498,6 +557,11 @@ class DatabasePostgres extends DatabaseBase {
}
}
+ /**
+ * @param ResultWrapper|stdClass $res
+ * @return stdClass
+ * @throws DBUnexpectedError
+ */
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -510,8 +574,12 @@ class DatabasePostgres extends DatabaseBase {
# @todo hashar: not sure if the following test really trigger if the object
# fetching failed.
if ( pg_last_error( $this->mConn ) ) {
- throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ );
}
+
return $row;
}
@@ -523,8 +591,12 @@ class DatabasePostgres extends DatabaseBase {
$row = pg_fetch_array( $res );
wfRestoreWarnings();
if ( pg_last_error( $this->mConn ) ) {
- throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ );
}
+
return $row;
}
@@ -536,8 +608,12 @@ class DatabasePostgres extends DatabaseBase {
$n = pg_num_rows( $res );
wfRestoreWarnings();
if ( pg_last_error( $this->mConn ) ) {
- throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
+ throw new DBUnexpectedError(
+ $this,
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ );
}
+
return $n;
}
@@ -545,6 +621,7 @@ class DatabasePostgres extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return pg_num_fields( $res );
}
@@ -552,6 +629,7 @@ class DatabasePostgres extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return pg_field_name( $res, $n );
}
@@ -559,16 +637,22 @@ class DatabasePostgres extends DatabaseBase {
* Return the result of the last call to nextSequenceValue();
* This must be called after nextSequenceValue().
*
- * @return integer|null
+ * @return int|null
*/
function insertId() {
return $this->mInsertId;
}
+ /**
+ * @param mixed $res
+ * @param int $row
+ * @return bool
+ */
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return pg_result_seek( $res, $row );
}
@@ -583,6 +667,7 @@ class DatabasePostgres extends DatabaseBase {
return 'No database connection';
}
}
+
function lastErrno() {
if ( $this->mLastResult ) {
return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
@@ -599,6 +684,7 @@ class DatabasePostgres extends DatabaseBase {
if ( empty( $this->mLastResult ) ) {
return 0;
}
+
return pg_affected_rows( $this->mLastResult );
}
@@ -608,9 +694,17 @@ class DatabasePostgres extends DatabaseBase {
* This is not necessarily an accurate estimate, so use sparingly
* Returns -1 if count cannot be found
* Takes same arguments as Database::select()
+ *
+ * @param string $table
+ * @param string $vars
+ * @param string $conds
+ * @param string $fname
+ * @param array $options
* @return int
*/
- function estimateRowCount( $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() ) {
+ function estimateRowCount( $table, $vars = '*', $conds = '',
+ $fname = __METHOD__, $options = array()
+ ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
@@ -621,12 +715,17 @@ class DatabasePostgres extends DatabaseBase {
$rows = $count[1];
}
}
+
return $rows;
}
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
+ *
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool|null
*/
function indexInfo( $table, $index, $fname = __METHOD__ ) {
@@ -640,6 +739,7 @@ class DatabasePostgres extends DatabaseBase {
return $row;
}
}
+
return false;
}
@@ -647,7 +747,9 @@ class DatabasePostgres extends DatabaseBase {
* Returns is of attributes used in index
*
* @since 1.19
- * @return Array
+ * @param string $index
+ * @param bool|string $schema
+ * @return array
*/
function indexAttributes( $index, $schema = false ) {
if ( $schema === false ) {
@@ -702,6 +804,7 @@ __INDEXATTR__;
} else {
return null;
}
+
return $a;
}
@@ -714,10 +817,8 @@ __INDEXATTR__;
if ( !$res ) {
return null;
}
- foreach ( $res as $row ) {
- return true;
- }
- return false;
+
+ return $res->numRows() > 0;
}
/**
@@ -727,10 +828,15 @@ __INDEXATTR__;
* In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
* can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to do
* so causes a DB error. This wrapper checks which tables can be locked and adjusts it accordingly.
+ *
+ * MySQL uses "ORDER BY NULL" as an optimization hint, but that syntax is illegal in PostgreSQL.
+ * @see DatabaseBase::selectSQLText
*/
- function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() ) {
+ function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = array(), $join_conds = array()
+ ) {
if ( is_array( $options ) ) {
- $forUpdateKey = array_search( 'FOR UPDATE', $options );
+ $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
if ( $forUpdateKey !== false && $join_conds ) {
unset( $options[$forUpdateKey] );
@@ -740,6 +846,10 @@ __INDEXATTR__;
}
}
}
+
+ if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
+ unset( $options['ORDER BY'] );
+ }
}
return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -751,11 +861,10 @@ __INDEXATTR__;
* $args may be a single associative array, or an array of these with numeric keys,
* for multi-row insert (Postgres version 8.2 and above only).
*
- * @param $table String: Name of the table to insert to.
- * @param $args Array: Items to insert into the table.
- * @param $fname String: Name of the function, for profiling
- * @param string $options or Array. Valid options: IGNORE
- *
+ * @param string $table Name of the table to insert to.
+ * @param array $args Items to insert into the table.
+ * @param string $fname Name of the function, for profiling
+ * @param array|string $options String or array. Valid options: IGNORE
* @return bool Success of insert operation. IGNORE always returns true.
*/
function insert( $table, $args, $fname = __METHOD__, $options = array() ) {
@@ -764,7 +873,7 @@ __INDEXATTR__;
}
$table = $this->tableName( $table );
- if ( !isset( $this->numeric_version ) ) {
+ if ( !isset( $this->numericVersion ) ) {
$this->getServerVersion();
}
@@ -793,7 +902,7 @@ __INDEXATTR__;
$sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
if ( $multi ) {
- if ( $this->numeric_version >= 8.2 && !$savepoint ) {
+ if ( $this->numericVersion >= 8.2 && !$savepoint ) {
$first = true;
foreach ( $args as $row ) {
if ( $first ) {
@@ -853,7 +962,7 @@ __INDEXATTR__;
}
}
if ( $savepoint ) {
- $olde = error_reporting( $olde );
+ error_reporting( $olde );
$savepoint->commit();
// Set the affected row count for the whole operation
@@ -869,15 +978,23 @@ __INDEXATTR__;
/**
* INSERT SELECT wrapper
* $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather then field names, but strings should be quoted with Database::addQuotes()
+ * Source items may be literals rather then field names, but strings should
+ * be quoted with Database::addQuotes()
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
* @todo FIXME: Implement this a little better (seperate select/insert)?
+ *
+ * @param string $destTable
+ * @param array|string $srcTable
+ * @param array $varMap
+ * @param array $conds
+ * @param string $fname
+ * @param array $insertOptions
+ * @param array $selectOptions
* @return bool
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
- $insertOptions = array(), $selectOptions = array() )
- {
+ $insertOptions = array(), $selectOptions = array() ) {
$destTable = $this->tableName( $destTable );
if ( !is_array( $insertOptions ) ) {
@@ -907,8 +1024,8 @@ __INDEXATTR__;
}
$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
- " SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex";
+ " SELECT $startOpts " . implode( ',', $varMap ) .
+ " FROM $srcTable $useIndex";
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
@@ -925,7 +1042,7 @@ __INDEXATTR__;
$savepoint->release();
$numrowsinserted++;
}
- $olde = error_reporting( $olde );
+ error_reporting( $olde );
$savepoint->commit();
// Set the affected row count for the whole operation
@@ -957,25 +1074,31 @@ __INDEXATTR__;
/**
* Return the next in a sequence, save the value for retrieval via insertId()
- * @return null
+ *
+ * @param string $seqName
+ * @return int|null
*/
function nextSequenceValue( $seqName ) {
$safeseq = str_replace( "'", "''", $seqName );
$res = $this->query( "SELECT nextval('$safeseq')" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
+
return $this->mInsertId;
}
/**
* Return the current value of a sequence. Assumes it has been nextval'ed in this session.
- * @return
+ *
+ * @param string $seqName
+ * @return int
*/
function currentSequenceValue( $seqName ) {
$safeseq = str_replace( "'", "''", $seqName );
$res = $this->query( "SELECT currval('$safeseq')" );
$row = $this->fetchRow( $res );
$currval = $row[0];
+
return $currval;
}
@@ -993,6 +1116,7 @@ __INDEXATTR__;
} else {
$size = $row->size;
}
+
return $size;
}
@@ -1007,7 +1131,9 @@ __INDEXATTR__;
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
$newName = $this->addIdentifierQuotes( $newName );
$oldName = $this->addIdentifierQuotes( $oldName );
- return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
+
+ return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
+ "(LIKE $oldName INCLUDING DEFAULTS)", $fname );
}
function listTables( $prefix = null, $fname = __METHOD__ ) {
@@ -1030,7 +1156,7 @@ __INDEXATTR__;
return wfTimestamp( TS_POSTGRES, $ts );
}
- /*
+ /**
* Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
* to http://www.php.net/manual/en/ref.pgsql.php
*
@@ -1042,10 +1168,10 @@ __INDEXATTR__;
* This should really be handled by PHP PostgreSQL module
*
* @since 1.19
- * @param $text string: postgreql array returned in a text form like {a,b}
- * @param $output string
- * @param $limit int
- * @param $offset int
+ * @param string $text Postgreql array returned in a text form like {a,b}
+ * @param string $output
+ * @param int $limit
+ * @param int $offset
* @return string
*/
function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
@@ -1062,8 +1188,8 @@ __INDEXATTR__;
$text, $match, 0, $offset );
$offset += strlen( $match[0] );
$output[] = ( '"' != $match[1][0]
- ? $match[1]
- : stripcslashes( substr( $match[1], 1, -1 ) ) );
+ ? $match[1]
+ : stripcslashes( substr( $match[1], 1, -1 ) ) );
if ( '},' == $match[3] ) {
return $output;
}
@@ -1071,18 +1197,22 @@ __INDEXATTR__;
$offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
}
} while ( $limit > $offset );
+
return $output;
}
/**
* Return aggregated value function call
+ * @param array $valuedata
+ * @param string $valuename
+ * @return array
*/
public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
}
/**
- * @return string wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
public function getSoftwareLink() {
return '[{{int:version-db-postgres-url}} PostgreSQL]';
@@ -1093,11 +1223,12 @@ __INDEXATTR__;
* Needs transaction
*
* @since 1.19
- * @return string return default schema for the current session
+ * @return string Default schema for the current session
*/
function getCurrentSchema() {
$res = $this->query( "SELECT current_schema()", __METHOD__ );
$row = $this->fetchRow( $res );
+
return $row[0];
}
@@ -1109,13 +1240,15 @@ __INDEXATTR__;
* @see getSearchPath()
* @see setSearchPath()
* @since 1.19
- * @return array list of actual schemas for the current sesson
+ * @return array List of actual schemas for the current sesson
*/
function getSchemas() {
$res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
$row = $this->fetchRow( $res );
$schemas = array();
+
/* PHP pgsql support does not support array type, "{a,b}" string is returned */
+
return $this->pg_array_parse( $row[0], $schemas );
}
@@ -1126,12 +1259,14 @@ __INDEXATTR__;
* Needs transaction
*
* @since 1.19
- * @return array how to search for table names schemas for the current user
+ * @return array How to search for table names schemas for the current user
*/
function getSearchPath() {
$res = $this->query( "SHOW search_path", __METHOD__ );
$row = $this->fetchRow( $res );
+
/* PostgreSQL returns SHOW values as strings */
+
return explode( ",", $row[0] );
}
@@ -1140,7 +1275,7 @@ __INDEXATTR__;
* Values may contain magic keywords like "$user"
* @since 1.19
*
- * @param $search_path array list of schemas to be searched by default
+ * @param array $search_path List of schemas to be searched by default
*/
function setSearchPath( $search_path ) {
$this->query( "SET search_path = " . implode( ", ", $search_path ) );
@@ -1157,14 +1292,15 @@ __INDEXATTR__;
* This will be also called by the installer after the schema is created
*
* @since 1.19
- * @param $desired_schema string
+ *
+ * @param string $desiredSchema
*/
- function determineCoreSchema( $desired_schema ) {
+ function determineCoreSchema( $desiredSchema ) {
$this->begin( __METHOD__ );
- if ( $this->schemaExists( $desired_schema ) ) {
- if ( in_array( $desired_schema, $this->getSchemas() ) ) {
- $this->mCoreSchema = $desired_schema;
- wfDebug( "Schema \"" . $desired_schema . "\" already in the search path\n" );
+ if ( $this->schemaExists( $desiredSchema ) ) {
+ if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
+ $this->mCoreSchema = $desiredSchema;
+ wfDebug( "Schema \"" . $desiredSchema . "\" already in the search path\n" );
} else {
/**
* Prepend our schema (e.g. 'mediawiki') in front
@@ -1173,14 +1309,15 @@ __INDEXATTR__;
*/
$search_path = $this->getSearchPath();
array_unshift( $search_path,
- $this->addIdentifierQuotes( $desired_schema ));
+ $this->addIdentifierQuotes( $desiredSchema ) );
$this->setSearchPath( $search_path );
- $this->mCoreSchema = $desired_schema;
- wfDebug( "Schema \"" . $desired_schema . "\" added to the search path\n" );
+ $this->mCoreSchema = $desiredSchema;
+ wfDebug( "Schema \"" . $desiredSchema . "\" added to the search path\n" );
}
} else {
$this->mCoreSchema = $this->getCurrentSchema();
- wfDebug( "Schema \"" . $desired_schema . "\" not found, using current \"" . $this->mCoreSchema . "\"\n" );
+ wfDebug( "Schema \"" . $desiredSchema . "\" not found, using current \"" .
+ $this->mCoreSchema . "\"\n" );
}
/* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
$this->commit( __METHOD__ );
@@ -1190,7 +1327,7 @@ __INDEXATTR__;
* Return schema name fore core MediaWiki tables
*
* @since 1.19
- * @return string core schema name
+ * @return string Core schema name
*/
function getCoreSchema() {
return $this->mCoreSchema;
@@ -1200,25 +1337,29 @@ __INDEXATTR__;
* @return string Version information from the database
*/
function getServerVersion() {
- if ( !isset( $this->numeric_version ) ) {
+ if ( !isset( $this->numericVersion ) ) {
$versionInfo = pg_version( $this->mConn );
if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
// Old client, abort install
- $this->numeric_version = '7.3 or earlier';
+ $this->numericVersion = '7.3 or earlier';
} elseif ( isset( $versionInfo['server'] ) ) {
// Normal client
- $this->numeric_version = $versionInfo['server'];
+ $this->numericVersion = $versionInfo['server'];
} else {
// Bug 16937: broken pgsql extension from PHP<5.3
- $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
+ $this->numericVersion = pg_parameter_status( $this->mConn, 'server_version' );
}
}
- return $this->numeric_version;
+
+ return $this->numericVersion;
}
/**
* Query whether a given relation exists (in the given schema, or the
* default mw one if not given)
+ * @param string $table
+ * @param array|string $types
+ * @param bool|string $schema
* @return bool
*/
function relationExists( $table, $types, $schema = false ) {
@@ -1231,17 +1372,21 @@ __INDEXATTR__;
$table = $this->realTableName( $table, 'raw' );
$etable = $this->addQuotes( $table );
$eschema = $this->addQuotes( $schema );
- $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
+ $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
. "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
. "AND c.relkind IN ('" . implode( "','", $types ) . "')";
- $res = $this->query( $SQL );
+ $res = $this->query( $sql );
$count = $res ? $res->numRows() : 0;
+
return (bool)$count;
}
/**
* For backward compatibility, this function checks both tables and
* views.
+ * @param string $table
+ * @param string $fname
+ * @param bool|string $schema
* @return bool
*/
function tableExists( $table, $fname = __METHOD__, $schema = false ) {
@@ -1271,6 +1416,7 @@ SQL;
return null;
}
$rows = $res->numRows();
+
return $rows;
}
@@ -1282,41 +1428,47 @@ SQL;
'schemaname' => $this->getCoreSchema()
)
);
+
return $exists === $rule;
}
function constraintExists( $table, $constraint ) {
- $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
- "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
+ $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
+ "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
$this->addQuotes( $this->getCoreSchema() ),
$this->addQuotes( $table ),
$this->addQuotes( $constraint )
);
- $res = $this->query( $SQL );
+ $res = $this->query( $sql );
if ( !$res ) {
return null;
}
$rows = $res->numRows();
+
return $rows;
}
/**
* Query whether a given schema exists. Returns true if it does, false if it doesn't.
+ * @param string $schema
* @return bool
*/
function schemaExists( $schema ) {
$exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
array( 'nspname' => $schema ), __METHOD__ );
+
return (bool)$exists;
}
/**
* Returns true if a given role (i.e. user) exists, false otherwise.
+ * @param string $roleName
* @return bool
*/
function roleExists( $roleName ) {
$exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
array( 'rolname' => $roleName ), __METHOD__ );
+
return (bool)$exists;
}
@@ -1326,17 +1478,20 @@ SQL;
/**
* pg_field_type() wrapper
+ * @param ResultWrapper|resource $res ResultWrapper or PostgreSQL query result resource
+ * @param int $index Field number, starting from 0
* @return string
*/
function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return pg_field_type( $res, $index );
}
/**
- * @param $b
+ * @param string $b
* @return Blob
*/
function encodeBlob( $b ) {
@@ -1347,6 +1502,7 @@ SQL;
if ( $b instanceof Blob ) {
$b = $b->fetch();
}
+
return pg_unescape_bytea( $b );
}
@@ -1355,7 +1511,7 @@ SQL;
}
/**
- * @param $s null|bool|Blob
+ * @param null|bool|Blob $s
* @return int|string
*/
function addQuotes( $s ) {
@@ -1366,6 +1522,7 @@ SQL;
} elseif ( $s instanceof Blob ) {
return "'" . $s->fetch( $s ) . "'";
}
+
return "'" . pg_escape_string( $this->mConn, $s ) . "'";
}
@@ -1373,21 +1530,18 @@ SQL;
* Postgres specific version of replaceVars.
* Calls the parent version in Database.php
*
- * @private
- *
* @param string $ins SQL string, read from a stream (usually tables.sql)
- *
* @return string SQL string
*/
protected function replaceVars( $ins ) {
$ins = parent::replaceVars( $ins );
- if ( $this->numeric_version >= 8.3 ) {
+ if ( $this->numericVersion >= 8.3 ) {
// Thanks for not providing backwards-compatibility, 8.3
$ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
}
- if ( $this->numeric_version <= 8.1 ) { // Our minimum version
+ if ( $this->numericVersion <= 8.1 ) { // Our minimum version
$ins = str_replace( 'USING gin', 'USING gist', $ins );
}
@@ -1397,10 +1551,8 @@ SQL;
/**
* Various select options
*
- * @private
- *
- * @param array $options an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
+ * @param array $options An associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
* @return array
*/
function makeSelectOptions( $options ) {
@@ -1425,8 +1577,9 @@ SQL;
//}
if ( isset( $options['FOR UPDATE'] ) ) {
- $postLimitTail .= ' FOR UPDATE OF ' . implode( ', ', $options['FOR UPDATE'] );
- } else if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+ $postLimitTail .= ' FOR UPDATE OF ' .
+ implode( ', ', array_map( array( &$this, 'tableName' ), $options['FOR UPDATE'] ) );
+ } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
$postLimitTail .= ' FOR UPDATE';
}
@@ -1437,9 +1590,6 @@ SQL;
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
- function setFakeMaster( $enabled = true ) {
- }
-
function getDBname() {
return $this->mDBname;
}
@@ -1452,6 +1602,14 @@ SQL;
return implode( ' || ', $stringList );
}
+ public function buildGroupConcatField(
+ $delimiter, $table, $field, $conds = '', $options = array(), $join_conds = array()
+ ) {
+ $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
public function getSearchEngine() {
return 'SearchPostgres';
}
@@ -1461,11 +1619,11 @@ SQL;
if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
if ( $this->delimiter ) {
$this->delimiter = false;
- }
- else {
+ } else {
$this->delimiter = ';';
}
}
+
return parent::streamStatementEnd( $sql, $newLine );
}
@@ -1473,9 +1631,9 @@ SQL;
* Check to see if a named lock is available. This is non-blocking.
* See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
*
- * @param string $lockName name of lock to poll
- * @param string $method name of method calling us
- * @return Boolean
+ * @param string $lockName Name of lock to poll
+ * @param string $method Name of method calling us
+ * @return bool
* @since 1.20
*/
public function lockIsFree( $lockName, $method ) {
@@ -1483,14 +1641,15 @@ SQL;
$result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
$row = $this->fetchObject( $result );
+
return ( $row->lockstatus === 't' );
}
/**
* See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
- * @param $lockName string
- * @param $method string
- * @param $timeout int
+ * @param string $lockName
+ * @param string $method
+ * @param int $timeout
* @return bool
*/
public function lock( $lockName, $method, $timeout = 5 ) {
@@ -1506,19 +1665,22 @@ SQL;
}
}
wfDebug( __METHOD__ . " failed to acquire lock\n" );
+
return false;
}
/**
- * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
- * @param $lockName string
- * @param $method string
+ * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM
+ * PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+ * @param string $lockName
+ * @param string $method
* @return bool
*/
public function unlock( $lockName, $method ) {
$key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
$result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
$row = $this->fetchObject( $result );
+
return ( $row->lockstatus === 't' );
}
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index 3e034649..dd2e813e 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -26,38 +26,53 @@
* @ingroup Database
*/
class DatabaseSqlite extends DatabaseBase {
-
+ /** @var bool Whether full text is enabled */
private static $fulltextEnabled = null;
- var $mAffectedRows;
- var $mLastResult;
- var $mDatabaseFile;
- var $mName;
+ /** @var string File name for SQLite database file */
+ public $mDatabaseFile;
- /**
- * @var PDO
- */
+ /** @var int The number of rows affected as an integer */
+ protected $mAffectedRows;
+
+ /** @var resource */
+ protected $mLastResult;
+
+ /** @var PDO */
protected $mConn;
- /**
- * Constructor.
- * Parameters $server, $user and $password are not used.
- * @param $server string
- * @param $user string
- * @param $password string
- * @param $dbName string
- * @param $flags int
- */
- function __construct( $server = false, $user = false, $password = false, $dbName = false, $flags = 0 ) {
- $this->mName = $dbName;
- parent::__construct( $server, $user, $password, $dbName, $flags );
+ /** @var FSLockManager (hopefully on the same server as the DB) */
+ protected $lockMgr;
+
+ function __construct( $p = null ) {
+ global $wgSharedDB, $wgSQLiteDataDir;
+
+ if ( !is_array( $p ) ) { // legacy calling pattern
+ wfDeprecated( __METHOD__ . " method called without parameter array.", "1.22" );
+ $args = func_get_args();
+ $p = array(
+ 'host' => isset( $args[0] ) ? $args[0] : false,
+ 'user' => isset( $args[1] ) ? $args[1] : false,
+ 'password' => isset( $args[2] ) ? $args[2] : false,
+ 'dbname' => isset( $args[3] ) ? $args[3] : false,
+ 'flags' => isset( $args[4] ) ? $args[4] : 0,
+ 'tablePrefix' => isset( $args[5] ) ? $args[5] : 'get from global',
+ 'schema' => 'get from global',
+ 'foreign' => isset( $args[6] ) ? $args[6] : false
+ );
+ }
+ $this->mDBname = $p['dbname'];
+ parent::__construct( $p );
// parent doesn't open when $user is false, but we can work with $dbName
- if ( $dbName && !$this->isOpen() ) {
- global $wgSharedDB;
- if ( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) {
- $this->attachDatabase( $wgSharedDB );
+ if ( $p['dbname'] && !$this->isOpen() ) {
+ if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
+ if ( $wgSharedDB ) {
+ $this->attachDatabase( $wgSharedDB );
+ }
}
}
+
+ $this->lockMgr = new FSLockManager( array( 'lockDirectory' => "$wgSQLiteDataDir/locks" ) );
}
/**
@@ -97,18 +112,20 @@ class DatabaseSqlite extends DatabaseBase {
throw new DBConnectionError( $this, "SQLite database not accessible" );
}
$this->openFile( $fileName );
+
return $this->mConn;
}
/**
* Opens a database file
*
- * @param $fileName string
- *
+ * @param string $fileName
* @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
function openFile( $fileName ) {
+ $err = false;
+
$this->mDatabaseFile = $fileName;
try {
if ( $this->mFlags & DBO_PERSISTENT ) {
@@ -120,18 +137,23 @@ class DatabaseSqlite extends DatabaseBase {
} catch ( PDOException $e ) {
$err = $e->getMessage();
}
+
if ( !$this->mConn ) {
wfDebug( "DB connection error: $err\n" );
throw new DBConnectionError( $this, $err );
}
+
$this->mOpened = !!$this->mConn;
# set error codes only, don't raise exceptions
if ( $this->mOpened ) {
$this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
# Enforce LIKE to be case sensitive, just like MySQL
$this->query( 'PRAGMA case_sensitive_like = 1' );
- return true;
+
+ return $this->mConn;
}
+
+ return false;
}
/**
@@ -140,6 +162,7 @@ class DatabaseSqlite extends DatabaseBase {
*/
protected function closeConnection() {
$this->mConn = null;
+
return true;
}
@@ -147,7 +170,7 @@ class DatabaseSqlite extends DatabaseBase {
* Generates a database file name. Explicitly public for installer.
* @param string $dir Directory where database resides
* @param string $dbName Database name
- * @return String
+ * @return string
*/
public static function generateFileName( $dir, $dbName ) {
return "$dir/$dbName.sqlite";
@@ -167,12 +190,13 @@ class DatabaseSqlite extends DatabaseBase {
self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
}
}
+
return self::$fulltextEnabled;
}
/**
* Returns version of currently supported SQLite fulltext search module or false if none present.
- * @return String
+ * @return string
*/
static function getFulltextSearchModule() {
static $cachedResult = null;
@@ -188,6 +212,7 @@ class DatabaseSqlite extends DatabaseBase {
$cachedResult = 'FTS3';
}
$db->close();
+
return $cachedResult;
}
@@ -195,10 +220,11 @@ class DatabaseSqlite extends DatabaseBase {
* Attaches external database to our connection, see http://sqlite.org/lang_attach.html
* for details.
*
- * @param string $name database name to be used in queries like SELECT foo FROM dbname.table
- * @param string $file database file name. If omitted, will be generated using $name and $wgSQLiteDataDir
- * @param string $fname calling function name
- *
+ * @param string $name Database name to be used in queries like
+ * SELECT foo FROM dbname.table
+ * @param bool|string $file Database file name. If omitted, will be generated
+ * using $name and $wgSQLiteDataDir
+ * @param string $fname Calling function name
* @return ResultWrapper
*/
function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
@@ -207,14 +233,14 @@ class DatabaseSqlite extends DatabaseBase {
$file = self::generateFileName( $wgSQLiteDataDir, $name );
}
$file = $this->addQuotes( $file );
+
return $this->query( "ATTACH DATABASE $file AS $name", $fname );
}
/**
* @see DatabaseBase::isWriteQuery()
*
- * @param $sql string
- *
+ * @param string $sql
* @return bool
*/
function isWriteQuery( $sql ) {
@@ -224,9 +250,8 @@ class DatabaseSqlite extends DatabaseBase {
/**
* SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
*
- * @param $sql string
- *
- * @return ResultWrapper
+ * @param string $sql
+ * @return bool|ResultWrapper
*/
protected function doQuery( $sql ) {
$res = $this->mConn->query( $sql );
@@ -237,11 +262,12 @@ class DatabaseSqlite extends DatabaseBase {
$this->mAffectedRows = $r->rowCount();
$res = new ResultWrapper( $this, $r->fetchAll() );
}
+
return $res;
}
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper|mixed $res
*/
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -252,8 +278,8 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $res ResultWrapper
- * @return object|bool
+ * @param ResultWrapper|array $res
+ * @return stdClass|bool
*/
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -274,11 +300,12 @@ class DatabaseSqlite extends DatabaseBase {
return $obj;
}
+
return false;
}
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper|mixed $res
* @return array|bool
*/
function fetchRow( $res ) {
@@ -290,51 +317,61 @@ class DatabaseSqlite extends DatabaseBase {
$cur = current( $r );
if ( is_array( $cur ) ) {
next( $r );
+
return $cur;
}
+
return false;
}
/**
* The PDO::Statement class implements the array interface so count() will work
*
- * @param $res ResultWrapper
- *
+ * @param ResultWrapper|array $res
* @return int
*/
function numRows( $res ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
+
return count( $r );
}
/**
- * @param $res ResultWrapper
+ * @param ResultWrapper $res
* @return int
*/
function numFields( $res ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
- return is_array( $r ) ? count( $r[0] ) : 0;
+ if ( is_array( $r ) && count( $r ) > 0 ) {
+ // The size of the result array is twice the number of fields. (Bug: 65578)
+ return count( $r[0] ) / 2;
+ } else {
+ // If the result is empty return 0
+ return 0;
+ }
}
/**
- * @param $res ResultWrapper
- * @param $n
+ * @param ResultWrapper $res
+ * @param int $n
* @return bool
*/
function fieldName( $res, $n ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
if ( is_array( $r ) ) {
$keys = array_keys( $r[0] );
+
return $keys[$n];
}
+
return false;
}
/**
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
*
- * @param $name
- * @param $format String
+ * @param string $name
+ * @param string $format
* @return string
*/
function tableName( $name, $format = 'quoted' ) {
@@ -342,14 +379,14 @@ class DatabaseSqlite extends DatabaseBase {
if ( strpos( $name, 'sqlite_' ) === 0 ) {
return $name;
}
+
return str_replace( '"', '', parent::tableName( $name, $format ) );
}
/**
* Index names have DB scope
*
- * @param $index string
- *
+ * @param string $index
* @return string
*/
function indexName( $index ) {
@@ -367,8 +404,8 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $res ResultWrapper
- * @param $row
+ * @param ResultWrapper|array $res
+ * @param int $row
*/
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
@@ -392,6 +429,7 @@ class DatabaseSqlite extends DatabaseBase {
return "Cannot return last error, no db connection";
}
$e = $this->mConn->errorInfo();
+
return isset( $e[2] ) ? $e[2] : '';
}
@@ -403,6 +441,7 @@ class DatabaseSqlite extends DatabaseBase {
return "Cannot return last error, no db connection";
} else {
$info = $this->mConn->errorInfo();
+
return $info[1];
}
}
@@ -419,6 +458,9 @@ class DatabaseSqlite extends DatabaseBase {
* Returns false if the index does not exist
* - if errors are explicitly ignored, returns NULL on failure
*
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return array
*/
function indexInfo( $table, $index, $fname = __METHOD__ ) {
@@ -434,13 +476,14 @@ class DatabaseSqlite extends DatabaseBase {
foreach ( $res as $row ) {
$info[] = $row->name;
}
+
return $info;
}
/**
- * @param $table
- * @param $index
- * @param $fname string
+ * @param string $table
+ * @param string $index
+ * @param string $fname
* @return bool|null
*/
function indexUnique( $table, $index, $fname = __METHOD__ ) {
@@ -460,14 +503,14 @@ class DatabaseSqlite extends DatabaseBase {
}
$firstPart = substr( $row->sql, 0, $indexPos );
$options = explode( ' ', $firstPart );
+
return in_array( 'UNIQUE', $options );
}
/**
* Filter the options used in SELECT statements
*
- * @param $options array
- *
+ * @param array $options
* @return array
*/
function makeSelectOptions( $options ) {
@@ -476,20 +519,23 @@ class DatabaseSqlite extends DatabaseBase {
$options[$k] = '';
}
}
+
return parent::makeSelectOptions( $options );
}
/**
- * @param $options array
+ * @param array $options
* @return string
*/
- function makeUpdateOptions( $options ) {
+ protected function makeUpdateOptionsArray( $options ) {
+ $options = parent::makeUpdateOptionsArray( $options );
$options = self::fixIgnore( $options );
- return parent::makeUpdateOptions( $options );
+
+ return $options;
}
/**
- * @param $options array
+ * @param array $options
* @return array
*/
static function fixIgnore( $options ) {
@@ -499,20 +545,26 @@ class DatabaseSqlite extends DatabaseBase {
$options[$k] = 'OR IGNORE';
}
}
+
return $options;
}
/**
- * @param $options array
+ * @param array $options
* @return string
*/
function makeInsertOptions( $options ) {
$options = self::fixIgnore( $options );
+
return parent::makeInsertOptions( $options );
}
/**
* Based on generic method (parent) with some prior SQLite-sepcific adjustments
+ * @param string $table
+ * @param array $a
+ * @param string $fname
+ * @param array $options
* @return bool
*/
function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
@@ -536,10 +588,10 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $table
- * @param $uniqueIndexes
- * @param $rows
- * @param $fname string
+ * @param string $table
+ * @param array $uniqueIndexes Unused
+ * @param string|array $rows
+ * @param string $fname
* @return bool|ResultWrapper
*/
function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
@@ -566,6 +618,8 @@ class DatabaseSqlite extends DatabaseBase {
* Returns the size of a text field, or -1 for "unlimited"
* In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though.
*
+ * @param string $table
+ * @param string $field
* @return int
*/
function textFieldSize( $table, $field ) {
@@ -580,12 +634,13 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $sqls
- * @param $all
+ * @param string $sqls
+ * @param bool $all Whether to "UNION ALL" or not
* @return string
*/
function unionQueries( $sqls, $all ) {
$glue = $all ? ' UNION ALL ' : ' UNION ';
+
return implode( $glue, $sqls );
}
@@ -611,7 +666,7 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @return string wikitext of a link to the server software's web site
+ * @return string Wikitext of a link to the server software's web site
*/
public function getSoftwareLink() {
return "[{{int:version-db-sqlite-url}} SQLite]";
@@ -622,6 +677,7 @@ class DatabaseSqlite extends DatabaseBase {
*/
function getServerVersion() {
$ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+
return $ver;
}
@@ -629,15 +685,17 @@ class DatabaseSqlite extends DatabaseBase {
* @return string User-friendly database information
*/
public function getServerInfo() {
- return wfMessage( self::getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() )->text();
+ return wfMessage( self::getFulltextSearchModule()
+ ? 'sqlite-has-fts'
+ : 'sqlite-no-fts', $this->getServerVersion() )->text();
}
/**
* Get information about a given field
* Returns false if the field does not exist.
*
- * @param $table string
- * @param $field string
+ * @param string $table
+ * @param string $field
* @return SQLiteField|bool False on failure
*/
function fieldInfo( $table, $field ) {
@@ -649,6 +707,7 @@ class DatabaseSqlite extends DatabaseBase {
return new SQLiteField( $row, $tableName );
}
}
+
return false;
}
@@ -685,15 +744,15 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $s string
+ * @param string $s
* @return string
*/
function strencode( $s ) {
- return substr( $this->addQuotes( $s ), 1, - 1 );
+ return substr( $this->addQuotes( $s ), 1, -1 );
}
/**
- * @param $b
+ * @param string $b
* @return Blob
*/
function encodeBlob( $b ) {
@@ -701,18 +760,19 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @param $b Blob|string
+ * @param Blob|string $b
* @return string
*/
function decodeBlob( $b ) {
if ( $b instanceof Blob ) {
$b = $b->fetch();
}
+
return $b;
}
/**
- * @param $s Blob|string
+ * @param Blob|string $s
* @return string
*/
function addQuotes( $s ) {
@@ -741,6 +801,7 @@ class DatabaseSqlite extends DatabaseBase {
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
$params = $params[0];
}
+
return parent::buildLike( $params ) . "ESCAPE '\' ";
}
@@ -753,16 +814,18 @@ class DatabaseSqlite extends DatabaseBase {
/**
* No-op version of deadlockLoop
+ *
* @return mixed
*/
public function deadlockLoop( /*...*/ ) {
$args = func_get_args();
$function = array_shift( $args );
+
return call_user_func_array( $function, $args );
}
/**
- * @param $s string
+ * @param string $s
* @return string
*/
protected function replaceVars( $s ) {
@@ -777,7 +840,11 @@ class DatabaseSqlite extends DatabaseBase {
// INT -> INTEGER
$s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
// floating point types -> REAL
- $s = preg_replace( '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i', 'REAL', $s );
+ $s = preg_replace(
+ '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
+ 'REAL',
+ $s
+ );
// varchar -> TEXT
$s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
// TEXT normalization
@@ -803,37 +870,73 @@ class DatabaseSqlite extends DatabaseBase {
$s = preg_replace( '/\(\d+\)/', '', $s );
// No FULLTEXT
$s = preg_replace( '/\bfulltext\b/i', '', $s );
+ } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
+ // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
+ $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
+ } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) {
+ // INSERT IGNORE --> INSERT OR IGNORE
+ $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s );
}
+
return $s;
}
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ global $wgSQLiteDataDir;
+
+ if ( !is_dir( "$wgSQLiteDataDir/locks" ) ) { // create dir as needed
+ if ( !is_writable( $wgSQLiteDataDir ) || !mkdir( "$wgSQLiteDataDir/locks" ) ) {
+ throw new DBError( "Cannot create directory \"$wgSQLiteDataDir/locks\"." );
+ }
+ }
+
+ return $this->lockMgr->lock( array( $lockName ), LockManager::LOCK_EX, $timeout )->isOK();
+ }
+
+ public function unlock( $lockName, $method ) {
+ return $this->lockMgr->unlock( array( $lockName ), LockManager::LOCK_EX )->isOK();
+ }
+
/**
* Build a concatenation list to feed into a SQL query
*
- * @param $stringList array
- *
+ * @param string[] $stringList
* @return string
*/
function buildConcat( $stringList ) {
return '(' . implode( ') || (', $stringList ) . ')';
}
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = array()
+ ) {
+ $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
+
+ return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
+ }
+
/**
* @throws MWException
- * @param $oldName
- * @param $newName
- * @param $temporary bool
- * @param $fname string
+ * @param string $oldName
+ * @param string $newName
+ * @param bool $temporary
+ * @param string $fname
* @return bool|ResultWrapper
*/
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
- $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" . $this->addQuotes( $oldName ) . " AND type='table'", $fname );
+ $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
+ $this->addQuotes( $oldName ) . " AND type='table'", $fname );
$obj = $this->fetchObject( $res );
if ( !$obj ) {
throw new MWException( "Couldn't retrieve structure for table $oldName" );
}
$sql = $obj->sql;
- $sql = preg_replace( '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/', $this->addIdentifierQuotes( $newName ), $sql, 1 );
+ $sql = preg_replace(
+ '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/',
+ $this->addIdentifierQuotes( $newName ),
+ $sql,
+ 1
+ );
if ( $temporary ) {
if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" );
@@ -841,6 +944,7 @@ class DatabaseSqlite extends DatabaseBase {
$sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
}
}
+
return $this->query( $sql, $fname );
}
@@ -848,7 +952,7 @@ class DatabaseSqlite extends DatabaseBase {
* List all tables on the database
*
* @param string $prefix Only show tables with this prefix, e.g. mw_
- * @param string $fname calling function name
+ * @param string $fname Calling function name
*
* @return array
*/
@@ -869,13 +973,11 @@ class DatabaseSqlite extends DatabaseBase {
if ( strpos( $table, 'sqlite_' ) !== 0 ) {
$endArray[] = $table;
}
-
}
}
return $endArray;
}
-
} // end DatabaseSqlite class
/**
@@ -895,6 +997,7 @@ class DatabaseSqliteStandalone extends DatabaseSqlite {
*/
class SQLiteField implements Field {
private $info, $tableName;
+
function __construct( $info, $tableName ) {
$this->info = $info;
$this->tableName = $tableName;
@@ -915,6 +1018,7 @@ class SQLiteField implements Field {
return str_replace( "''", "'", $this->info->dflt_value );
}
}
+
return $this->info->dflt_value;
}
@@ -928,5 +1032,4 @@ class SQLiteField implements Field {
function type() {
return $this->info->type;
}
-
} // end SQLiteField
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index de58bab6..c1e80d33 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -51,7 +51,8 @@ class DBObject {
* This allows us to distinguish a blob from a normal string and an array of strings
*/
class Blob {
- private $mData;
+ /** @var string */
+ protected $mData;
function __construct( $data ) {
$this->mData = $data;
@@ -97,13 +98,23 @@ interface Field {
* @ingroup Database
*/
class ResultWrapper implements Iterator {
- var $db, $result, $pos = 0, $currentRow = null;
+ /** @var resource */
+ public $result;
+
+ /** @var DatabaseBase */
+ protected $db;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var object|null */
+ protected $currentRow = null;
/**
* Create a new result object from a result resource and a Database object
*
* @param DatabaseBase $database
- * @param resource $result
+ * @param resource|ResultWrapper $result
*/
function __construct( $database, $result ) {
$this->db = $database;
@@ -118,7 +129,7 @@ class ResultWrapper implements Iterator {
/**
* Get the number of rows in a result object
*
- * @return integer
+ * @return int
*/
function numRows() {
return $this->db->numRows( $this );
@@ -129,7 +140,7 @@ class ResultWrapper implements Iterator {
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
*
- * @return object
+ * @return stdClass
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchObject() {
@@ -140,7 +151,7 @@ class ResultWrapper implements Iterator {
* Fetch the next row from the given result object, in associative array
* form. Fields are retrieved with $row['fieldname'].
*
- * @return Array
+ * @return array
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchRow() {
@@ -160,14 +171,14 @@ class ResultWrapper implements Iterator {
* Change the position of the cursor in a result object.
* See mysql_data_seek()
*
- * @param $row integer
+ * @param int $row
*/
function seek( $row ) {
$this->db->dataSeek( $this, $row );
}
- /*********************
- * Iterator functions
+ /*
+ * ======= Iterator functions =======
* Note that using these in combination with the non-iterator functions
* above may cause rows to be skipped or repeated.
*/
@@ -181,12 +192,13 @@ class ResultWrapper implements Iterator {
}
/**
- * @return int
+ * @return stdClass|array|bool
*/
function current() {
if ( is_null( $this->currentRow ) ) {
$this->next();
}
+
return $this->currentRow;
}
@@ -198,11 +210,12 @@ class ResultWrapper implements Iterator {
}
/**
- * @return int
+ * @return stdClass
*/
function next() {
$this->pos++;
$this->currentRow = $this->fetchObject();
+
return $this->currentRow;
}
@@ -219,10 +232,17 @@ class ResultWrapper implements Iterator {
* doesn't go anywhere near an actual database.
*/
class FakeResultWrapper extends ResultWrapper {
- var $result = array();
- var $db = null; // And it's going to stay that way :D
- var $pos = 0;
- var $currentRow = null;
+ /** @var array */
+ public $result = array();
+
+ /** @var null And it's going to stay that way :D */
+ protected $db = null;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var array|stdClass|bool */
+ protected $currentRow = null;
function __construct( $array ) {
$this->result = $array;
@@ -235,6 +255,9 @@ class FakeResultWrapper extends ResultWrapper {
return count( $this->result );
}
+ /**
+ * @return array|bool
+ */
function fetchRow() {
if ( $this->pos < count( $this->result ) ) {
$this->currentRow = $this->result[$this->pos];
@@ -256,7 +279,10 @@ class FakeResultWrapper extends ResultWrapper {
function free() {
}
- // Callers want to be able to access fields with $this->fieldName
+ /**
+ * Callers want to be able to access fields with $this->fieldName
+ * @return bool|stdClass
+ */
function fetchObject() {
$this->fetchRow();
if ( $this->currentRow ) {
@@ -271,16 +297,21 @@ class FakeResultWrapper extends ResultWrapper {
$this->currentRow = null;
}
+ /**
+ * @return bool|stdClass
+ */
function next() {
return $this->fetchObject();
}
}
/**
- * Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE clauses
- * and thus need no escaping. Don't instantiate it manually, use DatabaseBase::anyChar() and anyString() instead.
+ * Used by DatabaseBase::buildLike() to represent characters that have special
+ * meaning in SQL LIKE clauses and thus need no escaping. Don't instantiate it
+ * manually, use DatabaseBase::anyChar() and anyString() instead.
*/
class LikeMatch {
+ /** @var string */
private $str;
/**
@@ -295,7 +326,7 @@ class LikeMatch {
/**
* Return the original stored string.
*
- * @return String
+ * @return string
*/
public function toString() {
return $this->str;
@@ -304,6 +335,8 @@ class LikeMatch {
/**
* An object representing a master or slave position in a replicated setup.
+ *
+ * The implementation details of this opaque type are up to the database subclass.
*/
interface DBMasterPos {
}
diff --git a/includes/db/IORMRow.php b/includes/db/IORMRow.php
index 39411791..c66cddfd 100644
--- a/includes/db/IORMRow.php
+++ b/includes/db/IORMRow.php
@@ -32,7 +32,6 @@
*/
interface IORMRow {
-
/**
* Load the specified fields from the database.
*
@@ -40,8 +39,8 @@ interface IORMRow {
* @deprecated since 1.22
*
* @param array|null $fields
- * @param boolean $override
- * @param boolean $skipLoaded
+ * @param bool $override
+ * @param bool $skipLoaded
*
* @return bool Success indicator
*/
@@ -86,7 +85,7 @@ interface IORMRow {
*
* @since 1.20
*
- * @return integer|null
+ * @return int|null
*/
public function getId();
@@ -95,7 +94,7 @@ interface IORMRow {
*
* @since 1.20
*
- * @param integer|null $id
+ * @param int|null $id
*/
public function setId( $id );
@@ -106,7 +105,7 @@ interface IORMRow {
*
* @param string $name
*
- * @return boolean
+ * @return bool
*/
public function hasField( $name );
@@ -115,7 +114,7 @@ interface IORMRow {
*
* @since 1.20
*
- * @return boolean
+ * @return bool
*/
public function hasIdField();
@@ -125,7 +124,7 @@ interface IORMRow {
* @since 1.20
*
* @param array $fields The fields to set
- * @param boolean $override Override already set fields with the provided values?
+ * @param bool $override Override already set fields with the provided values?
*/
public function setFields( array $fields, $override = true );
@@ -136,7 +135,7 @@ interface IORMRow {
* @since 1.20
*
* @param null|array $fields
- * @param boolean $incNullId
+ * @param bool $incNullId
*
* @return array
*/
@@ -148,7 +147,7 @@ interface IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $override
+ * @param bool $override
*/
public function loadDefaults( $override = true );
@@ -161,7 +160,7 @@ interface IORMRow {
* @param string|null $functionName
* @deprecated since 1.22
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function save( $functionName = null );
@@ -171,7 +170,7 @@ interface IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function remove();
@@ -214,9 +213,9 @@ interface IORMRow {
* @deprecated since 1.22
*
* @param string $field
- * @param integer $amount
+ * @param int $amount
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function addToField( $field, $amount );
@@ -245,7 +244,7 @@ interface IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $update
+ * @param bool $update
*/
public function setUpdateSummaries( $update );
@@ -255,7 +254,7 @@ interface IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $summaryMode
+ * @param bool $summaryMode
*/
public function setSummaryMode( $summaryMode );
@@ -268,5 +267,4 @@ interface IORMRow {
* @return IORMTable
*/
public function getTable();
-
}
diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php
index 36865655..4dc693ac 100644
--- a/includes/db/IORMTable.php
+++ b/includes/db/IORMTable.php
@@ -28,7 +28,6 @@
*/
interface IORMTable {
-
/**
* Returns the name of the database table objects of this type are stored in.
*
@@ -63,8 +62,9 @@ interface IORMTable {
* * array
* * blob
*
- * TODO: get rid of the id field. Every row instance needs to have
- * one so this is just causing hassle at various locations by requiring an extra check for field name.
+ * @todo Get rid of the id field. Every row instance needs to have one so
+ * this is just causing hassle at various locations by requiring an extra
+ * check for field name.
*
* @since 1.20
*
@@ -107,10 +107,10 @@ interface IORMTable {
* @param string|null $functionName
*
* @return ORMResult The result set
- * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode)
+ * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode)
*/
public function select( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Selects the the specified fields of the records matching the provided
@@ -123,10 +123,10 @@ interface IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return array of self
+ * @return array Array of self
*/
public function selectObjects( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Do the actual select.
@@ -139,10 +139,10 @@ interface IORMTable {
* @param null|string $functionName
*
* @return ResultWrapper
- * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode)
+ * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode)
*/
public function rawSelect( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Selects the the specified fields of the records matching the provided
@@ -161,13 +161,13 @@ interface IORMTable {
* @param array|string|null $fields
* @param array $conditions
* @param array $options
- * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param bool $collapse Set to false to always return each result row as associative array.
* @param string|null $functionName
*
- * @return array of array
+ * @return array Array of array
*/
public function selectFields( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null );
+ array $options = array(), $collapse = true, $functionName = null );
/**
* Selects the the specified fields of the first matching record.
@@ -183,7 +183,7 @@ interface IORMTable {
* @return IORMRow|bool False on failure
*/
public function selectRow( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Selects the the specified fields of the records matching the provided
@@ -199,7 +199,7 @@ interface IORMTable {
* @return ResultWrapper
*/
public function rawSelectRow( array $fields, array $conditions = array(),
- array $options = array(), $functionName = null );
+ array $options = array(), $functionName = null );
/**
* Selects the the specified fields of the first record matching the provided
@@ -213,13 +213,13 @@ interface IORMTable {
* @param array|string|null $fields
* @param array $conditions
* @param array $options
- * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param bool $collapse Set to false to always return each result row as associative array.
* @param string|null $functionName
*
* @return mixed|array|bool False on failure
*/
public function selectFieldsRow( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null );
+ array $options = array(), $collapse = true, $functionName = null );
/**
* Returns if there is at least one record matching the provided conditions.
@@ -229,7 +229,7 @@ interface IORMTable {
*
* @param array $conditions
*
- * @return boolean
+ * @return bool
*/
public function has( array $conditions = array() );
@@ -238,7 +238,7 @@ interface IORMTable {
*
* @since 1.21
*
- * @return boolean
+ * @return bool
*/
public function exists();
@@ -254,7 +254,7 @@ interface IORMTable {
* @param array $conditions
* @param array $options
*
- * @return integer
+ * @return int
*/
public function count( array $conditions = array(), array $options = array() );
@@ -266,7 +266,7 @@ interface IORMTable {
* @param array $conditions
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function delete( array $conditions, $functionName = null );
@@ -275,8 +275,8 @@ interface IORMTable {
*
* @since 1.20
*
- * @param boolean $requireParams
- * @param boolean $setDefaults
+ * @param bool $requireParams
+ * @param bool $setDefaults
*
* @return array
*/
@@ -298,14 +298,14 @@ interface IORMTable {
*
* @since 1.20
*
- * @return integer DB_ enum
+ * @return int DB_ enum
*/
public function getReadDb();
/**
* Set the database type to use for read operations.
*
- * @param integer $db
+ * @param int $db
*
* @since 1.20
*/
@@ -316,14 +316,16 @@ interface IORMTable {
*
* @since 1.20
*
- * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
+ * @return string|bool The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki is used)
*/
public function getTargetWiki();
/**
* Set the ID of the any foreign wiki to use as a target for database operations
*
- * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
+ * @param string|bool $wiki The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki shall be used)
*
* @since 1.20
*/
@@ -370,7 +372,7 @@ interface IORMTable {
*
* @see LoadBalancer::reuseConnection
*
- * @param DatabaseBase $db the database
+ * @param DatabaseBase $db The database
*
* @since 1.20
*/
@@ -386,7 +388,7 @@ interface IORMTable {
* @param array $values
* @param array $conditions
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function update( array $values, array $conditions = array() );
@@ -419,7 +421,7 @@ interface IORMTable {
*
* @since 1.20
*
- * @param array|string $fields
+ * @param array $fields
*
* @return array
*/
@@ -488,7 +490,7 @@ interface IORMTable {
* @since 1.20
*
* @param array $data
- * @param boolean $loadDefaults
+ * @param bool $loadDefaults
*
* @return IORMRow
*/
@@ -510,8 +512,7 @@ interface IORMTable {
*
* @param string $name
*
- * @return boolean
+ * @return bool
*/
public function canHaveField( $name );
-
}
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index 16c43a00..73456e23 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -26,11 +26,8 @@
* @ingroup Database
*/
abstract class LBFactory {
-
- /**
- * @var LBFactory
- */
- static $instance;
+ /** @var LBFactory */
+ protected static $instance;
/**
* Disables all access to the load balancer, will cause all database access
@@ -38,7 +35,7 @@ abstract class LBFactory {
*/
public static function disableBackend() {
global $wgLBFactoryConf;
- self::$instance = new LBFactory_Fake( $wgLBFactoryConf );
+ self::$instance = new LBFactoryFake( $wgLBFactoryConf );
}
/**
@@ -47,15 +44,47 @@ abstract class LBFactory {
* @return LBFactory
*/
static function &singleton() {
+ global $wgLBFactoryConf;
+
if ( is_null( self::$instance ) ) {
- global $wgLBFactoryConf;
- $class = $wgLBFactoryConf['class'];
+ $class = self::getLBFactoryClass( $wgLBFactoryConf );
+
self::$instance = new $class( $wgLBFactoryConf );
}
+
return self::$instance;
}
/**
+ * Returns the LBFactory class to use and the load balancer configuration.
+ *
+ * @param array $config (e.g. $wgLBFactoryConf)
+ * @return string Class name
+ */
+ public static function getLBFactoryClass( array $config ) {
+ // For configuration backward compatibility after removing
+ // underscores from class names in MediaWiki 1.23.
+ $bcClasses = array(
+ 'LBFactory_Simple' => 'LBFactorySimple',
+ 'LBFactory_Single' => 'LBFactorySingle',
+ 'LBFactory_Multi' => 'LBFactoryMulti',
+ 'LBFactory_Fake' => 'LBFactoryFake',
+ );
+
+ $class = $config['class'];
+
+ if ( isset( $bcClasses[$class] ) ) {
+ $class = $bcClasses[$class];
+ wfDeprecated(
+ '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details',
+ '1.23'
+ );
+ }
+
+ return $class;
+ }
+
+ /**
* Shut down, close connections and destroy the cached instance.
*/
static function destroyInstance() {
@@ -69,7 +98,7 @@ abstract class LBFactory {
/**
* Set the instance to be the given object
*
- * @param $instance LBFactory
+ * @param LBFactory $instance
*/
static function setInstance( $instance ) {
self::destroyInstance();
@@ -78,7 +107,7 @@ abstract class LBFactory {
/**
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
- * @param $conf
+ * @param array $conf
*/
abstract function __construct( $conf );
@@ -86,7 +115,7 @@ abstract class LBFactory {
* Create a new load balancer object. The resulting object will be untracked,
* not chronology-protected, and the caller is responsible for cleaning it up.
*
- * @param string $wiki wiki ID, or false for the current wiki
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function newMainLB( $wiki = false );
@@ -94,7 +123,7 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer object.
*
- * @param string $wiki wiki ID, or false for the current wiki
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function getMainLB( $wiki = false );
@@ -104,9 +133,8 @@ abstract class LBFactory {
* untracked, not chronology-protected, and the caller is responsible for
* cleaning it up.
*
- * @param string $cluster external storage cluster, or false for core
- * @param string $wiki wiki ID, or false for the current wiki
- *
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function newExternalLB( $cluster, $wiki = false );
@@ -114,9 +142,8 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer for external storage
*
- * @param string $cluster external storage cluster, or false for core
- * @param string $wiki wiki ID, or false for the current wiki
- *
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function &getExternalLB( $cluster, $wiki = false );
@@ -125,7 +152,8 @@ abstract class LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
- * @param $callback string|array
+ *
+ * @param callable $callback
* @param array $params
*/
abstract function forEachLB( $callback, $params = array() );
@@ -139,8 +167,9 @@ abstract class LBFactory {
/**
* Call a method of each tracked load balancer
- * @param $methodName string
- * @param $args array
+ *
+ * @param string $methodName
+ * @param array $args
*/
function forEachLBCallMethod( $methodName, $args = array() ) {
$this->forEachLB( array( $this, 'callMethod' ), array( $methodName, $args ) );
@@ -148,9 +177,9 @@ abstract class LBFactory {
/**
* Private helper for forEachLBCallMethod
- * @param $loadBalancer
- * @param $methodName string
- * @param $args
+ * @param LoadBalancer $loadBalancer
+ * @param string $methodName
+ * @param array $args
*/
function callMethod( $loadBalancer, $methodName, $args ) {
call_user_func_array( array( $loadBalancer, $methodName ), $args );
@@ -162,39 +191,62 @@ abstract class LBFactory {
function commitMasterChanges() {
$this->forEachLBCallMethod( 'commitMasterChanges' );
}
+
+ /**
+ * Rollback changes on all master connections
+ * @since 1.23
+ */
+ function rollbackMasterChanges() {
+ $this->forEachLBCallMethod( 'rollbackMasterChanges' );
+ }
+
+ /**
+ * Detemine if any master connection has pending changes.
+ * @since 1.23
+ * @return bool
+ */
+ function hasMasterChanges() {
+ $ret = false;
+ $this->forEachLB( function ( $lb ) use ( &$ret ) {
+ $ret = $ret || $lb->hasMasterChanges();
+ } );
+ return $ret;
+ }
}
/**
* A simple single-master LBFactory that gets its configuration from the b/c globals
*/
-class LBFactory_Simple extends LBFactory {
+class LBFactorySimple extends LBFactory {
+ /** @var LoadBalancer */
+ protected $mainLB;
- /**
- * @var LoadBalancer
- */
- var $mainLB;
- var $extLBs = array();
+ /** @var LoadBalancer[] */
+ protected $extLBs = array();
- # Chronology protector
- var $chronProt;
+ /** @var ChronologyProtector */
+ protected $chronProt;
function __construct( $conf ) {
$this->chronProt = new ChronologyProtector;
}
/**
- * @param $wiki
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function newMainLB( $wiki = false ) {
- global $wgDBservers, $wgMasterWaitTimeout;
+ global $wgDBservers;
if ( $wgDBservers ) {
$servers = $wgDBservers;
} else {
global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
global $wgDBssl, $wgDBcompress;
- $flags = ( $wgDebugDumpSql ? DBO_DEBUG : 0 ) | DBO_DEFAULT;
+ $flags = DBO_DEFAULT;
+ if ( $wgDebugDumpSql ) {
+ $flags |= DBO_DEBUG;
+ }
if ( $wgDBssl ) {
$flags |= DBO_SSL;
}
@@ -210,17 +262,16 @@ class LBFactory_Simple extends LBFactory {
'type' => $wgDBtype,
'load' => 1,
'flags' => $flags
- ));
+ ) );
}
return new LoadBalancer( array(
'servers' => $servers,
- 'masterWaitTimeout' => $wgMasterWaitTimeout
- ));
+ ) );
}
/**
- * @param $wiki
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function getMainLB( $wiki = false ) {
@@ -229,13 +280,14 @@ class LBFactory_Simple extends LBFactory {
$this->mainLB->parentInfo( array( 'id' => 'main' ) );
$this->chronProt->initLB( $this->mainLB );
}
+
return $this->mainLB;
}
/**
* @throws MWException
- * @param $cluster
- * @param $wiki
+ * @param string $cluster
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function newExternalLB( $cluster, $wiki = false ) {
@@ -243,14 +295,15 @@ class LBFactory_Simple extends LBFactory {
if ( !isset( $wgExternalServers[$cluster] ) ) {
throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
+
return new LoadBalancer( array(
'servers' => $wgExternalServers[$cluster]
- ));
+ ) );
}
/**
- * @param $cluster
- * @param $wiki
+ * @param string $cluster
+ * @param bool|string $wiki
* @return array
*/
function &getExternalLB( $cluster, $wiki = false ) {
@@ -259,6 +312,7 @@ class LBFactory_Simple extends LBFactory {
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
$this->chronProt->initLB( $this->extLBs[$cluster] );
}
+
return $this->extLBs[$cluster];
}
@@ -266,8 +320,9 @@ class LBFactory_Simple extends LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
- * @param $callback
- * @param $params array
+ *
+ * @param callable $callback
+ * @param array $params
*/
function forEachLB( $callback, $params = array() ) {
if ( isset( $this->mainLB ) ) {
@@ -296,7 +351,7 @@ class LBFactory_Simple extends LBFactory {
* Call LBFactory::disableBackend() to start using this, and
* LBFactory::enableBackend() to return to normal behavior
*/
-class LBFactory_Fake extends LBFactory {
+class LBFactoryFake extends LBFactory {
function __construct( $conf ) {
}
@@ -325,6 +380,7 @@ class LBFactory_Fake extends LBFactory {
*/
class DBAccessError extends MWException {
function __construct() {
- parent::__construct( "Mediawiki tried to access the database via wfGetDB(). This is not allowed." );
+ parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
+ "This is not allowed." );
}
}
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactoryMulti.php
index 3043946a..bac96523 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactoryMulti.php
@@ -26,49 +26,130 @@
* Ignores the old configuration globals
*
* Configuration:
- * sectionsByDB A map of database names to section names
+ * sectionsByDB A map of database names to section names.
*
- * sectionLoads A 2-d map. For each section, gives a map of server names to load ratios.
- * For example: array( 'section1' => array( 'db1' => 100, 'db2' => 100 ) )
+ * sectionLoads A 2-d map. For each section, gives a map of server names to
+ * load ratios. For example:
+ * array(
+ * 'section1' => array(
+ * 'db1' => 100,
+ * 'db2' => 100
+ * )
+ * )
*
- * serverTemplate A server info associative array as documented for $wgDBservers. The host,
- * hostName and load entries will be overridden.
+ * serverTemplate A server info associative array as documented for $wgDBservers.
+ * The host, hostName and load entries will be overridden.
*
- * groupLoadsBySection A 3-d map giving server load ratios for each section and group. For example:
- * array( 'section1' => array( 'group1' => array( 'db1' => 100, 'db2' => 100 ) ) )
+ * groupLoadsBySection A 3-d map giving server load ratios for each section and group.
+ * For example:
+ * array(
+ * 'section1' => array(
+ * 'group1' => array(
+ * 'db1' => 100,
+ * 'db2' => 100
+ * )
+ * )
+ * )
*
* groupLoadsByDB A 3-d map giving server load ratios by DB name.
*
* hostsByName A map of hostname to IP address.
*
- * externalLoads A map of external storage cluster name to server load map
+ * externalLoads A map of external storage cluster name to server load map.
*
- * externalTemplateOverrides A set of server info keys overriding serverTemplate for external storage
+ * externalTemplateOverrides A set of server info keys overriding serverTemplate for external
+ * storage.
*
- * templateOverridesByServer A 2-d map overriding serverTemplate and externalTemplateOverrides on a
- * server-by-server basis. Applies to both core and external storage.
+ * templateOverridesByServer A 2-d map overriding serverTemplate and
+ * externalTemplateOverrides on a server-by-server basis. Applies
+ * to both core and external storage.
*
- * templateOverridesByCluster A 2-d map overriding the server info by external storage cluster
+ * templateOverridesByCluster A 2-d map overriding the server info by external storage cluster.
*
* masterTemplateOverrides An override array for all master servers.
*
- * readOnlyBySection A map of section name to read-only message. Missing or false for read/write.
+ * readOnlyBySection A map of section name to read-only message.
+ * Missing or false for read/write.
*
* @ingroup Database
*/
-class LBFactory_Multi extends LBFactory {
+class LBFactoryMulti extends LBFactory {
// Required settings
- var $sectionsByDB, $sectionLoads, $serverTemplate;
+
+ /** @var array A map of database names to section names */
+ protected $sectionsByDB;
+
+ /**
+ * @var array A 2-d map. For each section, gives a map of server names to
+ * load ratios
+ */
+ protected $sectionLoads;
+
+ /**
+ * @var array A server info associative array as documented for
+ * $wgDBservers. The host, hostName and load entries will be
+ * overridden
+ */
+ protected $serverTemplate;
+
// Optional settings
- var $groupLoadsBySection = array(), $groupLoadsByDB = array(), $hostsByName = array();
- var $externalLoads = array(), $externalTemplateOverrides, $templateOverridesByServer;
- var $templateOverridesByCluster, $masterTemplateOverrides, $readOnlyBySection = array();
+
+ /** @var array A 3-d map giving server load ratios for each section and group */
+ protected $groupLoadsBySection = array();
+
+ /** @var array A 3-d map giving server load ratios by DB name */
+ protected $groupLoadsByDB = array();
+
+ /** @var array A map of hostname to IP address */
+ protected $hostsByName = array();
+
+ /** @var array A map of external storage cluster name to server load map */
+ protected $externalLoads = array();
+
+ /**
+ * @var array A set of server info keys overriding serverTemplate for
+ * external storage
+ */
+ protected $externalTemplateOverrides;
+
+ /**
+ * @var array A 2-d map overriding serverTemplate and
+ * externalTemplateOverrides on a server-by-server basis. Applies to both
+ * core and external storage
+ */
+ protected $templateOverridesByServer;
+
+ /** @var array A 2-d map overriding the server info by external storage cluster */
+ protected $templateOverridesByCluster;
+
+ /** @var array An override array for all master servers */
+ protected $masterTemplateOverrides;
+
+ /**
+ * @var array|bool A map of section name to read-only message. Missing or
+ * false for read/write
+ */
+ protected $readOnlyBySection = array();
+
// Other stuff
- var $conf, $mainLBs = array(), $extLBs = array();
- var $lastWiki, $lastSection;
+
+ /** @var array Load balancer factory configuration */
+ protected $conf;
+
+ /** @var LoadBalancer[] */
+ protected $mainLBs = array();
+
+ /** @var LoadBalancer[] */
+ protected $extLBs = array();
+
+ /** @var string */
+ protected $lastWiki;
+
+ /** @var string */
+ protected $lastSection;
/**
- * @param $conf array
+ * @param array $conf
* @throws MWException
*/
function __construct( $conf ) {
@@ -102,7 +183,7 @@ class LBFactory_Multi extends LBFactory {
}
/**
- * @param $wiki bool|string
+ * @param bool|string $wiki
* @return string
*/
function getSectionForWiki( $wiki = false ) {
@@ -117,11 +198,12 @@ class LBFactory_Multi extends LBFactory {
}
$this->lastSection = $section;
$this->lastWiki = $wiki;
+
return $section;
}
/**
- * @param $wiki bool|string
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function newMainLB( $wiki = false ) {
@@ -131,14 +213,20 @@ class LBFactory_Multi extends LBFactory {
if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
$groupLoads = $this->groupLoadsByDB[$dbName];
}
+
if ( isset( $this->groupLoadsBySection[$section] ) ) {
$groupLoads = array_merge_recursive( $groupLoads, $this->groupLoadsBySection[$section] );
}
- return $this->newLoadBalancer( $this->serverTemplate, $this->sectionLoads[$section], $groupLoads );
+
+ return $this->newLoadBalancer(
+ $this->serverTemplate,
+ $this->sectionLoads[$section],
+ $groupLoads
+ );
}
/**
- * @param $wiki bool|string
+ * @param bool|string $wiki
* @return LoadBalancer
*/
function getMainLB( $wiki = false ) {
@@ -149,12 +237,13 @@ class LBFactory_Multi extends LBFactory {
$this->chronProt->initLB( $lb );
$this->mainLBs[$section] = $lb;
}
+
return $this->mainLBs[$section];
}
/**
* @param string $cluster
- * @param bool $wiki
+ * @param bool|string $wiki
* @throws MWException
* @return LoadBalancer
*/
@@ -169,12 +258,13 @@ class LBFactory_Multi extends LBFactory {
if ( isset( $this->templateOverridesByCluster[$cluster] ) ) {
$template = $this->templateOverridesByCluster[$cluster] + $template;
}
+
return $this->newLoadBalancer( $template, $this->externalLoads[$cluster], array() );
}
/**
- * @param $cluster
- * @param $wiki
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
function &getExternalLB( $cluster, $wiki = false ) {
@@ -183,33 +273,33 @@ class LBFactory_Multi extends LBFactory {
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
$this->chronProt->initLB( $this->extLBs[$cluster] );
}
+
return $this->extLBs[$cluster];
}
/**
* Make a new load balancer object based on template and load array
*
- * @param $template
- * @param $loads array
- * @param $groupLoads
+ * @param array $template
+ * @param array $loads
+ * @param array $groupLoads
* @return LoadBalancer
*/
function newLoadBalancer( $template, $loads, $groupLoads ) {
- global $wgMasterWaitTimeout;
$servers = $this->makeServerArray( $template, $loads, $groupLoads );
$lb = new LoadBalancer( array(
'servers' => $servers,
- 'masterWaitTimeout' => $wgMasterWaitTimeout
- ));
+ ) );
+
return $lb;
}
/**
* Make a server array as expected by LoadBalancer::__construct, using a template and load array
*
- * @param $template
- * @param $loads array
- * @param $groupLoads
+ * @param array $template
+ * @param array $loads
+ * @param array $groupLoads
* @return array
*/
function makeServerArray( $template, $loads, $groupLoads ) {
@@ -245,12 +335,13 @@ class LBFactory_Multi extends LBFactory {
$serverInfo['load'] = $load;
$servers[] = $serverInfo;
}
+
return $servers;
}
/**
* Take a group load array indexed by group then server, and reindex it by server then group
- * @param $groupLoads
+ * @param array $groupLoads
* @return array
*/
function reindexGroupLoads( $groupLoads ) {
@@ -260,17 +351,19 @@ class LBFactory_Multi extends LBFactory {
$reindexed[$server][$group] = $load;
}
}
+
return $reindexed;
}
/**
* Get the database name and prefix based on the wiki ID
- * @param $wiki bool
+ * @param bool|string $wiki
* @return array
*/
function getDBNameAndPrefix( $wiki = false ) {
if ( $wiki === false ) {
global $wgDBname, $wgDBprefix;
+
return array( $wgDBname, $wgDBprefix );
} else {
return wfSplitWikiID( $wiki );
@@ -281,8 +374,8 @@ class LBFactory_Multi extends LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
- * @param $callback
- * @param $params array
+ * @param callable $callback
+ * @param array $params
*/
function forEachLB( $callback, $params = array() ) {
foreach ( $this->mainLBs as $lb ) {
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactorySingle.php
index 7dca06d7..3a4d829b 100644
--- a/includes/db/LBFactory_Single.php
+++ b/includes/db/LBFactorySingle.php
@@ -24,7 +24,8 @@
/**
* An LBFactory class that always returns a single database object.
*/
-class LBFactory_Single extends LBFactory {
+class LBFactorySingle extends LBFactory {
+ /** @var LoadBalancerSingle */
protected $lb;
/**
@@ -32,50 +33,46 @@ class LBFactory_Single extends LBFactory {
* - connection: The DatabaseBase connection object
*/
function __construct( $conf ) {
- $this->lb = new LoadBalancer_Single( $conf );
+ $this->lb = new LoadBalancerSingle( $conf );
}
/**
- * @param $wiki bool|string
- *
- * @return LoadBalancer_Single
+ * @param bool|string $wiki
+ * @return LoadBalancerSingle
*/
function newMainLB( $wiki = false ) {
return $this->lb;
}
/**
- * @param $wiki bool|string
- *
- * @return LoadBalancer_Single
+ * @param bool|string $wiki
+ * @return LoadBalancerSingle
*/
function getMainLB( $wiki = false ) {
return $this->lb;
}
/**
- * @param $cluster
- * @param $wiki bool|string
- *
- * @return LoadBalancer_Single
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
+ * @return LoadBalancerSingle
*/
function newExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
/**
- * @param $cluster
- * @param $wiki bool|string
- *
- * @return LoadBalancer_Single
+ * @param string $cluster External storage cluster, or false for core
+ * @param bool|string $wiki Wiki ID, or false for the current wiki
+ * @return LoadBalancerSingle
*/
function &getExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
/**
- * @param $callback string|array
- * @param $params array
+ * @param string|callable $callback
+ * @param array $params
*/
function forEachLB( $callback, $params = array() ) {
call_user_func_array( $callback, array_merge( array( $this->lb ), $params ) );
@@ -83,17 +80,14 @@ class LBFactory_Single extends LBFactory {
}
/**
- * Helper class for LBFactory_Single.
+ * Helper class for LBFactorySingle.
*/
-class LoadBalancer_Single extends LoadBalancer {
-
- /**
- * @var DatabaseBase
- */
- var $db;
+class LoadBalancerSingle extends LoadBalancer {
+ /** @var DatabaseBase */
+ protected $db;
/**
- * @param $params array
+ * @param array $params
*/
function __construct( $params ) {
$this->db = $params['connection'];
@@ -107,8 +101,8 @@ class LoadBalancer_Single extends LoadBalancer {
/**
*
- * @param $server string
- * @param $dbNameOverride bool
+ * @param string $server
+ * @param bool $dbNameOverride
*
* @return DatabaseBase
*/
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index 857109db..e517a025 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -28,19 +28,43 @@
* @ingroup Database
*/
class LoadBalancer {
- private $mServers, $mConns, $mLoads, $mGroupLoads;
+ /** @var array Map of (server index => server config array) */
+ private $mServers;
+ /** @var array Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */
+ private $mConns;
+ /** @var array Map of (server index => weight) */
+ private $mLoads;
+ /** @var array Map of (group => server index => weight) */
+ private $mGroupLoads;
+ /** @var bool Whether to disregard slave lag as a factor in slave selection */
+ private $mAllowLagged;
+ /** @var integer Seconds to spend waiting on slave lag to resolve */
+ private $mWaitTimeout;
+
+ /** @var array LBFactory information */
+ private $mParentInfo;
+ /** @var string The LoadMonitor subclass name */
+ private $mLoadMonitorClass;
+ /** @var LoadMonitor */
+ private $mLoadMonitor;
+
+ /** @var bool|DatabaseBase Database connection that caused a problem */
private $mErrorConnection;
- private $mReadIndex, $mAllowLagged;
- private $mWaitForPos, $mWaitTimeout;
- private $mLaggedSlaveMode, $mLastError = 'Unknown error';
- private $mParentInfo, $mLagTimes;
- private $mLoadMonitorClass, $mLoadMonitor;
+ /** @var integer The generic (not query grouped) slave index (of $mServers) */
+ private $mReadIndex;
+ /** @var bool|DBMasterPos False if not set */
+ private $mWaitForPos;
+ /** @var bool Whether the generic reader fell back to a lagged slave */
+ private $mLaggedSlaveMode;
+ /** @var string The last DB selection or connection error */
+ private $mLastError = 'Unknown error';
+ /** @var array Process cache of LoadMonitor::getLagTimes() */
+ private $mLagTimes;
/**
- * @param array $params with keys:
- * servers Required. Array of server info structures.
- * masterWaitTimeout Replication lag wait timeout
- * loadMonitor Name of a class used to fetch server lag and load.
+ * @param array $params Array with keys:
+ * servers Required. Array of server info structures.
+ * loadMonitor Name of a class used to fetch server lag and load.
* @throws MWException
*/
function __construct( $params ) {
@@ -48,12 +72,7 @@ class LoadBalancer {
throw new MWException( __CLASS__ . ': missing servers parameter' );
}
$this->mServers = $params['servers'];
-
- if ( isset( $params['waitTimeout'] ) ) {
- $this->mWaitTimeout = $params['waitTimeout'];
- } else {
- $this->mWaitTimeout = 10;
- }
+ $this->mWaitTimeout = 10;
$this->mReadIndex = -1;
$this->mWriteIndex = -1;
@@ -72,9 +91,9 @@ class LoadBalancer {
} else {
$master = reset( $params['servers'] );
if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
- $this->mLoadMonitorClass = 'LoadMonitor_MySQL';
+ $this->mLoadMonitorClass = 'LoadMonitorMySQL';
} else {
- $this->mLoadMonitorClass = 'LoadMonitor_Null';
+ $this->mLoadMonitorClass = 'LoadMonitorNull';
}
}
@@ -101,13 +120,14 @@ class LoadBalancer {
$class = $this->mLoadMonitorClass;
$this->mLoadMonitor = new $class( $this );
}
+
return $this->mLoadMonitor;
}
/**
* Get or set arbitrary data used by the parent object, usually an LBFactory
- * @param $x
- * @return Mixed
+ * @param mixed $x
+ * @return mixed
*/
function parentInfo( $x = null ) {
return wfSetVar( $this->mParentInfo, $x );
@@ -119,8 +139,7 @@ class LoadBalancer {
*
* @deprecated since 1.21, use ArrayUtils::pickRandom()
*
- * @param $weights array
- *
+ * @param array $weights
* @return bool|int|string
*/
function pickRandom( $weights ) {
@@ -128,8 +147,8 @@ class LoadBalancer {
}
/**
- * @param $loads array
- * @param $wiki bool
+ * @param array $loads
+ * @param bool|string $wiki Wiki to get non-lagged for
* @return bool|int|string
*/
function getRandomNonLagged( $loads, $wiki = false ) {
@@ -138,10 +157,10 @@ class LoadBalancer {
foreach ( $lags as $i => $lag ) {
if ( $i != 0 ) {
if ( $lag === false ) {
- wfDebugLog( 'replication', "Server #$i is not replicating\n" );
+ wfDebugLog( 'replication', "Server #$i is not replicating" );
unset( $loads[$i] );
} elseif ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
- wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)\n" );
+ wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)" );
unset( $loads[$i] );
}
}
@@ -176,13 +195,13 @@ class LoadBalancer {
* always return a consistent index during a given invocation
*
* Side effect: opens connections to databases
- * @param $group bool
- * @param $wiki bool
+ * @param bool|string $group
+ * @param bool|string $wiki
* @throws MWException
* @return bool|int|string
*/
function getReaderIndex( $group = false, $wiki = false ) {
- global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype;
+ global $wgReadOnly, $wgDBtype;
# @todo FIXME: For now, only go through all this for mysql databases
if ( $wgDBtype != 'mysql' ) {
@@ -192,17 +211,12 @@ class LoadBalancer {
if ( count( $this->mServers ) == 1 ) {
# Skip the load balancing if there's only one server
return 0;
- } elseif ( $group === false and $this->mReadIndex >= 0 ) {
+ } elseif ( $group === false && $this->mReadIndex >= 0 ) {
# Shortcut if generic reader exists already
return $this->mReadIndex;
}
- wfProfileIn( __METHOD__ );
-
- $totalElapsed = 0;
-
- # convert from seconds to microseconds
- $timeout = $wgDBClusterTimeout * 1e6;
+ $section = new ProfileSection( __METHOD__ );
# Find the relevant load array
if ( $group !== false ) {
@@ -211,15 +225,14 @@ class LoadBalancer {
} else {
# No loads for this group, return false and the caller can use some other group
wfDebug( __METHOD__ . ": no loads for group $group\n" );
- wfProfileOut( __METHOD__ );
+
return false;
}
} else {
$nonErrorLoads = $this->mLoads;
}
- if ( !$nonErrorLoads ) {
- wfProfileOut( __METHOD__ );
+ if ( !count( $nonErrorLoads ) ) {
throw new MWException( "Empty server array given to LoadBalancer" );
}
@@ -228,92 +241,60 @@ class LoadBalancer {
$laggedSlaveMode = false;
+ # No server found yet
+ $i = false;
# First try quickly looking through the available servers for a server that
# meets our criteria
- do {
- $totalThreadsConnected = 0;
- $overloadedServers = 0;
- $currentLoads = $nonErrorLoads;
- while ( count( $currentLoads ) ) {
- if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
+ $currentLoads = $nonErrorLoads;
+ while ( count( $currentLoads ) ) {
+ if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
+ $i = ArrayUtils::pickRandom( $currentLoads );
+ } else {
+ $i = $this->getRandomNonLagged( $currentLoads, $wiki );
+ if ( $i === false && count( $currentLoads ) != 0 ) {
+ # All slaves lagged. Switch to read-only mode
+ wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode" );
+ $wgReadOnly = 'The database has been automatically locked ' .
+ 'while the slave database servers catch up to the master';
$i = ArrayUtils::pickRandom( $currentLoads );
- } else {
- $i = $this->getRandomNonLagged( $currentLoads, $wiki );
- if ( $i === false && count( $currentLoads ) != 0 ) {
- # All slaves lagged. Switch to read-only mode
- wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode\n" );
- $wgReadOnly = 'The database has been automatically locked ' .
- 'while the slave database servers catch up to the master';
- $i = ArrayUtils::pickRandom( $currentLoads );
- $laggedSlaveMode = true;
- }
- }
-
- if ( $i === false ) {
- # pickRandom() returned false
- # This is permanent and means the configuration or the load monitor
- # wants us to return false.
- wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false\n" );
- wfProfileOut( __METHOD__ );
- return false;
+ $laggedSlaveMode = true;
}
+ }
- wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: {$this->mServers[$i]['host']}...\n" );
- $conn = $this->openConnection( $i, $wiki );
-
- if ( !$conn ) {
- wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki\n" );
- unset( $nonErrorLoads[$i] );
- unset( $currentLoads[$i] );
- continue;
- }
-
- // Perform post-connection backoff
- $threshold = isset( $this->mServers[$i]['max threads'] )
- ? $this->mServers[$i]['max threads'] : false;
- $backoff = $this->getLoadMonitor()->postConnectionBackoff( $conn, $threshold );
-
- // Decrement reference counter, we are finished with this connection.
- // It will be incremented for the caller later.
- if ( $wiki !== false ) {
- $this->reuseConnection( $conn );
- }
+ if ( $i === false ) {
+ # pickRandom() returned false
+ # This is permanent and means the configuration or the load monitor
+ # wants us to return false.
+ wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false" );
- if ( $backoff ) {
- # Post-connection overload, don't use this server for now
- $totalThreadsConnected += $backoff;
- $overloadedServers++;
- unset( $currentLoads[$i] );
- } else {
- # Return this server
- break 2;
- }
+ return false;
}
- # No server found yet
- $i = false;
+ wfDebugLog( 'connect', __METHOD__ .
+ ": Using reader #$i: {$this->mServers[$i]['host']}..." );
- # If all servers were down, quit now
- if ( !count( $nonErrorLoads ) ) {
- wfDebugLog( 'connect', "All servers down\n" );
- break;
+ $conn = $this->openConnection( $i, $wiki );
+ if ( !$conn ) {
+ wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki" );
+ unset( $nonErrorLoads[$i] );
+ unset( $currentLoads[$i] );
+ $i = false;
+ continue;
}
- # Some servers must have been overloaded
- if ( $overloadedServers == 0 ) {
- throw new MWException( __METHOD__ . ": unexpectedly found no overloaded servers" );
+ // Decrement reference counter, we are finished with this connection.
+ // It will be incremented for the caller later.
+ if ( $wiki !== false ) {
+ $this->reuseConnection( $conn );
}
- # Back off for a while
- # Scale the sleep time by the number of connected threads, to produce a
- # roughly constant global poll rate
- $avgThreads = $totalThreadsConnected / $overloadedServers;
- $totalElapsed += $this->sleep( $wgDBAvgStatusPoll * $avgThreads );
- } while ( $totalElapsed < $timeout );
-
- if ( $totalElapsed >= $timeout ) {
- wfDebugLog( 'connect', "All servers busy\n" );
- $this->mErrorConnection = false;
- $this->mLastError = 'All servers busy';
+
+ # Return this server
+ break;
+ }
+
+ # If all servers were down, quit now
+ if ( !count( $nonErrorLoads ) ) {
+ wfDebugLog( 'connect', "All servers down" );
}
if ( $i !== false ) {
@@ -324,17 +305,17 @@ class LoadBalancer {
$this->mServers[$i]['slave pos'] = $conn->getSlavePos();
}
}
- if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $i !== false ) {
+ if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group !== false ) {
$this->mReadIndex = $i;
}
}
- wfProfileOut( __METHOD__ );
+
return $i;
}
/**
* Wait for a specified number of microseconds, and return the period waited
- * @param $t int
+ * @param int $t
* @return int
*/
function sleep( $t ) {
@@ -342,6 +323,7 @@ class LoadBalancer {
wfDebug( __METHOD__ . ": waiting $t us\n" );
usleep( $t );
wfProfileOut( __METHOD__ );
+
return $t;
}
@@ -349,7 +331,7 @@ class LoadBalancer {
* Set the master wait position
* If a DB_SLAVE connection has been opened already, waits
* Otherwise sets a variable telling it to wait if such a connection is opened
- * @param $pos DBMasterPos
+ * @param DBMasterPos $pos
*/
public function waitFor( $pos ) {
wfProfileIn( __METHOD__ );
@@ -367,22 +349,31 @@ class LoadBalancer {
/**
* Set the master wait position and wait for ALL slaves to catch up to it
- * @param $pos DBMasterPos
+ * @param DBMasterPos $pos
+ * @param int $timeout Max seconds to wait; default is mWaitTimeout
+ * @return bool Success (able to connect and no timeouts reached)
*/
- public function waitForAll( $pos ) {
+ public function waitForAll( $pos, $timeout = null ) {
wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
- for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
- $this->doWait( $i, true );
+ $serverCount = count( $this->mServers );
+
+ $ok = true;
+ for ( $i = 1; $i < $serverCount; $i++ ) {
+ if ( $this->mLoads[$i] > 0 ) {
+ $ok = $this->doWait( $i, true, $timeout ) && $ok;
+ }
}
wfProfileOut( __METHOD__ );
+
+ return $ok;
}
/**
* Get any open connection to a given server index, local or foreign
* Returns false if there is no connection open
*
- * @param $i int
+ * @param int $i
* @return DatabaseBase|bool False on failure
*/
function getAnyOpenConnection( $i ) {
@@ -391,40 +382,47 @@ class LoadBalancer {
return reset( $conns[$i] );
}
}
+
return false;
}
/**
* Wait for a given slave to catch up to the master pos stored in $this
- * @param $index
- * @param $open bool
+ * @param int $index Server index
+ * @param bool $open Check the server even if a new connection has to be made
+ * @param int $timeout Max seconds to wait; default is mWaitTimeout
* @return bool
*/
- protected function doWait( $index, $open = false ) {
+ protected function doWait( $index, $open = false, $timeout = null ) {
# Find a connection to wait on
$conn = $this->getAnyOpenConnection( $index );
if ( !$conn ) {
if ( !$open ) {
wfDebug( __METHOD__ . ": no connection open\n" );
+
return false;
} else {
$conn = $this->openConnection( $index, '' );
if ( !$conn ) {
wfDebug( __METHOD__ . ": failed to open connection\n" );
+
return false;
}
}
}
wfDebug( __METHOD__ . ": Waiting for slave #$index to catch up...\n" );
- $result = $conn->masterPosWait( $this->mWaitForPos, $this->mWaitTimeout );
+ $timeout = $timeout ?: $this->mWaitTimeout;
+ $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
if ( $result == -1 || is_null( $result ) ) {
# Timed out waiting for slave, use master instead
wfDebug( __METHOD__ . ": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" );
+
return false;
} else {
wfDebug( __METHOD__ . ": Done\n" );
+
return true;
}
}
@@ -433,8 +431,8 @@ class LoadBalancer {
* Get a connection by index
* This is the main entry point for this class.
*
- * @param $i Integer: server index
- * @param array $groups query groups
+ * @param int $i Server index
+ * @param array $groups Query groups
* @param bool|string $wiki Wiki ID
*
* @throws MWException
@@ -443,12 +441,10 @@ class LoadBalancer {
public function &getConnection( $i, $groups = array(), $wiki = false ) {
wfProfileIn( __METHOD__ );
- if ( $i == DB_LAST ) {
+ if ( $i === null || $i === false ) {
wfProfileOut( __METHOD__ );
- throw new MWException( 'Attempt to call ' . __METHOD__ . ' with deprecated server index DB_LAST' );
- } elseif ( $i === null || $i === false ) {
- wfProfileOut( __METHOD__ );
- throw new MWException( 'Attempt to call ' . __METHOD__ . ' with invalid server index' );
+ throw new MWException( 'Attempt to call ' . __METHOD__ .
+ ' with invalid server index' );
}
if ( $wiki === wfWikiID() ) {
@@ -485,6 +481,7 @@ class LoadBalancer {
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
wfProfileOut( __METHOD__ );
+
return $this->reportConnectionError();
}
}
@@ -493,10 +490,12 @@ class LoadBalancer {
$conn = $this->openConnection( $i, $wiki );
if ( !$conn ) {
wfProfileOut( __METHOD__ );
+
return $this->reportConnectionError();
}
wfProfileOut( __METHOD__ );
+
return $conn;
}
@@ -511,15 +510,9 @@ class LoadBalancer {
public function reuseConnection( $conn ) {
$serverIndex = $conn->getLBInfo( 'serverIndex' );
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
- $dbName = $conn->getDBname();
- $prefix = $conn->tablePrefix();
- if ( strval( $prefix ) !== '' ) {
- $wiki = "$dbName-$prefix";
- } else {
- $wiki = $dbName;
- }
if ( $serverIndex === null || $refCount === null ) {
wfDebug( __METHOD__ . ": this connection was not opened as a foreign connection\n" );
+
/**
* This can happen in code like:
* foreach ( $dbs as $db ) {
@@ -530,10 +523,20 @@ class LoadBalancer {
* When a connection to the local DB is opened in this way, reuseConnection()
* should be ignored
*/
+
return;
}
+
+ $dbName = $conn->getDBname();
+ $prefix = $conn->tablePrefix();
+ if ( strval( $prefix ) !== '' ) {
+ $wiki = "$dbName-$prefix";
+ } else {
+ $wiki = $dbName;
+ }
if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) {
- throw new MWException( __METHOD__ . ": connection not found, has the connection been freed already?" );
+ throw new MWException( __METHOD__ . ": connection not found, has " .
+ "the connection been freed already?" );
}
$conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
if ( $refCount <= 0 ) {
@@ -552,9 +555,9 @@ class LoadBalancer {
*
* @see LoadBalancer::getConnection() for parameter information
*
- * @param integer $db
+ * @param int $db
* @param mixed $groups
- * @param string $wiki
+ * @param bool|string $wiki
* @return DBConnRef
*/
public function getConnectionRef( $db, $groups = array(), $wiki = false ) {
@@ -568,9 +571,9 @@ class LoadBalancer {
*
* @see LoadBalancer::getConnection() for parameter information
*
- * @param integer $db
+ * @param int $db
* @param mixed $groups
- * @param string $wiki
+ * @param bool|string $wiki
* @return DBConnRef
*/
public function getLazyConnectionRef( $db, $groups = array(), $wiki = false ) {
@@ -585,8 +588,8 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param $i Integer server index
- * @param string $wiki wiki ID to open
+ * @param int $i Server index
+ * @param bool|string $wiki Wiki ID to open
* @return DatabaseBase
*
* @access private
@@ -596,6 +599,7 @@ class LoadBalancer {
if ( $wiki !== false ) {
$conn = $this->openForeignConnection( $i, $wiki );
wfProfileOut( __METHOD__ );
+
return $conn;
}
if ( isset( $this->mConns['local'][$i][0] ) ) {
@@ -614,6 +618,7 @@ class LoadBalancer {
}
}
wfProfileOut( __METHOD__ );
+
return $conn;
}
@@ -631,8 +636,8 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param $i Integer: server index
- * @param string $wiki wiki ID to open
+ * @param int $i Server index
+ * @param string $wiki Wiki ID to open
* @return DatabaseBase
*/
function openForeignConnection( $i, $wiki ) {
@@ -688,13 +693,14 @@ class LoadBalancer {
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
}
wfProfileOut( __METHOD__ );
+
return $conn;
}
/**
* Test if the specified index represents an open connection
*
- * @param $index Integer: server index
+ * @param int $index Server index
* @access private
* @return bool
*/
@@ -702,6 +708,7 @@ class LoadBalancer {
if ( !is_integer( $index ) ) {
return false;
}
+
return (bool)$this->getAnyOpenConnection( $index );
}
@@ -710,8 +717,8 @@ class LoadBalancer {
* Returns a Database object whether or not the connection was successful.
* @access private
*
- * @param $server
- * @param $dbNameOverride bool
+ * @param array $server
+ * @param bool $dbNameOverride
* @throws MWException
* @return DatabaseBase
*/
@@ -741,6 +748,7 @@ class LoadBalancer {
if ( isset( $server['fakeMaster'] ) ) {
$db->setFakeMaster( true );
}
+
return $db;
}
@@ -753,15 +761,16 @@ class LoadBalancer {
if ( !is_object( $conn ) ) {
// No last connection, probably due to all servers being too busy
- wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}\n" );
+ wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}" );
// If all servers were busy, mLastError will contain something sensible
throw new DBConnectionError( null, $this->mLastError );
} else {
$server = $conn->getProperty( 'mServer' );
- wfLogDBError( "Connection error: {$this->mLastError} ({$server})\n" );
+ wfLogDBError( "Connection error: {$this->mLastError} ({$server})" );
$conn->reportConnectionError( "{$this->mLastError} ({$server})" ); // throws DBConnectionError
}
+
return false; /* not reached */
}
@@ -775,7 +784,7 @@ class LoadBalancer {
/**
* Returns true if the specified index is a valid server index
*
- * @param $i
+ * @param string $i
* @return bool
*/
function haveIndex( $i ) {
@@ -785,7 +794,7 @@ class LoadBalancer {
/**
* Returns true if the specified index is valid and has non-zero load
*
- * @param $i
+ * @param string $i
* @return bool
*/
function isNonZeroLoad( $i ) {
@@ -804,7 +813,7 @@ class LoadBalancer {
/**
* Get the host name or IP address of the server with the specified index
* Prefer a readable name if available.
- * @param $i
+ * @param string $i
* @return string
*/
function getServerName( $i ) {
@@ -819,8 +828,8 @@ class LoadBalancer {
/**
* Return the server info structure for a given index, or false if the index is invalid.
- * @param $i
- * @return bool
+ * @param int $i
+ * @return array|bool
*/
function getServerInfo( $i ) {
if ( isset( $this->mServers[$i] ) ) {
@@ -831,9 +840,10 @@ class LoadBalancer {
}
/**
- * Sets the server info structure for the given index. Entry at index $i is created if it doesn't exist
- * @param $i
- * @param $serverInfo
+ * Sets the server info structure for the given index. Entry at index $i
+ * is created if it doesn't exist
+ * @param int $i
+ * @param array $serverInfo
*/
function setServerInfo( $i, $serverInfo ) {
$this->mServers[$i] = $serverInfo;
@@ -848,17 +858,21 @@ class LoadBalancer {
# master (however unlikely that may be), then we can fetch the position from the slave.
$masterConn = $this->getAnyOpenConnection( 0 );
if ( !$masterConn ) {
- for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
+ $serverCount = count( $this->mServers );
+ for ( $i = 1; $i < $serverCount; $i++ ) {
$conn = $this->getAnyOpenConnection( $i );
if ( $conn ) {
wfDebug( "Master pos fetched from slave\n" );
+
return $conn->getSlavePos();
}
}
} else {
wfDebug( "Master pos fetched from master\n" );
+
return $masterConn->getMasterPos();
}
+
return false;
}
@@ -868,6 +882,7 @@ class LoadBalancer {
function closeAll() {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
+ /** @var DatabaseBase $conn */
foreach ( $conns3 as $conn ) {
$conn->close();
}
@@ -881,21 +896,10 @@ class LoadBalancer {
}
/**
- * Deprecated function, typo in function name
- *
- * @deprecated in 1.18
- * @param $conn
- */
- function closeConnecton( $conn ) {
- wfDeprecated( __METHOD__, '1.18' );
- $this->closeConnection( $conn );
- }
-
- /**
* Close a connection
* Using this function makes sure the LoadBalancer knows the connection is closed.
* If you use $conn->close() directly, the load balancer won't update its state.
- * @param $conn DatabaseBase
+ * @param DatabaseBase $conn
*/
function closeConnection( $conn ) {
$done = false;
@@ -922,6 +926,7 @@ class LoadBalancer {
function commitAll() {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
+ /** @var DatabaseBase[] $conns3 */
foreach ( $conns3 as $conn ) {
if ( $conn->trxLevel() ) {
$conn->commit( __METHOD__, 'flush' );
@@ -941,6 +946,7 @@ class LoadBalancer {
if ( empty( $conns2[$masterIndex] ) ) {
continue;
}
+ /** @var DatabaseBase $conn */
foreach ( $conns2[$masterIndex] as $conn ) {
if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
$conn->commit( __METHOD__, 'flush' );
@@ -950,8 +956,59 @@ class LoadBalancer {
}
/**
- * @param $value null
- * @return Mixed
+ * Issue ROLLBACK only on master, only if queries were done on connection
+ * @since 1.23
+ */
+ function rollbackMasterChanges() {
+ // Always 0, but who knows.. :)
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $conns2 ) {
+ if ( empty( $conns2[$masterIndex] ) ) {
+ continue;
+ }
+ /** @var DatabaseBase $conn */
+ foreach ( $conns2[$masterIndex] as $conn ) {
+ if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+ $conn->rollback( __METHOD__, 'flush' );
+ }
+ }
+ }
+ }
+
+ /**
+ * @return bool Whether a master connection is already open
+ * @since 1.24
+ */
+ function hasMasterConnection() {
+ return $this->isOpen( $this->getWriterIndex() );
+ }
+
+ /**
+ * Determine if there are any pending changes that need to be rolled back
+ * or committed.
+ * @since 1.23
+ * @return bool
+ */
+ function hasMasterChanges() {
+ // Always 0, but who knows.. :)
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $conns2 ) {
+ if ( empty( $conns2[$masterIndex] ) ) {
+ continue;
+ }
+ /** @var DatabaseBase $conn */
+ foreach ( $conns2[$masterIndex] as $conn ) {
+ if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param mixed $value
+ * @return mixed
*/
function waitTimeout( $value = null ) {
return wfSetVar( $this->mWaitTimeout, $value );
@@ -966,7 +1023,7 @@ class LoadBalancer {
/**
* Disables/enables lag checks
- * @param $mode null
+ * @param null|bool $mode
* @return bool
*/
function allowLagged( $mode = null ) {
@@ -974,6 +1031,7 @@ class LoadBalancer {
return $this->mAllowLagged;
}
$this->mAllowLagged = $mode;
+
return $this->mAllowLagged;
}
@@ -984,6 +1042,7 @@ class LoadBalancer {
$success = true;
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
+ /** @var DatabaseBase[] $conns3 */
foreach ( $conns3 as $conn ) {
if ( !$conn->ping() ) {
$success = false;
@@ -991,12 +1050,13 @@ class LoadBalancer {
}
}
}
+
return $success;
}
/**
* Call a function with each open connection object
- * @param $callback
+ * @param callable $callback
* @param array $params
*/
function forEachOpenConnection( $callback, $params = array() ) {
@@ -1016,16 +1076,29 @@ class LoadBalancer {
* May attempt to open connections to slaves on the default DB. If there is
* no lag, the maximum lag will be reported as -1.
*
- * @param string $wiki Wiki ID, or false for the default database
- *
+ * @param bool|string $wiki Wiki ID, or false for the default database
* @return array ( host, max lag, index of max lagged host )
*/
function getMaxLag( $wiki = false ) {
$maxLag = -1;
$host = '';
$maxIndex = 0;
- if ( $this->getServerCount() > 1 ) { // no replication = no lag
+
+ if ( $this->getServerCount() <= 1 ) { // no replication = no lag
+ return array( $host, $maxLag, $maxIndex );
+ }
+
+ // Try to get the max lag info from the server cache
+ $key = 'loadbalancer:maxlag:cluster:' . $this->mServers[0]['host'];
+ $cache = ObjectCache::newAccelerator( array(), 'hash' );
+ $maxLagInfo = $cache->get( $key ); // (host, lag, index)
+
+ // Fallback to connecting to each slave and getting the lag
+ if ( !$maxLagInfo ) {
foreach ( $this->mServers as $i => $conn ) {
+ if ( $i == $this->getWriterIndex() ) {
+ continue; // nothing to check
+ }
$conn = false;
if ( $wiki === false ) {
$conn = $this->getAnyOpenConnection( $i );
@@ -1043,16 +1116,18 @@ class LoadBalancer {
$maxIndex = $i;
}
}
+ $maxLagInfo = array( $host, $maxLag, $maxIndex );
+ $cache->set( $key, $maxLagInfo, 5 );
}
- return array( $host, $maxLag, $maxIndex );
+
+ return $maxLagInfo;
}
/**
* Get lag time for each server
* Results are cached for a short time in memcached, and indefinitely in the process cache
*
- * @param $wiki
- *
+ * @param string|bool $wiki
* @return array
*/
function getLagTimes( $wiki = false ) {
@@ -1068,6 +1143,7 @@ class LoadBalancer {
$this->mLagTimes = $this->getLoadMonitor()->getLagTimes(
array_keys( $this->mServers ), $wiki );
}
+
return $this->mLagTimes;
}
@@ -1082,8 +1158,7 @@ class LoadBalancer {
* function instead of Database::getLag() avoids a fatal error in this
* case on many installations.
*
- * @param $conn DatabaseBase
- *
+ * @param DatabaseBase $conn
* @return int
*/
function safeGetLag( $conn ) {
@@ -1112,14 +1187,16 @@ class LoadBalancer {
class DBConnRef implements IDatabase {
/** @var LoadBalancer */
protected $lb;
+
/** @var DatabaseBase|null */
protected $conn;
- /** @var Array|null */
+
+ /** @var array|null */
protected $params;
/**
- * @param $lb LoadBalancer
- * @param $conn DatabaseBase|array Connection or (server index, group, wiki ID) array
+ * @param LoadBalancer $lb
+ * @param DatabaseBase|array $conn Connection or (server index, group, wiki ID) array
*/
public function __construct( LoadBalancer $lb, $conn ) {
$this->lb = $lb;
@@ -1135,6 +1212,7 @@ class DBConnRef implements IDatabase {
list( $db, $groups, $wiki ) = $this->params;
$this->conn = $this->lb->getConnection( $db, $groups, $wiki );
}
+
return call_user_func_array( array( $this->conn, $name ), $arguments );
}
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 519e2dfd..7281485b 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -32,61 +32,35 @@ interface LoadMonitor {
*
* @param LoadBalancer $parent
*/
- function __construct( $parent );
+ public function __construct( $parent );
/**
* Perform pre-connection load ratio adjustment.
- * @param $loads array
- * @param string $group the selected query group
- * @param $wiki String
+ * @param array $loads
+ * @param string|bool $group The selected query group. Default: false
+ * @param string|bool $wiki Default: false
*/
- function scaleLoads( &$loads, $group = false, $wiki = false );
-
- /**
- * Perform post-connection backoff.
- *
- * If the connection is in overload, this should return a backoff factor
- * which will be used to control polling time. The number of threads
- * connected is a good measure.
- *
- * If there is no overload, zero can be returned.
- *
- * A threshold thread count is given, the concrete class may compare this
- * to the running thread count. The threshold may be false, which indicates
- * that the sysadmin has not configured this feature.
- *
- * @param $conn DatabaseBase
- * @param $threshold Float
- */
- function postConnectionBackoff( $conn, $threshold );
+ public function scaleLoads( &$loads, $group = false, $wiki = false );
/**
* Return an estimate of replication lag for each server
*
- * @param $serverIndexes
- * @param $wiki
+ * @param array $serverIndexes
+ * @param string $wiki
*
* @return array
*/
- function getLagTimes( $serverIndexes, $wiki );
+ public function getLagTimes( $serverIndexes, $wiki );
}
-class LoadMonitor_Null implements LoadMonitor {
- function __construct( $parent ) {
- }
-
- function scaleLoads( &$loads, $group = false, $wiki = false ) {
+class LoadMonitorNull implements LoadMonitor {
+ public function __construct( $parent ) {
}
- function postConnectionBackoff( $conn, $threshold ) {
+ public function scaleLoads( &$loads, $group = false, $wiki = false ) {
}
- /**
- * @param $serverIndexes
- * @param $wiki
- * @return array
- */
- function getLagTimes( $serverIndexes, $wiki ) {
+ public function getLagTimes( $serverIndexes, $wiki ) {
return array_fill_keys( $serverIndexes, 0 );
}
}
@@ -97,58 +71,44 @@ class LoadMonitor_Null implements LoadMonitor {
*
* @ingroup Database
*/
-class LoadMonitor_MySQL implements LoadMonitor {
+class LoadMonitorMySQL implements LoadMonitor {
+ /** @var LoadBalancer */
+ public $parent;
+ /** @var BagOStuff */
+ protected $cache;
- /**
- * @var LoadBalancer
- */
- var $parent;
+ public function __construct( $parent ) {
+ global $wgMemc;
- /**
- * @param LoadBalancer $parent
- */
- function __construct( $parent ) {
$this->parent = $parent;
+ $this->cache = $wgMemc ?: wfGetMainCache();
}
- /**
- * @param $loads
- * @param $group bool
- * @param $wiki bool
- */
- function scaleLoads( &$loads, $group = false, $wiki = false ) {
+ public function scaleLoads( &$loads, $group = false, $wiki = false ) {
}
- /**
- * @param $serverIndexes
- * @param $wiki
- * @return array
- */
- function getLagTimes( $serverIndexes, $wiki ) {
+ public function getLagTimes( $serverIndexes, $wiki ) {
if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
// Single server only, just return zero without caching
return array( 0 => 0 );
}
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
+
$expiry = 5;
$requestRate = 10;
- global $wgMemc;
- if ( empty( $wgMemc ) ) {
- $wgMemc = wfGetMainCache();
- }
-
+ $cache = $this->cache;
$masterName = $this->parent->getServerName( 0 );
$memcKey = wfMemcKey( 'lag_times', $masterName );
- $times = $wgMemc->get( $memcKey );
+ $times = $cache->get( $memcKey );
if ( is_array( $times ) ) {
# Randomly recache with probability rising over $expiry
$elapsed = time() - $times['timestamp'];
$chance = max( 0, ( $expiry - $elapsed ) * $requestRate );
if ( mt_rand( 0, $chance ) != 0 ) {
unset( $times['timestamp'] ); // hide from caller
- wfProfileOut( __METHOD__ );
+
return $times;
}
wfIncrStats( 'lag_cache_miss_expired' );
@@ -157,15 +117,15 @@ class LoadMonitor_MySQL implements LoadMonitor {
}
# Cache key missing or expired
- if ( $wgMemc->add( "$memcKey:lock", 1, 10 ) ) {
+ if ( $cache->add( "$memcKey:lock", 1, 10 ) ) {
# Let this process alone update the cache value
- $unlocker = new ScopedCallback( function() use ( $wgMemc, $memcKey ) {
- $wgMemc->delete( $memcKey );
+ $unlocker = new ScopedCallback( function () use ( $cache, $memcKey ) {
+ $cache->delete( $memcKey );
} );
} elseif ( is_array( $times ) ) {
# Could not acquire lock but an old cache exists, so use it
unset( $times['timestamp'] ); // hide from caller
- wfProfileOut( __METHOD__ );
+
return $times;
}
@@ -177,34 +137,19 @@ class LoadMonitor_MySQL implements LoadMonitor {
$times[$i] = $conn->getLag();
} elseif ( false !== ( $conn = $this->parent->openConnection( $i, $wiki ) ) ) {
$times[$i] = $conn->getLag();
+ // Close the connection to avoid sleeper connections piling up.
+ // Note that the caller will pick one of these DBs and reconnect,
+ // which is slightly inefficient, but this only matters for the lag
+ // time cache miss cache, which is far less common that cache hits.
+ $this->parent->closeConnection( $conn );
}
}
# Add a timestamp key so we know when it was cached
$times['timestamp'] = time();
- $wgMemc->set( $memcKey, $times, $expiry + 10 );
+ $cache->set( $memcKey, $times, $expiry + 10 );
unset( $times['timestamp'] ); // hide from caller
- wfProfileOut( __METHOD__ );
return $times;
}
-
- /**
- * @param $conn DatabaseBase
- * @param $threshold
- * @return int
- */
- function postConnectionBackoff( $conn, $threshold ) {
- if ( !$threshold ) {
- return 0;
- }
- $status = $conn->getMysqlStatus( "Thread%" );
- if ( $status['Threads_running'] > $threshold ) {
- $server = $conn->getProperty( 'mServer' );
- wfLogDBError( "LB backoff from $server - Threads_running = {$status['Threads_running']}\n" );
- return $status['Threads_connected'];
- } else {
- return 0;
- }
- }
}
diff --git a/includes/db/ORMIterator.php b/includes/db/ORMIterator.php
index 077eab0f..e8104b6f 100644
--- a/includes/db/ORMIterator.php
+++ b/includes/db/ORMIterator.php
@@ -27,5 +27,4 @@
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
interface ORMIterator extends Iterator {
-
}
diff --git a/includes/db/ORMResult.php b/includes/db/ORMResult.php
index 160033c4..327d20d9 100644
--- a/includes/db/ORMResult.php
+++ b/includes/db/ORMResult.php
@@ -30,14 +30,13 @@
*/
class ORMResult implements ORMIterator {
-
/**
* @var ResultWrapper
*/
protected $res;
/**
- * @var integer
+ * @var int
*/
protected $key;
@@ -63,7 +62,7 @@ class ORMResult implements ORMIterator {
}
/**
- * @param $row
+ * @param bool|object $row
*/
protected function setCurrent( $row ) {
if ( $row === false ) {
@@ -74,14 +73,14 @@ class ORMResult implements ORMIterator {
}
/**
- * @return integer
+ * @return int
*/
public function count() {
return $this->res->numRows();
}
/**
- * @return boolean
+ * @return bool
*/
public function isEmpty() {
return $this->res->numRows() === 0;
@@ -95,7 +94,7 @@ class ORMResult implements ORMIterator {
}
/**
- * @return integer
+ * @return int
*/
public function key() {
return $this->key;
@@ -114,10 +113,9 @@ class ORMResult implements ORMIterator {
}
/**
- * @return boolean
+ * @return bool
*/
public function valid() {
return $this->current !== false;
}
-
}
diff --git a/includes/db/ORMRow.php b/includes/db/ORMRow.php
index 5ce3794d..b0bade33 100644
--- a/includes/db/ORMRow.php
+++ b/includes/db/ORMRow.php
@@ -32,7 +32,6 @@
*/
class ORMRow implements IORMRow {
-
/**
* The fields of the object.
* field name (w/o prefix) => value
@@ -79,7 +78,7 @@ class ORMRow implements IORMRow {
*
* @param IORMTable|null $table Deprecated since 1.22
* @param array|null $fields
- * @param boolean $loadDefaults Deprecated since 1.22
+ * @param bool $loadDefaults Deprecated since 1.22
*/
public function __construct( IORMTable $table = null, $fields = null, $loadDefaults = false ) {
$this->table = $table;
@@ -102,8 +101,8 @@ class ORMRow implements IORMRow {
* @deprecated since 1.22
*
* @param array|null $fields
- * @param boolean $override
- * @param boolean $skipLoaded
+ * @param bool $override
+ * @param bool $skipLoaded
*
* @return bool Success indicator
*/
@@ -130,8 +129,10 @@ class ORMRow implements IORMRow {
if ( $result !== false ) {
$this->setFields( $this->table->getFieldsFromDBResult( $result ), $override );
+
return true;
}
+
return false;
}
@@ -144,7 +145,7 @@ class ORMRow implements IORMRow {
* @since 1.20
*
* @param string $name Field name
- * @param $default mixed: Default value to return when none is found
+ * @param mixed $default Default value to return when none is found
* (default: null)
*
* @throws MWException
@@ -166,7 +167,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param $name string
+ * @param string $name
*
* @return mixed
*/
@@ -194,7 +195,7 @@ class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @return integer|null
+ * @return int|null
*/
public function getId() {
return $this->getField( 'id' );
@@ -205,7 +206,7 @@ class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @param integer|null $id
+ * @param int|null $id
*/
public function setId( $id ) {
$this->setField( 'id', $id );
@@ -218,7 +219,7 @@ class ORMRow implements IORMRow {
*
* @param string $name
*
- * @return boolean
+ * @return bool
*/
public function hasField( $name ) {
return array_key_exists( $name, $this->fields );
@@ -229,11 +230,10 @@ class ORMRow implements IORMRow {
*
* @since 1.20
*
- * @return boolean
+ * @return bool
*/
public function hasIdField() {
- return $this->hasField( 'id' )
- && !is_null( $this->getField( 'id' ) );
+ return $this->hasField( 'id' ) && !is_null( $this->getField( 'id' ) );
}
/**
@@ -252,7 +252,7 @@ class ORMRow implements IORMRow {
$value = $this->fields[$name];
// Skip null id fields so that the DBMS can set the default.
- if ( $name === 'id' && is_null ( $value ) ) {
+ if ( $name === 'id' && is_null( $value ) ) {
continue;
}
@@ -278,7 +278,7 @@ class ORMRow implements IORMRow {
* @since 1.20
*
* @param array $fields The fields to set
- * @param boolean $override Override already set fields with the provided values?
+ * @param bool $override Override already set fields with the provided values?
*/
public function setFields( array $fields, $override = true ) {
foreach ( $fields as $name => $value ) {
@@ -295,7 +295,7 @@ class ORMRow implements IORMRow {
* @since 1.20
*
* @param null|array $fields
- * @param boolean $incNullId
+ * @param bool $incNullId
*
* @return array
*/
@@ -328,7 +328,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $override
+ * @param bool $override
*/
public function loadDefaults( $override = true ) {
$this->setFields( $this->table->getDefaults(), $override );
@@ -343,7 +343,7 @@ class ORMRow implements IORMRow {
*
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function save( $functionName = null ) {
if ( $this->hasIdField() ) {
@@ -361,7 +361,7 @@ class ORMRow implements IORMRow {
*
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
protected function saveExisting( $functionName = null ) {
$dbw = $this->table->getWriteDbConnection();
@@ -400,7 +400,7 @@ class ORMRow implements IORMRow {
* @param string|null $functionName
* @param array|null $options
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
protected function insert( $functionName = null, array $options = null ) {
$dbw = $this->table->getWriteDbConnection();
@@ -430,7 +430,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22, use IORMTable->removeRow
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function remove() {
$this->beforeRemove();
@@ -456,8 +456,9 @@ class ORMRow implements IORMRow {
/**
* Before removal of an object happens, @see beforeRemove gets called.
- * This method loads the fields of which the names have been returned by this one (or all fields if null is returned).
- * This allows for loading info needed after removal to get rid of linked data and the like.
+ * This method loads the fields of which the names have been returned by
+ * this one (or all fields if null is returned). This allows for loading
+ * info needed after removal to get rid of linked data and the like.
*
* @since 1.20
*
@@ -523,9 +524,9 @@ class ORMRow implements IORMRow {
* @deprecated since 1.22, use IORMTable->addToField
*
* @param string $field
- * @param integer $amount
+ * @param int $amount
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function addToField( $field, $amount ) {
return $this->table->addToField( $this->getUpdateConditions(), $field, $amount );
@@ -552,7 +553,6 @@ class ORMRow implements IORMRow {
* @param array|string|null $summaryFields
*/
public function loadSummaryFields( $summaryFields = null ) {
-
}
/**
@@ -561,7 +561,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $update
+ * @param bool $update
*/
public function setUpdateSummaries( $update ) {
$this->updateSummaries = $update;
@@ -573,7 +573,7 @@ class ORMRow implements IORMRow {
* @since 1.20
* @deprecated since 1.22
*
- * @param boolean $summaryMode
+ * @param bool $summaryMode
*/
public function setSummaryMode( $summaryMode ) {
$this->inSummaryMode = $summaryMode;
@@ -590,5 +590,4 @@ class ORMRow implements IORMRow {
public function getTable() {
return $this->table;
}
-
}
diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php
index 5f6723b9..2f898b75 100644
--- a/includes/db/ORMTable.php
+++ b/includes/db/ORMTable.php
@@ -29,7 +29,6 @@
*/
class ORMTable extends DBAccessBase implements IORMTable {
-
/**
* Cache for instances, used by the singleton method.
*
@@ -81,7 +80,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @var integer DB_ enum
+ * @var int DB_ enum
*/
protected $readDb = DB_SLAVE;
@@ -96,7 +95,9 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param string|null $rowClass
* @param string $fieldPrefix
*/
- public function __construct( $tableName = '', array $fields = array(), array $defaults = array(), $rowClass = null, $fieldPrefix = '' ) {
+ public function __construct( $tableName = '', array $fields = array(),
+ array $defaults = array(), $rowClass = null, $fieldPrefix = ''
+ ) {
$this->tableName = $tableName;
$this->fields = $fields;
$this->defaults = $defaults;
@@ -201,8 +202,10 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @return ORMResult
*/
public function select( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
$res = $this->rawSelect( $fields, $conditions, $options, $functionName );
+
return new ORMResult( $this, $res );
}
@@ -217,11 +220,12 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return array of row objects
- * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode).
+ * @return array Array of row objects
+ * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode).
*/
public function selectObjects( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
$result = $this->selectFields( $fields, $conditions, $options, false, $functionName );
$objects = array();
@@ -239,19 +243,19 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @since 1.20
*
* @param null|string|array $fields
- * @param array $conditions
- * @param array $options
- * @param null|string $functionName
- *
+ * @param array $conditions
+ * @param array $options
+ * @param null|string $functionName
* @return ResultWrapper
- * @throws DBQueryError if the quey failed (even if the database was in ignoreErrors mode).
+ * @throws DBQueryError If the query failed (even if the database was in
+ * ignoreErrors mode).
*/
public function rawSelect( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
if ( is_null( $fields ) ) {
$fields = array_keys( $this->getFields() );
- }
- else {
+ } else {
$fields = (array)$fields;
}
@@ -307,13 +311,14 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array|string|null $fields
* @param array $conditions
* @param array $options
- * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param bool $collapse Set to false to always return each result row as associative array.
* @param string|null $functionName
*
- * @return array of array
+ * @return array Array of array
*/
public function selectFields( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null ) {
+ array $options = array(), $collapse = true, $functionName = null
+ ) {
$objects = array();
$result = $this->rawSelect( $fields, $conditions, $options, $functionName );
@@ -325,8 +330,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
if ( $collapse ) {
if ( count( $fields ) === 1 ) {
$objects = array_map( 'array_shift', $objects );
- }
- elseif ( count( $fields ) === 2 ) {
+ } elseif ( count( $fields ) === 2 ) {
$o = array();
foreach ( $objects as $object ) {
@@ -354,7 +358,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @return IORMRow|bool False on failure
*/
public function selectRow( $fields = null, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
$options['LIMIT'] = 1;
$objects = $this->select( $fields, $conditions, $options, $functionName );
@@ -373,10 +378,11 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $options
* @param string|null $functionName
*
- * @return ResultWrapper
+ * @return stdClass
*/
public function rawSelectRow( array $fields, array $conditions = array(),
- array $options = array(), $functionName = null ) {
+ array $options = array(), $functionName = null
+ ) {
$dbr = $this->getReadDbConnection();
$result = $dbr->selectRow(
@@ -388,6 +394,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
);
$this->releaseConnection( $dbr );
+
return $result;
}
@@ -403,13 +410,14 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array|string|null $fields
* @param array $conditions
* @param array $options
- * @param boolean $collapse Set to false to always return each result row as associative array.
+ * @param bool $collapse Set to false to always return each result row as associative array.
* @param string|null $functionName
*
* @return mixed|array|bool False on failure
*/
public function selectFieldsRow( $fields = null, array $conditions = array(),
- array $options = array(), $collapse = true, $functionName = null ) {
+ array $options = array(), $collapse = true, $functionName = null
+ ) {
$options['LIMIT'] = 1;
$objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName );
@@ -425,7 +433,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @param array $conditions
*
- * @return boolean
+ * @return bool
*/
public function has( array $conditions = array() ) {
return $this->selectRow( array( 'id' ), $conditions ) !== false;
@@ -436,7 +444,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.21
*
- * @return boolean
+ * @return bool
*/
public function exists() {
$dbr = $this->getReadDbConnection();
@@ -458,7 +466,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $conditions
* @param array $options
*
- * @return integer
+ * @return int
*/
public function count( array $conditions = array(), array $options = array() ) {
$res = $this->rawSelectRow(
@@ -479,7 +487,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $conditions
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function delete( array $conditions, $functionName = null ) {
$dbw = $this->getWriteDbConnection();
@@ -491,6 +499,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
) !== false; // DatabaseBase::delete does not always return true for success as documented...
$this->releaseConnection( $dbw );
+
return $result;
}
@@ -499,8 +508,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @param boolean $requireParams
- * @param boolean $setDefaults
+ * @param bool $requireParams
+ * @param bool $setDefaults
*
* @return array
*/
@@ -535,7 +544,9 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
if ( $setDefaults && $hasDefault ) {
- $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field];
+ $default = is_array( $defaults[$field] )
+ ? implode( '|', $defaults[$field] )
+ : $defaults[$field];
$params[$field][ApiBase::PARAM_DFLT] = $default;
}
}
@@ -561,16 +572,17 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @return integer DB_ enum
+ * @return int DB_ enum
*/
public function getReadDb() {
return $this->readDb;
}
/**
- * Set the database ID to use for read operations, use DB_XXX constants or an index to the load balancer setup.
+ * Set the database ID to use for read operations, use DB_XXX constants or
+ * an index to the load balancer setup.
*
- * @param integer $db
+ * @param int $db
*
* @since 1.20
*/
@@ -583,7 +595,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
+ * @return string|bool The target wiki, in a form that LBFactory understands
+ * (or false if the local wiki is used)
*/
public function getTargetWiki() {
return $this->wiki;
@@ -592,7 +605,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
/**
* Set the ID of the any foreign wiki to use as a target for database operations
*
- * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
+ * @param string|bool $wiki The target wiki, in a form that LBFactory
+ * understands (or false if the local wiki shall be used)
*
* @since 1.20
*/
@@ -634,13 +648,15 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @see LoadBalancer::reuseConnection
*
- * @param DatabaseBase $db the database
+ * @param DatabaseBase $db
*
* @since 1.20
*/
+ // @codingStandardsIgnoreStart Suppress "useless method overriding" sniffer warning
public function releaseConnection( DatabaseBase $db ) {
parent::releaseConnection( $db ); // just make it public
}
+ // @codingStandardsIgnoreEnd
/**
* Update the records matching the provided conditions by
@@ -652,7 +668,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $values
* @param array $conditions
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function update( array $values, array $conditions = array() ) {
$dbw = $this->getWriteDbConnection();
@@ -665,6 +681,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
) !== false; // DatabaseBase::update does not always return true for success as documented...
$this->releaseConnection( $dbw );
+
return $result;
}
@@ -711,8 +728,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
if ( is_array( $value ) ) {
$field = $value[0];
$value = $value[1];
- }
- else {
+ } else {
$value = explode( ' ', $value, 2 );
$value[0] = $this->getPrefixedField( $value[0] );
$prefixedValues[] = implode( ' ', $value );
@@ -732,7 +748,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @param array|string $fields
+ * @param array $fields
*
* @return array
*/
@@ -809,7 +825,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @since 1.20
*
* @param stdClass $result
- *
+ * @throws MWException
* @return array
*/
public function getFieldsFromDBResult( stdClass $result ) {
@@ -872,7 +888,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
/**
* @see ORMTable::newRowFromFromDBResult
*
- * @deprecated use newRowFromDBResult instead
+ * @deprecated since 1.20 use newRowFromDBResult instead
* @since 1.20
*
* @param stdClass $result
@@ -899,11 +915,11 @@ class ORMTable extends DBAccessBase implements IORMTable {
/**
* @see ORMTable::newRow
*
- * @deprecated use newRow instead
+ * @deprecated since 1.20 use newRow instead
* @since 1.20
*
* @param array $data
- * @param boolean $loadDefaults
+ * @param bool $loadDefaults
*
* @return IORMRow
*/
@@ -917,7 +933,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @since 1.20
*
* @param array $fields
- * @param boolean $loadDefaults
+ * @param bool $loadDefaults
*
* @return IORMRow
*/
@@ -945,7 +961,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @param string $name
*
- * @return boolean
+ * @return bool
*/
public function canHaveField( $name ) {
return array_key_exists( $name, $this->getFields() );
@@ -959,7 +975,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param IORMRow $row The row to save
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function updateRow( IORMRow $row, $functionName = null ) {
$dbw = $this->getWriteDbConnection();
@@ -986,7 +1002,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param string|null $functionName
* @param array|null $options
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function insertRow( IORMRow $row, $functionName = null, array $options = null ) {
$dbw = $this->getWriteDbConnection();
@@ -1052,7 +1068,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param IORMRow $row
* @param string|null $functionName
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function removeRow( IORMRow $row, $functionName = null ) {
$success = $this->delete(
@@ -1071,9 +1087,9 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @param array $conditions
* @param string $field
- * @param integer $amount
+ * @param int $amount
*
- * @return boolean Success indicator
+ * @return bool Success indicator
* @throws MWException
*/
public function addToField( array $conditions, $field, $amount ) {
@@ -1103,5 +1119,4 @@ class ORMTable extends DBAccessBase implements IORMTable {
return $success;
}
-
}
diff --git a/includes/debug/Debug.php b/includes/debug/MWDebug.php
index 6e9ccc41..c2f22233 100644
--- a/includes/debug/Debug.php
+++ b/includes/debug/MWDebug.php
@@ -31,7 +31,6 @@
* @since 1.19
*/
class MWDebug {
-
/**
* Log lines
*
@@ -83,7 +82,7 @@ class MWDebug {
* enabled.
*
* @since 1.19
- * @param $out OutputPage
+ * @param OutputPage $out
*/
public static function addModules( OutputPage $out ) {
if ( self::$enabled ) {
@@ -97,7 +96,7 @@ class MWDebug {
* @todo Add support for passing objects
*
* @since 1.19
- * @param $str string
+ * @param string $str
*/
public static function log( $str ) {
if ( !self::$enabled ) {
@@ -133,12 +132,12 @@ class MWDebug {
* Adds a warning entry to the log
*
* @since 1.19
- * @param $msg string
- * @param $callerOffset int
- * @param $level int A PHP error level. See sendWarning()
- * @param $log string: 'production' will always trigger a php error, 'auto'
- * will trigger an error if $wgDevelopmentWarnings is true, and 'debug'
- * will only write to the debug log(s).
+ * @param string $msg
+ * @param int $callerOffset
+ * @param int $level A PHP error level. See sendMessage()
+ * @param string $log 'production' will always trigger a php error, 'auto'
+ * will trigger an error if $wgDevelopmentWarnings is true, and 'debug'
+ * will only write to the debug log(s).
*
* @return mixed
*/
@@ -155,7 +154,7 @@ class MWDebug {
$callerDescription = self::getCallerDescription( $callerOffset );
- self::sendWarning( $msg, $callerDescription, $level );
+ self::sendMessage( $msg, $callerDescription, 'warning', $level );
if ( self::$enabled ) {
self::$log[] = array(
@@ -179,13 +178,14 @@ class MWDebug {
* @param string $function Function that is deprecated.
* @param string|bool $version Version in which the function was deprecated.
* @param string|bool $component Component to which the function belongs.
- * If false, it is assumbed the function is in MediaWiki core.
- * @param $callerOffset integer: How far up the callstack is the original
+ * If false, it is assumbed the function is in MediaWiki core.
+ * @param int $callerOffset How far up the callstack is the original
* caller. 2 = function that called the function that called
* MWDebug::deprecated() (Added in 1.20).
- * @return mixed
*/
- public static function deprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
+ public static function deprecated( $function, $version = false,
+ $component = false, $callerOffset = 2
+ ) {
$callerDescription = self::getCallerDescription( $callerOffset );
$callerFunc = $callerDescription['func'];
@@ -227,7 +227,12 @@ class MWDebug {
if ( $sendToLog ) {
global $wgDevelopmentWarnings; // we could have a more specific $wgDeprecationWarnings setting.
- self::sendWarning( $msg, $callerDescription, $wgDevelopmentWarnings ? E_USER_DEPRECATED : false );
+ self::sendMessage(
+ $msg,
+ $callerDescription,
+ 'deprecated',
+ $wgDevelopmentWarnings ? E_USER_DEPRECATED : false
+ );
}
if ( self::$enabled ) {
@@ -247,9 +252,9 @@ class MWDebug {
/**
* Get an array describing the calling function at a specified offset.
*
- * @param $callerOffset integer: How far up the callstack is the original
+ * @param int $callerOffset How far up the callstack is the original
* caller. 0 = function that called getCallerDescription()
- * @return array with two keys: 'file' and 'func'
+ * @return array Array with two keys: 'file' and 'func'
*/
private static function getCallerDescription( $callerOffset ) {
$callers = wfDebugBacktrace();
@@ -282,21 +287,22 @@ class MWDebug {
}
/**
- * Send a warning to the debug log and optionally also trigger a PHP
+ * Send a message to the debug log and optionally also trigger a PHP
* error, depending on the $level argument.
*
- * @param $msg string Message to send
- * @param $caller array caller description get from getCallerDescription()
- * @param $level int|bool error level to use; set to false to not trigger an error
+ * @param string $msg Message to send
+ * @param array $caller Caller description get from getCallerDescription()
+ * @param string $group Log group on which to send the message
+ * @param int|bool $level Error level to use; set to false to not trigger an error
*/
- private static function sendWarning( $msg, $caller, $level ) {
+ private static function sendMessage( $msg, $caller, $group, $level ) {
$msg .= ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']';
if ( $level !== false ) {
trigger_error( $msg, $level );
}
- wfDebug( "$msg\n" );
+ wfDebugLog( $group, $msg, 'log' );
}
/**
@@ -304,7 +310,7 @@ class MWDebug {
* Do NOT use this method, use MWDebug::log or wfDebug()
*
* @since 1.19
- * @param $str string
+ * @param string $str
*/
public static function debugMsg( $str ) {
global $wgDebugComments, $wgShowDebug;
@@ -318,9 +324,9 @@ class MWDebug {
* Begins profiling on a database query
*
* @since 1.19
- * @param $sql string
- * @param $function string
- * @param $isMaster bool
+ * @param string $sql
+ * @param string $function
+ * @param bool $isMaster
* @return int ID number of the query to pass to queryTime or -1 if the
* debugger is disabled
*/
@@ -329,6 +335,28 @@ class MWDebug {
return -1;
}
+ // Replace invalid UTF-8 chars with a square UTF-8 character
+ // This prevents json_encode from erroring out due to binary SQL data
+ $sql = preg_replace(
+ '/(
+ [\xC0-\xC1] # Invalid UTF-8 Bytes
+ | [\xF5-\xFF] # Invalid UTF-8 Bytes
+ | \xE0[\x80-\x9F] # Overlong encoding of prior code point
+ | \xF0[\x80-\x8F] # Overlong encoding of prior code point
+ | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
+ | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
+ | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
+ | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
+ | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]
+ |[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
+ | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
+ | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
+ | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
+ )/x',
+ '■',
+ $sql
+ );
+
self::$query[] = array(
'sql' => $sql,
'function' => $function,
@@ -344,7 +372,7 @@ class MWDebug {
* Calculates how long a query took.
*
* @since 1.19
- * @param $id int
+ * @param int $id
*/
public static function queryTime( $id ) {
if ( $id === -1 || !self::$enabled ) {
@@ -358,7 +386,7 @@ class MWDebug {
/**
* Returns a list of files included, along with their size
*
- * @param $context IContextSource
+ * @param IContextSource $context
* @return array
*/
protected static function getFilesIncluded( IContextSource $context ) {
@@ -379,7 +407,7 @@ class MWDebug {
* Returns the HTML to add to the page for the toolbar
*
* @since 1.19
- * @param $context IContextSource
+ * @param IContextSource $context
* @return string
*/
public static function getDebugHTML( IContextSource $context ) {
@@ -445,10 +473,15 @@ class MWDebug {
$display = "\xc2\xa0";
}
- if ( !$ident && $diff < 0 && substr( $display, 0, 9 ) != 'Entering ' && substr( $display, 0, 8 ) != 'Exiting ' ) {
+ if ( !$ident
+ && $diff < 0
+ && substr( $display, 0, 9 ) != 'Entering '
+ && substr( $display, 0, 8 ) != 'Exiting '
+ ) {
$ident = $curIdent;
$diff = 0;
- $display = '<span style="background:yellow;">' . nl2br( htmlspecialchars( $display ) ) . '</span>';
+ $display = '<span style="background:yellow;">' .
+ nl2br( htmlspecialchars( $display ) ) . '</span>';
} else {
$display = nl2br( htmlspecialchars( $display ) );
}
@@ -460,7 +493,7 @@ class MWDebug {
} else {
$ret .= str_repeat( "<ul><li>\n", $diff );
}
- $ret .= "<tt>$display</tt>\n";
+ $ret .= "<code>$display</code>\n";
$curIdent = $ident;
}
@@ -473,8 +506,8 @@ class MWDebug {
/**
* Append the debug info to given ApiResult
*
- * @param $context IContextSource
- * @param $result ApiResult
+ * @param IContextSource $context
+ * @param ApiResult $result
*/
public static function appendDebugInfoToApiResult( IContextSource $context, ApiResult $result ) {
if ( !self::$enabled ) {
@@ -501,13 +534,14 @@ class MWDebug {
$result->setIndexedTagName( $debugInfo['debugLog'], 'msg' );
$result->setIndexedTagName( $debugInfo['queries'], 'query' );
$result->setIndexedTagName( $debugInfo['includes'], 'queries' );
+ $result->setIndexedTagName( $debugInfo['profile'], 'function' );
$result->addValue( null, 'debuginfo', $debugInfo );
}
/**
* Returns the HTML to add to the page for the toolbar
*
- * @param $context IContextSource
+ * @param IContextSource $context
* @return array
*/
public static function getDebugInfo( IContextSource $context ) {
@@ -517,9 +551,17 @@ class MWDebug {
global $wgVersion, $wgRequestTime;
$request = $context->getRequest();
+
+ // HHVM's reported memory usage from memory_get_peak_usage()
+ // is not useful when passing false, but we continue passing
+ // false for consistency of historical data in zend.
+ // see: https://github.com/facebook/hhvm/issues/2257#issuecomment-39362246
+ $realMemoryUsage = wfIsHHVM();
+
return array(
'mwVersion' => $wgVersion,
- 'phpVersion' => PHP_VERSION,
+ 'phpEngine' => wfIsHHVM() ? 'HHVM' : 'PHP',
+ 'phpVersion' => wfIsHHVM() ? HHVM_VERSION : PHP_VERSION,
'gitRevision' => GitInfo::headSHA1(),
'gitBranch' => GitInfo::currentBranch(),
'gitViewUrl' => GitInfo::headViewUrl(),
@@ -533,9 +575,10 @@ class MWDebug {
'headers' => $request->getAllHeaders(),
'params' => $request->getValues(),
),
- 'memory' => $context->getLanguage()->formatSize( memory_get_usage() ),
- 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage() ),
+ 'memory' => $context->getLanguage()->formatSize( memory_get_usage( $realMemoryUsage ) ),
+ 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage( $realMemoryUsage ) ),
'includes' => self::getFilesIncluded( $context ),
+ 'profile' => Profiler::instance()->getRawData(),
);
}
}
diff --git a/includes/CallableUpdate.php b/includes/deferred/CallableUpdate.php
index 6eb55413..808626d0 100644
--- a/includes/CallableUpdate.php
+++ b/includes/deferred/CallableUpdate.php
@@ -4,14 +4,14 @@
* Deferrable Update for closure/callback
*/
class MWCallableUpdate implements DeferrableUpdate {
-
/**
- * @var closure/callabck
+ * @var Closure|callable
*/
private $callback;
/**
* @param callable $callback
+ * @throws MWException
*/
public function __construct( $callback ) {
if ( !is_callable( $callback ) ) {
@@ -26,5 +26,4 @@ class MWCallableUpdate implements DeferrableUpdate {
public function doUpdate() {
call_user_func( $this->callback );
}
-
}
diff --git a/includes/DataUpdate.php b/includes/deferred/DataUpdate.php
index 7b9ac281..ed12c601 100644
--- a/includes/DataUpdate.php
+++ b/includes/deferred/DataUpdate.php
@@ -25,12 +25,11 @@
* Abstract base class for update jobs that do something with some secondary
* data extracted from article.
*
- * @note: subclasses should NOT start or commit transactions in their doUpdate() method,
- * a transaction will automatically be wrapped around the update. If need be,
- * subclasses can override the beginTransaction() and commitTransaction() methods.
+ * @note subclasses should NOT start or commit transactions in their doUpdate() method,
+ * a transaction will automatically be wrapped around the update. If need be,
+ * subclasses can override the beginTransaction() and commitTransaction() methods.
*/
abstract class DataUpdate implements DeferrableUpdate {
-
/**
* Constructor
*/
@@ -74,7 +73,7 @@ abstract class DataUpdate implements DeferrableUpdate {
* This allows for limited transactional logic across multiple backends for storing
* secondary data.
*
- * @param array $updates a list of DataUpdate instances
+ * @param array $updates A list of DataUpdate instances
* @throws Exception|null
*/
public static function runUpdates( $updates ) {
@@ -109,7 +108,8 @@ abstract class DataUpdate implements DeferrableUpdate {
}
} catch ( Exception $ex ) {
$exception = $ex;
- wfDebug( "Caught exception, will rethrow after rollback: " . $ex->getMessage() );
+ wfDebug( "Caught exception, will rethrow after rollback: " .
+ $ex->getMessage() . "\n" );
}
// rollback remaining transactions
@@ -122,5 +122,4 @@ abstract class DataUpdate implements DeferrableUpdate {
throw $exception; // rethrow after cleanup
}
}
-
}
diff --git a/includes/DeferredUpdates.php b/includes/deferred/DeferredUpdates.php
index c385f138..b0c1899f 100644
--- a/includes/DeferredUpdates.php
+++ b/includes/deferred/DeferredUpdates.php
@@ -46,7 +46,7 @@ class DeferredUpdates {
/**
* Add an update to the deferred list
- * @param $update DeferrableUpdate Some object that implements doUpdate()
+ * @param DeferrableUpdate $update Some object that implements doUpdate()
*/
public static function addUpdate( DeferrableUpdate $update ) {
array_push( self::$updates, $update );
@@ -56,8 +56,8 @@ class DeferredUpdates {
* HTMLCacheUpdates are the most common deferred update people use. This
* is a shortcut method for that.
* @see HTMLCacheUpdate::__construct()
- * @param $title
- * @param $table
+ * @param Title $title
+ * @param string $table
*/
public static function addHTMLCacheUpdate( $title, $table ) {
self::addUpdate( new HTMLCacheUpdate( $title, $table ) );
@@ -76,8 +76,8 @@ class DeferredUpdates {
/**
* Do any deferred updates and clear the list
*
- * @param string $commit set to 'commit' to commit after every update to
- * prevent lock contention
+ * @param string $commit Set to 'commit' to commit after every update to
+ * prevent lock contention
*/
public static function doUpdates( $commit = '' ) {
global $wgDeferredUpdateList;
@@ -89,32 +89,39 @@ class DeferredUpdates {
// No need to get master connections in case of empty updates array
if ( !count( $updates ) ) {
wfProfileOut( __METHOD__ );
+
return;
}
+ $dbw = false;
$doCommit = $commit == 'commit';
if ( $doCommit ) {
$dbw = wfGetDB( DB_MASTER );
}
- foreach ( $updates as $update ) {
- try {
- $update->doUpdate();
+ while ( $updates ) {
+ self::clearPendingUpdates();
- if ( $doCommit && $dbw->trxLevel() ) {
- $dbw->commit( __METHOD__, 'flush' );
- }
- } catch ( MWException $e ) {
- // We don't want exceptions thrown during deferred updates to
- // be reported to the user since the output is already sent.
- // Instead we just log them.
- if ( !$e instanceof ErrorPageError ) {
- MWExceptionHandler::logException( $e );
+ /** @var DeferrableUpdate $update */
+ foreach ( $updates as $update ) {
+ try {
+ $update->doUpdate();
+
+ if ( $doCommit && $dbw->trxLevel() ) {
+ $dbw->commit( __METHOD__, 'flush' );
+ }
+ } catch ( MWException $e ) {
+ // We don't want exceptions thrown during deferred updates to
+ // be reported to the user since the output is already sent.
+ // Instead we just log them.
+ if ( !$e instanceof ErrorPageError ) {
+ MWExceptionHandler::logException( $e );
+ }
}
}
+ $updates = array_merge( $wgDeferredUpdateList, self::$updates );
}
- self::clearPendingUpdates();
wfProfileOut( __METHOD__ );
}
diff --git a/includes/cache/HTMLCacheUpdate.php b/includes/deferred/HTMLCacheUpdate.php
index 992809ef..54fa5943 100644
--- a/includes/cache/HTMLCacheUpdate.php
+++ b/includes/deferred/HTMLCacheUpdate.php
@@ -27,18 +27,15 @@
* @ingroup Cache
*/
class HTMLCacheUpdate implements DeferrableUpdate {
- /**
- * @var Title
- */
+ /** @var Title */
public $mTitle;
+ /** @var string */
public $mTable;
/**
- * @param $titleTo
- * @param $table
- * @param $start bool
- * @param $end bool
+ * @param Title $titleTo
+ * @param string $table
*/
function __construct( Title $titleTo, $table ) {
$this->mTitle = $titleTo;
@@ -57,13 +54,13 @@ class HTMLCacheUpdate implements DeferrableUpdate {
)
);
- $count = $this->mTitle->getBacklinkCache()->getNumLinks( $this->mTable, 200 );
- if ( $count >= 200 ) { // many backlinks
+ $count = $this->mTitle->getBacklinkCache()->getNumLinks( $this->mTable, 100 );
+ if ( $count >= 100 ) { // many backlinks
JobQueueGroup::singleton()->push( $job );
JobQueueGroup::singleton()->deduplicateRootJob( $job );
} else { // few backlinks ($count might be off even if 0)
$dbw = wfGetDB( DB_MASTER );
- $dbw->onTransactionIdle( function() use ( $job ) {
+ $dbw->onTransactionIdle( function () use ( $job ) {
$job->run(); // just do the purge query now
} );
}
diff --git a/includes/LinksUpdate.php b/includes/deferred/LinksUpdate.php
index ed52eb9c..45d26648 100644
--- a/includes/LinksUpdate.php
+++ b/includes/deferred/LinksUpdate.php
@@ -26,22 +26,46 @@
* @todo document (e.g. one-sentence top-level class description).
*/
class LinksUpdate extends SqlDataUpdate {
-
// @todo make members protected, but make sure extensions don't break
- public $mId, //!< Page ID of the article linked from
- $mTitle, //!< Title object of the article linked from
- $mParserOutput, //!< Parser output
- $mLinks, //!< Map of title strings to IDs for the links in the document
- $mImages, //!< DB keys of the images used, in the array key only
- $mTemplates, //!< Map of title strings to IDs for the template references, including broken ones
- $mExternals, //!< URLs of external links, array key only
- $mCategories, //!< Map of category names to sort keys
- $mInterlangs, //!< Map of language codes to titles
- $mProperties, //!< Map of arbitrary name to value
- $mDb, //!< Database connection reference
- $mOptions, //!< SELECT options to be used (array)
- $mRecursive; //!< Whether to queue jobs for recursive updates
+ /** @var int Page ID of the article linked from */
+ public $mId;
+
+ /** @var Title Title object of the article linked from */
+ public $mTitle;
+
+ /** @var ParserOutput */
+ public $mParserOutput;
+
+ /** @var array Map of title strings to IDs for the links in the document */
+ public $mLinks;
+
+ /** @var array DB keys of the images used, in the array key only */
+ public $mImages;
+
+ /** @var array Map of title strings to IDs for the template references, including broken ones */
+ public $mTemplates;
+
+ /** @var array URLs of external links, array key only */
+ public $mExternals;
+
+ /** @var array Map of category names to sort keys */
+ public $mCategories;
+
+ /** @var array Map of language codes to titles */
+ public $mInterlangs;
+
+ /** @var array Map of arbitrary name to value */
+ public $mProperties;
+
+ /** @var DatabaseBase Database connection reference */
+ public $mDb;
+
+ /** @var array SELECT options to be used */
+ public $mOptions;
+
+ /** @var bool Whether to queue jobs for recursive updates */
+ public $mRecursive;
/**
* @var null|array Added links if calculated.
@@ -56,9 +80,9 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Constructor
*
- * @param $title Title of the page we're updating
- * @param $parserOutput ParserOutput: output from a full parse of this page
- * @param $recursive Boolean: queue jobs for recursive updates?
+ * @param Title $title Title of the page we're updating
+ * @param ParserOutput $parserOutput Output from a full parse of this page
+ * @param bool $recursive Queue jobs for recursive updates?
* @throws MWException
*/
function __construct( $title, $parserOutput, $recursive = true ) {
@@ -78,7 +102,8 @@ class LinksUpdate extends SqlDataUpdate {
$this->mId = $title->getArticleID();
if ( !$this->mId ) {
- throw new MWException( "The Title object did not provide an article ID. Perhaps the page doesn't exist?" );
+ throw new MWException( "The Title object did not provide an article " .
+ "ID. Perhaps the page doesn't exist?" );
}
$this->mParserOutput = $parserOutput;
@@ -193,6 +218,9 @@ class LinksUpdate extends SqlDataUpdate {
$changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing );
$this->invalidateProperties( $changed );
+ # Update the links table freshness for this title
+ $this->updateLinksTimestamp();
+
# Refresh links of all pages including this page
# This will be in a separate transaction
if ( $this->mRecursive ) {
@@ -205,26 +233,31 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Queue recursive jobs for this page
*
- * Which means do LinksUpdate on all templates
- * that include the current page, using the job queue.
+ * Which means do LinksUpdate on all pages that include the current page,
+ * using the job queue.
*/
function queueRecursiveJobs() {
self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
+ if ( $this->mTitle->getNamespace() == NS_FILE ) {
+ // Process imagelinks in case the title is or was a redirect
+ self::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' );
+ }
}
/**
* Queue a RefreshLinks job for any table.
*
* @param Title $title Title to do job for
- * @param String $table Table to use (e.g. 'templatelinks')
+ * @param string $table Table to use (e.g. 'templatelinks')
*/
public static function queueRecursiveJobsForTable( Title $title, $table ) {
wfProfileIn( __METHOD__ );
if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
- $job = new RefreshLinksJob2(
+ $job = new RefreshLinksJob(
$title,
array(
'table' => $table,
+ 'recursive' => true,
) + Job::newRootJobParams( // "overall" refresh links job info
"refreshlinks:{$table}:{$title->getPrefixedText()}"
)
@@ -236,7 +269,7 @@ class LinksUpdate extends SqlDataUpdate {
}
/**
- * @param $cats
+ * @param array $cats
*/
function invalidateCategories( $cats ) {
$this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
@@ -244,8 +277,8 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Update all the appropriate counts in the category table.
- * @param array $added associative array of category name => sort key
- * @param array $deleted associative array of category name => sort key
+ * @param array $added Associative array of category name => sort key
+ * @param array $deleted Associative array of category name => sort key
*/
function updateCategoryCounts( $added, $deleted ) {
$a = WikiPage::factory( $this->mTitle );
@@ -255,7 +288,7 @@ class LinksUpdate extends SqlDataUpdate {
}
/**
- * @param $images
+ * @param array $images
*/
function invalidateImageDescriptions( $images ) {
$this->invalidatePages( NS_FILE, array_keys( $images ) );
@@ -263,10 +296,10 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Update a table by doing a delete query then an insert query
- * @param $table
- * @param $prefix
- * @param $deletions
- * @param $insertions
+ * @param string $table Table name
+ * @param string $prefix Field name prefix
+ * @param array $deletions
+ * @param array $insertions Rows to insert
*/
function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
if ( $table == 'page_props' ) {
@@ -296,7 +329,7 @@ class LinksUpdate extends SqlDataUpdate {
$toField = $prefix . '_to';
}
if ( count( $deletions ) ) {
- $where[] = "$toField IN (" . $this->mDb->makeList( array_keys( $deletions ) ) . ')';
+ $where[$toField] = array_keys( $deletions );
} else {
$where = false;
}
@@ -313,7 +346,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Get an array of pagelinks insertions for passing to the DB
* Skips the titles specified by the 2-D array $existing
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getLinkInsertions( $existing = array() ) {
@@ -325,17 +358,19 @@ class LinksUpdate extends SqlDataUpdate {
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
'pl_from' => $this->mId,
+ 'pl_from_namespace' => $this->mTitle->getNamespace(),
'pl_namespace' => $ns,
'pl_title' => $dbk
);
}
}
+
return $arr;
}
/**
* Get an array of template insertions. Like getLinkInsertions()
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getTemplateInsertions( $existing = array() ) {
@@ -345,18 +380,20 @@ class LinksUpdate extends SqlDataUpdate {
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
'tl_from' => $this->mId,
+ 'tl_from_namespace' => $this->mTitle->getNamespace(),
'tl_namespace' => $ns,
'tl_title' => $dbk
);
}
}
+
return $arr;
}
/**
* Get an array of image insertions
* Skips the names specified in $existing
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getImageInsertions( $existing = array() ) {
@@ -365,15 +402,17 @@ class LinksUpdate extends SqlDataUpdate {
foreach ( $diffs as $iname => $dummy ) {
$arr[] = array(
'il_from' => $this->mId,
+ 'il_from_namespace' => $this->mTitle->getNamespace(),
'il_to' => $iname
);
}
+
return $arr;
}
/**
* Get an array of externallinks insertions. Skips the names specified in $existing
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getExternalInsertions( $existing = array() ) {
@@ -389,13 +428,14 @@ class LinksUpdate extends SqlDataUpdate {
);
}
}
+
return $arr;
}
/**
* Get an array of category insertions
*
- * @param array $existing mapping existing category names to sort keys. If both
+ * @param array $existing Mapping existing category names to sort keys. If both
* match a link in $this, the link will be omitted from the output
*
* @return array
@@ -433,13 +473,14 @@ class LinksUpdate extends SqlDataUpdate {
'cl_type' => $type,
);
}
+
return $arr;
}
/**
* Get an array of interlanguage link insertions
*
- * @param array $existing mapping existing language codes to titles
+ * @param array $existing Mapping existing language codes to titles
*
* @return array
*/
@@ -453,37 +494,93 @@ class LinksUpdate extends SqlDataUpdate {
'll_title' => $title
);
}
+
return $arr;
}
/**
* Get an array of page property insertions
- * @param $existing array
+ * @param array $existing
* @return array
*/
function getPropertyInsertions( $existing = array() ) {
$diffs = array_diff_assoc( $this->mProperties, $existing );
+
$arr = array();
- foreach ( $diffs as $name => $value ) {
- $arr[] = array(
- 'pp_page' => $this->mId,
- 'pp_propname' => $name,
- 'pp_value' => $value,
- );
+ foreach ( array_keys( $diffs ) as $name ) {
+ $arr[] = $this->getPagePropRowData( $name );
}
+
return $arr;
}
/**
+ * Returns an associative array to be used for inserting a row into
+ * the page_props table. Besides the given property name, this will
+ * include the page id from $this->mId and any property value from
+ * $this->mProperties.
+ *
+ * The array returned will include the pp_sortkey field if this
+ * is present in the database (as indicated by $wgPagePropsHaveSortkey).
+ * The sortkey value is currently determined by getPropertySortKeyValue().
+ *
+ * @note this assumes that $this->mProperties[$prop] is defined.
+ *
+ * @param string $prop The name of the property.
+ *
+ * @return array
+ */
+ private function getPagePropRowData( $prop ) {
+ global $wgPagePropsHaveSortkey;
+
+ $value = $this->mProperties[$prop];
+
+ $row = array(
+ 'pp_page' => $this->mId,
+ 'pp_propname' => $prop,
+ 'pp_value' => $value,
+ );
+
+ if ( $wgPagePropsHaveSortkey ) {
+ $row['pp_sortkey'] = $this->getPropertySortKeyValue( $value );
+ }
+
+ return $row;
+ }
+
+ /**
+ * Determines the sort key for the given property value.
+ * This will return $value if it is a float or int,
+ * 1 or resp. 0 if it is a bool, and null otherwise.
+ *
+ * @note In the future, we may allow the sortkey to be specified explicitly
+ * in ParserOutput::setProperty.
+ *
+ * @param mixed $value
+ *
+ * @return float|null
+ */
+ private function getPropertySortKeyValue( $value ) {
+ if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
+ return floatval( $value );
+ }
+
+ return null;
+ }
+
+ /**
* Get an array of interwiki insertions for passing to the DB
* Skips the titles specified by the 2-D array $existing
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getInterwikiInsertions( $existing = array() ) {
$arr = array();
foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
- $diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
+ $diffs = isset( $existing[$prefix] )
+ ? array_diff_key( $dbkeys, $existing[$prefix] )
+ : $dbkeys;
+
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
'iwl_from' => $this->mId,
@@ -492,13 +589,14 @@ class LinksUpdate extends SqlDataUpdate {
);
}
}
+
return $arr;
}
/**
* Given an array of existing links, returns those links which are not in $this
* and thus should be deleted.
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getLinkDeletions( $existing ) {
@@ -510,13 +608,14 @@ class LinksUpdate extends SqlDataUpdate {
$del[$ns] = $existing[$ns];
}
}
+
return $del;
}
/**
* Given an array of existing templates, returns those templates which are not in $this
* and thus should be deleted.
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getTemplateDeletions( $existing ) {
@@ -528,13 +627,14 @@ class LinksUpdate extends SqlDataUpdate {
$del[$ns] = $existing[$ns];
}
}
+
return $del;
}
/**
* Given an array of existing images, returns those images which are not in $this
* and thus should be deleted.
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getImageDeletions( $existing ) {
@@ -544,7 +644,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Given an array of existing external links, returns those links which are not
* in $this and thus should be deleted.
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getExternalDeletions( $existing ) {
@@ -554,7 +654,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Given an array of existing categories, returns those categories which are not in $this
* and thus should be deleted.
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getCategoryDeletions( $existing ) {
@@ -564,7 +664,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Given an array of existing interlanguage links, returns those links which are not
* in $this and thus should be deleted.
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getInterlangDeletions( $existing ) {
@@ -573,7 +673,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Get array of properties which should be deleted.
- * @param $existing array
+ * @param array $existing
* @return array
*/
function getPropertyDeletions( $existing ) {
@@ -583,7 +683,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Given an array of existing interwiki links, returns those links which are not in $this
* and thus should be deleted.
- * @param $existing array
+ * @param array $existing
* @return array
*/
private function getInterwikiDeletions( $existing ) {
@@ -595,6 +695,7 @@ class LinksUpdate extends SqlDataUpdate {
$del[$prefix] = $existing[$prefix];
}
}
+
return $del;
}
@@ -613,6 +714,7 @@ class LinksUpdate extends SqlDataUpdate {
}
$arr[$row->pl_namespace][$row->pl_title] = 1;
}
+
return $arr;
}
@@ -631,6 +733,7 @@ class LinksUpdate extends SqlDataUpdate {
}
$arr[$row->tl_namespace][$row->tl_title] = 1;
}
+
return $arr;
}
@@ -646,6 +749,7 @@ class LinksUpdate extends SqlDataUpdate {
foreach ( $res as $row ) {
$arr[$row->il_to] = 1;
}
+
return $arr;
}
@@ -661,6 +765,7 @@ class LinksUpdate extends SqlDataUpdate {
foreach ( $res as $row ) {
$arr[$row->el_to] = 1;
}
+
return $arr;
}
@@ -676,6 +781,7 @@ class LinksUpdate extends SqlDataUpdate {
foreach ( $res as $row ) {
$arr[$row->cl_to] = $row->cl_sortkey_prefix;
}
+
return $arr;
}
@@ -692,6 +798,7 @@ class LinksUpdate extends SqlDataUpdate {
foreach ( $res as $row ) {
$arr[$row->ll_lang] = $row->ll_title;
}
+
return $arr;
}
@@ -709,13 +816,14 @@ class LinksUpdate extends SqlDataUpdate {
}
$arr[$row->iwl_prefix][$row->iwl_title] = 1;
}
+
return $arr;
}
/**
* Get an array of existing categories, with the name in the key and sort key in the value.
*
- * @return array
+ * @return array Array of property names and values
*/
private function getExistingProperties() {
$res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ),
@@ -724,6 +832,7 @@ class LinksUpdate extends SqlDataUpdate {
foreach ( $res as $row ) {
$arr[$row->pp_propname] = $row->pp_value;
}
+
return $arr;
}
@@ -754,7 +863,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Invalidate any necessary link lists related to page property changes
- * @param $changed
+ * @param array $changed
*/
private function invalidateProperties( $changed ) {
global $wgPagePropLinkInvalidations;
@@ -776,7 +885,7 @@ class LinksUpdate extends SqlDataUpdate {
/**
* Fetch page links added by this LinksUpdate. Only available after the update is complete.
* @since 1.22
- * @return null|array of Titles
+ * @return null|array Array of Titles
*/
public function getAddedLinks() {
if ( $this->linkInsertions === null ) {
@@ -784,15 +893,16 @@ class LinksUpdate extends SqlDataUpdate {
}
$result = array();
foreach ( $this->linkInsertions as $insertion ) {
- $result[] = Title::makeTitle( $insertion[ 'pl_namespace' ], $insertion[ 'pl_title' ] );
+ $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] );
}
+
return $result;
}
/**
* Fetch page links removed by this LinksUpdate. Only available after the update is complete.
* @since 1.22
- * @return null|array of Titles
+ * @return null|array Array of Titles
*/
public function getRemovedLinks() {
if ( $this->linkDeletions === null ) {
@@ -804,21 +914,37 @@ class LinksUpdate extends SqlDataUpdate {
$result[] = Title::makeTitle( $ns, $title );
}
}
+
return $result;
}
+
+ /**
+ * Update links table freshness
+ */
+ protected function updateLinksTimestamp() {
+ if ( $this->mId ) {
+ // The link updates made here only reflect the freshness of the parser output
+ $timestamp = $this->mParserOutput->getCacheTime();
+ $this->mDb->update( 'page',
+ array( 'page_links_updated' => $this->mDb->timestamp( $timestamp ) ),
+ array( 'page_id' => $this->mId ),
+ __METHOD__
+ );
+ }
+ }
}
/**
* Update object handling the cleanup of links tables after a page was deleted.
**/
class LinksDeletionUpdate extends SqlDataUpdate {
-
- protected $mPage; //!< WikiPage the wikipage that was deleted
+ /** @var WikiPage The WikiPage that was deleted */
+ protected $mPage;
/**
* Constructor
*
- * @param $page WikiPage Page we are updating
+ * @param WikiPage $page Page we are updating
* @throws MWException
*/
function __construct( WikiPage $page ) {
@@ -881,8 +1007,8 @@ class LinksDeletionUpdate extends SqlDataUpdate {
/**
* Update all the appropriate counts in the category table.
- * @param array $added associative array of category name => sort key
- * @param array $deleted associative array of category name => sort key
+ * @param array $added Associative array of category name => sort key
+ * @param array $deleted Associative array of category name => sort key
*/
function updateCategoryCounts( $added, $deleted ) {
$a = WikiPage::factory( $this->mTitle );
diff --git a/includes/search/SearchUpdate.php b/includes/deferred/SearchUpdate.php
index 82a413e9..5d084afd 100644
--- a/includes/search/SearchUpdate.php
+++ b/includes/deferred/SearchUpdate.php
@@ -29,22 +29,13 @@
* @ingroup Search
*/
class SearchUpdate implements DeferrableUpdate {
- /**
- * Page id being updated
- * @var int
- */
+ /** @var int Page id being updated */
private $id = 0;
- /**
- * Title we're updating
- * @var Title
- */
+ /** @var Title Title we're updating */
private $title;
- /**
- * Content of the page (not text)
- * @var Content|false
- */
+ /** @var Content|bool Content of the page (not text) */
private $content;
/**
@@ -52,7 +43,7 @@ class SearchUpdate implements DeferrableUpdate {
*
* @param int $id Page id to update
* @param Title|string $title Title of page to update
- * @param Content|string|false $c Content of the page to update.
+ * @param Content|string|bool $c Content of the page to update. Default: false.
* If a Content object, text will be gotten from it. String is for back-compat.
* Passing false tells the backend to just update the title, not the content
*/
@@ -90,10 +81,10 @@ class SearchUpdate implements DeferrableUpdate {
wfProfileIn( __METHOD__ );
$page = WikiPage::newFromId( $this->id, WikiPage::READ_LATEST );
- $indexTitle = Title::indexTitle( $this->title->getNamespace(), $this->title->getText() );
foreach ( SearchEngine::getSearchTypes() as $type ) {
$search = SearchEngine::create( $type );
+ $indexTitle = $this->indexTitle( $search );
if ( !$search->supports( 'search-update' ) ) {
continue;
}
@@ -124,6 +115,8 @@ class SearchUpdate implements DeferrableUpdate {
* Clean text for indexing. Only really suitable for indexing in databases.
* If you're using a real search engine, you'll probably want to override
* this behavior and do something nicer with the original wikitext.
+ * @param string $text
+ * @return string
*/
public static function updateText( $text ) {
global $wgContLang;
@@ -180,6 +173,37 @@ class SearchUpdate implements DeferrableUpdate {
# Strip wiki '' and '''
$text = preg_replace( "/''[']*/", " ", $text );
wfProfileOut( __METHOD__ . '-regexps' );
+
return $text;
}
+
+ /**
+ * Get a string representation of a title suitable for
+ * including in a search index
+ *
+ * @param SearchEngine $search
+ * @return string A stripped-down title string ready for the search index
+ */
+ private function indexTitle( SearchEngine $search ) {
+ global $wgContLang;
+
+ $ns = $this->title->getNamespace();
+ $title = $this->title->getText();
+
+ $lc = $search->legalSearchChars() . '&#;';
+ $t = $wgContLang->normalizeForSearch( $title );
+ $t = preg_replace( "/[^{$lc}]+/", ' ', $t );
+ $t = $wgContLang->lc( $t );
+
+ # Handle 's, s'
+ $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t );
+ $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t );
+
+ $t = preg_replace( "/\\s+/", ' ', $t );
+
+ if ( $ns == NS_FILE ) {
+ $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
+ }
+ return trim( $t );
+ }
}
diff --git a/includes/deferred/SiteStatsUpdate.php b/includes/deferred/SiteStatsUpdate.php
new file mode 100644
index 00000000..7bfafee8
--- /dev/null
+++ b/includes/deferred/SiteStatsUpdate.php
@@ -0,0 +1,254 @@
+<?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
+ */
+
+/**
+ * Class for handling updates to the site_stats table
+ */
+class SiteStatsUpdate implements DeferrableUpdate {
+ /** @var int */
+ protected $views = 0;
+
+ /** @var int */
+ protected $edits = 0;
+
+ /** @var int */
+ protected $pages = 0;
+
+ /** @var int */
+ protected $articles = 0;
+
+ /** @var int */
+ protected $users = 0;
+
+ /** @var int */
+ protected $images = 0;
+
+ // @todo deprecate this constructor
+ function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
+ $this->views = $views;
+ $this->edits = $edits;
+ $this->articles = $good;
+ $this->pages = $pages;
+ $this->users = $users;
+ }
+
+ /**
+ * @param array $deltas
+ * @return SiteStatsUpdate
+ */
+ public static function factory( array $deltas ) {
+ $update = new self( 0, 0, 0 );
+
+ $fields = array( 'views', 'edits', 'pages', 'articles', 'users', 'images' );
+ foreach ( $fields as $field ) {
+ if ( isset( $deltas[$field] ) && $deltas[$field] ) {
+ $update->$field = $deltas[$field];
+ }
+ }
+
+ return $update;
+ }
+
+ public function doUpdate() {
+ global $wgSiteStatsAsyncFactor;
+
+ $rate = $wgSiteStatsAsyncFactor; // convenience
+ // If set to do so, only do actual DB updates 1 every $rate times.
+ // The other times, just update "pending delta" values in memcached.
+ if ( $rate && ( $rate < 0 || mt_rand( 0, $rate - 1 ) != 0 ) ) {
+ $this->doUpdatePendingDeltas();
+ } else {
+ // Need a separate transaction because this a global lock
+ wfGetDB( DB_MASTER )->onTransactionIdle( array( $this, 'tryDBUpdateInternal' ) );
+ }
+ }
+
+ /**
+ * Do not call this outside of SiteStatsUpdate
+ */
+ public function tryDBUpdateInternal() {
+ global $wgSiteStatsAsyncFactor;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $lockKey = wfMemcKey( 'site_stats' ); // prepend wiki ID
+ $pd = array();
+ if ( $wgSiteStatsAsyncFactor ) {
+ // Lock the table so we don't have double DB/memcached updates
+ if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
+ || !$dbw->lock( $lockKey, __METHOD__, 1 ) // 1 sec timeout
+ ) {
+ $this->doUpdatePendingDeltas();
+
+ return;
+ }
+ $pd = $this->getPendingDeltas();
+ // Piggy-back the async deltas onto those of this stats update....
+ $this->views += ( $pd['ss_total_views']['+'] - $pd['ss_total_views']['-'] );
+ $this->edits += ( $pd['ss_total_edits']['+'] - $pd['ss_total_edits']['-'] );
+ $this->articles += ( $pd['ss_good_articles']['+'] - $pd['ss_good_articles']['-'] );
+ $this->pages += ( $pd['ss_total_pages']['+'] - $pd['ss_total_pages']['-'] );
+ $this->users += ( $pd['ss_users']['+'] - $pd['ss_users']['-'] );
+ $this->images += ( $pd['ss_images']['+'] - $pd['ss_images']['-'] );
+ }
+
+ // Build up an SQL query of deltas and apply them...
+ $updates = '';
+ $this->appendUpdate( $updates, 'ss_total_views', $this->views );
+ $this->appendUpdate( $updates, 'ss_total_edits', $this->edits );
+ $this->appendUpdate( $updates, 'ss_good_articles', $this->articles );
+ $this->appendUpdate( $updates, 'ss_total_pages', $this->pages );
+ $this->appendUpdate( $updates, 'ss_users', $this->users );
+ $this->appendUpdate( $updates, 'ss_images', $this->images );
+ if ( $updates != '' ) {
+ $dbw->update( 'site_stats', array( $updates ), array(), __METHOD__ );
+ }
+
+ if ( $wgSiteStatsAsyncFactor ) {
+ // Decrement the async deltas now that we applied them
+ $this->removePendingDeltas( $pd );
+ // Commit the updates and unlock the table
+ $dbw->unlock( $lockKey, __METHOD__ );
+ }
+ }
+
+ /**
+ * @param DatabaseBase $dbw
+ * @return bool|mixed
+ */
+ public static function cacheUpdate( $dbw ) {
+ global $wgActiveUserDays;
+ $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow' ) );
+ # Get non-bot users than did some recent action other than making accounts.
+ # If account creation is included, the number gets inflated ~20+ fold on enwiki.
+ $activeUsers = $dbr->selectField(
+ 'recentchanges',
+ 'COUNT( DISTINCT rc_user_text )',
+ array(
+ 'rc_user != 0',
+ 'rc_bot' => 0,
+ 'rc_log_type != ' . $dbr->addQuotes( 'newusers' ) . ' OR rc_log_type IS NULL',
+ 'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX )
+ - $wgActiveUserDays * 24 * 3600 ) ),
+ ),
+ __METHOD__
+ );
+ $dbw->update(
+ 'site_stats',
+ array( 'ss_active_users' => intval( $activeUsers ) ),
+ array( 'ss_row_id' => 1 ),
+ __METHOD__
+ );
+
+ return $activeUsers;
+ }
+
+ protected function doUpdatePendingDeltas() {
+ $this->adjustPending( 'ss_total_views', $this->views );
+ $this->adjustPending( 'ss_total_edits', $this->edits );
+ $this->adjustPending( 'ss_good_articles', $this->articles );
+ $this->adjustPending( 'ss_total_pages', $this->pages );
+ $this->adjustPending( 'ss_users', $this->users );
+ $this->adjustPending( 'ss_images', $this->images );
+ }
+
+ /**
+ * @param string $sql
+ * @param string $field
+ * @param int $delta
+ */
+ protected function appendUpdate( &$sql, $field, $delta ) {
+ if ( $delta ) {
+ if ( $sql ) {
+ $sql .= ',';
+ }
+ if ( $delta < 0 ) {
+ $sql .= "$field=$field-" . abs( $delta );
+ } else {
+ $sql .= "$field=$field+" . abs( $delta );
+ }
+ }
+ }
+
+ /**
+ * @param string $type
+ * @param string $sign ('+' or '-')
+ * @return string
+ */
+ private function getTypeCacheKey( $type, $sign ) {
+ return wfMemcKey( 'sitestatsupdate', 'pendingdelta', $type, $sign );
+ }
+
+ /**
+ * Adjust the pending deltas for a stat type.
+ * Each stat type has two pending counters, one for increments and decrements
+ * @param string $type
+ * @param int $delta Delta (positive or negative)
+ */
+ protected function adjustPending( $type, $delta ) {
+ global $wgMemc;
+
+ if ( $delta < 0 ) { // decrement
+ $key = $this->getTypeCacheKey( $type, '-' );
+ } else { // increment
+ $key = $this->getTypeCacheKey( $type, '+' );
+ }
+
+ $magnitude = abs( $delta );
+ if ( !$wgMemc->incr( $key, $magnitude ) ) { // not there?
+ if ( !$wgMemc->add( $key, $magnitude ) ) { // race?
+ $wgMemc->incr( $key, $magnitude );
+ }
+ }
+ }
+
+ /**
+ * Get pending delta counters for each stat type
+ * @return array Positive and negative deltas for each type
+ */
+ protected function getPendingDeltas() {
+ global $wgMemc;
+
+ $pending = array();
+ foreach ( array( 'ss_total_views', 'ss_total_edits',
+ 'ss_good_articles', 'ss_total_pages', 'ss_users', 'ss_images' ) as $type
+ ) {
+ // Get pending increments and pending decrements
+ $pending[$type]['+'] = (int)$wgMemc->get( $this->getTypeCacheKey( $type, '+' ) );
+ $pending[$type]['-'] = (int)$wgMemc->get( $this->getTypeCacheKey( $type, '-' ) );
+ }
+
+ return $pending;
+ }
+
+ /**
+ * Reduce pending delta counters after updates have been applied
+ * @param array $pd Result of getPendingDeltas(), used for DB update
+ */
+ protected function removePendingDeltas( array $pd ) {
+ global $wgMemc;
+
+ foreach ( $pd as $type => $deltas ) {
+ foreach ( $deltas as $sign => $magnitude ) {
+ // Lower the pending counter now that we applied these changes
+ $wgMemc->decr( $this->getTypeCacheKey( $type, $sign ), $magnitude );
+ }
+ }
+ }
+}
diff --git a/includes/SqlDataUpdate.php b/includes/deferred/SqlDataUpdate.php
index 51188d85..9c58503f 100644
--- a/includes/SqlDataUpdate.php
+++ b/includes/deferred/SqlDataUpdate.php
@@ -25,26 +25,31 @@
* Abstract base class for update jobs that put some secondary data extracted
* from article content into the database.
*
- * @note: subclasses should NOT start or commit transactions in their doUpdate() method,
- * a transaction will automatically be wrapped around the update. Starting another
- * one would break the outer transaction bracket. If need be, subclasses can override
- * the beginTransaction() and commitTransaction() methods.
+ * @note subclasses should NOT start or commit transactions in their doUpdate() method,
+ * a transaction will automatically be wrapped around the update. Starting another
+ * one would break the outer transaction bracket. If need be, subclasses can override
+ * the beginTransaction() and commitTransaction() methods.
*/
abstract class SqlDataUpdate extends DataUpdate {
+ /** @var DatabaseBase Database connection reference */
+ protected $mDb;
- protected $mDb; //!< Database connection reference
- protected $mOptions; //!< SELECT options to be used (array)
+ /** @var array SELECT options to be used (array) */
+ protected $mOptions;
- private $mHasTransaction; //!< bool whether a transaction is open on this object (internal use only!)
- protected $mUseTransaction; //!< bool whether this update should be wrapped in a transaction
+ /** @var bool Whether a transaction is open on this object (internal use only!) */
+ private $mHasTransaction;
+
+ /** @var bool Whether this update should be wrapped in a transaction */
+ protected $mUseTransaction;
/**
* Constructor
*
- * @param bool $withTransaction whether this update should be wrapped in a transaction (default: true).
- * A transaction is only started if no transaction is already in progress,
- * see beginTransaction() for details.
- **/
+ * @param bool $withTransaction Whether this update should be wrapped in a
+ * transaction (default: true). A transaction is only started if no
+ * transaction is already in progress, see beginTransaction() for details.
+ */
public function __construct( $withTransaction = true ) {
global $wgAntiLockFlags;
@@ -56,7 +61,8 @@ abstract class SqlDataUpdate extends DataUpdate {
$this->mOptions = array( 'FOR UPDATE' );
}
- // @todo get connection only when it's needed? make sure that doesn't break anything, especially transactions!
+ // @todo Get connection only when it's needed? Make sure that doesn't
+ // break anything, especially transactions!
$this->mDb = wfGetDB( DB_MASTER );
$this->mWithTransaction = $withTransaction;
@@ -64,10 +70,12 @@ abstract class SqlDataUpdate extends DataUpdate {
}
/**
- * Begin a database transaction, if $withTransaction was given as true in the constructor for this SqlDataUpdate.
+ * Begin a database transaction, if $withTransaction was given as true in
+ * the constructor for this SqlDataUpdate.
*
- * Because nested transactions are not supported by the Database class, this implementation
- * checks Database::trxLevel() and only opens a transaction if none is already active.
+ * Because nested transactions are not supported by the Database class,
+ * this implementation checks Database::trxLevel() and only opens a
+ * transaction if none is already active.
*/
public function beginTransaction() {
if ( !$this->mWithTransaction ) {
@@ -105,8 +113,8 @@ abstract class SqlDataUpdate extends DataUpdate {
* Invalidate the cache of a list of pages from a single namespace.
* This is intended for use by subclasses.
*
- * @param $namespace Integer
- * @param $dbkeys Array
+ * @param int $namespace Namespace number
+ * @param array $dbkeys
*/
protected function invalidatePages( $namespace, array $dbkeys ) {
if ( $dbkeys === array() ) {
@@ -148,5 +156,4 @@ abstract class SqlDataUpdate extends DataUpdate {
), __METHOD__
);
}
-
}
diff --git a/includes/cache/SquidUpdate.php b/includes/deferred/SquidUpdate.php
index 71afeba9..0dcff44a 100644
--- a/includes/cache/SquidUpdate.php
+++ b/includes/deferred/SquidUpdate.php
@@ -26,7 +26,6 @@
* @ingroup Cache
*/
class SquidUpdate {
-
/**
* Collection of URLs to purge.
* @var array
@@ -83,6 +82,7 @@ class SquidUpdate {
}
wfProfileOut( __METHOD__ );
+
return new SquidUpdate( $blurlArr );
}
@@ -96,12 +96,14 @@ class SquidUpdate {
public static function newFromTitles( $titles, $urlArr = array() ) {
global $wgMaxSquidPurgeTitles;
$i = 0;
+ /** @var Title $title */
foreach ( $titles as $title ) {
$urlArr[] = $title->getInternalURL();
if ( $i++ > $wgMaxSquidPurgeTitles ) {
break;
}
}
+
return new SquidUpdate( $urlArr );
}
@@ -111,6 +113,7 @@ class SquidUpdate {
*/
public static function newSimplePurge( Title $title ) {
$urlArr = $title->getSquidURLs();
+
return new SquidUpdate( $urlArr );
}
@@ -136,7 +139,7 @@ class SquidUpdate {
return;
}
- wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) . "\n" );
+ wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) );
if ( $wgHTCPRouting ) {
self::HTCPPurge( $urlArr );
@@ -194,11 +197,12 @@ class SquidUpdate {
// pfsockopen doesn't work because we need set_sock_opt
$conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- if ( ! $conn ) {
+ if ( !$conn ) {
$errstr = socket_strerror( socket_last_error() );
wfDebugLog( 'squid', __METHOD__ .
- ": Error opening UDP socket: $errstr\n" );
+ ": Error opening UDP socket: $errstr" );
wfProfileOut( __METHOD__ );
+
return;
}
@@ -212,6 +216,11 @@ class SquidUpdate {
// Remove duplicate URLs from collection
$urlArr = array_unique( $urlArr );
+ // Get sequential trx IDs for packet loss counting
+ $ids = UIDGenerator::newSequentialPerNodeIDs(
+ 'squidhtcppurge', 32, count( $urlArr ), UIDGenerator::QUICK_VOLATILE
+ );
+
foreach ( $urlArr as $url ) {
if ( !is_string( $url ) ) {
wfProfileOut( __METHOD__ );
@@ -221,7 +230,7 @@ class SquidUpdate {
$conf = self::getRuleForURL( $url, $wgHTCPRouting );
if ( !$conf ) {
wfDebugLog( 'squid', __METHOD__ .
- "No HTCP rule configured for URL {$url} , skipping\n" );
+ "No HTCP rule configured for URL {$url} , skipping" );
continue;
}
@@ -239,7 +248,8 @@ class SquidUpdate {
// Construct a minimal HTCP request diagram
// as per RFC 2756
// Opcode 'CLR', no response desired, no auth
- $htcpTransID = rand();
+ $htcpTransID = current( $ids );
+ next( $ids );
$htcpSpecifier = pack( 'na4na*na8n',
4, 'HEAD', strlen( $url ), $url,
@@ -256,7 +266,7 @@ class SquidUpdate {
$htcpTransID, $htcpSpecifier, 2 );
wfDebugLog( 'squid', __METHOD__ .
- "Purging URL $url via HTCP\n" );
+ "Purging URL $url via HTCP" );
foreach ( $conf as $subconf ) {
socket_sendto( $conn, $htcpPacket, $htcpLen, 0,
$subconf['host'], $subconf['port'] );
@@ -295,6 +305,7 @@ class SquidUpdate {
return $routing;
}
}
+
return false;
}
}
diff --git a/includes/ViewCountUpdate.php b/includes/deferred/ViewCountUpdate.php
index 22a46493..8282295b 100644
--- a/includes/ViewCountUpdate.php
+++ b/includes/deferred/ViewCountUpdate.php
@@ -28,12 +28,13 @@
* from that table to update the 'page_counter' field in a batch operation.
*/
class ViewCountUpdate implements DeferrableUpdate {
+ /** @var int Page ID to increment the view count */
protected $id;
/**
* Constructor
*
- * @param $id Integer: page ID to increment the view count
+ * @param int $id Page ID to increment the view count
*/
public function __construct( $id ) {
$this->id = intval( $id );
@@ -48,18 +49,33 @@ class ViewCountUpdate implements DeferrableUpdate {
$dbw = wfGetDB( DB_MASTER );
if ( $wgHitcounterUpdateFreq <= 1 || $dbw->getType() == 'sqlite' ) {
- $dbw->update( 'page', array( 'page_counter = page_counter + 1' ), array( 'page_id' => $this->id ), __METHOD__ );
+ $id = $this->id;
+ $method = __METHOD__;
+ $dbw->onTransactionIdle( function () use ( $dbw, $id, $method ) {
+ try {
+ $dbw->update( 'page',
+ array( 'page_counter = page_counter + 1' ),
+ array( 'page_id' => $id ),
+ $method
+ );
+ } catch ( DBError $e ) {
+ MWExceptionHandler::logException( $e );
+ }
+ } );
return;
}
# Not important enough to warrant an error page in case of failure
try {
+ // Since `hitcounter` is non-transactional, the contention is minimal
$dbw->insert( 'hitcounter', array( 'hc_id' => $this->id ), __METHOD__ );
$checkfreq = intval( $wgHitcounterUpdateFreq / 25 + 1 );
if ( rand() % $checkfreq == 0 && $dbw->lastErrno() == 0 ) {
$this->collect();
}
- } catch ( DBError $e ) {}
+ } catch ( DBError $e ) {
+ MWExceptionHandler::logException( $e );
+ }
}
protected function collect() {
@@ -68,7 +84,6 @@ class ViewCountUpdate implements DeferrableUpdate {
$dbw = wfGetDB( DB_MASTER );
$rown = $dbw->selectField( 'hitcounter', 'COUNT(*)', array(), __METHOD__ );
-
if ( $rown < $wgHitcounterUpdateFreq ) {
return;
}
@@ -76,14 +91,13 @@ class ViewCountUpdate implements DeferrableUpdate {
wfProfileIn( __METHOD__ . '-collect' );
$old_user_abort = ignore_user_abort( true );
- $dbw->lockTables( array(), array( 'hitcounter' ), __METHOD__, false );
-
$dbType = $dbw->getType();
$tabletype = $dbType == 'mysql' ? "ENGINE=HEAP " : '';
$hitcounterTable = $dbw->tableName( 'hitcounter' );
$acchitsTable = $dbw->tableName( 'acchits' );
$pageTable = $dbw->tableName( 'page' );
+ $dbw->lockTables( array(), array( 'hitcounter' ), __METHOD__, false );
$dbw->query( "CREATE TEMPORARY TABLE $acchitsTable $tabletype AS " .
"SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable " .
'GROUP BY hc_id', __METHOD__ );
diff --git a/includes/diff/ArrayDiffFormatter.php b/includes/diff/ArrayDiffFormatter.php
new file mode 100644
index 00000000..c12b76ad
--- /dev/null
+++ b/includes/diff/ArrayDiffFormatter.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Portions 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
+ */
+
+/**
+ * A pseudo-formatter that just passes along the Diff::$edits array
+ * @ingroup DifferenceEngine
+ */
+class ArrayDiffFormatter extends DiffFormatter {
+
+ /**
+ * @param Diff $diff A Diff object.
+ *
+ * @return array[] List of associative arrays, each describing a difference.
+ */
+ public function format( $diff ) {
+ $oldline = 1;
+ $newline = 1;
+ $retval = array();
+ foreach ( $diff->getEdits() as $edit ) {
+ switch ( $edit->getType() ) {
+ case 'add':
+ foreach ( $edit->getClosing() as $line ) {
+ $retval[] = array(
+ 'action' => 'add',
+ 'new' => $line,
+ 'newline' => $newline++
+ );
+ }
+ break;
+ case 'delete':
+ foreach ( $edit->getOrig() as $line ) {
+ $retval[] = array(
+ 'action' => 'delete',
+ 'old' => $line,
+ 'oldline' => $oldline++,
+ );
+ }
+ break;
+ case 'change':
+ foreach ( $edit->getOrig() as $key => $line ) {
+ $retval[] = array(
+ 'action' => 'change',
+ 'old' => $line,
+ 'new' => $edit->getClosing( $key ),
+ 'oldline' => $oldline++,
+ 'newline' => $newline++,
+ );
+ }
+ break;
+ case 'copy':
+ $oldline += count( $edit->getOrig() );
+ $newline += count( $edit->getOrig() );
+ }
+ }
+
+ return $retval;
+ }
+
+}
diff --git a/includes/diff/DairikiDiff.php b/includes/diff/DairikiDiff.php
index 298d7240..a4c0168f 100644
--- a/includes/diff/DairikiDiff.php
+++ b/includes/diff/DairikiDiff.php
@@ -30,26 +30,64 @@
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp {
- var $type;
- var $orig;
- var $closing;
+abstract class DiffOp {
- function reverse() {
- trigger_error( 'pure virtual', E_USER_ERROR );
+ /**
+ * @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 $i
+ * @return 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
*/
- function norig() {
+ public function norig() {
return $this->orig ? count( $this->orig ) : 0;
}
/**
* @return int
*/
- function nclosing() {
+ public function nclosing() {
return $this->closing ? count( $this->closing ) : 0;
}
}
@@ -59,10 +97,10 @@ class _DiffOp {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Copy extends _DiffOp {
- var $type = 'copy';
+class DiffOpCopy extends DiffOp {
+ public $type = 'copy';
- function __construct( $orig, $closing = false ) {
+ public function __construct( $orig, $closing = false ) {
if ( !is_array( $closing ) ) {
$closing = $orig;
}
@@ -71,10 +109,10 @@ class _DiffOp_Copy extends _DiffOp {
}
/**
- * @return _DiffOp_Copy
+ * @return DiffOpCopy
*/
- function reverse() {
- return new _DiffOp_Copy( $this->closing, $this->orig );
+ public function reverse() {
+ return new DiffOpCopy( $this->closing, $this->orig );
}
}
@@ -83,19 +121,19 @@ class _DiffOp_Copy extends _DiffOp {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Delete extends _DiffOp {
- var $type = 'delete';
+class DiffOpDelete extends DiffOp {
+ public $type = 'delete';
- function __construct( $lines ) {
+ public function __construct( $lines ) {
$this->orig = $lines;
$this->closing = false;
}
/**
- * @return _DiffOp_Add
+ * @return DiffOpAdd
*/
- function reverse() {
- return new _DiffOp_Add( $this->orig );
+ public function reverse() {
+ return new DiffOpAdd( $this->orig );
}
}
@@ -104,19 +142,19 @@ class _DiffOp_Delete extends _DiffOp {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Add extends _DiffOp {
- var $type = 'add';
+class DiffOpAdd extends DiffOp {
+ public $type = 'add';
- function __construct( $lines ) {
+ public function __construct( $lines ) {
$this->closing = $lines;
$this->orig = false;
}
/**
- * @return _DiffOp_Delete
+ * @return DiffOpDelete
*/
- function reverse() {
- return new _DiffOp_Delete( $this->closing );
+ public function reverse() {
+ return new DiffOpDelete( $this->closing );
}
}
@@ -125,19 +163,19 @@ class _DiffOp_Add extends _DiffOp {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Change extends _DiffOp {
- var $type = 'change';
+class DiffOpChange extends DiffOp {
+ public $type = 'change';
- function __construct( $orig, $closing ) {
+ public function __construct( $orig, $closing ) {
$this->orig = $orig;
$this->closing = $closing;
}
/**
- * @return _DiffOp_Change
+ * @return DiffOpChange
*/
- function reverse() {
- return new _DiffOp_Change( $this->closing, $this->orig );
+ public function reverse() {
+ return new DiffOpChange( $this->closing, $this->orig );
}
}
@@ -146,14 +184,14 @@ class _DiffOp_Change extends _DiffOp {
*
* The algorithm used here is mostly lifted from the perl module
* Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
- * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
+ * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
*
* More ideas are taken from:
- * http://www.ics.uci.edu/~eppstein/161/960229.html
+ * http://www.ics.uci.edu/~eppstein/161/960229.html
*
* Some ideas are (and a bit of code) are from from analyze.c, from GNU
* diffutils-2.7, which can be found at:
- * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
+ * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
*
* closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
* are my own.
@@ -165,8 +203,7 @@ class _DiffOp_Change extends _DiffOp {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffEngine {
-
+class DiffEngine {
const MAX_XREF_LENGTH = 10000;
protected $xchanged, $ychanged;
@@ -179,19 +216,20 @@ class _DiffEngine {
protected $lcs = 0;
/**
- * @param $from_lines
- * @param $to_lines
- * @return array
+ * @param string[] $from_lines
+ * @param string[] $to_lines
+ *
+ * @return DiffOp[]
*/
- function diff( $from_lines, $to_lines ) {
+ public function diff( $from_lines, $to_lines ) {
wfProfileIn( __METHOD__ );
// Diff and store locally
- $this->diff_local( $from_lines, $to_lines );
+ $this->diffLocal( $from_lines, $to_lines );
// Merge edits when possible
- $this->_shift_boundaries( $from_lines, $this->xchanged, $this->ychanged );
- $this->_shift_boundaries( $to_lines, $this->ychanged, $this->xchanged );
+ $this->shiftBoundaries( $from_lines, $this->xchanged, $this->ychanged );
+ $this->shiftBoundaries( $to_lines, $this->ychanged, $this->xchanged );
// Compute the edit operations.
$n_from = count( $from_lines );
@@ -206,12 +244,13 @@ class _DiffEngine {
// Skip matching "snake".
$copy = array();
while ( $xi < $n_from && $yi < $n_to
- && !$this->xchanged[$xi] && !$this->ychanged[$yi] ) {
+ && !$this->xchanged[$xi] && !$this->ychanged[$yi]
+ ) {
$copy[] = $from_lines[$xi++];
++$yi;
}
if ( $copy ) {
- $edits[] = new _DiffOp_Copy( $copy );
+ $edits[] = new DiffOpCopy( $copy );
}
// Find deletes & adds.
@@ -226,22 +265,23 @@ class _DiffEngine {
}
if ( $delete && $add ) {
- $edits[] = new _DiffOp_Change( $delete, $add );
+ $edits[] = new DiffOpChange( $delete, $add );
} elseif ( $delete ) {
- $edits[] = new _DiffOp_Delete( $delete );
+ $edits[] = new DiffOpDelete( $delete );
} elseif ( $add ) {
- $edits[] = new _DiffOp_Add( $add );
+ $edits[] = new DiffOpAdd( $add );
}
}
wfProfileOut( __METHOD__ );
+
return $edits;
}
/**
- * @param $from_lines
- * @param $to_lines
+ * @param string[] $from_lines
+ * @param string[] $to_lines
*/
- function diff_local( $from_lines, $to_lines ) {
+ private function diffLocal( $from_lines, $to_lines ) {
global $wgExternalDiffEngine;
wfProfileIn( __METHOD__ );
@@ -282,21 +322,21 @@ class _DiffEngine {
// Ignore lines which do not exist in both files.
for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) {
- $xhash[$this->_line_hash( $from_lines[$xi] )] = 1;
+ $xhash[$this->lineHash( $from_lines[$xi] )] = 1;
}
for ( $yi = $skip; $yi < $n_to - $endskip; $yi++ ) {
$line = $to_lines[$yi];
- if ( ( $this->ychanged[$yi] = empty( $xhash[$this->_line_hash( $line )] ) ) ) {
+ if ( ( $this->ychanged[$yi] = empty( $xhash[$this->lineHash( $line )] ) ) ) {
continue;
}
- $yhash[$this->_line_hash( $line )] = 1;
+ $yhash[$this->lineHash( $line )] = 1;
$this->yv[] = $line;
$this->yind[] = $yi;
}
for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) {
$line = $from_lines[$xi];
- if ( ( $this->xchanged[$xi] = empty( $yhash[$this->_line_hash( $line )] ) ) ) {
+ if ( ( $this->xchanged[$xi] = empty( $yhash[$this->lineHash( $line )] ) ) ) {
continue;
}
$this->xv[] = $line;
@@ -304,17 +344,19 @@ class _DiffEngine {
}
// Find the LCS.
- $this->_compareseq( 0, count( $this->xv ), 0, count( $this->yv ) );
+ $this->compareSeq( 0, count( $this->xv ), 0, count( $this->yv ) );
}
wfProfileOut( __METHOD__ );
}
/**
* Returns the whole line if it's small enough, or the MD5 hash otherwise
- * @param $line string
+ *
+ * @param string $line
+ *
* @return string
*/
- function _line_hash( $line ) {
+ private function lineHash( $line ) {
if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
return md5( $line );
} else {
@@ -338,14 +380,16 @@ class _DiffEngine {
* of the two files do not match, and likewise that the last lines do not
* match. The caller must trim matching lines from the beginning and end
* of the portions it is going to specify.
- * @param $xoff
- * @param $xlim
- * @param $yoff
- * @param $ylim
- * @param $nchunks
- * @return array
+ *
+ * @param int $xoff
+ * @param int $xlim
+ * @param int $yoff
+ * @param int $ylim
+ * @param int $nchunks
+ *
+ * @return array List of two elements, integer and array[].
*/
- function _diag( $xoff, $xlim, $yoff, $ylim, $nchunks ) {
+ private function diag( $xoff, $xlim, $yoff, $ylim, $nchunks ) {
$flip = false;
if ( $xlim - $xoff > $ylim - $yoff ) {
@@ -375,28 +419,33 @@ class _DiffEngine {
for ( $chunk = 0; $chunk < $nchunks; $chunk++ ) {
if ( $chunk > 0 ) {
for ( $i = 0; $i <= $this->lcs; $i++ ) {
- $ymids[$i][$chunk -1] = $this->seq[$i];
+ $ymids[$i][$chunk - 1] = $this->seq[$i];
}
}
- $x1 = $xoff + (int)( ( $numer + ( $xlim -$xoff ) * $chunk ) / $nchunks );
+ $x1 = $xoff + (int)( ( $numer + ( $xlim - $xoff ) * $chunk ) / $nchunks );
+ // @codingStandardsIgnoreFile Ignore Squiz.WhiteSpace.SemicolonSpacing.Incorrect
for ( ; $x < $x1; $x++ ) {
+ // @codingStandardsIgnoreEnd
$line = $flip ? $this->yv[$x] : $this->xv[$x];
if ( empty( $ymatches[$line] ) ) {
continue;
}
+
+ $k = 0;
$matches = $ymatches[$line];
reset( $matches );
while ( list( , $y ) = each( $matches ) ) {
if ( empty( $this->in_seq[$y] ) ) {
- $k = $this->_lcs_pos( $y );
+ $k = $this->lcsPos( $y );
assert( '$k > 0' );
- $ymids[$k] = $ymids[$k -1];
+ $ymids[$k] = $ymids[$k - 1];
break;
}
}
+
while ( list( , $y ) = each( $matches ) ) {
- if ( $y > $this->seq[$k -1] ) {
+ if ( $y > $this->seq[$k - 1] ) {
assert( '$y < $this->seq[$k]' );
// Optimization: this is a common case:
// next match is just replacing previous match.
@@ -404,9 +453,9 @@ class _DiffEngine {
$this->seq[$k] = $y;
$this->in_seq[$y] = 1;
} elseif ( empty( $this->in_seq[$y] ) ) {
- $k = $this->_lcs_pos( $y );
+ $k = $this->lcsPos( $y );
assert( '$k > 0' );
- $ymids[$k] = $ymids[$k -1];
+ $ymids[$k] = $ymids[$k - 1];
}
}
}
@@ -425,14 +474,16 @@ class _DiffEngine {
}
/**
- * @param $ypos
+ * @param int $ypos
+ *
* @return int
*/
- function _lcs_pos( $ypos ) {
+ private function lcsPos( $ypos ) {
$end = $this->lcs;
if ( $end == 0 || $ypos > $this->seq[$end] ) {
$this->seq[++$this->lcs] = $ypos;
$this->in_seq[$ypos] = 1;
+
return $this->lcs;
}
@@ -451,6 +502,7 @@ class _DiffEngine {
$this->in_seq[$this->seq[$end]] = false;
$this->seq[$end] = $ypos;
$this->in_seq[$ypos] = 1;
+
return $end;
}
@@ -465,12 +517,13 @@ class _DiffEngine {
*
* Note that XLIM, YLIM are exclusive bounds.
* All line numbers are origin-0 and discarded lines are not counted.
- * @param $xoff
- * @param $xlim
- * @param $yoff
- * @param $ylim
+ *
+ * @param int $xoff
+ * @param int $xlim
+ * @param int $yoff
+ * @param int $ylim
*/
- function _compareseq( $xoff, $xlim, $yoff, $ylim ) {
+ private function compareSeq( $xoff, $xlim, $yoff, $ylim ) {
// Slide down the bottom initial diagonal.
while ( $xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff] ) {
++$xoff;
@@ -479,7 +532,8 @@ class _DiffEngine {
// Slide up the top initial diagonal.
while ( $xlim > $xoff && $ylim > $yoff
- && $this->xv[$xlim - 1] == $this->yv[$ylim - 1] ) {
+ && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]
+ ) {
--$xlim;
--$ylim;
}
@@ -491,7 +545,7 @@ class _DiffEngine {
// $nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
// $nchunks = max(2,min(8,(int)$nchunks));
$nchunks = min( 7, $xlim - $xoff, $ylim - $yoff ) + 1;
- list( $lcs, $seps ) = $this->_diag( $xoff, $xlim, $yoff, $ylim, $nchunks );
+ list( $lcs, $seps ) = $this->diag( $xoff, $xlim, $yoff, $ylim, $nchunks );
}
if ( $lcs == 0 ) {
@@ -508,7 +562,7 @@ class _DiffEngine {
reset( $seps );
$pt1 = $seps[0];
while ( $pt2 = next( $seps ) ) {
- $this->_compareseq( $pt1[0], $pt2[0], $pt1[1], $pt2[1] );
+ $this->compareSeq( $pt1[0], $pt2[0], $pt1[1], $pt2[1] );
$pt1 = $pt2;
}
}
@@ -527,7 +581,7 @@ class _DiffEngine {
*
* This is extracted verbatim from analyze.c (GNU diffutils-2.7).
*/
- function _shift_boundaries( $lines, &$changed, $other_changed ) {
+ private function shiftBoundaries( $lines, &$changed, $other_changed ) {
wfProfileIn( __METHOD__ );
$i = 0;
$j = 0;
@@ -552,7 +606,7 @@ class _DiffEngine {
$j++;
}
- while ( $i < $len && ! $changed[$i] ) {
+ while ( $i < $len && !$changed[$i] ) {
assert( '$j < $other_len && ! $other_changed[$j]' );
$i++;
$j++;
@@ -654,20 +708,30 @@ class _DiffEngine {
* @ingroup DifferenceEngine
*/
class Diff {
- var $edits;
+
+ /**
+ * @var DiffOp[]
+ */
+ public $edits;
/**
* Constructor.
* Computes diff between sequences of strings.
*
- * @param $from_lines array An array of strings.
- * (Typically these are lines from a file.)
- * @param $to_lines array An array 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.
*/
- function __construct( $from_lines, $to_lines ) {
- $eng = new _DiffEngine;
+ public function __construct( $from_lines, $to_lines ) {
+ $eng = new DiffEngine;
$this->edits = $eng->diff( $from_lines, $to_lines );
- // $this->_check($from_lines, $to_lines);
+ }
+
+ /**
+ * @return DiffOp[]
+ */
+ public function getEdits() {
+ return $this->edits;
}
/**
@@ -675,17 +739,20 @@ class Diff {
*
* SYNOPSIS:
*
- * $diff = new Diff($lines1, $lines2);
- * $rev = $diff->reverse();
+ * $diff = new Diff($lines1, $lines2);
+ * $rev = $diff->reverse();
+ *
* @return Object A Diff object representing the inverse of the
- * original diff.
+ * original diff.
*/
- function reverse() {
+ public function reverse() {
$rev = $this;
$rev->edits = array();
+ /** @var DiffOp $edit */
foreach ( $this->edits as $edit ) {
$rev->edits[] = $edit->reverse();
}
+
return $rev;
}
@@ -694,12 +761,13 @@ class Diff {
*
* @return bool True if two sequences were identical.
*/
- function isEmpty() {
+ public function isEmpty() {
foreach ( $this->edits as $edit ) {
if ( $edit->type != 'copy' ) {
return false;
}
}
+
return true;
}
@@ -710,13 +778,14 @@ class Diff {
*
* @return int The length of the LCS.
*/
- function lcs() {
+ public function lcs() {
$lcs = 0;
foreach ( $this->edits as $edit ) {
if ( $edit->type == 'copy' ) {
$lcs += count( $edit->orig );
}
}
+
return $lcs;
}
@@ -726,9 +795,9 @@ class Diff {
* This reconstructs the $from_lines parameter passed to the
* constructor.
*
- * @return array The original sequence of strings.
+ * @return string[] The original sequence of strings.
*/
- function orig() {
+ public function orig() {
$lines = array();
foreach ( $this->edits as $edit ) {
@@ -736,6 +805,7 @@ class Diff {
array_splice( $lines, count( $lines ), 0, $edit->orig );
}
}
+
return $lines;
}
@@ -745,9 +815,9 @@ class Diff {
* This reconstructs the $to_lines parameter passed to the
* constructor.
*
- * @return array The sequence of strings.
+ * @return string[] The sequence of strings.
*/
- function closing() {
+ public function closing() {
$lines = array();
foreach ( $this->edits as $edit ) {
@@ -755,44 +825,8 @@ class Diff {
array_splice( $lines, count( $lines ), 0, $edit->closing );
}
}
- return $lines;
- }
-
- /**
- * Check a Diff for validity.
- *
- * This is here only for debugging purposes.
- * @param $from_lines
- * @param $to_lines
- */
- function _check( $from_lines, $to_lines ) {
- wfProfileIn( __METHOD__ );
- if ( serialize( $from_lines ) != serialize( $this->orig() ) ) {
- trigger_error( "Reconstructed original doesn't match", E_USER_ERROR );
- }
- if ( serialize( $to_lines ) != serialize( $this->closing() ) ) {
- trigger_error( "Reconstructed closing doesn't match", E_USER_ERROR );
- }
-
- $rev = $this->reverse();
- if ( serialize( $to_lines ) != serialize( $rev->orig() ) ) {
- trigger_error( "Reversed original doesn't match", E_USER_ERROR );
- }
- if ( serialize( $from_lines ) != serialize( $rev->closing() ) ) {
- trigger_error( "Reversed closing doesn't match", E_USER_ERROR );
- }
-
- $prevtype = 'none';
- foreach ( $this->edits as $edit ) {
- if ( $prevtype == $edit->type ) {
- trigger_error( 'Edit sequence is non-optimal', E_USER_ERROR );
- }
- $prevtype = $edit->type;
- }
- $lcs = $this->lcs();
- trigger_error( 'Diff okay: LCS = ' . $lcs, E_USER_NOTICE );
- wfProfileOut( __METHOD__ );
+ return $lines;
}
}
@@ -811,21 +845,18 @@ class MappedDiff extends Diff {
* case-insensitve diffs, or diffs which ignore
* changes in white-space.
*
- * @param $from_lines array An array of strings.
- * (Typically these are lines from a file.)
- *
- * @param $to_lines array An array of strings.
- *
- * @param $mapped_from_lines array This array should
- * have the same size number of elements as $from_lines.
- * The elements in $mapped_from_lines and
- * $mapped_to_lines are what is actually compared
- * when computing the diff.
- *
- * @param $mapped_to_lines array This array should
- * have the same number of elements as $to_lines.
+ * @param string[] $from_lines An array of strings.
+ * Typically these are lines from a file.
+ * @param string[] $to_lines An array of strings.
+ * @param string[] $mapped_from_lines This array should
+ * have the same size number of elements as $from_lines.
+ * The elements in $mapped_from_lines and
+ * $mapped_to_lines are what is actually compared
+ * when computing the diff.
+ * @param string[] $mapped_to_lines This array should
+ * have the same number of elements as $to_lines.
*/
- function __construct( $from_lines, $to_lines,
+ public function __construct( $from_lines, $to_lines,
$mapped_from_lines, $mapped_to_lines ) {
wfProfileIn( __METHOD__ );
@@ -835,7 +866,8 @@ class MappedDiff extends Diff {
parent::__construct( $mapped_from_lines, $mapped_to_lines );
$xi = $yi = 0;
- for ( $i = 0; $i < count( $this->edits ); $i++ ) {
+ $editCount = count( $this->edits );
+ for ( $i = 0; $i < $editCount; $i++ ) {
$orig = &$this->edits[$i]->orig;
if ( is_array( $orig ) ) {
$orig = array_slice( $from_lines, $xi, count( $orig ) );
@@ -853,362 +885,63 @@ class MappedDiff extends Diff {
}
/**
- * A class to format Diffs
- *
- * This class formats the diff in classic diff format.
- * It is intended that this class be customized via inheritance,
- * to obtain fancier outputs.
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class DiffFormatter {
- /**
- * Number of leading context "lines" to preserve.
- *
- * This should be left at zero for this class, but subclasses
- * may want to set this to other values.
- */
- var $leading_context_lines = 0;
-
- /**
- * Number of trailing context "lines" to preserve.
- *
- * This should be left at zero for this class, but subclasses
- * may want to set this to other values.
- */
- var $trailing_context_lines = 0;
-
- /**
- * Format a diff.
- *
- * @param $diff Diff A Diff object.
- * @return string The formatted output.
- */
- function format( $diff ) {
- wfProfileIn( __METHOD__ );
-
- $xi = $yi = 1;
- $block = false;
- $context = array();
-
- $nlead = $this->leading_context_lines;
- $ntrail = $this->trailing_context_lines;
-
- $this->_start_diff();
-
- foreach ( $diff->edits as $edit ) {
- if ( $edit->type == 'copy' ) {
- if ( is_array( $block ) ) {
- if ( count( $edit->orig ) <= $nlead + $ntrail ) {
- $block[] = $edit;
- } else {
- if ( $ntrail ) {
- $context = array_slice( $edit->orig, 0, $ntrail );
- $block[] = new _DiffOp_Copy( $context );
- }
- $this->_block( $x0, $ntrail + $xi - $x0,
- $y0, $ntrail + $yi - $y0,
- $block );
- $block = false;
- }
- }
- $context = $edit->orig;
- } else {
- if ( !is_array( $block ) ) {
- $context = array_slice( $context, count( $context ) - $nlead );
- $x0 = $xi - count( $context );
- $y0 = $yi - count( $context );
- $block = array();
- if ( $context ) {
- $block[] = new _DiffOp_Copy( $context );
- }
- }
- $block[] = $edit;
- }
-
- if ( $edit->orig ) {
- $xi += count( $edit->orig );
- }
- if ( $edit->closing ) {
- $yi += count( $edit->closing );
- }
- }
-
- if ( is_array( $block ) ) {
- $this->_block( $x0, $xi - $x0,
- $y0, $yi - $y0,
- $block );
- }
-
- $end = $this->_end_diff();
- wfProfileOut( __METHOD__ );
- return $end;
- }
-
- /**
- * @param $xbeg
- * @param $xlen
- * @param $ybeg
- * @param $ylen
- * @param $edits
- */
- function _block( $xbeg, $xlen, $ybeg, $ylen, &$edits ) {
- wfProfileIn( __METHOD__ );
- $this->_start_block( $this->_block_header( $xbeg, $xlen, $ybeg, $ylen ) );
- foreach ( $edits as $edit ) {
- if ( $edit->type == 'copy' ) {
- $this->_context( $edit->orig );
- } elseif ( $edit->type == 'add' ) {
- $this->_added( $edit->closing );
- } elseif ( $edit->type == 'delete' ) {
- $this->_deleted( $edit->orig );
- } elseif ( $edit->type == 'change' ) {
- $this->_changed( $edit->orig, $edit->closing );
- } else {
- trigger_error( 'Unknown edit type', E_USER_ERROR );
- }
- }
- $this->_end_block();
- wfProfileOut( __METHOD__ );
- }
-
- function _start_diff() {
- ob_start();
- }
-
- /**
- * @return string
- */
- function _end_diff() {
- $val = ob_get_contents();
- ob_end_clean();
- return $val;
- }
-
- /**
- * @param $xbeg
- * @param $xlen
- * @param $ybeg
- * @param $ylen
- * @return string
- */
- function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
- if ( $xlen > 1 ) {
- $xbeg .= ',' . ( $xbeg + $xlen - 1 );
- }
- if ( $ylen > 1 ) {
- $ybeg .= ',' . ( $ybeg + $ylen - 1 );
- }
-
- return $xbeg . ( $xlen ? ( $ylen ? 'c' : 'd' ) : 'a' ) . $ybeg;
- }
-
- function _start_block( $header ) {
- echo $header . "\n";
- }
-
- function _end_block() {
- }
-
- /**
- * @param $lines
- * @param $prefix string
- */
- function _lines( $lines, $prefix = ' ' ) {
- foreach ( $lines as $line ) {
- echo "$prefix $line\n";
- }
- }
-
- /**
- * @param $lines
- */
- function _context( $lines ) {
- $this->_lines( $lines );
- }
-
- /**
- * @param $lines
- */
- function _added( $lines ) {
- $this->_lines( $lines, '>' );
- }
-
- /**
- * @param $lines
- */
- function _deleted( $lines ) {
- $this->_lines( $lines, '<' );
- }
-
- /**
- * @param $orig
- * @param $closing
- */
- function _changed( $orig, $closing ) {
- $this->_deleted( $orig );
- echo "---\n";
- $this->_added( $closing );
- }
-}
-
-/**
- * A formatter that outputs unified diffs
- * @ingroup DifferenceEngine
- */
-class UnifiedDiffFormatter extends DiffFormatter {
- var $leading_context_lines = 2;
- var $trailing_context_lines = 2;
-
- /**
- * @param $lines
- */
- function _added( $lines ) {
- $this->_lines( $lines, '+' );
- }
-
- /**
- * @param $lines
- */
- function _deleted( $lines ) {
- $this->_lines( $lines, '-' );
- }
-
- /**
- * @param $orig
- * @param $closing
- */
- function _changed( $orig, $closing ) {
- $this->_deleted( $orig );
- $this->_added( $closing );
- }
-
- /**
- * @param $xbeg
- * @param $xlen
- * @param $ybeg
- * @param $ylen
- * @return string
- */
- function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
- return "@@ -$xbeg,$xlen +$ybeg,$ylen @@";
- }
-}
-
-/**
- * A pseudo-formatter that just passes along the Diff::$edits array
- * @ingroup DifferenceEngine
- */
-class ArrayDiffFormatter extends DiffFormatter {
-
- /**
- * @param $diff
- * @return array
- */
- function format( $diff ) {
- $oldline = 1;
- $newline = 1;
- $retval = array();
- foreach ( $diff->edits as $edit ) {
- switch ( $edit->type ) {
- case 'add':
- foreach ( $edit->closing as $l ) {
- $retval[] = array(
- 'action' => 'add',
- 'new' => $l,
- 'newline' => $newline++
- );
- }
- break;
- case 'delete':
- foreach ( $edit->orig as $l ) {
- $retval[] = array(
- 'action' => 'delete',
- 'old' => $l,
- 'oldline' => $oldline++,
- );
- }
- break;
- case 'change':
- foreach ( $edit->orig as $i => $l ) {
- $retval[] = array(
- 'action' => 'change',
- 'old' => $l,
- 'new' => isset( $edit->closing[$i] ) ? $edit->closing[$i] : null,
- 'oldline' => $oldline++,
- 'newline' => $newline++,
- );
- }
- break;
- case 'copy':
- $oldline += count( $edit->orig );
- $newline += count( $edit->orig );
- }
- }
- return $retval;
- }
-}
-
-/**
* Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
*/
-define( 'NBSP', '&#160;' ); // iso-8859-x non-breaking space.
-
/**
* @todo document
* @private
* @ingroup DifferenceEngine
*/
-class _HWLDF_WordAccumulator {
- function __construct() {
- $this->_lines = array();
- $this->_line = '';
- $this->_group = '';
- $this->_tag = '';
- }
+class HWLDFWordAccumulator {
+ public $insClass = ' class="diffchange diffchange-inline"';
+ public $delClass = ' class="diffchange diffchange-inline"';
+
+ private $lines = array();
+ private $line = '';
+ private $group = '';
+ private $tag = '';
/**
- * @param $new_tag
+ * @param string $new_tag
*/
- function _flushGroup( $new_tag ) {
- if ( $this->_group !== '' ) {
- if ( $this->_tag == 'ins' ) {
- $this->_line .= '<ins class="diffchange diffchange-inline">' .
- htmlspecialchars( $this->_group ) . '</ins>';
- } elseif ( $this->_tag == 'del' ) {
- $this->_line .= '<del class="diffchange diffchange-inline">' .
- htmlspecialchars( $this->_group ) . '</del>';
+ private function flushGroup( $new_tag ) {
+ if ( $this->group !== '' ) {
+ if ( $this->tag == 'ins' ) {
+ $this->line .= "<ins{$this->insClass}>" .
+ htmlspecialchars( $this->group ) . '</ins>';
+ } elseif ( $this->tag == 'del' ) {
+ $this->line .= "<del{$this->delClass}>" .
+ htmlspecialchars( $this->group ) . '</del>';
} else {
- $this->_line .= htmlspecialchars( $this->_group );
+ $this->line .= htmlspecialchars( $this->group );
}
}
- $this->_group = '';
- $this->_tag = $new_tag;
+ $this->group = '';
+ $this->tag = $new_tag;
}
/**
- * @param $new_tag
+ * @param string $new_tag
*/
- function _flushLine( $new_tag ) {
- $this->_flushGroup( $new_tag );
- if ( $this->_line != '' ) {
- array_push( $this->_lines, $this->_line );
+ private function flushLine( $new_tag ) {
+ $this->flushGroup( $new_tag );
+ if ( $this->line != '' ) {
+ array_push( $this->lines, $this->line );
} else {
# make empty lines visible by inserting an NBSP
- array_push( $this->_lines, NBSP );
+ array_push( $this->lines, '&#160;' );
}
- $this->_line = '';
+ $this->line = '';
}
/**
- * @param $words
- * @param $tag string
+ * @param string[] $words
+ * @param string $tag
*/
- function addWords( $words, $tag = '' ) {
- if ( $tag != $this->_tag ) {
- $this->_flushGroup( $tag );
+ public function addWords( $words, $tag = '' ) {
+ if ( $tag != $this->tag ) {
+ $this->flushGroup( $tag );
}
foreach ( $words as $word ) {
@@ -1217,20 +950,21 @@ class _HWLDF_WordAccumulator {
continue;
}
if ( $word[0] == "\n" ) {
- $this->_flushLine( $tag );
+ $this->flushLine( $tag );
$word = substr( $word, 1 );
}
assert( '!strstr( $word, "\n" )' );
- $this->_group .= $word;
+ $this->group .= $word;
}
}
/**
- * @return array
+ * @return string[]
*/
- function getLines() {
- $this->_flushLine( '~done' );
- return $this->_lines;
+ public function getLines() {
+ $this->flushLine( '~done' );
+
+ return $this->lines;
}
}
@@ -1243,25 +977,26 @@ class WordLevelDiff extends MappedDiff {
const MAX_LINE_LENGTH = 10000;
/**
- * @param $orig_lines
- * @param $closing_lines
+ * @param string[] $orig_lines
+ * @param string[] $closing_lines
*/
- function __construct( $orig_lines, $closing_lines ) {
+ public function __construct( $orig_lines, $closing_lines ) {
wfProfileIn( __METHOD__ );
- list( $orig_words, $orig_stripped ) = $this->_split( $orig_lines );
- list( $closing_words, $closing_stripped ) = $this->_split( $closing_lines );
+ list( $orig_words, $orig_stripped ) = $this->split( $orig_lines );
+ list( $closing_words, $closing_stripped ) = $this->split( $closing_lines );
parent::__construct( $orig_words, $closing_words,
- $orig_stripped, $closing_stripped );
+ $orig_stripped, $closing_stripped );
wfProfileOut( __METHOD__ );
}
/**
- * @param $lines
- * @return array
+ * @param string[] $lines
+ *
+ * @return array[]
*/
- function _split( $lines ) {
+ private function split( $lines ) {
wfProfileIn( __METHOD__ );
$words = array();
@@ -1282,8 +1017,8 @@ class WordLevelDiff extends MappedDiff {
} else {
$m = array();
if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
- $line, $m ) )
- {
+ $line, $m )
+ ) {
foreach ( $m[0] as $word ) {
$words[] = $word;
}
@@ -1294,15 +1029,16 @@ class WordLevelDiff extends MappedDiff {
}
}
wfProfileOut( __METHOD__ );
+
return array( $words, $stripped );
}
/**
- * @return array
+ * @return string[]
*/
- function orig() {
+ public function orig() {
wfProfileIn( __METHOD__ );
- $orig = new _HWLDF_WordAccumulator;
+ $orig = new HWLDFWordAccumulator;
foreach ( $this->edits as $edit ) {
if ( $edit->type == 'copy' ) {
@@ -1313,15 +1049,16 @@ class WordLevelDiff extends MappedDiff {
}
$lines = $orig->getLines();
wfProfileOut( __METHOD__ );
+
return $lines;
}
/**
- * @return array
+ * @return string[]
*/
- function closing() {
+ public function closing() {
wfProfileIn( __METHOD__ );
- $closing = new _HWLDF_WordAccumulator;
+ $closing = new HWLDFWordAccumulator;
foreach ( $this->edits as $edit ) {
if ( $edit->type == 'copy' ) {
@@ -1332,164 +1069,8 @@ class WordLevelDiff extends MappedDiff {
}
$lines = $closing->getLines();
wfProfileOut( __METHOD__ );
- return $lines;
- }
-}
-/**
- * Wikipedia Table style diff formatter.
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class TableDiffFormatter extends DiffFormatter {
- function __construct() {
- $this->leading_context_lines = 2;
- $this->trailing_context_lines = 2;
- }
-
- /**
- * @static
- * @param $msg
- * @return mixed
- */
- public static function escapeWhiteSpace( $msg ) {
- $msg = preg_replace( '/^ /m', '&#160; ', $msg );
- $msg = preg_replace( '/ $/m', ' &#160;', $msg );
- $msg = preg_replace( '/ /', '&#160; ', $msg );
- return $msg;
- }
-
- /**
- * @param $xbeg
- * @param $xlen
- * @param $ybeg
- * @param $ylen
- * @return string
- */
- function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
- $r = '<tr><td colspan="2" class="diff-lineno"><!--LINE ' . $xbeg . "--></td>\n" .
- '<td colspan="2" class="diff-lineno"><!--LINE ' . $ybeg . "--></td></tr>\n";
- return $r;
- }
-
- /**
- * @param $header
- */
- function _start_block( $header ) {
- echo $header;
- }
-
- function _end_block() {
- }
-
- function _lines( $lines, $prefix = ' ', $color = 'white' ) {
- }
-
- /**
- * HTML-escape parameter before calling this
- * @param $line
- * @return string
- */
- function addedLine( $line ) {
- return $this->wrapLine( '+', 'diff-addedline', $line );
- }
-
- /**
- * HTML-escape parameter before calling this
- * @param $line
- * @return string
- */
- function deletedLine( $line ) {
- return $this->wrapLine( '−', 'diff-deletedline', $line );
- }
-
- /**
- * HTML-escape parameter before calling this
- * @param $line
- * @return string
- */
- function contextLine( $line ) {
- return $this->wrapLine( '&#160;', 'diff-context', $line );
- }
-
- /**
- * @param $marker
- * @param $class
- * @param $line
- * @return string
- */
- private function wrapLine( $marker, $class, $line ) {
- if ( $line !== '' ) {
- // The <div> wrapper is needed for 'overflow: auto' style to scroll properly
- $line = Xml::tags( 'div', null, $this->escapeWhiteSpace( $line ) );
- }
- return "<td class='diff-marker'>$marker</td><td class='$class'>$line</td>";
- }
-
- /**
- * @return string
- */
- function emptyLine() {
- return '<td colspan="2">&#160;</td>';
- }
-
- /**
- * @param $lines array
- */
- function _added( $lines ) {
- foreach ( $lines as $line ) {
- echo '<tr>' . $this->emptyLine() .
- $this->addedLine( '<ins class="diffchange">' .
- htmlspecialchars( $line ) . '</ins>' ) . "</tr>\n";
- }
- }
-
- /**
- * @param $lines
- */
- function _deleted( $lines ) {
- foreach ( $lines as $line ) {
- echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
- htmlspecialchars( $line ) . '</del>' ) .
- $this->emptyLine() . "</tr>\n";
- }
- }
-
- /**
- * @param $lines
- */
- function _context( $lines ) {
- foreach ( $lines as $line ) {
- echo '<tr>' .
- $this->contextLine( htmlspecialchars( $line ) ) .
- $this->contextLine( htmlspecialchars( $line ) ) . "</tr>\n";
- }
+ return $lines;
}
- /**
- * @param $orig
- * @param $closing
- */
- function _changed( $orig, $closing ) {
- wfProfileIn( __METHOD__ );
-
- $diff = new WordLevelDiff( $orig, $closing );
- $del = $diff->orig();
- $add = $diff->closing();
-
- # Notice that WordLevelDiff returns HTML-escaped output.
- # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
-
- while ( $line = array_shift( $del ) ) {
- $aline = array_shift( $add );
- echo '<tr>' . $this->deletedLine( $line ) .
- $this->addedLine( $aline ) . "</tr>\n";
- }
- foreach ( $add as $line ) { # If any leftovers
- echo '<tr>' . $this->emptyLine() .
- $this->addedLine( $line ) . "</tr>\n";
- }
- wfProfileOut( __METHOD__ );
- }
}
diff --git a/includes/diff/DiffFormatter.php b/includes/diff/DiffFormatter.php
new file mode 100644
index 00000000..40df0d75
--- /dev/null
+++ b/includes/diff/DiffFormatter.php
@@ -0,0 +1,247 @@
+<?php
+/**
+ * Base for diff rendering classes. Portions 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
+ */
+
+/**
+ * Base class for diff formatters
+ *
+ * This class formats the diff in classic diff format.
+ * It is intended that this class be customized via inheritance,
+ * to obtain fancier outputs.
+ * @todo document
+ * @ingroup DifferenceEngine
+ */
+abstract class DiffFormatter {
+
+ /** @var int Number of leading context "lines" to preserve.
+ *
+ * This should be left at zero for this class, but subclasses
+ * may want to set this to other values.
+ */
+ protected $leadingContextLines = 0;
+
+ /** @var int Number of trailing context "lines" to preserve.
+ *
+ * This should be left at zero for this class, but subclasses
+ * may want to set this to other values.
+ */
+ protected $trailingContextLines = 0;
+
+ /**
+ * Format a diff.
+ *
+ * @param Diff $diff
+ *
+ * @return string The formatted output.
+ */
+ public function format( $diff ) {
+ wfProfileIn( __METHOD__ );
+
+ $xi = $yi = 1;
+ $block = false;
+ $context = array();
+
+ $nlead = $this->leadingContextLines;
+ $ntrail = $this->trailingContextLines;
+
+ $this->startDiff();
+
+ // Initialize $x0 and $y0 to prevent IDEs from getting confused.
+ $x0 = $y0 = 0;
+ foreach ( $diff->edits as $edit ) {
+ if ( $edit->type == 'copy' ) {
+ if ( is_array( $block ) ) {
+ if ( count( $edit->orig ) <= $nlead + $ntrail ) {
+ $block[] = $edit;
+ } else {
+ if ( $ntrail ) {
+ $context = array_slice( $edit->orig, 0, $ntrail );
+ $block[] = new DiffOpCopy( $context );
+ }
+ $this->block( $x0, $ntrail + $xi - $x0,
+ $y0, $ntrail + $yi - $y0,
+ $block );
+ $block = false;
+ }
+ }
+ $context = $edit->orig;
+ } else {
+ if ( !is_array( $block ) ) {
+ $context = array_slice( $context, count( $context ) - $nlead );
+ $x0 = $xi - count( $context );
+ $y0 = $yi - count( $context );
+ $block = array();
+ if ( $context ) {
+ $block[] = new DiffOpCopy( $context );
+ }
+ }
+ $block[] = $edit;
+ }
+
+ if ( $edit->orig ) {
+ $xi += count( $edit->orig );
+ }
+ if ( $edit->closing ) {
+ $yi += count( $edit->closing );
+ }
+ }
+
+ if ( is_array( $block ) ) {
+ $this->block( $x0, $xi - $x0,
+ $y0, $yi - $y0,
+ $block );
+ }
+
+ $end = $this->endDiff();
+ wfProfileOut( __METHOD__ );
+
+ return $end;
+ }
+
+ /**
+ * @param int $xbeg
+ * @param int $xlen
+ * @param int $ybeg
+ * @param int $ylen
+ * @param array $edits
+ *
+ * @throws MWException If the edit type is not known.
+ */
+ protected function block( $xbeg, $xlen, $ybeg, $ylen, &$edits ) {
+ wfProfileIn( __METHOD__ );
+ $this->startBlock( $this->blockHeader( $xbeg, $xlen, $ybeg, $ylen ) );
+ foreach ( $edits as $edit ) {
+ if ( $edit->type == 'copy' ) {
+ $this->context( $edit->orig );
+ } elseif ( $edit->type == 'add' ) {
+ $this->added( $edit->closing );
+ } elseif ( $edit->type == 'delete' ) {
+ $this->deleted( $edit->orig );
+ } elseif ( $edit->type == 'change' ) {
+ $this->changed( $edit->orig, $edit->closing );
+ } else {
+ throw new MWException( "Unknown edit type: {$edit->type}" );
+ }
+ }
+ $this->endBlock();
+ wfProfileOut( __METHOD__ );
+ }
+
+ protected function startDiff() {
+ ob_start();
+ }
+
+ /**
+ * @return string
+ */
+ protected function endDiff() {
+ $val = ob_get_contents();
+ ob_end_clean();
+
+ return $val;
+ }
+
+ /**
+ * @param int $xbeg
+ * @param int $xlen
+ * @param int $ybeg
+ * @param int $ylen
+ *
+ * @return string
+ */
+ protected function blockHeader( $xbeg, $xlen, $ybeg, $ylen ) {
+ if ( $xlen > 1 ) {
+ $xbeg .= ',' . ( $xbeg + $xlen - 1 );
+ }
+ if ( $ylen > 1 ) {
+ $ybeg .= ',' . ( $ybeg + $ylen - 1 );
+ }
+
+ return $xbeg . ( $xlen ? ( $ylen ? 'c' : 'd' ) : 'a' ) . $ybeg;
+ }
+
+ /**
+ * Called at the start of a block of connected edits.
+ * This default implementation writes the header and a newline to the output buffer.
+ *
+ * @param string $header
+ */
+ protected function startBlock( $header ) {
+ echo $header . "\n";
+ }
+
+ /**
+ * Called at the end of a block of connected edits.
+ * This default implementation does nothing.
+ */
+ protected function endBlock() {
+ }
+
+ /**
+ * Writes all (optionally prefixed) lines to the output buffer, separated by newlines.
+ *
+ * @param string[] $lines
+ * @param string $prefix
+ */
+ protected function lines( $lines, $prefix = ' ' ) {
+ foreach ( $lines as $line ) {
+ echo "$prefix $line\n";
+ }
+ }
+
+ /**
+ * @param string[] $lines
+ */
+ protected function context( $lines ) {
+ $this->lines( $lines );
+ }
+
+ /**
+ * @param string[] $lines
+ */
+ protected function added( $lines ) {
+ $this->lines( $lines, '>' );
+ }
+
+ /**
+ * @param string[] $lines
+ */
+ protected function deleted( $lines ) {
+ $this->lines( $lines, '<' );
+ }
+
+ /**
+ * Writes the two sets of lines to the output buffer, separated by "---" and a newline.
+ *
+ * @param string[] $orig
+ * @param string[] $closing
+ */
+ protected function changed( $orig, $closing ) {
+ $this->deleted( $orig );
+ echo "---\n";
+ $this->added( $closing );
+ }
+
+}
diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php
index e436f58d..50e08ca1 100644
--- a/includes/diff/DifferenceEngine.php
+++ b/includes/diff/DifferenceEngine.php
@@ -34,60 +34,80 @@ define( 'MW_DIFF_VERSION', '1.11a' );
* @ingroup DifferenceEngine
*/
class DifferenceEngine extends ContextSource {
- /**#@+
- * @private
- */
- var $mOldid, $mNewid;
- var $mOldTags, $mNewTags;
- /**
- * @var Content
- */
- var $mOldContent, $mNewContent;
+
+ /** @var int */
+ public $mOldid;
+
+ /** @var int */
+ public $mNewid;
+
+ private $mOldTags;
+ private $mNewTags;
+
+ /** @var Content */
+ public $mOldContent;
+
+ /** @var Content */
+ public $mNewContent;
+
+ /** @var Language */
protected $mDiffLang;
- /**
- * @var Title
- */
- var $mOldPage, $mNewPage;
+ /** @var Title */
+ public $mOldPage;
- /**
- * @var Revision
- */
- var $mOldRev, $mNewRev;
- private $mRevisionsIdsLoaded = false; // Have the revisions IDs been loaded
- var $mRevisionsLoaded = false; // Have the revisions been loaded
- var $mTextLoaded = 0; // How many text blobs have been loaded, 0, 1 or 2?
- var $mCacheHit = false; // Was the diff fetched from cache?
+ /** @var Title */
+ public $mNewPage;
+
+ /** @var Revision */
+ public $mOldRev;
+
+ /** @var Revision */
+ public $mNewRev;
+
+ /** @var bool Have the revisions IDs been loaded */
+ private $mRevisionsIdsLoaded = false;
+
+ /** @var bool Have the revisions been loaded */
+ public $mRevisionsLoaded = false;
+
+ /** @var int How many text blobs have been loaded, 0, 1 or 2? */
+ public $mTextLoaded = 0;
+
+ /** @var bool Was the diff fetched from cache? */
+ public $mCacheHit = false;
/**
* Set this to true to add debug info to the HTML output.
* Warning: this may cause RSS readers to spuriously mark articles as "new"
* (bug 20601)
*/
- var $enableDebugComment = false;
+ public $enableDebugComment = false;
- // If true, line X is not displayed when X is 1, for example to increase
- // readability and conserve space with many small diffs.
+ /** @var bool If true, line X is not displayed when X is 1, for example
+ * to increase readability and conserve space with many small diffs.
+ */
protected $mReducedLineNumbers = false;
- // Link to action=markpatrolled
+ /** @var string Link to action=markpatrolled */
protected $mMarkPatrolledLink = null;
- protected $unhide = false; # show rev_deleted content if allowed
+ /** @var bool Show rev_deleted content if allowed */
+ protected $unhide = false;
/**#@-*/
/**
* Constructor
- * @param $context IContextSource context to use, anything else will be ignored
- * @param $old Integer old ID we want to show and diff with.
- * @param $new String either 'prev' or 'next'.
- * @param $rcid Integer Deprecated, no longer used!
- * @param $refreshCache boolean If set, refreshes the diff cache
- * @param $unhide boolean If set, allow viewing deleted revs
+ * @param IContextSource $context Context to use, anything else will be ignored
+ * @param int $old Old ID we want to show and diff with.
+ * @param string|int $new Either revision ID or 'prev' or 'next'. Default: 0.
+ * @param int $rcid Deprecated, no longer used!
+ * @param bool $refreshCache If set, refreshes the diff cache
+ * @param bool $unhide If set, allow viewing deleted revs
*/
- function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
- $refreshCache = false, $unhide = false )
- {
+ public function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
+ $refreshCache = false, $unhide = false
+ ) {
if ( $context instanceof IContextSource ) {
$this->setContext( $context );
}
@@ -101,43 +121,46 @@ class DifferenceEngine extends ContextSource {
}
/**
- * @param $value bool
+ * @param bool $value
*/
- function setReducedLineNumbers( $value = true ) {
+ public function setReducedLineNumbers( $value = true ) {
$this->mReducedLineNumbers = $value;
}
/**
* @return Language
*/
- function getDiffLang() {
+ public function getDiffLang() {
if ( $this->mDiffLang === null ) {
# Default language in which the diff text is written.
$this->mDiffLang = $this->getTitle()->getPageLanguage();
}
+
return $this->mDiffLang;
}
/**
* @return bool
*/
- function wasCacheHit() {
+ public function wasCacheHit() {
return $this->mCacheHit;
}
/**
* @return int
*/
- function getOldid() {
+ public function getOldid() {
$this->loadRevisionIds();
+
return $this->mOldid;
}
/**
- * @return Bool|int
+ * @return bool|int
*/
- function getNewid() {
+ public function getNewid() {
$this->loadRevisionIds();
+
return $this->mNewid;
}
@@ -145,10 +168,11 @@ class DifferenceEngine extends ContextSource {
* Look up a special:Undelete link to the given deleted revision id,
* as a workaround for being unable to load deleted diffs in currently.
*
- * @param int $id revision ID
+ * @param int $id Revision ID
+ *
* @return mixed URL or false
*/
- function deletedLink( $id ) {
+ public function deletedLink( $id ) {
if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'archive', '*',
@@ -157,22 +181,25 @@ class DifferenceEngine extends ContextSource {
if ( $row ) {
$rev = Revision::newFromArchiveRow( $row );
$title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
+
return SpecialPage::getTitleFor( 'Undelete' )->getFullURL( array(
'target' => $title->getPrefixedText(),
'timestamp' => $rev->getTimestamp()
- ));
+ ) );
}
}
+
return false;
}
/**
* Build a wikitext link toward a deleted revision, if viewable.
*
- * @param int $id revision ID
- * @return string wikitext fragment
+ * @param int $id Revision ID
+ *
+ * @return string Wikitext fragment
*/
- function deletedIdMarker( $id ) {
+ public function deletedIdMarker( $id ) {
$link = $this->deletedLink( $id );
if ( $link ) {
return "[$link $id]";
@@ -201,7 +228,7 @@ class DifferenceEngine extends ContextSource {
$this->getLanguage()->listToText( $missing ), count( $missing ) );
}
- function showDiffPage( $diffOnly = false ) {
+ public function showDiffPage( $diffOnly = false ) {
wfProfileIn( __METHOD__ );
# Allow frames except in certain special cases
@@ -212,6 +239,7 @@ class DifferenceEngine extends ContextSource {
if ( !$this->loadRevisionData() ) {
$this->showMissingRevision();
wfProfileOut( __METHOD__ );
+
return;
}
@@ -227,7 +255,6 @@ class DifferenceEngine extends ContextSource {
}
$rollback = '';
- $undoLink = '';
$query = array();
# Carry over 'diffonly' param via navigation links
@@ -259,8 +286,8 @@ class DifferenceEngine extends ContextSource {
$out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
$samePage = true;
} else {
- $out->setPageTitle( $this->msg( 'difference-title-multipage', $this->mOldPage->getPrefixedText(),
- $this->mNewPage->getPrefixedText() ) );
+ $out->setPageTitle( $this->msg( 'difference-title-multipage',
+ $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
$out->addSubtitle( $this->msg( 'difference-multipage' ) );
$samePage = false;
}
@@ -273,17 +300,21 @@ class DifferenceEngine extends ContextSource {
$rollback = '&#160;&#160;&#160;' . $rollbackLink;
}
}
- if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+
+ if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) &&
+ !$this->mNewRev->isDeleted( Revision::DELETED_TEXT )
+ ) {
$undoLink = Html::element( 'a', array(
'href' => $this->mNewPage->getLocalURL( array(
'action' => 'edit',
'undoafter' => $this->mOldid,
- 'undo' => $this->mNewid ) ),
- 'title' => Linker::titleAttrib( 'undo' )
+ 'undo' => $this->mNewid
+ ) ),
+ 'title' => Linker::titleAttrib( 'undo' ),
),
$this->msg( 'editundo' )->text()
);
- $revisionTools[] = $undoLink;
+ $revisionTools['mw-diff-undo'] = $undoLink;
}
}
@@ -311,9 +342,9 @@ class DifferenceEngine extends ContextSource {
$oldHeader = '<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader . '</strong></div>' .
'<div id="mw-diff-otitle2">' .
- Linker::revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
+ Linker::revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
'<div id="mw-diff-otitle3">' . $oldminor .
- Linker::revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
+ Linker::revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
'<div id="mw-diff-otitle5">' . $oldChangeTags[0] . '</div>' .
'<div id="mw-diff-otitle4">' . $prevlink . '</div>';
@@ -353,20 +384,27 @@ class DifferenceEngine extends ContextSource {
$rdel = $this->revisionDeleteLink( $this->mNewRev );
# Allow extensions to define their own revision tools
- wfRunHooks( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools ) );
+ wfRunHooks( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools, $this->mOldRev ) );
$formattedRevisionTools = array();
// Put each one in parentheses (poor man's button)
- foreach ( $revisionTools as $tool ) {
- $formattedRevisionTools[] = $this->msg( 'parentheses' )->rawParams( $tool )->escaped();
+ foreach ( $revisionTools as $key => $tool ) {
+ $toolClass = is_string( $key ) ? $key : 'mw-diff-tool';
+ $element = Html::rawElement(
+ 'span',
+ array( 'class' => $toolClass ),
+ $this->msg( 'parentheses' )->rawParams( $tool )->escaped()
+ );
+ $formattedRevisionTools[] = $element;
}
- $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) . ' ' . implode( ' ', $formattedRevisionTools );
+ $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) .
+ ' ' . implode( ' ', $formattedRevisionTools );
$newChangeTags = ChangeTags::formatSummaryRow( $this->mNewTags, 'diff' );
$newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' .
'<div id="mw-diff-ntitle2">' . Linker::revUserTools( $this->mNewRev, !$this->unhide ) .
- " $rollback</div>" .
+ " $rollback</div>" .
'<div id="mw-diff-ntitle3">' . $newminor .
- Linker::revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
+ Linker::revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
'<div id="mw-diff-ntitle5">' . $newChangeTags[0] . '</div>' .
'<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>';
@@ -390,9 +428,13 @@ class DifferenceEngine extends ContextSource {
array( $msg ) );
} else {
# Give explanation and add a link to view the diff...
- $link = $this->getTitle()->getFullURL( $this->getRequest()->appendQueryValue( 'unhide', '1', true ) );
+ $query = $this->getRequest()->appendQueryValue( 'unhide', '1', true );
+ $link = $this->getTitle()->getFullURL( $query );
$msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
- $out->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
+ $out->wrapWikiMsg(
+ "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
+ array( $msg, $link )
+ );
}
# Otherwise, output a regular diff...
} else {
@@ -400,7 +442,9 @@ class DifferenceEngine extends ContextSource {
$notice = '';
if ( $deleted ) {
$msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view';
- $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" . $this->msg( $msg )->parse() . "</div>\n";
+ $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" .
+ $this->msg( $msg )->parse() .
+ "</div>\n";
}
$this->showDiff( $oldHeader, $newHeader, $notice );
if ( !$diffOnly ) {
@@ -416,7 +460,7 @@ class DifferenceEngine extends ContextSource {
* Side effect: When the patrol link is build, this method will call
* OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
*
- * @return String
+ * @return string
*/
protected function markPatrolledLink() {
global $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
@@ -483,21 +527,23 @@ class DifferenceEngine extends ContextSource {
}
/**
- * @param $rev Revision
- * @return String
+ * @param Revision $rev
+ *
+ * @return string
*/
protected function revisionDeleteLink( $rev ) {
$link = Linker::getRevDeleteLink( $this->getUser(), $rev, $rev->getTitle() );
if ( $link !== '' ) {
$link = '&#160;&#160;&#160;' . $link . ' ';
}
+
return $link;
}
/**
* Show the new revision of the page.
*/
- function renderNewRevision() {
+ public function renderNewRevision() {
wfProfileIn( __METHOD__ );
$out = $this->getOutput();
$revHeader = $this->getRevisionHeader( $this->mNewRev );
@@ -505,6 +551,7 @@ class DifferenceEngine extends ContextSource {
$out->addHTML( "<hr class='diff-hr' />
<h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
# Page content may be handled by a hooked call instead...
+ # @codingStandardsIgnoreStart Ignoring long lines.
if ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $out ) ) ) {
$this->loadNewText();
$out->setRevisionId( $this->mNewid );
@@ -513,7 +560,7 @@ class DifferenceEngine extends ContextSource {
// NOTE: only needed for B/C: custom rendering of JS/CSS via hook
if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
- // Stolen from Article::view --AG 2007-10-11
+ // This needs to be synchronised with Article::showCssOrJsPage(), which sucks
// Give hooks a chance to customise the output
// @todo standardize this crap into one function
if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
@@ -521,8 +568,9 @@ class DifferenceEngine extends ContextSource {
// use the content object's own rendering
$cnt = $this->mNewRev->getContent();
$po = $cnt ? $cnt->getParserOutput( $this->mNewRev->getTitle(), $this->mNewRev->getId() ) : null;
- $txt = $po ? $po->getText() : '';
- $out->addHTML( $txt );
+ if ( $po ) {
+ $out->addParserOutputContent( $po );
+ }
}
} elseif ( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
// Handled by extension
@@ -543,23 +591,14 @@ class DifferenceEngine extends ContextSource {
$parserOutput = $this->getParserOutput( $wikiPage, $this->mNewRev );
- # Also try to load it as a redirect
- $rt = $this->mNewContent ? $this->mNewContent->getRedirectTarget() : null;
-
- if ( $rt ) {
- $article = Article::newFromTitle( $this->mNewPage, $this->getContext() );
- $out->addHTML( $article->viewRedirect( $rt ) );
-
- # WikiPage::getParserOutput() should not return false, but just in case
- if ( $parserOutput ) {
- # Show categories etc.
- $out->addParserOutputNoText( $parserOutput );
- }
- } elseif ( $parserOutput ) {
+ # WikiPage::getParserOutput() should not return false, but just in case
+ if ( $parserOutput ) {
$out->addParserOutput( $parserOutput );
}
}
}
+ # @codingStandardsIgnoreEnd
+
# Add redundant patrol link on bottom...
$out->addHTML( $this->markPatrolledLink() );
@@ -574,6 +613,7 @@ class DifferenceEngine extends ContextSource {
}
$parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
+
return $parserOutput;
}
@@ -581,16 +621,22 @@ class DifferenceEngine extends ContextSource {
* Get the diff text, send it to the OutputPage object
* Returns false if the diff could not be generated, otherwise returns true
*
+ * @param string|bool $otitle Header for old text or false
+ * @param string|bool $ntitle Header for new text or false
+ * @param string $notice HTML between diff header and body
+ *
* @return bool
*/
- function showDiff( $otitle, $ntitle, $notice = '' ) {
+ public function showDiff( $otitle, $ntitle, $notice = '' ) {
$diff = $this->getDiff( $otitle, $ntitle, $notice );
if ( $diff === false ) {
$this->showMissingRevision();
+
return false;
} else {
$this->showDiffStyle();
$this->getOutput()->addHTML( $diff );
+
return true;
}
}
@@ -598,7 +644,7 @@ class DifferenceEngine extends ContextSource {
/**
* Add style sheets and supporting JS for diff display.
*/
- function showDiffStyle() {
+ public function showDiffStyle() {
$this->getOutput()->addModuleStyles( 'mediawiki.action.history.diff' );
}
@@ -608,20 +654,24 @@ class DifferenceEngine extends ContextSource {
* @param string|bool $otitle Header for old text or false
* @param string|bool $ntitle Header for new text or false
* @param string $notice HTML between diff header and body
+ *
* @return mixed
*/
- function getDiff( $otitle, $ntitle, $notice = '' ) {
+ public function getDiff( $otitle, $ntitle, $notice = '' ) {
$body = $this->getDiffBody();
if ( $body === false ) {
return false;
- } else {
- $multi = $this->getMultiNotice();
- // Display a message when the diff is empty
- if ( $body === '' ) {
- $notice .= '<div class="mw-diff-empty">' . $this->msg( 'diff-empty' )->parse() . "</div>\n";
- }
- return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
}
+
+ $multi = $this->getMultiNotice();
+ // Display a message when the diff is empty
+ if ( $body === '' ) {
+ $notice .= '<div class="mw-diff-empty">' .
+ $this->msg( 'diff-empty' )->parse() .
+ "</div>\n";
+ }
+
+ return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
}
/**
@@ -636,26 +686,34 @@ class DifferenceEngine extends ContextSource {
// Check if the diff should be hidden from this user
if ( !$this->loadRevisionData() ) {
wfProfileOut( __METHOD__ );
+
return false;
- } elseif ( $this->mOldRev && !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+ } elseif ( $this->mOldRev &&
+ !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ ) {
wfProfileOut( __METHOD__ );
+
return false;
- } elseif ( $this->mNewRev && !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+ } elseif ( $this->mNewRev &&
+ !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ ) {
wfProfileOut( __METHOD__ );
+
return false;
}
// Short-circuit
if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
- && $this->mOldRev->getID() == $this->mNewRev->getID() ) )
- {
+ && $this->mOldRev->getID() == $this->mNewRev->getID() )
+ ) {
wfProfileOut( __METHOD__ );
+
return '';
}
// Cacheable?
$key = false;
if ( $this->mOldid && $this->mNewid ) {
- $key = wfMemcKey( 'diff', 'version', MW_DIFF_VERSION,
- 'oldid', $this->mOldid, 'newid', $this->mNewid );
+ $key = $this->getDiffBodyCacheKey();
+
// Try cache
if ( !$this->mRefreshCache ) {
$difftext = $wgMemc->get( $key );
@@ -664,6 +722,7 @@ class DifferenceEngine extends ContextSource {
$difftext = $this->localiseLineNumbers( $difftext );
$difftext .= "\n<!-- diff cache key $key -->\n";
wfProfileOut( __METHOD__ );
+
return $difftext;
}
} // don't try to load but save the result
@@ -673,6 +732,7 @@ class DifferenceEngine extends ContextSource {
// Loadtext is permission safe, this just clears out the diff
if ( !$this->loadText() ) {
wfProfileOut( __METHOD__ );
+
return false;
}
@@ -692,10 +752,28 @@ class DifferenceEngine extends ContextSource {
$difftext = $this->localiseLineNumbers( $difftext );
}
wfProfileOut( __METHOD__ );
+
return $difftext;
}
/**
+ * Returns the cache key for diff body text or content.
+ *
+ * @since 1.23
+ *
+ * @throws MWException
+ * @return string
+ */
+ protected function getDiffBodyCacheKey() {
+ if ( !$this->mOldid || !$this->mNewid ) {
+ throw new MWException( 'mOldid and mNewid must be set to get diff cache key.' );
+ }
+
+ return wfMemcKey( 'diff', 'version', MW_DIFF_VERSION,
+ 'oldid', $this->mOldid, 'newid', $this->mNewid );
+ }
+
+ /**
* Generate a diff, no caching.
*
* This implementation uses generateTextDiffBody() to generate a diff based on the default
@@ -706,17 +784,18 @@ class DifferenceEngine extends ContextSource {
* perhaps taking advantage of the content's native form. This is required for all content
* models that are not text based.
*
- * @param $old Content: old content
- * @param $new Content: new content
+ * @since 1.21
+ *
+ * @param Content $old Old content
+ * @param Content $new New content
*
+ * @throws MWException If old or new content is not an instance of TextContent.
* @return bool|string
- * @since 1.21
- * @throws MWException if $old or $new are not instances of TextContent.
*/
- function generateContentDiffBody( Content $old, Content $new ) {
+ public function generateContentDiffBody( Content $old, Content $new ) {
if ( !( $old instanceof TextContent ) ) {
- throw new MWException( "Diff not implemented for " . get_class( $old ) . "; "
- . "override generateContentDiffBody to fix this." );
+ throw new MWException( "Diff not implemented for " . get_class( $old ) . "; " .
+ "override generateContentDiffBody to fix this." );
}
if ( !( $new instanceof TextContent ) ) {
@@ -733,12 +812,13 @@ class DifferenceEngine extends ContextSource {
/**
* Generate a diff, no caching
*
- * @param string $otext old text, must be already segmented
- * @param string $ntext new text, must be already segmented
+ * @param string $otext Old text, must be already segmented
+ * @param string $ntext New text, must be already segmented
+ *
* @return bool|string
* @deprecated since 1.21, use generateContentDiffBody() instead!
*/
- function generateDiffBody( $otext, $ntext ) {
+ public function generateDiffBody( $otext, $ntext ) {
ContentHandler::deprecated( __METHOD__, "1.21" );
return $this->generateTextDiffBody( $otext, $ntext );
@@ -749,11 +829,12 @@ class DifferenceEngine extends ContextSource {
*
* @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
*
- * @param string $otext old text, must be already segmented
- * @param string $ntext new text, must be already segmented
+ * @param string $otext Old text, must be already segmented
+ * @param string $ntext New text, must be already segmented
+ *
* @return bool|string
*/
- function generateTextDiffBody( $otext, $ntext ) {
+ public function generateTextDiffBody( $otext, $ntext ) {
global $wgExternalDiffEngine, $wgContLang;
wfProfileIn( __METHOD__ );
@@ -764,9 +845,10 @@ class DifferenceEngine extends ContextSource {
if ( $wgExternalDiffEngine == 'wikidiff' && function_exists( 'wikidiff_do_diff' ) ) {
# For historical reasons, external diff engine expects
# input text to be HTML-escaped already
- $otext = htmlspecialchars ( $wgContLang->segmentForDiff( $otext ) );
- $ntext = htmlspecialchars ( $wgContLang->segmentForDiff( $ntext ) );
+ $otext = htmlspecialchars( $wgContLang->segmentForDiff( $otext ) );
+ $ntext = htmlspecialchars( $wgContLang->segmentForDiff( $ntext ) );
wfProfileOut( __METHOD__ );
+
return $wgContLang->unsegmentForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
$this->debug( 'wikidiff1' );
}
@@ -779,6 +861,7 @@ class DifferenceEngine extends ContextSource {
$text .= $this->debug( 'wikidiff2' );
wfProfileOut( 'wikidiff2_do_diff' );
wfProfileOut( __METHOD__ );
+
return $text;
}
if ( $wgExternalDiffEngine != 'wikidiff3' && $wgExternalDiffEngine !== false ) {
@@ -790,11 +873,13 @@ class DifferenceEngine extends ContextSource {
$tempFile1 = fopen( $tempName1, "w" );
if ( !$tempFile1 ) {
wfProfileOut( __METHOD__ );
+
return false;
}
$tempFile2 = fopen( $tempName2, "w" );
if ( !$tempFile2 ) {
wfProfileOut( __METHOD__ );
+
return false;
}
fwrite( $tempFile1, $otext );
@@ -809,6 +894,7 @@ class DifferenceEngine extends ContextSource {
unlink( $tempName1 );
unlink( $tempName2 );
wfProfileOut( __METHOD__ );
+
return $difftext;
}
@@ -818,13 +904,17 @@ class DifferenceEngine extends ContextSource {
$diffs = new Diff( $ota, $nta );
$formatter = new TableDiffFormatter();
$difftext = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) .
- wfProfileOut( __METHOD__ );
+ wfProfileOut( __METHOD__ );
+
return $difftext;
}
/**
* Generate a debug comment indicating diff generating time,
* server node, and generator backend.
+ *
+ * @param string $generator : What diff engine was used
+ *
* @return string
*/
protected function debug( $generator = "internal" ) {
@@ -837,35 +927,41 @@ class DifferenceEngine extends ContextSource {
$data[] = wfHostname();
}
$data[] = wfTimestamp( TS_DB );
+
return "<!-- diff generator: " .
- implode( " ",
- array_map(
- "htmlspecialchars",
- $data ) ) .
+ implode( " ", array_map( "htmlspecialchars", $data ) ) .
" -->\n";
}
/**
* Replace line numbers with the text in the user's language
+ *
+ * @param string $text
+ *
* @return mixed
*/
- function localiseLineNumbers( $text ) {
- return preg_replace_callback( '/<!--LINE (\d+)-->/',
- array( &$this, 'localiseLineNumbersCb' ), $text );
+ public function localiseLineNumbers( $text ) {
+ return preg_replace_callback(
+ '/<!--LINE (\d+)-->/',
+ array( &$this, 'localiseLineNumbersCb' ),
+ $text
+ );
}
- function localiseLineNumbersCb( $matches ) {
+ public function localiseLineNumbersCb( $matches ) {
if ( $matches[1] === '1' && $this->mReducedLineNumbers ) {
return '';
}
+
return $this->msg( 'lineno' )->numParams( $matches[1] )->escaped();
}
/**
* If there are revisions between the ones being compared, return a note saying so.
+ *
* @return string
*/
- function getMultiNotice() {
+ public function getMultiNotice() {
if ( !is_object( $this->mOldRev ) || !is_object( $this->mNewRev ) ) {
return '';
} elseif ( !$this->mOldPage->equals( $this->mNewPage ) ) {
@@ -881,39 +977,54 @@ class DifferenceEngine extends ContextSource {
$newRev = $this->mNewRev;
}
- $nEdits = $this->mNewPage->countRevisionsBetween( $oldRev, $newRev );
- if ( $nEdits > 0 ) {
+ // Sanity: don't show the notice if too many rows must be scanned
+ // @todo show some special message for that case
+ $nEdits = $this->mNewPage->countRevisionsBetween( $oldRev, $newRev, 1000 );
+ if ( $nEdits > 0 && $nEdits <= 1000 ) {
$limit = 100; // use diff-multi-manyusers if too many users
- $numUsers = $this->mNewPage->countAuthorsBetween( $oldRev, $newRev, $limit );
+ $users = $this->mNewPage->getAuthorsBetween( $oldRev, $newRev, $limit );
+ $numUsers = count( $users );
+
+ if ( $numUsers == 1 && $users[0] == $newRev->getRawUserText() ) {
+ $numUsers = 0; // special case to say "by the same user" instead of "by one other user"
+ }
+
return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
}
+
return ''; // nothing
}
/**
* Get a notice about how many intermediate edits and users there are
- * @param $numEdits int
- * @param $numUsers int
- * @param $limit int
+ *
+ * @param int $numEdits
+ * @param int $numUsers
+ * @param int $limit
+ *
* @return string
*/
public static function intermediateEditsMsg( $numEdits, $numUsers, $limit ) {
- if ( $numUsers > $limit ) {
+ if ( $numUsers === 0 ) {
+ $msg = 'diff-multi-sameuser';
+ } elseif ( $numUsers > $limit ) {
$msg = 'diff-multi-manyusers';
$numUsers = $limit;
} else {
- $msg = 'diff-multi';
+ $msg = 'diff-multi-otherusers';
}
+
return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
}
/**
* Get a header for a specified revision.
*
- * @param $rev Revision
+ * @param Revision $rev
* @param string $complete 'complete' to get the header wrapped depending
* the visibility of the revision and a link to edit the page.
- * @return String HTML fragment
+ *
+ * @return string HTML fragment
*/
protected function getRevisionHeader( Revision $rev, $complete = '' ) {
$lang = $this->getLanguage();
@@ -945,11 +1056,21 @@ class DifferenceEngine extends ContextSource {
$editQuery['oldid'] = $rev->getID();
}
- $msg = $this->msg( $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold' )->escaped();
- $header .= ' ' . $this->msg( 'parentheses' )->rawParams(
- Linker::linkKnown( $title, $msg, array(), $editQuery ) )->plain();
+ $key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold';
+ $msg = $this->msg( $key )->escaped();
+ $editLink = $this->msg( 'parentheses' )->rawParams(
+ Linker::linkKnown( $title, $msg, array( ), $editQuery ) )->plain();
+ $header .= ' ' . Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-diff-edit' ),
+ $editLink
+ );
if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
+ $header = Html::rawElement(
+ 'span',
+ array( 'class' => 'history-deleted' ),
+ $header
+ );
}
} else {
$header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
@@ -961,9 +1082,16 @@ class DifferenceEngine extends ContextSource {
/**
* Add the header to a diff body
*
+ * @param string $diff Diff body
+ * @param string $otitle Old revision header
+ * @param string $ntitle New revision header
+ * @param string $multi Notice telling user that there are intermediate
+ * revisions between the ones being compared
+ * @param string $notice Other notices, e.g. that user is viewing deleted content
+ *
* @return string
*/
- function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
+ public function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
// shared.css sets diff in interface language/dir, but the actual content
// is often in a different language, mostly the page content language/dir
$tableClass = 'diff diff-contentalign-' . htmlspecialchars( $this->getDiffLang()->alignStart() );
@@ -998,7 +1126,8 @@ class DifferenceEngine extends ContextSource {
}
if ( $multi != '' ) {
- $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;' class='diff-multi'>{$multi}</td></tr>";
+ $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;' " .
+ "class='diff-multi'>{$multi}</td></tr>";
}
if ( $notice != '' ) {
$header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;'>{$notice}</td></tr>";
@@ -1011,7 +1140,7 @@ class DifferenceEngine extends ContextSource {
* Use specified text instead of loading from the database
* @deprecated since 1.21, use setContent() instead.
*/
- function setText( $oldText, $newText ) {
+ public function setText( $oldText, $newText ) {
ContentHandler::deprecated( __METHOD__, "1.21" );
$oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
@@ -1022,9 +1151,11 @@ class DifferenceEngine extends ContextSource {
/**
* Use specified text instead of loading from the database
+ * @param Content $oldContent
+ * @param Content $newContent
* @since 1.21
*/
- function setContent( Content $oldContent, Content $newContent ) {
+ public function setContent( Content $oldContent, Content $newContent ) {
$this->mOldContent = $oldContent;
$this->mNewContent = $newContent;
@@ -1035,13 +1166,42 @@ class DifferenceEngine extends ContextSource {
/**
* Set the language in which the diff text is written
* (Defaults to page content language).
+ * @param Language|string $lang
* @since 1.19
*/
- function setTextLanguage( $lang ) {
+ public function setTextLanguage( $lang ) {
$this->mDiffLang = wfGetLangObj( $lang );
}
/**
+ * Maps a revision pair definition as accepted by DifferenceEngine constructor
+ * to a pair of actual integers representing revision ids.
+ *
+ * @param int $old Revision id, e.g. from URL parameter 'oldid'
+ * @param int|string $new Revision id or strings 'next' or 'prev', e.g. from URL parameter 'diff'
+ *
+ * @return int[] List of two revision ids, older first, later second.
+ * Zero signifies invalid argument passed.
+ * false signifies that there is no previous/next revision ($old is the oldest/newest one).
+ */
+ public function mapDiffPrevNext( $old, $new ) {
+ if ( $new === 'prev' ) {
+ // Show diff between revision $old and the previous one. Get previous one from DB.
+ $newid = intval( $old );
+ $oldid = $this->getTitle()->getPreviousRevisionID( $newid );
+ } elseif ( $new === 'next' ) {
+ // Show diff between revision $old and the next one. Get next one from DB.
+ $oldid = intval( $old );
+ $newid = $this->getTitle()->getNextRevisionID( $oldid );
+ } else {
+ $oldid = intval( $old );
+ $newid = intval( $new );
+ }
+
+ return array( $oldid, $newid );
+ }
+
+ /**
* Load revision IDs
*/
private function loadRevisionIds() {
@@ -1054,26 +1214,17 @@ class DifferenceEngine extends ContextSource {
$old = $this->mOldid;
$new = $this->mNewid;
- if ( $new === 'prev' ) {
- # Show diff between revision $old and the previous one.
- # Get previous one from DB.
- $this->mNewid = intval( $old );
- $this->mOldid = $this->getTitle()->getPreviousRevisionID( $this->mNewid );
- } elseif ( $new === 'next' ) {
- # Show diff between revision $old and the next one.
- # Get next one from DB.
- $this->mOldid = intval( $old );
- $this->mNewid = $this->getTitle()->getNextRevisionID( $this->mOldid );
- if ( $this->mNewid === false ) {
- # if no result, NewId points to the newest old revision. The only newer
- # revision is cur, which is "0".
- $this->mNewid = 0;
- }
- } else {
- $this->mOldid = intval( $old );
- $this->mNewid = intval( $new );
- wfRunHooks( 'NewDifferenceEngine', array( $this->getTitle(), &$this->mOldid, &$this->mNewid, $old, $new ) );
+ list( $this->mOldid, $this->mNewid ) = self::mapDiffPrevNext( $old, $new );
+ if ( $new === 'next' && $this->mNewid === false ) {
+ # if no result, NewId points to the newest old revision. The only newer
+ # revision is cur, which is "0".
+ $this->mNewid = 0;
}
+
+ wfRunHooks(
+ 'NewDifferenceEngine',
+ array( $this->getTitle(), &$this->mOldid, &$this->mNewid, $old, $new )
+ );
}
/**
@@ -1088,7 +1239,7 @@ class DifferenceEngine extends ContextSource {
*
* @return bool
*/
- function loadRevisionData() {
+ public function loadRevisionData() {
if ( $this->mRevisionsLoaded ) {
return true;
}
@@ -1099,9 +1250,15 @@ class DifferenceEngine extends ContextSource {
$this->loadRevisionIds();
// Load the new revision object
- $this->mNewRev = $this->mNewid
- ? Revision::newFromId( $this->mNewid )
- : Revision::newFromTitle( $this->getTitle(), false, Revision::READ_NORMAL );
+ if ( $this->mNewid ) {
+ $this->mNewRev = Revision::newFromId( $this->mNewid );
+ } else {
+ $this->mNewRev = Revision::newFromTitle(
+ $this->getTitle(),
+ false,
+ Revision::READ_NORMAL
+ );
+ }
if ( !$this->mNewRev instanceof Revision ) {
return false;
@@ -1162,7 +1319,7 @@ class DifferenceEngine extends ContextSource {
*
* @return bool
*/
- function loadText() {
+ public function loadText() {
if ( $this->mTextLoaded == 2 ) {
return true;
}
@@ -1196,7 +1353,7 @@ class DifferenceEngine extends ContextSource {
*
* @return bool
*/
- function loadNewText() {
+ public function loadNewText() {
if ( $this->mTextLoaded >= 1 ) {
return true;
}
@@ -1211,4 +1368,5 @@ class DifferenceEngine extends ContextSource {
return true;
}
+
}
diff --git a/includes/diff/TableDiffFormatter.php b/includes/diff/TableDiffFormatter.php
new file mode 100644
index 00000000..db7318f2
--- /dev/null
+++ b/includes/diff/TableDiffFormatter.php
@@ -0,0 +1,214 @@
+<?php
+/**
+ * Portions 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
+ */
+
+/**
+ * MediaWiki default table style diff formatter
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class TableDiffFormatter extends DiffFormatter {
+
+ function __construct() {
+ $this->leadingContextLines = 2;
+ $this->trailingContextLines = 2;
+ }
+
+ /**
+ * @static
+ * @param string $msg
+ *
+ * @return mixed
+ */
+ public static function escapeWhiteSpace( $msg ) {
+ $msg = preg_replace( '/^ /m', '&#160; ', $msg );
+ $msg = preg_replace( '/ $/m', ' &#160;', $msg );
+ $msg = preg_replace( '/ /', '&#160; ', $msg );
+
+ return $msg;
+ }
+
+ /**
+ * @param int $xbeg
+ * @param int $xlen
+ * @param int $ybeg
+ * @param int $ylen
+ *
+ * @return string
+ */
+ protected function blockHeader( $xbeg, $xlen, $ybeg, $ylen ) {
+ // '<!--LINE \d+ -->' get replaced by a localised line number
+ // in DifferenceEngine::localiseLineNumbers
+ $r = '<tr><td colspan="2" class="diff-lineno"><!--LINE ' . $xbeg . "--></td>\n" .
+ '<td colspan="2" class="diff-lineno"><!--LINE ' . $ybeg . "--></td></tr>\n";
+
+ return $r;
+ }
+
+ /**
+ * Writes the header to the output buffer.
+ *
+ * @param string $header
+ */
+ protected function startBlock( $header ) {
+ echo $header;
+ }
+
+ protected function endBlock() {
+ }
+
+ /**
+ * @param string[] $lines
+ * @param string $prefix
+ * @param string $color
+ */
+ protected function lines( $lines, $prefix = ' ', $color = 'white' ) {
+ }
+
+ /**
+ * HTML-escape parameter before calling this
+ *
+ * @param string $line
+ *
+ * @return string
+ */
+ protected function addedLine( $line ) {
+ return $this->wrapLine( '+', 'diff-addedline', $line );
+ }
+
+ /**
+ * HTML-escape parameter before calling this
+ *
+ * @param string $line
+ *
+ * @return string
+ */
+ protected function deletedLine( $line ) {
+ return $this->wrapLine( '−', 'diff-deletedline', $line );
+ }
+
+ /**
+ * HTML-escape parameter before calling this
+ *
+ * @param string $line
+ *
+ * @return string
+ */
+ protected function contextLine( $line ) {
+ return $this->wrapLine( '&#160;', 'diff-context', $line );
+ }
+
+ /**
+ * @param string $marker
+ * @param string $class Unused
+ * @param string $line
+ *
+ * @return string
+ */
+ protected function wrapLine( $marker, $class, $line ) {
+ if ( $line !== '' ) {
+ // The <div> wrapper is needed for 'overflow: auto' style to scroll properly
+ $line = Xml::tags( 'div', null, $this->escapeWhiteSpace( $line ) );
+ }
+
+ return "<td class='diff-marker'>$marker</td><td class='$class'>$line</td>";
+ }
+
+ /**
+ * @return string
+ */
+ protected function emptyLine() {
+ return '<td colspan="2">&#160;</td>';
+ }
+
+ /**
+ * Writes all lines to the output buffer, each enclosed in <tr>.
+ *
+ * @param string[] $lines
+ */
+ protected function added( $lines ) {
+ foreach ( $lines as $line ) {
+ echo '<tr>' . $this->emptyLine() .
+ $this->addedLine( '<ins class="diffchange">' .
+ htmlspecialchars( $line ) . '</ins>' ) . "</tr>\n";
+ }
+ }
+
+ /**
+ * Writes all lines to the output buffer, each enclosed in <tr>.
+ *
+ * @param string[] $lines
+ */
+ protected function deleted( $lines ) {
+ foreach ( $lines as $line ) {
+ echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
+ htmlspecialchars( $line ) . '</del>' ) .
+ $this->emptyLine() . "</tr>\n";
+ }
+ }
+
+ /**
+ * Writes all lines to the output buffer, each enclosed in <tr>.
+ *
+ * @param string[] $lines
+ */
+ protected function context( $lines ) {
+ foreach ( $lines as $line ) {
+ echo '<tr>' .
+ $this->contextLine( htmlspecialchars( $line ) ) .
+ $this->contextLine( htmlspecialchars( $line ) ) . "</tr>\n";
+ }
+ }
+
+ /**
+ * Writes the two sets of lines to the output buffer, each enclosed in <tr>.
+ *
+ * @param string[] $orig
+ * @param string[] $closing
+ */
+ protected function changed( $orig, $closing ) {
+ wfProfileIn( __METHOD__ );
+
+ $diff = new WordLevelDiff( $orig, $closing );
+ $del = $diff->orig();
+ $add = $diff->closing();
+
+ # Notice that WordLevelDiff returns HTML-escaped output.
+ # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
+
+ while ( $line = array_shift( $del ) ) {
+ $aline = array_shift( $add );
+ echo '<tr>' . $this->deletedLine( $line ) .
+ $this->addedLine( $aline ) . "</tr>\n";
+ }
+ foreach ( $add as $line ) { # If any leftovers
+ echo '<tr>' . $this->emptyLine() .
+ $this->addedLine( $line ) . "</tr>\n";
+ }
+ wfProfileOut( __METHOD__ );
+ }
+
+}
diff --git a/includes/diff/UnifiedDiffFormatter.php b/includes/diff/UnifiedDiffFormatter.php
new file mode 100644
index 00000000..32a76055
--- /dev/null
+++ b/includes/diff/UnifiedDiffFormatter.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Portions 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
+ */
+
+/**
+ * A formatter that outputs unified diffs
+ * @ingroup DifferenceEngine
+ */
+class UnifiedDiffFormatter extends DiffFormatter {
+
+ /** @var int */
+ protected $leadingContextLines = 2;
+
+ /** @var int */
+ protected $trailingContextLines = 2;
+
+ /**
+ * @param string[] $lines
+ */
+ protected function added( $lines ) {
+ $this->lines( $lines, '+' );
+ }
+
+ /**
+ * @param string[] $lines
+ */
+ protected function deleted( $lines ) {
+ $this->lines( $lines, '-' );
+ }
+
+ /**
+ * @param string[] $orig
+ * @param string[] $closing
+ */
+ protected function changed( $orig, $closing ) {
+ $this->deleted( $orig );
+ $this->added( $closing );
+ }
+
+ /**
+ * @param int $xbeg
+ * @param int $xlen
+ * @param int $ybeg
+ * @param int $ylen
+ *
+ * @return string
+ */
+ protected function blockHeader( $xbeg, $xlen, $ybeg, $ylen ) {
+ return "@@ -$xbeg,$xlen +$ybeg,$ylen @@";
+ }
+
+}
diff --git a/includes/diff/WikiDiff3.php b/includes/diff/WikiDiff3.php
index ea6f6e5d..7a0f7403 100644
--- a/includes/diff/WikiDiff3.php
+++ b/includes/diff/WikiDiff3.php
@@ -138,8 +138,9 @@ class WikiDiff3 {
*/
$max = min( $this->m, $this->n );
for ( $forwardBound = 0; $forwardBound < $max
- && $this->from[$forwardBound] === $this->to[$forwardBound];
- ++$forwardBound ) {
+ && $this->from[$forwardBound] === $this->to[$forwardBound];
+ ++$forwardBound
+ ) {
$this->removed[$forwardBound] = $this->added[$forwardBound] = false;
}
@@ -147,7 +148,8 @@ class WikiDiff3 {
$backBoundL2 = $this->n - 1;
while ( $backBoundL1 >= $forwardBound && $backBoundL2 >= $forwardBound
- && $this->from[$backBoundL1] === $this->to[$backBoundL2] ) {
+ && $this->from[$backBoundL1] === $this->to[$backBoundL2]
+ ) {
$this->removed[$backBoundL1--] = $this->added[$backBoundL2--] = false;
}
@@ -156,8 +158,14 @@ class WikiDiff3 {
$snake = array( 0, 0, 0 );
$this->length = $forwardBound + $this->m - $backBoundL1 - 1
- + $this->lcs_rec( $forwardBound, $backBoundL1,
- $forwardBound, $backBoundL2, $V, $snake );
+ + $this->lcs_rec(
+ $forwardBound,
+ $backBoundL1,
+ $forwardBound,
+ $backBoundL2,
+ $V,
+ $snake
+ );
}
$this->m = $m;
@@ -189,8 +197,9 @@ class WikiDiff3 {
while ( $xi < $this->m || $yi < $this->n ) {
// Matching "snake".
while ( $xi < $this->m && $yi < $this->n
- && !$this->removed[$xi]
- && !$this->added[$yi] ) {
+ && !$this->removed[$xi]
+ && !$this->added[$yi]
+ ) {
++$xi;
++$yi;
}
@@ -206,10 +215,10 @@ class WikiDiff3 {
}
if ( $xi > $xstart || $yi > $ystart ) {
- $ranges[] = new RangeDifference( $xstart, $xi,
- $ystart, $yi );
+ $ranges[] = new RangeDifference( $xstart, $xi, $ystart, $yi );
}
}
+
return $ranges;
}
@@ -220,7 +229,7 @@ class WikiDiff3 {
}
$d = $this->find_middle_snake( $bottoml1, $topl1, $bottoml2,
- $topl2, $V, $snake );
+ $topl2, $V, $snake );
// need to store these so we don't lose them when they're
// overwritten by the recursion
@@ -236,9 +245,9 @@ class WikiDiff3 {
if ( $d > 1 ) {
return $len
+ $this->lcs_rec( $bottoml1, $startx - 1, $bottoml2,
- $starty - 1, $V, $snake )
+ $starty - 1, $V, $snake )
+ $this->lcs_rec( $startx + $len, $topl1, $starty + $len,
- $topl2, $V, $snake );
+ $topl2, $V, $snake );
} elseif ( $d == 1 ) {
/*
* In this case the sequences differ by exactly 1 line. We have
@@ -250,8 +259,10 @@ class WikiDiff3 {
$this->removed[$bottoml1 + $i] =
$this->added[$bottoml2 + $i] = false;
}
+
return $max + $len;
}
+
return $len;
}
@@ -263,8 +274,8 @@ class WikiDiff3 {
$snake0 = &$snake[0];
$snake1 = &$snake[1];
$snake2 = &$snake[2];
- $bottoml1_min_1 = $bottoml1 -1;
- $bottoml2_min_1 = $bottoml2 -1;
+ $bottoml1_min_1 = $bottoml1 - 1;
+ $bottoml2_min_1 = $bottoml2 - 1;
$N = $topl1 - $bottoml1_min_1;
$M = $topl2 - $bottoml2_min_1;
$delta = $N - $M;
@@ -307,7 +318,8 @@ class WikiDiff3 {
// compute forward furthest reaching paths
for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
if ( $k == -$d || ( $k < $d
- && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] ) ) {
+ && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] )
+ ) {
$x = $V0[$limit_plus_1 + $k];
} else {
$x = $V0[$limit_min_1 + $k] + 1;
@@ -320,12 +332,13 @@ class WikiDiff3 {
++$absx;
++$absy;
}
- $x = $absx -$bottoml1;
+ $x = $absx - $bottoml1;
- $snake2 = $absx -$snake0;
+ $snake2 = $absx - $snake0;
$V0[$limit + $k] = $x;
if ( $k >= $delta - $d + 1 && $k <= $delta + $d - 1
- && $x >= $V1[$limit + $k - $delta] ) {
+ && $x >= $V1[$limit + $k - $delta]
+ ) {
return 2 * $d - 1;
}
@@ -345,7 +358,8 @@ class WikiDiff3 {
// compute backward furthest reaching paths
for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
if ( $k == $d
- || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] ) ) {
+ || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] )
+ ) {
$x = $V1[$limit_min_1 + $k];
} else {
$x = $V1[$limit_plus_1 + $k] - 1;
@@ -355,7 +369,8 @@ class WikiDiff3 {
$snake2 = 0;
while ( $x > 0 && $y > 0
- && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1] ) {
+ && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1]
+ ) {
--$x;
--$y;
++$snake2;
@@ -380,7 +395,8 @@ class WikiDiff3 {
// compute forward furthest reaching paths
for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
if ( $k == -$d
- || ( $k < $d && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] ) ) {
+ || ( $k < $d && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] )
+ ) {
$x = $V0[$limit_plus_1 + $k];
} else {
$x = $V0[$limit_min_1 + $k] + 1;
@@ -393,14 +409,14 @@ class WikiDiff3 {
++$absx;
++$absy;
}
- $x = $absx -$bottoml1;
- $snake2 = $absx -$snake0;
+ $x = $absx - $bottoml1;
+ $snake2 = $absx - $snake0;
$V0[$limit + $k] = $x;
// check to see if we can cut down the diagonal range
if ( $x >= $N && $end_forward > $k - 1 ) {
$end_forward = $k - 1;
- } elseif ( $absy -$bottoml2 >= $M ) {
+ } elseif ( $absy - $bottoml2 >= $M ) {
$start_forward = $k + 1;
$value_to_add_forward = 0;
}
@@ -413,7 +429,8 @@ class WikiDiff3 {
// compute backward furthest reaching paths
for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
if ( $k == $d
- || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] ) ) {
+ || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] )
+ ) {
$x = $V1[$limit_min_1 + $k];
} else {
$x = $V1[$limit_plus_1 + $k] - 1;
@@ -423,7 +440,8 @@ class WikiDiff3 {
$snake2 = 0;
while ( $x > 0 && $y > 0
- && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1] ) {
+ && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1]
+ ) {
--$x;
--$y;
++$snake2;
@@ -431,9 +449,11 @@ class WikiDiff3 {
$V1[$limit + $k] = $x;
if ( $k >= -$delta - $d && $k <= $d - $delta
- && $x <= $V0[$limit + $k + $delta] ) {
+ && $x <= $V0[$limit + $k + $delta]
+ ) {
$snake0 = $bottoml1 + $x;
$snake1 = $bottoml2 + $y;
+
return 2 * $d;
}
@@ -460,6 +480,7 @@ class WikiDiff3 {
$snake2 = 0;
wfDebug( "Computing the LCS is too expensive. Using a heuristic.\n" );
$this->heuristicUsed = true;
+
return 5; /*
* HACK: since we didn't really finish the LCS computation
* we don't really know the length of the SES. We don't do
@@ -554,8 +575,9 @@ class WikiDiff3 {
public function getLcsLength() {
if ( $this->heuristicUsed && !$this->lcsLengthCorrectedForHeuristic ) {
$this->lcsLengthCorrectedForHeuristic = true;
- $this->length = $this->m -array_sum( $this->added );
+ $this->length = $this->m - array_sum( $this->added );
}
+
return $this->length;
}
@@ -569,12 +591,22 @@ class WikiDiff3 {
*/
class RangeDifference {
+ /** @var int */
public $leftstart;
+
+ /** @var int */
public $leftend;
+
+ /** @var int */
public $leftlength;
+ /** @var int */
public $rightstart;
+
+ /** @var int */
public $rightend;
+
+ /** @var int */
public $rightlength;
function __construct( $leftstart, $leftend, $rightstart, $rightend ) {
@@ -585,4 +617,5 @@ class RangeDifference {
$this->rightend = $rightend;
$this->rightlength = $rightend - $rightstart;
}
+
}
diff --git a/includes/exception/BadTitleError.php b/includes/exception/BadTitleError.php
new file mode 100644
index 00000000..e62f8bd6
--- /dev/null
+++ b/includes/exception/BadTitleError.php
@@ -0,0 +1,51 @@
+<?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
+ */
+
+/**
+ * Show an error page on a badtitle.
+ * Similar to ErrorPage, but emit a 400 HTTP error code to let mobile
+ * browser it is not really a valid content.
+ *
+ * @since 1.19
+ * @ingroup Exception
+ */
+class BadTitleError extends ErrorPageError {
+ /**
+ * @param string|Message $msg A message key (default: 'badtitletext')
+ * @param array $params Parameter to wfMessage()
+ */
+ public function __construct( $msg = 'badtitletext', $params = array() ) {
+ parent::__construct( 'badtitle', $msg, $params );
+ }
+
+ /**
+ * Just like ErrorPageError::report() but additionally set
+ * a 400 HTTP status code (bug 33646).
+ */
+ public function report() {
+ global $wgOut;
+
+ // bug 33646: a badtitle error page need to return an error code
+ // to let mobile browser now that it is not a normal page.
+ $wgOut->setStatusCode( 400 );
+ parent::report();
+ }
+
+}
diff --git a/includes/exception/ErrorPageError.php b/includes/exception/ErrorPageError.php
new file mode 100644
index 00000000..3631a340
--- /dev/null
+++ b/includes/exception/ErrorPageError.php
@@ -0,0 +1,61 @@
+<?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
+ */
+
+/**
+ * An error page which can definitely be safely rendered using the OutputPage.
+ *
+ * @since 1.7
+ * @ingroup Exception
+ */
+class ErrorPageError extends MWException {
+ public $title, $msg, $params;
+
+ /**
+ * Note: these arguments are keys into wfMessage(), not text!
+ *
+ * @param string|Message $title Message key (string) for page title, or a Message object
+ * @param string|Message $msg Message key (string) for error text, or a Message object
+ * @param array $params Array with parameters to wfMessage()
+ */
+ public function __construct( $title, $msg, $params = array() ) {
+ $this->title = $title;
+ $this->msg = $msg;
+ $this->params = $params;
+
+ // Bug 44111: Messages in the log files should be in English and not
+ // customized by the local wiki. So get the default English version for
+ // passing to the parent constructor. Our overridden report() below
+ // makes sure that the page shown to the user is not forced to English.
+ if ( $msg instanceof Message ) {
+ $enMsg = clone( $msg );
+ } else {
+ $enMsg = wfMessage( $msg, $params );
+ }
+ $enMsg->inLanguage( 'en' )->useDatabase( false );
+ parent::__construct( $enMsg->text() );
+ }
+
+ public function report() {
+ global $wgOut;
+
+ $wgOut->showErrorPage( $this->title, $this->msg, $this->params );
+ $wgOut->output();
+ }
+}
diff --git a/includes/exception/FatalError.php b/includes/exception/FatalError.php
new file mode 100644
index 00000000..a7d672fa
--- /dev/null
+++ b/includes/exception/FatalError.php
@@ -0,0 +1,43 @@
+<?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
+ */
+
+/**
+ * Exception class which takes an HTML error message, and does not
+ * produce a backtrace. Replacement for OutputPage::fatalError().
+ *
+ * @since 1.7
+ * @ingroup Exception
+ */
+class FatalError extends MWException {
+
+ /**
+ * @return string
+ */
+ public function getHTML() {
+ return $this->getMessage();
+ }
+
+ /**
+ * @return string
+ */
+ public function getText() {
+ return $this->getMessage();
+ }
+}
diff --git a/includes/exception/HttpError.php b/includes/exception/HttpError.php
new file mode 100644
index 00000000..6ab6e039
--- /dev/null
+++ b/includes/exception/HttpError.php
@@ -0,0 +1,93 @@
+<?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
+ */
+
+/**
+ * Show an error that looks like an HTTP server error.
+ * Replacement for wfHttpError().
+ *
+ * @since 1.19
+ * @ingroup Exception
+ */
+class HttpError extends MWException {
+ private $httpCode, $header, $content;
+
+ /**
+ * Constructor
+ *
+ * @param int $httpCode HTTP status code to send to the client
+ * @param string|Message $content Content of the message
+ * @param string|Message $header Content of the header (\<title\> and \<h1\>)
+ */
+ public function __construct( $httpCode, $content, $header = null ) {
+ parent::__construct( $content );
+ $this->httpCode = (int)$httpCode;
+ $this->header = $header;
+ $this->content = $content;
+ }
+
+ /**
+ * Returns the HTTP status code supplied to the constructor.
+ *
+ * @return int
+ */
+ public function getStatusCode() {
+ return $this->httpCode;
+ }
+
+ /**
+ * Report the HTTP error.
+ * Sends the appropriate HTTP status code and outputs an
+ * HTML page with an error message.
+ */
+ public function report() {
+ $httpMessage = HttpStatus::getMessage( $this->httpCode );
+
+ header( "Status: {$this->httpCode} {$httpMessage}", true, $this->httpCode );
+ header( 'Content-type: text/html; charset=utf-8' );
+
+ print $this->getHTML();
+ }
+
+ /**
+ * Returns HTML for reporting the HTTP error.
+ * This will be a minimal but complete HTML document.
+ *
+ * @return string HTML
+ */
+ public function getHTML() {
+ if ( $this->header === null ) {
+ $header = HttpStatus::getMessage( $this->httpCode );
+ } elseif ( $this->header instanceof Message ) {
+ $header = $this->header->escaped();
+ } else {
+ $header = htmlspecialchars( $this->header );
+ }
+
+ if ( $this->content instanceof Message ) {
+ $content = $this->content->escaped();
+ } else {
+ $content = htmlspecialchars( $this->content );
+ }
+
+ return "<!DOCTYPE html>\n" .
+ "<html><head><title>$header</title></head>\n" .
+ "<body><h1>$header</h1><p>$content</p></body></html>\n";
+ }
+}
diff --git a/includes/exception/MWException.php b/includes/exception/MWException.php
new file mode 100644
index 00000000..074128f8
--- /dev/null
+++ b/includes/exception/MWException.php
@@ -0,0 +1,262 @@
+<?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
+ */
+
+/**
+ * MediaWiki exception
+ *
+ * @ingroup Exception
+ */
+class MWException extends Exception {
+ /**
+ * Should the exception use $wgOut to output the error?
+ *
+ * @return bool
+ */
+ public function useOutputPage() {
+ return $this->useMessageCache() &&
+ !empty( $GLOBALS['wgFullyInitialised'] ) &&
+ !empty( $GLOBALS['wgOut'] ) &&
+ !defined( 'MEDIAWIKI_INSTALL' );
+ }
+
+ /**
+ * Whether to log this exception in the exception debug log.
+ *
+ * @since 1.23
+ * @return bool
+ */
+ public function isLoggable() {
+ return true;
+ }
+
+ /**
+ * Can the extension use the Message class/wfMessage to get i18n-ed messages?
+ *
+ * @return bool
+ */
+ public function useMessageCache() {
+ global $wgLang;
+
+ foreach ( $this->getTrace() as $frame ) {
+ if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
+ return false;
+ }
+ }
+
+ return $wgLang instanceof Language;
+ }
+
+ /**
+ * Run hook to allow extensions to modify the text of the exception
+ *
+ * @param string $name Class name of the exception
+ * @param array $args Arguments to pass to the callback functions
+ * @return string|null String to output or null if any hook has been called
+ */
+ public function runHooks( $name, $args = array() ) {
+ global $wgExceptionHooks;
+
+ if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
+ return null; // Just silently ignore
+ }
+
+ if ( !array_key_exists( $name, $wgExceptionHooks ) ||
+ !is_array( $wgExceptionHooks[$name] )
+ ) {
+ return null;
+ }
+
+ $hooks = $wgExceptionHooks[$name];
+ $callargs = array_merge( array( $this ), $args );
+
+ foreach ( $hooks as $hook ) {
+ if (
+ is_string( $hook ) ||
+ ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
+ ) {
+ // 'function' or array( 'class', hook' )
+ $result = call_user_func_array( $hook, $callargs );
+ } else {
+ $result = null;
+ }
+
+ if ( is_string( $result ) ) {
+ return $result;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get a message from i18n
+ *
+ * @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
+ * @return string Message with arguments replaced
+ */
+ public function msg( $key, $fallback /*[, params...] */ ) {
+ $args = array_slice( func_get_args(), 2 );
+
+ if ( $this->useMessageCache() ) {
+ return wfMessage( $key, $args )->text();
+ } else {
+ return wfMsgReplaceArgs( $fallback, $args );
+ }
+ }
+
+ /**
+ * If $wgShowExceptionDetails is true, return a HTML message with a
+ * backtrace to the error, otherwise show a message to ask to set it to true
+ * to show that information.
+ *
+ * @return string Html to output
+ */
+ public function getHTML() {
+ global $wgShowExceptionDetails;
+
+ if ( $wgShowExceptionDetails ) {
+ return '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) .
+ '</p><p>Backtrace:</p><p>' .
+ nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
+ "</p>\n";
+ } else {
+ return "<div class=\"errorbox\">" .
+ '[' . MWExceptionHandler::getLogId( $this ) . '] ' .
+ gmdate( 'Y-m-d H:i:s' ) .
+ ": Fatal exception of type " . get_class( $this ) . "</div>\n" .
+ "<!-- Set \$wgShowExceptionDetails = true; " .
+ "at the bottom of LocalSettings.php to show detailed " .
+ "debugging information. -->";
+ }
+ }
+
+ /**
+ * Get the text to display when reporting the error on the command line.
+ * If $wgShowExceptionDetails is true, return a text message with a
+ * backtrace to the error.
+ *
+ * @return string
+ */
+ public function getText() {
+ global $wgShowExceptionDetails;
+
+ if ( $wgShowExceptionDetails ) {
+ return MWExceptionHandler::getLogMessage( $this ) .
+ "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n";
+ } else {
+ return "Set \$wgShowExceptionDetails = true; " .
+ "in LocalSettings.php to show detailed debugging information.\n";
+ }
+ }
+
+ /**
+ * Return the title of the page when reporting this error in a HTTP response.
+ *
+ * @return string
+ */
+ public function getPageTitle() {
+ return $this->msg( 'internalerror', 'Internal error' );
+ }
+
+ /**
+ * Output the exception report using HTML.
+ */
+ public function reportHTML() {
+ global $wgOut, $wgSitename;
+ if ( $this->useOutputPage() ) {
+ $wgOut->prepareErrorPage( $this->getPageTitle() );
+
+ $hookResult = $this->runHooks( get_class( $this ) );
+ if ( $hookResult ) {
+ $wgOut->addHTML( $hookResult );
+ } else {
+ $wgOut->addHTML( $this->getHTML() );
+ }
+
+ $wgOut->output();
+ } else {
+ self::header( 'Content-Type: text/html; charset=utf-8' );
+ echo "<!DOCTYPE html>\n" .
+ '<html><head>' .
+ // Mimick OutputPage::setPageTitle behaviour
+ '<title>' .
+ htmlspecialchars( $this->msg( 'pagetitle', "$1 - $wgSitename", $this->getPageTitle() ) ) .
+ '</title>' .
+ '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
+ "</head><body>\n";
+
+ $hookResult = $this->runHooks( get_class( $this ) . 'Raw' );
+ if ( $hookResult ) {
+ echo $hookResult;
+ } else {
+ echo $this->getHTML();
+ }
+
+ echo "</body></html>\n";
+ }
+ }
+
+ /**
+ * Output a report about the exception and takes care of formatting.
+ * It will be either HTML or plain text based on isCommandLine().
+ */
+ public function report() {
+ global $wgMimeType;
+
+ MWExceptionHandler::logException( $this );
+
+ if ( defined( 'MW_API' ) ) {
+ // Unhandled API exception, we can't be sure that format printer is alive
+ self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
+ wfHttpError( 500, 'Internal Server Error', $this->getText() );
+ } elseif ( self::isCommandLine() ) {
+ MWExceptionHandler::printError( $this->getText() );
+ } else {
+ self::header( 'HTTP/1.1 500 MediaWiki exception' );
+ self::header( 'Status: 500 MediaWiki exception' );
+ self::header( "Content-Type: $wgMimeType; charset=utf-8" );
+
+ $this->reportHTML();
+ }
+ }
+
+ /**
+ * Check whether we are in command line mode or not to report the exception
+ * in the correct format.
+ *
+ * @return bool
+ */
+ public static function isCommandLine() {
+ return !empty( $GLOBALS['wgCommandLineMode'] );
+ }
+
+ /**
+ * Send a header, if we haven't already sent them. We shouldn't,
+ * but sometimes we might in a weird case like Export
+ * @param string $header
+ */
+ private static function header( $header ) {
+ if ( !headers_sent() ) {
+ header( $header );
+ }
+ }
+}
diff --git a/includes/exception/MWExceptionHandler.php b/includes/exception/MWExceptionHandler.php
new file mode 100644
index 00000000..71917e13
--- /dev/null
+++ b/includes/exception/MWExceptionHandler.php
@@ -0,0 +1,372 @@
+<?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
+ */
+
+/**
+ * Handler class for MWExceptions
+ * @ingroup Exception
+ */
+class MWExceptionHandler {
+ /**
+ * Install an exception handler for MediaWiki exception types.
+ */
+ public static function installHandler() {
+ set_exception_handler( array( 'MWExceptionHandler', 'handle' ) );
+ }
+
+ /**
+ * Report an exception to the user
+ * @param Exception $e
+ */
+ protected static function report( Exception $e ) {
+ global $wgShowExceptionDetails;
+
+ $cmdLine = MWException::isCommandLine();
+
+ if ( $e instanceof MWException ) {
+ try {
+ // Try and show the exception prettily, with the normal skin infrastructure
+ $e->report();
+ } catch ( Exception $e2 ) {
+ // Exception occurred from within exception handler
+ // Show a simpler error message for the original exception,
+ // don't try to invoke report()
+ $message = "MediaWiki internal error.\n\n";
+
+ if ( $wgShowExceptionDetails ) {
+ $message .= 'Original exception: ' . self::getLogMessage( $e ) .
+ "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
+ "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
+ "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
+ } else {
+ $message .= "Exception caught inside exception handler.\n\n" .
+ "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
+ "to show detailed debugging information.";
+ }
+
+ $message .= "\n";
+
+ if ( $cmdLine ) {
+ self::printError( $message );
+ } else {
+ echo nl2br( htmlspecialchars( $message ) ) . "\n";
+ }
+ }
+ } else {
+ $message = "Unexpected non-MediaWiki exception encountered, of type \"" .
+ get_class( $e ) . "\"";
+
+ if ( $wgShowExceptionDetails ) {
+ $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
+ self::getRedactedTraceAsString( $e ) . "\n";
+ }
+
+ if ( $cmdLine ) {
+ self::printError( $message );
+ } else {
+ echo nl2br( htmlspecialchars( $message ) ) . "\n";
+ }
+ }
+ }
+
+ /**
+ * Print a message, if possible to STDERR.
+ * Use this in command line mode only (see isCommandLine)
+ *
+ * @param string $message Failure text
+ */
+ public static function printError( $message ) {
+ # NOTE: STDERR may not be available, especially if php-cgi is used from the
+ # command line (bug #15602). Try to produce meaningful output anyway. Using
+ # echo may corrupt output to STDOUT though.
+ if ( defined( 'STDERR' ) ) {
+ fwrite( STDERR, $message );
+ } else {
+ echo $message;
+ }
+ }
+
+ /**
+ * If there are any open database transactions, roll them back and log
+ * the stack trace of the exception that should have been caught so the
+ * transaction could be aborted properly.
+ * @since 1.23
+ * @param Exception $e
+ */
+ public static function rollbackMasterChangesAndLog( Exception $e ) {
+ $factory = wfGetLBFactory();
+ if ( $factory->hasMasterChanges() ) {
+ wfDebugLog( 'Bug56269',
+ 'Exception thrown with an uncommited database transaction: ' .
+ MWExceptionHandler::getLogMessage( $e ) . "\n" .
+ $e->getTraceAsString()
+ );
+ $factory->rollbackMasterChanges();
+ }
+ }
+
+ /**
+ * Exception handler which simulates the appropriate catch() handling:
+ *
+ * try {
+ * ...
+ * } catch ( MWException $e ) {
+ * $e->report();
+ * } catch ( Exception $e ) {
+ * echo $e->__toString();
+ * }
+ * @param Exception $e
+ */
+ public static function handle( $e ) {
+ global $wgFullyInitialised;
+
+ self::rollbackMasterChangesAndLog( $e );
+
+ self::report( $e );
+
+ // Final cleanup
+ if ( $wgFullyInitialised ) {
+ try {
+ // uses $wgRequest, hence the $wgFullyInitialised condition
+ wfLogProfilingData();
+ } catch ( Exception $e ) {
+ }
+ }
+
+ // Exit value should be nonzero for the benefit of shell jobs
+ exit( 1 );
+ }
+
+ /**
+ * Generate a string representation of an exception's stack trace
+ *
+ * Like Exception::getTraceAsString, but replaces argument values with
+ * argument type or class name.
+ *
+ * @param Exception $e
+ * @return string
+ */
+ public static function getRedactedTraceAsString( Exception $e ) {
+ $text = '';
+
+ foreach ( self::getRedactedTrace( $e ) as $level => $frame ) {
+ if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
+ $text .= "#{$level} {$frame['file']}({$frame['line']}): ";
+ } else {
+ // 'file' and 'line' are unset for calls via call_user_func (bug 55634)
+ // This matches behaviour of Exception::getTraceAsString to instead
+ // display "[internal function]".
+ $text .= "#{$level} [internal function]: ";
+ }
+
+ if ( isset( $frame['class'] ) ) {
+ $text .= $frame['class'] . $frame['type'] . $frame['function'];
+ } else {
+ $text .= $frame['function'];
+ }
+
+ if ( isset( $frame['args'] ) ) {
+ $text .= '(' . implode( ', ', $frame['args'] ) . ")\n";
+ } else {
+ $text .= "()\n";
+ }
+ }
+
+ $level = $level + 1;
+ $text .= "#{$level} {main}";
+
+ return $text;
+ }
+
+ /**
+ * Return a copy of an exception's backtrace as an array.
+ *
+ * Like Exception::getTrace, but replaces each element in each frame's
+ * argument array with the name of its class (if the element is an object)
+ * or its type (if the element is a PHP primitive).
+ *
+ * @since 1.22
+ * @param Exception $e
+ * @return array
+ */
+ public static function getRedactedTrace( Exception $e ) {
+ return array_map( function ( $frame ) {
+ if ( isset( $frame['args'] ) ) {
+ $frame['args'] = array_map( function ( $arg ) {
+ return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
+ }, $frame['args'] );
+ }
+ return $frame;
+ }, $e->getTrace() );
+ }
+
+ /**
+ * Get the ID for this error.
+ *
+ * The ID is saved so that one can match the one output to the user (when
+ * $wgShowExceptionDetails is set to false), to the entry in the debug log.
+ *
+ * @since 1.22
+ * @param Exception $e
+ * @return string
+ */
+ public static function getLogId( Exception $e ) {
+ if ( !isset( $e->_mwLogId ) ) {
+ $e->_mwLogId = wfRandomString( 8 );
+ }
+ return $e->_mwLogId;
+ }
+
+ /**
+ * If the exception occurred in the course of responding to a request,
+ * returns the requested URL. Otherwise, returns false.
+ *
+ * @since 1.23
+ * @return string|bool
+ */
+ public static function getURL() {
+ global $wgRequest;
+ if ( !isset( $wgRequest ) || $wgRequest instanceof FauxRequest ) {
+ return false;
+ }
+ return $wgRequest->getRequestURL();
+ }
+
+ /**
+ * Return the requested URL and point to file and line number from which the
+ * exception occurred.
+ *
+ * @since 1.22
+ * @param Exception $e
+ * @return string
+ */
+ public static function getLogMessage( Exception $e ) {
+ $id = self::getLogId( $e );
+ $file = $e->getFile();
+ $line = $e->getLine();
+ $message = $e->getMessage();
+ $url = self::getURL() ?: '[no req]';
+
+ return "[$id] $url Exception from line $line of $file: $message";
+ }
+
+ /**
+ * Serialize an Exception object to JSON.
+ *
+ * The JSON object will have keys 'id', 'file', 'line', 'message', and
+ * 'url'. These keys map to string values, with the exception of 'line',
+ * which is a number, and 'url', which may be either a string URL or or
+ * null if the exception did not occur in the context of serving a web
+ * request.
+ *
+ * If $wgLogExceptionBacktrace is true, it will also have a 'backtrace'
+ * key, mapped to the array return value of Exception::getTrace, but with
+ * each element in each frame's "args" array (if set) replaced with the
+ * argument's class name (if the argument is an object) or type name (if
+ * the argument is a PHP primitive).
+ *
+ * @par Sample JSON record ($wgLogExceptionBacktrace = false):
+ * @code
+ * {
+ * "id": "c41fb419",
+ * "file": "/var/www/mediawiki/includes/cache/MessageCache.php",
+ * "line": 704,
+ * "message": "Non-string key given",
+ * "url": "/wiki/Main_Page"
+ * }
+ * @endcode
+ *
+ * @par Sample JSON record ($wgLogExceptionBacktrace = true):
+ * @code
+ * {
+ * "id": "dc457938",
+ * "file": "/vagrant/mediawiki/includes/cache/MessageCache.php",
+ * "line": 704,
+ * "message": "Non-string key given",
+ * "url": "/wiki/Main_Page",
+ * "backtrace": [{
+ * "file": "/vagrant/mediawiki/extensions/VisualEditor/VisualEditor.hooks.php",
+ * "line": 80,
+ * "function": "get",
+ * "class": "MessageCache",
+ * "type": "->",
+ * "args": ["array"]
+ * }]
+ * }
+ * @endcode
+ *
+ * @since 1.23
+ * @param Exception $e
+ * @param bool $pretty Add non-significant whitespace to improve readability (default: false).
+ * @param int $escaping Bitfield consisting of FormatJson::.*_OK class constants.
+ * @return string|bool JSON string if successful; false upon failure
+ */
+ public static function jsonSerializeException( Exception $e, $pretty = false, $escaping = 0 ) {
+ global $wgLogExceptionBacktrace;
+
+ $exceptionData = array(
+ 'id' => self::getLogId( $e ),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'message' => $e->getMessage(),
+ );
+
+ // Because MediaWiki is first and foremost a web application, we set a
+ // 'url' key unconditionally, but set it to null if the exception does
+ // not occur in the context of a web request, as a way of making that
+ // fact visible and explicit.
+ $exceptionData['url'] = self::getURL() ?: null;
+
+ if ( $wgLogExceptionBacktrace ) {
+ // Argument values may not be serializable, so redact them.
+ $exceptionData['backtrace'] = self::getRedactedTrace( $e );
+ }
+
+ return FormatJson::encode( $exceptionData, $pretty, $escaping );
+ }
+
+ /**
+ * Log an exception to the exception log (if enabled).
+ *
+ * This method must not assume the exception is an MWException,
+ * it is also used to handle PHP errors or errors from other libraries.
+ *
+ * @since 1.22
+ * @param Exception $e
+ */
+ public static function logException( Exception $e ) {
+ global $wgLogExceptionBacktrace;
+
+ if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
+ $log = self::getLogMessage( $e );
+ if ( $wgLogExceptionBacktrace ) {
+ wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() );
+ } else {
+ wfDebugLog( 'exception', $log );
+ }
+
+ $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK );
+ if ( $json !== false ) {
+ wfDebugLog( 'exception-json', $json, 'private' );
+ }
+ }
+
+ }
+
+}
diff --git a/includes/exception/PermissionsError.php b/includes/exception/PermissionsError.php
new file mode 100644
index 00000000..bfba7b27
--- /dev/null
+++ b/includes/exception/PermissionsError.php
@@ -0,0 +1,58 @@
+<?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
+ */
+
+/**
+ * Show an error when a user tries to do something they do not have the necessary
+ * permissions for.
+ *
+ * @since 1.18
+ * @ingroup Exception
+ */
+class PermissionsError extends ErrorPageError {
+ public $permission, $errors;
+
+ public function __construct( $permission, $errors = array() ) {
+ global $wgLang;
+
+ $this->permission = $permission;
+
+ if ( !count( $errors ) ) {
+ $groups = array_map(
+ array( 'User', 'makeGroupLinkWiki' ),
+ User::getGroupsWithPermission( $this->permission )
+ );
+
+ if ( $groups ) {
+ $errors[] = array( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
+ } else {
+ $errors[] = array( 'badaccess-group0' );
+ }
+ }
+
+ $this->errors = $errors;
+ }
+
+ public function report() {
+ global $wgOut;
+
+ $wgOut->showPermissionsErrorPage( $this->errors, $this->permission );
+ $wgOut->output();
+ }
+}
diff --git a/includes/exception/ReadOnlyError.php b/includes/exception/ReadOnlyError.php
new file mode 100644
index 00000000..cebeb1c3
--- /dev/null
+++ b/includes/exception/ReadOnlyError.php
@@ -0,0 +1,36 @@
+<?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
+ */
+
+/**
+ * Show an error when the wiki is locked/read-only and the user tries to do
+ * something that requires write access.
+ *
+ * @since 1.18
+ * @ingroup Exception
+ */
+class ReadOnlyError extends ErrorPageError {
+ public function __construct() {
+ parent::__construct(
+ 'readonly',
+ 'readonlytext',
+ wfReadOnlyReason() ?: array()
+ );
+ }
+}
diff --git a/includes/exception/ThrottledError.php b/includes/exception/ThrottledError.php
new file mode 100644
index 00000000..bec0d904
--- /dev/null
+++ b/includes/exception/ThrottledError.php
@@ -0,0 +1,40 @@
+<?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
+ */
+
+/**
+ * Show an error when the user hits a rate limit.
+ *
+ * @since 1.18
+ * @ingroup Exception
+ */
+class ThrottledError extends ErrorPageError {
+ public function __construct() {
+ parent::__construct(
+ 'actionthrottled',
+ 'actionthrottledtext'
+ );
+ }
+
+ public function report() {
+ global $wgOut;
+ $wgOut->setStatusCode( 429 );
+ parent::report();
+ }
+}
diff --git a/includes/exception/UserBlockedError.php b/includes/exception/UserBlockedError.php
new file mode 100644
index 00000000..9d19f8b6
--- /dev/null
+++ b/includes/exception/UserBlockedError.php
@@ -0,0 +1,33 @@
+<?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
+ */
+
+/**
+ * Show an error when the user tries to do something whilst blocked.
+ *
+ * @since 1.18
+ * @ingroup Exception
+ */
+class UserBlockedError extends ErrorPageError {
+ public function __construct( Block $block ) {
+ // @todo FIXME: Implement a more proper way to get context here.
+ $params = $block->getPermissionsError( RequestContext::getMain() );
+ parent::__construct( 'blockedtitle', array_shift( $params ), $params );
+ }
+}
diff --git a/includes/exception/UserNotLoggedIn.php b/includes/exception/UserNotLoggedIn.php
new file mode 100644
index 00000000..03ba0b20
--- /dev/null
+++ b/includes/exception/UserNotLoggedIn.php
@@ -0,0 +1,102 @@
+<?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
+ */
+
+/**
+ * Redirect a user to the login page
+ *
+ * This is essentially an ErrorPageError exception which by default uses the
+ * 'exception-nologin' as a title and 'exception-nologin-text' for the message.
+ *
+ * @note In order for this exception to redirect, the error message passed to the
+ * constructor has to be explicitly added to LoginForm::validErrorMessages. Otherwise,
+ * the user will just be shown the message rather than redirected.
+ *
+ * @par Example:
+ * @code
+ * if( $user->isAnon() ) {
+ * throw new UserNotLoggedIn();
+ * }
+ * @endcode
+ *
+ * Note the parameter order differs from ErrorPageError, this allows you to
+ * simply specify a reason without overriding the default title.
+ *
+ * @par Example:
+ * @code
+ * if( $user->isAnon() ) {
+ * throw new UserNotLoggedIn( 'action-require-loggedin' );
+ * }
+ * @endcode
+ *
+ * @see bug 37627
+ * @since 1.20
+ * @ingroup Exception
+ */
+class UserNotLoggedIn extends ErrorPageError {
+
+ /**
+ * @note The value of the $reasonMsg parameter must be put into LoginForm::validErrorMessages
+ * if you want the user to be automatically redirected to the login form.
+ *
+ * @param string $reasonMsg A message key containing the reason for the error.
+ * Optional, default: 'exception-nologin-text'
+ * @param string $titleMsg A message key to set the page title.
+ * Optional, default: 'exception-nologin'
+ * @param array $params Parameters to wfMessage().
+ * Optional, default: array()
+ */
+ public function __construct(
+ $reasonMsg = 'exception-nologin-text',
+ $titleMsg = 'exception-nologin',
+ $params = array()
+ ) {
+ parent::__construct( $titleMsg, $reasonMsg, $params );
+ }
+
+ /**
+ * Redirect to Special:Userlogin if the specified message is compatible. Otherwise,
+ * show an error page as usual.
+ */
+ public function report() {
+ // If an unsupported message is used, don't try redirecting to Special:Userlogin,
+ // since the message may not be compatible.
+ if ( !in_array( $this->msg, LoginForm::$validErrorMessages ) ) {
+ parent::report();
+ }
+
+ // Message is valid. Redirec to Special:Userlogin
+
+ $context = RequestContext::getMain();
+
+ $output = $context->getOutput();
+ $query = $context->getRequest()->getValues();
+ // Title will be overridden by returnto
+ unset( $query['title'] );
+ // Redirect to Special:Userlogin
+ $output->redirect( SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( array(
+ // Return to this page when the user logs in
+ 'returnto' => $context->getTitle()->getFullText(),
+ 'returntoquery' => wfArrayToCgi( $query ),
+ 'warning' => $this->msg,
+ ) ) );
+
+ $output->output();
+ }
+}
diff --git a/includes/externalstore/ExternalStore.php b/includes/externalstore/ExternalStore.php
index 462b0b90..688130e0 100644
--- a/includes/externalstore/ExternalStore.php
+++ b/includes/externalstore/ExternalStore.php
@@ -59,6 +59,7 @@ class ExternalStore {
}
$class = 'ExternalStore' . ucfirst( $proto );
+
// Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
return class_exists( $class ) ? new $class( $params ) : false;
}
@@ -120,6 +121,7 @@ class ExternalStore {
$retval[$url] = false;
}
}
+
return $retval;
}
@@ -129,7 +131,7 @@ class ExternalStore {
* class itself as a parameter.
*
* @param string $url A partial external store URL ("<store type>://<location>")
- * @param $data string
+ * @param string $data
* @param array $params Associative array of ExternalStoreMedium parameters
* @return string|bool The URL of the stored data item, or false on error
* @throws MWException
@@ -176,7 +178,7 @@ class ExternalStore {
* itself. It also fails-over to the next possible clusters
* as provided in the first parameter.
*
- * @param array $tryStores refer to $wgDefaultExternalStore
+ * @param array $tryStores Refer to $wgDefaultExternalStore
* @param string $data
* @param array $params Associative array of ExternalStoreMedium parameters
* @return string|bool The URL of the stored data item, or false on error
@@ -216,8 +218,8 @@ class ExternalStore {
}
/**
- * @param $data string
- * @param $wiki string
+ * @param string $data
+ * @param string $wiki
* @return string|bool The URL of the stored data item, or false on error
* @throws MWException
*/
diff --git a/includes/externalstore/ExternalStoreDB.php b/includes/externalstore/ExternalStoreDB.php
index 46a89379..952bf63b 100644
--- a/includes/externalstore/ExternalStoreDB.php
+++ b/includes/externalstore/ExternalStoreDB.php
@@ -37,11 +37,12 @@ class ExternalStoreDB extends ExternalStoreMedium {
*/
public function fetchFromURL( $url ) {
list( $cluster, $id, $itemID ) = $this->parseURL( $url );
- $ret =& $this->fetchBlob( $cluster, $id, $itemID );
+ $ret = $this->fetchBlob( $cluster, $id, $itemID );
if ( $itemID !== false && $ret !== false ) {
return $ret->getItem( $itemID );
}
+
return $ret;
}
@@ -66,6 +67,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
$ret = array();
foreach ( $batched as $cluster => $batchByCluster ) {
$res = $this->batchFetchBlobs( $cluster, $batchByCluster );
+ /** @var HistoryBlob $blob */
foreach ( $res as $id => $blob ) {
foreach ( $batchByCluster[$id] as $itemID ) {
$url = $inverseUrlMap[$cluster][$id][$itemID];
@@ -77,6 +79,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
}
}
}
+
return $ret;
}
@@ -93,19 +96,17 @@ class ExternalStoreDB extends ExternalStoreMedium {
if ( !$id ) {
throw new MWException( __METHOD__ . ': no insert ID' );
}
- if ( $dbw->getFlag( DBO_TRX ) ) {
- $dbw->commit( __METHOD__ );
- }
+
return "DB://$cluster/$id";
}
/**
* Get a LoadBalancer for the specified cluster
*
- * @param string $cluster cluster name
- * @return LoadBalancer object
+ * @param string $cluster Cluster name
+ * @return LoadBalancer
*/
- function &getLoadBalancer( $cluster ) {
+ function getLoadBalancer( $cluster ) {
$wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
@@ -114,48 +115,56 @@ class ExternalStoreDB extends ExternalStoreMedium {
/**
* Get a slave database connection for the specified cluster
*
- * @param string $cluster cluster name
- * @return DatabaseBase object
+ * @param string $cluster Cluster name
+ * @return DatabaseBase
*/
- function &getSlave( $cluster ) {
+ function getSlave( $cluster ) {
global $wgDefaultExternalStore;
$wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
- $lb =& $this->getLoadBalancer( $cluster );
+ $lb = $this->getLoadBalancer( $cluster );
if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
- wfDebug( "read only external store" );
+ wfDebug( "read only external store\n" );
$lb->allowLagged( true );
} else {
- wfDebug( "writable external store" );
+ wfDebug( "writable external store\n" );
}
- return $lb->getConnection( DB_SLAVE, array(), $wiki );
+ $db = $lb->getConnection( DB_SLAVE, array(), $wiki );
+ $db->clearFlag( DBO_TRX ); // sanity
+
+ return $db;
}
/**
* Get a master database connection for the specified cluster
*
- * @param string $cluster cluster name
- * @return DatabaseBase object
+ * @param string $cluster Cluster name
+ * @return DatabaseBase
*/
- function &getMaster( $cluster ) {
+ function getMaster( $cluster ) {
$wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
- $lb =& $this->getLoadBalancer( $cluster );
- return $lb->getConnection( DB_MASTER, array(), $wiki );
+ $lb = $this->getLoadBalancer( $cluster );
+
+ $db = $lb->getConnection( DB_MASTER, array(), $wiki );
+ $db->clearFlag( DBO_TRX ); // sanity
+
+ return $db;
}
/**
* Get the 'blobs' table name for this database
*
- * @param $db DatabaseBase
- * @return String: table name ('blobs' by default)
+ * @param DatabaseBase $db
+ * @return string Table name ('blobs' by default)
*/
- function getTable( &$db ) {
+ function getTable( $db ) {
$table = $db->getLBInfo( 'blobs table' );
if ( is_null( $table ) ) {
$table = 'blobs';
}
+
return $table;
}
@@ -163,13 +172,13 @@ class ExternalStoreDB extends ExternalStoreMedium {
* Fetch a blob item out of the database; a cache of the last-loaded
* blob will be kept so that multiple loads out of a multi-item blob
* can avoid redundant database access and decompression.
- * @param $cluster
- * @param $id
- * @param $itemID
+ * @param string $cluster
+ * @param string $id
+ * @param string $itemID
* @return mixed
* @private
*/
- function &fetchBlob( $cluster, $id, $itemID ) {
+ function fetchBlob( $cluster, $id, $itemID ) {
/**
* One-step cache variable to hold base blobs; operations that
* pull multiple revisions may often pull multiple times from
@@ -181,26 +190,27 @@ class ExternalStoreDB extends ExternalStoreMedium {
$cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
if ( isset( $externalBlobCache[$cacheID] ) ) {
wfDebugLog( 'ExternalStoreDB-cache',
- "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
+ "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
+
return $externalBlobCache[$cacheID];
}
wfDebugLog( 'ExternalStoreDB-cache',
- "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
+ "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
- $dbr =& $this->getSlave( $cluster );
+ $dbr = $this->getSlave( $cluster );
$ret = $dbr->selectField( $this->getTable( $dbr ),
'blob_text', array( 'blob_id' => $id ), __METHOD__ );
if ( $ret === false ) {
wfDebugLog( 'ExternalStoreDB',
- "ExternalStoreDB::fetchBlob master fallback on $cacheID\n" );
+ "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
// Try the master
- $dbw =& $this->getMaster( $cluster );
+ $dbw = $this->getMaster( $cluster );
$ret = $dbw->selectField( $this->getTable( $dbw ),
'blob_text', array( 'blob_id' => $id ), __METHOD__ );
if ( $ret === false ) {
wfDebugLog( 'ExternalStoreDB',
- "ExternalStoreDB::fetchBlob master failed to find $cacheID\n" );
+ "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
}
}
if ( $itemID !== false && $ret !== false ) {
@@ -208,7 +218,8 @@ class ExternalStoreDB extends ExternalStoreMedium {
$ret = unserialize( $ret );
}
- $externalBlobCache = array( $cacheID => &$ret );
+ $externalBlobCache = array( $cacheID => $ret );
+
return $ret;
}
@@ -217,7 +228,8 @@ class ExternalStoreDB extends ExternalStoreMedium {
*
* @param string $cluster A cluster name valid for use with LBFactory
* @param array $ids A map from the blob_id's to look for to the requested itemIDs in the blobs
- * @return array A map from the blob_id's requested to their content. Unlocated ids are not represented
+ * @return array A map from the blob_id's requested to their content.
+ * Unlocated ids are not represented
*/
function batchFetchBlobs( $cluster, array $ids ) {
$dbr = $this->getSlave( $cluster );
@@ -230,7 +242,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
if ( $ids ) {
wfDebugLog( __CLASS__, __METHOD__ .
" master fallback on '$cluster' for: " .
- implode( ',', array_keys( $ids ) ) . "\n" );
+ implode( ',', array_keys( $ids ) ) );
// Try the master
$dbw = $this->getMaster( $cluster );
$res = $dbw->select( $this->getTable( $dbr ),
@@ -238,7 +250,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
array( 'blob_id' => array_keys( $ids ) ),
__METHOD__ );
if ( $res === false ) {
- wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'\n" );
+ wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'" );
} else {
$this->mergeBatchResult( $ret, $ids, $res );
}
@@ -246,8 +258,9 @@ class ExternalStoreDB extends ExternalStoreMedium {
if ( $ids ) {
wfDebugLog( __CLASS__, __METHOD__ .
" master on '$cluster' failed locating items: " .
- implode( ',', array_keys( $ids ) ) . "\n" );
+ implode( ',', array_keys( $ids ) ) );
}
+
return $ret;
}
@@ -272,8 +285,13 @@ class ExternalStoreDB extends ExternalStoreMedium {
}
}
+ /**
+ * @param string $url
+ * @return array
+ */
protected function parseURL( $url ) {
$path = explode( '/', $url );
+
return array(
$path[2], // cluster
$path[3], // id
diff --git a/includes/externalstore/ExternalStoreMedium.php b/includes/externalstore/ExternalStoreMedium.php
index 02bdcb51..e9c34a4d 100644
--- a/includes/externalstore/ExternalStoreMedium.php
+++ b/includes/externalstore/ExternalStoreMedium.php
@@ -29,7 +29,7 @@
* @since 1.21
*/
abstract class ExternalStoreMedium {
- /** @var Array */
+ /** @var array */
protected $params = array();
/**
@@ -61,17 +61,18 @@ abstract class ExternalStoreMedium {
// Dont return when false to allow for simpler implementations.
// errored urls are handled in ExternalStore::batchFetchFromURLs
if ( $data !== false ) {
- $retval[$urls] = $data;
+ $retval[$url] = $data;
}
}
+
return $retval;
}
/**
* Insert a data item into a given location
*
- * @param string $location the location name
- * @param string $data the data item
+ * @param string $location The location name
+ * @param string $data The data item
* @return string|bool The URL of the stored data item, or false on error
* @throws MWException
*/
diff --git a/includes/externalstore/ExternalStoreMwstore.php b/includes/externalstore/ExternalStoreMwstore.php
index aa486796..89ac7345 100644
--- a/includes/externalstore/ExternalStoreMwstore.php
+++ b/includes/externalstore/ExternalStoreMwstore.php
@@ -43,6 +43,7 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
// backends should at least have "read-after-create" consistency.
return $be->getFileContents( array( 'src' => $url ) );
}
+
return false;
}
@@ -63,9 +64,10 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
}
$blobs = array();
foreach ( $pathsByBackend as $backendName => $paths ) {
- $be = FileBackendGroup::get( $backendName );
+ $be = FileBackendGroup::singleton()->get( $backendName );
$blobs = $blobs + $be->getFileContentsMulti( array( 'srcs' => $paths ) );
}
+
return $blobs;
}
@@ -82,14 +84,17 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
// Segregate items by wiki ID for the sake of bookkeeping
$wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : wfWikiID();
- $url = $be->getContainerStoragePath( 'data' ) . '/' .
- rawurlencode( $wiki ) . "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}";
+ $url = $be->getContainerStoragePath( 'data' ) . '/' . rawurlencode( $wiki );
+ $url .= ( $be instanceof FSFileBackend )
+ ? "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}" // keep directories small
+ : "/{$rand[0]}/{$rand[1]}/{$id}"; // container sharding is only 2-levels
$be->prepare( array( 'dir' => dirname( $url ), 'noAccess' => 1, 'noListing' => 1 ) );
if ( $be->create( array( 'dst' => $url, 'content' => $data ) )->isOK() ) {
return $url;
}
}
+
return false;
}
}
diff --git a/includes/filebackend/FSFile.php b/includes/filebackend/FSFile.php
index 8f0a1334..1659c62a 100644
--- a/includes/filebackend/FSFile.php
+++ b/includes/filebackend/FSFile.php
@@ -27,26 +27,25 @@
* @ingroup FileBackend
*/
class FSFile {
- protected $path; // path to file
- protected $sha1Base36; // file SHA-1 in base 36
+ /** @var string Path to file */
+ protected $path;
+
+ /** @var string File SHA-1 in base 36 */
+ protected $sha1Base36;
/**
* Sets up the file object
*
* @param string $path Path to temporary file on local disk
- * @throws MWException
*/
public function __construct( $path ) {
- if ( FileBackend::isStoragePath( $path ) ) {
- throw new MWException( __METHOD__ . " given storage path `$path`." );
- }
$this->path = $path;
}
/**
* Returns the file system path
*
- * @return String
+ * @return string
*/
public function getPath() {
return $this->path;
@@ -82,6 +81,7 @@ class FSFile {
if ( $timestamp !== false ) {
$timestamp = wfTimestamp( TS_MW, $timestamp );
}
+
return $timestamp;
}
@@ -98,7 +98,7 @@ class FSFile {
* Get an associative array containing information about
* a file with the given storage path.
*
- * @param Mixed $ext: the file extension, or true to extract it from the filename.
+ * @param string|bool $ext The file extension, or true to extract it from the filename.
* Set it to false to ignore the extension.
*
* @return array
@@ -118,9 +118,9 @@ class FSFile {
$ext = self::extensionFromPath( $this->path );
}
- # mime type according to file contents
+ # MIME type according to file contents
$info['file-mime'] = $this->getMimeType();
- # logical mime type
+ # logical MIME type
$info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
list( $info['major_mime'], $info['minor_mime'] ) = File::splitMime( $info['mime'] );
@@ -147,13 +147,14 @@ class FSFile {
}
wfProfileOut( __METHOD__ );
+
return $info;
}
/**
* Placeholder file properties to use for files that don't exist
*
- * @return Array
+ * @return array
*/
public static function placeholderProps() {
$info = array();
@@ -165,6 +166,7 @@ class FSFile {
$info['width'] = 0;
$info['height'] = 0;
$info['bits'] = 0;
+
return $info;
}
@@ -172,7 +174,7 @@ class FSFile {
* Exract image size information
*
* @param array $gis
- * @return Array
+ * @return array
*/
protected function extractImageSizeInfo( array $gis ) {
$info = array();
@@ -184,6 +186,7 @@ class FSFile {
} else {
$info['bits'] = 0;
}
+
return $info;
}
@@ -202,6 +205,7 @@ class FSFile {
if ( $this->sha1Base36 !== null && !$recache ) {
wfProfileOut( __METHOD__ );
+
return $this->sha1Base36;
}
@@ -214,6 +218,7 @@ class FSFile {
}
wfProfileOut( __METHOD__ );
+
return $this->sha1Base36;
}
@@ -225,19 +230,21 @@ class FSFile {
*/
public static function extensionFromPath( $path ) {
$i = strrpos( $path, '.' );
+
return strtolower( $i ? substr( $path, $i + 1 ) : '' );
}
/**
* Get an associative array containing information about a file in the local filesystem.
*
- * @param string $path absolute local filesystem path
- * @param Mixed $ext: the file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension.
+ * @param string $path Absolute local filesystem path
+ * @param string|bool $ext The file extension, or true to extract it from the filename.
+ * Set it to false to ignore the extension.
* @return array
*/
public static function getPropsFromPath( $path, $ext = true ) {
$fsFile = new self( $path );
+
return $fsFile->getProps( $ext );
}
@@ -253,6 +260,7 @@ class FSFile {
*/
public static function getSha1Base36FromPath( $path ) {
$fsFile = new self( $path );
+
return $fsFile->getSha1Base36();
}
}
diff --git a/includes/filebackend/FSFileBackend.php b/includes/filebackend/FSFileBackend.php
index 6d642162..b99ffb62 100644
--- a/includes/filebackend/FSFileBackend.php
+++ b/includes/filebackend/FSFileBackend.php
@@ -39,14 +39,22 @@
* @since 1.19
*/
class FSFileBackend extends FileBackendStore {
- protected $basePath; // string; directory holding the container directories
- /** @var Array Map of container names to root paths */
- protected $containerPaths = array(); // for custom container paths
- protected $fileMode; // integer; file permission mode
- protected $fileOwner; // string; required OS username to own files
- protected $currentUser; // string; OS username running this script
-
- /** @var Array */
+ /** @var string Directory holding the container directories */
+ protected $basePath;
+
+ /** @var array Map of container names to root paths for custom container paths */
+ protected $containerPaths = array();
+
+ /** @var int File permission mode */
+ protected $fileMode;
+
+ /** @var string Required OS username to own files */
+ protected $fileOwner;
+
+ /** @var string OS username running this script */
+ protected $currentUser;
+
+ /** @var array */
protected $hadWarningErrors = array();
/**
@@ -82,6 +90,10 @@ class FSFileBackend extends FileBackendStore {
}
}
+ public function getFeatures() {
+ return !wfIsWindows() ? FileBackend::ATTR_UNICODE_PATHS : 0;
+ }
+
protected function resolveContainerPath( $container, $relStoragePath ) {
// Check that container has a root directory
if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
@@ -90,6 +102,7 @@ class FSFileBackend extends FileBackendStore {
return $relStoragePath;
}
}
+
return null;
}
@@ -125,6 +138,7 @@ class FSFileBackend extends FileBackendStore {
} elseif ( isset( $this->basePath ) ) {
return "{$this->basePath}/{$fullCont}";
}
+
return null; // no container base path defined
}
@@ -144,6 +158,7 @@ class FSFileBackend extends FileBackendStore {
if ( $relPath != '' ) {
$fsPath .= "/{$relPath}";
}
+
return $fsPath;
}
@@ -174,6 +189,7 @@ class FSFileBackend extends FileBackendStore {
$dest = $this->resolveToFSPath( $params['dst'] );
if ( $dest === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+
return $status;
}
@@ -181,6 +197,7 @@ class FSFileBackend extends FileBackendStore {
$tempFile = TempFSFile::factory( 'create_', 'tmp' );
if ( !$tempFile ) {
$status->fatal( 'backend-fail-create', $params['dst'] );
+
return $status;
}
$this->trapWarnings();
@@ -188,6 +205,7 @@ class FSFileBackend extends FileBackendStore {
$this->untrapWarnings();
if ( $bytes === false ) {
$status->fatal( 'backend-fail-create', $params['dst'] );
+
return $status;
}
$cmd = implode( ' ', array(
@@ -195,7 +213,13 @@ class FSFileBackend extends FileBackendStore {
wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
) );
- $status->value = new FSFileOpHandle( $this, $params, 'Create', $cmd, $dest );
+ $handler = function ( $errors, Status $status, array $params, $cmd ) {
+ if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+ $status->fatal( 'backend-fail-create', $params['dst'] );
+ trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+ }
+ };
+ $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
$tempFile->bind( $status->value );
} else { // immediate write
$this->trapWarnings();
@@ -203,6 +227,7 @@ class FSFileBackend extends FileBackendStore {
$this->untrapWarnings();
if ( $bytes === false ) {
$status->fatal( 'backend-fail-create', $params['dst'] );
+
return $status;
}
$this->chmod( $dest );
@@ -211,22 +236,13 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FSFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseCreate( $errors, Status $status, array $params, $cmd ) {
- if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
- $status->fatal( 'backend-fail-create', $params['dst'] );
- trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
- }
- }
-
protected function doStoreInternal( array $params ) {
$status = Status::newGood();
$dest = $this->resolveToFSPath( $params['dst'] );
if ( $dest === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+
return $status;
}
@@ -236,7 +252,13 @@ class FSFileBackend extends FileBackendStore {
wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
) );
- $status->value = new FSFileOpHandle( $this, $params, 'Store', $cmd, $dest );
+ $handler = function ( $errors, Status $status, array $params, $cmd ) {
+ if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+ $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
+ trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+ }
+ };
+ $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
} else { // immediate write
$this->trapWarnings();
$ok = copy( $params['src'], $dest );
@@ -248,6 +270,7 @@ class FSFileBackend extends FileBackendStore {
trigger_error( __METHOD__ . ": copy() failed but returned true." );
}
$status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
+
return $status;
}
$this->chmod( $dest );
@@ -256,28 +279,20 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FSFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseStore( $errors, Status $status, array $params, $cmd ) {
- if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
- $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
- trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
- }
- }
-
protected function doCopyInternal( array $params ) {
$status = Status::newGood();
$source = $this->resolveToFSPath( $params['src'] );
if ( $source === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
return $status;
}
$dest = $this->resolveToFSPath( $params['dst'] );
if ( $dest === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+
return $status;
}
@@ -285,6 +300,7 @@ class FSFileBackend extends FileBackendStore {
if ( empty( $params['ignoreMissingSource'] ) ) {
$status->fatal( 'backend-fail-copy', $params['src'] );
}
+
return $status; // do nothing; either OK or bad status
}
@@ -294,7 +310,13 @@ class FSFileBackend extends FileBackendStore {
wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
) );
- $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest );
+ $handler = function ( $errors, Status $status, array $params, $cmd ) {
+ if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+ }
+ };
+ $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
} else { // immediate write
$this->trapWarnings();
$ok = ( $source === $dest ) ? true : copy( $source, $dest );
@@ -308,6 +330,7 @@ class FSFileBackend extends FileBackendStore {
trigger_error( __METHOD__ . ": copy() failed but returned true." );
}
$status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+
return $status;
}
$this->chmod( $dest );
@@ -316,28 +339,20 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FSFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseCopy( $errors, Status $status, array $params, $cmd ) {
- if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
- trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
- }
- }
-
protected function doMoveInternal( array $params ) {
$status = Status::newGood();
$source = $this->resolveToFSPath( $params['src'] );
if ( $source === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
return $status;
}
$dest = $this->resolveToFSPath( $params['dst'] );
if ( $dest === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+
return $status;
}
@@ -345,6 +360,7 @@ class FSFileBackend extends FileBackendStore {
if ( empty( $params['ignoreMissingSource'] ) ) {
$status->fatal( 'backend-fail-move', $params['src'] );
}
+
return $status; // do nothing; either OK or bad status
}
@@ -354,7 +370,13 @@ class FSFileBackend extends FileBackendStore {
wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
) );
- $status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd );
+ $handler = function ( $errors, Status $status, array $params, $cmd ) {
+ if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+ $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+ }
+ };
+ $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
} else { // immediate write
$this->trapWarnings();
$ok = ( $source === $dest ) ? true : rename( $source, $dest );
@@ -362,6 +384,7 @@ class FSFileBackend extends FileBackendStore {
clearstatcache(); // file no longer at source
if ( !$ok ) {
$status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+
return $status;
}
}
@@ -369,22 +392,13 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FSFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseMove( $errors, Status $status, array $params, $cmd ) {
- if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
- $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
- trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
- }
- }
-
protected function doDeleteInternal( array $params ) {
$status = Status::newGood();
$source = $this->resolveToFSPath( $params['src'] );
if ( $source === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
return $status;
}
@@ -392,6 +406,7 @@ class FSFileBackend extends FileBackendStore {
if ( empty( $params['ignoreMissingSource'] ) ) {
$status->fatal( 'backend-fail-delete', $params['src'] );
}
+
return $status; // do nothing; either OK or bad status
}
@@ -400,13 +415,20 @@ class FSFileBackend extends FileBackendStore {
wfIsWindows() ? 'DEL' : 'unlink',
wfEscapeShellArg( $this->cleanPathSlashes( $source ) )
) );
- $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd );
+ $handler = function ( $errors, Status $status, array $params, $cmd ) {
+ if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+ $status->fatal( 'backend-fail-delete', $params['src'] );
+ trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+ }
+ };
+ $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
} else { // immediate write
$this->trapWarnings();
$ok = unlink( $source );
$this->untrapWarnings();
if ( !$ok ) {
$status->fatal( 'backend-fail-delete', $params['src'] );
+
return $status;
}
}
@@ -415,15 +437,11 @@ class FSFileBackend extends FileBackendStore {
}
/**
- * @see FSFileBackend::doExecuteOpHandlesInternal()
+ * @param string $fullCont
+ * @param string $dirRel
+ * @param array $params
+ * @return Status
*/
- protected function _getResponseDelete( $errors, Status $status, array $params, $cmd ) {
- if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
- $status->fatal( 'backend-fail-delete', $params['src'] );
- trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
- }
- }
-
protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
@@ -444,6 +462,7 @@ class FSFileBackend extends FileBackendStore {
if ( is_dir( $dir ) && !$existed ) {
$status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
}
+
return $status;
}
@@ -471,6 +490,7 @@ class FSFileBackend extends FileBackendStore {
$status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
}
}
+
return $status;
}
@@ -498,6 +518,7 @@ class FSFileBackend extends FileBackendStore {
}
$this->untrapWarnings();
}
+
return $status;
}
@@ -511,6 +532,7 @@ class FSFileBackend extends FileBackendStore {
rmdir( $dir ); // remove directory if empty
}
$this->untrapWarnings();
+
return $status;
}
@@ -557,7 +579,10 @@ class FSFileBackend extends FileBackendStore {
/**
* @see FileBackendStore::getDirectoryListInternal()
- * @return Array|null
+ * @param string $fullCont
+ * @param string $dirRel
+ * @param array $params
+ * @return array|null
*/
public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
@@ -566,17 +591,23 @@ class FSFileBackend extends FileBackendStore {
$exists = is_dir( $dir );
if ( !$exists ) {
wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
+
return array(); // nothing under this dir
} elseif ( !is_readable( $dir ) ) {
wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
+
return null; // bad permissions?
}
+
return new FSFileBackendDirList( $dir, $params );
}
/**
* @see FileBackendStore::getFileListInternal()
- * @return Array|FSFileBackendFileList|null
+ * @param string $fullCont
+ * @param string $dirRel
+ * @param array $params
+ * @return array|FSFileBackendFileList|null
*/
public function getFileListInternal( $fullCont, $dirRel, array $params ) {
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
@@ -585,11 +616,14 @@ class FSFileBackend extends FileBackendStore {
$exists = is_dir( $dir );
if ( !$exists ) {
wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
+
return array(); // nothing under this dir
} elseif ( !is_readable( $dir ) ) {
wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
+
return null; // bad permissions?
}
+
return new FSFileBackendFileList( $dir, $params );
}
@@ -662,8 +696,8 @@ class FSFileBackend extends FileBackendStore {
foreach ( $fileOpHandles as $index => $fileOpHandle ) {
$status = Status::newGood();
- $function = '_getResponse' . $fileOpHandle->call;
- $this->$function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
+ $function = $fileOpHandle->call;
+ $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
$statuses[$index] = $status;
if ( $status->isOK() && $fileOpHandle->chmodPath ) {
$this->chmod( $fileOpHandle->chmodPath );
@@ -718,8 +752,6 @@ class FSFileBackend extends FileBackendStore {
/**
* Listen for E_WARNING errors and track whether any happen
- *
- * @return void
*/
protected function trapWarnings() {
$this->hadWarningErrors[] = false; // push to stack
@@ -737,7 +769,7 @@ class FSFileBackend extends FileBackendStore {
}
/**
- * @param integer $errno
+ * @param int $errno
* @param string $errstr
* @return bool
* @access private
@@ -745,6 +777,7 @@ class FSFileBackend extends FileBackendStore {
public function handleWarning( $errno, $errstr ) {
wfDebugLog( 'FSFileBackend', $errstr ); // more detailed error logging
$this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
+
return true; // suppress from PHP handler
}
}
@@ -759,9 +792,9 @@ class FSFileOpHandle extends FileBackendStoreOpHandle {
/**
* @param FSFileBackend $backend
* @param array $params
- * @param string $call
+ * @param callable $call
* @param string $cmd
- * @param integer|null $chmodPath
+ * @param int|null $chmodPath
*/
public function __construct(
FSFileBackend $backend, array $params, $call, $cmd, $chmodPath = null
@@ -784,13 +817,18 @@ class FSFileOpHandle extends FileBackendStoreOpHandle {
abstract class FSFileBackendList implements Iterator {
/** @var Iterator */
protected $iter;
- protected $suffixStart; // integer
- protected $pos = 0; // integer
- /** @var Array */
+
+ /** @var int */
+ protected $suffixStart;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var array */
protected $params = array();
/**
- * @param string $dir file system directory
+ * @param string $dir File system directory
* @param array $params
*/
public function __construct( $dir, array $params ) {
@@ -811,7 +849,7 @@ abstract class FSFileBackendList implements Iterator {
/**
* Return an appropriate iterator object to wrap
*
- * @param string $dir file system directory
+ * @param string $dir File system directory
* @return Iterator
*/
protected function initIterator( $dir ) {
@@ -823,6 +861,7 @@ abstract class FSFileBackendList implements Iterator {
# 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
@@ -832,7 +871,7 @@ abstract class FSFileBackendList implements Iterator {
/**
* @see Iterator::key()
- * @return integer
+ * @return int
*/
public function key() {
return $this->pos;
@@ -848,7 +887,7 @@ abstract class FSFileBackendList implements Iterator {
/**
* @see Iterator::next()
- * @return void
+ * @throws FileBackendError
*/
public function next() {
try {
@@ -862,7 +901,7 @@ abstract class FSFileBackendList implements Iterator {
/**
* @see Iterator::rewind()
- * @return void
+ * @throws FileBackendError
*/
public function rewind() {
$this->pos = 0;
@@ -885,13 +924,14 @@ abstract class FSFileBackendList implements Iterator {
/**
* Filter out items by advancing to the next ones
*/
- protected function filterViaNext() {}
+ 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 $path
+ * @param string $dir
* @return string
*/
protected function getRelPath( $dir ) {
@@ -899,6 +939,7 @@ abstract class FSFileBackendList implements Iterator {
if ( $path === false ) {
$path = $dir;
}
+
return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
}
}
diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php
index f586578b..8c0a61a1 100644
--- a/includes/filebackend/FileBackend.php
+++ b/includes/filebackend/FileBackend.php
@@ -47,12 +47,35 @@
* For legacy reasons, the FSFileBackend class allows manually setting the paths of
* containers to ones that do not respect the "wiki ID".
*
- * In key/value stores, the container is the only hierarchy (the rest is emulated).
+ * In key/value (object) stores, containers are the only hierarchy (the rest is emulated).
* FS-based backends are somewhat more restrictive due to the existence of real
* directory files; a regular file cannot have the same name as a directory. Other
* backends with virtual directories may not have this limitation. Callers should
* store files in such a way that no files and directories are under the same path.
*
+ * In general, this class allows for callers to access storage through the same
+ * interface, without regard to the underlying storage system. However, calling code
+ * must follow certain patterns and be aware of certain things to ensure compatibility:
+ * - a) Always call prepare() on the parent directory before trying to put a file there;
+ * key/value stores only need the container to exist first, but filesystems need
+ * all the parent directories to exist first (prepare() is aware of all this)
+ * - b) Always call clean() on a directory when it might become empty to avoid empty
+ * directory buildup on filesystems; key/value stores never have empty directories,
+ * so doing this helps preserve consistency in both cases
+ * - c) Likewise, do not rely on the existence of empty directories for anything;
+ * calling directoryExists() on a path that prepare() was previously called on
+ * will return false for key/value stores if there are no files under that path
+ * - d) Never alter the resulting FSFile returned from getLocalReference(), as it could
+ * either be a copy of the source file in /tmp or the original source file itself
+ * - e) Use a file layout that results in never attempting to store files over directories
+ * or directories over files; key/value stores allow this but filesystems do not
+ * - f) Use ASCII file names (e.g. base32, IDs, hashes) to avoid Unicode issues in Windows
+ * - g) Do not assume that move operations are atomic (difficult with key/value stores)
+ * - h) Do not assume that file stat or read operations always have immediate consistency;
+ * various methods have a "latest" flag that should always be used if up-to-date
+ * information is required (this trades performance for correctness as needed)
+ * - i) Do not assume that directory listings have immediate consistency
+ *
* Methods of subclasses should avoid throwing exceptions at all costs.
* As a corollary, external dependencies should be kept to a minimum.
*
@@ -60,58 +83,69 @@
* @since 1.19
*/
abstract class FileBackend {
- protected $name; // string; unique backend name
- protected $wikiId; // string; unique wiki name
- protected $readOnly; // string; read-only explanation message
- protected $parallelize; // string; when to do operations in parallel
- protected $concurrency; // integer; how many operations can be done in parallel
+ /** @var string Unique backend name */
+ protected $name;
+
+ /** @var string Unique wiki name */
+ protected $wikiId;
+
+ /** @var string Read-only explanation message */
+ protected $readOnly;
+
+ /** @var string When to do operations in parallel */
+ protected $parallelize;
+
+ /** @var int How many operations can be done in parallel */
+ protected $concurrency;
/** @var LockManager */
protected $lockManager;
+
/** @var FileJournal */
protected $fileJournal;
+ /** Bitfield flags for supported features */
+ const ATTR_HEADERS = 1; // files can be tagged with standard HTTP headers
+ const ATTR_METADATA = 2; // files can be stored with metadata key/values
+ const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII)
+
/**
* Create a new backend instance from configuration.
* This should only be called from within FileBackendGroup.
*
- * $config includes:
+ * @param array $config Parameters include:
* - name : The unique name of this backend.
* This should consist of alphanumberic, '-', and '_' characters.
* This name should not be changed after use (e.g. with journaling).
* Note that the name is *not* used in actual container names.
* - wikiId : Prefix to container names that is unique to this backend.
- * If not provided, this defaults to the current wiki ID.
* It should only consist of alphanumberic, '-', and '_' characters.
* This ID is what avoids collisions if multiple logical backends
* use the same storage system, so this should be set carefully.
- * - lockManager : Registered name of a file lock manager to use.
- * - fileJournal : File journal configuration; see FileJournal::factory().
- * Journals simply log changes to files stored in the backend.
+ * - lockManager : LockManager object to use for any file locking.
+ * If not provided, then no file locking will be enforced.
+ * - fileJournal : FileJournal object to use for logging changes to files.
+ * If not provided, then change journaling will be disabled.
* - readOnly : Write operations are disallowed if this is a non-empty string.
* It should be an explanation for the backend being read-only.
* - parallelize : When to do file operations in parallel (when possible).
* Allowed values are "implicit", "explicit" and "off".
* - concurrency : How many file operations can be done in parallel.
- *
- * @param array $config
- * @throws MWException
+ * @throws FileBackendException
*/
public function __construct( array $config ) {
$this->name = $config['name'];
+ $this->wikiId = $config['wikiId']; // e.g. "my_wiki-en_"
if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
- throw new MWException( "Backend name `{$this->name}` is invalid." );
+ throw new FileBackendException( "Backend name '{$this->name}' is invalid." );
+ } elseif ( !is_string( $this->wikiId ) ) {
+ throw new FileBackendException( "Backend wiki ID not provided for '{$this->name}'." );
}
- $this->wikiId = isset( $config['wikiId'] )
- ? $config['wikiId']
- : wfWikiID(); // e.g. "my_wiki-en_"
- $this->lockManager = ( $config['lockManager'] instanceof LockManager )
+ $this->lockManager = isset( $config['lockManager'] )
? $config['lockManager']
- : LockManagerGroup::singleton( $this->wikiId )->get( $config['lockManager'] );
+ : new NullLockManager( array() );
$this->fileJournal = isset( $config['fileJournal'] )
- ? ( ( $config['fileJournal'] instanceof FileJournal )
- ? $config['fileJournal']
- : FileJournal::factory( $config['fileJournal'], $this->name ) )
+ ? $config['fileJournal']
: FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
$this->readOnly = isset( $config['readOnly'] )
? (string)$config['readOnly']
@@ -165,6 +199,27 @@ abstract class FileBackend {
}
/**
+ * Get the a bitfield of extra features supported by the backend medium
+ *
+ * @return int Bitfield of FileBackend::ATTR_* flags
+ * @since 1.23
+ */
+ public function getFeatures() {
+ return self::ATTR_UNICODE_PATHS;
+ }
+
+ /**
+ * Check if the backend medium supports a field of extra features
+ *
+ * @param int $bitfield Bitfield of FileBackend::ATTR_* flags
+ * @return bool
+ * @since 1.23
+ */
+ final public function hasFeatures( $bitfield ) {
+ return ( $this->getFeatures() & $bitfield ) === $bitfield;
+ }
+
+ /**
* This is the main entry point into the backend for write operations.
* Callers supply an ordered list of operations to perform as a transaction.
* Files will be locked, the stat cache cleared, and then the operations attempted.
@@ -671,8 +726,7 @@ abstract class FileBackend {
* otherwise safe from modification from other processes. Normally,
* the file will be a new temp file, which should be adequate.
*
- * @param array $params Operation parameters
- * $params include:
+ * @param array $params Operation parameters, include:
* - srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
* - dst : file system path to 0-byte temp file
* - parallelize : try to do operations in parallel when possible
@@ -691,8 +745,7 @@ abstract class FileBackend {
* However, setting them is not guaranteed to actually do anything.
* Additional server configuration may be needed to achieve the desired effect.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
* - noAccess : try to deny file access (since 1.20)
* - noListing : try to deny file listing (since 1.20)
@@ -721,8 +774,7 @@ abstract class FileBackend {
* This is not guaranteed to actually make files or listings publically hidden.
* Additional server configuration may be needed to achieve the desired effect.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
* - noAccess : try to deny file access
* - noListing : try to deny file listing
@@ -752,8 +804,7 @@ abstract class FileBackend {
* This is not guaranteed to actually make files or listings publically viewable.
* Additional server configuration may be needed to achieve the desired effect.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
* - access : try to allow file access
* - listing : try to allow file listing
@@ -779,8 +830,7 @@ abstract class FileBackend {
* Backends using key/value stores may do nothing unless the directory
* is that of an empty container, in which case it will be deleted.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
* - recursive : recursively delete empty subdirectories first (since 1.20)
* - bypassReadOnly : allow writes in read-only mode (since 1.20)
@@ -807,12 +857,13 @@ abstract class FileBackend {
* @return ScopedCallback|null
*/
final protected function getScopedPHPBehaviorForOps() {
- if ( php_sapi_name() != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
+ if ( PHP_SAPI != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
$old = ignore_user_abort( true ); // avoid half-finished operations
- return new ScopedCallback( function() use ( $old ) {
+ return new ScopedCallback( function () use ( $old ) {
ignore_user_abort( $old );
} );
}
+
return null;
}
@@ -820,8 +871,7 @@ abstract class FileBackend {
* Check if a file exists at a storage path in the backend.
* This returns false if only a directory exists at the path.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
* @return bool|null Returns null on failure
@@ -831,8 +881,7 @@ abstract class FileBackend {
/**
* Get the last-modified timestamp of the file at a storage path.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
* @return string|bool TS_MW timestamp or false on failure
@@ -843,8 +892,7 @@ abstract class FileBackend {
* Get the contents of a file at a storage path in the backend.
* This should be avoided for potentially large files.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
* @return string|bool Returns false on failure
@@ -863,24 +911,42 @@ abstract class FileBackend {
*
* @see FileBackend::getFileContents()
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - srcs : list of source storage paths
* - latest : use the latest available data
* - parallelize : try to do operations in parallel when possible
- * @return Array Map of (path name => string or false on failure)
+ * @return array Map of (path name => string or false on failure)
* @since 1.20
*/
abstract public function getFileContentsMulti( array $params );
/**
- * Get the size (bytes) of a file at a storage path in the backend.
+ * Get metadata about a file at a storage path in the backend.
+ * If the file does not exist, then this returns false.
+ * Otherwise, the result is an associative array that includes:
+ * - headers : map of HTTP headers used for GET/HEAD requests (name => value)
+ * - metadata : map of file metadata (name => value)
+ * Metadata keys and headers names will be returned in all lower-case.
+ * Additional values may be included for internal use only.
+ *
+ * Use FileBackend::hasFeatures() to check how well this is supported.
*
* @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
- * @return integer|bool Returns false on failure
+ * @return array|bool Returns false on failure
+ * @since 1.23
+ */
+ abstract public function getFileXAttributes( array $params );
+
+ /**
+ * Get the size (bytes) of a file at a storage path in the backend.
+ *
+ * @param array $params Parameters include:
+ * - src : source storage path
+ * - latest : use the latest available data
+ * @return int|bool Returns false on failure
*/
abstract public function getFileSize( array $params );
@@ -892,19 +958,17 @@ abstract class FileBackend {
* - size : the file size (bytes)
* Additional values may be included for internal use only.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return Array|bool|null Returns null on failure
+ * @return array|bool|null Returns null on failure
*/
abstract public function getFileStat( array $params );
/**
* Get a SHA-1 hash of the file at a storage path in the backend.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
* @return string|bool Hash string or false on failure
@@ -915,11 +979,10 @@ abstract class FileBackend {
* Get the properties of the file at a storage path in the backend.
* This gives the result of FSFile::getProps() on a local copy of the file.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return Array Returns FSFile::placeholderProps() on failure
+ * @return array Returns FSFile::placeholderProps() on failure
*/
abstract public function getFileProps( array $params );
@@ -930,8 +993,7 @@ abstract class FileBackend {
* will be sent if streaming began, while none will be sent otherwise.
* Implementations should flush the output buffer before sending data.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - headers : list of additional HTTP headers to send on success
* - latest : use the latest available data
@@ -952,8 +1014,7 @@ abstract class FileBackend {
* In that later case, there are copies of the file that must stay in sync.
* Additionally, further calls to this function may return the same file.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
* @return FSFile|null Returns null on failure
@@ -972,12 +1033,11 @@ abstract class FileBackend {
*
* @see FileBackend::getLocalReference()
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - srcs : list of source storage paths
* - latest : use the latest available data
* - parallelize : try to do operations in parallel when possible
- * @return Array Map of (path name => FSFile or null on failure)
+ * @return array Map of (path name => FSFile or null on failure)
* @since 1.20
*/
abstract public function getLocalReferenceMulti( array $params );
@@ -987,8 +1047,7 @@ abstract class FileBackend {
* The temporary copy will have the same file extension as the source.
* Temporary files may be purged when the file object falls out of scope.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
* @return TempFSFile|null Returns null on failure
@@ -1007,12 +1066,11 @@ abstract class FileBackend {
*
* @see FileBackend::getLocalCopy()
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - srcs : list of source storage paths
* - latest : use the latest available data
* - parallelize : try to do operations in parallel when possible
- * @return Array Map of (path name => TempFSFile or null on failure)
+ * @return array Map of (path name => TempFSFile or null on failure)
* @since 1.20
*/
abstract public function getLocalCopyMulti( array $params );
@@ -1027,8 +1085,7 @@ abstract class FileBackend {
* Otherwise, one would need to use getLocalReference(), which involves loading
* the entire file on to local disk.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - src : source storage path
* - ttl : lifetime (seconds) if pre-authenticated; default is 1 day
* @return string|null
@@ -1043,8 +1100,7 @@ abstract class FileBackend {
*
* Storage backends with eventual consistency might return stale data.
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
* @return bool|null Returns null on failure
* @since 1.20
@@ -1063,11 +1119,10 @@ abstract class FileBackend {
*
* Failures during iteration can result in FileBackendError exceptions (since 1.22).
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
* - topOnly : only return direct child dirs of the directory
- * @return Traversable|Array|null Returns null on failure
+ * @return Traversable|array|null Returns null on failure
* @since 1.20
*/
abstract public function getDirectoryList( array $params );
@@ -1080,10 +1135,9 @@ abstract class FileBackend {
*
* Failures during iteration can result in FileBackendError exceptions (since 1.22).
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
- * @return Traversable|Array|null Returns null on failure
+ * @return Traversable|array|null Returns null on failure
* @since 1.20
*/
final public function getTopDirectoryList( array $params ) {
@@ -1102,12 +1156,11 @@ abstract class FileBackend {
*
* Failures during iteration can result in FileBackendError exceptions (since 1.22).
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
* - topOnly : only return direct child files of the directory (since 1.20)
* - adviseStat : set to true if stat requests will be made on the files (since 1.22)
- * @return Traversable|Array|null Returns null on failure
+ * @return Traversable|array|null Returns null on failure
*/
abstract public function getFileList( array $params );
@@ -1119,11 +1172,10 @@ abstract class FileBackend {
*
* Failures during iteration can result in FileBackendError exceptions (since 1.22).
*
- * @param array $params
- * $params include:
+ * @param array $params Parameters include:
* - dir : storage directory
* - adviseStat : set to true if stat requests will be made on the files (since 1.22)
- * @return Traversable|Array|null Returns null on failure
+ * @return Traversable|array|null Returns null on failure
* @since 1.20
*/
final public function getTopFileList( array $params ) {
@@ -1131,22 +1183,38 @@ abstract class FileBackend {
}
/**
- * Preload persistent file stat and property cache into in-process cache.
+ * Preload persistent file stat cache and property cache into in-process cache.
* This should be used when stat calls will be made on a known list of a many files.
*
+ * @see FileBackend::getFileStat()
+ *
* @param array $paths Storage paths
- * @return void
*/
- public function preloadCache( array $paths ) {}
+ abstract public function preloadCache( array $paths );
/**
* Invalidate any in-process file stat and property cache.
* If $paths is given, then only the cache for those files will be cleared.
*
+ * @see FileBackend::getFileStat()
+ *
* @param array $paths Storage paths (optional)
- * @return void
*/
- public function clearCache( array $paths = null ) {}
+ abstract public function clearCache( array $paths = null );
+
+ /**
+ * Preload file stat information (concurrently if possible) into in-process cache.
+ * This should be used when stat calls will be made on a known list of a many files.
+ *
+ * @see FileBackend::getFileStat()
+ *
+ * @param array $params Parameters include:
+ * - srcs : list of source storage paths
+ * - latest : use the latest available data
+ * @return bool All requests proceeded without I/O errors (since 1.24)
+ * @since 1.23
+ */
+ abstract public function preloadFileStat( array $params );
/**
* Lock the files at the given storage paths in the backend.
@@ -1155,23 +1223,26 @@ abstract class FileBackend {
* Callers should consider using getScopedFileLocks() instead.
*
* @param array $paths Storage paths
- * @param integer $type LockManager::LOCK_* constant
+ * @param int $type LockManager::LOCK_* constant
+ * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
* @return Status
*/
- final public function lockFiles( array $paths, $type ) {
+ final public function lockFiles( array $paths, $type, $timeout = 0 ) {
$paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
- return $this->lockManager->lock( $paths, $type );
+
+ return $this->lockManager->lock( $paths, $type, $timeout );
}
/**
* Unlock the files at the given storage paths in the backend.
*
* @param array $paths Storage paths
- * @param integer $type LockManager::LOCK_* constant
+ * @param int $type LockManager::LOCK_* constant
* @return Status
*/
final public function unlockFiles( array $paths, $type ) {
$paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
+
return $this->lockManager->unlock( $paths, $type );
}
@@ -1186,11 +1257,12 @@ abstract class FileBackend {
* @see ScopedLock::factory()
*
* @param array $paths List of storage paths or map of lock types to path lists
- * @param integer|string $type LockManager::LOCK_* constant or "mixed"
+ * @param int|string $type LockManager::LOCK_* constant or "mixed"
* @param Status $status Status to update on lock/unlock
+ * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
* @return ScopedLock|null Returns null on failure
*/
- final public function getScopedFileLocks( array $paths, $type, Status $status ) {
+ final public function getScopedFileLocks( array $paths, $type, Status $status, $timeout = 0 ) {
if ( $type === 'mixed' ) {
foreach ( $paths as &$typePaths ) {
$typePaths = array_map( 'FileBackend::normalizeStoragePath', $typePaths );
@@ -1198,7 +1270,8 @@ abstract class FileBackend {
} else {
$paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
}
- return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
+
+ return ScopedLock::factory( $this->lockManager, $paths, $type, $status, $timeout );
}
/**
@@ -1214,7 +1287,7 @@ abstract class FileBackend {
*
* @param array $ops List of file operations to FileBackend::doOperations()
* @param Status $status Status to update on lock/unlock
- * @return Array List of ScopedFileLocks or null values
+ * @return array List of ScopedFileLocks or null values
* @since 1.20
*/
abstract public function getScopedLocksForOps( array $ops, Status $status );
@@ -1267,7 +1340,7 @@ abstract class FileBackend {
* This does not do any path normalization or traversal checks.
*
* @param string $storagePath
- * @return Array (backend, container, rel object) or (null, null, null)
+ * @return array (backend, container, rel object) or (null, null, null)
*/
final public static function splitStoragePath( $storagePath ) {
if ( self::isStoragePath( $storagePath ) ) {
@@ -1281,6 +1354,7 @@ abstract class FileBackend {
}
}
}
+
return array( null, null, null );
}
@@ -1301,6 +1375,7 @@ abstract class FileBackend {
: "mwstore://{$backend}/{$container}";
}
}
+
return null;
}
@@ -1315,6 +1390,7 @@ abstract class FileBackend {
final public static function parentStoragePath( $storagePath ) {
$storagePath = dirname( $storagePath );
list( , , $rel ) = self::splitStoragePath( $storagePath );
+
return ( $rel === null ) ? null : $storagePath;
}
@@ -1322,11 +1398,20 @@ abstract class FileBackend {
* Get the final extension from a storage or FS path
*
* @param string $path
+ * @param string $case One of (rawcase, uppercase, lowercase) (since 1.24)
* @return string
*/
- final public static function extensionFromPath( $path ) {
+ final public static function extensionFromPath( $path, $case = 'lowercase' ) {
$i = strrpos( $path, '.' );
- return strtolower( $i ? substr( $path, $i + 1 ) : '' );
+ $ext = $i ? substr( $path, $i + 1 ) : '';
+
+ if ( $case === 'lowercase' ) {
+ $ext = strtolower( $ext );
+ } elseif ( $case === 'uppercase' ) {
+ $ext = strtoupper( $ext );
+ }
+
+ return $ext;
}
/**
@@ -1345,7 +1430,7 @@ abstract class FileBackend {
*
* @param string $type One of (attachment, inline)
* @param string $filename Suggested file name (should not contain slashes)
- * @throws MWException
+ * @throws FileBackendError
* @return string
* @since 1.20
*/
@@ -1354,7 +1439,7 @@ abstract class FileBackend {
$type = strtolower( $type );
if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) {
- throw new MWException( "Invalid Content-Disposition type '$type'." );
+ throw new FileBackendError( "Invalid Content-Disposition type '$type'." );
}
$parts[] = $type;
@@ -1395,12 +1480,25 @@ abstract class FileBackend {
return null;
}
}
+
return $path;
}
}
/**
+ * Generic file backend exception for checked and unexpected (e.g. config) exceptions
+ *
+ * @ingroup FileBackend
+ * @since 1.23
+ */
+class FileBackendException extends MWException {
+}
+
+/**
+ * File backend exception for checked exceptions (e.g. I/O errors)
+ *
* @ingroup FileBackend
* @since 1.22
*/
-class FileBackendError extends MWException {}
+class FileBackendError extends FileBackendException {
+}
diff --git a/includes/filebackend/FileBackendGroup.php b/includes/filebackend/FileBackendGroup.php
index be8a2076..1b88db7e 100644
--- a/includes/filebackend/FileBackendGroup.php
+++ b/includes/filebackend/FileBackendGroup.php
@@ -29,15 +29,14 @@
* @since 1.19
*/
class FileBackendGroup {
- /**
- * @var FileBackendGroup
- */
+ /** @var FileBackendGroup */
protected static $instance = null;
- /** @var Array (name => ('class' => string, 'config' => array, 'instance' => object)) */
+ /** @var array (name => ('class' => string, 'config' => array, 'instance' => object)) */
protected $backends = array();
- protected function __construct() {}
+ protected function __construct() {
+ }
/**
* @return FileBackendGroup
@@ -47,13 +46,12 @@ class FileBackendGroup {
self::$instance = new self();
self::$instance->initFromGlobals();
}
+
return self::$instance;
}
/**
* Destroy the singleton instance
- *
- * @return void
*/
public static function destroySingleton() {
self::$instance = null;
@@ -61,8 +59,6 @@ class FileBackendGroup {
/**
* Register file backends from the global variables
- *
- * @return void
*/
protected function initFromGlobals() {
global $wgLocalFileRepo, $wgForeignFileRepos, $wgFileBackends;
@@ -116,20 +112,19 @@ class FileBackendGroup {
/**
* Register an array of file backend configurations
*
- * @param Array $configs
- * @return void
- * @throws MWException
+ * @param array $configs
+ * @throws FileBackendException
*/
protected function register( array $configs ) {
foreach ( $configs as $config ) {
if ( !isset( $config['name'] ) ) {
- throw new MWException( "Cannot register a backend with no name." );
+ throw new FileBackendException( "Cannot register a backend with no name." );
}
$name = $config['name'];
if ( isset( $this->backends[$name] ) ) {
- throw new MWException( "Backend with name `{$name}` already registered." );
+ throw new FileBackendException( "Backend with name `{$name}` already registered." );
} elseif ( !isset( $config['class'] ) ) {
- throw new MWException( "Cannot register backend `{$name}` with no class." );
+ throw new FileBackendException( "Backend with name `{$name}` has no class." );
}
$class = $config['class'];
@@ -147,18 +142,27 @@ class FileBackendGroup {
*
* @param string $name
* @return FileBackend
- * @throws MWException
+ * @throws FileBackendException
*/
public function get( $name ) {
if ( !isset( $this->backends[$name] ) ) {
- throw new MWException( "No backend defined with the name `$name`." );
+ throw new FileBackendException( "No backend defined with the name `$name`." );
}
// Lazy-load the actual backend instance
if ( !isset( $this->backends[$name]['instance'] ) ) {
$class = $this->backends[$name]['class'];
$config = $this->backends[$name]['config'];
+ $config['wikiId'] = isset( $config['wikiId'] )
+ ? $config['wikiId']
+ : wfWikiID(); // e.g. "my_wiki-en_"
+ $config['lockManager'] =
+ LockManagerGroup::singleton( $config['wikiId'] )->get( $config['lockManager'] );
+ $config['fileJournal'] = isset( $config['fileJournal'] )
+ ? FileJournal::factory( $config['fileJournal'], $name )
+ : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $name );
$this->backends[$name]['instance'] = new $class( $config );
}
+
return $this->backends[$name]['instance'];
}
@@ -166,14 +170,15 @@ class FileBackendGroup {
* Get the config array for a backend object with a given name
*
* @param string $name
- * @return Array
- * @throws MWException
+ * @return array
+ * @throws FileBackendException
*/
public function config( $name ) {
if ( !isset( $this->backends[$name] ) ) {
- throw new MWException( "No backend defined with the name `$name`." );
+ throw new FileBackendException( "No backend defined with the name `$name`." );
}
$class = $this->backends[$name]['class'];
+
return array( 'class' => $class ) + $this->backends[$name]['config'];
}
@@ -188,6 +193,7 @@ class FileBackendGroup {
if ( $backend !== null && isset( $this->backends[$backend] ) ) {
return $this->get( $backend );
}
+
return null;
}
}
diff --git a/includes/filebackend/FileBackendMultiWrite.php b/includes/filebackend/FileBackendMultiWrite.php
index 97584a71..bfffcc0f 100644
--- a/includes/filebackend/FileBackendMultiWrite.php
+++ b/includes/filebackend/FileBackendMultiWrite.php
@@ -40,15 +40,25 @@
* @since 1.19
*/
class FileBackendMultiWrite extends FileBackend {
- /** @var Array Prioritized list of FileBackendStore objects */
- protected $backends = array(); // array of (backend index => backends)
- protected $masterIndex = -1; // integer; index of master backend
- protected $syncChecks = 0; // integer; bitfield
- protected $autoResync = false; // boolean
+ /** @var array Prioritized list of FileBackendStore objects.
+ * array of (backend index => backends)
+ */
+ protected $backends = array();
+
+ /** @var int Index of master backend */
+ protected $masterIndex = -1;
+
+ /** @var int Bitfield */
+ protected $syncChecks = 0;
- /** @var Array */
+ /** @var string|bool */
+ protected $autoResync = false;
+
+ /** @var array */
protected $noPushDirConts = array();
- protected $noPushQuickOps = false; // boolean
+
+ /** @var bool */
+ protected $noPushQuickOps = false;
/* Possible internal backend consistency checks */
const CHECK_SIZE = 1;
@@ -81,8 +91,8 @@ class FileBackendMultiWrite extends FileBackend {
* - noPushQuickOps : (hack) Only apply doQuickOperations() to the master backend.
* - noPushDirConts : (hack) Only apply directory functions to the master backend.
*
- * @param Array $config
- * @throws MWException
+ * @param array $config
+ * @throws FileBackendError
*/
public function __construct( array $config ) {
parent::__construct( $config );
@@ -109,30 +119,30 @@ class FileBackendMultiWrite extends FileBackend {
}
$name = $config['name'];
if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
- throw new MWException( "Two or more backends defined with the name $name." );
+ throw new FileBackendError( "Two or more backends defined with the name $name." );
}
$namesUsed[$name] = 1;
// Alter certain sub-backend settings for sanity
unset( $config['readOnly'] ); // use proxy backend setting
unset( $config['fileJournal'] ); // use proxy backend journal
+ unset( $config['lockManager'] ); // lock under proxy backend
$config['wikiId'] = $this->wikiId; // use the proxy backend wiki ID
- $config['lockManager'] = 'nullLockManager'; // lock under proxy backend
if ( !empty( $config['isMultiMaster'] ) ) {
if ( $this->masterIndex >= 0 ) {
- throw new MWException( 'More than one master backend defined.' );
+ throw new FileBackendError( 'More than one master backend defined.' );
}
$this->masterIndex = $index; // this is the "master"
$config['fileJournal'] = $this->fileJournal; // log under proxy backend
}
// Create sub-backend object
if ( !isset( $config['class'] ) ) {
- throw new MWException( 'No class given for a backend config.' );
+ throw new FileBackendError( 'No class given for a backend config.' );
}
$class = $config['class'];
$this->backends[$index] = new $class( $config );
}
if ( $this->masterIndex < 0 ) { // need backends and must have a master
- throw new MWException( 'No master backend defined.' );
+ throw new FileBackendError( 'No master backend defined.' );
}
}
@@ -167,6 +177,7 @@ class FileBackendMultiWrite extends FileBackend {
// Try to resync the clone backends to the master on the spot...
if ( !$this->autoResync || !$this->resyncFiles( $relevantPaths )->isOK() ) {
$status->merge( $syncStatus );
+
return $status; // abort
}
}
@@ -321,8 +332,8 @@ class FileBackendMultiWrite extends FileBackend {
// already synced; nothing to do
} elseif ( $mSha1 !== false ) { // file is in master
if ( $this->autoResync === 'conservative'
- && $cStat && $cStat['mtime'] > $mStat['mtime'] )
- {
+ && $cStat && $cStat['mtime'] > $mStat['mtime']
+ ) {
$status->fatal( 'backend-fail-synced', $path );
continue; // don't rollback data
}
@@ -348,7 +359,7 @@ class FileBackendMultiWrite extends FileBackend {
* Get a list of file storage paths to read or write for a list of operations
*
* @param array $ops Same format as doOperations()
- * @return Array List of storage paths to files (does not include directories)
+ * @return array List of storage paths to files (does not include directories)
*/
protected function fileStoragePathsForOps( array $ops ) {
$paths = array();
@@ -357,8 +368,8 @@ class FileBackendMultiWrite extends FileBackend {
// For things like copy/move/delete with "ignoreMissingSource" and there
// is no source file, nothing should happen and there should be no errors.
if ( empty( $op['ignoreMissingSource'] )
- || $this->fileExists( array( 'src' => $op['src'] ) ) )
- {
+ || $this->fileExists( array( 'src' => $op['src'] ) )
+ ) {
$paths[] = $op['src'];
}
}
@@ -369,6 +380,7 @@ class FileBackendMultiWrite extends FileBackend {
$paths[] = $op['dst'];
}
}
+
return array_values( array_unique( array_filter( $paths, 'FileBackend::isStoragePath' ) ) );
}
@@ -378,7 +390,7 @@ class FileBackendMultiWrite extends FileBackend {
*
* @param array $ops List of file operation arrays
* @param FileBackendStore $backend
- * @return Array
+ * @return array
*/
protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
$newOps = array(); // operations
@@ -391,6 +403,7 @@ class FileBackendMultiWrite extends FileBackend {
}
$newOps[] = $newOp;
}
+
return $newOps;
}
@@ -399,10 +412,11 @@ class FileBackendMultiWrite extends FileBackend {
*
* @param array $ops File operation array
* @param FileBackendStore $backend
- * @return Array
+ * @return array
*/
protected function substOpPaths( array $ops, FileBackendStore $backend ) {
$newOps = $this->substOpBatchPaths( array( $ops ), $backend );
+
return $newOps[0];
}
@@ -411,7 +425,7 @@ class FileBackendMultiWrite extends FileBackend {
*
* @param array|string $paths List of paths or single string path
* @param FileBackendStore $backend
- * @return Array|string
+ * @return array|string
*/
protected function substPaths( $paths, FileBackendStore $backend ) {
return preg_replace(
@@ -425,7 +439,7 @@ class FileBackendMultiWrite extends FileBackend {
* Substitute the backend of internal storage paths with the proxy backend's name
*
* @param array|string $paths List of paths or single string path
- * @return Array|string
+ * @return array|string
*/
protected function unsubstPaths( $paths ) {
return preg_replace(
@@ -456,6 +470,7 @@ class FileBackendMultiWrite extends FileBackend {
$status->success = $masterStatus->success;
$status->successCount = $masterStatus->successCount;
$status->failCount = $masterStatus->failCount;
+
return $status;
}
@@ -465,6 +480,7 @@ class FileBackendMultiWrite extends FileBackend {
*/
protected function replicateContainerDirChanges( $path ) {
list( , $shortCont, ) = self::splitStoragePath( $path );
+
return !in_array( $shortCont, $this->noPushDirConts );
}
@@ -477,6 +493,7 @@ class FileBackendMultiWrite extends FileBackend {
$status->merge( $backend->doPrepare( $realParams ) );
}
}
+
return $status;
}
@@ -489,6 +506,7 @@ class FileBackendMultiWrite extends FileBackend {
$status->merge( $backend->doSecure( $realParams ) );
}
}
+
return $status;
}
@@ -501,6 +519,7 @@ class FileBackendMultiWrite extends FileBackend {
$status->merge( $backend->doPublish( $realParams ) );
}
}
+
return $status;
}
@@ -513,35 +532,47 @@ class FileBackendMultiWrite extends FileBackend {
$status->merge( $backend->doClean( $realParams ) );
}
}
+
return $status;
}
public function concatenate( array $params ) {
// We are writing to an FS file, so we don't need to do this per-backend
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->concatenate( $realParams );
}
public function fileExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->fileExists( $realParams );
}
public function getFileTimestamp( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->getFileTimestamp( $realParams );
}
public function getFileSize( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->getFileSize( $realParams );
}
public function getFileStat( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->getFileStat( $realParams );
}
+ public function getFileXAttributes( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
+ return $this->backends[$this->masterIndex]->getFileXAttributes( $realParams );
+ }
+
public function getFileContentsMulti( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
$contentsM = $this->backends[$this->masterIndex]->getFileContentsMulti( $realParams );
@@ -550,21 +581,25 @@ class FileBackendMultiWrite extends FileBackend {
foreach ( $contentsM as $path => $data ) {
$contents[$this->unsubstPaths( $path )] = $data;
}
+
return $contents;
}
public function getFileSha1Base36( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->getFileSha1Base36( $realParams );
}
public function getFileProps( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->getFileProps( $realParams );
}
public function streamFile( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->streamFile( $realParams );
}
@@ -576,6 +611,7 @@ class FileBackendMultiWrite extends FileBackend {
foreach ( $fsFilesM as $path => $fsFile ) {
$fsFiles[$this->unsubstPaths( $path )] = $fsFile;
}
+
return $fsFiles;
}
@@ -587,29 +623,38 @@ class FileBackendMultiWrite extends FileBackend {
foreach ( $tempFilesM as $path => $tempFile ) {
$tempFiles[$this->unsubstPaths( $path )] = $tempFile;
}
+
return $tempFiles;
}
public function getFileHttpUrl( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->getFileHttpUrl( $realParams );
}
public function directoryExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->directoryExists( $realParams );
}
public function getDirectoryList( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
}
public function getFileList( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+
return $this->backends[$this->masterIndex]->getFileList( $realParams );
}
+ public function getFeatures() {
+ return $this->backends[$this->masterIndex]->getFeatures();
+ }
+
public function clearCache( array $paths = null ) {
foreach ( $this->backends as $backend ) {
$realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
@@ -617,6 +662,16 @@ class FileBackendMultiWrite extends FileBackend {
}
}
+ public function preloadCache( array $paths ) {
+ $realPaths = $this->substPaths( $paths, $this->backends[$this->masterIndex] );
+ $this->backends[$this->masterIndex]->preloadCache( $realPaths );
+ }
+
+ public function preloadFileStat( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->preloadFileStat( $realParams );
+ }
+
public function getScopedLocksForOps( array $ops, Status $status ) {
$realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
$fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
@@ -627,6 +682,7 @@ class FileBackendMultiWrite extends FileBackend {
LockManager::LOCK_UW => $this->unsubstPaths( $paths[LockManager::LOCK_UW] ),
LockManager::LOCK_EX => $this->unsubstPaths( $paths[LockManager::LOCK_EX] )
);
+
// Actually acquire the locks
return array( $this->getScopedFileLocks( $pbPaths, 'mixed', $status ) );
}
diff --git a/includes/filebackend/FileBackendStore.php b/includes/filebackend/FileBackendStore.php
index 0921e99f..495ac3c0 100644
--- a/includes/filebackend/FileBackendStore.php
+++ b/includes/filebackend/FileBackendStore.php
@@ -43,16 +43,16 @@ abstract class FileBackendStore extends FileBackend {
/** @var ProcessCacheLRU Map of paths to large (RAM/disk) cache items */
protected $expensiveCache;
- /** @var Array Map of container names to sharding config */
+ /** @var array Map of container names to sharding config */
protected $shardViaHashLevels = array();
- /** @var callback Method to get the MIME type of files */
+ /** @var callable Method to get the MIME type of files */
protected $mimeCallback;
protected $maxFileSize = 4294967296; // integer bytes (4GiB)
const CACHE_TTL = 10; // integer; TTL in seconds for process cache entries
- const CACHE_CHEAP_SIZE = 300; // integer; max entries in "cheap cache"
+ const CACHE_CHEAP_SIZE = 500; // integer; max entries in "cheap cache"
const CACHE_EXPENSIVE_SIZE = 5; // integer; max entries in "expensive cache"
/**
@@ -68,8 +68,8 @@ abstract class FileBackendStore extends FileBackend {
parent::__construct( $config );
$this->mimeCallback = isset( $config['mimeCallback'] )
? $config['mimeCallback']
- : function( $storagePath, $content, $fsPath ) {
- // @TODO: handle the case of extension-less files using the contents
+ : function ( $storagePath, $content, $fsPath ) {
+ // @todo handle the case of extension-less files using the contents
return StreamFile::contentTypeFromPath( $storagePath ) ?: 'unknown/unknown';
};
$this->memCache = new EmptyBagOStuff(); // disabled by default
@@ -82,7 +82,7 @@ abstract class FileBackendStore extends FileBackend {
* medium restrictions and basic performance constraints.
* Do not call this function from places outside FileBackend and FileOp.
*
- * @return integer Bytes
+ * @return int Bytes
*/
final public function maxFileSizeInternal() {
return $this->maxFileSize;
@@ -129,11 +129,13 @@ abstract class FileBackendStore extends FileBackend {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
}
+
return $status;
}
/**
* @see FileBackendStore::createInternal()
+ * @param array $params
* @return Status
*/
abstract protected function doCreateInternal( array $params );
@@ -168,11 +170,13 @@ abstract class FileBackendStore extends FileBackend {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
}
+
return $status;
}
/**
* @see FileBackendStore::storeInternal()
+ * @param array $params
* @return Status
*/
abstract protected function doStoreInternal( array $params );
@@ -203,11 +207,13 @@ abstract class FileBackendStore extends FileBackend {
if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
+
return $status;
}
/**
* @see FileBackendStore::copyInternal()
+ * @param array $params
* @return Status
*/
abstract protected function doCopyInternal( array $params );
@@ -236,6 +242,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::deleteInternal()
+ * @param array $params
* @return Status
*/
abstract protected function doDeleteInternal( array $params );
@@ -267,11 +274,13 @@ abstract class FileBackendStore extends FileBackend {
if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
+
return $status;
}
/**
* @see FileBackendStore::moveInternal()
+ * @param array $params
* @return Status
*/
protected function doMoveInternal( array $params ) {
@@ -285,6 +294,7 @@ abstract class FileBackendStore extends FileBackend {
$status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) );
$status->setResult( true, $status->value ); // ignore delete() errors
}
+
return $status;
}
@@ -311,11 +321,13 @@ abstract class FileBackendStore extends FileBackend {
} else {
$status = Status::newGood(); // nothing to do
}
+
return $status;
}
/**
* @see FileBackendStore::describeInternal()
+ * @param array $params
* @return Status
*/
protected function doDescribeInternal( array $params ) {
@@ -345,8 +357,8 @@ abstract class FileBackendStore extends FileBackend {
$status->merge( $this->doConcatenate( $params ) );
$sec = microtime( true ) - $start_time;
if ( !$status->isOK() ) {
- wfDebugLog( 'FileOperation', get_class( $this ) . " failed to concatenate " .
- count( $params['srcs'] ) . " file(s) [$sec sec]" );
+ wfDebugLog( 'FileOperation', get_class( $this ) . "-{$this->name}" .
+ " failed to concatenate " . count( $params['srcs'] ) . " file(s) [$sec sec]" );
}
}
@@ -355,6 +367,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::concatenate()
+ * @param array $params
* @return Status
*/
protected function doConcatenate( array $params ) {
@@ -368,6 +381,7 @@ abstract class FileBackendStore extends FileBackend {
wfRestoreWarnings();
if ( !$ok ) { // not present or not empty
$status->fatal( 'backend-fail-opentemp', $tmpPath );
+
return $status;
}
@@ -378,6 +392,7 @@ abstract class FileBackendStore extends FileBackend {
$fsFile = $this->getLocalReference( array( 'src' => $path ) );
if ( !$fsFile ) { // retry failed?
$status->fatal( 'backend-fail-read', $path );
+
return $status;
}
}
@@ -388,6 +403,7 @@ abstract class FileBackendStore extends FileBackend {
$tmpHandle = fopen( $tmpPath, 'ab' );
if ( $tmpHandle === false ) {
$status->fatal( 'backend-fail-opentemp', $tmpPath );
+
return $status;
}
@@ -398,6 +414,7 @@ abstract class FileBackendStore extends FileBackend {
if ( $sourceHandle === false ) {
fclose( $tmpHandle );
$status->fatal( 'backend-fail-read', $virtualSource );
+
return $status;
}
// Append chunk to file (pass chunk size to avoid magic quotes)
@@ -405,12 +422,14 @@ abstract class FileBackendStore extends FileBackend {
fclose( $sourceHandle );
fclose( $tmpHandle );
$status->fatal( 'backend-fail-writetemp', $tmpPath );
+
return $status;
}
fclose( $sourceHandle );
}
if ( !fclose( $tmpHandle ) ) {
$status->fatal( 'backend-fail-closetemp', $tmpPath );
+
return $status;
}
@@ -426,6 +445,7 @@ abstract class FileBackendStore extends FileBackend {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+
return $status; // invalid storage path
}
@@ -444,6 +464,9 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::doPrepare()
+ * @param string $container
+ * @param string $dir
+ * @param array $params
* @return Status
*/
protected function doPrepareInternal( $container, $dir, array $params ) {
@@ -457,6 +480,7 @@ abstract class FileBackendStore extends FileBackend {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+
return $status; // invalid storage path
}
@@ -475,6 +499,9 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::doSecure()
+ * @param string $container
+ * @param string $dir
+ * @param array $params
* @return Status
*/
protected function doSecureInternal( $container, $dir, array $params ) {
@@ -488,6 +515,7 @@ abstract class FileBackendStore extends FileBackend {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+
return $status; // invalid storage path
}
@@ -506,6 +534,9 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::doPublish()
+ * @param string $container
+ * @param string $dir
+ * @param array $params
* @return Status
*/
protected function doPublishInternal( $container, $dir, array $params ) {
@@ -531,6 +562,7 @@ abstract class FileBackendStore extends FileBackend {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+
return $status; // invalid storage path
}
@@ -558,6 +590,9 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::doClean()
+ * @param string $container
+ * @param string $dir
+ * @param array $params
* @return Status
*/
protected function doCleanInternal( $container, $dir, array $params ) {
@@ -567,18 +602,21 @@ abstract class FileBackendStore extends FileBackend {
final public function fileExists( array $params ) {
$section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
+
return ( $stat === null ) ? null : (bool)$stat; // null => failure
}
final public function getFileTimestamp( array $params ) {
$section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
+
return $stat ? $stat['mtime'] : false;
}
final public function getFileSize( array $params ) {
$section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
+
return $stat ? $stat['size'] : false;
}
@@ -606,27 +644,32 @@ abstract class FileBackendStore extends FileBackend {
}
}
}
- wfProfileIn( __METHOD__ . '-miss' );
wfProfileIn( __METHOD__ . '-miss-' . $this->name );
$stat = $this->doGetFileStat( $params );
wfProfileOut( __METHOD__ . '-miss-' . $this->name );
- wfProfileOut( __METHOD__ . '-miss' );
if ( is_array( $stat ) ) { // file exists
- $stat['latest'] = $latest;
+ // Strongly consistent backends can automatically set "latest"
+ $stat['latest'] = isset( $stat['latest'] ) ? $stat['latest'] : $latest;
$this->cheapCache->set( $path, 'stat', $stat );
$this->setFileCache( $path, $stat ); // update persistent cache
if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
$this->cheapCache->set( $path, 'sha1',
array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
}
+ if ( isset( $stat['xattr'] ) ) { // some backends store headers/metadata
+ $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
+ $this->cheapCache->set( $path, 'xattr',
+ array( 'map' => $stat['xattr'], 'latest' => $latest ) );
+ }
} elseif ( $stat === false ) { // file does not exist
$this->cheapCache->set( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
- $this->cheapCache->set( $path, 'sha1', // the SHA-1 must be false too
- array( 'hash' => false, 'latest' => $latest ) );
+ $this->cheapCache->set( $path, 'xattr', array( 'map' => false, 'latest' => $latest ) );
+ $this->cheapCache->set( $path, 'sha1', array( 'hash' => false, 'latest' => $latest ) );
wfDebug( __METHOD__ . ": File $path does not exist.\n" );
} else { // an error occurred
wfDebug( __METHOD__ . ": Could not stat file $path.\n" );
}
+
return $stat;
}
@@ -646,7 +689,8 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::getFileContentsMulti()
- * @return Array
+ * @param array $params
+ * @return array
*/
protected function doGetFileContentsMulti( array $params ) {
$contents = array();
@@ -655,9 +699,44 @@ abstract class FileBackendStore extends FileBackend {
$contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false;
wfRestoreWarnings();
}
+
return $contents;
}
+ final public function getFileXAttributes( array $params ) {
+ $path = self::normalizeStoragePath( $params['src'] );
+ if ( $path === null ) {
+ return false; // invalid storage path
+ }
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $latest = !empty( $params['latest'] ); // use latest data?
+ if ( $this->cheapCache->has( $path, 'xattr', self::CACHE_TTL ) ) {
+ $stat = $this->cheapCache->get( $path, 'xattr' );
+ // If we want the latest data, check that this cached
+ // value was in fact fetched with the latest available data.
+ if ( !$latest || $stat['latest'] ) {
+ return $stat['map'];
+ }
+ }
+ wfProfileIn( __METHOD__ . '-miss' );
+ wfProfileIn( __METHOD__ . '-miss-' . $this->name );
+ $fields = $this->doGetFileXAttributes( $params );
+ $fields = is_array( $fields ) ? self::normalizeXAttributes( $fields ) : false;
+ wfProfileOut( __METHOD__ . '-miss-' . $this->name );
+ wfProfileOut( __METHOD__ . '-miss' );
+ $this->cheapCache->set( $path, 'xattr', array( 'map' => $fields, 'latest' => $latest ) );
+
+ return $fields;
+ }
+
+ /**
+ * @see FileBackendStore::getFileXAttributes()
+ * @return bool|string
+ */
+ protected function doGetFileXAttributes( array $params ) {
+ return array( 'headers' => array(), 'metadata' => array() ); // not supported
+ }
+
final public function getFileSha1Base36( array $params ) {
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
@@ -673,17 +752,17 @@ abstract class FileBackendStore extends FileBackend {
return $stat['hash'];
}
}
- wfProfileIn( __METHOD__ . '-miss' );
wfProfileIn( __METHOD__ . '-miss-' . $this->name );
$hash = $this->doGetFileSha1Base36( $params );
wfProfileOut( __METHOD__ . '-miss-' . $this->name );
- wfProfileOut( __METHOD__ . '-miss' );
$this->cheapCache->set( $path, 'sha1', array( 'hash' => $hash, 'latest' => $latest ) );
+
return $hash;
}
/**
* @see FileBackendStore::getFileSha1Base36()
+ * @param array $params
* @return bool|string
*/
protected function doGetFileSha1Base36( array $params ) {
@@ -699,6 +778,7 @@ abstract class FileBackendStore extends FileBackend {
$section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$fsFile = $this->getLocalReference( $params );
$props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
+
return $props;
}
@@ -738,7 +818,8 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::getLocalReferenceMulti()
- * @return Array
+ * @param array $params
+ * @return array
*/
protected function doGetLocalReferenceMulti( array $params ) {
return $this->doGetLocalCopyMulti( $params );
@@ -755,12 +836,14 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::getLocalCopyMulti()
- * @return Array
+ * @param array $params
+ * @return array
*/
abstract protected function doGetLocalCopyMulti( array $params );
/**
* @see FileBackend::getFileHttpUrl()
+ * @param array $params
* @return string|null
*/
public function getFileHttpUrl( array $params ) {
@@ -782,11 +865,9 @@ abstract class FileBackendStore extends FileBackend {
if ( $res == StreamFile::NOT_MODIFIED ) {
// do nothing; client cache is up to date
} elseif ( $res == StreamFile::READY_STREAM ) {
- wfProfileIn( __METHOD__ . '-send' );
wfProfileIn( __METHOD__ . '-send-' . $this->name );
$status = $this->doStreamFile( $params );
wfProfileOut( __METHOD__ . '-send-' . $this->name );
- wfProfileOut( __METHOD__ . '-send' );
if ( !$status->isOK() ) {
// Per bug 41113, nasty things can happen if bad cache entries get
// stuck in cache. It's also possible that this error can come up
@@ -804,6 +885,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::streamFile()
+ * @param array $params
* @return Status
*/
protected function doStreamFile( array $params ) {
@@ -839,6 +921,7 @@ abstract class FileBackendStore extends FileBackend {
$res = null; // if we don't find anything, it is indeterminate
}
}
+
return $res;
}
}
@@ -865,6 +948,7 @@ abstract class FileBackendStore extends FileBackend {
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
// File listing spans multiple containers/shards
list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
+
return new FileBackendStoreShardDirIterator( $this,
$fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
}
@@ -878,7 +962,7 @@ abstract class FileBackendStore extends FileBackend {
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
* @param array $params
- * @return Traversable|Array|null Returns null on failure
+ * @return Traversable|array|null Returns null on failure
*/
abstract public function getDirectoryListInternal( $container, $dir, array $params );
@@ -894,6 +978,7 @@ abstract class FileBackendStore extends FileBackend {
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
// File listing spans multiple containers/shards
list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
+
return new FileBackendStoreShardFileIterator( $this,
$fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
}
@@ -907,7 +992,7 @@ abstract class FileBackendStore extends FileBackend {
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
* @param array $params
- * @return Traversable|Array|null Returns null on failure
+ * @return Traversable|array|null Returns null on failure
*/
abstract public function getFileListInternal( $container, $dir, array $params );
@@ -919,8 +1004,8 @@ abstract class FileBackendStore extends FileBackend {
* An exception is thrown if an unsupported operation is requested.
*
* @param array $ops Same format as doOperations()
- * @return Array List of FileOp objects
- * @throws MWException
+ * @return array List of FileOp objects
+ * @throws FileBackendError
*/
final public function getOperationsInternal( array $ops ) {
$supportedOps = array(
@@ -944,7 +1029,7 @@ abstract class FileBackendStore extends FileBackend {
// Append the FileOp class
$performOps[] = new $class( $this, $params );
} else {
- throw new MWException( "Operation '$opName' is not supported." );
+ throw new FileBackendError( "Operation '$opName' is not supported." );
}
}
@@ -959,7 +1044,7 @@ abstract class FileBackendStore extends FileBackend {
* normalized.
*
* @param array $performOps List of FileOp objects
- * @return Array (LockManager::LOCK_UW => path list, LockManager::LOCK_EX => path list)
+ * @return array (LockManager::LOCK_UW => path list, LockManager::LOCK_EX => path list)
*/
final public function getPathsToLockForOpsInternal( array $performOps ) {
// Build up a list of files to lock...
@@ -981,6 +1066,7 @@ abstract class FileBackendStore extends FileBackend {
public function getScopedLocksForOps( array $ops, Status $status ) {
$paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
+
return array( $this->getScopedFileLocks( $paths, 'mixed', $status ) );
}
@@ -1010,18 +1096,43 @@ abstract class FileBackendStore extends FileBackend {
$this->clearCache();
}
- // Load from the persistent file and container caches
- $this->primeFileCache( $performOps );
- $this->primeContainerCache( $performOps );
+ // Build the list of paths involved
+ $paths = array();
+ foreach ( $performOps as $op ) {
+ $paths = array_merge( $paths, $op->storagePathsRead() );
+ $paths = array_merge( $paths, $op->storagePathsChanged() );
+ }
- // Actually attempt the operation batch...
- $opts = $this->setConcurrencyFlags( $opts );
- $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
+ // Enlarge the cache to fit the stat entries of these files
+ $this->cheapCache->resize( max( 2 * count( $paths ), self::CACHE_CHEAP_SIZE ) );
+
+ // Load from the persistent container caches
+ $this->primeContainerCache( $paths );
+ // Get the latest stat info for all the files (having locked them)
+ $ok = $this->preloadFileStat( array( 'srcs' => $paths, 'latest' => true ) );
+
+ if ( $ok ) {
+ // Actually attempt the operation batch...
+ $opts = $this->setConcurrencyFlags( $opts );
+ $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
+ } else {
+ // If we could not even stat some files, then bail out...
+ $subStatus = Status::newFatal( 'backend-fail-internal', $this->name );
+ foreach ( $ops as $i => $op ) { // mark each op as failed
+ $subStatus->success[$i] = false;
+ ++$subStatus->failCount;
+ }
+ wfDebugLog( 'FileOperation', get_class( $this ) . "-{$this->name} " .
+ " stat failure; aborted operations: " . FormatJson::encode( $ops ) );
+ }
// Merge errors into status fields
$status->merge( $subStatus );
$status->success = $subStatus->success; // not done in merge()
+ // Shrink the stat cache back to normal size
+ $this->cheapCache->resize( self::CACHE_CHEAP_SIZE );
+
return $status;
}
@@ -1035,7 +1146,8 @@ abstract class FileBackendStore extends FileBackend {
// Clear any file cache entries
$this->clearCache();
- $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
+ $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'describe', 'null' );
+ // Parallel ops may be disabled in config due to dependencies (e.g. needing popen())
$async = ( $this->parallelize === 'implicit' && count( $ops ) > 1 );
$maxConcurrency = $this->concurrency; // throttle
@@ -1045,7 +1157,7 @@ abstract class FileBackendStore extends FileBackend {
// Perform the sync-only ops and build up op handles for the async ops...
foreach ( $ops as $index => $params ) {
if ( !in_array( $params['op'], $supportedOps ) ) {
- throw new MWException( "Operation '{$params['op']}' is not supported." );
+ throw new FileBackendError( "Operation '{$params['op']}' is not supported." );
}
$method = $params['op'] . 'Internal'; // e.g. "storeInternal"
$subStatus = $this->$method( array( 'async' => $async ) + $params );
@@ -1086,36 +1198,40 @@ abstract class FileBackendStore extends FileBackend {
* The resulting Status object fields will correspond
* to the order in which the handles where given.
*
- * @param array $handles List of FileBackendStoreOpHandle objects
- * @return Array Map of Status objects
- * @throws MWException
+ * @param array $fileOpHandles
+ * @throws FileBackendError
+ * @internal param array $handles List of FileBackendStoreOpHandle objects
+ * @return array Map of Status objects
*/
final public function executeOpHandlesInternal( array $fileOpHandles ) {
$section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+
foreach ( $fileOpHandles as $fileOpHandle ) {
if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
- throw new MWException( "Given a non-FileBackendStoreOpHandle object." );
+ throw new FileBackendError( "Given a non-FileBackendStoreOpHandle object." );
} elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) {
- throw new MWException( "Given a FileBackendStoreOpHandle for the wrong backend." );
+ throw new FileBackendError( "Given a FileBackendStoreOpHandle for the wrong backend." );
}
}
$res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
foreach ( $fileOpHandles as $fileOpHandle ) {
$fileOpHandle->closeResources();
}
+
return $res;
}
/**
* @see FileBackendStore::executeOpHandlesInternal()
* @param array $fileOpHandles
- * @throws MWException
- * @return Array List of corresponding Status objects
+ * @throws FileBackendError
+ * @return array List of corresponding Status objects
*/
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
- foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty
- throw new MWException( "This backend supports no asynchronous operations." );
+ if ( count( $fileOpHandles ) ) {
+ throw new FileBackendError( "This backend supports no asynchronous operations." );
}
+
return array();
}
@@ -1126,7 +1242,7 @@ abstract class FileBackendStore extends FileBackend {
* specific errors, especially in the middle of batch file operations.
*
* @param array $op Same format as doOperation()
- * @return Array
+ * @return array
*/
protected function stripInvalidHeadersFromOp( array $op ) {
static $longs = array( 'Content-Disposition' );
@@ -1141,6 +1257,7 @@ abstract class FileBackendStore extends FileBackend {
}
}
}
+
return $op;
}
@@ -1178,9 +1295,71 @@ abstract class FileBackendStore extends FileBackend {
* @see FileBackend::clearCache()
*
* @param array $paths Storage paths (optional)
- * @return void
*/
- protected function doClearCache( array $paths = null ) {}
+ protected function doClearCache( array $paths = null ) {
+ }
+
+ final public function preloadFileStat( array $params ) {
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $success = true; // no network errors
+
+ $params['concurrency'] = ( $this->parallelize !== 'off' ) ? $this->concurrency : 1;
+ $stats = $this->doGetFileStatMulti( $params );
+ if ( $stats === null ) {
+ return true; // not supported
+ }
+
+ $latest = !empty( $params['latest'] ); // use latest data?
+ foreach ( $stats as $path => $stat ) {
+ $path = FileBackend::normalizeStoragePath( $path );
+ if ( $path === null ) {
+ continue; // this shouldn't happen
+ }
+ if ( is_array( $stat ) ) { // file exists
+ // Strongly consistent backends can automatically set "latest"
+ $stat['latest'] = isset( $stat['latest'] ) ? $stat['latest'] : $latest;
+ $this->cheapCache->set( $path, 'stat', $stat );
+ $this->setFileCache( $path, $stat ); // update persistent cache
+ if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
+ $this->cheapCache->set( $path, 'sha1',
+ array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
+ }
+ if ( isset( $stat['xattr'] ) ) { // some backends store headers/metadata
+ $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
+ $this->cheapCache->set( $path, 'xattr',
+ array( 'map' => $stat['xattr'], 'latest' => $latest ) );
+ }
+ } elseif ( $stat === false ) { // file does not exist
+ $this->cheapCache->set( $path, 'stat',
+ $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
+ $this->cheapCache->set( $path, 'xattr',
+ array( 'map' => false, 'latest' => $latest ) );
+ $this->cheapCache->set( $path, 'sha1',
+ array( 'hash' => false, 'latest' => $latest ) );
+ wfDebug( __METHOD__ . ": File $path does not exist.\n" );
+ } else { // an error occurred
+ $success = false;
+ wfDebug( __METHOD__ . ": Could not stat file $path.\n" );
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Get file stat information (concurrently if possible) for several files
+ *
+ * @see FileBackend::getFileStat()
+ *
+ * @param array $params Parameters include:
+ * - srcs : list of source storage paths
+ * - latest : use the latest available data
+ * @return array|null Map of storage paths to array|bool|null (returns null if not supported)
+ * @since 1.23
+ */
+ protected function doGetFileStatMulti( array $params ) {
+ return null; // not supported
+ }
/**
* Is this a key/value store where directories are just virtual?
@@ -1218,7 +1397,7 @@ abstract class FileBackendStore extends FileBackend {
* be scanned by looking in all the container shards.
*
* @param string $storagePath
- * @return Array (container, path, container suffix) or (null, null, null) if invalid
+ * @return array (container, path, container suffix) or (null, null, null) if invalid
*/
final protected function resolveStoragePath( $storagePath ) {
list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
@@ -1242,6 +1421,7 @@ abstract class FileBackendStore extends FileBackend {
}
}
}
+
return array( null, null, null );
}
@@ -1258,13 +1438,14 @@ abstract class FileBackendStore extends FileBackend {
* @see FileBackendStore::resolveStoragePath()
*
* @param string $storagePath
- * @return Array (container, path) or (null, null) if invalid
+ * @return array (container, path) or (null, null) if invalid
*/
final protected function resolveStoragePathReal( $storagePath ) {
list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath );
if ( $cShard !== null && substr( $relPath, -1 ) !== '/' ) {
return array( $container, $relPath );
}
+
return array( null, null );
}
@@ -1299,8 +1480,10 @@ abstract class FileBackendStore extends FileBackend {
if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) {
return '.' . implode( '', array_slice( $m, 1 ) );
}
+
return null; // failed to match
}
+
return ''; // no sharding
}
@@ -1314,6 +1497,7 @@ abstract class FileBackendStore extends FileBackend {
*/
final public function isSingleShardPathInternal( $storagePath ) {
list( , , $shard ) = $this->resolveStoragePath( $storagePath );
+
return ( $shard !== null );
}
@@ -1323,7 +1507,7 @@ abstract class FileBackendStore extends FileBackend {
* the container are required to be hashed accordingly.
*
* @param string $container
- * @return Array (integer levels, integer base, repeat flag) or (0, 0, false)
+ * @return array (integer levels, integer base, repeat flag) or (0, 0, false)
*/
final protected function getContainerHashLevels( $container ) {
if ( isset( $this->shardViaHashLevels[$container] ) ) {
@@ -1336,6 +1520,7 @@ abstract class FileBackendStore extends FileBackend {
}
}
}
+
return array( 0, 0, false ); // no sharding
}
@@ -1343,7 +1528,7 @@ abstract class FileBackendStore extends FileBackend {
* Get a list of full container shard suffixes for a container
*
* @param string $container
- * @return Array
+ * @return array
*/
final protected function getContainerSuffixes( $container ) {
$shards = array();
@@ -1354,6 +1539,7 @@ abstract class FileBackendStore extends FileBackend {
$shards[] = '.' . wfBaseConvert( $index, 10, $base, $digits );
}
}
+
return $shards;
}
@@ -1404,7 +1590,7 @@ abstract class FileBackendStore extends FileBackend {
* @return string
*/
private function containerCacheKey( $container ) {
- return wfMemcKey( 'backend', $this->getName(), 'container', $container );
+ return "filebackend:{$this->name}:{$this->wikiId}:container:{$container}";
}
/**
@@ -1412,7 +1598,6 @@ abstract class FileBackendStore extends FileBackend {
*
* @param string $container Resolved container name
* @param array $val Information to cache
- * @return void
*/
final protected function setContainerCache( $container, array $val ) {
$this->memCache->add( $this->containerCacheKey( $container ), $val, 14 * 86400 );
@@ -1423,7 +1608,6 @@ abstract class FileBackendStore extends FileBackend {
* The cache key is salted for a while to prevent race conditions.
*
* @param string $container Resolved container name
- * @return void
*/
final protected function deleteContainerCache( $container ) {
if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) {
@@ -1433,11 +1617,10 @@ abstract class FileBackendStore extends FileBackend {
/**
* Do a batch lookup from cache for container stats for all containers
- * used in a list of container names, storage paths, or FileOp objects.
+ * used in a list of container names or storage paths objects.
* This loads the persistent cache values into the process cache.
*
- * @param Array $items
- * @return void
+ * @param array $items
*/
final protected function primeContainerCache( array $items ) {
$section = new ProfileSection( __METHOD__ . "-{$this->name}" );
@@ -1446,10 +1629,7 @@ abstract class FileBackendStore extends FileBackend {
$contNames = array(); // (cache key => resolved container name)
// Get all the paths/containers from the items...
foreach ( $items as $item ) {
- if ( $item instanceof FileOp ) {
- $paths = array_merge( $paths, $item->storagePathsRead() );
- $paths = array_merge( $paths, $item->storagePathsChanged() );
- } elseif ( self::isStoragePath( $item ) ) {
+ if ( self::isStoragePath( $item ) ) {
$paths[] = $item;
} elseif ( is_string( $item ) ) { // full container name
$contNames[$this->containerCacheKey( $item )] = $item;
@@ -1480,9 +1660,9 @@ abstract class FileBackendStore extends FileBackend {
* Only containers that actually exist should appear in the map.
*
* @param array $containerInfo Map of resolved container names to cached info
- * @return void
*/
- protected function doPrimeContainerCache( array $containerInfo ) {}
+ protected function doPrimeContainerCache( array $containerInfo ) {
+ }
/**
* Get the cache key for a file path
@@ -1491,7 +1671,7 @@ abstract class FileBackendStore extends FileBackend {
* @return string
*/
private function fileCacheKey( $path ) {
- return wfMemcKey( 'backend', $this->getName(), 'file', sha1( $path ) );
+ return "filebackend:{$this->name}:{$this->wikiId}:file:" . sha1( $path );
}
/**
@@ -1501,7 +1681,6 @@ abstract class FileBackendStore extends FileBackend {
*
* @param string $path Storage path
* @param array $val Stat information to cache
- * @return void
*/
final protected function setFileCache( $path, array $val ) {
$path = FileBackend::normalizeStoragePath( $path );
@@ -1510,7 +1689,22 @@ abstract class FileBackendStore extends FileBackend {
}
$age = time() - wfTimestamp( TS_UNIX, $val['mtime'] );
$ttl = min( 7 * 86400, max( 300, floor( .1 * $age ) ) );
- $this->memCache->add( $this->fileCacheKey( $path ), $val, $ttl );
+ $key = $this->fileCacheKey( $path );
+ // Set the cache unless it is currently salted with the value "PURGED".
+ // Using add() handles this except it also is a no-op in that case where
+ // the current value is not "latest" but $val is, so use CAS in that case.
+ if ( !$this->memCache->add( $key, $val, $ttl ) && !empty( $val['latest'] ) ) {
+ $this->memCache->merge(
+ $key,
+ function ( BagOStuff $cache, $key, $cValue ) use ( $val ) {
+ return ( is_array( $cValue ) && empty( $cValue['latest'] ) )
+ ? $val // update the stat cache with the lastest info
+ : false; // do nothing (cache is salted or some error happened)
+ },
+ $ttl,
+ 1
+ );
+ }
}
/**
@@ -1520,7 +1714,6 @@ abstract class FileBackendStore extends FileBackend {
* a file is created at a path were there was none before.
*
* @param string $path Storage path
- * @return void
*/
final protected function deleteFileCache( $path ) {
$path = FileBackend::normalizeStoragePath( $path );
@@ -1537,8 +1730,7 @@ abstract class FileBackendStore extends FileBackend {
* used in a list of storage paths or FileOp objects.
* This loads the persistent cache values into the process cache.
*
- * @param array $items List of storage paths or FileOps
- * @return void
+ * @param array $items List of storage paths
*/
final protected function primeFileCache( array $items ) {
$section = new ProfileSection( __METHOD__ . "-{$this->name}" );
@@ -1547,10 +1739,7 @@ abstract class FileBackendStore extends FileBackend {
$pathNames = array(); // (cache key => storage path)
// Get all the paths/containers from the items...
foreach ( $items as $item ) {
- if ( $item instanceof FileOp ) {
- $paths = array_merge( $paths, $item->storagePathsRead() );
- $paths = array_merge( $paths, $item->storagePathsChanged() );
- } elseif ( self::isStoragePath( $item ) ) {
+ if ( self::isStoragePath( $item ) ) {
$paths[] = FileBackend::normalizeStoragePath( $item );
}
}
@@ -1573,15 +1762,41 @@ abstract class FileBackendStore extends FileBackend {
$this->cheapCache->set( $path, 'sha1',
array( 'hash' => $val['sha1'], 'latest' => $val['latest'] ) );
}
+ if ( isset( $val['xattr'] ) ) { // some backends store headers/metadata
+ $val['xattr'] = self::normalizeXAttributes( $val['xattr'] );
+ $this->cheapCache->set( $path, 'xattr',
+ array( 'map' => $val['xattr'], 'latest' => $val['latest'] ) );
+ }
}
}
}
/**
+ * Normalize file headers/metadata to the FileBackend::getFileXAttributes() format
+ *
+ * @param array $xattr
+ * @return array
+ * @since 1.22
+ */
+ final protected static function normalizeXAttributes( array $xattr ) {
+ $newXAttr = array( 'headers' => array(), 'metadata' => array() );
+
+ foreach ( $xattr['headers'] as $name => $value ) {
+ $newXAttr['headers'][strtolower( $name )] = $value;
+ }
+
+ foreach ( $xattr['metadata'] as $name => $value ) {
+ $newXAttr['metadata'][strtolower( $name )] = $value;
+ }
+
+ return $newXAttr;
+ }
+
+ /**
* Set the 'concurrency' option from a list of operation options
*
* @param array $opts Map of operation options
- * @return Array
+ * @return array
*/
final protected function setConcurrencyFlags( array $opts ) {
$opts['concurrency'] = 1; // off
@@ -1594,6 +1809,7 @@ abstract class FileBackendStore extends FileBackend {
$opts['concurrency'] = $this->concurrency;
}
}
+
return $opts;
}
@@ -1603,7 +1819,7 @@ abstract class FileBackendStore extends FileBackend {
* @param string $storagePath
* @param string|null $content File data
* @param string|null $fsPath File system path
- * @return MIME type
+ * @return string MIME type
*/
protected function getContentType( $storagePath, $content, $fsPath ) {
return call_user_func_array( $this->mimeCallback, func_get_args() );
@@ -1619,19 +1835,17 @@ abstract class FileBackendStore extends FileBackend {
* passed to FileBackendStore::executeOpHandlesInternal().
*/
abstract class FileBackendStoreOpHandle {
- /** @var Array */
+ /** @var array */
public $params = array(); // params to caller functions
/** @var FileBackendStore */
public $backend;
- /** @var Array */
+ /** @var array */
public $resourcesToClose = array();
public $call; // string; name that identifies the function called
/**
* Close all open file handles
- *
- * @return void
*/
public function closeResources() {
array_map( 'fclose', $this->resourcesToClose );
@@ -1647,13 +1861,17 @@ abstract class FileBackendStoreOpHandle {
abstract class FileBackendStoreShardListIterator extends FilterIterator {
/** @var FileBackendStore */
protected $backend;
- /** @var Array */
+
+ /** @var array */
protected $params;
- protected $container; // string; full container name
- protected $directory; // string; resolved relative path
+ /** @var string Full container name */
+ protected $container;
- /** @var Array */
+ /** @var string Resolved relative path */
+ protected $directory;
+
+ /** @var array */
protected $multiShardPaths = array(); // (rel path => 1)
/**
@@ -1689,6 +1907,7 @@ abstract class FileBackendStoreShardListIterator extends FilterIterator {
return false;
} else {
$this->multiShardPaths[$rel] = 1;
+
return true;
}
}
diff --git a/includes/filebackend/FileOp.php b/includes/filebackend/FileOp.php
index fe833084..66d87943 100644
--- a/includes/filebackend/FileOp.php
+++ b/includes/filebackend/FileOp.php
@@ -34,20 +34,35 @@
* @since 1.19
*/
abstract class FileOp {
- /** @var Array */
+ /** @var array */
protected $params = array();
+
/** @var FileBackendStore */
protected $backend;
- protected $state = self::STATE_NEW; // integer
- protected $failed = false; // boolean
- protected $async = false; // boolean
- protected $batchId; // string
+ /** @var int */
+ protected $state = self::STATE_NEW;
+
+ /** @var bool */
+ protected $failed = false;
+
+ /** @var bool */
+ protected $async = false;
+
+ /** @var string */
+ protected $batchId;
- protected $doOperation = true; // boolean; operation is not a no-op
- protected $sourceSha1; // string
- protected $overwriteSameCase; // boolean
- protected $destExists; // boolean
+ /** @var bool Operation is not a no-op */
+ protected $doOperation = true;
+
+ /** @var string */
+ protected $sourceSha1;
+
+ /** @var bool */
+ protected $overwriteSameCase;
+
+ /** @var bool */
+ protected $destExists;
/* Object life-cycle */
const STATE_NEW = 1;
@@ -58,47 +73,29 @@ abstract class FileOp {
* Build a new batch file operation transaction
*
* @param FileBackendStore $backend
- * @param Array $params
- * @throws MWException
+ * @param array $params
+ * @throws FileBackendError
*/
final public function __construct( FileBackendStore $backend, array $params ) {
$this->backend = $backend;
- list( $required, $optional ) = $this->allowedParams();
- // @todo normalizeAnyStoragePaths() calls are overzealous, use a parameter list
+ list( $required, $optional, $paths ) = $this->allowedParams();
foreach ( $required as $name ) {
if ( isset( $params[$name] ) ) {
- // Normalize paths so the paths to the same file have the same string
- $this->params[$name] = self::normalizeAnyStoragePaths( $params[$name] );
+ $this->params[$name] = $params[$name];
} else {
- throw new MWException( "File operation missing parameter '$name'." );
+ throw new FileBackendError( "File operation missing parameter '$name'." );
}
}
foreach ( $optional as $name ) {
if ( isset( $params[$name] ) ) {
- // Normalize paths so the paths to the same file have the same string
- $this->params[$name] = self::normalizeAnyStoragePaths( $params[$name] );
+ $this->params[$name] = $params[$name];
}
}
- $this->params = $params;
- }
-
- /**
- * Normalize $item or anything in $item that is a valid storage path
- *
- * @param string $item|array
- * @return string|Array
- */
- protected function normalizeAnyStoragePaths( $item ) {
- if ( is_array( $item ) ) {
- $res = array();
- foreach ( $item as $k => $v ) {
- $k = self::normalizeIfValidStoragePath( $k );
- $v = self::normalizeIfValidStoragePath( $v );
- $res[$k] = $v;
+ foreach ( $paths as $name ) {
+ if ( isset( $this->params[$name] ) ) {
+ // Normalize paths so the paths to the same file have the same string
+ $this->params[$name] = self::normalizeIfValidStoragePath( $this->params[$name] );
}
- return $res;
- } else {
- return self::normalizeIfValidStoragePath( $item );
}
}
@@ -111,8 +108,10 @@ abstract class FileOp {
protected static function normalizeIfValidStoragePath( $path ) {
if ( FileBackend::isStoragePath( $path ) ) {
$res = FileBackend::normalizeStoragePath( $path );
+
return ( $res !== null ) ? $res : $path;
}
+
return $path;
}
@@ -120,7 +119,6 @@ abstract class FileOp {
* Set the batch UUID this operation belongs to
*
* @param string $batchId
- * @return void
*/
final public function setBatchId( $batchId ) {
$this->batchId = $batchId;
@@ -148,7 +146,7 @@ abstract class FileOp {
/**
* Get a new empty predicates array for precheck()
*
- * @return Array
+ * @return array
*/
final public static function newPredicates() {
return array( 'exists' => array(), 'sha1' => array() );
@@ -157,7 +155,7 @@ abstract class FileOp {
/**
* Get a new empty dependency tracking array for paths read/written to
*
- * @return Array
+ * @return array
*/
final public static function newDependencies() {
return array( 'read' => array(), 'write' => array() );
@@ -167,19 +165,20 @@ abstract class FileOp {
* Update a dependency tracking array to account for this operation
*
* @param array $deps Prior path reads/writes; format of FileOp::newPredicates()
- * @return Array
+ * @return array
*/
final public function applyDependencies( array $deps ) {
$deps['read'] += array_fill_keys( $this->storagePathsRead(), 1 );
$deps['write'] += array_fill_keys( $this->storagePathsChanged(), 1 );
+
return $deps;
}
/**
* Check if this operation changes files listed in $paths
*
- * @param array $paths Prior path reads/writes; format of FileOp::newPredicates()
- * @return boolean
+ * @param array $deps Prior path reads/writes; format of FileOp::newPredicates()
+ * @return bool
*/
final public function dependsOn( array $deps ) {
foreach ( $this->storagePathsChanged() as $path ) {
@@ -192,6 +191,7 @@ abstract class FileOp {
return true; // "flow" dependency
}
}
+
return false;
}
@@ -200,7 +200,7 @@ abstract class FileOp {
*
* @param array $oPredicates Pre-op info about files (format of FileOp::newPredicates)
* @param array $nPredicates Post-op info about files (format of FileOp::newPredicates)
- * @return Array
+ * @return array
*/
final public function getJournalEntries( array $oPredicates, array $nPredicates ) {
if ( !$this->doOperation ) {
@@ -232,6 +232,7 @@ abstract class FileOp {
);
}
}
+
return array_merge( $nullEntries, $updateEntries, $deleteEntries );
}
@@ -240,7 +241,7 @@ abstract class FileOp {
* This must update $predicates for each path that the op can change
* except when a failing status object is returned.
*
- * @param Array $predicates
+ * @param array $predicates
* @return Status
*/
final public function precheck( array &$predicates ) {
@@ -252,10 +253,12 @@ abstract class FileOp {
if ( !$status->isOK() ) {
$this->failed = true;
}
+
return $status;
}
/**
+ * @param array $predicates
* @return Status
*/
protected function doPrecheck( array &$predicates ) {
@@ -283,6 +286,7 @@ abstract class FileOp {
} else { // no-op
$status = Status::newGood();
}
+
return $status;
}
@@ -302,23 +306,24 @@ abstract class FileOp {
$this->async = true;
$result = $this->attempt();
$this->async = false;
+
return $result;
}
/**
* Get the file operation parameters
*
- * @return Array (required params list, optional params list)
+ * @return array (required params list, optional params list, list of params that are paths)
*/
protected function allowedParams() {
- return array( array(), array() );
+ return array( array(), array(), array() );
}
/**
* Adjust params to FileBackendStore internal file calls
*
- * @param Array $params
- * @return Array (required params list, optional params list)
+ * @param array $params
+ * @return array (required params list, optional params list)
*/
protected function setFlags( array $params ) {
return array( 'async' => $this->async ) + $params;
@@ -327,7 +332,7 @@ abstract class FileOp {
/**
* Get a list of storage paths read from for this operation
*
- * @return Array
+ * @return array
*/
public function storagePathsRead() {
return array();
@@ -336,7 +341,7 @@ abstract class FileOp {
/**
* Get a list of storage paths written to for this operation
*
- * @return Array
+ * @return array
*/
public function storagePathsChanged() {
return array();
@@ -347,7 +352,7 @@ abstract class FileOp {
* Also set the destExists, overwriteSameCase and sourceSha1 member variables.
* A bad status will be returned if there is no chance it can be overwritten.
*
- * @param Array $predicates
+ * @param array $predicates
* @return Status
*/
protected function precheckDestExistence( array $predicates ) {
@@ -373,12 +378,15 @@ abstract class FileOp {
} else {
$this->overwriteSameCase = true; // OK
}
+
return $status; // do nothing; either OK or bad status
} else {
$status->fatal( 'backend-fail-alreadyexists', $this->params['dst'] );
+
return $status;
}
}
+
return $status;
}
@@ -396,7 +404,7 @@ abstract class FileOp {
* Check if a file will exist in storage when this operation is attempted
*
* @param string $source Storage path
- * @param Array $predicates
+ * @param array $predicates
* @return bool
*/
final protected function fileExists( $source, array $predicates ) {
@@ -404,6 +412,7 @@ abstract class FileOp {
return $predicates['exists'][$source]; // previous op assures this
} else {
$params = array( 'src' => $source, 'latest' => true );
+
return $this->backend->fileExists( $params );
}
}
@@ -412,7 +421,7 @@ abstract class FileOp {
* Get the SHA-1 of a file in storage when this operation is attempted
*
* @param string $source Storage path
- * @param Array $predicates
+ * @param array $predicates
* @return string|bool False on failure
*/
final protected function fileSha1( $source, array $predicates ) {
@@ -422,6 +431,7 @@ abstract class FileOp {
return false; // previous op assures this
} else {
$params = array( 'src' => $source, 'latest' => true );
+
return $this->backend->getFileSha1Base36( $params );
}
}
@@ -439,7 +449,6 @@ abstract class FileOp {
* Log a file operation failure and preserve any temp files
*
* @param string $action
- * @return void
*/
final public function logFailure( $action ) {
$params = $this->params;
@@ -459,8 +468,11 @@ abstract class FileOp {
*/
class CreateFileOp extends FileOp {
protected function allowedParams() {
- return array( array( 'content', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'headers' ) );
+ return array(
+ array( 'content', 'dst' ),
+ array( 'overwrite', 'overwriteSame', 'headers' ),
+ array( 'dst' )
+ );
}
protected function doPrecheck( array &$predicates ) {
@@ -470,11 +482,13 @@ class CreateFileOp extends FileOp {
$status->fatal( 'backend-fail-maxsize',
$this->params['dst'], $this->backend->maxFileSizeInternal() );
$status->fatal( 'backend-fail-create', $this->params['dst'] );
+
return $status;
// Check if a file can be placed/changed at the destination
} elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['dst'] );
$status->fatal( 'backend-fail-create', $this->params['dst'] );
+
return $status;
}
// Check if destination file exists
@@ -485,6 +499,7 @@ class CreateFileOp extends FileOp {
$predicates['exists'][$this->params['dst']] = true;
$predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
}
+
return $status; // safe to call attempt()
}
@@ -493,6 +508,7 @@ class CreateFileOp extends FileOp {
// Create the file at the destination
return $this->backend->createInternal( $this->setFlags( $this->params ) );
}
+
return Status::newGood();
}
@@ -511,8 +527,11 @@ class CreateFileOp extends FileOp {
*/
class StoreFileOp extends FileOp {
protected function allowedParams() {
- return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'headers' ) );
+ return array(
+ array( 'src', 'dst' ),
+ array( 'overwrite', 'overwriteSame', 'headers' ),
+ array( 'src', 'dst' )
+ );
}
protected function doPrecheck( array &$predicates ) {
@@ -520,17 +539,20 @@ class StoreFileOp extends FileOp {
// Check if the source file exists on the file system
if ( !is_file( $this->params['src'] ) ) {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
+
return $status;
// Check if the source file is too big
} elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
$status->fatal( 'backend-fail-maxsize',
$this->params['dst'], $this->backend->maxFileSizeInternal() );
$status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+
return $status;
// Check if a file can be placed/changed at the destination
} elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['dst'] );
$status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+
return $status;
}
// Check if destination file exists
@@ -541,6 +563,7 @@ class StoreFileOp extends FileOp {
$predicates['exists'][$this->params['dst']] = true;
$predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
}
+
return $status; // safe to call attempt()
}
@@ -549,6 +572,7 @@ class StoreFileOp extends FileOp {
// Store the file at the destination
return $this->backend->storeInternal( $this->setFlags( $this->params ) );
}
+
return Status::newGood();
}
@@ -559,6 +583,7 @@ class StoreFileOp extends FileOp {
if ( $hash !== false ) {
$hash = wfBaseConvert( $hash, 16, 36, 31 );
}
+
return $hash;
}
@@ -573,8 +598,11 @@ class StoreFileOp extends FileOp {
*/
class CopyFileOp extends FileOp {
protected function allowedParams() {
- return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ) );
+ return array(
+ array( 'src', 'dst' ),
+ array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ),
+ array( 'src', 'dst' )
+ );
}
protected function doPrecheck( array &$predicates ) {
@@ -586,15 +614,18 @@ class CopyFileOp extends FileOp {
// Update file existence predicates (cache 404s)
$predicates['exists'][$this->params['src']] = false;
$predicates['sha1'][$this->params['src']] = false;
+
return $status; // nothing to do
} else {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
+
return $status;
}
- // Check if a file can be placed/changed at the destination
+ // Check if a file can be placed/changed at the destination
} elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['dst'] );
$status->fatal( 'backend-fail-copy', $this->params['src'], $this->params['dst'] );
+
return $status;
}
// Check if destination file exists
@@ -605,6 +636,7 @@ class CopyFileOp extends FileOp {
$predicates['exists'][$this->params['dst']] = true;
$predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
}
+
return $status; // safe to call attempt()
}
@@ -621,6 +653,7 @@ class CopyFileOp extends FileOp {
// Copy the file to the destination
$status = $this->backend->copyInternal( $this->setFlags( $this->params ) );
}
+
return $status;
}
@@ -639,8 +672,11 @@ class CopyFileOp extends FileOp {
*/
class MoveFileOp extends FileOp {
protected function allowedParams() {
- return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ) );
+ return array(
+ array( 'src', 'dst' ),
+ array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ),
+ array( 'src', 'dst' )
+ );
}
protected function doPrecheck( array &$predicates ) {
@@ -652,15 +688,18 @@ class MoveFileOp extends FileOp {
// Update file existence predicates (cache 404s)
$predicates['exists'][$this->params['src']] = false;
$predicates['sha1'][$this->params['src']] = false;
+
return $status; // nothing to do
} else {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
+
return $status;
}
// Check if a file can be placed/changed at the destination
} elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['dst'] );
$status->fatal( 'backend-fail-move', $this->params['src'], $this->params['dst'] );
+
return $status;
}
// Check if destination file exists
@@ -673,6 +712,7 @@ class MoveFileOp extends FileOp {
$predicates['exists'][$this->params['dst']] = true;
$predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
}
+
return $status; // safe to call attempt()
}
@@ -697,6 +737,7 @@ class MoveFileOp extends FileOp {
// Move the file to the destination
$status = $this->backend->moveInternal( $this->setFlags( $this->params ) );
}
+
return $status;
}
@@ -715,7 +756,7 @@ class MoveFileOp extends FileOp {
*/
class DeleteFileOp extends FileOp {
protected function allowedParams() {
- return array( array( 'src' ), array( 'ignoreMissingSource' ) );
+ return array( array( 'src' ), array( 'ignoreMissingSource' ), array( 'src' ) );
}
protected function doPrecheck( array &$predicates ) {
@@ -727,20 +768,24 @@ class DeleteFileOp extends FileOp {
// Update file existence predicates (cache 404s)
$predicates['exists'][$this->params['src']] = false;
$predicates['sha1'][$this->params['src']] = false;
+
return $status; // nothing to do
} else {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
+
return $status;
}
// Check if a file can be placed/changed at the source
} elseif ( !$this->backend->isPathUsableInternal( $this->params['src'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['src'] );
$status->fatal( 'backend-fail-delete', $this->params['src'] );
+
return $status;
}
// Update file existence predicates
$predicates['exists'][$this->params['src']] = false;
$predicates['sha1'][$this->params['src']] = false;
+
return $status; // safe to call attempt()
}
@@ -760,7 +805,7 @@ class DeleteFileOp extends FileOp {
*/
class DescribeFileOp extends FileOp {
protected function allowedParams() {
- return array( array( 'src' ), array( 'headers' ) );
+ return array( array( 'src' ), array( 'headers' ), array( 'src' ) );
}
protected function doPrecheck( array &$predicates ) {
@@ -768,11 +813,13 @@ class DescribeFileOp extends FileOp {
// Check if the source file exists
if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
+
return $status;
// Check if a file can be placed/changed at the source
} elseif ( !$this->backend->isPathUsableInternal( $this->params['src'] ) ) {
$status->fatal( 'backend-fail-usable', $this->params['src'] );
$status->fatal( 'backend-fail-describe', $this->params['src'] );
+
return $status;
}
// Update file existence predicates
@@ -780,6 +827,7 @@ class DescribeFileOp extends FileOp {
$this->fileExists( $this->params['src'], $predicates );
$predicates['sha1'][$this->params['src']] =
$this->fileSha1( $this->params['src'], $predicates );
+
return $status; // safe to call attempt()
}
@@ -796,4 +844,5 @@ class DescribeFileOp extends FileOp {
/**
* Placeholder operation that has no params and does nothing
*/
-class NullFileOp extends FileOp {}
+class NullFileOp extends FileOp {
+}
diff --git a/includes/filebackend/FileOpBatch.php b/includes/filebackend/FileOpBatch.php
index 785c0bc9..b0d83e01 100644
--- a/includes/filebackend/FileOpBatch.php
+++ b/includes/filebackend/FileOpBatch.php
@@ -55,13 +55,13 @@ class FileOpBatch {
* @return Status
*/
public static function attempt( array $performOps, array $opts, FileJournal $journal ) {
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
$status = Status::newGood();
$n = count( $performOps );
if ( $n > self::MAX_BATCH_SIZE ) {
$status->fatal( 'backend-fail-batchsize', $n, self::MAX_BATCH_SIZE );
- wfProfileOut( __METHOD__ );
+
return $status;
}
@@ -107,7 +107,6 @@ class FileOpBatch {
$status->success[$index] = false;
++$status->failCount;
if ( !$ignoreErrors ) {
- wfProfileOut( __METHOD__ );
return $status; // abort
}
}
@@ -121,7 +120,6 @@ class FileOpBatch {
if ( count( $entries ) ) {
$subStatus = $journal->logChangeBatch( $entries, $batchId );
if ( !$subStatus->isOK() ) {
- wfProfileOut( __METHOD__ );
return $subStatus; // abort
}
}
@@ -133,7 +131,6 @@ class FileOpBatch {
// Attempt each operation (in parallel if allowed and possible)...
self::runParallelBatches( $pPerformOps, $status );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -145,9 +142,8 @@ class FileOpBatch {
* within any given sub-batch do not depend on each other.
* This will abort remaining ops on failure.
*
- * @param Array $pPerformOps
+ * @param array $pPerformOps Batches of file ops (batches use original indexes)
* @param Status $status
- * @return bool Success
*/
protected static function runParallelBatches( array $pPerformOps, Status $status ) {
$aborted = false; // set to true on unexpected errors
@@ -156,6 +152,8 @@ class FileOpBatch {
// We can't continue (even with $ignoreErrors) as $predicates is wrong.
// Log the remaining ops as failed for recovery...
foreach ( $performOpsBatch as $i => $fileOp ) {
+ $status->success[$i] = false;
+ ++$status->failCount;
$performOpsBatch[$i]->logFailure( 'attempt_aborted' );
}
continue;
@@ -168,9 +166,9 @@ class FileOpBatch {
// If attemptAsync() returns a Status, it was either due to an error
// or the backend does not support async ops and did it synchronously.
foreach ( $performOpsBatch as $i => $fileOp ) {
- if ( !$fileOp->failed() ) { // failed => already has Status
- // If the batch is just one operation, it's faster to avoid
- // pipelining as that can involve creating new TCP connections.
+ if ( !isset( $status->success[$i] ) ) { // didn't already fail in precheck()
+ // Parallel ops may be disabled in config due to missing dependencies,
+ // (e.g. needing popen()). When they are, $performOpsBatch has size 1.
$subStatus = ( count( $performOpsBatch ) > 1 )
? $fileOp->attemptAsync()
: $fileOp->attempt();
@@ -185,7 +183,7 @@ class FileOpBatch {
$statuses = $statuses + $backend->executeOpHandlesInternal( $opHandles );
// Marshall and merge all the responses (blocking)...
foreach ( $performOpsBatch as $i => $fileOp ) {
- if ( !$fileOp->failed() ) { // failed => already has Status
+ if ( !isset( $status->success[$i] ) ) { // didn't already fail in precheck()
$subStatus = $statuses[$i];
$status->merge( $subStatus );
if ( $subStatus->isOK() ) {
@@ -199,6 +197,5 @@ class FileOpBatch {
}
}
}
- return $status;
}
}
diff --git a/includes/filebackend/MemoryFileBackend.php b/includes/filebackend/MemoryFileBackend.php
new file mode 100644
index 00000000..7c2f8256
--- /dev/null
+++ b/includes/filebackend/MemoryFileBackend.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Simulation of a backend storage in memory.
+ *
+ * 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 Aaron Schulz
+ */
+
+/**
+ * Simulation of a backend storage in memory.
+ *
+ * All data in the backend is automatically deleted at the end of PHP execution.
+ * Since the data stored here is volatile, this is only useful for staging or testing.
+ *
+ * @ingroup FileBackend
+ * @since 1.23
+ */
+class MemoryFileBackend extends FileBackendStore {
+ /** @var array Map of (file path => (data,mtime) */
+ protected $files = array();
+
+ public function isPathUsableInternal( $storagePath ) {
+ return true;
+ }
+
+ protected function doCreateInternal( array $params ) {
+ $status = Status::newGood();
+
+ $dst = $this->resolveHashKey( $params['dst'] );
+ if ( $dst === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+
+ return $status;
+ }
+
+ $this->files[$dst] = array(
+ 'data' => $params['content'],
+ 'mtime' => wfTimestamp( TS_MW, time() )
+ );
+
+ return $status;
+ }
+
+ protected function doStoreInternal( array $params ) {
+ $status = Status::newGood();
+
+ $dst = $this->resolveHashKey( $params['dst'] );
+ if ( $dst === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+
+ return $status;
+ }
+
+ wfSuppressWarnings();
+ $data = file_get_contents( $params['src'] );
+ wfRestoreWarnings();
+ if ( $data === false ) { // source doesn't exist?
+ $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
+
+ return $status;
+ }
+
+ $this->files[$dst] = array(
+ 'data' => $data,
+ 'mtime' => wfTimestamp( TS_MW, time() )
+ );
+
+ return $status;
+ }
+
+ protected function doCopyInternal( array $params ) {
+ $status = Status::newGood();
+
+ $src = $this->resolveHashKey( $params['src'] );
+ if ( $src === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
+ return $status;
+ }
+
+ $dst = $this->resolveHashKey( $params['dst'] );
+ if ( $dst === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+
+ return $status;
+ }
+
+ if ( !isset( $this->files[$src] ) ) {
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ }
+
+ return $status;
+ }
+
+ $this->files[$dst] = array(
+ 'data' => $this->files[$src]['data'],
+ 'mtime' => wfTimestamp( TS_MW, time() )
+ );
+
+ return $status;
+ }
+
+ protected function doDeleteInternal( array $params ) {
+ $status = Status::newGood();
+
+ $src = $this->resolveHashKey( $params['src'] );
+ if ( $src === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
+ return $status;
+ }
+
+ if ( !isset( $this->files[$src] ) ) {
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-delete', $params['src'] );
+ }
+
+ return $status;
+ }
+
+ unset( $this->files[$src] );
+
+ return $status;
+ }
+
+ protected function doGetFileStat( array $params ) {
+ $src = $this->resolveHashKey( $params['src'] );
+ if ( $src === null ) {
+ return null;
+ }
+
+ if ( isset( $this->files[$src] ) ) {
+ return array(
+ 'mtime' => $this->files[$src]['mtime'],
+ 'size' => strlen( $this->files[$src]['data'] ),
+ );
+ }
+
+ return false;
+ }
+
+ protected function doGetLocalCopyMulti( array $params ) {
+ $tmpFiles = array(); // (path => TempFSFile)
+ foreach ( $params['srcs'] as $srcPath ) {
+ $src = $this->resolveHashKey( $srcPath );
+ if ( $src === null || !isset( $this->files[$src] ) ) {
+ $fsFile = null;
+ } else {
+ // Create a new temporary file with the same extension...
+ $ext = FileBackend::extensionFromPath( $src );
+ $fsFile = TempFSFile::factory( 'localcopy_', $ext );
+ if ( $fsFile ) {
+ $bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] );
+ if ( $bytes !== strlen( $this->files[$src]['data'] ) ) {
+ $fsFile = null;
+ }
+ }
+ }
+ $tmpFiles[$srcPath] = $fsFile;
+ }
+
+ return $tmpFiles;
+ }
+
+ protected function doStreamFile( array $params ) {
+ $status = Status::newGood();
+
+ $src = $this->resolveHashKey( $params['src'] );
+ if ( $src === null || !isset( $this->files[$src] ) ) {
+ $status->fatal( 'backend-fail-stream', $params['src'] );
+
+ return $status;
+ }
+
+ print $this->files[$src]['data'];
+
+ return $status;
+ }
+
+ protected function doDirectoryExists( $container, $dir, array $params ) {
+ $prefix = rtrim( "$container/$dir", '/' ) . '/';
+ foreach ( $this->files as $path => $data ) {
+ if ( strpos( $path, $prefix ) === 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function getDirectoryListInternal( $container, $dir, array $params ) {
+ $dirs = array();
+ $prefix = rtrim( "$container/$dir", '/' ) . '/';
+ $prefixLen = strlen( $prefix );
+ foreach ( $this->files as $path => $data ) {
+ if ( strpos( $path, $prefix ) === 0 ) {
+ $relPath = substr( $path, $prefixLen );
+ if ( $relPath === false ) {
+ continue;
+ } elseif ( strpos( $relPath, '/' ) === false ) {
+ continue; // just a file
+ }
+ $parts = array_slice( explode( '/', $relPath ), 0, -1 ); // last part is file name
+ if ( !empty( $params['topOnly'] ) ) {
+ $dirs[$parts[0]] = 1; // top directory
+ } else {
+ $current = '';
+ foreach ( $parts as $part ) { // all directories
+ $dir = ( $current === '' ) ? $part : "$current/$part";
+ $dirs[$dir] = 1;
+ $current = $dir;
+ }
+ }
+ }
+ }
+
+ return array_keys( $dirs );
+ }
+
+ public function getFileListInternal( $container, $dir, array $params ) {
+ $files = array();
+ $prefix = rtrim( "$container/$dir", '/' ) . '/';
+ $prefixLen = strlen( $prefix );
+ foreach ( $this->files as $path => $data ) {
+ if ( strpos( $path, $prefix ) === 0 ) {
+ $relPath = substr( $path, $prefixLen );
+ if ( $relPath === false ) {
+ continue;
+ } elseif ( !empty( $params['topOnly'] ) && strpos( $relPath, '/' ) !== false ) {
+ continue;
+ }
+ $files[] = $relPath;
+ }
+ }
+
+ return $files;
+ }
+
+ protected function directoriesAreVirtual() {
+ return true;
+ }
+
+ /**
+ * Get the absolute file system path for a storage path
+ *
+ * @param string $storagePath Storage path
+ * @return string|null
+ */
+ protected function resolveHashKey( $storagePath ) {
+ list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
+ if ( $relPath === null ) {
+ return null; // invalid
+ }
+
+ return ( $relPath !== '' ) ? "$fullCont/$relPath" : $fullCont;
+ }
+}
diff --git a/includes/filebackend/README b/includes/filebackend/README
index 569f3376..c06f6fc7 100644
--- a/includes/filebackend/README
+++ b/includes/filebackend/README
@@ -51,7 +51,7 @@ On files:
* read a file into a string or several files into a map of path names to strings
* download a file or set of files to a temporary file (on a mounted file system)
* get the SHA1 hash of a file
-* get various properties of a file (stat information, content time, mime information, ...)
+* get various properties of a file (stat information, content time, MIME information, ...)
On directories:
* get a list of files directly under a directory
diff --git a/includes/filebackend/SwiftFileBackend.php b/includes/filebackend/SwiftFileBackend.php
index db090a98..f40ec46e 100644
--- a/includes/filebackend/SwiftFileBackend.php
+++ b/includes/filebackend/SwiftFileBackend.php
@@ -26,10 +26,6 @@
/**
* @brief Class for an OpenStack Swift (or Ceph RGW) based file backend.
*
- * This requires the SwiftCloudFiles MediaWiki extension, which includes
- * the php-cloudfiles library (https://github.com/rackspace/php-cloudfiles).
- * php-cloudfiles requires the curl, fileinfo, and mb_string PHP extensions.
- *
* Status messages should avoid mentioning the Swift account name.
* Likewise, error suppression should be used to avoid path disclosure.
*
@@ -37,32 +33,47 @@
* @since 1.19
*/
class SwiftFileBackend extends FileBackendStore {
- /** @var CF_Authentication */
- protected $auth; // Swift authentication handler
- protected $authTTL; // integer seconds
- protected $swiftTempUrlKey; // string; shared secret value for making temp urls
- protected $swiftAnonUser; // string; username to handle unauthenticated requests
- protected $swiftUseCDN; // boolean; whether CloudFiles CDN is enabled
- protected $swiftCDNExpiry; // integer; how long to cache things in the CDN
- protected $swiftCDNPurgable; // boolean; whether object CDN purging is enabled
-
- // Rados Gateway specific options
- protected $rgwS3AccessKey; // string; S3 access key
- protected $rgwS3SecretKey; // string; S3 authentication key
-
- /** @var CF_Connection */
- protected $conn; // Swift connection handle
- protected $sessionStarted = 0; // integer UNIX timestamp
-
- /** @var CloudFilesException */
- protected $connException;
- protected $connErrorTime = 0; // UNIX timestamp
+ /** @var MultiHttpClient */
+ protected $http;
+
+ /** @var int TTL in seconds */
+ protected $authTTL;
+
+ /** @var string Authentication base URL (without version) */
+ protected $swiftAuthUrl;
+
+ /** @var string Swift user (account:user) to authenticate as */
+ protected $swiftUser;
+
+ /** @var string Secret key for user */
+ protected $swiftKey;
+
+ /** @var string Shared secret value for making temp URLs */
+ protected $swiftTempUrlKey;
+
+ /** @var string S3 access key (RADOS Gateway) */
+ protected $rgwS3AccessKey;
+
+ /** @var string S3 authentication key (RADOS Gateway) */
+ protected $rgwS3SecretKey;
/** @var BagOStuff */
protected $srvCache;
- /** @var ProcessCacheLRU */
- protected $connContainerCache; // container object cache
+ /** @var ProcessCacheLRU Container stat cache */
+ protected $containerStatCache;
+
+ /** @var array */
+ protected $authCreds;
+
+ /** @var int UNIX timestamp */
+ protected $authSessionTimestamp = 0;
+
+ /** @var int UNIX timestamp */
+ protected $authErrorTimestamp = null;
+
+ /** @var bool Whether the server is an Ceph RGW */
+ protected $isRGW = false;
/**
* @see FileBackendStore::__construct()
@@ -73,16 +84,6 @@ class SwiftFileBackend extends FileBackendStore {
* - swiftAuthTTL : Swift authentication TTL (seconds)
* - swiftTempUrlKey : Swift "X-Account-Meta-Temp-URL-Key" value on the account.
* Do not set this until it has been set in the backend.
- * - swiftAnonUser : Swift user used for end-user requests (account:username).
- * If set, then views of public containers are assumed to go
- * through this user. If not set, then public containers are
- * accessible to unauthenticated requests via ".r:*" in the ACL.
- * - swiftUseCDN : Whether a Cloud Files Content Delivery Network is set up
- * - swiftCDNExpiry : How long (in seconds) to store content in the CDN.
- * If files may likely change, this should probably not exceed
- * a few days. For example, deletions may take this long to apply.
- * If object purging is enabled, however, this is not an issue.
- * - swiftCDNPurgable : Whether object purge requests are allowed by the CDN.
* - shardViaHashLevels : Map of container names to sharding config with:
* - base : base of hash characters, 16 or 36
* - levels : the number of hash levels (and digits)
@@ -91,12 +92,12 @@ class SwiftFileBackend extends FileBackendStore {
* - cacheAuthInfo : Whether to cache authentication tokens in APC, XCache, ect.
* If those are not available, then the main cache will be used.
* This is probably insecure in shared hosting environments.
- * - rgwS3AccessKey : Ragos Gateway S3 "access key" value on the account.
+ * - rgwS3AccessKey : Rados Gateway S3 "access key" value on the account.
* Do not set this until it has been set in the backend.
* This is used for generating expiring pre-authenticated URLs.
* Only use this when using rgw and to work around
* http://tracker.newdream.net/issues/3454.
- * - rgwS3SecretKey : Ragos Gateway S3 "secret key" value on the account.
+ * - rgwS3SecretKey : Rados Gateway S3 "secret key" value on the account.
* Do not set this until it has been set in the backend.
* This is used for generating expiring pre-authenticated URLs.
* Only use this when using rgw and to work around
@@ -104,48 +105,32 @@ class SwiftFileBackend extends FileBackendStore {
*/
public function __construct( array $config ) {
parent::__construct( $config );
- if ( !class_exists( 'CF_Constants' ) ) {
- throw new MWException( 'SwiftCloudFiles extension not installed.' );
- }
// Required settings
- $this->auth = new CF_Authentication(
- $config['swiftUser'],
- $config['swiftKey'],
- null, // account; unused
- $config['swiftAuthUrl']
- );
+ $this->swiftAuthUrl = $config['swiftAuthUrl'];
+ $this->swiftUser = $config['swiftUser'];
+ $this->swiftKey = $config['swiftKey'];
// Optional settings
$this->authTTL = isset( $config['swiftAuthTTL'] )
? $config['swiftAuthTTL']
: 5 * 60; // some sane number
- $this->swiftAnonUser = isset( $config['swiftAnonUser'] )
- ? $config['swiftAnonUser']
- : '';
$this->swiftTempUrlKey = isset( $config['swiftTempUrlKey'] )
? $config['swiftTempUrlKey']
: '';
$this->shardViaHashLevels = isset( $config['shardViaHashLevels'] )
? $config['shardViaHashLevels']
: '';
- $this->swiftUseCDN = isset( $config['swiftUseCDN'] )
- ? $config['swiftUseCDN']
- : false;
- $this->swiftCDNExpiry = isset( $config['swiftCDNExpiry'] )
- ? $config['swiftCDNExpiry']
- : 12 * 3600; // 12 hours is safe (tokens last 24 hours per http://docs.openstack.org)
- $this->swiftCDNPurgable = isset( $config['swiftCDNPurgable'] )
- ? $config['swiftCDNPurgable']
- : true;
$this->rgwS3AccessKey = isset( $config['rgwS3AccessKey'] )
? $config['rgwS3AccessKey']
: '';
$this->rgwS3SecretKey = isset( $config['rgwS3SecretKey'] )
? $config['rgwS3SecretKey']
: '';
+ // HTTP helper client
+ $this->http = new MultiHttpClient( array() );
// Cache container information to mask latency
$this->memCache = wfGetMainCache();
// Process cache for container info
- $this->connContainerCache = new ProcessCacheLRU( 300 );
+ $this->containerStatCache = new ProcessCacheLRU( 300 );
// Cache auth token information to avoid RTTs
if ( !empty( $config['cacheAuthInfo'] ) ) {
if ( PHP_SAPI === 'cli' ) {
@@ -153,22 +138,25 @@ class SwiftFileBackend extends FileBackendStore {
} else {
try { // look for APC, XCache, WinCache, ect...
$this->srvCache = ObjectCache::newAccelerator( array() );
- } catch ( Exception $e ) {}
+ } catch ( Exception $e ) {
+ }
}
}
- $this->srvCache = $this->srvCache ? $this->srvCache : new EmptyBagOStuff();
+ $this->srvCache = $this->srvCache ?: new EmptyBagOStuff();
+ }
+
+ public function getFeatures() {
+ return ( FileBackend::ATTR_UNICODE_PATHS |
+ FileBackend::ATTR_HEADERS | FileBackend::ATTR_METADATA );
}
- /**
- * @see FileBackendStore::resolveContainerPath()
- * @return null
- */
protected function resolveContainerPath( $container, $relStoragePath ) {
if ( !mb_check_encoding( $relStoragePath, 'UTF-8' ) ) { // mb_string required by CF
return null; // not UTF-8, makes it hard to use CF and the swift HTTP API
} elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
return null; // too long for Swift
}
+
return $relStoragePath;
}
@@ -178,45 +166,48 @@ class SwiftFileBackend extends FileBackendStore {
return false; // invalid
}
- try {
- $this->getContainer( $container );
- return true; // container exists
- } catch ( NoSuchContainerException $e ) {
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, null, __METHOD__, array( 'path' => $storagePath ) );
- }
-
- return false;
+ return is_array( $this->getContainerStat( $container ) );
}
/**
- * @param array $headers
- * @return array
+ * Sanitize and filter the custom headers from a $params array.
+ * We only allow certain Content- and X-Content- headers.
+ *
+ * @param array $params
+ * @return array Sanitized value of 'headers' field in $params
*/
- protected function sanitizeHdrs( array $headers ) {
- // By default, Swift has annoyingly low maximum header value limits
- if ( isset( $headers['Content-Disposition'] ) ) {
- $headers['Content-Disposition'] = $this->truncDisp( $headers['Content-Disposition'] );
+ protected function sanitizeHdrs( array $params ) {
+ $headers = array();
+
+ // Normalize casing, and strip out illegal headers
+ if ( isset( $params['headers'] ) ) {
+ foreach ( $params['headers'] as $name => $value ) {
+ $name = strtolower( $name );
+ if ( preg_match( '/^content-(type|length)$/', $name ) ) {
+ continue; // blacklisted
+ } elseif ( preg_match( '/^(x-)?content-/', $name ) ) {
+ $headers[$name] = $value; // allowed
+ } elseif ( preg_match( '/^content-(disposition)/', $name ) ) {
+ $headers[$name] = $value; // allowed
+ }
+ }
}
- return $headers;
- }
-
- /**
- * @param string $disposition Content-Disposition header value
- * @return string Truncated Content-Disposition header value to meet Swift limits
- */
- protected function truncDisp( $disposition ) {
- $res = '';
- foreach ( explode( ';', $disposition ) as $part ) {
- $part = trim( $part );
- $new = ( $res === '' ) ? $part : "{$res};{$part}";
- if ( strlen( $new ) <= 255 ) {
- $res = $new;
- } else {
- break; // too long; sigh
+ // By default, Swift has annoyingly low maximum header value limits
+ if ( isset( $headers['content-disposition'] ) ) {
+ $disposition = '';
+ foreach ( explode( ';', $headers['content-disposition'] ) as $part ) {
+ $part = trim( $part );
+ $new = ( $disposition === '' ) ? $part : "{$disposition};{$part}";
+ if ( strlen( $new ) <= 255 ) {
+ $disposition = $new;
+ } else {
+ break; // too long; sigh
+ }
}
+ $headers['content-disposition'] = $disposition;
}
- return $res;
+
+ return $headers;
}
protected function doCreateInternal( array $params ) {
@@ -225,152 +216,109 @@ class SwiftFileBackend extends FileBackendStore {
list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
if ( $dstRel === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dst'] );
- return $status;
- }
- // (a) Check the destination container and object
- try {
- $dContObj = $this->getContainer( $dstCont );
- } catch ( NoSuchContainerException $e ) {
- $status->fatal( 'backend-fail-create', $params['dst'] );
- return $status;
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
return $status;
}
- // (b) Get a SHA-1 hash of the object
$sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 );
-
- // (c) Actually create the object
- try {
- // Create a fresh CF_Object with no fields preloaded.
- // We don't want to preserve headers, metadata, and such.
- $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
- $obj->setMetadataValues( array( 'Sha1base36' => $sha1Hash ) );
- // Manually set the ETag (https://github.com/rackspace/php-cloudfiles/issues/59).
- // The MD5 here will be checked within Swift against its own MD5.
- $obj->set_etag( md5( $params['content'] ) );
- // Use the same content type as StreamFile for security
- $obj->content_type = $this->getContentType( $params['dst'], $params['content'], null );
- // Set any other custom headers if requested
- if ( isset( $params['headers'] ) ) {
- $obj->headers += $this->sanitizeHdrs( $params['headers'] );
- }
- if ( !empty( $params['async'] ) ) { // deferred
- $op = $obj->write_async( $params['content'] );
- $status->value = new SwiftFileOpHandle( $this, $params, 'Create', $op );
- $status->value->affectedObjects[] = $obj;
- } else { // actually write the object in Swift
- $obj->write( $params['content'] );
- $this->purgeCDNCache( array( $obj ) );
+ $contentType = $this->getContentType( $params['dst'], $params['content'], null );
+
+ $reqs = array( array(
+ 'method' => 'PUT',
+ 'url' => array( $dstCont, $dstRel ),
+ 'headers' => array(
+ 'content-length' => strlen( $params['content'] ),
+ 'etag' => md5( $params['content'] ),
+ 'content-type' => $contentType,
+ 'x-object-meta-sha1base36' => $sha1Hash
+ ) + $this->sanitizeHdrs( $params ),
+ 'body' => $params['content']
+ ) );
+
+ $be = $this;
+ $method = __METHOD__;
+ $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
+ if ( $rcode === 201 ) {
+ // good
+ } elseif ( $rcode === 412 ) {
+ $status->fatal( 'backend-fail-contenttype', $params['dst'] );
+ } else {
+ $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( BadContentTypeException $e ) {
- $status->fatal( 'backend-fail-contenttype', $params['dst'] );
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ };
+
+ $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
+ if ( !empty( $params['async'] ) ) { // deferred
+ $status->value = $opHandle;
+ } else { // actually write the object in Swift
+ $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
}
return $status;
}
- /**
- * @see SwiftFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseCreate( CF_Async_Op $cfOp, Status $status, array $params ) {
- try {
- $cfOp->getLastResponse();
- } catch ( BadContentTypeException $e ) {
- $status->fatal( 'backend-fail-contenttype', $params['dst'] );
- }
- }
-
protected function doStoreInternal( array $params ) {
$status = Status::newGood();
list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
if ( $dstRel === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dst'] );
- return $status;
- }
- // (a) Check the destination container and object
- try {
- $dContObj = $this->getContainer( $dstCont );
- } catch ( NoSuchContainerException $e ) {
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
- return $status;
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
return $status;
}
- // (b) Get a SHA-1 hash of the object
wfSuppressWarnings();
$sha1Hash = sha1_file( $params['src'] );
wfRestoreWarnings();
if ( $sha1Hash === false ) { // source doesn't exist?
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
+
return $status;
}
$sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 );
+ $contentType = $this->getContentType( $params['dst'], null, $params['src'] );
- // (c) Actually store the object
- try {
- // Create a fresh CF_Object with no fields preloaded.
- // We don't want to preserve headers, metadata, and such.
- $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
- $obj->setMetadataValues( array( 'Sha1base36' => $sha1Hash ) );
- // The MD5 here will be checked within Swift against its own MD5.
- $obj->set_etag( md5_file( $params['src'] ) );
- // Use the same content type as StreamFile for security
- $obj->content_type = $this->getContentType( $params['dst'], null, $params['src'] );
- // Set any other custom headers if requested
- if ( isset( $params['headers'] ) ) {
- $obj->headers += $this->sanitizeHdrs( $params['headers'] );
- }
- if ( !empty( $params['async'] ) ) { // deferred
- wfSuppressWarnings();
- $fp = fopen( $params['src'], 'rb' );
- wfRestoreWarnings();
- if ( !$fp ) {
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
- } else {
- $op = $obj->write_async( $fp, filesize( $params['src'] ), true );
- $status->value = new SwiftFileOpHandle( $this, $params, 'Store', $op );
- $status->value->resourcesToClose[] = $fp;
- $status->value->affectedObjects[] = $obj;
- }
- } else { // actually write the object in Swift
- $obj->load_from_filename( $params['src'], true ); // calls $obj->write()
- $this->purgeCDNCache( array( $obj ) );
- }
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( BadContentTypeException $e ) {
- $status->fatal( 'backend-fail-contenttype', $params['dst'] );
- } catch ( IOException $e ) {
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ $handle = fopen( $params['src'], 'rb' );
+ if ( $handle === false ) { // source doesn't exist?
+ $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
+
+ return $status;
}
- return $status;
- }
+ $reqs = array( array(
+ 'method' => 'PUT',
+ 'url' => array( $dstCont, $dstRel ),
+ 'headers' => array(
+ 'content-length' => filesize( $params['src'] ),
+ 'etag' => md5_file( $params['src'] ),
+ 'content-type' => $contentType,
+ 'x-object-meta-sha1base36' => $sha1Hash
+ ) + $this->sanitizeHdrs( $params ),
+ 'body' => $handle // resource
+ ) );
+
+ $be = $this;
+ $method = __METHOD__;
+ $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
+ if ( $rcode === 201 ) {
+ // good
+ } elseif ( $rcode === 412 ) {
+ $status->fatal( 'backend-fail-contenttype', $params['dst'] );
+ } else {
+ $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
+ }
+ };
- /**
- * @see SwiftFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseStore( CF_Async_Op $cfOp, Status $status, array $params ) {
- try {
- $cfOp->getLastResponse();
- } catch ( BadContentTypeException $e ) {
- $status->fatal( 'backend-fail-contenttype', $params['dst'] );
- } catch ( IOException $e ) {
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
+ if ( !empty( $params['async'] ) ) { // deferred
+ $status->value = $opHandle;
+ } else { // actually write the object in Swift
+ $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
}
+
+ return $status;
}
protected function doCopyInternal( array $params ) {
@@ -379,221 +327,202 @@ class SwiftFileBackend extends FileBackendStore {
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
if ( $srcRel === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
return $status;
}
list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
if ( $dstRel === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dst'] );
- return $status;
- }
- // (a) Check the source/destination containers and destination object
- try {
- $sContObj = $this->getContainer( $srcCont );
- $dContObj = $this->getContainer( $dstCont );
- } catch ( NoSuchContainerException $e ) {
- if ( empty( $params['ignoreMissingSource'] ) || isset( $sContObj ) ) {
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
- }
- return $status;
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
return $status;
}
- // (b) Actually copy the file to the destination
- try {
- $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
- $hdrs = array(); // source file headers to override with new values
- // Set any other custom headers if requested
- if ( isset( $params['headers'] ) ) {
- $hdrs += $this->sanitizeHdrs( $params['headers'] );
- }
- if ( !empty( $params['async'] ) ) { // deferred
- $op = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs );
- $status->value = new SwiftFileOpHandle( $this, $params, 'Copy', $op );
- $status->value->affectedObjects[] = $dstObj;
- } else { // actually write the object in Swift
- $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel, null, $hdrs );
- $this->purgeCDNCache( array( $dstObj ) );
- }
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( NoSuchObjectException $e ) { // source object does not exist
- if ( empty( $params['ignoreMissingSource'] ) ) {
+ $reqs = array( array(
+ 'method' => 'PUT',
+ 'url' => array( $dstCont, $dstRel ),
+ 'headers' => array(
+ 'x-copy-from' => '/' . rawurlencode( $srcCont ) .
+ '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) )
+ ) + $this->sanitizeHdrs( $params ), // extra headers merged into object
+ ) );
+
+ $be = $this;
+ $method = __METHOD__;
+ $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
+ if ( $rcode === 201 ) {
+ // good
+ } elseif ( $rcode === 404 ) {
$status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ } else {
+ $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ };
+
+ $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
+ if ( !empty( $params['async'] ) ) { // deferred
+ $status->value = $opHandle;
+ } else { // actually write the object in Swift
+ $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
}
return $status;
}
- /**
- * @see SwiftFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseCopy( CF_Async_Op $cfOp, Status $status, array $params ) {
- try {
- $cfOp->getLastResponse();
- } catch ( NoSuchObjectException $e ) { // source object does not exist
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
- }
- }
-
protected function doMoveInternal( array $params ) {
$status = Status::newGood();
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
if ( $srcRel === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
return $status;
}
list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
if ( $dstRel === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+
return $status;
}
- // (a) Check the source/destination containers and destination object
- try {
- $sContObj = $this->getContainer( $srcCont );
- $dContObj = $this->getContainer( $dstCont );
- } catch ( NoSuchContainerException $e ) {
- if ( empty( $params['ignoreMissingSource'] ) || isset( $sContObj ) ) {
- $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
- }
- return $status;
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
- return $status;
+ $reqs = array(
+ array(
+ 'method' => 'PUT',
+ 'url' => array( $dstCont, $dstRel ),
+ 'headers' => array(
+ 'x-copy-from' => '/' . rawurlencode( $srcCont ) .
+ '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) )
+ ) + $this->sanitizeHdrs( $params ) // extra headers merged into object
+ )
+ );
+ if ( "{$srcCont}/{$srcRel}" !== "{$dstCont}/{$dstRel}" ) {
+ $reqs[] = array(
+ 'method' => 'DELETE',
+ 'url' => array( $srcCont, $srcRel ),
+ 'headers' => array()
+ );
}
- // (b) Actually move the file to the destination
- try {
- $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
- $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
- $hdrs = array(); // source file headers to override with new values
- // Set any other custom headers if requested
- if ( isset( $params['headers'] ) ) {
- $hdrs += $this->sanitizeHdrs( $params['headers'] );
- }
- if ( !empty( $params['async'] ) ) { // deferred
- $op = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs );
- $status->value = new SwiftFileOpHandle( $this, $params, 'Move', $op );
- $status->value->affectedObjects[] = $srcObj;
- $status->value->affectedObjects[] = $dstObj;
- } else { // actually write the object in Swift
- $sContObj->move_object_to( $srcRel, $dContObj, $dstRel, null, $hdrs );
- $this->purgeCDNCache( array( $srcObj ) );
- $this->purgeCDNCache( array( $dstObj ) );
- }
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( NoSuchObjectException $e ) { // source object does not exist
- if ( empty( $params['ignoreMissingSource'] ) ) {
+ $be = $this;
+ $method = __METHOD__;
+ $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
+ if ( $request['method'] === 'PUT' && $rcode === 201 ) {
+ // good
+ } elseif ( $request['method'] === 'DELETE' && $rcode === 204 ) {
+ // good
+ } elseif ( $rcode === 404 ) {
$status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ } else {
+ $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ };
+
+ $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
+ if ( !empty( $params['async'] ) ) { // deferred
+ $status->value = $opHandle;
+ } else { // actually move the object in Swift
+ $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
}
return $status;
}
- /**
- * @see SwiftFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseMove( CF_Async_Op $cfOp, Status $status, array $params ) {
- try {
- $cfOp->getLastResponse();
- } catch ( NoSuchObjectException $e ) { // source object does not exist
- $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
- }
- }
-
protected function doDeleteInternal( array $params ) {
$status = Status::newGood();
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
if ( $srcRel === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
return $status;
}
- try {
- $sContObj = $this->getContainer( $srcCont );
- $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
- if ( !empty( $params['async'] ) ) { // deferred
- $op = $sContObj->delete_object_async( $srcRel );
- $status->value = new SwiftFileOpHandle( $this, $params, 'Delete', $op );
- $status->value->affectedObjects[] = $srcObj;
- } else { // actually write the object in Swift
- $sContObj->delete_object( $srcRel );
- $this->purgeCDNCache( array( $srcObj ) );
- }
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( NoSuchContainerException $e ) {
- if ( empty( $params['ignoreMissingSource'] ) ) {
- $status->fatal( 'backend-fail-delete', $params['src'] );
- }
- } catch ( NoSuchObjectException $e ) {
- if ( empty( $params['ignoreMissingSource'] ) ) {
- $status->fatal( 'backend-fail-delete', $params['src'] );
+ $reqs = array( array(
+ 'method' => 'DELETE',
+ 'url' => array( $srcCont, $srcRel ),
+ 'headers' => array()
+ ) );
+
+ $be = $this;
+ $method = __METHOD__;
+ $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
+ if ( $rcode === 204 ) {
+ // good
+ } elseif ( $rcode === 404 ) {
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-delete', $params['src'] );
+ }
+ } else {
+ $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ };
+
+ $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
+ if ( !empty( $params['async'] ) ) { // deferred
+ $status->value = $opHandle;
+ } else { // actually delete the object in Swift
+ $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
}
return $status;
}
- /**
- * @see SwiftFileBackend::doExecuteOpHandlesInternal()
- */
- protected function _getResponseDelete( CF_Async_Op $cfOp, Status $status, array $params ) {
- try {
- $cfOp->getLastResponse();
- } catch ( NoSuchContainerException $e ) {
- $status->fatal( 'backend-fail-delete', $params['src'] );
- } catch ( NoSuchObjectException $e ) {
- if ( empty( $params['ignoreMissingSource'] ) ) {
- $status->fatal( 'backend-fail-delete', $params['src'] );
- }
- }
- }
-
protected function doDescribeInternal( array $params ) {
$status = Status::newGood();
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
if ( $srcRel === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['src'] );
+
return $status;
}
- try {
- $sContObj = $this->getContainer( $srcCont );
- // Get the latest version of the current metadata
- $srcObj = $sContObj->get_object( $srcRel,
- $this->headersFromParams( array( 'latest' => true ) ) );
- // Merge in the metadata updates...
- if ( isset( $params['headers'] ) ) {
- $srcObj->headers = $this->sanitizeHdrs( $params['headers'] ) + $srcObj->headers;
- }
- $srcObj->sync_metadata(); // save to Swift
- $this->purgeCDNCache( array( $srcObj ) );
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( NoSuchContainerException $e ) {
- $status->fatal( 'backend-fail-describe', $params['src'] );
- } catch ( NoSuchObjectException $e ) {
+ // Fetch the old object headers/metadata...this should be in stat cache by now
+ $stat = $this->getFileStat( array( 'src' => $params['src'], 'latest' => 1 ) );
+ if ( $stat && !isset( $stat['xattr'] ) ) { // older cache entry
+ $stat = $this->doGetFileStat( array( 'src' => $params['src'], 'latest' => 1 ) );
+ }
+ if ( !$stat ) {
$status->fatal( 'backend-fail-describe', $params['src'] );
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+
+ return $status;
+ }
+
+ // POST clears prior headers, so we need to merge the changes in to the old ones
+ $metaHdrs = array();
+ foreach ( $stat['xattr']['metadata'] as $name => $value ) {
+ $metaHdrs["x-object-meta-$name"] = $value;
+ }
+ $customHdrs = $this->sanitizeHdrs( $params ) + $stat['xattr']['headers'];
+
+ $reqs = array( array(
+ 'method' => 'POST',
+ 'url' => array( $srcCont, $srcRel ),
+ 'headers' => $metaHdrs + $customHdrs
+ ) );
+
+ $be = $this;
+ $method = __METHOD__;
+ $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
+ if ( $rcode === 202 ) {
+ // good
+ } elseif ( $rcode === 404 ) {
+ $status->fatal( 'backend-fail-describe', $params['src'] );
+ } else {
+ $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
+ }
+ };
+
+ $opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
+ if ( !empty( $params['async'] ) ) { // deferred
+ $status->value = $opHandle;
+ } else { // actually change the object in Swift
+ $status->merge( current( $this->doExecuteOpHandlesInternal( array( $opHandle ) ) ) );
}
return $status;
@@ -603,110 +532,62 @@ class SwiftFileBackend extends FileBackendStore {
$status = Status::newGood();
// (a) Check if container already exists
- try {
- $this->getContainer( $fullCont );
- // NoSuchContainerException not thrown: container must exist
- return $status; // already exists
- } catch ( NoSuchContainerException $e ) {
- // NoSuchContainerException thrown: container does not exist
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ $stat = $this->getContainerStat( $fullCont );
+ if ( is_array( $stat ) ) {
+ return $status; // already there
+ } elseif ( $stat === null ) {
+ $status->fatal( 'backend-fail-internal', $this->name );
+
return $status;
}
- // (b) Create container as needed
- try {
- $contObj = $this->createContainer( $fullCont );
- if ( !empty( $params['noAccess'] ) ) {
- // Make container private to end-users...
- $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
- } else {
- // Make container public to end-users...
- $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) );
- }
- if ( $this->swiftUseCDN ) { // Rackspace style CDN
- $contObj->make_public( $this->swiftCDNExpiry );
- }
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
- return $status;
+ // (b) Create container as needed with proper ACLs
+ if ( $stat === false ) {
+ $params['op'] = 'prepare';
+ $status->merge( $this->createContainer( $fullCont, $params ) );
}
return $status;
}
- /**
- * @see FileBackendStore::doSecureInternal()
- * @return Status
- */
protected function doSecureInternal( $fullCont, $dir, array $params ) {
$status = Status::newGood();
if ( empty( $params['noAccess'] ) ) {
return $status; // nothing to do
}
- // Restrict container from end-users...
- try {
- // doPrepareInternal() should have been called,
- // so the Swift container should already exist...
- $contObj = $this->getContainer( $fullCont ); // normally a cache hit
- // NoSuchContainerException not thrown: container must exist
-
+ $stat = $this->getContainerStat( $fullCont );
+ if ( is_array( $stat ) ) {
// Make container private to end-users...
$status->merge( $this->setContainerAccess(
- $contObj,
- array( $this->auth->username ), // read
- array( $this->auth->username ) // write
+ $fullCont,
+ array( $this->swiftUser ), // read
+ array( $this->swiftUser ) // write
) );
- if ( $this->swiftUseCDN && $contObj->is_public() ) { // Rackspace style CDN
- $contObj->make_private();
- }
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ } elseif ( $stat === false ) {
+ $status->fatal( 'backend-fail-usable', $params['dir'] );
+ } else {
+ $status->fatal( 'backend-fail-internal', $this->name );
}
return $status;
}
- /**
- * @see FileBackendStore::doPublishInternal()
- * @return Status
- */
protected function doPublishInternal( $fullCont, $dir, array $params ) {
$status = Status::newGood();
- // Unrestrict container from end-users...
- try {
- // doPrepareInternal() should have been called,
- // so the Swift container should already exist...
- $contObj = $this->getContainer( $fullCont ); // normally a cache hit
- // NoSuchContainerException not thrown: container must exist
-
+ $stat = $this->getContainerStat( $fullCont );
+ if ( is_array( $stat ) ) {
// Make container public to end-users...
- if ( $this->swiftAnonUser != '' ) {
- $status->merge( $this->setContainerAccess(
- $contObj,
- array( $this->auth->username, $this->swiftAnonUser ), // read
- array( $this->auth->username, $this->swiftAnonUser ) // write
- ) );
- } else {
- $status->merge( $this->setContainerAccess(
- $contObj,
- array( $this->auth->username, '.r:*' ), // read
- array( $this->auth->username ) // write
- ) );
- }
- if ( $this->swiftUseCDN && !$contObj->is_public() ) { // Rackspace style CDN
- $contObj->make_public();
- }
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ $status->merge( $this->setContainerAccess(
+ $fullCont,
+ array( $this->swiftUser, '.r:*' ), // read
+ array( $this->swiftUser ) // write
+ ) );
+ } elseif ( $stat === false ) {
+ $status->fatal( 'backend-fail-usable', $params['dir'] );
+ } else {
+ $status->fatal( 'backend-fail-internal', $this->name );
}
return $status;
@@ -721,73 +602,74 @@ class SwiftFileBackend extends FileBackendStore {
}
// (a) Check the container
- try {
- $contObj = $this->getContainer( $fullCont, true );
- } catch ( NoSuchContainerException $e ) {
+ $stat = $this->getContainerStat( $fullCont, true );
+ if ( $stat === false ) {
return $status; // ok, nothing to do
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ } elseif ( !is_array( $stat ) ) {
+ $status->fatal( 'backend-fail-internal', $this->name );
+
return $status;
}
// (b) Delete the container if empty
- if ( $contObj->object_count == 0 ) {
- try {
- $this->deleteContainer( $fullCont );
- } catch ( NoSuchContainerException $e ) {
- return $status; // race?
- } catch ( NonEmptyContainerException $e ) {
- return $status; // race? consistency delay?
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
- return $status;
- }
+ if ( $stat['count'] == 0 ) {
+ $params['op'] = 'clean';
+ $status->merge( $this->deleteContainer( $fullCont, $params ) );
}
return $status;
}
protected function doGetFileStat( array $params ) {
- list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
- if ( $srcRel === null ) {
- return false; // invalid storage path
- }
+ $params = array( 'srcs' => array( $params['src'] ), 'concurrency' => 1 ) + $params;
+ unset( $params['src'] );
+ $stats = $this->doGetFileStatMulti( $params );
+
+ return reset( $stats );
+ }
- $stat = false;
+ /**
+ * Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT"/"2013-05-11T07:37:27.678360Z".
+ * Dates might also come in like "2013-05-11T07:37:27.678360" from Swift listings,
+ * missing the timezone suffix (though Ceph RGW does not appear to have this bug).
+ *
+ * @param string $ts
+ * @param int $format Output format (TS_* constant)
+ * @return string
+ * @throws FileBackendError
+ */
+ protected function convertSwiftDate( $ts, $format = TS_MW ) {
try {
- $contObj = $this->getContainer( $srcCont );
- $srcObj = $contObj->get_object( $srcRel, $this->headersFromParams( $params ) );
- $this->addMissingMetadata( $srcObj, $params['src'] );
- $stat = array(
- // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW
- 'mtime' => wfTimestamp( TS_MW, $srcObj->last_modified ),
- 'size' => (int)$srcObj->content_length,
- 'sha1' => $srcObj->getMetadataValue( 'Sha1base36' )
- );
- } catch ( NoSuchContainerException $e ) {
- } catch ( NoSuchObjectException $e ) {
- } catch ( CloudFilesException $e ) { // some other exception?
- $stat = null;
- $this->handleException( $e, null, __METHOD__, $params );
- }
+ $timestamp = new MWTimestamp( $ts );
- return $stat;
+ return $timestamp->getTimestamp( $format );
+ } catch ( MWException $e ) {
+ throw new FileBackendError( $e->getMessage() );
+ }
}
/**
* Fill in any missing object metadata and save it to Swift
*
- * @param CF_Object $obj
+ * @param array $objHdrs Object response headers
* @param string $path Storage path to object
- * @return bool Success
- * @throws Exception cloudfiles exceptions
+ * @return array New headers
*/
- protected function addMissingMetadata( CF_Object $obj, $path ) {
- if ( $obj->getMetadataValue( 'Sha1base36' ) !== null ) {
- return true; // nothing to do
+ protected function addMissingMetadata( array $objHdrs, $path ) {
+ if ( isset( $objHdrs['x-object-meta-sha1base36'] ) ) {
+ return $objHdrs; // nothing to do
}
- wfProfileIn( __METHOD__ );
+
+ $section = new ProfileSection( __METHOD__ . '-' . $this->name );
trigger_error( "$path was not stored with SHA-1 metadata.", E_USER_WARNING );
+
+ $auth = $this->getAuthentication();
+ if ( !$auth ) {
+ $objHdrs['x-object-meta-sha1base36'] = false;
+
+ return $objHdrs; // failed
+ }
+
$status = Status::newGood();
$scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status );
if ( $status->isOK() ) {
@@ -795,101 +677,79 @@ class SwiftFileBackend extends FileBackendStore {
if ( $tmpFile ) {
$hash = $tmpFile->getSha1Base36();
if ( $hash !== false ) {
- $obj->setMetadataValues( array( 'Sha1base36' => $hash ) );
- $obj->sync_metadata(); // save to Swift
- wfProfileOut( __METHOD__ );
- return true; // success
+ $objHdrs['x-object-meta-sha1base36'] = $hash;
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
+ 'method' => 'POST',
+ 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
+ 'headers' => $this->authTokenHeaders( $auth ) + $objHdrs
+ ) );
+ if ( $rcode >= 200 && $rcode <= 299 ) {
+ return $objHdrs; // success
+ }
}
}
}
trigger_error( "Unable to set SHA-1 metadata for $path", E_USER_WARNING );
- $obj->setMetadataValues( array( 'Sha1base36' => false ) );
- wfProfileOut( __METHOD__ );
- return false; // failed
+ $objHdrs['x-object-meta-sha1base36'] = false;
+
+ return $objHdrs; // failed
}
protected function doGetFileContentsMulti( array $params ) {
$contents = array();
+ $auth = $this->getAuthentication();
+
$ep = array_diff_key( $params, array( 'srcs' => 1 ) ); // for error logging
// Blindly create tmp files and stream to them, catching any exception if the file does
// not exist. Doing stats here is useless and will loop infinitely in addMissingMetadata().
- foreach ( array_chunk( $params['srcs'], $params['concurrency'] ) as $pathBatch ) {
- $cfOps = array(); // (path => CF_Async_Op)
-
- foreach ( $pathBatch as $path ) { // each path in this concurrent batch
- list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
- if ( $srcRel === null ) {
- $contents[$path] = false;
- continue;
- }
- $data = false;
- try {
- $sContObj = $this->getContainer( $srcCont );
- $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
- // Create a new temporary memory file...
- $handle = fopen( 'php://temp', 'wb' );
- if ( $handle ) {
- $headers = $this->headersFromParams( $params );
- if ( count( $pathBatch ) > 1 ) {
- $cfOps[$path] = $obj->stream_async( $handle, $headers );
- $cfOps[$path]->_file_handle = $handle; // close this later
- } else {
- $obj->stream( $handle, $headers );
- rewind( $handle ); // start from the beginning
- $data = stream_get_contents( $handle );
- fclose( $handle );
- }
- } else {
- $data = false;
- }
- } catch ( NoSuchContainerException $e ) {
- $data = false;
- } catch ( NoSuchObjectException $e ) {
- $data = false;
- } catch ( CloudFilesException $e ) { // some other exception?
- $data = false;
- $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep );
- }
- $contents[$path] = $data;
- }
+ $reqs = array(); // (path => op)
- $batch = new CF_Async_Op_Batch( $cfOps );
- $cfOps = $batch->execute();
- foreach ( $cfOps as $path => $cfOp ) {
- try {
- $cfOp->getLastResponse();
- rewind( $cfOp->_file_handle ); // start from the beginning
- $contents[$path] = stream_get_contents( $cfOp->_file_handle );
- } catch ( NoSuchContainerException $e ) {
- $contents[$path] = false;
- } catch ( NoSuchObjectException $e ) {
- $contents[$path] = false;
- } catch ( CloudFilesException $e ) { // some other exception?
- $contents[$path] = false;
- $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep );
- }
- fclose( $cfOp->_file_handle ); // close open handle
+ foreach ( $params['srcs'] as $path ) { // each path in this concurrent batch
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
+ if ( $srcRel === null || !$auth ) {
+ $contents[$path] = false;
+ continue;
}
+ // Create a new temporary memory file...
+ $handle = fopen( 'php://temp', 'wb' );
+ if ( $handle ) {
+ $reqs[$path] = array(
+ 'method' => 'GET',
+ 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
+ 'headers' => $this->authTokenHeaders( $auth )
+ + $this->headersFromParams( $params ),
+ 'stream' => $handle,
+ );
+ }
+ $contents[$path] = false;
+ }
+
+ $opts = array( 'maxConnsPerHost' => $params['concurrency'] );
+ $reqs = $this->http->runMulti( $reqs, $opts );
+ foreach ( $reqs as $path => $op ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
+ if ( $rcode >= 200 && $rcode <= 299 ) {
+ rewind( $op['stream'] ); // start from the beginning
+ $contents[$path] = stream_get_contents( $op['stream'] );
+ } elseif ( $rcode === 404 ) {
+ $contents[$path] = false;
+ } else {
+ $this->onError( null, __METHOD__,
+ array( 'src' => $path ) + $ep, $rerr, $rcode, $rdesc );
+ }
+ fclose( $op['stream'] ); // close open handle
}
return $contents;
}
- /**
- * @see FileBackendStore::doDirectoryExists()
- * @return bool|null
- */
protected function doDirectoryExists( $fullCont, $dir, array $params ) {
- try {
- $container = $this->getContainer( $fullCont );
- $prefix = ( $dir == '' ) ? null : "{$dir}/";
- return ( count( $container->list_objects( 1, null, $prefix ) ) > 0 );
- } catch ( NoSuchContainerException $e ) {
- return false;
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, null, __METHOD__,
- array( 'cont' => $fullCont, 'dir' => $dir ) );
+ $prefix = ( $dir == '' ) ? null : "{$dir}/";
+ $status = $this->objectListing( $fullCont, 'names', 1, null, $prefix );
+ if ( $status->isOk() ) {
+ return ( count( $status->value ) ) > 0;
}
return null; // error
@@ -897,6 +757,9 @@ class SwiftFileBackend extends FileBackendStore {
/**
* @see FileBackendStore::getDirectoryListInternal()
+ * @param string $fullCont
+ * @param string $dir
+ * @param array $params
* @return SwiftFileBackendDirList
*/
public function getDirectoryListInternal( $fullCont, $dir, array $params ) {
@@ -905,6 +768,9 @@ class SwiftFileBackend extends FileBackendStore {
/**
* @see FileBackendStore::getFileListInternal()
+ * @param string $fullCont
+ * @param string $dir
+ * @param array $params
* @return SwiftFileBackendFileList
*/
public function getFileListInternal( $fullCont, $dir, array $params ) {
@@ -916,10 +782,10 @@ class SwiftFileBackend extends FileBackendStore {
*
* @param string $fullCont Resolved container name
* @param string $dir Resolved storage directory with no trailing slash
- * @param string|null $after Storage path of file to list items after
- * @param integer $limit Max number of items to list
+ * @param string|null $after Resolved container relative path to list items after
+ * @param int $limit Max number of items to list
* @param array $params Parameters for getDirectoryList()
- * @return Array List of resolved paths of directories directly under $dir
+ * @return array List of container relative resolved paths of directories directly under $dir
* @throws FileBackendError
*/
public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
@@ -929,156 +795,181 @@ class SwiftFileBackend extends FileBackendStore {
}
$section = new ProfileSection( __METHOD__ . '-' . $this->name );
- try {
- $container = $this->getContainer( $fullCont );
- $prefix = ( $dir == '' ) ? null : "{$dir}/";
- // Non-recursive: only list dirs right under $dir
- if ( !empty( $params['topOnly'] ) ) {
- $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
- foreach ( $objects as $object ) { // files and directories
- if ( substr( $object, -1 ) === '/' ) {
- $dirs[] = $object; // directories end in '/'
- }
+
+ $prefix = ( $dir == '' ) ? null : "{$dir}/";
+ // Non-recursive: only list dirs right under $dir
+ if ( !empty( $params['topOnly'] ) ) {
+ $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix, '/' );
+ if ( !$status->isOk() ) {
+ return $dirs; // error
+ }
+ $objects = $status->value;
+ foreach ( $objects as $object ) { // files and directories
+ if ( substr( $object, -1 ) === '/' ) {
+ $dirs[] = $object; // directories end in '/'
}
+ }
+ } else {
// Recursive: list all dirs under $dir and its subdirs
- } else {
- // Get directory from last item of prior page
- $lastDir = $this->getParentDir( $after ); // must be first page
- $objects = $container->list_objects( $limit, $after, $prefix );
- foreach ( $objects as $object ) { // files
- $objectDir = $this->getParentDir( $object ); // directory of object
- if ( $objectDir !== false && $objectDir !== $dir ) {
- // Swift stores paths in UTF-8, using binary sorting.
- // See function "create_container_table" in common/db.py.
- // If a directory is not "greater" than the last one,
- // then it was already listed by the calling iterator.
- if ( strcmp( $objectDir, $lastDir ) > 0 ) {
- $pDir = $objectDir;
- do { // add dir and all its parent dirs
- $dirs[] = "{$pDir}/";
- $pDir = $this->getParentDir( $pDir );
- } while ( $pDir !== false // sanity
- && strcmp( $pDir, $lastDir ) > 0 // not done already
- && strlen( $pDir ) > strlen( $dir ) // within $dir
- );
- }
- $lastDir = $objectDir;
+ $getParentDir = function ( $path ) {
+ return ( strpos( $path, '/' ) !== false ) ? dirname( $path ) : false;
+ };
+
+ // Get directory from last item of prior page
+ $lastDir = $getParentDir( $after ); // must be first page
+ $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix );
+
+ if ( !$status->isOk() ) {
+ return $dirs; // error
+ }
+
+ $objects = $status->value;
+
+ foreach ( $objects as $object ) { // files
+ $objectDir = $getParentDir( $object ); // directory of object
+
+ if ( $objectDir !== false && $objectDir !== $dir ) {
+ // Swift stores paths in UTF-8, using binary sorting.
+ // See function "create_container_table" in common/db.py.
+ // If a directory is not "greater" than the last one,
+ // then it was already listed by the calling iterator.
+ if ( strcmp( $objectDir, $lastDir ) > 0 ) {
+ $pDir = $objectDir;
+ do { // add dir and all its parent dirs
+ $dirs[] = "{$pDir}/";
+ $pDir = $getParentDir( $pDir );
+ } while ( $pDir !== false // sanity
+ && strcmp( $pDir, $lastDir ) > 0 // not done already
+ && strlen( $pDir ) > strlen( $dir ) // within $dir
+ );
}
+ $lastDir = $objectDir;
}
}
- // Page on the unfiltered directory listing (what is returned may be filtered)
- if ( count( $objects ) < $limit ) {
- $after = INF; // avoid a second RTT
- } else {
- $after = end( $objects ); // update last item
- }
- } catch ( NoSuchContainerException $e ) {
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, null, __METHOD__,
- array( 'cont' => $fullCont, 'dir' => $dir ) );
- throw new FileBackendError( "Got " . get_class( $e ) . " exception." );
+ }
+ // Page on the unfiltered directory listing (what is returned may be filtered)
+ if ( count( $objects ) < $limit ) {
+ $after = INF; // avoid a second RTT
+ } else {
+ $after = end( $objects ); // update last item
}
return $dirs;
}
- protected function getParentDir( $path ) {
- return ( strpos( $path, '/' ) !== false ) ? dirname( $path ) : false;
- }
-
/**
* Do not call this function outside of SwiftFileBackendFileList
*
* @param string $fullCont Resolved container name
* @param string $dir Resolved storage directory with no trailing slash
- * @param string|null $after Storage path of file to list items after
- * @param integer $limit Max number of items to list
+ * @param string|null $after Resolved container relative path of file to list items after
+ * @param int $limit Max number of items to list
* @param array $params Parameters for getDirectoryList()
- * @return Array List of resolved paths of files under $dir
+ * @return array List of resolved container relative paths of files under $dir
* @throws FileBackendError
*/
public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
- $files = array();
+ $files = array(); // list of (path, stat array or null) entries
if ( $after === INF ) {
return $files; // nothing more
}
$section = new ProfileSection( __METHOD__ . '-' . $this->name );
- try {
- $container = $this->getContainer( $fullCont );
- $prefix = ( $dir == '' ) ? null : "{$dir}/";
- // Non-recursive: only list files right under $dir
- if ( !empty( $params['topOnly'] ) ) { // files and dirs
- if ( !empty( $params['adviseStat'] ) ) {
- $limit = min( $limit, self::CACHE_CHEAP_SIZE );
- // Note: get_objects() does not include directories
- $objects = $this->loadObjectListing( $params, $dir,
- $container->get_objects( $limit, $after, $prefix, null, '/' ) );
- $files = $objects;
- } else {
- $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
- foreach ( $objects as $object ) { // files and directories
- if ( substr( $object, -1 ) !== '/' ) {
- $files[] = $object; // directories end in '/'
- }
- }
- }
- // Recursive: list all files under $dir and its subdirs
- } else { // files
- if ( !empty( $params['adviseStat'] ) ) {
- $limit = min( $limit, self::CACHE_CHEAP_SIZE );
- $objects = $this->loadObjectListing( $params, $dir,
- $container->get_objects( $limit, $after, $prefix ) );
- } else {
- $objects = $container->list_objects( $limit, $after, $prefix );
- }
- $files = $objects;
+
+ $prefix = ( $dir == '' ) ? null : "{$dir}/";
+ // $objects will contain a list of unfiltered names or CF_Object items
+ // Non-recursive: only list files right under $dir
+ if ( !empty( $params['topOnly'] ) ) {
+ if ( !empty( $params['adviseStat'] ) ) {
+ $status = $this->objectListing( $fullCont, 'info', $limit, $after, $prefix, '/' );
+ } else {
+ $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix, '/' );
}
- // Page on the unfiltered object listing (what is returned may be filtered)
- if ( count( $objects ) < $limit ) {
- $after = INF; // avoid a second RTT
+ } else {
+ // Recursive: list all files under $dir and its subdirs
+ if ( !empty( $params['adviseStat'] ) ) {
+ $status = $this->objectListing( $fullCont, 'info', $limit, $after, $prefix );
} else {
- $after = end( $objects ); // update last item
+ $status = $this->objectListing( $fullCont, 'names', $limit, $after, $prefix );
}
- } catch ( NoSuchContainerException $e ) {
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, null, __METHOD__,
- array( 'cont' => $fullCont, 'dir' => $dir ) );
- throw new FileBackendError( "Got " . get_class( $e ) . " exception." );
+ }
+
+ // Reformat this list into a list of (name, stat array or null) entries
+ if ( !$status->isOk() ) {
+ return $files; // error
+ }
+
+ $objects = $status->value;
+ $files = $this->buildFileObjectListing( $params, $dir, $objects );
+
+ // Page on the unfiltered object listing (what is returned may be filtered)
+ if ( count( $objects ) < $limit ) {
+ $after = INF; // avoid a second RTT
+ } else {
+ $after = end( $objects ); // update last item
+ $after = is_object( $after ) ? $after->name : $after;
}
return $files;
}
/**
- * Load a list of objects that belong under $dir into stat cache
- * and return a list of the names of the objects in the same order.
+ * Build a list of file objects, filtering out any directories
+ * and extracting any stat info if provided in $objects (for CF_Objects)
*
* @param array $params Parameters for getDirectoryList()
* @param string $dir Resolved container directory path
- * @param array $cfObjects List of CF_Object items
- * @return array List of object names
+ * @param array $objects List of CF_Object items or object names
+ * @return array List of (names,stat array or null) entries
*/
- private function loadObjectListing( array $params, $dir, array $cfObjects ) {
+ private function buildFileObjectListing( array $params, $dir, array $objects ) {
$names = array();
- $storageDir = rtrim( $params['dir'], '/' );
- $suffixStart = ( $dir === '' ) ? 0 : strlen( $dir ) + 1; // size of "path/to/dir/"
- // Iterate over the list *backwards* as this primes the stat cache, which is LRU.
- // If this fills the cache and the caller stats an uncached file before stating
- // the ones on the listing, there would be zero cache hits if this went forwards.
- for ( end( $cfObjects ); key( $cfObjects ) !== null; prev( $cfObjects ) ) {
- $object = current( $cfObjects );
- $path = "{$storageDir}/" . substr( $object->name, $suffixStart );
- $val = array(
- // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW
- 'mtime' => wfTimestamp( TS_MW, $object->last_modified ),
- 'size' => (int)$object->content_length,
- 'latest' => false // eventually consistent
- );
- $this->cheapCache->set( $path, 'stat', $val );
- $names[] = $object->name;
+ foreach ( $objects as $object ) {
+ if ( is_object( $object ) ) {
+ if ( isset( $object->subdir ) || !isset( $object->name ) ) {
+ continue; // virtual directory entry; ignore
+ }
+ $stat = array(
+ // Convert various random Swift dates to TS_MW
+ 'mtime' => $this->convertSwiftDate( $object->last_modified, TS_MW ),
+ 'size' => (int)$object->bytes,
+ // Note: manifiest ETags are not an MD5 of the file
+ 'md5' => ctype_xdigit( $object->hash ) ? $object->hash : null,
+ 'latest' => false // eventually consistent
+ );
+ $names[] = array( $object->name, $stat );
+ } elseif ( substr( $object, -1 ) !== '/' ) {
+ // Omit directories, which end in '/' in listings
+ $names[] = array( $object, null );
+ }
+ }
+
+ return $names;
+ }
+
+ /**
+ * Do not call this function outside of SwiftFileBackendFileList
+ *
+ * @param string $path Storage path
+ * @param array $val Stat value
+ */
+ public function loadListingStatInternal( $path, array $val ) {
+ $this->cheapCache->set( $path, 'stat', $val );
+ }
+
+ protected function doGetFileXAttributes( array $params ) {
+ $stat = $this->getFileStat( $params );
+ if ( $stat ) {
+ if ( !isset( $stat['xattr'] ) ) {
+ // Stat entries filled by file listings don't include metadata/headers
+ $this->clearCache( array( $params['src'] ) );
+ $stat = $this->getFileStat( $params );
+ }
+
+ return $stat['xattr'];
+ } else {
+ return false;
}
- return array_reverse( $names ); // keep the paths in original order
}
protected function doGetFileSha1base36( array $params ) {
@@ -1089,6 +980,7 @@ class SwiftFileBackend extends FileBackendStore {
$this->clearCache( array( $params['src'] ) );
$stat = $this->getFileStat( $params );
}
+
return $stat['sha1'];
} else {
return false;
@@ -1103,24 +995,29 @@ class SwiftFileBackend extends FileBackendStore {
$status->fatal( 'backend-fail-invalidpath', $params['src'] );
}
- try {
- $cont = $this->getContainer( $srcCont );
- } catch ( NoSuchContainerException $e ) {
+ $auth = $this->getAuthentication();
+ if ( !$auth || !is_array( $this->getContainerStat( $srcCont ) ) ) {
$status->fatal( 'backend-fail-stream', $params['src'] );
- return $status;
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+
return $status;
}
- try {
- $output = fopen( 'php://output', 'wb' );
- $obj = new CF_Object( $cont, $srcRel, false, false ); // skip HEAD
- $obj->stream( $output, $this->headersFromParams( $params ) );
- } catch ( NoSuchObjectException $e ) {
+ $handle = fopen( 'php://output', 'wb' );
+
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
+ 'method' => 'GET',
+ 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
+ 'headers' => $this->authTokenHeaders( $auth )
+ + $this->headersFromParams( $params ),
+ 'stream' => $handle,
+ ) );
+
+ if ( $rcode >= 200 && $rcode <= 299 ) {
+ // good
+ } elseif ( $rcode === 404 ) {
$status->fatal( 'backend-fail-stream', $params['src'] );
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status, __METHOD__, $params );
+ } else {
+ $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
}
return $status;
@@ -1129,66 +1026,60 @@ class SwiftFileBackend extends FileBackendStore {
protected function doGetLocalCopyMulti( array $params ) {
$tmpFiles = array();
+ $auth = $this->getAuthentication();
+
$ep = array_diff_key( $params, array( 'srcs' => 1 ) ); // for error logging
// Blindly create tmp files and stream to them, catching any exception if the file does
// not exist. Doing a stat here is useless causes infinite loops in addMissingMetadata().
- foreach ( array_chunk( $params['srcs'], $params['concurrency'] ) as $pathBatch ) {
- $cfOps = array(); // (path => CF_Async_Op)
+ $reqs = array(); // (path => op)
- foreach ( $pathBatch as $path ) { // each path in this concurrent batch
- list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
- if ( $srcRel === null ) {
- $tmpFiles[$path] = null;
- continue;
- }
- $tmpFile = null;
- try {
- $sContObj = $this->getContainer( $srcCont );
- $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
- // Get source file extension
- $ext = FileBackend::extensionFromPath( $path );
- // Create a new temporary file...
- $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
- if ( $tmpFile ) {
- $handle = fopen( $tmpFile->getPath(), 'wb' );
- if ( $handle ) {
- $headers = $this->headersFromParams( $params );
- if ( count( $pathBatch ) > 1 ) {
- $cfOps[$path] = $obj->stream_async( $handle, $headers );
- $cfOps[$path]->_file_handle = $handle; // close this later
- } else {
- $obj->stream( $handle, $headers );
- fclose( $handle );
- }
- } else {
- $tmpFile = null;
- }
- }
- } catch ( NoSuchContainerException $e ) {
- $tmpFile = null;
- } catch ( NoSuchObjectException $e ) {
- $tmpFile = null;
- } catch ( CloudFilesException $e ) { // some other exception?
+ foreach ( $params['srcs'] as $path ) { // each path in this concurrent batch
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
+ if ( $srcRel === null || !$auth ) {
+ $tmpFiles[$path] = null;
+ continue;
+ }
+ // Get source file extension
+ $ext = FileBackend::extensionFromPath( $path );
+ // Create a new temporary file...
+ $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
+ if ( $tmpFile ) {
+ $handle = fopen( $tmpFile->getPath(), 'wb' );
+ if ( $handle ) {
+ $reqs[$path] = array(
+ 'method' => 'GET',
+ 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
+ 'headers' => $this->authTokenHeaders( $auth )
+ + $this->headersFromParams( $params ),
+ 'stream' => $handle,
+ );
+ } else {
$tmpFile = null;
- $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep );
}
- $tmpFiles[$path] = $tmpFile;
}
-
- $batch = new CF_Async_Op_Batch( $cfOps );
- $cfOps = $batch->execute();
- foreach ( $cfOps as $path => $cfOp ) {
- try {
- $cfOp->getLastResponse();
- } catch ( NoSuchContainerException $e ) {
+ $tmpFiles[$path] = $tmpFile;
+ }
+
+ $opts = array( 'maxConnsPerHost' => $params['concurrency'] );
+ $reqs = $this->http->runMulti( $reqs, $opts );
+ foreach ( $reqs as $path => $op ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
+ fclose( $op['stream'] ); // close open handle
+ if ( $rcode >= 200 && $rcode <= 299 ) {
+ $size = $tmpFiles[$path] ? $tmpFiles[$path]->getSize() : 0;
+ // Double check that the disk is not full/broken
+ if ( $size != $rhdrs['content-length'] ) {
$tmpFiles[$path] = null;
- } catch ( NoSuchObjectException $e ) {
- $tmpFiles[$path] = null;
- } catch ( CloudFilesException $e ) { // some other exception?
- $tmpFiles[$path] = null;
- $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep );
+ $rerr = "Got {$size}/{$rhdrs['content-length']} bytes";
+ $this->onError( null, __METHOD__,
+ array( 'src' => $path ) + $ep, $rerr, $rcode, $rdesc );
}
- fclose( $cfOp->_file_handle ); // close open handle
+ } elseif ( $rcode === 404 ) {
+ $tmpFiles[$path] = false;
+ } else {
+ $tmpFiles[$path] = null;
+ $this->onError( null, __METHOD__,
+ array( 'src' => $path ) + $ep, $rerr, $rcode, $rdesc );
}
}
@@ -1197,46 +1088,55 @@ class SwiftFileBackend extends FileBackendStore {
public function getFileHttpUrl( array $params ) {
if ( $this->swiftTempUrlKey != '' ||
- ( $this->rgwS3AccessKey != '' && $this->rgwS3SecretKey != '' ) )
- {
+ ( $this->rgwS3AccessKey != '' && $this->rgwS3SecretKey != '' )
+ ) {
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
if ( $srcRel === null ) {
return null; // invalid path
}
- try {
- $ttl = isset( $params['ttl'] ) ? $params['ttl'] : 86400;
- $sContObj = $this->getContainer( $srcCont );
- $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
- if ( $this->swiftTempUrlKey != '' ) {
- return $obj->get_temp_url( $this->swiftTempUrlKey, $ttl, "GET" );
- } else { // give S3 API URL for rgw
- $expires = time() + $ttl;
- // Path for signature starts with the bucket
- $spath = '/' . rawurlencode( $srcCont ) . '/' .
- str_replace( '%2F', '/', rawurlencode( $srcRel ) );
- // Calculate the hash
- $signature = base64_encode( hash_hmac(
- 'sha1',
- "GET\n\n\n{$expires}\n{$spath}",
- $this->rgwS3SecretKey,
- true // raw
- ) );
- // See http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html.
- // Note: adding a newline for empty CanonicalizedAmzHeaders does not work.
- return wfAppendQuery(
- str_replace( '/swift/v1', '', // S3 API is the rgw default
- $sContObj->cfs_http->getStorageUrl() . $spath ),
- array(
- 'Signature' => $signature,
- 'Expires' => $expires,
- 'AWSAccessKeyId' => $this->rgwS3AccessKey )
- );
- }
- } catch ( NoSuchContainerException $e ) {
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, null, __METHOD__, $params );
+
+ $auth = $this->getAuthentication();
+ if ( !$auth ) {
+ return null;
+ }
+
+ $ttl = isset( $params['ttl'] ) ? $params['ttl'] : 86400;
+ $expires = time() + $ttl;
+
+ if ( $this->swiftTempUrlKey != '' ) {
+ $url = $this->storageUrl( $auth, $srcCont, $srcRel );
+ // Swift wants the signature based on the unencoded object name
+ $contPath = parse_url( $this->storageUrl( $auth, $srcCont ), PHP_URL_PATH );
+ $signature = hash_hmac( 'sha1',
+ "GET\n{$expires}\n{$contPath}/{$srcRel}",
+ $this->swiftTempUrlKey
+ );
+
+ return "{$url}?temp_url_sig={$signature}&temp_url_expires={$expires}";
+ } else { // give S3 API URL for rgw
+ // Path for signature starts with the bucket
+ $spath = '/' . rawurlencode( $srcCont ) . '/' .
+ str_replace( '%2F', '/', rawurlencode( $srcRel ) );
+ // Calculate the hash
+ $signature = base64_encode( hash_hmac(
+ 'sha1',
+ "GET\n\n\n{$expires}\n{$spath}",
+ $this->rgwS3SecretKey,
+ true // raw
+ ) );
+ // See http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html.
+ // Note: adding a newline for empty CanonicalizedAmzHeaders does not work.
+ return wfAppendQuery(
+ str_replace( '/swift/v1', '', // S3 API is the rgw default
+ $this->storageUrl( $auth ) . $spath ),
+ array(
+ 'Signature' => $signature,
+ 'Expires' => $expires,
+ 'AWSAccessKeyId' => $this->rgwS3AccessKey )
+ );
}
}
+
return null;
}
@@ -1250,37 +1150,61 @@ class SwiftFileBackend extends FileBackendStore {
* $params is currently only checked for a 'latest' flag.
*
* @param array $params
- * @return Array
+ * @return array
*/
protected function headersFromParams( array $params ) {
$hdrs = array();
if ( !empty( $params['latest'] ) ) {
- $hdrs[] = 'X-Newest: true';
+ $hdrs['x-newest'] = 'true';
}
+
return $hdrs;
}
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
$statuses = array();
- $cfOps = array(); // list of CF_Async_Op objects
+ $auth = $this->getAuthentication();
+ if ( !$auth ) {
+ foreach ( $fileOpHandles as $index => $fileOpHandle ) {
+ $statuses[$index] = Status::newFatal( 'backend-fail-connect', $this->name );
+ }
+
+ return $statuses;
+ }
+
+ // Split the HTTP requests into stages that can be done concurrently
+ $httpReqsByStage = array(); // map of (stage => index => HTTP request)
foreach ( $fileOpHandles as $index => $fileOpHandle ) {
- $cfOps[$index] = $fileOpHandle->cfOp;
- }
- $batch = new CF_Async_Op_Batch( $cfOps );
-
- $cfOps = $batch->execute();
- foreach ( $cfOps as $index => $cfOp ) {
- $status = Status::newGood();
- $function = '_getResponse' . $fileOpHandles[$index]->call;
- try { // catch exceptions; update status
- $this->$function( $cfOp, $status, $fileOpHandles[$index]->params );
- $this->purgeCDNCache( $fileOpHandles[$index]->affectedObjects );
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, $status,
- __CLASS__ . ":$function", $fileOpHandles[$index]->params );
+ $reqs = $fileOpHandle->httpOp;
+ // Convert the 'url' parameter to an actual URL using $auth
+ foreach ( $reqs as $stage => &$req ) {
+ list( $container, $relPath ) = $req['url'];
+ $req['url'] = $this->storageUrl( $auth, $container, $relPath );
+ $req['headers'] = isset( $req['headers'] ) ? $req['headers'] : array();
+ $req['headers'] = $this->authTokenHeaders( $auth ) + $req['headers'];
+ $httpReqsByStage[$stage][$index] = $req;
+ }
+ $statuses[$index] = Status::newGood();
+ }
+
+ // Run all requests for the first stage, then the next, and so on
+ $reqCount = count( $httpReqsByStage );
+ for ( $stage = 0; $stage < $reqCount; ++$stage ) {
+ $httpReqs = $this->http->runMulti( $httpReqsByStage[$stage] );
+ foreach ( $httpReqs as $index => $httpReq ) {
+ // Run the callback for each request of this operation
+ $callback = $fileOpHandles[$index]->callback;
+ call_user_func_array( $callback, array( $httpReq, $statuses[$index] ) );
+ // On failure, abort all remaining requests for this operation
+ // (e.g. abort the DELETE request if the COPY request fails for a move)
+ if ( !$statuses[$index]->isOK() ) {
+ $stages = count( $fileOpHandles[$index]->httpOp );
+ for ( $s = ( $stage + 1 ); $s < $stages; ++$s ) {
+ unset( $httpReqsByStage[$s][$index] );
+ }
+ }
}
- $statuses[$index] = $status;
}
return $statuses;
@@ -1289,7 +1213,13 @@ class SwiftFileBackend extends FileBackendStore {
/**
* Set read/write permissions for a Swift container.
*
- * $readGrps is a list of the possible criteria for a request to have
+ * @see http://swift.openstack.org/misc.html#acls
+ *
+ * In general, we don't allow listings to end-users. It's not useful, isn't well-defined
+ * (lists are truncated to 10000 item with no way to page), and is just a performance risk.
+ *
+ * @param string $container Resolved Swift container
+ * @param array $readGrps List of the possible criteria for a request to have
* access to read a container. Each item is one of the following formats:
* - account:user : Grants access if the request is by the given user
* - ".r:<regex>" : Grants access if the request is from a referrer host that
@@ -1297,228 +1227,438 @@ class SwiftFileBackend extends FileBackendStore {
* Setting this to '*' effectively makes a container public.
* -".rlistings:<regex>" : Grants access if the request is from a referrer host that
* matches the expression and the request is for a listing.
- *
- * $writeGrps is a list of the possible criteria for a request to have
+ * @param array $writeGrps A list of the possible criteria for a request to have
* access to write to a container. Each item is of the following format:
* - account:user : Grants access if the request is by the given user
- *
- * @see http://swift.openstack.org/misc.html#acls
- *
- * In general, we don't allow listings to end-users. It's not useful, isn't well-defined
- * (lists are truncated to 10000 item with no way to page), and is just a performance risk.
- *
- * @param CF_Container $contObj Swift container
- * @param array $readGrps List of read access routes
- * @param array $writeGrps List of write access routes
* @return Status
*/
- protected function setContainerAccess(
- CF_Container $contObj, array $readGrps, array $writeGrps
- ) {
- $creds = $contObj->cfs_auth->export_credentials();
+ protected function setContainerAccess( $container, array $readGrps, array $writeGrps ) {
+ $status = Status::newGood();
+ $auth = $this->getAuthentication();
- $url = $creds['storage_url'] . '/' . rawurlencode( $contObj->name );
+ if ( !$auth ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
- // Note: 10 second timeout consistent with php-cloudfiles
- $req = MWHttpRequest::factory( $url, array( 'method' => 'POST', 'timeout' => 10 ) );
- $req->setHeader( 'X-Auth-Token', $creds['auth_token'] );
- $req->setHeader( 'X-Container-Read', implode( ',', $readGrps ) );
- $req->setHeader( 'X-Container-Write', implode( ',', $writeGrps ) );
+ return $status;
+ }
+
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
+ 'method' => 'POST',
+ 'url' => $this->storageUrl( $auth, $container ),
+ 'headers' => $this->authTokenHeaders( $auth ) + array(
+ 'x-container-read' => implode( ',', $readGrps ),
+ 'x-container-write' => implode( ',', $writeGrps )
+ )
+ ) );
+
+ if ( $rcode != 204 && $rcode !== 202 ) {
+ $status->fatal( 'backend-fail-internal', $this->name );
+ }
- return $req->execute(); // should return 204
+ return $status;
}
/**
- * Purge the CDN cache of affected objects if CDN caching is enabled.
- * This is for Rackspace/Akamai CDNs.
+ * Get a Swift container stat array, possibly from process cache.
+ * Use $reCache if the file count or byte count is needed.
*
- * @param array $objects List of CF_Object items
- * @return void
+ * @param string $container Container name
+ * @param bool $bypassCache Bypass all caches and load from Swift
+ * @return array|bool|null False on 404, null on failure
*/
- public function purgeCDNCache( array $objects ) {
- if ( $this->swiftUseCDN && $this->swiftCDNPurgable ) {
- foreach ( $objects as $object ) {
- try {
- $object->purge_from_cdn();
- } catch ( CDNNotEnabledException $e ) {
- // CDN not enabled; nothing to see here
- } catch ( CloudFilesException $e ) {
- $this->handleException( $e, null, __METHOD__,
- array( 'cont' => $object->container->name, 'obj' => $object->name ) );
+ protected function getContainerStat( $container, $bypassCache = false ) {
+ $section = new ProfileSection( __METHOD__ . '-' . $this->name );
+
+ if ( $bypassCache ) { // purge cache
+ $this->containerStatCache->clear( $container );
+ } elseif ( !$this->containerStatCache->has( $container, 'stat' ) ) {
+ $this->primeContainerCache( array( $container ) ); // check persistent cache
+ }
+ if ( !$this->containerStatCache->has( $container, 'stat' ) ) {
+ $auth = $this->getAuthentication();
+ if ( !$auth ) {
+ return null;
+ }
+
+ wfProfileIn( __METHOD__ . "-{$this->name}-miss" );
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
+ 'method' => 'HEAD',
+ 'url' => $this->storageUrl( $auth, $container ),
+ 'headers' => $this->authTokenHeaders( $auth )
+ ) );
+ wfProfileOut( __METHOD__ . "-{$this->name}-miss" );
+
+ if ( $rcode === 204 ) {
+ $stat = array(
+ 'count' => $rhdrs['x-container-object-count'],
+ 'bytes' => $rhdrs['x-container-bytes-used']
+ );
+ if ( $bypassCache ) {
+ return $stat;
+ } else {
+ $this->containerStatCache->set( $container, 'stat', $stat ); // cache it
+ $this->setContainerCache( $container, $stat ); // update persistent cache
}
+ } elseif ( $rcode === 404 ) {
+ return false;
+ } else {
+ $this->onError( null, __METHOD__,
+ array( 'cont' => $container ), $rerr, $rcode, $rdesc );
+
+ return null;
}
}
+
+ return $this->containerStatCache->get( $container, 'stat' );
}
/**
- * Get an authenticated connection handle to the Swift proxy
+ * Create a Swift container
*
- * @throws CloudFilesException
- * @throws CloudFilesException|Exception
- * @return CF_Connection|bool False on failure
+ * @param string $container Container name
+ * @param array $params
+ * @return Status
*/
- protected function getConnection() {
- if ( $this->connException instanceof CloudFilesException ) {
- if ( ( time() - $this->connErrorTime ) < 60 ) {
- throw $this->connException; // failed last attempt; don't bother
- } else { // actually retry this time
- $this->connException = null;
- $this->connErrorTime = 0;
- }
+ protected function createContainer( $container, array $params ) {
+ $status = Status::newGood();
+
+ $auth = $this->getAuthentication();
+ if ( !$auth ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+
+ return $status;
}
- // Session keys expire after a while, so we renew them periodically
- $reAuth = ( ( time() - $this->sessionStarted ) > $this->authTTL );
- // Authenticate with proxy and get a session key...
- if ( !$this->conn || $reAuth ) {
- $this->sessionStarted = 0;
- $this->connContainerCache->clear();
- $cacheKey = $this->getCredsCacheKey( $this->auth->username );
- $creds = $this->srvCache->get( $cacheKey ); // credentials
- if ( is_array( $creds ) ) { // cache hit
- $this->auth->load_cached_credentials(
- $creds['auth_token'], $creds['storage_url'], $creds['cdnm_url'] );
- $this->sessionStarted = time() - ceil( $this->authTTL / 2 ); // skew for worst case
- } else { // cache miss
- try {
- $this->auth->authenticate();
- $creds = $this->auth->export_credentials();
- $this->srvCache->add( $cacheKey, $creds, ceil( $this->authTTL / 2 ) ); // cache
- $this->sessionStarted = time();
- } catch ( CloudFilesException $e ) {
- $this->connException = $e; // don't keep re-trying
- $this->connErrorTime = time();
- throw $e; // throw it back
- }
- }
- if ( $this->conn ) { // re-authorizing?
- $this->conn->close(); // close active cURL handles in CF_Http object
- }
- $this->conn = new CF_Connection( $this->auth );
+
+ // @see SwiftFileBackend::setContainerAccess()
+ if ( empty( $params['noAccess'] ) ) {
+ $readGrps = array( '.r:*', $this->swiftUser ); // public
+ } else {
+ $readGrps = array( $this->swiftUser ); // private
+ }
+ $writeGrps = array( $this->swiftUser ); // sanity
+
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
+ 'method' => 'PUT',
+ 'url' => $this->storageUrl( $auth, $container ),
+ 'headers' => $this->authTokenHeaders( $auth ) + array(
+ 'x-container-read' => implode( ',', $readGrps ),
+ 'x-container-write' => implode( ',', $writeGrps )
+ )
+ ) );
+
+ if ( $rcode === 201 ) { // new
+ // good
+ } elseif ( $rcode === 202 ) { // already there
+ // this shouldn't really happen, but is OK
+ } else {
+ $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
}
- return $this->conn;
+
+ return $status;
}
/**
- * Close the connection to the Swift proxy
+ * Delete a Swift container
*
- * @return void
+ * @param string $container Container name
+ * @param array $params
+ * @return Status
*/
- protected function closeConnection() {
- if ( $this->conn ) {
- $this->conn->close(); // close active cURL handles in CF_Http object
- $this->conn = null;
- $this->sessionStarted = 0;
- $this->connContainerCache->clear();
+ protected function deleteContainer( $container, array $params ) {
+ $status = Status::newGood();
+
+ $auth = $this->getAuthentication();
+ if ( !$auth ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+
+ return $status;
+ }
+
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
+ 'method' => 'DELETE',
+ 'url' => $this->storageUrl( $auth, $container ),
+ 'headers' => $this->authTokenHeaders( $auth )
+ ) );
+
+ if ( $rcode >= 200 && $rcode <= 299 ) { // deleted
+ $this->containerStatCache->clear( $container ); // purge
+ } elseif ( $rcode === 404 ) { // not there
+ // this shouldn't really happen, but is OK
+ } elseif ( $rcode === 409 ) { // not empty
+ $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc ); // race?
+ } else {
+ $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
}
+
+ return $status;
}
/**
- * Get the cache key for a container
+ * Get a list of objects under a container.
+ * Either just the names or a list of stdClass objects with details can be returned.
*
- * @param string $username
- * @return string
+ * @param string $fullCont
+ * @param string $type ('info' for a list of object detail maps, 'names' for names only)
+ * @param int $limit
+ * @param string|null $after
+ * @param string|null $prefix
+ * @param string|null $delim
+ * @return Status With the list as value
*/
- private function getCredsCacheKey( $username ) {
- return wfMemcKey( 'backend', $this->getName(), 'usercreds', $username );
+ private function objectListing(
+ $fullCont, $type, $limit, $after = null, $prefix = null, $delim = null
+ ) {
+ $status = Status::newGood();
+
+ $auth = $this->getAuthentication();
+ if ( !$auth ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+
+ return $status;
+ }
+
+ $query = array( 'limit' => $limit );
+ if ( $type === 'info' ) {
+ $query['format'] = 'json';
+ }
+ if ( $after !== null ) {
+ $query['marker'] = $after;
+ }
+ if ( $prefix !== null ) {
+ $query['prefix'] = $prefix;
+ }
+ if ( $delim !== null ) {
+ $query['delimiter'] = $delim;
+ }
+
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
+ 'method' => 'GET',
+ 'url' => $this->storageUrl( $auth, $fullCont ),
+ 'query' => $query,
+ 'headers' => $this->authTokenHeaders( $auth )
+ ) );
+
+ $params = array( 'cont' => $fullCont, 'prefix' => $prefix, 'delim' => $delim );
+ if ( $rcode === 200 ) { // good
+ if ( $type === 'info' ) {
+ $status->value = FormatJson::decode( trim( $rbody ) );
+ } else {
+ $status->value = explode( "\n", trim( $rbody ) );
+ }
+ } elseif ( $rcode === 204 ) {
+ $status->value = array(); // empty container
+ } elseif ( $rcode === 404 ) {
+ $status->value = array(); // no container
+ } else {
+ $this->onError( $status, __METHOD__, $params, $rerr, $rcode, $rdesc );
+ }
+
+ return $status;
+ }
+
+ protected function doPrimeContainerCache( array $containerInfo ) {
+ foreach ( $containerInfo as $container => $info ) {
+ $this->containerStatCache->set( $container, 'stat', $info );
+ }
+ }
+
+ protected function doGetFileStatMulti( array $params ) {
+ $stats = array();
+
+ $auth = $this->getAuthentication();
+
+ $reqs = array();
+ foreach ( $params['srcs'] as $path ) {
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
+ if ( $srcRel === null ) {
+ $stats[$path] = false;
+ continue; // invalid storage path
+ } elseif ( !$auth ) {
+ $stats[$path] = null;
+ continue;
+ }
+
+ // (a) Check the container
+ $cstat = $this->getContainerStat( $srcCont );
+ if ( $cstat === false ) {
+ $stats[$path] = false;
+ continue; // ok, nothing to do
+ } elseif ( !is_array( $cstat ) ) {
+ $stats[$path] = null;
+ continue;
+ }
+
+ $reqs[$path] = array(
+ 'method' => 'HEAD',
+ 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
+ 'headers' => $this->authTokenHeaders( $auth ) + $this->headersFromParams( $params )
+ );
+ }
+
+ $opts = array( 'maxConnsPerHost' => $params['concurrency'] );
+ $reqs = $this->http->runMulti( $reqs, $opts );
+
+ foreach ( $params['srcs'] as $path ) {
+ if ( array_key_exists( $path, $stats ) ) {
+ continue; // some sort of failure above
+ }
+ // (b) Check the file
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[$path]['response'];
+ if ( $rcode === 200 || $rcode === 204 ) {
+ // Update the object if it is missing some headers
+ $rhdrs = $this->addMissingMetadata( $rhdrs, $path );
+ // Fetch all of the custom metadata headers
+ $metadata = array();
+ foreach ( $rhdrs as $name => $value ) {
+ if ( strpos( $name, 'x-object-meta-' ) === 0 ) {
+ $metadata[substr( $name, strlen( 'x-object-meta-' ) )] = $value;
+ }
+ }
+ // Fetch all of the custom raw HTTP headers
+ $headers = $this->sanitizeHdrs( array( 'headers' => $rhdrs ) );
+ $stat = array(
+ // Convert various random Swift dates to TS_MW
+ 'mtime' => $this->convertSwiftDate( $rhdrs['last-modified'], TS_MW ),
+ // Empty objects actually return no content-length header in Ceph
+ 'size' => isset( $rhdrs['content-length'] ) ? (int)$rhdrs['content-length'] : 0,
+ 'sha1' => $rhdrs['x-object-meta-sha1base36'],
+ // Note: manifiest ETags are not an MD5 of the file
+ 'md5' => ctype_xdigit( $rhdrs['etag'] ) ? $rhdrs['etag'] : null,
+ 'xattr' => array( 'metadata' => $metadata, 'headers' => $headers )
+ );
+ if ( $this->isRGW ) {
+ $stat['latest'] = true; // strong consistency
+ }
+ } elseif ( $rcode === 404 ) {
+ $stat = false;
+ } else {
+ $stat = null;
+ $this->onError( null, __METHOD__, $params, $rerr, $rcode, $rdesc );
+ }
+ $stats[$path] = $stat;
+ }
+
+ return $stats;
}
/**
- * Get a Swift container object, possibly from process cache.
- * Use $reCache if the file count or byte count is needed.
- *
- * @param string $container Container name
- * @param bool $bypassCache Bypass all caches and load from Swift
- * @return CF_Container
- * @throws CloudFilesException
+ * @return array|null Credential map
*/
- protected function getContainer( $container, $bypassCache = false ) {
- $conn = $this->getConnection(); // Swift proxy connection
- if ( $bypassCache ) { // purge cache
- $this->connContainerCache->clear( $container );
- } elseif ( !$this->connContainerCache->has( $container, 'obj' ) ) {
- $this->primeContainerCache( array( $container ) ); // check persistent cache
+ protected function getAuthentication() {
+ if ( $this->authErrorTimestamp !== null ) {
+ if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
+ return null; // failed last attempt; don't bother
+ } else { // actually retry this time
+ $this->authErrorTimestamp = null;
+ }
}
- if ( !$this->connContainerCache->has( $container, 'obj' ) ) {
- $contObj = $conn->get_container( $container );
- // NoSuchContainerException not thrown: container must exist
- $this->connContainerCache->set( $container, 'obj', $contObj ); // cache it
- if ( !$bypassCache ) {
- $this->setContainerCache( $container, // update persistent cache
- array( 'bytes' => $contObj->bytes_used, 'count' => $contObj->object_count )
- );
+ // Session keys expire after a while, so we renew them periodically
+ $reAuth = ( ( time() - $this->authSessionTimestamp ) > $this->authTTL );
+ // Authenticate with proxy and get a session key...
+ if ( !$this->authCreds || $reAuth ) {
+ $this->authSessionTimestamp = 0;
+ $cacheKey = $this->getCredsCacheKey( $this->swiftUser );
+ $creds = $this->srvCache->get( $cacheKey ); // credentials
+ // Try to use the credential cache
+ if ( isset( $creds['auth_token'] ) && isset( $creds['storage_url'] ) ) {
+ $this->authCreds = $creds;
+ // Skew the timestamp for worst case to avoid using stale credentials
+ $this->authSessionTimestamp = time() - ceil( $this->authTTL / 2 );
+ } else { // cache miss
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
+ 'method' => 'GET',
+ 'url' => "{$this->swiftAuthUrl}/v1.0",
+ 'headers' => array(
+ 'x-auth-user' => $this->swiftUser,
+ 'x-auth-key' => $this->swiftKey
+ )
+ ) );
+
+ if ( $rcode >= 200 && $rcode <= 299 ) { // OK
+ $this->authCreds = array(
+ 'auth_token' => $rhdrs['x-auth-token'],
+ 'storage_url' => $rhdrs['x-storage-url']
+ );
+ $this->srvCache->set( $cacheKey, $this->authCreds, ceil( $this->authTTL / 2 ) );
+ $this->authSessionTimestamp = time();
+ } elseif ( $rcode === 401 ) {
+ $this->onError( null, __METHOD__, array(), "Authentication failed.", $rcode );
+ $this->authErrorTimestamp = time();
+
+ return null;
+ } else {
+ $this->onError( null, __METHOD__, array(), "HTTP return code: $rcode", $rcode );
+ $this->authErrorTimestamp = time();
+
+ return null;
+ }
+ }
+ // Ceph RGW does not use <account> in URLs (OpenStack Swift uses "/v1/<account>")
+ if ( substr( $this->authCreds['storage_url'], -3 ) === '/v1' ) {
+ $this->isRGW = true; // take advantage of strong consistency
}
}
- return $this->connContainerCache->get( $container, 'obj' );
+
+ return $this->authCreds;
}
/**
- * Create a Swift container
- *
- * @param string $container Container name
- * @return CF_Container
- * @throws CloudFilesException
+ * @param array $creds From getAuthentication()
+ * @param string $container
+ * @param string $object
+ * @return array
*/
- protected function createContainer( $container ) {
- $conn = $this->getConnection(); // Swift proxy connection
- $contObj = $conn->create_container( $container );
- $this->connContainerCache->set( $container, 'obj', $contObj ); // cache
- return $contObj;
+ protected function storageUrl( array $creds, $container = null, $object = null ) {
+ $parts = array( $creds['storage_url'] );
+ if ( strlen( $container ) ) {
+ $parts[] = rawurlencode( $container );
+ }
+ if ( strlen( $object ) ) {
+ $parts[] = str_replace( "%2F", "/", rawurlencode( $object ) );
+ }
+
+ return implode( '/', $parts );
}
/**
- * Delete a Swift container
- *
- * @param string $container Container name
- * @return void
- * @throws CloudFilesException
+ * @param array $creds From getAuthentication()
+ * @return array
*/
- protected function deleteContainer( $container ) {
- $conn = $this->getConnection(); // Swift proxy connection
- $this->connContainerCache->clear( $container ); // purge
- $conn->delete_container( $container );
+ protected function authTokenHeaders( array $creds ) {
+ return array( 'x-auth-token' => $creds['auth_token'] );
}
- protected function doPrimeContainerCache( array $containerInfo ) {
- try {
- $conn = $this->getConnection(); // Swift proxy connection
- foreach ( $containerInfo as $container => $info ) {
- $contObj = new CF_Container( $conn->cfs_auth, $conn->cfs_http,
- $container, $info['count'], $info['bytes'] );
- $this->connContainerCache->set( $container, 'obj', $contObj );
- }
- } catch ( CloudFilesException $e ) { // some other exception?
- $this->handleException( $e, null, __METHOD__, array() );
- }
+ /**
+ * Get the cache key for a container
+ *
+ * @param string $username
+ * @return string
+ */
+ private function getCredsCacheKey( $username ) {
+ return 'swiftcredentials:' . md5( $username . ':' . $this->swiftAuthUrl );
}
/**
* Log an unexpected exception for this backend.
* This also sets the Status object to have a fatal error.
*
- * @param Exception $e
- * @param Status $status|null
+ * @param Status|null $status
* @param string $func
* @param array $params
- * @return void
+ * @param string $err Error string
+ * @param int $code HTTP status
+ * @param string $desc HTTP status description
*/
- protected function handleException( Exception $e, $status, $func, array $params ) {
+ public function onError( $status, $func, array $params, $err = '', $code = 0, $desc = '' ) {
if ( $status instanceof Status ) {
- if ( $e instanceof AuthenticationException ) {
- $status->fatal( 'backend-fail-connect', $this->name );
- } else {
- $status->fatal( 'backend-fail-internal', $this->name );
- }
- }
- if ( $e->getMessage() ) {
- trigger_error( "$func: " . $e->getMessage(), E_USER_WARNING );
+ $status->fatal( 'backend-fail-internal', $this->name );
}
- if ( $e instanceof InvalidResponseException ) { // possibly a stale token
- $this->srvCache->delete( $this->getCredsCacheKey( $this->auth->username ) );
- $this->closeConnection(); // force a re-connect and re-auth next time
+ if ( $code == 401 ) { // possibly a stale token
+ $this->srvCache->delete( $this->getCredsCacheKey( $this->swiftUser ) );
}
wfDebugLog( 'SwiftBackend',
- get_class( $e ) . " in '{$func}' (given '" . FormatJson::encode( $params ) . "')" .
- ( $e->getMessage() ? ": {$e->getMessage()}" : "" )
+ "HTTP $code ($desc) in '{$func}' (given '" . FormatJson::encode( $params ) . "')" .
+ ( $err ? ": $err" : "" )
);
}
}
@@ -1527,24 +1667,20 @@ class SwiftFileBackend extends FileBackendStore {
* @see FileBackendStoreOpHandle
*/
class SwiftFileOpHandle extends FileBackendStoreOpHandle {
- /** @var CF_Async_Op */
- public $cfOp;
- /** @var Array */
- public $affectedObjects = array();
+ /** @var array List of Requests for MultiHttpClient */
+ public $httpOp;
+ /** @var Closure */
+ public $callback;
/**
* @param SwiftFileBackend $backend
- * @param array $params
- * @param string $call
- * @param CF_Async_Op $cfOp
+ * @param Closure $callback Function that takes (HTTP request array, status)
+ * @param array $httpOp MultiHttpClient op
*/
- public function __construct(
- SwiftFileBackend $backend, array $params, $call, CF_Async_Op $cfOp
- ) {
+ public function __construct( SwiftFileBackend $backend, Closure $callback, array $httpOp ) {
$this->backend = $backend;
- $this->params = $params;
- $this->call = $call;
- $this->cfOp = $cfOp;
+ $this->callback = $callback;
+ $this->httpOp = $httpOp;
}
}
@@ -1556,18 +1692,29 @@ class SwiftFileOpHandle extends FileBackendStoreOpHandle {
* @ingroup FileBackend
*/
abstract class SwiftFileBackendList implements Iterator {
- /** @var Array */
+ /** @var array List of path or (path,stat array) entries */
protected $bufferIter = array();
- protected $bufferAfter = null; // string; list items *after* this path
- protected $pos = 0; // integer
- /** @var Array */
+
+ /** @var string List items *after* this path */
+ protected $bufferAfter = null;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var array */
protected $params = array();
/** @var SwiftFileBackend */
protected $backend;
- protected $container; // string; container name
- protected $dir; // string; storage directory
- protected $suffixStart; // integer
+
+ /** @var string Container name */
+ protected $container;
+
+ /** @var string Storage directory */
+ protected $dir;
+
+ /** @var int */
+ protected $suffixStart;
const PAGE_SIZE = 9000; // file listing buffer size
@@ -1594,7 +1741,7 @@ abstract class SwiftFileBackendList implements Iterator {
/**
* @see Iterator::key()
- * @return integer
+ * @return int
*/
public function key() {
return $this->pos;
@@ -1602,7 +1749,6 @@ abstract class SwiftFileBackendList implements Iterator {
/**
* @see Iterator::next()
- * @return void
*/
public function next() {
// Advance to the next file in the page
@@ -1619,7 +1765,6 @@ abstract class SwiftFileBackendList implements Iterator {
/**
* @see Iterator::rewind()
- * @return void
*/
public function rewind() {
$this->pos = 0;
@@ -1646,10 +1791,10 @@ abstract class SwiftFileBackendList implements Iterator {
*
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
- * @param string $after|null
- * @param integer $limit
+ * @param string $after
+ * @param int $limit
* @param array $params
- * @return Traversable|Array
+ * @return Traversable|array
*/
abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
}
@@ -1666,10 +1811,6 @@ class SwiftFileBackendDirList extends SwiftFileBackendList {
return substr( current( $this->bufferIter ), $this->suffixStart, -1 );
}
- /**
- * @see SwiftFileBackendList::pageFromList()
- * @return Array
- */
protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
}
@@ -1684,13 +1825,16 @@ class SwiftFileBackendFileList extends SwiftFileBackendList {
* @return string|bool String (relative path) or false
*/
public function current() {
- return substr( current( $this->bufferIter ), $this->suffixStart );
+ 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;
}
- /**
- * @see SwiftFileBackendList::pageFromList()
- * @return Array
- */
protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
}
diff --git a/includes/filebackend/TempFSFile.php b/includes/filebackend/TempFSFile.php
index 8266e420..1b68130f 100644
--- a/includes/filebackend/TempFSFile.php
+++ b/includes/filebackend/TempFSFile.php
@@ -28,11 +28,24 @@
* @ingroup FileBackend
*/
class TempFSFile extends FSFile {
- protected $canDelete = false; // bool; garbage collect the temp file
+ /** @var bool Garbage collect the temp file */
+ protected $canDelete = false;
- /** @var Array of active temp files to purge on shutdown */
+ /** @var array Active temp files to purge on shutdown */
protected static $instances = array();
+ /** @var array Map of (path => 1) for paths to delete on shutdown */
+ protected static $pathsCollect = null;
+
+ public function __construct( $path ) {
+ parent::__construct( $path );
+
+ if ( self::$pathsCollect === null ) {
+ self::$pathsCollect = array();
+ register_shutdown_function( array( __CLASS__, 'purgeAllOnShutdown' ) );
+ }
+ }
+
/**
* Make a new temporary file on the file system.
* Temporary files may be purged when the file object falls out of scope.
@@ -56,12 +69,14 @@ class TempFSFile extends FSFile {
}
if ( $attempt >= 5 ) {
wfProfileOut( __METHOD__ );
+
return null; // give up
}
}
$tmpFile = new self( $path );
- $tmpFile->canDelete = true; // safely instantiated
+ $tmpFile->autocollect(); // safely instantiated
wfProfileOut( __METHOD__ );
+
return $tmpFile;
}
@@ -75,13 +90,16 @@ class TempFSFile extends FSFile {
wfSuppressWarnings();
$ok = unlink( $this->path );
wfRestoreWarnings();
+
+ unset( self::$pathsCollect[$this->path] );
+
return $ok;
}
/**
* Clean up the temporary file only after an object goes out of scope
*
- * @param Object $object
+ * @param stdClass $object
* @return TempFSFile This object
*/
public function bind( $object ) {
@@ -92,6 +110,7 @@ class TempFSFile extends FSFile {
}
$object->tempFSFileReferences[] = $this;
}
+
return $this;
}
@@ -102,6 +121,9 @@ class TempFSFile extends FSFile {
*/
public function preserve() {
$this->canDelete = false;
+
+ unset( self::$pathsCollect[$this->path] );
+
return $this;
}
@@ -112,17 +134,31 @@ class TempFSFile extends FSFile {
*/
public function autocollect() {
$this->canDelete = true;
+
+ self::$pathsCollect[$this->path] = 1;
+
return $this;
}
/**
+ * Try to make sure that all files are purged on error
+ *
+ * This method should only be called internally
+ */
+ public static function purgeAllOnShutdown() {
+ foreach ( self::$pathsCollect as $path ) {
+ wfSuppressWarnings();
+ unlink( $path );
+ wfRestoreWarnings();
+ }
+ }
+
+ /**
* Cleans up after the temporary file by deleting it
*/
function __destruct() {
if ( $this->canDelete ) {
- wfSuppressWarnings();
- unlink( $this->path );
- wfRestoreWarnings();
+ $this->purge();
}
}
}
diff --git a/includes/filebackend/filejournal/DBFileJournal.php b/includes/filebackend/filejournal/DBFileJournal.php
index 9250aa5e..4f64f022 100644
--- a/includes/filebackend/filejournal/DBFileJournal.php
+++ b/includes/filebackend/filejournal/DBFileJournal.php
@@ -34,10 +34,9 @@ class DBFileJournal extends FileJournal {
/**
* Construct a new instance from configuration.
- * $config includes:
- * 'wiki' : wiki name to use for LoadBalancer
*
- * @param $config Array
+ * @param array $config Includes:
+ * 'wiki' : wiki name to use for LoadBalancer
*/
protected function __construct( array $config ) {
parent::__construct( $config );
@@ -47,6 +46,8 @@ class DBFileJournal extends FileJournal {
/**
* @see FileJournal::logChangeBatch()
+ * @param array $entries
+ * @param string $batchId
* @return Status
*/
protected function doLogChangeBatch( array $entries, $batchId ) {
@@ -56,6 +57,7 @@ class DBFileJournal extends FileJournal {
$dbw = $this->getMasterDB();
} catch ( DBError $e ) {
$status->fatal( 'filejournal-fail-dbconnect', $this->backend );
+
return $status;
}
@@ -80,6 +82,7 @@ class DBFileJournal extends FileJournal {
}
} catch ( DBError $e ) {
$status->fatal( 'filejournal-fail-dbquery', $this->backend );
+
return $status;
}
@@ -88,7 +91,7 @@ class DBFileJournal extends FileJournal {
/**
* @see FileJournal::doGetCurrentPosition()
- * @return integer|false
+ * @return bool|mixed The value from the field, or false on failure.
*/
protected function doGetCurrentPosition() {
$dbw = $this->getMasterDB();
@@ -101,13 +104,14 @@ class DBFileJournal extends FileJournal {
/**
* @see FileJournal::doGetPositionAtTime()
- * @param $time integer|string timestamp
- * @return integer|false
+ * @param int|string $time Timestamp
+ * @return bool|mixed The value from the field, or false on failure.
*/
protected function doGetPositionAtTime( $time ) {
$dbw = $this->getMasterDB();
$encTimestamp = $dbw->addQuotes( $dbw->timestamp( $time ) );
+
return $dbw->selectField( 'filejournal', 'fj_id',
array( 'fj_backend' => $this->backend, "fj_timestamp <= $encTimestamp" ),
__METHOD__,
@@ -117,8 +121,9 @@ class DBFileJournal extends FileJournal {
/**
* @see FileJournal::doGetChangeEntries()
- * @return Array
- * @throws DBError
+ * @param int $start
+ * @param int $limit
+ * @return array
*/
protected function doGetChangeEntries( $start, $limit ) {
$dbw = $this->getMasterDB();
@@ -179,6 +184,7 @@ class DBFileJournal extends FileJournal {
$this->dbw = $lb->getConnection( DB_MASTER, array(), $this->wiki );
$this->dbw->clearFlag( DBO_TRX );
}
+
return $this->dbw;
}
}
diff --git a/includes/filebackend/filejournal/FileJournal.php b/includes/filebackend/filejournal/FileJournal.php
index a1b7a459..c0651485 100644
--- a/includes/filebackend/filejournal/FileJournal.php
+++ b/includes/filebackend/filejournal/FileJournal.php
@@ -36,15 +36,17 @@
* @since 1.20
*/
abstract class FileJournal {
- protected $backend; // string
- protected $ttlDays; // integer
+ /** @var string */
+ protected $backend;
+
+ /** @var int */
+ protected $ttlDays;
/**
* Construct a new instance from configuration.
- * $config includes:
- * 'ttlDays' : days to keep log entries around (false means "forever")
*
- * @param $config Array
+ * @param array $config Includes:
+ * 'ttlDays' : days to keep log entries around (false means "forever")
*/
protected function __construct( array $config ) {
$this->ttlDays = isset( $config['ttlDays'] ) ? $config['ttlDays'] : false;
@@ -53,7 +55,7 @@ abstract class FileJournal {
/**
* Create an appropriate FileJournal object from config
*
- * @param $config Array
+ * @param array $config
* @param string $backend A registered file backend name
* @throws MWException
* @return FileJournal
@@ -65,6 +67,7 @@ abstract class FileJournal {
throw new MWException( "Class given is not an instance of FileJournal." );
}
$jrn->backend = $backend;
+
return $jrn;
}
@@ -79,18 +82,18 @@ abstract class FileJournal {
$s .= mt_rand( 0, 2147483647 );
}
$s = wfBaseConvert( sha1( $s ), 16, 36, 31 );
+
return substr( wfBaseConvert( wfTimestamp( TS_MW ), 10, 36, 9 ) . $s, 0, 31 );
}
/**
* Log changes made by a batch file operation.
- * $entries is an array of log entries, each of which contains:
+ *
+ * @param array $entries List of file operations (each an array of parameters) which contain:
* op : Basic operation name (create, update, delete)
* path : The storage path of the file
* newSha1 : The final base 36 SHA-1 of the file
- * Note that 'false' should be used as the SHA-1 for non-existing files.
- *
- * @param array $entries List of file operations (each an array of parameters)
+ * Note that 'false' should be used as the SHA-1 for non-existing files.
* @param string $batchId UUID string that identifies the operation batch
* @return Status
*/
@@ -98,6 +101,7 @@ abstract class FileJournal {
if ( !count( $entries ) ) {
return Status::newGood();
}
+
return $this->doLogChangeBatch( $entries, $batchId );
}
@@ -113,7 +117,7 @@ abstract class FileJournal {
/**
* Get the position ID of the latest journal entry
*
- * @return integer|false
+ * @return int|bool
*/
final public function getCurrentPosition() {
return $this->doGetCurrentPosition();
@@ -121,15 +125,15 @@ abstract class FileJournal {
/**
* @see FileJournal::getCurrentPosition()
- * @return integer|false
+ * @return int|bool
*/
abstract protected function doGetCurrentPosition();
/**
* Get the position ID of the latest journal entry at some point in time
*
- * @param $time integer|string timestamp
- * @return integer|false
+ * @param int|string $time Timestamp
+ * @return int|bool
*/
final public function getPositionAtTime( $time ) {
return $this->doGetPositionAtTime( $time );
@@ -137,8 +141,8 @@ abstract class FileJournal {
/**
* @see FileJournal::getPositionAtTime()
- * @param $time integer|string timestamp
- * @return integer|false
+ * @param int|string $time Timestamp
+ * @return int|bool
*/
abstract protected function doGetPositionAtTime( $time );
@@ -146,7 +150,10 @@ abstract class FileJournal {
* Get an array of file change log entries.
* A starting change ID and/or limit can be specified.
*
- * The result as a list of associative arrays, each having:
+ * @param int $start Starting change ID or null
+ * @param int $limit Maximum number of items to return
+ * @param string &$next Updated to the ID of the next entry.
+ * @return array List of associative arrays, each having:
* id : unique, monotonic, ID for this change
* batch_uuid : UUID for an operation batch
* backend : the backend name
@@ -154,13 +161,7 @@ abstract class FileJournal {
* path : affected storage path
* new_sha1 : base 36 sha1 of the new file had the operation succeeded
* timestamp : TS_MW timestamp of the batch change
-
- * Also, $next is updated to the ID of the next entry.
- *
- * @param $start integer Starting change ID or null
- * @param $limit integer Maximum number of items to return
- * @param &$next string
- * @return Array
+ * Also, $next is updated to the ID of the next entry.
*/
final public function getChangeEntries( $start = null, $limit = 0, &$next = null ) {
$entries = $this->doGetChangeEntries( $start, $limit ? $limit + 1 : 0 );
@@ -170,12 +171,15 @@ abstract class FileJournal {
} else {
$next = null; // end of list
}
+
return $entries;
}
/**
* @see FileJournal::getChangeEntries()
- * @return Array
+ * @param int $start
+ * @param int $limit
+ * @return array
*/
abstract protected function doGetChangeEntries( $start, $limit );
@@ -202,8 +206,8 @@ abstract class FileJournal {
class NullFileJournal extends FileJournal {
/**
* @see FileJournal::doLogChangeBatch()
- * @param $entries array
- * @param $batchId string
+ * @param array $entries
+ * @param string $batchId
* @return Status
*/
protected function doLogChangeBatch( array $entries, $batchId ) {
@@ -212,7 +216,7 @@ class NullFileJournal extends FileJournal {
/**
* @see FileJournal::doGetCurrentPosition()
- * @return integer|false
+ * @return int|bool
*/
protected function doGetCurrentPosition() {
return false;
@@ -220,8 +224,8 @@ class NullFileJournal extends FileJournal {
/**
* @see FileJournal::doGetPositionAtTime()
- * @param $time integer|string timestamp
- * @return integer|false
+ * @param int|string $time Timestamp
+ * @return int|bool
*/
protected function doGetPositionAtTime( $time ) {
return false;
@@ -229,7 +233,9 @@ class NullFileJournal extends FileJournal {
/**
* @see FileJournal::doGetChangeEntries()
- * @return Array
+ * @param int $start
+ * @param int $limit
+ * @return array
*/
protected function doGetChangeEntries( $start, $limit ) {
return array();
diff --git a/includes/filebackend/lockmanager/DBLockManager.php b/includes/filebackend/lockmanager/DBLockManager.php
index 3e934ba5..450ccc82 100644
--- a/includes/filebackend/lockmanager/DBLockManager.php
+++ b/includes/filebackend/lockmanager/DBLockManager.php
@@ -37,7 +37,7 @@
* @since 1.19
*/
abstract class DBLockManager extends QuorumLockManager {
- /** @var Array Map of DB names to server config */
+ /** @var array Map of DB names to server config */
protected $dbServers; // (DB name => server config array)
/** @var BagOStuff */
protected $statusCache;
@@ -46,13 +46,13 @@ abstract class DBLockManager extends QuorumLockManager {
protected $safeDelay; // integer number of seconds
protected $session = 0; // random integer
- /** @var Array Map Database connections (DB name => Database) */
+ /** @var array Map Database connections (DB name => Database) */
protected $conns = array();
/**
* Construct a new instance from configuration.
*
- * $config paramaters include:
+ * @param array $config Paramaters include:
* - dbServers : Associative array of DB names to server configuration.
* Configuration is an associative array that includes:
* - host : DB server name
@@ -70,8 +70,6 @@ abstract class DBLockManager extends QuorumLockManager {
* - lockExpiry : Lock timeout (seconds) for dropped connections. [optional]
* This tells the DB server how long to wait before assuming
* connection failure and releasing all the locks for a session.
- *
- * @param array $config
*/
public function __construct( array $config ) {
parent::__construct( $config );
@@ -110,12 +108,13 @@ abstract class DBLockManager extends QuorumLockManager {
$this->session = wfRandomString( 31 );
}
- // @TODO: change this code to work in one batch
+ // @todo change this code to work in one batch
protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
$status = Status::newGood();
foreach ( $pathsByType as $type => $paths ) {
$status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
}
+
return $status;
}
@@ -125,6 +124,7 @@ abstract class DBLockManager extends QuorumLockManager {
/**
* @see QuorumLockManager::isServerUp()
+ * @param string $lockSrv
* @return bool
*/
protected function isServerUp( $lockSrv ) {
@@ -135,15 +135,17 @@ abstract class DBLockManager extends QuorumLockManager {
$this->getConnection( $lockSrv );
} catch ( DBError $e ) {
$this->cacheRecordFailure( $lockSrv );
+
return false; // failed to connect
}
+
return true;
}
/**
* Get (or reuse) a connection to a lock DB
*
- * @param $lockDb string
+ * @param string $lockDb
* @return DatabaseBase
* @throws DBError
*/
@@ -175,24 +177,25 @@ abstract class DBLockManager extends QuorumLockManager {
if ( !$this->conns[$lockDb]->trxLevel() ) {
$this->conns[$lockDb]->begin( __METHOD__ ); // start transaction
}
+
return $this->conns[$lockDb];
}
/**
* Do additional initialization for new lock DB connection
*
- * @param $lockDb string
- * @param $db DatabaseBase
- * @return void
+ * @param string $lockDb
+ * @param DatabaseBase $db
* @throws DBError
*/
- protected function initConnection( $lockDb, DatabaseBase $db ) {}
+ protected function initConnection( $lockDb, DatabaseBase $db ) {
+ }
/**
* Checks if the DB has not recently had connection/query errors.
* This just avoids wasting time on doomed connection attempts.
*
- * @param $lockDb string
+ * @param string $lockDb
* @return bool
*/
protected function cacheCheckFailures( $lockDb ) {
@@ -204,7 +207,7 @@ abstract class DBLockManager extends QuorumLockManager {
/**
* Log a lock request failure to the cache
*
- * @param $lockDb string
+ * @param string $lockDb
* @return bool Success
*/
protected function cacheRecordFailure( $lockDb ) {
@@ -216,7 +219,7 @@ abstract class DBLockManager extends QuorumLockManager {
/**
* Get a cache key for recent query misses for a DB
*
- * @param $lockDb string
+ * @param string $lockDb
* @return string
*/
protected function getMissKey( $lockDb ) {
@@ -242,7 +245,7 @@ abstract class DBLockManager extends QuorumLockManager {
* @ingroup LockManager
*/
class MySqlLockManager extends DBLockManager {
- /** @var Array Mapping of lock types to the type actually used */
+ /** @var array Mapping of lock types to the type actually used */
protected $lockTypeMap = array(
self::LOCK_SH => self::LOCK_SH,
self::LOCK_UW => self::LOCK_SH,
@@ -250,8 +253,8 @@ class MySqlLockManager extends DBLockManager {
);
/**
- * @param $lockDb string
- * @param $db DatabaseBase
+ * @param string $lockDb
+ * @param DatabaseBase $db
*/
protected function initConnection( $lockDb, DatabaseBase $db ) {
# Let this transaction see lock rows from other transactions
@@ -263,6 +266,9 @@ class MySqlLockManager extends DBLockManager {
* This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
*
* @see DBLockManager::getLocksOnServer()
+ * @param string $lockSrv
+ * @param array $paths
+ * @param string $type
* @return Status
*/
protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
@@ -359,7 +365,7 @@ class MySqlLockManager extends DBLockManager {
* @ingroup LockManager
*/
class PostgreSqlLockManager extends DBLockManager {
- /** @var Array Mapping of lock types to the type actually used */
+ /** @var array Mapping of lock types to the type actually used */
protected $lockTypeMap = array(
self::LOCK_SH => self::LOCK_SH,
self::LOCK_UW => self::LOCK_SH,
@@ -374,7 +380,7 @@ class PostgreSqlLockManager extends DBLockManager {
$db = $this->getConnection( $lockSrv ); // checked in isServerUp()
$bigints = array_unique( array_map(
- function( $key ) {
+ function ( $key ) {
return wfBaseConvert( substr( $key, 0, 15 ), 16, 10 );
},
array_map( array( $this, 'sha1Base16Absolute' ), $paths )
diff --git a/includes/filebackend/lockmanager/FSLockManager.php b/includes/filebackend/lockmanager/FSLockManager.php
index eacba704..bce6b34c 100644
--- a/includes/filebackend/lockmanager/FSLockManager.php
+++ b/includes/filebackend/lockmanager/FSLockManager.php
@@ -34,7 +34,7 @@
* @since 1.19
*/
class FSLockManager extends LockManager {
- /** @var Array Mapping of lock types to the type actually used */
+ /** @var array Mapping of lock types to the type actually used */
protected $lockTypeMap = array(
self::LOCK_SH => self::LOCK_SH,
self::LOCK_UW => self::LOCK_SH,
@@ -43,16 +43,14 @@ class FSLockManager extends LockManager {
protected $lockDir; // global dir for all servers
- /** @var Array Map of (locked key => lock file handle) */
+ /** @var array Map of (locked key => lock file handle) */
protected $handles = array();
/**
* Construct a new instance from configuration.
*
- * $config includes:
+ * @param array $config Includes:
* - lockDirectory : Directory containing the lock files
- *
- * @param array $config
*/
function __construct( array $config ) {
parent::__construct( $config );
@@ -62,8 +60,8 @@ class FSLockManager extends LockManager {
/**
* @see LockManager::doLock()
- * @param $paths array
- * @param $type int
+ * @param array $paths
+ * @param int $type
* @return Status
*/
protected function doLock( array $paths, $type ) {
@@ -77,6 +75,7 @@ class FSLockManager extends LockManager {
} else {
// Abort and unlock everything
$status->merge( $this->doUnlock( $lockedPaths, $type ) );
+
return $status;
}
}
@@ -86,8 +85,8 @@ class FSLockManager extends LockManager {
/**
* @see LockManager::doUnlock()
- * @param $paths array
- * @param $type int
+ * @param array $paths
+ * @param int $type
* @return Status
*/
protected function doUnlock( array $paths, $type ) {
@@ -103,8 +102,8 @@ class FSLockManager extends LockManager {
/**
* Lock a single resource key
*
- * @param $path string
- * @param $type integer
+ * @param string $path
+ * @param int $type
* @return Status
*/
protected function doSingleLock( $path, $type ) {
@@ -148,8 +147,8 @@ class FSLockManager extends LockManager {
/**
* Unlock a single resource key
*
- * @param $path string
- * @param $type integer
+ * @param string $path
+ * @param int $type
* @return Status
*/
protected function doSingleUnlock( $path, $type ) {
@@ -191,8 +190,8 @@ class FSLockManager extends LockManager {
}
/**
- * @param $path string
- * @param $handlesToClose array
+ * @param string $path
+ * @param array $handlesToClose
* @return Status
*/
private function closeLockHandles( $path, array $handlesToClose ) {
@@ -205,11 +204,12 @@ class FSLockManager extends LockManager {
$status->warning( 'lockmanager-fail-closelock', $path );
}
}
+
return $status;
}
/**
- * @param $path string
+ * @param string $path
* @return Status
*/
private function pruneKeyLockFiles( $path ) {
@@ -221,12 +221,13 @@ class FSLockManager extends LockManager {
}
unset( $this->handles[$path] );
}
+
return $status;
}
/**
* Get the path to the lock file for a key
- * @param $path string
+ * @param string $path
* @return string
*/
protected function getLockPath( $path ) {
diff --git a/includes/filebackend/lockmanager/LSLockManager.php b/includes/filebackend/lockmanager/LSLockManager.php
deleted file mode 100644
index 97de8dca..00000000
--- a/includes/filebackend/lockmanager/LSLockManager.php
+++ /dev/null
@@ -1,218 +0,0 @@
-<?php
-/**
- * Version of LockManager based on using lock daemon servers.
- *
- * 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 LockManager
- */
-
-/**
- * Manage locks using a lock daemon server.
- *
- * Version of LockManager based on using lock daemon servers.
- * This is meant for multi-wiki systems that may share files.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * All lock requests for a resource, identified by a hash string, will map
- * to one bucket. Each bucket maps to one or several peer servers, each
- * running LockServerDaemon.php, listening on a designated TCP port.
- * A majority of peers must agree for a lock to be acquired.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class LSLockManager extends QuorumLockManager {
- /** @var Array Mapping of lock types to the type actually used */
- protected $lockTypeMap = array(
- self::LOCK_SH => self::LOCK_SH,
- self::LOCK_UW => self::LOCK_SH,
- self::LOCK_EX => self::LOCK_EX
- );
-
- /** @var Array Map of server names to server config */
- protected $lockServers; // (server name => server config array)
-
- /** @var Array Map Server connections (server name => resource) */
- protected $conns = array();
-
- protected $connTimeout; // float number of seconds
- protected $session = ''; // random SHA-1 string
-
- /**
- * Construct a new instance from configuration.
- *
- * $config paramaters include:
- * - lockServers : Associative array of server names to configuration.
- * Configuration is an associative array that includes:
- * - host : IP address/hostname
- * - port : TCP port
- * - authKey : Secret string the lock server uses
- * - srvsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
- * each having an odd-numbered list of server names (peers) as values.
- * - connTimeout : Lock server connection attempt timeout. [optional]
- *
- * @param array $config
- */
- public function __construct( array $config ) {
- parent::__construct( $config );
-
- $this->lockServers = $config['lockServers'];
- // Sanitize srvsByBucket config to prevent PHP errors
- $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
- $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
-
- if ( isset( $config['connTimeout'] ) ) {
- $this->connTimeout = $config['connTimeout'];
- } else {
- $this->connTimeout = 3; // use some sane amount
- }
-
- $this->session = wfRandomString( 32 ); // 128 bits
- }
-
- /**
- * @see QuorumLockManager::getLocksOnServer()
- * @return Status
- */
- protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
- $status = Status::newGood();
-
- // Send out the command and get the response...
- $type = ( $type == self::LOCK_SH ) ? 'SH' : 'EX';
- $keys = array_unique( array_map( array( $this, 'sha1Base36Absolute' ), $paths ) );
- $response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, $keys );
-
- if ( $response !== 'ACQUIRED' ) {
- foreach ( $paths as $path ) {
- $status->fatal( 'lockmanager-fail-acquirelock', $path );
- }
- }
-
- return $status;
- }
-
- /**
- * @see QuorumLockManager::freeLocksOnServer()
- * @return Status
- */
- protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
- $status = Status::newGood();
-
- // Send out the command and get the response...
- $type = ( $type == self::LOCK_SH ) ? 'SH' : 'EX';
- $keys = array_unique( array_map( array( $this, 'sha1Base36Absolute' ), $paths ) );
- $response = $this->sendCommand( $lockSrv, 'RELEASE', $type, $keys );
-
- if ( $response !== 'RELEASED' ) {
- foreach ( $paths as $path ) {
- $status->fatal( 'lockmanager-fail-releaselock', $path );
- }
- }
-
- return $status;
- }
-
- /**
- * @see QuorumLockManager::releaseAllLocks()
- * @return Status
- */
- protected function releaseAllLocks() {
- $status = Status::newGood();
-
- foreach ( $this->conns as $lockSrv => $conn ) {
- $response = $this->sendCommand( $lockSrv, 'RELEASE_ALL', '', array() );
- if ( $response !== 'RELEASED_ALL' ) {
- $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
- }
- }
-
- return $status;
- }
-
- /**
- * @see QuorumLockManager::isServerUp()
- * @return bool
- */
- protected function isServerUp( $lockSrv ) {
- return (bool)$this->getConnection( $lockSrv );
- }
-
- /**
- * Send a command and get back the response
- *
- * @param $lockSrv string
- * @param $action string
- * @param $type string
- * @param $values Array
- * @return string|bool
- */
- protected function sendCommand( $lockSrv, $action, $type, $values ) {
- $conn = $this->getConnection( $lockSrv );
- if ( !$conn ) {
- return false; // no connection
- }
- $authKey = $this->lockServers[$lockSrv]['authKey'];
- // Build of the command as a flat string...
- $values = implode( '|', $values );
- $key = hash_hmac( 'sha1', "{$this->session}\n{$action}\n{$type}\n{$values}", $authKey );
- // Send out the command...
- if ( fwrite( $conn, "{$this->session}:$key:$action:$type:$values\n" ) === false ) {
- return false;
- }
- // Get the response...
- $response = fgets( $conn );
- if ( $response === false ) {
- return false;
- }
- return trim( $response );
- }
-
- /**
- * Get (or reuse) a connection to a lock server
- *
- * @param $lockSrv string
- * @return resource
- */
- protected function getConnection( $lockSrv ) {
- if ( !isset( $this->conns[$lockSrv] ) ) {
- $cfg = $this->lockServers[$lockSrv];
- wfSuppressWarnings();
- $errno = $errstr = '';
- $conn = fsockopen( $cfg['host'], $cfg['port'], $errno, $errstr, $this->connTimeout );
- wfRestoreWarnings();
- if ( $conn === false ) {
- return null;
- }
- $sec = floor( $this->connTimeout );
- $usec = floor( ( $this->connTimeout - floor( $this->connTimeout ) ) * 1e6 );
- stream_set_timeout( $conn, $sec, $usec );
- $this->conns[$lockSrv] = $conn;
- }
- return $this->conns[$lockSrv];
- }
-
- /**
- * Make sure remaining locks get cleared for sanity
- */
- function __destruct() {
- $this->releaseAllLocks();
- foreach ( $this->conns as $conn ) {
- fclose( $conn );
- }
- }
-}
diff --git a/includes/filebackend/lockmanager/LockManager.php b/includes/filebackend/lockmanager/LockManager.php
index dad8a624..df8d2d4f 100644
--- a/includes/filebackend/lockmanager/LockManager.php
+++ b/includes/filebackend/lockmanager/LockManager.php
@@ -43,14 +43,14 @@
* @since 1.19
*/
abstract class LockManager {
- /** @var Array Mapping of lock types to the type actually used */
+ /** @var array Mapping of lock types to the type actually used */
protected $lockTypeMap = array(
self::LOCK_SH => self::LOCK_SH,
self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
self::LOCK_EX => self::LOCK_EX
);
- /** @var Array Map of (resource path => lock type => count) */
+ /** @var array Map of (resource path => lock type => count) */
protected $locksHeld = array();
protected $domain; // string; domain (usually wiki ID)
@@ -64,12 +64,10 @@ abstract class LockManager {
/**
* Construct a new instance from configuration
*
- * $config paramaters include:
+ * @param array $config Paramaters include:
* - domain : Domain (usually wiki ID) that all resources are relative to [optional]
* - lockTTL : Age (in seconds) at which resource locks should expire.
* This only applies if locks are not tied to a connection/process.
- *
- * @param $config Array
*/
public function __construct( array $config ) {
$this->domain = isset( $config['domain'] ) ? $config['domain'] : wfWikiID();
@@ -87,8 +85,8 @@ abstract class LockManager {
* Lock the resources at the given abstract paths
*
* @param array $paths List of resource names
- * @param $type integer LockManager::LOCK_* constant
- * @param integer $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
+ * @param int $type LockManager::LOCK_* constant
+ * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
* @return Status
*/
final public function lock( array $paths, $type = self::LOCK_EX, $timeout = 0 ) {
@@ -99,7 +97,7 @@ abstract class LockManager {
* Lock the resources at the given abstract paths
*
* @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
- * @param integer $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
+ * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
* @return Status
* @since 1.22
*/
@@ -119,6 +117,7 @@ abstract class LockManager {
$elapsed = microtime( true ) - $start;
} while ( $elapsed < $timeout && $elapsed >= 0 );
wfProfileOut( __METHOD__ );
+
return $status;
}
@@ -126,7 +125,7 @@ abstract class LockManager {
* Unlock the resources at the given abstract paths
*
* @param array $paths List of paths
- * @param $type integer LockManager::LOCK_* constant
+ * @param int $type LockManager::LOCK_* constant
* @return Status
*/
final public function unlock( array $paths, $type = self::LOCK_EX ) {
@@ -145,6 +144,7 @@ abstract class LockManager {
$pathsByType = $this->normalizePathsByType( $pathsByType );
$status = $this->doUnlockByType( $pathsByType );
wfProfileOut( __METHOD__ );
+
return $status;
}
@@ -153,7 +153,7 @@ abstract class LockManager {
* Before hashing, the path will be prefixed with the domain ID.
* This should be used interally for lock key or file names.
*
- * @param $path string
+ * @param string $path
* @return string
*/
final protected function sha1Base36Absolute( $path ) {
@@ -165,7 +165,7 @@ abstract class LockManager {
* Before hashing, the path will be prefixed with the domain ID.
* This should be used interally for lock key or file names.
*
- * @param $path string
+ * @param string $path
* @return string
*/
final protected function sha1Base16Absolute( $path ) {
@@ -176,8 +176,8 @@ abstract class LockManager {
* Normalize the $paths array by converting LOCK_UW locks into the
* appropriate type and removing any duplicated paths for each lock type.
*
- * @param array $paths Map of LockManager::LOCK_* constants to lists of paths
- * @return Array
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+ * @return array
* @since 1.22
*/
final protected function normalizePathsByType( array $pathsByType ) {
@@ -185,12 +185,13 @@ abstract class LockManager {
foreach ( $pathsByType as $type => $paths ) {
$res[$this->lockTypeMap[$type]] = array_unique( $paths );
}
+
return $res;
}
/**
* @see LockManager::lockByType()
- * @param array $paths Map of LockManager::LOCK_* constants to lists of paths
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
* @since 1.22
*/
@@ -203,12 +204,13 @@ abstract class LockManager {
$lockedByType[$type] = $paths;
} else {
// Release the subset of locks that were acquired
- foreach ( $lockedByType as $type => $paths ) {
- $status->merge( $this->doUnlock( $paths, $type ) );
+ foreach ( $lockedByType as $lType => $lPaths ) {
+ $status->merge( $this->doUnlock( $lPaths, $lType ) );
}
break;
}
}
+
return $status;
}
@@ -216,14 +218,14 @@ abstract class LockManager {
* Lock resources with the given keys and lock type
*
* @param array $paths List of paths
- * @param $type integer LockManager::LOCK_* constant
+ * @param int $type LockManager::LOCK_* constant
* @return Status
*/
abstract protected function doLock( array $paths, $type );
/**
* @see LockManager::unlockByType()
- * @param array $paths Map of LockManager::LOCK_* constants to lists of paths
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
* @since 1.22
*/
@@ -232,6 +234,7 @@ abstract class LockManager {
foreach ( $pathsByType as $type => $paths ) {
$status->merge( $this->doUnlock( $paths, $type ) );
}
+
return $status;
}
@@ -239,7 +242,7 @@ abstract class LockManager {
* Unlock resources with the given keys and lock type
*
* @param array $paths List of paths
- * @param $type integer LockManager::LOCK_* constant
+ * @param int $type LockManager::LOCK_* constant
* @return Status
*/
abstract protected function doUnlock( array $paths, $type );
diff --git a/includes/filebackend/lockmanager/LockManagerGroup.php b/includes/filebackend/lockmanager/LockManagerGroup.php
index 9aff2415..19fc4fef 100644
--- a/includes/filebackend/lockmanager/LockManagerGroup.php
+++ b/includes/filebackend/lockmanager/LockManagerGroup.php
@@ -29,12 +29,12 @@
* @since 1.19
*/
class LockManagerGroup {
- /** @var Array (domain => LockManager) */
+ /** @var array (domain => LockManager) */
protected static $instances = array();
protected $domain; // string; domain (usually wiki ID)
- /** @var Array of (name => ('class' => ..., 'config' => ..., 'instance' => ...)) */
+ /** @var array Array of (name => ('class' => ..., 'config' => ..., 'instance' => ...)) */
protected $managers = array();
/**
@@ -45,7 +45,7 @@ class LockManagerGroup {
}
/**
- * @param string $domain Domain (usually wiki ID)
+ * @param bool|string $domain Domain (usually wiki ID). Default: false.
* @return LockManagerGroup
*/
public static function singleton( $domain = false ) {
@@ -54,13 +54,12 @@ class LockManagerGroup {
self::$instances[$domain] = new self( $domain );
self::$instances[$domain]->initFromGlobals();
}
+
return self::$instances[$domain];
}
/**
* Destroy the singleton instances
- *
- * @return void
*/
public static function destroySingletons() {
self::$instances = array();
@@ -68,8 +67,6 @@ class LockManagerGroup {
/**
* Register lock managers from the global variables
- *
- * @return void
*/
protected function initFromGlobals() {
global $wgLockManagers;
@@ -80,8 +77,7 @@ class LockManagerGroup {
/**
* Register an array of file lock manager configurations
*
- * @param $configs Array
- * @return void
+ * @param array $configs
* @throws MWException
*/
protected function register( array $configs ) {
@@ -107,7 +103,7 @@ class LockManagerGroup {
/**
* Get the lock manager object with a given name
*
- * @param $name string
+ * @param string $name
* @return LockManager
* @throws MWException
*/
@@ -121,14 +117,15 @@ class LockManagerGroup {
$config = $this->managers[$name]['config'];
$this->managers[$name]['instance'] = new $class( $config );
}
+
return $this->managers[$name]['instance'];
}
/**
* Get the config array for a lock manager object with a given name
*
- * @param $name string
- * @return Array
+ * @param string $name
+ * @return array
* @throws MWException
*/
public function config( $name ) {
@@ -136,6 +133,7 @@ class LockManagerGroup {
throw new MWException( "No lock manager defined with the name `$name`." );
}
$class = $this->managers[$name]['class'];
+
return array( 'class' => $class ) + $this->managers[$name]['config'];
}
diff --git a/includes/filebackend/lockmanager/MemcLockManager.php b/includes/filebackend/lockmanager/MemcLockManager.php
index 5eab03ee..9bb01c21 100644
--- a/includes/filebackend/lockmanager/MemcLockManager.php
+++ b/includes/filebackend/lockmanager/MemcLockManager.php
@@ -36,31 +36,31 @@
* @since 1.20
*/
class MemcLockManager extends QuorumLockManager {
- /** @var Array Mapping of lock types to the type actually used */
+ /** @var array Mapping of lock types to the type actually used */
protected $lockTypeMap = array(
self::LOCK_SH => self::LOCK_SH,
self::LOCK_UW => self::LOCK_SH,
self::LOCK_EX => self::LOCK_EX
);
- /** @var Array Map server names to MemcachedBagOStuff objects */
+ /** @var array Map server names to MemcachedBagOStuff objects */
protected $bagOStuffs = array();
- /** @var Array */
- protected $serversUp = array(); // (server name => bool)
- protected $session = ''; // string; random UUID
+ /** @var array (server name => bool) */
+ protected $serversUp = array();
+
+ /** @var string Random UUID */
+ protected $session = '';
/**
* Construct a new instance from configuration.
*
- * $config paramaters include:
+ * @param array $config Paramaters include:
* - lockServers : Associative array of server names to "<IP>:<port>" strings.
* - srvsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
* each having an odd-numbered list of server names (peers) as values.
* - memcConfig : Configuration array for ObjectCache::newFromParams. [optional]
* If set, this must use one of the memcached classes.
- *
- * @param array $config
* @throws MWException
*/
public function __construct( array $config ) {
@@ -88,7 +88,7 @@ class MemcLockManager extends QuorumLockManager {
$this->session = wfRandomString( 32 );
}
- // @TODO: change this code to work in one batch
+ // @todo Change this code to work in one batch
protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
$status = Status::newGood();
@@ -100,8 +100,8 @@ class MemcLockManager extends QuorumLockManager {
? array_merge( $lockedPaths[$type], $paths )
: $paths;
} else {
- foreach ( $lockedPaths as $type => $paths ) {
- $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
+ foreach ( $lockedPaths as $lType => $lPaths ) {
+ $status->merge( $this->doFreeLocksOnServer( $lockSrv, $lPaths, $lType ) );
}
break;
}
@@ -110,7 +110,7 @@ class MemcLockManager extends QuorumLockManager {
return $status;
}
- // @TODO: change this code to work in one batch
+ // @todo Change this code to work in one batch
protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
$status = Status::newGood();
@@ -123,6 +123,9 @@ class MemcLockManager extends QuorumLockManager {
/**
* @see QuorumLockManager::getLocksOnServer()
+ * @param string $lockSrv
+ * @param array $paths
+ * @param string $type
* @return Status
*/
protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
@@ -136,6 +139,7 @@ class MemcLockManager extends QuorumLockManager {
foreach ( $paths as $path ) {
$status->fatal( 'lockmanager-fail-acquirelock', $path );
}
+
return $status;
}
@@ -195,6 +199,9 @@ class MemcLockManager extends QuorumLockManager {
/**
* @see QuorumLockManager::freeLocksOnServer()
+ * @param string $lockSrv
+ * @param array $paths
+ * @param string $type
* @return Status
*/
protected function doFreeLocksOnServer( $lockSrv, array $paths, $type ) {
@@ -208,7 +215,8 @@ class MemcLockManager extends QuorumLockManager {
foreach ( $paths as $path ) {
$status->fatal( 'lockmanager-fail-releaselock', $path );
}
- return;
+
+ return $status;
}
// Fetch all the existing lock records...
@@ -254,6 +262,7 @@ class MemcLockManager extends QuorumLockManager {
/**
* @see QuorumLockManager::isServerUp()
+ * @param string $lockSrv
* @return bool
*/
protected function isServerUp( $lockSrv ) {
@@ -280,11 +289,12 @@ class MemcLockManager extends QuorumLockManager {
return null; // server appears to be down
}
}
+
return $memc;
}
/**
- * @param $path string
+ * @param string $path
* @return string
*/
protected function recordKeyForPath( $path ) {
@@ -292,27 +302,28 @@ class MemcLockManager extends QuorumLockManager {
}
/**
- * @return Array An empty lock structure for a key
+ * @return array An empty lock structure for a key
*/
protected static function newLockArray() {
return array( self::LOCK_SH => array(), self::LOCK_EX => array() );
}
/**
- * @param $a array
- * @return Array An empty lock structure for a key
+ * @param array $a
+ * @return array An empty lock structure for a key
*/
protected static function sanitizeLockArray( $a ) {
if ( is_array( $a ) && isset( $a[self::LOCK_EX] ) && isset( $a[self::LOCK_SH] ) ) {
return $a;
} else {
trigger_error( __METHOD__ . ": reset invalid lock array.", E_USER_WARNING );
+
return self::newLockArray();
}
}
/**
- * @param $memc MemcachedBagOStuff
+ * @param MemcachedBagOStuff $memc
* @param array $keys List of keys to acquire
* @return bool
*/
@@ -350,9 +361,8 @@ class MemcLockManager extends QuorumLockManager {
}
/**
- * @param $memc MemcachedBagOStuff
+ * @param MemcachedBagOStuff $memc
* @param array $keys List of acquired keys
- * @return void
*/
protected function releaseMutexes( MemcachedBagOStuff $memc, array $keys ) {
foreach ( $keys as $key ) {
diff --git a/includes/filebackend/lockmanager/QuorumLockManager.php b/includes/filebackend/lockmanager/QuorumLockManager.php
index 8356d32a..a692012d 100644
--- a/includes/filebackend/lockmanager/QuorumLockManager.php
+++ b/includes/filebackend/lockmanager/QuorumLockManager.php
@@ -29,9 +29,10 @@
* @since 1.20
*/
abstract class QuorumLockManager extends LockManager {
- /** @var Array Map of bucket indexes to peer server lists */
+ /** @var array Map of bucket indexes to peer server lists */
protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...))
- /** @var Array Map of degraded buckets */
+
+ /** @var array Map of degraded buckets */
protected $degradedBuckets = array(); // (buckey index => UNIX timestamp)
final protected function doLock( array $paths, $type ) {
@@ -65,6 +66,7 @@ abstract class QuorumLockManager extends LockManager {
$status->merge( $this->doLockingRequestBucket( $bucket, $pathsToLockByType ) );
if ( !$status->isOK() ) {
$status->merge( $this->doUnlockByType( $lockedPaths ) );
+
return $status;
}
// Record these locks as active
@@ -120,7 +122,7 @@ abstract class QuorumLockManager extends LockManager {
* Attempt to acquire locks with the peers for a bucket.
* This is all or nothing; if any key is locked then this totally fails.
*
- * @param $bucket integer
+ * @param int $bucket
* @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
*/
@@ -162,7 +164,7 @@ abstract class QuorumLockManager extends LockManager {
/**
* Attempt to release locks with the peers for a bucket
*
- * @param $bucket integer
+ * @param int $bucket
* @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
*/
@@ -176,8 +178,8 @@ abstract class QuorumLockManager extends LockManager {
foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
if ( !$this->isServerUp( $lockSrv ) ) {
$status->warning( 'lockmanager-fail-svr-release', $lockSrv );
- // Attempt to release the lock on this peer
} else {
+ // Attempt to release the lock on this peer
$status->merge( $this->freeLocksOnServer( $lockSrv, $pathsByType ) );
++$yesVotes; // success for this peer
// Normally the first peers form the quorum, and the others are ignored.
@@ -198,8 +200,8 @@ abstract class QuorumLockManager extends LockManager {
* Get the bucket for resource path.
* This should avoid throwing any exceptions.
*
- * @param $path string
- * @return integer
+ * @param string $path
+ * @return int
*/
protected function getBucketFromPath( $path ) {
$prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
@@ -210,7 +212,7 @@ abstract class QuorumLockManager extends LockManager {
* Check if a lock server is up.
* This should process cache results to reduce RTT.
*
- * @param $lockSrv string
+ * @param string $lockSrv
* @return bool
*/
abstract protected function isServerUp( $lockSrv );
@@ -218,7 +220,7 @@ abstract class QuorumLockManager extends LockManager {
/**
* Get a connection to a lock server and acquire locks
*
- * @param $lockSrv string
+ * @param string $lockSrv
* @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
*/
@@ -229,7 +231,7 @@ abstract class QuorumLockManager extends LockManager {
*
* Subclasses must effectively implement this or releaseAllLocks().
*
- * @param $lockSrv string
+ * @param string $lockSrv
* @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
*/
diff --git a/includes/filebackend/lockmanager/RedisLockManager.php b/includes/filebackend/lockmanager/RedisLockManager.php
index 43b0198a..90e05817 100644
--- a/includes/filebackend/lockmanager/RedisLockManager.php
+++ b/includes/filebackend/lockmanager/RedisLockManager.php
@@ -38,7 +38,7 @@
* @since 1.22
*/
class RedisLockManager extends QuorumLockManager {
- /** @var Array Mapping of lock types to the type actually used */
+ /** @var array Mapping of lock types to the type actually used */
protected $lockTypeMap = array(
self::LOCK_SH => self::LOCK_SH,
self::LOCK_UW => self::LOCK_SH,
@@ -47,21 +47,21 @@ class RedisLockManager extends QuorumLockManager {
/** @var RedisConnectionPool */
protected $redisPool;
- /** @var Array Map server names to hostname/IP and port numbers */
+
+ /** @var array Map server names to hostname/IP and port numbers */
protected $lockServers = array();
- protected $session = ''; // string; random UUID
+ /** @var string Random UUID */
+ protected $session = '';
/**
* Construct a new instance from configuration.
*
- * $config paramaters include:
+ * @param array $config Parameters include:
* - lockServers : Associative array of server names to "<IP>:<port>" strings.
* - srvsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
* each having an odd-numbered list of server names (peers) as values.
* - redisConfig : Configuration for RedisConnectionPool::__construct().
- *
- * @param Array $config
* @throws MWException
*/
public function __construct( array $config ) {
@@ -78,115 +78,89 @@ class RedisLockManager extends QuorumLockManager {
$this->session = wfRandomString( 32 );
}
- // @TODO: change this code to work in one batch
protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
$status = Status::newGood();
- $lockedPaths = array();
- foreach ( $pathsByType as $type => $paths ) {
- $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
- if ( $status->isOK() ) {
- $lockedPaths[$type] = isset( $lockedPaths[$type] )
- ? array_merge( $lockedPaths[$type], $paths )
- : $paths;
- } else {
- foreach ( $lockedPaths as $type => $paths ) {
- $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
- }
- break;
- }
- }
-
- return $status;
- }
-
- // @TODO: change this code to work in one batch
- protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
- $status = Status::newGood();
-
- foreach ( $pathsByType as $type => $paths ) {
- $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
- }
-
- return $status;
- }
-
- protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
- $status = Status::newGood();
-
$server = $this->lockServers[$lockSrv];
$conn = $this->redisPool->getConnection( $server );
if ( !$conn ) {
- foreach ( $paths as $path ) {
+ foreach ( array_merge( array_values( $pathsByType ) ) as $path ) {
$status->fatal( 'lockmanager-fail-acquirelock', $path );
}
+
return $status;
}
- $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
+ $pathsByKey = array(); // (type:hash => path) map
+ foreach ( $pathsByType as $type => $paths ) {
+ $typeString = ( $type == LockManager::LOCK_SH ) ? 'SH' : 'EX';
+ foreach ( $paths as $path ) {
+ $pathsByKey[$this->recordKeyForPath( $path, $typeString )] = $path;
+ }
+ }
try {
static $script =
<<<LUA
- if ARGV[1] ~= 'EX' and ARGV[1] ~= 'SH' then
- return redis.error_reply('Unrecognized lock type given (must be EX or SH)')
- end
local failed = {}
+ -- Load input params (e.g. session, ttl, time of request)
+ local rSession, rTTL, rTime = unpack(ARGV)
-- Check that all the locks can be acquired
- for i,resourceKey in ipairs(KEYS) do
+ for i,requestKey in ipairs(KEYS) do
+ local _, _, rType, resourceKey = string.find(requestKey,"(%w+):(%w+)$")
local keyIsFree = true
local currentLocks = redis.call('hKeys',resourceKey)
for i,lockKey in ipairs(currentLocks) do
+ -- Get the type and session of this lock
local _, _, type, session = string.find(lockKey,"(%w+):(%w+)")
-- Check any locks that are not owned by this session
- if session ~= ARGV[2] then
- local lockTimestamp = redis.call('hGet',resourceKey,lockKey)
- if 1*lockTimestamp < ( ARGV[4] - ARGV[3] ) then
+ if session ~= rSession then
+ local lockExpiry = redis.call('hGet',resourceKey,lockKey)
+ if 1*lockExpiry < 1*rTime then
-- Lock is stale, so just prune it out
redis.call('hDel',resourceKey,lockKey)
- elseif ARGV[1] == 'EX' or type == 'EX' then
+ elseif rType == 'EX' or type == 'EX' then
keyIsFree = false
break
end
end
end
if not keyIsFree then
- failed[#failed+1] = resourceKey
+ failed[#failed+1] = requestKey
end
end
-- If all locks could be acquired, then do so
if #failed == 0 then
- for i,resourceKey in ipairs(KEYS) do
- redis.call('hSet',resourceKey,ARGV[1] .. ':' .. ARGV[2],ARGV[4])
+ for i,requestKey in ipairs(KEYS) do
+ local _, _, rType, resourceKey = string.find(requestKey,"(%w+):(%w+)$")
+ redis.call('hSet',resourceKey,rType .. ':' .. rSession,rTime + rTTL)
-- In addition to invalidation logic, be sure to garbage collect
- redis.call('expire',resourceKey,ARGV[3])
+ redis.call('expire',resourceKey,rTTL)
end
end
return failed
LUA;
$res = $conn->luaEval( $script,
array_merge(
- $keys, // KEYS[0], KEYS[1],...KEYS[N]
+ array_keys( $pathsByKey ), // KEYS[0], KEYS[1],...,KEYS[N]
array(
- $type === self::LOCK_SH ? 'SH' : 'EX', // ARGV[1]
- $this->session, // ARGV[2]
- $this->lockTTL, // ARGV[3]
- time() // ARGV[4]
+ $this->session, // ARGV[1]
+ $this->lockTTL, // ARGV[2]
+ time() // ARGV[3]
)
),
- count( $keys ) # number of first argument(s) that are keys
+ count( $pathsByKey ) # number of first argument(s) that are keys
);
} catch ( RedisException $e ) {
$res = false;
- $this->redisPool->handleException( $server, $conn, $e );
+ $this->redisPool->handleError( $conn, $e );
}
if ( $res === false ) {
- foreach ( $paths as $path ) {
+ foreach ( array_merge( array_values( $pathsByType ) ) as $path ) {
$status->fatal( 'lockmanager-fail-acquirelock', $path );
}
} else {
- $pathsByKey = array_combine( $keys, $paths );
foreach ( $res as $key ) {
$status->fatal( 'lockmanager-fail-acquirelock', $pathsByKey[$key] );
}
@@ -195,61 +169,66 @@ LUA;
return $status;
}
- protected function doFreeLocksOnServer( $lockSrv, array $paths, $type ) {
+ protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
$status = Status::newGood();
$server = $this->lockServers[$lockSrv];
$conn = $this->redisPool->getConnection( $server );
if ( !$conn ) {
- foreach ( $paths as $path ) {
+ foreach ( array_merge( array_values( $pathsByType ) ) as $path ) {
$status->fatal( 'lockmanager-fail-releaselock', $path );
}
+
return $status;
}
- $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
+ $pathsByKey = array(); // (type:hash => path) map
+ foreach ( $pathsByType as $type => $paths ) {
+ $typeString = ( $type == LockManager::LOCK_SH ) ? 'SH' : 'EX';
+ foreach ( $paths as $path ) {
+ $pathsByKey[$this->recordKeyForPath( $path, $typeString )] = $path;
+ }
+ }
try {
static $script =
<<<LUA
- if ARGV[1] ~= 'EX' and ARGV[1] ~= 'SH' then
- return redis.error_reply('Unrecognized lock type given (must be EX or SH)')
- end
local failed = {}
- for i,resourceKey in ipairs(KEYS) do
- local released = redis.call('hDel',resourceKey,ARGV[1] .. ':' .. ARGV[2])
+ -- Load input params (e.g. session)
+ local rSession = unpack(ARGV)
+ for i,requestKey in ipairs(KEYS) do
+ local _, _, rType, resourceKey = string.find(requestKey,"(%w+):(%w+)$")
+ local released = redis.call('hDel',resourceKey,rType .. ':' .. rSession)
if released > 0 then
-- Remove the whole structure if it is now empty
if redis.call('hLen',resourceKey) == 0 then
redis.call('del',resourceKey)
end
else
- failed[#failed+1] = resourceKey
+ failed[#failed+1] = requestKey
end
end
return failed
LUA;
$res = $conn->luaEval( $script,
array_merge(
- $keys, // KEYS[0], KEYS[1],...KEYS[N]
+ array_keys( $pathsByKey ), // KEYS[0], KEYS[1],...,KEYS[N]
array(
- $type === self::LOCK_SH ? 'SH' : 'EX', // ARGV[1]
- $this->session // ARGV[2]
+ $this->session, // ARGV[1]
)
),
- count( $keys ) # number of first argument(s) that are keys
+ count( $pathsByKey ) # number of first argument(s) that are keys
);
} catch ( RedisException $e ) {
$res = false;
- $this->redisPool->handleException( $server, $conn, $e );
+ $this->redisPool->handleError( $conn, $e );
}
if ( $res === false ) {
- foreach ( $paths as $path ) {
+ foreach ( array_merge( array_values( $pathsByType ) ) as $path ) {
$status->fatal( 'lockmanager-fail-releaselock', $path );
}
} else {
- $pathsByKey = array_combine( $keys, $paths );
foreach ( $res as $key ) {
$status->fatal( 'lockmanager-fail-releaselock', $pathsByKey[$key] );
}
@@ -267,11 +246,13 @@ LUA;
}
/**
- * @param $path string
+ * @param string $path
+ * @param string $type One of (EX,SH)
* @return string
*/
- protected function recordKeyForPath( $path ) {
- return implode( ':', array( __CLASS__, 'locks', $this->sha1Base36Absolute( $path ) ) );
+ protected function recordKeyForPath( $path, $type ) {
+ return implode( ':',
+ array( __CLASS__, 'locks', "$type:" . $this->sha1Base36Absolute( $path ) ) );
}
/**
@@ -279,10 +260,13 @@ LUA;
*/
function __destruct() {
while ( count( $this->locksHeld ) ) {
+ $pathsByType = array();
foreach ( $this->locksHeld as $path => $locks ) {
- $this->doUnlock( array( $path ), self::LOCK_EX );
- $this->doUnlock( array( $path ), self::LOCK_SH );
+ foreach ( $locks as $type => $count ) {
+ $pathsByType[$type][] = $path;
+ }
}
+ $this->unlockByType( $pathsByType );
}
}
}
diff --git a/includes/filebackend/lockmanager/ScopedLock.php b/includes/filebackend/lockmanager/ScopedLock.php
index 5faad4a6..2056e101 100644
--- a/includes/filebackend/lockmanager/ScopedLock.php
+++ b/includes/filebackend/lockmanager/ScopedLock.php
@@ -34,9 +34,11 @@
class ScopedLock {
/** @var LockManager */
protected $manager;
+
/** @var Status */
protected $status;
- /** @var Array Map of lock types to resource paths */
+
+ /** @var array Map of lock types to resource paths */
protected $pathsByType;
/**
@@ -55,14 +57,13 @@ class ScopedLock {
* Any locks are released once this object goes out of scope.
* The status object is updated with any errors or warnings.
*
- * $type can be "mixed" and $paths can be a map of types to paths (since 1.22).
- * Otherwise $type should be an integer and $paths should be a list of paths.
- *
* @param LockManager $manager
* @param array $paths List of storage paths or map of lock types to path lists
- * @param integer|string $type LockManager::LOCK_* constant or "mixed"
+ * @param int|string $type LockManager::LOCK_* constant or "mixed" and $paths
+ * can be a map of types to paths (since 1.22). Otherwise $type should be an
+ * integer and $paths should be a list of paths.
* @param Status $status
- * @param integer $timeout Timeout in seconds (0 means non-blocking) (since 1.22)
+ * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.22)
* @return ScopedLock|null Returns null on failure
*/
public static function factory(
@@ -74,6 +75,7 @@ class ScopedLock {
if ( $lockStatus->isOK() ) {
return new self( $manager, $pathsByType, $status );
}
+
return null;
}
@@ -83,7 +85,6 @@ class ScopedLock {
* This is the same as setting the lock object to null.
*
* @param ScopedLock $lock
- * @return void
* @since 1.21
*/
public static function release( ScopedLock &$lock = null ) {
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index 42c9c945..5896aba1 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -31,9 +31,8 @@
* @deprecated since 1.19
*/
class FSRepo extends FileRepo {
-
/**
- * @param $info array
+ * @param array $info
* @throws MWException
*/
function __construct( array $info ) {
@@ -57,7 +56,8 @@ class FSRepo extends FileRepo {
// Get the FS backend configuration
$backend = new FSFileBackend( array(
'name' => $info['name'] . '-backend',
- 'lockManager' => 'fsLockManager',
+ 'wikiId' => wfWikiID(),
+ 'lockManager' => LockManagerGroup::singleton( wfWikiID() )->get( 'fsLockManager' ),
'containerPaths' => array(
"{$repoName}-public" => "{$directory}",
"{$repoName}-temp" => "{$directory}/temp",
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index 1195d5f8..59295257 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -40,29 +40,91 @@ class FileRepo {
const OVERWRITE_SAME = 4;
const SKIP_LOCKING = 8;
+ const NAME_AND_TIME_ONLY = 1;
+
+ /** @var bool Whether to fetch commons image description pages and display
+ * them on the local wiki */
+ public $fetchDescription;
+
+ /** @var int */
+ public $descriptionCacheExpiry;
+
/** @var FileBackend */
protected $backend;
- /** @var Array Map of zones to config */
+
+ /** @var array Map of zones to config */
protected $zones = array();
- var $thumbScriptUrl, $transformVia404;
- var $descBaseUrl, $scriptDirUrl, $scriptExtension, $articleUrl;
- var $fetchDescription, $initialCapital;
- var $pathDisclosureProtection = 'simple'; // 'paranoid'
- var $descriptionCacheExpiry, $url, $thumbUrl;
- var $hashLevels, $deletedHashLevels;
+ /** @var string URL of thumb.php */
+ protected $thumbScriptUrl;
+
+ /** @var bool Whether to skip media file transformation on parse and rely
+ * on a 404 handler instead. */
+ protected $transformVia404;
+
+ /** @var string URL of image description pages, e.g.
+ * http://en.wikipedia.org/wiki/File:
+ */
+ protected $descBaseUrl;
+
+ /** @var string URL of the MediaWiki installation, equivalent to
+ * $wgScriptPath, e.g. https://en.wikipedia.org/w
+ */
+ protected $scriptDirUrl;
+
+ /** @var string Script extension of the MediaWiki installation, equivalent
+ * to $wgScriptExtension, e.g. .php5 defaults to .php */
+ protected $scriptExtension;
+
+ /** @var string Equivalent to $wgArticlePath, e.g. http://en.wikipedia.org/wiki/$1 */
+ protected $articleUrl;
+
+ /** @var bool Equivalent to $wgCapitalLinks (or $wgCapitalLinkOverrides[NS_FILE],
+ * determines whether filenames implicitly start with a capital letter.
+ * The current implementation may give incorrect description page links
+ * when the local $wgCapitalLinks and initialCapital are mismatched.
+ */
+ protected $initialCapital;
+
+ /** @var string May be 'paranoid' to remove all parameters from error
+ * messages, 'none' to leave the paths in unchanged, or 'simple' to
+ * replace paths with placeholders. Default for LocalRepo is
+ * 'simple'.
+ */
+ protected $pathDisclosureProtection = 'simple';
+
+ /** @var bool Public zone URL. */
+ protected $url;
+
+ /** @var string The base thumbnail URL. Defaults to "<url>/thumb". */
+ protected $thumbUrl;
+
+ /** @var int The number of directory levels for hash-based division of files */
+ protected $hashLevels;
+
+ /** @var int The number of directory levels for hash-based division of deleted files */
+ protected $deletedHashLevels;
+
+ /** @var int File names over this size will use the short form of thumbnail
+ * names. Short thumbnail names only have the width, parameters, and the
+ * extension.
+ */
protected $abbrvThreshold;
+ /** @var string The URL of the repo's favicon, if any */
+ protected $favicon;
+
/**
* Factory functions for creating new files
* Override these in the base class
*/
- var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
- var $oldFileFactory = false;
- var $fileFactoryKey = false, $oldFileFactoryKey = false;
+ protected $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
+ protected $oldFileFactory = false;
+ protected $fileFactoryKey = false;
+ protected $oldFileFactoryKey = false;
/**
- * @param $info array|null
+ * @param array|null $info
* @throws MWException
*/
public function __construct( array $info = null ) {
@@ -72,7 +134,8 @@ class FileRepo {
|| !array_key_exists( 'name', $info )
|| !array_key_exists( 'backend', $info )
) {
- throw new MWException( __CLASS__ . " requires an array of options having both 'name' and 'backend' keys.\n" );
+ throw new MWException( __CLASS__ .
+ " requires an array of options having both 'name' and 'backend' keys.\n" );
}
// Required settings
@@ -87,7 +150,7 @@ class FileRepo {
$optionalSettings = array(
'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
'thumbScriptUrl', 'pathDisclosureProtection', 'descriptionCacheExpiry',
- 'scriptExtension'
+ 'scriptExtension', 'favicon'
);
foreach ( $optionalSettings as $var ) {
if ( isset( $info[$var] ) ) {
@@ -167,13 +230,14 @@ class FileRepo {
throw new MWException( "No '$zone' zone defined in the {$this->name} repo." );
}
}
+
return $status;
}
/**
* Determine if a string is an mwrepo:// URL
*
- * @param $url string
+ * @param string $url
* @return bool
*/
public static function isVirtualUrl( $url ) {
@@ -185,7 +249,7 @@ class FileRepo {
* The suffix, if supplied, is considered to be unencoded, and will be
* URL-encoded before being returned.
*
- * @param $suffix string|bool
+ * @param string|bool $suffix
* @return string
*/
public function getVirtualUrl( $suffix = false ) {
@@ -193,6 +257,7 @@ class FileRepo {
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
+
return $path;
}
@@ -201,14 +266,17 @@ class FileRepo {
*
* @param string $zone One of: public, deleted, temp, thumb
* @param string|null $ext Optional file extension
- * @return String or false
+ * @return string|bool
*/
public function getZoneUrl( $zone, $ext = null ) {
- if ( in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) { // standard public zones
+ if ( in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) {
+ // standard public zones
if ( $ext !== null && isset( $this->zones[$zone]['urlsByExt'][$ext] ) ) {
- return $this->zones[$zone]['urlsByExt'][$ext]; // custom URL for extension/zone
+ // custom URL for extension/zone
+ return $this->zones[$zone]['urlsByExt'][$ext];
} elseif ( isset( $this->zones[$zone]['url'] ) ) {
- return $this->zones[$zone]['url']; // custom URL for zone
+ // custom URL for zone
+ return $this->zones[$zone]['url'];
}
}
switch ( $zone ) {
@@ -228,32 +296,17 @@ class FileRepo {
}
/**
- * Get the thumb zone URL configured to be handled by scripts like thumb_handler.php.
- * This is probably only useful for internal requests, such as from a fast frontend server
- * to a slower backend server.
- *
- * Large sites may use a different host name for uploads than for wikis. In any case, the
- * wiki configuration is needed in order to use thumb.php. To avoid extracting the wiki ID
- * from the URL path, one can configure thumb_handler.php to recognize a special path on the
- * same host name as the wiki that is used for viewing thumbnails.
- *
- * @param string $zone one of: public, deleted, temp, thumb
- * @return String or false
+ * @return bool Whether non-ASCII path characters are allowed
*/
- public function getZoneHandlerUrl( $zone ) {
- if ( isset( $this->zones[$zone]['handlerUrl'] )
- && in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) )
- {
- return $this->zones[$zone]['handlerUrl'];
- }
- return false;
+ public function backendSupportsUnicodePaths() {
+ return ( $this->getBackend()->getFeatures() & FileBackend::ATTR_UNICODE_PATHS );
}
/**
* Get the backend storage path corresponding to a virtual URL.
* Use this function wisely.
*
- * @param $url string
+ * @param string $url
* @throws MWException
* @return string
*/
@@ -273,26 +326,28 @@ class FileRepo {
if ( !$base ) {
throw new MWException( __METHOD__ . ": invalid zone: $zone" );
}
+
return $base . '/' . rawurldecode( $rel );
}
/**
* The the storage container and base path of a zone
*
- * @param $zone string
- * @return Array (container, base path) or (null, null)
+ * @param string $zone
+ * @return array (container, base path) or (null, null)
*/
protected function getZoneLocation( $zone ) {
if ( !isset( $this->zones[$zone] ) ) {
return array( null, null ); // bogus
}
+
return array( $this->zones[$zone]['container'], $this->zones[$zone]['directory'] );
}
/**
* Get the storage path corresponding to one of the zones
*
- * @param $zone string
+ * @param string $zone
* @return string|null Returns null if the zone is not defined
*/
public function getZonePath( $zone ) {
@@ -304,18 +359,19 @@ class FileRepo {
if ( $base != '' ) { // may not be set
$base = "/{$base}";
}
+
return "mwstore://$backendName/{$container}{$base}";
}
/**
* Create a new File object from the local repository
*
- * @param $title Mixed: Title object or string
- * @param $time Mixed: Time at which the image was uploaded.
- * If this is specified, the returned object will be an
- * instance of the repository's old file class instead of a
- * current file. Repositories not supporting version control
- * should return false if this parameter is set.
+ * @param Title|string $title Title object or string
+ * @param bool|string $time Time at which the image was uploaded. If this
+ * is specified, the returned object will be an instance of the
+ * repository's old file class instead of a current file. Repositories
+ * not supporting version control should return false if this parameter
+ * is set.
* @return File|null A File, or null if passed an invalid Title
*/
public function newFile( $title, $time = false ) {
@@ -339,17 +395,15 @@ class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param $title Mixed: Title object or string
+ * @param Title|string $title Title object or string
* @param array $options Associative array of options:
- * time: requested time for a specific file version, or false for the
- * current version. An image object will be returned which was
- * created at the specified time (which may be archived or current).
- *
- * ignoreRedirect: If true, do not follow file redirects
- *
- * private: If true, return restricted (deleted) files if the current
- * user is allowed to view them. Otherwise, such files will not
- * be found.
+ * time: requested time for a specific file version, or false for the
+ * current version. An image object will be returned which was
+ * created at the specified time (which may be archived or current).
+ * ignoreRedirect: If true, do not follow file redirects
+ * private: If true, return restricted (deleted) files if the current
+ * user is allowed to view them. Otherwise, such files will not
+ * be found. If a User object, use that user instead of the current.
* @return File|bool False on failure
*/
public function findFile( $title, $options = array() ) {
@@ -372,7 +426,11 @@ class FileRepo {
if ( $img && $img->exists() ) {
if ( !$img->isDeleted( File::DELETED_FILE ) ) {
return $img; // always OK
- } elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
+ } elseif ( !empty( $options['private'] ) &&
+ $img->userCan( File::DELETED_FILE,
+ $options['private'] instanceof User ? $options['private'] : null
+ )
+ ) {
return $img;
}
}
@@ -390,9 +448,11 @@ class FileRepo {
}
if ( $img->exists() ) {
$img->redirectedFrom( $title->getDBkey() );
+
return $img;
}
}
+
return false;
}
@@ -405,9 +465,15 @@ class FileRepo {
* $findItem = array( 'title' => $title, 'private' => true );
* $findBatch = array( $findItem );
* $repo->findFiles( $findBatch );
- * @return array
+ *
+ * No title should appear in $items twice, as the result use titles as keys
+ * @param int $flags Supports:
+ * - FileRepo::NAME_AND_TIME_ONLY : return a (search title => (title,timestamp)) map.
+ * The search title uses the input titles; the other is the final post-redirect title.
+ * All titles are returned as string DB keys and the inner array is associative.
+ * @return array Map of (file name => File objects) for matches
*/
- public function findFiles( array $items ) {
+ public function findFiles( array $items, $flags = 0 ) {
$result = array();
foreach ( $items as $item ) {
if ( is_array( $item ) ) {
@@ -420,9 +486,18 @@ class FileRepo {
}
$file = $this->findFile( $title, $options );
if ( $file ) {
- $result[$file->getTitle()->getDBkey()] = $file;
+ $searchName = File::normalizeTitle( $title )->getDBkey(); // must be valid
+ if ( $flags & self::NAME_AND_TIME_ONLY ) {
+ $result[$searchName] = array(
+ 'title' => $file->getTitle()->getDBkey(),
+ 'timestamp' => $file->getTimestamp()
+ );
+ } else {
+ $result[$searchName] = $file;
+ }
}
}
+
return $result;
}
@@ -431,7 +506,7 @@ class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param string $sha1 base 36 SHA-1 hash
+ * @param string $sha1 Base 36 SHA-1 hash
* @param array $options Option array, same as findFile().
* @return File|bool False on failure
*/
@@ -452,11 +527,16 @@ class FileRepo {
if ( $img && $img->exists() ) {
if ( !$img->isDeleted( File::DELETED_FILE ) ) {
return $img; // always OK
- } elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
+ } elseif ( !empty( $options['private'] ) &&
+ $img->userCan( File::DELETED_FILE,
+ $options['private'] instanceof User ? $options['private'] : null
+ )
+ ) {
return $img;
}
}
}
+
return false;
}
@@ -465,8 +545,8 @@ class FileRepo {
* SHA-1 content hash.
*
* STUB
- * @param $hash
- * @return array
+ * @param string $hash SHA-1 hash
+ * @return File[]
*/
public function findBySha1( $hash ) {
return array();
@@ -487,6 +567,7 @@ class FileRepo {
$result[$hash] = $files;
}
}
+
return $result;
}
@@ -531,10 +612,10 @@ class FileRepo {
}
/**
- * Get the name of an image from its title object
+ * Get the name of a file from its title object
*
- * @param $title Title
- * @return String
+ * @param Title $title
+ * @return string
*/
public function getNameFromTitle( Title $title ) {
global $wgContLang;
@@ -546,6 +627,7 @@ class FileRepo {
} else {
$name = $title->getDBkey();
}
+
return $name;
}
@@ -583,8 +665,8 @@ class FileRepo {
}
/**
- * @param $name
- * @param $levels
+ * @param string $name
+ * @param int $levels
* @return string
*/
protected static function getHashPathForLevel( $name, $levels ) {
@@ -596,6 +678,7 @@ class FileRepo {
for ( $i = 1; $i <= $levels; $i++ ) {
$path .= substr( $hash, 0, $i ) . '/';
}
+
return $path;
}
}
@@ -603,7 +686,7 @@ class FileRepo {
/**
* Get the number of hash directory levels
*
- * @return integer
+ * @return int
*/
public function getHashLevels() {
return $this->hashLevels;
@@ -621,15 +704,17 @@ class FileRepo {
/**
* Make an url to this repo
*
- * @param $query mixed Query string to append
+ * @param string $query Query string to append
* @param string $entry Entry point; defaults to index
* @return string|bool False on failure
*/
public function makeUrl( $query = '', $entry = 'index' ) {
if ( isset( $this->scriptDirUrl ) ) {
$ext = isset( $this->scriptExtension ) ? $this->scriptExtension : '.php';
+
return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
}
+
return false;
}
@@ -642,13 +727,13 @@ class FileRepo {
* In particular, it uses the article paths as specified to the repository
* constructor, whereas local repositories use the local Title functions.
*
- * @param $name string
+ * @param string $name
* @return string
*/
public function getDescriptionUrl( $name ) {
$encName = wfUrlencode( $name );
if ( !is_null( $this->descBaseUrl ) ) {
- # "http://example.com/wiki/Image:"
+ # "http://example.com/wiki/File:"
return $this->descBaseUrl . $encName;
}
if ( !is_null( $this->articleUrl ) ) {
@@ -667,6 +752,7 @@ class FileRepo {
# and just sort of hope index.php is right. ;)
return $this->makeUrl( "title=Image:$encName" );
}
+
return false;
}
@@ -676,8 +762,8 @@ class FileRepo {
* repository's file class, since it may return invalid results. User code
* should use File::getDescriptionText().
*
- * @param string $name name of image to fetch
- * @param string $lang language to fetch it in, if any.
+ * @param string $name Name of image to fetch
+ * @param string $lang Language to fetch it in, if any.
* @return string
*/
public function getDescriptionRenderUrl( $name, $lang = null ) {
@@ -710,21 +796,22 @@ class FileRepo {
return $this->makeUrl( 'title=MediaWiki:Filepage.css&' .
wfArrayToCgi( Skin::getDynamicStylesheetQuery() ) );
}
+
return false;
}
/**
* Store a file to a given destination.
*
- * @param string $srcPath source file system path, storage path, or virtual URL
- * @param string $dstZone destination zone
- * @param string $dstRel destination relative path
- * @param $flags Integer: bitwise combination of the following flags:
- * self::DELETE_SOURCE Delete the source file after upload
- * self::OVERWRITE Overwrite an existing destination file instead of failing
- * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
- * same contents as the source
- * self::SKIP_LOCKING Skip any file locking when doing the store
+ * @param string $srcPath Source file system path, storage path, or virtual URL
+ * @param string $dstZone Destination zone
+ * @param string $dstRel Destination relative path
+ * @param int $flags Bitwise combination of the following flags:
+ * self::DELETE_SOURCE Delete the source file after upload
+ * self::OVERWRITE Overwrite an existing destination file instead of failing
+ * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
+ * same contents as the source
+ * self::SKIP_LOCKING Skip any file locking when doing the store
* @return FileRepoStatus
*/
public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
@@ -742,12 +829,12 @@ class FileRepo {
* Store a batch of files
*
* @param array $triplets (src, dest zone, dest rel) triplets as per store()
- * @param $flags Integer: bitwise combination of the following flags:
- * self::DELETE_SOURCE Delete the source file after upload
- * self::OVERWRITE Overwrite an existing destination file instead of failing
- * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
- * same contents as the source
- * self::SKIP_LOCKING Skip any file locking when doing the store
+ * @param int $flags Bitwise combination of the following flags:
+ * self::DELETE_SOURCE Delete the source file after upload
+ * self::OVERWRITE Overwrite an existing destination file instead of failing
+ * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
+ * same contents as the source
+ * self::SKIP_LOCKING Skip any file locking when doing the store
* @throws MWException
* @return FileRepoStatus
*/
@@ -824,8 +911,8 @@ class FileRepo {
* It will try to delete each file, but ignores any errors that may occur.
*
* @param array $files List of files to delete
- * @param $flags Integer: bitwise combination of the following flags:
- * self::SKIP_LOCKING Skip any file locking when doing the deletions
+ * @param int $flags Bitwise combination of the following flags:
+ * self::SKIP_LOCKING Skip any file locking when doing the deletions
* @return FileRepoStatus
*/
public function cleanupBatch( array $files, $flags = 0 ) {
@@ -863,11 +950,13 @@ class FileRepo {
*
* @param string $src Source file system path, storage path, or virtual URL
* @param string $dst Virtual URL or storage path
- * @param string|null $disposition Content-Disposition if given and supported
+ * @param array|string|null $options An array consisting of a key named headers
+ * listing extra headers. If a string, taken as content-disposition header.
+ * (Support for array of options new in 1.23)
* @return FileRepoStatus
*/
- final public function quickImport( $src, $dst, $disposition = null ) {
- return $this->quickImportBatch( array( array( $src, $dst, $disposition ) ) );
+ final public function quickImport( $src, $dst, $options = null ) {
+ return $this->quickImportBatch( array( array( $src, $dst, $options ) ) );
}
/**
@@ -904,7 +993,7 @@ class FileRepo {
* This is intended for copying generated thumbnails into the repo.
*
* All path parameters may be a file system path, storage path, or virtual URL.
- * When "dispositions" are given they are used as Content-Disposition if supported.
+ * When "headers" are given they are used as HTTP headers if supported.
*
* @param array $triples List of (source path, destination path, disposition)
* @return FileRepoStatus
@@ -916,11 +1005,21 @@ class FileRepo {
list( $src, $dst ) = $triple;
$src = $this->resolveToStoragePath( $src );
$dst = $this->resolveToStoragePath( $dst );
+
+ if ( !isset( $triple[2] ) ) {
+ $headers = array();
+ } elseif ( is_string( $triple[2] ) ) {
+ // back-compat
+ $headers = array( 'Content-Disposition' => $triple[2] );
+ } elseif ( is_array( $triple[2] ) && isset( $triple[2]['headers'] ) ) {
+ $headers = $triple[2]['headers'];
+ }
+ // @fixme: $headers might not be defined
$operations[] = array(
'op' => FileBackend::isStoragePath( $src ) ? 'copy' : 'store',
'src' => $src,
'dst' => $dst,
- 'disposition' => isset( $triple[2] ) ? $triple[2] : null
+ 'headers' => $headers
);
$status->merge( $this->initDirectory( dirname( $dst ) ) );
}
@@ -957,10 +1056,10 @@ class FileRepo {
* Returns a FileRepoStatus object with the file Virtual URL in the value,
* file can later be disposed using FileRepo::freeTemp().
*
- * @param string $originalName the base name of the file as specified
- * by the user. The file extension will be maintained.
- * @param string $srcPath the current location of the file.
- * @return FileRepoStatus object with the URL in the value.
+ * @param string $originalName The base name of the file as specified
+ * by the user. The file extension will be maintained.
+ * @param string $srcPath The current location of the file.
+ * @return FileRepoStatus Object with the URL in the value.
*/
public function storeTemp( $originalName, $srcPath ) {
$this->assertWritableRepo(); // fail out if read-only
@@ -979,8 +1078,8 @@ class FileRepo {
/**
* Remove a temporary file or mark it for garbage collection
*
- * @param string $virtualUrl the virtual URL returned by FileRepo::storeTemp()
- * @return Boolean: true on success, false on failure
+ * @param string $virtualUrl The virtual URL returned by FileRepo::storeTemp()
+ * @return bool True on success, false on failure
*/
public function freeTemp( $virtualUrl ) {
$this->assertWritableRepo(); // fail out if read-only
@@ -988,6 +1087,7 @@ class FileRepo {
$temp = $this->getVirtualUrl( 'temp' );
if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) {
wfDebug( __METHOD__ . ": Invalid temp virtual URL\n" );
+
return false;
}
@@ -999,8 +1099,8 @@ class FileRepo {
*
* @param array $srcPaths Ordered list of source virtual URLs/storage paths
* @param string $dstPath Target file system path
- * @param $flags Integer: bitwise combination of the following flags:
- * self::DELETE_SOURCE Delete the source files
+ * @param int $flags Bitwise combination of the following flags:
+ * self::DELETE_SOURCE Delete the source files
* @return FileRepoStatus
*/
public function concatenate( array $srcPaths, $dstPath, $flags = 0 ) {
@@ -1043,12 +1143,12 @@ class FileRepo {
* Options to $options include:
* - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
*
- * @param string $srcPath the source file system path, storage path, or URL
- * @param string $dstRel the destination relative path
- * @param string $archiveRel the relative path where the existing file is to
- * be archived, if there is one. Relative to the public zone root.
- * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
- * that the source file should be deleted if possible
+ * @param string $srcPath The source file system path, storage path, or URL
+ * @param string $dstRel The destination relative path
+ * @param string $archiveRel The relative path where the existing file is to
+ * be archived, if there is one. Relative to the public zone root.
+ * @param int $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * that the source file should be deleted if possible
* @param array $options Optional additional parameters
* @return FileRepoStatus
*/
@@ -1075,9 +1175,9 @@ class FileRepo {
* Publish a batch of files
*
* @param array $ntuples (source, dest, archive) triplets or
- * (source, dest, archive, options) 4-tuples as per publish().
- * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
- * that the source files should be deleted if possible
+ * (source, dest, archive, options) 4-tuples as per publish().
+ * @param int $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * that the source files should be deleted if possible
* @throws MWException
* @return FileRepoStatus
*/
@@ -1129,7 +1229,7 @@ class FileRepo {
// This will check if the archive file also exists and fail if does.
// This is a sanity check to avoid data loss. On Windows and Linux,
// copy() will overwrite, so the existence check is vulnerable to
- // race conditions unless an functioning LockManager is used.
+ // race conditions unless a functioning LockManager is used.
// LocalFile also uses SELECT FOR UPDATE for synchronization.
$operations[] = array(
'op' => 'copy',
@@ -1238,6 +1338,7 @@ class FileRepo {
*/
public function fileExists( $file ) {
$result = $this->fileExistsBatch( array( $file ) );
+
return $result[0];
}
@@ -1245,14 +1346,18 @@ class FileRepo {
* Checks existence of an array of files.
*
* @param array $files Virtual URLs (or storage paths) of files to check
- * @return array|bool Either array of files and existence flags, or false
+ * @return array Map of files and existence flags, or false
*/
public function fileExistsBatch( array $files ) {
+ $paths = array_map( array( $this, 'resolveToStoragePath' ), $files );
+ $this->backend->preloadFileStat( array( 'srcs' => $paths ) );
+
$result = array();
foreach ( $files as $key => $file ) {
- $file = $this->resolveToStoragePath( $file );
- $result[$key] = $this->backend->fileExists( array( 'src' => $file ) );
+ $path = $this->resolveToStoragePath( $file );
+ $result[$key] = $this->backend->fileExists( array( 'src' => $path ) );
}
+
return $result;
}
@@ -1261,10 +1366,10 @@ class FileRepo {
* If no valid deletion archive exists, this may either delete the file
* or throw an exception, depending on the preference of the repository
*
- * @param $srcRel Mixed: relative path for the file to be deleted
- * @param $archiveRel Mixed: relative path for the archive location.
- * Relative to a private archive directory.
- * @return FileRepoStatus object
+ * @param mixed $srcRel Relative path for the file to be deleted
+ * @param mixed $archiveRel Relative path for the archive location.
+ * Relative to a private archive directory.
+ * @return FileRepoStatus
*/
public function delete( $srcRel, $archiveRel ) {
$this->assertWritableRepo(); // fail out if read-only
@@ -1282,10 +1387,10 @@ class FileRepo {
* assumes a naming scheme in the deleted zone based on content hash, as
* opposed to the public zone which is assumed to be unique.
*
- * @param array $sourceDestPairs of source/destination pairs. Each element
- * is a two-element array containing the source file path relative to the
- * public root in the first element, and the archive file path relative
- * to the deleted zone root in the second element.
+ * @param array $sourceDestPairs Array of source/destination pairs. Each element
+ * is a two-element array containing the source file path relative to the
+ * public root in the first element, and the archive file path relative
+ * to the deleted zone root in the second element.
* @throws MWException
* @return FileRepoStatus
*/
@@ -1346,6 +1451,7 @@ class FileRepo {
* Delete files in the deleted directory if they are not referenced in the filearchive table
*
* STUB
+ * @param array $storageKeys
*/
public function cleanupDeletedBatch( array $storageKeys ) {
$this->assertWritableRepo();
@@ -1355,7 +1461,7 @@ class FileRepo {
* Get a relative path for a deletion archive key,
* e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
*
- * @param $key string
+ * @param string $key
* @throws MWException
* @return string
*/
@@ -1367,6 +1473,7 @@ class FileRepo {
for ( $i = 0; $i < $this->deletedHashLevels; $i++ ) {
$path .= $key[$i] . '/';
}
+
return $path;
}
@@ -1374,7 +1481,7 @@ class FileRepo {
* If a path is a virtual URL, resolve it to a storage path.
* Otherwise, just return the path as it is.
*
- * @param $path string
+ * @param string $path
* @return string
* @throws MWException
*/
@@ -1382,6 +1489,7 @@ class FileRepo {
if ( $this->isVirtualUrl( $path ) ) {
return $this->resolveVirtualUrl( $path );
}
+
return $path;
}
@@ -1389,11 +1497,12 @@ class FileRepo {
* Get a local FS copy of a file with a given virtual URL/storage path.
* Temporary files may be purged when the file object falls out of scope.
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return TempFSFile|null Returns null on failure
*/
public function getLocalCopy( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getLocalCopy( array( 'src' => $path ) );
}
@@ -1402,11 +1511,12 @@ class FileRepo {
* The file is either an original or a copy. It should not be changed.
* Temporary files may be purged when the file object falls out of scope.
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return FSFile|null Returns null on failure.
*/
public function getLocalReference( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getLocalReference( array( 'src' => $path ) );
}
@@ -1414,57 +1524,62 @@ class FileRepo {
* Get properties of a file with a given virtual URL/storage path.
* Properties should ultimately be obtained via FSFile::getProps().
*
- * @param $virtualUrl string
- * @return Array
+ * @param string $virtualUrl
+ * @return array
*/
public function getFileProps( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getFileProps( array( 'src' => $path ) );
}
/**
* Get the timestamp of a file with a given virtual URL/storage path
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return string|bool False on failure
*/
public function getFileTimestamp( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getFileTimestamp( array( 'src' => $path ) );
}
/**
* Get the size of a file with a given virtual URL/storage path
*
- * @param $virtualUrl string
- * @return integer|bool False on failure
+ * @param string $virtualUrl
+ * @return int|bool False on failure
*/
public function getFileSize( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getFileSize( array( 'src' => $path ) );
}
/**
* Get the sha1 (base 36) of a file with a given virtual URL/storage path
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return string|bool
*/
public function getFileSha1( $virtualUrl ) {
$path = $this->resolveToStoragePath( $virtualUrl );
+
return $this->backend->getFileSha1Base36( array( 'src' => $path ) );
}
/**
* Attempt to stream a file with the given virtual URL/storage path
*
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @param array $headers Additional HTTP headers to send on success
* @return bool Success
*/
public function streamFile( $virtualUrl, $headers = array() ) {
$path = $this->resolveToStoragePath( $virtualUrl );
$params = array( 'src' => $path, 'headers' => $headers );
+
return $this->backend->streamFile( $params )->isOK();
}
@@ -1473,7 +1588,7 @@ class FileRepo {
* This only acts on the current version of files, not any old versions.
* May use either the database or the filesystem.
*
- * @param $callback Array|string
+ * @param callable $callback
* @return void
*/
public function enumFiles( $callback ) {
@@ -1484,7 +1599,7 @@ class FileRepo {
* Call a callback function for every public file in the repository.
* May use either the database or the filesystem.
*
- * @param $callback Array|string
+ * @param callable $callback
* @return void
*/
protected function enumFilesInStorage( $callback ) {
@@ -1509,20 +1624,21 @@ class FileRepo {
/**
* Determine if a relative path is valid, i.e. not blank or involving directory traveral
*
- * @param $filename string
+ * @param string $filename
* @return bool
*/
public function validateFilename( $filename ) {
if ( strval( $filename ) == '' ) {
return false;
}
+
return FileBackend::isPathTraversalFree( $filename );
}
/**
* Get a callback function to use for cleaning error message parameters
*
- * @return Array
+ * @return array
*/
function getErrorCleanupFunction() {
switch ( $this->pathDisclosureProtection ) {
@@ -1539,7 +1655,7 @@ class FileRepo {
/**
* Path disclosure protection function
*
- * @param $param string
+ * @param string $param
* @return string
*/
function paranoidClean( $param ) {
@@ -1549,7 +1665,7 @@ class FileRepo {
/**
* Path disclosure protection function
*
- * @param $param string
+ * @param string $param
* @return string
*/
function passThrough( $param ) {
@@ -1559,18 +1675,20 @@ class FileRepo {
/**
* Create a new fatal error
*
+ * @param string $message
* @return FileRepoStatus
*/
public function newFatal( $message /*, parameters...*/ ) {
$params = func_get_args();
array_unshift( $params, $this );
+
return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
}
/**
* Create a new good result
*
- * @param $value null|string
+ * @param null|string $value
* @return FileRepoStatus
*/
public function newGood( $value = null ) {
@@ -1582,8 +1700,8 @@ class FileRepo {
* title object. If not, return false.
* STUB
*
- * @param $title Title of image
- * @return Bool
+ * @param Title $title Title of image
+ * @return bool
*/
public function checkRedirect( Title $title ) {
return false;
@@ -1594,9 +1712,10 @@ class FileRepo {
* Doesn't do anything for repositories that don't support image redirects.
*
* STUB
- * @param $title Title of image
+ * @param Title $title Title of image
*/
- public function invalidateImageRedirect( Title $title ) {}
+ public function invalidateImageRedirect( Title $title ) {
+ }
/**
* Get the human-readable name of the repo
@@ -1604,10 +1723,12 @@ class FileRepo {
* @return string
*/
public function getDisplayName() {
- // We don't name our own repo, return nothing
+ global $wgSitename;
+
if ( $this->isLocal() ) {
- return null;
+ return $wgSitename;
}
+
// 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
return wfMessageFallback( 'shared-repo-name-' . $this->name, 'shared-repo' )->text();
}
@@ -1616,7 +1737,7 @@ class FileRepo {
* Get the portion of the file that contains the origin file name.
* If that name is too long, then the name "thumbnail.<ext>" will be given.
*
- * @param $name string
+ * @param string $name
* @return string
*/
public function nameForThumb( $name ) {
@@ -1624,6 +1745,7 @@ class FileRepo {
$ext = FileBackend::extensionFromPath( $name );
$name = ( $ext == '' ) ? 'thumbnail' : "thumbnail.$ext";
}
+
return $name;
}
@@ -1658,6 +1780,7 @@ class FileRepo {
public function getLocalCacheKey( /*...*/ ) {
$args = func_get_args();
array_unshift( $args, 'filerepo', $this->getName() );
+
return call_user_func_array( 'wfMemcKey', $args );
}
@@ -1680,13 +1803,13 @@ class FileRepo {
),
'thumb' => array(
'container' => $this->zones['thumb']['container'],
- 'directory' => ( $this->zones['thumb']['directory'] == '' )
+ 'directory' => $this->zones['thumb']['directory'] == ''
? 'temp'
: $this->zones['thumb']['directory'] . '/temp'
),
'transcoded' => array(
'container' => $this->zones['transcoded']['container'],
- 'directory' => ( $this->zones['transcoded']['directory'] == '' )
+ 'directory' => $this->zones['transcoded']['directory'] == ''
? 'temp'
: $this->zones['transcoded']['directory'] . '/temp'
)
@@ -1701,7 +1824,7 @@ class FileRepo {
/**
* Get an UploadStash associated with this repo.
*
- * @param $user User
+ * @param User $user
* @return UploadStash
*/
public function getUploadStash( User $user = null ) {
@@ -1715,8 +1838,8 @@ class FileRepo {
* @return void
* @throws MWException
*/
- protected function assertWritableRepo() {}
-
+ protected function assertWritableRepo() {
+ }
/**
* Return information about the repository.
@@ -1725,12 +1848,24 @@ class FileRepo {
* @since 1.22
*/
public function getInfo() {
- return array(
+ $ret = array(
'name' => $this->getName(),
'displayname' => $this->getDisplayName(),
- 'rootUrl' => $this->getRootUrl(),
+ 'rootUrl' => $this->getZoneUrl( 'public' ),
'local' => $this->isLocal(),
);
+
+ $optionalSettings = array(
+ 'url', 'thumbUrl', 'initialCapital', 'descBaseUrl', 'scriptDirUrl', 'articleUrl',
+ 'fetchDescription', 'descriptionCacheExpiry', 'scriptExtension', 'favicon'
+ );
+ foreach ( $optionalSettings as $k ) {
+ if ( isset( $this->$k ) ) {
+ $ret[$k] = $this->$k;
+ }
+ }
+
+ return $ret;
}
}
diff --git a/includes/filerepo/FileRepoStatus.php b/includes/filerepo/FileRepoStatus.php
index 6f28b104..56848df2 100644
--- a/includes/filerepo/FileRepoStatus.php
+++ b/includes/filerepo/FileRepoStatus.php
@@ -29,8 +29,7 @@ class FileRepoStatus extends Status {
/**
* Factory function for fatal errors
*
- * @param $repo FileRepo
- *
+ * @param FileRepo $repo
* @return FileRepoStatus
*/
static function newFatal( $repo /*, parameters...*/ ) {
@@ -38,22 +37,24 @@ class FileRepoStatus extends Status {
$result = new self( $repo );
call_user_func_array( array( &$result, 'error' ), $params );
$result->ok = false;
+
return $result;
}
/**
- * @param $repo FileRepo
- * @param $value
+ * @param FileRepo|bool $repo Default: false
+ * @param mixed $value
* @return FileRepoStatus
*/
static function newGood( $repo = false, $value = null ) {
$result = new self( $repo );
$result->value = $value;
+
return $result;
}
/**
- * @param $repo FileRepo
+ * @param bool|FileRepo $repo
*/
function __construct( $repo = false ) {
if ( $repo ) {
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index 5eec9a50..6924f0a6 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -42,17 +42,31 @@ class ForeignAPIRepo extends FileRepo {
* Update the version every time you make breaking or significant changes. */
const VERSION = "2.1";
- var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
- /* Check back with Commons after a day */
- var $apiThumbCacheExpiry = 86400; /* 24*60*60 */
- /* Redownload thumbnail files after a month */
- var $fileCacheExpiry = 2592000; /* 86400*30 */
+ /**
+ * List of iiprop values for the thumbnail fetch queries.
+ * @since 1.23
+ */
+ protected static $imageInfoProps = array(
+ 'url',
+ 'thumbnail',
+ 'timestamp',
+ );
+
+ protected $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
+ /** @var int Check back with Commons after a day (24*60*60) */
+ protected $apiThumbCacheExpiry = 86400;
+
+ /** @var int Redownload thumbnail files after a month (86400*30) */
+ protected $fileCacheExpiry = 2592000;
- protected $mQueryCache = array();
+ /** @var array */
protected $mFileExists = array();
+ /** @var array */
+ private $mQueryCache = array();
+
/**
- * @param $info array|null
+ * @param array|null $info
*/
function __construct( $info ) {
global $wgLocalFileRepo;
@@ -92,19 +106,20 @@ class ForeignAPIRepo extends FileRepo {
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
*
- * @param $title Title
- * @param $time string|bool
+ * @param Title $title
+ * @param string|bool $time
* @return File
*/
function newFile( $title, $time = false ) {
if ( $time ) {
return false;
}
+
return parent::newFile( $title, $time );
}
/**
- * @param $files array
+ * @param array $files
* @return array
*/
function fileExistsBatch( array $files ) {
@@ -126,8 +141,11 @@ class ForeignAPIRepo extends FileRepo {
}
}
- $data = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ),
- 'prop' => 'imageinfo' ) );
+ $data = $this->fetchImageQuery( array(
+ 'titles' => implode( $files, '|' ),
+ 'prop' => 'imageinfo' )
+ );
+
if ( isset( $data['query']['pages'] ) ) {
# First, get results from the query. Note we only care whether the image exists,
# not whether it has a description page.
@@ -151,11 +169,12 @@ class ForeignAPIRepo extends FileRepo {
$results[$key] = $this->mFileExists[$file];
}
}
+
return $results;
}
/**
- * @param $virtualUrl string
+ * @param string $virtualUrl
* @return bool
*/
function getFileProps( $virtualUrl ) {
@@ -163,11 +182,11 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * @param $query array
+ * @param array $query
* @return string
*/
function fetchImageQuery( $query ) {
- global $wgMemc, $wgLanguageCode;
+ global $wgLanguageCode;
$query = array_merge( $query,
array(
@@ -190,7 +209,7 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * @param $data array
+ * @param array $data
* @return bool|array
*/
function getImageInfo( $data ) {
@@ -201,11 +220,12 @@ class ForeignAPIRepo extends FileRepo {
}
}
}
+
return false;
}
/**
- * @param $hash string
+ * @param string $hash
* @return array
*/
function findBySha1( $hash ) {
@@ -224,21 +244,23 @@ class ForeignAPIRepo extends FileRepo {
$ret[] = new ForeignAPIFile( Title::makeTitle( NS_FILE, $img['name'] ), $this, $img );
}
}
+
return $ret;
}
/**
- * @param $name string
- * @param $width int
- * @param $height int
- * @param $result null
- * @param $otherParams string
+ * @param string $name
+ * @param int $width
+ * @param int $height
+ * @param array $result Out parameter that will be changed by the function.
+ * @param string $otherParams
+ *
* @return bool
*/
function getThumbUrl( $name, $width = -1, $height = -1, &$result = null, $otherParams = '' ) {
$data = $this->fetchImageQuery( array(
'titles' => 'File:' . $name,
- 'iiprop' => 'url|timestamp',
+ 'iiprop' => self::getIIProps(),
'iiurlwidth' => $width,
'iiurlheight' => $height,
'iiurlparam' => $otherParams,
@@ -248,6 +270,7 @@ class ForeignAPIRepo extends FileRepo {
if ( $data && $info && isset( $info['thumburl'] ) ) {
wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" );
$result = $info;
+
return $info['thumburl'];
} else {
return false;
@@ -255,17 +278,18 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * @param $name string
- * @param $width int
- * @param $height int
- * @param $otherParams string
+ * @param string $name
+ * @param int $width
+ * @param int $height
+ * @param string $otherParams
+ * @param string $lang Language code for language of error
* @return bool|MediaTransformError
* @since 1.22
*/
function getThumbError( $name, $width = -1, $height = -1, $otherParams = '', $lang = null ) {
$data = $this->fetchImageQuery( array(
'titles' => 'File:' . $name,
- 'iiprop' => 'url|timestamp',
+ 'iiprop' => self::getIIProps(),
'iiurlwidth' => $width,
'iiurlheight' => $height,
'iiurlparam' => $otherParams,
@@ -276,6 +300,7 @@ class ForeignAPIRepo extends FileRepo {
if ( $data && $info && isset( $info['thumberror'] ) ) {
wfDebug( __METHOD__ . " got remote thumb error " . $info['thumberror'] . "\n" );
+
return new MediaTransformError(
'thumbnail_error_remote',
$width,
@@ -294,10 +319,11 @@ class ForeignAPIRepo extends FileRepo {
* If the url has been requested today, get it from cache
* Otherwise retrieve remote thumb url, check for local file.
*
- * @param string $name is a dbkey form of a title
- * @param $width
- * @param $height
- * @param string $params Other rendering parameters (page number, etc) from handler's makeParamString.
+ * @param string $name Is a dbkey form of a title
+ * @param int $width
+ * @param int $height
+ * @param string $params Other rendering parameters (page number, etc)
+ * from handler's makeParamString.
* @return bool|string
*/
function getThumbUrlFromCache( $name, $width, $height, $params = "" ) {
@@ -322,6 +348,7 @@ class ForeignAPIRepo extends FileRepo {
if ( isset( $knownThumbUrls[$sizekey] ) ) {
wfDebug( __METHOD__ . ': Got thumburl from local cache: ' .
"{$knownThumbUrls[$sizekey]} \n" );
+
return $knownThumbUrls[$sizekey];
}
/* This size is not yet known */
@@ -332,6 +359,7 @@ class ForeignAPIRepo extends FileRepo {
if ( !$foreignUrl ) {
wfDebug( __METHOD__ . " Could not find thumburl\n" );
+
return false;
}
@@ -339,14 +367,17 @@ class ForeignAPIRepo extends FileRepo {
$fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
if ( !$this->validateFilename( $fileName ) ) {
wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" );
+
return false;
}
$localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name;
$localFilename = $localPath . "/" . $fileName;
- $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName );
+ $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) .
+ rawurlencode( $name ) . "/" . rawurlencode( $fileName );
if ( $backend->fileExists( array( 'src' => $localFilename ) )
- && isset( $metadata['timestamp'] ) ) {
+ && isset( $metadata['timestamp'] )
+ ) {
wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" );
$modified = $backend->getFileTimestamp( array( 'src' => $localFilename ) );
$remoteModified = strtotime( $metadata['timestamp'] );
@@ -356,6 +387,7 @@ class ForeignAPIRepo extends FileRepo {
/* Use our current and already downloaded thumbnail */
$knownThumbUrls[$sizekey] = $localUrl;
$wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
+
return $localUrl;
}
/* There is a new Commons file, or existing thumbnail older than a month */
@@ -363,6 +395,7 @@ class ForeignAPIRepo extends FileRepo {
$thumb = self::httpGet( $foreignUrl );
if ( !$thumb ) {
wfDebug( __METHOD__ . " Could not download thumb\n" );
+
return false;
}
@@ -371,19 +404,21 @@ class ForeignAPIRepo extends FileRepo {
$params = array( 'dst' => $localFilename, 'content' => $thumb );
if ( !$backend->quickCreate( $params )->isOK() ) {
wfDebug( __METHOD__ . " could not write to thumb path '$localFilename'\n" );
+
return $foreignUrl;
}
$knownThumbUrls[$sizekey] = $localUrl;
$wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" );
+
return $localUrl;
}
/**
* @see FileRepo::getZoneUrl()
- * @param $zone String
+ * @param string $zone
* @param string|null $ext Optional file extension
- * @return String
+ * @return string
*/
function getZoneUrl( $zone, $ext = null ) {
switch ( $zone ) {
@@ -398,7 +433,7 @@ class ForeignAPIRepo extends FileRepo {
/**
* Get the local directory corresponding to one of the basic zones
- * @param $zone string
+ * @param string $zone
* @return bool|null|string
*/
function getZonePath( $zone ) {
@@ -406,6 +441,7 @@ class ForeignAPIRepo extends FileRepo {
if ( in_array( $zone, $supported ) ) {
return parent::getZonePath( $zone );
}
+
return false;
}
@@ -450,6 +486,10 @@ class ForeignAPIRepo extends FileRepo {
$info['articlepath'] = $general['articlepath'];
$info['server'] = $general['server'];
+
+ if ( isset( $general['favicon'] ) ) {
+ $info['favicon'] = $general['favicon'];
+ }
}
return $info;
@@ -458,10 +498,10 @@ class ForeignAPIRepo extends FileRepo {
/**
* Like a Http:get request, but with custom User-Agent.
* @see Http:get
- * @param $url string
- * @param $timeout string
- * @param $options array
- * @return bool|String
+ * @param string $url
+ * @param string $timeout
+ * @param array $options
+ * @return bool|string
*/
public static function httpGet( $url, $timeout = 'default', $options = array() ) {
$options['timeout'] = $timeout;
@@ -486,10 +526,19 @@ class ForeignAPIRepo extends FileRepo {
}
/**
+ * @return string
+ * @since 1.23
+ */
+ protected static function getIIProps() {
+ return join( '|', self::$imageInfoProps );
+ }
+
+ /**
* HTTP GET request to a mediawiki API (with caching)
- * @param $target string Used in cache key creation, mostly
- * @param $query array The query parameters for the API request
- * @param $cacheTTL int Time to live for the memcached caching
+ * @param string $target Used in cache key creation, mostly
+ * @param array $query The query parameters for the API request
+ * @param int $cacheTTL Time to live for the memcached caching
+ * @return null
*/
public function httpGetCached( $target, $query, $cacheTTL = 3600 ) {
if ( $this->mApiBase ) {
@@ -526,7 +575,7 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * @param $callback Array|string
+ * @param callable $callback
* @throws MWException
*/
function enumFiles( $callback ) {
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index 37c65723..6e9e6add 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -27,17 +27,37 @@
* @ingroup FileRepo
*/
class ForeignDBRepo extends LocalRepo {
- # Settings
- var $dbType, $dbServer, $dbUser, $dbPassword, $dbName, $dbFlags,
- $tablePrefix, $hasSharedCache;
+ /** @var string */
+ protected $dbType;
+
+ /** @var string */
+ protected $dbServer;
+
+ /** @var string */
+ protected $dbUser;
+
+ /** @var string */
+ protected $dbPassword;
+
+ /** @var string */
+ protected $dbName;
+
+ /** @var string */
+ protected $dbFlags;
+
+ /** @var string */
+ protected $tablePrefix;
+
+ /** @var bool */
+ protected $hasSharedCache;
# Other stuff
- var $dbConn;
- var $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
- var $fileFromRowFactory = array( 'ForeignDBFile', 'newFromRow' );
+ protected $dbConn;
+ protected $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
+ protected $fileFromRowFactory = array( 'ForeignDBFile', 'newFromRow' );
/**
- * @param $info array|null
+ * @param array|null $info
*/
function __construct( $info ) {
parent::__construct( $info );
@@ -68,6 +88,7 @@ class ForeignDBRepo extends LocalRepo {
)
);
}
+
return $this->dbConn;
}
@@ -95,6 +116,7 @@ class ForeignDBRepo extends LocalRepo {
if ( $this->hasSharedCache() ) {
$args = func_get_args();
array_unshift( $args, $this->dbName, $this->tablePrefix );
+
return call_user_func_array( 'wfForeignMemcKey', $args );
} else {
return false;
@@ -104,4 +126,14 @@ class ForeignDBRepo extends LocalRepo {
protected function assertWritableRepo() {
throw new MWException( get_class( $this ) . ': write operations are not supported.' );
}
+
+ /**
+ * Return information about the repository.
+ *
+ * @return array
+ * @since 1.22
+ */
+ function getInfo() {
+ return FileRepo::getInfo();
+ }
}
diff --git a/includes/filerepo/ForeignDBViaLBRepo.php b/includes/filerepo/ForeignDBViaLBRepo.php
index 7951fb13..8153ffb4 100644
--- a/includes/filerepo/ForeignDBViaLBRepo.php
+++ b/includes/filerepo/ForeignDBViaLBRepo.php
@@ -27,12 +27,23 @@
* @ingroup FileRepo
*/
class ForeignDBViaLBRepo extends LocalRepo {
- var $wiki, $dbName, $tablePrefix;
- var $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
- var $fileFromRowFactory = array( 'ForeignDBFile', 'newFromRow' );
+ /** @var string */
+ protected $wiki;
+
+ /** @var string */
+ protected $dbName;
+
+ /** @var string */
+ protected $tablePrefix;
+
+ /** @var array */
+ protected $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
+
+ /** @var array */
+ protected $fileFromRowFactory = array( 'ForeignDBFile', 'newFromRow' );
/**
- * @param $info array|null
+ * @param array|null $info
*/
function __construct( $info ) {
parent::__construct( $info );
@@ -69,6 +80,7 @@ class ForeignDBViaLBRepo extends LocalRepo {
if ( $this->hasSharedCache() ) {
$args = func_get_args();
array_unshift( $args, $this->wiki );
+
return implode( ':', $args );
} else {
return false;
@@ -78,4 +90,8 @@ class ForeignDBViaLBRepo extends LocalRepo {
protected function assertWritableRepo() {
throw new MWException( get_class( $this ) . ': write operations are not supported.' );
}
+
+ public function getInfo() {
+ return FileRepo::getInfo();
+ }
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index 9b62243b..926fd0b8 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -29,16 +29,27 @@
* @ingroup FileRepo
*/
class LocalRepo extends FileRepo {
- var $fileFactory = array( 'LocalFile' , 'newFromTitle' );
- var $fileFactoryKey = array( 'LocalFile' , 'newFromKey' );
- var $fileFromRowFactory = array( 'LocalFile' , 'newFromRow' );
- var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
- var $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
- var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
+ /** @var array */
+ protected $fileFactory = array( 'LocalFile', 'newFromTitle' );
+
+ /** @var array */
+ protected $fileFactoryKey = array( 'LocalFile', 'newFromKey' );
+
+ /** @var array */
+ protected $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
+
+ /** @var array */
+ protected $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
+
+ /** @var array */
+ protected $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
+
+ /** @var array */
+ protected $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
/**
* @throws MWException
- * @param $row
+ * @param stdClass $row
* @return LocalFile
*/
function newFileFromRow( $row ) {
@@ -52,8 +63,8 @@ class LocalRepo extends FileRepo {
}
/**
- * @param $title
- * @param $archiveName
+ * @param Title $title
+ * @param string $archiveName
* @return OldLocalFile
*/
function newFromArchiveName( $title, $archiveName ) {
@@ -66,7 +77,7 @@ class LocalRepo extends FileRepo {
* interleave database locks with file operations, which is potentially a
* remote operation.
*
- * @param $storageKeys array
+ * @param array $storageKeys
*
* @return FileRepoStatus
*/
@@ -80,7 +91,7 @@ class LocalRepo extends FileRepo {
$hashPath = $this->getDeletedHashPath( $key );
$path = "$root/$hashPath$key";
$dbw->begin( __METHOD__ );
- // Check for usage in deleted/hidden files and pre-emptively
+ // Check for usage in deleted/hidden files and preemptively
// lock the key to avoid any future use until we are finished.
$deleted = $this->deletedFileHasKey( $key, 'lock' );
$hidden = $this->hiddenFileHasKey( $key, 'lock' );
@@ -97,6 +108,7 @@ class LocalRepo extends FileRepo {
}
$dbw->commit( __METHOD__ );
}
+
return $status;
}
@@ -111,6 +123,7 @@ class LocalRepo extends FileRepo {
$options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array();
$dbw = $this->getMasterDB();
+
return (bool)$dbw->selectField( 'filearchive', '1',
array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
__METHOD__, $options
@@ -131,6 +144,7 @@ class LocalRepo extends FileRepo {
$ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) );
$dbw = $this->getMasterDB();
+
return (bool)$dbw->selectField( 'oldimage', '1',
array( 'oi_sha1' => $sha1,
'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
@@ -152,8 +166,8 @@ class LocalRepo extends FileRepo {
/**
* Checks if there is a redirect named as $title
*
- * @param $title Title of file
- * @return bool
+ * @param Title $title Title of file
+ * @return bool|Title
*/
function checkRedirect( Title $title ) {
global $wgMemc;
@@ -178,6 +192,7 @@ class LocalRepo extends FileRepo {
$id = $this->getArticleID( $title );
if ( !$id ) {
$wgMemc->add( $memcKey, " ", $expiry );
+
return false;
}
$dbr = $this->getSlaveDB();
@@ -191,9 +206,11 @@ class LocalRepo extends FileRepo {
if ( $row && $row->rd_namespace == NS_FILE ) {
$targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title );
$wgMemc->add( $memcKey, $targetTitle->getDBkey(), $expiry );
+
return $targetTitle;
} else {
$wgMemc->add( $memcKey, '', $expiry );
+
return false;
}
}
@@ -202,7 +219,7 @@ class LocalRepo extends FileRepo {
* Function link Title::getArticleID().
* We can't say Title object, what database it should use, so we duplicate that function here.
*
- * @param $title Title
+ * @param Title $title
* @return bool|int|mixed
*/
protected function getArticleID( $title ) {
@@ -219,15 +236,141 @@ class LocalRepo extends FileRepo {
),
__METHOD__ //Function name
);
+
return $id;
}
+ public function findFiles( array $items, $flags = 0 ) {
+ $finalFiles = array(); // map of (DB key => corresponding File) for matches
+
+ $searchSet = array(); // map of (normalized DB key => search params)
+ foreach ( $items as $item ) {
+ if ( is_array( $item ) ) {
+ $title = File::normalizeTitle( $item['title'] );
+ if ( $title ) {
+ $searchSet[$title->getDBkey()] = $item;
+ }
+ } else {
+ $title = File::normalizeTitle( $item );
+ if ( $title ) {
+ $searchSet[$title->getDBkey()] = array();
+ }
+ }
+ }
+
+ $fileMatchesSearch = function ( File $file, array $search ) {
+ // Note: file name comparison done elsewhere (to handle redirects)
+ $user = ( !empty( $search['private'] ) && $search['private'] instanceof User )
+ ? $search['private']
+ : null;
+
+ return (
+ $file->exists() &&
+ (
+ ( empty( $search['time'] ) && !$file->isOld() ) ||
+ ( !empty( $search['time'] ) && $search['time'] === $file->getTimestamp() )
+ ) &&
+ ( !empty( $search['private'] ) || !$file->isDeleted( File::DELETED_FILE ) ) &&
+ $file->userCan( File::DELETED_FILE, $user )
+ );
+ };
+
+ $repo = $this;
+ $applyMatchingFiles = function ( ResultWrapper $res, &$searchSet, &$finalFiles )
+ use ( $repo, $fileMatchesSearch, $flags )
+ {
+ global $wgContLang;
+ $info = $repo->getInfo();
+ foreach ( $res as $row ) {
+ $file = $repo->newFileFromRow( $row );
+ // There must have been a search for this DB key, but this has to handle the
+ // cases were title capitalization is different on the client and repo wikis.
+ $dbKeysLook = array( str_replace( ' ', '_', $file->getName() ) );
+ if ( !empty( $info['initialCapital'] ) ) {
+ // Search keys for "hi.png" and "Hi.png" should use the "Hi.png file"
+ $dbKeysLook[] = $wgContLang->lcfirst( $file->getName() );
+ }
+ foreach ( $dbKeysLook as $dbKey ) {
+ if ( isset( $searchSet[$dbKey] )
+ && $fileMatchesSearch( $file, $searchSet[$dbKey] )
+ ) {
+ $finalFiles[$dbKey] = ( $flags & FileRepo::NAME_AND_TIME_ONLY )
+ ? array( 'title' => $dbKey, 'timestamp' => $file->getTimestamp() )
+ : $file;
+ unset( $searchSet[$dbKey] );
+ }
+ }
+ }
+ };
+
+ $dbr = $this->getSlaveDB();
+
+ // Query image table
+ $imgNames = array();
+ foreach ( array_keys( $searchSet ) as $dbKey ) {
+ $imgNames[] = $this->getNameFromTitle( File::normalizeTitle( $dbKey ) );
+ }
+
+ if ( count( $imgNames ) ) {
+ $res = $dbr->select( 'image',
+ LocalFile::selectFields(), array( 'img_name' => $imgNames ), __METHOD__ );
+ $applyMatchingFiles( $res, $searchSet, $finalFiles );
+ }
+
+ // Query old image table
+ $oiConds = array(); // WHERE clause array for each file
+ foreach ( $searchSet as $dbKey => $search ) {
+ if ( isset( $search['time'] ) ) {
+ $oiConds[] = $dbr->makeList(
+ array(
+ 'oi_name' => $this->getNameFromTitle( File::normalizeTitle( $dbKey ) ),
+ 'oi_timestamp' => $dbr->timestamp( $search['time'] )
+ ),
+ LIST_AND
+ );
+ }
+ }
+
+ if ( count( $oiConds ) ) {
+ $res = $dbr->select( 'oldimage',
+ OldLocalFile::selectFields(), $dbr->makeList( $oiConds, LIST_OR ), __METHOD__ );
+ $applyMatchingFiles( $res, $searchSet, $finalFiles );
+ }
+
+ // Check for redirects...
+ foreach ( $searchSet as $dbKey => $search ) {
+ if ( !empty( $search['ignoreRedirect'] ) ) {
+ continue;
+ }
+
+ $title = File::normalizeTitle( $dbKey );
+ $redir = $this->checkRedirect( $title ); // hopefully hits memcached
+
+ if ( $redir && $redir->getNamespace() == NS_FILE ) {
+ $file = $this->newFile( $redir );
+ if ( $file && $fileMatchesSearch( $file, $search ) ) {
+ $file->redirectedFrom( $title->getDBkey() );
+ if ( $flags & FileRepo::NAME_AND_TIME_ONLY ) {
+ $finalFiles[$dbKey] = array(
+ 'title' => $file->getTitle()->getDBkey(),
+ 'timestamp' => $file->getTimestamp()
+ );
+ } else {
+ $finalFiles[$dbKey] = $file;
+ }
+ }
+ }
+ }
+
+ return $finalFiles;
+ }
+
/**
* Get an array or iterator of file objects for files that have a given
* SHA-1 content hash.
*
- * @param string $hash a sha1 hash to look for
- * @return Array
+ * @param string $hash A sha1 hash to look for
+ * @return File[]
*/
function findBySha1( $hash ) {
$dbr = $this->getSlaveDB();
@@ -299,13 +442,14 @@ class LocalRepo extends FileRepo {
'img_name ' . $dbr->buildLike( $prefix, $dbr->anyString() ),
__METHOD__,
$selectOptions
- );
+ );
// Build file objects
$files = array();
foreach ( $res as $row ) {
$files[] = $this->newFileFromRow( $row );
}
+
return $files;
}
@@ -334,13 +478,14 @@ class LocalRepo extends FileRepo {
*/
function getSharedCacheKey( /*...*/ ) {
$args = func_get_args();
+
return call_user_func_array( 'wfMemcKey', $args );
}
/**
* Invalidates image redirect cache related to that image
*
- * @param $title Title of page
+ * @param Title $title Title of page
* @return void
*/
function invalidateImageRedirect( Title $title ) {
@@ -354,4 +499,18 @@ class LocalRepo extends FileRepo {
$wgMemc->set( $memcKey, ' PURGED', 12 );
}
}
+
+ /**
+ * Return information about the repository.
+ *
+ * @return array
+ * @since 1.22
+ */
+ function getInfo() {
+ global $wgFavicon;
+
+ return array_merge( parent::getInfo(), array(
+ 'favicon' => wfExpandUrl( $wgFavicon ),
+ ) );
+ }
}
diff --git a/includes/filerepo/NullRepo.php b/includes/filerepo/NullRepo.php
index dda51cea..f2b7395c 100644
--- a/includes/filerepo/NullRepo.php
+++ b/includes/filerepo/NullRepo.php
@@ -26,11 +26,11 @@
* @ingroup FileRepo
*/
class NullRepo extends FileRepo {
-
/**
- * @param $info array|null
+ * @param array|null $info
*/
- function __construct( $info ) {}
+ function __construct( $info ) {
+ }
protected function assertWritableRepo() {
throw new MWException( get_class( $this ) . ': write operations are not supported.' );
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index b2b9477a..fab42162 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -27,19 +27,28 @@
* @ingroup FileRepo
*/
class RepoGroup {
- /**
- * @var LocalRepo
- */
- var $localRepo;
+ /** @var LocalRepo */
+ protected $localRepo;
- var $foreignRepos, $reposInitialised = false;
- var $localInfo, $foreignInfo;
- var $cache;
+ /** @var FileRepo[] */
+ protected $foreignRepos;
- /**
- * @var RepoGroup
- */
+ /** @var bool */
+ protected $reposInitialised = false;
+
+ /** @var array */
+ protected $localInfo;
+
+ /** @var array */
+ protected $foreignInfo;
+
+ /** @var ProcessCacheLRU */
+ protected $cache;
+
+ /** @var RepoGroup */
protected static $instance;
+
+ /** Maximum number of cache items */
const MAX_CACHE_SIZE = 500;
/**
@@ -53,6 +62,7 @@ class RepoGroup {
}
global $wgLocalFileRepo, $wgForeignFileRepos;
self::$instance = new RepoGroup( $wgLocalFileRepo, $wgForeignFileRepos );
+
return self::$instance;
}
@@ -70,7 +80,7 @@ class RepoGroup {
* It's not enough to just create a superclass ... you have
* to get people to call into it even though all they know is RepoGroup::singleton()
*
- * @param $instance RepoGroup
+ * @param RepoGroup $instance
*/
static function setSingleton( $instance ) {
self::$instance = $instance;
@@ -80,35 +90,32 @@ class RepoGroup {
* Construct a group of file repositories.
*
* @param array $localInfo Associative array for local repo's info
- * @param array $foreignInfo of repository info arrays.
- * Each info array is an associative array with the 'class' member
- * giving the class name. The entire array is passed to the repository
- * constructor as the first parameter.
+ * @param array $foreignInfo Array of repository info arrays.
+ * Each info array is an associative array with the 'class' member
+ * giving the class name. The entire array is passed to the repository
+ * constructor as the first parameter.
*/
function __construct( $localInfo, $foreignInfo ) {
$this->localInfo = $localInfo;
$this->foreignInfo = $foreignInfo;
- $this->cache = array();
+ $this->cache = new ProcessCacheLRU( self::MAX_CACHE_SIZE );
}
/**
* Search repositories for an image.
* You can also use wfFindFile() to do this.
*
- * @param $title Title|string Title object or string
+ * @param Title|string $title Title object or string
* @param array $options Associative array of options:
- * time: requested time for an archived image, or false for the
- * current version. An image object will be returned which was
- * created at the specified time.
- *
- * ignoreRedirect: If true, do not follow file redirects
- *
- * private: If true, return restricted (deleted) files if the current
- * user is allowed to view them. Otherwise, such files will not
- * be found.
- *
- * bypassCache: If true, do not use the process-local cache of File objects
- * @return File object or false if it is not found
+ * time: requested time for an archived image, or false for the
+ * current version. An image object will be returned which was
+ * created at the specified time.
+ * ignoreRedirect: If true, do not follow file redirects
+ * private: If true, return restricted (deleted) files if the current
+ * user is allowed to view them. Otherwise, such files will not
+ * be found.
+ * bypassCache: If true, do not use the process-local cache of File objects
+ * @return File|bool False if title is not found
*/
function findFile( $title, $options = array() ) {
if ( !is_array( $options ) ) {
@@ -126,16 +133,12 @@ class RepoGroup {
# Check the cache
if ( empty( $options['ignoreRedirect'] )
&& empty( $options['private'] )
- && empty( $options['bypassCache'] ) )
- {
+ && empty( $options['bypassCache'] )
+ ) {
$time = isset( $options['time'] ) ? $options['time'] : '';
$dbkey = $title->getDBkey();
- if ( isset( $this->cache[$dbkey][$time] ) ) {
- wfDebug( __METHOD__ . ": got File:$dbkey from process cache\n" );
- # Move it to the end of the list so that we can delete the LRU entry later
- $this->pingCache( $dbkey );
- # Return the entry
- return $this->cache[$dbkey][$time];
+ if ( $this->cache->has( $dbkey, $time, 60 ) ) {
+ return $this->cache->get( $dbkey, $time );
}
$useCache = true;
} else {
@@ -158,18 +161,30 @@ class RepoGroup {
$image = $image ? $image : false; // type sanity
# Cache file existence or non-existence
if ( $useCache && ( !$image || $image->isCacheable() ) ) {
- $this->trimCache();
- $this->cache[$dbkey][$time] = $image;
+ $this->cache->set( $dbkey, $time, $image );
}
return $image;
}
/**
- * @param $inputItems array
- * @return array
+ * Search repositories for many files at once.
+ *
+ * @param array $inputItems An array of titles, or an array of findFile() options with
+ * the "title" option giving the title. Example:
+ *
+ * $findItem = array( 'title' => $title, 'private' => true );
+ * $findBatch = array( $findItem );
+ * $repo->findFiles( $findBatch );
+ *
+ * No title should appear in $items twice, as the result use titles as keys
+ * @param int $flags Supports:
+ * - FileRepo::NAME_AND_TIME_ONLY : return a (search title => (title,timestamp)) map.
+ * The search title uses the input titles; the other is the final post-redirect title.
+ * All titles are returned as string DB keys and the inner array is associative.
+ * @return array Map of (file name => File objects) for matches
*/
- function findFiles( $inputItems ) {
+ function findFiles( array $inputItems, $flags = 0 ) {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
@@ -185,7 +200,7 @@ class RepoGroup {
}
}
- $images = $this->localRepo->findFiles( $items );
+ $images = $this->localRepo->findFiles( $items, $flags );
foreach ( $this->foreignRepos as $repo ) {
// Remove found files from $items
@@ -193,15 +208,16 @@ class RepoGroup {
unset( $items[$name] );
}
- $images = array_merge( $images, $repo->findFiles( $items ) );
+ $images = array_merge( $images, $repo->findFiles( $items, $flags ) );
}
+
return $images;
}
/**
* Interface for FileRepo::checkRedirect()
- * @param $title Title
- * @return bool
+ * @param Title $title
+ * @return bool|Title
*/
function checkRedirect( Title $title ) {
if ( !$this->reposInitialised ) {
@@ -212,12 +228,14 @@ class RepoGroup {
if ( $redir ) {
return $redir;
}
+
foreach ( $this->foreignRepos as $repo ) {
$redir = $repo->checkRedirect( $title );
if ( $redir ) {
return $redir;
}
}
+
return false;
}
@@ -225,9 +243,9 @@ class RepoGroup {
* Find an instance of the file with this key, created at the specified time
* Returns false if the file does not exist.
*
- * @param string $hash base 36 SHA-1 hash
+ * @param string $hash Base 36 SHA-1 hash
* @param array $options Option array, same as findFile()
- * @return File object or false if it is not found
+ * @return File|bool File object or false if it is not found
*/
function findFileFromKey( $hash, $options = array() ) {
if ( !$this->reposInitialised ) {
@@ -243,14 +261,15 @@ class RepoGroup {
}
}
}
+
return $file;
}
/**
* Find all instances of files with this key
*
- * @param string $hash base 36 SHA-1 hash
- * @return Array of File objects
+ * @param string $hash Base 36 SHA-1 hash
+ * @return File[]
*/
function findBySha1( $hash ) {
if ( !$this->reposInitialised ) {
@@ -262,14 +281,15 @@ class RepoGroup {
$result = array_merge( $result, $repo->findBySha1( $hash ) );
}
usort( $result, 'File::compare' );
+
return $result;
}
/**
* Find all instances of files with this keys
*
- * @param array $hashes base 36 SHA-1 hashes
- * @return Array of array of File objects
+ * @param array $hashes Base 36 SHA-1 hashes
+ * @return array Array of array of File objects
*/
function findBySha1s( array $hashes ) {
if ( !$this->reposInitialised ) {
@@ -284,12 +304,13 @@ class RepoGroup {
foreach ( $result as $hash => $files ) {
usort( $result[$hash], 'File::compare' );
}
+
return $result;
}
/**
* Get the repo instance with a given key.
- * @param $index string|int
+ * @param string|int $index
* @return bool|LocalRepo
*/
function getRepo( $index ) {
@@ -307,7 +328,7 @@ class RepoGroup {
/**
* Get the repo instance by its name
- * @param $name string
+ * @param string $name
* @return bool
*/
function getRepoByName( $name ) {
@@ -319,6 +340,7 @@ class RepoGroup {
return $repo;
}
}
+
return false;
}
@@ -336,25 +358,32 @@ class RepoGroup {
* Call a function for each foreign repo, with the repo object as the
* first parameter.
*
- * @param $callback Callback: the function to call
- * @param array $params optional additional parameters to pass to the function
+ * @param callable $callback The function to call
+ * @param array $params Optional additional parameters to pass to the function
* @return bool
*/
function forEachForeignRepo( $callback, $params = array() ) {
+ if ( !$this->reposInitialised ) {
+ $this->initialiseRepos();
+ }
foreach ( $this->foreignRepos as $repo ) {
$args = array_merge( array( $repo ), $params );
if ( call_user_func_array( $callback, $args ) ) {
return true;
}
}
+
return false;
}
/**
* Does the installation have any foreign repos set up?
- * @return Boolean
+ * @return bool
*/
function hasForeignRepos() {
+ if ( !$this->reposInitialised ) {
+ $this->initialiseRepos();
+ }
return (bool)$this->foreignRepos;
}
@@ -376,17 +405,20 @@ class RepoGroup {
/**
* Create a repo class based on an info structure
+ * @param array $info
+ * @return FileRepo
*/
protected function newRepo( $info ) {
$class = $info['class'];
+
return new $class( $info );
}
/**
* Split a virtual URL into repo, zone and rel parts
- * @param $url string
+ * @param string $url
* @throws MWException
- * @return array containing repo, zone and rel
+ * @return array Containing repo, zone and rel
*/
function splitVirtualUrl( $url ) {
if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
@@ -397,11 +429,12 @@ class RepoGroup {
if ( count( $bits ) != 3 ) {
throw new MWException( __METHOD__ . ": invalid mwrepo URL: $url" );
}
+
return $bits;
}
/**
- * @param $fileName string
+ * @param string $fileName
* @return array
*/
function getFileProps( $fileName ) {
@@ -411,6 +444,7 @@ class RepoGroup {
$repoName = 'local';
}
$repo = $this->getRepo( $repoName );
+
return $repo->getFileProps( $fileName );
} else {
return FSFile::getPropsFromPath( $fileName );
@@ -418,40 +452,14 @@ class RepoGroup {
}
/**
- * Move a cache entry to the top (such as when accessed)
- */
- protected function pingCache( $key ) {
- if ( isset( $this->cache[$key] ) ) {
- $tmp = $this->cache[$key];
- unset( $this->cache[$key] );
- $this->cache[$key] = $tmp;
- }
- }
-
- /**
- * Limit cache memory
- */
- protected function trimCache() {
- while ( count( $this->cache ) >= self::MAX_CACHE_SIZE ) {
- reset( $this->cache );
- $key = key( $this->cache );
- wfDebug( __METHOD__ . ": evicting $key\n" );
- unset( $this->cache[$key] );
- }
- }
-
- /**
* Clear RepoGroup process cache used for finding a file
- * @param $title Title|null Title of the file or null to clear all files
+ * @param Title|null $title Title of the file or null to clear all files
*/
public function clearCache( Title $title = null ) {
if ( $title == null ) {
- $this->cache = array();
+ $this->cache->clear();
} else {
- $dbKey = $title->getDBkey();
- if ( isset( $this->cache[$dbKey] ) ) {
- unset( $this->cache[$dbKey] );
- }
+ $this->cache->clear( $title->getDBkey() );
}
}
}
diff --git a/includes/filerepo/file/ArchivedFile.php b/includes/filerepo/file/ArchivedFile.php
index 749f11a5..5b0d8e2b 100644
--- a/includes/filerepo/file/ArchivedFile.php
+++ b/includes/filerepo/file/ArchivedFile.php
@@ -27,40 +27,73 @@
* @ingroup FileAbstraction
*/
class ArchivedFile {
- /**#@+
- * @private
- */
- var $id, # filearchive row ID
- $name, # image name
- $group, # FileStore storage group
- $key, # FileStore sha1 key
- $size, # file dimensions
- $bits, # size in bytes
- $width, # width
- $height, # height
- $metadata, # metadata string
- $mime, # mime type
- $media_type, # media type
- $description, # upload description
- $user, # user ID of uploader
- $user_text, # user name of uploader
- $timestamp, # time of upload
- $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
- $deleted, # Bitfield akin to rev_deleted
- $sha1, # sha1 hash of file content
- $pageCount,
- $archive_name;
+ /** @var int Filearchive row ID */
+ private $id;
- /**
- * @var MediaHandler
- */
- var $handler;
- /**
- * @var Title
+ /** @var string File name */
+ private $name;
+
+ /** @var string FileStore storage group */
+ private $group;
+
+ /** @var string FileStore SHA-1 key */
+ private $key;
+
+ /** @var int File size in bytes */
+ private $size;
+
+ /** @var int Size in bytes */
+ private $bits;
+
+ /** @var int Width */
+ private $width;
+
+ /** @var int Height */
+ private $height;
+
+ /** @var string Metadata string */
+ private $metadata;
+
+ /** @var string MIME type */
+ private $mime;
+
+ /** @var string Media type */
+ private $media_type;
+
+ /** @var string Upload description */
+ private $description;
+
+ /** @var int User ID of uploader */
+ private $user;
+
+ /** @var string User name of uploader */
+ private $user_text;
+
+ /** @var string Time of upload */
+ private $timestamp;
+
+ /** @var bool Whether or not all this has been loaded from the database (loadFromXxx) */
+ private $dataLoaded;
+
+ /** @var int Bitfield akin to rev_deleted */
+ private $deleted;
+
+ /** @var string SHA-1 hash of file content */
+ private $sha1;
+
+ /** @var string Number of pages of a multipage document, or false for
+ * documents which aren't multipage documents
*/
- var $title; # image title
+ private $pageCount;
+
+ /** @var string Original base filename */
+ private $archive_name;
- /**#@-*/
+ /** @var MediaHandler */
+ protected $handler;
+
+ /** @var Title */
+ protected $title; # image title
/**
* @throws MWException
@@ -162,13 +195,13 @@ class ArchivedFile {
/**
* Loads a file object from the filearchive table
*
- * @param $row
- *
+ * @param stdClass $row
* @return ArchivedFile
*/
public static function newFromRow( $row ) {
$file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) );
$file->loadFromRow( $row );
+
return $file;
}
@@ -204,7 +237,7 @@ class ArchivedFile {
/**
* Load ArchivedFile object fields from a DB row.
*
- * @param $row Object database row
+ * @param stdClass $row Object database row
* @since 1.21
*/
public function loadFromRow( $row ) {
@@ -231,6 +264,9 @@ class ArchivedFile {
// old row, populate from key
$this->sha1 = LocalRepo::getHashFromKey( $this->key );
}
+ if ( !$this->title ) {
+ $this->title = Title::makeTitleSafe( NS_FILE, $row->fa_name );
+ }
}
/**
@@ -239,6 +275,9 @@ class ArchivedFile {
* @return Title
*/
public function getTitle() {
+ if ( !$this->title ) {
+ $this->load();
+ }
return $this->title;
}
@@ -248,6 +287,10 @@ class ArchivedFile {
* @return string
*/
public function getName() {
+ if ( $this->name === false ) {
+ $this->load();
+ }
+
return $this->name;
}
@@ -256,6 +299,7 @@ class ArchivedFile {
*/
public function getID() {
$this->load();
+
return $this->id;
}
@@ -264,6 +308,7 @@ class ArchivedFile {
*/
public function exists() {
$this->load();
+
return $this->exists;
}
@@ -273,6 +318,7 @@ class ArchivedFile {
*/
public function getKey() {
$this->load();
+
return $this->key;
}
@@ -298,6 +344,7 @@ class ArchivedFile {
*/
public function getWidth() {
$this->load();
+
return $this->width;
}
@@ -307,6 +354,7 @@ class ArchivedFile {
*/
public function getHeight() {
$this->load();
+
return $this->height;
}
@@ -316,6 +364,7 @@ class ArchivedFile {
*/
public function getMetadata() {
$this->load();
+
return $this->metadata;
}
@@ -325,6 +374,7 @@ class ArchivedFile {
*/
public function getSize() {
$this->load();
+
return $this->size;
}
@@ -334,15 +384,17 @@ class ArchivedFile {
*/
public function getBits() {
$this->load();
+
return $this->bits;
}
/**
- * Returns the mime type of the file.
+ * Returns the MIME type of the file.
* @return string
*/
public function getMimeType() {
$this->load();
+
return $this->mime;
}
@@ -354,12 +406,14 @@ class ArchivedFile {
if ( !isset( $this->handler ) ) {
$this->handler = MediaHandler::getHandler( $this->getMimeType() );
}
+
return $this->handler;
}
/**
* Returns the number of pages of a multipage document, or false for
* documents which aren't multipage documents
+ * @return bool|int
*/
function pageCount() {
if ( !isset( $this->pageCount ) ) {
@@ -369,6 +423,7 @@ class ArchivedFile {
$this->pageCount = false;
}
}
+
return $this->pageCount;
}
@@ -379,6 +434,7 @@ class ArchivedFile {
*/
public function getMediaType() {
$this->load();
+
return $this->media_type;
}
@@ -389,6 +445,7 @@ class ArchivedFile {
*/
public function getTimestamp() {
$this->load();
+
return wfTimestamp( TS_MW, $this->timestamp );
}
@@ -400,29 +457,40 @@ class ArchivedFile {
*/
function getSha1() {
$this->load();
+
return $this->sha1;
}
/**
- * Return the user ID of the uploader.
+ * Returns ID or name of user who uploaded the file
*
- * @return int
+ * @note Prior to MediaWiki 1.23, this method always
+ * returned the user id, and was inconsistent with
+ * the rest of the file classes.
+ * @param string $type 'text' or 'id'
+ * @return int|string
+ * @throws MWException
*/
- public function getUser() {
+ public function getUser( $type = 'text' ) {
$this->load();
- if ( $this->isDeleted( File::DELETED_USER ) ) {
- return 0;
- } else {
+
+ if ( $type == 'text' ) {
+ return $this->user_text;
+ } elseif ( $type == 'id' ) {
return $this->user;
}
+
+ throw new MWException( "Unknown type '$type'." );
}
/**
* Return the user name of the uploader.
*
+ * @deprecated since 1.23 Use getUser( 'text' ) instead.
* @return string
*/
public function getUserText() {
+ wfDeprecated( __METHOD__, '1.23' );
$this->load();
if ( $this->isDeleted( File::DELETED_USER ) ) {
return 0;
@@ -452,6 +520,7 @@ class ArchivedFile {
*/
public function getRawUser() {
$this->load();
+
return $this->user;
}
@@ -462,6 +531,7 @@ class ArchivedFile {
*/
public function getRawUserText() {
$this->load();
+
return $this->user_text;
}
@@ -472,6 +542,7 @@ class ArchivedFile {
*/
public function getRawDescription() {
$this->load();
+
return $this->description;
}
@@ -481,29 +552,33 @@ class ArchivedFile {
*/
public function getVisibility() {
$this->load();
+
return $this->deleted;
}
/**
* for file or revision rows
*
- * @param $field Integer: one of DELETED_* bitfield constants
+ * @param int $field One of DELETED_* bitfield constants
* @return bool
*/
public function isDeleted( $field ) {
$this->load();
+
return ( $this->deleted & $field ) == $field;
}
/**
* Determine if the current user is allowed to view a particular
* field of this FileStore image file, if it's marked as deleted.
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
+ * @param int $field
+ * @param null|User $user User object to check, or null to use $wgUser
* @return bool
*/
public function userCan( $field, User $user = null ) {
$this->load();
- return Revision::userCanBitfield( $this->deleted, $field, $user );
+
+ $title = $this->getTitle();
+ return Revision::userCanBitfield( $this->deleted, $field, $user, $title ? : null );
}
}
diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php
index ec5f927b..b574c5e7 100644
--- a/includes/filerepo/file/File.php
+++ b/includes/filerepo/file/File.php
@@ -91,45 +91,67 @@ abstract class File {
* The following member variables are not lazy-initialised
*/
- /**
- * @var FileRepo|bool
- */
- var $repo;
+ /** @var FileRepo|LocalRepo|ForeignAPIRepo|bool */
+ public $repo;
- /**
- * @var Title
- */
- var $title;
+ /** @var Title|string|bool */
+ protected $title;
- var $lastError, $redirected, $redirectedTitle;
+ /** @var string Text of last error */
+ protected $lastError;
- /**
- * @var FSFile|bool False if undefined
- */
+ /** @var string Main part of the title, with underscores (Title::getDBkey) */
+ protected $redirected;
+
+ /** @var Title */
+ protected $redirectedTitle;
+
+ /** @var FSFile|bool False if undefined */
protected $fsFile;
- /**
- * @var MediaHandler
- */
+ /** @var MediaHandler */
protected $handler;
- /**
- * @var string
+ /** @var string The URL corresponding to one of the four basic zones */
+ protected $url;
+
+ /** @var string File extension */
+ protected $extension;
+
+ /** @var string The name of a file from its title object */
+ protected $name;
+
+ /** @var string The storage path corresponding to one of the zones */
+ protected $path;
+
+ /** @var string Relative path including trailing slash */
+ protected $hashPath;
+
+ /** @var string Number of pages of a multipage document, or false for
+ * documents which aren't multipage documents
*/
- protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript;
+ protected $pageCount;
+ /** @var string URL of transformscript (for example thumb.php) */
+ protected $transformScript;
+
+ /** @var Title */
protected $redirectTitle;
- /**
- * @var bool
- */
- protected $canRender, $isSafeFile;
+ /** @var bool Wether the output of transform() for this file is likely to be valid. */
+ protected $canRender;
- /**
- * @var string Required Repository class type
+ /** @var bool Wether this media file is in a format that is unlikely to
+ * contain viruses or malicious content
*/
+ protected $isSafeFile;
+
+ /** @var string Required Repository class type */
protected $repoClass = 'FileRepo';
+ /** @var array Cache of tmp filepaths pointing to generated bucket thumbnails, keyed by width */
+ protected $tmpBucketedThumbCache = array();
+
/**
* Call this constructor from child classes.
*
@@ -137,8 +159,8 @@ abstract class File {
* may return false or throw exceptions if they are not set.
* Most subclasses will want to call assertRepoDefined() here.
*
- * @param $title Title|string|bool
- * @param $repo FileRepo|bool
+ * @param Title|string|bool $title
+ * @param FileRepo|bool $repo
*/
function __construct( $title, $repo ) {
if ( $title !== false ) { // subclasses may not use MW titles
@@ -152,7 +174,7 @@ abstract class File {
* Given a string or Title object return either a
* valid Title object with namespace NS_FILE or null
*
- * @param $title Title|string
+ * @param Title|string $title
* @param string|bool $exception Use 'exception' to throw an error on bad titles
* @throws MWException
* @return Title|null
@@ -174,6 +196,7 @@ abstract class File {
if ( !$ret && $exception !== false ) {
throw new MWException( "`$title` is not a valid file title." );
}
+
return $ret;
}
@@ -183,6 +206,7 @@ abstract class File {
return null;
} else {
$this->$name = call_user_func( $function );
+
return $this->$name;
}
}
@@ -214,7 +238,7 @@ abstract class File {
/**
* Checks if file extensions are compatible
*
- * @param $old File Old file
+ * @param File $old Old file
* @param string $new New name
*
* @return bool|null
@@ -224,6 +248,7 @@ abstract class File {
$n = strrpos( $new, '.' );
$newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' );
$mimeMagic = MimeMagic::singleton();
+
return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
}
@@ -232,7 +257,8 @@ abstract class File {
* Called by ImagePage
* STUB
*/
- function upgradeRow() {}
+ function upgradeRow() {
+ }
/**
* Split an internet media type into its two components; if not
@@ -252,10 +278,9 @@ abstract class File {
/**
* Callback for usort() to do file sorts by name
*
- * @param $a File
- * @param $b File
- *
- * @return Integer: result of name comparison
+ * @param File $a
+ * @param File $b
+ * @return int Result of name comparison
*/
public static function compare( File $a, File $b ) {
return strcmp( $a->getName(), $b->getName() );
@@ -271,6 +296,7 @@ abstract class File {
$this->assertRepoDefined();
$this->name = $this->repo->getNameFromTitle( $this->title );
}
+
return $this->name;
}
@@ -285,6 +311,7 @@ abstract class File {
$this->extension = self::normalizeExtension(
$n ? substr( $this->getName(), $n + 1 ) : '' );
}
+
return $this->extension;
}
@@ -306,6 +333,7 @@ abstract class File {
if ( $this->redirected ) {
return $this->getRedirectedTitle();
}
+
return $this->title;
}
@@ -320,6 +348,7 @@ abstract class File {
$ext = $this->getExtension();
$this->url = $this->repo->getZoneUrl( 'public', $ext ) . '/' . $this->getUrlRel();
}
+
return $this->url;
}
@@ -328,7 +357,7 @@ abstract class File {
* Upload URL paths _may or may not_ be fully qualified, so
* we check. Local paths are assumed to belong on $wgServer.
*
- * @return String
+ * @return string
*/
public function getFullUrl() {
return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
@@ -351,6 +380,7 @@ abstract class File {
} else {
wfDebug( __METHOD__ . ': supposed to render ' . $this->getName() .
' (' . $this->getMimeType() . "), but can't!\n" );
+
return $this->getURL(); #hm... return NULL?
}
} else {
@@ -376,6 +406,7 @@ abstract class File {
$this->assertRepoDefined();
$this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
}
+
return $this->path;
}
@@ -394,6 +425,7 @@ abstract class File {
$this->fsFile = false; // null => false; cache negative hits
}
}
+
return ( $this->fsFile )
? $this->fsFile->getPath()
: false;
@@ -406,9 +438,8 @@ abstract class File {
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
*
- * @param $page int
- *
- * @return number
+ * @param int $page
+ * @return int|bool
*/
public function getWidth( $page = 1 ) {
return false;
@@ -421,20 +452,62 @@ abstract class File {
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
*
- * @param $page int
- *
- * @return bool|number False on failure
+ * @param int $page
+ * @return bool|int False on failure
*/
public function getHeight( $page = 1 ) {
return false;
}
/**
+ * Return the smallest bucket from $wgThumbnailBuckets which is at least
+ * $wgThumbnailMinimumBucketDistance larger than $desiredWidth. The returned bucket, if any,
+ * will always be bigger than $desiredWidth.
+ *
+ * @param int $desiredWidth
+ * @param int $page
+ * @return bool|int
+ */
+ public function getThumbnailBucket( $desiredWidth, $page = 1 ) {
+ global $wgThumbnailBuckets, $wgThumbnailMinimumBucketDistance;
+
+ $imageWidth = $this->getWidth( $page );
+
+ if ( $imageWidth === false ) {
+ return false;
+ }
+
+ if ( $desiredWidth > $imageWidth ) {
+ return false;
+ }
+
+ if ( !$wgThumbnailBuckets ) {
+ return false;
+ }
+
+ $sortedBuckets = $wgThumbnailBuckets;
+
+ sort( $sortedBuckets );
+
+ foreach ( $sortedBuckets as $bucket ) {
+ if ( $bucket > $imageWidth ) {
+ return false;
+ }
+
+ if ( $bucket - $wgThumbnailMinimumBucketDistance > $desiredWidth ) {
+ return $bucket;
+ }
+ }
+
+ // Image is bigger than any available bucket
+ return false;
+ }
+
+ /**
* Returns ID or name of user who uploaded the file
* STUB
*
* @param string $type 'text' or 'id'
- *
* @return string|int
*/
public function getUser( $type = 'text' ) {
@@ -444,7 +517,7 @@ abstract class File {
/**
* Get the duration of a media file in seconds
*
- * @return number
+ * @return int
*/
public function getLength() {
$handler = $this->getHandler();
@@ -470,11 +543,47 @@ abstract class File {
}
/**
+ * Gives a (possibly empty) list of languages to render
+ * the file in.
+ *
+ * If the file doesn't have translations, or if the file
+ * format does not support that sort of thing, returns
+ * an empty array.
+ *
+ * @return array
+ * @since 1.23
+ */
+ public function getAvailableLanguages() {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ return $handler->getAvailableLanguages( $this );
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * In files that support multiple language, what is the default language
+ * to use if none specified.
+ *
+ * @return string Lang code, or null if filetype doesn't support multiple languages.
+ * @since 1.23
+ */
+ public function getDefaultRenderLanguage() {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ return $handler->getDefaultRenderLanguage( $this );
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Will the thumbnail be animated if one would expect it to be.
*
* Currently used to add a warning to the image description page
*
- * @return bool false if the main image is both animated
+ * @return bool False if the main image is both animated
* and the thumbnail is not. In all other cases must return
* true. If image is not renderable whatsoever, should
* return true.
@@ -506,18 +615,35 @@ abstract class File {
* Get handler-specific metadata
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
- * @return bool
+ * @return bool|array
*/
public function getMetadata() {
return false;
}
/**
+ * Like getMetadata but returns a handler independent array of common values.
+ * @see MediaHandler::getCommonMetaArray()
+ * @return array|bool Array or false if not supported
+ * @since 1.23
+ */
+ public function getCommonMetaArray() {
+ $handler = $this->getHandler();
+
+ if ( !$handler ) {
+ return false;
+ }
+
+ return $handler->getCommonMetaArray( $this );
+ }
+
+ /**
* get versioned metadata
*
- * @param $metadata Mixed Array or String of (serialized) metadata
- * @param $version integer version number.
- * @return Array containing metadata, or what was passed to it on fail (unserializing if not array)
+ * @param array|string $metadata Array or string of (serialized) metadata
+ * @param int $version Version number.
+ * @return array Array containing metadata, or what was passed to it on fail
+ * (unserializing if not array)
*/
public function convertMetadataVersion( $metadata, $version ) {
$handler = $this->getHandler();
@@ -553,7 +679,7 @@ abstract class File {
}
/**
- * Returns the mime type of the file.
+ * Returns the MIME type of the file.
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
*
@@ -588,8 +714,9 @@ abstract class File {
*/
function canRender() {
if ( !isset( $this->canRender ) ) {
- $this->canRender = $this->getHandler() && $this->handler->canRender( $this );
+ $this->canRender = $this->getHandler() && $this->handler->canRender( $this ) && $this->exists();
}
+
return $this->canRender;
}
@@ -639,8 +766,9 @@ abstract class File {
*/
function isSafeFile() {
if ( !isset( $this->isSafeFile ) ) {
- $this->isSafeFile = $this->_getIsSafeFile();
+ $this->isSafeFile = $this->getIsSafeFileUncached();
}
+
return $this->isSafeFile;
}
@@ -658,7 +786,7 @@ abstract class File {
*
* @return bool
*/
- protected function _getIsSafeFile() {
+ protected function getIsSafeFileUncached() {
global $wgTrustedMediaFormats;
if ( $this->allowInlineDisplay() ) {
@@ -713,7 +841,7 @@ abstract class File {
*
* Overridden by LocalFile to avoid unnecessary stat calls.
*
- * @return boolean Whether file exists in the repository.
+ * @return bool Whether file exists in the repository.
*/
public function exists() {
return $this->getPath() && $this->repo->fileExists( $this->path );
@@ -723,7 +851,7 @@ abstract class File {
* Returns true if file exists in the repository and can be included in a page.
* It would be unsafe to include private images, making public thumbnails inadvertently
*
- * @return boolean Whether file exists in the repository and is includable.
+ * @return bool Whether file exists in the repository and is includable.
*/
public function isVisible() {
return $this->exists();
@@ -742,13 +870,14 @@ abstract class File {
}
}
}
+
return $this->transformScript;
}
/**
* Get a ThumbnailImage which is the same size as the source
*
- * @param $handlerParams array
+ * @param array $handlerParams
*
* @return string
*/
@@ -760,6 +889,9 @@ abstract class File {
return $this->iconThumb();
}
$hp['width'] = $width;
+ // be sure to ignore any height specification as well (bug 62258)
+ unset( $hp['height'] );
+
return $this->transform( $hp );
}
@@ -768,14 +900,15 @@ abstract class File {
* Use File::THUMB_FULL_NAME to always get a name like "<params>-<source>".
* Otherwise, the format may be "<params>-<source>" or "<params>-thumbnail.<ext>".
*
- * @param array $params handler-specific parameters
- * @param $flags integer Bitfield that supports THUMB_* constants
+ * @param array $params Handler-specific parameters
+ * @param int $flags Bitfield that supports THUMB_* constants
* @return string
*/
public function thumbName( $params, $flags = 0 ) {
$name = ( $this->repo && !( $flags & self::THUMB_FULL_NAME ) )
? $this->repo->nameForThumb( $this->getName() )
: $this->getName();
+
return $this->generateThumbName( $name, $params );
}
@@ -784,7 +917,6 @@ abstract class File {
*
* @param string $name
* @param array $params Parameters which will be passed to MediaHandler::makeParamString
- *
* @return string
*/
public function generateThumbName( $name, $params ) {
@@ -792,12 +924,13 @@ abstract class File {
return null;
}
$extension = $this->getExtension();
- list( $thumbExt, $thumbMime ) = $this->handler->getThumbType(
+ list( $thumbExt, ) = $this->getHandler()->getThumbType(
$extension, $this->getMimeType(), $params );
- $thumbName = $this->handler->makeParamString( $params ) . '-' . $name;
+ $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $name;
if ( $thumbExt != $extension ) {
$thumbName .= ".$thumbExt";
}
+
return $thumbName;
}
@@ -813,8 +946,8 @@ abstract class File {
* specified, the generated image will be no bigger than width x height,
* and will also have correct aspect ratio.
*
- * @param $width Integer: maximum width of the generated thumbnail
- * @param $height Integer: maximum height of the image (optional)
+ * @param int $width Maximum width of the generated thumbnail
+ * @param int $height Maximum height of the image (optional)
*
* @return string
*/
@@ -824,9 +957,10 @@ abstract class File {
$params['height'] = $height;
}
$thumb = $this->transform( $params );
- if ( is_null( $thumb ) || $thumb->isError() ) {
+ if ( !$thumb || $thumb->isError() ) {
return '';
}
+
return $thumb->getUrl();
}
@@ -835,8 +969,8 @@ abstract class File {
*
* @param string $thumbPath Thumbnail storage path
* @param string $thumbUrl Thumbnail URL
- * @param $params Array
- * @param $flags integer
+ * @param array $params
+ * @param int $flags
* @return MediaTransformOutput
*/
protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
@@ -854,13 +988,13 @@ abstract class File {
/**
* Transform a media file
*
- * @param array $params an associative array of handler-specific parameters.
- * Typical keys are width, height and page.
- * @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering
+ * @param array $params An associative array of handler-specific parameters.
+ * Typical keys are width, height and page.
+ * @param int $flags A bitfield, may contain self::RENDER_NOW to force rendering
* @return MediaTransformOutput|bool False on failure
*/
function transform( $params, $flags = 0 ) {
- global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
+ global $wgThumbnailEpoch;
wfProfileIn( __METHOD__ );
do {
@@ -895,15 +1029,13 @@ abstract class File {
if ( $this->repo ) {
// Defer rendering if a 404 handler is set up...
if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
- wfDebug( __METHOD__ . " transformation deferred." );
+ wfDebug( __METHOD__ . " transformation deferred.\n" );
// XXX: Pass in the storage path even though we are not rendering anything
// and the path is supposed to be an FS path. This is due to getScalerType()
// getting called on the path and clobbering $thumb->getUrl() if it's false.
$thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
break;
}
- // Clean up broken thumbnails as needed
- $this->migrateThumbFile( $thumbName );
// Check if an up-to-date thumbnail already exists...
wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" );
if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) {
@@ -919,94 +1051,274 @@ abstract class File {
} elseif ( $flags & self::RENDER_FORCE ) {
wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
}
- }
- // If the backend is ready-only, don't keep generating thumbnails
- // only to return transformation errors, just return the error now.
- if ( $this->repo->getReadOnlyReason() !== false ) {
- $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
- break;
+ // If the backend is ready-only, don't keep generating thumbnails
+ // only to return transformation errors, just return the error now.
+ if ( $this->repo->getReadOnlyReason() !== false ) {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
+ break;
+ }
}
- // Create a temp FS file with the same extension and the thumbnail
- $thumbExt = FileBackend::extensionFromPath( $thumbPath );
- $tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
+ $tmpFile = $this->makeTransformTmpFile( $thumbPath );
+
if ( !$tmpFile ) {
$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
- break;
+ } else {
+ $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
}
- $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
-
- // Actually render the thumbnail...
- wfProfileIn( __METHOD__ . '-doTransform' );
- $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
- wfProfileOut( __METHOD__ . '-doTransform' );
- $tmpFile->bind( $thumb ); // keep alive with $thumb
-
- if ( !$thumb ) { // bad params?
- $thumb = null;
- } elseif ( $thumb->isError() ) { // transform error
- $this->lastError = $thumb->toText();
- // Ignore errors if requested
- if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
- $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
- }
- } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
- // Copy the thumbnail from the file system into storage...
- $disposition = $this->getThumbDisposition( $thumbName );
- $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
- if ( $status->isOK() ) {
- $thumb->setStoragePath( $thumbPath );
- } else {
- $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
+ } while ( false );
+
+ wfProfileOut( __METHOD__ );
+
+ return is_object( $thumb ) ? $thumb : false;
+ }
+
+ /**
+ * Generates a thumbnail according to the given parameters and saves it to storage
+ * @param TempFSFile $tmpFile Temporary file where the rendered thumbnail will be saved
+ * @param array $transformParams
+ * @param int $flags
+ * @return bool|MediaTransformOutput
+ */
+ public function generateAndSaveThumb( $tmpFile, $transformParams, $flags ) {
+ global $wgUseSquid, $wgIgnoreImageErrors;
+
+ $handler = $this->getHandler();
+
+ $normalisedParams = $transformParams;
+ $handler->normaliseParams( $this, $normalisedParams );
+
+ $thumbName = $this->thumbName( $normalisedParams );
+ $thumbUrl = $this->getThumbUrl( $thumbName );
+ $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
+
+ $tmpThumbPath = $tmpFile->getPath();
+
+ if ( $handler->supportsBucketing() ) {
+ $this->generateBucketsIfNeeded( $normalisedParams, $flags );
+ }
+
+ // Actually render the thumbnail...
+ wfProfileIn( __METHOD__ . '-doTransform' );
+ $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
+ wfProfileOut( __METHOD__ . '-doTransform' );
+ $tmpFile->bind( $thumb ); // keep alive with $thumb
+
+ if ( !$thumb ) { // bad params?
+ $thumb = false;
+ } elseif ( $thumb->isError() ) { // transform error
+ $this->lastError = $thumb->toText();
+ // Ignore errors if requested
+ if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
+ $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
+ }
+ } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
+ // Copy the thumbnail from the file system into storage...
+ $disposition = $this->getThumbDisposition( $thumbName );
+ $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
+ if ( $status->isOK() ) {
+ $thumb->setStoragePath( $thumbPath );
+ } else {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags );
+ }
+ // Give extensions a chance to do something with this thumbnail...
+ wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
+ }
+
+ // Purge. Useful in the event of Core -> Squid connection failure or squid
+ // purge collisions from elsewhere during failure. Don't keep triggering for
+ // "thumbs" which have the main image URL though (bug 13776)
+ if ( $wgUseSquid ) {
+ if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
+ SquidUpdate::purge( array( $thumbUrl ) );
+ }
+ }
+
+ return $thumb;
+ }
+
+ /**
+ * Generates chained bucketed thumbnails if needed
+ * @param array $params
+ * @param int $flags
+ * @return bool Whether at least one bucket was generated
+ */
+ protected function generateBucketsIfNeeded( $params, $flags = 0 ) {
+ if ( !$this->repo
+ || !isset( $params['physicalWidth'] )
+ || !isset( $params['physicalHeight'] )
+ || !( $bucket = $this->getThumbnailBucket( $params['physicalWidth'] ) )
+ || $bucket == $params['physicalWidth'] ) {
+ return false;
+ }
+
+ $bucketPath = $this->getBucketThumbPath( $bucket );
+
+ if ( $this->repo->fileExists( $bucketPath ) ) {
+ return false;
+ }
+
+ $params['physicalWidth'] = $bucket;
+ $params['width'] = $bucket;
+
+ $params = $this->getHandler()->sanitizeParamsForBucketing( $params );
+
+ $bucketName = $this->getBucketThumbName( $bucket );
+
+ $tmpFile = $this->makeTransformTmpFile( $bucketPath );
+
+ if ( !$tmpFile ) {
+ return false;
+ }
+
+ $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
+
+ if ( !$thumb || $thumb->isError() ) {
+ return false;
+ }
+
+ $this->tmpBucketedThumbCache[$bucket] = $tmpFile->getPath();
+ // For the caching to work, we need to make the tmp file survive as long as
+ // this object exists
+ $tmpFile->bind( $this );
+
+ return true;
+ }
+
+ /**
+ * Returns the most appropriate source image for the thumbnail, given a target thumbnail size
+ * @param array $params
+ * @return array Source path and width/height of the source
+ */
+ public function getThumbnailSource( $params ) {
+ if ( $this->repo
+ && $this->getHandler()->supportsBucketing()
+ && isset( $params['physicalWidth'] )
+ && $bucket = $this->getThumbnailBucket( $params['physicalWidth'] )
+ ) {
+ if ( $this->getWidth() != 0 ) {
+ $bucketHeight = round( $this->getHeight() * ( $bucket / $this->getWidth() ) );
+ } else {
+ $bucketHeight = 0;
+ }
+
+ // Try to avoid reading from storage if the file was generated by this script
+ if ( isset( $this->tmpBucketedThumbCache[$bucket] ) ) {
+ $tmpPath = $this->tmpBucketedThumbCache[$bucket];
+
+ if ( file_exists( $tmpPath ) ) {
+ return array(
+ 'path' => $tmpPath,
+ 'width' => $bucket,
+ 'height' => $bucketHeight
+ );
}
- // Give extensions a chance to do something with this thumbnail...
- wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
}
- // Purge. Useful in the event of Core -> Squid connection failure or squid
- // purge collisions from elsewhere during failure. Don't keep triggering for
- // "thumbs" which have the main image URL though (bug 13776)
- if ( $wgUseSquid ) {
- if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
- SquidUpdate::purge( array( $thumbUrl ) );
+ $bucketPath = $this->getBucketThumbPath( $bucket );
+
+ if ( $this->repo->fileExists( $bucketPath ) ) {
+ $fsFile = $this->repo->getLocalReference( $bucketPath );
+
+ if ( $fsFile ) {
+ return array(
+ 'path' => $fsFile->getPath(),
+ 'width' => $bucket,
+ 'height' => $bucketHeight
+ );
}
}
- } while ( false );
+ }
- wfProfileOut( __METHOD__ );
- return is_object( $thumb ) ? $thumb : false;
+ // Thumbnailing a very large file could result in network saturation if
+ // everyone does it at once.
+ if ( $this->getSize() >= 1e7 ) { // 10MB
+ $that = $this;
+ $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ),
+ array(
+ 'doWork' => function() use ( $that ) {
+ return $that->getLocalRefPath();
+ }
+ )
+ );
+ $srcPath = $work->execute();
+ } else {
+ $srcPath = $this->getLocalRefPath();
+ }
+
+ // Original file
+ return array(
+ 'path' => $srcPath,
+ 'width' => $this->getWidth(),
+ 'height' => $this->getHeight()
+ );
+ }
+
+ /**
+ * Returns the repo path of the thumb for a given bucket
+ * @param int $bucket
+ * @return string
+ */
+ protected function getBucketThumbPath( $bucket ) {
+ $thumbName = $this->getBucketThumbName( $bucket );
+ return $this->getThumbPath( $thumbName );
+ }
+
+ /**
+ * Returns the name of the thumb for a given bucket
+ * @param int $bucket
+ * @return string
+ */
+ protected function getBucketThumbName( $bucket ) {
+ return $this->thumbName( array( 'physicalWidth' => $bucket ) );
+ }
+
+ /**
+ * Creates a temp FS file with the same extension and the thumbnail
+ * @param string $thumbPath Thumbnail path
+ * @return TempFSFile
+ */
+ protected function makeTransformTmpFile( $thumbPath ) {
+ $thumbExt = FileBackend::extensionFromPath( $thumbPath );
+ return TempFSFile::factory( 'transform_', $thumbExt );
}
/**
* @param string $thumbName Thumbnail name
+ * @param string $dispositionType Type of disposition (either "attachment" or "inline")
* @return string Content-Disposition header value
*/
- function getThumbDisposition( $thumbName ) {
+ function getThumbDisposition( $thumbName, $dispositionType = 'inline' ) {
$fileName = $this->name; // file name to suggest
$thumbExt = FileBackend::extensionFromPath( $thumbName );
if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
$fileName .= ".$thumbExt";
}
- return FileBackend::makeContentDisposition( 'inline', $fileName );
+
+ return FileBackend::makeContentDisposition( $dispositionType, $fileName );
}
/**
* Hook into transform() to allow migration of thumbnail files
* STUB
* Overridden by LocalFile
+ * @param string $thumbName
*/
- function migrateThumbFile( $thumbName ) {}
+ function migrateThumbFile( $thumbName ) {
+ }
/**
* Get a MediaHandler instance for this file
*
- * @return MediaHandler|boolean Registered MediaHandler for file's mime type or false if none found
+ * @return MediaHandler|bool Registered MediaHandler for file's MIME type
+ * or false if none found
*/
function getHandler() {
if ( !isset( $this->handler ) ) {
$this->handler = MediaHandler::getHandler( $this->getMimeType() );
}
+
return $this->handler;
}
@@ -1016,23 +1328,26 @@ abstract class File {
* @return ThumbnailImage
*/
function iconThumb() {
- global $wgStylePath, $wgStyleDirectory;
+ global $wgResourceBasePath, $IP;
+ $assetsPath = "$wgResourceBasePath/resources/assets/file-type-icons/";
+ $assetsDirectory = "$IP/resources/assets/file-type-icons/";
$try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
foreach ( $try as $icon ) {
- $path = '/common/images/icons/' . $icon;
- $filepath = $wgStyleDirectory . $path;
- if ( file_exists( $filepath ) ) { // always FS
+ if ( file_exists( $assetsDirectory . $icon ) ) { // always FS
$params = array( 'width' => 120, 'height' => 120 );
- return new ThumbnailImage( $this, $wgStylePath . $path, false, $params );
+
+ return new ThumbnailImage( $this, $assetsPath . $icon, false, $params );
}
}
+
return null;
}
/**
* Get last thumbnailing error.
* Largely obsolete.
+ * @return string
*/
function getLastError() {
return $this->lastError;
@@ -1053,9 +1368,10 @@ abstract class File {
* STUB
* Overridden by LocalFile
* @param array $options Options, which include:
- * 'forThumbRefresh' : The purging is only to refresh thumbnails
+ * 'forThumbRefresh' : The purging is only to refresh thumbnails
*/
- function purgeCache( $options = array() ) {}
+ function purgeCache( $options = array() ) {
+ }
/**
* Purge the file description page, but don't go after
@@ -1091,9 +1407,9 @@ abstract class File {
* Return a fragment of the history of file.
*
* STUB
- * @param $limit integer Limit of rows to return
- * @param string $start timestamp Only revisions older than $start will be returned
- * @param string $end timestamp Only revisions newer than $end will be returned
+ * @param int $limit Limit of rows to return
+ * @param string $start Only revisions older than $start will be returned
+ * @param string $end Only revisions newer than $end will be returned
* @param bool $inc Include the endpoints of the time range
*
* @return array
@@ -1121,7 +1437,8 @@ abstract class File {
* STUB
* Overridden in LocalFile.
*/
- public function resetHistory() {}
+ public function resetHistory() {
+ }
/**
* Get the filename hash component of the directory including trailing slash,
@@ -1135,6 +1452,7 @@ abstract class File {
$this->assertRepoDefined();
$this->hashPath = $this->repo->getHashPath( $this->getName() );
}
+
return $this->hashPath;
}
@@ -1151,7 +1469,7 @@ abstract class File {
/**
* Get the path of an archived file relative to the public zone root
*
- * @param bool|string $suffix if not false, the name of an archived thumbnail file
+ * @param bool|string $suffix If not false, the name of an archived thumbnail file
*
* @return string
*/
@@ -1162,6 +1480,7 @@ abstract class File {
} else {
$path .= $suffix;
}
+
return $path;
}
@@ -1169,8 +1488,7 @@ abstract class File {
* Get the path, relative to the thumbnail zone root, of the
* thumbnail directory or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getThumbRel( $suffix = false ) {
@@ -1178,6 +1496,7 @@ abstract class File {
if ( $suffix !== false ) {
$path .= '/' . $suffix;
}
+
return $path;
}
@@ -1195,9 +1514,8 @@ abstract class File {
* Get the path, relative to the thumbnail zone root, for an archived file's thumbs directory
* or a specific thumb if the $suffix is given.
*
- * @param string $archiveName the timestamped name of an archived image
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param string $archiveName The timestamped name of an archived image
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getArchiveThumbRel( $archiveName, $suffix = false ) {
@@ -1207,64 +1525,64 @@ abstract class File {
} else {
$path .= $suffix;
}
+
return $path;
}
/**
* Get the path of the archived file.
*
- * @param bool|string $suffix if not false, the name of an archived file.
- *
+ * @param bool|string $suffix If not false, the name of an archived file.
* @return string
*/
function getArchivePath( $suffix = false ) {
$this->assertRepoDefined();
+
return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
}
/**
* Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified
*
- * @param string $archiveName the timestamped name of an archived image
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param string $archiveName The timestamped name of an archived image
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getArchiveThumbPath( $archiveName, $suffix = false ) {
$this->assertRepoDefined();
+
return $this->repo->getZonePath( 'thumb' ) . '/' .
- $this->getArchiveThumbRel( $archiveName, $suffix );
+ $this->getArchiveThumbRel( $archiveName, $suffix );
}
/**
* Get the path of the thumbnail directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getThumbPath( $suffix = false ) {
$this->assertRepoDefined();
+
return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
}
/**
* Get the path of the transcoded directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a media file
- *
+ * @param bool|string $suffix If not false, the name of a media file
* @return string
*/
function getTranscodedPath( $suffix = false ) {
$this->assertRepoDefined();
+
return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix );
}
/**
* Get the URL of the archive directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of an archived file
- *
+ * @param bool|string $suffix If not false, the name of an archived file
* @return string
*/
function getArchiveUrl( $suffix = false ) {
@@ -1276,15 +1594,15 @@ abstract class File {
} else {
$path .= rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified
*
- * @param string $archiveName the timestamped name of an archived image
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param string $archiveName The timestamped name of an archived image
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getArchiveThumbUrl( $archiveName, $suffix = false ) {
@@ -1297,16 +1615,16 @@ abstract class File {
} else {
$path .= rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the URL of the zone directory, or a particular file if $suffix is specified
*
- * @param string $zone name of requested zone
- * @param bool|string $suffix if not false, the name of a file in zone
- *
- * @return string path
+ * @param string $zone Name of requested zone
+ * @param bool|string $suffix If not false, the name of a file in zone
+ * @return string Path
*/
function getZoneUrl( $zone, $suffix = false ) {
$this->assertRepoDefined();
@@ -1315,15 +1633,15 @@ abstract class File {
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the URL of the thumbnail directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
- * @return string path
+ * @param bool|string $suffix If not false, the name of a thumbnail file
+ * @return string Path
*/
function getThumbUrl( $suffix = false ) {
return $this->getZoneUrl( 'thumb', $suffix );
@@ -1332,9 +1650,8 @@ abstract class File {
/**
* Get the URL of the transcoded directory, or a particular file if $suffix is specified
*
- * @param bool|string $suffix if not false, the name of a media file
- *
- * @return string path
+ * @param bool|string $suffix If not false, the name of a media file
+ * @return string Path
*/
function getTranscodedUrl( $suffix = false ) {
return $this->getZoneUrl( 'transcoded', $suffix );
@@ -1343,8 +1660,7 @@ abstract class File {
/**
* Get the public zone virtual URL for a current version source file
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getVirtualUrl( $suffix = false ) {
@@ -1353,14 +1669,14 @@ abstract class File {
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the public zone virtual URL for an archived version source file
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getArchiveVirtualUrl( $suffix = false ) {
@@ -1371,14 +1687,14 @@ abstract class File {
} else {
$path .= rawurlencode( $suffix );
}
+
return $path;
}
/**
* Get the virtual URL for a thumbnail file or directory
*
- * @param bool|string $suffix if not false, the name of a thumbnail file
- *
+ * @param bool|string $suffix If not false, the name of a thumbnail file
* @return string
*/
function getThumbVirtualUrl( $suffix = false ) {
@@ -1387,6 +1703,7 @@ abstract class File {
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
+
return $path;
}
@@ -1395,6 +1712,7 @@ abstract class File {
*/
function isHashed() {
$this->assertRepoDefined();
+
return (bool)$this->repo->getHashLevels();
}
@@ -1409,18 +1727,20 @@ abstract class File {
* Record a file upload in the upload log and the image table
* STUB
* Overridden by LocalFile
- * @param $oldver
- * @param $desc
- * @param $license string
- * @param $copyStatus string
- * @param $source string
- * @param $watch bool
- * @param $timestamp string|bool
- * @param $user User object or null to use $wgUser
+ * @param string $oldver
+ * @param string $desc
+ * @param string $license
+ * @param string $copyStatus
+ * @param string $source
+ * @param bool $watch
+ * @param string|bool $timestamp
+ * @param null|User $user User object or null to use $wgUser
* @return bool
* @throws MWException
*/
- function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false, $timestamp = false, User $user = null ) {
+ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
+ $watch = false, $timestamp = false, User $user = null
+ ) {
$this->readOnlyError();
}
@@ -1435,13 +1755,12 @@ abstract class File {
* Options to $options include:
* - headers : name/value map of HTTP headers to use in response to GET/HEAD requests
*
- * @param string $srcPath local filesystem path to the source image
- * @param $flags Integer: a bitwise combination of:
- * File::DELETE_SOURCE Delete the source file, i.e. move
- * rather than copy
+ * @param string $srcPath Local filesystem path to the source image
+ * @param int $flags A bitwise combination of:
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
* @param array $options Optional additional parameters
- * @return FileRepoStatus object. On success, the value member contains the
- * archive name, or an empty string if it was a new file.
+ * @return FileRepoStatus On success, the value member contains the
+ * archive name, or an empty string if it was a new file.
*
* STUB
* Overridden by LocalFile
@@ -1457,6 +1776,7 @@ abstract class File {
if ( !$this->getHandler() ) {
return false;
}
+
return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
}
@@ -1481,7 +1801,7 @@ abstract class File {
/**
* Returns the repository
*
- * @return FileRepo|bool
+ * @return FileRepo|LocalRepo|bool
*/
function getRepo() {
return $this->repo;
@@ -1501,8 +1821,7 @@ abstract class File {
* Is this file a "deleted" file in a private archive?
* STUB
*
- * @param integer $field one of DELETED_* bitfield constants
- *
+ * @param int $field One of DELETED_* bitfield constants
* @return bool
*/
function isDeleted( $field ) {
@@ -1525,6 +1844,7 @@ abstract class File {
*/
function wasDeleted() {
$title = $this->getTitle();
+
return $title && $title->isDeletedQuick();
}
@@ -1537,8 +1857,8 @@ abstract class File {
* Cache purging is done; checks for validity
* and logging are caller's responsibility
*
- * @param $target Title New file name
- * @return FileRepoStatus object.
+ * @param Title $target New file name
+ * @return FileRepoStatus
*/
function move( $target ) {
$this->readOnlyError();
@@ -1552,13 +1872,14 @@ abstract class File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $reason String
- * @param $suppress Boolean: hide content from sysops?
- * @return bool on success, false on some kind of failure
+ * @param string $reason
+ * @param bool $suppress Hide content from sysops?
+ * @param User|null $user
+ * @return bool Boolean on success, false on some kind of failure
* STUB
* Overridden by LocalFile
*/
- function delete( $reason, $suppress = false ) {
+ function delete( $reason, $suppress = false, $user = null ) {
$this->readOnlyError();
}
@@ -1568,11 +1889,11 @@ abstract class File {
*
* May throw database exceptions on error.
*
- * @param array $versions set of record ids of deleted items to restore,
- * or empty to restore all revisions.
- * @param bool $unsuppress remove restrictions on content upon restoration?
- * @return int|bool the number of file revisions restored if successful,
- * or false on failure
+ * @param array $versions Set of record ids of deleted items to restore,
+ * or empty to restore all revisions.
+ * @param bool $unsuppress Remove restrictions on content upon restoration?
+ * @return int|bool The number of file revisions restored if successful,
+ * or false on failure
* STUB
* Overridden by LocalFile
*/
@@ -1585,7 +1906,7 @@ abstract class File {
* e.g. DJVU or PDF. Note that this may be true even if the file in
* question only has a single page.
*
- * @return Bool
+ * @return bool
*/
function isMultipage() {
return $this->getHandler() && $this->handler->isMultiPage( $this );
@@ -1605,15 +1926,16 @@ abstract class File {
$this->pageCount = false;
}
}
+
return $this->pageCount;
}
/**
* Calculate the height of a thumbnail using the source and destination width
*
- * @param $srcWidth
- * @param $srcHeight
- * @param $dstWidth
+ * @param int $srcWidth
+ * @param int $srcHeight
+ * @param int $dstWidth
*
* @return int
*/
@@ -1628,16 +1950,20 @@ abstract class File {
/**
* Get an image size array like that returned by getImageSize(), or false if it
- * can't be determined.
+ * can't be determined. Loads the image size directly from the file ignoring caches.
*
- * @param string $fileName The filename
- * @return Array
+ * @note Use getWidth()/getHeight() instead of this method unless you have a
+ * a good reason. This method skips all caches.
+ *
+ * @param string $filePath The path to the file (e.g. From getLocalPathRef() )
+ * @return array The width, followed by height, with optionally more things after
*/
- function getImageSize( $fileName ) {
+ function getImageSize( $filePath ) {
if ( !$this->getHandler() ) {
return false;
}
- return $this->handler->getImageSize( $this, $fileName );
+
+ return $this->getHandler()->getImageSize( $this, $filePath );
}
/**
@@ -1657,7 +1983,7 @@ abstract class File {
/**
* Get the HTML text of the description page, if available
*
- * @param $lang Language Optional language to fetch description in
+ * @param bool|Language $lang Optional language to fetch description in
* @return string
*/
function getDescriptionText( $lang = false ) {
@@ -1672,11 +1998,16 @@ abstract class File {
if ( $renderUrl ) {
if ( $this->repo->descriptionCacheExpiry > 0 ) {
wfDebug( "Attempting to get the description from cache..." );
- $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $lang->getCode(),
- $this->getName() );
+ $key = $this->repo->getLocalCacheKey(
+ 'RemoteFileDescription',
+ 'url',
+ $lang->getCode(),
+ $this->getName()
+ );
$obj = $wgMemc->get( $key );
if ( $obj ) {
wfDebug( "success!\n" );
+
return $obj;
}
wfDebug( "miss\n" );
@@ -1686,6 +2017,7 @@ abstract class File {
if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
$wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
}
+
return $res;
} else {
return false;
@@ -1696,12 +2028,12 @@ abstract class File {
* Get description of file revision
* STUB
*
- * @param $audience Integer: one of:
- * File::FOR_PUBLIC to be displayed to all users
- * File::FOR_THIS_USER to be displayed to the given user
- * File::RAW get the description regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
+ * @param int $audience One of:
+ * File::FOR_PUBLIC to be displayed to all users
+ * File::FOR_THIS_USER to be displayed to the given user
+ * File::RAW get the description regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is
+ * passed to the $audience parameter
* @return string
*/
function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
@@ -1715,6 +2047,7 @@ abstract class File {
*/
function getTimestamp() {
$this->assertRepoDefined();
+
return $this->repo->getFileTimestamp( $this->getPath() );
}
@@ -1725,6 +2058,7 @@ abstract class File {
*/
function getSha1() {
$this->assertRepoDefined();
+
return $this->repo->getFileSha1( $this->getPath() );
}
@@ -1740,6 +2074,7 @@ abstract class File {
}
$ext = $this->getExtension();
$dotExt = $ext === '' ? '' : ".$ext";
+
return $hash . $dotExt;
}
@@ -1747,53 +2082,16 @@ abstract class File {
* Determine if the current user is allowed to view a particular
* field of this file, if it's marked as deleted.
* STUB
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
- * @return Boolean
+ * @param int $field
+ * @param User $user User object to check, or null to use $wgUser
+ * @return bool
*/
function userCan( $field, User $user = null ) {
return true;
}
/**
- * Get an associative array containing information about a file in the local filesystem.
- *
- * @param string $path absolute local filesystem path
- * @param $ext Mixed: the file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension.
- *
- * @return array
- * @deprecated since 1.19
- */
- static function getPropsFromPath( $path, $ext = true ) {
- wfDebug( __METHOD__ . ": Getting file info for $path\n" );
- wfDeprecated( __METHOD__, '1.19' );
-
- $fsFile = new FSFile( $path );
- return $fsFile->getProps();
- }
-
- /**
- * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
- * encoding, zero padded to 31 digits.
- *
- * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
- * fairly neatly.
- *
- * @param $path string
- *
- * @return bool|string False on failure
- * @deprecated since 1.19
- */
- static function sha1Base36( $path ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- $fsFile = new FSFile( $path );
- return $fsFile->getSha1Base36();
- }
-
- /**
- * @return Array HTTP header name/value map to use for HEAD/GET request responses
+ * @return array HTTP header name/value map to use for HEAD/GET request responses
*/
function getStreamHeaders() {
$handler = $this->getHandler();
@@ -1841,7 +2139,7 @@ abstract class File {
}
/**
- * @return
+ * @return string
*/
function getRedirected() {
return $this->redirected;
@@ -1855,13 +2153,15 @@ abstract class File {
if ( !$this->redirectTitle ) {
$this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
}
+
return $this->redirectTitle;
}
+
return null;
}
/**
- * @param $from
+ * @param string $from
* @return void
*/
function redirectedFrom( $from ) {
@@ -1877,7 +2177,7 @@ abstract class File {
/**
* Check if this file object is small and can be cached
- * @return boolean
+ * @return bool
*/
public function isCacheable() {
return true;
@@ -1902,4 +2202,13 @@ abstract class File {
throw new MWException( "A Title object is not set for this File.\n" );
}
}
+
+ /**
+ * True if creating thumbnails from the file is large or otherwise resource-intensive.
+ * @return bool
+ */
+ public function isExpensiveToThumbnail() {
+ $handler = $this->getHandler();
+ return $handler ? $handler->isExpensiveToThumbnail( $this ) : false;
+ }
}
diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php
index ed96d446..3d5d5d60 100644
--- a/includes/filerepo/file/ForeignAPIFile.php
+++ b/includes/filerepo/file/ForeignAPIFile.php
@@ -33,9 +33,9 @@ class ForeignAPIFile extends File {
protected $repoClass = 'ForeignApiRepo';
/**
- * @param $title
- * @param $repo ForeignApiRepo
- * @param $info
+ * @param Title|string|bool $title
+ * @param ForeignApiRepo $repo
+ * @param array $info
* @param bool $exists
*/
function __construct( $title, $repo, $info, $exists = false ) {
@@ -48,8 +48,8 @@ class ForeignAPIFile extends File {
}
/**
- * @param $title Title
- * @param $repo ForeignApiRepo
+ * @param Title $title
+ * @param ForeignApiRepo $repo
* @return ForeignAPIFile|null
*/
static function newFromTitle( Title $title, $repo ) {
@@ -57,7 +57,10 @@ class ForeignAPIFile extends File {
'titles' => 'File:' . $title->getDBkey(),
'iiprop' => self::getProps(),
'prop' => 'imageinfo',
- 'iimetadataversion' => MediaHandler::getMetadataVersion()
+ 'iimetadataversion' => MediaHandler::getMetadataVersion(),
+ // extmetadata is language-dependant, accessing the current language here
+ // would be problematic, so we just get them all
+ 'iiextmetadatamultilang' => 1,
) );
$info = $repo->getImageInfo( $data );
@@ -75,6 +78,7 @@ class ForeignAPIFile extends File {
} else {
$img = new self( $title, $repo, $info, true );
}
+
return $img;
} else {
return null;
@@ -86,7 +90,7 @@ class ForeignAPIFile extends File {
* @return string
*/
static function getProps() {
- return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype';
+ return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype|extmetadata';
}
// Dummy functions...
@@ -130,6 +134,7 @@ class ForeignAPIFile extends File {
);
if ( $thumbUrl === false ) {
global $wgLang;
+
return $this->repo->getThumbError(
$this->getName(),
$width,
@@ -138,13 +143,14 @@ class ForeignAPIFile extends File {
$wgLang->getCode()
);
}
+
return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );
}
// Info we can get from API...
/**
- * @param $page int
+ * @param int $page
* @return int|number
*/
public function getWidth( $page = 1 ) {
@@ -152,7 +158,7 @@ class ForeignAPIFile extends File {
}
/**
- * @param $page int
+ * @param int $page
* @return int
*/
public function getHeight( $page = 1 ) {
@@ -166,11 +172,24 @@ class ForeignAPIFile extends File {
if ( isset( $this->mInfo['metadata'] ) ) {
return serialize( self::parseMetadata( $this->mInfo['metadata'] ) );
}
+
return null;
}
/**
- * @param $metadata array
+ * @return array|null Extended metadata (see imageinfo API for format) or
+ * null on error
+ */
+ public function getExtendedMetadata() {
+ if ( isset( $this->mInfo['extmetadata'] ) ) {
+ return $this->mInfo['extmetadata'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param array $metadata
* @return array
*/
public static function parseMetadata( $metadata ) {
@@ -181,6 +200,7 @@ class ForeignAPIFile extends File {
foreach ( $metadata as $meta ) {
$ret[$meta['name']] = self::parseMetadata( $meta['value'] );
}
+
return $ret;
}
@@ -207,6 +227,8 @@ class ForeignAPIFile extends File {
}
/**
+ * @param int $audience
+ * @param User $user
* @return null|string
*/
public function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
@@ -214,7 +236,7 @@ class ForeignAPIFile extends File {
}
/**
- * @return null|String
+ * @return null|string
*/
function getSha1() {
return isset( $this->mInfo['sha1'] )
@@ -223,7 +245,7 @@ class ForeignAPIFile extends File {
}
/**
- * @return bool|Mixed|string
+ * @return bool|string
*/
function getTimestamp() {
return wfTimestamp( TS_MW,
@@ -241,6 +263,7 @@ class ForeignAPIFile extends File {
$magic = MimeMagic::singleton();
$this->mInfo['mime'] = $magic->guessTypesForExtension( $this->getExtension() );
}
+
return $this->mInfo['mime'];
}
@@ -252,6 +275,7 @@ class ForeignAPIFile extends File {
return $this->mInfo['mediatype'];
}
$magic = MimeMagic::singleton();
+
return $magic->getMediaType( null, $this->getMimeType() );
}
@@ -266,7 +290,7 @@ class ForeignAPIFile extends File {
/**
* Only useful if we're locally caching thumbs anyway...
- * @param $suffix string
+ * @param string $suffix
* @return null|string
*/
function getThumbPath( $suffix = '' ) {
@@ -275,6 +299,7 @@ class ForeignAPIFile extends File {
if ( $suffix ) {
$path = $path . $suffix . '/';
}
+
return $path;
} else {
return null;
@@ -314,7 +339,7 @@ class ForeignAPIFile extends File {
}
/**
- * @param $options array
+ * @param array $options
*/
function purgeThumbnails( $options = array() ) {
global $wgMemc;
diff --git a/includes/filerepo/file/ForeignDBFile.php b/includes/filerepo/file/ForeignDBFile.php
index 01d6b0f5..561ead75 100644
--- a/includes/filerepo/file/ForeignDBFile.php
+++ b/includes/filerepo/file/ForeignDBFile.php
@@ -27,11 +27,10 @@
* @ingroup FileAbstraction
*/
class ForeignDBFile extends LocalFile {
-
/**
- * @param $title
- * @param $repo
- * @param $unused
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param null $unused
* @return ForeignDBFile
*/
static function newFromTitle( $title, $repo, $unused = null ) {
@@ -42,23 +41,23 @@ class ForeignDBFile extends LocalFile {
* Create a ForeignDBFile from a title
* Do not call this except from inside a repo class.
*
- * @param $row
- * @param $repo
- *
+ * @param stdClass $row
+ * @param FileRepo $repo
* @return ForeignDBFile
*/
static function newFromRow( $row, $repo ) {
$title = Title::makeTitle( NS_FILE, $row->img_name );
$file = new self( $title, $repo );
$file->loadFromRow( $row );
+
return $file;
}
/**
- * @param $srcPath String
- * @param $flags int
- * @param $options Array
- * @return \FileRepoStatus
+ * @param string $srcPath
+ * @param int $flags
+ * @param array $options
+ * @return FileRepoStatus
* @throws MWException
*/
function publish( $srcPath, $flags = 0, array $options = array() ) {
@@ -66,14 +65,14 @@ class ForeignDBFile extends LocalFile {
}
/**
- * @param $oldver
- * @param $desc string
- * @param $license string
- * @param $copyStatus string
- * @param $source string
- * @param $watch bool
- * @param $timestamp bool|string
- * @param $user User object or null to use $wgUser
+ * @param string $oldver
+ * @param string $desc
+ * @param string $license
+ * @param string $copyStatus
+ * @param string $source
+ * @param bool $watch
+ * @param bool|string $timestamp
+ * @param User $user User object or null to use $wgUser
* @return bool
* @throws MWException
*/
@@ -83,9 +82,9 @@ class ForeignDBFile extends LocalFile {
}
/**
- * @param $versions array
- * @param $unsuppress bool
- * @return \FileRepoStatus
+ * @param array $versions
+ * @param bool $unsuppress
+ * @return FileRepoStatus
* @throws MWException
*/
function restore( $versions = array(), $unsuppress = false ) {
@@ -93,18 +92,19 @@ class ForeignDBFile extends LocalFile {
}
/**
- * @param $reason string
- * @param $suppress bool
- * @return \FileRepoStatus
+ * @param string $reason
+ * @param bool $suppress
+ * @param User|null $user
+ * @return FileRepoStatus
* @throws MWException
*/
- function delete( $reason, $suppress = false ) {
+ function delete( $reason, $suppress = false, $user = null ) {
$this->readOnlyError();
}
/**
- * @param $target Title
- * @return \FileRepoStatus
+ * @param Title $target
+ * @return FileRepoStatus
* @throws MWException
*/
function move( $target ) {
@@ -120,7 +120,7 @@ class ForeignDBFile extends LocalFile {
}
/**
- * @param $lang Language Optional language to fetch description in.
+ * @param bool|Language $lang Optional language to fetch description in.
* @return string
*/
function getDescriptionText( $lang = false ) {
diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php
index d18f42e4..8824b669 100644
--- a/includes/filerepo/file/LocalFile.php
+++ b/includes/filerepo/file/LocalFile.php
@@ -46,45 +46,88 @@ define( 'MW_FILE_VERSION', 9 );
class LocalFile extends File {
const CACHE_FIELD_MAX_LEN = 1000;
- /**#@+
- * @private
- */
- var
- $fileExists, # does the file exist on disk? (loadFromXxx)
- $historyLine, # Number of line to return by nextHistoryLine() (constructor)
- $historyRes, # result of the query for the file's history (nextHistoryLine)
- $width, # \
- $height, # |
- $bits, # --- returned by getimagesize (loadFromXxx)
- $attr, # /
- $media_type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
- $mime, # MIME type, determined by MimeMagic::guessMimeType
- $major_mime, # Major mime type
- $minor_mime, # Minor mime type
- $size, # Size in bytes (loadFromXxx)
- $metadata, # Handler-specific metadata
- $timestamp, # Upload timestamp
- $sha1, # SHA-1 base 36 content hash
- $user, $user_text, # User, who uploaded the file
- $description, # Description of current revision of the file
- $dataLoaded, # Whether or not core data has been loaded from the database (loadFromXxx)
- $extraDataLoaded, # Whether or not lazy-loaded data has been loaded from the database
- $upgraded, # Whether the row was upgraded on load
- $locked, # True if the image row is locked
- $lockedOwnTrx, # True if the image row is locked with a lock initiated transaction
- $missing, # True if file is not present in file system. Not to be cached in memcached
- $deleted; # Bitfield akin to rev_deleted
-
- /**#@-*/
-
- /**
- * @var LocalRepo
- */
- var $repo;
+ /** @var bool Does the file exist on disk? (loadFromXxx) */
+ protected $fileExists;
+ /** @var int Image width */
+ protected $width;
+
+ /** @var int Image height */
+ protected $height;
+
+ /** @var int Returned by getimagesize (loadFromXxx) */
+ protected $bits;
+
+ /** @var string MEDIATYPE_xxx (bitmap, drawing, audio...) */
+ protected $media_type;
+
+ /** @var string MIME type, determined by MimeMagic::guessMimeType */
+ protected $mime;
+
+ /** @var int Size in bytes (loadFromXxx) */
+ protected $size;
+
+ /** @var string Handler-specific metadata */
+ protected $metadata;
+
+ /** @var string SHA-1 base 36 content hash */
+ protected $sha1;
+
+ /** @var bool Whether or not core data has been loaded from the database (loadFromXxx) */
+ protected $dataLoaded;
+
+ /** @var bool Whether or not lazy-loaded data has been loaded from the database */
+ protected $extraDataLoaded;
+
+ /** @var int Bitfield akin to rev_deleted */
+ protected $deleted;
+
+ /** @var string */
protected $repoClass = 'LocalRepo';
+ /** @var int Number of line to return by nextHistoryLine() (constructor) */
+ private $historyLine;
+
+ /** @var int Result of the query for the file's history (nextHistoryLine) */
+ private $historyRes;
+
+ /** @var string Major MIME type */
+ private $major_mime;
+
+ /** @var string Minor MIME type */
+ private $minor_mime;
+
+ /** @var string Upload timestamp */
+ private $timestamp;
+
+ /** @var int User ID of uploader */
+ private $user;
+
+ /** @var string User name of uploader */
+ private $user_text;
+
+ /** @var string Description of current revision of the file */
+ private $description;
+
+ /** @var bool Whether the row was upgraded on load */
+ private $upgraded;
+
+ /** @var bool True if the image row is locked */
+ private $locked;
+
+ /** @var bool True if the image row is locked with a lock initiated transaction */
+ private $lockedOwnTrx;
+
+ /** @var bool True if file is not present in file system. Not to be cached in memcached */
+ private $missing;
+
+ /** @var int UNIX timestamp of last markVolatile() call */
+ private $lastMarkedVolatile = 0;
+
const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata)
+ const LOAD_VIA_SLAVE = 2; // integer; use a slave to load the data
+
+ const VOLATILE_TTL = 300; // integer; seconds
/**
* Create a LocalFile from a title
@@ -92,9 +135,9 @@ class LocalFile extends File {
*
* Note: $unused param is only here to avoid an E_STRICT
*
- * @param $title
- * @param $repo
- * @param $unused
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param null $unused
*
* @return LocalFile
*/
@@ -106,8 +149,8 @@ class LocalFile extends File {
* Create a LocalFile from a title
* Do not call this except from inside a repo class.
*
- * @param $row
- * @param $repo
+ * @param stdClass $row
+ * @param FileRepo $repo
*
* @return LocalFile
*/
@@ -123,10 +166,9 @@ class LocalFile extends File {
* Create a LocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
*
- * @param string $sha1 base-36 SHA-1
- * @param $repo LocalRepo
+ * @param string $sha1 Base-36 SHA-1
+ * @param LocalRepo $repo
* @param string|bool $timestamp MW_timestamp (optional)
- *
* @return bool|LocalFile
*/
static function newFromKey( $sha1, $repo, $timestamp = false ) {
@@ -171,6 +213,8 @@ class LocalFile extends File {
/**
* Constructor.
* Do not call this except from inside a repo class.
+ * @param Title $title
+ * @param FileRepo $repo
*/
function __construct( $title, $repo ) {
parent::__construct( $title, $repo );
@@ -188,7 +232,7 @@ class LocalFile extends File {
/**
* Get the memcached key for the main data for this file, or false if
* there is no access to the shared cache.
- * @return bool
+ * @return string|bool
*/
function getCacheKey() {
$hashedName = md5( $this->getName() );
@@ -210,6 +254,7 @@ class LocalFile extends File {
if ( !$key ) {
wfProfileOut( __METHOD__ );
+
return false;
}
@@ -236,6 +281,7 @@ class LocalFile extends File {
}
wfProfileOut( __METHOD__ );
+
return $this->dataLoaded;
}
@@ -284,12 +330,13 @@ class LocalFile extends File {
}
/**
- * @param $prefix string
+ * @param string $prefix
* @return array
*/
function getCacheFields( $prefix = 'img_' ) {
static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
- 'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user', 'user_text', 'description' );
+ 'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user',
+ 'user_text', 'description' );
static $results = array();
if ( $prefix == '' ) {
@@ -308,6 +355,7 @@ class LocalFile extends File {
}
/**
+ * @param string $prefix
* @return array
*/
function getLazyCacheFields( $prefix = 'img_' ) {
@@ -331,8 +379,9 @@ class LocalFile extends File {
/**
* Load file metadata from the DB
+ * @param int $flags
*/
- function loadFromDB() {
+ function loadFromDB( $flags = 0 ) {
# Polymorphic function name to distinguish foreign and local fetches
$fname = get_class( $this ) . '::' . __FUNCTION__;
wfProfileIn( $fname );
@@ -341,7 +390,10 @@ class LocalFile extends File {
$this->dataLoaded = true;
$this->extraDataLoaded = true;
- $dbr = $this->repo->getMasterDB();
+ $dbr = ( $flags & self::LOAD_VIA_SLAVE )
+ ? $this->repo->getSlaveDB()
+ : $this->repo->getMasterDB();
+
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
@@ -366,19 +418,13 @@ class LocalFile extends File {
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
$this->extraDataLoaded = true;
- $dbr = $this->repo->getSlaveDB();
- // In theory the file could have just been renamed/deleted...oh well
- $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
- array( 'img_name' => $this->getName() ), $fname );
-
- if ( !$row ) { // fallback to master
- $dbr = $this->repo->getMasterDB();
- $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
- array( 'img_name' => $this->getName() ), $fname );
+ $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getSlaveDB(), $fname );
+ if ( !$fieldMap ) {
+ $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
}
- if ( $row ) {
- foreach ( $this->unprefixRow( $row, 'img_' ) as $name => $value ) {
+ if ( $fieldMap ) {
+ foreach ( $fieldMap as $name => $value ) {
$this->$name = $value;
}
} else {
@@ -390,9 +436,36 @@ class LocalFile extends File {
}
/**
- * @param Row $row
- * @param $prefix string
- * @return Array
+ * @param DatabaseBase $dbr
+ * @param string $fname
+ * @return array|bool
+ */
+ private function loadFieldsWithTimestamp( $dbr, $fname ) {
+ $fieldMap = false;
+
+ $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
+ array( 'img_name' => $this->getName(), 'img_timestamp' => $this->getTimestamp() ),
+ $fname );
+ if ( $row ) {
+ $fieldMap = $this->unprefixRow( $row, 'img_' );
+ } else {
+ # File may have been uploaded over in the meantime; check the old versions
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
+ array( 'oi_name' => $this->getName(), 'oi_timestamp' => $this->getTimestamp() ),
+ $fname );
+ if ( $row ) {
+ $fieldMap = $this->unprefixRow( $row, 'oi_' );
+ }
+ }
+
+ return $fieldMap;
+ }
+
+ /**
+ * @param array $row Row
+ * @param string $prefix
+ * @throws MWException
+ * @return array
*/
protected function unprefixRow( $row, $prefix = 'img_' ) {
$array = (array)$row;
@@ -407,14 +480,15 @@ class LocalFile extends File {
foreach ( $array as $name => $value ) {
$decoded[substr( $name, $prefixLength )] = $value;
}
+
return $decoded;
}
/**
* Decode a row from the database (either object or array) to an array
* with timestamps and MIME types decoded, and the field prefix removed.
- * @param $row
- * @param $prefix string
+ * @param object $row
+ * @param string $prefix
* @throws MWException
* @return array
*/
@@ -442,6 +516,9 @@ class LocalFile extends File {
/**
* Load file metadata from a DB result row
+ *
+ * @param object $row
+ * @param string $prefix
*/
function loadFromRow( $row, $prefix = 'img_' ) {
$this->dataLoaded = true;
@@ -459,12 +536,12 @@ class LocalFile extends File {
/**
* Load file metadata from cache or DB, unless already loaded
- * @param integer $flags
+ * @param int $flags
*/
function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
if ( !$this->loadFromCache() ) {
- $this->loadFromDB();
+ $this->loadFromDB( $this->isVolatile() ? 0 : self::LOAD_VIA_SLAVE );
$this->saveToCache();
}
$this->dataLoaded = true;
@@ -518,8 +595,10 @@ class LocalFile extends File {
# Don't destroy file info of missing files
if ( !$this->fileExists ) {
+ $this->unlock();
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
wfProfileOut( __METHOD__ );
+
return;
}
@@ -527,7 +606,9 @@ class LocalFile extends File {
list( $major, $minor ) = self::splitMime( $this->mime );
if ( wfReadOnly() ) {
+ $this->unlock();
wfProfileOut( __METHOD__ );
+
return;
}
wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
@@ -541,7 +622,7 @@ class LocalFile extends File {
'img_media_type' => $this->media_type,
'img_major_mime' => $major,
'img_minor_mime' => $minor,
- 'img_metadata' => $dbw->encodeBlob($this->metadata),
+ 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
'img_sha1' => $this->sha1,
),
array( 'img_name' => $this->getName() ),
@@ -562,6 +643,8 @@ class LocalFile extends File {
*
* If 'mime' is given, it will be split into major_mime/minor_mime.
* If major_mime/minor_mime are given, $this->mime will also be set.
+ *
+ * @param array $info
*/
function setProps( $info ) {
$this->dataLoaded = true;
@@ -599,13 +682,14 @@ class LocalFile extends File {
list( $fileExists ) = $this->repo->fileExists( $this->getVirtualUrl() );
$this->missing = !$fileExists;
}
+
return $this->missing;
}
/**
* Return the width of the image
*
- * @param $page int
+ * @param int $page
* @return int
*/
public function getWidth( $page = 1 ) {
@@ -632,7 +716,7 @@ class LocalFile extends File {
/**
* Return the height of the image
*
- * @param $page int
+ * @param int $page
* @return int
*/
public function getHeight( $page = 1 ) {
@@ -686,34 +770,38 @@ class LocalFile extends File {
*/
function getBitDepth() {
$this->load();
+
return $this->bits;
}
/**
- * Return the size of the image file, in bytes
+ * Returns the size of the image file, in bytes
* @return int
*/
public function getSize() {
$this->load();
+
return $this->size;
}
/**
- * Returns the mime type of the file.
+ * Returns the MIME type of the file.
* @return string
*/
function getMimeType() {
$this->load();
+
return $this->mime;
}
/**
- * Return the type of the media in the file.
+ * Returns the type of the media in the file.
* Use the value returned by this function with the MEDIATYPE_xxx constants.
* @return string
*/
function getMediaType() {
$this->load();
+
return $this->media_type;
}
@@ -725,10 +813,11 @@ class LocalFile extends File {
/**
* Returns true if the file exists on disk.
- * @return boolean Whether file exist on disk.
+ * @return bool Whether file exist on disk.
*/
public function exists() {
$this->load();
+
return $this->fileExists;
}
@@ -738,40 +827,6 @@ class LocalFile extends File {
/** createThumb inherited */
/** transform inherited */
- /**
- * Fix thumbnail files from 1.4 or before, with extreme prejudice
- * @todo : do we still care about this? Perhaps a maintenance script
- * can be made instead. Enabling this code results in a serious
- * RTT regression for wikis without 404 handling.
- */
- function migrateThumbFile( $thumbName ) {
- /* Old code for bug 2532
- $thumbDir = $this->getThumbPath();
- $thumbPath = "$thumbDir/$thumbName";
- if ( is_dir( $thumbPath ) ) {
- // Directory where file should be
- // This happened occasionally due to broken migration code in 1.5
- // Rename to broken-*
- for ( $i = 0; $i < 100; $i++ ) {
- $broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName";
- if ( !file_exists( $broken ) ) {
- rename( $thumbPath, $broken );
- break;
- }
- }
- // Doesn't exist anymore
- clearstatcache();
- }
- */
-
- /*
- if ( $this->repo->fileExists( $thumbDir ) ) {
- // Delete file where directory should be
- $this->repo->cleanupBatch( array( $thumbDir ) );
- }
- */
- }
-
/** getHandler inherited */
/** iconThumb inherited */
/** getLastError inherited */
@@ -779,7 +834,7 @@ class LocalFile extends File {
/**
* Get all thumbnail names previously generated for this file
* @param string|bool $archiveName Name of an archive file, default false
- * @return array first element is the base dir, then files in that base dir.
+ * @return array First element is the base dir, then files in that base dir.
*/
function getThumbnails( $archiveName = false ) {
if ( $archiveName ) {
@@ -795,7 +850,8 @@ class LocalFile extends File {
foreach ( $iterator as $file ) {
$files[] = $file;
}
- } catch ( FileBackendError $e ) {} // suppress (bug 54674)
+ } catch ( FileBackendError $e ) {
+ } // suppress (bug 54674)
return $files;
}
@@ -828,7 +884,7 @@ class LocalFile extends File {
/**
* Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid.
*
- * @param Array $options An array potentially with the key forThumbRefresh.
+ * @param array $options An array potentially with the key forThumbRefresh.
*
* @note This used to purge old thumbnails by default as well, but doesn't anymore.
*/
@@ -847,7 +903,7 @@ class LocalFile extends File {
/**
* Delete cached transformed files for an archived version only.
- * @param string $archiveName name of the archived file
+ * @param string $archiveName Name of the archived file
*/
function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
@@ -855,12 +911,13 @@ class LocalFile extends File {
// Get a list of old thumbnails and URLs
$files = $this->getThumbnails( $archiveName );
- $dir = array_shift( $files );
- $this->purgeThumbList( $dir, $files );
// Purge any custom thumbnail caches
wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, $archiveName ) );
+ $dir = array_shift( $files );
+ $this->purgeThumbList( $dir, $files );
+
// Purge the squid
if ( $wgUseSquid ) {
$urls = array();
@@ -875,6 +932,7 @@ class LocalFile extends File {
/**
* Delete cached transformed files for the current version only.
+ * @param array $options
*/
function purgeThumbnails( $options = array() ) {
global $wgUseSquid;
@@ -883,8 +941,8 @@ class LocalFile extends File {
// Delete thumbnails
$files = $this->getThumbnails();
// Always purge all files from squid regardless of handler filters
+ $urls = array();
if ( $wgUseSquid ) {
- $urls = array();
foreach ( $files as $file ) {
$urls[] = $this->getThumbUrl( $file );
}
@@ -899,12 +957,12 @@ class LocalFile extends File {
}
}
- $dir = array_shift( $files );
- $this->purgeThumbList( $dir, $files );
-
// Purge any custom thumbnail caches
wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, false ) );
+ $dir = array_shift( $files );
+ $this->purgeThumbList( $dir, $files );
+
// Purge the squid
if ( $wgUseSquid ) {
SquidUpdate::purge( $urls );
@@ -915,8 +973,8 @@ class LocalFile extends File {
/**
* Delete a list of thumbnails visible at urls
- * @param string $dir base dir of the files.
- * @param array $files of strings: relative filenames (to $dir)
+ * @param string $dir Base dir of the files.
+ * @param array $files Array of strings: relative filenames (to $dir)
*/
protected function purgeThumbList( $dir, $files ) {
$fileListDebug = strtr(
@@ -946,10 +1004,10 @@ class LocalFile extends File {
/** purgeEverything inherited */
/**
- * @param $limit null
- * @param $start null
- * @param $end null
- * @param $inc bool
+ * @param int $limit Optional: Limit to number of results
+ * @param int $start Optional: Timestamp, start from
+ * @param int $end Optional: Timestamp, end at
+ * @param bool $inc
* @return array
*/
function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
@@ -984,11 +1042,7 @@ class LocalFile extends File {
$r = array();
foreach ( $res as $row ) {
- if ( $this->repo->oldFileFromRowFactory ) {
- $r[] = call_user_func( $this->repo->oldFileFromRowFactory, $row, $this->repo );
- } else {
- $r[] = OldLocalFile::newFromRow( $row, $this->repo );
- }
+ $r[] = $this->repo->newFileFromRow( $row );
}
if ( $order == 'ASC' ) {
@@ -999,7 +1053,7 @@ class LocalFile extends File {
}
/**
- * Return the history of this file, line by line.
+ * Returns the history of this file, line by line.
* starts with current version, then old versions.
* uses $this->historyLine to check which line to return:
* 0 return line for current version
@@ -1013,7 +1067,7 @@ class LocalFile extends File {
$dbr = $this->repo->getSlaveDB();
- if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
+ if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
$this->historyRes = $dbr->select( 'image',
array(
'*',
@@ -1027,6 +1081,7 @@ class LocalFile extends File {
if ( 0 == $dbr->numRows( $this->historyRes ) ) {
$this->historyRes = null;
+
return false;
}
} elseif ( $this->historyLine == 1 ) {
@@ -1036,7 +1091,7 @@ class LocalFile extends File {
array( 'ORDER BY' => 'oi_timestamp DESC' )
);
}
- $this->historyLine ++;
+ $this->historyLine++;
return $dbr->fetchObject( $this->historyRes );
}
@@ -1066,21 +1121,24 @@ class LocalFile extends File {
/**
* Upload a file and record it in the DB
- * @param string $srcPath source storage path, virtual URL, or filesystem path
- * @param string $comment upload description
- * @param string $pageText text to use for the new description page,
- * if a new description page is created
- * @param $flags Integer|bool: flags for publish()
- * @param array|bool $props File properties, if known. This can be used to reduce the
- * upload time when uploading virtual URLs for which the file info
- * is already known
- * @param string|bool $timestamp timestamp for img_timestamp, or false to use the current time
- * @param $user User|null: User object or null to use $wgUser
+ * @param string $srcPath Source storage path, virtual URL, or filesystem path
+ * @param string $comment Upload description
+ * @param string $pageText Text to use for the new description page,
+ * if a new description page is created
+ * @param int|bool $flags Flags for publish()
+ * @param array|bool $props File properties, if known. This can be used to
+ * reduce the upload time when uploading virtual URLs for which the file
+ * info is already known
+ * @param string|bool $timestamp Timestamp for img_timestamp, or false to use the
+ * current time
+ * @param User|null $user User object or null to use $wgUser
*
- * @return FileRepoStatus object. On success, the value member contains the
+ * @return FileRepoStatus On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
- function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) {
+ function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false,
+ $timestamp = false, $user = null
+ ) {
global $wgContLang;
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
@@ -1090,8 +1148,8 @@ class LocalFile extends File {
if ( !$props ) {
wfProfileIn( __METHOD__ . '-getProps' );
if ( $this->repo->isVirtualUrl( $srcPath )
- || FileBackend::isStoragePath( $srcPath ) )
- {
+ || FileBackend::isStoragePath( $srcPath )
+ ) {
$props = $this->repo->getFileProps( $srcPath );
} else {
$props = FSFile::getPropsFromPath( $srcPath );
@@ -1110,16 +1168,19 @@ class LocalFile extends File {
// Trim spaces on user supplied text
$comment = trim( $comment );
- // truncate nicely or the DB will do it for us
+ // Truncate nicely or the DB will do it for us
// non-nicely (dangling multi-byte chars, non-truncated version in cache).
$comment = $wgContLang->truncate( $comment, 255 );
$this->lock(); // begin
$status = $this->publish( $srcPath, $flags, $options );
- if ( $status->successCount > 0 ) {
- # Essentially we are displacing any existing current file and saving
- # a new current file at the old location. If just the first succeeded,
- # we still need to displace the current DB entry and put in a new one.
+ if ( $status->successCount >= 2 ) {
+ // There will be a copy+(one of move,copy,store).
+ // The first succeeding does not commit us to updating the DB
+ // since it simply copied the current version to a timestamped file name.
+ // It is only *preferable* to avoid leaving such files orphaned.
+ // Once the second operation goes through, then the current version was
+ // updated and we must therefore update the DB too.
if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) {
$status->fatal( 'filenotfound', $srcPath );
}
@@ -1132,19 +1193,18 @@ class LocalFile extends File {
/**
* Record a file upload in the upload log and the image table
- * @param $oldver
- * @param $desc string
- * @param $license string
- * @param $copyStatus string
- * @param $source string
- * @param $watch bool
- * @param $timestamp string|bool
- * @param $user User object or null to use $wgUser
+ * @param string $oldver
+ * @param string $desc
+ * @param string $license
+ * @param string $copyStatus
+ * @param string $source
+ * @param bool $watch
+ * @param string|bool $timestamp
+ * @param User|null $user User object or null to use $wgUser
* @return bool
*/
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
- $watch = false, $timestamp = false, User $user = null )
- {
+ $watch = false, $timestamp = false, User $user = null ) {
if ( !$user ) {
global $wgUser;
$user = $wgUser;
@@ -1159,21 +1219,22 @@ class LocalFile extends File {
if ( $watch ) {
$user->addWatch( $this->getTitle() );
}
+
return true;
}
/**
* Record a file upload in the upload log and the image table
- * @param $oldver
- * @param $comment string
- * @param $pageText string
- * @param $props bool|array
- * @param $timestamp bool|string
- * @param $user null|User
+ * @param string $oldver
+ * @param string $comment
+ * @param string $pageText
+ * @param bool|array $props
+ * @param string|bool $timestamp
+ * @param null|User $user
* @return bool
*/
- function recordUpload2(
- $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null
+ function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false,
+ $user = null
) {
wfProfileIn( __METHOD__ );
@@ -1191,8 +1252,13 @@ class LocalFile extends File {
wfProfileOut( __METHOD__ . '-getProps' );
}
+ # Imports or such might force a certain timestamp; otherwise we generate
+ # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
if ( $timestamp === false ) {
$timestamp = $dbw->timestamp();
+ $allowTimeKludge = true;
+ } else {
+ $allowTimeKludge = false;
}
$props['description'] = $comment;
@@ -1204,7 +1270,9 @@ class LocalFile extends File {
# Fail now if the file isn't there
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
+ $dbw->rollback( __METHOD__ );
wfProfileOut( __METHOD__ );
+
return false;
}
@@ -1227,13 +1295,27 @@ class LocalFile extends File {
'img_description' => $comment,
'img_user' => $user->getId(),
'img_user_text' => $user->getName(),
- 'img_metadata' => $dbw->encodeBlob($this->metadata),
+ 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
'img_sha1' => $this->sha1
),
__METHOD__,
'IGNORE'
);
if ( $dbw->affectedRows() == 0 ) {
+ if ( $allowTimeKludge ) {
+ # Use FOR UPDATE to ignore any transaction snapshotting
+ $ltimestamp = $dbw->selectField( 'image', 'img_timestamp',
+ array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ $lUnixtime = $ltimestamp ? wfTimestamp( TS_UNIX, $ltimestamp ) : false;
+ # Avoid a timestamp that is not newer than the last version
+ # TODO: the image/oldimage tables should be like page/revision with an ID field
+ if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
+ sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
+ $timestamp = $dbw->timestamp( $lUnixtime + 1 );
+ $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
+ }
+ }
+
# (bug 34993) Note: $oldver can be empty here, if the previous
# version of the file was broken. Allow registration of the new
# version to continue anyway, because that's better than having
@@ -1244,21 +1326,21 @@ class LocalFile extends File {
# Insert previous contents into oldimage
$dbw->insertSelect( 'oldimage', 'image',
array(
- 'oi_name' => 'img_name',
+ 'oi_name' => 'img_name',
'oi_archive_name' => $dbw->addQuotes( $oldver ),
- 'oi_size' => 'img_size',
- 'oi_width' => 'img_width',
- 'oi_height' => 'img_height',
- 'oi_bits' => 'img_bits',
- 'oi_timestamp' => 'img_timestamp',
- 'oi_description' => 'img_description',
- 'oi_user' => 'img_user',
- 'oi_user_text' => 'img_user_text',
- 'oi_metadata' => 'img_metadata',
- 'oi_media_type' => 'img_media_type',
- 'oi_major_mime' => 'img_major_mime',
- 'oi_minor_mime' => 'img_minor_mime',
- 'oi_sha1' => 'img_sha1'
+ 'oi_size' => 'img_size',
+ 'oi_width' => 'img_width',
+ 'oi_height' => 'img_height',
+ 'oi_bits' => 'img_bits',
+ 'oi_timestamp' => 'img_timestamp',
+ 'oi_description' => 'img_description',
+ 'oi_user' => 'img_user',
+ 'oi_user_text' => 'img_user_text',
+ 'oi_metadata' => 'img_metadata',
+ 'oi_media_type' => 'img_media_type',
+ 'oi_major_mime' => 'img_major_mime',
+ 'oi_minor_mime' => 'img_minor_mime',
+ 'oi_sha1' => 'img_sha1'
),
array( 'img_name' => $this->getName() ),
__METHOD__
@@ -1267,19 +1349,19 @@ class LocalFile extends File {
# Update the current image row
$dbw->update( 'image',
array( /* SET */
- 'img_size' => $this->size,
- 'img_width' => intval( $this->width ),
- 'img_height' => intval( $this->height ),
- 'img_bits' => $this->bits,
- 'img_media_type' => $this->media_type,
- 'img_major_mime' => $this->major_mime,
- 'img_minor_mime' => $this->minor_mime,
- 'img_timestamp' => $timestamp,
+ 'img_size' => $this->size,
+ 'img_width' => intval( $this->width ),
+ 'img_height' => intval( $this->height ),
+ 'img_bits' => $this->bits,
+ 'img_media_type' => $this->media_type,
+ 'img_major_mime' => $this->major_mime,
+ 'img_minor_mime' => $this->minor_mime,
+ 'img_timestamp' => $timestamp,
'img_description' => $comment,
- 'img_user' => $user->getId(),
- 'img_user_text' => $user->getName(),
- 'img_metadata' => $dbw->encodeBlob($this->metadata),
- 'img_sha1' => $this->sha1
+ 'img_user' => $user->getId(),
+ 'img_user_text' => $user->getName(),
+ 'img_metadata' => $dbw->encodeBlob( $this->metadata ),
+ 'img_sha1' => $this->sha1
),
array( 'img_name' => $this->getName() ),
__METHOD__
@@ -1333,7 +1415,8 @@ class LocalFile extends File {
$dbw,
$descTitle->getArticleID(),
$editSummary,
- false
+ false,
+ $user
);
if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
@@ -1349,6 +1432,12 @@ class LocalFile extends File {
# to after $wikiPage->doEdit has been called.
$dbw->commit( __METHOD__ );
+ # Save to memcache.
+ # We shall not saveToCache before the commit since otherwise
+ # in case of a rollback there is an usable file from memcached
+ # which in fact doesn't really exist (bug 24978)
+ $this->saveToCache();
+
if ( $exists ) {
# Invalidate the cache for the description page
$descTitle->invalidateCache();
@@ -1358,7 +1447,13 @@ class LocalFile extends File {
# There's already a log entry, so don't make a second RC entry
# Squid and file cache for the description page are purged by doEditContent.
$content = ContentHandler::makeContent( $pageText, $descTitle );
- $status = $wikiPage->doEditContent( $content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
+ $status = $wikiPage->doEditContent(
+ $content,
+ $comment,
+ EDIT_NEW | EDIT_SUPPRESS_RC,
+ false,
+ $user
+ );
$dbw->begin( __METHOD__ ); // XXX; doEdit() uses a transaction
// Now that the page exists, make an RC entry.
@@ -1373,15 +1468,8 @@ class LocalFile extends File {
$dbw->commit( __METHOD__ ); // commit before anything bad can happen
}
-
wfProfileOut( __METHOD__ . '-edit' );
- # Save to cache and purge the squid
- # We shall not saveToCache before the commit since otherwise
- # in case of a rollback there is an usable file from memcached
- # which in fact doesn't really exist (bug 24978)
- $this->saveToCache();
-
if ( $reupload ) {
# Delete old thumbnails
wfProfileIn( __METHOD__ . '-purge' );
@@ -1404,18 +1492,8 @@ class LocalFile extends File {
LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
}
- # Invalidate cache for all pages that redirects on this page
- $redirs = $this->getTitle()->getRedirectsHere();
-
- foreach ( $redirs as $redir ) {
- if ( !$reupload && $redir->getNamespace() === NS_FILE ) {
- LinksUpdate::queueRecursiveJobsForTable( $redir, 'imagelinks' );
- }
- $update = new HTMLCacheUpdate( $redir, 'imagelinks' );
- $update->doUpdate();
- }
-
wfProfileOut( __METHOD__ );
+
return true;
}
@@ -1427,11 +1505,11 @@ class LocalFile extends File {
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param string $srcPath local filesystem path to the source image
- * @param $flags Integer: a bitwise combination of:
- * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
+ * @param string $srcPath Local filesystem path to the source image
+ * @param int $flags A bitwise combination of:
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
* @param array $options Optional additional parameters
- * @return FileRepoStatus object. On success, the value member contains the
+ * @return FileRepoStatus On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
function publish( $srcPath, $flags = 0, array $options = array() ) {
@@ -1445,12 +1523,12 @@ class LocalFile extends File {
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param string $srcPath local filesystem path to the source image
- * @param string $dstRel target relative path
- * @param $flags Integer: a bitwise combination of:
- * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
+ * @param string $srcPath Local filesystem path to the source image
+ * @param string $dstRel Target relative path
+ * @param int $flags A bitwise combination of:
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
* @param array $options Optional additional parameters
- * @return FileRepoStatus object. On success, the value member contains the
+ * @return FileRepoStatus On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
function publishTo( $srcPath, $dstRel, $flags = 0, array $options = array() ) {
@@ -1490,8 +1568,8 @@ class LocalFile extends File {
* Cache purging is done; checks for validity
* and logging are caller's responsibility
*
- * @param $target Title New file name
- * @return FileRepoStatus object.
+ * @param Title $target New file name
+ * @return FileRepoStatus
*/
function move( $target ) {
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
@@ -1515,7 +1593,7 @@ class LocalFile extends File {
// Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
// tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
$this->getRepo()->getMasterDB()->onTransactionIdle(
- function() use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
+ function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
$oldTitleFile->purgeEverything();
foreach ( $archiveNames as $archiveName ) {
$oldTitleFile->purgeOldThumbnails( $archiveName );
@@ -1543,16 +1621,17 @@ class LocalFile extends File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $reason
- * @param $suppress
- * @return FileRepoStatus object.
+ * @param string $reason
+ * @param bool $suppress
+ * @param User|null $user
+ * @return FileRepoStatus
*/
- function delete( $reason, $suppress = false ) {
+ function delete( $reason, $suppress = false, $user = null ) {
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
return $this->readOnlyFatalStatus();
}
- $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
+ $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
$this->lock(); // begin
$batch->addCurrent();
@@ -1569,7 +1648,7 @@ class LocalFile extends File {
// tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
$file = $this;
$this->getRepo()->getMasterDB()->onTransactionIdle(
- function() use ( $file, $archiveNames ) {
+ function () use ( $file, $archiveNames ) {
global $wgUseSquid;
$file->purgeEverything();
@@ -1599,19 +1678,20 @@ class LocalFile extends File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $archiveName String
- * @param $reason String
- * @param $suppress Boolean
- * @throws MWException or FSException on database or file store failure
- * @return FileRepoStatus object.
+ * @param string $archiveName
+ * @param string $reason
+ * @param bool $suppress
+ * @param User|null $user
+ * @throws MWException Exception on database or file store failure
+ * @return FileRepoStatus
*/
- function deleteOld( $archiveName, $reason, $suppress = false ) {
+ function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
global $wgUseSquid;
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
return $this->readOnlyFatalStatus();
}
- $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
+ $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
$this->lock(); // begin
$batch->addOld( $archiveName );
@@ -1638,9 +1718,9 @@ class LocalFile extends File {
*
* May throw database exceptions on error.
*
- * @param array $versions set of record ids of deleted items to restore,
- * or empty to restore all revisions.
- * @param $unsuppress Boolean
+ * @param array $versions Set of record ids of deleted items to restore,
+ * or empty to restore all revisions.
+ * @param bool $unsuppress
* @return FileRepoStatus
*/
function restore( $versions = array(), $unsuppress = false ) {
@@ -1675,7 +1755,7 @@ class LocalFile extends File {
/**
* Get the URL of the file description page.
- * @return String
+ * @return string
*/
function getDescriptionUrl() {
return $this->title->getLocalURL();
@@ -1686,7 +1766,7 @@ class LocalFile extends File {
* This is not used by ImagePage for local files, since (among other things)
* it skips the parser cache.
*
- * @param $lang Language What language to get description in (Optional)
+ * @param Language $lang What language to get description in (Optional)
* @return bool|mixed
*/
function getDescriptionText( $lang = null ) {
@@ -1699,10 +1779,13 @@ class LocalFile extends File {
return false;
}
$pout = $content->getParserOutput( $this->title, null, new ParserOptions( null, $lang ) );
+
return $pout->getText();
}
/**
+ * @param int $audience
+ * @param User $user
* @return string
*/
function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
@@ -1710,8 +1793,8 @@ class LocalFile extends File {
if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
return '';
} elseif ( $audience == self::FOR_THIS_USER
- && !$this->userCan( self::DELETED_COMMENT, $user ) )
- {
+ && !$this->userCan( self::DELETED_COMMENT, $user )
+ ) {
return '';
} else {
return $this->description;
@@ -1723,6 +1806,7 @@ class LocalFile extends File {
*/
function getTimestamp() {
$this->load();
+
return $this->timestamp;
}
@@ -1756,15 +1840,17 @@ class LocalFile extends File {
*/
function isCacheable() {
$this->load();
+
// If extra data (metadata) was not loaded then it must have been large
return $this->extraDataLoaded
- && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
+ && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
}
/**
* Start a transaction and lock the image for update
* Increments a reference counter if the lock is already held
- * @return boolean True if the image exists, false otherwise
+ * @throws MWException Throws an error if the lock was not acquired
+ * @return bool Success
*/
function lock() {
$dbw = $this->repo->getMasterDB();
@@ -1776,19 +1862,22 @@ class LocalFile extends File {
}
$this->locked++;
// Bug 54736: use simple lock to handle when the file does not exist.
- // SELECT FOR UPDATE only locks records not the gaps where there are none.
- $cache = wfGetMainCache();
- $key = $this->getCacheKey();
- if ( !$cache->lock( $key, 60 ) ) {
+ // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
+ // Also, that would cause contention on INSERT of similarly named rows.
+ $backend = $this->getRepo()->getBackend();
+ $lockPaths = array( $this->getPath() ); // represents all versions of the file
+ $status = $backend->lockFiles( $lockPaths, LockManager::LOCK_EX, 5 );
+ if ( !$status->isGood() ) {
throw new MWException( "Could not acquire lock for '{$this->getName()}.'" );
}
- $dbw->onTransactionIdle( function() use ( $cache, $key ) {
- $cache->unlock( $key ); // release on commit
+ $dbw->onTransactionIdle( function () use ( $backend, $lockPaths ) {
+ $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX ); // release on commit
} );
}
- return $dbw->selectField( 'image', '1',
- array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ $this->markVolatile(); // file may change soon
+
+ return true;
}
/**
@@ -1807,6 +1896,48 @@ class LocalFile extends File {
}
/**
+ * Mark a file as about to be changed
+ *
+ * This sets a cache key that alters master/slave DB loading behavior
+ *
+ * @return bool Success
+ */
+ protected function markVolatile() {
+ global $wgMemc;
+
+ $key = $this->repo->getSharedCacheKey( 'file-volatile', md5( $this->getName() ) );
+ if ( $key ) {
+ $this->lastMarkedVolatile = time();
+ return $wgMemc->set( $key, $this->lastMarkedVolatile, self::VOLATILE_TTL );
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if a file is about to be changed or has been changed recently
+ *
+ * @see LocalFile::isVolatile()
+ * @return bool Whether the file is volatile
+ */
+ protected function isVolatile() {
+ global $wgMemc;
+
+ $key = $this->repo->getSharedCacheKey( 'file-volatile', md5( $this->getName() ) );
+ if ( !$key ) {
+ // repo unavailable; bail.
+ return false;
+ }
+
+ if ( $this->lastMarkedVolatile === 0 ) {
+ $this->lastMarkedVolatile = $wgMemc->get( $key ) ?: 0;
+ }
+
+ $volatileDuration = time() - $this->lastMarkedVolatile;
+ return $volatileDuration <= self::VOLATILE_TTL;
+ }
+
+ /**
* Roll back the DB transaction and mark the image unlocked
*/
function unlockAndRollback() {
@@ -1823,6 +1954,13 @@ class LocalFile extends File {
return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
$this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
}
+
+ /**
+ * Clean up any dangling locks
+ */
+ function __destruct() {
+ $this->unlock();
+ }
} // LocalFile class
# ------------------------------------------------------------------------------
@@ -1832,24 +1970,46 @@ class LocalFile extends File {
* @ingroup FileAbstraction
*/
class LocalFileDeleteBatch {
+ /** @var LocalFile */
+ private $file;
- /**
- * @var LocalFile
- */
- var $file;
+ /** @var string */
+ private $reason;
+
+ /** @var array */
+ private $srcRels = array();
+
+ /** @var array */
+ private $archiveUrls = array();
+
+ /** @var array Items to be processed in the deletion batch */
+ private $deletionBatch;
- var $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
- var $status;
+ /** @var bool Wether to suppress all suppressable fields when deleting */
+ private $suppress;
+
+ /** @var FileRepoStatus */
+ private $status;
+
+ /** @var User */
+ private $user;
/**
- * @param $file File
- * @param $reason string
- * @param $suppress bool
+ * @param File $file
+ * @param string $reason
+ * @param bool $suppress
+ * @param User|null $user
*/
- function __construct( File $file, $reason = '', $suppress = false ) {
+ 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();
}
@@ -1858,7 +2018,7 @@ class LocalFileDeleteBatch {
}
/**
- * @param $oldName string
+ * @param string $oldName
*/
function addOld( $oldName ) {
$this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
@@ -1867,7 +2027,7 @@ class LocalFileDeleteBatch {
/**
* Add the old versions of the image to the batch
- * @return Array List of archive names from old versions
+ * @return array List of archive names from old versions
*/
function addOlds() {
$archiveNames = array();
@@ -1919,7 +2079,8 @@ class LocalFileDeleteBatch {
$res = $dbw->select(
'oldimage',
array( 'oi_archive_name', 'oi_sha1' ),
- 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ array( 'oi_archive_name' => array_keys( $oldRels ),
+ 'oi_name' => $this->file->getName() ), // performance
__METHOD__
);
@@ -1962,11 +2123,9 @@ class LocalFileDeleteBatch {
}
function doDBInserts() {
- global $wgUser;
-
$dbw = $this->file->repo->getMasterDB();
$encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
- $encUserId = $dbw->addQuotes( $wgUser->getId() );
+ $encUserId = $dbw->addQuotes( $this->user->getId() );
$encReason = $dbw->addQuotes( $this->reason );
$encGroup = $dbw->addQuotes( 'deleted' );
$ext = $this->file->getExtension();
@@ -1992,27 +2151,31 @@ class LocalFileDeleteBatch {
$dbw->insertSelect( 'filearchive', 'image',
array(
'fa_storage_group' => $encGroup,
- 'fa_storage_key' => "CASE WHEN img_sha1='' THEN '' ELSE $concat END",
- 'fa_deleted_user' => $encUserId,
+ 'fa_storage_key' => $dbw->conditional(
+ array( 'img_sha1' => '' ),
+ $dbw->addQuotes( '' ),
+ $concat
+ ),
+ 'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
- 'fa_deleted_reason' => $encReason,
- 'fa_deleted' => $this->suppress ? $bitfield : 0,
+ 'fa_deleted_reason' => $encReason,
+ 'fa_deleted' => $this->suppress ? $bitfield : 0,
- 'fa_name' => 'img_name',
+ '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' => 'img_description',
- 'fa_user' => 'img_user',
- 'fa_user_text' => 'img_user_text',
- 'fa_timestamp' => 'img_timestamp',
- 'fa_sha1' => 'img_sha1',
+ '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' => 'img_description',
+ 'fa_user' => 'img_user',
+ 'fa_user_text' => 'img_user_text',
+ 'fa_timestamp' => 'img_timestamp',
+ 'fa_sha1' => 'img_sha1',
), $where, __METHOD__ );
}
@@ -2020,31 +2183,35 @@ class LocalFileDeleteBatch {
$concat = $dbw->buildConcat( array( "oi_sha1", $encExt ) );
$where = array(
'oi_name' => $this->file->getName(),
- 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')' );
+ 'oi_archive_name' => array_keys( $oldRels ) );
$dbw->insertSelect( 'filearchive', 'oldimage',
array(
'fa_storage_group' => $encGroup,
- 'fa_storage_key' => "CASE WHEN oi_sha1='' THEN '' ELSE $concat END",
- 'fa_deleted_user' => $encUserId,
+ 'fa_storage_key' => $dbw->conditional(
+ array( 'oi_sha1' => '' ),
+ $dbw->addQuotes( '' ),
+ $concat
+ ),
+ 'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
- 'fa_deleted_reason' => $encReason,
- 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
+ 'fa_deleted_reason' => $encReason,
+ 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
- 'fa_name' => 'oi_name',
+ 'fa_name' => 'oi_name',
'fa_archive_name' => 'oi_archive_name',
- 'fa_size' => 'oi_size',
- 'fa_width' => 'oi_width',
- 'fa_height' => 'oi_height',
- 'fa_metadata' => 'oi_metadata',
- 'fa_bits' => 'oi_bits',
- 'fa_media_type' => 'oi_media_type',
- 'fa_major_mime' => 'oi_major_mime',
- 'fa_minor_mime' => 'oi_minor_mime',
- 'fa_description' => 'oi_description',
- 'fa_user' => 'oi_user',
- 'fa_user_text' => 'oi_user_text',
- 'fa_timestamp' => 'oi_timestamp',
- 'fa_sha1' => 'oi_sha1',
+ 'fa_size' => 'oi_size',
+ 'fa_width' => 'oi_width',
+ 'fa_height' => 'oi_height',
+ 'fa_metadata' => 'oi_metadata',
+ 'fa_bits' => 'oi_bits',
+ 'fa_media_type' => 'oi_media_type',
+ 'fa_major_mime' => 'oi_major_mime',
+ 'fa_minor_mime' => 'oi_minor_mime',
+ 'fa_description' => 'oi_description',
+ 'fa_user' => 'oi_user',
+ 'fa_user_text' => 'oi_user_text',
+ 'fa_timestamp' => 'oi_timestamp',
+ 'fa_sha1' => 'oi_sha1',
), $where, __METHOD__ );
}
}
@@ -2083,7 +2250,7 @@ class LocalFileDeleteBatch {
$res = $dbw->select( 'oldimage',
array( 'oi_archive_name' ),
array( 'oi_name' => $this->file->getName(),
- 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ 'oi_archive_name' => array_keys( $oldRels ),
$dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
__METHOD__ );
@@ -2117,7 +2284,12 @@ class LocalFileDeleteBatch {
$this->doDBInserts();
// Removes non-existent file from the batch, so we don't get errors.
- $this->deletionBatch = $this->removeNonexistentFiles( $this->deletionBatch );
+ $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 );
@@ -2132,6 +2304,7 @@ class LocalFileDeleteBatch {
// TODO: delete the defunct filearchive rows if we are using a non-transactional DB
$this->file->unlockAndRollback();
wfProfileOut( __METHOD__ );
+
return $this->status;
}
@@ -2147,8 +2320,8 @@ class LocalFileDeleteBatch {
/**
* Removes non-existent files from a deletion batch.
- * @param $batch array
- * @return array
+ * @param array $batch
+ * @return Status
*/
function removeNonexistentFiles( $batch ) {
$files = $newBatch = array();
@@ -2159,6 +2332,10 @@ class LocalFileDeleteBatch {
}
$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]] ) {
@@ -2166,7 +2343,7 @@ class LocalFileDeleteBatch {
}
}
- return $newBatch;
+ return Status::newGood( $newBatch );
}
}
@@ -2177,16 +2354,24 @@ class LocalFileDeleteBatch {
* @ingroup FileAbstraction
*/
class LocalFileRestoreBatch {
- /**
- * @var LocalFile
- */
- var $file;
+ /** @var LocalFile */
+ private $file;
+
+ /** @var array List of file IDs to restore */
+ private $cleanupBatch;
+
+ /** @var array List of file IDs to restore */
+ private $ids;
+
+ /** @var bool Add all revisions of the file */
+ private $all;
- var $cleanupBatch, $ids, $all, $unsuppress = false;
+ /** @var bool Wether to remove all settings for suppressed fields */
+ private $unsuppress = false;
/**
- * @param $file File
- * @param $unsuppress bool
+ * @param File $file
+ * @param bool $unsuppress
*/
function __construct( File $file, $unsuppress = false ) {
$this->file = $file;
@@ -2197,6 +2382,7 @@ class LocalFileRestoreBatch {
/**
* Add a file by ID
+ * @param int $fa_id
*/
function addId( $fa_id ) {
$this->ids[] = $fa_id;
@@ -2204,6 +2390,7 @@ class LocalFileRestoreBatch {
/**
* Add a whole lot of files by ID
+ * @param int[] $ids
*/
function addIds( $ids ) {
$this->ids = array_merge( $this->ids, $ids );
@@ -2232,16 +2419,20 @@ class LocalFileRestoreBatch {
return $this->file->repo->newGood();
}
- $exists = $this->file->lock();
+ $this->file->lock();
+
$dbw = $this->file->repo->getMasterDB();
$status = $this->file->repo->newGood();
+ $exists = (bool)$dbw->selectField( 'image', '1',
+ array( 'img_name' => $this->file->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+
// Fetch all or selected archived revisions for the file,
// sorted from the most recent to the oldest.
$conditions = array( 'fa_name' => $this->file->getName() );
if ( !$this->all ) {
- $conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')';
+ $conditions['fa_id'] = $this->ids;
}
$result = $dbw->select(
@@ -2276,7 +2467,8 @@ class LocalFileRestoreBatch {
continue;
}
- $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key;
+ $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) .
+ $row->fa_storage_key;
$deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
if ( isset( $row->fa_sha1 ) ) {
@@ -2294,7 +2486,8 @@ class LocalFileRestoreBatch {
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 ) ) {
+ || is_null( $row->fa_metadata )
+ ) {
// Refresh our metadata
// Required for a new current revision; nice for older ones too. :)
$props = RepoGroup::singleton()->getFileProps( $deletedUrl );
@@ -2303,7 +2496,7 @@ class LocalFileRestoreBatch {
'minor_mime' => $row->fa_minor_mime,
'major_mime' => $row->fa_major_mime,
'media_type' => $row->fa_media_type,
- 'metadata' => $row->fa_metadata
+ 'metadata' => $row->fa_metadata
);
}
@@ -2311,20 +2504,20 @@ class LocalFileRestoreBatch {
// This revision will be published as the new current version
$destRel = $this->file->getRel();
$insertCurrent = array(
- '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_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_description' => $row->fa_description,
- 'img_user' => $row->fa_user,
- 'img_user_text' => $row->fa_user_text,
- 'img_timestamp' => $row->fa_timestamp,
- 'img_sha1' => $sha1
+ 'img_user' => $row->fa_user,
+ 'img_user_text' => $row->fa_user_text,
+ 'img_timestamp' => $row->fa_timestamp,
+ 'img_sha1' => $sha1
);
// The live (current) version cannot be hidden!
@@ -2350,22 +2543,22 @@ class LocalFileRestoreBatch {
$archiveNames[$archiveName] = true;
$destRel = $this->file->getArchiveRel( $archiveName );
$insertBatch[] = array(
- 'oi_name' => $row->fa_name,
+ '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_description' => $row->fa_description,
- 'oi_user' => $row->fa_user,
- 'oi_user_text' => $row->fa_user_text,
- '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 );
+ 'oi_size' => $row->fa_size,
+ 'oi_width' => $row->fa_width,
+ 'oi_height' => $row->fa_height,
+ 'oi_bits' => $row->fa_bits,
+ 'oi_description' => $row->fa_description,
+ 'oi_user' => $row->fa_user,
+ 'oi_user_text' => $row->fa_user_text,
+ '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 );
}
$deleteIds[] = $row->fa_id;
@@ -2391,7 +2584,12 @@ class LocalFileRestoreBatch {
}
// Remove missing files from batch, so we don't get errors when undeleting them
- $storeBatch = $this->removeNonexistentFiles( $storeBatch );
+ $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
@@ -2424,7 +2622,7 @@ class LocalFileRestoreBatch {
if ( $deleteIds ) {
$dbw->delete( 'filearchive',
- array( 'fa_id IN (' . $dbw->makeList( $deleteIds ) . ')' ),
+ array( 'fa_id' => $deleteIds ),
__METHOD__ );
}
@@ -2450,8 +2648,8 @@ class LocalFileRestoreBatch {
/**
* Removes non-existent files from a store batch.
- * @param $triplets array
- * @return array
+ * @param array $triplets
+ * @return Status
*/
function removeNonexistentFiles( $triplets ) {
$files = $filteredTriplets = array();
@@ -2460,6 +2658,10 @@ class LocalFileRestoreBatch {
}
$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]] ) {
@@ -2467,12 +2669,12 @@ class LocalFileRestoreBatch {
}
}
- return $filteredTriplets;
+ return Status::newGood( $filteredTriplets );
}
/**
* Removes non-existent files from a cleanup batch.
- * @param $batch array
+ * @param array $batch
* @return array
*/
function removeNonexistentFromCleanup( $batch ) {
@@ -2541,23 +2743,22 @@ class LocalFileRestoreBatch {
* @ingroup FileAbstraction
*/
class LocalFileMoveBatch {
+ /** @var LocalFile */
+ protected $file;
- /**
- * @var LocalFile
- */
- var $file;
+ /** @var Title */
+ protected $target;
- /**
- * @var Title
- */
- var $target;
+ protected $cur;
- var $cur, $olds, $oldCount, $archive;
+ protected $olds;
- /**
- * @var DatabaseBase
- */
- var $db;
+ protected $oldCount;
+
+ protected $archive;
+
+ /** @var DatabaseBase */
+ protected $db;
/**
* @param File $file
@@ -2584,7 +2785,7 @@ class LocalFileMoveBatch {
/**
* Add the old versions of the image to the batch
- * @return Array List of archive names from old versions
+ * @return array List of archive names from old versions
*/
function addOlds() {
$archiveBase = 'archive';
@@ -2595,7 +2796,8 @@ class LocalFileMoveBatch {
$result = $this->db->select( 'oldimage',
array( 'oi_archive_name', 'oi_deleted' ),
array( 'oi_name' => $this->oldName ),
- __METHOD__
+ __METHOD__,
+ array( 'FOR UPDATE' ) // ignore snapshot
);
foreach ( $result as $row ) {
@@ -2640,9 +2842,16 @@ class LocalFileMoveBatch {
$status = $repo->newGood();
$triplets = $this->getMoveTriplets();
- $triplets = $this->removeNonexistentFiles( $triplets );
+ $checkStatus = $this->removeNonexistentFiles( $triplets );
+ if ( !$checkStatus->isGood() ) {
+ $status->merge( $checkStatus );
+ return $status;
+ }
+ $triplets = $checkStatus->value;
+ $destFile = wfLocalFile( $this->target );
$this->file->lock(); // begin
+ $destFile->lock(); // quickly fail if destination is not available
// Rename the file versions metadata in the DB.
// This implicitly locks the destination file, which avoids race conditions.
// If we moved the files from A -> C before DB updates, another process could
@@ -2650,25 +2859,32 @@ class LocalFileMoveBatch {
// cleanupTarget() to trigger. It would delete the C files and cause data loss.
$statusDb = $this->doDBUpdates();
if ( !$statusDb->isGood() ) {
+ $destFile->unlock();
$this->file->unlockAndRollback();
$statusDb->ok = false;
+
return $statusDb;
}
- wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
+ wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} in database: " .
+ "{$statusDb->successCount} successes, {$statusDb->failCount} failures" );
// 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" );
+ 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->unlockAndRollback(); // unlocks the destination
wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
$statusMove->ok = false;
+
return $statusMove;
}
+ $destFile->unlock();
$this->file->unlock(); // done
// Everything went ok, remove the source files
@@ -2704,6 +2920,7 @@ class LocalFileMoveBatch {
} else {
$status->failCount++;
$status->fatal( 'imageinvalidfilename' );
+
return $status;
}
@@ -2745,7 +2962,10 @@ class LocalFileMoveBatch {
// $move: (oldRelativePath, newRelativePath)
$srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
$triplets[] = array( $srcUrl, 'public', $move[1] );
- wfDebugLog( 'imagemove', "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}" );
+ wfDebugLog(
+ 'imagemove',
+ "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}"
+ );
}
return $triplets;
@@ -2753,8 +2973,8 @@ class LocalFileMoveBatch {
/**
* Removes non-existent files from move batch.
- * @param $triplets array
- * @return array
+ * @param array $triplets
+ * @return Status
*/
function removeNonexistentFiles( $triplets ) {
$files = array();
@@ -2764,8 +2984,12 @@ class LocalFileMoveBatch {
}
$result = $this->file->repo->fileExistsBatch( $files );
- $filteredTriplets = array();
+ if ( in_array( null, $result, true ) ) {
+ return Status::newFatal( 'backend-fail-internal',
+ $this->file->repo->getBackend()->getName() );
+ }
+ $filteredTriplets = array();
foreach ( $triplets as $file ) {
if ( $result[$file[0]] ) {
$filteredTriplets[] = $file;
@@ -2774,12 +2998,13 @@ class LocalFileMoveBatch {
}
}
- return $filteredTriplets;
+ 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
*/
function cleanupTarget( $triplets ) {
// Create dest pairs from the triplets
@@ -2795,6 +3020,7 @@ class LocalFileMoveBatch {
/**
* 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
*/
function cleanupSource( $triplets ) {
// Create source file names from the triplets
diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php
index 2c545963..710058fb 100644
--- a/includes/filerepo/file/OldLocalFile.php
+++ b/includes/filerepo/file/OldLocalFile.php
@@ -27,15 +27,19 @@
* @ingroup FileAbstraction
*/
class OldLocalFile extends LocalFile {
- var $requestedTime, $archive_name;
+ /** @var string Timestamp */
+ protected $requestedTime;
+
+ /** @var string Archive name */
+ protected $archive_name;
const CACHE_VERSION = 1;
const MAX_CACHE_ROWS = 20;
/**
- * @param $title Title
- * @param $repo FileRepo
- * @param $time null
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param null|int $time Timestamp or null
* @return OldLocalFile
* @throws MWException
*/
@@ -44,13 +48,14 @@ class OldLocalFile extends LocalFile {
if ( $time === null ) {
throw new MWException( __METHOD__ . ' got null for $time parameter' );
}
+
return new self( $title, $repo, $time, null );
}
/**
- * @param $title Title
- * @param $repo FileRepo
- * @param $archiveName
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param string $archiveName
* @return OldLocalFile
*/
static function newFromArchiveName( $title, $repo, $archiveName ) {
@@ -58,14 +63,15 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param $row
- * @param $repo FileRepo
+ * @param stdClass $row
+ * @param FileRepo $repo
* @return OldLocalFile
*/
static function newFromRow( $row, $repo ) {
$title = Title::makeTitle( NS_FILE, $row->oi_name );
$file = new self( $title, $repo, null, $row->oi_archive_name );
$file->loadFromRow( $row, 'oi_' );
+
return $file;
}
@@ -73,8 +79,8 @@ class OldLocalFile extends LocalFile {
* Create a OldLocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
*
- * @param string $sha1 base-36 SHA-1
- * @param $repo LocalRepo
+ * @param string $sha1 Base-36 SHA-1
+ * @param LocalRepo $repo
* @param string|bool $timestamp MW_timestamp (optional)
*
* @return bool|OldLocalFile
@@ -121,10 +127,10 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param $title Title
- * @param $repo FileRepo
- * @param string $time timestamp or null to load by archive name
- * @param string $archiveName archive name or null to load by timestamp
+ * @param Title $title
+ * @param FileRepo $repo
+ * @param string $time Timestamp or null to load by archive name
+ * @param string $archiveName Archive name or null to load by timestamp
* @throws MWException
*/
function __construct( $title, $repo, $time, $archiveName ) {
@@ -144,12 +150,13 @@ class OldLocalFile extends LocalFile {
}
/**
- * @return String
+ * @return string
*/
function getArchiveName() {
if ( !isset( $this->archive_name ) ) {
$this->load();
}
+
return $this->archive_name;
}
@@ -167,10 +174,11 @@ class OldLocalFile extends LocalFile {
return $this->exists() && !$this->isDeleted( File::DELETED_FILE );
}
- function loadFromDB() {
+ function loadFromDB( $flags = 0 ) {
wfProfileIn( __METHOD__ );
$this->dataLoaded = true;
+
$dbr = $this->repo->getSlaveDB();
$conds = array( 'oi_name' => $this->getName() );
if ( is_null( $this->requestedTime ) ) {
@@ -226,13 +234,14 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param $prefix string
+ * @param string $prefix
* @return array
*/
function getCacheFields( $prefix = 'img_' ) {
$fields = parent::getCacheFields( $prefix );
$fields[] = $prefix . 'archive_name';
$fields[] = $prefix . 'deleted';
+
return $fields;
}
@@ -258,6 +267,7 @@ class OldLocalFile extends LocalFile {
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
wfProfileOut( __METHOD__ );
+
return;
}
@@ -267,15 +277,15 @@ class OldLocalFile extends LocalFile {
wfDebug( __METHOD__ . ': upgrading ' . $this->archive_name . " to the current schema\n" );
$dbw->update( 'oldimage',
array(
- 'oi_size' => $this->size, // sanity
- 'oi_width' => $this->width,
- 'oi_height' => $this->height,
- 'oi_bits' => $this->bits,
+ 'oi_size' => $this->size, // sanity
+ 'oi_width' => $this->width,
+ 'oi_height' => $this->height,
+ 'oi_bits' => $this->bits,
'oi_media_type' => $this->media_type,
'oi_major_mime' => $major,
'oi_minor_mime' => $minor,
- 'oi_metadata' => $this->metadata,
- 'oi_sha1' => $this->sha1,
+ 'oi_metadata' => $this->metadata,
+ 'oi_sha1' => $this->sha1,
), array(
'oi_name' => $this->getName(),
'oi_archive_name' => $this->archive_name ),
@@ -285,12 +295,13 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param $field Integer: one of DELETED_* bitfield constants
- * for file or revision rows
+ * @param int $field One of DELETED_* bitfield constants for file or
+ * revision rows
* @return bool
*/
function isDeleted( $field ) {
$this->load();
+
return ( $this->deleted & $field ) == $field;
}
@@ -300,6 +311,7 @@ class OldLocalFile extends LocalFile {
*/
function getVisibility() {
$this->load();
+
return (int)$this->deleted;
}
@@ -307,12 +319,13 @@ class OldLocalFile extends LocalFile {
* Determine if the current user is allowed to view a particular
* field of this image file, if it's marked as deleted.
*
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
+ * @param int $field
+ * @param User|null $user User object to check, or null to use $wgUser
* @return bool
*/
function userCan( $field, User $user = null ) {
$this->load();
+
return Revision::userCanBitfield( $this->deleted, $field, $user );
}
@@ -321,12 +334,11 @@ class OldLocalFile extends LocalFile {
*
* @param string $srcPath File system path of the source file
* @param string $archiveName Full archive name of the file, in the form
- * $timestamp!$filename, where $filename must match $this->getName()
- *
- * @param $timestamp string
- * @param $comment string
- * @param $user
- * @param $flags int
+ * $timestamp!$filename, where $filename must match $this->getName()
+ * @param string $timestamp
+ * @param string $comment
+ * @param User $user
+ * @param int $flags
* @return FileRepoStatus
*/
function uploadOld( $srcPath, $archiveName, $timestamp, $comment, $user, $flags = 0 ) {
@@ -353,9 +365,9 @@ class OldLocalFile extends LocalFile {
*
* @param string $srcPath File system path to the source file
* @param string $archiveName The archive name of the file
- * @param $timestamp string
+ * @param string $timestamp
* @param string $comment Upload comment
- * @param $user User User who did this upload
+ * @param User $user User who did this upload
* @return bool
*/
function recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) {
@@ -370,21 +382,21 @@ class OldLocalFile extends LocalFile {
$dbw->insert( 'oldimage',
array(
- 'oi_name' => $this->getName(),
+ 'oi_name' => $this->getName(),
'oi_archive_name' => $archiveName,
- 'oi_size' => $props['size'],
- 'oi_width' => intval( $props['width'] ),
- 'oi_height' => intval( $props['height'] ),
- 'oi_bits' => $props['bits'],
- 'oi_timestamp' => $dbw->timestamp( $timestamp ),
- 'oi_description' => $comment,
- 'oi_user' => $user->getId(),
- 'oi_user_text' => $user->getName(),
- 'oi_metadata' => $props['metadata'],
- 'oi_media_type' => $props['media_type'],
- 'oi_major_mime' => $props['major_mime'],
- 'oi_minor_mime' => $props['minor_mime'],
- 'oi_sha1' => $props['sha1'],
+ 'oi_size' => $props['size'],
+ 'oi_width' => intval( $props['width'] ),
+ 'oi_height' => intval( $props['height'] ),
+ 'oi_bits' => $props['bits'],
+ 'oi_timestamp' => $dbw->timestamp( $timestamp ),
+ 'oi_description' => $comment,
+ 'oi_user' => $user->getId(),
+ 'oi_user_text' => $user->getName(),
+ 'oi_metadata' => $props['metadata'],
+ 'oi_media_type' => $props['media_type'],
+ 'oi_major_mime' => $props['major_mime'],
+ 'oi_minor_mime' => $props['minor_mime'],
+ 'oi_sha1' => $props['sha1'],
), __METHOD__
);
@@ -393,4 +405,17 @@ class OldLocalFile extends LocalFile {
return true;
}
+ /**
+ * If archive name is an empty string, then file does not "exist"
+ *
+ * This is the case for a couple files on Wikimedia servers where
+ * the old version is "lost".
+ */
+ public function exists() {
+ $archiveName = $this->getArchiveName();
+ if ( $archiveName === '' || !is_string( $archiveName ) ) {
+ return false;
+ }
+ return parent::exists();
+ }
}
diff --git a/includes/filerepo/file/UnregisteredLocalFile.php b/includes/filerepo/file/UnregisteredLocalFile.php
index 47ba6d6b..5a3e4e9c 100644
--- a/includes/filerepo/file/UnregisteredLocalFile.php
+++ b/includes/filerepo/file/UnregisteredLocalFile.php
@@ -27,23 +27,34 @@
*
* Read-only.
*
- * TODO: Currently it doesn't really work in the repository role, there are
+ * @todo Currently it doesn't really work in the repository role, there are
* lots of functions missing. It is used by the WebStore extension in the
* standalone role.
*
* @ingroup FileAbstraction
*/
class UnregisteredLocalFile extends File {
- var $title, $path, $mime, $dims, $metadata;
+ /** @var Title */
+ protected $title;
- /**
- * @var MediaHandler
- */
- var $handler;
+ /** @var string */
+ protected $path;
+
+ /** @var bool|string */
+ protected $mime;
+
+ /** @var array Dimension data */
+ protected $dims;
+
+ /** @var bool|string Handler-specific metadata which will be saved in the img_metadata field */
+ protected $metadata;
+
+ /** @var MediaHandler */
+ public $handler;
/**
* @param string $path Storage path
- * @param $mime string
+ * @param string $mime
* @return UnregisteredLocalFile
*/
static function newFromPath( $path, $mime ) {
@@ -51,8 +62,8 @@ class UnregisteredLocalFile extends File {
}
/**
- * @param $title
- * @param $repo
+ * @param Title $title
+ * @param FileRepo $repo
* @return UnregisteredLocalFile
*/
static function newFromTitle( $title, $repo ) {
@@ -64,14 +75,15 @@ class UnregisteredLocalFile extends File {
* A FileRepo object is not required here, unlike most other File classes.
*
* @throws MWException
- * @param $title Title|bool
- * @param $repo FileRepo|bool
- * @param $path string|bool
- * @param $mime string|bool
+ * @param Title|bool $title
+ * @param FileRepo|bool $repo
+ * @param string|bool $path
+ * @param string|bool $mime
*/
function __construct( $title = false, $repo = false, $path = false, $mime = false ) {
if ( !( $title && $repo ) && !$path ) {
- throw new MWException( __METHOD__ . ': not enough parameters, must specify title and repo, or a full path' );
+ throw new MWException( __METHOD__ .
+ ': not enough parameters, must specify title and repo, or a full path' );
}
if ( $title instanceof Title ) {
$this->title = File::normalizeTitle( $title, 'exception' );
@@ -95,7 +107,7 @@ class UnregisteredLocalFile extends File {
}
/**
- * @param $page int
+ * @param int $page
* @return bool
*/
private function cachePageDimensions( $page = 1 ) {
@@ -105,24 +117,27 @@ class UnregisteredLocalFile extends File {
}
$this->dims[$page] = $this->handler->getPageDimensions( $this, $page );
}
+
return $this->dims[$page];
}
/**
- * @param $page int
- * @return number
+ * @param int $page
+ * @return int
*/
function getWidth( $page = 1 ) {
$dim = $this->cachePageDimensions( $page );
+
return $dim['width'];
}
/**
- * @param $page int
- * @return number
+ * @param int $page
+ * @return int
*/
function getHeight( $page = 1 ) {
$dim = $this->cachePageDimensions( $page );
+
return $dim['height'];
}
@@ -134,17 +149,19 @@ class UnregisteredLocalFile extends File {
$magic = MimeMagic::singleton();
$this->mime = $magic->guessMimeType( $this->getLocalRefPath() );
}
+
return $this->mime;
}
/**
- * @param $filename String
- * @return Array|bool
+ * @param string $filename
+ * @return array|bool
*/
function getImageSize( $filename ) {
if ( !$this->getHandler() ) {
return false;
}
+
return $this->handler->getImageSize( $this, $this->getLocalRefPath() );
}
@@ -159,6 +176,7 @@ class UnregisteredLocalFile extends File {
$this->metadata = $this->handler->getMetadata( $this, $this->getLocalRefPath() );
}
}
+
return $this->metadata;
}
@@ -179,6 +197,7 @@ class UnregisteredLocalFile extends File {
*/
function getSize() {
$this->assertRepoDefined();
+
return $this->repo->getFileSize( $this->path );
}
@@ -187,7 +206,7 @@ class UnregisteredLocalFile extends File {
* The file at the path of $fsFile should not be deleted (or at least
* not until the end of the request). This is mostly a performance hack.
*
- * @param $fsFile FSFile
+ * @param FSFile $fsFile
* @return void
*/
public function setLocalReference( FSFile $fsFile ) {
diff --git a/includes/gallery/ImageGalleryBase.php b/includes/gallery/ImageGalleryBase.php
index f8b8c505..b0a593de 100644
--- a/includes/gallery/ImageGalleryBase.php
+++ b/includes/gallery/ImageGalleryBase.php
@@ -28,53 +28,89 @@
* @ingroup Media
*/
abstract class ImageGalleryBase extends ContextSource {
- var $mImages, $mShowBytes, $mShowFilename, $mMode;
- var $mCaption = false;
+ /**
+ * @var array Gallery images
+ * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
+ */
+ public $mImages;
+
+ /**
+ * @var bool Whether to show the filesize in bytes in categories
+ * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
+ */
+ public $mShowBytes;
+
+ /**
+ * @var bool Whether to show the filename. Default: true
+ * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
+ */
+ public $mShowFilename;
+
+ /**
+ * @var string Gallery mode. Default: traditional
+ * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
+ */
+ public $mMode;
+
+ /**
+ * @var bool|string Gallery caption. Default: false
+ * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
+ */
+ public $mCaption = false;
/**
- * Hide blacklisted images?
+ * @var bool Hide blacklisted images?
+ * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
*/
- var $mHideBadImages;
+ public $mHideBadImages;
/**
- * Registered parser object for output callbacks
- * @var Parser
+ * @var Parser Registered parser object for output callbacks
*/
- var $mParser;
+ public $mParser;
/**
- * Contextual title, used when images are being screened
- * against the bad image list
+ * @var Title Contextual title, used when images are being screened against
+ * the bad image list
*/
protected $contextTitle = false;
+ /** @var array */
protected $mAttribs = array();
+ /** @var bool */
static private $modeMapping = false;
/**
* Get a new image gallery. This is the method other callers
* should use to get a gallery.
*
- * @param String|bool $mode Mode to use. False to use the default.
+ * @param string|bool $mode Mode to use. False to use the default
+ * @param IContextSource|null $context
+ * @return ImageGalleryBase
+ * @throws MWException
*/
- static function factory( $mode = false ) {
- global $wgGalleryOptions, $wgContLang;
+ static function factory( $mode = false, IContextSource $context = null ) {
+ global $wgContLang;
self::loadModes();
+ if ( !$context ) {
+ $context = RequestContext::getMainAndWarn( __METHOD__ );
+ }
if ( !$mode ) {
- $mode = $wgGalleryOptions['mode'];
+ $galleryOpions = $context->getConfig()->get( 'GalleryOptions' );
+ $mode = $galleryOpions['mode'];
}
$mode = $wgContLang->lc( $mode );
if ( isset( self::$modeMapping[$mode] ) ) {
- return new self::$modeMapping[$mode]( $mode );
+ return new self::$modeMapping[$mode]( $mode, $context );
} else {
throw new MWException( "No gallery class registered for mode $mode" );
}
}
- static private function loadModes() {
+ private static function loadModes() {
if ( self::$modeMapping === false ) {
self::$modeMapping = array(
'traditional' => 'TraditionalImageGallery',
@@ -93,18 +129,24 @@ abstract class ImageGalleryBase extends ContextSource {
*
* You should not call this directly, but instead use
* ImageGalleryBase::factory().
+ * @param string $mode
+ * @param IContextSource|null $context
*/
- function __construct( $mode = 'traditional' ) {
- global $wgGalleryOptions;
+ function __construct( $mode = 'traditional', IContextSource $context = null ) {
+ if ( $context ) {
+ $this->setContext( $context );
+ }
+
+ $galleryOptions = $this->getConfig()->get( 'GalleryOptions' );
$this->mImages = array();
- $this->mShowBytes = $wgGalleryOptions['showBytes'];
+ $this->mShowBytes = $galleryOptions['showBytes'];
$this->mShowFilename = true;
$this->mParser = false;
$this->mHideBadImages = false;
- $this->mPerRow = $wgGalleryOptions['imagesPerRow'];
- $this->mWidths = $wgGalleryOptions['imageWidth'];
- $this->mHeights = $wgGalleryOptions['imageHeight'];
- $this->mCaptionLength = $wgGalleryOptions['captionLength'];
+ $this->mPerRow = $galleryOptions['imagesPerRow'];
+ $this->mWidths = $galleryOptions['imageWidth'];
+ $this->mHeights = $galleryOptions['imageHeight'];
+ $this->mCaptionLength = $galleryOptions['captionLength'];
$this->mMode = $mode;
}
@@ -116,7 +158,7 @@ abstract class ImageGalleryBase extends ContextSource {
* @note This also triggers using the page's target
* language instead of the user language.
*
- * @param $parser Parser
+ * @param Parser $parser
*/
function setParser( $parser ) {
$this->mParser = $parser;
@@ -124,6 +166,7 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Set bad image flag
+ * @param bool $flag
*/
function setHideBadImages( $flag = true ) {
$this->mHideBadImages = $flag;
@@ -150,8 +193,8 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Set how many images will be displayed per row.
*
- * @param $num Integer >= 0; If perrow=0 the gallery layout will adapt to screensize
- * invalid numbers will be rejected
+ * @param int $num Integer >= 0; If perrow=0 the gallery layout will adapt
+ * to screensize invalid numbers will be rejected
*/
public function setPerRow( $num ) {
if ( $num >= 0 ) {
@@ -162,7 +205,7 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Set how wide each image will be, in pixels.
*
- * @param $num Integer > 0; invalid numbers will be ignored
+ * @param int $num Integer > 0; invalid numbers will be ignored
*/
public function setWidths( $num ) {
if ( $num > 0 ) {
@@ -173,7 +216,7 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Set how high each image will be, in pixels.
*
- * @param $num Integer > 0; invalid numbers will be ignored
+ * @param int $num Integer > 0; invalid numbers will be ignored
*/
public function setHeights( $num ) {
if ( $num > 0 ) {
@@ -186,29 +229,20 @@ abstract class ImageGalleryBase extends ContextSource {
* to allow extensions to add additional parameters to
* <gallery> parser tag.
*
- * @param Array $options Attributes of gallery tag.
- */
- public function setAdditionalOptions( $options ) { }
-
- /**
- * Instruct the class to use a specific skin for rendering
- *
- * @param $skin Skin object
- * @deprecated since 1.18 Not used anymore
+ * @param array $options Attributes of gallery tag
*/
- function useSkin( $skin ) {
- wfDeprecated( __METHOD__, '1.18' );
- /* no op */
+ public function setAdditionalOptions( $options ) {
}
/**
* Add an image to the gallery.
*
- * @param $title Title object of the image that is added to the gallery
- * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
- * @param $alt String: Alt text for the image
- * @param $link String: Override image link (optional)
- * @param $handlerOpts Array: Array of options for image handler (aka page number)
+ * @param Title $title Title object of the image that is added to the gallery
+ * @param string $html Additional HTML text to be shown. The name and size
+ * of the image are always shown.
+ * @param string $alt Alt text for the image
+ * @param string $link Override image link (optional)
+ * @param array $handlerOpts Array of options for image handler (aka page number)
*/
function add( $title, $html = '', $alt = '', $link = '', $handlerOpts = array() ) {
if ( $title instanceof File ) {
@@ -222,11 +256,12 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Add an image at the beginning of the gallery.
*
- * @param $title Title object of the image that is added to the gallery
- * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
- * @param $alt String: Alt text for the image
- * @param $link String: Override image link (optional)
- * @param $handlerOpts Array: Array of options for image handler (aka page number)
+ * @param Title $title Title object of the image that is added to the gallery
+ * @param string $html Additional HTML text to be shown. The name and size
+ * of the image are always shown.
+ * @param string $alt Alt text for the image
+ * @param string $link Override image link (optional)
+ * @param array $handlerOpts Array of options for image handler (aka page number)
*/
function insert( $title, $html = '', $alt = '', $link = '', $handlerOpts = array() ) {
if ( $title instanceof File ) {
@@ -237,6 +272,14 @@ abstract class ImageGalleryBase extends ContextSource {
}
/**
+ * Returns the list of images this gallery contains
+ * @return array
+ */
+ public function getImages() {
+ return $this->mImages;
+ }
+
+ /**
* isEmpty() returns true if the gallery contains no images
* @return bool
*/
@@ -248,7 +291,7 @@ abstract class ImageGalleryBase extends ContextSource {
* Enable/Disable showing of the file size of an image in the gallery.
* Enabled by default.
*
- * @param $f Boolean: set to false to disable.
+ * @param bool $f Set to false to disable
*/
function setShowBytes( $f ) {
$this->mShowBytes = (bool)$f;
@@ -258,7 +301,7 @@ abstract class ImageGalleryBase extends ContextSource {
* Enable/Disable showing of the filename of an image in the gallery.
* Enabled by default.
*
- * @param $f Boolean: set to false to disable.
+ * @param bool $f Set to false to disable
*/
function setShowFilename( $f ) {
$this->mShowFilename = (bool)$f;
@@ -271,7 +314,7 @@ abstract class ImageGalleryBase extends ContextSource {
* Note -- if taking from user input, you should probably run through
* Sanitizer::validateAttributes() first.
*
- * @param array $attribs of HTML attribute pairs
+ * @param array $attribs Array of HTML attribute pairs
*/
function setAttributes( $attribs ) {
$this->mAttribs = $attribs;
@@ -280,12 +323,12 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Display an html representation of the gallery
*
- * @return String The html
+ * @return string The html
*/
abstract public function toHTML();
/**
- * @return Integer: number of images in the gallery
+ * @return int Number of images in the gallery
*/
public function count() {
return count( $this->mImages );
@@ -294,7 +337,7 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Set the contextual title
*
- * @param $title Title: contextual title
+ * @param Title $title Contextual title
*/
public function setContextTitle( $title ) {
$this->contextTitle = $title;
@@ -303,7 +346,7 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Get the contextual title, if applicable
*
- * @return mixed Title or false
+ * @return Title|bool Title or false
*/
public function getContextTitle() {
return is_object( $this->contextTitle ) && $this->contextTitle instanceof Title
@@ -313,19 +356,11 @@ abstract class ImageGalleryBase extends ContextSource {
/**
* Determines the correct language to be used for this image gallery
- * @return Language object
+ * @return Language
*/
protected function getRenderLang() {
return $this->mParser
? $this->mParser->getTargetLanguage()
: $this->getLanguage();
}
-
- /* Old constants no longer used.
- const THUMB_PADDING = 30;
- const GB_PADDING = 5;
- const GB_BORDERS = 8;
- */
-
}
-
diff --git a/includes/gallery/NolinesImageGallery.php b/includes/gallery/NolinesImageGallery.php
index 6b0d0fa6..70f5bd93 100644
--- a/includes/gallery/NolinesImageGallery.php
+++ b/includes/gallery/NolinesImageGallery.php
@@ -22,7 +22,6 @@
*/
class NolinesImageGallery extends TraditionalImageGallery {
-
protected function getThumbPadding() {
return 0;
}
diff --git a/includes/gallery/PackedImageGallery.php b/includes/gallery/PackedImageGallery.php
index 963ee6b9..52a49ddb 100644
--- a/includes/gallery/PackedImageGallery.php
+++ b/includes/gallery/PackedImageGallery.php
@@ -21,7 +21,6 @@
*/
class PackedImageGallery extends TraditionalImageGallery {
-
function __construct( $mode = 'traditional' ) {
parent::__construct( $mode );
// Does not support per row option.
@@ -49,6 +48,7 @@ class PackedImageGallery extends TraditionalImageGallery {
/**
* @param File $img The file being transformed. May be false
+ * @return array
*/
protected function getThumbParams( $img ) {
if ( $img && $img->getMediaType() === MEDIATYPE_AUDIO ) {
@@ -58,6 +58,7 @@ class PackedImageGallery extends TraditionalImageGallery {
// factor, so use random big number.
$width = $this->mHeights * 10 + 100;
}
+
// self::SCALE_FACTOR so the js has some room to manipulate sizes.
return array(
'width' => $width * self::SCALE_FACTOR,
@@ -70,14 +71,18 @@ class PackedImageGallery extends TraditionalImageGallery {
if ( $thumbWidth < 60 * self::SCALE_FACTOR ) {
$thumbWidth = 60 * self::SCALE_FACTOR;
}
+
return $thumbWidth / self::SCALE_FACTOR + $this->getThumbPadding();
}
/**
- * @param MediaTransformOutput|bool $thumb the thumbnail, or false if no thumb (which can happen)
+ * @param MediaTransformOutput|bool $thumb The thumbnail, or false if no
+ * thumb (which can happen)
+ * @return float
*/
protected function getGBWidth( $thumb ) {
$thumbWidth = $thumb ? $thumb->getWidth() : $this->mWidths * self::SCALE_FACTOR;
+
return $this->getThumbDivWidth( $thumbWidth ) + $this->getGBPadding();
}
@@ -90,6 +95,7 @@ class PackedImageGallery extends TraditionalImageGallery {
/**
* Add javascript which auto-justifies the rows by manipulating the image sizes.
* Also ensures that the hover version of this degrades gracefully.
+ * @return array
*/
protected function getModules() {
return array( 'mediawiki.page.gallery' );
@@ -98,6 +104,7 @@ class PackedImageGallery extends TraditionalImageGallery {
/**
* Do not support per-row on packed. It really doesn't work
* since the images have varying widths.
+ * @param int $num
*/
public function setPerRow( $num ) {
return;
diff --git a/includes/gallery/PackedOverlayImageGallery.php b/includes/gallery/PackedOverlayImageGallery.php
index bba06fcf..01360d05 100644
--- a/includes/gallery/PackedOverlayImageGallery.php
+++ b/includes/gallery/PackedOverlayImageGallery.php
@@ -22,12 +22,13 @@
*/
class PackedOverlayImageGallery extends PackedImageGallery {
-
/**
* Add the wrapper html around the thumb's caption
*
- * @param String $galleryText The caption
- * @param MediaTransformOutput|boolean $thumb The thumb this caption is for or false for bad image.
+ * @param string $galleryText The caption
+ * @param MediaTransformOutput|bool $thumb The thumb this caption is for
+ * or false for bad image.
+ * @return string
*/
protected function wrapGalleryText( $galleryText, $thumb ) {
@@ -37,17 +38,19 @@ class PackedOverlayImageGallery extends PackedImageGallery {
return '';
}
- # ATTENTION: The newline after <div class="gallerytext"> is needed to accommodate htmltidy which
- # in version 4.8.6 generated crackpot html in its absence, see:
- # http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar
+ # ATTENTION: The newline after <div class="gallerytext"> is needed to
+ # accommodate htmltidy which in version 4.8.6 generated crackpot HTML
+ # in its absence, see: http://bugzilla.wikimedia.org/show_bug.cgi?id=1765
+ # -Ævar
$thumbWidth = $this->getGBWidth( $thumb ) - $this->getThumbPadding() - $this->getGBPadding();
$captionWidth = ceil( $thumbWidth - 20 );
$outerWrapper = '<div class="gallerytextwrapper" style="width: ' . $captionWidth . 'px">';
+
return "\n\t\t\t" . $outerWrapper . '<div class="gallerytext">' . "\n"
- . $galleryText
- . "\n\t\t\t</div>";
+ . $galleryText
+ . "\n\t\t\t</div></div>";
}
}
@@ -57,4 +60,5 @@ class PackedOverlayImageGallery extends PackedImageGallery {
* falls back to PackedHoverGallery. Degrades gracefully for
* screen readers.
*/
-class PackedHoverImageGallery extends PackedOverlayImageGallery { }
+class PackedHoverImageGallery extends PackedOverlayImageGallery {
+}
diff --git a/includes/gallery/TraditionalImageGallery.php b/includes/gallery/TraditionalImageGallery.php
index 1f60fa69..37f2221f 100644
--- a/includes/gallery/TraditionalImageGallery.php
+++ b/includes/gallery/TraditionalImageGallery.php
@@ -21,8 +21,6 @@
*/
class TraditionalImageGallery extends ImageGalleryBase {
-
-
/**
* Return a HTML representation of the image gallery
*
@@ -38,8 +36,10 @@ class TraditionalImageGallery extends ImageGalleryBase {
if ( $this->mPerRow > 0 ) {
$maxwidth = $this->mPerRow * ( $this->mWidths + $this->getAllPadding() );
$oldStyle = isset( $this->mAttribs['style'] ) ? $this->mAttribs['style'] : '';
- # _width is ignored by any sane browser. IE6 doesn't know max-width so it uses _width instead
- $this->mAttribs['style'] = "max-width: {$maxwidth}px;_width: {$maxwidth}px;" . $oldStyle;
+ # _width is ignored by any sane browser. IE6 doesn't know max-width
+ # so it uses _width instead
+ $this->mAttribs['style'] = "max-width: {$maxwidth}px;_width: {$maxwidth}px;" .
+ $oldStyle;
}
$attribs = Sanitizer::mergeAttributes(
@@ -60,6 +60,7 @@ class TraditionalImageGallery extends ImageGalleryBase {
$lang = $this->getRenderLang();
# Output each image...
foreach ( $this->mImages as $pair ) {
+ /** @var Title $nt */
$nt = $pair[0];
$text = $pair[1]; # "text" means "caption" here
$alt = $pair[2];
@@ -90,28 +91,31 @@ class TraditionalImageGallery extends ImageGalleryBase {
if ( !$img ) {
# We're dealing with a non-image, spit out the name and be done with it.
- $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: ' . ( $this->getThumbPadding() + $this->mHeights ) . 'px;">'
+ $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: '
+ . ( $this->getThumbPadding() + $this->mHeights ) . 'px;">'
. htmlspecialchars( $nt->getText() ) . '</div>';
if ( $this->mParser instanceof Parser ) {
$this->mParser->addTrackingCategory( 'broken-file-category' );
}
- } elseif ( $this->mHideBadImages && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() ) ) {
+ } elseif ( $this->mHideBadImages
+ && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() )
+ ) {
# The image is blacklisted, just show it as a text link.
- $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: ' . ( $this->getThumbPadding() + $this->mHeights ) . 'px;">' .
- Linker::link(
+ $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: ' .
+ ( $this->getThumbPadding() + $this->mHeights ) . 'px;">' .
+ Linker::linkKnown(
$nt,
- htmlspecialchars( $nt->getText() ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ htmlspecialchars( $nt->getText() )
) .
'</div>';
} elseif ( !( $thumb = $img->transform( $transformOptions ) ) ) {
# Error generating thumbnail.
- $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: ' . ( $this->getThumbPadding() + $this->mHeights ) . 'px;">'
+ $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: '
+ . ( $this->getThumbPadding() + $this->mHeights ) . 'px;">'
. htmlspecialchars( $img->getLastError() ) . '</div>';
} else {
+ /** @var MediaTransformOutput $thumb */
$vpad = $this->getVPad( $this->mHeights, $thumb->getHeight() );
$imageParameters = array(
@@ -120,7 +124,9 @@ class TraditionalImageGallery extends ImageGalleryBase {
'alt' => $alt,
'custom-url-link' => $link
);
- # In the absence of both alt text and caption, fall back on providing screen readers with the filename as alt text
+
+ // In the absence of both alt text and caption, fall back on
+ // providing screen readers with the filename as alt text
if ( $alt == '' && $text == '' ) {
$imageParameters['alt'] = $nt->getText();
}
@@ -128,22 +134,27 @@ class TraditionalImageGallery extends ImageGalleryBase {
$this->adjustImageParameters( $thumb, $imageParameters );
# Set both fixed width and min-height.
- $thumbhtml = "\n\t\t\t" .
- '<div class="thumb" style="width: ' . $this->getThumbDivWidth( $thumb->getWidth() ) . 'px;">'
- # Auto-margin centering for block-level elements. Needed now that we have video
- # handlers since they may emit block-level elements as opposed to simple <img> tags.
- # ref http://css-discuss.incutio.com/?page=CenteringBlockElement
+ $thumbhtml = "\n\t\t\t"
+ . '<div class="thumb" style="width: '
+ . $this->getThumbDivWidth( $thumb->getWidth() ) . 'px;">'
+ # Auto-margin centering for block-level elements. Needed
+ # now that we have video handlers since they may emit block-
+ # level elements as opposed to simple <img> tags. ref
+ # http://css-discuss.incutio.com/?page=CenteringBlockElement
. '<div style="margin:' . $vpad . 'px auto;">'
. $thumb->toHtml( $imageParameters ) . '</div></div>';
// Call parser transform hook
- if ( $this->mParser && $img->getHandler() ) {
- $img->getHandler()->parserTransformHook( $this->mParser, $img );
+ /** @var MediaHandler $handler */
+ $handler = $img->getHandler();
+ if ( $this->mParser && $handler ) {
+ $handler->parserTransformHook( $this->mParser, $img );
}
}
- //TODO
- // $linkTarget = Title::newFromText( $wgContLang->getNsText( MWNamespace::getUser() ) . ":{$ut}" );
+ // @todo Code is incomplete.
+ // $linkTarget = Title::newFromText( $wgContLang->getNsText( MWNamespace::getUser() ) .
+ // ":{$ut}" );
// $ul = Linker::link( $linkTarget, $ut );
if ( $this->mShowBytes ) {
@@ -158,48 +169,46 @@ class TraditionalImageGallery extends ImageGalleryBase {
}
$textlink = $this->mShowFilename ?
- Linker::link(
+ Linker::linkKnown(
$nt,
- htmlspecialchars( $lang->truncate( $nt->getText(), $this->mCaptionLength ) ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ htmlspecialchars( $lang->truncate( $nt->getText(), $this->mCaptionLength ) )
) . "<br />\n" :
'';
-
$galleryText = $textlink . $text . $fileSize;
$galleryText = $this->wrapGalleryText( $galleryText, $thumb );
# Weird double wrapping (the extra div inside the li) needed due to FF2 bug
# Can be safely removed if FF2 falls completely out of existence
- $output .=
- "\n\t\t" . '<li class="gallerybox" style="width: ' . $this->getGBWidth( $thumb ) . 'px">'
- . '<div style="width: ' . $this->getGBWidth( $thumb ) . 'px">'
- . $thumbhtml
- . $galleryText
- . "\n\t\t</div></li>";
+ $output .= "\n\t\t" . '<li class="gallerybox" style="width: '
+ . $this->getGBWidth( $thumb ) . 'px">'
+ . '<div style="width: ' . $this->getGBWidth( $thumb ) . 'px">'
+ . $thumbhtml
+ . $galleryText
+ . "\n\t\t</div></li>";
}
$output .= "\n</ul>";
return $output;
}
-
/**
* Add the wrapper html around the thumb's caption
*
- * @param String $galleryText The caption
- * @param MediaTransformOutput|boolean $thumb The thumb this caption is for or false for bad image.
+ * @param string $galleryText The caption
+ * @param MediaTransformOutput|bool $thumb The thumb this caption is for
+ * or false for bad image.
+ * @return string
*/
protected function wrapGalleryText( $galleryText, $thumb ) {
- # ATTENTION: The newline after <div class="gallerytext"> is needed to accommodate htmltidy which
- # in version 4.8.6 generated crackpot html in its absence, see:
- # http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar
+ # ATTENTION: The newline after <div class="gallerytext"> is needed to
+ # accommodate htmltidy which in version 4.8.6 generated crackpot html in
+ # its absence, see: http://bugzilla.wikimedia.org/show_bug.cgi?id=1765
+ # -Ævar
return "\n\t\t\t" . '<div class="gallerytext">' . "\n"
- . $galleryText
- . "\n\t\t\t</div>";
+ . $galleryText
+ . "\n\t\t\t</div>";
}
/**
@@ -213,7 +222,6 @@ class TraditionalImageGallery extends ImageGalleryBase {
}
/**
- *
* @note GB stands for gallerybox (as in the <li class="gallerybox"> element)
*
* @return int
@@ -236,7 +244,7 @@ class TraditionalImageGallery extends ImageGalleryBase {
/**
* Get total padding.
*
- * @return int How many pixels of whitespace surround the thumbnail.
+ * @return int Number of pixels of whitespace surrounding the thumbnail.
*/
protected function getAllPadding() {
return $this->getThumbPadding() + $this->getGBPadding() + $this->getGBBorders();
@@ -249,7 +257,7 @@ class TraditionalImageGallery extends ImageGalleryBase {
*
* @param int $boxHeight How high we want the box to be.
* @param int $thumbHeight How high the thumbnail is.
- * @return int How many vertical padding to add on each side.
+ * @return int Vertical padding to add on each side.
*/
protected function getVPad( $boxHeight, $thumbHeight ) {
return ( $this->getThumbPadding() + $boxHeight - $thumbHeight ) / 2;
@@ -259,6 +267,7 @@ class TraditionalImageGallery extends ImageGalleryBase {
* Get the transform parameters for a thumbnail.
*
* @param File $img The file in question. May be false for invalid image
+ * @return array
*/
protected function getThumbParams( $img ) {
return array(
@@ -285,8 +294,8 @@ class TraditionalImageGallery extends ImageGalleryBase {
* plus padding on gallerybox.
*
* @note Important: parameter will be false if no thumb used.
- * @param Mixed $thumb MediaTransformObject object or false.
- * @return int width of gallerybox element
+ * @param MediaTransformOutput|bool $thumb MediaTransformObject object or false.
+ * @return int Width of gallerybox element
*/
protected function getGBWidth( $thumb ) {
return $this->mWidths + $this->getThumbPadding() + $this->getGBPadding();
@@ -297,7 +306,7 @@ class TraditionalImageGallery extends ImageGalleryBase {
*
* Primarily intended for subclasses.
*
- * @return Array modules to include
+ * @return array Modules to include
*/
protected function getModules() {
return array();
@@ -308,9 +317,10 @@ class TraditionalImageGallery extends ImageGalleryBase {
*
* Used by a subclass to insert extra high resolution images.
* @param MediaTransformOutput $thumb The thumbnail
- * @param Array $imageParameters Array of options
+ * @param array $imageParameters Array of options
*/
- protected function adjustImageParameters( $thumb, &$imageParameters ) { }
+ protected function adjustImageParameters( $thumb, &$imageParameters ) {
+ }
}
/**
@@ -318,7 +328,7 @@ class TraditionalImageGallery extends ImageGalleryBase {
* if called the old way, for extensions that may expect traditional
* mode.
*
- * @deprecated 1.22 Use ImageGalleryBase::factory instead.
+ * @deprecated since 1.22 Use ImageGalleryBase::factory instead.
*/
class ImageGallery extends TraditionalImageGallery {
function __construct( $mode = 'traditional' ) {
diff --git a/includes/htmlform/HTMLApiField.php b/includes/htmlform/HTMLApiField.php
new file mode 100644
index 00000000..f988e622
--- /dev/null
+++ b/includes/htmlform/HTMLApiField.php
@@ -0,0 +1,19 @@
+<?php
+
+class HTMLApiField extends HTMLFormField {
+ public function getTableRow( $value ) {
+ return '';
+ }
+
+ public function getDiv( $value ) {
+ return $this->getTableRow( $value );
+ }
+
+ public function getRaw( $value ) {
+ return $this->getTableRow( $value );
+ }
+
+ public function getInputHTML( $value ) {
+ return '';
+ }
+}
diff --git a/includes/htmlform/HTMLAutoCompleteSelectField.php b/includes/htmlform/HTMLAutoCompleteSelectField.php
new file mode 100644
index 00000000..49053628
--- /dev/null
+++ b/includes/htmlform/HTMLAutoCompleteSelectField.php
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * Text field for selecting a value from a large list of possible values, with
+ * auto-completion and optionally with a select dropdown for selecting common
+ * options.
+ *
+ * If one of 'options-messages', 'options', or 'options-message' is provided
+ * and non-empty, the select dropdown will be shown. An 'other' key will be
+ * appended using message 'htmlform-selectorother-other' if not already
+ * present.
+ *
+ * Besides the parameters recognized by HTMLTextField, the following are
+ * recognized:
+ * options-messages - As for HTMLSelectField
+ * options - As for HTMLSelectField
+ * options-message - As for HTMLSelectField
+ * autocomplete - Associative array mapping display text to values.
+ * autocomplete-messages - Like autocomplete, but keys are message names.
+ * require-match - Boolean, if true the value must be in the options or the
+ * autocomplete.
+ * other-message - Message to use instead of htmlform-selectorother-other for
+ * the 'other' message.
+ * other - Raw text to use for the 'other' message
+ *
+ */
+class HTMLAutoCompleteSelectField extends HTMLTextField {
+ protected $autocomplete = array();
+
+ function __construct( $params ) {
+ $params += array(
+ 'require-match' => false,
+ );
+
+ parent::__construct( $params );
+
+ if ( array_key_exists( 'autocomplete-messages', $this->mParams ) ) {
+ foreach ( $this->mParams['autocomplete-messages'] as $key => $value ) {
+ $key = $this->msg( $key )->plain();
+ $this->autocomplete[$key] = strval( $value );
+ }
+ } elseif ( array_key_exists( 'autocomplete', $this->mParams ) ) {
+ foreach ( $this->mParams['autocomplete'] as $key => $value ) {
+ $this->autocomplete[$key] = strval( $value );
+ }
+ }
+ if ( !is_array( $this->autocomplete ) || !$this->autocomplete ) {
+ throw new MWException( 'HTMLAutoCompleteSelectField called without any autocompletions' );
+ }
+
+ $this->getOptions();
+ if ( $this->mOptions && !in_array( 'other', $this->mOptions, true ) ) {
+ if ( isset( $params['other-message'] ) ) {
+ $msg = wfMessage( $params['other-message'] )->text();
+ } elseif ( isset( $params['other'] ) ) {
+ $msg = $params['other'];
+ } else {
+ $msg = wfMessage( 'htmlform-selectorother-other' )->text();
+ }
+ $this->mOptions[$msg] = 'other';
+ }
+ }
+
+ function loadDataFromRequest( $request ) {
+ if ( $request->getCheck( $this->mName ) ) {
+ $val = $request->getText( $this->mName . '-select', 'other' );
+
+ if ( $val === 'other' ) {
+ $val = $request->getText( $this->mName );
+ if ( isset( $this->autocomplete[$val] ) ) {
+ $val = $this->autocomplete[$val];
+ }
+ }
+
+ return $val;
+ } else {
+ return $this->getDefault();
+ }
+ }
+
+ function validate( $value, $alldata ) {
+ $p = parent::validate( $value, $alldata );
+
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+
+ if ( in_array( strval( $value ), $validOptions, true ) ) {
+ return true;
+ } elseif ( in_array( strval( $value ), $this->autocomplete, true ) ) {
+ return true;
+ } elseif ( $this->mParams['require-match'] ) {
+ return $this->msg( 'htmlform-select-badoption' )->parse();
+ }
+
+ return true;
+ }
+
+ function getAttributes( array $list ) {
+ $attribs = array(
+ 'type' => 'text',
+ 'data-autocomplete' => FormatJson::encode( array_keys( $this->autocomplete ) ),
+ ) + parent::getAttributes( $list );
+
+ if ( $this->getOptions() ) {
+ $attribs['data-hide-if'] = FormatJson::encode(
+ array( '!==', $this->mName . '-select', 'other' )
+ );
+ }
+
+ return $attribs;
+ }
+
+ function getInputHTML( $value ) {
+ $oldClass = $this->mClass;
+ $this->mClass = (array)$this->mClass;
+
+ $valInSelect = false;
+ $ret = '';
+
+ if ( $this->getOptions() ) {
+ if ( $value !== false ) {
+ $value = strval( $value );
+ $valInSelect = in_array(
+ $value, HTMLFormField::flattenOptions( $this->getOptions() ), true
+ );
+ }
+
+ $selected = $valInSelect ? $value : 'other';
+ $select = new XmlSelect( $this->mName . '-select', $this->mID . '-select', $selected );
+ $select->addOptions( $this->getOptions() );
+ $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
+
+ if ( !empty( $this->mParams['disabled'] ) ) {
+ $select->setAttribute( 'disabled', 'disabled' );
+ }
+
+ if ( isset( $this->mParams['tabindex'] ) ) {
+ $select->setAttribute( 'tabindex', $this->mParams['tabindex'] );
+ }
+
+ $ret = $select->getHTML() . "<br />\n";
+
+ $this->mClass[] = 'mw-htmlform-hide-if';
+ }
+
+ if ( $valInSelect ) {
+ $value = '';
+ } else {
+ $key = array_search( strval( $value ), $this->autocomplete, true );
+ if ( $key !== false ) {
+ $value = $key;
+ }
+ }
+
+ $this->mClass[] = 'mw-htmlform-autocomplete';
+ $ret .= parent::getInputHTML( $valInSelect ? '' : $value );
+ $this->mClass = $oldClass;
+
+ return $ret;
+ }
+
+}
diff --git a/includes/htmlform/HTMLButtonField.php b/includes/htmlform/HTMLButtonField.php
new file mode 100644
index 00000000..09c0ad97
--- /dev/null
+++ b/includes/htmlform/HTMLButtonField.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Adds a generic button inline to the form. Does not do anything, you must add
+ * click handling code in JavaScript. Use a HTMLSubmitField if you merely
+ * wish to add a submit button to a form.
+ *
+ * @since 1.22
+ */
+class HTMLButtonField extends HTMLFormField {
+ protected $buttonType = 'button';
+
+ public function __construct( $info ) {
+ $info['nodata'] = true;
+ parent::__construct( $info );
+ }
+
+ public function getInputHTML( $value ) {
+ $attr = array(
+ 'class' => 'mw-htmlform-submit ' . $this->mClass,
+ 'id' => $this->mID,
+ ) + $this->getAttributes( array( 'disabled', 'tabindex' ) );
+
+ return Html::input( $this->mName, $value, $this->buttonType, $attr );
+ }
+
+ protected function needsLabel() {
+ return false;
+ }
+
+ /**
+ * Button cannot be invalid
+ *
+ * @param string $value
+ * @param array $alldata
+ *
+ * @return bool
+ */
+ public function validate( $value, $alldata ) {
+ return true;
+ }
+}
diff --git a/includes/htmlform/HTMLCheckField.php b/includes/htmlform/HTMLCheckField.php
new file mode 100644
index 00000000..5f70362a
--- /dev/null
+++ b/includes/htmlform/HTMLCheckField.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * A checkbox field
+ */
+class HTMLCheckField extends HTMLFormField {
+ function getInputHTML( $value ) {
+ global $wgUseMediaWikiUIEverywhere;
+
+ if ( !empty( $this->mParams['invert'] ) ) {
+ $value = !$value;
+ }
+
+ $attr = $this->getTooltipAndAccessKey();
+ $attr['id'] = $this->mID;
+
+ $attr += $this->getAttributes( array( 'disabled', 'tabindex' ) );
+
+ if ( $this->mClass !== '' ) {
+ $attr['class'] = $this->mClass;
+ }
+
+ if ( $this->mParent->isVForm() ) {
+ // Nest checkbox inside label.
+ return Html::rawElement( 'label',
+ array(
+ 'class' => 'mw-ui-checkbox-label'
+ ),
+ Xml::check( $this->mName, $value, $attr ) . $this->mLabel );
+ } else {
+ $chkLabel = Xml::check( $this->mName, $value, $attr )
+ . '&#160;'
+ . Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
+
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ $chkLabel = Html::rawElement(
+ 'div',
+ array( 'class' => 'mw-ui-checkbox' ),
+ $chkLabel
+ );
+ }
+
+ return $chkLabel;
+ }
+ }
+
+ /**
+ * For a checkbox, the label goes on the right hand side, and is
+ * added in getInputHTML(), rather than HTMLFormField::getRow()
+ * @return string
+ */
+ function getLabel() {
+ return '&#160;';
+ }
+
+ /**
+ * checkboxes don't need a label.
+ * @return bool
+ */
+ protected function needsLabel() {
+ return false;
+ }
+
+ /**
+ * @param WebRequest $request
+ *
+ * @return string
+ */
+ function loadDataFromRequest( $request ) {
+ $invert = false;
+ if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
+ $invert = true;
+ }
+
+ // GetCheck won't work like we want for checks.
+ // Fetch the value in either one of the two following case:
+ // - we have a valid token (form got posted or GET forged by the user)
+ // - checkbox name has a value (false or true), ie is not null
+ if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName ) !== null ) {
+ // XOR has the following truth table, which is what we want
+ // INVERT VALUE | OUTPUT
+ // true true | false
+ // false true | true
+ // false false | false
+ // true false | true
+ return $request->getBool( $this->mName ) xor $invert;
+ } else {
+ return $this->getDefault();
+ }
+ }
+}
diff --git a/includes/htmlform/HTMLCheckMatrix.php b/includes/htmlform/HTMLCheckMatrix.php
new file mode 100644
index 00000000..6c538fdd
--- /dev/null
+++ b/includes/htmlform/HTMLCheckMatrix.php
@@ -0,0 +1,250 @@
+<?php
+
+/**
+ * A checkbox matrix
+ * Operates similarly to HTMLMultiSelectField, but instead of using an array of
+ * options, uses an array of rows and an array of columns to dynamically
+ * construct a matrix of options. The tags used to identify a particular cell
+ * are of the form "columnName-rowName"
+ *
+ * Options:
+ * - columns
+ * - Required list of columns in the matrix.
+ * - rows
+ * - Required list of rows in the matrix.
+ * - force-options-on
+ * - Accepts array of column-row tags to be displayed as enabled but unavailable to change
+ * - force-options-off
+ * - Accepts array of column-row tags to be displayed as disabled but unavailable to change.
+ * - tooltips
+ * - Optional array mapping row label to tooltip content
+ * - tooltip-class
+ * - Optional CSS class used on tooltip container span. Defaults to mw-icon-question.
+ */
+class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
+ static private $requiredParams = array(
+ // Required by underlying HTMLFormField
+ 'fieldname',
+ // Required by HTMLCheckMatrix
+ 'rows',
+ 'columns'
+ );
+
+ public function __construct( $params ) {
+ $missing = array_diff( self::$requiredParams, array_keys( $params ) );
+ if ( $missing ) {
+ throw new HTMLFormFieldRequiredOptionsException( $this, $missing );
+ }
+ parent::__construct( $params );
+ }
+
+ function validate( $value, $alldata ) {
+ $rows = $this->mParams['rows'];
+ $columns = $this->mParams['columns'];
+
+ // Make sure user-defined validation callback is run
+ $p = parent::validate( $value, $alldata );
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ // Make sure submitted value is an array
+ if ( !is_array( $value ) ) {
+ return false;
+ }
+
+ // If all options are valid, array_intersect of the valid options
+ // and the provided options will return the provided options.
+ $validOptions = array();
+ foreach ( $rows as $rowTag ) {
+ foreach ( $columns as $columnTag ) {
+ $validOptions[] = $columnTag . '-' . $rowTag;
+ }
+ }
+ $validValues = array_intersect( $value, $validOptions );
+ if ( count( $validValues ) == count( $value ) ) {
+ return true;
+ } else {
+ return $this->msg( 'htmlform-select-badoption' )->parse();
+ }
+ }
+
+ /**
+ * Build a table containing a matrix of checkbox options.
+ * The value of each option is a combination of the row tag and column tag.
+ * mParams['rows'] is an array with row labels as keys and row tags as values.
+ * mParams['columns'] is an array with column labels as keys and column tags as values.
+ *
+ * @param array $value Array of the options that should be checked
+ *
+ * @return string
+ */
+ function getInputHTML( $value ) {
+ $html = '';
+ $tableContents = '';
+ $rows = $this->mParams['rows'];
+ $columns = $this->mParams['columns'];
+
+ $attribs = $this->getAttributes( array( 'disabled', 'tabindex' ) );
+
+ // Build the column headers
+ $headerContents = Html::rawElement( 'td', array(), '&#160;' );
+ foreach ( $columns as $columnLabel => $columnTag ) {
+ $headerContents .= Html::rawElement( 'td', array(), $columnLabel );
+ }
+ $tableContents .= Html::rawElement( 'tr', array(), "\n$headerContents\n" );
+
+ $tooltipClass = 'mw-icon-question';
+ if ( isset( $this->mParams['tooltip-class'] ) ) {
+ $tooltipClass = $this->mParams['tooltip-class'];
+ }
+
+ // Build the options matrix
+ foreach ( $rows as $rowLabel => $rowTag ) {
+ // Append tooltip if configured
+ if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) {
+ $tooltipAttribs = array(
+ 'class' => "mw-htmlform-tooltip $tooltipClass",
+ 'title' => $this->mParams['tooltips'][$rowLabel],
+ );
+ $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' );
+ }
+ $rowContents = Html::rawElement( 'td', array(), $rowLabel );
+ foreach ( $columns as $columnTag ) {
+ $thisTag = "$columnTag-$rowTag";
+ // Construct the checkbox
+ $thisId = "{$this->mID}-$thisTag";
+ $thisAttribs = array(
+ 'id' => $thisId,
+ 'value' => $thisTag,
+ );
+ $checked = in_array( $thisTag, (array)$value, true );
+ if ( $this->isTagForcedOff( $thisTag ) ) {
+ $checked = false;
+ $thisAttribs['disabled'] = 1;
+ } elseif ( $this->isTagForcedOn( $thisTag ) ) {
+ $checked = true;
+ $thisAttribs['disabled'] = 1;
+ }
+ $chkBox = Xml::check( "{$this->mName}[]", $checked, $attribs + $thisAttribs );
+ if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $chkBox = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
+ $chkBox .
+ Html::element( 'label', array( 'for' => $thisId ) ) .
+ Html::closeElement( 'div' );
+ }
+ $rowContents .= Html::rawElement(
+ 'td',
+ array(),
+ $chkBox
+ );
+ }
+ $tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" );
+ }
+
+ // Put it all in a table
+ $html .= Html::rawElement( 'table',
+ array( 'class' => 'mw-htmlform-matrix' ),
+ Html::rawElement( 'tbody', array(), "\n$tableContents\n" ) ) . "\n";
+
+ return $html;
+ }
+
+ protected function isTagForcedOff( $tag ) {
+ return isset( $this->mParams['force-options-off'] )
+ && in_array( $tag, $this->mParams['force-options-off'] );
+ }
+
+ protected function isTagForcedOn( $tag ) {
+ return isset( $this->mParams['force-options-on'] )
+ && in_array( $tag, $this->mParams['force-options-on'] );
+ }
+
+ /**
+ * Get the complete table row for the input, including help text,
+ * labels, and whatever.
+ * We override this function since the label should always be on a separate
+ * line above the options in the case of a checkbox matrix, i.e. it's always
+ * a "vertical-label".
+ *
+ * @param string $value The value to set the input to
+ *
+ * @return string Complete HTML table row
+ */
+ function getTableRow( $value ) {
+ list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
+ $inputHtml = $this->getInputHTML( $value );
+ $fieldType = get_class( $this );
+ $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
+ $cellAttributes = array( 'colspan' => 2 );
+
+ $label = $this->getLabelHtml( $cellAttributes );
+
+ $field = Html::rawElement(
+ 'td',
+ array( 'class' => 'mw-input' ) + $cellAttributes,
+ $inputHtml . "\n$errors"
+ );
+
+ $html = Html::rawElement( 'tr', array( 'class' => 'mw-htmlform-vertical-label' ), $label );
+ $html .= Html::rawElement( 'tr',
+ array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
+ $field );
+
+ return $html . $helptext;
+ }
+
+ /**
+ * @param WebRequest $request
+ *
+ * @return array
+ */
+ function loadDataFromRequest( $request ) {
+ if ( $this->mParent->getMethod() == 'post' ) {
+ if ( $request->wasPosted() ) {
+ // Checkboxes are not added to the request arrays if they're not checked,
+ // so it's perfectly possible for there not to be an entry at all
+ return $request->getArray( $this->mName, array() );
+ } else {
+ // That's ok, the user has not yet submitted the form, so show the defaults
+ return $this->getDefault();
+ }
+ } else {
+ // This is the impossible case: if we look at $_GET and see no data for our
+ // field, is it because the user has not yet submitted the form, or that they
+ // have submitted it with all the options unchecked. We will have to assume the
+ // latter, which basically means that you can't specify 'positive' defaults
+ // for GET forms.
+ return $request->getArray( $this->mName, array() );
+ }
+ }
+
+ function getDefault() {
+ if ( isset( $this->mDefault ) ) {
+ return $this->mDefault;
+ } else {
+ return array();
+ }
+ }
+
+ function filterDataForSubmit( $data ) {
+ $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] );
+ $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] );
+ $res = array();
+ foreach ( $columns as $column ) {
+ foreach ( $rows as $row ) {
+ // Make sure option hasn't been forced
+ $thisTag = "$column-$row";
+ if ( $this->isTagForcedOff( $thisTag ) ) {
+ $res[$thisTag] = false;
+ } elseif ( $this->isTagForcedOn( $thisTag ) ) {
+ $res[$thisTag] = true;
+ } else {
+ $res[$thisTag] = in_array( $thisTag, $data );
+ }
+ }
+ }
+
+ return $res;
+ }
+}
diff --git a/includes/htmlform/HTMLEditTools.php b/includes/htmlform/HTMLEditTools.php
new file mode 100644
index 00000000..77924ef2
--- /dev/null
+++ b/includes/htmlform/HTMLEditTools.php
@@ -0,0 +1,51 @@
+<?php
+
+class HTMLEditTools extends HTMLFormField {
+ public function getInputHTML( $value ) {
+ return '';
+ }
+
+ public function getTableRow( $value ) {
+ $msg = $this->formatMsg();
+
+ return
+ '<tr><td></td><td class="mw-input">' .
+ '<div class="mw-editTools">' .
+ $msg->parseAsBlock() .
+ "</div></td></tr>\n";
+ }
+
+ /**
+ * @param string $value
+ * @return string
+ * @since 1.20
+ */
+ public function getDiv( $value ) {
+ $msg = $this->formatMsg();
+
+ return '<div class="mw-editTools">' . $msg->parseAsBlock() . '</div>';
+ }
+
+ /**
+ * @param string $value
+ * @return string
+ * @since 1.20
+ */
+ public function getRaw( $value ) {
+ return $this->getDiv( $value );
+ }
+
+ protected function formatMsg() {
+ if ( empty( $this->mParams['message'] ) ) {
+ $msg = $this->msg( 'edittools' );
+ } else {
+ $msg = $this->msg( $this->mParams['message'] );
+ if ( $msg->isDisabled() ) {
+ $msg = $this->msg( 'edittools' );
+ }
+ }
+ $msg->inContentLanguage();
+
+ return $msg;
+ }
+}
diff --git a/includes/htmlform/HTMLFloatField.php b/includes/htmlform/HTMLFloatField.php
new file mode 100644
index 00000000..3b38fbe8
--- /dev/null
+++ b/includes/htmlform/HTMLFloatField.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * A field that will contain a numeric value
+ */
+class HTMLFloatField extends HTMLTextField {
+ function getSize() {
+ return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 20;
+ }
+
+ function validate( $value, $alldata ) {
+ $p = parent::validate( $value, $alldata );
+
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ $value = trim( $value );
+
+ # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
+ # with the addition that a leading '+' sign is ok.
+ if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
+ return $this->msg( 'htmlform-float-invalid' )->parseAsBlock();
+ }
+
+ # The "int" part of these message names is rather confusing.
+ # They make equal sense for all numbers.
+ if ( isset( $this->mParams['min'] ) ) {
+ $min = $this->mParams['min'];
+
+ if ( $min > $value ) {
+ return $this->msg( 'htmlform-int-toolow', $min )->parseAsBlock();
+ }
+ }
+
+ if ( isset( $this->mParams['max'] ) ) {
+ $max = $this->mParams['max'];
+
+ if ( $max < $value ) {
+ return $this->msg( 'htmlform-int-toohigh', $max )->parseAsBlock();
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php
new file mode 100644
index 00000000..d582da3b
--- /dev/null
+++ b/includes/htmlform/HTMLForm.php
@@ -0,0 +1,1472 @@
+<?php
+
+/**
+ * HTML form generation and submission handling.
+ *
+ * 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
+ */
+
+/**
+ * Object handling generic submission, CSRF protection, layout and
+ * other logic for UI forms. in a reusable manner.
+ *
+ * In order to generate the form, the HTMLForm object takes an array
+ * structure detailing the form fields available. Each element of the
+ * array is a basic property-list, including the type of field, the
+ * label it is to be given in the form, callbacks for validation and
+ * 'filtering', and other pertinent information.
+ *
+ * Field types are implemented as subclasses of the generic HTMLFormField
+ * object, and typically implement at least getInputHTML, which generates
+ * the HTML for the input field to be placed in the table.
+ *
+ * You can find extensive documentation on the www.mediawiki.org wiki:
+ * - https://www.mediawiki.org/wiki/HTMLForm
+ * - https://www.mediawiki.org/wiki/HTMLForm/tutorial
+ *
+ * The constructor input is an associative array of $fieldname => $info,
+ * where $info is an Associative Array with any of the following:
+ *
+ * 'class' -- the subclass of HTMLFormField that will be used
+ * to create the object. *NOT* the CSS class!
+ * 'type' -- roughly translates into the <select> type attribute.
+ * if 'class' is not specified, this is used as a map
+ * through HTMLForm::$typeMappings to get the class name.
+ * 'default' -- default value when the form is displayed
+ * 'id' -- HTML id attribute
+ * 'cssclass' -- CSS class
+ * 'csshelpclass' -- CSS class used to style help text
+ * 'options' -- associative array mapping labels to values.
+ * Some field types support multi-level arrays.
+ * 'options-messages' -- associative array mapping message keys to values.
+ * Some field types support multi-level arrays.
+ * 'options-message' -- message key to be parsed to extract the list of
+ * options (like 'ipbreason-dropdown').
+ * 'label-message' -- message key for a message to use as the label.
+ * can be an array of msg key and then parameters to
+ * the message.
+ * 'label' -- alternatively, a raw text message. Overridden by
+ * label-message
+ * 'help' -- message text for a message to use as a help text.
+ * 'help-message' -- message key for a message to use as a help text.
+ * can be an array of msg key and then parameters to
+ * the message.
+ * Overwrites 'help-messages' and 'help'.
+ * 'help-messages' -- array of message key. As above, each item can
+ * be an array of msg key and then parameters.
+ * Overwrites 'help'.
+ * 'required' -- passed through to the object, indicating that it
+ * is a required field.
+ * 'size' -- the length of text fields
+ * 'filter-callback -- a function name to give you the chance to
+ * massage the inputted value before it's processed.
+ * @see HTMLForm::filter()
+ * 'validation-callback' -- a function name to give you the chance
+ * to impose extra validation on the field input.
+ * @see HTMLForm::validate()
+ * 'name' -- By default, the 'name' attribute of the input field
+ * is "wp{$fieldname}". If you want a different name
+ * (eg one without the "wp" prefix), specify it here and
+ * it will be used without modification.
+ *
+ * Since 1.20, you can chain mutators to ease the form generation:
+ * @par Example:
+ * @code
+ * $form = new HTMLForm( $someFields );
+ * $form->setMethod( 'get' )
+ * ->setWrapperLegendMsg( 'message-key' )
+ * ->prepareForm()
+ * ->displayForm( '' );
+ * @endcode
+ * Note that you will have prepareForm and displayForm at the end. Other
+ * methods call done after that would simply not be part of the form :(
+ *
+ * @todo Document 'section' / 'subsection' stuff
+ */
+class HTMLForm extends ContextSource {
+ // A mapping of 'type' inputs onto standard HTMLFormField subclasses
+ public static $typeMappings = array(
+ 'api' => 'HTMLApiField',
+ 'text' => 'HTMLTextField',
+ 'textarea' => 'HTMLTextAreaField',
+ 'select' => 'HTMLSelectField',
+ 'radio' => 'HTMLRadioField',
+ 'multiselect' => 'HTMLMultiSelectField',
+ 'limitselect' => 'HTMLSelectLimitField',
+ 'check' => 'HTMLCheckField',
+ 'toggle' => 'HTMLCheckField',
+ 'int' => 'HTMLIntField',
+ 'float' => 'HTMLFloatField',
+ 'info' => 'HTMLInfoField',
+ 'selectorother' => 'HTMLSelectOrOtherField',
+ 'selectandother' => 'HTMLSelectAndOtherField',
+ 'submit' => 'HTMLSubmitField',
+ 'hidden' => 'HTMLHiddenField',
+ 'edittools' => 'HTMLEditTools',
+ 'checkmatrix' => 'HTMLCheckMatrix',
+ 'cloner' => 'HTMLFormFieldCloner',
+ 'autocompleteselect' => 'HTMLAutoCompleteSelectField',
+ // HTMLTextField will output the correct type="" attribute automagically.
+ // There are about four zillion other HTML5 input types, like range, but
+ // we don't use those at the moment, so no point in adding all of them.
+ 'email' => 'HTMLTextField',
+ 'password' => 'HTMLTextField',
+ 'url' => 'HTMLTextField',
+ );
+
+ public $mFieldData;
+
+ protected $mMessagePrefix;
+
+ /** @var HTMLFormField[] */
+ protected $mFlatFields;
+
+ protected $mFieldTree;
+ protected $mShowReset = false;
+ protected $mShowSubmit = true;
+ protected $mSubmitModifierClass = 'mw-ui-constructive';
+
+ protected $mSubmitCallback;
+ protected $mValidationErrorMessage;
+
+ protected $mPre = '';
+ protected $mHeader = '';
+ protected $mFooter = '';
+ protected $mSectionHeaders = array();
+ protected $mSectionFooters = array();
+ protected $mPost = '';
+ protected $mId;
+ protected $mTableId = '';
+
+ protected $mSubmitID;
+ protected $mSubmitName;
+ protected $mSubmitText;
+ protected $mSubmitTooltip;
+
+ protected $mTitle;
+ protected $mMethod = 'post';
+ protected $mWasSubmitted = false;
+
+ /**
+ * Form action URL. false means we will use the URL to set Title
+ * @since 1.19
+ * @var bool|string
+ */
+ protected $mAction = false;
+
+ protected $mUseMultipart = false;
+ protected $mHiddenFields = array();
+ protected $mButtons = array();
+
+ protected $mWrapperLegend = false;
+
+ /**
+ * Salt for the edit token.
+ * @var string|array
+ */
+ protected $mTokenSalt = '';
+
+ /**
+ * If true, sections that contain both fields and subsections will
+ * render their subsections before their fields.
+ *
+ * Subclasses may set this to false to render subsections after fields
+ * instead.
+ */
+ protected $mSubSectionBeforeFields = true;
+
+ /**
+ * Format in which to display form. For viable options,
+ * @see $availableDisplayFormats
+ * @var string
+ */
+ protected $displayFormat = 'table';
+
+ /**
+ * Available formats in which to display the form
+ * @var array
+ */
+ protected $availableDisplayFormats = array(
+ 'table',
+ 'div',
+ 'raw',
+ 'vform',
+ );
+
+ /**
+ * Build a new HTMLForm from an array of field attributes
+ *
+ * @param array $descriptor Array of Field constructs, as described above
+ * @param IContextSource $context Available since 1.18, will become compulsory in 1.18.
+ * Obviates the need to call $form->setTitle()
+ * @param string $messagePrefix A prefix to go in front of default messages
+ */
+ public function __construct( $descriptor, /*IContextSource*/ $context = null,
+ $messagePrefix = ''
+ ) {
+ if ( $context instanceof IContextSource ) {
+ $this->setContext( $context );
+ $this->mTitle = false; // We don't need them to set a title
+ $this->mMessagePrefix = $messagePrefix;
+ } elseif ( is_null( $context ) && $messagePrefix !== '' ) {
+ $this->mMessagePrefix = $messagePrefix;
+ } elseif ( is_string( $context ) && $messagePrefix === '' ) {
+ // B/C since 1.18
+ // it's actually $messagePrefix
+ $this->mMessagePrefix = $context;
+ }
+
+ // Expand out into a tree.
+ $loadedDescriptor = array();
+ $this->mFlatFields = array();
+
+ foreach ( $descriptor as $fieldname => $info ) {
+ $section = isset( $info['section'] )
+ ? $info['section']
+ : '';
+
+ if ( isset( $info['type'] ) && $info['type'] == 'file' ) {
+ $this->mUseMultipart = true;
+ }
+
+ $field = self::loadInputFromParameters( $fieldname, $info );
+ // FIXME During field's construct, the parent form isn't available!
+ // could add a 'parent' name-value to $info, could add a third parameter.
+ $field->mParent = $this;
+
+ // vform gets too much space if empty labels generate HTML.
+ if ( $this->isVForm() ) {
+ $field->setShowEmptyLabel( false );
+ }
+
+ $setSection =& $loadedDescriptor;
+ if ( $section ) {
+ $sectionParts = explode( '/', $section );
+
+ while ( count( $sectionParts ) ) {
+ $newName = array_shift( $sectionParts );
+
+ if ( !isset( $setSection[$newName] ) ) {
+ $setSection[$newName] = array();
+ }
+
+ $setSection =& $setSection[$newName];
+ }
+ }
+
+ $setSection[$fieldname] = $field;
+ $this->mFlatFields[$fieldname] = $field;
+ }
+
+ $this->mFieldTree = $loadedDescriptor;
+ }
+
+ /**
+ * Set format in which to display the form
+ *
+ * @param string $format The name of the format to use, must be one of
+ * $this->availableDisplayFormats
+ *
+ * @throws MWException
+ * @since 1.20
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setDisplayFormat( $format ) {
+ if ( !in_array( $format, $this->availableDisplayFormats ) ) {
+ throw new MWException( 'Display format must be one of ' .
+ print_r( $this->availableDisplayFormats, true ) );
+ }
+ $this->displayFormat = $format;
+
+ return $this;
+ }
+
+ /**
+ * Getter for displayFormat
+ * @since 1.20
+ * @return string
+ */
+ public function getDisplayFormat() {
+ $format = $this->displayFormat;
+ if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $format === 'table' ) {
+ $format = 'div';
+ }
+ return $format;
+ }
+
+ /**
+ * Test if displayFormat is 'vform'
+ * @since 1.22
+ * @return bool
+ */
+ public function isVForm() {
+ return $this->displayFormat === 'vform';
+ }
+
+ /**
+ * Get the HTMLFormField subclass for this descriptor.
+ *
+ * The descriptor can be passed either 'class' which is the name of
+ * a HTMLFormField subclass, or a shorter 'type' which is an alias.
+ * This makes sure the 'class' is always set, and also is returned by
+ * this function for ease.
+ *
+ * @since 1.23
+ *
+ * @param string $fieldname Name of the field
+ * @param array $descriptor Input Descriptor, as described above
+ *
+ * @throws MWException
+ * @return string Name of a HTMLFormField subclass
+ */
+ public static function getClassFromDescriptor( $fieldname, &$descriptor ) {
+ if ( isset( $descriptor['class'] ) ) {
+ $class = $descriptor['class'];
+ } elseif ( isset( $descriptor['type'] ) ) {
+ $class = self::$typeMappings[$descriptor['type']];
+ $descriptor['class'] = $class;
+ } else {
+ $class = null;
+ }
+
+ if ( !$class ) {
+ throw new MWException( "Descriptor with no class for $fieldname: "
+ . print_r( $descriptor, true ) );
+ }
+
+ return $class;
+ }
+
+ /**
+ * Initialise a new Object for the field
+ *
+ * @param string $fieldname Name of the field
+ * @param array $descriptor Input Descriptor, as described above
+ *
+ * @throws MWException
+ * @return HTMLFormField Instance of a subclass of HTMLFormField
+ */
+ public static function loadInputFromParameters( $fieldname, $descriptor ) {
+ $class = self::getClassFromDescriptor( $fieldname, $descriptor );
+
+ $descriptor['fieldname'] = $fieldname;
+
+ # @todo This will throw a fatal error whenever someone try to use
+ # 'class' to feed a CSS class instead of 'cssclass'. Would be
+ # great to avoid the fatal error and show a nice error.
+ $obj = new $class( $descriptor );
+
+ return $obj;
+ }
+
+ /**
+ * Prepare form for submission.
+ *
+ * @attention When doing method chaining, that should be the very last
+ * method call before displayForm().
+ *
+ * @throws MWException
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function prepareForm() {
+ # Check if we have the info we need
+ if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
+ throw new MWException( "You must call setTitle() on an HTMLForm" );
+ }
+
+ # Load data from the request.
+ $this->loadData();
+
+ return $this;
+ }
+
+ /**
+ * Try submitting, with edit token check first
+ * @return Status|bool
+ */
+ function tryAuthorizedSubmit() {
+ $result = false;
+
+ $submit = false;
+ if ( $this->getMethod() != 'post' ) {
+ $submit = true; // no session check needed
+ } elseif ( $this->getRequest()->wasPosted() ) {
+ $editToken = $this->getRequest()->getVal( 'wpEditToken' );
+ if ( $this->getUser()->isLoggedIn() || $editToken != null ) {
+ // Session tokens for logged-out users have no security value.
+ // However, if the user gave one, check it in order to give a nice
+ // "session expired" error instead of "permission denied" or such.
+ $submit = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt );
+ } else {
+ $submit = true;
+ }
+ }
+
+ if ( $submit ) {
+ $this->mWasSubmitted = true;
+ $result = $this->trySubmit();
+ }
+
+ return $result;
+ }
+
+ /**
+ * The here's-one-I-made-earlier option: do the submission if
+ * posted, or display the form with or without funky validation
+ * errors
+ * @return bool|Status Whether submission was successful.
+ */
+ function show() {
+ $this->prepareForm();
+
+ $result = $this->tryAuthorizedSubmit();
+ if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
+ return $result;
+ }
+
+ $this->displayForm( $result );
+
+ return false;
+ }
+
+ /**
+ * Validate all the fields, and call the submission callback
+ * function if everything is kosher.
+ * @throws MWException
+ * @return bool|string|array|Status
+ * - Bool true or a good Status object indicates success,
+ * - Bool false indicates no submission was attempted,
+ * - Anything else indicates failure. The value may be a fatal Status
+ * object, an HTML string, or an array of arrays (message keys and
+ * params) or strings (message keys)
+ */
+ function trySubmit() {
+ $this->mWasSubmitted = true;
+
+ # Check for cancelled submission
+ foreach ( $this->mFlatFields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ }
+ if ( $field->cancelSubmit( $this->mFieldData[$fieldname], $this->mFieldData ) ) {
+ $this->mWasSubmitted = false;
+ return false;
+ }
+ }
+
+ # Check for validation
+ foreach ( $this->mFlatFields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ }
+ if ( $field->isHidden( $this->mFieldData ) ) {
+ continue;
+ }
+ if ( $field->validate(
+ $this->mFieldData[$fieldname],
+ $this->mFieldData )
+ !== true
+ ) {
+ return isset( $this->mValidationErrorMessage )
+ ? $this->mValidationErrorMessage
+ : array( 'htmlform-invalid-input' );
+ }
+ }
+
+ $callback = $this->mSubmitCallback;
+ if ( !is_callable( $callback ) ) {
+ throw new MWException( 'HTMLForm: no submit callback provided. Use ' .
+ 'setSubmitCallback() to set one.' );
+ }
+
+ $data = $this->filterDataForSubmit( $this->mFieldData );
+
+ $res = call_user_func( $callback, $data, $this );
+ if ( $res === false ) {
+ $this->mWasSubmitted = false;
+ }
+
+ return $res;
+ }
+
+ /**
+ * Test whether the form was considered to have been submitted or not, i.e.
+ * whether the last call to tryAuthorizedSubmit or trySubmit returned
+ * non-false.
+ *
+ * This will return false until HTMLForm::tryAuthorizedSubmit or
+ * HTMLForm::trySubmit is called.
+ *
+ * @since 1.23
+ * @return bool
+ */
+ function wasSubmitted() {
+ return $this->mWasSubmitted;
+ }
+
+ /**
+ * Set a callback to a function to do something with the form
+ * once it's been successfully validated.
+ *
+ * @param callable $cb The function will be passed the output from
+ * HTMLForm::filterDataForSubmit and this HTMLForm object, and must
+ * return as documented for HTMLForm::trySubmit
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setSubmitCallback( $cb ) {
+ $this->mSubmitCallback = $cb;
+
+ return $this;
+ }
+
+ /**
+ * Set a message to display on a validation error.
+ *
+ * @param string|array $msg String or Array of valid inputs to wfMessage()
+ * (so each entry can be either a String or Array)
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setValidationErrorMessage( $msg ) {
+ $this->mValidationErrorMessage = $msg;
+
+ return $this;
+ }
+
+ /**
+ * Set the introductory message, overwriting any existing message.
+ *
+ * @param string $msg Complete text of message to display
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setIntro( $msg ) {
+ $this->setPreText( $msg );
+
+ return $this;
+ }
+
+ /**
+ * Set the introductory message, overwriting any existing message.
+ * @since 1.19
+ *
+ * @param string $msg Complete text of message to display
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setPreText( $msg ) {
+ $this->mPre = $msg;
+
+ return $this;
+ }
+
+ /**
+ * Add introductory text.
+ *
+ * @param string $msg Complete text of message to display
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function addPreText( $msg ) {
+ $this->mPre .= $msg;
+
+ return $this;
+ }
+
+ /**
+ * Add header text, inside the form.
+ *
+ * @param string $msg Complete text of message to display
+ * @param string|null $section The section to add the header to
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function addHeaderText( $msg, $section = null ) {
+ if ( is_null( $section ) ) {
+ $this->mHeader .= $msg;
+ } else {
+ if ( !isset( $this->mSectionHeaders[$section] ) ) {
+ $this->mSectionHeaders[$section] = '';
+ }
+ $this->mSectionHeaders[$section] .= $msg;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set header text, inside the form.
+ * @since 1.19
+ *
+ * @param string $msg Complete text of message to display
+ * @param string|null $section The section to add the header to
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setHeaderText( $msg, $section = null ) {
+ if ( is_null( $section ) ) {
+ $this->mHeader = $msg;
+ } else {
+ $this->mSectionHeaders[$section] = $msg;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add footer text, inside the form.
+ *
+ * @param string $msg Complete text of message to display
+ * @param string|null $section The section to add the footer text to
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function addFooterText( $msg, $section = null ) {
+ if ( is_null( $section ) ) {
+ $this->mFooter .= $msg;
+ } else {
+ if ( !isset( $this->mSectionFooters[$section] ) ) {
+ $this->mSectionFooters[$section] = '';
+ }
+ $this->mSectionFooters[$section] .= $msg;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set footer text, inside the form.
+ * @since 1.19
+ *
+ * @param string $msg Complete text of message to display
+ * @param string|null $section The section to add the footer text to
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setFooterText( $msg, $section = null ) {
+ if ( is_null( $section ) ) {
+ $this->mFooter = $msg;
+ } else {
+ $this->mSectionFooters[$section] = $msg;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add text to the end of the display.
+ *
+ * @param string $msg Complete text of message to display
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function addPostText( $msg ) {
+ $this->mPost .= $msg;
+
+ return $this;
+ }
+
+ /**
+ * Set text at the end of the display.
+ *
+ * @param string $msg Complete text of message to display
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setPostText( $msg ) {
+ $this->mPost = $msg;
+
+ return $this;
+ }
+
+ /**
+ * Add a hidden field to the output
+ *
+ * @param string $name Field name. This will be used exactly as entered
+ * @param string $value Field value
+ * @param array $attribs
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function addHiddenField( $name, $value, $attribs = array() ) {
+ $attribs += array( 'name' => $name );
+ $this->mHiddenFields[] = array( $value, $attribs );
+
+ return $this;
+ }
+
+ /**
+ * Add an array of hidden fields to the output
+ *
+ * @since 1.22
+ *
+ * @param array $fields Associative array of fields to add;
+ * mapping names to their values
+ *
+ * @return HTMLForm $this for chaining calls
+ */
+ public function addHiddenFields( array $fields ) {
+ foreach ( $fields as $name => $value ) {
+ $this->mHiddenFields[] = array( $value, array( 'name' => $name ) );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a button to the form
+ *
+ * @param string $name Field name.
+ * @param string $value Field value
+ * @param string $id DOM id for the button (default: null)
+ * @param array $attribs
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function addButton( $name, $value, $id = null, $attribs = null ) {
+ $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
+
+ return $this;
+ }
+
+ /**
+ * Set the salt for the edit token.
+ *
+ * Only useful when the method is "post".
+ *
+ * @since 1.24
+ * @param string|array $salt Salt to use
+ * @return HTMLForm $this For chaining calls
+ */
+ public function setTokenSalt( $salt ) {
+ $this->mTokenSalt = $salt;
+
+ return $this;
+ }
+
+ /**
+ * Display the form (sending to the context's OutputPage object), with an
+ * appropriate error message or stack of messages, and any validation errors, etc.
+ *
+ * @attention You should call prepareForm() before calling this function.
+ * Moreover, when doing method chaining this should be the very last method
+ * call just after prepareForm().
+ *
+ * @param bool|string|array|Status $submitResult Output from HTMLForm::trySubmit()
+ *
+ * @return void Nothing, should be last call
+ */
+ function displayForm( $submitResult ) {
+ $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
+ }
+
+ /**
+ * Returns the raw HTML generated by the form
+ *
+ * @param bool|string|array|Status $submitResult Output from HTMLForm::trySubmit()
+ *
+ * @return string
+ */
+ function getHTML( $submitResult ) {
+ # For good measure (it is the default)
+ $this->getOutput()->preventClickjacking();
+ $this->getOutput()->addModules( 'mediawiki.htmlform' );
+ if ( $this->isVForm() ) {
+ $this->getOutput()->addModuleStyles( array(
+ 'mediawiki.ui',
+ 'mediawiki.ui.button',
+ ) );
+ // @todo Should vertical form set setWrapperLegend( false )
+ // to hide ugly fieldsets?
+ }
+
+ $html = ''
+ . $this->getErrors( $submitResult )
+ . $this->mHeader
+ . $this->getBody()
+ . $this->getHiddenFields()
+ . $this->getButtons()
+ . $this->mFooter;
+
+ $html = $this->wrapForm( $html );
+
+ return '' . $this->mPre . $html . $this->mPost;
+ }
+
+ /**
+ * Wrap the form innards in an actual "<form>" element
+ *
+ * @param string $html HTML contents to wrap.
+ *
+ * @return string Wrapped HTML.
+ */
+ function wrapForm( $html ) {
+
+ # Include a <fieldset> wrapper for style, if requested.
+ if ( $this->mWrapperLegend !== false ) {
+ $html = Xml::fieldset( $this->mWrapperLegend, $html );
+ }
+ # Use multipart/form-data
+ $encType = $this->mUseMultipart
+ ? 'multipart/form-data'
+ : 'application/x-www-form-urlencoded';
+ # Attributes
+ $attribs = array(
+ 'action' => $this->getAction(),
+ 'method' => $this->getMethod(),
+ 'class' => array( 'visualClear' ),
+ 'enctype' => $encType,
+ );
+ if ( !empty( $this->mId ) ) {
+ $attribs['id'] = $this->mId;
+ }
+
+ if ( $this->isVForm() ) {
+ array_push( $attribs['class'], 'mw-ui-vform', 'mw-ui-container' );
+ }
+
+ return Html::rawElement( 'form', $attribs, $html );
+ }
+
+ /**
+ * Get the hidden fields that should go inside the form.
+ * @return string HTML.
+ */
+ function getHiddenFields() {
+ $html = '';
+ if ( $this->getMethod() == 'post' ) {
+ $html .= Html::hidden(
+ 'wpEditToken',
+ $this->getUser()->getEditToken( $this->mTokenSalt ),
+ array( 'id' => 'wpEditToken' )
+ ) . "\n";
+ $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
+ }
+
+ $articlePath = $this->getConfig()->get( 'ArticlePath' );
+ if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() == 'get' ) {
+ $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
+ }
+
+ foreach ( $this->mHiddenFields as $data ) {
+ list( $value, $attribs ) = $data;
+ $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
+ }
+
+ return $html;
+ }
+
+ /**
+ * Get the submit and (potentially) reset buttons.
+ * @return string HTML.
+ */
+ function getButtons() {
+ $buttons = '';
+ $useMediaWikiUIEverywhere = $this->getConfig()->get( 'UseMediaWikiUIEverywhere' );
+
+ if ( $this->mShowSubmit ) {
+ $attribs = array();
+
+ if ( isset( $this->mSubmitID ) ) {
+ $attribs['id'] = $this->mSubmitID;
+ }
+
+ if ( isset( $this->mSubmitName ) ) {
+ $attribs['name'] = $this->mSubmitName;
+ }
+
+ if ( isset( $this->mSubmitTooltip ) ) {
+ $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
+ }
+
+ $attribs['class'] = array( 'mw-htmlform-submit' );
+
+ if ( $this->isVForm() || $useMediaWikiUIEverywhere ) {
+ array_push( $attribs['class'], 'mw-ui-button', $this->mSubmitModifierClass );
+ }
+
+ if ( $this->isVForm() ) {
+ // mw-ui-block is necessary because the buttons aren't necessarily in an
+ // immediate child div of the vform.
+ // @todo Let client specify if the primary submit button is progressive or destructive
+ array_push(
+ $attribs['class'],
+ 'mw-ui-big',
+ 'mw-ui-block'
+ );
+ }
+
+ $buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
+ }
+
+ if ( $this->mShowReset ) {
+ $buttons .= Html::element(
+ 'input',
+ array(
+ 'type' => 'reset',
+ 'value' => $this->msg( 'htmlform-reset' )->text()
+ )
+ ) . "\n";
+ }
+
+ foreach ( $this->mButtons as $button ) {
+ $attrs = array(
+ 'type' => 'submit',
+ 'name' => $button['name'],
+ 'value' => $button['value']
+ );
+
+ if ( $button['attribs'] ) {
+ $attrs += $button['attribs'];
+ }
+
+ if ( isset( $button['id'] ) ) {
+ $attrs['id'] = $button['id'];
+ }
+
+ if ( $this->isVForm() || $useMediaWikiUIEverywhere ) {
+ if ( isset( $attrs['class'] ) ) {
+ $attrs['class'] .= ' mw-ui-button';
+ } else {
+ $attrs['class'] = 'mw-ui-button';
+ }
+ if ( $this->isVForm() ) {
+ $attrs['class'] .= ' mw-ui-big mw-ui-block';
+ }
+ }
+
+ $buttons .= Html::element( 'input', $attrs ) . "\n";
+ }
+
+ $html = Html::rawElement( 'span',
+ array( 'class' => 'mw-htmlform-submit-buttons' ), "\n$buttons" ) . "\n";
+
+ // Buttons are top-level form elements in table and div layouts,
+ // but vform wants all elements inside divs to get spaced-out block
+ // styling.
+ if ( $this->mShowSubmit && $this->isVForm() ) {
+ $html = Html::rawElement( 'div', null, "\n$html" ) . "\n";
+ }
+
+ return $html;
+ }
+
+ /**
+ * Get the whole body of the form.
+ * @return string
+ */
+ function getBody() {
+ return $this->displaySection( $this->mFieldTree, $this->mTableId );
+ }
+
+ /**
+ * Format and display an error message stack.
+ *
+ * @param string|array|Status $errors
+ *
+ * @return string
+ */
+ function getErrors( $errors ) {
+ if ( $errors instanceof Status ) {
+ if ( $errors->isOK() ) {
+ $errorstr = '';
+ } else {
+ $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
+ }
+ } elseif ( is_array( $errors ) ) {
+ $errorstr = $this->formatErrors( $errors );
+ } else {
+ $errorstr = $errors;
+ }
+
+ return $errorstr
+ ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr )
+ : '';
+ }
+
+ /**
+ * Format a stack of error messages into a single HTML string
+ *
+ * @param array $errors Array of message keys/values
+ *
+ * @return string HTML, a "<ul>" list of errors
+ */
+ public static function formatErrors( $errors ) {
+ $errorstr = '';
+
+ foreach ( $errors as $error ) {
+ if ( is_array( $error ) ) {
+ $msg = array_shift( $error );
+ } else {
+ $msg = $error;
+ $error = array();
+ }
+
+ $errorstr .= Html::rawElement(
+ 'li',
+ array(),
+ wfMessage( $msg, $error )->parse()
+ );
+ }
+
+ $errorstr = Html::rawElement( 'ul', array(), $errorstr );
+
+ return $errorstr;
+ }
+
+ /**
+ * Set the text for the submit button
+ *
+ * @param string $t Plaintext
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setSubmitText( $t ) {
+ $this->mSubmitText = $t;
+
+ return $this;
+ }
+
+ /**
+ * Identify that the submit button in the form has a destructive action
+ *
+ */
+ public function setSubmitDestructive() {
+ $this->mSubmitModifierClass = 'mw-ui-destructive';
+ }
+
+ /**
+ * Set the text for the submit button to a message
+ * @since 1.19
+ *
+ * @param string|Message $msg Message key or Message object
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setSubmitTextMsg( $msg ) {
+ if ( !$msg instanceof Message ) {
+ $msg = $this->msg( $msg );
+ }
+ $this->setSubmitText( $msg->text() );
+
+ return $this;
+ }
+
+ /**
+ * Get the text for the submit button, either customised or a default.
+ * @return string
+ */
+ function getSubmitText() {
+ return $this->mSubmitText
+ ? $this->mSubmitText
+ : $this->msg( 'htmlform-submit' )->text();
+ }
+
+ /**
+ * @param string $name Submit button name
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setSubmitName( $name ) {
+ $this->mSubmitName = $name;
+
+ return $this;
+ }
+
+ /**
+ * @param string $name Tooltip for the submit button
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setSubmitTooltip( $name ) {
+ $this->mSubmitTooltip = $name;
+
+ return $this;
+ }
+
+ /**
+ * Set the id for the submit button.
+ *
+ * @param string $t
+ *
+ * @todo FIXME: Integrity of $t is *not* validated
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setSubmitID( $t ) {
+ $this->mSubmitID = $t;
+
+ return $this;
+ }
+
+ /**
+ * Stop a default submit button being shown for this form. This implies that an
+ * alternate submit method must be provided manually.
+ *
+ * @since 1.22
+ *
+ * @param bool $suppressSubmit Set to false to re-enable the button again
+ *
+ * @return HTMLForm $this for chaining calls
+ */
+ function suppressDefaultSubmit( $suppressSubmit = true ) {
+ $this->mShowSubmit = !$suppressSubmit;
+
+ return $this;
+ }
+
+ /**
+ * Set the id of the \<table\> or outermost \<div\> element.
+ *
+ * @since 1.22
+ *
+ * @param string $id New value of the id attribute, or "" to remove
+ *
+ * @return HTMLForm $this for chaining calls
+ */
+ public function setTableId( $id ) {
+ $this->mTableId = $id;
+
+ return $this;
+ }
+
+ /**
+ * @param string $id DOM id for the form
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setId( $id ) {
+ $this->mId = $id;
+
+ return $this;
+ }
+
+ /**
+ * Prompt the whole form to be wrapped in a "<fieldset>", with
+ * this text as its "<legend>" element.
+ *
+ * @param string|bool $legend HTML to go inside the "<legend>" element, or
+ * false for no <legend>
+ * Will be escaped
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setWrapperLegend( $legend ) {
+ $this->mWrapperLegend = $legend;
+
+ return $this;
+ }
+
+ /**
+ * Prompt the whole form to be wrapped in a "<fieldset>", with
+ * this message as its "<legend>" element.
+ * @since 1.19
+ *
+ * @param string|Message $msg Message key or Message object
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setWrapperLegendMsg( $msg ) {
+ if ( !$msg instanceof Message ) {
+ $msg = $this->msg( $msg );
+ }
+ $this->setWrapperLegend( $msg->text() );
+
+ return $this;
+ }
+
+ /**
+ * Set the prefix for various default messages
+ * @todo Currently only used for the "<fieldset>" legend on forms
+ * with multiple sections; should be used elsewhere?
+ *
+ * @param string $p
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setMessagePrefix( $p ) {
+ $this->mMessagePrefix = $p;
+
+ return $this;
+ }
+
+ /**
+ * Set the title for form submission
+ *
+ * @param Title $t Title of page the form is on/should be posted to
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function setTitle( $t ) {
+ $this->mTitle = $t;
+
+ return $this;
+ }
+
+ /**
+ * Get the title
+ * @return Title
+ */
+ function getTitle() {
+ return $this->mTitle === false
+ ? $this->getContext()->getTitle()
+ : $this->mTitle;
+ }
+
+ /**
+ * Set the method used to submit the form
+ *
+ * @param string $method
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setMethod( $method = 'post' ) {
+ $this->mMethod = $method;
+
+ return $this;
+ }
+
+ public function getMethod() {
+ return $this->mMethod;
+ }
+
+ /**
+ * @todo Document
+ *
+ * @param array[]|HTMLFormField[] $fields Array of fields (either arrays or
+ * objects).
+ * @param string $sectionName ID attribute of the "<table>" tag for this
+ * section, ignored if empty.
+ * @param string $fieldsetIDPrefix ID prefix for the "<fieldset>" tag of
+ * each subsection, ignored if empty.
+ * @param bool &$hasUserVisibleFields Whether the section had user-visible fields.
+ *
+ * @return string
+ */
+ public function displaySection( $fields,
+ $sectionName = '',
+ $fieldsetIDPrefix = '',
+ &$hasUserVisibleFields = false ) {
+ $displayFormat = $this->getDisplayFormat();
+
+ $html = '';
+ $subsectionHtml = '';
+ $hasLabel = false;
+
+ switch ( $displayFormat ) {
+ case 'table':
+ $getFieldHtmlMethod = 'getTableRow';
+ break;
+ case 'vform':
+ // Close enough to a div.
+ $getFieldHtmlMethod = 'getDiv';
+ break;
+ case 'div':
+ $getFieldHtmlMethod = 'getDiv';
+ break;
+ default:
+ $getFieldHtmlMethod = 'get' . ucfirst( $displayFormat );
+ }
+
+ foreach ( $fields as $key => $value ) {
+ if ( $value instanceof HTMLFormField ) {
+ $v = empty( $value->mParams['nodata'] )
+ ? $this->mFieldData[$key]
+ : $value->getDefault();
+ $html .= $value->$getFieldHtmlMethod( $v );
+
+ $labelValue = trim( $value->getLabel() );
+ if ( $labelValue != '&#160;' && $labelValue !== '' ) {
+ $hasLabel = true;
+ }
+
+ if ( get_class( $value ) !== 'HTMLHiddenField' &&
+ get_class( $value ) !== 'HTMLApiField'
+ ) {
+ $hasUserVisibleFields = true;
+ }
+ } elseif ( is_array( $value ) ) {
+ $subsectionHasVisibleFields = false;
+ $section =
+ $this->displaySection( $value,
+ "mw-htmlform-$key",
+ "$fieldsetIDPrefix$key-",
+ $subsectionHasVisibleFields );
+ $legend = null;
+
+ if ( $subsectionHasVisibleFields === true ) {
+ // Display the section with various niceties.
+ $hasUserVisibleFields = true;
+
+ $legend = $this->getLegend( $key );
+
+ if ( isset( $this->mSectionHeaders[$key] ) ) {
+ $section = $this->mSectionHeaders[$key] . $section;
+ }
+ if ( isset( $this->mSectionFooters[$key] ) ) {
+ $section .= $this->mSectionFooters[$key];
+ }
+
+ $attributes = array();
+ if ( $fieldsetIDPrefix ) {
+ $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
+ }
+ $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
+ } else {
+ // Just return the inputs, nothing fancy.
+ $subsectionHtml .= $section;
+ }
+ }
+ }
+
+ if ( $displayFormat !== 'raw' ) {
+ $classes = array();
+
+ if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
+ $classes[] = 'mw-htmlform-nolabel';
+ }
+
+ $attribs = array(
+ 'class' => implode( ' ', $classes ),
+ );
+
+ if ( $sectionName ) {
+ $attribs['id'] = Sanitizer::escapeId( $sectionName );
+ }
+
+ if ( $displayFormat === 'table' ) {
+ $html = Html::rawElement( 'table',
+ $attribs,
+ Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
+ } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) {
+ $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
+ }
+ }
+
+ if ( $this->mSubSectionBeforeFields ) {
+ return $subsectionHtml . "\n" . $html;
+ } else {
+ return $html . "\n" . $subsectionHtml;
+ }
+ }
+
+ /**
+ * Construct the form fields from the Descriptor array
+ */
+ function loadData() {
+ $fieldData = array();
+
+ foreach ( $this->mFlatFields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ } elseif ( !empty( $field->mParams['disabled'] ) ) {
+ $fieldData[$fieldname] = $field->getDefault();
+ } else {
+ $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() );
+ }
+ }
+
+ # Filter data.
+ foreach ( $fieldData as $name => &$value ) {
+ $field = $this->mFlatFields[$name];
+ $value = $field->filter( $value, $this->mFlatFields );
+ }
+
+ $this->mFieldData = $fieldData;
+ }
+
+ /**
+ * Stop a reset button being shown for this form
+ *
+ * @param bool $suppressReset Set to false to re-enable the button again
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ function suppressReset( $suppressReset = true ) {
+ $this->mShowReset = !$suppressReset;
+
+ return $this;
+ }
+
+ /**
+ * Overload this if you want to apply special filtration routines
+ * to the form as a whole, after it's submitted but before it's
+ * processed.
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ function filterDataForSubmit( $data ) {
+ return $data;
+ }
+
+ /**
+ * Get a string to go in the "<legend>" of a section fieldset.
+ * Override this if you want something more complicated.
+ *
+ * @param string $key
+ *
+ * @return string
+ */
+ public function getLegend( $key ) {
+ return $this->msg( "{$this->mMessagePrefix}-$key" )->text();
+ }
+
+ /**
+ * Set the value for the action attribute of the form.
+ * When set to false (which is the default state), the set title is used.
+ *
+ * @since 1.19
+ *
+ * @param string|bool $action
+ *
+ * @return HTMLForm $this for chaining calls (since 1.20)
+ */
+ public function setAction( $action ) {
+ $this->mAction = $action;
+
+ return $this;
+ }
+
+ /**
+ * Get the value for the action attribute of the form.
+ *
+ * @since 1.22
+ *
+ * @return string
+ */
+ public function getAction() {
+ // If an action is alredy provided, return it
+ if ( $this->mAction !== false ) {
+ return $this->mAction;
+ }
+
+ $articlePath = $this->getConfig()->get( 'ArticlePath' );
+ // Check whether we are in GET mode and the ArticlePath contains a "?"
+ // meaning that getLocalURL() would return something like "index.php?title=...".
+ // As browser remove the query string before submitting GET forms,
+ // it means that the title would be lost. In such case use wfScript() instead
+ // and put title in an hidden field (see getHiddenFields()).
+ if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
+ return wfScript();
+ }
+
+ return $this->getTitle()->getLocalURL();
+ }
+}
diff --git a/includes/htmlform/HTMLFormField.php b/includes/htmlform/HTMLFormField.php
new file mode 100644
index 00000000..4cf23942
--- /dev/null
+++ b/includes/htmlform/HTMLFormField.php
@@ -0,0 +1,893 @@
+<?php
+
+/**
+ * The parent class to generate form fields. Any field type should
+ * be a subclass of this.
+ */
+abstract class HTMLFormField {
+ public $mParams;
+
+ protected $mValidationCallback;
+ protected $mFilterCallback;
+ protected $mName;
+ protected $mLabel; # String label. Set on construction
+ protected $mID;
+ protected $mClass = '';
+ protected $mHelpClass = false;
+ protected $mDefault;
+ protected $mOptions = false;
+ protected $mOptionsLabelsNotFromMessage = false;
+ protected $mHideIf = null;
+
+ /**
+ * @var bool If true will generate an empty div element with no label
+ * @since 1.22
+ */
+ protected $mShowEmptyLabels = true;
+
+ /**
+ * @var HTMLForm
+ */
+ public $mParent;
+
+ /**
+ * This function must be implemented to return the HTML to generate
+ * the input object itself. It should not implement the surrounding
+ * table cells/rows, or labels/help messages.
+ *
+ * @param string $value The value to set the input to; eg a default
+ * text for a text input.
+ *
+ * @return string Valid HTML.
+ */
+ abstract function getInputHTML( $value );
+
+ /**
+ * Get a translated interface message
+ *
+ * This is a wrapper around $this->mParent->msg() if $this->mParent is set
+ * and wfMessage() otherwise.
+ *
+ * Parameters are the same as wfMessage().
+ *
+ * @return Message
+ */
+ function msg() {
+ $args = func_get_args();
+
+ if ( $this->mParent ) {
+ $callback = array( $this->mParent, 'msg' );
+ } else {
+ $callback = 'wfMessage';
+ }
+
+ return call_user_func_array( $callback, $args );
+ }
+
+
+ /**
+ * Fetch a field value from $alldata for the closest field matching a given
+ * name.
+ *
+ * This is complex because it needs to handle array fields like the user
+ * would expect. The general algorithm is to look for $name as a sibling
+ * of $this, then a sibling of $this's parent, and so on. Keeping in mind
+ * that $name itself might be referencing an array.
+ *
+ * @param array $alldata
+ * @param string $name
+ * @return string
+ */
+ protected function getNearestFieldByName( $alldata, $name ) {
+ $tmp = $this->mName;
+ $thisKeys = array();
+ while ( preg_match( '/^(.+)\[([^\]]+)\]$/', $tmp, $m ) ) {
+ array_unshift( $thisKeys, $m[2] );
+ $tmp = $m[1];
+ }
+ if ( substr( $tmp, 0, 2 ) == 'wp' &&
+ !isset( $alldata[$tmp] ) &&
+ isset( $alldata[substr( $tmp, 2 )] )
+ ) {
+ // Adjust for name mangling.
+ $tmp = substr( $tmp, 2 );
+ }
+ array_unshift( $thisKeys, $tmp );
+
+ $tmp = $name;
+ $nameKeys = array();
+ while ( preg_match( '/^(.+)\[([^\]]+)\]$/', $tmp, $m ) ) {
+ array_unshift( $nameKeys, $m[2] );
+ $tmp = $m[1];
+ }
+ array_unshift( $nameKeys, $tmp );
+
+ $testValue = '';
+ for ( $i = count( $thisKeys ) - 1; $i >= 0; $i-- ) {
+ $keys = array_merge( array_slice( $thisKeys, 0, $i ), $nameKeys );
+ $data = $alldata;
+ while ( $keys ) {
+ $key = array_shift( $keys );
+ if ( !is_array( $data ) || !isset( $data[$key] ) ) {
+ continue 2;
+ }
+ $data = $data[$key];
+ }
+ $testValue = (string)$data;
+ break;
+ }
+
+ return $testValue;
+ }
+
+ /**
+ * Helper function for isHidden to handle recursive data structures.
+ *
+ * @param array $alldata
+ * @param array $params
+ * @return bool
+ */
+ protected function isHiddenRecurse( array $alldata, array $params ) {
+ $origParams = $params;
+ $op = array_shift( $params );
+
+ try {
+ switch ( $op ) {
+ case 'AND':
+ foreach ( $params as $i => $p ) {
+ if ( !is_array( $p ) ) {
+ throw new MWException(
+ "Expected array, found " . gettype( $p ) . " at index $i"
+ );
+ }
+ if ( !$this->isHiddenRecurse( $alldata, $p ) ) {
+ return false;
+ }
+ }
+ return true;
+
+ case 'OR':
+ foreach ( $params as $p ) {
+ if ( !is_array( $p ) ) {
+ throw new MWException(
+ "Expected array, found " . gettype( $p ) . " at index $i"
+ );
+ }
+ if ( $this->isHiddenRecurse( $alldata, $p ) ) {
+ return true;
+ }
+ }
+ return false;
+
+ case 'NAND':
+ foreach ( $params as $i => $p ) {
+ if ( !is_array( $p ) ) {
+ throw new MWException(
+ "Expected array, found " . gettype( $p ) . " at index $i"
+ );
+ }
+ if ( !$this->isHiddenRecurse( $alldata, $p ) ) {
+ return true;
+ }
+ }
+ return false;
+
+ case 'NOR':
+ foreach ( $params as $p ) {
+ if ( !is_array( $p ) ) {
+ throw new MWException(
+ "Expected array, found " . gettype( $p ) . " at index $i"
+ );
+ }
+ if ( $this->isHiddenRecurse( $alldata, $p ) ) {
+ return false;
+ }
+ }
+ return true;
+
+ case 'NOT':
+ if ( count( $params ) !== 1 ) {
+ throw new MWException( "NOT takes exactly one parameter" );
+ }
+ $p = $params[0];
+ if ( !is_array( $p ) ) {
+ throw new MWException(
+ "Expected array, found " . gettype( $p ) . " at index 0"
+ );
+ }
+ return !$this->isHiddenRecurse( $alldata, $p );
+
+ case '===':
+ case '!==':
+ if ( count( $params ) !== 2 ) {
+ throw new MWException( "$op takes exactly two parameters" );
+ }
+ list( $field, $value ) = $params;
+ if ( !is_string( $field ) || !is_string( $value ) ) {
+ throw new MWException( "Parameters for $op must be strings" );
+ }
+ $testValue = $this->getNearestFieldByName( $alldata, $field );
+ switch ( $op ) {
+ case '===':
+ return ( $value === $testValue );
+ case '!==':
+ return ( $value !== $testValue );
+ }
+
+ default:
+ throw new MWException( "Unknown operation" );
+ }
+ } catch ( MWException $ex ) {
+ throw new MWException(
+ "Invalid hide-if specification for $this->mName: " .
+ $ex->getMessage() . " in " . var_export( $origParams, true ),
+ 0, $ex
+ );
+ }
+ }
+
+ /**
+ * Test whether this field is supposed to be hidden, based on the values of
+ * the other form fields.
+ *
+ * @since 1.23
+ * @param array $alldata The data collected from the form
+ * @return bool
+ */
+ function isHidden( $alldata ) {
+ if ( !$this->mHideIf ) {
+ return false;
+ }
+
+ return $this->isHiddenRecurse( $alldata, $this->mHideIf );
+ }
+
+ /**
+ * Override this function if the control can somehow trigger a form
+ * submission that shouldn't actually submit the HTMLForm.
+ *
+ * @since 1.23
+ * @param string|array $value The value the field was submitted with
+ * @param array $alldata The data collected from the form
+ *
+ * @return bool True to cancel the submission
+ */
+ function cancelSubmit( $value, $alldata ) {
+ return false;
+ }
+
+ /**
+ * Override this function to add specific validation checks on the
+ * field input. Don't forget to call parent::validate() to ensure
+ * that the user-defined callback mValidationCallback is still run
+ *
+ * @param string|array $value The value the field was submitted with
+ * @param array $alldata The data collected from the form
+ *
+ * @return bool|string True on success, or String error to display, or
+ * false to fail validation without displaying an error.
+ */
+ function validate( $value, $alldata ) {
+ if ( $this->isHidden( $alldata ) ) {
+ return true;
+ }
+
+ if ( isset( $this->mParams['required'] )
+ && $this->mParams['required'] !== false
+ && $value === ''
+ ) {
+ return $this->msg( 'htmlform-required' )->parse();
+ }
+
+ if ( isset( $this->mValidationCallback ) ) {
+ return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent );
+ }
+
+ return true;
+ }
+
+ function filter( $value, $alldata ) {
+ if ( isset( $this->mFilterCallback ) ) {
+ $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent );
+ }
+
+ return $value;
+ }
+
+ /**
+ * Should this field have a label, or is there no input element with the
+ * appropriate id for the label to point to?
+ *
+ * @return bool True to output a label, false to suppress
+ */
+ protected function needsLabel() {
+ return true;
+ }
+
+ /**
+ * Tell the field whether to generate a separate label element if its label
+ * is blank.
+ *
+ * @since 1.22
+ *
+ * @param bool $show Set to false to not generate a label.
+ * @return void
+ */
+ public function setShowEmptyLabel( $show ) {
+ $this->mShowEmptyLabels = $show;
+ }
+
+ /**
+ * Get the value that this input has been set to from a posted form,
+ * or the input's default value if it has not been set.
+ *
+ * @param WebRequest $request
+ * @return string The value
+ */
+ function loadDataFromRequest( $request ) {
+ if ( $request->getCheck( $this->mName ) ) {
+ return $request->getText( $this->mName );
+ } else {
+ return $this->getDefault();
+ }
+ }
+
+ /**
+ * Initialise the object
+ *
+ * @param array $params Associative Array. See HTMLForm doc for syntax.
+ *
+ * @since 1.22 The 'label' attribute no longer accepts raw HTML, use 'label-raw' instead
+ * @throws MWException
+ */
+ function __construct( $params ) {
+ $this->mParams = $params;
+
+ # Generate the label from a message, if possible
+ if ( isset( $params['label-message'] ) ) {
+ $msgInfo = $params['label-message'];
+
+ if ( is_array( $msgInfo ) ) {
+ $msg = array_shift( $msgInfo );
+ } else {
+ $msg = $msgInfo;
+ $msgInfo = array();
+ }
+
+ $this->mLabel = wfMessage( $msg, $msgInfo )->parse();
+ } elseif ( isset( $params['label'] ) ) {
+ if ( $params['label'] === '&#160;' ) {
+ // Apparently some things set &nbsp directly and in an odd format
+ $this->mLabel = '&#160;';
+ } else {
+ $this->mLabel = htmlspecialchars( $params['label'] );
+ }
+ } elseif ( isset( $params['label-raw'] ) ) {
+ $this->mLabel = $params['label-raw'];
+ }
+
+ $this->mName = "wp{$params['fieldname']}";
+ if ( isset( $params['name'] ) ) {
+ $this->mName = $params['name'];
+ }
+
+ $validName = Sanitizer::escapeId( $this->mName );
+ $validName = str_replace( array( '.5B', '.5D' ), array( '[', ']' ), $validName );
+ if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
+ throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
+ }
+
+ $this->mID = "mw-input-{$this->mName}";
+
+ if ( isset( $params['default'] ) ) {
+ $this->mDefault = $params['default'];
+ }
+
+ if ( isset( $params['id'] ) ) {
+ $id = $params['id'];
+ $validId = Sanitizer::escapeId( $id );
+
+ if ( $id != $validId ) {
+ throw new MWException( "Invalid id '$id' passed to " . __METHOD__ );
+ }
+
+ $this->mID = $id;
+ }
+
+ if ( isset( $params['cssclass'] ) ) {
+ $this->mClass = $params['cssclass'];
+ }
+
+ if ( isset( $params['csshelpclass'] ) ) {
+ $this->mHelpClass = $params['csshelpclass'];
+ }
+
+ if ( isset( $params['validation-callback'] ) ) {
+ $this->mValidationCallback = $params['validation-callback'];
+ }
+
+ if ( isset( $params['filter-callback'] ) ) {
+ $this->mFilterCallback = $params['filter-callback'];
+ }
+
+ if ( isset( $params['flatlist'] ) ) {
+ $this->mClass .= ' mw-htmlform-flatlist';
+ }
+
+ if ( isset( $params['hidelabel'] ) ) {
+ $this->mShowEmptyLabels = false;
+ }
+
+ if ( isset( $params['hide-if'] ) ) {
+ $this->mHideIf = $params['hide-if'];
+ }
+ }
+
+ /**
+ * Get the complete table row for the input, including help text,
+ * labels, and whatever.
+ *
+ * @param string $value The value to set the input to.
+ *
+ * @return string Complete HTML table row.
+ */
+ function getTableRow( $value ) {
+ list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
+ $inputHtml = $this->getInputHTML( $value );
+ $fieldType = get_class( $this );
+ $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
+ $cellAttributes = array();
+ $rowAttributes = array();
+ $rowClasses = '';
+
+ if ( !empty( $this->mParams['vertical-label'] ) ) {
+ $cellAttributes['colspan'] = 2;
+ $verticalLabel = true;
+ } else {
+ $verticalLabel = false;
+ }
+
+ $label = $this->getLabelHtml( $cellAttributes );
+
+ $field = Html::rawElement(
+ 'td',
+ array( 'class' => 'mw-input' ) + $cellAttributes,
+ $inputHtml . "\n$errors"
+ );
+
+ if ( $this->mHideIf ) {
+ $rowAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
+ $rowClasses .= ' mw-htmlform-hide-if';
+ }
+
+ if ( $verticalLabel ) {
+ $html = Html::rawElement( 'tr',
+ $rowAttributes + array( 'class' => "mw-htmlform-vertical-label $rowClasses" ), $label );
+ $html .= Html::rawElement( 'tr',
+ $rowAttributes + array(
+ 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
+ ),
+ $field );
+ } else {
+ $html =
+ Html::rawElement( 'tr',
+ $rowAttributes + array(
+ 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
+ ),
+ $label . $field );
+ }
+
+ return $html . $helptext;
+ }
+
+ /**
+ * Get the complete div for the input, including help text,
+ * labels, and whatever.
+ * @since 1.20
+ *
+ * @param string $value The value to set the input to.
+ *
+ * @return string Complete HTML table row.
+ */
+ public function getDiv( $value ) {
+ list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
+ $inputHtml = $this->getInputHTML( $value );
+ $fieldType = get_class( $this );
+ $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
+ $cellAttributes = array();
+ $label = $this->getLabelHtml( $cellAttributes );
+
+ $outerDivClass = array(
+ 'mw-input',
+ 'mw-htmlform-nolabel' => ( $label === '' )
+ );
+
+ $field = Html::rawElement(
+ 'div',
+ array( 'class' => $outerDivClass ) + $cellAttributes,
+ $inputHtml . "\n$errors"
+ );
+ $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass, $errorClass );
+ if ( $this->mParent->isVForm() ) {
+ $divCssClasses[] = 'mw-ui-vform-field';
+ }
+
+ $wrapperAttributes = array(
+ 'class' => $divCssClasses,
+ );
+ if ( $this->mHideIf ) {
+ $wrapperAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
+ $wrapperAttributes['class'][] = ' mw-htmlform-hide-if';
+ }
+ $html = Html::rawElement( 'div', $wrapperAttributes, $label . $field );
+ $html .= $helptext;
+
+ return $html;
+ }
+
+ /**
+ * Get the complete raw fields for the input, including help text,
+ * labels, and whatever.
+ * @since 1.20
+ *
+ * @param string $value The value to set the input to.
+ *
+ * @return string Complete HTML table row.
+ */
+ public function getRaw( $value ) {
+ list( $errors, ) = $this->getErrorsAndErrorClass( $value );
+ $inputHtml = $this->getInputHTML( $value );
+ $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
+ $cellAttributes = array();
+ $label = $this->getLabelHtml( $cellAttributes );
+
+ $html = "\n$errors";
+ $html .= $label;
+ $html .= $inputHtml;
+ $html .= $helptext;
+
+ return $html;
+ }
+
+ /**
+ * Generate help text HTML in table format
+ * @since 1.20
+ *
+ * @param string|null $helptext
+ * @return string
+ */
+ public function getHelpTextHtmlTable( $helptext ) {
+ if ( is_null( $helptext ) ) {
+ return '';
+ }
+
+ $rowAttributes = array();
+ if ( $this->mHideIf ) {
+ $rowAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
+ $rowAttributes['class'] = 'mw-htmlform-hide-if';
+ }
+
+ $tdClasses = array( 'htmlform-tip' );
+ if ( $this->mHelpClass !== false ) {
+ $tdClasses[] = $this->mHelpClass;
+ }
+ $row = Html::rawElement( 'td', array( 'colspan' => 2, 'class' => $tdClasses ), $helptext );
+ $row = Html::rawElement( 'tr', $rowAttributes, $row );
+
+ return $row;
+ }
+
+ /**
+ * Generate help text HTML in div format
+ * @since 1.20
+ *
+ * @param string|null $helptext
+ *
+ * @return string
+ */
+ public function getHelpTextHtmlDiv( $helptext ) {
+ if ( is_null( $helptext ) ) {
+ return '';
+ }
+
+ $wrapperAttributes = array(
+ 'class' => 'htmlform-tip',
+ );
+ if ( $this->mHelpClass !== false ) {
+ $wrapperAttributes['class'] .= " {$this->mHelpClass}";
+ }
+ if ( $this->mHideIf ) {
+ $wrapperAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
+ $wrapperAttributes['class'] .= ' mw-htmlform-hide-if';
+ }
+ $div = Html::rawElement( 'div', $wrapperAttributes, $helptext );
+
+ return $div;
+ }
+
+ /**
+ * Generate help text HTML formatted for raw output
+ * @since 1.20
+ *
+ * @param string|null $helptext
+ * @return string
+ */
+ public function getHelpTextHtmlRaw( $helptext ) {
+ return $this->getHelpTextHtmlDiv( $helptext );
+ }
+
+ /**
+ * Determine the help text to display
+ * @since 1.20
+ * @return string
+ */
+ public function getHelpText() {
+ $helptext = null;
+
+ if ( isset( $this->mParams['help-message'] ) ) {
+ $this->mParams['help-messages'] = array( $this->mParams['help-message'] );
+ }
+
+ if ( isset( $this->mParams['help-messages'] ) ) {
+ foreach ( $this->mParams['help-messages'] as $name ) {
+ $helpMessage = (array)$name;
+ $msg = $this->msg( array_shift( $helpMessage ), $helpMessage );
+
+ if ( $msg->exists() ) {
+ if ( is_null( $helptext ) ) {
+ $helptext = '';
+ } else {
+ $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
+ }
+ $helptext .= $msg->parse(); // Append message
+ }
+ }
+ } elseif ( isset( $this->mParams['help'] ) ) {
+ $helptext = $this->mParams['help'];
+ }
+
+ return $helptext;
+ }
+
+ /**
+ * Determine form errors to display and their classes
+ * @since 1.20
+ *
+ * @param string $value The value of the input
+ * @return array
+ */
+ public function getErrorsAndErrorClass( $value ) {
+ $errors = $this->validate( $value, $this->mParent->mFieldData );
+
+ if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
+ $errors = '';
+ $errorClass = '';
+ } else {
+ $errors = self::formatErrors( $errors );
+ $errorClass = 'mw-htmlform-invalid-input';
+ }
+
+ return array( $errors, $errorClass );
+ }
+
+ function getLabel() {
+ return is_null( $this->mLabel ) ? '' : $this->mLabel;
+ }
+
+ function getLabelHtml( $cellAttributes = array() ) {
+ # Don't output a for= attribute for labels with no associated input.
+ # Kind of hacky here, possibly we don't want these to be <label>s at all.
+ $for = array();
+
+ if ( $this->needsLabel() ) {
+ $for['for'] = $this->mID;
+ }
+
+ $labelValue = trim( $this->getLabel() );
+ $hasLabel = false;
+ if ( $labelValue !== '&#160;' && $labelValue !== '' ) {
+ $hasLabel = true;
+ }
+
+ $displayFormat = $this->mParent->getDisplayFormat();
+ $html = '';
+
+ if ( $displayFormat === 'table' ) {
+ $html =
+ Html::rawElement( 'td',
+ array( 'class' => 'mw-label' ) + $cellAttributes,
+ Html::rawElement( 'label', $for, $labelValue ) );
+ } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
+ if ( $displayFormat === 'div' ) {
+ $html =
+ Html::rawElement( 'div',
+ array( 'class' => 'mw-label' ) + $cellAttributes,
+ Html::rawElement( 'label', $for, $labelValue ) );
+ } else {
+ $html = Html::rawElement( 'label', $for, $labelValue );
+ }
+ }
+
+ return $html;
+ }
+
+ function getDefault() {
+ if ( isset( $this->mDefault ) ) {
+ return $this->mDefault;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the attributes required for the tooltip and accesskey.
+ *
+ * @return array Attributes
+ */
+ public function getTooltipAndAccessKey() {
+ if ( empty( $this->mParams['tooltip'] ) ) {
+ return array();
+ }
+
+ return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
+ }
+
+ /**
+ * Returns the given attributes from the parameters
+ *
+ * @param array $list List of attributes to get
+ * @return array Attributes
+ */
+ public function getAttributes( array $list ) {
+ static $boolAttribs = array( 'disabled', 'required', 'autofocus', 'multiple', 'readonly' );
+
+ $ret = array();
+
+ foreach ( $list as $key ) {
+ if ( in_array( $key, $boolAttribs ) ) {
+ if ( !empty( $this->mParams[$key] ) ) {
+ $ret[$key] = '';
+ }
+ } elseif ( isset( $this->mParams[$key] ) ) {
+ $ret[$key] = $this->mParams[$key];
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Given an array of msg-key => value mappings, returns an array with keys
+ * being the message texts. It also forces values to strings.
+ *
+ * @param array $options
+ * @return array
+ */
+ private function lookupOptionsKeys( $options ) {
+ $ret = array();
+ foreach ( $options as $key => $value ) {
+ $key = $this->msg( $key )->plain();
+ $ret[$key] = is_array( $value )
+ ? $this->lookupOptionsKeys( $value )
+ : strval( $value );
+ }
+ return $ret;
+ }
+
+ /**
+ * Recursively forces values in an array to strings, because issues arise
+ * with integer 0 as a value.
+ *
+ * @param array $array
+ * @return array
+ */
+ static function forceToStringRecursive( $array ) {
+ if ( is_array( $array ) ) {
+ return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array );
+ } else {
+ return strval( $array );
+ }
+ }
+
+ /**
+ * Fetch the array of options from the field's parameters. In order, this
+ * checks 'options-messages', 'options', then 'options-message'.
+ *
+ * @return array|null Options array
+ */
+ public function getOptions() {
+ if ( $this->mOptions === false ) {
+ if ( array_key_exists( 'options-messages', $this->mParams ) ) {
+ $this->mOptions = $this->lookupOptionsKeys( $this->mParams['options-messages'] );
+ } elseif ( array_key_exists( 'options', $this->mParams ) ) {
+ $this->mOptionsLabelsNotFromMessage = true;
+ $this->mOptions = self::forceToStringRecursive( $this->mParams['options'] );
+ } elseif ( array_key_exists( 'options-message', $this->mParams ) ) {
+ /** @todo This is copied from Xml::listDropDown(), deprecate/avoid duplication? */
+ $message = $this->msg( $this->mParams['options-message'] )->inContentLanguage()->plain();
+
+ $optgroup = false;
+ $this->mOptions = array();
+ foreach ( explode( "\n", $message ) as $option ) {
+ $value = trim( $option );
+ if ( $value == '' ) {
+ continue;
+ } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
+ # A new group is starting...
+ $value = trim( substr( $value, 1 ) );
+ $optgroup = $value;
+ } elseif ( substr( $value, 0, 2 ) == '**' ) {
+ # groupmember
+ $opt = trim( substr( $value, 2 ) );
+ if ( $optgroup === false ) {
+ $this->mOptions[$opt] = $opt;
+ } else {
+ $this->mOptions[$optgroup][$opt] = $opt;
+ }
+ } else {
+ # groupless reason list
+ $optgroup = false;
+ $this->mOptions[$option] = $option;
+ }
+ }
+ } else {
+ $this->mOptions = null;
+ }
+ }
+
+ return $this->mOptions;
+ }
+
+ /**
+ * flatten an array of options to a single array, for instance,
+ * a set of "<options>" inside "<optgroups>".
+ *
+ * @param array $options Associative Array with values either Strings or Arrays
+ * @return array Flattened input
+ */
+ public static function flattenOptions( $options ) {
+ $flatOpts = array();
+
+ foreach ( $options as $value ) {
+ if ( is_array( $value ) ) {
+ $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
+ } else {
+ $flatOpts[] = $value;
+ }
+ }
+
+ return $flatOpts;
+ }
+
+ /**
+ * Formats one or more errors as accepted by field validation-callback.
+ *
+ * @param string|Message|array $errors Array of strings or Message instances
+ * @return string HTML
+ * @since 1.18
+ */
+ protected static function formatErrors( $errors ) {
+ if ( is_array( $errors ) && count( $errors ) === 1 ) {
+ $errors = array_shift( $errors );
+ }
+
+ if ( is_array( $errors ) ) {
+ $lines = array();
+ foreach ( $errors as $error ) {
+ if ( $error instanceof Message ) {
+ $lines[] = Html::rawElement( 'li', array(), $error->parse() );
+ } else {
+ $lines[] = Html::rawElement( 'li', array(), $error );
+ }
+ }
+
+ return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
+ } else {
+ if ( $errors instanceof Message ) {
+ $errors = $errors->parse();
+ }
+
+ return Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
+ }
+ }
+}
diff --git a/includes/htmlform/HTMLFormFieldCloner.php b/includes/htmlform/HTMLFormFieldCloner.php
new file mode 100644
index 00000000..029911cd
--- /dev/null
+++ b/includes/htmlform/HTMLFormFieldCloner.php
@@ -0,0 +1,382 @@
+<?php
+
+/**
+ * A container for HTMLFormFields that allows for multiple copies of the set of
+ * fields to be displayed to and entered by the user.
+ *
+ * Recognized parameters, besides the general ones, include:
+ * fields - HTMLFormField descriptors for the subfields this cloner manages.
+ * The format is just like for the HTMLForm. A field with key 'delete' is
+ * special: it must have type = submit and will serve to delete the group
+ * of fields.
+ * required - If specified, at least one group of fields must be submitted.
+ * format - HTMLForm display format to use when displaying the subfields:
+ * 'table', 'div', or 'raw'.
+ * row-legend - If non-empty, each group of subfields will be enclosed in a
+ * fieldset. The value is the name of a message key to use as the legend.
+ * create-button-message - Message key to use as the text of the button to
+ * add an additional group of fields.
+ * delete-button-message - Message key to use as the text of automatically-
+ * generated 'delete' button. Ignored if 'delete' is included in 'fields'.
+ *
+ * In the generated HTML, the subfields will be named along the lines of
+ * "clonerName[index][fieldname]", with ids "clonerId--index--fieldid". 'index'
+ * may be a number or an arbitrary string, and may likely change when the page
+ * is resubmitted. Cloners may be nested, resulting in field names along the
+ * lines of "cloner1Name[index1][cloner2Name][index2][fieldname]" and
+ * corresponding ids.
+ *
+ * Use of cloner may result in submissions of the page that are not submissions
+ * of the HTMLForm, when non-JavaScript clients use the create or remove buttons.
+ *
+ * The result is an array, with values being arrays mapping subfield names to
+ * their values. On non-HTMLForm-submission page loads, there may also be
+ * additional (string) keys present with other types of values.
+ *
+ * @since 1.23
+ */
+class HTMLFormFieldCloner extends HTMLFormField {
+ private static $counter = 0;
+
+ /**
+ * @var string String uniquely identifying this cloner instance and
+ * unlikely to exist otherwise in the generated HTML, while still being
+ * valid as part of an HTML id.
+ */
+ protected $uniqueId;
+
+ public function __construct( $params ) {
+ $this->uniqueId = get_class( $this ) . ++self::$counter . 'x';
+ parent::__construct( $params );
+
+ if ( empty( $this->mParams['fields'] ) || !is_array( $this->mParams['fields'] ) ) {
+ throw new MWException( 'HTMLFormFieldCloner called without any fields' );
+ }
+
+ // Make sure the delete button, if explicitly specified, is sane
+ if ( isset( $this->mParams['fields']['delete'] ) ) {
+ $class = 'mw-htmlform-cloner-delete-button';
+ $info = $this->mParams['fields']['delete'] + array(
+ 'cssclass' => $class
+ );
+ unset( $info['name'], $info['class'] );
+
+ if ( !isset( $info['type'] ) || $info['type'] !== 'submit' ) {
+ throw new MWException(
+ 'HTMLFormFieldCloner delete field, if specified, must be of type "submit"'
+ );
+ }
+
+ if ( !in_array( $class, explode( ' ', $info['cssclass'] ) ) ) {
+ $info['cssclass'] .= " $class";
+ }
+
+ $this->mParams['fields']['delete'] = $info;
+ }
+ }
+
+ /**
+ * Create the HTMLFormFields that go inside this element, using the
+ * specified key.
+ *
+ * @param string $key Array key under which these fields should be named
+ * @return HTMLFormField[]
+ */
+ protected function createFieldsForKey( $key ) {
+ $fields = array();
+ foreach ( $this->mParams['fields'] as $fieldname => $info ) {
+ $name = "{$this->mName}[$key][$fieldname]";
+ if ( isset( $info['name'] ) ) {
+ $info['name'] = "{$this->mName}[$key][{$info['name']}]";
+ } else {
+ $info['name'] = $name;
+ }
+ if ( isset( $info['id'] ) ) {
+ $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--{$info['id']}" );
+ } else {
+ $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--$fieldname" );
+ }
+ $field = HTMLForm::loadInputFromParameters( $name, $info );
+ $field->mParent = $this->mParent;
+ $fields[$fieldname] = $field;
+ }
+ return $fields;
+ }
+
+ /**
+ * Re-key the specified values array to match the names applied by
+ * createFieldsForKey().
+ *
+ * @param string $key Array key under which these fields should be named
+ * @param array $values Values array from the request
+ * @return array
+ */
+ protected function rekeyValuesArray( $key, $values ) {
+ $data = array();
+ foreach ( $values as $fieldname => $value ) {
+ $name = "{$this->mName}[$key][$fieldname]";
+ $data[$name] = $value;
+ }
+ return $data;
+ }
+
+ protected function needsLabel() {
+ return false;
+ }
+
+ public function loadDataFromRequest( $request ) {
+ // It's possible that this might be posted with no fields. Detect that
+ // by looking for an edit token.
+ if ( !$request->getCheck( 'wpEditToken' ) && $request->getArray( $this->mName ) === null ) {
+ return $this->getDefault();
+ }
+
+ $values = $request->getArray( $this->mName );
+ if ( $values === null ) {
+ $values = array();
+ }
+
+ $ret = array();
+ foreach ( $values as $key => $value ) {
+ if ( $key === 'create' || isset( $value['delete'] ) ) {
+ $ret['nonjs'] = 1;
+ continue;
+ }
+
+ // Add back in $request->getValues() so things that look for e.g.
+ // wpEditToken don't fail.
+ $data = $this->rekeyValuesArray( $key, $value ) + $request->getValues();
+
+ $fields = $this->createFieldsForKey( $key );
+ $subrequest = new DerivativeRequest( $request, $data, $request->wasPosted() );
+ $row = array();
+ foreach ( $fields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ } elseif ( !empty( $field->mParams['disabled'] ) ) {
+ $row[$fieldname] = $field->getDefault();
+ } else {
+ $row[$fieldname] = $field->loadDataFromRequest( $subrequest );
+ }
+ }
+ $ret[] = $row;
+ }
+
+ if ( isset( $values['create'] ) ) {
+ // Non-JS client clicked the "create" button.
+ $fields = $this->createFieldsForKey( $this->uniqueId );
+ $row = array();
+ foreach ( $fields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ } else {
+ $row[$fieldname] = $field->getDefault();
+ }
+ }
+ $ret[] = $row;
+ }
+
+ return $ret;
+ }
+
+ public function getDefault() {
+ $ret = parent::getDefault();
+
+ // The default default is one entry with all subfields at their
+ // defaults.
+ if ( $ret === null ) {
+ $fields = $this->createFieldsForKey( $this->uniqueId );
+ $row = array();
+ foreach ( $fields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ } else {
+ $row[$fieldname] = $field->getDefault();
+ }
+ }
+ $ret = array( $row );
+ }
+
+ return $ret;
+ }
+
+ public function cancelSubmit( $values, $alldata ) {
+ if ( isset( $values['nonjs'] ) ) {
+ return true;
+ }
+
+ foreach ( $values as $key => $value ) {
+ $fields = $this->createFieldsForKey( $key );
+ foreach ( $fields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ }
+ if ( $field->cancelSubmit( $value[$fieldname], $alldata ) ) {
+ return true;
+ }
+ }
+ }
+
+ return parent::cancelSubmit( $values, $alldata );
+ }
+
+ public function validate( $values, $alldata ) {
+ if ( isset( $this->mParams['required'] )
+ && $this->mParams['required'] !== false
+ && !$values
+ ) {
+ return $this->msg( 'htmlform-cloner-required' )->parseAsBlock();
+ }
+
+ if ( isset( $values['nonjs'] ) ) {
+ // The submission was a non-JS create/delete click, so fail
+ // validation in case cancelSubmit() somehow didn't already handle
+ // it.
+ return false;
+ }
+
+ foreach ( $values as $key => $value ) {
+ $fields = $this->createFieldsForKey( $key );
+ foreach ( $fields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ }
+ $ok = $field->validate( $value[$fieldname], $alldata );
+ if ( $ok !== true ) {
+ return false;
+ }
+ }
+ }
+
+ return parent::validate( $values, $alldata );
+ }
+
+ /**
+ * Get the input HTML for the specified key.
+ *
+ * @param string $key Array key under which the fields should be named
+ * @param array $values
+ * @return string
+ */
+ protected function getInputHTMLForKey( $key, $values ) {
+ $displayFormat = isset( $this->mParams['format'] )
+ ? $this->mParams['format']
+ : $this->mParent->getDisplayFormat();
+
+ switch ( $displayFormat ) {
+ case 'table':
+ $getFieldHtmlMethod = 'getTableRow';
+ break;
+ case 'vform':
+ // Close enough to a div.
+ $getFieldHtmlMethod = 'getDiv';
+ break;
+ default:
+ $getFieldHtmlMethod = 'get' . ucfirst( $displayFormat );
+ }
+
+ $html = '';
+ $hasLabel = false;
+
+ $fields = $this->createFieldsForKey( $key );
+ foreach ( $fields as $fieldname => $field ) {
+ $v = ( empty( $field->mParams['nodata'] ) && $values !== null )
+ ? $values[$fieldname]
+ : $field->getDefault();
+ $html .= $field->$getFieldHtmlMethod( $v );
+
+ $labelValue = trim( $field->getLabel() );
+ if ( $labelValue != '&#160;' && $labelValue !== '' ) {
+ $hasLabel = true;
+ }
+ }
+
+ if ( !isset( $fields['delete'] ) ) {
+ $name = "{$this->mName}[$key][delete]";
+ $label = isset( $this->mParams['delete-button-message'] )
+ ? $this->mParams['delete-button-message']
+ : 'htmlform-cloner-delete';
+ $field = HTMLForm::loadInputFromParameters( $name, array(
+ 'type' => 'submit',
+ 'name' => $name,
+ 'id' => Sanitizer::escapeId( "{$this->mID}--$key--delete" ),
+ 'cssclass' => 'mw-htmlform-cloner-delete-button',
+ 'default' => $this->msg( $label )->text(),
+ ) );
+ $field->mParent = $this->mParent;
+ $v = $field->getDefault();
+
+ if ( $displayFormat === 'table' ) {
+ $html .= $field->$getFieldHtmlMethod( $v );
+ } else {
+ $html .= $field->getInputHTML( $v );
+ }
+ }
+
+ if ( $displayFormat !== 'raw' ) {
+ $classes = array(
+ 'mw-htmlform-cloner-row',
+ );
+
+ if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
+ $classes[] = 'mw-htmlform-nolabel';
+ }
+
+ $attribs = array(
+ 'class' => implode( ' ', $classes ),
+ );
+
+ if ( $displayFormat === 'table' ) {
+ $html = Html::rawElement( 'table',
+ $attribs,
+ Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
+ } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) {
+ $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
+ }
+ }
+
+ if ( !empty( $this->mParams['row-legend'] ) ) {
+ $legend = $this->msg( $this->mParams['row-legend'] )->text();
+ $html = Xml::fieldset( $legend, $html );
+ }
+
+ return $html;
+ }
+
+ public function getInputHTML( $values ) {
+ $html = '';
+
+ foreach ( (array)$values as $key => $value ) {
+ if ( $key === 'nonjs' ) {
+ continue;
+ }
+ $html .= Html::rawElement( 'li', array( 'class' => 'mw-htmlform-cloner-li' ),
+ $this->getInputHTMLForKey( $key, $value )
+ );
+ }
+
+ $template = $this->getInputHTMLForKey( $this->uniqueId, null );
+ $html = Html::rawElement( 'ul', array(
+ 'id' => "mw-htmlform-cloner-list-{$this->mID}",
+ 'class' => 'mw-htmlform-cloner-ul',
+ 'data-template' => $template,
+ 'data-unique-id' => $this->uniqueId,
+ ), $html );
+
+ $name = "{$this->mName}[create]";
+ $label = isset( $this->mParams['create-button-message'] )
+ ? $this->mParams['create-button-message']
+ : 'htmlform-cloner-create';
+ $field = HTMLForm::loadInputFromParameters( $name, array(
+ 'type' => 'submit',
+ 'name' => $name,
+ 'id' => Sanitizer::escapeId( "{$this->mID}--create" ),
+ 'cssclass' => 'mw-htmlform-cloner-create-button',
+ 'default' => $this->msg( $label )->text(),
+ ) );
+ $field->mParent = $this->mParent;
+ $html .= $field->getInputHTML( $field->getDefault() );
+
+ return $html;
+ }
+}
diff --git a/includes/htmlform/HTMLFormFieldRequiredOptionsException.php b/includes/htmlform/HTMLFormFieldRequiredOptionsException.php
new file mode 100644
index 00000000..76f52866
--- /dev/null
+++ b/includes/htmlform/HTMLFormFieldRequiredOptionsException.php
@@ -0,0 +1,9 @@
+<?php
+
+class HTMLFormFieldRequiredOptionsException extends MWException {
+ public function __construct( HTMLFormField $field, array $missing ) {
+ parent::__construct( sprintf( "Form type `%s` expected the following parameters to be set: %s",
+ get_class( $field ),
+ implode( ', ', $missing ) ) );
+ }
+}
diff --git a/includes/htmlform/HTMLHiddenField.php b/includes/htmlform/HTMLHiddenField.php
new file mode 100644
index 00000000..e32c0bb2
--- /dev/null
+++ b/includes/htmlform/HTMLHiddenField.php
@@ -0,0 +1,44 @@
+<?php
+
+class HTMLHiddenField extends HTMLFormField {
+ public function __construct( $params ) {
+ parent::__construct( $params );
+
+ # Per HTML5 spec, hidden fields cannot be 'required'
+ # http://www.w3.org/TR/html5/forms.html#hidden-state-%28type=hidden%29
+ unset( $this->mParams['required'] );
+ }
+
+ public function getTableRow( $value ) {
+ $params = array();
+ if ( $this->mID ) {
+ $params['id'] = $this->mID;
+ }
+
+ $this->mParent->addHiddenField( $this->mName, $this->mDefault, $params );
+
+ return '';
+ }
+
+ /**
+ * @param string $value
+ * @return string
+ * @since 1.20
+ */
+ public function getDiv( $value ) {
+ return $this->getTableRow( $value );
+ }
+
+ /**
+ * @param string $value
+ * @return string
+ * @since 1.20
+ */
+ public function getRaw( $value ) {
+ return $this->getTableRow( $value );
+ }
+
+ public function getInputHTML( $value ) {
+ return '';
+ }
+}
diff --git a/includes/htmlform/HTMLInfoField.php b/includes/htmlform/HTMLInfoField.php
new file mode 100644
index 00000000..a422047a
--- /dev/null
+++ b/includes/htmlform/HTMLInfoField.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * An information field (text blob), not a proper input.
+ */
+class HTMLInfoField extends HTMLFormField {
+ public function __construct( $info ) {
+ $info['nodata'] = true;
+
+ parent::__construct( $info );
+ }
+
+ public function getInputHTML( $value ) {
+ return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value );
+ }
+
+ public function getTableRow( $value ) {
+ if ( !empty( $this->mParams['rawrow'] ) ) {
+ return $value;
+ }
+
+ return parent::getTableRow( $value );
+ }
+
+ /**
+ * @param string $value
+ * @return string
+ * @since 1.20
+ */
+ public function getDiv( $value ) {
+ if ( !empty( $this->mParams['rawrow'] ) ) {
+ return $value;
+ }
+
+ return parent::getDiv( $value );
+ }
+
+ /**
+ * @param string $value
+ * @return string
+ * @since 1.20
+ */
+ public function getRaw( $value ) {
+ if ( !empty( $this->mParams['rawrow'] ) ) {
+ return $value;
+ }
+
+ return parent::getRaw( $value );
+ }
+
+ protected function needsLabel() {
+ return false;
+ }
+}
diff --git a/includes/htmlform/HTMLIntField.php b/includes/htmlform/HTMLIntField.php
new file mode 100644
index 00000000..28876e2c
--- /dev/null
+++ b/includes/htmlform/HTMLIntField.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * A field that must contain a number
+ */
+class HTMLIntField extends HTMLFloatField {
+ function validate( $value, $alldata ) {
+ $p = parent::validate( $value, $alldata );
+
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers
+ # with the addition that a leading '+' sign is ok. Note that leading zeros
+ # are fine, and will be left in the input, which is useful for things like
+ # phone numbers when you know that they are integers (the HTML5 type=tel
+ # input does not require its value to be numeric). If you want a tidier
+ # value to, eg, save in the DB, clean it up with intval().
+ if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) )
+ ) {
+ return $this->msg( 'htmlform-int-invalid' )->parseAsBlock();
+ }
+
+ return true;
+ }
+}
diff --git a/includes/htmlform/HTMLMultiSelectField.php b/includes/htmlform/HTMLMultiSelectField.php
new file mode 100644
index 00000000..1b71ab95
--- /dev/null
+++ b/includes/htmlform/HTMLMultiSelectField.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * Multi-select field
+ */
+class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable {
+ function validate( $value, $alldata ) {
+ $p = parent::validate( $value, $alldata );
+
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ if ( !is_array( $value ) ) {
+ return false;
+ }
+
+ # If all options are valid, array_intersect of the valid options
+ # and the provided options will return the provided options.
+ $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+
+ $validValues = array_intersect( $value, $validOptions );
+ if ( count( $validValues ) == count( $value ) ) {
+ return true;
+ } else {
+ return $this->msg( 'htmlform-select-badoption' )->parse();
+ }
+ }
+
+ function getInputHTML( $value ) {
+ $value = HTMLFormField::forceToStringRecursive( $value );
+ $html = $this->formatOptions( $this->getOptions(), $value );
+
+ return $html;
+ }
+
+ function formatOptions( $options, $value ) {
+ $html = '';
+
+ $attribs = $this->getAttributes( array( 'disabled', 'tabindex' ) );
+ $elementFunc = array( 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' );
+
+ foreach ( $options as $label => $info ) {
+ if ( is_array( $info ) ) {
+ $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
+ $html .= $this->formatOptions( $info, $value );
+ } else {
+ $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
+
+ // @todo: Make this use checkLabel for consistency purposes
+ $checkbox = Xml::check(
+ $this->mName . '[]',
+ in_array( $info, $value, true ),
+ $attribs + $thisAttribs
+ );
+ $checkbox .= '&#160;' . call_user_func( $elementFunc,
+ 'label',
+ array( 'for' => "{$this->mID}-$info" ),
+ $label
+ );
+
+ $html .= ' ' . Html::rawElement(
+ 'div',
+ array( 'class' => 'mw-htmlform-flatlist-item' ),
+ $checkbox
+ );
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * @param WebRequest $request
+ *
+ * @return string
+ */
+ function loadDataFromRequest( $request ) {
+ if ( $this->mParent->getMethod() == 'post' ) {
+ if ( $request->wasPosted() ) {
+ # Checkboxes are just not added to the request arrays if they're not checked,
+ # so it's perfectly possible for there not to be an entry at all
+ return $request->getArray( $this->mName, array() );
+ } else {
+ # That's ok, the user has not yet submitted the form, so show the defaults
+ return $this->getDefault();
+ }
+ } else {
+ # This is the impossible case: if we look at $_GET and see no data for our
+ # field, is it because the user has not yet submitted the form, or that they
+ # have submitted it with all the options unchecked? We will have to assume the
+ # latter, which basically means that you can't specify 'positive' defaults
+ # for GET forms.
+ # @todo FIXME...
+ return $request->getArray( $this->mName, array() );
+ }
+ }
+
+ function getDefault() {
+ if ( isset( $this->mDefault ) ) {
+ return $this->mDefault;
+ } else {
+ return array();
+ }
+ }
+
+ function filterDataForSubmit( $data ) {
+ $data = HTMLFormField::forceToStringRecursive( $data );
+ $options = HTMLFormField::flattenOptions( $this->getOptions() );
+
+ $res = array();
+ foreach ( $options as $opt ) {
+ $res["$opt"] = in_array( $opt, $data, true );
+ }
+
+ return $res;
+ }
+
+ protected function needsLabel() {
+ return false;
+ }
+}
diff --git a/includes/htmlform/HTMLNestedFilterable.php b/includes/htmlform/HTMLNestedFilterable.php
new file mode 100644
index 00000000..2c09ea4e
--- /dev/null
+++ b/includes/htmlform/HTMLNestedFilterable.php
@@ -0,0 +1,11 @@
+<?php
+
+interface HTMLNestedFilterable {
+ /**
+ * Support for seperating multi-option preferences into multiple preferences
+ * Due to lack of array support.
+ *
+ * @param array $data
+ */
+ function filterDataForSubmit( $data );
+}
diff --git a/includes/htmlform/HTMLRadioField.php b/includes/htmlform/HTMLRadioField.php
new file mode 100644
index 00000000..8765407b
--- /dev/null
+++ b/includes/htmlform/HTMLRadioField.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * Radio checkbox fields.
+ */
+class HTMLRadioField extends HTMLFormField {
+ function validate( $value, $alldata ) {
+ $p = parent::validate( $value, $alldata );
+
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ if ( !is_string( $value ) && !is_int( $value ) ) {
+ return false;
+ }
+
+ $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+
+ if ( in_array( strval( $value ), $validOptions, true ) ) {
+ return true;
+ } else {
+ return $this->msg( 'htmlform-select-badoption' )->parse();
+ }
+ }
+
+ /**
+ * This returns a block of all the radio options, in one cell.
+ * @see includes/HTMLFormField#getInputHTML()
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ function getInputHTML( $value ) {
+ $html = $this->formatOptions( $this->getOptions(), strval( $value ) );
+
+ return $html;
+ }
+
+ function formatOptions( $options, $value ) {
+ $html = '';
+
+ $attribs = $this->getAttributes( array( 'disabled', 'tabindex' ) );
+ $elementFunc = array( 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' );
+
+ # @todo Should this produce an unordered list perhaps?
+ foreach ( $options as $label => $info ) {
+ if ( is_array( $info ) ) {
+ $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
+ $html .= $this->formatOptions( $info, $value );
+ } else {
+ $id = Sanitizer::escapeId( $this->mID . "-$info" );
+ $radio = Xml::radio( $this->mName, $info, $info === $value, $attribs + array( 'id' => $id ) );
+ $radio .= '&#160;' . call_user_func( $elementFunc, 'label', array( 'for' => $id ), $label );
+
+ $html .= ' ' . Html::rawElement(
+ 'div',
+ array( 'class' => 'mw-htmlform-flatlist-item' ),
+ $radio
+ );
+ }
+ }
+
+ return $html;
+ }
+
+ protected function needsLabel() {
+ return false;
+ }
+}
diff --git a/includes/htmlform/HTMLSelectAndOtherField.php b/includes/htmlform/HTMLSelectAndOtherField.php
new file mode 100644
index 00000000..65176dd7
--- /dev/null
+++ b/includes/htmlform/HTMLSelectAndOtherField.php
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * Double field with a dropdown list constructed from a system message in the format
+ * * Optgroup header
+ * ** <option value>
+ * * New Optgroup header
+ * Plus a text field underneath for an additional reason. The 'value' of the field is
+ * "<select>: <extra reason>", or "<extra reason>" if nothing has been selected in the
+ * select dropdown.
+ * @todo FIXME: If made 'required', only the text field should be compulsory.
+ */
+class HTMLSelectAndOtherField extends HTMLSelectField {
+ function __construct( $params ) {
+ if ( array_key_exists( 'other', $params ) ) {
+ } elseif ( array_key_exists( 'other-message', $params ) ) {
+ $params['other'] = wfMessage( $params['other-message'] )->plain();
+ } else {
+ $params['other'] = wfMessage( 'htmlform-selectorother-other' )->plain();
+ }
+
+ parent::__construct( $params );
+
+ if ( $this->getOptions() === null ) {
+ # Sulk
+ throw new MWException( 'HTMLSelectAndOtherField called without any options' );
+ }
+ if ( !in_array( 'other', $this->mOptions, true ) ) {
+ // Have 'other' always as first element
+ $this->mOptions = array( $params['other'] => 'other' ) + $this->mOptions;
+ }
+ $this->mFlatOptions = self::flattenOptions( $this->getOptions() );
+
+ }
+
+ function getInputHTML( $value ) {
+ $select = parent::getInputHTML( $value[1] );
+
+ $textAttribs = array(
+ 'id' => $this->mID . '-other',
+ 'size' => $this->getSize(),
+ );
+
+ if ( $this->mClass !== '' ) {
+ $textAttribs['class'] = $this->mClass;
+ }
+
+ $allowedParams = array(
+ 'required',
+ 'autofocus',
+ 'multiple',
+ 'disabled',
+ 'tabindex'
+ );
+
+ $textAttribs += $this->getAttributes( $allowedParams );
+
+ $textbox = Html::input( $this->mName . '-other', $value[2], 'text', $textAttribs );
+
+ return "$select<br />\n$textbox";
+ }
+
+ /**
+ * @param WebRequest $request
+ *
+ * @return array("<overall message>","<select value>","<text field value>")
+ */
+ function loadDataFromRequest( $request ) {
+ if ( $request->getCheck( $this->mName ) ) {
+
+ $list = $request->getText( $this->mName );
+ $text = $request->getText( $this->mName . '-other' );
+
+ if ( $list == 'other' ) {
+ $final = $text;
+ } elseif ( !in_array( $list, $this->mFlatOptions, true ) ) {
+ # User has spoofed the select form to give an option which wasn't
+ # in the original offer. Sulk...
+ $final = $text;
+ } elseif ( $text == '' ) {
+ $final = $list;
+ } else {
+ $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
+ }
+ } else {
+ $final = $this->getDefault();
+
+ $list = 'other';
+ $text = $final;
+ foreach ( $this->mFlatOptions as $option ) {
+ $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
+ if ( strpos( $text, $match ) === 0 ) {
+ $list = $option;
+ $text = substr( $text, strlen( $match ) );
+ break;
+ }
+ }
+ }
+
+ return array( $final, $list, $text );
+ }
+
+ function getSize() {
+ return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 45;
+ }
+
+ function validate( $value, $alldata ) {
+ # HTMLSelectField forces $value to be one of the options in the select
+ # field, which is not useful here. But we do want the validation further up
+ # the chain
+ $p = parent::validate( $value[1], $alldata );
+
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ if ( isset( $this->mParams['required'] )
+ && $this->mParams['required'] !== false
+ && $value[1] === ''
+ ) {
+ return $this->msg( 'htmlform-required' )->parse();
+ }
+
+ return true;
+ }
+}
diff --git a/includes/htmlform/HTMLSelectField.php b/includes/htmlform/HTMLSelectField.php
new file mode 100644
index 00000000..a198037a
--- /dev/null
+++ b/includes/htmlform/HTMLSelectField.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * A select dropdown field. Basically a wrapper for Xmlselect class
+ */
+class HTMLSelectField extends HTMLFormField {
+ function validate( $value, $alldata ) {
+ $p = parent::validate( $value, $alldata );
+
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+
+ if ( in_array( strval( $value ), $validOptions, true ) ) {
+ return true;
+ } else {
+ return $this->msg( 'htmlform-select-badoption' )->parse();
+ }
+ }
+
+ function getInputHTML( $value ) {
+ $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
+
+ if ( !empty( $this->mParams['disabled'] ) ) {
+ $select->setAttribute( 'disabled', 'disabled' );
+ }
+
+ $allowedParams = array( 'tabindex', 'size' );
+ $customParams = $this->getAttributes( $allowedParams );
+ foreach ( $customParams as $name => $value ) {
+ $select->setAttribute( $name, $value );
+ }
+
+ if ( $this->mClass !== '' ) {
+ $select->setAttribute( 'class', $this->mClass );
+ }
+
+ $select->addOptions( $this->getOptions() );
+
+ return $select->getHTML();
+ }
+}
diff --git a/includes/htmlform/HTMLSelectLimitField.php b/includes/htmlform/HTMLSelectLimitField.php
new file mode 100644
index 00000000..e7f1c047
--- /dev/null
+++ b/includes/htmlform/HTMLSelectLimitField.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * A limit dropdown, which accepts any valid number
+ */
+class HTMLSelectLimitField extends HTMLSelectField {
+ /**
+ * Basically don't do any validation. If it's a number that's fine. Also,
+ * add it to the list if it's not there already
+ *
+ * @param string $value
+ * @param array $alldata
+ * @return bool
+ */
+ function validate( $value, $alldata ) {
+ if ( $value == '' ) {
+ return true;
+ }
+
+ // Let folks pick an explicit limit not from our list, as long as it's a real numbr.
+ if ( !in_array( $value, $this->mParams['options'] )
+ && $value == intval( $value )
+ && $value > 0
+ ) {
+ // This adds the explicitly requested limit value to the drop-down,
+ // then makes sure it's sorted correctly so when we output the list
+ // later, the custom option doesn't just show up last.
+ $this->mParams['options'][$this->mParent->getLanguage()->formatNum( $value )] =
+ intval( $value );
+ asort( $this->mParams['options'] );
+ }
+
+ return true;
+ }
+}
diff --git a/includes/htmlform/HTMLSelectOrOtherField.php b/includes/htmlform/HTMLSelectOrOtherField.php
new file mode 100644
index 00000000..cbf7d122
--- /dev/null
+++ b/includes/htmlform/HTMLSelectOrOtherField.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * Select dropdown field, with an additional "other" textbox.
+ */
+class HTMLSelectOrOtherField extends HTMLTextField {
+ function __construct( $params ) {
+ parent::__construct( $params );
+ $this->getOptions();
+ if ( !in_array( 'other', $this->mOptions, true ) ) {
+ $msg =
+ isset( $params['other'] )
+ ? $params['other']
+ : wfMessage( 'htmlform-selectorother-other' )->text();
+ // Have 'other' always as first element
+ $this->mOptions = array( $msg => 'other' ) + $this->mOptions;
+ }
+
+ }
+
+ function getInputHTML( $value ) {
+ $valInSelect = false;
+
+ if ( $value !== false ) {
+ $value = strval( $value );
+ $valInSelect = in_array(
+ $value, HTMLFormField::flattenOptions( $this->getOptions() ), true
+ );
+ }
+
+ $selected = $valInSelect ? $value : 'other';
+
+ $select = new XmlSelect( $this->mName, $this->mID, $selected );
+ $select->addOptions( $this->getOptions() );
+
+ $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
+
+ $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() );
+
+ if ( !empty( $this->mParams['disabled'] ) ) {
+ $select->setAttribute( 'disabled', 'disabled' );
+ $tbAttribs['disabled'] = 'disabled';
+ }
+
+ if ( isset( $this->mParams['tabindex'] ) ) {
+ $select->setAttribute( 'tabindex', $this->mParams['tabindex'] );
+ $tbAttribs['tabindex'] = $this->mParams['tabindex'];
+ }
+
+ $select = $select->getHTML();
+
+ if ( isset( $this->mParams['maxlength'] ) ) {
+ $tbAttribs['maxlength'] = $this->mParams['maxlength'];
+ }
+
+ if ( $this->mClass !== '' ) {
+ $tbAttribs['class'] = $this->mClass;
+ }
+
+ $textbox = Html::input( $this->mName . '-other', $valInSelect ? '' : $value, 'text', $tbAttribs );
+
+ return "$select<br />\n$textbox";
+ }
+
+ /**
+ * @param WebRequest $request
+ *
+ * @return string
+ */
+ function loadDataFromRequest( $request ) {
+ if ( $request->getCheck( $this->mName ) ) {
+ $val = $request->getText( $this->mName );
+
+ if ( $val === 'other' ) {
+ $val = $request->getText( $this->mName . '-other' );
+ }
+
+ return $val;
+ } else {
+ return $this->getDefault();
+ }
+ }
+}
diff --git a/includes/htmlform/HTMLSubmitField.php b/includes/htmlform/HTMLSubmitField.php
new file mode 100644
index 00000000..653c08c0
--- /dev/null
+++ b/includes/htmlform/HTMLSubmitField.php
@@ -0,0 +1,9 @@
+<?php
+
+/**
+ * Add a submit button inline in the form (as opposed to
+ * HTMLForm::addButton(), which will add it at the end).
+ */
+class HTMLSubmitField extends HTMLButtonField {
+ protected $buttonType = 'submit';
+}
diff --git a/includes/htmlform/HTMLTextAreaField.php b/includes/htmlform/HTMLTextAreaField.php
new file mode 100644
index 00000000..21173d2a
--- /dev/null
+++ b/includes/htmlform/HTMLTextAreaField.php
@@ -0,0 +1,38 @@
+<?php
+
+class HTMLTextAreaField extends HTMLFormField {
+ const DEFAULT_COLS = 80;
+ const DEFAULT_ROWS = 25;
+
+ function getCols() {
+ return isset( $this->mParams['cols'] ) ? $this->mParams['cols'] : static::DEFAULT_COLS;
+ }
+
+ function getRows() {
+ return isset( $this->mParams['rows'] ) ? $this->mParams['rows'] : static::DEFAULT_ROWS;
+ }
+
+ function getInputHTML( $value ) {
+ $attribs = array(
+ 'id' => $this->mID,
+ 'cols' => $this->getCols(),
+ 'rows' => $this->getRows(),
+ ) + $this->getTooltipAndAccessKey();
+
+ if ( $this->mClass !== '' ) {
+ $attribs['class'] = $this->mClass;
+ }
+
+ $allowedParams = array(
+ 'placeholder',
+ 'tabindex',
+ 'disabled',
+ 'readonly',
+ 'required',
+ 'autofocus'
+ );
+
+ $attribs += $this->getAttributes( $allowedParams );
+ return Html::textarea( $this->mName, $value, $attribs );
+ }
+}
diff --git a/includes/htmlform/HTMLTextField.php b/includes/htmlform/HTMLTextField.php
new file mode 100644
index 00000000..10bc67f0
--- /dev/null
+++ b/includes/htmlform/HTMLTextField.php
@@ -0,0 +1,65 @@
+<?php
+
+class HTMLTextField extends HTMLFormField {
+ function getSize() {
+ return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 45;
+ }
+
+ function getInputHTML( $value ) {
+ $attribs = array(
+ 'id' => $this->mID,
+ 'name' => $this->mName,
+ 'size' => $this->getSize(),
+ 'value' => $value,
+ ) + $this->getTooltipAndAccessKey();
+
+ if ( $this->mClass !== '' ) {
+ $attribs['class'] = $this->mClass;
+ }
+
+ # @todo Enforce pattern, step, required, readonly on the server side as
+ # well
+ $allowedParams = array(
+ 'min',
+ 'max',
+ 'pattern',
+ 'title',
+ 'step',
+ 'placeholder',
+ 'list',
+ 'maxlength',
+ 'tabindex',
+ 'disabled',
+ 'required',
+ 'autofocus',
+ 'multiple',
+ 'readonly'
+ );
+
+ $attribs += $this->getAttributes( $allowedParams );
+
+ # Implement tiny differences between some field variants
+ # here, rather than creating a new class for each one which
+ # is essentially just a clone of this one.
+ $type = 'text';
+ if ( isset( $this->mParams['type'] ) ) {
+ switch ( $this->mParams['type'] ) {
+ case 'int':
+ $type = 'number';
+ break;
+ case 'float':
+ $type = 'number';
+ $attribs['step'] = 'any';
+ break;
+ # Pass through
+ case 'email':
+ case 'password':
+ case 'file':
+ case 'url':
+ $type = $this->mParams['type'];
+ break;
+ }
+ }
+ return Html::input( $this->mName, $value, $type, $attribs );
+ }
+}
diff --git a/includes/installer/CliInstaller.php b/includes/installer/CliInstaller.php
index f944fbed..72907408 100644
--- a/includes/installer/CliInstaller.php
+++ b/includes/installer/CliInstaller.php
@@ -49,9 +49,9 @@ class CliInstaller extends Installer {
/**
* Constructor.
*
- * @param $siteName
- * @param $admin
- * @param $option Array
+ * @param string $siteName
+ * @param string $admin
+ * @param array $option
*/
function __construct( $siteName, $admin = null, array $option = array() ) {
global $wgContLang;
@@ -107,6 +107,15 @@ class CliInstaller extends Installer {
if ( isset( $option['pass'] ) ) {
$this->setVar( '_AdminPassword', $option['pass'] );
}
+
+ // Set up the default skins
+ $skins = $this->findExtensions( 'skins' );
+ $this->setVar( '_Skins', $skins );
+
+ if ( $skins ) {
+ $skinNames = array_map( 'strtolower', $skins );
+ $this->setVar( 'wgDefaultSkin', $this->getDefaultSkin( $skinNames ) );
+ }
}
/**
@@ -138,7 +147,8 @@ class CliInstaller extends Installer {
public function startStage( $step ) {
// Messages: config-install-database, config-install-tables, config-install-interwiki,
- // config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage
+ // config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage,
+ // config-install-extensions
$this->showMessage( "config-install-$step" );
}
@@ -158,7 +168,7 @@ class CliInstaller extends Installer {
}
/**
- * @param $params array
+ * @param array $params
*
* @return string
*/
diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php
index 0110ac52..31b93c88 100644
--- a/includes/installer/DatabaseInstaller.php
+++ b/includes/installer/DatabaseInstaller.php
@@ -163,19 +163,26 @@ abstract class DatabaseInstaller {
}
/**
- * Create database tables from scratch.
+ * Apply a SQL source file to the database as part of running an installation step.
*
+ * @param string $sourceFileMethod
+ * @param string $stepName
+ * @param string $archiveTableMustNotExist
* @return Status
*/
- public function createTables() {
+ private function stepApplySourceFile(
+ $sourceFileMethod,
+ $stepName,
+ $archiveTableMustNotExist = false
+ ) {
$status = $this->getConnection();
if ( !$status->isOK() ) {
return $status;
}
$this->db->selectDB( $this->getVar( 'wgDBname' ) );
- if ( $this->db->tableExists( 'archive', __METHOD__ ) ) {
- $status->warning( 'config-install-tables-exist' );
+ if ( $archiveTableMustNotExist && $this->db->tableExists( 'archive', __METHOD__ ) ) {
+ $status->warning( "config-$stepName-tables-exist" );
$this->enableLB();
return $status;
@@ -184,11 +191,13 @@ abstract class DatabaseInstaller {
$this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
$this->db->begin( __METHOD__ );
- $error = $this->db->sourceFile( $this->db->getSchemaPath() );
+ $error = $this->db->sourceFile(
+ call_user_func( array( $this->db, $sourceFileMethod ) )
+ );
if ( $error !== true ) {
$this->db->reportQueryError( $error, 0, '', __METHOD__ );
$this->db->rollback( __METHOD__ );
- $status->fatal( 'config-install-tables-failed', $error );
+ $status->fatal( "config-$stepName-tables-failed", $error );
} else {
$this->db->commit( __METHOD__ );
}
@@ -201,6 +210,24 @@ abstract class DatabaseInstaller {
}
/**
+ * Create database tables from scratch.
+ *
+ * @return Status
+ */
+ public function createTables() {
+ return $this->stepApplySourceFile( 'getSchemaPath', 'install', true );
+ }
+
+ /**
+ * Insert update keys into table to prevent running unneded updates.
+ *
+ * @return Status
+ */
+ public function insertUpdateKeys() {
+ return $this->stepApplySourceFile( 'getUpdateKeysPath', 'updates', false );
+ }
+
+ /**
* Create the tables for each extension the user enabled
* @return Status
*/
@@ -219,7 +246,7 @@ abstract class DatabaseInstaller {
/**
* Get the DBMS-specific options for LocalSettings.php generation.
*
- * @return String
+ * @return string
*/
abstract public function getLocalSettings();
@@ -243,7 +270,10 @@ abstract class DatabaseInstaller {
if ( $status->isOK() ) {
$status->value->setSchemaVars( $this->getSchemaVars() );
} else {
- throw new MWException( __METHOD__ . ': unexpected DB connection error' );
+ $msg = __METHOD__ . ': unexpected error while establishing'
+ . ' a database connection with message: '
+ . $status->getMessage()->plain();
+ throw new MWException( $msg );
}
}
@@ -257,14 +287,14 @@ abstract class DatabaseInstaller {
if ( !$status->isOK() ) {
throw new MWException( __METHOD__ . ': unexpected DB connection error' );
}
- LBFactory::setInstance( new LBFactory_Single( array(
+ LBFactory::setInstance( new LBFactorySingle( array(
'connection' => $status->value ) ) );
}
/**
* Perform database upgrades
*
- * @return Boolean
+ * @return bool
*/
public function doUpgrade() {
$this->setupSchemaVars();
@@ -311,7 +341,7 @@ abstract class DatabaseInstaller {
/**
* Construct and initialise parent.
* This is typically only called from Installer::getDBInstaller()
- * @param $parent
+ * @param WebInstaller $parent
*/
public function __construct( $parent ) {
$this->parent = $parent;
@@ -321,7 +351,7 @@ abstract class DatabaseInstaller {
* Convenience function.
* Check if a named extension is present.
*
- * @param $name
+ * @param string $name
* @return bool
*/
protected static function checkExtension( $name ) {
@@ -330,7 +360,7 @@ abstract class DatabaseInstaller {
/**
* Get the internationalised name for this DBMS.
- * @return String
+ * @return string
*/
public function getReadableName() {
// Messages: config-type-mysql, config-type-postgres, config-type-sqlite,
@@ -357,8 +387,8 @@ abstract class DatabaseInstaller {
/**
* Get a variable, taking local defaults into account.
- * @param $var string
- * @param $default null
+ * @param string $var
+ * @param mixed|null $default
* @return mixed
*/
public function getVar( $var, $default = null ) {
@@ -375,8 +405,8 @@ abstract class DatabaseInstaller {
/**
* Convenience alias for $this->parent->setVar()
- * @param $name string
- * @param $value mixed
+ * @param string $name
+ * @param mixed $value
*/
public function setVar( $name, $value ) {
$this->parent->setVar( $name, $value );
@@ -385,10 +415,10 @@ abstract class DatabaseInstaller {
/**
* Get a labelled text box to configure a local variable.
*
- * @param $var string
- * @param $label string
- * @param $attribs array
- * @param $helpData string
+ * @param string $var
+ * @param string $label
+ * @param array $attribs
+ * @param string $helpData
* @return string
*/
public function getTextBox( $var, $label, $attribs = array(), $helpData = "" ) {
@@ -412,10 +442,10 @@ abstract class DatabaseInstaller {
* Get a labelled password box to configure a local variable.
* Implements password hiding.
*
- * @param $var string
- * @param $label string
- * @param $attribs array
- * @param $helpData string
+ * @param string $var
+ * @param string $label
+ * @param array $attribs
+ * @param string $helpData
* @return string
*/
public function getPasswordBox( $var, $label, $attribs = array(), $helpData = "" ) {
@@ -438,6 +468,10 @@ abstract class DatabaseInstaller {
/**
* Get a labelled checkbox to configure a local boolean variable.
*
+ * @param string $var
+ * @param string $label
+ * @param array $attribs Optional.
+ * @param string $helpData Optional.
* @return string
*/
public function getCheckBox( $var, $label, $attribs = array(), $helpData = "" ) {
@@ -457,8 +491,7 @@ abstract class DatabaseInstaller {
/**
* Get a set of labelled radio buttons.
*
- * @param $params Array:
- * Parameters are:
+ * @param array $params Parameters are:
* var: The variable to be configured (required)
* label: The message name for the label (required)
* itemLabelPrefix: The message name prefix for the item labels (required)
@@ -478,7 +511,7 @@ abstract class DatabaseInstaller {
* Convenience function to set variables based on form data.
* Assumes that variables containing "password" in the name are (potentially
* fake) passwords.
- * @param $varNames Array
+ * @param array $varNames
* @return array
*/
public function setVarsFromRequest( $varNames ) {
@@ -493,7 +526,7 @@ abstract class DatabaseInstaller {
* Traditionally, this is done by testing for the existence of either
* the revision table or the cur table.
*
- * @return Boolean
+ * @return bool
*/
public function needsUpgrade() {
$status = $this->getConnection();
@@ -512,7 +545,7 @@ abstract class DatabaseInstaller {
/**
* Get a standard install-user fieldset.
*
- * @return String
+ * @return string
*/
public function getInstallUserBox() {
return Html::openElement( 'fieldset' ) .
@@ -544,10 +577,10 @@ abstract class DatabaseInstaller {
/**
* Get a standard web-user fieldset
- * @param string $noCreateMsg Message to display instead of the creation checkbox.
- * Set this to false to show a creation checkbox.
+ * @param string|bool $noCreateMsg Message to display instead of the creation checkbox.
+ * Set this to false to show a creation checkbox (default).
*
- * @return String
+ * @return string
*/
public function getWebUserBox( $noCreateMsg = false ) {
$wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php
index 267b6c5a..193d5920 100644
--- a/includes/installer/DatabaseUpdater.php
+++ b/includes/installer/DatabaseUpdater.php
@@ -72,12 +72,13 @@ abstract class DatabaseUpdater {
'PopulateImageSha1',
'FixExtLinksProtocolRelative',
'PopulateFilearchiveSha1',
+ 'PopulateBacklinkNamespace'
);
/**
* File handle for SQL output.
*
- * @var Filehandle
+ * @var resource
*/
protected $fileHandle = null;
@@ -89,11 +90,16 @@ abstract class DatabaseUpdater {
protected $skipSchema = false;
/**
+ * Hold the value of $wgContentHandlerUseDB during the upgrade.
+ */
+ protected $holdContentHandlerUseDB = true;
+
+ /**
* Constructor
*
- * @param $db DatabaseBase object to perform updates on
+ * @param DatabaseBase $db To perform updates on
* @param bool $shared Whether to perform updates on shared tables
- * @param $maintenance Maintenance Maintenance object which created us
+ * @param Maintenance $maintenance Maintenance object which created us
*/
protected function __construct( DatabaseBase &$db, $shared, Maintenance $maintenance = null ) {
$this->db = $db;
@@ -150,10 +156,11 @@ abstract class DatabaseUpdater {
}
/**
- * @throws MWException
* @param DatabaseBase $db
* @param bool $shared
- * @param null $maintenance
+ * @param Maintenance $maintenance
+ *
+ * @throws MWException
* @return DatabaseUpdater
*/
public static function newForDB( &$db, $shared = false, $maintenance = null ) {
@@ -199,7 +206,7 @@ abstract class DatabaseUpdater {
*
* @since 1.17
*
- * @param array $update the update to run. Format is the following:
+ * @param array $update The update to run. Format is the following:
* first item is the callback function, it also can be a
* simple string with the name of a function in this class,
* following elements are parameters to the function.
@@ -226,9 +233,9 @@ abstract class DatabaseUpdater {
/**
* @since 1.19
*
- * @param $tableName string
- * @param $indexName string
- * @param $sqlPath string
+ * @param string $tableName
+ * @param string $indexName
+ * @param string $sqlPath
*/
public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
$this->extensionUpdates[] = array( 'addIndex', $tableName, $indexName, $sqlPath, true );
@@ -238,9 +245,9 @@ abstract class DatabaseUpdater {
*
* @since 1.19
*
- * @param $tableName string
- * @param $columnName string
- * @param $sqlPath string
+ * @param string $tableName
+ * @param string $columnName
+ * @param string $sqlPath
*/
public function addExtensionField( $tableName, $columnName, $sqlPath ) {
$this->extensionUpdates[] = array( 'addField', $tableName, $columnName, $sqlPath, true );
@@ -250,9 +257,9 @@ abstract class DatabaseUpdater {
*
* @since 1.20
*
- * @param $tableName string
- * @param $columnName string
- * @param $sqlPath string
+ * @param string $tableName
+ * @param string $columnName
+ * @param string $sqlPath
*/
public function dropExtensionField( $tableName, $columnName, $sqlPath ) {
$this->extensionUpdates[] = array( 'dropField', $tableName, $columnName, $sqlPath, true );
@@ -275,8 +282,8 @@ abstract class DatabaseUpdater {
*
* @since 1.20
*
- * @param $tableName string
- * @param $sqlPath string
+ * @param string $tableName
+ * @param string $sqlPath
*/
public function dropExtensionTable( $tableName, $sqlPath ) {
$this->extensionUpdates[] = array( 'dropTable', $tableName, $sqlPath, true );
@@ -290,9 +297,9 @@ abstract class DatabaseUpdater {
* @param string $tableName The table name
* @param string $oldIndexName The old index name
* @param string $newIndexName The new index name
- * @param $skipBothIndexExistWarning Boolean: Whether to warn if both the old
- * and the new indexes exist. [facultative; by default, false]
* @param string $sqlPath The path to the SQL change path
+ * @param bool $skipBothIndexExistWarning Whether to warn if both the old
+ * and the new indexes exist. [facultative; by default, false]
*/
public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
$sqlPath, $skipBothIndexExistWarning = false
@@ -323,7 +330,7 @@ abstract class DatabaseUpdater {
*
* @since 1.20
*
- * @param $tableName string
+ * @param string $tableName
* @return bool
*/
public function tableExists( $tableName ) {
@@ -346,7 +353,7 @@ abstract class DatabaseUpdater {
/**
* Get the list of extension-defined updates
*
- * @return Array
+ * @return array
*/
protected function getExtensionUpdates() {
return $this->extensionUpdates;
@@ -365,6 +372,7 @@ abstract class DatabaseUpdater {
* @since 1.21
*
* Writes the schema updates desired to a file for the DB Admin to run.
+ * @param array $schemaUpdate
*/
private function writeSchemaUpdateFile( $schemaUpdate = array() ) {
$updates = $this->updatesSkipped;
@@ -383,7 +391,7 @@ abstract class DatabaseUpdater {
/**
* Do all the updates
*
- * @param array $what what updates to perform
+ * @param array $what What updates to perform
*/
public function doUpdates( $what = array( 'core', 'extensions', 'stats' ) ) {
global $wgVersion;
@@ -417,9 +425,8 @@ abstract class DatabaseUpdater {
/**
* Helper function for doUpdates()
*
- * @param array $updates of updates to run
- * @param $passSelf Boolean: whether to pass this object we calling external
- * functions
+ * @param array $updates Array of updates to run
+ * @param bool $passSelf Whether to pass this object we calling external functions
*/
private function runUpdates( array $updates, $passSelf ) {
$updatesDone = array();
@@ -445,8 +452,8 @@ abstract class DatabaseUpdater {
}
/**
- * @param $version
- * @param $updates array
+ * @param string $version
+ * @param array $updates
*/
protected function setAppliedUpdates( $version, $updates = array() ) {
$this->db->clearFlag( DBO_DDLMODE );
@@ -465,13 +472,13 @@ abstract class DatabaseUpdater {
* Obviously, only use this for updates that occur after the updatelog table was
* created!
* @param string $key Name of the key to check for
- *
* @return bool
*/
public function updateRowExists( $key ) {
$row = $this->db->selectRow(
'updatelog',
- '1',
+ # Bug 65813
+ '1 AS X',
array( 'ul_key' => $key ),
__METHOD__
);
@@ -484,7 +491,7 @@ abstract class DatabaseUpdater {
* Obviously, only use this for updates that occur after the updatelog table was
* created!
* @param string $key Name of key to insert
- * @param string $val [optional] value to insert along with the key
+ * @param string $val [optional] Value to insert along with the key
*/
public function insertUpdateRow( $key, $val = null ) {
$this->db->clearFlag( DBO_DDLMODE );
@@ -502,7 +509,7 @@ abstract class DatabaseUpdater {
* class does). Pre-1.17 wikis won't have this column, and really old wikis
* might not even have updatelog at all
*
- * @return boolean
+ * @return bool
*/
protected function canUseNewUpdatelog() {
return $this->db->tableExists( 'updatelog', __METHOD__ ) &&
@@ -514,7 +521,7 @@ abstract class DatabaseUpdater {
* Updates will be prevented if the table is a shared table and it is not
* specified to run updates on shared tables.
*
- * @param string $name table name
+ * @param string $name Table name
* @return bool
*/
protected function doTable( $name ) {
@@ -526,7 +533,12 @@ abstract class DatabaseUpdater {
return true;
}
- return !in_array( $name, $wgSharedTables );
+ if ( in_array( $name, $wgSharedTables ) ) {
+ $this->output( "...skipping update to shared table $name.\n" );
+ return false;
+ } else {
+ return true;
+ }
}
/**
@@ -579,7 +591,7 @@ abstract class DatabaseUpdater {
* 1.13...) with the values being arrays of updates, identical to how
* updaters.inc did it (for now)
*
- * @return Array
+ * @return array
*/
abstract protected function getCoreUpdateList();
@@ -600,8 +612,8 @@ abstract class DatabaseUpdater {
*
* This is used as a callback for for sourceLine().
*
- * @param string $line text to append to the file
- * @return Boolean false to skip actually executing the file
+ * @param string $line Text to append to the file
+ * @return bool False to skip actually executing the file
* @throws MWException
*/
public function appendLine( $line ) {
@@ -617,9 +629,9 @@ abstract class DatabaseUpdater {
* Applies a SQL patch
*
* @param string $path Path to the patch file
- * @param $isFullPath Boolean Whether to treat $path as a relative or not
+ * @param bool $isFullPath Whether to treat $path as a relative or not
* @param string $msg Description of the patch
- * @return boolean false if patch is skipped.
+ * @return bool False if patch is skipped.
*/
protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
if ( $msg === null ) {
@@ -651,8 +663,8 @@ abstract class DatabaseUpdater {
*
* @param string $name Name of the new table
* @param string $patch Path to the patch file
- * @param $fullpath Boolean Whether to treat $patch path as a relative or not
- * @return Boolean false if this was skipped because schema changes are skipped
+ * @param bool $fullpath Whether to treat $patch path as a relative or not
+ * @return bool False if this was skipped because schema changes are skipped
*/
protected function addTable( $name, $patch, $fullpath = false ) {
if ( !$this->doTable( $name ) ) {
@@ -674,8 +686,8 @@ abstract class DatabaseUpdater {
* @param string $table Name of the table to modify
* @param string $field Name of the new field
* @param string $patch Path to the patch file
- * @param $fullpath Boolean Whether to treat $patch path as a relative or not
- * @return Boolean false if this was skipped because schema changes are skipped
+ * @param bool $fullpath Whether to treat $patch path as a relative or not
+ * @return bool False if this was skipped because schema changes are skipped
*/
protected function addField( $table, $field, $patch, $fullpath = false ) {
if ( !$this->doTable( $table ) ) {
@@ -699,8 +711,8 @@ abstract class DatabaseUpdater {
* @param string $table Name of the table to modify
* @param string $index Name of the new index
* @param string $patch Path to the patch file
- * @param $fullpath Boolean Whether to treat $patch path as a relative or not
- * @return Boolean false if this was skipped because schema changes are skipped
+ * @param bool $fullpath Whether to treat $patch path as a relative or not
+ * @return bool False if this was skipped because schema changes are skipped
*/
protected function addIndex( $table, $index, $patch, $fullpath = false ) {
if ( !$this->doTable( $table ) ) {
@@ -724,8 +736,8 @@ abstract class DatabaseUpdater {
* @param string $table Name of the table to modify
* @param string $field Name of the old field
* @param string $patch Path to the patch file
- * @param $fullpath Boolean Whether to treat $patch path as a relative or not
- * @return Boolean false if this was skipped because schema changes are skipped
+ * @param bool $fullpath Whether to treat $patch path as a relative or not
+ * @return bool False if this was skipped because schema changes are skipped
*/
protected function dropField( $table, $field, $patch, $fullpath = false ) {
if ( !$this->doTable( $table ) ) {
@@ -747,8 +759,8 @@ abstract class DatabaseUpdater {
* @param string $table Name of the table to modify
* @param string $index Name of the index
* @param string $patch Path to the patch file
- * @param $fullpath Boolean: Whether to treat $patch path as a relative or not
- * @return Boolean false if this was skipped because schema changes are skipped
+ * @param bool $fullpath Whether to treat $patch path as a relative or not
+ * @return bool False if this was skipped because schema changes are skipped
*/
protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
if ( !$this->doTable( $table ) ) {
@@ -770,11 +782,11 @@ abstract class DatabaseUpdater {
* @param string $table Name of the table to modify
* @param string $oldIndex Old name of the index
* @param string $newIndex New name of the index
- * @param $skipBothIndexExistWarning Boolean: Whether to warn if both the
+ * @param bool $skipBothIndexExistWarning Whether to warn if both the
* old and the new indexes exist.
* @param string $patch Path to the patch file
- * @param $fullpath Boolean: Whether to treat $patch path as a relative or not
- * @return Boolean false if this was skipped because schema changes are skipped
+ * @param bool $fullpath Whether to treat $patch path as a relative or not
+ * @return bool False if this was skipped because schema changes are skipped
*/
protected function renameIndex( $table, $oldIndex, $newIndex,
$skipBothIndexExistWarning, $patch, $fullpath = false
@@ -825,10 +837,10 @@ abstract class DatabaseUpdater {
*
* Public @since 1.20
*
- * @param $table string
- * @param $patch string|false
- * @param $fullpath bool
- * @return Boolean false if this was skipped because schema changes are skipped
+ * @param string $table Table to drop.
+ * @param string|bool $patch String of patch file that will drop the table. Default: false.
+ * @param bool $fullpath Whether $patch is a full path. Default: false.
+ * @return bool False if this was skipped because schema changes are skipped
*/
public function dropTable( $table, $patch = false, $fullpath = false ) {
if ( !$this->doTable( $table ) ) {
@@ -855,11 +867,11 @@ abstract class DatabaseUpdater {
/**
* Modify an existing field
*
- * @param string $table name of the table to which the field belongs
- * @param string $field name of the field to modify
- * @param string $patch path to the patch file
- * @param $fullpath Boolean: whether to treat $patch path as a relative or not
- * @return Boolean false if this was skipped because schema changes are skipped
+ * @param string $table Name of the table to which the field belongs
+ * @param string $field Name of the field to modify
+ * @param string $patch Path to the patch file
+ * @param bool $fullpath Whether to treat $patch path as a relative or not
+ * @return bool False if this was skipped because schema changes are skipped
*/
public function modifyField( $table, $field, $patch, $fullpath = false ) {
if ( !$this->doTable( $table ) ) {
@@ -895,7 +907,7 @@ abstract class DatabaseUpdater {
if ( $wgLocalisationCacheConf['manualRecache'] ) {
$this->rebuildLocalisationCache();
}
- MessageBlobStore::clear();
+ MessageBlobStore::getInstance()->clear();
$this->output( "done.\n" );
}
@@ -972,6 +984,7 @@ abstract class DatabaseUpdater {
/**
* Updates the timestamps in the transcache table
+ * @return bool
*/
protected function doUpdateTranscacheField() {
if ( $this->updateRowExists( 'convert transcache field' ) ) {
@@ -1033,4 +1046,30 @@ abstract class DatabaseUpdater {
$cl->execute();
$this->output( "done.\n" );
}
+
+ /**
+ * Turns off content handler fields during parts of the upgrade
+ * where they aren't available.
+ */
+ protected function disableContentHandlerUseDB() {
+ global $wgContentHandlerUseDB;
+
+ if ( $wgContentHandlerUseDB ) {
+ $this->output( "Turning off Content Handler DB fields for this part of upgrade.\n" );
+ $this->holdContentHandlerUseDB = $wgContentHandlerUseDB;
+ $wgContentHandlerUseDB = false;
+ }
+ }
+
+ /**
+ * Turns content handler fields back on.
+ */
+ protected function enableContentHandlerUseDB() {
+ global $wgContentHandlerUseDB;
+
+ if ( $this->holdContentHandlerUseDB ) {
+ $this->output( "Content Handler DB fields should be usable now.\n" );
+ $wgContentHandlerUseDB = $this->holdContentHandlerUseDB;
+ }
+ }
}
diff --git a/includes/installer/InstallDocFormatter.php b/includes/installer/InstallDocFormatter.php
index 6d3819cd..3250ff8a 100644
--- a/includes/installer/InstallDocFormatter.php
+++ b/includes/installer/InstallDocFormatter.php
@@ -62,7 +62,7 @@ class InstallDocFormatter {
}
protected function replaceConfigLinks( $matches ) {
- return '<span class="config-plainlink">[http://www.mediawiki.org/wiki/Manual:' .
+ return '<span class="config-plainlink">[https://www.mediawiki.org/wiki/Manual:' .
$matches[1] . ' ' . $matches[1] . ']</span>';
}
}
diff --git a/includes/installer/Installer.i18n.php b/includes/installer/Installer.i18n.php
deleted file mode 100644
index a9971b4f..00000000
--- a/includes/installer/Installer.i18n.php
+++ /dev/null
@@ -1,21554 +0,0 @@
-<?php
-/**
- * Internationalization file for the install/upgrade process. None of the
- * messages used here are loaded during normal operations, only during
- * install and upgrade. So you should not put normal messages here.
- *
- * @file
- * @ingroup Deployment
- */
-
-$messages = array();
-
-/** English */
-$messages['en'] = array(
- 'config-desc' => 'The installer for MediaWiki',
- 'config-title' => 'MediaWiki $1 installation',
- 'config-information' => 'Information',
- 'config-localsettings-upgrade' => "A <code>LocalSettings.php</code> file has been detected.
-To upgrade this installation, please enter the value of <code>\$wgUpgradeKey</code> in the box below.
-You will find it in <code>LocalSettings.php</code>.",
- 'config-localsettings-cli-upgrade' => 'A <code>LocalSettings.php</code> file has been detected.
-To upgrade this installation, please run <code>update.php</code> instead',
- 'config-localsettings-key' => 'Upgrade key:',
- 'config-localsettings-badkey' => 'The key you provided is incorrect.',
- 'config-upgrade-key-missing' => 'An existing installation of MediaWiki has been detected.
-To upgrade this installation, please put the following line at the bottom of your <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'The existing <code>LocalSettings.php</code> appears to be incomplete.
-The $1 variable is not set.
-Please change <code>LocalSettings.php</code> so that this variable is set, and click "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'An error was encountered when connecting to the database using the settings specified in <code>LocalSettings.php</code> or <code>AdminSettings.php</code>. Please fix these settings and try again.
-
-$1',
- 'config-session-error' => 'Error starting session: $1',
- 'config-session-expired' => 'Your session data seems to have expired.
-Sessions are configured for a lifetime of $1.
-You can increase this by setting <code>session.gc_maxlifetime</code> in php.ini.
-Restart the installation process.',
- 'config-no-session' => 'Your session data was lost!
-Check your php.ini and make sure <code>session.save_path</code> is set to an appropriate directory.',
- 'config-your-language' => 'Your language:',
- 'config-your-language-help' => 'Select a language to use during the installation process.',
- 'config-wiki-language' => 'Wiki language:',
- 'config-wiki-language-help' => 'Select the language that the wiki will predominantly be written in.',
- 'config-back' => '← Back',
- 'config-continue' => 'Continue →',
- 'config-page-language' => 'Language',
- 'config-page-welcome' => 'Welcome to MediaWiki!',
- 'config-page-dbconnect' => 'Connect to database',
- 'config-page-upgrade' => 'Upgrade existing installation',
- 'config-page-dbsettings' => 'Database settings',
- 'config-page-name' => 'Name',
- 'config-page-options' => 'Options',
- 'config-page-install' => 'Install',
- 'config-page-complete' => 'Complete!',
- 'config-page-restart' => 'Restart installation',
- 'config-page-readme' => 'Read me',
- 'config-page-releasenotes' => 'Release notes',
- 'config-page-copying' => 'Copying',
- 'config-page-upgradedoc' => 'Upgrading',
- 'config-page-existingwiki' => 'Existing wiki',
- 'config-help-restart' => 'Do you want to clear all saved data that you have entered and restart the installation process?',
- 'config-restart' => 'Yes, restart it',
- 'config-welcome' => "=== Environmental checks ===
-Basic checks will now be performed to see if this environment is suitable for MediaWiki installation.
-Remember to include this information if you seek support on how to complete the installation.",
- 'config-copyright' => "=== Copyright and Terms ===
-
-$1
-
-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 <doclink href=Copying>a copy of the GNU General Public License</doclink> along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. or [http://www.gnu.org/copyleft/gpl.html read it online].",
- 'config-sidebar' => "* [//www.mediawiki.org MediaWiki home]
-* [//www.mediawiki.org/wiki/Help:Contents User's Guide]
-* [//www.mediawiki.org/wiki/Manual:Contents Administrator's Guide]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>Read me</doclink>
-* <doclink href=ReleaseNotes>Release notes</doclink>
-* <doclink href=Copying>Copying</doclink>
-* <doclink href=UpgradeDoc>Upgrading</doclink>",
- 'config-env-good' => 'The environment has been checked.
-You can install MediaWiki.',
- 'config-env-bad' => 'The environment has been checked.
-You cannot install MediaWiki.',
- 'config-env-php' => 'PHP $1 is installed.',
- 'config-env-php-toolow' => 'PHP $1 is installed.
-However, MediaWiki requires PHP $2 or higher.',
- 'config-unicode-using-utf8' => 'Using Brion Vibber\'s utf8_normalize.so for Unicode normalization.',
- 'config-unicode-using-intl' => 'Using the [http://pecl.php.net/intl intl PECL extension] for Unicode normalization.',
- 'config-unicode-pure-php-warning' => "'''Warning:''' The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalization, falling back to slow pure-PHP implementation.
-If you run a high-traffic site, you should read a little on [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization].",
- 'config-unicode-update-warning' => "'''Warning:''' The installed version of the Unicode normalization wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.
-You should [//www.mediawiki.org/wiki/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
- 'config-no-db' => 'Could not find a suitable database driver! You need to install a database driver for PHP.
-The following database types are supported: $1.
-
-If you compiled PHP yourself, reconfigure it with a database client enabled, for example, using <code>./configure --with-mysqli</code>.
-If you installed PHP from a Debian or Ubuntu package, then you also need to install, for example, the <code>php5-mysql</code> package.',
- 'config-outdated-sqlite' => "'''Warning:''' you have SQLite $1, which is lower than minimum required version $2. SQLite will be unavailable.",
- 'config-no-fts3' => "'''Warning:''' SQLite is compiled without the [//sqlite.org/fts3.html FTS3 module], search features will be unavailable on this backend.",
- 'config-register-globals' => "'''Warning: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.'''
-'''Disable it if you can.'''
-MediaWiki will work, but your server is exposed to potential security vulnerabilities.",
- 'config-magic-quotes-runtime' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is active!'''
-This option corrupts data input unpredictably.
-You cannot install or use MediaWiki unless this option is disabled.",
- 'config-magic-quotes-sybase' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] is active!'''
-This option corrupts data input unpredictably.
-You cannot install or use MediaWiki unless this option is disabled.",
- 'config-mbstring' => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is active!'''
-This option causes errors and may corrupt data unpredictably.
-You cannot install or use MediaWiki unless this option is disabled.",
- 'config-ze1' => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] is active!'''
-This option causes horrible bugs with MediaWiki.
-You cannot install or use MediaWiki unless this option is disabled.",
- 'config-safe-mode' => "'''Warning:''' PHP's [http://www.php.net/features.safe-mode safe mode] is active.
-It may cause problems, particularly if using file uploads and <code>math</code> support.",
- 'config-xml-bad' => "PHP's XML module is missing.
-MediaWiki requires functions in this module and will not work in this configuration.
-If you're running Mandrake, install the php-xml package.",
- 'config-pcre-old' => "'''Fatal:''' PCRE $1 or later is required.
-Your PHP binary is linked with PCRE $2.
-[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE More information].",
- 'config-pcre-no-utf8' => "'''Fatal:''' PHP's PCRE module seems to be compiled without PCRE_UTF8 support.
-MediaWiki requires UTF-8 support to function correctly.",
- 'config-memory-raised' => "PHP's <code>memory_limit</code> is $1, raised to $2.",
- 'config-memory-bad' => "'''Warning:''' PHP's <code>memory_limit</code> is $1.
-This is probably too low.
-The installation may fail!",
- 'config-ctype' => "'''Fatal:''' PHP must be compiled with support for the [http://www.php.net/manual/en/ctype.installation.php Ctype extension].",
- 'config-json' => "'''Fatal:''' PHP was compiled without JSON support.
-You must install either the PHP JSON extension or the [http://pecl.php.net/package/jsonc PECL jsonc] extension before installing MediaWiki.
-* The PHP extension is included in Red Hat Enterprise Linux (CentOS) 5 and 6, though must be enabled in <code>/etc/php.ini</code> or <code>/etc/php.d/json.ini</code>.
-* Some Linux distributions released after May 2013 omit the PHP extension, instead packaging the PECL extension as <code>php5-json</code> or <code>php-pecl-jsonc</code>.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] is installed',
- 'config-apc' => '[http://www.php.net/apc APC] is installed',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] is installed',
- 'config-no-cache' => "'''Warning:''' Could not find [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].
-Object caching is not enabled.",
- 'config-mod-security' => "'''Warning:''' Your web server has [http://modsecurity.org/ mod_security] enabled. If misconfigured, it can cause problems for MediaWiki or other software that allows users to post arbitrary content.
-Refer to [http://modsecurity.org/documentation/ mod_security documentation] or contact your host's support if you encounter random errors.",
- 'config-diff3-bad' => 'GNU diff3 not found.',
- 'config-git' => 'Found the Git version control software: <code>$1</code>.',
- 'config-git-bad' => 'Git version control software not found.',
- 'config-imagemagick' => 'Found ImageMagick: <code>$1</code>.
-Image thumbnailing will be enabled if you enable uploads.',
- 'config-gd' => 'Found GD graphics library built-in.
-Image thumbnailing will be enabled if you enable uploads.',
- 'config-no-scaling' => 'Could not find GD library or ImageMagick.
-Image thumbnailing will be disabled.',
- 'config-no-uri' => "'''Error:''' Could not determine the current URI.
-Installation aborted.",
- 'config-no-cli-uri' => "'''Warning:''' No --scriptpath specified, using default: <code>$1</code>.",
- 'config-using-server' => 'Using server name "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Using server URL "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Warning:''' Your default directory for uploads <code>$1</code> is vulnerable to arbitrary scripts execution.
-Although MediaWiki checks all uploaded files for security threats, it is highly recommended to [//www.mediawiki.org/wiki/Manual:Security#Upload_security close this security vulnerability] before enabling uploads.",
- 'config-no-cli-uploads-check' => "'''Warning:''' Your default directory for uploads (<code>$1</code>) is not checked for vulnerability
-to arbitrary script execution during the CLI install.",
- 'config-brokenlibxml' => 'Your system has a combination of PHP and libxml2 versions which is buggy and can cause hidden data corruption in MediaWiki and other web applications.
-Upgrade to PHP 5.2.9 or later and libxml2 2.7.3 or later ([//bugs.php.net/bug.php?id=45996 bug filed with PHP]).
-Installation aborted.',
- 'config-using531' => 'MediaWiki cannot be used with PHP $1 due to a bug involving reference parameters to <code>__call()</code>.
-Upgrade to PHP 5.3.2 or higher, or downgrade to PHP 5.3.0 to resolve this.
-Installation aborted.',
- 'config-suhosin-max-value-length' => "Suhosin is installed and limits the GET parameter <code>length</code> to $1 bytes.
-MediaWiki's ResourceLoader component will work around this limit, but that will degrade performance.
-If at all possible, you should set <code>suhosin.get.max_value_length</code> to 1024 or higher in <code>php.ini</code>, and set <code>\$wgResourceLoaderMaxQueryLength</code> to the same value in <code>LocalSettings.php</code>.",
- 'config-db-type' => 'Database type:',
- 'config-db-host' => 'Database host:',
- 'config-db-host-help' => 'If your database server is on different server, enter the host name or IP address here.
-
-If you are using shared web hosting, your hosting provider should give you the correct host name in their documentation.
-
-If you are installing on a Windows server and using MySQL, using "localhost" may not work for the server name. If it does not, try "127.0.0.1" for the local IP address.
-
-If you are using PostgreSQL, leave this field blank to connect via a Unix socket.',
- 'config-db-host-oracle' => 'Database TNS:',
- 'config-db-host-oracle-help' => 'Enter a valid [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; a tnsnames.ora file must be visible to this installation.<br />If you are using client libraries 10g or newer you can also use the [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] naming method.',
- 'config-db-wiki-settings' => 'Identify this wiki',
- 'config-db-name' => 'Database name:',
- 'config-db-name-help' => 'Choose a name that identifies your wiki.
-It should not contain spaces.
-
-If you are using shared web hosting, your hosting provider will either give you a specific database name to use or let you create databases via a control panel.',
- 'config-db-name-oracle' => 'Database schema:',
- 'config-db-account-oracle-warn' => "There are three supported scenarios for installing Oracle as database backend:
-
-If you wish to create database account as part of the installation process, please supply an account with SYSDBA role as database account for installation and specify the desired credentials for the web-access account, otherwise you can either create the web-access account manually and supply only that account (if it has required permissions to create the schema objects) or supply two different accounts, one with create privileges and a restricted one for web access.
-
-Script for creating an account with required privileges can be found in \"maintenance/oracle/\" directory of this installation. Keep in mind that using a restricted account will disable all maintenance capabilities with the default account.",
- 'config-db-install-account' => 'User account for installation',
- 'config-db-username' => 'Database username:',
- 'config-db-password' => 'Database password:',
- 'config-db-password-empty' => 'Please enter a password for the new database user: $1.
-While it may be possible to create users with no passwords, it is not secure.',
- 'config-db-install-username' => 'Enter the username that will be used to connect to the database during the installation process.
-This is not the username of the MediaWiki account; this is the username for your database.',
- 'config-db-install-password' => 'Enter the password that will be used to connect to the database during the installation process.
-This is not the password for the MediaWiki account; this is the password for your database.',
- 'config-db-install-help' => 'Enter the username and password that will be used to connect to the database during the installation process.',
- 'config-db-account-lock' => 'Use the same username and password during normal operation',
- 'config-db-wiki-account' => 'User account for normal operation',
- 'config-db-wiki-help' => 'Enter the username and password that will be used to connect to the database during normal wiki operation.
-If the account does not exist, and the installation account has sufficient privileges, this user account will be created with the minimum privileges required to operate the wiki.',
- 'config-db-prefix' => 'Database table prefix:',
- 'config-db-prefix-help' => 'If you need to share one database between multiple wikis, or between MediaWiki and another web application, you may choose to add a prefix to all the table names to avoid conflicts.
-Do not use spaces.
-
-This field is usually left empty.',
- 'config-db-charset' => 'Database character set',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 backwards-compatible UTF-8',
- 'config-charset-help' => "'''Warning:''' If you use '''backwards-compatible UTF-8''' on MySQL 4.1+, and subsequently back up the database with <code>mysqldump</code>, it may destroy all non-ASCII characters, irreversibly corrupting your backups!
-
-In '''binary mode''', MediaWiki stores UTF-8 text to the database in binary fields.
-This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters.
-In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately,
-but it will not let you store characters above the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-mysql-old' => 'MySQL $1 or later is required, you have $2.',
- 'config-db-port' => 'Database port:',
- 'config-db-schema' => 'Schema for MediaWiki:',
- 'config-db-schema-help' => 'This schema will usually be fine.
-Only change it if you know you need to.',
- 'config-pg-test-error' => "Cannot connect to database '''$1''': $2",
- 'config-sqlite-dir' => 'SQLite data directory:',
- 'config-sqlite-dir-help' => "SQLite stores all data in a single file.
-
-The directory you provide must be writable by the webserver during installation.
-
-It should '''not''' be accessible via the web, this is why we're not putting it where your PHP files are.
-
-The installer will write a <code>.htaccess</code> file along with it, but if that fails someone can gain access to your raw database.
-That includes raw user data (email addresses, hashed passwords) as well as deleted revisions and other restricted data on the wiki.
-
-Consider putting the database somewhere else altogether, for example in <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Default tablespace:',
- 'config-oracle-temp-ts' => 'Temporary tablespace:',
- 'config-type-mysql' => 'MySQL (or compatible)',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki supports the following database systems:
-
-$1
-
-If 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-mysql-url}} MySQL] is the primary target for MediaWiki and is best supported. MediaWiki also works with [{{int:version-db-mariadb-url}} MariaDB] and [{{int:version-db-percona-url}} Percona Server], which are MySQL compatible. ([http://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. There may be some minor outstanding bugs, and it is not recommended for use in a production environment. ([http://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. ([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] is a commercial enterprise database. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
- 'config-header-mysql' => 'MySQL settings',
- 'config-header-postgres' => 'PostgreSQL settings',
- 'config-header-sqlite' => 'SQLite settings',
- 'config-header-oracle' => 'Oracle settings',
- 'config-invalid-db-type' => 'Invalid database type',
- 'config-missing-db-name' => 'You must enter a value for "Database name"',
- 'config-missing-db-host' => 'You must enter a value for "Database host"',
- 'config-missing-db-server-oracle' => 'You must enter a value for "Database TNS"',
- 'config-invalid-db-server-oracle' => 'Invalid database TNS "$1".
-Use either "TNS Name" or an "Easy Connect" string ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods])',
- 'config-invalid-db-name' => 'Invalid database name "$1".
-Use only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and hyphens (-).',
- 'config-invalid-db-prefix' => 'Invalid database prefix "$1".
-Use only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and hyphens (-).',
- 'config-connection-error' => '$1.
-
-Check the host, username and password and try again.',
- 'config-invalid-schema' => 'Invalid schema for MediaWiki "$1".
-Use only ASCII letters (a-z, A-Z), numbers (0-9) and underscores (_).',
- 'config-db-sys-create-oracle' => 'Installer only supports using a SYSDBA account for creating a new account.',
- 'config-db-sys-user-exists-oracle' => 'User account "$1" already exists. SYSDBA can only be used for creating of a new account!',
- 'config-postgres-old' => 'PostgreSQL $1 or later is required, you have $2.',
- 'config-sqlite-name-help' => 'Choose a name that identifies your wiki.
-Do not use spaces or hyphens.
-This will be used for the SQLite data file name.',
- 'config-sqlite-parent-unwritable-group' => 'Cannot create the data directory <code><nowiki>$1</nowiki></code>, because the parent directory <code><nowiki>$2</nowiki></code> is not writable by the webserver.
-
-The installer has determined the user your webserver is running as.
-Make the <code><nowiki>$3</nowiki></code> directory writable by it to continue.
-On a Unix/Linux system do:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Cannot create the data directory <code><nowiki>$1</nowiki></code>, because the parent directory <code><nowiki>$2</nowiki></code> is not writable by the webserver.
-
-The installer could not determine the user your webserver is running as.
-Make the <code><nowiki>$3</nowiki></code> directory globally writable by it (and others!) to continue.
-On a Unix/Linux system do:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Error creating the data directory "$1".
-Check the location and try again.',
- 'config-sqlite-dir-unwritable' => 'Unable to write to the directory "$1".
-Change its permissions so that the webserver can write to it, and try again.',
- 'config-sqlite-connection-error' => '$1.
-
-Check the data directory and database name below and try again.',
- 'config-sqlite-readonly' => 'The file <code>$1</code> is not writeable.',
- 'config-sqlite-cant-create-db' => 'Could not create database file <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP is missing FTS3 support, downgrading tables',
- 'config-can-upgrade' => "There are MediaWiki tables in this database.
-To upgrade them to MediaWiki $1, click '''Continue'''.",
- 'config-upgrade-done' => "Upgrade complete.
-
-You can now [$1 start using your wiki].
-
-If you want to regenerate your <code>LocalSettings.php</code> file, click the button below.
-This is '''not recommended''' unless you are having problems with your wiki.",
- 'config-upgrade-done-no-regenerate' => "Upgrade complete.
-
-You can now [$1 start using your wiki].",
- 'config-regenerate' => 'Regenerate LocalSettings.php →',
- 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> query failed!',
- 'config-unknown-collation' => "'''Warning:''' Database is using unrecognized collation.",
- 'config-db-web-account' => 'Database account for web access',
- 'config-db-web-help' => 'Select the username and password that the web server will use to connect to the database server, during ordinary operation of the wiki.',
- 'config-db-web-account-same' => 'Use the same account as for installation',
- 'config-db-web-create' => 'Create the account if it does not already exist',
- 'config-db-web-no-create-privs' => 'The account you specified for installation does not have enough privileges to create an account.
-The account you specify here must already exist.',
- 'config-mysql-engine' => 'Storage engine:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Warning:''' You have selected MyISAM as storage engine for MySQL, which is not recommended for use with MediaWiki, because:
-* it barely supports concurrency due to table locking
-* it is more prone to corruption than other engines
-* the MediaWiki codebase does not always handle MyISAM as it should
-
-If your MySQL installation supports InnoDB, it is highly recommended that you choose that instead.
-If your MySQL installation does not support InnoDB, maybe it's time for an upgrade.",
- 'config-mysql-only-myisam-dep' => "'''Warning:''' MyISAM is the only available storage engine for MySQL, which is not recommended for use with MediaWiki, because:
-* it barely supports concurrency due to table locking
-* it is more prone to corruption than other engines
-* the MediaWiki codebase does not always handle MyISAM as it should
-
-Your MySQL installation does not support InnoDB, maybe it's time for an upgrade.",
- 'config-mysql-engine-help' => "'''InnoDB''' is almost always the best option, since it has good concurrency support.
-
-'''MyISAM''' may be faster in single-user or read-only installations.
-MyISAM databases tend to get corrupted more often than InnoDB databases.",
- 'config-mysql-charset' => 'Database character set:',
- 'config-mysql-binary' => 'Binary',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "In '''binary mode''', MediaWiki stores UTF-8 text to the database in binary fields.
-This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters.
-
-In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately, but it will not let you store characters above the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
-
- 'config-site-name' => 'Name of wiki:',
- 'config-site-name-help' => "This will appear in the title bar of the browser and in various other places.",
- 'config-site-name-blank' => 'Enter a site name.',
- 'config-project-namespace' => 'Project namespace:',
- 'config-ns-generic' => 'Project',
- 'config-ns-site-name' => 'Same as the wiki name: $1',
- 'config-ns-other' => 'Other (specify)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => 'Following Wikipedia\'s example, many wikis keep their policy pages separate from their content pages, in a "\'\'\'project namespace\'\'\'".
-All page titles in this namespace start with a certain prefix, which you can specify here.
-Usually, this prefix is derived from the name of the wiki, but it cannot contain punctuation characters such as "#" or ":".',
- 'config-ns-invalid' => 'The specified namespace "<nowiki>$1</nowiki>" is invalid.
-Specify a different project namespace.',
- 'config-ns-conflict' => 'The specified namespace "<nowiki>$1</nowiki>" conflicts with a default MediaWiki namespace.
-Specify a different project namespace.',
- 'config-admin-box' => 'Administrator account',
- 'config-admin-name' => 'Your name:',
- 'config-admin-password' => 'Password:',
- 'config-admin-password-confirm' => 'Password again:',
- 'config-admin-help' => 'Enter your preferred username here, for example "Joe Bloggs".
-This is the name you will use to log in to the wiki.',
- 'config-admin-name-blank' => 'Enter an administrator username.',
- 'config-admin-name-invalid' => 'The specified username "<nowiki>$1</nowiki>" is invalid.
-Specify a different username.',
- 'config-admin-password-blank' => 'Enter a password for the administrator account.',
- 'config-admin-password-same' => 'The password must not be the same as the username.',
- 'config-admin-password-mismatch' => 'The two passwords you entered do not match.',
- 'config-admin-email' => 'Email address:',
- 'config-admin-email-help' => 'Enter an email address here to allow you to receive email from other users on the wiki, reset your password, and be notified of changes to pages on your watchlist. You can leave this field empty.',
- 'config-admin-error-user' => 'Internal error when creating an admin with the name "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Internal error when setting a password for the admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'You have entered an invalid email address.',
- 'config-subscribe' => 'Subscribe to the [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release announcements mailing list].',
- 'config-subscribe-help' => 'This is a low-volume mailing list used for release announcements, including important security announcements.
-You should subscribe to it and update your MediaWiki installation when new versions come out.',
- 'config-subscribe-noemail' => 'You tried to subscribe to the release announcements mailing list without providing an email address.
-Please provide an email address if you wish to subscribe to the mailing list.',
- 'config-almost-done' => 'You are almost done!
-You can now skip the remaining configuration and install the wiki right now.',
- 'config-optional-continue' => 'Ask me more questions.',
- 'config-optional-skip' => "I'm bored already, just install the wiki.",
- 'config-profile' => 'User rights profile:',
- 'config-profile-wiki' => 'Open wiki',
- 'config-profile-no-anon' => 'Account creation required',
- 'config-profile-fishbowl' => 'Authorized editors only',
- 'config-profile-private' => 'Private wiki',
- 'config-profile-help' => "Wikis work best when you let as many people edit them as possible.
-In MediaWiki, it is easy to review the recent changes, and to revert any damage that is done by naive or malicious users.
-
-However, many have found MediaWiki to be useful in a wide variety of roles, and sometimes it is not easy to convince everyone of the benefits of the wiki way.
-So you have the choice.
-
-The '''{{int:config-profile-wiki}}''' model allows anyone to edit, without even logging in.
-A wiki with '''{{int:config-profile-no-anon}}''' provides extra accountability, but may deter casual contributors.
-
-The '''{{int:config-profile-fishbowl}}''' scenario allows approved users to edit, but the public can view the pages, including history.
-A '''{{int:config-profile-private}}''' only allows approved users to view pages, with the same group allowed to edit.
-
-More complex user rights configurations are available after installation, see the [//www.mediawiki.org/wiki/Manual:User_rights relevant manual entry].",
- 'config-license' => 'Copyright and license:',
- 'config-license-none' => 'No license footer',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (Public Domain)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 or later',
- 'config-license-pd' => 'Public Domain',
- 'config-license-cc-choose' => 'Select a custom Creative Commons license',
- 'config-license-help' => "Many public wikis put all contributions under a [http://freedomdefined.org/Definition free license].
-This helps to create a sense of community ownership and encourages long-term contribution.
-It is not generally necessary for a private or corporate wiki.
-
-If 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 '''Creative Commons Attribution Share Alike'''.
-
-Wikipedia previously used the GNU Free Documentation License.
-The GFDL is a valid license, but it is difficult to understand.
-It 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, [http://www.php.net/manual/en/mail.configuration.php PHP's mail settings] need to be configured correctly.
-If 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',
- 'config-email-usertalk-help' => 'Allow users to receive notifications on user talk page changes, if they have enabled it in their preferences.',
- 'config-email-watchlist' => 'Enable watchlist notification',
- 'config-email-watchlist-help' => 'Allow users to receive notifications about their watched pages if they have enabled it in their preferences.',
- 'config-email-auth' => 'Enable email authentication',
- 'config-email-auth-help' => "If this option is enabled, users have to confirm their email address using a link sent to them whenever they set or change it.
-Only authenticated email addresses can receive emails from other users or change notification emails.
-Setting this option is '''recommended''' for public wikis because of potential abuse of the email features.",
- 'config-email-sender' => 'Return email address:',
- 'config-email-sender-help' => 'Enter the email address to use as the return address on outbound email.
-This is where bounces will be sent.
-Many mail servers require at least the domain name part to be valid.',
- 'config-upload-settings' => 'Images and file uploads',
- 'config-upload-enable' => 'Enable file uploads',
- 'config-upload-help' => "File uploads potentially expose your server to security risks.
-For more information, read the [//www.mediawiki.org/wiki/Manual:Security security section] in the manual.
-
-To enable file uploads, change the mode on the <code>images</code> subdirectory under MediaWiki's root directory so that the web server can write to it.
-Then enable this option.",
- 'config-upload-deleted' => 'Directory for deleted files:',
- 'config-upload-deleted-help' => 'Choose a directory in which to archive deleted files.
-Ideally, this should not be accessible from the web.',
- 'config-logo' => 'Logo URL:',
- 'config-logo-help' => "MediaWiki's default skin includes space for a 135x160 pixel logo above the sidebar menu.
-Upload an image of the appropriate size, and enter the URL here.
-
-You can use <code>\$wgStylePath</code> or <code>\$wgScriptPath</code> if your logo is relative to those paths.
-
-If you do not want a logo, leave this box blank.",
- 'config-instantcommons' => 'Enable Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] is a feature that allows wikis to use images, sounds and other media found on the [//commons.wikimedia.org/ Wikimedia Commons] site.
-In order to do this, MediaWiki requires access to the Internet.
-
-For more information on this feature, including instructions on how to set it up for wikis other than the Wikimedia Commons, consult [//mediawiki.org/wiki/Manual:$wgForeignFileRepos the manual].',
- 'config-cc-error' => 'The Creative Commons license chooser gave no result.
-Enter the license name manually.',
- 'config-cc-again' => 'Pick again...',
- 'config-cc-not-chosen' => 'Choose which Creative Commons license you want and click "proceed".',
- 'config-advanced-settings' => 'Advanced configuration',
- 'config-cache-options' => 'Settings for object caching:',
- 'config-cache-help' => 'Object caching is used to improve the speed of MediaWiki by caching frequently used data.
-Medium to large sites are highly encouraged to enable this, and small sites will see benefits as well.',
- 'config-cache-none' => 'No caching (no functionality is removed, but speed may be impacted on larger wiki sites)',
- 'config-cache-accel' => 'PHP object caching (APC, XCache or WinCache)',
- 'config-cache-memcached' => 'Use Memcached (requires additional setup and configuration)',
- 'config-memcached-servers' => 'Memcached servers:',
- 'config-memcached-help' => 'List of IP addresses to use for Memcached.
-Should specify one per line and specify the port to be used. For example:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'You selected Memcached as your cache type but did not specify any servers.',
- 'config-memcache-badip' => 'You have entered an invalid IP address for Memcached: $1.',
- 'config-memcache-noport' => 'You did not specify a port to use for Memcached server: $1.
-If you do not know the port, the default is 11211.',
- 'config-memcache-badport' => 'Memcached port numbers should be between $1 and $2.',
- 'config-extensions' => 'Extensions',
- 'config-extensions-help' => 'The extensions listed above were detected in your <code>./extensions</code> directory.
-
-They may require additional configuration, but you can enable them now',
- 'config-install-alreadydone' => "'''Warning:''' You seem to have already installed MediaWiki and are trying to install it again.
-Please proceed to the next page.",
- 'config-install-begin' => 'By pressing "{{int:config-continue}}", you will begin the installation of MediaWiki.
-If you still want to make changes, press "{{int:config-back}}".',
- 'config-install-step-done' => 'done',
- 'config-install-step-failed' => 'failed',
- 'config-install-extensions' => 'Including extensions',
- 'config-install-database' => 'Setting up database',
- 'config-install-schema' => 'Creating schema',
- 'config-install-pg-schema-not-exist' => 'PostgreSQL schema does not exist.',
- 'config-install-pg-schema-failed' => 'Tables creation failed.
-Make sure that the user "$1" can write to the schema "$2".',
- 'config-install-pg-commit' => 'Committing changes',
- 'config-install-pg-plpgsql' => 'Checking for language PL/pgSQL',
- 'config-pg-no-plpgsql' => 'You need to install the language PL/pgSQL in the database $1',
- 'config-pg-no-create-privs' => 'The account you specified for installation does not have enough privileges to create an account.',
- 'config-pg-not-in-role' => 'The account you specified for the web user already exists.
-The account you specified for installation is not a superuser and is not a member of the web user\'s role, so it is unable to create objects owned by the web user.
-
-MediaWiki currently requires that the tables be owned by the web user. Please specify another web account name, or click "back" and specify a suitably privileged install user.',
- 'config-install-user' => 'Creating database user',
- 'config-install-user-alreadyexists' => 'User "$1" already exists',
- 'config-install-user-create-failed' => 'Creating user "$1" failed: $2',
- 'config-install-user-grant-failed' => 'Granting permission to user "$1" failed: $2',
- 'config-install-user-missing' => 'The specified user "$1" does not exist.',
- 'config-install-user-missing-create' => 'The specified user "$1" does not exist.
-Please click the "create account" checkbox below if you want to create it.',
- 'config-install-tables' => 'Creating tables',
- 'config-install-tables-exist' => "'''Warning:''' MediaWiki tables seem to already exist.
-Skipping creation.",
- 'config-install-tables-failed' => "'''Error:''' Table creation failed with the following error: $1",
- 'config-install-interwiki' => 'Populating default interwiki table',
- 'config-install-interwiki-list' => 'Could not read file <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Warning:''' The interwiki table seems to already have entries.
-Skipping default list.",
- 'config-install-stats' => 'Initializing statistics',
- 'config-install-keys' => 'Generating secret keys',
- 'config-insecure-keys' => "'''Warning:''' {{PLURAL:$2|A secure key|Secure keys}} ($1) generated during installation {{PLURAL:$2|is|are}} not completely safe. Consider changing {{PLURAL:$2|it|them}} manually.",
- 'config-install-sysop' => 'Creating administrator user account',
- 'config-install-subscribe-fail' => 'Unable to subscribe to mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL is not installed and allow_url_fopen is not available.',
- 'config-install-mainpage' => 'Creating main page with default content',
- 'config-install-extension-tables' => 'Creating tables for enabled extensions',
- 'config-install-mainpage-failed' => 'Could not insert main page: $1',
- 'config-install-done' => "'''Congratulations!'''
-You have successfully installed MediaWiki.
-
-The installer has generated a <code>LocalSettings.php</code> file.
-It contains all your configuration.
-
-You will need to download it and put it in the base of your wiki installation (the same directory as index.php). The download should have started automatically.
-
-If the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:
-
-$3
-
-'''Note:''' If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.
-
-When that has been done, you can '''[$2 enter your wiki]'''.",
- 'config-download-localsettings' => 'Download <code>LocalSettings.php</code>',
- 'config-help' => 'help',
- 'config-nofile' => 'File "$1" could not be found. Has it been deleted?',
- 'config-extension-link' => 'Did you know that your wiki supports [//www.mediawiki.org/wiki/Manual:Extensions extensions]?
-
-You can browse [//www.mediawiki.org/wiki/Category:Extensions_by_category extensions by category] or the [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] to see the full list of extensions.',
- 'mainpagetext' => "'''MediaWiki has been successfully installed.'''",
- 'mainpagedocfooter' => "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.
-
-== Getting started ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localise MediaWiki for your language]",
-);
-
-/** Message documentation (Message documentation)
- * @author Amire80
- * @author Dani
- * @author EugeneZelenko
- * @author Kghbln
- * @author McDutchie
- * @author Mormegil
- * @author Nemo bis
- * @author Nike
- * @author Platonides
- * @author Purodha
- * @author Raymond
- * @author SPQRobin
- * @author Shirayuki
- * @author Siebrand
- * @author Umherirrender
- * @author Waldir
- */
-$messages['qqq'] = array(
- 'config-desc' => 'Short description of the installer.',
- 'config-title' => 'Parameters:
-* $1 is the version of MediaWiki that is being installed.',
- 'config-information' => '{{Identical|Information}}',
- 'config-localsettings-upgrade' => '{{doc-important|Do not translate <code>LocalSettings.php</code> and <code>$wgUpgradeKey</code>.}}',
- 'config-localsettings-cli-upgrade' => '{{doc-important|Do not translate the <code>LocalSettings.php</code> and the <code>update.php</code> parts.}}',
- 'config-upgrade-key-missing' => 'Used in info box. Parameters:
-* $1 - the upgrade key, enclosed in <code><nowiki><pre></nowikI></code> tag.',
- 'config-localsettings-incomplete' => '{{doc-important|Do not translate <code>LocalSettings.php</code> and <code><nowiki>{{int:Config-continue}}</nowiki><code>.}}
-Parameters:
-* $1 - name of variable (any one of required variables or installer-specific global variables)',
- 'config-localsettings-connection-error' => '{{doc-important|Do not translate <code>LocalSettings.php</code> and <code>AdminSettings.php</code>.}}
-Used as error message. Parameters:
-* $1 - (probably empty string)',
- 'config-session-error' => 'Parameters:
-* $1 is the error that was encountered with the session.',
- 'config-session-expired' => 'Parameters:
-* $1 is the configured session lifetime.',
- 'config-no-session' => '{{doc-important|Do not translate <code>php.ini</code> and <code>session.save_path</code>.}}
-Used as error message.',
- 'config-back' => '{{Identical|Back}}',
- 'config-continue' => '{{Identical|Continue}}',
- 'config-page-language' => '{{Identical|Language}}',
- 'config-page-name' => '{{Identical|Name}}',
- 'config-page-options' => '{{Identical|Options}}',
- 'config-page-install' => '{{Identical|Install}}',
- 'config-page-complete' => '{{Identical|Complete}}',
- 'config-page-releasenotes' => '{{Identical|Release notes}}',
- 'config-page-copying' => 'This is a link to the full GPL text',
- 'config-restart' => 'Button text to confirm the installation procedure has to be restarted.',
- 'config-copyright' => 'This message follows {{msg-mw|config-env-good}}.
-
-Parameters:
-* $1 - copyright and author list',
- 'config-sidebar' => 'Maximum width for words is 24 characters. Only visible part of the translation counts to this limit.',
- 'config-env-php' => 'Parameters:
-* $1 - the version of PHP that has been installed
-See also:
-* {{msg-mw|config-env-php-toolow}}',
- 'config-env-php-toolow' => 'Parameters:
-* $1 - the version of PHP that has been installed
-* $2 - minimum PHP version number
-See also:
-* {{msg-mw|config-env-php}}',
- 'config-unicode-pure-php-warning' => 'PECL is the name of a group producing standard pieces of software for PHP, and intl is the name of their library handling some aspects of internationalization.',
- 'config-unicode-update-warning' => "ICU is a body producing standard software tools for support of Unicode and other internationalization aspects. This message warns the system administrator installing MediaWiki that the server's software is not up-to-date and MediaWiki will have problems handling some characters.",
- 'config-no-db' => '{{doc-important|Do not translate "<code>./configure --with-mysqli</code>" and "<code>php5-mysql</code>".}}
-Parameters:
-* $1 is comma separated list of database types supported by MediaWiki.',
- 'config-outdated-sqlite' => 'Used as warning. Parameters:
-* $1 - the version of SQLite that has been installed
-* $2 - minimum version',
- 'config-no-fts3' => 'A "[[:wikipedia:Front and back ends|backend]]" is a system or component that ordinary users don\'t interact with directly and don\'t need to know about, and that is responsible for a distinct task or service - for example, a storage back-end is a generic system for storing data which other applications can use. Possible alternatives for back-end are "system" or "service", or (depending on context and language) even leave it untranslated.',
- 'config-magic-quotes-runtime' => '{{Related|Config-fatal}}',
- 'config-magic-quotes-sybase' => '{{Related|Config-fatal}}',
- 'config-mbstring' => '{{Related|Config-fatal}}',
- 'config-ze1' => '{{Related|Config-fatal}}',
- 'config-pcre' => 'PCRE is an initialism for "Perl-compatible regular expression". Perl is programming language whose [[:w:regular expression|regular expression]] syntax is popular and used in other languages using a library called PCRE.',
- 'config-pcre-old' => 'Parameters:
-* $1 - minimum PCRE version number
-* $2 - the installed version of [[wikipedia:PCRE|PCRE]]
-{{Related|Config-fatal}}',
- 'config-pcre-no-utf8' => "PCRE is a name of a programmers' library for supporting regular expressions. It can probably be translated without change.
-{{Related|Config-fatal}}",
- 'config-memory-raised' => 'Parameters:
-* $1 is the configured <code>memory_limit</code>.
-* $2 is the value to which <code>memory_limit</code> was raised.',
- 'config-memory-bad' => 'Parameters:
-* $1 is the configured <code>memory_limit</code>.',
- 'config-ctype' => 'Message if support for [http://www.php.net/manual/en/ctype.installation.php Ctype] is missing from PHP',
- 'config-json' => 'Message if support for [[wikipedia:JSON|JSON]] is missing from PHP.
-* "[[wikipedia:Red Hat Enterprise Linux|Red Hat Enterprise Linux]]" (RHEL) and "[[wikipedia:CentOS|CentOS]]" refer to two almost-identical Linux distributions. "5 and 6" refers to version 5 or 6 of either distribution. Because RHEL 7 likely will not include the PHP extension, do not translate as "5 or newer".
-* "The [http://www.php.net/json PHP extension]" is the JSON extension included with PHP 5.2 and newer.
-* "The [http://pecl.php.net/package/jsonc PECL extension]" is based on the PHP extension, though excludes code some distributions have found unacceptable (see [[bugzilla:47431]]).',
- 'config-xcache' => 'Message indicates if this program is available',
- 'config-apc' => 'Message indicates if this program is available',
- 'config-wincache' => 'Message indicates if this program is available',
- 'config-git' => 'Message if Git version control software is available.
-Parameter:
-* $1 is the <code>Git</code> executable file name.',
- 'config-git-bad' => 'Message if Git version control software is not found.',
- 'config-imagemagick' => '$1 is ImageMagick\'s <code>convert</code> executable file name.
-
-Add dir="ltr" to the <nowiki><code></nowiki> for right-to-left languages.',
- 'config-no-cli-uri' => 'Parameters:
-* $1 is the default value for scriptpath.',
- 'config-using-server' => 'Used as a part of environment check result. Parameters:
-* $1 - default server name',
- 'config-using-uri' => 'Used as a part of environment check result. Parameters:
-* $1 - server name
-* $2 - script path',
- 'config-uploads-not-safe' => 'Used as a part of environment check result. Parameters:
-* $1 - name of directory for images: <code>$IP/images/</code>',
- 'config-no-cli-uploads-check' => 'CLI = [[w:Command-line interface|command-line interface]] (i.e. the installer runs as a command-line script, not using HTML interface via an internet browser)',
- 'config-using531' => 'Used as error message. Parameters:
-* $1 - the version of PHP that has been installed',
- 'config-suhosin-max-value-length' => '{{doc-important|Do not translate "length", "suhosin.get.max_value_length", "php.ini", "$wgResourceLoaderMaxQueryLength" and "LocalSettings.php".}}
-Message shown when PHP parameter <code>suhosin.get.max_value_length</code> is between 0 and 1023 (that max value is hard set in MediaWiki software).',
- 'config-db-host-help' => '{{doc-singularthey}}',
- 'config-db-host-oracle' => 'TNS = [[:wikipedia:Transparent Network Substrate|Transparent Network Substrate]] (<== wikipedia link)',
- 'config-db-host-oracle-help' => 'See also:
-* {{msg-mw|Config-invalid-db-server-oracle}}',
- 'config-db-wiki-settings' => 'This is more acurate: "Enter identifying or distinguishing data for this wiki" since a MySQL database can host tables of several wikis.',
- 'config-db-account-oracle-warn' => 'A "[[:wikipedia:Front and back ends|backend]]" is a system or component that ordinary users don\'t interact with directly and don\'t need to know about, and that is responsible for a distinct task or service - for example, a storage back-end is a generic system for storing data which other applications can use. Possible alternatives for back-end are "system" or "service", or (depending on context and language) even leave it untranslated.',
- 'config-db-password-empty' => 'Used as error message. Parameters:
-* $1 - database username',
- 'config-db-account-lock' => "It might be easier to translate ''normal operation'' as \"also after the installation process\"",
- 'config-mysql-old' => 'Used as error message. Parameters:
-* $1 - minimum version
-* $2 - the version of MySQL that has been installed',
- 'config-pg-test-error' => '* $1 - database name
-* $2 - error message',
- 'config-sqlite-dir-help' => '{{doc-important|Do not translate <code>.htaccess</code> and <code>/var/lib/mediawiki/yourwiki</code>.}}
-Used in help box.',
- 'config-type-mysql' => '"Or compatible" refers to several database systems that are compatible with MySQL, as explained in {{msg-mw|config-dbsupport-mysql}}, and thus also work with this choice of database type.',
- 'config-type-postgres' => '{{optional}}',
- 'config-type-sqlite' => '{{optional}}',
- 'config-type-oracle' => '{{optional}}',
- 'config-support-info' => 'Parameters:
-* $1 - a list of DBMSs that MediaWiki supports, composed with config-dbsupport-* messages.',
- 'config-support-mysql' => 'Parameters:
-* $1 - a link to the MySQL home page having the anchor text "MySQL".',
- 'config-support-postgres' => 'Parameters:
-* $1 - a link to the PostgreSQL home page having the anchor text "PostgreSQL".',
- 'config-support-sqlite' => 'Parameters:
-* $1 - a link to the SQLite home page having the anchor text "SQLite".',
- 'config-support-oracle' => 'Parameters:
-* $1 - a link to the Oracle home page, the anchor text of which is "Oracle".',
- 'config-dbsupport-mysql' => 'Used in {{msg-mw|config-support-info}}.',
- 'config-dbsupport-postgres' => 'Used in {{msg-mw|config-support-info}}.',
- 'config-dbsupport-sqlite' => 'Used in {{msg-mw|config-support-info}}.',
- 'config-dbsupport-oracle' => 'Used in {{msg-mw|config-support-info}}.',
- 'config-invalid-db-server-oracle' => 'Used as error message. Parameters:
-* $1 - database server name
-See also:
-* {{msg-mw|Config-db-host-oracle-help}}',
- 'config-invalid-db-name' => 'Used as error message. Parameters:
-* $1 - database name
-See also:
-* {{msg-mw|Config-invalid-db-prefix}}',
- 'config-invalid-db-prefix' => 'Used as error message. Parameters:
-* $1 - database prefix
-See also:
-* {{msg-mw|Config-invalid-db-name}}',
- 'config-connection-error' => '$1 is the external error from the database, such as "DB connection error: Access denied for user \'dba\'@\'localhost\' (using password: YES) (localhost)."
-
-If you\'re translating this message to a right-to-left language, consider writing <nowiki><div dir="ltr">$1.</div></nowiki>. (When the bidi features for HTML5 will be implemented in the browsers, it will probably be a good idea to write it as <nowiki><div dir="auto">$1.</div></nowiki>.)',
- 'config-invalid-schema' => '*$1 - schema name',
- 'config-db-sys-user-exists-oracle' => 'Used as error message. Parameters:
-* $1 - database username',
- 'config-postgres-old' => 'Used as error message. Used as warning. Parameters:
-* $1 - minimum version
-* $2 - the version of PostgreSQL that has been installed',
- 'config-sqlite-parent-unwritable-group' => 'Used as SQLite error message. Parameters:
-* $1 - data directory
-* $2 - "dirname" part of $1
-* $3 - "basename" part of $1
-* $4 - web server\'s primary group name
-See also:
-* {{msg-mw|Config-sqlite-parent-unwritable-nogroup}}',
- 'config-sqlite-parent-unwritable-nogroup' => 'Used as SQLite error message. Parameters:
-* $1 - data directory
-* $2 - "dirname" part of $1
-* $3 - "basename" part of $1
-See also:
-* {{msg-mw|Config-sqlite-parent-unwritable-group}}',
- 'config-sqlite-mkdir-error' => 'Used as SQLite error message. Parameters:
-* $1 - data directory name',
- 'config-sqlite-dir-unwritable' => 'webserver refers to a software like Apache or Lighttpd.',
- 'config-sqlite-connection-error' => 'Used as SQLite error message. Parameters:
-* $1 - error message which SQLite server returned',
- 'config-sqlite-readonly' => 'Used as SQLite error message. Parameters:
-* $1 - filename',
- 'config-sqlite-cant-create-db' => 'Used as SQLite error message. Parameters:
-* $1 - filename',
- 'config-can-upgrade' => 'Parameters:
-* $1 - Version or Revision indicator.',
- 'config-upgrade-done' => 'Used as success message. Parameters:
-* $1 - full URL of index.php
-See also:
-* {{msg-mw|config-upgrade-done-no-regenerate}}',
- 'config-upgrade-done-no-regenerate' => 'Used as success message. Parameters:
-* $1 - full URL of index.php
-See also:
-* {{msg-mw|config-upgrade-done}}',
- 'config-regenerate' => 'This message appears in a button after LocalSettings.php is generated and downloaded at the end of the MediaWiki installation process.',
- 'config-show-table-status' => '{{doc-important|"<code>SHOW TABLE STATUS</code>" is a MySQL command. Do not translate this.}}',
- 'config-db-web-account-same' => 'checkbox label',
- 'config-db-web-create' => 'checkbox label',
- 'config-mysql-only-myisam-dep' => 'Used as warning message when mysql does not support the minimum suggested feature set.',
- 'config-mysql-binary' => '{{Identical|Binary}}',
- 'config-ns-generic' => 'Used as label for "namespace type" radio button.
-
-See also:
-* {{msg-mw|Config-ns-site-name}}
-* {{msg-mw|Config-ns-other}}
-{{Identical|Project}}',
- 'config-ns-site-name' => 'Used as label for "namespace type" radio button. Parameters:
-* $1 - wiki name
-See also:
-* {{msg-mw|Config-ns-generic}}
-* {{msg-mw|Config-ns-other}}',
- 'config-ns-other' => "Used as label for \"namespace type\" radio button.
-
-This message is followed by the input box which enables to '''specify''' a namespace name.
-
-See also:
-* {{msg-mw|Config-ns-site-name}}
-* {{msg-mw|Config-ns-generic}}",
- 'config-ns-invalid' => 'Used as error message. Parameters:
-* $1 - namespace name
-See also:
-* {{msg-mw|Config-ns-conflict}}',
- 'config-ns-conflict' => 'Used as error message. Parameters:
-* $1 - namespace name
-See also:
-* {{msg-mw|Config-ns-invalid}}',
- 'config-admin-name' => '{{Identical|Your name}}',
- 'config-admin-password' => '{{Identical|Password}}',
- 'config-admin-name-invalid' => 'Used as error message. Parameters:
-* $1 - username of administrator',
- 'config-admin-email' => '{{Identical|E-mail address}}',
- 'config-admin-error-user' => 'Used as error message. Parameters:
-* $1 - username of administrator
-See also:
-* {{msg-mw|Config-admin-error-password}}',
- 'config-admin-error-password' => 'Used as error message. Parameters:
-* $1 - username of administrator
-* $2 - error message
-See also:
-* {{msg-mw|Config-admin-error-user}}',
- 'config-subscribe' => 'Used as label for the installer checkbox',
- 'config-subscribe-help' => '"Low-volume" in this context means that there will be few e-mails to that mailing list per time period.',
- 'config-profile-help' => 'Messages referenced:
-* {{msg-mw|config-profile-wiki}}
-* {{msg-mw|config-profile-no-anon}}
-* {{msg-mw|config-profile-fishbowl}}
-* {{msg-mw|config-profile-private}}',
- 'config-email-settings' => '{{Identical|E-mail setting}}',
- 'config-email-user' => '{{Identical|Enable user-to-user e-mail}}',
- 'config-upload-help' => 'The word "mode" here refers to the access rights given to various user groups when attempting to create and store files and/or subdiretories in the said directory on the server. It also refers to the <code>mode</code> command used to maipulate said right mask under Unix, Linux, and similar operating systems. A less operating-system-centric translation is fine.',
- 'config-logo-help' => '',
- 'config-instantcommons' => 'Used as label for the checkbox.
-
-The help message for this checkbox is:
-* {{msg-mw|Config-instantcommons-help}}',
- 'config-instantcommons-help' => 'Used as help message for the checkbox which is labeled {{msg-mw|config-instantcommons}}.',
- 'config-cc-not-chosen' => '{{doc-important|Do not translate the "<code>proceed</code>" part.}}
-This message refers to a block of HTML being embedded into the installer page. It comes from the Creative Commons Web site. The block is in the English language. It is a scripted license chooser. When an individual license has been selected, it asks you to click "proceed" so as to return to the MediaWiki installer page.',
- 'config-memcached-servers' => '{{doc-important|Do not translate "memcached".}}
-{{Identical|Memcached server}}',
- 'config-memcache-badip' => 'Used as error message. Parameters:
-* $1 - IP address for Memcached
-See also:
-* {{msg-mw|Config-memcache-noport}}
-* {{msg-mw|Config-memcache-badport}}',
- 'config-memcache-noport' => 'Used as error message. Parameters:
-* $1 - Memcached server name
-See also:
-* {{msg-mw|Config-memcache-badip}}
-* {{msg-mw|Config-memcache-badport}}',
- 'config-memcache-badport' => 'Used as error message. Parameters:
-* $1 - 1 (hard-coded)
-* $2 - 65535 (hard-coded)
-See also:
-* {{msg-mw|Config-memcache-badip}}
-* {{msg-mw|Config-memcache-noport}}',
- 'config-extensions' => '{{Identical|Extension}}',
- 'config-extensions-help' => '{{doc-important|Do not translate <code>./extensions</code>.}}
-Used in help box.',
- 'config-install-step-done' => '{{Identical|Done}}',
- 'config-install-step-failed' => '{{Identical|Failed}}',
- 'config-install-database' => '*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-install-schema' => '*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-install-pg-schema-failed' => 'Parameters:
-* $1 = database user name (usernames in the database are unrelated to wiki user names)
-* $2 =',
- 'config-pg-no-plpgsql' => 'Used as error message. Parameters:
-* $1 - database name',
- 'config-install-user' => 'Message indicates that the user is being created
-
-See also:
-*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-install-user-alreadyexists' => 'Used as warning. Parameters:
-* $1 - database username',
- 'config-install-user-create-failed' => 'Used as MySQL warning and as PostgreSQL error. Parameters:
-* $1 - database username
-* $2 - detailed warning/error message',
- 'config-install-user-grant-failed' => 'Parameters:
-* $1 is the database username for which granting rights failed
-* $2 is the error message',
- 'config-install-user-missing' => 'Used as PostgreSQL error message. Parameters:
-* $1 - database username
-See also:
-* {{msg-mw|Config-install-user-missing-create}}',
- 'config-install-user-missing-create' => 'Used as PostgreSQL error message. Parameters:
-* $1 - database username
-See also:
-* {{msg-mw|Config-install-user-missing}}',
- 'config-install-tables' => 'Message indicates that the tables are being created
-
-See also:
-*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-install-tables-failed' => 'Used as PostgreSQL error message. Parameters:
-* $1 - detailed error message',
- 'config-install-interwiki' => 'Message indicates that the interwikitables are being populated
-
-See also:
-*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-install-interwiki-list' => '{{doc-important|Do not translate <code>interwiki.list</code>.}}
-Used as error message.',
- 'config-install-stats' => '*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-install-keys' => '*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-insecure-keys' => 'Parameters:
-* $1 - A list of names of the secret keys that were generated.
-* $2 - the number of items in the list $1, to be used with PLURAL.',
- 'config-install-sysop' => 'Message indicates that the administrator user account is being created
-
-See also:
-*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-install-subscribe-fail' => '{{doc-important|"mediawiki-announce" is the name of a mailing list and should not be translated.}}',
- 'config-install-mainpage' => '*{{msg-mw|Config-install-database}}
-*{{msg-mw|Config-install-tables}}
-*{{msg-mw|Config-install-schema}}
-*{{msg-mw|Config-install-user}}
-*{{msg-mw|Config-install-interwiki}}
-*{{msg-mw|Config-install-stats}}
-*{{msg-mw|Config-install-keys}}
-*{{msg-mw|Config-install-sysop}}
-*{{msg-mw|Config-install-mainpage}}',
- 'config-install-mainpage-failed' => 'Used as error message. Parameters:
-* $1 - detailed error message',
- 'config-install-done' => 'Parameters:
-* $1 is the URL to LocalSettings download
-* $2 is a link to the wiki.
-* $3 is a download link with attached download icon. The config-download-localsettings message will be used as the link text.',
- 'config-download-localsettings' => 'The link text used in the download link in config-install-done.',
- 'config-help' => 'This is used in help boxes.
-{{Identical|Help}}',
- 'config-nofile' => 'Used as failure message. Parameters:
-* $1 - filename',
- 'config-extension-link' => 'Shown on last page of installation to inform about possible extensions.',
- 'mainpagetext' => 'Along with {{msg-mw|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.',
- 'mainpagedocfooter' => 'Along with {{msg-mw|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.
-This might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example.',
-);
-
-/** Afrikaans (Afrikaans)
- * @author Naudefj
- */
-$messages['af'] = array(
- 'config-desc' => 'Die Installasieprogram vir MediaWiki',
- 'config-title' => 'Installasie MediaWiki $1',
- 'config-information' => 'Inligting',
- 'config-localsettings-key' => 'Opgradeer-sleutel:',
- 'config-localsettings-badkey' => 'Die sleutel wat u verskaf het is verkeerd.',
- 'config-session-error' => 'Fout met begin van sessie: $1',
- 'config-no-session' => "U sessiedata is verlore!
-Kontroleer u php.ini en maak seker dat <code>session.save_path</code> na 'n geldige gids wys.",
- 'config-your-language' => 'U taal:',
- 'config-your-language-help' => "Kies 'n taal om tydens die installasieproses te gebruik.",
- 'config-wiki-language' => 'Wiki se taal:',
- 'config-wiki-language-help' => 'Kies die taal waarin die wiki hoofsaaklik geskryf sal word.',
- 'config-back' => '← Terug',
- 'config-continue' => 'Gaan voort →',
- 'config-page-language' => 'Taal',
- 'config-page-welcome' => 'Welkom by MediaWiki!',
- 'config-page-dbconnect' => 'Konnekteer na die databasis',
- 'config-page-upgrade' => "Opgradeer 'n bestaande installasie",
- 'config-page-dbsettings' => 'Databasis-instellings',
- 'config-page-name' => 'Naam',
- 'config-page-options' => 'Opsies',
- 'config-page-install' => 'Installeer',
- 'config-page-complete' => 'Voltooi!',
- 'config-page-restart' => 'Herbegin installasie',
- 'config-page-readme' => 'Lees my',
- 'config-page-releasenotes' => 'Vrystellingsnotas',
- 'config-page-copying' => 'Besig met kopiëring',
- 'config-page-upgradedoc' => 'Besig met opgradering',
- 'config-page-existingwiki' => 'Bestaande wiki',
- 'config-restart' => 'Ja, herbegin dit',
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki tuisblad]
-* [//www.mediawiki.org/wiki/Help:Contents Gebruikershandleiding] (Engelstalig)
-* [//www.mediawiki.org/wiki/Manual:Contents Administrateurshandleiding] (Engelstalig)
-* [//www.mediawiki.org/wiki/Manual:FAQ Algemene vrae] (Engelstalig)
-----
-* <doclink href=Readme>Lees my</doclink>
-* <doclink href=ReleaseNotes>Vrystellingsnotas</doclink>
-* <doclink href=Copying>Kopiëring</doclink>
-* <doclink href=UpgradeDoc>Opgradering</doclink>',
- 'config-env-good' => 'Die omgewing is gekontroleer.
-U kan MediaWiki installeer.',
- 'config-env-bad' => 'Die omgewing is gekontroleer.
-U kan nie MediaWiki installeer nie.</span>',
- 'config-env-php' => 'PHP $1 is tans geïnstalleer.',
- 'config-no-db' => "Kon nie 'n geskikte databasisdrywer vind nie!", # Fuzzy
- 'config-memory-raised' => 'PHP se <code>memory_limit</code> is $1, en is verhoog tot $2.',
- 'config-memory-bad' => "'''Waarskuwing:''' PHP se <code>memory_limit</code> is $1.
-Dit is waarskynlik te laag.
-Die installasie mag moontlik faal!",
- 'config-xcache' => '[Http://trac.lighttpd.net/xcache/ XCache] is geïnstalleer',
- 'config-apc' => '[Http://www.php.net/apc APC] is geïnstalleer',
- 'config-wincache' => '[Http://www.iis.net/download/WinCacheForPhp WinCache] is geïnstalleer',
- 'config-diff3-bad' => 'GNU diff3 nie gevind nie.',
- 'config-db-type' => 'Databasistipe:',
- 'config-db-host' => 'Databasisbediener:',
- 'config-db-host-oracle' => 'Databasis-TNS:',
- 'config-db-wiki-settings' => 'Identifiseer hierdie wiki',
- 'config-db-name' => 'Databasisnaam:',
- 'config-db-name-oracle' => 'Databasis-skema:',
- 'config-db-install-account' => 'Gebruiker vir die installasie',
- 'config-db-username' => 'Databasis gebruikersnaam:',
- 'config-db-password' => 'Databasis wagwoord:',
- 'config-db-prefix' => 'Voorvoegsel vir databasistabelle:',
- 'config-db-charset' => 'Karakterstelsel vir databasis',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binêr',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-mysql-old' => 'U moet MySQL $1 of later gebruik.
-U gebruik tans $2.',
- 'config-db-port' => 'Databasispoort:',
- 'config-db-schema' => 'Skema vir MediaWiki',
- 'config-sqlite-dir' => 'Gids vir SQLite se data:',
- 'config-oracle-def-ts' => 'Standaard tabelruimte:',
- 'config-oracle-temp-ts' => 'Tydelike tabelruimte:',
- 'config-header-mysql' => 'MySQL-instellings',
- 'config-header-postgres' => 'PostgreSQL-instellings',
- 'config-header-sqlite' => 'SQLite-instellings',
- 'config-header-oracle' => 'Oracle-instellings',
- 'config-invalid-db-type' => 'Ongeldige databasistipe',
- 'config-missing-db-name' => 'U moet \'n waarde vir "Databasnaam" verskaf',
- 'config-sqlite-readonly' => 'Die lêer <code>$1</code> kan nie geskryf word nie.',
- 'config-sqlite-cant-create-db' => 'Kon nie databasislêer <code>$1</code> skep nie.',
- 'config-upgrade-done-no-regenerate' => 'Opgradering is voltooi.
-
-U kan nou [$1 u wiki gebruik].',
- 'config-regenerate' => 'Herskep LocalSettings.php →',
- 'config-show-table-status' => 'Die uitvoer van <code>SHOW TABLE STATUS</code> het gefaal!',
- 'config-db-web-account' => 'Databasisgebruiker vir toegang tot die web',
- 'config-mysql-engine' => 'Stoor-enjin:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => 'Karakterstelsel vir databasis:',
- 'config-mysql-binary' => 'Binêr',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Naam van die wiki:',
- 'config-site-name-blank' => "Verskaf 'n naam vir u webwerf.",
- 'config-project-namespace' => 'Projeknaamruimte:',
- 'config-ns-generic' => 'Projek',
- 'config-ns-site-name' => 'Dieselfde as die wiki: $1',
- 'config-ns-other' => 'Ander (spesifiseer)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-admin-box' => 'Administrateur se gebruiker',
- 'config-admin-name' => 'U naam:',
- 'config-admin-password' => 'Wagwoord:',
- 'config-admin-password-confirm' => 'Wagwoord weer:',
- 'config-admin-password-blank' => "Verskaf 'n wagwoord vir die administrateur in.",
- 'config-admin-password-same' => 'Die wagwoord mag nie dieselfde as die gebruikersnaam wees nie.',
- 'config-admin-password-mismatch' => 'Die twee wagwoorde wat u ingetik het stem nie ooreen nie.',
- 'config-admin-email' => 'E-posadres:',
- 'config-optional-continue' => 'Vra my meer vrae.',
- 'config-optional-skip' => 'Ek is reeds verveeld, installeer maar net die wiki.',
- 'config-profile-wiki' => 'Tradisionele wiki', # Fuzzy
- 'config-profile-no-anon' => 'Skep van gebruiker is verpligtend',
- 'config-profile-fishbowl' => 'Slegs vir gemagtigde redaksie',
- 'config-profile-private' => 'Privaat wiki',
- 'config-license' => 'Kopiereg en lisensie:',
- 'config-license-none' => 'Geen lisensie in die onderskrif',
- 'config-license-pd' => 'Publieke Domein',
- 'config-license-cc-choose' => "Kies 'n Creative Commons-lisensie",
- 'config-email-settings' => 'E-posinstellings',
- 'config-email-sender' => 'E-posadres vir antwoorde:',
- 'config-upload-settings' => 'Oplaai van beelde en lêer',
- 'config-upload-enable' => 'Aktiveer die oplaai van lêers',
- 'config-upload-deleted' => 'Gids vir verwyderde lêers:',
- 'config-logo' => 'URL vir logo:',
- 'config-cc-again' => 'Kies weer...',
- 'config-advanced-settings' => 'Gevorderde konfigurasie',
- 'config-memcached-servers' => 'Memcached-bedieners:',
- 'config-extensions' => 'Uitbreidings',
- 'config-install-step-done' => 'gedoen',
- 'config-install-step-failed' => 'het misluk',
- 'config-install-extensions' => 'Insluitende uitbreidings',
- 'config-install-database' => 'Stel die databasis op',
- 'config-install-pg-schema-not-exist' => 'Die skema vir PostgreSQL bestaan ​​nie.',
- 'config-install-pg-schema-failed' => 'Die skep van tabelle het gefaal.
-Maak seker dat die gebruiker "$1" na skema "$2" mag skryf.',
- 'config-install-pg-commit' => 'Wysigings word gestoor',
- 'config-install-pg-plpgsql' => 'Kontroleer vir taal PL/pgSQL',
- 'config-pg-no-plpgsql' => 'U moet die taal PL/pgSQL in die database $1 installeer',
- 'config-install-user' => 'Besig om die databasisgebruiker te skep',
- 'config-install-user-alreadyexists' => 'Gebruiker "$1" bestaan al reeds',
- 'config-install-user-create-failed' => 'Skep van gebruiker "$1" het gefaal: $2',
- 'config-install-user-grant-failed' => 'Die toekenning van regte aan gebruiker "$1" het gefaal: $2',
- 'config-install-tables' => 'Skep tabelle',
- 'config-install-tables-exist' => "'''Waarskuwing''': Dit lyk of MediaWiki se tabelle reeds bestaan.
-Die skep van tabelle word oorgeslaan.",
- 'config-install-tables-failed' => "'''Fout''': die skep van 'n tabel het gefaal met die volgende fout: $1",
- 'config-install-interwiki' => 'Besig om data in die interwiki-tabel in te laai',
- 'config-install-interwiki-list' => 'Kon nie die lêer <code>interwiki.list</code> vind nie.',
- 'config-install-interwiki-exists' => "'''Waarskuwing''': Die interwiki-tabel bevat reeds inskrywings.
-Die standaardlys word oorgeslaan.",
- 'config-install-stats' => 'Inisialiseer statistieke',
- 'config-install-keys' => 'Genereer geheime sleutel', # Fuzzy
- 'config-install-sysop' => "Skep 'n gebruiker vir die administrateur",
- 'config-install-subscribe-fail' => 'Kon nie vir MediaWiki-announce inskryf nie: $1',
- 'config-install-mainpage' => 'Skep die hoofblad met standaard inhoud',
- 'config-install-extension-tables' => 'Skep tabelle vir aangeskakel uitbreidings',
- 'config-install-mainpage-failed' => 'Kon nie die hoofblad laai nie: $1',
- 'config-install-done' => "'''Veels geluk!'''
-U het MediaWiki suksesvol geïnstalleer.
-
-Die installeerder het 'n <code>LocalSettings.php</code> lêer opgestel.
-Dit bevat al u instellings.
-
-U sal dit moet [$1 aflaai] en dit in die hoofgids van u wiki-installasie plaas; in dieselfde gids as index.php.
-'''Let wel''': As u dit nie nou doen nie, sal die gegenereerde konfigurasielêer nie later meer beskikbaar wees nadat u die installasie afgesluit het nie.
-
-As dit gedoen is, kan u '''[u $2 wiki besoek]'''.", # Fuzzy
- 'config-download-localsettings' => 'Laai <code>LocalSettings.php</code> af',
- 'config-help' => 'hulp',
- 'mainpagetext' => "'''MediaWiki is suksesvol geïnstalleer.'''",
- 'mainpagedocfooter' => "Konsulteer '''[//meta.wikimedia.org/wiki/Help:Contents User's Guide]''' vir inligting oor hoe om die wikisagteware te gebruik.
-
-== Hoe om te Begin ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Gheg Albanian (Gegë)
- * @author Bresta
- */
-$messages['aln'] = array(
- 'mainpagetext' => "'''MediaWiki software u instalue me sukses.'''",
- 'mainpagedocfooter' => 'Për mâ shumë informata rreth përdorimit të softwareit wiki, ju lutem shikoni [//meta.wikimedia.org/wiki/Help:Contents dokumentacionin].
-
-
-== Për fillim ==
-
-* [//www.mediawiki.org/wiki/Help:Configuration_settings Konfigurimi i MediaWikit]
-* [//www.mediawiki.org/wiki/Help:FAQ Pyetjet e shpeshta rreth MediaWikit]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWikit]', # Fuzzy
-);
-
-/** Amharic (አማርኛ)
- */
-$messages['am'] = array(
- 'mainpagetext' => "'''MediaWiki በትክክል ማስገባቱ ተከናወነ።'''",
- 'mainpagedocfooter' => "ስለ ዊኪ ሶፍትዌር ጥቅም ለመረዳት፣ [//meta.wikimedia.org/wiki/Help:Contents User's Guide] ያንብቡ።
-
-== ለመጀመር ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Aragonese (aragonés)
- * @author Juanpabl
- */
-$messages['an'] = array(
- 'mainpagetext' => "'''O programa MediaWiki s'ha instalato correctament.'''",
- 'mainpagedocfooter' => "Consulta a [//meta.wikimedia.org/wiki/Help:Contents Guía d'usuario] ta mirar información sobre cómo usar o software wiki.
-
-== Ta prencipiar ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de caracteristicas confegurables]
-* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas cutianas sobre MediaWiki (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correu sobre ta anuncios de MediaWiki]", # Fuzzy
-);
-
-/** Old English (Ænglisc)
- * @author Gott wisst
- */
-$messages['ang'] = array(
- 'mainpagetext' => "'''MediaWiki hafaþ geworden spēdige inseted.'''",
- 'mainpagedocfooter' => 'Þeahta þone [//meta.wikimedia.org/wiki/Help:Contents Brūcenda Lǣdend] on helpe mid þǣre nytte of ƿikisōftƿare.
-
-== Beȝinnunȝ ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Onfæstnunȝa ȝesetednessa ȝetæl]
-* [//www.mediawiki.org/wiki/Manual:FAQ Ȝetæl oft ascodra ascunȝa ymb MediaǷiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ǣrendunȝȝetæl nīƿra MediaǷiki forþsendnessa]', # Fuzzy
-);
-
-/** Arabic (العربية)
- * @author Meno25
- * @author Mido
- * @author OsamaK
- * @author روخو
- */
-$messages['ar'] = array(
- 'config-desc' => 'مثبت لميدياويكي',
- 'config-title' => 'تثبيت ميدياويكي $1',
- 'config-information' => 'معلومات',
- 'config-back' => '→ ارجع',
- 'config-continue' => 'استمر ←',
- 'config-page-language' => 'اللغة',
- 'config-page-name' => 'الاسم',
- 'config-db-username' => 'اسم مستخدم قاعدة البيانات:',
- 'config-db-password' => 'كلمة سر قاعدة البيانات:',
- 'config-db-port' => 'منفذ قاعدة البيانات:',
- 'config-type-mysql' => 'ماي إس كيو إل',
- 'config-type-postgres' => 'بوستجر إس كيو إل',
- 'config-type-sqlite' => 'إس كيو لايت',
- 'config-type-oracle' => 'أوراكل',
- 'config-admin-email' => 'عنوان البريد الإلكتروني:',
- 'mainpagetext' => "'''تم تثبيت ميدياويكي بنجاح.'''",
- 'mainpagedocfooter' => 'استشر [//meta.wikimedia.org/wiki/Help:Contents دليل المستخدم] لمعلومات حول استخدام برنامج الويكي.
-
-== البداية ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings قائمة إعدادات الضبط]
-* [//www.mediawiki.org/wiki/Manual:FAQ أسئلة متكررة حول ميدياويكي]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce القائمة البريدية الخاصة بإصدار ميدياويكي]', # Fuzzy
-);
-
-/** Aramaic (ܐܪܡܝܐ)
- * @author Basharh
- */
-$messages['arc'] = array(
- 'config-information' => 'ܝܕ̈ܥܬܐ',
- 'config-your-language' => 'ܠܫܢܐ ܕܝܠܟ:',
- 'config-wiki-language' => 'ܠܫܢܐ ܕܘܝܩܝ:',
- 'config-page-language' => 'ܠܫܢܐ',
- 'config-page-name' => 'ܫܡܐ',
- 'config-page-options' => 'ܓܒܝܬ̈ܐ',
- 'config-page-install' => 'ܢܨܘܒ',
- 'config-ns-other-default' => 'ܘܝܩܝ ܕܝܠܝ',
- 'config-admin-box' => 'ܚܘܫܒܢܐ ܕܡܕܒܪܢܐ',
- 'config-admin-name' => 'ܫܡܐ ܕܝܠܟ:',
- 'config-admin-password' => 'ܡܠܬܐ ܕܥܠܠܐ:',
- 'config-admin-password-confirm' => 'ܡܠܬܐ ܕܥܠܠܐ ܙܒܢܬܐ ܐܚܪܬܐ:',
- 'config-admin-email' => 'ܡܘܢܥܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ:',
- 'config-profile-private' => 'ܘܝܩܝ ܦܪܨܘܦܝܐ',
- 'config-email-settings' => 'ܛܘܝܒ̈ܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
-);
-
-/** Moroccan Spoken Arabic (Maġribi)
- * @author Enzoreg
- */
-$messages['ary'] = array(
- 'mainpagetext' => "'''MediaWiki ṫ'instala be najaḫ.'''",
- 'mainpagedocfooter' => 'Ila bġiṫiw meĝlomaṫ ĥrin baċ ṫesṫeĝmlo had l-lojisyél siro ċofo [//meta.wikimedia.org/wiki/Aide:Contenu Gid dyal l-mosṫeĥdim]
-
-== L-bdaya mĝa MediaWiki ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dyal l-paramétraṫ dyal l-konfigurasyon]
-* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ fe MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista dyal l-modakaraṫ ĝla versyonaṫ jdad dyal MediaWiki]', # Fuzzy
-);
-
-/** Egyptian Spoken Arabic (مصرى)
- */
-$messages['arz'] = array(
- 'mainpagetext' => "''' ميدياويكى اتنزلت بنجاح.'''",
- 'mainpagedocfooter' => 'اسال [//meta.wikimedia.org/wiki/Help:Contents دليل اليوزر] للمعلومات حوالين استخدام برنامج الويكى.
-
-== البداية ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings لستة اعدادات الضبط]
-* [//www.mediawiki.org/wiki/Manual:FAQ أسئلة بتكرر حوالين الميدياويكى]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce لستة الايميلات بتاعة اعلانات الميدياويكى]', # Fuzzy
-);
-
-/** Assamese (অসমীয়া)
- * @author Chaipau
- * @author Gitartha.bordoloi
- */
-$messages['as'] = array(
- 'mainpagetext' => "'''মিডিয়াৱিকি সফলভাবে ইন্সটল কৰা হ'ল ।'''",
- 'mainpagedocfooter' => "ৱিকি চ'ফটৱেৰ কেনেকৈ ব্যৱহাৰ কৰিব [//meta.wikimedia.org/wiki/Help:Contents সদস্যৰ সহায়িকা] চাওঁক ।
-
-== আৰম্ভণি কৰিবলৈ ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Asturian (asturianu)
- * @author Xuacu
- */
-$messages['ast'] = array(
- 'config-desc' => "L'instalador pa MediaWiki",
- 'config-title' => 'Instalación de MediaWiki $1',
- 'config-information' => 'Información',
- 'config-localsettings-upgrade' => "Detectose un ficheru <code>LocalSettings.php</code>.
-P'anovar esta instalación, escriba'l valor de
-<code>\$wgUpgradeKey</code> nel cuadru d'abaxo.
-Alcontraralu en <code>LocalSettings.php</code>.",
- 'config-localsettings-cli-upgrade' => "Deteutose un ficheru <code>LocalSettings.php</code>.
-P'anovar esta instalación, execute <code>update.php</code>",
- 'config-localsettings-key' => "Clave d'anovamientu:",
- 'config-localsettings-badkey' => 'La clave que dio ye incorreuta.',
- 'config-upgrade-key-missing' => "Deteutose una instalación esistente de MediaWiki.
-P'anovar esta instalación, ponga la llinia siguiente al final del ficheru <code>LocalSettings.php</code>:
-
-$1",
- 'config-localsettings-incomplete' => 'Paez que\'l ficheru <code>LocalSettings.php</code> esistente ta incompletu.
-La variable $1 nun ta definida.
-Camude\'l ficheru <code>LocalSettings.php</code> pa qu\'esta variable quede definida y calque "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Alcontrose un error al conectar cola base de datos usando la configuración especificada en <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Corrixa esta configuración y vuelva a intentalo.
-
-$1',
- 'config-your-language' => 'La so llingua:',
- 'config-your-language-help' => "Seleicione la llingua a emplegar 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-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-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-download-localsettings' => 'Descargar <code>LocalSettings.php</code>',
- 'config-help' => 'Ayuda',
- 'config-nofile' => 'Nun pudo atopase\'l ficheru "$1". ¿Desaniciose?',
- 'mainpagetext' => "'''MediaWiki instalóse correchamente.'''",
- 'mainpagedocfooter' => 'Visita la [//meta.wikimedia.org/wiki/Help:Contents Guía del usuariu] pa saber cómo usar esti software wiki.
-
-== Empecipiando ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Llista de les opciones de configuración]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de corréu de les ediciones de MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Llocaliza MediaWiki na to llingua]',
-);
-
-/** Kotava (Kotava)
- */
-$messages['avk'] = array(
- 'mainpagetext' => "'''MediaWiki inkeyen talpeyot.'''",
-);
-
-/** Azerbaijani (azərbaycanca)
- * @author Cekli829
- * @author Vago
- * @author Wertuose
- */
-$messages['az'] = array(
- 'config-back' => '← Geri',
- 'config-continue' => 'Davam et →',
- 'config-page-language' => 'Dil',
- 'config-page-welcome' => 'MediaWiki-yə xoş gəlmişsiniz!',
- 'config-page-dbconnect' => 'Verilənlər bazasına birləşdir',
- 'config-page-dbsettings' => 'Verilənlər bazasının nizamlanması',
- 'config-page-name' => 'Ad',
- 'config-page-options' => 'Nizamlamalar:',
- 'config-page-install' => 'Nizamlama',
- 'config-page-complete' => 'Komplektləşdir!',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-ns-generic' => 'Layihə',
- 'config-admin-name' => 'Sizin adınız:',
- 'config-admin-password' => 'Parol:',
- 'config-admin-email' => 'E-poçt ünvanı',
- 'config-help' => 'kömək',
- 'mainpagetext' => "'''MediaWiki müvəffəqiyyətlə quraşdırıldı.'''",
- 'mainpagedocfooter' => 'Bu vikinin istifadəsi ilə bağlı məlumat almaq üçün [//meta.wikimedia.org/wiki/Help:Contents İstifadəçi məlumat səhifəsinə] baxın.
-
-== Faydalı keçidlər ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Tənzimləmələrin siyahısı]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki haqqında tez-tez soruşulan suallar]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçt siyahısı]', # Fuzzy
-);
-
-/** Bashkir (башҡортса)
- * @author Haqmar
- */
-$messages['ba'] = array(
- 'mainpagetext' => '«MediaWiki» уңышлы рәүештә ҡоролдо.',
- 'mainpagedocfooter' => 'Был вики менән эшләү тураһында мәғлүмәтте [//meta.wikimedia.org/wiki/Ярҙам:Белешмә ошонда] табып була.
-
-== Файҙалы сығанаҡтар ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Көйләүҙәр исемлеге (инг.)];
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki тураһында йыш бирелгән һорауҙар һәм яуаптар (инг.)];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-ның яңы версиялары тураһында хәбәрҙәр алып тороу].', # Fuzzy
-);
-
-/** Bavarian (Boarisch)
- * @author Mucalexx
- */
-$messages['bar'] = array(
- 'mainpagetext' => "'''MediaWiki is erfoigreich installird worn.'''",
- 'mainpagedocfooter' => 'A Hüf zur da Benützung und Konfigurazion voh da Wiki-Software findst auf [//meta.wikimedia.org/wiki/Help:Contents Benützerhåndbuach].
-
-== Starthüfe ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listen voh de Konfigurazionsvariaablen]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglisten voh de neichen MediaWiki-Versionen]', # Fuzzy
-);
-
-/** Southern Balochi (بلوچی مکرانی)
- */
-$messages['bcc'] = array(
- 'mainpagetext' => "'''مدیا وی کی گون موفقیت نصب بوت.'''",
- 'mainpagedocfooter' => "مشورت کنیت گون [//meta.wikimedia.org/wiki/Help:Contents User's Guide] په گشیترین اطلاعات په استفاده چه برنامه ویکی.
-
-== شروع بیت ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Bikol Central (Bikol Central)
- */
-$messages['bcl'] = array(
- 'mainpagetext' => "'''Instalado na an MediaWiki.'''",
- 'mainpagedocfooter' => "Konsultarón tabì an [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para sa impormasyon sa paggamit nin progama kaining wiki.
-
-== Pagpopoon ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Belarusian (беларуская)
- */
-$messages['be'] = array(
- 'mainpagetext' => "'''MediaWiki паспяхова ўсталяваная.'''",
- 'mainpagedocfooter' => 'Гл. [//meta.wikimedia.org/wiki/Help:Contents Дапаможнік карыстальніка (англ.)] па далейшыя звесткі аб карыстанні вікі-праграмамі.
-
-== З чаго пачаць ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Пералік параметраў канфігурацыі (англ.)]
-* [//www.mediawiki.org/wiki/Manual:FAQ ЧАПЫ MediaWiki (англ.)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ліставанне аб выпусках MediaWiki (англ.)]', # Fuzzy
-);
-
-/** Belarusian (Taraškievica orthography) (беларуская (тарашкевіца)‎)
- * @author EugeneZelenko
- * @author Jim-by
- * @author Wizardist
- * @author Zedlik
- * @author 아라
- */
-$messages['be-tarask'] = array(
- 'config-desc' => 'Праграма ўсталяваньня MediaWiki',
- 'config-title' => 'Усталяваньне MediaWiki $1',
- 'config-information' => 'Інфармацыя',
- 'config-localsettings-upgrade' => 'Выяўлены файл <code>LocalSettings.php</code>.
-Каб абнавіць гэтае усталяваньне, калі ласка, увядзіце значэньне <code>$wgUpgradeKey</code> у полі ніжэй.
-Яго можна знайсьці ў <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Быў знойдзены файл <code>LocalSettings.php</code>.
-Каб зьмяніць гэтае ўсталяваньне, калі ласка, запусьціце <code>update.php</code>',
- 'config-localsettings-key' => 'Ключ паляпшэньня:',
- 'config-localsettings-badkey' => 'Пададзены Вамі ключ зьяўляецца няслушным',
- 'config-upgrade-key-missing' => 'Выяўленае існуючае ўсталяваньне MediaWiki.
-Каб абнавіць гэтае ўсталяваньне, калі ласка, устаўце наступны радок у канец Вашага <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Выглядае, што існуючы <code>LocalSettings.php</code> зьяўляецца няпоўным.
-Не ўстаноўленая пераменная $1.
-Калі ласка, зьмяніце <code>LocalSettings.php</code> так, каб была ўстаноўленая гэтая пераменная, і націсьніце «{{int:Config-continue}}».',
- 'config-localsettings-connection-error' => 'Адбылася памылка падчас злучэньня з базай зьвестак з выкарыстаньнем наладаў, пазначаных у <code>LocalSettings.php</code> ці <code>AdminSettings.php</code>. Калі ласка, выпраўце гэтыя налады і паспрабуйце зноў.
-
-$1',
- 'config-session-error' => 'Памылка стварэньня сэсіі: $1',
- 'config-session-expired' => 'Скончыўся тэрмін дзеяньня зьвестак сэсіі.
-Сэсія мае абмежаваны тэрмін у $1.
-Вы можаце павялічыць яго, зьмяніўшы парамэтар <code>session.gc_maxlifetime</code> у php.ini.
-Перазапусьціце праграму ўсталяваньня.',
- 'config-no-session' => 'Зьвесткі сэсіі згубленыя!
-Праверце php.ini і ўпэўніцеся, што ўстаноўлены слушны шлях у <code>session.save_path</code>.',
- 'config-your-language' => 'Вашая мова:',
- 'config-your-language-help' => 'Выберыце мову для выкарыстаньня падчас усталяваньня.',
- 'config-wiki-language' => 'Мова вікі:',
- 'config-wiki-language-help' => 'Выберыце мову, на якой пераважна будзе пісацца зьмест у вікі.',
- 'config-back' => '← Назад',
- 'config-continue' => 'Далей →',
- 'config-page-language' => 'Мова',
- 'config-page-welcome' => 'Вітаем у MediaWiki!',
- 'config-page-dbconnect' => 'Падключэньне да базы зьвестак',
- 'config-page-upgrade' => 'Абнавіць існуючую ўстаноўку',
- 'config-page-dbsettings' => 'Налады базы зьвестак',
- 'config-page-name' => 'Назва',
- 'config-page-options' => 'Налады',
- 'config-page-install' => 'Усталяваць',
- 'config-page-complete' => 'Зроблена!',
- 'config-page-restart' => 'Пачаць усталяваньне зноў',
- 'config-page-readme' => 'Дадатковыя зьвесткі',
- 'config-page-releasenotes' => 'Заўвагі да выпуску',
- 'config-page-copying' => 'Капіяваньне',
- 'config-page-upgradedoc' => 'Абнаўленьне',
- 'config-page-existingwiki' => 'Існуючая вікі',
- 'config-help-restart' => 'Ці жадаеце выдаліць усе ўведзеныя зьвесткі і пачаць працэс усталяваньня зноў?',
- 'config-restart' => 'Так, пачаць зноў',
- 'config-welcome' => '== Праверка асяродзьдзя ==
-Праверка патрэбная для запэўніваньня, што гэтае асяродзьдзе слушнае для ўсталяваньня MediaWiki.
-Вам патрэбна будзе падаць усе вынікі праверкі, калі спатрэбіцца дапамога падчас усталяваньня.',
- 'config-copyright' => "== Аўтарскае права і ўмовы ==
-
-$1
-
-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 <doclink href=Copying>a copy of the GNU General Public License</doclink> along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. or [http://www.gnu.org/copyleft/gpl.html read it online].",
- 'config-sidebar' => '* [//www.mediawiki.org Хатняя старонка MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Даведка для ўдзельнікаў]
-* [//www.mediawiki.org/wiki/Manual:Contents Даведка для адміністратараў]
-* [//www.mediawiki.org/wiki/Manual:FAQ Адказы на частыя пытаньні]
-----
-* <doclink href=Readme>Прачытайце</doclink>
-* <doclink href=ReleaseNotes>Паляпшэньні ў вэрсіі</doclink>
-* <doclink href=Copying>Капіяваньне</doclink>
-* <doclink href=UpgradeDoc>Абнаўленьне</doclink>',
- 'config-env-good' => 'Асяродзьдзе было праверанае.
-Вы можаце ўсталёўваць MediaWiki.',
- 'config-env-bad' => 'Асяродзьдзе было праверанае.
-Усталяваньне MediaWiki немагчымае.',
- 'config-env-php' => 'Усталяваны PHP $1.',
- 'config-env-php-toolow' => 'Усталяваны PHP $1.
-Але MediaWiki патрабуе PHP вэрсіі $2 ці навейшай.',
- 'config-unicode-using-utf8' => 'Выкарыстоўваецца бібліятэка Unicode-нармалізацыі Браяна Вібэра',
- 'config-unicode-using-intl' => 'Выкарыстоўваецца [http://pecl.php.net/intl intl пашырэньне з PECL] для Unicode-нармалізацыі',
- 'config-unicode-pure-php-warning' => "'''Папярэджаньне''': [http://pecl.php.net/intl Пашырэньне intl з PECL] — ня слушнае для Unicode-нармалізацыі, цяпер выкарыстоўваецца марудная PHP-рэалізацыя.
-Калі ў Вас сайт з высокай наведваемасьцю, раім пачытаць пра [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-нармалізацыю].",
- 'config-unicode-update-warning' => "'''Папярэджаньне''': усталяваная вэрсія бібліятэкі для Unicode-нармалізацыі выкарыстоўвае састарэлую вэрсію бібліятэкі з [http://site.icu-project.org/ праекту ICU].
-Раім [//www.mediawiki.org/wiki/Unicode_normalization_considerations абнавіць], калі ваш сайт будзе працаваць зь Unicode.",
- 'config-no-db' => 'Немагчыма знайсьці адпаведны драйвэр базы зьвестак. Вам неабходна ўсталяваць драйвэр базы зьвестак для PHP.
-Падтрымліваюцца наступныя тыпы базаў зьвестак: $1.
-
-Калі вы выкарыстоўваеце агульны хостынг, запытайцеся ў свайго хостынг-правайдэра наконт усталяваньня патрабуемага драйвэру базы зьвестак.
-Калі Вы кампілявалі PHP самастойна, пераканфігуруйце і сабярыце яго з дазволеным кліентам базаў зьвестак, напрыклад, <code>./configure --with-mysql</code>.
-Калі Вы ўсталёўвалі PHP з пакетаў Debian ці Ubuntu, то Вам трэба ўсталяваць дадаткова модуль <code>php5-mysql</code>.',
- 'config-outdated-sqlite' => "'''Папярэджаньне''': усталяваны SQLite $1, у той час, калі мінімальная сумяшчальная вэрсія — $2. SQLite ня будзе даступны.",
- 'config-no-fts3' => "'''Папярэджаньне''': SQLite створаны без модуля [//sqlite.org/fts3.html FTS3], для гэтага ўнутранага інтэрфэйсу ня будзе даступная магчымасьць пошуку.",
- 'config-register-globals' => "'''Папярэджаньне: уключаная опцыя PHP <code>[http://php.net/register_globals register_globals]</code>.'''
-'''Адключыце яе, калі можаце.'''
-MediaWiki будзе працаваць, але гэта панізіць узровень бясьпекі сэрвэра.",
- 'config-magic-quotes-runtime' => "'''Фатальная памылка: уключаная опцыя PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
-Гэтая опцыя псуе ўводны паток зьвестак непрадказальным чынам.
-Працяг усталяваньня альбо выкарыстаньне MediaWiki без адключэньня гэтай опцыі немагчымыя.",
- 'config-magic-quotes-sybase' => "'''Фатальная памылка: рэжым [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] уключаны!'''
-Гэты рэжым шкодзіць уваходныя зьвесткі непрадказальным чынам.
-Працяг усталяваньня альбо выкарыстаньне MediaWiki немагчымыя, пакуль рэжым ня будзе выключаны.",
- 'config-mbstring' => "'''Фатальная памылка: рэжым [http://www.php.net/manual/en/ref.info.php#mbstring.overload mbstring.func_overload] уключаны!'''
-Гэты рэжым выклікае памылкі і можа шкодзіць зьвесткі непрадказальным чынам.
-Працяг усталяваньня альбо выкарыстаньне MediaWiki немагчымыя, пакуль рэжым ня будзе выключаны.",
- 'config-ze1' => "'''Фатальная памылка: рэжым [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] уключаны!'''
-Гэтая рэжым стварае вялікія праблемы ў працы MediaWiki.
-Працяг усталяваньня альбо выкарыстаньне MediaWiki немагчымыя, пакуль рэжым ня будзе выключаны.",
- 'config-safe-mode' => "'''Папярэджаньне:''' [http://www.php.net/features.safe-mode бясьпечны рэжым] PHP уключаны.
-Гэта можа выклікаць праблемы, галоўным чынам падчас загрузак файлаў і ў падтрымцы <code>math</code>.",
- 'config-xml-bad' => 'Ня знойдзены модуль XML для PHP.
-MediaWiki патрэбныя функцыі з гэтага модулю, таму MediaWiki ня будзе працаваць у гэтай канфігурацыі.
-Калі Вы выкарыстоўваеце Mandrake, усталюйце пакет php-xml.',
- 'config-pcre' => 'Ня знойдзены модуль падтрымкі PCRE.
-MediaWiki для працы патрабуюцца функцыі рэгулярных выразаў у стылі Perl.',
- 'config-pcre-no-utf8' => "'''Фатальная памылка''': модуль PCRE для PHP скампіляваны без падтрымкі PCRE_UTF8.
-MediaWiki патрабуе падтрымкі UTF-8 для слушнай працы.",
- 'config-memory-raised' => 'Абмежаваньне на даступную для PHP памяць <code>memory_limit</code> было падвышанае з $1 да $2.',
- 'config-memory-bad' => "'''Папярэджаньне:''' памер PHP <code>memory_limit</code> складае $1.
-Верагодна, гэта вельмі мала.
-Усталяваньне можа быць няўдалым!",
- 'config-ctype' => "'''Фатальная памылка''': PHP мусіць быць скампіляваны з падтрымкай [http://www.php.net/manual/en/ctype.installation.php пашырэньня Ctype].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] усталяваны',
- 'config-apc' => '[http://www.php.net/apc APC] усталяваны',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] усталяваны',
- 'config-no-cache' => "'''Папярэджаньне:''' немагчыма знайсьці [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ці [http://www.iis.net/download/WinCacheForPhp WinCache].
-Аб’ектнае кэшаваньне ня ўключанае.",
- 'config-mod-security' => "'''Папярэджаньне''': на Вашым ўэб-сэрверы ўключаны [http://modsecurity.org/ mod_security]. У выпадку няслушнай наладцы, ён можа стаць прычынай праблемаў для MediaWiki ці іншага праграмнага забесьпячэньня, якое дазваляе ўдзельнікам дасылаць на сэрвэр любы зьмест.
-Глядзіце [http://modsecurity.org/documentation/ дакумэнтацыю mod_security] ці зьвярніцеся ў падтрымку Вашага хосту, калі ў Вас узьнікаюць выпадковыя праблемы.",
- 'config-diff3-bad' => 'GNU diff3 ня знойдзены.',
- 'config-git' => 'Знойдзеная сыстэма канстролю вэрсіяў Git: <code>$1</code>',
- 'config-git-bad' => 'Сыстэма кантролю вэрсіяў Git ня знойдзеная.',
- 'config-imagemagick' => 'Знойдзены ImageMagick: <code>$1</code>.
-Пасьля ўключэньня загрузак будзе ўключанае маштабаваньне выяваў.',
- 'config-gd' => 'GD падтрымліваецца ўбудавана.
-Пасьля ўключэньня загрузак будзе ўключанае маштабаваньне выяваў.',
- 'config-no-scaling' => 'Ні GD, ні ImageMagick ня знойдзеныя.
-Маштабаваньне выяваў будзе адключанае.',
- 'config-no-uri' => "'''Памылка:''' Не магчыма вызначыць цяперашні URI.
-Усталяваньне спыненае.",
- 'config-no-cli-uri' => "'''Папярэджаньне''': Не пазначаны --scriptpath, па змоўчваньні выкарыстоўваецца: <code>$1</code>.",
- 'config-using-server' => 'Выкарыстоўваецца назва сэрвэра «<nowiki>$1</nowiki>».',
- 'config-using-uri' => 'Выкарыстоўваецца URL-спасылка сэрвэра «<nowiki>$1$2</nowiki>».',
- 'config-uploads-not-safe' => "'''Папярэджаньне:''' дырэкторыя для загрузак па змоўчваньні <code>$1</code> уразьлівая да выкананьня адвольнага коду.
-Хоць MediaWiki і правярае ўсе файлы перад захаваньнем, вельмі рэкамэндуецца [//www.mediawiki.org/wiki/Manual:Security#Upload_security закрыць гэтую ўразьлівасьць] перад уключэньнем магчымасьці загрузкі файлаў.",
- 'config-no-cli-uploads-check' => "'''Папярэджаньне:''' Вашая дырэкторыя для загрузак па змоўчваньні (<code>$1</code>), не правераная на ўразьлівасьць да выкананьня адвольных скрыптоў падчас усталяваньня CLI.
-.",
- 'config-brokenlibxml' => 'У Вашай сыстэме ўсталяваныя PHP і libxml2 зь несумяшчальнымі вэрсіямі, што можа прывесьці да пашкоджаньня зьвестак MediaWiki і іншых ўэб-дастасаваньняў.
-Абнавіце PHP да вэрсіі 5.2.9 ці болей позьняй, а libxml2 да 2.7.3 ці болей позьняй ([//bugs.php.net/bug.php?id=45996 паведамленьне пра памылку на сайце PHP]).
-Усталяваньне перарванае.',
- 'config-using531' => 'PHP $1 не сумяшчальнае з MediaWiki з-за памылкі ў перадачы парамэтраў па ўказальніку да <code>__call()</code>.
-Абнавіце PHP да вэрсіі 5.3.2 ці болей позьняй, ці адкаціце да вэрсіі 5.3.0 каб гэта выправіць.
-Усталяваньне перарванае.',
- 'config-suhosin-max-value-length' => 'Suhosin усталяваны і абмяжоўвае <code>даўжыню</code> парамэтра GET да $1 {{PLURAL:$1|байта|байтаў}}.
-ResourceLoader, складнік MediaWiki, будзе абходзіць гэтае абмежаваньне, што, адаб’ецца на прадукцыйнасьці.
-Калі магчыма, варта ўсталяваць у <code>php.ini</code> <code>suhosin.get.max_value_length</code> роўным 1024 ці больш, а таксама вызначыць тое ж значэньне для <code>$wgResourceLoaderMaxQueryLength</code> у LocalSettings.php.',
- 'config-db-type' => 'Тып базы зьвестак:',
- 'config-db-host' => 'Хост базы зьвестак:',
- 'config-db-host-help' => 'Калі сэрвэр Вашай базы зьвестак знаходзіцца на іншым сэрвэры, увядзіце тут імя хоста ці IP-адрас.
-
-Калі Вы карыстаецеся shared-хостынгам, Ваш хостынг-правайдэр мусіць даць Вам слушнае імя хоста базы зьвестак у сваёй дакумэнтацыі.
-
-Калі Вы усталёўваеце сэрвэр Windows з выкарыстаньнем MySQL, выкарыстаньне «localhost» можа не працаваць для назвы сэрвэра. У гэтым выпадку паспрабуйце пазначыць «127.0.0.1» для лякальнага IP-адраса.
-
-Калі Вы выкарыстоўваеце PostgreSQL, пакіньце поле пустым, каб далучыцца праз Unix-сокет.',
- 'config-db-host-oracle' => 'TNS базы зьвестак:',
- 'config-db-host-oracle-help' => 'Увядзіце слушнае [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm лякальнае імя злучэньня]; файл tnsnames.ora павінен быць бачным для гэтага ўсталяваньня.<br />Калі Вы выкарыстоўваеце кліенцкія бібліятэкі 10g ці больш новыя, Вы можаце таксама выкарыстоўваць мэтад наданьня назваў [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm лёгкае злучэньне].',
- 'config-db-wiki-settings' => 'Ідэнтыфікацыя гэтай вікі',
- 'config-db-name' => 'Назва базы зьвестак:',
- 'config-db-name-help' => 'Выберыце імя для вызначэньня Вашай вікі.
-Яно ня мусіць зьмяшчаць прагалаў.
-
-Калі Вы набываеце shared-хостынг, Ваш хостынг-правайдэр мусіць надаць Вам ці пэўнае імя базы зьвестак для выкарыстаньня, ці магчымасьць ствараць базы зьвестак праз кантрольную панэль.',
- 'config-db-name-oracle' => 'Схема базы зьвестак:',
- 'config-db-account-oracle-warn' => 'Існуюць тры сцэнары ўсталяваньня Oracle як базы зьвестак для MediaWiki:
-
-Калі Вы жадаеце стварыць рахунак базы зьвестак як частку працэсу ўсталяваньня, калі ласка, падайце рахунак з роляй SYSDBA як рахунак базы зьвестак для ўсталяваньня і пазначце пажаданыя правы рахунку з доступам да Інтэрнэту, у адваротным выпадку Вы можаце таксама стварыць рахунак з доступам да Інтэрнэту ўручную і падаць толькі гэты рахунак (калі патрабуюцца правы для стварэньня схемы аб’ектаў) ці падайце два розных рахункі, адзін з правамі на стварэньне і адзін з абмежаваньнямі для доступу да Інтэрнэту.
-
-Скрыпт для стварэньня рахунку з патрабуемымі правамі можна знайсьці ў дырэкторыі гэтага ўсталяваньня «maintenance/oracle/». Памятайце, што выкарыстаньне рахунку з абмежаваньнямі адключыць усе падтрымліваемыя магчымасьці даступныя па змоўчваньні.',
- 'config-db-install-account' => 'Імя карыстальніка для ўсталяваньня',
- 'config-db-username' => 'Імя карыстальніка базы зьвестак:',
- 'config-db-password' => 'Пароль базы зьвестак:',
- 'config-db-password-empty' => 'Калі ласка, увядзіце пароль для новага карыстальніка базы зьвестак: $1.
-Магчыма стварыць карыстальніка без паролю, але гэта небясьпечна.',
- 'config-db-install-username' => 'Увядзіце імя карыстальніка, якое будзе выкарыстоўвацца для злучэньня з базай зьвестак падчас усталяваньня. Гэта не назва рахунку MediaWiki; гэта імя карыстальніка Вашай базы зьвестак.',
- 'config-db-install-password' => 'Увядзіце пароль, які будзе выкарыстоўвацца для злучэньня з базай зьвестак падчас усталяваньня. Гэта не пароль рахунку MediaWiki; гэта пароль Вашай базы зьвестак.',
- 'config-db-install-help' => 'Увядзіце імя карыстальніка і пароль, якія будуць выкарыстаныя для далучэньня да базы зьвестак падчас працэсу ўсталяваньня.',
- 'config-db-account-lock' => 'Выкарыстоўваць тыя ж імя карыстальніка і пароль пасьля ўсталяваньня',
- 'config-db-wiki-account' => 'Імя карыстальніка для працы',
- 'config-db-wiki-help' => 'Увядзіце імя карыстальніка і пароль, якія будуць выкарыстаныя для далучэньня да базы зьвестак падчас працы (пасьля ўсталяваньня).
-Калі рахунак ня створаны, а рахунак для ўсталяваньня мае значныя правы, гэты рахунак будзе створаны зь мінімальна патрэбнымі для працы вікі правамі.',
- 'config-db-prefix' => 'Прэфікс табліцаў базы зьвестак:',
- 'config-db-prefix-help' => 'Калі Вы разьдзяляеце адну базу зьвестак паміж некалькімі вікі, ці паміж MediaWiki і іншым вэб-дастасаваньнем, можаце вызначыць прэфікс назваў табліцаў для пазьбяганьня канфліктаў.
-Пазьбягайце прагалаў.
-
-Гэтае поле звычайна пакідаецца пустым.',
- 'config-db-charset' => 'Кадаваньне базы зьвестак',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 UTF-8 з адваротнай сумяшчальнасьцю',
- 'config-charset-help' => "'''Папярэджаньне:''' калі Вы выкарыстоўваеце '''UTF-8 з адваротнай сумяшчальнасьцю''' на MySQL 4.1+ і зробіце рэзэрвовую копію праз <code>mysqldump</code>, ён можа зьнішчыць усе не-ASCII-сымбалі беспаваротна!
-
-У '''бінарным (binary)''' рэжыме MediaWiki захоўвае тэксты ў UTF-8 у палёх тыпу binary.
-Гэты рэжым болей эфэктыўны за рэжым MySQL UTF-8 і дазваляе выкарыстоўваць увесь абсяг сымбаляў Unicode.
-У рэжыме '''UTF-8''' MySQL будзе ведаць, у якім кадаваньне Вы зьмяшчаеце зьвесткі, і будзе вяртаць іх у адпаведным кадаваньні,
-але MySQL ня можа ўтрымліваць сымбалі па-за [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Стандартным шматмоўным пластом] сымбаляў Unicode.",
- 'config-mysql-old' => 'Патрабуецца MySQL $1 ці навейшая, усталяваная вэрсія $2.',
- 'config-db-port' => 'Порт базы зьвестак:',
- 'config-db-schema' => 'Схема для MediaWiki',
- 'config-db-schema-help' => 'Гэтая схема слушная ў большасьці выпадкаў.
-Зьмяняйце яе толькі тады, калі Вы ведаеце, што гэта неабходна.',
- 'config-pg-test-error' => "Немагчыма далучыцца да базы зьвестак '''$1''': $2",
- 'config-sqlite-dir' => 'Дырэкторыя зьвестак SQLite:',
- 'config-sqlite-dir-help' => "SQLite захоўвае ўсе зьвесткі ў адзіным файле.
-
-Пададзеная Вамі дырэкторыя павінна быць даступнай да запісу вэб-сэрвэрам падчас усталяваньня.
-
-Яна '''ня''' мусіць быць даступнай праз Сеціва, вось чаму мы не захоўваем яе ў адным месцы з файламі PHP.
-
-Праграма ўсталяваньня дадаткова створыць файл <code>.htaccess</code>, але калі ён не выкарыстоўваецца, хто заўгодна зможа атрымаць зьвесткі з базы зьвестак.
-Гэта ўключае як прыватныя зьвесткі ўдзельнікаў (адрасы электроннай пошты, хэшы пароляў), гэтак і выдаленыя вэрсіі старонак і іншыя зьвесткі, доступ да якіх маецца абмежаваны.
-
-Падумайце над тым, каб зьмяшчаць базу зьвестак у іншым месцы, напрыклад у <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Прастора табліцаў па змоўчваньні:',
- 'config-oracle-temp-ts' => 'Часовая прастора табліцаў:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki падтрымлівае наступныя сыстэмы базаў зьвестак:
-
-$1
-
-Калі Вы ня бачыце сыстэму базаў зьвестак, якую Вы спрабуеце выкарыстоўваць ў сьпісе ніжэй, перайдзіце па спасылцы інструкцыі, якая знаходзіцца ніжэй, каб уключыць падтрымку.',
- 'config-support-mysql' => '* $1 зьяўляецца галоўнай мэтай MediaWiki і падтрымліваецца лепей за ўсё ([http://www.php.net/manual/en/mysql.installation.php як кампіляваць PHP з падтрымкай MySQL])',
- 'config-support-postgres' => '* $1 — вядомая сыстэма базы зьвестак з адкрытым кодам, якая зьяўляецца альтэрнатывай MySQL ([http://www.php.net/manual/en/pgsql.installation.php як кампіляваць PHP з падтрымкай PostgreSQL]). Яна можа ўтрымліваць дробныя памылкі, і не рэкамэндуецца выкарыстоўваць яе для працуючых праектаў.',
- 'config-support-sqlite' => '* $1 — невялікая сыстэма базы зьвестак, якая мае вельмі добрую падтрымку. ([http://www.php.net/manual/en/pdo.installation.php як кампіляваць PHP з падтрымкай SQLite], выкарыстоўвае PDO)',
- 'config-support-oracle' => '* $1 зьяўляецца камэрцыйнай прафэсійнай базай зьвестак. ([http://www.php.net/manual/en/oci8.installation.php Як скампіляваць PHP з падтрымкай OCI8])',
- 'config-header-mysql' => 'Налады MySQL',
- 'config-header-postgres' => 'Налады PostgreSQL',
- 'config-header-sqlite' => 'Налады SQLite',
- 'config-header-oracle' => 'Налады Oracle',
- 'config-invalid-db-type' => 'Няслушны тып базы зьвестак',
- 'config-missing-db-name' => 'Вы павінны ўвесьці значэньне парамэтру «Імя базы зьвестак»',
- 'config-missing-db-host' => 'Вы павінны ўвесьці значэньне парамэтру «Хост базы зьвестак»',
- 'config-missing-db-server-oracle' => 'Вы павінны ўвесьці значэньне парамэтру «TNS базы зьвестак»',
- 'config-invalid-db-server-oracle' => 'Няслушнае TNS базы зьвестак «$1».
-Назва можа ўтрымліваць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), сымбалі падкрэсьліваньня(_) і кропкі (.).', # Fuzzy
- 'config-invalid-db-name' => 'Няслушная назва базы зьвестак «$1».
-Назва можа ўтрымліваць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), сымбалі падкрэсьліваньня(_) і працяжнікі (-).',
- 'config-invalid-db-prefix' => 'Няслушны прэфікс базы зьвестак «$1».
-Ён можа зьмяшчаць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), сымбалі падкрэсьліваньня (_) і працяжнікі (-).',
- 'config-connection-error' => '$1.
-
-Праверце хост, імя карыстальніка і пароль ніжэй і паспрабуйце зноў.',
- 'config-invalid-schema' => 'Няслушная схема для MediaWiki «$1».
-Выкарыстоўвайце толькі ASCII-літары (a-z, A-Z), лічбы (0-9) і сымбалі падкрэсьліваньня (_).',
- 'config-db-sys-create-oracle' => 'Праграма ўсталяваньня падтрымлівае толькі выкарыстаньне рахунку SYSDBA для стварэньня новага рахунку.',
- 'config-db-sys-user-exists-oracle' => 'Рахунак карыстальніка «$1» ужо існуе. SYSDBA можа выкарыстоўвацца толькі для стварэньня новых рахункаў!',
- 'config-postgres-old' => 'Патрабуецца PostgreSQL $1 ці навейшая, усталяваная вэрсія $2.',
- 'config-sqlite-name-help' => 'Выберыце назву, якая будзе ідэнтыфікаваць Вашую вікі.
-Не выкарыстоўвайце прагалы ці злучкі.
-Назва будзе выкарыстоўвацца ў назьве файла зьвестак SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Немагчыма стварыць дырэкторыю зьвестак <code><nowiki>$1</nowiki></code>, таму што бацькоўская дырэкторыя <code><nowiki>$2</nowiki></code> абароненая ад запісаў вэб-сэрвэра.
-
-Праграма ўсталяваньня вызначыла карыстальніка, які запусьціў вэб-сэрвэр.
-Дазвольце запісы ў дырэкторыю <code><nowiki>$3</nowiki></code> для працягу.
-У сыстэме Unix/Linux зрабіце:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Немагчыма стварыць дырэкторыю зьвестак <code><nowiki>$1</nowiki></code>, таму што бацькоўская дырэкторыя <code><nowiki>$2</nowiki></code> абароненая ад запісаў вэб-сэрвэра.
-
-Праграма ўсталяваньня вызначыла карыстальніка, які запусьціў вэб-сэрвэр.
-Дазвольце яму (і іншым) запісы ў дырэкторыю <code><nowiki>$3</nowiki></code> для працягу.
-У сыстэме Unix/Linux зрабіце:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Памылка падчас стварэньня дырэкторыі «$1».
-Праверце шлях і паспрабуйце зноў.',
- 'config-sqlite-dir-unwritable' => 'Запіс у дырэкторыю «$1» немагчымы.
-Зьмяніце налады доступу, каб вэб-сэрвэр меў правы на запіс, і паспрабуйце зноў.',
- 'config-sqlite-connection-error' => '$1.
-
-Праверце дырэкторыю для зьвестак, назву базы зьвестак і паспрабуйце зноў.',
- 'config-sqlite-readonly' => 'Файл <code>$1</code> недаступны для запісу.',
- 'config-sqlite-cant-create-db' => 'Немагчыма стварыць файл базы зьвестак <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP бракуе падтрымкі FTS3 — табліцы пагаршаюцца',
- 'config-can-upgrade' => "У гэтай базе зьвестак ёсьць табліцы MediaWiki.
-Каб абнавіць іх да MediaWiki $1, націсьніце '''Працягнуць'''.",
- 'config-upgrade-done' => "Абнаўленьне завершанае.
-
-Цяпер Вы можаце [$1 пачаць выкарыстаньне вікі].
-
-Калі Вы жадаеце рэгенэраваць <code>LocalSettings.php</code>, націсьніце кнопку ніжэй.
-Гэтае дзеяньне '''не рэкамэндуецца''', калі Вы ня маеце праблемаў у працы вікі.",
- 'config-upgrade-done-no-regenerate' => 'Абнаўленьне скончанае.
-
-Цяпер Вы можаце [$1 пачаць працу з вікі].',
- 'config-regenerate' => 'Рэгенэраваць LocalSettings.php →',
- 'config-show-table-status' => "Запыт '<code>SHOW TABLE STATUS</code>' не атрымаўся!",
- 'config-unknown-collation' => "'''Папярэджаньне:''' база зьвестак выкарыстоўвае нераспазнанае супастаўленьне.",
- 'config-db-web-account' => 'Рахунак базы зьвестак для вэб-доступу',
- 'config-db-web-help' => 'Выберыце імя карыстальніка і пароль, які выкарыстоўваецца вэб-сэрвэрам для злучэньня з сэрвэрам базы зьвестак, падчас звычайных апэрацыяў вікі.',
- 'config-db-web-account-same' => 'Выкарыстоўваць той жа рахунак, што для ўсталяваньня',
- 'config-db-web-create' => 'Стварыць рахунак, калі ён яшчэ не існуе',
- 'config-db-web-no-create-privs' => 'Рахунак, які Вы пазначылі для ўсталяваньня ня мае правоў для стварэньня рахунку.
-Рахунак, які Вы пазначылі тут, мусіць ужо існаваць.',
- 'config-mysql-engine' => 'Рухавік сховішча:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Папярэджаньне''': Вы выбралі MyISAM у якасьці рухавіка для захоўваньня зьвестак у MySQL, які не рэкамэндуецца да выкарыстаньня з MediaWiki па прычынах:
-* кепская падтрымка паралельнай апрацоўкі з-за таблічных блякаваньняў;
-* большая імавернасьць пашкоджаньня зьвестак у параўнаньні зь іншымі рухавікамі;
-* код MediaWiki не ва ўсіх выпадках улічвае асаблівасьці MyISAM.
-
-Калі Ваш MySQL-сэрвэр падтрымлівае InnoDB, вельмі рэкамэндуецца выкарыстаньне менавіта гэтага рухавіка.
-Калі MySQL-сэрвэр не падтрымлівае InnoDB, пэўна, настаў час абнавіць яго.",
- 'config-mysql-engine-help' => "'''InnoDB''' — звычайна найбольш слушны варыянт, таму што добра падтрымлівае паралелізм.
-
-'''MyISAM''' можа быць хутчэйшай у вікі з адным удзельнікам, ці толькі для чытаньня.
-Базы зьвестак на MyISAM вядомыя тым, што ў іх зьвесткі шкодзяцца нашмат часьцей за InnoDB.",
- 'config-mysql-charset' => 'Кадаваньне базы зьвестак:',
- 'config-mysql-binary' => 'Двайковае',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "У '''двайковым рэжыме''', MediaWiki захоўвае тэкст у кадаваньні UTF-8 у базе зьвестак у двайковых палях.
-Гэта болей эфэктыўна за рэжым MySQL UTF-8, і дазваляе Вам выкарыстоўваць увесь дыяпазон сымбаляў Unicode.
-
-У '''рэжыме UTF-8''', MySQL ведае, якая табліцы сымбаляў выкарыстоўваецца ў Вашых зьвестках, і можа адпаведна прадстаўляць і канвэртаваць іх, але гэта не дазволіць Вам захоўваць сымбалі па-за межамі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базавага шматмоўнага дыяпазону].",
- 'config-site-name' => 'Назва вікі:',
- 'config-site-name-help' => 'Назва будзе паказвацца ў загалоўку браўзэра і ў некаторых іншых месцах.',
- 'config-site-name-blank' => 'Увядзіце назву сайта.',
- 'config-project-namespace' => 'Прастора назваў праекту:',
- 'config-ns-generic' => 'Праект',
- 'config-ns-site-name' => 'Такая ж, як і назва вікі: $1',
- 'config-ns-other' => 'Іншая (вызначце)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => "Па прыкладу Вікіпэдыі, шматлікія вікі трымаюць уласныя старонкі з правіламі асобна ад старонак са зьместам, у «'''прасторы назваў праекту'''».
-Усе назвы старонак у гэтай прасторы назваў пачынаюцца з прыстаўкі, якую Вы можаце пазначыць тут.
-Традыцыйна, гэтая прыстаўка вытворная ад назвы вікі, яле яна ня можа ўтрымліваць некаторыя сымбалі, такія як «#» ці «:».",
- 'config-ns-invalid' => 'Пададзеная няслушная прастора назваў «<nowiki>$1</nowiki>».
-Падайце іншую прастору назваў праекту.',
- 'config-ns-conflict' => 'Пазначаная прастора назваў «<nowiki>$1</nowiki>» канфліктуе з прасторай назваў MediaWiki па змоўчваньні.
-Пазначце іншую прастору назваў праекту.',
- 'config-admin-box' => 'Рахунак адміністратара',
- 'config-admin-name' => 'Вашае імя:',
- 'config-admin-password' => 'Пароль:',
- 'config-admin-password-confirm' => 'Пароль яшчэ раз:',
- 'config-admin-help' => 'Увядзіце тут Вашае імя ўдзельніка, напрыклад «Янка Кавалевіч».
-Гэтае імя будзе выкарыстоўвацца для ўваходу ў вікі.',
- 'config-admin-name-blank' => 'Увядзіце імя адміністратара.',
- 'config-admin-name-invalid' => 'Пададзенае няслушнае імя ўдзельніка «<nowiki>$1</nowiki>».
-Падайце іншае імя ўдзельніка.',
- 'config-admin-password-blank' => 'Увядзіце пароль рахунку адміністратара.',
- 'config-admin-password-same' => 'Пароль ня можа быць аднолькавым зь іменем удзельніка.',
- 'config-admin-password-mismatch' => 'Уведзеныя Вамі паролі не супадаюць.',
- 'config-admin-email' => 'Адрас электроннай пошты:',
- 'config-admin-email-help' => 'Увядзіце тут адрас электроннай пошты, каб атрымліваць электронныя лісты ад іншых удзельнікаў вікі, скідваць Ваш пароль і атрымліваць абвешчаньні пра зьмены старонак, якія знаходзяцца ў Вашым сьпісе назіраньня. Вы можаце пакінуць гэтае поле пустым.',
- 'config-admin-error-user' => 'Унутраная памылка падчас стварэньня рахунку адміністратара зь іменем «<nowiki>$1</nowiki>».',
- 'config-admin-error-password' => 'Унутраная памылка падчас устаноўкі паролю для адміністратара «<nowiki>$1</nowiki>»: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Вы ўвялі няслушны адрас электроннай пошты',
- 'config-subscribe' => 'Падпісацца на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў].',
- 'config-subscribe-help' => 'Гэта ня вельмі актыўны сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў, які ўключаючы важныя навіны пра бясьпеку.
-Вам неабходна падпісацца на яго і абнавіць Вашае ўсталяваньне MediaWiki, калі зьявяцца новыя вэрсіі.',
- 'config-subscribe-noemail' => 'Вы спрабавалі падпісацца на рассылку паведамленьняў пра выхад новых вэрсіяў, не пазначыўшы адрас электроннай пошты.
-Калі ласка, падайце слушны адрас, калі Вы жадаеце падпісацца на рассылку.',
- 'config-almost-done' => 'Вы амаль што скончылі!
-Астатнія налады можна прапусьціць і пачаць усталяваньне вікі.',
- 'config-optional-continue' => 'Задаць болей пытаньняў.',
- 'config-optional-skip' => 'Хопіць, проста ўсталяваць вікі.',
- 'config-profile' => 'Профіль правоў удзельніка:',
- 'config-profile-wiki' => 'Адкрытая вікі',
- 'config-profile-no-anon' => 'Патрэбнае стварэньне рахунку',
- 'config-profile-fishbowl' => 'Толькі для аўтарызаваных рэдактараў',
- 'config-profile-private' => 'Прыватная вікі',
- 'config-profile-help' => "Вікі працуюць лепей, калі Вы дазваляеце як мага большай колькасьці людзей рэдагаваць яе.
-У MediaWiki вельмі лёгка праглядаць апошнія зьмены і выпраўляць любыя пашкоджаньні зробленыя недасьведчанымі ўдзельнікамі альбо вандаламі.
-
-Тым ня менш, многія лічаць, што MediaWiki можа быць карыснай ў шматлікіх іншых ролях, і часта вельмі нялёгка растлумачыць усім перавагі выкарыстаньня тэхналёгіяў вікі.
-Таму Вы маеце выбар.
-
-'''{{int:config-profile-wiki}}''' дазваляе рэдагаваць усім, нават без уваходу ў сыстэму.
-Вікі з '''{{int:config-profile-no-anon}}''' дазваляе дадатковую адказнасьць, але можа адштурхнуць некаторых патэнцыйных удзельнікаў.
-
-Сцэнар '''{{int:config-profile-fishbowl}}''' дазваляе рэдагаваць зацьверджаным удзельнікам, але ўсе могуць праглядаць старонкі іх гісторыю.
-'''{{int:config-profile-private}}''' дазваляе праглядаць і рэдагаваць старонкі толькі зацьверджаным удзельнікам.
-
-Больш складаныя правы ўдзельнікаў даступныя пасьля ўсталяваньня, глядзіце [//www.mediawiki.org/wiki/Manual:User_rights адпаведную старонку дакумэнтацыі].", # Fuzzy
- 'config-license' => 'Аўтарскія правы і ліцэнзія:',
- 'config-license-none' => 'Без інфармацыі пра ліцэнзію',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (грамадзкі набытак)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 ці болей позьняя',
- 'config-license-pd' => 'Грамадзкі набытак',
- 'config-license-cc-choose' => 'Выберыце іншую ліцэнзію Creative Commons',
- 'config-license-help' => "Шматлікія адкрытыя вікі публікуюць увесь унёсак у праект на ўмовах [http://freedomdefined.org/Definition вольнай ліцэнзіі].
-Гэта дазваляе ствараць эфэкт супольнай уласнасьці і садзейнічае доўгатэрміноваму ўнёску.
-Для прыватных і карпаратыўных вікі гэта не зьяўляецца неабходнасьцю.
-
-Калі Вы жадаеце выкарыстоўваць тэкст зь Вікіпэдыі, і жадаеце, каб Вікіпэдыя магла прымаць тэксты, скапіяваныя з Вашай вікі, Вам неабходна выбраць ліцэнзію '''Creative Commons Attribution Share Alike'''.
-
-Раней Вікіпэдыя выкарыстоўвала ліцэнзію GNU Free Documentation.
-Яна ўсё яшчэ дзейнічае, але яна ўтрымлівае некаторыя моманты,
-якія ўскладняюць паўторнае выкарыстоўваньне і інтэрпрэтацыю матэрыялаў.",
- 'config-email-settings' => 'Налады электроннай пошты',
- 'config-enable-email' => 'Дазволіць выходзячыя электронныя лісты',
- 'config-enable-email-help' => 'Калі Вы жадаеце, каб працавала электронная пошта, неабходна сканфігураваць PHP [http://www.php.net/manual/en/mail.configuration.php адпаведным чынам].
-Калі Вы не жадаеце выкарыстоўваць магчымасьці электроннай пошты, Вы можаце яе адключыць.',
- 'config-email-user' => 'Дазволіць электронную пошту для сувязі паміж удзельнікамі',
- 'config-email-user-help' => 'Дазволіць усім удзельнікам дасылаць адзін аднаму электронныя лісты, калі ўключаная адпаведная магчымасьць ў іх наладах.',
- 'config-email-usertalk' => 'Уключыць абвяшчэньні пра паведамленьні на старонцы абмеркаваньня',
- 'config-email-usertalk-help' => 'Дазваляе ўдзельнікам атрымліваць абвяшчэньні пра зьмены на старонцы абмеркаваньня, калі гэтая магчымасьць уключаная ў іх наладах.',
- 'config-email-watchlist' => 'Уключыць абвяшчэньні пра зьмены ў сьпісе назіраньня',
- 'config-email-watchlist-help' => 'Дазваляе ўдзельнікам атрымліваць абвяшчэньні пра зьмены ў іх сьпісе назіраньня, калі гэтая магчымасьць уключаная ў іх наладах.',
- 'config-email-auth' => 'Уключыць аўтэнтыфікацыю праз электронную пошту',
- 'config-email-auth-help' => "Калі гэтая магчымасьць уключаная, удзельнікі павінны пацьвердзіць іх адрас электроннай пошты праз спасылку, якая дасылаецца ім праз электронную пошту. Яна дасылаецца і падчас зьмены адрасу электроннай пошты.
-Толькі аўтэнтыфікаваныя адрасы электроннай пошты могуць атрымліваць электронныя лісты ад іншых удзельнікаў, ці зьмяняць абвяшчэньні дасылаемыя праз электронную пошту.
-Уключэньне гэтай магчымасьці '''рэкамэндуецца''' для адкрытых вікі, з-за магчымых злоўжываньняў магчымасьцямі электроннай пошты.",
- 'config-email-sender' => 'Адрас электроннай пошты для вяртаньня:',
- 'config-email-sender-help' => 'Увядзіце адрас электроннай пошты для вяртаньня ў якасьці адрасу дасылаемых электронных лістоў.
-Сюды будуць дасылацца неатрыманыя электронныя лісты.
-Шматлікія паштовыя сэрвэры патрабуюць, каб хаця б назва дамэну была слушнай.',
- 'config-upload-settings' => 'Загрузкі выяваў і файлаў',
- 'config-upload-enable' => 'Дазволіць загрузку файлаў',
- 'config-upload-help' => 'Дазвол загрузкі файлаў можа патэнцыйна пагражаць бясьпекі сэрвэра.
-Дадатковую інфармацыю можна атрымаць ў [//www.mediawiki.org/wiki/Manual:Security разьдзеле бясьпекі].
-
-Каб дазволіць загрузку файлаў, зьмяніце рэжым падкаталёга <code>images</code> у карэннай дырэкторыі MediaWiki так, каб ўэб-сэрвэр меў доступ на запіс.
-Потым дазвольце гэтую магчымасьць.',
- 'config-upload-deleted' => 'Дырэкторыя для выдаленых файлаў:',
- 'config-upload-deleted-help' => 'Выберыце дырэкторыю, у якой будуць захоўвацца выдаленыя файлы.
-У ідэальным выпадку, яна не павінна мець доступу з Інтэрнэту.',
- 'config-logo' => 'URL-адрас лягатыпу:',
- 'config-logo-help' => 'Афармленьне MediaWiki па змоўчваньні уключае прастору для лягатыпу памерам 135×160 піксэляў у верхнім левым куце.
-Загрузіце выяву адпаведнага памеру і увядзіце тут URL-адрас.
-
-Калі Вы не жадаеце мець ніякага лягатыпу, пакіньце поле пустым.', # Fuzzy
- 'config-instantcommons' => 'Дазволіць Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — магчымасьць, якая дазваляе вікі выкарыстоўваць выявы, гукі і іншыя мэдыя, якія знаходзяцца на сайце [//commons.wikimedia.org/ Wikimedia Commons].
-Каб гэта зрабіць, MediaWiki патрабуе доступу да Інтэрнэту.
-
-Каб даведацца болей пра гэтую магчымасьць, уключаючы інструкцыю пра тое, як яе ўстанавіць ў любой вікі, акрамя Wikimedia Commons, глядзіце [//mediawiki.org/wiki/Manual:$wgForeignFileRepos дакумэнтацыю].',
- 'config-cc-error' => 'Выбар ліцэнзіі Creative Commons ня даў вынікаў.
-Увядзіце назву ліцэнзіі ўручную.',
- 'config-cc-again' => 'Выберыце яшчэ раз…',
- 'config-cc-not-chosen' => 'Выберыце, якую ліцэнзію Creative Commons Вы жадаеце выкарыстоўваць і націсьніце «працягваць».',
- 'config-advanced-settings' => 'Дадатковыя налады',
- 'config-cache-options' => 'Налады кэшаваньня аб’ектаў:',
- 'config-cache-help' => 'Кэшаваньне аб’ектаў павялічвае хуткасьць працы MediaWiki праз кэшаваньне зьвестак, якія часта выкарыстоўваюцца.
-Вельмі рэкамэндуем уключыць гэта для сярэдніх і буйных сайтаў, таксама будзе карысна для дробных сайтаў.',
- 'config-cache-none' => 'Без кэшаваньня (ніякія магчымасьці не страчваюцца, але хуткасьць працы буйных сайтаў можа зьнізіцца)',
- 'config-cache-accel' => 'Кэшаваньне аб’ектаў PHP (APC, XCache ці WinCache)',
- 'config-cache-memcached' => 'Выкарыстоўваць Memcached (патрабуе дадатковай канфігурацыі)',
- 'config-memcached-servers' => 'Сэрвэры memcached:',
- 'config-memcached-help' => 'Сьпіс IP-адрасоў, якія будуць выкарыстоўвацца Memcached.
-Адрасы павінны быць у асобным радку з пазначэньнем порту, які будзе выкарыстоўвацца. Напрыклад:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Вы выбралі Memcached у якасьці тыпу Вашага кэша, але не пазначылі ніякага сэрвэра',
- 'config-memcache-badip' => 'Вы ўвялі няслушны IP-адрас для Memcached: $1',
- 'config-memcache-noport' => 'Вы не пазначылі порт для выкарыстаньня сэрвэрам Memcached: $1.
-Калі Вы ня ведаеце порт, то па змоўчваньні выкарыстоўваецца 11211',
- 'config-memcache-badport' => 'Нумар порту Memcached павінен быць паміж $1 і $2',
- 'config-extensions' => 'Пашырэньні',
- 'config-extensions-help' => 'Пашырэньні пададзеныя вышэй, былі знойдзеныя ў Вашай дырэкторыі <code>./extensions</code>.
-
-Яны могуць патрабаваць дадатковых наладаў, але іх можна ўключыць зараз',
- 'config-install-alreadydone' => "'''Папярэджаньне:''' здаецца, што Вы ўжо ўсталёўвалі MediaWiki і спрабуеце зрабіць гэтай зноў.
-Калі ласка, перайдзіце на наступную старонку.",
- 'config-install-begin' => 'Пасьля націску кнопкі «{{int:config-continue}}» пачнецца ўсталяваньне MediaWiki.
-Калі Вы жадаеце што-небудзь зьмяніць, націсьніце кнопку «Вярнуцца».', # Fuzzy
- 'config-install-step-done' => 'зроблена',
- 'config-install-step-failed' => 'не атрымалася',
- 'config-install-extensions' => 'Уключаючы пашырэньні',
- 'config-install-database' => 'Налада базы зьвестак',
- 'config-install-schema' => 'Стварэньне схемы',
- 'config-install-pg-schema-not-exist' => 'Схема PostgreSQL не існуе',
- 'config-install-pg-schema-failed' => 'Немагчыма стварыць табліцу.
-Упэўніцеся, што карыстальнік «$1» можа пісаць у схему «$2».',
- 'config-install-pg-commit' => 'Захаваньне зьменаў',
- 'config-install-pg-plpgsql' => 'Праверка падтрымкі мовы PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Вам неабходна ўсталяваць падтрымку мовы PL/pgSQL у базе зьвестак $1',
- 'config-pg-no-create-privs' => 'Рахунак, які Вы пазначылі для ўсталяваньня ня мае дастаткова правоў для стварэньня рахунку.',
- 'config-pg-not-in-role' => 'Пазначаны Вамі рахунак для ўэб-карыстальніка ўжо існуе.
-Пазначаны Вамі рахунак для ўсталяваньня ня мае правоў і не зьяўляецца сябрам ролі ўэб-карыстальніка, таму немагчыма стварыць аб’екты, якія належаць ўэб-карыстальніку.
-
-Цяпер MediaWiki патрабуе, каб табліцы належалі да ўэб-карыстальніку. Калі ласка, пазначце іншы рахунак, ці націсьніце кнопку «Вярнуцца» і пазначце карыстальніка з неабходнымі для ўсталяваньня правамі.',
- 'config-install-user' => 'Стварэньне карыстальніка базы зьвестак',
- 'config-install-user-alreadyexists' => 'Удзельнік «$1» ужо існуе',
- 'config-install-user-create-failed' => 'Немагчыма стварыць ўдзельніка «$1»: $2',
- 'config-install-user-grant-failed' => 'Немагчыма даць правы удзельніку «$1»: $2',
- 'config-install-user-missing' => 'Пазначаны карыстальнік «$1» не існуе.',
- 'config-install-user-missing-create' => 'Пазначаны карыстальнік «$1» не існуе.
-Калі ласка, пазначце «стварыць рахунак», калі Вы жадаеце яго стварыць.',
- 'config-install-tables' => 'Стварэньне табліцаў',
- 'config-install-tables-exist' => "'''Папярэджаньне''': Выглядае, што табліцы MediaWiki ужо існуюць.
-Стварэньне прапушчанае.",
- 'config-install-tables-failed' => "'''Памылка''': немагчыма стварыць табліцы з-за наступнай памылкі: $1",
- 'config-install-interwiki' => 'Запаўненьне табліцы інтэрвікі па змоўчваньні',
- 'config-install-interwiki-list' => 'Немагчыма знайсьці файл <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Папярэджаньне''': выглядае, што табліца інтэрвікі ўжо запоўненая.
-Сьпіс па змоўчваньні прапушчаны.",
- 'config-install-stats' => 'Ініцыялізацыі статыстыкі',
- 'config-install-keys' => 'Стварэньне сакрэтных ключоў',
- 'config-insecure-keys' => "'''Папярэджаньне:''' {{PLURAL:$2|Ключ бясьпекі $1 створаны|Ключы бясьпекі $1 створаныя}} падчас усталяваньня, не зьяўляюцца поўнасьцю бясьпечнымі. Рэкамэндуецца зьмяніць {{PLURAL:$2|яго ўручную|іх уручную}}.",
- 'config-install-sysop' => 'Стварэньне рахунку адміністратара',
- 'config-install-subscribe-fail' => 'Немагчыма падпісацца на «mediawiki-announce»: $1',
- 'config-install-subscribe-notpossible' => 'cURL не ўсталяваны, allow_url_fopen недаступны.',
- 'config-install-mainpage' => 'Стварэньне галоўнай старонкі са зьместам па змоўчваньні',
- 'config-install-extension-tables' => 'Стварэньне табліцаў для ўключаных пашырэньняў',
- 'config-install-mainpage-failed' => 'Немагчыма ўставіць галоўную старонку: $1',
- 'config-install-done' => "'''Віншуем!'''
-Вы пасьпяхова ўсталявалі MediaWiki.
-
-Праграма ўсталяваньня стварыла файл <code>LocalSettings.php</code>.
-Ён утрымлівае ўсе Вашыя налады.
-
-Вам неабходна загрузіць яго і захаваць у карэнную дырэкторыю Вашай вікі (у тую ж самую дырэкторыю, дзе знаходзіцца index.php). Загрузка павінна пачацца аўтаматычна.
-
-Калі загрузка не пачалася, ці Вы яе адмянілі, Вы можаце перазапусьціць яе націснуўшы на спасылку ніжэй:
-
-$3
-
-'''Заўвага''': калі Вы гэтага ня зробіце зараз, то створаны файл ня будзе даступны Вам потым, калі Вы выйдзеце з праграмы ўсталяваньня без яго загрузкі.
-
-Калі Вы гэта зробіце, Вы можаце '''[$2 ўвайсьці ў Вашую вікі]'''.",
- 'config-download-localsettings' => 'Загрузіць <code>LocalSettings.php</code>',
- 'config-help' => 'дапамога',
- 'config-nofile' => 'Файл «$1» ня знойдзены. Ці быў ён выдалены?',
- 'mainpagetext' => "'''MediaWiki пасьпяхова ўсталяваная.'''",
- 'mainpagedocfooter' => 'Глядзіце [//meta.wikimedia.org/wiki/Help:Contents дапаможнік карыстальніка] для атрыманьня інфармацыі па карыстаньні вікі-праграмамі.
-
-== З чаго пачаць ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Сьпіс парамэтраў канфігурацыі]
-* [//www.mediawiki.org/wiki/Manual:FAQ Частыя пытаньні MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка паведамленьняў пра зьяўленьне новых вэрсіяў MediaWiki]', # Fuzzy
-);
-
-/** Bulgarian (български)
- * @author DCLXVI
- * @author 아라
- */
-$messages['bg'] = array(
- 'config-desc' => 'Инсталатор на МедияУики',
- 'config-title' => 'Инсталиране на МедияУики $1',
- 'config-information' => 'Информация',
- 'config-localsettings-upgrade' => 'Беше открит файл <code>LocalSettings.php</code>.
-За надграждане на съществуващата инсталация, необходимо е в кутията по-долу да се въведе стойността на <code>$wgUpgradeKey</code>.
-Тази информация е налична в <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Беше открит файл <code>LocalSettings.php</code>.
-За надграждане на наличната инсталация, необходимо е да се стартира <code>update.php</code>',
- 'config-localsettings-key' => 'Ключ за надграждане:',
- 'config-localsettings-badkey' => 'Предоставеният ключ е неправилен.',
- 'config-upgrade-key-missing' => 'Беше открита съществуваща инсталация на МедияУики.
-За надграждане на съществуващата инсталация, необходимо е да се постави следният ред в края на файла <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Съществуващият файл <code>LocalSettings.php</code> изглежда непълен.
-Променливата $1 не е зададена.
-Необходимо е да се редактира файлът <code>LocalSettings.php</code> и да се зададе променливата, след което да се натисне „{{int:Config-continue}}“.',
- 'config-localsettings-connection-error' => 'Възникна грешка при свързване с базата от данни чрез данните, посочени в <code>LocalSettings.php</code> или <code>AdminSettings.php</code>. Необходимо е да се коригират тези настройки преди повторен опит за свързване.
-
-$1',
- 'config-session-error' => 'Грешка при създаване на сесия: $1',
- 'config-session-expired' => 'Срокът на валидност на данните от сесията са изтекли.
-Продължителността на сесиите е настроена на $1.
-Това може да бъде увеличено чрез настройване на <code>session.gc_maxlifetime</code> в php.ini.
-Необходимо е рестартиране на инсталационния процес.',
- 'config-no-session' => 'Данните за сесията бяха загубени!
-Проверете вашия php.ini и се уверете, че на <code>session.save_path</code> е настроена подходящата директория.',
- 'config-your-language' => 'Вашият език:',
- 'config-your-language-help' => 'Избиране на език за използване по време на инсталацията.',
- 'config-wiki-language' => 'Език на уикито:',
- 'config-wiki-language-help' => 'Избиране на език, на който ще е основното съдържание на уикито.',
- 'config-back' => '← Връщане',
- 'config-continue' => 'Продължаване →',
- 'config-page-language' => 'Език',
- 'config-page-welcome' => 'Добре дошли в МедияУики!',
- 'config-page-dbconnect' => 'Свързване с базата от данни',
- 'config-page-upgrade' => 'Надграждане на съществуваща инсталация',
- 'config-page-dbsettings' => 'Настройки на базата от данни',
- 'config-page-name' => 'Име',
- 'config-page-options' => 'Настройки',
- 'config-page-install' => 'Инсталиране',
- 'config-page-complete' => 'Готово!',
- 'config-page-restart' => 'Рестартиране на инсталацията',
- 'config-page-readme' => 'Информация за софтуера',
- 'config-page-releasenotes' => 'Бележки за версията',
- 'config-page-copying' => 'Лицензно споразумение',
- 'config-page-upgradedoc' => 'Надграждане',
- 'config-page-existingwiki' => 'Съществуващо уики',
- 'config-help-restart' => 'Необходимо е потвърждение за изтриване на всички въведени и съхранени данни и започване отначало на процеса по инсталация.',
- 'config-restart' => 'Да, започване отначало',
- 'config-welcome' => '=== Проверка на средата ===
-Извършени бяха основни проверки, за да се провери дали средата е подходяща за инсталиране на МедияУики.
-Ако е необходима помощ по време на инсталацията, резултатите от направените проверки трябва също да бъдат предоставени.',
- 'config-copyright' => "=== Авторски права и Условия ===
-
-$1
-
-Тази програма е свободен софтуер, който може да се променя и/или разпространява според Общия публичен лиценз на GNU, както е публикуван от Free Software Foundation във версия на Лиценза 2 или по-късна версия.
-
-Тази програма се разпространява с надеждата, че ще е полезна, но '''без каквито и да е гаранции'''; без дори косвена гаранция за '''продаваемост''' или '''прогодност за конкретна употреба'''.
-За повече подробности се препоръчва преглеждането на Общия публичен лиценз на GNU.
-
-Към програмата трябва да е приложено <doclink href=Copying>копие на Общия публичен лиценз на GNU</doclink>; ако не, можете да пишете на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. или да [http://www.gnu.org/copyleft/gpl.html го прочетете онлайн].",
- 'config-sidebar' => '* [//www.mediawiki.org Сайт на МедияУики]
-* [//www.mediawiki.org/wiki/Help:Contents Наръчник на потребителя]
-* [//www.mediawiki.org/wiki/Manual:Contents Наръчник на администратора]
-* [//www.mediawiki.org/wiki/Manual:FAQ ЧЗВ]
-----
-* <doclink href=Readme>Документация</doclink>
-* <doclink href=ReleaseNotes>Бележки за версията</doclink>
-* <doclink href=Copying>Авторски права</doclink>
-* <doclink href=UpgradeDoc>Обновяване</doclink>',
- 'config-env-good' => 'Средата беше проверена.
-Инсталирането на МедияУики е възможно.',
- 'config-env-bad' => 'Средата беше проверена.
-Не е възможна инсталация на МедияУики.',
- 'config-env-php' => 'Инсталирана е версия на PHP $1.',
- 'config-env-php-toolow' => 'Инсталирана е версия на PHP $1.
-МедияУики изисква версия PHP $2 или по-нова.',
- 'config-unicode-using-utf8' => 'Използване на utf8_normalize.so от Brion Vibber за нормализация на Уникод.',
- 'config-unicode-using-intl' => 'Използване на разширението [http://pecl.php.net/intl intl PECL] за нормализация на Уникод.',
- 'config-unicode-pure-php-warning' => "'''Предупреждение''': [http://pecl.php.net/intl Разширението intl PECL] не е налично за справяне с нормализацията на Уникод, превключване към по-бавното изпълнение на чист PHP.
-Ако сайтът е с голям трафик, препоръчително е запознаването с [//www.mediawiki.org/wiki/Unicode_normalization_considerations нормализацията на Уникод].",
- 'config-unicode-update-warning' => "'''Предупреждение''': Инсталираната версия на Обвивката за нормализация на Unicode използва по-старата версия на библиотеката на [http://site.icu-project.org/ проекта ICU].
-Необходимо е да [//www.mediawiki.org/wiki/Unicode_normalization_considerations инсталирате по-нова верия], в случай че сте загрижени за използването на Unicode.",
- 'config-no-db' => 'Не може да бъде открит подходящ драйвер за база от данни! Необходимо е да се инсталира драйвер за база от данни за PHP.
-Поддържат се следните типове базни от данни: $1.
-
-Ако използвате споделен хостинг, помолете доставчика на услугата да инсталира подходящ драйвер за база от данни.
-Ако сами сте компилирали PHP, преконфигурирайте го с включен клиент за база от данни, например чрез използване на <code>./configure --with-mysql</code>.
-Ако сте инсталирали PHP от пакет за Debian или Убунту, необходимо е, също така, да инсталирате и модула php5-mysql.',
- 'config-no-fts3' => "'''Предупреждение''': SQLite е компилирана без [//sqlite.org/fts3.html модула FTS3], затова възможностите за търсене няма да са достъпни.",
- 'config-register-globals' => "'''Предупреждение: Настройката на PHP <code>[http://php.net/register_globals register_globals]</code> е включена.'''
-'''При възможност е препоръчително тя да бъде изключена.'''
-МедияУики ще работи, но сървърът е изложен на евентуални пропуски в сигурността.",
- 'config-magic-quotes-runtime' => "'''Фатално: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] е активирана!'''
-Това може да повреди непредвидимо въвеждането на данните.
-Инсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
- 'config-magic-quotes-sybase' => "'''Фатално: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] е активирана!'''
-Това може да повреди непредвидимо въвеждането на данните.
-Инсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
- 'config-mbstring' => "'''Фатално: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] е активирана!'''
-Това може да повреди непредвидимо въвеждането на данните.
-Инсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
- 'config-ze1' => "'''Фатално: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] е активирана!'''
-Тази настройка причинява ужасни грешки в МедияУики.
-Невъзможно е инсталирането и използването на МедияУики докато тази настройка не бъде изключена.",
- 'config-safe-mode' => "'''Предупреждение:''' PHP работи в [http://www.php.net/features.safe-mode безопасен режим].
-Това може да създаде проблеми, особено ако качването на файлове е разрешено, както и при поддръжката на <code>math</code>.",
- 'config-xml-bad' => 'Липсва XML модулът на PHP.
-МедияУики се нуждае от някои функции от този модул и няма да работи при наличната конфигурация.
-При Mandrake, необходимо е да се инсталира пакетът php-xml.',
- 'config-pcre' => 'Липсва модулът PCRE.
-За да работи, МедияУики изисква съвместими с Perl функии за регилярни изрази.',
- 'config-pcre-no-utf8' => "'''Фатално''': Модулът PCRE на PHP изглежда е компилиран без поддръжка на PCRE_UTF8.
-За да функционира правилно, МедияУики изисква поддръжка на UTF-8.",
- 'config-memory-raised' => '<code>memory_limit</code> на PHP е $1, увеличаване до $2.',
- 'config-memory-bad' => "'''Предупреждение:''' <code>memory_limit</code> на PHP е $1.
-Стойността вероятно е твърде ниска.
-Възможно е инсталацията да се провали!",
- 'config-ctype' => "'''Фатално''': Необходимо е PHP да бъде компилиран с поддръжка на [http://www.php.net/manual/en/ctype.installation.php разширението Ctype].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] е инсталиран',
- 'config-apc' => '[http://www.php.net/apc APC] е инсталиран',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] е инсталиран',
- 'config-no-cache' => "'''Предупреждение:''' Не бяха открити [http://www.php.net/apc APC] [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
-Обектното кеширане не е включено.",
- 'config-diff3-bad' => 'GNU diff3 не беше намерен.',
- 'config-imagemagick' => 'Открит е ImageMagick: <code>$1</code>.
-Преоразмеряването на картинки ще бъде включено ако качването на файлове бъде разрешено.',
- 'config-gd' => 'Открита е вградена графичната библиотека GD.
-Ако качването на файлове бъде включено, ще бъде включена възможността за преоразмеряване на картинки.',
- 'config-no-scaling' => 'Не са открити библиотеките GD или ImageMagick.
-Преоразмеряването на картинки ще бъде изключено.',
- 'config-no-uri' => "'''Грешка:''' Не може да се определи текущия адрес.
-Инсталация беше прекратена.",
- 'config-using-server' => 'Използване на сървърното име "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Използване на сървърния адрес (URL) "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Предупреждение:''' Папката по подразбиране за качване <code>$1</code> е уязвима от изпълнение на зловредни скриптове.
-Въпреки че МедияУики извършва проверка за заплахи в сигурността на всички качени файлове, силно препоръчително е да се [//www.mediawiki.org/wiki/Manual:Security#Upload_security затвори тази уязвимост в сигурността] преди разрешаване за качване на файлове.",
- 'config-brokenlibxml' => 'Вашата система използа комбинация от версии на PHP и libxml2, които са с много грешки и могат да причинят скрити повреди на данните в МедияУики или други уеб приложения.
-Необходимо е обновяване до PHP 5.2.9 или по-нова версия и libxml2 2.7.3 или по-нова версия ([//bugs.php.net/bug.php?id=45996 докладвана грешка при PHP]).
-Инсталацията беше прекратена.',
- 'config-using531' => 'МедияУики не може да се използва с PHP $1 заради проблем с референтните параметри за <code>__call()</code>.
-За разрешаване на този проблем е необходимо да се обнови до PHP 5.3.2 или по-нова версия или да се инсталира по-стара версия, напр. PHP 5.3.0.
-Инсталацията беше прекратена.',
- 'config-suhosin-max-value-length' => 'Suhosin е инсталиран и ограничава дължината на параметъра GET на $1 байта. Компонентът на МедияУики ResourceLoader ще може да пренебрегне частично това ограничение, но това ще намали производителността. По възможност е препоръчително да се настрои <code>suhosin.get.max_value_length</code> на 1024 или по-голяма стойност в <code>php.ini</code> и в LocalSettings.php да се настрои <code>$wgResourceLoaderMaxQueryLength</code> със същата стойност.', # Fuzzy
- 'config-db-type' => 'Тип на базата от данни:',
- 'config-db-host' => 'Хост на базата от данни:',
- 'config-db-host-help' => 'Ако базата от данни е на друг сървър, в кутията се въвежда името на хоста или IP адреса.
-
-Ако се използва споделен уеб хостинг, доставчикът на услугата би трябвало да е предоставил в документацията си коректния хост.
-
-Ако инсталацията протича на Windows-сървър и се използва MySQL, използването на "localhost" може да е неприемливо. В такива случаи се използва "127.0.0.1" за локален IP адрес.
-
-При използване на PostgreSQL, това поле се оставя празно, за свързване чрез Unix socket.',
- 'config-db-host-oracle' => 'TNS на базата данни:',
- 'config-db-wiki-settings' => 'Идентифициране на това уики',
- 'config-db-name' => 'Име на базата от данни:',
- 'config-db-name-help' => 'Избира се име, което да идентифицира уикито.
-То не трябва да съдържа интервали.
-
-Ако се използва споделен хостинг, доставчикът на услугата би трябвало да е предоставил или име на базата от данни, която да бъде използвана, или да позволява създаването на бази от данни чрез контролния панел.',
- 'config-db-name-oracle' => 'Схема на базата от данни:',
- 'config-db-install-account' => 'Потребителска сметка за инсталацията',
- 'config-db-username' => 'Потребителско име за базата от данни:',
- 'config-db-password' => 'Парола за базата от данни:',
- 'config-db-password-empty' => 'Въведете парола за новия потребител на базата от данни: $1.
-Въпреки че е допустимо да се създават потребители без пароли, това е незащитено действие.',
- 'config-db-install-username' => 'Въвежда се потребителско име, което ще се използва за свързване с базата от данни по време на процеса по инсталация.
-Това не е потребителско име за сметка в МедияУики; това е потребителско име за базата от данни.',
- 'config-db-install-password' => 'Въвежда се парола, която ще бъде използвана за свързване с базата от данни по време на инсталационния процес.
-Това не е парола за сметка в МедияУики; това е парола за базата от данни.',
- 'config-db-install-help' => 'Въвеждат се потребителско име и парола, които ще бъдат използвани за свързване с базата от данни по време на инсталационния процес.',
- 'config-db-account-lock' => 'Използване на същото потребителско име и парола по време на нормална работа',
- 'config-db-wiki-account' => 'Потребителска сметка за нормална работа',
- 'config-db-wiki-help' => 'Въвежда се потребителско име и парола, които ще се използват при нормалното функциониране на уикито.
-Ако сметката не съществува и използваната при инсталацията сметка има необходимите права, тази потребителска сметка ще бъде създадена с минималните необходими права за работа с уикито.',
- 'config-db-prefix' => 'Представка за таблиците в базата от данни:',
- 'config-db-prefix-help' => 'Ако е необходимо да се сподели базата от данни между няколко уикита или между МедияУики и друго уеб приложение, може да се добави представка пред имената на таблиците, за да се избегнат конфликти.
-Не се използват интервали.
-
-Това поле обикновено се оставя празно.',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 бинарно',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 с обратна съвестимост с UTF-8',
- 'config-mysql-old' => 'Изисква се MySQL $1 или по-нова версия, наличната версия е $2.',
- 'config-db-port' => 'Порт на базата от данни:',
- 'config-db-schema' => 'Схема за МедияУики',
- 'config-db-schema-help' => 'Схемата по-горе обикновено е коректна.
-Промени се извършват ако наистина е необходимо.',
- 'config-pg-test-error' => "Невъзможно свързване с базата данни '''$1''': $2",
- 'config-sqlite-dir' => 'Директория за данни на SQLite:',
- 'config-sqlite-dir-help' => "SQLite съхранява всички данни в един файл.
-
-По време на инсталацията уеб сървърът трябва да има права за писане в посочената директория.
-
-Тя '''не трябва''' да е достъпна през уеб, затова не е там, където са PHP файловете.
-
-Инсталаторът ще съхрани заедно с нея файл <code>.htaccess</code>, но ако този метод пропадне, някой може да придобие даостъп до суровите данни от базата от данни.
-Това включва сурови данни за потребителите (адреси за е-поща, хеширани пароли), както и изтрити версии на страници и друга чувствителна и с ограничен достъп информация от и за уикито.
-
-Базата от данни е препоръчително да се разположи на друго място, например в <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-support-info' => 'МедияУики поддържа следните системи за бази от данни:
-
-$1
-
-Ако не виждате желаната за използване система в списъка по-долу, следвайте инструкциите за активиране на поддръжка по-горе.',
- 'config-support-mysql' => '* $1 е най-добре поддържаната система за база от данни, с най-добра поддръжка от МедияУики ([http://www.php.net/manual/en/mysql.installation.php Как се компилира PHP с поддръжка на MySQL])',
- 'config-support-postgres' => '* $1 е популярна система за бази от данни с отворен изходен код, която е алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php как се компилира PHP с поддръжка на PostgreSQL]). Възможно е все още да има грешки, затова не се препоръчва да се използва в общодостъпна среда.',
- 'config-support-sqlite' => '* $1 е лека система за база от данни, която е много добре поддържана. ([http://www.php.net/manual/en/pdo.installation.php Как се компилира PHP с поддръжка на SQLite], използва PDO)',
- 'config-support-oracle' => '* $1 е комерсиална корпоративна база от данни. ([http://www.php.net/manual/en/oci8.installation.php Как се компилира PHP с поддръжка на OCI8])',
- 'config-header-mysql' => 'Настройки за MySQL',
- 'config-header-postgres' => 'Настройки за PostgreSQL',
- 'config-header-sqlite' => 'Настройки за SQLite',
- 'config-header-oracle' => 'Настройки за Oracle',
- 'config-invalid-db-type' => 'Невалиден тип база от данни',
- 'config-missing-db-name' => 'Необходимо е да се въведе стойност за "Име на базата от данни"',
- 'config-missing-db-host' => 'Необходимо е да се въведе стойност за "Хост на базата от данни"',
- 'config-missing-db-server-oracle' => 'Необходимо е да се въведе стойност за "Database TNS"',
- 'config-invalid-db-server-oracle' => 'Невалиден TNS на базата от данни "$1".
-Допустими са само ASCII букви (a-z, A-Z), цифри (0-9), символите за долна черта (_) и точка (.).',
- 'config-invalid-db-name' => 'Невалидно име на базата от данни "$1".
-Използват се само ASCII букви (a-z, A-Z), цифри (0-9), долни черти (_) и тирета (-).',
- 'config-invalid-db-prefix' => 'Невалидна представка за базата от данни "$1".
-Позволени са само ASCII букви (a-z, A-Z), цифри (0-9), долни черти (_) и тирета (-).',
- 'config-connection-error' => '$1.
-
-Необходимо е да се проверят хостът, потребителското име и паролата, след което да се опита отново.',
- 'config-invalid-schema' => 'Невалидна схема за МедияУики "$1".
-Допустими са само ASCII букви (a-z, A-Z), цифри (0-9) и долни черти (_).',
- 'config-db-sys-create-oracle' => 'Инсталаторът поддържа само сметка SYSDBA за създаване на нова сметка.',
- 'config-db-sys-user-exists-oracle' => 'Потребителската сметка "$1" вече съществува. SYSDBA може да се използва само за създаване на нова сметка!',
- 'config-postgres-old' => 'Изисква се PostgreSQL $1 или по-нова версия, наличната версия е $2.',
- 'config-sqlite-name-help' => 'Избира се име, което да идентифицира уикито.
-Не се използват интервали или тирета.
-Това име ще се използва за име на файла за данни на SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Дикректорията за данни <code><nowiki>$1</nowiki></code> не може да бъде създадена, тъй като уеб сървърът няма права за писане в родителската директория <code><nowiki>$2</nowiki></code>.
-
-Инсталаторът разпознава потребителското име, с което работи уеб сървърът.
-Уверете се, че той притежава права за писане в директорията <code><nowiki>$3</nowiki></code> преди да продължите.
-В Unix/Линукс системи можете да използвате:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Дикректорията за данни <code><nowiki>$1</nowiki></code> не може да бъде създадена, тъй като уеб сървърът няма права за писане в родителската директория <code><nowiki>$2</nowiki></code>.
-
-Инсталаторът не може да определи потребителското име, с което работи уеб сървърът.
-Уверете се, че в директория <code><nowiki>$3</nowiki></code> може да бъде писано от уебсървъра (или от други потребители!) преди да продължите.
-На Unix/Линукс системи можете да използвате:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Грешка при създаване на директорията за данни "$1".
-Проверете местоположението ѝ и опитайте отново.',
- 'config-sqlite-dir-unwritable' => 'Уебсървърът няма права за писане в директория "$1".
-Променете правата му така, че да може да пише в нея, и опитайте отново.',
- 'config-sqlite-connection-error' => '$1.
-
-Проверете директорията за данни и името на базата от данни по-долу и опитайте отново.',
- 'config-sqlite-readonly' => 'Файлът <code>$1</code> няма права за писане.',
- 'config-sqlite-cant-create-db' => 'Файлът за базата от данни <code>$1</code> не може да бъде създаден.',
- 'config-sqlite-fts3-downgrade' => 'Липсва поддръжката на FTS3 за PHP, извършен беше downgradе на таблиците',
- 'config-can-upgrade' => "В базата от данни има таблици за МедияУики.
-За надграждането им за MediaWiki $1, натиска се '''Продължаване'''.",
- 'config-upgrade-done' => "Обновяването приключи.
-
-Вече е възможно [$1 да използвате уикито].
-
-Ако е необходимо, възможно е файлът <code>LocalSettings.php</code> да бъде създаден отново чрез натискане на бутона по-долу.
-Това '''не е препоръчително действие''', освен ако не срещате затруднения с уикито.",
- 'config-upgrade-done-no-regenerate' => 'Обновяването приключи.
-
-Вече е възможно [$1 да използвате уикито].',
- 'config-regenerate' => 'Създаване на LocalSettings.php →',
- 'config-show-table-status' => 'Заявката <code>SHOW TABLE STATUS</code> не сполучи!',
- 'config-unknown-collation' => "'''Предупреждение:''' Базата от данни използва неразпозната колация.",
- 'config-db-web-account' => 'Сметка за уеб достъп до базата от данни',
- 'config-db-web-help' => 'Избиране на потребителско име и парола, които уеб сървърът ще използва да се свързва с базата от данни при обичайната работа на уикито.',
- 'config-db-web-account-same' => 'Използване на същата сметка като при инсталацията.',
- 'config-db-web-create' => 'Създаване на сметката ако все още не съществува',
- 'config-db-web-no-create-privs' => 'Посочената сметка за инсталацията не разполага с достатъчно права за създаване на нова сметка.
-Необходимо е посочената сметка вече да съществува.',
- 'config-mysql-engine' => 'Хранилище на данни:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Предупреждение''': Избрана е MyISAM като система за складиране в MySQL, която не се препоръчва за използване с МедияУики, защото:
-* почти не поддържа паралелност заради заключване на таблиците
-* е по-податлива на повреди в сравнение с други системи
-* кодът на МедияУики не винаги поддържа MyISAM коректно
-
-Ако инсталацията на MySQL поддържа InnoDB, силно е препоръчително да се използва тя.
-Ако инсталацията на MySQL не поддържа InnoDB, вероятно е време за обновяване.",
- 'config-mysql-engine-help' => "'''InnoDB''' почти винаги е най-добрата възможност заради навременната си поддръжка.
-
-'''MyISAM''' може да е по-бърза при инсталации с един потребител или само за четене.
-Базите от данни MyISAM се повреждат по-често от InnoDB.",
- 'config-mysql-charset' => 'Набор от символи в базата от данни:',
- 'config-mysql-binary' => 'Бинарен',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "В '''бинарен режим''' МедияУики съхранява текстовете в UTF-8 в бинарни полета в базата от данни.
-Това е по-ефективно от UTF-8 режима на MySQL и позволява използването на пълния набор от символи в Уникод.
-
-В '''UTF-8 режим''' MySQL ще знае в кой набор от символи са данните от уикито и ще може да ги показва и променя по подходящ начин, но няма да позволява складиране на символи извън [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основния многоезичен набор].",
- 'config-site-name' => 'Име на уикито:',
- 'config-site-name-help' => 'Това име ще се показва в заглавната лента на браузъра и на различни други места.',
- 'config-site-name-blank' => 'Необходимо е да се въведе име на уикито.',
- 'config-project-namespace' => 'Именно пространство на проекта:',
- 'config-ns-generic' => 'Проект',
- 'config-ns-site-name' => 'Същото като името на уикито: $1',
- 'config-ns-other' => 'Друго (уточняване)',
- 'config-ns-other-default' => 'МоетоУики',
- 'config-project-namespace-help' => 'Следвайки примера на Уикипедия, много уикита съхраняват страниците си с правила в "\'\'\'именно пространство на проекта\'\'\'", отделно от основното съдържание.
-Всички заглавия на страниците в това именно пространство започват с определена представка, която може да бъде зададена тук.
-Обикновено представката произлиза от името на уикито, но не може да съдържа символи като "#" или ":".',
- 'config-ns-invalid' => 'Посоченото именно пространство "<nowiki>$1</nowiki>" е невалидно.
-Необходимо е да бъде посочено друго.',
- 'config-ns-conflict' => 'Посоченото именно пространство "<nowiki>$1</nowiki>" е в конфликт с използваното по подразбиране именно пространство MediaWiki.
-Необходимо е да се посочи друго именно пространство.',
- 'config-admin-box' => 'Администраторска сметка',
- 'config-admin-name' => 'Потребителско име:',
- 'config-admin-password' => 'Парола:',
- 'config-admin-password-confirm' => 'Парола (повторно):',
- 'config-admin-help' => 'Въвежда се предпочитаното потребителско име, например "Иванчо Иванчев".
-Това ще е потребителското име, което администраторът ще използва за влизане в уикито.',
- 'config-admin-name-blank' => 'Необходимо е да бъде въведено потребителско име на администратора.',
- 'config-admin-name-invalid' => 'Посоченото потребителско име "<nowiki>$1</nowiki>" е невалидно.
-Необходимо е да се посочи друго.',
- 'config-admin-password-blank' => 'Неовходимо е да се въведе парола за администраторската сметка.',
- 'config-admin-password-same' => 'Паролата не трябва да е същата като потребителското име.',
- 'config-admin-password-mismatch' => 'Двете въведени пароли не съвпадат.',
- 'config-admin-email' => 'Адрес за електронна поща:',
- 'config-admin-email-help' => 'Въвеждането на адрес за е-поща позволява получаване на е-писма от другите потребители на уикито, възстановяване на изгубена или забравена парола, оповестяване при промени в страниците от списъка за наблюдение. Това поле може да бъде оставено празно.',
- 'config-admin-error-user' => 'Възникна вътрешна грешка при създаване на администратор с името "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Възникна вътрешна грешка при задаване на парола за администратора "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Въведен е невалиден адрес за електронна поща',
- 'config-subscribe' => 'Абониране за [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce пощенския списък за нови версии].',
- 'config-subscribe-help' => 'Това е пощенски списък с малко трафик, който се използва за съобщения при излизане на нови версии, както и за важни проблеми със сигурността.
-Абонирането е препоръчително, както и надграждането на инсталацията на МедияУики при излизането на нова версия.',
- 'config-subscribe-noemail' => 'Опитахте да се абонирате за пощенския списък за нови версии без да посочите адрес за електронна поща.
-Необходимо е да се предостави адрес за електронна поща, в случай че желаете да се абонирате за пощенския списък.',
- 'config-almost-done' => 'Инсталацията е почти готова!
-Възможно е пропускане на оставащата конфигурация и моментално инсталиране на уикито.',
- 'config-optional-continue' => 'Задаване на допълнителни въпроси.',
- 'config-optional-skip' => 'Достатъчно, инсталиране на уикито.',
- 'config-profile' => 'Профил на потребителските права:',
- 'config-profile-wiki' => 'Отворено уики',
- 'config-profile-no-anon' => 'Необходимо е създаване на сметка',
- 'config-profile-fishbowl' => 'Само одобрени редактори',
- 'config-profile-private' => 'Затворено уики',
- 'config-profile-help' => "Уикитата функционират най-добре, когато позволяват на възможно най-много хора да ги редактират.
-В МедияУики лесно се преглеждат последните промени и се възстановяват пораженип от недобронамерени потребители.
-
-Въпреки това мнозина смятат МедияУики за полезен софтуер по различни начини и често е трудно да се убедят всички от предимствата на уики модела.
-Затова се предоставя възможност за избор.
-
-Уикитата от типа '''{{int:config-profile-wiki}}''' позволяват на всички потребители да редактират, дори и без регистрация.
-Уикитата от типа '''{{int:config-profile-no-anon}}''' позволяват достъп до страниците и редактирането им само след създаване на потребителска сметка.
-
-Уики, което е '''{{int:config-profile-fishbowl}}''' позволява на всички да преглеждат страниците, но само предварително одобрени редактори могат да редактират съдържанието.
-В '''{{int:config-profile-private}}''' само предварително одобрени потребители могат да четат и редактират съдържанието.
-
-Детайлно обяснение на конфигурациите на потребителските права е достъпно след инсталацията в [//www.mediawiki.org/wiki/Manual:User_rights Наръчника за потребителски права].", # Fuzzy
- 'config-license' => 'Авторски права и лиценз:',
- 'config-license-none' => 'Без лиценз',
- 'config-license-cc-by-sa' => 'Криейтив Комънс Признание-Споделяне на споделеното',
- 'config-license-cc-by' => 'Криейтив Комънс Признание',
- 'config-license-cc-by-nc-sa' => 'Криейтив Комънс Признание-Некомерсиално-Споделяне на споделеното',
- 'config-license-cc-0' => 'Криейтив Комънс Нула (обществено достояние)',
- 'config-license-gfdl' => 'Лиценз за свободна документация на GNU 1.3 или по-нов',
- 'config-license-pd' => 'Обществено достояние',
- 'config-license-cc-choose' => 'Избиране на друг лиценз от Криейтив Комънс',
- 'config-license-help' => "Много публични уикита поставят всички приноси под [http://freedomdefined.org/Definition/Bg свободен лиценз].
-Това помага създаване на усещане за общност и насърчава дългосрочните приноси.
-Това не е необходимо за частни или корпоративни уикита.
-
-Ако е необходимо да се използват текстове от Уикипедия, както и Уикипедия да може да използва текстове от уикито, необходимо е да се избере лиценз '''Криейтив Комънс Признание-Споделяне на споделеното'''.
-
-Лицензът за свободна документация на GNU е старият лиценз на съдържанието на Уикипедия.
-Той все още е валиден лиценз, но някои негови условия са трудни за разбиране и правят по-сложни повторното използване и интерпретацията.",
- 'config-email-settings' => 'Настройки за е-поща',
- 'config-enable-email' => 'Разрешаване на изходящи е-писма',
- 'config-enable-email-help' => 'За да работят възможностите за използване на е-поща, необходимо е [http://www.php.net/manual/en/mail.configuration.php настройките за поща на PHP] да бъдат конфигурирани правилно.
-Ако няма да се използват услугите за е-поща в уикито, те могат да бъдат изключени тук.',
- 'config-email-user' => 'Позволяване на потребителите да си изпращат е-писма през уикито',
- 'config-email-user-help' => 'Позволяване на потребителите да си изпращат е-писма ако са разрешили това в настройките си.',
- 'config-email-usertalk' => 'Оповестяване при промяна на потребителската беседа',
- 'config-email-usertalk-help' => 'Позволява на потребителите да получават оповестяване при промяна на беседата им, ако това е разрешено в настройките им.',
- 'config-email-watchlist' => 'Оповестяване за списъка за наблюдение',
- 'config-email-watchlist-help' => 'Позволява на потребителите да получават оповестяване за техните наблюдавани страници, ако това е разрешено в настройките им.',
- 'config-email-auth' => 'Потвърждаване на адреса за електронна поща',
- 'config-email-auth-help' => "Ако тази настройка е включена, потребителите трябва да потвърдят адреса си за е-поща чрез препратка, която им се изпраща при настройване или промяна.
-Само валидните адреси могат да получават е-писма от други потребители или да променят писмата за оповестяване.
-Настройването на това е '''препоръчително''' за публични уикита заради потенциални злоупотреби с възможностите за електронна поща.",
- 'config-email-sender' => 'Адрес за обратна връзка:',
- 'config-email-sender-help' => 'Въвежда се адрес за електронна поща, който ще се използва за обратен адрес при изходящи е-писма.
-Това е адресът, на който ще се получават върнатите и неполучени писма.
-Много е-пощенски сървъри изискват поне домейн името да е валидно.',
- 'config-upload-settings' => 'Картинки и качване на файлове',
- 'config-upload-enable' => 'Позволяне качването на файлове',
- 'config-upload-help' => 'Качването на файлове е възможно да доведе до пробели със сигурността на сървъра.
-Повече информация по темата има в [//www.mediawiki.org/wiki/Manual:Security раздела за сигурност] в Наръчника.
-
-За позволяване качването на файлове, необходимо е уебсървърът да може да записва в поддиректорията на МедияУики <code>images</code>.
-След като това условие е изпълнено, функционалността може да бъде активирана.',
- 'config-upload-deleted' => 'Директория за изтритите файлове:',
- 'config-upload-deleted-help' => 'Избиране на директория, в която ще се складират изтритите файлове.
-В най-добрия случай тя не трябва да е достъпна през уеб.',
- 'config-logo' => 'Адрес на логото:',
- 'config-logo-help' => 'Обликът по подразбиране на МедияУики вклчва място с размери 135х160 пиксела за лого над страничното меню.
-Ако има наличен файл с подходящ размер, неговият адрес може да бъде посочен тук.
-
-Ако не е необходимо лого, полето може да се остави празно.', # Fuzzy
- 'config-instantcommons' => 'Включване на Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функционалност, която позволява на уикитата да използват картинки, звуци и друга медиа от сайта на Уикимедия [//commons.wikimedia.org/ Общомедия].
-За да е възможно това, МедияУики изисква достъп до Интернет.
-
-Повече информация за тази функционалност, както и инструкции за настройване за други уикита, различни от Общомедия, е налична в [//mediawiki.org/wiki/Manual:$wgForeignFileRepos наръчника].',
- 'config-cc-error' => 'Избирането на лиценз на Криейтив Комънс не даде резултат.
-Необходимо е името на лиценза да бъде въведено ръчно.',
- 'config-cc-again' => 'Повторно избиране...',
- 'config-cc-not-chosen' => 'Изберете кой лиценз на Криейтив Комънс желаете и щракнете "proceed".',
- 'config-advanced-settings' => 'Разширена конфигурация',
- 'config-cache-options' => 'Настройки за обектното кеширане:',
- 'config-cache-help' => 'Обектното кеширане се използва за подобряване на скоростта на МедияУики чрез кеширане на често използваните данни.
-Силно препоръчително е на средните и големите сайтове да включат тази настройка, но малките също могат да се възползват от нея.',
- 'config-cache-none' => 'Без кеширане (не се премахва от функционалността, но това влияе на скоростта на по-големи уикита)',
- 'config-cache-accel' => 'PHP обектно кеширане (APC, XCache или WinCache)',
- 'config-cache-memcached' => 'Използване на Memcached (изисква допълнителни настройки и конфигуриране)',
- 'config-memcached-servers' => 'Memcached сървъри:',
- 'config-memcached-help' => 'Списък с IP адреси за използване за Memcached.
-Необходимо е да бъдат разделени по един на ред, както и да е посочен порта. Пример:
-127.0.0.1:11211
-192.168.1.25:1234',
- 'config-memcache-needservers' => 'Избран е Memcached като складиращ тип, но не са посочени сървъри.',
- 'config-memcache-badip' => 'Беше въведен невалиден IP адрес за Memcached: $1.',
- 'config-memcache-noport' => 'Не е посочен порт за използване за Memcached сървъра: $1.
-В случай, че не знаете порта, този по подразбиране е 11211.',
- 'config-memcache-badport' => 'Портовете за Memcached трябва да бъдат между $1 и $2.',
- 'config-extensions' => 'Разширения',
- 'config-extensions-help' => 'Разширенията от списъка по-горе бяха открити в директорията <code>./extensions</code>.
-
-Възможно е те да изискват допълнително конфигуриране, но сега могат да бъдат включени.',
- 'config-install-alreadydone' => "'''Предупреждение:''' Изглежда вече сте инсталирали МедияУики и се опитвате да го инсталирате отново.
-Продължете към следващата страница.",
- 'config-install-begin' => 'Инсталацията на МедияУики ще започне след натискане на бутона „{{int:config-continue}}“.
-В случай, че е необходимо да се направят промени, използва се бутона „{{int:config-back}}“.',
- 'config-install-step-done' => 'готово',
- 'config-install-step-failed' => 'неуспешно',
- 'config-install-extensions' => 'Добавяне на разширенията',
- 'config-install-database' => 'Създаване на базата от данни',
- 'config-install-schema' => 'Създаване на схема',
- 'config-install-pg-schema-not-exist' => 'PostgreSQL схемата не съществува',
- 'config-install-pg-schema-failed' => 'Създаването на таблиците пропадна.
-Необходимо е потребител "$1" да има права за писане в схемата "$2".',
- 'config-install-pg-plpgsql' => 'Проверяване за езика PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Необходимо е да се инсталира езикът PL/pgSQL в базата от данни $1',
- 'config-pg-no-create-privs' => 'Посочената сметка за инсталацията не притежава достатъчно права за създаване на сметка.',
- 'config-pg-not-in-role' => 'Посочената сметка за уеб потребител вече съществува.
-Посочената сметка за инсталация не с права на суперпотребител и не е член на ролите на уеб потребителя и не може да създава обекти, собственост на уеб потребителя.
-
-Текущо МедияУики изисква таблиците да са собственост на уеб потребителя. Необходимо е да се посочи друго потребителско име за уеб или да се натисне "връщане" и да се избере друг потребител за инсталацията с подходящите права.',
- 'config-install-user' => 'Създаване на потребител за базата от данни',
- 'config-install-user-alreadyexists' => 'Потребител „$1“ вече съществува',
- 'config-install-user-create-failed' => 'Създаването на потребител „$1“ беше неуспешно: $2',
- 'config-install-user-grant-failed' => 'Предоставянето на права на потребител "$1" беше неуспешно: $2',
- 'config-install-user-missing' => 'Посоченият потребител " $1 "не съществува.',
- 'config-install-user-missing-create' => 'Посоченият потребител "$1" не съществува.
-Ако желаете да го създадете, поставете отметка на "създаване на сметка".',
- 'config-install-tables' => 'Създаване на таблиците',
- 'config-install-tables-exist' => "'''Предупреждение''': Таблиците за МедияУики изглежда вече съществуват.
-Пропускане на създаването им.",
- 'config-install-tables-failed' => "'''Грешка''': Създаването на таблиците пропадна и върна следната грешка: $1",
- 'config-install-interwiki' => 'Попълване на таблицата с междууикитата по подразбиране',
- 'config-install-interwiki-list' => 'Файлът <code>interwiki.list</code> не можа да бъде открит.',
- 'config-install-interwiki-exists' => "'''Предупреждение''': Таблицата с междууикита изглежда вече съдържа данни.
-Пропускане на списъка по подразбиране.",
- 'config-install-stats' => 'Инициализиране на статистиките',
- 'config-install-keys' => 'Генериране на тайни ключове',
- 'config-insecure-keys' => "'''Предупреждение:''' {{PLURAL:$2|Сигурният ключ, създаден по време на инсталацията, не е напълно надежден|Сигурните ключове, създадени по време на инсталацията, не са напълно надеждни}} $1 . Обмислете да {{PLURAL:$2|го|ги}} смените ръчно.",
- 'config-install-sysop' => 'Създаване на администраторска сметка',
- 'config-install-subscribe-fail' => 'Невъзможно беше абонирането за mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'не е инсталиран cURL и allow_url_fopen не е налична.',
- 'config-install-mainpage' => 'Създаване на Началната страница със съдържание по подразбиране',
- 'config-install-extension-tables' => 'Създаване на таблици за включените разширения',
- 'config-install-mainpage-failed' => 'Вмъкването на Началната страница беше невъзможно: $1',
- 'config-install-done' => "'''Поздравления!'''
-Инсталирането на МедияУики приключи успешно.
-
-Инсталаторът създаде файл <code>LocalSettings.php</code>.
-Той съдържа всичката необходима основна конфигурация на уикито.
-
-Необходимо е той да бъде изтеглен и поставен в основната директория на уикито (директорията, в която е и index.php). Изтеглянето би трябвало да започне автоматично.
-
-Ако изтеглянето не започне автоматично или е било прекратено, файлът може да бъде изтеглен чрез щракване на препратката по-долу:
-
-$3
-
-'''Забележка''': Ако това не бъде извършено сега, генерираният конфигурационен файл няма да е достъпен на по-късен етап ако не бъде изтеглен сега или инсталацията приключи без изтеглянето му.
-
-Когато файлът вече е в основната директория, '''[$2 уикито ще е достъпно на този адрес]'''.",
- 'config-download-localsettings' => 'Изтегляне на <code>LocalSettings.php</code>',
- 'config-help' => 'помощ',
- 'config-nofile' => 'Файлът „$1“ не може да бъде открит. Да не е бил изтрит?',
- 'mainpagetext' => "'''Уикито беше успешно инсталирано.'''",
- 'mainpagedocfooter' => 'Разгледайте [//meta.wikimedia.org/wiki/Help:Contents ръководството] за подробна информация относно използването на уики софтуера.
-
-== Първи стъпки ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Настройки за конфигуриране]
-* [//www.mediawiki.org/wiki/Manual:FAQ ЧЗВ за МедияУики]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Пощенски списък относно нови версии на МедияУики]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Локализиране на МедияУики]',
-);
-
-/** Banjar (Bahasa Banjar)
- * @author Ezagren
- * @author J Subhi
- */
-$messages['bjn'] = array(
- 'mainpagetext' => "'''MediaWiki sudah tapasang awan sukses'''.",
- 'mainpagedocfooter' => 'Carii panjalasan [//meta.wikimedia.org/wiki/Help:Contents Panduan Pamuruk] gasan mamuruk parangkat lunak wiki
-
-== Gasan bamula ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Daptar konpigurasi setélan]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki nang rancak ditakunakan]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki rilis milis]', # Fuzzy
-);
-
-/** Bengali (বাংলা)
- * @author Bellayet
- * @author Wikitanvir
- */
-$messages['bn'] = array(
- 'config-desc' => 'মিডিয়াউইকির জন্য ইন্সটলার',
- 'config-title' => 'মিডিয়াউইকি $1 ইন্সটলেশন',
- 'config-information' => 'তথ্য',
- 'config-localsettings-key' => 'হালনাগাদ কি',
- 'config-session-error' => 'সেশন শুরুতে ত্রুটি: $1',
- 'config-your-language' => 'আপনার ভাষা:',
- 'config-your-language-help' => 'ইন্সটল করা সময় ব্যবহারের জন্য ভাষা নির্বাচন করুন।',
- 'config-wiki-language' => 'উইকি ভাষা:',
- 'config-back' => '← পেছনে',
- 'config-continue' => 'অব্যাহত →',
- 'config-page-language' => 'ভাষা',
- 'config-page-welcome' => 'মিডিয়াউইকিতে স্বাগতম!',
- 'config-page-dbconnect' => 'ডেটাবেজে সংযোগ দিন',
- 'config-page-upgrade' => 'ইতিমধ্যেই থাকা ইন্সটলেশন হালনাগাদ করুন',
- 'config-page-dbsettings' => 'ডেটাবেজ সেটিংস',
- 'config-page-name' => 'নাম',
- 'config-page-options' => 'অপশন',
- 'config-page-install' => 'ইন্সটল',
- 'config-page-complete' => 'সম্পূর্ণ!',
- 'config-page-restart' => 'পুনরায় ইন্সটল প্রক্রিয়া চালু করুন',
- 'config-page-readme' => 'এটি পড়ুন',
- 'config-page-releasenotes' => 'রিলিজ নোট',
- 'config-page-copying' => 'অনুলেপন',
- 'config-page-upgradedoc' => 'হালনাগাদকরণ',
- 'config-page-existingwiki' => 'ইতিমধ্যেই থাকা উইকি',
- 'config-restart' => 'হ্যাঁ, পুনরায় চালু করুন',
- 'config-env-php' => 'পিএইচপি $1 ইন্সটল করা হয়েছে।',
- 'config-db-type' => 'ডেটাবেজের ধরন:',
- 'config-db-host' => 'ডেটাবেজের হোস্ট:',
- 'config-db-install-account' => 'ইন্সটলের জন্য ব্যবহারকারী অ্যাকাউন্ট',
- 'config-db-username' => 'ডেটাবেজের ব্যবহারকারী নাম:',
- 'config-db-password' => 'ডেটাবেজের শব্দচাবি:',
- 'config-db-charset' => 'ডেটাবেজের অক্ষর সেট',
- 'config-db-port' => 'ডেটাবেজ পোর্ট:',
- 'config-db-schema' => 'মিডিয়াউইকির স্কিমা',
- 'config-sqlite-dir' => 'এসকিউলাইট ডেটা ডিরেক্টরি:',
- 'config-oracle-def-ts' => 'পূর্বনির্ধারিত টেবিলস্পেস',
- 'config-oracle-temp-ts' => 'সাময়কি টেবিলস্পেস:',
- 'config-header-mysql' => 'মাইএসকিউএল সেটিংস',
- 'config-header-postgres' => 'পোস্টগ্রেএসকিউএল সেটিংস',
- 'config-header-sqlite' => 'এসকিউলাইট সেটিংস',
- 'config-header-oracle' => 'ওরাকল সেটিংস',
- 'config-invalid-db-type' => 'ডেটাবেজের ধরন অগ্রহযোগ্য',
- 'config-missing-db-name' => 'আপনাকে অবশ্যই "ডেটাবেজ নাম"-এর জন্য একটি মান প্রবেশ করাতে হবে',
- 'config-missing-db-host' => 'আপনাকে অবশ্যই "ডেটাবেজ হোস্ট"-এর জন্য একটি মান প্রবেশ করাতে হবে',
- 'config-missing-db-server-oracle' => 'আপনাকে অবশ্যই "ডেটাবেজ টিএনএস"-এর জন্য একটি মান প্রবেশ করাতে হবে',
- 'config-mysql-engine' => 'সংরক্ষণ ইঞ্জিন:',
- 'config-mysql-innodb' => 'ইনোডিবি',
- 'config-mysql-myisam' => 'মাইআইএসএএম',
- 'config-mysql-charset' => 'ডেটাবেজের অক্ষর সেট',
- 'config-mysql-binary' => 'বাইনারি',
- 'config-mysql-utf8' => 'ইউটিএফ-৮',
- 'config-site-name' => 'উইকির নাম:',
- 'config-site-name-blank' => 'একটি সাইটের নাম প্রবেশ করান।',
- 'config-project-namespace' => 'প্রকল্প নামস্থান:',
- 'config-ns-generic' => 'প্রকল্প',
- 'config-ns-site-name' => 'উইকি নামের অনুরুপ: $1',
- 'config-ns-other' => 'অন্যান্য (নির্দিষ্ট করুন)',
- 'config-ns-other-default' => 'মাইউইকি',
- 'config-admin-box' => 'প্রশাসক অ্যাকাউন্ট',
- 'config-admin-name' => 'আপনার নাম:',
- 'config-admin-password' => 'শব্দচাবি:',
- 'config-admin-password-confirm' => 'শব্দচাবি আবারও প্রবেশ করান:',
- 'config-admin-name-blank' => 'একটি প্রশাসক ব্যবহারকারী নাম প্রবেশ করান',
- 'config-admin-password-blank' => 'প্রশাসক অ্যাকাউন্টের জন্য পাসওয়ার্ড প্রবেশ করান।',
- 'config-admin-password-same' => 'পাসওয়ার্ড অবশ্যই ব্যবহারকারী নামের অনুরুপ হওয়া চলবে না।',
- 'config-admin-password-mismatch' => 'আপনি যে দুটি শব্দচাবি দিয়েছেন তারা পরস্পর মেলেনি।',
- 'config-admin-email' => 'ইমেইল ঠিকানা:',
- 'config-optional-continue' => 'আরও প্রশ্ন জিজ্ঞেস করুন।',
- 'config-optional-skip' => 'আমি ইতিমধ্যেই বিরক্ত হয়ে গেছি, উইকিটি ইন্সটল করো।',
- 'config-profile' => 'ব্যবহারকারী অধিকার প্রোফাইল:',
- 'config-profile-wiki' => 'গতানুগতিক উইকি', # Fuzzy
- 'config-profile-no-anon' => 'অ্যাকাউন্ট তৈরি করা বাধ্যতামূলক',
- 'config-profile-fishbowl' => 'শুধুমাত্র নির্ধারিত সম্পাদকদের জন্যই',
- 'config-profile-private' => 'ব্যক্তিগত উইকি',
- 'config-license' => 'কপিরাইট ও লাইসেন্স:',
- 'config-license-none' => 'কোনো লাইসেন্স ফুটার নেই',
- 'config-license-cc-by-sa' => 'ক্রিয়েটিভ কমন্স অ্যাট্রিবিউশন শেয়ার অ্যালাইক',
- 'config-license-cc-by-nc-sa' => 'ক্রিয়েটিভ কমন্স অ্যাট্রিবিউশন নন-কমার্শিয়াল শেয়ার অ্যালাইক',
- 'config-license-cc-0' => 'ক্রিয়েটিভ কমন্স জিরো', # Fuzzy
- 'config-license-pd' => 'পাবলিক ডোমেইন',
- 'config-license-cc-choose' => 'একটি স্বনির্ধারিত ক্রিয়েটিভ কমন্স লাইসেন্ট নির্বাচন করুন',
- 'config-email-settings' => 'ই-মেইল সেটিংস',
- 'config-email-user' => 'ব্যবহারকারী-থেকে-ব্যবহারকারী ই-মেইল সুবিধা সক্রিয় করো',
- 'config-upload-settings' => 'চিত্র এবং ফাইল আপলোড',
- 'config-upload-enable' => 'ফাইল আপলোড সক্রিয় করো',
- 'config-upload-deleted' => 'অপসারণকৃত ফাইলের ডিরেক্টরি:',
- 'config-logo' => 'লোগো ইউআরএল:',
- 'config-memcached-servers' => 'মেমক্যাশেকৃত সার্ভারসমূহ:',
- 'config-extensions' => 'এক্সটেনশন',
- 'config-install-step-done' => 'সম্পন্ন',
- 'config-install-step-failed' => 'ব্যর্থ',
- 'config-install-extensions' => 'এক্সটেনশন সহকারে',
- 'config-install-database' => 'ডেটাবেজ সেটআপ',
- 'config-install-pg-schema-not-exist' => 'পোস্টগ্রেএসকিউএল স্কিমা খুঁজে পাওয়া যায়নি।',
- 'config-install-tables' => 'টেবিল তৈরি',
- 'config-install-keys' => 'গোপন কি তৈরি',
- 'config-help' => 'সাহায্য',
- 'mainpagetext' => "'''মিডিয়াউইকি সফলভাবে ইন্সটল করা হয়েছে।'''",
- 'mainpagedocfooter' => 'কী ভাবে উইকি সফটওয়্যারটি ব্যবহারকার করবেন, তা জানতে [//meta.wikimedia.org/wiki/Help:Contents ব্যবহারকারী সহায়িকা] দেখুন।
-
-== কোথা থেকে শুরু করবেন ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings কনফিগারেশন সেটিংস তালিকা]
-* [//www.mediawiki.org/wiki/Manual:FAQ প্রশ্নোত্তরে মিডিয়াউইকি]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়াউইকি রিলিজের মেইলিং লিস্ট]', # Fuzzy
-);
-
-/** Bishnupria Manipuri (বিষ্ণুপ্রিয়া মণিপুরী)
- */
-$messages['bpy'] = array(
- 'mainpagetext' => "'''মিডিয়াউইকি হবাবালা ইয়া ইন্সটল ইল.'''",
- 'mainpagedocfooter' => 'উইকি সফটৱ্যার এহান আতানির বারে দরকার ইলে [//meta.wikimedia.org/wiki/Help:Contents আতাকুরার গাইড]হানর পাঙলাক নেগা।
-
-== অকরানিহান ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings কনফিগারেশন সেটিংর তালিকাহান]
-* [//www.mediawiki.org/wiki/Manual:FAQ মিডিয়া উইকি আঙলাক]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়া উইকির ফঙপার বারে মেইলর তালিকাহান]', # Fuzzy
-);
-
-/** Breton (brezhoneg)
- * @author Fohanno
- * @author Fulup
- * @author Gwendal
- * @author Y-M D
- * @author 아라
- */
-$messages['br'] = array(
- 'config-desc' => 'Poellad staliañ MediaWIki',
- 'config-title' => 'Staliadur MediaWiki $1',
- 'config-information' => 'Titouroù',
- 'config-localsettings-upgrade' => 'Kavet ez eus bet ur restr <code>LocalSettings.php</code>.
-Evit hizivaat ar staliadur-se, merkit an talvoud <code>$wgUpgradeKey</code> er voest dindan.
-E gavout a rit e <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Dinoet ez eus bet ur restr <code>LocalSettings.php</code>.
-Evit lakaat ar staliadur-mañ a-live, implijit <code>update.php</code> e plas',
- 'config-localsettings-key' => "Alc'hwez hizivaat :",
- 'config-localsettings-badkey' => "Direizh eo an alc'hwez merket ganeoc'h",
- 'config-upgrade-key-missing' => 'Kavet ez eus bet ur staliadur kent eus MediaWiki.
-Evit hizivaat ar staliadur-se, ouzhpennit al linenn da-heul e traoñ ho restr <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => "Diglok e seblant bezañ ar restr <code>LocalSettings.php</code> zo anezhi dija.
-An argemmenn $1 n'eo ket termenet.
-Kemmit <code>LocalSettings.php</code> evit ma vo termenet an argemmenn-se, ha klikit war « {{int:Config-continue}} ».",
- 'config-localsettings-connection-error' => "C'hoarvezet ez eus ur fazi en ur gevreañ ouzh an diaz roadennoù oc'h implijout an arventennoù diferet e <code>LocalSettings.php</code> pe <code>AdminSettings.php</code>. Reizhit an arventennoù-se hag esaeit en-dro.
-
-$1",
- 'config-session-error' => "Fazi e-ser loc'hañ an dalc'h : $1",
- 'config-session-expired' => "Kloz eo an dalc'h evit doare.
-Kefluniet eo an dalc'hoù evit padout $1.
-Kreskiñ ar pad-mañ a c'hallit dre e arventenniñ <code>session.gc_maxlifetime</code> e php.ini.
-Adgrogit gant ar staliadur.",
- 'config-no-session' => "Kolle teo bet roadennoù ho talc'h !
-Gwiriit ar restr php.ini ha bezit sur emañ staliet <code>session.save_path</code> en ur c'havlec'h a zere.",
- 'config-your-language' => 'Ho yezh :',
- 'config-your-language-help' => 'Dibabit ur yezh da implijout e-pad an argerzh staliañ.',
- 'config-wiki-language' => 'Yezh ar wiki :',
- 'config-wiki-language-help' => 'Diuzañ ar yezh a vo implijet ar muiañ er wiki.',
- 'config-back' => '← Distreiñ',
- 'config-continue' => "Kenderc'hel →",
- 'config-page-language' => 'Yezh',
- 'config-page-welcome' => 'Degemer mat e MediaWiki !',
- 'config-page-dbconnect' => "Kevreañ d'an diaz roadennoù",
- 'config-page-upgrade' => 'Hizivaat ar staliadur a zo dioutañ',
- 'config-page-dbsettings' => 'Arventennoù an diaz roadennoù',
- 'config-page-name' => 'Anv',
- 'config-page-options' => 'Dibarzhioù',
- 'config-page-install' => 'Staliañ',
- 'config-page-complete' => 'Graet !',
- 'config-page-restart' => 'Adlañsañ ar staliadur',
- 'config-page-readme' => 'Lennit-me',
- 'config-page-releasenotes' => 'Notennoù stumm',
- 'config-page-copying' => 'O eilañ',
- 'config-page-upgradedoc' => 'O hizivaat',
- 'config-page-existingwiki' => 'Wiki zo anezhañ dija',
- 'config-help-restart' => "Ha c'hoant hoc'h eus da ziverkañ an holl roadennoù hoc'h eus ebarzhet ha da adlañsañ an argerzh staliañ ?",
- 'config-restart' => "Ya, adloc'hañ anezhañ",
- 'config-welcome' => "=== Gwiriadennoù a denn d'an endro ===
-Rekis eo un nebeud gwiriadennoù diazez da welet hag azas eo an endro evit gallout staliañ MediaWiki.
-Dleout a rafec'h merkañ disoc'hoù ar gwiriadennoù-se m'hoc'h eus ezhomm skoazell e-pad ar staliadenn.",
- 'config-copyright' => "=== Gwiriañ aozer ha Termenoù implijout ===
-
-$1
-
-Ur meziant frank eo ar programm-mañ; gallout a rit skignañ anezhañ ha/pe kemmañ anezhañ dindan termenoù ar GNU Aotre-implijout Foran Hollek evel m'emañ embannet gant Diazezadur ar Meziantoù Frank; pe diouzh stumm 2 an aotre-implijout, pe (evel mar karit) diouzh ne vern pe stumm nevesoc'h.
-
-Ingalet eo ar programm gant ar spi e vo talvoudus met n'eus '''tamm gwarant ebet'''; hep zoken gwarant empleg ar '''varc'hadusted''' pe an '''azaster ouzh ur pal bennak'''. Gwelet ar GNU Aotre-Implijout Foran Hollek evit muioc'h a ditouroù.
-
-Sañset oc'h bezañ resevet <doclink href=Copying>un eilskrid eus ar GNU Aotre-implijout Foran Hollek</doclink> a-gevret gant ar programm-mañ; ma n'hoc'h eus ket, skrivit da Diazezadur ar Meziantoù Frank/Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, SUA pe [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html lennit anezhañ enlinenn].",
- 'config-sidebar' => "* [//www.mediawiki.org MediaWiki degemer]
-* [//www.mediawiki.org/wiki/Help:Contents Sturlevr an implijerien]
-* [//www.mediawiki.org/wiki/Manual:Contents Sturlevr ar verourien]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAG]
-----
-* <doclink href=Readme>Lennit-me</doclink>
-* <doclink href=ReleaseNotes>Notennoù embann</doclink>
-* <doclink href=Copying>Oc'h eilañ</doclink>
-* <doclink href=UpgradeDoc>O hizivaat</doclink>",
- 'config-env-good' => 'Gwiriet eo bet an endro.
-Gallout a rit staliañ MediaWiki.',
- 'config-env-bad' => "Gwiriet eo bet an endro.
-Ne c'hallit ket staliañ MediaWiki.",
- 'config-env-php' => 'Staliet eo PHP $1.',
- 'config-env-php-toolow' => "Staliet eo PHP $1.
-Nemet eo rekis PHP $2 pe nevesoc'h evit MediaWiki.",
- 'config-unicode-using-utf8' => "Oc'h implijout utf8_normalize.so gant Brion Vibber evit ar reolata Unicode.",
- 'config-unicode-using-intl' => "Oc'h implijout [http://pecl.php.net/intl an astenn PECL intl] evit ar reolata Unicode.",
- 'config-unicode-pure-php-warning' => "'''Diwallit''' : N'haller ket kaout an [http://pecl.php.net/intl intl PECL astenn] evit merañ reoladur Unicode, a zistro d'ar stumm gorrek emplementet e-PHP.
-Ma lakait da dreiñ ul lec'hienn darempredet-stank e vo mat deoc'h lenn un tammig bihan diwar-benn se war [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization]. (e saozneg)",
- 'config-unicode-update-warning' => "'''Diwallit''': ober a ra stumm staliet endalc'her skoueriekaat Unicode gant ur stumm kozh eus [http://site.icu-project.org/ levraoueg meziantoù ar raktres ICU].
-Dleout a rafec'h [//www.mediawiki.org/wiki/Unicode_normalization_considerations hizivaat] ma seblant deoc'h bezañ pouezus ober gant Unicode.",
- 'config-no-db' => "N'eus ket bet gallet kavout ur sturier diazoù roadennoù a zere ! Ret eo deoc'h staliañ ur sturier diazoù roadennoù evit PHP.
-Skoret eo an diazoù roadennoù da-heul : $1.
-
-Ma rit gant un herberc'hiañ kenrannet, goulennit digant ho herberc'hier staliañ ur sturier diaz roadennoù azas.
-Ma kempunit PHP c'hwi hoc'h-unan, adkeflugnit-eñ en ur weredekaat un arval diaz roadennoù, da skouer en ur ober gant <code>./configure --mysql</code>.
-M'hoc'h eus staliet PHP adalek ur pakad Debian pe Ubuntu, eo ret deoc'h staliañ ar vodulenn php5-mysql ivez.",
- 'config-no-fts3' => "'''Diwallit ''': Kempunet eo SQLite hep ar [//sqlite.org/fts3.html vodulenn FTS3]; ne vo ket posupl ober gant an arc'hwelioù klask er staliadur-mañ",
- 'config-register-globals' => "'''Diwallit : Gweredekaet eo dibarzh <code>[http://php.net/register_globals register_globals]</code> PHP.'''
-'''Diweredekait anezhañ ma c'hallit.'''
-Mont a raio MediaWiki en-dro met fazioù surentez a c'hallo c'hoari war ho servijer",
- 'config-magic-quotes-runtime' => "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] !'''
-Breinañ a ra an dibarzh-mañ ar roadennoù en ur mod dic'hortoz.
-N'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
- 'config-magic-quotes-sybase' => "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] !'''
-Breinañ a ra an dibarzh-mañ ar roadennoù en ur mod dic'hortoz.
-N'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
- 'config-mbstring' => "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] !'''
-Degas a ra an dibarzh-mañ fazioù ha gallout a ra breinañ ar roadennoù en ur mod dic'hortoz.
-N'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
- 'config-ze1' => "'''Fazi diremed : [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mod] zo gweredekaet !'''
-An dibarzh-mañ zo kaoz da zrein euzhus gant MediaWiki.
-Ne c'hallit ket staliañ nag implijout MediaWiki keit ha m'eo gweredekaet an dibarzh-mañ.",
- 'config-safe-mode' => "'''Diwallit :''' Gweredekaet eo [http://www.php.net/features.safe-mode mod surentez] PHP.
-Kudennoù a c'hall sevel abalamour da gement-se, dreist-holl ma pellgargit restroù ha ma skorit <code>math</code>.",
- 'config-xml-bad' => "Mankout a ra modulenn XML PHP.
-Ezhomm en deus MediaWiki eus arc'hwelioù zo eus ar vodulenn-se ha ne'z aio ket en-dro gant ar c'hefluniadur zo.
-M'emaoc'h gant Mandrake, stailhit pakad php-xml.",
- 'config-pcre' => "Evit doare e vank ar vodulenn skorañ PCRE.
-Evit mont en-dro plaen en deus ezhomm MediaWiki eus an arc'hwelioù jediñ reoliek kenglotus gant Perl.",
- 'config-pcre-no-utf8' => "'''Fazi groñs ''': evit doare eo bet kempunet modulenn PCRE PHP hep ar skor PCRE_UTF8.
-Ezhomm 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.
-Re izel eo moarvat.
-Marteze e c'hwito ar staliadenn !",
- 'config-xcache' => 'Staliet eo [http://xcache.lighttpd.net/ XCache]',
- 'config-apc' => 'Staliet eo [http://www.php.net/apc APC]',
- 'config-wincache' => 'Staliet eo [http://www.iis.net/download/WinCacheForPhp WinCache]',
- 'config-no-cache' => "'''Diwallit:''' N'eus ket bet gallet kavout [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] pe [http://www.iis.net/download/WinCacheForPhp WinCache].
-N'eo ket gweredekaet ar c'hrubuilhañ traezoù.",
- 'config-diff3-bad' => "N'eo ket bet kavet GNU diff3.",
- 'config-imagemagick' => "ImageMagick kavet : <code>$1</code>.
-Gweredekaet e vo ar bihanaat skeudennoù ma vez gweredekaet ganeoc'h ar pellgargañ restroù.",
- 'config-gd' => "Kavet eo bet al levraoueg c'hrafek GD enframmet.
-Gweredekaet e vo ar bihanaat skeudennoù ma vez gweredekaet an enporzhiañ restroù.",
- 'config-no-scaling' => "N'eus ket bet gallet kavout al levraoueg GD pe ImageMagick.
-Diweredekaet e vo ar bihanaat skeudennoù.",
- 'config-no-uri' => "'''Fazi :''' N'eus ket tu da anavezout URI ar skript red.
-Staliadur nullet.",
- 'config-uploads-not-safe' => "'''Diwallit :'''Bresk eo ho kavlec'h pellgargañ dre ziouer <code>$1</code> rak gallout a ra erounit ne vern pe skript.
-ha pa vefe gwiriet gant MediaWiki an holl restroù pellgarget eo erbedet-groñs da [//www.mediawiki.org/wiki/Manual:Security#Upload_security serriñ ar breskter surentez-mañ] a-rao gweredekaat ar pellgargañ.",
- 'config-brokenlibxml' => "Ur meskad stummoù PHP ha libxml2 dreinek a vez implijet gant ho reizhiad. Gallout a ra breinañ ar roadennoù e MediaWiki hag en arloadoù web all.
-Hizivait da PHP 5.2.9 pe nevesoc'h ha libxml2 2.7.3 pe nevesoc'h ([//bugs.php.net/bug.php?id=45996 draet renablet gant PHP]).
-Staliadur paouezet.",
- 'config-using531' => "N'haller ket implijout MediaWiki gant PHP $1 abalamour d'un draen a zegas trubuilh en arventennoù kaset en ur ober dave da <code>__call()</code>.
-Hizivait ho reizhiad gant PHP 5.3.2 pe nevesoc'h, pe distroit da PHP 5.3.0 evit renkañ an dra-se.
-Staliadur paouezet.",
- 'config-db-type' => 'Doare an diaz roadennoù :',
- 'config-db-host' => 'Anv implijer an diaz roadennoù :',
- 'config-db-host-help' => "M'emañ ho servijer roadennoù war ur servijer disheñvel, merkit amañ an anv ostiz pe ar chomlec'h IP.
-
-Ma rit gant un herberc'hiañ kenrannet, e tlefe ho herberc'hier bezañ pourchaset deoc'h un anv ostiz reizh en teulioù titouriñ.
-
-M'emaoc'h o staliañ ur servijer Windows ha ma rit gant MySQL, marteze ne'z aio ket en-dro \"localhost\" evel anv servijer. Ma ne dro ket, klaskit ober gant \"127.0.0.1\" da chomlec'h IP lechel.", # Fuzzy
- 'config-db-host-oracle' => 'TNS an diaz roadennoù :',
- 'config-db-wiki-settings' => 'Anavezout ar wiki-mañ',
- 'config-db-name' => 'Anv an diaz roadennoù :',
- 'config-db-name-help' => "Dibabit un anv evit ho wiki.
-Na lakait ket a esaouennoù ennañ.
-
-Ma ri gant un herberc'hiañ kenrannet e vo pourchaset deoc'h un anv diaz roadennoù dibar da vezañ graet gantañ gant ho herberc'hier pe e lezo ac'hanoc'h da grouiñ diazoù roadennoù dre ur banell gontrolliñ.",
- 'config-db-name-oracle' => 'Brastres diaz roadennoù :',
- 'config-db-install-account' => 'Kont implijer evit ar staliadur',
- 'config-db-username' => 'Anv implijer an diaz roadennoù :',
- 'config-db-password' => 'Ger-tremen an diaz roadennoù :',
- 'config-db-password-empty' => "Lakait ur ger-tremen evit kont nevez an diaz roadennoù : $1.
-Ha pa vefe posupl da grouiñ kontoù hep ger-tremen, n'eo ket erbedet evit abegoù surentez.",
- 'config-db-install-username' => "Ebarzhit an anv implijer a vo implijet da gevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.
-N'eo ket anv implijer ar gont MediaWiki, an anv implijer evit ho tiaz roadennoù eo.",
- 'config-db-install-password' => "Ebarzhit ar ger-tremen a vo implijet da gevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.
-N'eo ket ar ger-tremen evit ar gont MediaWiki, ar ger-tremen evit ho tiaz roadennoù eo.",
- 'config-db-install-help' => 'Merkañ anv an implijer hag ar ger-tremen a vo implijet evit kevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.',
- 'config-db-account-lock' => 'Implijout ar memes anv implijer ha ger-tremen e-kerzh oberiadurioù boutin',
- 'config-db-wiki-account' => 'Kont implijer evit oberiadurioù boutin',
- 'config-db-prefix' => 'Rakrann taolennoù an diaz roadennoù :',
- 'config-db-charset' => 'Strobad arouezennoù an diaz roadennoù',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binarel',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 kilkenglotus UTF-8',
- 'config-mysql-old' => "Rekis eo MySQL $1 pe ur stumm nevesoc'h; ober a rit gant $2.",
- 'config-db-port' => 'Porzh an diaz roadennoù :',
- 'config-db-schema' => 'Brastres evit MediaWiki',
- 'config-db-schema-help' => "Peurliesañ e vo digudenn ar chema-mañ.
-Arabat cheñch anezho ma n'hoc'h eus ket ezhomm d'en ober.",
- 'config-pg-test-error' => "N'haller ket kevreañ ouzh an diaz-titouroù '''$1''' : $2",
- 'config-sqlite-dir' => "Kavlec'h roadennoù SQLite :",
- 'config-oracle-def-ts' => 'Esaouenn stokañ ("tablespace") dre ziouer :',
- 'config-oracle-temp-ts' => "Esaouenn stokañ (''tablespace'') da c'hortoz :",
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => "Skoret eo ar reizhiadoù diaz titouroù da-heul gant MediaWiki :
-
-$1
-
-Ma 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-support-mysql' => '* $1 eo an dibab kentañ evit MediaWiki hag an hini skoret ar gwellañ ([http://www.php.net/manual/en/mysql.installation.php penaos kempunañ PHP gant skor MySQL])',
- 'config-support-postgres' => "* Ur reizhiad diaz titouroù brudet ha digor eo $1. Gallout a ra ober evit MySQL ([http://www.php.net/manual/en/pgsql.installation.php Penaos kempunañ PHP gant skor PostgreSQL]). Gallout a ra bezañ un nebeud drein bihan enni ha n'eo ket erbedet he implijout en un endro produiñ.",
- 'config-support-sqlite' => "* $1 zo ur reizhiad diaz titouroù 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-support-oracle' => '* $1 zo un diaz titouroù kenwerzhel. ([http://www.php.net/manual/en/oci8.installation.php Penaos kempunañ PHP gant skor OCI8])',
- 'config-header-mysql' => 'Arventennoù MySQL',
- 'config-header-postgres' => 'Arventennoù PostgreSQL',
- 'config-header-sqlite' => 'Arventennoù SQLite',
- 'config-header-oracle' => 'Arventennoù Oracle',
- 'config-invalid-db-type' => 'Direizh eo ar seurt diaz roadennoù',
- 'config-missing-db-name' => 'Ret eo deoc\'h merkañ un dalvoudenn evit "Anv an diaz titouroù"',
- 'config-missing-db-host' => 'Ret eo deoc\'h merkañ un dalvoudenn evit "Ostiz an diaz titouroù"',
- 'config-missing-db-server-oracle' => 'Ret eo deoc\'h merkañ un dalvoudenn evit "Anv TNS an diaz titouroù"',
- 'config-invalid-db-server-oracle' => 'Direizh eo anv TNS an diaz titouroù "$1".
-Ober hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha pikoù (.).',
- 'config-invalid-db-name' => 'Direizh eo anv an diaz titouroù "$1".
-Ober hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha tiredoù (-).',
- 'config-invalid-db-prefix' => 'Direizh eo rakger an diaz titouroù "$1".
-Ober hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha tiredoù (-).',
- 'config-connection-error' => '$1.
-
-Gwiriit anv an ostiz, an anv implijer, ar ger-tremen ha klaskit en-dro.',
- 'config-invalid-schema' => 'Chema direizh evit MediaWiki "$1".
-Grit hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9) hag arouezennoù islinennañ (_).',
- 'config-db-sys-create-oracle' => "N'anavez ar stalier nemet ar c'hontoù SYSDBA evit krouiñ kontoù nevez.",
- 'config-db-sys-user-exists-oracle' => 'Bez\' ez eus eus ar gont "$1" c\'hoazh. N\'haller ober gant SYSDBA nemet evit krouiñ kontoù nevez !',
- 'config-postgres-old' => "Rekis eo PostgreSQL $1 pe ur stumm nevesoc'h; ober a rit gant $2.",
- 'config-sqlite-name-help' => "Dibabit un anv dibar d'ho wiki.
-Arabat ober gant esaouennoù pe barrennigoù-stagañ.
-Implijet e vo evit ar restr roadennoù SQLite.",
- 'config-sqlite-mkdir-error' => 'Ur fazi zo bet e-ser krouiñ ar c\'havlec\'h roadennoù "$1".
-Gwiriañ al lec\'hiadur ha klask en-dro.',
- 'config-sqlite-dir-unwritable' => 'Dibosupl skrivañ er c\'havlec\'h "$1".
-Cheñchit ar aotreoù evit ma c\'hallfe ar servijer web skrivañ ennañ ha klaskit en-dro.',
- 'config-sqlite-connection-error' => "$1.
-
-Gwiriañ ar c'havlec'h roadennoù hag anv an diaz roadennoù a-is ha klaskit en-dro.",
- 'config-sqlite-readonly' => "N'haller ket skrivañ er restr <code>$1</code>.",
- 'config-sqlite-cant-create-db' => "N'haller ket krouiñ restr an diaz roadennoù <code>$1</code>.",
- 'config-sqlite-fts3-downgrade' => "N'eo ket kenglotus ar PHP gant FTS3, o lakaat an taolennoù da glotañ gant ur stumm koshoc'h",
- 'config-can-upgrade' => "Taolennoù MediaWiki zo en diaz titouroù.
-Da hizivaat anezho da VediaWiki $1, klikañ war '''Kenderc'hel'''.",
- 'config-upgrade-done-no-regenerate' => 'Hizivadenn kaset da benn.
-
-Gallout a rit [$1 kregiñ da implijout ho wiki].',
- 'config-regenerate' => 'Adgenel LocalSettings.php →',
- 'config-show-table-status' => "C'hwitet ar reked <code>SHOW TABLE STATUS</code> !",
- 'config-unknown-collation' => "'''Diwallit :''' Emañ an diaz roadennoù o renkañ an traoù diouzh un urzh lizherennek dianav.",
- 'config-db-web-account' => 'Kont an diaz roadennoù evit ar voned Kenrouedad',
- 'config-db-web-help' => 'Diuzañ an anv implijer hag ar ger-tremen a vo implijet gant ar servijer web evit kevreañ ouzh ar servijer diaz roadennoù pa vez ar wiki o vont en-dro war ar pemdez.',
- 'config-db-web-account-same' => 'Ober gant an hevelep kont hag an hini implijet evit ar staliañ',
- 'config-db-web-create' => "Krouiñ ar gont ma n'eus ket anezhi c'hoazh",
- 'config-mysql-engine' => 'Lusker stokañ :',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => 'Strobad arouezennoù an diaz roadennoù :',
- 'config-mysql-binary' => 'Binarel',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Anv ar wiki :',
- 'config-site-name-help' => "Dont a raio war wel e barrenn ditl ar merdeer hag e meur a lec'h all c'hoazh.",
- 'config-site-name-blank' => "Lakait anv ul lec'hienn .",
- 'config-project-namespace' => 'Esaouenn anv ar raktres :',
- 'config-ns-generic' => 'Raktres',
- 'config-ns-site-name' => 'Hevelep anv hag hini ar wiki : $1',
- 'config-ns-other' => 'All (spisaat)',
- 'config-ns-other-default' => 'MaWiki',
- 'config-ns-invalid' => 'Direizh eo an esaouenn anv "<nowiki>$1</nowiki>" spisaet.
-Merkit un esaouenn anv disheñvel evit ar raktres.',
- 'config-ns-conflict' => 'Tabut zo etre an esaouenn anv spisaet "<nowiki>$1</nowiki>" hag un esaouenn anv dre ziouer eus MediaWiki.
-Spisait un anv raktres esaouenn anv all.',
- 'config-admin-box' => 'Kont merour',
- 'config-admin-name' => "Hoc'h anv :",
- 'config-admin-password' => 'Ger-tremen :',
- 'config-admin-password-confirm' => 'Adskrivañ ar ger-tremen :',
- 'config-admin-help' => 'Merkit hoc\'h anv implijer amañ, da skouer "Yann Vlog".
-Hemañ eo an anv a implijot evit kevreañ d\'ar wiki-mañ.',
- 'config-admin-name-blank' => 'Lakait anv ur merour.',
- 'config-admin-name-invalid' => 'Direizh eo an anv implijer spisaet "<nowiki>$1</nowiki>".
-Merkit un anv implijer all.',
- 'config-admin-password-blank' => 'Reiñ ur ger-tremen evit kont ar merour.',
- 'config-admin-password-same' => "Ne c'hall ket ar ger-tremen bezañ heñvel ouzh anv ar gont.",
- 'config-admin-password-mismatch' => "Ne glot ket ar gerioù-tremen hoc'h eus merket an eil gant egile.",
- 'config-admin-email' => "Chomlec'h postel :",
- 'config-admin-email-help' => "Merkit ur chomlec'h postel amañ evit gallout resev posteloù a-berzh implijerien all eus ar wiki, adderaouekaat ho ker-tremen ha bezañ kelaouet eus ar c'hemmoù degaset d'ar pajennoù zo en ho roll evezhiañ. Gallout a rit lezel ar vaezienn-mañ goullo.",
- 'config-admin-error-user' => 'Fazi diabarzh en ur grouiñ ur merer gant an anv "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Fazi diabarzh o lakaat ur ger-tremen evit ar merour « <nowiki>$1</nowiki> » : <pre>$2</pre>',
- 'config-admin-error-bademail' => "Ebarzhet hoc'h eus ur chomlec'h postel direizh.",
- 'config-subscribe' => 'Koumanantit da [https://lists.wikimedia.org/mailman/listinfo/mediawiki-listenn kemennadoù evit ar stummoù nevez].',
- 'config-almost-done' => "Kazi echu eo !
-Gellout a rit tremen ar c'hefluniadur nevez ha staliañ ar wiki war-eeun.",
- 'config-optional-continue' => "Sevel muioc'h a goulennoù ouzhin.",
- 'config-optional-skip' => 'Aet on skuizh, staliañ ar wiki hepken.',
- 'config-profile' => 'Profil ar gwirioù implijer :',
- 'config-profile-wiki' => 'Wiki digor',
- 'config-profile-no-anon' => 'Krouidigezh ur gont ret',
- 'config-profile-fishbowl' => 'Embanner aotreet hepken',
- 'config-profile-private' => 'Wiki prevez',
- 'config-license' => 'Copyright hag aotre-implijout:',
- 'config-license-none' => 'Aotre ebet en traoñ pajenn',
- 'config-license-cc-by-sa' => 'Creative Commons Deroadenn Kenrannañ heñvel',
- 'config-license-cc-by' => 'Creative Commons Deroadenn',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Deroadenn Angenwerzhel Kenrannañ heñvel',
- 'config-license-cc-0' => 'Creative Commons Zero (Domani foran)',
- 'config-license-pd' => 'Domani foran',
- 'config-license-cc-choose' => 'Dibabit un aotre-implijout Creative Commons personelaet',
- 'config-email-settings' => 'Arventennoù ar postel',
- 'config-enable-email' => 'Gweredekaat ar posteloù a ya kuit',
- 'config-enable-email-help' => "Mar fell deoc'h ober gant ar posteler eo ret deoc'h [http://www.php.net/manual/en/mail.configuration.php kefluniañ arventennoù postel PHP] ervat.
-Mar ne fell ket deoc'h ober gant ar servij posteloù e c'hall bezañ diweredekaet amañ.",
- 'config-email-user' => 'Gweredekaat ar posteloù a implijer da implijer',
- 'config-email-user-help' => "Aotren a ra an holl implijerien da gas posteloù an eil d'egile mard eo bet gweredekaet an arc'hwel ganto en ho penndibaboù.",
- 'config-email-usertalk' => 'Gweredekaat kemennadur pajennoù kaozeal an implijerien',
- 'config-email-usertalk-help' => "Talvezout a ra d'an implijerien da resev kemennadennoù ma vez kemmet o fajennoù kaozeal, ma vez gweredekaet en o fenndibaboù.",
- 'config-email-watchlist' => "Gweredekaat ar c'hemenn listenn evezhiañ",
- 'config-email-watchlist-help' => "Talvezout a ra d'an implijerien da resev kemennadennoù diwar-benn ar pajennoù evezhiet ganto, ma vez gweredekaet en o fenndibaboù.",
- 'config-email-auth' => 'Gweredekaat an dilesadur dre bostel',
- 'config-email-sender' => "Chomlec'h postel respont :",
- 'config-email-sender-help' => "Merkit ar chomlec'h postel da vezañ implijet da chomlec'h distreiñ ar posteloù a ya er-maez.
-Di e vo kaset ar posteloù distaolet.
-Niverus eo ar servijerioù postel a c'houlenn da nebeutañ un [http://fr.wikipedia.org/wiki/Nom_de_domaine anv domani] reizh.",
- 'config-upload-settings' => 'Pellgargañ skeudennoù ha restroù',
- 'config-upload-enable' => 'Gweredekaat ar pellgargañ restroù',
- 'config-upload-deleted' => "Kavlec'h evit ar restroù dilamet :",
- 'config-upload-deleted-help' => "Dibab ur c'havlec'h da ziellaouiñ ar restroù diverket.
-Ar pep gwellañ e vije ma ne vije ket tu d'e dizhout adalek ar Genrouedad.",
- 'config-logo' => 'URL al logo :',
- 'config-instantcommons' => "Gweredekaat ''InstantCommons''",
- 'config-cc-error' => "N'eus deuet disoc'h ebet gant dibaber aotreoù-implijout Creative Commons.
-Merkit anv an aotre-implijout gant an dorn.",
- 'config-cc-again' => 'Dibabit adarre...',
- 'config-cc-not-chosen' => 'Dibabit an aotre-implijout Creative Commons a fell deoc\'h ober gantañ ha klikit war "kenderc\'hel".',
- 'config-advanced-settings' => 'Kefluniadur araokaet',
- 'config-cache-options' => 'Arventennoù evit krubuilhañ traezoù :',
- 'config-cache-accel' => 'Krubuilhañ traezoù PHP (APC, XCache pe WinCache)',
- 'config-cache-memcached' => 'Implijout Memcached (en deus ezhomm bezañ staliet ha kefluniet)',
- 'config-memcached-servers' => 'Servijerioù Memcached :',
- 'config-memcached-help' => "Roll ar chomlec'hioù IP da implijout evit Memcached.
-Ret eo spisaat unan dre linenn ha spisaat ar porzh da vezañ implijet. Da skouer :
-127.0.0.1:11211
-192.168.1.25:1234",
- 'config-memcache-needservers' => "Diuzet hoc'h eus Memcached evel seurt krubuilh met n'hoc'h eus spisaet servijer ebet.",
- 'config-memcache-badip' => "Ur chomlec'h IP direizh hoc'h eus lakaet evit Memcached : $1.",
- 'config-memcache-badport' => 'Niverennoù porzh Memcached a zlefe bezañ etre $1 ha $2.',
- 'config-extensions' => 'Astennoù',
- 'config-extensions-help' => "N'eo ket bet detektet an astennoù rollet a-us en ho kavlec'h <code>./astennoù</code>.
-
-Marteze e vo ezhomm kefluniañ pelloc'h met gallout a rit o gweredekaat bremañ.",
- 'config-install-alreadydone' => "'''Diwallit''': Staliet hoc'h eus MediaWiki dija war a seblant hag emaoc'h o klask e staliañ c'hoazh.
-Kit d'ar bajenn war-lerc'h, mar plij.",
- 'config-install-begin' => 'Pa vo bet pouezet ganeoc\'h war "{{int:config-continue}}" e krogo staliadur MediaWiki.
-Pouezit war "{{int:config-back}}" mar fell deoc\'h cheñch tra pe dra.',
- 'config-install-step-done' => 'graet',
- 'config-install-step-failed' => "c'hwitet",
- 'config-install-extensions' => 'En ur gontañ an astennoù',
- 'config-install-database' => 'Krouiñ an diaz roadennoù',
- 'config-install-schema' => 'O krouiñ ar chema',
- 'config-install-pg-schema-not-exist' => "N'eus ket eus chema PostgreSQL.",
- 'config-install-pg-schema-failed' => "C'hwitet eo krouidigezh an taolennoù.
-Gwiriit hag-eñ e c'hall an implijer « $1 » skrivañ er brastres « $2 ».",
- 'config-install-pg-commit' => "O wiriekaat ar c'hemmoù",
- 'config-install-pg-plpgsql' => 'O wiriañ ar yezh PL/pgSQL',
- 'config-pg-no-plpgsql' => "Ret eo deoc'h staliañ ar yezh PL/pgSQL en diaz roadennoù $1",
- 'config-pg-no-create-privs' => "N'eus ket gwirioù a-walc'h gant ar gont hoc'h eus merket evit ar staliadur evit gallout krouiñ ur gont.",
- 'config-install-user' => 'O krouiñ an diaz roadennoù implijer',
- 'config-install-user-alreadyexists' => 'An implijer "$1" zo anezhañ dija',
- 'config-install-user-create-failed' => 'Fazi e-ser krouiñ an implijer "$1" : $2',
- 'config-install-user-grant-failed' => 'N\'eus ket bet gallet reiñ an aotre d\'an implijer "$1" : $2',
- 'config-install-tables' => 'Krouiñ taolennoù',
- 'config-install-tables-failed' => "'''Fazi :''' c'hwitet eo krouidigezh an daolenn gant ar fazi-mañ : $1",
- 'config-install-interwiki' => 'O leuniañ dre ziouer an daolenn etrewiki',
- 'config-install-interwiki-list' => "Ne c'haller ket kavout ar restr <code>interwiki.list</code>.",
- 'config-install-stats' => 'O sevel ar stadegoù',
- 'config-install-keys' => "Genel an alc'hwezioù kuzh",
- 'config-install-sysop' => 'Krouidigezh kont ar merour',
- 'config-install-subscribe-fail' => "N'haller ket koumanantiñ da mediawiki-announce : $1",
- 'config-install-mainpage' => "O krouiñ ar bajenn bennañ gant un endalc'had dre ziouer",
- 'config-install-extension-tables' => 'O krouiñ taolennoù evit an astennoù gweredekaet',
- 'config-install-mainpage-failed' => "Ne c'haller ket ensoc'hañ ar bajenn bennañ: $1",
- 'config-download-localsettings' => 'Pellgargañ <code>LocalSettings.php</code>',
- 'config-help' => 'skoazell',
- 'config-nofile' => 'N\'eus ket bet gallet kavout ar restr "$1". Daoust ha dilamet eo bet ?',
- 'mainpagetext' => "'''Meziant MediaWiki staliet.'''",
- 'mainpagedocfooter' => "Sellit ouzh [//meta.wikimedia.org/wiki/Help:Contents Sturlevr an implijerien] evit gouzout hiroc'h war an doare da implijout ar meziant wiki.
-
-== Kregiñ ganti ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Roll an arventennoù kefluniañ]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAG MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Roll ar c'haozeadennoù diwar-benn dasparzhoù MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Lec'hiañ MediaWiki en ho yezh", # Fuzzy
-);
-
-/** Bosnian (bosanski)
- * @author CERminator
- */
-$messages['bs'] = array(
- 'config-desc' => 'Instalacija za MediaWiki',
- 'config-title' => 'MediaWiki $1 instalacija',
- 'config-information' => 'Informacija',
- 'config-localsettings-upgrade' => 'Otkrivena je datoteka <code>LocalSettings.php</code>.
-Da biste unaprijedili vaš softver, molimo vas upišite vrijednost od <code>$wgUpgradeKey</code> u okvir ispod.
-Naći ćete ga u <code>LocalSettings.php</code>.',
- 'config-localsettings-key' => 'Ključ za nadgradnju:',
- 'config-session-error' => 'Greška pri pokretanju sesije: $1',
- 'config-no-session' => 'Vaši podaci sesije su izgubljeni!
-Provjerite vaš php.ini i provjerite da li je <code>session.save_path</code> postavljen na pravilni direktorijum.',
- 'config-your-language' => 'Vaš jezik:',
- 'config-your-language-help' => 'Odaberite jezik koji ćete koristiti tokom procesa instalacije.',
- 'config-wiki-language' => 'Wiki jezik:',
- 'config-wiki-language-help' => 'Odaberite jezik na kojem će wiki biti najvećim dijelim pisana.',
- 'config-back' => '← Nazad',
- 'config-continue' => 'Nastavi →',
- 'config-page-language' => 'Jezik',
- 'config-page-welcome' => 'Dobrodošli u MediaWiki!',
- 'config-page-dbconnect' => 'Poveži sa bazom podataka',
- 'config-page-upgrade' => 'Unaprijedi postojeću instalaciju',
- 'config-page-dbsettings' => 'Postavke baze podataka',
- 'config-page-name' => 'Naziv',
- 'config-page-options' => 'Opcije',
- 'config-page-install' => 'Instaliraj',
- 'config-page-complete' => 'Završeno!',
- 'config-page-restart' => 'Ponovi instalaciju ispočetka',
- 'config-page-readme' => 'Pročitaj me',
- 'config-page-releasenotes' => 'Bilješke izdanja',
- 'config-page-copying' => 'Kopiram',
- 'config-page-upgradedoc' => 'Nadograđujem',
- 'config-help-restart' => 'Da li želite očistiti sve spremljene podatke koje ste unijeli i da započnete ponovo proces instalacije?',
- 'config-restart' => 'Da, pokreni ponovo',
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki Početna strana]
-* [//www.mediawiki.org/wiki/Help:Contents Vodič za korisnike]
-* [//www.mediawiki.org/wiki/Manual:Contents Vodič za administratore]
-* [//www.mediawiki.org/wiki/Manual:FAQ NPP]
-----
-* <doclink href=Readme>Pročitaj me</doclink>
-* <doclink href=ReleaseNotes>Napomene izdanja</doclink>
-* <doclink href=Copying>Kopiranje</doclink>
-* <doclink href=UpgradeDoc>Poboljšavanje</doclink>',
- 'config-env-good' => 'Okruženje je provjereno.
-Možete instalirati MediaWiki.',
- 'config-env-php' => 'PHP $1 je instaliran.',
- 'config-no-db' => 'Nije mogao biti pronađen pogodan driver za bazu podataka! Morate instalirati driver baze podataka za PHP.
-Slijedeće vrste baza podataka su podržane: $1.
-
-Ako se na dijeljenom serveru, tražite od vašeg pružaoca usluga da instalira pogodan driver za bazu podataka.
-Ako se sami kompajlirali PHP, podesite ga sa omogućenim klijentom baze podataka, koristeći naprimjer <code>./configure --with-mysql</code>.
-Ako ste instalirali PHP iz Debian ili Ubuntu paketa, možda morate instalirati i modul php5-mysql.',
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] je instaliran',
- 'config-apc' => '[http://www.php.net/apc APC] je instaliran',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] je instaliran',
- 'config-diff3-bad' => 'GNU diff3 nije pronađen.',
- 'config-db-type' => 'Vrsta baze podataka:',
- 'config-db-host' => 'Domaćin baze podataka:',
- 'config-db-wiki-settings' => 'Identificiraj ovu wiki',
- 'config-db-name' => 'Naziv baze podataka:',
- 'config-db-name-oracle' => 'Šema baze podataka:',
- 'config-header-mysql' => 'Postavke MySQL',
- 'config-header-postgres' => 'Postavke PostgreSQL',
- 'config-header-sqlite' => 'Postavke SQLite',
- 'config-header-oracle' => 'Postavke Oracle',
- 'config-invalid-db-type' => 'Nevaljana vrsta baze podataka',
- 'config-upgrade-done' => "Nadogradnja završena.
-
-Sada možete [$1 početi koristiti vašu wiki].
-
-Ako želite regenerisati vašu datoteku <code>LocalSettings.php</code>, kliknite na dugme ispod.
-Ovo '''nije preporučeno''' osim ako nemate problema s vašom wiki.",
- 'config-admin-name' => 'Vaše ime:',
- 'config-admin-password' => 'Šifra:',
- 'mainpagetext' => "'''MediaViki softver is uspješno instaliran.'''",
- 'mainpagedocfooter' => 'Kontaktirajte [//meta.wikimedia.org/wiki/Help:Contents uputstva za korisnike] za informacije o upotrebi wiki programa.
-
-== Početak ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista postavki]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki najčešće postavljana pitanja]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]', # Fuzzy
-);
-
-/** Catalan (català)
- * @author Pitort
- * @author පසිඳු කාවින්ද
- */
-$messages['ca'] = array(
- 'config-page-language' => 'Llengua',
- 'config-page-name' => 'Nom',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-ns-generic' => 'Projecte',
- 'config-admin-password' => 'Contrasenya:',
- 'config-profile-wiki' => 'Wiki públic',
- 'config-profile-private' => 'Wiki privat',
- 'config-license-pd' => 'Domini públic',
- 'config-upload-deleted' => 'Directori pels arxius suprimits:',
- 'config-advanced-settings' => 'Configuració avançada',
- 'config-extensions' => 'Extensions',
- 'mainpagetext' => "'''El programari del MediaWiki s'ha instaŀlat correctament.'''",
- 'mainpagedocfooter' => "Consulteu la [//meta.wikimedia.org/wiki/Help:Contents Guia d'Usuari] per a més informació sobre com utilitzar-lo.
-
-== Per a començar ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Llista de característiques configurables]
-* [//www.mediawiki.org/wiki/Manual:FAQ PMF del MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de correu (''listserv'') per a anuncis del MediaWiki]", # Fuzzy
-);
-
-/** Chechen (нохчийн)
- * @author Sasan700
- * @author Умар
- */
-$messages['ce'] = array(
- 'config-your-language' => 'Хьан мотт:',
- 'config-continue' => 'Кхин дӀа →',
- 'config-page-language' => 'Мотт',
- 'config-page-name' => 'ЦӀе',
- 'config-no-fts3' => "'''Тергам бе''': SQLite гулйина хуттург йоцуш [//sqlite.org/fts3.html FTS3] — лахар болхбеш хир дац оцу бухца.",
- 'config-site-name' => 'Викин цӀе:',
- 'config-site-name-blank' => 'Язъе сайтан цӀе.',
- 'config-license' => 'Авторан бакъонаш а лицензи а:',
- 'config-license-pd' => 'Юкъараллин хьал',
- 'config-help' => 'гӀо',
- 'mainpagetext' => "'''Вики-белха гlирс «MediaWiki» кхочуш дика дlахlоттийна.'''",
- 'mainpagedocfooter' => 'Викийца болх бан хаамаш карор бу хlокху чохь [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 нисвохааман куьйгаллица].
-
-== Цхьаболу пайде гlирсаш ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Гlирс нисбан тарлушболу могlам];
-* [//www.mediawiki.org/wiki/Manual:FAQ Сих сиха лушдолу хаттарш а жоьпаш оцу MediaWiki];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Хаам бохьуьйту араяларца башхонца керла MediaWiki].', # Fuzzy
-);
-
-/** Cebuano (Cebuano)
- */
-$messages['ceb'] = array(
- 'mainpagetext' => "'''Malamposon ang pag-instalar sa MediaWiki.'''",
- 'mainpagedocfooter' => 'Konsultaha ang [//meta.wikimedia.org/wiki/Help:Contents Giya sa mga gumagamit] alang sa impormasyon unsaon paggamit niining wiki nga software.
-
-== Pagsugod ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listahan sa mga setting sa kompigurasyon]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ sa MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list sa mga release sa MediaWiki]', # Fuzzy
-);
-
-/** Sorani Kurdish (کوردی)
- * @author Asoxor
- * @author Calak
- * @author Muhammed taha
- */
-$messages['ckb'] = array(
- 'config-wiki-language' => 'زمانی ویکی:',
- 'config-back' => '→ گەڕانەوە',
- 'config-continue' => 'بەردەوامبوون ←',
- 'config-page-language' => 'زمان',
- 'config-page-welcome' => 'بەخێربێیت بۆ میدیاویکی!',
- 'config-page-dbconnect' => 'پەیوەندی دەکات بەبنکەی زانیارییەکان',
- 'config-page-upgrade' => 'نويکردنەوەی دابەزاندنی پێشوو',
- 'config-page-dbsettings' => 'ڕێکخستنەکانی بنکەی زانیارییەکان',
- 'config-page-name' => 'ناو',
- 'config-page-options' => 'ھەڵبژاردەکان',
- 'config-page-install' => 'دابەزاندن',
- 'config-page-complete' => 'تەواو!',
- 'config-page-restart' => 'دەست پێکردنەوە بەدابەزاندن',
- 'config-page-readme' => 'بمخوێنەوە',
- 'config-page-copying' => 'لەبەردەگیرێتەوە',
- 'config-page-upgradedoc' => 'نوێدەکرێتەوە',
- 'config-page-existingwiki' => 'ویکی پێشوو',
- 'config-restart' => 'بەڵێ، دەستی پێ بکەرەوە',
- 'config-env-php' => 'PHP $1 دابەزێندرا.',
- 'config-env-php-toolow' => 'PHP $1 دابەزێندرا.
-ھەرچۆنێک بێت میدیاویکی پێویستی بە PHP $2 یان بەرزتر ھەیە.',
- 'mainpagetext' => "'''میدیاویکی بە سەرکەوتوویی دامەزرا.'''",
- 'mainpagedocfooter' => 'لە [//meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی کەڵک وەربگرە.
-
-== دەستپێکردن ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings پێرستی ڕێکخستنەکانی شێوەپێدان]
-* [//www.mediawiki.org/wiki/Manual:FAQ پرسیارە دووپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce پێرستی ئیمەیلی وەشانەکانی میدیاویکی]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources خۆماڵیکردنی ویکیمیدیا بۆ زمانەکەت]',
-);
-
-/** Capiznon (Capiceño)
- * @author Oxyzen
- */
-$messages['cps'] = array(
- 'mainpagetext' => "'''Madalag-on nga na-install ang MediaWiki.'''",
- 'mainpagedocfooter' => 'Kunsultahon ang [//meta.wikimedia.org/wiki/Help:Pagtuytoy sa Manug-usar] para sa impormasyon sa paggamit sang wiki nga "software".
-
-==Pag-umpisa==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sang mga setting sang konpigurayon]
-* [//www.mediawiki.org/wiki/Manual:FAQ Mga perme napangkot sa MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat sang MediaWiki]', # Fuzzy
-);
-
-/** Crimean Turkish (Cyrillic script) (къырымтатарджа (Кирилл)‎)
- */
-$messages['crh-cyrl'] = array(
- 'mainpagetext' => "'''MediaWiki мувафакъиетнен къурулды.'''",
- 'mainpagedocfooter' => "Бу викининъ ёл-ёругъыны [//meta.wikimedia.org/wiki/Help:Contents User's Guide къулланыджы къылавузындан] огренип оласынъыз.
-
-== Базы файдалы сайтлар ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Олуджы сазламалар джедвели];
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki боюнджа сыкъ берильген суаллернен джеваплар];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-нинъ янъы версияларынынъ чыкъувындан хабер йиберюв].", # Fuzzy
-);
-
-/** Crimean Turkish (Latin script) (qırımtatarca (Latin)‎)
- */
-$messages['crh-latn'] = array(
- 'mainpagetext' => "'''MediaWiki muvafaqiyetnen quruldı.'''",
- 'mainpagedocfooter' => "Bu vikiniñ yol-yoruğını [//meta.wikimedia.org/wiki/Help:Contents User's Guide qullanıcı qılavuzından] ögrenip olasıñız.
-
-== Bazı faydalı saytlar ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Olucı sazlamalar cedveli];
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki boyunca sıq berilgen suallernen cevaplar];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-niñ yañı versiyalarınıñ çıquvından haber yiberüv].", # Fuzzy
-);
-
-/** Czech (česky)
- * @author Danny B.
- * @author Jezevec
- * @author Mormegil
- * @author 아라
- */
-$messages['cs'] = array(
- 'config-desc' => 'Instalační program pro MediaWiki',
- 'config-title' => 'Instalace MediaWiki $1',
- 'config-information' => 'Informace',
- 'config-localsettings-upgrade' => 'Byl nalezen soubor <code>LocalSettings.php</code>.
-Pokud chcete stávající instalaci aktualizovat, zadejte hodnotu <code>$wgUpgradeKey</code>, kterou naleznete v souboru <code>LocalSettings.php</code>, do následujícího rámečku.',
- 'config-localsettings-cli-upgrade' => 'Byl detekován soubor <code><code>LocalSettings.php</code></code>
-Pro aktualizaci spusťte místo instalace skript <code>update.php</code>.',
- 'config-localsettings-key' => 'Klíč pro aktualizaci:',
- 'config-localsettings-badkey' => 'Zadaný klíč je nesprávný.',
- 'config-upgrade-key-missing' => 'Byla detekována existující instalace MediaWiki.
-Pokud ji chcete aktualizovat, přidejte následující řádku na konec souboru <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Existující soubor <code>LocalSettings.php</code> vypadá neúplný.
-Není nastavena proměnná $1.
-Upravte soubor <code>LocalSettings.php</code> tak, aby tuto proměnnou obsahoval, a klikněte na „{{int:Config-continue}}“.',
- 'config-localsettings-connection-error' => 'Při připojování k databázi s využitím nastavení uvedených v <code>LocalSettings.php</code> nebo <code>AdminSettings.php</code> došlo k chybě. Opravte tato nastavení a zkuste to znovu.
-
-$1',
- 'config-session-error' => 'Nepodařilo se inicializovat relaci: $1',
- 'config-session-expired' => 'Platnost dat vašeho sezení patrně vypršela.
-Sezení má nastavenu životnost $1.
-Prodloužit ji můžete nastavením <code>session.gc_maxlifetime</code> v php.ini.
-Spusťte instalační proces od začátku.',
- 'config-no-session' => 'Data vašeho sezení se ztratila!
-Zkontrolujte svůj soubor php.ini a ujistěte se, že <code>session.save_path</code> je nastaveno na odpovídající adresář.',
- 'config-your-language' => 'Váš jazyk:',
- 'config-your-language-help' => 'Zvolte jazyk, který se má použít v průběhu instalace.',
- 'config-wiki-language' => 'Jazyk wiki:',
- 'config-wiki-language-help' => 'Zvolte jazyk, ve kterém bude většina obsahu wiki.',
- 'config-back' => '← Zpět',
- 'config-continue' => 'Pokračovat →',
- 'config-page-language' => 'Jazyk',
- 'config-page-welcome' => 'Vítejte v MediaWiki!',
- 'config-page-dbconnect' => 'Připojení k databázi',
- 'config-page-upgrade' => 'Aktualizace existující instalace',
- 'config-page-dbsettings' => 'Nastavení databáze',
- 'config-page-name' => 'Název',
- 'config-page-options' => 'Nastavení',
- 'config-page-install' => 'Instalovat',
- 'config-page-complete' => 'Hotovo!',
- 'config-page-restart' => 'Restartovat instalaci',
- 'config-page-readme' => 'Soubor Čti mě',
- 'config-page-releasenotes' => 'Poznámky k vydání',
- 'config-page-copying' => 'Licence',
- 'config-page-upgradedoc' => 'Upgrade',
- 'config-page-existingwiki' => 'Existující wiki',
- 'config-help-restart' => 'Chcete smazat všechny údaje, které jste zadali, a spustit proces instalace znovu od začátku?',
- 'config-restart' => 'Ano, restartovat',
- 'config-welcome' => '=== Kontrola prostředí ===
-Nyní se provedou základní kontroly, aby se zjistilo, zda je toto prostředí použitelné k instalaci MediaWiki.
-Pokud budete potřebovat k dokončení instalace pomoc, nezapomeňte sdělit výsledky těchto testů.',
- 'config-copyright' => "=== Licence a podmínky ===
-$1
-
-Tento program je svobodný software; můžete jej šířit nebo modifikovat podle podmínek GNU General Public License, vydávané Free Software Foundation; buď verze 2 této licence anebo (podle vašeho uvážení) kterékoli pozdější verze.
-
-Tento program je distribuován v naději, že bude užitečný, avšak '''bez jakékoli záruky'''; neposkytují se ani odvozené záruky '''prodejnosti''' anebo '''vhodnosti pro určitý účel'''.
-Podrobnosti se dočtete v textu GNU General Public License.
-
-<doclink href=Copying>Kopii GNU General Public License</doclink> jste měli obdržet spolu s tímto programem; pokud ne, napište na Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA nebo [http://www.gnu.org/copyleft/gpl.html si ji přečtěte online].",
- 'config-sidebar' => '* [//www.mediawiki.org Oficiální web MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Uživatelská příručka]
-* [//www.mediawiki.org/wiki/Manual:Contents Administrátorská příručka]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>Čti mě</doclink>
-* <doclink href=ReleaseNotes>Poznámky k vydání</doclink>
-* <doclink href=Copying>Licence</doclink>
-* <doclink href=UpgradeDoc>Upgrade</doclink>',
- 'config-env-good' => 'Prostředí bylo zkontrolováno.
-Můžete nainstalovat MediaWiki.',
- 'config-env-bad' => 'Prostředí bylo zkontrolováno.
-MediaWiki nelze nainstalovat.',
- 'config-env-php' => 'Je nainstalováno PHP $1.',
- 'config-env-php-toolow' => 'Je nainstalováno PHP $1.
-MediaWiki ale vyžaduje PHP $2 nebo vyšší.',
- 'config-unicode-using-utf8' => 'Pro normalizaci Unicode se používá utf8_normalize.so Briona Vibbera.',
- 'config-unicode-using-intl' => 'Pro normalizaci Unicode se používá [http://pecl.php.net/intl PECL rozšíření intl].',
- 'config-unicode-pure-php-warning' => "'''Upozornění''': Není dostupné [http://pecl.php.net/intl PECL rozšíření intl] pro normalizaci Unicode, bude se využívat pomalá implementace v čistém PHP.
-Pokud provozujete wiki s velkým provozem, měli byste si přečíst něco o [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalizaci Unicode].",
- 'config-unicode-update-warning' => "'''Upozornění''': Nainstalovaná verze vrstvy pro normalizaci Unicode používá starší verzi knihovny [http://site.icu-project.org/ projektu ICU].
-Pokud vám aspoň trochu záleží na používání Unicode, měli byste [//www.mediawiki.org/wiki/Unicode_normalization_considerations ji aktualizovat].",
- 'config-no-db' => 'Nepodařilo se nalézt vhodný databázový ovladač! Musíte do PHP nainstalovat databázový ovladač.
-Jsou podporovány následující typy databází: $1.
-
-Pokud jste na sdíleném hostingu, požádejte svého poskytovale o instalaci vhodného databázového ovladače.
-Pokud jste si PHP přeložili sami, překonfigurujte ho se zapnutým databázovým klientem, například pomocí <code>./configure --with-mysql</code>.
-Pokud jste PHP nainstalovali z balíčku Debian či Ubuntu, potřebujete nainstalovat také modul php5-mysql.',
- 'config-outdated-sqlite' => "'''Upozornění''': Máte SQLite $1, které je starší než minimálně vyžadovaná verze $2. SQLite nebude dostupné.",
- 'config-no-fts3' => "'''Upozornění''': SQLite bylo přeloženo bez [//sqlite.org/fts3.html modulu FTS3], funkce pro vyhledávání zde nebudou dostupné.",
- 'config-register-globals' => "'''Upozornění: Je zapnuta PHP volba <code>[http://php.net/register_globals register_globals]</code>.'''
-'''Pokud můžete, vypněte ji.'''
-MediaWiki bude fungovat, ale váš server je vystaven potenciálním bezpečnostním hrozbám.",
- 'config-magic-quotes-runtime' => "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
-Toto nastavení nepředvídatelně poškozuje vstupní data.
-MediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
- 'config-magic-quotes-sybase' => "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''
-Toto nastavení nepředvídatelně poškozuje vstupní data.
-MediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
- 'config-mbstring' => "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''
-Toto nastavení způsobuje chyby a může nepředvídatelně poškozovat vstupní data.
-MediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
- 'config-ze1' => "'''Kritická chyba: Je zapnut [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]!'''
-Toto nastavení způsobuje s MediaWiki příšerné chyby.
-MediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
- 'config-safe-mode' => "'''Upozornění:''' Je aktivní [http://www.php.net/features.safe-mode bezpečný režim] PHP.
-Může způsobovat potíže, zejména při použití načítání souborů a podpory <code>math</code>.",
- 'config-xml-bad' => 'Chybí XML modul pro PHP.
-MediaWiki potřebuje funkce v tomto modulu a v této konfiguraci nebude fungovat.
-Pokud běžíte na Mandrake, nainstalujte balíček php-xml.',
- 'config-pcre' => 'Zdá se, že modul s podporou PCRE chybí.
-MediaWiki ke své činnosti potřebuje funkce pro Perl-kompatibilní regulární výrazy.',
- 'config-pcre-no-utf8' => "'''Kritická chyba''': PHP modul PCRE byl zřejmě přeložen bez podpory PCRE_UTF8.
-MediaWiki 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' => "'''Upozornění:''' <code>memory_limit</code> je v PHP nastaven na $1.
-To je pravděpodobně příliš málo.
-Instalace může selhat!",
- 'config-ctype' => "'''Kritická chyba''': PHP musí být přeloženo s podporou pro [http://www.php.net/manual/en/ctype.installation.php rozšíření Ctype].",
- 'config-json' => "'''Kritická chyba:''' PHP bylo přeloženo bez podpory JSON.
-Před instalací MediaWiki musíte buď nainstalovat rozšíření PHP JSON nebo rozšíření [http://pecl.php.net/package/jsonc PECL jsonc].
-* Rozšíření PHP je součástí Red Hat Enterprise Linux (CentOS) 5 a 6, avšak musí se povolit v <code>/etc/php.ini</code> nebo <code>/etc/php.d/json.ini</code>.
-* V některých linuxových distribucích vydaných po květnu 2013 může toto rozšíření PHP chybět a místo toho mohou používat rozšíření PECL jako <code>php5-json</code> nebo <code>php-pecl-jsonc</code>.",
- 'config-xcache' => 'Je nainstalována [http://xcache.lighttpd.net/ XCache]',
- 'config-apc' => 'Je nainstalováno [http://www.php.net/apc APC]',
- 'config-wincache' => 'Je nainstalována [http://www.iis.net/download/WinCacheForPhp WinCache]',
- 'config-no-cache' => "'''Upozornění:''' Nebylo nalezeno [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], ani [http://www.iis.net/download/WinCacheForPhp WinCache].
-Kešování objektů bude vypnuto.",
- 'config-mod-security' => "'''Upozornění''': váš webový server má zapnuto [http://modsecurity.org/ mod_security]. Při chybné konfiguraci může způsobovat potíže MediaWiki či dalším programům, které umožňují ukládat libovolný obsah.
-Pokud narazíte na náhodné chyby, podívejte se do [http://modsecurity.org/documentation/ dokumentace mod_security] nebo kontaktujte technickou podporu vašeho poskytovatele.",
- 'config-diff3-bad' => 'Nebyl nalezen GNU diff3.',
- 'config-git' => 'Nalezen software pro správu verzí Git: <code>$1</code>.',
- 'config-git-bad' => 'Software pro správu verzí Git nebyl nalezen.',
- 'config-imagemagick' => 'Nalezen ImageMagick: <code>$1</code>.
-Pokud povolíte načítání souborů, bude zapnuto vytváření náhledů.',
- 'config-gd' => 'Nalezena vestavěná grafická knihovna GD.
-Pokud povolíte načítání souborů, bude zapnuto vytváření náhledů.',
- 'config-no-scaling' => 'Nebyla nalezena knihovna GD ani ImageMagick.
-Vytváření náhledů bude vypnuto.',
- 'config-no-uri' => "'''Chyba:''' Nepodařilo se určit aktuální URI.
-Instalace přerušena.",
- 'config-no-cli-uri' => "'''Upozornění''': Nebylo uvedeno --scriptpath, používá se implicitní hodnota: <code>$1</code>.",
- 'config-using-server' => 'Použito jméno serveru „<nowiki>$1</nowiki>“.',
- 'config-using-uri' => 'Použito URL serveru „<nowiki>$1$2</nowiki>“.',
- 'config-uploads-not-safe' => "'''Upozornění:''' Váš implicitní adresář pro načítání souborů <code>$1</code> umožňuje provádění libovolných skriptů.
-Přestože MediaWiki všechny načítané soubory kontroluje proti bezpečnostním hrozbám, je důrazně doporučeno [//www.mediawiki.org/wiki/Manual:Security#Upload_security tuto bezpečnostní díru zacelit] před povolením načítání souborů.",
- 'config-no-cli-uploads-check' => "'''Upozornění:''' Váš implicitní adresář pro načítané soubory (<code>$1</code>) se při instalaci z příkazového řádku nekontroluje na bezpečnostní hrozbu provádění libovolných skriptů.",
- 'config-brokenlibxml' => 'Váš systém obsahuje kombinaci verzí PHP a libxml2, která je chybná a může v MediaWiki a dalších webových aplikacích způsobovat skryté poškozování dat.
-Aktualizujte na PHP 5.2.9 nebo novější a libxml2 2.7.3 nebo novější ([//bugs.php.net/bug.php?id=45996 chyba evidovaná u PHP]).
-Instalace přerušena.',
- 'config-using531' => 'MediaWiki nelze používat na PHP $1 kvůli chybě při předávání parametrů odkazem do <code>__call()</code>.
-Pro vyřešení upgradujte na PHP 5.3.2 nebo vyšší nebo downgradujte na PHP 5.3.0.
-Instalace přerušena.',
- 'config-suhosin-max-value-length' => 'Je nainstalován Suhosin, který omezuje délku parametrů GET na $1 bajtů.
-Komponenta ResourceLoader z MediaWiki dokáže s tímto omezením pracovat, ale sníží to výkon.
-Pokud to je alespoň trochu možné, měli byste v <code>php.ini</code> nastavit <code>suhosin.get.max_value_length</code> na 1024 nebo vyšší a na stejnou hodnotu nastavit v <code>LocalSettings.php</code> proměnnou <code>$wgResourceLoaderMaxQueryLength</code>.',
- 'config-db-type' => 'Typ databáze:',
- 'config-db-host' => 'Databázový server:',
- 'config-db-host-help' => 'Pokud je váš databázový server na jiném počítači, zadejte zde jméno stroje nebo IP adresu.
-
-Pokud používáte sdílený webový hosting, váš poskytovatel by vám měl v dokumentaci sdělit správné jméno stroje.
-
-Pokud instalujete na server běžící na Windows a používáte MySQL, jméno „localhost“ nemusí fungovat. V takovém případě zkuste jako místní IP adresu zadat „127.0.0.1“.
-
-Pokud používáte PostgreSQL, můžete se připojit Unixovými sockety tak, že toto pole necháte prázdné.',
- 'config-db-host-oracle' => 'Databázové TNS:',
- 'config-db-host-oracle-help' => 'Zadejte platné [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; tato instalace musí vidět soubor tnsnames.ora.<br />Pokud používáte klientské knihovny verze 10g nebo novější, můžete také používat názvy [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identifikace této wiki',
- 'config-db-name' => 'Jméno databáze:',
- 'config-db-name-help' => 'Zvolte jméno, které označuje vaši wiki.
-Nemělo by obsahovat mezery.
-
-Pokud používáte sdílený webový hosting, váš poskytovatel vám buď sdělí konkrétní jméno databáze, nebo vás nechá vytvářet databáze pomocí nějakého ovládacího panelu.',
- 'config-db-name-oracle' => 'Databázové schéma:',
- 'config-db-account-oracle-warn' => 'Existují tři podporované možnosti pro instalaci s použitím databáze Oracle.
-
-Pokud chcete v rámci instalace založit databázový účet, zadejte jako databázový účet pro instalaci účet s rolí SYSDBA a uveďte požadované údaje pro účet pro webový přístup, jinak můžete vytvořit účet pro webový přístup ručně a zadat pouze tento účet (pokud má dostatečná oprávnění k zakládání objektů schématu) nebo poskytnout dva různé účty, jeden s oprávněními k zakládání, druhý omezený pro webový přístup.
-
-Skript pro založení účtu s potřebnými privilegii můžete v této instalaci nalézt v adresáři „maintenance/oracle/“. Nezapomeňte, že použití omezeného účtu znepřístupní veškeré možnosti údržby přes implicitní účet.',
- 'config-db-install-account' => 'Uživatelský účet pro instalaci',
- 'config-db-username' => 'Databázové uživatelské jméno:',
- 'config-db-password' => 'Databázové heslo:',
- 'config-db-password-empty' => 'Zadejte heslo pro nového databázového uživatele: $1.
-Přestože může jít zakládat nové uživatele i bez hesel, není to bezpečné.',
- 'config-db-install-username' => 'Zadejte uživatelské jméno, které se použije pro připojení k databázi v průběhu instalace.
-Toto není jméno uživatelského účtu MediaWiki; toto je uživatelské jméno k vaší databázi.',
- 'config-db-install-password' => 'Zadejte heslo, které se použije pro připojení k databázi v průběhu instalace.
-Toto není heslo uživatelského účtu MediaWiki; toto je heslo k vaší databázi.',
- 'config-db-install-help' => 'Zadejte uživatelské jméno a heslo, které se použijí pro připojení k databázi v průběhu instalace.',
- 'config-db-account-lock' => 'Použít stejné uživatelské jméno a heslo pro běžnou činnost',
- 'config-db-wiki-account' => 'Uživatelský účet pro běžnou činnost',
- 'config-db-wiki-help' => 'Zadejte uživatelské jméno a heslo, které se bude používat pro připojení k databázi za běžného provozu wiki.
-Pokud účet neexistuje a instalační účet má dostatečná oprávnění, bude tento uživatelský účet založen s minimálními oprávněními potřebnými k provozu wiki.',
- 'config-db-prefix' => 'Prefix databázových tabulek:',
- 'config-db-prefix-help' => 'Pokud potřebujete sdílet jednu databázi mezi vícero wiki, případně mezi MediaWiki a další webovou aplikací, můžete přidat k názvu každé tabulky prefix, abyste se vyhnuli konfliktům.
-Nepoužívejte mezery.
-
-Toto pole se zpravidla ponechává prázdné.',
- 'config-db-charset' => 'Znaková sada databáze',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binární',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 zpětně kompatibilní UTF-8',
- 'config-charset-help' => "'''Upozornění:''' Pokud použijete '''zpětně kompatibilní UTF-8''' na MySQL 4.1+ a následně zazálohujete databázi pomocí <code>mysqldump</code>, může to zničit všechny ne-ASCII znaky, což nevratně poškodí vaše zálohy!
-
-V '''binárním režimu''' ukládá MediaWiki text v UTF-8 do databáze v binárních sloupcích.
-To je výkonnější než UTF-8 režim MySQL a umožňuje využít plný rozsah znaků Unicode.
-V '''režimu UTF-8''' bude MySQL znát znakovou sadu vašich dat a může je příslušně zobrazovat a převádět,
-ale neumožní vám uložit znaky mimo [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-mysql-old' => 'Je vyžadováno MySQL $1 nebo novější, vy máte $2.',
- 'config-db-port' => 'Databázový port:',
- 'config-db-schema' => 'Schéma pro MediaWiki:',
- 'config-db-schema-help' => 'Toto schéma zpravidla stačí.
-Měňte ho, jen pokud víte, že je to potřeba.',
- 'config-pg-test-error' => "Nelze se připojit k databázi '''$1''': $2",
- 'config-sqlite-dir' => 'Adresář pro data SQLite:',
- 'config-sqlite-dir-help' => "SQLite ukládá veškerá data v jediném souboru.
-
-Zadaný adresář musí být v průběhu instalace být přístupný pro zápis.
-
-'''Neměl by''' být dostupný z webu, proto ho nedáváme tam, kde jsou vaše PHP soubory.
-
-Instalátor do adresáře přidá soubor <code>.htaccess</code>, ale pokud to selže, mohl by někdo získat přístup k vaší holé databázi.
-To zahrnuje syrová uživatelská data (e-mailové adresy, hašovaná hesla), jako i smazané revize a další data s omezeným přístupem z vaší wiki.
-
-Zvažte umístění databáze někam zcela jinam, například do <code>/var/lib/mediawiki/mojewiki</code>.",
- 'config-oracle-def-ts' => 'Implicitní tabulkový prostor:',
- 'config-oracle-temp-ts' => 'Dočasný tabulkový prostor:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Věštba',
- 'config-support-info' => 'MediaWiki podporuje následující databázové systémy:
-
-$1
-
-Pokud 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-support-mysql' => '* $1 je pro MediaWiki hlavní platformou a je podporováno nejlépe ([http://www.php.net/manual/en/mysql.installation.php jak zkompilovat PHP s podporou MySQL])',
- 'config-support-postgres' => '* $1 je populární open-source databázový systém používaný jako alternativa k MySQL ([http://www.php.net/manual/en/pgsql.installation.php jak přeložit PHP s podporou PostgreSQL]). Mohou se vyskytnout ještě nějaké menší chyby, použití v produkčním prostředí se nedoporučuje.',
- 'config-support-sqlite' => '* $1 je velmi dobře podporovaný lehký databázový systém. ([http://www.php.net/manual/en/pdo.installation.php Jak přeložit PHP s podporou SQLite], používá PDO)',
- 'config-support-oracle' => '* $1 je komerční podniková databáze. ([http://www.php.net/manual/en/oci8.installation.php Jak přeložit PHP s podporou OCI8])',
- 'config-header-mysql' => 'Nastavení MySQL',
- 'config-header-postgres' => 'Nastavení PostgreSQL',
- 'config-header-sqlite' => 'Nastavení SQLite',
- 'config-header-oracle' => 'Nastavení Oracle',
- 'config-invalid-db-type' => 'Chybný typ databáze',
- 'config-missing-db-name' => 'Musíte zadat hodnotu pro „Jméno databáze“',
- 'config-missing-db-host' => 'Musíte zadat hodnotu pro „Databázový server“',
- 'config-missing-db-server-oracle' => 'Musíte zadat hodnotu pro „Databázové TNS“',
- 'config-invalid-db-server-oracle' => 'Chybné databázové TNS „$1“.
-Používejte buď „TNS Name“ nebo „Easy Connect“ (vizte [http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]).',
- 'config-invalid-db-name' => 'Chybné jméno databáze „$1“.
-Používejte pouze ASCII písmena (a-z, A-Z), čísla (0-9), podtržítko (_) a spojovník (-).',
- 'config-invalid-db-prefix' => 'Chybný databázový prefix „$1“.
-Používejte pouze ASCII písmena (a-z, A-Z), čísla (0-9), podtržítko (_) a spojovník (-).',
- 'config-connection-error' => '$1.
-
-Zkontrolujte server, uživatelské jméno a heslo a zkuste to znovu.',
- 'config-invalid-schema' => 'Neplatné schéma pro MediaWiki „$1“.
-Používejte pouze ASCII písmena (a-z, A-Z), čísla (0-9) a podtržítko (_).',
- 'config-db-sys-create-oracle' => 'Instalátor podporuje zakládání nového účtu pouze prostřednictvím účtu SYSDBA.',
- 'config-db-sys-user-exists-oracle' => 'Uživatelský účet „$1“ již existuje. SYSDBA lze použít pouze pro založení nového účtu!',
- 'config-postgres-old' => 'Je vyžadován PostgreSQL $1 nebo novější, vy máte $2.',
- 'config-sqlite-name-help' => 'Zvolte jméno, které označuje vaši wiki.
-Nepoužívejte mezery a spojovníky.
-Použije se jako název souboru s daty SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Nelze vytvořit datový adresář <code><nowiki>$1</nowiki></code>, protože do nadřazeného adresáře <code><nowiki>$2</nowiki></code> nemá webový server právo zapisovat.
-
-Instalátor zjistil uživatele, pod kterým váš webový server běží.
-Abyste mohli pokračovat, umožněte mu zapisovat do adresáře <code><nowiki>$3</nowiki></code>.
-Na systémech Unix/Linux proveďte:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Nelze vytvořit datový adresář <code><nowiki>$1</nowiki></code>, protože do nadřazeného adresáře <code><nowiki>$2</nowiki></code> nemá webový server právo zapisovat.
-
-Instalátoru se nepodařilo zjistit uživatele, pod kterým váš webový server běží.
-Abyste mohli pokračovat, umožněte zápis do <code><nowiki>$3</nowiki></code> všem uživatelům.
-Na systémech Unix/Linux proveďte:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Chyba při vytváření datového adresáře „$1“.
-Zkontrolujte umístění a zkuste to znovu.',
- 'config-sqlite-dir-unwritable' => 'Nelze zapisovat do adresáře „$1“.
-Změňte na něm oprávnění, aby do něj mohl webový server zapisovat, a zkuste to znovu.',
- 'config-sqlite-connection-error' => '$1.
-
-Zkontrolujte datový adresář a jméno databáze níže a zkuste to znovu.',
- 'config-sqlite-readonly' => 'Do souboru <code>$1</code> nelze zapisovat.',
- 'config-sqlite-cant-create-db' => 'Nepodařilo se vytvořit databázový soubor <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP neobsahuje podporu FTS3, downgradují se tabulky',
- 'config-can-upgrade' => "V této databázi jsou tabulky MediaWiki.
-Pokud je chcete aktualizovat na MediaWiki $1, klikněte na '''Pokračovat'''.",
- 'config-upgrade-done' => "Aktualizace byla dokončena.
-
-Svou wiki teď můžete [$1 začít používat].
-
-Pokud chcete přegenerovat soubor <code>LocalSettings.php</code>, klikněte na tlačítko níže.
-To se ale '''nedoporučuje''', pokud s wiki nemáte problémy.",
- 'config-upgrade-done-no-regenerate' => 'Aktualizace byla dokončena.
-
-Svou wiki teď můžete [$1 začít používat].',
- 'config-regenerate' => 'Přegenerovat LocalSettings.php →',
- 'config-show-table-status' => 'Dotaz <code>SHOW TABLE STATUS</code> se nezdařil!',
- 'config-unknown-collation' => "'''Upozornění:''' Databáze používá nerozpoznané řazení.",
- 'config-db-web-account' => 'Databázový účet pro webový přístup',
- 'config-db-web-help' => 'Zvolte uživatelské jméno a heslo, které bude webový server používat pro připojení k databázovému serveru při běžném provozu wiki.',
- 'config-db-web-account-same' => 'Použít stejný účet jako pro instalaci',
- 'config-db-web-create' => 'Založit účet, pokud zatím neexistuje',
- 'config-db-web-no-create-privs' => 'Účet uvedený pro instalaci nemá oprávnění dostatečná pro založení nového účtu.
-Účet, který zde uvedete, již musí existovat.',
- 'config-mysql-engine' => 'Typ úložiště:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Upozornění''': Jako typ úložiště pro MySQL jste zvolili MyISAM, které není pro použití v MediaWiki doporučeno, neboť:
-* stěží podporuje současný přístup kvůli zamykání tabulek,
-* je náchylnější na poškození dat než jiná úložiště,
-* kód MediaWiki nepodporuje MyISAM vždy tak dobře, jak by měl.
-
-Pokud vaše instalace MySQL podporuje InnoDB, důrazně doporučujeme použít spíše to.
-Pokud vaše instalace MySQL InnoDB nepodporuje, možná je čas na aktualizaci.",
- 'config-mysql-only-myisam-dep' => "'''Upozornění:''' Jediným dostupným formátem dat pro MySQL je MyISAM, který se k užití pro MediaWiki nedoporučuje, protože:
-* téměř nepodporuje současný přístup kvůli zamykání tabulek,
-* oproti jiným formátům je náchylnější k poškození,
-* kód MediaWiki nepodporuje MyISAM tak dobře, jak by bylo potřeba.
-
-Vaše instalace MySQL nepodporuje InnoDB, možná je na čase upgradovat.",
- 'config-mysql-engine-help' => "'''InnoDB''' je téměř vždy nejlepší volba, neboť má dobrou podporu současného přístupu.
-
-'''MyISAM''' může být rychlejší u instalací pro jednoho uživatele nebo jen pro čtení.
-Databáze MyISAM bývají poškozeny častěji než databáze InnoDB.",
- 'config-mysql-charset' => 'Znaková sada databáze:',
- 'config-mysql-binary' => 'Binární',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "V '''binárním režimu''' ukládá MediaWiki text v UTF-8 do databáze v binárních sloupcích.
-To je výkonnější než UTF-8 režim MySQL a umožňuje využít plný rozsah znaků Unicode.
-
-V '''režimu UTF-8''' bude MySQL znát znakovou sadu vašich dat a může je příslušně zobrazovat a převádět, ale neumožní vám uložit znaky mimo [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-site-name' => 'Název wiki:',
- 'config-site-name-help' => 'Bude se zobrazovat v titulku prohlížeče a na dalších místech.',
- 'config-site-name-blank' => 'Zadejte název serveru.',
- 'config-project-namespace' => 'Jmenný prostor projektu:',
- 'config-ns-generic' => 'Projekt',
- 'config-ns-site-name' => 'Stejný jako název wiki: $1',
- 'config-ns-other' => 'Jiný (uveďte)',
- 'config-ns-other-default' => 'MojeWiki',
- 'config-project-namespace-help' => "Po vzoru Wikipedie udržuje mnoho wiki stránky se svými pravidly odděleně od stránek s vlastním obsahem, v „'''jmenném prostoru projektu'''“.
-Názvy všech stránek v tomto jmenném prostoru začínají jistým prefixem, který zde můžete nastavit.
-Zvykem je odvozovat tento prefix z názvu wiki, ale nesmí obsahovat jisté interpunkční znaky jako „#“ nebo „:“.",
- 'config-ns-invalid' => 'Uvedený jmenný prostor „<nowiki>$1</nowiki>“ je neplatný.
-Zadejte jiný jmenný prostor projektu.',
- 'config-ns-conflict' => 'Uvedený jmenný prostor „<nowiki>$1</nowiki>“ koliduje se standardním jmenným prostorem MediaWiki.
-Zadejte jiný jmenný prostor projektu.',
- 'config-admin-box' => 'Správcovský účet',
- 'config-admin-name' => 'Vaše jméno:',
- 'config-admin-password' => 'Heslo:',
- 'config-admin-password-confirm' => 'Heslo ještě jednou:',
- 'config-admin-help' => 'Zde zadejte své požadované uživatelské jméno, například „Pepa Novák“.
-Tímto jménem se budete do wiki hlásit.',
- 'config-admin-name-blank' => 'Zadejte uživatelské jméno správce.',
- 'config-admin-name-invalid' => 'Uvedené uživatelské jméno „<nowiki>$1</nowiki>“ není platné.
-Zadejte jiné uživatelské jméno.',
- 'config-admin-password-blank' => 'Zadejte heslo ke správcovskému účtu.',
- 'config-admin-password-same' => 'Heslo nesmí být stejné jako uživatelské jméno.',
- 'config-admin-password-mismatch' => 'Uvedená hesla se neshodují.',
- 'config-admin-email' => 'E-mailová adresa:',
- 'config-admin-email-help' => 'Zde zadejte e-mailovou adresu, která vám umožní přijímat e-maily od ostatních uživatelů wiki, získat nové heslo a přijímat notifikace o změnách sledovaných stránek. Tohle pole můžete nechat prázdné.',
- 'config-admin-error-user' => 'Vnitřní chyba při vytváření správce se jménem „<nowiki>$1</nowiki>“.',
- 'config-admin-error-password' => 'Vnitřní chyba při nastavování hesla správci se jménem „<nowiki>$1</nowiki>“: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Zadali jste neplatnou e-mailovou adresu.',
- 'config-subscribe' => 'Přihlásit se k odběru [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-mailové konference pro oznamování nových verzí].',
- 'config-subscribe-help' => 'Tohle je e-mailová konference s nízkým provozem, na které se oznamují nové verze, včetně důležitých bezpečnostních oznámení.
-Měli byste se do ní přihlásit a při vydání nových verzí aktualizovat svou instalaci MediaWiki.',
- 'config-subscribe-noemail' => 'Pokusili jste se přihlásit k odběru e-mailové konference pro oznamování nových verzí, aniž byste poskytli e-mailovou adresu.
-Pokud se chcete přihlásit k odběru, zadejte e-mailovou adresu.',
- 'config-almost-done' => 'Už jsme skoro hotovi!
-Zbývající konfiguraci už můžete přeskočit a nainstalovat wiki hned teď.',
- 'config-optional-continue' => 'Ptejte se mě dál.',
- 'config-optional-skip' => 'Už mě to nudí, prostě nainstalujte wiki.',
- 'config-profile' => 'Profil uživatelských práv:',
- 'config-profile-wiki' => 'Otevřená wiki',
- 'config-profile-no-anon' => 'Vyžadována registrace uživatelů',
- 'config-profile-fishbowl' => 'Editace jen pro vybrané',
- 'config-profile-private' => 'Soukromá wiki',
- 'config-profile-help' => "Wiki fungují nejlépe, když je necháte editovat co největším možným počtem lidí.
-V MediaWiki můžete snadno kontrolovat poslední změny a vracet zpět libovolnou škodu způsobenou hloupými nebo zlými uživateli.
-
-Mnoho lidí však zjistilo, že je MediaWiki užitečné v širokém spektru rolí a někdy není snadné všechny přesvědčit o výhodách wikizvyklostí.
-Takže si můžete vybrat.
-
-Model '''{{int:config-profile-wiki}}''' dovoluje editovat všem, aniž by se museli přihlašovat.
-Na wiki, kde je '''{{int:config-profile-no-anon}}''', se lépe řídí zodpovědnost, ale může to odradit náhodné přispěvatele.
-
-Profil '''{{int:config-profile-fishbowl}}''' umožňuje schváleným uživatelům editovat, ale veřejnost si může stránky prohlížet včetně jejich historie.
-'''{{int:config-profile-private}}''' dovoluje stránky prohlížet jen schváleným uživatelům, kteří je i mohou editovat.
-
-Po instalaci je možná komplexní konfigurace uživatelských práv; vizte [//www.mediawiki.org/wiki/Manual:User_rights odpovídající stránku příručky].",
- 'config-license' => 'Autorská práva a licence:',
- 'config-license-none' => 'Bez patičky s licencí',
- 'config-license-cc-by-sa' => 'Creative Commons Uveďte autora-Zachovejte licenci',
- 'config-license-cc-by' => 'Creative Commons Uveďte autora',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Uveďte autora-Nevyužívejte dílo komerčně-Zachovejte licenci',
- 'config-license-cc-0' => 'Creative Commons Zero (volné dílo)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 nebo novější',
- 'config-license-pd' => 'Volné dílo',
- 'config-license-cc-choose' => 'Zvolit vlastní licenci Creative Commons',
- 'config-license-help' => "Mnoho veřejných wiki všechny příspěvky zveřejňuje pod některou [http://freedomdefined.org/Definition/Cs svobodnou licencí].
-To pomáhá vytvořit duch komunitního vlastnictví a povzbuzuje dlouhodobé přispívání.
-To obecně není potřeba u soukromé nebo firemní wiki.
-
-Pokud 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 '''Creative Commons Uveďte autora-Zachovejte licenci'''.
-
-Dříve Wikipedie používala GNU Free Documentation License.
-GFDL je platná licence, ale složité jí porozumět.
-Také 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 [http://www.php.net/manual/en/mail.configuration.php e-mailová nastavení PHP].
-Pokud 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',
- 'config-email-usertalk-help' => 'Umožní uživatelům přijímat notifikace o změnách uživatelských diskusí, pokud si to zapnou v nastavení.',
- 'config-email-watchlist' => 'Umožnit notifikace ke sledovaným stránkám',
- 'config-email-watchlist-help' => 'Umožní uživatelům přijímat notifikace o změnách sledovaných stránek, pokud si to zapnou v nastavení.',
- 'config-email-auth' => 'Zapnout ověřování e-mailů',
- 'config-email-auth-help' => "Pokud je tato volba vybrána, uživatelé musí potvrdit svou e-mailovou adresu pomocí odkazu, který je jim poslán, kdykoli si ji nastaví nebo změní.
-Jen potvrzené e-mailové adresy mohou přijímat e-maily od ostatních uživatelů a e-maily s notifikacemi o změnách.
-Nastavení této volby je '''doporučeno''' pro veřejné wiki kvůli možnosti zneužití e-mailových funkcí.",
- 'config-email-sender' => 'Návratová e-mailová adresa:',
- 'config-email-sender-help' => 'Zadejte e-mailovou adresu, která se má použít jako návratová na odchozích e-mailech.
-Sem budou zasílány nedoručitelné zprávy.
-Mnoho mailových serverů vyžaduje, aby byla přinejmenším část s doménovým jménem platná.',
- 'config-upload-settings' => 'Obrázky a načítání souborů',
- 'config-upload-enable' => 'Povolit načítání souborů',
- 'config-upload-help' => 'Načítání souborů potenciálně vystavuje váš server bezpečnostním rizikům.
-Více informací naleznete v [//www.mediawiki.org/wiki/Manual:Security části o bezpečnosti] v příručce.
-
-Pro umožnění načítání souborů změňte práva na podadresáři <code>images</code> pod kořenovým adresářem MediaWiki, aby do něj mohl webový server zapisovat.
-Poté zapněte tuto volbu.',
- 'config-upload-deleted' => 'Adresář pro smazané soubory:',
- 'config-upload-deleted-help' => 'Zvolte adresář, do kterého se mají archivovat smazané soubory.
-Tento adresář by ideálně neměl být dostupný z webu.',
- 'config-logo' => 'URL loga:',
- 'config-logo-help' => 'Základní vzhled MediaWiki zahrnuje místo pro logo o velikosti 135×160 pixelů nad bočním menu.
-Načtěte obrázek odpovídající velikosti a zadejte sem jeho URL.
-
-Pokud je vaše logo umístěno relativně vůči <code>$wgStylePath</code> nebo <code>$wgScriptPath</code>, můžete zde tyto proměnné použít.
-
-Pokud logo nechcete, ponechte toto pole prázdné.',
- 'config-instantcommons' => 'Zapnout Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] je funkce, která umožňuje wiki používat obrázky, zvuky a další mediální soubory ze serveru [//commons.wikimedia.org/wiki/Hlavn%C3%AD_strana Wikimedia Commons].
-Aby to bylo možné, potřebuje mít MediaWiki přístup k internetu.
-
-Více informací o této funkci, včetně instrukcí, jak ji nastavit pro jiné wiki než Wikimedia Commons, najdete v [//mediawiki.org/wiki/Manual:$wgForeignFileRepos příručce].',
- 'config-cc-error' => 'Volič licence Creative Commons nevrátil žádný výsledek.
-Zadejte název licence ručně.',
- 'config-cc-again' => 'Zvolit znovu…',
- 'config-cc-not-chosen' => 'Zvolte si požadovanou licenci Creative Commons a klikněte na tlačítko.',
- 'config-advanced-settings' => 'Pokročilá konfigurace',
- 'config-cache-options' => 'Nastavení cachování objektů:',
- 'config-cache-help' => 'Cachování objektů se používá pro vylepšení rychlosti MediaWiki tím, že se cachují často používaná data.
-Středním až velkým serverům se jeho zapnutí důrazně doporučuje, i menší servery pocítí jeho výhody.',
- 'config-cache-none' => 'Bez cachování (o žádnou funkcionalitu nepřijdete, na větších wiki však může dojít ke zhoršení rychlosti)',
- 'config-cache-accel' => 'Cachování PHP objektů (APC, XCache nebo WinCache)',
- 'config-cache-memcached' => 'Použít Memcached (vyžaduje další nastavení a konfiguraci)',
- 'config-memcached-servers' => 'Servery Memcached:',
- 'config-memcached-help' => 'Seznam IP adres, které se mají používat pro Memcached.
-Uveďte jednu na řádek spolu s portem. Například:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Jako typ cache jste zvolili Memcached, ale neuvedli jste žádné servery.',
- 'config-memcache-badip' => 'Zadali jste neplatnou IP adresu pro Memcached: $1.',
- 'config-memcache-noport' => 'Nezadali jste port serveru Memcached: $1.
-Pokud port neznáte, implicitní je 11211.',
- 'config-memcache-badport' => 'Čísla portů pro Memcached by měla být mezi $1 a $2.',
- 'config-extensions' => 'Rozšíření',
- 'config-extensions-help' => 'Výše uvedená rozšíření byla nalezena ve vašem adresáři <code>./extensions</code>.
-
-Mohou vyžadovat dodatečnou konfiguraci, ale teď je můžete povolit.',
- 'config-install-alreadydone' => "'''Upozornění:''' Vypadá to, že jste MediaWiki již nainstalovali a teď se o to pokoušíte znovu.
-Pokračujte na další stránku.",
- 'config-install-begin' => 'Stisknutím „{{int:config-continue}}“ spustíte instalaci MediaWiki.
-Pokud ještě chcete udělat nějaké změny, stiskněte „{{int:config-back}}“.',
- 'config-install-step-done' => 'hotovo',
- 'config-install-step-failed' => 'selhaly',
- 'config-install-extensions' => 'Vkládají se rozšíření',
- 'config-install-database' => 'Připravuje se databáze',
- 'config-install-schema' => 'Vytváří se schéma',
- 'config-install-pg-schema-not-exist' => 'Schéma PostgreSQL neexistuje.',
- 'config-install-pg-schema-failed' => 'Založení tabulek se nezdařilo.
-Ujistěte se, že uživatel „$1“ může zapisovat do schématu „$2“.',
- 'config-install-pg-commit' => 'Potvrzují se změny',
- 'config-install-pg-plpgsql' => 'Kontroluje se jazyk PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Musíte do databáze $1 nainstalovat jazyk PL/pgSQL',
- 'config-pg-no-create-privs' => 'Účet zadaný pro instalaci nemá oprávnění k založení uživatelského účtu.',
- 'config-pg-not-in-role' => 'Účet zadaný pro webového uživatele již existuje
-Účet zadaný pro instalaci není superuživatelský a není členem role webového uživatele, takže nemůže zakládat objekty vlastněné webovým uživatelem.
-
-MediaWiki v současné době vyžaduje, aby byl vlastníkem tabulek webový uživatel. Uveďte jiný název účtu webového uživatele nebo klikněte na „zpět“ a zadejte instalačního uživatele s odpovídajícími oprávněními.',
- 'config-install-user' => 'Vytváří se databázový uživatel',
- 'config-install-user-alreadyexists' => 'Uživatel „$1“ už existuje',
- 'config-install-user-create-failed' => 'Vytváření uživatele „$1“ selhalo: $2',
- 'config-install-user-grant-failed' => 'Uživateli „$1“ se nepodařilo přidělit oprávnění: $2',
- 'config-install-user-missing' => 'Zadaný uživatel „$1“ neexistuje.',
- 'config-install-user-missing-create' => 'Zadaný uživatel „$1“ neexistuje.
-Pokud ho chcete založit, zaškrtněte možnost „založit účet“ níže.',
- 'config-install-tables' => 'Vytvářejí se tabulky',
- 'config-install-tables-exist' => "'''Upozornění''': Vypadá to, že tabulky MediaWiki již existují.
-Přeskakuje se jejich zakládání.",
- 'config-install-tables-failed' => "'''Chyba''': Vytvoření tabulek selhalo s následující chybou: $1",
- 'config-install-interwiki' => 'Tabulka interwiki se plní implicitními položkami',
- 'config-install-interwiki-list' => 'Nelze přečíst soubor <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Upozornění''': Vypadá to, že tabulka interwiki již obsahuje nějaké záznamy.
-Přeskakuje se implicitní seznam.",
- 'config-install-stats' => 'Inicializují se statistiky',
- 'config-install-keys' => 'Vytvářejí se tajné klíče',
- 'config-insecure-keys' => "'''Upozornění:''' {{PLURAL:$2|Tajný klíč|Tajné klíče}} ($1) vytvořené v průběhu instalace {{PLURAL:$2|není|nejsou}} zcela {{PLURAL:$2|bezpečný|bezpečné}}. Zvažte {{PLURAL:$2|jeho|jejich}} ruční změnu.",
- 'config-install-sysop' => 'Zakládá se uživatelský účet správce',
- 'config-install-subscribe-fail' => 'Nelze se přihlásit k odběru mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'Není nainstalován cURL a není dostupné allow_url_fopen.',
- 'config-install-mainpage' => 'Vytváří se počáteční obsah hlavní strany',
- 'config-install-extension-tables' => 'Vytvářejí se tabulky pro zapnutá rozšíření',
- 'config-install-mainpage-failed' => 'Nepodařilo se vložit hlavní stranu: $1',
- 'config-install-done' => "'''Gratulujeme!'''
-Úspěšně jste nainstalovali MediaWiki.
-
-Instalátor vytvořil soubor <code>LocalSettings.php</code>.
-Ten obsahuje veškerou vaši konfiguraci.
-
-Budete si ho muset stáhnout a uložit do základního adresáře vaší instalace wiki (do stejného adresáře jako soubor index.php). Stažení souboru se mělo spustit automaticky.
-
-Pokud se vám stažení nenabídlo nebo jste ho zrušili, můžete ho spustit znovu kliknutím na následující odkaz:
-
-$3
-
-'''Poznámka''': Pokud to neuděláte hned, tento vygenerovaný konfigurační soubor nebude později dostupný, pokud instalaci opustíte, aniž byste si ho stáhli.
-
-Až to dokončíte, můžete '''[$2 vstoupit do své wiki]'''.",
- 'config-download-localsettings' => 'Stáhnout <code>LocalSettings.php</code>',
- 'config-help' => 'nápověda',
- 'config-nofile' => 'Soubor „$1“ nelze nalézt. Byl smazán?',
- 'config-extension-link' => 'Věděli jste, že vaše wiki podporuje [//www.mediawiki.org/wiki/Manual:Extensions rozšíření]?
-
-Můžete procházet [//www.mediawiki.org/wiki/Category:Extensions_by_category rozšíření po kategoriích] nebo si prohlédnout [//www.mediawiki.org/wiki/Extension_Matrix Matici rozšíření] obsahující úplný seznam.',
- 'mainpagetext' => "'''MediaWiki byla úspěšně nainstalována.'''",
- 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents Uživatelská příručka] vám napoví, jak MediaWiki používat.
-
-== Začínáme ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Nastavení konfigurace]
-* [//www.mediawiki.org/wiki/Manual:FAQ Často kladené otázky o MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-mailová konference oznámení MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Překlad MediaWiki do vašeho jazyka]',
-);
-
-/** Kashubian (kaszëbsczi)
- */
-$messages['csb'] = array(
- 'mainpagetext' => "'''MediaWiki òsta zainstalowónô.'''",
-);
-
-/** Church Slavic (словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ)
- * @author ОйЛ
- */
-$messages['cu'] = array(
- 'config-page-language' => 'ѩꙁꙑкъ',
- 'config-page-name' => 'имѧ',
- 'config-help' => 'помощь',
-);
-
-/** Chuvash (Чӑвашла)
- */
-$messages['cv'] = array(
- 'mainpagetext' => "'''«MediaWiki» вики-движока лартасси ăнăçлă вĕçленчĕ.'''",
- 'mainpagedocfooter' => 'Ку википе ĕçлеме пулăшакан информацине [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 усăç руководствинче] тупма пултаратăр.
-
-== Пулăшма пултарĕç ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Ĕнерлевсен списокĕ];
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki тăрăх час-часах ыйтакан ыйтусемпе хуравсем];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki çĕнĕ верси тухнине пĕлтерекен рассылка].', # Fuzzy
-);
-
-/** Welsh (Cymraeg)
- * @author Lloffiwr
- * @author Xxglennxx
- */
-$messages['cy'] = array(
- 'mainpagetext' => "'''Wedi llwyddo gosod meddalwedd MediaWiki yma'''",
- 'mainpagedocfooter' => "Ceir cymorth (yn Saesneg) ar ddefnyddio meddalwedd wici yn y [//meta.wikimedia.org/wiki/Help:Contents Canllaw Defnyddwyr] ar wefan Wikimedia.
-
-==Cychwyn arni==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Rhestr osodiadau wrth gyflunio]
-* [//www.mediawiki.org/wiki/Manual:FAQ Cwestiynau poblogaidd ar MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rhestr postio datganiadau MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Cyfieithu MediaWici i'ch iaith chi]",
-);
-
-/** Danish (dansk)
- * @author Peter Alberti
- */
-$messages['da'] = array(
- 'mainpagetext' => "'''MediaWiki er nu installeret.'''",
- 'mainpagedocfooter' => 'Se [//meta.wikimedia.org/wiki/Help:Contents brugervejledningen] for oplysninger om brugen af wikiprogrammellet.
-
-== At komme i gang ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listen over opsætningsmuligheder]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki ofte stillede spørgsmål]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Postliste angående udgivelser af MediaWiki]', # Fuzzy
-);
-
-/** German (Deutsch)
- * @author Geitost
- * @author Kghbln
- * @author LWChris
- * @author Metalhead64
- * @author Purodha
- * @author Rillke
- * @author The Evil IP address
- * @author Umherirrender
- * @author Wikinaut
- * @author 아라
- */
-$messages['de'] = array(
- 'config-desc' => 'Das MediaWiki-Installationsprogramm',
- 'config-title' => 'Installation von MediaWiki $1',
- 'config-information' => 'Informationen',
- 'config-localsettings-upgrade' => 'Eine Datei <code>LocalSettings.php</code> wurde gefunden.
-Um die vorhandene Installation aktualisieren zu können, muss der Wert des Parameters <code>$wgUpgradeKey</code> im folgenden Eingabefeld angegeben werden.
-Der Parameterwert befindet sich in der Datei <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Eine Datei <code><code>LocalSettings.php</code></code> wurde gefunden.
-Um die vorhandene Installation zu aktualisieren, muss die Datei <code>update.php</code> ausgeführt werden.',
- 'config-localsettings-key' => 'Aktualisierungsschlüssel:',
- 'config-localsettings-badkey' => 'Der angegebene Aktualisierungsschlüssel ist falsch.',
- 'config-upgrade-key-missing' => 'Eine MediaWiki-Installation wurde gefunden.
-Um die vorhandene Installation aktualisieren zu können, muss die unten angegebene Codezeile in die Datei <code>LocalSettings.php</code> an deren Ende eingefügt werden:
-
-$1',
- 'config-localsettings-incomplete' => 'Die vorhandene Datei <code>LocalSettings.php</code> scheint unvollständig zu sein.
-Die Variable <code>$1</code> wurde nicht definiert.
-Die Datei <code>LocalSettings.php</code> muss entsprechend geändert werden, so dass sie definiert ist. Klicke danach auf „{{int:Config-continue}}“.',
- 'config-localsettings-connection-error' => 'Beim Verbindungsversuch zur Datenbank ist, unter Verwendung der in den Dateien <code>LocalSettings.php</code> oder <code>AdminSettings.php</code> hinterlegten Einstellungen, ein Fehler aufgetreten. Diese Einstellungen müssen korrigiert werden. Danach kann ein erneuter Versuch unternommen werden.
-
-$1',
- 'config-session-error' => 'Fehler beim Starten der Sitzung: $1',
- 'config-session-expired' => 'Die Sitzungsdaten scheinen abgelaufen zu sein.
-Sitzungen sind für einen Zeitraum von $1 konfiguriert.
-Dieser kann durch Anhebung des Parameters <code>session.gc_maxlifetime</code> in der Datei <code>php.ini</code> erhöht werden.
-Den Installationsvorgang erneut starten.',
- 'config-no-session' => 'Die Sitzungsdaten sind verloren gegangen!
-Die Datei <code>php.ini</code> muss geprüft und es muss dabei sichergestellt werden, dass der Parameter <code>session.save_path</code> auf das richtige Verzeichnis verweist.',
- 'config-your-language' => 'Sprache während des Installierens:',
- 'config-your-language-help' => 'Bitte die Sprache auswählen, die während des Installationsvorgangs verwendet werden soll.',
- 'config-wiki-language' => 'Sprache des Wikis:',
- 'config-wiki-language-help' => 'Bitte die Sprache auswählen, die überwiegend beim Erstellen der Inhalte verwendet werden soll.',
- 'config-back' => '← Zurück',
- 'config-continue' => 'Weiter →',
- 'config-page-language' => 'Sprache',
- 'config-page-welcome' => 'Willkommen bei MediaWiki!',
- 'config-page-dbconnect' => 'Mit der Datenbank verbinden',
- 'config-page-upgrade' => 'Eine vorhandene Installation aktualisieren',
- 'config-page-dbsettings' => 'Einstellungen zur Datenbank',
- 'config-page-name' => 'Name',
- 'config-page-options' => 'Optionen',
- 'config-page-install' => 'Installieren',
- 'config-page-complete' => 'Fertig!',
- 'config-page-restart' => 'Installationsvorgang erneut starten',
- 'config-page-readme' => 'Lies mich',
- 'config-page-releasenotes' => 'Versionsinfos (en)',
- 'config-page-copying' => 'Kopie der Lizenz',
- 'config-page-upgradedoc' => 'Aktualisiere',
- 'config-page-existingwiki' => 'Vorhandenes Wiki',
- 'config-help-restart' => 'Sollen alle bereits eingegebenen Daten gelöscht und der Installationsvorgang erneut gestartet werden?',
- 'config-restart' => 'Ja, erneut starten',
- 'config-welcome' => '=== Prüfung der Installationsumgebung ===
-Die Basisprüfungen werden jetzt durchgeführt, um festzustellen, ob die Installationsumgebung für die Installation von MediaWiki geeignet ist.
-Nimm diese Information auf, falls du Unterstützung bei der Vervollständigung der Installation benötigst.',
- 'config-copyright' => "=== Lizenz und Nutzungsbedingungen ===
-
-$1
-
-Dieses Programm ist freie Software, d. h. es kann, gemäß den Bedingungen der von der Free Software Foundation veröffentlichten ''GNU General Public License'', weiterverteilt und/oder modifiziert werden. Dabei kann die Version 2, oder nach eigenem Ermessen, jede neuere Version der Lizenz verwendet werden.
-
-Dieses Programm wird in der Hoffnung verteilt, dass es nützlich sein wird, allerdings '''ohne jegliche Garantie''' und sogar ohne die implizierte Garantie einer '''Marktgängigkeit''' oder '''Eignung für einen bestimmten Zweck'''. Hierzu sind weitere Hinweise in der ''GNU General Public License'' enthalten.
-
-Eine <doclink href=Copying>Kopie der GNU General Public License</doclink> sollte zusammen mit diesem Programm verteilt worden sein. Sofern dies nicht der Fall war, kann eine Kopie bei der Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftlich angefordert oder auf deren Website [http://www.gnu.org/copyleft/gpl.html online gelesen] werden.",
- 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/de Website von MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents/de Benutzeranleitung]
-* [//www.mediawiki.org/wiki/Manual:Contents/de Administratorenanleitung]
-* [//www.mediawiki.org/wiki/Manual:FAQ/de Häufig gestellte Fragen]
-----
-* <doclink href=Readme>Lies mich</doclink>
-* <doclink href=ReleaseNotes>Versionsinformationen</doclink>
-* <doclink href=Copying>Lizenzbestimmungen</doclink>
-* <doclink href=UpgradeDoc>Aktualisierung</doclink>',
- 'config-env-good' => 'Die Installationsumgebung wurde geprüft.
-MediaWiki kann installiert werden.',
- 'config-env-bad' => 'Die Installationsumgebung wurde geprüft.
-MediaWiki kann nicht installiert werden.',
- 'config-env-php' => 'PHP $1 ist installiert.',
- 'config-env-php-toolow' => 'PHP $1 ist installiert.
-Allerdings benötigt MediaWiki PHP $2 oder höher.',
- 'config-unicode-using-utf8' => 'Zur Unicode-Normalisierung wird Brion Vibbers <code>utf8_normalize.so</code> eingesetzt.',
- 'config-unicode-using-intl' => 'Zur Unicode-Normalisierung wird die [http://pecl.php.net/intl PECL-Erweiterung intl] eingesetzt.',
- 'config-unicode-pure-php-warning' => "'''Warnung:''' Die [http://pecl.php.net/intl PECL-Erweiterung intl] ist für die Unicode-Normalisierung nicht verfügbar, so dass stattdessen die langsame pure-PHP-Implementierung genutzt wird.
-Sofern eine Website mit großer Benutzeranzahl betrieben wird, sollten weitere Informationen auf der Webseite [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-Normalisierung (en)] gelesen werden.",
- 'config-unicode-update-warning' => "'''Warnung:''' Die installierte Version des Unicode-Normalisierungswrappers nutzt einer ältere Version der Bibliothek des [http://site.icu-project.org/ ICU-Projekts].
-Diese sollte [//www.mediawiki.org/wiki/Unicode_normalization_considerations aktualisiert] werden, sofern auf die Verwendung von Unicode Wert gelegt wird.",
- 'config-no-db' => 'Es konnte kein adäquater Datenbanktreiber gefunden werden. Es muss daher ein Datenbanktreiber für PHP installiert werden.
-Die folgenden Datenbanksysteme werden unterstützt: $1
-
-Sofern ein gemeinschaftlich genutzter Server für das Hosting verwendet wird, muss der Hoster gefragt werden einen adäquaten Datenbanktreiber zu installieren.
-Sofern PHP selbst kompiliert wurde, muss es mit es neu konfiguriert werden, wobei der Datenbankclient zu aktivierten ist. Hierzu kann beispielsweise <code>./configure --with-mysql</code> ausgeführt werden.
-Sofern PHP über die Paketverwaltung einer Debian- oder Ubuntu-Installation installiert wurde, muss das „php5-mysql“-Paket nachinstalliert werden.',
- 'config-outdated-sqlite' => "'''Warnung:''' SQLite $1 ist installiert. Allerdings benötigt MediaWiki SQLite $2 oder höher. SQLite wird daher nicht verfügbar sein.",
- 'config-no-fts3' => "'''Warnung:''' SQLite wurde ohne das [//sqlite.org/fts3.html FTS3-Modul] kompiliert, so dass keine Suchfunktionen für dieses Datenbanksystem zur Verfügung stehen werden.",
- 'config-register-globals' => "'''Warnung: Der Parameter <code>[http://php.net/register_globals register_globals]</code> von PHP ist aktiviert.'''
-'''Sie sollte deaktiviert werden, sofern dies möglich ist.'''
-Die MediaWiki-Installation wird zwar laufen, wobei aber der Server für potentielle Sicherheitsprobleme anfällig ist.",
- 'config-magic-quotes-runtime' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/function.set-magic-quotes-runtime.php set_magic_quotes_runtime]</code> von PHP ist aktiviert!'''
-Diese Einstellung führt zu unvorhersehbaren Problemen bei der Dateneingabe.
-MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
- 'config-magic-quotes-sybase' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/sybase.configuration.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> von PHP ist aktiviert!'''
-Diese Einstellung führt zu unvorhersehbaren Problemen bei der Dateneingabe.
-MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
- 'config-mbstring' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> von PHP ist aktiviert!'''
-Diese Einstellung verursacht Fehler und führt zu unvorhersehbaren Problemen bei der Dateneingabe.
-MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
- 'config-ze1' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/ini.core.php zend.ze1_compatibility_mode]</code> von PHP ist aktiviert!'''
-Diese Einstellung führt zu großen Fehlern bei MediaWiki.
-MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
- 'config-safe-mode' => "'''Warnung:''' Der Funktion <code>[http://www.php.net/features.safe-mode Safe Mode]</code> von PHP ist aktiviert.
-Dies kann zu Problemen führen, insbesondere wenn das Hochladen von Dateien möglich sein, bzw. der Auszeichner <code>math</code> genutzt werden soll.",
- 'config-xml-bad' => 'Das XML-Modul von PHP fehlt.
-MediaWiki benötigt Funktionen, die dieses Modul bereitstellt und wird in der bestehenden Konfiguration nicht funktionieren.
-Sofern Mandriva genutzt wird, muss noch das „php-xml“-Paket installiert werden.',
- 'config-pcre' => 'Das PHP-Modul für die PCRE-Unterstützung wurde nicht gefunden.
-MediaWiki benötigt allerdings perl-kompatible reguläre Ausdrücke, um lauffähig zu sein.',
- 'config-pcre-no-utf8' => "'''Fataler Fehler:''' Das PHP-Modul PCRE scheint ohne PCRE_UTF8-Unterstützung kompiliert worden zu sein.
-MediaWiki 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.
-Dieser Wert ist wahrscheinlich zu niedrig.
-Der Installationsvorgang könnte daher scheitern!",
- 'config-ctype' => "'''Fataler Fehler:''' PHP muss mit Unterstützung für das [http://www.php.net/manual/de/ctype.installation.php Modul ctype] kompiliert werden.",
- 'config-json' => "'''Fatal:''' PHP wurde ohne JSON-Support kompiliert.
-Du musst entweder die PHP-JSON- oder die [http://pecl.php.net/package/jsonc PECL-jsonc]-Erweiterung installieren, bevor du MediaWiki installierst.
-* Die PHP-Erweiterung ist in Red Hat Enterprise Linux (CentOS) 5 und 6 enthalten, muss jedoch in <code>/etc/php.ini</code> oder <code>/etc/php.d/json.ini</code> aktiviert werden.
-* Einige Linux-Distributionen, die nach Mai 2013 veröffentlicht wurden, lassen die PHP-Erweiterung weg, stattdessen wird die PECL-Erweiterung als <code>php5-json</code> oder <code>php-pecl-jsonc</code> mitgeliefert.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] ist installiert',
- 'config-apc' => '[http://www.php.net/apc APC] ist installiert',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] ist installiert',
- 'config-no-cache' => "'''Warnung:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] wurden nicht gefunden.
-Das Objektcaching kann daher nicht aktiviert werden.",
- 'config-mod-security' => "'''Warnung:''' Auf dem Webserver wurde [http://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.
-Für weitere Informationen empfehlen wir die [http://modsecurity.org/documentation/ Dokumentation zu ModSecurity] oder den Kontakt zum Hoster, sofern Fehler auftreten.",
- 'config-diff3-bad' => 'GNU diff3 wurde nicht gefunden.',
- 'config-git' => 'Die Git-Versionsverwaltungssoftware wurde gefunden: <code>$1</code>.',
- 'config-git-bad' => 'Die Git-Versionsverwaltungssoftware wurde nicht gefunden.',
- 'config-imagemagick' => 'ImageMagick wurde gefunden: <code>$1</code>.
-Miniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dateien aktiviert wurde.',
- 'config-gd' => 'Die im System integrierte GD-Grafikbibliothek wurde gefunden.
-Miniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dateien aktiviert wurde.',
- 'config-no-scaling' => 'Weder die GD-Grafikbibliothek noch ImageMagick wurden gefunden.
-Miniaturansichten von Bildern sind daher nicht möglich.',
- 'config-no-uri' => "'''Fehler:''' Die aktuelle URL konnte nicht ermittelt werden.
-Der Installationsvorgang wurde daher abgebrochen.",
- 'config-no-cli-uri' => "'''Warnung''': Es wurde kein Pfad zum Skipt (--scriptpath) angegeben. Daher wird der Standardpfad genutzt: <code>$1</code>.",
- 'config-using-server' => 'Der Servername „<nowiki>$1</nowiki>“ wird verwendet.',
- 'config-using-uri' => 'Die Server-URL „<nowiki>$1$2</nowiki>“ wird verwendet.',
- 'config-uploads-not-safe' => "'''Warnung:''' Das Standardverzeichnis für hochgeladene Dateien <code>$1</code> ist für die willkürliche Ausführung von Skripten anfällig.
-Obwohl MediaWiki die hochgeladenen Dateien auf Sicherheitsrisiken überprüft, wird dennoch dringend empfohlen diese [//www.mediawiki.org/wiki/Manual:Security#Upload_security Sicherheitslücke] zu schließen, bevor das Hochladen von Dateien aktiviert wird.",
- 'config-no-cli-uploads-check' => "'''Warnung''': Das Standardverzeichnis für hochgeladene Dateien (<code>$1</code>) wird, während der Installation über die Kommandozeile, nicht auf Sicherheitsanfälligkeiten hinsichtlich willkürlicher Skriptausführungen geprüft.",
- 'config-brokenlibxml' => 'Das System nutzt eine Kombination aus PHP- und libxml2-Versionen, die fehleranfällig ist und versteckte Datenfehler bei MediaWiki und anderen Webanwendungen verursachen kann.
-PHP muss auf Version 5.2.9 oder später sowie libxml2 auf die Version 2.7.3 oder später aktualisiert werden, um das Problem zu lösen. Installationsabbruch ([//bugs.php.net/bug.php?id=45996 siehe hierzu die Fehlermeldung bei PHP]).',
- 'config-using531' => 'MediaWiki kann nicht zusammen mit PHP $1 verwendet werden. Grund hierfür ist ein Fehler im Zusammenhang mit den Verweisparametern zu <code>__call()</code>.
-PHP muss auf Version 5.3.2 oder höher oder 5.3.0 oder niedriger aktualisiert werden, um das Problem zu beheben.
-Die Installation wurde abgebrochen.',
- 'config-suhosin-max-value-length' => 'Suhosin ist installiert und beschränkt die Länge des GET-Parameters auf $1 Bytes.
-Der ResouceLoader von MediaWiki wird zwar unter diesen Bedingungen funktionieren, allerdings nur mit verminderter Leistungsfähigkeit.
-Sofern möglich sollte der Parameter <code>suhosin.get.max_value_length</code> in der Datei <code>php.ini</code> auf 1024 oder höher festgelegt werden.
-Gleichzeitig muss der Parameter <code>$wgResourceLoaderMaxQueryLength</code> in der Datei <code>LocalSettings.php</code> auf den selben Wert eingestellt werden.',
- 'config-db-type' => 'Datenbanksystem:',
- 'config-db-host' => 'Datenbankserver:',
- 'config-db-host-help' => 'Sofern sich die Datenbank auf einem anderen Server befindet, ist hier der Servername oder die entsprechende IP-Adresse anzugeben.
-
-Sofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster den zutreffenden Servernamen in seiner Dokumentation angegeben haben.
-
-Sofern auf einem Windows-Server installiert und MySQL genutzt wird, funktioniert der Servername „localhost“ voraussichtlich nicht. Wenn nicht, sollte „127.0.0.1“ oder die lokale IP-Adresse angegeben werden.
-
-Sofern PostgresQL genutzt wird, muss dieses Feld leer gelassen werden, um über ein Unix-Socket zu verbinden.',
- 'config-db-host-oracle' => 'Datenbank-TNS:',
- 'config-db-host-oracle-help' => 'Einen gültigen [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „Local Connect“-Namen] angeben. Die „tnsnames.ora“-Datei muss von dieser Installation erkannt werden können.<br />Sofern die Client-Bibliotheken für Version 10g oder neuer verwendet werden, kann auch [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „Easy Connect“] zur Namensgebung genutzt werden.',
- 'config-db-wiki-settings' => 'Bitte Daten zur eindeutigen Identifikation dieses Wikis angeben',
- 'config-db-name' => 'Datenbankname:',
- 'config-db-name-help' => 'Bitte einen Namen angeben, mit dem das Wiki identifiziert werden kann.
-Dabei sollten keine Leerzeichen verwendet werden.
-
-Sofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster den Datenbanknamen angegeben oder aber die Erstellung einer Datenbank über ein entsprechendes Interface gestattet haben.',
- 'config-db-name-oracle' => 'Datenbankschema:',
- 'config-db-account-oracle-warn' => 'Es gibt drei von MediaWiki unterstützte Möglichkeiten Oracle als Datenbank einzurichten:
-
-Sofern das Datenbankbenutzerkonto während des Installationsvorgangs erstellt werden soll, muss ein Datenbankbenutzerkonto mit der SYSDBA-Berechtigung zusammen mit den entsprechenden Anmeldeinformationen angegeben werden, mit dem dann über das Web auf die Datenbank zugegriffen werden kann. Alternativ kann man auch lediglich ein einzelnes manuell angelegtes Datenbankbenutzerkonto angeben, mit dem über das Web auf die Datenbank zugegriffen werden kann, sofern dieses über die Berechtigung zur Erstellung von Datenbankschemen verfügt. Zudem ist es möglich zwei Datenbankbenutzerkonten anzugeben von denen eines die Berechtigung zur Erstellung von Datenbankschemen hat und das andere, um mit ihm über das Web auf die Datenbank zuzugreifen.
-
-Ein Skript zum Anlegen eines Datenbankbenutzerkontos mit den notwendigen Berechtigungen findet man unter dem Pfad „…/maintenance/oracle/“ dieser MediaWiki-Installation. Es ist dabei zu bedenken, dass die Verwendung eines Datenbankbenutzerkontos mit beschränkten Berechtigungen die Nutzung der Wartungsfunktionen für das Standarddatenbankbenutzerkonto deaktiviert.',
- 'config-db-install-account' => 'Benutzerkonto für die Installation',
- 'config-db-username' => 'Name des Datenbankbenutzers:',
- 'config-db-password' => 'Passwort des Datenbankbenutzers:',
- 'config-db-password-empty' => 'Bitte ein Passwort für den neuen Datenbankbenutzer angeben: $1
-Obzwar es möglich ist Datenbankbenutzer ohne Passwort anzulegen, so ist dies aber nicht sicher.',
- 'config-db-install-username' => 'Den Benutzernamen angeben, der für die Verbindung mit der Datenbank während des Installationsvorgangs genutzt werden soll. Es handelt sich dabei nicht um den Benutzernamen für das MediaWiki-Konto, sondern um den Benutzernamen der vorgesehenen Datenbank.',
- 'config-db-install-password' => 'Das Passwort angeben, das für die Verbindung mit der Datenbank während des Installationsvorgangs genutzt werden soll. Es handelt sich dabei nicht um das Passwort für das MediaWiki-Konto, sondern um das Passwort der vorgesehenen Datenbank.',
- 'config-db-install-help' => 'Benutzername und Passwort, die während des Installationsvorgangs, für die Verbindung mit der Datenbank, genutzt werden sollen, sind nun anzugeben.',
- 'config-db-account-lock' => 'Derselbe Benutzername und das Passwort müssen während des Normalbetriebs des Wikis verwendet werden.',
- 'config-db-wiki-account' => 'Benutzerkonto für den normalen Betrieb',
- 'config-db-wiki-help' => 'Bitte Benutzernamen und Passwort angeben, die der Webserver während des Normalbetriebes dazu verwenden soll, eine Verbindung zum Datenbankserver herzustellen.
-Sofern ein entsprechendes Benutzerkonto nicht vorhanden ist und das Benutzerkonto für den Installationsvorgang über ausreichende Berechtigungen verfügt, wird dieses Benutzerkonto automatisch mit den Mindestberechtigungen zum Normalbetrieb des Wikis angelegt.',
- 'config-db-prefix' => 'Datenbanktabellenpräfix:',
- 'config-db-prefix-help' => 'Sofern eine Datenbank für mehrere Wikiinstallationen oder eine Wikiinstallation und eine andere Programminstallation genutzt werden soll, muss ein Datenbanktabellenpräfix angegeben werden, um Datenbankprobleme zu vermeiden.
-Es können keine Leerzeichen verwendet werden.
-
-Gewöhnlich bleibt dieses Datenfeld leer.',
- 'config-db-charset' => 'Datenbankzeichensatz',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binär',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 abwärtskompatibles UTF-8',
- 'config-charset-help' => "'''Warnung:''' Sofern '''abwärtskompatibles UTF-8''' bei MySQL 4.1+ verwendet und anschließend die Datenbank mit <code>mysqldump</code> gesichert wird, könnten alle nicht mit ASCII-codierten Zeichen beschädigt werden, was zu irreversiblen Schäden der Datensicherung führt!
-
-Im '''binären Modus''' speichert MediaWiki UTF-8 Texte in der Datenbank in binär kodierte Datenfelder.
-Dies ist effizienter als der UTF-8-Modus von MySQL und ermöglicht so die Verwendung jeglicher Unicode-Zeichen.
-Im '''UTF-8-Modus''' wird MySQL den Zeichensatz der Daten erkennen und sie richtig anzeigen und konvertieren.
-Es können allerdings keine Zeichen außerhalb des [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
- 'config-mysql-old' => 'MySQL $1 oder höher wird benötigt. MySQL $2 ist momentan vorhanden.',
- 'config-db-port' => 'Datenbankport:',
- 'config-db-schema' => 'Datenschema für MediaWiki',
- 'config-db-schema-help' => 'Dieses Datenschema ist in der Regel allgemein verwendbar.
-Nur Änderungen daran vornehmen, sofern es gute Gründe dafür gibt.',
- 'config-pg-test-error' => "Es kann keine Verbindung zur Datenbank '''$1''' hergestellt werden: $2",
- 'config-sqlite-dir' => 'SQLite-Datenverzeichnis:',
- 'config-sqlite-dir-help' => "SQLite speichert alle Daten in einer einzigen Datei.
-
-Das für sie vorgesehene Verzeichnis muss während des Installationsvorgangs beschreibbar sein.
-
-Es sollte '''nicht''' über das Web zugänglich sein, was der Grund ist, warum die Datei nicht dort abgelegt wird, wo sich die PHP-Dateien befinden.
-
-Das Installationsprogramm wird mit der Datei zusammen eine zusätzliche <code>.htaccess</code>-Datei erstellen. Sofern dies scheitert, können Dritte auf die Datendatei zugreifen.
-Dies umfasst die Nutzerdaten (E-Mail-Adressen, Passwörter, etc.) wie auch gelöschte Seitenversionen und andere vertrauliche Daten, die im Wiki gespeichert sind.
-
-Es ist daher zu erwägen, die Datendatei an gänzlich anderer Stelle abzulegen, beispielsweise im Verzeichnis <code>./var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Standardtabellenraum:',
- 'config-oracle-temp-ts' => 'Temporärer Tabellenraum:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki unterstützt die folgenden Datenbanksysteme:
-
-$1
-
-Sofern nicht das Datenbanksystem angezeigt wird, das verwendet werden soll, gibt es oben einen Link zur Anleitung mit Informationen, wie dieses aktiviert werden kann.',
- 'config-support-mysql' => '* $1 ist das von MediaWiki primär unterstützte Datenbanksystem ([http://www.php.net/manual/en/mysql.installation.php Anleitung zur Kompilierung von PHP mit MySQL-Unterstützung (en)])',
- 'config-support-postgres' => '* $1 ist ein beliebtes Open-Source-Datenbanksystem und eine Alternative zu MySQL ([http://www.php.net/manual/de/pgsql.installation.php Anleitung zur Kompilierung von PHP mit PostgreSQL-Unterstützung]). Es gibt allerdings einige kleinere Implementierungsfehler, so dass von der Nutzung in einer Produktivumgebung abgeraten wird.',
- 'config-support-sqlite' => '* $1 ist ein verschlanktes Datenbanksystem, das auch gut unterstützt wird ([http://www.php.net/manual/de/pdo.installation.php Anleitung zur Kompilierung von PHP mit SQLite-Unterstützung], verwendet PHP Data Objects (PDO))',
- 'config-support-oracle' => '* $1 ist eine kommerzielle Unternehmensdatenbank ([http://www.php.net/manual/en/oci8.installation.php Anleitung zur Kompilierung von PHP mit OCI8-Unterstützung (en)])',
- 'config-header-mysql' => 'MySQL-Einstellungen',
- 'config-header-postgres' => 'PostgreSQL-Einstellungen',
- 'config-header-sqlite' => 'SQLite-Einstellungen',
- 'config-header-oracle' => 'Oracle-Einstellungen',
- 'config-invalid-db-type' => 'Unzulässiges Datenbanksystem',
- 'config-missing-db-name' => 'Bei „Datenbankname“ muss ein Wert angegeben werden.',
- 'config-missing-db-host' => 'Bei „Datenbankhost“ muss ein Wert angegeben werden.',
- 'config-missing-db-server-oracle' => 'Für das „Datenbank-TNS“ muss ein Wert eingegeben werden',
- 'config-invalid-db-server-oracle' => 'Ungültiges Datenbank-TNS „$1“.
-Entweder „TNS Name“ oder eine „Easy Connect“-Zeichenfolge verwenden ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle-Benennungsmethoden])',
- 'config-invalid-db-name' => 'Ungültiger Datenbankname „$1“.
-Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9), Unter- (_) sowie Bindestriche (-) verwendet werden.',
- 'config-invalid-db-prefix' => 'Ungültiger Datenbanktabellenpräfix „$1“.
-Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9), Unter- (_) sowie Bindestriche (-) verwendet werden.',
- 'config-connection-error' => '$1.
-
-Bitte unten angegebenen Servernamen, Benutzernamen sowie das Passwort überprüfen und es danach erneut versuchen.',
- 'config-invalid-schema' => 'Ungültiges Datenschema für MediaWiki „$1“.
-Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9) und Unterstriche (_) verwendet werden.',
- 'config-db-sys-create-oracle' => 'Das Installationsprogramm unterstützt nur die Verwendung eines Datenbankbenutzerkontos mit SYSDBA-Berechtigung zum Anlegen eines neuen Datenbankbenutzerkontos.',
- 'config-db-sys-user-exists-oracle' => 'Das Datenbankbenutzerkonto „$1“ ist bereits vorhanden. Ein Datenbankbenutzerkontos mit SYSDBA-Berechtigung kann nur zum Anlegen eines neuen Datenbankbenutzerkontos genutzt werden.',
- 'config-postgres-old' => 'PostgreSQL $1 oder höher wird benötigt. PostgreSQL $2 ist momentan vorhanden.',
- 'config-sqlite-name-help' => 'Bitten einen Namen angeben, mit dem das Wiki identifiziert werden kann.
-Dabei bitte keine Leerzeichen oder Bindestriche verwenden.
-Dieser Name wird für die SQLite-Datendateinamen genutzt.',
- 'config-sqlite-parent-unwritable-group' => 'Das Datenverzeichnis <code><nowiki>$1</nowiki></code> kann nicht erzeugt werden, da das übergeordnete Verzeichnis <code><nowiki>$2</nowiki></code> nicht für den Webserver beschreibbar ist.
-
-Das Installationsprogramm konnte den Benutzer bestimmen, mit dem Webserver ausgeführt wird.
-Schreibzugriff auf das <code><nowiki>$3</nowiki></code>-Verzeichnis muss für diesen ermöglicht werden, um den Installationsvorgang fortsetzen zu können.
-
-Auf einem Unix- oder Linux-System:
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Das Datenverzeichnis <code><nowiki>$1</nowiki></code> kann nicht erzeugt werden, da das übergeordnete Verzeichnis <code><nowiki>$2</nowiki></code> nicht für den Webserver beschreibbar ist.
-
-Das Installationsprogramm konnte den Benutzer bestimmen, mit dem Webserver ausgeführt wird.
-Schreibzugriff auf das <code><nowiki>$3</nowiki></code>-Verzeichnis muss global für diesen und andere Benutzer ermöglicht werden, um den Installationsvorgang fortsetzen zu können.
-
-Auf einem Unix- oder Linux-System:
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Fehler beim Erstellen des Datenverzeichnisses „$1“.
-
-Bitte den Speicherort überprüfen und es danach erneut versuchen.',
- 'config-sqlite-dir-unwritable' => 'Das Verzeichnis „$1“ ist nicht beschreibbar.
-Bitte die Zugriffsberechtigungen so ändern, dass dieses Verzeichnis für den Webserver beschreibbar ist und es danach erneut versuchen.',
- 'config-sqlite-connection-error' => '$1.
-
-Bitte unten angegebenes Datenverzeichnis sowie den Datenbanknamen überprüfen und es danach erneut versuchen.',
- 'config-sqlite-readonly' => 'Die Datei <code>$1</code> ist nicht beschreibbar.',
- 'config-sqlite-cant-create-db' => 'Die Datenbankdatei <code>$1</code> konnte nicht erzeugt werden.',
- 'config-sqlite-fts3-downgrade' => 'PHP verfügt nicht über FTS3-Unterstützung. Die Tabellen wurden zurückgestuft.',
- 'config-can-upgrade' => "Es wurden MediaWiki-Tabellen in dieser Datenbank gefunden.
-Um sie auf MediaWiki $1 zu aktualisieren, bitte auf '''Weiter''' klicken.",
- 'config-upgrade-done' => "Die Aktualisierung ist nun abgeschlossen.
-
-Das Wiki kann nun [$1 genutzt werden].
-
-Sofern die Datei <code>LocalSettings.php</code> neu erzeugt werden soll, bitte auf die Schaltfläche unten klicken.
-Dies wird '''nicht empfohlen''', es sei denn, es treten Probleme mit dem Wiki auf.",
- 'config-upgrade-done-no-regenerate' => 'Die Aktualisierung ist abgeschlossen.
-
-Das Wiki kann nun [$1 genutzt werden].',
- 'config-regenerate' => 'LocalSettings.php neu erstellen →',
- 'config-show-table-status' => 'Die Abfrage <code>SHOW TABLE STATUS</code> ist gescheitert!',
- 'config-unknown-collation' => "'''Warnung:''' Die Datenbank nutzt eine unbekannte Kollation.",
- 'config-db-web-account' => 'Datenbankkonto für den Webzugriff',
- 'config-db-web-help' => 'Bitte Benutzernamen und Passwort auswählen, die der Webserver während des Normalbetriebes dazu verwenden soll, eine Verbindung zum Datenbankserver herzustellen.',
- 'config-db-web-account-same' => 'Dasselbe Datenbankkonto wie während des Installationsvorgangs verwenden',
- 'config-db-web-create' => 'Sofern nicht bereits vorhanden, muss nun das Konto erstellt werden',
- 'config-db-web-no-create-privs' => 'Das angegebene und für den Installationsvorgang vorgesehene Datenbankkonto verfügt nicht über ausreichend Berechtigungen, um ein weiteres Datenbankkonto zu erstellen.
-Das hier angegebene Datenbankkonto muss daher bereits vorhanden sein.',
- 'config-mysql-engine' => 'Speicher-Engine:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Warnung:''' Es wurde MyISAM als Speicher-Engine für MySQL ausgewählt, die aus folgenden Gründen nicht für den Einsatz mit MediaWiki empfohlen ist:
-* Sie unterstützt aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen.
-* Sie ist anfälliger für Datenprobleme.
-* Sie wird von MediaWiki nicht immer adäquat unterstützt.
-
-Sofern die vorhandene MySQL-Installation die Speicher-Engine InnoDB unterstützt, wird deren Verwendung eindringlich empfohlen.
-Sofern sie sie nicht unterstützt, sollte eine entsprechende Aktualisierung nunmehr Erwägung gezogen werden.",
- 'config-mysql-only-myisam-dep' => "'''Warnung:''' MyISAM ist die einzige verfügbare Speicher-Engine für MySQL, die nicht für die Verwendung mit MediaWiki empfohlen wird, da sie
-* aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen unterstützt,
-* anfälliger für Datenprobleme ist und
-* von MediaWiki nicht immer adäquat unterstützt wird.
-
-Deine MySQL-Installation unterstützt nicht InnoDB. Eventuell muss eine Aktualisierung durchgeführt werden.",
- 'config-mysql-engine-help' => "'''InnoDB''' ist fast immer die bessere Wahl, da es gleichzeitige Zugriffe gut unterstützt.
-
-'''MyISAM''' ist in Einzelnutzerumgebungen sowie bei schreibgeschützten Wikis schneller.
-Bei MyISAM-Datenbanken treten tendenziell häufiger Fehler auf als bei InnoDB-Datenbanken.",
- 'config-mysql-charset' => 'Datenbankzeichensatz:',
- 'config-mysql-binary' => 'binär',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "Im '''binären Modus''' speichert MediaWiki UTF-8 Texte in der Datenbank in binär kodierte Datenfelder.
-Dies ist effizienter als der UTF-8-Modus von MySQL und ermöglicht so die Verwendung jeglicher Unicode-Zeichen.
-
-Im '''UTF-8-Modus''' wird MySQL den Zeichensatz der Daten erkennen und sie richtig anzeigen und konvertieren,
-allerdings können keine Zeichen außerhalb des [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
- 'config-site-name' => 'Name des Wikis:',
- 'config-site-name-help' => 'Er wird in der Titelleiste des Browsers, wie auch verschiedenen anderen Stellen, genutzt.',
- 'config-site-name-blank' => 'Den Namen des Wikis angeben.',
- 'config-project-namespace' => 'Name des Projektnamensraums:',
- 'config-ns-generic' => 'Projekt',
- 'config-ns-site-name' => 'Entspricht dem Namen des Wikis: $1',
- 'config-ns-other' => 'Anderer Name (bitte angeben)',
- 'config-ns-other-default' => 'MeinWiki',
- 'config-project-namespace-help' => "Dem Beispiel von Wikipedia folgend, unterscheiden viele Wikis zwischen den Seiten für Inhalte und denen für Richtlinien. Letztere werden im „'''Projektnamensraum'''“ hinterlegt.
-Alle Seiten dieses Namensraumes verfügen über einen Seitenpräfix, der nun an dieser Stelle angegeben werden kann.
-Traditionell steht dieser Seitenpräfix mit dem Namen des Wikis in einem engen Zusammenhang. Dabei können bestimmte Sonderzeichen wie „#“ oder „:“ nicht verwendet werden.",
- 'config-ns-invalid' => 'Der angegebene Namensraum „<nowiki>$1</nowiki>“ ist ungültig.
-Bitte einen abweichenden Projektnamensraum angeben.',
- 'config-ns-conflict' => 'Der angegebene Namensraum „<nowiki>$1</nowiki>“ verursacht Problem mit dem Standardnamensraum von MediaWiki.
-Bitte einen abweichenden Projektnamensraum angeben.',
- 'config-admin-box' => 'Administratorkonto',
- 'config-admin-name' => 'Name:',
- 'config-admin-password' => 'Passwort:',
- 'config-admin-password-confirm' => 'Passwort wiederholen:',
- 'config-admin-help' => 'Bitte den bevorzugten Benutzernamen angeben, beispielsweise „Knut Wuchtig“.
-Dies ist der Name, der benötigt wird, um sich im Wiki anzumelden.',
- 'config-admin-name-blank' => 'Bitte den Benutzernamen für den Administratoren angeben.',
- 'config-admin-name-invalid' => 'Der angegebene Benutzername „<nowiki>$1</nowiki>“ ist ungültig.
-Bitte einen abweichenden Benutzernamen angeben.',
- 'config-admin-password-blank' => 'Bitte das Passwort für das Administratorkonto angeben.',
- 'config-admin-password-same' => 'Das Passwort darf nicht mit dem Benutzernamen übereinstimmen.',
- 'config-admin-password-mismatch' => 'Die beiden Passwörter stimmen nicht überein.',
- 'config-admin-email' => 'E-Mail-Adresse:',
- 'config-admin-email-help' => 'Bitte hier eine E-Mail-Adresse angeben, die den E-Mail-Empfang von anderen Benutzern des Wikis, das Zurücksetzen des Passwortes sowie Benachrichtigungen zu Änderungen an beobachteten Seiten ermöglicht. Diese Feld kann leer gelassen werden.',
- 'config-admin-error-user' => 'Es ist beim Erstellen des Administrators mit dem Namen „<nowiki>$1</nowiki>“ ein interner Fehler aufgetreten.',
- 'config-admin-error-password' => 'Es ist beim Setzen des Passworts für den Administrator „<nowiki>$1</nowiki>“ ein interner Fehler aufgetreten: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Es wurde eine ungültige E-Mail-Adresse angegeben',
- 'config-subscribe' => 'Bitte die Mailingliste [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mitteilungen zu Versionsveröffentlichungen] abonnieren.',
- 'config-subscribe-help' => 'Es handelt sich hierbei um eine Mailingliste mit wenigen Aussendungen, die für Mitteilungen zu Versionsveröffentlichungen, einschließlich wichtiger Sicherheitsveröffentlichungen, genutzt wird.
-Diese Mailingliste sollte abonniert werden. Zudem sollte die MediaWiki-Installation stets aktualisiert werden, sobald eine neue Programmversion veröffentlicht wurde.',
- 'config-subscribe-noemail' => 'Beim Abonnieren der Mailingliste mit Mitteilungen zu Versionsveröffentlichungen wurde keine E-Mail-Adresse angegeben.
-Bitte eine E-Mail-Adresse angeben, sofern die Mailingliste abonniert werden soll.',
- 'config-almost-done' => 'Der Vorgang ist fast abgeschlossen!
-Die verbleibenden Konfigurationseinstellungen können übersprungen und das Wiki umgehend installiert werden.',
- 'config-optional-continue' => 'Ja, es sollen weitere Konfigurationseinstellungen vorgenommen werden.',
- 'config-optional-skip' => 'Nein, das Wiki soll nun installiert werden.',
- 'config-profile' => 'Profil der Benutzerberechtigungen:',
- 'config-profile-wiki' => 'offenes Wiki',
- 'config-profile-no-anon' => 'Erstellung eines Benutzerkontos erforderlich',
- 'config-profile-fishbowl' => 'ausschließlich berechtigte Bearbeiter',
- 'config-profile-private' => 'geschlossenes Wiki',
- 'config-profile-help' => "Wikis sind am nützlichsten, wenn so viele Menschen als möglich Bearbeitungen vornehmen können.
-Mit MediaWiki ist es einfach die letzten Änderungen nachzuvollziehen und unbrauchbare Bearbeitungen, beispielsweise von unbedarften oder böswilligen Benutzern, rückgängig zu machen.
-
-Allerdings finden etliche Menschen Wikis auch mit anderen Bearbeitungskonzepten sinnvoll. Manchmal ist es zudem nicht einfach alle Beteiligten von den Vorteilen des „Wiki-Prinzips” zu überzeugen. Darum ist diese Auswahl möglich.
-
-Das Modell „'''{{int:config-profile-wiki}}'''“ ermöglicht es jedermann, sogar ohne über ein Benutzerkonto zu verfügen, Bearbeitungen vorzunehmen.
-Ein Wiki bei dem die '''{{int:config-profile-no-anon}}''' ist, fordert von den Benutzern eine höhere Verantwortung für ihre Bearbeitungen ein, könnte allerdings Personen abschrecken, die nur gelegentlich Bearbeitungen vornehmen wollen. Ein Wiki für '''{{int:config-profile-fishbowl}}''' gestattet es nur bestimmten Benutzern, Bearbeitungen vorzunehmen. Allerdings kann dabei die Allgemeinheit die Seiten immer noch betrachten und Änderungen nachvollziehen. Ein '''{{int:config-profile-private}}''' gestattet es nur ausgewählten Benutzern, Seiten zu betrachten sowie zu bearbeiten.
-
-Komplexere Konzepte zur Zugriffssteuerung können erst nach abgeschlossenem Installationsvorgang eingerichtet werden. Hierzu gibt es weitere Informationen auf der Website mit der [//www.mediawiki.org/wiki/Manual:User_rights entsprechenden Anleitung].",
- 'config-license' => 'Lizenz:',
- 'config-license-none' => 'Keine Lizenzangabe in der Fußzeile',
- 'config-license-cc-by-sa' => 'Creative Commons „Namensnennung, Weitergabe unter gleichen Bedingungen“',
- 'config-license-cc-by' => 'Creative Commons „Namensnennung“',
- 'config-license-cc-by-nc-sa' => 'Creative Commons „Namensnennung, nicht kommerziell, Weitergabe unter gleichen Bedingungen“',
- 'config-license-cc-0' => 'Creative Commons „Zero“ (Gemeinfreiheit)',
- 'config-license-gfdl' => 'GNU-Lizenz für freie Dokumentation 1.3 oder höher',
- 'config-license-pd' => 'Gemeinfreiheit',
- 'config-license-cc-choose' => 'Eine benutzerdefinierte Creative-Commons-Lizenz auswählen',
- 'config-license-help' => 'Viele öffentliche Wikis publizieren alle Beiträge unter einer [http://freedomdefined.org/Definition/De freien Lizenz].
-Dies trägt dazu bei ein Gefühl von Gemeinschaft zu schaffen und ermutigt zu längerfristiger Mitarbeit.
-Dahingegen ist im Allgemeinen eine freie Lizenz auf geschlossenen Wikis nicht notwendig.
-
-Sofern man Texte aus der Wikipedia verwenden möchte und umgekehrt, sollte die Creative Commons-Lizenz „Namensnennung, Weitergabe unter gleichen Bedingungen“ gewählt werden.
-
-Die Wikipedia nutzte vormals die GNU-Lizenz für freie Dokumentation (GFDL).
-Die GFDL ist eine gültige Lizenz, die allerdings schwer zu verstehen ist.
-Es ist zudem schwierig gemäß dieser Lizenz lizenzierte Inhalten 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 [http://www.php.net/manual/en/mail.configuration.php PHP-E-Mail-Einstellungen] richtig konfiguriert werden.
-Fü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',
- 'config-email-usertalk-help' => 'Ermöglicht es Benutzern, Benachrichtigungen zu Änderungen an ihren Benutzerdiskussionsseiten zu erhalten, sofern sie dies in ihren Einstellungen aktiviert haben.',
- 'config-email-watchlist' => 'Benachrichtigungen zu Änderungen an Seiten auf der Beobachtungsliste ermöglichen',
- 'config-email-watchlist-help' => 'Ermöglicht es Benutzern, Benachrichtigungen zu Änderungen an Seiten auf ihrer Beobachtungsliste zu erhalten, sofern sie dies in ihren Einstellungen aktiviert haben.',
- 'config-email-auth' => 'E-Mail-Authentifizierung ermöglichen',
- 'config-email-auth-help' => "Sofern diese Funktion aktiviert ist, müssen Benutzer ihre E-Mail-Adresse bestätigen, indem sie den Bestätigungslink nutzen, der ihnen immer dann zugesandt wird, wenn sie ihre E-Mail-Adresse angeben oder ändern.
-Nur bestätigte E-Mail-Adressen können Nachrichten von anderen Benutzer oder Benachrichtigungsmitteilungen erhalten.
-Die Aktivierung dieser Funktion wird bei offenen Wikis, mit Hinblick auf möglichen Missbrauch der E-Mail-Funktionen, '''empfohlen.'''",
- 'config-email-sender' => 'E-Mail-Adresse für Antworten:',
- 'config-email-sender-help' => 'Bitte hier die E-Mail-Adresse angeben, die als Absenderadresse bei ausgehenden E-Mails eingesetzt werden soll.
-Rücklaufende E-Mails werden an diese E-Mail-Adresse gesandt.
-Bei vielen E-Mail-Servern muss der Teil der E-Mail-Adresse mit der Domainangabe korrekt sein.',
- 'config-upload-settings' => 'Hochladen von Bildern und Dateien',
- 'config-upload-enable' => 'Das Hochladen von Dateien ermöglichen',
- 'config-upload-help' => 'Das Hochladen von Dateien macht den Server für potentielle Sicherheitsprobleme anfällig.
-Weitere Informationen hierzu können im [//www.mediawiki.org/wiki/Manual:Security Abschnitt Sicherheit] der Anleitung nachgelesen werden.
-
-Um das Hochladen von Dateien zu ermöglichen, muss der Zugriff auf das Unterverzeichnis <code>./images</code> so geändert werden, das dieses für den Webserver beschreibbar ist.
-Hernach kann diese Option aktiviert werden.',
- 'config-upload-deleted' => 'Verzeichnis für gelöschte Dateien:',
- 'config-upload-deleted-help' => 'Bitte ein Verzeichnis auswählen, in dem gelöschte Dateien archiviert werden sollen.
-Idealerweise sollte es nicht über das Internet zugänglich sein.',
- 'config-logo' => 'URL des Logos:',
- 'config-logo-help' => 'Die Standardoberfläche von MediaWiki verfügt links oberhalb der Seitenleiste über Platz für ein Logo mit den Maßen 135x160 Pixel.
-Bitte ein Logo in entsprechender Größe hochladen und die zugehörige URL an dieser Stelle angeben.
-
-Du kannst <code>$wgStylePath</code> oder <code>$wgScriptPath</code> verwenden, falls dein Logo relativ zu diesen Pfaden ist.
-
-Sofern kein Logo benötigt wird, kann dieses Datenfeld leer bleiben.',
- 'config-instantcommons' => '„InstantCommons“ aktivieren',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons] ist eine Funktion, die es Wikis ermöglicht, Bild-, Klang- und andere Mediendateien zu nutzen, die auf der Website [//commons.wikimedia.org/ Wikimedia Commons] verfügbar sind.
-Um diese Funktion nutzen zu können, muss das Wiki über eine Verbindung zum Internet verfügen.
-
-Weitere Informationen zu dieser Funktion, einschließlich der Anleitung, wie hierfür andere Wikis als Wikimedia Commons eingerichtet werden können, gibt es im [//mediawiki.org/wiki/Manual:$wgForeignFileRepos Handbuch].',
- 'config-cc-error' => 'Der Creativ-Commons-Lizenzassistent konnte keine Lizenz ermitteln.
-Die Lizenz ist daher jetzt manuell einzugeben.',
- 'config-cc-again' => 'Erneut auswählen …',
- 'config-cc-not-chosen' => 'Die gewünschte Creative-Commons-Lizenz auswählen und dann auf „weiter“ klicken.',
- 'config-advanced-settings' => 'Erweiterte Konfiguration',
- 'config-cache-options' => 'Einstellungen für die Zwischenspeicherung von Objekten:',
- 'config-cache-help' => 'Das Objektcaching wird dazu genutzt die Geschwindigkeit von MediaWiki zu verbessern, indem häufig genutzte Daten zwischengespeichert werden.
-Es wird sehr empfohlen es für mittelgroße bis große Wikis zu nutzen, aber auch für kleine Wikis ergeben sich erkennbare Geschwindigkeitsverbesserungen.',
- 'config-cache-none' => 'Kein Objektcaching (es wird keine Funktion entfernt, allerdings kann dies die Geschwindigkeit größerer Wikis negativ beeinflussen)',
- 'config-cache-accel' => 'Objektcaching von PHP (APC, XCache oder WinCache)',
- 'config-cache-memcached' => 'Memcached Cacheserver nutzen (erfordert einen zusätzlichen Installationsvorgang mitsamt Konfiguration)',
- 'config-memcached-servers' => 'Memcached Cacheserver',
- 'config-memcached-help' => 'Liste der für Memcached nutzbaren IP-Adressen.
-Es sollte eine je Zeile mitsamt des vorgesehenen Ports angegeben werden. Beispiele:
-127.0.0.1:11211 oder
-192.168.1.25:1234 usw.',
- 'config-memcache-needservers' => 'Memcached wurde als Cacheserver ausgewählt. Dabei wurde allerdings kein Server angegeben.',
- 'config-memcache-badip' => 'Es wurde für Memcached eine ungültige IP-Adresse angegeben: $1',
- 'config-memcache-noport' => 'Es wurde kein Port zur Nutzung durch den Memcached Cacheserver angegeben: $1
-Sofern der Port unbekannt ist, ist 11211 die Standardangabe.',
- 'config-memcache-badport' => 'Der Ports für den Memcached Cacheserver sollten zwischen $1 und $2 liegen',
- 'config-extensions' => 'Erweiterungen',
- 'config-extensions-help' => 'Die obig angegebenen Erweiterungen wurden im Verzeichnis <code>./extensions</code> gefunden.
-
-Es könnten zusätzliche Konfigurierungen zu einzelnen Erweiterungen erforderlich sein, dennoch können sie aber bereits jetzt aktiviert werden.',
- 'config-install-alreadydone' => "'''Warnung:''' Es wurde eine vorhandene MediaWiki-Installation gefunden.
-Es muss daher mit den nächsten Seite weitergemacht werden.",
- 'config-install-begin' => 'Durch Drücken von „{{int:config-continue}}“ wird die Installation von MediaWiki gestartet.
-Sofern Änderungen vorgenommen werden sollen, kann man auf „{{int:config-back}}“ klicken.',
- 'config-install-step-done' => 'erledigt',
- 'config-install-step-failed' => 'gescheitert',
- 'config-install-extensions' => 'Programmerweiterungen',
- 'config-install-database' => 'Datenbank wird eingerichtet',
- 'config-install-schema' => 'Datenschema wird erstellt',
- 'config-install-pg-schema-not-exist' => 'Das PostgesSQL-Datenschema ist nicht vorhanden',
- 'config-install-pg-schema-failed' => 'Das Erstellen der Datentabellen ist gescheitert.
-Es muss sichergestellt sein, dass der Benutzer „$1“ Schreibzugriff auf das Datenschema „$2“ hat.',
- 'config-install-pg-commit' => 'Änderungen anwenden',
- 'config-install-pg-plpgsql' => 'Suche nach der Datenbanksprache PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Für Datenbank $1 muss die Datenbanksprache PL/pgSQL installiert werden',
- 'config-pg-no-create-privs' => 'Das für die Installation angegeben Konto verfügt nicht über ausreichende Berechtigungen, um ein Datenbanknutzerkonto zu erstellen.',
- 'config-pg-not-in-role' => 'Das für den Webbenutzer angegebene Benutzerkonto ist bereits vorhanden.
-Das für den Installationsvorgang angegebene Benutzerkonto ist kein Superbenutzer und nicht Mitglied der Benutzergruppe der Webbenutzer, so dass keine dem Webbenutzer zugeordneten Datenobjekte erstellt werden können.
-
-Für MediaWiki ist es momentan erforderlich, dass die Tabellen dem Webbenutzer rechtemäßig zugeordnet sind. Bitte einen anderen Namen für den Wikibenutzer angeben oder „← Zurück“ anklicken, um einen ausreichend berechtigten Benutzer für den Installationsvorgang anzugeben.',
- 'config-install-user' => 'Datenbankbenutzer wird erstellt',
- 'config-install-user-alreadyexists' => 'Datenbankbenutzer „$1“ ist bereits vorhanden',
- 'config-install-user-create-failed' => 'Das Anlegen des Datenbankbenutzers „$1“ ist gescheitert: $2',
- 'config-install-user-grant-failed' => 'Die Gewährung der Berechtigung für Datenbankbenutzer „$1“ ist gescheitert: $2',
- 'config-install-user-missing' => 'Der angegebene Benutzer „$1“ ist nicht vorhanden.',
- 'config-install-user-missing-create' => 'Der angegebene Benutzer „$1“ ist nicht vorhanden.
-Bitte das Auswahlkästchen „Benutzerkonto erstellen“ anklicken, sofern dieser erstellt werden soll.',
- 'config-install-tables' => 'Datentabellen werden erstellt',
- 'config-install-tables-exist' => "'''Warnung:''' Es wurden MediaWiki-Datentabellen gefunden.
-Die Erstellung wurde übersprungen.",
- 'config-install-tables-failed' => "'''Fehler:''' Die Erstellung der Datentabellen ist aufgrund des folgenden Fehlers gescheitert: $1",
- 'config-install-interwiki' => 'Interwikitabellen werden eingerichtet',
- 'config-install-interwiki-list' => 'Die Datei <code>interwiki.list</code> konnte nicht gelesen werden.',
- 'config-install-interwiki-exists' => "'''Warnung:''' Es wurden Interwikitabellen mit Daten gefunden.
-Die Standardliste wird übersprungen.",
- 'config-install-stats' => 'Statistiken werden initialisiert',
- 'config-install-keys' => 'Geheimschlüssel werden erstellt',
- 'config-insecure-keys' => "'''Warnung:''' {{PLURAL:$2|Der Geheimschlüssel|Die Geheimschlüssel}} $1, {{PLURAL:$2|der|die}} während des Installationsvorgangs generiert {{PLURAL:$2|wurde, ist|wurden, sind}} nicht sehr sicher. {{PLURAL:$2|Er sollte|Sie sollten}} manuell geändert werden.",
- 'config-install-sysop' => 'Administratorkonto wird erstellt',
- 'config-install-subscribe-fail' => 'Abonnieren von „mediawiki-announce“ ist gescheitert: $1',
- 'config-install-subscribe-notpossible' => 'cURL ist nicht installiert und allow_url_fopen ist nicht verfügbar.',
- 'config-install-mainpage' => 'Erstellung der Hauptseite mit Standardinhalten',
- 'config-install-extension-tables' => 'Erstellung der Tabellen für die aktivierten Erweiterungen',
- 'config-install-mainpage-failed' => 'Die Hauptseite konnte nicht erstellt werden: $1',
- 'config-install-done' => "'''Herzlichen Glückwunsch!'''
-MediaWiki wurde erfolgreich installiert.
-
-Das Installationsprogramm hat die Datei <code>LocalSettings.php</code> erzeugt.
-Sie enthält alle vorgenommenen Konfigurationseinstellungen.
-
-Diese Datei muss nun heruntergeladen und anschließend in das Stammverzeichnis der MediaWiki-Installation hochgeladen werden. Dies ist dasselbe Verzeichnis, in dem sich auch die Datei <code>index.php</code> befindet. Das Herunterladen sollte inzwischen automatisch gestartet worden sein.
-
-Sofern dies nicht der Fall war, oder das Herunterladen unterbrochen wurde, kann der Vorgang durch einen Klick auf den folgenden Link erneut gestartet werden:
-
-$3
-
-'''Hinweis:''' Die Konfigurationsdatei sollte jetzt unbedingt heruntergeladen werden. Sie wird nach Beenden des Installationsprogramms, nicht mehr zur Verfügung stehen.
-
-Sobald alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''. Wir wünschen viel Spaß und Erfolg mit dem Wiki.",
- 'config-download-localsettings' => '<code>LocalSettings.php</code> herunterladen',
- 'config-help' => 'Hilfe',
- 'config-nofile' => 'Die Datei „$1“ konnte nicht gefunden werden. Wurde sie gelöscht?',
- 'config-extension-link' => 'Wusstest du, dass dein Wiki [//www.mediawiki.org/wiki/Manual:Extensions Erweiterungen] unterstützt?
-
-Du kannst [//www.mediawiki.org/wiki/Category:Extensions_by_category Erweiterungen nach Kategorie] durchsuchen oder die [//www.mediawiki.org/wiki/Extension_Matrix Erweiterungstabelle] ansehen, um eine volle Erweiterungsliste zu erhalten.',
- 'mainpagetext' => "'''MediaWiki wurde erfolgreich installiert.'''",
- 'mainpagedocfooter' => 'Hilfe zur Benutzung und Konfiguration der Wiki-Software findest du im [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].
-
-== Starthilfen ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste der Konfigurationsvariablen]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Lokalisiere MediaWiki für deine Sprache]',
-);
-
-/** German (formal address) (Deutsch (Sie-Form)‎)
- * @author MichaelFrey
- */
-$messages['de-formal'] = array(
- 'mainpagedocfooter' => 'Hilfe zur Benutzung und Konfiguration der Wiki-Software finden Sie im [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].
-
-== Starthilfen ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste der Konfigurationsvariablen]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]', # Fuzzy
-);
-
-/** Zazaki (Zazaki)
- * @author Erdemaslancan
- * @author Mirzali
- */
-$messages['diq'] = array(
- 'config-title' => 'MediaWiki $1 sazkerdış',
- 'config-information' => 'Melumat',
- 'config-your-language' => 'Zıwanê şıma:',
- 'config-wiki-language' => 'Wiki zıwan:',
- 'config-back' => '← Peyser',
- 'config-continue' => 'Dewam ke',
- 'config-page-language' => 'Zıwan',
- 'config-page-welcome' => 'Şıma xeyr ameyê MediaWiki!',
- 'config-page-dbconnect' => 'Database rê grêdey',
- 'config-page-name' => 'Name',
- 'config-page-options' => 'Weçinegi',
- 'config-page-install' => 'Barine',
- 'config-page-complete' => 'Temamyayo',
- 'config-page-readme' => 'Mı bıwane',
- 'config-page-copying' => 'Kopyayeno',
- 'config-page-upgradedoc' => 'Berzkerdış',
- 'config-restart' => 'E, fına dest pekê',
- 'config-sidebar' => "* [//www.mediawiki.org MediaWiki keye]
-* [//www.mediawiki.org/wiki/Help:Contents User's Şınasiye]
-* [//www.mediawiki.org/wiki/Manual:Contents Xizmetkara şınasiye]
-* [//www.mediawiki.org/wiki/Manual:FAQ Peşti]
-----
-* <doclink href=Readme>Mı buwanê</doclink>
-* <doclink href=ReleaseNotes>Notê elekeyıni</doclink>
-* <doclink href=Copying>Telifiye</doclink>
-* <doclink href=UpgradeDoc>Weşkerdış</doclink>",
- 'config-env-php' => 'PHP $1 i biyo saz.',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 dılet',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-db-port' => 'Portê database:',
- 'config-header-mysql' => 'Sazkardışê MySQL',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-binary' => 'Dılet',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Namey wiki:',
- 'config-ns-generic' => 'Proce',
- 'config-ns-other' => 'Zewbi (keyfiyo)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-admin-box' => 'Hesabê Administratori',
- 'config-admin-name' => 'Namey şıma:',
- 'config-admin-password' => 'Parola:',
- 'config-admin-password-confirm' => 'Fına parola:',
- 'config-admin-email' => 'Adresa e-postey:',
- 'config-profile-private' => 'Bexse wiki',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-pd' => 'Malê Şari',
- 'config-extensions' => 'Olekeni',
- 'config-help' => 'peşti',
- 'mainpagetext' => "'''MediaWiki vıst ra ser, vıraziya.'''",
- 'mainpagedocfooter' => 'Seba gurenayış u eyarkerdışê Wiki-Softwarey [//meta.wikimedia.org/wiki/Help:Contents İdarê karberi] de mıracaet ke.
-
-== Yardımê Sıftekerdışi ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista eyaranê vıraştışi]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki de ÇZP]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-dayışê postey]', # Fuzzy
-);
-
-/** Lower Sorbian (dolnoserbski)
- * @author Michawiki
- */
-$messages['dsb'] = array(
- 'mainpagetext' => "'''MediaWiki jo se wuspěšnje instalěrowało.'''",
- 'mainpagedocfooter' => "Pomoc pśi wužywanju softwary wiki namakajoš pód [//meta.wikimedia.org/wiki/Help:Contents User's Guide].
-
-Pomoc pśi wužywanju softwary wiki namakajoš pód [//meta.wikimedia.org/wiki/Help:Contents User's Guide].
-
-== Na zachopjenje ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfiguracija lisćiny połoženjow]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ (pšašanja a wótegrona)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lisćina e-mailowych nakładow MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki za twóju rěc lokalizěrowaś]",
-);
-
-/** Central Dusun (Dusun Bundu-liwan)
- * @author FRANELYA
- */
-$messages['dtp'] = array(
- 'mainpagetext' => "'''Nopongo no do popodokot ot ModiaWiki.'''",
- 'mainpagedocfooter' => 'Rujuko hilo [//meta.wikimedia.org/wiki/Help:Contents Ponudukan Momomoguno] kokomoi koilaan do momoguno posusuang-suangon wiki.
-
-== Kopotimpuunan ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lis papatantu nuludan]
-* [//www.mediawiki.org/wiki/Manual:FAQ Ponguhatan Koinsoruan om Simbar ModiaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis pininsuratan pinolabus do ModiaWiki]', # Fuzzy
-);
-
-/** Greek (Ελληνικά)
- * @author Glavkos
- * @author Protnet
- * @author ZaDiak
- */
-$messages['el'] = array(
- 'config-desc' => 'Το πρόγραμμα εγκατάστασης για το MediaWiki',
- 'config-title' => 'Εγκατάσταση MediaWiki $1',
- 'config-information' => 'Πληροφορίες',
- 'config-your-language' => 'Η γλώσσα σας:',
- 'config-wiki-language' => 'Γλώσσα του wiki:',
- 'config-back' => '← Πίσω',
- 'config-continue' => 'Συνέχεια →',
- 'config-page-language' => 'Γλώσσα',
- 'config-page-welcome' => 'Καλώς ήλθατε στο MediaWiki!',
- 'config-page-name' => 'Όνομα',
- 'config-page-options' => 'Επιλογές',
- 'config-page-install' => 'Εγκατάσταση',
- 'config-page-complete' => 'Ολοκληρώθηκε!',
- 'config-page-restart' => 'Επανεκκίνηση εγκατάστασης',
- 'config-page-copying' => 'Αντιγραφή',
- 'config-page-upgradedoc' => 'Αναβάθμιση',
- 'config-page-existingwiki' => 'Υπάρχον βίκι',
- 'config-restart' => 'Ναι, κάντε επανεκκίνηση',
- 'config-env-php' => 'H PHP $1 είναι εγκατεστημένη.',
- 'config-db-type' => 'Τύπος βάσης δεδομένων:',
- 'config-db-host' => 'Φιλοξενία βάσης δεδομένων:',
- 'config-db-wiki-settings' => 'Αναγνώριση αυτού του βίκι',
- 'config-db-name' => 'Όνομα βάσης δεδομένων:',
- 'config-db-install-account' => 'Λογαριασμός χρήστη για την εγκατάσταση',
- 'config-db-username' => 'Όνομα χρήστη βάσης δεδομένων:',
- 'config-db-password' => 'Κωδικός πρόσβασης βάσης δεδομένων:',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 δυαδικό',
- 'config-header-mysql' => 'Ρυθμίσεις MySQL',
- 'config-header-postgres' => 'Ρυθμίσεις PostgreSQL',
- 'config-header-sqlite' => 'Ρυθμίσεις SQLite',
- 'config-header-oracle' => 'Ρυθμίσεις Oracle',
- 'config-invalid-db-type' => 'Μη έγκυρος τύπος βάσης δεδομένων',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Όνομα του βίκι:',
- 'config-site-name-blank' => 'Εισαγάγετε όνομα ιστοχώρου.',
- 'config-project-namespace' => 'Ονοματοχώρος εγχειρήματος:',
- 'config-ns-generic' => 'Εγχείρημα',
- 'config-ns-site-name' => 'Ίδιο με το όνομα του wiki: $1',
- 'config-ns-other' => 'Άλλο (προσδιορίστε)',
- 'config-admin-box' => 'Λογαριασμός διαχειριστή',
- 'config-admin-name' => 'Το όνομά σας:',
- 'config-admin-password' => 'Κωδικός πρόσβασης:',
- 'config-admin-password-confirm' => 'Επανάληψη κωδικού πρόσβασης:',
- 'config-admin-email' => 'Διεύθυνση ηλεκτρονικού ταχυδρομείου:',
- 'config-optional-continue' => 'Να ερωτηθώ περισσότερες ερωτήσεις.',
- 'config-profile-wiki' => 'Παραδοσιακό wiki', # Fuzzy
- 'config-profile-no-anon' => 'Απαιτείται η δημιουργία λογαριασμού',
- 'config-profile-private' => 'Ιδιωτικό wiki',
- 'config-email-settings' => 'Ρυθμίσεις ηλεκτρονικού ταχυδρομείου',
- 'config-upload-settings' => 'Ανέβασμα εικόνων και άλλων αρχείων',
- 'config-upload-enable' => 'Ενεργοποιήστε το ανέβασμα αρχείων',
- 'config-logo' => 'Διεύθυνση URL λογότυπου:',
- 'config-cc-again' => 'Επιλέξτε ξανά...',
- 'config-extensions' => 'Επεκτάσεις',
- 'config-install-step-done' => 'έγινε',
- 'config-install-step-failed' => 'απέτυχε',
- 'config-help' => 'βοήθεια',
- 'mainpagetext' => "'''To λογισμικό MediaWiki εγκαταστάθηκε με επιτυχία.'''",
- 'mainpagedocfooter' => 'Περισσότερες πληροφορίες σχετικά με τη χρήση και με τη ρύθμιση παραμέτρων θα βρείτε στους συνδέσμους: [//meta.wikimedia.org/wiki/MediaWiki_localisation Οδηγίες για τροποποίηση του περιβάλλοντος εργασίας] και [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Εγχειρίδιο χρήστη].', # Fuzzy
-);
-
-/** British English (British English)
- * @author Shirayuki
- */
-$messages['en-gb'] = array(
- 'config-unicode-using-utf8' => "Using Brion Vibber's utf8_normalize.so for Unicode normalisation.",
- 'config-unicode-using-intl' => 'Using the [http://pecl.php.net/intl intl PECL extension] for Unicode normalisation.',
- 'config-unicode-pure-php-warning' => "'''Warning:''' The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalisation, falling back to slow pure-PHP implementation.
-If you run a high-traffic site, you should read a little on [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalisation].",
- 'config-unicode-update-warning' => "'''Warning:''' The installed version of the Unicode normalisation wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.
-You should [//www.mediawiki.org/wiki/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
- 'config-unknown-collation' => "'''Warning:''' Database is using unrecognised collation.",
- 'config-profile-fishbowl' => 'Authorised editors only',
- 'config-install-stats' => 'Initialising statistics',
-);
-
-/** Esperanto (Esperanto)
- * @author Airon90
- * @author Yekrats
- */
-$messages['eo'] = array(
- 'config-your-language' => 'Via lingvo:',
- 'config-your-language-help' => 'Elekti lingvon uzi dum la instalada procezo.',
- 'config-wiki-language' => 'Lingvo de la vikio:',
- 'config-wiki-language-help' => 'Elekti la ĉefe skribotan lingvon de la vikio.',
- 'config-page-welcome' => 'Bonvenon al MediaWiki!',
- 'config-page-dbsettings' => 'Agordoj de la datumbazo',
- 'config-page-name' => 'Nomo',
- 'config-page-options' => 'Agordoj',
- 'config-page-install' => 'Instali',
- 'config-page-complete' => 'Farita!',
- 'mainpagetext' => "'''MediaWiki estis sukcese instalita.'''",
- 'mainpagedocfooter' => "Konsultu la [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide User's Guide] por informo pri uzado de vikia programaro.
-
-==Kiel komenci==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listo de konfiguraĵoj] (angla)
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Oftaj Demandoj] (angla)
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki dissendolisto pri anoncoj] (angla)", # Fuzzy
-);
-
-/** Spanish (español)
- * @author Armando-Martin
- * @author Ciencia Al Poder
- * @author Crazymadlover
- * @author Danke7
- * @author Fitoschido
- * @author Locos epraix
- * @author Od1n
- * @author Platonides
- * @author Sanbec
- * @author Translationista
- * @author Vivaelcelta
- * @author 아라
- */
-$messages['es'] = array(
- 'config-desc' => 'El instalador para MediaWiki',
- 'config-title' => 'MediaWiki $1 instalación',
- 'config-information' => 'Información',
- 'config-localsettings-upgrade' => 'Se ha encontrado un archivo <code>LocalSettings.php</code>.
-Para actualizar esta instalación, por favor ingresa el valor de <code>$wgUpgradeKey</code> en el cuadro de abajo.
-Lo encontrarás en <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Se ha detectado un archivo <code>LocalSettings.php</code>.
-Para actualizar la instalación, vuelva a ejecutar <code>update.php</code>',
- 'config-localsettings-key' => 'Clave de actualización:',
- 'config-localsettings-badkey' => 'La clave proporcionada es incorrecta.',
- 'config-upgrade-key-missing' => 'Se ha detectado una instalación existente de MediaWiki.
-Para actualizar la instalación, por favor, ponga la siguiente línea al final de su archivo <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'El archivo <code>LocalSettings.php</code> existente parece estar incompleto.
-La variable $1 no está definida.
-Cambie el archivo <code>LocalSettings.php</code> para que esta variable quede establecida y haga clic en "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Se detectó un error al conectarse a la base de datos utilizando la configuración especificada en los archivos <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Corrija estas opciones y vuelva a intentarlo.
-
-$1',
- 'config-session-error' => 'Error comenzando sesión: $1',
- 'config-session-expired' => 'Tus datos de sesión parecen haber expirado.
-Las sesiones están configuradas por una duración de $1.
-Puedes incrementar esto configurando <code>session.gc_maxlifetime</code> en php.ini.
-Reiniciar el proceso de instalación.',
- 'config-no-session' => 'Se han perdido los datos de sesión.
-Verifica tu php.ini y comprueba que <code>session.save_path</code> está establecido en un directorio apropiado.',
- 'config-your-language' => 'Tu idioma:',
- 'config-your-language-help' => 'Seleccionar un idioma a usar durante el proceso de instalación.',
- 'config-wiki-language' => 'Idioma del wiki:',
- 'config-wiki-language-help' => 'Seleccionar el idioma en el que el wiki será escrito predominantemente.',
- 'config-back' => '← Atrás',
- 'config-continue' => 'Continuar →',
- 'config-page-language' => 'Idioma',
- 'config-page-welcome' => 'Bienvenido a MediaWiki!',
- 'config-page-dbconnect' => 'Conectar a la base de datos',
- 'config-page-upgrade' => 'Actualizar instalación existente',
- 'config-page-dbsettings' => 'Configuración de la base de datos',
- 'config-page-name' => 'Nombre',
- 'config-page-options' => 'Opciones',
- 'config-page-install' => 'Instalar',
- 'config-page-complete' => 'Completo!',
- 'config-page-restart' => 'Reiniciar instalación',
- 'config-page-readme' => 'Léeme',
- 'config-page-releasenotes' => 'Notas de la versión',
- 'config-page-copying' => 'Copiando',
- 'config-page-upgradedoc' => 'Actualizando',
- 'config-page-existingwiki' => 'Wiki existente',
- 'config-help-restart' => '¿Deseas borrar todos los datos que has ingresado hasta ahora y reiniciar el proceso de instalación desde el principio?',
- 'config-restart' => 'Sí, reiniciarlo',
- 'config-welcome' => '=== Comprobación del entorno ===
-Se realiza comprobaciones básicas para ver si el entorno es adecuado para la instalación de MediaWiki.
-Deberás suministrar los resultados de tales comprobaciones si necesitas ayuda durante la instalación.', # Fuzzy
- 'config-copyright' => "=== Derechos de autor y Términos de uso ===
-
-$1
-
-Este programa es software libre; puedes redistribuirlo y/o modificarlo en los términos de la Licencia Pública General de GNU, tal como aparece publicada por la Fundación para el Software Libre, tanto la versión 2 de la Licencia, como cualquier versión posterior (según prefiera).
-
-Este programa es distribuido en la esperanza de que sea útil, pero '''sin cualquier garantía'''; inclusive, sin la garantía implícita de la '''posibilidad de ser comercializado''' o de '''idoneidad para cualquier finalidad específica'''.
-Consulte la licencia *GNU General *Public *License para más detalles.
-
-En conjunto con este programa debe haber recibido <doclink href=Copying>una copia de la Licencia Pública General de GNU</doclink>; si no la recibió, pídala por escrito a Fundación para el Software Libre, Inc., 51 Franklin Street, Fifth Floor, Boston, ME La 02110-1301, USA o [http://www.gnu.org/copyleft/gpl.html léala en internet].",
- 'config-sidebar' => '* [//www.mediawiki.org Página principal de MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Guía del usuario]
-* [//www.mediawiki.org/wiki/Manual:Contents Guía del administrador]
-* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas frecuentes]
-----
-* <doclink href=Readme>Léeme</doclink>
-* <doclink href=ReleaseNotes>Notas de la versión</doclink>
-* <doclink href=Copying>Copia</doclink>
-* <doclink href=UpgradeDoc>Actualización</doclink>',
- 'config-env-good' => 'El entorno ha sido comprobado.
-Puedes instalar MediaWiki.',
- 'config-env-bad' => 'El entorno ha sido comprobado.
-No puedes instalar MediaWiki.',
- 'config-env-php' => 'PHP $1 está instalado.',
- 'config-env-php-toolow' => 'PHP $1 está instalado.
-Sin embargo, MediaWiki requiere PHP $2 o superior.',
- 'config-unicode-using-utf8' => 'Usando utf8_normalize.so de Brion Vibber para la normalización Unicode.',
- 'config-unicode-using-intl' => 'Usando la [http://pecl.php.net/intl extensión intl PECL] para la normalización Unicode.',
- 'config-unicode-pure-php-warning' => "'''Advertencia''': La [http://pecl.php.net/intl extensión intl] no está disponible para efectuar la normalización Unicode. Utilizando la implementación más lenta en PHP.
-Si tu web tiene mucho tráfico, te recomendamos leer acerca de la [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalización Unicode].",
- 'config-unicode-update-warning' => "'''Warning''': La versión instalada del contenedor de normalización Unicode usa una versión anterior de la biblioteca del [http://site.icu-project.org/ proyecto ICU].
-Deberás [//www.mediawiki.org/wiki/Unicode_normalization_considerations actualizar] si realmente deseas usar Unicode.",
- 'config-no-db' => 'No fue posible encontrar un controlador adecuado para la base de datos! Necesitas instalar un controlador de base de datos para PHP.
-Las siguientes bases de datos son soportadas: $1.
-Si estás en alojamiento compartido, pregunta a tu proveedor el instalar un controlador de base de datos adecuado.
-Si estás compilando PHP por ti mismo, reconfigúralo con un cliente de base de datos disponible, por ejemplo usando <code>./configure --with-mysql</code>.
-Si instalaste PHP de un paquete Debian o Ubuntu, entonces también necesitas instalar el módulo php5-mysql.',
- 'config-outdated-sqlite' => "''' Advertencia ''': tiene la versión SQLite $1, que es inferior a la mínima versión requerida: $2 . SQLite no estará disponible.",
- 'config-no-fts3' => "'''Advertencia''': SQLite está compilado sin el [//sqlite.org/fts3.html módulo FTS3]. Las funcionalidades de búsqueda no estarán disponibles en esta instalación.",
- 'config-register-globals' => "'''Advertencia: La opción de <code>[http://php.net/register_globals register_globals]</code> de PHP está habilitada.'''
-'''Desactívela si puede.'''
-MediaWiki funcionará, pero tu servidor quedará expuesto a vulnerabilidades de seguridad potenciales.",
- 'config-magic-quotes-runtime' => "'''Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está activada!'''
-Esta opción causa la imprevisible corrupción de la entrada de datos.
-No puedes instalar o utilizar MediaWiki a menos que esta opción esté inhabilitada.",
- 'config-magic-quotes-sybase' => "'''Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está activada!'''
-Esta opción causa la imprevisible corrupción de la entrada de datos.
-No puedes instalar o utilizar MediaWiki a menos que esta opción esté inhabilitada.",
- 'config-mbstring' => "'''Fatal: La opción [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está activada!'''
-Esta opción causa errores y puede corromper los datos de una forma imprevisible.
-No se puede instalar o usar MediaWiki a menos que esta opción sea desactivada.",
- 'config-ze1' => "'''Fatal: ¡La opción [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] está activada!'''
-Esta opción causa problemas significativos en MediaWiki.
-No se puede instalar o usar MediaWiki a menos que esta opción sea desactivada.",
- 'config-safe-mode' => "'''Advertencia:''' El [http://www.php.net/features.safe-mode modo seguro] de PHP está activado.
-Este modo puede causar problemas, especialmente en la carga de archivosy en compatibilidad con <code>math</code>.",
- 'config-xml-bad' => 'Falta el módulo XML de PHP.
-MediaWiki necesita funciones en este módulo y no funcionará con esta configuración.
-Si está ejecutando Mandrake, instale el paquete php-xml.',
- 'config-pcre' => 'Parece faltar el módulo de compatibilidad PCRE.
-MediaWiki necesita que las funciones de expresiones regulares compatibles con Perl estén funcionando.',
- 'config-pcre-no-utf8' => "'''Error fatal ''': Parece que el módulo PCRE de PHP fue compilado sin el soporte PCRE_UTF8.
-MediaWiki requiere compatibilidad con UTF-8 para funcionar correctamente.",
- 'config-memory-raised' => 'el parámetro <code>memory_limit</code> de PHP es $1, aumentada a $2.',
- 'config-memory-bad' => "'''Advertencia:''' El parámetro <code>memory_limit</code> de PHP es $1.
-Probablemente este valor es demasiado bajo.
-¡La instalación podrá fallar!",
- 'config-ctype' => "'''Fatal''': PHP debe ser compilado con soporte para la [http://www.php.net/manual/en/ctype.installation.php extensión Ctype].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] está instalado',
- 'config-apc' => '[http://www.php.net/apc APC] está instalado',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] está instalado',
- 'config-no-cache' => "'''Advertencia:''' No pudo encontrarse [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
-El caché de objetos no está habilitado.",
- 'config-mod-security' => "''' Advertencia ''': Su servidor web tiene [http://modsecurity.org/ mod_security] habilitado. Si la configuración es incorrecta, puede causar problemas a MediaWiki u otro software que permita a los usuarios publicar contenido arbitrarios.
-Consulte la [http://modsecurity.org/documentation/ documentación de mod_security] o contacte con el soporte de su servidor (''host'') si encuentra errores aleatorios.",
- 'config-diff3-bad' => 'GNU diff3 no se encuentra.',
- 'config-git-bad' => 'No se encontró el software de control de versiones Git.',
- 'config-imagemagick' => 'ImageMagick encontrado: <code>$1</code>.
-La miniaturización de imágenes se habilitará si habilitas las cargas.',
- 'config-gd' => 'Se ha encontrado una biblioteca de gráficos GD integrada.
-La miniaturización de imágenes se habilitará si habilitas las subidas.',
- 'config-no-scaling' => 'No se ha encontrado ninguma biblioteca GD o ImageMagik.
-Se inhabilitará la miniaturización de imágenes.',
- 'config-no-uri' => "'''Error:''' No se pudo determinar el URI actual.
-Instalación abortada.",
- 'config-no-cli-uri' => "''' Advertencia ''': No se ha especificado ningún --scriptpath, por defecto, se usará: <code>$1</code> .",
- 'config-using-server' => 'Utilizando el nombre de servidor "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Utilizando la dirección URL del servidor "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Atención:''' Su directorio por defecto para las cargas, <code>$1</code>, es vulnerable a la ejecución de scripts arbitrarios.
-Aunque MediaWiki comprueba todos los archivos cargados por si hubiese amenazas de seguridad, es altamente recomendable [//www.mediawiki.org/wiki/Manual:Security#Upload_security cerrar esta vulnerabilidad de seguridad] antes de activar las cargas.",
- 'config-no-cli-uploads-check' => "'''Atención:''' Su directorio predeterminado para cargas (<code>$1</code>) no está comprobado para la vulnerabilidad
- de ejecución arbitraria de comandos script durante la instalación de CLI.",
- 'config-brokenlibxml' => 'El sistema tiene una combinación de versiones de PHP y de libxml2 que puede ser problemática y puede causar daños en datos ocultos de MediaWiki y otras aplicaciones web
-Actualizar a PHP 5.2.9 o posterior y a libxml2 2.7.3 o posterior ([//bugs.php.net/bug.php?id=45996 bug presentado con PHP]).
-Instalación anulada.',
- 'config-using531' => 'MediaWiki no puede utilizarse con PHP $1 debido a un error con los parámetros de referencia para <code>__call()</code> .
-Actualice el sistema a PHP 5.3.2 o superior, o vuelva a la versión PHP 5.3.0 para resolver este problema.
-Instalación anulada.',
- 'config-suhosin-max-value-length' => 'Suhosin está instalado y limita el parámetro <code>length</code> GET a $1 bytes.
-El componente ResourceLoader (gestor de recursos) de MediaWiki trabajará en este límite, pero eso perjudicará el rendimiento.
-Si es posible, deberías establecer <code>suhosin.get.max_value_length</code> en el valor 1024 o superior en <code>php.ini</code> y establecer <code>$wgResourceLoaderMaxQueryLength</code> en el mismo valor en <code>php.ini</code>.',
- 'config-db-type' => 'Tipo de base de datos',
- 'config-db-host' => 'Servidor de la base de datos:',
- 'config-db-host-help' => 'Si su servidor de base de datos está en otro servidor, escriba el nombre del host o su dirección IP aquí.
-Si está utilizando alojamiento web compartido, su proveedor de alojamiento debería darle el nombre correcto del servidor de alojamiento (host) en su documentación.
-Si va a instalarlo en un servidor Windows y utiliza MySQL, el uso de "localhost" como nombre del servidor puede no funcionar. Si no es así, intente poner "127.0.0.1" como dirección IP local.
-Si utiliza PostgreSQL, deje este campo en blanco para conectarse a través de un socket de Unix.',
- 'config-db-host-oracle' => 'TNS de la base de datos:',
- 'config-db-host-oracle-help' => 'Introduzca un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nombre de conexión local] válido; un archivo tnsnames.ora debe ser visible para esta instalación.<br />Si está utilizando bibliotecas de cliente 10g o más recientes también puede utilizar el método de asignación de nombres [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identifique este wiki',
- 'config-db-name' => 'Nombre de base de datos:',
- 'config-db-name-help' => 'Elija un nombre que identifique su wiki.
-No debe contener espacios.
-
-Si está utilizando alojamiento web compartido, su proveedor de alojamiento le dará un nombre específico de base de datos para que lo utilice, o bien le permitirá crear bases de datos a través de un panel de control.',
- 'config-db-name-oracle' => 'Esquema de base de datos:',
- 'config-db-account-oracle-warn' => 'Hay tres escenarios compatibles para la instalación de Oracle como base de datos back-end:
-
-Si desea crear una cuenta de base de datos como parte del proceso de instalación, por favor suministre una cuenta con función SYSDBA como cuenta de base de datos para la instalación y especifique las credenciales deseadas de la cuenta de acceso al web, de lo contrario puede crear manualmente la cuenta de acceso al web y suministrar sólo esa cuenta (si tiene los permisos necesarios para crear los objetos de esquema) o suministrar dos cuentas diferentes, una con privilegios de creación y otra con acceso restringido a la web
-
-La secuencia de comandos (script) para crear una cuenta con los privilegios necesarios puede encontrarse en el directorio "maintenance/oracle/" de esta instalación. Tenga en cuenta que utilizando una cuenta restringida desactivará todas las capacidades de mantenimiento con la cuenta predeterminada.',
- 'config-db-install-account' => 'Cuenta de usuario para instalación',
- 'config-db-username' => 'Nombre de usuario de base de datos:',
- 'config-db-password' => 'contraseña de base de datos:',
- 'config-db-password-empty' => 'Introduzca una contraseña para el nuevo usuario de base de datos: $1.
-Aunque es posible crear usuarios sin contraseña, esto no es seguro.',
- 'config-db-install-username' => 'Introduzca el nombre de usuario que se utilizará para conectarse a la base de datos durante el proceso de instalación.
-Este no es el nombre de usuario de la cuenta de MediaWiki; Este es el nombre de usuario para la base de datos.',
- 'config-db-install-password' => 'Introduzca la contraseña que se utilizará para conectarse a la base de datos durante el proceso de instalación.
-Esta no es la contraseña para la cuenta de MediaWiki; esta es la contraseña para la base de datos.',
- 'config-db-install-help' => 'Ingresar el nombre de usuario y la contraseña que será usada para conectar a la base de datos durante el proceso de instalación.',
- 'config-db-account-lock' => 'Usar el mismo nombre de usuario y contraseña durante operación normal',
- 'config-db-wiki-account' => 'Usar cuenta para operación normal',
- 'config-db-wiki-help' => 'Introduce el nombre de usuario y la contraseña que serán usados para acceder a la base de datos durante la operación normal del wiki.
-Si esta cuenta no existe y la cuenta de instalación tiene suficientes privilegios, se creará esta cuenta de usuario con los privilegios mínimos necesarios para la operación normal del wiki.',
- 'config-db-prefix' => 'Prefijo para las tablas de la base de datos:',
- 'config-db-prefix-help' => 'Si necesita compartir una base de datos entre múltiples wikis, o entre MediaWiki y otra aplicación web, puede optar por agregar un prefijo a todos los nombres de tabla para evitar conflictos.
-No utilice espacios.
-
-Normalmente se deja este campo vacío.',
- 'config-db-charset' => 'Conjunto de caracteres de la base de datos',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binario',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 retrocompatible UTF-8',
- 'config-charset-help' => "'''Atención:''' Si emplea '''backwards-compatible UTF-8''' en MySQL 4.1+ y posteriormente hace copia de seguridad de la base de datos con <code>mysqldump</code> , puede destruir todos los caracteres no-ASCII, ¡dañando irreversiblemente sus copias de seguridad!
-
-En '''modo binario''', MediaWiki almacena texto UTF-8 en la base de datos en campos binarios.
-Esto es más eficiente que el modo UTF-8 de MySQL, y le permite utilizar la gama completa de caracteres Unicode.
-En ''' modo UTF-8'' ', MySQL sabrá el juego de caracteres de sus datos y puede presentarlos y convertirlos apropiadamente,
-pero no le permitirá almacenar caracteres por encima del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plano multilingüe básico].",
- 'config-mysql-old' => 'Se necesita MySQL $1 o una versión más reciente. Tienes la versión $2.',
- 'config-db-port' => 'Puerto de la base de datos:',
- 'config-db-schema' => 'Esquema para MediaWiki',
- 'config-db-schema-help' => 'Estos esquemas usualmente estarán bien.
-Altéralos sólo si tienes la seguridad de que necesitas hacerlo.',
- 'config-pg-test-error' => "No se puede conectar a la base de datos '''$1''': $2",
- 'config-sqlite-dir' => 'Directorio de datos SQLite:',
- 'config-sqlite-dir-help' => "SQLite almacena todos los datos en un único archivo.
-
-El directorio que proporcione debe ser escribible por el servidor Web durante la instalación.
-
-'''No''' debería ser accesible a través de Internet, por eso no vamos a ponerlo en el sitio donde están los archivos PHP.
-
-El instalador escribirá un archivo <code>.htaccess</code> junto con él, pero si falla alguien podría tener acceso a la base de datos en bloque.
-Eso incluye los datos de usuario en bloque (direcciones de correo electrónico, las contraseñas con hash) así como revisiones eliminadas y otros datos restringidos del wiki.
-
-Considere la posibilidad de poner la base de datos en algún otro sitio, por ejemplo en <code>/var/lib/mediawiki/yourwiki</code> .",
- 'config-oracle-def-ts' => 'Espacio de tablas por defecto:',
- 'config-oracle-temp-ts' => 'Espacio de tablas temporal:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki es compatible con los siguientes sistemas de bases de datos:
-
-$1
-
-Si no encuentras en el listado el sistema de base de datos que estás intentando utilizar, sigue las instrucciones vinculadas arriba para habilitar la compatibilidad.',
- 'config-support-mysql' => '* $1 es la base de datos mayoritaria para MediaWiki y la que goza de mayor compatibilidad ([http://www.php.net/manual/es/mysql.installation.php cómo compilar PHP con compatibilidad MySQL])',
- 'config-support-postgres' => '$1 es un popular sistema de base de datos de código abierto, alternativa a MySQL. ([http://www.php.net/manual/es/pgsql.installation.php cómo compilar PHP con compatibilidad PostgreSQL]). Puede haber algunos defectos menores destacables, y no es recomendable para uso en un entorno de producción.',
- 'config-support-sqlite' => '* $1 es una base de datos ligera con gran compatibilidad con MediaWiki ([http://www.php.net/manual/es/pdo.installation.php cómo compilar PHP con compatibilidad SQLite usando PDO]).',
- 'config-support-oracle' => '* $1 es una base de datos comercial a nivel empresarial ([http://www.php.net/manual/es/oci8.installation.php cómo compilar PHP con compatibilidad con OCI8])',
- 'config-header-mysql' => 'Configuración de MySQL',
- 'config-header-postgres' => 'Configuración de PostgreSQL',
- 'config-header-sqlite' => 'Configuración de SQLite',
- 'config-header-oracle' => 'Configuración de Oracle',
- 'config-invalid-db-type' => 'Tipo de base de datos inválida',
- 'config-missing-db-name' => 'Debes introducir un valor para "Nombre de la base de datos"',
- 'config-missing-db-host' => 'Debe introducir un valor para "Servidor (host) de base de datos"',
- 'config-missing-db-server-oracle' => 'Debe introducir un valor para "TNS de la base de datos"',
- 'config-invalid-db-server-oracle' => 'El TNS de la base de datos, "$1", es inválido.Use sólo carateres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_) y guiones (-).Usa sólo caracteres ASCII: letras (a-z, A-Z), dígitos (0-9), guiones bajos (_) y puntos (.).', # Fuzzy
- 'config-invalid-db-name' => 'El nombre de la base de datos "$1" es inválido.
-Usa sólo caracteres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_)y guiones (-).',
- 'config-invalid-db-prefix' => 'El prefijo de la base de datos "$1" es inválido.
-Use sólo carateres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_) y guiones (-).',
- 'config-connection-error' => '$1.
-
-Verifique el servidor, el nombre de usuario y la contraseña, e intente de nuevo.',
- 'config-invalid-schema' => 'El esquema de la base de datos "$1" es inválido.
-Use sólo carateres ASCII: letras (a-z, A-Z), guarismos (0-9) y guiones bajos (_).',
- 'config-db-sys-create-oracle' => 'El instalador sólo admite el empleo de cuentas SYSDBA como método para crear una cuenta nueva.',
- 'config-db-sys-user-exists-oracle' => 'La cuenta de usuario "$1" ya existe. ¡SYSDBA sólo puede utilizarse para crear una nueva cuenta!',
- 'config-postgres-old' => 'Se necesita PostgreSQL $1 o una versión más reciente; tienes la versión $2.',
- 'config-sqlite-name-help' => 'Elige el nombre que identificará tu wiki.
-No uses espacios o guiones.
-Este nombre será usado como nombre del archivo de datos de SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'No se puede crear el directorio de datos <code><nowiki>$1</nowiki></code> , porque el directorio padre <code><nowiki>$2</nowiki></code> no es accesible en escritura por el servidor Web.
-
-El instalador ha determinado el usuario cuyo servidor Web se está ejecutando.
-Conceda permisos de escritura en el directorio <code><nowiki>$3</nowiki></code> para continuar.
-En un sistema Unix/Linux haga:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'No se puede crear el directorio de datos <code><nowiki>$1</nowiki></code> , porque el directorio padre <code><nowiki>$2</nowiki></code> no es accesible en escritura por el servidor Web.
-
-El programa de instalación no pudo determinar el usuario que se ejecuta en el servidor Web
-Conceda permisos de escritura en el directorio <code><nowiki>$3</nowiki></code> para continuar.
-En un sistema Unix/Linux haga:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Error al crear el directorio de datos "$1".
-Comprueba la ubicación e inténtalo de nuevo.',
- 'config-sqlite-dir-unwritable' => 'No se puede escribir en el directorio "$1".
-Modifica los permisos para que el servidor web pueda escribir en él y vuelve a intentarlo.',
- 'config-sqlite-connection-error' => '$1.
-
-Verifique el directório de datos y el nombre de la base de datos mostrada a continuación e inténtalo nuevamente.',
- 'config-sqlite-readonly' => 'El archivo <code>$1</code> no se puede escribir.',
- 'config-sqlite-cant-create-db' => 'No fue posible crear el archivo de la base de datos <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'El PHP no tiene compatibilidad FTS3. actualizando tablas a una versión anterior',
- 'config-can-upgrade' => "Esta base de datos contiene tablas de MediaWiki.
-Para actualizarlas a MediaWiki $1, haz clic en '''Continuar'''.",
- 'config-upgrade-done' => "Actualización completa.
-
-Usted puede ahora [ $1 empezar a usar su wiki].
-
-Si desea regenerar su archivo <code>LocalSettings.php</code> de archivo, haga clic en el botón de abajo.
-Esto '''no se recomienda''' a menos que esté teniendo problemas con su wiki.",
- 'config-upgrade-done-no-regenerate' => 'Actualización completa.
-
-Usted puede ahora [$1 empezar a usar su wiki].',
- 'config-regenerate' => 'Regenerar LocalSettings.php →',
- 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> ha fallado!',
- 'config-unknown-collation' => "'''Advertencia:''' La base de datos está utilizando una intercalación no reconocida.",
- 'config-db-web-account' => 'Cuenta de base de datos para acceso Web',
- 'config-db-web-help' => 'Elige el usuario y contraseña que el servidor Web usará para conectarse al servidor de la base de datos durante el fincionamiento normal del wiki.',
- 'config-db-web-account-same' => 'Utilizar la misma cuenta que en la instalación',
- 'config-db-web-create' => 'Crear la cuenta si no existe',
- 'config-db-web-no-create-privs' => 'La cuenta que has especificado para la instalación no tiene privilegios suficientes para crear una cuenta.
-La cuenta que especifiques aquí debe existir.',
- 'config-mysql-engine' => 'Motor de almacenamiento:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Atención''': Ha seleccionado MyISAM como motor de almacenamiento de MySQL, el cual no está recomendado para su uso con MediaWiki, porque:
- * apenas soporta accesos simultáneos debido al bloqueo de tablas
- * es más propenso a la corrupción que otros motores
- * el código MediaWiki no siempre controla MyISAM como debiera
-
-Si su instalación de MySQL soporta InnoDB, es muy recomendable que lo elija en su lugar.
-Si la instalación de MySQL no admite InnoDB, quizás es el momento de una actualización.",
- 'config-mysql-engine-help' => "'''InnoDB''' es casi siempre la mejor opción, dado que soporta bien los accesos simultáneos.
-
-'''MyISAM''' es más rápido en instalaciones de usuario único o de sólo lectura.
-Las bases de datos MyISAM tienden a corromperse más a menudo que las bases de datos InnoDB.",
- 'config-mysql-charset' => 'Conjunto de caracteres de la base de datos:',
- 'config-mysql-binary' => 'Binario',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "En '''modo binario''', MediaWiki almacena texto UTF-8 para la base de datos en campos binarios.
-Esto es más eficiente que el modo UTF-8 de MySQL y le permite utilizar la gama completa de caracteres Unicode.
-
-En '''modo UTF-8''', MySQL sabrá qué conjunto de caracteres emplean sus datos y puede presentarlos y convertirlos adecuadamente, pero no le permitirá almacenar caracteres por encima del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plano multilingüe básico].",
- 'config-site-name' => 'Nombre del wiki:',
- 'config-site-name-help' => 'Esto aparecerá en la barra de título del navegador y en varios otros lugares.',
- 'config-site-name-blank' => 'Ingresar un nombre de sitio.',
- 'config-project-namespace' => 'Espacio de nombre de proyecto:',
- 'config-ns-generic' => 'Proyecto',
- 'config-ns-site-name' => 'Igual como el nombre del wiki: $1',
- 'config-ns-other' => 'Otro (especificar)',
- 'config-ns-other-default' => 'MiWiki',
- 'config-project-namespace-help' => 'Siguiendo el ejemplo de Wikipedia, muchos wikis mantienen sus páginas de políticas separadas de sus páginas de contenido, en un "\'\'\'espacio de nombres del proyecto\'\'\'".
-
-Todos los títulos de página en este espacio de nombres comienzan con un determinado prefijo, que usted puede especificar aquí.
-Tradicionalmente, este prefijo se deriva del nombre del wiki, pero no puede contener caracteres de puntuación como "#" o ":".',
- 'config-ns-invalid' => 'El espacio de nombre especificado "<nowiki>$1</nowiki>" no es válido.
-Especifica un espacio de nombre de proyecto diferente.',
- 'config-ns-conflict' => 'El espacio de nombres especificado "<nowiki>$1</nowiki>" entra en conflicto con un espacio de nombres predeterminado de MediaWiki.
-Especifique un espacio de nombres de proyecto diferente.',
- 'config-admin-box' => 'Cuenta de administrador',
- 'config-admin-name' => 'Su nombre:',
- 'config-admin-password' => 'Contraseña:',
- 'config-admin-password-confirm' => 'Repita la contraseña:',
- 'config-admin-help' => 'Escribe aquí el nombre de usuario que desees, como por ejemplo "Pedro Bloggs".
-Este es el nombre que usarás para entrar al wiki.',
- 'config-admin-name-blank' => 'Introduce un nombre de usuario de administrador.',
- 'config-admin-name-invalid' => 'El nombre de usuario especificado "<nowiki>$1</nowiki>" no es válido.
-Especifique un nombre de usuario diferente.',
- 'config-admin-password-blank' => 'Introduzca una contraseña para la cuenta de administrador.',
- 'config-admin-password-same' => 'La contraseña no debe ser la misma que el nombre de usuario.',
- 'config-admin-password-mismatch' => 'Las dos contraseñas que ingresaste no coinciden.',
- 'config-admin-email' => 'Dirección de correo electrónico:',
- 'config-admin-email-help' => 'Introduce aquí un correo electrónico que te permita recibir mensajes de otros usuarios del wiki, vuelve a configurar tu contraseña y recibe notificaciones de cambios realizados a tus páginas vigiladas. Puedes dejar este campo vacío.',
- 'config-admin-error-user' => 'Error interno al crear un administrador con el nombre "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Error interno al establecer una contraseña para el administrador " <nowiki>$1</nowiki> ": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Ha introducido una dirección de correo electrónico inválida.',
- 'config-subscribe' => 'Suscribirse para recibir [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce avisos de nuevas versiones].',
- 'config-subscribe-help' => 'Esta es una lista de divulgación de bajo volumen para anuncios de lanzamiento de versiones nuevas, incluyendo anuncios de seguridad importantes.
-Te recomendamos suscribirte y actualizar tu instalación MediaWiki cada vez que se lance una nueva versión.',
- 'config-subscribe-noemail' => 'Ha intentado suscribirse a la lista de correo de anuncios de nuevos lanzamientos sin proporcionar una dirección de correo electrónico.
-Proporcione una dirección de correo electrónico si desea suscribirse a la lista de correo.',
- 'config-almost-done' => '¡Ya casi has terminado!
-Ahora puedes saltarte el resto de pasos e instalar el wiki con valores predeterminados.',
- 'config-optional-continue' => 'Hazme más preguntas.',
- 'config-optional-skip' => 'Ya estoy aburrido, sólo instala el wiki.',
- 'config-profile' => 'Perfil de derechos de usuario:',
- 'config-profile-wiki' => 'Wiki abierto',
- 'config-profile-no-anon' => 'Creación de cuenta requerida',
- 'config-profile-fishbowl' => 'Sólo editores autorizados',
- 'config-profile-private' => 'Wiki privado',
- 'config-profile-help' => "Los wikis funcionan mejor cuando dejas que los edite tanta gente como sea posible.
-En MediaWiki, es fácil revisar los cambios recientes y revertir los daños realizados por usuarios malintencionados o novatos.
-Sin embargo, muchos han encontrado que MediaWiki es útil para una amplia variedad de funciones, y a veces no es fácil convencer a todos de los beneficios de la forma wiki.
-Por lo tanto tienes la elección.
-
-Un '''{{int:config-profile-wiki}}''' permite que cualquiera pueda editar, sin siquiera iniciar sesión.
-Un wiki con '''{{int:config-profile-no-anon}}''' ofrece rendición de cuentas adicional, pero puede disuadir a colaboradores.
-
-El escenario '''{{int:config-profile-fishbowl}}''' permite editar a los usuarios autorizados, pero el público puede ver las páginas, incluyendo el historial.
-Un '''{{int:config-profile-private}}''' sólo permite ver páginas a los usuarios autorizados, el mismo grupo al que le está permitido editar.
-
-Configuraciones más complejas de derechos de usuario están disponibles después de la instalación, consulte [//www.mediawiki.org/wiki/Manual:User_rights esta entrada en el manual].", # Fuzzy
- 'config-license' => 'Copyright and licencia:',
- 'config-license-none' => 'Pie sin licencia',
- 'config-license-cc-by-sa' => 'Creative Commons Reconocimiento Compartir Igual',
- 'config-license-cc-by' => 'Creative Commons Reconocimiento',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Reconocimiento Compartir Igual no comercial',
- 'config-license-cc-0' => 'Creative Commons Zero (dominio público)',
- 'config-license-gfdl' => 'Licencia de documentación libre de GNU 1.3 o posterior',
- 'config-license-pd' => 'Dominio Público',
- 'config-license-cc-choose' => 'Selecciona una licencia personalizada de Creative Commons',
- 'config-license-help' => "Muchos wikis públicos ponen todas las contribuciones bajo una [http://freedomdefined.org/Definition licencia libre].
-Esto ayuda a crear un sentido de propiedad comunitaria y alienta la contribución a largo plazo.
-Esto no es generalmente necesario para un wiki privado o corporativo.
-
-Si desea poder utilizar texto de Wikipedia, y desea que Wikipedia pueda aceptar el texto copiado de tu wiki, debe elegir '''Creative Commons Reconocimiento Compartir Igual'''.
-
-Wikipedia utilizaba anteriormente la licencia de documentación libre de GNU (GFDL).
-La GFDL es una licencia válida, pero es difícil de entender.
-Tambié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 [http://www.php.net/manual/en/mail.configuration.php configuración PHP de correo electrónico] debe ser la correcta.
-Si no quieres la funcionalidad de correo electrónico, puedes desactivarla aquí.',
- 'config-email-user' => 'Habilitar correo electrónico de usuario a usuario',
- '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',
- 'config-email-usertalk-help' => 'Permitir a los usuarios recibir notificaciones de cambios en la página de discusión de usuario, si lo han activado en sus preferencias.',
- 'config-email-watchlist' => 'Activar notificación de alteraciones a la páginas vigiladas',
- 'config-email-watchlist-help' => 'Permitir a los usuarios recibir notificaciones de cambios en la páginas que vigilan, si lo han activado en sus preferencias.',
- 'config-email-auth' => 'Activar autenticación del correo electrónico',
- 'config-email-auth-help' => "Si esta opción está habilitada, los usuarios tienen que confirmar su dirección de correo electrónico mediante un enlace que se les envía a ellos cuando éstos lo establecen o lo cambian.
-Solo las direcciones de correo electrónico autenticadas pueden recibir correos electrónicos de otros usuarios o correos electrónicos de notificación de cambios.
-Esta opción está '''recomendada''' para wikis públicos debido a posibles abusos de las características del correo electrónico.",
- 'config-email-sender' => 'Dirección de correo electrónico de retorno:',
- 'config-email-sender-help' => 'Introduce la dirección de correo electrónico que será usada como dirección de retorno en los mensajes electrónicos de salida.
-Aquí llegarán los correos electrónicos que no lleguen a su destino.
-Muchos servidores de correo electrónico exigen que por lo menos la parte del nombre del dominio sea válida.',
- 'config-upload-settings' => 'Cargas de imágenes y archivos',
- 'config-upload-enable' => 'Habilitar la subida de archivos',
- 'config-upload-help' => 'La carga de archivos expone potencialmente su servidor a riesgos de seguridad.
-Para obtener más información, lea la [//www.mediawiki.org/wiki/Manual:Security sección de seguridad] en el manual.
-
-Para habilitar la carga de archivos, cambie el modo en el subdirectorio <code>images</code> bajo el directorio raíz de MediaWiki para que el servidor web pueda escribir en él.
-A continuación, habilite esta opción.',
- 'config-upload-deleted' => '*Directorio para los archivos eliminados:',
- 'config-upload-deleted-help' => 'Elige un directorio en el que guardar los archivos eliminados.
-Lo ideal es una carpeta no accesible desde la red.',
- 'config-logo' => 'URL del logo :',
- 'config-logo-help' => 'La apariencia por defecto de MediaWiki incluye espacio para un logotipo de 135x160 píxeles encima del menú de la barra lateral.
-Cargue una imagen de tamaño adecuado e introduzca la dirección URL aquí.
-
-Si no desea un logotipo, deje esta casilla en blanco.', # Fuzzy
- 'config-instantcommons' => 'Habilitar Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] es una característica que permite que los wikis puedan utilizar imágenes, sonidos y otros archivos multimedia que se encuentran en el sitio [//commons.wikimedia.org/ Wikimedia Commons].
-Para ello, MediaWiki requiere acceso a Internet.
-
-Para obtener más información sobre esta función, incluidas las instrucciones sobre cómo configurarlo para otras wikis distintas de Wikimedia Commons, consulte [//mediawiki.org/wiki/Manual:$wgForeignFileRepos el manual].',
- 'config-cc-error' => 'El selector de licencia de Creative Commons no dio resultado.
-Escribe el nombre de la licencia manualmente.',
- 'config-cc-again' => 'Elegir otra vez...',
- 'config-cc-not-chosen' => 'Elige la licencia Creative Commons que desees y haz clic en "continuar".',
- 'config-advanced-settings' => 'Configuración avanzada',
- 'config-cache-options' => 'Configuración de la caché de objetos:',
- 'config-cache-help' => 'El almacenamiento en caché de objetos se utiliza para mejorar la velocidad de MediaWiki mediante el almacenamiento en caché los datos usados más frecuentemente.
-A los sitios medianos y grandes se les recomienda que permitirlo. También es beneficioso para los sitios pequeños.',
- 'config-cache-none' => 'Sin almacenamiento en caché (no se pierde ninguna funcionalidad, pero la velocidad puede resentirse en sitios grandes)',
- 'config-cache-accel' => 'Almacenamiento en caché de objetos PHP (APC, XCache o WinCache)',
- 'config-cache-memcached' => 'Utilizar Memcached (necesita ser instalado y configurado aparte)',
- 'config-memcached-servers' => 'Servidores Memcached:',
- 'config-memcached-help' => 'Lista de direcciones IP que serán usadas por Memcached.
-Deben especificarse una por cada línea y especificar el puerto a utilizar. Por ejemplo:
-127.0.0.1:11211
-192.168.1.25:1234',
- 'config-memcache-needservers' => 'Ha seleccionado Memcached como su tipo de caché pero no especificó ninguno de los servidores.',
- 'config-memcache-badip' => 'Ha introducido una dirección IP no válida para Memcached: $1 .',
- 'config-memcache-noport' => 'No ha especificado un puerto a usar en el servidor Memcached: $1 .
-Si no conoce el puerto, el valor predeterminado es 11211.',
- 'config-memcache-badport' => 'Los números de puerto de Memcached deben estar entre $1 y $2.',
- 'config-extensions' => 'Extensiones',
- 'config-extensions-help' => 'Se ha detectado en tu directorio <code>./extensions</code> las extensiones listadas arriba.
-
-Puede que necesiten configuraciones adicionales, pero puedes habilitarlas ahora.',
- 'config-install-alreadydone' => "'''Aviso:''' Parece que ya habías instalado MediaWiki y estás intentando instalarlo nuevamente.
-Pasa a la próxima página, por favor.",
- 'config-install-begin' => 'Pulsando "{{int:config-continue}}", se iniciará la instalación de MediaWiki.
-Si todavía desea realizar algún cambio, pulse atrás.', # Fuzzy
- 'config-install-step-done' => 'hecho',
- 'config-install-step-failed' => 'falló',
- 'config-install-extensions' => 'Extensiones inclusive',
- 'config-install-database' => 'Configurando la base de datos',
- 'config-install-schema' => 'Creando el esquema',
- 'config-install-pg-schema-not-exist' => 'El esquema PostgreSQL no existe.',
- 'config-install-pg-schema-failed' => 'La creación de las tablas ha fallado.
-Asegúrate de que el usuario "$1" puede escribir en el esquema "$2".',
- 'config-install-pg-commit' => 'Validando los cambios',
- 'config-install-pg-plpgsql' => 'Comprobación de lenguaje PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Necesita instalar el lenguaje PL/pgSQL en la base de datos $1',
- 'config-pg-no-create-privs' => 'La cuenta especificada para la instalación no tiene suficientes privilegios para crear una cuenta.',
- 'config-pg-not-in-role' => 'La cuenta especificada para el usuario web ya existe.
-La cuenta especificada para la instalación no es de un superusuario y no es miembro del grupo de usuarios con acceso a la web, por lo que es incapaz de crear objetos pertenecientes al usuario web.
-
-MediaWiki requiere actualmente que las tablas sean propiedad del usuario web. Especifique otro nombre de cuenta web, o haga clic en "atrás" y especifique un usuario de instalación con los privilegios convenientes.',
- 'config-install-user' => 'Creando el usuario de la base de datos',
- 'config-install-user-alreadyexists' => 'El usuario "$1" ya existe',
- 'config-install-user-create-failed' => 'La creación del usuario "$1" falló: $2',
- 'config-install-user-grant-failed' => 'La concesión de permisos para el usuario "$1" ha fallado: $2',
- 'config-install-user-missing' => 'El usuario especificado "$1" no existe.',
- 'config-install-user-missing-create' => 'El usuario especificado "$1" no existe.
-Por favor, haga clic en la casilla "Crear cuenta" que aparece a continuación si desea crearlo.',
- 'config-install-tables' => 'Creando tablas',
- 'config-install-tables-exist' => "'''Advertencia''': Al parecer, las tablas de MediaWiki ya existen. Saltándose su creación.",
- 'config-install-tables-failed' => "'''Error''': La creación de las tablas falló con el siguiente error: $1",
- 'config-install-interwiki' => 'Llenando la tabla interwiki predeterminada',
- 'config-install-interwiki-list' => 'No se pudo encontrar el archivo <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Advertencia''': La tabla de interwikis parece ya contener entradas.
-Se omitirá la lista predeterminada.",
- 'config-install-stats' => 'Iniciando las estadísticas',
- 'config-install-keys' => 'Generación de claves secretas',
- 'config-insecure-keys' => "''' Atención:'' ' {{PLURAL:$2|Una clave de seguridad generada|Las claves de seguridad generadas}} ($1) durante la instalación no {{PLURAL:$2|es totalmente segura|son totalmente seguras}}. Considere {{PLURAL:$2| cambiarla|cambiarlas}} manualmente.",
- 'config-install-sysop' => 'Creando cuenta de usuario del administrador',
- 'config-install-subscribe-fail' => 'No se ha podido suscribir a mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL no está instalado y allow_url_fopen no está disponible.',
- 'config-install-mainpage' => 'Creando página principal con contenido predeterminado',
- 'config-install-extension-tables' => 'Creando las tablas para las extensiones habilitadas',
- 'config-install-mainpage-failed' => 'No se pudo insertar la página principal: $1',
- 'config-install-done' => "''' Felicidades!'''
-Ha instalado MediaWiki correctamente.
-
-El instalador ha generado un archivo<code>LocalSettings.php</code>.
-Contiene toda su configuración.
-
-Deberá descargarlo y ponerlo en la base de la instalación de wiki (el mismo directorio que index.php). Debería haber comenzado automáticamente la descarga.
-
-Si no comenzó la descarga, o si se ha cancelado, puede reiniciar la descarga haciendo clic en el enlace siguiente:
-$3
-
-'''Nota''': Si no haces esto ahora, este archivo de configuración generado no estará disponible para usted más tarde si sale de la instalación sin descargarlo.
-
-Cuando lo haya hecho, usted puede '''[$2 entrar en su wiki]'''.",
- 'config-download-localsettings' => 'Descargar archivo <code>LocalSettings.php</code>',
- 'config-help' => 'Ayuda',
- 'config-nofile' => 'El archivo "$1" no se pudo encontrar. ¿Se ha eliminado?',
- 'mainpagetext' => "'''MediaWiki ha sido instalado con éxito.'''",
- 'mainpagedocfooter' => 'Consulta la [//meta.wikimedia.org/wiki/Ayuda:Guía del usuario de contenidos] para obtener información sobre el uso del software wiki.
-
-== Empezando ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de ajustes de configuración]
-* [//www.mediawiki.org/wiki/Manual:FAQ/es FAQ de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de distribución de MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Regionalizar MediaWiki para tu idioma]',
-);
-
-/** español (formal) (español (formal))
- * @author Dferg
- */
-$messages['es-formal'] = array(
- 'mainpagedocfooter' => 'Consulte usted la [//meta.wikimedia.org/wiki/Ayuda:Contenido Guía de usuario] para obtener información sobre el uso del software wiki.
-
-== Empezando ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de ajustes de configuración]
-* [//www.mediawiki.org/wiki/Manual:FAQ/es FAQ de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de distribución de MediaWiki]', # Fuzzy
-);
-
-/** Estonian (eesti)
- * @author Avjoska
- * @author Pikne
- */
-$messages['et'] = array(
- 'config-information' => 'Teave',
- 'config-session-error' => 'Tõrge seansi alustamisel: $1',
- 'config-your-language' => 'Oma keel:',
- 'config-wiki-language' => 'Viki keel:',
- 'config-back' => '← Tagasi',
- 'config-continue' => 'Jätka →',
- 'config-page-language' => 'Keel',
- 'config-page-welcome' => 'Tere tulemast MediaWikisse!',
- 'config-page-dbconnect' => 'Andmebaasiga ühendamine',
- 'config-page-upgrade' => 'Olemasoleva installi uuendus',
- 'config-page-dbsettings' => 'Andmebaasi sätted',
- 'config-page-name' => 'Nimi',
- 'config-page-options' => 'Seaded',
- 'config-page-install' => 'Paigaldamine',
- 'config-page-complete' => 'Valmis!',
- 'config-page-restart' => 'Alusta installimist uuesti',
- 'config-page-readme' => 'Loe mind',
- 'config-page-copying' => 'Kopeerimine',
- 'config-page-upgradedoc' => 'Uuendamine',
- 'config-page-existingwiki' => 'Olemasolev viki',
- 'config-restart' => 'Jah, tee taaskäivitus',
- 'config-db-name' => 'Andmebaasi nimi:',
- 'config-db-username' => 'Andmebaasi kasutajanimi:',
- 'config-db-password' => 'Andmebaasi parool:',
- 'config-db-port' => 'Andmebaasi port:',
- 'config-invalid-db-type' => 'Vigane andmebaasi tüüp',
- 'config-site-name' => 'Viki nimi:',
- 'config-site-name-blank' => 'Sisestage lehekülje nimi.',
- 'config-project-namespace' => 'Projekti nimeruum:',
- 'config-ns-generic' => 'Projekt',
- 'config-admin-box' => 'Administraatorikonto',
- 'config-admin-name' => 'Sinu nimi:',
- 'config-admin-password' => 'Parool:',
- 'config-admin-password-confirm' => 'Parool uuesti:',
- 'config-admin-name-blank' => 'Sisesta administraatori kasutajanimi.',
- 'config-admin-password-blank' => 'Sisesta administraatorikonto parool.',
- 'config-admin-password-same' => 'Parool ei tohi kattuda kasutajanimega.',
- 'config-admin-password-mismatch' => 'Sisestatud kaks parooli ei lange kokku.',
- 'config-admin-email' => 'E-posti aadress:',
- 'config-admin-error-bademail' => 'Sisestasid vigase e-posti aadressi.',
- 'config-optional-continue' => 'Küsi minult veel küsimusi.',
- 'config-profile-private' => 'Eraviki',
- 'config-license' => 'Autoriõigus ja litsents:',
- 'config-license-none' => 'Litsentsijaluseta',
- 'config-license-cc-by-sa' => 'Creative Commonsi litsents "Autorile viitamine + jagamine samadel tingimustel"',
- 'config-license-cc-by' => 'Creative Commonsi litsents "Autorile viitamine"',
- 'config-license-cc-by-nc-sa' => 'Creative Commonsi litsents "Autorile viitamine + mitteäriline eesmärk + jagamine samadel tingimustel"',
- 'config-email-settings' => 'E-posti sätted',
- 'config-email-sender' => 'Saatja e-aadress:',
- 'config-logo' => 'Logo internetiaadress:',
- 'config-cc-again' => 'Vali uuesti...',
- 'config-extensions' => 'Lisad',
- 'config-install-step-done' => 'valmis',
- 'config-install-step-failed' => 'ebaõnnestus',
- 'config-install-user-alreadyexists' => 'Kasutaja "$1" on juba olemas',
- 'config-install-tables' => 'Tabelite loomine',
- 'config-help' => 'abi',
- 'mainpagetext' => "'''MediaWiki tarkvara on edukalt paigaldatud.'''",
- 'mainpagedocfooter' => 'Vikitarkvara kasutamise kohta leiad lisateavet [//meta.wikimedia.org/wiki/Help:Contents juhendist].
-
-== Alustamine ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Häälestussätete loend]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki KKK]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki versiooniuuenduste postiloend]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki lokaliseerimine]',
-);
-
-/** Basque (euskara)
- * @author An13sa
- * @author පසිඳු කාවින්ද
- */
-$messages['eu'] = array(
- 'config-desc' => 'MediaWiki instalatzailea',
- 'config-title' => 'MediaWiki $1 instalazioa',
- 'config-information' => 'Informazioa',
- 'config-session-error' => 'Saio hasierako errorea: $1',
- 'config-your-language' => 'Zure hizkuntza:',
- 'config-your-language-help' => 'Aukeratu instalazio prozesuan erabiliko den hizkuntza',
- 'config-wiki-language' => 'Wiki hizkuntza:',
- 'config-back' => '← Atzera',
- 'config-continue' => 'Jarraitu →',
- 'config-page-language' => 'Hizkuntza',
- 'config-page-welcome' => 'Ongi etorri MediaWikira!',
- 'config-page-dbconnect' => 'Datu-basera konektatu',
- 'config-page-dbsettings' => 'Datu-basearen ezarpenak',
- 'config-page-name' => 'Izena',
- 'config-page-options' => 'Aukerak',
- 'config-page-install' => 'Instalatu',
- 'config-page-complete' => 'Bukatua!',
- 'config-page-restart' => 'Instalazioa berriz hasi',
- 'config-page-readme' => 'Irakur nazazu',
- 'config-page-copying' => 'Kopiatzea',
- 'config-page-upgradedoc' => 'Eguneratu',
- 'config-restart' => 'Bai, berriz hasi',
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki nagusia]
-* [//www.mediawiki.org/wiki/Help:Contents Erabiltzaileentzako Gida]
-* [//www.mediawiki.org/wiki/Manual:Contents Administratzaileentzako Gida]
-* [//www.mediawiki.org/wiki/Manual:FAQ MEG]
-----
-* <doclink href=Readme>Irakur nazazu</doclink>
-* <doclink href=ReleaseNotes>Oharren argitalpena</doclink>
-* <doclink href=Copying>Kopiaketa</doclink>
-* <doclink href=UpgradeDoc>Eguneratzea</doclink>',
- 'config-env-php' => 'PHP $1 instalatuta dago.',
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] instalatuta dago',
- 'config-apc' => '[http://www.php.net/apc APC] instalatuta dago',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] instalatuta dago',
- 'config-diff3-bad' => 'GNU diff3 ez da aurkitu.',
- 'config-db-type' => 'Datu-base mota:',
- 'config-db-wiki-settings' => 'Wiki hau identifikatu',
- 'config-db-name' => 'Datu-base izena:',
- 'config-db-username' => 'Datu-base lankide izena:',
- 'config-db-password' => 'Datu-base pasahitza:',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 bitarra',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-header-mysql' => 'MySQL hobespenak',
- 'config-header-postgres' => 'PostgreSQL hobespenak',
- 'config-header-sqlite' => 'SQLite hobespenak',
- 'config-header-oracle' => 'Oracle hobespenak',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-binary' => 'Bitarra',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Wikiaren izena:',
- 'config-project-namespace' => 'Proiektuaren izen-tartea:',
- 'config-ns-generic' => 'Proiektua',
- 'config-ns-other' => 'Bestelakoa (zehaztu)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-admin-box' => 'Administratzaile kontua',
- 'config-admin-name' => 'Zure izena:',
- 'config-admin-password' => 'Pasahitza:',
- 'config-admin-password-confirm' => 'Pasahitza berriz:',
- 'config-admin-email' => 'E-posta helbidea:',
- 'config-profile-wiki' => 'Wiki tradizionala', # Fuzzy
- 'config-profile-private' => 'Wiki pribatua',
- 'config-license' => 'Copyright eta lizentzia:',
- 'config-license-pd' => 'Domeinu Askea',
- 'config-email-settings' => 'E-posta hobespenak',
- 'config-logo' => 'Logo URL:',
- 'config-extensions' => 'Luzapenak',
- 'config-install-step-done' => 'egina',
- 'config-help' => 'Laguntza',
- 'mainpagetext' => "'''MediaWiki arrakastaz instalatu da.'''",
- 'mainpagedocfooter' => 'Ikus [//meta.wikimedia.org/wiki/Help:Contents Erabiltzaile Gida] wiki softwarea erabiltzen hasteko informazio gehiagorako.
-
-== Nola hasi ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigurazio balioen zerrenda]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ (Maiz egindako galderak)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWikiren argitalpenen posta zerrenda]', # Fuzzy
-);
-
-/** Extremaduran (estremeñu)
- */
-$messages['ext'] = array(
- 'mainpagetext' => "'''MeyaGüiqui s'á istalau satihatoriamenti.'''",
- 'mainpagedocfooter' => "Consurta la [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pa sabel mas al tentu el huncionamientu el software güiqui.
-
-== Esminciandu ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Persian (فارسی)
- * @author Mjbmr
- */
-$messages['fa'] = array(
- 'config-desc' => 'نصب کنندهٔ ویکی‌مدیا',
- 'config-title' => 'نصب ویکی‌مدیا $1',
- 'config-information' => 'اطلاعات',
- 'config-localsettings-key' => 'کلید ارتقا:',
- 'config-your-language' => 'زبان شما:',
- 'config-wiki-language' => 'زبان ویکی:',
- 'config-back' => '→ بازگشت',
- 'config-continue' => 'ادامه ←',
- 'config-page-language' => 'زبان',
- 'config-page-welcome' => 'به مدیاویکی خوش آمدید!',
- 'config-page-dbconnect' => 'اتصال به پایگاه داده',
- 'config-page-name' => 'نام',
- 'config-page-options' => 'گزینه‌ها',
- 'config-page-install' => 'نصب',
- 'config-page-complete' => 'کامل!',
- 'config-page-readme' => 'مرا بخوان',
- 'config-page-releasenotes' => 'یادداشت‌های انتشار',
- 'config-page-copying' => 'تکثیر',
- 'config-page-upgradedoc' => 'ارتقا',
- 'config-page-existingwiki' => 'ویکی موجود',
- 'config-restart' => 'بله ، آن دوباره راه اندازی کن',
- 'config-sidebar' => '* [//www.mediawiki.org صفحهٔ اصلی مدیاویکی]
-* [//www.mediawiki.org/wiki/Help:Contents راهنمای کاربر]
-* [//www.mediawiki.org/wiki/Manual:Contents راهنمای مدیر]
-* [//www.mediawiki.org/wiki/Manual:FAQ پرسش‌های رایج]
-----
-* <doclink href=Readme>مرا بخوان</doclink>
-* <doclink href=ReleaseNotes>یادداشت‌های انتشار</doclink>
-* <doclink href=Copying>نسخه برداری</doclink>
-* <doclink href=UpgradeDoc>ارتقا</doclink>',
- 'config-env-php' => 'پی‌اچ‌پی $1 نصب شده است.',
- 'config-env-php-toolow' => 'پی‌اچ‌پی $1 نصب شده است.
-در هر صورت، مدیاویکی نیاز به پی‌اچ‌پی نسخهٔ $2 یا بالاتر دارد.',
- 'config-db-type' => 'نوع پایگاه اطلاعات:',
- 'config-db-host' => 'میزبان پایگاه اطلاعات:',
- 'config-db-username' => 'نام کاربری پایگاه اطلاعات:',
- 'config-db-password' => 'کلمه عبور پایگاه اطلاعات:',
- 'config-mysql-old' => 'مای‌اس‌کیو‌ال نسخهٔ $1 و یا بالاتر نیاز است، شما نسخهٔ $2 را دارید.',
- 'config-db-port' => 'درگاه پایگاه‌داده:',
- 'config-header-mysql' => 'تنظیمات مای‌اس‌کیو‌ال',
- 'config-connection-error' => '$1.
-
-میزبان، نام کاربری و گذرواژه را بررسی کنید و دوباره امتحان کنید.',
- 'config-mysql-binary' => 'دودویی',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'نام ویکی:',
- 'config-site-name-blank' => 'نام تارنما را وارد کنید.',
- 'config-project-namespace' => 'فضای نام پروژه:',
- 'config-ns-generic' => 'پروژه',
- 'config-ns-other-default' => 'ویکی‌من',
- 'config-admin-box' => 'حساب مدیر سیستم',
- 'config-admin-name' => 'نام شما:',
- 'config-admin-password' => 'کلمه عبور:',
- 'config-admin-password-confirm' => 'دوباره کلمه عبور:',
- 'config-admin-email' => 'پست الکترونیکی شما:',
- 'config-profile-private' => 'ویکی خصوصی',
- 'config-license' => 'حق تکثیر و مجوز:',
- 'config-license-none' => 'بدون پاورقی مجوز',
- 'config-license-pd' => 'دامنه عمومی',
- 'config-license-cc-choose' => 'انتخاب یک مجوز سفارشی عوام خلاق',
- 'config-email-settings' => 'تنظیمات پست الکترونیکی',
- 'config-upload-enable' => 'فعال سازی بارگذاری پرونده',
- 'config-logo' => 'نشانی نامواره:',
- 'config-extensions' => 'افزونه‌ها',
- 'config-install-step-done' => 'انجام شد',
- 'config-install-step-failed' => 'ناموفق بود',
- 'config-help' => 'راهنما',
- 'mainpagetext' => "'''نرم‌افزار ویکی با موفقیت نصب شد.'''",
- 'mainpagedocfooter' => 'از [//meta.wikimedia.org/wiki/Help:Contents راهنمای کاربران]
-برای استفاده از نرم‌افزار ویکی کمک بگیرید.
-
-== آغاز به کار ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings تنظیم پیکربندی]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki پرسش‌های متداول]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce فهرست پست الکترونیکی نسخه‌های مدیاویکی]', # Fuzzy
-);
-
-/** Finnish (suomi)
- * @author Beluga
- * @author Centerlink
- * @author Crt
- * @author Nike
- * @author Olli
- * @author Silvonen
- * @author Str4nd
- * @author VezonThunder
- * @author 아라
- */
-$messages['fi'] = array(
- 'config-desc' => 'MediaWiki-asennin',
- 'config-title' => 'MediaWikin version $1 asennus',
- 'config-information' => 'Tiedot',
- 'config-localsettings-upgrade' => '<code>LocalSettings.php</code>-tiedosto havaittiin.
-Kirjoita muuttujan <code>$wgUpgradeKey</code> arvo alla olevaan kenttään päivittääksesi asennuksen.
-Löydät sen <code>LocalSettings.php</code>-tiedostosta.',
- 'config-localsettings-cli-upgrade' => '<code>LocalSettings.php</code>-tiedosto havaittiin.
-Päivitä asennus suorittamalla <code>update.php</code>.',
- 'config-localsettings-key' => 'Päivitysavain',
- 'config-localsettings-badkey' => 'Antamasi avain on virheellinen.',
- 'config-upgrade-key-missing' => 'Havaittiin aiempi MediaWiki-asennus.
-Päivittääksesi tämän asennuksen lisää <code>LocalSettings.php</code>-tiedostosi loppuun seuraava rivi:
-
-$1',
- 'config-localsettings-incomplete' => 'Nykyinen <code>LocalSettings.php</code>-tiedosto näyttää olevan puutteellinen.
-Muuttujaa $1 ei ole asetettu.
-Muuta <code>LocalSettings.php</code>-tiedostoa siten, että muuttuja on asetettu ja napsauta »{{int:Config-continue}}».',
- 'config-localsettings-connection-error' => 'Virhe yhdistettäessä tietokantaan käyttäen tiedostossa <code>LocalSettings.php</code> tai <code>AdminSettings.php</code> määritettyjä asetuksia. Korjaa asetukset ja yritä uudelleen.
-
-$1',
- 'config-session-error' => 'Istunnon aloittaminen epäonnistui: $1',
- 'config-session-expired' => 'Istuntotietosi näyttävät olevan vanhentuneita.
-Istuntojen elinajaksi on määritelty $1.
-Voit muuttaa tätä asetusta vaihtamalla kohtaa <code>session.gc_maxlifetime</code> php.ini-tiedostossa.
-Käynnistä asennusprosessi uudelleen.',
- 'config-no-session' => 'Istuntosi tiedot menetettiin!
-Tarkista php.ini-tiedostosi ja varmista, että <code>session.save_path</code> on asetettu sopivaan kansioon.',
- 'config-your-language' => 'Asennuksen kieli',
- 'config-your-language-help' => 'Valitse kieli, jota haluat käyttää asennuksen ajan.',
- 'config-wiki-language' => 'Wikin kieli',
- 'config-wiki-language-help' => 'Valitse kieli, jota wikissä tullaan etupäässä käyttämään.',
- 'config-back' => '← Takaisin',
- 'config-continue' => 'Jatka →',
- 'config-page-language' => 'Kieli',
- 'config-page-welcome' => 'Tervetuloa MediaWikiin!',
- 'config-page-dbconnect' => 'Tietokantaan yhdistäminen',
- 'config-page-upgrade' => 'Olemassa olevan asennuksen päivitys',
- 'config-page-dbsettings' => 'Tietokannan asetukset',
- 'config-page-name' => 'Nimi',
- 'config-page-options' => 'Asetukset',
- 'config-page-install' => 'Asenna',
- 'config-page-complete' => 'Valmis!',
- 'config-page-restart' => 'Aloita asennus alusta',
- 'config-page-readme' => 'Lue minut',
- 'config-page-releasenotes' => 'Julkaisutiedot',
- 'config-page-copying' => 'Kopiointi',
- 'config-page-upgradedoc' => 'Päivittäminen',
- 'config-page-existingwiki' => 'Aikaisempi asennus',
- 'config-help-restart' => 'Haluatko poistaa kaikki annetut tiedot ja aloittaa asennuksen alusta?',
- 'config-restart' => 'Kyllä',
- 'config-welcome' => '=== Ympäristön tarkistukset ===
-Varmistetaan MediaWikin asennettavuus tähän ympäristöön.
-Sinun pitäisi antaa näiden tarkistusten tulokset, jos tarvitset apua asennuksen aikana.',
- 'config-sidebar' => '* [//www.mediawiki.org MediaWikin kotisivu]
-* [//www.mediawiki.org/wiki/Help:Contents Käyttöopas]
-* [//www.mediawiki.org/wiki/Manual:Contents Hallintaopas]
-* [//www.mediawiki.org/wiki/Manual:FAQ UKK]
-----
-* <doclink href=Readme>Lue minut</doclink>
-* <doclink href=ReleaseNotes>Julkaisutiedot</doclink>
-* <doclink href=Copying>Kopiointi</doclink>
-* <doclink href=UpgradeDoc>Päivittäminen</doclink>',
- 'config-env-good' => 'Asennusympäristö on tarkastettu.
-Voit asentaa MediaWikin.',
- 'config-env-bad' => 'Asennusympäristö on tarkastettu.
-Et voi asentaa MediaWikiä.',
- 'config-env-php' => 'PHP $1 on asennettu.',
- 'config-env-php-toolow' => 'PHP $1 on asennettu.
-MediaWiki vaatii PHP:n version $2 tai uudemman.',
- 'config-no-db' => 'Sopivaa tietokanta-ajuria ei löytynyt! Sinun täytyy asentaa tietokanta-ajurit PHP:lle.
-Seuraavat tietokantatyypit ovat tuettuja: $1.', # Fuzzy
- 'config-safe-mode' => "'''Varoitus:''' PHP:n [http://www.php.net/features.safe-mode safe mode] -tila on aktiivinen.
-Se voi aiheuttaa ongelmia erityisesti tiedostojen tallentamisen ja matemaattisten kaavojen kanssa.",
- 'config-pcre' => 'PCRE-tukimoduuli puuttuu.
-MediaWiki vaatii toimiakseen Perl-yhteensopivat säännölliset lausekkeet.',
- 'config-memory-bad' => "'''Varoitus:''' PHP:n <code>memory_limit</code> on $1.
-Tämä on luultavasti liian alhainen.
-Asennus saattaa epäonnistua!",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] on asennettu',
- 'config-apc' => '[http://www.php.net/apc APC] on asennettu.',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] on asennettu',
- 'config-diff3-bad' => 'GNU diff3:a ei löytynyt.',
- 'config-db-type' => 'Tietokannan tyyppi',
- 'config-db-host' => 'Tietokantapalvelin',
- 'config-db-name' => 'Tietokannan nimi',
- 'config-db-username' => 'Tietokannan käyttäjätunnus',
- 'config-db-password' => 'Tietokannan salasana',
- 'config-db-install-help' => 'Anna käyttäjätunnus ja salasana, joita käytetään asennuksen aikana.',
- 'config-db-account-lock' => 'Käytä samaa tunnusta ja salasanaa myös asennuksen jälkeen',
- 'config-db-prefix' => 'Tietokantataulujen etuliite',
- 'config-db-charset' => 'Tietokannan merkistö',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0, binääri',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0, UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0, taaksepäin yhteensopiva UTF-8',
- 'config-mysql-old' => 'MediaWiki tarvitsee MySQL:n version $1 tai uudemman. Nykyinen versio on $2.',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-header-mysql' => 'MySQL-asetukset',
- 'config-header-postgres' => 'PostgreSQL-asetukset',
- 'config-header-sqlite' => 'SQLite-asetukset',
- 'config-header-oracle' => 'Oracle-asetukset',
- 'config-invalid-db-type' => 'Virheellinen tietokantatyyppi',
- 'config-missing-db-name' => 'Kenttä »Tietokannan nimi» on pakollinen',
- 'config-invalid-db-name' => '”$1” ei kelpaa tietokannan nimeksi.
-Käytä ainoastaan kirjaimia (a-z, A-Z), numeroita (0-9), alaviivoja (_) ja tavuviivoja (-).',
- 'config-invalid-db-prefix' => '”$1” ei kelpaa tietokannan etuliitteeksi.
-Käytä ainoastaan kirjaimia (a-z, A-Z), numeroita (0-9), alaviivoja (_) ja tavuviivoja (-).',
- 'config-postgres-old' => 'MediaWiki tarvitsee PostgreSQL:n version $1 tai uudemman. Nykyinen versio on $2.',
- 'config-sqlite-name-help' => 'Valitse nimi, joka yksilöi tämän wikin.
-Älä käytä välilyöntejä tai viivoja.
-Nimeä käytetään SQLite-tietokannan tiedostonimessä.',
- 'config-sqlite-dir-unwritable' => 'Hakemistoon ”$1” kirjoittaminen epäonnistui.
-Muuta hakemiston käyttöoikeuksia siten, että palvelinohjelmisto voi kirjoittaa siihen ja yritä uudelleen.',
- 'config-sqlite-readonly' => 'Tiedostoon <code>$1</code> ei voi kirjoittaa.',
- 'config-sqlite-fts3-downgrade' => 'PHP:stä puuttuu FTS3-tuki. Poistetaan ominaisuus käytöstä tietokantatauluista.',
- 'config-upgrade-done' => "Päivitys valmis.
-
-Voit [$1 aloittaa wikin käytön].
-
-Napsauta alla olevaa painiketta, jos haluat luoda uudelleen <code>LocalSettings.php</code>-tiedoston.
-Tämä '''ei ole suositeltavaa''', jos wikissäsi ei ole ongelmia.",
- 'config-upgrade-done-no-regenerate' => 'Päivitys valmis.
-
-Voit [$1 aloittaa wikin käytön].',
- 'config-regenerate' => 'Luo LocalSettings.php uudelleen →',
- 'config-show-table-status' => 'Kysely <code>SHOW TABLE STATUS</code> epäonnistui!',
- 'config-mysql-engine' => 'Tallennusmoottori',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-binary' => 'Binääri',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Wikin nimi',
- 'config-site-name-blank' => 'Kirjoita sivuston nimi.',
- 'config-project-namespace' => 'Projektinimiavaruus',
- 'config-ns-generic' => 'Projekti',
- 'config-ns-site-name' => 'Sama kuin wikin nimi: $1',
- 'config-ns-other' => 'Muu (määritä)',
- 'config-admin-name' => 'Nimesi',
- 'config-admin-password' => 'Salasana',
- 'config-admin-password-confirm' => 'Salasana uudelleen',
- 'config-admin-name-blank' => 'Anna ylläpitäjän käyttäjänimi.',
- 'config-admin-email' => 'Sähköpostiosoite',
- 'config-admin-error-bademail' => 'Annoit virheellisen sähköpostiosoitteen.',
- 'config-almost-done' => 'Olet jo lähes valmis!
-Voit ohittaa jäljellä olevat määritykset ja asentaa wikin juuri nyt.',
- 'config-profile-wiki' => 'Avoin wiki',
- 'config-profile-no-anon' => 'Tunnuksen luonti vaaditaan',
- 'config-profile-private' => 'Yksityinen wiki',
- 'config-license' => 'Tekijänoikeus ja lisenssi:',
- 'config-license-pd' => 'Public domain',
- 'config-email-settings' => 'Sähköpostiasetukset',
- 'config-logo' => 'Logon URL-osoite',
- 'config-cc-again' => 'Valitse uudelleen...',
- 'config-extensions' => 'Laajennukset',
- 'config-install-step-done' => 'valmis',
- 'config-install-step-failed' => 'epäonnistui',
- 'config-install-user-alreadyexists' => 'Käyttäjä $1 on jo olemassa',
- 'config-install-interwiki-list' => 'Tiedostoa <code>interwiki.list</code> ei voitu lukea.',
- 'config-download-localsettings' => 'Lataa <code>LocalSettings.php</code>',
- 'config-help' => 'ohje',
- 'mainpagetext' => "'''MediaWiki on onnistuneesti asennettu.'''",
- 'mainpagedocfooter' => "Lisätietoja käytöstä on sivulla [//meta.wikimedia.org/wiki/Help:Contents User's Guide].
-
-=== Lisäohjeita ===
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Asetusten teko-ohjeita]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWikin FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Sähköpostilista, jolla tiedotetaan MediaWikin uusista versioista]
-
-=== Asetukset ===
-
-Tarkista, että alla olevat taivutusmuodot ovat oikein. Jos eivät, tee tarvittavat muutokset tiedostoon LocalSettings.php seuraavasti:
- \$wgGrammarForms['fi']['genitive']['{{SITENAME}}'] = '...';
- \$wgGrammarForms['fi']['partitive']['{{SITENAME}}'] = '...';
- \$wgGrammarForms['fi']['elative']['{{SITENAME}}'] = '...';
- \$wgGrammarForms['fi']['inessive']['{{SITENAME}}'] = '...';
- \$wgGrammarForms['fi']['illative']['{{SITENAME}}'] = '...';
-Taivutusmuodot: {{GRAMMAR:genitive|{{SITENAME}}}} (yön) – {{GRAMMAR:partitive|{{SITENAME}}}} (yötä) – {{GRAMMAR:elative|{{SITENAME}}}} (yöstä) – {{GRAMMAR:inessive|{{SITENAME}}}} (yössä) – {{GRAMMAR:illative|{{SITENAME}}}} (yöhön).",
-);
-
-/** Faroese (føroyskt)
- */
-$messages['fo'] = array(
- 'mainpagetext' => "'''Innlegging av Wiki-ritbúnaði væleydnað.'''",
-);
-
-/** French (français)
- * @author Aadri
- * @author Crochet.david
- * @author Gomoko
- * @author Grondin
- * @author Guillom
- * @author Hashar
- * @author IAlex
- * @author Jean-Frédéric
- * @author McDutchie
- * @author Peter17
- * @author Reedy
- * @author Sherbrooke
- * @author Urhixidur
- * @author Verdy p
- * @author Wyz
- * @author Yumeki
- * @author 아라
- */
-$messages['fr'] = array(
- 'config-desc' => 'Le programme d’installation de MediaWiki',
- 'config-title' => 'Installation de MediaWiki $1',
- 'config-information' => 'Informations',
- 'config-localsettings-upgrade' => 'Un fichier <code>LocalSettings.php</code> a été détecté.
-Pour mettre à jour cette installation, veuillez saisir la valeur de <code>$wgUpgradeKey</code> dans le champ ci-dessous.
-Vous la trouverez dans <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Un fichier <code>LocalSettings.php</code> a été détecté.
-Pour mettre à niveau cette installation, veuillez exécuter <code>update.php</code>',
- 'config-localsettings-key' => 'Clé de mise à jour :',
- 'config-localsettings-badkey' => 'La clé que vous avez fournie est incorrecte',
- 'config-upgrade-key-missing' => 'Une installation existante de MediaWiki a été détectée.
-
-Pour mettre à jour cette installation, veuillez ajouter la ligne suivante à la fin de votre fichier <code>LocalSettings.php</code>
-
-$1',
- 'config-localsettings-incomplete' => 'Le fichier <code>LocalSettings.php</code> existant semble être incomplet.
-La variable $1 n’est pas définie.
-Veuillez modifier <code>LocalSettings.php</code> de sorte que cette variable soit définie, puis cliquer sur « {{int:Config-continue}} ».',
- 'config-localsettings-connection-error' => 'Une erreur est survenue lors de la connexion à la base de données en utilisant la configuration spécifiée dans <code>LocalSettings.php</code> ou <code>AdminSettings.php</code>. Veuillez corriger cette configuration puis réessayer.
-
-$1',
- 'config-session-error' => 'Erreur lors du démarrage de la session : $1',
- 'config-session-expired' => "↓Les données de votre session semblent avoir expiré.
-Les sessions sont configurées pour une durée de $1.
-Vous pouvez l'augmenter en configurant <code>session.gc_maxlifetime</code> dans le fichier php.ini.
-Redémarrer le processus d'installation.",
- 'config-no-session' => 'Les données de votre session ont été perdues !
-Vérifiez votre fichier php.ini et assurez-vous que <code>session.save_path</code> contient le chemin d’un répertoire approprié.',
- 'config-your-language' => 'Votre langue :',
- 'config-your-language-help' => "Sélectionnez la langue à utiliser pendant le processus d'installation.",
- 'config-wiki-language' => 'Langue du wiki :',
- 'config-wiki-language-help' => 'Sélectionner la langue dans laquelle le wiki sera principalement écrit.',
- 'config-back' => '← Retour',
- 'config-continue' => 'Continuer →',
- 'config-page-language' => 'Langue',
- 'config-page-welcome' => 'Bienvenue sur MediaWiki !',
- 'config-page-dbconnect' => 'Se connecter à la base de données',
- 'config-page-upgrade' => 'Mettre à jour l’installation existante',
- 'config-page-dbsettings' => 'Paramètres de la base de données',
- 'config-page-name' => 'Nom',
- 'config-page-options' => 'Options',
- 'config-page-install' => 'Installer',
- 'config-page-complete' => 'Terminé !',
- 'config-page-restart' => 'Redémarrer l’installation',
- 'config-page-readme' => 'Lisez-moi',
- 'config-page-releasenotes' => 'Notes de version',
- 'config-page-copying' => 'Copie',
- 'config-page-upgradedoc' => 'Mise à jour',
- 'config-page-existingwiki' => 'Wiki existant',
- 'config-help-restart' => "Voulez-vous effacer toutes les données enregistrées que vous avez entrées et relancer le processus d'installation ?",
- 'config-restart' => 'Oui, le relancer',
- 'config-welcome' => '=== Vérifications liées à l’environnement ===
-Des vérifications de base vont maintenant être effectuées pour voir si cet environnement est adapté à l’installation de MediaWiki.
-Rappelez-vous d’inclure ces informations si vous recherchez de l’aide sur la manière de terminer l’installation.',
- 'config-copyright' => "=== Droit d'auteur et conditions ===
-
-$1
-
-Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier selon les termes de la Licence Publique Générale GNU telle que publiée par la Free Software Foundation (version 2 de la Licence, ou, à votre choix, toute version ultérieure).
-
-Ce programme est distribué dans l’espoir qu’il sera utile, mais '''sans aucune garantie''' : sans même les garanties implicites de '''commerciabilité''' ou d’'''adéquation à un usage particulier'''.
-Voir la Licence Publique Générale GNU pour plus de détails.
-
-Vous devriez avoir reçu <doclink href=Copying>une copie de la Licence Publique Générale GNU</doclink> avec ce programme ; dans le cas contraire, écrivez à la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ou [http://www.gnu.org/copyleft/gpl.html lisez-le en ligne].",
- 'config-sidebar' => '* [//www.mediawiki.org Accueil MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Guide de l’utilisateur]
-* [//www.mediawiki.org/wiki/Manual:Contents Guide de l’administrateur]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>Lisez-moi</doclink>
-* <doclink href=ReleaseNotes>Notes de publication</doclink>
-* <doclink href=Copying>Copie</doclink>
-* <doclink href=UpgradeDoc>Mise à jour</doclink>',
- 'config-env-good' => 'L’environnement a été vérifié.
-Vous pouvez installer MediaWiki.',
- 'config-env-bad' => 'L’environnement a été vérifié.
-Vous ne pouvez pas installer MediaWiki.',
- 'config-env-php' => 'PHP $1 est installé.',
- 'config-env-php-toolow' => 'PHP $1 est installé.
-Cependant, MediaWiki requiert PHP $2 ou plus haut.',
- 'config-unicode-using-utf8' => 'Utilisation de utf8_normalize.so par Brion Vibber pour la normalisation Unicode.',
- 'config-unicode-using-intl' => "Utilisation de [http://pecl.php.net/intl l'extension PECL intl] pour la normalisation Unicode.",
- 'config-unicode-pure-php-warning' => "'''Attention''': L'[http://pecl.php.net/intl extension PECL intl] n'est pas disponible pour la normalisation d’Unicode, retour à la version lente implémentée en PHP.
-Si votre site web sera très fréquenté, vous devriez lire ceci : [//www.mediawiki.org/wiki/Unicode_normalization_considerations ''Unicode normalization''] (en anglais).",
- 'config-unicode-update-warning' => "'''Attention''': La version installée du ''wrapper'' de normalisation Unicode utilise une vieille version de la [http://site.icu-project.org/ bibliothèque logicielle ''ICU Project''].
-Vous devriez faire une [//www.mediawiki.org/wiki/Unicode_normalization_considerations mise à jour] (texte en anglais) si l'usage d'Unicode vous semble important.",
- 'config-no-db' => "Impossible de trouver un pilote de base de données approprié ! Vous devez installer un pilote pour PHP. Ces types de bases de données sont reconnus : $1.
-
-Si vous êtes sur un site partagé, demandez à votre hébergeur d'installer un pilote de base de données approprié. Si vous avez compilé PHP, le configurer avec client de base de données activé, par exemple en insérant la directive <code>./configure --with-mysql</code>.
-
-Si vous avez installé PHP d'une distribution Debian ou Ubuntu, vous devez aussi installer le module <code>php5-mysql</code>.",
- 'config-outdated-sqlite' => "'''Attention''': vous avez SQLite $1, qui est inférieur à la version minimale requise $2. SQLite sera indisponible.",
- 'config-no-fts3' => "'''Attention :''' SQLite est compilé sans le module [//sqlite.org/fts3.html FTS3] ; les fonctions de recherche ne seront pas disponibles sur ce moteur.",
- 'config-register-globals' => "'''Attention : l'option <code>[http://php.net/register_globals register_globals]</code> de PHP est activée.'''
-'''Désactivez-la si vous le pouvez.'''
-MediaWiki fonctionnera, mais votre serveur sera exposé à de potentielles failles de sécurité.",
- 'config-magic-quotes-runtime' => "'''Erreur fatale : [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] est activé !'''
-Cette option corrompt les données de manière imprévisible.
-Vous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
- 'config-magic-quotes-sybase' => "'''Erreur fatale : [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybasee] est activé !'''
-Cette option corrompt les données de manière imprévisible.
-Vous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
- 'config-mbstring' => "'''Erreur fatale : [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] est activé !'''
-Cette option provoque des erreurs et peut corrompre les données de manière imprévisible.
-Vous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
- 'config-ze1' => "'''Erreur fatale : [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mod] est activé !'''
-Cette option provoque des bugs horribles avec MediaWiki.
-Vous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
- 'config-safe-mode' => "'''Attention : le « [http://www.php.net/features.safe-mode safe mode] » est activé !'''
-Ceci peut causer des problèmes, en particulier si vous utilisez le téléversement de fichiers et le support de <code>math</code>.",
- 'config-xml-bad' => 'Le module XML de PHP est manquant.
-MediaWiki requiert des fonctions de ce module et ne fonctionnera pas avec cette configuration.
-Si vous êtes sous Mandrake, installez le paquet php-xml.',
- 'config-pcre' => 'Le module de support PCRE semble manquer.
-MediaWiki requiert les fonctions d’expressions rationnelles compatibles avec Perl.',
- 'config-pcre-no-utf8' => "'''Erreur fatale''': Le module PCRE de PHP semble être compilé sans le support PCRE_UTF8.
-MédiaWiki nécessite 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' => "'''Attention :''' Le paramètre <code>memory_limit</code> de PHP est à $1.
-Cette valeur est probablement trop faible.
-Il est possible que l’installation échoue !",
- 'config-ctype' => "'''Fatal ''': PHP doit être compilé avec le support pour l'[http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
- 'config-json' => "'''Erreur fatale :''' PHP a été compilé sans le support de JSON.
-Vous devez soit installez l’extension JSON de PHP ou l’extension [http://pecl.php.net/package/jsonc PECL jsonc] avant d’installer MediaWiki.
-* L’extension PHP est comprise dans Red Hat Enterprise Linux (CentOS) 5 et 6, mais doit être activée dans <code>/etc/php.ini</code> ou <code>/etc/php.d/json.ini</code>.
-* Certaines distributions Linux après mai 2013 ne comprennent pas l’extension PHP, mais oint mis à la place l’extension PECL sous al forme <code>php5-json</code> ou <code>php-pecl-jsonc</code>.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] est installé',
- 'config-apc' => '[http://www.php.net/apc APC] est installé',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] est installé',
- 'config-no-cache' => "'''Attention :''' Impossible de trouver [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
-La mise en cache d'objets n'est pas activée.",
- 'config-mod-security' => "'''Attention''': Votre serveur web a [http://modsecurity.org/ mod_security] activé. S&il est mal configuré, cela peut poser des problèmes à MediaWiki ou à d'autres applications qui permettent aux utilisateurs de publier un contenu quelconque.
-Reportez-vous à [http://modsecurity.org/documentation/ la documentation de mod_security] ou contactez le support de votre hébergeur si vous rencontrez des erreurs aléatoires.",
- 'config-diff3-bad' => 'GNU diff3 introuvable.',
- 'config-git' => 'Logiciel de contrôle de version Git trouvé : <code>$1</code>.',
- 'config-git-bad' => 'Logiciel de contrôle de version Git non trouvé.',
- 'config-imagemagick' => "ImageMagick trouvé : <code>$1</code>.
-La miniaturisation d'images sera activée si vous activez le téléversement de fichiers.",
- 'config-gd' => "La bibliothèque graphique GD intégrée a été trouvée.
-La miniaturisation d'images sera activée si vous activez le téléversement de fichiers.",
- 'config-no-scaling' => "Impossible de trouver la bibliothèque GD ou ImageMagick.
-La miniaturisation d'images sera désactivé.",
- 'config-no-uri' => "'''Erreur :''' Impossible de déterminer l'URI du script actuel.
-Installation avortée.",
- 'config-no-cli-uri' => "'''Attention''': Aucun --scriptpath n'a été spécifié; <code>$1</code> sera utilisé par défaut",
- 'config-using-server' => 'Utilisation du nom de serveur "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Utilisation de l\'URL de serveur "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Attention:''' Votre répertoire par défaut pour les téléchargements, <code>$1</code>, est vulnérable, car il peut exécuter n'importe quel script.
-Bien que MediaWiki vérifie tous les fichiers téléchargés, il est fortement recommandé de [//www.mediawiki.org/wiki/Manual:Security#Upload_security fermer cette vulnérabilité de sécurité] (texte en anglais) avant d'activer les téléchargements.",
- 'config-no-cli-uploads-check' => "'''Attention:''' Votre répertoire par défaut pour les imports(<code>$1</code>) n'est pas contrôlé concernant la vulnérabilité d'exécution de scripts arbitraires lors de l'installation CLI.",
- 'config-brokenlibxml' => 'Votre système utilise une combinaison de versions de PHP et libxml2 qui est boguée et peut engendrer des corruptions cachées de données dans MediaWiki et d’autres applications web.
-Veuillez mettre à jour votre système vers PHP 5.2.9 ou plus récent et libxml2 2.7.3 ou plus récent ([//bugs.php.net/bug.php?id=45996 bogue déposé auprès de PHP]).
-Installation interrompue.',
- 'config-using531' => 'MediaWiki ne peut pas être utilisé avec PHP $1 à cause d’un bogue affectant les paramètres passés par référence à <code>__call()</code>.
-Veuillez mettre à jour votre système vers PHP 5.3.2 ou plus récent ou revenir à PHP 5.3.0 pour résoudre ce problème.
-Installation interrompue.',
- 'config-suhosin-max-value-length' => 'Suhosin est installé et limite la <code>longueur</code> du paramètre GET à $1 octets.
-Le composant ResourceLoader de MediaWiki va répondre en respectant cette limite, mais ses performances seront dégradées. Si possible, vous devriez définir <code>suhosin.get.max_value_length</code> à 1024 ou plus dans le fichier <code>php.ini</code>, et fixer <code>$wgResourceLoaderMaxQueryLength</code> à la même valeur dans <code>LocalSettings.php</code>.',
- 'config-db-type' => 'Type de base de données :',
- 'config-db-host' => 'Nom d’hôte de la base de données :',
- 'config-db-host-help' => 'Si votre serveur de base de données est sur un serveur différent, saisissez ici son nom d’hôte ou son adresse IP.
-
-Si vous utilisez un hébergement mutualisé, votre hébergeur doit vous avoir fourni le nom d’hôte correct dans sa documentation.
-
-Si vous installez sur un serveur Windows et utilisez MySQL, « localhost » peut ne pas fonctionner comme nom de serveur. S’il ne fonctionne pas, essayez « 127.0.0.1 » comme adresse IP locale.
-
-Si vous utilisez PostgreSQL, laissez ce champ vide pour vous connecter via un socket Unix.',
- 'config-db-host-oracle' => 'Nom TNS de la base de données :',
- 'config-db-host-oracle-help' => 'Entrez un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nom de connexion locale] valide ; un fichier tnsnames.ora doit être visible par cette installation.<br /> Si vous utilisez les bibliothèques clientes version 10g ou plus récentes, vous pouvez également utiliser la méthode de nommage [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identifier ce wiki',
- 'config-db-name' => 'Nom de la base de données :',
- 'config-db-name-help' => "Choisissez un nom qui identifie votre wiki.
-Il ne doit pas contenir d'espaces.
-
-Si vous utilisez un hébergement web partagé, votre hébergeur vous fournira un nom spécifique de base de données à utiliser, ou bien vous permet de créer des bases de données via un panneau de contrôle.",
- 'config-db-name-oracle' => 'Schéma de base de données :',
- 'config-db-account-oracle-warn' => "Il existe trois scénarios pris en charge pour l’installation d'Oracle comme backend de base :
-
-Si vous souhaitez créer un compte de base de données dans le cadre de la procédure d’installation, veuillez fournir un compte avec le rôle de SYSDBA comme compte de base de données pour l’installation et spécifiez les informations d’identification souhaitées pour le compte d'accès au web, sinon vous pouvez créer le compte d’accès web manuellement et fournir uniquement ce compte (si elle a exigé des autorisations nécessaires pour créer les objets de schéma) ou fournir deux comptes différents, l’un avec les privilèges de créer et une restreinte pour l’accès web.
-
-Un script pour créer un compte avec des privilèges requis peut être trouvé dans le répertoire « entretien/oracle/ » de cette installation. N’oubliez pas que le fait de l’utilisation d’un compte limité désactive toutes les fonctionnalités d’entretien avec le compte par défaut.",
- 'config-db-install-account' => "Compte d'utilisateur pour l'installation",
- 'config-db-username' => 'Nom d’utilisateur de la base de données :',
- 'config-db-password' => 'Mot de passe de la base de données :',
- 'config-db-password-empty' => "Veuillez entrer un mot de passe pour le nouvel compte de la base de données : $1.
-Bien qu'il soit possible de créer un compte sans mot de passe, ce n'est pas recommandé pour des questions de sécurité.",
- 'config-db-install-username' => "Entrez le nom d’utilisateur qui sera utilisé pour se connecter à la base de données pendant le processus d'installation. Il ne s’agit pas du nom d’utilisateur du compte MediaWiki, mais du nom d’utilisateur pour votre base de données.",
- 'config-db-install-password' => "Entrez le mot de passe qui sera utilisé pour se connecter à la base de données pendant le processus d'installation. Il ne s’agit pas du mot de passe du compte MediaWiki, mais du mot de passe pour votre base de données.",
- 'config-db-install-help' => "Entrez le nom d'utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le processus d'installation.",
- 'config-db-account-lock' => "Utiliser le même nom d'utilisateur et le même mot de passe pendant le fonctionnement habituel",
- 'config-db-wiki-account' => "Compte d'utilisateur pour le fonctionnement habituel",
- 'config-db-wiki-help' => "Entrez le nom d'utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le fonctionnement habituel du wiki.
-Si le compte n'existe pas, et le compte d'installation dispose de privilèges suffisants, ce compte d'utilisateur sera créé avec les privilèges minimum requis pour faire fonctionner le wiki.",
- 'config-db-prefix' => 'Préfixe des tables de la base de données :',
- 'config-db-prefix-help' => "Si vous avez besoin de partager une base de données entre plusieurs wikis, ou entre MediaWiki et une autre application Web, vous pouvez choisir d'ajouter un préfixe à tous les noms de table pour éviter les conflits.
-Ne pas utiliser des espaces.
-
-Ce champ est généralement laissé vide.",
- 'config-db-charset' => 'Jeu de caractères de la base de données',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binaire',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 rétrocompatible UTF-8',
- 'config-charset-help' => "'''Attention:''' Si vous utilisez ''backwards-compatible UTF-8'' sur MySQL 4.1+, et ensuite sauvegardez la base de données avec <code>mysqldump</code>, cela peut détruire tous les caractères non-ASCII, ce qui rend inutilisable vos copies de sauvegarde de façon irréversible !
-
-En ''mode binaire'', MediaWiki stocke le texte UTF-8 dans des champs binaires de la base de données. C'est plus efficace que le ''mode UTF-8'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
-En ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pourra présenter et convertir les données de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
- 'config-mysql-old' => 'MySQL $1 ou version ultérieure est requis, vous avez $2.',
- 'config-db-port' => 'Port de la base de données :',
- 'config-db-schema' => 'Schéma pour MediaWiki',
- 'config-db-schema-help' => "Les schémas ci-dessus sont généralement corrects.
-Ne les changez que si vous êtes sûr que c'est nécessaire.",
- 'config-pg-test-error' => "Impossible de se connecter à la base de données '''$1''' : $2",
- 'config-sqlite-dir' => 'Dossier des données SQLite :',
- 'config-sqlite-dir-help' => "SQLite stocke toutes les données dans un fichier unique.
-
-Le répertoire que vous inscrivez doit être accessible en écriture par le serveur lors de l'installation.
-
-Il '''ne faut pas''' qu'il soit accessible via le web, c'est pourquoi il n'est pas à l'endroit où vos fichiers PHP sont.
-
-L'installateur écrira un fichier <code>.htaccess</code> en même temps, mais s'il y a échec, quelqu'un peut accéder à votre base de données.
-Cela comprend les données des utilisateurs (adresses de courriel, mots de passe hachés) ainsi que des révisions supprimées et d'autres données confidentielles du wiki.
-
-Envisagez de placer la base de données ailleurs, par exemple dans <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => "Espace de stockage (''tablespace'') par défaut :",
- 'config-oracle-temp-ts' => "Espace de stockage (''tablespace'') temporaire :",
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => "MediaWiki supporte ces systèmes de bases de données :
-
-$1
-
-Si 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 le support.",
- 'config-support-mysql' => '* $1 est le premier choix pour MediaWiki et est mieux pris en charge ([http://www.php.net/manual/en/mysql.installation.php how to compile PHP with MySQL support])',
- 'config-support-postgres' => "* $1 est un système de base de données populaire et ''open source'' qui peut être une alternative à MySQL ([http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]). Il peut contenir quelques bogues mineurs et n'est pas recommandé dans un environnement de production.",
- 'config-support-sqlite' => '* $1 est un système de base de données léger qui est bien supporté. ([http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], utilise PDO)',
- 'config-support-oracle' => '* $1 est un système commercial de gestion de base de données d’entreprise. ([http://www.php.net/manual/en/oci8.installation.php Comment compiler PHP avec le support OCI8])',
- 'config-header-mysql' => 'Paramètres de MySQL',
- 'config-header-postgres' => 'Paramètres de PostgreSQL',
- 'config-header-sqlite' => 'Paramètres de SQLite',
- 'config-header-oracle' => 'Paramètres d’Oracle',
- 'config-invalid-db-type' => 'Type de base de données non valide',
- 'config-missing-db-name' => 'Vous devez saisir une valeur pour « Nom de la base de données »',
- 'config-missing-db-host' => "Vous devez entrer une valeur pour « l'hôte de la base de données »",
- 'config-missing-db-server-oracle' => 'Vous devez saisir une valeur pour le « Nom TNS de la base de données »',
- 'config-invalid-db-server-oracle' => 'Le nom TNS de la base de données (« $1 ») est invalide.
-Utilisez uniquement la chaîne "TNS Name" ou "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Méthodes de nommage Oracle])',
- 'config-invalid-db-name' => 'Nom de la base de données invalide (« $1 »).
-Il ne peut contenir que des lettres latines (a-z, A-Z), des chiffres (0-9), des caractères de soulignement (_) et des tirets (-).',
- 'config-invalid-db-prefix' => 'Préfixe de la base de données non valide « $1 ».
-Il ne peut contenir que des lettres latines (a-z, A-Z), des chiffres (0-9), des caractères de soulignement (_) et des tirets (-).',
- 'config-connection-error' => '$1.
-
-Vérifier le nom d’hôte, le nom d’utilisateur et le mot de passe ci-dessous puis réessayer.',
- 'config-invalid-schema' => 'Schéma invalide pour MediaWiki « $1 ».
-Utilisez seulement des lettres latines (a-z, A-Z), des chiffres (0-9) et des caractères de soulignement (_).',
- 'config-db-sys-create-oracle' => "L'installateur ne reconnaît que les compte SYSDBA lors de la création d'un nouveau compte.",
- 'config-db-sys-user-exists-oracle' => 'Le compte « $1 » existe déjà. Un SYSDBA peut seulement servir à créer un nouveau compte.',
- 'config-postgres-old' => 'PostgreSQL $1 ou version ultérieure est requis, vous avez $2.',
- 'config-sqlite-name-help' => "Choisir un nom qui identifie votre wiki.
-Ne pas utiliser des espaces ou des traits d'union.
-Il sera utilisé pour le fichier de données SQLite.",
- 'config-sqlite-parent-unwritable-group' => "Impossible de créer le répertoire de données <nowiki><code>$1</code></nowiki>, parce que le répertoire parent <nowiki><code>$2</code></nowiki> n'est pas accessible en écriture par le serveur Web.
-
-L'utilisateur du serveur web est connu.
-Rendre le répertoire <nowiki><code>$3</code></nowiki> accessible en écriture pour continuer.
-Sur un système UNIX/Linux, saisir :
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>",
- 'config-sqlite-parent-unwritable-nogroup' => "Impossible de créer le répertoire de données <nowiki><code>$1</code></nowiki>, parce que le répertoire parent <nowiki><code>$2</code></nowiki> n'est pas accessible en écriture par le serveur Web.
-
-L'utilisateur du serveur web est inconnu.
-Rendre le répertoire <nowiki><code>$3</code></nowiki> globalement accessible en écriture pour continuer.
-Sur un système UNIX/Linux, saisir :
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>",
- 'config-sqlite-mkdir-error' => "Erreur de création du répertoire de données « $1 ».
-Vérifiez l'emplacement et essayez à nouveau.",
- 'config-sqlite-dir-unwritable' => "Impossible d'écrire dans le répertoire « $1 ».
-Changer les permissions de sorte que le serveur peut y écrire et essayez à nouveau.",
- 'config-sqlite-connection-error' => '$1.
-
-Vérifier le répertoire des données et le nom de la base de données ci-dessous et réessayer.',
- 'config-sqlite-readonly' => "Le fichier <code>$1</code> n'est pas accessible en écriture.",
- 'config-sqlite-cant-create-db' => 'Impossible de créer le fichier de base de données <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP ne vient pas avec FTS3, les tables sont diminuées.',
- 'config-can-upgrade' => "Il y a des tables MediaWiki dans cette base de données.
-Pour les mettre au niveau de MediaWiki $1, cliquez sur '''Continuer'''.",
- 'config-upgrade-done' => "Mise à jour complétée.
-
-Vous pouvez maintenant [$1 commencer à utiliser votre wiki].
-
-Si vous souhaitez régénérer votre fichier <code>LocalSettings.php</code>, cliquez sur le bouton ci-dessous.
-Ce '''n'est pas recommandé''' sauf si vous rencontrez des problèmes avec votre wiki.",
- 'config-upgrade-done-no-regenerate' => 'Mise à jour terminée.
-
-Vous pouvez maintenant [$1 commencer à utiliser votre wiki].',
- 'config-regenerate' => 'Regénérer LocalSettings.php →',
- 'config-show-table-status' => 'Échec de la requête <code>SHOW TABLE STATUS</code> !',
- 'config-unknown-collation' => "'''Attention:''' La base de données effectue un classement alphabétique (''collation'') inconnu.",
- 'config-db-web-account' => "Compte de la base de données pour l'accès Web",
- 'config-db-web-help' => "Sélectionnez le nom d'utilisateur et le mot de passe que le serveur web utilisera pour se connecter au serveur de base de données pendant le fonctionnement habituel du wiki.",
- 'config-db-web-account-same' => "Utilisez le même compte que pour l'installation",
- 'config-db-web-create' => "Créez le compte s'il n'existe pas déjà",
- 'config-db-web-no-create-privs' => "Le compte que vous avez spécifié pour l'installation n'a pas de privilèges suffisants pour créer un compte.
-Le compte que vous spécifiez ici doit déjà exister.",
- 'config-mysql-engine' => 'Moteur de stockage :',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "''' Avertissement ''': vous avez sélectionné MyISAM comme moteur de stockage pour MySQL, ce qui n'est pas recommandé pour une utilisation avec MediaWiki, parce que:
- * il supporte à peine la simultanéité en raison de verrouillage de table
- * il est plus sujet à la corruption que les autres moteurs
- * le codebase MediaWiki ne gère pas toujours MyISAM comme il se doit
-Si votre installation MySQL supporte InnoDB, il est fortement recommandé que vous le choisissez plutôt. Si votre installation MySQL ne supporte pas les tables InnoDB, il est peut-être temps de faire une mise à niveau.",
- 'config-mysql-only-myisam-dep' => "'''Attention :''' MyISAM est le seul moteur de stockage disponible pour MySQL qui ne soit pas recommandé pour une utilsiation avec MédiaWiki, car :
-* il supporte très peu les accès concurrents à cause du verrouillage des tables
-* il est plus sujet à corruption que les autres moteurs
-* le code de base de MédiaWiki ne gère pas toujours MyISAM comme il faudrait
-
-Votre installation MySQL ne supporte pas InnoDB ; il est peut-être temps de la mettre à jour.",
- 'config-mysql-engine-help' => "'''InnoDB''' est presque toujours la meilleure option, car il supporte bien l'[http://fr.wikipedia.org/wiki/Ordonnancement_dans_les_syst%C3%A8mes_d%27exploitation ordonnancement].
-
-'''MyISAM''' peut être plus rapide dans les installations monoposte ou en lecture seule. Les bases de données MyISAM ont tendance à se corrompre plus souvent que celles d'InnoDB.",
- 'config-mysql-charset' => 'Jeu de caractères de la base de données :',
- 'config-mysql-binary' => 'Binaire',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "En ''mode binaire'', MediaWiki stocke le texte au format UTF-8 dans la base de données. C'est plus efficace que le ''UTF-8 mode'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
-
-En ''mode binaire'', MediaWiki stocke le texte UTF-8 dans des champs binaires de la base de données. C'est plus efficace que le ''mode UTF-8'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
-En ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pourra présenter et convertir les données de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
- 'config-site-name' => 'Nom du wiki :',
- 'config-site-name-help' => 'Il apparaîtra dans la barre de titre du navigateur et en divers autres endroits.',
- 'config-site-name-blank' => 'Entrez un nom de site.',
- 'config-project-namespace' => 'Espace de noms du projet :',
- 'config-ns-generic' => 'Projet',
- 'config-ns-site-name' => 'Même nom que le wiki : $1',
- 'config-ns-other' => 'Autre (préciser)',
- 'config-ns-other-default' => 'MonWiki',
- 'config-project-namespace-help' => "Suivant l'exemple de Wikipédia, plusieurs wikis gardent leurs pages de politique séparées de leurs pages de contenu, dans un ''espace de noms'' propre.
-Tous les titres de page de cet espace de noms commence par un préfixe défini, que vous pouvez spécifier ici.
-Traditionnellement, ce préfixe est dérivé du nom du wiki, mais il ne peut contenir des caractères de ponctuation tels que « # » ou « : ».",
- 'config-ns-invalid' => "L'espace de noms spécifié « <nowiki>$1</nowiki> » n'est pas valide.
-Spécifiez un espace de noms pour le projet.",
- 'config-ns-conflict' => "L'espace de noms spécifié « <nowiki>$1</nowiki> » est en conflit avec un espace de noms par défaut de MediaWiki.
-Choisir un autre espace de noms.",
- 'config-admin-box' => 'Compte administrateur',
- 'config-admin-name' => 'Votre nom :',
- 'config-admin-password' => 'Mot de passe :',
- 'config-admin-password-confirm' => 'Saisir à nouveau le mot de passe :',
- 'config-admin-help' => "Entrez votre nom d'utilisateur préféré ici, par exemple « Jean Blogue ».
-C'est le nom que vous utiliserez pour vous connecter au wiki.",
- 'config-admin-name-blank' => "Entrez un nom d'administrateur.",
- 'config-admin-name-invalid' => "Le nom d'utilisateur spécifié « <nowiki>$1</nowiki> » n'est pas valide.
-Indiquez un nom d'utilisateur différent.",
- 'config-admin-password-blank' => 'Entrez un mot de passe pour le compte administrateur.',
- 'config-admin-password-same' => "Le mot de passe doit être différent du nom d'utilisateur.",
- 'config-admin-password-mismatch' => 'Les deux mots de passe que vous avez saisis ne correspondent pas.',
- 'config-admin-email' => 'Adresse de courriel :',
- 'config-admin-email-help' => "Entrez une adresse de courriel ici pour vous permettre de recevoir des courriels d'autres utilisateurs du wiki, réinitialiser votre mot de passe, et être informé des modifications apportées aux pages de votre liste de suivi. Vous pouvez laisser ce champ vide.",
- 'config-admin-error-user' => "Erreur interne lors de la création d'un administrateur avec le nom « <nowiki>$1</nowiki> ».",
- 'config-admin-error-password' => "Erreur interne lors de l'inscription d'un mot de passe pour l'administrateur « <nowiki>$1</nowiki> » : <pre>$2</pre>",
- 'config-admin-error-bademail' => 'Vous avez entré une adresse de courriel invalide',
- 'config-subscribe' => "Abonnez-vous à la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce liste d'annonce des nouvelles versions] (la page peut afficher le texte en français).",
- 'config-subscribe-help' => "Il s'agit d'une liste de diffusion à faible volume utilisée servant à annoncer les nouvelles versions, y compris les versions améliorant la sécurité du logiciel.
-Vous devriez y souscrire et mettre à jour votre version de MediaWiki lorsque de nouvelles versions sont publiées.",
- 'config-subscribe-noemail' => "Vous avez essayé de vous abonner à la liste de diffusion des communiqués, sans fournir une adresse courriel ! S'il vous plaît, fournir une adresse électronique si vous souhaitez vous abonner à la liste de diffusion.",
- 'config-almost-done' => 'Vous avez presque fini !
-Vous pouvez passer la configuration restante et installer immédiatement le wiki.',
- 'config-optional-continue' => 'Me poser davantage de questions.',
- 'config-optional-skip' => 'J’en ai assez, installer simplement le wiki.',
- 'config-profile' => 'Profil des droits d’utilisateurs :',
- 'config-profile-wiki' => 'Wiki ouvert',
- 'config-profile-no-anon' => 'Création de compte requise',
- 'config-profile-fishbowl' => 'Éditeurs autorisés seulement',
- 'config-profile-private' => 'Wiki privé',
- 'config-profile-help' => "Les wikis fonctionnent mieux lorsque vous laissez le plus de personnes possible le modifier.
-Avec MediaWiki, il est facile de vérifier les modifications récentes et de révoquer tout dommage créé par des utilisateurs débutants ou mal intentionnés.
-
-Cependant, de nombreuses autres utilisations ont été trouvées au logiciel et il n’est pas toujours facile de convaincre tout le monde des bénéfices de l’esprit wiki.
-Vous avez donc le choix.
-
-Le modèle '''{{int:config-profile-wiki}}''' autorise toute personne à modifier, y compris sans s’identifier.
-'''{{int:config-profile-no-anon}}''' fournit plus de contrôle, par l’identification, mais peut rebuter les contributeurs occasionnels.
-
-'''{{int:config-profile-fishbowl}}''' autorise la modification par les utilisateurs approuvés, mais le public peut toujours lire les pages et leur historique.
-'''{{int:config-profile-private}}''' n’autorise que les utilisateurs approuvés à voir et modifier les pages.
-
-Des configurations de droits d’utilisateurs plus complexes sont disponibles après l'installation, voir la [//www.mediawiki.org/wiki/Manual:User_rights page correspondante du manuel].",
- 'config-license' => "Droits d'auteur et licence :",
- 'config-license-none' => 'Aucune licence en bas de page',
- 'config-license-cc-by-sa' => "Creative Commons attribution partage à l'identique",
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons paternité – non commercial – partage à l’identique',
- 'config-license-cc-0' => 'Creative Commons Zero (domaine public)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 ou ultérieure',
- 'config-license-pd' => 'Domaine public',
- 'config-license-cc-choose' => 'Sélectionner une licence Creative Commons personnalisée',
- 'config-license-help' => "Beaucoup de wikis publics mettent l'ensemble des contributions sous [http://freedomdefined.org/Definition/Fr licence libre].
-Cela contribue à créer un sentiment d'appartenance dans leur communauté et encourage les contributions sur le long terme.
-Ce n'est généralement pas nécessaire pour un wiki privé ou d'entreprise.
-
-Si vous souhaitez utiliser des textes de Wikipédia, et souhaitez que Wikipédia réutilise des textes de votre wiki, vous devriez choisir la [http://creativecommons.org/licenses/by-sa/3.0/deed.fr licence ''Creative Commons Attribution Share Alike''] (CC-by-sa).
-
-Wikipédia a déjà été publié selon les termes de la [http://fr.wikipedia.org/wiki/Licence_de_documentation_libre_GNU ''GNU Free Documentation License''] (GFDL).
-C'est une licence valide, mais elle est difficile à comprendre. De plus, elle possède des caractéristiques qui rendent difficiles la réutilisation.",
- '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 [http://www.php.net/manual/en/mail.configuration.php configurer des paramètres PHP] (texte en anglais).
-Si 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',
- 'config-email-usertalk-help' => 'Permet aux utilisateurs de recevoir une notification en cas de modification de leurs pages de discussion, si cela est activé dans leurs préférences.',
- 'config-email-watchlist' => 'Activer la notification de la liste de suivi',
- 'config-email-watchlist-help' => "Permet aux utilisateurs de recevoir des notifications à propos des pages qu'ils ont en suivi (si cette préférence est activée).",
- 'config-email-auth' => "Activer l'authentification par courriel",
- 'config-email-auth-help' => "Si cette option est activée, les utilisateurs doivent confirmer leur adresse de courriel en utilisant l'hyperlien envoyé à chaque fois qu'ils la définissent ou la modifient.
-Seules les adresses authentifiées peuvent recevoir des courriels des autres utilisateurs ou lorsqu'il y a des notifications de modification.
-L'activation de cette option est '''recommandée''' pour les wikis publics en raison d'abus potentiels des fonctionnalités de courriels.",
- 'config-email-sender' => 'Adresse de courriel de retour :',
- 'config-email-sender-help' => "Entrez l'adresse de courriel à utiliser comme adresse de retour des courriels sortant.
-Les courriels rejetés y seront envoyés.
-De nombreux serveurs de courriels exigent au moins un [http://fr.wikipedia.org/wiki/Nom_de_domaine nom de domaine] valide.",
- 'config-upload-settings' => 'Téléchargement des images et des fichiers',
- 'config-upload-enable' => 'Activer le téléchargement des fichiers',
- 'config-upload-help' => "Le téléchargement des fichiers expose votre serveur à des risques de sécurité.
-Pour plus d'informations, lire la section [//www.mediawiki.org/wiki/Manual:Security ''Security''] du manuel d'installation (en anglais).
-
-Pour autoriser le téléchargement des fichiers, modifier le mode du sous-répertoire <code>images</code> qui se situe sous le répertoire racine de MediaWiki.
-Ensuite, activez cette option.",
- 'config-upload-deleted' => 'Répertoire pour les fichiers supprimés :',
- 'config-upload-deleted-help' => 'Choisissez un répertoire qui servira à archiver les fichiers supprimés.
-Idéalement, il ne devrait pas être accessible depuis le web.',
- 'config-logo' => 'URL du logo :',
- 'config-logo-help' => 'L’habillage par défaut de MediaWiki comprend l’espace pour un logo de 135x160 pixels au-dessus de la barre de menu latérale.
-Téléchargez une image de la taille appropriée, et entrez son URL ici.
-
-Vous pouvez utiliser <code>$wgStylePath</code> ou <code>$wgScriptPath</code> si votre logo est relatif à ces chemins.
-
-Si vous ne voulez pas de logo, laissez cette case vide.',
- 'config-instantcommons' => "Activer ''InstantCommons''",
- 'config-instantcommons-help' => "[//www.mediawiki.org/wiki/InstantCommons InstantCommons] est un service qui permet d'utiliser les images, les sons et les autres médias disponibles sur le site [//commons.wikimedia.org/ Wikimedia Commons].
-Pour se faire, il faut que MediaWiki accède à Internet.
-
-Pour plus d'informations sur ce service, y compris les instructions sur la façon de le configurer pour d'autres wikis que Wikimedia Commons, consultez le [//mediawiki.org/wiki/Manual:\$wgForeignFileRepos manuel] (en anglais).",
- 'config-cc-error' => "Le sélection d'une licence ''Creative Commons'' n'a donné aucun résultat.
-Entrez le nom de la licence manuellement.",
- 'config-cc-again' => 'Choisissez à nouveau...',
- 'config-cc-not-chosen' => "Choisissez une licence ''Creative Commons'' et cliquez sur « Continuer ».",
- 'config-advanced-settings' => 'Configuration avancée',
- 'config-cache-options' => 'Paramètres pour la mise en cache des objets:',
- 'config-cache-help' => "La mise en cache des objets améliore la vitesse de MediaWiki en mettant en cache les données fréquemment utilisées.
-Les sites de taille moyenne à grande sont fortement encouragés à l'activer. Les petits sites y verront également des avantages.",
- 'config-cache-none' => 'Aucune mise en cache (aucune fonctionnalité supprimée, mais la vitesse peut changer sur les wikis importants)',
- 'config-cache-accel' => 'Mise en cache des objets PHP (APC, XCache ou WinCache)',
- 'config-cache-memcached' => 'Utiliser Memcached (nécessite une installation et une configuration supplémentaires)',
- 'config-memcached-servers' => 'serveurs pour Memcached :',
- 'config-memcached-help' => 'Liste des adresses IP à utiliser pour Memcached.
-Elles doivent être séparés par des virgules et vous devez spécifier le port à utiliser. Par exemple :
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Vous avez sélectionné Memcached comme type de cache, mais ne précisez pas de serveur.',
- 'config-memcache-badip' => 'Vous avez entré une adresse IP invalide pour Memcached: $1.',
- 'config-memcache-noport' => "Vous n'avez pas entré un port pour le serveur Memcached : $1.
-Si vous ne le connaissez pas, la valeur par défaut est 11211.",
- 'config-memcache-badport' => 'Les numéros de port de Memcached sont situés entre $1 et $2.',
- 'config-extensions' => 'Extensions',
- 'config-extensions-help' => 'Les extensions énumérées ci-dessus ont été détectées dans votre répertoire <code>./extensions</code>.
-
-Elles peuvent nécessiter une configuration supplémentaire, mais vous pouvez les activer maintenant',
- 'config-install-alreadydone' => "'''Attention''': Vous semblez avoir déjà installé MediaWiki et tentez de l'installer à nouveau.
-S'il vous plaît, allez à la page suivante.",
- 'config-install-begin' => 'En appuyant sur {{int:config-continue}}, vous commencerez l\'installation de MediaWiki.
-Si vous voulez apporter des modifications, appuyez sur "{{int:config-back}}".',
- 'config-install-step-done' => 'fait',
- 'config-install-step-failed' => 'échec',
- 'config-install-extensions' => 'Inclusion des extensions',
- 'config-install-database' => 'Création de la base de données',
- 'config-install-schema' => 'Création de schéma',
- 'config-install-pg-schema-not-exist' => "Le schéma PostgreSQL n'existe pas",
- 'config-install-pg-schema-failed' => "Échec lors de la création des tables.
-Assurez-vous que l'utilisateur « $1 » peut écrire selon le schéma « $2 ».",
- 'config-install-pg-commit' => 'Validation des modifications',
- 'config-install-pg-plpgsql' => 'Vérification du langage PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Vous devez installer le langage PL/pgSQL dans la base de données $1',
- 'config-pg-no-create-privs' => "Le compte que vous avez spécifié pour l'installation n'a pas suffisamment de privilèges pour créer un compte.",
- 'config-pg-not-in-role' => "Le compte que vous avez spécifié pour l'utilisateur web existe déjà !
-Le compte que vous avez spécifié pour l'installation n'est pas un super-utilisateur et n'est pas membre du rôle de l'internaute, il est donc incapable de créer des objets appartenant à l'utilisateur web.!
-
-MediaWiki exige actuellement que les tableaux soient possédés par un utilisateur web. S'il vous plaît, spécifier un autre nom de compte web, ou cliquez sur \"retour\" et spécifier un utilisateur avec les privilèges suffisants.",
- 'config-install-user' => "Création d'un utilisateur de la base de données",
- 'config-install-user-alreadyexists' => "L'utilisateur « $1 » existe déjà.",
- 'config-install-user-create-failed' => "Échec lors de la création de l'utilisateur « $1 » : $2",
- 'config-install-user-grant-failed' => "Échec lors de l'ajout de permissions à l'utilisateur « $1 » : $2",
- 'config-install-user-missing' => 'L\'utilisateur "$1" n\'existe pas.',
- 'config-install-user-missing-create' => 'L\'utilisateur "$1" n\'existe pas !
-S\'il vous plaît, cocher "Compte de créer" dans la case ci-dessous si vous voulez le créer.',
- 'config-install-tables' => 'Création des tables',
- 'config-install-tables-exist' => "'''Avertissement:''' Les tables MediaWiki semblent déjà exister.
-Création omise.",
- 'config-install-tables-failed' => "'''Erreur:''' échec lors de la création de la table avec l'erreur suivante: $1",
- 'config-install-interwiki' => 'Remplissage par défaut de la table des interwikis',
- 'config-install-interwiki-list' => 'Impossible de trouver le fichier <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Attention:''' La table des interwikis semble déjà contenir des entrées.
-La liste par défaut ne sera pas inscrite.",
- 'config-install-stats' => 'Initialisation des statistiques',
- 'config-install-keys' => 'Génération de la clé secrète',
- 'config-insecure-keys' => "'''Avertissement''' : {{PLURAL:$2|Une clé de sécurité générée ($1) pendant l'installation n'est pas complètement sécuritaire. Envisagez de la modifier manuellement.|Des clés de sécurité générées ($1) pendant l'installation ne sont pas complètement sécuritaires. Envisagez de les modifier manuellement.}}",
- 'config-install-sysop' => 'Création du compte administrateur',
- 'config-install-subscribe-fail' => "Impossible de s'abonner à mediawiki-announce : $1",
- 'config-install-subscribe-notpossible' => 'cURL n’est pas installé et allow_url_fopen n’est pas disponible.',
- 'config-install-mainpage' => 'Création de la page principale avec un contenu par défaut',
- 'config-install-extension-tables' => 'Création de tables pour les extensions activées',
- 'config-install-mainpage-failed' => 'Impossible d’insérer la page principale: $1',
- 'config-install-done' => "'''Félicitations!'''
-Vous avez réussi à installer MediaWiki.
-
-Le programme d'installation a généré <code>LocalSettings.php</code>, un fichier qui contient tous les paramètres de configuration.
-
-Si le téléchargement n'a pas été offert, ou que vous l'avez annulé, vous pouvez démarrer à nouveau le téléchargement en cliquant ce lien :
-
-$3
-
-'''Note''': Si vous ne le faites pas maintenant, ce fichier de configuration généré ne sera pas disponible plus tard si vous quittez l'installation sans le télécharger.
-
-Lorsque c'est fait, vous pouvez '''[$2 accéder à votre wiki]'''.",
- 'config-download-localsettings' => 'Télécharger <code>LocalSettings.php</code>',
- 'config-help' => 'aide',
- 'config-nofile' => 'Le fichier « $1 » est introuvable. A-t-il été supprimé ?',
- 'config-extension-link' => 'Saviez-vous que votre wiki supporte [//www.mediawiki.org/wiki/Manual:Extensions des extensions] ?
-
-Vous pouvez consulter les [//www.mediawiki.org/wiki/Category:Extensions_by_category extensions par catégorie] ou la [//www.mediawiki.org/wiki/Extension_Matrix Matrice des extensions] pourvoir la liste complète des extensions.',
- 'mainpagetext' => "'''MediaWiki a été installé avec succès.'''",
- 'mainpagedocfooter' => 'Consultez le [//meta.wikimedia.org/wiki/Aide:Contenu Guide de l’utilisateur] pour plus d’informations sur l’utilisation de ce logiciel de wiki.
-
-== Pour démarrer ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste des paramètres de configuration]
-* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ sur MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste de discussion sur les distributions de MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Adaptez MediaWiki dans votre langue]',
-);
-
-/** Cajun French (français cadien)
- */
-$messages['frc'] = array(
- 'mainpagetext' => "'''Vous avez bien installé MediaWiki.'''",
- 'mainpagedocfooter' => 'Lisez la [//meta.wikimedia.org/wiki/Help:Contents Guide des Useurs] pour apprendre à user le wiki software.
-
-== Pour Commencer ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Réglage]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki: Questions Souvent Posées]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki Liste à Malle]', # Fuzzy
-);
-
-/** Franco-Provençal (arpetan)
- * @author ChrisPtDe
- */
-$messages['frp'] = array(
- 'config-desc' => 'La programeria d’enstalacion de MediaWiki',
- 'config-title' => 'Enstalacion de MediaWiki $1',
- 'config-information' => 'Enformacions',
- 'config-localsettings-key' => 'Cllâf de misa a jorn :',
- 'config-session-error' => 'Èrror pendent l’emmodâ de la sèance : $1',
- 'config-your-language' => 'Voutra lengoua :',
- 'config-wiki-language' => 'Lengoua du vouiqui :',
- 'config-back' => '← Retôrn',
- 'config-continue' => 'Continuar →',
- 'config-page-language' => 'Lengoua',
- 'config-page-welcome' => 'Benvegnua dessus MediaWiki !',
- 'config-page-dbconnect' => 'Sè branchiér a la bâsa de balyês',
- 'config-page-upgrade' => 'Betar a jorn l’enstalacion ègzistenta',
- 'config-page-dbsettings' => 'Paramètres de la bâsa de balyês',
- 'config-page-name' => 'Nom',
- 'config-page-options' => 'Chouèx',
- 'config-page-install' => 'Enstalar',
- 'config-page-complete' => 'Chavonâ !',
- 'config-page-restart' => 'Tornar emmodar l’enstalacion',
- 'config-page-readme' => 'Liéséd-mè',
- 'config-page-releasenotes' => 'Notes de publecacion',
- 'config-page-copying' => 'Copia',
- 'config-page-upgradedoc' => 'Misa a jorn',
- 'config-page-existingwiki' => 'Vouiqui ègzistent',
- 'config-env-php' => 'PHP $1 est enstalâ.',
- 'config-env-php-toolow' => 'PHP $1 est enstalâ.
-Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
- 'config-memory-raised' => 'Lo paramètre <code>memory_limit</code> de PHP ére a $1, portâ a $2.',
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] est enstalâ',
- 'config-apc' => '[http://www.php.net/apc APC] est enstalâ',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] est enstalâ',
- 'config-diff3-bad' => 'GNU diff3 entrovâblo.',
- 'config-db-type' => 'Tipo de bâsa de balyês :',
- 'config-db-host' => 'Hôto de la bâsa de balyês :',
- 'config-db-host-oracle' => 'TNS de la bâsa de balyês :',
- 'config-db-wiki-settings' => 'Identifiar cél vouiqui',
- 'config-db-name' => 'Nom de la bâsa de balyês :',
- 'config-db-name-oracle' => 'Plan de bâsa de balyês :',
- 'config-db-install-account' => 'Compto usanciér por l’enstalacion',
- 'config-db-username' => 'Nom d’usanciér de la bâsa de balyês :',
- 'config-db-password' => 'Contresegno de la bâsa de balyês :',
- 'config-db-wiki-account' => 'Compto usanciér por l’opèracion normala',
- 'config-db-prefix' => 'Prèfixo de les trâbles de la bâsa de balyês :',
- 'config-db-charset' => 'Juè de caractèros de la bâsa de balyês',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binèro',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 rètrocompatiblo UTF-8',
- 'config-mysql-old' => 'MySQL $1 ou ben ples novél est nècèssèro, vos avéd $2.',
- 'config-db-port' => 'Pôrt de la bâsa de balyês :',
- 'config-db-schema' => 'Plan por MediaWiki',
- 'config-pg-test-error' => "Empossiblo de sè branchiér a la bâsa de donâs '''$1''' : $2",
- 'config-sqlite-dir' => 'Dossiér de les balyês SQLite :',
- 'config-oracle-def-ts' => "Èspâço de stocâjo (''tablespace'') per dèfôt :",
- 'config-oracle-temp-ts' => "Èspâço de stocâjo (''tablespace'') temporèro :",
- 'config-header-mysql' => 'Paramètres de MySQL',
- 'config-header-postgres' => 'Paramètres de PostgreSQL',
- 'config-header-sqlite' => 'Paramètres de SQLite',
- 'config-header-oracle' => 'Paramètres d’Oracle',
- 'config-invalid-db-type' => 'Tipo de bâsa de balyês envalido',
- 'config-missing-db-name' => 'Vos dête buchiér una valor por « Nom de la bâsa de balyês »',
- 'config-missing-db-host' => 'Vos dête buchiér una valor por « Hôto de la bâsa de balyês »',
- 'config-missing-db-server-oracle' => 'Vos dête buchiér una valor por « TNS de la bâsa de balyês »',
- 'config-sqlite-readonly' => 'Lo fichiér <code>$1</code> est pas accèssiblo en ècritura.',
- 'config-regenerate' => 'Refâre LocalSettings.php →',
- 'config-show-table-status' => 'Falyita de la requéta <code>SHOW TABLE STATUS</code> !',
- 'config-db-web-account' => 'Compto de la bâsa de balyês por l’accès vouèbe',
- 'config-db-web-account-same' => 'Utilisâd lo mémo compto que por l’enstalacion',
- 'config-db-web-create' => 'Féte lo compto s’ègziste p’oncor',
- 'config-mysql-engine' => 'Motor de stocâjo :',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => 'Juè de caractèros de la bâsa de balyês :',
- 'config-mysql-binary' => 'Binèro',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Nom du vouiqui :',
- 'config-site-name-blank' => 'Buchiéd un nom de seto.',
- 'config-project-namespace' => 'Èspâço de noms du projèt :',
- 'config-ns-generic' => 'Projèt',
- 'config-ns-site-name' => 'Mémo nom que lo vouiqui : $1',
- 'config-ns-other' => 'Ôtro (spècefiar)',
- 'config-ns-other-default' => 'MonVouiqui',
- 'config-admin-box' => 'Compto administrator',
- 'config-admin-name' => 'Voutron nom :',
- 'config-admin-password' => 'Contresegno :',
- 'config-admin-password-confirm' => 'Tornar buchiér lo contresegno :',
- 'config-admin-name-blank' => 'Buchiéd un nom d’administrator.',
- 'config-admin-password-blank' => 'Buchiéd un contresegno por lo compto administrator.',
- 'config-admin-email' => 'Adrèce èlèctronica :',
- 'config-optional-continue' => 'Mè posar més de quèstions.',
- 'config-profile' => 'Profil des drêts d’usanciér :',
- 'config-profile-wiki' => 'Vouiqui tradicionâl', # Fuzzy
- 'config-profile-no-anon' => 'Crèacion de compto nècèssèra',
- 'config-profile-fishbowl' => 'Solament los èditors ôtorisâs',
- 'config-profile-private' => 'Vouiqui privâ',
- 'config-license' => 'Drêts d’ôtor et licence :',
- 'config-license-none' => 'Gins de licence d’avâl la pâge',
- 'config-license-cc-by-sa' => 'Creative Commons patèrnitât - partâjo a l’identico',
- 'config-license-cc-by' => 'Creative Commons patèrnitât',
- 'config-license-cc-by-nc-sa' => 'Creative Commons patèrnitât pas comèrciâla - partâjo a l’identico',
- 'config-license-cc-0' => 'Creative Commons Zero (domêno publico)',
- 'config-license-gfdl' => 'Licence de documentacion libra GNU 1.3 ou ben ples novèla',
- 'config-license-pd' => 'Domêno publico',
- 'config-license-cc-choose' => 'Chouèsir una licence Creative Commons pèrsonalisâ',
- 'config-email-settings' => 'Paramètres de mèssageria èlèctronica',
- 'config-enable-email' => 'Activar los mèssâjos que sôrtont',
- 'config-email-user' => 'Activar los mèssâjos d’usanciér a usanciér',
- 'config-email-usertalk' => 'Activar la notificacion de les pâges de discussion ux usanciérs',
- 'config-email-watchlist' => 'Activar la notificacion de la lista de survelyence',
- 'config-email-auth' => 'Activar l’ôtenticacion per mèssageria èlèctronica',
- 'config-email-sender' => 'Adrèce èlèctronica de retôrn :',
- 'config-upload-settings' => 'Tèlèchargement de les émâges et des fichiérs',
- 'config-upload-enable' => 'Activar lo tèlèchargement des fichiérs',
- 'config-upload-deleted' => 'Dossiér por los fichiérs suprimâs :',
- 'config-logo' => 'URL du logô :',
- 'config-instantcommons' => 'Activar Instant Commons',
- 'config-cc-again' => 'Tornâd chouèsir...',
- 'config-advanced-settings' => 'Configuracion avanciê',
- 'config-cache-options' => 'Paramètres por la misa en cache de les chouses :',
- 'config-cache-accel' => 'Misa en cache de les chouses PHP (APC, XCache ou ben WinCache)',
- 'config-memcached-servers' => 'Sèrvors por memcached :',
- 'config-extensions' => 'Èxtensions',
- 'config-install-step-done' => 'fêt',
- 'config-install-step-failed' => 'falyita',
- 'config-install-extensions' => 'Encllusion de les èxtensions',
- 'config-install-database' => 'Crèacion de la bâsa de balyês',
- 'config-install-schema' => 'Crèacion de plan',
- 'config-install-pg-schema-not-exist' => 'Lo plan PostgreSQL ègziste pas',
- 'config-install-pg-commit' => 'Validacion des changements',
- 'config-install-pg-plpgsql' => 'Contrôlo du lengâjo PL/pgSQL',
- 'config-install-user' => 'Crèacion d’un usanciér de la bâsa de balyês',
- 'config-install-user-alreadyexists' => 'L’usanciér « $1 » ègziste ja',
- 'config-install-user-create-failed' => 'Falyita pendent la crèacion de l’usanciér « $1 » : $2',
- 'config-install-user-grant-failed' => 'Falyita pendent l’aponsa de pèrmissions a l’usanciér « $1 » : $2',
- 'config-install-tables' => 'Crèacion de les trâbles',
- 'config-install-interwiki' => 'Remplissâjo per dèfôt de la trâbla des entèrvouiquis',
- 'config-install-interwiki-list' => 'Empossiblo de trovar lo fichiér <code>interwiki.list</code>.',
- 'config-install-stats' => 'Inicialisacion de les statistiques',
- 'config-install-keys' => 'G·ènèracion de les cllâfs secrètes',
- 'config-install-sysop' => 'Crèacion du compto administrator',
- 'config-install-subscribe-fail' => 'Empossiblo de s’abonar a mediawiki-announce : $1',
- 'config-install-mainpage' => 'Crèacion de la pâge principâla avouéc un contegnu per dèfôt',
- 'config-install-extension-tables' => 'Crèacion de trâbles por les èxtensions activâs',
- 'config-install-mainpage-failed' => 'Empossiblo d’entrebetar la pâge principâla : $1',
- 'config-download-localsettings' => 'Tèlèchargiér <code>LocalSettings.php</code>',
- 'config-help' => 'éde',
- 'mainpagetext' => "'''MediaWiki at étâ enstalâ avouéc reusséta.'''",
- 'mainpagedocfooter' => 'Vêde lo [//meta.wikimedia.org/wiki/Aide:Contenu guido d’usanciér] por més d’enformacions sur l’usâjo de la programeria vouiqui.
-
-== Emmodar avouéc MediaWiki ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista des paramètres de configuracion]
-* [//www.mediawiki.org/wiki/Manual:FAQ/fr FDQ sur MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussion sur les distribucions de MediaWiki]', # Fuzzy
-);
-
-/** Northern Frisian (Nordfriisk)
- * @author Murma174
- * @author Pyt
- */
-$messages['frr'] = array(
- 'mainpagetext' => "'''MediaWiki wörd ma erfolch instaliird.'''",
- 'mainpagedocfooter' => "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.
-
-== Getting started ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localise MediaWiki for your language]",
-);
-
-/** Friulian (furlan)
- */
-$messages['fur'] = array(
- 'mainpagetext' => "'''MediaWiki e je stade instalade cun sucès.'''",
-);
-
-/** Western Frisian (Frysk)
- */
-$messages['fy'] = array(
- 'mainpagetext' => "'''MediaWiki-program goed ynstallearre.'''",
- 'mainpagedocfooter' => "Rieplachtsje de [//meta.wikimedia.org/wiki/Help:Ynhâldsopjefte hantlieding] foar ynformaasje oer it gebrûk fan 'e wikisoftware.
-
-== Mear help oer Mediawiki ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings List mei ynstellings]
-* [//www.mediawiki.org/wiki/Manual:FAQ Faak stelde fragen (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglist foar oankundigings fan nije ferzjes]", # Fuzzy
-);
-
-/** Irish (Gaeilge)
- * @author පසිඳු කාවින්ද
- */
-$messages['ga'] = array(
- 'config-page-language' => 'Teanga',
- 'config-page-name' => 'Ainm',
- 'config-admin-password' => "D'fhocal faire:",
- 'config-help' => 'Cuidiú',
- 'mainpagetext' => "'''D'éirigh le suiteáil MediaWiki.'''",
- 'mainpagedocfooter' => 'Féach ar [//meta.wikimedia.org/wiki/MediaWiki_localisation doiciméid um conas an chomhéadán a athrú]
-agus an [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Lámhleabhar úsáideora] chun cabhair úsáide agus fíoraíochta a fháil.', # Fuzzy
-);
-
-/** Gagauz (Gagauz)
- */
-$messages['gag'] = array(
- 'mainpagetext' => "'''MediaWiki başarılan kuruldu.'''",
- 'mainpagedocfooter' => "Vikilän iş uurunda bilgi almaa için [//meta.wikimedia.org/wiki/Help:Contents User's Guide] sayfasına bakınız
-
-== Eni başlayanlar için ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Simplified Gan script (赣语(简体)‎)
- */
-$messages['gan-hans'] = array(
- 'mainpagetext' => "'''安装正MediaWiki喽。'''",
- 'mainpagedocfooter' => '参看[//meta.wikimedia.org/wiki/Help:Contents 用户指南]里头会话到啷用wiki软件
-
-== 开始使用 ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置设定列表]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 平常问题解答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 发布email清单]', # Fuzzy
-);
-
-/** Traditional Gan script (贛語(繁體)‎)
- * @author Symane
- */
-$messages['gan-hant'] = array(
- 'mainpagetext' => "'''安裝正MediaWiki哩。'''",
- 'mainpagedocfooter' => '參看[//meta.wikimedia.org/wiki/Help:Contents 用戶指南]裡頭會話到啷用wiki軟件
-
-== 開始使用 ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定列表]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 平常問題解答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈email清單]', # Fuzzy
-);
-
-/** Scottish Gaelic (Gàidhlig)
- * @author Akerbeltz
- */
-$messages['gd'] = array(
- 'mainpagetext' => "'''Chaidh MediaWiki a stàladh gu soirbheachail.'''",
- 'mainpagedocfooter' => "Cuir sùil air [//meta.wikimedia.org/wiki/Help:Contents treòir nan cleachdaichean] airson fiosrachadh mu chleachdadh a' bhathar-bhog wiki.
-
-== Toiseach tòiseachaidh ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liosta suidheachadh nan roghainnean]
-* [//www.mediawiki.org/wiki/Manual:FAQ CÀBHA MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liosta puist nan sgaoilidhean MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Cuir do chànan air MediaWiki]",
-);
-
-/** Galician (galego)
- * @author Elisardojm
- * @author Toliño
- * @author 아라
- */
-$messages['gl'] = array(
- 'config-desc' => 'O programa de instalación de MediaWiki',
- 'config-title' => 'Instalación de MediaWiki $1',
- 'config-information' => 'Información',
- 'config-localsettings-upgrade' => 'Detectouse un ficheiro <code>LocalSettings.php</code>.
-Para actualizar esta instalación, introduza o valor de <code>$wgUpgradeKey</code> na caixa.
-Pode atopalo en <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Detectouse un ficheiro <code>LocalSettings.php</code>.
-Para actualizar esta instalación, execute <code>update.php</code>',
- 'config-localsettings-key' => 'Clave de actualización:',
- 'config-localsettings-badkey' => 'A clave dada é incorrecta',
- 'config-upgrade-key-missing' => 'Detectouse unha instalación existente de MediaWiki.
-Para actualizar esta instalación, inclúa esta liña ao final do ficheiro <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Semella que o ficheiro <code>LocalSettings.php</code> existente está incompleto.
-A variable $1 non está establecida.
-Modifique o ficheiro <code>LocalSettings.php</code> de xeito que a variable quede establecida e prema en "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Atopouse un erro ao conectar coa base de datos empregando a configuración especificada no ficheiro <code>LocalSettings.php</code> ou no ficheiro <code>AdminSettings.php</code>. Corrixa esta configuración e inténteo de novo.
-
-$1',
- 'config-session-error' => 'Erro ao iniciar a sesión: $1',
- 'config-session-expired' => 'Semella que os seus datos da sesión caducaron.
-As sesións están configuradas para unha duración de $1.
-Pode incrementar isto fixando <code>session.gc_maxlifetime</code> en php.ini.
-Reinicie o proceso de instalación.',
- 'config-no-session' => 'Perdéronse os datos da súa sesión!
-Comprobe o seu php.ini e asegúrese de que en <code>session.save_path</code> está definido un directorio correcto.',
- 'config-your-language' => 'A súa lingua:',
- 'config-your-language-help' => 'Seleccione a lingua que se empregará durante o proceso de instalación.',
- 'config-wiki-language' => 'Lingua do wiki:',
- 'config-wiki-language-help' => 'Seleccione a lingua que predominará no wiki.',
- 'config-back' => '← Volver',
- 'config-continue' => 'Continuar →',
- 'config-page-language' => 'Lingua',
- 'config-page-welcome' => 'Benvido a MediaWiki!',
- 'config-page-dbconnect' => 'Conectarse á base de datos',
- 'config-page-upgrade' => 'Actualizar a instalación actual',
- 'config-page-dbsettings' => 'Configuración da base de datos',
- 'config-page-name' => 'Nome',
- 'config-page-options' => 'Opcións',
- 'config-page-install' => 'Instalar',
- 'config-page-complete' => 'Completo!',
- 'config-page-restart' => 'Reiniciar a instalación',
- 'config-page-readme' => 'Léame',
- 'config-page-releasenotes' => 'Notas de lanzamento',
- 'config-page-copying' => 'Copiar',
- 'config-page-upgradedoc' => 'Actualizar',
- 'config-page-existingwiki' => 'Wiki existente',
- 'config-help-restart' => 'Quere eliminar todos os datos gardados e reiniciar o proceso de instalación?',
- 'config-restart' => 'Si, reiniciala',
- 'config-welcome' => '=== Comprobación da contorna ===
-Cómpre realizar agora unhas comprobacións básicas para ver se a contorna é axeitada para a instalación de MediaWiki.
-Lembre incluír esta información se necesita axuda para completar a instalación.',
- 'config-copyright' => "=== Dereitos de autor e termos de uso ===
-
-$1
-
-Este programa é software libre; pode redistribuílo e/ou modificalo segundo os termos da licenza pública xeral GNU publicada pola Free Software Foundation; versión 2 ou (na súa escolla) calquera outra posterior.
-
-Este programa distribúese coa esperanza de que poida ser útil, pero '''sen garantía ningunha'''; nin sequera a garantía implícita de '''comercialización''' ou '''adecuación a unha finalidade específica'''.
-Olle a licenza pública xeral GNU para obter máis detalles.
-
-Debería recibir <doclink href=Copying>unha copia da licenza pública xeral GNU</doclink> xunto ao programa; se non é así, escriba á Free Software Foundation, Inc., rúa Franklin, número 51, quinto andar, Boston, Massachusetts, 02110-1301, Estados Unidos de América ou [http://www.gnu.org/copyleft/gpl.html lea a licenza en liña].",
- 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/gl Páxina principal de MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Guía de usuario]
-* [//www.mediawiki.org/wiki/Manual:Contents Guía de administrador]
-* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas máis frecuentes]
-----
-* <doclink href=Readme>Léame</doclink>
-* <doclink href=ReleaseNotes>Notas de lanzamento</doclink>
-* <doclink href=Copying>Copia</doclink>
-* <doclink href=UpgradeDoc>Actualizacións</doclink>',
- 'config-env-good' => 'Rematou a comprobación da contorna.
-Pode instalar MediaWiki.',
- 'config-env-bad' => 'Rematou a comprobación da contorna.
-Non pode instalar MediaWiki.',
- 'config-env-php' => 'Está instalado o PHP $1.',
- 'config-env-php-toolow' => 'Está instalado o PHP $1.
-Porén, MediaWiki necesita o PHP $2 ou superior.',
- 'config-unicode-using-utf8' => 'Usando utf8_normalize.so de Brion Vibber para a normalización Unicode.',
- 'config-unicode-using-intl' => 'Usando a [http://pecl.php.net/intl extensión intl PECL] para a normalización Unicode.',
- 'config-unicode-pure-php-warning' => "'''Atención:''' A [http://pecl.php.net/intl extensión intl PECL] non está dispoñible para manexar a normalización Unicode; volvendo á implementación lenta de PHP puro.
-Se o seu sitio posúe un alto tráfico de visitantes, debería ler un chisco sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalización Unicode].",
- 'config-unicode-update-warning' => "'''Atención:''' A versión instalada da envoltura de normalización Unicode emprega unha versión vella da biblioteca [http://site.icu-project.org/ do proxecto ICU].
-Debería [//www.mediawiki.org/wiki/Unicode_normalization_considerations actualizar] se o uso de Unicode é importante para vostede.",
- 'config-no-db' => 'Non se puido atopar un controlador axeitado para a base de datos! Necesita instalar un controlador de base de datos para PHP.
-Os tipos de base de datos admitidos son os seguintes: $1.
-
-Se está nun aloxamento compartido, pregunte ao seu provedor de hospedaxe para instalar un controlador de base de datos axeitado.
-Se compilou o PHP vostede mesmo, reconfigúreo activando un cliente de base de datos, por exemplo, usando <code>./configure --with-mysql</code>.
-Se instalou o PHP desde un paquete Debian ou Ubuntu, entón tamén necesita instalar o módulo php5-mysql.',
- 'config-outdated-sqlite' => "'''Atención:''' Ten o SQLite $1, que é inferior á versión mínima necesaria: $2. O SQLite non estará dispoñible.",
- 'config-no-fts3' => "'''Atención:''' O SQLite está compilado sen o [//sqlite.org/fts3.html módulo FTS3]; as características de procura non estarán dispoñibles nesta instalación.",
- 'config-register-globals' => "'''Atención: A opción PHP <code>[http://php.net/register_globals register_globals]</code> está activada.'''
-'''Desactívea se pode.'''
-MediaWiki funcionará, pero o seu servidor está exposto a potenciais vulnerabilidades de seguridade.",
- 'config-magic-quotes-runtime' => "'''Erro fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está activado!'''
-Esta opción corrompe os datos de entrada de xeito imprevisible.
-Non pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
- 'config-magic-quotes-sybase' => "'''Erro fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está activado!'''
-Esta opción corrompe os datos de entrada de xeito imprevisible.
-Non pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
- 'config-mbstring' => "'''Erro fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está activado!'''
-Esta opción causa erros e pode corromper os datos de xeito imprevisible.
-Non pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
- 'config-ze1' => "'''Erro fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] está activado!'''
-Esta opción causa erros horribles en MediaWiki.
-Non pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
- 'config-safe-mode' => "'''Atención:''' O [http://www.php.net/features.safe-mode safe mode] do PHP está activado.
-Isto pode causar problemas, particularmente se emprega cargas de ficheiros e soporte de <code>math</code>.",
- 'config-xml-bad' => 'Falta o módulo XML do PHP.
-MediaWiki necesita funcións neste módulo e non funcionará con esta configuración.
-Se está executando o Mandrake, instale o paquete php-xml.',
- 'config-pcre' => 'Semella que falta o módulo de soporte PCRE.
-MediaWiki necesita que funcionen as expresións regulares compatibles co Perl.',
- 'config-pcre-no-utf8' => "'''Erro fatal:''' Semella que o módulo PCRE do PHP foi compilado sen o soporte PCRE_UTF8.
-MediaWiki 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' => "'''Atención:''' O parámetro <code>memory_limit</code> do PHP é $1.
-Probablemente é un valor baixo de máis.
-A instalación pode fallar!",
- 'config-ctype' => "'''Erro fatal:''' O PHP debe compilarse co soporte para a [http://www.php.net/manual/en/ctype.installation.php extensión Ctype].",
- 'config-json' => "'''Erro fatal:''' O PHP compilouse sen o soporte de JSON.
-Debe instalar ben a extensión JSON do PHP ou a extensión [http://pecl.php.net/package/jsonc PECL jsonc] antes de instalar MediaWiki.
-* A extensión do PHP está incluída en Red Hat Enterprise Linux (CentOS) 5 e 6, mais debe activarse <code>/etc/php.ini</code> ou <code>/etc/php.d/json.ini</code>.
-* Algunhas distribucións do Linux lanzadas despois de maio de 2013 omiten a extensión do PHP, pero inclúen a extensión PECL como <code>php5-json</code> ou <code>php-pecl-jsonc</code>.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] está instalado',
- 'config-apc' => '[http://www.php.net/apc APC] está instalado',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] está instalado',
- 'config-no-cache' => "'''Atención:''' Non se puido atopar [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
-A caché de obxectos está desactivada.",
- 'config-mod-security' => "'''Atención:''' O seu servidor web ten o [http://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.
-Olle a [http://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-git-bad' => 'Non se atopou o software de control da versión de Git.',
- 'config-imagemagick' => 'ImageMagick atopado: <code>$1</code>.
-As miniaturas de imaxes estarán dispoñibles se activa as cargas.',
- 'config-gd' => 'Atopouse a biblioteca gráfica GD integrada.
-As miniaturas de imaxes estarán dispoñibles se activa as cargas.',
- 'config-no-scaling' => 'Non se puido atopar a biblioteca GD ou ImageMagick.
-As miniaturas de imaxes estarán desactivadas.',
- 'config-no-uri' => "'''Erro:''' Non se puido determinar o URI actual.
-Instalación abortada.",
- 'config-no-cli-uri' => "'''Aviso:''' Non se especificou ningún --scriptpath; por defecto, usarase: <code>$1</code>.",
- 'config-using-server' => 'Usando o nome do servidor "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Usando o URL do servidor "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Atención:''' O seu directorio por defecto para as cargas, <code>$1</code>, é vulnerable a execucións arbitrarias de escrituras.
-Aínda que MediaWiki comproba todos os ficheiros cargados por se houbese ameazas de seguridade, é amplamente recomendable [//www.mediawiki.org/wiki/Manual:Security#Upload_security pechar esta vulnerabilidade de seguridade] antes de activar as cargas.",
- 'config-no-cli-uploads-check' => "'''Atención:''' Durante a instalación CLI, o seu directorio por defecto para as cargas, <code>$1</code>, non se comproba fronte a posibles vulnerabilidades de execucións arbitrarias de escrituras.",
- 'config-brokenlibxml' => 'O seu sistema ten unha combinación de versións de PHP e libxml2 que pode ser problemático e causar corrupción de datos en MediaWiki e outras aplicacións web.
-Actualice o sistema á versión 5.2.9 ou posterior do PHP e á 2.7.3 ou posterior de libxml2 ([//bugs.php.net/bug.php?id=45996 erro presentado co PHP]).
-Instalación abortada.',
- 'config-using531' => 'O PHP $1 non é compatible con MediaWiki debido a un erro que afecta aos parámetros de referencia de <code>__call()</code>.
-Actualice o sistema á versión 5.3.2 ou posterior do PHP ou volva á versión 5.3.0 do PHP para arranxar o problema.
-Instalación abortada.',
- 'config-suhosin-max-value-length' => 'Suhosin está instalado e limita o parámetro GET <code>length</code> a $1 bytes.
-O compoñente ResourceLoader (xestor de recursos) de MediaWiki traballa neste límite, pero este prexudica o rendemento.
-Se é posible, debería establecer <code>suhosin.get.max_value_length</code> no valor 1024 ou superior en <code>php.ini</code> e establecer <code>$wgResourceLoaderMaxQueryLength</code> no mesmo valor en <code>LocalSettings.php</code>.',
- 'config-db-type' => 'Tipo de base de datos:',
- 'config-db-host' => 'Servidor da base de datos:',
- 'config-db-host-help' => 'Se o servidor da súa base de datos está nun servidor diferente, escriba o nome do servidor ou o enderezo IP aquí.
-
-Se está usando un aloxamento web compartido, o seu provedor de hospedaxe debe darlle o nome de servidor correcto na súa documentación.
-
-Se está a realizar a instalación nun servidor de Windows con MySQL, o nome "localhost" pode non valer como servidor. Se non funcionase, inténteo con "127.0.0.1" como enderezo IP local.
-
-Se está usando PostgreSQL, deixe este campo en branco para facer a conexión a través do conectador Unix.',
- 'config-db-host-oracle' => 'TNS da base de datos:',
- 'config-db-host-oracle-help' => 'Insira un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nome de conexión local] válido; cómpre que haxa visible un ficheiro tnsnames.ora para esta instalación.<br />Se está a empregar bibliotecas cliente versión 10g ou máis recentes, tamén pode usar o método de atribución de nomes [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identificar o wiki',
- 'config-db-name' => 'Nome da base de datos:',
- 'config-db-name-help' => 'Escolla un nome que identifique o seu wiki.
-Non debe conter espazos.
-
-Se está usando un aloxamento web compartido, o seu provedor de hospedaxe daralle un nome específico para a base de datos ou deixaralle crear unha a través do panel de control.',
- 'config-db-name-oracle' => 'Esquema da base de datos:',
- 'config-db-account-oracle-warn' => 'Existen tres escenarios soportados para a instalación de Oracle como fin da base de datos:
-
-Se quere crear unha conta para a base de datos como parte do proceso de instalación, proporcione unha conta co papel SYSDBA e especifique as credenciais desexadas para a conta; senón pode crear a conta manualmente e dar só esa conta (se ten os permisos necesarios para crear os obxectos do esquema) ou fornecer dous contas diferentes, unha con privilexios de creación e outra restrinxida para o acceso á web.
-
-A escritura para crear unha conta cos privilexios necesarios atópase no directorio "maintenance/oracle/" desta instalación. Teña en conta que o emprego de contas restrinxidas desactivará todas as operacións de mantemento da conta predeterminada.',
- 'config-db-install-account' => 'Conta de usuario para a instalación',
- 'config-db-username' => 'Nome de usuario da base de datos:',
- 'config-db-password' => 'Contrasinal da base de datos:',
- 'config-db-password-empty' => 'Introduza un contrasinal para o novo usuario da base de datos: $1.
-Malia que é posible crear usuarios sen contrasinal, esta práctica non é segura.',
- 'config-db-install-username' => 'Escriba o nome de usuario que empregará para conectarse á base de datos durante o proceso de instalación. Este non é o nome de usuario da conta de MediaWiki, trátase do nome de usuario para a súa base de datos.',
- 'config-db-install-password' => 'Escriba o contrasinal que empregará para conectarse á base de datos durante o proceso de instalación. Este non é o contrasinal da conta de MediaWiki, trátase do contrasinal para a súa base de datos.',
- 'config-db-install-help' => 'Introduza o nome de usuario e contrasinal que se usará para conectar á base de datos durante o proceso de instalación.',
- 'config-db-account-lock' => 'Use o mesmo nome de usuario e contrasinal despois do proceso de instalación',
- 'config-db-wiki-account' => 'Conta de usuario para despois do proceso de instalación',
- 'config-db-wiki-help' => 'Introduza o nome de usuario e mais o contrasinal que se usarán para conectar á base de datos durante o funcionamento habitual do wiki.
-Se a conta non existe e a conta de instalación ten privilexios suficientes, esa conta de usuario será creada cos privilexios mínimos necesarios para o funcionamento do wiki.',
- 'config-db-prefix' => 'Prefixo das táboas da base de datos:',
- 'config-db-prefix-help' => 'Se necesita compartir unha base de datos entre varios wikis ou entre MediaWiki e outra aplicación web, pode optar por engadir un prefixo a todos os nomes da táboa para evitar conflitos.
-Non utilice espazos.
-
-O normal é que este campo quede baleiro.',
- 'config-db-charset' => 'Conxunto de caracteres da base de datos',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binario',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 retrocompatible UTF-8',
- 'config-charset-help' => "'''Atención:''' Se emprega '''backwards-compatible UTF-8''' no MySQL 4.1+ e posteriormente realiza unha copia de seguridade da base de datos con <code>mysqldump</code>, pode destruír todos os caracteres que non sexan ASCII, corrompendo de xeito irreversible as súas copias!
-
-No '''modo binario''', MediaWiki almacena texto UTF-8 na base de datos en campos binarios.
-Isto é máis eficaz ca o modo UTF-8 de MySQL e permítelle usar o rango completo de caracteres Unicode.
-No '''modo UTF-8''', MySQL saberá o xogo de caracteres dos seus datos e pode presentar e converter os datos de maneira axeitada,
-pero non lle deixará gardar caracteres por riba do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
- 'config-mysql-old' => 'Necesítase MySQL $1 ou posterior; ten a versión $2.',
- 'config-db-port' => 'Porto da base de datos:',
- 'config-db-schema' => 'Esquema para MediaWiki',
- 'config-db-schema-help' => 'O normal é que este esquema sexa correcto.
-Cámbieo soamente se sabe que é necesario.',
- 'config-pg-test-error' => "Non se pode conectar coa base de datos '''$1''': $2",
- 'config-sqlite-dir' => 'Directorio de datos SQLite:',
- 'config-sqlite-dir-help' => "SQLite recolle todos os datos nun ficheiro único.
-
-O servidor web debe ter permisos sobre o directorio para que poida escribir nel durante a instalación.
-
-Ademais, o servidor '''non''' debe ser accesible a través da web, motivo polo que non está no mesmo lugar ca os ficheiros PHP.
-
-Asemade, o programa de instalación escribirá un ficheiro <code>.htaccess</code>, pero se erra alguén pode obter acceso á súa base de datos.
-Isto inclúe datos de usuario (enderezos de correo electrónico, contrasinais codificados), así como revisións borradas e outros datos restrinxidos no wiki.
-
-Considere poñer a base de datos nun só lugar, por exemplo en <code>/var/lib/mediawiki/oseuwiki</code>.",
- 'config-oracle-def-ts' => 'Espazo de táboas por defecto:',
- 'config-oracle-temp-ts' => 'Espazo de táboas temporal:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki soporta os seguintes sistemas de bases de datos:
-
-$1
-
-Se 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-support-mysql' => '* $1 é o obxectivo principal para MediaWiki e está mellor soportado ([http://www.php.net/manual/en/mysql.installation.php como compilar o PHP con soporte MySQL])',
- 'config-support-postgres' => '* $1 é un sistema de base de datos popular e de código aberto como alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar o PHP con soporte PostgreSQL]). É posible que haxa algúns pequenos erros e non se recomenda o seu uso nunha contorna de produción.',
- 'config-support-sqlite' => '* $1 é un sistema de base de datos lixeiro moi ben soportado. ([http://www.php.net/manual/en/pdo.installation.php Como compilar o PHP con soporte SQLite], emprega PDO)',
- 'config-support-oracle' => '* $1 é un sistema comercial de xestión de base de datos de empresa. ([http://www.php.net/manual/en/oci8.installation.php Como compilar o PHP con soporte OCI8])',
- 'config-header-mysql' => 'Configuración do MySQL',
- 'config-header-postgres' => 'Configuración do PostgreSQL',
- 'config-header-sqlite' => 'Configuración do SQLite',
- 'config-header-oracle' => 'Configuración do Oracle',
- 'config-invalid-db-type' => 'Tipo de base de datos incorrecto',
- 'config-missing-db-name' => 'Debe escribir un valor "Nome da base de datos"',
- 'config-missing-db-host' => 'Debe escribir un valor "Servidor da base de datos"',
- 'config-missing-db-server-oracle' => 'Debe escribir un valor "TNS da base de datos"',
- 'config-invalid-db-server-oracle' => 'O TNS da base de datos, "$1", é incorrecto.
-Utilice só "TNS Name" ou unha cadea de texto "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm métodos de nomeamento de Oracle])',
- 'config-invalid-db-name' => 'O nome da base de datos, "$1", é incorrecto.
-Só pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e guións (-).',
- 'config-invalid-db-prefix' => 'O prefixo da base de datos, "$1", é incorrecto.
-Só pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e guións (-).',
- 'config-connection-error' => '$1.
-
-Comprobe o servidor, nome de usuario e contrasinal que hai a continuación e inténteo de novo.',
- 'config-invalid-schema' => 'O esquema de MediaWiki, "$1", é incorrecto.
-Só pode conter letras ASCII (a-z, A-Z), números (0-9) e guións baixos (_).',
- 'config-db-sys-create-oracle' => 'O programa de instalación soamente soporta o emprego de contas SYSDBA como método para crear unha nova conta.',
- 'config-db-sys-user-exists-oracle' => 'A conta de usuario "$1" xa existe. SYSDBA soamente se pode empregar para a creación dunha nova conta!',
- 'config-postgres-old' => 'Necesítase PostgreSQL $1 ou posterior; ten a versión $2.',
- 'config-sqlite-name-help' => 'Escolla un nome que identifique o seu wiki.
-Non utilice espazos ou guións.
-Este nome será utilizado para o ficheiro de datos SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Non se puido crear o directorio de datos <code><nowiki>$1</nowiki></code>, porque o servidor web non pode escribir no directorio pai <code><nowiki>$2</nowiki></code>.
-
-O programa de instalación determinou o usuario que executa o seu servidor web.
-Para continuar, faga que se poida escribir no directorio <code><nowiki>$3</nowiki></code>.
-Nun sistema Unix/Linux cómpre realizar:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Non se puido crear o directorio de datos <code><nowiki>$1</nowiki></code>, porque o servidor web non pode escribir no directorio pai <code><nowiki>$2</nowiki></code>.
-
-O programa de instalación non puido determinar o usuario que executa o seu servidor web.
-Para continuar, faga que se poida escribir globalmente no directorio <code><nowiki>$3</nowiki></code>.
-Nun sistema Unix/Linux cómpre realizar:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Erro ao crear o directorio de datos "$1".
-Comprobe a localización e inténteo de novo.',
- 'config-sqlite-dir-unwritable' => 'Non se puido escribir o directorio "$1".
-Cambie os permisos para que o servidor poida escribir nel e inténteo de novo.',
- 'config-sqlite-connection-error' => '$1.
-
-Comprobe o directorio de datos e o nome da base de datos que hai a continuación e inténteo de novo.',
- 'config-sqlite-readonly' => 'Non se pode escribir no ficheiro <code>$1</code>.',
- 'config-sqlite-cant-create-db' => 'Non se puido crear o ficheiro da base de datos <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'Falta o soporte FTS3 para o PHP; diminuíndo as táboas',
- 'config-can-upgrade' => "Existen táboas MediaWiki nesta base de datos.
-Para actualizalas a MediaWiki \$1, prema sobre \"'''Continuar'''\".",
- 'config-upgrade-done' => "Actualización completada.
-
-Agora pode [$1 comezar a utilizar o seu wiki].
-
-Se quere rexenerar o seu ficheiro <code>LocalSettings.php</code>, prema no botón que aparece a continuación.
-Isto '''non é recomendable''' a menos que estea a ter problemas co seu wiki.",
- 'config-upgrade-done-no-regenerate' => 'Actualización completada.
-
-Xa pode [$1 comezar a usar o seu wiki].',
- 'config-regenerate' => 'Rexenerar LocalSettings.php →',
- 'config-show-table-status' => 'A pescuda <code>SHOW TABLE STATUS</code> fallou!',
- 'config-unknown-collation' => "'''Atención:''' A base de datos está a empregar unha clasificación alfabética irrecoñecible.",
- 'config-db-web-account' => 'Conta na base de datos para o acceso á internet',
- 'config-db-web-help' => 'Seleccione o nome de usuario e contrasinal que o servidor web empregará para se conectar ao servidor da base de datos durante o funcionamento normal do wiki.',
- 'config-db-web-account-same' => 'Empregar a mesma conta que para a instalación',
- 'config-db-web-create' => 'Crear a conta se aínda non existe',
- 'config-db-web-no-create-privs' => 'A conta que especificou para a instalación non ten os privilexios suficientes para crear unha conta.
-A conta que se especifique aquí xa debe existir.',
- 'config-mysql-engine' => 'Motor de almacenamento:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Atención:''' Seleccionou MyISAM como o motor de almacenamento para MySQL, unha combinación non recomendada para MediaWiki, porque:
-* practicamente non soporta os accesos simultáneos debido ao bloqueo de táboas
-* é máis propenso a corromperse ca outros motores
-* o código base de MediaWiki non sempre manexa o MyISAM como debera
-
-Se a súa instalación MySQL soporta InnoDB, recoméndase elixilo no canto de MyISAM.
-Se a súa instalación MySQL non soporta InnoDB, quizais sexa boa idea realizar unha actualización.",
- 'config-mysql-only-myisam-dep' => "'''Atención:''' MyISAM é o único motor de almacenamento para MySQL, unha combinación non recomendada para MediaWiki, porque:
-* practicamente non soporta os accesos simultáneos debido ao bloqueo de táboas
-* é máis propenso a corromperse ca outros motores
-* o código base de MediaWiki non sempre manexa o MyISAM como debera
-
-A súa instalación MySQL non soporta InnoDB, quizais sexa boa idea realizar unha actualización.",
- 'config-mysql-engine-help' => "'''InnoDB''' é case sempre a mellor opción, dado que soporta ben os accesos simultáneos.
-
-'''MyISAM''' é máis rápido en instalacións de usuario único e de só lectura.
-As bases de datos MyISAM tenden a se corromper máis a miúdo ca as bases de datos InnoDB.",
- 'config-mysql-charset' => 'Conxunto de caracteres da base de datos:',
- 'config-mysql-binary' => 'Binario',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "No '''modo binario''', MediaWiki almacena texto UTF-8 na base de datos en campos binarios.
-Isto é máis eficaz ca o modo UTF-8 de MySQL e permítelle usar o rango completo de caracteres Unicode.
-
-No '''modo UTF-8''', MySQL saberá o xogo de caracteres dos seus datos e pode presentar e converter os datos de maneira axeitada,
-pero non lle deixará gardar caracteres por riba do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
- 'config-site-name' => 'Nome do wiki:',
- 'config-site-name-help' => 'Isto aparecerá na barra de títulos do navegador e noutros lugares.',
- 'config-site-name-blank' => 'Escriba o nome do sitio.',
- 'config-project-namespace' => 'Espazo de nomes do proxecto:',
- 'config-ns-generic' => 'Proxecto',
- 'config-ns-site-name' => 'O mesmo nome que o wiki: $1',
- 'config-ns-other' => 'Outro (especificar)',
- 'config-ns-other-default' => 'OMeuWiki',
- 'config-project-namespace-help' => 'Seguindo o exemplo da Wikipedia, moitos wikis manteñen as súas páxinas de políticas separadas das súas páxinas de contido, nun "\'\'\'espazo de nomes do proxecto\'\'\'".
-Todos os títulos presentes neste espazo de nomes comezan cun prefixo determinado, que pode especificar aquí.
-Tradicionalmente, este prefixo deriva do nome do wiki, pero non pode conter caracteres de puntuación como "#" ou ":".',
- 'config-ns-invalid' => 'O espazo de nomes especificado, "<nowiki>$1</nowiki>", é incorrecto.
-Especifique un espazo de nomes do proxecto diferente.',
- 'config-ns-conflict' => 'O espazo de nomes especificado, "<nowiki>$1</nowiki>", entra en conflito co espazo de nomes MediaWiki por defecto.
-Especifique un espazo de nomes do proxecto diferente.',
- 'config-admin-box' => 'Conta de administrador',
- 'config-admin-name' => 'O seu nome:',
- 'config-admin-password' => 'Contrasinal:',
- 'config-admin-password-confirm' => 'Repita o contrasinal:',
- 'config-admin-help' => 'Escriba o nome de usuario que queira aquí, por exemplo, "Joe Bloggs".
-Este é o nome que usará para acceder ao sistema do wiki.',
- 'config-admin-name-blank' => 'Escriba un nome de usuario para o administrador.',
- 'config-admin-name-invalid' => 'O nome de usuario especificado, "<nowiki>$1</nowiki>", é incorrecto.
-Especifique un nome de usuario diferente.',
- 'config-admin-password-blank' => 'Escriba un contrasinal para a conta de administrador.',
- 'config-admin-password-same' => 'O contrasinal debe diferir do nome de usuario.',
- 'config-admin-password-mismatch' => 'Os contrasinais non coinciden.',
- 'config-admin-email' => 'Enderezo de correo electrónico:',
- 'config-admin-email-help' => 'Escriba aquí un enderezo de correo electrónico para que poida recibir mensaxes doutros usuarios a través do wiki, restablecer o contrasinal e ser notificado das modificacións feitas nas páxinas presentes na súa lista de vixilancia. Pode deixar este campo en branco.',
- 'config-admin-error-user' => 'Erro interno ao crear un administrador co nome "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Erro interno ao establecer un contrasinal para o administrador "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Escribiu un enderezo de correo electrónico non válido.',
- 'config-subscribe' => 'Subscríbase á [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de correo de anuncios sobre lanzamentos].',
- 'config-subscribe-help' => 'Esta é unha lista de correos de baixo volume usada para anuncios sobre lanzamentos de novas versións, incluíndo avisos de seguridade importantes.
-Debería subscribirse a ela e actualizar a súa instalación MediaWiki cando saian as novas versións.',
- 'config-subscribe-noemail' => 'Intentou subscribirse á lista de correo dos anuncios de novos lanzamentos sen proporcionar o enderezo de correo electrónico.
-Dea un enderezo de correo electrónico se quere efectuar a subscrición á lista de correo.',
- 'config-almost-done' => 'Xa case rematou!
-Neste paso pode saltar o resto da configuración e instalar o wiki agora mesmo.',
- 'config-optional-continue' => 'Facédeme máis preguntas.',
- 'config-optional-skip' => 'Xa estou canso. Instalade o wiki.',
- 'config-profile' => 'Perfil dos dereitos de usuario:',
- 'config-profile-wiki' => 'Wiki aberto',
- 'config-profile-no-anon' => 'Necesítase a creación dunha conta',
- 'config-profile-fishbowl' => 'Só os editores autorizados',
- 'config-profile-private' => 'Wiki privado',
- 'config-profile-help' => "Os wikis funcionan mellor canta máis xente os edite.
-En MediaWiki, é doado revisar os cambios recentes e reverter calquera dano feito por usuarios novatos ou con malas intencións.
-Porén, moita xente atopa MediaWiki útil nunha ampla variedade de papeis, e ás veces non é fácil convencer a todos dos beneficios que leva consigo o estilo wiki.
-Vostede decide.
-
-O modelo '''{{int:config-profile-wiki}}''' permite a edición por parte de calquera, mesmo sen rexistro.
-A opción '''{{int:config-profile-no-anon}}''' proporciona un control maior, pero pode desalentar os colaboradores casuais.
-
-O escenario '''{{int:config-profile-fishbowl}}''' restrinxe a edición aos usuarios aprobados, pero o público pode ollar as páxinas, incluíndo os historiais.
-O tipo '''{{int:config-profile-private}}''' só deixa que os usuarios aprobados vexan e editen as páxinas.
-
-Hai dispoñibles configuracións de dereitos de usuario máis complexas despois da instalación; bótelle un ollo a [//www.mediawiki.org/wiki/Manual:User_rights esta entrada no manual].",
- 'config-license' => 'Dereitos de autor e licenza:',
- 'config-license-none' => 'Sen licenza ao pé',
- 'config-license-cc-by-sa' => 'Creative Commons recoñecemento compartir igual',
- 'config-license-cc-by' => 'Creative Commons recoñecemento',
- 'config-license-cc-by-nc-sa' => 'Creative Commons recoñecemento non comercial compartir igual',
- 'config-license-cc-0' => 'Creative Commons Zero (dominio público)',
- 'config-license-gfdl' => 'Licenza de documentación libre de GNU 1.3 ou posterior',
- 'config-license-pd' => 'Dominio público',
- 'config-license-cc-choose' => 'Seleccione unha licenza Creative Commons personalizada',
- 'config-license-help' => "Moitos wikis públicos liberan todas as súas contribucións baixo unha [http://freedomdefined.org/Definition/Gl licenza libre].
-Isto axuda a crear un sentido de propiedade comunitaria e anima a seguir contribuíndo durante moito tempo.
-Xeralmente, non é necesario nos wikis privados ou de empresas.
-
-Se quere poder empregar textos da Wikipedia, así como que a Wikipedia poida aceptar textos copiados do seu wiki, escolla a licenza '''Creative Commons recoñecemento compartir igual'''.
-
-A licenza de documentación libre de GNU era a licenza anterior da Wikipedia.
-Malia aínda ser unha licenza válida, é difícil de entender.
-Tamé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 [http://www.php.net/manual/en/mail.configuration.php parámetros PHP] correctamente.
-Se non quere ningunha característica no correo, 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',
- 'config-email-usertalk-help' => 'Permitir que os usuarios reciban notificacións cando a súa páxina de conversa de usuario sufra modificacións, se o teñen activado nas súas preferencias.',
- 'config-email-watchlist' => 'Activar a notificación da lista de vixilancia',
- 'config-email-watchlist-help' => 'Permitir que os usuarios reciban notificacións sobre modificacións nas páxinas que vixían, se o teñen activado nas súas preferencias.',
- 'config-email-auth' => 'Activar a autenticación do correo electrónico',
- 'config-email-auth-help' => "Se esta opción está activada, os usuarios teñen que confirmar o seu correo electrónico mediante unha ligazón enviada ao enderezo cando o definan ou o cambien.
-Só os enderezos autenticados poden recibir correos doutros usuarios ou de notificación.
-É '''recomendable''' establecer esta opción nos wikis públicos para evitar abusos potenciais das características do correo.",
- 'config-email-sender' => 'Enderezo de correo electrónico de retorno:',
- 'config-email-sender-help' => 'Introduza o enderezo de correo electrónico a usar como enderezo de retorno dos correos de saída.
-Aquí é onde irán parar os correos rexeitados.
-Moitos servidores de correo electrónico esixen que polo menos a parte do nome de dominio sexa válido.',
- 'config-upload-settings' => 'Imaxes e carga de ficheiros',
- 'config-upload-enable' => 'Activar a carga de ficheiros',
- 'config-upload-help' => 'A subida de ficheiros expón potencialmente o servidor a riscos de seguridade.
-Para obter máis información, lea a [//www.mediawiki.org/wiki/Manual:Security sección de seguridade] no manual.
-
-Para activar a carga de ficheiros, cambie o modo no subdirectorio <code>images</code> que está baixo o directorio raíz de MediaWiki, de xeito que o servidor web poida escribir nel.
-A continuación, active esta opción.',
- 'config-upload-deleted' => 'Directorio para os ficheiros borrados:',
- 'config-upload-deleted-help' => 'Escolla un directorio no que arquivar os ficheiros borrados.
-O ideal é que non sexa accesible desde a web.',
- 'config-logo' => 'URL do logo:',
- 'config-logo-help' => 'A aparencia de MediaWiki por defecto inclúe espazo para un logo de 135x160 píxeles por riba do menú lateral.
-Cargue unha imaxe do tamaño axeitado e introduza o enderezo URL aquí.
-
-Pode utilizar <code>$wgStylePath</code> ou <code>$wgScriptPath</code> se o seu logo está relacionado con esas rutas.
-
-Se non quere un logo, deixe esta caixa en branco.',
- 'config-instantcommons' => 'Activar Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons] é unha característica que permite aos wikis usar imaxes, sons e outros ficheiros multimedia atopados no sitio da [//commons.wikimedia.org/wiki/Portada_galega Wikimedia Commons].
-Para facer isto, MediaWiki necesita acceso á internet.
-
-Para obter máis información sobre esta característica, incluíndo as instrucións sobre como configuralo para outros wikis que non sexan a Wikimedia Commons, consulte [//mediawiki.org/wiki/Manual:$wgForeignFileRepos o manual].',
- 'config-cc-error' => 'A escolla da licenza Creative Commons non deu resultados.
-Escriba o nome da licenza manualmente.',
- 'config-cc-again' => 'Escolla outra vez...',
- 'config-cc-not-chosen' => 'Escolla a licenza Creative Commons que desexe e prema en "continuar".',
- 'config-advanced-settings' => 'Configuración avanzada',
- 'config-cache-options' => 'Configuración da caché de obxectos:',
- 'config-cache-help' => 'A caché de obxectos emprégase para mellorar a velocidade de MediaWiki mediante a memorización de datos usados con frecuencia.
-É amplamente recomendable a súa activación nos sitios de tamaño medio e grande; os sitios pequenos obterán tamén beneficios.',
- 'config-cache-none' => 'Sen caché (non se elimina ningunha funcionalidade, pero pode afectar á velocidade en wikis grandes)',
- 'config-cache-accel' => 'Caché de obxectos do PHP (APC, XCache ou WinCache)',
- 'config-cache-memcached' => 'Empregar o Memcached (necesita unha instalación e configuración adicional)',
- 'config-memcached-servers' => 'Servidores da memoria caché:',
- 'config-memcached-help' => 'Lista de enderezos IP para Memcached.
-Debe especificarse un por liña, así como o porto a usar. Por exemplo:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Seleccionou Memcached como o seu tipo de caché, pero non especificou ningún servidor.',
- 'config-memcache-badip' => 'Escribiu un enderezo IP inválido para Memcached: $1.',
- 'config-memcache-noport' => 'Non especificou o porto a usar no servidor Memcached: $1.
-Se non sabe o porto, o predeterminado é 11211.',
- 'config-memcache-badport' => 'Os números de porto Memcached deben estar entre $1 e $2.',
- 'config-extensions' => 'Extensións',
- 'config-extensions-help' => 'As extensións anteriores detectáronse no seu directorio <code>./extensions</code>.
-
-Quizais necesite algunha configuración adicional, pero pode activalas agora',
- 'config-install-alreadydone' => "'''Atención:''' Semella que xa instalou MediaWiki e que o está a instalar de novo.
-Vaia ata a seguinte páxina.",
- 'config-install-begin' => 'Ao premer en "{{int:config-continue}}", comezará a instalación de MediaWiki.
-Se aínda quere facer algún cambio, prema en "{{int:config-back}}".',
- 'config-install-step-done' => 'feito',
- 'config-install-step-failed' => 'erro',
- 'config-install-extensions' => 'Incluíndo as extensións',
- 'config-install-database' => 'Configurando a base de datos',
- 'config-install-schema' => 'Creando o esquema',
- 'config-install-pg-schema-not-exist' => 'O esquema PostgreSQL non existe.',
- 'config-install-pg-schema-failed' => 'Fallou a creación de táboas.
-Asegúrese de que o usuario "$1" pode escribir no esquema "$2".',
- 'config-install-pg-commit' => 'Validando os cambios',
- 'config-install-pg-plpgsql' => 'Comprobación da lingua PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Cómpre instalar a lingua PL/pgSQL na base de datos $1',
- 'config-pg-no-create-privs' => 'A conta especificada para a instalación non ten os privilexios necesarios para crear unha conta.',
- 'config-pg-not-in-role' => 'A conta especificada para o usuario web xa existe.
-A conta que especificou para a instalación non é un superusuario e non pertence ao grupo de usuarios con acceso á web, polo que non pode crear obxectos pertencentes ao usuario da rede.
-
-Actualmente, MediaWiki necesita que as táboas sexan propiedade do usuario da rede. Especifique outro nome de conta web ou prema no botón "Atrás" e dea un usuario de instalación cos privilexios axeitados.',
- 'config-install-user' => 'Creando o usuario da base de datos',
- 'config-install-user-alreadyexists' => 'O usuario "$1" xa existe',
- 'config-install-user-create-failed' => 'A creación do usuario "$1" fallou: $2',
- 'config-install-user-grant-failed' => 'Fallou a concesión de permisos ao usuario "$1": $2',
- 'config-install-user-missing' => 'O usuario especificado, "$1", non existe.',
- 'config-install-user-missing-create' => 'O usuario especificado, "$1", non existe.
-Prema na caixa de verificación "crear unha conta" que hai a continuación se quere crear unha.',
- 'config-install-tables' => 'Creando as táboas',
- 'config-install-tables-exist' => "'''Atención:''' Semella que as táboas de MediaWiki xa existen.
-Saltando a creación.",
- 'config-install-tables-failed' => "'''Erro:''' Fallou a creación da táboa. Descrición do erro: $1",
- 'config-install-interwiki' => 'Enchendo a táboa de interwiki por defecto',
- 'config-install-interwiki-list' => 'Non se puido atopar o ficheiro <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Atención:''' Semella que a táboa de interwiki xa contén entradas.
-Saltando a lista por defecto.",
- 'config-install-stats' => 'Iniciando as estatísticas',
- 'config-install-keys' => 'Xerando as claves secretas',
- 'config-insecure-keys' => "'''Atención:''' {{PLURAL:$2|A clave de seguridade|As claves de seguridade}} ($1) {{PLURAL:$2|xerada|xeradas}} durante a instalación non {{PLURAL:$2|é|son}} completamente {{PLURAL:$2|segura|seguras}}. Considere a posibilidade de {{PLURAL:$2|cambiala|cambialas}} manualmente.",
- 'config-install-sysop' => 'Creando a conta de usuario de administrador',
- 'config-install-subscribe-fail' => 'Non se puido subscribir á lista mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL non está instalado e allow_url_fopen non está dispoñible.',
- 'config-install-mainpage' => 'Creando a páxina principal co contido por defecto',
- 'config-install-extension-tables' => 'Creando as táboas para as extensións activadas',
- 'config-install-mainpage-failed' => 'Non se puido inserir a páxina principal: $1',
- 'config-install-done' => "'''Parabéns!'''
-Instalou correctamente MediaWiki.
-
-O programa de instalación xerou un ficheiro <code>LocalSettings.php</code>.
-Este ficheiro contén toda a súa configuración.
-
-Terá que descargalo e poñelo na base da instalación do seu wiki (no mesmo directorio ca index.php). A descarga debería comezar automaticamente.
-
-Se non comezou a descarga ou se a cancelou, pode facer que comece de novo premendo na ligazón que aparece a continuación:
-
-$3
-
-'''Nota:''' Se non fai iso agora, este ficheiro de configuración xerado non estará dispoñible máis adiante se sae da instalación sen descargalo.
-
-Cando faga todo isto, xa poderá '''[$2 entrar no seu wiki]'''.",
- 'config-download-localsettings' => 'Descargar o <code>LocalSettings.php</code>',
- 'config-help' => 'axuda',
- 'config-nofile' => 'Non se puido atopar o ficheiro "$1". Se cadra, foi borrado.',
- 'config-extension-link' => 'Sabía que o seu wiki soporta [//www.mediawiki.org/wiki/Manual:Extensions extensións]?
-
-Pode explorar as [//www.mediawiki.org/wiki/Category:Extensions_by_category extensións por categoría] ou a [//www.mediawiki.org/wiki/Extension_Matrix matriz de extensións] para ollar a lista completa de extensións.',
- 'mainpagetext' => "'''MediaWiki instalouse correctamente.'''",
- 'mainpagedocfooter' => 'Consulte a [//meta.wikimedia.org/wiki/Help:Contents guía de usuario] para obter máis información sobre como usar o software wiki.
-
-== Primeiros pasos ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista das opcións de configuración]
-* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas máis frecuentes sobre MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo dos lanzamentos de MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localice MediaWiki á súa lingua]',
-);
-
-/** Goan Konkani (Latin script) (Konknni)
- * @author The Discoverer
- */
-$messages['gom-latn'] = array(
- 'config-page-language' => 'Bhas',
-);
-
-/** Ancient Greek (Ἀρχαία ἑλληνικὴ)
- * @author Crazymadlover
- * @author Omnipaedista
- */
-$messages['grc'] = array(
- 'config-page-language' => 'Γλῶττα',
- 'mainpagetext' => "'''Ἡ ἐγκατάστασις τῆς MediaWiki ἦν ἐπιτυχής'''",
- 'mainpagedocfooter' => 'Βουλευθήσεσθε τὰς [//meta.wikimedia.org/wiki/Help:Contents βουλὰς τοῖς Χρωμένοις] ἵνα πληροφορηθῇτε περὶ τοῦ βίκιλογισμικοῦ.
-
-== Ἄρξασθε ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Διαλογή παραμέτρων διαμορφώσεως]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki: τὰ πολλάκις αἰτηθέντα]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Διαλογή διαλέξεων ἐπὶ τῶν διανομῶν τῆς MediaWiki]', # Fuzzy
-);
-
-/** Swiss German (Alemannisch)
- * @author Als-Holder
- */
-$messages['gsw'] = array(
- 'config-desc' => 'S MediaWiki-Inschtallationsprogramm',
- 'config-title' => 'MediaWiki $1 inschtalliere',
- 'config-information' => 'Information',
- 'config-localsettings-upgrade' => "'''Warnig:''' E Datei <code>LocalSettings.php</code> isch gfunde wore.
-Fir d Aktualisierig vu dr däre Inschtallation, gib bitte dr Wärt vum Parameter <code>\$wgUpgradeKey</code> im Fäld unten yy.
-Du findsch dr Wärt in dr Datei <code>LocalSettings.php</code>.",
- 'config-localsettings-key' => 'Aktualisierigsschlissel:',
- 'config-localsettings-badkey' => 'Dr Aktualisierigsschlissel, wu du aagee hesch, isch falsch.',
- 'config-session-error' => 'Fähler bim Starte vu dr Sitzig: $1',
- 'config-session-expired' => 'D Sitzigsdate sin schyns abgloffe.
-Sitzige sin fir e Zytruum vu $1 konfiguriert.
-Dää cha dur Aalupfe vum Parameter <code>session.gc_maxlifetime</code> in dr Datei <code>php.ini</code> greßer gmacht wäre.
-Dr Inschtallationsvorgang nomol starte.',
- 'config-no-session' => 'Dyyni Sitzigsdate sin verlore gange!
-D Datei <code>php.ini</code> mueß prieft wäre un s mueß derby sichergstellt wäre, ass dr Parameter <code>session.save_path</code> uf s richtig Verzeichnis verwyyst.',
- 'config-your-language' => 'Dyy Sproch:',
- 'config-your-language-help' => 'Bitte d Sproch uuswehle, wu bim Inschtallationsvorgang soll brucht wäre.',
- 'config-wiki-language' => 'Wikisproch:',
- 'config-wiki-language-help' => 'Bitte d Sproch uuswehle, wu s Wiki in dr Hauptsach din gschribe wird.',
- 'config-back' => '← Zruck',
- 'config-continue' => 'Wyter →',
- 'config-page-language' => 'Sproch',
- 'config-page-welcome' => 'Willchuu bi MediaWiki!',
- 'config-page-dbconnect' => 'Mit dr Datebank verbinde',
- 'config-page-upgrade' => 'E Inschtallition, wu s scho het, aktualisiere',
- 'config-page-dbsettings' => 'Datebankyystellige',
- 'config-page-name' => 'Name',
- 'config-page-options' => 'Optione',
- 'config-page-install' => 'Inschtalliere',
- 'config-page-complete' => 'Fertig!',
- 'config-page-restart' => 'Inschtallation nomol aafange',
- 'config-page-readme' => 'Liis mi',
- 'config-page-releasenotes' => 'Hiiwys fir d Vereffentlichung',
- 'config-page-copying' => 'Am Kopiere',
- 'config-page-upgradedoc' => 'Am Aktualisiere',
- 'config-help-restart' => 'Witt alli Date, wu Du yygee hesch, lesche un d Inschtallation nomol aafange?',
- 'config-restart' => 'Jo, nomol aafange',
- 'config-welcome' => '=== Priefig vu dr Inschtallationsumgäbig ===
-Basispriefige wäre durgfiert zum Feschtstelle, eb d Inschtallationsumgäbig fir d Inschtallation vu MediaWiki geignet isch.
-Du sottsch d Ergebnis vu däre Priefig aagee, wänn Du bi dr Inschtallation Hilf bruchsch.',
- 'config-copyright' => "=== Copyright un Nutzigsbedingige ===
-
-$1
-
-Des Programm isch e freji Software, d. h. s cha, no dr Bedingige vu dr GNU General Public-Lizänz, wu vu dr Free Software Foundation vereffentligt woren isch, wyterverteilt un/oder modifiziert wäre. Doderbyy cha d Version 2, oder no eigenem Ermässe, jedi nejeri Version vu dr Lizänz brucht wäre.
-
-Des Programm wird in dr Hoffnig verteilt, ass es nitzli isch, aber '''ohni jedi Garanti''' un sogar ohni di impliziert Garanti vun ere '''Märtgängigkeit''' oder '''Eignig fir e bstimmte Zwäck'''. Doderzue git meh Hiiwys in dr GNU General Public-Lizänz.
-
-E <doclink href=Copying>Kopi vu dr GNU General Public-Lizänz</doclink> sott zämme mit däm Programm verteilt wore syy. Wänn des nit eso isch, cha ne Kopi bi dr Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftli aagforderet oder [http://www.gnu.org/copyleft/gpl.html online gläse] wäre.",
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki Websyte vu MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Nutzeraaleitig zue MediaWiki]
-* [//www.mediawiki.org/wiki/Manual:Contents Adminischtratoreaaleitig zue MediaWiki]
-* [//www.mediawiki.org/wiki/Manual:FAQ Vilmol gstellti Froge zue MediaWiki]', # Fuzzy
- 'config-env-good' => 'D Inschtallationsumgäbig isch prieft wore.
-Du chasch MediaWiki inschtalliere.',
- 'config-env-bad' => 'D Inschtallationsumgäbigisch prieft wore.
-Du chasch MediaWiki nit inschtalliere.',
- 'config-env-php' => 'PHP $1 isch inschtalliert.',
- 'config-unicode-using-utf8' => 'Fir d Unicode-Normalisierig wird em Brion Vibber syy utf8_normalize.so yygsetzt.',
- 'config-unicode-using-intl' => 'For d Unicode-Normalisierig wird d [http://pecl.php.net/intl PECL-Erwyterig intl] yygsetzt.',
- 'config-unicode-pure-php-warning' => "'''Warnig:''' D [http://pecl.php.net/intl PECL-Erwyterig intl] isch fir d Unicode-Normalisierig nit verfiegbar. Wäge däm wird di langsam pure-PHP-Implementierig brucht.
-Wänn Du ne Websyte mit ere große Bsuechrzahl bedrybsch, sottsch e weng ebis läse iber [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-Normalisierig (en)].",
- 'config-unicode-update-warning' => "'''Warnig:''' Di inschtalliert Version vum Unicode-Normalisierigswrapper verwändet e elteri Version vu dr Bibliothek vum [http://site.icu-project.org/ ICU-Projäkt].
-Du sottsch si [//www.mediawiki.org/wiki/Unicode_normalization_considerations aktualisiere], wänn Dor d Verwändig vu Unicode wichtig isch.",
- 'config-no-db' => 'S isch kei adäquate Datebanktryyber gfunde wore!', # Fuzzy
- 'config-no-fts3' => "'''Warnig:''' SQLite isch ohni s [//sqlite.org/fts3.html FTS3-Modul] kumpiliert wore, s stehn kei Suechfunktione z Verfiegig.",
- 'config-register-globals' => "'''Warnig: Dr Parameter <code>[http://php.net/register_globals register_globals]</code> vu PHP isch aktiviert.'''
-'''Är sott deaktiviert wäre, wänn des megli isch.'''
-D MediaWiki-Inschtallation lauft einwäg, aber dr Server isch aafällig fi megligi Sicherheitsprobläm.",
- 'config-magic-quotes-runtime' => "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]</code> vu PHP isch aktiviert!'''
-Die Yystellig fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.
-MediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
- 'config-magic-quotes-sybase' => "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> vu PHP isch aktiviert!'''
-Die Yystellig fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.
-MediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
- 'config-mbstring' => "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> vu PHP isch aktiviert!'''
-Die Yystellig verursacht Fähler un fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.
-MediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
- 'config-ze1' => "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]</code> vu PHP isch aktiviert!'''
-Die Yystellig fiert zue große Fähler bi MediaWiki.
-MediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
- 'config-safe-mode' => "'''Warnig:''' D Funktion <code>[http://www.php.net/features.safe-mode Safe Mode]</code> vu PHP isch aktiviert.
-Des cha zue Probläm fiere, vor allem wänn s Uffelade vu Dateie soll megli syy bzw. dr Uuszeichner <code>math</code> soll brucht wäre.",
- 'config-xml-bad' => 'S XML-Modul vu PHP fählt.
-MediaWiki brucht Funktione, wu au des Modul z Verfiegig stellt, un funktioniert in däre Konfiguration nit.
-Wänn Mandriva brucht wird, mueß no s „php-xml“-Paket inschtalliert wäre.',
- 'config-pcre' => 'S PHP-Modul fir d PCRE-Unterstitzig isch nit gfunde wore.
-MediaWiki brucht aber perl-kompatibli reguläri Uusdruck zum lauffähig syy.',
- 'config-pcre-no-utf8' => "'''Fatale Fähler: S PHP-Modul PCRE isch schyns ohni PCRE_UTF8-Unterstitzig kompiliert wore.'''
-MediaWiki 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.
-Dää Wärt isch wahrschyns z nider.
-Dr Inschtallationsvorgang chennt wäge däm fählschlaa!",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] isch inschtalliert',
- 'config-apc' => '[http://www.php.net/apc APC] isch inschtalliert',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] isch inschtalliert',
- 'config-no-cache' => "'''Warnig:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] hän nit chenne gfunde wäre.
-S Objäktcaching isch wäge däm nit aktiviert.",
- 'config-diff3-bad' => 'GNU diff3 isch nit gfunde wore.',
- 'config-imagemagick' => 'ImageMagick isch gfunde wore: <code>$1</code>.
-Miniaturaasichte vu Bilder sin megli, sobald s Uffelade vu Dateie aktiviert isch.',
- 'config-help' => 'Hilf',
- 'mainpagetext' => "'''MediaWiki isch erfolgrich inschtalliert worre.'''",
- 'mainpagedocfooter' => 'Lueg uf d [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentation fir d Aapassig vu dr Benutzeroberflächi] un s [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuech] fir d Hilf iber d Benutzig un s Yystelle.', # Fuzzy
-);
-
-/** Gujarati (ગુજરાતી)
- * @author Ashok modhvadia
- * @author Dineshjk
- */
-$messages['gu'] = array(
- 'mainpagetext' => "'''મિડીયાવિકિ સફળતાપૂર્વક ઇન્સટોલ થયું છે.'''",
- 'mainpagedocfooter' => 'વિકિ સોફ્ટવેર વાપરવાની માહીતિ માટે [//meta.wikimedia.org/wiki/Help:Contents સભ્ય માર્ગદર્શિકા] જુઓ.
-
-== શરૂઆતના તબક્કે ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings કોનફીગ્યુરેશન સેટીંગ્સની યાદી]
-* [//www.mediawiki.org/wiki/Manual:FAQ વારંવાર પુછાતા પ્રશ્નો]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce મિડીયાવિકિ રીલીઝ મેઇલીંગ લીસ્ટ]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localise MediaWiki for your language]',
-);
-
-/** Manx (Gaelg)
- */
-$messages['gv'] = array(
- 'mainpagetext' => "'''Ta MediaWiki currit stiagh nish.'''",
-);
-
-/** Hakka (Hak-kâ-fa)
- */
-$messages['hak'] = array(
- 'mainpagetext' => "'''Yí-kîn sṳ̀n-kûng ôn-chông MediaWiki.'''",
- 'mainpagedocfooter' => 'chhiáng fóng-mun [//meta.wikimedia.org/wiki/Help:Contents Yung-fu sú-chhak] yî-khi̍p sṳ́-yung chhṳ́ wiki ngiôn-khien ke sin-sit!
-
-== Ngi̍p-mùn ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki Phi-chṳ sat-thin chhîn-tân]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Phìn-sòng mun-thì kié-tap]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki fat-phu email chhîn-tân]', # Fuzzy
-);
-
-/** Hawaiian (Hawai`i)
- */
-$messages['haw'] = array(
- 'mainpagetext' => "'''Ua pono ka ho‘ouka ‘ana o MediaWiki.'''",
-);
-
-/** Hebrew (עברית)
- * @author Amire80
- * @author YaronSh
- * @author ערן
- * @author 아라
- */
-$messages['he'] = array(
- 'config-desc' => 'תכנית ההתקנה של מדיה־ויקי',
- 'config-title' => 'התקנת מדיה־ויקי $1',
- 'config-information' => 'פרטים',
- 'config-localsettings-upgrade' => 'זוהה קובץ <code>LocalSettings.php</code>.
-כדי לשדרג את ההתקנה הזאת, נא להקליד את הערך של <code>$wgUpgradeKey</code> בתיבה להלן.
-אפשר למצוא אותו בקובץ <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'זוהה קובץ <code>LocalSettings.php</code>.
-כדי לשדרג את ההתקנה הזאת, הריצו את <code>update.php</code> ולא את הקובץ הזה.',
- 'config-localsettings-key' => 'מפתח השדרוג:',
- 'config-localsettings-badkey' => 'המפתח שהקלדתם שגוי',
- 'config-upgrade-key-missing' => 'זוהתה התקנה קיימת של מדיה־ויקי.
-כדי לשדרג את ההתקנה הזאת, אנא כתבו את השורה הבא בתחתית קובץ <code>LocalSettings.php</code> שלכם:
-
-$1',
- 'config-localsettings-incomplete' => 'נראה שקובץ <code>LocalSettings.php</code> הקיים אינו שלם.
-המשתנה $1 אינו מוגדר.
-נו לשנות את קובץ <code>LocalSettings.php</code> שלכם כך שהמשתנה הזה יהיה מוגדר וללחוץ "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'אירעה שגיאה בעת חיבור למסד נתונים עם הגדרות ב־<code>LocalSettings.php</code> או ב־<code>AdminSettings.php</code>. נא לתקן את ההגדרות האלו ולנסות שוב.
-
-$1',
- 'config-session-error' => 'שגיאה באתחול שיחה: $1',
- 'config-session-expired' => 'נראה שנתוני השיחה שלכם פגו.
-השיחות מוגדרות להיות תקפות לזמן של $1.
-אפשר להגדיל את זה ב־<code>session.gc_maxlifetime</code> בקובץ php.ini.
-יש להתחיל מחדש את תהליך ההתקנה.',
- 'config-no-session' => 'נתוני השיחה שלכם אבדו!
-יש לבדוק את קובץ php.ini שלכם ולוודא שתיקייה נכונה מוגדרת ב־<code>session.save_path</code>.',
- 'config-your-language' => 'השפה שלכם:',
- 'config-your-language-help' => 'נא לבחור את השפה שתשמש במהלך ההתקנה.',
- 'config-wiki-language' => 'שפת הוויקי:',
- 'config-wiki-language-help' => 'נא לבחור את השפה העיקרית שבה ייכתב ויקי זה.',
- 'config-back' => '→ חזרה',
- 'config-continue' => 'המשך ←',
- 'config-page-language' => 'שפה',
- 'config-page-welcome' => 'ברוכים הבאים למדיה־ויקי!',
- 'config-page-dbconnect' => 'התחברות למסד הנתונים',
- 'config-page-upgrade' => 'שדרוג התקנה קיימת',
- 'config-page-dbsettings' => 'הגדרות מסד הנתונים',
- 'config-page-name' => 'שם',
- 'config-page-options' => 'אפשרויות',
- 'config-page-install' => 'התקנה',
- 'config-page-complete' => 'הושלמה!',
- 'config-page-restart' => 'הפעלת ההתקנה מחדש',
- 'config-page-readme' => 'קרא־אותי',
- 'config-page-releasenotes' => 'הערות גרסה',
- 'config-page-copying' => 'העתקה',
- 'config-page-upgradedoc' => 'שדרוג',
- 'config-page-existingwiki' => 'ויקי קיים',
- 'config-help-restart' => 'האם ברצונך לנקות את כל הנתונים שהזנת ולהתחיל מחדש את תהליך ההתקנה?',
- 'config-restart' => 'כן, להפעיל מחדש',
- 'config-welcome' => '=== בדיקות סביבה ===
-בדיקות בסיסיות מתבצעות כדי לבדוק שהסביבה מתאימה להתקנת מדיה־ויקי.
-יש לתת את תוצאות הבדיקות האלו אם תזדקקו לעזרה בזמן ההתקנה.',
- 'config-copyright' => "=== זכויות יוצרים ותנאים ===
-
-$1
-
-תכנית זו היא תכנה חופשית; באפשרותך להפיצה מחדש ו/או לשנות אותה על פי תנאי הרישיון הציבורי הכללי של GNU כפי שפורסם על ידי קרן התכנה החופשית; בין אם גרסה 2 של הרישיון, ובין אם (לפי בחירתך) כל גרסה מאוחרת שלו.
-
-תכנית זו מופצת בתקווה שתהיה מועילה, אבל '''בלא אחריות כלשהי'''; ואפילו ללא האחריות המשתמעת בדבר '''מסחריותה''' או '''התאמתה למטרה '''מסוימת'''. לפרטים נוספים, ניתן לעיין ברישיון הציבורי הכללי של GNU.
-
-לתכנית זו אמור היה להיות מצורף <doclink href=Copying>עותק של הרישיון הציבורי הכללי של GNU</doclink>; אם לא, עליך לכתוב ל־Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, , MA 02111-1307, USA. או [http://www.gnu.org/copyleft/gpl.html לקרוא אותו דרך האינטרנט].",
- 'config-sidebar' => '* [//www.mediawiki.org אתר הבית של מדיה־ויקי]
-* [//www.mediawiki.org/wiki/Help:Contents המדריך למשתמשים]
-* [//www.mediawiki.org/wiki/Manual:Contents המדריך למנהלים]
-* [//www.mediawiki.org/wiki/Manual:FAQ שו״ת]
-----
-* <doclink href=Readme>קרא אותי</doclink>
-* <doclink href=ReleaseNotes>הערות גרסה</doclink>
-* <doclink href=Copying>העתקה</doclink>
-* <doclink href=UpgradeDoc>שדרוג</doclink>',
- 'config-env-good' => 'הסביבה שלכם נבדקה.
-אפשר להתקין מדיה־ויקי.',
- 'config-env-bad' => 'הסביבה שלכם נבדקה.
-אי־אפשר להתקין מדיה־ויקי.',
- 'config-env-php' => 'מותקנת <span dir="ltr">PHP $1</span>.',
- 'config-env-php-toolow' => 'מותקנת <span dir="ltr">PHP $1</span>.
-למדיה־ויקי נדרשת <span dir="ltr">PHP $2</span> או גרסה גבוהה יותר.',
- 'config-unicode-using-utf8' => 'משתמש ב־normalize.so של בריון ויבר לנרמול יוניקוד.',
- 'config-unicode-using-intl' => 'משתמש בהרחבת [http://pecl.php.net/intl הרחבת intl PECL] לנרמול יוניקוד',
- 'config-unicode-pure-php-warning' => "'''אזהרה''': [http://pecl.php.net/intl הרחבת intl PECL] אינה זמינה לטיפול בנרמול יוניקוד. משתמש ביישום PHP טהור ואטי יותר.
-אם זהו אתר בעל תעבורה גבוהה, כדאי לקרוא את המסמך הבא: [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization].",
- 'config-unicode-update-warning' => "'''אזהרה''': הגרסה המותקנת של מעטפת נרמול יוניקוד משתמשת בגרסה ישנה של הספרייה של [http://site.icu-project.org/ פרויקט ICU].
-כדאי [//www.mediawiki.org/wiki/Unicode_normalization_considerations לעדכן] אם חשוב לכם הטיפול ביוניקוד.",
- 'config-no-db' => 'לא נמצא דרייבר מסד נתונים מתאים. יש להתקין דרייבר מסד נתונים ל־PHP.
-נתמכים הסוגים הבאים של מסדי נתונים: $1.
-
-אם אתם משתמשים באירוח משותף, בקשו מספק האירוח שלכם להתקין דרייבר מסד נתונים מתאים.
-אם קִמפלתם את PHP בעצמכם, הגדירו אותו מחדש והפעילו את לקוח מסד נתונים, למשל באמצעות <code dir="ltr">./configure --with-mysql</code>.
-אם התקנתם את PHP כחבילה של דביאן או של אובונטו, יש להתקין את המודול php5-mysql.',
- 'config-outdated-sqlite' => "'''אזהרה''': במערכת מתוקן SQLite $1. גרסה זו לא נתמכת ולשימוש ב SQLite נדרשת גרסה $2 ומעלה. מסיבה זו האפשרות SQLite לא מאופשרת.",
- 'config-no-fts3' => "'''אזהרה''': SQLite מקומפל ללא [//sqlite.org/fts3.html מודול FTS]. יכולות חיפוש לא יהיו זמינות בהתקנה הזאת.",
- 'config-register-globals' => "'''אזהרה: האפשרות <code>[http://php.net/register_globals register_globals]</code> של PHP מופעלת.'''
-'''כבו אותה אם אתם יכולים.'''
-מדיה־ויקי תעבוד, אבל השרת שלכם חשוף לפגיעות אבטחה.",
- 'config-magic-quotes-runtime' => "'''שגיאה סופנית: האפשרות [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] פעילה!'''
-האפשרות הזאת מעוותת את נתוני הקלט באופן בלתי־צפוי.
-לא ניתן להתקין את מדיה־ויקי אלא אם האפשרות הזאת תכובה.",
- 'config-magic-quotes-sybase' => "'''שגיאה סופנית''': האפשרות [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] פעילה!'''
-האפשרות הזאת מעוותת את נתוני הקלט באופן בלתי־צפוי.
-לא ניתן להתקין את מדיה־ויקי או להשתמש בה אלא אם האפשרות הזאת תכובה.",
- 'config-mbstring' => "'''שגיאה סופנית''': האפשרות [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] פעילה!'''
-האפשרות הזאת גורמת לשגיאות ומעוותת את נתוני הקלט באופן בלתי־צפוי.
-לא ניתן להתקין את מדיה־ויקי או להשתמש בה אלא אם האפשרות הזאת תכובה.",
- 'config-ze1' => "'''שגיאה סופנית''': האפשרות [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] פעילה!'''
-האפשרות הזאת גורמת לתקלות מזעזעות במדיה־ויקי.
-לא ניתן להתקין את מדיה־ויקי או להשתמש בה אלא אם האפשרות הזאת תכובה.",
- 'config-safe-mode' => "'''אזהרה:''' האפשרות [http://www.php.net/features.safe-mode safe mode] של PHP פעילה.
-היא יכולה לגרום לבעיות, במיוחד אם אתם משתמשים בהעלאת קבצים או ב־<code>math</code>.",
- 'config-xml-bad' => 'מודול XML של PHP חסר.
-מדיה־ויקי דורשת פונקציות של המודול ולא תעבוד עם הגדרות כאלו.
-אם מערכת ההפעלה שלהם היא Mandrake, התקינו את החבילה php-xml.',
- 'config-pcre' => 'נראה שחסרה תמיכה במודול PCRE.
-כדי שמדיה־ויקי תעבוד, נדרשת תמיכה בביטויים רגולריים תואמי Perl.',
- 'config-pcre-no-utf8' => "'''שגיאה סופנית''': נראה שמודול PCRE של PHP מקומפל ללא תמיכה ב־PCRE_UTF8.
-מדיה־ויקי דורשת תמיכה ב־UTF-8 לפעילות נכונה.",
- 'config-memory-raised' => 'ערך האפשרות <code>memory_limit</code> של PHP הוא $1, הועלה ל־$2.',
- 'config-memory-bad' => "'''אזהרה:''' ערך האפשרות <code>memory_limit</code> של PHP הוא $1.
-זה כנראה נמוך מדי.
-ההתקנה עשויה להיכשל!",
- 'config-ctype' => "'''שגיאה סופנית''': נדרשת גרסת PHP שתומכת בהרחבה [http://www.php.net/manual/en/ctype.installation.php Ctype].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] מותקן',
- 'config-apc' => '[http://www.php.net/apc APC] מותקן',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] מותקן',
- 'config-no-cache' => "'''אזהרה:''' אחת מהתוכנות הבאות לא נמצאה: [http://www.php.net/apc APC]&rlm;, [http://xcache.lighttpd.net/ XCache] או [http://www.iis.net/download/WinCacheForPhp WinCache].
-מטמון עצמים לא מופעל.",
- 'config-mod-security' => "'''אזהרה''': בשרת הווב שלכם מופעל [http://modsecurity.org/ mod_security]. אם הוא לא מוגדר טוב, זה יכול לגרום לבעיות במדיה־ויקי ובתכנה אחרת שמאפשרת למשתמשים לשלוח תוכן שרירותי.
-קראו את [http://modsecurity.org/documentation/ התיעוד של mod_security] או צרו קשר עם אנשי התמיכה של שירותי האירוח שלכם אם אתם נתקלים בשגיאות אקראיות.",
- 'config-diff3-bad' => 'GNU diff3 לא נמצא.',
- 'config-imagemagick' => 'נמצא ImageMagick&rlm;: <code dir="ltr">$1</code>.
-מזעור תמונות יופעל, אם תפעילו את האפשרות להעלות קבצים.',
- 'config-gd' => 'נמצאה ספריית הגרפיקה GD המובנית.
-מזעור תמונות יופעל, אם תפעילו את האפשרות להעלות קבצים.',
- 'config-no-scaling' => 'ספריית GD או ImageMagick לא נמצאו.
-מזעור תמונות לא יופעל.',
- 'config-no-uri' => "'''שגיאה:''' אי־אפשר לזהות את הכתובת הנוכחית.
-ההתקנה בוטלה.",
- 'config-no-cli-uri' => 'אזהרה: לא הוגדר <span dir="ltr">--scriptpath</span>, משתמש בבררת המחדל: <code dir="ltr">$1</code>.',
- 'config-using-server' => 'שם השרת בשימוש: "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'נעשה שימוש בכתובת השרת "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''אזהרה:''' תיקיית ההעלאות ההתחלתית <code>$1</code> חשופה להרצת סקריפטים שרירותיים.
-מדיה־ויקי בודקת את כל הקבצים המוּעלים לאיומי אבטחה, מומלץ מאוד למנוע את [//www.mediawiki.org/wiki/Manual:Security#Upload_security פרצת האבטחה] הזאת לפני שאתם מפעילים את ההעלאות.",
- 'config-no-cli-uploads-check' => "'''אזהרה:''' תיקיית בררת המחדל להעלאות (<code>$1</code>) לא נבדקת לפגיעוּת להרצת תסריטים אקראיים בזמן התקנה דרך CLI.",
- 'config-brokenlibxml' => 'במערכת שלכם יש שילוב של גרסאות של PHP ושל libxml2 שחשוף לבאגים ויכול לגרום לעיוות נתונים נסתר במדיה־ויקי וביישומי רשת אחרים.
-שדרגו ל־PHP 5.2.9 או לגרסה חדשה יותר ול־libxml2 2.7.3 או גרסה חדשה יותר ([//bugs.php.net/bug.php?id=45996 באג מתויק ב־PHP]).
-ההתקנה בוטלה.',
- 'config-using531' => 'אי־אפשר להשתמש במדיה־ויקי עם <span dir="ltr">PHP $1</span> בגלל באג בפרמטרים של הפניות (reference parameters) ל־<code dir="ltr">__call()</code>.
-שדרגו ל־PHP 5.3.2 או לגרסה גבוהה יותר כדי לתקן את זה ([//bugs.php.net/bug.php?id=50394 bug filed with PHP]) או שַנמכו ל־PHP 5.3.0 כדי לפתור את הבעיה הזאת.
-ההתקנה בוטלה.',
- 'config-suhosin-max-value-length' => 'מותקן פה Suhosin והוא מגביל את אורך פרמטר GET ל־$1 בתים. רכיב ResourceLoader של מדיה־ויקי יעקוף את המגלבה הזאת, אבל זה יפגע בביצועים. אם זה בכלל אפשרי, כדאי לתקן את הערך של <code>suhosin.get.max_value_length</code> ל־1024 או יותר בקובץ <code>php.ini</code> ולהגדיר את ‎<code>$wgResourceLoaderMaxQueryLength</code> לאותו הערך בקובץ LocalSettings.php.',
- 'config-db-type' => 'סוג מסד הנתונים:',
- 'config-db-host' => 'שרת מסד הנתונים:',
- 'config-db-host-help' => 'אם שרת מסד הנתונים שלכם נמצא על שרת אחר, הקלידו את שם המחשב או את כתובת ה־IP כאן.
-
-אם אתם משתמשים באירוח משותף, ספק האירוח שלכם אמור לתת לכם את שם השרת הנכון במסמכים.
-
-אם אתם מתקינים בשרת Windows ומשתמשים ב־MySQL, השימוש ב־localhost עשוי לא לעבוד. אם הוא לא עובד, נסו את "127.0.0.1" בתור כתובת ה־IP המקומית.
-
-אם אתם משתמשים ב־PostgreSQL, תשאירו את השדה הזה ריק כדי להתחבר דרך שקע יוניקס.',
- 'config-db-host-oracle' => 'TNS של מסד הנתונים:',
- 'config-db-host-oracle-help' => 'הקלידו [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm שם חיבור מקומי (Local Connect Name)] תקין; הקובץ tnsnames.ora צריך להיות זמין להתקנה הזאת.<br />
-אם אתם משתמשים ב־client libraries 10g או בגרסה חדשה יותר, אתם יכולים גם להשתמש בשיטת מתן השמות [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'זיהוי ויקי זה',
- 'config-db-name' => 'שם מסד הנתונים:',
- 'config-db-name-help' => 'בחרו שם שמזהה את הוויקי שלכם.
-לא צריכים להיות בו רווחים.
-
-אם אתם משתמשים באירוח משותף, ספק האירוח שלכם ייתן לכם שם מסד נתונים מסוים שתוכלו להשתמש בו או יאפשר לכם ליצור מסד נתונים דרך לוח בקרה.',
- 'config-db-name-oracle' => 'סכמה של מסד נתונים:',
- 'config-db-account-oracle-warn' => 'קיימים שלושה תרחישים נתמכים עבור התקנת אורקל בתור מסד הנתונים:
-
-אם אתם רוצים ליצור חשבון מסד נתונים כחלק מתהליך ההתקנה, נא לספק חשבון בעל תפקיד SYSDBA בתור חשבון מסד הנתונים עבור ההתקנה ולציין את האישורים המבוקשים עבור חשבון הגישה לאינטרנט, אחרת ניתן ליצור באופן ידני את חשבון הגישה לאינטרנט, ולספק חשבון זה בלבד (אם יש לו ההרשאות הדרושות ליצירת עצמי סכמה) או לספק שני חשבונות שונים, אחד עם הרשאות יצירה ואחד מוגבלת עבור גישה לאינטרנט.
-
-סקריפט ליצירת חשבון עם ההרשאות הנדרשות ניתן למצוא בתיקייה "<span dir="ltr">maintenance/oracle/</span>" של ההתקנה זו. זכרו כי שימוש בחשבון מוגבל יגרום להשבתת כל יכולות תחזוקה עם חשבון בררת המחדל.',
- 'config-db-install-account' => 'חשבון משתמש להתקנה',
- 'config-db-username' => 'שם המשתמש במסד הנתונים:',
- 'config-db-password' => 'הססמה במסד הנתונים:',
- 'config-db-password-empty' => 'נא להזין ססמה למשתמש מסד הנתונים החדש: $1.
-אף־על־פי שאפשר ליצור חשבונות ללא ססמה, זה לא מאובטח.',
- 'config-db-install-username' => 'הכניסו שם משתמש שישמש אתכם לחיבור למסד נתונים במהלך ההתקנה.
-זהו לא שם משתמש לחשבון במדיה־ויקי; זהו שם משתמש בשרת מסד נתונים.',
- 'config-db-install-password' => 'הקלידו ססמה שתשמש אתכם לצורך חיבור למסד נתונים במהלך ההתקנה.
-זוהי לא ססמה של חשבון במדיה־ויקי; זוהי ססמה לשרת מסד נתונים.',
- 'config-db-install-help' => 'הקלידו את שם המשתמש ואת הססמה להתחברות למסד הנתונים במהלך ההתקנה.',
- 'config-db-account-lock' => 'להשתמש באותו שם המשתמש ובאותה ססמה בזמן הפעלה רגילה',
- 'config-db-wiki-account' => 'חשבון משתמש להפעלה רגילה',
- 'config-db-wiki-help' => 'הקלידו את שם המשתמש והססמה לחיבור למסד הנתונים במהלך פעילות רגילה של הוויקי.
-אם החשבון אינו קיים ולחשבון שבו מתבצעת ההתקנה יש הרשאות מספיקות, החשבון הזה ייווצר עם ההרשאות המזעריות הנחוצות להפעלת הוויקי.',
- 'config-db-prefix' => 'תחילית לטבלאות של מסד נתונים (database table prefix):',
- 'config-db-prefix-help' => 'אם אתם צריכים לשתף מסד נתונים אחד בין אתרי ויקי שונים או בין מדיה־ויקי ויישום וב אחר, תוכלו לבחור להוסיף תחילית וכל שמות הטבלאות כדי להימנע מהתנגשויות.
-אל תשתמשו ברווחים.
-
-השדה הזה בדרך כלל אמור להיות ריק.',
- 'config-db-charset' => 'קבוצת התווים (character set) של מסד הנתונים',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 backwards-compatible UTF-8',
- 'config-charset-help' => "'''אזהרה:''' אם אתם משתמשים ב־'''backwards-compatible UTF-8''' ב־<span dir=\"ltr\">MySQL 4.1+</span>, ומגבים את מסד הנתונים באמצעות <code>mysqldump</code>, זה יכול להרוס את כל תווי ה־ASCII ויהרוס באופן בלתי־הפיך את הגיבויים שלכם!
-
-ב'''מצב בינרי''' (binary mode) מדיה־ויקי שומרת טקסט UTF-8 במסד הנתונים בשדות בינריים.
-זה יעיל יותר ממצב UTF-8 של MySQL ומאפשר לכם להשתמש בכל הטווח של תווי יוניקוד.
-ב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קבוצת התווים (character set) של הטקסט שלכם ויציג וימיר אותו בהתאם, אבל לא יאפשר לכם לשמור תווים שאינם נמצאים בטווח הרב־לשוני הבסיסי ([//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane]).",
- 'config-mysql-old' => 'נדרשת גרסה <span dir="ltr">$1</span> של MySQL או גרסה חדשה יותר. הגרסה הנוכחית שלכם היא $2.',
- 'config-db-port' => 'פִּתְחַת מסד הנתונים (database port):',
- 'config-db-schema' => 'סכמה למדיה־ויקי',
- 'config-db-schema-help' => 'הסְכֵמָה הבאה בדרך כלל מתאימה.
-שנו אותה רק אם אתם יודעים שאתם חייבים.',
- 'config-pg-test-error' => "ההתחברות למסד הנתונים '''$1''' לא מצליחה: $2",
- 'config-sqlite-dir' => 'תיקיית נתונים (data directory) של SQLite:',
- 'config-sqlite-dir-help' => 'SQLite שומר את כל הנתונים בקובץ אחד.
-
-לשרת הווב צריכה להיות הרשאה לכתוב לתיקייה שאתם מגדירים.
-
-היא לא צריכה נגישה לכולם דרך האינטרנט – בגלל זה איננו שמים אותה באותו מקום עם קובצי ה־PHP.
-
-תוכנת ההתקנה תכתוב קובץ <code dir="ltr">.htaccess</code> יחד אִתו, אבל אם זה ייכשל, מישהו יוכל להשיג גישה למסד הנתונים שלכם. שם נמצא מידע מפורש של משתמשים (כתובות דוא״ל, ססמאות מגובבות) וגם גרסאות מחוקות של דפים ומידע מוגבל אחר.
-
-כדאי לשקול לשים את מסד הנתונים במקום אחר לגמרי, למשל ב־<code dir="ltr">/var/lib/mediawiki/yourwik</code>.',
- 'config-oracle-def-ts' => 'מרחב טבלאות לפי בררת מחדל (default tablespace):',
- 'config-oracle-temp-ts' => 'מרחב טבלאות זמני (temporary tablespace):',
- 'config-support-info' => 'מדיה־ויקי תומכת במערכות מסדי הנתונים הבאות:
-
-$1
-
-אם אינכם רואים את מסד הנתונים שלכם ברשימה, עקבו אחר ההוראות המקושרות לעיל כדי להפעיל את התמיכה.',
- 'config-support-mysql' => '* $1 הוא היעד העיקרי עבור מדיה־ויקי ולו התמיכה הטובה ביותר (ר׳ [http://www.php.net/manual/en/mysql.installation.php how to compile PHP with MySQL support])',
- 'config-support-postgres' => '$1 הוא מסד נתונים נפוץ בקוד פתוח והוא נפוץ בתור חלופה ל־MySQL (ר׳ [http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]). ייתכן שיש בתצורה הזאת באגים מסוימים והיא לא מומלצת לסביבות מבצעיות.',
- 'config-support-sqlite' => '* $1 הוא מסד נתונים קליל עם תמיכה טובה מאוד. (ר׳ [http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], משתמש ב־PDO)',
- 'config-support-oracle' => '* $1 הוא מסד נתונים עסקי מסחרי. (ר׳ [http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
- 'config-header-mysql' => 'הגדרות MySQL',
- 'config-header-postgres' => 'הגדרות PostgreSQL',
- 'config-header-sqlite' => 'הגדרות SQLite',
- 'config-header-oracle' => 'הגדרות Oracle',
- 'config-invalid-db-type' => 'סוג מסד הנתונים שגוי',
- 'config-missing-db-name' => 'עליך להזין ערך עבור "שם מסד הנתונים"',
- 'config-missing-db-host' => 'יש להכניס ערך לשדה "שרת מסד הנתונים"',
- 'config-missing-db-server-oracle' => 'יש להכניס ערך לשדה "TNS של מסד הנתונים"',
- 'config-invalid-db-server-oracle' => '"$1" הוא TNS בלתי תקין.
-יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9), קווים תחתיים (_) ונקודות (.).',
- 'config-invalid-db-name' => '"$1" הוא שם מסד נתונים בלתי תקין.
-יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9), קווים תחתיים (_) ומינוסים (-).',
- 'config-invalid-db-prefix' => '"$1" היא תחילית מסד נתונים בלתי תקינה.
-יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9), קווים תחתיים (_) ומינוסים (-).',
- 'config-connection-error' => '<div dir="ltr">$1.</div>
-
-בדקו את שם השרת, את שם המשתמש ואת הססמה בטופס להלן ונסו שוב.',
- 'config-invalid-schema' => '"$1" היא סכמה לא תקינה עבור מדיה־ויקי.
-יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9) וקווים תחתיים (_).',
- 'config-db-sys-create-oracle' => 'תוכנית ההתקנה תומכת רק בשימוש בחשבון SYSDBA ליצירת חשבון חדש.',
- 'config-db-sys-user-exists-oracle' => 'חשבון המשתמש "$1" כבר קיים. SYSDBA יכול לשמש רק ליצירת חשבון חדש!',
- 'config-postgres-old' => 'נדרש PostgreSQL $1 או גרסה חדשה יותר, הגרסה הנוכחית שלכם היא $2.',
- 'config-sqlite-name-help' => 'בחרו בשם שמזהה את הוויקי שלכם.
-אל תשתמשו ברווחים או במינוסים.
-זה יהיה שם קובץ הנתונים ל־SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'לא ניתן ליצור את תיקיית הנתונים <code><nowiki>$1</nowiki></code>, כי לשָׁרַת הווב אין הרשאות לכתוב לתיקיית האם <code><nowiki>$2</nowiki></code> .
-
-תוכנת ההתקנה זיהתה את החשבון שתחתיו רץ שרת הווב שלכם.
-אפשרו לשָׁרַת הווב לכתוב לתיקייה <code><nowiki>$3</nowiki></code>.
-במערכת Unix/Linux כִתבו:
-
-<div dir="ltr"><pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre></div>',
- 'config-sqlite-parent-unwritable-nogroup' => 'לא ניתן ליצור את תיקיית הנתונים <code><nowiki>$1</nowiki></code>, כי לשָׁרַת הווב אין הרשאות לכתוב לתיקיית האם <code><nowiki>$2</nowiki></code> .
-
-תוכנת ההתקנה לא זיהתה את החשבון שתחתיו רץ שרת הווב שלכם.
-אפשרו לכל החשבונות לכתוב לתיקייה <code><nowiki>$3</nowiki></code> כדי להמשיך.
-במערכת Unix/Linux כִתבו:
-
-<div dir="ltr"><pre>cd $2
-mkdir $3
-chmod a+w $3</pre></div>',
- 'config-sqlite-mkdir-error' => 'אירעה שגיאה בעת יצירת תיקיית הנתונים "$1".
-נא לבדוק את המיקום ולנסות שוב.',
- 'config-sqlite-dir-unwritable' => 'אי־אפשר לכתוב לתיקייה "$1".
-שנו את ההרשאות שלה כך ששרת הווב יוכל לכתוב אליה ונסו שוב.',
- 'config-sqlite-connection-error' => '$1.
-
-בִדקו את תיקיית הנתונים את שם מסת הנתונים להלן ונסו שוב.',
- 'config-sqlite-readonly' => 'לא ניתן לכתוב אל הקובץ <code>$1</code>.',
- 'config-sqlite-cant-create-db' => 'לא ניתן ליצור את קובץ מסד הנתונים <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'ב־PHP חסרה תמיכה ב־FTS3, יבתצע שנמוך טבלאות',
- 'config-can-upgrade' => "יש טבלאות מדיה־ויקי במסד הנתונים.
-כדי לשדרג אותן למדיה־ויקי $1, לחצו '''המשך'''.",
- 'config-upgrade-done' => "השדרוג הושלם.
-
-עכשיו אפשר [$1 להשתמש בוויקי שלכם].
-
-אם תרצו ליצור מחדש את קובץ ה־<code>LocalSettings.php</code> שלכם, לחצו על הכפתור להלן.
-זה '''לא מומלץ''', אלא אם כן יש לכם בעיות עם הוויקי שלכם.",
- 'config-upgrade-done-no-regenerate' => 'השדרוג הושלם.
-
-עכשיו אפשר [$1 להתחיל להשתמש בוויקי שלכם].',
- 'config-regenerate' => 'לחולל מחדש את LocalSettings.php ←',
- 'config-show-table-status' => 'שאילתת <code>SHOW TABLE STATUS</code> נכשלה!',
- 'config-unknown-collation' => "'''אזהרה:''' מסד הנתונים משתמש בשיטת מיון שאינה מוּכּרת.",
- 'config-db-web-account' => 'חשבון במסד הנתונים לגישה מהרשת',
- 'config-db-web-help' => 'לבחור את שם המשתמש ואת הססמה ששרת הווב ישתמש בו להתחברות לשרת מסד הנתונים בזמן פעילות רגילה של הוויקי.',
- 'config-db-web-account-same' => 'להשתמש באותו חשבון כמו עבור ההתקנה',
- 'config-db-web-create' => 'ליצור חשבון אם הוא אינו קיים כבר.',
- 'config-db-web-no-create-privs' => 'לחשבון שהקלדתם להתקנה אין מספיק הרשאות ליצירת חשבות.
-החשבון שאתם מקלידים כאן צריך להיות קיים.',
- 'config-mysql-engine' => 'מנגנון האחסון:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''אזהרה''': בחרתם ב־MyISAM בתור מנוע אחסון של MySQL, וזה לא מומלץ מהסיבות הבאות:
-* המנוע הזה בקושי תומך בעיבוד מקבילי בגלל נעילת טבלאות
-* הוא פחות עמיד בפני אובדן מידע ממנועים אחרים
-* הקוד של מדיה־ויקי לא תמיד מטפל ב־MyISAM כפי שצריך
-
-אם התקנת MySQL שלכם תומכת ב־InnoDB, מומלץ מאוד שתבחרו באפשרות הזאת.
-אם התקנת MySQL שלכם אינה תומכת ב־InnoDB, אולי זה הזמן לשקול לשדרג אותה.",
- 'config-mysql-engine-help' => "'''InnoDB''' הוא כמעט תמיד האפשרות הטובה ביותר, כי במנוע הזה יש תמיכה טובה ביותר בעיבוד מקבילי.
-
-'''MyISAM''' עשוי להיות בהתקנות שמיועדות למשתמש אחד ולהתקנות לקריאה בלבד.
-מסדי נתונים עם MyISAM נוטים להיהרס לעתים קרובות יותר מאשר מסדי נתונים עם InnoDB.",
- 'config-mysql-charset' => 'ערכת הקידוד של מסד הנתונים:',
- 'config-mysql-binary' => 'בינרי',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "ב'''מצב בינרי''' (binary mode) מדיה־ויקי שומרת טקסט UTF-8 במסד הנתונים בשדות בינריים.
-זה יעיל יותר ממצב UTF-8 של MySQL ומאפשר לכם להשתמש בכל הטווח של תווי יוניקוד.
-
-ב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קבוצת התווים (character set) של הטקסט שלכם ויציג וימיר אותו בהתאם, אבל לא יאפשר לכם לשמור תווים שאינם נמצאים בטווח הרב־לשוני הבסיסי ([//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane]).",
- 'config-site-name' => 'שם הוויקי:',
- 'config-site-name-help' => 'זה יופיע בשורת הכותרת של הדפדפן ובמקומות רבים אחרים.',
- 'config-site-name-blank' => 'נא להזין שם לאתר.',
- 'config-project-namespace' => 'מרחב שמות לדפי מיזם:',
- 'config-ns-generic' => 'מיזם',
- 'config-ns-site-name' => 'זהה לשם הוויקי: $1',
- 'config-ns-other' => 'אחר (לציין)',
- 'config-ns-other-default' => 'הוויקי שלי',
- 'config-project-namespace-help' => "בהתאם לדוגמה של ויקיפדיה, אתרי ויקי רבים שומרים על דפי המדיניות שלהם בנפרד מדפי התוכן שלהם ב\"'''מרחב השמות של המיזם'''\" (\"'''project namespace'''\").
-כל שמות הדפים במרחב השמות הזה מתחילים בתחילית מסוימת שאתם יכולים להגדיר כאן.
-באופן מסורתי התחילית הזאת מבוססת על שם הוויקי, והיא אינה יכולה להכיל תווי פיסוק כגון \"#\" או \":\".",
- 'config-ns-invalid' => 'מרחב השמות "<nowiki>$1</nowiki>" אינו תקין.
-הקלידו שם אחר למרחב השמות של המיזם.',
- 'config-ns-conflict' => 'מרחב השמות שהגדרתם "<nowiki>$1</nowiki>" מתנגש עם מרחב שמות מובנה של מדיה־ויקי.
-הגדירו מרחב שמות מיזם שונה.',
- 'config-admin-box' => 'חשבון מפעיל',
- 'config-admin-name' => 'שמכם:',
- 'config-admin-password' => 'ססמה:',
- 'config-admin-password-confirm' => 'הססמה שוב:',
- 'config-admin-help' => 'הקלידו כאן את שם המשתמש, למשל "שקד לוי" או "Joe Bloggs".
-זה השם שישמש אתכם כדי להיכנס לוויקי.',
- 'config-admin-name-blank' => 'נא להזין את שם המשתמש של המפעיל.',
- 'config-admin-name-invalid' => 'שם המשתמש שהוקלד "<nowiki>$1</nowiki>" אינו תקין.
-הקלידו שם משתמש אחר.',
- 'config-admin-password-blank' => 'הקלידו ססמה לחשבון המפעיל.',
- 'config-admin-password-same' => 'הססמה לא יכולה להיות זהה לשם המשתמש.',
- 'config-admin-password-mismatch' => 'שתי הססמאות שהוזנו אינן מתאימות.',
- 'config-admin-email' => 'כתובת הדוא״ל:',
- 'config-admin-email-help' => 'הקלידו כתובת דוא״ל שתאפשר לכם לקבל מכתבים ממשתמשים אחרים בוויקי, לאתחל את הססמה, ולקבל הודעות על שינויים בדפים ברשימת המעקב שלכם. אפשר להשאיר את השדה הזה ריק.',
- 'config-admin-error-user' => 'שגיאה פנימית ביצירת מפעיל בשם "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'שגיאה פנימית בהגדרת ססמה עבור המפעיל "<nowiki>$1</nowiki>"&rlm;: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'הכנסתם כתובת דוא״ל לא תקינה.',
- 'config-subscribe' => 'להירשם ל[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה עם הודעות על גרסאות חדשות].',
- 'config-subscribe-help' => 'זוהי רשימת תפוצה עם הודעות מעטות שמשמשת להודעות על הוצאת גרסאות, כולל עדכוני אבטחה חשובים.
-מומלץ להירשם אליה ולעדכן את מדיה־ויקי כאשר יוצאות גרסאות חדשות.',
- 'config-subscribe-noemail' => 'ניסיתם להירשם לרשימת תפוצה של הודעות בלי לתת כתובת דוא"ל.
-נא לתת כתובת דוא"ל אם אתם רוצים להירשם לרשימת התפוצה.',
- 'config-almost-done' => 'כמעט סיימתם!
-אפשר לדלג על שאר ההגדרות ולהתקין את הוויקי כבר עכשיו.',
- 'config-optional-continue' => 'הצגת שאלות נוספות.',
- 'config-optional-skip' => 'משעמם לי, תתקינו לי כבר את הוויקי הזה.',
- 'config-profile' => 'תסריט הרשאות משתמשים:',
- 'config-profile-wiki' => 'ויקי פיתוח',
- 'config-profile-no-anon' => 'נדרשת יצירת חשבון',
- 'config-profile-fishbowl' => 'עורכים מורשים בלבד',
- 'config-profile-private' => 'ויקי פרטי',
- 'config-profile-help' => "אתרי ויקי עובדים הכי טוב כאשר אתם מאפשרים לכמה שיותר אנשים לערוך אותם.
-במדיה־ויקי קל לסקור את השינויים האחרונים ולשחזר כל נזק שעושים משתמשים תמימים או משחיתים.
-
-עם זאת, אנשים שונים מצאו למדיה־ויקי שימושים מגוּונים ולעתים לא קל לשכנע את כולם ביתרונות של \"דרך הוויקי\" המסורתית. ולכן יש לכם בררה.
-
-באתר מסוג '''{{int:config-profile-wiki}}''' – לכולם יש הרשאה לערוך, אפילו בלי להיכנס לחשבון.
-באתר וויקי מסוג '''{{int:config-profile-no-anon}}''' יש ביטחון גדול יותר, אבל הגדרה כזאת יכולה להרתיע תורמים מזדמנים.
-
-בתסריט '''{{int:config-profile-fishbowl}}''' רק משתמשים שקיבלו אישור יכולים לערוך, אבל כל הגולשים יכולים לקרוא את הדפים ואת גרסאותיהם הקודמות.
-ב'''{{int:config-profile-private}}''' רק משתמשים שקיבלו אישור יכולים לקרוא ולערוך דפים.
-
-הגדרות מורכבות של הרשאות אפשריות אחרי ההתקנה, ר׳ את [//www.mediawiki.org/wiki/Manual:User_rights הפרק על הנושא הזה בספר ההדרכה].",
- 'config-license' => 'זכויות יוצרים ורישיון:',
- 'config-license-none' => 'ללא כותרת תחתית עם רישיון',
- 'config-license-cc-by-sa' => 'קריאייטיב קומונז–ייחוס–שיתוף זהה',
- 'config-license-cc-by' => 'קריאייטיב קומונז–ייחוס',
- 'config-license-cc-by-nc-sa' => 'קריאייטיב קומונז ייחוס–ללא שימוש מסחרי–שיתוף זהה',
- 'config-license-cc-0' => 'קריאייטיב קומונז אפס (נחלת הכלל)',
- 'config-license-gfdl' => 'רישיון חופשי למסמכים של גנו גרסה 1.3 או חדשה יותר',
- 'config-license-pd' => 'נחלת הכלל',
- 'config-license-cc-choose' => 'בחרו רישיון קריאייטיב קומונז מותאם אישית',
- 'config-license-help' => "אתרי ויקי ציבוריים רבים מפרסמים את כל התרומות תחת [http://freedomdefined.org/Definition רישיון חופשי].
-זה עוזר ליצור תחושה של בעלות קהילתית ומעודד תרומה לאורך זמן.
-זה בדרך כלל לא נחוץ לאתר ויקי פרטי או בחברה מסחרית.
-
-אם אתם רוצים אפשרות להשתמש בטקסט מוויקיפדיה ואתם רוצים שוויקיפדיה תוכל לקבל עותקים של טקסטים מהוויקי שלכם, כדאי לכם לבחור ב'''רישיון קריאייטיב קומונז ייחוס–שיתוף זהה''' (CC-BY-SA).
-
-ויקיפדיה השתמשה בעבר ברישיון החופשי למסמכים של גנו (GNU FDL או GFDL).
-הוא עדיין רישיון תקין, אבל קשה להבנה.
-כמו־כן, קשה לעשות שימוש חוזר ביצירות שפורסמו לפי GFDL.",
- 'config-email-settings' => 'הגדרות דוא״ל',
- 'config-enable-email' => 'להפעיל דוא״ל יוצא',
- 'config-enable-email-help' => 'אם אתם רוצים שדוא״ל יעבוד, [http://www.php.net/manual/en/mail.configuration.php אפשרויות הדוא״ל של PHP] צריכות להיות מוגדרות נכון.
-אם אינכם רוצים להפעיל שום אפשרויות דוא״ל, כבו אותן כאן ועכשיו.',
- 'config-email-user' => 'להפעיל שליחת דוא״ל ממשתמש למשתמש',
- 'config-email-user-help' => 'לאפשר לכל המשתמשים לשלוח אחד לשני דוא״ל אם הם הפעילו את זה בהעדפות שלהם.',
- 'config-email-usertalk' => 'להפעיל הודעות על דף שיחת משתמש',
- 'config-email-usertalk-help' => 'לאפשר למשתמשים לקבל הודעות על שינויים בדפי המשתמש שלהם, אם הם הפעילו את זה בהעדפות שלהם.',
- 'config-email-watchlist' => 'הפעלת התרעה על רשימת המעקב',
- 'config-email-watchlist-help' => 'לאפשר למשתמשים לקבל הודעות על הדפים ברשימת המעקב שלהם אם הם הפעילו את זה בהעדפות שלהם.',
- 'config-email-auth' => 'הפעלת התרעה בדוא״ל',
- 'config-email-auth-help' => "אם האפשרות הזאת מופעלת, משתמשים יצטרכו לאשר את כתובת הדוא״ל שלהם באמצעות קישור שיישלח אליהם בכל פעם שהם יגדירו או ישנו אותה.
-רק כתובות דוא״ל מאושרות יכולות לקבלת דוא״ל ממשתמשים אחרים או מכתבים עם הודעות על שינויים.
-'''מומלץ''' להגדיר את האפשרות הזאת לאתרי ויקי ציבוריים כי אפשר לעשות שימוש לרעה בתכונות הדוא״ל.",
- 'config-email-sender' => 'כתובת דוא״ל לתשובות:',
- 'config-email-sender-help' => 'הכניסו את כתובת הדוא״ל שתשמש ככתובת לתשובה לכל הדואר היוצא.
-לשם יישלחו תגובות שגיאה (bounce).
-שרתי דוא״ל רבים דורשים שלפחות החלק של המתחם יהיה תקין.',
- 'config-upload-settings' => 'העלאת קבצים ותמונות',
- 'config-upload-enable' => 'להפעיל העלאת קבצים',
- 'config-upload-help' => 'העלאות קבצים חושפות את השרת שלכם לסיכוני אבטחה.
-למידע נוסף, קִראו את [//www.mediawiki.org/wiki/Manual:Security חלק האבטחה] בספר ההדרכה.
-
-כדי להפעיל העלאת קבצים שנו את ההרשאות של התיקייה <code>images</code> תחת תיקיית השורש של מדיה־ויקי כך ששרת הווב יוכל לכתוב אליה.
-זה מפעיל את האפשרות הזאת.',
- 'config-upload-deleted' => 'תיקיית לקבצים שנמחקו:',
- 'config-upload-deleted-help' => 'בחרו את התיקייה לארכוב קבצים מחוקים.
-כדאי שזה לא יהיה נגיש לכל העולם דרך הרשת.',
- 'config-logo' => 'כתובת הסמל:',
- 'config-logo-help' => 'המראה ההתחלתי של מדיה־ויקי מכיל מקום לסמל של 135 על 160 פיקסלים בפינה העליונה מעל תפריט הצד.
-יש להעלות תמונה בגודל מתאים ולהכניס את הכתובת כאן.
-
-אם אינכם רוצים סמל, השאירו את התיבה הזאת ריקה.', # Fuzzy
- 'config-instantcommons' => 'להפעיל את Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] היא תכונה שמאפשרת לאתרי ויקי להשתמש בתמונות, בצלילים ובמדיה אחרת שנמצאת באתר [//commons.wikimedia.org/ ויקישיתוף] (Wikimedia Commons).
-כדי לעשות את זה, מדיה־ויקי צריך לגשת לאינטרנט.
-
-למידע נוסף על התכונה הזאת, כולל הוראות איך להפעיל את זה לאתרי ויקי שאינם ויקישיתוף, ר׳ [//mediawiki.org/wiki/Manual:$wgForeignFileRepos את ספר ההדרכה].',
- 'config-cc-error' => 'בורר רישיונות קריאייטיב קומונז לא החזיר שום תוצאה.
-הקלידו את שם הרישיון ידנית.',
- 'config-cc-again' => 'נא לבחור שוב...',
- 'config-cc-not-chosen' => 'בחרו באיזה רישיון קריאייטיב קומונז להשתמש ולחצו "המשך".',
- 'config-advanced-settings' => 'הגדרות מתקדמות',
- 'config-cache-options' => 'הגדרות למטמון עצמים (object caching):',
- 'config-cache-help' => 'מטמון עצמים משמש לשיפור המהירות של מדיה־ויקי על־ידי שמירה של נתונים שהשימוש בהם נפוץ במטמון.
-לאתרים בינוניים וגדולים כדאי מאוד להפעיל את זה, וגם אתרים קטנים ייהנו מזה.',
- 'config-cache-none' => 'ללא מטמון (שום יכולת אינה מוּסרת, אבל הביצועים באתרים גדולים ייפגעו)',
- 'config-cache-accel' => 'מטמון עצמים (object caching) של PHP&rlm; (APC&rlm;, XCache או WinCache)',
- 'config-cache-memcached' => 'להשתמש ב־Memcached (דורש התקנות והגדרות נוספות)',
- 'config-memcached-servers' => 'שרתי Memcached:',
- 'config-memcached-help' => 'רשימת כתובות IP ש־Memcached ישתמש בהן.
-יש לרשום כתובת אחת בכל שורה ולציין את הפִּתְחָה (port), למשל:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'בחרת ב־Memcached בתתור סוג המטמון שלכם, אבל לא הגדרתם שום שרת.',
- 'config-memcache-badip' => 'הקלדתם כתובת IP בלתי תקינה ל־Memcached&lrm;: $1.',
- 'config-memcache-noport' => 'לא הגדרתם פתחה לשימוש שרת Memcached&rlm;: $1.
-אם אינכם יודעים את מספר הפתחה, בררת המחדל היא 11211.',
- 'config-memcache-badport' => 'מספרי פתחה של Memcached צריכים להיות בין $1 ל־$2',
- 'config-extensions' => 'הרחבות',
- 'config-extensions-help' => 'ההרחבות ברשימה לעיל התגלו בתיקיית <span dir="ltr"><code>./extensions</code></span> שלכם.
-
-ייתכן שזה ידרוש הגדרות נוספות, אבל תוכלו להפעיל אותן עכשיו.',
- 'config-install-alreadydone' => "'''אזהרה:''' נראה שכבר התקנתם את מדיה־ויקי ואתם מנסים להתקין אותה שוב.
-אנה התקדמו לדף הבא.",
- 'config-install-begin' => 'כשתלחצו על "{{int:config-continue}}", תתחילו את ההתקנה של מדיה־ויקי.
-אם אתם עדיין רוצים לשנות משהו, לחצו על "{{int:config-back}}"',
- 'config-install-step-done' => 'בוצע',
- 'config-install-step-failed' => 'נכשל',
- 'config-install-extensions' => 'כולל הרחבות',
- 'config-install-database' => 'הקמת מסד נתונים',
- 'config-install-schema' => 'יצירת סכמה',
- 'config-install-pg-schema-not-exist' => 'סכמה של PostgreSQL אינה קיימת',
- 'config-install-pg-schema-failed' => 'יצירת טבלאות נכשלה.
-ודאו כי המשתמש "$1" יכול לכתוב לסכמה "$2".',
- 'config-install-pg-commit' => 'שמירת שינויים',
- 'config-install-pg-plpgsql' => 'בדיקת שפת PL/pgSQL',
- 'config-pg-no-plpgsql' => 'צריך להתקין את שפת PL/pgSQL במסד הנתונים $1',
- 'config-pg-no-create-privs' => 'לחשבון שהגדרתם להתקנה אין מספיק הרשאות ליצירת חשבון.',
- 'config-pg-not-in-role' => 'החשבון שציינתם עבור משתמש שרת הווב כבר קיים.
-החשבון שסיפקתם להתקנה אינו חשבון בעל הרשאות (superuser) ואינו חבר בתפקיד (role) של משתמש שרת הווב, אז אין אפשרות ליצור עצמים בבעלות משתמש שרת הווב.
-
-כעת נדרש במדיה־ויקי שהטבלאות יהיו בבעלות של משתמש שרת הווב. נא לציין שם חשבון שרת וב אחר או ללחוץ על כפתור "אחורה" ולציין משתמש התקנה בעל הרשאות מתאימות.',
- 'config-install-user' => 'יצירת חשבון במסד נתונים',
- 'config-install-user-alreadyexists' => 'המשתמש "$1" כבר קיים',
- 'config-install-user-create-failed' => 'יצירת משתמש "$1" נכשלה: $2',
- 'config-install-user-grant-failed' => 'מתן הרשאות למשתמש "$1" נכשל: $2',
- 'config-install-user-missing' => 'המשתמש "$1" שצוין אינו קיים.',
- 'config-install-user-missing-create' => 'המשתמש "$1" שצוין אינו קיים.
-נא ללחוץ על תיבת בסימון "יצירת חשבון" להלן אם אתם רוצים ליצור אותו.',
- 'config-install-tables' => 'יצירת טבלאות',
- 'config-install-tables-exist' => "'''אזהרה:''' נראה שטבלאות מדיה־ויקי כבר קיימות.
-מדלג על יצירתן.",
- 'config-install-tables-failed' => "'''שגיאה:''' יצירת הטבלה נכשלה עם השגיאה הבאה: $1",
- 'config-install-interwiki' => 'אכלוס טבלת בינוויקי התחלתית',
- 'config-install-interwiki-list' => 'קריאת הקובץ <code>interwiki.list</code> לא הצליחה.',
- 'config-install-interwiki-exists' => "'''אזהרה:''': נראה שבטבלת הבינוויקי כבר יש רשומות.
-מדלג על הרשומה ההתחלתית.",
- 'config-install-stats' => 'אתחול סטטיסטיקות',
- 'config-install-keys' => 'יצירת מפתחות סודיים',
- 'config-insecure-keys' => "'''אזהרה''': {{PLURAL:$2|מפתח|מפתחות}} אבטחה ($1) {{PLURAL:$2|שנוצר|שנוצרו}} במהלך ההתקנה {{PLURAL:$2|אינו בטוח|אינם בטוחים}} מספיק. מומלץ לשקול לשנות {{PLURAL:$2|אותו|אותם}} ידנית.",
- 'config-install-sysop' => 'יצירת חשבון מפעיל',
- 'config-install-subscribe-fail' => 'הרישום ל־mediawiki-announce לא הצליח: $1',
- 'config-install-subscribe-notpossible' => 'cURL אינה מותקנת ו־allow_url_fopen אינה זמינה.',
- 'config-install-mainpage' => 'יצירת דף ראשי עם תוכן התחלתי.',
- 'config-install-extension-tables' => 'יצירת טבלאות להרחבות מופעלות',
- 'config-install-mainpage-failed' => 'לא הצליחה הכנסת דף ראשי: $1.',
- 'config-install-done' => "'''מזל טוב!'''
-התקנתם בהצלחה את מדיה־ויקי.
-
-תוכנת ההתקנה יצרה את הקובץ <code>LocalSettings.php</code>.
-הוא מכיל את כל ההגדרות שלכם.
-
-תצטרכו להוריד אותו ולשים אותו בבסיס ההתקנה של הוויקי שלכם (אותה התיקייה שבה נמצא הקובץ index.php). ההורדה הייתה אמורה להתחיל באופן אוטומטי.
-
-אם ההורדה לא התחילה, או אם ביטלתם אותה, אפשר להתחיל אותה מחדש בלחיצה על הקישור הבא:
-
-$3
-
-'''שימו לב''': אם לא תעשו זאת עכשיו, קובץ ההגדרות המחוּלל לא יהיה זמין לכם שוב.
-
-אחרי שתעשו את זה, תוכלו '''[$2 להיכנס לוויקי שלכם]'''.",
- 'config-download-localsettings' => 'הורדת <code>LocalSettings.php</code>',
- 'config-help' => 'עזרה',
- 'config-nofile' => 'הקובץ "$1" לא נמצא. האם הוא נמחק?',
- 'mainpagetext' => "'''תוכנת מדיה־ויקי הותקנה בהצלחה.'''",
- 'mainpagedocfooter' => 'היעזרו ב[//meta.wikimedia.org/wiki/Help:Contents מדריך למשתמש] למידע על שימוש בתוכנת הוויקי.
-
-== קישורים שימושיים ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings רשימת ההגדרות]
-* [//www.mediawiki.org/wiki/Manual:FAQ שאלות ותשובות על מדיה־ויקי]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה על השקת גרסאות]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources תרגום מדיה־ויקי לשפה שלך]',
-);
-
-/** Hindi (हिन्दी)
- */
-$messages['hi'] = array(
- 'mainpagetext' => "'''मीडियाविकिका इन्स्टॉलेशन पूरा हो गया हैं ।'''",
- 'mainpagedocfooter' => 'विकि सॉफ्टवेयरके इस्तेमाल के लिये [//meta.wikimedia.org/wiki/Help:Contents उपयोगकर्ता गाईड] देखें ।
-
-== शुरुवात करें ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings कॉन्फिगरेशन सेटींगकी सूची]
-* [//www.mediawiki.org/wiki/Manual:FAQ मीडियाविकिके बारे में प्राय: पूछे जाने वाले सवाल]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मीडियाविकि मेलिंग लिस्ट]', # Fuzzy
-);
-
-/** Fiji Hindi (Latin script) (Fiji Hindi)
- * @author Thakurji
- */
-$messages['hif-latn'] = array(
- 'mainpagetext' => "'''MediaWiki ke safalta se install kar dewa gais hai.'''",
- 'mainpagedocfooter' => "Wiki software ke use kare ke aur jaankari ke khatir [//meta.wikimedia.org/wiki/Help:Contents User's Guide] ke dekho.
-
-== Getting started ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Hiligaynon (Ilonggo)
- * @author Anjoeli9806
- */
-$messages['hil'] = array(
- 'mainpagetext' => "'''Ang MediaWiki madinalag-on nga na-instala.'''",
- 'mainpagedocfooter' => " Magkonsulta sa [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para sa mga impormasyon sa paggamit sang wiki nga software.
-
-== Pag-umpisa ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sang mga konpigorasyon sang pagkay-o]
-* [//www.mediawiki.org/wiki/Manual:FAQ Mga Masami Pamangkoton sa MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat kon may paguha-on nga MediaWiki]", # Fuzzy
-);
-
-/** Croatian (hrvatski)
- */
-$messages['hr'] = array(
- 'mainpagetext' => "'''Softver MediaWiki je uspješno instaliran.'''",
- 'mainpagedocfooter' => 'Pogledajte [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentaciju o prilagodbi sučelja]
-i [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Vodič za suradnike] za pomoć pri uporabi i podešavanju.', # Fuzzy
-);
-
-/** Upper Sorbian (hornjoserbsce)
- * @author Michawiki
- * @author 아라
- */
-$messages['hsb'] = array(
- 'config-desc' => 'Instalaciski program za MediaWiki',
- 'config-title' => 'Instalacija MediaWiki $1',
- 'config-information' => 'Informacije',
- 'config-localsettings-upgrade' => 'Dataja <code>LocalSettings.php</code> je so wotkryła.
-Zo by tutu instalaciju aktualizował, zapodaj prošu hódnotu za parameter <code>$wgUpgradeKey</code> do slědowaceho pola.
-Namakaš tón parameter w dataji <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Dataja <code>LocalSettings.php</code> bu wotkryta.
-Zo by tutu instalaciju aktualizował, wuwjedźće <code>update.php</code>',
- 'config-localsettings-key' => 'Aktualizaciski kluč:',
- 'config-localsettings-badkey' => 'Kluč, kotryž sy podał, je wopak',
- 'config-upgrade-key-missing' => 'Eksistowaca instalacija MediaWiki je so wotkryła.
-Zo by tutu instalaciju aktualizował, staj prošu slědowacu linku deleka w dataji <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Zda so, zo eksistwoaca dataja <code>LocalSettings.php</code> je njedospołna.
-Wariabla $1 njeje nastajena.
-Prošu změń dataju <code>LocalSettings.php</code>, zo by so tuta wariabla nastajiła a klikń na "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Při zwjazowanju z datowej banku z pomocu nastajenjow podatych w <code>LocalSettings.php</code> abo <code>AdminSettings.php</code> je zmylk wustupił. Prošu skoriguj tute nastajenja a spytaj hišće raz.
-
-$1',
- 'config-session-error' => 'Zmylk při startowanju posedźenja: $1',
- 'config-session-expired' => 'Zda so, zo twoje posedźenske daty su spadnjene.
-Posedźenja su za čas žiwjenja $1 skonfigurowane.
-Móžeš jón přez nastajenje <code>session.gc_maxlifetime</code> w php.ini powyšić.
-Startuj instalaciski proces znowa.',
- 'config-no-session' => 'Twoje posedźenske daty su so zhubili!
-Skontroluj swój php.ini a zawěsć, zo <code>session.save_path</code> je na prawy zapis nastajeny.',
- 'config-your-language' => 'Twoja rěč:',
- 'config-your-language-help' => 'Wubjer rěč, kotraž ma so za instalaciski proces wužiwać.',
- 'config-wiki-language' => 'Wikirěč:',
- 'config-wiki-language-help' => 'Wubjer rěč, w kotrejž wiki ma so zwjetša pisać.',
- 'config-back' => '← Wróćo',
- 'config-continue' => 'Dale →',
- 'config-page-language' => 'Rěč',
- 'config-page-welcome' => 'Witaj do MediaWiki!',
- 'config-page-dbconnect' => 'Z datowej banku zwjazać',
- 'config-page-upgrade' => 'Eksistowacu instalaciju aktualizować',
- 'config-page-dbsettings' => 'Nastajenja datoweje banki',
- 'config-page-name' => 'Mjeno',
- 'config-page-options' => 'Opcije',
- 'config-page-install' => 'Instalować',
- 'config-page-complete' => 'Dokónčeny!',
- 'config-page-restart' => 'Instalaciju znowa startować',
- 'config-page-readme' => 'Čitaj mje',
- 'config-page-releasenotes' => 'Wersijowe informacije',
- 'config-page-copying' => 'Kopěrowanje',
- 'config-page-upgradedoc' => 'Aktualizowanje',
- 'config-page-existingwiki' => 'Eksistowacy wiki',
- 'config-help-restart' => 'Chceš wšě składowane daty hašeć, kotrež sy zapodał a instalaciski proces znowa startować?',
- 'config-restart' => 'Haj, znowa startować',
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki Startowa strona MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Nawod za wužiwarjow]
-* [//www.mediawiki.org/wiki/Manual:Contents Nawod za administratorow]
-* [//www.mediawiki.org/wiki/Manual:FAQ Huste prašenja]
-----
-* <doclink href=Readme>Čitaj mje</doclink>
-* <doclink href=ReleaseNotes>Wersijowe informacije</doclink>
-* <doclink href=Copying>Licencne postajenja</doclink>
-* <doclink href=UpgradeDoc>Aktualizacija</doclink>',
- 'config-env-good' => 'Wokolina je so skontrolowała.
-Móžeš MediaWiki instalować.',
- 'config-env-bad' => 'Wokolina je so skontrolowała.
-Njemóžeš MediaWiki instalować.',
- 'config-env-php' => 'PHP $1 je instalowany.',
- 'config-env-php-toolow' => 'PHP $1 je instalowany.
-Ale MediaWiki wužaduje sej PHP $2 abo wyši.',
- 'config-unicode-using-utf8' => 'Za normalizaciju Unicode so utf8_normalize.so Briona Vibbera wužiwa.',
- 'config-unicode-using-intl' => 'Za normalizaciju Unicode so [http://pecl.php.net/intl PECL-rozšěrjenje intl] wužiwa.',
- 'config-no-db' => 'Njeda so přihódny ćěrjak datoweje banki namakać! Dyrbiš ćěrjak datoweje banki za PHP instalować.
-Slědowace typy datoweje banki so podpěruja: $1.
-
-Jeli wužiwaš zhromadnje wužiwany serwer, proš swojeho poskićowarja, zo by přihódny ćěrjak datoweje banki instalował.
-Jeli sy PHP sam kompilował, konfiguruj jón znowa z aktiwizowanym programom datoweje banki, na přikład z pomocu <code>./configure --with-mysql</code>.
-Jeli sy PHP z Debianoweho abo Ubuntuoweho paketa instalował, dyrbiš tež modul php5-mysql instalować.',
- 'config-outdated-sqlite' => "'''Warnowanje''': maš SQLite $1, kotryž je starši hač minimalna trěbna wersija $2. SQLite njebudźe k dispoziciji stać.",
- 'config-no-fts3' => "'''Warnowanje''': SQLite je so bjez [//sqlite.org/fts3.html FTS3-modula] kompilował, pytanske funkcije njebudu k dispoziciji stać.",
- 'config-register-globals' => "'''Warnowanje: Funkcija <code>[http://php.net/register_globals register_globals]</code> PHP je zmóžnjena.'''
-'''Znjemóžń ju, jeli móžeš.'''
-MediaWiki budźe fungować, ale twój serwer je potencielnym wěstotnym njedostatkam wustajeny.",
- 'config-ze1' => "'''Chutny zmylk: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] je aktiwny!'''
-Tuta opcija zawinuje grawěrowace zmylki při MediaWiki.
-Njemóžeš MediaWiki instalować abo wužiwać, chibazo tuta opcija je znjemóžnjena.",
- 'config-safe-mode' => "'''Warnowanje:''' [http://www.php.net/features.safe-mode wěsty modus] PHP je aktiwny.
-To móže problemy zawinować, předewšěm, jeli so datajowe nahraća a podpěra <code>math</code> wužiwaja.",
- 'config-xml-bad' => 'XML-modul za PHP faluje.
-MediaWiki trjeba funkcije w tutym modulu a njebudźe w tutej konfiguraciji fungować.
-Jeli wužiwaš Mandrake, instaluj paket php-xml.',
- 'config-pcre' => 'Zda so, zo modul za PCRE-podpěru faluje.
-MediaWiki trjeba z Perl kompatibelne funkcije za regularne wurazy, zo by fungował.',
- 'config-pcre-no-utf8' => "'''Ćežki zmylk''': Zda so, zo PCRE-modul za PHP ma so bjez PCRE_UTF8-podpěry kompilować.
-MediaWiki 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,
-To je najskerje přeniske.
-Instalacija móhła so njeporadźić!",
- 'config-ctype' => "'''Ćežki zmylk''': PHP dyrbi so z podpěru za [http://www.php.net/manual/en/ctype.installation.php rozšěrjenje Ctype] kompilować.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] je instalowany',
- 'config-apc' => '[http://www.php.net/apc APC] je instalowany',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] je instalowany',
- 'config-diff3-bad' => 'GNU diff3 njenamakany.',
- 'config-no-uri' => "'''Zmylk:''' Aktualny URI njeda so postajić.
-Instalacija bu přetorhnjena.",
- 'config-no-cli-uri' => "'''Warnowanje''': Žana skriptowa šćežka (--scriptpath) podata, standard so wužiwa: <code>$1</code>.",
- 'config-using-server' => 'Serwerowe mjeno "<nowiki>$1</nowiki>" so wužiwa.',
- 'config-using-uri' => 'Serwerowy URL "<nowiki>$1$2</nowiki>" so wužiwa.',
- 'config-db-type' => 'Typ datoweje banki:',
- 'config-db-host' => 'Serwer datoweje banki:',
- 'config-db-host-oracle' => 'Datowa banka TNS:',
- 'config-db-wiki-settings' => 'Tutón wiki identifikować',
- 'config-db-name' => 'Mjeno datoweje banki:',
- 'config-db-name-oracle' => 'Šema datoweje banki:',
- 'config-db-install-account' => 'Wužiwarske konto za instalaciju',
- 'config-db-username' => 'Wužiwarske mjeno datoweje banki:',
- 'config-db-password' => 'Hesło datoweje banki:',
- 'config-db-password-empty' => 'Prošu zapodaj hesło za noweho wužiwarja datoweje banki: $1.
-Byrnjež było móžno wužiwarjow bjez hesłow wutworić, njeje to wěste.',
- 'config-db-install-username' => 'Zapodaj wužiwarske mjeno, kotrež budźe so za zwisk z datowej banku za instalaciski proces wužiwać.
-To njeje wužiwarske mjeno konta MediaWiki; to je wužiwarske mjeno za twoju datowu banku.',
- 'config-db-install-password' => 'Zapodaj hesło, kotrež budźe so za zwisk z datowej banku za instalaciski proces wužiwać.
-To njeje hesło konta MediaWiki; to je hesło za twoju datowu banku.',
- 'config-db-install-help' => 'Zapodaj wužiwarske mjeno a hesło, kotrejž měłoj so za zwisk z datowej banku za instalaciski proces wužiwać.',
- 'config-db-account-lock' => 'Samsne wužiwarske mjeno a hesło za normalnu operaciju wužiwać',
- 'config-db-wiki-account' => 'Wužiwarske konto za normalnu operaciju',
- 'config-db-prefix' => 'Tabelowy prefiks datoweje banki:',
- 'config-db-charset' => 'Znamješkowa sadźba datoweje banki',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binarny',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 wróćokompatibelny UTF-8',
- 'config-mysql-old' => 'MySQL $1 abo nowši trěbny, maš $2.',
- 'config-db-port' => 'Port datoweje banki:',
- 'config-db-schema' => 'Šema za MediaWiki',
- 'config-db-schema-help' => 'Tuta šema da so zwjetša derje wužiwać.
-Změń ju jenož, jeli su přeswědčiwe přičiny za to.',
- 'config-pg-test-error' => "Zwisk z datowej banku '''$1''' móžno njeje: $2",
- 'config-sqlite-dir' => 'Zapis SQLite-datow:',
- 'config-oracle-def-ts' => 'Standardny tabelowy rum:',
- 'config-oracle-temp-ts' => 'Nachwilny tabelowy rum:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-mysql' => '* $1 je primarny cil za MediaWiki a podpěruje so najlěpje ([http://www.php.net/manual/en/mysql.installation.php Nawod ke kompilowanju PHP z MySQL-podpěru])',
- 'config-support-postgres' => '* $1 je popularny system datoweje banki zjawneho žórła jako alternatiwa k MySQL ([http://www.php.net/manual/en/pgsql.installation.php nawod za kompilowanje PHP z podpěru PostgreSQL]). Móhło hišće někotre zmylki eksistować, a njeporuča so jón w produktiwnej wokolinje wužiwać.',
- 'config-support-oracle' => '* $1 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',
- 'config-header-sqlite' => 'Nastajenja SQLite',
- 'config-header-oracle' => 'Nastajenja Oracle',
- 'config-invalid-db-type' => 'Njepłaćiwy typ datoweje banki',
- 'config-missing-db-name' => 'Dyrbiš hódnotu za "Mjeno datoweje banki" zapodać',
- 'config-missing-db-host' => 'Dyrbiš hódnotu za "Database host" zapodać',
- 'config-missing-db-server-oracle' => 'Dyrbiš hódnotu za "Database TNS" zapodać',
- 'config-invalid-db-server-oracle' => 'Njepłaćiwa datowa banka TNS "$1".
-Wužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9), podsmužki (_) a dypki (.).',
- 'config-invalid-db-name' => 'Njepłaćiwe mjeno "$1" datoweje banki.
-Wužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9),a podsmužki (_) a wjazawki (-).',
- 'config-invalid-db-prefix' => 'Njepłaćiwy prefiks "$1" datoweje banki.
-Wužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9), podsmužki (_) a wjazawki (-).',
- 'config-connection-error' => '$1.
-
-Skontroluj serwer, wužiwarske a hesło a spytaj hišće raz.',
- 'config-invalid-schema' => 'Njepłaćiwe šema za MediaWiki "$1".
-Wužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9) a podsmužki (_).',
- 'config-db-sys-create-oracle' => 'Instalaciski program podpěruje jenož wužiwanje SYSDBA-konta za zakoženje noweho konta.',
- 'config-db-sys-user-exists-oracle' => 'Wužiwarske konto "$1" hižo eksistuje. SYSDBA hodźi so jenož za załoženje noweho konta wužiwać!',
- 'config-postgres-old' => 'PostgreSQL $1 abo nowši trěbny, maš $2.',
- 'config-sqlite-name-help' => 'Wubjer mjeno, kotrež twój wiki identifikuje.
-Njewužij mjezery abo wjazawki.
-To budźe so za mjeno dataje SQLite-datow wužiwać.',
- 'config-sqlite-mkdir-error' => 'Zmylk při wutworjenju datoweho zapisa "$1".
-Skontroluj městno a spytaj hišće raz.',
- 'config-sqlite-dir-unwritable' => 'Njeje móžno do zapisa "$1" pisać.
-Změń jeho prawa, tak zo webserwer móže do njeho pisać a spytaj hišće raz.',
- 'config-sqlite-connection-error' => '$1.
-
-Skontroluj datowy zapis a mjeno datoweje banki kaj spytaj hišće raz.',
- 'config-sqlite-readonly' => 'Do dataje <code>$1</code> njeda so pisać.',
- 'config-sqlite-cant-create-db' => 'Dataja <code>$1</code> datoweje banki njeda so wutworić.',
- 'config-sqlite-fts3-downgrade' => 'PHP wo podpěrje FTS3 k dispoziciji njesteji, table so znižuja',
- 'config-can-upgrade' => "Su tabele MediaWiki w tutej datowej bance.
-Zo by je na MediaWiki $1 aktualizował, klikń na '''Dale'''.",
- 'config-upgrade-done-no-regenerate' => 'Aktualizacija dokónčena.
-
-Móžeš nětko [$1 swój wiki wužiwać].',
- 'config-regenerate' => 'LocalSettings.php znowa wutworić →',
- 'config-show-table-status' => 'Naprašowanje <code>SHOW TABLE STATUS</code> je so njeporadźiło!',
- 'config-unknown-collation' => "'''Warnowanje:''' Datowa banka njeznatu kolaciju wužiwa.",
- 'config-db-web-account' => 'Konto datoweje banki za webpřistup',
- 'config-db-web-help' => 'wubjer wužiwarske mjeno a hesło, kotrejž webserwer budźe wužiwać, zo by z serwerom datoweje banki za wšědnu operaciju zwjazać',
- 'config-db-web-account-same' => 'Samsne konto kaž za instalaciju wužiwać',
- 'config-db-web-create' => 'Załož konto, jeli hišće njeeksistuje.',
- 'config-db-web-no-create-privs' => 'Konto, kotrež sy za instalaciju podał, nima dosć woprawnjenjow, zo by konto wutworiło.
-Konto, kotrež tu podawaće, dyrbi hižo eksistować.',
- 'config-mysql-engine' => 'Składowanska mašina:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => 'Znamješkowa sadźba datoweje banki:',
- 'config-mysql-binary' => 'Binarny',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Mjeno wikija:',
- 'config-site-name-help' => 'To zjewi so w titulowej lejstwje wobhladaka kaž tež na wšelakich druhich městnach.',
- 'config-site-name-blank' => 'Zapodaj sydłowe mjeno.',
- 'config-project-namespace' => 'Mjenowy rum projekta:',
- 'config-ns-generic' => 'Projekt',
- 'config-ns-site-name' => 'Samsne kaž wikimjeno: $1',
- 'config-ns-other' => 'Druhe (podać)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-ns-invalid' => 'Podaty mjenowy rum "<nowiki>$1</nowiki>" je njepłaćiwy.
-Podaj druhi projektowy mjenowy rum.',
- 'config-ns-conflict' => 'Podaty mjenowy rum "<nowiki>$1</nowiki>" je w konflikće ze standardnym mjenjowym rumom MediaWiki.
-Podaj druhi projektowy mjenowy rum.',
- 'config-admin-box' => 'Administratorowe konto',
- 'config-admin-name' => 'Twoje mjeno:',
- 'config-admin-password' => 'Hesło:',
- 'config-admin-password-confirm' => 'Hesło wospjetować:',
- 'config-admin-help' => 'Zapodaj swoje preferowane wužiwarske mjeno, na přikład "Jurij Serb".
-To je mjeno, kotrež budźeš wužiwać, zo by so do wikija přizjewił.',
- 'config-admin-name-blank' => 'Zapodaj administratorowe wužiwarske mjeno.',
- 'config-admin-name-invalid' => 'Podate wužiwarske mjeno "<nowiki>$1</nowiki>" je njepłaćiwe.
-Podaj druhe wužiwarske mjeno.',
- 'config-admin-password-blank' => 'Zapodaj hesło za administratorowe konto.',
- 'config-admin-password-same' => 'Hesło dyrbi so wot wužiwarskeho mjena rozeznać.',
- 'config-admin-password-mismatch' => 'Wobě hesle, kotrejž sy zapodał, njejstej jenakej.',
- 'config-admin-email' => 'E-mejlowa adresa:',
- 'config-admin-email-help' => 'Zapodaj tu e-mejlowu adresu, zo by přijimanje e-mejlow wot druhich wužiwarjow w tutym wikiju zmóžnił, swoje hesło wróćo stajił a zdźělenki wo změnach na swojich wobkedźbowanych stronach dostał. Móžeš polo prózdne wostajić.',
- 'config-admin-error-user' => 'Interny zmylk při wutworjenju administratora z mjenom "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Interny zmylk při nastajenju hesła za administratora "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Sy njepłaćiwu e-mejlowu adresu zapodał.',
- 'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rozesyłansku lisćinu wo připowědźenjach nowych wersijow ].abonować',
- 'config-almost-done' => 'Sy skoro hotowy!
-Móžeš nětko zbytnu konfiguraciju přeskočić a wiki hnydom instalować.',
- 'config-optional-continue' => 'Dalše prašenja?',
- 'config-optional-skip' => 'Instaluj nětko wiki.',
- 'config-profile' => 'Profil wužiwarskich prawow:',
- 'config-profile-wiki' => 'Zjawny wiki',
- 'config-profile-no-anon' => 'Załoženje konto je trěbne',
- 'config-profile-fishbowl' => 'Jenož awtorizowani wobdźěłarjo',
- 'config-profile-private' => 'Priwatny wiki',
- 'config-license' => 'Awtorske prawo a licenca:',
- 'config-license-none' => 'Žane licencne podaća w nohowej lince',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (zjawnosći přistupny)',
- 'config-license-gfdl' => 'GNU-licenca za swobodnu dokumentaciju 1.3 abo nowša',
- 'config-license-pd' => 'Powšitkownosći přistupny',
- 'config-license-cc-choose' => 'Swójsku licencu Creative Commons wubrać',
- 'config-email-settings' => 'E-mejlowe nastajenja',
- 'config-enable-email' => 'Wuchadźace e-mejlki zmóžnić',
- 'config-enable-email-help' => 'Jeli chceš e-mejl wužiwać, dyrbja so [http://www.php.net/manual/en/mail.configuration.php e-mejlowe nastajenja PHP] prawje konfigurować.
-Jeli nochceš e-mejlowe funkcije wužiwać, móžeš je tu znjemóžnić.',
- 'config-email-user' => 'E-mejl mjez wužiwarjemi zmóžnić',
- 'config-email-user-help' => 'Wšěm wužiwarjam dowolić, jednomu druhemu e-mejlki pósłać, jeli su tutu funkciju w swojich nastajenjach zmóžnili.',
- 'config-email-usertalk' => 'Zdźělenja za wužiwarske diskusijne strony zmóžnić',
- 'config-email-usertalk-help' => 'Wužiwarjam dowolić zdźělenki wo změnach na wužiwarskich diskusijnych stronach dóstać, jeli woni su to w swojich nastajenjach zmóžnili.',
- 'config-email-watchlist' => 'Zdźělenja za wobkedźbowanki zmóžnić',
- 'config-email-watchlist-help' => 'Wužiwarjam dowolić zdźělenki wo jich wobked´bowanych stronach dóstać, jeli woni su to w swojich nastajenjach zmóžnili.',
- 'config-email-auth' => 'E-mejlowu awtentifikaciju zmóžnić',
- 'config-email-sender' => 'E-mejlowa adresa za wotmołwy:',
- 'config-upload-settings' => 'Wobrazy a nahraća datajow',
- 'config-upload-enable' => 'Nahraće datajow zmóžnić',
- 'config-upload-deleted' => 'Zapis za zhašane dataje:',
- 'config-upload-deleted-help' => 'Wubjer zapis, w kotrymž zhašene dataje maja so archiwować.
-Idealnje tón njeměł z weba přistupny być.',
- 'config-logo' => 'URL loga:',
- 'config-instantcommons' => 'Instant commons zmóžnić',
- 'config-cc-error' => 'Pytanje za licencu Creative Commons njeje žadyn wuslědk přinjesło.
-Zapodaj licencne mjeno manuelnje.',
- 'config-cc-again' => 'Zaso wubrać...',
- 'config-cc-not-chosen' => 'Wubjer licencu Creative Commons a klikń na "dale".',
- 'config-advanced-settings' => 'Rozšěrjena konfiguraćija',
- 'config-cache-options' => 'Nastajenja za objektowe pufrowanje:',
- 'config-cache-none' => 'Žane pufrowanje (žana funkcionalnosć so njewotstronja, ale spěšnosć móže so na wjetšich wikijowych sydłach wobwliwować)',
- 'config-cache-accel' => 'Objektowe pufrowanje PHP (APC, XCache abo WinCache)',
- 'config-cache-memcached' => 'Memcached wužiwać (wužaduje sej přidatnu instalaciju a konfiguraciju)',
- 'config-memcached-servers' => 'Serwery memcached:',
- 'config-memcached-help' => 'Lisćina IP-adresow, kotrež maja so za Memcached wužiwać.
-Kóžda linka měła jenož jednu IP-adresu a port, kotryž ma so wužiwać, wobsahować. Na přikład:
-127.0.0.1:11211
-192.168.1.25:1234',
- 'config-memcache-needservers' => 'Sy Memcached jako swój pufrowakowy typ wubrał, ale njejsy žane serwery podał',
- 'config-memcache-badip' => 'Sy njepłaćiwu IP-adresu za Memcached zapodał: $1',
- 'config-memcache-noport' => 'Njejsy žadyn port za wužiwanje serwera Memcached podał: $1.
-Jeli port njewěš, standard je 11211.',
- 'config-memcache-badport' => 'Portowe čisła za Memcached měli mjez $1 a $2 być',
- 'config-extensions' => 'Rozšěrjenja',
- 'config-extensions-help' => 'Rozšěrjenja podate horjeka buchu w twojim zapisu <code>./extensions</code> namakane.
-
-To móže sej přidatnu konfiguraciju wužadać, ale móžeš je nětko zmóžnić.',
- 'config-install-alreadydone' => "'''Warnowanje:''' Zda so, zo sy hižo MediaWiki instalował a pospytuješ jón znowa instalować.
-Prošu pokročuj z přichodnej stronu.",
- 'config-install-begin' => 'Přez kliknjenje na "{{int:config-continue}}" budźe so instalacija MediaWiki startować.
-Jeli hišće chceš něšto změnić, klikń na "{{int:config-back}}".',
- 'config-install-step-done' => 'dokónčene',
- 'config-install-step-failed' => 'njeporadźiło',
- 'config-install-extensions' => 'Inkluziwnje rozšěrjenja',
- 'config-install-database' => 'Datowa banka so připrawja',
- 'config-install-schema' => 'Datowa šema so twori',
- 'config-install-pg-schema-not-exist' => 'Šema PostgreSQL njeeksistuje',
- 'config-install-pg-schema-failed' => 'Wutworjenje tabelow je so njeporadźiło.
-Zawěsć, zo wužiwar "$1" móže do šemy "$2" pisać.',
- 'config-install-pg-commit' => 'Změny so wotesyłaja',
- 'config-install-pg-plpgsql' => 'Pruwowanje za rěču PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Dyrbiš rěč PL/pgSQL w datowej bance $1 instalować',
- 'config-pg-no-create-privs' => 'Konto, kotrež sy za instalaciju podał, nima dosahace prawa za wutworjenje konta.',
- 'config-install-user' => 'Tworjenje wužiwarja datoweje banki',
- 'config-install-user-alreadyexists' => 'Wužiwar "$1" hižo eksistuje',
- 'config-install-user-create-failed' => 'Wutworjenje wužiwarja "$1" je so njeporadźiło: $2',
- 'config-install-user-grant-failed' => 'Prawo njeda so wužiwarjej "$1" dać: $2',
- 'config-install-user-missing' => 'Podaty wužiwar "$1" njeeksistuje.',
- 'config-install-user-missing-create' => 'Podaty wužiwar "$1" njeeksistuje.
-Prošu klikń na slědowacy kontrolny kašćik "konto załožić", jeli chceš jo wutworić.',
- 'config-install-tables' => 'Tworjenje tabelow',
- 'config-install-tables-exist' => "'''Warnowanje''': Zda so, zo tabele MediaWiki hižo eksistuja.
-Wutworjenje so přeskakuje.",
- 'config-install-tables-failed' => "'''Zmylk''': Wutworjenje tabele je so slědowaceho zmylka dla njeporadźiło: $1",
- 'config-install-interwiki' => 'Standardna tabela interwikijow so pjelni',
- 'config-install-interwiki-list' => '<code>interwiki.list</code> njeda so namakać.',
- 'config-install-interwiki-exists' => "'''Warnowanje''': Zda so, zo tabela interwikjow hižo zapiski wobsahuje.
-Standardna lisćina sp přeskakuje.",
- 'config-install-stats' => 'Statistika so inicializuje',
- 'config-install-keys' => 'Tajne kluče so tworja',
- 'config-install-sysop' => 'Tworjenje administratoroweho wužiwarskeho konta',
- 'config-install-subscribe-fail' => 'Abonowanje "mediawiki-announce" njemóžno: $1',
- 'config-install-subscribe-notpossible' => 'cURL njeje instalowany a allow_url_fopen k dispoziciji njesteji.',
- 'config-install-mainpage' => 'Hłowna strona so ze standardnym wobsahom wutworja',
- 'config-install-extension-tables' => 'Tabele za zmóžnjene rozšěrjenja so tworja',
- 'config-install-mainpage-failed' => 'Powěsć njeda so zasunyć: $1',
- 'config-download-localsettings' => '<code>LocalSettings.php</code> sćahnyć',
- 'config-help' => 'pomoc',
- 'config-nofile' => 'Dataja "$1" njeje so namakała. Je so zhašała?',
- 'mainpagetext' => "'''MediaWiki bu wuspěšnje instalowany.'''",
- 'mainpagedocfooter' => 'Prošu hlej [//meta.wikimedia.org/wiki/Help:Contents dokumentaciju] za informacije wo wužiwanju softwary.
-
-== Za nowačkow ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Wo nastajenjach]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki za twoju rěč lokalizować]',
-);
-
-/** Haitian (Kreyòl ayisyen)
- * @author Boukman
- */
-$messages['ht'] = array(
- 'mainpagetext' => "'''MedyaWiki byen enstale l.'''",
- 'mainpagedocfooter' => 'Konsilte [//meta.wikimedia.org/wiki/Help:Konteni Gid Itilizatè] pou enfòmasyon sou kijan pou w itilize logisyèl wiki a.
-
-== Kijan pou kòmanse ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lis paramèt yo pou konfigirasyon]
-* [//www.mediawiki.org/wiki/Manyèl:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis diskisyon ki parèt sou MediaWiki]', # Fuzzy
-);
-
-/** Hungarian (magyar)
- * @author Dani
- * @author Glanthor Reviol
- * @author 아라
- */
-$messages['hu'] = array(
- 'config-desc' => 'A MediaWiki telepítője',
- 'config-title' => 'A MediaWiki $1 telepítése',
- 'config-information' => 'Információ',
- 'config-localsettings-upgrade' => 'Már létezik a <code>LocalSettings.php</code> fájl.
-A telepített szoftver frissítéséhez írd be az alábbi mezőbe a <code>$wgUpgradeKey</code> beállítás értékét, melyet a <code>LocalSettings.php</code> nevű fájlban találhatsz meg.',
- 'config-localsettings-cli-upgrade' => 'A <code>LocalSettings.php</code> fájl megtalálható.
-A telepített rendszer frissítéséhez futtasd az <code>update.php</code>-t.',
- 'config-localsettings-key' => 'Frissítési kulcs:',
- 'config-localsettings-badkey' => 'A megadott kulcs érvénytelen.',
- 'config-upgrade-key-missing' => 'A telepítő a MediaWiki meglévő példányát észlelte.
-A telepített rendszer frissítéséhez helyezd el az alábbi sort a <code>LocalSettings.php</code> végére:
-
-$1',
- 'config-localsettings-incomplete' => 'A meglévő <code>LocalSettings.php</code> hiányosnak tűnik.
-A(z) $1 változó értéke nincs beállítva.
-Módosítsd a <code>LocalSettings.php</code> fájlt úgy, hogy ez a változó be legyen állítva, majd kattints a „{{int:Config-continue}}” gombra.',
- 'config-localsettings-connection-error' => 'Nem sikerült csatlakozni az adatbázishoz a <code>LocalSettings.php</code>-ben vagy az <code>AdminSettings.php</code>-ben megadott adatokkal. Ellenőrizd a beállításokat, majd próbáld újra.
-
-$1',
- 'config-session-error' => 'Nem sikerült elindítani a munkamenetet: $1',
- 'config-session-expired' => 'Úgy tűnik, hogy a munkamenetadatok lejártak.
-A munkamenetek élettartama a következőre van beállítva: $1.
-Az érték növelhető a php.ini <code>session.gc_maxlifetime</code> beállításának módosításával.
-Indítsd újra a telepítési folyamatot.',
- 'config-no-session' => 'Elvesztek a munkamenetadatok!
-Ellenőrizd, hogy a php.ini-ben a <code>session.save_path</code> a megfelelő könyvtárra mutat-e.',
- 'config-your-language' => 'Nyelv:',
- 'config-your-language-help' => 'A telepítési folyamat során használandó nyelv.',
- 'config-wiki-language' => 'A wiki nyelve:',
- 'config-wiki-language-help' => 'Az a nyelv, amin a wiki tartalmának legnagyobb része íródik.',
- 'config-back' => '← Vissza',
- 'config-continue' => 'Folytatás →',
- 'config-page-language' => 'Nyelv',
- 'config-page-welcome' => 'Üdvözöl a MediaWiki!',
- 'config-page-dbconnect' => 'Kapcsolódás az adatbázishoz',
- 'config-page-upgrade' => 'Telepített változat frissítése',
- 'config-page-dbsettings' => 'Adatbázis-beállítások',
- 'config-page-name' => 'Név',
- 'config-page-options' => 'Beállítások',
- 'config-page-install' => 'Telepítés',
- 'config-page-complete' => 'Kész!',
- 'config-page-restart' => 'Telepítés újraindítása',
- 'config-page-readme' => 'Tudnivalók',
- 'config-page-releasenotes' => 'Kiadási megjegyzések',
- 'config-page-copying' => 'Másolás',
- 'config-page-upgradedoc' => 'Frissítés',
- 'config-page-existingwiki' => 'Létező wiki',
- 'config-help-restart' => 'Szeretnéd törölni az eddig megadott összes adatot és újraindítani a telepítési folyamatot?',
- 'config-restart' => 'Igen, újraindítás',
- 'config-welcome' => '=== A környezet ellenőrzése ===
-Néhány alapvető ellenőrzés lett végrehajtva, ami meghatározza, hogy ez a környezet alkalmas-e a MediaWiki telepítésére.
-Ha telepítéssel kapcsolatos segítségre van szükséged, add meg ezen ellenőrzések eredményét.',
- 'config-copyright' => "=== Licenc és feltételek ===
-
-$1
-
-Ez a program szabad szoftver; terjeszthető illetve módosítható a Free Software Foundation által kiadott GNU General Public License dokumentumában leírtak; akár a licenc 2-es, akár (tetszőleges) későbbi változata szerint.
-
-Ez a program abban a reményben kerül közreadásra, hogy hasznos lesz, de minden egyéb '''garancia nélkül''', az '''eladhatóságra''' vagy '''valamely célra való alkalmazhatóságra''' való származtatott garanciát is beleértve. További részleteket a GNU General Public License tartalmaz.
-
-A felhasználónak a programmal együtt meg kell kapnia a <doclink href=Copying>GNU General Public License egy példányát</doclink>; ha mégsem kapta meg, akkor írjon a Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. címre, vagy [http://www.gnu.org/copyleft/gpl.html tekintse meg online].",
- 'config-sidebar' => '* [//www.mediawiki.org A MediaWiki honlapja]
-* [//www.mediawiki.org/wiki/Help:Contents Felhasználói kézikönyv]
-* [//www.mediawiki.org/wiki/Manual:Contents Útmutató adminisztrátoroknak]
-* [//www.mediawiki.org/wiki/Manual:FAQ GyIK]
-----
-* <doclink href=Readme>Ismertető</doclink>
-* <doclink href=ReleaseNotes>Kiadási megjegyzések</doclink>
-* <doclink href=Copying>Másolás</doclink>
-* <doclink href=UpgradeDoc>Frissítés</doclink>',
- 'config-env-good' => 'A környezet ellenőrzése befejeződött.
-A MediaWiki telepíthető.',
- 'config-env-bad' => 'A környezet ellenőrzése befejeződött.
-A MediaWiki nem telepíthető.',
- 'config-env-php' => 'A PHP verziója: $1',
- 'config-env-php-toolow' => 'PHP $1 van telepítve,
-azonban a MediaWikinek PHP $2, vagy újabb szükséges.',
- 'config-unicode-using-utf8' => 'A rendszer Unicode normalizálására Brion Vibber utf8_normalize.so könyvtárát használja.',
- 'config-unicode-using-intl' => 'A rendszer Unicode normalizálására az [http://pecl.php.net/intl intl PECL kiterjesztést] használja.',
- 'config-unicode-pure-php-warning' => "'''Figyelmeztetés''': Az Unicode normalizáláshoz szükséges [http://pecl.php.net/intl intl PECL kiterjesztés] nem érhető el, helyette a lassú, PHP alapú implementáció lesz használva.
-Ha nagy látogatottságú oldalt üzemeltetsz, itt találhatsz további információkat [//www.mediawiki.org/wiki/Unicode_normalization_considerations a témáról].",
- 'config-unicode-update-warning' => "'''Figyelmeztetés''': Az Unicode normalizáláshoz szükséges burkolókönyvtár [http://site.icu-project.org/ ICU projekt] függvénykönyvtárának régebbi változatát használja.
-Ha ügyelni kívánsz a Unicode használatára, fontold meg a [//www.mediawiki.org/wiki/Unicode_normalization_considerations frissítését].",
- 'config-no-db' => 'Nem sikerült egyetlen használható adatbázis-illesztőprogramot sem találni. Telepítened kell egyet a PHP-hez.
-A következő adatbázistípusok támogatottak: $1.
-
-Ha megosztott tárhelyszolgáltatást használsz, kérd meg a szolgáltatódat, hogy telepítsen egy megfelelő illesztőprogramot.
-Ha a PHP-t magad fordítottad, konfiguráld újra úgy, hogy engedélyezve legyen egy adatbáziskliens, pl. a <code>./configure --with-mysql</code> parancs használatával.
-Ha a PHP-t Debian vagy Ubuntu csomaggal telepítetted, akkor szükséged lesz a php5-mysql modulra is.',
- 'config-no-fts3' => "'''Figyelmeztetés''': Az SQLite [//sqlite.org/fts3.html FTS3 modul] nélkül lett fordítva, a keresési funkciók nem fognak működni ezen a rendszeren.",
- 'config-register-globals' => "'''Figyelmeztetés: A PHP <code>[http://php.net/register_globals register_globals]</code> beállítása engedélyezve van.'''
-'''Tiltsd le, ha van rá lehetőséged.'''
-A MediaWiki működőképes a beállítás használata mellett, de a szerver biztonsági kockázatnak lesz kitéve.",
- 'config-magic-quotes-runtime' => "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] aktív!'''
-Ez a beállítás kiszámíthatatlan károkat okoz a bevitt adatokban.
-A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
- 'config-magic-quotes-sybase' => "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] aktív!'''
-Ez a beállítás kiszámíthatatlan károkat okoz a bevitt adatokban.
-A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
- 'config-mbstring' => "'''Kritikus hiba: az [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime mbstring.func_overload] aktív!'''
-Ez a beállítás hibákat okoz és kiszámíthatatlanul károsíthatja bevitt adatokat.
-A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
- 'config-ze1' => "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] aktív!'''
-Ez a beállítás borzalmas hibákat okoz a MediaWiki futása során.
-A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
- 'config-safe-mode' => "'''Figyelmeztetés:''' A PHP [http://www.php.net/features.safe-mode safe mode]-ja be van kapcsolva.
-Problémákat okozhat, különösen a fájlfeltöltéseknél és a <code>math</code>-támogatás használatánál.",
- 'config-xml-bad' => 'A PHP XML-modulja hiányzik.
-Egyes MediaWiki-funkciók, melyek ezt a modult igénylik, nem fognak működni ilyen beállítások mellett.
-Ha Madrake-et futtatsz, telepítsd a php-xml csomagot.',
- 'config-pcre' => 'Úgy tűnik, hogy a PCRE támogató modul hiányzik.
-A MediaWikinek Perl-kompatibilis reguláriskifejezés-függvényekre van szüksége a működéshez.',
- 'config-pcre-no-utf8' => "'''Kritikus hiba''': Úgy tűnik, hogy a PHP PRCE modulja PRCE_UTF8 támogatás nélkül lett fordítva.
-A 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' => "'''Figyelmeztetés:''' A PHP <code>memory_limit</code> beállításának értéke $1.
-Ez az érték valószínűleg túl kevés, a telepítés sikertelen lehet.",
- 'config-xcache' => 'Az [http://xcache.lighttpd.net/ XCache] telepítve van',
- 'config-apc' => 'Az [http://www.php.net/apc APC] telepítve van',
- 'config-wincache' => 'A [http://www.iis.net/download/WinCacheForPhp WinCache] telepítve van',
- 'config-no-cache' => "'''Figyelmeztetés:''' Nem található [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] és [http://www.iis.net/download/WinCacheForPhp WinCache] sem.
-Objektum-gyorsítótárazás nem lesz engedélyezve.",
- 'config-diff3-bad' => 'GNU diff3 nem található.',
- 'config-imagemagick' => 'Az ImageMagick megtalálható a rendszeren: <code>$1</code>.
-A bélyegképek készítése engedélyezve lesz a feltöltések engedélyezése esetén.',
- 'config-gd' => 'A GD grafikai könyvtár elérhető.
-Bélyegképek készítése működni fog, miután engedélyezted a fájlfeltöltést.',
- 'config-no-scaling' => 'Nem található a GD könyvtár és az ImageMagick.
-A bélyegképek készítése le lesz tiltva.',
- 'config-no-uri' => "'''Hiba:''' Nem sikerült megállapítani a jelenlegi URI-t.
-Telepítés megszakítva.",
- 'config-using-server' => 'A következő szervernév használata: „<nowiki>$1</nowiki>”.',
- 'config-using-uri' => 'A következő szerver URL-cím használata: „<nowiki>$1$2</nowiki>”.',
- 'config-uploads-not-safe' => "'''Figyelmeztetés:''' a feltöltésekhez használt alapértelmezett könyvtárban (<code>$1</code>) tetszőleges külső szkript futtatható.
-Habár a MediaWiki ellenőrzi a feltöltött fájlokat az efféle biztonsági veszélyek megtalálása érdekében, a feltöltés engedélyezése előtt erősen ajánlott a [//www.mediawiki.org/wiki/Manual:Security#Upload_security a sérülékenység megszüntetése].",
- 'config-brokenlibxml' => 'A rendszereden a PHP és libxml2 verziók olyan kombinációja található meg, ami hibásan működik, és észrevehetetlen adatkárosodást okoz a MediaWikiben és más webalkalmazásokban.
-Frissíts a PHP 5.2.9-es vagy újabb, valamint a libxml2 2.7.3 vgy újabb verziójára ([//bugs.php.net/bug.php?id=45996 A hiba bejelentése a PHP-nél]).
-Telepítés megszakítva.',
- 'config-using531' => 'A MediaWiki nem használható a PHP $1-es verziójával, mert hiba van a <code>__call()</code> függvénynek átadott referenciaparaméterekkel.
-A probléma kiküszöböléséhez frissíts a PHP 5.3.2-es verziójára, vagy használd a korábbi, 5.3.0-ásat.
-Telepítés megszakítva.',
- 'config-suhosin-max-value-length' => 'A Suhosin telepítve van, és a GET paraméter hosszát $1 bájtra korlátozza. A MediaWiki erőforrásbetöltő összetevője megkerüli a problémát, de így csökkenni fog a teljesítmény. Ha lehetséges, állítsd be a <code>suhosin.get.max_value_length</code> értékét legalább 1024-re a <code>php.ini</code>ben, és állítsd be a <code>$wgResourceLoaderMaxQueryLength</code> változót ugyanerre az értékre a LocalSettings.php-ben.', # Fuzzy
- 'config-db-type' => 'Adatbázis típusa:',
- 'config-db-host' => 'Adatbázis hosztneve:',
- 'config-db-host-help' => 'Ha az adatbázisszerver másik szerveren található, add meg a hosztnevét vagy az IP-címét.
-
-Ha megosztott webtárhelyet használsz, a szolgáltató dokumentációjában megtalálható a helyes hosztnév.
-
-Ha Windows-alapú szerverre telepítesz, és MySQL-t használsz, a „localhost” nem biztos, hogy működni fog. Ha így van, próbáld meg a „127.0.0.1” helyi IP-cím használatát.', # Fuzzy
- 'config-db-host-oracle' => 'Adatbázis TNS:',
- 'config-db-wiki-settings' => 'A wiki azonosítása',
- 'config-db-name' => 'Adatbázisnév:',
- 'config-db-name-help' => 'Válassz egy nevet a wiki azonosítására.
-Ne tartalmazzon szóközt.
-
-Ha megosztott webtárhelyet használsz, a szolgáltatód vagy egy konkrét adatbázisnevet ad neked használatra, vagy te magad hozhatsz létre adatbázisokat a vezérlőpulton keresztül.',
- 'config-db-name-oracle' => 'Adatbázisséma:',
- 'config-db-account-oracle-warn' => 'Oracle adatbázisba való telepítésnek három támogatott módja van:
-
-Ha a telepítési folyamat során adatbázisfiókot szeretnél létrehozni, akkor egy olyan fiókot kell használnod, mely rendelkezik SYSDBA jogosultsággal, majd meg kell adnod a létrehozandó, webes hozzáféréshez használt fiók adatait. Emellett a fiók kézzel is létrehozható, ekkor ennek az adatait kell megadni (a fióknak rendelkeznie kell megfelelő jogosul adatbázis-objektumok létrehozásához), vagy megadhatsz két fiókot: egyet a létrehozáshoz szükséges jogosultságokkal, és egy korlátozottat a webes hozzáféréshez.
-
-A megfelelő jogosultságokkal rendelkező fiók létrehozásához használható szkript a szoftver „maintenance/oracle/” könyvtárában található. Ne feledd, hogy korlátozott fiók használatakor az alapértelmezett fiókkal nem végezhetőek el a karbantartási műveletek.',
- 'config-db-install-account' => 'A telepítéshez használt felhasználói fiók adatai',
- 'config-db-username' => 'Felhasználónév:',
- 'config-db-password' => 'Jelszó:',
- 'config-db-password-empty' => 'Írd be az új adatbázis-felhasználó jelszavát: $1
-Van lehetőség jelszó nélküli felhasználók létrehozására, azonban ez nem ajánlott.',
- 'config-db-install-username' => 'Írd be az adatbázisrendszerhez való csatlakozáshoz használt felhasználónevet.
-Ez nem a MediaWiki fiók felhasználóneve; ez az adatbázisrendszeren használt felhasználóneved.',
- 'config-db-install-password' => 'Írd be az adatbázisrendszerhez való csatlakozáshoz használt jelszót.
-Ez nem a MediaWiki-fiók jelszava; ez az adatbázisrendszeren használt jelszavad.',
- 'config-db-install-help' => 'Add meg a felhasználónevet és jelszót, amivel a telepítő csatlakozhat az adatbázishoz.',
- 'config-db-account-lock' => 'Általános működés során is ezen információk használata',
- 'config-db-wiki-account' => 'Általános működéshez használt felhasználói adatok',
- 'config-db-wiki-help' => 'Add meg azt a felhasználónevet és jelszót, amivel a wiki fog csatlakozni az adatbázishoz működés közben.
-Ha a fiók nem létezik és a telepítést végző fiók rendelkezik megfelelő jogosultsággal, egy új fiók készül a megadott a névvel, azon minimális jogosultságkörrel, ami a wiki működéséhez szükséges.',
- 'config-db-prefix' => 'Adatbázistáblák nevének előtagja:',
- 'config-db-prefix-help' => 'Ha egyetlen adatbázison osztozik több wiki, vagy a MediaWiki és más webalkalmazás, választhatsz egy előtagot a táblaneveknek, hogy megelőzd a konfliktusokat.
-Ne használj szóközöket.
-
-A mezőt általában üresen kell hagyni.',
- 'config-db-charset' => 'Az adatbázis karakterkészlete',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0, bináris',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0, visszafelé kompatibilis UTF-8',
- 'config-charset-help' => "'''Figyelmezetés:''' Ha a '''visszafelé kompatibilis UTF-8''' beállítást használod MySQL 4.1 vagy újabb verziók esetén, és utána a <code>mysqldump</code> programmal készítesz róla biztonsági másolatot, az tönkreteheti az összes nem ASCII-karaktert, visszafordíthatatlanul károsítva a másolatokban tárolt adatokat!
-
-'''Bináris''' módban a MediaWiki az UTF-8-ban kódolt szöveget bináris mezőkben tárolja az adatbázisban.
-Ez sokkal hatékonyabb a MySQL UTF-8-módjától, és lehetővé teszi, hogy a teljes Unicode-karakterkészletet használd.
-'''UTF-8-módban''' MySQL tudja, hogy milyen karakterkészlettel van kódolva az adat, és megfelelően tárolja és konvertálja, de
-nem használhatod a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] feletti karaktereket.",
- 'config-mysql-old' => 'A MySQL $1 vagy újabb verziója szükséges, a rendszeren $2 van.',
- 'config-db-port' => 'Adatbázisport:',
- 'config-db-schema' => 'MediaWiki-séma',
- 'config-db-schema-help' => 'A fenti sémák általában megfelelőek.
-Csak akkor módosíts rajtuk, ha tudod, hogy szükséges.',
- 'config-pg-test-error' => "Nem sikerült csatlakozni a(z) '''$1''' adatbázishoz: $2",
- 'config-sqlite-dir' => 'SQLite-adatkönyvtár:',
- 'config-sqlite-dir-help' => "Az SQLite minden adatot egyetlen fájlban tárol.
-
-A megadott könyvtárban írási jogosultsággal kell rendelkeznie a webszervernek.
-
-'''Nem''' szabad elérhetőnek lennie weben keresztül, ezért nem rakjuk oda, ahol a PHP-fájljaid vannak.
-
-A telepítő készít egy <code>.htaccess</code> fájlt az adatbázis mellé, azonban ha valamilyen okból nem sikerül, akkor akárki hozzáférhet a teljes adatbázisodhoz. Ez a felhasználók adatai (e-mail címek, jelszók hashei) mellett a törölt változatokat és más, korlátozott hozzáférésű információkat is tartalmaz.
-
-Fontold meg az adatbázis más helyre történő elhelyezését, például a <code>/var/lib/mediawiki/tewikid</code> könyvtárba.",
- 'config-oracle-def-ts' => 'Alapértelmezett táblatér:',
- 'config-oracle-temp-ts' => 'Ideiglenes táblatér:',
- 'config-support-info' => 'A MediaWiki a következő adatbázisrendszereket támogatja:
-
-$1
-
-Ha 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-support-mysql' => '* A $1 a MediaWiki elsődleges célpontja, így a legjobban támogatott ([http://www.php.net/manual/en/mysql.installation.php Hogyan fordítható a PHP MySQL-támogatással])',
- 'config-support-postgres' => '* A $1 népszerű, nyílt forráskódú adatbázisrendszer, a MySQL alternatívája ([http://www.php.net/manual/en/pgsql.installation.php Hogyan fordítható a PHP PostgreSQL-támogatással]). Több apró, javítatlan hiba is előfordulhat, így nem ajánlott éles környezetben használni.',
- 'config-support-sqlite' => '* Az $1 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-support-oracle' => '* Az $1 kereskedelmi, vállalati adatbázisrendszer. ([http://www.php.net/manual/en/oci8.installation.php Hogyan fordítható a PHP OCI8-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',
- 'config-header-oracle' => 'Oracle-beállítások',
- 'config-invalid-db-type' => 'Érvénytelen adatbázistípus',
- 'config-missing-db-name' => 'Meg kell adnod az „Adatbázisnév” értékét',
- 'config-missing-db-host' => 'Meg kell adnod az „Adatbázis hosztneve” értékét',
- 'config-missing-db-server-oracle' => 'Meg kell adnod az „Adatbázis TNS” értékét',
- 'config-invalid-db-server-oracle' => 'Érvénytelen adatbázis TNS: „$1”
-Csak ASCII betűk (a-z, A-Z), számok (0-9), alulvonás (_) és pont (.) használható.',
- 'config-invalid-db-name' => 'Érvénytelen adatbázisnév: „$1”.
-Csak ASCII-karakterek (a-z, A-Z), számok (0-9), alulvonás (_) és kötőjel (-) használható.',
- 'config-invalid-db-prefix' => 'Érvénytelen adatbázisnév-előtag: „$1”.
-Csak ASCII-karakterek (a-z, A-Z), számok (0-9), alulvonás (_) és kötőjel (-) használható.',
- 'config-connection-error' => '$1.
-
-Ellenőrizd a hosztot, felhasználónevet és jelszót, majd próbáld újra.',
- 'config-invalid-schema' => 'Érvénytelen MediaWiki-séma: „$1”.
-Csak ASCII-karakterek (a-z, A-Z), számok (0-9) és alulvonás (_) használható.',
- 'config-db-sys-create-oracle' => 'A telepítő csak a SYSDBA fiókkal tud új felhasználói fiókot létrehozni.',
- 'config-db-sys-user-exists-oracle' => 'Már létezik „$1” nevű felhasználói fiók. A SYSDBA csak új fiók létrehozására használható!',
- 'config-postgres-old' => 'A PostgreSQL $1 vagy újabb verziója szükséges, a rendszeren $2 van.',
- 'config-sqlite-name-help' => 'Válassz egy nevet a wiki azonosítására.
-Ne tartalmazzon szóközt vagy kötőjelet.
-Ez lesz az SQLite-adatfájl neve.',
- 'config-sqlite-parent-unwritable-group' => 'Nem hozható létre a(z) <code><nowiki>$1</nowiki></code> adatkönyvtár, mert a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>) nem írhat a webszerver.
-
-A telepítő megállapította, hogy mely felhasználó futtatja a webszervert.
-A folytatáshoz tedd írhatóvá a(z) <code><nowiki>$3</nowiki></code> könyvtárat.
-Unix/Linux rendszeren tedd a következőt:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Nem lehet létrehozni az adatok tárolásához szükséges <code><nowiki>$1</nowiki></code> könyvtárat, mert a webszerver nem írhat a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>).
-
-A telepítő nem tudta megállapíteni, hogy melyik felhasználói fiókon fut a webszerver.
-A folytatáshoz tedd írhatóvá ezen fiók (és más fiókok!) számára a következő könyvtárat: <code><nowiki>$3</nowiki></code>.
-Unix/Linux rendszereken tedd a következőt:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Nem sikerült létrehozni a következő adatkönyvtárat: „$1”.
-Ellenőrizd a helyet, majd próbáld újra.',
- 'config-sqlite-dir-unwritable' => 'Nem sikerült írni a következő könyvtárba: „$1”.
-Módosítsd a jogosultságokat úgy, hogy a webszerver tudjon oda írni, majd próbáld újra.',
- 'config-sqlite-connection-error' => '$1.
-
-Ellenőrizd az adatkönyvtárat és az adatbázisnevet, majd próbáld újra.',
- 'config-sqlite-readonly' => 'A következő fájl nem írható: <code>$1</code>.',
- 'config-sqlite-cant-create-db' => 'Nem sikerült létrehozni a következő adatbázisfájlt: <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'A PHP nem rendelkezik FTS3-támogatással, táblák visszaminősítése',
- 'config-can-upgrade' => "Ebben az adatábizban MediaWiki-táblák találhatóak.
-A MediaWiki $1 verzióra történő frissítéséhez kattints a '''Folytatás''' gombra.",
- 'config-upgrade-done' => "A frissítés befejeződött.
-
-Most már '''[$1 beléphetsz a wikibe]'''.
-
-Ha újra szeretnéd generálni a <code>LocalSettings.php</code> fájlt, kattints az alábbi gombra.
-Ez '''nem ajánlott''', csak akkor, ha problémák vannak a wikivel.",
- 'config-upgrade-done-no-regenerate' => "A frissítés befejeződött.
-
-Most már '''[$1 beléphetsz a wikibe]'''.",
- 'config-regenerate' => 'LocalSettings.php elkészítése újra →',
- 'config-show-table-status' => 'A <code>SHOW TABLE STATUS</code> lekérdezés nem sikerült!',
- 'config-unknown-collation' => "'''Figyelmeztetés:''' az adatbázis ismeretlen egybevetést használ.",
- 'config-db-web-account' => 'A webes hozzáférésnél használt adatbázisfiók',
- 'config-db-web-help' => 'Add meg azt a felhasználónevet és jelszót, amit a webszerver a wiki általános működése során használ a csatlakozáshoz.',
- 'config-db-web-account-same' => 'A telepítéshez használt fiók használata',
- 'config-db-web-create' => 'Fiók létrehozása, ha még nem létezik.',
- 'config-db-web-no-create-privs' => 'A telepítéshez megadott fiók nem rendelkezik megfelelő jogosultságokkal új felhasználó létrehozásához.
-Az itt megadott fióknak léteznie kell.',
- 'config-mysql-engine' => 'Tárolómotor:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Figyelmeztetés''': A MyISAM tárolómotort választottad, ami nem ajánlott a MediaWiki használatánál, mert:
-* nagyon rosszul kezeli a párhuzamos lekéréseket a táblák zárolása miatt
-* sokkal nagyobb az esélye az adatkorrupció kialakulásának
-* a MediaWiki kódbázisa nem mindig úgy kezeli a MyISAM-ot, ahogyan kellene
-
-Ha a feltelepített MySQL támogatja az InnoDB-t, erősen ajánlott, hogy inkább azt válaszd.
-Ha nem, akkor lehet, hogy itt az ideje a frissítésnek.",
- 'config-mysql-engine-help' => "A legtöbb esetben az '''InnoDB''' a legjobb választás, mivel megfelelően támogatja a párhuzamosságot.
-
-A '''MyISAM''' gyorsabb megoldás lehet egyfelhasználós vagy csak olvasható környezetekben, azonban a MyISAM-adatbázisok sokkal gyakrabban sérülnek meg, mint az InnoDB-adatbázisok.",
- 'config-mysql-charset' => 'Adatbázis karakterkészlete:',
- 'config-mysql-binary' => 'Bináris',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "'''Bináris módban''' a MediaWiki az UTF-8-as szövegeket bináris mezőkben tárolja az adatbázisban.
-Ez sokkal hatékonyabb a MySQL UTF-8-as módjánál, és lehetővé teszi a teljes Unicode-karakterkészlet használatát.
-
-'''UTF-8-as módban''' a MySQL tudni fogja,hogy az adatok milyen karakterkészlettel rendelkeznek, és megfelelően átalakítja őket, azonban nem tárolhatóak olyan karakterek, melyek a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] felett vannak.",
- 'config-site-name' => 'A wiki neve:',
- 'config-site-name-help' => 'A böngésző címsorában és még számos más helyen jelenik meg.',
- 'config-site-name-blank' => 'Add meg az oldal nevét.',
- 'config-project-namespace' => 'Projektnévtér:',
- 'config-ns-generic' => 'Projekt',
- 'config-ns-site-name' => 'Ugyanaz, mint a wiki neve: $1',
- 'config-ns-other' => 'Más (meg kell adni)',
- 'config-ns-other-default' => 'SajátWiki',
- 'config-project-namespace-help' => "A Wikipédia példáját követve számos wiki elkülöníti egy '''projekt névtérbe''' az irányelveit a tartalommal rendelkező lapoktól
-Az ebben a névtérben található lapok nevei egy előtaggal kezdődnek, amit itt adhatsz meg.
-Általában az előtag a wiki nevéből származik, de nem tartalmazhat írásjeleket, például „#”-t vagy „:”-t.",
- 'config-ns-invalid' => 'A megadott névtér („<nowiki>$1</nowiki>”) érvénytelen.
-Válassz másik projektnévteret!',
- 'config-ns-conflict' => 'A megadott névtér („<nowiki>$1</nowiki>”) ütközik az egyik alapértelmezett MediaWiki-névtérrel.
-Válassz másik projektnévteret!',
- 'config-admin-box' => 'Adminisztrátori fiók',
- 'config-admin-name' => 'Név:',
- 'config-admin-password' => 'Jelszó:',
- 'config-admin-password-confirm' => 'Jelszó újra:',
- 'config-admin-help' => 'Írd be a kívánt felhasználónevet, például „Kovács János”.
-Ezzel a névvel fogsz majd bejelentkezni a wikibe.',
- 'config-admin-name-blank' => 'Add meg az adminisztrátor felhasználónevét!',
- 'config-admin-name-invalid' => 'A megadott felhasználónév (<nowiki>$1</nowiki>) érvénytelen.
-Adj meg egy másik felhasználónevet.',
- 'config-admin-password-blank' => 'Add meg az adminisztrátori fiók jelszavát!',
- 'config-admin-password-same' => 'A jelszó nem lehet ugyanaz, mint a felhasználónév.',
- 'config-admin-password-mismatch' => 'A megadott jelszavak nem egyeznek.',
- 'config-admin-email' => 'E-mail cím:',
- 'config-admin-email-help' => 'Add meg az e-mail címedet, hogy más felhasználók küldhessenek e-maileket a wikin keresztül, új jelszót tudj kérni, és értesülhess a figyelőlistádon lévő lapokon történt változásokról. Üresen is hagyhatod ezt a mezőt.',
- 'config-admin-error-user' => 'Belső hiba történt a(z) „<nowiki>$1</nowiki>” nevű adminisztrátor létrehozásakor.',
- 'config-admin-error-password' => 'Belső hiba történt a(z) „<nowiki>$1</nowiki>” nevű adminisztrátor jelszavának beállításakor: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Érvénytelen e-mail címet adtál meg.',
- 'config-subscribe' => 'Feliratkozás a [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce kiadási bejelentések levelezőlistájára].',
- 'config-subscribe-help' => 'Ez egy alacsony forgalmú levelezőlista, ahol a kiadásokkal kapcsolatos bejelentések jelennek meg, a fontos biztonsági javításokkal együtt.
-Ajánlott feliratkozni rá, és frissíteni a MediaWikit, ha új verzió jön ki.',
- 'config-subscribe-noemail' => 'Anélkül próbáltál feliratkozni a kiadási bejelentések levelezőlistájára, hogy megadtál volna egy e-mail címet.
-Adj meg egyet, ha fel szeretnél iratkozni a levelezőlistára.',
- 'config-almost-done' => 'Már majdnem kész!
-A további konfigurációt kihagyhatod, és most azonnal elindíthatod a wiki telepítését.',
- 'config-optional-continue' => 'További információk megadása.',
- 'config-optional-skip' => 'Épp elég volt, települjön a wiki!',
- 'config-profile' => 'Felhasználói jogosultságok profilja:',
- 'config-profile-wiki' => 'Hagyományos wiki', # Fuzzy
- 'config-profile-no-anon' => 'Felhasználói fiók létrehozása szükséges',
- 'config-profile-fishbowl' => 'Csak engedélyezett szerkesztők',
- 'config-profile-private' => 'Privát wiki',
- 'config-profile-help' => "A wikik akkor működnek a legjobban, ha minél több felhasználó számára engedélyezett a szerkesztés.
-A MediaWikiben könnyű ellenőrizni a legutóbbi változtatásokat,és visszaállítani a naiv vagy káros felhasználók által okozott károkat.
-
-A MediaWiki azonban számos helyzetben hasznos lehet, és néha nem könnyű mindenkit meggyőzni a wiki előnyeiről.
-Választhatsz!
-
-'''{{int:config-profile-wiki}}kben''' bárki szerkeszthet, akár bejelentkezés nélkül is. A '''{{int:config-profile-no-anon}}''' beállítás további biztonságot nyújt, azonban elijesztheti az alkalmi szerkesztőket.
-
-Lehetőség van arra is, hogy '''{{lc:{{int:config-profile-fishbowl}}}}''' módosíthassák a lapokat, de a nyilvánosság ekkor megtekintheti a lapokat és azok laptörténetét is. '''{{int:config-profile-private}}''' esetén csak az engedélyezett szerkesztők tekinthetik meg a lapokat, és ugyanez a csoport szerkeszthet.
-
-Telepítés után jóval összetettebb jogosultságrendszer állítható össze, további információ a [//www.mediawiki.org/wiki/Manual:User_rights kézikönyv kapcsolódó bejegyzésében].", # Fuzzy
- 'config-license' => 'Szerzői jog és licenc:',
- 'config-license-none' => 'Nincs licencjelzés',
- 'config-license-cc-by-sa' => 'Creative Commons Nevezd meg! - Így add tovább!',
- 'config-license-cc-by' => 'Creative Commons Nevezd meg!',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Nevezd meg! - Ne add el! - Így add tovább!',
- 'config-license-cc-0' => 'Creative Commons Zero (közkincs)',
- 'config-license-gfdl' => 'GNU Szabad Dokumentációs Licenc 1.3 vagy újabb',
- 'config-license-pd' => 'Közkincs',
- 'config-license-cc-choose' => 'Creative Commons-licenc választása',
- 'config-license-help' => "A legtöbb wiki valamilyen [http://freedomdefined.org/Definition szabad licenc] alatt teszi közzé a szerkesztéseit.
-Ez erősíti a közösségi tulajdon érzését, és elősegíti a hosszú távú közreműködők megjelenését.
-Általában nem szükséges magán- vagy vállalati wiki esetén.
-
-Ha a Wikipédiáról szeretnél szövegeket másolni, és a Wikipédián felhasználhassák a wikidben található szöveget, akkor a '''Creative Commons Nevezd meg! - Így add tovább!''' lehetőséget válaszd.
-
-A Wikipédia korábban a GNU Szabad Dokumentációs Licencet használta.
-Ez a licenc még ma is használható, azonban nem könnyű megérteni,
-továbbá a GFDL alatt közzétett tartalom újrafelhasználása nehézkes.",
- 'config-email-settings' => 'E-mail beállítások',
- 'config-enable-email' => 'Kimenő e-mailek engedélyezése',
- 'config-enable-email-help' => 'E-mailek küldéséhez [http://www.php.net/manual/en/mail.configuration.php a PHP mail beállításait] megfelelően meg kell adni.
-Ha nem akarsz semmilyen e-mailes funkciót használni, itt tilthatod le őket.',
- 'config-email-user' => 'A felhasználók küldhetnek egymásnak e-maileket',
- 'config-email-user-help' => 'Bármelyik felhasználó küldhet másiknak e-mail üzenetet, amennyiben engedélyezték a lehetőséget a beállításaiknál.',
- 'config-email-usertalk' => 'Vitalapi értesítések engedélyezése',
- 'config-email-usertalk-help' => 'A felhasználók értesítéseket kapnak a vitalapjuk változásairól, amennyiben engedélyezték ezt a lehetőséget a beállításaiknál.',
- 'config-email-watchlist' => 'Figyelőlistai értesítések engedélyezése',
- 'config-email-watchlist-help' => 'A felhasználók értesítéseket kapnak a figyelt lapjaik változásairól, amennyiben engedélyezték ezt a lehetőséget a beállításaiknál.',
- 'config-email-auth' => 'E-mailes hitelesítés engedélyezése',
- 'config-email-auth-help' => "Ha a beállítás engedélyezve van, a felhasználóknak meg kell erősíteniük az e-mail címüket egy kiküldött link segítségével, amikor megadják vagy módosítják azt.
-Csak a megerősített e-mail címmel rendelkezők kaphatnak e-maileket más felhasználóktól vagy értesítéseket.
-A beállítás engedélyezése '''ajánlott''' publikus wikiknél, mivel így megakadályozható az e-mailes funkciókkal való visszaélés.",
- 'config-email-sender' => 'Válaszcím:',
- 'config-email-sender-help' => 'Add meg a kimenő e-mail-üzenetek válaszcímét.
-Ide lesznek küldve a visszapattant üzenetek is.
-Számos levelezőszerver számára a cím domainrészének érvényesnek kell lennie.',
- 'config-upload-settings' => 'Képek és fájlok feltöltése',
- 'config-upload-enable' => 'Fájlfeltöltés engedélyezése',
- 'config-upload-help' => 'A fájlfeltöltés lehetséges biztonsági kockázatoknak teszi ki a szerveredet.
-További információért olvasd el a [//www.mediawiki.org/wiki/Manual:Security biztonságról szóló szakaszt] a kézikönyvben.
-
-A fájlfeltöltés engedélyezéséhez változtasd meg a MediaWiki gyökérkönyvtárában található <code>images</code> alkönyvtár jogosultságát úgy, hogy a szerver írhasson oda, majd engedélyezd itt a beállítást.',
- 'config-upload-deleted' => 'Törölt fájlok könyvtára:',
- 'config-upload-deleted-help' => 'Válaszd ki azt a könyvtárat, ahol a törölt fájlok lesznek archiválva.
-Normális esetben ennek nem szabad elérhetőnek lennie az internetről.',
- 'config-logo' => 'A logó URL-címe:',
- 'config-logo-help' => 'A MediaWiki alapértelmezett felülete helyet ad egy 135×160 pixeles logónak a bal felső sarokban.
-Tölts fel egy megfelelő méretű képet, majd írd be ide az URL-címét!
-
-Ha nem szeretnél logót használni, egyszerűen hagyd üresen a mezőt.', # Fuzzy
- 'config-instantcommons' => 'Instant Commons engedélyezése',
- 'config-instantcommons-help' => 'Az [//www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [//commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.
-A használatához a MediaWikinek internethozzáférésre van szüksége.
-
-A funkcióról és hogy hogyan állítható be más wikik esetén [//mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhatsz további információkat.',
- 'config-cc-error' => 'A Creative Commons-licencválasztó nem tért vissza eredménnyel.
-Add meg kézzel a licencet.',
- 'config-cc-again' => 'Válassz újra…',
- 'config-cc-not-chosen' => 'Válaszd ki a kívánt Creative Commons licencet, majd kattints a „Folytatás gombra”!',
- 'config-advanced-settings' => 'Haladó beállítások',
- 'config-cache-options' => 'Objektum-gyorsítótárazás beállításai:',
- 'config-cache-help' => 'Az objektumgyorsítótárazás célja, hogy felgyorsítsa a MediaWiki működését a gyakran használt adatok gyorsítótárazásával.
-Közepes vagy nagyobb oldalak esetén erősen ajánlott a használata, de kisebb oldalak esetén is hasznos lehet.',
- 'config-cache-none' => 'Nincs gyorsítótárazás (minden funkció működik, de nagyobb wiki esetében lassabb működést eredményezhet)',
- 'config-cache-accel' => 'PHP-objektumok gyorsítótárazása (APC, XCache or WinCache)',
- 'config-cache-memcached' => 'Memcached használata (további telepítés és konfigurálás szükséges)',
- 'config-memcached-servers' => 'Memcached-szerverek:',
- 'config-memcached-help' => 'Azon IP-címek listája, melyeket a Memcached használhat.
-Vesszővel kell elválasztani őket, és meg kell adni a portot is. Például:
- 127.0.0.1:11211
- 192.168.1.25:11211',
- 'config-memcache-needservers' => 'Memcachedet választottad gyorsítótárnak, de nem adtál meg egyetlen szervert sem.',
- 'config-memcache-badip' => 'Érvénytelen IP-címet adtál meg a Memcachednek: $1.',
- 'config-memcache-noport' => 'Nem adtál meg portot a Memcached-szervernek: $1.
-Ha nem ismered a portszámot, használd az alapértelmezettet: 11211.',
- 'config-memcache-badport' => 'A Memcached a(z) $1 és $2 közötti portokat szokta használni.',
- 'config-extensions' => 'Kiterjesztések',
- 'config-extensions-help' => 'A fent felsorolt kiterjesztések találhatóak meg az <code>./extensions</code> könyvtárban.
-
-Lehetséges, hogy további beállításra lesz szükség hozzájuk, de már most engedélyezheted őket.',
- 'config-install-alreadydone' => "'''Figyelmeztetés:''' Úgy tűnik, hogy a MediaWiki telepítve van, és te ismét megpróbálod telepíteni.
-Folytasd a következő oldalon.",
- 'config-install-begin' => 'A „{{int:config-continue}}” gomb megnyomása elindítja a MediaWiki telepítését.
-Ha szeretnél módosítani a beállításokon, kattints a vissza gombra.', # Fuzzy
- 'config-install-step-done' => 'kész',
- 'config-install-step-failed' => 'sikertelen',
- 'config-install-extensions' => 'Kiterjesztések beillesztése',
- 'config-install-database' => 'Adatbázis felállítása',
- 'config-install-schema' => 'Adatbázis-szerkezet létrehozása',
- 'config-install-pg-schema-not-exist' => 'A PostgreSQL-adatbázis nem létezik.',
- 'config-install-pg-schema-failed' => 'A táblák létrehozása nem sikerült.
-Ellenőrizd, hogy „$1” felhasználó írhat-e a következő adatbázisba: „$2”.',
- 'config-install-pg-commit' => 'Változtatások közzététele',
- 'config-install-pg-plpgsql' => 'PL/pgSQL nyelv meglétének ellenőrzése',
- 'config-pg-no-plpgsql' => 'Telepítened kell a PL/pgSQL nyelvet a következő adatbázishoz: $1',
- 'config-pg-no-create-privs' => 'A telepítéshez megadott felhasználói fiók nem rendelkezik új fiók létrehozásához szükséges jogosultságokkal.',
- 'config-install-user' => 'Adatbázis-felhasználó létrehozása',
- 'config-install-user-alreadyexists' => 'Már létezik „$1” nevű felhasználó',
- 'config-install-user-create-failed' => 'Nem sikerült a(z) „$1” nevű felhasználó létrehozása: $2',
- 'config-install-user-grant-failed' => 'Nem sikerült jogosultságokkal felruházni a(z) „$1” nevű felhasználót: $2',
- 'config-install-user-missing' => 'A megadott felhasználó („$1”) nem létezik.',
- 'config-install-user-missing-create' => 'A megadott felhasználó („$1”) nem létezik.
-Pipáld ki a „Fiók létrehozása” dobozt, ha létre szeretnéd hozni.',
- 'config-install-tables' => 'Táblák létrehozása',
- 'config-install-tables-exist' => "'''Figyelmeztetés''': úgy tűnik, hogy a MediaWiki táblái már léteznek.
-Létrehozás kihagyása.",
- 'config-install-tables-failed' => "'''Hiba''': a tábla létrehozása nem sikerült a következő miatt: $1",
- 'config-install-interwiki' => 'Alapértelmezett nyelvközihivatkozás-tábla feltöltése',
- 'config-install-interwiki-list' => 'Az <code>interwiki.list</code> fájl nem található.',
- 'config-install-interwiki-exists' => "'''Figyelmeztetés''': Úgy tűnik, hogy az interwiki táblában már vannak bejegyzések.
-Alapértelmezett lista kihagyása.",
- 'config-install-stats' => 'Statisztika inicializálása',
- 'config-install-keys' => 'Titkos kulcsok generálása',
- 'config-insecure-keys' => "'''Figyelmeztetés:''' A telepítés során generált $1 {{PLURAL:$2|biztonsági kulcs|biztonsági kulcsok}} nem teljesen $1 {{PLURAL:$2|biztonságos|biztonságosak}}. Érdemes {{PLURAL:$2||őket}} manuálisan megváltoztatni.",
- 'config-install-sysop' => 'Az adminisztrátor felhasználói fiókjának létrehozása',
- 'config-install-subscribe-fail' => 'Nem sikerült feliratkozni a mediawiki-announce levelezőlistára: $1',
- 'config-install-subscribe-notpossible' => 'A cURL nincs telepítve és az allow_url_fopen nem érhető el.',
- 'config-install-mainpage' => 'Kezdőlap létrehozása az alapértelmezett tartalommal',
- 'config-install-extension-tables' => 'Táblák létrehozása az engedélyezett kiterjesztésekhez',
- 'config-install-mainpage-failed' => 'Nemsikerült létrehozni a kezdőlapot: $1',
- 'config-install-done' => "'''Gratulálunk!'''
-A MediaWiki telepítése sikeresen befejeződött.
-
-A telepítő elkészítette a <code>LocalSettings.php</code> fájlt, amely tartalmazza az összes beállítást.
-
-Ezt le kell tölteni, majd elhelyezni a wiki telepítési könyvtárába (az a könyvtár, ahol az index.php is található).
-
-A letöltés automatikusan elindul. Ha mégsem indulna el, vagy megszakítottad, az alábbi linkre kattintva újra letöltheted:
-
-$3
-
-'''Megjegyzés''': Ha ezt most nem teszed meg, és kilépsz a telepítésből, az elkészített konfigurációs fájlt nem tudod elérni a későbbiekben.
-
-Ha végeztél a fájl elhelyezésével, '''[$2 beléphetsz a wikibe]'''.",
- 'config-download-localsettings' => '<code>LocalSettings.php</code> letöltése',
- 'config-help' => 'segítség',
- 'mainpagetext' => "'''A MediaWiki telepítése sikeresen befejeződött.'''",
- 'mainpagedocfooter' => "Ha segítségre van szükséged a wikiszoftver használatához, akkor keresd fel a [//meta.wikimedia.org/wiki/Help:Contents User's Guide] oldalt.
-
-== Alapok (angol nyelven) ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Beállítások listája]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki GyIK]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]", # Fuzzy
-);
-
-/** Magyar (magázó) (Magyar (magázó))
- * @author Dani
- * @author Glanthor Reviol
- */
-$messages['hu-formal'] = array(
- 'config-localsettings-upgrade' => "'''Figyelmeztetés''': már létezik a <code>LocalSettings.php</code> fájl.
-A szoftver frissíthető.
-Adja meg a <code>\$wgUpgradeKey</code>-ben található kulcsot a beviteli mezőben", # Fuzzy
- 'config-session-expired' => 'Úgy tűnik, hogy a munkamenetadatok lejártak.
-A munkamenetek élettartama a következőre van beállítva: $1.
-Az érték növelhető a php.ini <code>session.gc_maxlifetime</code> beállításának módosításával.
-Indítsa újra a telepítési folyamatot.',
- 'config-no-session' => 'Elvesztek a munkamenetadatok!
-Ellenőrizze, hogy a php.ini-ben a <code>session.save_path</code> beállítás a megfelelő könyvtárra mutat-e.',
- 'config-your-language-help' => 'Válassza ki a telepítési folyamat során használandó nyelvet.',
- 'config-wiki-language-help' => 'Az a nyelv, amin a wiki tartalmának legnagyobb része íródik.',
- 'config-page-welcome' => 'Üdvözli a MediaWiki!',
- 'config-help-restart' => 'Szeretné törölni az eddig megadott összes adatot és újraindítani a telepítési folyamatot?',
- 'config-welcome' => '=== Környezet ellenőrzése ===
-Alapvető ellenőrzés, ami megmondja, hogy a környezet alkalmas-e a MediaWiki számára.
-Ha probléma merülne fel a telepítés során, meg kell adnia mások számára az alább megjelenő információkat.',
- 'config-unicode-pure-php-warning' => "'''Figyelmeztetés''': Az [http://pecl.php.net/intl intl PECL kiterjesztés] nem érhető el Unicode normalizáláshoz.
-Ha nagy látogatottságú oldalt üzemeltet, itt találhat információkat [//www.mediawiki.org/wiki/Unicode_normalization_considerations a témáról].", # Fuzzy
- 'config-register-globals' => "'''Figyelmeztetés: A PHP <code>[http://php.net/register_globals register_globals]</code> beállítása engedélyezve van.'''
-'''Tiltsa le, ha van rá lehetősége.'''
-A MediaWiki működőképes a beállítás használata mellett, de a szerver biztonsági kockázatnak lesz kitéve.",
- 'config-imagemagick' => 'Az ImageMagick megtalálható a rendszeren: <code>$1</code>.
-A bélyegképek készítése engedélyezve lesz, ha engedélyezi a feltöltéseket.',
- 'config-db-name-help' => 'Válassza ki a wikije azonosítására használt nevet.
-Nem tartalmazhat szóközt vagy kötőjelet.
-
-Ha megosztott webtárhelyet használ, a szolgáltatója vagy egy konkrét adatbázisnevet ad önnek használatra, vagy létrehozhat egyet a vezérlőpulton keresztül.', # Fuzzy
- 'config-db-install-help' => 'Adja meg a felhasználónevet és jelszót, amivel a telepítő csatlakozhat az adatbázishoz.',
- 'config-db-wiki-help' => 'Adja meg azt a felhasználónevet és jelszót, amivel a wiki fog csatlakozni az adatbázishoz működés közben.
-Ha a fiók nem létezik és a telepítést végző fiók rendelkezik megfelelő jogosultsággal, egy új fiók készül a megadott a névvel, azon minimális jogosultságkörrel, ami a wiki működéséhez szükséges.',
- 'config-charset-help' => "'''Figyelmezetés:''' Ha a '''visszafelé kompatibilis UTF-8''' beállítást használja MySQL 4.1 vagy újabb verziók esetén, és utána a <code>mysqldump</code> programmal készít róla biztonsági másolatot, az tönkreteheti az összes nem ASCII-karaktert, visszafordíthatatlanul károsítva a másolatokban tárolt adatokat!
-
-'''Bináris''' módban a MediaWiki az UTF-8-ban kódolt szöveget bináris mezőkben tárolja az adatbázisban.
-Ez sokkal hatékonyabb a MySQL UTF-8-módjától, és lehetővé teszi, hogy a teljes Unicode-karakterkészletet használja.
-'''UTF-8-módban''' MySQL tudja, hogy milyen karakterkészlettel van kódolva az adat, megfelelően van megjelenítve és konvertálva, de
-nem használhatja a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] feletti karaktereket.",
- 'config-db-schema-help' => 'A fenti sémák általában megfelelőek.
-Csak akkor módosítson rajta, ha szükség van rá.', # Fuzzy
- 'config-sqlite-parent-unwritable-nogroup' => 'Nem lehet létrehozni az adatok tárolásához szükséges <code><nowiki>$1</nowiki></code> könyvtárat, mert a webszerver nem írhat a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>).
-
-A telepítő nem tudta megállapíteni, hogy melyik felhasználói fiókon fut a webszerver.
-A folytatáshoz tegye írhatóvá ezen fiók (és más fiókok!) számára a következő könyvtárat: <code><nowiki>$3</nowiki></code>.
-Unix/Linux rendszereken tedd a következőt:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-ns-other' => 'Más (adja meg)',
- 'config-admin-name-blank' => 'Adja meg az adminisztrátor felhasználónevét!',
- 'config-admin-name-invalid' => 'A megadott felhasználónév (<nowiki>$1</nowiki>) érvénytelen.
-Adjon meg egy másik felhasználónevet.',
- 'config-admin-password-blank' => 'Adja meg az adminisztrátori fiók jelszavát!',
- 'config-instantcommons-help' => 'Az [//www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [//commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.
-A használatához a MediaWikinek internethozzáférésre van szüksége.
-
-A funkcióról és hogy hogyan állítható be más wikik esetén [//mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhat további információkat.',
- 'config-install-done' => "'''Gratulálunk!'''
-Sikeresen telepítette a MediaWikit.
-
-A telepítő készített egy <code>LocalSettings.php</code> fájlt.
-Ez tartalmazza az összes beállítást.
-
-[$1 Le kell töltenie], és el kell helyeznie a MediaWiki telepítési könyvtárába (az a könyvtár, ahol az index.php van).
-'''Megjegyzés''': Ha ezt most nem teszi meg, és kilép, a generált fájl nem lesz elérhető a későbbiekben.
-
-Ha ezzel készen van, '''[$2 beléphet a wikibe]'''.", # Fuzzy
- 'mainpagedocfooter' => "Ha segítségre van szüksége a wikiszoftver használatához, akkor keresse fel a [//meta.wikimedia.org/wiki/Help:Contents User's Guide] oldalt.
-
-== Alapok (angol nyelven) ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Beállítások listája]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki GyIK]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]", # Fuzzy
-);
-
-/** Armenian (Հայերեն)
- */
-$messages['hy'] = array(
- 'mainpagetext' => "'''«MediaWiki» ծրագիրը հաջողությամբ տեղադրվեց։'''",
- 'mainpagedocfooter' => "Այցելեք [//meta.wikimedia.org/wiki/Help:Contents User's Guide]՝ վիքի ծրագրային ապահովման օգտագործման մասին տեղեկությունների համար։
-
-== Որոշ օգտակար ռեսուրսներ ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Interlingua (interlingua)
- * @author McDutchie
- * @author 아라
- */
-$messages['ia'] = array(
- 'config-desc' => 'Le installator de MediaWiki',
- 'config-title' => 'Installation de MediaWiki $1',
- 'config-information' => 'Information',
- 'config-localsettings-upgrade' => 'Un file <code>LocalSettings.php</code> ha essite detegite.
-Pro actualisar iste installation, per favor entra le valor de <code>$wgUpgradeKey</code> in le quadro hic infra.
-Iste se trova in <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Un file <code>LocalSettings.php</code> file ha essite detegite.
-Pro actualisar iste installation, per favor executa <code>update.php</code>.',
- 'config-localsettings-key' => 'Clave de actualisation:',
- 'config-localsettings-badkey' => 'Le clave que tu forniva es incorrecte',
- 'config-upgrade-key-missing' => 'Un installation existente de MediaWiki ha essite detegite.
-Pro actualisar iste installation, es necessari adjunger le sequente linea al fin del file <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Le file <code>LocalSettings.php</code> existente pare esser incomplete.
-Le variabile $1 non es definite.
-Per favor cambia <code>LocalSettings.php</code> de sorta que iste variabile es definite, e clicca "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Un error esseva incontrate durante le connexion al base de datos usante le configurationes specificate in <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Per favor repara iste configurationes e tenta lo de novo.
-
-$1',
- 'config-session-error' => 'Error al comenciamento del session: $1',
- 'config-session-expired' => 'Le datos de tu session pare haber expirate.
-Le sessiones es configurate pro un duration de $1.
-Tu pote augmentar isto per definir <code>session.gc_maxlifetime</code> in php.ini.
-Reinitia le processo de installation.',
- 'config-no-session' => 'Le datos de tu session es perdite!
-Verifica tu php.ini e assecura te que un directorio appropriate es definite in <code>session.save_path</code>.',
- 'config-your-language' => 'Tu lingua:',
- 'config-your-language-help' => 'Selige un lingua a usar durante le processo de installation.',
- 'config-wiki-language' => 'Lingua del wiki:',
- 'config-wiki-language-help' => 'Selige le lingua in que le wiki essera predominantemente scribite.',
- 'config-back' => '← Retro',
- 'config-continue' => 'Continuar →',
- 'config-page-language' => 'Lingua',
- 'config-page-welcome' => 'Benvenite a MediaWiki!',
- 'config-page-dbconnect' => 'Connecter al base de datos',
- 'config-page-upgrade' => 'Actualisar le installation existente',
- 'config-page-dbsettings' => 'Configuration del base de datos',
- 'config-page-name' => 'Nomine',
- 'config-page-options' => 'Optiones',
- 'config-page-install' => 'Installar',
- 'config-page-complete' => 'Complete!',
- 'config-page-restart' => 'Reinitiar installation',
- 'config-page-readme' => 'Lege me',
- 'config-page-releasenotes' => 'Notas del version',
- 'config-page-copying' => 'Copiar',
- 'config-page-upgradedoc' => 'Actualisar',
- 'config-page-existingwiki' => 'Wiki existente',
- 'config-help-restart' => 'Vole tu rader tote le datos salveguardate que tu ha entrate e reinitiar le processo de installation?',
- 'config-restart' => 'Si, reinitia lo',
- 'config-welcome' => '=== Verificationes del ambiente ===
-Verificationes de base es exequite pro determinar si iste ambiente es apte pro le installation de MediaWiki.
-Tu deberea indicar le resultatos de iste verificationes si tu ha besonio de adjuta durante le installation.',
- 'config-copyright' => "=== Copyright and Terms ===
-
-$1
-
-Iste programma es software libere; vos pote redistribuer lo e/o modificar lo sub le conditiones del Licentia Public General de GNU publicate per le Free Software Foundation; version 2 del Licentia, o (a vostre option) qualcunque version posterior.
-
-Iste programma es distribuite in le sperantia que illo sia utile, ma '''sin garantia''', sin mesmo le implicite garantia de '''commercialisation''' o '''aptitude pro un proposito particular'''.
-Vide le Licentia Public General de GNU pro plus detalios.
-
-Vos deberea haber recipite <doclink href=Copying>un exemplar del Licentia Public General de GNU</doclink> con iste programma; si non, scribe al Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [http://www.gnu.org/copyleft/gpl.html lege lo in linea].",
- 'config-sidebar' => '* [//www.mediawiki.org Pagina principal de MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Guida pro usatores]
-* [//www.mediawiki.org/wiki/Manual:Contents Guida pro administratores]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>Lege me</doclink>
-* <doclink href=ReleaseNotes>Notas de iste version</doclink>
-* <doclink href=Copying>Conditiones de copia</doclink>
-* <doclink href=UpgradeDoc>Actualisation</doclink>',
- 'config-env-good' => 'Le ambiente ha essite verificate.
-Tu pote installar MediaWiki.',
- 'config-env-bad' => 'Le ambiente ha essite verificate.
-Tu non pote installar MediaWiki.',
- 'config-env-php' => 'PHP $1 es installate.',
- 'config-env-php-toolow' => 'PHP $1 es installate.
-Nonobstante, MediaWiki require PHP $2 o plus recente.',
- 'config-unicode-using-utf8' => 'utf8_normalize.so per Brion Vibber es usate pro le normalisation Unicode.',
- 'config-unicode-using-intl' => 'Le [http://pecl.php.net/intl extension PECL intl] es usate pro le normalisation Unicode.',
- 'config-unicode-pure-php-warning' => "'''Aviso''': Le [http://pecl.php.net/intl extension PECL intl] non es disponibile pro exequer le normalisation Unicode; le systema recurre al implementation lente in PHP pur.
-Si tu sito ha un alte volumine de traffico, tu deberea informar te un poco super le [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalisation Unicode].",
- 'config-unicode-update-warning' => "'''Aviso''': Le version installate del bibliotheca inveloppante pro normalisation Unicode usa un version ancian del bibliotheca del [http://site.icu-project.org/ projecto ICU].
-Tu deberea [//www.mediawiki.org/wiki/Unicode_normalization_considerations actualisar lo] si le uso de Unicode importa a te.",
- 'config-no-db' => 'Non poteva trovar un driver appropriate pro le base de datos! Es necessari installar un driver de base de datos pro PHP.
-Le sequente typos de base de datos es supportate: $1.
-
-Si tu sito usa un servitor dividite (shared hosting), demanda a tu providitor de installar un driver de base de datos appropriate.
-Si tu compilava PHP tu mesme, reconfigura lo con un cliente de base de datos activate, per exemplo usante <code>./configure --with-mysql</code>.
-Si tu installava PHP ex un pacchetto Debian o Ubuntu, tu debe installar equalmente le modulo php5-mysql.',
- 'config-outdated-sqlite' => "'''Attention''': tu ha SQLite $1, que es inferior al version minimal requirite, $2. SQLite essera indisponibile.",
- 'config-no-fts3' => "'''Attention''': SQLite es compilate sin [//sqlite.org/fts3.html modulo FTS3]; functionalitate de recerca non essera disponibile in iste back-end.",
- 'config-register-globals' => "'''Attention: le option <code>[http://php.net/register_globals register_globals]</code> de PHP es activate.'''
-'''Disactiva lo si tu pote.'''
-MediaWiki functionara, ma tu servitor es exponite a potential vulnerabilitates de securitate.",
- 'config-magic-quotes-runtime' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] es active!'''
-Iste option corrumpe le entrata de datos imprevisibilemente.
-Tu non pote installar o usar MediaWiki si iste option non es disactivate.",
- 'config-magic-quotes-sybase' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] es active!'''
-Iste option corrumpe le entrata de datos imprevisibilemente.
-Tu non pote installar o usar MediaWiki si iste option non es disactivate.",
- 'config-mbstring' => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] es active!'''
-Iste option causa errores e pote corrumper datos imprevisibilemente.
-Tu non pote installar o usar MediaWiki si iste option non es disactivate.",
- 'config-ze1' => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] es active!'''
-Iste option causa horribile defectos con MediaWiki.
-Tu non pote installar o usar MediaWiki si iste option non es disactivate.",
- 'config-safe-mode' => "'''Aviso:''' Le [http://www.php.net/features.safe-mode modo secur] de PHP es active.
-Isto pote causar problemas, particularmente si es usate le incargamento de files e le supporto de <code>math</code>.",
- 'config-xml-bad' => 'Le modulo XML de PHP es mancante.
-MediaWiki require functiones de iste modulo e non functionara in iste configuration.
-Si tu usa Mandrake, installa le pacchetto php-xml.',
- 'config-pcre' => 'Le modulo de supporto PCRE pare esser mancante.
-MediaWiki require le functiones de expression regular compatibile con Perl pro poter functionar.',
- 'config-pcre-no-utf8' => "'''Fatal''': Le modulo PCRE de PHP pare haber essite compilate sin supporto de PCRE_UTF8.
-MediaWiki 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.
-Isto es probabilemente troppo basse.
-Le installation pote faller!",
- 'config-ctype' => "'''Fatal''': PHP debe esser compilate con supporto pro le [http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] es installate',
- 'config-apc' => '[http://www.php.net/apc APC] es installate',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] es installate',
- 'config-no-cache' => "'''Aviso:''' Non poteva trovar [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
-Le cache de objectos non es activate.",
- 'config-mod-security' => "'''Attention''': [http://modsecurity.org/ mod_security] es active in tu servitor web. Si mal configurate, isto pote causar problemas pro MediaWiki o altere software que permitte al usatores de publicar contento arbitrari.
-Consulta le [http://modsecurity.org/documentation/ documentation de mod_security] o contacta le servicio de adjuta de tu host si tu incontra estranie errores.",
- 'config-diff3-bad' => 'GNU diff3 non trovate.',
- 'config-imagemagick' => 'ImageMagick trovate: <code>$1</code>.
-Le miniaturas de imagines essera activate si tu activa le incargamento de files.',
- 'config-gd' => 'Le bibliotheca graphic GD se trova integrate in le systema.
-Le miniaturas de imagines essera activate si tu activa le incargamento de files.',
- 'config-no-scaling' => 'Non poteva trovar le bibliotheca GD ni ImageMagick.
-Le miniaturas de imagines essera disactivate.',
- 'config-no-uri' => "'''Error:''' Non poteva determinar le URI actual.
-Installation abortate.",
- 'config-no-cli-uri' => "'''Attention''': Cammino al script (--scriptpath) non specificate. Le predefinition es usate: <code>$1</code>.",
- 'config-using-server' => 'Es usate le nomine de servitor "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Le URL de servitor "<nowiki>$1$2</nowiki>" es usate.',
- 'config-uploads-not-safe' => "'''Aviso:''' Le directorio predefinite pro files incargate <code>$1</code> es vulnerabile al execution arbitrari de scripts.
-Ben que MediaWiki verifica tote le files incargate contra le menacias de securitate, il es altemente recommendate [//www.mediawiki.org/wiki/Manual:Security#Upload_security remediar iste vulnerabilitate de securitate] ante de activar le incargamento de files.",
- 'config-no-cli-uploads-check' => "'''Attention:''' Le directorio predefinite pro files incargate (<code>$1</code>) non es verificate contra le vulnerabilitate
-al execution arbitrari de scripts durante le installation de CLI.",
- 'config-brokenlibxml' => 'Vostre systema ha un combination de versiones de PHP e libxml2 que es defectuose e pote causar corruption celate de datos in MediaWiki e altere applicationes web.
-Actualisa a PHP 5.2.9 o plus recente e libxml2 2.7.3 o plus recente ([//bugs.php.net/bug.php?id=45996 problema reportate presso PHP]).
-Installation abortate.',
- 'config-using531' => 'MediaWiki non pote esser usate con PHP $1 a causa de un defecto concernente parametros de referentia a <code>__call()</code>.
-Actualisa a PHP 5.3.2 o plus recente, o retrograda a PHP 5.3.0 pro remediar isto.
-Installation abortate.',
- 'config-suhosin-max-value-length' => 'Suhosin es installate e limita parametro <code>length</code> de GET a $1 bytes.
-Le componente ResourceLoader de MediaWiki va contornar iste limite, ma isto prejudicara le rendimento.
-Si possibile, tu deberea mitter <code>suhosin.get.max_value_length</code> a 1024 o superior in <code>php.ini</code>, e mitter <code>$wgResourceLoaderMaxQueryLength</code> al mesme valor in <code>LocalSettings.php</code>.',
- 'config-db-type' => 'Typo de base de datos:',
- 'config-db-host' => 'Servitor de base de datos:',
- 'config-db-host-help' => 'Si tu servitor de base de datos es in un altere servitor, entra hic le nomine o adresse IP del servitor.
-
-Si tu usa un servitor web usate in commun, tu providitor deberea dar te le correcte nomine de servitor in su documentation.
-
-Si tu face le installation in un servitor Windows e usa MySQL, le nomine "localhost" possibilemente non functiona como nomine de servitor. In tal caso, essaya "127.0.0.1", i.e. le adresse IP local.
-
-Si tu usa PostgreSQL, lassa iste campo vacue pro connecter via un "socket" de Unix.',
- 'config-db-host-oracle' => 'TNS del base de datos:',
- 'config-db-host-oracle-help' => 'Entra un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nomine Local Connect] valide; un file tnsnames.ora debe esser visibile a iste installation.<br />Si tu usa bibliothecas de cliente 10g o plus recente, tu pote anque usar le methodo de nomination [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identificar iste wiki',
- 'config-db-name' => 'Nomine del base de datos:',
- 'config-db-name-help' => 'Selige un nomine que identifica tu wiki.
-Illo non pote continer spatios.
-
-Si tu usa un servitor web usate in commun, tu providitor te fornira le nomine specific de un base de datos a usar, o te permitte crear un base de datos via un pannello de controlo.',
- 'config-db-name-oracle' => 'Schema del base de datos:',
- 'config-db-account-oracle-warn' => 'Il ha tres scenarios supportate pro le installation de Oracle como le base de datos de iste systema:
-
-Si tu vole crear un conto del base de datos como parte del processo de installation, per favor specifica un conto con le rolo SYSDBA como le conto del base de datos pro installation, e specifica le nomine e contrasigno desirate pro le conto de accesso per web. Alteremente tu pote crear le conto de accesso per web manualmente e specificar solmente iste conto (si illo ha le permissiones requisite pro crear le objectos de schema) o specifica duo contos differente, un con privilegios de creation e un conto restringite pro accesso per web.
-
-Un script pro crear un conto con le privilegios requisite se trova in le directorio "maintenance/oracle/" de iste installation. Non oblida que le uso de un conto restringite disactiva tote le capacitates de mantenentia in le conto predefinite.',
- 'config-db-install-account' => 'Conto de usator pro installation',
- 'config-db-username' => 'Nomine de usator del base de datos:',
- 'config-db-password' => 'Contrasigno del base de datos:',
- 'config-db-password-empty' => 'Per favor entra un contrasigno pro le nove usator del base de datos: $1.
-Ben que il es possibile crear usatores sin contrasigno, isto non es secur.',
- 'config-db-install-username' => 'Entra le nomine de usator que essera usate pro connecter al base de datos durante le processo de installation. Isto non es le nomine de usator del conto MediaWiki; isto es le nomine de usator pro tu base de datos.',
- 'config-db-install-password' => 'Entra le contrasigno que essera usate pro connecter al base de datos durante le processo de installation. Isto non es le contrasigno del conto MediaWiki; isto es le contrasigno pro tu base de datos.',
- 'config-db-install-help' => 'Entra le nomine de usator e contrasigno que essera usate pro connecter al base de datos durante le processo de installation.',
- 'config-db-account-lock' => 'Usar le mesme nomine de usator e contrasigno durante le operation normal',
- 'config-db-wiki-account' => 'Conto de usator pro operation normal',
- 'config-db-wiki-help' => 'Entra le nomine de usator e contrasigno que essera usate pro connecter al base de datos durante le operation normal del wiki.
-Si le conto non existe, e si le conto de installation possede sufficiente privilegios, iste conto de usator essera create con le minime privilegios necessari pro operar le wiki.',
- 'config-db-prefix' => 'Prefixo de tabella del base de datos:',
- 'config-db-prefix-help' => 'Si il es necessari usar un base de datos in commun inter multiple wikis, o inter MediaWiki e un altere application web, tu pote optar pro adder un prefixo a tote le nomines de tabella pro evitar conflictos.
-Non usa spatios.
-
-Iste campo usualmente resta vacue.',
- 'config-db-charset' => 'Codification de characteres in le base de datos',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binari',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 retrocompatibile UTF-8',
- 'config-charset-help' => "'''Aviso:''' Si tu usa '''UTF-8 retrocompatibile''' sur MySQL 4.1+, e postea face un copia de reserva del base de datos con <code>mysqldump</code>, tote le characteres non ASCII pote esser destruite, resultante in corruption irreversibile de tu copias de reserva!
-
-In '''modo binari''', MediaWiki immagazina texto in UTF-8 in le base de datos in campos binari.
-Isto es plus efficiente que le modo UTF-8 de MySQL, e permitte usar le rango complete de characteres de Unicode.
-In '''modo UTF-8''', MySQL sapera in qual codification de characteres tu datos es, e pote presentar e converter lo appropriatemente,
-ma non te permittera immagazinar characteres supra le [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
- 'config-mysql-old' => 'MySQL $1 o plus recente es requirite, tu ha $2.',
- 'config-db-port' => 'Porto de base de datos:',
- 'config-db-schema' => 'Schema pro MediaWiki',
- 'config-db-schema-help' => 'Iste schema es generalmente correcte.
-Solmente cambia lo si tu es secur que es necessari.',
- 'config-pg-test-error' => "Impossibile connecter al base de datos '''$1''': $2",
- 'config-sqlite-dir' => 'Directorio pro le datos de SQLite:',
- 'config-sqlite-dir-help' => "SQLite immagazina tote le datos in un sol file.
-
-Le directorio que tu forni debe permitter le accesso de scriptura al servitor web durante le installation.
-
-Illo '''non''' debe esser accessibile via web. Pro isto, nos non lo pone ubi tu files PHP es.
-
-Le installator scribera un file <code>.htaccess</code> insimul a illo, ma si isto falli, alcuno pote ganiar accesso directe a tu base de datos.
-Isto include le crude datos de usator (adresses de e-mail, contrasignos codificate) assi como versiones delite e altere datos restringite super le wiki.
-
-Considera poner le base de datos in un loco completemente differente, per exemplo in <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Spatio de tabellas predefinite:',
- 'config-oracle-temp-ts' => 'Spatio de tabellas temporari:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki supporta le sequente systemas de base de datos:
-
-$1
-
-Si 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-support-mysql' => '* $1 es le systema primari pro MediaWiki e le melio supportate ([http://www.php.net/manual/en/mysql.installation.php como compilar PHP con supporto de MySQL])',
- 'config-support-postgres' => '* $1 es un systema de base de datos popular e open source, alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar PHP con supporto de PostgreSQL]). Es possibile que resta alcun minor defectos non resolvite, dunque illo non es recommendate pro uso in un ambiente de production.',
- 'config-support-sqlite' => '* $1 es un systema de base de datos legier que es multo ben supportate. ([http://www.php.net/manual/en/pdo.installation.php Como compilar PHP con supporto de SQLite], usa PDO)',
- 'config-support-oracle' => '* $1 es un banca de datos commercial pro interprisas. ([http://www.php.net/manual/en/oci8.installation.php Como compilar PHP con supporto de OCI8])',
- 'config-header-mysql' => 'Configuration de MySQL',
- 'config-header-postgres' => 'Configuration de PostgreSQL',
- 'config-header-sqlite' => 'Configuration de SQLite',
- 'config-header-oracle' => 'Configuration de Oracle',
- 'config-invalid-db-type' => 'Typo de base de datos invalide',
- 'config-missing-db-name' => 'Tu debe entrar un valor pro "Nomine de base de datos"',
- 'config-missing-db-host' => 'Tu debe entrar un valor pro "Host del base de datos"',
- 'config-missing-db-server-oracle' => 'You must enter a value for "TNS del base de datos"',
- 'config-invalid-db-server-oracle' => 'TNS de base de datos "$1" invalide.
-Usa o "TNS Name" o un catena "Easy Connect". ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Methodos de nomenclatura de Oracle])',
- 'config-invalid-db-name' => 'Nomine de base de datos "$1" invalide.
-Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e tractos de union (-).',
- 'config-invalid-db-prefix' => 'Prefixo de base de datos "$1" invalide.
-Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e tractos de union (-).',
- 'config-connection-error' => '$1.
-
-Verifica le servitor, nomine de usator e contrasigno hic infra e reproba.',
- 'config-invalid-schema' => 'Schema invalide pro MediaWiki "$1".
-Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9) e characteres de sublineamento (_).',
- 'config-db-sys-create-oracle' => 'Le installator supporta solmente le uso de un conto SYSDBA pro le creation de un nove conto.',
- 'config-db-sys-user-exists-oracle' => 'Le conto de usator "$1" ja existe. SYSDBA pote solmente esser usate pro le creation de un nove conto!',
- 'config-postgres-old' => 'PostgreSQL $1 o plus recente es requirite, tu ha $2.',
- 'config-sqlite-name-help' => 'Selige un nomine que identifica tu wiki.
-Non usar spatios o tractos de union.
-Isto essera usate pro le nomine del file de datos de SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Impossibile crear le directorio de datos <code><nowiki>$1</nowiki></code>, proque le directorio superjacente <code><nowiki>$2</nowiki></code> non concede le accesso de scriptura al servitor web.
-
-Le installator ha determinate le usator sub que le servitor web es executate.
-Concede le accesso de scriptura in le directorio <code><nowiki>$3</nowiki></code> a iste usator pro continuar.
-In un systema Unix/Linux:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Impossibile crear le directorio de datos <code><nowiki>$1</nowiki></code>, proque le directorio superjacente <code><nowiki>$2</nowiki></code> non concede le accesso de scriptura al servitor web.
-
-Le installator non poteva determinar le usator sub que le servitor web es executate.
-Concede le accesso de scriptura in le directorio <code><nowiki>$3</nowiki></code> a iste usator (e alteres!) pro continuar.
-In un systema Unix/Linux:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Error al creation del directorio de datos "$1".
-Verifica le loco e reproba.',
- 'config-sqlite-dir-unwritable' => 'Impossibile scriber in le directorio "$1".
-Cambia su permissiones de sorta que le servitor web pote scriber in illo, e reproba.',
- 'config-sqlite-connection-error' => '$1.
-
-Verifica le directorio de datos e le nomine de base de datos hic infra e reproba.',
- 'config-sqlite-readonly' => 'Le file <code>$1</code> non es accessibile pro scriptura.',
- 'config-sqlite-cant-create-db' => 'Non poteva crear le file de base de datos <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP non ha supporto pro FTS3. Le tabellas es retrogradate.',
- 'config-can-upgrade' => "Il ha tabellas MediaWiki in iste base de datos.
-Pro actualisar los a MediaWiki $1, clicca super '''Continuar'''.",
- 'config-upgrade-done' => "Actualisation complete.
-
-Tu pote ora [$1 comenciar a usar tu wiki].
-
-Si tu vole regenerar tu file <code>LocalSettings.php</code>, clicca super le button hic infra.
-Isto '''non es recommendate''' si tu non ha problemas con tu wiki.",
- 'config-upgrade-done-no-regenerate' => 'Actualisation complete.
-
-Tu pote ora [$1 comenciar a usar tu wiki].',
- 'config-regenerate' => 'Regenerar LocalSettings.php →',
- 'config-show-table-status' => 'Le consulta <code>SHOW TABLE STATUS</code> falleva!',
- 'config-unknown-collation' => "'''Aviso:''' Le base de datos usa un collation non recognoscite.",
- 'config-db-web-account' => 'Conto de base de datos pro accesso via web',
- 'config-db-web-help' => 'Selige le nomine de usator e contrasigno que le servitor web usara pro connecter al servitor de base de datos, durante le operation ordinari del wiki.',
- 'config-db-web-account-same' => 'Usar le mesme conto que pro le installation',
- 'config-db-web-create' => 'Crear le conto si illo non jam existe',
- 'config-db-web-no-create-privs' => 'Le conto que tu specificava pro installation non ha sufficiente privilegios pro crear un conto.
-Le conto que tu specifica hic debe jam exister.',
- 'config-mysql-engine' => 'Motor de immagazinage:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "* '''Attention:''' Tu ha seligite MyISAM como motor de immagazinage pro MySQL, lo que non es recommendate pro uso con MediaWiki, perque:
-* illo a pena supporta le processamento simultanee a causa del blocada le tabulas
-* illo es plus susceptibile al corruption que altere motores
-* le base de codice de MediaWiki non sempre manea MyISAM como illo deberea
-
-Si tu installation de MySQL supporta InnoDB, es multo recommendate que tu selige iste in su loco.
-Si tu installation de MySQL non supporta InnoDB, forsan isto es un bon occasion pro actualisar lo.",
- 'config-mysql-engine-help' => "'''InnoDB''' es quasi sempre le melior option, post que illo ha bon supporto pro simultaneitate.
-
-'''MyISAM''' pote esser plus rapide in installationes a usator singule o a lectura solmente.
-Le bases de datos MyISAM tende a esser corrumpite plus frequentemente que le base de datos InnoDB.",
- 'config-mysql-charset' => 'Codification de characteres in le base de datos:',
- 'config-mysql-binary' => 'Binari',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "In '''modo binari''', MediaWiki immagazina le texto UTF-8 in le base de datos in campos binari.
-Isto es plus efficiente que le modo UTF-8 de MySQL, e permitte usar le rango complete de characteres Unicode.
-
-In '''modo UTF-8''', MySQL cognoscera le codification de characteres usate pro tu dats, e pote presentar e converter lo appropriatemente, ma illo non permittera immagazinar characteres supra le [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
- 'config-site-name' => 'Nomine del wiki:',
- 'config-site-name-help' => 'Isto apparera in le barra de titulo del navigator e in varie altere locos.',
- 'config-site-name-blank' => 'Entra un nomine de sito.',
- 'config-project-namespace' => 'Spatio de nomines del projecto:',
- 'config-ns-generic' => 'Projecto',
- 'config-ns-site-name' => 'Mesme nomine que le wiki: $1',
- 'config-ns-other' => 'Altere (specifica)',
- 'config-ns-other-default' => 'MiWiki',
- 'config-project-namespace-help' => 'Sequente le exemplo de Wikipedia, multe wikis tene lor paginas de politica separate de lor paginas de contento, in un "\'\'\'spatio de nomines de projecto\'\'\'".
-Tote le titulos de pagina in iste spatio de nomines comencia con un certe prefixo, le qual tu pote specificar hic.
-Traditionalmente, iste prefixo deriva del nomine del wiki, ma illo non pote continer characteres de punctuation como "#" o ":".',
- 'config-ns-invalid' => 'Le spatio de nomines specificate "<nowiki>$1</nowiki>" es invalide.
-Specifica un altere spatio de nomines de projecto.',
- 'config-ns-conflict' => 'Le spatio de nomines specificate "<nowiki>$1</nowiki>" conflige con un spatio de nomines predefinite de MediaWiki.
-Specifica un altere spatio de nomines pro le projecto.',
- 'config-admin-box' => 'Conto de administrator',
- 'config-admin-name' => 'Tu nomine:',
- 'config-admin-password' => 'Contrasigno:',
- 'config-admin-password-confirm' => 'Repete contrasigno:',
- 'config-admin-help' => 'Entra hic tu nomine de usator preferite, per exemplo "Julio Cesare".
-Isto es le nomine que tu usara pro aperir session in le wiki.',
- 'config-admin-name-blank' => 'Entra un nomine de usator pro administrator.',
- 'config-admin-name-invalid' => 'Le nomine de usator specificate "<nowiki>$1</nowiki>" es invalide.
-Specifica un altere nomine de usator.',
- 'config-admin-password-blank' => 'Entra un contrasigno pro le conto de administrator.',
- 'config-admin-password-same' => 'Le contrasigno non pote esser le mesme que le nomine de usator.',
- 'config-admin-password-mismatch' => 'Le duo contrasignos que tu scribeva non es identic.',
- 'config-admin-email' => 'Adresse de e-mail:',
- 'config-admin-email-help' => 'Entra un adresse de e-mail hic pro permitter le reception de e-mail ab altere usatores del wiki, pro poter reinitialisar tu contrasigno, e pro reciper notification de cambios a paginas in tu observatorio. Iste campo pote esser lassate vacue.',
- 'config-admin-error-user' => 'Error interne durante le creation de un administrator con le nomine "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Error interne durante le definition de un contrasigno pro le administrator "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Tu ha entrate un adresse de e-mail invalide',
- 'config-subscribe' => 'Subscribe al [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de diffusion pro annuncios de nove versiones].',
- 'config-subscribe-help' => 'Isto es un lista de e-mail a basse volumine pro annuncios de nove versiones, includente importante annuncios de securitate.
-Tu deberea subscriber a illo e actualisar tu installation de MediaWiki quando nove versiones es editate.',
- 'config-subscribe-noemail' => 'Tu tentava abonar te al lista de diffusion pro annunciamento de nove versiones sin fornir un adresse de e-mail.
-Per favor specifica un adresse de e-mail si tu vole abonar te al lista de diffusion.',
- 'config-almost-done' => 'Tu ha quasi finite!
-Tu pote ora saltar le configuration remanente e installar le wiki immediatemente.',
- 'config-optional-continue' => 'Pone me plus questiones.',
- 'config-optional-skip' => 'Isto me es jam tediose. Simplemente installa le wiki.',
- 'config-profile' => 'Profilo de derectos de usator:',
- 'config-profile-wiki' => 'Wiki aperte',
- 'config-profile-no-anon' => 'Creation de conto obligatori',
- 'config-profile-fishbowl' => 'Modificatores autorisate solmente',
- 'config-profile-private' => 'Wiki private',
- 'config-profile-help' => "Le wikis functiona melio si tu permitte a tante personas como possibile de modificar los.
-In MediaWiki, il es facile revider le modificationes recente, e reverter omne damno facite per usatores naive o malitiose.
-
-Nonobstante, multes ha trovate MediaWiki utile in un grande varietate de rolos, e alcun vices il non es facile convincer omnes del beneficios del principio wiki.
-Dunque, a te le option.
-
-Le modello '''{{int:config-profile-wiki}}''' permitte a omnes de modificar, sin mesmo aperir un session.
-Un wiki con '''{{int:config-profile-no-anon}}''' attribue additional responsabilitate, ma pote dissuader contributores occasional.
-
-Le scenario '''{{int:config-profile-fishbowl}}''' permitte al usatores approbate de modificar, ma le publico pote vider le paginas, includente lor historia.
-Un '''{{int:config-profile-private}}''' permitte solmente al usatores approbate de vider le paginas e de modificar los.
-
-Configurationes de derectos de usator plus complexe es disponibile post installation, vide le [//www.mediawiki.org/wiki/Manual:User_rights pertinente section del manual].",
- 'config-license' => 'Copyright e licentia:',
- 'config-license-none' => 'Nulle licentia in pede de paginas',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (dominio public)',
- 'config-license-gfdl' => 'Licentia GNU pro Documentation Libere 1.3 o plus recente',
- 'config-license-pd' => 'Dominio public',
- 'config-license-cc-choose' => 'Seliger un licentia Creative Commons personalisate',
- 'config-license-help' => "Multe wikis public pone tote le contributiones sub un [http://freedomdefined.org/Definition/Ia?uselang=ia licentia libere].
-Isto adjuta a crear un senso de proprietate communitari e incoragia le contribution in longe termino.
-Isto non es generalmente necessari pro un wiki private o de interprisa.
-
-Si tu vole poter usar texto de Wikipedia, e si tu vole que Wikipedia pote acceptar texto copiate de tu wiki, tu debe seliger '''Creative Commons Attribution Share Alike'''.
-
-Wikipedia usava anteriormente le Licentia GNU pro Documentation Libere (GFDL).
-Iste es un licentia valide, ma es difficile a comprender.
-Il es anque difficile reusar le contento licentiate sub GFDL.",
- 'config-email-settings' => 'Configuration de e-mail',
- 'config-enable-email' => 'Activar le e-mail sortiente',
- 'config-enable-email-help' => 'Si tu vole que e-mail functiona, [http://www.php.net/manual/en/mail.configuration.php le optiones de e-mail de PHP] debe esser configurate correctemente.
-Si tu non vole functiones de e-mail, tu pote disactivar los hic.',
- 'config-email-user' => 'Activar le e-mail de usator a usator',
- 'config-email-user-help' => 'Permitter a tote le usatores de inviar e-mail inter se, si illes lo ha activate in lor preferentias.',
- 'config-email-usertalk' => 'Activar notification de cambios in paginas de discussion de usatores',
- 'config-email-usertalk-help' => 'Permitter al usatores de reciper notification de modificationes in lor paginas de discussion personal, si illes lo ha activate in lor preferentias.',
- 'config-email-watchlist' => 'Activar notification de observatorio',
- 'config-email-watchlist-help' => 'Permitter al usatores de reciper notification super lor paginas sub observation, si illes lo ha activate in lor preferentias.',
- 'config-email-auth' => 'Activar authentication de e-mail',
- 'config-email-auth-help' => "Si iste option es activate, le usatores debe confirmar lor adresse de e-mail usante un ligamine inviate a illes, quandocunque illes lo defini o cambia.
-Solmente le adresses de e-mail authenticate pote reciper e-mail de altere usatores o alterar le e-mails de notification.
-Es '''recommendate''' activar iste option pro wikis public a causa de abuso potential del functionalitate de e-mail.",
- 'config-email-sender' => 'Adresse de e-mail de retorno:',
- 'config-email-sender-help' => 'Entra le adresse de e-mail a usar como adresse de retorno in e-mail sortiente.
-Hic es recipite le notificationes de non-livration.
-Multe servitores de e-mail require que al minus le parte de nomine de dominio sia valide.',
- 'config-upload-settings' => 'Incargamento de imagines e files',
- 'config-upload-enable' => 'Activar le incargamento de files',
- 'config-upload-help' => 'Le incargamento de files potentialmente expone tu servitor a riscos de securitate.
-Pro plus information, lege le [//www.mediawiki.org/wiki/Manual:Security section de securitate] in le manual.
-
-Pro activar le incargamento de files, cambia le modo in le subdirectorio <code>images</code> sub le directorio-radice de MediaWiki de sorta que le servitor web pote scriber in illo.
-Postea activa iste option.',
- 'config-upload-deleted' => 'Directorio pro files delite:',
- 'config-upload-deleted-help' => 'Selige un directorio in le qual archivar le files delite.
-Idealmente, isto non debe esser accessibile ab le web.',
- 'config-logo' => 'URL del logotypo:',
- 'config-logo-help' => 'Le apparentia predefinite de MediaWiki include spatio pro un logotypo de 135×160 pixels supra le menu del barra lateral.
-Incarga un imagine con le dimensiones appropriate, e entra le URL hic.
-
-Tu pote usar <code>$wgStylePath</code> o <code>$wgScriptPath</code> si le loco de tu logotypo es relative a iste camminos.
-
-Si tu non vole un logotypo, lassa iste quadro vacue.',
- 'config-instantcommons' => 'Activar "Instant Commons"',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] es un function que permitte a wikis de usar imagines, sonos e altere multimedia trovate in le sito [//commons.wikimedia.org/ Wikimedia Commons].
-Pro poter facer isto, MediaWiki require accesso a Internet.
-
-Pro plus information super iste function, includente instructiones super como configurar lo pro wikis altere que Wikimedia Commons, consulta [//mediawiki.org/wiki/Manual:$wgForeignFileRepos le manual].',
- 'config-cc-error' => 'Le selector de licentia Creative Commons non dava un resultato.
-Entra le nomine del licentia manualmente.',
- 'config-cc-again' => 'Selige de novo…',
- 'config-cc-not-chosen' => 'Selige le licentia Creative Commons que tu prefere e clicca "proceder".',
- 'config-advanced-settings' => 'Configuration avantiate',
- 'config-cache-options' => 'Configuration del cache de objectos:',
- 'config-cache-help' => 'Le cache de objectos es usate pro meliorar le rapiditate de MediaWiki per immagazinar le datos frequentemente usate.
-Le sitos medie o grande es multo incoragiate de activar isto, ma anque le sitos parve percipera le beneficios.',
- 'config-cache-none' => 'Nulle cache (nulle functionalitate es removite, ma le rapiditate pote diminuer in grande sitos wiki)',
- 'config-cache-accel' => 'Cache de objectos de PHP (APC, XCache o WinCache)',
- 'config-cache-memcached' => 'Usar Memcached (require additional installation e configuration)',
- 'config-memcached-servers' => 'Servitores Memcached:',
- 'config-memcached-help' => 'Lista de adresses IP a usar pro Memcached.
-Debe specificar un per linea e specificar le porto a usar. Per exemplo:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Tu seligeva Memcached como typo de cache ma non specificava alcun servitores',
- 'config-memcache-badip' => 'Tu ha entrate un adresse IP invalide pro Memcached: $1',
- 'config-memcache-noport' => 'Tu non specificava un porto a usar pro le servitor Memcached: $1.
-Si tu non cognosce le porto, le standard es 11211',
- 'config-memcache-badport' => 'Le numeros de porto de Memcached debe esser inter $1 e $2',
- 'config-extensions' => 'Extensiones',
- 'config-extensions-help' => 'Le extensiones listate hic supra esseva detegite in tu directorio <code>./extensions</code>.
-
-Istes pote requirer additional configuration, ma tu pote activar los ora.',
- 'config-install-alreadydone' => "'''Aviso:''' Il pare que tu ha jam installate MediaWiki e tenta installar lo de novo.
-Per favor continua al proxime pagina.",
- 'config-install-begin' => 'Un clic sur "{{int:config-continue}}" comencia le installation de MediaWiki.
-Pro facer alterationes, clicca sur "{{int:config-back}}".',
- 'config-install-step-done' => 'finite',
- 'config-install-step-failed' => 'fallite',
- 'config-install-extensions' => 'Include le extensiones',
- 'config-install-database' => 'Configura le base de datos',
- 'config-install-schema' => 'Creation de schema',
- 'config-install-pg-schema-not-exist' => 'Iste schema de PostgreSQL non existe',
- 'config-install-pg-schema-failed' => 'Le creation del tabellas falleva.
-Assecura te que le usator "$1" pote scriber in le schema "$2".',
- 'config-install-pg-commit' => 'Committer cambiamentos',
- 'config-install-pg-plpgsql' => 'Verifica le presentia del linguage PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Es necessari installar le linguage PL/pgSQL in le base de datos $1',
- 'config-pg-no-create-privs' => 'Le conto que tu specificava pro installation non ha sufficiente privilegios pro crear un conto.',
- 'config-pg-not-in-role' => 'Le conto que tu specificava pro le usator web ja existe.
-Le conto que tu specificava pro installation non es superusator e non es membro del rolo de usator web, dunque es incapace de crear objectos possedite per le usator web.
-
-MediaWiki require actualmente que le tabellas sia possedite per le usator web. Per favor specifica un altere nomine de conto web, o clicca super "retornar" e specifica un usator de installation con sufficiente privilegios.',
- 'config-install-user' => 'Crea usator pro base de datos',
- 'config-install-user-alreadyexists' => 'Le usator "$1" ja existe',
- 'config-install-user-create-failed' => 'Le creation del usator "$1" ha fallite: $2',
- 'config-install-user-grant-failed' => 'Le concession de permission al usator "$1" falleva: $2',
- 'config-install-user-missing' => 'Le usator specificate, "$1", non existe.',
- 'config-install-user-missing-create' => 'Le usator specificate, "$1", non existe.
-Per favor marca le quadrato "crear conto" hic infra si tu vole crear lo.',
- 'config-install-tables' => 'Crea tabellas',
- 'config-install-tables-exist' => "'''Aviso''': Il pare que le tabellas de MediaWiki jam existe.
-Le creation es saltate.",
- 'config-install-tables-failed' => "'''Error''': Le creation del tabellas falleva con le sequente error: $1",
- 'config-install-interwiki' => 'Plena le tabella interwiki predefinite',
- 'config-install-interwiki-list' => 'Non poteva trovar le file <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Aviso''': Le tabella interwiki pare jam haber entratas.
-Le lista predefinite es saltate.",
- 'config-install-stats' => 'Initialisation del statisticas',
- 'config-install-keys' => 'Generation de claves secrete',
- 'config-insecure-keys' => "'''Attention:''' {{PLURAL:$2|Un clave|Alcun claves}} secur ($1) generate durante le installation non es completemente secur. Considera cambiar {{PLURAL:$2|lo|los}} manualmente.",
- 'config-install-sysop' => 'Crea conto de usator pro administrator',
- 'config-install-subscribe-fail' => 'Impossibile subscriber a mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL non es installate e allow_url_fopen non es disponibile.',
- 'config-install-mainpage' => 'Crea pagina principal con contento predefinite',
- 'config-install-extension-tables' => 'Creation de tabellas pro le extensiones activate',
- 'config-install-mainpage-failed' => 'Non poteva inserer le pagina principal: $1',
- 'config-install-done' => "'''Felicitationes!'''
-Tu ha installate MediaWiki con successo.
-
-Le installator ha generate un file <code>LocalSettings.php</code>.
-Iste contine tote le configuration.
-
-Es necessari discargar lo e poner lo in le base del installation wiki (le mesme directorio que index.php).
-Le discargamento debe haber comenciate automaticamente.
-
-Si le discargamento non ha comenciate, o si illo esseva cancellate, es possibile recomenciar le discargamento con un clic sur le ligamine sequente:
-
-$3
-
-'''Nota''': Si tu non discarga iste file de configuration ora, illo non essera disponibile plus tarde.
-
-Post facer isto, tu pote '''[$2 entrar in tu wiki]'''.",
- 'config-download-localsettings' => 'Discargar <code>LocalSettings.php</code>',
- 'config-help' => 'adjuta',
- 'config-nofile' => 'Le file "$1" non poteva esser trovate. Ha illo essite delite?',
- 'mainpagetext' => "'''MediaWiki ha essite installate con successo.'''",
- 'mainpagedocfooter' => 'Consulta le [//meta.wikimedia.org/wiki/Help:Contents Guida del usator] pro informationes super le uso del software wiki.
-
-== Pro initiar ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de configurationes]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ a proposito de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de diffusion pro annuncios de nove versiones de MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Traducer MediaWiki in tu lingua]',
-);
-
-/** Indonesian (Bahasa Indonesia)
- * @author Farras
- * @author IvanLanin
- * @author Kenrick95
- * @author Reedy
- * @author 아라
- */
-$messages['id'] = array(
- 'config-desc' => 'Penginstal untuk MediaWiki',
- 'config-title' => 'Instalasi MediaWiki $1',
- 'config-information' => 'Informasi',
- 'config-localsettings-upgrade' => 'Berkas <code>LocalSettings.php</code> sudah ada.
-Untuk memutakhirkan instalasi ini, masukkan nilai <code>$wgUpgradeKey</code> dalam kotak yang tersedia di bawah ini.
-Anda dapat menemukan nilai tersebut dalam <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Berkas <code>LocalSettings.php</code> terdeteksi.
-Untuk meningkatkan versi, harap jalankan <code>update.php</code>.',
- 'config-localsettings-key' => 'Kunci pemutakhiran:',
- 'config-localsettings-badkey' => 'Kunci yang Anda berikan tidak benar',
- 'config-upgrade-key-missing' => 'Suatu instalasi MediaWiki telah terdeteksi.
-Untuk memutakhirkan instalasi ini, silakan masukkan baris berikut di bagian bawah <code>LocalSettings.php</code> Anda:
-
-$1',
- 'config-localsettings-incomplete' => '<code>LocalSettings.php</code> yang ada tampaknya tidak lengkap.
-Variabel $1 tidak diatur.
-Silakan ubah <code>LocalSettings.php</code> untuk mengatur variabel ini dan klik "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Timbul galat saat menghubungkan ke basis data dengan menggunakan setelan yang ditentukan di <code>LocalSettings.php</code> atau <code>AdminSettings.php</code>. Harap perbaiki setelan ini dan coba lagi.
-
-$1',
- 'config-session-error' => 'Kesalahan sesi mulai: $1',
- 'config-session-expired' => 'Data sesi tampaknya telah kedaluwarsa.
-Sesi dikonfigurasi untuk berlaku selama $1.
-Anda dapat menaikkannya dengan menetapkan <code>session.gc_maxlifetime</code> dalam php.ini.
-Ulangi proses instalasi.',
- 'config-no-session' => 'Data sesi Anda hilang!
-Cek php.ini Anda dan pastikan bahwa <code>session.save_path</code> diatur ke direktori yang sesuai.',
- 'config-your-language' => 'Bahasa Anda:',
- 'config-your-language-help' => 'Pilih bahasa yang akan digunakan selama proses instalasi.',
- 'config-wiki-language' => 'Bahasa wiki:',
- 'config-wiki-language-help' => 'Pilih bahasa yang akan digunakan tulisan-tulisan wiki.',
- 'config-back' => '← Kembali',
- 'config-continue' => 'Lanjut →',
- 'config-page-language' => 'Bahasa',
- 'config-page-welcome' => 'Selamat datang di MediaWiki',
- 'config-page-dbconnect' => 'Hubungkan ke basis data',
- 'config-page-upgrade' => 'Perbarui instalasi yang ada',
- 'config-page-dbsettings' => 'Pengaturan basis data',
- 'config-page-name' => 'Nama',
- 'config-page-options' => 'Pilihan',
- 'config-page-install' => 'Instal',
- 'config-page-complete' => 'Selesai!',
- 'config-page-restart' => 'Ulangi instalasi',
- 'config-page-readme' => 'Baca saya',
- 'config-page-releasenotes' => 'Catatan pelepasan',
- 'config-page-copying' => 'Menyalin',
- 'config-page-upgradedoc' => 'Memerbarui',
- 'config-page-existingwiki' => 'Wiki yang ada',
- 'config-help-restart' => 'Apakah Anda ingin menghapus semua data tersimpan yang telah Anda masukkan dan mengulang proses instalasi?',
- 'config-restart' => 'Ya, nyalakan ulang',
- 'config-welcome' => '=== Pengecekan lingkungan ===
-Pengecekan dasar dilakukan untuk melihat apakah lingkungan ini memadai untuk instalasi MediaWiki.
-Anda harus memberikan hasil pemeriksaan ini jika Anda memerlukan bantuan selama instalasi.',
- 'config-copyright' => "=== Hak cipta dan persyaratan ===
-
-\$1
-
-Program ini adalah perangkat lunak bebas; Anda dapat mendistribusikan dan/atau memodifikasi di bawah persyaratan GNU General Public License seperti yang diterbitkan oleh Free Software Foundation; baik versi 2 lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.
-
-Program ini didistribusikan dengan harapan bahwa itu akan berguna, tetapi '''tanpa jaminan apa pun'''; bahkan tanpa jaminan tersirat untuk '''dapat diperjualbelikan ''' atau '''sesuai untuk tujuan tertentu'''.
-Lihat GNU General Public License untuk lebih jelasnya.
-
-Anda seharusnya telah menerima <doclink href=\"Copying\">salinan dari GNU General Public License</doclink> bersama dengan program ini; jika tidak, kirimkan surat untuk Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. atau [http://www.gnu.org/copyleft/gpl.html baca versi daring].",
- 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/id Situs MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents/id Pedoman Pengguna]
-* [//www.mediawiki.org/wiki/Manual:Contents/id Pedoman Administrator]
-* [//www.mediawiki.org/wiki/Manual:FAQ/id FAQ]
-----
-* <doclink href=Readme>Read me</doclink>
-* <doclink href=ReleaseNotes>Release notes</doclink>
-* <doclink href=Copying>Copying</doclink>
-* <doclink href=UpgradeDoc>Upgrading</doclink>',
- 'config-env-good' => 'Kondisi telah diperiksa.
-Anda dapat menginstal MediaWiki.',
- 'config-env-bad' => 'Kondisi telah diperiksa.
-Anda tidak dapat menginstal MediaWiki.',
- 'config-env-php' => 'PHP $1 diinstal.',
- 'config-env-php-toolow' => 'PHP $1 telah terinstal.
-Namun, MediaWiki memerlukan PHP $2 atau lebih tinggi.',
- 'config-unicode-using-utf8' => 'Menggunakan utf8_normalize.so Brion Vibber untuk normalisasi Unicode.',
- 'config-unicode-using-intl' => 'Menggunakan [http://pecl.php.net/intl ekstensi PECL intl] untuk normalisasi Unicode.',
- 'config-unicode-pure-php-warning' => "'''Peringatan''': [http://pecl.php.net/intl Ekstensi intl PECL] untuk menangani normalisasi Unicode tidak tersedia, kembali menggunakan implementasi murni PHP yang lambat.
-Jika Anda menjalankan situs berlalu lintas tinggi, Anda harus sedikit membaca [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalisasi Unicode].",
- 'config-unicode-update-warning' => "'''Peringatan''': Versi terinstal dari pembungkus normalisasi Unicode menggunakan versi lama pustaka [http://site.icu-project.org/ proyek ICU].
-Anda harus [//www.mediawiki.org/wiki/Unicode_normalization_considerations memutakhirkannya] jika Anda ingin menggunakan Unicode.",
- 'config-no-db' => 'Pengandar basis data yang sesuai tidak ditemukan! Anda perlu menginstal pengandar basis data untuk PHP.
-Jenis basis data yang didukung: $1.
-
-Jika Anda menggunakan inang bersama, mintalah penyedia inang Anda untuk menginstal pengandar basis data yang sesuai.
-Jika Anda mengompilasi sendiri PHP, ubahlah konfigurasinya dengan mengaktifkan klien basis data, misalnya menggunakan <code>./configure --with-mysql</code>.
-Jika Anda menginstal PHP dari paket Debian atau Ubuntu, maka Anda juga perlu menginstal modul php5-mysql.',
- 'config-no-fts3' => "'''Peringatan''': SQLite dikompilasi tanpa [//sqlite.org/fts3.html modul FTS3], fitur pencarian tidak akan tersedia pada konfigurasi ini.",
- 'config-register-globals' => "'''Peringatan: Opsi <code>[http://php.net/register_globals register_globals]</code> PHP diaktifkan.'''
-'''Nonaktifkan kalau bisa.'''
-MediaWiki akan bekerja, tetapi server Anda memiliki potensi kerentanan keamanan.",
- 'config-magic-quotes-runtime' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] aktif!'''
-Pilihan ini dapat merusak masukan data secara tidak terduga.
-Anda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
- 'config-magic-quotes-sybase' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic_quotes_sybase magic_quotes_sybase] aktif!'''
-Pilihan ini dapat merusak masukan data secara tidak terduga.
-Anda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
- 'config-mbstring' => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] aktif!'' '
-Pilihan ini dapat menyebabkan kesalahan dan kerusakan data yang tidak terduga.
-Anda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
- 'config-ze1' => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] aktif!'''
-Pilihan ini dapat menyebabkan bug yang mengerikan pada MediaWiki.
-Anda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
- 'config-safe-mode' => "''' Peringatan:''' [http://www.php.net/features.safe-mode Mode aman] PHP aktif.
-Hal ini akan menyebabkan masalah, terutama jika menggunakan pengunggahan berkas dan dukungan <code>math</code>.",
- 'config-xml-bad' => 'Modul XML PHP hilang.
-MediaWiki membutuhkan fungsi dalam modul ini dan tidak akan bekerja dalam konfigurasi ini.
-Jika Anda menggunakan Mandrake, instal paket php-xml.',
- 'config-pcre' => 'Modul pendukung PCRE tampaknya hilang.
-MediaWiki memerlukan fungsi persamaan reguler kompatibel Perl untuk bekerja.',
- 'config-pcre-no-utf8' => "'''Fatal''': Modul PCRE PHP tampaknya dikompilasi tanpa dukungan PCRE_UTF8.
-MediaWiki 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.
-Ini terlalu rendah.
-Instalasi terancam gagal!",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] telah diinstal',
- 'config-apc' => '[http://www.php.net/apc APC] telah diinstal',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] telah diinstal',
- 'config-no-cache' => "'''Peringatan:''' Tidak dapat menemukan [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], atau [http://www.iis.net/download/WinCacheForPhp WinCache]. Pinggahan obyek tidak dinonaktifkan.",
- 'config-diff3-bad' => 'GNU diff3 tidak ditemukan.',
- 'config-imagemagick' => 'ImageMagick ditemukan: <code>$1</code> .
-Pembuatan gambar mini akan diaktifkan jika Anda mengaktifkan pengunggahan.',
- 'config-gd' => 'Pustaka grafis GD terpasang ditemukan.
-Pembuatan gambar mini akan diaktifkan jika Anda mengaktifkan pengunggahan.',
- 'config-no-scaling' => 'Pustaka GD atau ImageMagick tidak ditemukan.
-Pembuatan gambar mini dinonaktifkan.',
- 'config-no-uri' => "'''Kesalahan:''' URI saat ini tidak dapat ditentukan.
-Instalasi dibatalkan.",
- 'config-uploads-not-safe' => "'''Peringatan:''' Direktori bawaan pengunggahan <code>$1</code> Anda rentan terhadap eksekusi skrip yang sewenang-wenang.
-Meskipun MediaWiki memeriksa semua berkas unggahan untuk ancaman keamanan, sangat dianjurkan untuk [//www.mediawiki.org/wiki/Manual:Security#Upload_security menutup kerentanan keamanan ini] sebelum mengaktifkan pengunggahan.",
- 'config-brokenlibxml' => 'Sistem Anda memiliki kombinasi versi PHP dan libxml2 yang memiliki bug dan dapat menyebabkan kerusakan data tersembunyi pada MediaWiki dan aplikasi web lain.
-Mutakhirkan ke PHP 5.2.9 atau yang lebih baru dan libxml2 2.7.3 atau yang lebih baru ([//bugs.php.net/bug.php?id=45996 arsip bug di PHP]).
-Instalasi dibatalkan.',
- 'config-using531' => 'MediaWiki tidak dapat dijalankan dengan PHP $1 karena bug yang melibatkan parameter referensi untuk <code>__call()</code> .
-Tingkatkan ke PHP 5.3.2 atau lebih baru, atau turunkan ke PHP versi 5.3.0 untuk menyelesaikan hal ini.
-Instalasi dibatalkan.',
- 'config-suhosin-max-value-length' => 'Suhosin terpasang dan membatasi panjang parameter GET sebesar $1 bita. Komponen ResourceLoader MediaWiki akan mengatasi batasan ini, tapi penanganannya akan menurunkan kinerja. Jika memungkinkan, Anda sebaiknya menetapkan nilai <code>suhosin.get.max_value_length</code> menjadi 1024 atau lebih tinggi dalam <code>php.ini</code> dan menyetel <code>$wgResourceLoaderMaxQueryLength</code> dengan nilai yang sama dalam LocalSettings.php.', # Fuzzy
- 'config-db-type' => 'Jenis basis data:',
- 'config-db-host' => 'Inang basis data:',
- 'config-db-host-help' => 'Jika server basis data Anda berada di server yang berbeda, masukkan nama inang atau alamat IP di sini.
-
-Jika Anda menggunakan inang web bersama, penyedia inang Anda harus memberikan nama inang yang benar di dokumentasi mereka.
-
-Jika Anda menginstal pada server Windows dan menggunakan MySQL, "localhost" mungkin tidak dapat digunakan sebagai nama server. Jika demikian, coba "127.0.0.1" untuk alamat IP lokal.', # Fuzzy
- 'config-db-host-oracle' => 'TNS basis data:',
- 'config-db-host-oracle-help' => 'Masukkan [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] yang sah; berkas tnsnames.ora harus dapat diakses oleh instalasi ini.<br />Jika Anda menggunakan pustaka klien 10g atau lebih baru, Anda juga dapat menggunakan metode penamaan [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identifikasi wiki ini',
- 'config-db-name' => 'Nama basis data:',
- 'config-db-name-help' => 'Pilih nama yang mengidentifikasikan wiki Anda.
-Nama tersebut tidak boleh mengandung spasi.
-
-Jika Anda menggunakan inang web bersama, penyedia inang Anda dapat memberikan Anda nama basis data khusus untuk digunakan atau mengizinkan Anda membuat basis data melalui panel kontrol.',
- 'config-db-name-oracle' => 'Skema basis data:',
- 'config-db-account-oracle-warn' => 'Ada tiga skenario yang didukung untuk instalasi Oracle sebagai basis data pendukung:
-
-Jika Anda ingin membuat akun basis data sebagai bagian dari proses instalasi, silakan masukkan akun dengan peran SYSDBA sebagai akun basis data untuk instalasi dan tentukan kredensial yang diinginkan untuk akun akses web. Jika tidak, Anda dapat membuat akun akses web secara manual dan hanya memberikan akun tersebut (jika memiliki izin yang diperlukan untuk membuat objek skema) atau memasukkan dua akun yang berbeda, satu dengan hak membuat objek dan satu dibatasi untuk akses web.
-
-Skrip untuk membuat akun dengan privilese yang diperlukan dapat ditemukan pada direktori "maintenance/oracle/" instalasi ini. Harap diingat bahwa penggunaan akun terbatas akan menonaktifkan semua kemampuan pemeliharaan dengan akun bawaan.',
- 'config-db-install-account' => 'Akun pengguna untuk instalasi',
- 'config-db-username' => 'Nama pengguna basis data:',
- 'config-db-password' => 'Kata sandi basis data:',
- 'config-db-password-empty' => 'Silakan masukkan sandi untuk pengguna basis data baru: $1.
-Meskipun dimungkinkan untuk membuat pengguna tanpa sandi, hal itu tidak aman.',
- 'config-db-install-username' => 'Masukkan nama pengguna yang akan digunakan untuk terhubung ke basis data selama proses instalasi.
-Ini bukan nama pengguna akun MediaWiki, melainkan nama pengguna untuk basis data Anda.',
- 'config-db-install-password' => 'Masukkan sandi yang akan digunakan untuk terhubung ke basis data selama proses instalasi.
-Ini bukan sandi untuk akun MediaWiki, melainkan sandi untuk basis data Anda.',
- 'config-db-install-help' => 'Masukkan nama pengguna dan sandi yang akan digunakan untuk terhubung ke basis data pada saat proses instalasi.',
- 'config-db-account-lock' => 'Gunakan nama pengguna dan kata sandi yang sama selama operasi normal',
- 'config-db-wiki-account' => 'Akun pengguna untuk operasi normal',
- 'config-db-wiki-help' => 'Masukkan nama pengguna dan sandi yang akan digunakan untuk terhubung ke basis data wiki selama operasi normal.
-Jika akun tidak ada, akun instalasi memiliki hak yang memadai, akun pengguna ini akan dibuat dengan hak akses minimum yang diperlukan untuk mengoperasikan wiki.',
- 'config-db-prefix' => 'Prefiks tabel basis data:',
- 'config-db-prefix-help' => 'Jika Anda perlu berbagi satu basis data di antara beberapa wiki, atau antara MediaWiki dan aplikasi web lain, Anda dapat memilih untuk menambahkan prefiks terhadap semua nama tabel demi menghindari konflik.
-Jangan gunakan spasi.
-
-Prefiks ini biasanya dibiarkan kosong.',
- 'config-db-charset' => 'Set karakter basis data',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 biner',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'UTF-8 yang kompatibel balik dengan MySQL 4.0',
- 'config-charset-help' => "'''Peringatan:''' Jika Anda menggunakan '''UTF-8 kompatibel balik''' pada MySQL 4.1+, dan kemudian mencadangkan basis data dengan <code>mysqldump</code>, proses itu mungkin menghancurkan semua karakter non-ASCII dan merusak cadangan Anda tanpa dapat dikembalikan!
-
-Dalam '''modus biner''', MediaWiki menyimpan teks UTF-8 ke basis data dalam bidang biner.
-Ini lebih efisien dibandingkan modus UTF-8 MySQL dan memungkinkan Anda untuk menggunakan berbagai karakter Unicode.
-Dalam '''modus UTF-8''', MySQL akan tahu apa set karakter data anda dan dapat menyajikan dan mengubahnya denga tepat, namun tidak akan mengizinkan Anda menyimpan karakter di atas [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-mysql-old' => 'MySQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.',
- 'config-db-port' => 'Porta basis data:',
- 'config-db-schema' => 'Skema untuk MediaWiki',
- 'config-db-schema-help' => 'Skema ini biasanya berjalan baik.
-Ubah hanya jika Anda tahu Anda perlu mengubahnya.',
- 'config-sqlite-dir' => 'Direktori data SQLite:',
- 'config-sqlite-dir-help' => "SQLite menyimpan semua data dalam satu berkas.
-
-Direktori yang Anda berikan harus dapat ditulisi oleh server web selama instalasi.
-
-Direktori itu '''tidak''' boleh dapat diakses melalui web, inilah sebabnya kami tidak menempatkannya bersama dengan berkas PHP lain.
-
-Penginstal akan membuat berkas <code>.htaccess</code> bersamaan dengan itu, tetapi jika gagal, orang dapat memperoleh akses ke basis data mentah Anda.
-Itu termasuk data mentah pengguna (alamat surel, hash sandi) serta revisi yang dihapus dan data lainnya yang dibatasi pada wiki.
-
-Pertimbangkan untuk menempatkan basis data di tempat lain, misalnya di <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Tablespace bawaan:',
- 'config-oracle-temp-ts' => 'Tablespace sementara:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki mendukung sistem basis data berikut:
-
-$1
-
-Jika Anda tidak melihat sistem basis data yang Anda gunakan tercantum di bawah ini, ikuti petunjuk terkait di atas untuk mengaktifkan dukungan.',
- 'config-support-mysql' => '* $1 adalah target utama MediaWiki dan memiliki dukungan terbaik ([http://www.php.net/manual/en/mysql.installation.php cara mengompilasi PHP dengan dukungan MySQL])',
- 'config-support-postgres' => '* $1 adalah sistem basis data sumber terbuka populer sebagai alternatif untuk MySQL ([http://www.php.net/manual/en/pgsql.installation.php cara mengompilasi PHP dengan dukungan PostgreSQL]). Mungkin ada beberapa bug terbuka dan alternatif ini tidak direkomendasikan untuk dipakai dalam lingkungan produksi.',
- 'config-support-sqlite' => '* $1 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-support-oracle' => '* $1 adalah basis data komersial untuka perusahaan. ([http://www.php.net/manual/en/oci8.installation.php cara mengompilasi PHP dengan dukungan OCI8])',
- 'config-header-mysql' => 'Pengaturan MySQL',
- 'config-header-postgres' => 'Pengaturan PostgreSQL',
- 'config-header-sqlite' => 'Pengaturan SQLite',
- 'config-header-oracle' => 'Pengaturan Oracle',
- 'config-invalid-db-type' => 'Jenis basis data tidak sah',
- 'config-missing-db-name' => 'Anda harus memasukkan nilai untuk "Nama basis data"',
- 'config-missing-db-host' => 'Anda harus memasukkan nilai untuk "Inang basis data"',
- 'config-missing-db-server-oracle' => 'Anda harus memasukkan nilai untuk "TNS basis data"',
- 'config-invalid-db-server-oracle' => 'TNS basis data "$1" tidak sah.
-Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan titik (.).',
- 'config-invalid-db-name' => 'Nama basis data "$1" tidak sah.
-Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).',
- 'config-invalid-db-prefix' => 'Prefiks basis data "$1" tidak sah.
-Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).',
- 'config-connection-error' => '$1.
-
-Periksa nama inang, pengguna, dan sandi di bawah ini dan coba lagi.',
- 'config-invalid-schema' => 'Skema MediaWiki "$1" tidak sah.
-Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), dan garis bawah (_).',
- 'config-db-sys-create-oracle' => 'Penginstal hanya mendukung penggunaan akun SYSDBA untuk membuat akun baru.',
- 'config-db-sys-user-exists-oracle' => 'Akun pengguna "$1"sudah ada. SYSDBA hanya dapat digunakan untuk membuat akun baru!',
- 'config-postgres-old' => 'PostgreSQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.',
- 'config-sqlite-name-help' => 'Pilih nama yang mengidentifikasi wiki Anda.
-Jangan gunakan spasi atau tanda hubung.
-Nama ini akan digunakan untuk nama berkas data SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Tidak dapat membuat direktori data <code><nowiki>$1</nowiki></code>, karena direktori induk <code><nowiki>$2</nowiki></code> tidak bisa ditulisi oleh server web.
-
-Penginstal telah menentukan pengguna yang menjalankan server web Anda.
-Buat direktori <code><nowiki>$3</nowiki></code> menjadi dapat ditulisi olehnya.
-Pada sistem Unix/Linux lakukan hal berikut:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Tidak dapat membuat direktori data <code><nowiki>$1</nowiki></code>, karena direktori induk <code><nowiki>$2</nowiki></code> tidak bisa ditulisi oleh server web.
-
-Penginstal tidak dapat menentukan pengguna yang menjalankan server web Anda.
-Buat direktori <code><nowiki>$3</nowiki></code> menjadi dapat ditulisi oleh semua orang.
-Pada sistem Unix/Linux lakukan hal berikut:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Kesalahan saat membuat direktori data "$1".
-Periksa lokasi dan coba lagi.',
- 'config-sqlite-dir-unwritable' => 'Tidak dapat menulisi direktori "$1".
-Ubah hak akses direktori sehingga server web dapat menulis ke sana, dan coba lagi.',
- 'config-sqlite-connection-error' => '$1.
-
-Periksa direktori data dan nama basis data di bawah dan coba lagi.',
- 'config-sqlite-readonly' => 'Berkas <code>$1</code> tidak dapat ditulisi.',
- 'config-sqlite-cant-create-db' => 'Tidak dapat membuat berkas basis data <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP tidak memiliki dukungan FTS3, tabel dituruntarafkan.',
- 'config-can-upgrade' => "Ada tabel MediaWiki di basis dataini.
-Untuk memperbaruinya ke MediaWiki $1, klik '''Lanjut'''.",
- 'config-upgrade-done' => "Pemutakhiran selesai.
-
-Anda sekarang dapat [$1 mulai menggunakan wiki Anda].
-
-Jika Anda ingin membuat ulang berkas <code>LocalSettings.php</code>, klik tombol di bawah ini.
-Tindakan ini '''tidak dianjurkan''' kecuali jika Anda mengalami masalah dengan wiki Anda.",
- 'config-upgrade-done-no-regenerate' => 'Pemutakhiran selesai.
-
-Anda sekarang dapat [$1 mulai menggunakan wiki Anda].',
- 'config-regenerate' => 'Regenerasi LocalSettings.php →',
- 'config-show-table-status' => 'Kueri <code>SHOW TABLE STATUS</code> gagal!',
- 'config-unknown-collation' => "'''Peringatan:''' basis data menggunakan kolasi yang tidak dikenal.",
- 'config-db-web-account' => 'Akun basis data untuk akses web',
- 'config-db-web-help' => 'Masukkan nama pengguna dan sandi yang akan digunakan server web untuk terhubung ke server basis data saat operasi normal wiki.',
- 'config-db-web-account-same' => 'Gunakan akun yang sama seperti untuk instalasi',
- 'config-db-web-create' => 'Buat akun jika belum ada',
- 'config-db-web-no-create-privs' => 'Akun Anda berikan untuk instalasi tidak memiliki hak yang cukup untuk membuat akun.
-Akun yang Anda berikan harus sudah ada.',
- 'config-mysql-engine' => 'Mesin penyimpanan:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-engine-help' => "'''InnoDB''' hampir selalu merupakan pilihan terbaik karena memiliki dukungan konkurensi yang baik.
-
-'''MyISAM''' mungkin lebih cepat dalam instalasi pengguna-tunggal atau hanya-baca.
-Basis data MyISAM cenderung lebih sering rusak daripada basis data InnoDB.",
- 'config-mysql-charset' => 'Set karakter basis data:',
- 'config-mysql-binary' => 'Biner',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "Dalam '''modus biner''', MediaWiki menyimpan teks UTF-8 untuk basis data dalam bidang biner.
-Ini lebih efisien daripada modus UTF-8 MySQL dan memungkinkan Anda untuk menggunakan ragam penuh karakter Unicode.
-
-Dalam '''modus UTF-8''', MySQL akan tahu apa set karakter data dan dapat menampilkan dan mengubahnya sesuai keperluan, tetapi tidak akan mengizinkan Anda menyimpan karakter di atas [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-site-name' => 'Nama wiki:',
- 'config-site-name-help' => 'Ini akan muncul di bilah judul peramban dan di berbagai tempat lainnya.',
- 'config-site-name-blank' => 'Masukkan nama situs.',
- 'config-project-namespace' => 'Ruang nama proyek:',
- 'config-ns-generic' => 'Proyek',
- 'config-ns-site-name' => 'Sama seperti nama wiki: $1',
- 'config-ns-other' => 'Lainnya (sebutkan)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => 'Mengikuti contoh Wikipedia, banyak wiki menyimpan halaman kebijakan mereka terpisah dari halaman konten mereka, dalam "\'\'\'ruang nama proyek\'\'\'".
-Semua judul halaman dalam ruang nama ini diawali dengan prefiks tertentu yang dapat Anda tetapkan di sini.
-Biasanya, prefiks ini berasal dari nama wiki, tetapi tidak dapat berisi karakter tanda baca seperti "#" atau ":".',
- 'config-ns-invalid' => 'Ruang nama "<nowiki>$1</nowiki>" yang ditentukan tidak sah.
-Berikan ruang nama proyek lain.',
- 'config-ns-conflict' => 'Ruang nama "<nowiki>$1</nowiki>" yang diberikan berkonflik dengan ruang nama bawaan MediaWiki.
-Tentukan ruang nama proyek yang berbeda.',
- 'config-admin-box' => 'Akun pengurus',
- 'config-admin-name' => 'Nama Anda:',
- 'config-admin-password' => 'Kata sandi:',
- 'config-admin-password-confirm' => 'Kata sandi lagi:',
- 'config-admin-help' => 'Masukkan nama pengguna pilihan Anda di sini, misalnya "Udin Wiki".
-Ini adalah nama yang akan Anda gunakan untuk masuk ke wiki.',
- 'config-admin-name-blank' => 'Masukkan nama pengguna pengurus.',
- 'config-admin-name-invalid' => 'Nama pengguna "<nowiki>$1</nowiki>" yang diberikan tidak sah.
-Berikan nama pengguna lain.',
- 'config-admin-password-blank' => 'Masukkan kata sandi untuk akun pengurus.',
- 'config-admin-password-same' => 'Kata sandi harus tidak sama seperti nama pengguna.',
- 'config-admin-password-mismatch' => 'Dua kata sandi yang Anda masukkan tidak cocok.',
- 'config-admin-email' => 'Alamat surel:',
- 'config-admin-email-help' => 'Masukkan alamat surel untuk memungkinkan Anda menerima surel dari pengguna lain, menyetel ulang sandi, dan mendapat pemberitahuan tentang perubahan atas daftar pantauan Anda. Anda dapat mengosongkan bidang ini.',
- 'config-admin-error-user' => 'Kesalahan internal saat membuat admin dengan nama "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Kesalahan internal saat membuat sandi untuk admin "<nowiki>$1</nowiki>":<pre>$2</pre>',
- 'config-admin-error-bademail' => 'Anda memasukkan alamat surel yang tidak sah',
- 'config-subscribe' => 'Berlangganan ke [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce milis pengumuman rilis].',
- 'config-subscribe-help' => 'Ini adalah milis bervolume rendah yang digunakan untuk pengumuman rilis, termasuk pengumuman keamanan penting.
-Anda sebaiknya berlangganan dan memperbarui instalasi MediaWiki saat versi baru keluar.',
- 'config-almost-done' => 'Anda hampir selesai!
-Anda sekarang dapat melewati sisa konfigurasi dan menginstal wiki sekarang.',
- 'config-optional-continue' => 'Berikan saya pertanyaan lagi.',
- 'config-optional-skip' => 'Saya sudah bosan, instal saja wikinya.',
- 'config-profile' => 'Profil hak pengguna:',
- 'config-profile-wiki' => 'Wiki tradisional', # Fuzzy
- 'config-profile-no-anon' => 'Pembuatan akun diperlukan',
- 'config-profile-fishbowl' => 'Khusus penyunting terdaftar',
- 'config-profile-private' => 'Wiki pribadi',
- 'config-profile-help' => "Wiki paling baik bekerja jika Anda membiarkan sebanyak mungkin orang untuk menyunting. Dengan MediaWiki, sangat mudah meninjau perubahan terbaru dan mengembalikan kerusakan yang dilakukan oleh pengguna naif atau berbahaya.
-
-Namun, berbagai kegunaan lain dari MediaWiki telah ditemukan, dan kadang tidak mudah untuk meyakinkan semua orang manfaat dari cara wiki. Jadi, Anda yang menentukan.
-
-'''{{int:config-profile-wiki}}''' memungkinkan setiap orang untuk menyunting, bahkan tanpa masuk.
-'''{{int:config-profile-no-anon}}''' menyediakan akuntabilitas tambahan, tetapi dapat mencegah kontributor biasa.
-
-'''{{int:config-profile-fishbowl}}''' memungkinkan pengguna yang disetujui untuk menyunting, tetapi publik dapat melihat halaman, termasuk riwayatnya.
-'''{{int:config-profile-private}}''' hanya memungkinkan pengguna yang disetujui untuk melihat dan menyunting halaman.
-
-Konfigurasi hak pengguna yang lebih kompleks tersedia setelah instalasi. Lihat [//www.mediawiki.org/wiki/Manual:User_rights/id entri manual terkait].", # Fuzzy
- 'config-license' => 'Hak cipta dan lisensi:',
- 'config-license-none' => 'Tidak ada lisensi',
- 'config-license-cc-by-sa' => 'Creative Commons Atribusi Berbagi Serupa',
- 'config-license-cc-by' => 'Creative Commons Atribusi',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Atribusi Nonkomersial Berbagi Serupa',
- 'config-license-cc-0' => 'Creative Commons Zero (Domain Publik)',
- 'config-license-gfdl' => 'Lisensi Dokumentasi Bebas GNU 1.3 atau versi terbaru',
- 'config-license-pd' => 'Domain Umum',
- 'config-license-cc-choose' => 'Pilih lisensi Creative Commons kustom',
- 'config-license-help' => "Banyak wiki publik melisensikan semua kontribusi di bawah [http://freedomdefined.org/Definition lisensi bebas].
-Hal ini membantu menciptakan rasa kepemilikan komunitas dan mendorong kontribusi jangka panjang.
-Hal ini umumnya tidak diperlukan untuk wiki pribadi atau perusahaan.
-
-Jika Anda ingin dapat menggunakan teks dari Wikipedia dan Anda ingin agar Wikipedia dapat menerima teks yang disalin dari wiki Anda, Anda harus memilih'''Creative Commons Attribution Share Alike'''.
-
-Wikipedia sebelumnya menggunakan GNU Free Documentation License.
-Lisensi ini masih sah, namun sulit dipahami.
-Selain itu, sulit untuk menggunakan ulang konten yang dilisensikan di bawah GFDL.",
- 'config-email-settings' => 'Pengaturan surel',
- 'config-enable-email' => 'Aktifkan surel keluar',
- 'config-enable-email-help' => 'Jika Anda ingin mengaktifkan surel, [http://www.php.net/manual/en/mail.configuration.php setelah surel PHP] perlu dikonfigurasi dengan benar.
-Jika Anda tidak perlu fitur surel, Anda dapat menonaktifkannya di sini.',
- 'config-email-user' => 'Aktifkan surel antarpengguna',
- 'config-email-user-help' => 'Memungkinkan semua pengguna untuk saling berkirim surel jika mereka mengaktifkan pilihan tersebut dalam preferensi mereka.',
- 'config-email-usertalk' => 'Aktifkan pemberitahuan perubahan halaman pembicaraan pengguna',
- 'config-email-usertalk-help' => 'Memungkinkan pengguna untuk menerima pemberitahuan tentang perubahan halaman pembicaraan pengguna, jika pilihan tersebut telah diaktifkan dalam preferensi mereka.',
- 'config-email-watchlist' => 'Aktifkan pemberitahuan daftar pantau',
- 'config-email-watchlist-help' => 'Memungkinkan pengguna untuk menerima pemberitahuan tentang perubahan halaman yang ada dalam daftar pantauan mereka, jika pilihan tersebut telah diaktifkan dalam preferensi mereka.',
- 'config-email-auth' => 'Aktifkan otentikasi surel',
- 'config-email-auth-help' => "Jika opsi ini diaktifkan, pengguna harus mengonfirmasi alamat surel dengan menggunakan pranala yang dikirim kepadanya setiap kali mereka mengatur atau mengubahnya.
-Hanya alamat surel yang dikonfirmasi yang dapat menerima surel dari pengguna lain atau surel pemberitahuan perubahan.
-Penetapan opsi ini '''direkomendasikan''' untuk wiki publik karena adanya potensi penyalahgunaan fitur surel.",
- 'config-email-sender' => 'Alamat surel balasan:',
- 'config-email-sender-help' => 'Masukkan alamat surel untuk digunakan sebagai alamat pengirim pada surel keluar.
-Alamat ini akan menerima pentalan.
-Banyak server surel mensyaratkan paling tidak bagian nama domain yang sah.',
- 'config-upload-settings' => 'Pengunggahan gambar dan berkas',
- 'config-upload-enable' => 'Aktifkan pengunggahan berkas',
- 'config-upload-help' => 'Pengunggahan berkas berpotensi memaparkan server Anda dengan risiko keamanan.
-Untuk informasi lebih lanjut, baca [//www.mediawiki.org/wiki/Manual:Security/id manual keamanan].
-
-Untuk mengaktifkan pengunggahan berkas, ubah modus subdirektori <code>images</code> di bawah direktori akar MediaWiki agar server web dapat menulis ke sana.
-Kemudian aktifkan opsi ini.',
- 'config-upload-deleted' => 'Direktori untuk berkas terhapus:',
- 'config-upload-deleted-help' => 'Pilih direktori tempat mengarsipkan berkas yang dihapus.
-Idealnya, direktori ini tidak boleh dapat diakses dari web.',
- 'config-logo' => 'URL logo:',
- 'config-logo-help' => 'Kulit bawaan MediaWiki memberikan ruang untuk logo berukuran 135x160 piksel di atas menu bilah samping.
-Unggah gambar dengan ukuran yang sesuai, lalu masukkan URL di sini.
-
-Jika Anda tidak ingin menyertakan logo, biarkan kotak ini kosong.', # Fuzzy
- 'config-instantcommons' => 'Aktifkan Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] adalah fitur yang memungkinkan wiki untuk menggunakan gambar, suara, dan media lain dari [//commons.wikimedia.org/ Wikimedia Commons].
-Untuk melakukannya, MediaWiki memerlukan akses ke Internet.
-
-Untuk informasi lebih lanjut tentang fitur ini, termasuk petunjuk tentang cara untuk mengatur untuk wiki selain Wikimedia Commons, baca [//mediawiki.org/wiki/Manual:$wgForeignFileRepos manual].',
- 'config-cc-error' => 'Pemilih lisensi Creative Commons tidak memberikan hasil.
-Masukkan nama lisensi secara manual.',
- 'config-cc-again' => 'Pilih lagi...',
- 'config-cc-not-chosen' => 'Pilih lisensi Creative Commons yang Anda inginkan dan klik "lanjutkan".',
- 'config-advanced-settings' => 'Konfigurasi lebih lanjut',
- 'config-cache-options' => 'Pengaturan untuk penyinggahan objek:',
- 'config-cache-help' => 'Penyinggahan objek digunakan untuk meningkatkan kecepatan MediaWiki dengan menyinggahkan data yang sering digunakan.
-Situs berukuran sedang hingga besar sangat dianjurkan untuk mengaktifkan fitur ini, dan situs kecil juga akan merasakan manfaatnya.',
- 'config-cache-none' => 'Tidak ada penyinggahan (tidak ada fungsi yang dibuang, tetapi kecepatan dapat terpengaruh pada situs wiki yang besar)',
- 'config-cache-accel' => 'Penyinggahan objek PHP (APC, XCache atau WinCache)',
- 'config-cache-memcached' => 'Gunakan Memcached (memerlukan setup dan konfigurasi tambahan)',
- 'config-memcached-servers' => 'Server Memcached:',
- 'config-memcached-help' => 'Daftar alamat IP yang digunakan untuk Memcached.
-Harus dispesifikasikan per baris berikut porta yang akan digunakan. Contoh:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Anda memilih Memcached sebagai jenis singgahan, tetapi tidak menentukan server apa pun.',
- 'config-memcache-badip' => 'Anda memasukkan alamat IP yang tidak sah untuk Memcached: $1 .',
- 'config-memcache-noport' => 'Anda tidak menentukan suatu porta untuk digunakan oleh server Memcached: $1.
-Jika Anda tidak tahu porta tersebut, porta bawaan adalah 11211.',
- 'config-memcache-badport' => 'Nomor porta Memcached harus antara $1 dan $2.',
- 'config-extensions' => 'Ekstensi',
- 'config-extensions-help' => 'Ekstensi yang tercantum di atas terdeteksi di direktori <code>./extensions</code>.
-
-Ekstensi tersebut mungkin memerlukan konfigurasi tambahan, tetapi Anda dapat mengaktifkannya sekarang.',
- 'config-install-alreadydone' => "'''Peringatan:''' Anda tampaknya telah menginstal MediaWiki dan mencoba untuk menginstalnya lagi.
-Lanjutkan ke halaman berikutnya.",
- 'config-install-begin' => 'Dengan menekan "{{int:config-continue}}", Anda akan memulai instalasi MediaWiki.
-Jika Anda masih ingin membuat perubahan, tekan "{{int:config-back}}".', # Fuzzy
- 'config-install-step-done' => 'selesai',
- 'config-install-step-failed' => 'gagal',
- 'config-install-extensions' => 'Termasuk ekstensi',
- 'config-install-database' => 'Menyiapkan basis data',
- 'config-install-pg-schema-not-exist' => 'Skema PostgreSQL tidak tersedia.',
- 'config-install-pg-schema-failed' => 'Pembuatan tabel gagal.
-Pastikan bahwa pengguna "$1" dapat menulis ke skema "$2".',
- 'config-install-pg-commit' => 'Melakukan perubahan',
- 'config-install-pg-plpgsql' => 'Memeriksa bahasa PL / pgSQL',
- 'config-pg-no-plpgsql' => 'Anda perlu menginstal bahasa PL/pgSQL pada basis data $1',
- 'config-pg-no-create-privs' => 'Akun yang Anda tetapkan untuk instalasi tidak memiliki hak yang cukup untuk membuat akun.',
- 'config-install-user' => 'Membuat pengguna basis data',
- 'config-install-user-alreadyexists' => 'Pengguna "$1" sudah ada',
- 'config-install-user-create-failed' => 'Pembuatan pengguna "$1" gagal: $2',
- 'config-install-user-grant-failed' => 'Memberikan izin untuk pengguna "$1" gagal: $2',
- 'config-install-tables' => 'Membuat tabel',
- 'config-install-tables-exist' => "'''Peringatan''': Tabel MediaWiki sepertinya sudah ada.
-Melompati pembuatan.",
- 'config-install-tables-failed' => "'''Kesalahan''': Pembuatan tabel gagal dengan kesalahan berikut: $1",
- 'config-install-interwiki' => 'Mengisi tabel bawaan antarwiki',
- 'config-install-interwiki-list' => 'Tidak dapat menemukan berkas <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Peringatan''': Tabel antarwiki tampaknya sudah memiliki entri.
-Mengabaikan daftar bawaan.",
- 'config-install-stats' => 'Inisialisasi statistik',
- 'config-install-keys' => 'Membuat kunci rahasia',
- 'config-insecure-keys' => "'''Peringatan:''' {{PLURAL:$2|Suatu|Beberapa}} kunci aman ($1) yang dibuat selama instalasi {{PLURAL:$2|tidak|tidak}} benar-benar aman. Pertimbangkan untuk mengubah {{PLURAL:$2|kunci|kunci-kunci}} tersebut secara manual.",
- 'config-install-sysop' => 'Membuat akun pengguna pengurus',
- 'config-install-subscribe-fail' => 'Tidak dapat berlangganan mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL tidak diinstal dan allow_url_fopen tidak tersedia.',
- 'config-install-mainpage' => 'Membuat halaman utama dengan konten bawaan',
- 'config-install-extension-tables' => 'Pembuatan tabel untuk ekstensi yang diaktifkan',
- 'config-install-mainpage-failed' => 'Tidak dapat membuat halaman utama: $1',
- 'config-install-done' => "'''Selamat!'''
-Anda telah berhasil menginstal MediaWiki.
-
-Penginstal telah membuat berkas <code>LocalSettings.php</code>.
-Berkas itu berisi semua konfigurasi Anda.
-
-Anda perlu mengunduh berkas itu dan meletakkannya di direktori instalasi wiki (direktori yang sama dengan index.php). Pengunduhan akan dimulai secara otomatis.
-
-Jika pengunduhan tidak terjadi, atau jika Anda membatalkannya, Anda dapat mengulangi pengunduhan dengan mengeklik tautan berikut:
-
-$3
-
-'''Catatan''': Jika Anda tidak melakukannya sekarang, berkas konfigurasi yang dihasilkan ini tidak akan tersedia lagi setelah Anda keluar dari proses instalasi tanpa mengunduhnya.
-
-Setelah melakukannya, Anda dapat '''[$2 memasuki wiki Anda]'''.",
- 'config-download-localsettings' => 'Unduh <code>LocalSettings.php</code>',
- 'config-help' => 'bantuan',
- 'config-nofile' => 'Berkas "$1" tidak dapat ditemukan. Mungkin sudah dihapus?',
- 'mainpagetext' => "'''MediaWiki telah terpasang dengan sukses'''.",
- 'mainpagedocfooter' => 'Silakan baca [//www.mediawiki.org/wiki/Help:Contents/id Panduan Pengguna] untuk cara penggunaan perangkat lunak wiki ini.
-
-== Memulai penggunaan ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings/id Daftar pengaturan konfigurasi]
-* [//www.mediawiki.org/wiki/Manual:FAQ/id Daftar pertanyaan yang sering diajukan mengenai MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Terjemahkan MediaWiki ke bahasa Anda]',
-);
-
-/** Interlingue (Interlingue)
- */
-$messages['ie'] = array(
- 'mainpagetext' => "'''Software del wiki installat con successe.'''",
-);
-
-/** Igbo (Igbo)
- * @author Ukabia
- */
-$messages['ig'] = array(
- 'config-back' => '← Laàzú',
- 'config-continue' => 'Gawazie →',
- 'config-page-language' => 'Ásụ̀sụ̀',
- 'config-page-name' => 'Áhà',
- 'config-page-install' => 'Sụ̀ímé',
- 'config-restart' => 'Eeh, bìdówárí ya.',
- 'config-admin-password' => 'Okwúngáfè:',
- 'config-admin-password-confirm' => 'Okwúngáfè mgbe ozor:',
- 'mainpagetext' => "'''MediaWiki a banyélé nke oma.'''",
- 'mainpagedocfooter' => "Gbàkpó [//meta.wikimedia.org/wiki/Help:Contents Ǹdù Ọ'bànifé] màkà ụmá màkà Í jí ngwa nsónùsòrò bu wiki.
-
-== I bídó ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Ndétu ndósé ihe]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce wéfù ndétu nke ozi MediaWiki]", # Fuzzy
-);
-
-/** Iloko (Ilokano)
- */
-$messages['ilo'] = array(
- 'mainpagetext' => "'''Sibaballigi a nainstolar ti MediaWiki.'''",
-);
-
-/** Ido (Ido)
- * @author Wyvernoid
- */
-$messages['io'] = array(
- 'mainpagetext' => "'''MediaWiki instalesis sucese.'''",
- 'mainpagedocfooter' => "Videz la [//meta.wikimedia.org/wiki/Help:Contents Guidilo por Uzanti] por informo pri uzar la wiki programo.
-
-== Komencar ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listo di ''Configuration setting'']
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki OQQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki nova versioni posto-listo]", # Fuzzy
-);
-
-/** Icelandic (íslenska)
- */
-$messages['is'] = array(
- 'mainpagetext' => "'''Uppsetning á MediaWiki heppnaðist.'''",
- 'mainpagedocfooter' => 'Ráðfærðu þig við [//meta.wikimedia.org/wiki/Help:Contents Notandahandbókina] fyrir frekari upplýsingar um notkun wiki-hugbúnaðarins.
-
-== Fyrir byrjendur ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listi yfir uppsetningarstillingar]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Algengar spurningar MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Póstlisti MediaWiki-útgáfa]', # Fuzzy
-);
-
-/** Italian (italiano)
- * @author Beta16
- * @author Darth Kule
- * @author F. Cosoleto
- * @author Gianfranco
- * @author Karika
- * @author 아라
- */
-$messages['it'] = array(
- 'config-desc' => 'Il programma di installazione per MediaWiki',
- 'config-title' => 'Installazione MediaWiki $1',
- 'config-information' => 'Informazioni',
- 'config-localsettings-upgrade' => 'È stato rilevato un file <code>LocalSettings.php</code>.
-Per aggiornare questa installazione, si prega di inserire il valore di <code>$wgUpgradeKey</code> nella casella qui sotto.
-Lo potete trovare in <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'È stato rilevato un file <code>LocalSettings.php</code>.
-Per aggiornare questa installazione, eseguire <code>update.php</code>',
- 'config-localsettings-key' => 'Chiave di aggiornamento:',
- 'config-localsettings-badkey' => 'La chiave che hai fornito non è corretta.',
- 'config-upgrade-key-missing' => "È stata rilevata un'installazione esistente di MediaWiki.
-Per aggiornare questa installazione, si prega di inserire la seguente riga nella parte inferiore del tuo <code>LocalSettings.php</code>:
-
-$1",
- 'config-localsettings-incomplete' => 'Il file <code>LocalSettings.php</code> esistente sembra essere incompleto.
-La variabile $1 non è impostata.
-Cambia <code>LocalSettings.php</code> in modo che questa variabile sia impostata e fai clic su "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Si è verificato un errore durante la connessione al database utilizzando le impostazioni specificate in <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Si prega di correggere queste impostazioni e riprovare.
-
-$1',
- 'config-session-error' => "Errore nell'avvio della sessione: $1",
- 'config-session-expired' => 'I dati della sessione sembrano essere scaduti.
-Le sessioni sono configurate per una durata di $1.
-Puoi aumentarla impostando <code>session.gc_maxlifetime</code> nel file php.ini.
-Riavvia il processo di installazione.',
- 'config-no-session' => 'I dati della sessione sono andati persi!
-Controlla il tuo file php.ini ed assicurati che <code>session.save_path</code> è impostato su una directory appropriata.',
- 'config-your-language' => 'La tua lingua:',
- 'config-your-language-help' => 'Seleziona una lingua da utilizzare durante il processo di installazione.',
- 'config-wiki-language' => 'La lingua del wiki:',
- 'config-wiki-language-help' => 'Seleziona la lingua che verrà prevalentemente usata nel wiki.',
- 'config-back' => '← Indietro',
- 'config-continue' => 'Continua →',
- 'config-page-language' => 'Lingua',
- 'config-page-welcome' => 'Benvenuti in MediaWiki!',
- 'config-page-dbconnect' => 'Connessione al database',
- 'config-page-upgrade' => "Aggiornamento dell'installazione esistente",
- 'config-page-dbsettings' => 'Impostazioni del database',
- 'config-page-name' => 'Nome',
- 'config-page-options' => 'Opzioni',
- 'config-page-install' => 'Installa',
- 'config-page-complete' => 'Completa!',
- 'config-page-restart' => 'Riavvio installazione',
- 'config-page-readme' => 'Leggimi',
- 'config-page-releasenotes' => 'Note di versione',
- 'config-page-upgradedoc' => 'Aggiornamento',
- 'config-page-existingwiki' => 'Wiki esistenti',
- 'config-help-restart' => 'Vuoi cancellare tutti i dati salvati che hai inserito e riavviare il processo di installazione?',
- 'config-restart' => 'Sì, riavvia',
- 'config-welcome' => "=== Controllo dell'ambiente ===
-Saranno eseguiti controlli di base per vedere se questo ambiente è adatto per l'installazione di MediaWiki.
-Ricordati di includere queste informazioni se chiedi assistenza su come completare l'installazione.",
- 'config-sidebar' => '* [//www.mediawiki.org Pagina principale MediaWiki]
-* [//www.mediawiki.org/wiki/Aiuto:Guida ai contenuti per utenti]
-* [//www.mediawiki.org/wiki/Manuale:Guida ai contenuti per admin]
-* [//www.mediawiki.org/wiki/Manuale:FAQ FAQ]
-----
-* <doclink href=Readme>Leggimi</doclink>
-* <doclink href=ReleaseNotes>Note di versione</doclink>
-* <doclink href=Copying>Copie</doclink>
-* <doclink href=UpgradeDoc>Aggiornamenti</doclink>',
- 'config-env-good' => "L'ambiente è stato controllato.
-È possibile installare MediaWiki.",
- 'config-env-bad' => "L'ambiente è stato controllato.
-Non è possibile installare MediaWiki.",
- 'config-env-php' => 'PHP $1 è installato.',
- 'config-env-php-toolow' => 'PHP $1 è installato.
-Tuttavia, MediaWiki richiede PHP $2 o superiore.',
- 'config-outdated-sqlite' => "'''Attenzione''': è presente SQLite $1 mentre è richiesta la versione $2, SQLite non sarà disponibile.",
- 'config-no-fts3' => "'''Attenzione''': SQLite è compilato senza il [//sqlite.org/fts3.html modulo FTS3], le funzionalità di ricerca non saranno disponibili su questo backend.",
- 'config-xml-bad' => 'Il modulo XML di PHP è mancante.
-MediaWIki necessita di funzioni presenti in questo modulo e non funzionerà con la configurazione corrente.
-Se si sta eseguendo Mandrake, installare il paccketto php-xml.',
- 'config-pcre' => 'Il modulo per il supporto PCRE sembra essere mancante.
-MediaWiki necessita di questo modulo per funzinare, che fornisce funzioni per le espressioni regolari Perl-compatibili.',
- '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.
-Probabilmente è troppo basso.
-L'installazione potrebbe non riuscire!",
- 'config-ctype' => "'''Errore''': PHP deve essere compilato con il supporto per l'[http://www.php.net/manual/it/ctype.installation.php estensione Ctype].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] è installato',
- 'config-apc' => '[http://www.php.net/apc APC] è installato',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] è installato',
- 'config-no-cache' => "'''Attenzione:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache] non sono stati trovati.
-La caching degli oggetti non è attivata.",
- 'config-diff3-bad' => 'GNU diff3 non trovato.',
- 'config-git' => 'Trovato software di controllo della versione Git: <code>$1</code>.',
- 'config-git-bad' => 'Software di controllo della versione Git non trovato.',
- 'config-imagemagick' => 'Trovato ImageMagick: <code>$1</code>.
-Le miniature delle immagini saranno presenti se gli upload vengono abilitati.',
- 'config-gd' => 'Trovata la GD Graphics Library built-in.
-Le miniature delle immagini saranno presenti se gli upload vengono abilitati.',
- 'config-no-scaling' => 'Impossibile trovare GD library o ImageMagick.
-Le miniature delle immagini saranno disabilitate.',
- 'config-no-uri' => "'''Errore:''' Impossibile determinare l'URI attuale.
-Installazione interrotta.",
- 'config-no-cli-uri' => "'''Attenzione''': --scriptpath non specificato, si utilizza il valore predefinito: <code>$1</code>.",
- 'config-using-server' => 'Nome server in uso "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'URL del server in uso "<nowiki>$1$2</nowiki>".',
- 'config-brokenlibxml' => 'Il tuo sistema ha una combinazione di versioni di PHP e libxml2 che è difettosa e che può provocare un danneggiamento non visibile di dati in MediaWiki ed in altre applicazioni per il web.
-Aggiorna a PHP 5.2.9 o successivo, ed a libxml2 2.7.3 o successivo ([//bugs.php.net/bug.php?id=45996 il bug è studiato dal lato PHP]).
-Installazione interrotta.',
- 'config-using531' => 'MediaWiki non può essere usato con il PHP $1 a causa di un bug che coinvolge i parametri di riferimento a <code>__call()</code>.
-Aggiorna a PHP 5.3.2 o superiore, o fai un downgrade tornando a PHP 5.3.0 per risolvere il problema.
-Installazione interrotta.',
- 'config-db-type' => 'Tipo di database:',
- 'config-db-host' => 'Host del database:',
- 'config-db-host-help' => 'Se il server del tuo database è su un server diverso, immetti qui il nome dell\'host o il suo indirizzo IP.
-
-Se stai utilizzando un web hosting condiviso, il tuo hosting provider dovrebbe fornirti il nome host corretto nella sua documentazione.
-
-Se stai installando su un server Windows con uso di MySQL, l\'uso di "localhost" potrebbe non funzionare correttamente come nome del server. In caso di problemi, prova a impostare "127.0.0.1" come indirizzo IP locale.
-
-Se usi PostgreSQL, lascia questo campo vuoto per consentire di connettersi tramite un socket Unix.',
- 'config-db-host-oracle' => 'TNS del database:',
- 'config-db-wiki-settings' => 'Identifica questo wiki',
- 'config-db-name' => 'Nome del database:',
- 'config-db-name-help' => 'Scegli un nome che identifica il tuo wiki.
-Non deve contenere spazi.
-
-Se utilizzi un web hosting condiviso, il tuo hosting provider o ti fornisce uno specifico nome di database da utilizzare, oppure ti consentirà di creare il database tramite un pannello di controllo.',
- 'config-db-name-oracle' => 'Schema del database:',
- 'config-db-install-account' => "Account utente per l'installazione",
- 'config-db-username' => 'Nome utente del database:',
- 'config-db-password' => 'Password del database:',
- 'config-db-password-empty' => 'Inserire una password per il nuovo utente del database: $1.
-Anche se può essere possibile creare utenti senza password, questo non è sicuro.',
- 'config-db-install-username' => "Inserisci il nome utente che verrà utilizzato per connettersi al database durante il processo di installazione.
-Questo non è il nome utente dell'account MediaWiki; ma quello per il tuo database.",
- 'config-db-install-password' => "Inserisci la password che verrà utilizzato per connettersi al database durante il processo di installazione.
-Questa non è la password dell'account MediaWiki; ma quella per il tuo database.",
- 'config-db-install-help' => "Inserire il nome utente e la password che verranno usate per la connessione al database durante il processo d'installazione.",
- 'config-db-account-lock' => 'Utilizza lo stesso nome utente e password durante il normale funzionamento',
- 'config-db-wiki-account' => 'Account utente per il normale funzionamento',
- 'config-db-wiki-help' => "Inserisci il nome utente e la password che verrà utilizzato per connettersi al database durante il normale funzionamento del wiki.
-Se l'account non esiste, e l'account di installazione dispone di privilegi sufficienti, verrà creato con privilegi minimi necessari per operare sul wiki.",
- 'config-db-prefix' => 'Prefisso tabella del database:',
- 'config-db-prefix-help' => "Se hai bisogno di condividere un database tra più wiki, o tra MediaWiki e un'altra applicazione web, puoi scegliere di aggiungere un prefisso a tutti i nomi di tabella, per evitare conflitti.
-Non utilizzare spazi.
-
-Solitamente, questo campo viene lasciato vuoto.",
- 'config-db-charset' => 'Set di caratteri del database',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binario',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 con compatibilità UTF-8',
- 'config-mysql-old' => 'MySQL $1 o una versione successiva è necessaria, rilevata la $2.',
- 'config-db-port' => 'Porta del database:',
- 'config-db-schema' => 'Schema per MediaWiki:',
- 'config-db-schema-help' => 'Questo schema in genere andrà bene.
-Da cambiare solamente se si è sicuri di averne bisogno.',
- 'config-pg-test-error' => "Impossibile connettersi al database '''$1''': $2",
- 'config-sqlite-dir' => 'Directory data di SQLite:',
- 'config-oracle-def-ts' => 'Tablespace di default:',
- 'config-oracle-temp-ts' => 'Tablespace temporaneo:',
- 'config-support-info' => 'MediaWiki supporta i seguenti sistemi di database:
-
-$1
-
-Se fra quelli elencati qui sotto non vedi il sistema di database che vorresti utilizzare, seguire le istruzioni linkate sopra per abilitare il supporto.',
- 'config-support-mysql' => '* $1 è la configurazione preferibile per MediaWiki ed è quella meglio supportata ([http://www.php.net/manual/en/mysql.installation.php come compilare PHP con supporto MySQL])',
- 'config-header-mysql' => 'Impostazioni MySQL',
- 'config-header-postgres' => 'Impostazioni PostgreSQL',
- 'config-header-sqlite' => 'Impostazioni SQLite',
- 'config-header-oracle' => 'Impostazioni Oracle',
- 'config-invalid-db-type' => 'Tipo di database non valido',
- 'config-missing-db-name' => 'È necessario immettere un valore per "Nome del database"',
- 'config-missing-db-host' => 'È necessario immettere un valore per "Host del database"',
- 'config-missing-db-server-oracle' => 'È necessario immettere un valore per "TNS del database"',
- 'config-invalid-db-name' => 'Nome di database "$1" non valido.
-Utilizza soltanto caratteri ASCII come lettere (a-z, A-Z), numeri (0-9), sottolineatura (_) e trattini (-).',
- 'config-invalid-db-prefix' => 'Prefisso database "$1" non valido.
-Utilizza soltanto caratteri ASCII come lettere (a-z, A-Z), numeri (0-9), sottolineatura (_) e trattini (-).',
- 'config-connection-error' => '$1.
-
-Controlla host, nome utente e password e prova ancora.',
- 'config-db-sys-create-oracle' => "Il programma di installazione supporta solo l'utilizzo di un account SYSDBA per la creazione di un nuovo account.",
- 'config-db-sys-user-exists-oracle' => 'L\'account utente "$1" esiste già. SYSDBA può essere usato solo per la creazione di un nuovo account!',
- 'config-postgres-old' => 'PostgreSQL $1 o una versione successiva è necessaria, rilevata la $2.',
- 'config-sqlite-name-help' => 'Scegli un nome che identifichi il tuo wiki.
-Non utilizzare spazi o trattini.
-Questo servirà per il nome del file di dati SQLite.',
- 'config-sqlite-mkdir-error' => 'Errore durante la creazione della directory dati "$1".
-Controlla la posizione e riprova.',
- 'config-sqlite-dir-unwritable' => 'Impossibile scrivere nella directory "$1".
-Modifica le autorizzazioni in modo che il webserver possa scrivere in essa e riprova.',
- 'config-sqlite-connection-error' => '$1.
-
-Controlla la directory dati e il nome del database qui sotto, poi riprova.',
- 'config-sqlite-readonly' => 'Il file <code>$1</code> non è scrivibile.',
- 'config-sqlite-cant-create-db' => 'Impossibile creare il file di database <code>$1</code> .',
- 'config-sqlite-fts3-downgrade' => 'Il PHP è mancante del supporto FTS3, declassamento tabelle in corso',
- 'config-can-upgrade' => "Ci sono tabelle di MediaWiki in questo database.
-Per aggiornarle a MediaWiki $1, fai clic su '''continua'''.",
- 'config-upgrade-done' => "Aggiornamento completo.
-
-Puoi [$1 iniziare ad usare il tuo wiki].
-
-Se vuoi rigenerare il tuo file <code>LocalSettings.php</code>, clicca sul pulsante sotto. Questa operazione '''non è raccomandata''', a meno che non hai problemi con il tuo wiki.",
- 'config-upgrade-done-no-regenerate' => 'Aggiornamento completo.
-
-Puoi [$1 iniziare ad usare il tuo wiki].',
- 'config-regenerate' => 'Rigenera LocalSettings.php →',
- 'config-show-table-status' => 'La query <code>SHOW TABLE STATUS</code> è fallita!',
- 'config-unknown-collation' => "'''Attenzione:''' il database utilizza regole di confronto non riconosciute.",
- 'config-db-web-account' => "Account del database per l'accesso web",
- 'config-db-web-help' => 'Seleziona il nome utente e la password che il server web utilizzerà per connettersi al server di database, durante il normale funzionamento del wiki.',
- 'config-db-web-account-same' => "Utilizza lo stesso account dell'installazione",
- 'config-db-web-create' => "Crea l'account se non esiste già",
- 'config-db-web-no-create-privs' => "L'account usato per l'installazione non dispone dei privilegi necessari per creare un altro account.
-L'account indicato qui deve già esistere.",
- 'config-mysql-engine' => 'Storage engine:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => 'Set di caratteri del database:',
- 'config-mysql-binary' => 'Binario',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Nome del wiki:',
- 'config-site-name-help' => 'Questo verrà visualizzato nella barra del titolo del browser e in vari altri posti.',
- 'config-site-name-blank' => 'Inserisci il nome del sito.',
- 'config-project-namespace' => 'Namespace del progetto:',
- 'config-ns-generic' => 'Progetto',
- 'config-ns-site-name' => 'Stesso nome del wiki: $1',
- 'config-ns-other' => 'Altro (specificare)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-admin-box' => 'Account amministratore',
- 'config-admin-name' => 'Tuo nome:',
- 'config-admin-password' => 'Password:',
- 'config-admin-password-confirm' => 'Ripeti la password:',
- 'config-admin-help' => 'Inserisci il tuo nome utente scelto qui, ad esempio "Mario Rossi".
-Questo è il nome che userai per accedere al wiki.',
- 'config-admin-name-blank' => "Inserisci un nome utente per l'amministratore.",
- 'config-admin-name-invalid' => 'Il nome utente specificato "<nowiki>$1</nowiki>" non è valido.
-Specificare un nome utente diverso.',
- 'config-admin-password-blank' => "Inserisci una password per l'account di amministratore.",
- 'config-admin-password-same' => 'La password non deve essere uguale al nome utente.',
- 'config-admin-password-mismatch' => 'Le password inserite non coincidono tra loro.',
- 'config-admin-email' => 'Indirizzo e-mail:',
- 'config-admin-error-bademail' => 'È stato inserito un indirizzo email non valido.',
- 'config-subscribe-help' => 'Si tratta di una mailing list a basso traffico dedicata agli annunci di nuove versioni, compresi importanti segnalazioni riguardanti la sicurezza.
-È consigliato iscriversi e aggiornare la proprio installazione di MediaWiki quando una nuova versione viene resa pubblica.',
- 'config-subscribe-noemail' => "Hai provato ad iscriverti alla mailing list dedicata agli annunci delle nuove versioni senza fornire un indirizzo email.
-Inserire un indirizzo email se si desidera effettuare l'iscrizione alla mailing list.",
- 'config-almost-done' => 'Hai quasi finito!
-Adesso puoi saltare la rimanente parte della configurazione e semplicemente installare la wiki.',
- 'config-optional-continue' => 'Fammi altre domande.',
- 'config-optional-skip' => 'Sono già stanco, installa solo il wiki.',
- 'config-profile' => 'Profilo dei diritti utente:',
- 'config-profile-wiki' => 'Wiki aperto',
- 'config-profile-no-anon' => 'Creazione utenza obbligatoria',
- 'config-profile-fishbowl' => 'Solo editori autorizzati',
- 'config-profile-private' => 'Wiki privata',
- 'config-license' => 'Copyright e licenza:',
- 'config-license-none' => 'Nessun piè di pagina per la licenza',
- 'config-license-cc-by-sa' => 'Creative Commons Attribuzione-Condividi allo stesso modo',
- 'config-license-cc-by' => 'Creative Commons Attribuzione',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribuzione-Non commerciale-Condividi allo stesso modo',
- 'config-license-cc-0' => 'Creative Commons Zero (pubblico dominio)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 o versioni successive',
- 'config-license-pd' => 'Pubblico dominio',
- 'config-license-cc-choose' => 'Seleziona una delle licenze Creative Commons',
- 'config-license-help' => "Molti wiki pubblici rilasciano i loro contributi con una [http://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.
-
-Se vuoi usare testi da Wikipedia, o desideri che Wikipedia possa essere in grado di accettare testi copiati dal tuo wiki, dovresti scegliere '''Creative Commons Attribution Share Alike'''.
-
-In 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-email-user' => 'Abilita invio email fra utenti',
- 'config-email-usertalk' => 'Abilita le notifiche per le pagine di discussione utente',
- 'config-email-watchlist' => 'Abilita le notifiche per gli osservati speciali',
- 'config-email-auth' => 'Abilita autenticazione via email',
- 'config-email-sender' => 'Indirizzo email di ritorno:',
- 'config-upload-settings' => 'Caricamenti di immagini e file',
- 'config-upload-enable' => 'Consentire il caricamento di file',
- 'config-upload-deleted' => 'Directory per i file cancellati:',
- 'config-logo' => 'URL del logo:',
- 'config-instantcommons' => 'Abilita Instant Commons',
- 'config-cc-again' => 'Seleziona di nuovo...',
- 'config-cc-not-chosen' => 'Scegliere quale licenza Creative Commons si desidera e cliccare su "procedi".',
- 'config-advanced-settings' => 'Configurazione avanzata',
- 'config-memcached-servers' => 'Server di memcached:',
- 'config-memcache-needservers' => 'È stato selezionato il tipo di caching Memcached, ma non è stato impostato alcun server.',
- 'config-memcache-badip' => 'È stato inserito un indirizzo IP non valido per Memcached: $1.',
- 'config-memcache-badport' => 'I numeri di porta per memcached dovrebbero essere tra $1 e $2.',
- 'config-extensions' => 'Estensioni',
- 'config-install-step-done' => 'fatto',
- 'config-install-step-failed' => 'non riuscito',
- 'config-install-database' => 'Configurazione database',
- 'config-install-schema' => 'Creazione dello schema',
- 'config-install-user' => 'Creazione di utente del database',
- 'config-install-user-alreadyexists' => 'L\'utente "$1" è già presente',
- 'config-install-user-create-failed' => 'Creazione dell\'utente "$1" non riuscita: $2',
- 'config-install-user-missing' => 'L\'utente indicato "$1" non esiste.',
- 'config-install-tables' => 'Creazione tabelle',
- 'config-install-tables-failed' => "'''Errore''': La creazione della tabella non è riuscita: $1",
- 'config-install-interwiki' => 'Riempimento della tabella interwiki predefinita',
- 'config-install-interwiki-list' => 'Impossibile leggere il file <code>interwiki.list</code>.',
- 'config-install-stats' => 'Inizializzazione delle statistiche',
- 'config-install-keys' => 'Generazione delle chiavi segrete',
- 'config-install-sysop' => "Creazione dell'account utente per l'amministratore",
- 'config-install-subscribe-fail' => 'Impossibile sottoscrivere mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL non è installato e allow_url_fopen non è disponibile.',
- 'config-install-mainpage' => 'Creazione della pagina principale con contenuto predefinito',
- 'config-install-extension-tables' => 'Creazione delle tabelle per le estensioni attivate',
- 'config-install-mainpage-failed' => 'Impossibile inserire la pagina principale: $1',
- 'config-download-localsettings' => 'Scarica <code>LocalSettings.php</code>',
- 'config-help' => 'aiuto',
- 'config-nofile' => 'Il file "$1" non può essere trovato. È stato eliminato?',
- 'mainpagetext' => "'''Installazione di MediaWiki completata correttamente.'''",
- 'mainpagedocfooter' => "Consultare la [//meta.wikimedia.org/wiki/Aiuto:Sommario Guida utente] per maggiori informazioni sull'uso di questo software wiki.
-
-== Per iniziare ==
-I seguenti collegamenti sono in lingua inglese:
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Impostazioni di configurazione]
-* [//www.mediawiki.org/wiki/Manual:FAQ Domande frequenti su MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annunci MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localizza MediaWiki nella tua lingua]",
-);
-
-/** Japanese (日本語)
- * @author Aphaia
- * @author Fryed-peach
- * @author Iwai.masaharu
- * @author Mizusumashi
- * @author Ninomy
- * @author Ohgi
- * @author Shirayuki
- * @author Whym
- * @author Yanajin66
- * @author 青子守歌
- * @author 아라
- */
-$messages['ja'] = array(
- 'config-desc' => 'MediaWiki のインストーラー',
- 'config-title' => 'MediaWiki $1 のインストール',
- 'config-information' => '情報',
- 'config-localsettings-upgrade' => 'ファイル <code>LocalSettings.php</code> を検出しました。
-インストールされているものをアップグレードするには、<code>$wgUpgradeKey</code> の値を以下の欄に入力してください。
-この値は <code>LocalSettings.php</code> 内にあります。',
- 'config-localsettings-cli-upgrade' => 'ファイル <code>LocalSettings.php</code> を検出しました。
-インストールされているものをアップグレードするには、<code>update.php</code> を実行してください',
- 'config-localsettings-key' => 'アップグレード キー:',
- 'config-localsettings-badkey' => '与えられたキーが間違っています',
- 'config-upgrade-key-missing' => 'MediaWiki が既にインストールされていることを検出しました。
-インストールされているものをアップグレードするために、以下の行を <code>LocalSettings.php</code> の末尾に挿入してください:
-
-$1',
- 'config-localsettings-incomplete' => '既存の <code>LocalSettings.php</code> の内容は不完全のようです。
-変数 $1 が設定されていません。
-<code>LocalSettings.php</code> 内でこの変数を設定して、「{{int:Config-continue}}」をクリックしてください。',
- 'config-localsettings-connection-error' => '<code>LocalSettings.php</code> または <code>AdminSettings.php</code> で指定した設定を使用してデータベースに接続する際にエラーが発生しました。
-設定を修正してから再度試してください。
-
-$1',
- 'config-session-error' => 'セッションの開始エラー: $1',
- 'config-session-expired' => 'セッションの有効期限が切れたようです。
-セッションの有効期間は$1に設定されています。
-php.iniの<code>session.gc_maxlifetime</code>を設定することで、この問題を改善できます。
-インストール作業を再起動させてください。',
- 'config-no-session' => 'セッションのデータが消失しました!
-php.ini 内で <code>session.save_path</code> が適切なディレクトリに設定されていることを確認してください。',
- 'config-your-language' => 'あなたの言語:',
- 'config-your-language-help' => 'インストール作業に使用する言語を選択してください。',
- 'config-wiki-language' => 'ウィキの言語:',
- 'config-wiki-language-help' => 'ウィキで主に書き込まれる言語を選択してください。',
- 'config-back' => '← 戻る',
- 'config-continue' => '続行 →',
- 'config-page-language' => '言語',
- 'config-page-welcome' => 'MediaWiki へようこそ!',
- 'config-page-dbconnect' => 'データベースに接続',
- 'config-page-upgrade' => '既存のインストールを更新',
- 'config-page-dbsettings' => 'データベースの設定',
- 'config-page-name' => '名前',
- 'config-page-options' => 'オプション',
- 'config-page-install' => 'インストール',
- 'config-page-complete' => '完了!',
- 'config-page-restart' => 'インストールを再起動',
- 'config-page-readme' => 'お読みください',
- 'config-page-releasenotes' => 'リリースノート',
- 'config-page-copying' => 'コピー',
- 'config-page-upgradedoc' => 'アップグレード',
- 'config-page-existingwiki' => '既存のウィキ',
- 'config-help-restart' => '入力した保存データをすべて消去して、インストール作業を再起動しますか?',
- 'config-restart' => 'はい、再起動します',
- 'config-welcome' => '=== 環境の確認 ===
-基本的な確認では、現在の環境が MediaWiki のインストールに適しているかを確認します。
-インストール方法について助けが必要になった場合は、必ずこの確認結果を添えてください。',
- 'config-copyright' => '=== 著作権および規約 ===
-$1
-
-この作品はフリーソフトウェアです。あなたは、フリーソフトウェア財団の発行するGNU一般公衆利用許諾書 (GNU General Public License) (バージョン2、またはそれ以降のライセンス) の規約に基づき、このライブラリを再配布および改変できます。
-
-この作品は、有用であることを期待して配布されていますが、商用あるいは特定の目的に適するかどうかも含めて、暗黙的にも、一切保証されません。
-詳しくは、GNU一般公衆利用許諾書をご覧ください。
-
-あなたはこのプログラムと共に、<doclink href=Copying>GNU一般公衆利用許諾契約書の複製</doclink>を一部受け取ったはずです。受け取っていない場合は、フリーソフトウェア財団 (宛先は the Free Software Foundation, Inc., 59Temple Place, Suite 330, Boston, MA 02111-1307 USA) まで請求してください。',
- 'config-sidebar' => '* [//www.mediawiki.org MediaWikiのホーム]
-* [//www.mediawiki.org/wiki/Help:Contents 利用者向け案内]
-* [//www.mediawiki.org/wiki/Manual:Contents 管理者向け案内]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>お読みください</doclink>
-* <doclink href=ReleaseNotes>リリースノート</doclink>
-* <doclink href=Copying>コピー</doclink>
-* <doclink href=UpgradeDoc>アップグレード</doclink>',
- 'config-env-good' => '環境の確認が終わりました。
-MediaWikiをインストールできます。',
- 'config-env-bad' => '環境の確認が終わりました。
-MediaWikiのインストールはできません。',
- 'config-env-php' => 'PHP $1がインストールされています。',
- 'config-env-php-toolow' => 'PHP $1 がインストールされています。
-しかし、MediaWikiには PHP $2 以上が必要です。',
- 'config-unicode-using-utf8' => 'Unicode正規化に、Brion Vibberのutf8_normalize.soを使用。',
- 'config-unicode-using-intl' => 'Unicode正規化に[http://pecl.php.net/intl intl PECL 拡張機能]を使用。',
- 'config-unicode-pure-php-warning' => "'''警告:''' Unicode 正規化の処理に [http://pecl.php.net/intl intl PECL 拡張機能]を利用できないため、処理が遅いピュア PHP の実装を代わりに使用しています。
-高トラフィックのサイトを運営する場合は、[//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode 正規化]をお読みください。",
- 'config-unicode-update-warning' => "'''警告:''' インストールされているバージョンの Unicode 正規化ラッパーは、[http://site.icu-project.org/ ICU プロジェクト]のライブラリの古いバージョンを使用しています。
-Unicode を少しでも利用する可能性がある場合は、[//www.mediawiki.org/wiki/Unicode_normalization_considerations アップグレード]してください。",
- 'config-no-db' => '適切なデータベース ドライバーが見つかりませんでした! PHP にデータベース ドライバーをインストールする必要があります。
-以下の種類のデータベースに対応しています: $1
-
-共有サーバーを使用している場合は、適切なデータベース ドライバーのインストールを、サーバーの管理者に依頼してください。
-PHP を自分でコンパイルした場合は、例えば <code>./configure --with-mysql</code> を実行して、データベース クライアントを使用できるように再設定してください。
-Debian または Ubuntu のパッケージから PHP をインストールした場合は、php5-mysql モジュールもインストールする必要があります。',
- 'config-outdated-sqlite' => "'''警告:''' あなたは SQLite $1 を使用していますが、最低限必要なバージョン $2 より古いバージョンです。SQLite は利用できません。",
- 'config-no-fts3' => "'''警告:''' SQLite は [//sqlite.org/fts3.html FTS3] モジュールなしでコンパイルされており、このバックエンドでは検索機能は利用できなくなります。",
- 'config-register-globals' => "'''警告: PHP の <code>[http://php.net/register_globals register_globals]</code> オプションが有効になっています。'''
-'''可能なら無効化してください。'''
-MediaWiki は動作しますが、サーバーの潜在的なセキュリティ脆弱性が露呈されます。",
- 'config-magic-quotes-runtime' => "'''致命的エラー: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] が動作しています!'''
-このオプションは、予期せずデータ入力を破壊します。
-このオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
- 'config-magic-quotes-sybase' => "'''致命的エラー: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] が動作しています!'''
-このオプションは、予期せずデータ入力を破壊します。
-このオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
- 'config-mbstring' => "'''致命的エラー: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] が動作しています!'''
-このオプションは、エラーを引き起こし、予期せずデータを破壊するおそれがあります。
-このオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
- 'config-ze1' => "'''致命的エラー: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] が動作しています!'''
-このオプションは、MediaWiki において深刻なバグを引き起こします。
-このオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
- 'config-safe-mode' => "'''警告:''' PHPの[http://www.php.net/features.safe-mode セーフモード]が有効になっています。
-特に、ファイルのアップロードや<code>math</code>機能で、問題が発生するおそれがあります。",
- 'config-xml-bad' => 'PHPのXMLモジュールが不足しています。
-MediaWikiは、このモジュールの関数を必要としているため、この構成では動作しません。
-Mandrakeを実行している場合、php-xmlパッケージをインストールしてください。',
- 'config-pcre' => 'PCREをサポートしているモジュールが不足しているようです。
-MediaWikiは、Perl互換の正規表現関数の動作が必要です。',
- 'config-pcre-no-utf8' => "'''致命的エラー:''' PHP の PCRE が PCRE_UTF8 対応なしでコンパイルされているようです。
-MediaWiki を正しく動作させるには、UTF-8 対応が必要です。",
- 'config-memory-raised' => 'PHPの<code>memory_limit</code>は$1で、$2に引き上げられました。',
- 'config-memory-bad' => "'''警告:''' PHPの<code>memory_limit</code>に$1に設定されています。
-この値はおそらく小さすぎます。
-インストールが失敗するおそれがあります!",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] がインストール済み',
- 'config-apc' => '[http://www.php.net/apc APC] がインストール済み',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] がインストール済み',
- 'config-no-cache' => "'''警告:''' [http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache]、[http://www.iis.net/download/WinCacheForPhp WinCache] のいずれも見つかりませんでした。
-オブジェクトのキャッシュは有効化されません。",
- 'config-diff3-bad' => 'GNU diff3 が見つかりません。',
- 'config-git' => 'バージョン管理ソフトウェア Git が見つかりました: <code>$1</code>',
- 'config-git-bad' => 'バージョン管理ソフトウェア Git が見つかりません。',
- 'config-imagemagick' => 'ImageMagickが見つかりました: <code>$1</code>。
-アップロードが有効であれば、画像のサムネイルを利用できます。',
- 'config-gd' => 'GD画像ライブラリが内蔵されていることが確認されました。
-アップロードが有効なら、画像のサムネイルが利用できます。',
- 'config-no-scaling' => 'GDライブラリもImageMagickも見つかりませんでした。
-画像のサムネイル生成は無効になります。',
- 'config-no-uri' => "'''エラー:''' 現在のURIを決定できませんでした。
-インストールは中止されました。",
- 'config-no-cli-uri' => "'''警告:''' --scriptpath が指定されていないため、既定値 <code>$1</code> を使用します。",
- 'config-using-server' => 'サーバー名「<nowiki>$1</nowiki>」を使用しています。',
- 'config-using-uri' => 'サーバー URL「<nowiki>$1$2</nowiki>」を使用しています。',
- 'config-uploads-not-safe' => "'''警告:''' アップロードの既定ディレクトリ <code>$1</code> に、任意のスクリプト実行に関する脆弱性があります。
-MediaWiki はアップロードされたファイルのセキュリティ上の脅威を確認しますが、アップロードを有効化する前に、[//www.mediawiki.org/wiki/Manual:Security#Upload_security このセキュリティ上の脆弱性を解決する]ことを強く推奨します。",
- 'config-brokenlibxml' => 'このシステムで使われているPHPとlibxml2のバージョンのこの組み合わせにはバグがあります。具体的には、MediaWikiやその他のウェブアプリケーションでhiddenデータが破損する可能性があります。
-PHPを5.2.9かそれ以降のバージョンに、libxml2を2.7.3かそれ以降のバージョンにアップグレードしてください([//bugs.php.net/bug.php?id=45996 PHPでのバグ情報])。
-インストールを終了します。',
- 'config-using531' => 'PHP$1は<code>__call()</code>の引数参照に関するバグのため、MediaWikiと互換性がありません。
-PHP5.3.2以降に更新するか、この([//bugs.php.net/bug.php?id=50394 PHPに提出されたバグ])を修正するためにPHP5.3.0へ戻してください。
-インストールは中止されました。',
- 'config-suhosin-max-value-length' => 'Suhosin がインストールされており、GET パラメーターの <code>length</code> を $1 バイトに制限しています。
-MediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。
-可能な限り、<code>php.ini</code> で <code>suhosin.get.max_value_length</code> を 1024 以上に設定し、同じ値を <code>LocalSettings.php</code> 内で <code>$wgResourceLoaderMaxQueryLength</code> に設定してください。',
- 'config-db-type' => 'データベースの種類:',
- 'config-db-host' => 'データベースのホスト:',
- 'config-db-host-help' => '異なるサーバー上にデータベースサーバーがある場合、ホスト名またはIPアドレスをここに入力してください。
-
-もし、共有されたウェブホスティングを使用している場合、ホスティングプロバイダーは正確なホスト名を解説しているはずです。
-
-WindowsでMySQLを使用している場合に、「localhost」は、サーバー名としてはうまく働かないでしょう。もしそのような場合は、ローカルIPアドレスとして「127.0.0.1」を試してみてください。
-
-PostgreSQLを使用している場合、UNIXソケットで接続するにはこの欄を空欄のままにしてください。',
- 'config-db-host-oracle' => 'データベース TNS:',
- 'config-db-host-oracle-help' => '有効な[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm ローカル接続名]を入力してください。tnsnames.ora ファイルは、このインストール先から参照できる場所に置いてください。<br />ご使用中のクライアント ライブラリが 10g 以降の場合、[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] ネーミング メソッドを使用できます。',
- 'config-db-wiki-settings' => 'このウィキの識別情報',
- 'config-db-name' => 'データベース名:',
- 'config-db-name-help' => 'このウィキを識別する名前を入力してください。
-空白を含めることはできません。
-
-共有ウェブホストを利用している場合、ホスティングプロバイダーが特定の使用可能なデータベース名を提供するか、あるいは管理パネルからデータベースを作成できるようにしているでしょう。',
- 'config-db-name-oracle' => 'データベースのスキーマ:',
- 'config-db-install-account' => 'インストールで使用する利用者アカウント',
- 'config-db-username' => 'データベースのユーザー名:',
- 'config-db-password' => 'データベースのパスワード:',
- 'config-db-password-empty' => '新しいデータベースの利用者名 $1 のパスワードを入力してください。
-パスワードを設定せずにユーザーを作成できる場合もありますが、安全ではありません。',
- 'config-db-install-username' => 'インストール中にデータベースへの接続で使用するユーザー名を入力してください。
-これは MediaWiki アカウントの利用者名のことではありません。あなたのデータベースでのユーザー名です。',
- 'config-db-install-password' => 'インストール中にデータベースへの接続で使用するパスワードを入力してください。
-これは MediaWiki アカウントのパスワードのことではありません。あなたのデータベースでのパスワードです。',
- 'config-db-install-help' => 'インストール作業中にデータベースに接続するための利用者名とパスワードを入力してください。',
- 'config-db-account-lock' => 'インストール作業終了後も同じ利用者名とパスワードを使用する',
- 'config-db-wiki-account' => 'インストール作業終了後の利用者アカウント',
- 'config-db-wiki-help' => '通常のウィキ操作中にデータベースへの接続する時に利用する利用者名とパスワードを入力してください。
-アカウントが存在せず、インストールのアカウントに十分な権限がある場合は、この利用者アカウントは、ウィキを操作する上で最小限の権限を持った状態で作成されます。',
- 'config-db-prefix' => 'データベース テーブルの接頭辞:',
- 'config-db-prefix-help' => 'データベースを複数のウィキ間、あるいはMediaWikiと他のウェブアプリケーションで共有する必要がある場合、衝突を避けるために、すべてのテーブル名に接頭辞を付ける必要があります。
-空白は使用できません。
-
-このフィールドは、通常は空のままです。',
- 'config-db-charset' => 'データベースの文字セット',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 バイナリ',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 後方互換 UTF-8',
- 'config-charset-help' => "'''警告:''' MySQL 4.1+ で'''後方互換 UTF-8''' を使用している状態で、<code>mysqldump</code> でデータベースをバックアップすると、すべての非 ASCII 文字が破壊されてしまい、バックアップが不可逆的に破損してしまいます!
-
-'''バイナリ モード'''では、MediaWiki は、UTF-8 テキストをデータベースのバイナリ フィールドに格納します。
-これは、MySQL の UTF-8 モードより効率的で、Unicode 文字の全範囲を利用できるようになります。
-'''UTF-8 モード'''では、MySQL は、データ内で使用している文字集合を知っているため、適切に表現や変換ができますが、
-[//ja.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E8%A8%80%E8%AA%9E%E9%9D%A2 基本多言語面]の外にある文字を格納できません。",
- 'config-mysql-old' => 'MySQL $1 以降が必要です。ご使用中の MySQL は $2 です。',
- 'config-db-port' => 'データベースのポート:',
- 'config-db-schema' => 'MediaWiki のスキーマ:',
- 'config-db-schema-help' => '通常はこのスキーマで問題ありません。
-必要な場合のみ変更してください。',
- 'config-pg-test-error' => "データベース '''$1''' に接続できません: $2",
- 'config-sqlite-dir' => 'SQLite データ ディレクトリ:',
- 'config-sqlite-dir-help' => "SQLite は単一のファイル内にすべてのデータを格納しています。
-
-指定したディレクトリは、インストール時にウェブ サーバーが書き込めるようにしておく必要があります。
-
-このディレクトリはウェブからアクセス'''不可能'''である必要があります。PHP ファイルがある場所には配置できないのはこのためです。
-
-インストーラーは <code>.htaccess</code> ファイルにも書き込みます。しかし、これが失敗した場合は、誰かが生のデータベースにアクセスできてしまいます。
-データベースは、生のデータ (メールアドレス、パスワードのハッシュ値) の他、削除された版、その他ウィキ上の制限されているデータを含んでいます。
-
-例えば <code>/var/lib/mediawiki/yourwiki</code> のように、別の場所にデータベースを配置することを検討してください。",
- 'config-oracle-def-ts' => '既定のテーブル領域:',
- 'config-oracle-temp-ts' => '一時的なテーブル領域:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki は以下のデータベース システムに対応しています:
-
-$1
-
-使用しようとしているデータベース システムが下記の一覧にない場合は、上記リンク先の手順に従ってインストールしてください。',
- 'config-support-mysql' => '* $1はMediaWikiの主要な対象であり、最もサポートされています ([http://www.php.net/manual/en/mysql.installation.php MySQLに対応したPHPをコンパイルする方法])',
- 'config-support-postgres' => '* $1は、MySQLの代替として人気があるオープンソースのデータベースシステムです ([http://www.php.net/manual/en/pgsql.installation.php PostgreSQLに対応したPHPをコンパイルする方法])',
- 'config-support-sqlite' => '* $1は、良くサポートされている、軽量データベースシステムです。([http://www.php.net/manual/ja/pdo.installation.php SQLiteに対応したPHPをコンパイルする方法]、PDOを使用)',
- 'config-support-oracle' => '* $1は商業企業のデータベースです。([http://www.php.net/manual/en/oci8.installation.php OCI8サポートなPHPをコンパイルする方法])',
- 'config-header-mysql' => 'MySQL の設定',
- 'config-header-postgres' => 'PostgreSQL の設定',
- 'config-header-sqlite' => 'SQLite の設定',
- 'config-header-oracle' => 'Oracle の設定',
- 'config-invalid-db-type' => '無効なデータベースの種類',
- 'config-missing-db-name' => '「データベース名」を入力してください',
- 'config-missing-db-host' => '「データベースのホスト」を入力してください',
- 'config-missing-db-server-oracle' => '「データベース TNS」の値を入力してください',
- 'config-invalid-db-server-oracle' => '「$1」は無効なデータベース TNS です。
-「TNS 名」「Easy Connect」文字列のいずれかを使用してください ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle ネーミング メソッド])',
- 'config-invalid-db-name' => '「$1」は無効なデータベース名です。
-半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_)、ハイフン (-) のみを使用してください。',
- 'config-invalid-db-prefix' => '「$1」は無効なデータベース接頭辞です。
-半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_)、ハイフン (-) のみを使用してください。',
- 'config-connection-error' => '$1。
-
-以下のホスト名、ユーザー名、パスワードを確認してから再度試してください。',
- 'config-invalid-schema' => '「$1」は MediaWiki のスキーマとして無効です。
-半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_) のみを使用してください。',
- 'config-postgres-old' => 'PostgreSQL $1 以降が必要です。ご使用中の PostgreSQL は $2 です。',
- 'config-sqlite-name-help' => 'あなたのウェキと同一性のある名前を選んでください。
-空白およびハイフンは使用しないでください。
-SQLiteのデータファイル名として使用されます。',
- 'config-sqlite-parent-unwritable-group' => 'データ ディレクトリ <code><nowiki>$1</nowiki></code> を作成できません。ウェブ サーバーは親ディレクトリ <code><nowiki>$2</nowiki></code> に書き込めませんでした。
-
-インストーラーは、ウェブ サーバーの実行ユーザーを特定しました。
-続行するには、ディレクトリ <code><nowiki>$3</nowiki></code> に書き込めるようにしてください。
-Unix または Linux であれば、以下を実行してください:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'データ ディレクトリ <code><nowiki>$1</nowiki></code> を作成できません。ウェブ サーバーは、親ディレクトリ <code><nowiki>$2</nowiki></code> に書き込めませんでした。
-
-インストーラーは、ウェブ サーバーの実行ユーザーを特定できませんでした。
-続行するには、ディレクトリ <code><nowiki>$3</nowiki></code> に、ウェブ サーバー (と、あらゆる人々!) がグローバルに書き込めるようにしてください。
-Unix または Linux では、以下を実行してください:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'データ ディレクトリ「$1」を作成する際にエラーが発生しました。
-場所を確認してから、再度試してください。',
- 'config-sqlite-dir-unwritable' => 'ディレクトリ「$1」に書き込めません。
-ウェブ サーバーが書き込めるようにパーミッションを変更してから、再度試してください。',
- 'config-sqlite-connection-error' => '$1。
-
-データ ディレクトリおよびデータベース名を確認してから、再度試してください。',
- 'config-sqlite-readonly' => 'ファイル <code>$1</code> に書き込めません。',
- 'config-sqlite-cant-create-db' => 'データベース ファイル <code>$1</code> を作成できませんでした。',
- 'config-sqlite-fts3-downgrade' => 'PHP が FTS3 に対応していないため、テーブルをダウングレードしています',
- 'config-can-upgrade' => 'このデータベースには MediaWiki テーブルがあります。
-これらのテーブルを MediaWiki $1 にアップグレードするには、「続行」をクリックしてください。',
- 'config-upgrade-done' => "更新は完了しました。
-
-[$1 ウィキを使い始める]ことができます。
-
-<code>LocalSettings.php</code> ファイルを再生成したい場合は、下のボタンを押してください。
-ウィキに問題がある場合を除き、再生成は'''推奨されません'''。",
- 'config-upgrade-done-no-regenerate' => 'アップグレードが完了しました。
-
-[$1 ウィキの使用を開始]することができます。',
- 'config-regenerate' => 'LocalSettings.php を再生成→',
- 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> クエリが失敗しました!',
- 'config-unknown-collation' => "'''警告:''' データベースは認識されない照合を使用しています。",
- 'config-db-web-account' => 'ウェブアクセスのためのデータベースアカウント',
- 'config-db-web-help' => 'ウィキの通常の操作の際に、ウェブ サーバーがデータベース サーバーに接続できるように、ユーザー名とパスワードを指定してください。',
- 'config-db-web-account-same' => 'インストール作業と同じアカウントを使用する',
- 'config-db-web-create' => 'アカウントが存在しない場合は作成する',
- 'config-db-web-no-create-privs' => 'あなたがインストールのために定義したアカウントは、アカウント作成のための特権としては不充分です。
-あなたがここで指定したアカウントは既に存在している必要があります。',
- 'config-mysql-engine' => 'ストレージ エンジン:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-engine-help' => "'''InnoDB'''は、並行処理のサポートに優れているので、ほとんどの場合において最良の選択肢です。
-
-'''MyISAM'''は、利用者が1人の場合、あるいは読み込み専用でインストールする場合に、より処理が早くなるでしょう。
-ただし、MyISAMのデータベースは、InnoDBより高頻度で破損する傾向があります。",
- 'config-mysql-charset' => 'データベースの文字セット:',
- 'config-mysql-binary' => 'バイナリ',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "'''バイナリ モード'''では、MediaWiki は、UTF-8 テキストをデータベースのバイナリ フィールドに格納します。
-これは、MySQL の UTF-8 モードより効率的で、Unicode 文字の全範囲を利用できるようになります。
-
-'''UTF-8 モード'''では、MySQL は、データ内で使用している文字集合を知っているため、適切に表現や変換ができますが、
-[//ja.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E8%A8%80%E8%AA%9E%E9%9D%A2 基本多言語面]の外にある文字を格納できません。",
- 'config-site-name' => 'ウィキ名:',
- 'config-site-name-help' => 'この事象はブラウザーのタイトルバーと他のさまざまな場所に現れる。',
- 'config-site-name-blank' => 'サイト名を入力してください。',
- 'config-project-namespace' => 'プロジェクト名前空間:',
- 'config-ns-generic' => 'プロジェクト',
- 'config-ns-site-name' => 'ウィキ名と同じ: $1',
- 'config-ns-other' => 'その他 (指定してください)',
- 'config-ns-other-default' => 'マイウィキ',
- 'config-project-namespace-help' => "ウィキペディアの例に従い、多くのウィキは、コンテンツのページとは分離したポリシーページを「'''プロジェクトの名前空間'''」に持っています。
-この名前空間内のページのページ名はすべて特定の接頭辞で始まります。それをここで指定できます。
-通常、この接頭辞はウィキ名に基づきますが、「#」や「:」のような区切り文字を含めることはできません。",
- 'config-ns-invalid' => '指定した名前空間「<nowiki>$1</nowiki>」は無効です。
-別のプロジェクト名前空間を指定してください。',
- 'config-admin-box' => '管理アカウント',
- 'config-admin-name' => '名前:',
- 'config-admin-password' => 'パスワード:',
- 'config-admin-password-confirm' => 'パスワードの再入力:',
- 'config-admin-help' => '希望するユーザー名をここに入力してください (例:「Joe Bloggs」)。
-この名前でこのウィキにログインすることになります。',
- 'config-admin-name-blank' => '管理者のユーザー名を入力してください。',
- 'config-admin-name-invalid' => '指定したユーザー名「<nowiki>$1</nowiki>」は無効です。
-別のユーザー名を指定してください。',
- 'config-admin-password-blank' => '管理者アカウントのパスワードを入力してください。',
- 'config-admin-password-same' => 'ユーザー名と同じパスワードは使用できません。',
- 'config-admin-password-mismatch' => '入力された2つのパスワードが一致しません。',
- 'config-admin-email' => 'メールアドレス:',
- 'config-admin-email-help' => 'メールアドレスを入力してください。他の利用者からのメールの受け取り、パスワードのリセット、ウォッチリストに登録したページの更新通知に使用します。空欄のままにすることもできます。',
- 'config-admin-error-user' => '「<nowiki>$1</nowiki>」という名前の管理者を作成する際に内部エラーが発生しました。',
- 'config-admin-error-password' => '管理者「<nowiki>$1</nowiki>」のパスワードを設定する際に内部エラーが発生しました: <pre>$2</pre>',
- 'config-admin-error-bademail' => '無効なメールアドレスを入力しました。',
- 'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce リリース告知のメーリングリスト]を購読する。',
- 'config-subscribe-help' => 'これは、リリースの告知 (重要なセキュリティに関する案内を含む) に使用される、流量が少ないメーリングリストです。
-このメーリングリストを購読して、新しいバージョンが出た場合にMediaWikiを更新してください。',
- 'config-almost-done' => 'これでほぼ終わりました!
-残りの設定を飛ばして、ウィキを今すぐインストールできます。',
- 'config-optional-continue' => '私にもっと質問してください。',
- 'config-optional-skip' => 'もう飽きてしまったので、とにかくウィキをインストールしてください。',
- 'config-profile' => '利用者権限のプロファイル:',
- 'config-profile-wiki' => '公開ウィキ',
- 'config-profile-no-anon' => 'アカウントの作成が必要',
- 'config-profile-fishbowl' => '承認された編集者のみ',
- 'config-profile-private' => '非公開ウィキ',
- 'config-profile-help' => "ウィキは、できるだけ多くの人が編集できるようにすると最も優れた働きをします。
-MediaWikiでは、最近の更新を確認しやすく、神経質な、または悪意を持った利用者からの損害を簡単に差し戻せます。
-
-しかし一方で、MediaWikiは、さらにさまざまな形態での利用も優れていると言われています。また、時には、すべての人にウィキ手法の利点を説得させるのは容易ではないかもしれません。
-そこで、選択肢があります。
-
-「'''{{int:config-profile-wiki}}'''」モデルでは、ログインしなくても、誰でも編集できます。
-「'''{{int:config-profile-no-anon}}'''」なウィキでは、各編集に対してより強い説明責任を付与しますが、気軽な投稿を阻害するかもしれません。
-
-「'''{{int:config-profile-fishbowl}}'''」シナリオでは、承認された利用者のみが編集でき、一般の人はページ (とその履歴) を閲覧できます。
-「'''{{int:config-profile-private}}'''」では、承認された利用者のみがページを閲覧でき、そのグループが編集できます。
-
-より複雑な利用者権限の設定は、インストール後に設定できます。詳細は[//www.mediawiki.org/wiki/Manual:User_rights 関連するマニュアル]をご覧ください。",
- 'config-license' => '著作権とライセンス:',
- 'config-license-none' => 'ライセンスのフッターを付けない',
- 'config-license-cc-by-sa' => 'クリエイティブ・コモンズ 表示-継承',
- 'config-license-cc-by' => 'クリエイティブ・コモンズ 表示',
- 'config-license-cc-by-nc-sa' => 'クリエイティブ・コモンズ 表示-非営利-継承',
- 'config-license-gfdl' => 'GNU フリー文書利用許諾契約書 1.3 以降',
- 'config-license-pd' => 'パブリック・ドメイン',
- 'config-license-cc-choose' => 'その他のクリエイティブ・コモンズ・ライセンスを選択する',
- 'config-license-help' => "多くの公開ウィキでは、すべての寄稿物が[http://freedomdefined.org/Definition フリーライセンス]のもとに置かれています。
-こうすることにより、コミュニティによる共有の感覚が生まれ、長期的な寄稿が促されます。
-私的ウィキや企業のウィキでは、通常、フリーライセンスにする必要はありません。
-
-ウィキペディアにあるテキストをあなたのウィキで利用し、逆にあなたのウィキにあるテキストをウィキペディアに複製することを許可したい場合には、'''クリエイティブ・コモンズ 表示-継承'''を選択するべきです。
-
-ウィキペディアは以前、GNUフリー文書利用許諾契約書(GFDL)を使用していました。
-GFDLは有効なライセンスですが、内容を理解するのは困難です。
-また、GFDLのもとに置かれているコンテンツの再利用も困難です。",
- 'config-email-settings' => 'メールの設定',
- 'config-enable-email' => 'メール送信を有効にする',
- 'config-enable-email-help' => 'メールを使用したい場合は、[http://www.php.net/manual/en/mail.configuration.php PHP のメール設定]が正しく設定されている必要があります。
-メールの機能を使用しない場合は、ここで無効にすることができます。',
- 'config-email-user' => '利用者間のメールを有効にする',
- 'config-email-user-help' => '設定で有効になっている場合、すべてのユーザーがお互いにメールのやりとりを行うことを許可する。',
- 'config-email-usertalk' => 'ユーザーのトークページでの通知を有効にする',
- 'config-email-usertalk-help' => '設定で有効にしている場合は、ユーザーのトークページの変更の通知を受けることをユーザーに許可する。',
- 'config-email-watchlist' => 'ウォッチリストの通知を有効にする',
- 'config-email-watchlist-help' => '利用者が設定で有効にしている場合、閲覧されたページに関する通知を受け取ることを許可する。',
- 'config-email-auth' => 'メールの認証を有効にする',
- 'config-email-auth-help' => "この選択肢を有効にすると、利用者がメールアドレスを設定あるいは変更したときに送信されるリンクにより、そのアドレスを確認しなければならなくなります。
-認証済みのアドレスだけが、他の利用者からのメールや、変更通知のメールを受け取ることができます。
-公開ウィキでは、メール機能による潜在的な不正利用の防止のため、この選択肢を設定することが'''推奨'''されます。",
- 'config-email-sender' => '返信先メールアドレス:',
- 'config-email-sender-help' => '送信メールで返信先として使用するメールアドレスを入力してください。
-このアドレスは、宛先不明の場合の通知の宛先になります。
-多くのメールサーバーでは、少なくともドメイン名部分は有効である必要があります。',
- 'config-upload-settings' => '画像およびファイルのアップロード',
- 'config-upload-enable' => 'ファイルのアップロードを有効にする',
- 'config-upload-help' => 'ファイルのアップロードは、あなたのサーバーをセキュリティ上の潜在的な危険に晒します。
-この詳細は、マニュアルの [//www.mediawiki.org/wiki/Manual:Security security section] をお読みください。
-
-ファイルのアップロードを有効にするには、MediaWiki のルート ディレクトリ内の <code>images</code> サブ ディレクトリのモードを変更します。これにより、ウェブ サーバーがそこに書き込めるようになります。
-そして、このオプションを有効にしてください。',
- 'config-upload-deleted' => '削除されたファイルのためのディレクトリ:',
- 'config-upload-deleted-help' => '削除されるファイルを保存するためのディレクトリを選択してください。
-これがウェブからアクセスできないことが理想です。',
- 'config-logo' => 'ロゴ のURL:',
- 'config-logo-help' => 'MediaWiki の既定の外装では、サイドバー上部に135x160ピクセルのロゴ用の余白があります。
-適切なサイズの画像をアップロードして、その URL をここに入力してください。
-
-ロゴが相対パスの場合は、<code>$wgStylePath</code> や <code>$wgScriptPath</code> を使用できます。
-
-ロゴが不要の場合は、この欄を空白のままにしてください。',
- 'config-instantcommons' => 'Instant Commons 機能を有効にする',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] は、[//commons.wikimedia.org/ ウィキメディア・コモンズ]のサイトにある画像、音声、その他のメディアをウィキ上で利用できるようにする機能です。
-これを使用するには、MediaWiki がインターネットに接続できる必要があります。
-
-ウィキメディア・コモンズ以外のウィキを同様に設定する手順など、この機能に関する詳細な情報は、[//mediawiki.org/wiki/Manual:$wgForeignFileRepos マニュアル]をご覧ください。',
- 'config-cc-error' => 'クリエイティブ・コモンズ・ライセンスの選択器から結果が得られませんでした。
-ライセンスの名前を手動で入力してください。',
- 'config-cc-again' => 'もう一度選択してください...',
- 'config-cc-not-chosen' => '希望するクリエイティブ・コモンズのライセンスを選択して、「続行」をクリックしてください。',
- 'config-advanced-settings' => '高度な設定',
- 'config-cache-options' => 'オブジェクトのキャッシュの設定:',
- 'config-cache-help' => 'オブジェクトのキャッシュを使用すると、頻繁に使用するデータをキャッシュするため MediaWiki の動作速度を改善できます。
-中〜大規模サイトではこれを有効にすることを強くお勧めします。小規模サイトでも同様に効果があります。',
- 'config-cache-none' => 'キャッシングしない(機能は取り払われます、しかもより大きなウィキサイト上でスピードの問題が発生します)',
- 'config-cache-accel' => 'PHP オブジェクト キャッシュ (APC、XCache、WinCache のいずれか)',
- 'config-cache-memcached' => 'memcached を使用 (追加の設定が必要)',
- 'config-memcached-servers' => 'memcached サーバー:',
- 'config-memcached-help' => 'Memcachedを使用するIPアドレスの一覧。
-カンマ区切りで、利用する特定のポートの指定が必要です。例:
-127.0.0.1:11211
-192.168.1.25:1234',
- 'config-memcache-noport' => 'Memcached サーバー $1 で使用するポート番号を指定していません。
-ポート番号が分からない場合、既定値は 11211 です。',
- 'config-memcache-badport' => 'Memcached のポート番号は $1 から $2 の範囲にしてください。',
- 'config-extensions' => '拡張機能',
- 'config-extensions-help' => '<code>./extensions</code>ディレクトリ内で、上記リストの拡張機能が発見されました。
-
-これらは更に多くの設定を要求するかもしれませんが、今これらを有効にすることができます。',
- 'config-install-alreadydone' => "'''警告:''' 既にMediaWikiがインストール済みで、再びインストールし直そうとしています。
-次のページへ進んでください。",
- 'config-install-begin' => '「{{int:config-continue}}」を押すと、MediaWiki のインストールを開始できます。
-変更したい設定がある場合は、「{{int:config-back}}」を押してください。',
- 'config-install-step-done' => '実行',
- 'config-install-step-failed' => '失敗した',
- 'config-install-extensions' => '拡張機能を含む',
- 'config-install-database' => 'データベースの構築',
- 'config-install-schema' => 'スキーマの作成',
- 'config-install-pg-schema-not-exist' => 'PostgreSQL スキーマがありません。',
- 'config-install-pg-schema-failed' => 'テーブルの作成に失敗しました。
-利用者「$1」がスキーマ「$2」に書き込めるようにしてください。',
- 'config-install-pg-commit' => '変更を送信',
- 'config-pg-no-plpgsql' => 'データベース $1 内に PL/pgSQL 言語をインストールする必要があります。',
- 'config-install-user' => 'データベースユーザーの作成',
- 'config-install-user-alreadyexists' => 'ユーザー「$1」は既に存在します',
- 'config-install-user-create-failed' => 'ユーザー「$1」の作成に失敗しました: $2',
- 'config-install-user-grant-failed' => 'ユーザー「$1」に許可を与えることに失敗しました: $2',
- 'config-install-user-missing' => '指定したユーザー「$1」は存在しません。',
- 'config-install-user-missing-create' => '指定したユーザー「$1」は存在しません。
-アカウントを作成する場合は、下の「アカウント作成」をクリックしてください。',
- 'config-install-tables' => 'テーブルの作成',
- 'config-install-tables-exist' => "'''警告:''' MediaWiki テーブルは既に存在するようです。
-作成を省略します。",
- 'config-install-tables-failed' => "'''エラー:''' テーブルの作成が、以下のエラーにより失敗しました: $1",
- 'config-install-interwiki' => '既定のウィキ間テーブルの導入',
- 'config-install-interwiki-list' => 'ファイル <code>interwiki.list</code> から読み取れませんでした。',
- 'config-install-interwiki-exists' => "'''警告:''' ウィキ間テーブルは既に登録されているようです。
-既定のテーブルを無視します。",
- 'config-install-stats' => '統計情報の初期化',
- 'config-install-keys' => '秘密鍵の生成',
- 'config-install-sysop' => '管理者のアカウントの作成',
- 'config-install-mainpage' => 'メインページを既定の内容で作成',
- 'config-install-mainpage-failed' => 'メインページを挿入できませんでした: $1',
- 'config-install-done' => "'''おめでとうございます!'''
-MediaWikiのインストールに成功しました。
-
-<code>LocalSettings.php</code>ファイルが生成されました。
-このファイルはすべての設定を含んでいます。
-
-これをダウンロードして、ウィキをインストールした基準ディレクトリ (index.phpと同じディレクトリ) に設置する必要があります。ダウンロードは自動的に開始されるはずです。
-
-ダウンロードが開始されていない場合、またはダウンロードをキャンセルした場合は、下記のリンクをクリックしてダウンロードを再開できます:
-
-$3
-
-'''注意:''' この生成された設定ファイルをダウンロードせずにインストールを終了すると、このファイルは利用できなくなります。
-
-上記の作業が完了すると、'''[$2 ウィキに入る]'''ことができます。",
- 'config-download-localsettings' => '<code>LocalSettings.php</code> をダウンロード',
- 'config-help' => 'ヘルプ',
- 'config-nofile' => 'ファイル「$1」が見つかりませんでした。削除された可能性があります。',
- 'mainpagetext' => "'''MediaWiki のインストールに成功しました。'''",
- 'mainpagedocfooter' => 'ウィキソフトウェアの使い方に関する情報は[//meta.wikimedia.org/wiki/Help:Contents 利用者案内]を参照してください。
-
-== はじめましょう ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings/ja 設定の一覧]
-* [//www.mediawiki.org/wiki/Manual:FAQ/ja MediaWiki よくある質問と回答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki リリース情報メーリングリスト]
-* [//www.mediawiki.org/wiki/Localisation/ja MediaWiki のあなたの言語へのローカライズ]',
-);
-
-/** Jamaican Creole English (Patois)
- * @author Yocahuna
- */
-$messages['jam'] = array(
- 'mainpagetext' => "'''MediaWiki don instaal soksesful.'''",
- 'mainpagedocfooter' => "Kansolt di [//meta.wikimedia.org/wiki/Help:Contents User's Guide] fi infamieshan ou fi yuuz di wiki saafwier.
-
-== Taatop ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Jutish (jysk)
- * @author Huslåke
- */
-$messages['jut'] = array(
- 'mainpagetext' => "'''MediaWiki er nu installeret.'''",
- 'mainpagedocfooter' => "Se vores engelskspråĝede [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentåsje tilpasnenge'm åf æ brugergrænseflade] og [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide æ brugervejlednenge] før åplysnenger åpsætnenge'm og anvendelse.", # Fuzzy
-);
-
-/** Javanese (Basa Jawa)
- */
-$messages['jv'] = array(
- 'mainpagetext' => "'''Prangkat empuk wiki wis suksès dipasang.'''",
- 'mainpagedocfooter' => "Mangga maca [//meta.wikimedia.org/wiki/Help:Contents User's Guide] kanggo katrangan luwih langkung prakara panggunan prangkat empuk wiki
-== Miwiti panggunan ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Daftar pangaturan préférènsi]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]", # Fuzzy
-);
-
-/** Georgian (ქართული)
- * @author David1010
- */
-$messages['ka'] = array(
- 'config-information' => 'ინფორმაცია',
- 'config-your-language' => 'თქვენი ენა:',
- 'config-wiki-language' => 'ვიკის ენა:',
- 'config-back' => '← უკან',
- 'config-continue' => 'გაგრძელება →',
- 'config-page-language' => 'ენა',
- 'config-page-welcome' => 'კეთილი იყოს თქვენი მობრძანება მედიავიკიში!',
- 'config-page-dbconnect' => 'მონაცემთა ბაზასთან დაკავშირება',
- 'config-page-dbsettings' => 'მონაცემთა ბაზის კონფიგურაცია',
- 'config-page-name' => 'სახელი',
- 'config-page-options' => 'პარამეტრები',
- 'config-page-install' => 'ინსტალაცია',
- 'config-page-complete' => 'დასრულებულია!',
- 'config-page-restart' => 'ინსტალაციის თავიდან დაწყება',
- 'config-page-readme' => 'წამიკითხე',
- 'config-page-copying' => 'ლიცენზია',
- 'config-page-upgradedoc' => 'განახლება',
- 'config-restart' => 'დიახ, თავიდან დაიწყეთ',
- 'config-sidebar' => '* [//www.mediawiki.org მედიავიკის ვებ-გვერდი]
-* [//www.mediawiki.org/wiki/Help:Contents/ka მომხმარებლების დახმარება]
-* [//www.mediawiki.org/wiki/Manual:Contents/ka ადმინისტრატორების დახმარება]
-* [//www.mediawiki.org/wiki/Manual:FAQ/ka FAQ]
-----
-* <doclink href=Readme>წამიკითხე</doclink>
-* <doclink href=ReleaseNotes>ინფორმაცია გამოშვებაზე</doclink>
-* <doclink href=Copying>ლიცენზია</doclink>
-* <doclink href=UpgradeDoc>განახლება</doclink>',
- 'config-db-type' => 'მონაცემთა ბაზის ტიპი:',
- 'config-db-host-oracle' => 'მონაცემთა ბაზის TNS:',
- 'config-db-name' => 'მონაცემთა ბაზის სახელი:',
- 'config-db-name-oracle' => 'მონაცემთა ბაზის სქემა:',
- 'config-db-username' => 'მონაცემთა ბაზის მომხმარებლის სახელი:',
- 'config-db-password' => 'მონაცემთა ბაზის პაროლი:',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 ორობითი',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-db-port' => 'მონაცემთა ბაზის პორტი:',
- 'config-db-schema' => 'მედიავიკის სქემა:',
- 'config-header-mysql' => 'MySQL-ის პარამეტრები',
- 'config-header-postgres' => 'PostgreSQL-ის პარამეტრები',
- 'config-header-sqlite' => 'SQLite-ის პარამეტრები',
- 'config-header-oracle' => 'Oracle-ის პარამეტრები',
- 'config-invalid-db-type' => 'არასწორი მონაცემთა ბაზის ტიპი',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-binary' => 'ორობითი',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'ვიკის სახელი:',
- 'config-site-name-blank' => 'შეიყვანეთ ვებ-გვერდის სახელი.',
- 'config-project-namespace' => 'პროექტის სახელთა სივრცე:',
- 'config-ns-generic' => 'პროექტი',
- 'config-ns-other' => 'სხვა (მიუთითეთ)',
- 'config-ns-other-default' => 'ჩემი ვიკი',
- 'config-admin-box' => 'ადმინისტრატორის ანგარიში',
- 'config-admin-name' => 'თქვენი სახელი:',
- 'config-admin-password' => 'პაროლი:',
- 'config-admin-password-confirm' => 'პაროლი ხელმეორედ:',
- 'config-admin-name-blank' => 'შეიყვანეთ ადმინისტრატორის მომხმარებლის სახელი.',
- 'config-admin-email' => 'ელ. ფოსტის მისამართი:',
- 'config-profile' => 'მომხმარებელთა უფლებების პროფილი:',
- 'config-profile-wiki' => 'ღია ვიკი',
- 'config-profile-private' => 'დახურული ვიკი',
- 'config-license' => 'საავტორო უფლები და ლიცენზია:',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (საზოგადოებრივი საკუთრება)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 ან უფრო გვიანი',
- 'config-license-pd' => 'საზოგადოებრივი საკუთრება',
- 'config-license-cc-choose' => 'აირჩიეთ Creative Commons-ის ლიცენზიიდან ერთ-ერთი',
- 'config-email-settings' => 'ელ. ფოსტის პარამეტრები',
- 'config-upload-settings' => 'სურათებისა და ფაილების ატვირთვა',
- 'config-upload-enable' => 'ფაილების ატვირთვის ჩართვა',
- 'config-logo' => 'ლოგოს URL:',
- 'config-cc-again' => 'აირჩიეთ კიდევ ერთხელ...',
- 'config-extensions' => 'გაფართოებები',
- 'config-install-step-done' => 'შესრულდა',
- 'config-install-step-failed' => 'ვერ მოხერხდა',
- 'config-install-tables' => 'ცხრილების შექმნა',
- 'config-install-interwiki-list' => 'ვერ მოიძებნა ფაილი <code>interwiki.list</code>.',
- 'config-download-localsettings' => '<code>LocalSettings.php</code>-ის გადმოწერა',
- 'config-help' => 'დახმარება',
- 'mainpagetext' => "'''მედიავიკი წარმატებით ჩაიტვირთა.'''",
- 'mainpagedocfooter' => 'ვიკი პროგრამის გამოყენების ინფორმაციისთვის იხილეთ [//meta.wikimedia.org/wiki/Help:Contents მომხმარებლის მეგზური].
-
-== დაწყება ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings კონფიგურაციის მაჩვენებლების სია]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce მედიავიკის გამოცემის დაგზავნის სია]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources მედიავიკის ლოკალიზება თქვენ ენაზე]',
-);
-
-/** Kara-Kalpak (Qaraqalpaqsha)
- */
-$messages['kaa'] = array(
- 'mainpagetext' => "'''MediaWiki tabıslı ornatıldı.'''",
- 'mainpagedocfooter' => "Wiki bag'darlamasın qollanıw haqqındag'i mag'lıwmat usın [//meta.wikimedia.org/wiki/Help:Contents Paydalanıwshılar qollanbasınan] ken'es alın'.
-
-== Baslaw ushın ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfiguratsiya sazlaw dizimi]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWikidin' Ko'p Soralatug'ın Sorawları]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki haqqında xat tarqatıw dizimi]", # Fuzzy
-);
-
-/** Адыгэбзэ (Адыгэбзэ)
- * @author Bogups
- */
-$messages['kbd-cyrl'] = array(
- 'mainpagetext' => "'''«MediaWiki» узыншу хэгъува.'''",
- 'mainpagedocfooter' => 'Мы виким и лэжьыгъэ хъыбархэр здэбгъуэтыфынур [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 дэӀэпыкъуэгъу тхылъым].
-
-
-== Къыщхьэпэгъуэ хъуфынухэр ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Зэгъэзэхуэгъуэ гуэрэхэм я тхылъ];
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-м упщӀэ нахъыбу ятхэмрэ я жэуапхэмрэ];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-м и версиэ щӀэуэ къэжахэм я къэӀохугъуэ].', # Fuzzy
-);
-
-/** Khowar (کھوار)
- * @author Rachitrali
- */
-$messages['khw'] = array(
- 'mainpagetext' => "'''میڈیاوکیو کامیابیو سورا چالو کورونو بیتی شیر۔.'''",
-);
-
-/** Kirmanjki (Kırmancki)
- * @author Mirzali
- */
-$messages['kiu'] = array(
- 'mainpagetext' => "'''MediaWiki fist ra ser, vıraziya.'''",
- 'mainpagedocfooter' => "Serba melumatê gurenaena ''wiki software''i [//meta.wikimedia.org/wiki/Help:Contents İdarê karberi] de mıracaet ke.
-
-== Gamê verêni ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista ayarunê vırastene]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki de ÇZP]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-daena postey]", # Fuzzy
-);
-
-/** Kazakh (Arabic script) (قازاقشا (تٴوتە)‏)
- */
-$messages['kk-arab'] = array(
- 'mainpagetext' => "'''مەدىياۋىيكىي بۋماسى ٴساتتى ورناتىلدى.'''",
- 'mainpagedocfooter' => 'ۋىيكىي باعدارلامالىق جاساقتاماسىن قالاي قولداناتىن اقپاراتى ٴۇشىن [//meta.wikimedia.org/wiki/Help:Contents پايدالانۋشىلىق نۇسقاۋلارىنان] كەڭەس الىڭىز.
-
-== باستاۋ ٴۇشىن ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings باپتالىم قالاۋلارىنىڭ ٴتىزىمى]
-* [//www.mediawiki.org/wiki/Manual:FAQ مەدىياۋىيكىيدىڭ جىيى قويىلعان ساۋالدارى]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce مەدىياۋىيكىي شىعۋ تۋرالى حات تاراتۋ ٴتىزىمى]', # Fuzzy
-);
-
-/** Kazakh (Cyrillic script) (қазақша (кирил)‎)
- */
-$messages['kk-cyrl'] = array(
- 'mainpagetext' => "'''МедиаУики бумасы сәтті орнатылды.'''",
- 'mainpagedocfooter' => 'Уики бағдарламалық жасақтамасын қалай қолданатын ақпараты үшін [//meta.wikimedia.org/wiki/Help:Contents Пайдаланушылық нұсқауларынан] кеңес алыңыз.
-
-== Бастау үшін ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Бапталым қалауларының тізімі]
-* [//www.mediawiki.org/wiki/Manual:FAQ МедиаУикидің Жиы Қойылған Сауалдары]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаУики шығу туралы хат тарату тізімі]', # Fuzzy
-);
-
-/** Kazakh (Latin script) (qazaqşa (latın)‎)
- */
-$messages['kk-latn'] = array(
- 'mainpagetext' => "'''MedïaWïkï bwması sätti ornatıldı.'''",
- 'mainpagedocfooter' => 'Wïkï bağdarlamalıq jasaqtamasın qalaý qoldanatın aqparatı üşin [//meta.wikimedia.org/wiki/Help:Contents Paýdalanwşılıq nusqawlarınan] keñes alıñız.
-
-== Bastaw üşin ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Baptalım qalawlarınıñ tizimi]
-* [//www.mediawiki.org/wiki/Manual:FAQ MedïaWïkïdiñ Jïı Qoýılğan Sawaldarı]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MedïaWïkï şığw twralı xat taratw tizimi]', # Fuzzy
-);
-
-/** Khmer (ភាសាខ្មែរ)
- * @author Thearith
- * @author គីមស៊្រុន
- */
-$messages['km'] = array(
- 'config-your-language' => 'ភាសារបស់អ្នក៖',
- 'config-your-language-help' => 'ជ្រើសយកភាសាដើម្បីប្រើក្នុងពេលតំលើង។',
- 'config-wiki-language' => 'ភាសាវិគី៖',
- 'config-wiki-language-help' => 'ជ្រើសរើសភាសាដែលវិគីនេះប្រើជាចំបង។',
- 'config-back' => '← ត្រលប់ក្រោយ',
- 'config-continue' => 'បន្ត →',
- 'config-page-language' => 'ភាសា',
- 'config-page-welcome' => 'មេឌាវិគីសូមស្វាគមន៍!',
- 'config-page-dbconnect' => 'ភ្ជាប់ទៅមូលដ្ឋានទិន្នន័យ',
- 'config-page-name' => 'ឈ្មោះ',
- 'config-page-options' => 'ជំរើស',
- 'config-page-install' => 'តំលើង',
- 'config-page-complete' => 'បញ្ចប់!',
- 'config-page-restart' => 'តំលើងឡើងវិញ',
- 'config-help' => 'ជំនួយ',
- 'mainpagetext' => "'''មេឌាវិគីត្រូវបានដំឡើងសំរេចហើយ​។'''",
- 'mainpagedocfooter' => 'សូមពិនិត្យមើល [//meta.wikimedia.org/wiki/ជំនួយ​៖ ខ្លឹមសារ​ណែនាំ​ប្រើប្រាស់]សម្រាប់​ព័ត៌មាន​​បន្ថែមចំពោះ​ការប្រើប្រាស់ ផ្នែកទន់វិគី​។
-
-== ចាប់ផ្ដើមជាមួយមេឌាវិគី ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings បញ្ជីកំណត់ទម្រង់]
-* [//www.mediawiki.org/wiki/Manual:FAQ/km សំណួរញឹកញាប់​មេឌាវិគី]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce បញ្ជី​ពិភាក្សា​ការផ្សព្វផ្សាយ​របស់​មេឌាវិគី]', # Fuzzy
-);
-
-/** Kannada (ಕನ್ನಡ)
- */
-$messages['kn'] = array(
- 'mainpagetext' => "'''ವಿಕಿ ತಂತ್ರಾಂಶವನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಅನುಸ್ಥಾಪಿಸಲಾಯಿತು.'''",
- 'mainpagedocfooter' => 'ವಿಕಿ ತಂತ್ರಾಂಶವನ್ನು ಬಳಸುವ ಬಗ್ಗೆ ಮಾಹಿತಿಗೆ [//meta.wikimedia.org/wiki/Help:Contents ಬಳಕೆದಾರರಿಗೆ ನಿರ್ದೇಶನ ಪುಟ] ನೋಡಿ.
-
-== ಪ್ರಾರಂಭಿಸುವುದು ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ ಮೀಡಿಯವಿಕಿ FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]', # Fuzzy
-);
-
-/** Korean (한국어)
- * @author Kwj2772
- * @author 아라
- */
-$messages['ko'] = array(
- 'config-desc' => '미디어위키 설치 프로그램',
- 'config-title' => 'MediaWiki $1 설치',
- 'config-information' => '정보',
- 'config-localsettings-upgrade' => '<code>LocalSettings.php</code> 파일을 감지했습니다.
-이 설치를 업그레이드하려면 아래 상자에 <code>$wgUpgradeKey</code>의 값을 입력하세요.
-<code>LocalSettings.php</code>에 찾을 수 있습니다.',
- 'config-localsettings-cli-upgrade' => '<code>LocalSettings.php</code> 파일을 감지했습니다.
-이 설치를 업그레이드하려면 <code>update.php</code>를 대신 실행하세요',
- 'config-localsettings-key' => '업그레이드 키:',
- 'config-localsettings-badkey' => '제공한 키가 잘못되었습니다.',
- 'config-upgrade-key-missing' => '미디어위키의 기존 설치가 감지되었습니다.
-이 설치를 업그레이드하려면 <code>LocalSettings.php</code>의 아래에 다음 줄을 넣으세요:
-
-$1',
- 'config-localsettings-incomplete' => '기존 <code>LocalSettings.php</code>가 완전하지 않은 것 같습니다.
-$1 변수가 설정되어 있지 않습니다.
-이 변수가 설정되도록 <code>LocalSettings.php</code>를 바꾸고 "{{int:Config-continue}}"을 클릭하세요.',
- 'config-localsettings-connection-error' => '<code>LocalSettings.php</code> 또는 <code>AdminSettings.php</code>에 지정한 설정을 사용하여 데이터베이스에 연결할 때 오류가 발생했습니다. 이러한 설정을 수정하고 다시 시도하세요.
-
-$1',
- 'config-session-error' => '세션 시작 오류: $1',
- 'config-session-expired' => '세션 데이터가 만료된 것 같습니다.
-세션은 $1의 작동 시간 동안 구성됩니다.
-php.ini에 있는 <code>session.gc_maxlifetime</code>에서 설정해 이를 증가시킬 수 있습니다.
-설치 과정을 다시 시작합니다.',
- 'config-no-session' => '세션 데이터가 없어졌습니다!
-php.ini를 확인하고 <code>session.save_path</code>가 적절한 디렉토리로 설정되어 있는지 확인하세요.',
- 'config-your-language' => '설치 언어:',
- 'config-your-language-help' => '설치 과정에서 사용할 언어를 선택하세요.',
- 'config-wiki-language' => '위키 언어:',
- 'config-wiki-language-help' => '위키에 주로 작성될 언어를 선택하세요.',
- 'config-back' => '← 뒤로',
- 'config-continue' => '계속 →',
- 'config-page-language' => '언어',
- 'config-page-welcome' => '미디어위키에 오신 것을 환영합니다!',
- 'config-page-dbconnect' => '데이터베이스에 연결',
- 'config-page-upgrade' => '기존 설치 업그레이드',
- 'config-page-dbsettings' => '데이터베이스 설정',
- 'config-page-name' => '이름',
- 'config-page-options' => '설정',
- 'config-page-install' => '설치',
- 'config-page-complete' => '완료!',
- 'config-page-restart' => '설치 다시 시작',
- 'config-page-readme' => '읽어보기',
- 'config-page-releasenotes' => '배포 노트',
- 'config-page-copying' => '전문',
- 'config-page-upgradedoc' => '업그레이드하기',
- 'config-page-existingwiki' => '기존 위키',
- 'config-help-restart' => '입력한 모든 저장된 데이터를 지우고 설치 과정을 다시 시작하겠습니까?',
- 'config-restart' => '예, 다시 시작합니다',
- 'config-welcome' => '=== 사용 환경 검사 ===
-기본 검사는 지금 이 환경이 미디어위키 설치에 적합한지 수행합니다.
-설치를 완료하는 방법에 대한 지원을 찾는다면 이 정보를 포함해야 하는 것을 기억하세요.',
- 'config-copyright' => "=== 저작권 및 이용 약관 ===
-
-$1
-
-이 프로그램은 자유 소프트웨어입니다. 당신은 자유 소프트웨어 재단이 발표한 GNU 일반 공중 사용 허가서 버전 2나 그 이후 버전에 따라 이 프로그램을 재배포하거나 수정할 수 있습니다.
-
-이 프로그램이 유용하게 사용될 수 있기를 바라지만 '''상용으로 사용'''되거나 '''특정 목적에 맞을 것'''이라는 것을 '''보증하지 않습니다'''.
-자세한 내용은 GNU 일반 공중 사용 허가서를 참고하십시오.
-
-당신은 이 프로그램을 통해 <doclink href=Copying>GNU 일반 공중 사용 허가서 전문</doclink>을 받았습니다. 그렇지 않다면, Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA로 편지를 보내주시거나 [http://www.gnu.org/copyleft/gpl.html 온라인으로 읽어보시기] 바랍니다.",
- 'config-sidebar' => '* [//www.mediawiki.org 미디어위키 홈]
-* [//www.mediawiki.org/wiki/Help:Contents 사용자 가이드]
-* [//www.mediawiki.org/wiki/Manual:Contents 관리자 가이드]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>읽어보기</doclink>
-* <doclink href=ReleaseNotes>배포 노트</doclink>
-* <doclink href=Copying>전문</doclink>
-* <doclink href=UpgradeDoc>업그레이드하기</doclink>',
- 'config-env-good' => '환경이 확인되었습니다.
-미디어위키를 설치할 수 있습니다.',
- 'config-env-bad' => '환경이 확인되었습니다.
-미디어위키를 설치할 수 없습니다.',
- 'config-env-php' => 'PHP $1(이)가 설치되었습니다.',
- 'config-env-php-toolow' => 'PHP $1(이)가 설치되었습니다.
-하지만 미디어위키는 PHP $2 이상이 필요합니다.',
- 'config-unicode-using-utf8' => '유니코드 정규화에 Brion Vibber의 utf8_normalize.so를 사용합니다.',
- 'config-unicode-using-intl' => '유니코드 정규화에 [http://pecl.php.net/intl intl PECL 확장 기능]을 사용합니다.',
- 'config-unicode-pure-php-warning' => "'''경고''': 유니코드 정규화를 처리할 [http://pecl.php.net/intl intl PECL 확장 기능]을 사용할 수 없기 때문에 느린 순수한 PHP 구현을 대신 사용합니다.
-높은 트래픽 사이트에서 실행하려면 [//www.mediawiki.org/wiki/Unicode_normalization_considerations 유니코드 정규화]를 읽어보시기 바랍니다.",
- 'config-unicode-update-warning' => "'''경고''': 유니코드 정규화 래퍼의 설치된 버전은 [http://site.icu-project.org/ ICU 프로젝트]의 라이브러리의 이전 버전을 사용합니다.
-만약 유니코드를 사용하는 것에 대해 우려가 된다면 [//www.mediawiki.org/wiki/Unicode_normalization_considerations 업그레이드]해야합니다.",
- 'config-no-db' => '적절한 데이터베이스 드라이버를 찾을 수 없습니다! PHP에 데이터베이스 드라이버를 설치해야 합니다.
-다음 데이터베이스 유형을 지원합니다 : $1.
-
-공유하는 호스팅을 사용하고 있다면 적절한 데이터베이스 드라이버를 설치하도록 호스팅 제공 업체에 문의하세요.
-PHP를 직접 컴파일했다면 예를 들어 <code>./configure --with-mysql</code>을 사용하여 데이터베이스 클라이언트를 활성화하도록 다시 설정하세요.
-데비안이나 우분트 패키지에서 PHP를 설치했다면 php-mysql 모듈도 설치해야 합니다.',
- 'config-outdated-sqlite' => "'''경고''': SQLite 필요한 최소 $2 버전보다 낮은 $1(이)가 있습니다. SQLite는 사용할 수 없습니다.",
- 'config-no-fts3' => "'''경고''': SQLite는 [//sqlite.org/fts3.html FTS3 모듈] 없이 컴파일되어, 검색 기능은 백엔드에 사용할 수 없습니다.",
- 'config-register-globals' => "'''경고: PHP의 <code>[http://php.net/register_globals register_globals]</code> 옵션이 활성화되어 있습니다.'''
-'''가능하면 이를 비활성화하십시오.'''
-미디어위키는 작동하지만 서버에 잠재적인 보안 취약점이 노출됩니다.",
- 'config-magic-quotes-runtime' => "'''치명: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]이 활성합니다!'''
-이 옵션은 데이터를 입력하는 데 예기치 않는 손상이 일어납니다.
-이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
- 'config-magic-quotes-sybase' => "'''치명: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]이 활성합니다!'''
-이 옵션은 데이터를 입력하는 데 예기치 않는 손상이 일어납니다.
-이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
- 'config-mbstring' => "'''치명: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]이 활성합니다!'''
-이 옵션은 오류가 발생하고 데이터를 입력하는 데 예기치 않는 손상이 일어날 수 있습니다.
-이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
- 'config-ze1' => "'''치명: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]이 활성합니다!'''
-이 옵션은 미디어위키에 심간한 버그를 일으킵니다.
-이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
- 'config-safe-mode' => "'''경고:''' [http://www.php.net/features.safe-mode 안전 모드]이 활성합니다!
-특히 파일을 올리거나 <code>math</code>를 지원하는 데 문제가 발생할 수 있습니다.",
- 'config-xml-bad' => 'PHP의 XML 모듈이 없습니다.
-미디어위키는 이 모듈의 기능이 필요하며 이 설정에서는 작동하지 않습니다.
-Mandrake를 실행하고 있다면 php-xml 패키지를 설치하세요.',
- 'config-pcre' => 'PCRE 지원 모듈이 없는 것 같습니다.
-미디어위키는 Perl 호환 정규 표현식을 작동시켜야 합니다.',
- 'config-pcre-no-utf8' => "'''치명''': PHP의 PCRE 모듈은 RCRE_UTF8 지원 없이 컴파일된 것 같습니다.
-미디어위키가 제대로 작동하려면 UTF-8 지원이 필요합니다.",
- 'config-memory-raised' => 'PHP의 <code>memory_limit</code>는 $1이며 $2(으)로 늘리세요.',
- 'config-memory-bad' => "'''경고:''' PHP의 <code>memory_limit</code>는 $1입니다.
-아마도 너무 낮은 것 같습니다.
-설치가 실패할 수 있습니다!",
- 'config-ctype' => "'''치명''': PHP는 [http://www.php.net/manual/en/ctype.installation.php Ctype 확장 기능]에 대해 지원하여 컴파일해야 합니다.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache]가 설치되었습니다',
- 'config-apc' => '[http://www.php.net/apc APC]가 설치되었습니다',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache]가 설치되었습니다',
- 'config-no-cache' => "'''경고:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] 또는 [http://www.iis.net/download/WinCacheForPhp WinCache]를 찾을 수 없습니다.
-개체 캐싱을 활성화하지 않습니다.",
- 'config-mod-security' => "'''경고''': 웹 서버에 [http://modsecurity.org/ mod_security]가 허용되었습니다. 잘못 설정된 경우 미디어위키나 사용자가 임의의 콘텐츠를 게시할 수 있는 다른 소프트웨어에 대한 문제를 일으킬 수 있습니다.
-[http://modsecurity.org/documentation/ mod_security] 문서를 참고하거나 임의의 오류가 발생할 경우 호스트의 지원 요청에 문의하십시오.",
- 'config-diff3-bad' => 'GNU diff3를 찾을 수 없습니다.',
- 'config-git' => 'Git 버전 관리 소프트웨어를 찾았습니다: <code>$1</code>.',
- 'config-git-bad' => 'Git 버전 관리 소프트웨어를 찾을 수 없습니다.',
- 'config-imagemagick' => 'ImageMagick를 찾았습니다: <code>$1</code>.
-올리기를 활성화할 경우 그림 섬네일이 활성화됩니다.',
- 'config-gd' => '내장된 GD 그래픽 라이브러리를 찾았습니다.
-올리기를 활성화할 경우 그림 섬네일이 활성화됩니다.',
- 'config-no-scaling' => 'GD 라이브러리나 ImageMagick를 찾을 수 없습니다.
-그림 섬네일이 비활성화됩니다.',
- 'config-no-uri' => "'''오류:''' 현재 URI를 확인할 수 없습니다.
-설치가 중단되었습니다.",
- 'config-no-cli-uri' => "'''경고''': 기본값을 사용하여 --scriptpath를 지정하지 않았습니다: <code>$1</code>.",
- 'config-using-server' => '"<nowiki>$1</nowiki>"(을)를 서버 이름으로 사용합니다.',
- 'config-using-uri' => '"<nowiki>$1$2</nowiki>"(을)를 서버 URL로 사용합니다.',
- 'config-uploads-not-safe' => "'''경고:''' 올리기에 대한 기본 디렉터리(<code>$1</code>)는 임의의 스크립트 실행에 취약합니다.
-미디어위키는 보안 위협에 대한 모든 올린 파일을 검사하지만, 올리기를 활성화하기 전에 [//www.mediawiki.org/wiki/Manual:Security#Upload_security 이 보안 취약점을 해결할 것]을 매우 권장합니다.",
- 'config-no-cli-uploads-check' => "'''경고:''' 올리기에 대한 기본 디렉터리(<code>$1</code>)는 CLI를 설치하는 동안 임의의 스크립트 실행에 대한 취약점에 대해 검사되지 않습니다.",
- 'config-brokenlibxml' => '시스템에 버그가 있는 PHP와 libxml2의 조합이 있으며 미디어위키나 다른 웹 어플리케이션에 숨겨진 데이터 손상을 일으킬 수 있습니다.
-PHP 5.2.9 이후와 libxml2 2.7.3 이후로 업그레이드하세요. ([//bugs.php.net/bug.php?id=45996 PHP에 제기한 버그])
-설치가 중단되었습니다.',
- 'config-using531' => '미디어위키는 <code>__call()</code>을 참고로 매개 변수를 포함하는 버그로 인해 PHP $1(와)과 함께 사용할 수 없습니다.
-문제를 해결하려면 PHP 5.3.2 이상로 업그레이드하거나 PHP 5.3.0으로 다운그레이드를 하세요.
-설치가 중단되었습니다.',
- 'config-suhosin-max-value-length' => '수호신(Suhosin)이 설치되었고 $1 바이트로 GET 매개 변수인 <code>length</code>를 제한하고 있습니다.
-미디어위키의 ResourceLoader 구성 요소는 이 제한을 해결하지만 성능이 저하됩니다.
-가능하면 <code>php.ini</code>의 <code>suhosin.get.max_value_length</code>에 1024 이상으로 설정하고 <code>LocalSettings.php</code>의 <code>$wgResourceLoaderMaxQueryLength</code>에 같은 값을 설정해야 합니다.',
- 'config-db-type' => '데이터베이스 종류:',
- 'config-db-host' => '데이터베이스 호스트:',
- 'config-db-host-help' => '데이터베이스 서버가 다른 서버에 있으면 여기에 호스트 이름이나 IP 주소를 입력하세요.
-
-공유하는 웹 호스팅을 사용하고 있으면 호스팅 제공 업체는 호스트 이름을 설명하고 있을 것입니다.
-
-윈도 서버에 설치하고 MySQL을 사용하면 "localhost"는 서버 이름으로 작동하지 않을 수 있습니다. 그렇게 된다면 로컬 IP 주소로 "127.0.0.1"를 시도하세요.
-
-PostgreSQL을 사용하면 유닉스 소켓을 통해 연결되도록 입력란을 비워두세요.',
- 'config-db-host-oracle' => '데이터베이스 TNS:',
- 'config-db-host-oracle-help' => '올바른 [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 로컬 연결 이름]을 입력하세요. tnsnames.ora 파일이 이 설치에 보여야 합니다.<br />10g 이후의 클라이언트 라이브러리를 사용하는 경우 [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 쉬운 연결] 네이밍 메소드도 사용할 수 있습니다.',
- 'config-db-wiki-settings' => '이 위키 식별',
- 'config-db-name' => '데이터베이스 이름:',
- 'config-db-name-help' => '위키를 식별하기 위한 이름을 선택하세요.
-공백이 없어야 합니다.
-
-공유하는 웹 호스팅 사용하면 호스팅 제공 업체가 특정 데이터베이스 이름을 제공하거나 관리 패널에서 데이터베이스를 만들 수 있습니다.',
- 'config-db-name-oracle' => '데이터베이스 스키마:',
- 'config-db-account-oracle-warn' => '데이터베이스 백엔드로 오라클을 설치하기 위해 지원하는 세 가지 시나리오가 있습니다:
-
-설치 과정의 일부로 데이터베이스 계정을 만들려면 설치를 위해 데이터베이스 계정으로 SYSDBA 역할을 가진 계정을 제공하고 웹 접근 계정에 대해 원하는 자격 증명을 지정하세요, 그렇지 않으면 수동으로 웹 접근 계정을 만들 수 있으며 (필요한 경우 권한 스키마 개체를 만들어야 합니다) 또는 다른 계정 두 개를 만들고 권한을 가진 하나의 웹 접근을 위한 제한된 하나를 제공할 수 있습니다.
-
-필요한 권한을 가진 계정을 만드는 스크립트는 이 설치의 "maintenance/oracle/" 디렉토리에서 찾을 수 있습니다. 제한된 계정을 사용하면 기본 계정으로 모든 관리 기능을 비활성화할 것을 유의하십시오.',
- 'config-db-install-account' => '설치를 위한 사용자 계정',
- 'config-db-username' => '데이터베이스 사용자 이름:',
- 'config-db-password' => '데이터베이스 비밀번호:',
- 'config-db-password-empty' => '새 데이터베이스 사용자의 비밀번호를 입력하세요: $1.
-비밀번호 없이 사용자를 만들 수도 있지만 안전하지 않습니다.',
- 'config-db-install-username' => '설치 과정 중에 데이터베이스에 연결할 때 사용할 사용자 이름을 입력하세요.
-미디어위키 계정의 사용자 이름이 아닌 데이터베이스에 대한 사용자 이름입니다.',
- 'config-db-install-password' => '설치 과정 중에 데이터베이스에 연결할 때 사용할 비밀번호을 입력하세요.
-미디어위키 계정의 비밀번호가 아닌 데이터베이스에 대한 비밀번호입니다.',
- 'config-db-install-help' => '설치 과정 중에 데이터베이스에 연결할 때 사용할 사용자 이름과 비밀번호를 입력하세요.',
- 'config-db-account-lock' => '정상적으로 작동하는 동안 같은 사용자 이름과 비밀번호를 사용함',
- 'config-db-wiki-account' => '정상적인 작동을 위한 사용자 계정',
- 'config-db-wiki-help' => '정상적인 위키 작업 동안 데이터베이스에 연결하는 데 사용할 사용자 이름과 비밀 번호를 입력하세요.
-계정이 존재하지 않고 설치 계정에 충분한 권한이 있는 경우 이 사용자 계정은 위키를 작동하는 데 필요한 최소 권한으로 만들어집니다.',
- 'config-db-prefix' => '데이터베이스 테이블 접두어:',
- 'config-db-prefix-help' => '여러 위키 사이 또는 미디어위키와 다른 웹 응용 프로그램 사이에서 하나의 데이터베이스를 공유해야 하는 경우, 충돌을 피하기 위해 모든 테이블 이름에 접두어를 추가하도록 선택할 수 있습니다.
-공백을 사용하지 마세요.
-
-이 필드는 일반적으로 비어있습니다.',
- 'config-db-charset' => '데이터베이스 문자 집합',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 바이너리',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 UTF-8 하위 호환성',
- 'config-charset-help' => "'''경고:''' MySQL 4.1에서 '''UTF-8 하위 호환성'''을 사용하고 나서 <code>mysqldump</code>로 데이터베이스에 백업한다면 모든 ASCII가 아닌 문자를 파괴하고 손상한 백업을 되돌릴 수 없습니다!
-
-'''바이너리 모드'''에서는 미디어위키는 바이너리 필드의 데이터베이스에 UTF-8 텍스트를 저장합니다.
-MySQL의 UTF-8 모드를 보다 더 효율적이고 유니코드 문자의 전체 범위를 사용할 수 있습니다.
-'''UTF-8 모드'''에서는 MySQL은 데이터를 설정하는 문자 집합을 알고 있기 때문에 적절하게 표현하고 변환할 수 있지만
-[//ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_%ED%8F%89%EB%A9%B4#.EA.B8.B0.EB.B3.B8_.EB.8B.A4.EA.B5.AD.EC.96.B4_.ED.8F.89.EB.A9.B4 기본 다국어 평면] 밖의 문자를 저장할 수 없습니다.",
- 'config-mysql-old' => 'MySQL $1 이상이 필요하나 $2(이)가 있습니다.',
- 'config-db-port' => '데이터베이스 포트:',
- 'config-db-schema' => '미디어위키에 대한 스키마:',
- 'config-db-schema-help' => '보통 이 스키마는 문제가 없습니다.
-필요한 경우에만 바꾸세요.',
- 'config-pg-test-error' => "'''$1''' 데이터베이스에 연결할 수 없습니다: $2",
- 'config-sqlite-dir' => 'SQLite 데이터 디렉터리:',
- 'config-sqlite-dir-help' => "SQLite는 하나의 파일에 모든 데이터를 저장합니다.
-
-제공하는 디렉토리는 설치하는 동안 웹 서버가 쓸 수 있어야 합니다.
-
-이 디렉토리는 웹을 통해 접근할 수 '''없어야''' 하는데 PHP 파일이 있는 곳에 넣을 수 없는 것은 이 때문입니다.
-
-설치 프로그램은 <code>.htaccess</code> 파일을 작성하지만 이것이 실패하면 누군가가 원본 데이터베이스에 접근할 수 있습니다.
-데이터베이스는 원본 사용자 데이터(이메일 주소, 해시한 비밀번호) 뿐만 아니라 삭제된 판과 위키의 다른 제한된 데이터를 포함합니다.
-
-예를 들어 <code>/var/lib/mediawiki/yourwiki</code>와 같이 다른 곳에 데이터베이스를 넣는 것이 좋습니다.",
- 'config-oracle-def-ts' => '기본 테이블공간:',
- 'config-oracle-temp-ts' => '임시 테이블공간:',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => '미디어위키는 다음의 데이터베이스 시스템을 지원합니다:
-
-$1
-
-데이터베이스 시스템이 표시되지 않을 때 아래에 나열된 다음 지원을 활성화하려면 위의 링크된 지시에 따라 설치해볼 수 있습니다.',
- 'config-support-mysql' => '* $1은 미디어위키의 기본 대상으로 가장 잘 지원합니다. ([http://www.php.net/manual/en/mysql.installation.php MySQL을 지원하여 PHP를 컴파일하는 방법])',
- 'config-support-postgres' => '* $1은 MySQL의 대안으로 인기 있는 오픈 소스 데이터베이스 시스템입니다. ([http://www.php.net/manual/en/pgsql.installation.php PostgreSQL을 지원하여 PHP를 컴파일하는 방법]) 몇가지 사소한 해결하지 못한 버그가 있을 수 있으며, 이를 제작 환경에서 사용하지 않는 것이 좋습니다.',
- 'config-support-sqlite' => '* $1는 매우 잘 지원하는 가벼운 데이터베이스 시스템입니다. ([http://www.php.net/manual/en/pdo.installation.php SQLite를 지원하여 PHP를 컴파일하는 방법], PDO 사용)',
- 'config-support-oracle' => '* $1은 상용 엔터프라이스 데이터베이스입니다. ([http://www.php.net/manual/en/oci8.installation.php OCI8을 지원하여 PHP를 컴파일하는 방법])',
- 'config-header-mysql' => 'MySQL 설정',
- 'config-header-postgres' => 'PostgreSQL 설정',
- 'config-header-sqlite' => 'SQLite 설정',
- 'config-header-oracle' => 'Oracle 설정',
- 'config-invalid-db-type' => '잘못된 데이터베이스 종류',
- 'config-missing-db-name' => '"데이터베이스 이름"에 대한 값을 입력해야 합니다',
- 'config-missing-db-host' => '"데이터베이스 호스트"에 대한 값을 입력해야 합니다',
- 'config-missing-db-server-oracle' => '"데이터베이스 TNS"에 대한 값을 입력해야 합니다',
- 'config-invalid-db-server-oracle' => '"$1" 데이터베이스 TNS가 잘못됐습니다.
-"TNS Name"이나 "Easy Connect" 문자열 중 하나를 사용하세요 ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle 네이밍 메서드])',
- 'config-invalid-db-name' => '"$1" 데이터베이스 이름이 잘못되었습니다.
-ASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하세요.',
- 'config-invalid-db-prefix' => '"$1" 데이터베이스 접두어가 잘못됐습니다.
-ASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하세요.',
- 'config-connection-error' => '$1.
-
-호스트, 계정 이름과 비밀번호를 확인하고 다시 시도하세요.',
- 'config-invalid-schema' => '미디어위키 "$1"에 대한 스키마가 잘못됐습니다.
-ASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하세요.',
- 'config-db-sys-create-oracle' => '설치 프로그램은 새 계정을 만들기 위한 SYSDBA 계정만을 지원합니다.',
- 'config-db-sys-user-exists-oracle' => '"$1" 사용자 계정이 이미 존재합니다. SYSDBA는 새 계정을 만드는 데에만 사용할 수 있습니다!',
- 'config-postgres-old' => 'PostgreSQL $1 이상이 필요하나 $2(이)가 있습니다.',
- 'config-sqlite-name-help' => '위키를 식별하기 위한 이름을 선택하세요.
-공백이나 하이픈을 사용하지 마십시오.
-SQLite 데이터 파일 이름에 사용됩니다.',
- 'config-sqlite-parent-unwritable-group' => '<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며 웹 서버는 <code><nowiki>$2</nowiki></code> 상위 디렉토리에 쓸 수 없기 때문입니다.
-
-설치 프로그램은 웹 서버로 실행중인 사용자를 지정할 수 없습니다.
-계속하려면 웹 서버가 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.
-유닉스/리눅스 시스템에서의 수행:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => '<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며 웹 서버는 <code><nowiki>$2</nowiki></code> 상위 디렉토리에 쓸 수 없기 때문입니다.
-
-설치 프로그램은 웹 서버로 실행중인 사용자를 지정할 수 없습니다.
-계속하려면 웹 서버(와 기타!)가 전역으로 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.
-유닉스/리눅스 시스템에서의 수행:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => '"$1" 데이터 디렉토리를 만드는 중 오류가 났습니다.
-경로를 확인하고 다시 시도하세요.',
- 'config-sqlite-dir-unwritable' => '"$1" 디렉토리에 쓸 수 없습니다.
-웹 서버를 쓸 수 있도록 권한을 바꾸고 다시 시도하세요.',
- 'config-sqlite-connection-error' => '$1.
-
-호스트, 계정 이름과 비밀번호를 확인하고 다시 시도하세요.',
- 'config-sqlite-readonly' => '<code>$1</code> 파일은 쓸 수 없습니다.',
- 'config-sqlite-cant-create-db' => '<code>$1</code> 데이터베이스 파일을 만들 수 없습니다.',
- 'config-sqlite-fts3-downgrade' => 'PHP가 FTS3 지원이 없어졌습니다. 테이블을 다운그레이드하세요.',
- 'config-can-upgrade' => "이 데이터베이스에 미디어위키 테이블이 있습니다.
-미디어위키 $1(으)로 업그레이드하려면 '''계속'''을 클릭하세요.",
- 'config-upgrade-done' => "업그레이드가 완료되었습니다.
-
-이제 [$1 위키를 시작]할 수 있습니다.
-
-만약 <code>LocalSettings.php</code> 파일을 다시 만들기를 원하면 아래의 버튼을 클릭하세요.
-위키에 문제가 있지 않는 한 '''권장하지 않습니다'''.",
- 'config-upgrade-done-no-regenerate' => '업그레이드가 완료되었습니다.
-
-이제 [$1 위키를 시작]할 수 있습니다.',
- 'config-regenerate' => 'LocalSettings.php 다시 만들기 →',
- 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> 쿼리를 실패했습니다!',
- 'config-unknown-collation' => "'''경고:''' 데이터베이스가 인식하지 않는 정렬을 사용하고 있습니다.",
- 'config-db-web-account' => '웹 접근을 위한 데이터베이스 계정',
- 'config-db-web-help' => '위키의 일반적인 작업 중에 데이터베이스 서버에 연결하는 데 사용할 웹 서버에 대한 계정 이름과 비밀번호를 선택하세요.',
- 'config-db-web-account-same' => '설치를 위해 같은 계정 사용',
- 'config-db-web-create' => '이 계정이 아직 존재하지 않을 경우 계정 만들기',
- 'config-db-web-no-create-privs' => '설치를 위해 지정한 계정은 계정을 만들 수 있는 충분한 권한이 없습니다.
-여기서 지정한 계정은 이미 존재해야 합니다.',
- 'config-mysql-engine' => '스토리지 엔진:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''경고''': 미디어위키에 사용하지 않는 것이 좋은 MySQL에 대한 스토리지 엔진으로 MyISAM을 선택하였습니다. 이유는:
-* 테이블 잠금에 의해 간신히 동시성을 지원합니다
-* 다른 엔진보다 손상하는 경향이 있습니다
-* 미디어위키 코드베이스가 항상 정상적으로 MyISAM을 처리하지 않습니다
-
-MySQL 설치가 InnoDB를 지원한다면, 그 선택 대신에 InnoDB를 선택할 것을 매우 권장합니다.
-MySQL 설치가 InnoDB를 지원하지 않는다면, 아마도 업그레이드를 할 시간입니다.",
- 'config-mysql-only-myisam-dep' => "'''경고''': 미디어위키에 사용하지 않는 것이 좋은 MySQL에 대한 유일하게 사용할 수 있는 스토리지 엔진입니다. 이유는:
-* 테이블 잠금에 의해 간신히 동시성을 지원합니다
-* 다른 엔진보다 손상하는 경향이 있습니다
-* 미디어위키 코드베이스가 항상 정상적으로 MyISAM을 처리하지 않습니다
-
-MySQL 설치가 InnoDB를 지원하지 않으며, 아마도 업그레이드를 할 시간입니다.",
- 'config-mysql-engine-help' => "'''InnoDB'''는 동시적인 지원에 좋기 때문에 대부분 최고의 옵션입니다.
-
-'''MyISAM'''은 단일 사용자 또는 읽기 전용 설치에 빠를 수 있습니다.
-MyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실될 수 있습니다.",
- 'config-mysql-charset' => '데이터베이스 문자 집합:',
- 'config-mysql-binary' => '바이너리',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "'''바이너리 모드'''에서는 미디어위키는 바이너리 필드의 데이터베이스에 UTF-8 텍스트를 저장합니다.
-MySQL의 UTF-8 모드를 보다 더 효율적이고 유니코드 문자의 전체 범위를 사용할 수 있습니다.
-'''UTF-8 모드'''에서는 MySQL은 데이터를 설정하는 문자 집합을 알고 있기 때문에 적절하게 표현하고 변환할 수 있지만
-[//ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_%ED%8F%89%EB%A9%B4#.EA.B8.B0.EB.B3.B8_.EB.8B.A4.EA.B5.AD.EC.96.B4_.ED.8F.89.EB.A9.B4 기본 다국어 평면] 밖의 문자를 저장할 수 없습니다.",
- 'config-site-name' => '위키 이름:',
- 'config-site-name-help' => '브라우저 제목 표시줄과 다른 여러 곳에 나타납니다.',
- 'config-site-name-blank' => '사이트 이름을 입력하세요.',
- 'config-project-namespace' => '프로젝트 이름공간:',
- 'config-ns-generic' => '프로젝트',
- 'config-ns-site-name' => '위키 이름과 같은 이름: $1',
- 'config-ns-other' => '기타 (지정)',
- 'config-ns-other-default' => '내위키',
- 'config-project-namespace-help' => '위키백과의 예를 따라서, 많은 위키는 "\'\'\'프로젝트 이름공간\'\'\'"에 그들의 콘텐츠 페이지에서 그들의 정책 페이지는 별도로 보관합니다.
-이 이름공간에 있는 모든 페이지의 제목은 여기서 지정할 수 있는 특정 접두어로 시작합니다.
-보통 이 접두어는 위키의 이름에서 파생되지만, "#" 또는 ":"와 같은 특수 문자를 포함할 수 없습니다.',
- 'config-ns-invalid' => '특정 "<nowiki>$1</nowiki>" 이름공간이 잘못되었습니다.
-다른 프로젝트 이름공간을 지정하세요.',
- 'config-ns-conflict' => '특정 "<nowiki>$1</nowiki>" 이름공간이 기본 미디어위키 이름공간과 충돌합니다.
-다른 프로젝트 이름공간을 지정하세요.',
- 'config-admin-box' => '관리자 계정',
- 'config-admin-name' => '사용자 이름:',
- 'config-admin-password' => '비밀번호:',
- 'config-admin-password-confirm' => '비밀번호 확인:',
- 'config-admin-help' => '"홍길동"과 같이 여기에 원하는 사용자 이름을 입력하세요.
-위키에 로그인하는 데 사용되는 이름입니다.',
- 'config-admin-name-blank' => '관리자의 사용자 이름을 입력하세요.',
- 'config-admin-name-invalid' => '특정 "<nowiki>$1</nowiki>" 사용자 이름이 잘못되었습니다.
-다른 사용자 이름을 지정하세요.',
- 'config-admin-password-blank' => '관리자 계정의 비밀번호를 입력하세요.',
- 'config-admin-password-same' => '비밀번호는 사용자 이름과 같아서는 안 됩니다.',
- 'config-admin-password-mismatch' => '입력한 비밀번호 두 개가 일치하지 않습니다.',
- 'config-admin-email' => '이메일 주소:',
- 'config-admin-email-help' => '위키의 다른 사용자로부터 이메일을 전달받거나 비밀번호를 재설정하고 주시문서 목록에 대한 바뀜 알림을 받기 위해 여기에 이메일 주소를 입력하세요. 이 필드를 비워 둘 수 있습니다.',
- 'config-admin-error-user' => '"<nowiki>$1</nowiki>" 이름의 관리자를 만드는 중 내부 오류가 발생했습니다.',
- 'config-admin-error-password' => '"<nowiki>$1</nowiki>" 관리자의 비밀번호를 설정하는 중 내부 오류가 발생했습니다: <pre>$2</pre>',
- 'config-admin-error-bademail' => '이메일 주소를 잘못 입력하였습니다.',
- 'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 배포 발표 메일링 리스트]에 가입합니다.',
- 'config-subscribe-help' => '중요한 보안 알림을 포함한 배포 알림에 대해 사용되는 로우 볼륨 메일링 리스트입니다.
-이 리스트를 구독하고 나서 새 버전이 나올 때 미디어위키 설치를 업데이트하십시오.',
- 'config-subscribe-noemail' => '이메일 주소를 제공하지 않고 배포 발표 메일링 리스트에 가입하려 합니다.
-메일링 리스트에 가입하고자 할 경우 이메일 주소를 제공하세요.',
- 'config-almost-done' => '거의 다 완료했습니다!
-이제 남은 설정을 생략하고 지금 바로 위키를 설치할 수 있습니다.',
- 'config-optional-continue' => '더 많은 질문을 물어보세요.',
- 'config-optional-skip' => '지겨워요, 그냥 위키를 설치할래요.',
- 'config-profile' => '사용자 권한 프로필:',
- 'config-profile-wiki' => '열린 위키',
- 'config-profile-no-anon' => '계정 만들기 필요',
- 'config-profile-fishbowl' => '승인된 편집자만',
- 'config-profile-private' => '비공개 위키',
- 'config-profile-help' => "위키는 많은 사람들이 가능한 한 편집할 수 있도록 하면 가장 뛰어난 역할을 합니다.
-미디어위키에서는 최근 바뀜을 검토하기 쉽고, 선하거나 악의적인 사용자의 어떠한 손실을 되돌리는 것이 쉽습니다.
-
-그러나 많은 사람이 미디어위키는 다양한 역할로 유용하지만, 때로는 모든 사람에게 위키 방식의 장점을 설득하기 쉽지 않을 지도 모릅니다.
-그래서 선택할 수 있습니다.
-
-'''{{int:config-profile-wiki}}''' 모델은 로그인하지 않고도 누구나 편집할 수 있습니다.
-'''{{int:config-profile-no-anon}}'''인 위키는 각 편집에 추가적으로 강한 책임을 제공하지만, 부담 없는 기여를 저해할 수도 있습니다.
-
-'''{{int:config-profile-fishbowl}}''' 시나리오는 승인된 사용자만 편집할 수 있지만, 대중은 역사를 포함하여 문서를 볼 수 있습니다.
-'''{{int:config-profile-private}}'''는 승인된 사용자만 문서를 볼 수 있으며 해당 그룹을 편집할 수 있습니다.
-
-더 복잡한 사용자 권한을 설정은 설치한 후 사용할 수 있으며 [//www.mediawiki.org/wiki/Manual:User_rights 관련 설명서 항목]을 참고하세요.",
- 'config-license' => '저작권 및 라이선스:',
- 'config-license-none' => '라이선스 바닥글 없음',
- 'config-license-cc-by-sa' => '크리에이티브 커먼즈 저작자표시-동일조건변경허락',
- 'config-license-cc-by' => '크리에이티브 커먼즈 저작자표시',
- 'config-license-cc-by-nc-sa' => '크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락',
- 'config-license-cc-0' => '크리에이티브 커먼즈 제로 (퍼블릭 도메인)',
- 'config-license-gfdl' => 'GNU 자유 문서 사용 허가서 1.3 이상',
- 'config-license-pd' => '퍼블릭 도메인',
- 'config-license-cc-choose' => '다른 크리에이티브 커먼즈 라이선스 선택',
- 'config-license-help' => "많은 공개 위키는 모든 기여를 [http://freedomdefined.org/Definition 자유 라이선스]에 따라 넣습니다.
-이렇게 하면 커뮤니티 소유권의 이해를 할 수 있도록 하고 장기적인 기여를 장려합니다.
-일반적으로 개인 또는 회사 위키에 대해서는 필요하지 않습니다.
-
-위키백과의 텍스트를 사용할 수 있도록 하고 위키백과가 위키에서 복사한 텍스트를 사용할 수 있도록 원한다면 '''크리에이티브 커먼즈 저작자표시-동일조건변경허락'''으로 선택해야 합니다.
-
-위키백과는 이전에 GNU 자유 문서 사용 허가서를 사용했습니다.
-GFDL은 유효한 라이선스이지만 내용을 이해하기 어렵습니다.
-GFDL에 따라 사용이 허가된 내용을 재사용하는 것도 어렵습니다.",
- 'config-email-settings' => '이메일 설정',
- 'config-enable-email' => '발신 이메일 활성화',
- 'config-enable-email-help' => '이메일을 작동하려면 [http://www.php.net/manual/en/mail.configuration.php PHP의 메일 설정]을 올바르게 설정해야 합니다.
-이메일 기능을 사용하지 않으려면 이를 비활성화할 수 있습니다.',
- 'config-email-user' => '사용자와 사용자 간 이메일 활성화',
- 'config-email-user-help' => '환경 설정에서 활성화한 경우 모든 사용자가 이메일을 서로 보내도록 활성화합니다.',
- 'config-email-usertalk' => '사용자 토론 문서 알림 활성화',
- 'config-email-usertalk-help' => '환경 설정에서 활성화한 경우 사용자는 사용자 토론 문서의 바뀜 알림을 받도록 활성화합니다.',
- 'config-email-watchlist' => '주시문서 목록 알림 활성화',
- 'config-email-watchlist-help' => '환경 설정에서 활성화한 경우 사용자가 주시한 문서에 대한 알림을 받도록 활성화합니다.',
- 'config-email-auth' => '이메일 인증 활성화',
- 'config-email-auth-help' => "이 설정이 활성화되어 있으면 사용자는 이메일 주소를 설정하거나 바꿀 때마다 링크를 사용하여 이메일 주소를 확인해야 합니다.
-인증된 이메일 주소만 다른 사용자로부터의 이메일이나 바뀜 알림 이메일을 받을 수 있습니다.
-이메일 기능의 남용 가능성이 있기 때문에 이 옵션을 설정하는 것은 공개 위키에서 '''권장'''합니다.",
- 'config-email-sender' => '반송 이메일 주소',
- 'config-email-sender-help' => '발신한 이메일에 대한 반송 주소로 사용할 이메일 주소를 입력하세요.
-반송할 때 보내는 주소입니다.
-대부분의 메일 서버는 적어도 도메인 이름 부분은 유효합니다.',
- 'config-upload-settings' => '그림과 파일 올리기',
- 'config-upload-enable' => '파일 올리기 활성화',
- 'config-upload-help' => '파일 올리기는 서버에 잠재적인 보안 위험에 쉽게 노출될 수 있습니다.
-자세한 내용은 매뉴얼의 [//www.mediawiki.org/wiki/Manual:Security 보안 문단]을 참고하세요.
-
-파일 올리기를 활성화하려면 미디어위키의 루트 디렉토리에 있는 <code>images</code> 하위 디렉토리에서 웹 서버가 기록할 수 있도록 모드를 바꿉니다.
-그 다음 이 옵션을 활성화합니다.',
- 'config-upload-deleted' => '삭제된 파일에 대한 디렉터리:',
- 'config-upload-deleted-help' => '삭제된 파일을 보관할 디렉토리를 선택하세요.
-이상적으로 웹에서 접근할 수 없게 해야 합니다.',
- 'config-logo' => '로고 URL:',
- 'config-logo-help' => '미디어위키의 기본 스킨은 사이드바 메뉴 위에 135×160 픽셀의 로고의 공간을 포함하고 있습니다.
-적당한 크기로 그림을 올리고 여기에 URL을 입력하세요.
-
-로고가 상대적인 경로에 있으면 <code>$wgStylePath</code>나 <code>$wgScriptPath</code>를 사용할 수 있습니다.
-
-로고 사용을 원하지 않으면 이 상자를 비우세요.',
- 'config-instantcommons' => '인스턴트 공용 활성화',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons 인스턴트 공용]은 [//commons.wikimedia.org/ 위키미디어 공용] 사이트에서 찾을 수 있는 그림, 소리 및 다른 미디어를 위키에서 사용할 수 있도록 하는 기능입니다.
-이렇게 하려면 미디어위키가 인터넷에 접근해야합니다.
-
-위키미디어 공용 외에 기타 위키를 설정하는 방법에 대한 지침을 포함한 기능에 대한 자세한 내용은 [//mediawiki.org/wiki/Manual:$wgForeignFileRepos 매뉴얼]을 참고하세요.',
- 'config-cc-error' => '크리에이티브 커먼즈 라이선스 선택기에 결과가 없습니다.
-수동으로 라이선스 이름을 입력하세요.',
- 'config-cc-again' => '다시 선택...',
- 'config-cc-not-chosen' => '원하는 크리에이티브 커먼즈 라이선스를 선택하고 "진행"을 클릭하세요.',
- 'config-advanced-settings' => '고급 설정',
- 'config-cache-options' => '개체 캐싱을 위한 설정:',
- 'config-cache-help' => '개체 캐싱은 자주 사용하는 데이터를 캐싱하여 미디어위키의 속도를 개선하는 데 사용합니다.
-큰 사이트의 규모에는 이를 많이 사용하도록 권장하고 있으며, 소규모 사이트들도 물론 이익을 볼 수 있습니다.',
- 'config-cache-none' => '캐시하지 않음 (기능적으로는 삭제되지 않지만 큰 위키 사이트에 속도에 영향을 받을 수 있습니다)',
- 'config-cache-accel' => 'PHP 개체 캐싱 (APC, XCache 또는 WinCache)',
- 'config-cache-memcached' => 'Memcached 사용 (추가적인 설치와 설정이 필요합니다)',
- 'config-memcached-servers' => 'Memcached 서버:',
- 'config-memcached-help' => 'Memcached의 사용하기 위한 IP 주소 목록입니다.
-한 줄에 하나씩 사용할 포트를 지정해야 합니다. 예를 들어 :
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => '캐시 종류로 Memcached를 선택했지만 어떠한 서버도 지정하지 않았습니다.',
- 'config-memcache-badip' => 'Memcached에 대해 잘못된 IP 주소를 입력했습니다: $1.',
- 'config-memcache-noport' => 'Memcached 서버에 사용할 포트를 지정하지 않았습니다: $1.
-포트를 모를 경우 기본값은 11211입니다.',
- 'config-memcache-badport' => 'Memcached 포트 번호는 $1(와)과 $2 사이여야 합니다.',
- 'config-extensions' => '확장 기능',
- 'config-extensions-help' => '위에 나열된 확장 기능이 <code>./extensions</code>에서 발견되었습니다.
-
-추가적인 설정이 필요할 수 있습니다만 지금 활성화시킬 수 있습니다.',
- 'config-install-alreadydone' => "'''경고:''' 이미 미디어위키를 설치했고 다시 설치하려고 합니다.
-다음 페이지에서 진행하세요.",
- 'config-install-begin' => '"{{int:config-continue}}"을 누르면 미디어위키의 설치를 시작합니다.
-그래도 바꾸는 것을 원한다면 "{{int:config-back}}"를 누르세요.',
- 'config-install-step-done' => '완료',
- 'config-install-step-failed' => '실패',
- 'config-install-extensions' => '확장 기능을 포함하는 중',
- 'config-install-database' => '데이터베이스를 설정하는 중',
- 'config-install-schema' => '스키마를 만드는 중',
- 'config-install-pg-schema-not-exist' => 'PostgreSQL 스키마가 존재하지 않습니다.',
- 'config-install-pg-schema-failed' => '테이블을 만드는 데 실패했습니다.
-"$2" 스키마에 쓸 수 있는 "$1" 사용자가 있는지 확인하세요.',
- 'config-install-pg-commit' => '바뀐 사항을 적용하는 중',
- 'config-install-pg-plpgsql' => 'PL/pgSQL 언어에 대해 확인하는 중',
- 'config-pg-no-plpgsql' => '$1 데이터베이스에 PL/pgSQL 언어를 설치해야 합니다',
- 'config-pg-no-create-privs' => '설치를 위한 지정한 계정에 계정을 만드는 데 충분한 권한이 없습니다,',
- 'config-pg-not-in-role' => '웹 사용자에 대해 지정한 계정이 이미 존재합니다.
-설치에 대한 지정한 사용자는 슈퍼 사용자가 아니고, 그것은 웹 사용자의 역할의 구성원이 아니며, 그래서 웹 사용자가 소유한 개체를 만들 수 없습니다.
-
-현재 미디어위키는 테이블을 웹 사용자가 소유해야 합니다. 다른 웹 계정 이름을 지정하거나 "뒤로"를 클릭하고 적절한 권한의 설치할 사용자를 지정하세요.',
- 'config-install-user' => '데이터베이스 사용자를 만드는 중',
- 'config-install-user-alreadyexists' => '"$1" 사용자가 이미 존재합니다',
- 'config-install-user-create-failed' => '"$1" 사용자 만드는 중 실패: $2',
- 'config-install-user-grant-failed' => '"$1" 사용자에 대한 권한 부여 실패: $2',
- 'config-install-user-missing' => '지정한 "$1" 사용자가 존재하지 않습니다.',
- 'config-install-user-missing-create' => '지정된 "$1" 사용자가 존재하지 않습니다.
-이를 만드는 것을 원하면 아래의 "계정 만들기" 확인 상자를 클릭하세요.',
- 'config-install-tables' => '테이블을 만드는 중',
- 'config-install-tables-exist' => "'''경고''': 미디어위키 테이블이 이미 있는 것 같습니다.
-테이블 만들기를 생략합니다.",
- 'config-install-tables-failed' => "'''오류''': 다음 오류와 함께 테이블 만들기에 실패했습니다: $1",
- 'config-install-interwiki' => '기본 인터위키 테이블을 채우는 중',
- 'config-install-interwiki-list' => '<code>interwiki.list</code> 파일을 불러올 수 없습니다.',
- 'config-install-interwiki-exists' => "'''경고''': 인터위키 테이블이 이미 항목을 갖고 있는 것 같습니다.
-기본 목록으로 넘어갑니다.",
- 'config-install-stats' => '통계를 초기화하는 중',
- 'config-install-keys' => '보안 키를 만드는 중',
- 'config-insecure-keys' => "'''경고:''' 설치 중에 생성한 {{PLURAL:$2|보안 키}} ($1)를 설치하는 동안 완전히 안전하지 {{PLURAL:$2|않습니다}}. 직접 바꾸기를 고려하세요.",
- 'config-install-sysop' => '관리자 사용자 계정을 만드는 중',
- 'config-install-subscribe-fail' => '미디어위키 발표를 구독할 수 없습니다: $1',
- 'config-install-subscribe-notpossible' => 'cURL이 설치되지 않았고 allow_url_fopen를 사용할 수 없습니다.',
- 'config-install-mainpage' => '기본 콘텐츠로 대문을 만드는 중',
- 'config-install-extension-tables' => '활성화된 확장 기능을 위한 테이블을 만드는 중',
- 'config-install-mainpage-failed' => '대문을 삽입할 수 없습니다: $1',
- 'config-install-done' => "'''축하합니다!'''
-미디어위키가 성공적으로 설치되었습니다.
-
-설치 프로그램이 <code>LocalSettings.php</code> 파일을 만들었습니다.
-모든 설정이 포함되어 있습니다.
-
-파일을 다운로드하여 위키 설치의 거점에 넣어야 합니다. (index.php와 같은 디렉터리) 다운로드가 자동으로 시작됩니다.
-
-다운로드가 제공되지 않을 경우나 그것을 취소한 경우에는 아래의 링크를 클릭하여 다운로드를 다시 시작할 수 있습니다:
-
-$3
-
-'''참고:''' 이 생성한 설정 파일을 다운로드하지 않고 설치를 끝내면 이 파일은 나중에 사용할 수 없습니다.
-
-완료되었으면 '''[$2 위키에 들어갈 수 있습니다]'''.",
- 'config-download-localsettings' => '<code>LocalSettings.php</code> 다운로드',
- 'config-help' => '도움말',
- 'config-nofile' => '"$1" 파일을 찾을 수 없습니다. 이미 삭제되었나요?',
- 'config-extension-link' => '당신의 위키가 [//www.mediawiki.org/wiki/Manual:Extensions 확장 기능]을 지원한다는 것을 알고 계십니까?
-
-전체 확장 기능의 목록을 확인하려면 [//www.mediawiki.org/wiki/Category:Extensions_by_category 분류별 확장 기능]이나 [//www.mediawiki.org/wiki/Extension_Matrix 확장 기능 표]를 찾아보실 수 있습니다.',
- 'mainpagetext' => "'''미디어위키가 성공적으로 설치되었습니다.'''",
- 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents 이곳]에서 위키 소프트웨어에 대한 정보를 얻을 수 있습니다.
-
-== 시작하기 ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings 설정하기 목록]
-* [//www.mediawiki.org/wiki/Manual:FAQ 미디어위키 FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 미디어위키 발표 메일링 리스트]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources 내 언어로 미디어위키 지역화]',
-);
-
-/** Karachay-Balkar (къарачай-малкъар)
- * @author Iltever
- */
-$messages['krc'] = array(
- 'mainpagetext' => "'''«MediaWiki» тыйыншлы салынды.'''",
- 'mainpagedocfooter' => "Бу вики бла къалай ишлерге ангылатхан информацияны [//meta.wikimedia.org/wiki/Help:Contents_User's_Guide къошулуучугъа юретиуде] табаргъа боллукъду.
-
-== Файдалы ресурсла ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings тюрлендириулени списогу (ингил.)];
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-ни юсюнден кёб берилген соруула];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-ни джангы версиясыны чыкъгъанын билдириу письмола].", # Fuzzy
-);
-
-/** Colognian (Ripoarisch)
- * @author Mormegil
- * @author Purodha
- * @author Reedy
- */
-$messages['ksh'] = array(
- 'config-desc' => 'Et Projramm för Mediwiki opzesäze.',
- 'config-title' => 'MediaWiki $1 opsäze',
- 'config-information' => 'Enfomazjuhn',
- 'config-localsettings-upgrade' => 'De Dattei <code lang="en"><code>LocalSettings.php</code></code> es ald doh.
-De Projramme vum Wiki künne op der neußte Shtand jebraat wääde:
-Donn doför dä Wäät vum <code lang="en">$wgUpgradeKey</code> en dat heh Feld enjävve.
-Do fenggs_et en dä Dattei <code lang="en"><code>LocalSettings.php</code></code> om ẞööver.',
- 'config-localsettings-cli-upgrade' => 'En Dattei <code lang="en"><code>LocalSettings.php</code></code> es jefonge woode.
-Öm et Wiki_Projramm op ene neue Shtand ze bränge, donn <code lang="en">update.php</code> oproofe.',
- 'config-localsettings-key' => 'Der Schlößel för et Projramm op ene neue Schtand ze bränge:',
- 'config-localsettings-badkey' => 'Dinge Schlößel paß nit.',
- 'config-upgrade-key-missing' => 'Mer han jefonge, dat MediaWiki ald enschtalleed es.
-Üm de Projramme un Daate o der neue Schtand bränge ze künne, dunn aan et Engk vun dä Dattei <code lang="en"><code>LocalSettings.php</code></code> op dämm ẞööver:
-
-$1
-
-aanhange.',
- 'config-localsettings-incomplete' => 'Mer han en Dattei <code lang="en"><code>LocalSettings.php</code>:</code> jefonge, ävver di schingk nit kumplätt ze sin.
-De Varijable <code lang="en">$1</code> es nit jesatz.
-Bes esu joot, un donn di Dattei esu aanpaße, dat se jesaz ea, un dann donn op „{{int:config-continue}}“ klecke.',
- 'config-localsettings-connection-error' => 'Ene Fähler es opjetrodde wi mer en Verbendung noh de Datebangk opmaache wullte met dä Enshtellunge uß dä Dattei <code lang="en">LocalSettings</code> udder uß dä Dattei <code lang="en">LocalSettings</code> un et hät nit jeflupp. Bes esu joot un dat repareere un versöhg et dann norr_ens.
-
-$1',
- 'config-session-error' => 'Ene Fähler es opjetrodde beim Aanmelde för en Sezung: $1',
- 'config-session-expired' => 'De Daate för Ding Setzung sinn wall övverholld of afjeloufe.
-De Setzungunge sin esu enjeshtallt, nit mieh wi $1 ze doore.
-Dat kanns De verlängere, endämm dat De de <code lang="en">session.gc_maxlifetime</code> en dä Dattei <code>php.ini</code> jrüüßer määß.
-Don dat Projramm för et Opsäze norr_ens aanschmiiße.',
- 'config-no-session' => 'De Daate för Ding Setzung sinn verschött jejange.
-Donn en dä Dattei <code>php.ini</code> nohloore, ov dä <code lang="en">session.save_path</code> op e zopaß Verzeijschneß zeisch.',
- 'config-your-language' => 'Ding Schprooch:',
- 'config-your-language-help' => 'Donn heh di Shprooch ußsöhke, di dat Enshtallzjuhnsprojramm kalle sull.',
- 'config-wiki-language' => 'Dem Wiki sing Shprooch:',
- 'config-wiki-language-help' => 'Donn heh di Shprooch ußsöhke, di et Wiki shtandattmääßesch kalle sull.',
- 'config-back' => '← Retuur',
- 'config-continue' => 'Wigger →',
- 'config-page-language' => 'Schprooch',
- 'config-page-welcome' => 'Wellkumme beim MediaWiki!',
- 'config-page-dbconnect' => 'Met dä Daatebangk Verbenge',
- 'config-page-upgrade' => 'En Inshtallzjuhn op der neuste Shtand bränge',
- 'config-page-dbsettings' => 'Parrameeter för de Daatebangk',
- 'config-page-name' => 'Name',
- 'config-page-options' => 'Ennställunge',
- 'config-page-install' => 'Opsäzze',
- 'config-page-complete' => 'Fäädesch!',
- 'config-page-restart' => 'Et Opsäze norr_ens neu aanfange',
- 'config-page-readme' => 'Donn mesch lässe! (<i lang="en">read me</i>)',
- 'config-page-releasenotes' => 'Henwies för heh di Version vum Projramm (<i lang="en">Release notes</i>)',
- 'config-page-copying' => 'Ben aam Kopeere',
- 'config-page-upgradedoc' => 'Ben op der neuste Stand aam bränge',
- 'config-page-existingwiki' => 'Mer han ald e Wiki!',
- 'config-help-restart' => 'Wells De all Ding enjejovve Sachee fottjeschmesse han, un dä janze Vörjang vun fürre aan neu aanfange?',
- 'config-restart' => 'Joh, neu aanfange!',
- 'config-welcome' => '=== Ömjevong Prööfe ===
-Mer maache en Aanzal jrundlääje Prövunge, öm erus ze fenge, ov di Ömjevong heh paß, för Mediawiki opzesäze.
-Wann de Hölp bem Opsäze bruchs, donn wigger ssare, wat erus kohm, wat heh shteiht.',
- 'config-copyright' => "=== Urhävverrääsch un Lizänzbedengunge ===
-
-\$1
-
-Dat Projramm heh es frei, mer kann et wiggerjävve un verdeijle un och verändere ungger dä Bedengunge vun de GNU <i lang=\"en\">General Public License</i> (Alljemeine öffentlesche Lizänz) wi se vun de <i lang=\"en\">Free Software Foundation</i> (de Shteftung för frei Projramme) veröffentlesch woode es. Dobei kanns De Der de Version 2 vun dä Lizanz ußsöhke, udder jeede Version donoh, wi et Der jefällt.
-
-Dat Projramm weed wigger jejovve met dä Hoffnung, dat et jät nöz, ävver '''ohne Jarrantie''', sujaa ohne de onußjeshproche Jarantie, '''verkoufbaa''' ze sin, udder '''för öhnds_ene beshtemmpte Zweck ze bruche''' ze sin.
-Liß de GNU <i lang=\"en\">General Public License</i> sellver, öm mieh ze erfahre.
-
-Do sullts en <doclink href=Copying>Kopie vun dä alljemene öffentlesche Lizänz vun dä GNU</doclink> (<i lang=\"en\">GNU General Public License</i>) zosamme met heh däm Projramm krääje han. Wann dat nit esu es, schrief aan de <i lang=\"en\">Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA</i>. udder [http://www.gnu.org/copyleft/gpl.html liß se online övver et Internet].",
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki sing Hompäjdsch]
-* [//www.mediawiki.org/wiki/Help:Contents Handbooch för Aanwender]
-* [//www.mediawiki.org/wiki/Manual:Contents Handbooch för Administratore un Wiki_Köbesse]
-* [//www.mediawiki.org/wiki/Manual:FAQ Öff jeshtallte Froore met Antwoote]
-----
-* <doclink href=Readme>Liß Mesch! (<i lang="en">Read me</i>)</doclink>
-* <doclink href=ReleaseNotes><i lang="en">Release notes</i> Övver heh di Projrammversion</doclink>
-* <doclink href=Copying><i lang="en">Copying</i> — Lizänzbeshtemmunge</doclink>
-* <doclink href=UpgradeDoc><i lang="en">Upgrading</i> — Ob en neu Projrammversion jonn</doclink>',
- 'config-env-good' => 'De Ömjävung es jeprööf.
-Do kanns MediaWiki opsäze.',
- 'config-env-bad' => 'De Ömjävung es jeprööf.
-Do kanns MediaWiki nit opsäze.',
- 'config-env-php' => 'PHP $1 es doh.',
- 'config-env-php-toolow' => 'PHP $1 es enshtalleert.
-Ävver MediaWiki bruch PHP $2 udder hühter.',
- 'config-unicode-using-utf8' => 'För et <i lang="en">Unicode</i>-Nommaliseere dom_mer däm <i lang="en">Brion Vibber</i> sing Projramm <code lang="en">utf8_normalize.so</code> nämme.',
- 'config-unicode-using-intl' => 'För et <i lang="en">Unicode</i>-Nommaliseere dom_mer dä [http://pecl.php.net/intl Zohsaz <code lang="en">intl</code> uss em <code lang="en">PECL</code>] nämme.',
- 'config-unicode-pure-php-warning' => '\'\'\'Opjepaß:\'\'\' Mer kunnte dä [http://pecl.php.net/intl Zohsaz <code lang="en">intl</code> uss em <code lang="en">PECL</code>] för et <i lang="en">Unicode</i>-Nommaliseere nit fenge. Dröm nämme mer dat eijfache, ävver ärsh lahme, <i lang="en">PHP</i>-Projrammshtöck doför.
-För jruuße Wikis met vill Metmaachere doht Üsch die Sigg övver et [//www.mediawiki.org/wiki/Unicode_normalization_considerations <i lang="en">Unicode</i>-Nommaliseere] (es op Änglesch) aanloore.',
- 'config-unicode-update-warning' => "'''Opjepaß:''' Dat Projramm för der <i lang=\"en\">Unicode</i> zo normaliseere boud em Momang op en ählter Version vun dä Bibliothek vum [http://site.icu-project.org/ ICU-Projäk] op.
-Doht di [//www.mediawiki.org/wiki/Unicode_normalization_considerations op der neuste Shtand bränge], wann auf dat Wiki em Äänz <i lang=\"en\">Unicode</i> bruche sull.",
- 'config-no-db' => 'Mer kunnte kei zopaß Daatebangk-Driiverprojamm fenge.
-Mer bruche e Daatebangk-Driiverprojamm för PHP. Dat moß enjeresht wääde.
-Mer künne met heh dä Daatebangke ömjonn: $1.
-
-Wann De nit om eijene Rääshner bes, moß De Dinge <i lang="en">provider</i> bedde, dat hä Der ene zopaß Driiver enresht.
-Wann de PHP sellver övversaz häs, donn e Zohjangsprjramm för en Daatebangk enbenge, för e Beishpell met: <code lang="en">./configure --with-mysql</code> op ene <i lang="en">command shell</i>.
-Wann De PHP uss enem <i lang="en">Debian</i> udder <i lang="en">Ubuntu</i> Pakätt enjeresht häs, moß De dann och noch et <code lang="en">php5-mysql</code> op Dinge Räschner bränge.',
- 'config-outdated-sqlite' => '\'\'\'Opjepaß:\'\'\' <i lang="en">SQLite</i> $1 es enschtaleert. Avver MediaWiki bruch <i lang="en">SQLite</i> $2 udder hühter. <i lang="en">SQLite</i> kann dröm nit enjesaz wääde.',
- 'config-no-fts3' => "'''Opjepaß:''' De Projramme vum <i lang=\"en\">SQLite</i> sin der ohne et [//sqlite.org/fts3.html FTS3-Modul] övversaz, dröm wääde de Funxjohne för et Söhke fähle.",
- 'config-register-globals' => "'''Opjepaß:''' dem PHP singe Schallder <code lang=\"en\">[http://php.net/register_globals register_globals]</code> es enjeschalldt.
-'''Donn dä ußmaache, wann De kann.'''
-MediaWiki löp och esu, dä künnt ävver Sesherheitslöcke opmaache, di mer noch nit jefonge un eruß jemaat hät.",
- 'config-magic-quotes-runtime' => "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]</code> es enjeschalldt.
-Dä määt enjejovve Daate kapott, un doh draan kam_mer dann nix mieh repareere.
-Domet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.
-Dat heiß, mer moß en affschallde, söns jeiht nix.",
- 'config-magic-quotes-sybase' => "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> es enjeschalldt.
-Dä määt enjejovve Daate kapott, un doh draan kam_mer dann nix mieh repareere.
-Domet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.
-Dat heiß, mer moß en affschallde, söns jeiht nix.",
- 'config-mbstring' => "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> es enjeschalldt.
-Dat sorresch för Fähler un kann enjejovve Daate esu kapott maach, dat doh draan nix mieh ze repareere es.
-Domet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.
-Dat heiß, mer moß en affschallde, söns jeiht nix.",
- 'config-ze1' => "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]</code> es enjeschalldt.
-Dat sorresch för schräcklejje Fähler em MediaWiki.
-Dat kam_mer domet nit ennreeshte un och nit loufe lohße.
-Dat heiß, mer moß en affschallde, söns jeiht nix.",
- 'config-safe-mode' => "'''Opjepaß:''' Dem PHP singe <code lang=\"en\">[http://www.php.net/features.safe-mode safe mode]</code> es aanjeschalldt. Dat kann Ärjer maache, besönders beim Datteie Huhlaade bei de Ongershtözung för <code lang=\"en\">math</code>-Befähle.",
- 'config-xml-bad' => 'Dem PHP sing XML-Modul es nit ze fenge.
-MediaWiki bruch Funxjohne en däm Modul un deiht et esu nit.
-Wann De <i lang="en">Mandrake</i> aam loufehäs, donn dat Pakätt <code lang="en">php-xml</code> enstalleere.',
- 'config-pcre' => 'Dem PHP sing Modul för <i lang="en">PCRE</i> schingk ze fähle.
-MediaWiki deiht et nit der ohne de Funxjohne för de rejolähre Ußdrök vun dä Zoot, wi <i lang="en">Perl</i> se kännt.',
- '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.
-MediaWiki 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.
-Dat es wall ze winnisch.
-Et Enreeschte kunnt doh draan kappott jon!",
- 'config-ctype' => "'''Fähler:''' <i lang=\"en\">PHP</i> moß met dä Ongerschtözong för der [http://www.php.net/manual/en/ctype.installation.php <code lang=\"en\">Ctype</code> Zohsaz] övversaz woode sin.",
- 'config-xcache' => 'Dä <code lang="en">[http://xcache.lighttpd.net/ XCache]</code> es ennjeresht.',
- 'config-apc' => 'Dä <code lang="en">[http://www.php.net/apc APC]</code> es ennjeresht.',
- 'config-wincache' => 'Dä <code lang="en">[http://www.iis.net/download/WinCacheForPhp WinCache]</code> es ennjeresht.',
- 'config-no-cache' => '\'\'\'Opjepaß:\'\'\' Mer kunnte dä <code lang="en">[http://www.php.net/apc APC]</code>, dä <code lang="en">[http://xcache.lighttpd.net/ XCache]</code> un dä <code lang="en">[http://www.iis.net/download/WinCacheForPhp WinCache]</code> nit fenge.
-Et <i lang="en">object caching</i> es nit müjjelesh un ußjeschalldt.',
- 'config-mod-security' => "'''Opjepaß''': Dinge Webßööver hät <code lang=\"en\">[http://modsecurity.org/ mod_security]</code> enjeschalldt. Wann doh derbei en Enschtällong nit janz akeraat paßß, dann kann et goot sin, dat mer Probleme met MeedijaWiki un oc met ander Projramme kritt, die zohlööt, dat vun ußerhallef öhndsene Krohm op dä Webßööver jebraat wääde künnt.Beloor Der di Sigg <code lang=\"en\">[http://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 bemerke 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-git-bad' => 'Dat Väsjohnsverwalldongsprojamm <i lang="en">Git</i> ham_mer nit jefonge.',
- 'config-imagemagick' => 'Mer han <i lang="en">ImageMagick</i> jefonge: <code>$1</code>.
-Et Ömrääschne en Minni-Beldsche weed müjjelesch sin, wann De et Belder Huhlaade zohlöhß.',
- 'config-gd' => 'Mer han de ennjeboute GD-Jrafik-Projramm-Biblijotheek jefonge.
-Et Ömrääschne en Minni-Beldsche weed müjjelesch sin, wann De et Belder Huhlaade zohlöhß.',
- 'config-no-scaling' => 'Mer han weeder de GD-Jrafik-Projramm-Biblijotheek, noch <i lang="en">ImageMagick</i> jefonge.
-Et Ömrääschne en Minni-Beldsche weed ußjeschalldt.',
- 'config-no-uri' => "'''Fähler:''' Mer kunnte der aktoälle <i lang=\"en\">URI</i> nit erusfenge.
-Et Enreeschte es domet heh aam Engk.",
- 'config-no-cli-uri' => "'''Opjepaß''': <code lang=\"en\">--scriptpath</code> es nit aanjejovve, mer nämme der Schtandatt: <code>\$1</code>.",
- 'config-using-server' => 'Mer nämmen dem ẞööver singe Name: „<nowiki>$1</nowiki>“.',
- 'config-using-uri' => 'Mer nämmen dem ẞööver singe <i lang="en">URL</i>: „<nowiki>$1$2</nowiki>“.',
- 'config-uploads-not-safe' => "'''Opjepaß:''' Uß däm jewöhnlijje Verzeichnes för de huhjelaade Datteie, dat es <code>$1</code>, künnte öhnzwällsche Skrepte un Projramme ußjeföhrt wääde. Och wann MediaWiki de huhjelaade Datteie prööf, dat kein bekannte Risike dren sin, sullt mer doch dat [//www.mediawiki.org/wiki/Manual:Security#Upload_security Sesherheitsloch] zoh maache, ih dat mer et Dattei Huhlaade zohlöht.",
- 'config-no-cli-uploads-check' => "'''Opjepaß''': <code>\$1</code> es dat Schtandatt-Verzeijschneß för et Datteije-Huhlaade. Beim Opsäze met <abbr lang=\"en\" title=\"Call Level Interface\">CLI</abbr> donn mer ävver nit övverpröhve, dat dat jeschöz es dojääje, dat Skrepte vun doh loufe künne, di mer nit loufe han well.",
- 'config-brokenlibxml' => 'Op Dingem Rääschner loufe Versione vun PHP un <code lang="en">libxml2</code> zosamme, di ävver nit zosamme paßße, un de Daate em MediaWiki un ander Web_Aanwändunge [//bugs.php.net/bug.php?id=45996 bug kapott maache].
-Jangk op PHP 5.2.9 udder dohnoh un op <code lang="en">libxml2</code> 2.7.3 udder dohnoh.
-Heh jeihd et nit wigger.',
- 'config-using531' => 'MediaWiki läuf nit met PHP $1 zosamme wääje enem [//bugs.php.net/bug.php?id=50394 Fähler em Zosammehang met Parrameetere för <code lang="en">__call()</code>].
-Jangk op de Version 5.3.2 vum <i lang="en">PHP</i> ov dohnoh, udder op de Version 5.3.0 udder dovöör, öm dat Problem ze ömjonn.
-Heh jeiht et nit wigger.',
- 'config-suhosin-max-value-length' => '<i lang="en">Suhosin</i> es enschtalleet. Dröm kann ene <code lang="en">GET</code>-Parrameeter nit övver {{PLURAL:$1|ei Byte|$1 Bytes|noll Byte}} lang wääde. Dem MediaWiki singe <i lang="en">ResourceLoader</i> kütt doh zwa drömeröm, ävver dat bräms. Wann müjelesch, doht <code lang="en">suhosin.get.max_value_length</code> en dä Dattei <code lang="en">php.ini</code> op 1024 Bytes udder drövver enschtälle, un dann moß <code lang="en">$wgResourceLoaderMaxQueryLength</code> en dä Dattei <code lang="en">LocalSettings.php</code> op däsälve Wäät jesaz wääde.',
- 'config-db-type' => 'De Zoot Daatebangk:',
- 'config-db-host' => 'Dä Name vun däm Rääschner met dä Daatebangk:',
- 'config-db-host-help' => 'Wann Dinge ẞööver för de Daatebangk ob enem andere Rääschner es, donn heh dämm singe Name udder dämm sing <i lang="en">IP</i>-Addräß enjävve.
-
-Wann De ob enem Meetẞööver beß, weet Der Dinge Provaider odder däm sing Dokemäntazjuhn saare, wat De endraare moß.
-
-Wann De ob enem ẞööver onger <i lang="en">Windows</i> am enshtalleere bes un en <i lang="en">MySQL</i>-Daatebangk häs, künnd_et sin, dat „<code lang="en">localhost</code>“ nit douch för der Name vum ẞööver. Wann dad-esu es, versöhg et ens met „<code lang="en">127.0.0.1</code>“ als <i lang="en">IP</i>-Addräß vum eije Rääschner.
-
-Wann De ene <i lang="en">PostgreSQL</i>-ẞööver häs, donn dat Fäld läddesch lohße, öm en Verbendung övver e <i lang="en">Unix socket</i> opzemaache.',
- 'config-db-host-oracle' => 'Dä Daatebangk ier <i lang="en" title="Transparent Network Substrate">TNS</i>:',
- 'config-db-host-oracle-help' => 'Donn ene jöltije [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „<i lang="en">Local Connect</i>“-Name] aanjävve. De Dattei „<code lang="en">tnsnames.ora</code>“ moß för heh dat Projamm seschbaa un ze Lässe sin.<br />Wann heh de Projamm_Biblijoteeke für de Aanwänderprojramme för de Version 10g udder neuer enjesaz wääde, kam_mer och et [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „<i lang="en">Easy Connect</i>“] jenumme wääde för der Name ze verjävve.',
- 'config-db-wiki-settings' => 'De Daate vum Wiki',
- 'config-db-name' => 'Dä Name vun dä Daatebangk:',
- 'config-db-name-help' => 'Jiff ene Name aan, dä för Ding Wiki passe deiht.
-Doh sullte kei Zweschrereum un kein Stresche dren sin.
-
-Wann De nit op Dingem eije Rääschner bes, künnt et sin, dat Dinge Provaider Der extra ene beshtemmpte Name för de Daatebangk jejovve hät, uffr dat de dä drom froore moß udder dat De de Daatebangke övver e Fommulaa selver enreeschte moß.',
- 'config-db-name-oracle' => 'Schema för de Daatebangk:',
- 'config-db-account-oracle-warn' => 'Mer han drei Aate, wi mer <i lang="en">Oracle</i> als Daatebangk aanbenge künne.
-
-Wann De ene neue Zohjang op de Daatenbangk met Naame un Paßwoot mem Projramm för et Opsäze aanlääje wells, dann jif ene Zohjang met däm Rääsch „<i lang="en">SYSDBA</i>“ aan, dä et alld jitt, un jif däm di Daate aan för dä neue Zohjang aanzelääje.
-Do kanns och dä neue Zohjang vun Hand aanlääje un heh beim Opsäze nur dää aanjävve — wann dä dat Rääsch hät, en de Daatebangk Schema_Objäkte aanzelääje.
-Udder De jiß zwei ongerscheidlijje Zohjäng op de Daatenbangk aan, woh eine vun dat Rääsch zom Aanlääje hät un dä andere moß dat nit un es för der nomaale Bedrief zohshtändesch.
-
-En Skrep, wat ene Zohjang op de Daatenbangk aanlääsch met all dä nüüdejje Rääschde, fengks De em Verzeishneß <code lang="en">maintenance/oracle/</code> vun Dingem MediaWiki. Donn draan dengke, dat ene Zohjang met beschrängkte Rääschde all di Müjjeleschkeite för et Waade un Repareere nit hät, di de jewöhnlejje Zoot Zohjang met sesh brängk.',
- 'config-db-install-account' => 'Der Zohjang för en Enreeschte',
- 'config-db-username' => 'Dä Name vun däm Aanwender för dä Zohjref op de Daatebangk:',
- 'config-db-password' => 'Et Paßwoot vun däm Aanwender för dä Zohjref op de Daatebangk:',
- 'config-db-password-empty' => 'Jiv e Paßwoot aan, för dä neue Aanwender för dä Zohjref op de Daatebangk, $1.
-Ed es zwa müjjelesch, Aanwender för dä Zohjref op de Daatebangk der ohne e Paßwoot aanzelääje,
-ävver dat wöhr en schwere Jevah för de Sescherheit vum Wiki.',
- 'config-db-install-username' => 'Jiv ene Name aan för dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere.
-Dat es keine Metmaacher_Name em Wiki — heh dä Name es alleins en der Daatebangk bikannt.',
- 'config-db-install-password' => 'Jiv e Paßwoot aan för dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere.
-Dat es kei Paßwoot för ene Metmaacher em Wiki — et es alleins en der Daatebangk bikannt.',
- 'config-db-install-help' => 'Donn dä Name un et Paßwoot vun däm Aanwänder för der Zohjreff op de Daatebangk jäz för et Enreeshte aanjävve.',
- 'config-db-account-lock' => 'Donn dersälve Name un et sälve Paßwoot för der nomaale Bedrief vum Wiki bruche',
- 'config-db-wiki-account' => 'Dä Name vun däm Aanwender för dä Zohjref op de Daatebangk em nomaale Bedrief:',
- 'config-db-wiki-help' => 'Jiv ene Name un e Paßwoot aan, för dä Aanwender för dä Zohjref op de Daatebangk, wann et wiki nommaal aam Loufe es.
-Wan et dä Name en der Daatebangk noch it jit, un dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere
-jenooch Beräschtijunge hät, läät dä heh dä Aanwender en der Daatebangk aan un jidd_em di Rääschde, di dä nüüdesch hät, ävver nit mieh.',
- 'config-db-prefix' => 'Vörsaz för de Name vun de Tabälle en de Daatebangk:',
- 'config-db-prefix-help' => 'Wann ein Daatebangk för mieh wi ein Wiki udder e Wiki uns söns jät zosamme jebruch weed, dann kam_mer noch jet vör de Tabälle ier Name säze. Esu ene Vörsaz sull dubblte Tabällename vermeide hälfe.
-Donn kein Zwescheräum enjävve!
-
-Jewöhnlesch bliev dat Feld heh ävver läddesch.',
- 'config-db-charset' => 'Dä Daatebangk iere Zeishesaz',
- 'config-charset-mysql5-binary' => 'MySQL (4.1 udder 5.0) binär',
- 'config-charset-mysql5' => 'MySQL (4.1 udder 5.0) UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 röckwääts kompatibel UTF-8',
- 'config-charset-help' => "''' Opjepaß:'''
-Wann De et '''röckwääts kompatibel UTF-8 Fommaat''' nemmps, met dem <i lang=\"en\">MySQL</i> singe Version4.1 udder hüüter, dann künnt dat all di Zeishe kappott maache, die nit em <i lang=\"en\" title=\"American Standard Code for Information Interchange\">ASCII</i> sen, un domet all ding Sesherungskopieje kapott maache, wat mer nieh mieh retuur krijje kann.
-
-Beim Shpeishere em '''binäre Fomaat''' deiht MediaWiki de Täxte, di em UTF-8 Fommaat kumme, en dä Daatebangk en binär kodeerte Daatefälder faßhallde.
-Dat es flöcker un spaasaamer wi et UTF-8 Fommaat vum <i lang=\"en\">MySQL</i> un määd et müjjelesch, all un jeedes <i lang=\"en\">Unicode</i>-Zeishe met faßzehallde.
-
-Beim Shpeishere em '''UTF-8 Fomaat''' deiht et <i lang=\"en\">MySQL</i> der Zeishesaz un de Kodeerung vun dä Daate känne, un kann se akeraat aanzeije un ömwandelle,
-allerdengs künne kein Zeishe ußerhalv vum [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Shprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeshpeishert wääde.",
- 'config-mysql-old' => 'Mer bruche <i lang="en">MySQL</i> $1 udder neuer. Em Momang es <i lang="en">MySQL</i> $2 aam Loufe.',
- 'config-db-port' => 'De Pooz-Nommer (<i lang="en">port</i>) för de Daatebangk:',
- 'config-db-schema' => 'Et Schema en de Datebangk för MediaWiki:',
- 'config-db-schema-help' => 'För jewöhnlesch es dat Schema en Odenong.
-Donn bloß jät draan ändere, wann De sescher weiß, dat dat nüüdesch es.',
- 'config-pg-test-error' => "Mer krijje kein Verbendung zor Daatebank '''$1''': $2",
- 'config-sqlite-dir' => 'Dem <i lang="en">SQLite</i> sing Daateverzeishnes:',
- 'config-sqlite-dir-help' => '<i lang="en">SQLite</i> hät all sing Daate zosamme en en einzel Dattei.
-
-En dat Verzeishneß, wat De aanjiß, moß dat Web_ẞööver_Projramm beim Opsäze eren schriive dörrve.
-
-Dat Verzeishneß sullt \'\'\'nit\'\'\' övver et Web zohjänglesch sin, dröm dom_mer et nit dohen, woh de <i lang="en">PHP</i>-Datteije sin.
-
-Mer donn beim Opsäze zwa uß Vöörssh en <code lang="en">.htaccess</code> Dattei dobei, ävver wann di nit werrek, künnte Lück vun ußerhallef aan Ding Daatebangk_Dattei eraan kumme.
-Doh shtonn Saache dren, wi de Addräße för de Metmaacher ier <i lang="en">e-mail</i> un de verschlößelte Paßwööter un de vershtoche un de fottjeschmeße Sigge un ander Saache ussem Wiki, di mer nit öffentlesch maache darref.
-
-Donn Ding Daatebangk et beß janz woh anders hen, noh <code lang="en">/var/lib/mediawiki/\'\'wikiname\'\'</code> för e Beishpell.',
- 'config-oracle-def-ts' => 'Tabälleroum för der Shtandattjebruch:',
- 'config-oracle-temp-ts' => 'Tabälleroum för der Jebruch zweschedorsh:',
- 'config-type-mysql' => '<i lang="en">MySQL</i>',
- 'config-type-postgres' => '<i lang="en">PostgreSQL</i>',
- 'config-type-sqlite' => '<i lang="en">SQLite</i>',
- 'config-type-oracle' => '<i lang="en">Oracle</i>',
- 'config-support-info' => 'MediaWiki kann met heh dä Daatebangk_Süßteeme zosamme jonn:
-
-$1
-
-Wann 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-support-mysql' => '* <i lang="en">$1</i> es dat vum MediaWiki et eets ongershtöz Daatebangksüßteem ([http://www.php.net/manual/de/mysql.installation.php Aanleidung för et Övversäze un Enreeschte von PHP met <i lang="en">MySQL</i> dobei, op Deutsch])',
- 'config-support-postgres' => '* <i lang="en">$1</i> es e bikannt Daatebangksüßteem met offe Quälltäxde, un en och en Wahl nävve <i lang="en">MySQL</i> ([http://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]) Et sinn_er ävver paa klein Fählershe bekannt, um kunne dat em Momang för et reschtijje Werke nit emfähle.',
- 'config-support-sqlite' => '* <i lang="en">$1</i> es e eijfach Daatebangksüßteem, wat joot ongershtöz 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-support-oracle' => '* <i lang="en">$1</i> es e jeschäfflesch Daatebangksüßteem 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">OCI8</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',
- 'config-header-oracle' => 'De Enshtällunge för de <i lang="en">Oracle</i> Daatebangk',
- 'config-invalid-db-type' => 'Dat es en onjöltijje Zoot Daatebangk.',
- 'config-missing-db-name' => 'Do moß jät enjävve för dä Name vun dä Daatebangk.',
- 'config-missing-db-host' => 'Do moß jät enjävve för dä Name vun däm Rääschner met dä Daatebangk.',
- 'config-missing-db-server-oracle' => 'Do moß jät enjävve för dä Daatebangk ier <i lang="en" title="Transparent Network Substrate">TNS</i>.',
- 'config-invalid-db-server-oracle' => 'Dä Daatebangk ier <i lang="en" title="Transparent Network Substrate">TNS</i> kann nit „$1“ sin, dat es esu nit jöltesch.
-Döh dörve bloß <i lang="en" title="American Standard Code for Information Interchange">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), Ongerstreshe (_), un Punkte (.) dren vörkumme.',
- 'config-invalid-db-name' => 'Dä Daatebangk iere Name kann nit „$1“ sin, dä es esu nit jöltesch.
-Döh dörve bloß <i lang="en" title="American Standard Code for Information Interchange">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), Ongerstresh (_), un Bendeshtresh (-) dren vörkumme.',
- 'config-invalid-db-prefix' => 'Dä Vörsaz för de Name vun de Tabälle en de Daatebangk kann nit „$1“ sin, dä es esu nit jöltesch.
-Döh dörve bloß <i lang="en" title="American Standard Code for Information Interchange">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), Ongerstreshe (_), un Bendeshtreshe (-) dren vörkumme.',
- 'config-connection-error' => '$1.
-
-Donn de Name för dä Rääschner, vun däm Aanwender för dä Zohjref op de Daatebangk, un et Paßwoot prööfe, repareere, un dann versöhg et norr_ens.',
- 'config-invalid-schema' => 'Dat Schema för MediaWiki kann nit „$1“ sin, dä Name wöhr esu nit jöltesch.
-Döh dörve bloß <i lang="en" title="American Standard Code for Information Interchange">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), un Ongerstreshe (_) dren vörkumme.',
- 'config-db-sys-create-oracle' => 'Dat Projramm för MediaWiki opzesäze kann bloß <i lang="en">SYSDBA</i> bruche för ene neue Zohjang zor Daatebangk enzereeschte!',
- 'config-db-sys-user-exists-oracle' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk jidd_et ald. <i lang="en">SYSDBA</i> kam_mer bloß bruche, för ene neue Zohjang enzereeschte!',
- 'config-postgres-old' => 'Mer bruche <i lang="en">PostgreSQL</i> $1 udder neuer. Em Momang es <i lang="en">PostgreSQL</i> $2 aam Loufe.',
- 'config-sqlite-name-help' => 'Söhk enen Name uß, dä Ding Wiki beschrief.
-Donn kein Bendeschresch un Zweschräum en däm Name bruche.
-Dä Name weed för der Dateiname för de <i lang="en">SQLite</i> Daatebangk jenumme.',
- 'config-sqlite-parent-unwritable-group' => 'Mer kunnte dat Verzeischneß för de Daate, <code lang="en"><nowiki>$1</nowiki></code>, nit enreeschte, weil dat Projramm fö dä Web_ẞööver en dat Verzeischneß doh drövver, <code><nowiki>$2</nowiki></code>, nix erin donn darref.
-
-Mer han dä Name vun däm Zohjang op et Süßteem eruß jefonge, onger dämm dat Web_ẞööver_Projramm läuf. Jez moß De bloß doför sorrje, dat dä en dat Verzeischneß <code><nowiki>$3</nowiki></code> schrieve kann, öm heh wigger maache ze künne.
-Ob enem Süßteem met <i lang="en">Unix</i>- oder <i lang="en">Linux</i> jeiht dat esu:
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Mer kunnte dat Verzeischneß för de Daate, <code lang="en"><nowiki>$1</nowiki></code>, nit enreeschte, weil dat Projramm fö dä Web_ẞööver en dat Verzeischneß doh drövver, <code><nowiki>$2</nowiki></code>, nix erin donn darref.
-
-Mer han dä Name vun däm Zohjang op et Süßteem nit eruß fenge künne, onger dämm dat Web_ẞööver_Projramm läuf. Jez moß De bloß doför sorrje, dat dä en dat Verzeischneß <code><nowiki>$3</nowiki></code> schrieve kann, öm heh wigger maache ze künne. Wann De dä Name och nit weiß, maach, dat jeeder_ein doh schrieve kann.
-Ob enem Süßteem met <i lang="en">Unix</i>- oder <i lang="en">Linux</i> jeiht dat esu:
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Ene Fähler es opjetrodde beim Aanlääje vum Daate_Verzeishneß „$1“.
-Don dä Plaz för et Shpeishere prööfe un Repareere, dann versöhg et norr_ens.',
- 'config-sqlite-dir-unwritable' => 'Mer künne nit en dat Verzeishneß „$1“ schrieeve
-Donn dohvun de Zohjreffs_Rääschde esu verändere, dat der Webßööver doh dren schrieeve kann, un dann versöhg et norr_ens.',
- 'config-sqlite-connection-error' => '$1.
-
-Donn onge dat Verzeishnes un der Name vun der Daatebangk prööfe un repareere, un dann versöhg_et norr-ens.',
- 'config-sqlite-readonly' => 'En di Dattei <code lang="en">$1</code> künne mer nit schrieve.',
- 'config-sqlite-cant-create-db' => 'Mer kunnte di Dattei <code lang="en">$1</code> för de Daatebangk nit aanlääje.',
- 'config-sqlite-fts3-downgrade' => 'Dat PHP heh hät kein Ongershtözong för FTS3, dröm donn mer de Daatebangktabälle eronger shtoofe.',
- 'config-can-upgrade' => 'Et sinn-er ald Daatebangktabelle vum MediaWiki en dä Daatebangk.
-Öm di op der Shtand vum MediaWiki $1 ze bränge, donn jäz op „{{int:config-continue}}“ klecke.',
- 'config-upgrade-done' => "Alles es jäz om neue Shtand.
-
-Mer kann dat Wiki jäz [\$1 bruche].
-
-Wann De Ding Dattei <code lang=\"en\">LocalSettings.php</code> neu schrieve wells, donn onge op dä Knopp klicke.
-Dat dom_mer ävver '''nit vörschlonn'''em Jääjedeil, ußer, wann et Probleme mem Wiki jitt.",
- 'config-upgrade-done-no-regenerate' => 'Alles es jäz om neue Shtand.
-
-Mer kann dat Wiki jäz [$1 bruche].',
- 'config-regenerate' => 'Donn de Dattei <code lang="en">LocalSettings.php</code> neu opsäze →',
- 'config-show-table-status' => 'Et Kommando <code lang="en"><code>SHOW TABLE STATUS</code></code> aan de Daatebangk es donävve jejange!',
- 'config-unknown-collation' => "'''Opjepaß:''' De Daatabangk deiht en onbikannte Reijefollsch bruche, för Booshtaabe un Zeishe ze verjliishe un ze zotteere.",
- 'config-db-web-account' => 'Dä Zohjang zor Daatebangk för et Wiki',
- 'config-db-web-help' => 'Donn ene Name un e Paßwoot för der Zohjang zor Daatebangk för et Wiki em nomaale Bedrief aanjävve.',
- 'config-db-web-account-same' => 'Donn dersällve Zohjang nämme, wi heh beim Opsäze.',
- 'config-db-web-create' => 'Donn dä Zohjang aanlääje, wann dä noch nit doh es.',
- 'config-db-web-no-create-privs' => 'Dä Zohjang för et Opsäze es nit berääschtesch, ene ander Zohjan enzereeschte.
-Dä aanjejovve Zohjang för der Nomaalbedrief moß dröm schunn enjersht sen!',
- 'config-mysql-engine' => 'De Zoot udder et Fommaat vun de Tabälle:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => '\'\'\'Opjepaß:\'\'\' <i lang="en">MyISAM</i> es als Speicher för <i lang="en">MySQL</i> nit besönders joot för et Zosammeschpell met MediaWiki zo bruche:
-* Dorj_et kumplätte Sperre vun Tabälle, künne koum ens Saache parrallel en dä Daatebangk jedonn wääde.
-* Dat Fomaat es anfällesch för Probleme met de Daate.
-* Et weed vun MediaWiki nit ėmmer zopaß ongerschtöz.
-
-Wann Ding <i lang="en">MySQL</i> et Schpeischere en <i lang="en">InnoDB</i>-Datteije ongerschtöze deiht, dom_mer dat nohdröcklesch ämfähle.
-Kann dä ẞööver dat nit, künnd et joode jelääjeheit sin, dä ens op der neuste Schtand ze bränge.',
- 'config-mysql-only-myisam-dep' => '\'\'\'Opjepaß:\'\'\' <i lang="en">MyISAM</i> es de einzeje Zoot Speischerprojramm för <i lang="en">MySQL</i>, di nit för MediaWiki ze ämfähle es, weil:
-* wääje dem Schpärre vun jannze Tabälle sin koum paralleele Axjuhne en dä Daatebangk möjjelesch,
-* et es aanfällesch för Probleeme met de Daate es, un
-* et weed vun MediaWiki nit emmer jood ongerschtöz.
-
-Ding Enschtallazjuhn vum <i lang="en">MySQL</i> kann nit met <i lang="en">InnoDB</i> ömjonn.
-Wi wöhr et met ener neuere väsjohn vum <i lang="en">MySQL</i>?',
- 'config-mysql-engine-help' => "'''InnoDB''' es fö jewöhnlesch et beß, weil vill Zohjreffe op eijmohl joot ongershtöz wääde.
-
-'''MyISAM''' es flöcker op Rääschnere met bloß einem Minsch draan, un bei Wikis, di mer bloß lässe un nit schrieeve kann.
-MyISAM-Daatebangke han em Schnett mieh Fähler un jon flöcker kappott, wi InnoDB-Daatebangke.",
- 'config-mysql-charset' => 'Dä Daatebangk iere Zeishesaz:',
- 'config-mysql-binary' => 'binär',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "Beim Shpeishere em '''binäre Fomaat''' deiht MediaWiki de Täxte, di em UTF-8 Fommaat kumme, en dä Daatebangk en binär kodeerte Daatefälder faßhallde.
-Dat es flöcker un spaasaamer wi et UTF-8 Fommaat vum <i lang=\"en\">MySQL</i> un määd et müjjelesch, all un jeedes <i lang=\"en\">Unicode</i>-Zeishe met faßzehallde.
-
-Beim Shpeishere em '''UTF-8 Fomaat''' deiht et <i lang=\"en\">MySQL</i> der Zeishesaz un de Kodeerung vun dä Daate känne, un kann se akeraat aanzeije un ömwandelle,
-allerdengs künne kein Zeishe ußerhalv vum [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Shprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeshpeishert wääde.",
- 'config-site-name' => 'Däm Wiki singe Name:',
- 'config-site-name-help' => 'Dä douch em Tittel vun de Brauserfinstere un aan ätlije andere Shtälle op.',
- 'config-site-name-blank' => 'Donn ene Name för di Sait aanjävve.',
- 'config-project-namespace' => 'Dä Name för et Appachtemang övver et Projäk:',
- 'config-ns-generic' => 'Projäk',
- 'config-ns-site-name' => 'Et sällve wi däm Wiki singe Name: $1',
- 'config-ns-other' => 'Andere (jiff aan wälshe)',
- 'config-ns-other-default' => 'MingWiki',
- 'config-project-namespace-help' => "Noh dämm Vörbeld vun de Wikipeedija, donn vill Wikis dänne ier Sigge övver et Wiki un sing Rääjelle vun dä Sigge mem Enhald vum Wiki tränne, un en enem extra Appachtemang för et „'''Projäk'''“ afflääje.
-Sigge en däm Appachtemang fange all med enem beshtemmpte Vörsaz aan, däm Name vum Appachtemang, un dä moß De heh faßlääje.
-Dä Name kann beshtemmpte Zeiche nit enthallde, wi „#“ un „:“ un et es Tradizjuhn, dat hä vum Name vum Wiki her kütt.",
- 'config-ns-invalid' => 'Dat aanjejovve Appachtemang „<nowiki>$1</nowiki>“ es nit jöltesch.
-Nemm ene andere Name för däm Wiki sing eije Appachtemang.',
- 'config-ns-conflict' => 'Dat aanjejovve Appachtemang „<nowiki>$1</nowiki>“ kütt ald als Standatt-Appachtemang em MediaWiki vör.
-Nemm ene andere Name för däm Wiki sing eije Appachtemang.',
- 'config-admin-box' => 'Der Zohjang för der eezte Wiki_Köbes',
- 'config-admin-name' => 'Metmaacher_Name:',
- 'config-admin-password' => 'Et Paßwoot:',
- 'config-admin-password-confirm' => 'Norrens dat Paßwoot:',
- 'config-admin-help' => 'Jif Dinge leevste Name als Metmaacher för Desch aan, för e Beishpell „Schmitzens Pitter“
-— Dat weed dä Name wääde, met dämm De Desch enlogge deihs.',
- 'config-admin-name-blank' => 'Jiv ene Metmaacher_Name en för dä Wiki-Köbes.',
- 'config-admin-name-invalid' => '„<nowiki>$1</nowiki>“ es keine jöltijje Metmaacher_Name.
-Jiv ene joode Name en!',
- 'config-admin-password-blank' => 'Do mos_e Paßwoot för dä Wiki_Köbes aanjävve!',
- 'config-admin-password-same' => 'Dat Paßwoot un dä Name dörve nit ejaal sin!',
- 'config-admin-password-mismatch' => 'Di Paßwööter sin ongerscheidlesh!',
- 'config-admin-email' => 'Addräß för de <i lang="en">e-mail</i>:',
- 'config-admin-email-help' => 'Jiv heh di Adräß för de <i lang="en">e-mail</i> aan, woh De <i lang="en">e-mail</i> vun ander Metmaacher uss_em Wiki hen krijje wells, di et Der müjjelesh määt, Ding Paßwoot automatetsch truusche ze lohße, un woh Nohreeshte övver veränderte Sigge op Dinge Oppaßleß hen jescheck wääde sulle.
-De kanns dat Fäld ävver och läddesch lohße.',
- 'config-admin-error-user' => 'Beim Enreeshte vum Zohjang för dä Wiki_Köbes „<nowiki>$1</nowiki>“ es ene Fähler em Wiki opjetrodde.',
- 'config-admin-error-password' => 'Beim Paßwoot-Säze för dä Wiki_Köbes „<nowiki>$1</nowiki>“ es ene Fähler em Wiki opjetrodde.: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Do häs_en onjöltijje Addräß för de <i lang="en">e-mail</i> aanjejovve.',
- 'config-subscribe' => 'Donn de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce <i lang="en">e-mail</i>-Leß met de Aanköndijunge vum MediaWiki] abonnere.',
- 'config-subscribe-help' => 'Do kumme bloß winnish Meddeilunge un di jonn övver neu Versiohne vom MediaWiki un weeshtejje Saache vun däm sing Sesherheit.
-Do sullts se abbonneere, un Ding MediWiki_Projramme op der neue Shtand bränge, wann neu Version eruß kumme.',
- 'config-subscribe-noemail' => 'Do has versöhk, der ohne en Addräß för Ding <i lang="en">e-mail<i> aanzejävve, de Aanköndijonge för Aanköndijunge för neue Versione ze abboneere. Jivv en Addräß aan, wann De di Aanköndijonge hann wells.',
- 'config-almost-done' => 'Do bes beinah dorsh!
-Do künnts jez der Räß vun de einzel Enshtellunge övverjonn, un et Wiki tiräktemang fäädesch opsäze.',
- 'config-optional-continue' => 'De wells noch mieh Frore jeshtallt krijje un noch mieh Enshtällunge maache?',
- 'config-optional-skip' => 'Nä, lohß dä Ömshtand, donn eifarr_et Wiki opsäze.',
- 'config-profile' => 'Enshtällunge för de Metmaacher ier Rääschte:',
- 'config-profile-wiki' => 'En offe Wiki',
- 'config-profile-no-anon' => 'Schriever möße enlogge',
- 'config-profile-fishbowl' => 'Bloß ußdröcklesch zohjelohße Schriever',
- 'config-profile-private' => 'E jeschloße Privat_Wiki',
- 'config-profile-help' => "Wikis loufe et bäß, wam_mer esu vill Lück wi möjjelesch draan metmaache un schrieve löht.
-Met MediaWiki es et ejfach, de neuste Änderonge ze beloore un wat ahnungslose udder fiese Lück kapott jemaat han wider retuur ze maache.
-
-Bloß, mänsch eine häd_eruß jefonge, dat mer MediaWiki jood en en jruuße Zahl ongerscheidlijje Rolle bruche kann, un nit emmer es et leisch, ene vum onverfälschte Wiki_Wääsch ze övverzeuje.
-Esu häß De de Wahl:
-
-'''{{int:config-profile-wiki}}''' löht jeder_ein metschrieve, och ohne sesch enzelogge.
-
-'''{{int:config-profile-no-anon}}''', dat sorsch för mieh seeschbaa Verantwootlischkeite, künnt ävver zohfällije Methellefer verschrecke.
-
-'''{{int:config-profile-fishbowl}}''' löht nor de ußjesöhk Metmaacher schrieve, ävver de janze Öffentleshkeit kann et lässe un süht och de ällder Versione, un wat wää wann draan jedonn hät.
-
-'''{{int:config-profile-private}}''' kann nur lässe, wäh en et Wiki zohjelohße es, un desellve Jropp kann uch schrieve.
-
-Noch ander un un opwändijere Enschtellunge för de Rääschte sin möjjelesch, wann et Wiki ens aam Loufe es. Loor Der doför de [//www.mediawiki.org/wiki/Manual:User_rights zopaß Hölp em Handbooch] aan.",
- 'config-license' => 'Urhävverrääsch un Lizänz:',
- 'config-license-none' => 'Kein Fooßreih övver de Lizänz',
- 'config-license-cc-by-sa' => '<i lang="en">Creative Commons</i> Der Name moß jenannt sin, et Wiggerjävve es zohjelohße onger dersellve Bedengunge',
- 'config-license-cc-by' => 'De <i lang="en">Creative Commons</i> ier Lizänz met Namensnännong',
- 'config-license-cc-by-nc-sa' => '<i lang="en">Creative Commons</i> Nit för e Jeschäff ze maache, et Wiggerjävve es zohjelohße unger dersellve Bedengunge',
- 'config-license-cc-0' => '<i lang="en">Creative Commons</i> „Noll“ (jemeinfrei udder Pablic Domain)',
- 'config-license-gfdl' => 'De <i lang="en">GNU</i>-Lizänz för frei Dokemäntazjuhne, Version 1.3 udder en späädere',
- 'config-license-pd' => 'Allmende (jemeinfrei, <i lang="en">public domain</i>)',
- 'config-license-cc-choose' => 'En <i lang="en">Creative Commons</i> Lizänz, sellver ußjesöhk:',
- 'config-license-help' => "Ättlijje öffentleje Wikis donn iehr Beidrääsh onger en [http://freedomdefined.org/Definition frei Lizänz] shtelle.
-Dat hellef, e Jeföhl vun Jemeinsamkeid opzeboue, un op lange Seesh emmer wider Beidrääsch ze krijje.
-Dat es nit onbedengk nüüdesh för e Jeschäffs- udder Privaat_Wiki.
-
-Wä Stöcke uß de Wikipedia bruche well, un han well, dat de Wikipedia uss_em eije Wiki jät övvernämme kann, sullt „'''<i lang=\"en\">Creative Commons</i>, dem Schriever singe Name moß jenannt wääde, un Wiggerjävve zoh dersellve Bedengunge es zohjelohße'''“ ußwähle.
-
-De su jenannte '''<i lang=\"en\">GNU Free Documentation License</i>''' (de freije Lizänz för Dokemäntazjuhne vun dä GNU) sen de ahle Lizänzbedenonge vun de Wikipedia. Se es emmer noch in Odenong un jöltesch, ävver se es schwer ze vershtonn un et Wiggerjävve un widder Verwände es manshmool schwieeresch domet.",
- 'config-email-settings' => 'Enschtellunge för de <i lang="en">e-mail</i>',
- 'config-enable-email' => 'De <i lang="en">e-mail</i> noh druße zohlohße',
- 'config-enable-email-help' => 'Sulle <i lang="en">e-mails</i> zohjelohße sin, moß mer, domet et noher flupp, de [http://www.php.net/manual/en/mail.configuration.php Enschtellunge em PHP för de <i lang="en">e-mails</i>] zopaß jemaat han.
-Wann kein <i lang="en">e-mails</i> nüüdesch sin, kam_mer se heh afschallde.',
- 'config-email-user' => '<i lang="en">e-mails</i> zwesche de Metmaacher zohlohße',
- 'config-email-user-help' => 'Määt et müjjelesch, dat sesch de Metmaacher jääjesiggesch <i lang="en">e-mails</i> schecke künne, wann se dat en iehre eije Enschtellunge och enjeschalldt han.',
- 'config-email-usertalk' => '<i lang="en">e-mails</i> mem Bescheid zohlohße, dat einem sing Klaafsigg verändert woodt',
- 'config-email-usertalk-help' => 'Maach et müjjelesch, dat Metmaaacher en iere Enstellunge <i lang="en">e-mails</i> mem Bescheid zohlohße, dat einem sing Klaafsigg verändert woodt.',
- 'config-email-watchlist' => 'Nohreeschte övver Änderonge aan Sigg op de Opaßleßte zohlohße',
- 'config-email-watchlist-help' => 'Lohß Metmaacher Nohreeshte övver de Sigge op dänne iehr Oppaßleß krijje, wann se et en iehre Enschtellonge ußjewählt han.',
- 'config-email-auth' => 'Donn de Övverprööfung för Zohjangsberääschtejunge övver de <i lang="en">e-mail</i> zohlohße',
- 'config-email-auth-help' => 'Wann dat aanjeschald es, möße Metmaacher, di iehr Adräß för de <i lang="en">e-mail</i> neu aanjävve udder ändere, di Addräß övver ene Lengk beschtäätejje, dä se met de <i lang="en">e-mail</i> jescheck krijje.
-Bloß aan esu beschtääteschte Adräße deiht et Wiki <i lang="en">e-mails</i> schecke, Di künne vun annder Metmaachere kumme, udder vum Wiki sellver, wann en Sigg en däm Metmaacher singe Oppaßleß verändert woode es.
-Mer \'\'\'schlonn vör, dat aanzeschallde\'\'\' för öffentlesch Wikis, weil sönß zoh leisch Driß mem Wiki singe <i lang="en">e-mail</i> jemaat wääde künnt.',
- 'config-email-sender' => 'De Adräß för de Antwoote op <i lang="en">e-mails</i>:',
- 'config-email-sender-help' => 'Jiff de Adräß för de <i lang="en">e-mail</i> en, woh Antwoote ob em Wiki singe <i lang="en">e-mails</i> hen jonn sulle.
-Dat es och de Adräß, woh de <i lang="en">e-mails</i> met Fählermäldonge hen jon.
-Vill ẞöövere för de <i lang="en">e-mail</i> welle winnischßdens ene jöltijje Domain en dä Adräß han.',
- 'config-upload-settings' => 'Belder un Datteie huh laade',
- 'config-upload-enable' => 'Belder un Datteie huh laade zohlohße',
- 'config-upload-help' => 'Datteije huh ze laade künnt e Risiko för dem ẞööver singe Sescherheit sin.
-Mieh doh drövver kam_mer em [//www.mediawiki.org/wiki/Manual:Security Kapitel övver de Sescherheit] em Handbooch lässe.
-
-Öm et Huhlaade zohzelohße donn de Rääschde för der Zohjreff op dat Ongerverzeischneß <code lang="en">images</code> em MediaWiki singem Houpverzeischneß esu enshtälle, dat et Webßööverprojramm doh Datteije un Verzeischneße eren schrieve kann.
-Donoh donn heh di Saach zohlohße.',
- 'config-upload-deleted' => 'Dat Verzeishneß för fottjeschmeße Datteije:',
- 'config-upload-deleted-help' => 'Söhk e Verzeijschneß uß för de fottjeschmeße Datteije vum Wiki dren afzelääje.
-Et bäß es, wam_mer vum <i lang="en">world wide web</i> doh nit drahn kumme kann.',
- 'config-logo' => 'Dem Wiki singem Logo sing <i lang="en">URL</i>:',
- 'config-logo-help' => 'De Schtandart_Bedeen_Bovverfläsch vum MediaWiki hät e Logo bovve en der Eck met 135x160 Pixele.
-Donn e zopaß Logo huh laade, un donn däm sing URL heh endraare.
-
-Do kanns <code lang="en">$wgStylePath</code> udder <code lang="en">$wgScriptPath</code> nämme, wann Ding Logo en einem vun dänne Pahde litt.
-
-Wells De kei Logo han, draach heh nix en.',
- 'config-instantcommons' => 'Donn <i lang="en">InstantCommons</i> zohlohße.',
- 'config-instantcommons-help' => '<i lang="en">[//www.mediawiki.org/wiki/InstantCommons InstantCommons]</i> es en Eijeschaff, di et för Wikis müjjelesch määt, Belder, Tondatteie un ander Meedijedatteie enzebenge, di op dä Webßait vun de <i lang="en">[//commons.wikimedia.org/ Wikimedia Commons]</i> ongerjebraat sin. Öm dat noze ze künne, moß dä ẞööver vum MediaWiki en Verbendung nohm Internet opnämme künne.
-
-Mieh Aanjaabe doh drövver un en Aanleidung, wi mer och ander Wikis ußer de <i lang="en">Wikimedia Commons</i> doför enreeschte kann, fengk mer em [//mediawiki.org/wiki/Manual:$wgForeignFileRepos Handbooch].',
- 'config-cc-error' => 'Et Ußsöhke övver de <i lang="en">Creative Commons</i> iehr Projramm zum Lizänzbeshtemme hät nix jebraat.
-Donn de Lizänz sellver beshtemme.',
- 'config-cc-again' => 'Noch ens neu ußsöhke&nbsp;…',
- 'config-cc-not-chosen' => 'Söhk uß, wat för en Lizänz vun de <i lang="en">Creative Commons</i> De han wells, un donn dann op „<i lang="en">proceed</i>“ klecke.',
- 'config-advanced-settings' => 'Fottjeschredde Enshtellunge',
- 'config-cache-options' => 'Enshtällunge för et Faßhallde vun Objäkte em Zweschsheisher:',
- 'config-cache-help' => 'Objäkte em Zwescheshpeisher faßhallde, dat heiß öff jebruchte Daate en der <i lang="en">cache</i> donn, bruche mer, öm MediaWiki flöcker ze maache,
-Meddlere un jruuße Wiki-ẞaits sullte dat onbedengk ußnoze, un och bei klein Wikis weed mer et jood merke.',
- 'config-cache-none' => 'Keine Zweschshpeijsher (Et jeid_em Wiki nix verloore, ußer velleish Schnälleshkeid wann vill loss es)',
- 'config-cache-accel' => 'Ene Objäk<i lang="en">cache</i> vum PHP (<i lang="en">APC</i>, <i lang="en">XCache</i>, udder <i lang="en">WinCache</i>)',
- 'config-cache-memcached' => 'Donn der <code lang="en">memcached</code> ẞööver nämme (Määt extra Enshtellunge un Opsäze nüüdesch)',
- 'config-memcached-servers' => 'De <code lang="en">memcached</code> ßöövere:',
- 'config-memcached-help' => 'Donn de Leß aanhjävve, met de <i lang="en">IP</i>-Addräße för der <code lang="en">memcached</code> ẞööver ze bruche.
-Se sullte ein pro Reih opjeschrevve sin, un en Pooz (<i lang="en">port</i>) ier Nommer han, För e Beishpell, esu:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Do häss der <code lang="en">memcached</code> als Dinge Zoot vun Zwescheshpeijscher aanjejovve, ävver nit eine ẞööver doför.',
- 'config-memcache-badip' => 'Do häss en onjöltijje <i lang="en">IP</i>-Addräß för der <code lang="en">memcached</code> ẞööver aanjejovve: $1.',
- 'config-memcache-noport' => 'Do has kein Pooz (<code lang="en">port</code>) Nommer aanjejovve för mem <code lang="en">memcached</code> ẞööver ze bruche: $1.
-Wann De di Nommer nit weiß, der Shtandatt es 11211.',
- 'config-memcache-badport' => 'Dem <code lang="en">memcached</code> ẞööver singe Pooz (<code lang="en">port</code>) Nommere sullte zwesche $1 un $2 sin.',
- 'config-extensions' => 'Projramm-Zohsäz (<i lang="en">Extensions</i>)',
- 'config-extensions-help' => 'Di bovve opjeleß Zohsazprojramme för et MediaWiki sin em Verzeischneß <code lang="en">./extensions</code> ald ze fenge.
-
-Do kann se heh un jez aanschallde, ävver se künnte noch zohsäzlesch Enshtellunge bruche.',
- 'config-install-alreadydone' => "'''Opjepaß:'''
-Et sühd esu uß, wi wann De MediaWiki ald enshtalleet hätß, un wöhrs aam Versöhke, dat norr_ens ze donn.
-Jang wigger op de näähßte Sigg.",
- 'config-install-begin' => 'Wann De op „{{int:config-continue}}“ klecks, jeiht de Enshtallazjuhn vum MediaWiki loßß.
-Wann De noch Änderonge maache wells, dann kleck op „{{int:config-back}}“.',
- 'config-install-step-done' => 'jedonn',
- 'config-install-step-failed' => 'donävve jejange',
- 'config-install-extensions' => 'Zohsazprojramme enjeschloße',
- 'config-install-database' => 'Ben de Daatebangk aam ennreeschte.',
- 'config-install-schema' => 'Dat Schema en dä Daatebank weed aanjelaat.',
- 'config-install-pg-schema-not-exist' => 'Dat Scheema för <i lang="en">PostgreSQL</i> es nit doh.',
- 'config-install-pg-schema-failed' => 'Et Tabälle-Opsäze es donävve jejange.
-Donn doför sorrje, dat dä Daatebangk-Aanwänder „$1“ en dämm Daatebangkscheema „$2“ schrieve kann.',
- 'config-install-pg-commit' => 'Ben de Änderonge aam ennbränge.',
- 'config-install-pg-plpgsql' => 'Ben noh dä Daatebangkshprooch <code lang="en">PL/pgSQL</code> aam söhke.',
- 'config-pg-no-plpgsql' => 'Do moß de Daatebangkshprooch <code lang="en">PL/pgSQL</code> en dä Daatebangk $1 enreeschte.',
- 'config-pg-no-create-privs' => 'Dä Daatebangk-Aanwänder för et Enreeschte hät nit jenooch Rääschde, öm ene andere Daatebangk-Aanwänder en dä Daatebangk aanzelääje.',
- 'config-pg-not-in-role' => 'Dä aanjejovve Zohjang för et Web jiddet ald.
-Dä aanjejovve Zohjang för et Enschtalleere es keine <i lang="en">superuser<i> un es nit en de Web-Jropp, dröm kam_mer domet kein Dateije aanlääje, di däm Zohjang för et Web jehüüre.
-
-För MeedijaWiki mößße dämm ävver em Momang di Tabälle jehüüre.
-Dröm donn ene andere Name för dä Zohjang zom Wäb nämme, udder donn „retuur“ klicke, un jivv ene Zohjang för et Enschtalleere aan, dä jenooch Rääschte hät.',
- 'config-install-user' => 'Ben unse Daatebangk-Aanwänder en de Daatebangk am aanlääje.',
- 'config-install-user-alreadyexists' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kann nit aanjelaat wääde, et jidd_en alld.',
- 'config-install-user-create-failed' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kunnt nit aanjelaat wääde, wäje: <code lang="en">$2</code>',
- 'config-install-user-grant-failed' => 'Däm Daatebangk-Aanwänder sing Beräschtijunge ze säze däät nit fluppe wääje: $2',
- 'config-install-user-missing' => 'Dä aanjejovve Metmaacher „$1“ jidd_et nit.',
- 'config-install-user-missing-create' => '{{int:Config-install-user-missing}}<!-- $1 -->
-Donn e Höhksche en et Käßje „{{int:Createaccount}}“ onge, wann De dä aanlääje wells.',
- 'config-install-tables' => 'Ben de Daatebangk-Tabälle aam aanlääje.',
- 'config-install-tables-exist' => "'''Opjepaß''': Et schingk, dem MediaWiki sing Tabälle sin alt doh.
-Doh dom_mer nix aanlääje.",
- 'config-install-tables-failed' => "'''Fähler''': De Tabälle kunnte nit aanjelaat wääde, wääje: $1",
- 'config-install-interwiki' => 'Ben de Engerwiki-Tabäll met de shtandattmääßejje Daate aam fölle.',
- 'config-install-interwiki-list' => 'Mer kunnte de Dattei <code lang="en">interwiki.list</code> nit fenge.',
- 'config-install-interwiki-exists' => "'''Opjepaß''': En der Engewiki-Tabäll schingk alt jät dren ze shtonn.
-Doh dom_mer nix dobei.",
- 'config-install-stats' => 'De Shtatestek-Zahle wääde op Aanfang jeshtallt.',
- 'config-install-keys' => 'Jeheime Schlößel wääde opjebout.',
- 'config-insecure-keys' => "'''Opjepaß:''' {{PLURAL:$2|Ene jeheime Schlößel|Jeheim Schlößele|Keine jeheime Schlößel}} ($1) {{PLURAL:$2|es|sin|es}} automattesch aanjelaat woode. {{PLURAL:$2|Dä es|Di sin|Hä es}} ävver nit onbedengk janz sescher. Övverlääsch Der, {{PLURAL:$2|dä|di|en}} norr_ens vun Hand ze ändere.",
- 'config-install-sysop' => 'Dä Zohjang för der Wiki-Köbes weed aanjelaat.',
- 'config-install-subscribe-fail' => 'Mer künne de <i lang="en">e-mail</i>-Leß <code lang="en">mediawiki-announce</code> nit abonneere: $1',
- 'config-install-subscribe-notpossible' => '<code lang="en">cURL</code> es nit enstalleed un <code lang="en">allow_url_fopen</code>es nit doh.',
- 'config-install-mainpage' => 'Ben de Houpsigg med enem shtandatmääßeje Enhald aam aanlääje',
- 'config-install-extension-tables' => 'Ben Datebangk-Tabälle för de Zohsazprojramme aam ennreschte',
- 'config-install-mainpage-failed' => 'Kunnt de Houpsigg nit afshpeishere: $1',
- 'config-install-done' => "'''Jlöckwonsch!'''
-MediaWiki es jetz enstalleet.
-
-Et Projramm zom Enreeschte hät en Dattei <code lang=\"en\">LocalSettings.php</code> aanjelaat.
-Doh sin de Enstellunge vum Wiki dren.
-
-Do weeß se eronge laade möße un dann en dem Wiki sing Aanfangsverzeishnes donn möße, et sellve Verzeisneß, woh di Dattei <code lang=\"en\">index.php</code> dren litt. Dat Erongerlaade sullt automattesch aanjefange han.
-
-Wann domet jet nit jeflupp hät, udder De di Dattei norr_ens han wells, donn op dä Lengk heh dronger klecke:
-
-\$3
-
-'''Opjepaß''': Wann De dat jez nit deihß, es alles verschött, wat De bes jöz enjejovve häs, weil di Dattei fott es en däm Momang, woh heh dat Projamm aam Engk es.
-
-Wann De mem Ronger- un widder Huhlaade fäädesh bes, kanns De '''[\$2 en Ding Wiki jonn]'''.",
- 'config-download-localsettings' => 'Donn di Dattei <code lang="en">LocalSettings.php</code> eronger laade',
- 'config-help' => 'Hölp',
- 'config-nofile' => 'De Dattei „$1“ ham_mer nit jefonge. Es di fottjeschmeße?',
- 'config-extension-link' => 'Häs De jewoß, dat et Wiki [//www.mediawiki.org/wiki/Manual:Extensions Zohsazprojramme] hann kann?
-
-Do kanns [//www.mediawiki.org/wiki/Category:Extensions_by_category Zohsazprojramme noh Saachjroppe] söhke udder en de [//www.mediawiki.org/wiki/Extension_Matrix Tabäll met de Zohsazprojramme] kike, öm de kumplätte Leß met de Zohsazprojramme ze krijje.',
- 'mainpagetext' => "'''MediaWiki es jetz enstalleet.'''",
- 'mainpagedocfooter' => 'Luur en et (änglesche) [//meta.wikimedia.org/wiki/Help:Contents Handbooch] wann De wesse wells wie de Wiki-Soffwär jebruch un bedeent wääde moß.
-
-== För dä Aanfang ==
-Dat es och all op Änglesch:
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Donn MediaWiki op Ding Schprooch aanpaße]',
-);
-
-/** Kurdish (Latin script) (Kurdî (latînî)‎)
- * @author George Animal
- */
-$messages['ku-latn'] = array(
- 'config-information' => 'Agahî',
- 'config-your-language' => 'Zimanê te:',
- 'config-page-language' => 'Ziman',
- 'config-page-name' => 'Nav',
- 'config-page-options' => 'Vebijêrk',
- 'config-ns-generic' => 'Proje',
- 'config-install-step-done' => 'çêbû',
- 'config-help' => 'alîkarî',
- 'mainpagetext' => "'''MediaWiki serketî hate çêkirin.'''",
- 'mainpagedocfooter' => 'Alîkarî ji bo bikaranîn û guherandin yê datayê Wîkî tu di bin [//meta.wikimedia.org/wiki/Help:Contents pirtûka alîkarîyê ji bikarhêneran] da dikarê bibînê.
-
-== Alîkarî ji bo destpêkê ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lîsteya varîyablên konfîgûrasîyonê]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lîsteya e-nameyên versyonên nuh yê MediaWiki]', # Fuzzy
-);
-
-/** Ladino (Ladino)
- * @author Universal Life
- */
-$messages['lad'] = array(
- 'mainpagetext' => "'''MedyaViki ya se kureó con reuxitá.'''",
- 'mainpagedocfooter' => 'Konsulta la [//meta.wikimedia.org/wiki/Ayudo:Contenido Guía de usador] para tomar enformasyones encima de como usar el lojikal viki.
-
-== En Empeçando ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings La lista de los arreglamientos de la konfiggurasyón]
-* [//www.mediawiki.org/wiki/Manual:FAQ/lad DDS de MedyaViki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce La lista de las letrales (e-mail) de MedyaViki]', # Fuzzy
-);
-
-/** Luxembourgish (Lëtzebuergesch)
- * @author Robby
- * @author Soued031
- * @author 아라
- */
-$messages['lb'] = array(
- 'config-desc' => 'Den Installatiounsprogramm vu MediaWiki',
- 'config-title' => 'MediaWiki $1 Installatioun',
- 'config-information' => 'Informatioun',
- 'config-localsettings-upgrade' => "'''Opgepasst''': E Fichier <code>LocalSettings.php</code> gouf fonnt.
-Är Software kann aktualiséiert ginn, setzt w.e.g. de Wäert vum <code>\$wgUpgradeKey</code> an d'Këscht.
-Dir fannt en am <code>LocalSettings.php</code>.",
- 'config-localsettings-cli-upgrade' => "E Fichier <code>LocalSettings.php</code> gouf fonnt.
-Fir dës Installatioun z'aktuaéliséieren start w.e.g. <code>update.php</code>",
- 'config-localsettings-key' => 'Aktualisatiounsschlëssel:',
- 'config-localsettings-badkey' => 'De Schlëssel deen Dir aginn hutt ass net korrekt',
- 'config-localsettings-incomplete' => 'De Fichier <code>LocalSettings.php</code> schéngt net komplett ze sinn.
-D\'Variabel $1 ass net definéiert.
-Ännert w.e.g. de Fichier <code>LocalSettings.php</code> esou datt déi Variabel definéiert ass a klickt op "{{int:Config-continue}}".',
- 'config-session-error' => 'Feeler beim Starte vun der Sessioun: $1',
- 'config-no-session' => "D'Donnéeë vun ärer Sessioun si verluergaangen!
-Kuckt Är php.ini no a vergewëssert Iech datt <code>session.save_path</code> op adequate REpertoire agestallt ass.",
- 'config-your-language' => 'Är Sprooch',
- 'config-your-language-help' => 'Sicht déi Sprooch eraus déi Dir während der Installatioun benotze wëllt',
- 'config-wiki-language' => 'Sprooch vun der Wiki:',
- 'config-wiki-language-help' => "Sicht d'Sprooch eraus an där d'Wiki haaptsächlech geschriwwe gëtt.",
- 'config-back' => '← Zréck',
- 'config-continue' => 'Weider →',
- 'config-page-language' => 'Sprooch',
- 'config-page-welcome' => 'Wëllkomm bäi MediaWiki!',
- 'config-page-dbconnect' => 'Mat der Datebank verbannen',
- 'config-page-upgrade' => 'Eng Installatioun déi besteet aktualiséieren',
- 'config-page-dbsettings' => 'Astellunge vun der Datebank',
- 'config-page-name' => 'Numm',
- 'config-page-options' => 'Optiounen',
- 'config-page-install' => 'Installéieren',
- 'config-page-complete' => 'Fäerdeg!',
- 'config-page-restart' => 'Installatioun neistarten',
- 'config-page-readme' => 'Liest dëst',
- 'config-page-releasenotes' => 'Informatiounen zur Versioun',
- 'config-page-copying' => 'Kopéieren',
- 'config-page-upgradedoc' => 'Aktualiséieren',
- 'config-page-existingwiki' => 'Wiki déi et gëtt',
- 'config-help-restart' => 'Wëllt dir all gespäichert Donnéeë läschen déi dir bis elo aginn hutt an den Installatiounsprozess nei starten?',
- 'config-restart' => 'Jo, neistarten',
- 'config-welcome' => "=== Iwwerpréifung vum Installatiounsenvironnement ===
-Et gi grondsätzlech Iwwerpréifunge gemaach fir ze kucken ob den Environnment gëeegent ass fir MediaWiki z'installéieren.
-Dir sollt d'Resultater vun dëser Iwwerpréifung ugi wann Dir während der Installatioun Hëllef frot wéi Dir D'Installatioun ofschléisse kënnt.",
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki Haaptsäit]
-* [//www.mediawiki.org/wiki/Help:Contents Benotzerguide]
-* [//www.mediawiki.org/wiki/Manual:Contents Guide fir Administrateuren]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>Liest dëst</doclink>
-* <doclink href=ReleaseNotes>Informatioune vun der aktueller Versioun</doclink>
-* <doclink href=Copying>Lizenzbedingungen</doclink>
-* <doclink href=UpgradeDoc>Aktualisatioun</doclink>',
- 'config-env-good' => 'Den Environement gouf nogekuckt.
-Dir kënnt MediaWiki installéieren.',
- 'config-env-bad' => 'Den Environnement gouf iwwerpréift.
-Dir kënnt MediWiki net installéieren.',
- 'config-env-php' => 'PHP $1 ass installéiert.',
- 'config-env-php-toolow' => 'PHP $1 ass installéiert.
-Awer MediaWiki brauch PHP $2 oder méi héich.',
- 'config-unicode-using-utf8' => "Fir d'Unicode-Normalisatioun gëtt dem Brion Vibber säin <code>utf8_normalize.so</code> benotzt.",
- 'config-no-db' => "Et konnt kee passenden Datebank-Driver fonnt ginn! Dir musst een Datebank-Driver fir PHP installéieren.
-Dës Datebank-Type ginn ënnerstëtzt: $1.
-
-Wann Dir op engem gesharte Server sidd, da frot Ären Hosting-Provider fir de passenden Datebank-Driver z'installéieren.
-Wann Dir PHP selwer compiléiert hutt, da reconfiguréiert en mat dem ageschalten Datebank-Client, zum Beispill an deem Dir <code>./configure --with-mysql</code> benotzt.
-Wann 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.
-Dat ass wahrscheinlech ze niddreg.
-D'Installatioun kéint net funktionéieren.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] ass installéiert',
- 'config-apc' => '[http://www.php.net/apc APC] ass installéiert',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] ass installéiert',
- 'config-diff3-bad' => 'GNU diff3 gouf net fonnt.',
- 'config-no-uri' => "'''Feeler:''' Déi aktuell URI konnt net festgestallt ginn.
-Installatioun ofgebrach.",
- 'config-using-server' => 'De Servernumm "<nowiki>$1</nowiki>" gëtt benotzt.',
- 'config-using-uri' => 'D\'Server URL "<nowiki>$1$2</nowiki>" gëtt benotzt.',
- 'config-db-type' => 'Datebanktyp:',
- 'config-db-host' => 'Host vun der Datebank:',
- 'config-db-host-oracle' => 'Datebank-TNS:',
- 'config-db-wiki-settings' => 'Dës Wiki identifizéieren',
- 'config-db-name' => 'Numm vun der Datebank:',
- 'config-db-name-oracle' => 'Datebankschema:',
- 'config-db-install-account' => "Benotzerkont fir d'Installatioun",
- 'config-db-username' => 'Datebank-Benotzernumm:',
- 'config-db-password' => 'Passwuert vun der Datebank:',
- 'config-db-install-help' => 'Gitt de Benotzernumm an Passwuert an dat wàhrend der Installatioun benotzt gëtt fir sech mat der Datebank ze verbannen.',
- 'config-db-account-lock' => 'De selwechte Benotzernumm a Passwuert fir déi normal Operatioune benotzen',
- 'config-db-wiki-account' => 'Benotzerkont fir normal Operatiounen',
- 'config-db-wiki-help' => "Gitt de Benotzernumm an d'Passwuert an dat benotzt wäert gi fir sech bei den normale Wiki-Operatiounen mat der Datebank ze connectéieren.
-Wann et de Kont net gëtt, a wann den Installatiouns-Kont genuch Rechter huet, gëtt dëse Benotzerkont opgemaach mat dem Minimum vu Rechter déi gebraucht gi fir dës Wiki bedreiwen ze kënnen.",
- 'config-db-charset' => 'Zeechesaz (character set) vun der Datebank',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binair',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-mysql-old' => 'MySQL $1 oder eng méi nei Versioun gëtt gebraucht, Dir hutt $2.',
- 'config-db-port' => 'Port vun der Datebank:',
- 'config-db-schema' => 'Schema fir MediaWiki',
- 'config-db-schema-help' => "D'Schemaen hei driwwer si gewéinlech korrekt.
-Ännert se nëmme wann Dir wësst datt et néideg ass.",
- 'config-pg-test-error' => "Et ass net méiglech d'Datebank '''$1''' ze kontaktéieren: $2",
- 'config-sqlite-dir' => 'Repertoire vun den SQLite-Donnéeën',
- 'config-oracle-def-ts' => "Standard 'tablespace':",
- 'config-oracle-temp-ts' => "Temporären 'tablespace':",
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-header-mysql' => 'MySQL-Astellungen',
- 'config-header-postgres' => 'PostgreSQL-Astellungen',
- 'config-header-sqlite' => 'SQLite-Astellungen',
- 'config-header-oracle' => 'Oracle-Astellungen',
- 'config-invalid-db-type' => 'Net valabelen Datebank-Typ',
- 'config-missing-db-name' => 'Dir musst en Numm fir de Wäert "Numm vun der Datebank" uginn',
- 'config-missing-db-host' => 'Dir musst e Wäert fir "Database host" uginn',
- 'config-missing-db-server-oracle' => 'Dir musst e Wäert fir "Datebank-TNS" uginn',
- 'config-db-sys-user-exists-oracle' => 'De Benotzerkont "$1" gëtt et schonn. SYSDBA kann nëmme benotzt gi fir en neie Benotzerkont opzemaachen.',
- 'config-postgres-old' => 'PostgreSQL $1 oder eng méi nei Versioun gëtt gebraucht, Dir hutt $2.',
- 'config-sqlite-name-help' => 'Sicht en Numm deen Är wiki identifizéiert.
-Benotzt keng Espacen a Bindestrécher.
-E gëtt fir den Numm vum SQLite Date-Fichier benotzt.',
- 'config-sqlite-readonly' => 'An de Fichier <code>$1</code> Kann net geschriwwe ginn.',
- 'config-sqlite-cant-create-db' => 'Den Datebank-Fichier <code>$1</code> konnt net ugeluecht ginn.',
- 'config-upgrade-done-no-regenerate' => "D'Aktualisatioun ass ofgeschloss.
-
-Dir kënnt elo [$1 ufänken Är Wiki ze benotzen]",
- 'config-regenerate' => 'LocalSettings.php regeneréieren →',
- 'config-db-web-account' => 'Datebankkont fir den Accès iwwer de Web',
- 'config-db-web-account-same' => 'Dee selwechte Kont wéi bei der Installatioun benotzen',
- 'config-db-web-create' => 'De Kont uleeë wann et e net scho gëtt',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-binary' => 'binär',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Numm vun der Wiki:',
- 'config-site-name-help' => 'Dësen daucht an der Titelleescht vum Browser an op verschiddenen anere Plazen op.',
- 'config-site-name-blank' => 'Gitt den Numm vum Site un.',
- 'config-project-namespace' => 'Projet Nummraum:',
- 'config-ns-generic' => 'Projet',
- 'config-ns-site-name' => 'Deeselwechte wéi den Numm vun der Wiki: $1',
- 'config-ns-other' => 'Anerer (spezifizéieren)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-admin-box' => 'Administrateurs-Kont',
- 'config-admin-name' => 'Ären Numm:',
- 'config-admin-password' => 'Passwuert:',
- 'config-admin-password-confirm' => 'Passwuert confirméieren:',
- 'config-admin-help' => 'Gitt w.e.g. Äre gewënschte Benotzernumm hei an, zum Beispill "Jang Muller".
-Dësen Numm gëtt da gebraucht fir sech an d\'Wiki anzeloggen.',
- 'config-admin-name-blank' => 'Gitt e Benotzernumm fir den Administrateur an.',
- 'config-admin-name-invalid' => 'De spezifizéierte Benotzernumm "<nowiki>$1</nowiki>" ass net valabel.
-Spezifizéiert en anere Benotzernumm.',
- 'config-admin-password-blank' => 'Gitt e Passwuert fir den Adminstateur-Kont an.',
- 'config-admin-password-same' => "D'Passwuert däerf net dat selwecht si wéi de Benotzernumm.",
- 'config-admin-password-mismatch' => 'Déi zwee Passwierder Déi Dir aginn hutt stëmmen net iwwereneen.',
- 'config-admin-email' => 'E-Mail-Adress:',
- 'config-admin-error-user' => 'Interne Feeler beim uleeë vun engem Administrateur mam Numm "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Interne Feeler beim Setze vum Passwuert fir den Admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Dir hutt eng E-Mail-Adress aginn déi net valabel ass',
- 'config-subscribe' => "Sech op d'[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ukënnegunge vun neie Versiounen] abonnéieren.",
- 'config-almost-done' => "Dir sidd bal fäerdeg!
-Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki elo direkt installéieren.",
- 'config-optional-continue' => 'Stellt mir méi Froen.',
- 'config-optional-skip' => "Ech hunn es genuch, installéier just d'Wiki.",
- 'config-profile' => 'Profil vun de Benotzerrechter:',
- 'config-profile-wiki' => 'Oppe Wiki',
- 'config-profile-no-anon' => 'Uleeë vun engem Benotzerkont verlaangt',
- 'config-profile-fishbowl' => 'Nëmmen autoriséiert Editeuren',
- 'config-profile-private' => 'Privat Wiki',
- 'config-license' => 'Copyright a Lizenz:',
- 'config-license-none' => 'Keng Lizenz ënnen op der Säit',
- 'config-license-pd' => 'Ëffentlechen Domaine',
- 'config-email-settings' => 'E-Mail-Astellungen',
- 'config-enable-email' => 'E-Mailen déi no bausse ginn aschalten',
- 'config-email-user' => 'Benotzer-op-Benotzer E-Mail aschalten',
- 'config-email-usertalk' => 'Benoriichtege bäi Ännerung vun der Benotzerdiskussiounssäit aschalten',
- 'config-email-watchlist' => 'Benoriichtigung vun der Iwwerwaachungslëscht aschalten',
- 'config-email-auth' => 'E-Mail-Authentifizéierung aschalten',
- 'config-email-sender' => 'E-Mailadress fir Äntwerten:',
- 'config-upload-settings' => 'Eropgeluede Biller a Fichieren',
- 'config-upload-enable' => 'Eropluede vu Fichieren aschalten',
- 'config-upload-deleted' => 'Repertoire fir geläscht Fichieren:',
- 'config-logo' => 'URL vum Logo:',
- 'config-instantcommons' => '"Instant Commons" aktivéieren',
- 'config-cc-again' => 'Nach eng kéier eraussichen...',
- 'config-advanced-settings' => 'Erweidert Astellungen',
- 'config-extensions' => 'Erweiderungen',
- 'config-install-step-done' => 'fäerdeg',
- 'config-install-step-failed' => 'huet net funktionéiert',
- 'config-install-extensions' => 'Mat den Ereiderungen',
- 'config-install-database' => 'Datebank gëtt installéiert',
- 'config-install-pg-plpgsql' => 'No der Sprooch PL/pgSQL sichen',
- 'config-pg-no-plpgsql' => "Fir d'Datebank $1 muss d'Datebanksprooch PL/pgSQL installéiert ginn",
- 'config-install-user' => 'Datebank Benotzer uleeën',
- 'config-install-user-alreadyexists' => 'De Benotzer "$1" gëtt et schonn!',
- 'config-install-user-create-failed' => 'D\'Opmaache vum Benotzer "$1" huet net funktionéiert: $2',
- 'config-install-user-grant-failed' => 'D\'Bäisetze vu Rechter fir de Benotzer "$1" huet net funktionéiert: $2',
- 'config-install-user-missing' => 'De Benotzer "$1" deen ugi gouf gëtt et net.',
- 'config-install-user-missing-create' => 'De spezifizéierte Benotzer "$1" gëtt et net.
-Klickt d\'Checkbox "Benotzerkont uleeën" wann Dir dee Benotzer uleeë wëllt.',
- 'config-install-tables' => 'Tabelle ginn ugeluecht',
- 'config-install-interwiki' => 'Standard Interwiki-Tabell gëtt ausgefëllt',
- 'config-install-interwiki-list' => 'De Fichier <code>interwiki.list</code> gouf net fonnt.',
- 'config-install-stats' => 'Initialisatioun vun de Statistiken',
- 'config-install-keys' => 'Generéiere vum Geheimschlëssel',
- 'config-install-sysop' => 'Administrateur Benotzerkont gëtt ugeluecht',
- 'config-install-mainpage' => 'Haaptsäit mat Standard-Inhalt gëtt ugeluecht',
- 'config-install-extension-tables' => "D'Tabelle fir déi aktivéiert Erweiderunge ginn ugeluecht",
- 'config-install-mainpage-failed' => "D'Haaptsäit konnt net dragesat ginn: $1",
- 'config-download-localsettings' => '<code>LocalSettings.php</code> eroflueden',
- 'config-help' => 'Hëllef',
- 'config-nofile' => 'De Fichier "$1" gouf net fonnt. Gouf e geläscht?',
- 'mainpagetext' => "'''MediaWiki gouf installéiert.'''",
- 'mainpagedocfooter' => "Kuckt w.e.g. [//meta.wikimedia.org/wiki/Help:Contents d'Benotzerhandbuch] fir Informatiounen iwwer de Gebruach vun der Wiki Software.
-
-== Fir unzefänken ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Hëllef bei der Konfiguratioun]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglëscht vun neie MediaWiki-Versiounen]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Lokaliséiert MediaWiki fir Är Sprooch]",
-);
-
-/** Lingua Franca Nova (Lingua Franca Nova)
- */
-$messages['lfn'] = array(
- 'mainpagetext' => "'''MediaWiki es aora instalada.'''",
- 'mainpagedocfooter' => 'Atenda la [//meta.wikimedia.org/wiki/Help:Contents Gida per Usores] per informa supra la usa de la programa de vici.
-
-== Comensa ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de ajustas de la desinia]
-* [//www.mediawiki.org/wiki/Manual:FAQ Demandas comun de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista per receta anunsias de novas supra MediaWiki]', # Fuzzy
-);
-
-/** Ganda (Luganda)
- * @author Kizito
- */
-$messages['lg'] = array(
- 'mainpagetext' => 'MediaWiki kati ewangidwa ku sisitemu yo',
- 'mainpagedocfooter' => "Okuyiga ku nkozesa ya sofutiweya owa wiki, kebera [//meta.wikimedia.org/wiki/Help:Contents Okulagirira Abakozesa].
-
-== Amagezi agakuyamba okutandika ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lukalala lw'eby'enteekateeka yo]
-* [//www.mediawiki.org/wiki/Manual:FAQ Ebiter'okubuuzibwa ku MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Wewandise ofunenga amawulire aga email ag'ebifa ku MediaWiki]", # Fuzzy
-);
-
-/** Limburgish (Limburgs)
- */
-$messages['li'] = array(
- 'mainpagetext' => "'''MediaWiki software succesvol geïnsjtalleerd.'''",
- 'mainpagedocfooter' => "Raodpleeg de [//meta.wikimedia.org/wiki/NL_Help:Inhoudsopgave handjleiding] veur informatie euver 't gebroek van de wikisoftware.
-
-== Mieë hölp ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lies mit instellinge]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki VGV (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki mailinglies veur nuuj versies]", # Fuzzy
-);
-
-/** Lao (ລາວ)
- */
-$messages['lo'] = array(
- 'mainpagetext' => "'''ຕິດຕັ້ງມີເດຍວິກິນີ້ສຳເລັດແລ້ວ.'''",
-);
-
-/** Lithuanian (lietuvių)
- * @author Eitvys200
- * @author Mantak111
- */
-$messages['lt'] = array(
- 'config-desc' => 'MediaWiki diegimas',
- 'config-title' => 'MediaWiki $1 diegimas',
- 'config-information' => 'Informacija',
- 'config-your-language' => 'Jūsų kalba:',
- 'config-wiki-language' => 'Viki kalba:',
- 'config-back' => '← Atgal',
- 'config-continue' => 'Toliau →',
- 'config-page-language' => 'Kalba',
- 'config-page-welcome' => 'Sveiki atvykę į MediaWiki!',
- 'config-page-dbconnect' => 'Prisijungti prie duomenų bazės',
- 'config-page-dbsettings' => 'Duomenų bazės nustatymai',
- 'config-page-name' => 'Vardas',
- 'config-page-options' => 'Parinktys',
- 'config-page-install' => 'Įdiegti',
- 'config-page-complete' => 'Baigta!',
- 'config-page-restart' => 'Iš naujo paleiskite diegimą',
- 'config-page-readme' => 'Perskaityk manę',
- 'config-page-copying' => 'Kopijuojama',
- 'config-page-upgradedoc' => 'Atnaujinama',
- 'config-restart' => 'Taip, paleiskite jį iš naujo',
- 'config-env-php' => 'PHP $1 yra įdiegtas.',
- 'config-env-php-toolow' => 'PHP $1 įdiegta.
-Tačiau, MediaWiki reikia PHP $2 ar naujesnės.',
- 'config-db-type' => 'Duomenų bazės tipas:',
- 'config-db-host' => 'Duomenų bazės serveris:',
- 'config-db-name' => 'Duomenų bazės pavadinimas:',
- 'config-db-name-oracle' => 'Duomenų bazės schema:',
- 'config-db-username' => 'Duomenų bazės vartotojo vardas:',
- 'config-db-password' => 'Duomenų bazės slaptažodis:',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-db-port' => 'Duomenų bazės prievadas:',
- 'config-db-schema' => 'MediaWiki schema:',
- 'config-header-mysql' => 'MySQL nustatymai',
- 'config-header-postgres' => 'PostgreSQL nustatymai',
- 'config-header-sqlite' => 'SQLite nustatymai',
- 'config-header-oracle' => 'Oracle nustatymai',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Viki pavadinimas:',
- 'config-site-name-blank' => 'Įveskite svetainės pavadinimą.',
- 'config-project-namespace' => 'Projekto pavadinimas:',
- 'config-ns-generic' => 'Projektas',
- 'config-ns-site-name' => 'Toks pat kaip viki pavadinimas: $1',
- 'config-ns-other-default' => 'ManoWiki',
- 'config-admin-box' => 'Administratoriaus paskyra',
- 'config-admin-name' => 'Jūsų vardas:',
- 'config-admin-password' => 'Slaptažodis:',
- 'config-admin-password-confirm' => 'Slaptažodis dar kartą:',
- 'config-admin-name-blank' => 'Įveskite administratoriaus vartotojo vardą.',
- 'config-admin-password-blank' => 'Įvesti administratoriaus paskyros slaptažodį.',
- 'config-admin-password-same' => 'Slaptažodis turi būti ne toks pat, kaip vartotojo vardas.',
- 'config-admin-password-mismatch' => 'Įvesti slaptažodžiai nesutampa.',
- 'config-admin-email' => 'El. pašto adresas:',
- 'config-optional-continue' => 'Paklausti daugiau klausimų.',
- 'config-optional-skip' => 'Man jau nuobodu, tiesiog įdiekite viki.',
- 'config-profile' => 'Vartotojo teisių paskyra:',
- 'config-profile-wiki' => 'Tradicinė viki', # Fuzzy
- 'config-profile-private' => 'Privati viki',
- 'config-license-pd' => 'Viešas Domenas',
- 'config-email-settings' => 'El. pašto nustatymai',
- 'config-upload-enable' => 'Įgalinti failų įkėlimus',
- 'config-logo' => 'Logotipo URL:',
- 'config-cc-again' => 'Pasirinkti dar kartą...',
- 'config-extensions' => 'Plėtiniai',
- 'config-install-step-done' => 'atlikta',
- 'config-install-step-failed' => 'nepavyko',
- 'config-install-schema' => 'Kuriama schema',
- 'config-install-tables' => 'Kuriamos lentelės',
- 'config-install-stats' => 'Inicijuojamos statistikos',
- 'config-install-keys' => 'Generuojami slapti raktai',
- 'config-install-done' => "'''Sveikiname!'''
-Jūs sėkmingai įdiegėte MediaWiki.
-
-Įdiegimo programa sukūrė <code>LocalSettings.php</code> failą.
-Jame yra visos jūsų konfigūracijos.
-
-Jums reikės atsisiųsti ir įdėti jį į savo wiki įdiegimo bazę (pačiame kataloge, kaip index.php). Atsisiuntimas turėtų prasidėti automatiškai.
-
-Jei atsisiuntimas nebuvo pasiūlytas, arba jį atšaukėte, galite iš naujo atsisiųsti paspaudę žemiau esančią nuorodą:
-
-$3
-
-'''Pastaba:''' Jei jūs to nepadarysite dabar, tada šis sukurtas konfigūracijos failas nebus galimas vėliau, jei išeisite iš įdiegimo be atsisiuntimo.
-
-Kai baigsite, jūs galėsite '''[$2 įeiti į savo wiki]'''.",
- 'config-download-localsettings' => 'Atsisiųsti <code>LocalSettings.php</code>',
- 'config-help' => 'pagalba',
- 'mainpagetext' => "'''MediaWiki sėkmingai įdiegta.'''",
- 'mainpagedocfooter' => 'Informacijos apie wiki programinės įrangos naudojimą, ieškokite [//meta.wikimedia.org/wiki/Help:Contents žinyne].
-
-== Pradžiai ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigūracijos nustatymų sąrašas]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki DUK]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki pranešimai paštu apie naujas versijas]', # Fuzzy
-);
-
-/** Latvian (latviešu)
- * @author GreenZeb
- */
-$messages['lv'] = array(
- 'config-back' => '← Atpakaļ',
- 'config-continue' => 'Turpināt →',
- 'config-page-language' => 'Valoda',
- 'config-page-welcome' => 'Laipni lūdzam MediaWiki!',
- 'config-page-dbconnect' => 'Savienoties ar datubāzi',
- 'config-page-upgrade' => 'Atjaunināt pašreizējo instalāciju',
- 'config-page-dbsettings' => 'Datubāzes iestatījumi',
- 'config-page-name' => 'Vārds',
- 'config-page-options' => 'Iespējas',
- 'config-page-install' => 'Instalēt',
- 'config-page-complete' => 'Pabeigts!',
- 'config-page-restart' => 'Pārstartēt instalāciju',
- 'config-page-readme' => 'Lasīt mani',
- 'config-page-releasenotes' => 'Informācija par laidienu',
- 'mainpagetext' => "'''MediaWiki veiksmīgi ieinstalēts'''",
- 'mainpagedocfooter' => 'Izlasi [//meta.wikimedia.org/wiki/Help:Contents Lietotāja pamācību], lai iegūtu vairāk informācijas par Wiki programmatūras lietošanu.
-
-== Pirmie soļi ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigurācijas iespēju saraksts]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki J&A]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Parakstīties uz paziņojumiem par jaunām MediaWiki versijām]', # Fuzzy
-);
-
-/** Literary Chinese (文言)
- */
-$messages['lzh'] = array(
- 'mainpagetext' => "'''共筆臺已立'''",
- 'mainpagedocfooter' => "欲識維基,見[//meta.wikimedia.org/wiki/Help:Contents User's Guide]
-
-== 始 ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Lazuri (Lazuri)
- * @author Bombola
- */
-$messages['lzz'] = array(
- 'mainpagetext' => "'''Mediawiki dido k'ai ik'idu.'''",
- 'mainpagedocfooter' => "Vik'i şeni muç'o ixmarinen ya mutxanepe oguru şeni [//meta.wikimedia.org/wiki/Help:Contents oxmaruşi rexberis] o3'k'edit.
-
-== Ağani na gyoç’k’u maxmarepe ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Ok'iduşi ayarepeşi liste]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki P'anda Na-k'itxu K'itxalape]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-mailepeşiş liste]", # Fuzzy
-);
-
-/** Maithili (मैथिली)
- * @author Umeshberma
- */
-$messages['mai'] = array(
- 'mainpagetext' => "'''मीडियाविकी नीक जकाँ प्रस्थापित भेल।'''",
- 'mainpagedocfooter' => "सम्पर्क करू [//meta.wikimedia.org/wiki/Help:Contents User's Guide] विकी तंत्रांशक प्रयोगक जानकारी लेल।
-
-==प्रारम्भ कोना करी==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Moksha (мокшень)
- */
-$messages['mdf'] = array(
- 'mainpagetext' => "'''МедиаВикить арафтозь лац.'''",
- 'mainpagedocfooter' => 'Ванк [//meta.wikimedia.org/wiki/Help:Contents Ветямовал Тиинди] тяса ули кода содамс Вики програпнень эрявикснень колга.
-
-== Эрявикс сюлмафксне ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Васьфневи арафнематнень кярькссь]
-* [//www.mediawiki.org/wiki/Manual:FAQ МедиаВикить Сидеста Кеподеви Кизефксне]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаВикить од верзиятнень колга кулянь пачфтема]', # Fuzzy
-);
-
-/** Malagasy (Malagasy)
- * @author Jagwar
- */
-$messages['mg'] = array(
- 'config-session-error' => 'Hadisoana teo am-panombohana ny fidirana : $1',
- 'config-your-language' => 'Ny fiteninao :',
- 'config-wiki-language' => "Fiteny ho ampiasain'ny wiki :",
- 'config-back' => '← Miverina',
- 'config-continue' => 'Manohy →',
- 'config-page-language' => 'Fiteny',
- 'config-page-welcome' => "Tonga soa eto amin'i MediaWiki !",
- 'config-page-dbconnect' => "Hiditra eo amin'i banky angona",
- 'config-page-name' => 'Anarana',
- 'config-page-readme' => 'Vakio aho',
- 'config-page-copying' => 'Hala-tahaka',
- 'config-page-upgradedoc' => 'Fanavaozina',
- 'config-page-existingwiki' => 'Wiki efa misy',
- 'config-help-restart' => "Tianao hofafana avokoa ve ny data voaangona natsofokao ary hamerina ny fizotran'ny fametrahana ?",
- 'config-restart' => 'Eny, avereno atao',
- 'config-db-username' => "Anaram-pikamban'ny banky angona :",
- 'config-db-password' => "Tenimiafin'ny banky angona :",
- 'config-header-mysql' => "Parametatr'i MySQL",
- 'config-header-sqlite' => "Parametatr'i SQLite",
- 'config-header-oracle' => "Parametatr'i Oracle",
- 'config-mysql-innodb' => 'innoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-ns-generic' => 'Tetikasa',
- 'config-ns-other' => 'Hafa (lazao)',
- 'config-admin-name' => 'Ny anaranao :',
- 'config-admin-password' => 'Tenimiafina :',
- 'config-admin-email' => 'Adiresy imailaka :',
- 'config-profile-wiki' => 'Wiki tsotra', # Fuzzy
- 'config-profile-no-anon' => 'Mila mamorona kaonty',
- 'config-profile-fishbowl' => 'Mpanova mahazo alalana ihany',
- 'config-profile-private' => 'Wiki tsy sarababem-bahoaka',
- 'config-license' => 'Zom-pamorona ary lisansa :',
- 'config-license-none' => 'Tsy misy lisansa any an-tongom-pejy',
- 'config-email-user' => 'Avela mifandefa imailaka ny mpikambana',
- 'config-email-user-help' => "Hahafahan'ny mpikambana mifandefa imailaka raha omen'ny mpikambana alalana ao amin'ny safidiny.",
- 'config-upload-deleted' => "Petra-drakitra ho an'ny rakitra voafafa :",
- 'config-extensions' => 'Fanitarana',
- 'config-install-step-done' => 'vita',
- 'config-install-step-failed' => 'hadisoana',
- 'config-install-user' => "Famoronana mpapiasan'ny banky angona",
- 'config-install-tables' => 'Famoronana tabilao',
- 'config-install-stats' => 'Fanombohana ny statistika',
- 'config-install-keys' => 'Fanamboarana lakile miafina',
- 'config-help' => 'fanoroana',
- 'mainpagetext' => "'''Tafajoro soa aman-tsara ny rindrankajy Wiki.'''",
- 'mainpagedocfooter' => "Vangio ny [//meta.wikimedia.org/wiki/Aide:Contenu Fanoroana ho an'ny mpampiasa] ra te hitady fanoroana momba ny fampiasan'ity rindrankajy ity.
-
-== Hanomboka amin'ny MediaWiki ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lisitra ny paramètre de configuration]
-* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ momba ny MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Resaka momba ny fizaràn'ny MediaWiki]", # Fuzzy
-);
-
-/** Eastern Mari (олык марий)
- */
-$messages['mhr'] = array(
- 'mainpagetext' => "'''MediaWiki сай шындыме.'''",
-);
-
-/** Minangkabau (Baso Minangkabau)
- * @author Iwan Novirion
- * @author Luthfi94
- */
-$messages['min'] = array(
- 'mainpagetext' => "'''MediaWiki alah tapasang jo sukses'''.",
- 'mainpagedocfooter' => 'Konsultasian [//meta.wikimedia.org/wiki/Help:Contents/min Panduan Panggunoan] untuak informasi caro panggunoan parangkaik lunak wiki.
-
-== Mamulai panggunoan ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings/id Daftar pangaturan konfigurasi]
-* [//www.mediawiki.org/wiki/Manual:FAQ/id Daftar patanyoan nan acok diajukan manganai MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Palokalan MediaWiki untuak bahaso Sanak]',
-);
-
-/** Macedonian (македонски)
- * @author Bjankuloski06
- * @author 아라
- */
-$messages['mk'] = array(
- 'config-desc' => 'Инсталатор на МедијаВики',
- 'config-title' => 'Инсталатор на МедијаВики $1',
- 'config-information' => 'Информации',
- 'config-localsettings-upgrade' => 'Востановена е податотека <code>LocalSettings.php</code>.
-За да ја надградите инсталцијава, внесете ја вредноста на <code>$wgUpgradeKey</code> во полето подолу.
-Тоа е го најдете во <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Утврдено е присуството на податотеката „<code>LocalSettings.php</code>“.
-За да ја надградите инсталацијата, пуштете ја „<code>update.php</code>“ наместо горенаведената.',
- 'config-localsettings-key' => 'Надградбен клуч:',
- 'config-localsettings-badkey' => 'Клучот што го наведовте е погрешен',
- 'config-upgrade-key-missing' => 'Востановена е постоечка инсталација на МедијаВики.
-За да ја надградите, вметнете го следниов ред на дното од вашата страница <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Постоечката страница <code>LocalSettings.php</code> е нецелосна.
-Не е поставена променливата $1.
-Изменете ја страницата <code>LocalSettings.php</code> така што ќе ѝ зададете вредност на променливата, па стиснете на „{{int:Config-continue}}“.',
- 'config-localsettings-connection-error' => 'Се појави грешка при поврзувањето со базата користејќи ги поставките назначени во <code>LocalSettings.php</code> или <code>AdminSettings.php</code>. Исправете ги овие поставки и обидете се повторно.
-
-$1',
- 'config-session-error' => 'Грешка при започнување на сесијата: $1',
- 'config-session-expired' => 'Вашите сесиски податоци истекоа.
-Поставките на сесиите траат $1.
-Нивниот рок можете да го зголемите со задавање на <code>session.gc_maxlifetime</code> во php.ini.
-Почнете ја инсталацијата одново.',
- 'config-no-session' => 'Вашите сесиски податоци се изгубени!
-Погледајте во php.ini дали <code>session.save_path</code> е поставен во правилна папка.',
- 'config-your-language' => 'Вашиот јазик:',
- 'config-your-language-help' => 'Одберете на кој јазик да се одвива инсталацијата.',
- 'config-wiki-language' => 'Јазик на викито:',
- 'config-wiki-language-help' => 'Одберете на кој јазик ќе бидат содржините на викито.',
- 'config-back' => '← Назад',
- 'config-continue' => 'Продолжи →',
- 'config-page-language' => 'Јазик',
- 'config-page-welcome' => 'Добредојдовте на МедијаВики!',
- 'config-page-dbconnect' => 'Поврзување со базата',
- 'config-page-upgrade' => 'Надградба на постоечката инсталација',
- 'config-page-dbsettings' => 'Нагодувања на базата',
- 'config-page-name' => 'Назив',
- 'config-page-options' => 'Поставки',
- 'config-page-install' => 'Инсталирај',
- 'config-page-complete' => 'Готово!',
- 'config-page-restart' => 'Пушти ја инсталацијата одново',
- 'config-page-readme' => 'Прочитај ме',
- 'config-page-releasenotes' => 'Белешки за изданието',
- 'config-page-copying' => 'Копирање',
- 'config-page-upgradedoc' => 'Надградба',
- 'config-page-existingwiki' => 'Постоечко вики',
- 'config-help-restart' => 'Дали сакате да ги исчистите сите зачувани податоци што ги внесовте и да ја започнете инсталацијата одново?',
- 'config-restart' => 'Да, почни одново',
- 'config-welcome' => '=== Проверки на околината ===
-Сега ќе се извршиме основни проверки за да се востанови дали околината е погодна за инсталирање на МедијаВики. Не заборавајте да ги приложите овие информации ако барате помош со довршување на инсталацијата.',
- 'config-copyright' => "=== Авторски права и услови ===
-
-$1
-
-Ова е слободна програмска опрема (free software); можете да го редистрибуирате и/или менувате согласно условите на ГНУ-овата општа јавна лиценца (GNU General Public License) на Фондацијата за слободна програмска опрема (Free Software Foundation); верзија 2 или било која понова верзија на лиценцата (по ваш избор).
-
-Овој програм се нуди со надеж дека ќе биде корисен, но '''без никаква гаранција'''; дури ни подразбраната гаранција за '''продажна способност''' или '''погодност за определена цел'''.
-Повеќе информации ќе најдете во текстот на ГНУ-овата општа јавна лиценца.
-
-Би требало да имате добиено <doclink href=Copying>примерок од ГНУ-овата општа јавна лиценца</doclink> заедно со програмов; ако немате добиено, тогаш пишете ни на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. или [http://www.gnu.org/copyleft/gpl.html прочитајте ја тука].",
- 'config-sidebar' => '* [//www.mediawiki.org Домашна страница на МедијаВики]
-* [//www.mediawiki.org/wiki/Help:Contents Водич за корисници]
-* [//www.mediawiki.org/wiki/Manual:Contents Водич за администратори]
-* [//www.mediawiki.org/wiki/Manual:FAQ ЧПП]
-----
-* <doclink href=Readme>Прочитај ме</doclink>
-* <doclink href=ReleaseNotes>Белешки за изданието</doclink>
-* <doclink href=Copying>Копирање</doclink>
-* <doclink href=UpgradeDoc>Надградување</doclink>',
- 'config-env-good' => 'Околината е проверена.
-Можете да го инсталирате МедијаВики.',
- 'config-env-bad' => 'Околината е проверена.
-Не можете да го инсталирате МедијаВики.',
- 'config-env-php' => 'PHP $1 е инсталиран.',
- 'config-env-php-toolow' => 'PHP $1 е инсталиран.
-Меѓутоа, МедијаВики бара PHP $2 или поново.',
- 'config-unicode-using-utf8' => 'Со utf8_normalize.so за уникодна нормализација од Брајон Вибер (Brion Vibber).',
- 'config-unicode-using-intl' => 'Со додатокот [http://pecl.php.net/intl intl PECL] за уникодна нормализација.',
- 'config-unicode-pure-php-warning' => "'''Предупредување''': Додатокот [http://pecl.php.net/intl intl PECL] не е достапен за врши уникодна нормализација, враќајќи се на бавна примена на чист PHP.
-
-Ако имате високопрометно мрежно место, тогаш ќе треба да прочитате повеќе за [//www.mediawiki.org/wiki/Unicode_normalization_considerations уникодната нормализација].",
- 'config-unicode-update-warning' => "'''Предупредување''': Инсталираната верзија на обвивката за уникодна нормализација користи постара верзија на библиотеката на [http://site.icu-project.org/ проектот ICU].
-За да користите Уникод, ќе треба да направите [//www.mediawiki.org/wiki/Unicode_normalization_considerations надградба].",
- 'config-no-db' => 'Не можев да пронајдам соодветен двигател за базата на податоци! Ќе треба да инсталирате двигател за базата на податоци за PHP.
-Поддржани се следниве типови на бази $1.
-
-Ако сте на заедничко (споделено) вдомување, побарајте му на вдомителот да инсталира соодветен двигател за базата.
-Ако вие самите го составивте ова PHP, сменете ги поставките така што ќе овозможите клиент на базата - на пр. со кодот <code>./configure --with-mysql</code>.
-Ако инсталиравте PHP од пакет на Debian или Ubuntu, тогаш ќе треба да го инсталирате и модулот php5-mysql.',
- 'config-outdated-sqlite' => "'''Предупредување''': имате SQLite $1. Најстарата допуштена верзија е $2. Затоа, SQLite ќе биде недостапен.",
- 'config-no-fts3' => "'''Предупредување''': SQLite iе составен без модулот [//sqlite.org/fts3.html FTS3] - за оваа база нема да има можност за пребарување.",
- 'config-register-globals' => "'''Предупредување: Можноста <code>[http://php.net/register_globals register_globals]</code> за PHP е овозможена.'''
-'''Оневозможете ја ако е можно.'''
-МедијаВики ќе работи, но опслужувачот ви е изложен на безбедносни ризици.",
- 'config-magic-quotes-runtime' => "'''Кобно: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] е активно!'''
-Оваа можност непредвидливо го расипува вносот на податоци.
-Оваа можност мора да е исклучена. Во спротивно нема да можете да го инсталирате и користите МедијаВики.",
- 'config-magic-quotes-sybase' => "'''Кобно: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] е активно!'''
-Оваа можност непредвидливо го расипува вносот на податоци.
-Оваа можност мора да е исклучена. Во спротивно нема да можете да го инсталирате и користите МедијаВики.",
- 'config-mbstring' => "'''Кобно: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] е активно!'''
-Оваа можност предизвикува грешки и може непредвидиво да го расипува вносот на податоци.
-Оваа можност мора да е исклучена. Во спротивно нема да можете да го инсталирате и користите МедијаВики.",
- 'config-ze1' => "'''Кобно: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] е активно!'''
-Оваа можност предизвикува ужасни грешки во МедијаВики.
-Оваа можност мора да е исклучена. Во спротивно нема да можете да го инсталирате и користите МедијаВики.",
- 'config-safe-mode' => "'''Предупредување:''' [http://www.php.net/features.safe-mode безбедниот режим] на PHP е активен.
-Ова може да предизвика проблеми, особено ако користите подигања и поддршка за <code>math</code>.",
- 'config-xml-bad' => 'XML-модулот за PHP недостасува.
-МедијаВики има потреба од функции во овој модул и нема да работи со овие поставки.
-Ако работите со Mandrake, инсталирајте го php-xml пакетот.',
- 'config-pcre' => 'Недостасува модулот за поддршка на PCRE.
-МедијаВики не може да работи без функции за регуларни изрази соодветни на Perl.',
- 'config-pcre-no-utf8' => "'''Фатално''': PCRE-модулот на PHP е составен без поддршка за PCRE_UTF8.
-МедијаВики бара поддршка за UTF-8 за да може да работи правилно.",
- 'config-memory-raised' => '<code>memory_limit</code> за PHP изнесува $1, зголемен на $2.',
- 'config-memory-bad' => "'''Предупредување:''' <code>memory_limit</code> за PHP изнесува $1.
-Ова е веројатно премалку.
-Инсталацијата може да не успее!",
- 'config-ctype' => "'''Фатална грешка''': PHP мора да се состави со поддршка за [http://www.php.net/manual/en/ctype.installation.php додатокот Ctype].",
- 'config-json' => "'''Кобно:''' PHP беше срочен без поддршка од JSON.
-Ќе мора да го инсталирате додатокот за JSON во PHP, или додатокот [http://pecl.php.net/package/jsonc PECL jsonc] пред да го инсталирате МедијаВики.
-* Додатокот за PHP е вклучен во верзиите 5 и 6 на Linux (од Red Hat Enterprise) (CentOS), но мора да се активира во <code>/etc/php.ini</code> или <code>/etc/php.d/json.ini</code>.
-* Некои варијанти на Linux излезени по мај 2013 г. не го содржат додатокот за PHP, туку го пакуваат додатокот PECL како <code>php5-json</code> или <code>php-pecl-jsonc</code>.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] е инсталиран',
- 'config-apc' => '[http://www.php.net/apc APC] е инсталиран',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] е инсталиран',
- 'config-no-cache' => "'''Предупредување:''' Не можев да го најдам [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
-Кеширањето на објекти не е овозможено.",
- 'config-mod-security' => "'''Предупредување''': на вашиот опслужувач има овозможено [http://modsecurity.org/ mod_security]. Ако не е поставено како што треба, ова може да предизвика проблеми кај МедијаВики и други програми што им овозможуваат на корисниците да објавуваат произволни содржини.
-Погледнете ја [http://modsecurity.org/documentation/ mod_security документацијата] или обратете се кај домаќинот ако наидете на случајни грешки.",
- 'config-diff3-bad' => 'GNU diff3 не е пронајден.',
- 'config-git' => 'Го пронајдов Git програмот за контрола на верзии: <code>$1</code>.',
- 'config-git-bad' => 'Не го пронајдов Git-програмот за контрола на верзии.',
- 'config-imagemagick' => 'Пронајден е ImageMagick: <code>$1</code>.
-Ако овозможите подигање, тогаш ќе биде овозможена минијатуризација на сликите.',
- 'config-gd' => 'Утврдив дека има вградена GD графичка библиотека.
-Ако овозможите подигање, тогаш ќе биде овозможена минијатураизација на сликите.',
- 'config-no-scaling' => 'Не можев да пронајдам GD-библиотека или ImageMagick.
-Минијатуризацијата на сликите ќе биде оневозможена.',
- 'config-no-uri' => "'''Грешка:''' Не можев да го утврдам тековниот URI.
-Инсталацијата е откажана.",
- 'config-no-cli-uri' => "'''Предупредување''': Нема наведено --scriptpath. Ќе се користи основниот: <code>$1</code>.",
- 'config-using-server' => 'Користите опслужувач под името „<nowiki>$1</nowiki>“.',
- 'config-using-uri' => 'Користите опслужувач со URL-адреса „<nowiki>$1$2</nowiki>“.',
- 'config-uploads-not-safe' => "'''Предупредување:''' Вашата матична папка за подигање <code>$1</code> е подложна на извршување (пуштање) на произволни скрипти.
-Иако МедијаВики врши безбедносни проверки на сите подигнати податотеки, ве советуваме [//www.mediawiki.org/wiki/Manual:Security#Upload_security да ја затворите оваа безбедносна дупка] пред да овозможите подигање.",
- 'config-no-cli-uploads-check' => "'''Предупредување:''' Вашата основна папка за подигања (<code>$1</code>) не е проверена дали е подложна
-произволно извршување на скрипти во текот на инсталацијата на посредникот на повикувачко ниво (CLI).",
- 'config-brokenlibxml' => 'Вашиот систем има комбинација од PHP и libxml2 верзии и затоа има грешки и може да предизвика скриено расипување на податоците кај МедијаВики и други мрежни програми.
-Надградете го на PHP 5.2.9 и libxml2 2.7.3 или нивни понови верзии! ПРЕКИНУВАМ ([//bugs.php.net/bug.php?id=45996 грешката е заведена во PHP]).',
- 'config-using531' => 'МедијаВики не може да се користи со PHP $1 поради грешка кај упатните параметри за <code>__call()</code>.
-За да го решите проблемот, надградете го на PHP 5.3.2 или понова верзија, или пак користете го постариот PHP 5.3.0.',
- 'config-suhosin-max-value-length' => 'Suhosin е инсталиран и ја ограничува должината на параметарот GET на $1 бајти. Делот ResourceLoader на МедијаВики ќе ја заобиколува ова граница, но со тоа ќе се влоши делотворноста. Ако е воопшто можно, на <code>suhosin.get.max_value_length</code> треба да го наместите на 1024 или повеќе во <code>php.ini</code>, и да му ја зададете истата вредност на <code>$wgResourceLoaderMaxQueryLength</code> во <code>LocalSettings.php</code>.',
- 'config-db-type' => 'Тип на база:',
- 'config-db-host' => 'Домаќин на базата:',
- 'config-db-host-help' => 'Ако вашата база е на друг опслужувач, тогаш тука внесете го името на домаќинот или IP-адресата.
-
-Ако користите заедничко (споделено) вдомување, тогаш вашиот вдомител треба да го наведе точното име на домаќинот во неговата документација.
-
-Ако инсталирате на опслужувач на Windows и користите MySQL, можноста „localhost“ може да не функционира за опслужувачкото име. Во тој случај, обидете се со внесување на „127.0.0.1“ како локална IP-адреса.
-
-Ако користите PostgreSQL, оставете го полево празно за да се поврзете преку Unix-приклучок.',
- 'config-db-host-oracle' => 'TNS на базата:',
- 'config-db-host-oracle-help' => 'Внесете важечко [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm локално име за поврзување]. На оваа инсталација мора да ѝ биде видлива податотеката tnsnames.ora.<br />Ако користите клиентски библиотеки 10g или понови, тогаш можете да го користите и методот на иметнување на [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Идентификувај го викиво',
- 'config-db-name' => 'Име на базата:',
- 'config-db-name-help' => 'Одберете име што ќе го претставува вашето вики.
-Името не смее да содржи празни места.
-
-Ако користите заедничко (споделено) вдомување, тогаш вашиот вдомител ќе ви даде конкретно име на база за користење, или пак ќе ви даде да создавате бази преку контролната табла.',
- 'config-db-name-oracle' => 'Шема на базата:',
- 'config-db-account-oracle-warn' => 'Постојат три поддржани сценарија за инсталирање на Oracle како базен услужник:
-
-Ако сакате да создадете сметка на базата како дел од постапката за инсталација, наведете сметка со SYSDBA-улога како сметка за базата што ќе се инсталира и наведете ги саканите податоци за сметката за мрежен пристап. Во друг случај, можете да создадете сметка за мрежен пристап рачно и да ја наведете само таа сметка (ако има дозволи за создавање на шематски објекти) или пак да наведете две различни сметки, една со привилегии за создавање, а друга (ограничена) за мрежен пристап.
-
-Скриптата за создавање сметка со задолжителни привилегии ќе ја најдете во папката „maintenance/oracle/“ од оваа инсталација. Имајте на ум дека ако користите ограничена сметка ќе ги оневозможите сите функции за одржување со основната сметка.',
- 'config-db-install-account' => 'Корисничка смета за инсталација',
- 'config-db-username' => 'Корисничко име за базата:',
- 'config-db-password' => 'Лозинка за базата:',
- 'config-db-password-empty' => 'Внесете лозинка за новиот корисник на базата: $1.
-Иако може да се создаваат корисници без лозинка, тоа не е безбедно.',
- 'config-db-install-username' => 'Внесете корисничко име што ќе се користи за поврзување со базата во текот на инсталацијата. Ова не е корисничкото име од сметката на МедијаВики, туку посебно корисничко име за вашата база на податоци.',
- 'config-db-install-password' => 'Внесете клозинка што ќе се користи за поврзување со базата во текот на инсталацијата. Ова не е лозинката од сметката на МедијаВики, туку посебна лозинка за вашата база на податоци.',
- 'config-db-install-help' => 'Внесете го корисничкото име и лозинката што ќе се користи за поврзување со базата на податоци во текот на инсталацијата.',
- 'config-db-account-lock' => 'Користи го истото корисничко име и лозинка за редовна работа',
- 'config-db-wiki-account' => 'Корисничко име за редовна работа',
- 'config-db-wiki-help' => 'Внесете корисничко име и лозинка што ќе се користат за поврзување со базата на податоци во текот на редовната работа со викито.
-Ако сметката не постои, а инсталационата сметка има доволно привилегии, тогаш оваа корисничка сметка ќе биде создадена со минималните привилегии потребни за работа со викито.',
- 'config-db-prefix' => 'Префикс на табелата на базата:',
- 'config-db-prefix-help' => 'Ако треба да делите една база на податоци со повеќе викија, или со МедијаВики и друг мрежен програм, тогаш можете да додадете префикс на сите називи на табелите за да спречите проблематични ситуации.
-Не користете празни простори.
-
-Ова поле обично се остава празно.',
- 'config-db-charset' => 'Збир знаци за базата',
- 'config-charset-mysql5-binary' => 'Бинарен за MySQL 4.1/5.0',
- 'config-charset-mysql5' => 'UTF-8 за MySQL 4.1/5.0',
- 'config-charset-mysql4' => 'Назадно-соодветен UTF-8 за MySQL 4.0',
- 'config-charset-help' => "'''ПРЕДУПРЕДУВАЊЕ:''' Ако користите '''назадно-соодветен UTF-8''' во MySQL 4.1+, а потоа направите резервен примерок на базата со <code>mysqldump</code>, ова може да ги опустоши сите не-ASCII знаци, и со тоа неповратно да ја расипе целата зачувана резерва!
-
-Во '''бинарен режим''', во базата МедијаВики го складира UTF-8 текстот во бинарни полиња.
-Ова е поефикансно отколку UTF-8 режимот на MySQL бидејќи ви овозможува да го користите целиот спектар на уникодни знаци.
-Во '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори,
-но нема да ви дозволи да складирате знаци над [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
- 'config-mysql-old' => 'Се бара MySQL $1 или поново, а вие имате $2.',
- 'config-db-port' => 'Порта на базата:',
- 'config-db-schema' => 'Шема за МедијаВики',
- 'config-db-schema-help' => 'Оваа шема обично по правило ќе работи нормално.
-Сменете ја само ако знаете дека треба да се смени.',
- 'config-pg-test-error' => "Не можам да се поврзам со базата '''$1''': $2",
- 'config-sqlite-dir' => 'Папка на SQLite-податоци:',
- 'config-sqlite-dir-help' => "SQLite ги складира сите податоци во една податотека.
-
-Папката што ќе ја наведете мора да е запислива од мрежниот опслужувач во текот на инсталацијата.
-
-Таа '''не''' смее да биде достапна преку интернет, и затоа не ја ставаме кајшто ви се наоѓаат PHP-податотеките.
-
-Инсталаторот воедно ќе создаде податотека <code>.htaccess</code>, но ако таа не функционира како што треба, тогаш некој ќе може да ви влезе во вашата необработена (сирова) база на податоци.
-Тука спаѓаат необработени кориснички податоци (е-поштенски адреси, хеширани лозинки) како и избришани ревизии и други податоци за викито до кои се има ограничен пристап.
-
-Се препорачува целата база да ја сместите некаде, како на пр. <code>/var/lib/mediawiki/вашетовики</code>.",
- 'config-oracle-def-ts' => 'Стандарден таблеарен простор:',
- 'config-oracle-temp-ts' => 'Привремен табеларен простор:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'МедијаВики ги поддржува следниве системи на бази на податоци:
-
-$1
-
-Ако системот што сакате да го користите не е наведен подолу, тогаш проследете ја горенаведената врска со инструкции за да овозможите поддршка за тој систем.',
- 'config-support-mysql' => '* $1 е главната цел на МедијаВики и најдобро се поддржува ([http://www.php.net/manual/en/mysql.installation.php како се составува PHP со поддршка за MySQL])',
- 'config-support-postgres' => '* $1 е популарен систем на бази на податоци со отворен код кој претставува алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php како да составите PHP со поддршка за PostgreSQL]). Може сè уште да има некои грешки. па затоа не се препорачува за употреба во производна средина.',
- 'config-support-sqlite' => '* $1 е лесен систем за бази на податоци кој е многу добро поддржан. ([http://www.php.net/manual/en/pdo.installation.php Како да составите PHP со поддршка за SQLite], користи PDO)',
- 'config-support-oracle' => '* $1 е база на податоци на комерцијално претпријатие. ([http://www.php.net/manual/en/oci8.installation.php Како да составите PHP со поддршка за OCI8])',
- 'config-header-mysql' => 'Нагодувања на MySQL',
- 'config-header-postgres' => 'Нагодувања на PostgreSQL',
- 'config-header-sqlite' => 'Нагодувања на SQLite',
- 'config-header-oracle' => 'Нагодувања на Oracle',
- 'config-invalid-db-type' => 'Неважечки тип на база',
- 'config-missing-db-name' => 'Мора да внесете значење за параметарот „Име на базата“',
- 'config-missing-db-host' => 'Мора да внесете вредност за „Домаќин на базата на податоци“',
- 'config-missing-db-server-oracle' => 'Мора да внесете вредност за „TNS на базата“',
- 'config-invalid-db-server-oracle' => 'Неважечки TNS „$1“.
-Користете или „TNS Name“ или низата „Easy Connect“ ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методи на именување за Oracle])',
- 'config-invalid-db-name' => 'Неважечко име на базата „$1“.
-Користете само ASCII-букви (a-z, A-Z), бројки (0-9), долни црти (_) и цртички (-).',
- 'config-invalid-db-prefix' => 'Неважечки префикс за базата „$1“.
-Користете само ASCII-букви (a-z, A-Z), бројки (0-9), долни црти (_) и цртички (-).',
- 'config-connection-error' => '$1.
-
-Проверете го долунаведениот домаќин, корисничко име и лозинка и обидете се повторно.',
- 'config-invalid-schema' => 'Неважечка шема за МедијаВики „$1“.
-Користете само букви, бројки и долни црти.',
- 'config-db-sys-create-oracle' => 'Инсталаторот поддржува само употреба на SYSDBA-сметка за создавање на нова сметка.',
- 'config-db-sys-user-exists-oracle' => 'Корисничката сметка „$1“ веќе постои. SYSDBA служи само за создавање на нова сметка!',
- 'config-postgres-old' => 'Се бара PostgreSQL $1 или поново, а вие имате $2.',
- 'config-sqlite-name-help' => 'Одберете име кое ќе го претставува вашето вики.
-Не користете празни простори и црти.
-Ова ќе се користи за податотечното име на SQLite-податоците.',
- 'config-sqlite-parent-unwritable-group' => 'Не можам да ја создадам папката <code><nowiki>$1</nowiki></code> бидејќи мрежниот опслужувач не може да запише во матичната папка <code><nowiki>$2</nowiki></code>.
-
-Инсталаторот го утврди корисникот под кој работи вашиот мрежен опслужувач.
-За да продолжите, наместете да може да запишува во папката <code><nowiki>$3</nowiki></code>.
-На Unix/Linux систем направете го следново:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Не можам да ја создадам папката <code><nowiki>$1</nowiki></code> бидејќи мрежниот опслужувач не може да запише во матичната папка <code><nowiki>$2</nowiki></code>.
-
-Инсталаторот не можеше го утврди корисникот под кој работи вашиот мрежен опслужувач.
-За да продолжите, наместете тој (и други!) да може глобално да запишува во папката <code><nowiki>$3</nowiki></code>
-На Unix/Linux систем направете го следново:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Грешка при создавањето на податочната папка „$1“.
-Проверете каде се наоѓа и обидете се повторно.',
- 'config-sqlite-dir-unwritable' => 'Не можам да запишам во папката „$1“.
-Во дозволите за неа, овозможете му на мрежниот опслужувач да запишува во неа и обидете се повторно.',
- 'config-sqlite-connection-error' => '$1.
-
-Проверете ја податочната папка и името на базата, и обидете се повторно.',
- 'config-sqlite-readonly' => 'Податотеката <code>$1</code> е незапислива.',
- 'config-sqlite-cant-create-db' => 'Не можев да ја создадам податотеката <code>$1</code> за базата.',
- 'config-sqlite-fts3-downgrade' => 'PHP нема поддршка за FTS3 — ја поништувам надградбата за табелите',
- 'config-can-upgrade' => "Во оваа база има табели на МедијаВики.
-За да ги надградите на МедијаВики $1, кликнете на '''Продолжи'''.",
- 'config-upgrade-done' => "Надградбата заврши.
-
-Сега можете да [$1 почнете да го користите вашето вики].
-
-Ако сакате да ја пресоздадете вашата податотека <code>LocalSettings.php</code>, тогаш кликнете на копчето подолу.
-Ова '''не се препорачува''' освен во случај на проблеми со викито.",
- 'config-upgrade-done-no-regenerate' => 'Надградбата заврши.
-
-Сега можете да [$1 почнете да го користите викито].',
- 'config-regenerate' => 'Пресоздај LocalSettings.php →',
- 'config-show-table-status' => 'Барањето <code>SHOW TABLE STATUS</code> не успеа!',
- 'config-unknown-collation' => "'''Предупредување:''' Базата корисни непрепознаена упатна споредба.",
- 'config-db-web-account' => 'Сметка на базата за мрежен пристап',
- 'config-db-web-help' => 'Одберете корисничко име и лозинка што ќе ги користи мрежниот опслужувач за поврзување со опслужувачот на базта на податоци во текот на редовната работа со викито.',
- 'config-db-web-account-same' => 'Користи ја истата сметка од инсталацијата',
- 'config-db-web-create' => 'Создај ја сметката ако веќе не постои',
- 'config-db-web-no-create-privs' => 'Сметката што ја назначивте за инсталација нема доволно привилегии за да може да создаде сметка.
-Тука мора да назначите постоечка сметка.',
- 'config-mysql-engine' => 'Складишен погон:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Предупредување''': Го одбравте MyISAM како складишен погон за MySQL. Но тој не се препорачува за МедијаВики бидејќи:
-* одвај поддржува едновременост поради заклучување на табелите
-* поподложен на расипување од другите погони
-* кодната база на МедијаВики не секогаш може да работи со MyISAM како што треба
-
-Ако вашата инсталација на MySQL поддржува InnoDB, тогаш сериозно препорачуваме да го користите него наместо MyISAM.
-Ако вашата инсталација на MySQL не поддржува InnoDB, веројатно дошло време за надградба.",
- 'config-mysql-only-myisam-dep' => "'''Предупредување:''' MyISAM е единствениот достапен складишен погон за MySQL, што не се препорачува за употреба со МедијаВики, бидејќи:
-* речиси не поддржува истовремено извршување на задачите поради заклучувањето на табелите
-* поподложен е на расипувања од другите погони
-* кодната база на МедијаВИки не секогаш работи исправно со MyISAM
-Вашата инсталација на MySQL не поддржува InnoDB. Можеби е време да ја надградите.",
- 'config-mysql-engine-help' => "'''InnoDB''' речиси секогаш е најдобар избор, бидејќи има добра поддршка за едновременост.
-
-'''MyISAM''' може да е побрз кај инсталациите наменети за само еден корисник или незаписни инсталации (само читање).
-Базите на податоци од MyISAM почесто се расипуваат од базите на InnoDB.",
- 'config-mysql-charset' => 'Збир знаци за базата:',
- 'config-mysql-binary' => 'Бинарен',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "Во '''бинарен режим''', во базата на податоци МедијаВики складира UTF-8 текст во бинарни полиња.
-Ова е поефикасно отколку TF-8 режимот на MySQL, и ви овозможува да ја користите целата палета на уникодни знаци.
-
-Во '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори, но нема да ви дозволи да складиратезнаци над [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
- 'config-site-name' => 'Име на викито:',
- 'config-site-name-help' => 'Ова ќе се појавува во заглавната лента на прелистувачот и на разни други места.',
- 'config-site-name-blank' => 'Внесете име на мрежното место.',
- 'config-project-namespace' => 'Проектен именски простор:',
- 'config-ns-generic' => 'Проект',
- 'config-ns-site-name' => 'Исто име како викито: $1',
- 'config-ns-other' => 'Друго (наведете)',
- 'config-ns-other-default' => 'МоеВики',
- 'config-project-namespace-help' => "По примерот на Википедија, многу викија ги чуваат страниците со правила на посебно место од самите содржини, т.е. во „'''проектен именски простор'''“.
-Сите наслови на страниците во овој именски простор почнуваат со извесна претставка, којшто можете да го укажете тука.
-По традиција претставката произлегува од името на викито, но не смее да содржи интерпункциски знаци како „#“ или „:“.",
- 'config-ns-invalid' => 'Назначениот именски простор „<nowiki>$1</nowiki>“ е неважечки.
-Назначете друг проектен именски простор.',
- 'config-ns-conflict' => 'Наведениот именски простор „<nowiki>$1</nowiki>“ се коси со основниот именски простор на МедијаВики.
-Наведете друг именски простор за проектот.',
- 'config-admin-box' => 'Администратоска сметка',
- 'config-admin-name' => 'Вашето име:',
- 'config-admin-password' => 'Лозинка:',
- 'config-admin-password-confirm' => 'Пак лозинката:',
- 'config-admin-help' => 'Тука внесете го вашето корисничко име, на пр. „Петар Петровски“.
-Ова име ќесе користи за најава во викито.',
- 'config-admin-name-blank' => 'Внесете администраторско корисничко име.',
- 'config-admin-name-invalid' => 'Назначенотго корисничко име „<nowiki>$1</nowiki>“ е неважечко.
-Назначете друго.',
- 'config-admin-password-blank' => 'Внесете лозинка за администраторската сметка',
- 'config-admin-password-same' => 'Лозинката не може да биде иста со корисничкото име.',
- 'config-admin-password-mismatch' => 'Лозинките што ги внесовте не се совпаѓаат.',
- 'config-admin-email' => 'Е-поштенска адреса:',
- 'config-admin-email-help' => 'Тука внесете е-поштенска адреса за да можете да добивате е-пошта од други корисници на викито, да ја менувате лозинката, и да бидете известувани за промени во страниците на вашиот список на набљудувања. Можете и да го оставите празно.',
- 'config-admin-error-user' => 'Се појави внатрешна грешка при создавањето на администраторот со име „<nowiki>$1</nowiki>“.',
- 'config-admin-error-password' => 'Се појави внатрешна грешка при задавање на лозинката за администраторот „<nowiki>$1</nowiki>“: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Внесовте неважечка е-поштенска адреса',
- 'config-subscribe' => 'Претплатете се на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release поштенскиот список за известувања].',
- 'config-subscribe-help' => 'Ова е нископрометен поштенски список кој се користи за соопштувања во врска со изданија, вклучувајќи важни безбедносни соопштенија.
-Треба да се претплатите и да ја надградувате вашата инсталација на МедијаВики кога излегуваат нови верзии.',
- 'config-subscribe-noemail' => 'Се обидовте да се претплатите на поштенскиот список со известувања за нови изданија без да наведете е-пошта.
-Наведете е-поштенска адреса ако сакате да се претплатите на списокот.',
- 'config-almost-done' => 'Уште малку сте готови!
-Сега можете да ги прескокнете преостанатите поставувања и веднаш да го инсталирате викито.',
- 'config-optional-continue' => 'Постави ми повеќе прашања.',
- 'config-optional-skip' => 'Веќе ми здосади, дај само инсталирај го викито.',
- 'config-profile' => 'Профил на кориснички права:',
- 'config-profile-wiki' => 'Отворено вики',
- 'config-profile-no-anon' => 'Задолжително отворање сметка',
- 'config-profile-fishbowl' => 'Само овластени уредници',
- 'config-profile-private' => 'Приватно вики',
- 'config-profile-help' => "Викијата функционираат најдобро кога имаат што повеќе уредници.
-Во МедијаВики лесно се проверуваат скорешните промени, и лесно се исправа (технички: „враќа“) штетата направена од неупатени или злонамерни корисници.
-
-Многумина имаат најдено најразлични полезни примени за МедијаВики, но понекогаш не е лесно да убедите некого во предностите на вики-концептот.
-Значи имате избор.
-
-'''{{int:config-profile-wiki}}''' — модел според кој секој може да уредува, дури и без најавување.
-Ако имате вики со '''задолжително отворање на сметка''', тогаш добивате повеќе контрола, но ова може даги одврати спонтаните учесници.
-
-'''{{int:config-profile-fishbowl}}''' — може да уредуваат само уредници што имаат добиено дозвола за тоа, но јавноста може да ги гледа страниците, вклучувајќи ја нивната историја.
-'''{{int:config-profile-private}}''' — страниците се видливи и уредливи само за овластени корисници.
-
-По инсталацијата имате на избор и посложени кориснички права и поставки. Погледајте во [//www.mediawiki.org/wiki/Manual:User_rights прирачникот].",
- 'config-license' => 'Авторски права и лиценца:',
- 'config-license-none' => 'Без подножје за лиценца',
- 'config-license-cc-by-sa' => 'Криејтив комонс НаведиИзвор СподелиПодИстиУслови',
- 'config-license-cc-by' => 'Криејтив комонс НаведиИзвор',
- 'config-license-cc-by-nc-sa' => 'Криејтив комонс НаведиИзвор-Некомерцијално-СподелиПодИстиУслови',
- 'config-license-cc-0' => 'Криејтив комонс Нула (јавна сопственост)',
- 'config-license-gfdl' => 'ГНУ-ова лиценца за слободна документација 1.3 или понова',
- 'config-license-pd' => 'Јавна сопственост',
- 'config-license-cc-choose' => 'Одберете друга лиценца на Криејтив комонс по ваш избор',
- 'config-license-help' => "Многу јавни викија ги ставаат сите придонеси под [http://freedomdefined.org/Definition слободна лиценца].
-Со ова се создава атмосфера на општа сопственост и поттикнува долгорочно учество.
-Ова не е неопходно за викија на поединечни физички или правни лица.
-
-Ако сакате да користите текст од Википедија, и сакате Википедија да прифаќа текст прекопиран од вашето вики, тогаш треба да ја одберете лиценцата '''Криејтив комонс НаведиИзвор СподелиПодИстиУслови'''.
-
-ГНУ-овата лиценца за слободна документација (ГЛСД) е старата лиценца на Википедија.
-Оваа лиценца сè уште важи, но е тешка за разбирање.
-Исто така треба да се има на ум дека пренамената на содржините под ГЛСД не е лесна.",
- 'config-email-settings' => 'Нагодувања за е-пошта',
- 'config-enable-email' => 'Овозможи излезна е-пошта',
- 'config-enable-email-help' => 'Ако сакате да работи е-поштата, [http://www.php.net/manual/en/mail.configuration.php поштенските нагодувања на PHP] треба да се правилно наместени.
-Ако воопшто не сакате никакви функции за е-пошта, тогаш можете да ги оневозможите тука.',
- 'config-email-user' => 'Овозможи е-пошта од корисник до корисник',
- 'config-email-user-help' => 'Дозволи сите корисници да можат да си праќаат е-пошта ако ја имаат овозможено во нагодувањата.',
- 'config-email-usertalk' => 'Овозможи известувања за промени во кориснички страници за разговор',
- 'config-email-usertalk-help' => 'Овозможи корисниците да добиваат известувања за промени во нивните кориснички страници за разговор ако ги имаат овозможено во нагодувањата.',
- 'config-email-watchlist' => 'Овозможи известувања за список на набљудувања',
- 'config-email-watchlist-help' => 'Овозможи корисниците да добиваат известувања за нивните набљудувани страници ако ги имаат овозможено во нагодувањата.',
- 'config-email-auth' => 'Овозможи потврдување на е-пошта',
- 'config-email-auth-help' => "Ако оваа можност е вклучена, тогаш корисниците ќе мора да ја потврдат нивната е-поштенска адреса преку врска испратена до нив кога ја укажуваат или менуваат е-поштенската адреса.
-Само корисници со потврдена е-пошта можат да добиваат е-пошта од други корисници или да ги менуваат писмата за известување.
-Оваа можност е '''препорачана''' за јавни викија поради можни злоупотреби на е-поштенската функција.",
- 'config-email-sender' => 'Повратна е-поштенска адреса:',
- 'config-email-sender-help' => 'Внесете ја е-поштенската адреса што ќе се користи како повратна адреса за излезна е-пошта.
-Таму ќе се испраќаат вратените (непримени) писма.
-Многу поштенски опслужувачи бараат барем делот за доменско име да биде важечки.',
- 'config-upload-settings' => 'Подигање на слики и податотеки',
- 'config-upload-enable' => 'Овозможи подигање на податотеки',
- 'config-upload-help' => 'Подигањето на податотеки потенцијално го изложуваат вашиот опслужувач на безбедносни ризици.
-За повеќе информации, прочитајте го [//www.mediawiki.org/wiki/Manual:Security поглавието за безбедност] во прирачникот.
-
-За да овозможите подигање на податотеки, сменете го режимот на потпапката <code>images</code> во основната папка на МедијаВики, за да му овозможите на мрежниот опслужувач да запишува во неа.
-Потоа овозможете ја оваа функција.',
- 'config-upload-deleted' => 'Папка за избришаните податотеки:',
- 'config-upload-deleted-help' => 'Одберете во која папка да се архивираат избришаните податотеки.
-Најдобро би било ако таа не е достапна преку интернет.',
- 'config-logo' => 'URL за логото:',
- 'config-logo-help' => 'Матичното руво на МедијаВики има простор за лого од 135x160 пиксели над страничната лента.
-
-Можете да употребите <code>$wgStylePath</code> или <code>$wgScriptPath</code> ако вашето лого е релативно на тие патеки.
-
-Ако не сакате да имате лого, тогаш оставете го ова поле празно.',
- 'config-instantcommons' => 'Овозможи Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функција која им овозможува на викијата да користат слики, звучни записи и други мултимедијални содржини од [//commons.wikimedia.org/ Заедничката Ризница].
-За да може ова да работи, МедијаВики бара пристап до интернет.
-
-За повеќе информации за оваа функција и напатствија за нејзино поставување на вики (сите други освен Ризницата), коносултирајте го [//mediawiki.org/wiki/Manual:$wgForeignFileRepos прирачникот].',
- 'config-cc-error' => 'Изборникот на лиценци од Криејтив комонс не даде резултати.
-Внесете го името на лиценцата рачно.',
- 'config-cc-again' => 'Одберете повторно...',
- 'config-cc-not-chosen' => 'Одберете ја саканата лиценца од Криејтив комонс и стиснете на „продолжи“.',
- 'config-advanced-settings' => 'Напредни нагодувања',
- 'config-cache-options' => 'Нагодувања за кеширање на објекти:',
- 'config-cache-help' => 'Кеширањето на објекти се користи за зголемување на брзината на МедијаВики со кеширање на често употребуваните податоци.
-Ова многу се препорачува на средни до големи викија, но од тоа ќе имаат полза и малите викија.',
- 'config-cache-none' => 'Без кеширање (не се остранува ниедна функција, но може да влијае на брзината кај поголеми викија)',
- 'config-cache-accel' => 'Кеширање на PHP-објекти (APC, XCache или WinCache)',
- 'config-cache-memcached' => 'Користи Memcached (бара дополнително поставување и нагодување)',
- 'config-memcached-servers' => 'Memcached-опслужувачи:',
- 'config-memcached-help' => 'Список на IP-адреси за употреба во Memcached.
-Треба да се наведе по една во секој ред, како и портата што ќе се користи. На пример:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Го одбравте Memcached како ваш ваш тип на скришно памтење (кеш), но не наведовте опслужувач(и)',
- 'config-memcache-badip' => 'Внесовте неважечка IP-адреса за Memcached: $1',
- 'config-memcache-noport' => 'Не ја наведовте портата за опслужувачот на Memcached: $1.
-Ако не знаете која порта треба да се користи, основната е 11211',
- 'config-memcache-badport' => 'Бројките за портата на Memcached треба да бидат помеѓу $1 и $2',
- 'config-extensions' => 'Додатоци',
- 'config-extensions-help' => 'Во вашата папка <code>./extensions</code> беа востановени горенаведените додатоци.
-
-За ова може да треба дополнително нагодување, но можете да ги овозможите сега',
- 'config-install-alreadydone' => "'''Предупредување:''' Изгледа дека веќе го имате инсталирано МедијаВики и сега сакате да го инсталирате повторно.
-Продолжете на следната страница.",
- 'config-install-begin' => 'Стискајќи на „{{int:config-continue}}“ ќе ја започнете инсталацијата на МедијаВики.
-Ако сакате да направите измени во досегашното, стиснете на „{{int:config-back}}“.',
- 'config-install-step-done' => 'готово',
- 'config-install-step-failed' => 'не успеа',
- 'config-install-extensions' => 'Вклучувам додатоци',
- 'config-install-database' => 'Ја поставувам базата на податоци',
- 'config-install-schema' => 'Создавам шема',
- 'config-install-pg-schema-not-exist' => 'PostgreSQL-шемата не постои',
- 'config-install-pg-schema-failed' => 'Создавањето натабелите не успеа.
-Проверете дали корисникот „$1“ може да запишува во шемата „$2“.',
- 'config-install-pg-commit' => 'Спроведување на промени',
- 'config-install-pg-plpgsql' => 'Проверувам јазик PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Ќе треба да го инсталирате јазикот PL/pgSQL во базата $1',
- 'config-pg-no-create-privs' => 'Сметката што ја наведовте за инсталацијата нема доволно привилегии за да создаде друга сметка.',
- 'config-pg-not-in-role' => 'Сметката што ја наведовте за мрежниот корисник веќе постои.
-Сметката што ја наведовте за инсталација не е суперкорисник и не ѝ припаѓа на улогата на мрежниот корисник, па затоа не може да создава објекти во негова сопственост.
-
-МедијаВики налага дека табелите мора да се во сопственост на мрежниот корисник. Наведете друга мрежна сметка, или стиснете на „назад“ и наведете соодветно привилегиран корисник за инталацијата.',
- 'config-install-user' => 'Создавам корисник за базата',
- 'config-install-user-alreadyexists' => 'Корисникот „$1“ веќе постои',
- 'config-install-user-create-failed' => 'Создавањето на корисникот „$1“ не успеа: $2',
- 'config-install-user-grant-failed' => 'Доделувањето на дозвола на корисникот „$1“ не успеа: $2',
- 'config-install-user-missing' => 'Наведениот корисник „$1“ не постои.',
- 'config-install-user-missing-create' => 'Наведениот корисник „$1“ не постои.
-Ако сакате да го создадете, штиклирајте ја можноста „создај сметка“.',
- 'config-install-tables' => 'Создавам табели',
- 'config-install-tables-exist' => "'''Предупредување''': Изгледа дека табелите за МедијаВики веќе постојат.
-Го прескокнувам создавањето.",
- 'config-install-tables-failed' => "'''Грешка''': Создавањето на табелата не успеа поради следнава грешка: $1",
- 'config-install-interwiki' => 'Ги пополнувам основно зададените меѓувики-табели',
- 'config-install-interwiki-list' => 'Не можев да ја пронајдам податотеката <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Предупредување''': Табелата со интервикија веќе содржи ставки.
-Го прескокнувам основно-зададениот список.",
- 'config-install-stats' => 'Ги подготвувам статистиките',
- 'config-install-keys' => 'Создавање на тајни клучеви',
- 'config-insecure-keys' => "'''Предупредување:''' {{PLURAL:$2|Безбедносниот клуч $1 создаден во текот на инсталацијата не е сосем безбеден|Безбедносните клучеви $1 создадени во текот на инсталацијата не се сосем безбедни}}. Ви препорачуваме да {{PLURAL:$2|го|ги}} смените рачно.",
- 'config-install-sysop' => 'Создавање на администраторска корисничка сметка',
- 'config-install-subscribe-fail' => 'Не можам да ве претплатам на известувањето mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL не е инсталиран, а allow_url_fopen не е достапно.',
- 'config-install-mainpage' => 'Создавам главна страница со стандардна содржина',
- 'config-install-extension-tables' => 'Изработка на табели за овозможени додатоци',
- 'config-install-mainpage-failed' => 'Не можев да вметнам главна страница: $1',
- 'config-install-done' => "'''Честитаме!'''
-Успешно го инсталиравте МедијаВики.
-
-Инсталаторот создаде податотека <code>LocalSettings.php</code>.
-Таму се содржат сите ваши нагодувања.
-
-Ќе треба да ја преземете и да ја ставите во основата на инсталацијата (истата папка во која се наоѓа index.php). Преземањето треба да е започнато автоматски.
-
-Ако не ви е понудено преземање, или пак ако сте го откажале, можете да го почнете одново стискајќи на следнава врска:
-
-$3
-
-'''Напомена''': Ако ова не го направите сега, податотеката со поставки повеќе нема да биде на достапна.
-
-Откога ќе завршите со тоа, можете да '''[$2 влезете на вашето вики]'''.",
- 'config-download-localsettings' => 'Преземи го <code>LocalSettings.php</code>',
- 'config-help' => 'помош',
- 'config-nofile' => 'Податотеката „$1“ не е пронајдена. Да не е избришана?',
- 'config-extension-link' => 'Дали сте знаеле дека вашето вики поддржува [//www.mediawiki.org/wiki/Manual:Extensions додатоци]?
-
-Можете да ги прелистате [//www.mediawiki.org/wiki/Category:Extensions_by_category по категории] или да ја посетите [//www.mediawiki.org/wiki/Extension_Matrix матрицата], каде ќе најдете полн список на додатоци.',
- 'mainpagetext' => "'''МедијаВики е успешно инсталиран.'''",
- 'mainpagedocfooter' => 'Погледнете го [//meta.wikimedia.org/wiki/Help:Contents Упатството за корисници] за подетални иформации како се користи вики-програмот.
-
-==Од каде да почнете==
-* [//meta.wikimedia.org/wiki/Manual:Configuration_settings Список на нагодувања]
-* [//meta.wikimedia.org/wiki/Manual:FAQ ЧПП (често поставувани прашања) за МедијаВики].
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Поштенски список на МедијаВики за нови верзии]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Локализирајте го МедијаВики на вашиот јазик]',
-);
-
-/** Malayalam (മലയാളം)
- * @author Praveenp
- * @author Sadik Khalid
- */
-$messages['ml'] = array(
- 'config-desc' => 'മീഡിയവിക്കി ഇൻസ്റ്റോളർ',
- 'config-title' => 'മീഡിയവിക്കി $1 ഇൻസ്റ്റലേഷൻ',
- 'config-information' => 'വിവരങ്ങൾ',
- 'config-localsettings-upgrade' => "'''അറിയിപ്പ്''': ഒരു <code>LocalSettings.php</code> ഫയൽ കാണുന്നു.
-സോഫ്റ്റ്‌വേർ അപ്‌ഗ്രേഡ് ചെയ്യുക സാദ്ധ്യമാണ്.
-ദയവായി പെട്ടിയിൽ <code>\$wgUpgradeKey</code> എന്നതിന്റെ വില നൽകുക.", # Fuzzy
- 'config-localsettings-key' => 'അപ്‌ഗ്രേഡ് ചാവി:',
- 'config-localsettings-badkey' => 'താങ്കൾ നൽകിയ ചാവി തെറ്റാണ്',
- 'config-session-error' => 'സെഷൻ തുടങ്ങുന്നതിൽ പിഴവ്: $1',
- 'config-your-language' => 'താങ്കളുടെ ഭാഷ:',
- 'config-your-language-help' => 'ഇൻസ്റ്റലേഷൻ പ്രക്രിയയിൽ ഉപയോഗിക്കേണ്ട ഭാഷ തിരഞ്ഞെടുക്കുക.',
- 'config-wiki-language' => 'വിക്കി ഭാഷ:',
- 'config-wiki-language-help' => 'വിക്കിയിൽ പ്രധാനമായി ഉപയോഗിക്കേണ്ട ഭാഷ തിരഞ്ഞെടുക്കുക.',
- 'config-back' => '← പിന്നിലേയ്ക്ക്',
- 'config-continue' => 'തുടരുക →',
- 'config-page-language' => 'ഭാഷ',
- 'config-page-welcome' => 'മീഡിയവിക്കിയിലേയ്ക്ക് സ്വാഗതം!',
- 'config-page-dbconnect' => 'ഡേറ്റാബേസുമായി ബന്ധപ്പെടുക',
- 'config-page-upgrade' => 'നിലവിലുള്ള ഇൻസ്റ്റലേഷൻ അപ്‌ഗ്രേഡ് ചെയ്യുക',
- 'config-page-dbsettings' => 'ഡേറ്റാബേസ് സജ്ജീകരണങ്ങൾ',
- 'config-page-name' => 'പേര്',
- 'config-page-options' => 'ഐച്ഛികങ്ങൾ',
- 'config-page-install' => 'ഇൻസ്റ്റോൾ',
- 'config-page-complete' => 'സമ്പൂർണ്ണം!',
- 'config-page-restart' => 'ഇൻസ്റ്റലേഷൻ അടച്ച ശേഷം പുനർപ്രവർത്തിപ്പിക്കുക',
- 'config-page-readme' => 'ഇത് വായിക്കൂ',
- 'config-page-releasenotes' => 'പ്രകാശന കുറിപ്പുകൾ',
- 'config-page-copying' => 'പകർത്തൽ',
- 'config-page-upgradedoc' => 'അപ്‌ഗ്രേഡിങ്',
- 'config-help-restart' => 'ഇതുവരെ ഉൾപ്പെടുത്തിയ എല്ലാവിവരങ്ങളും ഒഴിവാക്കാനും ഇൻസ്റ്റലേഷൻ പ്രക്രിയ നിർത്തി-വീണ്ടുമാരംഭിക്കാനും താങ്കളാഗ്രഹിക്കുന്നുണ്ടോ?',
- 'config-restart' => 'അതെ, പുനർപ്രവർത്തിപ്പിക്കുക',
- 'config-sidebar' => '* [//www.mediawiki.org മീഡിയവിക്കി പ്രധാനതാൾ]
-* [//www.mediawiki.org/wiki/Help:Contents ഉപയോക്തൃസഹായി]
-* [//www.mediawiki.org/wiki/Manual:Contents കാര്യനിർവഹണസഹായി]
-* [//www.mediawiki.org/wiki/Manual:FAQ പതിവുചോദ്യങ്ങൾ]', # Fuzzy
- 'config-env-php' => 'പി.എച്ച്.പി. $1 ഇൻസ്റ്റോൾ ചെയ്തിട്ടുണ്ട്.',
- 'config-no-db' => 'അനുയോജ്യമായ ഡേറ്റാബേസ് ഡ്രൈവർ കണ്ടെത്താനായില്ല!', # Fuzzy
- 'config-memory-raised' => 'പി.എച്ച്.പി.യുടെ <code>memory_limit</code> $1 ആണ്, $2 ആയി ഉയർത്തിയിരിക്കുന്നു.',
- 'config-memory-bad' => "'''മുന്നറിയിപ്പ്:''' പി.എച്ച്.പി.യുടെ <code>memory_limit</code> $1 ആണ്.
-ഇത് മിക്കവാറും വളരെ കുറവാണ്.
-ഇൻസ്റ്റലേഷൻ പരാജയപ്പെട്ടേക്കാം!",
- 'config-db-type' => 'ഡേറ്റാബേസ് തരം:',
- 'config-db-host' => 'ഡേറ്റാബേസ് ഹോസ്റ്റ്:',
- 'config-db-name' => 'ഡേറ്റാബേസിന്റെ പേര്:',
- 'config-db-name-oracle' => 'ഡേറ്റാബേസ് സ്കീമ:',
- 'config-db-install-account' => 'ഇൻസ്റ്റലേഷനുള്ള ഉപയോക്തൃ അംഗത്വം',
- 'config-db-username' => 'ഡേറ്റാബേസ് ഉപയോക്തൃനാമം:',
- 'config-db-password' => 'ഡേറ്റാബേസ് രഹസ്യവാക്ക്:',
- 'config-mysql-old' => 'മൈഎസ്‌ക്യൂഎൽ $1 അഥവാ അതിലും പുതിയത് ആവശ്യമാണ്, താങ്കളുടെ പക്കൽ ഉള്ളത് $2 ആണ്.',
- 'config-db-port' => 'ഡേറ്റാബേസ് പോർട്ട്:',
- 'config-db-schema' => 'മീഡിയവിക്കിയ്ക്കായുള്ള സ്കീമ',
- 'config-support-info' => 'മീഡിയവിക്കി താഴെ പറയുന്ന ഡേറ്റാബേസ് സിസ്റ്റംസ് പിന്തുണയ്ക്കുന്നു:
-
-$1
-
-താങ്കൾ ഉപയോഗിക്കാനാഗ്രഹിക്കുന്ന ഡേറ്റാബേസ് സിസ്റ്റം പട്ടികയിലില്ലെങ്കിൽ, ദയവായി പിന്തുണ സജ്ജമാക്കാനായി മുകളിൽ നൽകിയിട്ടുള്ള ലിങ്കിലെ നിർദ്ദേശങ്ങൾ ചെയ്യുക.',
- 'config-header-mysql' => 'മൈഎസ്‌ക്യൂഎൽ സജ്ജീകരണങ്ങൾ',
- 'config-invalid-db-type' => 'അസാധുവായ ഡേറ്റാബേസ് തരം',
- 'config-missing-db-name' => '"ഡേറ്റാബേസിന്റെ പേരി"ന് ഒരു വില നിർബന്ധമായും നൽകിയിരിക്കണം',
- 'config-connection-error' => '$1.
-
-താഴെ നൽകിയിരിക്കുന്ന ഹോസ്റ്റ്, ഉപയോക്തൃനാമം, രഹസ്യവാക്ക് എന്നിവ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.',
- 'config-regenerate' => 'LocalSettings.php പുനഃസൃഷ്ടിക്കുക →',
- 'config-mysql-engine' => 'സ്റ്റോറേജ് എൻജിൻ:',
- 'config-site-name' => 'വിക്കിയുടെ പേര്:',
- 'config-site-name-help' => 'ഇത് ബ്രൗസറിന്റെ ടൈറ്റിൽ ബാറിലും മറ്റനേകം ഇടങ്ങളിലും പ്രദർശിപ്പിക്കപ്പെടും.',
- 'config-site-name-blank' => 'സൈറ്റിന്റെ പേര് നൽകുക.',
- 'config-project-namespace' => 'പദ്ധതി നാമമേഖല:',
- 'config-ns-generic' => 'പദ്ധതി',
- 'config-ns-site-name' => 'വിക്കിയുടെ പേര് തന്നെ: $1',
- 'config-ns-other' => 'ഇതരം (വ്യക്തമാക്കുക)',
- 'config-ns-other-default' => 'എന്റെ‌വിക്കി',
- 'config-admin-box' => 'കാര്യനിർവാഹക അംഗത്വം',
- 'config-admin-name' => 'താങ്കളുടെ പേര്:',
- 'config-admin-password' => 'രഹസ്യവാക്ക്:',
- 'config-admin-password-confirm' => 'രഹസ്യവാക്ക് ഒരിക്കൽക്കൂടി:',
- 'config-admin-help' => 'ഇവിടെ താങ്കളുടെ ഇച്ഛാനുസരണമുള്ള ഉപയോക്തൃനാമം നൽകുക, ഉദാഹരണം "ശശി കൊട്ടാരത്തിൽ".
-ഈ പേരായിരിക്കണം വിക്കിയിൽ പ്രവേശിക്കാൻ താങ്കൾ ഉപയോഗിക്കേണ്ടത്.',
- 'config-admin-name-blank' => 'ഒരു കാര്യനിർവാഹക ഉപയോക്തൃനാമം നൽകുക.',
- 'config-admin-name-invalid' => 'നൽകിയിട്ടുള്ള ഉപയോക്തൃനാമം "<nowiki>$1</nowiki>" അസാധുവാണ്.
-മറ്റൊരു ഉപയോക്തൃനാമം നൽകുക.',
- 'config-admin-password-blank' => 'കാര്യനിർവാഹക അംഗത്വത്തിനുള്ള രഹസ്യവാക്ക് നൽകുക.',
- 'config-admin-password-same' => 'രഹസ്യവാക്കും ഉപയോക്തൃനാമവും ഒന്നാകരുത്.',
- 'config-admin-password-mismatch' => 'താങ്കൾ നൽകിയ രഹസ്യവാക്കുകൾ രണ്ടും തമ്മിൽ യോജിക്കുന്നില്ല.',
- 'config-admin-email' => 'ഇമെയിൽ വിലാസം:',
- 'config-admin-error-user' => '"<nowiki>$1</nowiki>" എന്ന പേരിലുള്ള കാര്യനിർവഹണ അംഗത്വ നിർമ്മിതിയ്ക്കിടെ ആന്തരികമായ പിഴവുണ്ടായി.',
- 'config-admin-error-password' => '"<nowiki>$1</nowiki>" എന്ന പേരിലുള്ള കാര്യനിർവാഹക അംഗത്വത്തിനു രഹസ്യവാക്ക് സജ്ജീകരിച്ചപ്പോൾ ആന്തരികമായ പിഴവുണ്ടായി: <pre>$2</pre>',
- 'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce പ്രകാശന അറിയിപ്പ് മെയിലിങ് ലിസ്റ്റിൽ] വരിക്കാരാകുക.',
- 'config-subscribe-help' => 'പുറത്തിറക്കൽ അറിയിപ്പുകളും, പ്രധാന സുരക്ഷാ അറിയിപ്പുകളും പ്രസിദ്ധീകരിക്കുന്ന വളരെ എഴുത്തുകളൊന്നും ഉണ്ടാകാറില്ലാത്ത മെയിലിങ് ലിസ്റ്റ് ആണിത്.
-പുതിയ പതിപ്പുകൾ പുറത്ത് വരുന്നതനുസരിച്ച് അവയെക്കുറിച്ചറിയാനും മീഡിയവിക്കി ഇൻസ്റ്റലേഷൻ പുതുക്കാനും ഇതിന്റെ വരിക്കാരൻ/വരിക്കാരി ആവുക.',
- 'config-almost-done' => 'മിക്കവാറും പൂർത്തിയായിരിക്കുന്നു!
-ബാക്കിയുള്ളവ അവഗണിച്ച് വിക്കി ഇൻസ്റ്റോൾ ചെയ്യാവുന്നതാണ്.',
- 'config-optional-continue' => 'കൂടുതൽ ചോദ്യങ്ങൾ ചോദിക്കൂ.',
- 'config-optional-skip' => 'എനിക്ക് മടുത്തു, ഒന്ന് ഇൻസ്റ്റോൾ ചെയ്ത് തീർക്ക്.',
- 'config-profile-wiki' => 'പരമ്പരാഗത വിക്കി', # Fuzzy
- 'config-profile-no-anon' => 'അംഗത്വ സൃഷ്ടി ചെയ്യേണ്ടതുണ്ട്',
- 'config-profile-fishbowl' => 'അനുവാദമുള്ളവർ മാത്രം തിരുത്തുക',
- 'config-profile-private' => 'സ്വകാര്യ വിക്കി',
- 'config-license' => 'പകർപ്പവകാശവും അനുമതിയും:',
- 'config-license-cc-by-sa' => 'ക്രിയേറ്റീവ് കോമൺസ് ആട്രിബ്യൂഷൻ ഷെയർ എലൈക്',
- 'config-license-cc-by-nc-sa' => 'ക്രിയേറ്റീവ് കോമൺസ് ആട്രിബ്യൂഷൻ നോൺ-കൊമേഴ്സ്യൽ ഷെയർ എലൈക്',
- 'config-license-pd' => 'പൊതുസഞ്ചയം',
- 'config-email-settings' => 'ഇമെയിൽ സജ്ജീകരണങ്ങൾ',
- 'config-enable-email-help' => "ഇമെയിൽ പ്രവർത്തിക്കണമെങ്കിൽ, [http://www.php.net/manual/en/mail.configuration.php PHP's മെയിൽ സജ്ജീകരണങ്ങൾ] ശരിയായി ക്രമീകരിക്കേണ്ടതുണ്ട്.
-ഇമെയിൽ സൗകര്യം ആവശ്യമില്ലെങ്കിൽ, ഇവിടെത്തന്നെ അത് നിർജ്ജീവമാക്കാം.",
- 'config-email-user' => 'ഉപയോക്താക്കൾ തമ്മിലുള്ള ഇമെയിൽ പ്രവർത്തനസജ്ജമാക്കുക',
- 'config-email-user-help' => 'സ്വന്തം ക്രമീകരണങ്ങളിൽ ഇമെയിൽ സജ്ജമാക്കിയിട്ടുണ്ടെങ്കിൽ ഉപയോക്താക്കളെ മറ്റുള്ളവർക്ക് ഇമെയിൽ അയയ്ക്കാൻ അനുവദിക്കുക.',
- 'config-email-usertalk' => 'ഉപയോക്തൃസംവാദം താളിൽ മാറ്റങ്ങളുണ്ടായാൽ അറിയിക്കുക',
- 'config-email-watchlist' => 'ശ്രദ്ധിക്കുന്നവയിൽ മാറ്റം വന്നാൽ അറിയിക്കുക',
- 'config-email-auth' => 'ഇമെയിലിന്റെ സാധുതാപരിശോധന സജ്ജമാക്കുക',
- 'config-email-sender' => 'മറുപടിയ്ക്കുള്ള ഇമെയിൽ വിലാസം:',
- 'config-upload-settings' => 'ചിത്രങ്ങളും പ്രമാണങ്ങളും അപ്‌ലോഡ് ചെയ്യൽ',
- 'config-upload-enable' => 'പ്രമാണ അപ്‌ലോഡുകൾ സജ്ജമാക്കുക',
- 'config-upload-deleted' => 'മായ്ക്കപ്പെട്ട ഫയലുകൾക്കുള്ള ഡയറക്റ്ററി:',
- 'config-logo' => 'ലോഗോയുടെ യൂ.ആർ.എൽ.:',
- 'config-logo-help' => 'മീഡിയവിക്കിയിൽ സ്വതേയുള്ള ദൃശ്യരൂപത്തിൽ 135x160 പിക്സലുള്ള ലോഗോ മുകളിൽ ഇടത് മൂലയിൽ കാണാം.
-അനുയോജ്യമായ വലിപ്പമുള്ള ഒരു ചിത്രം അപ്‌ലോഡ് ചെയ്തിട്ട്, അതിന്റെ യൂ.ആർ.എൽ. ഇവിടെ നൽകുക.
-
-താങ്കൾക്ക് ലോഗോ ആവശ്യമില്ലെങ്കിൽ, ഈ പെട്ടി ശൂന്യമായിടുക.', # Fuzzy
- 'config-cc-again' => 'ഒന്നുകൂടി എടുക്കൂ...',
- 'config-advanced-settings' => 'വിപുലീകൃത ക്രമീകരണങ്ങൾ',
- 'config-extensions' => 'അനുബന്ധങ്ങൾ',
- 'config-install-step-done' => 'ചെയ്തു കഴിഞ്ഞു',
- 'config-install-step-failed' => 'പരാജയപ്പെട്ടു',
- 'config-install-extensions' => 'അനുബന്ധങ്ങൾ ഉൾപ്പെടുത്തുന്നു',
- 'config-install-database' => 'ഡേറ്റാബേസ് സജ്ജമാക്കുന്നു',
- 'config-install-pg-commit' => 'മാറ്റങ്ങൾ സ്വീകരിക്കുന്നു',
- 'config-install-user' => 'ഡേറ്റാബേസ് ഉപയോക്താവിനെ സൃഷ്ടിക്കുന്നു',
- 'config-install-sysop' => 'കാര്യനിർവാഹക അംഗത്വം സൃഷ്ടിക്കുന്നു',
- 'config-install-mainpage' => 'സ്വാഭാവിക ഉള്ളടക്കത്തോടുകൂടി പ്രധാനതാൾ സൃഷ്ടിക്കുന്നു',
- 'config-install-mainpage-failed' => 'പ്രധാന താൾ ഉൾപ്പെടുത്താൻ കഴിഞ്ഞില്ല: $1',
- 'config-install-done' => "'''അഭിനന്ദനങ്ങൾ!'''
-താങ്കൾ വിജയകരമായി മീഡിയവിക്കി സജ്ജീകരിച്ചിരിക്കുന്നു.
-
-ഇൻസ്റ്റോളർ താങ്കളുടെ എല്ലാ ക്രമീകരണങ്ങളുമടങ്ങുന്ന <code>LocalSettings.php</code> ഫയൽ സൃഷ്ടിച്ചിട്ടുണ്ട്.
-
-പ്രസ്തുത പ്രമാണം ഡൗൺലോഡ് ചെയ്ത് താങ്കളുടെ വിക്കി സജ്ജീകരണത്തിന്റെ അടിസ്ഥാന ഡയറക്റ്ററിയിൽ ഇടേണ്ടതാണ് (index.php കിടക്കുന്ന അതേ ഡയറക്റ്ററിയിൽ). ഡൗൺലോഡിങ്ങ് സ്വയം ആരംഭിക്കുന്നതാണ്. ഡൗൺലോഡിങ്ങ് സ്വയം തുടങ്ങാതിരിക്കുകയോ, താങ്കൾ റദ്ദാക്കുകയോ ചെയ്ത പക്ഷം താഴെ കാണുന്ന കണ്ണിയിൽ ഞെക്കുക:
-$3
-
-'''ശ്രദ്ധിക്കുക''': താങ്കൾ ഇപ്പോൾ ചെയ്തില്ലെങ്കിൽ, ഫയൽ എടുക്കാതെ ഇൻസ്റ്റലേഷൻ പ്രക്രിയയിൽ നിന്ന് പുറത്തിറങ്ങിയാൽ, സൃഷ്ടിക്കപ്പെട്ട ക്രമീകരണങ്ങളടങ്ങുന്ന പ്രമാണം പിന്നീട് ലഭ്യമായിരിക്കില്ല.
-
-മുകളിൽ പറഞ്ഞ പ്രകാരം ചെയ്തു കഴിഞ്ഞാൽ, താങ്കൾക്ക് '''[$2 വിക്കിയിൽ പ്രവേശിക്കാവുന്നതാണ്]'''.",
- 'mainpagetext' => "'''മീഡിയവിക്കി വിജയകരമായി സജ്ജീകരിച്ചിരിക്കുന്നു.'''",
- 'mainpagedocfooter' => 'വിക്കി സോഫ്റ്റ്‌വെയർ ഉപയോഗിക്കുന്നതിനെ കുറിച്ചുള്ള വിശദാംശങ്ങൾക്ക് [//meta.wikimedia.org/wiki/Help:Contents സോഫ്റ്റ്‌വെയർ സഹായി] കാണുക.
-
-== പ്രാരംഭസഹായികൾ ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings ക്രമീകരണങ്ങളുടെ പട്ടിക]
-* [//www.mediawiki.org/wiki/Manual:FAQ മീഡിയവിക്കി പതിവുചോദ്യങ്ങൾ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce മീഡിയവിക്കി പ്രകാശന മെയിലിങ് ലിസ്റ്റ്]', # Fuzzy
-);
-
-/** Mongolian (монгол)
- * @author Chinneeb
- */
-$messages['mn'] = array(
- 'config-page-language' => 'Хэл',
- 'mainpagetext' => "'''МедиаВики амжилттай суулаа.'''",
- 'mainpagedocfooter' => 'Вики программыг хэрэглэх талаар заавар авахын тулд [//meta.wikimedia.org/wiki/Help:Contents хэрэглэгчийн гарын авлага]-г үзнэ үү.
-
-== Эхлэх ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Тохиргоо]
-* [//www.mediawiki.org/wiki/Manual:FAQ МедиаВикигийн тогтмол тавигддаг асуултууд]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаВикигийн мэдээний мэйл явуулах жагсаалт]', # Fuzzy
-);
-
-/** Marathi (मराठी)
- */
-$messages['mr'] = array(
- 'mainpagetext' => "'''मीडियाविकीचे इन्स्टॉलेशन पूर्ण.'''",
- 'mainpagedocfooter' => 'विकी सॉफ्टवेअर वापरण्याकरिता [//meta.wikimedia.org/wiki/Help:Contents यूजर गाईड] पहा.
-
-== सुरुवात ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings कॉन्फिगरेशन सेटींगची यादी]
-* [//www.mediawiki.org/wiki/Manual:FAQ मीडियाविकी नेहमी विचारले जाणारे प्रश्न]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मीडियाविकि मेलिंग लिस्ट]', # Fuzzy
-);
-
-/** Malay (Bahasa Melayu)
- * @author Anakmalaysia
- * @author Pizza1016
- */
-$messages['ms'] = array(
- 'config-desc' => 'Pemasang MediaWiki',
- 'config-title' => 'Pasangan MediaWiki $1',
- 'config-information' => 'Maklumat',
- 'config-localsettings-key' => 'Kunci naik taraf:',
- 'config-localsettings-badkey' => 'Kunci yang anda berikan tidak betul.',
- 'config-session-error' => 'Ralat ketika memulakan sesi: $1',
- 'config-your-language' => 'Bahasa anda:',
- 'config-your-language-help' => 'Pilihkan bahasa untuk digunakan dalam proses pemasangan ini.',
- 'config-wiki-language' => 'Bahasa wiki:',
- 'config-wiki-language-help' => 'Pilih bahasa utama wiki yang bakal dicipta ini.',
- 'config-back' => '← Undur',
- 'config-continue' => 'Teruskan →',
- 'config-page-language' => 'Bahasa',
- 'config-page-welcome' => 'Selamat datang ke MediaWiki!',
- 'config-page-dbconnect' => 'Bersambung dengan pangkalan data',
- 'config-page-upgrade' => 'Naik taraf pemasangan sedia ada',
- 'config-page-dbsettings' => 'Tetapan pangkalan data',
- 'config-page-name' => 'Nama',
- 'config-page-options' => 'Pilihan',
- 'config-page-install' => 'Pasang',
- 'config-page-complete' => 'Selesai!',
- 'config-page-restart' => 'Mulakan semula pemasangan',
- 'config-page-readme' => 'Baca saya',
- 'config-page-releasenotes' => 'Catatan keluaran',
- 'config-page-copying' => 'Sedang menyalin',
- 'config-page-upgradedoc' => 'Sedang menaik taraf',
- 'config-page-existingwiki' => 'Wiki sedia ada',
- 'config-env-php' => 'PHP $1 dipasang.',
- 'config-env-php-toolow' => 'PHP $1 dipasang.
-Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
- 'config-unicode-using-utf8' => 'utf8_normalize.so oleh Brion Vibber digunakan untuk penormalan Unicode.',
- 'config-unicode-using-intl' => '[http://pecl.php.net/intl Sambungan intl PECL] digunakan untuk penormalan Unicode.',
- 'config-db-charset' => 'Peranggu aksara pangkalan data',
- 'config-header-mysql' => 'Keutamaan MySQL',
- 'config-header-postgres' => 'Keutamaan PostgreSQL',
- 'config-header-sqlite' => 'Keutamaan SQLite',
- 'config-header-oracle' => 'Keutamaan Oracle',
- 'config-invalid-db-type' => 'Jenis pangkalan data tidak sah',
- 'config-db-web-account-same' => 'Gunakan akaun yang sama seperti dalam pemasangan',
- 'config-db-web-create' => 'Ciptakan akaun jika belum wujud',
- 'config-mysql-engine' => 'Enjin storan:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => 'Peranggu aksara pangkalan data:',
- 'config-mysql-binary' => 'Perduaan',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Nama wiki:',
- 'config-site-name-help' => 'Ini akan dipaparkan pada bar tajuk perisian pelayar dan tempat-tempat lain yang berkenaan.',
- 'config-site-name-blank' => 'Isikan nama tapak.',
- 'config-project-namespace' => 'Ruang nama projek:',
- 'config-ns-generic' => 'Projek',
- 'config-ns-site-name' => 'Sama dengan nama wiki: $1',
- 'config-ns-other' => 'Lain-lain (nyatakan)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-admin-box' => 'Akaun penyelia',
- 'config-admin-name' => 'Nama kamu:',
- 'config-admin-password' => 'Kata laluan:',
- 'config-admin-password-confirm' => 'Kata laluan lagi:',
- 'config-admin-password-mismatch' => 'Kata-kata laluan yang kamu berikan tidak sepadan.',
- 'config-admin-email' => 'Alamat e-mel:',
- 'config-admin-error-bademail' => 'Kamu telah memberikan alamat e-mel yang tidak betul.',
- 'config-optional-skip' => 'Saya sudah bosan, pasangkanlah wiki sahaja.',
- 'config-license' => 'Hak cipta dan lesen:',
- 'config-license-none' => 'Tiada pengaki lesen',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (Domain Awam)',
- 'config-license-gfdl' => 'Lesen Dokumentasi Bebas GNU 1.3 atau ke atas',
- 'config-license-pd' => 'Domain Awam',
- 'config-email-settings' => 'Tetapan e-mel',
- 'config-install-step-done' => 'siap',
- 'config-install-step-failed' => 'gagal',
- 'config-install-user-alreadyexists' => 'Pengguna "$1" sudah wujud',
- 'config-help' => 'bantuan',
- 'mainpagetext' => "'''MediaWiki telah berjaya dipasang.'''",
- 'mainpagedocfooter' => 'Sila rujuk [//meta.wikimedia.org/wiki/Help:Contents Panduan Penggunaan] untuk maklumat mengenai penggunaan perisian wiki ini.
-
-== Permulaan ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Senarai tetapan konfigurasi]
-* [//www.mediawiki.org/wiki/Manual:FAQ Soalan Lazim MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Senarai surat keluaran MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Terjemahkan MediaWiki ke dalam bahasa anda]',
-);
-
-/** Maltese (Malti)
- * @author Chrisportelli
- */
-$messages['mt'] = array(
- 'config-title' => "Installazzjoni ta' MediaWiki $1",
- 'config-information' => 'Informazzjoni',
- 'config-localsettings-key' => 'Ċavetta tal-aġġornament:',
- 'config-localsettings-badkey' => 'Iċ-ċavetta li tajt hija ħażina.',
- 'config-your-language' => 'Il-lingwa tiegħek:',
- 'config-your-language-help' => "Agħżel lingwa li tixtieq tuża' matul il-proċess ta' installazzjoni.",
- 'config-wiki-language' => 'Lingwi tal-wiki:',
- 'config-wiki-language-help' => 'Agħżel il-lingwa li l-wiki se tkun l-aktar użata fil-wiki.',
- 'config-back' => '← Lura',
- 'config-continue' => 'Kompli →',
- 'config-page-language' => 'Lingwa',
- 'config-page-welcome' => 'Merħba fuq MediaWiki!',
- 'config-page-dbconnect' => 'Aqbad mad-databażi',
- 'config-page-upgrade' => 'Aġġorna l-installazzjoni eżistenti',
- 'config-page-dbsettings' => 'Impostazzjonijiet tad-databażi',
- 'config-page-name' => 'Isem',
- 'config-page-options' => 'Għażliet',
- 'config-page-install' => 'Installa',
- 'config-page-complete' => 'Lesta!',
- 'config-page-restart' => "Erġa' ibda l-installazzjoni",
- 'config-page-readme' => 'Aqrani',
- 'config-page-releasenotes' => 'Noti tal-verżjoni',
- 'config-page-upgradedoc' => 'Aġġornament',
- 'config-page-existingwiki' => 'Wiki eżistenti',
- 'config-restart' => "Iva, erġa' ibda",
- 'config-env-php' => 'PHP $1 huwa installat.',
- 'config-db-wiki-settings' => 'Identifika din il-wiki',
- 'config-db-name' => 'Isem tad-databażi:',
- 'config-db-install-account' => 'Kont tal-utent għall-installazzjoni',
- 'config-db-username' => 'Isem tal-utent tad-databażi:',
- 'config-db-password' => 'Password tad-databażi:',
- 'config-db-port' => 'Port tad-databażi:',
- 'config-db-schema' => 'Skema għal MediaWiki:',
- 'config-db-web-create' => 'Oħloq il-kont jekk għadu ma jeżistix',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => "Sett ta' karattri tad-databażi:",
- 'config-mysql-binary' => 'Binarju',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Isem tal-wiki:',
- 'config-site-name-help' => "Dan se jidher fil-barra tat-titlu tal-browżer u f'diversi postijiet oħra.",
- 'config-site-name-blank' => 'Daħħal isem tas-sit.',
- 'config-project-namespace' => 'Spazju tal-isem tal-proġett:',
- 'config-ns-generic' => 'Proġett',
- 'config-ns-site-name' => 'L-istess bħall-isem tal-wiki: $1',
- 'config-ns-other' => 'Oħrajn (speċifika)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-ns-invalid' => 'L-ispazju speċifikat "<nowiki>$1</nowiki>" huwa ħażin.
-Speċifika spazju tal-isem tal-proġett differenti.',
- 'config-ns-conflict' => 'L-ispazju speċifikat "<nowiki>$1</nowiki>" joħloq kunflitt ma\' spazju tal-isem ieħor tal-MediaWiki.
-Speċifika spazju tal-isem tal-proġett differenti.',
- 'config-admin-box' => 'Kont tal-amministratur',
- 'config-admin-name' => 'Ismek:',
- 'config-admin-password' => 'Password:',
- 'config-admin-password-confirm' => "Erġa' daħħal il-password:",
- 'config-admin-help' => 'Daħħal l-isem tal-utent preferit hawnhekk, per eżempju "Joe Borg".
-Dan huwa l-isem li se tuża\' kull darba li tidħol fil-wiki.',
- 'config-admin-name-blank' => 'Daħħal isem tal-utent għall-amministratur.',
- 'config-admin-name-invalid' => 'L-isem tal-utent speċifikat "<nowiki>$1</nowiki>" huwa ħażin.
-Speċifika isem tal-utent differenti.',
- 'config-admin-password-blank' => 'Daħħal password għall-kont tal-amministratur.',
- 'config-admin-password-same' => 'Il-password ma tistax tkun l-istess bħall-isem tal-utent.',
- 'config-admin-password-mismatch' => 'Il-passwords li daħħalt ma jaqblux.',
- 'config-admin-email' => 'Indirizz elettroniku:',
- 'config-admin-error-bademail' => 'Daħħalt indirizz elettroniku ħażin.',
- 'config-almost-done' => "Kważi lest!
-Jekk trid tista' taqbeż il-parti li jmiss tal-konfigurazzjoni u sempliċiment tinstalla l-wiki.",
- 'config-optional-continue' => 'Staqsini aktar mistoqsijiet.',
- 'config-optional-skip' => 'Xbajt diġà, installa l-wiki.',
- 'config-profile-wiki' => 'Wiki tradizzjonali', # Fuzzy
- 'config-profile-no-anon' => 'Huwa obbligatorju l-ħolqien tal-kont',
- 'config-profile-fishbowl' => 'Edituri awtorizzati biss',
- 'config-profile-private' => 'Wiki privata',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (dominju pubbliku)',
- 'config-license-pd' => 'Dominju pubbliku',
- 'config-license-cc-choose' => 'Agħżel waħda mil-liċenzji tal-Creative Commons',
- 'config-upload-deleted' => 'Direttorju għall-fajls imħassra:',
- 'config-upload-deleted-help' => "Agħżel direttorju fejn iżżomm fajls imħassra.
-Idealment, dan m'għandux ikun aċċessibbli mill-web.",
- 'config-logo' => 'URL tal-logo:',
- 'config-download-localsettings' => 'Niżżel <code>LocalSettings.php</code>',
- 'config-help' => 'għajnuna',
- 'config-nofile' => 'Il-fajl "$1" ma setax jinstab. Dan ġie mħassar?',
- 'mainpagetext' => "'''MediaWiki ġie installat b'suċċess.'''",
- 'mainpagedocfooter' => "Ikkonsulta l-[//meta.wikimedia.org/wiki/Help:Contents Gwida għall-utenti] sabiex tikseb iktar informazzjoni dwar kif tuża' s-softwer tal-wiki.
-
-== Biex tibda ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista ta' preferenzi għall-konfigurazzjoni]
-* [//www.mediawiki.org/wiki/Manual:FAQ Mistoqsijiet rikorrenti fuq il-MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Il-lista tal-posta tħabbar 'l MediaWiki]", # Fuzzy
-);
-
-/** Burmese (မြန်မာဘာသာ)
- * @author Lionslayer
- */
-$messages['my'] = array(
- 'mainpagetext' => "'''မီဒီယာဝီကီကို အောင်မြင်စွာ သွင်းပြီးပါပြီ။'''",
-);
-
-/** Erzya (эрзянь)
- * @author Botuzhaleny-sodamo
- */
-$messages['myv'] = array(
- 'config-page-language' => 'Кель',
- 'config-page-name' => 'Лемезэ',
- 'config-page-readme' => 'Ловномак',
- 'config-admin-name' => 'Леметь:',
- 'config-admin-password' => 'Совамо валот:',
- 'config-admin-password-confirm' => 'Совамо валот одов:',
- 'config-admin-email' => 'Е-сёрма паргот:',
- 'config-install-step-done' => 'теезь',
- 'mainpagetext' => "'''МедияВикинь тевс аравтомазо парсте лиссь.'''",
-);
-
-/** Mazanderani (مازِرونی)
- * @author محک
- */
-$messages['mzn'] = array(
- 'config-help' => 'راهنما',
-);
-
-/** Nahuatl (Nāhuatl)
- */
-$messages['nah'] = array(
- 'mainpagetext' => "'''MediaHuiqui cualli ōmotlahtlāli.'''",
-);
-
-/** Min Nan Chinese (Bân-lâm-gú)
- * @author Ianbu
- */
-$messages['nan'] = array(
- 'mainpagetext' => "'''MediaWiki已經裝好矣。'''",
- 'mainpagedocfooter' => '請查看[//meta.wikimedia.org/wiki/Help:Contents 用者說明書]的資料通使用wiki 軟體
-
-== 入門 ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings 配置的設定]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki時常問答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki的公布列單]', # Fuzzy
-);
-
-/** Norwegian Bokmål (norsk bokmål)
- * @author Event
- * @author Nghtwlkr
- * @author 아라
- */
-$messages['nb'] = array(
- 'config-desc' => 'Installasjonsprogrammet for MediaWiki',
- 'config-title' => 'Installasjon av MediaWiki $1',
- 'config-information' => 'Informasjon',
- 'config-localsettings-upgrade' => 'En <code>LocalSettings.php</code>-fil har blitt oppdaget.
-For å oppgradere denne installasjonen, skriv inn verdien av <code>$wgUpgradeKey</code> i boksen nedenfor.
-Du finner denne i <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => "Filen ''<code>LocalSettings.php</code>'' er funnet.
-For å oppgradere denne installasjonen, vennligst kjør ''update.php'' i stedet",
- 'config-localsettings-key' => 'Oppgraderingsnøkkel:',
- 'config-localsettings-badkey' => 'Nøkkelen du oppga er feil.',
- 'config-upgrade-key-missing' => "En eksisterende installasjon av MediaWiki er funnet.
-For å oppgradere denne installasjonen, vær vennlig å legge til følgende linje helt til slutt i din ''<code>LocalSettings.php</code>''-fil:
-
-$1",
- 'config-localsettings-incomplete' => "Den eksisterende ''<code>LocalSettings.php</code>'' ser ut til å være ufullstendig.
-Variabelen $1 har ingen verdi.
-Vær vennlig å endre ''<code>LocalSettings.php</code>'' slik at variabelen får en verdi, og klikk ''{{int:Config-continue}}''.",
- 'config-localsettings-connection-error' => "Det ble funnet en feil ved tilknytning av databasen med innstillingene i ''<code>LocalSettings.php</code>'' eller ''<code>AdminSettings.php</code>''. Vær vennlig å rette opp disse innstillingene og prøv igjen.
-
-$1",
- 'config-session-error' => 'Feil under oppstart av økt: $1',
- 'config-session-expired' => 'Dine øktdata ser ut til å ha utløpt.
-Økter er konfigurert for en levetid på $1.
-Du kan øke dette ved å sette <code>session.gc_maxlifetime</code> i php.ini.
-Start installasjonsprosessen på nytt.',
- 'config-no-session' => 'Dine øktdata ble tapt!
-Sjekk din php.ini og sørg for at <code>session.save_path</code> er satt til en passende mappe.',
- 'config-your-language' => 'Ditt språk:',
- 'config-your-language-help' => 'Velg et språk å bruke under installasjonsprosessen.',
- 'config-wiki-language' => 'Wikispråk:',
- 'config-wiki-language-help' => 'Velg språket som wikien hovedsakelig vil bli skrevet i.',
- 'config-back' => '← Tilbake',
- 'config-continue' => 'Fortsett →',
- 'config-page-language' => 'Språk',
- 'config-page-welcome' => 'Velkommen til MediaWiki!',
- 'config-page-dbconnect' => 'Koble til database',
- 'config-page-upgrade' => 'Oppgrader eksisterende innstallasjon',
- 'config-page-dbsettings' => 'Databaseinnstillinger',
- 'config-page-name' => 'Navn',
- 'config-page-options' => 'Valg',
- 'config-page-install' => 'Installer',
- 'config-page-complete' => 'Ferdig!',
- 'config-page-restart' => 'Start installasjonen på nytt',
- 'config-page-readme' => 'Les meg',
- 'config-page-releasenotes' => 'Utgivelsesnotat',
- 'config-page-copying' => 'Kopiering',
- 'config-page-upgradedoc' => 'Oppgradering',
- 'config-page-existingwiki' => 'Eksisterende wiki',
- 'config-help-restart' => 'Ønsker du å fjerne alle lagrede data som du har skrevet inn og starte installasjonsprosessen på nytt?',
- 'config-restart' => 'Ja, start på nytt',
- 'config-welcome' => '=== Miljøsjekker ===
-Grunnleggende sjekker utføres for å se om dette miljøet er egnet for en MediaWiki-installasjon.
-Du bør oppgi resultatene fra disse sjekkene om du trenger hjelp under installasjonen.',
- 'config-copyright' => "=== Opphavsrett og vilkår ===
-
-$1
-
-MediaWiki er fri programvare; du kan redistribuere det og/eller modifisere det under betingelsene i GNU General Public License som publisert av Free Software Foundation; enten versjon 2 av lisensen, eller (etter eget valg) enhver senere versjon.
-
-Dette programmet er distribuert i håp om at det vil være nyttig, men '''uten noen garanti'''; ikke engang implisitt garanti av '''salgbarhet''' eller '''egnethet for et bestemt formål'''.
-Se GNU General Public License for flere detaljer.
-
-Du skal ha mottatt <doclink href=Copying>en kopi av GNU General Public License</doclink> sammen med dette programmet; hvis ikke, skriv til Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA eller [http://www.gnu.org/copyleft/gpl.html les det på nettet].",
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki hjem]
-* [//www.mediawiki.org/wiki/Help:Contents Brukerguide]
-* [//www.mediawiki.org/wiki/Manual:Contents Administratorguide]
-* [//www.mediawiki.org/wiki/Manual:FAQ OSS]
-----
-* <doclink href=Readme>Les meg</doclink>
-* <doclink href=ReleaseNotes>Utgivelsesnotater</doclink>
-* <doclink href=Copying>Kopiering</doclink>
-* <doclink href=UpgradeDoc>Oppgradering</doclink>',
- 'config-env-good' => 'Miljøet har blitt sjekket.
-Du kan installere MediaWiki.',
- 'config-env-bad' => 'Miljøet har blitt sjekket.
-Du kan installere MediaWiki.',
- 'config-env-php' => 'PHP $1 er innstallert.',
- 'config-env-php-toolow' => 'PHP $1 er installert.
-MediaWiki krever imidlertid PHP $2 eller høyere.',
- 'config-unicode-using-utf8' => 'Bruker Brion Vibbers utf8_normalize.so for Unicode-normalisering.',
- 'config-unicode-using-intl' => 'Bruker [http://pecl.php.net/intl intl PECL-utvidelsen] for Unicode-normalisering.',
- 'config-unicode-pure-php-warning' => "'''Advarsel''': [http://pecl.php.net/intl intl PECL-utvidelsen] er ikke tilgjengelig for å håndtere Unicode-normaliseringen, faller tilbake til en langsommere ren-PHP-implementasjon.
-Om du kjører et nettsted med høy trafikk bør du lese litt om [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-normalisering].",
- 'config-unicode-update-warning' => "'''Advarsel''': Den installerte versjonen av Unicode-normalisereren bruker en eldre versjon av [http://site.icu-project.org/ ICU-prosjektets] bibliotek.
-Du bør [//www.mediawiki.org/wiki/Unicode_normalization_considerations oppgradere] om du er bekymret for å bruke Unicode.",
- 'config-no-db' => 'Fant ikke en passende databasedriver! Du må installere en databasedriver for PHP.
-Følgende databasetyper er støttet: $1
-
-Om du er på delt vertsskap, spør din vertsleverandør om å installere en passende databasedriver.
-Om du kompilerte PHP selv, rekonfigirer den med en aktivert databaseklient, for eksempel ved å bruke <code>./configure --with-mysql</code>.
-Om du installerte PHP fra en Debian- eller Ubuntu-pakke må du også installere modulen php5-mysql.',
- 'config-outdated-sqlite' => "'''Advarsel''': Du har SQLite $1, som er en eldre versjon enn minimumskravet SQLite $2. SQLite vil ikke være tilgjengelig.",
- 'config-no-fts3' => "'''Advarsel''': SQLite er kompilert uten [//sqlite.org/fts3.html FTS3-modulen], søkefunksjoner vil ikke være tilgjengelig på dette bakstykket.",
- 'config-register-globals' => "'''Advarsel: PHPs <code>[http://php.net/register_globals register_globals]</code>-alternativ er aktivert.'''
-'''Deaktiver det om du kan.'''
-MediaWiki vil fungere, men tjeneren din er utsatt for potensielle sikkerhetssårbarheter.",
- 'config-magic-quotes-runtime' => "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] er aktiv!'''
-Dette alternativet ødelegger inndata på en uforutsigbar måte.
-Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
- 'config-magic-quotes-sybase' => "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] er aktiv!'''
-Dette alternativet ødelegger inndata på en uforutsigbar måte.
-Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
- 'config-mbstring' => "'''Kritisk: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] er aktiv!'''
-Dette alternativet fører til feil og kan ødelegge data på en uforutsigbar måte.
-Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
- 'config-ze1' => "'''Kritisk: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] er aktiv!'''
-Dette alternativet fører til horrible feil med MediaWiki.
-Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
- 'config-safe-mode' => "'''Advarsel:''' PHPs [http://www.php.net/features.safe-mode safe mode] er aktiv.
-Det kan føre til problem, spesielt hvis du bruker støtte for filopplastinger og <code>math</code>.",
- 'config-xml-bad' => 'PHPs XML-modul mangler.
-MediaWiki krever funksjonene i denne modulen og vil ikke virke i denne konfigurasjonen.
-Hvis du kjører Mandrak, installer pakken php-xml.',
- 'config-pcre' => 'PCRE-støttemodulen ser ut til å mangle.
-MediaWiki krever funksjonene for de Perl-kompatible regulære uttrykkene for å virke.',
- 'config-pcre-no-utf8' => "'''Fatal''': PHPs PCRE modul ser ut til å være kompilert uten PCRE_UTF8-støtte.
-MediaWiki 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.
-Dette er sannsynligvis for lavt.
-Installasjonen kan mislykkes!",
- 'config-ctype' => "'''Fatal feil''': PHP må kompileres med støtte for [http://www.php.net/manual/en/ctype.installation.php Ctype-utvidelsen].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] er innstallert',
- 'config-apc' => '[http://www.php.net/apc APC] er innstallert',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] er installert',
- 'config-no-cache' => "'''Advarsel:''' Kunne ikke finne [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].
-Objekthurtiglagring er ikke aktivert.",
- 'config-mod-security' => "'''Advarsel''': Din web-tjener har [http://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.
-Sjekk [http://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-imagemagick' => 'Fant ImageMagick: <code>$1</code>.
-Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
- 'config-gd' => 'Fant innebygd GD-grafikkbibliotek.
-Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
- 'config-no-scaling' => 'Kunne ikke finne GD-bibliotek eller ImageMagick.
-Bildeminiatyrisering vil være deaktivert.',
- 'config-no-uri' => "'''Feil:''' Kunne ikke bestemme gjeldende URI.
-Installasjon avbrutt.",
- 'config-no-cli-uri' => "'''Advarsel''': Ingen --scriptpath er angitt; bruker standard: <code>$1</code>.",
- 'config-using-server' => 'Bruker servernavnet "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Bruker server-URL "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Advarsel:''' Din standardmappe for opplastinger <code>$1</code> er sårbar for kjøring av vilkårlige skript.
-Selv om MediaWiki sjekker alle opplastede filer for sikkerhetstrusler er det sterkt anbefalt å [//www.mediawiki.org/wiki/Manual:Security#Upload_security lukke denne sikkerhetssårbarheten] før du aktiverer opplastinger.",
- 'config-no-cli-uploads-check' => "'''Advarsel:''' Din standard-katalog for opplastinger (<code>$1</code>) er ikke kontrollert for sårbarhet overfor vilkårlig skript-kjøring under CLI-installasjonen.",
- 'config-brokenlibxml' => 'Ditt system har en kombinasjon av PHP- og libxml2-versjoner som er feilaktige og kan forårsake skjult dataødeleggelse i MediaWiki og andre web-applikasjoner.
-Oppgrader til PHP 5.2.9 eller nyere og libxml 2 2.7.3 eller nyere ([//bugs.php.net/bug.php?id=45996 Feil-liste for PHP]).
-Installasjon abortert.',
- 'config-using531' => 'MediaWiki kan ikke brukes med PHP $1 på grunn av en feil med referanseparametere til <code>__call()</code>.
-Oppgrader til PHP 5.3.2 eller høyere, eller nedgrader til PHP 5.3.0 for å løse dette.
-Installasjonen avbrutt.',
- 'config-suhosin-max-value-length' => 'Suhosin er installert og begrenser GET-parameterlengder til $1 bytes. MediaWiki\'s ResourceLoader-komponent klarer å komme rundt denne begrensningen, med med redusert ytelse. På mulig bør du sette <code>suhosin.get.max_value_length</code> til minst 1024 i <code>php.ini</code>, og sette <code>$wgResourceLoaderMaxQueryLength</code> til samme verdi i LocalSettings.php.', # Fuzzy
- 'config-db-type' => 'Databasetype:',
- 'config-db-host' => 'Databasevert:',
- 'config-db-host-help' => 'Hvis databasen kjører på en annen tjenermaskin, skriv inn vertsnavnet eller IP-adressen her.
-
-Hvis du bruker et webhotell, vil du kunne be om aktuelt vertsnavn fra din leverandør.
-
-Hvis du installerer på en Windowstjener og bruker MySQL, kan det hende at «localhost» ikke brukes som tjenernavn. Hvis så er tilfelle, prøv «127.0.0.1» som lokal IP-adresse.
-
-Hvis du bruker PostgreSQL, la dette feltet være blankt slik at koplingen gjøres via en "Unix socket".',
- 'config-db-host-oracle' => 'Database TNS:',
- 'config-db-host-oracle-help' => 'Skriv inn et gyldig [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; en tnsnames.ora-fil må være synlig for installasjonsprosessen.<br />Hvis du bruker klientbibliotek 10g eller nyere kan du også bruke navngivingsmetoden [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identifiser denne wikien',
- 'config-db-name' => 'Databasenavn:',
- 'config-db-name-help' => 'Velg et navn som identifiserer wikien din.
-Det bør ikke inneholde mellomrom.
-
-Hvis du bruker en delt nettvert vil verten din enten gi deg et spesifikt databasenavn å bruke, eller la deg opprette databaser via kontrollpanelet.',
- 'config-db-name-oracle' => 'Databaseskjema:',
- 'config-db-account-oracle-warn' => 'Det finnes tre mulig fremgangsmåter for å installere Oracle som database:
-
-Hvis du ønsker å opprette en databasekonto som del av installasjonsprosessen, oppgi da en konto med SYSDBA-rolle som databasekonto for installasjonen og angi påkrevd autentiseringsinformasjon for web-aksesskontoen. Ellers kan du enten opprette web-aksesskontoen manuelt eller kun oppgi den kontoen (hvis den har påkrevede tillatelser for å opprette skjemeobjektene) , alternativt oppgi to ulike kontoer, en med opprettelsesprivilegier (create) og en begrenset konto for web-aksess.
-
-Skript for å opprette en konto med påkrevde privilegier finnes i "maintenance/oracle/"-folderen av denne installasjonen. Husk at det å bruke en begrenset konto vil blokkere all vedlikeholdsfunksjonalitet med standard konto.',
- 'config-db-install-account' => 'Brukerkonto for installasjon',
- 'config-db-username' => 'Databasebrukernavn:',
- 'config-db-password' => 'Databasepassord:',
- 'config-db-password-empty' => 'Skriv inn et passord for den nye databasebrukeren: $1.
-Det er mulig å opprette brukere uten passord, men dette er ikke sikkert.',
- 'config-db-install-username' => 'Skriv inn brukernavnet som vil bli brukt til å koble til databasen under installasjonsprosessen.
-Dette er ikke brukernavnet på MediaWiki-kontoen; dette er brukernavnet for databasen din.',
- 'config-db-install-password' => 'Skriv inn passordet som vil bli brukt til å koble til databasen under installasjonsprosessen.
-Dette er ikke passordet på MediaWiki-kontoen; dette er passordet for databasen din.',
- 'config-db-install-help' => 'Skriv inn brukernavnet og passordet som vil bli brukt for å koble til databasen under installasjonsprosessen.',
- 'config-db-account-lock' => 'Bruk det samme brukernavnet og passordet under normal drift',
- 'config-db-wiki-account' => 'Brukerkonto for normal drift',
- 'config-db-wiki-help' => 'Skriv inn brukernavnet og passordet som vil bli brukt til å koble til databasen under normal wikidrift.
-Hvis kontoen ikke finnes, og installasjonskontoen har tilstrekkelige privilegier, vil denne brukerkontoen bli opprettet med et minimum av privilegier, tilstrekkelig for å operere wikien.',
- 'config-db-prefix' => 'Databasetabellprefiks:',
- 'config-db-prefix-help' => 'Hvis du trenger å dele en database mellom flere wikier, eller mellom MediaWiki og andre nettapplikasjoner, kan du velge å legge til et prefiks til alle tabellnavnene for å unngå konflikter.
-Ikke bruk mellomrom.
-
-Dette feltet er vanligvis tomt.',
- 'config-db-charset' => 'Databasetegnsett',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binær',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 bakoverkompatibel UTF-8',
- 'config-charset-help' => "'''Advarsel:''' Hvis du bruker '''bakoverkompatibel UTF-8''' på MySQL 4.1+, og deretter sikkerhetskopierer databasen med <code>mysqldump</code> kan det ødelegge alle ikke-ASCII tegn og irreversibelt ødelegge dine sikkerhetskopier!
-
-I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.
-Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.
-I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
-men det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
- 'config-mysql-old' => 'MySQL $1 eller senere kreves, du har $2.',
- 'config-db-port' => 'Databaseport:',
- 'config-db-schema' => 'Skjema for MediaWiki',
- 'config-db-schema-help' => 'Dette skjemaet er som regel riktig.
-Bare endre det hvis du vet at du trenger det.',
- 'config-pg-test-error' => "Får ikke kontakt med database '''$1''': $2",
- 'config-sqlite-dir' => 'SQLite datamappe:',
- 'config-sqlite-dir-help' => "SQLite lagrer alle data i en enkelt fil.
-
-Mappen du oppgir må være skrivbar for nettjeneren under installasjonen.
-
-Den bør '''ikke''' være tilgjengelig fra nettet, dette er grunnen til at vi ikke legger det der PHP-filene dine er.
-
-Installasjonsprogrammet vil skrive en <code>.htaccess</code>-fil sammen med det, men om det mislykkes kan noen få tilgang til din råe database. Dette inkluderer rå brukerdata (e-postadresser, hashede passord) samt slettede revisjoner og andre begrensede data på wikien.
-
-Vurder å plassere databasen et helt annet sted, for eksempel i <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Standard tabellrom:',
- 'config-oracle-temp-ts' => 'Midlertidig tabellrom:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki støtter følgende databasesystem:
-
-$1
-
-Hvis du ikke ser databasesystemet du prøver å bruke i listen nedenfor, følg instruksjonene det er lenket til over for å aktivere støtte.',
- 'config-support-mysql' => '* $1 er det primære målet for MediaWiki og er best støttet ([http://www.php.net/manual/en/mysql.installation.php hvordan kompilere PHP med MySQL-støtte])',
- 'config-support-postgres' => '* $1 er et populært åpen kildekode-databasesystem som er et alternativ til MySQL ([http://www.php.net/manual/en/pgsql.installation.php hvordan kompilere PHP med PostgreSQL-støtte]). Det kan være noen små utestående feil og det anbefales ikke for bruk i et produksjonsmiljø.',
- 'config-support-sqlite' => '* $1 er et lettvekts-databasesystem som er veldig godt støttet. ([http://www.php.net/manual/en/pdo.installation.php hvordan kompilere PHP med SQLite-støtte], bruker PDO)',
- 'config-support-oracle' => '* $1 er en kommersiell bedriftsdatabase. ([http://www.php.net/manual/en/oci8.installation.php Hvordan kompilere PHP med OCI8-støtte])',
- 'config-header-mysql' => 'MySQL-innstillinger',
- 'config-header-postgres' => 'PostgreSQL-innstillinger',
- 'config-header-sqlite' => 'SQLite-innstillinger',
- 'config-header-oracle' => 'Oracle-innstillinger',
- 'config-invalid-db-type' => 'Ugyldig databasetype',
- 'config-missing-db-name' => 'Du må skrive inn en verdi for «Databasenavn»',
- 'config-missing-db-host' => 'Du må skrive inn en verdi for «Databasevert»',
- 'config-missing-db-server-oracle' => 'Du må skrive inn en verdi for «Database TNS»',
- 'config-invalid-db-server-oracle' => 'Ugyldig database-TNS «$1».
-Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_) og punktum (.).',
- 'config-invalid-db-name' => 'Ugyldig databasenavn «$1».
-Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9), undestreker (_) og bindestreker (-).',
- 'config-invalid-db-prefix' => 'Ugyldig databaseprefiks «$1».
-Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9), undestreker (_) og bindestreker (-).',
- 'config-connection-error' => '$1.
-
-Sjekk verten, brukernavnet og passordet nedenfor og prøv igjen.',
- 'config-invalid-schema' => 'Ugyldig skjema for MediaWiki «$1».
-Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
- 'config-db-sys-create-oracle' => 'Installasjonsprogrammet støtter kun bruk av en SYSDBA-konto for opprettelse av en ny konto.',
- 'config-db-sys-user-exists-oracle' => 'Brukerkontoen «$1» finnes allerede. SYSDBA kan kun brukes for oppretting av nye kontoer!',
- 'config-postgres-old' => 'PostgreSQL $1 eller senere kreves, du har $2.',
- 'config-sqlite-name-help' => 'Velg et navn som identifiserer wikien din.
-Ikke bruk mellomrom eller bindestreker.
-Dette vil bli brukt til SQLite-datafilnavnet.',
- 'config-sqlite-parent-unwritable-group' => 'Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.
-
-Installasjonsprogrammet har bestemt brukeren nettjeneren din kjører som.
-Gjør <code><nowiki>$3</nowiki></code>-mappen skrivbar for denne for å fortsette.
-På et Unix/Linux-system, gjør:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.
-
-Installasjonsprogrammet kunne ikke bestemme brukeren nettjeneren din kjører som.
-Gjør <code><nowiki>$3</nowiki></code>-mappen globalt skrivbar for denne (og andre!) for å fortsette.
-På et Unix/Linux-system, gjør:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Feil under oppretting av datamappen «$1».
-Sjekk plasseringen og prøv igjen.',
- 'config-sqlite-dir-unwritable' => 'Kan ikke skrive til mappen «$1».
-Endre dens tilganger slik at nettjeneren kan skrive til den og prøv igjen.',
- 'config-sqlite-connection-error' => '$1.
-
-Sjekk datamappen og databasenavnet nedenfor og prøv igjen.',
- 'config-sqlite-readonly' => 'Filen <code>$1</code> er ikke skrivbar.',
- 'config-sqlite-cant-create-db' => 'Kunne ikke opprette databasefilen <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP mangler FTS3-støtte, nedgraderer tabeller',
- 'config-can-upgrade' => "Det er MediaWiki-tabeller i denne databasen.
-For å oppgradere dem til MediaWiki $1, klikk '''Fortsett'''.",
- 'config-upgrade-done' => "Oppgradering fullført.
-
-Du kan nå [$1 begynne å bruke wikien din].
-
-Hvis du ønsker å regenerere <code>LocalSettings.php</code>-filen din, klikk på knappen nedenfor.
-Dette er '''ikke anbefalt''' med mindre du har problemer med wikien din.",
- 'config-upgrade-done-no-regenerate' => 'Oppgradering fullført.
-
-Du kan nå [$1 begynne å bruke wikien din].',
- 'config-regenerate' => 'Regenerer LocalSettings.php →',
- 'config-show-table-status' => '<code>SHOW TABLE STATUS</code> etterspørselen mislyktes!',
- 'config-unknown-collation' => "'''Advarsel:''' Databasen bruker en ukjent sortering.",
- 'config-db-web-account' => 'Databasekonto for nettilgang',
- 'config-db-web-help' => 'Velg brukernavnet og passordet som nettjeneren skal bruke for å koble til databasetjeneren under ordinær drift av wikien.',
- 'config-db-web-account-same' => 'Bruk samme konto som for installasjonen',
- 'config-db-web-create' => 'Opprett kontoen om den ikke finnes allerede',
- 'config-db-web-no-create-privs' => 'Kontoen du oppga for installasjonen har ikke nok privilegier til å opprette en konto.
-Kontoen du oppgir her må finnes allerede.',
- 'config-mysql-engine' => 'Lagringsmotor:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-engine-help' => "'''InnoDB''' er nesten alltid det beste alternativet siden den har god støtte for samtidighet («concurrency»).
-
-'''MyISAM''' kan være raskere i enbruker- eller les-bare-installasjoner.
-MyISAM-databaser har en tendens til å bli ødelagt oftere enn InnoDB-databaser.",
- 'config-mysql-charset' => 'Databasetegnsett:',
- 'config-mysql-binary' => 'Binær',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.
-Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.
-
-I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
-men det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
- 'config-site-name' => 'Navn på wiki:',
- 'config-site-name-help' => 'Dette vil vises i tittellinjen i nettleseren og diverse andre steder.',
- 'config-site-name-blank' => 'Skriv inn et nettstedsnavn.',
- 'config-project-namespace' => 'Prosjektnavnerom:',
- 'config-ns-generic' => 'Prosjekt',
- 'config-ns-site-name' => 'Samme som wikinavnet: $1',
- 'config-ns-other' => 'Annet (spesifiser)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => "Etter Wikipedias eksempel holder mange wikier deres sider med retningslinjer atskilt fra sine innholdssider, i et «'''prosjektnavnerom'''».
-Alle sidetitler i dette navnerommet starter med et gitt prefiks som du kan angi her.
-Tradisjonelt er dette prefikset avledet fra navnet på wikien, men det kan ikke innholde punkttegn som «#» eller «:».",
- 'config-ns-invalid' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er ugyldig.
-Angi et annet prosjektnavnerom.',
- 'config-ns-conflict' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er i konflikt med et standard MediaWiki-navnerom.
-Angi et annet prosjekt-navnerom.',
- 'config-admin-box' => 'Administratorkonto',
- 'config-admin-name' => 'Ditt navn:',
- 'config-admin-password' => 'Passord:',
- 'config-admin-password-confirm' => 'Passord igjen:',
- 'config-admin-help' => 'Skriv inn ditt ønskede brukernavn her, for eksempel «Joe Bloggs».
-Dette er navnet du vil bruke for å logge inn på denne wikien.',
- 'config-admin-name-blank' => 'Skriv inn et administratorbrukernavn.',
- 'config-admin-name-invalid' => 'Det angitte brukernavnet «<nowiki>$1</nowiki>» er ugyldig.
-Angi et annet brukernavn.',
- 'config-admin-password-blank' => 'Skriv inn et passord for administratorkontoen.',
- 'config-admin-password-same' => 'Passordet skal ikke være det samme som brukernavnet.',
- 'config-admin-password-mismatch' => 'De to passordene du skrev inn samsvarte ikke.',
- 'config-admin-email' => 'E-postadresse:',
- 'config-admin-email-help' => 'Skriv inn en e-postadresse her for at du skal kunne motta e-post fra andre brukere på wikien, tilbakestille passordet ditt, og bli varslet om endringer på sider på overvåkningslisten din. Du kan la dette feltet stå tomt.',
- 'config-admin-error-user' => 'Intern feil ved opprettelse av en admin med navnet «<nowiki>$1</nowiki>».',
- 'config-admin-error-password' => 'Intern feil ved opprettelse av passord for admin «<nowiki>$1</nowiki>»: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Du har skrevet inn en ugyldig e-postadresse.',
- 'config-subscribe' => 'Abonner på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlisten for utgivelsesannonseringer].',
- 'config-subscribe-help' => 'Dette er en lav-volums e-postliste brukt til utgivelsesannonseringer, herunder viktige sikkerhetsannonseringer.
-Du bør abonnere på den og oppdatere MediaWikiinstallasjonen din når nye versjoner kommer ut.',
- 'config-almost-done' => 'Du er nesten ferdig!
-Du kan hoppe over de resterende konfigurasjonene og installere wikien nå.',
- 'config-optional-continue' => 'Spør meg flere spørsmål.',
- 'config-optional-skip' => 'Jeg er lei, bare installer wikien.',
- 'config-profile' => 'Brukerrettighetsprofil:',
- 'config-profile-wiki' => 'Tradisjonell wiki', # Fuzzy
- 'config-profile-no-anon' => 'Kontoopprettelse påkrevd',
- 'config-profile-fishbowl' => 'Kun autoriserte bidragsytere',
- 'config-profile-private' => 'Privat wiki',
- 'config-profile-help' => "Wikier fungerer best når du lar så mange mennesker som mulig redigere den.
-I MediaWiki er det lett å revidere siste endringer og tilbakestille eventuell skade som er gjort av naive eller ondsinnede brukere.
-
-Imidlertid har mange funnet at MediaWiki er nyttig i mange roller, og av og til er det ikke lett å overbevise alle om fordelene med wikimåten.
-Så du har valget.
-
-En '''{{int:config-profile-wiki}}''' tillater alle å redigere, selv uten å logge inn.
-En wiki med '''{{int:config-profile-no-anon}}''' tilbyr ekstra ansvarlighet, men kan avskrekke tilfeldige bidragsytere.
-
-'''{{int:config-profile-fishbowl}}'''-scenariet tillater godkjente brukere å redigere, mens publikum kan se sider, og også historikken.
-En '''{{int:config-profile-private}}''' tillater kun godkjente brukere å se sider, den samme gruppen som får lov til å redigere dem.
-
-Mer komplekse konfigurasjoner av brukerrettigheter er tilgjengelig etter installasjon, se det [//www.mediawiki.org/wiki/Manual:User_rights relevante manualavsnittet].", # Fuzzy
- 'config-license' => 'Opphavsrett og lisens:',
- 'config-license-none' => 'Ingen lisensbunntekst',
- 'config-license-cc-by-sa' => 'Creative Commons Navngivelse Del på samme vilkår',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Navngivelse Ikke-kommersiell Del på samme vilkår',
- 'config-license-cc-0' => 'Creative Commons Zero', # Fuzzy
- 'config-license-pd' => 'Offentlig rom',
- 'config-license-cc-choose' => 'Velg en egendefinert Creative Commons-lisens',
- '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å [http://www.php.net/manual/en/mail.configuration.php PHPs e-postinnstillinger] bli konfigurert riktig.
-Hvis du ikke ønsker noen e-postfunksjoner 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',
- 'config-email-usertalk-help' => 'Tillat brukere å motta varsler ved endringer på deres brukerdiskusjonsside hvis de har aktivert dette i deres innstillinger.',
- 'config-email-watchlist' => 'Aktiver overvåkningslistevarsler',
- 'config-email-watchlist-help' => 'Tillat brukere å motta varsler ved endringer på deres overvåkede sider hvis de har aktivert dette i deres innstillinger.',
- 'config-email-auth' => 'Aktiver e-postautentisering',
- 'config-email-auth-help' => "Om dette alternativet er aktivert må brukere bekrefte sin e-postadresse ved å bruke en lenke som blir sendt til dem når de setter eller endrer adressen sin.
-Kun autentiserte e-postadresser kan motta e-post fra andre brukere eller endringsvarsel.
-Å sette dette valget er '''anbefalt''' for offentlige wikier på grunn av potensiell misbruk av e-postfunksjonene.",
- 'config-email-sender' => 'Svar-e-postadresse:',
- 'config-email-sender-help' => 'Skriv inn e-postadressen som skal brukes som svar-adresse ved utgående e-post.
-Det er hit returmeldinger vil bli sendt.
-Mange e-posttjenere krever at minst domenenavnet må være gyldig.',
- 'config-upload-settings' => 'Bilde- og filopplastinger',
- 'config-upload-enable' => 'Aktiver filopplastinger',
- 'config-upload-help' => 'Filopplastinger kan potensielt utsette tjeneren din for sikkerhetsrisikoer.
-For mer informasjon, les [//www.mediawiki.org/wiki/Manual:Security sikkerhetsseksjonen] i manualen.
-
-For å aktivere filopplastinger, endre modusen i <code>images</code>-undermappen i MediaWikis rotmappe slik at nettjeneren kan skrive til den.
-Aktiver så dette alternativet.',
- 'config-upload-deleted' => 'Mappe for slettede filer:',
- 'config-upload-deleted-help' => 'Velg en mappe for å arkivere slettede filer.
-Ideelt burde ikke denne være tilgjengelig for nettet.',
- 'config-logo' => 'Logo-URL:',
- 'config-logo-help' => 'MediaWikis standarddrakt inkluderer plass til en 135x160 pikslers logo i øvre venstre hjørne.
-Last opp et bilde i passende størrelse og skriv inn nettadressen her.
-
-Hvis du ikke ønsker en logo, la denne boksen være tom.', # Fuzzy
- 'config-instantcommons' => 'Aktiver Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] er en funksjon som gjør det mulig for wikier å bruke bilder, lyder og andre media funnet på nettstedet [//commons.wikimedia.org/ Wikimedia Commons].
-For å gjøre dette krever MediaWiki tilgang til internett.
-
-For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man setter opp dette for andre wikier enn Wikimedia Commons, konsulter [//mediawiki.org/wiki/Manual:$wgForeignFileRepos manualen].',
- 'config-cc-again' => 'Velg igjen...',
- 'config-cc-not-chosen' => 'Velg hvilken Creative Commons-lisens du ønsker og klikk «fortsett».',
- 'config-advanced-settings' => 'Avansert konfigurasjon',
- 'config-extensions' => 'Utvidelser',
- 'config-install-step-done' => 'ferdig',
- 'config-install-step-failed' => 'mislyktes',
- 'config-install-extensions' => 'Inkludert utvidelser',
- 'config-install-database' => 'Setter opp database',
- 'config-install-user' => 'Oppretter databasebruker',
- 'config-install-user-alreadyexists' => 'Brukeren «$1» finnes allerede',
- 'config-install-user-create-failed' => 'Opprettelse av brukeren «$1» mislyktes: $2',
- 'config-install-user-grant-failed' => 'Å gi tillatelse til brukeren «$1» mislyktes: $2',
- 'config-install-tables' => 'Oppretter tabeller',
- 'config-install-mainpage-failed' => 'Kunne ikke sette inn hovedside: $1',
- 'config-download-localsettings' => 'Last ned <code>LocalSettings.php</code>',
- 'config-help' => 'hjelp',
- 'config-nofile' => 'Filen "$1" ble ikke funnet. Kan den være blitt slettet?',
- 'mainpagetext' => "'''MediaWiki-programvaren er nå installert.'''",
- 'mainpagedocfooter' => 'Se [//meta.wikimedia.org/wiki/Help:Contents brukerveiledningen] for informasjon om hvordan du bruker wiki-programvaren.
-
-==Å starte==
-*[//www.mediawiki.org/wiki/Manual:Configuration_settings Oppsettsliste]
-*[//www.mediawiki.org/wiki/Manual:FAQ Ofte stilte spørsmål]
-*[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-postliste]', # Fuzzy
-);
-
-/** Low German (Plattdüütsch)
- * @author Joachim Mos
- */
-$messages['nds'] = array(
- 'config-page-name' => 'Naam',
- 'config-ns-generic' => 'Projekt',
- 'config-admin-name' => 'Dien Naam:',
- 'config-admin-password' => 'Passwoord:',
- 'config-help' => 'Hülp',
- 'mainpagetext' => "'''De MediaWiki-Software is mit Spood installeert worrn.'''",
- 'mainpagedocfooter' => 'Kiek de [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentatschoon för dat Anpassen vun de Brukerböversiet]
-un dat [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Brukerhandbook] för Hülp to de Bruuk un Konfiguratschoon.', # Fuzzy
-);
-
-/** Low Saxon (Netherlands) (Nedersaksies)
- * @author Servien
- */
-$messages['nds-nl'] = array(
- 'mainpagetext' => "'''’t Installeren van de MediaWiki programmatuur is succesvol.'''",
- 'mainpagedocfooter' => 'Bekiek de [//meta.wikimedia.org/wiki/Help:Contents haandleiding] veur informasie over t gebruuk van de wikiprogrammatuur.
-
-== Meer hulpe ==
-* [//www.mediawiki.org/wiki/Help:Configuration_settings Lieste mit instellingen]
-* [//www.mediawiki.org/wiki/Help:FAQ MediaWiki-vragen die vake esteld wörden]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-postlieste veur nieje versies]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Maak MediaWiki beschikbaor in joew taal]',
-);
-
-/** Nepali (नेपाली)
- * @author Bhawani Gautam
- * @author RajeshPandey
- */
-$messages['ne'] = array(
- 'mainpagetext' => "'''मीडिया सफलतापूर्वक कम्प्यूटरमा स्थापित भयो ।'''",
- 'mainpagedocfooter' => ' विकी अनुप्रयोग कसरी प्रयोग गर्ने भन्ने जानकारीको लागि [//meta.wikimedia.org/wiki/Help:Contents प्रयोगकर्ता सहायता] हेर्नुहोस्
-
-== सुरू गर्नको लागि ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings विन्यास सेटिङ्ग सूची]
-* [//www.mediawiki.org/wiki/Manual:FAQ मेडियाविकि सामान्य प्रश्नका उत्तरहरु]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मेडियाविकि सुचना मेलिङ्ग सूची]', # Fuzzy
-);
-
-/** Dutch (Nederlands)
- * @author Catrope
- * @author McDutchie
- * @author Purodha
- * @author SPQRobin
- * @author Siebrand
- * @author Tjcool007
- * @author 아라
- */
-$messages['nl'] = array(
- 'config-desc' => 'Het installatieprogramma voor MediaWiki',
- 'config-title' => 'Installatie MediaWiki $1',
- 'config-information' => 'Gegevens',
- 'config-localsettings-upgrade' => 'Er is een bestaand instellingenbestand <code>LocalSettings.php</code> gevonden.
-Voer de waarde van <code>$wgUpgradeKey</code> in in onderstaande invoerveld om deze installatie bij te werken.
-De instelling is terug te vinden in <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Het bestand <code>LocalSettings.php</code> is al aanwezig.
-Voer <code>update.php</code> uit om deze installatie bij te werken.',
- 'config-localsettings-key' => 'Upgradesleutel:',
- 'config-localsettings-badkey' => 'De sleutel die u hebt opgegeven is onjuist',
- 'config-upgrade-key-missing' => 'Er is een bestaande installatie van MediaWiki aangetroffen.
-Plaats de volgende regel onderaan uw <code>LocalSettings.php</code> om deze installatie bij te werken:
-
-$1',
- 'config-localsettings-incomplete' => 'De bestaande inhoud van <code>LocalSettings.php</code> lijkt incompleet.
-De variabele $1 is niet ingesteld.
-Wijzig <code>LocalSettings.php</code> zodat deze variabele is ingesteld en klik op "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Er is een fout opgetreden tijdens het verbinden van de database met de instellingen uit <code>LocalSettings.php</code> of <code>AdminSettings.php</code>. Los het probleem met de instellingen op en probeer het daarna opnieuw.
-
-$1',
- 'config-session-error' => 'Fout bij het begin van de sessie: $1',
- 'config-session-expired' => 'Uw sessiegegevens zijn verlopen.
-Sessies zijn ingesteld om een levensduur van $1 te hebben.
-U kunt deze wijzigen via de instelling <code>session.gc_maxlifetime</code> in php.ini.
-Begin het installatieproces opnieuw.',
- 'config-no-session' => 'Uw sessiegegevens zijn verloren gegaan.
-Controleer uw php.ini en zorg dat er een juiste map is ingesteld voor <code>session.save_path</code>.',
- 'config-your-language' => 'Uw taal:',
- 'config-your-language-help' => 'Selecteer een taal om tijdens het installatieproces te gebruiken.',
- 'config-wiki-language' => 'Wikitaal:',
- 'config-wiki-language-help' => 'Selecteer de taal waar de wiki voornamelijk in wordt geschreven.',
- 'config-back' => '← Terug',
- 'config-continue' => 'Doorgaan →',
- 'config-page-language' => 'Taal',
- 'config-page-welcome' => 'Welkom bij MediaWiki!',
- 'config-page-dbconnect' => 'Verbinding maken met database',
- 'config-page-upgrade' => 'Bestaande installatie bijwerken',
- 'config-page-dbsettings' => 'Databaseinstellingen',
- 'config-page-name' => 'Naam',
- 'config-page-options' => 'Opties',
- 'config-page-install' => 'Installeren',
- 'config-page-complete' => 'Voltooid!',
- 'config-page-restart' => 'Installatie herstarten',
- 'config-page-readme' => 'Lees mij',
- 'config-page-releasenotes' => 'Release notes',
- 'config-page-copying' => 'Kopiëren',
- 'config-page-upgradedoc' => 'Bijwerken',
- 'config-page-existingwiki' => 'Bestaande wiki',
- 'config-help-restart' => 'Wilt u alle opgeslagen gegevens die u hebt ingevoerd wissen en het installatieproces opnieuw starten?',
- 'config-restart' => 'Ja, opnieuw starten',
- 'config-welcome' => '=== Controle omgeving ===
-Er worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.
-Als u hulp nodig hebt bij de installatie, lever deze gegevens dan ook aan.',
- 'config-copyright' => "=== Auteursrechten en voorwaarden ===
-
-$1
-
-Dit programma is vrije software. U mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar uw keuze - enige latere versie.
-
-Dit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.
-Zie de GNU General Public License voor meer informatie.
-
-Samen met dit programma hoort u een <doclink href=Copying>exemplaar van de GNU General Public License</doclink> ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [http://www.gnu.org/copyleft/gpl.html lees de licentie online].",
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki thuispagina]
-* [//www.mediawiki.org/wiki/Help:Contents Gebruikershandleiding] (Engelstalig)
-* [//www.mediawiki.org/wiki/Manual:Contents Beheerdershandleiding] (Engelstalig)
-* [//www.mediawiki.org/wiki/Manual:FAQ Veel gestelde vragen] (Engelstalig)
-----
-* <doclink href=Readme>Leesmij</doclink> (Engelstalig)
-* <doclink href=ReleaseNotes>Release notes</doclink> (Engelstalig)
-* <doclink href=Copying>Kopiëren</doclink> (Engelstalig)
-* <doclink href=UpgradeDoc>Versie bijwerken</doclink> (Engelstalig)',
- 'config-env-good' => 'De omgeving is gecontroleerd.
-U kunt MediaWiki installeren.',
- 'config-env-bad' => 'De omgeving is gecontroleerd.
-U kunt MediaWiki niet installeren.',
- 'config-env-php' => 'PHP $1 is op dit moment geïnstalleerd.',
- 'config-env-php-toolow' => 'PHP $1 is geïnstalleerd.
-MediaWiki heeft PHP $2 of hoger nodig om correct te kunnen werken.',
- 'config-unicode-using-utf8' => 'Voor Unicode-normalisatie wordt utf8_normalize.so van Brion Vibber gebruikt.',
- 'config-unicode-using-intl' => 'Voor Unicode-normalisatie wordt de [http://pecl.php.net/intl PECL-extensie intl] gebruikt.',
- 'config-unicode-pure-php-warning' => "'''Waarschuwing''': de [http://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicodenormalisatie af te handelen en daarom wordt de langzame PHP-implementatie gebruikt.
-Als u MediaWiki voor een website met veel verkeer installeert, lees u dan in over [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicodenormalisatie].",
- 'config-unicode-update-warning' => "'''Waarschuwing''': de geïnstalleerde versie van de Unicodenormalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].
-U moet [//www.mediawiki.org/wiki/Unicode_normalization_considerations bijwerken] als Unicode voor u van belang is.",
- 'config-no-db' => 'Het was niet mogelijk een geschikte databasedriver te vinden voor PHP.
-U moet een databasedriver installeren voor PHP.
-De volgende databases worden ondersteund: $1.
-
-Als u op een gedeelde omgeving zit, vraag dan aan uw hostingprovider een geschikte databasedriver te installeren.
-Als u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.
-Als u PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.',
- 'config-outdated-sqlite' => "''' Waarschuwing:''' u gebruikt SQLite $1. SQLite is niet beschikbaar omdat de minimaal vereiste versie $2 is.",
- 'config-no-fts3' => "'''Waarschuwing''': SQLite is gecompileerd zonder de module [//sqlite.org/fts3.html FTS3]; er zijn geen zoekfuncties niet beschikbaar.",
- 'config-register-globals' => "'''Waarschuwing: de PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
-'''Schakel deze uit als dat mogelijk is.'''
-MediaWiki kan ermee werken, maar uw server is dan meer kwetsbaar voor beveiligingslekken.",
- 'config-magic-quotes-runtime' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is actief!'''
-Deze instelling zorgt voor gegevenscorruptie.
-U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
- 'config-magic-quotes-sybase' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] is actief!'''
-Deze instelling zorgt voor gegevenscorruptie.
-U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
- 'config-mbstring' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is actief!'''
-Deze instelling zorgt voor gegevenscorruptie.
-U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
- 'config-ze1' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] is actief!'''
-Deze instelling zorgt voor grote problemen in MediaWiki.
-U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
- 'config-safe-mode' => "'''Waarschuwing:'''
-'''PHP's [http://www.php.net/features.safe-mode veilige modus] is actief.'''
-Dit kan problemen veroorzaken, vooral bij het uploaden van bestanden en ondersteuning van <code>math</code>.",
- 'config-xml-bad' => 'De XML-module van PHP ontbreekt.
-MediaWiki heeft de functies van deze module nodig en werkt niet zonder deze module.
-Als u gebruik maakt van Mandrake, installeer dan het package php-xml.',
- 'config-pcre' => 'De ondersteuningsmodule PCRE lijkt te missen.
-MediaWiki vereist dat de met Perl compatibele reguliere expressies werken.',
- 'config-pcre-no-utf8' => "'''Fataal:''' de module PRCE van PHP lijkt te zijn gecompileerd zonder ondersteuning voor PCRE_UTF8.
-MediaWiki 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.
-Dit is waarschijnlijk te laag.
-De installatie kan mislukken!",
- 'config-ctype' => "'''Fataal:''' PHP moet gecompileerd zijn met ondersteuning voor de [http://www.php.net/manual/en/ctype.installation.php extensie Ctype].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] is op dit moment geïnstalleerd',
- 'config-apc' => '[http://www.php.net/apc APC] is op dit moment geïnstalleerd',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] is op dit moment geïnstalleerd',
- 'config-no-cache' => "'''Waarschuwing:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] of [http://www.iis.net/download/WinCacheForPhp WinCache] is niet aangetroffen.
-Het cachen van objecten is niet ingeschakeld.",
- 'config-mod-security' => "'''Waarschuwing:''' uw webserver heeft de module [http://modsecurity.org/ mod_security] ingeschakeld. Als deze onjuist is ingesteld, kan dit problemen geven in combinatie met MediaWiki of andere software die gebruikers in staat stelt willekeurige inhoud te posten.
-Lees de [http://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.',
- 'config-git' => 'Versiecontrolesoftware git is aangetroffen: <code>$1</code>',
- 'config-git-bad' => 'Geen git versiecontrolesoftware aangetroffen.',
- 'config-imagemagick' => 'ImageMagick aangetroffen: <code>$1</code>.
-Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.',
- 'config-gd' => 'Ingebouwde GD grafische bibliotheek aangetroffen.
-Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.',
- 'config-no-scaling' => 'De GD-bibliotheek en ImageMagick zijn niet aangetroffen.
-Het maken van miniaturen van afbeeldingen wordt uitgeschakeld.',
- 'config-no-uri' => "'''Fout:''' de huidige URI kon niet vastgesteld worden.
-De installatie is afgebroken.",
- 'config-no-cli-uri' => "'''Waarschuwing:''' de parameter ==scriptpath is niet opgegeven. De standaardwaarde wordt gebruikt: <code>$1</code>.",
- 'config-using-server' => 'De servernaam "<nowiki>$1</nowiki>" wordt gebruikt.',
- 'config-using-uri' => 'De server-URL "<nowiki>$1$2</nowiki>" wordt gebruikt.',
- 'config-uploads-not-safe' => "'''Waarschuwing:''' uw uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.
-Hoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [//www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
- 'config-no-cli-uploads-check' => "''Waarschuwing:'' uw standaardmap voor uploads (<code>$1</code>) wordt niet gecontroleerd op kwetsbaarheden voor het uitvoeren van willekeurige scripts gedurende de CLI-installatie.",
- 'config-brokenlibxml' => 'Uw systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.
-Upgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger([//bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).
-De installatie wordt afgebroken.',
- 'config-using531' => 'PHP $1 is niet compatibel met MediaWiki vanwege een fout met betrekking tot referentieparameters met <code>__call()</code>.
-Werk uw PHP bij naar PHP 5.3.2 of hoger of werk bij naar de lagere versie PHP 5.3.0 om dit op te lossen.
-De installatie wordt afgebroken.',
- 'config-suhosin-max-value-length' => 'Suhosin is geïnstalleerd en beperkt de GET-parameter <code>length</code> tot $1 bytes.
-De ResourceLoader van MediaWiki omzeilt deze beperking, maar dat is slecht voor de prestaties.
-Als het mogelijk is, moet u de waarde "<code>suhosin.get.max_value_length</code>" in <code>php.ini</code> instellen op 1024 of hoger en <code>$wgResourceLoaderMaxQueryLength</code> in LocalSettings.php op dezelfde waarde instellen.',
- 'config-db-type' => 'Databasetype:',
- 'config-db-host' => 'Databasehost:',
- 'config-db-host-help' => 'Als uw databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.
-
-Als u gebruik maakt van gedeelde webhosting, hoort uw provider u de juiste hostnaam te hebben verstrekt.
-
-Als u MediaWiki op een Windowsserver installeert en MySQL gebruikt, dan werkt "localhost" mogelijk niet als servernaam.
-Als het inderdaad niet werkt, probeer dan "127.0.0.1" te gebruiken als lokaal IP-adres.
-
-Als u PostgreSQL gebruikt, laat dit veld dan leeg om via een Unix-socket te verbinden.',
- 'config-db-host-oracle' => 'Database-TNS:',
- 'config-db-host-oracle-help' => 'Voer een geldige [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] in; een tnsnames.ora-bestand moet zichtbaar zijn voor deze installatie.<br />Als u gebruik maakt van clientlibraries 10g of een latere versie, kunt u ook gebruik maken van de naamgevingsmethode [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identificeer deze wiki',
- 'config-db-name' => 'Databasenaam:',
- 'config-db-name-help' => 'Kies een naam die uw wiki identificeert.
-Er mogen geen spaties gebruikt worden.
-Als u gebruik maakt van gedeelde webhosting, dan hoort uw provider ofwel u een te gebruiken databasenaam gegeven te hebben, of u aangegeven te hebben hoe u databases kunt aanmaken.',
- 'config-db-name-oracle' => 'Databaseschema:',
- 'config-db-account-oracle-warn' => 'Er zijn drie ondersteunde scenario\'s voor het installeren van Oracle als databasebackend:
-
-Als u een databasegebruiker wilt aanmaken als onderdeel van het installatieproces, geef dan de gegevens op van een databasegebruiker in met de rol SYSDBA voor de installatie en voer de gewenste aanmeldgegevens in voor de gebruiker met webtoegang. U kunt ook de gebruiker met webtoegang handmatig aanmaken en alleen van die gebruiker de aanmeldgegevens opgeven als deze de vereiste rechten heeft om schemaobjecten aan te maken. Als laatste is het mogelijk om aanmeldgegevens van twee verschillende gebruikers op te geven; een met de rechten om schemaobjecten aan te maken, en een met alleen webtoegang.
-
-Een script voor het aanmaken van een gebruiker met de vereiste rechten is te vinden in de map "maintenance/oracle/" van deze installatie. Onthoud dat het gebruiken van een gebruiker met beperkte rechten alle mogelijkheden om beheerscripts uit te voeren met de standaard gebruiker onmogelijk maakt.',
- 'config-db-install-account' => 'Gebruiker voor installatie',
- 'config-db-username' => 'Gebruikersnaam voor database:',
- 'config-db-password' => 'Wachtwoord voor database:',
- 'config-db-password-empty' => 'Voer een wachtwoord in voor de nieuwe databasegebruiker: $1.
-Hoewel het wellicht mogelijk is gebruikers aan te maken zonder wachtwoord, is dit niet veilig.',
- 'config-db-install-username' => 'Voer de gebruikersnaam in die gebruikt moet worden om te verbinden met de database tijdens het installatieproces. Dit is niet de gebruikersnaam van de MediaWikigebruiker. Dit is de gebruikersnaam voor de database.',
- 'config-db-install-password' => 'Voer het wachtwoord in dat gebruikt moet worden om te verbinden met de database tijdens het installatieproces. Dit is niet het wachtwoord van de MediaWikigebruiker. Dit is het wachtwoord voor de database.',
- 'config-db-install-help' => 'Voer de gebruikersnaam en het wachtwoord in die worden gebruikt voor de databaseverbinding tijdens het installatieproces.',
- 'config-db-account-lock' => 'Dezelfde gebruiker en wachwoord gebruiken na de installatie',
- 'config-db-wiki-account' => 'Gebruiker voor na de installatie',
- 'config-db-wiki-help' => 'Voer de gebruikersnaam en het wachtwoord in die gebruikt worden om verbinding te maken met de database na de installatie.
-Als de gebruiker niet bestaat en de gebruiker die tijdens de installatie gebruikt wordt voldoende rechten heeft, wordt deze gebruiker aangemaakt met de minimaal benodigde rechten voor het laten werken van de wiki.',
- 'config-db-prefix' => 'Databasetabelvoorvoegsel:',
- 'config-db-prefix-help' => "Als u een database moet gebruiken voor meerdere wiki's, of voor MediaWiki en een andere applicatie, dan kunt u ervoor kiezen om een voorvoegsel toe te voegen aan de tabelnamen om conflicten te voorkomen.
-Gebruik geen spaties.
-
-Dit veld wordt meestal leeg gelaten.",
- 'config-db-charset' => 'Tekenset voor de database',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binair',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 UTF-8-compatibel',
- 'config-charset-help' => "'''Waarschuwing:''' als u '''achterwaarts compatibel met UTF-8''' gebruikt met MySQL 4.1+ en een back-up van de database maakt met <code>mysqldump</code>, dan kunnen alle niet-ASCII-tekens in uw back-ups onherstelbaar beschadigd raken.
-
-In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
-Dit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledige reeks Unicode-tekens te gebruiken.
-In '''UTF-8-modus''' kent MySQL de tekenset van uw gegevens en kan de databaseserver ze juist weergeven en converteren.
-Het is dan niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
- 'config-mysql-old' => 'U moet MySQL $1 of later gebruiken.
-U gebruikt $2.',
- 'config-db-port' => 'Databasepoort:',
- 'config-db-schema' => 'Schema voor MediaWiki',
- 'config-db-schema-help' => 'Dit schema klopt meestal.
-Wijzig het alleen als u weet dat dit nodig is.',
- 'config-pg-test-error' => "Kan geen verbinding maken met database '''$1''': $2",
- 'config-sqlite-dir' => 'Gegevensmap voor SQLite:',
- 'config-sqlite-dir-help' => "SQLite slaat alle gegevens op in een enkel bestand.
-
-De map die u opgeeft moet beschrijfbaar zijn voor de webserver tijdens de installatie.
-
-Deze mag '''niet toegankelijk''' zijn via het web en het bestand mag dus niet tussen de PHP-bestanden staan.
-
-Het installatieprogramma schrijft het bestand <code>.htaccess</code> weg met het databasebestand, maar als dat niet werkt kan iemand zich toegang tot het ruwe databasebestand verschaffen.
-Ook de gebruikersgegevens (e-mailadressen, wachtwoordhashes) en verwijderde versies en overige gegevens met beperkte toegang via MediaWiki zijn dan onbeschermd.
-
-Overweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld in <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Standaard tablespace:',
- 'config-oracle-temp-ts' => 'Tijdelijke tablespace:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki ondersteunt de volgende databasesystemen:
-
-$1
-
-Als 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-support-mysql' => '* $1 is het primaire databasesysteem voor voor MediaWiki en wordt het best ondersteund ([http://www.php.net/manual/en/mysql.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor MySQL])',
- 'config-support-postgres' => '* $1 is een populair open source databasesysteem als alternatief voor MySQL ([http://www.php.net/manual/en/pgsql.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor PostgreSQL]). Het is mogelijk dat er een aantal bekende problemen zijn met MediaWiki in combinatie met deze database en daarom wordt PostgreSQL niet aanbevolen voor een productieomgeving.',
- 'config-support-sqlite' => '* $1 is een zeer goed ondersteund lichtgewicht databasesysteem ([http://www.php.net/manual/en/pdo.installation.php hoe PHP gecompileerd zijn met ondersteuning voor SQLite]; gebruikt PDO)',
- 'config-support-oracle' => '* $1 is een commerciële data voor grote bedrijven ([http://www.php.net/manual/en/oci8.installation.php PHP compileren met ondersteuning voor OCI8]).',
- 'config-header-mysql' => 'MySQL-instellingen',
- 'config-header-postgres' => 'PostgreSQL-instellingen',
- 'config-header-sqlite' => 'SQLite-instellingen',
- 'config-header-oracle' => 'Oracle-instellingen',
- 'config-invalid-db-type' => 'Ongeldig databasetype',
- 'config-missing-db-name' => 'U moet een waarde opgeven voor "Databasenaam"',
- 'config-missing-db-host' => 'U moet een waarde invoeren voor "Databaseserver"',
- 'config-missing-db-server-oracle' => 'U moet een waarde opgeven voor "Database-TNS"',
- 'config-invalid-db-server-oracle' => 'Ongeldige database-TNS "$1".
-Gebruik "TNS Names" of een "Easy Connect" tekst ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle naamgevingsmethoden])',
- 'config-invalid-db-name' => 'Ongeldige databasenaam "$1".
-Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en streepjes (-).',
- 'config-invalid-db-prefix' => 'Ongeldig databasevoorvoegsel "$1".
-Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en streepjes (-).',
- 'config-connection-error' => '$1.
-
-Controleer de host, gebruikersnaam en wachtwoord en probeer het opnieuw.',
- 'config-invalid-schema' => 'Ongeldig schema voor MediaWiki "$1".
-Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_).',
- 'config-db-sys-create-oracle' => 'Het installatieprogramma biedt alleen de mogelijkheid een nieuwe gebruiker aan te maken met de SYSDBA-gebruiker.',
- 'config-db-sys-user-exists-oracle' => 'De gebruiker "$1" bestaat al. SYSDBA kan alleen gebruikt worden voor het aanmaken van een nieuwe gebruiker!',
- 'config-postgres-old' => 'PostgreSQL $1 of hoger is vereist.
-U gebruikt $2.',
- 'config-sqlite-name-help' => 'Kies een naam die uw wiki identificeert.
-Gebruik geen spaties of koppeltekens.
-Deze naam wordt gebruikt voor het gegevensbestand van SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Het was niet mogelijk de gegevensmap <code><nowiki>$1</nowiki></code> te maken omdat in de bovenliggende map <code><nowiki>$2</nowiki></code> niet geschreven mag worden door de webserver.
-
-Het installatieprogramma heeft vast kunnen stellen onder welke gebruiker de webserver draait.
-Maak de map <code><nowiki>$3</nowiki></code> beschrijfbaar om door te kunnen gaan.
-Voer op een Linux-systeem de volgende opdrachten uit:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Het was niet mogelijk de gegevensmap <code><nowiki>$1</nowiki></code> te maken omdat in de bovenliggende map <code><nowiki>$2</nowiki></code> niet geschreven mag worden door de webserver.
-
-Het installatieprogramma heeft niet vast kunnen stellen onder welke gebruiker de webserver draait.
-Maak de map <code><nowiki>$3</nowiki></code> beschrijfbaar voor de webserver (en anderen!) om door te kunnen gaan.
-Voer op een Linux-systeem de volgende opdrachten uit:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Er is een fout opgetreden tijdens het aanmaken van de gegevensmap "$1".
-Controleer de locatie en probeer het opnieuw.',
- 'config-sqlite-dir-unwritable' => 'Het was niet mogelijk in de map "$1" te schrijven.
-Wijzig de rechten zodat de webserver erin kan schrijven en probeer het opnieuw.',
- 'config-sqlite-connection-error' => '$1.
-
-Controleer de map voor gegevens en de databasenaam hieronder en probeer het opnieuw.',
- 'config-sqlite-readonly' => 'Er kan niet naar bestand <code>$1</code> worden geschreven.',
- 'config-sqlite-cant-create-db' => 'Het was niet mogelijk het databasebestand <code>$1</code> aan te maken.',
- 'config-sqlite-fts3-downgrade' => 'PHP heeft geen ondersteuning voor FTS3.
-De tabellen worden gedowngrade.',
- 'config-can-upgrade' => "Er staan al tabellen voor MediaWiki in deze database.
-Klik op '''Doorgaan''' om ze bij te werken naar MediaWiki $1.",
- 'config-upgrade-done' => "Het bijwerken is afgerond.
-
-Uw kunt [$1 uw wiki nu gebruiken].
-
-Als u uw <code>LocalSettings.php</code> opnieuw wilt aanmaken, klik dan op de knop hieronder.
-Dit is '''niet aan te raden''' tenzij u problemen hebt met uw wiki.",
- 'config-upgrade-done-no-regenerate' => 'Het bijwerken is afgerond.
-
-U kunt nu [$1 uw wiki gebruiken].',
- 'config-regenerate' => 'LocalSettings.php opnieuw aanmaken →',
- 'config-show-table-status' => 'Het uitvoeren van <code>SHOW TABLE STATUS</code> is mislukt!',
- 'config-unknown-collation' => "'''Waarschuwing:''' de database gebruikt een collatie die niet wordt herkend.",
- 'config-db-web-account' => 'Databasegebruiker voor webtoegang',
- 'config-db-web-help' => 'Selecteer de gebruikersnaam en het wachtwoord die de webserver gebruikt om verbinding te maken met de databaseserver na de installatie.',
- 'config-db-web-account-same' => 'Dezelfde gebruiker gebruiken als voor de installatie',
- 'config-db-web-create' => 'Maak de gebruiker aan als deze nog niet bestaat',
- 'config-db-web-no-create-privs' => 'De gebruiker die u hebt opgegeven voor de installatie heeft niet voldoende rechten om een gebruiker aan te maken.
-De gebruiker die u hier opgeeft moet al bestaan.',
- 'config-mysql-engine' => 'Opslagmethode:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Waarschuwing''': u hebt MyISAM geselecteerd als opslagengine voor MySQL. Dit is niet aan te raden voor MediaWiki omdat:
-* het nauwelijks ondersteuning biedt voor gebruik door meerdere gebruikers tegelijkertijd door het locken van tabellen;
-* het meer vatbaar is voor corruptie dan andere engines;
-* de code van MediaWiki niet alstijd omgaat met MyISAM zoals dat zou moeten.
-
-Als uw installatie van MySQL InnoDB ondersteunt, gebruik dat dan vooral.
-Als uw installatie van MySQL geen ondersteuning heeft voor InnoDB, denk dan na over upgraden.",
- 'config-mysql-only-myisam-dep' => "'''Waarschuwing:''' MyISAM is enige beschikbare opslagmethode voor MySQL, en deze wordt niet aangeraden voor gebruik met MediaWiki, omdat:
-* er nauwelijks ondersteuning is voor meerdere gelijktijdige transacties omdat tabellen op slot gezet worden;
-* tabellen makkelijker stuk kunnen gaan;
-* de code van MediaWiki niet altijd op de juiste wijze omgaat met MyISAM.
-
-Uw installatie van MySQL heeft geen ondersteuning voor InnoDB. We raden u aan om een meer recente versie te gebruiken.",
- 'config-mysql-engine-help' => "'''InnoDB''' is vrijwel altijd de beste instelling, omdat deze goed omgaat met meerdere verzoeken tegelijkertijd.
-
-'''MyISAM''' is bij een zeer beperkt aantal gebruikers mogelijk sneller, of als de wiki alleen-lezen is.
-MyISAM-databases raken vaker corrupt dan InnoDB-databases.",
- 'config-mysql-charset' => 'Tekenset voor de database:',
- 'config-mysql-binary' => 'Binair',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
-Dit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledige reeks Unicodetekens te gebruiken.
-
-In '''UTF-8-modus''' kent MySQL de tekenset van uw gegevens en kan de databaseserver ze juist weergeven en converteren.
-Het is dat niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
- 'config-site-name' => 'Naam van de wiki:',
- 'config-site-name-help' => 'Deze naam verschijnt in de titelbalk van browsers en op andere plaatsen.',
- 'config-site-name-blank' => 'Geef een naam op voor de site.',
- 'config-project-namespace' => 'Projectnaamruimte:',
- 'config-ns-generic' => 'Project',
- 'config-ns-site-name' => 'Zelfde als de wiki: $1',
- 'config-ns-other' => 'Andere (geef aan welke)',
- 'config-ns-other-default' => 'MijnWiki',
- 'config-project-namespace-help' => "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".
-Alle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat u hier kunt opgeven.
-Dit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
- 'config-ns-invalid' => 'De opgegeven naamruimte "<nowiki>$1</nowiki>" is ongeldig.
-Geef een andere naamruimte op.',
- 'config-ns-conflict' => 'De opgegeven naamruimte "<nowiki>$1</nowiki>" conflicteert met een standaard naamruimte in MediaWiki.
-Geef een andere naam op voor de projectnaamruimte.',
- 'config-admin-box' => 'Beheerdersgebruiker',
- 'config-admin-name' => 'Uw naam:',
- 'config-admin-password' => 'Wachtwoord:',
- 'config-admin-password-confirm' => 'Wachtwoord opnieuw:',
- 'config-admin-help' => 'Voer de gebruikersnaam hier in, bijvoorbeeld "Jan Jansen".
-Dit is de naam die wordt gebruikt om aan de melden bij de wiki.',
- 'config-admin-name-blank' => 'Geef een gebruikersnaam op voor de beheerder.',
- 'config-admin-name-invalid' => 'De opgegeven gebruikersnaam "<nowiki>$1</nowiki>" is ongeldig.
-Kies een andere gebruikersnaam.',
- 'config-admin-password-blank' => 'Voer een wachtwoord voor de beheerder in.',
- 'config-admin-password-same' => 'Het wachtwoord mag niet hetzelfde zijn als de gebruikersnaam.',
- 'config-admin-password-mismatch' => 'De twee door u ingevoerde wachtwoorden komen niet overeen.',
- 'config-admin-email' => 'E-mailadres:',
- 'config-admin-email-help' => "Voer hier een e-mailadres in om e-mail te kunnen ontvangen van andere gebruikers op de wiki, uw wachtwoord opnieuw in te kunnen stellen en op de hoogte te worden gehouden van wijzigingen van pagina's op uw volglijst. U kunt het veld leeg laten.",
- 'config-admin-error-user' => 'Interne fout bij het aanmaken van een beheerder met de naam "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Interne fout bij het instellen van een wachtwoord voor de bejeerder "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'U hebt een ongeldig e-mailadres opgegeven',
- 'config-subscribe' => 'Abonneren op de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailinglijst releaseaankondigen].',
- 'config-subscribe-help' => 'Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.
-Abonneer uzelf erop en werk uw MediaWiki-installatie bij als er nieuwe versies uitkomen.',
- 'config-subscribe-noemail' => 'U hebt geprobeerd zich te abonneren op de mailinglijst voor release-aankondigingen zonder een e-mailadres op te geven.
-Geef een e-mailadres op als u zich wilt abonneren op de mailinglijst.',
- 'config-almost-done' => 'U bent bijna klaar!
-Als u wilt kunt u de overige instellingen overslaan en de wiki nu installeren.',
- 'config-optional-continue' => 'Stel me meer vragen.',
- 'config-optional-skip' => 'Laat dat maar, installeer gewoon de wiki.',
- 'config-profile' => 'Gebruikersrechtenprofiel:',
- 'config-profile-wiki' => 'Open wiki',
- 'config-profile-no-anon' => 'Gebruiker aanmaken verplicht',
- 'config-profile-fishbowl' => 'Alleen voor geautoriseerde bewerkers',
- 'config-profile-private' => 'Privéwiki',
- 'config-profile-help' => "Wiki's werken het beste als ze door zoveel mogelijk gebruikers worden bewerkt.
-In MediaWiki is het eenvoudig om de recente wijzigingen te controleren en eventuele foutieve of kwaadwillende bewerkingen terug te draaien.
-
-Daarnaast vinden velen MediaWiki goed inzetbaar in vele andere rollen, en soms is het niet handig om helemaal \"op de wikimanier\" te werken.
-Daarom biedt dit installatieprogramma u de volgende keuzes voor de basisinstelling van gebruikersvrijheden:
-
-Het profiel '''{{int:config-profile-wiki}}''' staat iedereen toe te bewerken, zonder zelfs aan te melden.
-Een wiki met '''{{int:config-profile-no-anon}}\" biedt extra verantwoordelijkheid, maar kan afschrikken toevallige gebruikers afschrikken.
-
-Het scenario '''{{int:config-profile-fishbowl}}''' laat gebruikers waarvoor dat is ingesteld bewerkt, maar andere gebruikers kunnen alleen pagina's bekijken, inclusief de bewerkingsgeschiedenis.
-In een '''{{int:config-profile-private}}''' kunnen alleen goedgekeurde gebruikers pagina's bekijken en bewerken.
-
-Meer complexe instellingen voor gebruikersrechten zijn te maken na de installatie; hierover is meer te lezen in de [//www.mediawiki.org/wiki/Manual:User_rights handleiding].",
- 'config-license' => 'Auteursrechten en licentie:',
- 'config-license-none' => 'Geen licentie in de voettekst',
- 'config-license-cc-by-sa' => 'Creative Commons Naamsvermelding-Gelijk delen',
- 'config-license-cc-by' => 'Creative Commons Naamsvermelding',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Naamsvermelding-Niet Commercieel-Gelijk delen',
- 'config-license-cc-0' => 'Creative Commons Zero (Publiek domein)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 of hoger',
- 'config-license-pd' => 'Publiek domein',
- 'config-license-cc-choose' => 'Een Creative Commons-licentie selecteren',
- 'config-license-help' => "In veel openbare wiki's zijn alle bijdragen beschikbaar onder een [http://freedomdefined.org/Definition vrije licentie].
-Dit helpt bij het creëren van een gevoel van gemeenschappelijk eigendom en stimuleert bijdragen op lange termijn.
-Dit is over het algemeen niet nodig is voor een particuliere of zakelijke wiki.
-
-Als 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 '''Creative Commons Naamsvermelding-Gelijk delen'''.
-
-De GNU Free Documentation License is de oude licentie voor inhoud uit Wikipedia.
-Dit is nog steeds een geldige licentie, maar deze licentie is lastig te begrijpen.
-Het 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 [http://www.php.net/manual/en/mail.configuration.php e-mailinstellingen van PHP] correct zijn.
-Als 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',
- 'config-email-usertalk-help' => 'Gebruikers toestaan meldingen te ontvangen bij wijzigingen op de eigen overlegpagina, als dit in de voorkeuren is ingesteld.',
- 'config-email-watchlist' => 'Volglijstmeldingen inschakelen',
- 'config-email-watchlist-help' => "Gebruikers toestaan meldingen te ontvangen bij wijzigingen van pagina's op hun volglijst, als dit in de voorkeuren is ingesteld.",
- 'config-email-auth' => 'E-mailbevestiging inschakelen',
- 'config-email-auth-help' => "Als deze instelling actief is, moeten gebruikers hun e-mailadres bevestigen via een verwijziging die ze per e-mail wordt toegezonden.
-Alleen bevestigde e-mailadressen kunnen e-mail ontvangen van andere gebruikers of wijzigingsnotificaties ontvangen.
-Het inschakelen van deze instelling wordt '''aangeraden''' voor openbare wiki's vanwege de mogelijkheden voor misbruik van e-mailmogelijkheden.",
- 'config-email-sender' => 'E-mailadres voor antwoorden:',
- 'config-email-sender-help' => 'Voer het e-mailadres in dat u wilt gebruiken als antwoordadres voor uitgaande e-mail.
-Als een e-mail niet bezorgd kan worden, wordt dat op dit e-mailadres gemeld.
-Veel mailservers vereisen dat tenminste het domein bestaat.',
- 'config-upload-settings' => 'Afbeeldingen en bestanden uploaden',
- 'config-upload-enable' => 'Uploaden van bestanden inschakelen',
- 'config-upload-help' => "Het toestaan van het uploaden van bestanden stelt uw server mogelijk bloot aan beveiligingsrisico's.
-Er is meer [//www.mediawiki.org/wiki/Manual:Security informatie over beveiliging] beschikbaar in de handleiding.
-
-Om het bestandsuploads mogelijk te maken kunt u de rechten op de submap <code>images</code> onder de hoofdmap van MediaWiki aanpassen, zodat de webserver erin kan schrijven.
-Daarmee wordt deze functie ingeschakeld.",
- 'config-upload-deleted' => 'Map voor verwijderde bestanden:',
- 'config-upload-deleted-help' => 'Kies een map waarin verwijderde bestanden gearchiveerd kunnen worden.
-Idealiter is deze map niet via het web te benaderen.',
- 'config-logo' => 'URL voor logo:',
- 'config-logo-help' => 'Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x160 pixels boven het menu.
-Upload een afbeelding met de juiste afmetingen en voer de URL hier in.
-
-U kunt <code>$wgStylePath</code> of <code>$wgScriptPath</code> gebruiken als uw logo relatief is aan een van deze paden.
-
-Als u geen logo wilt gebruiken, kunt u dit veld leeg laten.',
- 'config-instantcommons' => 'Instant Commons inschakelen',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] is functie die het mogelijk maakt om afbeeldingen, geluidsbestanden en andere mediabestanden te gebruiken van de website [//commons.wikimedia.org/ Wikimedia Commons].
-Hiervoor heeft MediaWiki toegang nodig tot Internet.
-
-Meer informatie over deze functie en hoe deze in te stellen voor andere wiki\'s dan Wikimedia Commons is te vinden in de [//mediawiki.org/wiki/Manual:$wgForeignFileRepos handleiding].',
- 'config-cc-error' => 'De licentiekiezer van Creative Commons heeft geen resultaat opgeleverd.
-Voer de licentie handmatig in.',
- 'config-cc-again' => 'Opnieuw kiezen...',
- 'config-cc-not-chosen' => 'Kies de Creative Commonslicentie die u wilt gebruiken en klik op "doorgaan".',
- 'config-advanced-settings' => 'Gevorderde instellingen',
- 'config-cache-options' => 'Instellingen voor het cachen van objecten:',
- 'config-cache-help' => 'Het cachen van objecten wordt gebruikt om de snelheid van MediaWiki te verbeteren door vaak gebruikte gegevens te bewaren.
-Middelgrote tot grote websites wordt geadviseerd dit in te schakelen en ook kleine sites merken de voordelen.',
- 'config-cache-none' => 'Niets cachen.
-Er gaat geen functionaliteit verloren, maar dit kan invloed hebben op de prestaties.',
- 'config-cache-accel' => 'Cachen van objecten via PHP (APC, XCache of WinCache)',
- 'config-cache-memcached' => 'Memcached gebruiken (dit vereist aanvullende instellingen)',
- 'config-memcached-servers' => 'Memcachedservers:',
- 'config-memcached-help' => 'Lijst met IP-adressen te gebruiken voor Memcached.
-Eén IP-adres per regel met een poortnummer.
-Bijvoorbeeld:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'U hebt Memcached geselecteerd als uw cache, maar u hebt geen servers opgegeven.',
- 'config-memcache-badip' => 'U hebt een ongeldig IP-adres ingevoerd voor Memcached: $1.',
- 'config-memcache-noport' => 'U hebt geen poort opgegeven voor de Memcachedserver: $1.
-De standaardpoort is 11211.',
- 'config-memcache-badport' => 'Poortnummers voor Memcached moeten tussen $1 en $2 liggen.',
- 'config-extensions' => 'Uitbreidingen',
- 'config-extensions-help' => 'De bovenstaande uitbreidingen zijn aangetroffen in de map <code>./extensions</code>.
-
-Mogelijk moet u aanvullende instellingen maken, maar u kunt deze uitbreidingen nu inschakelen.',
- 'config-install-alreadydone' => "'''Waarschuwing:''' het lijkt alsof u MediaWiki al hebt geïnstalleerd en probeert het programma opnieuw te installeren.
-Ga door naar de volgende pagina.",
- 'config-install-begin' => 'Als u nu op "{{int:config-continue}}" klikt, begint de installatie van MediaWiki.
-Als u nog wijzigingen wilt maken, klik dan op "{{int:config-back}}".',
- 'config-install-step-done' => 'afgerond',
- 'config-install-step-failed' => 'mislukt',
- 'config-install-extensions' => 'Inclusief uitbreidingen',
- 'config-install-database' => 'Database inrichten',
- 'config-install-schema' => 'Het schema wordt aangemaakt',
- 'config-install-pg-schema-not-exist' => 'Het schema voor PostgreSQL bestaat niet',
- 'config-install-pg-schema-failed' => 'Het aanmaken van de tabellen is mislukt.
-Zorg dat de gebruiker "$1" in het schema "$2" mag schrijven.',
- 'config-install-pg-commit' => 'Wijzigingen worden doorgevoerd',
- 'config-install-pg-plpgsql' => 'Controle op de taal PL/pgSQL',
- 'config-pg-no-plpgsql' => 'U moet de taal PL/pgSQL installeren in de database $1',
- 'config-pg-no-create-privs' => 'De gebruiker die u hebt opgegeven door de installatie heeft niet voldoende rechten om een gebruiker aan te maken.',
- 'config-pg-not-in-role' => 'De gebruiker die u hebt opgegeven voor de webgebruiker bestaat al.
-De gebruiker die u hebt opgegeven voor installatie is geen superuser en geen lid van de rol van de webgebruiker, en kan het dus geen objecten aanmaken die van de webgebruiker zijn.
-
-MediaWiki vereist momenteel dat de tabellen van de webgebruiker zijn. Geef een andere webgebruikersnaam op, of klik op "terug" en geef een gebruiker op die voldoende installatierechten heeft.',
- 'config-install-user' => 'Databasegebruiker aan het aanmaken',
- 'config-install-user-alreadyexists' => 'Gebruiker "$1" bestaat al',
- 'config-install-user-create-failed' => 'Het aanmaken van de gebruiker "$1" is mislukt: $2',
- 'config-install-user-grant-failed' => 'Het geven van rechten aan gebruiker "$1" is mislukt: $2',
- 'config-install-user-missing' => 'De opgegeven gebruiker "$1" bestaat niet.',
- 'config-install-user-missing-create' => 'De opgegeven gebruiker "$1" bestaat niet.
-Klik op "registreren" onderaan als u de gebruiker wilt aanmaken.',
- 'config-install-tables' => 'Tabellen aanmaken',
- 'config-install-tables-exist' => "'''Waarschuwing''': de MediaWikitabellen lijken al te bestaan.
-Het aanmaken wordt overgeslagen.",
- 'config-install-tables-failed' => "'''Fout''': het aanmaken van een tabel is mislukt met de volgende foutmelding: $1",
- 'config-install-interwiki' => 'Bezig met het vullen van de interwikitabel',
- 'config-install-interwiki-list' => 'Het bestand <code>interwiki.list</code> is niet aangetroffen',
- 'config-install-interwiki-exists' => "'''Waarschuwing''': de interwikitabel heeft al inhoud.
-De standaardlijst wordt overgeslagen.",
- 'config-install-stats' => 'Statistieken initialiseren',
- 'config-install-keys' => 'Bezig met aanmaken van geheime sleutels',
- 'config-insecure-keys' => "'''Waarschuwing:''' De {{PLURAL:$2|sleutel die is aangemaakt|sleutels die zijn aangemaakt}} ($1) tijdens de installatie {{PLURAL:$2|is|zijn}} niet volledig veilig. Overweeg deze handmatig te wijzigen.",
- 'config-install-sysop' => 'Gebruiker voor beheerder aanmaken',
- 'config-install-subscribe-fail' => 'Het is niet mogelijk te abonneren op mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL is niet geïnstalleerd en <code>allow_url_fopen</code> is niet beschikbaar.',
- 'config-install-mainpage' => 'Hoofdpagina aanmaken met standaard inhoud',
- 'config-install-extension-tables' => 'Tabellen voor ingeschakelde uitbreidingen worden aangemaakt',
- 'config-install-mainpage-failed' => 'Het was niet mogelijk de hoofdpagina in te voegen: $1',
- 'config-install-done' => "'''Gefeliciteerd!'''
-U hebt MediaWiki met geïnstalleerd.
-
-Het installatieprogramma heeft het bestand <code>LocalSettings.php</code> aangemaakt.
-Dit bevat al uw instellingen.
-
-U moet het bestand downloaden en in de hoofdmap van uw wiki-installatie plaatsten; in dezelfde map als index.php.
-De download moet u automatisch zijn aangeboden.
-
-Als de download niet is aangeboden of als u de download hebt geannuleerd, dan kunt u de download opnieuw starten door op de onderstaande koppeling te klikken:
-
-$3
-
-'''Let op''': als u dit niet nu doet, dan het is bestand als u later de installatieprocedure afsluit zonder het bestand te downloaden niet meer beschikbaar.
-
-Na het plaatsen van het bestand met instellingen kunt u '''[$2 uw wiki betreden]'''.",
- 'config-download-localsettings' => '<code>LocalSettings.php</code> downloaden',
- 'config-help' => 'hulp',
- 'config-nofile' => 'Het bestand "$1" is niet gevonden. Is het verwijderd?',
- 'config-extension-link' => 'Weet u dat u [//www.mediawiki.org/wiki/Manual:Extensions uitbreidingen] kunt gebruiken voor uw wiki?
-U kunt [//www.mediawiki.org/wiki/Category:Extensions_by_category uitbreidingen op categorie] bekijken of ga naar de [//www.mediawiki.org/wiki/Extension_Matrix Uitbreidingenmatrix] om de volledige lijst met uitbreidingen te bekijken.',
- 'mainpagetext' => "'''De installatie van MediaWiki is geslaagd.'''",
- 'mainpagedocfooter' => 'Raadpleeg de [//meta.wikimedia.org/wiki/NL_Help:Inhoudsopgave handleiding] voor informatie over het gebruik van de wikisoftware.
-
-== Meer hulp over MediaWiki ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lijst met instellingen]
-* [//www.mediawiki.org/wiki/Manual:FAQ Veelgestelde vragen (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglijst voor aankondigingen van nieuwe versies]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Maak MediaWiki beschikbaar in uw taal]',
-);
-
-/** Nederlands (informeel)‎ (Nederlands (informeel)‎)
- * @author Siebrand
- */
-$messages['nl-informal'] = array(
- 'config-localsettings-badkey' => 'De sleutel die je hebt opgegeven is onjuist',
- 'config-upgrade-key-missing' => 'Er is een bestaande installatie van MediaWiki aangetroffen.
-Plaats de volgende regel onderaan je <code>LocalSettings.php</code> om deze installatie bij te werken:
-
-$1',
- 'config-session-expired' => 'Je sessiegegevens zijn verlopen.
-Sessies zijn ingesteld om een levensduur van $1 te hebben.
-Je kunt deze wijzigen via de instelling <code>session.gc_maxlifetime</code> in php.ini.
-Begin het installatieproces opnieuw.',
- 'config-no-session' => 'Je sessiegegevens zijn verloren gegaan.
-Controleer je php.ini en zorg dat er een juiste map is ingesteld voor <code>session.save_path</code>.',
- 'config-your-language' => 'Jouw taal:',
- 'config-help-restart' => 'Wil je alle opgeslagen gegevens die je hebt ingevoerd wissen en het installatieproces opnieuw starten?',
- 'config-welcome' => '=== Controle omgeving ===
-Er worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.
-Als je hulp nodig hebt bij de installatie, lever deze gegevens dan ook aan.',
- 'config-copyright' => "=== Auteursrechten en voorwaarden ===
-
-$1
-
-Dit programma is vrije software. Je mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar eigen keuze - enige latere versie.
-
-Dit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.
-Zie de GNU General Public License voor meer informatie.
-
-Samen met dit programma hoor je een <doclink href=Copying>exemplaar van de GNU General Public License</doclink> ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [http://www.gnu.org/copyleft/gpl.html lees de licentie online].",
- 'config-env-good' => 'De omgeving is gecontroleerd.
-Je kunt MediaWiki installeren.',
- 'config-env-bad' => 'De omgeving is gecontroleerd.
-Je kunt MediaWiki niet installeren.',
- 'config-unicode-pure-php-warning' => "'''Waarschuwing''': de [http://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicodenormalisatie af te handelen en daarom wordt de langzame PHP-implementatie gebruikt.
-Als je MediaWiki voor een website met veel verkeer installeert, lees je dan in over [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicodenormalisatie].",
- 'config-unicode-update-warning' => "'''Waarschuwing''': de geïnstalleerde versie van de Unicodenormalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].
-Je moet [//www.mediawiki.org/wiki/Unicode_normalization_considerations bijwerken] als Unicode voor jou van belang is.",
- 'config-no-db' => 'Het was niet mogelijk een geschikte databasedriver te vinden voor PHP.
-Je moet een databasedriver installeren voor PHP.
-De volgende databases worden ondersteund: $1.
-
-Als je op een gedeelde omgeving zit, vraag dan aan je hostingprovider een geschikte databasedriver te installeren.
-Als je PHP zelf hebt gecompileerd, wijzig dan je instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.
-Als je PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.',
- 'config-outdated-sqlite' => "''' Waarschuwing:''' je gebruikt SQLite $1. SQLite is niet beschikbaar omdat de minimaal vereiste versie $2 is.",
- 'config-register-globals' => "'''Waarschuwing: de PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
-'''Schakel deze uit als dat mogelijk is.'''
-MediaWiki kan ermee werken, maar je server is dan meer kwetsbaar voor beveiligingslekken.",
- 'config-magic-quotes-runtime' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is actief!'''
-Deze instelling zorgt voor gegevenscorruptie.
-Je kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
- 'config-magic-quotes-sybase' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] is actief!'''
-Deze instelling zorgt voor gegevenscorruptie.
-Je kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
- 'config-mbstring' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is actief!'''
-Deze instelling zorgt voor gegevenscorruptie.
-Je kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
- 'config-ze1' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] is actief!'''
-Deze instelling zorgt voor grote problemen in MediaWiki.
-Je kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
- 'config-xml-bad' => 'De XML-module van PHP ontbreekt.
-MediaWiki heeft de functies van deze module nodig en werkt niet zonder deze module.
-Als je gebruik maakt van Mandrake, installeer dan het package php-xml.',
- 'config-mod-security' => "'''Waarschuwing:''' je webserver heeft de module [http://modsecurity.org/ mod_security] ingeschakeld. Als deze onjuist is ingesteld, kan dit problemen geven in combinatie met MediaWiki of andere software die gebruikers in staat stelt willekeurige inhoud te posten.
-Lees de [http://modsecurity.org/documentation/ documentatie over mod_security] of neem contact op met de helpdesk van je provider als je tegen problemen aanloopt.",
- 'config-imagemagick' => 'ImageMagick aangetroffen: <code>$1</code>.
-Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als je uploaden inschakelt.',
- 'config-gd' => 'Ingebouwde GD grafische bibliotheek aangetroffen.
-Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als je uploaden inschakelt.',
- 'config-uploads-not-safe' => "'''Waarschuwing:''' je uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.
-Hoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [//www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
- 'config-no-cli-uploads-check' => "''Waarschuwing:'' je standaardmap voor uploads (<code>$1</code>) wordt niet gecontroleerd op kwetsbaarheden voor het uitvoeren van willekeurige scripts gedurende de CLI-installatie.",
- 'config-brokenlibxml' => 'Je systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.
-Upgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger([//bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).
-De installatie wordt afgebroken.',
- 'config-db-host-help' => 'Als je databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.
-
-Als je gebruik maakt van gedeelde webhosting, hoort je provider je de juiste hostnaam te hebben verstrekt.
-
-Als je MediaWiki op een Windowsserver installeert en MySQL gebruikt, dan werkt "localhost" mogelijk niet als servernaam.
-Als het inderdaad niet werkt, probeer dan "127.0.0.1" te gebruiken als lokaal IP-adres.
-
-Als je PostgreSQL gebruikt, laat dit veld dan leeg om via een Unix-socket te verbinden.',
- 'config-db-host-oracle-help' => 'Voer een geldige [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] in; een tnsnames.ora-bestand moet zichtbaar zijn voor deze installatie.<br />Als je gebruik maakt van clientlibraries 10g of een latere versie, kan je ook gebruik maken van de naamgevingsmethode [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-name-help' => 'Kies een naam die je wiki identificeert.
-Er mogen geen spaties gebruikt worden.
-Als je gebruik maakt van gedeelde webhosting, dan hoort je provider ofwel jou een te gebruiken databasenaam gegeven te hebben, of je aangegeven te hebben hoe je databases kunt aanmaken.',
- 'config-db-account-oracle-warn' => 'Er zijn drie ondersteunde scenario\'s voor het installeren van Oracle als databasebackend:
-
-Als je een databasegebruiker wilt aanmaken als onderdeel van het installatieproces, geef dan de gegevens op van een databasegebruiker in met de rol SYSDBA voor de installatie en voer de gewenste aanmeldgegevens in voor de gebruiker met webtoegang. Je kunt ook de gebruiker met webtoegang handmatig aanmaken en alleen van die gebruiker de aanmeldgegevens opgeven als deze de vereiste rechten heeft om schemaobjecten aan te maken. Als laatste is het mogelijk om aanmeldgegevens van twee verschillende gebruikers op te geven; een met de rechten om schemaobjecten aan te maken, en een met alleen webtoegang.
-
-Een script voor het aanmaken van een gebruiker met de vereiste rechten is te vinden in de map "maintenance/oracle/" van deze installatie. Onthoud dat het gebruiken van een gebruiker met beperkte rechten alle mogelijkheden om beheerscripts uit te voeren met de standaard gebruiker onmogelijk maakt.',
- 'config-db-prefix-help' => "Als je een database moet gebruiken voor meerdere wiki's, of voor MediaWiki en een andere applicatie, dan kan je ervoor kiezen om een voorvoegsel toe te voegen aan de tabelnamen om conflicten te voorkomen.
-Gebruik geen spaties.
-
-Dit veld wordt meestal leeg gelaten.",
- 'config-charset-help' => "'''Waarschuwing:''' als je '''achterwaarts compatibel met UTF-8''' gebruikt met MySQL 4.1+ en een back-up van de database maakt met <code>mysqldump</code>, dan kunnen alle niet-ASCII-tekens in je back-ups onherstelbaar beschadigd raken.
-
-In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
-Dit is efficiënter dan de UTF-8-modus van MySQL en stelt je in staat de volledige reeks Unicode-tekens te gebruiken.
-In '''UTF-8-modus''' kent MySQL de tekenset van je gegevens en kan de databaseserver ze juist weergeven en converteren.
-Het is dan niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
- 'config-mysql-old' => 'Je moet MySQL $1 of later gebruiken.
-Jij gebruikt $2.',
- 'config-db-schema-help' => 'Dit schema klopt meestal.
-Wijzig het alleen als je weet dat dit nodig is.',
- 'config-sqlite-dir-help' => "SQLite slaat alle gegevens op in een enkel bestand.
-
-De map die je opgeeft moet beschrijfbaar zijn voor de webserver tijdens de installatie.
-
-Deze mag '''niet toegankelijk''' zijn via het web en het bestand mag dus niet tussen de PHP-bestanden staan.
-
-Het installatieprogramma schrijft het bestand <code>.htaccess</code> weg met het databasebestand, maar als dat niet werkt kan iemand zich toegang tot het ruwe databasebestand verschaffen.
-Ook de gebruikersgegevens (e-mailadressen, wachtwoordhashes) en verwijderde versies en overige gegevens met beperkte toegang via MediaWiki zijn dan onbeschermd.
-
-Overweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld in <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-support-info' => 'MediaWiki ondersteunt de volgende databasesystemen:
-
-$1
-
-Als je het databasesysteem dat je wilt gebruiken niet in de lijst terugvindt, volg dan de handleiding waarnaar hierboven wordt verwezen om ondersteuning toe te voegen.',
- 'config-missing-db-name' => 'Je moet een waarde opgeven voor "Databasenaam"',
- 'config-missing-db-host' => 'Je moet een waarde invoeren voor "Databaseserver"',
- 'config-missing-db-server-oracle' => 'Je moet een waarde opgeven voor "Database-TNS"',
- 'config-postgres-old' => 'PostgreSQL $1 of hoger is vereist.
-Jij gebruikt $2.',
- 'config-sqlite-name-help' => 'Kies een naam die je wiki identificeert.
-Gebruik geen spaties of koppeltekens.
-Deze naam wordt gebruikt voor het gegevensbestand van SQLite.',
- 'config-upgrade-done' => "Het bijwerken is afgerond.
-
-Je kunt [$1 je wiki nu gebruiken].
-
-Als je je <code>LocalSettings.php</code> opnieuw wilt aanmaken, klik dan op de knop hieronder.
-Dit is '''niet aan te raden''' tenzij je problemen hebt met je wiki.",
- 'config-upgrade-done-no-regenerate' => 'Het bijwerken is afgerond.
-
-Je kunt nu [$1 je wiki gebruiken].',
- 'config-db-web-no-create-privs' => 'De gebruiker die je hebt opgegeven voor de installatie heeft niet voldoende rechten om een gebruiker aan te maken.
-De gebruiker die je hier opgeeft moet al bestaan.',
- 'config-mysql-myisam-dep' => "'''Waarschuwing''': je hebt MyISAM geselecteerd als opslagengine voor MySQL. Dit is niet aan te raden voor MediaWiki omdat:
-* het nauwelijks ondersteuning biedt voor gebruik door meerdere gebruikers tegelijkertijd door het locken van tabellen;
-* het meer vatbaar is voor corruptie dan andere engines;
-* de code van MediaWiki niet alstijd omgaat met MyISAM zoals dat zou moeten.
-
-Als je installatie van MySQL InnoDB ondersteunt, gebruik dat dan vooral.
-Als je installatie van MySQL geen ondersteuning heeft voor InnoDB, denk dan na over upgraden.",
- 'config-mysql-charset-help' => "In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
-Dit is efficiënter dan de UTF-8-modus van MySQL en stelt je in staat de volledige reeks Unicodetekens te gebruiken.
-
-In '''UTF-8-modus''' kent MySQL de tekenset van je gegevens en kan de databaseserver ze juist weergeven en converteren.
-Het is dat niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
- 'config-project-namespace-help' => "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".
-Alle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat je hier kunt opgeven.
-Dit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
- 'config-admin-name' => 'Je naam:',
- 'config-admin-password-mismatch' => 'De twee door jou ingevoerde wachtwoorden komen niet overeen.',
- 'config-admin-email-help' => "Voer hier een e-mailadres in om e-mail te kunnen ontvangen van andere gebruikers op de wiki, je wachtwoord opnieuw in te kunnen stellen en op de hoogte te worden gehouden van wijzigingen van pagina's op uw volglijst. Je kunt het veld leeg laten.",
- 'config-admin-error-bademail' => 'Je hebt een ongeldig e-mailadres opgegeven',
- 'config-subscribe-help' => 'Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.
-Abonneer jezelf erop en werk je MediaWiki-installatie bij als er nieuwe versies uitkomen.',
- 'config-subscribe-noemail' => 'Je hebt geprobeerd je te abonneren op de mailinglijst voor release-aankondigingen zonder een e-mailadres op te geven.
-Geef een e-mailadres op als je je wilt abonneren op de mailinglijst.',
- 'config-almost-done' => 'Je bent bijna klaar!
-Als je wilt kan je de overige instellingen overslaan en de wiki nu installeren.',
- 'config-profile-help' => "Wiki's werken het beste als ze door zoveel mogelijk gebruikers worden bewerkt.
-In MediaWiki is het eenvoudig om de recente wijzigingen te controleren en eventuele foutieve of kwaadwillende bewerkingen terug te draaien.
-
-Daarnaast vinden velen MediaWiki goed inzetbaar in vele andere rollen, en soms is het niet handig om helemaal \"op de wikimanier\" te werken.
-Daarom biedt dit installatieprogramma je de volgende keuzes voor de basisinstelling van gebruikersvrijheden:
-
-Een '''{{int:config-profile-wiki}}''' staat iedereen toe te bewerken, zonder zelfs aan te melden.
-Een wiki met '''{{int:config-profile-no-anon}}\" biedt extra verantwoordelijkheid, maar kan afschrikken toevallige gebruikers afschrikken.
-
-Het scenario '''{{int:config-profile-fishbowl}}''' laat gebruikers waarvoor dat is ingesteld bewerkt, maar andere gebruikers kunnen alleen pagina's bekijken, inclusief de bewerkingsgeschiedenis.
-In een '''{{int:config-profile-private}}''' kunnen alleen goedgekeurde gebruikers pagina's bekijken en bewerken.
-
-Meer complexe instellingen voor gebruikersrechten zijn te maken na de installatie; hierover is meer te lezen in de [//www.mediawiki.org/wiki/Manual:User_rights handleiding].", # Fuzzy
- 'config-license-help' => "In veel openbare wiki's zijn alle bijdragen beschikbaar onder een [http://freedomdefined.org/Definition vrije licentie].
-Dit helpt bij het creëren van een gevoel van gemeenschappelijk eigendom en stimuleert bijdragen op lange termijn.
-Dit is over het algemeen niet nodig is voor een particuliere of zakelijke wiki.
-
-Als je teksten uit Wikipedia wilt kunnen gebruiken en je wilt het mogelijk maken teksten uit je wiki naar Wikipedia te kopiëren, kies dan de licentie '''Creative Commons Naamsvermelding-Gelijk delen'''.
-
-De GNU Free Documentation License is de oude licentie voor inhoud uit Wikipedia.
-Dit is nog steeds een geldige licentie, maar deze licentie is lastig te begrijpen.
-Het is ook lastig inhoud te hergebruiken onder de GFDL.",
- 'config-enable-email-help' => 'Als je wilt dat e-mailen mogelijk is, dan moeten de [http://www.php.net/manual/en/mail.configuration.php e-mailinstellingen van PHP] correct zijn.
-Als je niet wilt dat e-mailen mogelijk is, dan kan je de instellingen hier uitschakelen.',
- 'config-upload-help' => "Het toestaan van het uploaden van bestanden stelt je server mogelijk bloot aan beveiligingsrisico's.
-Er is meer [//www.mediawiki.org/wiki/Manual:Security informatie over beveiliging] beschikbaar in de handleiding.
-
-Om het bestandsuploads mogelijk te maken kan je de rechten op de submap <code>images</code> onder de hoofdmap van MediaWiki aanpassen, zodat de webserver erin kan schrijven.
-Daarmee wordt deze functie ingeschakeld.",
- 'config-logo-help' => 'Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x160 pixels boven het menu.
-Upload een afbeelding met de juiste afmetingen en voer de URL hier in.
-
-Als je geen logo wilt gebruiken, kan je dit veld leeg laten.', # Fuzzy
- 'config-cc-not-chosen' => 'Kies de Creative Commonslicentie die je wilt gebruiken en klik op "doorgaan".',
- 'config-memcache-needservers' => 'Je hebt Memcached geselecteerd als je cache, maar je hebt geen servers opgegeven.',
- 'config-memcache-badip' => 'Je hebt een ongeldig IP-adres ingevoerd voor Memcached: $1.',
- 'config-memcache-noport' => 'Je hebt geen poort opgegeven voor de Memcachedserver: $1.
-De standaardpoort is 11211.',
- 'config-extensions-help' => 'De bovenstaande uitbreidingen zijn aangetroffen in de map <code>./extensions</code>.
-
-Mogelijk moet je aanvullende instellingen maken, maar je kunt deze uitbreidingen nu inschakelen.',
- 'config-install-alreadydone' => "'''Waarschuwing:''' het lijkt alsof je MediaWiki al hebt geïnstalleerd en probeert het programma opnieuw te installeren.
-Ga door naar de volgende pagina.",
- 'config-install-begin' => 'Als je nu op "{{int:config-continue}}" klikt, begint de installatie van MediaWiki.
-Als je nog wijzigingen wilt maken, klik dan op "Terug".', # Fuzzy
- 'config-pg-no-plpgsql' => 'Je moet de taal PL/pgSQL installeren in de database $1',
- 'config-pg-no-create-privs' => 'De gebruiker die je hebt opgegeven door de installatie heeft niet voldoende rechten om een gebruiker aan te maken.',
- 'config-pg-not-in-role' => 'De gebruiker die je hebt opgegeven voor de webgebruiker bestaat al.
-De gebruiker die je hebt opgegeven voor installatie is geen superuser en geen lid van de rol van de webgebruiker, en kan het dus geen objecten aanmaken die van de webgebruiker zijn.
-
-MediaWiki vereist momenteel dat de tabellen van de webgebruiker zijn. Geef een andere webgebruikersnaam op, of klik op "terug" en geef een gebruiker op die voldoende installatierechten heeft.',
- 'config-install-user-missing-create' => 'De opgegeven gebruiker "$1" bestaat niet.
-Klik op "registreren" onderaan als je de gebruiker wilt aanmaken.',
- 'config-install-done' => "'''Gefeliciteerd!'''
-Je hebt MediaWiki met geïnstalleerd.
-
-Het installatieprogramma heeft het bestand <code>LocalSettings.php</code> aangemaakt.
-Dit bevat al je instellingen.
-
-Je moet het bestand downloaden en in de hoofdmap van uw wikiinstallatie plaatsten; in dezelfde map als index.php.
-De download moet je automatisch zijn aangeboden.
-
-Als de download niet is aangeboden of als je de download hebt geannuleerd, dan kan je de download opnieuw starten door op de onderstaande koppeling te klikken:
-
-$3
-
-'''Let op''': als je dit niet nu doet, dan het is bestand als u later de installatieprocedure afsluit zonder het bestand te downloaden niet meer beschikbaar.
-
-Na het plaatsen van het bestand met instellingen kan je '''[$2 je wiki betreden]'''.",
- 'mainpagedocfooter' => 'Raadpleeg de [//meta.wikimedia.org/wiki/NL_Help:Inhoudsopgave handleiding] voor informatie over het gebruik van de wikisoftware.
-
-== Meer hulp over MediaWiki ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lijst met instellingen]
-* [//www.mediawiki.org/wiki/Manual:FAQ Veelgestelde vragen (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglijst voor aankondigingen van nieuwe versies]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Maak MediaWiki beschikbaar in jouw taal]',
-);
-
-/** Norwegian Nynorsk (norsk nynorsk)
- * @author Harald Khan
- * @author Nghtwlkr
- */
-$messages['nn'] = array(
- 'config-your-language' => 'Språket ditt:',
- 'config-wiki-language' => 'Wikispråk:',
- 'config-back' => '← Attende',
- 'config-continue' => 'Hald fram →',
- 'config-page-language' => 'Språk',
- 'config-memory-raised' => 'PHPs <code>memory_limit</code> er $1, auka til $2.',
- 'config-memory-bad' => "'''Advarsel:''' PHPs <code>memory_limit</code> er $1.
-Dette er sannsynlegvis for lågt.
-Installasjonen kan mislukkast!",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] er innstallert',
- 'config-apc' => '[http://www.php.net/apc APC] er innstallert',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] er installert',
- 'config-db-name' => 'Databasenamn:',
- 'config-db-username' => 'Databasebrukarnamn:',
- 'config-db-password' => 'Databasepassord:',
- 'config-db-charset' => 'Databaseteiknsett',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binær',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 bakoverkompatibel UTF-8',
- 'config-mysql-old' => 'MySQL $1 eller seinare krevst, du har $2.',
- 'config-db-port' => 'Databaseport:',
- 'config-db-schema' => 'Skjema for MediaWiki',
- 'config-header-mysql' => 'MySQL-innstillingar',
- 'config-header-postgres' => 'PostgreSQL-innstillingar',
- 'config-header-sqlite' => 'SQLite-innstillingar',
- 'config-header-oracle' => 'Oracle-innstillingar',
- 'config-invalid-db-type' => 'Ugyldig databasetype',
- 'config-invalid-db-name' => 'Ugyldig databasenamn «$1».
-Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).', # Fuzzy
- 'config-invalid-db-prefix' => 'Ugyldig databaseprefiks «$1».
-Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).', # Fuzzy
- 'config-invalid-schema' => 'Ugyldig skjema for MediaWiki «$1».
-Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).',
- 'config-postgres-old' => 'PostgreSQL $1 eller seinare krevst, du har $2.',
- 'config-email-settings' => 'E-postinnstillingar',
- 'config-logo' => 'Logo-URL:',
- 'mainpagetext' => "'''MediaWiki er no installert.'''",
- 'mainpagedocfooter' => 'Sjå [//meta.wikimedia.org/wiki/Help:Contents brukarmanualen] for informasjon om bruk og oppsettshjelp for wikiprogramvara.
-
-==Kome i gang==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste over oppsettsinnstillingar]
-* [//www.mediawiki.org/wiki/Manual:FAQ Spørsmål og svar om MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postliste med informasjon om nye MediaWiki-versjonar]', # Fuzzy
-);
-
-/** Norwegian (bokmål)‬ (‪norsk (bokmål)‬) */
-$messages['no'] = array(
- 'mainpagetext' => "'''MediaWiki-programvaren er nå installert.'''",
-);
-
-/** Occitan (occitan)
- * @author Cedric31
- */
-$messages['oc'] = array(
- 'config-desc' => 'Lo programa d’installacion de MediaWiki',
- 'config-title' => 'Installacion de MediaWiki $1',
- 'config-information' => 'Informacions',
- 'config-localsettings-key' => 'Clau de mesa a jorn :',
- 'config-localsettings-badkey' => "La clau qu'avètz provesida es incorrècta",
- 'config-session-error' => "Error al moment de l'aviada de la sesilha : $1",
- 'config-your-language' => 'Vòstra lenga :',
- 'config-wiki-language' => 'Lenga del wiki :',
- 'config-back' => '← Retorn',
- 'config-continue' => 'Contunhar →',
- 'config-page-language' => 'Lenga',
- 'config-page-welcome' => 'Benvenguda sus MediaWiki !',
- 'config-page-dbconnect' => 'Se connectar a la banca de donadas',
- 'config-page-dbsettings' => 'Paramètres de la banca de donadas',
- 'config-page-name' => 'Nom',
- 'config-page-options' => 'Opcions',
- 'config-page-install' => 'Installar',
- 'config-page-complete' => 'Acabat !',
- 'config-page-restart' => 'Reaviar l’installacion',
- 'config-page-readme' => 'Legissètz-me',
- 'config-page-releasenotes' => 'Nòtas de version',
- 'config-page-copying' => 'Còpia',
- 'config-page-upgradedoc' => 'Mesa a jorn',
- 'config-page-existingwiki' => 'Wiki existent',
- 'config-restart' => 'Òc, lo reaviar',
- 'config-env-php' => 'PHP $1 es installat.',
- 'config-db-host-oracle' => 'Nom TNS de la banca de donadas :',
- 'config-db-wiki-settings' => 'Identificar aqueste wiki',
- 'config-db-name' => 'Nom de la banca de donadas :',
- 'config-db-name-oracle' => 'Esquèma de banca de donadas :',
- 'config-db-install-account' => "Compte d'utilizaire per l'installacion",
- 'config-db-username' => "Nom d'utilizaire de la banca de donadas :",
- 'config-db-password' => 'Senhal de la banca de donadas :',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binari',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 retrocompatible UTF-8',
- 'config-db-port' => 'Pòrt de la banca de donadas :',
- 'config-db-schema' => 'Esquèma per MediaWiki',
- 'config-header-mysql' => 'Paramètres de MySQL',
- 'config-header-postgres' => 'Paramètres de PostgreSQL',
- 'config-header-sqlite' => 'Paramètres de SQLite',
- 'config-header-oracle' => 'Paramètres d’Oracle',
- 'config-mysql-engine' => "Motor d'emmagazinatge :",
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-binary' => 'Binari',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Nom del wiki :',
- 'config-ns-generic' => 'Projècte',
- 'config-ns-other-default' => 'MonWiki',
- 'config-admin-name' => 'Vòstre nom :',
- 'config-admin-password' => 'Senhal :',
- 'mainpagetext' => "'''MediaWiki es estat installat amb succès.'''",
- 'mainpagedocfooter' => "Consultatz lo [//meta.wikimedia.org/wiki/Ajuda:Contengut Guida de l'utilizaire] per mai d'entresenhas sus l'utilizacion d'aqueste logicial.
-
-== Començar amb MediaWiki ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dels paramètres de configuracion]
-* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussions de las parucions de MediaWiki]", # Fuzzy
-);
-
-/** Oriya (ଓଡ଼ିଆ)
- * @author Jnanaranjan Sahu
- */
-$messages['or'] = array(
- 'config-back' => '← ପଛକୁ',
- 'config-continue' => 'ଚାଲୁରଖିବେ →',
- 'config-page-language' => 'ଭାଷା',
- 'config-page-welcome' => 'ମେଡିଆଉଇକିକୁ ଆପଣଙ୍କୁ ସ୍ଵାଗତ',
- 'config-page-name' => 'ନାମ',
- 'config-page-options' => 'ପସନ୍ଦସମୂହ',
-);
-
-/** Ossetic (Ирон)
- * @author Amikeco
- */
-$messages['os'] = array(
- 'config-page-language' => 'Æвзаг',
- 'mainpagetext' => "'''Вики-скрипт «MediaWiki» æнтыстджынæй æвæрд æрцыд.'''",
-);
-
-/** Punjabi (ਪੰਜਾਬੀ)
- */
-$messages['pa'] = array(
- 'mainpagetext' => "'''ਮੀਡਿਆਵਿਕਿ ਠੀਕ ਤਰ੍ਹਾਂ ਇੰਸਟਾਲ ਹੋ ਗਿਆ ਹੈ।'''",
-);
-
-/** Pampanga (Kapampangan)
- */
-$messages['pam'] = array(
- 'mainpagetext' => "'''Melaus ing pamipalyari ning MediaWiki.'''",
- 'mainpagedocfooter' => "Basan me ing [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para king impormasiun keng pamangamit ning wiki software.
-
-== Pamagumpisa ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Picard (Picard)
- */
-$messages['pcd'] = array(
- 'mainpagetext' => "'''MediaWiki o té instalé aveuc victoère.'''",
-);
-
-/** Deitsch (Deitsch)
- * @author Xqt
- */
-$messages['pdc'] = array(
- 'config-continue' => 'Weider →',
- 'config-page-language' => 'Schprooch',
- 'config-admin-password' => 'Paesswatt:',
- 'config-install-step-done' => 'geduh',
- 'config-help' => 'Hilf',
- 'mainpagedocfooter' => "Hilf fer's Yuuse unn Konfiguriere vun de Wiki-Software kansch finne im [//meta.wikimedia.org/wiki/Help:Contents Handbuch fer Yuuser].
-
-== Hilf zum Schtaerte ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lischt vun Gnepp zum Konfiguriere]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Eposchde-Lischt fer neie MediaWiki-Versione]", # Fuzzy
-);
-
-/** Polish (polski)
- * @author Beau
- * @author BeginaFelicysym
- * @author Chrumps
- * @author Holek
- * @author Matma Rex
- * @author Michał Roszka
- * @author Saper
- * @author Sp5uhe
- * @author Woytecr
- * @author 아라
- */
-$messages['pl'] = array(
- 'config-desc' => 'Instalator MediaWiki',
- 'config-title' => 'Instalacja MediaWiki $1',
- 'config-information' => 'Informacja',
- 'config-localsettings-upgrade' => 'Plik <code>LocalSettings.php</code> istnieje.
-Aby oprogramowanie zostało zaktualizowane musisz wstawić wartość <code>$wgUpgradeKey</code> w poniższe pole.
-Odnajdziesz ją w <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Wykryto obecność pliku <code>LocalSettings.php</code>.
-Aktualizację należy wykonać poprzez uruchomienie <code>update.php</code>',
- 'config-localsettings-key' => 'Klucz aktualizacji',
- 'config-localsettings-badkey' => 'Podany klucz jest nieprawidłowy',
- 'config-upgrade-key-missing' => 'Wykryto zainstalowane wcześniej MediaWiki.
-Jeśli chcesz je zaktualizować dodaj na koniec pliku <code>LocalSettings.php</code> poniższą linię tekstu.
-
-$1',
- 'config-localsettings-incomplete' => 'Istniejący plik <code>LocalSettings.php</code> wygląda na niekompletny.
-Brak wartości zmiennej $1.
-Zmień plik <code>LocalSettings.php</code>, tak by zawierał deklarację wartości tej zmiennej, a następnie kliknij „{{int:Config-continue}}”.',
- 'config-localsettings-connection-error' => 'Wystąpił błąd podczas łączenia z bazą danych z wykorzystaniem danych z <code>LocalSettings.php</code> lub <code>AdminSettings.php</code>.
-Popraw ustawienia i spróbuj ponownie.
-
-$1',
- 'config-session-error' => 'Błąd uruchomienia sesji – $1',
- 'config-session-expired' => 'Wygląda na to, że Twoja sesja wygasła.
-Czas życia sesji został skonfigurowany na $1.
-Możesz go wydłużyć zmieniając <code>session.gc_maxlifetime</code> w pliku php.ini.
-Uruchom ponownie proces instalacji.',
- 'config-no-session' => 'Dane sesji zostały utracone.
-Sprawdź plik php.ini i upewnij się, że <code>session.save_path</code> wskazuje na odpowiedni katalog.',
- 'config-your-language' => 'Język',
- 'config-your-language-help' => 'Wybierz język używany podczas procesu instalacji.',
- 'config-wiki-language' => 'Język wiki',
- 'config-wiki-language-help' => 'Wybierz język, w którym będzie tworzona większość treści wiki',
- 'config-back' => '← Wstecz',
- 'config-continue' => 'Dalej →',
- 'config-page-language' => 'Język',
- 'config-page-welcome' => 'Witamy w MediaWiki!',
- 'config-page-dbconnect' => 'Połączenie z bazą danych',
- 'config-page-upgrade' => 'Uaktualnienie istniejącej instalacji',
- 'config-page-dbsettings' => 'Ustawienia bazy danych',
- 'config-page-name' => 'Nazwa',
- 'config-page-options' => 'Opcje',
- 'config-page-install' => 'Instaluj',
- 'config-page-complete' => 'Zakończono!',
- 'config-page-restart' => 'Rozpoczęcie instalacji od nowa',
- 'config-page-readme' => 'Podstawowe informacje',
- 'config-page-releasenotes' => 'Informacje o wersji',
- 'config-page-copying' => 'Kopiowanie',
- 'config-page-upgradedoc' => 'Uaktualnienie',
- 'config-page-existingwiki' => 'Istniejąca wiki',
- 'config-help-restart' => 'Czy chcesz usunąć wszystkie zapisane dane i uruchomić ponownie proces instalacji?',
- 'config-restart' => 'Tak, zacznij od nowa',
- 'config-welcome' => '=== Sprawdzenie środowiska instalacji ===
-Teraz zostaną wykonane podstawowe testy sprawdzające czy to środowisko jest odpowiednie dla instalacji MediaWiki.
-Jeśli potrzebujesz pomocy podczas instalacji, załącz wyniki tych testów.',
- 'config-copyright' => "=== Prawa autorskie i warunki użytkowania ===
-
-$1
-
-To oprogramowanie jest wolne; możesz je rozprowadzać dalej i modyfikować zgodnie z warunkami licencji GNU General Public License opublikowanej przez Free Software Foundation w wersji 2 tej licencji lub (według Twojego wyboru) którejś z późniejszych jej wersji.
-
-Niniejsze oprogramowanie jest rozpowszechniane w nadziei, że będzie użyteczne, ale '''bez żadnej gwarancji'''; nawet bez domniemanej gwarancji '''handlowej''' lub '''przydatności do określonego celu'''.
-Zobacz treść licencji GNU General Public License, aby uzyskać więcej szczegółów.
-
-Razem z oprogramowaniem powinieneś otrzymać <doclink href=Copying>kopię licencji GNU General Public License</doclink>. Jeśli jej nie otrzymałeś, napisz do Free Software Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. lub [http://www.gnu.org/copyleft/gpl.html przeczytaj ją online].",
- 'config-sidebar' => '* [//www.mediawiki.org Strona domowa MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Podręcznik użytkownika]
-* [//www.mediawiki.org/wiki/Manual:Contents Podręcznik administratora]
-* [//www.mediawiki.org/wiki/Manual:FAQ Odpowiedzi na często zadawane pytania]
-----
-* <doclink href=Readme>Przeczytaj to</doclink>
-* <doclink href=ReleaseNotes>Informacje o tej wersji</doclink>
-* <doclink href=Copying>Kopiowanie</doclink>
-* <doclink href=UpgradeDoc>Aktualizacja</doclink>',
- 'config-env-good' => 'Środowisko oprogramowania zostało sprawdzone.
-Możesz teraz zainstalować MediaWiki.',
- 'config-env-bad' => 'Środowisko oprogramowania zostało sprawdzone.
-Nie możesz zainstalować MediaWiki.',
- 'config-env-php' => 'Zainstalowane jest PHP w wersji $1.',
- 'config-env-php-toolow' => 'Zainstalowane jest PHP $1.
-Jednak MediaWiki wymaga PHP $2 lub nowszego.',
- 'config-unicode-using-utf8' => 'Korzystanie z normalizacji Unicode utf8_normalize.so napisanej przez Brion Vibbera.',
- 'config-unicode-using-intl' => 'Korzystanie z [http://pecl.php.net/intl rozszerzenia intl PECL] do normalizacji Unicode.',
- 'config-unicode-pure-php-warning' => "'''Uwaga!''' [http://pecl.php.net/intl Rozszerzenie intl PECL] do obsługi normalizacji Unicode nie jest dostępne. Użyta zostanie mało wydajna zwykła implementacja w PHP.
-Jeśli prowadzisz stronę o dużym natężeniu ruchu, powinieneś zapoznać się z informacjami o [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalizacji Unicode].",
- 'config-unicode-update-warning' => "'''Uwaga''' – zainstalowana wersja normalizacji Unicode korzysta z nieaktualnej biblioteki [http://site.icu-project.org/ projektu ICU].
-Powinieneś [//www.mediawiki.org/wiki/Unicode_normalization_considerations zrobić aktualizację] jeśli chcesz korzystać w pełni z Unicode.",
- 'config-no-db' => 'Nie można odnaleźć właściwego sterownika bazy danych! Musisz zainstalować sterownik bazy danych dla PHP.
-Można użyć następujących typów baz danych: $1.
-
-Jeżeli korzystasz ze współdzielonego hostingu, zwróć się do administratora o zainstalowanie odpowiedniego sterownika bazy danych.
-Jeśli skompilowałeś PHP samodzielnie, skonfiguruj je ponownie z włączonym klientem bazy danych, na przykład za pomocą polecenia <code>./configure --with-mysql</code>.
-Jeśli zainstalowałeś PHP jako pakiet Debiana lub Ubuntu, musisz również zainstalować moduł php5-mysql.',
- 'config-outdated-sqlite' => "'''Ostrzeżenie''': masz SQLite $1, która jest niższa od minimalnej wymaganej wersji $2 . SQLite będzie niedostępne.",
- 'config-no-fts3' => "'''Uwaga''' – SQLite został skompilowany bez [//sqlite.org/fts3.html modułu FTS3] – funkcje wyszukiwania nie będą dostępne.",
- 'config-register-globals' => "'''Uwaga – w konfiguracji PHP włączona jest opcja <code>[http://php.net/register_globals register_globals]</code>.'''
-'''Jeśli możesz, wyłącz ją.'''
-MediaWiki będzie działać, ale Twój serwer może być narażony potencjalnymi lukami w zabezpieczeniach.",
- 'config-magic-quotes-runtime' => "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
-Ta opcja powoduje nieprzewidywalne uszkodzenia wprowadzanych danych.
-Zainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
- 'config-magic-quotes-sybase' => "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''
-Ta opcja powoduje nieprzewidywalne uszkodzenia wprowadzanych danych.
-Zainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
- 'config-mbstring' => "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''
-Ta opcja powoduje błędy i może wywołać nieprzewidywalne uszkodzenia wprowadzanych danych.
-Zainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
- 'config-ze1' => "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]!'''
-Ta opcja powoduje okropne błędy podczas korzystania z MediaWiki.
-Zainstalować lub korzystać z MediaWiki można wyłącznie wtedy, gdy ta opcja jest wyłączona.",
- 'config-safe-mode' => "'''Ostrzeżenie''' – uaktywniono [http://www.php.net/features.safe-mode tryb awaryjny] PHP.
-Opcja ta może powodować problemy, szczególnie w przypadku korzystania z przesyłania plików i używania znacznika <code>math</code>.",
- 'config-xml-bad' => 'Brak modułu XML dla PHP.
-MediaWiki wymaga funkcji z tego modułu i nie może działać w tej konfiguracji.
-Jeśli korzystasz z Mandrake, zainstaluj pakiet php-xml.',
- 'config-pcre' => 'Wygląda na to, że brak modułu PCRE.
-MediaWiki do pracy wymaga funkcji obsługi wyrażeń regularnych kompatybilnej z Perlem.',
- 'config-pcre-no-utf8' => "'''Błąd krytyczny''' – wydaje się, że moduł PCRE w PHP został skompilowany bez wsparcia dla UTF‐8.
-MediaWiki 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.
-To jest prawdopodobnie zbyt mało.
-Instalacja może się nie udać!",
- 'config-ctype' => "''' Krytyczny ''': PHP musi być skompilowany z obsługą [http://www.php.net/manual/en/ctype.installation.php rozszerzenia Ctype].",
- 'config-xcache' => '[Http://trac.lighttpd.net/xcache/ XCache] jest zainstalowany',
- 'config-apc' => '[Http://www.php.net/apc APC] jest zainstalowany',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] jest zainstalowany',
- 'config-no-cache' => "'''Uwaga:''' Pamięć podręczna dla kodu MediaWiki nie będzie uruchomiona., gdyż nie ma żadnego z następujących narzędzi: [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] lub [http://www.iis.net/download/WinCacheForPhp WinCache].",
- 'config-mod-security' => "''' Ostrzeżenie ''': Serwer sieci web ma włączone [http://modsecurity.org/ mod_security]. Jeśli niepoprawnie skonfigurowane, może być przyczyną problemów MediaWiki lub innego oprogramowania, które pozwala użytkownikom na wysyłanie dowolnej zawartości.
-Sprawdź w [http://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 GNU diff3.',
- 'config-git' => 'Znaleziono oprogramowanie kontroli wersji Git: <code>$1</code>.',
- 'config-git-bad' => 'Oprogramowanie systemu kontroli wersji Git nie zostało znalezione.',
- 'config-imagemagick' => 'Mamy zainstalowany ImageMagick <code>$1</code>, dzięki czemu będzie można pomniejszać załadowane grafiki.',
- 'config-gd' => 'Mamy wbudowaną bibliotekę graficzną GD, dzięki czemu będzie można pomniejszać załadowane grafiki.',
- 'config-no-scaling' => 'Nie można odnaleźć biblioteki GD lub ImageMagick. Nie będzie działać pomniejszanie załadowane grafiki.',
- 'config-no-uri' => "'''Błąd.''' Nie można określić aktualnego URI.
-Instalacja została przerwana.",
- 'config-no-cli-uri' => "''' Ostrzeżenie ''': nie wskazano --scriptpath, użycie wartości domyślnej: <code>$1</code> .",
- 'config-using-server' => '„<nowiki>$1</nowiki>“ jest adresem serwera, na którym instalowana jest wiki.',
- 'config-using-uri' => 'Wiki będzie zainstalowana pod adresem "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Uwaga''' – domyślny katalog do którego zapisywane są przesyłane pliki <code>$1</code> jest podatny na wykonanie dowolnego skryptu.
-Chociaż MediaWiki sprawdza wszystkie przesłane pliki pod kątem bezpieczeństwa, zaleca się jednak, aby [//www.mediawiki.org/wiki/Manual:Security#Upload_security zamknąć tę lukę w zabezpieczeniach] przed włączeniem przesyłania plików.",
- 'config-no-cli-uploads-check' => "'''Ostrzeżenie:''' Katalog domyślny przesyłanych plików ( <code>$1</code> ) nie jest sprawdzona względem luki
- wykonania dowolnego skryptu podczas instalacji CLI w zabezpieczeniach.",
- 'config-brokenlibxml' => 'Twój system jest kombinacją wersji PHP i libxml2, które zawierają błędy mogące powodować ukryte uszkodzenia danych w MediaWiki i innych aplikacjach sieci web.
-Wykonaj aktualizację PHP do wersji 5.2.9 lub późniejszej oraz libxml2 do wersji 2.7.3 lub późniejszej ([//bugs.php.net/bug.php?id=45996 błąd w PHP]).
-Instalacja została przerwana.',
- 'config-using531' => 'MediaWiki nie może być używane z PHP $1 z powodu błędu dotyczącego referencyjnych argumentów funkcji <code>__call()</code>.
-Uaktualnij do PHP 5.3.2 lub nowszego. Możesz również cofnąć wersję do PHP 5.3.0, aby naprawić ten błąd.
-Instalacja została przerwana.',
- 'config-suhosin-max-value-length' => 'Jest zainstalowany Suhosin i ogranicza długość parametru GET <code>length</code> do $1 bajtów. Komponent ResourceLoader w MediaWiki wykona obejście tego ograniczenia, ale kosztem wydajności.
-Jeśli to możliwe, należy ustawić <code>suhosin.get.max_value_length</code> na 1024 lub wyżej w <code>php.ini</code> oraz ustawić <code>$wgResourceLoaderMaxQueryLength</code> w <code>LocalSettings.php</code> na tę samą wartość.',
- 'config-db-type' => 'Typ bazy danych',
- 'config-db-host' => 'Adres serwera bazy danych',
- 'config-db-host-help' => 'Jeśli serwer bazy danych jest na innej maszynie, wprowadź jej nazwę domenową lub adres IP.
-
-Jeśli korzystasz ze współdzielonego hostingu, operator serwera powinien podać Ci prawidłową nazwę serwera w swojej dokumentacji.
-
-Jeśli instalujesz oprogramowanie na serwerze Windowsowym i korzystasz z MySQL, użycie „localhost” może nie zadziałać jako nazwa hosta. Jeśli wystąpi ten problem użyj „127.0.0.1” jako lokalnego adresu IP.
-
-Jeżeli korzystasz z PostgreSQL, pozostaw to pole puste, aby połączyć się poprzez gniazdo Unix‐a.',
- 'config-db-host-oracle' => 'Nazwa instancji bazy danych (TNS):',
- 'config-db-host-oracle-help' => 'Wprowadź prawidłową [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nazwę połączenia lokalnego]. Plik „tnsnames.ora” musi być widoczny dla instalatora.<br />Jeśli używasz biblioteki klienckiej 10g lub nowszej możesz również skorzystać z metody nazw [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm łatwego łączenia].',
- 'config-db-wiki-settings' => 'Zidentyfikuj tę wiki',
- 'config-db-name' => 'Nazwa bazy danych',
- 'config-db-name-help' => 'Wybierz nazwę, która zidentyfikuje Twoją wiki.
-Nie może ona zawierać spacji.
-
-Jeśli korzystasz ze współdzielonego hostingu, dostawca usługi hostingowej może wymagać użycia konkretnej nazwy bazy danych lub pozwalać na tworzenie baz danych za pośrednictwem panelu użytkownika.',
- 'config-db-name-oracle' => 'Nazwa schematu bazy danych:',
- 'config-db-account-oracle-warn' => 'Bazę danych Oracle można przygotować do pracy z MediaWiki na trzy sposoby:
-
-Możesz utworzyć konto użytkownika bazy danych podczas instalacji MediaWiki. Wówczas należy podać nazwę i hasło użytkownika z rolą SYSDBA w celu użycia go przez instalator do utworzenia nowe konta użytkownika, z którego korzystać będzie MediaWiki.
-
-Możesz również skorzystać z konta użytkownika bazy danych utworzonego bezpośrednio w Oracle i wówczas wystarczy podać tylko nazwę i hasło tego użytkownika. Konto z rolą SYSDBA nie będzie potrzebne, jednak konto użytkownika powinno mieć uprawnienia do utworzenia obiektów w schemacie bazy danych. Możesz też podać dwa konta - konto dla instalatora, z pomocą którego zostaną obiekty w schemacie bazy danych i drugie konto, z którego będzie MediaWiki korzystać będzie do pracy.
-
-W podkatalogu "maintenance/oracle" znajduje się skrypt do tworzenia konta użytkownika. Korzystanie z konta użytkownika z ograniczonymi uprawnieniami spowoduje wyłączenie funkcji związanych z aktualizacją oprogramowania MediaWiki.',
- 'config-db-install-account' => 'Konto użytkownika dla instalatora',
- 'config-db-username' => 'Nazwa użytkownika bazy danych',
- 'config-db-password' => 'Hasło bazy danych',
- 'config-db-password-empty' => 'Wprowadź hasło dla nowego użytkownika bazy danych: $1.
-Choć istnieje możliwość tworzenia użytkowników bez hasła, nie jest to bezpieczne.',
- 'config-db-install-username' => 'Wprowadź nazwę użytkownika, który będzie używany do łączenia się z bazą danych podczas procesu instalacji.
-Nie jest to nazwa konta MediaWiki, a użytkownika bazy danych.',
- 'config-db-install-password' => 'Wprowadź hasło, które będzie wykorzystywane do łączenia się z bazą danych w procesie instalacji.
-To nie jest hasło konta MediaWiki, lecz hasło do bazy danych.',
- 'config-db-install-help' => 'Podaj nazwę użytkownika i jego hasło, które zostaną użyte do połączenia z bazą danych w czasie procesu instalacji.',
- 'config-db-account-lock' => 'Użyj tej samej nazwy użytkownika i hasła w czasie normalnej pracy.',
- 'config-db-wiki-account' => 'Konto użytkownika do normalnej pracy',
- 'config-db-wiki-help' => 'Wprowadź nazwę użytkownika i hasło, które będą używane do połączenia z bazą danych podczas normalnej pracy wiki.
-Jeśli konto nie istnieje, a konto instalacji ma wystarczające uprawnienia, to zostanie utworzone konto użytkownika z minimalnymi uprawnieniami wymaganymi do działania wiki.',
- 'config-db-prefix' => 'Przedrostek tabel bazy danych',
- 'config-db-prefix-help' => 'Jeśli zachodzi potrzeba współdzielenia jednej bazy danych między wieloma wiki, lub między MediaWiki i inną aplikacją sieciową, można dodać przedrostek do wszystkich nazw tabel w celu uniknięcia konfliktów.
-Nie należy używać spacji.
-
-To pole zwykle pozostawiane jest puste.',
- 'config-db-charset' => 'Zestaw znaków bazy danych',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binarny',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 kompatybilny wstecz UTF-8',
- 'config-charset-help' => "'''Ostrzeżenie:''' W przypadku użycia opcji '''UTF-8 zgodnego ze starszymi wersjami''' podczas korzystania z wersji MySQL nowszych niż 4.1 kopie zapasowe wykonane przy użyciu programu <code>mysqldump</code> będą bezużyteczne - wszystkie znaki inne niż ASCII zostaną zapisane nieprawidłowo.
-
-W '''trybie binarnym''', MediaWiki zapisuje tekst UTF-8 do bazy danych w polach binarnych.
-To jest bardziej wydajne niż tryb UTF-8 w MySQL i pozwala na używanie pełnego zakresu znaków Unicode.
-W ''' trybie UTF-8''', MySQL będzie wiedzieć, w jakim zestawie znaków są dane i umożliwia ich prezentowanie i odpowiednią konwersję, ale nie pozwoli Ci przechowywać znaków spoza [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes podstawowej płaszczyzny wielojęzyczności].",
- 'config-mysql-old' => 'Wymagany jest MySQL $1 lub nowszy; korzystasz z $2.',
- 'config-db-port' => 'Numer portu TCP dla połączeń do bazy danych',
- 'config-db-schema' => 'Nazwa schematu bazy danych, z którego ma korzystać MediaWiki',
- 'config-db-schema-help' => 'Zaproponowana nazwa schematu jest odpowiednia dla większości sytuacji i przeważnie nie trzeba jej zmieniać.',
- 'config-pg-test-error' => "Nie można połączyć się z bazą danych''' $1 ''': $2",
- 'config-sqlite-dir' => 'Katalog danych SQLite',
- 'config-sqlite-dir-help' => "SQLite przechowuje wszystkie dane w pojedynczym pliku.
-
-Wskazany katalog musi być dostępny do zapisu przez webserver podczas instalacji.
-
-Powinien '''nie''' być dostępny za z sieci web, dlatego nie umieszczamy ich tam, gdzie znajdują się pliki PHP.
-
-Instalator zapisze plik <code>.htaccess</code> obokniego, ale jeśli to zawiedzie, ktoś może uzyskać dostęp do nieprzetworzonej bazy danych.
-Zawiera ona nieopracowane dane użytkownika (adresy e-mail, zahaszowane hasła) jak również usunięte wersje oraz inne dane o ograniczonym dostępie na wiki.
-
-Warto rozważyć umieszczenie w bazie danych zupełnie gdzie indziej, na przykład w <code>/var/lib/mediawiki/yourwiki</code> .",
- 'config-oracle-def-ts' => 'Domyślna przestrzeń tabel',
- 'config-oracle-temp-ts' => 'Przestrzeń tabel tymczasowych',
- 'config-support-info' => 'MediaWiki może współpracować z następującymi systemami baz danych:
-
-$1
-
-Poniż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-support-mysql' => '* $1 jest bazą danych, na której rozwijane jest oprogramowanie MediaWiki. Ewentualne błędy w współpracy z bazą danych są odnajdowane i naprawiane dość szybko. ([http://www.php.net/manual/en/mysql.installation.php zobacz, jak skompilować PHP ze wsparciem dla MySQL])',
- 'config-support-postgres' => '* $1 jest popularnym systemem baz danych, często stosowanym zamiast MySQL ([http://www.php.net/manual/en/pgsql.installation.php Zobacz, jak skompilować PHP ze wsparciem dla PostgreSQL]). Z powodu możliwości wystąpienia drobnych błędów, nie jest zalecana do wymagających wdrożeń.',
- 'config-support-sqlite' => '* $1 jest niewielkim systemem bazy danych, z którym MediaWiki bardzo dobrze współpracuje. ([http://www.php.net/manual/en/pdo.installation.php Jak skompilować PHP ze wsparciem dla SQLite], korzystając z PDO)',
- 'config-support-oracle' => '* $1 jest komercyjną profesjonalną bazą danych. ([http://www.php.net/manual/en/oci8.installation.php Jak skompilować PHP ze wsparciem dla OCI8])',
- 'config-header-mysql' => 'Ustawienia MySQL',
- 'config-header-postgres' => 'Ustawienia PostgreSQL',
- 'config-header-sqlite' => 'Ustawienia SQLite',
- 'config-header-oracle' => 'Ustawienia Oracle',
- 'config-invalid-db-type' => 'Nieprawidłowy typ bazy danych',
- 'config-missing-db-name' => 'Należy wpisać wartość w polu „Nazwa bazy danych”',
- 'config-missing-db-host' => 'Musisz wpisać wartość w polu „Serwer bazy danych”',
- 'config-missing-db-server-oracle' => 'Należy wpisać wartość w polu „Nazwa instancji bazy danych (TNS)”',
- 'config-invalid-db-server-oracle' => 'Nieprawidłowa nazwa instancji bazy danych (TNS) „$1”.
-Użyj "TNS Name" lub "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods])',
- 'config-invalid-db-name' => 'Nieprawidłowa nazwa bazy danych „$1”.
-Używaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) lub znaku odejmowania (-).',
- 'config-invalid-db-prefix' => 'Nieprawidłowy prefiks bazy danych „$1”.
-Używaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) lub znaku odejmowania (-).',
- 'config-connection-error' => '$1.
-
-Sprawdź adres serwera, nazwę użytkownika i hasło, a następnie spróbuj ponownie.',
- 'config-invalid-schema' => 'Nieprawidłowa nazwa schematu dla MediaWiki „$1”.
-Nazwa może zawierać wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9) i podkreślenia (_).',
- 'config-db-sys-create-oracle' => 'Instalator może wykorzystać wyłącznie konto SYSDBA do tworzenia nowych kont użytkowników.',
- 'config-db-sys-user-exists-oracle' => 'Konto użytkownika „$1“ już istnieje. SYSDBA można użyć tylko do utworzenia nowego konta!',
- 'config-postgres-old' => 'Korzystasz z wersji $2 oprogramowania PostgreSQL, a potrzebna jest wersja co najmniej $1.',
- 'config-sqlite-name-help' => 'Wybierz nazwę, która będzie identyfikować Twoją wiki.
-Nie wolno używać spacji ani myślników.
-Zostanie ona użyta jako nazwa pliku danych SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Nie można utworzyć katalogu danych <code><nowiki>$1</nowiki></code> , ponieważ katalog nadrzędny <code><nowiki>$2</nowiki></code> nie jest dostępny do zapisu przez webserwer.
-
-Instalator nie może określić, jako kttóry użytkownik działa webserwer.
-Zezwól by katalog <code><nowiki>$3</nowiki></code> był dostępny do zapisu przez niego, aby przejść dalej.
-W systemie Unix/Linux wykonaj:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Nie można utworzyć katalogu danych <code><nowiki>$1</nowiki></code> , ponieważ katalog nadrzędny <code><nowiki>$2</nowiki></code> nie jest dostępny do zapisu przez webserwer.
-
-Instalator nie może określić, jako kttóry użytkownik działa webserwer.
-Zezwól by katalog <code><nowiki>$3</nowiki></code> był globalnie modyfikowalny przez niego (i innych!) aby przejść dalej.
-W systemie Unix/Linux wykonaj:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Błąd podczas tworzenia katalogu dla danych „$1”.
-Sprawdź lokalizację i spróbuj ponownie.',
- 'config-sqlite-dir-unwritable' => 'Nie można zapisać do katalogu „$1”.
-Zmień uprawnienia dostępu do katalogu tak, aby serwer WWW mógł pisać do niego, a następnie spróbuj ponownie.',
- 'config-sqlite-connection-error' => '$1.
-
-Sprawdź katalog danych oraz nazwę bazy danych, a następnie spróbuj ponownie.',
- 'config-sqlite-readonly' => 'Plik <code>$1</code> nie jest zapisywalny.',
- 'config-sqlite-cant-create-db' => 'Nie można utworzyć pliku bazy danych <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'Brak wsparcia FTS3 dla PHP. Tabele zostały cofnięte',
- 'config-can-upgrade' => "W bazie danych są już tabele MediaWiki.
-Aby uaktualnić je do MediaWiki $1, kliknij '''Dalej'''.",
- 'config-upgrade-done' => "Uaktualnienie kompletne.
-
-Można teraz [ $1 rozpocząć korzystanie z wiki].
-
-Jeśli chcesz ponownie wygenerować plik <code>LocalSettings.php</code>, kliknij przycisk poniżej.
-Jest to '''nie zalecane''', chyba że występują problemy z twoją wiki.",
- 'config-upgrade-done-no-regenerate' => 'Aktualizacja zakończona.
-
-Możesz wreszcie [$1 zacząć korzystać ze swojej wiki].',
- 'config-regenerate' => 'Ponowne generowanie LocalSettings.php →',
- 'config-show-table-status' => 'Zapytanie „<code>SHOW TABLE STATUS</code>” nie powiodło się!',
- 'config-unknown-collation' => "'''Uwaga''' – bazy danych używa nierozpoznanej metody porównywania.",
- 'config-db-web-account' => 'Konto bazy danych dla dostępu przez WWW',
- 'config-db-web-help' => 'Wybierz nazwę użytkownika i hasło, z których korzystać będzie serwer WWW do łączenia się z serwerem baz danych, podczas zwykłej pracy z wiki.',
- 'config-db-web-account-same' => 'Użyj tego samego konta, co dla instalacji',
- 'config-db-web-create' => 'Utwórz konto, jeśli jeszcze nie istnieje',
- 'config-db-web-no-create-privs' => 'Konto podane do wykonania instalacji nie ma wystarczających uprawnień, aby utworzyć nowe konto.
-Konto, które wskazałeś tutaj musi już istnieć.',
- 'config-mysql-engine' => 'Silnik przechowywania',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Ostrzeżenie''': wybrano MyISIAM jako silnik składowania danych MySQL, co nie jest zalecane do użytku w MediaWiki, ponieważ:
- * ledwo obsługuje współbieżnośći ze względu na blokowanie tabel
- * jest bardziej podatna na uszkodzenie niż inne silniki
- * kod źródłowy MediaWiki nie zawsze obsługuje MyISAM tak, jak powinien
-
-Jeśli instalacja MySQL obsługuje InnoDB, jest wysoce zalecane, by to je wybrać.
-Jeśli instalacja MySQL nie obsługuje InnoDB, być może nadszedł czas na jej uaktualnienie.",
- 'config-mysql-only-myisam-dep' => "'''Ostrzeżenie:''' MyISAM jest jedynym dostępnym mechanizmem składowania dla MySQL, który nie jest zalecany do używania z MediaWiki, ponieważ:
-* słabo obsługuje współbieżność z powodu blokowania tabel
-* jest bardziej skłonny do uszkodzeń niż inne silniki
-* kod MediaWiki nie zawsze traktuje MyISAM jak powinien
-
-Twoja instalacja MySQL nie obsługuje InnoDB, być może jest to czas na aktualizację.",
- 'config-mysql-engine-help' => "'''InnoDB''' jest prawie zawsze najlepszą opcją, ponieważ posiada dobrą obsługę współbieżności.
-
-'''MyISAM''' może być szybsze w instalacjach pojedynczego użytkownika lub tylko do odczytu.
-Bazy danych MyISAM mają tendencję do ulegania uszkodzeniom częściej niż bazy InnoDB.",
- 'config-mysql-charset' => 'Zestaw znaków bazy danych',
- 'config-mysql-binary' => 'binarny',
- 'config-mysql-utf8' => 'UTF‐8',
- 'config-mysql-charset-help' => "W '''trybie binarnym''', MediaWiki zapisuje tekst UTF-8 do bazy danych w polach binarnych.
-Jest on bardziej wydajny niż tryb UTF-8 w MySQL i pozwala na używanie znaków pełnego zakresu Unicode.
-
-W '''trybie UTF-8''', MySQL będzie znać zestaw znaków w jakim zakodowano dane, można też przedstawić i przekonwertuj je odpowiednio, ale nie pozwoli Ci przechowywać znaków spoza [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes podstawowej płaszczyzny wielojęzyczności].",
- 'config-site-name' => 'Nazwa wiki',
- 'config-site-name-help' => 'Ten napis pojawi się w pasku tytułowym przeglądarki oraz w różnych innych miejscach.',
- 'config-site-name-blank' => 'Wprowadź nazwę witryny.',
- 'config-project-namespace' => 'Przestrzeń nazw projektu',
- 'config-ns-generic' => 'Projekt',
- 'config-ns-site-name' => 'Taka sama jak nazwa wiki $1',
- 'config-ns-other' => 'Inna (należy określić)',
- 'config-ns-other-default' => 'MojaWiki',
- 'config-project-namespace-help' => 'Według przykładu Wikipedii wiele wiki przechowuje swoje strony zasad oddzielnie od stron z zawartością, w "\'\'\'przestrzeni nazw projektu\'\'\'".
-Wszystkie tytuły stron w tej przestrzeni nazw zaczynają się od pewnego przedrostka, który można tutaj określić.
-Tradycyjnie ten przedrostek wywodzi się od nazwy wiki, ale nie może zawierać pewnych znaków przestankowych takich jak "#" lub ":".',
- 'config-ns-invalid' => 'Podana przestrzeń nazw „<nowiki>$1</nowiki>” jest nieprawidłowa.
-Podaj inną przestrzeń nazw projektu.',
- 'config-ns-conflict' => 'Określona przestrzeń nazw "<nowiki>$1</nowiki>" powoduje konflikt z domyślną przestrzenią nazw MediaWiki.
-Wskaż inną przestrzeń nazw projektu.',
- 'config-admin-box' => 'Konto administratora',
- 'config-admin-name' => 'Administrator',
- 'config-admin-password' => 'Hasło',
- 'config-admin-password-confirm' => 'Hasło powtórnie',
- 'config-admin-help' => 'Wprowadź preferowaną nazwę użytkownika, na przykład „Jan Kowalski”.
-Tej nazwy będziesz używać do logowania się do wiki.',
- 'config-admin-name-blank' => 'Wpisz nazwę użytkownika, który będzie administratorem.',
- 'config-admin-name-invalid' => 'Podana nazwa użytkownika „<nowiki>$1</nowiki>” jest nieprawidłowa.
-Podaj inną nazwę.',
- 'config-admin-password-blank' => 'Wprowadź hasło dla konta administratora.',
- 'config-admin-password-same' => 'Hasło nie może być takie samo jak nazwa użytkownika.',
- 'config-admin-password-mismatch' => 'Wprowadzone dwa hasła różnią się między sobą.',
- 'config-admin-email' => 'Adres e‐mail',
- 'config-admin-email-help' => 'Wpisz adres e‐mail, aby mieć możliwość odbierania e‐maili od innych użytkowników wiki, zresetowania hasła oraz otrzymywania powiadomień o zmianach na stronach z listy obserwowanych. Możesz pozostawić to pole niewypełnione.',
- 'config-admin-error-user' => 'Błąd wewnętrzny podczas tworzenia konta administratora o nazwie „<nowiki>$1</nowiki>”.',
- 'config-admin-error-password' => 'Wewnętrzny błąd podczas ustawiania hasła dla administratora „<nowiki>$1</nowiki>”: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Wpisałeś nieprawidłowy adres e‐mail',
- 'config-subscribe' => 'Zapisz się na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce listę pocztową z ogłoszeniami o nowych wersjach].',
- 'config-subscribe-help' => 'Jest to lista o małej liczbie wiadomości, wykorzystywana do przesyłania informacji o udostępnieniu nowej wersji oraz istotnych sprawach dotyczących bezpieczeństwa.
-Powinieneś zapisać się na tę listę i aktualizować zainstalowane oprogramowanie MediaWiki gdy pojawia się nowa wersja.',
- 'config-subscribe-noemail' => 'Próbowano subskrybować listę mailingową ogłoszeń wersji bez podania adresu e-mail.
-Proszę podać adres e-mail, jeśli chcesz subskrybować listę wysyłkową.',
- 'config-almost-done' => 'To już prawie koniec!
-Możesz pominąć pozostałe czynności konfiguracyjne i zainstalować wiki.',
- 'config-optional-continue' => 'Zadaj mi więcej pytań.',
- 'config-optional-skip' => 'Jestem już znudzony, po prostu zainstaluj wiki.',
- 'config-profile' => 'Profil uprawnień użytkowników',
- 'config-profile-wiki' => 'Otwarte wiki',
- 'config-profile-no-anon' => 'Wymagane utworzenie konta',
- 'config-profile-fishbowl' => 'Wyłącznie zatwierdzeni edytorzy',
- 'config-profile-private' => 'Prywatna wiki',
- 'config-profile-help' => "Strony typu wiki działają najlepiej, gdy pozwolisz je edytować tak wielu osobom, jak to możliwie.
-W MediaWiki, można łatwo sprawdzić ostatnie zmiany i wycofać szkody, które są spowodowane przez naiwnych lub złośliwych użytkowników.
-
-Jednakże wielu uznało MediaWiki użytecznym w różnorodnych rolach, a czasami nie jest łatwo przekonać wszystkich do korzyści ze sposobu działania wiki. Masz więc wybór.
-
-Ustawienie '''{{int:config-profile-wiki}}''' pozwala każdemu na edycję, nawet bez logowania się.
-Wiki z '''{{int:config-profile-no-anon}}''' zawiera dodatkowe możliwości ale może powstrzymywać potencjalnych edytorów.
-
-Scenariusz '''{{int:config-profile-fishbowl}}''' umożliwia zatwierdzonym użytkownikom edycję, ale wyświetlanie stron jest powszechnie dostępne, włącznie z historią.
-Ustawienie '''{{int:config-profile-private}}'' ' pozwala na wyświetlanie stron tylko zatwierdzonym użytkownikom, ta sama grupa może je edytować.
-
-Bardziej skomplikowane konfiguracje uprawnień użytkowników są dostępne po zakończeniu instalacji, zobacz [//www.mediawiki.org/wiki/Manual:User_rights odpowiednią część podręcznika].",
- 'config-license' => 'Prawa autorskie i licencja',
- 'config-license-none' => 'Brak stopki z licencją',
- 'config-license-cc-by-sa' => 'Creative Commons – za uznaniem autora, na tych samych zasadach',
- 'config-license-cc-by' => 'Creative Commons – za podaniem autora',
- 'config-license-cc-by-nc-sa' => 'Creative Commons – za uznaniem autora, bez użycia komercyjnego, na tych samych zasadach',
- 'config-license-cc-0' => 'Creative Commons – zero (domena publiczna)',
- 'config-license-gfdl' => 'GNU licencja wolnej dokumentacji 1.3 lub nowsza',
- 'config-license-pd' => 'Domena publiczna',
- 'config-license-cc-choose' => 'Wybierz własną licencję Creative Commons',
- 'config-license-help' => "Wiele publicznych wiki umieszcza wszystkie dopisane treści na [http://freedomdefined.org/Definition wolnej licencji].
-To pomaga tworzyć poczucie wspólnoty i zachęca do długoterminowego wkładu.
-Nie jest to zazwyczaj konieczne w prywatnych lub firmowych wiki.
-
-Jeśli chcesz móc użyć tekstu z Wikipedii i chcesz Wikipedia mogła zaakceptować tekst skopiowany z twojej wiki, należy wybrać '''Creative Commons Attribution Share Alike'''.
-
-Wikipedia używała poprzednio GNU Free Documentation License.
-GFDL jest poprawną licencję, ale trudno ją zrozumieć.
-Trudno 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, [http://www.php.net/manual/en/mail.configuration.php Ustawienia poczty PHP] muszą być poprawnie wprowadzone.
-Jeś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-email-usertalk-help' => 'Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronie dyskusji użytkownika, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.',
- 'config-email-watchlist' => 'Włącz powiadomienie o zmianach stron obserwowanych',
- 'config-email-watchlist-help' => 'Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronach obserwowanych, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.',
- 'config-email-auth' => 'Włącz uwierzytelnianie e‐mailem',
- 'config-email-auth-help' => "Jeśli ta opcja jest włączona, użytkownicy będą musieli potwierdzić swoje adresy e-mail przy użyciu wysłanego do nich łącza, gdy będą je ustawiać lub zmieniać.
-Tylko uwierzytelnione adresy e-mail mogą otrzymywać wiadomości od innych użytkowników lub mailowe powiadomienia o zmianach.
-Ustawienie tej opcji jest'''zalecane''' na publicznych wiki ze względu na potencjalne nadużycia funkcji poczty e-mail.",
- 'config-email-sender' => 'Zwrotny adres e‐mail',
- 'config-email-sender-help' => 'Wprowadź adres e-mail używany jako adres zwrotny wiadomości wychodzących.
-To tam będą wysyłane szturchnięcia.
-Wiele serwerów poczty wymaga, by co najmniej część nazwy domeny była prawidłowa.',
- 'config-upload-settings' => 'Przesyłanie obrazków i plików',
- 'config-upload-enable' => 'Włącz przesyłanie plików na serwer',
- 'config-upload-help' => 'Przesyłanie plików potencjalnie wystawia serwer na zagrożenia.
-Więcej informacji na ten temat można znaleźć w [//www.mediawiki.org/wiki/Manual:Security sekcji zabezpieczeń] podręcznika.
-
-Aby włączyć przesyłanie plików, zmień właściwości podkatalogu <code>images</code> katalogu głównego MediaWiki tak, aby serwer sieci web mógł zapisywać do niego.
-Następnie włącz tę opcję.',
- 'config-upload-deleted' => 'Katalog dla usuniętych plików',
- 'config-upload-deleted-help' => 'Wybierz katalog, w którym będzie archiwum usuniętych plików.
-Najlepiej, aby nie był on dostępny z internetu.',
- 'config-logo' => 'Adres URL logo',
- 'config-logo-help' => 'Domyślny motyw MediaWiki zawiera miejsce na logo wielkości 135 x 160 pikseli powyżej menu na pasku bocznym.
-Prześlij obrazek o odpowiednim rozmiarze, a następnie wpisz jego URL tutaj.
-
-Możesz użyć <code>$wgStylePath</code> lub <code>$wgScriptPath</code> jeżeli twoje logo jest relatywne do tych ścieżek.
-
-Jeśli nie chcesz logo, pozostaw to pole puste.',
- 'config-instantcommons' => 'Włącz Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] jest funkcją, która pozwala wiki używać obrazów, dźwięków i innych mediów znalezionych na witrynie [//commons.wikimedia.org/ Wikimedia Commons].
-Aby to zrobić, MediaWiki wymaga dostępu do internetu.
-
-Aby uzyskać więcej informacji na temat tej funkcji, w tym instrukcje dotyczące sposobu ustawiania go na wiki innych niż Wikimedia Commons, sprawdź w [//mediawiki.org/wiki/Manual:$wgForeignFileRepos podręczniku].',
- 'config-cc-error' => 'Wybieranie licencji Creative Commons nie dało wyniku.
-Wpisz nazwę licencji ręcznie.',
- 'config-cc-again' => 'Wybierz jeszcze raz...',
- 'config-cc-not-chosen' => 'Wybierz którą chcesz licencję Creative Commons i kliknij „Dalej”.',
- 'config-advanced-settings' => 'Konfiguracja zaawansowana',
- 'config-cache-options' => 'Ustawienia buforowania obiektów',
- 'config-cache-help' => 'Buforowanie obiekto jest używane aby przyspieszyć MediaWiki przez trzymanie w pamięci podręcznej często używanych danych.
-Średnie oraz duże witryny są wysoce zachęcane by je włączyć, a małe witryny także dostrzegą korzyści.',
- 'config-cache-none' => 'Brak buforowania (wszystkie funkcje będą działać, ale mogą wystąpić kłopoty z wydajnością na dużych witrynach wiki)',
- 'config-cache-accel' => 'Buforowania obiektów PHP (APC, XCache lub WinCache)',
- 'config-cache-memcached' => 'Użyj Memcached (wymaga dodatkowej instalacji i konfiguracji)',
- 'config-memcached-servers' => 'Serwery Memcached:',
- 'config-memcached-help' => 'Lista adresów IP do wykorzystania przez Memcached.
-Adresy powinny być umieszczane po jednym w linii i określać również wykorzystywany port. Na przykład:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Został wybrany Memcached jako typ pamięci podręcznej, ale nie określono żadnych serwerów.',
- 'config-memcache-badip' => 'Wprowadzono nieprawidłowy adres IP dla Memcached: $1.',
- 'config-memcache-noport' => 'Nie określono portu dla serwera Memcached: $1.
-Jeśli nie znasz numeru portu, wartością domyślną jest 11211.',
- 'config-memcache-badport' => 'Numery portu Memcached powinny zawierać się pomiędzy $1 i $2.',
- 'config-extensions' => 'Rozszerzenia',
- 'config-extensions-help' => 'Rozszerzenia wyżej wymienione zostały wykryte w katalogu <code>./extensions</code>.
-
-Mogą one wymagać dodatkowych czynności konfiguracyjnych, ale można je teraz włączyć',
- 'config-install-alreadydone' => "'''Uwaga''' – wydaje się, że MediaWiki jest już zainstalowane, a obecnie próbujesz zainstalować je ponownie.
-Przejdź do następnej strony.",
- 'config-install-begin' => 'Po naciśnięciu "{{int:config-continue}}", rozpocznie się instalacja MediaWiki.
-Jeśli nadal chcesz dokonać zmian, naciśnij "{{int:config-back}}".',
- 'config-install-step-done' => 'gotowe',
- 'config-install-step-failed' => 'nieudane',
- 'config-install-extensions' => 'Włącznie z rozszerzeniami',
- 'config-install-database' => 'Konfigurowanie bazy danych',
- 'config-install-schema' => 'Tworzenie schematu',
- 'config-install-pg-schema-not-exist' => 'Schemat PostgreSQL nie istnieje.',
- 'config-install-pg-schema-failed' => 'Utworzenie tabel nie powiodło się.
-Upewnij się, że użytkownik „$1” może zapisywać do schematu „$2”.',
- 'config-install-pg-commit' => 'Zatwierdzanie zmian',
- 'config-install-pg-plpgsql' => 'Sprawdzanie języka PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Musisz zainstalować język PL/pgSQL w bazie danych $1',
- 'config-pg-no-create-privs' => 'Konto, które zostało określone dla instalacji nie ma wystarczających uprawnień, aby utworzyć konto.',
- 'config-pg-not-in-role' => 'Konto określone dla użytkownika sieci już istnieje.
-Konto określone dla instalacji nie ma uprawnień administratora ani nie jest przynależy do roli użytkownika sieci web, więc nie można utworzyć obiektów stanowiących własność użytkownika sieci.
-
-MediaWiki wymaga obecnie, by tabele były własnością konta zwykłego użytkownika. Podaj inną nazwę konta użytkownika, lub kliknij przycisk "Wstecz" i podaj nazwę konta użytkownika instalatora, które posiada odpowiednie uprawnienia.',
- 'config-install-user' => 'Tworzenie użytkownika bazy danych',
- 'config-install-user-alreadyexists' => 'Konto użytkownika „$1“ już istnieje',
- 'config-install-user-create-failed' => 'Tworzenie użytkownika "$1" nie powiodło się: $2',
- 'config-install-user-grant-failed' => 'Przyznanie uprawnień użytkownikowi „$1” nie powiodło się – $2',
- 'config-install-user-missing' => 'Nie istnieje konto użytkownika „$1“.',
- 'config-install-user-missing-create' => 'Określony użytkownik "$1" nie istnieje.
-Kliknij poniższe pole wyboru „utwórz konto" jeśli chcesz go utworzyć.',
- 'config-install-tables' => 'Tworzenie tabel',
- 'config-install-tables-exist' => "'''Uwaga''' – wygląda na to, że tabele MediaWiki już istnieją.
-Pomijam tworzenie tabel.",
- 'config-install-tables-failed' => "'''Błąd''' – tworzenie tabeli nie powiodło się z powodu błędu – $1",
- 'config-install-interwiki' => 'Wypełnianie tabeli domyślnymi interwiki',
- 'config-install-interwiki-list' => 'Nie można odnaleźć pliku <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Uwaga''' – wygląda na to, że tabela interwiki ma już jakieś wpisy.
-Tworzenie domyślnej listy pominięto.",
- 'config-install-stats' => 'Inicjowanie statystyki',
- 'config-install-keys' => 'Generowanie tajnych kluczy',
- 'config-insecure-keys' => "'''Ostrzeżenie:''' {{PLURAL:$2|Klucz bezpieczeństwa|Klucze bezpieczeństwa|Klucze bezpieczeństwa}} ($1) utworzone podczas instalacji {{PLURAL:$2|utworzony podczas instalacji nie jest|utworzone podczas instalacji nie są|utworzone podczas instalacji nie są}} w pełni bezpieczne. Być może warto wygenerować {{PLURAL:$2|własny klucz|własne klucze|własne klucze}}.",
- 'config-install-sysop' => 'Tworzenie konta administratora',
- 'config-install-subscribe-fail' => 'Nie można zapisać na listę „mediawiki-announce“ – $1',
- 'config-install-subscribe-notpossible' => 'cURL nie jest zainstalowany, więc allow_url_fopen nie jest dostępne.',
- 'config-install-mainpage' => 'Tworzenie strony głównej z domyślną zawartością',
- 'config-install-extension-tables' => 'Tworzenie tabel dla aktywnych rozszerzeń',
- 'config-install-mainpage-failed' => 'Nie udało się wstawić strony głównej – $1',
- 'config-install-done' => "'''Gratulacje!'''
-Udało Ci się zainstalować MediaWiki.
-
-Instalator wygenerował plik konfiguracyjny <code>LocalSettings.php</code>.
-
-Musisz go pobrać i umieścić w katalogu głównym Twojej instalacji wiki (tym samym katalogu co index.php). Pobieranie powinno zacząć się automatycznie.
-
-Jeżeli pobieranie nie zostało zaproponowane lub jeśli użytkownik je anulował, można ponownie uruchomić pobranie klikając poniższe łącze:
-
-$3
-
-'''Uwaga''': Jeśli nie zrobisz tego teraz, wygenerowany plik konfiguracyjny nie będzie już dostępny po zakończeniu instalacji.
-
-Po załadowaniu pliku konfiguracyjnego możesz '''[$2 wejść na wiki]'''.",
- 'config-download-localsettings' => 'Pobierz <code>LocalSettings.php</code>',
- 'config-help' => 'pomoc',
- 'config-nofile' => 'Nie udało się odnaleźć pliku "$1". Czy nie został usunięty?',
- 'config-extension-link' => 'Czy wiesz, że twoja wiki obsługuje [//www.mediawiki.org/wiki/Manual:Extensions/pl rozszerzenia]?
-
-Możesz przejrzeć [//www.mediawiki.org/wiki/Category:Extensions_by_category rozszerzenia według kategorii] lub [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] aby zobaczyć pełną listę rozszerzeń.',
- 'mainpagetext' => "'''Instalacja MediaWiki powiodła się.'''",
- 'mainpagedocfooter' => 'Zobacz [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] w celu uzyskania informacji o działaniu oprogramowania wiki.
-
-== Na początek ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings/pl Lista ustawień konfiguracyjnych]
-* [//www.mediawiki.org/wiki/Manual:FAQ/pl MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Przetłumacz MediaWiki na swój język]',
-);
-
-/** Piedmontese (Piemontèis)
- * @author Borichèt
- * @author Dragonòt
- * @author Krinkle
- * @author 아라
- */
-$messages['pms'] = array(
- 'config-desc' => "L'instalador për mediaWiki",
- 'config-title' => 'Anstalassion ëd MediaWiki $1',
- 'config-information' => 'Anformassion',
- 'config-localsettings-upgrade' => "A l'é stàit trovà n'archivi <code>LocalSettings.php</code>.
-Për agiorné cost'anstalassion, ch'a anserissa ël valor ëd <code>\$wgUpgradeKey</code> ant la casela sì-sota.
-A la trovrà an LocalSetting.php.",
- 'config-localsettings-cli-upgrade' => "N'archivi <code>LocalSettings.php</code> a l'é stàit trovà.
-Për agiorné sta instalassion, për piasì fà anvece giré <code>update.php</code>",
- 'config-localsettings-key' => "Ciav d'agiornament:",
- 'config-localsettings-badkey' => "La ciav ch'it l'has dàit a l'é pa giusta.",
- 'config-upgrade-key-missing' => "A l'é stàita trovà n'istalassion esistenta ëd MediaWiki.
-Për agiorné soa istalassion, për piasì ch'a buta la linia sì-sota al fond ëd sò <code>LocalSettings.php</code>:
-
-$1",
- 'config-localsettings-incomplete' => "L'esistent <code>LocalSettings.php</code> a smija esse ancomplet.
-La variàbil $1 a l'é nen ampostà.
-Për piasì, ch'a modìfica <code>LocalSettings.php</code> ëd fasson che costa variàbil a sia ampostà, e ch'a sgnaca «{{int:Config-continue}}».",
- 'config-localsettings-connection-error' => "A l'é ancapitaje n'eror an colegand-se a la base ëd dàit an dovrand j'ampostassion specificà an <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Për piasì, ch'a coregia cost'ampostassion e ch'a preuva torna.
-
-$1",
- 'config-session-error' => 'Eror an fasend parte la session: $1',
- 'config-session-expired' => "Ij sò dat ëd session a smijo scadù.
-Le session a son configurà për na durà ëd $1.
-A peul aumenté sòn an ampostand <code>session.gc_maxlifetime</code> an php.ini.
-Ch'a anandia torna ël process d'instalassion.",
- 'config-no-session' => "Ij sò dat ëd session a son përdù!
-Ch'a contròla sò php.ini e ch'as sigura che <code>session.save_path</code> a sia ampostà ant ël dossié giust.",
- 'config-your-language' => 'Toa lenga:',
- 'config-your-language-help' => "Selessioné na lenga da dovré durant ël process d'instalassion.",
- 'config-wiki-language' => 'Lenga dla Wiki:',
- 'config-wiki-language-help' => 'Selession-a la lenga dont la wiki a sarà prevalentement scrivùa.',
- 'config-back' => '← André',
- 'config-continue' => 'Continua →',
- 'config-page-language' => 'Lenga',
- 'config-page-welcome' => 'Bin ëvnù a MediaWiki!',
- 'config-page-dbconnect' => 'Coleghesse a la base ëd dàit',
- 'config-page-upgrade' => "Agiorné l'instalassion esistenta",
- 'config-page-dbsettings' => 'Ampostassion dla base ëd dàit',
- 'config-page-name' => 'Nòm',
- 'config-page-options' => 'Opsion',
- 'config-page-install' => 'Instala',
- 'config-page-complete' => 'Completa!',
- 'config-page-restart' => "Fé torna parte l'instalassion",
- 'config-page-readme' => 'Lesme',
- 'config-page-releasenotes' => 'Nòte ëd publicassion',
- 'config-page-copying' => 'Copié',
- 'config-page-upgradedoc' => 'Agiorné',
- 'config-page-existingwiki' => 'Wiki esistenta',
- 'config-help-restart' => "Veul-lo scancelé tùit ij dat salvà ch'a l'ha anserì e anandié torna ël process d'instalassion?",
- 'config-restart' => 'É!, felo torna parte',
- 'config-welcome' => "=== Contròj d'ambient ===
-Dle verìfiche ëd base a son fàite për vëdde se st'ambient a va bin për l'instalassion ëd MediaWiki.
-S'a l'ha da manca d'agiut durant l'anstalassion, a dovrìa fornì j'arzultà dë sti contròj.",
- 'config-copyright' => "=== Drit d'Autor e Condission ===
-
-$1
-
-Cost-sì a l'é un programa lìber e a gràtis: a peul ridistribuilo e/o modifichelo sota le condission dla licensa pùblica general GNU com publicà da la Free Software Foundation; la version 2 dla Licensa, o (a toa sèrnìa) qualsëssìa version pi recenta.
-
-Cost programa a l'é distribuì ant la speransa ch'a sia ùtil, ma '''sensa gnun-e garansìe'''; sensa gnanca la garansia implìssita ëd '''comersiabilità''' o '''d'esse adat a un but particolar'''.
-
-A dovrìa avèj arseivù <doclink href=Copying>na còpia ëd la licensa pùblica general GNU</doclink> ansema a sto programa; dësnò, ch'a scriva a la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA opura [http://www.gnu.org/copyleft/gpl.html ch'a la lesa an linia].",
- 'config-sidebar' => "* [//www.mediawiki.org Intrada MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Guida dl'Utent]
-* [//www.mediawiki.org/wiki/Manual:Contents Guida dl'Aministrator]
-* [//www.mediawiki.org/wiki/Manual:FAQ Soens an ciamo]
-----
-* <doclink href=Readme>Ch'am lesa</doclink>
-* <doclink href=ReleaseNotes>Nòte ëd publicassion</doclink>
-* <doclink href=Copying>Còpia</doclink>
-* <doclink href=UpgradeDoc>Agiornament</doclink>",
- 'config-env-good' => "L'ambient a l'é stàit controlà.
-It peule instalé MediaWiki.",
- 'config-env-bad' => "L'ambient a l'é stàit controlà.
-It peule pa instalé MediaWiki.",
- 'config-env-php' => "PHP $1 a l'é instalà.",
- 'config-env-php-toolow' => "PHP $1 a l'é instalà.
-Ant tùit ij cas, MediaWiki a ciama PHP $2 o pi neuv.",
- 'config-unicode-using-utf8' => 'As deuvra utf8_normalize.so ëd Brion Vibber për la normalisassion Unicode.',
- 'config-unicode-using-intl' => "As deuvra l'[http://pecl.php.net/intl estension intl PECL] për la normalisassion Unicode.",
- 'config-unicode-pure-php-warning' => "'''Avis:''' L'[http://pecl.php.net/intl estension intl PECL] a l'é pa disponìbil për gestì la normalisassion Unicode, da già che l'implementassion an PHP pur a faliss për lentëssa.
-S'a gestiss un sit a àut tràfich, a dovrìa lese cheicòs an sla [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalisassion Unicode].",
- 'config-unicode-update-warning' => "'''Avis:''' La version instalà dlë spassiador ëd normalisassion Unicode a deuvra na version veja ëd la librarìa dël [http://site.icu-project.org/ proget ICU].
-A dovrìa fé n'[//www.mediawiki.org/wiki/Unicode_normalization_considerations agiornament] s'a l'é anteressà a dovré Unicode.",
- 'config-no-db' => "Impossìbil trové un pilòta ëd base ëd dàit bon! A dev instalé un pilòta ëd base ëd dàit për PHP.
-Le sòrt ëd database ch'a ven-o a son apogià: $1.
-
-S'a l'é ansima a 'n servissi partagià, ch'a ciama a sò fornidor ëd servissi d'instalé un pilòta ëd base ëd dàit compatìbil.
-S'a l'é compilasse PHP chiel-midem, ch'a lo configura torna con un client ëd base ëd dàit abilità, për esempi an dovrand <code>./configure --with-mysql</code>.
-S'a l'ha instalà PHP dai pachèt Debian o Ubuntu, antlora a dev ëdcò istalé ël mòdul php5-mysql.",
- 'config-outdated-sqlite' => "'''Avis''': chiel a l'ha SQLite $1, che a l'é pi vej che la version mìnima dont a-i é damanca $2. SQLite a sarà pa disponìbil.",
- 'config-no-fts3' => "'''Avis''': SQLite a l'é compilà sensa ël mòdul [//sqlite.org/fts3.html FTS3], le funsion d'arserca a saran pa disponìbij su cost motor.",
- 'config-register-globals' => "'''Avis: L'opsion <code>[http://php.net/register_globals register_globals]</code> ëd PHP a l'é abilità.'''
-'''Ch'a la disabìlita s'a peul.'''
-MediaWiki a marcërà, ma sò servent a l'é espòst a 'd possìbij vunerabilità ëd sicurëssa.",
- 'config-magic-quotes-runtime' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] a l'é ativ!'''
-Costa opsion a danegia ij dat d'intrada an manera pa prevedìbil.
-A peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
- 'config-magic-quotes-sybase' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] a l'é ativ!'''
-Costa opsion a danegia ij dat d'intrada an manera pa prevedìbil.
-A peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
- 'config-mbstring' => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] a l'é ativ!'''
-Costa opsion a càusa d'eror e a peul danegié ij dat d'intrada an manera pa prevedìbil.
-A peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
- 'config-ze1' => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] a l'é ativ!'''
-Costa opsion a càusa dij bigat afros con MediaWiki.
-A peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
- 'config-safe-mode' => "'''Avis:''' [http://www.php.net/features.safe-mode Safe mode] ëd PHP a l'é ativ.
-A peul causé ëd problema, dzortut s'as deuvro ël cariament d'archivi e ël manteniment ëd <code>math</code>.",
- 'config-xml-bad' => "Mòdul XML ed PHP mancant.
-MediaWiki a l'ha da manca dle funsion an sto mòdul e a travajërà pa an costa configurassion.
-S'a fa giré mandrake, ch'a instala ël pachet php-xml.",
- 'config-pcre' => "A smija che ël mòdul d'apògg PCRE a sia mancant.
-MediaWiki a l'ha da manca dle funsion d'espression regolar Perl-compatìbij për marcé.",
- 'config-pcre-no-utf8' => "'''Fatal''': ël mòdul PCRE ëd PHP a smija esse compilà sensa l'apògg PCRE_UTF8.
-MediaWiki 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.
-Sossì a l'é probabilment tròp bass.
-L'instalassion a peul falì!",
- 'config-ctype' => "'''Fatal''': PHP a dev esse compilà con l'apògg për l'[http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
- 'config-xcache' => "[http://xcache.lighttpd.net/ XCache] a l'é instalà",
- 'config-apc' => "[http://www.php.net/apc APC] a l'é instalà",
- 'config-wincache' => "[http://www.iis.net/download/WinCacheForPhp WinCache] a l'é instalà",
- 'config-no-cache' => "'''Avis:''' As treuva pa [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache]. Ël buté d'oget an memòria local a l'é pa abilità.",
- 'config-mod-security' => "'''Avis''': Sò servent për l'aragnà a l'ha [http://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.
-Ch'a fasa arferiment a la [http://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-imagemagick' => "Trovà ImageMagick: <code>$1</code>.
-La miniaturisassion ëd figure a sarà abilità s'it abìlite le carie.",
- 'config-gd' => "Trovà la librarìa gràfica antëgrà GD.
-La miniaturisassion ëd figure a sarà abilità s'a abìlita ij cariament.",
- 'config-no-scaling' => 'As treuva pa la librarìa GD o ImageMagick.
-La miniaturisassion ëd figure a sarà disabilità.',
- 'config-no-uri' => "'''Eror:''' As peul pa determiné l'URI corenta.
-Instalassion abortìa.",
- 'config-no-cli-uri' => "'''Avis''': pa gnun --scriptpath specificà, a sarà dovrà ël predefinì: <code>$1</code>.",
- 'config-using-server' => 'Utilisassion dël nòm ëd servent "<nowiki>$1</nowiki>".',
- 'config-using-uri' => "Utilisassion ëd l'anliura ëd servent «<nowiki>$1$2</nowiki>».",
- 'config-uploads-not-safe' => "'''Avis:''' Sò dossié stàndard për carié <code>$1</code> a l'é vulneràbil a l'esecussion ëd qualsëssìa senari.
-Bele che MediaWiki a contròla j'aspet ëd sicurëssa ëd tùit j'archivi carià, a l'é motobin arcomandà ëd [//www.mediawiki.org/wiki/Manual:Security#Upload_security saré ës përtus ëd sicurëssa] prima d'abilité ij cariament.",
- 'config-no-cli-uploads-check' => "'''Avis:''' Toa cartela predefinìa për j-amportassion (<code>$1</code>) a l'é nen controlà a propòsit ëd la vulnerabilità
-d'esecussion ëd senari arbitrari durant l'istalassion CLI.",
- 'config-brokenlibxml' => "Sò sistema a l'ha na combinassion ëd version PHP e libxml2 che a l'ha dij bigat e a peul provoché la corussion ëd dat ëstërmà an MediaWiki e d'àutre aplicassion për l'aragnà.
-Ch'a agiorna a PHP 5.2.9 o pi neuv e libxml2 2.7.3 o pi neuv ([//bugs.php.net/bug.php?id=45996 bug filed with PHP]).
-Istalassion abortìa.",
- 'config-using531' => "MediaWiki a peul pa esse dovrà con PHP $1 a motiv d'un bigat ch'a ìmplica ij paràmetr d'arferiment a <code>__call()</code>.
-Ch'a agiorna a PHP 5.3.2 o pi neuv, o ch'a torna andré a PHP 5.3.0 për arzòlve ës problema.
-Istalassion abortìa.",
- 'config-suhosin-max-value-length' => 'Suhosin a l\'é instalà e a lìmita la longheur dël paràmetr GET a $1 byte. Ël component ResourceLoader ëd MediaWiki a travajerà an rispetand ës lìmit, ma sòn a degraderà le prestassion. Se possìbil, a dovrìa amposté suhosin.get.max_value_lenght a 1024 o pi àut an <code>php.ini</code>, e amposté <code>$wgResourceLoaderMaxQueryLength</code> al midem valor an LocalSettings.php .', # Fuzzy
- 'config-db-type' => 'Sòrt ëd base ëd dàit:',
- 'config-db-host' => 'Ospitant ëd la base ëd dàit:',
- 'config-db-host-help' => "Se sò servent ëd base ëd dàit a l'é su un servent diferent, ch'a anserissa ambelessì ël nòm dl'ospitant o l'adrëssa IP.
-
-S'a deuvra n'ospitalità partagià, sò fornidor d'ospitalità a dovrìa deje ël nòm dl'ospitant giust ant soa documentassion.
-
-Se a anstala su un servent Windows e a deuvra MySQL, dovré «localhost» a podrìa funsioné nen com nòm dël servent. S'a marcia nen, ch'a preuva «127.0.0.1» com adrëssa IP local.
-
-S'a deuvra PostgresSQL, ch'a lassa sto camp bianch për coleghesse a travers un socket UNIX.",
- 'config-db-host-oracle' => 'TNS dla base ëd dàit:',
- 'config-db-host-oracle-help' => "Anserì un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nòm ëd conession local] bon; n'archivi tnsnames.ora a dev esse visìbil da costa anstalassion..<br />S'a deuvra le librarìe cliente 10g o pi neuve a peul ëdcò dovré ël métod ëd nominassion [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
- 'config-db-wiki-settings' => 'Identìfica sta wiki',
- 'config-db-name' => 'Nòm dla base ëd dàit:',
- 'config-db-name-help' => "Ch'a serna un nòm ch'a identìfica soa wiki.
-A dovrìa conten-e gnun ëspassi.
-
-S'a deuvra n'ospitalità partagià, sò fornidor ëd l'ospitalità a-j darà un nòm ëd base ëd dàit specìfich da dovré o a lassrà ch'a lo crea via un panel ëd contròl.",
- 'config-db-name-oracle' => 'Schema dla base ëd dàit:',
- 'config-db-account-oracle-warn' => "A-i é tre possibilità mantnùe për istalé Oracle tanme terminal ëd base ëd dàit:
-
-S'a veul creé un cont ëd base ëd dàit com part dël process d'istalassion, për piasì ch'a fornissa un cont con ël ròl SYSDBA com cont ëd base ëd dàit për l'istalassion e ch'a specìfica le credensiaj vorsùe për ël cont d'acess an sl'aragnà, dësnò a peul ëdcò creé ël cont d'acess an sl'aragnà manualment e mach fornì col cont (se a l'ha ij përmess necessari për creé j'oget dë schema) o fornì doi cont diferent, un con ij privilegi ëd creé e un limità për l'acess an sla Ragnà.
-
-Ij senari për creé un cont con ij privilegi necessari a peul esse trovà ant la cartela «manutension/oracol/» ëd costa istalassion. Ch'a ten-a da ment che dovrand un cont limità a disabiliterà tute le funsion ëd manutension con ël cont predefinì.",
- 'config-db-install-account' => "Cont d'utent për l'instalassion.",
- 'config-db-username' => "Nòm d'utent dla base ëd dàit:",
- 'config-db-password' => 'Ciav dla base ëd dàit:',
- 'config-db-password-empty' => "Për piasì, ch'a anserissa na ciav për ël neuv utent ëd base ëd dàit: $1.
-Con tut ch'a sia possìbil creé d'utent sensa ciav, a l'é pa na ròba sigura.",
- 'config-db-install-username' => "Ch'a nserissa lë stranòm che a sarà dovrà për coleghesse a la base ëd dàit durant ël process d'istalassion.
-Cost-sì a l'é nen lë stranòm dël cont MediaWiki; a l'é lë stranòm për soa base ëd dàit.",
- 'config-db-install-password' => "Ch'a anserissa la ciav che a sarà dovrà për coleghesse a la base ëd dàit durant ël process d'istalassion.
-Costa-sì a l'é nen la ciav dël cont MediaWiki; a l'é la ciav për soa base ëd dàit.",
- 'config-db-install-help' => "Ch'a anserissa lë stranòm d'utent e la ciav che a saran dovrà për coleghesse a la base ëd dàit durant ël process d'instalassion.",
- 'config-db-account-lock' => "Dovré ij midem stranòm d'utent e ciav durant j'operassion normaj",
- 'config-db-wiki-account' => "Cont d'utent për j'operassion normaj",
- 'config-db-wiki-help' => "Ch'a anseriss lë stranòm d'utent e la ciav che a saran dovrà për coleghesse a la base ëd dàit durant j'operassion normaj dla wiki.
-S'ël cont a esist pa, e ël cont d'instalassion a l'ha ij privilegi ch'a-i van, sto cont utent a sarà creà con ij privilegi mìnin për fé marcé la wiki.",
- 'config-db-prefix' => 'Prefiss dle tàule dla base ëd dàit:',
- 'config-db-prefix-help' => "S'a l'ha dabzògn ëd partagé na base ëd dàit an tra vàire wiki, o tra MediaWiki e n'àutra aplicassion dl'aragnà, a peul serne ëd gionté un prefiss a tùit ij nòm ëd le tàule për evité ëd conflit.
-Ch'a deuvra pa dë spassi.
-
-Cost camp a l'é lassà normalment veuid.",
- 'config-db-charset' => 'Ansema dij caràter dla base ëd dàit',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binari',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => "MySQL 4.0 compatìbil a l'andaré con UTF-8",
- 'config-charset-help' => "'''Avis:''' S'a deuvra '''UTF-8 compatìbil a l'andaré''' su MySQL 4.1+, e peui a fa na còpia con <code>mysqldump</code>, a podrìa scancelé tùit ij caràter nen-ASCII, dësbland sensa speranse soe còpie!
-
-An '''manera binaria''', MediaWiki a memorisa ël test UTF-8 an dij camp binari ant la base ëd dàit.
-Sossì a l'é pi eficient che la manera UTF-8 ëd MySQL, e a përmët ëd dovré tut l'ansema ëd caràter Unicode.
-An '''manera UTF-8''', MySQL a arconòss an che ansema ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassrà pa memorisé ij caràter dzora al [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghe ëd base].",
- 'config-mysql-old' => "A-i é da manca ëd MySQL $1 o pi recent, chiel a l'ha $2.",
- 'config-db-port' => 'Porta dla base ëd dàit:',
- 'config-db-schema' => 'Schema për MediaWiki',
- 'config-db-schema-help' => "Lë schema sì-sota a l'é ëd sòlit giust.
-Ch'a lo cangia mach s'a sa ch'a n'ha da manca.",
- 'config-pg-test-error' => "Impossìbil coleghesse a la base ëd dàit '''$1'''; $2",
- 'config-sqlite-dir' => 'Dossié dij dat SQLite:',
- 'config-sqlite-dir-help' => "SQLite a memorisa tùit ij dat ant n'archivi ùnich.
-
-Ël dossié che chiel a forniss a dev esse scrivìbil dal servent durant l'instalassion.
-
-A dovrìa '''pa''' esse acessìbil da l'aragnà, sossì a l'é për sòn ch'i l'oma pa butalo andova a-i son ij sò file PHP.
-
-L'instalador a scriverà n'archivi <code>.htaccess</code> ansema con chiel, ma se lòn a faliss quaidun a peul intré an soa base ëd dàit originaria.
-Lòn a comprend ij dat brut ëd l'utent (adrëssa ëd pòsta eletrònica, ciav tërbola) e ëdcò le revision scancelà e d'àutri dat segret ëd la wiki.
-
-Ch'a consìdera ëd buté la base ëd dàit tuta antrega da n'àutra part, për esempi an <code>/var/lib/mediawiki/yourwiki</code>.",
- '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:
-
-$1
-
-S'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-support-mysql' => "* $1 e l'é l'obietiv primari për MediaWiki e a l'é mej mantnù ([http://www.php.net/manual/en/mysql.installation.php com compilé PHP con ël manteniment MySQL])",
- 'config-support-postgres' => "* $1 e l'é un sistema ëd base ëd dàit popolar a sorgiss duverta com alternativa a MySQL ([http://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-support-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-support-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',
- 'config-header-postgres' => 'Ampostassion PostgreSQL',
- 'config-header-sqlite' => 'Ampostassion SQLite',
- 'config-header-oracle' => 'Ampostassion Oracle',
- 'config-invalid-db-type' => 'Sòrt ëd ëd base ëd dàit pa bon-a',
- 'config-missing-db-name' => 'A dev buteje un valor për "Nòm ëd la base ëd dàit"',
- 'config-missing-db-host' => 'A dev buteje un valor për "l\'òspit ëd la base ëd dàit"',
- 'config-missing-db-server-oracle' => 'A dev buteje un valor për "TNS ëd la base ëd dat"',
- 'config-invalid-db-server-oracle' => 'TNS ëd la base ëd dat pa bon "$1".
-Dovré mach dle litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e pontin (.).',
- 'config-invalid-db-name' => 'Nòm ëd la base ëd dàit pa bon "$1".
-Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e tratin (-).',
- 'config-invalid-db-prefix' => 'Prefiss dla base ëd dàit pa bon "$1".
-Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e tratin (-).',
- 'config-connection-error' => "$1.
-
-Controla l'ospitant, lë stranòm d'utent e la ciav sì-sota e prové torna.",
- 'config-invalid-schema' => 'Schema pa bon për MediaWiki "$1".
-Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9) e sotlignadure (_).',
- 'config-db-sys-create-oracle' => "L'istalador a arconòss mach ij cont SYSDBA durant la creassion d'un cont neuv.",
- 'config-db-sys-user-exists-oracle' => 'Ël cont utent "$1" a esist già. SYSDBA a peul mach esse dovrà për creé un cont neuv!',
- 'config-postgres-old' => "A-i é da manca ëd PostgreSQL $1 o pi recent, chiel a l'ha $2.",
- 'config-sqlite-name-help' => "Serne un nòm ch'a identìfica soa wiki.
-Dovré nì dë spassi nì ëd tratin.
-Sòn a sarà dovrà për ël nòm ëd l'archivi ëd dat SQLite.",
- 'config-sqlite-parent-unwritable-group' => "As peul pa creesse ël dossié ëd dat <code><nowiki>$1</nowiki></code>, përchè ël dossié a mont <code><nowiki>$2</nowiki></code> a l'é pa scrivìbil dal servent.
-
-L'instalador a l'ha determinà sota che utent a gira sò servent.
-Fé an manera che ël dossié <code><nowiki>$3</nowiki></code> a sia scrivìbil da chiel për continué.
-Su un sistema Unix/Linux buté:
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>",
- 'config-sqlite-parent-unwritable-nogroup' => "As peul pa creesse ël dossié ëd dat <code><nowiki>$1</nowiki></code>, përchè ël dossié a mont <code><nowiki>$2</nowiki></code> a l'é pa scrivìbil dal servent.
-
-L'instalador a peul pa determiné l'utent sota ël qual a gira sò servent.
-Fé an manera che ël dossié <code><nowiki>$3</nowiki></code> a sia scrivìbil globalment da chiel (e da d'àutri) për continué.
-Su un sistema Unix/Linux buté:
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>",
- 'config-sqlite-mkdir-error' => 'Eror an creand ël dossié ëd dat "$1".
-Ch\'a contròla la locassion e ch\'a preuva torna.',
- 'config-sqlite-dir-unwritable' => 'As peul pa scrivse an sël dossié "$1".
-Modifiché ij sò përmess an manera che ël servent a peula scrivje ansima, e prové torna.',
- 'config-sqlite-connection-error' => '$1.
-
-Controlé ël dossié ëd dat e ël nòm ëd la base ëd dàit ambelessì-sota e prové torna.',
- 'config-sqlite-readonly' => "L'archivi <code>$1</code> a l'é nen scrivìbil.",
- 'config-sqlite-cant-create-db' => "As peul pa cresse l'archivi ëd base ëd dàit <code>$1</code>.",
- 'config-sqlite-fts3-downgrade' => "PHP a l'ha pa ël supòrt ëd FTS3, le tàule a son degradà",
- 'config-can-upgrade' => "A-i é dle tàule MediaWiki an costa base ëd dàit.
-Për agiorneje a MediaWiki $1, ch'a sgnaca su '''Continué'''.",
- 'config-upgrade-done' => "Agiornament completà.
-
-Adess a peule [$1 ancaminé a dovré soa wiki].
-
-S'a veul generé torna sò archivi <code>LocalSettings.php</code>, ch'a sgnaca ël boton sì-sota.
-Sòn a l'è '''pa arcomandà''' gavà ch'a rancontra dij problema con soa wiki.",
- 'config-upgrade-done-no-regenerate' => 'Agiornament complet.
-
-It peule adess [$1 ancaminé a dovré toa wiki].',
- 'config-regenerate' => 'Generé torna LocalSettings.php →',
- 'config-show-table-status' => 'Arcesta <code>SHOW TABLE STATUS</code> falìa!',
- 'config-unknown-collation' => "'''Avis:''' La base ëd dàit a deuvra na classificassion pa arconossùa.",
- 'config-db-web-account' => "Cont dla base ëd dàit për l'acess a l'aragnà",
- 'config-db-web-help' => "Ch'a selession-a lë stranòm d'utent e la ciav che ël servent ëd l'aragnà a dovrërà për coleghesse al servent dle base ëd dàit, durant j'operassion ordinarie dla wiki.",
- 'config-db-web-account-same' => "Ch'a deuvra ël midem cont com për l'istalassion",
- 'config-db-web-create' => "Crea ël cont se a esist pa anco'",
- 'config-db-web-no-create-privs' => "Ël cont ch'a l'ha specificà për l'instalassion a l'ha pa basta 'd privilegi për creé un cont.
-Ël cont ch'a spessìfica ambelessì a dev già esiste.",
- 'config-mysql-engine' => 'Motor ëd memorisassion:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Avis''': A l'ha selessionà MyISAM com motor ëd memorisassion për MySQL, che a l'é pa arcomandà da dovré con MediaWiki, përchè:
-* a sopòrta a pen-a la contemporanità për via ëd saradure ëd tàula
-* a l'é pi soget a la corussion che j'àutri motor
-* ël còdes bas ëd MediaWiki pa sempe a gestiss MyISAM com a dovrìa
-
-Se soa istalassion MySQL a manten InnoDB, a l'é fortement arcomandà ch'a serna pitòst col-lì.
-Se soa istalassion MySQL a manten nen InnoDB, a peul esse ch'a sia ël moment ëd n'agiornament.",
- 'config-mysql-engine-help' => "'''InnoDB''' a l'é scasi sempe la mej opsion, da già ch'a l'ha un bon manteniment dla concorensa.
-
-'''MyISAM''' a peul esse pi lest an instalassion për n'utent sol o mach an letura.
-La base ëd dàit MyISAM a tira a corompse pi 'd soens che la base ëd dàit InnoDB.",
- 'config-mysql-charset' => 'Ansem ëd caràter dla base ëd dàit:',
- 'config-mysql-binary' => 'Binari',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "An '''manera binaria''', MediaWiki a memorisa ël test UTF-8 ant la base ëd dàit an camp binari.
-Sòn a l'é pi eficient che la manera UTF-8 ëd MySQL, e a-j përmët ëd dovré l'ansema antregh ëd caràter Unicode.
-
-An '''manera UTF-8''', MySQL a conossrà an che ansem ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassa pa memorisé ij caràter ëdzora al [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghìstich ëd base].",
- 'config-site-name' => 'Nòm ëd la wiki:',
- 'config-site-name-help' => "Sòn a comparirà ant la bara dël tìtol dël navigador e an vàire d'àutri pòst.",
- 'config-site-name-blank' => "Ch'a buta un nòm ëd sit.",
- 'config-project-namespace' => 'Spassi nominal dël proget:',
- 'config-ns-generic' => 'Proget',
- 'config-ns-site-name' => 'Midem com ël nom dla wiki: $1',
- 'config-ns-other' => 'Àutr (specìfica)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => "Andasend daré a l'esempi ëd Wikipedia, vàire wiki a manten-o soe pàgine ëd regolament separà da soe pàgine ëd contnù, ant në \"'''spassi nominal ëd proget'''\".
-Tùit ij tìtoj ëd pàgina ant cost ëspassi nominal a parto con un sert prefiss, che a peul specifiché ambelessì.
-Tradissionalment, sto prefiss a l'é derivà dal nòm ëd la wiki, ma a peul pa conten-e caràter ëd pontegiatura coma \"#\" o \":\".",
- 'config-ns-invalid' => 'Lë spassi nominal specificà "<nowiki>$1</nowiki>" a l\'é pa bon.
-Specìfica në spassi nominal ëd proget diferent.',
- 'config-ns-conflict' => 'Lë spassi nominal specificà "<nowiki>$1</nowiki>" a và contra në spassi nominal predefinì ëd MediaWiki.
-Specìfica në spassi nominal ëd proget diferent.',
- 'config-admin-box' => "Cont ëd l'Aministrator",
- 'config-admin-name' => 'Tò nòm:',
- 'config-admin-password' => 'Ciav:',
- 'config-admin-password-confirm' => 'Buté torna la ciav:',
- 'config-admin-help' => "Ch'a butà ambelessì tò stranòm d'utent preferì, për esempi \"Gioann Scriv\".
-Cost-sì a l'é lë stranòm ch'a dovrërà për intré ant la wiki.",
- 'config-admin-name-blank' => "Ch'a anserissa në stranòm d'aministrator.",
- 'config-admin-name-invalid' => 'Ël nòm utent specificà "<nowiki>$1</nowiki>" a l\'é pa bon.
-Specìfica un nòm utent diferent.',
- 'config-admin-password-blank' => "Ch'a anserissa na ciav për ël cont d'aministrator.",
- 'config-admin-password-same' => "La ciav a dev nen esse l'istessa ëd lë stranòm d'utent.",
- 'config-admin-password-mismatch' => "Le doe ciav che a l'ha scrivù a son diferente antra 'd lor.",
- 'config-admin-email' => 'Adrëssa ëd pòsta eletrònica:',
- 'config-admin-email-help' => "Ch'a anserissa ambelessì n'adrëssa ëd pòsta eletrònica për përmëtt-je d'arsèive ëd mëssagi da d'àutri utent an sla wiki, riamposté soa ciav, e esse anformà dle modìfiche a le pàgine ch'a ten sot-euj. A peule lassé ës camp veuid.",
- 'config-admin-error-user' => 'Eror antern an creand n\'aministrator con lë stranòm "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Eror antern an ampostand na ciav për l\'admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => "A l'ha butà n'adrëssa ëd pòsta eletrònica pa bon-a.",
- 'config-subscribe' => "Ch'a sot-scriva la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista ëd discussion ëd j'anonsi ëd publicassion].",
- 'config-subscribe-help' => "Costa a l'é na lista ëd discussion a bass tràfich dovrà për j'anonsi ëd publicassion, comprèis d'amportant anonsi ëd sicurëssa.
-A dovrìa sot-ëscrivla e agiorné soa instalassion mediaWiki quand che ëd version neuve a rivo.",
- 'config-subscribe-noemail' => "A l'ha provà a abonesse a la lista ëd difusion dij comunicà sensa dé n'adrëssa ëd pòsta eletrònica.
-Për piasì, ch'a fornissa n'adrëssa ëd pòsta eletrònica s'a veul abonesse a la lista ëd pòsta.",
- 'config-almost-done' => "A l'ha bele che fàit!
-A peul adess sauté la configurassion rimanenta e instalé dlongh la wiki.",
- 'config-optional-continue' => "Ciameme d'àutre chestion.",
- 'config-optional-skip' => 'I son già anojà, instala mach la wiki.',
- 'config-profile' => "Profil dij drit d'utent:",
- 'config-profile-wiki' => 'Wiki duverta',
- 'config-profile-no-anon' => 'A venta creé un cont',
- 'config-profile-fishbowl' => 'Mach editor autorisà',
- 'config-profile-private' => 'Wiki privà',
- 'config-profile-help' => "Le wiki a marcio mej quand ch'a lassa che pì përsone possìbij a-j modìfico.
-An MediaWiki, a l'é bel fé revisioné j'ùltime modìfiche, e buté andré qualsëssìa dann che a sia fàit da dj'utent noviss o malissios.
-
-An tùit ij cas, an tanti a l'han trovà che MediaWiki a sia ùtil ant na gran varietà ëd manere, e dle vire a l'é pa bel fé convince cheidun dij vantagi dla wiki.
-Parèj a l'ha doe possibilità.
-
-Ël model '''{{int:config-profile-wiki}}''' a përmët a chicassìa ëd modifiché, bele sensa intré ant ël sistema.
-Na wiki con '''{{int:config-profile-no-anon}}''' a dà pì 'd contròl, ma a peul slontané dij contributor ocasionaj.
-
-Ël senari '''{{int:config-profile-fishbowl}}''' a përmët a j'utent aprovà ëd modifiché, ma ël pùblich a peul vëdde le pàgine, comprèisa la stòria.
-Un '''{{int:config-profile-private}}''' a përmët mach a j'utent aprovà ëd vëdde le pàgine, con la midema partìa ch'a peul modifiché.
-
-Configurassion ëd drit d'utent pi complicà a son disponìbij apress l'instalassion, vëdde la [//www.mediawiki.org/wiki/Manual:User_rights pàgina a pòsta dël manual].",
- 'config-license' => "Drit d'autor e licensa",
- 'config-license-none' => 'Gnun-a licensa an nòta an bass',
- 'config-license-cc-by-sa' => 'Creative Commons atribussion an part uguaj',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons atribussion nen comersial an part uguaj',
- 'config-license-cc-0' => 'Creative Commons Zero (domini pùblich)',
- 'config-license-gfdl' => 'Licensa GNU Free Documentation 1.3 o pi neuva',
- 'config-license-pd' => 'Domini Pùblich',
- 'config-license-cc-choose' => 'Selessioné na licensa Creative Commons përsonalisà',
- 'config-license-help' => "Vàire wiki pùbliche a buto tute le contribussion sota na [http://freedomdefined.org/Definition licensa lìbera]. Sòn a giuta a creé un sens d'apartenensa a la comunità e a ancoragia ëd contribussion ëd longa durà.
-A l'é generalment nen necessari për na wiki privà o d'asienda.
-
-S'a veul podèj dovré dij test da Wikipedia, e a veul che Wikipedia a aceta dij test copià da soa wiki, a dovrìa serne '''Creative Commons Attribution Share Alike'''.
-
-Wikipedia prima a dovrava la GNU Free Documentation License.
-La GDFL a l'é anco' na licensa bon-a, ma a l'é malfé da capila.
-A l'é ëdcò mal fé riutilisé dël contnù licensià sota la GDFL.",
- 'config-email-settings' => 'Ampostassion ëd pòsta eletrònica',
- 'config-enable-email' => 'Abilité ij mëssagi ëd pòsta eletrònica an surtìa',
- 'config-enable-email-help' => "S'a veul che la pòsta eletrònica a marcia, j'[http://www.php.net/manual/en/mail.configuration.php ampostassion ëd pòsta eletrònica PHP] a devo esse configurà për da bin.
-S'a veul pa 'd funsion ëd pòsta eletrònica, a dev disabiliteje ambelessì.",
- 'config-email-user' => 'Abilité ij mëssagi ëd pòsta eletrònica da utent a utent',
- 'config-email-user-help' => "A përmët a tùit j'utent ëd mandesse ëd mëssagi ëd pòsta eletrònica se lor a l'han abilità sòn an soe preferense.",
- 'config-email-usertalk' => "Abilité notìfica dle pàgine ëd discussion dj'utent",
- 'config-email-usertalk-help' => "A përmët a j'utent d'arsèive na notìfica dle modìfiche dle pàgine ëd discussion d'utent, s'a l'han abilitalo ant soe preferense.",
- 'config-email-watchlist' => "Abilité la notìfica ëd lòn ch'as ten sot euj",
- 'config-email-watchlist-help' => "A përmët a j'utent d'arsèive dle notificassion a propòsit dle pàgine ch'a ten-o sot euj s'a l'han abilitalo ant soe preferense.",
- 'config-email-auth' => "Abilité l'autenticassion për pòsta eletrònica",
- 'config-email-auth-help' => "Se st'opsion a l'é abilità, j'utent a devo confirmé soe adrësse ëd pòsta eletrònica an dovrand un colegament mandà a lor quand ch'a l'han ampostala o cambiala.
-Mach j'adrësse ëd pòsta eletrònica autenticà a peulo arsèive ëd mëssagi da j'àutri utent o cangé adrëssa ëd notìfica.
-Amposté st'opsion a l'é '''arcomandà''' për le wiki pùbliche a càusa ëd possìbij abus ëd le funsion ëd pòsta eletrònica.",
- 'config-email-sender' => 'Adrëssa ëd pòsta eletrònica ëd ritorn:',
- 'config-email-sender-help' => "Ch'a anserissa l'adrëssa ëd pòsta eletrònica da dovré com adrëssa d'artorn dij mëssagi an surtìa.
-Ambelessì a l'é andova j'arspòste a saran mandà.
-Motobin ëd servent ëd pòsta a ciamo che almanch la part dël nòm ëd domini a sia bon-a.",
- 'config-upload-settings' => 'Cariament ëd figure e archivi',
- 'config-upload-enable' => "Abilité ël cariament d'archivi",
- 'config-upload-help' => "Carié d'archivi potensialment a espon sò servent a d'arzigh ëd sicurëssa.
-Per pi d'anformassion, ch'a lesa la [//www.mediawiki.org/wiki/Manual:Security session ëd sicurëssa] d'ës manual.
-
-Për abilité ël cariament d'archivi, ch'a modìfica la manera dël sot-dossié dle <code>figure</code> sota al dossié rèis ëd MediaWiki an manera che ël servent dl'aragnà a peussa scrivlo.
-Peui ch'a abìlita costa opsion.",
- 'config-upload-deleted' => "Dossié për j'archivi scancelà:",
- 'config-upload-deleted-help' => "ch'a serna un dossié andova goerné j'archivi scancelà.
-Idealment, sòn a dovrìa pa esse acessìbil an sl'aragnà.",
- 'config-logo' => 'Anliura dla marca:',
- 'config-logo-help' => "La pel dë stàndard ëd MediaWiki a comprend lë spassi për na marca ëd 135x160 pontin dzora la lista dla bara lateral.
-Ch'a dëscaria na figura ëd la dimension aproprià, e ch'a anserissa l'anliura ambelessì.
-
-S'a veul gnun-e marche, ch'a lassa ës camp bianch.", # Fuzzy
- 'config-instantcommons' => 'Abìlita Instant Commons',
- 'config-instantcommons-help' => "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] a l'é na funsion ch'a përmët a le wiki ëd dovré dle figure, dij son e d'àutri mojen trovà an sël sit [//commons.wikimedia.org/ Wikimedia Commons].
-Për dovré sossì, MediaWiki a l'ha da manca dl'acess a la ragnà.
-
-Për pi d'anformassion su sta funsion, comprèise j'istrussion ëd com ampostela për wiki diferente da Wikimedia Commons, ch'a consulta [//mediawiki.org/wiki/Manual:\$wgForeignFileRepos ël manual].",
- 'config-cc-error' => "La selession ëd la licensa Creative Commons a l'ha dàit gnun arzultà.
-Ch'a anserissa ël nòm dla licensa a man.",
- 'config-cc-again' => 'Torna cheuje...',
- 'config-cc-not-chosen' => 'Sern che licensa Creative Commons it veule e sgnaca "anans".',
- 'config-advanced-settings' => 'Configurassion avansà',
- 'config-cache-options' => "Ampostassion për la memorisassion local d'oget:",
- 'config-cache-help' => "La memorisassion loca d'oget a l'é dovrà për amelioré l'andi ëd MediaWiki an butant an local dij dat dovrà 'd soens.
-Ij sit da mesan a gròss a son motobin ancoragià a abilité sòn, e ij sit cit a l'avran ëdcò dij benefissi.",
- 'config-cache-none' => "Gnun-a memorisassion local (gnun-a funsionalità gavà, ma l'andi a peul esse anfluensà an sij sit ëd wiki gròsse)",
- 'config-cache-accel' => "Memorisassion local d'oget PHP (APC, XCache o WinCache)",
- 'config-cache-memcached' => "Dovré Memcached (a ciama n'ampostassion e na configurassion adissionaj)",
- 'config-memcached-servers' => 'Servent Memcached:',
- 'config-memcached-help' => "Lista d'adrësse IP da dovré për Memcached.
-A dovrìa specifichene un-a për linia e specifiché la pòrta da dovré. Për esempi:
-127.0.0.1:11211
-192.168.1.25:11211",
- 'config-memcache-needservers' => "A l'ha selessionà Memcached com soa sòrt ëd memorisassion local ma a l'ha specificà gnun servent.",
- 'config-memcache-badip' => "It l'ha anserì n'adrëssa IP pa bon-a për Memcached: $1.",
- 'config-memcache-noport' => "A l'ha pa specificà na pòrta da dovré për ël servent Memcached: $1.
-S'a conòsse nen la pòrta, cola predefinìa a l'é 11211.",
- 'config-memcache-badport' => 'Ij nùmer ëd pòrta ëd Memcached a dovrìo esse tra $1 e $2.',
- 'config-extensions' => 'Estension',
- 'config-extensions-help' => "J'estension listà dì-sota a son ëstàite trovà ant sò dossié <code>./extensions</code>.
-
-A peulo avèj da manca ëd configurassion adissionaj, ma a peul abiliteje adess",
- 'config-install-alreadydone' => "'''Avis''' A smija ch'a l'abie già instalà MediaWiki e ch'a preuva a instalelo torna.
-Për piasì, ch'a vada a la pàgina ch'a-i ven.",
- 'config-install-begin' => "An sgnacand su «{{int:config-continue}}», a anandiërà l'istalassion ëd MediaWiki.
-S'a veul anco' fé dle modìfiche, ch'a sgnaca su «{{int:config-back}}».",
- 'config-install-step-done' => 'fàit',
- 'config-install-step-failed' => 'falì',
- 'config-install-extensions' => "Comprende j'estension",
- 'config-install-database' => 'Creassion ëd la base ëd dàit',
- 'config-install-schema' => 'Creassion dë schema',
- 'config-install-pg-schema-not-exist' => 'Lë schema postgreSQL a esist pa.',
- 'config-install-pg-schema-failed' => 'Creassion dle tàule falìa.
-Sigurte che l\'utent "$1" a peussa scrive lë schema "$2".',
- 'config-install-pg-commit' => 'Salvé ij cambi.',
- 'config-install-pg-plpgsql' => 'Contròl dël langagi PL/pgSQL',
- 'config-pg-no-plpgsql' => 'A dev istalé ël langage PL/pgSQL ant la base ëd dàit $1',
- 'config-pg-no-create-privs' => "Ël cont ch'a l'ha specificà për l'istalassion a l'ha pa basta 'd privilegi për creé un cont.",
- 'config-pg-not-in-role' => "Ël cont ch'a l'ha specificà për l'utent ëd la ragnà a esist già.
-Ël cont ch'a l'has specificà për l'istalassion a l'é pa un superutent e a l'é pa un mémber dla partìa dj'utent dla Ragnà, parèj a peul pa creé dj'oget ch'a apartenent a l'utent dla Ragnà.
-
-MediaWiki al moment a ciama che le tàule a sia possedùe da n'utent dla Ragnà. Për piasì, ch'a specìfica n'àutr nòm ëd cont dla Ragnà, o ch'a sgnaca ansima a \"andré\" e ch'a specìfica n'utent ch'a l'ha ij privilegi ch'a basto për l'anstalassion.",
- 'config-install-user' => "Creassion ëd n'utent ëd la base ëd dàit",
- 'config-install-user-alreadyexists' => 'L\'utent "$1" a esist già',
- 'config-install-user-create-failed' => "Faliment ant la creassion ëd l'utent «$1»: $2",
- 'config-install-user-grant-failed' => 'Falì a dé ij përmess a l\'utent "$1": $2',
- 'config-install-user-missing' => 'L\'utent specificà "$1" a esist pa.',
- 'config-install-user-missing-create' => "L'utent specificà «$1» a esist pa.
-Për piasì, ch'a selession-a la casela «cont da creé» sì-sota s'a veul creelo.",
- 'config-install-tables' => 'Creassion dle tàule',
- 'config-install-tables-exist' => "'''Avis''': A smija che le tàule ëd mediaWiki a esisto già.
-Sauté la creassion.",
- 'config-install-tables-failed' => "'''Eror''': Creassion ëd le tàule falìa con l'eror sì-dapress: $1",
- 'config-install-interwiki' => "Ampiniment dë stàndard ëd le tàule dj'anliure interwiki",
- 'config-install-interwiki-list' => "As peul pa trovesse l'archivi <code>interwiki.list</code>.",
- 'config-install-interwiki-exists' => "'''Avis''': La tàula interwiki a smija ch'a l'abia già dj'element.
-Për stàndard, la lista a sarà sautà.",
- 'config-install-stats' => 'Inissialisassion dle statìstiche',
- 'config-install-keys' => 'Generassion ëd le ciav segrete',
- 'config-insecure-keys' => "'''Avis:''' {{PLURAL:$2|Na ciav sigura|Dle ciav sigure}} ($1) generà durant l'istalassion {{PLURAL:$2|a l'é|a son}} pa completament sigure. Ch'a consìdera ëd modifiche{{PLURAL:$2|la|je}} manualment.",
- 'config-install-sysop' => "Creassion dël cont ëd l'utent aministrator",
- 'config-install-subscribe-fail' => 'As peul pa sot-scrivse mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => "cURL a l'é pa istalà e allow_url_fopen a l'é pa disponìbil.",
- 'config-install-mainpage' => 'Creassion ëd la pàgina prinsipal con un contnù predefinì',
- 'config-install-extension-tables' => "Creassion ëd tàule për j'estension abilità",
- 'config-install-mainpage-failed' => 'As peul pa inserisse la pàgina prinsipal: $1',
- 'config-install-done' => "'''Congratulassion!'''
-A l'ha instalà për da bin mediaWiki.
-
-L'instalador a l'ha generà n'archivi <code>LocalSettings.php</code>.
-A conten tuta soa configurassion.
-
-A dovrà dëscarielo e butelo ant la bas ëd l'instalassion ëd soa wiki (ël midem dossié d'index.php). La dëscaria a dovrìa esse ancaminà automaticament.
-
-Se la dëscaria a l'é pa disponìbil, o s'a l'ha scancelala, a peul torna ancaminé la dëscaria an sgnacand an sla liura sì-sota:
-
-$3
-
-'''Nòta''': S'a lo fa nen adess, cost archivi ëd configurassion generà a sarà pa disponìbil për chiel pi tard s'a chita l'instalassion sensa dëscarielo.
-
-Quand che a l'é stàit fàit, a peul '''[$2 intré an soa wiki]'''.",
- 'config-download-localsettings' => 'Dëscarié <code>LocalSettings.php</code>',
- 'config-help' => 'agiut',
- 'config-nofile' => "L'archivi «$1» as treuva nen. A l'é stàit ëscancelà?",
- 'mainpagetext' => "'''MediaWiki a l'é staita anstalà a la përfession.'''",
- 'mainpagedocfooter' => "Che a varda la [//meta.wikimedia.org/wiki/Help:Contents User's Guide] për avèj dj'anformassion ant sël coma dovré ël programa dla wiki.
-
-== Për anandiesse a travajé ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dij paràmeter ëd configurassion]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Chestion frequente]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista ëd discussion an sla distribussion ëd MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localisa MediaWiki për toa lenga]",
-);
-
-/** Pontic (Ποντιακά)
- * @author Sinopeus
- */
-$messages['pnt'] = array(
- 'mainpagetext' => "'''To λογισμικόν MediaWiki εθέκεν.'''",
-);
-
-/** Prussian (Prūsiskan)
- * @author Nertiks
- */
-$messages['prg'] = array(
- 'mainpagetext' => "'''MediaWiki's instalaciōni izpalla.'''",
- 'mainpagedocfooter' => 'Wīdais [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] kāi gaūlai infōrmaciōnei ezze wiki prōgramijas tērpausnan.
-
-== En pagaūseņu ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]', # Fuzzy
-);
-
-/** Pashto (پښتو)
- * @author Ahmed-Najib-Biabani-Ibrahimkhel
- */
-$messages['ps'] = array(
- 'config-information' => 'مالومات',
- 'config-your-language' => 'ستاسې ژبه:',
- 'config-wiki-language' => 'د ويکي ژبه:',
- 'config-page-language' => 'ژبه',
- 'config-page-welcome' => 'مېډياويکي ته ښه راغلاست!',
- 'config-page-name' => 'نوم',
- 'config-page-options' => 'خوښنې',
- 'config-page-install' => 'لگول',
- 'config-page-complete' => 'بشپړ!',
- 'config-env-php' => 'د $1 PHP نصب شو.',
- 'config-db-type' => 'د توکبنسټ ډول:',
- 'config-db-host' => 'د توکبنسټ کوربه:',
- 'config-db-host-oracle' => 'د توکبنسټ TNS:',
- 'config-db-name' => 'د توکبنسټ نوم:',
- 'config-db-username' => 'د توکبنسټ کارن-نوم:',
- 'config-db-password' => 'د توکبنسټ پټنوم:',
- 'config-header-mysql' => 'د MySQL امستنې',
- 'config-header-postgres' => 'د PostgreSQL امستنې',
- 'config-header-sqlite' => 'د SQLite امستنې',
- 'config-header-oracle' => 'د اورېکل امستنې',
- 'config-sqlite-readonly' => 'د <code>$1</code> دوتنه د ليکلو وړ نه ده.',
- 'config-sqlite-cant-create-db' => 'د توکبنسټ دوتنه <code>$1</code> جوړه نه شوه.',
- 'config-site-name' => 'د ويکي نوم:',
- 'config-site-name-blank' => 'د وېبځي نوم وليکۍ.',
- 'config-project-namespace' => 'د پروژې نوم-تشيال:',
- 'config-ns-generic' => 'پروژه',
- 'config-admin-box' => 'د پازوال گڼون',
- 'config-admin-name' => 'ستاسې نوم:',
- 'config-admin-password' => 'پټنوم:',
- 'config-admin-password-confirm' => 'پټنوم يو ځل بيا:',
- 'config-admin-email' => 'برېښليک پته:',
- 'config-profile-wiki' => 'پرانيستې ويکي',
- 'config-license-pd' => 'ټولگړی شپول',
- 'config-email-settings' => 'د برېښليک امستنې',
- 'config-install-step-done' => 'ترسره شو',
- 'config-install-tables' => 'لښتيالونه جوړول',
- 'config-help' => 'لارښود',
- 'mainpagetext' => "'''MediaWiki په برياليتوب سره نصب شو.'''",
- 'mainpagedocfooter' => 'د ويکي ساوترې د کارولو د مالوماتو په اړه [//meta.wikimedia.org/wiki/Help:Contents د کارن لارښود] سره سلا وکړۍ.
-
-== پيلول ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings د امستنو د سازونې لړليک]
-* [//www.mediawiki.org/wiki/Manual:FAQ د ميډياويکي ډېرځليزې پوښتنې]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce د مېډياويکي د برېښليکونو لړليک]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources خپلې ژبې لپاره MediaWiki ځايتابول]',
-);
-
-/** Portuguese (português)
- * @author Crazymadlover
- * @author Hamilton Abreu
- * @author Luckas
- * @author Mormegil
- * @author Platonides
- * @author SandroHc
- * @author Waldir
- * @author 아라
- * @author 555
- */
-$messages['pt'] = array(
- 'config-desc' => 'O instalador do MediaWiki',
- 'config-title' => 'Instalação MediaWiki $1',
- 'config-information' => 'Informação',
- 'config-localsettings-upgrade' => 'Foi detectado um ficheiro <code>LocalSettings.php</code>.
-Para atualizar esta instalação, por favor introduza o valor de <code>$wgUpgradeKey</code> na caixa abaixo.
-Encontra este valor no <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Foi detectada a existência de um ficheiro <code>LocalSettings.php</code>.
-Para atualizar esta instalação execute o <code>update.php</code>, por favor.',
- 'config-localsettings-key' => 'Chave de atualização:',
- 'config-localsettings-badkey' => 'A chave que forneceu está incorreta.',
- 'config-upgrade-key-missing' => 'Foi detectada uma instalação existente do MediaWiki.
-Para atualizar esta instalação, por favor coloque a seguinte linha no final do seu <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'O ficheiro <code>LocalSettings.php</code> existente parece estar incompleto.
-A variável $1 não está definida.
-Por favor defina esta variável no <code>LocalSettings.php</code> e clique "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Ocorreu um erro ao ligar à base de dados usando as configurações especificadas no <code>LocalSettings.php</code> ou <code>AdminSettings.php</code>. Por favor corrija essas configurações e tente novamente.
-
-$1',
- 'config-session-error' => 'Erro ao iniciar a sessão: $1',
- 'config-session-expired' => 'Os seus dados de sessão parecem ter expirado.
-As sessões estão configuradas para uma duração de $1.
-Pode aumentar esta duração configurando <code>session.gc_maxlifetime</code> no php.ini.
-Reinicie o processo de instalação.',
- 'config-no-session' => 'Os seus dados de sessão foram perdidos!
-Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um diretório apropriado.',
- 'config-your-language' => 'A sua língua:',
- 'config-your-language-help' => 'Selecione a língua que será usada durante o processo de instalação.',
- 'config-wiki-language' => 'Língua da wiki:',
- 'config-wiki-language-help' => 'Selecione a língua que será predominante na wiki.',
- 'config-back' => '← Voltar',
- 'config-continue' => 'Continuar →',
- 'config-page-language' => 'Língua',
- 'config-page-welcome' => 'Bem-vindo(a) ao MediaWiki!',
- 'config-page-dbconnect' => 'Ligar à base de dados',
- 'config-page-upgrade' => 'Atualizar a instalação existente',
- 'config-page-dbsettings' => 'Configurações da base de dados',
- 'config-page-name' => 'Nome',
- 'config-page-options' => 'Opções',
- 'config-page-install' => 'Instalar',
- 'config-page-complete' => 'Terminado!',
- 'config-page-restart' => 'Reiniciar a instalação',
- 'config-page-readme' => 'Leia-me',
- 'config-page-releasenotes' => 'Notas de lançamento',
- 'config-page-copying' => 'A copiar',
- 'config-page-upgradedoc' => 'Atualizando',
- 'config-page-existingwiki' => 'Wiki existente',
- 'config-help-restart' => 'Deseja limpar todos os dados gravados que introduziu e reiniciar o processo de instalação?',
- 'config-restart' => 'Sim, reiniciar',
- 'config-welcome' => '=== Verificações do ambiente ===
-São realizadas verificações básicas para determinar se este ambiente é apropriado para instalação do MediaWiki.
-Se necessitar de pedir ajuda durante a instalação, deve fornecer os resultados destas verificações.', # Fuzzy
- 'config-copyright' => "=== Direitos de autor e Condições de uso ===
-
-$1
-
-Este programa é software livre; pode redistribuí-lo e/ou modificá-lo nos termos da licença GNU General Public License, tal como publicada pela Free Software Foundation; tanto a versão 2 da Licença, como (por opção sua) qualquer versão posterior.
-
-Este programa é distribuído na esperança de que seja útil, mas '''sem qualquer garantia'''; inclusive, sem a garantia implícita da '''possibilidade de ser comercializado''' ou de '''adequação para qualquer finalidade específica'''.
-Consulte a licença GNU General Public License para mais detalhes.
-
-Em conjunto com este programa deve ter recebido <doclink href=Copying>uma cópia da licença GNU General Public License</doclink>; se não a recebeu, peça-a por escrito a Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA ou [http://www.gnu.org/copyleft/gpl.html leia-a na internet].",
- 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/pt Página principal do MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents/pt Ajuda]
-* [//www.mediawiki.org/wiki/Manual:Contents/pt Manual técnico]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>Leia-me</doclink>
-* <doclink href=ReleaseNotes>Notas de lançamento</doclink>
-* <doclink href=Copying>Cópia</doclink>
-* <doclink href=UpgradeDoc>Atualização</doclink>',
- 'config-env-good' => 'O ambiente foi verificado.
-Pode instalar o MediaWiki.',
- 'config-env-bad' => 'O ambiente foi verificado.
-Não pode instalar o MediaWiki.',
- 'config-env-php' => 'O PHP $1 está instalado.',
- 'config-env-php-toolow' => 'O PHP $1 está instalado.
-No entanto, o MediaWiki requer o PHP $2 ou superior.',
- 'config-unicode-using-utf8' => 'A usar o utf8_normalize.so, por Brion Vibber, para a normalização Unicode.',
- 'config-unicode-using-intl' => 'A usar a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.',
- 'config-unicode-pure-php-warning' => "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode. Irá recorrer-se à implementação em PHP puro, que é mais lenta.
-Se o seu site tem alto volume de tráfego, devia informar-se um pouco sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations/pt normalização Unicode].",
- 'config-unicode-update-warning' => "'''Aviso''': A versão instalada do wrapper de normalização Unicode usa uma versão mais antiga da biblioteca do [http://site.icu-project.org/ projeto ICU].
-Devia [//www.mediawiki.org/wiki/Unicode_normalization_considerations atualizá-la] se tem quaisquer preocupações sobre o uso do Unicode.",
- 'config-no-db' => "Não foi possível encontrar um controlador ''(driver)'' apropriado para a base de dados! Precisa de instalar um controlador para o PHP. São aceites os seguintes tipos de base de dados: $1.
-
-Se usa alojamento partilhado, peça ao fornecedor do alojamento para instalar um controlador apropriado.
-Se foi você quem compilou o PHP, reconfigure-o com um cliente de base de dados ativado; por exemplo, usando <code>./configure --with-mysql</code>.
-Se instalou o PHP a partir de um pacote Debian ou Ubuntu, então precisa de instalar também o módulo php5-mysql.",
- 'config-outdated-sqlite' => "'''Aviso''': Tem a versão $1 do SQLite, que é anterior à versão mínima necessária, a $2. O SQLite não estará disponível.",
- 'config-no-fts3' => "'''Aviso''': O SQLite foi compilado sem o módulo [//sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
- 'config-register-globals' => "'''Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está ativada.'''
-'''Desative-a, se puder.'''
-O MediaWiki funciona mesmo assim, mas o seu servidor está exposto a potenciais vulnerabilidades de segurança.",
- 'config-magic-quotes-runtime' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está ativada!'''
-Esta opção causa corrupção dos dados de entrada, de uma forma imprevisível.
-Não pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
- 'config-magic-quotes-sybase' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está ativada!'''
-Esta opção causa corrupção dos dados de entrada, de uma forma imprevisível.
-Não pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
- 'config-mbstring' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está ativada!'''
-Esta opção causa erros e pode corromper os dados de uma forma imprevisível.
-Não pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
- 'config-ze1' => "'''Fatal: A opção [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] está ativada!'''
-Esta opção causa problemas significativos no MediaWiki.
-Não pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
- 'config-safe-mode' => "'''Aviso:''' O [http://www.php.net/features.safe-mode safe mode] do PHP está ativo.
-Este modo pode causar problemas, especialmente no upload de ficheiros e no suporte a <code>math</code>.",
- 'config-xml-bad' => 'Falta o módulo XML do PHP.
-O MediaWiki necessita de funções deste módulo e não funcionará com esta configuração.
-Se está a executar o Mandrake, instale o pacote php-xml.',
- 'config-pcre' => 'Parece faltar o módulo de suporte PCRE.
-Para funcionar, o MediaWiki necessita das funções de expressões regulares compatíveis com Perl.',
- 'config-pcre-no-utf8' => "'''Fatal''': O módulo PCRE do PHP parece ter sido compilado sem suporte PCRE_UTF8.
-O 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' => "'''Aviso:''' A configuração <code>memory_limit</code> do PHP é $1.
-Isto é provavelmente demasiado baixo.
-A instalação poderá falhar!",
- 'config-ctype' => "'''Fatal''': O PHP tem de ser compilado com suporte para a [http://www.php.net/manual/en/ctype.installation.php extensão Ctype].",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] instalada',
- 'config-apc' => '[http://www.php.net/apc APC] instalada',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] instalada',
- 'config-no-cache' => "'''Aviso:''' Não foi possível encontrar: [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], nem [http://www.iis.net/download/WinCacheForPhp WinCache].
-A cache de objetos não está ativada.",
- 'config-mod-security' => "'''Aviso''': O seu servidor de internet tem o [http://modsecurity.org/ mod_security] ativado. Se este estiver mal configurado, pode causar problemas ao MediaWiki ou a outros programas, permitindo que os utilizadores publiquem conteúdos arbitrários.
-Consulte a [http://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 GNU diff3 não foi encontrado.',
- 'config-imagemagick' => 'Foi encontrado o ImageMagick: <code>$1</code>.
-Se possibilitar uploads, a miniaturização de imagens será ativada.',
- 'config-gd' => 'Foi encontrada a biblioteca gráfica GD.
-Se possibilitar uploads, a miniaturização de imagens será ativada.',
- 'config-no-scaling' => 'Não foi encontrada a biblioteca gráfica GD nem o ImageMagick.
-A miniaturização de imagens será desativada.',
- 'config-no-uri' => "'''Erro:''' Não foi possível determinar a URI atual.
-A instalação foi abortada.",
- 'config-no-cli-uri' => "'''Aviso''': Não foi especificado um --scriptpath; por omissão, será usado: <code>$1</code>.",
- 'config-using-server' => 'Será usado o nome do servidor "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Será usada a URL do servidor "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Aviso:''' O diretório por omissão para uploads <code>$1</code>, está vulnerável à execução arbitrária de scripts.
-Embora o MediaWiki verifique a existência de ameaças de segurança em todos os ficheiros enviados, é altamente recomendado que [//www.mediawiki.org/wiki/Manual:Security#Upload_security vede esta vulnerabilidade de segurança] antes de possibilitar uploads.",
- 'config-no-cli-uploads-check' => "'''Aviso:''' O diretório por omissão para uploads, <code>\$1</code>, não é verificado para determinar se é vulnerável à execução de código arbitrário durante a instalação por CLI (\"Command-line Interface\").",
- 'config-brokenlibxml' => 'O seu sistema tem uma combinação de versões de PHP e libxml2 conhecida por ser problemática, podendo causar corrupção de dados no MediaWiki e outras aplicações da internet.
-Atualize para o PHP versão 5.2.9 ou posterior e libxml2 versão 2.7.3 ou posterior ([//bugs.php.net/bug.php?id=45996 incidência reportada no PHP]).
-Instalação interrompida.',
- 'config-using531' => 'O MediaWiki não pode ser usado com o PHP $1 devido a um problema que envolve parâmetros de referência para <code>__call()</code>.
-Para resolver este problema, atualize o PHP para a versão 5.3.2 ou posterior, ou reverta-o para a 5.3.0.
-Instalação interrompida.',
- 'config-suhosin-max-value-length' => 'O Suhosin está instalado e limita a $1 bytes o comprimento do parâmetro GET. O componente ResourceLoader do MediaWiki pode tornear este limite, mas prejudicando o desempenho. Se lhe for possível, deve atribuir o valor 1024 ou maior ao parâmetro <code>suhosin.get.max_value_length</code> no ficheiro <code>php.ini</code>, e definir o mesmo valor para <code>$wgResourceLoaderMaxQueryLength</code> no ficheiro LocalSettings.php.', # Fuzzy
- 'config-db-type' => 'Tipo da base de dados:',
- 'config-db-host' => 'Servidor da base de dados:',
- 'config-db-host-help' => 'Se a base de dados estiver num servidor separado, introduza aqui o nome ou o endereço IP desse servidor.
-
-Se estiver a usar um servidor partilhado, o fornecedor do alojamento deve fornecer o nome do servidor na documentação.
-
-Se está a fazer a instalação num servidor Windows com MySQL, usar como nome do servidor "localhost" poderá não funcionar. Se não funcionar, tente usar "127.0.0.1" como endereço IP local.
-
-Se estiver a usar PostgreSQL, deixe este campo em branco para fazer a ligação através de um socket Unix.',
- 'config-db-host-oracle' => 'TNS (Transparent Network Substrate) da base de dados:',
- 'config-db-host-oracle-help' => 'Introduza um [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Nome Local de Ligação] válido; tem de estar visível para esta instalação um ficheiro tnsnames.ora.<br />Se está a usar bibliotecas cliente versão 10g ou posterior, também pode usar o método [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Ligação Fácil] de atribuição do nome.',
- 'config-db-wiki-settings' => 'Identifique esta wiki',
- 'config-db-name' => 'Nome da base de dados:',
- 'config-db-name-help' => 'Escolha um nome para identificar a sua wiki.
-O nome não deve conter espaços.
-
-Se estiver a usar um servidor partilhado, o fornecedor do alojamento deve poder fornecer-lhe o nome de uma base de dados que possa usar, ou permite-lhe criar bases de dados através de um painel de controle.',
- 'config-db-name-oracle' => "Esquema ''(schema)'' da base de dados:",
- 'config-db-account-oracle-warn' => "Há três cenários suportados na instalação do servidor de base de dados Oracle:
-
-Se pretende criar a conta de acesso pela internet na base de dados durante o processo de instalação, forneça como conta para a instalação uma conta com o papel de SYSDBA na base de dados e especifique as credenciais desejadas para a conta de acesso pela internet. Se não pretende criar a conta de acesso pela internet durante a instalação, pode criá-la manualmente e fornecer só essa conta para a instalação (se ela tiver as permissões necessárias para criar os objetos do esquema ''(schema)''). A terceira alternativa é fornecer duas contas diferentes; uma com privilégios de criação e outra com privilégios limitados para o acesso pela internet.
-
-Existe um script para criação de uma conta com os privilégios necessários no diretório \"maintenance/oracle/\" desta instalação. Mantenha em mente que usar uma conta com privilégios limitados impossibilita todas as operações de manutenção com a conta padrão.",
- 'config-db-install-account' => 'Conta do utilizador para a instalação',
- 'config-db-username' => 'Nome do utilizador da base de dados:',
- 'config-db-password' => 'Palavra-chave do utilizador da base de dados:',
- 'config-db-password-empty' => 'Introduza a palavra-chave do novo utilizador da base de dados: $1.
-Embora seja possível criar utilizadores sem palavra-chave, fazê-lo não é seguro.',
- 'config-db-install-username' => 'Introduza o nome de utilizador que será usado para aceder à base de dados durante o processo de instalação. Este utilizador não é o do MediaWiki; é o utilizador da base de dados.',
- 'config-db-install-password' => 'Introduza a palavra-chave do utilizador que será usado para aceder à base de dados durante o processo de instalação. Esta palavra-chave não é a do utilizador do MediaWiki; é a palavra-chave do utilizador da base de dados.',
- 'config-db-install-help' => 'Introduza o nome de utilizador e a palavra-chave que serão usados para aceder à base de dados durante o processo de instalação.',
- 'config-db-account-lock' => 'Usar o mesmo nome de utilizador e palavra-chave durante a operação normal',
- 'config-db-wiki-account' => 'Conta de utilizador para a operação normal',
- 'config-db-wiki-help' => 'Introduza o nome de utilizador e a palavra-chave que serão usados para aceder à base de dados durante a operação normal da wiki.
-Se o utilizador não existir na base de dados, mas a conta de instalação tiver privilégios suficientes, o utilizador que introduzir será criado na base de dados com os privilégios mínimos necessários para a operação normal da wiki.',
- 'config-db-prefix' => 'Prefixo para as tabelas da base de dados:',
- 'config-db-prefix-help' => 'Se necessitar de partilhar uma só base de dados entre várias wikis, ou entre o MediaWiki e outra aplicação, pode escolher adicionar um prefixo ao nome de todas as tabelas desta instalação, para evitar conflitos.
-Não use espaços.
-
-Normalmente, este campo deve ficar vazio.',
- 'config-db-charset' => 'Conjunto de caracteres da base de dados',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 backwards-compatible UTF-8',
- 'config-charset-help' => "'''Aviso:''' Se usar '''backwards-compatible UTF-8''' (\"UTF-8 compatível com versões anteriores\") no MySQL 4.1+, e depois fizer cópias de segurança da base de dados usando <code>mysqldump</code>, poderá destruir todos os caracteres que não fazem parte do conjunto ASCII, corrompendo assim, de forma irreversível, as suas cópias de segurança!
-
-No modo '''binary''' (\"binário\"), o MediaWiki armazena o texto UTF-8 na base de dados em campos binários.
-Isto é mais eficiente do que o modo UTF-8 do MySQL e permite que sejam usados todos os caracteres Unicode.
-No modo '''UTF-8''', o MySQL saberá em que conjunto de caracteres os seus dados estão e pode apresentá-los e convertê-los da forma mais adequada,
-mas não lhe permitirá armazenar caracteres acima do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
- 'config-mysql-old' => 'É necessário o MySQL $1 ou posterior; tem a versão $2.',
- 'config-db-port' => 'Porta da base de dados:',
- 'config-db-schema' => "Esquema ''(schema)'' do MediaWiki",
- 'config-db-schema-help' => 'Normalmente, este esquema estará correto.
-Altere-o só se souber que precisa de o fazer.',
- 'config-pg-test-error' => "Não foi possível criar uma ligação à base de dados '''$1''': $2",
- 'config-sqlite-dir' => 'Diretório de dados do SQLite:',
- 'config-sqlite-dir-help' => "O SQLite armazena todos os dados num único ficheiro.
-
-Durante a instalação, o servidor de internet precisa de ter permissão de escrita no diretório que especificar.
-
-Este diretório '''não''' deve poder ser acedido diretamente da internet, por isso está a ser colocado onde estão os seus ficheiros PHP.
-
-Juntamente com o diretório, o instalador irá criar um ficheiro <code>.htaccess</code>, mas se esta operação falhar é possível que alguém venha a ter acesso direto à base de dados.
-Isto inclui acesso aos dados dos utilizadores (endereços de correio eletrónico, palavras-chave encriptadas), às revisões eliminadas e a outros dados de acesso restrito na wiki.
-
-Considere colocar a base de dados num local completamente diferente, como, por exemplo, em <code>/var/lib/mediawiki/asuawiki</code>.",
- 'config-oracle-def-ts' => 'Tablespace padrão:',
- 'config-oracle-temp-ts' => 'Tablespace temporário:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'O MediaWiki suporta as seguintes plataformas de base de dados:
-
-$1
-
-Se a plataforma que pretende usar não está listada abaixo, siga as instruções nos links acima para ativar o suporte.',
- 'config-support-mysql' => '* $1 é a plataforma primária do MediaWiki e a melhor suportada ([http://www.php.net/manual/en/mysql.installation.php como compilar PHP com suporte MySQL])',
- 'config-support-postgres' => '* $1 é uma plataforma de base de dados comum, de fonte aberta, alternativa ao MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar PHP com suporte PostgreSQL]). Poderão existir alguns pequenos problemas e não é recomendado o seu uso em ambientes de exploração/produção.',
- 'config-support-sqlite' => '* $1 é uma plataforma de base de dados ligeira muito bem suportada. ([http://www.php.net/manual/en/pdo.installation.php Como compilar PHP com suporte SQLite], usa PDO)',
- 'config-support-oracle' => '* $1 é uma base de dados de uma empresa comercial. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
- 'config-header-mysql' => 'Definições MySQL',
- 'config-header-postgres' => 'Definições PostgreSQL',
- 'config-header-sqlite' => 'Definições SQLite',
- 'config-header-oracle' => 'Definições Oracle',
- 'config-invalid-db-type' => 'O tipo de base de dados é inválido',
- 'config-missing-db-name' => 'Tem de introduzir um valor para "Nome da base de dados"',
- 'config-missing-db-host' => 'Tem de introduzir um valor para "Servidor da base de dados"',
- 'config-missing-db-server-oracle' => 'Tem de introduzir um valor para "TNS da base de dados"',
- 'config-invalid-db-server-oracle' => 'O TNS da base de dados, "$1", é inválido.
-Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e pontos (.) dos caracteres ASCII.', # Fuzzy
- 'config-invalid-db-name' => 'O nome da base de dados, "$1", é inválido.
-Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e hífens (-) dos caracteres ASCII.',
- 'config-invalid-db-prefix' => 'O prefixo da base de dados, "$1", é inválido.
-Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e hífens (-) dos caracteres ASCII.',
- 'config-connection-error' => '$1.
-
-Verifique o servidor, o nome do utilizador e a palavra-chave abaixo e tente novamente.',
- 'config-invalid-schema' => "O esquema ''(schema)'' do MediaWiki, \"\$1\", é inválido.
-Use só letras (a-z, A-Z), algarismos (0-9) e sublinhados (_) dos caracteres ASCII.",
- 'config-db-sys-create-oracle' => 'O instalador só permite criar uma conta nova usando uma conta SYSDBA.',
- 'config-db-sys-user-exists-oracle' => 'A conta "$1" já existe. A conta SYSDBA só pode criar uma conta nova!',
- 'config-postgres-old' => 'É necessário o PostgreSQL $1 ou posterior; tem a versão $2.',
- 'config-sqlite-name-help' => 'Escolha o nome que identificará a sua wiki.
-Não use espaços ou hífens.
-Este nome será usado como nome do ficheiro de dados do SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Não é possível criar o diretório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no diretório que o contém <code><nowiki>$2</nowiki></code>.
-
-O instalador determinou em que nome de utilizador o seu servidor de internet está a correr.
-Para continuar, configure o diretório <code><nowiki>$3</nowiki></code> para poder ser escrito por este utilizador.
-Para fazê-lo em sistemas Unix ou Linux, use:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Não é possível criar o diretório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no diretório que o contém <code><nowiki>$2</nowiki></code>.
-
-Não foi possível determinar em que nome de utilizador o seu servidor de internet está a correr.
-Para continuar, configure o diretório <code><nowiki>$3</nowiki></code> para que este possa ser globalmente escrito por esse utilizador (e por outros!).
-Para fazê-lo em sistemas Unix ou Linux, use:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Ocorreu um erro ao criar o diretório de dados "$1".
-Verifique a localização e tente novamente.',
- 'config-sqlite-dir-unwritable' => 'Não foi possível escrever no diretório "$1".
-Altere as permissões para que ele possa ser escrito pelo servidor de internet e tente novamente.',
- 'config-sqlite-connection-error' => '$1.
-
-Verifique o diretório de dados e o nome da base de dados abaixo e tente novamente.',
- 'config-sqlite-readonly' => 'Não é possivel escrever no ficheiro <code>$1</code>.',
- 'config-sqlite-cant-create-db' => 'Não foi possível criar o ficheiro da base de dados <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'O PHP não tem suporte FTS3; a reverter o esquema das tabelas para o anterior',
- 'config-can-upgrade' => "Esta base de dados contém tabelas do MediaWiki.
-Para atualizá-las para o MediaWiki $1, clique '''Continuar'''.",
- 'config-upgrade-done' => "Atualização terminada.
-
-Agora pode [$1 começar a usar a sua wiki].
-
-Se quiser regenerar o seu ficheiro <code>LocalSettings.php</code>, clique o botão abaixo.
-Esta operação '''não é recomendada''' a menos que esteja a ter problemas com a sua wiki.",
- 'config-upgrade-done-no-regenerate' => 'Atualização terminada.
-
-Agora pode [$1 começar a usar a sua wiki].',
- 'config-regenerate' => 'Regenerar o LocalSettings.php →',
- 'config-show-table-status' => 'A consulta <code>SHOW TABLE STATUS</code> falhou!',
- 'config-unknown-collation' => "'''Aviso:''' A base de dados está a utilizar uma colação ''(collation)'' desconhecida.",
- 'config-db-web-account' => 'Conta na base de dados para acesso pela internet',
- 'config-db-web-help' => 'Selecione o nome de utilizador e a palavra-chave que o servidor de internet irá utilizar para aceder ao servidor da base de dados, durante a operação normal da wiki.',
- 'config-db-web-account-same' => 'Usar a mesma conta usada na instalação',
- 'config-db-web-create' => 'Criar a conta se ainda não existir',
- 'config-db-web-no-create-privs' => 'A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.
-A conta que especificar aqui já tem de existir.',
- 'config-mysql-engine' => 'Motor de armazenamento:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Aviso''': Selecionou o MyISAM para motor de armazenamento do MySQL, uma combinação desaconselhada para usar com o MediaWiki porque:
-* praticamente não permite acessos simultâneos, devido aos bloqueios de tabelas
-* o MyISAM é mais suscetível a perdas da integridade dos dados do que outros motores
-* o código do MediaWiki não trabalha devidamente com o MyISAM
-
-Se a sua instalação do MySQL suporta InnoDB, é altamente recomendado que o escolha em vez do MyISAM.
-Se não suporta o InnoDB, talvez esta seja uma boa altura para fazer a atualização para a versão mais recente do MySQL.",
- 'config-mysql-engine-help' => "'''InnoDB''' é quase sempre a melhor opção, porque suporta bem acessos simultâneos ''(concurrency)''.
-
-'''MyISAM''' pode ser mais rápido no modo de utilizador único ou em instalações somente para leitura.
-As bases de dados MyISAM tendem a ficar corrompidas com maior frequência do que as bases de dados InnoDB.",
- 'config-mysql-charset' => 'Conjunto de caracteres da base de dados:',
- 'config-mysql-binary' => 'Binary',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "No modo '''binary''' (\"binário\"), o MediaWiki armazena o texto UTF-8 na base de dados em campos binários.
-Isto é mais eficiente do que o modo UTF-8 do MySQL e permite que sejam usados todos os caracteres Unicode.
-
-No modo '''UTF-8''', o MySQL saberá em que conjunto de caracteres os seus dados estão e pode apresentá-los e convertê-los da forma mais adequada,
-mas não lhe permitirá armazenar caracteres acima do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
- 'config-site-name' => 'Nome da wiki:',
- 'config-site-name-help' => 'Este nome aparecerá no título da janela do seu browser e em vários outros sítios.',
- 'config-site-name-blank' => 'Introduza o nome do site.',
- 'config-project-namespace' => 'Espaço nominal do projeto:',
- 'config-ns-generic' => 'Projeto',
- 'config-ns-site-name' => 'O mesmo que o nome da wiki: $1',
- 'config-ns-other' => 'Outro (especifique)',
- 'config-ns-other-default' => 'AMinhaWiki',
- 'config-project-namespace-help' => 'Seguindo o exemplo da Wikipedia, muitas wikis mantêm as páginas das suas normas e políticas, separadas das páginas de conteúdo, num "\'\'\'espaço nominal do projeto\'\'\'".
-Todos os nomes das páginas neste espaço nominal começam com um determinado prefixo, que pode especificar aqui.
-Tradicionalmente, este prefixo deriva do nome da wiki, mas não pode conter caracteres de pontuação, como "#" ou ":".',
- 'config-ns-invalid' => 'O espaço nominal especificado "<nowiki>$1</nowiki>" é inválido.
-Introduza um espaço nominal de projeto diferente.',
- 'config-ns-conflict' => 'O espaço nominal que especificou, "<nowiki>$1</nowiki>", cria um conflito com um dos espaços nominais padrão do MediaWiki.
-Especifique um espaço nominal do projeto diferente.',
- 'config-admin-box' => 'Conta de administrador',
- 'config-admin-name' => 'O seu nome:',
- 'config-admin-password' => 'Palavra-chave:',
- 'config-admin-password-confirm' => 'Repita a palavra-chave:',
- 'config-admin-help' => 'Introduza aqui o seu nome de utilizador preferido, por exemplo, "João Beltrão".
-Este é o nome que irá utilizar para entrar na wiki.',
- 'config-admin-name-blank' => 'Introduza um nome de utilizador para administrador.',
- 'config-admin-name-invalid' => 'O nome de utilizador especificado "<nowiki>$1</nowiki>" é inválido.
-Introduza um nome de utilizador diferente.',
- 'config-admin-password-blank' => 'Introduza uma palavra-chave para a conta de administrador.',
- 'config-admin-password-same' => 'A palavra-chave tem de ser diferente do nome de utilizador.',
- 'config-admin-password-mismatch' => 'As duas palavras-chave que introduziu não coincidem.',
- 'config-admin-email' => 'Correio electrónico:',
- 'config-admin-email-help' => 'Introduza aqui um correio electrónico que lhe permita receber mensagens de outros utilizadores da wiki, reiniciar a sua palavra-chave e receber notificações de alterações às suas páginas vigiadas. Pode deixar o campo vazio.',
- 'config-admin-error-user' => 'Ocorreu um erro interno ao criar um administrador com o nome "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Ocorreu um erro interno ao definir uma palavra-chave para o administrador "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Introduziu um correio electrónico inválido',
- 'config-subscribe' => 'Subscreva a [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de divulgação de anúncios de lançamento].',
- 'config-subscribe-help' => 'Esta é uma lista de divulgação de baixo volume para anúncios de lançamento de versões novas, incluindo anúncios de segurança importantes.
-Deve subscrevê-la e atualizar a sua instalação MediaWiki quando são lançadas versões novas.',
- 'config-subscribe-noemail' => 'Tentou subscrever a lista de divulgação dos anúncios de novas versões, sem fornecer um endereço de correio electrónico.
-Para subscrever esta lista de divulgação tem de fornecer um endereço de correio electrónico.',
- 'config-almost-done' => 'Está quase a terminar!
-Agora pode saltar as configurações restantes e instalar já a wiki.',
- 'config-optional-continue' => 'Faz-me mais perguntas.',
- 'config-optional-skip' => 'Já estou aborrecido, instala lá a wiki.',
- 'config-profile' => 'Perfil de permissões:',
- 'config-profile-wiki' => 'Wiki tradicional', # Fuzzy
- 'config-profile-no-anon' => 'Criação de conta exigida',
- 'config-profile-fishbowl' => 'Somente utilizadores autorizados',
- 'config-profile-private' => 'Wiki privada',
- 'config-profile-help' => "As wikis funcionam melhor quando se deixa tantas pessoas editá-las quanto possível.
-No MediaWiki, é fácil rever as alterações recentes e reverter quaisquer estragos causados por utilizadores novatos ou maliciosos.
-
-No entanto, muitas pessoas consideram o MediaWiki útil de variadas formas e nem sempre é fácil convencer todas as pessoas dos benefícios desta filosofia wiki.
-Por isso pode optar.
-
-Uma '''{{int:config-profile-wiki}}''' permite que todos a editem, sem sequer necessitar de autenticação.
-Uma wiki com '''{{int:config-profile-no-anon}}''' atribui mais responsabilidade, mas pode afastar os colaboradores ocasionais.
-
-Um cenário '''{{int:config-profile-fishbowl}}''' permite que os utilizadores aprovados editem, mas que o público visione as páginas, incluindo o historial das mesmas.
-Uma '''{{int:config-profile-private}}''' só permite que os utilizadores aprovados visionem as páginas e as editem.
-
-Após a instalação, estarão disponíveis mais configurações de privilégios. Consulte [//www.mediawiki.org/wiki/Manual:User_rights a entrada relevante no Manual].", # Fuzzy
- 'config-license' => 'Direitos de autor e licença:',
- 'config-license-none' => 'Sem rodapé com a licença',
- 'config-license-cc-by-sa' => 'Creative Commons - Atribuição - Partilha nos Mesmos Termos',
- 'config-license-cc-by' => 'Creative Commons - Atribuição',
- 'config-license-cc-by-nc-sa' => 'Creative Commons - Atribuição - Uso Não Comercial - Partilha nos Mesmos Termos',
- 'config-license-cc-0' => 'Creative Commons Zero (Domínio Público)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 ou posterior',
- 'config-license-pd' => 'Domínio Público',
- 'config-license-cc-choose' => 'Selecione uma licença personalizada Creative Commons',
- 'config-license-help' => 'Muitas wikis de acesso público licenciam todas as colaborações com uma [http://freedomdefined.org/Definition licença livre].
-Isto ajuda a criar um sentido de propriedade da comunidade e encoraja as colaborações a longo prazo.
-Tal não é geralmente necessário nas wikis privadas ou corporativas.
-
-Se 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 Creative Commons - Atribuição - Partilha nos Mesmos Termos.
-
-A licença anterior da Wikipédia era a licença GNU Free Documentation License.
-A GFDL é uma licença válida, mas de difícil compreensão.
-Também é difícil reutilizar conteúdos licenciados com a GFDL.',
- 'config-email-settings' => 'Definições do correio electrónico',
- 'config-enable-email' => 'Ativar mensagens eletrónicas de saída',
- 'config-enable-email-help' => 'Se quer que o correio eletrónico funcione, as [http://www.php.net/manual/en/mail.configuration.php definições de correio eletrónico do PHP] têm de estar configuradas corretamente.
-Se 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',
- 'config-email-usertalk-help' => 'Permitir que os utilizadores recebam notificações de alterações à sua página de discussão, se tiverem ativado esta funcionalidade nas suas preferências.',
- 'config-email-watchlist' => 'Ativar notificação de alterações às páginas vigiadas',
- 'config-email-watchlist-help' => 'Permitir que os utilizadores recebam notificações de alterações às suas páginas vigiadas, se tiverem ativado esta funcionalidade nas suas preferências.',
- 'config-email-auth' => 'Ativar autenticação do correio eletrónico',
- 'config-email-auth-help' => "Se esta opção for ativada, os utilizadores têm de confirmar o seu endereço de correio eletrónico usando um link que lhes é enviado sempre que o definirem ou alterarem.
-Só os endereços de correio eletrónico autenticados podem receber mensagens eletrónicas dos outros utilizadores ou alterar as mensagens de notificação.
-É '''recomendado''' que esta opção seja ativada nas wikis de acesso público para impedir o uso abusivo das funcionalidades de correio eletrónico.",
- 'config-email-sender' => 'Endereço de correio electrónico de retorno:',
- 'config-email-sender-help' => 'Introduza o endereço de correio electrónico que será usado como endereço de retorno nas mensagens electrónicas de saída.
-É para este endereço que serão enviadas as mensagens que não podem ser entregues.
-Muitos servidores de correio electrónico exigem que pelo menos a parte do nome do domínio seja válida. \\',
- 'config-upload-settings' => 'Upload de imagens e ficheiros',
- 'config-upload-enable' => 'Possibilitar o upload de ficheiros',
- 'config-upload-help' => 'O upload de ficheiros expõe o seu servidor a riscos de segurança.
-Para mais informações, leia a [//www.mediawiki.org/wiki/Manual:Security seção sobre segurança] do Manual Técnico.
-
-Para permitir o upload de ficheiros, altere as permissões do subdiretório <code>images</code> no diretório de raiz do MediaWiki para que o servidor de internet possa escrever nele.
-Depois ative esta opção.',
- 'config-upload-deleted' => 'Diretório para os ficheiros apagados:',
- 'config-upload-deleted-help' => 'Escolha um diretório onde serão arquivados os ficheiros apagados.
-O ideal é que este diretório não possa ser diretamente acedido a partir da internet.',
- 'config-logo' => 'URL do logótipo:',
- 'config-logo-help' => 'O tema padrão do MediaWiki inclui espaço para um logótipo de 135x160 pixels acima do menu da barra lateral.
-Coloque na wiki uma imagem com estas dimensões e introduza aqui a URL dessa imagem.
-
-Se não pretende usar um logótipo, deixe este campo em branco.', # Fuzzy
- 'config-instantcommons' => 'Ativar Instant Commons',
- 'config-instantcommons-help' => 'O [//www.mediawiki.org/wiki/InstantCommons Instant Commons] é uma funcionalidade que permite que as wikis usem imagens, áudio e outros ficheiros multimédia disponíveis no site [//commons.wikimedia.org/ Wikimedia Commons].
-Para poder usá-los, o MediaWiki necessita de acesso à internet.
-
-Para mais informações sobre esta funcionalidade, incluindo instruções sobre como configurá-la para usar outras wikis em vez da Wikimedia Commons, consulte o [//mediawiki.org/wiki/Manual:$wgForeignFileRepos Manual Técnico].',
- 'config-cc-error' => 'O auxiliar de escolha de licenças da Creative Commons não produziu resultados.
-Introduza o nome da licença manualmente.',
- 'config-cc-again' => 'Escolha outra vez...',
- 'config-cc-not-chosen' => 'Escolha a licença da Creative Commons que pretende e clique "continuar".',
- 'config-advanced-settings' => 'Configuração avançada',
- 'config-cache-options' => 'Configuração da cache de objetos:',
- 'config-cache-help' => 'A cache de objetos é usada para melhorar o desempenho do MediaWiki. Armazena dados usados com frequência.
-Sites de tamanho médio ou grande são altamente encorajados a ativar esta funcionalidade e os sites pequenos também terão alguns benefícios em fazê-lo.',
- 'config-cache-none' => 'Sem cache (não é removida nenhuma funcionalidade, mas a velocidade de operação pode ser afectada nas wikis grandes)',
- 'config-cache-accel' => 'Cache de objetos do PHP (APC, XCache ou WinCache)',
- 'config-cache-memcached' => 'Usar Memcached (requer instalação e configurações adicionais)',
- 'config-memcached-servers' => 'Servidores Memcached:',
- 'config-memcached-help' => 'Lista de endereços IP que serão usados para o Memcached.
-Deve-se colocar um por linha e indicar a porta a utilizar. Por exemplo:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Selecionou o Memcached como tipo de chache, mas não especificou nenhum servidor.',
- 'config-memcache-badip' => 'Introduziu um endereço IP inválido para o Memcached: $1.',
- 'config-memcache-noport' => 'Não especificou a porta a usar para o servidor Memcached: $1.
-Se não sabe qual é a porta, a predefinida é a 11211.',
- 'config-memcache-badport' => 'Os números das portas do Memcached devem estar entre $1 e $2.',
- 'config-extensions' => 'Extensões',
- 'config-extensions-help' => 'Foi detectada a existência das extensões listadas acima, no seu diretório <code>./extensions</code>.
-
-Estas talvez necessitem de configurações adicionais, mas pode ativá-las agora',
- 'config-install-alreadydone' => "'''Aviso:''' Parece que já instalou o MediaWiki e está a tentar instalá-lo novamente.
-Passe para a próxima página, por favor.",
- 'config-install-begin' => 'Ao clicar "{{int:config-continue}}", vai iniciar a instalação do MediaWiki.
-Se quiser fazer mais alterações, clique Voltar.', # Fuzzy
- 'config-install-step-done' => 'terminado',
- 'config-install-step-failed' => 'falhou',
- 'config-install-extensions' => 'A incluir as extensões',
- 'config-install-database' => 'A preparar a base de dados',
- 'config-install-schema' => "A criar o esquema (''schema'') da base de dados",
- 'config-install-pg-schema-not-exist' => "O esquema ''(schema)'' PostgreSQL não existe",
- 'config-install-pg-schema-failed' => 'A criação das tabelas falhou.
-Certifique-se de que o utilizador "$1" pode escrever no esquema \'\'(schema)\'\' "$2".',
- 'config-install-pg-commit' => 'A gravar as alterações',
- 'config-install-pg-plpgsql' => 'A verificar a presença da linguagem PL/pgSQL',
- 'config-pg-no-plpgsql' => 'É preciso instalar a linguagem PL/pgSQL na base de dados $1',
- 'config-pg-no-create-privs' => 'A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.',
- 'config-pg-not-in-role' => 'A conta que especificou para o utilizador da internet já existe.
-A conta que especificou para a instalação não é a de um super-utilizador e não pertence ao grupo de utilizadores de acesso pela internet, por isso não pode criar objetos que pertencem ao utilizador da internet.
-
-O MediaWiki necessita que as tabelas pertençam ao utilizador da internet. Especifique outra conta de internet, ou clique "voltar" e especifique um utilizador com os privilégios necessários para a instalação.',
- 'config-install-user' => 'A criar o utilizador da base de dados',
- 'config-install-user-alreadyexists' => 'O utilizador "$1" já existe',
- 'config-install-user-create-failed' => 'A criação do utilizador "$1" falhou: $2',
- 'config-install-user-grant-failed' => 'A atribuição das permissões ao utilizador "$1" falhou: $2',
- 'config-install-user-missing' => 'O utilizador especificado, "$1", não existe.',
- 'config-install-user-missing-create' => 'O utilizador especificado, "$1", não existe.
-Marque a caixa de seleção "criar conta" abaixo se pretende criá-la, por favor.',
- 'config-install-tables' => 'A criar as tabelas',
- 'config-install-tables-exist' => "'''Aviso''': As tabelas do MediaWiki parecem já existir.
-A criação das tabelas será saltada.",
- 'config-install-tables-failed' => "'''Erro''': A criação das tabelas falhou com o seguinte erro: $1",
- 'config-install-interwiki' => 'A preencher a tabela padrão de interwikis',
- 'config-install-interwiki-list' => 'Não foi possível encontrar o ficheiro <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Aviso''': A tabela de interwikis parece já conter entradas.
-O preenchimento padrão desta tabela será saltado.",
- 'config-install-stats' => 'A inicializar as estatísticas',
- 'config-install-keys' => 'A gerar as chaves secretas',
- 'config-insecure-keys' => "'''Warning:''' {{PLURAL:$2|A chave segura|As chaves seguras}} ($1) {{PLURAL:$2|gerada durante a instalação não é completamente segura|geradas durante a instalação não são completamente seguras}}. Considere a possibilidade de {{PLURAL:$2|alterá-la|alterá-las}} manualmente.",
- 'config-install-sysop' => 'A criar a conta de administrador',
- 'config-install-subscribe-fail' => 'Não foi possível subscrever a lista mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL não está instalado e allow_url_fopen não está disponível.',
- 'config-install-mainpage' => 'A criar a página principal com o conteúdo padrão.',
- 'config-install-extension-tables' => 'A criar as tabelas das extensões ativadas',
- 'config-install-mainpage-failed' => 'Não foi possível inserir a página principal: $1',
- 'config-install-done' => "'''Parabéns!'''
-Terminou a instalação do MediaWiki.
-
-O instalador gerou um ficheiro <code>LocalSettings.php</code>.
-Este ficheiro contém todas as configurações.
-
-Precisa de fazer o download do ficheiro e colocá-lo no diretório de raiz da sua instalação (o mesmo diretório onde está o ficheiro index.php). Este download deverá ter sido iniciado automaticamente.
-
-Se o download não foi iniciado, ou se o cancelou, pode recomeçá-lo clicando o link abaixo:
-
-$3
-
-'''Nota''': Se não fizer isto agora, o ficheiro que foi gerado deixará de estar disponível quando sair do processo de instalação.
-
-Depois de terminar o passo anterior, pode '''[$2 entrar na wiki]'''.",
- 'config-download-localsettings' => 'Download do <code>LocalSettings.php</code>',
- 'config-help' => 'ajuda',
- 'config-nofile' => 'Não foi possível encontrar o ficheiro "$1". Terá sido apagado?',
- 'mainpagetext' => "'''MediaWiki instalado com sucesso.'''",
- 'mainpagedocfooter' => 'Consulte o [//meta.wikimedia.org/wiki/Help:Contents Guia de Utilizadores] para informações sobre o uso do software wiki.
-
-== Onde começar ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de opções de configuração]
-* [//www.mediawiki.org/wiki/Manual:FAQ Perguntas e respostas frequentes sobre o MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Subscreva a lista de divulgação de novas versões do MediaWiki]', # Fuzzy
-);
-
-/** Brazilian Portuguese (português do Brasil)
- * @author Cainamarques
- * @author Giro720
- * @author Gustavo
- * @author Luckas
- * @author Marcionunes
- * @author 555
- */
-$messages['pt-br'] = array(
- 'config-desc' => 'O instalador do MediaWiki',
- 'config-title' => 'Instalação do MediaWiki $1',
- 'config-information' => 'Informações',
- 'config-localsettings-upgrade' => 'Foi detectada a existência do arquivo <code>LocalSettings.php</code>.
-Para atualizar esta instalação, insira no box abaixo o valor de <code>$wgUpgradeKey</code>.
-Essa informação pode ser encontrada no arquivo <code>LocalSettings.php</code>',
- 'config-localsettings-cli-upgrade' => 'Foi detectada a existência do arquivo <code><code>LocalSettings.php</code></code>.
-Atualize esta instalação executando o arquivo <code>update.php</code>',
- 'config-localsettings-key' => 'Chave de atualização:',
- 'config-localsettings-badkey' => 'A chave fornecida está incorreta.',
- 'config-upgrade-key-missing' => 'Foi detectada uma instalação existente do MediaWiki.
-Para atualizar esta instalação, insira a seguinte linha na parte inferior do seu <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'O arquivo <code>LocalSettings.php</code> parece incompleto.
-A variável $1 não está definida.
-Altere seu <code>LocalSettings.php</code> com a definição dessa variável e clique em "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Ocorreu um erro ao conectar ao banco de dados através das configurações presentes ou no <code>LocalSettings.php</code> ou no <code>AdminSettings.php</code>. Corrija essas configurações e tente novamente.
-
-$1',
- 'config-session-error' => 'Erro ao iniciar a sessão: $1',
- 'config-session-expired' => 'Os seus dados de sessão parecem ter expirado.
-As sessões estão configuradas para uma duração de $1.
-Você pode aumentar esta duração configurando <code>session.gc_maxlifetime</code> no php.ini.
-Reinicie o processo de instalação.',
- 'config-no-session' => 'Os seus dados de sessão foram perdidos!
-Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um diretório apropriado.',
- 'config-your-language' => 'Seu idioma:',
- 'config-your-language-help' => 'Selecione o idioma que será usado durante o processo de instalação.',
- 'config-wiki-language' => 'Idioma do wiki:',
- 'config-wiki-language-help' => 'Selecione o idioma em que o wiki será predominantemente escrito.',
- 'config-back' => '← Voltar',
- 'config-continue' => 'Continuar →',
- 'config-page-language' => 'Idioma',
- 'config-page-welcome' => 'Bem-vindo(a) ao MediaWiki!',
- 'config-page-dbconnect' => 'Conectar ao banco de dados',
- 'config-page-upgrade' => 'Atualizar a instalação existente',
- 'config-page-dbsettings' => 'Configurações do banco de dados',
- 'config-page-name' => 'Nome',
- 'config-page-options' => 'Opções',
- 'config-page-install' => 'Instalar',
- 'config-page-complete' => 'Concluído!',
- 'config-page-restart' => 'Reiniciar a instalação',
- 'config-page-readme' => 'Leia-me',
- 'config-page-releasenotes' => 'Notas de lançamento',
- 'config-page-copying' => 'Copiando',
- 'config-page-upgradedoc' => 'Atualizando',
- 'config-page-existingwiki' => 'Wiki existente',
- 'config-help-restart' => 'Deseja limpar todos os dados salvos que você introduziu e reiniciar o processo de instalação?',
- 'config-restart' => 'Sim, reiniciar',
- 'config-welcome' => '=== Verificações de ambiente ===
-São realizadas verificações básicas para determinar se este ambiente é apropriado para a instalação do MediaWiki.
-Lembre-se de incluir estas informações se for procurar por suporte para a conclusão da instalação.',
- 'config-copyright' => "=== Direitos autorais e Termos de uso ===
-
-$1
-
-Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo nos termos da licença GNU General Public License, tal como publicada pela Free Software Foundation; tanto a versão 2 da Licença, como (por opção sua) qualquer versão posterior.
-
-Este programa é distribuído na esperança de que seja útil, mas '''sem qualquer garantia'''; inclusive, sem a garantia implícita da '''possibilidade de ser comercializado''' ou de '''adequação para qualquer finalidade específica'''.
-Consulte a licença GNU General Public License para mais detalhes.
-
-Em conjunto com este programa você deve ter recebido <doclink href=Copying>uma cópia da licença GNU General Public License</doclink>; se não a recebeu, peça-a por escrito para Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA ou [http://www.gnu.org/copyleft/gpl.html leia-a na internet].",
- 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki Página principal do MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Manual de uso]
-* [//www.mediawiki.org/wiki/Manual:Contents Manual administrativo]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>Leia-me</doclink>
-* <doclink href=ReleaseNotes>Notas de lançamento</doclink>
-* <doclink href=Copying>Licença</doclink>
-* <doclink href=UpgradeDoc>Como fazer upgrade</doclink>',
- 'config-env-good' => 'O ambiente foi verificado.
-Você pode instalar o MediaWiki.',
- 'config-env-bad' => 'O ambiente foi verificado.
-Você não pode instalar o MediaWiki.',
- 'config-env-php' => 'O PHP $1 está instalado.',
- 'config-unicode-using-utf8' => 'Usando o utf8_normalize.so, de Brion Vibber, para a normalização Unicode.',
- 'config-unicode-using-intl' => 'Usando a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.',
- 'config-unicode-pure-php-warning' => "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode sendo usada, em seu lugar, a lenta implementação de PHP puro.
-Se o seu site tem um alto volume de tráfego, informe-se sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalização Unicode].",
- 'config-no-db' => 'Não foi possível encontrar um driver de banco de dados adequado! É necessário instalar um driver de banco de dados para o PHP.
-São suportados os seguintes tipos de bancos de dados: $1.
-
-Se estiver em uma hospedagem partilhada, peça à sua empresa de hospedagem para instalar um driver de banco de dados adequado.
-Se você mesmo tiver compilado o PHP, reconfigure-o com um cliente de banco de dados ativado usando, por exemplo, <code>./configure --with-mysql</code>.
-Se você instalou o PHP a partir de um pacote do Debian ou do Ubuntu, instale também o módulo php5-mysql.',
- 'config-no-fts3' => "' ' 'Aviso' ' ': O SQLite foi compilado sem o módulo [//sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
- 'config-register-globals' => "' ' 'Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está ativada.'''
-' ' 'Desative-a, se puder.'''
-O MediaWiki funcionará mesmo assim, mas o seu servidor ficará exposto a potenciais vulnerabilidades de segurança.",
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-binary' => 'Binary',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-ns-generic' => 'Projeto',
- 'config-admin-box' => 'Conta de administrador',
- 'config-admin-name' => 'Seu nome:',
- 'config-admin-password' => 'Senha:',
- 'config-license-pd' => 'Domínio público',
- 'config-logo-help' => 'Faça o upload de uma imagem de tamanho adequado e insira seu URL aqui.
-
-Você pode usar <code>$wgStylePath</code> ou <code>$wgScriptPath</code> se o seu logotipo for associado a esses diretórios.',
- 'config-advanced-settings' => 'Configuração avançada',
- 'config-extensions' => 'Extensões',
- 'config-install-step-done' => 'feito',
- 'config-help' => 'ajuda',
- 'mainpagetext' => "'''MediaWiki instalado com sucesso.'''",
- 'mainpagedocfooter' => 'Consulte o [//meta.wikimedia.org/wiki/Help:Contents Manual de Usuário] para informações de como usar o software wiki.
-
-== Começando ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de opções de configuração]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ do MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussão com avisos de novas versões do MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Traduza o MediaWiki para seu idioma]',
-);
-
-/** Quechua (Runa Simi)
- * @author AlimanRuna
- */
-$messages['qu'] = array(
- 'config-desc' => 'MediaWiki tiyachiq',
- 'config-title' => 'MediaWiki $1 tiyachiy',
- 'config-information' => 'Willay',
- 'config-your-language' => 'Rimayniyki:',
- 'config-wiki-language' => 'Wiki rimay:',
- 'config-back' => '← Ñawpaqman',
- 'config-extensions' => "Mast'ariykuna",
- 'config-install-step-done' => 'rurasqañam',
- 'config-install-step-failed' => 'manam aypasqachu',
- 'config-help' => 'yanapay',
- 'config-nofile' => '"$1" sutiyuq willañiqiqa manam tarisqachu. Qullusqachu?',
- 'mainpagetext' => "'''MediaWiki nisqa llamp'u kaqqa aypaylla takyachisqañam.'''",
- 'mainpagedocfooter' => "Wiki llamp'u kaqmanta willasunaykipaqqa [//meta.wikimedia.org/wiki/Help:Contents Ruraqpaq yanapana] ''(User's Guide)'' sutiyuq p'anqata qhaway.
-
-== Qallarichkaspa ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Kunphigurasyun churanamanta sutisuyu]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki nisqamanta pasaq tapuykuna]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki kachaykuy e-chaski sutisuyu]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki nisqata qampa rimaykiman t'ikray]",
-);
-
-/** Romagnol (Rumagnôl)
- */
-$messages['rgn'] = array(
- 'mainpagetext' => "'''L'instalaziòn d'MediaWiki l'è andêda ben'''",
-);
-
-/** Romansh (rumantsch)
- * @author Gion-andri
- */
-$messages['rm'] = array(
- 'mainpagetext' => "'''MediaWiki è vegnì installà cun success.'''",
- 'mainpagedocfooter' => "Consultai il [//meta.wikimedia.org/wiki/Help:Contents manual per utilisaders] per infurmaziuns davart l'utilisaziun da questa software da wiki.
-
-== Cumenzar ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Glista da las opziuns per la configuraziun]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Glista da mail da MediaWiki cun annunzias da novas versiuns]", # Fuzzy
-);
-
-/** Romanian (română)
- * @author Firilacroco
- * @author Minisarm
- * @author Stelistcristi
- */
-$messages['ro'] = array(
- 'config-desc' => 'Programul de instalare pentru MediaWiki',
- 'config-title' => 'Instalarea MediaWiki $1',
- 'config-information' => 'Informații',
- 'config-localsettings-key' => 'Cheie de actualizare:',
- 'config-localsettings-badkey' => 'Cheia furnizată este incorectă.',
- 'config-session-error' => 'Eroare la pornirea sesiunii: $1',
- 'config-your-language' => 'Limba ta:',
- 'config-your-language-help' => 'Alege o limbă pentru a o utiliza în timpul procesului de instalare.',
- 'config-wiki-language' => 'Limbă wiki:',
- 'config-wiki-language-help' => 'Alege limba în care wiki-ul va fi scris predominant.',
- 'config-back' => '← Înapoi',
- 'config-continue' => 'Continuă →',
- 'config-page-language' => 'Limbă',
- 'config-page-welcome' => 'Bun venit la MediaWiki!',
- 'config-page-dbconnect' => 'Conectează la baza de date',
- 'config-page-upgrade' => 'Extinde instalarea existentă',
- 'config-page-dbsettings' => 'Setări ale bazei de date',
- 'config-page-name' => 'Nume',
- 'config-page-options' => 'Opţiuni',
- 'config-page-install' => 'Instalare',
- 'config-page-complete' => 'Finalizat!',
- 'config-page-restart' => 'Reporneşte instalarea',
- 'config-page-readme' => 'Citeşte-mă',
- 'config-page-releasenotes' => 'Note de lansare',
- 'config-page-copying' => 'Copiere',
- 'config-page-upgradedoc' => 'Actualizare',
- 'config-page-existingwiki' => 'Wiki existent',
- 'config-restart' => 'Da, repornește.',
- 'config-env-php' => 'PHP $1 este instalat.',
- 'config-env-php-toolow' => 'PHP $1 este instalat.
-Totuși, MediaWiki necesită PHP $2 sau mai nou.',
- 'config-db-type' => 'Tipul bazei de date:',
- 'config-db-host' => 'Gazdă bază de date:',
- 'config-db-host-oracle' => 'Baza de date TNS:',
- 'config-db-wiki-settings' => 'Identificați acest wiki',
- 'config-db-name' => 'Numele bazei de date:',
- 'config-db-name-oracle' => 'Schema bazei de date:',
- 'config-db-username' => 'Nume de utilizator pentru baza de date:',
- 'config-db-password' => 'Parola bazei de date:',
- 'config-db-prefix' => 'Prefixul tabelelor din baza de date:',
- 'config-db-charset' => 'Setul de caractere al bazei de date',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binar',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-db-port' => 'Portul bazei de date:',
- 'config-db-schema' => 'Schema pentru MediaWiki:',
- 'config-sqlite-dir' => 'Director de date SQLite:',
- 'config-oracle-def-ts' => 'Spațiu de stocare („tablespace”) implicit:',
- 'config-oracle-temp-ts' => 'Spațiu de stocare („tablespace”) temporar:',
- 'config-header-mysql' => 'Setările MySQL',
- 'config-header-postgres' => 'Setări PostgreSQL',
- 'config-header-sqlite' => 'Setări SQLite',
- 'config-header-oracle' => 'Setări Oracle',
- 'config-invalid-db-type' => 'Tip de bază de date incorect',
- 'config-missing-db-name' => 'Trebuie să introduci o valoare pentru „Numele bazei de date”',
- 'config-connection-error' => '$1.
-
-Verificați gazda, numele de utilizator și parola și reîncercați.',
- 'config-upgrade-done-no-regenerate' => 'Actualizare completă.
-
-Acum puteți [$1 începe să vă folosiți wikiul].',
- 'config-regenerate' => 'Regenerare LocalSettings.php →',
- 'config-unknown-collation' => 'AVERTISMENT: Baza de date folosește o colaționare nerecunoscută.',
- 'config-db-web-account' => 'Contul bazei de date pentru accesul web.',
- 'config-db-web-create' => 'Creați contul dacă nu există deja',
- 'config-mysql-engine' => 'Motor de stocare:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => 'Setul de caractere al bazei de date:',
- 'config-mysql-binary' => 'Binar',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Numele wikiului:',
- 'config-site-name-blank' => 'Introduceți un nume pentru sit.',
- 'config-project-namespace' => 'Spațiul de nume al proiectului:',
- 'config-ns-generic' => 'Proiect',
- 'config-ns-site-name' => 'Același nume ca al wikiului: $1',
- 'config-ns-other' => 'Altul (specificați)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-admin-box' => 'Cont de administrator',
- 'config-admin-name' => 'Numele dumneavoastră:',
- 'config-admin-password' => 'Parolă:',
- 'config-admin-password-confirm' => 'Parola, din nou:',
- 'config-admin-password-blank' => 'Introduceți o parolă pentru contul de administrator.',
- 'config-admin-password-same' => 'Parola trebuie să difere de numele de utilizator.',
- 'config-admin-password-mismatch' => 'Cele două parole introduse nu corespund.',
- 'config-admin-email' => 'Adresa de e-mail:',
- 'config-admin-error-bademail' => 'Ați introdus o adresă de e-mail incorectă.',
- 'config-almost-done' => 'Sunteți aproape gata!
-Puteți sări peste configurarea rămasă și să instalați wikiul chiar acum.',
- 'config-optional-continue' => 'Adresează-mi mai multe întrebări.',
- 'config-optional-skip' => 'Sunt deja plictisit, doar instalează wikiul.',
- 'config-profile' => 'Profilul drepturilor de utilizator:',
- 'config-profile-wiki' => 'Wiki tradițional', # Fuzzy
- 'config-profile-no-anon' => 'Crearea de cont este necesară',
- 'config-profile-fishbowl' => 'Doar editorii autorizați',
- 'config-profile-private' => 'Wiki privat',
- 'config-license' => 'Drepturi de autor și licență:',
- 'config-license-none' => 'Fără licență în subsolul paginii',
- 'config-license-cc-by-sa' => 'Creative Commons Atribuire și distribuire în condiții identice',
- 'config-license-cc-by' => 'Creative Commons Atribuire',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Atribuire, necomercial și distribuire în condiții identice',
- 'config-license-cc-0' => 'Creative Commons Zero (domeniu public)',
- 'config-license-gfdl' => 'Licența GNU pentru Documentare Liberă 1.3 sau ulterioară',
- 'config-license-pd' => 'Domeniu public',
- 'config-license-cc-choose' => 'Alegeți o licență Creative Commons personalizată',
- 'config-email-settings' => 'Setări pentru e-mail',
- 'config-email-usertalk' => 'Activați notificările pentru pagina de discuții a utilizatorului',
- 'config-upload-deleted' => 'Director pentru fișierele șterse:',
- 'config-logo' => 'Adresa URL a siglei:',
- 'config-cc-again' => 'Alegeți din nou...',
- 'config-advanced-settings' => 'Configurare avansată',
- 'config-cache-options' => 'Parametrii pentru stocarea temporară a obiectelor:',
- 'config-extensions' => 'Extensii',
- 'config-install-step-done' => 'realizat',
- 'config-install-step-failed' => 'eșuat',
- 'config-install-extensions' => 'Se includ extensiile',
- 'config-install-database' => 'Se creează baza de date',
- 'config-install-schema' => 'Se creează schema',
- 'config-install-pg-schema-not-exist' => 'Schema PostgreSQL nu există.',
- 'config-install-pg-commit' => 'Se validează modificările',
- 'config-install-user' => 'Se creează utilizatorul pentru baza de date',
- 'config-install-user-alreadyexists' => 'Utilizatorul „$1” există deja',
- 'config-install-user-create-failed' => 'Crearea utilizatorului „$1” a eșuat: $2',
- 'config-install-tables' => 'Se creează tabelele',
- 'config-install-stats' => 'Se inițializează statisticile',
- 'config-install-keys' => 'Se generează cheile secrete',
- 'config-install-sysop' => 'Se creează contul de administrator',
- 'config-install-mainpage-failed' => 'Nu s-a putut insera pagina principală: $1',
- 'config-download-localsettings' => 'Descarcă <code>LocalSettings.php</code>',
- 'config-help' => 'ajutor',
- 'mainpagetext' => "'''Programul Wiki a fost instalat cu succes.'''",
- 'mainpagedocfooter' => 'Consultați [//meta.wikimedia.org/wiki/Help:Contents Ghidul utilizatorului (en)] pentru informații despre utilizarea software-ului wiki.
-
-== Primii pași ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista parametrilor configurabili (en)]
-* [//www.mediawiki.org/wiki/Manual:FAQ Întrebări frecvente despre MediaWiki (en)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discuții a MediaWiki (en)]', # Fuzzy
-);
-
-/** tarandíne (tarandíne)
- * @author Joetaras
- */
-$messages['roa-tara'] = array(
- 'config-desc' => "'U 'nstallatore de MediaUicchi",
- 'config-title' => 'Installazzione de MediaUicchi $1',
- 'config-information' => "'Mbormaziune",
- 'config-localsettings-key' => 'Chiave de aggiornamende:',
- 'config-localsettings-badkey' => "'A chiave ca è date non g'è corrette.",
- 'config-session-error' => "Errore facenne accumenzà 'a sessione: $1",
- 'config-your-language' => "'A lènga toje:",
- 'config-your-language-help' => "Scacchie 'na lènghe da ausà duranne 'u processe de installazzione:",
- 'config-wiki-language' => 'Lènga de Uicchi:',
- 'config-back' => '← Rrète',
- 'config-continue' => 'Condinue →',
- 'config-page-language' => 'Lènghe',
- 'config-page-welcome' => "Bovègne jndr'à MediaUicchi!",
- 'config-page-dbconnect' => "Collegate a 'u database",
- 'config-page-upgrade' => "Aggiorne l'installazzione esistende",
- 'config-page-dbsettings' => "'Mbostaziune d'u database",
- 'config-page-name' => 'Nome',
- 'config-page-options' => 'Opziune',
- 'config-page-install' => 'Installe',
- 'config-page-complete' => 'Combletate!',
- 'config-page-restart' => "Riavvie l'installazzione",
- 'config-page-readme' => 'Liggeme',
- 'config-page-releasenotes' => 'Note de rilasce',
- 'config-page-copying' => 'Stoche a copie',
- 'config-page-upgradedoc' => 'Aggiornamende',
- 'config-page-existingwiki' => 'Uicchi esistende',
- 'config-db-type' => 'Tipe de database:',
- 'config-db-charset' => "'Nzieme de carattere d'u database",
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 backwards-compatible UTF-8',
- 'config-db-port' => "Porte d'u database:",
- 'config-db-schema' => 'Scheme pe MediaUicchi:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-admin-email' => 'Indirizze e-mail:',
- 'config-install-step-done' => 'fatte',
- 'config-install-step-failed' => 'fallite',
- 'config-install-extensions' => "'Ngludenne le estenziune",
- 'config-install-database' => "Stoche a 'mboste l'archivije",
- 'config-install-schema' => "Stoche a creje 'u scheme",
- 'config-install-pg-schema-not-exist' => "'U scheme PostgreSQL non g'esiste.",
- 'config-help' => 'ajute',
- 'mainpagetext' => "'''MediaUicchi ha state 'nstallete.'''",
- 'mainpagedocfooter' => "Vè vide [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pe l'mbormaziune sus a cumme s'ause 'u softuer wiki.
-
-== Pe accumenzà ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste pe le configuraziune]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Elenghe d'a poste de MediaUicchi]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localizzazzione de MediaUicchi pa lènga toje]",
-);
-
-/** Russian (русский)
- * @author Adata80
- * @author DCamer
- * @author Eleferen
- * @author Express2000
- * @author KPu3uC B Poccuu
- * @author Kaganer
- * @author Krinkle
- * @author Lockal
- * @author MaxSem
- * @author Okras
- * @author Yuriy Apostol
- * @author Александр Сигачёв
- * @author Сrower
- * @author 아라
- */
-$messages['ru'] = array(
- 'config-desc' => 'Инсталлятор MediaWiki',
- 'config-title' => 'Установка MediaWiki $1',
- 'config-information' => 'Информация',
- 'config-localsettings-upgrade' => 'Обнаружен файл <code>LocalSettings.php</code>.
-Для обновления этой установки, пожалуйста, введите значение <code>$wgUpgradeKey</code>.
-Его можно найти в файле <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Обнаружен файл <code>LocalSettings.php</code>.
-Для обновления этой установки, пожалуйста, запустите <code>update.php</code>',
- 'config-localsettings-key' => 'Ключ обновления:',
- 'config-localsettings-badkey' => 'Вы указали неправильный ключ',
- 'config-upgrade-key-missing' => 'Обнаружена существующая установленная копия MediaWiki.
-Чтобы обновить обнаруженную установку, пожалуйста, добавьте следующую строку в конец вашего файла <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Похоже, что существующий файл <code>LocalSettings.php</code> не является полными.
-Не установлена переменная $1.
-Пожалуйста, измените <code>LocalSettings.php</code> так, чтобы значение этой переменной было задано, затем нажмите «{{int:Config-continue}}».',
- 'config-localsettings-connection-error' => 'Произошла ошибка при подключении к базе данных с помощью настроек, указанных в <code>LocalSettings.php</code> или <code>AdminSettings.php</code>. Пожалуйста, исправьте эти настройки и повторите попытку.
-
-$1',
- 'config-session-error' => 'Ошибка при запуске сессии: $1',
- 'config-session-expired' => 'Ваша сессия истекла.
-Сессии настроены на длительность $1.
-Вы её можете увеличить, изменив <code>session.gc_maxlifetime</code> в php.ini.
-Перезапустите процесс установки.',
- 'config-no-session' => 'Данные сессии потеряны!
-Проверьте ваш php.ini и убедитесь, что <code>session.save_path</code> установлен в соответствующий каталог.',
- 'config-your-language' => 'Ваш язык:',
- 'config-your-language-help' => 'Выберите язык, на котором будет происходить процесс установки.',
- 'config-wiki-language' => 'Язык, который будет использовать вики:',
- 'config-wiki-language-help' => 'Выберите язык, на котором будут отображаться вики.',
- 'config-back' => '← Назад',
- 'config-continue' => 'Далее →',
- 'config-page-language' => 'Язык',
- 'config-page-welcome' => 'Добро пожаловать в MediaWiki!',
- 'config-page-dbconnect' => 'Подключение к базе данных',
- 'config-page-upgrade' => 'Обновление существующей установки',
- 'config-page-dbsettings' => 'Настройки базы данных',
- 'config-page-name' => 'Название',
- 'config-page-options' => 'Настройки',
- 'config-page-install' => 'Установка',
- 'config-page-complete' => 'Готово!',
- 'config-page-restart' => 'Начать установку заново',
- 'config-page-readme' => 'Прочти меня',
- 'config-page-releasenotes' => 'Информация о версии',
- 'config-page-copying' => 'Лицензия',
- 'config-page-upgradedoc' => 'Обновление',
- 'config-page-existingwiki' => 'Существующая вики',
- 'config-help-restart' => 'Вы хотите удалить все сохранённые данные, которые вы ввели, и запустить процесс установки заново?',
- 'config-restart' => 'Да, начать заново',
- 'config-welcome' => '=== Проверка окружения ===
-Будут проведены базовые проверки с целью определить, подходит ли данная система для установки MediaWiki.
-Не забудьте включить эту информацию, если вам потребуется помощь для завершения установки.',
- 'config-copyright' => "=== Авторские права и условия ===
-
-$1
-
-MediaWiki является свободным программным обеспечением, которое вы можете распространять и/или изменять в соответствии с условиями лицензии GNU General Public License, опубликованной фондом свободного программного обеспечения; второй версии, либо любой более поздней версии.
-
-MediaWiki распространяется в надежде, что она будет полезной, но '''без каких-либо гарантий''', даже без подразумеваемых гарантий '''коммерческой ценности''' или '''пригодности для определённой цели'''. См. лицензию GNU General Public License для более подробной информации.
-
-Вы должны были получить <doclink href=Copying>копию GNU General Public License</doclink> вместе с этой программой, если нет, то напишите Free Software Foundation, Inc., по адресу: 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA или [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html прочтите её онлайн].",
- 'config-sidebar' => '* [//www.mediawiki.org Сайт MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents/ru Справка для пользователей]
-* [//www.mediawiki.org/wiki/Manual:Contents/ru Справка для администраторов]
-* [//www.mediawiki.org/wiki/Manual:FAQ/ru FAQ]
-----
-* <doclink href=Readme>Readme-файл</doclink>
-* <doclink href=ReleaseNotes>Информация о выпуске</doclink>
-* <doclink href=Copying>Лицензия</doclink>
-* <doclink href=UpgradeDoc>Обновление</doclink>',
- 'config-env-good' => 'Проверка внешней среды была успешно проведена.
-Вы можете установить MediaWiki.',
- 'config-env-bad' => 'Была проведена проверка внешней среды.
-Вы не можете установить MediaWiki.',
- 'config-env-php' => 'Установленная версия PHP: $1.',
- 'config-env-php-toolow' => 'Найден PHP $1, тогда как MediaWiki требуется PHP версии $2 или выше.',
- 'config-unicode-using-utf8' => 'Использовать Brion Vibber utf8_normalize.so для нормализации Юникода.',
- 'config-unicode-using-intl' => 'Будет использовано [http://pecl.php.net/intl расширение «intl» для PECL] для нормализации Юникода.',
- 'config-unicode-pure-php-warning' => "'''Внимание!''': [http://pecl.php.net/intl расширение intl из PECL] недоступно для нормализации Юникода, будет использоваться медленная реализация на чистом PHP.
-Если ваш сайт работает под высокой нагрузкой, вам следует больше узнать о [//www.mediawiki.org/wiki/Unicode_normalization_considerations нормализации Юникода].",
- 'config-unicode-update-warning' => "'''Предупреждение''': установленная версия обёртки нормализации Юникода использует старую версию библиотеки [http://site.icu-project.org/ проекта ICU].
-Вы должны [//www.mediawiki.org/wiki/Unicode_normalization_considerations обновить версию], если хотите полноценно использовать Юникод.",
- 'config-no-db' => 'Не найдено поддержки баз данных! Вам необходимо установить драйвера базы данных для PHP.
-Поддерживаются следующие базы данных: $1.
-
-Если вы используете виртуальный хостинг, обратитесь к своему хостинг-провайдеру с просьбой установить подходящий драйвер базы данных.
-Если вы скомпилировали PHP сами, сконфигурируйте его снова с включенным клиентом базы данных, например, с помощью <code>./configure --with-mysql</code>.
-Если вы установили PHP из пакетов Debian или Ubuntu, то вам также необходимо установить модуль php5-mysql.',
- 'config-outdated-sqlite' => "'''Предупреждение''': у Вас установлен SQLite $1, версия которого ниже требуемой $2 . SQLite будет недоступен.",
- 'config-no-fts3' => "'''Внимание''': SQLite собран без модуля [//sqlite.org/fts3.html FTS3] — поиск не будет работать для этой базы данных.",
- 'config-register-globals' => "'''Внимание: PHP-опция <code>[http://php.net/register_globals register_globals]</code> включена.'''
-'''Отключите её, если это возможно.'''
-MediaWiki будет работать, но это снизит безопасность сервера и увеличит риск проникновения извне.",
- 'config-magic-quotes-runtime' => "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
-Это приводит к непредсказуемой порче вводимых данных.
-Установка и использование MediaWiki без выключения этой опции невозможно.",
- 'config-magic-quotes-sybase' => "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''
-Это приводит к непредсказуемой порче вводимых данных.
-Установка и использование MediaWiki без выключения этой опции невозможно.",
- 'config-mbstring' => "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''
-Это приводит к ошибкам и непредсказуемой порче вводимых данных.
-Установка и использование MediaWiki без выключения этой опции невозможно.",
- 'config-ze1' => "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]!'''
-Это приводит к катастрофическим сбоям в MediaWiki.
-Установка и использование MediaWiki без выключения этой опции невозможно.",
- 'config-safe-mode' => "'''Предупреждение:''' PHP работает в [http://www.php.net/features.safe-mode «безопасном режиме»].
-Это может привести к проблемам, особенно с загрузкой файлов и вставкой математических формул.",
- 'config-xml-bad' => 'XML-модуль РНР отсутствует.
-MediaWiki не будет работать в этой конфигурации, так как требуется функционал этого модуля.
-Если вы работаете в Mandrake, установите PHP XML-пакет.',
- 'config-pcre' => 'Модуль поддержки PCRE не найден.
-Для работы MediaWiki требуется поддержка Perl-совместимых регулярных выражений.',
- 'config-pcre-no-utf8' => "'''Фатальная ошибка'''. Модуль PCRE для PHP, похоже, собран без поддержки PCRE_UTF8.
-MediaWiki требует поддержки UTF-8 для корректной работы.",
- 'config-memory-raised' => 'Ограничение на доступную PHP память (<code>memory_limit</code>) поднято с $1 до $2.',
- 'config-memory-bad' => "'''Внимание:''' размер PHP <code>memory_limit</code> составляет $1.
-Вероятно, этого слишком мало.
-Установка может потерпеть неудачу!",
- 'config-ctype' => "'''Фатальная ошибка:''' PHP должен быть скомпилирован с поддержкой [http://www.php.net/manual/ru/ctype.installation.php расширения Ctype].",
- 'config-json' => "'''Фатальная ошибка:''' PHP был скомпилирован без поддержка JSON.
-Вам необходимо установить либо расширение PHP JSON, либо расширение [http://pecl.php.net/package/jsonc PECL jsonc] перед установкой MediaWiki.
-* PHP-расширение входит в состав Red Hat Enterprise Linux (CentOS) 5 и 6, хотя должна быть включено в <code>/etc/php.ini</code> или <code>/etc/php.d/json.ini</code>.
-* Некоторые дистрибутивы Linux, выпущенные после мая 2013 года, не включают расширение PHP, вместо того, чтобы упаковывать расширение PECL как <code>php5-json</code> или <code>php-pecl-jsonc</code>.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] установлен',
- 'config-apc' => '[http://www.php.net/apc APC] установлен',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] установлен',
- 'config-no-cache' => "'''Внимание:''' Не найдены [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
-Кэширование объектов будет отключено.",
- 'config-mod-security' => "'''Внимание''': на вашем веб-сервере включен [http://modsecurity.org/ mod_security]. При неправильной настройке он может вызывать проблемы для MediaWiki или другого ПО, позволяющего пользователям отправлять на сервер произвольный текст.
-Обратитесь к [http://modsecurity.org/documentation/ документации mod_security] или в поддержку вашего хостера, если при работе возникают непонятные ошибки.",
- 'config-diff3-bad' => 'GNU diff3 не найден.',
- 'config-git' => 'Найдена система контроля версий Git: <code>$1</code>.',
- 'config-git-bad' => 'Программное обеспечение по управлению версиями Git не найдено.',
- 'config-imagemagick' => 'Обнаружен ImageMagick: <code>$1</code>.
-Возможно отображение миниатюр изображений, если вы разрешите закачки файлов.',
- 'config-gd' => 'Найдена встроенная графическая библиотека GD.
-Возможность использования миниатюр изображений будет включена, если вы включите их загрузку.',
- 'config-no-scaling' => 'Не удалось найти встроенную библиотеку GD или ImageMagick.
-Возможность использования миниатюр изображений будет отключена.',
- 'config-no-uri' => "'''Ошибка:''' Не могу определить текущий URI.
-Установка прервана.",
- 'config-no-cli-uri' => "'''Предупреждение''': нет задан параметр --scriptpath, используется по умолчанию: <code>$1</code> .",
- 'config-using-server' => 'Используется имя сервера «<nowiki>$1</nowiki>».',
- 'config-using-uri' => 'Используется имя сервера "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Внимание:''' директория, используемая по умолчанию для загрузок (<code>$1</code>) уязвима к выполнению произвольных скриптов.
-Хотя MediaWiki проверяет все загружаемые файлы на наличие угроз, настоятельно рекомендуется [//www.mediawiki.org/wiki/Manual:Security#Upload_security закрыть данную уязвимость] перед включением загрузки файлов.",
- 'config-no-cli-uploads-check' => "'''Предупреждение:''' каталог для загрузки по умолчанию ( <code>$1</code> ) не проверялся на уязвимости
- на выполнение произвольного сценария во время установки CLI.",
- 'config-brokenlibxml' => 'В вашей системе имеется сочетание версий PHP и libxml2, могущее привести к скрытым повреждениям данных в MediaWiki и других веб-приложениях.
-Обновите PHP до версии 5.2.9 или старше и libxml2 до 2.7.3 или старше ([//bugs.php.net/bug.php?id=45996 сведения об ошибке]).
-Установка прервана.',
- 'config-using531' => 'PHP $1 не совместим с MediaWiki из-за ошибки с параметрами-ссылками при вызовах <code>__call()</code>.
-Обновитесь до PHP 5.3.2 и выше, или откатитесь до PHP 5.3.0, чтобы избежать этой проблемы.
-Установка прервана.',
- 'config-suhosin-max-value-length' => 'Suhosin установлен и ограничивает параметр GET <code>length</code> до $1 байт. Компонент MediaWiki ResourceLoader будет обходить это ограничение, но это снизит производительность. Если это возможно, следует установить <code>suhosin.get.max_value_length</code> в значение 1024 или выше в <code>php.ini</code>, а также установить для <code>$wgResourceLoaderMaxQueryLength</code> такое же значение в LocalSettings.php.',
- 'config-db-type' => 'Тип базы данных:',
- 'config-db-host' => 'Хост базы данных:',
- 'config-db-host-help' => 'Если сервер базы данных находится на другом сервере, введите здесь его имя хоста или IP-адрес.
-
-Если вы используете виртуальный хостинг, ваш провайдер должен указать правильное имя хоста в своей документации.
-
-Если вы устанавливаете систему на сервере под Windows и используете MySQL, имя сервера «localhost» может не работать. В этом случае попробуйте указать 127.0.0.1 локальный IP-адрес.
-
-Если вы используете PostgreSQL, оставьте это поле пустым для подключения через сокет Unix.',
- 'config-db-host-oracle' => 'TNS базы данных:',
- 'config-db-host-oracle-help' => 'Введите действительный [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; файл tnsnames.ora должен быть видимым для этой инсталляции. <br />При использовании клиентских библиотек версии 10g и старше также возможно использовать метод именования [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Идентификация этой вики',
- 'config-db-name' => 'Имя базы данных:',
- 'config-db-name-help' => 'Выберите название-идентификатор для вашей вики.
-Оно не должно содержать пробелов.
-
-Если вы используете виртуальный хостинг, провайдер или выдаст вам конкретное имя базы данных, или позволит создавать базы данных с помощью панели управления.',
- 'config-db-name-oracle' => 'Схема базы данных:',
- 'config-db-account-oracle-warn' => 'Поддерживаются три сценария установки Oracle в качестве базы данных:
-
-Если вы хотите создать учётную запись базы данных в процессе установки, пожалуйста, укажите учётную запись роли SYSDBA для установки и укажите желаемые полномочия учётной записи с веб-доступом. вы также можете учётную запись с веб-доступом вручную и указать только её (если у неё есть необходимые разрешения на создание объектов схемы) или указать две учётные записи, одну с правами создания объектов, а другую с ограничениями для веб-доступа.
-
-Сценарий для создания учётной записи с необходимыми привилегиями можно найти в папке «maintenance/oracle/» этой программы установки. Имейте в виду, что использование ограниченной учётной записи приведёт к отключению всех возможностей обслуживания с учётной записи по умолчанию.',
- 'config-db-install-account' => 'Учётная запись для установки',
- 'config-db-username' => 'Имя пользователя базы данных:',
- 'config-db-password' => 'Пароль базы данных:',
- 'config-db-password-empty' => 'Пожалуйста, введите пароль для нового пользователя базы данных «$1».
-Хотя и возможно создание пользователей без паролей, это небезопасно.',
- 'config-db-install-username' => 'Введите имя пользователя, которое будет использоваться для подключения к базе данных в процессе установки.
-Это не имя пользователя MediaWiki, это имя пользователя для базы данных.',
- 'config-db-install-password' => 'Введите пароль, который будет использоваться для подключения к базе данных в процессе установки.
-Это не пароль пользователя MediaWiki, это пароль для базы данных.',
- 'config-db-install-help' => 'Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время процесса установки.',
- 'config-db-account-lock' => 'Использовать то же имя пользователя и пароль для обычной работы',
- 'config-db-wiki-account' => 'Учётная запись для обычной работы',
- 'config-db-wiki-help' => 'Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время обычной работы вики.
-Если такой учётной записи не существует, а установочная учётная запись имеет достаточно привилегий, то обычная учётная запись будет создана с минимально необходимыми для работы вики привилегиями.',
- 'config-db-prefix' => 'Префикс таблиц базы данных:',
- 'config-db-prefix-help' => 'Если вам нужно делить одну базу данных между несколькими вики, или между MediaWiki и другими веб-приложениями, вы можете добавить префикс для всех имён таблиц.
-Не используйте пробелы.
-
-Это поле обычно остаётся пустым.',
- 'config-db-charset' => 'Набор символов базы данных',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 бинарная',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 обратно совместимая с UTF-8',
- 'config-charset-help' => "'''Внимание.''' Если вы используете '''обратно совместый UTF-8''' на MySQL 4.1+ и создаёте резервные копии базы данных с помощью <code>mysqldump</code>, то все не-ASCII символы могут быть искажены, а резервная копия окажется негодной!
-
-В '''бинарном режиме''' MediaWiki хранит юникодный текст в базе в виде двоичных полей.
-Это более эффективно, чем MySQL в режиме UTF-8, позволяет использовать полный набор символов Юникода.
-В '''режиме UTF-8''' MySQL будет знать к какому набору символу относятся ваши данные, сможет представлять и преобразовать их надлежащим образом (буква Ё окажется при сортировке после буквы Е, а не после буквы Я, как в бинарном режиме),
-но не позволит вам сохранять символы, выходящие за пределы [//ru.wikipedia.org/wiki/Символы,_представленные_в_Юникоде#.D0.91.D0.B0.D0.B7.D0.BE.D0.B2.D0.B0.D1.8F_.D0.BC.D0.BD.D0.BE.D0.B3.D0.BE.D1.8F.D0.B7.D1.8B.D0.BA.D0.BE.D0.B2.D0.B0.D1.8F_.D0.BF.D0.BB.D0.BE.D1.81.D0.BA.D0.BE.D1.81.D1.82.D1.8C BMP].",
- 'config-mysql-old' => 'Необходим MySQL $1 или более поздняя версия. У вас установлен MySQL $2.',
- 'config-db-port' => 'Порт базы данных:',
- 'config-db-schema' => 'Схема для MediaWiki',
- 'config-db-schema-help' => 'Эта схема обычно работают хорошо.
-Изменяйте её только если знаете, что вам это нужно.',
- 'config-pg-test-error' => "Не удаётся подключиться к базе данных '''$1''': $2",
- 'config-sqlite-dir' => 'Директория данных SQLite:',
- 'config-sqlite-dir-help' => "SQLite хранит все данные в одном файле.
-
-Директория, которую вы должны указать, должна быть доступна для записи веб-сервером во время установки.
-
-Она '''не должна''' быть доступна через Интернет, поэтому не должна совпадать с той, где хранятся PHP файлы.
-
-Установщик запишет в эту директорию файл <code>.htaccess</code>, но если это не сработает, кто-нибудь может получить доступ ко всей базе данных.
-В этой базе находится в том числе и информация о пользователях (адреса электронной почты, хэши паролей), а также удалённые страницы и другие секретные данные о вики.
-
-По возможности, расположите базу данных где-нибудь в стороне, например, в <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Пространство таблиц по умолчанию:',
- 'config-oracle-temp-ts' => 'Временное пространство таблиц:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki поддерживает следующие СУБД:
-
-$1
-
-Если вы не видите своей системы хранения данных в этом списке, следуйте инструкциям, на которые есть ссылка выше, чтобы получить поддержку.',
- 'config-support-mysql' => '* $1 — основная база данных для MediaWiki, и лучше поддерживается ([http://www.php.net/manual/en/mysql.installation.php инструкция, как собрать PHP с поддержкой MySQL])',
- 'config-support-postgres' => '* $1 — популярная открытая СУБД, альтернатива MySQL ([http://www.php.net/manual/en/pgsql.installation.php инструкция, как собрать PHP с поддержкой PostgreSQL]). Могут встречаться небольшие неисправленные ошибки, не рекомендуется для использования в рабочей системе.',
- 'config-support-sqlite' => '* $1 — это легковесная система баз данных, имеющая очень хорошую поддержку. ([http://www.php.net/manual/en/pdo.installation.php инструкция, как собрать PHP с поддержкой SQLite], работающей посредством PDO)',
- 'config-support-oracle' => '* $1 — это коммерческая база данных масштаба предприятия. ([http://www.php.net/manual/en/oci8.installation.php Как собрать PHP с поддержкой OCI8])',
- 'config-header-mysql' => 'Настройки MySQL',
- 'config-header-postgres' => 'Настройки PostgreSQL',
- 'config-header-sqlite' => 'Настройки SQLite',
- 'config-header-oracle' => 'Настройки Oracle',
- 'config-invalid-db-type' => 'Неверный тип базы данных',
- 'config-missing-db-name' => 'Вы должны ввести значение параметра «Имя базы данных»',
- 'config-missing-db-host' => 'Необходимо ввести значение параметра «Сервер базы данных»',
- 'config-missing-db-server-oracle' => 'Вы должны заполнить поле «TNS базы данных»',
- 'config-invalid-db-server-oracle' => 'Неверное TNS базы данных «$1».
-Используйте либо «TNS Name», либо строку «Easy Connect» ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методы наименования Oracle])',
- 'config-invalid-db-name' => 'Неверное имя базы данных «$1».
-Используйте только ASCII-символы (a-z, A-Z), цифры (0-9), знак подчёркивания (_) и дефис(-).',
- 'config-invalid-db-prefix' => 'Неверный префикс базы данных «$1».
-Используйте только буквы ASCII (a-z, A-Z), цифры (0-9), знак подчёркивания (_) и дефис (-).',
- 'config-connection-error' => '$1.
-
-Проверьте хост, имя пользователя и пароль и попробуйте ещё раз.',
- 'config-invalid-schema' => 'Неправильная схема для MediaWiki «$1».
-Используйте только ASCII символы (a-z, A-Z), цифры(0-9) и знаки подчёркивания(_).',
- 'config-db-sys-create-oracle' => 'Программа установки поддерживает только использование SYSDBA для создания новой учётной записи.',
- 'config-db-sys-user-exists-oracle' => 'Учётная запись «$1». SYSDBA может использоваться только для создания новой учётной записи!',
- 'config-postgres-old' => 'Необходим PostgreSQL $1 или более поздняя версия. У вас установлен PostgreSQL $2.',
- 'config-sqlite-name-help' => 'Выберите имя-идентификатор для вашей вики.
-Не используйте дефисы и пробелы.
-Эта строка будет использоваться в имени файла SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Не удалось создать директорию данных <nowiki><code>$1</code></nowiki>, так как у веб-сервера нет прав записи в родительскую директорию <nowiki><code>$2</code></nowiki>.
-
-Установщик определил пользователя, под которым работает веб-сервер.
-Сделайте директорию <nowiki><code>$3</code></nowiki> доступной для записи и продолжите.
-В Unix/Linux системе выполните:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Не удалось создать директорию для данных <code><nowiki>$1</nowiki></code>, так как у веб-сервера нет прав на запись в родительскую директорию <code><nowiki>$2</nowiki></code>.
-
-Программа установки не смогла определить пользователя, под которым работает веб-сервер.
-Для продолжения сделайте каталог <code><nowiki>$3</nowiki></code> глобально доступным для записи серверу (и другим).
-В Unix/Linux сделайте:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Ошибка при создании директории для данных «$1».
-Проверьте расположение и повторите попытку.',
- 'config-sqlite-dir-unwritable' => 'Невозможно произвести запись в каталог «$1».
-Измените настройки доступа так, чтобы веб-сервер мог записывать в этот каталог, и попробуйте ещё раз.',
- 'config-sqlite-connection-error' => '$1.
-
-Проверьте название базы данных и директорию с данными и попробуйте ещё раз.',
- 'config-sqlite-readonly' => 'Файл <code>$1</code> недоступен для записи.',
- 'config-sqlite-cant-create-db' => 'Не удаётся создать файл базы данных <code>$1</code> .',
- 'config-sqlite-fts3-downgrade' => 'У PHP отсутствует поддержка FTS3 — сбрасываем таблицы',
- 'config-can-upgrade' => "В базе данных найдены таблицы MediaWiki.
-Чтобы обновить их до MediaWiki $1, нажмите на кнопку '''«Продолжить»'''.",
- 'config-upgrade-done' => "Обновление завершено.
-
-Теперь вы можете [$1 начать использовать вики].
-
-Если вы хотите повторно создать файл <code>LocalSettings.php</code>, нажмите на кнопку ниже.
-Это действие '''не рекомендуется''', если у вас не возникло проблем при установке.",
- 'config-upgrade-done-no-regenerate' => 'Обновление завершено.
-
-Теперь вы можете [$1 начать работу с вики].',
- 'config-regenerate' => 'Создать LocalSettings.php заново →',
- 'config-show-table-status' => 'Запрос «<code>SHOW TABLE STATUS</code>» не выполнен!',
- 'config-unknown-collation' => "'''Внимание:''' База данных использует нераспознанные правила сортировки.",
- 'config-db-web-account' => 'Учётная запись для доступа к базе данных из веб-сервера',
- 'config-db-web-help' => 'Выберите имя пользователя и пароль, которые веб-сервер будет использовать для подключения к серверу базы данных при обычной работе вики.',
- 'config-db-web-account-same' => 'Использовать ту же учётную запись, что и для установки',
- 'config-db-web-create' => 'Создать учётную запись, если она ещё не существует',
- 'config-db-web-no-create-privs' => 'Учётная запись, указанная вами для установки, не обладает достаточными правами для создания учётной записи.
-Указанная здесь учётная запись уже должна существовать.',
- 'config-mysql-engine' => 'Движок базы данных:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "''' Внимание.''' Вы выбрали механизм MyISAM для хранения данных MySQL. Он не рекомендуется к использованию по следующим причинам:
-* он слабо поддерживает параллелизм из-за табличных блокировок;
-* более склонен к потере данных, по сравнению с другими механизмами;
-* код MediaWiki не всегда учитывает особенности MyISAM должным образом.
-
-Если ваша установка MySQL поддерживает InnoDB, настоятельно рекомендуется выбрать этот механизм.
-Если ваша установка MySQL не поддерживает InnoDB, возможно, настало время обновиться.",
- 'config-mysql-only-myisam-dep' => "'''Предупреждение:''' MyISAM является единственной доступной системой хранения данных для MySQL, которая, однако, не рекомендуется для использования с MediaWiki, потому что:
- * он слабо поддерживает параллелизм из-за блокировки таблиц
- * она больше других систем подвержена повреждению
- * кодовая база MediaWiki не всегда обрабатывает MyISAM так, как следует
-
-Ваша MySQL не поддерживает InnoDB, так что, возможно, настало время для обновления.",
- 'config-mysql-engine-help' => "'''InnoDB''' почти всегда предпочтительнее, так как он лучше справляется с параллельным доступом.
-
-'''MyISAM''' может оказаться быстрее для вики с одним пользователем или с минимальным количеством поступающих правок, однако базы данных на нём портятся чаще, чем на InnoDB.",
- 'config-mysql-charset' => 'Набор символов (кодовая таблица) базы данных:',
- 'config-mysql-binary' => 'Двоичный',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "В '''двоичном режиме''' MediaWiki хранит UTF-8 текст в бинарных полях базы данных.
-Это более эффективно, чем ''UTF-8 режим'' MySQL, и позволяет использовать полный набор символов Unicode.
-
-В '''режиме UTF-8''' MySQL будет знать в какой кодировке находятся Ваши данные и может отображать и преобразовывать их соответствующим образом, но это не позволит вам хранить символы выше [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базовой Многоязыковой Плоскости].",
- 'config-site-name' => 'Название вики:',
- 'config-site-name-help' => 'Название будет отображаться в заголовке окна браузера и в некоторых других местах вики.',
- 'config-site-name-blank' => 'Введите название сайта.',
- 'config-project-namespace' => 'Пространство имён проекта:',
- 'config-ns-generic' => 'Проект',
- 'config-ns-site-name' => 'То же, что имя вики: $1',
- 'config-ns-other' => 'Другое (укажите)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => "Следуя примеру Википедии, многие вики хранят свои страницы правил отдельно от страниц основного содержания, в так называемом '''«пространстве имён проекта»'''.
-Все названия страниц в этом пространстве имён начинается с определённого префикса, который вы можете задать здесь.
-Обычно, этот префикс происходит от имени вики, но он не может содержать знаки препинания, символы «#» или «:».",
- 'config-ns-invalid' => 'Указанное пространство имён <nowiki>$1</nowiki> недопустимо.
-Укажите другое пространство имён проекта.',
- 'config-ns-conflict' => 'Указанное пространство имён «<nowiki>$1</nowiki>» конфликтует со стандартным пространством имён MediaWiki.
-Укажите другое пространство имён проекта.',
- 'config-admin-box' => 'Учётная запись администратора',
- 'config-admin-name' => 'Имя:',
- 'config-admin-password' => 'Пароль:',
- 'config-admin-password-confirm' => 'Пароль ещё раз:',
- 'config-admin-help' => 'Введите ваше имя пользователя здесь, например, «Иван Иванов».
-Это имя будет использоваться для входа в вики.',
- 'config-admin-name-blank' => 'Введите имя пользователя администратора.',
- 'config-admin-name-invalid' => 'Указанное имя пользователя «<nowiki>$1</nowiki>» недопустимо.
-Укажите другое имя пользователя.',
- 'config-admin-password-blank' => 'Введите пароль для учётной записи администратора.',
- 'config-admin-password-same' => 'Пароль не должен быть таким же, как имя пользователя.',
- 'config-admin-password-mismatch' => 'Введённые вами пароли не совпадают.',
- 'config-admin-email' => 'Адрес электронной почты:',
- 'config-admin-email-help' => 'Введите адрес электронной почты, чтобы получать сообщения от других пользователей вики, иметь возможность восстановить пароль, а также получать уведомления об изменениях страниц из списка наблюдения. Вы можете оставить это поле пустым.',
- 'config-admin-error-user' => 'Внутренняя ошибка при создании учётной записи администратора с именем «<nowiki>$1</nowiki>».',
- 'config-admin-error-password' => 'Внутренняя ошибка при установке пароля для учётной записи администратора «<nowiki>$1</nowiki>»: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Вы ввели неправильный адрес электронной почты',
- 'config-subscribe' => 'Подписаться на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce рассылку новостей о появлении новых версий MediaWiki].',
- 'config-subscribe-help' => 'Это список рассылки с малым числом сообщений, используется для анонса новых выпусков и сообщений о проблемах с безопасностью.
-Вам следует подписаться на него и обновлять движок MediaWiki, по мере выхода новых версий.',
- 'config-subscribe-noemail' => 'Вы попытались подписаться на список рассылки уведомлений о новых выпусках без указания адреса электронной почты.
-Укажите адрес электронной почты, если вы хотите подписаться на список рассылки.',
- 'config-almost-done' => 'Вы почти у цели!
-Остальные настройки можно пропустить и приступить к установке вики.',
- 'config-optional-continue' => 'Произвести тонкую настройку',
- 'config-optional-skip' => 'Хватит, установить вики',
- 'config-profile' => 'Профиль прав прользователей:',
- 'config-profile-wiki' => 'Открытая вики',
- 'config-profile-no-anon' => 'Требуется создание учётной записи',
- 'config-profile-fishbowl' => 'Только для авторизованных редакторов',
- 'config-profile-private' => 'Закрытая вики',
- 'config-profile-help' => "Вики-технология лучше всего работает, когда вы позволяете редактировать сайт максимально широкому кругу лиц.
-В MediaWiki легко просмотреть последних изменений и, при необходимости, откатить любой ущерб сделанный злоумышленниками или наивными пользователями.
-
-Однако, движок MediaWiki можно использовать и иными способами, и не далеко не всех удаётся убедить в преимуществах открытой вики-работы.
-Так что в вас есть выбор.
-
-Модель '''«{{int:config-profile-wiki}}»''' позволяет всем править страницы даже не регистрируясь на сайте. Конфигурация '''{{int:config-profile-no-anon}}''' обеспечивает дополнительный учёт, но может отсечь случайных участников.
-
-Сценарий '''«{{int:config-profile-fishbowl}}»''' разрешает редактирование только определённым участникам, но общедоступным остаётся просмотр страниц, в том числе просмотр истории изменения. В режиме '''«{{int:config-profile-private}}»''' просмотр страниц разрешён только определённым пользователям, какая-то их часть может иметь также права на редактирование.
-
-Более сложные схемы разграничения прав можно настроить после установки, см. [//www.mediawiki.org/wiki/Manual:User_rights соответствующее руководство].",
- 'config-license' => 'Авторские права и лицензии:',
- 'config-license-none' => 'Не указывать лицензию в колонтитуле внизу страницы',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (общественное достояние)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 или более поздняя',
- 'config-license-pd' => 'Общественное достояние',
- 'config-license-cc-choose' => 'Выберите одну из лицензий Creative Commons',
- 'config-license-help' => "Многие общедоступные вики разрешают использовать свои материалы на условиях [http://freedomdefined.org/Definition/Ru свободных лицензий].
-Это помогает созданию чувства общности, стимулирует долгосрочное участие.
-Но в этом нет необходимости для частных или корпоративных вики.
-
-Если вы хотите использовать тексты из Википедии или хотите, что в Википедию можно было копировать тексты из вашей вики, вам следует выбрать '''Creative Commons Attribution Share Alike'''.
-
-Википедия ранее использовала лицензию GNU Free Documentation License.
-GFDL может быть использована, но она сложна для понимания и осложняет повторное использование материалов.",
- 'config-email-settings' => 'Настройки электронной почты',
- 'config-enable-email' => 'Включить исходящие e-mail',
- 'config-enable-email-help' => 'Если вы хотите, чтобы электронная почта работала, необходимо выполнить [http://www.php.net/manual/en/mail.configuration.php соответствующие настройки PHP].
-Если вы не хотите использовать возможности электронной почты в вики, вы можете её отключить.',
- 'config-email-user' => 'Включить электронную почту от участника к участнику',
- 'config-email-user-help' => 'Разрешить всем пользователям отправлять друг другу электронные письма, если выставлена соответствующая настройка в профиле.',
- 'config-email-usertalk' => 'Включить уведомления пользователей о сообщениях на их странице обсуждения',
- 'config-email-usertalk-help' => 'Разрешить пользователям получать уведомления об изменениях своих страниц обсуждения, если они разрешат это в своих настройках.',
- 'config-email-watchlist' => 'Включить уведомление на электронную почту об изменении списка наблюдения',
- 'config-email-watchlist-help' => 'Разрешить пользователям получать уведомления об отслеживаемых ими страницах, если они разрешили это в своих настройках.',
- 'config-email-auth' => 'Включить аутентификацию через электронную почту',
- 'config-email-auth-help' => "Если эта опция включена, пользователи должны подтвердить свой адрес электронной почты перейдя по ссылке, которая отправляется на e-mail. Подтверждение требуется каждый раз при смене электронного ящика в настройках пользователя.
-Только прошедшие проверку подлинности адреса электронной почты, могут получать электронные письма от других пользователей или изменять уведомления, отправляемые по электронной почте.
-Включение этой опции '''рекомендуется''' для открытых вики в целях пресечения потенциальных злоупотреблений возможностями электронной почты.",
- 'config-email-sender' => 'Обратный адрес электронной почты:',
- 'config-email-sender-help' => 'Введите адрес электронной почты для использования в качестве обратного адреса исходящей электронной почты.
-На него будут отправляться отказы.
-Многие почтовые серверы требуют, чтобы по крайней мере доменное имя в нём было правильным.',
- 'config-upload-settings' => 'Загрузка изображений и файлов',
- 'config-upload-enable' => 'Разрешить загрузку файлов',
- 'config-upload-help' => 'Разрешение загрузки файлов, потенциально, может привести к угрозе безопасности сервера.
-Для получения дополнительной информации, прочтите в руководстве [//www.mediawiki.org/wiki/Manual:Security раздел, посвящённый безопасности].
-
-Чтобы разрешить загрузку файлов, необходимо изменить права на каталог <code>images</code>, в корневой директории MediaWiki так, чтобы веб-сервер мог записывать в него файлы.
-Затем включите эту опцию.',
- 'config-upload-deleted' => 'Директория для удалённых файлов:',
- 'config-upload-deleted-help' => 'Выберите каталог, в котором будут храниться архивы удалённых файлов.
-В идеальном случае, в этот каталог не должно быть доступа из сети Интернет.',
- 'config-logo' => 'URL логотипа:',
- 'config-logo-help' => 'Стандартная тема оформления MediaWiki содержит над боковой панелью пространство для логотипа размером 135x160 пикселей.
-Загрузите изображение соответствующего размера, и введите его URL здесь.
-
-Вы можете использовать <code>$wgStylePath</code> или <code>$wgScriptPath</code>, если ваш логотип находится относительно к этим путям.
-
-Если вам не нужен логотип, оставьте это поле пустым.',
- 'config-instantcommons' => 'Включить Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — это функция, позволяющая использовать изображения, звуки и другие медиафайлы с Викисклада ([//commons.wikimedia.org/ Wikimedia Commons]).
-Для работы этой функции MediaWiki необходим доступ к Интернету.
-
-Дополнительную информацию об Instant Commons, в том числе указания о том, как её настроить для других вики, отличных от Викисклада, можно найти в [//mediawiki.org/wiki/Manual:$wgForeignFileRepos руководстве].',
- 'config-cc-error' => 'Механизм выбора лицензии Creative Commons не вернул результата.
-Введите название лицензии вручную.',
- 'config-cc-again' => 'Выберите ещё раз…',
- 'config-cc-not-chosen' => 'Выберите, какую лицензию Creative Commons Вы хотите использовать, и нажмите кнопку "Продолжить".',
- 'config-advanced-settings' => 'Дополнительные настройки',
- 'config-cache-options' => 'Параметры кэширования объектов:',
- 'config-cache-help' => 'Кэширование объектов используется для повышения скорости MediaWiki путем кэширования часто используемых данных.
-Для средних и больших сайтов кеширование настоятельно рекомендуется включать, а для небольших сайтов кеширование может показать преимущество.',
- 'config-cache-none' => 'Без кэширования (никакой функционал не теряется, но крупные вики-сайты могут работать медленнее)',
- 'config-cache-accel' => 'PHP кэширование объектов (APC, XCache или WinCache)',
- 'config-cache-memcached' => 'Использовать Memcached (требует дополнительной настройки)',
- 'config-memcached-servers' => 'Сервера Memcached:',
- 'config-memcached-help' => 'Список IP-адресов, используемых Memcached.
-Перечислите по одному адресу на строку с указанием портов. Например:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Вы выбрали тип кэширования Memcached, но не задали адреса серверов.',
- 'config-memcache-badip' => 'Вы ввели неверный IP-адрес для Memcached: $1.',
- 'config-memcache-noport' => 'Не указан порт для сервера Memcached: $1.
-Если вы не знаете порт, по умолчанию используется 11211.',
- 'config-memcache-badport' => 'Номера портов Memcached должны лежать в пределах от $1 до $2.',
- 'config-extensions' => 'Расширения',
- 'config-extensions-help' => 'Расширения MediaWiki, перечисленные выше, были найдены в каталоге <code>./extensions</code>.
-
-Они могут потребовать дополнительные настройки, но их можно включить прямо сейчас',
- 'config-install-alreadydone' => "'''Предупреждение:''' Вы, кажется, уже устанавливали MediaWiki и пытаетесь произвести повторную установку.
-Пожалуйста, перейдите на следующую страницу.",
- 'config-install-begin' => 'Нажав «{{int:config-continue}}», вы начнёте установку MediaWiki.
-Если вы хотите внести изменения, нажмите «{{int:config-back}}».',
- 'config-install-step-done' => 'выполнено',
- 'config-install-step-failed' => 'не удалось',
- 'config-install-extensions' => 'В том числе расширения',
- 'config-install-database' => 'Настройка базы данных',
- 'config-install-schema' => 'Создание схемы',
- 'config-install-pg-schema-not-exist' => 'Схемы PostgreSQL не существует',
- 'config-install-pg-schema-failed' => 'Не удалось создать таблицы.
-Убедитесь в том, что пользователь «$1» может писать в схему «$2».',
- 'config-install-pg-commit' => 'Внесение изменений',
- 'config-install-pg-plpgsql' => 'Проверка языка PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Вам необходимо установить поддержку языка PL/pgSQL для базы данных $1',
- 'config-pg-no-create-privs' => 'Учётная запись, указанная для установки, не обладает достаточными привилегиями для создания учётной записи.',
- 'config-pg-not-in-role' => 'Указанная учётная запись веб-пользователя уже существует.
-Указанная для установки учётная запись не является записью суперпользователя, и не относится к роли веб-пользователя, поэтому не получается создать объекты, принадлежащие веб-пользователю.
-
-MediaWiki в настоящее время требует, чтобы владельцем таблиц был веб-пользователь. Пожалуйста, укажите другое имя учётной записи для веб, или нажмите кнопку «назад» и укажите пользователя с достаточными для установки правами.',
- 'config-install-user' => 'Создание базы данных пользователей',
- 'config-install-user-alreadyexists' => 'Участник «$1» уже существует',
- 'config-install-user-create-failed' => 'Не получилось создать участника «$1»: $2',
- 'config-install-user-grant-failed' => 'Ошибка предоставления прав пользователю «$1»: $2',
- 'config-install-user-missing' => 'Указанного пользователя «$1» не существует.',
- 'config-install-user-missing-create' => 'Указанного пользователя «$1» не существует.
-Пожалуйста поставьте ниже отметку «Создать учётную запись», если вы хотите создать его.',
- 'config-install-tables' => 'Создание таблиц',
- 'config-install-tables-exist' => "'''Предупреждение''': таблицы MediaWiki, возможно, уже существуют.
-Пропуск повторного создания.",
- 'config-install-tables-failed' => "'''Ошибка''': Таблица не может быть создана из-за ошибки: $1",
- 'config-install-interwiki' => 'Заполнение таблицы интервики значениями по умолчанию',
- 'config-install-interwiki-list' => 'Не удалось найти файл <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Предупреждение''': в интервики-таблице, кажется, уже есть записи.
-Создание стандартного списка пропущено.",
- 'config-install-stats' => 'Статистика инициализации',
- 'config-install-keys' => 'Создание секретных ключей',
- 'config-insecure-keys' => "'''Предупреждение.''' {{PLURAL:$2|Ключ безопасности $1, созданный во время установки, недостаточно надёжен|Ключи безопасности $1, созданные во время установки, недостаточно надёжны}}. Рассмотрите возможность {{PLURAL:$2|его|их}} изменения вручную.",
- 'config-install-sysop' => 'Создание учётной записи администратора',
- 'config-install-subscribe-fail' => 'Не удаётся подписаться на mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL не установлен и не доступна опция allow_url_fopen.',
- 'config-install-mainpage' => 'Создание главной страницы с содержимым по умолчанию',
- 'config-install-extension-tables' => 'Создание таблиц для включённых расширений',
- 'config-install-mainpage-failed' => 'Не удаётся вставить главную страницу: $1',
- 'config-install-done' => "'''Поздравляем!'''
-Вы успешно установили MediaWiki.
-
-Во время установки был создан файл <code>LocalSettings.php</code>.
-Он содержит всю конфигурации вики.
-
-Вам необходимо скачать его и положить в корневую директорию вашей вики (ту же директорию, где находится файл index.php). Его загрузка должна начаться автоматически.
-
-Если автоматическая загрузка не началась или вы её отменили, вы можете скачать по ссылке ниже:
-
-$3
-
-'''Примечание''': Если вы не сделаете этого сейчас, то сгенерированный файл конфигурации не будет доступен вам в дальнейшем, если вы выйдете из установки, не скачивая его.
-
-По окончании действий, описанных выше, вы сможете '''[$2 войти в вашу вики]'''.",
- 'config-download-localsettings' => 'Загрузить <code>LocalSettings.php</code>',
- 'config-help' => 'справка',
- 'config-nofile' => 'Файл "$1" не удается найти. Он был удален?',
- 'config-extension-link' => 'Знаете ли вы, что ваш вики-проект поддерживает [//www.mediawiki.org/wiki/Manual:Extensions расширения]?
-
-Вы можете просмотреть [//www.mediawiki.org/wiki/Category:Extensions_by_category расширения по категориям] или [//www.mediawiki.org/wiki/Extension_Matrix матрицу расширений], чтобы увидеть их полный список.',
- 'mainpagetext' => "'''Вики-движок «MediaWiki» успешно установлен.'''",
- 'mainpagedocfooter' => 'Информацию по работе с этой вики можно найти в [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 справочном руководстве].
-
-== Некоторые полезные ресурсы ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Список возможных настроек];
-* [//www.mediawiki.org/wiki/Manual:FAQ Часто задаваемые вопросы и ответы по MediaWiki];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка уведомлений о выходе новых версий MediaWiki].
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Перевод MediaWiki на свой язык]',
-);
-
-/** Rusyn (русиньскый)
- * @author Gazeb
- */
-$messages['rue'] = array(
- 'mainpagetext' => "'''MediaWiki была успішно наіншталована.'''",
- 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents Мануял хоснователя] Вам порадить, як хосновати MediaWiki.
-
-== Про початок ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Наставлїня конфіґурації]
-* [//www.mediawiki.org/wiki/Manual:FAQ Часты вопросы о MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розосыланя повідомлїнь про новы верзії MediaWiki]', # Fuzzy
-);
-
-/** Sanskrit (संस्कृतम्)
- * @author Hemant wikikosh1
- */
-$messages['sa'] = array(
- 'mainpagetext' => 'मीडियाविकि तु सफलतया अन्तःस्थापितमस्ति',
-);
-
-/** Sakha (саха тыла)
- */
-$messages['sah'] = array(
- 'mainpagetext' => "'''«MediaWiki» сөпкө туруорулунна.'''",
- 'mainpagedocfooter' => 'Биики программатын туһунан [//meta.wikimedia.org/wiki/Help:Contents справочникка] көрүөххүн сөп.
-
-== Саҕаланыыта ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Конфигурация уларытыытын параметрдара]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki релизтарын почтовай испииһэгэ]', # Fuzzy
-);
-
-/** Sardinian (sardu)
- * @author Andria
- */
-$messages['sc'] = array(
- 'mainpagetext' => "'''MediaWiki est stadu installadu in modu currègidu.'''",
-);
-
-/** Sicilian (sicilianu)
- */
-$messages['scn'] = array(
- 'mainpagetext' => "'''Nstallazzioni di MediaWiki cumplitata currettamenti.'''",
- 'mainpagedocfooter' => "Pi favuri taliari [//meta.wikimedia.org/wiki/Help:Contents Guida utenti] pi aiutu supra l'usu e la cunfigurazzioni di stu software wiki.
-
-== P'accuminzari ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Alencu di mpustazzioni di cunfigurazzioni]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list dî rilassi di MediaWiki]", # Fuzzy
-);
-
-/** Scots (Scots)
- */
-$messages['sco'] = array(
- 'mainpagetext' => "'''MediaWiki haes been installit wi speed.'''",
- 'mainpagedocfooter' => "Aks the [//meta.wikimedia.org/wiki/Help:Contents Uiser's Manual] for speirins aboot using the wiki saftware.
-
-== Gettin startit ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settins leet]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki releese mailin leet]", # Fuzzy
-);
-
-/** Sassaresu (Sassaresu)
- */
-$messages['sdc'] = array(
- 'mainpagetext' => "'''Isthallazioni di MediaWiki accabadda currentementi.'''",
- 'mainpagedocfooter' => "Cunsultha la [//meta.wikimedia.org/wiki/Aggiuddu:Summàriu Ghia utenti] pa maggiori infuimmazioni i l'usu di chisthu software wiki.
-
-== Pa ischuminzà ==
-Li sighenti cullegamenti so in linga ingrese:
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Impusthazioni di cunfigurazioni]
-* [//www.mediawiki.org/wiki/Manual:FAQ Prigonti friquenti i MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annùnzii MediaWiki]", # Fuzzy
-);
-
-/** Cmique Itom (Cmique Itom)
- */
-$messages['sei'] = array(
- 'mainpagetext' => "'''MediaWiki coccebj installöx successua zo mii.'''",
-);
-
-/** Serbo-Croatian (srpskohrvatski / српскохрватски)
- * @author OC Ripper
- */
-$messages['sh'] = array(
- 'mainpagetext' => "'''MediaWiki softver is uspješno instaliran.'''",
- 'mainpagedocfooter' => 'Kontaktirajte [//meta.wikimedia.org/wiki/Help:Contents uputstva za korisnike] za informacije o upotrebi wiki programa.
-
-== Početak ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista postavki]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki najčešće postavljana pitanja]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]', # Fuzzy
-);
-
-/** Tachelhit (Tašlḥiyt/ⵜⴰⵛⵍⵃⵉⵜ)
- * @author Dalinanir
- */
-$messages['shi'] = array(
- 'mainpagetext' => "'''MediaWiki tǧizn (tsrbk) bla tamukrist.'''",
- 'mainpagedocfooter' => 'Ẓr taǧttnn [//meta.wikimedia.org/wiki/Aide:Contenu Guide de l’utilisateur] bac ad tawit inɣmisn yaḍn f manik sa tswwurt asɣẓan ad.
-
-== Izwir d MediaWiki ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Umuɣ n iɣwwarn n usgadda ]
-* [//www.mediawiki.org/wiki/Manual:FAQ/fr Isqqsitn f MidyWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Umuɣ n imsgdaln f imbḍitn n MidyaWiki]', # Fuzzy
-);
-
-/** Sinhala (සිංහල)
- * @author Singhalawap
- * @author පසිඳු කාවින්ද
- */
-$messages['si'] = array(
- 'config-desc' => 'මාධ්‍යවිකි සඳහා වූ ස්ථාපකය',
- 'config-title' => 'මාධ්‍යවිකි $1 ස්ථාපනය',
- 'config-information' => 'තොරතුරු',
- 'config-localsettings-key' => 'උසස්කිරීම් යතුර:',
- 'config-localsettings-badkey' => 'ඔබ ඉදිරිපත් කෙරූ යතුර වැරදිය.',
- 'config-session-error' => 'සැසිය ඇරඹීමේ දෝෂය: $1',
- 'config-your-language' => 'ඔබේ භාෂාව:',
- 'config-wiki-language' => 'විකි භාෂාව:',
- 'config-back' => '← ආපසු',
- 'config-continue' => 'ඉදිරියට →',
- 'config-page-language' => 'භාෂාව',
- 'config-page-welcome' => 'මාධ්‍යවිකි වෙත පිළිගනිමු!',
- 'config-page-dbconnect' => 'දත්ත සංචිතයට සම්බන්ධ කරන්න',
- 'config-page-upgrade' => 'පවත්නා ස්ථාපනය උසස් කරන්න',
- 'config-page-dbsettings' => 'දත්ත සංචිත සැකසුම්',
- 'config-page-name' => 'නම',
- 'config-page-options' => 'විකල්ප',
- 'config-page-install' => 'ස්ථාපනය',
- 'config-page-complete' => 'සම්පූර්ණයි!',
- 'config-page-restart' => 'ස්ථාපනය යළි අරඹන්න',
- 'config-page-readme' => 'මාව කියවන්න',
- 'config-page-releasenotes' => 'නිකුතු සටහන්',
- 'config-page-copying' => 'පිටපත් කරමින්',
- 'config-page-upgradedoc' => 'උසස් කරමින්',
- 'config-page-existingwiki' => 'පවත්නා විකිය',
- 'config-env-php' => 'PHP $1 ස්ථාපිතයි.',
- 'config-db-type' => 'දත්ත සංචිත වර්ගය:',
- 'config-db-host' => 'දත්ත සංචිත ධාරක:',
- 'config-db-wiki-settings' => 'මෙම විකිය හඳුනා ගන්න',
- 'config-db-name' => 'දත්ත සංචිතයේ නම:',
- 'config-db-name-oracle' => 'දත්ත සංචිත සංක්ෂිප්ත නිරූපණය:',
- 'config-db-install-account' => 'ස්ථාපනය සඳහා පරිශීලක ගිණුම',
- 'config-db-username' => 'දත්ත සංචිතයේ පරිශීලක නාමය:',
- 'config-db-password' => 'දත්ත සංචිතයේ මුරපදය:',
- 'config-db-wiki-account' => 'සාමාන්‍ය ක්‍රියාකාරිත්වය සඳහා පරිශීලක ගිණුම',
- 'config-db-prefix' => 'දත්ත සංචිත වගු උපසර්ගය:',
- 'config-db-charset' => 'දත්ත සංචිත අක්ෂර කට්ටලය',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 ද්විමය',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 ආපස්සට-ගැළපෙන UTF-8',
- 'config-db-port' => 'දත්ත සංචිතයේ කවුළුව:',
- 'config-db-schema' => 'මාධ්‍යවිකි සඳහා සංක්ෂිප්ත නිරූපණය:',
- 'config-pg-test-error' => "'''$1''' දත්ත සංචිතය වෙත සම්බන්ධ විය නොහැක: $2",
- 'config-sqlite-dir' => 'SQLite දත්ත නාමවලිය:',
- 'config-oracle-def-ts' => 'සාමාන්‍ය වගු අවකාශය:',
- 'config-oracle-temp-ts' => 'තාවකාලික වගු අවකාශය:',
- 'config-header-mysql' => 'MySQL සැකසුම්',
- 'config-header-postgres' => 'PostgreSQL සැකසුම්',
- 'config-header-sqlite' => 'SQLite සැකසුම්',
- 'config-header-oracle' => 'ඔරකල් සැකසුම්',
- 'config-invalid-db-type' => 'වලංගු නොවන දත්ත සංචිත වර්ගය',
- 'config-missing-db-name' => '"දත්ත සංචිත නාමය" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ',
- 'config-missing-db-host' => '"දත්ත සංචිත ධාරකය" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ',
- 'config-missing-db-server-oracle' => '"දත්ත සංචිත TNS" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ',
- 'config-regenerate' => 'නැවත ජනිත කරන්න LocalSettings.php →',
- 'config-db-web-account' => 'ජාල ප්‍රවේශනය සඳහා දත්ත සංචිත ගිණුම',
- 'config-mysql-engine' => 'ආචයන එන්ජිම:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-binary' => 'ද්විමය',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'විකියෙහි නම:',
- 'config-site-name-blank' => 'අඩවි නාමයක් යොදන්න.',
- 'config-project-namespace' => 'ව්‍යාපෘතියේ නාමඅවකාශය:',
- 'config-ns-generic' => 'ව්‍යාපෘතිය',
- 'config-ns-site-name' => 'විකියෙහි නම ලෙසම: $1',
- 'config-ns-other' => 'වෙනත් (විශේෂණය කරන්න)',
- 'config-ns-other-default' => 'මගේවිකිය',
- 'config-admin-box' => 'පරිපාලක ගිණුම',
- 'config-admin-name' => 'ඔබේ නම:',
- 'config-admin-password' => 'මුරපදය:',
- 'config-admin-password-confirm' => 'මුරපදය නැවතත්:',
- 'config-admin-name-blank' => 'පරිපාලක පරිශීලක නාමය යොදන්න.',
- 'config-admin-password-blank' => 'පරිපාලක ගිණුම සඳහා මුරපදය යොදන්න.',
- 'config-admin-password-same' => 'මුරපදය හා පරිශීලක නාමය එක සමාන නොවිය යුතුය.',
- 'config-admin-password-mismatch' => 'ඔබ ඇතුළු කල මුරපද දෙක නොගැලපේ.',
- 'config-admin-email' => 'විද්‍යුත්-තැපැල් ලිපිනය:',
- 'config-admin-error-bademail' => 'ඔබ විසින් වලංගු නොවන විද්‍යුත්-ලිපිනයක් යොදා ඇත.',
- 'config-optional-continue' => 'මගෙන් තව ප්‍රශ්ණ අහන්න.',
- 'config-optional-skip' => 'මම දැනටමත් කම්මැලි වී ඇත, විකිය ස්ථාපනය කරන්න.',
- 'config-profile' => 'පරිශීලක හිමිකම් පැතිකඩ:',
- 'config-profile-wiki' => 'සාම්ප්‍රදායික විකිය', # Fuzzy
- 'config-profile-no-anon' => 'ගිණුම් තැනීම අවශ්‍යයි',
- 'config-profile-fishbowl' => 'අවසරලත් සංස්කාරකවරුන් පමණි',
- 'config-profile-private' => 'පුද්ගලික විකිය',
- 'config-license' => 'කතුහිමිකම සහ බලපත්‍රය:',
- 'config-license-none' => 'බලපත්‍ර පාද තලයක් නොමැත',
- 'config-license-cc-by-sa' => 'නිර්මාණාත්මක පොදුජන ආරෝපණය හුවමාරුවට සමානව',
- 'config-license-cc-by' => 'නිර්මාණාත්මක පොදුජන ආරෝපණය',
- 'config-license-cc-by-nc-sa' => 'නිර්මාණාත්මක පොදුජන ආරෝපණය වාණිජ්‍ය-නොවන හුවමාරුවට සමානව',
- 'config-license-pd' => 'පොදු වසම',
- 'config-email-settings' => 'විද්‍යුත්-තැපැල් සැකසුම්',
- 'config-enable-email' => 'පිටතට යොමු වූ විද්‍යුත්-තැපෑල සක්‍රිය කරන්න',
- 'config-email-user' => 'පරිශීලක-වෙත-පරිශීලක විද්‍යුත්-තැපෑල සක්‍රිය කරන්න',
- 'config-email-usertalk' => 'පරිශීලක කතාබහ පිටු නිවේදනය සක්‍රිය කරන්න',
- 'config-email-watchlist' => 'මුරලැයිස්තු නිවේදනය සක්‍රිය කරන්න',
- 'config-email-auth' => 'විද්‍යුත්-තැපැල් සහතික කිරීම සක්‍රිය කරන්න',
- 'config-email-sender' => 'ප්‍රත්‍යාගමන විද්‍යුත්-තැපැල් ලිපිනය:',
- 'config-upload-settings' => 'පින්තූර සහ ගොනු උඩුගත කිරීම්',
- 'config-upload-enable' => 'ගොනු උඩුගත කිරීම් සක්‍රිය කරන්න',
- 'config-upload-deleted' => 'මැකූ ගොනු සඳහා නාමාවලිය:',
- 'config-logo' => 'ලාංඡනයේ URL:',
- 'config-instantcommons' => 'ක්ෂණික කොමන්ස් සක්‍රිය කරන්න',
- 'config-cc-again' => 'නැවත ඇහිඳගන්න...',
- 'config-advanced-settings' => 'උසස් වින්‍යාසගතකෙරුම',
- 'config-cache-options' => 'වස්තු කෑෂය සඳහා සැකසුම්:',
- 'config-memcached-servers' => 'මතකකෑෂිත සර්වරයන්:',
- 'config-extensions' => 'විස්තීර්ණ',
- 'config-install-step-done' => 'සිදුකලා',
- 'config-install-step-failed' => 'අසාර්ථකයි',
- 'config-install-extensions' => 'විස්තීර්ණ අඩංගු කරමින්',
- 'config-install-database' => 'දත්ත සංචිතය සකසමින්',
- 'config-install-schema' => 'සංක්ෂිප්ත නිරූපණය තනමින්',
- 'config-install-pg-schema-not-exist' => 'PostgreSQL සංක්ෂිප්ත නිරූපණය නොපවතියි.',
- 'config-install-pg-commit' => 'වෙනස්කම් ප්‍රයාපනය කරමින්',
- 'config-install-pg-plpgsql' => 'PL/pgSQL භාෂාව සඳහා පරික්ෂා කරමින්',
- 'config-install-user' => 'දත්ත සංචිත පරිශීලක තනමින්',
- 'config-install-user-alreadyexists' => '"$1" පරිශීලක දැනටමත් පවතී',
- 'config-install-user-create-failed' => '"$1" පරිශීලක තැනීම අසාර්ථකයි: $2',
- 'config-install-user-missing' => 'විශේෂණය කෙරූ "$1" පරිශීලකයා නොපවතියි.',
- 'config-install-tables' => 'වගු තනමින්',
- 'config-install-interwiki' => 'සාමාන්‍ය අන්තර්විකි වගුව ගහනය කරමින්',
- 'config-install-interwiki-list' => '<code>interwiki.list</code> ගොනුව කියවිය නොහැක.',
- 'config-install-stats' => 'සංඛ්‍යානය අරඹමින්',
- 'config-install-keys' => 'රහස් යතුරු ජනිත කරමින්',
- 'config-install-sysop' => 'පරිපාලක පරිශීලක ගිණුම තනමින්',
- 'config-install-mainpage' => 'සාමාන්‍ය අන්තර්ගතය සමඟින් ප්‍රධාන පිටුව තනමින්',
- 'config-install-mainpage-failed' => 'ප්‍රධාන පිටුව ඇතුල් කල නොහැක: $1',
- 'config-download-localsettings' => '<code>LocalSettings.php</code> බාගන්න',
- 'config-help' => 'උදව්',
- 'config-nofile' => '"$1" ගොනුව සොයාගත නොහැක. එක මැකිලා ගියාවත්ද?',
- 'mainpagetext' => "'''මීඩියාවිකි සාර්ථක ලෙස ස්ථාපනය කරන ලදි.'''",
- 'mainpagedocfooter' => 'විකි මෘදුකාංග භාවිතා කිරීම පිළිබඳ තොරතුරු සඳහා [//meta.wikimedia.org/wiki/Help:Contents පරිශීලකයන් සඳහා නියමුව] හදාරන්න.
-
-== ඇරඹුම ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings වින්‍යාස සැකසුම්]
-* [//www.mediawiki.org/wiki/Manual:FAQ මීඩියාවිකි නිති-විමසන-පැන]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce මීඩියාවිකි නිකුතුව තැපැල් ලැයිස්තුව]', # Fuzzy
-);
-
-/** Slovak (slovenčina)
- * @author Kusavica
- */
-$messages['sk'] = array(
- 'config-your-language' => 'Váš jazyk:',
- 'config-wiki-language' => 'Wiki jazyk:',
- 'config-back' => '← Späť',
- 'config-continue' => 'Pokračovať →',
- 'config-page-language' => 'Jazyk',
- 'config-download-localsettings' => 'Stiahnuť <code>LocalSettings.php</code>',
- 'config-nofile' => 'Súbor "$1" sa nenašiel. Bol zmazaný?',
- 'mainpagetext' => "'''Softvér MediaWiki bol úspešne nainštalovaný.'''",
- 'mainpagedocfooter' => 'Informácie ako používať wiki softvér nájdete v [//meta.wikimedia.org/wiki/Help:Contents Používateľskej príručke].
-
-== Začíname ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Zoznam konfiguračných nastavení]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailing list nových verzií MediaWiki]', # Fuzzy
-);
-
-/** Slovenian (slovenščina)
- * @author Dbc334
- * @author Eleassar
- */
-$messages['sl'] = array(
- 'config-desc' => 'Namestitveni program za MediaWiki',
- 'config-title' => 'Namestitev MediaWiki $1',
- 'config-information' => 'Informacije',
- 'config-localsettings-cli-upgrade' => 'Zaznana je bila datoteka <code>LocalSettings.php</code>.
-Za nadgradnjo te namestitve zaženite <code>update.php</code>',
- 'config-localsettings-key' => 'Nadgraditveni ključ:',
- 'config-localsettings-badkey' => 'Naveden ključ je napačen.',
- 'config-upgrade-key-missing' => 'Zaznana je bila obstoječa namestitev MediaWiki.
-Za nadgradnjo te namestitve vstavite naslednjo vrstico na dno vaše <code>LocalSettings.php</code>:
-
-$1',
- 'config-session-error' => 'Napaka pri začenjanju seje: $1',
- 'config-session-expired' => 'Kot kaže, so vaši podatki seje potekli.
-Seje so konfigurirane za dobo $1.
-To lahko povečate tako, da nastavite <code>session.gc_maxlifetime</code> v php.ini.
-Ponovno zaženite postopek namestitve.',
- 'config-no-session' => 'Vaši podatki seje so bili izgubljeni!
-Preverite vaš php.ini in se prepričajte, da je <code>session.save_path</code> nastavljena na ustrezno mapo.',
- 'config-your-language' => 'Vaš jezik:',
- 'config-your-language-help' => 'Izberite jezik, ki bo uporabljen med postopkom namestitve.',
- 'config-wiki-language' => 'Jezik wikija:',
- 'config-wiki-language-help' => 'Izberite jezik, v katerem bo wiki večinoma pisan.',
- 'config-back' => '← Nazaj',
- 'config-continue' => 'Nadaljuj →',
- 'config-page-language' => 'Jezik',
- 'config-page-welcome' => 'Dobrodošli na MediaWiki!',
- 'config-page-dbconnect' => 'Vzpostavi povezavo z zbirko podatkov',
- 'config-page-upgrade' => 'Nadgradi obstoječo namestitev',
- 'config-page-dbsettings' => 'Nastavitve zbirke podatkov',
- 'config-page-name' => 'Ime',
- 'config-page-options' => 'Možnosti',
- 'config-page-install' => 'Namesti',
- 'config-page-complete' => 'Končano!',
- 'config-page-restart' => 'Ponovno zaženi namestitev',
- 'config-page-readme' => 'Beri me',
- 'config-page-releasenotes' => 'Opombe ob izidu',
- 'config-page-copying' => 'Kopiranje',
- 'config-page-upgradedoc' => 'Nadgrajevanje',
- 'config-page-existingwiki' => 'Obstoječ wiki',
- 'config-help-restart' => 'Želite počistiti vse shranjene podatke, ki ste jih vnesti, in ponovno začeti s postopkom namestitve?',
- 'config-restart' => 'Da, ponovno zaženi',
- 'config-welcome' => '=== Pregledi okolja ===
-Izvedli bomo osnovne preglede, da vidimo, če je okolje primerno za namestitev MediaWiki.
-Posredujte rezultate teh pregledov, če med namestitvijo potrebujete pomoč.',
- 'config-sidebar' => '* [//www.mediawiki.org Domača stran MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Vodnik za uporabnike]
-* [//www.mediawiki.org/wiki/Manual:Contents Vodnik za administratorje]
-* [//www.mediawiki.org/wiki/Manual:FAQ Pogosto zastavljena vprašanja]
-----
-* <doclink href=Readme>Beri me</doclink>
-* <doclink href=ReleaseNotes>Opombe ob izidu</doclink>
-* <doclink href=Copying>Kopiranje</doclink>
-* <doclink href=UpgradeDoc>Nadgrajevanje</doclink>',
- 'config-env-good' => 'Okolje je pregledano.
-Lahko namestite MediaWiki.',
- 'config-env-bad' => 'Okolje je pregledano.
-Ne morete namestiti MediaWiki.',
- 'config-env-php' => 'Nameščen je PHP $1.',
- 'config-env-php-toolow' => 'Nameščen je PHP $1.
-Vendar pa MediaWiki zahteva PHP $2 ali višji.',
- 'config-unicode-using-utf8' => 'Uporaba utf8_normalize.so Briona Vibberja za normalizacijo unikoda.',
- 'config-unicode-using-intl' => 'Uporaba [http://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-xcache' => '[http://xcache.lighttpd.net/ XCache] je nameščen',
- 'config-apc' => '[http://www.php.net/apc APC] je nameščen',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] je nameščen',
- 'config-diff3-bad' => 'GNU diff3 ni bilo mogoče najti.',
- 'config-db-type' => 'Vrsta zbirke podatkov:',
- 'config-db-host' => 'Gostitelj zbirke podatkov:',
- 'config-db-host-oracle' => 'TNS zbirke podatkov:',
- 'config-db-wiki-settings' => 'Prepoznaj ta wiki:',
- 'config-db-name' => 'Ime zbirke podatkov:',
- 'config-db-name-oracle' => 'Shema zbirke podatkov:',
- 'config-db-username' => 'Uporabniško ime zbirke podatkov:',
- 'config-db-password' => 'Geslo zbirke podatkov:',
- 'config-db-prefix' => 'Predpona tabel zbirke podatkov:',
- 'config-db-charset' => 'Nabor znakov zbirke podatkov',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 dvojiško',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 nazaj združljiv UTF-8',
- 'config-mysql-old' => 'Potreben je MySQL $1 ali novejši; vi imate $2.',
- 'config-db-port' => 'Vrata zbirke podatkov:',
- 'config-db-schema' => 'Shema MediaWiki',
- 'config-db-schema-help' => 'Ta shema je po navadi v redu.
-Spremenite jo samo, če veste, da jo morate.',
- 'config-sqlite-dir' => 'Mapa podatkov SQLite:',
- 'config-support-info' => 'MediaWiki podpira naslednje sisteme zbirk podatkov:
-
-$1
-
-Če zgoraj ne vidite navedenega sistema zbirk podatkov, ki ga poskušate uporabiti, sledite navodilom na spodnji povezavi, da omogočite podporo.',
- 'config-header-mysql' => 'Nastavitve MySQL',
- 'config-header-postgres' => 'Nastavitve PostgreSQL',
- 'config-header-sqlite' => 'Nastavitve SQLite',
- 'config-header-oracle' => 'Nastavitve Oracle',
- 'config-invalid-db-type' => 'Neveljavna vrsta zbirke podatkov',
- 'config-missing-db-name' => 'Vnesti morate vrednost za »Ime zbirke podatkov«',
- 'config-missing-db-host' => 'Vnesti morate vrednost za »Gostitelj zbirke podatkov«',
- 'config-missing-db-server-oracle' => 'Vnesti morate vrednost za »TNS zbirke podatkov«',
- 'config-invalid-db-server-oracle' => 'Neveljaven TNS zbirke podatkov »$1«.
-Uporabite ali "ime TNS" ali niz "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Načini poimenovanja Oracle])',
- 'config-invalid-db-name' => 'Neveljavno ime zbirke podatkov »$1«.
-Uporabljajte samo črke ASCII (a-z, A-Z), številke (0-9), podčrtaje (_) in vezaje (-).',
- 'config-invalid-db-prefix' => 'Neveljavna predpona zbirke podatkov »$1«.
-Uporabljajte samo črke ASCII (a-z, A-Z), številke (0-9), podčrtaje (_) in vezaje (-).',
- 'config-connection-error' => '$1.
-
-Preverite gostitelja, uporabniško ime in geslo spodaj ter poskusite znova.',
- 'config-postgres-old' => 'Potreben je PostgreSQL $1 ali novejši; vi imate $2.',
- 'config-sqlite-connection-error' => '$1.
-
-Preverite mapo podatkov in ime zbirke podatkov spodaj ter poskusite znova.',
- 'config-sqlite-readonly' => 'Datoteka <code>$1</code> ni zapisljiva.',
- 'config-sqlite-cant-create-db' => 'Ne morem ustvariti datoteke zbirke podatkov <code>$1</code>.',
- 'config-upgrade-done-no-regenerate' => 'Nadgradnja je končana.
-
-Sedaj lahko [$1 začnete uporabljati vaš wiki].',
- 'config-regenerate' => 'Ponovno ustvari LocalSettings.php →',
- 'config-show-table-status' => 'Poizvedba <code>SHOW TABLE STATUS</code> ni uspela!',
- 'config-unknown-collation' => "'''Opozorilo:''' Zbirke podatkov uporablja neprepoznano razvrščanje znakov.",
- 'config-db-web-account' => 'Račun zbirke podatkov za spletni dostop',
- 'config-db-web-account-same' => 'Uporabi enak račun kot za namestitev',
- 'config-db-web-create' => 'Ustvari račun, če že ne obstaja',
- 'config-mysql-engine' => 'Pogon skladiščenja:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-charset' => 'Nabor znakov zbirke podatkov:',
- 'config-mysql-binary' => 'Dvojiško',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Ime wikija:',
- 'config-site-name-help' => 'To bo prikazano v naslovni vrstici brskalnika in na drugih različnih mestih.',
- 'config-site-name-blank' => 'Vnesite ime strani.',
- 'config-project-namespace' => 'Imenski prostor projekta:',
- 'config-ns-generic' => 'Projekt',
- 'config-ns-site-name' => 'Enako kot ime wikija: $1',
- 'config-ns-other' => 'Drugo (navedite)',
- 'config-ns-other-default' => 'MojWiki',
- 'config-ns-invalid' => 'Naveden imenski prostor »<nowiki>$1</nowiki>« ni veljaven.
-Določite drug imenski prostor projekta.',
- 'config-ns-conflict' => 'Naveden imenski prostor »<nowiki>$1</nowiki>« je v sporu s privzetim imenskim prostorom MediaWiki.
-Določite drug imenski prostor projekta.',
- 'config-admin-box' => 'Administratorski račun',
- 'config-admin-name' => 'Vaše ime:',
- 'config-admin-password' => 'Geslo:',
- 'config-admin-password-confirm' => 'Geslo, ponovno:',
- 'config-admin-help' => 'Tukaj vnesite želeno uporabniško ime, na primer »Janez Blog«.
-To je ime, ki ga boste uporabljali za prijavo v wiki.',
- 'config-admin-name-blank' => 'Vnesite uporabniško ime administratorja.',
- 'config-admin-name-invalid' => 'Navedeno uporabniško ime »<nowiki>$1</nowiki>« ni veljavno.
-Določite drugo uporabniško ime.',
- 'config-admin-password-blank' => 'Vnesite geslo za administratorski račun.',
- 'config-admin-password-same' => 'Geslo ne sme biti enako kot uporabniško ime.',
- 'config-admin-password-mismatch' => 'Vneseni gesli se ne ujemata.',
- 'config-admin-email' => 'E-poštni naslov:',
- 'config-admin-error-user' => 'Med ustvarjanjem administratorja »<nowiki>$1</nowiki>« je prišlo do notranje napake.',
- 'config-admin-error-password' => 'Med nastavljanjem gesla za administratorja »<nowiki>$1</nowiki>« je prišlo do notranje napake: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Vnesli ste neveljaven e-poštni naslov.',
- 'config-subscribe' => 'Naročite se na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce poštni seznam obvestil o izdajah].',
- 'config-almost-done' => 'Skoraj ste že končali!
-Preostalo konfiguriranje lahko zdaj preskočite in wiki takoj namestite.',
- 'config-optional-continue' => 'Zastavi mi več vprašanj.',
- 'config-optional-skip' => 'Se že dolgočasim; samo namesti wiki.',
- 'config-profile' => 'Profil uporabniških pravic:',
- 'config-profile-wiki' => 'Odprti wiki',
- 'config-profile-no-anon' => 'Zahtevano je ustvarjanje računa',
- 'config-profile-fishbowl' => 'Samo pooblaščeni urejevalci',
- 'config-profile-private' => 'Zasebni wiki',
- 'config-license' => 'Avtorske pravice in licenca:',
- 'config-license-none' => 'Brez noge dovoljenja',
- 'config-license-cc-by-sa' => 'Creative Commons Priznanje avtorstva-Deljenje pod enakimi pogoji',
- 'config-license-cc-by' => 'Creative Commons Priznanje avtorstva',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Priznanje avtorstva-Nekomercialno-Deljenje pod enakimi pogoji',
- 'config-license-cc-0' => 'Creative Commons Zero (javna last)',
- 'config-license-pd' => 'Javna last',
- 'config-license-cc-choose' => 'Izberite dovoljenje Creative Commons po meri',
- 'config-email-settings' => 'Nastavitve e-pošte',
- 'config-enable-email' => 'Omogoči odhodno e-pošto',
- 'config-email-user' => 'Omogoči e-pošto med uporabniki',
- 'config-email-auth' => 'Omogoči overitev preko e-pošte',
- 'config-email-sender' => 'E-poštni naslov za vrnjeno pošto:',
- 'config-upload-settings' => 'Nalaganje slike in datotek',
- 'config-upload-enable' => 'Omogoči nalaganje datotek',
- 'config-upload-deleted' => 'Mapa za izbrisane datoteke:',
- 'config-upload-deleted-help' => 'Izberite mapo za arhiviranje izbrisanih datotek.
-Najbolje je, da mapa ni dostopna preko spleta.',
- 'config-logo' => 'URL logotipa:',
- 'config-cc-error' => 'Izbirnik dovoljenja Creative Commons ni vrnil nobenih rezultatov.
-Vnesite ime dovoljenja ročno.',
- 'config-cc-again' => 'Izberi ponovno ...',
- 'config-cc-not-chosen' => 'Izberite licenco Creative Commons, ki jo želite uporabiti, in kliknite »proceed«.',
- 'config-advanced-settings' => 'Napredna konfiguracija',
- 'config-cache-accel' => 'Predpomnjenje predmetov PHP (APC, XCache ali WinCache)',
- 'config-cache-memcached' => 'Uporabi Memcached (zahteva dodatno namestitev in konfiguracijo)',
- 'config-memcached-servers' => 'Strežniki Memcached:',
- 'config-memcache-badip' => 'Vnesli ste neveljaven IP-naslov za Memcached: $1',
- 'config-extensions' => 'Razširitve',
- 'config-install-step-done' => 'končano',
- 'config-install-step-failed' => 'spodletelo',
- 'config-install-database' => 'Vzpostavljanje zbirke podatkov',
- 'config-install-pg-schema-not-exist' => 'Shema PostgreSQL ne obstaja.',
- 'config-install-user-alreadyexists' => 'Uporabnik »$1« že obstaja',
- 'config-install-tables' => 'Ustvarjanje tabel',
- 'config-download-localsettings' => 'Prenesi <code>LocalSettings.php</code>',
- 'config-help' => 'pomoč',
- 'mainpagetext' => "'''Programje MediaWiki je bilo uspešno nameščeno.'''",
- 'mainpagedocfooter' => 'Oglejte si [//meta.wikimedia.org/wiki/Help:Contents Uporabniški priročnik] za informacije o uporabi programja wiki.
-
-== Kako začeti ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Seznam konfiguracijskih nastavitev]
-* [//www.mediawiki.org/wiki/Manual:FAQ Poogsto zastavljena vprašanja MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Poštni seznam izdaj MediaWiki]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Prevedite MediaWiki v svoj jezik]',
-);
-
-/** Lower Silesian (Schläsch)
- * @author Äberlausitzer
- */
-$messages['sli'] = array(
- 'mainpagetext' => "'''MediaWiki wourde erfolgreich installiert.'''",
- 'mainpagedocfooter' => 'Hilfe zur Benutzung und Konfiguration der Wiki-Software fendest du eim [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbichl].
-
-== Stoarthilfa ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste der Konfigurationsvariablen]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]', # Fuzzy
-);
-
-/** Somali (Soomaaliga)
- * @author Maax
- */
-$messages['so'] = array(
- 'mainpagetext' => "'''MediaWiki Si fiican oo kuugu install gareeyay.'''",
- 'mainpagedocfooter' => "Meeshaan ka akhriso sidii aad u isticmaali leheed brogramka wiki [//meta.wikimedia.org/wiki/Help:Contents User's Guide] .
-== Bilaaw ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Albanian (shqip)
- */
-$messages['sq'] = array(
- 'mainpagetext' => "'''MediaWiki software u instalua me sukses.'''",
- 'mainpagedocfooter' => 'Për më shumë informata rreth përdorimit të softwerit wiki , ju lutem shikoni [//meta.wikimedia.org/wiki/Help:Contents dokumentacionin përkatës].
-
-== Sa për fillim==
-* [//www.mediawiki.org/wiki/Help:Configuration_settings Parazgjedhjet e MediaWiki-t]
-* [//www.mediawiki.org/wiki/Help:FAQ Pyetjet e shpeshta rreth MediaWiki-t]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWiki-t]', # Fuzzy
-);
-
-/** Serbian (Cyrillic script) (српски (ћирилица)‎)
- * @author Rancher
- * @author Михајло Анђелковић
- */
-$messages['sr-ec'] = array(
- 'config-session-error' => 'Грешка при започињању сесије: $1',
- 'config-session-expired' => 'Ваши подаци о сесији су истекли.
-Сесије су подешене да трају $1.
-Њихов рок можете повећати постављањем <code>session.gc_maxlifetime</code> у php.ini.
-Поново покрените инсталацију.',
- 'config-no-session' => 'Ваши подаци о сесији су изгубљени!
-Проверите Ваш php.ini и обезбедите да је <code>session.save_path</code> постављен на одговарајући директоријум.',
- 'config-your-language' => 'Ваш језик:',
- 'config-your-language-help' => 'Изаберите језик који желите да користите током инсталације.',
- 'config-wiki-language' => 'Језик викија:',
- 'config-wiki-language-help' => 'Изаберите језик на ком ће бити садржај викија.',
- 'config-back' => '← Назад',
- 'config-continue' => 'Настави →',
- 'config-page-language' => 'Језик',
- 'config-page-welcome' => 'Добро дошли на МедијаВики!',
- 'config-page-dbconnect' => 'Повезивање са базом података',
- 'config-page-upgrade' => 'Надоградња постојеће инсталације',
- 'config-page-dbsettings' => 'Подешавања базе података',
- 'config-page-name' => 'Назив',
- 'config-page-options' => 'Поставке',
- 'config-page-install' => 'Инсталирај',
- 'config-page-complete' => 'Завршено!',
- 'config-page-restart' => 'Поновно покретање инсталације',
- 'config-page-copying' => 'Умножавање',
- 'config-page-upgradedoc' => 'Надоградња',
- 'config-page-existingwiki' => 'Постојећи вики',
- 'config-help-restart' => 'Желите ли да обришете све сачуване податке које сте унели и поново покренете инсталацију?',
- 'config-restart' => 'Да, покрени поново',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'mainpagetext' => "'''Медијавики је успешно инсталиран.'''",
- 'mainpagedocfooter' => 'Погледајте [//meta.wikimedia.org/wiki/Help:Contents кориснички водич] за коришћење програма.
-
-== Увод ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Помоћ у вези са подешавањима]
-* [//www.mediawiki.org/wiki/Manual:FAQ Често постављена питања]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Дописна листа о издањима Медијавикија]', # Fuzzy
-);
-
-/** Serbian (Latin script) (srpski (latinica)‎)
- */
-$messages['sr-el'] = array(
- 'config-session-error' => 'Greška pri započinjanju sesije: $1',
- 'config-session-expired' => 'Vaši podaci o sesiji su istekli.
-Sesije su podešene da traju $1.
-Njihov rok možete povećati postavljanjem <code>session.gc_maxlifetime</code> u php.ini.
-Ponovo pokrenite instalaciju.',
- 'config-no-session' => 'Vaši podaci o sesiji su izgubljeni!
-Proverite Vaš php.ini i obezbedite da je <code>session.save_path</code> postavljen na odgovarajući direktorijum.',
- 'config-your-language' => 'Vaš jezik:',
- 'config-your-language-help' => 'Izaberite jezik koji želite da koristite tokom instalacije.',
- 'config-wiki-language' => 'Jezik vikija:',
- 'config-wiki-language-help' => 'Izaberite jezik na kom će biti sadržaj vikija.',
- 'config-back' => '← Nazad',
- 'config-continue' => 'Nastavi →',
- 'config-page-language' => 'Jezik',
- 'config-page-welcome' => 'Dobro došli na MedijaViki!',
- 'config-page-dbconnect' => 'Povezivanje sa bazom podataka',
- 'config-page-upgrade' => 'Nadogradnja postojeće instalacije',
- 'config-page-dbsettings' => 'Podešavanja baze podataka',
- 'config-page-name' => 'Naziv',
- 'config-page-options' => 'Postavke',
- 'config-page-install' => 'Instaliraj',
- 'config-page-complete' => 'Završeno!',
- 'config-page-restart' => 'Ponovno pokretanje instalacije',
- 'config-page-copying' => 'Umnožavanje',
- 'config-page-upgradedoc' => 'Nadogradnja',
- 'config-page-existingwiki' => 'Postojeći viki',
- 'config-help-restart' => 'Želite li da obrišete sve sačuvane podatke koje ste uneli i ponovo pokrenete instalaciju?',
- 'config-restart' => 'Da, pokreni ponovo',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'mainpagetext' => "'''MedijaViki je uspešno instaliran.'''",
- 'mainpagedocfooter' => 'Molimo vidite [//meta.wikimedia.org/wiki/Help:Contents korisnički vodič] za informacije o upotrebi viki softvera.
-
-== Za početak ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Pomoć u vezi sa podešavanjima]
-* [//www.mediawiki.org/wiki/Manual:FAQ Najčešće postavljena pitanja]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mejling lista o izdanjima MedijaVikija]', # Fuzzy
-);
-
-/** Sranan Tongo (Sranantongo)
- */
-$messages['srn'] = array(
- 'mainpagetext' => "'''MediaWiki seti kon bun.'''",
- 'mainpagedocfooter' => 'Luku na ini a [//meta.wikimedia.org/wiki/Help:Yepi yepibuku] fu si fa fu kebrouki a wikisoftware.
-
-== Moro yepi ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Den seti]
-* [//www.mediawiki.org/wiki/Manual:FAQ Sani di ben aksi furu (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Boskopu grupu gi nyun meki]', # Fuzzy
-);
-
-/** Swati (SiSwati)
- */
-$messages['ss'] = array(
- 'mainpagetext' => "'''i-MediaWiki seyifakeke ngalokuphelele.'''",
-);
-
-/** Seeltersk (Seeltersk)
- * @author Maartenvdbent
- */
-$messages['stq'] = array(
- 'mainpagetext' => "'''Ju MediaWiki Software wuude mäd Ärfoulch installierd.'''",
- 'mainpagedocfooter' => 'Sjuch ju [//meta.wikimedia.org/wiki/MediaWiki_localization Dokumentation tou de Anpaasenge fon dän Benutseruurfläche] un dät [//meta.wikimedia.org/wiki/Help:Contents Benutserhondbouk] foar Hälpe tou ju Benutsenge un Konfiguration.', # Fuzzy
-);
-
-/** Sundanese (Basa Sunda)
- */
-$messages['su'] = array(
- 'mainpagetext' => "'''''Software'' MediaWiki geus diinstal.'''",
- 'mainpagedocfooter' => "Mangga tingal ''[//meta.wikimedia.org/wiki/MediaWiki_localisation documentation on customizing the interface]'' jeung [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Tungtunan Pamaké] pikeun pitulung maké jeung konfigurasi.", # Fuzzy
-);
-
-/** Swedish (svenska)
- * @author Jopparn
- * @author Skalman
- * @author WikiPhoenix
- */
-$messages['sv'] = array(
- 'config-desc' => 'Installationsprogram för MediaWiki',
- 'config-title' => 'Installation av MediaWiki $1',
- 'config-information' => 'Information',
- 'config-localsettings-upgrade' => 'A <code>LocalSettings.php</code>-fil har upptäckts.
-För att uppgradera den här installationen, vänligen ange värdet för <code>$wgUpgradeKey</code> i rutan nedan.
-Du hittar den i <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'En <code>LocalSettings.php</code>-fil har upptäckts.
-För att uppgradera denna installation, kör <code>update.php</code> istället',
- 'config-localsettings-key' => 'Uppgraderingsnyckel:',
- 'config-localsettings-badkey' => 'Nyckeln du angav är inkorrekt.',
- 'config-upgrade-key-missing' => 'En nuvarande installerade av MediaWiki har upptäckts.
-För att uppgradera installationen, lägg till följande rad i slutet av din <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'De befintliga <code>LocalSettings.php</code> verkar vara ofullständig.
-Variabeln $1 är inte inställd.
-Ändra <code>LocalSettings.php</code> så att denna variabel är inställd och klicka på "{{int:Config-continue}}".',
- 'config-session-error' => 'Fel vid uppstart av session: $1',
- 'config-your-language' => 'Ditt språk:',
- 'config-your-language-help' => 'Välj ett språk som ska användas under installationen.',
- 'config-wiki-language' => 'Wikispråk:',
- 'config-wiki-language-help' => 'Välj det språk som wikin främst kommer att skrivas i.',
- 'config-back' => '← Tillbaka',
- 'config-continue' => 'Fortsätt →',
- 'config-page-language' => 'Språk',
- 'config-page-welcome' => 'Välkommen till MediaWiki!',
- 'config-page-dbconnect' => 'Anslut till databas',
- 'config-page-upgrade' => 'Uppgradera existerande installation',
- 'config-page-dbsettings' => 'Databasinställningar',
- 'config-page-name' => 'Namn',
- 'config-page-options' => 'Alternativ',
- 'config-page-install' => 'Installera',
- 'config-page-complete' => 'Slutfört!',
- 'config-page-restart' => 'Starta om installationen',
- 'config-page-readme' => 'Läs mig',
- 'config-page-releasenotes' => 'Utgivningsanteckningar',
- 'config-page-copying' => 'Kopiering',
- 'config-page-upgradedoc' => 'Uppgradering',
- 'config-page-existingwiki' => 'Befintlig wiki',
- 'config-help-restart' => 'Vill du rensa all sparad data som du har skrivit in och starta om installationen?',
- 'config-restart' => 'Ja, starta om',
- 'config-sidebar' => '* [//www.mediawiki.org MediaWikis hemsida]
-* [//www.mediawiki.org/wiki/Help:Contents Användarguide]
-* [//www.mediawiki.org/wiki/Manual:Contents Administratörguide]
-* [//www.mediawiki.org/wiki/Manual:FAQ Frågor och svar]
-----
-* <doclink href=Readme>Läs mig</doclink>
-* <doclink href=ReleaseNotes>Utgivningsanteckningar</doclink>
-* <doclink href=Copying>Kopiering</doclink>
-* <doclink href=UpgradeDoc>Uppgradering</doclink>',
- 'config-env-good' => 'Miljön har kontrollerats.
-Du kan installera MediaWiki.',
- 'config-env-bad' => 'Miljön har kontrollerats.
-Du kan inte installera MediaWiki.',
- 'config-env-php' => 'PHP $1 är installerad.',
- 'config-env-php-toolow' => 'PHP $1 är installerad.
-MediaWiki kräver PHP $2 eller högre.',
- 'config-outdated-sqlite' => "'''Varning:''' du har SQLite $1, vilket är lägre än minimikravet version $2. SQLite kommer inte att vara tillgänglig.",
- 'config-register-globals' => "'''Varning: PHP:s <code>[http://php.net/register_globals register_globals]</code> tillval är aktiverat.'''
-'''Inaktivera den om du kan.'''
-MediaWiki kommer att fungera, men din server exponeras för potentiella säkerhetsluckor.",
- 'config-safe-mode' => "''' Varning:''' PHP:s [http://www.php.net/features.safe-mode felsäkert läge] är aktivt.
-Det kan orsaka problem, särskilt om du använder filöverföringar och <code>math</code>-stöd.",
- 'config-xml-bad' => 'PHP:s XML-modul saknas.
-MediaWiki kräver funktioner i denna modul och kommer inte att fungera i den här konfigurationen.
-Om du kör Mandrake, installera php-xml-paketet.',
- 'config-memory-raised' => 'PHPs <code>memory_limit</code> är $1, ökar till $2.',
- 'config-memory-bad' => "''' Varning:''' PHP:s <code>memory_limit</code> är $1.
-Detta är förmodligen för lågt.
-Installationen kan misslyckas!",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] är installerad',
- 'config-apc' => '[http://www.php.net/apc APC] är installerad',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] är installerad',
- 'config-diff3-bad' => 'GNU diff3 hittades inte.',
- 'config-git-bad' => 'Git-versionen av kontrollmjukvaran hittades inte.',
- 'config-no-uri' => "'''Fel:''' Kunde inte fastställa det nuvarande URI:et.
-Installationen avbröts.",
- 'config-no-cli-uri' => "'''Varning:''' inget --scriptpath är anget, med standarden: <code>$1</code> .",
- 'config-using-server' => 'Använder servernamn "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Använder server-URL "<nowiki>$1$2</nowiki>".',
- 'config-no-cli-uploads-check' => "'''Varning:''' Din standardkatalog för uppladdningar (<code>$1</code>) inte är kontrollerad för sårbarhet från godtyckliga skriptkörning under CLI-installationen.",
- 'config-db-type' => 'Databastyp:',
- 'config-db-host' => 'Databasvärd:',
- 'config-db-wiki-settings' => 'Identifiera denna wiki',
- 'config-db-name' => 'Databasnamn:',
- 'config-db-name-oracle' => 'Databasschema:',
- 'config-db-install-account' => 'Användarkonto för installation',
- 'config-db-username' => 'Databas-användarnamn:',
- 'config-db-password' => 'Databas-lösenord:',
- 'config-db-password-empty' => 'Ange ett lösenord för den nya databasanvändaren: $1.
-Även om det kan vara möjligt att skapa användare utan lösenord är det inte säkert.',
- 'config-db-account-lock' => 'Använda samma användarnamn och lösenord under normal drift',
- 'config-db-wiki-account' => 'Användarkonto för normal drift',
- 'config-db-prefix' => 'Prefix för tabellerna i databasen:',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binär',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 bakåtkompatibel UTF-8',
- 'config-db-port' => 'Databasport:',
- 'config-db-schema' => 'Schema för MediaWiki',
- 'config-pg-test-error' => "Kan inte ansluta till databas '''$1''': $2",
- 'config-sqlite-dir' => 'SQLite data-katalog:',
- 'config-header-mysql' => 'MySQL-inställningar',
- 'config-header-postgres' => 'PostgreSQL-inställningar',
- 'config-header-sqlite' => 'SQLite-inställningar',
- 'config-header-oracle' => 'Oracle-inställningar',
- 'config-invalid-db-type' => 'Ogiltig databastyp',
- 'config-missing-db-name' => 'Du måste ange ett värde för "Databasnamn"',
- 'config-missing-db-host' => 'Du måste ange ett värde för "Databasvärd"',
- 'config-missing-db-server-oracle' => 'Du måste ange ett värde för "Databas TNS"',
- 'config-invalid-db-name' => '"$1" är ett ogiltigt databasnamn.
-Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).',
- 'config-invalid-db-prefix' => '"$1" är ett ogiltigt databasprefix.
-Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).',
- 'config-connection-error' => '$1.
-
-Kontrollera värden, användarnamnet och lösenordet nedan och försök igen',
- 'config-invalid-schema' => '"$1" är ett ogiltigt schema för MediaWiki.
-Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).',
- 'config-db-sys-create-oracle' => 'Installationsprogrammet stöder endast med ett SYSDBA-konto för att skapa ett nytt konto.',
- 'config-db-sys-user-exists-oracle' => 'Användarkontot "$1" finns redan. SYSDBA kan endast användas för att skapa ett nytt konto!',
- 'config-postgres-old' => 'PostgreSQL $1 eller senare krävs, du har $2.',
- 'config-sqlite-name-help' => 'Välja ett namn som identifierar din wiki.
-Använd inte mellanslag eller bindestreck.
-Detta kommer att användas för SQLite-data filnamnet.',
- 'config-sqlite-readonly' => 'Filen <code>$1</code> är inte skrivbar.',
- 'config-sqlite-cant-create-db' => 'Kunde inte skapa databasfilen <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP saknar stöd för FTS3, nedgraderar tabeller',
- 'config-can-upgrade' => "Det finns MediaWiki-tabeller i den här databasen.
-För att uppgradera dem till MediaWiki $1, klicka på '''Fortsätt'''.",
- 'config-upgrade-done' => "Uppgraderingen slutfördes.
-
-Du kan nu [$1 börja använda din wiki].
-
-Om du vill förnya din <code>LocalSettings.php</code>-fil, klicka på knappen nedan.
-Detta '''rekommenderas inte''' om du har problem med din wiki.",
- 'config-upgrade-done-no-regenerate' => 'Uppgraderingen slutfördes.
-
-Du kan nu [$1 börja använda din wiki].',
- 'config-regenerate' => 'Återskapa LocalSettings.php →',
- 'config-db-web-account' => 'Databaskonto för webbaccess',
- 'config-db-web-account-same' => 'Använd samma konto som för installation',
- 'config-db-web-create' => 'Skapa kontot om det inte redan finns',
- 'config-mysql-engine' => 'Lagringsmotor:',
- 'config-mysql-binary' => 'Binär',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'Namnet på wikin:',
- 'config-site-name-blank' => 'Ange ett sidnamn.',
- 'config-ns-generic' => 'Projekt',
- 'config-ns-site-name' => 'Samma som wikinamnet: $1',
- 'config-ns-other' => 'Annan (specificera)',
- 'config-ns-invalid' => 'Den angivna namnrymden "<nowiki>$1</nowiki>" är ogiltig.
-Ange ett annat namnrymd för projektet.',
- 'config-ns-conflict' => 'Den angivna namnrymden "<nowiki>$1</nowiki>" står i konflikt med en standardnamnrymd för MediaWiki.
-Ange ett annat namnrymd för projektet.',
- 'config-admin-box' => 'Administratörskonto',
- 'config-admin-name' => 'Ditt namn:',
- 'config-admin-password' => 'Lösenord:',
- 'config-admin-password-confirm' => 'Lösenord igen:',
- 'config-admin-help' => 'Skriv in ditt föredragna användarnamn här, t.ex. "Joe Bloggs".
-Detta är namnet du kommer att använda för att logga in på wikin.',
- 'config-admin-name-blank' => 'Ange ett användarnamn för administratörskontot.',
- 'config-admin-name-invalid' => 'Det angivna användarnamnet "<nowiki>$1</nowiki>" är ogiltigt.
-Ange ett annat användarnamn.',
- 'config-admin-password-blank' => 'Ange ett lösenord för administratörskontot.',
- 'config-admin-password-same' => 'Lösenordet får inte vara samma som användarnamnet.',
- 'config-admin-password-mismatch' => 'De två lösenord du uppgett överensstämmer inte med varandra.',
- 'config-admin-email' => 'E-postadress:',
- 'config-admin-error-user' => 'Internt fel när du skapar en administratör med namnet "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Internt fel när du väljer ett lösenord för administratören "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Du har angivit en felaktigt e-postadress.',
- 'config-subscribe' => 'Prenumerera på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlistan för tillkännagivanden].',
- 'config-almost-done' => 'Du är nästan färdig!
-Du kan nu hoppa över återstående konfigurationer och installera wikin nu.',
- 'config-optional-continue' => 'Ställ fler frågor till mig.',
- 'config-optional-skip' => 'Jag är redan uttråkad, bara installera wiki.',
- 'config-profile-wiki' => 'Öppen wiki',
- 'config-profile-no-anon' => 'Kontoskapande krävs',
- 'config-profile-fishbowl' => 'Endast auktoriserade redigerare',
- 'config-profile-private' => 'Privat wiki',
- 'config-license' => 'Upphovsrätt och licens:',
- 'config-license-none' => 'Ingen licenssidfot',
- 'config-license-cc-by-sa' => 'Creative Commons Erkännande Dela Lika',
- 'config-license-cc-by' => 'Creative Commons Erkännande',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Erkännande Icke-Kommersiell Dela Lika',
- 'config-license-cc-0' => 'Creative Commons Zero (allmän egendom)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 eller senare',
- 'config-license-pd' => 'Allmän egendom',
- 'config-license-cc-choose' => 'Välj en anpassad Creative Commons-licens',
- 'config-email-settings' => 'E-postinställningar',
- 'config-enable-email' => 'Aktivera utgående e-post',
- 'config-email-user' => 'Aktivera e-post mellan användare',
- 'config-email-user-help' => 'Tillåta alla användare att skicka mail till varandra om de har aktiverat det i deras preferenser.',
- 'config-email-usertalk' => 'Aktivera underrättelse för användardiskussionssidan',
- 'config-email-watchlist' => 'Aktivera meddelanden för bevakningslistan',
- 'config-email-auth' => 'Aktivera autentisering via e-post',
- 'config-upload-settings' => 'Bild- och filuppladdningar',
- 'config-upload-enable' => 'Aktivera filöverföringar',
- 'config-upload-deleted' => 'Mapp för raderade filer:',
- 'config-logo' => 'Logotyp-URL:',
- 'config-instantcommons' => 'Aktivera Instant Commons',
- 'config-cc-error' => 'Creative Commons-licens-väljaren gav inget resultat.
-Ange licensnamnet manuellt.',
- 'config-cc-again' => 'Välj igen...',
- 'config-cc-not-chosen' => 'Välj vilken Creative Commons-licens du vill ha och klicka på "gå vidare".',
- 'config-advanced-settings' => 'Avancerad konfiguration',
- 'config-extensions' => 'Tillägg',
- 'config-install-alreadydone' => "''' Varning:''' Du verkar redan ha installerat MediaWiki och försöker installera det igen.
-Vänligen fortsätt till nästa sida.",
- 'config-install-begin' => 'Genom att trycka på "{{int:config-continue}}", påbörjar du installationen av MediaWiki.
-Om du fortfarande vill göra ändringar trycker du på "{{int:config-back}}".',
- 'config-install-step-done' => 'klar',
- 'config-install-step-failed' => 'misslyckades',
- 'config-install-database' => 'Konfigurerar databas',
- 'config-install-schema' => 'Skapar schema',
- 'config-pg-no-plpgsql' => 'Du måste installera språket PL/pgSQL i databasen $1',
- 'config-install-user' => 'Skapar databasanvändare',
- 'config-install-user-alreadyexists' => 'Användaren "$1" finns redan',
- 'config-install-user-create-failed' => 'Misslyckades att skapa användare "$1": $2',
- 'config-install-user-grant-failed' => 'Beviljandet av behörighet till användaren "$1" misslyckades: $2',
- 'config-install-user-missing' => 'Den angivna användaren "$1" existerar inte.',
- 'config-install-user-missing-create' => 'Den angivna användaren "$1" existerar inte.
-Vänligen klicka på kryssrutan "skapa konto" nedan om du vill skapa den.',
- 'config-install-tables' => 'Skapar tabeller',
- 'config-install-tables-exist' => "''' Varning:''' MediaWiki-tabeller verkar redan finnas.
-Hoppar över skapandet.",
- 'config-install-tables-failed' => "''' Fel:''' Skapandet av tabell misslyckades med följande fel: $1",
- 'config-install-interwiki' => 'Lägger till standardtabell för interwiki',
- 'config-install-interwiki-list' => 'Kunde inte läsa filen <code>interwiki.list</code>.',
- 'config-install-stats' => 'Initierar statistik',
- 'config-install-keys' => 'Genererar hemliga nycklar',
- 'config-insecure-keys' => "'''Varning:''' {{PLURAL:$2|En säkerhetsnyckel|Säkerhetsnycklar}} ($1) som generades under installationen är inte helt {{PLURAL:$2|säker|säkra}} . Överväg att ändra {{PLURAL:$2|den|dem}} manuellt.",
- 'config-install-sysop' => 'Skapar administratörskonto',
- 'config-install-subscribe-fail' => 'Det gick inte att prenumerera på mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL är inte installerad och allow_url_fopen är inte tillgänglig.',
- 'config-install-mainpage' => 'Skapa huvudsida med standardinnehåll',
- 'config-install-extension-tables' => 'Skapar tabeller för aktiverade tillägg',
- 'config-install-mainpage-failed' => 'Kunde inte infoga huvudsidan: $1',
- 'config-install-done' => "'''Grattis!'''
-Du har installerat MediaWiki.
-
-Installationsprogrammet har genererat filen <code>LocalSettings.php</code>.
-Det innehåller alla dina konfigurationer.
-
-Du kommer att behöva ladda ned den och placera den i botten av din wiki-installation (samma mapp som index.php). Nedladdningen borde ha startats automatiskt.
-
-Om ingen nedladdning erbjöds, eller om du har avbrutit det kan du starta om nedladdningen genom att klicka på länken nedan:
-
-$3
-
-'''OBS''': Om du inte gör detta nu, kommer denna genererade konfigurationsfil inte vara tillgänglig för dig senare om du avslutar installationen utan att ladda ned den.
-
-När det är klart, kan du '''[$2 gå in på din wiki]'''.",
- 'config-download-localsettings' => 'Ladda ned <code>LocalSettings.php</code>',
- 'config-help' => 'hjälp',
- 'config-nofile' => 'Filen "$1" kunde inte hittas. Har den tagits bort?',
- 'mainpagetext' => "'''MediaWiki har installerats utan problem.'''",
- 'mainpagedocfooter' => 'Information om hur wiki-programvaran används finns i [//meta.wikimedia.org/wiki/Help:Contents användarguiden].
-
-== Att komma igång ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista över konfigurationsinställningar]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mail list]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Lokalisera MediaWiki för ditt språk]',
-);
-
-/** Swahili (Kiswahili)
- * @author Lloffiwr
- */
-$messages['sw'] = array(
- 'mainpagetext' => "'''MediaWiki imefanikiwa kuingizwa.'''",
- 'mainpagedocfooter' => 'Shauriana na [//meta.wikimedia.org/wiki/Help:Contents Mwongozo wa Mtumiaji] kwa habari juu ya utumiaji wa bidhaa pepe ya wiki.
-
-== Msaada wa kianzio ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Orodha ya mipangilio ya msingi]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ ya MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Orodha ya utoaji wa habari za MediaWiki]', # Fuzzy
-);
-
-/** Silesian (ślůnski)
- * @author Djpalar
- */
-$messages['szl'] = array(
- 'mainpagetext' => "'''Sztalowańy MediaWiki śe udoło.'''",
- 'mainpagedocfooter' => 'Uobezdrzij [//meta.wikimedia.org/wiki/Help:Contents przewodńik sprowjacza], kaj sům informacyje uo dźołańu uoprogramowańo MediaWiki.
-
-== Na sztart ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sztalowań konfiguracyje]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komuńikaty uo nowych wersyjach MediaWiki]', # Fuzzy
-);
-
-/** Tamil (தமிழ்)
- * @author Karthi.dr
- * @author TRYPPN
- * @author மதனாஹரன்
- */
-$messages['ta'] = array(
- 'config-title' => 'மீடியாவிக்கி $1 நிறுவுதல்',
- 'config-information' => 'தகவல்',
- 'config-localsettings-key' => 'தரமுயர்த்தல் குறியீடு:',
- 'config-localsettings-badkey' => 'நீங்கள் தந்த குறியீடு தவறானது.',
- 'config-your-language' => 'தங்களது மொழி:',
- 'config-your-language-help' => 'நிறுவல் செயன்முறையின்போது பயன்படுத்துவதற்கு ஒரு மொழியைத் தெரிவு செய்யவும்.',
- 'config-wiki-language' => 'விக்கி மொழி:',
- 'config-back' => '← முந்தைய',
- 'config-continue' => 'தொடரவும் →',
- 'config-page-language' => 'மொழி',
- 'config-page-welcome' => 'மீடியாவிக்கிக்கு வருக !',
- 'config-page-dbconnect' => 'தரவுத் தளத்துடன் தொடர்பு கொள்ளவும்',
- 'config-page-dbsettings' => 'தரவுத் தள அமைப்புகள்',
- 'config-page-name' => 'பெயர்',
- 'config-page-options' => 'விருப்பத்தேர்வுகள்',
- 'config-page-install' => 'நிறுவு',
- 'config-page-complete' => 'நிறைவு!',
- 'config-page-restart' => 'நிறுவலை மீண்டும் தொடங்கவும்',
- 'config-page-readme' => 'இதைப் படி',
- 'config-page-releasenotes' => 'வெளியீட்டு குறிப்புகள்',
- 'config-page-copying' => 'நகலெடுக்கப்படுகிறது',
- 'config-page-upgradedoc' => 'தரமுயர்த்தப்படுகிறது',
- 'config-page-existingwiki' => 'இருக்கின்ற விக்கி',
- 'config-restart' => 'ஆம், மறுமுறை துவங்கு',
- 'config-sidebar' => '* [//www.mediawiki.org மீடியாவிக்கி முகப்பு]
-* [//www.mediawiki.org/wiki/Help:Contents பயனரின் கையேடு]
-* [//www.mediawiki.org/wiki/Manual:Contents மேலாளரின் கையேடு]
-* [//www.mediawiki.org/wiki/Manual:FAQ அகேகே]
-----
-* <doclink href=Readme>என்னை வாசிக்கவும்</doclink>
-* <doclink href=ReleaseNotes>வெளியீட்டுக் குறிப்புகள்</doclink>
-* <doclink href=Copying>படியெடுத்தல்</doclink>
-* <doclink href=UpgradeDoc>நிகழ்நிலைப்படுத்தல்</doclink>',
- 'config-db-type' => 'தரவுத்தள வகை:',
- 'config-db-wiki-settings' => 'இந்த விக்கியைக் கண்டுபிடி',
- 'config-db-name' => 'தரவுதளப் பெயர்:',
- 'config-db-install-account' => 'நிறுவலுக்கான பயனர் கணக்கு',
- 'config-db-username' => 'தரவுத்தள பயனர்பெயர்:',
- 'config-db-password' => 'தரவுத்தள கடவுச்சொல்:',
- 'config-db-prefix' => 'தரவுத் தள வரிசைப் பட்டியல் முன்னொட்டு:',
- 'config-db-charset' => 'தரவுத் தள வரியுருத் தொகுதி',
- 'config-invalid-db-type' => 'செல்லாத தரவுத்தள வகை',
- 'config-upgrade-done-no-regenerate' => 'தரமுயர்த்தல் முழுமையடைந்தது.
-நீங்கள் தற்போது [$1 உங்கள் விக்கியைப் பயன்படுத்தத் துவங்கலாம்].',
- 'config-db-web-account' => 'வலை அணுகலுக்கான தரவுத் தளக் கணக்கு',
- 'config-mysql-engine' => 'சேமிப்பு இயந்திரம்:',
- 'config-mysql-charset' => 'தரவுத் தள வரியுருத் தொகுதி:',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'விக்கியின் பெயர்:',
- 'config-site-name-blank' => 'ஒரு தளத்தின் பெயரை உள்ளிடுக.',
- 'config-ns-generic' => 'திட்டம்',
- 'config-ns-other' => 'ஏனையவை (குறிப்பிடவும்)',
- 'config-admin-box' => 'நிருவாகி கணக்கு',
- 'config-admin-name' => 'தங்களது பெயர்:',
- 'config-admin-password' => 'கடவுச்சொல்:',
- 'config-admin-password-confirm' => 'கடவுச்சொல் மறுமுறையும்:',
- 'config-admin-name-blank' => 'நிருவாக அணுக்கம் உள்ள பயனர் பெயரை இடுக.',
- 'config-admin-password-blank' => 'நிருவாகி கணக்குக்கு கடவுச்சொல் ஒன்றை உள்ளிடவும்.',
- 'config-admin-password-same' => 'பயனர் பெயரையே கடவுச்சொல்லாக வைத்துக் கொள்ள முடியாது.',
- 'config-admin-password-mismatch' => 'நீங்கள் பதிந்த கடவுச்சொற்கள் ஒன்றுக்கொன்று பொருந்தவில்லை.',
- 'config-admin-email' => 'மின்னஞ்சல் முகவரி:',
- 'config-admin-error-bademail' => 'நீங்கள் செல்லாத ஒரு மின்னஞ்சல் முகவரியைத் தந்துள்ளீர்கள்.',
- 'config-optional-continue' => 'என்னை இன்னும் அதிகமாக வினவு.',
- 'config-optional-skip' => 'நான் ஏற்கனவே சோர்வடைந்துள்ளேன், விக்கியை மட்டும் உருவாக்கு.',
- 'config-profile' => 'பயனர் உரிமைகள் சுயவிவரம்:',
- 'config-profile-wiki' => 'பாரம்பரிய விக்கி', # Fuzzy
- 'config-profile-no-anon' => 'கணக்கு உருவாக்குதல் அவசியம்',
- 'config-profile-private' => 'தனியார் விக்கி',
- 'config-license' => 'பதிப்புரிமை மற்றும் உரிமம்:',
- 'config-license-pd' => 'பொதுக்களம்',
- 'config-email-settings' => 'மின்னஞ்சல் அமைப்புகள்',
- 'config-email-user' => 'பயனர்-பயனர் மின்னஞ்சலைச் செயற்படுத்தவும்',
- 'config-email-usertalk' => 'பயனர் பேச்சுப் பக்க அறிவிப்பைச் செயற்படுத்தவும்',
- 'config-email-watchlist' => 'கவனிப்புப் பட்டியல் அறிவிப்பைச் செயற்படுத்தவும்',
- 'config-upload-settings' => 'படிமம் மற்றும் கோப்பு பதிவேற்றங்கள்',
- 'config-upload-enable' => 'கோப்புப் பதிவேற்றங்களைச் செயற்படுத்தவும்',
- 'config-upload-deleted' => 'அழித்த கோப்புகளுக்கான அடைவு:',
- 'config-logo' => 'அடையாளச் சின்ன உரலி:',
- 'config-extensions' => 'நீட்சிகள்',
- 'config-install-step-done' => 'முடிந்தது',
- 'config-install-step-failed' => 'தோல்வியுற்றது',
- 'config-install-user' => 'தரவுத் தளப் பயனரை உருவாக்குகிறது',
- 'config-install-user-alreadyexists' => 'பயனர் "$1" ஏற்கனவே உள்ளது',
- 'config-install-tables' => 'வரிசைப் பட்டியல்களை உருவாக்குகிறது',
- 'config-install-mainpage' => 'இயல்புநிலை உள்ளடக்கத்துடன் முதற்பக்கத்தை உருவாக்குகிறது',
- 'config-install-extension-tables' => 'செயற்படுத்தப்பட்ட நீட்சிகளுக்கு வரிசைப் பட்டியல்களை உருவாக்குகிறது',
- 'config-download-localsettings' => '<code>LocalSettings.php</code>ஐத் தரவிறக்கவும்',
- 'config-help' => 'உதவி',
- 'mainpagetext' => "'''விக்கி மென்பொருள் வெற்றிகரமாக உள்ளிடப்பட்டது.'''",
- 'mainpagedocfooter' => 'விக்கி மென்பொருளைப் பயன்படுத்துவது தொடர்பாக [//meta.wikimedia.org/wiki/Help:Contents பயனர் வழிகாட்டியைப்] பார்க்க.
-
-== தொடக்கப்படிகள் ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings அமைப்புக்களை மாற்றம் செய்தல்]
-* [//www.mediawiki.org/wiki/Manual:FAQ மிடியாவிக்கி பொதுவான கேள்விகள்]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce மீடியாவிக்கி வெளியீடு மின்னஞ்சல் பட்டியல்]', # Fuzzy
-);
-
-/** Tulu (ತುಳು)
- */
-$messages['tcy'] = array(
- 'mainpagetext' => "'''ಮೀಡಿಯವಿಕಿ ಯಶಸ್ವಿಯಾದ್ ಇನ್’ಸ್ಟಾಲ್ ಆಂಡ್.'''",
- 'mainpagedocfooter' => 'ವಿಕಿ ತಂತ್ರಾಂಶನ್ ಉಪಗೋಗ ಮನ್ಪುನ ಬಗ್ಗೆ ಮಾಹಿತಿಗ್ [//meta.wikimedia.org/wiki/Help:Contents ಸದಸ್ಯೆರ್ನ ನಿರ್ದೇಶನ ಪುಟ] ತೂಲೆ.
-
-== ಎಂಚ ಶುರು ಮಲ್ಪುನಿ ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ ಮೀಡಿಯವಿಕಿ FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]', # Fuzzy
-);
-
-/** Telugu (తెలుగు)
- * @author Veeven
- */
-$messages['te'] = array(
- 'config-desc' => 'మీడియావికీ కొరకై స్థాపకి',
- 'config-title' => 'మీడియావికీ $1స్థాపన',
- 'config-information' => 'సమాచారం',
- 'config-your-language' => 'మీ భాష:',
- 'config-wiki-language' => 'వికీ భాష:',
- 'config-back' => '← వెనక్కి',
- 'config-continue' => 'కొనసాగించు →',
- 'config-page-language' => 'భాష',
- 'config-page-welcome' => 'మీడియావికీకి స్వాగతం!',
- 'config-page-dbsettings' => 'డాటాబేసు అమరికలు',
- 'config-page-name' => 'పేరు',
- 'config-page-options' => 'ఎంపికలు',
- 'config-page-install' => 'స్థాపించు',
- 'config-page-complete' => 'పూర్తయ్యింది!',
- 'config-page-readme' => 'నన్ను చదవండి',
- 'config-page-releasenotes' => 'విడుదల విశేషాలు',
- 'config-db-type' => 'డాటాబేసు రకం:',
- 'config-db-name' => 'డాటాబేసు పేరు:',
- 'config-db-install-account' => 'స్థాపనకి వాడుకరి ఖాతా',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-header-mysql' => 'MySQL అమరికలు',
- 'config-header-postgres' => 'PostgreSQL అమరికలు',
- 'config-header-sqlite' => 'SQLite అమరికలు',
- 'config-header-oracle' => 'Oracle అమరికలు',
- 'config-invalid-db-type' => 'తప్పుడు డాటాబేసు రకం',
- 'config-connection-error' => '$1.
-
-క్రింది హోస్టు, వాడుకరిపేరు మరియు సంకేతపదాలను ఒకసారి సరిచూసుకుని అప్పుడు ప్రయత్నించండి.',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-site-name' => 'వికీ యొక్క పేరు:',
- 'config-ns-other' => 'ఇతర (ఇవ్వండి)',
- 'config-ns-other-default' => 'నావికీ',
- 'config-admin-name' => 'మీ పేరు:',
- 'config-admin-password' => 'సంకేతపదం:',
- 'config-admin-password-confirm' => 'సంకేతపదం మళ్ళీ:',
- 'config-admin-email' => 'ఈ-మెయిలు చిరునామా:',
- 'config-optional-continue' => 'నన్ను మరిన్ని ప్రశ్నలు అడుగు.',
- 'config-profile-wiki' => 'సంప్రదాయ వికీ', # Fuzzy
- 'config-profile-no-anon' => 'ఖాతా సృష్టింపు తప్పనిసరి',
- 'config-profile-private' => 'అంతరంగిక వికీ',
- 'config-license' => 'కాపీహక్కులు మరియు లైసెన్సు:',
- 'config-license-pd' => 'సార్వజనీనం',
- 'config-email-settings' => 'ఈ-మెయిల్ అమరికలు',
- 'config-upload-deleted' => 'తొలగించిన దస్త్రాల కొరకు సంచయం:',
- 'config-advanced-settings' => 'ఉన్నత స్వరూపణం',
- 'config-install-step-done' => 'పూర్తయింది',
- 'config-install-step-failed' => 'విఫలమైంది',
- 'config-help' => 'సహాయం',
- 'mainpagetext' => "'''మీడియా వికీని విజయవంతంగా ప్రతిష్టించాం.'''",
- 'mainpagedocfooter' => 'వికీ సాఫ్టువేరును వాడటనికి కావలిసిన సమాచారం కోసం [//meta.wikimedia.org/wiki/Help:Contents వాడుకరుల గైడు]ను సందర్శించండి.
-
-== మొదలు పెట్టండి ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings మీడియావికీ పనితీరు, అమరిక మార్చుకునేందుకు వీలుకల్పించే చిహ్నాల జాబితా]
-* [//www.mediawiki.org/wiki/Manual:FAQ మీడియావికీపై తరుచుగా అడిగే ప్రశ్నలు]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce మీడియావికీ సాఫ్టువేరు కొత్త వెర్షను విడుదలల గురించి తెలిపే మెయిలింగు లిస్టు]', # Fuzzy
-);
-
-/** Tetum (tetun)
- * @author MF-Warburg
- */
-$messages['tet'] = array(
- 'config-page-language' => 'Lian',
- 'config-page-name' => 'Naran',
-);
-
-/** Tajik (Cyrillic script) (тоҷикӣ)
- */
-$messages['tg-cyrl'] = array(
- 'mainpagetext' => "'''Нармафзори МедиаВики бо муваффақият насб шуд.'''",
- 'mainpagedocfooter' => 'Аз [//meta.wikimedia.org/wiki/Help:Contents Роҳнамои Корбарон] барои истифодаи нармафзори вики кӯмак бигиред.
-
-== Оғоз ба кор ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Феҳристи танзимоти пайгирбандӣ]
-* [//www.mediawiki.org/wiki/Manual:FAQ Пурсишҳои МедиаВики]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Феҳристи ройномаҳои нусхаҳои МедиаВики]', # Fuzzy
-);
-
-/** Tajik (Latin script) (tojikī)
- * @author Liangent
- */
-$messages['tg-latn'] = array(
- 'mainpagetext' => "'''Narmafzori MediaViki bo muvaffaqijat nasb şud.'''",
- 'mainpagedocfooter' => 'Az [//meta.wikimedia.org/wiki/Help:Contents Rohnamoi Korbaron] baroi istifodai narmafzori viki kūmak bigired.
-
-== Oƣoz ba kor ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Fehristi tanzimoti pajgirbandī]
-* [//www.mediawiki.org/wiki/Manual:FAQ Pursişhoi MediaViki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Fehristi rojnomahoi nusxahoi MediaViki]', # Fuzzy
-);
-
-/** Thai (ไทย)
- * @author Korrawit
- */
-$messages['th'] = array(
- 'mainpagetext' => "'''ติดตั้งซอฟต์แวร์มีเดียวิกิเรียบร้อย'''",
- 'mainpagedocfooter' => 'ศึกษา[//meta.wikimedia.org/wiki/Help:Contents คู่มือการใช้งาน] สำหรับเริ่มต้นใช้งานซอฟต์แวร์วิกิ
-
-== เริ่มต้น ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings รายการการปรับแต่งระบบ] (ภาษาอังกฤษ)
-* [//www.mediawiki.org/wiki/Manual:FAQ คำถามที่ถามบ่อยในมีเดียวิกิ] (ภาษาอังกฤษ)
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce เมลลิงลิสต์ของมีเดียวิกิ]', # Fuzzy
-);
-
-/** Turkmen (Türkmençe)
- * @author Hanberke
- */
-$messages['tk'] = array(
- 'mainpagetext' => "'''MediaWiki şowlulyk bilen guruldy.'''",
- 'mainpagedocfooter' => 'Wiki programmasynyň ulanylyşy hakynda maglumat almak üçin [//meta.wikimedia.org/wiki/Help:Contents ulanyjy gollanmasyna] serediň.
-
-== Öwrenjeler ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigurasiýa sazlamalary]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki SSS]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçta sanawy]', # Fuzzy
-);
-
-/** Tagalog (Tagalog)
- * @author AnakngAraw
- * @author Sky Harbor
- * @author 아라
- */
-$messages['tl'] = array(
- 'config-desc' => 'Ang tagapagluklok para sa MediaWiki',
- 'config-title' => 'Instalasyong $1 ng MediaWiki',
- 'config-information' => 'Kabatiran',
- 'config-localsettings-upgrade' => 'Napansin ang isang talaksang <code>LocalSettings.php</code>.
-Upang maitaas ang uri ng pagluluklok na ito, paki ipasok ang halaga ng <code>$wgUpgradeKey</code> sa loob ng kahong nasa ibaba.
-Matatagpuan mo ito sa loob ng <code>LocalSettings.php</code>.',
- 'config-localsettings-cli-upgrade' => 'Napansin ang isang talaksan ng <code>LocalSettings.php</code>.
-Upang isapanahon ang pagtatalagang ito, mangyaring patakbuhin sa halip ang <code>update.php</code>',
- 'config-localsettings-key' => 'Susi ng pagsasapanahon:',
- 'config-localsettings-badkey' => 'Hindi tama ang susing ibinigay mo.',
- 'config-upgrade-key-missing' => 'Napansin ang isang umiiral na pagtatalaga ng MediaWiki.
-Upang isapanahon ang katalagahang ito, mangyaring ilagay ang sumusunod na guhit sa ilalim ng iyong <code>LocalSettings.php</code>:
-
-$1',
- 'config-localsettings-incomplete' => 'Lumilitaw na hindi pa buo ang umiiral na <code>LocalSettings.php</code>.
-Ang pabagu-bagong $1 ay hindi nakatakda.
-Mangyaring baguhin ang <code>LocalSettings.php</code> upang ang maitakda ang pagpapabagu-bagong ito, at pindutin ang "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Isang kamalian ang nakatagpo noong kumakabit sa kalipunan ng dato na ginagamit ang tinukoy na mga katakdaan sa loob ng <code>LocalSettings.php</code> o
-<code>AdminSettings.php</code>. Paki kumpunihin ang mga katakdaang ito at subukang muli.
-
-$1',
- 'config-session-error' => 'Kamalian sa pagsisimula ng sesyon: $1',
- 'config-session-expired' => 'Tila nagwakas na ang inilaan sa iyong panahon ng dato.
-Ang inilaang mga panahon ay iniayos para sa isang panahon ng buhay na $1.
-Mapapataas mo ito sa pamamagitan ng pagtatakda ng <code>session.gc_maxlifetime</code> sa loob ng php.ini.
-Muling simulan ang proseso ng pagluluklok.',
- 'config-no-session' => 'Nawala ang iyong datos ng sesyon!
-Suriin ang iyong php.ini at tiyakin na ang <code>session.save_path</code> ay nakatakda sa angkop na direktoryo.',
- 'config-your-language' => 'Ang wika mo:',
- 'config-your-language-help' => 'Pumili ng isang wikang gagamitin habang isinasagawa ang pagtatalaga.',
- 'config-wiki-language' => 'Wika ng Wiki:',
- 'config-wiki-language-help' => 'Piliin ang wika kung saan mangingibabaw na isusulat ang wiki.',
- 'config-back' => '← Bumalik',
- 'config-continue' => 'Magpatuloy →',
- 'config-page-language' => 'Wika',
- 'config-page-welcome' => 'Maligayang pagdating sa MediaWiki!',
- 'config-page-dbconnect' => 'Umugnay sa kalipunan ng datos',
- 'config-page-upgrade' => 'Itaas ng uri ang umiiral na pagkakatalaga',
- 'config-page-dbsettings' => 'Mga katakdaan ng kalipunan ng datos',
- 'config-page-name' => 'Pangalan',
- 'config-page-options' => 'Mga mapipili',
- 'config-page-install' => 'Italaga',
- 'config-page-complete' => 'Buo na!',
- 'config-page-restart' => 'Simulan muli ang pag-iinstala',
- 'config-page-readme' => 'Basahin ako',
- 'config-page-releasenotes' => 'Pakawalan ang mga tala',
- 'config-page-copying' => 'Kinokopya',
- 'config-page-upgradedoc' => 'Itinataas ang uri',
- 'config-page-existingwiki' => 'Umiiral na wiki',
- 'config-help-restart' => 'Nais mo bang hawiin ang lahat ng nasagip na datong ipinasok mo at muling simulan ang proseso ng pagluluklok?',
- 'config-restart' => 'Oo, muling simulan ito',
- 'config-welcome' => '=== Pagsusuring pangkapaligiran ===
-Isinasagawa ang payak na mga pagsusuri upang makita kung ang kapaligirang ito ay angkop para sa pagluluklok ng MediaWiki.
-Dapat mong ibigay ang mga kinalabasan ng mga pagsusuring ito kung kailangan mo ng tulong habang nagluluklok.',
- 'config-copyright' => "=== Karapatang-ari at Tadhana ===
-
-$1
-
-Ang programang ito ay malayang software; maaari mo itong ipamahagi at/o baguhin sa ilalim ng mga tadhana ng Pangkalahatang Pampublikong Lisensiyang GNU ayon sa pagkakalathala ng Free Software Foundation; na maaaring bersyong 2 ng Lisensiya, o (kung nais mo) anumang susunod na bersyon.
-
-Ipinamamahagi ang programang ito na umaasang magiging gamitin, subaliut '''walang anumang katiyakan'''; na walang pahiwatig ng '''pagiging mabenta''' o '''kaangkupan para sa isang tiyak na layunin'''.
-Tingnan ang Pangkalahatang Pampublikong Lisensiyang GNU para sa mas maraming detalye.
-
-Dapat nakatanggap ka ng <doclink href=Copying>isang sipi ng Pangkalahatang Pampublikong Lisensiyang GNU</doclink> kasama ng programang ito; kung hindi, sumulat sa Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [http://www.gnu.org/licenses//gpl.html basahin ito sa Internet].",
- 'config-sidebar' => '* [//www.mediawiki.org Tahanan ng MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Gabay ng Tagagamit]
-* [//www.mediawiki.org/wiki/Manual:Contents Gabay ng Tagapangasiwa]
-* [//www.mediawiki.org/wiki/Manual:FAQ Mga Malimit Itanong]
-----
-* <doclink href=Readme>Basahin ako</doclink>
-* <doclink href=ReleaseNotes>Mga tala ng paglalabas</doclink>
-* <doclink href=Copying>Pagkopya</doclink>
-* <doclink href=UpgradeDoc>Pagsasapanahon</doclink>',
- 'config-env-good' => 'Nasuri na ang kapaligiran.
-Mailuluklok mo ang MediaWiki.',
- 'config-env-bad' => 'Nasuri na ang kapaligiran.
-Hindi mo mailuklok ang MediaWiki.',
- 'config-env-php' => 'Naitalaga ang PHP na $1.',
- 'config-env-php-toolow' => 'Naitalaga ang PHP $1.
-Subalit, nangangailangan ang MediaWiki ng PHP $2 o mas mataas pa.',
- 'config-unicode-using-utf8' => 'Ginagamit ang utf8_normalize.so ni Brion Vibber para sa pagpapanormal ng Unikodigo.',
- 'config-unicode-using-intl' => 'Ginagamit ang [http://pecl.php.net/intl intl dugtong na PECL] para sa pagsasanormal ng Unikodigo.',
- 'config-unicode-pure-php-warning' => "'''Babala''': Ang [http://pecl.php.net/intl dugtong ng internasyunal na PECL] ay hindi makukuha upang makapanghawak ng pagpapanormal ng Unikodigo, na babagsak na pabalik sa mabagal na pagsasakatuparan ng dalisay na PHP.
-Kapag nagpapatakbo ka ng isang pook na mataas ang trapiko, dapat kang bumasa ng kaunti hinggil sa [//www.mediawiki.org/wiki/Unicode_normalization_considerations pagpapanormal ng Unikodigo].",
- 'config-unicode-update-warning' => "'''Babala''': Ang nakaluklok na bersiyon ng pambalot ng pagpapanormal ng Unikodigo ay gumagamit ng isang mas matandang bersiyon ng aklatan ng [http://site.icu-project.org/ proyekto ng ICU].
-Dapat kang [//www.mediawiki.org/wiki/Unicode_normalization_considerations magtaas ng uri] kung may pag-aalala ka hinggil sa paggamit ng Unikodigo.",
- 'config-no-db' => 'Hindi matagpuan ang isang angkop na tagapagmaneho ng kalipunan ng datos! Kailangan mong magluklok ng isang tagapagmaneho ng kalipunan ng dato para sa PHP.
-Tinatangkilik ang sumusunod na mga uri ng kalipunan ng dato: $1.
-
-Kung ikaw ay nasa isang pinagsasaluhang pagpapasinaya, hilingin sa iyong tagapagbigay ng pagpapasinaya na iluklok ang isang angkop na tagapagmaneho ng kalipunan ng dato.
-Kung ikaw mismo ang nangalap ng PHP, muling isaayos ito na pinagagana ang isang kliyente ng kalipunan ng dato, halimbawa na ang paggamit ng <code>./configure --with-mysql</code>.
-Kung iniluklok mo ang PHP mula sa isang pakete ng Debian o Ubuntu, kung gayon kailangan mo ring magluklok ng modyul na php5-mysql.',
- 'config-outdated-sqlite' => "'''Babala''': mayroong kang $1 ng SQLite, na mas mababa kaysa sa pinaka mababang kailangang bersiyon na $2. Magiging hindi makukuha ang SQLite.",
- 'config-no-fts3' => "'''Warning''': Ang SQLite ay hindi itinala at tinipon na wala ang [//sqlite.org/fts3.html modulong FTS3], ang mga tampok na panghanap ay magiging hindi makukuha sa ibabaw ng panlikod na dulong ito.",
- 'config-register-globals' => "'''Babala: Ang mapipili na <code>[http://php.net/register_globals register_globals]</code> ng PHP ay pinagagana.'''
-'''Huwag paganahin kung kaya mo.'''
-Aandar ang MediaWiki, subalit ang tagapaghain mo ay nakalantad sa maaaring maganap na mga kahinaang pangkatiwasayan.",
- 'config-magic-quotes-runtime' => "'''Malubha: Masigla ang [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
-Ang piniling ito ay hindi mahuhulaan na pipinsala sa lahok na dato.
-Hindi mo maaaring iluklok o gamitin ang MediaWiki maliban na lamang kung hindi na gumagana ang pinili na ito.",
- 'config-magic-quotes-sybase' => "'''Malubha: Masigla ang [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''
-Hindi mahuhulaan na sinisira ng napiling ito ang lahok na dato.
-Hindi mo maaaring iluklok o gamitin ang MediaWiki maliban na lamang kung hindi na pinagagana ang napiling ito.",
- 'config-mbstring' => "'''Malubha: Masigla ang [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''
-Ang napiling ito ay nagdurulot ng mga kamalian at maaaring sumira nang hindi nahuhulaan ang dato.
-Hindi mo maaaring iluklok o gamitin ang MediaWiki maliban na lamang kung hindi na pinagagana ang napiling ito.",
- 'config-ze1' => "'''Malubha: Masigla ang [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]!'''
-Ang napiling ito ay nagsasanhi ng karima-rimarim na mga sira sa MediaWiki.
-Hindi mo maaaring iluklok o gamitin ang MediaWiki maliban na lamang kung hindi na pinagagana ang napiling ito.",
- 'config-safe-mode' => "'''Babala:''' Masigla ang [http://www.php.net/features.safe-mode safe mode] ng PHP.
-Maaari itong magdulot ng mga suliranin, partikular na kung gumagamit ng mga ikinargang paitaas na talaksan at ng suporta sa <code>math</code>.",
- 'config-xml-bad' => 'Nawawala ang modulong XML ng PHP.
-Nangangailangan ang MediaWiki ng mga tungkulin sa loob ng modulong ito at hindi aandar sa loob ng ganitong pagkakaayos.
-Kung pinapatakbo mo ang Mandrake, iluklok ang pakete ng php-xml.',
- 'config-pcre' => 'Tila nawawala ang modyul na pangsuporta ng PCRE.
-Nangangailangan ang MediaWiki ng nakaukol sa Perl na mga tungkulin ng karaniwang pagsasaad upang gumana.',
- 'config-pcre-no-utf8' => "'''Malubha''': Tila tinipon ang modyul na PCRE ng PHP na wala ang suporta ng PCRE_UTF8.
-Nangangailangan 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.
-Ito ay maaaring napakababa.
-Maaaring mabigo ang pagluluklok!",
- 'config-ctype' => "'''Maluba''': Dapat na tipunin ang PHP na mayroong suporta para sa [http://www.php.net/manual/en/ctype.installation.php dugtong Ctype].",
- 'config-xcache' => 'Ininstala na ang [http://xcache.lighttpd.net/ XCache]',
- 'config-apc' => 'Ininstala na ang [http://www.php.net/apc APC]',
- 'config-wincache' => 'Ininstala na ang [http://www.iis.net/download/WinCacheForPhp WinCache]',
- 'config-no-cache' => "'''Babala:''' Hindi mahanap ang [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
-Hindi pinapagana ang pagbabaon ng mga bagay.",
- 'config-mod-security' => "'''Babala''': Ang tagapaghain mo ng sangkasaputan ay pinagana na mayroong [http://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.
-Sumangguni sa [http://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-imagemagick' => 'Natagpuan ang ImageMagick: <code>$1</code>.
-Papaganahin ang pagkakagyat ng larawan kapag pinagana mo ang mga pagkakargang paitaas.',
- 'config-gd' => 'Natagpuan ang pinasadyang nakapaloob na grapiks ng GD.
-Papaganahin ang pagkakagyat ng larawan kapag pinagana mo ang mga pagkakargang paitaas.',
- 'config-no-scaling' => 'Hindi matagpuan ang aklatang GD o ImageMagick.
-Hindi papaganahin ang pagkakagyat ng larawan.',
- 'config-no-uri' => "'''Kamalian:''' Hindi matukoy ang kasalukuyang URI.
-Pinigilan ang pag-iinstala.",
- 'config-no-cli-uri' => "'''Babala''': Walang tinukoy na --landas ng panitik, ginagamit ang likas na katakdaan: <code>$1</code>.",
- 'config-using-server' => 'Ginagamit ang pangalan ng tagapaghain na "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Ginagamit ang URL ng tagapaghain na "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Babala:''' Ang iyong likas na nakatakdang direktoryo para sa paitaas na mga pagkakarga na <code>$1</code> ay may kahinaan laban sa pagsasagawa ng mga panitiki na hindi makatwiran. Bagaman sinisiyasat ng MediaWiki ang lahat ng paitaas na naikargang mga talaksan para sa mga panganib na pangkatiwasayan, mataas na iminumungkahi na [//www.mediawiki.org/wiki/Manual:Security#Upload_security isara ang kahinaang ito na pangkatiwasayan] bago paganahin ang paitaas na mga pagkakarga.",
- 'config-no-cli-uploads-check' => "'''Babala:''' Ang iyong likas na nakatakdang direktoryo para sa paitaas na mga pagkakarga (<code>$1</code>) ay hindi nasuri para sa kahinaan laban sa pagsasagawa ng panitik na hindi makatwiran habang iniluluklok ang Ugnayang Mukha ng Guhit ng Kaataasan o Command-Line Interface (CLI).",
- 'config-brokenlibxml' => "Ang sistema mo ay mayroong isang pagsasama ng mga bersiyon ng PHP at libxml2 na maaaring masurot at maaaring makapagsanhi ng pagkasira ng datong nakakubli sa loob ng MediaWiki at iba pang mga aplikasyon ng sangkasaputan.
-Magtaas ng uri upang maging PHP 5.2.9 o mas lalong huli at libxml2 2.7.3 o mas lalong huli ([//bugs.php.net/bug.php?id=45996 isinalansan ang surot o ''bug'' na mayroong PHP]). Binigo ang pagluluklok.",
- 'config-using531' => 'Hindi maaaring gamitin ang MediaWiki na kapiling ang PHP na $1 dahil sa isang surot na kinasasangkutan ng mga parametrong pangsangguni sa <code>__call()</code>. Magtaas ng uri upang maging PHP 5.3.2 o mas mataas, o magbaba ng uri upang maging PHP 5.3.0 upang malutas ito. Binigo ang pagluluklok.',
- 'config-suhosin-max-value-length' => 'Nakaluklok ang Suhosin at hinahanggahan ang haba ng parametro ng GET sa $1 mga byte. Ang sangkap na ResourceLoader ng MediaWiki ay gagana sa paligid ng hangganang ito, subalit pasasamain nito ang pagganap. Kung talagang maaari, dapat mong itakda ang <code>suhosin.get.max_value_length</code> upang maging 1024 o mas mataas sa loob ng <code>php.ini</code>, at itakda ang <code>$wgResourceLoaderMaxQueryLength</code> sa katulad na halaga sa loob ng LocalSettings.php.', # Fuzzy
- 'config-db-type' => 'Uri ng kalipunan ng datos:',
- 'config-db-host' => 'Tagapagpasinaya ng kalipunan ng datos:',
- 'config-db-host-help' => 'Kung ang iyong tagapaghain ng kalipunan ng dato ay nasa ibabaw ng isang ibang tagapaghain, ipasok ang pangalan ng tagapagpasinaya o tirahan ng IP dito.
-
-Kung gumagamit ka ng pinagsasaluhang pagpapasinaya ng sangkasaputan, dapat ibigay sa iyo ng iyong tagapagbigay ng pagpapasinaya ang tamang pangalan ng tagapagpasinaya sa loob ng kanilang kasulatan.
-
-Kapag nagluluklok ka sa ibabaw ng isang tagapaghain ng Windows at gumagamit ng MySQL, maaaring hindi gumana ang paggamit ng "localhost" para sa pangalan ng tagapaghain. Kung hindi, subukan ang "127.0.0.1" para sa katutubong tirahan ng IP.
-
-Kapag gumagamit ka ng PostgreSQL, iwanang walang laman ang hanay na ito upang kumabit sa pamamagitan ng bokilya ng Unix.',
- 'config-db-host-oracle' => 'TNS ng kalipunan ng dato:',
- 'config-db-host-oracle-help' => 'Magpasok ng isang katanggap-tanggap na [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Katutubong Pangalan ng Pagkabit]; dapat na nakikita ang isang talaksan ng tnsnames.ora sa pagluluklok na ito.<br />Kung gumagamit ka ng mga aklatan ng kliyente na 10g o mas bago, maaari mo ring gamitin ang pamamaraan ng pagpapangalan ng [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Maginhawang Pagkabit].',
- 'config-db-wiki-settings' => 'Kilalanin ang wiking ito',
- 'config-db-name' => 'Pangalan ng kalipunan ng dato:',
- 'config-db-name-help' => 'Pumili ng isang pangalan na pangkilala sa wiki mo.
-Hindi ito dapat maglaman ng mga patlang.
-
-Kung gumagamit ka ng pinagsasaluhang pagpapasinaya ng sangkasaputan, ang iyong tagapagbigay ng pagpapasinaya ay maaaring bigyan ka ng isang tiyak na pangalan ng kalipunan ng datong gagamitin o papayagan kang lumikha ng mga kalipunan ng dato sa pamamagitan ng isang entrepanyong pantaban.',
- 'config-db-name-oracle' => 'Balangkas ng kalipunan ng dato:',
- 'config-db-account-oracle-warn' => 'Mayroong tatlong tinatangkilik na tagpo para sa pagluluklok ng Oracle bilang panlikurang hulihan ng kalipunan ng dato:
-
-Kung nais mong lumikha ng akawnt ng kalipunan ng dato bilang bahagi ng proseso ng pagluluklok, paki magbigay ng isang akawnt na mayroong gampanin ng SYSDBA bilang akawnt ng kalipunan ng dato para sa pagluluklok at tukuyin ang ninanais na mga kredensiyal para sa akawnt ng pagpunta sa sangkasaputan, o di kaya ay maaaring kinakamay na lumikha ng akawnt ng pagpunta sa sangkasaputan at ibigay lamang ang akawnt na iyan (kung mayroong ito ng kinakailangang mga pahintulot upang malikha ang mga bagay na pampagpapanukala) o magbigay ng dalawang magkaibang mga akawnt, isang mayroong pribilehiyo ng paglikha at isang may pagbabawal para sa pagpunta sa sangkasaputan.
-
-Ang panitik sa paglikha ng isang akawnt na mayroon ng kinakailangang mga pribilehiyo ay matatagpuan sa loob ng direktoryong "maintenance/oracle/" ng pagluluklok na ito. Pakatandaan na ang paggamit ng isang akawnt na may pagbabawal ay hindi magpapagana isa lahat ng mga kakayahang pampananatili sa piling ng likas na nakatakdang akawnt.',
- 'config-db-install-account' => 'Akawnt ng tagagamit para sa pagluluklok',
- 'config-db-username' => 'Pangalang pangtagagamit ng kalipunan ng dato:',
- 'config-db-password' => 'Hudyat sa kalipunan ng dato:',
- 'config-db-password-empty' => 'Paki magpasok ng isang hudyat para sa bagong tagagamit ng kalipunan ng dato: $1.
-Habang maging maaari na makalikha ng mga tagagamit na walang mga hudyat, hindi ito ligtas.',
- 'config-db-install-username' => 'Ipasok ang pangalan ng tagagamit na gagamitin upang kumabit sa kalipunan ng dato habang isinasagawa ang pagluluklok.
-Hindi ito ang pangalan ng tagagamit ng akawnt ng MediaWiki; ito ang pangalan ng tagagamit para sa iyong kalipunan ng dato.',
- 'config-db-install-password' => 'Ipasok ang hudyat na gagamitin upang kumabit sa kalipunan ng dato habang isinasagawa ang pagluluklok.
-Hindi ito ang hudyat para sa akawnt ng MediaWiki; ito ang hudyat para sa iyong kalipunan ng dato.',
- 'config-db-install-help' => 'Ipasok ang pangalan ng tagagamit at hudyat na gagamitin upang umugnay sa kalipunan ng dato habang isinasagawa ang pagluluklok.',
- 'config-db-account-lock' => 'Gamitin ang gayun ding pangalan ng tagagamit at hudyat habang nasa normal na operasyon',
- 'config-db-wiki-account' => 'Akawnt ng tagagamit para sa pangkaraniwang pagpapaandar',
- 'config-db-wiki-help' => 'Ipasok ang pangalan ng tagagamit at hudyat na gagamitin upang kumabit sa kalipunan ng dato habang nasa karaniwang pagtakbo ng wiki.
-Kung hindi umiiral ang akawnt, at ang akawnt ng pagluluklok ay mayroong sapat na mga pribilehiyo, ang akawnt na ito ng tagagamit ay lilikhain na mayroong pinaka mababang mga pribilehiyo na kailangan upang mapatakbo ang wiki.',
- 'config-db-prefix' => 'Unlapi ng talahanayan ng kalipunan ng dato:',
- 'config-db-prefix-help' => 'Kung kailangan mong ibahagi ang isang kalipunan ng dato sa pagitan ng maramihang mga wiki, o sa pagitan ng MediaWiki at ibang aplikasyon ng kasaputan, maaaring piliin mo na magdagdag ng isang unlapi sa lahat ng mga pangalan ng talahanayan upang maiwasan ang mga salungatan.
-Huwag gumamit ng mga patlang.
-
-Ang hanay na ito ay karaniwang iniiwanang walang laman.',
- 'config-db-charset' => 'Pangkat ng panitik ng kalipunan ng dato',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binaryo',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 paurong-kabagay UTF-8',
- 'config-charset-help' => "'''Babala:''' Kapag ginamit mo ang '''backwards-compatible UTF-8''' o \"nauukol na pabalik na UTF-8\" sa MySQL 4.1+, at may kasunod na pagtatabi ng pansalong kopya ng kalipunan ng dato na mayroong <code>mysqldump</code>, maaaring wasakin nito ang lahat ng mga panitik na hindi ASCII, na hindi na mababawi pa ang mga pansalong kopya.
-
-Sa '''gawi na nakahalo sa dalawa (binaryo)''', itinatabi ng MediaWiki ang tekstong UTF-8 sa kalipunan ng dato sa loob ng mga kahanayang binaryo.
-Mas kapaki-pakinabang ito kaysa sa gawi na UTF-8 ng MySQL, at nagpapahintulot sa iyo na gamitin ang buong kasaklawan ng mga panitik na Unikodigo.
-Sa '''gawi ng UTF-8''', malalaman ng MySQL kung anong pangkat ng panitik ang kinapapalooban ng iyong dato, at may kaangkupang maihaharap at mapapalitan ito, ngunit hindi ka nito papayagan na mag-imbak ng mga panitik sa ibabaw ng [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] o Saligang Patag na Kayas na Pangmaramihang Wika.",
- 'config-mysql-old' => 'Hindi kailangan ang MySQL na $1 o mas bago, mayroon kang $2.',
- 'config-db-port' => 'Daungan ng kalipunan ng dato:',
- 'config-db-schema' => 'Panukala para sa MediaWiki',
- 'config-db-schema-help' => 'Ang nasa itaas na panukala ay pangkaraniwang magiging maayos.
-Baguhin lamang ito kung alam mong kinakailangan.',
- 'config-pg-test-error' => "Hindi makakabit sa kalipunan ng dato na '''$1''': $2",
- 'config-sqlite-dir' => 'Direktoryo ng dato ng SQLite:',
- 'config-sqlite-dir-help' => "Iniimbak ng SQLite ang lahat ng dato sa loob ng isang nag-iisang talaksan.
-
-Ang ibibigay mong direktoryo ay dapat na maging masusulatan ng tagapaghain ng kasaputan habang nagluluklok.
-
-'''Hindi''' ito dapat na mapuntahan sa pamamagitan ng kasaputan, ito ang dahilan kung bakit hindi namin ito inilalagay sa kung nasaan ang iyong mga talaksan ng PHP.
-
-Ang tagapagluklok ay magsusulat ng isang talaksang <code>.htaccess</code> na kasama ito, subalit kapag nabigo iyon mayroong isang tao na maaaring makakuha ng pagka nakakapunta sa iyong hilaw na kalipunan ng dato.
-Kasama riyan ang hilaw na dato ng tagagamit (mga tirahan ng e-liham, pinaghalong mga hudyat) pati na ang nabura nang mga pagbabago at iba pang may pagbabawal na dato sa ibabaw ng wiki.
-
-Isaalang-alang ang paglalagay na magkakasama ang kalipunan ng dato sa ibang lugar, halimbawa na ang sa loob ng <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Likas na nakatakdang puwang ng talahanayan:',
- 'config-oracle-temp-ts' => 'Pansamantalang puwang ng talahanayan:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'Sinusuportahan ng MediaWiki ang sumusunod na mga sistema ng kalipunan ng dato:
-
-$1
-
-Kung 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-support-mysql' => '* Ang $1 ay ang pangunahing puntirya para sa MediaWiki at ang pinaka sinusuportahan ([http://www.php.net/manual/en/mysql.installation.php paano magtipon ng PHP na mayroong suporta ng MySQL])',
- 'config-support-postgres' => '* Ang $1 ay isang bantog na sistema ng kalipunan ng dato na bukas ang pinagmulan na panghalili sa MySQL ([http://www.php.net/manual/en/pgsql.installation.php paano magtipon ng PHP na mayroong suporta ng PostgreSQL]). Maaaring mayroong ilang hindi pangunahing mga surot na natitira pa, at hindi iminumungkahi para gamitin sa loob ng isang kapaligiran ng produksiyon.',
- 'config-support-sqlite' => 'Ang $1 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-support-oracle' => '* Ang $1 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 MySQL',
- 'config-header-postgres' => 'Mga katakdaan ng PostgreSQL',
- 'config-header-sqlite' => 'Mga katakdaan ng SQLite',
- 'config-header-oracle' => 'Mga katakdaan ng Oracle',
- 'config-invalid-db-type' => 'Hindi tanggap na uri ng kalipunan ng dato',
- 'config-missing-db-name' => 'Dapat kang magpasok ng isang halaga para sa "Pangalan ng kalipunan ng dato"',
- 'config-missing-db-host' => 'Dapat kang magpasok ng isang halaga para sa "Tagapagpasinaya ng kalipunan ng dato"',
- 'config-missing-db-server-oracle' => 'Dapat kang magpasok ng isang halaga para sa "TNS ng kalipunan ng dato"',
- 'config-invalid-db-server-oracle' => 'Hindi katanggap-tanggap na pangalan ng TNSng kalipunan ng dato na "$1".
-Gumamit lamang ng mga titik ng ASCII (a-z, A-Z), mga bilang (0-9), mga salungguhit (_) at mga tuldok (.).',
- 'config-invalid-db-name' => 'Hindi tanggap na pangalan ng kalipunan ng dato na "$1".
-Gumamit lamang ng mga titik ng ASCII (a-z, A-Z), mga bilang (0-9), mga salungguhit (_) at mga gitling (-).',
- 'config-invalid-db-prefix' => 'Hindi tanggap na unlapi ng kalipunan ng dato na "$1".
-Gamitin lamang ang mga titik na ASCII (a-z, A-Z), mga bilang (0-9), mga salungguhit (_) at mga gitling (-).',
- 'config-connection-error' => '$1.
-
-Suriin ang punong-abala, pangalan ng tagagamit at hudyat na nasa ibaba at subukan ulit.',
- 'config-invalid-schema' => 'Hindi katanggap-tanggap na panukala para sa "$1" ng MediaWiki.
-Gumamit lamang ng mga titik ng ASCII (a-z, A-Z), mga bilang (0-9), at mga salungguhit (_).',
- 'config-db-sys-create-oracle' => 'Ang panluklok ay tumatangkilik lamang sa paggamit ng isang akawnt ng SYSDBA para sa paglikha ng isang bagong akawnt.',
- 'config-db-sys-user-exists-oracle' => 'Umiiral na ang akawnt ng tagagamit na "$1". Magagamit lamang ang SYSDBA para sa paglikha ng isang bagong akawnt!',
- 'config-postgres-old' => 'Kailangan ang PostgreSQL $1 o mas bago, mayroon kang $2.',
- 'config-sqlite-name-help' => 'Pumili ng isang pangalan na pangkilala na wiki mo.
-Huwag gumamit ng mga puwang o mga gitling.
-Gagamitin ito para sa pangalan ng talaksan ng dato ng SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Hindi malikha ang direktoryo ng dato na <code><nowiki>$1</nowiki></code>, sapagkat ang magulang na direktoryong <code><nowiki>$2</nowiki></code> ay hindi masulatan ng tagapaghain ng kasaputan.
-
-Napag-alaman ng tagapagluklok kung sinong tagagamit ang kinatatakbuhan ng iyong tagapaghain ng kasaputan.
-Gawing nasusulatan nito ang <code><nowiki>$3</nowiki></code> ng direktoryo upang makapagpatuloy.
-Ito ang gawin sa ibabaw ng isang sistema ng Unix/Linux:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Hindi malikha ang direktoryo ng dato na <code><nowiki>$1</nowiki></code>, sapagkat ang magulang na direktoryong <code><nowiki>$2</nowiki></code> ay hindi masulatan ng tagapaghain ng kasaputan.
-
-Hindi malaman ng tagapagluklok kung sinong tagagamit ang kinatatakbuhan ng iyong tagapaghain ng kasaputan.
-Gawing nasusulatan nito (at ng mga iba pa) ang <code><nowiki>$3</nowiki></code> ng direktoryo upang makapagpatuloy.
-Ito ang gawin sa ibabaw ng isang sistema ng Unix/Linux:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Kamalian sa paglikha ng direktoryo ng datong "$1".
-Suriin ang kinalalagyan at subukang muli.',
- 'config-sqlite-dir-unwritable' => 'Hindi nagawang magsulat sa direktoryong "$1".
-Baguhin ang mga kapahintulutan nito upang makapagsulat dito ang tagapaghain ng sapot, at subukang muli.',
- 'config-sqlite-connection-error' => '$1.
-
-Surrin ang direktoryo ng dato at pangalan ng kalipunan ng datong nasa ibaba at subukan uli.',
- 'config-sqlite-readonly' => 'Ang talaksang <code>$1</code> ay hindi maisusulat.',
- 'config-sqlite-cant-create-db' => 'Hindi malikha ang talaksang <code>$1</code> ng kalipunan ng dato.',
- 'config-sqlite-fts3-downgrade' => 'Nawawala ang suportang FTS3 ng PHP, ibinababa ang uri ng mga talahanayan',
- 'config-can-upgrade' => "Mayroong mga talahanayan ng MediaWiki sa loob ng kalipunan ng datong ito.
-Upang maitaas ang uri ng mga ito upang maging MediaWiki na $1, pindutin ang '''Magpatuloy'''.",
- 'config-upgrade-done' => "Buo na ang pagtataas ng uri.
-
-Maaari mo na ngayong [$1 gamitin ang iyong wiki].
-
-Kung nais mong muling likhain ang iyong talaksang <code>LocalSettings.php</code>, lagitikin ang pindutang nasa ibaba.
-'''Hindi minumungkahi''' ito maliban na lamang kung nagkakaroon ka ng mga suliranin sa piling ng wiki mo.",
- 'config-upgrade-done-no-regenerate' => 'Buo na ang pagsasapanahon.
-
-Maaari ka na ngayong [$1 magsimula sa paggamit ng wiki mo].',
- 'config-regenerate' => 'Muling likhain ang LocalSettings.php →',
- 'config-show-table-status' => 'Nabigo ang pagtatanong na IPAKITA ANG KALAGAYAN NG TALAHANAYAN!', # Fuzzy
- 'config-unknown-collation' => "'''Babala:''' Ang kalipunan ng dato ay gumagagamit ng hindi nakikilalang pag-iipon.",
- 'config-db-web-account' => 'Akawnt ng kalipunan ng dato para sa pagpunta sa web',
- 'config-db-web-help' => 'Piliin ang pangalan ng tagagamit at hudyat na gagamitin ng tagapaghain ng web upang umugnay sa tagapaghain ng kalipunan ng dato, habang nasa pangkaraniwang pagtakbo ng wiki.',
- 'config-db-web-account-same' => 'Gamitin ang gayun din akawnt katulad ng sa pagluluklok',
- 'config-db-web-create' => 'Likhain ang akawnt kung hindi pa ito umiiral',
- 'config-db-web-no-create-privs' => 'Ang tinukoy mong akawnt na iluluklok ay walang sapat na mga pribilehiyo upang makalikha ng isang akawnt.
-Ang akawnt na tutukuyin mo rito ay umiiral na dapat.',
- 'config-mysql-engine' => 'Makinang imbakan:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Babala''': Pinili mo ang MyISAM bilang makinang imbakan para sa MySQL, na hindi iminumungkahi para gamitin sa MediaWiki, sapagkat:
-* bahagya lamang itong sumusuporta ng pagkakasundu-sundo dahil sa pagkakandado ng talahanayan
-* mas malaki ang pagkakataon na kapitan ng sira kaysa sa ibang mga makina
-* ang himpilang kodigo ng MediaWiki ay hindi palaging humahawak ng MyISAM ayon sa nararapat
-
-Kung ang iyong nakaluklok na MySQL ay sumusuporta ng InnoDB, higit na iminumungkahi na piliin mo iyon sa halip.
-Kung ang iyong nakaluklok na MySQL ay hindi sumusuporta ng InnoDB, marahil ay panahon na para sa isang pagtataas ng uri.",
- 'config-mysql-engine-help' => "Ang '''InnoDB''' ay ang halos palaging pinaka mainam na mapipili, dahil mayroon itong mabuting suporta ng pagkakasundu-sundo.
-
-Maaaring mas mabilis ang '''MyISAM''' sa mga pagluluklok na pang-isahang tagagamit o mababasa lamang.
-May gawi ang mga kalipunan ng dato ng MyISAM na masira nang mas madalas kaysa sa mga kalipunan ng dato ng InnoDB.",
- 'config-mysql-charset' => 'Pangkat ng panitik ng kalipunan ng dato:',
- 'config-mysql-binary' => 'Binaryo',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "Sa '''gawi na binaryo''', iniimbak ng MediaWiki ang tekstong UTF-8 sa kalipunan ng dato sa loob ng mga hanay na binaryo.
-Mas kapaki-pakinabang ito kaysa sa gawi na UTF-8 ng MySQL, at nagpapahintulot sa iyo upang magamit ang buong kasaklawan ng mga panitik ng Unikodigo.
-
-Sa ''gawi na UTF-8''', malalaman ng MySQL kung sa anong pangkat ng panitik nakapaloob ang iyong dato, at angkop na makakapagharap at makapapagpalit nito, subalit hindi ka nito papayagan na mag-imbak ng mga panitik na nasa itaas ng [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] o Saligang Tapyas na Pangmaramihang Wika.",
- 'config-site-name' => 'Pangalan ng wiki:',
- 'config-site-name-help' => "Lilitaw ito sa bareta ng pamagat ng pantingin-tingin at sa samu't saring ibang mga lugar.",
- 'config-site-name-blank' => 'Magpasok ng isang pangalan ng sityo.',
- 'config-project-namespace' => 'Puwang na pampangalan ng proyekto:',
- 'config-ns-generic' => 'Proyekto',
- 'config-ns-site-name' => 'Katulad ng sa pangalan ng wiki: $1',
- 'config-ns-other' => 'Iba pa (tukuyin)',
- 'config-ns-other-default' => 'Wiki Ko',
- 'config-project-namespace-help' => 'Bilang pagsunod sa halimbawa ng Wikipedia, maraming mga wiki ang nagpapanatili ng kanilang mga pahina ng patakaran na nakahiwalay magmula sa kanilang mga pahina ng nilalaman, na nasa loob ng isang "\'\'\'puwang na pampangalan ng proyekto\'\'\'".
-Ang lahat ng mga pamagat ng pahina na nasa loob ng puwang ng pangalang ito ay nagsisimula na mayroong isang partikular na unlapi, na maaari mong tukuyin dito.
-Sa nakaugalian, ang unlaping ito ay hinango mula sa pangalan ng wiki, subalit hindi ito maaaring maglaman ng mga panitik ng palabantasan na katulad ng "#" o ":".',
- 'config-ns-invalid' => 'Ang tinukoy na puwang ng pangalan na "<nowiki>$1</nowiki>" ay hindi katanggap-tanggap.
-Tumukoy ng isang ibang puwang ng pangalan ng proyekto.',
- 'config-ns-conflict' => 'Ang tinukoy na puwang ng pangalan na "<nowiki>$1</nowiki>" ay sumasalungat sa isang likas na nakatakdang puwang ng pangalan ng MediaWiki.
-Tumukoy ng isang ibang puwang ng pangalan ng proyekto.',
- 'config-admin-box' => 'Akawnt ng tagapangasiwa',
- 'config-admin-name' => 'Pangalan mo:',
- 'config-admin-password' => 'Hudyat:',
- 'config-admin-password-confirm' => 'Hudyat uli:',
- 'config-admin-help' => 'Ipasok dito ang mas ninanais mong pangalan ng tagagamit, bilang halimbawa na ang "Joe Bloggs".
-Ito ang pangalang gagamitin mo upang lumagdang papasok sa wiki.',
- 'config-admin-name-blank' => 'Magpasok ng isang pangalan ng tagagamit na tagapangasiwa.',
- 'config-admin-name-invalid' => 'Ang tinukoy na pangalan ng tagagamit na "<nowiki>$1</nowiki>" ay hindi tanggap.
-Tumukoy ng ibang pangalan ng tagagamit.',
- 'config-admin-password-blank' => 'Magpasok ng isang hudyat para sa akawnt ng tagapangasiwa.',
- 'config-admin-password-same' => 'Ang hudyat ay hindi dapat na katulad ng pangalan ng tagagamit.',
- 'config-admin-password-mismatch' => 'Hindi magkatugma ang ipinasok mong dalawang mga hudyat.',
- 'config-admin-email' => 'Tirahan ng e-liham:',
- 'config-admin-email-help' => 'Magpasok dito ng isang tirahan ng e-liham upang mapahintulutan kang makatanggap ng e-liham mula sa iba pang mga tagagamit sa ibabaw ng wiki, itakdang muli ang hudyat mo, at mapabatiran ng mga pagbabago sa mga pahinang nasa ibabaw ng iyong tala ng mga binabantayan. Maiiwanan mo na walang laman ang hanay na ito.',
- 'config-admin-error-user' => 'Panloob na kamalian kapag nililikha ang isang tagapangasiwa na may pangalang "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Panloob na kamalian kapag nagtatakda ng isang hudyat na para sa tagapangasiwang "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Nagpasok ka ng isang hindi katanggap-tanggap na tirahan ng e-liham.',
- 'config-subscribe' => 'Tumanggap mula sa [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce talaan ng mga pinadadalhan ng mga nilalabas na mga pabatid].',
- 'config-subscribe-help' => 'Isang itong tala ng pagliliham na mababa ang dami na ginagamit para sa pagpapakawala ng mga pahayag, kabilang na ang mahahalagang mga pahayag na pangkatiwasayan. Dapat kang magpasipi nito at isapanahon ang iyong nakaluklok na MediaWiki kapag lumalabas ang bagong mga bersiyon.',
- 'config-subscribe-noemail' => 'Sinubukan mong magpasipi sa tala ng nililihaman ng pagpapakawala ng mga pahayag na hindi nagbibigay ng isang tirahan ng -eliham. Paki magbigay ng isang tirahan ng e-liham kung nais mong magpasipi sa listahan ng pagliliham.',
- 'config-almost-done' => 'Halos tapos ka na!
-Maaari mo ngayong laktawan ang natitira pang pag-aayos at iluklok na ang wiki ngayon.',
- 'config-optional-continue' => 'Magtanong sa akin ng marami pang mga tanong.',
- 'config-optional-skip' => 'Naiinip na ako, basta iluklok na lang ang wiki.',
- 'config-profile' => 'Balangkas ng mga karapatan ng tagagamit:',
- 'config-profile-wiki' => 'Tradisyonal na wiki', # Fuzzy
- 'config-profile-no-anon' => 'Kailangan ang paglikha ng akawnt',
- 'config-profile-fishbowl' => 'Pinahintulutang mga patnugot lamang',
- 'config-profile-private' => 'Pribadong wiki',
- 'config-profile-help' => "Pinaka mahusay ang pagtakbo ng mga Wiki kapag pinapahintulutan mo ang pinaka maraming mga tao na makapamatnugot ng mga ito hanggang sa maaari.
-Sa loob ng MediaWiki, maginhawang masusuring muli ang kamakailang mga pagbabago, at mapanauli sa dati ang anumang nasira na nagawa ng isang walang muwang o may masamang hangarin na mga tagagamit.
-
-Subalit, marami ang nakatagpo na nagagamit ang MediaWiki sa loob ng malawak na sari-saring mga gampanin, at kung minsan ay hindi madaling makumbinsi ang lahat ng mga tao hinggil sa kapakinabangan ng kaparaanan ng wiki.
-Kung kaya't nasa iyo ang pagpili.
-
-Ang isang '''{{int:config-profile-wiki}}''' ay nagpapahintulot sa sinuman upang makapagbago, na hindi kailangan ang paglagdang papasok.
-Ang isang wiki na mayroong '''{{int:config-profile-no-anon}}''' ay nagbibigay ng karagdagang pananagutan, subalit maaaring pumigil sa nagkataon lamang na mga tagapag-ambag.
-
-Ang tagpo na '''{{int:config-profile-fishbowl}}''' ay nagpapahintulot lamang sa pinayagang mga tagagamit na makatingin ng mga pahina, na kapiling ang pangkat na pinayagang makapamatnugot.
-Ang isang '''{{int:config-profile-private}}''' ay nagpapahintulot lamang sa pinayagang mga tagagamit na makatingin ng mga pahina, na kapiling ang pangkat na pinayagang makapamatnugot.
-
-Ang mas masasalimuot na mga kaayusan ng mga karapatan ng tagagamit ay makukuha pagkaraan ng pagluluklok, tingnan ang [//www.mediawiki.org/wiki/Manual:User_rights may kaugnayang kinamay na lahok].", # Fuzzy
- 'config-license' => 'Karapatang-ari at lisensiya:',
- 'config-license-none' => 'Walang talababa ng lisensiya',
- 'config-license-cc-by-sa' => 'Malikhaing Pangkaraniwang Pagtukoy Pamamahaging Magkatulad',
- 'config-license-cc-by' => 'Atribusyon ng Creative Commons',
- 'config-license-cc-by-nc-sa' => 'Malikhaing Pangkaraniwang Pagtukoy Hindi-Pangkalakal Pamamahaging Magkatulad',
- 'config-license-cc-0' => 'Sero na Creative Commons (Nasasakop ng Madla)',
- 'config-license-gfdl' => 'Lisensiyang 1.3 ng Malayang Dokumentasyon ng GNU o mas lalong huli',
- 'config-license-pd' => 'Nasasakupan ng Madla',
- 'config-license-cc-choose' => 'Pumili ng isang pasadyang Lisensiya ng Malikhaing mga Pangkaraniwan',
- 'config-license-help' => "Maraming mga pangmadlang wiki ang naglalagay ng lahat ng mga ambag sa ilalim ng [http://freedomdefined.org/Definition lisensiyang malaya].
-Nakakatulong ito sa paglikha ng isang diwa ng pagmamay-ari ng pamayanan at nakapanghihikayat ng ambag na pangmahabang panahon.
-Sa pangkalahatan, hindi kailangan ang isang wiking pribado o pangsamahan.
-
-Kung nais mong magamit ang teksto magmula sa Wikipedia, at nais mong makatanggap ang Wikipedia ng tekstong kinopya magmula sa wiki mo, dapat mong piliin ang '''Creative Commons Attribution Share Alike''' (Pagbanggit na Pinagsasaluhang Magkatulad ng Malikhaing Pangkaraniwan).
-
-Dating ginamit ng Wikipedia ang Lisensiya ng Kasulatang Malaya ng GNU (GNU Free Documentation License o GFDL).
-Isang katanggap-tanggap na lisensiya ang GFDL, subalit mahirap itong maunawaan.
-Mahirap din ang paggamit na muli ng nilalaman na nasa ilalim ng GFDL.",
- 'config-email-settings' => 'Mga katakdaan ng e-liham',
- 'config-enable-email' => 'Paganahin ang palabas na e-liham',
- 'config-enable-email-help' => 'Kung nais mong gumana ang e-liham, ang mga katakdaan ng liham ng [http://www.php.net/manual/en/mail.configuration.php PHP] ay kailangang maging wasto ang pagkakaayos.
-Kung ayaw mo nang anumang mga katampukan ng e-liham, maaari mong huwag paganahin ang mga ito rito.',
- 'config-email-user' => 'Paganahin ang tagagamit-sa-tagagamit na e-liham',
- 'config-email-user-help' => 'Payagan ang lahat ng mga tagagamit na magpadala ng e-liham sa bawat isa kapag pinagana nila ito sa kanilang mga nais.',
- 'config-email-usertalk' => 'Paganahin ang pabatid na pampahina ng usapan ng tagagamit',
- 'config-email-usertalk-help' => 'Payagan ang mga tagagamit na tumanggap ng mga pabatid sa mga pagbabago ng pahina ng usapan ng tagagamit, kapag pinagana nila ito sa kanilang mga nais.',
- 'config-email-watchlist' => 'Paganahin ang pabatid ng talaan ng bantayan',
- 'config-email-watchlist-help' => 'Payagan ang mga tagagamit na tumanggap ng mga pabatid tungkol sa kanilang binabantayang mga pahina kapag pinagana nila ito sa kanilang mga nais.',
- 'config-email-auth' => 'Paganahin ang pagpapatunay ng e-liham',
- 'config-email-auth-help' => "Kapag pinagagana ang mapipiling ito, dapat tiyakin ng mga tagagamit ang kanilang tirahan ng e-liham na ginagamit ang isang kawing na ipinadala sa kanila tuwing itinatakda o binabago nila ito.
-Tanging napatunayang mga tirahan ng e-liham lamang ang makakatanggap ng mga e-liham magmula sa ibang mga tagagamit o makakapagbago ng mga e-liham ng pagpapabatid.
-'''Iminumungkahi''' ang mapipiling katakdaan na ito para sa mga wiking pangmadla dahil sa maaaring mangyaring pagmamalabis ng mga katampukan ng e-liham.",
- 'config-email-sender' => 'Pabalik na tirahan ng e-liham:',
- 'config-email-sender-help' => 'Ipasok ang tirahan ng e-liham na gagamitin bilang tirahang pagsasaulian ng e-liham na papalabas.
-Dito ang kung saan ipapadala ang mga pagtalbog.
-Maraming mga tagapaghain ng liham ang nangangailangan ng kahit na bahagi lamang ng pangalan ng nasasakupan upang maging katanggap-tanggap.',
- 'config-upload-settings' => 'Mga pagkakarga ng mga larawan at talaksan',
- 'config-upload-enable' => 'Paganahin ang pagkakarga ng talaksan',
- 'config-upload-help' => 'Ang paitaas na mga pagkakarga ng mga talaksan ay maaaring makapaglantad ng iyong tagapaghain sa mga panganib na pangkatiwasayan.
-Para sa mas marami pang kabatiran, basahin ang [//www.mediawiki.org/wiki/Manual:Security seksiyon ng katiwasayan] sa loob ng gabay.
-
-Upang mapagana ang paitaas na mga pagkakarga ng talaksan, baguhin ang gawi roon sa subdirektoryo ng <code>mga imahe</code> sa ilalim ng ugat na direktoryo ng MediaWiki upang ang tagapaghain ng kasaputan ay makapagsulat dito.
-Pagkaraan ay paganahin ang pipiliing ito.',
- 'config-upload-deleted' => 'Direktoryo para sa binurang mga talaksan:',
- 'config-upload-deleted-help' => 'Pumili ng isang direktoryong pagsusupnayan ng naburang mga talaksan.
-Ideyal na dapat itong hindi mapupuntahan mula sa web.',
- 'config-logo' => 'URL ng logo:',
- 'config-logo-help' => 'Ang likas na nakatakdang pabalat ng MediaWiki ay nagsasama ng puwang para sa isang logong 135x160 ang piksel na nasa itaas ng menu ng panggilid na bareta.
-Magkargang papaitaas ng isang imahe na mayroong naaangkop na sukat, at ipasok dito ang URL.
-
-Kung ayaw mo ng isang logo, iwanang walang laman ang kahong ito.', # Fuzzy
- 'config-instantcommons' => 'Paganahin ang Mga Pangkaraniwang Biglaan',
- 'config-instantcommons-help' => 'Ang [//www.mediawiki.org/wiki/InstantCommons Instant Commons] ay isang tampok na nagpapahintulot sa mga wiki upang gumamit ng mga imahe, mga tunog at iba pang mga midyang matatagpuan sa pook ng [//commons.wikimedia.org/ Wikimedia Commons].
-Upang magawa ito, nangangailangan ang MediaWiki ng pagka nakakapunta sa Internet.
-
-Para sa mas marami pang kabatiran hinggil sa tampok na ito, kabilang na ang mga tagubilin sa kung paano ito itakda para sa mga wiki na bukod pa kaysa sa Wikimedia Commons, sumangguni sa [//mediawiki.org/wiki/Manual:$wgForeignFileRepos gabay].',
- 'config-cc-error' => 'Hindi nagbigay ng resulta ang pampili ng lisensiya ng Malikhaing Pangkaraniwan.
-Ipasok na kinakamay ang pangalan ng lisensiya.',
- 'config-cc-again' => 'Pumili uli...',
- 'config-cc-not-chosen' => 'Piliin kung anong lisensiya ng Malikhaing mga Pangkaraniwan ang nais mo at pindutin ang "magpatuloy".',
- 'config-advanced-settings' => 'Mas masulong na pagkakaayos',
- 'config-cache-options' => 'Mga katakdaan para sa pagtatago ng bagay:',
- 'config-cache-help' => 'Ang pagtatago ng bagay ay ginagamit upang mapainam ang tulin ng MediaWiki sa pamamagitan ng pagtatago ng madalas gamiting dato.
-Ang mga pook na bahagya hanggang malalaki ang sukat ay labis na hinihikayat na paganahin ito, at ang mga pook na maliliit ay makakakita rin ng mga kapakinabangan.',
- 'config-cache-none' => 'Walang pagtatago (tinanggal ang katungkulan, subalit maaaring maapektuhan ang tulin sa mas malalaking mga pook ng wiki)',
- 'config-cache-accel' => 'Pagtatago ng bagay ng PHP (APC, XCache o WinCache)',
- 'config-cache-memcached' => 'Gamitin ang Pagtatago sa Alaala (Memcached) (nangangailangan ng karagdagang kaayusan ng pagkakahanda at pagsasaayos)',
- 'config-memcached-servers' => 'Mga tagapaghaing itinago sa alaala:',
- 'config-memcached-help' => 'Listahan ng mga tirahan ng IP na gagamitin para sa Memcached o Itinagong Alaala.
-Dapat na tukuyin na isa sa bawat guhit at tukuyin ang daungang gagamitin. Bilang halimbawa:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Pinili mo ang Memcached bilang uri mo ng taguan ngunit hindi tumukoy ng anumang mga tagapaghain.',
- 'config-memcache-badip' => 'Nagpasok ka ng isang hindi tanggap na tirahan ng IP para sa Memcached: $1.',
- 'config-memcache-noport' => 'Hindi ka tumukoy ng isang daungan na gagamitin para sa tagapaghain ng Memcached: $1.
-Kung hindi mo alam ang daungan, ang likas na nakatakda ay 11211.',
- 'config-memcache-badport' => 'Ang bilang ng daungan ng Memcached ay dapat na nasa pagitan ng $1 at $2.',
- 'config-extensions' => 'Mga dugtong',
- 'config-extensions-help' => 'Ang mga dugtong na nakalista sa ibabaw ay napansin sa loob ng iyong direktoryo ng <code>./extensions</code>.
-
-Maaaring mangailangan ang mga ito ng karagdagang kaayusan, subalit mapapagana mo ngayon ang mga ito',
- 'config-install-alreadydone' => "'''Babala:''' Tila nailuklok mo na ang MediaWiki at tinatangka mong iluklok ito ulit.
-Paki magpatuloy sa susunod na pahina.",
- 'config-install-begin' => 'Sa pamamagitan ng pagpindot sa "{{int:config-continue}}", sisimulan mo ang pagluluklok ng MediaWiki.
-Kung nais mo paring gumawa ng mga pagbabago, paki pindutin ang bumalik.', # Fuzzy
- 'config-install-step-done' => 'nagawa na',
- 'config-install-step-failed' => 'nabigo',
- 'config-install-extensions' => 'Isinasama ang mga karugtong',
- 'config-install-database' => 'Inihahanda ang kalipunan ng dato',
- 'config-install-schema' => 'Nililikha ang panukala',
- 'config-install-pg-schema-not-exist' => 'Hindi umiiral ang panukala ng PostgreSQL.',
- 'config-install-pg-schema-failed' => 'Nabigo ang paglikha ng mga talahanayan.
-Tiyakin na ang tagagamit na "$1" ay maaaring makasulat sa balangkas na "$2".',
- 'config-install-pg-commit' => 'Isinasagawa ang mga pagbabago',
- 'config-install-pg-plpgsql' => 'Sumusuri ng wikang PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Kailangan mong magtalaga ng wikang PL/pgSQL sa loob ng kalipunan ng datong $1',
- 'config-pg-no-create-privs' => 'Ang tinukoy mong akawnt para sa pagtatalaga ay walang sapat na mga pribilehiyo upang makalikha ng isang akawnt.',
- 'config-pg-not-in-role' => 'Umiiral na ang akawnt na tinukoy mo para sa tagagamit ng sangkasaputan.
-Ang tinukoy mong akawnt para sa pagluluklok ay hindi isang tagagamit na super at hindi isang kasapi sa gampanin ng tagagamit ng sangkasaputan, kung kaya\'t hindi nito nagawang makalikha ng mga bagay na pag-aari ng tagagamit ng sangkasaputan.
-
-Sa kasalukuyan, nangangailangan ang MediaWiki na ang mga talahanayan ay maging pag-aari ng tagagamit ng sangkasaputan. Paki tumukoy ng isa pang pangalan ng akawnt na pangsangkasaputan, o pindutin ang "bumalik" at tumukoy ng isang tagagamit na may kaangkupang pribilehiyo ng pagluluklok.',
- 'config-install-user' => 'Nililikha ang tagagamit ng kalipunan ng dato',
- 'config-install-user-alreadyexists' => 'Umiiral na ang tagagamit na "$1"',
- 'config-install-user-create-failed' => 'Nabigo ang paglikha ng tagagamit na "$1": $2',
- 'config-install-user-grant-failed' => 'Nabigo ang pagbibigay ng pahintulot sa tagagamit na "$1": $2',
- 'config-install-user-missing' => 'Hindi umiiral ang tinukoy na tagagamit na si "$1".',
- 'config-install-user-missing-create' => 'Hindi umiiral ang tinukoy na tagagamit na si "$1".
-Paki lagitikin ang nasa ibabang kahong natsetsekan na "likhain ang akawnt" kung nais mong likhain ito.',
- 'config-install-tables' => 'Nililikha ang mga talahanayan',
- 'config-install-tables-exist' => "'''Babala''': Tila umiiral na ang mga talahanayan ng MediaWiki.
-Nilalaktawan ang paglikha.",
- 'config-install-tables-failed' => "'''Kamalian''': Nabigo ang paglikha ng talahanayan na may sumusunod na kamalian: $1",
- 'config-install-interwiki' => 'Nilalagyan ng laman ang likas na nakatakdang talahanayan ng interwiki',
- 'config-install-interwiki-list' => 'Hindi matagpuan ang talaksang <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Babala''': Tila may mga laman na ang talahanayan ng interwiki.
-Nilalaktawan ang likas na nakatakdang talaan.",
- 'config-install-stats' => 'Sinisimulan ang estadistika',
- 'config-install-keys' => 'Ginagawa ang lihim na mga susi',
- 'config-insecure-keys' => "'''Babala:''' Nalikha ang {{PLURAL:$2|A secure key|ligtas na mga susi}} ($1) habang ang pagluluklok {{PLURAL:$2|ay|ay}} hindi pa lubos na ligtas. Isaalang-alang ang kinakamay na pagbago {{PLURAL:$2|nito|ng mga ito}}.",
- 'config-install-sysop' => 'Nililikha ang akawnt ng tagagamit na tagapangasiwa',
- 'config-install-subscribe-fail' => 'Hindi nagawang magpasipi mula sa mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'Hindi nakalagak ang cURL at hindi makukuha ang allow_url_fopen',
- 'config-install-mainpage' => 'Nililikha ang pangunahing pahina na may likas na nakatakdang nilalaman',
- 'config-install-extension-tables' => 'Nililikha ang mga talahanayan para sa pinagaganang mga dugtong',
- 'config-install-mainpage-failed' => 'Hindi maisingit ang pangunahing pahina: $1',
- 'config-install-done' => "'''Maligayang bati!'''
-Matagumpay mong nailuklok ang MediaWiki.
-
-Ang tagapagluklok ay nakagawa ng isang talaksan ng <code>LocalSettings.php</code>.
-Naglalaman ito ng lahat ng iyong mga pagsasaayos.
-
-Kailangan mo itong ikargang paibaba at ilagay ito sa lipon ng iyong pagluluklok ng wiki (katulad ng direktoryo ng index.php). Ang pagkakargang paibaba ay dapat na kusang magsimula.
-
-Kung ang pagkakargang paibaba ay hindi inialok, o kung hindi mo ito itinuloy, maaari mong muling simulan ang pagkakargang paibaba sa pamamagitan ng pagpindot sa kawing na nasa ibaba:
-
-$3
-
-'''Paunawa''': Kapag hindi mo ito ginawa ngayon, ang nagawang talaksang ito ng pagkakaayos ay hindi mo na makukuha mamaya kapag lumabas ka mula sa pagluluklok na hindi ikinakarga itong paibaba.
-
-Kapag nagawa na iyan, maaari ka nang '''[$2 pumasok sa wiki mo]'''.",
- 'config-download-localsettings' => 'Ikargang paibaba ang <code>LocalSettings.php</code>',
- 'config-help' => 'saklolo',
- 'config-nofile' => 'Hindi matagpuan ang talaksang "$1". Binura na ba ito?',
- 'mainpagetext' => "'''Matagumpay na ininstala ang MediaWiki.'''",
- 'mainpagedocfooter' => "Silipin ang [//meta.wikimedia.org/wiki/Help:Contents Patnubay sa Tagagamit] (''\"User's Guide\"'') para sa kaalaman sa paggamit ng wiking ''software''.
-
-== Pagsisimula ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Tala ng mga nakatakdang kumpigurasyon]
-* [//www.mediawiki.org/wiki/Manual:FAQ Mga malimit itanong sa MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Tala ng mga pinadadalhan ng liham ng MediaWiki]", # Fuzzy
-);
-
-/** толышә зывон (толышә зывон)
- * @author Erdemaslancan
- */
-$messages['tly'] = array(
- 'config-page-options' => 'Кукон',
-);
-
-/** Turkish (Türkçe)
- */
-$messages['tr'] = array(
- 'mainpagetext' => "'''MediaWiki başarı ile kuruldu.'''",
- 'mainpagedocfooter' => 'Viki yazılımının kullanımı hakkında bilgi almak için [//meta.wikimedia.org/wiki/Help:Contents kullanıcı rehberine] bakınız.
-
-== Yeni Başlayanlar ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Yapılandırma ayarlarının listesi]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki SSS]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]', # Fuzzy
-);
-
-/** Tatar (Cyrillic script) (татарча)
- * @author KhayR
- */
-$messages['tt-cyrl'] = array(
- 'mainpagetext' => '«MediaWiki» уңышлы куелды.',
- 'mainpagedocfooter' => "Бу вики турында мәгълүматны [//meta.wikimedia.org/wiki/Ярдәм:Эчтәлек биредә] табып була.
-
-== Кайбер файдалы ресурслар ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Көйләнмәләр исемлеге (инг.)];
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki турында еш бирелгән сораулар һәм җаваплар (инг.)];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki'ның яңа версияләре турында хәбәрләр яздырып алу].", # Fuzzy
-);
-
-/** Tatar (Latin script) (tatarça)
- * @author Don Alessandro
- */
-$messages['tt-latn'] = array(
- 'mainpagetext' => '«MediaWiki» uñışlı quyıldı.',
- 'mainpagedocfooter' => "Bu wiki turında mäğlümatnı [//meta.wikimedia.org/wiki/Yärdäm:Eçtälek biredä] tabıp bula.
-
-== Qayber faydalı resurslar ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Köylänmälär isemlege (ing.)];
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki turında yış birelgän sorawlar häm cawaplar (ing.)];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki'nıñ yaña versiäläre turında xäbärlär yazdırıp alu].", # Fuzzy
-);
-
-/** Udmurt (удмурт)
- * @author Andrewboltachev
- */
-$messages['udm'] = array(
- 'mainpagetext' => "'''MediaWiki движок азинлыко пуктэмын.'''",
-);
-
-/** Uyghur (Arabic script) (ئۇيغۇرچە)
- * @author Sahran
- */
-$messages['ug-arab'] = array(
- 'mainpagetext' => "'''MediaWiki مۇۋەپپەقىيەتلىك قاچىلاندى.'''",
- 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents ئىشلەتكۈچى قوللانمىسى] نى زىيارەت قىلىپ wiki يۇمشاق دېتالىنى ئىشلىتىش ئۇچۇرىغا ئېرىشىڭ.
-
-== دەسلەپكى ساۋات ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings سەپلىمە تەڭشەك تىزىملىكى]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki كۆپ ئۇچرايدىغان مەسىلىلەرگە جاۋاب]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki تارقاتقان ئېلخەت تىزىملىكى]', # Fuzzy
-);
-
-/** Ukrainian (українська)
- * @author AS
- * @author Ahonc
- * @author Alex Khimich
- * @author Andriykopanytsia
- * @author Base
- * @author Diemon.ukr
- * @author Ата
- * @author Тест
- * @author 아라
- */
-$messages['uk'] = array(
- 'config-desc' => 'Інсталятор MediaWiki',
- 'config-title' => 'Встановлення MediaWiki $1',
- 'config-information' => 'Інформація',
- 'config-localsettings-upgrade' => "'''Увага''': було виявлено файл <code>LocalSettings.php</code>.
-Ваше програмне забезпечення може бути оновлено.
-Будь-ласка, перемістіть файл <code>LocalSettings.php</code> в іншу безпечну директорію, а потім знову запустіть програму установки.",
- 'config-localsettings-cli-upgrade' => 'Виявлено файл <code>LocalSettings.php</code>.
-Щоб оновити наявну установку, запустіть <code>update.php</code>',
- 'config-localsettings-key' => 'Ключ оновлення:',
- 'config-localsettings-badkey' => 'Ви вказали неправильний ключ.',
- 'config-upgrade-key-missing' => 'Виявлено наявну установку MediaWiki.
-Для оновлення цієї установки, будь ласка, вставте такий рядок в кінець вашого <code>LocalSettings.php</code>:
-$1',
- 'config-localsettings-incomplete' => 'Існуючий файл <code>LocalSettings.php</code> виявився неповним.
-Не вказано змінну $1.
-Будь ласка, змініть <code>LocalSettings.php</code> так, щоб цю змінну було задано, і натисніть "{{int:Config-continue}}".',
- 'config-localsettings-connection-error' => 'Сталася помилка при підключення до бази даних з допомогою налаштувань на сторінці <code>LocalSettings.php</code> чи <code>AdminSettings.php</code>. Будь ласка, виплавте ці налаштування і спробуйте знову.
-
-$1',
- 'config-session-error' => 'Помилка початку сесії: $1',
- 'config-session-expired' => 'Час Вашої сесії минув.
-Задана тривалість сесії — $1.
-Ви можете збільшити її, змінивши <code>session.gc_maxlifetime</code> у php.ini.
-Перезапустіть процес встановлення.',
- 'config-no-session' => 'Дані сесії було втрачено!
-Перевірте Ваш php.ini і переконайтесь, що <code>session.save_path</code> встановлено у відповідну папку.',
- 'config-your-language' => 'Ваша мова:',
- 'config-your-language-help' => 'Оберіть мову для використання в процесі установки.',
- 'config-wiki-language' => 'Мова для вікі:',
- 'config-wiki-language-help' => 'Виберіть мову, якою буде відображатися вікі.',
- 'config-back' => '← Назад',
- 'config-continue' => 'Далі →',
- 'config-page-language' => 'Мова',
- 'config-page-welcome' => 'Ласкаво просимо на MediaWiki!',
- 'config-page-dbconnect' => 'Підключення до бази даних',
- 'config-page-upgrade' => 'Оновлення існуючої установки',
- 'config-page-dbsettings' => 'Налаштування бази даних',
- 'config-page-name' => 'Назва',
- 'config-page-options' => 'Параметри',
- 'config-page-install' => 'Установка',
- 'config-page-complete' => 'Готово!',
- 'config-page-restart' => 'Перезапустити установку',
- 'config-page-readme' => 'Прочитай мене',
- 'config-page-releasenotes' => 'Інформація про версію',
- 'config-page-copying' => 'Копіювання',
- 'config-page-upgradedoc' => 'Оновлення',
- 'config-page-existingwiki' => 'Існуюча вікі',
- 'config-help-restart' => 'Ви бажаєте видалити всі введені та збережені вами дані і запустити процес установки спочатку?',
- 'config-restart' => 'Так, перезапустити установку',
- 'config-welcome' => '=== Перевірка оточення ===
-Будуть проведені базові перевірки, щоб виявити, чи можлива установка MediaWiki у даній системі.
-Не забудьте включити цю інформацію, якщо ви звернетеся по підтримку, як завершити установку.',
- 'config-copyright' => "=== Авторське право і умови ===
-
-$1
-
-Ця програма є вільним програмним забезпеченням; Ви можете розповсюджувати та/або змінювати її під ліцензією GNU General Public License, опублікованою Фондом вільного програмного забезпечення; версією 2 цієї ліцензії або будь-якою пізнішою на Ваш вибір.
-
-Ця програма поширюється з надією на те, що вона буде корисною, однак '''без жодних гарантій'''; навіть без неявної гарантії '''комерційної цінності''' або '''придатності для певних цілей'''.
-Див. GNU General Public License для детальної інформації.
-
-Ви повинні були отримати <doclink href=Copying>копію GNU General Public License</doclink> разом із цією програмою; якщо ж ні, зверніться до Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. або [http://www.gnu.org/copyleft/gpl.html ознайомтесь з нею онлайн].",
- 'config-sidebar' => '* [//www.mediawiki.org Сайт MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents Посібник користувача]
-* [//www.mediawiki.org/wiki/Manual:Contents Посібник адміністратора]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
-----
-* <doclink href=Readme>Read me</doclink>
-* <doclink href=ReleaseNotes>Інформація про випуск</doclink>
-* <doclink href=Copying>Ліцензія</doclink>
-* <doclink href=UpgradeDoc>Оновлення</doclink>',
- 'config-env-good' => 'Перевірку середовища успішно завершено.
-Ви можете встановити MediaWiki.',
- 'config-env-bad' => 'Було проведено перевірку середовища. Ви не можете встановити MediaWiki.',
- 'config-env-php' => 'Встановлено версію PHP: $1.',
- 'config-env-php-toolow' => 'Встановлено PHP $1.
-Натомість MediaWiki вимагає PHP $2 і вище.',
- 'config-unicode-using-utf8' => 'Використовувати utf8_normalize.so Брайона Віббера для нормалізації Юнікоду.',
- 'config-unicode-using-intl' => 'Використовувати [http://pecl.php.net/intl міжнародне розширення PECL] для нормалізації Юнікоду.',
- 'config-unicode-pure-php-warning' => "'''Увага''': [http://pecl.php.net/intl міжнародне розширення PECL] не може провести нормалізацію Юнікоду.
-Якщо ваш сайт має високий трафік, вам варто почитати про [//www.mediawiki.org/wiki/Unicode_normalization_considerations нормалізацію Юнікоду].",
- 'config-unicode-update-warning' => "'''Увага''': Встановлена версія обгортки нормалізації Юнікоду використовує стару версію бібліотеки [http://site.icu-project.org/ проекту ICU].
-Ви маєте [//www.mediawiki.org/wiki/Unicode_normalization_considerations оновити версію], якщо плануєте повноцінно використовувати Юнікод.",
- 'config-no-db' => 'Не вдалося знайти відповідний драйвер бази даних! Вам необхідно встановити драйвер бази даних для PHP. Підтримуються такі типи баз даних: $1.
-
-Якщо ви користуєтесь віртуальним хостингом, попросіть вашого хостинг-провайдера інсталювати відповідний драйвер бази даних.
-Якщо ви скомпілювали PHP самостійно, переналаштуйте його з включенням клієнта бази даних, наприклад за допомогою <code>./configure --with-mysql</code>.
-Якщо установлено PHP з пакетів Debian або Ubuntu, тоді ви також повинні встановити php5-mysql модуль.',
- 'config-outdated-sqlite' => "'''Увага''': у Вас встановлена версія SQLite $1, а це нижче, ніж мінімально необхідна версія $2. SQLite буде недоступним.",
- 'config-no-fts3' => "'''Увага''': SQLite зібраний без [//sqlite.org/fts3.html модуля FTS3], функції пошуку не будуть працювати у цій системі.",
- 'config-register-globals' => "'''Увага: Опція PHP <code>[http://php.net/register_globals register_globals]</code> увімкнена.'''
-'''Вимкніть її, якщо це можливо.'''
-MediaWiki буде працювати, але Ваш сервер буде більш вразливим до потенційного проникнення зовні.",
- 'config-magic-quotes-runtime' => "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] увімкнена!'''
-Ця опція призводить до непередбачуваного пошкодження даних.
-Ви не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
- 'config-magic-quotes-sybase' => "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] увімкнена!'''
-Ця опція призводить до непередбачуваного пошкодження даних.
-Ви не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
- 'config-mbstring' => "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] увімкнена!'''
-Ця опція призводить до непередбачуваного пошкодження даних.
-Ви не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
- 'config-ze1' => "'''Проблема: Опція PHP [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] увімкнена!'''
-Ця опція призводить до непередбачуваного пошкодження даних.
-Ви не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
- 'config-safe-mode' => "'''Увага:''' Опція PHP [http://www.php.net/features.safe-mode «безпечний режим»] увімкнена.
-Це може спричинити проблеми, зокрема із завантаженням файлів та вставкою математичних формул.",
- 'config-xml-bad' => 'XML-модуть PHP відсутній.
-MediaWiki необхідні його функції, без цього модуля вона працювати не буде.
-Якщо Ви використовуєте Mandrake, встановіть php-xml пакет.',
- 'config-pcre' => 'Модуть підтримку PCRE не знайдено.
-Для роботи MediaWiki необхідна підтримка Perl-сумісних регулярних виразів.',
- 'config-pcre-no-utf8' => "'''Помилка''': PCRE-модуть PHP, вочевидь, було зібрано без підтримки PCRE_UTF8.
-MediaWiki вимагає підтримку UTF-8 для коректної роботи.",
- 'config-memory-raised' => "Обмеження пам'яті PHP (<code>memory_limit</code>) $1, піднято до $2.",
- 'config-memory-bad' => "'''Увага:''' Розмір пам'яті PHP (<code>memory_limit</code>) становить $1.
-Імовірно, це замало.
-Встановлення може не вдатись!",
- 'config-ctype' => "'''Помилка''': PHP має бути зібраним з підтримкою [http://www.php.net/manual/en/ctype.installation.php розширення Ctype].",
- 'config-json' => "'''Fatal:''' PHP був скомпільований без підтримки JSON.
-Вам потрібно встановити або розширення PHP JSON або розширення[http://pecl.php.net/package/jsonc PECL jsonc] перед встановлення Медіавікі.
-* Розширення PHP включено у Red Hat Enterprise Linux (CentOS) 5 та 6, хоча має бути доступним у <code>/etc/php.ini</code> або <code>/etc/php.d/json.ini</code>.
-* Деякі дистрибутиви Лінукса, випущені після травня 2013, пропустили розширення PHP, натомість упакували розширення PECL як <code>php5-json</code> або <code>php-pecl-jsonc</code>.",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] встановлено',
- 'config-apc' => '[http://www.php.net/apc APC] встановлено',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] встановлено',
- 'config-no-cache' => "'''Увага:''' Не вдалося знайти [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] чи [http://www.iis.net/download/WinCacheForPhp WinCache].
-Кешування об'єктів не ввімкнено.",
- 'config-mod-security' => "'''Увага''': на Вашому веб-сервері увімкнено [http://modsecurity.org/ mod_security]. У разі неправильних налаштувать, він може викликати проблеми MediaWiki або іншого ПЗ, яке дозволяє користувачам надсилати довільний вміст.
-Зверніться до [http://modsecurity.org/documentation/ документації mod_security] або підтримки Вашого хостера, якщо під час роботи виникають незрозумілі помилки.",
- 'config-diff3-bad' => 'GNU diff3 не знайдено.',
- 'config-git' => 'Знайшов програму управління версіями Git: <code>$1</code>.',
- 'config-git-bad' => 'Програму управління версіями Git не знайдено.',
- 'config-imagemagick' => 'Виявлено ImageMagick: <code>$1</code>.
-Буде ввімкнуто відображення мініатюр, якщо ви дозволите завантаження файлів.',
- 'config-gd' => 'Виявлено вбудовано графічну бібліотеку GD.
-Буде ввімкнуто відображення мініатюр, якщо ви дозволите завантаження файлів.',
- 'config-no-scaling' => 'Не вдалося виявити бібліотеку GD чи ImageMagick.
-Відображення мініатюр буде вимкнено.',
- 'config-no-uri' => "'''Помилка:''' Не вдалося визначити поточний URI.
-Встановлення перервано.",
- 'config-no-cli-uri' => "'''Увага''': Не задано параметр --scriptpath, використовується за замовчуванням: <code>$1</code>.",
- 'config-using-server' => 'Використовується ім\'я сервера "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Використовується URL сервера "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Увага:''' Ваша типова папка для завантажень <code>$1</code> вразлива до виконання довільних скриптів.
-Хоча MediaWiki перевіряє усі завантажені файли на наявність загроз, наполегливо рекомендується [//www.mediawiki.org/wiki/Manual:Security#Upload_security закрити дану вразливість] перед тим, як дозволяти завантаження файлів.",
- 'config-no-cli-uploads-check' => "'''Увага:''' Ваша типова папка для завантажень (<code>$1</code>) не перевірялась на вразливість до виконання довільних скриптів під час встановлення CLI.",
- 'config-brokenlibxml' => 'У Вашій системі невдале поєднання версій PHP і libxml2, яке може спричинити пошкодження прихованих даних у MediaWiki та інших веб-застосунках.
-Оновіть PHP до версії 5.2.9 або пізнішої і libxml2 до 2.7.3 або пізнішої ([//bugs.php.net/bug.php?id=45996 відомості про помилку]).
-Встановлення перервано.',
- 'config-using531' => 'MediaWiki не можна використовувати разом з PHP $1 через помилку з параметрами-посиланнями <code>__call()</code>.
-Оновіть PHP до версії 5.3.2 і вище або відкотіть до PHP 5.3.0 щоб уникнути цієї проблеми.
-Встановлення скасовано.',
- 'config-suhosin-max-value-length' => 'Suhosin встановлено і обмежує параметра GET <code>length</code> до $1 байта. Компонент MediaWiki ResourceLoader буде обходити це обмеження, однак це зменшить продуктивність. Якщо це можливо, Вам варто встановити значення <code>suhosin.get.max_value_length</code> як 1024 і більше у <code>php.ini</code> і встановити таке ж значення <code>$wgResourceLoaderMaxQueryLength</code> у LocalSettings.php .',
- 'config-db-type' => 'Тип бази даних:',
- 'config-db-host' => 'Хост бази даних:',
- 'config-db-host-help' => 'Якщо сервер бази даних знаходиться на іншому сервері, введіть тут ім\'я хосту і IP адресу.
-
-Якщо Ви використовуєте віртуальний хостинг, Ваш хостинг-провайдер має надати Вам правильне ім\'я хосту у його документації.
-
-Якщо у Вас сервер із Windows Ви використовуєте MySQL, параметр "localhost" може не працювати для імені сервера. Якщо не працює, використайте "127.0.0.1" як локальну IP-адресу.
-
-Якщо Ви використовуєте PostgreSQL, залиште це поле пустим, щоб під\'єднатись через сокет Unix.',
- 'config-db-host-oracle' => 'TNS бази даних:',
- 'config-db-host-oracle-help' => 'Введіть допустиме [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; файл tnsnames.ora має бути видимим для цієї інсталяції. <br />Якщо Ви використовуєте бібліотеки 10g чи новіші, можна також використовувати метод іменування [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Ідентифікувати цю вікі',
- 'config-db-name' => 'Назва бази даних:',
- 'config-db-name-help' => 'Виберіть назву, що ідентифікує Вашу вікі.
-Вона не повинна містити пробілів.
-
-Якщо Ви використовуєте віртуальний хостинг, Ваш хостинг-провайдер або надасть Вам конкретну назву бази даних, або дозволить створювати бази даних з допомогою панелі управління.',
- 'config-db-name-oracle' => 'Схема бази даних:',
- 'config-db-account-oracle-warn' => 'Є три підтримувані сценарії установки Oracle:
-
-Якщо Ви хочете створити обліковий запис бази даних у процесі встановлення, будь ласка, вкажіть обліковий запис ролі SYSDBA для установки і бажані повноваження для облікового запису з веб-доступом. В протилежному випадку Ви можете або створити обліковий запис з веб-доступом вручну і вказати тільки цей обліковий запис (якщо він має необхідні дозволи на створення об\'єктів-схем), або вказати два різні облікові записи, з яких в одного будуть права на створення, а в другого, обмеженого — права веб-доступу.
-
-Скрипт для створення облікового запису з необхідними повноваженнями можна знайти у папці "maintenance/oracle/" цієї інсталяції. Майте на увазі, що використання обмеженого облікового запису вимкне можливість використання технічного обслуговування з облікового запису за замовчуванням.',
- 'config-db-install-account' => 'Обліковий запис користувача для встановлення',
- 'config-db-username' => "Ім'я користувача бази даних:",
- 'config-db-password' => 'Пароль бази даних:',
- 'config-db-password-empty' => 'Будь ласка, введіть пароль для нового користувача бази даних: $1.
-Хоча можна створювати користувачів без паролів, це не є безпечним.',
- 'config-db-install-username' => "Введіть ім'я користувача, яке буде використано для підключення до бази даних під час процесу встановлення.
-Це не ім'я користувача облікового запису MediaWiki; це ім'я користувача для Вашої бази даних.",
- 'config-db-install-password' => 'Введіть пароль, який буде використано для підключення до бази даних під час процесу встановлення.
-Це не пароль облікового запису MediaWiki; це пароль для Вашої бази даних.',
- 'config-db-install-help' => "Введіть ім'я користувача і пароль, які буде використано для підключення до бази даних у процесі встановлення.",
- 'config-db-account-lock' => "Використовувати ті ж ім'я користувача і пароль і для звичайної роботи",
- 'config-db-wiki-account' => 'Обліковий запис користувача для звичайної роботи',
- 'config-db-wiki-help' => "Введіть ім'я користувача і пароль, які будуть використовуватись для з'єднання з базою даних під час звичайної роботи.
-Якщо обліковий запис не існує, а в облікового запису інсталяції є достатні повноваження, цей обліковий запис користувача буде створено з мінімальними правами, що необхідні для роботи з вікі.",
- 'config-db-prefix' => 'Префікс таблиць бази даних:',
- 'config-db-prefix-help' => 'Якщо треба ділити одну базу даних між декількома вікі або між MediaWiki та іншим веб-застосунком, Ви можете додати префікс до усіх назв таблиць для уникнення конфліктів.
-Не використовуйте пробіли.
-
-Це поле зазвичай залишають пустим.',
- 'config-db-charset' => 'Кодування бази даних',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 зворотно сумісна з UTF-8',
- 'config-charset-help' => "'''Увага:''' Якщо Ви використовуєте '''зворотно сумісну UTF-8''' на MySQL 4.1+ і створюєте резервні копії бази даних з допомогою <code>mysqldump</code>, це може викривити усі не-ASCII символи, незворотно пошкодивши резервні копії!
-
-У '''бінарному режимі''' MediaWiki зберігає текст UTF-8 у базі даних з бінарними полями.
-Це більш ефективно, ніж UTF-8 режим MySQL, і дозволяє використовувати увесь набір символів Юнікоду.
-У '''режимі UTF-8''' MySQL буде знати, якого символу стосуються Ваші дані, і могтиме відображати та конвертувати їх належним чином,
-але не дозволятиме зберігати символи, що виходять за межі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-mysql-old' => 'Необхідна MySQL $1 або пізніша, а у Вас $2.',
- 'config-db-port' => 'Порт бази даних:',
- 'config-db-schema' => 'Схема для MediaWiki',
- 'config-db-schema-help' => 'Ця схема зазвичай працює добре.
-Змінюйте її тільки якщо знаєте, що Вам це потрібно.',
- 'config-pg-test-error' => "Не вдається підключитися до бази даних '''$1''': $2",
- 'config-sqlite-dir' => 'Папка даних SQLite:',
- 'config-sqlite-dir-help' => "SQLite зберігає усі дані в єдиному файлі.
-
-Папка, яку Ви вказуєте, має бути доступна серверу для запису під час встановлення.
-
-Вона '''не''' повинна бути доступна через інтернет, тому ми і не поміщуємо її туди, де Ваші файли PHP.
-
-Інсталятор пропише у неї файл <code>.htaccess</code>, але якщо це не спрацює, хтось може отримати доступ до Вашої вихідної бази даних, яка містить вихідні дані користувача (адреси електронної пошти, хеші паролів), а також видалені версії та інші обмежені дані на вікі.
-
-За можливості розташуйте базу даних десь окремо, наприклад в <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Простір таблиць за замовчуванням:',
- 'config-oracle-temp-ts' => 'Тимчасовий простір таблиць:',
- 'config-support-info' => 'MediaWiki підтримує таки системи баз даних:
-
-$1
-
-Якщо Ви не бачите серед перерахованих систему баз даних, яку використовуєте, виконайте вказівки, вказані вище, щоб увімкнути підтримку.',
- 'config-support-mysql' => '* $1 є основною для MediaWiki і найкраще підтримується ([http://www.php.net/manual/en/mysql.installation.php як зібрати PHP з допомогою MySQL])',
- 'config-support-postgres' => '* $1 — популярна відкрита СУБД, альтернатива MySQL ([http://www.php.net/manual/en/pgsql.installation.php як зібрати PHP з допомогою PostgreSQL]). Можуть зустрічатись деякі невеликі невиправлені помилки, не рекомендується використовувати у робочій системі.',
- 'config-support-sqlite' => '* $1 — легка система баз даних, яка дуже добре підтримується. ([http://www.php.net/manual/en/pdo.installation.php Як зібрати PHP з допомогою SQLite], що використовує PDO)',
- 'config-support-oracle' => '* $1 — комерційна база даних масштабу підприємства. ([http://www.php.net/manual/en/oci8.installation.php Як зібрати PHP з підтримкою OCI8])',
- 'config-header-mysql' => 'Налаштування MySQL',
- 'config-header-postgres' => 'Налаштування PostgreSQL',
- 'config-header-sqlite' => 'Налаштування SQLite',
- 'config-header-oracle' => 'Налаштування Oracle',
- 'config-invalid-db-type' => 'Невірний тип бази даних',
- 'config-missing-db-name' => "Ви повинні ввести значення параметру «Ім'я бази даних»",
- 'config-missing-db-host' => 'Ви повинні ввести значення параметру «Хост бази даних»',
- 'config-missing-db-server-oracle' => 'Ви повинні ввести значення параметру «TNS бази даних»',
- 'config-invalid-db-server-oracle' => 'Неприпустиме TNS бази даних "$1".
-Використовуйте "TNS Name" або рядок "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методи найменування Oracle])',
- 'config-invalid-db-name' => 'Неприпустима назва бази даних "$1".
-Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).',
- 'config-invalid-db-prefix' => 'Неприпустимий префікс бази даних "$1".
-Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).',
- 'config-connection-error' => "$1.
-
-Перевірте хост, ім'я користувача та пароль і спробуйте ще раз.",
- 'config-invalid-schema' => 'Неприпустима схема для MediaWiki "$1".
-Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9) і знаки підкреслення(_).',
- 'config-db-sys-create-oracle' => 'Інсталятор підтримує лише використання облікового запису SYSDBA для створення нового облікового запису.',
- 'config-db-sys-user-exists-oracle' => 'Обліковий запис користувача "$1" уже існує. SYSDBA використовується лише для створення новий облікових записів!',
- 'config-postgres-old' => 'Необхідна PostgreSQL $1 або пізніша, а у Вас $2.',
- 'config-sqlite-name-help' => 'Виберіть назву, що ідентифікує Вашу вікі.
-Не використовуйте пробіли і дефіси.
-Це буде використовуватись у назві файлу даних SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Не можна створити папку даних <code><nowiki>$1</nowiki></code>, оскільки батьківська папка <code><nowiki>$2</nowiki></code> не доступна веб-серверу для запису.
-
-Інсталятор виявив, під яким користувачем працює Ваш сервер.
-Зробіть папку <code><nowiki>$3</nowiki></code> доступною для запису, щоб продовжити.
-В ОС Unix/Linux виконайте:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Не можна створити папку даних <code><nowiki>$1</nowiki></code>, оскільки батьківська папка <code><nowiki>$2</nowiki></code> не доступна веб-серверу для запису.
-
-Інсталятор не зміг виявити, під яким користувачем працює Ваш сервер.
-Зробіть папку <code><nowiki>$3</nowiki></code> доступною для запису серверу (і всім!) глобально, щоб продовжити.
-В ОС Unix/Linux виконайте:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Помилка при створенні папки даних "$1".
-Перевірте розташування і спробуйте знову.',
- 'config-sqlite-dir-unwritable' => 'Не можливо записати до папки "$1".
-Змініть налаштування доступу так, щоб веб-сервер міг писати до неї, і спробуйте ще раз.',
- 'config-sqlite-connection-error' => '$1.
-
-Перевірте папку даних і назву бази даних нижче та спробуйте знову.',
- 'config-sqlite-readonly' => 'Файл <code>$1</code> недоступний для запису.',
- 'config-sqlite-cant-create-db' => 'Не вдалося створити файл бази даних <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'У PHP немає підтримки FTS3, скидаю таблиці',
- 'config-can-upgrade' => "У цій базі даних є таблиці MediaWiki.
-Щоб оновити їх до MediaWiki $1, натисніть '''Продовжити'''.",
- 'config-upgrade-done' => "Оновлення завершено.
-
-Ви можете зараз [$1 починати використовувати свою вікі].
-
-Якщо Ви хочете повторно згенерувати файл <code>LocalSettings.php</code>, натисніть на кнопку нижче.
-Це '''не рекомендується''', якщо тільки у Вас не виникли проблеми з Вашою вікі.",
- 'config-upgrade-done-no-regenerate' => 'Оновлення завершено.
-
-Ви можете зараз [$1 починати використовувати свою вікі].',
- 'config-regenerate' => 'Повторно згенерувати LocalSettings.php →',
- 'config-show-table-status' => 'Запит <code>SHOW TABLE STATUS</code> не виконано!',
- 'config-unknown-collation' => "'''Увага:''' База даних використовує нерозпізнане сортування.",
- 'config-db-web-account' => 'Обліковий запис бази даних для інтернет-доступу',
- 'config-db-web-help' => "Оберіть ім'я користувача і пароль, які веб-сервер буде використовувати для з'єднання із сервером бази даних під час звичайної роботи вікі.",
- 'config-db-web-account-same' => 'Використати той же обліковий запис для встановлення',
- 'config-db-web-create' => 'Створити обліковий запис, якщо його ще не існує',
- 'config-db-web-no-create-privs' => 'Обліковий запис, вказаний Вами для встановлення, не має достатніх повноважень для створення облікового запису.
-Обліковий запис, який Ви вказуєте тут, уже повинен існувати.',
- 'config-mysql-engine' => 'Двигун бази даних:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Увага''': Ви обрали MyISAM для зберігання даних MySQL, що не рекомендовано для роботи з MediaWiki, оскільки:
-* він слабко підтримує паралелізм через блокування таблиць
-* він більш схильний до ушкоджень, ніж інші двигуни
-* база коду MediaWiki не завжди працює з MyISAM так, як мала б.
-
-Якщо Ваша інсталяція MySQL підтримує InnoDB, дуже рекомендується вибрати цей двигун.
-Якщо Ваша інсталяція MySQL не підтримує InnoDB, можливо настав час її оновити.",
- 'config-mysql-only-myisam-dep' => '"\'Зауваження:"\' MyISAM є єдиним механізмом для зберігання MySQL, який не рекомендується для використання з MediaWiki, оскільки:
-* слабо підтримує паралелізм через блокування таблиць
-* більш схильний до пошкоджень, ніж інші двигуни
-* код MediaWiki не завжди розглядає MyISAM, як повинен
-
-Твоє встановлення MySQL не підтримує InnoDB, можливо, потрібно оновити.',
- 'config-mysql-engine-help' => "'''InnoDB''' є завжди кращим вибором, оскільки краще підтримує паралельний доступ.
-
-'''MyISAM''' може бути швидшим для одного користувача або в інсталяціях read-only.
-Бази даних MyISAM схильні псуватись частіше, ніж бази InnoDB.",
- 'config-mysql-charset' => 'Кодування бази даних:',
- 'config-mysql-binary' => 'Двійкове',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "У '''бінарному режимі''' MediaWiki зберігає текст UTF-8 у базі даних з бінарними полями.
-Це більш ефективно, ніж UTF-8 режим MySQL, і дозволяє використовувати увесь набір символів Юнікоду.
-
-У '''режимі UTF-8''' MySQL буде знати, якого символу стосуються Ваші дані, і могтиме відображати та конвертувати їх належним чином, але не дозволятиме зберігати символи, що виходять за межі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-site-name' => 'Назва вікі:',
- 'config-site-name-help' => 'Це буде відображатись у заголовку вікна браузера та у деяких інших місцях.',
- 'config-site-name-blank' => 'Введіть назву сайту.',
- 'config-project-namespace' => 'Простір назв проекту:',
- 'config-ns-generic' => 'Проект',
- 'config-ns-site-name' => 'Те ж саме, що й назва вікі: $1',
- 'config-ns-other' => 'Інше (вкажіть)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => 'За прикладом Вікіпедії, чимало вікі тримають свої сторінки правил окремо від сторінок основного вмісту, у "\'\'\'просторі імен проекту\'\'\'".
-Усі назви сторінок у цьому просторі імен починаються з певного префікса, який Ви можете вказати тут.
-Традиційно цей префікс виводиться з назви вікі, але не може містити знаки пунктуація, як-то "#" чи ":".',
- 'config-ns-invalid' => 'Вказаний простір імен "<nowiki>$1</nowiki>" не припустимий.
-Вкажіть інший простір імен проекту.',
- 'config-ns-conflict' => 'Вказаний простір імен "<nowiki>$1</nowiki>" конфліктує зі стандартним простором імен MediaWiki.
-Вкажіть інший простір імен проекту.',
- 'config-admin-box' => 'Обліковий запис адміністратора',
- 'config-admin-name' => "Ваше ім'я:",
- 'config-admin-password' => 'Пароль:',
- 'config-admin-password-confirm' => 'Пароль ще раз:',
- 'config-admin-help' => 'Введіть бажане ім\'я користувача тут, наприклад "Павло НЛО".
-Це ім\'я ви будете використовувати про вході у вікі.',
- 'config-admin-name-blank' => "Введіть ім'я користувача адміністратора.",
- 'config-admin-name-invalid' => 'Вказане ім\'я користувача "<nowiki>$1</nowiki>" не припустиме.
-Вкажіть інше ім\'я користувача.',
- 'config-admin-password-blank' => 'Введіть пароль до облікового запису адміністратора.',
- 'config-admin-password-same' => "Пароль не може бути таким же, як ім'я користувача.",
- 'config-admin-password-mismatch' => 'Два введені вами паролі не збігаються.',
- 'config-admin-email' => 'Адреса електронної пошти:',
- 'config-admin-email-help' => 'Введіть адресу електронної пошти, щоб мати змогу отримувати електронну пошту від інших користувачів у вікі, відновити пароль і отримувати повідомлення про зміни, внесені до сторінок у Вашому списку спостереження. Ви можете залишити це поле пустим.',
- 'config-admin-error-user' => 'Внутрішня помилка під час створення адміністратора з ім\'ям "<nowiki>$1</nowiki>".',
- 'config-admin-error-password' => 'Внутрішня помилка під час встановлення пароля для адміністратора "<nowiki>$1</nowiki>":<pre>$2</pre>',
- 'config-admin-error-bademail' => 'Ви ввели недопустиму адресу електронної пошти.',
- 'config-subscribe' => 'Підписатися на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce розсилку анонсів нових версій MediaWiki].',
- 'config-subscribe-help' => "Це список розсилки з малим обсягом повідомлень, що використовується для анонсування релізів, а також важливих повідомлень про безпеку.
-Вам варто підписати і оновлювати інсталяцію MediaWiki, коли з'являтимуться нові версії.",
- 'config-subscribe-noemail' => 'Ви намагались підписатись на розсилку анонсів релізів, не вказавши адреси електронної пошти.
-Будь ласка, вкажіть адресу електронної пошти, якщо хочете підписатись на розсилку.',
- 'config-almost-done' => 'Майже готово!
-Ви можете зараз пропустити налаштування, що залишилось, і встановити вікі прямо зараз.',
- 'config-optional-continue' => 'Запитуйте ще.',
- 'config-optional-skip' => 'Це вже втомлює, просто встановити вікі.',
- 'config-profile' => 'Профіль прав користувача:',
- 'config-profile-wiki' => 'Відкрита вікі',
- 'config-profile-no-anon' => 'Необхідно створити обліковий запис',
- 'config-profile-fishbowl' => 'Тільки для авторизованих редакторів',
- 'config-profile-private' => 'Приватна вікі',
- 'config-profile-help' => "Вікі краще працюють, коли Ви дозволяєте їх редагувати якомога ширшому колу людей.
-У MediaWiki легко переглядати останні зміни і відкочувати будь-яку шкоду, спричинену недосвідченими або зловмисними користувачами.
-
-Одначе, MediaWiki може бути корисна по-різному, й інколи важко переконати у вигідності відкритої вікі-роботи.
-Тож у Вас є вибір.
-
-Модель '''{{int:config-profile-wiki}}''' дозволяє редагувати будь-кому, навіть без входження в систему.
-Вікі з вимогою \"'''{{int:config-profile-no-anon}}'''\" дає певний облік, але може відвернути випадкових дописувачів.
-Спосіб \"'''{{int:config-profile-fishbowl}}'''\" дозволяє редагувати підтвердженим користувачам, а переглядати сторінки і історію можуть усі.
-'''{{int:config-profile-private}}''' дозволяє переглядати сторінки і редагувати лише підтвердженим користувачам.
-
-Детальніші конфігурації прав користувачів доступні після встановлення, див. [//www.mediawiki.org/wiki/Manual:User_rights відповідний розділ посібника].",
- 'config-license' => 'Авторські права і ліцензія:',
- 'config-license-none' => 'Без ліцензії у нижньому колонтитулі',
- 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
- 'config-license-cc-by' => 'Creative Commons Attribution',
- 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero (Суспільне надбання)',
- 'config-license-gfdl' => 'GNU Free Documentation License 1.3 або пізніша',
- 'config-license-pd' => 'Суспільне надбання (Public Domain)',
- 'config-license-cc-choose' => 'Виберіть одну з ліцензій Creative Commons',
- 'config-license-help' => "Чимало загальнодоступних вікі публікують увесь свій вміст під [http://freedomdefined.org/Definition вільною ліцензією]. Це розвиває відчуття спільної власності і заохочує довготривалу участь. У загальному випадку для приватної чи корпоративної вікі у цьому немає необхідності.
-
-Якщо Ви хочете мати змогу використовувати текст з Вікіпедії і дати Вікіпедії змогу використовувати текст, скопійований з Вашої вікі, вам необхідно обрати '''Creative Commons Attribution Share Alike'''.
-
-Раніше Вікіпедія використовувала GNU Free Documentation License.
-GFDL — допустима ліцензія, але у ній важко розібратися, а контент під GFDL важко використовувати повторно.",
- 'config-email-settings' => 'Налаштування електронної пошти',
- 'config-enable-email' => 'Увімкнути вихідну електронну пошту',
- 'config-enable-email-help' => 'Якщо Ви хочете, що електронна пошта працювала, необхідно виставити коректні [http://www.php.net/manual/en/mail.configuration.php налаштування пошти у PHP].
-Якщо Вам не потрібні жодні можливості електронної пошти у вікі, можете тут їх відключити.',
- 'config-email-user' => 'Увімкнути електронну пошту користувач-користувачеві',
- 'config-email-user-help' => 'Дозволити усім користувачам надсилати один одному електронну пошту, якщо вони увімкнули цю можливість у своїх налаштуваннях.',
- 'config-email-usertalk' => 'Увімкнути сповіщення про повідомлення на сторінці обговорення користувача',
- 'config-email-usertalk-help' => 'Дозволити користувачам отримувати сповіщення про зміни на своїй сторінці обговорення, якщо вони увімкнули цю можливість у своїх налаштуваннях.',
- 'config-email-watchlist' => 'Увімкнути сповіщення про зміни у списку спостереження',
- 'config-email-watchlist-help' => 'Дозволити користувачам отримувати сповіщення про сторінки з їхнього списку спостереження, якщо вони увімкнули цю можливість у своїх налаштуваннях.',
- 'config-email-auth' => 'Увімкнути автентифікацію через електронну пошту',
- 'config-email-auth-help' => "Якщо ця опція увімкнена, користувачам треба підтвердити свою адресу електронної пошти з допомогою надісланого їм посилання, коли вони встановлюють чи змінюють її.
-Тільки автентифіковані адреси електронної пошти отримують листи від інших користувачів або змінювати поштові сповіщення.
-Увімкнення цієї опції '''рекомендується''' загальнодоступним вікі через можливі зловживання функціями електронної пошти.",
- 'config-email-sender' => 'Зворотна адреса електронної пошти:',
- 'config-email-sender-help' => "Введіть адресу електронної пошти, що буде використовуватись як зворотна адреса для вихідної пошти.
-На неї будуть надсилатись відмови.
-Чимало поштових серверів вимагають, щоб принаймні доменне ім'я було допустимим.",
- 'config-upload-settings' => 'Завантаження зображень і файлів',
- 'config-upload-enable' => 'Дозволити завантаження файлів',
- 'config-upload-help' => 'Завантаження файлів підставляє Ваш сервер під потенційні загрози.
-Детальнішу інформацію можна почитати у посібнику, [//www.mediawiki.org/wiki/Manual:Security розділ про безпеку].
-
-Щоб дозволити завантаження файлів, змініть режим підпапки <code>images</code> у кореневій папці MediaWiki так, щоб сервер міг у неї записувати.
-Потім увімкніть цю опцію.',
- 'config-upload-deleted' => 'Каталог для вилучених файлів:',
- 'config-upload-deleted-help' => 'Оберіть папку для архівації видалених файлів.
-В ідеалі, вона не має бути доступною через інтернет.',
- 'config-logo' => 'URL логотипу:',
- 'config-logo-help' => 'Стандартна схема оформлення MediaWiki містить вільне для логотипу місце над бічною панеллю розміром 135x160 пікселів.
-
-Завантажте зображення відповідного розміру і введіть тут його URL.
-
-Ви можете використати <code>$wgStylePath</code> або <code>$wgScriptPath</code>, якщо ваш логотип пов\'язаний з цими шляхами.
-
-Якщо Вам не потрібен логотип, залиште це поле пустим.',
- 'config-instantcommons' => 'Увімкнути Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] це функція, що дозволяє вікі використовувати зображення, звуки та інші медіа, розміщені на [//commons.wikimedia.org/ Вікісховищі].
-Для цього MediaWiki необхідний доступ до інтернету.
-
-Додаткову інформацію стосовно цієї функції, включаючи інструкції, як її увімкнути у вікі, відмінних від Вікісховища, дивіться у [//mediawiki.org/wiki/Manual:$wgForeignFileRepos посібнику].',
- 'config-cc-error' => 'Механізм вибору ліцензії Creative Commons не дав результатів.
-Введіть назву ліцензії вручну.',
- 'config-cc-again' => 'Виберіть знову ...',
- 'config-cc-not-chosen' => 'Оберіть, яку ліцензію Creative Commons Ви хочете використовувати, і натисніть "продовжити".',
- 'config-advanced-settings' => 'Розширені налаштування',
- 'config-cache-options' => "Налаштування кешування об'єктів:",
- 'config-cache-help' => "Кешування об'єктів використовується для покращення швидкодії MediaWiki методом кешування часто використовуваних даних.
-Заохочується увімкнення цієї можливості для середніх і великих сайтів, малі сайти також можуть відчути її перевагу.",
- 'config-cache-none' => 'Без кешування (жодні функції не втрачаються, але впливає на швидкодію великих вікі-сайтів)',
- 'config-cache-accel' => "PHP кешування об'єктів (APC, XCache чи WinCache)",
- 'config-cache-memcached' => 'Використовувати Memcached (вимагає додаткової установки і налаштування)',
- 'config-memcached-servers' => 'Сервери Memcached:',
- 'config-memcached-help' => 'Список IP-адрес, що викоритовує Memcached.
-Вкажіть по одному в рядку, разом з портами. Наприклад:
- 127.0.0.1:11211
- 192.168.1.25:1234',
- 'config-memcache-needservers' => 'Ви обрали тип кешування Memcached, але не вказали ніяких серверів.',
- 'config-memcache-badip' => 'Ви ввели недопустиму IP-адресу для Memcached: $1.',
- 'config-memcache-noport' => 'Ви не вказали порт для сервера Memcached: $1.
-Якщо Ви його не знаєте, за замовчуванням використовується 11211.',
- 'config-memcache-badport' => 'Номери портів Memcached повинні лежати в межах від $1 до $2.',
- 'config-extensions' => 'Розширення',
- 'config-extensions-help' => 'Розширення, перераховані вище, були знайдені у папці <code>./extensions</code>.
-
-Вони можуть потребувати додаткових налаштувань, але Ви можете увімкнути їх зараз.',
- 'config-install-alreadydone' => "'''Увага:''' Здається, Ви вже встановлювали MediaWiki і зараз намагаєтесь встановити її знову.
-Будь ласка, перейдіть на наступну сторінку.",
- 'config-install-begin' => 'Натискаючи "{{int:config-continue}}", Ви розпочинаєте встановлення MediaWiki.
-Якщо Ви все ще хочете внести зміни, натисніть "{{int:config-back}}".',
- 'config-install-step-done' => 'виконано',
- 'config-install-step-failed' => 'не вдалося',
- 'config-install-extensions' => 'У тому числі розширення',
- 'config-install-database' => 'Налаштування бази даних',
- 'config-install-schema' => 'Створення схеми',
- 'config-install-pg-schema-not-exist' => 'Схеми PostgreSQL не існує.',
- 'config-install-pg-schema-failed' => 'Не вдалось створити таблиці.
-Переконайтесь, що користувач "$1" може писати до схеми "$2".',
- 'config-install-pg-commit' => 'Внесення змін',
- 'config-install-pg-plpgsql' => 'Перевірка мови PL/pgSQL',
- 'config-pg-no-plpgsql' => 'Вам необхідно встановити мову PL/pgSQL у базі даних $1',
- 'config-pg-no-create-privs' => 'Обліковий запис, вказаний для встановлення, має недостатньо прав для створення облікового запису.',
- 'config-pg-not-in-role' => 'Обліковий запис, який Ви вказали для веб-користувача, уже існує.
-Обліковий запис, який Ви вказали для встановлення не є суперюзером і не відноситься до ролі веб-користувача, тому неможливо створити об\'єкти, що належать веб-користувачеві.
-
-У даний час MediaWiki вимагає, щоб усі таблиці належали веб-користувачу. Будь ласка, вкажіть інше ім\'я облікового запису або натисніть "Назад" та вкажіть користувача з достатніми правами.',
- 'config-install-user' => 'Створення користувача бази даних',
- 'config-install-user-alreadyexists' => 'Користувач "$1" уже існує',
- 'config-install-user-create-failed' => 'Не вдалося створити користувача "$1": $2',
- 'config-install-user-grant-failed' => 'Не вдалося надати права користувачеві "$1": $2',
- 'config-install-user-missing' => 'Зазначеного користувача "$1" не існує.',
- 'config-install-user-missing-create' => 'Зазначеного користувача "$1" не існує.
-Будь ласка, поставте галочку "Створити обліковий запис", якщо хочете його створити.',
- 'config-install-tables' => 'Створення таблиць',
- 'config-install-tables-exist' => "'''Увага''': Таблиці MediaWiki уже, здається, існують.
-Пропуск створення.",
- 'config-install-tables-failed' => "'''Помилка''': Не вдалося створити таблицю внаслідок такої помилки: $1",
- 'config-install-interwiki' => 'Заповнення таблиці інтервікі значеннями за замовчуванням',
- 'config-install-interwiki-list' => 'Не вдалося знайти файл <code>interwiki.list</code>.',
- 'config-install-interwiki-exists' => "'''Увага''': Таблиця інтервікі уже, здається, має записи.
-Створення стандартного списку пропускається.",
- 'config-install-stats' => 'Ініціалізація статистики',
- 'config-install-keys' => 'Генерація секретних ключів',
- 'config-insecure-keys' => "'''Увага:''' {{PLURAL:$2|Секретний ключ|Секретні ключі}} ($1), {{PLURAL:$2|згенерований в процесі встановлення, недостатньо надійний|згенеровані в процесі встановлення, недостатньо надійні}}. Розгляньте можливість {{PLURAL:$2|його|їх}} заміни вручку.",
- 'config-install-sysop' => 'Створення облікового запису адміністратора',
- 'config-install-subscribe-fail' => 'Не можливо підписатись на mediawiki-announce: $1',
- 'config-install-subscribe-notpossible' => 'cURL не встановлено і опція allow_url_fopen не доступна.',
- 'config-install-mainpage' => 'Створення головної сторінки із вмістом за замовчуванням',
- 'config-install-extension-tables' => 'Створення таблиць для увімкнених розширень',
- 'config-install-mainpage-failed' => 'Не вдається вставити головну сторінку: $1',
- 'config-install-done' => "'''Вітаємо!'''
-Ви успішно встановили MediaWiki.
-
-Інсталятор згенерував файл <code>LocalSettings.php</code>, який містить усі Ваші налаштування.
-
-Вам необхідно завантажити його і помістити у кореневу папку Вашої вікі (туди ж, де index.php). Завантаження мало початись автоматично.
-
-Якщо завантаження не почалось або Ви його скасували, можете заново його почати, натиснувши на посилання внизу:
-
-$3
-
-'''Примітка''': Якщо Ви не зробите цього зараз, цей файл не буде доступним пізніше, коли Ви вийдете з встановлення, не скачавши його.
-
-Після виконання дій, описаних вище, Ви зможете '''[$2 увійти у свою вікі]'''.",
- 'config-download-localsettings' => 'Завантажити <code>LocalSettings.php</code>',
- 'config-help' => 'допомога',
- 'config-nofile' => 'Файл "$1" не знайдено. Його видалено?',
- 'config-extension-link' => 'Чи знаєте ви, що ваше вікі підтримує [//www.mediawiki.org/wiki/Manual:Extensions розширення]?
-
-Ви можете переглядати [//www.mediawiki.org/wiki/Category:Extensions_by_category розширення по категорії] або в [//www.mediawiki.org/wiki/Extension_Matrix матрицю розширень] щоб побачити повний список розширень.',
- 'mainpagetext' => 'Програмне забезпечення «MediaWiki» успішно встановлене.',
- 'mainpagedocfooter' => 'Інформацію про роботу з цією вікі можна знайти в [//meta.wikimedia.org/wiki/Help:Contents посібнику користувача].
-
-== Деякі корисні ресурси ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Список налаштувань];
-* [//www.mediawiki.org/wiki/Manual:FAQ Часті питання з приводу MediaWiki];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розсилка повідомлень про появу нових версій MediaWiki];
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources Локалізуйте MediaWiki своєю мовою]',
-);
-
-/** Urdu (اردو)
- * @author Noor2020
- * @author පසිඳු කාවින්ද
- */
-$messages['ur'] = array(
- 'config-information' => 'معلومات',
- 'config-git' => 'Git ورژن کنٹرول مصنع لطیف ملا: <code>$1</code> ۔',
- 'config-git-bad' => 'GIT ورژن کنٹرول مصنع لطیف نہيں ملا ۔',
- 'config-mysql-only-myisam-dep' => "' ' تنبیہ: ' '[[MyISAM|مائ اسام]] واحد دستیاب 'ذخیرہ جاتی انجن' ہے جو مائی ایس کیو ایل کے لیے ہے ، جو کہ ناموزوں ہے میڈیا وکی کے لیے ،کیوں کہ :
-* یہ ہموار قطاروں کی سہولت بمشکل فراہم کرتا ہے
-* یہ دوسرے انجنوں کے مقابلے زیادہ بگڑ جاتا ہے
-* میڈیا وکی کوڈ بیس ہمیشہ سنبھال نہيں پاتا مائی اسام کو ۔
-
-آپ کا مائی ایس کیو ایل کا نصب ہمیشہ اننو ڈی بی کی سہولت نہيں دے سکتا ، ہو سکتا ہے یہ مزید ترقیاتی کام چاہے", # Fuzzy
- 'config-profile-fishbowl' => 'صرف مجاز ایڈیٹرز',
- 'config-license-pd' => 'پبلک ڈومین',
- 'config-email-settings' => 'ای میل کی ترتیبات',
- 'config-email-user-help' => 'تمام صارفین ای میل بھیجنے کیلئے ایک دوسرے اگر وہ یہ ان کی ترجیحات میں فعال ہے کی اجازت دیتے ہیں.',
- 'config-email-usertalk' => 'صارف بات صفحہ کی اطلاع فعال',
- 'config-email-usertalk-help' => 'اگر وہ یہ ان کی ترجیحات میں فعال ہے صارف بات صفحہ تبدیلی پر اطلاعات حاصل کرنے کے لئے صارفین کی اجازت دیں.',
- 'config-email-watchlist' => 'دیکھنی والی فہرست کی اطلاع فعال',
- 'config-email-auth' => 'فعال ای میل کی تصدیق',
- 'config-email-sender' => 'ای میل ایڈریس پر واپس:',
- 'config-upload-deleted' => 'ڈائرکٹری خارج کردہ فائلوں کے لیے:',
- 'config-advanced-settings' => 'اعلی درجے کی ترتیب',
- 'config-cache-options' => 'اعتراض کیش کے لئے ترتیب دینا:',
- 'config-extensions' => 'ملانے',
- 'config-install-step-done' => 'کیا کیا',
- 'config-install-step-failed' => 'میں ناکام رہے',
- 'config-install-extensions' => 'سمیت ملانے',
- 'config-install-database' => 'ڈیٹا بیس کی ترتیب',
- 'config-install-pg-commit' => 'تبدیلیوں کے ارتکاب',
- 'config-install-keys' => 'خفیہ چابیاں پیدا',
- 'config-install-sysop' => 'منتظم کے صارف کے اکاؤنٹ کی تشکیل',
- 'config-install-mainpage' => 'پہلے سے طے شدہ مواد کے ساتھ سب سے کامیاب کی تشکیل',
- 'mainpagetext' => "'''میڈیاوکی کو کامیابی سے چالو کردیا گیا ہے۔.'''",
-);
-
-/** Uzbek (oʻzbekcha)
- * @author Sociologist
- */
-$messages['uz'] = array(
- 'config-admin-password-blank' => 'Administrator hisob yozuvi uchun maxfiy soʻz kiriting.',
- 'mainpagetext' => "'''MediaWiki muvaffaqiyatli o'rnatildi.'''",
- 'mainpagedocfooter' => "Wiki dasturini ishlatish haqida ma'lumot olish uchun [//meta.wikimedia.org/wiki/Help:Contents Foydalanuvchi qo'llanmasi] sahifasiga murojaat qiling.
-
-== Dastlabki qadamlar ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Moslamalar ro'yxati]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki haqida ko'p so'raladigan savollar]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki yangi versiyasi chiqqanda xabar berish ro'yxati]", # Fuzzy
-);
-
-/** vèneto (vèneto)
- * @author Vajotwo
- */
-$messages['vec'] = array(
- 'mainpagetext' => "'''Instałasion de MediaWiki conpletà coretamente.'''",
- 'mainpagedocfooter' => "Varda ła [//meta.wikimedia.org/wiki/Aiuto:Sommario Guida utente] par majori informasion so l'uso de sto software wiki.
-
-== Par scumisiar ==
-
-I seguenti cołegamenti i xé en łengua inglese:
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Inpostasion de configurasion]
-* [//www.mediawiki.org/wiki/Manual:FAQ Domande frequenti so MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list anunsi MediaWiki]", # Fuzzy
-);
-
-/** Veps (vepsän kel’)
- * @author Игорь Бродский
- */
-$messages['vep'] = array(
- 'mainpagetext' => "'''MediaWiki-likutim om seižutadud jügedusita.'''",
- 'mainpagedocfooter' => 'Kc. [//meta.wikimedia.org/wiki/Help:Kävutajan abukirj], miše sada informacijad wikin kävutamižes.
-
-== Erased tarbhaižed resursad ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Järgendusiden nimikirjutez]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce počtnimikirjutez]', # Fuzzy
-);
-
-/** Vietnamese (Tiếng Việt)
- * @author පසිඳු කාවින්ද
- */
-$messages['vi'] = array(
- 'config-information' => 'Thông tin',
- 'config-page-language' => 'Ngôn ngữ',
- 'config-page-name' => 'Tên',
- 'config-page-options' => 'Tùy chọn',
- 'config-ns-generic' => 'Dự án',
- 'config-admin-password' => 'Mật khẩu:',
- 'config-admin-email' => 'Địa chỉ thư điện tử:',
- 'config-help' => 'Trợ giúp',
- 'mainpagetext' => "'''MediaWiki đã được cài đặt thành công.'''",
- 'mainpagedocfooter' => 'Xin đọc [//meta.wikimedia.org/wiki/Help:Contents Hướng dẫn sử dụng] để biết thêm thông tin về cách sử dụng phần mềm wiki.
-
-== Để bắt đầu ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Danh sách các thiết lập cấu hình]
-* [//www.mediawiki.org/wiki/Manual:FAQ Các câu hỏi thường gặp MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Danh sách gửi thư về việc phát hành MediaWiki]', # Fuzzy
-);
-
-/** Volapük (Volapük)
- */
-$messages['vo'] = array(
- 'mainpagetext' => "'''El MediaWiki pestiton benosekiko.'''",
- 'mainpagedocfooter' => 'Konsultolös [//meta.wikimedia.org/wiki/Help:Contents Gebanageidian] ad tuvön nünis dö geb programema vükik.
-
-== Nüdugot ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Parametalised]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki: SSP]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Potalised tefü fomams nulik ela MediaWiki]', # Fuzzy
-);
-
-/** Võro (Võro)
- */
-$messages['vro'] = array(
- 'mainpagetext' => "'''MediaWiki tarkvara paika säet.'''",
- 'mainpagedocfooter' => 'Vikitarkvara pruukmisõ kotsilõ loeq mano:
-* [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide MediaWiki pruukmisoppus (inglüse keelen)].
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Säädmiisi oppus (inglüse keelen)]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki kõgõ küsütümbäq küsümiseq (inglüse keelen)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postilist, minka andas teedäq MediaWiki vahtsist kujõst].', # Fuzzy
-);
-
-/** Walloon (walon)
- * @author Srtxg
- */
-$messages['wa'] = array(
- 'mainpagetext' => "'''Li programe MediaWiki a stî astalé a l' idêye.'''",
-);
-
-/** Waray (Winaray)
- * @author Harvzsf
- */
-$messages['war'] = array(
- 'mainpagetext' => "'''Malinamposon an pag-instalar han MediaWiki.'''",
- 'mainpagedocfooter' => "Kitaa an [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para hin impormasyon ha paggamit han wiki nga softweyr.
-
-== Ha pagtikang==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Wolof (Wolof)
- */
-$messages['wo'] = array(
- 'mainpagetext' => "'''Campug MediaWiki gi sotti na . '''",
- 'mainpagedocfooter' => 'Saytul [//meta.wikimedia.org/wiki/Ndimbal:Ndefu Gindikaayu jëfandikukat bi] ngir yeneeni xibaar ci jëfandiku gu tëriin gi.
-
-== Tambali ak MediaWiki ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Limu jumtukaayi kocc-koccal gi]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Limu waxtaan ci liy-génn ci MediaWiki]', # Fuzzy
-);
-
-/** Wu (吴语)
- * @author Wu-chinese.com
- */
-$messages['wuu'] = array(
- 'mainpagetext' => "'''MediaWiki安装成功哉!'''",
- 'mainpagedocfooter' => '请访问[//meta.wikimedia.org/wiki/Help:Contents 用户手册]以获得使用此维基软件个信息!
-
-== 入门 ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置设置列表]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常见问题解答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 发布邮件列表]', # Fuzzy
-);
-
-/** Kalmyk (хальмг)
- * @author Huuchin
- */
-$messages['xal'] = array(
- 'mainpagetext' => "Йовудта Mediawiki гүүлһүдә тәвллһн.'''",
- 'mainpagedocfooter' => 'Тер бики закллһна теткүл ю кеһәд олзлх туск [//meta.wikimedia.org/wiki/Help:Contents көтлвр] дастн.
-
-== Туста заавр ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Көгүдә бүрткл]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki туск ЮмБи]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki шинҗллһнә бүрткл]', # Fuzzy
-);
-
-/** Yiddish (ייִדיש)
- * @author פוילישער
- * @author පසිඳු කාවින්ද
- */
-$messages['yi'] = array(
- 'config-desc' => 'דער אינסטאלירער פאר מעדיעוויקי',
- 'config-title' => 'מעדיעוויקי $1 אינסטאלירונג',
- 'config-information' => 'אינפֿארמאציע',
- 'config-wiki-language' => 'ווקי שפראך:',
- 'config-back' => '→ צוריק',
- 'config-continue' => 'פֿארזעצן ←',
- 'config-page-language' => 'שפראַך',
- 'config-page-name' => 'נאָמען',
- 'config-page-options' => 'ברירות',
- 'config-db-type' => 'דאטנבאזע טיפ:',
- 'config-db-name' => 'דאטנבאזע נאָמען:',
- 'config-project-namespace' => 'פראיעקט נאָמענטייל:',
- 'config-ns-generic' => 'פראיעקט',
- 'config-admin-name' => 'אײַער נאָמען:',
- 'config-admin-password' => 'פאַסווארט:',
- 'config-admin-password-mismatch' => 'די צוויי פאסוועטרט איר האט איינגעגעבן שטימען נישט.',
- 'config-admin-email' => 'בליצפּאָסט אַדרעס:',
- 'config-install-tables' => 'שאפן טאבעלעס',
- 'config-install-tables-exist' => "'''ווארענונג''': זעט אויס אז די מעדיעוויקי טאבעלעס עקזיסטירן שוין.
-איבערהיפן שאפֿן.",
- 'config-download-localsettings' => 'אראפלאדן <code>LocalSettings.php</code>',
- 'config-help' => 'הילף',
- 'config-nofile' => 'מ\'האט נישט געקענט טרעפן די טעקע "$1". צי האט מען זי אויסגעמעקט?',
- 'mainpagetext' => "'''מעדיעוויקי אינסטאלירט מיט דערפאלג.'''",
- 'mainpagedocfooter' => "גיט זיך אן עצה מיט [//meta.wikimedia.org/wiki/Help:Contents באניצער'ס וועגווײַזער] פֿאר אינפֿארמאציע וויאזוי זיך באנוצן מיט וויקי ווייכוואַרג.
-
-== נוצליכע וועבלינקען פֿאַר אנהייבערס ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings רשימה פון קאנפֿיגוראציעס]
-* [//www.mediawiki.org/wiki/Manual:FAQ אפֿט געפֿרעגטע שאלות]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce מעדיעוויקי באפֿרײַאונג פאסטליסטע]* [//www.mediawiki.org/wiki/Localisation#Translation_resources איבערזעצן מעדיעוויקי אין אײַער שפראך]",
-);
-
-/** Yoruba (Yorùbá)
- * @author Demmy
- */
-$messages['yo'] = array(
- 'mainpagetext' => "'''MediaWiki ti jẹ́ gbígbékọ́sínú láyọrísírere.'''",
- 'mainpagedocfooter' => "Ẹ ṣàbẹ̀wò sí [//meta.wikimedia.org/wiki/Help:Contents User's Guide] fún ìfitólétí nípa líló atòlànà wíkì.
-
-== Láti bẹ̀rẹ̀ ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]", # Fuzzy
-);
-
-/** Cantonese (粵語)
- */
-$messages['yue'] = array(
- 'mainpagetext' => "'''MediaWiki已經裝好。'''",
- 'mainpagedocfooter' => '參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶指引](英),裏面有資料講點用wiki軟件。
-
-==開始使用==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings 配置設定清單](英)
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題](英)
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件名單](英)', # Fuzzy
-);
-
-/** Zeeuws (Zeêuws)
- */
-$messages['zea'] = array(
- 'mainpagetext' => "'''De installaotie van MediaWiki is geslaegd.'''",
- 'mainpagedocfooter' => "Raedpleeg de [//meta.wikimedia.org/wiki/ZEA_Ulpe:Inhoudsopgaeve andleidieng] voe informatie over 't gebruuk van de wikisoftware.
-
-== Meer ulpe over MediaWiki ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lieste mie instelliengen]
-* [//www.mediawiki.org/wiki/Manual:FAQ Veehestelde vraehen (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailienglieste voe ankondigiengen van nieuwe versies]", # Fuzzy
-);
-
-/** Simplified Chinese (中文(简体)‎)
- * @author Anthony Fok
- * @author Cwek
- * @author Hydra
- * @author Hzy980512
- * @author Liangent
- * @author Makecat
- * @author PhiLiP
- * @author Xiaomingyan
- * @author Yfdyh000
- * @author 乌拉跨氪
- * @author 阿pp
- * @author 아라
- */
-$messages['zh-hans'] = array(
- 'config-desc' => 'MediaWiki安装程序',
- 'config-title' => 'MediaWiki $1配置',
- 'config-information' => '信息',
- 'config-localsettings-upgrade' => '已检测到<code>LocalSettings.php</code>文件。要升级该配置,请在下面的框中输入<code>$wgUpgradeKey</code>的值。您可以在<code>LocalSettings.php</code>中找到它。',
- 'config-localsettings-cli-upgrade' => '已检测到<code>LocalSettings.php</code>文件。要升级该配置,请直接运行<code>update.php</code>。',
- 'config-localsettings-key' => '升级密钥:',
- 'config-localsettings-badkey' => '您提供的密钥不正确。',
- 'config-upgrade-key-missing' => '检测到MediaWiki的配置已经存在。若要升级该配置,请将下面一行文本添加到<code>LocalSettings.php</code>的底部:
-
-$1',
- 'config-localsettings-incomplete' => '当前的<code>LocalSettings.php</code>可能并不完整,因为变量$1没有设置。请在<code>LocalSettings.php</code>设置该变量,并单击“{{int:Config-continue}}”。',
- 'config-localsettings-connection-error' => '在使用<code>LocalSettings.php</code>或<code>AdminSettings.php</code>中指定的设置连接数据库时发生错误。请修复相应设置并重试。
-
-$1',
- 'config-session-error' => '启动会话出错:$1',
- 'config-session-expired' => '您的会话数据可能已经过期,当前会话的使用期限被设定为$1。您可以在php.ini中设置<code>session.gc_maxlifetime</code>来延长此期限,并重新启动本配置程序。',
- 'config-no-session' => '您的会话数据丢失了!请检查php.ini并确保<code>session.save_path</code>被设置为适当的目录。',
- 'config-your-language' => '您使用的语言:',
- 'config-your-language-help' => '选择在安装过程中使用的语言。',
- 'config-wiki-language' => 'Wiki使用的语言:',
- 'config-wiki-language-help' => '选择将要安装的wiki在多数情况下使用的语言。',
- 'config-back' => '← 后退',
- 'config-continue' => '继续 →',
- 'config-page-language' => '语言',
- 'config-page-welcome' => '欢迎使用MediaWiki!',
- 'config-page-dbconnect' => '连接到数据库',
- 'config-page-upgrade' => '升级当前配置',
- 'config-page-dbsettings' => '数据库设置',
- 'config-page-name' => '名称',
- 'config-page-options' => '选项',
- 'config-page-install' => '安装',
- 'config-page-complete' => '完成!',
- 'config-page-restart' => '重新开始安装',
- 'config-page-readme' => '自述',
- 'config-page-releasenotes' => '发布说明',
- 'config-page-copying' => '复制',
- 'config-page-upgradedoc' => '更新',
- 'config-page-existingwiki' => '已有wiki',
- 'config-help-restart' => '是否要清除所有已输入且保存的数据,并重新启动安装过程吗?',
- 'config-restart' => '是的,重启吧',
- 'config-welcome' => '=== 环境检查 ===
-对当前环境是否适合安装MediaWiki作基本的检查。如果您在安装过程中需要帮助,请提供这些检查的结果。',
- 'config-copyright' => "=== 版权和条款 ===
-
-\$1
-
-本程序为自由软件;您可依据自由软件基金会所发表的GNU通用公共授权条款规定,就本程序再为发布与/或修改;无论您依据的是本授权的第二版或(您自行选择的)任一日后发行的版本。
-
-本程序是基于使用目的而加以发布,然而'''不负任何担保责任''';亦无对'''适售性'''或'''特定目的适用性'''所为的默示性担保。详情请参照GNU通用公共授权。
-
-您应已收到附随于本程序的<doclink href=\"Copying\">GNU通用公共授权的副本</doclink>;如果没有,请写信至自由软件基金会:59 Temple Place - Suite 330, Boston, Ma 02111-1307, USA,或[http://www.gnu.org/copyleft/gpl.html 在线阅读]。",
- 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/zh-hans MediaWiki首页]
-* [//www.mediawiki.org/wiki/Help:Contents/zh-hans 用户指南]
-* [//www.mediawiki.org/wiki/Manual:Contents 管理员指南]
-* [//www.mediawiki.org/wiki/Manual:FAQ/zh-hans 常见问题解答]
-----
-* <doclink href=Readme>自述文件</doclink>
-* <doclink href=ReleaseNotes>发行说明</doclink>
-* <doclink href=Copying>协议副本</doclink>
-* <doclink href=UpgradeDoc>升级</doclink>',
- 'config-env-good' => '环境检查已经完成。您可以安装MediaWiki。',
- 'config-env-bad' => '环境检查已经完成。您不能安装MediaWiki。',
- 'config-env-php' => 'PHP $1已安装。',
- 'config-env-php-toolow' => '已安装PHP $1;但是,MediaWiki需要PHP $2或更高版本。',
- 'config-unicode-using-utf8' => '使用Brion Vibber的utf8_normalize.so实现Unicode正常化。',
- 'config-unicode-using-intl' => '使用[http://pecl.php.net/intl intl PECL扩展]实现Unicode正常化。',
- 'config-unicode-pure-php-warning' => "'''警告:'''因为尚未安装 [http://pecl.php.net/intl intl PECL 扩展]以处理 Unicode 正常化,故只能退而采用运行较慢的纯 PHP 实现的方法。
-如果您运行着一个高流量的站点,请参阅 [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode 正常化]一文。",
- 'config-unicode-update-warning' => "'''警告''':Unicode正常化封装器的已安装版本使用了旧版本的[http://site.icu-project.org/ ICU项目]库。如果您需要使用Unicode,请将其[//www.mediawiki.org/wiki/Unicode_normalization_considerations 升级]。",
- 'config-no-db' => '找不到合适的数据库驱动!您需要为PHP安装数据库驱动。目前支持以下数据库:$1。
-
-如果您正在使用共享主机,请向您的主机提供商申请安装合适的数据库驱动。如果您通过自行编译安装的PHP,请对其进行重新配置以启用数据库客户端,例如使用<code>./configure --with-mysql</code>。如果您通过Debian或Ubuntu包安装的PHP,您还需要安装php5-mysql模块。',
- 'config-outdated-sqlite' => "'''警告''':您已安装SQLite $1,但是它的版本低于最低要求版本$2。因此您无法选择SQLite。",
- 'config-no-fts3' => "'''警告''':已编译的SQLite不包含[//sqlite.org/fts3.html FTS3模块],后台搜索功能将不可用。",
- 'config-register-globals' => "'''警告:PHP的<code>[http://php.net/register_globals register_globals]</code>选项被启用。请尽量禁用该功能,'''虽然不会影响MediaWiki的运行,但您的服务器会被暴露给潜在的安全漏洞。",
- 'config-magic-quotes-runtime' => "'''毁灭性错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]已启用!'''
-此选项会无法预测地破坏输入的数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
- 'config-magic-quotes-sybase' => "'''毁灭性错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase]已启用!'''
-此选项会无法预测地破坏输入的数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
- 'config-mbstring' => "'''毁灭性错误:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]已启用!'''
-此选项会导致错误并不可预测地破坏数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
- 'config-ze1' => "'''毁灭性错误:[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]已启用!'''
-此选项将导致MediaWiki出现极其严重的故障,请将其禁用,否则您将不能安装或使用MediaWiki。",
- 'config-safe-mode' => "'''警告:'''PHP的[http://www.php.net/features.safe-mode 安全模式]已启用。它可能会导致一些问题,尤其在对文件上传和数学公式<code>math</code>的支持方面。",
- 'config-xml-bad' => '缺少PHP的XML模块。MediaWiki需要使用该模块提供的函数,在当前配置下将无法工作。如果您正在使用Mandrake Linux,请安装php-xml包。',
- 'config-pcre' => '可能缺少PCRE的支持模块。MediaWiki的运行需要兼容于Perl的正则表达式函数。',
- 'config-pcre-no-utf8' => "'''毁灭性错误''':PHP的PCRE模块在编译时可能没有包含PCRE_UTF8支持。MediaWiki需要UTF-8支持才能正常工作。",
- 'config-memory-raised' => 'PHP的内存使用上限<code>memory_limit</code>为$1,自动提升到$2。',
- 'config-memory-bad' => "'''警告:'''PHP的内存使用上限<code>memory_limit</code>为$1。该设定可能过低,并导致安装失败!",
- 'config-ctype' => "'''毁灭性错误''':PHP必须有[http://www.php.net/manual/en/ctype.installation.php Ctype 扩展]来支持编译。",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache]已安装',
- 'config-apc' => '[http://www.php.net/apc APC]已安装',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache]已安装',
- 'config-no-cache' => "'''警告:'''找不到[http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache]或[http://www.iis.net/download/WinCacheForPhp WinCache],无法启用对象缓存。
-Object caching is not enabled.",
- 'config-mod-security' => "'''警告''':您的服务器已启动[http://modsecurity.org/ mod_security]。若其配置错误, 会导致MediaWiki和其他软件的错误并允许用户任意发布内容。如果您遇到任何错误,请查阅[http://modsecurity.org/documentation/ mod_security文档]或联系您的客服。",
- 'config-diff3-bad' => '找不到GNU diff3。',
- 'config-git' => '发现Git版本控制软件:<code>$1</code>',
- 'config-git-bad' => 'Git版本控制软件未找到。',
- 'config-imagemagick' => '已找到ImageMagick:<code>$1</code>。如果你启用了上传功能,缩略图功能也将被启用。',
- 'config-gd' => '已找到内建的GD图形库。如果你启用了上传功能,缩略图功能也将被启用。',
- 'config-no-scaling' => '找不到GD库或ImageMagick。缩略图功能将不可用。',
- 'config-no-uri' => "'''错误:'''无法确定当前的URI。安装已中断。",
- 'config-no-cli-uri' => "'''警告''':未指定--scriptpath参数,使用默认值:<code>$1</code>。",
- 'config-using-server' => '使用服务器名“<nowiki>$1</nowiki>”。',
- 'config-using-uri' => '使用服务器URL“<nowiki>$1$2</nowiki>”。',
- 'config-uploads-not-safe' => "'''警告:'''您的默认上传目录<code>$1</code>存在允许执行任意脚本的漏洞。尽管MediaWiki会对所有已上传的文件进行安全检查,但我们仍然强烈建议您在启用上传功能前[//www.mediawiki.org/wiki/Manual:Security#Upload_security 关闭该安全漏洞]。",
- 'config-no-cli-uploads-check' => "'''警告''':在CLI安装过程中,没有对您的默认上传目录(<code>$1</code>)进行执行任意脚本的漏洞检查。",
- 'config-brokenlibxml' => '您的系统安装的PHP和libxml2版本组合存在故障,并可能在MediaWiki和其他web应用程序中造成隐藏的数据损坏。请将PHP升级到5.2.9或以上,libxml2升级到2.7.3或以上([//bugs.php.net/bug.php?id=45996 PHP的故障报告])。安装已中断。',
- 'config-using531' => '由于函数<code>__call()</code>的引用参数存在故障,PHP $1和MediaWiki无法兼容。请升级到PHP 5.3.2或更高版本,或降级到PHP 5.3.0以修复该问题。安装已中断。',
- 'config-suhosin-max-value-length' => 'Suhosin已经安装并将GET请求的参数长度限制在$1字节。MediaWiki的ResourceLoader部件可以在此限制下正常工作,但其性能会被降低。如果可能,请在<code>php.ini</code>中将<code>suhosin.get.max_value_length</code>设为1024或更高值,并在LocalSettings.php中将<code>$wgResourceLoaderMaxQueryLength</code>设为同一值。',
- 'config-db-type' => '数据库类型:',
- 'config-db-host' => '数据库主机:',
- 'config-db-host-help' => '如果您的数据库在别的服务器上,请在这里输入它的域名或IP地址。
-
-如果您在使用共享网站套餐,您的网站商应该已在他们的控制面板中给您数据库信息了。
-
-如果您在Windows中安装并且使用MySQL,“localhost”可能无效。如果确实无效,请输入“127.0.0.1”作为IP地址。
-
-如果您在使用PostgreSQL,并且要用Unix socket来连接,请留空。',
- 'config-db-host-oracle' => '数据库透明网络底层(TNS):',
- 'config-db-host-oracle-help' => '请输入合法的[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 本地连接名],并确保tnsnames.ora文件对本安装程序可见。<br />如果您使用的客户端库为10g或更新的版本,您还可以使用[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 简单连接名方法](easy connect naming method)。',
- 'config-db-wiki-settings' => '标识本wiki',
- 'config-db-name' => '数据库名称:',
- 'config-db-name-help' => '请输入一个可以标识您的wiki的名称。请勿使用空格。
-
-如果您正在使用共享web主机,您的主机提供商或会给您指定一个数据库名称,或会让您通过控制面板创建数据库。',
- 'config-db-name-oracle' => '数据库模式:',
- 'config-db-account-oracle-warn' => '现有三种已支持方案可以将Oracle设置为后端数据库:
-
-如果您希望在安装过程中创建数据库帐户,请为安装程序提供具有SYSDBA角色的数据库帐户,并为web访问帐户指定所需身份证明;否则您可以手动创建web访问的账户并仅须提供该帐户(确保帐户已有创建方案对象(schema object)的所需权限);或提供两个不同的帐户,其一具有创建权限,另一则被限制为web访问。
-
-具有所需权限账户的创建脚本存放于本程序的“maintenance/oracle/”目录下。请注意,使用受限制的帐户将禁用默认帐户的所有维护性功能。',
- 'config-db-install-account' => '用于安装的用户帐号',
- 'config-db-username' => '数据库用户名:',
- 'config-db-password' => '数据库密码:',
- 'config-db-password-empty' => '请为新数据库用户$1输入密码。尽管您可以创建不使用密码的用户,但这样做并不安全。',
- 'config-db-install-username' => '请输入在安装过程中用于连接数据库的用户名。请勿输入MediaWiki帐号的用户名,请输入您数据库的用户名。',
- 'config-db-install-password' => '请输入在安装过程中用于连接数据库的密码。请勿输入MediaWiki帐号的密码,请输入您数据库的密码。',
- 'config-db-install-help' => '请输入在安装过程中用于连接数据库的用户名和密码。',
- 'config-db-account-lock' => '在普通操作中使用相同的用户名和密码',
- 'config-db-wiki-account' => '用于普通操作的用户帐号',
- 'config-db-wiki-help' => '输入在普通的wiki操作中(安装完成后)将用于连接数据库的用户名和密码。如果该帐号并不存在,而安装帐号具有足够的权限,该用户帐号会被自动创建,并被赋予足以运行此wiki的最低权限。',
- 'config-db-prefix' => '数据库表前缀:',
- 'config-db-prefix-help' => '如果您需要在多个wiki之间(或在MediaWiki与其他web应用程序之间)共享一个数据库,您可以通过添加前缀的方式来避免出现表名称的冲突。请勿使用空格。
-
-此字段通常可留空。',
- 'config-db-charset' => '数据库字符集',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 二进制',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 UTF-8(向后兼容)',
- 'config-charset-help' => "'''警告:'''如果您在MySQL 4.1+中使用'''向后兼容的UTF-8'''字符集,并在之后使用<code>mysqldump</code>备份了数据库,则可能损坏所有的非ASCII字符,从而不可逆地破坏您的备份!
-
-在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。
-
-在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[//zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
- 'config-mysql-old' => '需要MySQL $1或更新的版本,您的版本为$2。',
- 'config-db-port' => '数据库端口:',
- 'config-db-schema' => 'MediaWiki的数据库模式',
- 'config-db-schema-help' => '此数据库模式通常是正确的,请在有明确需求时才改动之。',
- 'config-pg-test-error' => "无法连接到数据库'''$1''':$2",
- 'config-sqlite-dir' => 'SQLite数据目录:',
- 'config-sqlite-dir-help' => "SQLite会将所有的数据存储于单一文件中。
-
-您所提供的目录必须在安装过程中对网页服务器可写。
-
-该目录'''不应'''允许通过web访问,因此我们不会将数据文件和PHP文件放在一起。
-
-安装程序在创建数据文件时,亦会在相同目录下创建<code>.htaccess</code>以控制权限。假若此等控制失效,则可能会将您的数据文件暴露于公共空间,让他人可以获取用户数据(电子邮件地址、杂凑后的密码)、被删除的版本以及其他在wiki上被限制访问的数据。
-
-请考虑将数据库统一放置在某处,如<code>/var/lib/mediawiki/yourwiki</code>下。",
- 'config-oracle-def-ts' => '默认表空间:',
- 'config-oracle-temp-ts' => '临时表空间:',
- 'config-support-info' => 'MediaWiki支持以下数据库系统:
-
-$1
-
-如果您在下面列出的数据库系统中没有找到您希望使用的系统,请根据上方链向的指引启用支持。',
- 'config-support-mysql' => '* $1是MediaWiki的首选数据库,对它的支持最为完备([http://www.php.net/manual/en/mysql.installation.php 如何将对MySQL的支持编译进PHP中])',
- 'config-support-postgres' => '* $1是一种流行的开源数据库系统,可作为MySQL的替代([http://www.php.net/manual/en/pgsql.installation.php 如何将对PostgreSQL的支持编译进PHP中])。本程序中可能依然存在一些小而明显的错误,因此并不建议在生产环境中使用该数据库系统。',
- 'config-support-sqlite' => '* $1是一种轻量级的数据库系统,能被良好地支持。([http://www.php.net/manual/en/pdo.installation.php 如何将对SQLite的支持编译进PHP中],须使用PDO)',
- 'config-support-oracle' => '* $1是一种商用企业级的数据库。([http://www.php.net/manual/en/oci8.installation.php 如何将对OCI8的支持编译进PHP中])',
- 'config-header-mysql' => 'MySQL设置',
- 'config-header-postgres' => 'PostgreSQL设置',
- 'config-header-sqlite' => 'SQLite设置',
- 'config-header-oracle' => 'Oracle设置',
- 'config-invalid-db-type' => '无效的数据库类型',
- 'config-missing-db-name' => '您必须为“数据库名称”输入内容',
- 'config-missing-db-host' => '您必须为“数据库主机”输入内容',
- 'config-missing-db-server-oracle' => '您必须为“数据库透明网络底层(TNS)”输入内容',
- 'config-invalid-db-server-oracle' => '无效的数据库TNS“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)、下划线(_)和点号(.)。',
- 'config-invalid-db-name' => '无效的数据库名称“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)、下划线(_)和连字号(-)。',
- 'config-invalid-db-prefix' => '无效的数据库前缀“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)、下划线(_)和连字号(-)。',
- 'config-connection-error' => '$1。
-
-请检查下列的主机、用户名和密码设置后重试。',
- 'config-invalid-schema' => '无效的MediaWiki数据库模式“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)和下划线(_)。',
- 'config-db-sys-create-oracle' => '安装程序仅支持使用SYSDBA帐户创建新帐户。',
- 'config-db-sys-user-exists-oracle' => '用户帐户“$1”已经存在。SYSDBA仅可用于创建新帐户!',
- 'config-postgres-old' => '需要PostgreSQL $1或更新的版本,您的版本为$2。',
- 'config-sqlite-name-help' => '请为您的wiki指定一个用于标识的名称。请勿使用空格或连字号,该名称将被用作SQLite的数据文件名。',
- 'config-sqlite-parent-unwritable-group' => '由于父目录<code><nowiki>$2</nowiki></code>对网页服务器不可写,无法创建数据目录<code><nowiki>$1</nowiki></code>。
-
-安装程序已确定您网页服务器所使用的用户。请将<code><nowiki>$3</nowiki></code>目录设为对该用户可写以继续安装过程。在Unix/Linux系统中,您可以逐行输入下列命令:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => '由于父目录<code><nowiki>$2</nowiki></code>对网页服务器不可写,无法创建数据目录<code><nowiki>$1</nowiki></code>。
-
-安装程序无法确定您网页服务器所使用的用户。请将<code><nowiki>$3</nowiki></code>目录设为全局可写(对所有用户)以继续安装过程。在Unix/Linux系统中,您可以逐行输入下列命令:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => '创建数据目录“$1”时发生错误。请检查路径后重试。',
- 'config-sqlite-dir-unwritable' => '无法写入目录“$1”。请修改该目录的权限,使其对网页服务器可写后重试。',
- 'config-sqlite-connection-error' => '$1。
-
-请检查下列的数据目录和数据库名称后重试。',
- 'config-sqlite-readonly' => '文件<code>$1</code>不可写。',
- 'config-sqlite-cant-create-db' => '无法创建数据文件<code>$1</code>。',
- 'config-sqlite-fts3-downgrade' => 'PHP缺少FTS3支持,正在降级数据表',
- 'config-can-upgrade' => "在数据库中发现了MediaWiki的数据表。要将它们升级至MediaWiki $1,请点击'''继续'''。",
- 'config-upgrade-done' => "升级完成。
-
-现在您可以[$1 开始使用您的wiki]了。
-
-如果您需要重新生成<code>LocalSettings.php</code>文件,请点击下面的按钮。除非您的wiki出现了问题,我们'''不推荐'''您执行此操作。",
- 'config-upgrade-done-no-regenerate' => '升级完成。
-
-现在您可以[$1 开始使用您的wiki]了。',
- 'config-regenerate' => '重新生成LocalSettings.php →',
- 'config-show-table-status' => '<code>SHOW TABLE STATUS</code>语句执行失败!',
- 'config-unknown-collation' => "'''警告:'''数据库使用了无法识别的整理。",
- 'config-db-web-account' => '供网页访问使用的数据库帐号',
- 'config-db-web-help' => '请指定在wiki执行普通操作时,网页服务器用于连接数据库服务器的用户名和密码。',
- 'config-db-web-account-same' => '使用和安装程序相同的帐号',
- 'config-db-web-create' => '如果帐号不存在,则自动创建',
- 'config-db-web-no-create-privs' => '您指定给安装程序的帐号缺少创建帐号的权限,因此您指定的帐号必须已经存在。',
- 'config-mysql-engine' => '存储引擎:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''警告''':您选择了MyISAM作为MySQL的存储引擎,MediaWiki并不推荐您这么做,因为:
-* 它仅能通过表锁定来勉强支持并发
-* 与其他引擎相比,它更容易被损坏
-* MediaWiki代码库并不总会去处理MyISAM
-
-如果您的MySQL程序支持InnoDB,我们高度推荐您使用该引擎替代MyISAM。
-如果您的MySQL程序不支持InnoDB,请考虑升级。",
- 'config-mysql-only-myisam-dep' => "''''警告:'''MyISAM是MySQL唯一可用的存储引擎,但不适合用于MediaWiki,是由于:
-*由于只支持表级锁定,几乎不支持并发。
-*它比其他引擎更容易损坏。
-*MediaWiki代码不能总是按照预设地操作MyISAM。
-
-你的MySQL不支持InnoDB,是时候升级了。",
- 'config-mysql-engine-help' => "'''InnoDB'''通常是最佳选项,因为它对并发操作有着良好的支持。
-
-'''MyISAM'''在单用户或只读环境下可能会有更快的性能表现。但MyISAM数据库出错的概率一般要大于InnoDB数据库。",
- 'config-mysql-charset' => '数据库字符集:',
- 'config-mysql-binary' => '二进制',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。
-
-在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[//zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
- 'config-site-name' => 'Wiki的名称:',
- 'config-site-name-help' => '填入的内容会出现在浏览器的标题栏以及其他多处位置中。',
- 'config-site-name-blank' => '输入网站的名称。',
- 'config-project-namespace' => '项目名字空间:',
- 'config-ns-generic' => '项目',
- 'config-ns-site-name' => '与wiki名称相同:$1',
- 'config-ns-other' => '其他(自定义)',
- 'config-ns-other-default' => '我的Wiki',
- 'config-project-namespace-help' => "依循维基百科形成的惯例,许多wiki将他们的方针页面存放在与内容页面不同的“'''项目名字空间'''”中。所有位于该名字空间下的页面标题都会被冠以固定的前缀,您可以在此处指定这一前缀。传统上,这一前缀应与wiki的命名保持一致,但请勿在其中使用标点符号,如“#”或“:”。",
- 'config-ns-invalid' => '指定的名字空间“<nowiki>$1</nowiki>”无效,请为项目名字空间指定其他名称。',
- 'config-ns-conflict' => '指定的名字空间“<nowiki>$1</nowiki>”与默认的MediaWiki名字空间冲突。请指定一个不同的项目名字空间。',
- 'config-admin-box' => '管理员帐号',
- 'config-admin-name' => '您的名字:',
- 'config-admin-password' => '密码:',
- 'config-admin-password-confirm' => '确认密码:',
- 'config-admin-help' => '在此输入您想使用的用户名,例如“乔帮主”。您将使用该名称登录本wiki。',
- 'config-admin-name-blank' => '输入管理员的用户名。',
- 'config-admin-name-invalid' => '指定的用户名“<nowiki>$1</nowiki>”无效,请指定其他用户名。',
- 'config-admin-password-blank' => '输入管理员帐号的密码。',
- 'config-admin-password-same' => '密码不能和用户名相同。',
- 'config-admin-password-mismatch' => '两次输入的密码并不相同。',
- 'config-admin-email' => '电子邮件地址:',
- 'config-admin-email-help' => '输入电子邮件地址后,您可以收到此wiki上其他用户发来的电子邮件,并能重置您的密码,还可在监视列表中页面被更改时收到邮件通知。您可以将此字段留空。',
- 'config-admin-error-user' => '在创建用户名为“<nowiki>$1</nowiki>”的管理员帐号时发生内部错误。',
- 'config-admin-error-password' => '在为管理员“<nowiki>$1</nowiki>”设置密码时发生内部错误:<pre>$2</pre>',
- 'config-admin-error-bademail' => '您输入了无效的电子邮件地址。',
- 'config-subscribe' => '订阅[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 发行公告邮件列表]。',
- 'config-subscribe-help' => '此低流量的邮件列表仅用于发行公告,其中包括重要安全公告。请订阅该列表以便在新的版本推出时升级您的MediaWiki。',
- 'config-subscribe-noemail' => '您选择了订阅发行公告邮件列表,但没有提供电子邮件地址。请提供一个电子邮件地址以订阅邮件列表。',
- 'config-almost-done' => '您几乎已经完成了!现在您可以跳过剩下的配置流程并立即安装wiki。',
- 'config-optional-continue' => '多问我一些问题吧。',
- 'config-optional-skip' => '我已经不耐烦了,赶紧安装我的wiki。',
- 'config-profile' => '用户权限配置:',
- 'config-profile-wiki' => '开放的wiki',
- 'config-profile-no-anon' => '需要注册帐号',
- 'config-profile-fishbowl' => '编辑受限',
- 'config-profile-private' => '非公开wiki',
- 'config-profile-help' => "如果您允许尽量多的人编写wiki,网站上的内容会更加丰富。在MediaWiki中,您可以轻松地审查最近更改,并轻易回退掉新手或破坏者造成的损害。
-
-然而,许多人觉得让MediaWiki存在多种角色将更加好用;同时,要说服所有人都愿以wiki的方式作贡献并非一件易事。因此,您可以有以下选择:
-
-'''{{int:config-profile-wiki}}'''允许包括未登录用户在内的所有人编辑。'''{{int:config-profile-no-anon}}'''的wiki需要额外的注册流程,这有可能会阻碍随意贡献者。
-
-'''{{int:config-profile-fishbowl}}'''模式只允许获批准的用户编辑,但对公众开放页面浏览(包括历史记录)。'''{{int:config-profile-private}}'''则只允许获批准的用户浏览、编辑页面。
-
-安装完成后,您还可以对用户权限进行更多、更复杂的配置,参见[//www.mediawiki.org/wiki/Manual:User_rights 相关的使用手册]。",
- 'config-license' => '版权和许可证:',
- 'config-license-none' => '页脚无许可证',
- 'config-license-cc-by-sa' => '知识共享署名-相同方式分享',
- 'config-license-cc-by' => '知识共享署名',
- 'config-license-cc-by-nc-sa' => '知识共享署名-非商业性使用-相同方式共享',
- 'config-license-cc-0' => '知识共享Zero(公有领域)',
- 'config-license-gfdl' => 'GNU自由文档许可证1.3或更高版本',
- 'config-license-pd' => '公有领域',
- 'config-license-cc-choose' => '选择自定义的知识共享许可证',
- 'config-license-help' => "许多公共wiki会以[http://freedomdefined.org/Definition 自由许可证]的方式释放出编者的所有贡献。这有助于构建社区的主人翁意识,并能鼓励长期贡献。对于非公共wiki或公司wiki,这并非必要条件。
-
-如果您希望使用来自维基百科的内容,并希望维基百科能接受复制自您的wiki的内容,请选择'''知识共享署名-相同方式共享'''。
-
-GNU自由文档许可证是维基百科曾经使用过的许可证,并迄今有效。然而,该许可证难以理解,并会增加重用内容的难度。",
- 'config-email-settings' => '电子邮件设置',
- 'config-enable-email' => '启用出站电子邮件',
- 'config-enable-email-help' => '如果您希望使用电子邮件功能,请正确配置[http://www.php.net/manual/en/mail.configuration.php PHP的邮件设定]。如果您不需要任何电子邮件功能,请在此处禁用它。',
- 'config-email-user' => '启用用户到用户的电子邮件',
- 'config-email-user-help' => '允许所有用户互发邮件,假若他们启用了该功能。',
- 'config-email-usertalk' => '启用用户讨论页通知',
- 'config-email-usertalk-help' => '允许用户收到用户讨论页被修改的通知,假若他们启用了该功能。',
- 'config-email-watchlist' => '启用监视列表通知',
- 'config-email-watchlist-help' => '允许用户收到与其监视列表有关的通知,假若他们启用了该功能。',
- 'config-email-auth' => '启用电子邮件身份验证',
- 'config-email-auth-help' => "如果启用此选项,在用户设置或修改电子邮件地址时,就会收到一封邮件,内含确认电子地址的链接。只有经过身份验证的电子邮件地址,才能收到来自其他用户的电子邮件,或任何修改通知的邮件。'''建议'''公开wiki启用本选项,以防对电子邮件功能的滥用。",
- 'config-email-sender' => '回复电子邮件地址:',
- 'config-email-sender-help' => '输入要用来发送出站电子邮件的地址,该地址将会收到被拒收的邮件。许多邮件服务器要求域名部分必须有效。',
- 'config-upload-settings' => '图像和文件上传',
- 'config-upload-enable' => '启用文件上传',
- 'config-upload-help' => '文件上传可能会将您的服务器暴露在安全风险下。有关更多的信息,请参阅手册的[//www.mediawiki.org/wiki/Manual:Security 安全部分]。
-
-要启用文件上传,请先将MediaWiki根目录下的<code>images</code>子目录更改为对web服务器可写,然后再启用此选项。',
- 'config-upload-deleted' => '已删除文件的目录:',
- 'config-upload-deleted-help' => '指定用于存放被删除文件的目录。理想情况下,该目录不应能通过web访问。',
- 'config-logo' => '标志URL:',
- 'config-logo-help' => '在MediaWiki的默认外观中,左侧栏菜单之上有一块135x160像素的标志区。请上传一幅相应大小的图像,并在此输入URL。
-
-你可以用<code>$wgStylePath</code>或<code>$wgScriptPath</code>来表示相对于这些位置的路径。
-
-如果您不希望使用标志,请将本处留空。',
- 'config-instantcommons' => '启用即时共享资源',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons 即时共享资源]可以让wiki使用来自[//commons.wikimedia.org/ 维基共享资源]网站的图像、音频和其他媒体文件。要启用该功能,MediaWiki必须能够访问互联网。
-
-有关此功能的详细信息,包括如何将其他wiki网站设为具有类似共享功能的方法,请参考[//mediawiki.org/wiki/Manual:$wgForeignFileRepos 手册]。',
- 'config-cc-error' => '知识共享许可证挑选器无法找到结果,请手动输入许可证的名称。',
- 'config-cc-again' => '重新挑选……',
- 'config-cc-not-chosen' => '选择您希望使用的知识共享许可证,并点击“继续”。',
- 'config-advanced-settings' => '高级设置',
- 'config-cache-options' => '对象缓存设置:',
- 'config-cache-help' => '对象缓存可通过缓存频繁使用的数据来提高MediaWiki的速度。高度推荐中到大型的网站启用该功能,小型网站亦能从其中受益。',
- 'config-cache-none' => '无缓存(不影响功能,但对较大型的wiki网站会有速度影响)',
- 'config-cache-accel' => 'PHP对象缓存(APC、XCache或WinCache)',
- 'config-cache-memcached' => '使用Memcached(需要另外安装并配置)',
- 'config-memcached-servers' => 'Memcached服务器:',
- 'config-memcached-help' => '用于Memcached的IP地址列表。请保持每行一条,并指定要使用的端口。例如:
-127.0.0.1:11211
-192.168.1.25:1234',
- 'config-memcache-needservers' => '您选择了Memcached作为您的缓存,但并未指定任何服务器。',
- 'config-memcache-badip' => '您为Memcached输入了无效的IP地址:$1。',
- 'config-memcache-noport' => '您没有指定Memcached服务器的端口:$1。如果您不清楚端口是多少,默认值为11211。',
- 'config-memcache-badport' => 'Memcached的端口号应该在$1到$2之间。',
- 'config-extensions' => '扩展',
- 'config-extensions-help' => '已在您的<code>./extensions</code>目录中发现下列扩展。
-
-您可能要对它们进行额外的配置,但您现在可以启用它们。',
- 'config-install-alreadydone' => "'''警告:'''您似乎已经安装了MediaWiki,并试图重新安装它。请前往下一个页面。",
- 'config-install-begin' => '点击“{{int:config-continue}}”后,您将开始安装MediaWiki。如果您还想对配置作一些修改,请点击“{{int:config-back}}”。',
- 'config-install-step-done' => '完成',
- 'config-install-step-failed' => '失败',
- 'config-install-extensions' => '正在启用扩展',
- 'config-install-database' => '正在配置数据库',
- 'config-install-schema' => '创建架构',
- 'config-install-pg-schema-not-exist' => 'PostgreSQL 架构不存在',
- 'config-install-pg-schema-failed' => '创建数据表失败。请确保用户“$1”拥有写入模式“$2”的权限。',
- 'config-install-pg-commit' => '正在提交更改',
- 'config-install-pg-plpgsql' => '正在检查PL/pgSQL语言',
- 'config-pg-no-plpgsql' => '您需要为数据库$1安装PL/pgSQL语言',
- 'config-pg-no-create-privs' => '为安装程序指定的帐号缺少创建帐号的权限。',
- 'config-pg-not-in-role' => '您指定为web用户的帐户已经存在。
-您给本程序指定的帐户不是超级用户,也不是web用户角色的成员,所以它不能创建web用户所拥有的对象。
-
-MediaWiki当前需要使用由web用户所有的表。请指定另一个web帐户名称,或点击“后退”并指定具有适当权限的安装用户。',
- 'config-install-user' => '正在创建数据库用户',
- 'config-install-user-alreadyexists' => '用户“$1”已存在',
- 'config-install-user-create-failed' => '创建用户“$1”失败:$2',
- 'config-install-user-grant-failed' => '授予用户“$1”权限失败:$2',
- 'config-install-user-missing' => '指定的用户“$1”不存在。',
- 'config-install-user-missing-create' => '指定的用户“$1”不存在。如果您想要创建一名,请点选“创建帐户”下面的复选框。',
- 'config-install-tables' => '正在创建数据表',
- 'config-install-tables-exist' => "'''警告''':MediaWiki的数据表似乎已经存在,跳过创建。",
- 'config-install-tables-failed' => "'''错误''':创建数据表出错,下为错误信息:$1",
- 'config-install-interwiki' => '正在填充默认的跨wiki数据表',
- 'config-install-interwiki-list' => '找不到文件<code>interwiki.list</code>。',
- 'config-install-interwiki-exists' => "'''警告''':跨wiki数据表似乎已有内容,跳过默认列表。",
- 'config-install-stats' => '初始化统计',
- 'config-install-keys' => '生成密钥中',
- 'config-insecure-keys' => "'''警告''':在安装过程中生成的{{PLURAL:$2|安全密钥|安全密钥}}($1){{PLURAL:$2|并|并}}不一定安全。请考虑手动更改{{PLURAL:$2|它|它们}}。",
- 'config-install-sysop' => '正在创建管理员用户帐号',
- 'config-install-subscribe-fail' => '无法订阅mediawiki-announce:$1',
- 'config-install-subscribe-notpossible' => '没有安装cURL,allow_url_fopen也不可用。',
- 'config-install-mainpage' => '正在创建显示默认内容的首页',
- 'config-install-extension-tables' => '正在为已启用扩展创建数据表',
- 'config-install-mainpage-failed' => '无法插入首页:$1',
- 'config-install-done' => "'''恭喜!'''
-您已经成功地安装了MediaWiki。
-
-安装程序已经生成了<code>LocalSettings.php</code>文件,其中包含了您所有的配置。
-
-您需要下载该文件,并将其放在您wiki的根目录(index.php的同级目录)中。稍后下载将自动开始。
-
-如果浏览器没有提示您下载,或者您取消了下载,您可以点击下面的链接重新开始下载:
-
-$3
-
-'''注意''':如果您现在不完成本步骤,而是没有下载便退出了安装过程,此后您将无法获得自动生成的配置文件。
-
-当本步骤完成后,您可以 '''[$2 进入您的wiki]'''。",
- 'config-download-localsettings' => '下载<code>LocalSettings.php</code>',
- 'config-help' => '帮助',
- 'config-nofile' => '找不到文件“$1”。它是否已被删除?',
- 'mainpagetext' => "'''已成功安装MediaWiki。'''",
- 'mainpagedocfooter' => '请查阅[//meta.wikimedia.org/wiki/Help:Contents 用户指南]以获取使用本wiki软件的信息!
-
-== 入门 ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki配置设置列表]
-* [//www.mediawiki.org/wiki/Manual:FAQ/zh-hans MediaWiki常见问题]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki发布邮件列表]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources 本地化MediaWiki到您的语言]',
-);
-
-/** Traditional Chinese (中文(繁體)‎)
- * @author Anthony Fok
- * @author Hzy980512
- * @author Justincheng12345
- * @author Liangent
- * @author Mark85296341
- * @author Simon Shek
- * @author 아라
- */
-$messages['zh-hant'] = array(
- 'config-desc' => 'MediaWiki安裝程序',
- 'config-title' => 'MediaWiki $1配置',
- 'config-information' => '資訊',
- 'config-localsettings-upgrade' => '已檢測到<code>LocalSettings.php</code>文件。要升級該配置,請在下面的框中輸入<code>$wgUpgradeKey</code>的值。您可以在<code>LocalSettings.php</code>中找到它。',
- 'config-localsettings-cli-upgrade' => '已檢測到<code>LocalSettings.php</code>文件。要升級該配置,請直接執行<code>update.php</code>。',
- 'config-localsettings-key' => '升級密鑰:',
- 'config-localsettings-badkey' => '您提供的密鑰不正確。',
- 'config-upgrade-key-missing' => '檢測到MediaWiki的配置已經存在。若要升級該配置,請將下面一行文本添加到<code>LocalSettings.php</code>的底部:
-
-$1',
- 'config-localsettings-incomplete' => '當前的<code>LocalSettings.php</code>可能並不完整,因為變量$1沒有設置。請在<code>LocalSettings.php</code>設置該變量,並單擊“{{int:Config-continue}}”。',
- 'config-localsettings-connection-error' => '在使用<code>LocalSettings.php</code>或<code>AdminSettings.php</code>中指定的設置連接數據庫時發生錯誤。請修復相應設置並重試。
-
-$1',
- 'config-session-error' => '啟動會話出錯:$1',
- 'config-session-expired' => '您的會話數據可能已經過期,當前會話的使用期限被設定為$1。您可以在php.ini中設置<code>session.gc_maxlifetime</code>來延長此期限,並重新啟動本配置程序。',
- 'config-no-session' => '您的會話數據丟失了!請檢查php.ini並確保<code>session.save_path</code>被設置為適當的目錄。',
- 'config-your-language' => '您的語言:',
- 'config-your-language-help' => '選擇一個要使用的語言在安裝過程中。',
- 'config-wiki-language' => 'Wiki 語言:',
- 'config-wiki-language-help' => '選擇將要安裝的wiki在多數情況下使用的語言。',
- 'config-back' => '←返回',
- 'config-continue' => '繼續→',
- 'config-page-language' => '語言',
- 'config-page-welcome' => '歡迎您來到 MediaWiki!',
- 'config-page-dbconnect' => '連接到資料庫',
- 'config-page-upgrade' => '升級現有的安裝',
- 'config-page-dbsettings' => '資料庫設定',
- 'config-page-name' => '名稱',
- 'config-page-options' => '選項',
- 'config-page-install' => '安裝',
- 'config-page-complete' => '完成!',
- 'config-page-restart' => '重新安裝',
- 'config-page-readme' => '讀我',
- 'config-page-releasenotes' => '發布說明',
- 'config-page-copying' => '複製',
- 'config-page-upgradedoc' => '升級',
- 'config-page-existingwiki' => '已有wiki',
- 'config-help-restart' => '是否要清除所有已輸入且保存的數據,並重新啟動安裝過程嗎?',
- 'config-restart' => '是的,重新啟動',
- 'config-welcome' => '=== 環境檢查 ===
-對當前環境是否適合安裝MediaWiki作基本的檢查。如果您在安裝過程中需要幫助,請提供這些檢查的結果。',
- 'config-copyright' => "=== 版權和條款 ===
-
-\$1
-
-本程序為自由軟件;您可依據自由軟件基金會所發表的GNU通用公共授權條款規定,就本程序再為發布與/或修改;無論您依據的是本授權的第二版或(您自行選擇的)任一日後發行的版本。
-
-本程序是基於使用目的而加以發布,然而'''不負任何擔保責任''';亦無對'''適售性'''或'''特定目的適用性'''所為的默示性擔保。詳情請參照GNU通用公共授權。
-
-您應已收到附隨於本程序的<doclink href=\"Copying\">GNU通用公共授權的副本</doclink>;如果沒有,請寫信至自由軟件基金會:59 Temple Place - Suite 330, Boston, Ma 02111-1307, USA,或[http://www.gnu.org/copyleft/gpl.html 在線閱讀]。",
- 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/zh-hans MediaWiki首頁]
-* [//www.mediawiki.org/wiki/Help:Contents/zh-hans 用戶指南]
-* [//www.mediawiki.org/wiki/Manual:Contents 管理員指南]
-* [//www.mediawiki.org/wiki/Manual:FAQ/zh-hans 常見問題解答]
-----
-* <doclink href=Readme>自述文件</doclink>
-* <doclink href=ReleaseNotes>發行說明</doclink>
-* <doclink href=Copying>協議副本</doclink>
-* <doclink href=UpgradeDoc>升級</doclink>',
- 'config-env-good' => '環境檢查已經完成。您可以安裝MediaWiki。',
- 'config-env-bad' => '環境檢查已經完成。您不能安裝MediaWiki。',
- 'config-env-php' => 'PHP $1 已安裝。',
- 'config-env-php-toolow' => '已安裝 PHP $1;但是,MediaWiki 需要 PHP $2 或更高版本。',
- 'config-unicode-using-utf8' => '將使用 Brion Vibber 的 utf8_normalize.so 以實作 Unicode 正規化。',
- 'config-unicode-using-intl' => '將使用 [http://pecl.php.net/intl intl PECL 延伸函式庫]以實作 Unicode 正規化。',
- 'config-unicode-pure-php-warning' => "'''警告:'''因為尚未安裝 [http://pecl.php.net/intl intl PECL 延伸函式庫]以處理 Unicode 正規化,故只能退而採用較慢的純 PHP 實作。如果您運行着一個高流量的網站,請參閱 [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode 正規化]一文。",
- 'config-unicode-update-warning' => "'''警告''':Unicode正常化封裝器的已安裝版本使用了舊版本的[http://site.icu-project.org/ ICU項目]庫。如果您需要使用Unicode,請將其[//www.mediawiki.org/wiki/Unicode_normalization_considerations 升級]。",
- 'config-no-db' => '找不到合適的數據庫驅動!您需要為PHP安裝數據庫驅動。目前支持以下數據庫:$1。
-
-如果您正在使用共享主機,請向您的主機提供商申請安裝合適的數據庫驅動。如果您通過自行編譯安裝的PHP,請對其進行重新配置以啟用數據庫客戶端,例如使用<code>./configure --with-mysql</code>。如果您通過Debian或Ubuntu包安裝的PHP,您還需要安裝php5-mysql模塊。',
- 'config-outdated-sqlite' => "'''警告''':您已安裝SQLite $1,但是它的版本低於最低要求版本$2。因此您無法選擇SQLite。",
- 'config-no-fts3' => "'''警告''':已編譯的SQLite不包含[//sqlite.org/fts3.html FTS3模塊],後台搜索功能將不可用。",
- 'config-register-globals' => "'''警告:PHP的<code>[http://php.net/register_globals register_globals]</code>選項被啟用。請盡量禁用該功能,'''雖然不會影響MediaWiki的運行,但您的服務器會被暴露給潛在的安全漏洞。",
- 'config-magic-quotes-runtime' => "'''致命錯誤:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]被啟用!'''
-此選項會無法預測地破壞輸入的數據,請將其禁用,否則您將不能安裝或使用MediaWiki。",
- 'config-magic-quotes-sybase' => "'''致命錯誤:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase]被啟用!'''
-此選項會無法預測地破壞輸入的數據,請將其禁用,否則您將不能安裝或使用MediaWiki。",
- 'config-mbstring' => "'''致命錯誤:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]被啟用!'''
-此選項會導致錯誤並不可預測地破壞數據,請將其禁用,否則您將不能安裝或使用MediaWiki。",
- 'config-ze1' => "'''致命錯誤:[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]被啟用!'''
-此選項將導致MediaWiki出現極其嚴重的故障,請將其禁用,否則您將不能安裝或使用MediaWiki。",
- 'config-safe-mode' => "'''警告:'''PHP的[http://www.php.net/features.safe-mode 安全模式]已啟用。它可能會導致一些問題,尤其在對文件上傳和數學公式<code>math</code>的支持方面。",
- 'config-xml-bad' => '缺少PHP的XML模塊。MediaWiki需要使用該模塊提供的函數,在當前配置下將無法工作。如果您正在使用Mandrake Linux,請安裝php-xml包。',
- 'config-pcre' => '可能缺少PCRE的支持模塊。MediaWiki的運行需要兼容於Perl的正則表達式函數。',
- 'config-pcre-no-utf8' => "'''致命錯誤''':PHP的PCRE模塊在編譯時可能沒有包含PCRE_UTF8支持。MediaWiki需要UTF-8支持才能正常工作。",
- 'config-memory-raised' => 'PHP的內存使用上限<code>memory_limit</code>為$1,自動提升到$2。',
- 'config-memory-bad' => "'''警告:'''PHP的內存使用上限<code>memory_limit</code>為$1。該設定可能過低,並導致安裝失敗!",
- 'config-ctype' => "'''毀滅性錯誤''':PHP必須有[http://www.php.net/manual/en/ctype.installation.php Ctype 擴展]來支持編譯。",
- 'config-xcache' => '[http://xcache.lighttpd.net/ XCache]已安裝',
- 'config-apc' => '[http://www.php.net/apc APC]已安裝',
- 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache]已安裝',
- 'config-no-cache' => "'''警告:'''找不到[http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache]或[http://www.iis.net/download/WinCacheForPhp WinCache],無法啟用對象緩存。
-Object caching is not enabled.",
- 'config-mod-security' => "'''警告''':您的服務器已啟動[http://modsecurity.org/ mod_security]。若其配置錯誤, 會導致MediaWiki和其他軟件的錯誤並允許用戶任意發布內容。如果您遇到任何錯誤,請查閱[http://modsecurity.org/documentation/ mod_security文檔]或聯繫您的客服。",
- 'config-diff3-bad' => '找不到GNU diff3。',
- 'config-git' => '發現Git版本控制軟件:<code>$1</code>。',
- 'config-git-bad' => '無法找到Git版本控制軟件。',
- 'config-imagemagick' => '已找到ImageMagick:<code>$1</code>。如果你啟用了上傳功能,縮略圖功能也將被啟用。',
- 'config-gd' => '已找到內建的GD圖形庫。如果你啟用了上傳功能,縮略圖功能也將被啟用。',
- 'config-no-scaling' => '找不到GD庫或ImageMagick。縮略圖功能將不可用。',
- 'config-no-uri' => "'''錯誤:'''無法確定當前的URI。安裝已中斷。",
- 'config-no-cli-uri' => "'''警告''':未指定--scriptpath參數,使用默認值:<code>$1</code>。",
- 'config-using-server' => '使用服務器名“<nowiki>$1</nowiki>”。',
- 'config-using-uri' => '使用服務器URL“<nowiki>$1$2</nowiki>”。',
- 'config-uploads-not-safe' => "'''警告:'''您的默認上傳目錄<code>$1</code>存在允許執行任意腳本的漏洞。儘管MediaWiki會對所有已上傳的文件進行安全檢查,但我們仍然強烈建議您在啟用上傳功能前[//www.mediawiki.org/wiki/Manual:Security#Upload_security 關閉該安全漏洞]。",
- 'config-no-cli-uploads-check' => "'''警告''':在CLI安裝過程中,沒有對您的默認上傳目錄(<code>$1</code>)進行執行任意腳本的漏洞檢查。",
- 'config-brokenlibxml' => '您的系統安裝的PHP和libxml2版本組合存在故障,並可能在MediaWiki和其他web應用程序中造成隱藏的數據損壞。請將PHP升級到5.2.9或以上,libxml2升級到2.7.3或以上([//bugs.php.net/bug.php?id=45996 PHP的故障報告])。安裝已中斷。',
- 'config-using531' => '由於函數<code>__call()</code>的引用參數存在故障,PHP $1和MediaWiki無法兼容。請升級到PHP 5.3.2或更高版本,或降級到PHP 5.3.0以修復該問題。安裝已中斷。',
- 'config-suhosin-max-value-length' => 'Suhosin已經安裝並將GET請求的參數長度限制在$1字節。MediaWiki的ResourceLoader部件可以在此限制下正常工作,但其性能會被降低。如果可能,請在<code>php.ini</code>中將<code>suhosin.get.max_value_length</code>設為1024或更高值,並在LocalSettings.php中將<code>$wgResourceLoaderMaxQueryLength</code>設為同一值。', # Fuzzy
- 'config-db-type' => '資料庫類型:',
- 'config-db-host' => '資料庫主機:',
- 'config-db-host-help' => '如果您的數據庫在別的服務器上,請在這裡輸入它的域名或IP地址。
-
-如果您在使用共享網站套餐,您的網站商應該已在他們的控制面板中給您數據庫信息了。
-
-如果您在Windows中安裝並且使用MySQL,“localhost”可能無效。如果確實無效,請輸入“127.0.0.1”作為IP地址。
-
-如果您在使用PostgreSQL,並且要用Unix socket來連接,請留空。',
- 'config-db-host-oracle' => '資料庫的 TNS:',
- 'config-db-host-oracle-help' => '請輸入合法的[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 本地連接名],並確保tnsnames.ora文件對本安裝程序可見。<br />如果您使用的客戶端庫為10g或更新的版本,您還可以使用[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 簡單連接名方法](easy connect naming method)。',
- 'config-db-wiki-settings' => '識別這個 Wiki',
- 'config-db-name' => '資料庫名稱:',
- 'config-db-name-help' => '請輸入一個可以標識您的wiki的名稱。請勿使用空格。
-
-如果您正在使用共享web主機,您的主機提供商或會給您指定一個數據庫名稱,或會讓您通過控制面板創建數據庫。',
- 'config-db-name-oracle' => '資料庫架構:',
- 'config-db-account-oracle-warn' => '現有三種已支持方案可以將Oracle設置為後端數據庫:
-
-如果您希望在安裝過程中創建數據庫帳戶,請為安裝程序提供具有SYSDBA角色的數據庫帳戶,並為web訪問帳戶指定所需身份證明;否則您可以手動創建web訪問的賬戶並僅須提供該帳戶(確保帳戶已有創建方案對象(schema object)的所需權限);或提供兩個不同的帳戶,其一具有創建權限,另一則被限制為web訪問。
-
-具有所需權限賬戶的創建腳本存放於本程序的“maintenance/oracle/”目錄下。請注意,使用受限制的帳戶將禁用默認帳戶的所有維護性功能。',
- 'config-db-install-account' => '用於安裝的用戶帳號',
- 'config-db-username' => '資料庫使用者名稱:',
- 'config-db-password' => '資料庫密碼:',
- 'config-db-password-empty' => '請為新數據庫用戶$1輸入密碼。儘管您可以創建不使用密碼的用戶,但這樣做並不安全。',
- 'config-db-install-username' => '請輸入在安裝過程中用於連接數據庫的用戶名。請勿輸入MediaWiki帳號的用戶名,請輸入您數據庫的用戶名。',
- 'config-db-install-password' => '請輸入在安裝過程中用於連接數據庫的密碼。請勿輸入MediaWiki帳號的密碼,請輸入您數據庫的密碼。',
- 'config-db-install-help' => '請輸入在安裝過程中用於連接數據庫的用戶名和密碼。',
- 'config-db-account-lock' => '在普通操作中使用相同的用戶名和密碼',
- 'config-db-wiki-account' => '用於普通操作的用戶帳號',
- 'config-db-wiki-help' => '輸入在普通的wiki操作中(安裝完成後)將用於連接數據庫的用戶名和密碼。如果該帳號並不存在,而安裝帳號具有足夠的權限,該用戶帳號會被自動創建,並被賦予足以運行此wiki的最低權限。',
- 'config-db-prefix' => '數據庫表前綴:',
- 'config-db-prefix-help' => '如果您需要在多個wiki之間(或在MediaWiki與其他web應用程序之間)共享一個數據庫,您可以通過添加前綴的方式來避免出現表名稱的衝突。請勿使用空格。
-
-此字段通常可留空。',
- 'config-db-charset' => '數據庫字符集',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 二進制',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 UTF-8(向後兼容)',
- 'config-charset-help' => "'''警告:'''如果您在MySQL 4.1+中使用'''向後兼容的UTF-8'''字符集,並在之後使用<code>mysqldump</code>備份了數據庫,則可能損壞所有的非ASCII字符,從而不可逆地破壞您的備份!
-
-在'''二進制模式'''下,MediaWiki會將UTF-8編碼的文本存於數據庫的二進制字段中。相對於MySQL的UTF-8模式,這種方法效率更高,並允許您使用全範圍的Unicode字符。
-
-在'''UTF-8模式'''下,MySQL將知道您數據使用的字符集,並能適當地提供和轉換內容。但這樣做您將無法在數據庫中存儲[//zh.wikipedia.org/wiki/基本多文種平面 基本多文種平面]以外的字符。",
- 'config-mysql-old' => '需要MySQL $1或更新的版本,您的版本為$2。',
- 'config-db-port' => '數據庫端口:',
- 'config-db-schema' => 'MediaWiki的數據庫模式',
- 'config-db-schema-help' => '此數據庫模式通常是正確的,請在有明確需求時才改動之。',
- 'config-pg-test-error' => "無法連接到數據庫'''$1''':$2",
- 'config-sqlite-dir' => 'SQLite 的資料目錄:',
- 'config-sqlite-dir-help' => "SQLite會將所有的數據存儲於單一文件中。
-
-您所提供的目錄必須在安裝過程中對網頁服務器可寫。
-
-該目錄'''不應'''允許通過web訪問,因此我們不會將數據文件和PHP文件放在一起。
-
-安裝程序在創建數據文件時,亦會在相同目錄下創建<code>.htaccess</code>以控制權限。假若此等控制失效,則可能會將您的數據文件暴露於公共空間,讓他人可以獲取用戶數據(電子郵件地址、雜湊後的密碼)、被刪除的版本以及其他在wiki上被限制訪問的數據。
-
-請考慮將數據庫統一放置在某處,如<code>/var/lib/mediawiki/yourwiki</code>下。",
- 'config-oracle-def-ts' => '默認表空間:',
- 'config-oracle-temp-ts' => '臨時表空間:',
- 'config-support-info' => 'MediaWiki支持以下數據庫系統:
-
-$1
-
-如果您在下面列出的數據庫系統中沒有找到您希望使用的系統,請根據上方鏈向的指引啟用支持。',
- 'config-support-mysql' => '* $1是MediaWiki的首選數據庫,對它的支持最為完備([http://www.php.net/manual/en/mysql.installation.php 如何將對MySQL的支持編譯進PHP中])',
- 'config-support-postgres' => '* $1是一種流行的開源數據庫系統,可作為MySQL的替代([http://www.php.net/manual/en/pgsql.installation.php 如何將對PostgreSQL的支持編譯進PHP中])。本程序中可能依然存在一些小而明顯的錯誤,因此並不建議在生產環境中使用該數據庫系統。',
- 'config-support-sqlite' => '* $1是一種輕量級的數據庫系統,能被良好地支持。([http://www.php.net/manual/en/pdo.installation.php 如何將對SQLite的支持編譯進PHP中],須使用PDO)',
- 'config-support-oracle' => '* $1是一種商用企業級的數據庫。([http://www.php.net/manual/en/oci8.installation.php 如何將對OCI8的支持編譯進PHP中])',
- 'config-header-mysql' => 'MySQL 的設定',
- 'config-header-postgres' => 'PostgreSQL設置',
- 'config-header-sqlite' => 'SQLite 的設定',
- 'config-header-oracle' => '甲骨文設定',
- 'config-invalid-db-type' => '無效的資料庫類型',
- 'config-missing-db-name' => '您必須為“數據庫名稱”輸入內容',
- 'config-missing-db-host' => '您必須為“數據庫主機”輸入內容',
- 'config-missing-db-server-oracle' => '您必須為“數據庫透明網絡底層(TNS)”輸入內容',
- 'config-invalid-db-server-oracle' => '無效的數據庫TNS「$1」。
-請只使用「TNS Name」或「Easy Connect」 字串([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle命名法])',
- 'config-invalid-db-name' => '無效的數據庫名稱“$1”。請只使用ASCII字母(a-z、A-Z)、數字(0-9)、下劃線(_)和連字號(-)。',
- 'config-invalid-db-prefix' => '無效的數據庫前綴“$1”。請只使用ASCII字母(a-z、A-Z)、數字(0-9)、下劃線(_)和連字號(-)。',
- 'config-connection-error' => '$1。
-
-請檢查下列的主機、用戶名和密碼設置後重試。',
- 'config-invalid-schema' => '無效的MediaWiki數據庫模式“$1”。請只使用ASCII字母(a-z、A-Z)、數字(0-9)和下劃線(_)。',
- 'config-db-sys-create-oracle' => '安裝程序僅支持使用SYSDBA帳戶創建新帳戶。',
- 'config-db-sys-user-exists-oracle' => '用戶帳戶“$1”已經存在。SYSDBA僅可用於創建新帳戶!',
- 'config-postgres-old' => '需要PostgreSQL $1或更新的版本,您的版本為$2。',
- 'config-sqlite-name-help' => '請為您的wiki指定一個用於標識的名稱。請勿使用空格或連字號,該名稱將被用作SQLite的數據文件名。',
- 'config-sqlite-parent-unwritable-group' => '由於父目錄<code><nowiki>$2</nowiki></code>對網頁服務器不可寫,無法創建數據目錄<code><nowiki>$1</nowiki></code>。
-
-安裝程序已確定您網頁服務器所使用的用戶。請將<code><nowiki>$3</nowiki></code>目錄設為對該用戶可寫以繼續安裝過程。在Unix/Linux系統中,您可以逐行輸入下列命令:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => '由於父目錄<code><nowiki>$2</nowiki></code>對網頁服務器不可寫,無法創建數據目錄<code><nowiki>$1</nowiki></code>。
-
-安裝程序無法確定您網頁服務器所使用的用戶。請將<code><nowiki>$3</nowiki></code>目錄設為全局可寫(對所有用戶)以繼續安裝過程。在Unix/Linux系統中,您可以逐行輸入下列命令:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => '創建數據目錄“$1”時發生錯誤。請檢查路徑後重試。',
- 'config-sqlite-dir-unwritable' => '無法寫入目錄“$1”。請修改該目錄的權限,使其對網頁服務器可寫後重試。',
- 'config-sqlite-connection-error' => '$1。
-
-請檢查下列的數據目錄和數據庫名稱後重試。',
- 'config-sqlite-readonly' => '文件<code>$1</code>不可寫。',
- 'config-sqlite-cant-create-db' => '無法創建數據文件<code>$1</code>。',
- 'config-sqlite-fts3-downgrade' => 'PHP缺少FTS3支持,正在降級數據表',
- 'config-can-upgrade' => "在數據庫中發現了MediaWiki的數據表。要將它們升級至MediaWiki $1,請點擊'''繼續'''。",
- 'config-upgrade-done' => "升級完成。
-
-現在您可以[$1 開始使用您的wiki]了。
-
-如果您需要重新生成<code>LocalSettings.php</code>文件,請點擊下面的按鈕。除非您的wiki出現了問題,我們'''不推薦'''您執行此操作。",
- 'config-upgrade-done-no-regenerate' => '升級完成。
-
-現在您可以[$1 開始使用您的wiki]了。',
- 'config-regenerate' => '重新生成LocalSettings.php →',
- 'config-show-table-status' => '查詢<code>SHOW TABLE STATUS</code>失敗!',
- 'config-unknown-collation' => "'''警告:'''數據庫使用了無法識別的整理。",
- 'config-db-web-account' => '供網頁訪問使用的數據庫帳號',
- 'config-db-web-help' => '請指定在wiki執行普通操作時,網頁服務器用於連接數據庫服務器的用戶名和密碼。',
- 'config-db-web-account-same' => '使用和安裝程序相同的帳號',
- 'config-db-web-create' => '建立帳號,如果它不存在',
- 'config-db-web-no-create-privs' => '您指定給安裝程序的帳號缺少創建帳號的權限,因此您指定的帳號必須已經存在。',
- 'config-mysql-engine' => '存儲引擎:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''警告''':您選擇了MyISAM作為MySQL的存儲引擎,MediaWiki並不推薦您這麼做,因為:
-* 它僅能通過表鎖定來勉強支持並發
-* 與其他引擎相比,它更容易被損壞
-* MediaWiki代碼庫並不總會去處理MyISAM
-
-如果您的MySQL程序支持InnoDB,我們高度推薦您使用該引擎替代MyISAM。
-如果您的MySQL程序不支持InnoDB,請考慮升級。",
- 'config-mysql-engine-help' => "'''InnoDB'''通常是最佳選項,因為它對並發操作有着良好的支持。
-
-'''MyISAM'''在單用戶或只讀環境下可能會有更快的性能表現。但MyISAM數據庫出錯的概率一般要大於InnoDB數據庫。",
- 'config-mysql-charset' => '資料庫字符集:',
- 'config-mysql-binary' => '二進制',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "在'''二進制模式'''下,MediaWiki會將UTF-8編碼的文本存於數據庫的二進制字段中。相對於MySQL的UTF-8模式,這種方法效率更高,並允許您使用全範圍的Unicode字符。
-
-在'''UTF-8模式'''下,MySQL將知道您數據使用的字符集,並能適當地提供和轉換內容。但這樣做您將無法在數據庫中存儲[//zh.wikipedia.org/wiki/基本多文種平面 基本多文種平面]以外的字符。",
- 'config-site-name' => 'Wiki的名稱:',
- 'config-site-name-help' => '填入的內容會出現在瀏覽器的標題欄以及其他多處位置中。',
- 'config-site-name-blank' => '輸入站點名稱。',
- 'config-project-namespace' => '項目名字空間:',
- 'config-ns-generic' => '項目',
- 'config-ns-site-name' => '與wiki名稱相同:$1',
- 'config-ns-other' => '其他(請註明)',
- 'config-ns-other-default' => '我的Wiki',
- 'config-project-namespace-help' => "依循維基百科形成的慣例,許多wiki將他們的方針頁面存放在與內容頁面不同的“'''項目名字空間'''”中。所有位於該名字空間下的頁面標題都會被冠以固定的前綴,您可以在此處指定這一前綴。傳統上,這一前綴應與wiki的命名保持一致,但請勿在其中使用標點符號,如“#”或“:”。",
- 'config-ns-invalid' => '指定的名字空間“<nowiki>$1</nowiki>”無效,請為項目名字空間指定其他名稱。',
- 'config-ns-conflict' => '指定的名字空間“<nowiki>$1</nowiki>”與默認的MediaWiki名字空間衝突。請指定一個不同的項目名字空間。',
- 'config-admin-box' => '管理員帳號',
- 'config-admin-name' => '您的名字:',
- 'config-admin-password' => '密碼:',
- 'config-admin-password-confirm' => '再次輸入密碼:',
- 'config-admin-help' => '在此輸入您想使用的用戶名,例如“喬幫主”。您將使用該名稱登錄本wiki。',
- 'config-admin-name-blank' => '輸入管理員的使用者名稱。',
- 'config-admin-name-invalid' => '指定的用戶名“<nowiki>$1</nowiki>”無效,請指定其他用戶名。',
- 'config-admin-password-blank' => '輸入管理員帳號密碼。',
- 'config-admin-password-same' => '密碼不能與使用者名稱相同。',
- 'config-admin-password-mismatch' => '兩次輸入的密碼並不相同。',
- 'config-admin-email' => '電郵地址:',
- 'config-admin-email-help' => '輸入電子郵件地址後,您可以收到此wiki上其他用戶發來的電子郵件,並能重置您的密碼,還可在監視列表中頁面被更改時收到郵件通知。您可以將此字段留空。',
- 'config-admin-error-user' => '在創建用戶名為“<nowiki>$1</nowiki>”的管理員帳號時發生內部錯誤。',
- 'config-admin-error-password' => '在為管理員“<nowiki>$1</nowiki>”設置密碼時發生內部錯誤:<pre>$2</pre>',
- 'config-admin-error-bademail' => '你輸入了一個無效的電子郵件地址。',
- 'config-subscribe' => '訂閱[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 發行公告郵件列表]。',
- 'config-subscribe-help' => '此低流量的郵件列表僅用於發行公告,其中包括重要安全公告。請訂閱該列表以便在新的版本推出時升級您的MediaWiki。',
- 'config-subscribe-noemail' => '您選擇了訂閱發行公告郵件列表,但沒有提供電子郵件地址。請提供一個電子郵件地址以訂閱郵件列表。',
- 'config-almost-done' => '您幾乎已經完成了!現在您可以跳過剩下的配置流程並立即安裝wiki。',
- 'config-optional-continue' => '多問我一些問題吧。',
- 'config-optional-skip' => '我已經不耐煩了,趕緊安裝我的wiki。',
- 'config-profile' => '用戶權限配置:',
- 'config-profile-wiki' => '開放的wiki',
- 'config-profile-no-anon' => '需要註冊帳號',
- 'config-profile-fishbowl' => '編輯受限',
- 'config-profile-private' => '非公開wiki',
- 'config-profile-help' => "如果您允許盡量多的人編寫wiki,網站上的內容會更加豐富。在MediaWiki中,您可以輕鬆地審查最近更改,並輕易回退掉新手或破壞者造成的損害。
-
-然而,許多人覺得讓MediaWiki存在多種角色將更加好用;同時,要說服所有人都願以wiki的方式作貢獻並非一件易事。因此,您可以有以下選擇:
-
-'''{{int:config-profile-wiki}}'''允許包括未登錄用戶在內的所有人編輯。'''{{int:config-profile-no-anon}}'''的wiki需要額外的註冊流程,這有可能會阻礙隨意貢獻者。
-
-'''{{int:config-profile-fishbowl}}'''模式只允許獲批准的用戶編輯,但對公眾開放頁面瀏覽(包括歷史記錄)。'''{{int:config-profile-private}}'''則只允許獲批准的用戶瀏覽、編輯頁面。
-
-安裝完成後,您還可以對用戶權限進行更多、更複雜的配置,參見[//www.mediawiki.org/wiki/Manual:User_rights 相關的使用手冊]。", # Fuzzy
- 'config-license' => '版權和許可證:',
- 'config-license-none' => '頁腳無許可證',
- 'config-license-cc-by-sa' => '知識共享署名-相同方式分享',
- 'config-license-cc-by' => '知識共享署名',
- 'config-license-cc-by-nc-sa' => '知識共享署名-非商業性使用-相同方式共享',
- 'config-license-cc-0' => '知識共享Zero(公有領域)',
- 'config-license-gfdl' => 'GNU自由文檔許可證1.3或更高版本',
- 'config-license-pd' => '公共領域',
- 'config-license-cc-choose' => '選擇自定義的知識共享許可證',
- 'config-license-help' => "許多公共wiki會以[http://freedomdefined.org/Definition 自由許可證]的方式釋放出編者的所有貢獻。這有助於構建社區的主人翁意識,並能鼓勵長期貢獻。對於非公共wiki或公司wiki,這並非必要條件。
-
-如果您希望使用來自維基百科的內容,並希望維基百科能接受複製自您的wiki的內容,請選擇'''知識共享署名-相同方式共享'''。
-
-GNU自由文檔許可證是維基百科曾經使用過的許可證,並迄今有效。然而,該許可證難以理解,並會增加重用內容的難度。",
- 'config-email-settings' => 'E-mail 設定',
- 'config-enable-email' => '啟用出站電子郵件',
- 'config-enable-email-help' => '如果您希望使用電子郵件功能,請正確配置[http://www.php.net/manual/en/mail.configuration.php PHP的郵件設定]。如果您不需要任何電子郵件功能,請在此處禁用它。',
- 'config-email-user' => '啟用用戶到用戶的電子郵件',
- 'config-email-user-help' => '允許所有用戶互發郵件,假若他們啟用了該功能。',
- 'config-email-usertalk' => '啟用用戶討論頁通知',
- 'config-email-usertalk-help' => '允許用戶收到用戶討論頁被修改的通知,假若他們啟用了該功能。',
- 'config-email-watchlist' => '啟用監視列表通知',
- 'config-email-watchlist-help' => '允許用戶收到與其監視列表有關的通知,假若他們啟用了該功能。',
- 'config-email-auth' => '啟用電子郵件認證',
- 'config-email-auth-help' => "如果啟用此選項,在用戶設置或修改電子郵件地址時,就會收到一封郵件,內含確認電子地址的鏈接。只有經過身份驗證的電子郵件地址,才能收到來自其他用戶的電子郵件,或任何修改通知的郵件。'''建議'''公開wiki啟用本選項,以防對電子郵件功能的濫用。",
- 'config-email-sender' => '返回電子郵件地址:',
- 'config-email-sender-help' => '輸入要用來發送出站電子郵件的地址,該地址將會收到被拒收的郵件。許多郵件服務器要求域名部分必須有效。',
- 'config-upload-settings' => '圖片和檔案上傳',
- 'config-upload-enable' => '啟用檔案上傳',
- 'config-upload-help' => '文件上傳可能會將您的服務器暴露在安全風險下。有關更多的信息,請參閱手冊的[//www.mediawiki.org/wiki/Manual:Security 安全部分]。
-
-要啟用文件上傳,請先將MediaWiki根目錄下的<code>images</code>子目錄更改為對web服務器可寫,然後再啟用此選項。',
- 'config-upload-deleted' => '已刪除文件的目錄:',
- 'config-upload-deleted-help' => '指定用於存放被刪除文件的目錄。理想情況下,該目錄不應能通過web訪問。',
- 'config-logo' => '標誌URL:',
- 'config-logo-help' => '在MediaWiki的默認外觀中,左側欄菜單之上有一塊135x160像素的標誌區。請上傳一幅相應大小的圖像,並在此輸入URL。
-
-如果您不希望使用標誌,請將本處留空。', # Fuzzy
- 'config-instantcommons' => '啟用即時共享資源',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons 即時共享資源]可以讓wiki使用來自[//commons.wikimedia.org/ 維基共享資源]網站的圖像、音頻和其他媒體文件。要啟用該功能,MediaWiki必須能夠訪問互聯網。
-
-有關此功能的詳細信息,包括如何將其他wiki網站設為具有類似共享功能的方法,請參考[//mediawiki.org/wiki/Manual:$wgForeignFileRepos 手冊]。',
- 'config-cc-error' => '知識共享許可證挑選器無法找到結果,請手動輸入許可證的名稱。',
- 'config-cc-again' => '重新選取......',
- 'config-cc-not-chosen' => '選擇您希望使用的知識共享許可證,並點擊“繼續”。',
- 'config-advanced-settings' => '進階配置',
- 'config-cache-options' => '對象緩存設置:',
- 'config-cache-help' => '對象緩存可通過緩存頻繁使用的數據來提高MediaWiki的速度。高度推薦中到大型的網站啟用該功能,小型網站亦能從其中受益。',
- 'config-cache-none' => '無緩存(不影響功能,但對較大型的wiki網站會有速度影響)',
- 'config-cache-accel' => 'PHP對象緩存(APC、XCache或WinCache)',
- 'config-cache-memcached' => '使用Memcached(需要另外安裝並配置)',
- 'config-memcached-servers' => 'Memcached服務器:',
- 'config-memcached-help' => '用於Memcached的IP地址列表。請保持每行一條,並指定要使用的端口。例如:
-127.0.0.1:11211
-192.168.1.25:1234',
- 'config-memcache-needservers' => '您選擇了Memcached作為您的緩存,但並未指定任何服務器。',
- 'config-memcache-badip' => '您為Memcached輸入了無效的IP地址:$1。',
- 'config-memcache-noport' => '您沒有指定Memcached服務器的端口:$1。如果您不清楚端口是多少,默認值為11211。',
- 'config-memcache-badport' => 'Memcached的端口號應該在$1到$2之間。',
- 'config-extensions' => '擴充套件',
- 'config-extensions-help' => '已在您的<code>./extensions</code>目錄中發現下列擴展。
-
-您可能要對它們進行額外的配置,但您現在可以啟用它們。',
- 'config-install-alreadydone' => "'''警告:'''您似乎已經安裝了MediaWiki,並試圖重新安裝它。請前往下一個頁面。",
- 'config-install-begin' => '點擊“{{int:config-continue}}”後,您將開始安裝MediaWiki。如果您還想對配置作一些修改,請點擊後退。', # Fuzzy
- 'config-install-step-done' => '完成',
- 'config-install-step-failed' => '失敗',
- 'config-install-extensions' => '正在啟用擴展',
- 'config-install-database' => '正在配置數據庫',
- 'config-install-schema' => '創建架構',
- 'config-install-pg-schema-not-exist' => 'PostgreSQL 架構不存在',
- 'config-install-pg-schema-failed' => '創建數據表失敗。請確保用戶“$1”擁有寫入模式“$2”的權限。',
- 'config-install-pg-commit' => '提交更改',
- 'config-install-pg-plpgsql' => '正在檢查PL/pgSQL語言',
- 'config-pg-no-plpgsql' => '您需要為數據庫$1安裝PL/pgSQL語言',
- 'config-pg-no-create-privs' => '為安裝程序指定的帳號缺少創建帳號的權限。',
- 'config-pg-not-in-role' => '您指定為web用戶的帳戶已經存在。
-您給本程序指定的帳戶不是超級用戶,也不是web用戶角色的成員,所以它不能創建web用戶所擁有的對象。
-
-MediaWiki當前需要使用由web用戶所有的表。請指定另一個web帳戶名稱,或點擊“後退”並指定具有適當權限的安裝用戶。',
- 'config-install-user' => '正在創建數據庫用戶',
- 'config-install-user-alreadyexists' => '用戶“$1”已存在',
- 'config-install-user-create-failed' => '創建用戶“$1”失敗:$2',
- 'config-install-user-grant-failed' => '授予用戶“$1”權限失敗:$2',
- 'config-install-user-missing' => '指定的用戶“$1”不存在。',
- 'config-install-user-missing-create' => '指定的用戶“$1”不存在。如果您想要創建一名,請點選“創建帳戶”下面的複選框。',
- 'config-install-tables' => '正在創建數據表',
- 'config-install-tables-exist' => "'''警告''':MediaWiki的數據表似乎已經存在,跳過創建。",
- 'config-install-tables-failed' => "'''錯誤''':創建數據表出錯,下為錯誤信息:$1",
- 'config-install-interwiki' => '正在填充默認的跨wiki數據表',
- 'config-install-interwiki-list' => '找不到文件<code>interwiki.list</code>。',
- 'config-install-interwiki-exists' => "'''警告''':跨wiki數據表似乎已有內容,跳過默認列表。",
- 'config-install-stats' => '初始化統計',
- 'config-install-keys' => '生成密鑰中',
- 'config-insecure-keys' => "'''警告''':在安裝過程中生成的{{PLURAL:$2|安全密鑰|安全密鑰}}($1){{PLURAL:$2|並|並}}不一定安全。請考慮手動更改{{PLURAL:$2|它|它們}}。",
- 'config-install-sysop' => '正在創建管理員用戶帳號',
- 'config-install-subscribe-fail' => '無法訂閱mediawiki-announce:$1',
- 'config-install-subscribe-notpossible' => '沒有安裝cURL,allow_url_fopen也不可用。',
- 'config-install-mainpage' => '正在創建顯示默認內容的首頁',
- 'config-install-extension-tables' => '正在為已啟用擴展創建數據表',
- 'config-install-mainpage-failed' => '無法插入首頁:$1',
- 'config-install-done' => "'''恭喜!'''
-您已經成功地安裝了MediaWiki。
-
-安裝程序已經生成了<code>LocalSettings.php</code>文件,其中包含了您所有的配置。
-
-您需要下載該文件,並將其放在您wiki的根目錄(index.php的同級目錄)中。稍後下載將自動開始。
-
-如果瀏覽器沒有提示您下載,或者您取消了下載,您可以點擊下面的鏈接重新開始下載:
-
-$3
-
-'''注意''':如果您現在不完成本步驟,而是沒有下載便退出了安裝過程,此後您將無法獲得自動生成的配置文件。
-
-當本步驟完成後,您可以 '''[$2 進入您的wiki]'''。",
- 'config-download-localsettings' => '下載<code>LocalSettings.php</code>',
- 'config-help' => '說明',
- 'mainpagetext' => "'''已成功安裝MediaWiki。'''",
- 'mainpagedocfooter' => '請參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以獲得使用此wiki軟體的訊息!
-
-== 入門 ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki配置設定清單]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki常見問題解答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki發佈郵件清單]
-* [//www.mediawiki.org/wiki/Localisation#Translation_resources MediaWiki界面本地化]',
-);
-
-/** Chinese (Hong Kong) (‪中文(香港)‬)
- * @author Mark85296341
- */
-$messages['zh-hk'] = array(
- 'mainpagedocfooter' => '請參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以獲得使用此 wiki 軟件的訊息!
-
-== 入門 ==
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]',
-);
-
-/** Chinese (Taiwan) (‪中文(台灣)‬) */
-$messages['zh-tw'] = array(
- 'mainpagedocfooter' => '請參閱 [//meta.wikimedia.org/wiki/Help:Contents 使用者手冊] 以獲得使用此 wiki 軟體的訊息!
-
-== 入門 ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]
-* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]',
-);
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index a9908134..d2651ae5 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -38,9 +38,6 @@
*/
abstract class Installer {
- // This is the absolute minimum PHP version we can support
- const MINIMUM_PHP_VERSION = '5.3.2';
-
/**
* The oldest version of PCRE we can support.
*
@@ -71,7 +68,7 @@ abstract class Installer {
/**
* Minimum memory size in MB.
*
- * @var integer
+ * @var int
*/
protected $minMemorySize = 50;
@@ -102,6 +99,7 @@ abstract class Installer {
'mysql',
'postgres',
'oracle',
+ 'mssql',
'sqlite',
);
@@ -110,17 +108,18 @@ abstract class Installer {
* These may output warnings using showMessage(), and/or abort the
* installation process by returning false.
*
+ * For the WebInstaller these are only called on the Welcome page,
+ * if these methods have side-effects that should affect later page loads
+ * (as well as the generated stylesheet), use envPreps instead.
+ *
* @var array
*/
protected $envChecks = array(
'envCheckDB',
'envCheckRegisterGlobals',
'envCheckBrokenXML',
- 'envCheckPHP531',
'envCheckMagicQuotes',
- 'envCheckMagicSybase',
'envCheckMbstring',
- 'envCheckZE1',
'envCheckSafeMode',
'envCheckXML',
'envCheckPCRE',
@@ -132,16 +131,27 @@ abstract class Installer {
'envCheckGit',
'envCheckServer',
'envCheckPath',
- 'envCheckExtension',
'envCheckShellLocale',
'envCheckUploadsDirectory',
'envCheckLibicu',
'envCheckSuhosinMaxValueLength',
'envCheckCtype',
+ 'envCheckIconv',
'envCheckJSON',
);
/**
+ * A list of environment preparation methods called by doEnvironmentPreps().
+ *
+ * @var array
+ */
+ protected $envPreps = array(
+ 'envPrepExtension',
+ 'envPrepServer',
+ 'envPrepPath',
+ );
+
+ /**
* MediaWiki configuration globals that will eventually be passed through
* to LocalSettings.php. The names only are given here, the defaults
* typically come from DefaultSettings.php.
@@ -171,7 +181,6 @@ abstract class Installer {
'wgMetaNamespace',
'wgDeletedDirectory',
'wgEnableUploads',
- 'wgLogo',
'wgShellLocale',
'wgSecretKey',
'wgUseInstantCommons',
@@ -201,7 +210,7 @@ abstract class Installer {
'_NamespaceType' => 'site-name',
'_AdminName' => '', // will be set later, when the user selects language
'_AdminPassword' => '',
- '_AdminPassword2' => '',
+ '_AdminPasswordConfirm' => '',
'_AdminEmail' => '',
'_Subscribe' => false,
'_SkipOptional' => 'continue',
@@ -209,9 +218,14 @@ abstract class Installer {
'_LicenseCode' => 'none',
'_CCDone' => false,
'_Extensions' => array(),
+ '_Skins' => array(),
'_MemCachedServers' => '',
'_UpgradeKeySupplied' => false,
'_ExistingDBSettings' => false,
+
+ // $wgLogo is probably wrong (bug 48084); set something that will work.
+ // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped.
+ 'wgLogo' => '$wgScriptPath/resources/assets/wiki.png',
);
/**
@@ -272,27 +286,27 @@ abstract class Installer {
public $licenses = array(
'cc-by' => array(
'url' => 'http://creativecommons.org/licenses/by/3.0/',
- 'icon' => '{$wgStylePath}/common/images/cc-by.png',
+ 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by.png',
),
'cc-by-sa' => array(
'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
- 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
+ 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by-sa.png',
),
'cc-by-nc-sa' => array(
'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
- 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
+ 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by-nc-sa.png',
),
'cc-0' => array(
'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
- 'icon' => '{$wgStylePath}/common/images/cc-0.png',
+ 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-0.png',
),
'pd' => array(
'url' => '',
- 'icon' => '{$wgStylePath}/common/images/public-domain.png',
+ 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/public-domain.png',
),
'gfdl' => array(
'url' => 'http://www.gnu.org/copyleft/fdl.html',
- 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
+ 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/gnu-fdl.png',
),
'none' => array(
'url' => '',
@@ -327,19 +341,19 @@ abstract class Installer {
* The parameters are like parameters to wfMessage().
* 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 $msg
+ * @param string $msg
*/
abstract public function showMessage( $msg /*, ... */ );
/**
* Same as showMessage(), but for displaying errors
- * @param $msg
+ * @param string $msg
*/
abstract public function showError( $msg /*, ... */ );
/**
* Show a message to the installing user by using a Status object
- * @param $status Status
+ * @param Status $status
*/
abstract public function showStatusMessage( Status $status );
@@ -347,15 +361,27 @@ abstract class Installer {
* Constructor, always call this from child classes.
*/
public function __construct() {
- global $wgExtensionMessagesFiles, $wgUser;
+ global $wgMessagesDirs, $wgUser;
- // Disable the i18n cache and LoadBalancer
+ // Disable the i18n cache
Language::getLocalisationCache()->disableBackend();
+ // Disable LoadBalancer and wfGetDB etc.
LBFactory::disableBackend();
- // Load the installer's i18n file.
- $wgExtensionMessagesFiles['MediawikiInstaller'] =
- __DIR__ . '/Installer.i18n.php';
+ // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
+ // SqlBagOStuff will then throw since we just disabled wfGetDB)
+ $GLOBALS['wgMemc'] = new EmptyBagOStuff;
+ ObjectCache::clear();
+ $emptyCache = array( 'class' => 'EmptyBagOStuff' );
+ $GLOBALS['wgObjectCaches'] = array(
+ CACHE_NONE => $emptyCache,
+ CACHE_DB => $emptyCache,
+ CACHE_ANYTHING => $emptyCache,
+ CACHE_MEMCACHED => $emptyCache,
+ );
+
+ // Load the installer's i18n.
+ $wgMessagesDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
// Having a user with id = 0 safeguards us from DB access via User::loadOptions().
$wgUser = User::newFromId( 0 );
@@ -366,29 +392,20 @@ abstract class Installer {
$this->settings[$var] = $GLOBALS[$var];
}
- $compiledDBs = array();
+ $this->doEnvironmentPreps();
+
+ $this->compiledDBs = array();
foreach ( self::getDBTypes() as $type ) {
$installer = $this->getDBInstaller( $type );
if ( !$installer->isCompiled() ) {
continue;
}
- $compiledDBs[] = $type;
-
- $defaults = $installer->getGlobalDefaults();
-
- foreach ( $installer->getGlobalNames() as $var ) {
- if ( isset( $defaults[$var] ) ) {
- $this->settings[$var] = $defaults[$var];
- } else {
- $this->settings[$var] = $GLOBALS[$var];
- }
- }
+ $this->compiledDBs[] = $type;
}
- $this->compiledDBs = $compiledDBs;
$this->parserTitle = Title::newFromText( 'Installer' );
- $this->parserOptions = new ParserOptions; // language will be wrong :(
+ $this->parserOptions = new ParserOptions; // language will be wrong :(
$this->parserOptions->setEditSection( false );
}
@@ -415,25 +432,21 @@ abstract class Installer {
* @return Status
*/
public function doEnvironmentChecks() {
- $phpVersion = phpversion();
- if ( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
- $this->showMessage( 'config-env-php', $phpVersion );
- $good = true;
+ // Php version has already been checked by entry scripts
+ // Show message here for information purposes
+ if ( wfIsHHVM() ) {
+ $this->showMessage( 'config-env-hhvm', HHVM_VERSION );
} else {
- $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
- $good = false;
+ $this->showMessage( 'config-env-php', PHP_VERSION );
}
+ $good = true;
// Must go here because an old version of PCRE can prevent other checks from completing
- if ( $good ) {
- list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 );
- if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
- $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
- $good = false;
- }
- }
-
- if ( $good ) {
+ list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 );
+ if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
+ $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
+ $good = false;
+ } else {
foreach ( $this->envChecks as $check ) {
$status = $this->$check();
if ( $status === false ) {
@@ -447,11 +460,17 @@ abstract class Installer {
return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
}
+ public function doEnvironmentPreps() {
+ foreach ( $this->envPreps as $prep ) {
+ $this->$prep();
+ }
+ }
+
/**
* Set a MW configuration variable, or internal installer configuration variable.
*
- * @param $name String
- * @param $value Mixed
+ * @param string $name
+ * @param mixed $value
*/
public function setVar( $name, $value ) {
$this->settings[$name] = $value;
@@ -462,8 +481,8 @@ abstract class Installer {
* The defaults come from $GLOBALS (ultimately DefaultSettings.php).
* Installer variables are typically prefixed by an underscore.
*
- * @param $name String
- * @param $default Mixed
+ * @param string $name
+ * @param mixed $default
*
* @return mixed
*/
@@ -487,7 +506,7 @@ abstract class Installer {
/**
* Get an instance of DatabaseInstaller for the specified DB type.
*
- * @param $type Mixed: DB installer for which is needed, false to use default.
+ * @param mixed $type DB installer for which is needed, false to use default.
*
* @return DatabaseInstaller
*/
@@ -507,10 +526,9 @@ abstract class Installer {
}
/**
- * Determine if LocalSettings.php exists. If it does, return its variables,
- * merged with those from AdminSettings.php, as an array.
+ * Determine if LocalSettings.php exists. If it does, return its variables.
*
- * @return Array
+ * @return array
*/
public static function getExistingLocalSettings() {
global $IP;
@@ -521,6 +539,7 @@ abstract class Installer {
// registration out of the global scope and into a real format.
// @see https://bugzilla.wikimedia.org/67440
global $wgAutoloadClasses;
+ $wgAutoloadClasses = array();
wfSuppressWarnings();
$_lsExists = file_exists( "$IP/LocalSettings.php" );
@@ -533,9 +552,6 @@ abstract class Installer {
require "$IP/includes/DefaultSettings.php";
require "$IP/LocalSettings.php";
- if ( file_exists( "$IP/AdminSettings.php" ) ) {
- require "$IP/AdminSettings.php";
- }
return get_defined_vars();
}
@@ -545,7 +561,7 @@ abstract class Installer {
* This is a security mechanism to avoid compromise of the password in the
* event of session ID compromise.
*
- * @param $realPassword String
+ * @param string $realPassword
*
* @return string
*/
@@ -557,8 +573,8 @@ abstract class Installer {
* Set a variable which stores a password, except if the new value is a
* fake password in which case leave it as it is.
*
- * @param $name String
- * @param $value Mixed
+ * @param string $name
+ * @param mixed $value
*/
public function setPassword( $name, $value ) {
if ( !preg_match( '/^\*+$/', $value ) ) {
@@ -604,9 +620,9 @@ abstract class Installer {
* whatever, this function is guarded to catch the attempted DB access and to present
* some fallback text.
*
- * @param $text String
- * @param $lineStart Boolean
- * @return String
+ * @param string $text
+ * @param bool $lineStart
+ * @return string
*/
public function parse( $text, $lineStart = false ) {
global $wgParser;
@@ -645,7 +661,7 @@ abstract class Installer {
* Install step which adds a row to the site_stats table with appropriate
* initial values.
*
- * @param $installer DatabaseInstaller
+ * @param DatabaseInstaller $installer
*
* @return Status
*/
@@ -654,15 +670,19 @@ abstract class Installer {
if ( !$status->isOK() ) {
return $status;
}
- $status->value->insert( 'site_stats', array(
- 'ss_row_id' => 1,
- 'ss_total_views' => 0,
- 'ss_total_edits' => 0,
- 'ss_good_articles' => 0,
- 'ss_total_pages' => 0,
- 'ss_users' => 0,
- 'ss_images' => 0 ),
- __METHOD__, 'IGNORE' );
+ $status->value->insert(
+ 'site_stats',
+ array(
+ 'ss_row_id' => 1,
+ 'ss_total_views' => 0,
+ 'ss_total_edits' => 0,
+ 'ss_good_articles' => 0,
+ 'ss_total_pages' => 0,
+ 'ss_users' => 0,
+ 'ss_images' => 0
+ ),
+ __METHOD__, 'IGNORE'
+ );
return Status::newGood();
}
@@ -719,11 +739,16 @@ abstract class Installer {
/**
* Environment check for register_globals.
+ * Prevent installation if enabled
+ * @return bool
*/
protected function envCheckRegisterGlobals() {
if ( wfIniGetBool( 'register_globals' ) ) {
- $this->showMessage( 'config-register-globals' );
+ $this->showMessage( 'config-register-globals-error' );
+ return false;
}
+
+ return true;
}
/**
@@ -742,48 +767,19 @@ abstract class Installer {
}
/**
- * Test PHP (probably 5.3.1, but it could regress again) to make sure that
- * reference parameters to __call() are not converted to null
- * @return bool
- */
- protected function envCheckPHP531() {
- $test = new PhpRefCallBugTester;
- $test->execute();
- if ( !$test->ok ) {
- $this->showError( 'config-using531', phpversion() );
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Environment check for magic_quotes_runtime.
+ * Environment check for magic_quotes_(gpc|runtime|sybase).
* @return bool
*/
protected function envCheckMagicQuotes() {
- if ( wfIniGetBool( "magic_quotes_runtime" ) ) {
- $this->showError( 'config-magic-quotes-runtime' );
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Environment check for magic_quotes_sybase.
- * @return bool
- */
- protected function envCheckMagicSybase() {
- if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
- $this->showError( 'config-magic-quotes-sybase' );
-
- return false;
+ $status = true;
+ foreach ( array( 'gpc', 'runtime', 'sybase' ) as $magicJunk ) {
+ if ( wfIniGetBool( "magic_quotes_$magicJunk" ) ) {
+ $this->showError( "config-magic-quotes-$magicJunk" );
+ $status = false;
+ }
}
- return true;
+ return $status;
}
/**
@@ -801,20 +797,6 @@ abstract class Installer {
}
/**
- * Environment check for zend.ze1_compatibility_mode.
- * @return bool
- */
- protected function envCheckZE1() {
- if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
- $this->showError( 'config-ze1' );
-
- return false;
- }
-
- return true;
- }
-
- /**
* Environment check for safe_mode.
* @return bool
*/
@@ -995,55 +977,29 @@ abstract class Installer {
}
/**
- * Environment check for the server hostname.
+ * Environment check to inform user which server we've assumed.
+ *
+ * @return bool
*/
protected function envCheckServer() {
$server = $this->envGetDefaultServer();
if ( $server !== null ) {
$this->showMessage( 'config-using-server', $server );
- $this->setVar( 'wgServer', $server );
}
-
return true;
}
/**
- * Helper function to be called from envCheckServer()
- * @return String
- */
- abstract protected function envGetDefaultServer();
-
- /**
- * Environment check for setting $IP and $wgScriptPath.
+ * Environment check to inform user which paths we've assumed.
+ *
* @return bool
*/
protected function envCheckPath() {
- global $IP;
- $IP = dirname( dirname( __DIR__ ) );
- $this->setVar( 'IP', $IP );
-
$this->showMessage(
'config-using-uri',
$this->getVar( 'wgServer' ),
$this->getVar( 'wgScriptPath' )
);
-
- return true;
- }
-
- /**
- * Environment check for setting the preferred PHP file extension.
- * @return bool
- */
- protected function envCheckExtension() {
- // @todo FIXME: Detect this properly
- if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
- $ext = 'php5';
- } else {
- $ext = 'php';
- }
- $this->setVar( 'wgScriptExtension', ".$ext" );
-
return true;
}
@@ -1160,7 +1116,7 @@ abstract class Installer {
/**
* Convert a hex string representing a Unicode code point to that code point.
- * @param $c String
+ * @param string $c
* @return string
*/
protected function unicodeChar( $c ) {
@@ -1171,11 +1127,11 @@ abstract class Installer {
return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
} elseif ( $c <= 0xFFFF ) {
return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F )
- . chr( 0x80 | $c & 0x3F );
+ . chr( 0x80 | $c & 0x3F );
} elseif ( $c <= 0x10FFFF ) {
return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F )
- . chr( 0x80 | $c >> 6 & 0x3F )
- . chr( 0x80 | $c & 0x3F );
+ . chr( 0x80 | $c >> 6 & 0x3F )
+ . chr( 0x80 | $c & 0x3F );
} else {
return false;
}
@@ -1248,6 +1204,19 @@ abstract class Installer {
/**
* @return bool
*/
+ protected function envCheckIconv() {
+ if ( !function_exists( 'iconv' ) ) {
+ $this->showError( 'config-iconv' );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
protected function envCheckJSON() {
if ( !function_exists( 'json_decode' ) ) {
$this->showError( 'config-json' );
@@ -1259,11 +1228,49 @@ abstract class Installer {
}
/**
+ * Environment prep for the server hostname.
+ */
+ protected function envPrepServer() {
+ $server = $this->envGetDefaultServer();
+ if ( $server !== null ) {
+ $this->setVar( 'wgServer', $server );
+ }
+ }
+
+ /**
+ * Helper function to be called from envPrepServer()
+ * @return string
+ */
+ abstract protected function envGetDefaultServer();
+
+ /**
+ * Environment prep for setting the preferred PHP file extension.
+ */
+ protected function envPrepExtension() {
+ // @todo FIXME: Detect this properly
+ if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
+ $ext = '.php5';
+ } else {
+ $ext = '.php';
+ }
+ $this->setVar( 'wgScriptExtension', $ext );
+ }
+
+ /**
+ * Environment prep for setting $IP and $wgScriptPath.
+ */
+ protected function envPrepPath() {
+ global $IP;
+ $IP = dirname( dirname( __DIR__ ) );
+ $this->setVar( 'IP', $IP );
+ }
+
+ /**
* Get an array of likely places we can find executables. Check a bunch
* of known Unix-like defaults, as well as the PATH environment variable
* (which should maybe make it work for Windows?)
*
- * @return Array
+ * @return array
*/
protected static function getPossibleBinPaths() {
return array_merge(
@@ -1280,11 +1287,11 @@ abstract class Installer {
*
* Used only by environment checks.
*
- * @param string $path path to search
- * @param array $names of executable names
- * @param $versionInfo Boolean false or array with two members:
- * 0 => Command to run for version check, with $1 for the full executable name
- * 1 => String to compare the output with
+ * @param string $path Path to search
+ * @param array $names Array of executable names
+ * @param array|bool $versionInfo False or array with two members:
+ * 0 => Command to run for version check, with $1 for the full executable name
+ * 1 => String to compare the output with
*
* If $versionInfo is not false, only executables with a version
* matching $versionInfo[1] will be returned.
@@ -1320,8 +1327,13 @@ abstract class Installer {
/**
* Same as locateExecutable(), but checks in getPossibleBinPaths() by default
* @see locateExecutable()
- * @param $names
- * @param $versionInfo bool
+ * @param array $names Array of possible names.
+ * @param array|bool $versionInfo Default: false or array with two members:
+ * 0 => Command to run for version check, with $1 for the full executable name
+ * 1 => String to compare the output with
+ *
+ * If $versionInfo is not false, only executables with a version
+ * matching $versionInfo[1] will be returned.
* @return bool|string
*/
public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
@@ -1339,8 +1351,8 @@ abstract class Installer {
* Checks if scripts located in the given directory can be executed via the given URL.
*
* Used only by environment checks.
- * @param $dir string
- * @param $url string
+ * @param string $dir
+ * @param string $url
* @return bool|int|string
*/
public function dirIsExecutable( $dir, $url ) {
@@ -1405,7 +1417,7 @@ abstract class Installer {
/**
* ParserOptions are constructed before we determined the language, so fix it
*
- * @param $lang Language
+ * @param Language $lang
*/
public function setParserLanguage( $lang ) {
$this->parserOptions->setTargetLanguage( $lang );
@@ -1414,7 +1426,7 @@ abstract class Installer {
/**
* Overridden by WebInstaller to provide lastPage parameters.
- * @param $page string
+ * @param string $page
* @return string
*/
protected function getDocUrl( $page ) {
@@ -1422,35 +1434,20 @@ abstract class Installer {
}
/**
- * Load the extension credits for i18n strings. Very hacky for
- * now, but I expect it only be used for 1.22.0 at the most.
- */
- public function getExtensionInfo( $file ) {
- global $wgExtensionCredits, $wgVersion, $wgResourceModules;
-
- $wgVersion = "1.22";
- $wgResourceModules = array();
- require_once $file ;
- $e = array_values( $wgExtensionCredits );
- if( $e ) {
- $ext = array_values( $e[0] );
- $wgExtensionCredits = array();
- return $ext[0];
- }
- }
-
- /**
- * Finds extensions that follow the format /extensions/Name/Name.php,
+ * Finds extensions that follow the format /$directory/Name/Name.php,
* and returns an array containing the value for 'Name' for each found extension.
*
+ * Reasonable values for $directory include 'extensions' (the default) and 'skins'.
+ *
+ * @param string $directory Directory to search in
* @return array
*/
- public function findExtensions() {
+ public function findExtensions( $directory = 'extensions' ) {
if ( $this->getVar( 'IP' ) === null ) {
return array();
}
- $extDir = $this->getVar( 'IP' ) . '/extensions';
+ $extDir = $this->getVar( 'IP' ) . '/' . $directory;
if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
return array();
}
@@ -1461,26 +1458,33 @@ abstract class Installer {
if ( !is_dir( "$extDir/$file" ) ) {
continue;
}
-
- $extFile = "$extDir/$file/$file.php";
- $extI18NFile = "$extDir/$file/$file.i18n.php";
- if ( file_exists( $extFile ) ) {
- if ( $info = $this->getExtensionInfo( $extFile ) ) {
- $exts[$info['name']] = $info;
-
- if ( file_exists( $extI18NFile ) ) {
- global $wgExtensionMessagesFiles;
- $wgExtensionMessagesFiles[$file] = $extI18NFile;
- }
- }
+ if ( file_exists( "$extDir/$file/$file.php" ) ) {
+ $exts[] = $file;
}
}
closedir( $dh );
- uksort( $exts, 'strnatcasecmp' );
+ natcasesort( $exts );
+
return $exts;
}
/**
+ * Returns a default value to be used for $wgDefaultSkin: the preferred skin, if available among
+ * the installed skins, or any other one otherwise.
+ *
+ * @param string[] $skinNames Names of installed skins.
+ * @return string
+ */
+ public function getDefaultSkin( array $skinNames ) {
+ $defaultSkin = $GLOBALS['wgDefaultSkin'];
+ if ( in_array( $defaultSkin, $skinNames ) ) {
+ return $defaultSkin;
+ } else {
+ return $skinNames[0];
+ }
+ }
+
+ /**
* Installs the auto-detected extensions.
*
* @return Status
@@ -1503,9 +1507,8 @@ abstract class Installer {
require "$IP/includes/DefaultSettings.php";
- $extensions = $this->findExtensions();
foreach ( $exts as $e ) {
- require_once $extensions[$e]['path'];
+ require_once "$IP/extensions/$e/$e.php";
}
$hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
@@ -1527,7 +1530,7 @@ abstract class Installer {
* There must be a config-install-$name message defined per step, which will
* be shown on install.
*
- * @param $installer DatabaseInstaller so we can make callbacks
+ * @param DatabaseInstaller $installer DatabaseInstaller so we can make callbacks
* @return array
*/
protected function getInstallSteps( DatabaseInstaller $installer ) {
@@ -1537,6 +1540,7 @@ abstract class Installer {
array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
+ array( 'name' => 'updates', 'callback' => array( $installer, 'insertUpdateKeys' ) ),
array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
);
@@ -1578,10 +1582,10 @@ abstract class Installer {
/**
* Actually perform the installation.
*
- * @param array $startCB A callback array for the beginning of each step
- * @param array $endCB A callback array for the end of each step
+ * @param callable $startCB A callback array for the beginning of each step
+ * @param callable $endCB A callback array for the end of each step
*
- * @return Array of Status objects
+ * @return array Array of Status objects
*/
public function performInstallation( $startCB, $endCB ) {
$installResults = array();
@@ -1630,7 +1634,7 @@ abstract class Installer {
* Generate a secret value for variables using our CryptRand generator.
* Produce a warning if the random source was insecure.
*
- * @param $keys Array
+ * @param array $keys
* @return Status
*/
protected function doGenerateKeys( $keys ) {
@@ -1700,7 +1704,7 @@ abstract class Installer {
}
/**
- * @param $s Status
+ * @param Status $s
*/
private function subscribeToMediaWikiAnnounce( Status $s ) {
$params = array(
@@ -1731,7 +1735,7 @@ abstract class Installer {
/**
* Insert Main Page with default content.
*
- * @param $installer DatabaseInstaller
+ * @param DatabaseInstaller $installer
* @return Status
*/
protected function createMainpage( DatabaseInstaller $installer ) {
@@ -1784,19 +1788,14 @@ abstract class Installer {
// Some of the environment checks make shell requests, remove limits
$GLOBALS['wgMaxShellMemory'] = 0;
-
- // Don't bother embedding images into generated CSS, which is not cached
- $GLOBALS['wgResourceLoaderLESSFunctions']['embeddable'] = function( $frame, $less ) {
- return $less->toBool( false );
- };
}
/**
* Add an installation step following the given step.
*
- * @param array $callback A valid installation callback array, in this form:
+ * @param callable $callback A valid installation callback array, in this form:
* array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
- * @param string $findStep the step to find. Omit to put the step at the beginning
+ * @param string $findStep The step to find. Omit to put the step at the beginning
*/
public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
$this->extraInstallSteps[$findStep][] = $callback;
diff --git a/includes/installer/LocalSettingsGenerator.php b/includes/installer/LocalSettingsGenerator.php
index 53939826..c0ba300d 100644
--- a/includes/installer/LocalSettingsGenerator.php
+++ b/includes/installer/LocalSettingsGenerator.php
@@ -43,12 +43,13 @@ class LocalSettingsGenerator {
/**
* Constructor.
*
- * @param $installer Installer subclass
+ * @param Installer $installer
*/
public function __construct( Installer $installer ) {
$this->installer = $installer;
$this->extensions = $installer->getVar( '_Extensions' );
+ $this->skins = $installer->getVar( '_Skins' );
$db = $installer->getDBInstaller( $installer->getVar( 'wgDBtype' ) );
@@ -105,9 +106,9 @@ class LocalSettingsGenerator {
/**
* Returns the escaped version of a string of php code.
*
- * @param $string String
+ * @param string $string
*
- * @return String
+ * @return string
*/
public static function escapePhpString( $string ) {
if ( is_array( $string ) || is_object( $string ) ) {
@@ -129,33 +130,42 @@ class LocalSettingsGenerator {
/**
* Return the full text of the generated LocalSettings.php file,
- * including the extensions
+ * including the extensions and skins.
*
- * @return String
+ * @return string
*/
public function getText() {
$localSettings = $this->getDefaultText();
+ if ( count( $this->skins ) ) {
+ $localSettings .= "
+# Enabled skins.
+# The following skins were automatically enabled:\n";
+
+ foreach ( $this->skins as $skinName ) {
+ $encSkinName = self::escapePhpString( $skinName );
+ $localSettings .= "require_once \"\$IP/skins/$encSkinName/$encSkinName.php\";\n";
+ }
+
+ $localSettings .= "\n";
+ }
+
if ( count( $this->extensions ) ) {
- $extensions = $this->installer->findExtensions();
$localSettings .= "
# Enabled Extensions. Most extensions are enabled by including the base extension file here
# but check specific extension documentation for more details
# The following extensions were automatically enabled:\n";
- $ip = $this->installer->getVar( 'IP' );
- foreach ( $this->extensions as $ext) {
- $path = str_replace( $ip, '', $extensions[$ext]['path'] );
- $prefix = '';
- if ( $path !== $extensions[$ext]['path'] ) {
- $prefix = '$IP';
- }
- $path = $prefix . self::escapePhpString( $path );
- $localSettings .= "require_once \"$path\";\n";
+ foreach ( $this->extensions as $extName ) {
+ $encExtName = self::escapePhpString( $extName );
+ $localSettings .= "require_once \"\$IP/extensions/$encExtName/$encExtName.php\";\n";
}
+
+ $localSettings .= "\n";
}
- $localSettings .= "\n\n# End of automatically generated settings.
+ $localSettings .= "
+# End of automatically generated settings.
# Add more configuration options below.\n\n";
return $localSettings;
@@ -171,7 +181,7 @@ class LocalSettingsGenerator {
}
/**
- * @return String
+ * @return string
*/
protected function buildMemcachedServerList() {
$servers = $this->values['_MemCachedServers'];
@@ -192,7 +202,7 @@ class LocalSettingsGenerator {
}
/**
- * @return String
+ * @return string
*/
protected function getDefaultText() {
if ( !$this->values['wgImageMagickConvertCommand'] ) {
@@ -209,7 +219,6 @@ class LocalSettingsGenerator {
$locale = '';
}
- //$rightsUrl = $this->values['wgRightsUrl'] ? '' : '#'; // @todo FIXME: I'm unused!
$hashedUploads = $this->safeMode ? '' : '#';
$metaNamespace = '';
if ( $this->values['wgMetaNamespace'] !== $this->values['wgSitename'] ) {
@@ -217,6 +226,7 @@ class LocalSettingsGenerator {
}
$groupRights = '';
+ $noFollow = '';
if ( $this->groupPermissions ) {
$groupRights .= "# The following permissions were set based on your choice in the installer\n";
foreach ( $this->groupPermissions as $group => $rightArr ) {
@@ -227,12 +237,28 @@ class LocalSettingsGenerator {
wfBoolToStr( $perm ) . ";\n";
}
}
+ $groupRights .= "\n";
+
+ if ( ( isset( $this->groupPermissions['*']['edit'] ) &&
+ $this->groupPermissions['*']['edit'] === false )
+ && ( isset( $this->groupPermissions['*']['createaccount'] ) &&
+ $this->groupPermissions['*']['createaccount'] === false )
+ && ( isset( $this->groupPermissions['*']['read'] ) &&
+ $this->groupPermissions['*']['read'] !== false )
+ ) {
+ $noFollow = "# Set \$wgNoFollowLinks to true if you open up your wiki to editing by\n"
+ . "# the general public and wish to apply nofollow to external links as a\n"
+ . "# deterrent to spammers. Nofollow is not a comprehensive anti-spam solution\n"
+ . "# and open wikis will generally require other anti-spam measures; for more\n"
+ . "# information, see https://www.mediawiki.org/wiki/Manual:Combating_spam\n"
+ . "\$wgNoFollowLinks = false;\n\n";
+ }
}
- $wgServerSetting = "";
+ $serverSetting = "";
if ( array_key_exists( 'wgServer', $this->values ) && $this->values['wgServer'] !== null ) {
- $wgServerSetting = "\n## The protocol and server name to use in fully-qualified URLs\n";
- $wgServerSetting .= "\$wgServer = \"{$this->values['wgServer']}\";\n";
+ $serverSetting = "\n## The protocol and server name to use in fully-qualified URLs\n";
+ $serverSetting .= "\$wgServer = \"{$this->values['wgServer']}\";\n";
}
switch ( $this->values['wgMainCacheType'] ) {
@@ -259,7 +285,7 @@ class LocalSettingsGenerator {
# file, not there.
#
# Further documentation for configuration settings may be found at:
-# http://www.mediawiki.org/wiki/Manual:Configuration_settings
+# https://www.mediawiki.org/wiki/Manual:Configuration_settings
# Protect against web entry
if ( !defined( 'MEDIAWIKI' ) ) {
@@ -275,16 +301,16 @@ if ( !defined( 'MEDIAWIKI' ) ) {
## defaults for all runtime URL paths are based off of this.
## For more information on customizing the URLs
## (like /w/index.php/Page_title to /wiki/Page_title) please see:
-## http://www.mediawiki.org/wiki/Manual:Short_URL
+## https://www.mediawiki.org/wiki/Manual:Short_URL
\$wgScriptPath = \"{$this->values['wgScriptPath']}\";
\$wgScriptExtension = \"{$this->values['wgScriptExtension']}\";
-${wgServerSetting}
+${serverSetting}
## The relative URL path to the skins directory
\$wgStylePath = \"\$wgScriptPath/skins\";
## The relative URL path to the logo. Make sure you change this from the default,
## or else you'll overwrite your logo when you upgrade!
-\$wgLogo = \"{$this->values['wgLogo']}\";
+\$wgLogo = \"{$this->values['wgLogo']}\";
## UPO means: this is also a user preference option
@@ -345,10 +371,6 @@ ${wgServerSetting}
# web installer while LocalSettings.php is in place
\$wgUpgradeKey = \"{$this->values['wgUpgradeKey']}\";
-## Default skin: you can change the default skin. Use the internal symbolic
-## names, ie 'cologneblue', 'monobook', 'vector':
-\$wgDefaultSkin = \"{$this->values['wgDefaultSkin']}\";
-
## For attaching licensing metadata to pages, and displaying an
## appropriate copyright notice / icon. GNU Free Documentation
## License and Creative Commons licenses are supported so far.
@@ -360,6 +382,9 @@ ${wgServerSetting}
# Path to the GNU diff3 utility. Used for conflict resolution.
\$wgDiff3 = \"{$this->values['wgDiff3']}\";
-{$groupRights}";
+{$groupRights}{$noFollow}## Default skin: you can change the default skin. Use the internal symbolic
+## names, ie 'vector', 'monobook':
+\$wgDefaultSkin = \"{$this->values['wgDefaultSkin']}\";
+";
}
}
diff --git a/includes/installer/MssqlInstaller.php b/includes/installer/MssqlInstaller.php
new file mode 100644
index 00000000..46bb86c0
--- /dev/null
+++ b/includes/installer/MssqlInstaller.php
@@ -0,0 +1,737 @@
+<?php
+/**
+ * Microsoft SQL Server-specific installer.
+ *
+ * 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 Deployment
+ */
+
+/**
+ * Class for setting up the MediaWiki database using Microsoft SQL Server.
+ *
+ * @ingroup Deployment
+ * @since 1.23
+ */
+class MssqlInstaller extends DatabaseInstaller {
+
+ protected $globalNames = array(
+ 'wgDBserver',
+ 'wgDBname',
+ 'wgDBuser',
+ 'wgDBpassword',
+ 'wgDBmwschema',
+ 'wgDBprefix',
+ 'wgDBWindowsAuthentication',
+ );
+
+ protected $internalDefaults = array(
+ '_InstallUser' => 'sa',
+ '_InstallWindowsAuthentication' => 'sqlauth',
+ '_WebWindowsAuthentication' => 'sqlauth',
+ );
+
+ // SQL Server 2005 RTM
+ // @todo Are SQL Express version numbers different?)
+ public $minimumVersion = '9.00.1399';
+
+ // These are schema-level privs
+ // Note: the web user will be created will full permissions if possible, this permission
+ // list is only used if we are unable to grant full permissions.
+ public $webUserPrivs = array(
+ 'DELETE',
+ 'INSERT',
+ 'SELECT',
+ 'UPDATE',
+ 'EXECUTE',
+ );
+
+ /**
+ * @return string
+ */
+ public function getName() {
+ return 'mssql';
+ }
+
+ /**
+ * @return bool
+ */
+ public function isCompiled() {
+ return self::checkExtension( 'sqlsrv' );
+ }
+
+ /**
+ * @return string
+ */
+ public function getConnectForm() {
+ if ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth' ) {
+ $displayStyle = 'display: none;';
+ } else {
+ $displayStyle = 'display: block;';
+ }
+
+ return $this->getTextBox(
+ 'wgDBserver',
+ 'config-db-host',
+ array(),
+ $this->parent->getHelpBox( 'config-db-host-help' )
+ ) .
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', array(), wfMessage( 'config-db-wiki-settings' )->text() ) .
+ $this->getTextBox( 'wgDBname', 'config-db-name', array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-name-help' ) ) .
+ $this->getTextBox( 'wgDBmwschema', 'config-db-schema', array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-schema-help' ) ) .
+ $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
+ Html::closeElement( 'fieldset' ) .
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', array(), wfMessage( 'config-db-install-account' )->text() ) .
+ $this->getRadioSet( array(
+ 'var' => '_InstallWindowsAuthentication',
+ 'label' => 'config-mssql-auth',
+ 'itemLabelPrefix' => 'config-mssql-',
+ 'values' => array( 'sqlauth', 'windowsauth' ),
+ 'itemAttribs' => array(
+ 'sqlauth' => array(
+ 'class' => 'showHideRadio',
+ 'rel' => 'dbCredentialBox',
+ ),
+ 'windowsauth' => array(
+ 'class' => 'hideShowRadio',
+ 'rel' => 'dbCredentialBox',
+ )
+ ),
+ 'help' => $this->parent->getHelpBox( 'config-mssql-install-auth' )
+ ) ) .
+ Html::openElement( 'div', array( 'id' => 'dbCredentialBox', 'style' => $displayStyle ) ) .
+ $this->getTextBox(
+ '_InstallUser',
+ 'config-db-username',
+ array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-install-username' )
+ ) .
+ $this->getPasswordBox(
+ '_InstallPassword',
+ 'config-db-password',
+ array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-install-password' )
+ ) .
+ Html::closeElement( 'div' ) .
+ Html::closeElement( 'fieldset' );
+ }
+
+ public function submitConnectForm() {
+ // Get variables from the request.
+ $newValues = $this->setVarsFromRequest( array(
+ 'wgDBserver',
+ 'wgDBname',
+ 'wgDBmwschema',
+ 'wgDBprefix'
+ ) );
+
+ // Validate them.
+ $status = Status::newGood();
+ if ( !strlen( $newValues['wgDBserver'] ) ) {
+ $status->fatal( 'config-missing-db-host' );
+ }
+ if ( !strlen( $newValues['wgDBname'] ) ) {
+ $status->fatal( 'config-missing-db-name' );
+ } elseif ( !preg_match( '/^[a-z0-9_]+$/i', $newValues['wgDBname'] ) ) {
+ $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
+ }
+ if ( !preg_match( '/^[a-z0-9_]*$/i', $newValues['wgDBmwschema'] ) ) {
+ $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
+ }
+ if ( !preg_match( '/^[a-z0-9_]*$/i', $newValues['wgDBprefix'] ) ) {
+ $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ // Check for blank schema and remap to dbo
+ if ( $newValues['wgDBmwschema'] === '' ) {
+ $this->setVar( 'wgDBmwschema', 'dbo' );
+ }
+
+ // User box
+ $this->setVarsFromRequest( array(
+ '_InstallUser',
+ '_InstallPassword',
+ '_InstallWindowsAuthentication'
+ ) );
+
+ // Try to connect
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ /**
+ * @var $conn DatabaseBase
+ */
+ $conn = $status->value;
+
+ // Check version
+ $version = $conn->getServerVersion();
+ if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
+ return Status::newFatal( 'config-mssql-old', $this->minimumVersion, $version );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @return Status
+ */
+ public function openConnection() {
+ global $wgDBWindowsAuthentication;
+ $status = Status::newGood();
+ $user = $this->getVar( '_InstallUser' );
+ $password = $this->getVar( '_InstallPassword' );
+
+ if ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth' ) {
+ // Use Windows authentication for this connection
+ $wgDBWindowsAuthentication = true;
+ } else {
+ $wgDBWindowsAuthentication = false;
+ }
+
+ try {
+ $db = DatabaseBase::factory( 'mssql', array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $user,
+ 'password' => $password,
+ 'dbname' => false,
+ 'flags' => 0,
+ 'schema' => $this->getVar( 'wgDBmwschema' ),
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
+ $db->prepareStatements( false );
+ $db->scrollableCursor( false );
+ $status->value = $db;
+ } catch ( DBConnectionError $e ) {
+ $status->fatal( 'config-connection-error', $e->getMessage() );
+ }
+
+ return $status;
+ }
+
+ public function preUpgrade() {
+ global $wgDBuser, $wgDBpassword;
+
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ $this->parent->showStatusError( $status );
+
+ return;
+ }
+ /**
+ * @var $conn DatabaseBase
+ */
+ $conn = $status->value;
+ $conn->selectDB( $this->getVar( 'wgDBname' ) );
+
+ # Normal user and password are selected after this step, so for now
+ # just copy these two
+ $wgDBuser = $this->getVar( '_InstallUser' );
+ $wgDBpassword = $this->getVar( '_InstallPassword' );
+ }
+
+ /**
+ * Return true if the install user can create accounts
+ *
+ * @return bool
+ */
+ public function canCreateAccounts() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return false;
+ }
+ /** @var $conn DatabaseBase */
+ $conn = $status->value;
+
+ // We need the server-level ALTER ANY LOGIN permission to create new accounts
+ $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( NULL, 'SERVER' )" );
+ $serverPrivs = array(
+ 'ALTER ANY LOGIN' => false,
+ 'CONTROL SERVER' => false,
+ );
+
+ foreach ( $res as $row ) {
+ $serverPrivs[$row->permission_name] = true;
+ }
+
+ if ( !$serverPrivs['ALTER ANY LOGIN'] ) {
+ return false;
+ }
+
+ // Check to ensure we can grant everything needed as well
+ // We can't actually tell if we have WITH GRANT OPTION for a given permission, so we assume we do
+ // and just check for the permission
+ // http://technet.microsoft.com/en-us/library/ms178569.aspx
+ // The following array sets up which permissions imply whatever permissions we specify
+ $implied = array(
+ // schema database server
+ 'DELETE' => array( 'DELETE', 'CONTROL SERVER' ),
+ 'EXECUTE' => array( 'EXECUTE', 'CONTROL SERVER' ),
+ 'INSERT' => array( 'INSERT', 'CONTROL SERVER' ),
+ 'SELECT' => array( 'SELECT', 'CONTROL SERVER' ),
+ 'UPDATE' => array( 'UPDATE', 'CONTROL SERVER' ),
+ );
+
+ $grantOptions = array_flip( $this->webUserPrivs );
+
+ // Check for schema and db-level permissions, but only if the schema/db exists
+ $schemaPrivs = $dbPrivs = array(
+ 'DELETE' => false,
+ 'EXECUTE' => false,
+ 'INSERT' => false,
+ 'SELECT' => false,
+ 'UPDATE' => false,
+ );
+
+ $dbPrivs['ALTER ANY USER'] = false;
+
+ if ( $this->databaseExists( $this->getVar( 'wgDBname' ) ) ) {
+ $conn->selectDB( $this->getVar( 'wgDBname' ) );
+ $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( NULL, 'DATABASE' )" );
+
+ foreach ( $res as $row ) {
+ $dbPrivs[$row->permission_name] = true;
+ }
+
+ // If the db exists, we need ALTER ANY USER privs on it to make a new user
+ if ( !$dbPrivs['ALTER ANY USER'] ) {
+ return false;
+ }
+
+ if ( $this->schemaExists( $this->getVar( 'wgDBmwschema' ) ) ) {
+ // wgDBmwschema is validated to only contain alphanumeric + underscore, so this is safe
+ $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( "
+ . "'{$this->getVar( 'wgDBmwschema' )}', 'SCHEMA' )" );
+
+ foreach ( $res as $row ) {
+ $schemaPrivs[$row->permission_name] = true;
+ }
+ }
+ }
+
+ // Now check all the grants we'll need to be doing to see if we can
+ foreach ( $this->webUserPrivs as $permission ) {
+ if ( ( isset( $schemaPrivs[$permission] ) && $schemaPrivs[$permission] )
+ || ( isset( $dbPrivs[$implied[$permission][0]] )
+ && $dbPrivs[$implied[$permission][0]] )
+ || ( isset( $serverPrivs[$implied[$permission][1]] )
+ && $serverPrivs[$implied[$permission][1]] )
+ ) {
+ unset( $grantOptions[$permission] );
+ }
+ }
+
+ if ( count( $grantOptions ) ) {
+ // Can't grant everything
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSettingsForm() {
+ if ( $this->canCreateAccounts() ) {
+ $noCreateMsg = false;
+ } else {
+ $noCreateMsg = 'config-db-web-no-create-privs';
+ }
+
+ $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
+ $displayStyle = $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth'
+ ? 'display: none'
+ : '';
+ $s = Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', array(), wfMessage( 'config-db-web-account' )->text() ) .
+ $this->getCheckBox(
+ '_SameAccount', 'config-db-web-account-same',
+ array( 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' )
+ ) .
+ Html::openElement( 'div', array( 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ) ) .
+ $this->getRadioSet( array(
+ 'var' => '_WebWindowsAuthentication',
+ 'label' => 'config-mssql-auth',
+ 'itemLabelPrefix' => 'config-mssql-',
+ 'values' => array( 'sqlauth', 'windowsauth' ),
+ 'itemAttribs' => array(
+ 'sqlauth' => array(
+ 'class' => 'showHideRadio',
+ 'rel' => 'dbCredentialBox',
+ ),
+ 'windowsauth' => array(
+ 'class' => 'hideShowRadio',
+ 'rel' => 'dbCredentialBox',
+ )
+ ),
+ 'help' => $this->parent->getHelpBox( 'config-mssql-web-auth' )
+ ) ) .
+ Html::openElement( 'div', array( 'id' => 'dbCredentialBox', 'style' => $displayStyle ) ) .
+ $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
+ $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
+ Html::closeElement( 'div' );
+
+ if ( $noCreateMsg ) {
+ $s .= $this->parent->getWarningBox( wfMessage( $noCreateMsg )->plain() );
+ } else {
+ $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
+ }
+
+ $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
+
+ return $s;
+ }
+
+ /**
+ * @return Status
+ */
+ public function submitSettingsForm() {
+ $this->setVarsFromRequest( array(
+ 'wgDBuser',
+ 'wgDBpassword',
+ '_SameAccount',
+ '_CreateDBAccount',
+ '_WebWindowsAuthentication'
+ ) );
+
+ if ( $this->getVar( '_SameAccount' ) ) {
+ $this->setVar( '_WebWindowsAuthentication', $this->getVar( '_InstallWindowsAuthentication' ) );
+ $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
+ $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
+ }
+
+ if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
+ $this->setVar( 'wgDBuser', '' );
+ $this->setVar( 'wgDBpassword', '' );
+ $this->setVar( 'wgDBWindowsAuthentication', true );
+ } else {
+ $this->setVar( 'wgDBWindowsAuthentication', false );
+ }
+
+ if ( $this->getVar( '_CreateDBAccount' )
+ && $this->getVar( '_WebWindowsAuthentication' ) == 'sqlauth'
+ && strval( $this->getVar( 'wgDBpassword' ) ) == ''
+ ) {
+ return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
+ }
+
+ // Validate the create checkbox
+ $canCreate = $this->canCreateAccounts();
+ if ( !$canCreate ) {
+ $this->setVar( '_CreateDBAccount', false );
+ $create = false;
+ } else {
+ $create = $this->getVar( '_CreateDBAccount' );
+ }
+
+ if ( !$create ) {
+ // Test the web account
+ $user = $this->getVar( 'wgDBuser' );
+ $password = $this->getVar( 'wgDBpassword' );
+
+ if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
+ $user = 'windowsauth';
+ $password = 'windowsauth';
+ }
+
+ try {
+ DatabaseBase::factory( 'mssql', array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $user,
+ 'password' => $password,
+ 'dbname' => false,
+ 'flags' => 0,
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' ),
+ 'schema' => $this->getVar( 'wgDBmwschema' ),
+ ) );
+ } catch ( DBConnectionError $e ) {
+ return Status::newFatal( 'config-connection-error', $e->getMessage() );
+ }
+ }
+
+ return Status::newGood();
+ }
+
+ public function preInstall() {
+ # Add our user callback to installSteps, right before the tables are created.
+ $callback = array(
+ 'name' => 'user',
+ 'callback' => array( $this, 'setupUser' ),
+ );
+ $this->parent->addInstallStep( $callback, 'tables' );
+ }
+
+ /**
+ * @return Status
+ */
+ public function setupDatabase() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ /** @var DatabaseBase $conn */
+ $conn = $status->value;
+ $dbName = $this->getVar( 'wgDBname' );
+ $schemaName = $this->getVar( 'wgDBmwschema' );
+ if ( !$this->databaseExists( $dbName ) ) {
+ $conn->query(
+ "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ),
+ __METHOD__
+ );
+ $conn->selectDB( $dbName );
+ if ( !$this->schemaExists( $schemaName ) ) {
+ $conn->query(
+ "CREATE SCHEMA " . $conn->addIdentifierQuotes( $schemaName ),
+ __METHOD__
+ );
+ }
+ if ( !$this->catalogExists( $schemaName ) ) {
+ $conn->query(
+ "CREATE FULLTEXT CATALOG " . $conn->addIdentifierQuotes( $schemaName ),
+ __METHOD__
+ );
+ }
+ }
+ $this->setupSchemaVars();
+
+ return $status;
+ }
+
+ /**
+ * @return Status
+ */
+ public function setupUser() {
+ $dbUser = $this->getVar( 'wgDBuser' );
+ if ( $dbUser == $this->getVar( '_InstallUser' )
+ || ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth'
+ && $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) ) {
+ return Status::newGood();
+ }
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $this->setupSchemaVars();
+ $dbName = $this->getVar( 'wgDBname' );
+ $this->db->selectDB( $dbName );
+ $server = $this->getVar( 'wgDBserver' );
+ $password = $this->getVar( 'wgDBpassword' );
+ $schemaName = $this->getVar( 'wgDBmwschema' );
+
+ if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
+ $dbUser = 'windowsauth';
+ $password = 'windowsauth';
+ }
+
+ if ( $this->getVar( '_CreateDBAccount' ) ) {
+ $tryToCreate = true;
+ } else {
+ $tryToCreate = false;
+ }
+
+ $escUser = $this->db->addIdentifierQuotes( $dbUser );
+ $escDb = $this->db->addIdentifierQuotes( $dbName );
+ $escSchema = $this->db->addIdentifierQuotes( $schemaName );
+ $grantableNames = array();
+ if ( $tryToCreate ) {
+ $escPass = $this->db->addQuotes( $password );
+
+ if ( !$this->loginExists( $dbUser ) ) {
+ try {
+ $this->db->begin();
+ $this->db->selectDB( 'master' );
+ $logintype = $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth'
+ ? 'FROM WINDOWS'
+ : "WITH PASSWORD = $escPass";
+ $this->db->query( "CREATE LOGIN $escUser $logintype" );
+ $this->db->selectDB( $dbName );
+ $this->db->query( "CREATE USER $escUser FOR LOGIN $escUser WITH DEFAULT_SCHEMA = $escSchema" );
+ $this->db->commit();
+ $grantableNames[] = $dbUser;
+ } catch ( DBQueryError $dqe ) {
+ $this->db->rollback();
+ $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() );
+ }
+ } elseif ( !$this->userExists( $dbUser ) ) {
+ try {
+ $this->db->begin();
+ $this->db->selectDB( $dbName );
+ $this->db->query( "CREATE USER $escUser FOR LOGIN $escUser WITH DEFAULT_SCHEMA = $escSchema" );
+ $this->db->commit();
+ $grantableNames[] = $dbUser;
+ } catch ( DBQueryError $dqe ) {
+ $this->db->rollback();
+ $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() );
+ }
+ } else {
+ $status->warning( 'config-install-user-alreadyexists', $dbUser );
+ $grantableNames[] = $dbUser;
+ }
+ }
+
+ // Try to grant to all the users we know exist or we were able to create
+ $this->db->selectDB( $dbName );
+ foreach ( $grantableNames as $name ) {
+ try {
+ // First try to grant full permissions
+ $fullPrivArr = array(
+ 'BACKUP DATABASE', 'BACKUP LOG', 'CREATE FUNCTION', 'CREATE PROCEDURE',
+ 'CREATE TABLE', 'CREATE VIEW', 'CREATE FULLTEXT CATALOG', 'SHOWPLAN'
+ );
+ $fullPrivList = implode( ', ', $fullPrivArr );
+ $this->db->begin();
+ $this->db->query( "GRANT $fullPrivList ON DATABASE :: $escDb TO $escUser", __METHOD__ );
+ $this->db->query( "GRANT CONTROL ON SCHEMA :: $escSchema TO $escUser", __METHOD__ );
+ $this->db->commit();
+ } catch ( DBQueryError $dqe ) {
+ // If that fails, try to grant the limited subset specified in $this->webUserPrivs
+ try {
+ $privList = implode( ', ', $this->webUserPrivs );
+ $this->db->rollback();
+ $this->db->begin();
+ $this->db->query( "GRANT $privList ON SCHEMA :: $escSchema TO $escUser", __METHOD__ );
+ $this->db->commit();
+ } catch ( DBQueryError $dqe ) {
+ $this->db->rollback();
+ $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getText() );
+ }
+ // Also try to grant SHOWPLAN on the db, but don't fail if we can't
+ // (just makes a couple things in mediawiki run slower since
+ // we have to run SELECT COUNT(*) instead of getting the query plan)
+ try {
+ $this->db->query( "GRANT SHOWPLAN ON DATABASE :: $escDb TO $escUser", __METHOD__ );
+ } catch ( DBQueryError $dqe ) {
+ }
+ }
+ }
+
+ return $status;
+ }
+
+ public function createTables() {
+ $status = parent::createTables();
+
+ // Do last-minute stuff like fulltext indexes (since they can't be inside a transaction)
+ if ( $status->isOk() ) {
+ $searchindex = $this->db->tableName( 'searchindex' );
+ $schema = $this->db->addIdentifierQuotes( $this->getVar( 'wgDBmwschema' ) );
+ try {
+ $this->db->query( "CREATE FULLTEXT INDEX ON $searchindex (si_title, si_text) "
+ . "KEY INDEX si_page ON $schema" );
+ } catch ( DBQueryError $dqe ) {
+ $status->fatal( 'config-install-tables-failed', $dqe->getText() );
+ }
+ }
+
+ return $status;
+ }
+
+ public function getGlobalDefaults() {
+ // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
+ // the use of a schema, so we need to set it here
+ return array(
+ 'wgDBmwschema' => 'mediawiki',
+ );
+ }
+
+ /**
+ * Try to see if the login exists
+ * @param string $user Username to check
+ * @return bool
+ */
+ private function loginExists( $user ) {
+ $res = $this->db->selectField( 'sys.sql_logins', 1, array( 'name' => $user ) );
+ return (bool)$res;
+ }
+
+ /**
+ * Try to see if the user account exists
+ * We assume we already have the appropriate database selected
+ * @param string $user Username to check
+ * @return bool
+ */
+ private function userExists( $user ) {
+ $res = $this->db->selectField( 'sys.sysusers', 1, array( 'name' => $user ) );
+ return (bool)$res;
+ }
+
+ /**
+ * Try to see if a given database exists
+ * @param string $dbName Database name to check
+ * @return bool
+ */
+ private function databaseExists( $dbName ) {
+ $res = $this->db->selectField( 'sys.databases', 1, array( 'name' => $dbName ) );
+ return (bool)$res;
+ }
+
+ /**
+ * Try to see if a given schema exists
+ * We assume we already have the appropriate database selected
+ * @param string $schemaName Schema name to check
+ * @return bool
+ */
+ private function schemaExists( $schemaName ) {
+ $res = $this->db->selectField( 'sys.schemas', 1, array( 'name' => $schemaName ) );
+ return (bool)$res;
+ }
+
+ /**
+ * Try to see if a given fulltext catalog exists
+ * We assume we already have the appropriate database selected
+ * @param string $catalogName Catalog name to check
+ * @return bool
+ */
+ private function catalogExists( $catalogName ) {
+ $res = $this->db->selectField( 'sys.fulltext_catalogs', 1, array( 'name' => $catalogName ) );
+ return (bool)$res;
+ }
+
+ /**
+ * Get variables to substitute into tables.sql and the SQL patch files.
+ *
+ * @return array
+ */
+ public function getSchemaVars() {
+ return array(
+ 'wgDBname' => $this->getVar( 'wgDBname' ),
+ 'wgDBmwschema' => $this->getVar( 'wgDBmwschema' ),
+ 'wgDBuser' => $this->getVar( 'wgDBuser' ),
+ 'wgDBpassword' => $this->getVar( 'wgDBpassword' ),
+ );
+ }
+
+ public function getLocalSettings() {
+ $schema = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBmwschema' ) );
+ $prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) );
+ $windowsauth = $this->getVar( 'wgDBWindowsAuthentication' ) ? 'true' : 'false';
+
+ return "# MSSQL specific settings
+\$wgDBWindowsAuthentication = {$windowsauth};
+\$wgDBmwschema = \"{$schema}\";
+\$wgDBprefix = \"{$prefix}\";";
+ }
+}
diff --git a/includes/installer/MssqlUpdater.php b/includes/installer/MssqlUpdater.php
new file mode 100644
index 00000000..ed11f8b6
--- /dev/null
+++ b/includes/installer/MssqlUpdater.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * Microsoft SQL Server-specific installer.
+ *
+ * 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 Deployment
+ */
+
+/**
+ * Class for setting up the MediaWiki database using Microsoft SQL Server.
+ *
+ * @ingroup Deployment
+ * @since 1.23
+ */
+
+class MssqlUpdater extends DatabaseUpdater {
+
+ /**
+ * @var DatabaseMssql
+ */
+ protected $db;
+
+ protected function getCoreUpdateList() {
+ return array(
+ // 1.23
+ array( 'addField', 'mwuser', 'user_password_expires', 'patch-user_password_expires.sql' ),
+
+ // 1.24
+ array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql'),
+ // Constraint updates
+ array( 'updateConstraints', 'category_types', 'categorylinks', 'cl_type' ),
+ array( 'updateConstraints', 'major_mime', 'filearchive', 'fa_major_mime' ),
+ array( 'updateConstraints', 'media_type', 'filearchive', 'fa_media_type' ),
+ array( 'updateConstraints', 'major_mime', 'oldimage', 'oi_major_mime' ),
+ array( 'updateConstraints', 'media_type', 'oldimage', 'oi_media_type' ),
+ array( 'updateConstraints', 'major_mime', 'image', 'img_major_mime' ),
+ array( 'updateConstraints', 'media_type', 'image', 'img_media_type' ),
+ array( 'updateConstraints', 'media_type', 'uploadstash', 'us_media_type' ),
+ // END: Constraint updates
+
+ array( 'modifyField', 'image', 'img_major_mime',
+ 'patch-img_major_mime-chemical.sql' ),
+ array( 'modifyField', 'oldimage', 'oi_major_mime',
+ 'patch-oi_major_mime-chemical.sql' ),
+ array( 'modifyField', 'filearchive', 'fa_major_mime',
+ 'patch-fa_major_mime-chemical.sql' ),
+ );
+ }
+
+ /**
+ * Drops unnamed and creates named constraints following the pattern
+ * <column>_ckc
+ *
+ * @param string $constraintType
+ * @param string $table Name of the table to which the field belongs
+ * @param string $field Name of the field to modify
+ * @return bool False if patch is skipped.
+ */
+ protected function updateConstraints( $constraintType, $table, $field ) {
+ global $wgDBname, $wgDBmwschema;
+
+ if ( !$this->doTable( $table ) ) {
+ return true;
+ }
+
+ $this->output( "...updating constraints on [$table].[$field] ..." );
+ $updateKey = "$field-$constraintType-ck";
+ if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
+ $this->output( "...$table table does not exist, skipping modify field patch.\n" );
+ return true;
+ } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
+ $this->output( "...$field field does not exist in $table table, " .
+ "skipping modify field patch.\n" );
+ return true;
+ } elseif ( $this->updateRowExists( $updateKey ) ) {
+ $this->output( "...$field in table $table already patched.\n" );
+ return true;
+ }
+
+ # After all checks passed, start the update
+ $this->insertUpdateRow( $updateKey );
+ $path = 'named_constraints.sql';
+ $constraintMap = array(
+ 'category_types' =>
+ "($field in('page', 'subcat', 'file'))",
+ 'major_mime' =>
+ "($field in('unknown', 'application', 'audio', 'image', 'text', 'video'," .
+ " 'message', 'model', 'multipart'))",
+ 'media_type' =>
+ "($field in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA'," .
+ "'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE'))"
+ );
+ $constraint = $constraintMap[$constraintType];
+
+ # and hack-in those variables that should be replaced
+ # in our template file right now
+ $this->db->setSchemaVars( array(
+ 'tableName' => $table,
+ 'fieldName' => $field,
+ 'checkConstraint' => $constraint,
+ 'wgDBname' => $wgDBname,
+ 'wgDBmwschema' => $wgDBmwschema,
+ ) );
+
+ # Full path from file name
+ $path = $this->db->patchPath( $path );
+
+ # No need for a cursor allowing result-iteration; just apply a patch
+ # store old value for re-setting later
+ $wasScrollable = $this->db->scrollableCursor( false );
+
+ # Apply patch
+ $this->db->sourceFile( $path );
+
+ # Reset DB instance to have original state
+ $this->db->setSchemaVars( false );
+ $this->db->scrollableCursor( $wasScrollable );
+
+ $this->output( "done.\n" );
+
+ return true;
+ }
+}
diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php
index 5f76972c..b82e6114 100644
--- a/includes/installer/MysqlInstaller.php
+++ b/includes/installer/MysqlInstaller.php
@@ -65,7 +65,7 @@ class MysqlInstaller extends DatabaseInstaller {
}
/**
- * @return Bool
+ * @return bool
*/
public function isCompiled() {
return self::checkExtension( 'mysql' ) || self::checkExtension( 'mysqli' );
@@ -100,7 +100,9 @@ class MysqlInstaller extends DatabaseInstaller {
public function submitConnectForm() {
// Get variables from the request.
- $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBname', 'wgDBprefix' ) );
+ $newValues = $this->setVarsFromRequest( array(
+ 'wgDBserver', 'wgDBname', 'wgDBprefix', '_InstallUser', '_InstallPassword'
+ ) );
// Validate them.
$status = Status::newGood();
@@ -115,6 +117,12 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !preg_match( '/^[a-z0-9_-]*$/i', $newValues['wgDBprefix'] ) ) {
$status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
}
+ if ( !strlen( $newValues['_InstallUser'] ) ) {
+ $status->fatal( 'config-db-username-empty' );
+ }
+ if ( !strlen( $newValues['_InstallPassword'] ) ) {
+ $status->fatal( 'config-db-password-empty', $newValues['_InstallUser'] );
+ }
if ( !$status->isOK() ) {
return $status;
}
@@ -268,9 +276,7 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return false;
}
- /**
- * @var $conn DatabaseBase
- */
+ /** @var $conn DatabaseBase */
$conn = $status->value;
// Get current account name
@@ -436,13 +442,14 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !$create ) {
// Test the web account
try {
- $db = DatabaseBase::factory( 'mysql', array(
+ DatabaseBase::factory( 'mysql', array(
'host' => $this->getVar( 'wgDBserver' ),
'user' => $this->getVar( 'wgDBuser' ),
'password' => $this->getVar( 'wgDBpassword' ),
'dbname' => false,
'flags' => 0,
- 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' )
+ ) );
} catch ( DBConnectionError $e ) {
return Status::newFatal( 'config-connection-error', $e->getMessage() );
}
@@ -479,6 +486,7 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
+ /** @var DatabaseBase $conn */
$conn = $status->value;
$dbName = $this->getVar( 'wgDBname' );
if ( !$conn->selectDB( $dbName ) ) {
@@ -516,13 +524,14 @@ class MysqlInstaller extends DatabaseInstaller {
if ( $this->getVar( '_CreateDBAccount' ) ) {
// Before we blindly try to create a user that already has access,
try { // first attempt to connect to the database
- $db = DatabaseBase::factory( 'mysql', array(
+ DatabaseBase::factory( 'mysql', array(
'host' => $server,
'user' => $dbUser,
'password' => $password,
'dbname' => false,
'flags' => 0,
- 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' )
+ ) );
$grantableNames[] = $this->buildFullUserName( $dbUser, $server );
$tryToCreate = false;
} catch ( DBConnectionError $e ) {
@@ -594,7 +603,7 @@ class MysqlInstaller extends DatabaseInstaller {
* Return a formal 'User'@'Host' username for use in queries
* @param string $name Username, quotes will be added
* @param string $host Hostname, quotes will be added
- * @return String
+ * @return string
*/
private function buildFullUserName( $name, $host ) {
return $this->db->addQuotes( $name ) . '@' . $this->db->addQuotes( $host );
@@ -605,7 +614,7 @@ class MysqlInstaller extends DatabaseInstaller {
* access to mysql.user, so false means "no" or "maybe"
* @param string $host Hostname to check
* @param string $user Username to check
- * @return boolean
+ * @return bool
*/
private function userDefinitelyExists( $host, $user ) {
try {
@@ -622,7 +631,7 @@ class MysqlInstaller extends DatabaseInstaller {
* Return any table options to be applied to all tables that don't
* override them.
*
- * @return String
+ * @return string
*/
protected function getTableOptions() {
$options = array();
diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php
index f348ecd4..990b5b03 100644
--- a/includes/installer/MysqlUpdater.php
+++ b/includes/installer/MysqlUpdater.php
@@ -28,9 +28,10 @@
* @since 1.17
*/
class MysqlUpdater extends DatabaseUpdater {
-
protected function getCoreUpdateList() {
return array(
+ array( 'disableContentHandlerUseDB' ),
+
// 1.2
array( 'addField', 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ),
array( 'addField', 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ),
@@ -38,6 +39,7 @@ class MysqlUpdater extends DatabaseUpdater {
array( 'doIndexUpdate' ),
array( 'addTable', 'hitcounter', 'patch-hitcounter.sql' ),
array( 'addField', 'recentchanges', 'rc_type', 'patch-rc_type.sql' ),
+ array( 'addIndex', 'recentchanges', 'new_name_timestamp', 'patch-rc-newindex.sql' ),
// 1.3
array( 'addField', 'user', 'user_real_name', 'patch-user-realname.sql' ),
@@ -218,6 +220,7 @@ class MysqlUpdater extends DatabaseUpdater {
array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'enableContentHandlerUseDB' ),
array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
array( 'addTable', 'sites', 'patch-sites.sql' ),
@@ -239,6 +242,30 @@ class MysqlUpdater extends DatabaseUpdater {
'patch-iwlinks-from-title-index.sql' ),
array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
+
+ // 1.23
+ array( 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ),
+ array( 'addIndex', 'logging', 'log_user_text_type_time',
+ 'patch-logging_user_text_type_time_index.sql' ),
+ array( 'addIndex', 'logging', 'log_user_text_time', 'patch-logging_user_text_time_index.sql' ),
+ array( 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ),
+ array( 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ),
+
+ // 1.24
+ array( 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ),
+ array( 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ),
+ array( 'addIndex', 'watchlist', 'wl_user_notificationtimestamp',
+ 'patch-watchlist-user-notificationtimestamp-index.sql' ),
+ array( 'addField', 'page', 'page_lang', 'patch-page_lang.sql' ),
+ array( 'addField', 'pagelinks', 'pl_from_namespace', 'patch-pl_from_namespace.sql' ),
+ array( 'addField', 'templatelinks', 'tl_from_namespace', 'patch-tl_from_namespace.sql' ),
+ array( 'addField', 'imagelinks', 'il_from_namespace', 'patch-il_from_namespace.sql' ),
+ array( 'modifyField', 'image', 'img_major_mime',
+ 'patch-img_major_mime-chemical.sql' ),
+ array( 'modifyField', 'oldimage', 'oi_major_mime',
+ 'patch-oi_major_mime-chemical.sql' ),
+ array( 'modifyField', 'filearchive', 'fa_major_mime',
+ 'patch-fa_major_mime-chemical.sql' ),
);
}
@@ -246,15 +273,17 @@ class MysqlUpdater extends DatabaseUpdater {
* 1.4 betas were missing the 'binary' marker from logging.log_title,
* which causes a collation mismatch error on joins in MySQL 4.1.
*
- * @param string $table table name
- * @param string $field field name to check
- * @param string $patchFile path to the patch to correct the field
+ * @param string $table Table name
+ * @param string $field Field name to check
+ * @param string $patchFile Path to the patch to correct the field
+ * @return bool
*/
protected function checkBin( $table, $field, $patchFile ) {
if ( !$this->doTable( $table ) ) {
return true;
}
+ /** @var MySQLField $fieldInfo */
$fieldInfo = $this->db->fieldInfo( $table, $field );
if ( $fieldInfo->isBinary() ) {
$this->output( "...$table table has correct $field encoding.\n" );
@@ -266,10 +295,10 @@ class MysqlUpdater extends DatabaseUpdater {
/**
* Check whether an index contain a field
*
- * @param string $table table name
- * @param string $index index name to check
- * @param string $field field that should be in the index
- * @return Boolean
+ * @param string $table Table name
+ * @param string $index Index name to check
+ * @param string $field Field that should be in the index
+ * @return bool
*/
protected function indexHasField( $table, $index, $field ) {
if ( !$this->doTable( $table ) ) {
@@ -382,6 +411,16 @@ class MysqlUpdater extends DatabaseUpdater {
'wl_notificationtimestamp' => 'wl_notificationtimestamp'
), array( 'NOT (wl_namespace & 1)' ), __METHOD__, 'IGNORE' );
$this->output( "done.\n" );
+
+ $this->output( "Adding missing watchlist subject page rows... " );
+ $this->db->insertSelect( 'watchlist', 'watchlist',
+ array(
+ 'wl_user' => 'wl_user',
+ 'wl_namespace' => 'wl_namespace & ~1',
+ 'wl_title' => 'wl_title',
+ 'wl_notificationtimestamp' => 'wl_notificationtimestamp'
+ ), array( 'wl_namespace & 1' ), __METHOD__, 'IGNORE' );
+ $this->output( "done.\n" );
}
function doSchemaRestructuring() {
@@ -620,25 +659,23 @@ class MysqlUpdater extends DatabaseUpdater {
);
global $wgContLang;
- foreach ( MWNamespace::getCanonicalNamespaces() as $ns => $name ) {
+ foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
if ( $ns == 0 ) {
continue;
}
$this->output( "Cleaning up broken links for namespace $ns... " );
-
- $pagelinks = $this->db->tableName( 'pagelinks' );
- $name = $wgContLang->getNsText( $ns );
- $prefix = $this->db->strencode( $name );
- $likeprefix = str_replace( '_', '\\_', $prefix );
-
- $sql = "UPDATE $pagelinks
- SET pl_namespace=$ns,
- pl_title=TRIM(LEADING '$prefix:' FROM pl_title)
- WHERE pl_namespace=0
- AND pl_title LIKE '$likeprefix:%'";
-
- $this->db->query( $sql, __METHOD__ );
+ $this->db->update( 'pagelinks',
+ array(
+ 'pl_namespace' => $ns,
+ "pl_title = TRIM(LEADING {$this->db->addQuotes( "$name:" )} FROM pl_title)",
+ ),
+ array(
+ 'pl_namespace' => 0,
+ 'pl_title' . $this->db->buildLike( "$name:", $this->db->anyString() ),
+ ),
+ __METHOD__
+ );
$this->output( "done.\n" );
}
}
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
index 8ce74f42..9e692ea4 100644
--- a/includes/installer/OracleInstaller.php
+++ b/includes/installer/OracleInstaller.php
@@ -247,6 +247,7 @@ class OracleInstaller extends DatabaseInstaller {
return $status;
}
}
+
$this->db = $status->value;
$this->setupSchemaVars();
diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php
index ec91e57b..18468544 100644
--- a/includes/installer/OracleUpdater.php
+++ b/includes/installer/OracleUpdater.php
@@ -38,6 +38,8 @@ class OracleUpdater extends DatabaseUpdater {
protected function getCoreUpdateList() {
return array(
+ array( 'disableContentHandlerUseDB' ),
+
// 1.17
array( 'doNamespaceDefaults' ),
array( 'doFKRenameDeferr' ),
@@ -47,13 +49,13 @@ class OracleUpdater extends DatabaseUpdater {
array( 'doRemoveNotNullEmptyDefaults' ),
array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ),
- //1.18
+ // 1.18
array( 'addIndex', 'user', 'i02', 'patch-user_email_index.sql' ),
array( 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ),
array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
array( 'doRecentchangesFK2Cascade' ),
- //1.19
+ // 1.19
array( 'addIndex', 'logging', 'i05', 'patch-logging_type_action_index.sql' ),
array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1_field.sql' ),
array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1_field.sql' ),
@@ -64,12 +66,12 @@ class OracleUpdater extends DatabaseUpdater {
array( 'addIndex', 'job', 'i02', 'patch-job_timestamp_index.sql' ),
array( 'doPageRestrictionsPKUKFix' ),
- //1.20
+ // 1.20
array( 'addIndex', 'ipblocks', 'i05', 'patch-ipblocks_i05_index.sql' ),
array( 'addIndex', 'revision', 'i05', 'patch-revision_i05_index.sql' ),
array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ),
- //1.21
+ // 1.21
array( 'addField', 'revision', 'rev_content_format',
'patch-revision-rev_content_format.sql' ),
array( 'addField', 'revision', 'rev_content_model',
@@ -79,6 +81,7 @@ class OracleUpdater extends DatabaseUpdater {
array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'enableContentHandlerUseDB' ),
array( 'dropField', 'site_stats', 'ss_admins', 'patch-ss_admins.sql' ),
array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
array( 'addTable', 'sites', 'patch-sites.sql' ),
@@ -90,6 +93,16 @@ class OracleUpdater extends DatabaseUpdater {
array( 'modifyField', 'user_former_groups', 'ufg_group',
'patch-ufg_group-length-increase-255.sql' ),
+ // 1.23
+ array( 'addIndex', 'logging', 'i06', 'patch-logging_user_text_type_time_index.sql' ),
+ array( 'addIndex', 'logging', 'i07', 'patch-logging_user_text_time_index.sql' ),
+ array( 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ),
+ array( 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ),
+ array( 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ),
+
+ // 1.24
+ array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql' ),
+
// KEEP THIS AT THE BOTTOM!!
array( 'doRebuildDuplicateFunction' ),
@@ -251,7 +264,7 @@ class OracleUpdater extends DatabaseUpdater {
/**
* Overload: after this action field info table has to be rebuilt
*
- * @param $what array
+ * @param array $what
*/
public function doUpdates( $what = array( 'core', 'extensions', 'purge', 'stats' ) ) {
parent::doUpdates( $what );
diff --git a/includes/installer/PhpBugTests.php b/includes/installer/PhpBugTests.php
index 54712644..0460134a 100644
--- a/includes/installer/PhpBugTests.php
+++ b/includes/installer/PhpBugTests.php
@@ -45,29 +45,3 @@ class PhpXmlBugTester {
$this->parsedData .= $data;
}
}
-
-/**
- * Test for PHP bug #50394 (PHP 5.3.x conversion to null only, not 5.2.x)
- * @see http://bugs.php.net/bug.php?id=50394
- * @ingroup PHPBugTests
- */
-class PhpRefCallBugTester {
- public $ok = false;
-
- function __call( $name, $args ) {
- $old = error_reporting( E_ALL & ~E_WARNING );
- call_user_func_array( array( $this, 'checkForBrokenRef' ), $args );
- error_reporting( $old );
- }
-
- function checkForBrokenRef( &$var ) {
- if ( $var ) {
- $this->ok = true;
- }
- }
-
- function execute() {
- $var = true;
- call_user_func_array( array( $this, 'foo' ), array( &$var ) );
- }
-}
diff --git a/includes/installer/PostgresInstaller.php b/includes/installer/PostgresInstaller.php
index 2cf41564..c30a989e 100644
--- a/includes/installer/PostgresInstaller.php
+++ b/includes/installer/PostgresInstaller.php
@@ -83,8 +83,10 @@ class PostgresInstaller extends DatabaseInstaller {
function submitConnectForm() {
// Get variables from the request
- $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBport',
- 'wgDBname', 'wgDBmwschema' ) );
+ $newValues = $this->setVarsFromRequest( array(
+ 'wgDBserver', 'wgDBport', 'wgDBname', 'wgDBmwschema',
+ '_InstallUser', '_InstallPassword'
+ ) );
// Validate them
$status = Status::newGood();
@@ -96,6 +98,12 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
$status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
}
+ if ( !strlen( $newValues['_InstallUser'] ) ) {
+ $status->fatal( 'config-db-username-empty' );
+ }
+ if ( !strlen( $newValues['_InstallPassword'] ) ) {
+ $status->fatal( 'config-db-password-empty', $newValues['_InstallUser'] );
+ }
// Submit user box
if ( $status->isOK() ) {
@@ -144,17 +152,18 @@ class PostgresInstaller extends DatabaseInstaller {
* @param string $user User name
* @param string $password Password
* @param string $dbName Database name
+ * @param string $schema Database schema
* @return Status
*/
- protected function openConnectionWithParams( $user, $password, $dbName ) {
+ protected function openConnectionWithParams( $user, $password, $dbName, $schema ) {
$status = Status::newGood();
try {
- $db = new DatabasePostgres(
- $this->getVar( 'wgDBserver' ),
- $user,
- $password,
- $dbName
- );
+ $db = DatabaseBase::factory( 'postgres', array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $user,
+ 'password' => $password,
+ 'dbname' => $dbName,
+ 'schema' => $schema ) );
$status->value = $db;
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-connection-error', $e->getMessage() );
@@ -210,8 +219,7 @@ class PostgresInstaller extends DatabaseInstaller {
* - create-tables: A connection with a role suitable for creating tables.
*
* @throws MWException
- * @return Status object. On success, a connection object will be in the
- * value member.
+ * @return Status On success, a connection object will be in the value member.
*/
protected function openPgConnection( $type ) {
switch ( $type ) {
@@ -223,7 +231,8 @@ class PostgresInstaller extends DatabaseInstaller {
return $this->openConnectionWithParams(
$this->getVar( '_InstallUser' ),
$this->getVar( '_InstallPassword' ),
- $this->getVar( 'wgDBname' ) );
+ $this->getVar( 'wgDBname' ),
+ $this->getVar( 'wgDBmwschema' ) );
case 'create-tables':
$status = $this->openPgConnection( 'create-schema' );
if ( $status->isOK() ) {
@@ -406,7 +415,7 @@ class PostgresInstaller extends DatabaseInstaller {
/**
* Recursive helper for canCreateObjectsForWebUser().
- * @param $conn DatabaseBase object
+ * @param DatabaseBase $conn
* @param int $targetMember Role ID of the member to look for
* @param int $group Role ID of the group to look for
* @param int $maxDepth Maximum recursive search depth
@@ -615,6 +624,14 @@ class PostgresInstaller extends DatabaseInstaller {
return $status;
}
+ public function getGlobalDefaults() {
+ // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
+ // the use of a schema, so we need to set it here
+ return array(
+ 'wgDBmwschema' => 'mediawiki',
+ );
+ }
+
public function setupPLpgSQL() {
// Connect as the install user, since it owns the database and so is
// the user that needs to run "CREATE LANGAUGE"
diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php
index 304c5466..9e8ee94f 100644
--- a/includes/installer/PostgresUpdater.php
+++ b/includes/installer/PostgresUpdater.php
@@ -27,7 +27,6 @@
* @ingroup Deployment
* @since 1.17
*/
-
class PostgresUpdater extends DatabaseUpdater {
/**
@@ -235,6 +234,7 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'changeNullableField', 'image', 'img_metadata', 'NOT NULL' ),
array( 'changeNullableField', 'filearchive', 'fa_metadata', 'NOT NULL' ),
array( 'changeNullableField', 'recentchanges', 'rc_cur_id', 'NULL' ),
+ array( 'changeNullableField', 'recentchanges', 'rc_cur_time', 'NULL' ),
array( 'checkOiDeleted' ),
@@ -250,6 +250,7 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgIndex', 'recentchanges', 'rc_timestamp_bot', '(rc_timestamp) WHERE rc_bot = 0' ),
array( 'addPgIndex', 'templatelinks', 'templatelinks_from', '(tl_from)' ),
array( 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ),
+ array( 'addPgIndex', 'watchlist', 'wl_user_notificationtimestamp', '(wl_user, wl_notificationtimestamp)' ),
array( 'addPgIndex', 'logging', 'logging_user_type_time',
'(log_user, log_type, log_timestamp)' ),
array( 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ),
@@ -260,6 +261,9 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgIndex', 'job', 'job_cmd_token', '(job_cmd, job_token, job_random)' ),
array( 'addPgIndex', 'job', 'job_cmd_token_id', '(job_cmd, job_token, job_id)' ),
array( 'addPgIndex', 'filearchive', 'fa_sha1', '(fa_sha1)' ),
+ array( 'addPgIndex', 'logging', 'logging_user_text_type_time',
+ '(log_user_text, log_type, log_timestamp)' ),
+ array( 'addPgIndex', 'logging', 'logging_user_text_time', '(log_user_text, log_timestamp)' ),
array( 'checkIndex', 'pagelink_unique', array(
array( 'pl_from', 'int4_ops', 'btree', 0 ),
@@ -398,6 +402,22 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addInterwikiType' ),
# end
array( 'tsearchFixes' ),
+
+ // 1.23
+ array( 'addPgField', 'recentchanges', 'rc_source', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'page', 'page_links_updated', "TIMESTAMPTZ NULL" ),
+ array( 'addPgField', 'mwuser', 'user_password_expires', 'TIMESTAMPTZ NULL' ),
+ array( 'changeFieldPurgeTable', 'l10n_cache', 'lc_value', 'bytea',
+ "replace(lc_value,'\','\\\\')::bytea" ),
+
+ // 1.24
+ array( 'addPgField', 'page_props', 'pp_sortkey', 'float NULL' ),
+ array( 'addPgIndex', 'page_props', 'pp_propname_sortkey_page',
+ '( pp_propname, pp_sortkey, pp_page ) WHERE ( pp_sortkey IS NOT NULL )' ),
+ array( 'addPgField', 'page', 'page_lang', 'TEXT default NULL' ),
+ array( 'addPgField', 'pagelinks', 'pl_from_namespace', 'INTEGER NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'templatelinks', 'tl_from_namespace', 'INTEGER NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'imagelinks', 'il_from_namespace', 'INTEGER NOT NULL DEFAULT 0' ),
);
}
@@ -664,6 +684,35 @@ END;
}
}
+ protected function changeFieldPurgeTable( $table, $field, $newtype, $default ) {
+ ## For a cache table, empty it if the field needs to be changed, because the old contents
+ ## may be corrupted. If the column is already the desired type, refrain from purging.
+ $fi = $this->db->fieldInfo( $table, $field );
+ if ( is_null( $fi ) ) {
+ $this->output( "...ERROR: expected column $table.$field to exist\n" );
+ exit( 1 );
+ }
+
+ if ( $fi->type() === $newtype ) {
+ $this->output( "...column '$table.$field' is already of type '$newtype'\n" );
+ } else {
+ $this->output( "Purging data from cache table '$table'\n" );
+ $this->db->query( "DELETE from $table" );
+ $this->output( "Changing column type of '$table.$field' from '{$fi->type()}' to '$newtype'\n" );
+ $sql = "ALTER TABLE $table ALTER $field TYPE $newtype";
+ if ( strlen( $default ) ) {
+ $res = array();
+ if ( preg_match( '/DEFAULT (.+)/', $default, $res ) ) {
+ $sqldef = "ALTER TABLE $table ALTER $field SET DEFAULT $res[1]";
+ $this->db->query( $sqldef );
+ $default = preg_replace( '/\s*DEFAULT .+/', '', $default );
+ }
+ $sql .= " USING $default";
+ }
+ $this->db->query( $sql );
+ }
+ }
+
protected function setDefault( $table, $field, $default ) {
$info = $this->db->fieldInfo( $table, $field );
diff --git a/includes/installer/SqliteInstaller.php b/includes/installer/SqliteInstaller.php
index 19218c60..351b0223 100644
--- a/includes/installer/SqliteInstaller.php
+++ b/includes/installer/SqliteInstaller.php
@@ -50,7 +50,7 @@ class SqliteInstaller extends DatabaseInstaller {
/**
*
- * @return Status:
+ * @return Status
*/
public function checkPrerequisites() {
$result = Status::newGood();
@@ -98,7 +98,7 @@ class SqliteInstaller extends DatabaseInstaller {
/**
* Safe wrapper for PHP's realpath() that fails gracefully if it's unable to canonicalize the path.
*
- * @param $path string
+ * @param string $path
*
* @return string
*/
@@ -132,8 +132,8 @@ class SqliteInstaller extends DatabaseInstaller {
}
/**
- * @param $dir
- * @param $create bool
+ * @param string $dir
+ * @param bool $create
* @return Status
*/
private static function dataDirOKmaybeCreate( $dir, $create = false ) {
@@ -255,7 +255,7 @@ class SqliteInstaller extends DatabaseInstaller {
}
/**
- * @param $status Status
+ * @param Status $status
* @return Status
*/
public function setupSearchIndex( &$status ) {
diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php
index 78ca5110..ab5ab7d7 100644
--- a/includes/installer/SqliteUpdater.php
+++ b/includes/installer/SqliteUpdater.php
@@ -31,6 +31,8 @@ class SqliteUpdater extends DatabaseUpdater {
protected function getCoreUpdateList() {
return array(
+ array( 'disableContentHandlerUseDB' ),
+
// 1.14
array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
array( 'doActiveUsersInit' ),
@@ -97,6 +99,8 @@ class SqliteUpdater extends DatabaseUpdater {
array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'enableContentHandlerUseDB' ),
+
array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
array( 'addTable', 'sites', 'patch-sites.sql' ),
@@ -111,9 +115,28 @@ class SqliteUpdater extends DatabaseUpdater {
array( 'addIndex', 'page_props', 'pp_propname_page',
'patch-page_props-propname-page-index.sql' ),
array( 'addIndex', 'image', 'img_media_mime', 'patch-img_media_mime-index.sql' ),
+
+ // 1.22
array( 'addIndex', 'iwlinks', 'iwl_prefix_from_title', 'patch-iwlinks-from-title-index.sql' ),
array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
+
+ // 1.23
+ array( 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ),
+ array( 'addIndex', 'logging', 'log_user_text_type_time',
+ 'patch-logging_user_text_type_time_index.sql' ),
+ array( 'addIndex', 'logging', 'log_user_text_time', 'patch-logging_user_text_time_index.sql' ),
+ array( 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ),
+ array( 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ),
+
+ // 1.24
+ array( 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ),
+ array( 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ),
+ array( 'addIndex', 'watchlist', 'wl_user_notificationtimestamp', 'patch-watchlist-user-notificationtimestamp-index.sql' ),
+ array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql' ),
+ array( 'addField', 'pagelinks', 'pl_from_namespace', 'patch-pl_from_namespace.sql' ),
+ array( 'addField', 'templatelinks', 'tl_from_namespace', 'patch-tl_from_namespace.sql' ),
+ array( 'addField', 'imagelinks', 'il_from_namespace', 'patch-il_from_namespace.sql' ),
);
}
diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php
index 1f8ee00a..f3dba3a7 100644
--- a/includes/installer/WebInstaller.php
+++ b/includes/installer/WebInstaller.php
@@ -44,13 +44,14 @@ class WebInstaller extends Installer {
/**
* Cached session array.
*
- * @var array
+ * @var array[]
*/
protected $session;
/**
* Captured PHP error text. Temporary.
- * @var array
+ *
+ * @var string[]
*/
protected $phpErrors;
@@ -60,8 +61,9 @@ class WebInstaller extends Installer {
* To add a new installer page:
* * Add it to this WebInstaller::$pageSequence property
* * Add a "config-page-<name>" message
- * * Add a "WebInstaller_<name>" class
- * @var array
+ * * Add a "WebInstaller<name>" class
+ *
+ * @var string[]
*/
public $pageSequence = array(
'Language',
@@ -78,7 +80,8 @@ class WebInstaller extends Installer {
/**
* Out of sequence pages, selectable by the user at any time.
- * @var array
+ *
+ * @var string[]
*/
protected $otherPages = array(
'Restart',
@@ -91,7 +94,8 @@ class WebInstaller extends Installer {
/**
* Array of pages which have declared that they have been submitted, have validated
* their input, and need no further processing.
- * @var array
+ *
+ * @var bool[]
*/
protected $happyPages;
@@ -99,24 +103,28 @@ class WebInstaller extends Installer {
* List of "skipped" pages. These are pages that will automatically continue
* to the next page on any GET request. To avoid breaking the "back" button,
* they need to be skipped during a back operation.
- * @var array
+ *
+ * @var bool[]
*/
protected $skippedPages;
/**
* Flag indicating that session data may have been lost.
+ *
* @var bool
*/
public $showSessionWarning = false;
/**
* Numeric index of the page we're on
+ *
* @var int
*/
protected $tabIndex = 1;
/**
* Name of the page we're on
+ *
* @var string
*/
protected $currentPageName;
@@ -124,7 +132,7 @@ class WebInstaller extends Installer {
/**
* Constructor.
*
- * @param $request WebRequest
+ * @param WebRequest $request
*/
public function __construct( WebRequest $request ) {
parent::__construct();
@@ -140,9 +148,9 @@ class WebInstaller extends Installer {
/**
* Main entry point.
*
- * @param array $session initial session array
+ * @param array[] $session Initial session array
*
- * @return Array: new session array
+ * @return array[] New session array
*/
public function execute( array $session ) {
$this->session = $session;
@@ -172,12 +180,9 @@ class WebInstaller extends Installer {
return $this->session;
}
- $cssDir = $this->request->getVal( 'css' );
- if ( $cssDir ) {
- $cssDir = ( $cssDir == 'rtl' ? 'rtl' : 'ltr' );
- $this->request->response()->header( 'Content-type: text/css' );
- echo $this->output->getCSS( $cssDir );
-
+ $isCSS = $this->request->getVal( 'css' );
+ if ( $isCSS ) {
+ $this->outputCss();
return $this->session;
}
@@ -326,6 +331,7 @@ class WebInstaller extends Installer {
/**
* Start the PHP session. This may be called before execute() to start the PHP session.
*
+ * @throws Exception
* @return bool
*/
public function startSession() {
@@ -336,12 +342,15 @@ class WebInstaller extends Installer {
$this->phpErrors = array();
set_error_handler( array( $this, 'errorHandler' ) );
- session_start();
+ try {
+ session_start();
+ } catch ( Exception $e ) {
+ restore_error_handler();
+ throw $e;
+ }
restore_error_handler();
if ( $this->phpErrors ) {
- $this->showError( 'config-session-error', $this->phpErrors[0] );
-
return false;
}
@@ -378,7 +387,7 @@ class WebInstaller extends Installer {
/**
* Show an error message in a box. Parameters are like wfMessage().
- * @param $msg
+ * @param string $msg
*/
public function showError( $msg /*...*/ ) {
$args = func_get_args();
@@ -390,8 +399,9 @@ class WebInstaller extends Installer {
/**
* Temporary error handler for session start debugging.
- * @param $errno
- * @param $errstr string
+ *
+ * @param int $errno Unused
+ * @param string $errstr
*/
public function errorHandler( $errno, $errstr ) {
$this->phpErrors[] = $errstr;
@@ -400,7 +410,7 @@ class WebInstaller extends Installer {
/**
* Clean up from execute()
*
- * @return array
+ * @return array[]
*/
public function finish() {
$this->output->output();
@@ -424,7 +434,8 @@ class WebInstaller extends Installer {
/**
* Get a URL for submission back to the same script.
*
- * @param $query array
+ * @param string[] $query
+ *
* @return string
*/
public function getUrl( $query = array() ) {
@@ -442,11 +453,11 @@ class WebInstaller extends Installer {
/**
* Get a WebInstallerPage by name.
*
- * @param $pageName String
+ * @param string $pageName
* @return WebInstallerPage
*/
public function getPageByName( $pageName ) {
- $pageClass = 'WebInstaller_' . $pageName;
+ $pageClass = 'WebInstaller' . $pageName;
return new $pageClass( $this );
}
@@ -454,9 +465,10 @@ class WebInstaller extends Installer {
/**
* Get a session variable.
*
- * @param $name String
- * @param $default
- * @return null
+ * @param string $name
+ * @param array $default
+ *
+ * @return array
*/
public function getSession( $name, $default = null ) {
if ( !isset( $this->session[$name] ) ) {
@@ -468,8 +480,9 @@ class WebInstaller extends Installer {
/**
* Set a session variable.
- * @param string $name key for the variable
- * @param $value Mixed
+ *
+ * @param string $name Key for the variable
+ * @param mixed $value
*/
public function setSession( $name, $value ) {
$this->session[$name] = $value;
@@ -477,6 +490,7 @@ class WebInstaller extends Installer {
/**
* Get the next tabindex attribute value.
+ *
* @return int
*/
public function nextTabIndex() {
@@ -523,7 +537,7 @@ class WebInstaller extends Installer {
/**
* Called by execute() before page output starts, to show a page list.
*
- * @param $currentPageName string
+ * @param string $currentPageName
*/
private function startPageWrapper( $currentPageName ) {
$s = "<div class=\"config-page-wrapper\">\n";
@@ -563,9 +577,9 @@ class WebInstaller extends Installer {
/**
* Get a list item for the page list.
*
- * @param $pageName string
- * @param $enabled boolean
- * @param $currentPageName string
+ * @param string $pageName
+ * @param bool $enabled
+ * @param string $currentPageName
*
* @return string
*/
@@ -630,7 +644,7 @@ class WebInstaller extends Installer {
/**
* Get HTML for an error box with an icon.
*
- * @param string $text wikitext, get this with wfMessage()->plain()
+ * @param string $text Wikitext, get this with wfMessage()->plain()
*
* @return string
*/
@@ -641,7 +655,7 @@ class WebInstaller extends Installer {
/**
* Get HTML for a warning box with an icon.
*
- * @param string $text wikitext, get this with wfMessage()->plain()
+ * @param string $text Wikitext, get this with wfMessage()->plain()
*
* @return string
*/
@@ -652,27 +666,27 @@ class WebInstaller extends Installer {
/**
* Get HTML for an info box with an icon.
*
- * @param string $text wikitext, get this with wfMessage()->plain()
- * @param string $icon icon name, file in skins/common/images
- * @param string $class additional class name to add to the wrapper div
+ * @param string $text Wikitext, get this with wfMessage()->plain()
+ * @param string|bool $icon Icon name, file in mw-config/images. Default: false
+ * @param string|bool $class Additional class name to add to the wrapper div. Default: false.
*
* @return string
*/
public function getInfoBox( $text, $icon = false, $class = false ) {
$text = $this->parse( $text, true );
$icon = ( $icon == false ) ?
- '../skins/common/images/info-32.png' :
- '../skins/common/images/' . $icon;
+ 'images/info-32.png' :
+ 'images/' . $icon;
$alt = wfMessage( 'config-information' )->text();
- return Html::infoBox( $text, $icon, $alt, $class, false );
+ return Html::infoBox( $text, $icon, $alt, $class );
}
/**
* Get small text indented help for a preceding form field.
* Parameters like wfMessage().
*
- * @param $msg
+ * @param string $msg
* @return string
*/
public function getHelpBox( $msg /*, ... */ ) {
@@ -683,15 +697,16 @@ class WebInstaller extends Installer {
$html = $this->parse( $text, true );
return "<div class=\"mw-help-field-container\">\n" .
- "<span class=\"mw-help-field-hint\">" . wfMessage( 'config-help' )->escaped() .
- "</span>\n" .
+ "<span class=\"mw-help-field-hint\" title=\"" .
+ wfMessage( 'config-help-tooltip' )->escaped() . "\">" .
+ wfMessage( 'config-help' )->escaped() . "</span>\n" .
"<span class=\"mw-help-field-data\">" . $html . "</span>\n" .
"</div>\n";
}
/**
* Output a help box.
- * @param string $msg key for wfMessage()
+ * @param string $msg Key for wfMessage()
*/
public function showHelpBox( $msg /*, ... */ ) {
$args = func_get_args();
@@ -703,7 +718,7 @@ class WebInstaller extends Installer {
* Show a short informational message.
* Output looks like a list.
*
- * @param $msg string
+ * @param string $msg
*/
public function showMessage( $msg /*, ... */ ) {
$args = func_get_args();
@@ -715,7 +730,7 @@ class WebInstaller extends Installer {
}
/**
- * @param $status Status
+ * @param Status $status
*/
public function showStatusMessage( Status $status ) {
$errors = array_merge( $status->getErrorsArray(), $status->getWarningsArray() );
@@ -728,10 +743,10 @@ class WebInstaller extends Installer {
* Label a control by wrapping a config-input div around it and putting a
* label before it.
*
- * @param $msg
- * @param $forId
- * @param $contents
- * @param $helpData string
+ * @param string $msg
+ * @param string $forId
+ * @param string $contents
+ * @param string $helpData
* @return string
*/
public function label( $msg, $forId, $contents, $helpData = "" ) {
@@ -764,7 +779,7 @@ class WebInstaller extends Installer {
/**
* Get a labelled text box to configure a variable.
*
- * @param $params Array
+ * @param mixed[] $params
* Parameters are:
* var: The variable to be configured (required)
* label: The message name for the label (required)
@@ -811,7 +826,7 @@ class WebInstaller extends Installer {
/**
* Get a labelled textarea to configure a variable
*
- * @param $params Array
+ * @param mixed[] $params
* Parameters are:
* var: The variable to be configured (required)
* label: The message name for the label (required)
@@ -860,7 +875,7 @@ class WebInstaller extends Installer {
* Get a labelled password box to configure a variable.
*
* Implements password hiding
- * @param $params Array
+ * @param mixed[] $params
* Parameters are:
* var: The variable to be configured (required)
* label: The message name for the label (required)
@@ -889,7 +904,7 @@ class WebInstaller extends Installer {
/**
* Get a labelled checkbox to configure a boolean variable.
*
- * @param $params Array
+ * @param mixed[] $params
* Parameters are:
* var: The variable to be configured (required)
* label: The message name for the label (required)
@@ -917,10 +932,8 @@ class WebInstaller extends Installer {
}
if ( isset( $params['rawtext'] ) ) {
$labelText = $params['rawtext'];
- } elseif ( isset( $params['label'] ) ) {
- $labelText = $this->parse( wfMessage( $params['label'] )->text() );
} else {
- $labelText = "";
+ $labelText = $this->parse( wfMessage( $params['label'] )->text() );
}
return "<div class=\"config-input-check\">\n" .
@@ -942,11 +955,12 @@ class WebInstaller extends Installer {
/**
* Get a set of labelled radio buttons.
*
- * @param $params Array
+ * @param mixed[] $params
* Parameters are:
* var: The variable to be configured (required)
* label: The message name for the label (required)
* itemLabelPrefix: The message name prefix for the item labels (required)
+ * itemLabels: List of message names to use for the item labels instead of itemLabelPrefix, keyed by values
* values: List of allowed values (required)
* itemAttribs: Array of attribute arrays, outer key is the value name (optional)
* commonAttribs: Attribute array applied to all items
@@ -957,23 +971,49 @@ class WebInstaller extends Installer {
* @return string
*/
public function getRadioSet( $params ) {
- if ( !isset( $params['controlName'] ) ) {
- $params['controlName'] = 'config_' . $params['var'];
- }
-
- if ( !isset( $params['value'] ) ) {
- $params['value'] = $this->getVar( $params['var'] );
- }
+ $items = $this->getRadioElements( $params );
if ( !isset( $params['label'] ) ) {
$label = '';
} else {
$label = $params['label'];
}
+
+ if ( !isset( $params['controlName'] ) ) {
+ $params['controlName'] = 'config_' . $params['var'];
+ }
+
if ( !isset( $params['help'] ) ) {
$params['help'] = "";
}
+
$s = "<ul>\n";
+ foreach ( $items as $value => $item ) {
+ $s .= "<li>$item</li>\n";
+ }
+ $s .= "</ul>\n";
+
+ return $this->label( $label, $params['controlName'], $s, $params['help'] );
+ }
+
+ /**
+ * Get a set of labelled radio buttons. You probably want to use getRadioSet(), not this.
+ *
+ * @see getRadioSet
+ *
+ * @return array
+ */
+ public function getRadioElements( $params ) {
+ if ( !isset( $params['controlName'] ) ) {
+ $params['controlName'] = 'config_' . $params['var'];
+ }
+
+ if ( !isset( $params['value'] ) ) {
+ $params['value'] = $this->getVar( $params['var'] );
+ }
+
+ $items = array();
+
foreach ( $params['values'] as $value ) {
$itemAttribs = array();
@@ -990,25 +1030,23 @@ class WebInstaller extends Installer {
$itemAttribs['id'] = $id;
$itemAttribs['tabindex'] = $this->nextTabIndex();
- $s .=
- '<li>' .
+ $items[$value] =
Xml::radio( $params['controlName'], $value, $checked, $itemAttribs ) .
'&#160;' .
Xml::tags( 'label', array( 'for' => $id ), $this->parse(
- wfMessage( $params['itemLabelPrefix'] . strtolower( $value ) )->plain()
- ) ) .
- "</li>\n";
+ isset( $params['itemLabels'] ) ?
+ wfMessage( $params['itemLabels'][$value] )->plain() :
+ wfMessage( $params['itemLabelPrefix'] . strtolower( $value ) )->plain()
+ ) );
}
- $s .= "</ul>\n";
-
- return $this->label( $label, $params['controlName'], $s, $params['help'] );
+ return $items;
}
/**
* Output an error or warning box using a Status object.
*
- * @param $status Status
+ * @param Status $status
*/
public function showStatusBox( $status ) {
if ( !$status->isGood() ) {
@@ -1029,16 +1067,20 @@ class WebInstaller extends Installer {
* Assumes that variables containing "password" in the name are (potentially
* fake) passwords.
*
- * @param $varNames Array
- * @param string $prefix the prefix added to variables to obtain form names
+ * @param string[] $varNames
+ * @param string $prefix The prefix added to variables to obtain form names
*
- * @return array
+ * @return string[]
*/
public function setVarsFromRequest( $varNames, $prefix = 'config_' ) {
$newValues = array();
foreach ( $varNames as $name ) {
- $value = trim( $this->request->getVal( $prefix . $name ) );
+ $value = $this->request->getVal( $prefix . $name );
+ // bug 30524, do not trim passwords
+ if ( stripos( $name, 'password' ) === false ) {
+ $value = trim( $value );
+ }
$newValues[$name] = $value;
if ( $value === null ) {
@@ -1059,7 +1101,8 @@ class WebInstaller extends Installer {
/**
* Helper for Installer::docLink()
*
- * @param $page
+ * @param string $page
+ *
* @return string
*/
protected function getDocUrl( $page ) {
@@ -1075,9 +1118,10 @@ class WebInstaller extends Installer {
/**
* Extension tag hook for a documentation link.
*
- * @param $linkText
- * @param $attribs
- * @param $parser
+ * @param string $linkText
+ * @param string[] $attribs
+ * @param Parser $parser Unused
+ *
* @return string
*/
public function docLink( $linkText, $attribs, $parser ) {
@@ -1091,20 +1135,17 @@ class WebInstaller extends Installer {
/**
* Helper for "Download LocalSettings" link on WebInstall_Complete
*
- * @param $text
- * @param $attribs
- * @param $parser
- * @return String Html for download link
+ * @param string $text Unused
+ * @param string[] $attribs Unused
+ * @param Parser $parser Unused
+ *
+ * @return string Html for download link
*/
public function downloadLinkHook( $text, $attribs, $parser ) {
- $img = Html::element( 'img', array(
- 'src' => '../skins/common/images/download-32.png',
- 'width' => '32',
- 'height' => '32',
- ) );
$anchor = Html::rawElement( 'a',
array( 'href' => $this->getURL( array( 'localsettings' => 1 ) ) ),
- $img . ' ' . wfMessage( 'config-download-localsettings' )->parse() );
+ wfMessage( 'config-download-localsettings' )->parse()
+ );
return Html::rawElement( 'div', array( 'class' => 'config-download-link' ), $anchor );
}
@@ -1123,8 +1164,18 @@ class WebInstaller extends Installer {
$path = $_SERVER['SCRIPT_NAME'];
}
if ( $path !== false ) {
- $uri = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
- $this->setVar( 'wgScriptPath', $uri );
+ $scriptPath = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
+ $scriptExtension = $this->getVar( 'wgScriptExtension' );
+
+ $this->setVar( 'wgScriptPath', "$scriptPath" );
+ // Update variables set from Setup.php that are derived from wgScriptPath
+ $this->setVar( 'wgScript', "$scriptPath/index$scriptExtension" );
+ $this->setVar( 'wgLoadScript', "$scriptPath/load$scriptExtension" );
+ $this->setVar( 'wgStylePath', "$scriptPath/skins" );
+ $this->setVar( 'wgLocalStylePath', "$scriptPath/skins" );
+ $this->setVar( 'wgExtensionAssetsPath', "$scriptPath/extensions" );
+ $this->setVar( 'wgUploadPath', "$scriptPath/images" );
+
} else {
$this->showError( 'config-no-uri' );
@@ -1134,7 +1185,26 @@ class WebInstaller extends Installer {
return parent::envCheckPath();
}
+ /**
+ * @return string
+ */
protected function envGetDefaultServer() {
return WebRequest::detectServer();
}
+
+ /**
+ * Output stylesheet for web installer pages
+ */
+ public function outputCss() {
+ $this->request->response()->header( 'Content-type: text/css' );
+ echo $this->output->getCSS();
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getPhpErrors() {
+ return $this->phpErrors;
+ }
+
}
diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php
index f2dc37fe..3094d557 100644
--- a/includes/installer/WebInstallerOutput.php
+++ b/includes/installer/WebInstallerOutput.php
@@ -33,6 +33,7 @@
* @since 1.17
*/
class WebInstallerOutput {
+
/**
* The WebInstaller object this WebInstallerOutput is used by.
*
@@ -42,7 +43,7 @@ class WebInstallerOutput {
/**
* Buffered contents that haven't been output yet
- * @var String
+ * @var string
*/
private $contents = '';
@@ -52,6 +53,9 @@ class WebInstallerOutput {
*/
private $headerDone = false;
+ /**
+ * @var string
+ */
public $redirectTarget;
/**
@@ -69,27 +73,39 @@ class WebInstallerOutput {
private $useShortHeader = false;
/**
- * Constructor.
- *
- * @param $parent WebInstaller
+ * @param WebInstaller $parent
*/
public function __construct( WebInstaller $parent ) {
$this->parent = $parent;
}
+ /**
+ * @param string $html
+ */
public function addHTML( $html ) {
$this->contents .= $html;
$this->flush();
}
+ /**
+ * @param string $text
+ */
public function addWikiText( $text ) {
$this->addHTML( $this->parent->parse( $text ) );
}
+ /**
+ * @param string $html
+ */
public function addHTMLNoFlush( $html ) {
$this->contents .= $html;
}
+ /**
+ * @param string $url
+ *
+ * @throws MWException
+ */
public function redirect( $url ) {
if ( $this->headerDone ) {
throw new MWException( __METHOD__ . ' called after sending headers' );
@@ -103,98 +119,69 @@ class WebInstallerOutput {
}
/**
- * Get the raw vector CSS, flipping if needed
- *
- * @todo Possibly get rid of this function and use ResourceLoader in the manner it was
- * designed to be used in, rather than just grabbing a list of filenames from it,
- * and not properly handling such details as media types in module definitions.
+ * Get the stylesheet of the MediaWiki skin.
*
- * @param string $dir 'ltr' or 'rtl'
- * @return String
+ * @return string
*/
- public function getCSS( $dir ) {
- // All CSS files these modules reference will be concatenated in sequence
- // and loaded as one file.
+ public function getCSS() {
+ global $wgStyleDirectory;
+
$moduleNames = array(
+ // See SkinTemplate::setupSkinUserCss
'mediawiki.legacy.shared',
- 'skins.vector',
- 'mediawiki.legacy.config',
+ // See Vector::setupSkinUserCss
+ 'mediawiki.skinning.interface',
);
- $prepend = '';
- $css = '';
+ if ( file_exists( "$wgStyleDirectory/Vector/Vector.php" ) ) {
+ // Force loading Vector skin if available as a fallback skin
+ // for whatever ResourceLoader wants to have as the default.
+
+ // Include instead of require, as this will work without it, it will just look bad.
+ // We need the 'global' statement for $wgResourceModules because the Vector skin adds the
+ // definitions for its RL modules there that we use implicitly below.
+
+ // @codingStandardsIgnoreStart
+ global $wgResourceModules; // This is NOT UNUSED!
+ // @codingStandardsIgnoreEnd
+
+ include_once "$wgStyleDirectory/Vector/Vector.php";
+
+ $moduleNames[] = 'skins.vector.styles';
+ }
+
+ $moduleNames[] = 'mediawiki.legacy.config';
- $cssFileNames = array();
$resourceLoader = new ResourceLoader();
+ $rlContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( array(
+ 'debug' => 'true',
+ 'lang' => $this->getLanguageCode(),
+ 'only' => 'styles',
+ ) ) );
+
+ $styles = array();
foreach ( $moduleNames as $moduleName ) {
+ /** @var ResourceLoaderFileModule $module */
$module = $resourceLoader->getModule( $moduleName );
- $cssFileNames = $module->getAllStyleFiles();
-
- wfSuppressWarnings();
- foreach ( $cssFileNames as $cssFileName ) {
- if ( !file_exists( $cssFileName ) ) {
- $prepend .= ResourceLoader::makeComment( "Unable to find $cssFileName." );
- continue;
- }
-
- if ( !is_readable( $cssFileName ) ) {
- $prepend .= ResourceLoader::makeComment( "Unable to read $cssFileName. Please check file permissions." );
- continue;
- }
-
- try {
-
- if ( preg_match( '/\.less$/', $cssFileName ) ) {
- // Run the LESS compiler for *.less files (bug 55589)
- $compiler = ResourceLoader::getLessCompiler();
- $cssFileContents = $compiler->compileFile( $cssFileName );
- } else {
- // Regular CSS file
- $cssFileContents = file_get_contents( $cssFileName );
- }
-
- if ( $cssFileContents ) {
- // Rewrite URLs, though don't bother embedding images. While static image
- // files may be cached, CSS returned by this function is definitely not.
- $cssDirName = dirname( $cssFileName );
- $css .= CSSMin::remap(
- /* source */ $cssFileContents,
- /* local */ $cssDirName,
- /* remote */ '..' . str_replace(
- array( $GLOBALS['IP'], DIRECTORY_SEPARATOR ),
- array( '', '/' ),
- $cssDirName
- ),
- /* embedData */ false
- );
- } else {
- $prepend .= ResourceLoader::makeComment( "Unable to read $cssFileName." );
- }
-
- } catch ( Exception $e ) {
- $prepend .= ResourceLoader::formatException( $e );
- }
-
- $css .= "\n";
- }
- wfRestoreWarnings();
- }
-
- $css = $prepend . $css;
- if ( $dir == 'rtl' ) {
- $css = CSSJanus::transform( $css, true );
+ // Based on: ResourceLoaderFileModule::getStyles (without the DB query)
+ $styles = array_merge( $styles, ResourceLoader::makeCombinedStyles(
+ $module->readStyleFiles(
+ $module->getStyleFiles( $rlContext ),
+ $module->getFlip( $rlContext )
+ ) ) );
}
- return $css;
+ return implode( "\n", $styles );
}
/**
- * "<link>" to index.php?css=foobar for the "<head>"
- * @return String
+ * "<link>" to index.php?css=1 for the "<head>"
+ *
+ * @return string
*/
private function getCssUrl() {
- return Html::linkedStyle( $_SERVER['PHP_SELF'] . '?css=' . $this->getDir() );
+ return Html::linkedStyle( $_SERVER['PHP_SELF'] . '?css=1' );
}
public function useShortHeader( $use = true ) {
@@ -235,7 +222,7 @@ class WebInstallerOutput {
}
/**
- * @return array
+ * @return string[]
*/
public function getHeadAttribs() {
return array(
@@ -246,6 +233,7 @@ class WebInstallerOutput {
/**
* Get whether the header has been output
+ *
* @return bool
*/
public function headerDone() {
@@ -279,14 +267,14 @@ class WebInstallerOutput {
<title><?php $this->outputTitle(); ?></title>
<?php echo $this->getCssUrl() . "\n"; ?>
<?php echo $this->getJQuery() . "\n"; ?>
- <?php echo Html::linkedScript( '../skins/common/config.js' ) . "\n"; ?>
+ <?php echo Html::linkedScript( 'config.js' ) . "\n"; ?>
</head>
<?php echo Html::openElement( 'body', array( 'class' => $this->getDir() ) ) . "\n"; ?>
<div id="mw-page-base"></div>
<div id="mw-head-base"></div>
-<div id="content">
-<div id="bodyContent">
+<div id="content" class="mw-body">
+<div id="bodyContent" class="mw-body-content">
<h1><?php $this->outputTitle(); ?></h1>
<?php
@@ -294,20 +282,18 @@ class WebInstallerOutput {
public function outputFooter() {
if ( $this->useShortHeader ) {
-?>
-</body></html>
-<?php
+ echo Html::closeElement( 'body' ) . Html::closeElement( 'html' );
+
return;
}
?>
</div></div>
-
<div id="mw-panel">
<div class="portal" id="p-logo">
- <a style="background-image: url(../skins/common/images/mediawiki.png);"
- href="http://www.mediawiki.org/"
+ <a style="background-image: url(images/installer-logo.png);"
+ href="https://www.mediawiki.org/"
title="Main Page"></a>
</div>
<div class="portal"><div class="body">
@@ -317,9 +303,8 @@ class WebInstallerOutput {
</div></div>
</div>
-</body>
-</html>
<?php
+ echo Html::closeElement( 'body' ) . Html::closeElement( 'html' );
}
public function outputShortHeader() {
@@ -331,7 +316,7 @@ class WebInstallerOutput {
<title><?php $this->outputTitle(); ?></title>
<?php echo $this->getCssUrl() . "\n"; ?>
<?php echo $this->getJQuery(); ?>
- <?php echo Html::linkedScript( '../skins/common/config.js' ); ?>
+ <?php echo Html::linkedScript( 'config.js' ); ?>
</head>
<body style="background-image: none">
@@ -343,7 +328,11 @@ class WebInstallerOutput {
echo wfMessage( 'config-title', $wgVersion )->escaped();
}
+ /**
+ * @return string
+ */
public function getJQuery() {
- return Html::linkedScript( "../resources/jquery/jquery.js" );
+ return Html::linkedScript( "../resources/lib/jquery/jquery.js" );
}
+
}
diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php
index d3b550fe..2e31e413 100644
--- a/includes/installer/WebInstallerPage.php
+++ b/includes/installer/WebInstallerPage.php
@@ -36,12 +36,13 @@ abstract class WebInstallerPage {
*/
public $parent;
+ /**
+ * @return string
+ */
abstract public function execute();
/**
- * Constructor.
- *
- * @param $parent WebInstaller
+ * @param WebInstaller $parent
*/
public function __construct( WebInstaller $parent ) {
$this->parent = $parent;
@@ -51,12 +52,16 @@ abstract class WebInstallerPage {
* Is this a slow-running page in the installer? If so, WebInstaller will
* set_time_limit(0) before calling execute(). Right now this only applies
* to Install and Upgrade pages
- * @return bool
+ *
+ * @return bool Always false in this default implementation.
*/
public function isSlow() {
return false;
}
+ /**
+ * @param string $html
+ */
public function addHTML( $html ) {
$this->parent->output->addHTML( $html );
}
@@ -124,18 +129,34 @@ abstract class WebInstallerPage {
$this->addHTML( $s );
}
+ /**
+ * @return string
+ */
public function getName() {
- return str_replace( 'WebInstaller_', '', get_class( $this ) );
+ return str_replace( 'WebInstaller', '', get_class( $this ) );
}
+ /**
+ * @return string
+ */
protected function getId() {
return array_search( $this->getName(), $this->parent->pageSequence );
}
- public function getVar( $var ) {
- return $this->parent->getVar( $var );
+ /**
+ * @param string $var
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getVar( $var, $default = null ) {
+ return $this->parent->getVar( $var, $default );
}
+ /**
+ * @param string $name
+ * @param mixed $value
+ */
public function setVar( $name, $value ) {
$this->parent->setVar( $name, $value );
}
@@ -143,7 +164,7 @@ abstract class WebInstallerPage {
/**
* Get the starting tags of a fieldset.
*
- * @param string $legend message name
+ * @param string $legend Message name
*
* @return string
*/
@@ -166,7 +187,7 @@ abstract class WebInstallerPage {
protected function startLiveBox() {
$this->addHTML(
'<div id="config-spinner" style="display:none;">' .
- '<img src="../skins/common/images/ajax-loader.gif" /></div>' .
+ '<img src="images/ajax-loader.gif" /></div>' .
'<script>jQuery( "#config-spinner" ).show();</script>' .
'<div id="config-live-log">' .
'<textarea name="LiveLog" rows="10" cols="30" readonly="readonly">'
@@ -175,17 +196,21 @@ abstract class WebInstallerPage {
}
/**
- * Opposite to startLiveBox()
+ * Opposite to WebInstallerPage::startLiveBox
*/
protected function endLiveBox() {
$this->addHTML( '</textarea></div>
<script>jQuery( "#config-spinner" ).hide()</script>' );
$this->parent->output->flush();
}
+
}
-class WebInstaller_Language extends WebInstallerPage {
+class WebInstallerLanguage extends WebInstallerPage {
+ /**
+ * @return string|null
+ */
public function execute() {
global $wgLang;
$r = $this->parent->request;
@@ -245,15 +270,18 @@ class WebInstaller_Language extends WebInstallerPage {
$this->parent->getHelpBox( 'config-wiki-language-help' ) );
$this->addHTML( $s );
$this->endForm( 'continue', false );
+
+ return null;
}
/**
* Get a "<select>" for selecting languages.
*
- * @param $name
- * @param $label
- * @param $selectedCode
- * @param $helpHtml string
+ * @param string $name
+ * @param string $label
+ * @param string $selectedCode
+ * @param string $helpHtml
+ *
* @return string
*/
public function getLanguageSelector( $name, $label, $selectedCode, $helpHtml = '' ) {
@@ -276,9 +304,14 @@ class WebInstaller_Language extends WebInstallerPage {
return $this->parent->label( $label, $name, $s );
}
+
}
-class WebInstaller_ExistingWiki extends WebInstallerPage {
+class WebInstallerExistingWiki extends WebInstallerPage {
+
+ /**
+ * @return string
+ */
public function execute() {
// If there is no LocalSettings.php, continue to the installer welcome page
$vars = Installer::getExistingLocalSettings();
@@ -367,6 +400,12 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
$this->endForm( 'continue' );
}
+ /**
+ * @param string[] $names
+ * @param mixed[] $vars
+ *
+ * @return Status
+ */
protected function importVariables( $names, $vars ) {
$status = Status::newGood();
foreach ( $names as $name ) {
@@ -381,7 +420,9 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
/**
* Initiate an upgrade of the existing database
- * @param array $vars Variables from LocalSettings.php and AdminSettings.php
+ *
+ * @param mixed[] $vars Variables from LocalSettings.php
+ *
* @return Status
*/
protected function handleExistingUpgrade( $vars ) {
@@ -427,10 +468,14 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
return $status;
}
+
}
-class WebInstaller_Welcome extends WebInstallerPage {
+class WebInstallerWelcome extends WebInstallerPage {
+ /**
+ * @return string
+ */
public function execute() {
if ( $this->parent->request->wasPosted() ) {
if ( $this->getVar( '_Environment' ) ) {
@@ -452,10 +497,14 @@ class WebInstaller_Welcome extends WebInstallerPage {
return '';
}
+
}
-class WebInstaller_DBConnect extends WebInstallerPage {
+class WebInstallerDBConnect extends WebInstallerPage {
+ /**
+ * @return string|null When string, "skip" or "continue"
+ */
public function execute() {
if ( $this->getVar( '_ExistingDBSettings' ) ) {
return 'skip';
@@ -481,9 +530,9 @@ class WebInstaller_DBConnect extends WebInstallerPage {
$defaultType = $this->getVar( 'wgDBtype' );
// Messages: config-dbsupport-mysql, config-dbsupport-postgres, config-dbsupport-oracle,
- // config-dbsupport-sqlite
+ // config-dbsupport-sqlite, config-dbsupport-mssql
$dbSupport = '';
- foreach ( $this->parent->getDBTypes() as $type ) {
+ foreach ( Installer::getDBTypes() as $type ) {
$dbSupport .= wfMessage( "config-dbsupport-$type" )->plain() . "\n";
}
$this->addHTML( $this->parent->getInfoBox(
@@ -528,8 +577,13 @@ class WebInstaller_DBConnect extends WebInstallerPage {
$this->addHTML( $this->parent->label( 'config-db-type', false, $types ) . $settings );
$this->endForm();
+
+ return null;
}
+ /**
+ * @return Status
+ */
public function submit() {
$r = $this->parent->request;
$type = $r->getVal( 'DBType' );
@@ -544,13 +598,21 @@ class WebInstaller_DBConnect extends WebInstallerPage {
return $installer->submitConnectForm();
}
+
}
-class WebInstaller_Upgrade extends WebInstallerPage {
+class WebInstallerUpgrade extends WebInstallerPage {
+
+ /**
+ * @return bool Always true.
+ */
public function isSlow() {
return true;
}
+ /**
+ * @return string|null
+ */
public function execute() {
if ( $this->getVar( '_UpgradeDone' ) ) {
// Allow regeneration of LocalSettings.php, unless we are working
@@ -602,6 +664,8 @@ class WebInstaller_Upgrade extends WebInstallerPage {
$this->addHTML( $this->parent->getInfoBox(
wfMessage( 'config-can-upgrade', $GLOBALS['wgVersion'] )->plain() ) );
$this->endForm();
+
+ return null;
}
public function showDoneMessage() {
@@ -625,10 +689,14 @@ class WebInstaller_Upgrade extends WebInstallerPage {
$this->parent->restoreLinkPopups();
$this->endForm( $regenerate ? 'regenerate' : false, false );
}
+
}
-class WebInstaller_DBSettings extends WebInstallerPage {
+class WebInstallerDBSettings extends WebInstallerPage {
+ /**
+ * @return string|null
+ */
public function execute() {
$installer = $this->parent->getDBInstaller( $this->getVar( 'wgDBtype' ) );
@@ -652,11 +720,17 @@ class WebInstaller_DBSettings extends WebInstallerPage {
$this->startForm();
$this->addHTML( $form );
$this->endForm();
+
+ return null;
}
+
}
-class WebInstaller_Name extends WebInstallerPage {
+class WebInstallerName extends WebInstallerPage {
+ /**
+ * @return string
+ */
public function execute() {
$r = $this->parent->request;
if ( $r->wasPosted() ) {
@@ -717,7 +791,7 @@ class WebInstaller_Name extends WebInstallerPage {
'label' => 'config-admin-password',
) ) .
$this->parent->getPasswordBox( array(
- 'var' => '_AdminPassword2',
+ 'var' => '_AdminPasswordConfirm',
'label' => 'config-admin-password-confirm'
) ) .
$this->parent->getTextBox( array(
@@ -750,10 +824,13 @@ class WebInstaller_Name extends WebInstallerPage {
return 'output';
}
+ /**
+ * @return bool
+ */
public function submit() {
$retVal = true;
$this->parent->setVarsFromRequest( array( 'wgSitename', '_NamespaceType',
- '_AdminName', '_AdminPassword', '_AdminPassword2', '_AdminEmail',
+ '_AdminName', '_AdminPassword', '_AdminPasswordConfirm', '_AdminEmail',
'_Subscribe', '_SkipOptional', 'wgMetaNamespace' ) );
// Validate site name
@@ -826,22 +903,24 @@ class WebInstaller_Name extends WebInstallerPage {
$msg = false;
$pwd = $this->getVar( '_AdminPassword' );
$user = User::newFromName( $cname );
- $valid = $user && $user->getPasswordValidity( $pwd );
+ if ( $user ) {
+ $valid = $user->getPasswordValidity( $pwd );
+ } else {
+ $valid = 'config-admin-name-invalid';
+ }
if ( strval( $pwd ) === '' ) {
# $user->getPasswordValidity just checks for $wgMinimalPasswordLength.
# This message is more specific and helpful.
$msg = 'config-admin-password-blank';
- } elseif ( $pwd !== $this->getVar( '_AdminPassword2' ) ) {
+ } elseif ( $pwd !== $this->getVar( '_AdminPasswordConfirm' ) ) {
$msg = 'config-admin-password-mismatch';
} elseif ( $valid !== true ) {
- # As of writing this will only catch the username being e.g. 'FOO' and
- # the password 'foo'
$msg = $valid;
}
if ( $msg !== false ) {
call_user_func_array( array( $this->parent, 'showError' ), (array)$msg );
$this->setVar( '_AdminPassword', '' );
- $this->setVar( '_AdminPassword2', '' );
+ $this->setVar( '_AdminPasswordConfirm', '' );
$retVal = false;
}
@@ -860,11 +939,17 @@ class WebInstaller_Name extends WebInstallerPage {
return $retVal;
}
+
}
-class WebInstaller_Options extends WebInstallerPage {
+class WebInstallerOptions extends WebInstallerPage {
+
+ /**
+ * @return string|null
+ */
public function execute() {
if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
+ $this->submitSkins();
return 'skip';
}
if ( $this->parent->request->wasPosted() ) {
@@ -942,30 +1027,48 @@ class WebInstaller_Options extends WebInstallerPage {
$this->getFieldSetEnd()
);
+ $skins = $this->parent->findExtensions( 'skins' );
+ $skinHtml = $this->getFieldSetStart( 'config-skins' );
+
+ if ( $skins ) {
+ $skinNames = array_map( 'strtolower', $skins );
+
+ $radioButtons = $this->parent->getRadioElements( array(
+ 'var' => 'wgDefaultSkin',
+ 'itemLabels' => array_fill_keys( $skinNames, 'config-skins-use-as-default' ),
+ 'values' => $skinNames,
+ 'value' => $this->getVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) ),
+ ) );
+
+ foreach ( $skins as $skin ) {
+ $skinHtml .=
+ '<div class="config-skins-item">' .
+ $this->parent->getCheckBox( array(
+ 'var' => "skin-$skin",
+ 'rawtext' => $skin,
+ 'value' => $this->getVar( "skin-$skin", true ), // all found skins enabled by default
+ ) ) .
+ '<div class="config-skins-use-as-default">' . $radioButtons[strtolower( $skin )] . '</div>' .
+ '</div>';
+ }
+ } else {
+ $skinHtml .= $this->parent->getWarningBox( wfMessage( 'config-skins-missing' )->plain() );
+ }
+
+ $skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) .
+ $this->getFieldSetEnd();
+ $this->addHTML( $skinHtml );
+
$extensions = $this->parent->findExtensions();
if ( $extensions ) {
$extHtml = $this->getFieldSetStart( 'config-extensions' );
- /* Force a recache, so we load extensions descriptions */
- global $wgLang;
- $lc = Language::getLocalisationCache();
- $lc->setInitialisedLanguages( array() );
- $lc->getItem( $wgLang->mCode, '' );
- LinkCache::singleton()->useDatabase( false );
-
foreach ( $extensions as $ext ) {
- if ( isset( $ext['descriptionmsg'] ) ) {
- $desc = wfMessage( $ext['descriptionmsg'] )->useDatabase( false )->parse();
- } else {
- $desc = '';
- }
$extHtml .= $this->parent->getCheckBox( array(
- 'var' => "ext-{$ext['name']}",
- 'rawtext' => "<b>{$ext['name']}</b>: " .
- $desc,
+ 'var' => "ext-$ext",
+ 'rawtext' => $ext,
) );
-
}
$extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
@@ -980,10 +1083,6 @@ class WebInstaller_Options extends WebInstallerPage {
$this->getVar( 'wgDeletedDirectory' )
)
);
- // If we're using the default, let the user set it relative to $wgScriptPath
- $curLogo = $this->getVar( 'wgLogo' );
- $logoString = ( $curLogo == "/wiki/skins/common/images/wiki.png" ) ?
- '$wgStylePath/common/images/wiki.png' : $curLogo;
$uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
$this->addHTML(
@@ -1005,7 +1104,6 @@ class WebInstaller_Options extends WebInstallerPage {
'</div>' .
$this->parent->getTextBox( array(
'var' => 'wgLogo',
- 'value' => $logoString,
'label' => 'config-logo',
'attribs' => array( 'dir' => 'ltr' ),
'help' => $this->parent->getHelpBox( 'config-logo-help' )
@@ -1060,6 +1158,8 @@ class WebInstaller_Options extends WebInstallerPage {
$this->getFieldSetEnd()
);
$this->endForm();
+
+ return null;
}
/**
@@ -1076,7 +1176,7 @@ class WebInstaller_Options extends WebInstallerPage {
'config_wgRightsIcon' => '[license_button]',
) );
$styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
- '/skins/common/config-cc.css';
+ '/mw-config/config-cc.css';
$iframeUrl = 'http://creativecommons.org/license/?' .
wfArrayToCgi( array(
'partner' => 'MediaWiki',
@@ -1088,6 +1188,9 @@ class WebInstaller_Options extends WebInstallerPage {
return $iframeUrl;
}
+ /**
+ * @return string
+ */
public function getCCChooser() {
$iframeAttribs = array(
'class' => 'config-cc-iframe',
@@ -1109,6 +1212,9 @@ class WebInstaller_Options extends WebInstallerPage {
"</div>\n";
}
+ /**
+ * @return string
+ */
public function getCCDoneBox() {
$js = "parent.document.getElementById('config-cc-wrapper').style.height = '$1';";
// If you change this height, also change it in config.css
@@ -1148,15 +1254,37 @@ class WebInstaller_Options extends WebInstallerPage {
$this->addHTML( $this->getCCDoneBox() );
}
+ /**
+ * If the user skips this installer page, we still need to set up the default skins, but ignore
+ * everything else.
+ *
+ * @return bool
+ */
+ public function submitSkins() {
+ $skins = $this->parent->findExtensions( 'skins' );
+ $this->parent->setVar( '_Skins', $skins );
+
+ if ( $skins ) {
+ $skinNames = array_map( 'strtolower', $skins );
+ $this->parent->setVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
public function submit() {
$this->parent->setVarsFromRequest( array( '_RightsProfile', '_LicenseCode',
'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads', 'wgLogo',
'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
'wgEmailAuthentication', 'wgMainCacheType', '_MemCachedServers',
- 'wgUseInstantCommons' ) );
+ 'wgUseInstantCommons', 'wgDefaultSkin' ) );
- if ( !in_array( $this->getVar( '_RightsProfile' ),
- array_keys( $this->parent->rightsProfiles ) )
+ $retVal = true;
+
+ if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles )
) {
reset( $this->parent->rightsProfiles );
$this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
@@ -1166,10 +1294,9 @@ class WebInstaller_Options extends WebInstallerPage {
if ( $code == 'cc-choose' ) {
if ( !$this->getVar( '_CCDone' ) ) {
$this->parent->showError( 'config-cc-not-chosen' );
-
- return false;
+ $retVal = false;
}
- } elseif ( in_array( $code, array_keys( $this->parent->licenses ) ) ) {
+ } elseif ( array_key_exists( $code, $this->parent->licenses ) ) {
// Messages:
// config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
// config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none,
@@ -1188,16 +1315,33 @@ class WebInstaller_Options extends WebInstallerPage {
$this->setVar( 'wgRightsIcon', '' );
}
- $extsAvailable = array_map(
- function( $e ) {
- if( isset( $e['name'] ) ) {
- return $e['name'];
- }
- }, $this->parent->findExtensions() );
+ $skinsAvailable = $this->parent->findExtensions( 'skins' );
+ $skinsToInstall = array();
+ foreach ( $skinsAvailable as $skin ) {
+ $this->parent->setVarsFromRequest( array( "skin-$skin" ) );
+ if ( $this->getVar( "skin-$skin" ) ) {
+ $skinsToInstall[] = $skin;
+ }
+ }
+ $this->parent->setVar( '_Skins', $skinsToInstall );
+
+ if ( !$skinsToInstall && $skinsAvailable ) {
+ $this->parent->showError( 'config-skins-must-enable-some' );
+ $retVal = false;
+ }
+ $defaultSkin = $this->getVar( 'wgDefaultSkin' );
+ $skinsToInstallLowercase = array_map( 'strtolower', $skinsToInstall );
+ if ( $skinsToInstall && array_search( $defaultSkin, $skinsToInstallLowercase ) === false ) {
+ $this->parent->showError( 'config-skins-must-enable-default' );
+ $retVal = false;
+ }
+
+ $extsAvailable = $this->parent->findExtensions();
$extsToInstall = array();
- foreach ( $extsAvailable as $key => $ext ) {
- if ( $this->parent->request->getCheck( 'config_ext-' . $ext ) ) {
- $extsToInstall[] = $extsAvailable[ $key ];
+ foreach ( $extsAvailable as $ext ) {
+ $this->parent->setVarsFromRequest( array( "ext-$ext" ) );
+ if ( $this->getVar( "ext-$ext" ) ) {
+ $extsToInstall[] = $ext;
}
}
$this->parent->setVar( '_Extensions', $extsToInstall );
@@ -1206,8 +1350,7 @@ class WebInstaller_Options extends WebInstallerPage {
$memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
if ( !$memcServers ) {
$this->parent->showError( 'config-memcache-needservers' );
-
- return false;
+ $retVal = false;
}
foreach ( $memcServers as $server ) {
@@ -1217,29 +1360,34 @@ class WebInstaller_Options extends WebInstallerPage {
&& ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) )
) {
$this->parent->showError( 'config-memcache-badip', $memcParts[0] );
-
- return false;
+ $retVal = false;
} elseif ( !isset( $memcParts[1] ) ) {
$this->parent->showError( 'config-memcache-noport', $memcParts[0] );
-
- return false;
+ $retVal = false;
} elseif ( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
$this->parent->showError( 'config-memcache-badport', 1, 65535 );
-
- return false;
+ $retVal = false;
}
}
}
- return true;
+ return $retVal;
}
+
}
-class WebInstaller_Install extends WebInstallerPage {
+class WebInstallerInstall extends WebInstallerPage {
+
+ /**
+ * @return bool Always true.
+ */
public function isSlow() {
return true;
}
+ /**
+ * @return string|bool
+ */
public function execute() {
if ( $this->getVar( '_UpgradeDone' ) ) {
return 'skip';
@@ -1268,6 +1416,9 @@ class WebInstaller_Install extends WebInstallerPage {
return true;
}
+ /**
+ * @param string $step
+ */
public function startStage( $step ) {
// Messages: config-install-database, config-install-tables, config-install-interwiki,
// config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage
@@ -1280,8 +1431,8 @@ class WebInstaller_Install extends WebInstallerPage {
}
/**
- * @param $step
- * @param $status Status
+ * @param string $step
+ * @param Status $status
*/
public function endStage( $step, $status ) {
if ( $step == 'extension-tables' ) {
@@ -1297,9 +1448,11 @@ class WebInstaller_Install extends WebInstallerPage {
$this->parent->showStatusBox( $status );
}
}
+
}
-class WebInstaller_Complete extends WebInstallerPage {
+class WebInstallerComplete extends WebInstallerPage {
+
public function execute() {
// Pop up a dialog box, to make it difficult for the user to forget
// to download the file
@@ -1309,7 +1462,7 @@ class WebInstaller_Complete extends WebInstallerPage {
) {
// JS appears to be the only method that works consistently with IE7+
$this->addHtml( "\n<script>jQuery( function () { document.location = " .
- Xml::encodeJsVar( $lsUrl ) . "; } );</script>\n" );
+ Xml::encodeJsVar( $lsUrl ) . "; } );</script>\n" );
} else {
$this->parent->request->response()->header( "Refresh: 0;url=$lsUrl" );
}
@@ -1333,10 +1486,14 @@ class WebInstaller_Complete extends WebInstallerPage {
$this->parent->restoreLinkPopups();
$this->endForm( false, false );
}
+
}
-class WebInstaller_Restart extends WebInstallerPage {
+class WebInstallerRestart extends WebInstallerPage {
+ /**
+ * @return string|null
+ */
public function execute() {
$r = $this->parent->request;
if ( $r->wasPosted() ) {
@@ -1352,11 +1509,17 @@ class WebInstaller_Restart extends WebInstallerPage {
$s = $this->parent->getWarningBox( wfMessage( 'config-help-restart' )->plain() );
$this->addHTML( $s );
$this->endForm( 'restart' );
+
+ return null;
}
+
}
-abstract class WebInstaller_Document extends WebInstallerPage {
+abstract class WebInstallerDocument extends WebInstallerPage {
+ /**
+ * @return string
+ */
abstract protected function getFileName();
public function execute() {
@@ -1367,6 +1530,9 @@ abstract class WebInstaller_Document extends WebInstallerPage {
$this->endForm( false );
}
+ /**
+ * @return string
+ */
public function getFileContents() {
$file = __DIR__ . '/../../' . $this->getFileName();
if ( !file_exists( $file ) ) {
@@ -1375,15 +1541,26 @@ abstract class WebInstaller_Document extends WebInstallerPage {
return file_get_contents( $file );
}
+
}
-class WebInstaller_Readme extends WebInstaller_Document {
+class WebInstallerReadme extends WebInstallerDocument {
+
+ /**
+ * @return string
+ */
protected function getFileName() {
return 'README';
}
+
}
-class WebInstaller_ReleaseNotes extends WebInstaller_Document {
+class WebInstallerReleaseNotes extends WebInstallerDocument {
+
+ /**
+ * @throws MWException
+ * @return string
+ */
protected function getFileName() {
global $wgVersion;
@@ -1393,16 +1570,27 @@ class WebInstaller_ReleaseNotes extends WebInstaller_Document {
return 'RELEASE-NOTES-' . $result[1] . '.' . $result[2];
}
+
}
-class WebInstaller_UpgradeDoc extends WebInstaller_Document {
+class WebInstallerUpgradeDoc extends WebInstallerDocument {
+
+ /**
+ * @return string
+ */
protected function getFileName() {
return 'UPGRADE';
}
+
}
-class WebInstaller_Copying extends WebInstaller_Document {
+class WebInstallerCopying extends WebInstallerDocument {
+
+ /**
+ * @return string
+ */
protected function getFileName() {
return 'COPYING';
}
+
}
diff --git a/includes/installer/i18n/af.json b/includes/installer/i18n/af.json
new file mode 100644
index 00000000..92409823
--- /dev/null
+++ b/includes/installer/i18n/af.json
@@ -0,0 +1,154 @@
+{
+ "@metadata": {
+ "authors": [
+ "Naudefj",
+ "Winstonza"
+ ]
+ },
+ "config-desc": "Die Installasieprogram vir MediaWiki",
+ "config-title": "Installasie MediaWiki $1",
+ "config-information": "Inligting",
+ "config-localsettings-key": "Opgradeer-sleutel:",
+ "config-localsettings-badkey": "Die sleutel wat u verskaf het is verkeerd.",
+ "config-session-error": "Fout met begin van sessie: $1",
+ "config-no-session": "U sessiedata is verlore!\nKontroleer u php.ini en maak seker dat <code>session.save_path</code> na 'n geldige gids wys.",
+ "config-your-language": "U taal:",
+ "config-your-language-help": "Kies 'n taal om tydens die installasieproses te gebruik.",
+ "config-wiki-language": "Wiki se taal:",
+ "config-wiki-language-help": "Kies die taal waarin die wiki hoofsaaklik geskryf sal word.",
+ "config-back": "← Terug",
+ "config-continue": "Gaan voort →",
+ "config-page-language": "Taal",
+ "config-page-welcome": "Welkom by MediaWiki!",
+ "config-page-dbconnect": "Konnekteer na die databasis",
+ "config-page-upgrade": "Opgradeer 'n bestaande installasie",
+ "config-page-dbsettings": "Databasis-instellings",
+ "config-page-name": "Naam",
+ "config-page-options": "Opsies",
+ "config-page-install": "Installeer",
+ "config-page-complete": "Voltooi!",
+ "config-page-restart": "Herbegin installasie",
+ "config-page-readme": "Lees my",
+ "config-page-releasenotes": "Vrystellingsnotas",
+ "config-page-copying": "Besig met kopiëring",
+ "config-page-upgradedoc": "Besig met opgradering",
+ "config-page-existingwiki": "Bestaande wiki",
+ "config-restart": "Ja, herbegin dit",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki tuisblad]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Gebruikershandleiding] (Engelstalig)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administrateurshandleiding] (Engelstalig)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Algemene vrae] (Engelstalig)\n----\n* <doclink href=Readme>Lees my</doclink>\n* <doclink href=ReleaseNotes>Vrystellingsnotas</doclink>\n* <doclink href=Copying>Kopiëring</doclink>\n* <doclink href=UpgradeDoc>Opgradering</doclink>",
+ "config-env-good": "Die omgewing is gekontroleer.\nU kan MediaWiki installeer.",
+ "config-env-bad": "Die omgewing is gekontroleer.\nU kan nie MediaWiki installeer nie.</span>",
+ "config-env-php": "PHP $1 is tans geïnstalleer.",
+ "config-no-db": "Kon nie 'n geskikte databasisdrywer vind nie!",
+ "config-memory-raised": "PHP se <code>memory_limit</code> is $1, en is verhoog tot $2.",
+ "config-memory-bad": "'''Waarskuwing:''' PHP se <code>memory_limit</code> is $1.\nDit is waarskynlik te laag.\nDie installasie mag moontlik faal!",
+ "config-xcache": "[Http://trac.lighttpd.net/xcache/ XCache] is geïnstalleer",
+ "config-apc": "[Http://www.php.net/apc APC] is geïnstalleer",
+ "config-wincache": "[Http://www.iis.net/download/WinCacheForPhp WinCache] is geïnstalleer",
+ "config-diff3-bad": "GNU diff3 nie gevind nie.",
+ "config-db-type": "Databasistipe:",
+ "config-db-host": "Databasisbediener:",
+ "config-db-host-oracle": "Databasis-TNS:",
+ "config-db-wiki-settings": "Identifiseer hierdie wiki",
+ "config-db-name": "Databasisnaam:",
+ "config-db-name-oracle": "Databasis-skema:",
+ "config-db-install-account": "Gebruiker vir die installasie",
+ "config-db-username": "Databasis gebruikersnaam:",
+ "config-db-password": "Databasis wagwoord:",
+ "config-db-prefix": "Voorvoegsel vir databasistabelle:",
+ "config-db-charset": "Karakterstelsel vir databasis",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binêr",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-mysql-old": "U moet MySQL $1 of later gebruik.\nU gebruik tans $2.",
+ "config-db-port": "Databasispoort:",
+ "config-db-schema": "Skema vir MediaWiki",
+ "config-sqlite-dir": "Gids vir SQLite se data:",
+ "config-oracle-def-ts": "Standaard tabelruimte:",
+ "config-oracle-temp-ts": "Tydelike tabelruimte:",
+ "config-header-mysql": "MySQL-instellings",
+ "config-header-postgres": "PostgreSQL-instellings",
+ "config-header-sqlite": "SQLite-instellings",
+ "config-header-oracle": "Oracle-instellings",
+ "config-invalid-db-type": "Ongeldige databasistipe",
+ "config-missing-db-name": "U moet 'n waarde vir \"Databasnaam\" verskaf",
+ "config-sqlite-readonly": "Die lêer <code>$1</code> kan nie geskryf word nie.",
+ "config-sqlite-cant-create-db": "Kon nie databasislêer <code>$1</code> skep nie.",
+ "config-upgrade-done-no-regenerate": "Opgradering is voltooi.\n\nU kan nou [$1 u wiki gebruik].",
+ "config-regenerate": "Herskep LocalSettings.php →",
+ "config-show-table-status": "Die uitvoer van <code>SHOW TABLE STATUS</code> het gefaal!",
+ "config-db-web-account": "Databasisgebruiker vir toegang tot die web",
+ "config-mysql-engine": "Stoor-enjin:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Karakterstelsel vir databasis:",
+ "config-mysql-binary": "Binêr",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Naam van die wiki:",
+ "config-site-name-blank": "Verskaf 'n naam vir u webwerf.",
+ "config-project-namespace": "Projeknaamruimte:",
+ "config-ns-generic": "Projek",
+ "config-ns-site-name": "Dieselfde as die wiki: $1",
+ "config-ns-other": "Ander (spesifiseer)",
+ "config-ns-other-default": "MyWiki",
+ "config-admin-box": "Administrateur se gebruiker",
+ "config-admin-name": "U gebruikersnaam:",
+ "config-admin-password": "Wagwoord:",
+ "config-admin-password-confirm": "Wagwoord weer:",
+ "config-admin-password-blank": "Verskaf 'n wagwoord vir die administrateur in.",
+ "config-admin-password-mismatch": "Die twee wagwoorde wat u ingetik het stem nie ooreen nie.",
+ "config-admin-email": "E-posadres:",
+ "config-optional-continue": "Vra my meer vrae.",
+ "config-optional-skip": "Ek is reeds verveeld, installeer maar net die wiki.",
+ "config-profile-wiki": "Tradisionele wiki",
+ "config-profile-no-anon": "Skep van gebruiker is verpligtend",
+ "config-profile-fishbowl": "Slegs vir gemagtigde redaksie",
+ "config-profile-private": "Privaat wiki",
+ "config-license": "Kopiereg en lisensie:",
+ "config-license-none": "Geen lisensie in die onderskrif",
+ "config-license-pd": "Publieke Domein",
+ "config-license-cc-choose": "Kies 'n Creative Commons-lisensie",
+ "config-email-settings": "E-posinstellings",
+ "config-email-user": "Laat e-pos tussen gebruikers toe",
+ "config-email-user-help": "Stel alle gebruikers in staat om aan mekaar e-pos te stuur indien dit so in hul voorkeure aangedui is.",
+ "config-email-usertalk": "Laat kennisgewings op gebruikersbesprekingsblad toe.",
+ "config-email-auth": "Laat e-pos-verifikasie toe",
+ "config-email-sender": "E-posadres vir antwoorde:",
+ "config-upload-settings": "Oplaai van beelde en lêer",
+ "config-upload-enable": "Aktiveer die oplaai van lêers",
+ "config-upload-deleted": "Gids vir verwyderde lêers:",
+ "config-logo": "URL vir logo:",
+ "config-cc-again": "Kies weer...",
+ "config-advanced-settings": "Gevorderde konfigurasie",
+ "config-memcached-servers": "Memcached-bedieners:",
+ "config-extensions": "Uitbreidings",
+ "config-install-step-done": "gedoen",
+ "config-install-step-failed": "het misluk",
+ "config-install-extensions": "Insluitende uitbreidings",
+ "config-install-database": "Stel die databasis op",
+ "config-install-pg-schema-not-exist": "Die skema vir PostgreSQL bestaan ​​nie.",
+ "config-install-pg-schema-failed": "Die skep van tabelle het gefaal.\nMaak seker dat die gebruiker \"$1\" na skema \"$2\" mag skryf.",
+ "config-install-pg-commit": "Wysigings word gestoor",
+ "config-install-pg-plpgsql": "Kontroleer vir taal PL/pgSQL",
+ "config-pg-no-plpgsql": "U moet die taal PL/pgSQL in die database $1 installeer",
+ "config-install-user": "Besig om die databasisgebruiker te skep",
+ "config-install-user-alreadyexists": "Gebruiker \"$1\" bestaan al reeds",
+ "config-install-user-create-failed": "Skep van gebruiker \"$1\" het gefaal: $2",
+ "config-install-user-grant-failed": "Die toekenning van regte aan gebruiker \"$1\" het gefaal: $2",
+ "config-install-tables": "Skep tabelle",
+ "config-install-tables-exist": "'''Waarskuwing''': Dit lyk of MediaWiki se tabelle reeds bestaan.\nDie skep van tabelle word oorgeslaan.",
+ "config-install-tables-failed": "'''Fout''': die skep van 'n tabel het gefaal met die volgende fout: $1",
+ "config-install-interwiki": "Besig om data in die interwiki-tabel in te laai",
+ "config-install-interwiki-list": "Kon nie die lêer <code>interwiki.list</code> vind nie.",
+ "config-install-interwiki-exists": "'''Waarskuwing''': Die interwiki-tabel bevat reeds inskrywings.\nDie standaardlys word oorgeslaan.",
+ "config-install-stats": "Inisialiseer statistieke",
+ "config-install-keys": "Genereer geheime sleutel",
+ "config-install-sysop": "Skep 'n gebruiker vir die administrateur",
+ "config-install-subscribe-fail": "Kon nie vir MediaWiki-announce inskryf nie: $1",
+ "config-install-mainpage": "Skep die hoofblad met standaard inhoud",
+ "config-install-extension-tables": "Skep tabelle vir aangeskakel uitbreidings",
+ "config-install-mainpage-failed": "Kon nie die hoofblad laai nie: $1",
+ "config-install-done": "'''Veels geluk!'''\nU het MediaWiki suksesvol geïnstalleer.\n\nDie installeerder het 'n <code>LocalSettings.php</code> lêer opgestel.\nDit bevat al u instellings.\n\nU sal dit moet [$1 aflaai] en dit in die hoofgids van u wiki-installasie plaas; in dieselfde gids as index.php.\n'''Let wel''': As u dit nie nou doen nie, sal die gegenereerde konfigurasielêer nie later meer beskikbaar wees nadat u die installasie afgesluit het nie.\n\nAs dit gedoen is, kan u '''[u $2 wiki besoek]'''.",
+ "config-download-localsettings": "Laai <code>LocalSettings.php</code> af",
+ "config-help": "hulp",
+ "mainpagetext": "'''MediaWiki is suksesvol geïnstalleer.'''",
+ "mainpagedocfooter": "Konsulteer '''[//meta.wikimedia.org/wiki/Help:Contents User's Guide]''' vir inligting oor hoe om die wikisagteware te gebruik.\n\n== Hoe om te Begin ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/aln.json b/includes/installer/i18n/aln.json
new file mode 100644
index 00000000..48784bf6
--- /dev/null
+++ b/includes/installer/i18n/aln.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bresta"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki software u instalue me sukses.'''",
+ "mainpagedocfooter": "Për mâ shumë informata rreth përdorimit të softwareit wiki, ju lutem shikoni [//meta.wikimedia.org/wiki/Help:Contents dokumentacionin].\n\n\n== Për fillim ==\n\n* [//www.mediawiki.org/wiki/Help:Configuration_settings Konfigurimi i MediaWikit]\n* [//www.mediawiki.org/wiki/Help:FAQ Pyetjet e shpeshta rreth MediaWikit]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWikit]"
+}
diff --git a/includes/installer/i18n/am.json b/includes/installer/i18n/am.json
new file mode 100644
index 00000000..613778e5
--- /dev/null
+++ b/includes/installer/i18n/am.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki በትክክል ማስገባቱ ተከናወነ።'''",
+ "mainpagedocfooter": "ስለ ዊኪ ሶፍትዌር ጥቅም ለመረዳት፣ [//meta.wikimedia.org/wiki/Help:Contents User's Guide] ያንብቡ።\n\n== ለመጀመር ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/an.json b/includes/installer/i18n/an.json
new file mode 100644
index 00000000..6f2642d1
--- /dev/null
+++ b/includes/installer/i18n/an.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Juanpabl"
+ ]
+ },
+ "mainpagetext": "'''O programa MediaWiki s'ha instalato correctament.'''",
+ "mainpagedocfooter": "Consulta a [//meta.wikimedia.org/wiki/Help:Contents Guía d'usuario] ta mirar información sobre cómo usar o software wiki.\n\n== Ta prencipiar ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de caracteristicas confegurables]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Preguntas cutianas sobre MediaWiki (FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correu sobre ta anuncios de MediaWiki]"
+}
diff --git a/includes/installer/i18n/ang.json b/includes/installer/i18n/ang.json
new file mode 100644
index 00000000..82cf3705
--- /dev/null
+++ b/includes/installer/i18n/ang.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gott wisst"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki hafaþ geworden spēdige inseted.'''",
+ "mainpagedocfooter": "Þeahta þone [//meta.wikimedia.org/wiki/Help:Contents Brūcenda Lǣdend] on helpe mid þǣre nytte of ƿikisōftƿare.\n\n== Beȝinnunȝ ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Onfæstnunȝa ȝesetednessa ȝetæl]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Ȝetæl oft ascodra ascunȝa ymb MediaǷiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ǣrendunȝȝetæl nīƿra MediaǷiki forþsendnessa]"
+}
diff --git a/includes/installer/i18n/ar.json b/includes/installer/i18n/ar.json
new file mode 100644
index 00000000..7edb97c4
--- /dev/null
+++ b/includes/installer/i18n/ar.json
@@ -0,0 +1,120 @@
+{
+ "@metadata": {
+ "authors": [
+ "Meno25",
+ "Mido",
+ "OsamaK",
+ "روخو",
+ "Claw eg",
+ "Kuwaity26"
+ ]
+ },
+ "config-desc": "مثبت لميدياويكي",
+ "config-title": "تثبيت ميدياويكي $1",
+ "config-information": "معلومات",
+ "config-localsettings-upgrade": "<code>LocalSettings.php</code> قد تم كشف ملف.\nلترقية هذا التنصيب، رجاء أدخل قيمة <code>$wgUpgradeKey</code> في الصندوق أدناه.\nستجده في <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "<code>LocalSettings.php</code> قد تم كشف ملف.\nلترقية هذا التنصيب، رجاء قم بتفعيل <code>update.php</code> عوضًا عن ذلك",
+ "config-localsettings-key": "مفتاح ترقية:",
+ "config-localsettings-badkey": "المفتاح الذي قدمته غير صحيح.",
+ "config-upgrade-key-missing": "تنصيب موجود للميدياويكي قد تم اكتشافه.\nلترقية هذا التنصيب، الرجاء وضع السطر أسفل <code>LocalSettings.php</code> الخاصة بك:\n\n$1",
+ "config-localsettings-incomplete": "صفحة <code>LocalSettings.php</code> يبدو أنها ناقصة.\nالمتغير $1 لم يتم تعيينه.\nالرجاء تغيير <code>LocalSettings.php</code> لكي يتم تعيين المتغير، ثم اضغط على \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "تمت مصادفة خطأ أثناء الاتصال بقاعدة البيانات باستخدام الإعدادات المحددة في <code>LocalSettings.php</code> أو <code>AdminSettings.php</code>. الرجاء إصلاح هذه الإعدادات وحاول مجددًا.\n\n$1",
+ "config-session-error": "خطأ في بدء الجلسة: $1",
+ "config-session-expired": "يبدو أن بيانات جلستك قد انتهت صلاحيتها.\nالجلسات مكونة مدى الحياة من $1.\nيمكنك زيادة هذه بتعيين <code>session.gc_maxlifetime</code> في php.ini.\nأعد تشغيل عميل التثبيت.",
+ "config-no-session": "بيانات جلستك قد ضاعت!\nتحقق من php.ini للتأكد أن <code>session.save_path</code> تم تعيينه كدليل مناسب.",
+ "config-your-language": "لغتك:",
+ "config-your-language-help": "حدد لغة لاستخدامها أثناء عملية التثبيت.",
+ "config-wiki-language": "لغة ويكي:",
+ "config-wiki-language-help": "قم بتحديد اللغة التي ستُكتب بها الويكي غالبًا.",
+ "config-back": "→ ارجع",
+ "config-continue": "استمر ←",
+ "config-page-language": "اللغة",
+ "config-page-welcome": "مرحبًا في ميدياويكي!",
+ "config-page-dbconnect": "الاتصال بقاعدة البيانات",
+ "config-page-upgrade": "قم بترقية التثبيت الموجود",
+ "config-page-dbsettings": "إعدادات قاعدة البيانات",
+ "config-page-name": "الاسم",
+ "config-page-options": "خيارات",
+ "config-page-install": "تنصيب",
+ "config-page-complete": "اكتمل!",
+ "config-page-restart": "قم بإعادة تشغيل التثبيت",
+ "config-page-readme": "اقرأني",
+ "config-page-releasenotes": "ملاحظات الإصدار",
+ "config-page-copying": "نسخ",
+ "config-page-upgradedoc": "ترقية",
+ "config-page-existingwiki": "ويكي موجودة",
+ "config-help-restart": "هل تريد إزالة البيانات المحفوظة التي قد قمت بإدخالها وإعادة تشغيل عملية التثبيت؟",
+ "config-restart": "نعم، إعادة التشغيل",
+ "config-env-php": "بي إتش بي $1 مثبت.",
+ "config-db-type": "نوع قاعدة البيانات:",
+ "config-db-wiki-settings": "حدِّد هذا الويكي",
+ "config-db-name": "اسم قاعدة البيانات",
+ "config-db-username": "اسم مستخدم قاعدة البيانات:",
+ "config-db-password": "كلمة سر قاعدة البيانات:",
+ "config-db-port": "منفذ قاعدة البيانات:",
+ "config-db-schema": "سكيما لميدياويكي",
+ "config-type-mysql": "ماي إس كيو إل",
+ "config-type-postgres": "بوستجر إس كيو إل",
+ "config-type-sqlite": "إس كيو لايت",
+ "config-type-oracle": "أوراكل",
+ "config-header-mysql": "إعدادات MySQL",
+ "config-header-postgres": "إعدادات PostgreSQL",
+ "config-header-sqlite": "إعدادات SQLite",
+ "config-header-oracle": "إعدادات أوراكل",
+ "config-invalid-db-type": "نوع قاعدة بيانات غير صحيح",
+ "config-mysql-engine": "محرك التخزين",
+ "config-mysql-innodb": "إنو دي بي",
+ "config-mysql-myisam": "ماي إسام",
+ "config-mysql-binary": "ثنائي",
+ "config-mysql-utf8": "يو تي إف-8",
+ "config-site-name": "اسم الويكي:",
+ "config-site-name-blank": "أدخل اسم موقع.",
+ "config-project-namespace": "نطاق المشروع:",
+ "config-ns-generic": "المشروع",
+ "config-ns-site-name": "مثل اسم الويكي: $1",
+ "config-ns-other": "أخرى (حدد)",
+ "config-ns-other-default": "ماي ويكي",
+ "config-admin-box": "حساب إداري",
+ "config-admin-name": "اسم المستخدم:",
+ "config-admin-password": "كلمة السر:",
+ "config-admin-password-confirm": "كلمة المرور مرة أخرى:",
+ "config-admin-name-blank": "أدخل اسم مستخدم لإداري.",
+ "config-admin-email": "عنوان البريد الإلكتروني:",
+ "config-optional-continue": "اسألني المزيد من الأسئلة",
+ "config-optional-skip": "إنني أشعر بالملل بالفعل، فقط قم بتثبيت الويكي",
+ "config-profile": "ملف صلاحيات المستخدم:",
+ "config-profile-wiki": "افتح ويكي",
+ "config-profile-no-anon": "إنشاء الحساب مطلوب",
+ "config-profile-private": "ويكي خاص",
+ "config-license": "حقوق النسخ والترخيص:",
+ "config-license-none": "لا تذييل ترخيص",
+ "config-license-cc-by-sa": "المشاع الإبداعي النسبة للمؤلف المشاركة بالمثل",
+ "config-license-cc-by": "المشاع الإبداعي النسبة للمؤلف",
+ "config-license-cc-by-nc-sa": "المشاع الإبداعي النسبة للمؤلف غير تجاري المشاركة بالمثل",
+ "config-license-pd": "ملكية عامة",
+ "config-email-settings": "إعدادات البريد الإلكتروني",
+ "config-email-usertalk": "فعل إخطارات صفحات نقاش المستخدمين",
+ "config-email-watchlist": "تمكين إشعارات قائمة المراقبة",
+ "config-email-sender": "يرجع عنوان البريد الإلكتروني:",
+ "config-upload-settings": "الصور وتحميل الملفات",
+ "config-upload-enable": "تمكين تحميل الملفات",
+ "config-logo": "مسار الشعار:",
+ "config-cc-again": "اختر مجددًا",
+ "config-advanced-settings": "ضبط متقدم",
+ "config-extensions": "امتدادات",
+ "config-skins": "الواجهات",
+ "config-skins-use-as-default": "استخدم هذه الواجهة كافتراضية",
+ "config-install-step-done": "نفذ",
+ "config-install-step-failed": "فشل",
+ "config-install-extensions": "متضمنا الامتدادات",
+ "config-install-database": "إنشاء قاعدة البيانات",
+ "config-install-schema": "إنشاء السكيما",
+ "config-install-user": "إنشاء مستخدم قاعدة البيانات",
+ "config-install-user-alreadyexists": "المستخدم \"$1\" موجود بالفعل",
+ "config-install-user-create-failed": "إنشاء مستخدم \"$1\" فشل:$2",
+ "config-install-tables": "إنشاء الجداول",
+ "config-install-keys": "توليد المفاتيح السرية",
+ "config-help": "مساعدة",
+ "mainpagetext": "'''تم تثبيت ميدياويكي بنجاح.'''",
+ "mainpagedocfooter": "استشر [//meta.wikimedia.org/wiki/Help:Contents دليل المستخدم] لمعلومات حول استخدام برنامج الويكي.\n\n== البداية ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings قائمة إعدادات الضبط]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ أسئلة متكررة حول ميدياويكي]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce القائمة البريدية الخاصة بإصدار ميدياويكي]"
+}
diff --git a/includes/installer/i18n/arc.json b/includes/installer/i18n/arc.json
new file mode 100644
index 00000000..27ad2de7
--- /dev/null
+++ b/includes/installer/i18n/arc.json
@@ -0,0 +1,22 @@
+{
+ "@metadata": {
+ "authors": [
+ "Basharh"
+ ]
+ },
+ "config-information": "ܝܕ̈ܥܬܐ",
+ "config-your-language": "ܠܫܢܐ ܕܝܠܟ:",
+ "config-wiki-language": "ܠܫܢܐ ܕܘܝܩܝ:",
+ "config-page-language": "ܠܫܢܐ",
+ "config-page-name": "ܫܡܐ",
+ "config-page-options": "ܓܒܝܬ̈ܐ",
+ "config-page-install": "ܢܨܘܒ",
+ "config-ns-other-default": "ܘܝܩܝ ܕܝܠܝ",
+ "config-admin-box": "ܚܘܫܒܢܐ ܕܡܕܒܪܢܐ",
+ "config-admin-name": "ܫܡܐ ܕܟ܆ܛ܆ܡܦܠܚܢܐ ܕܝܠܟ:",
+ "config-admin-password": "ܡܠܬܐ ܕܥܠܠܐ:",
+ "config-admin-password-confirm": "ܡܠܬܐ ܕܥܠܠܐ ܙܒܢܬܐ ܐܚܪܬܐ:",
+ "config-admin-email": "ܡܘܢܥܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ:",
+ "config-profile-private": "ܘܝܩܝ ܦܪܨܘܦܝܐ",
+ "config-email-settings": "ܛܘܝܒ̈ܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ"
+}
diff --git a/includes/installer/i18n/ary.json b/includes/installer/i18n/ary.json
new file mode 100644
index 00000000..48aa2d6b
--- /dev/null
+++ b/includes/installer/i18n/ary.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Enzoreg",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki ṫ'instala be najaḫ.'''",
+ "mainpagedocfooter": "Ila bġiṫiw meĝlomaṫ ĥrin baċ ṫesṫeĝmlo had l-lojisyél siro ċofo [//meta.wikimedia.org/wiki/Help:Contents/fr Gid dyal l-mosṫeĥdim]\n\n== L-bdaya mĝa MediaWiki ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista dyal l-paramétraṫ dyal l-konfigurasyon]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/fr FAQ fe MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista dyal l-modakaraṫ ĝla versyonaṫ jdad dyal MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/arz.json b/includes/installer/i18n/arz.json
new file mode 100644
index 00000000..b6815b40
--- /dev/null
+++ b/includes/installer/i18n/arz.json
@@ -0,0 +1,18 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ghaly"
+ ]
+ },
+ "config-your-language": "اللغه بتاعتك:",
+ "config-your-language-help": " إختار لغة البرنامج بتاعتك:",
+ "config-wiki-language": " ويكى لانجواج :",
+ "config-back": "→ ارجع",
+ "config-continue": "استمر",
+ "config-page-language": "اللغه",
+ "config-page-welcome": "اهلا ف ميديا ويكى",
+ "config-page-name": "الاسم:",
+ "config-page-install": "تركيب",
+ "mainpagetext": "''' ميدياويكى اتنزلت بنجاح.'''",
+ "mainpagedocfooter": "اسال [//meta.wikimedia.org/wiki/Help:Contents دليل اليوزر] للمعلومات حوالين استخدام برنامج الويكى.\n\n== البداية ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings لستة اعدادات الضبط]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ أسئلة بتكرر حوالين الميدياويكى]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce لستة الايميلات بتاعة اعلانات الميدياويكى]"
+}
diff --git a/includes/installer/i18n/as.json b/includes/installer/i18n/as.json
new file mode 100644
index 00000000..142bdc8d
--- /dev/null
+++ b/includes/installer/i18n/as.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chaipau",
+ "Gitartha.bordoloi"
+ ]
+ },
+ "mainpagetext": "'''মিডিয়াৱিকি সফলভাবে ইন্সটল কৰা হ'ল ।'''",
+ "mainpagedocfooter": "ৱিকি চ'ফটৱেৰ কেনেকৈ ব্যৱহাৰ কৰিব [//meta.wikimedia.org/wiki/Help:Contents সদস্যৰ সহায়িকা] চাওঁক ।\n\n== আৰম্ভণি কৰিবলৈ ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/ast.json b/includes/installer/i18n/ast.json
new file mode 100644
index 00000000..df7184f5
--- /dev/null
+++ b/includes/installer/i18n/ast.json
@@ -0,0 +1,71 @@
+{
+ "@metadata": {
+ "authors": [
+ "Xuacu"
+ ]
+ },
+ "config-desc": "L'instalador pa 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-key": "Clave d'anovamientu:",
+ "config-localsettings-badkey": "La clave que dio 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-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-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-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-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-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-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; pue 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 prefiera) cualquier versión posterior.\n\nEsti programa distribúise cola esperanza de que pueda ser útil, pero '''ensin garantía denguna'''; nin siquiera la garantía implícita de '''comercialidá''' o '''adautación a un fin determináu'''.\nVea la Llicencia pública xeneral GNU pa más detalles.\n\nHabría de tener recibío <doclink href=Copying>una copia de la llicencia pública xeneral GNU</doclink> xunto con esti programa; sinón, escriba a la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [http://www.gnu.org/copyleft/gpl.html lléala en llinia].",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/gl Páxina principal de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guía del usuariu]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guía del alministrador]\n* [//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-php": "PHP $1 ta instaláu.",
+ "config-env-php-toolow": "PHP $1 ta instaláu.\nSicasí, MediaWiki necesita PHP $2 o superior.",
+ "config-unicode-using-utf8": "Usando utf8_normalize.so de Brion Vibber pa la normalización Unicode.",
+ "config-unicode-using-intl": "Usando la [http://pecl.php.net/intl estensión intl PECL] pa la normalización Unicode.",
+ "config-unicode-pure-php-warning": "'''Avisu:''' La [http://pecl.php.net/intl estensión intl PECL] nun ta disponible pa xestionar la normalización Unicode; volviendo a la implementación lenta en PHP puru.\nSi xestiona un sitiu con un tráficu altu, tendría de lleer una migaya sobro la [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].",
+ "config-unicode-update-warning": "'''Avisu:''' La versión instalada del envoltoriu de normalización Unicode usa una versión antigua de la biblioteca [http://site.icu-project.org/ de los proyeutos ICU].\nTendría [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations d'anovala] si ye importante pa vusté usar Unicode.",
+ "config-no-db": "¡Nun pudo alcontrase un controlador de base de datos afayadizu! Necesita instalar un controlador de base de datos pa PHP.\nTienen sofitu los tipos de base de datos siguientes: $1.\n\nSi compiló PHP vusté mesmu, reconfigúrelu con un cliente de base de datos activáu, por exemplu, usando <code>./configure --with-mysqli</code>.\nSi instaló PHP dende un paquete de Debian o Ubuntu, necesita instalar tamién,por exemplu, el paquete <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Avisu:''' tien SQLite $1, que ye inferior a la versión mínima necesaria $2. SQLite nun tará disponible.",
+ "config-no-fts3": "'''Avisu:''' SQLite ta compiláu ensin el [//sqlite.org/fts3.html módulu FTS3]; les funciones de gueta nun tarán disponibles nesti sistema.",
+ "config-register-globals": "'''Avisu: La opción de PHP <code>[http://php.net/register_globals register_globals]</code> ta activada.'''\n'''Desactívela si ye posible.'''\nMediaWiki funcionará, pero'l so sirvidor queda albentestate ente posibles vulnerabilidaes de seguridá.",
+ "config-site-name": "Nome de la wiki:",
+ "config-site-name-help": "Esto apaecerá na barra de títulos del navegador y en dellos sitios más.",
+ "config-site-name-blank": "Escriba un nome pal sitiu.",
+ "config-project-namespace": "Espaciu de nomes del proyeutu:",
+ "config-ns-generic": "Proyeutu",
+ "config-ns-site-name": "Igual que'l nome de la wiki: $1",
+ "config-ns-other": "Otru (especificar)",
+ "config-ns-other-default": "MioWiki",
+ "config-download-localsettings": "Descargar <code>LocalSettings.php</code>",
+ "config-help": "Ayuda",
+ "config-nofile": "Nun pudo atopase'l ficheru \"$1\". ¿Desaniciose?",
+ "mainpagetext": "'''MediaWiki instalóse correchamente.'''",
+ "mainpagedocfooter": "Visita la [//meta.wikimedia.org/wiki/Help:Contents Guía del usuariu] pa saber cómo usar esti software wiki.\n\n== Empecipiando ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Llista de les opciones de configuración]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ de MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de corréu de les ediciones de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Llocaliza MediaWiki na to llingua]"
+}
diff --git a/includes/installer/i18n/av.json b/includes/installer/i18n/av.json
new file mode 100644
index 00000000..0ce76fa0
--- /dev/null
+++ b/includes/installer/i18n/av.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gazimagomedov"
+ ]
+ },
+ "config-page-options": "Рекъезаби"
+}
diff --git a/includes/installer/i18n/avk.json b/includes/installer/i18n/avk.json
new file mode 100644
index 00000000..6d6b80c8
--- /dev/null
+++ b/includes/installer/i18n/avk.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki inkeyen talpeyot.'''"
+}
diff --git a/includes/installer/i18n/az.json b/includes/installer/i18n/az.json
new file mode 100644
index 00000000..8688fe4e
--- /dev/null
+++ b/includes/installer/i18n/az.json
@@ -0,0 +1,33 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cekli829",
+ "Vago",
+ "Wertuose",
+ "Khan27"
+ ]
+ },
+ "config-desc": "MediaWiki yükləyicisi",
+ "config-information": "Məlumat",
+ "config-back": "← Geri",
+ "config-continue": "Davam et →",
+ "config-page-language": "Dil",
+ "config-page-welcome": "MediaWiki-yə xoş gəlmişsiniz!",
+ "config-page-dbconnect": "Verilənlər bazasına birləşdir",
+ "config-page-dbsettings": "Verilənlər bazasının nizamlanması",
+ "config-page-name": "Ad",
+ "config-page-options": "Nizamlamalar:",
+ "config-page-install": "Nizamlama",
+ "config-page-complete": "Komplektləşdir!",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-utf8": "UTF-8",
+ "config-ns-generic": "Layihə",
+ "config-admin-name": "Sizin adınız:",
+ "config-admin-password": "Parol:",
+ "config-admin-email": "E-poçt ünvanı",
+ "config-license-pd": "İctimai istifadə",
+ "config-help": "kömək",
+ "mainpagetext": "'''MediaWiki müvəffəqiyyətlə quraşdırıldı.'''",
+ "mainpagedocfooter": "Bu vikinin istifadəsi ilə bağlı məlumat almaq üçün [//meta.wikimedia.org/wiki/Help:Contents İstifadəçi məlumat səhifəsinə] baxın.\n\n== Faydalı keçidlər ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Tənzimləmələrin siyahısı]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki haqqında tez-tez soruşulan suallar]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçt siyahısı]"
+}
diff --git a/includes/installer/i18n/ba.json b/includes/installer/i18n/ba.json
new file mode 100644
index 00000000..cca993ae
--- /dev/null
+++ b/includes/installer/i18n/ba.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Haqmar",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "«MediaWiki» уңышлы рәүештә ҡоролдо.",
+ "mainpagedocfooter": "Был вики менән эшләү тураһында мәғлүмәтте [//meta.wikimedia.org/wiki/Help:Contents ошонда] табып була.\n\n== Файҙалы сығанаҡтар ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Көйләүҙәр исемлеге (инг.)];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki тураһында йыш бирелгән һорауҙар һәм яуаптар (инг.)];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-ның яңы версиялары тураһында хәбәрҙәр алып тороу].\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/bar.json b/includes/installer/i18n/bar.json
new file mode 100644
index 00000000..d949a8d4
--- /dev/null
+++ b/includes/installer/i18n/bar.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mucalexx",
+ "Matthias Klostermayr"
+ ]
+ },
+ "config-desc": "As Installationsprogramm vo MediaWiki",
+ "config-title": "Installation vo MediaWiki $1",
+ "config-information": "Information",
+ "mainpagetext": "'''MediaWiki is erfoigreich installird worn.'''",
+ "mainpagedocfooter": "A Hüf zur da Benützung und Konfigurazion voh da Wiki-Software findst auf [//meta.wikimedia.org/wiki/Help:Contents Benützerhåndbuach].\n\n== Starthüfe ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Listen voh de Konfigurazionsvariaablen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglisten voh de neichen MediaWiki-Versionen]"
+}
diff --git a/includes/installer/i18n/bcc.json b/includes/installer/i18n/bcc.json
new file mode 100644
index 00000000..e97d7acc
--- /dev/null
+++ b/includes/installer/i18n/bcc.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''مدیا وی کی گون موفقیت نصب بوت.'''",
+ "mainpagedocfooter": "مشورت کنیت گون [//meta.wikimedia.org/wiki/Help:Contents User's Guide] په گشیترین اطلاعات په استفاده چه برنامه ویکی.\n\n== شروع بیت ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/bcl.json b/includes/installer/i18n/bcl.json
new file mode 100644
index 00000000..533035c3
--- /dev/null
+++ b/includes/installer/i18n/bcl.json
@@ -0,0 +1,37 @@
+{
+ "@metadata": {
+ "authors": [
+ "Geopoet"
+ ]
+ },
+ "config-desc": "An tagapagkaag para sa MediaWiki",
+ "config-title": "MediaWiki $1 na pagkakaag",
+ "config-information": "Impormasyon",
+ "config-localsettings-key": "Ipag-angat an susi:",
+ "config-localsettings-badkey": "The susi na saimong pinagtao bakong tama.",
+ "config-your-language": "An Saimong lengguwahe:",
+ "config-wiki-language": "Lengguwahe sa Wiki:",
+ "config-back": "← Ibuwelta",
+ "config-continue": "Ipadagos →",
+ "config-page-language": "Lengguwahe",
+ "config-page-welcome": "Maogmang pag-abot sa MediaWiki!",
+ "config-page-dbsettings": "Mga panuytoy nin datos-sarayan",
+ "config-page-name": "Pangaran",
+ "config-page-options": "Mga Pagpipilian",
+ "config-page-install": "Kaagon",
+ "config-page-complete": "Kumpleto!",
+ "config-page-restart": "Poonan otro an pagkakaag",
+ "config-page-readme": "Basaha ako",
+ "config-page-releasenotes": "Buhian an mga katalaanan",
+ "config-page-copying": "Pinagkokopya",
+ "config-page-upgradedoc": "Ipinagpapalangkaw",
+ "config-page-existingwiki": "Eksistidong wiki",
+ "config-restart": "Iyo, pakipoon kaini otro",
+ "config-db-wiki-settings": "Bistohon ining wiki",
+ "config-db-name": "Pangaran kan datos-sarayan:",
+ "config-db-username": "Ngaran-paragamit nin datos-sarayan:",
+ "config-db-password": "Pasa-taramon nin datos-sarayan:",
+ "config-db-password-empty": "Pakilaog nin sarong pasa-taramon para sa baguhong paragamit nin datos-sarayan: $1.\nMantang ini posibleng makamukna nin mga paragamit na mayong mga pasa-taramon, bako po ining segurado.",
+ "mainpagetext": "'''Instalado na an MediaWiki.'''",
+ "mainpagedocfooter": "Konsultarón tabì an [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para sa impormasyon sa paggamit nin progama kaining wiki.\n\n== Pagpopoon ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/be-tarask.json b/includes/installer/i18n/be-tarask.json
new file mode 100644
index 00000000..0aeae7fc
--- /dev/null
+++ b/includes/installer/i18n/be-tarask.json
@@ -0,0 +1,333 @@
+{
+ "@metadata": {
+ "authors": [
+ "EugeneZelenko",
+ "Jim-by",
+ "Wizardist",
+ "Zedlik",
+ "아라",
+ "Red Winged Duck"
+ ]
+ },
+ "config-desc": "Праграма ўсталяваньня MediaWiki",
+ "config-title": "Усталяваньне MediaWiki $1",
+ "config-information": "Інфармацыя",
+ "config-localsettings-upgrade": "Выяўлены файл <code>LocalSettings.php</code>.\nКаб абнавіць гэтае ўсталяваньне, калі ласка, увядзіце значэньне <code>$wgUpgradeKey</code> у полі ніжэй.\nЯго можна знайсьці ў <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Быў знойдзены файл <code>LocalSettings.php</code>.\nКаб зьмяніць гэтае ўсталяваньне, калі ласка, запусьціце <code>update.php</code>",
+ "config-localsettings-key": "Ключ паляпшэньня:",
+ "config-localsettings-badkey": "Пададзены Вамі ключ зьяўляецца няслушным.",
+ "config-upgrade-key-missing": "Знойдзена ўсталяваная MediaWiki.\nКаб абнавіць гэтае ўсталяваньне, калі ласка, устаўце наступны радок у канец Вашага <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Выглядае, што актуальны <code>LocalSettings.php</code> зьяўляецца няпоўным.\nНе зададзеная зьменная $1.\nКалі ласка, зьмяніце <code>LocalSettings.php</code> так, каб прысутнічала гэтая зьменная, і націсьніце «{{int:Config-continue}}».",
+ "config-localsettings-connection-error": "Адбылася памылка падчас злучэньня з базай зьвестак з выкарыстаньнем наладаў, пазначаных у <code>LocalSettings.php</code>. Калі ласка, выпраўце гэтыя налады і паспрабуйце яшчэ раз.\n\n$1",
+ "config-session-error": "Памылка стварэньня сэсіі: $1",
+ "config-session-expired": "Скончыўся тэрмін дзеяньня зьвестак сэсіі.\nСэсія мае абмежаваны тэрмін у $1.\nВы можаце павялічыць яго, зьмяніўшы парамэтар <code>session.gc_maxlifetime</code> ў php.ini.\nПеразапусьціце працэс усталяваньня.",
+ "config-no-session": "Зьвесткі сэсіі згубленыя!\nПраверце php.ini і ўпэўніцеся, што ўстаноўлены слушны шлях у <code>session.save_path</code>.",
+ "config-your-language": "Вашая мова:",
+ "config-your-language-help": "Выберыце мову для выкарыстаньня падчас усталяваньня.",
+ "config-wiki-language": "Мова вікі:",
+ "config-wiki-language-help": "Выберыце мову, на якой пераважна будзе пісацца зьмест у вікі.",
+ "config-back": "← Назад",
+ "config-continue": "Далей →",
+ "config-page-language": "Мова",
+ "config-page-welcome": "Вітаем у MediaWiki!",
+ "config-page-dbconnect": "Падключэньне да базы зьвестак",
+ "config-page-upgrade": "Абнавіць існуючую ўстаноўку",
+ "config-page-dbsettings": "Налады базы зьвестак",
+ "config-page-name": "Назва",
+ "config-page-options": "Налады",
+ "config-page-install": "Усталяваць",
+ "config-page-complete": "Зроблена!",
+ "config-page-restart": "Пачаць усталяваньне зноў",
+ "config-page-readme": "Дадатковыя зьвесткі",
+ "config-page-releasenotes": "Заўвагі да выпуску",
+ "config-page-copying": "Капіяваньне",
+ "config-page-upgradedoc": "Абнаўленьне",
+ "config-page-existingwiki": "Існуючая вікі",
+ "config-help-restart": "Ці жадаеце выдаліць усе ўведзеныя зьвесткі і пачаць працэс усталяваньня зноў?",
+ "config-restart": "Так, пачаць зноў",
+ "config-welcome": "== Праверка асяродзьдзя ==\nЗараз будуць праведзеныя праверкі для запэўніваньня, што гэтае асяродзьдзе адпаведнае для ўсталяваньня MediaWiki.\nНе забудзьце далучыць гэтую інфармацыю, калі вам спатрэбіцца дапамога для завяршэньня ўсталяваньня.",
+ "config-copyright": "== Аўтарскае права і ўмовы ==\n\n$1\n\nThis 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.\n\nThis 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'''.\nSee the GNU General Public License for more details.\n\nYou should have received <doclink href=Copying>a copy of the GNU General Public License</doclink> along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. or [http://www.gnu.org/copyleft/gpl.html read it online].",
+ "config-sidebar": "* [//www.mediawiki.org Хатняя старонка MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Даведка для ўдзельнікаў]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Даведка для адміністратараў]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Адказы на частыя пытаньні]\n----\n* <doclink href=Readme>Прачытайце</doclink>\n* <doclink href=ReleaseNotes>Паляпшэньні ў вэрсіі</doclink>\n* <doclink href=Copying>Капіяваньне</doclink>\n* <doclink href=UpgradeDoc>Абнаўленьне</doclink>",
+ "config-env-good": "Асяродзьдзе было праверанае.\nВы можаце ўсталёўваць MediaWiki.",
+ "config-env-bad": "Асяродзьдзе было праверанае.\nУсталяваньне MediaWiki немагчымае.",
+ "config-env-php": "Усталяваны PHP $1.",
+ "config-env-hhvm": "HHVM $1 усталяваная.",
+ "config-unicode-using-utf8": "Выкарыстоўваецца бібліятэка Unicode-нармалізацыі Браяна Вібэра",
+ "config-unicode-using-intl": "Выкарыстоўваецца [http://pecl.php.net/intl intl пашырэньне з PECL] для Unicode-нармалізацыі",
+ "config-unicode-pure-php-warning": "'''Папярэджаньне''': [http://pecl.php.net/intl Пашырэньне intl з PECL] — ня слушнае для Unicode-нармалізацыі, цяпер выкарыстоўваецца марудная PHP-рэалізацыя.\nКалі ў Вас сайт з высокай наведваемасьцю, раім пачытаць пра [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-нармалізацыю].",
+ "config-unicode-update-warning": "'''Папярэджаньне''': усталяваная вэрсія бібліятэкі для Unicode-нармалізацыі выкарыстоўвае састарэлую вэрсію бібліятэкі з [http://site.icu-project.org/ праекту ICU].\nРаім [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations абнавіць], калі ваш сайт будзе працаваць зь Unicode.",
+ "config-no-db": "Немагчыма знайсьці адпаведны драйвэр базы зьвестак. Вам неабходна ўсталяваць драйвэр базы зьвестак для PHP.\nПадтрымліваюцца наступныя тыпы базаў зьвестак: $1.\n\nКалі вы скампілявалі PHP самастойна, зьмяніце канфігурацыю, каб уключыць кліента базы зьвестак, напрыклад, з дапамогай <code>./configure --with-mysqli</code>.\nКалі вы ўсталявалі PHP з пакунку Debian або Ubuntu, тады вам трэба таксама ўсталяваць, напрыклад, пакунак <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Папярэджаньне''': усталяваны SQLite $1, у той час, калі мінімальная сумяшчальная вэрсія — $2. SQLite ня будзе даступны.",
+ "config-no-fts3": "'''Папярэджаньне''': SQLite створаны без модуля [//sqlite.org/fts3.html FTS3], для гэтага ўнутранага інтэрфэйсу ня будзе даступная магчымасьць пошуку.",
+ "config-register-globals-error": "<strong>Памылка: парамэтар PHP <code>[http://php.net/register_globals register_globals]</code> уключаны.\nЁн павінен быць адключаны, каб працягнуць усталяваньне.</strong>\nГлядзіце [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] дзеля дапамогі, як зрабіць гэта.",
+ "config-magic-quotes-gpc": "<strong>Непапраўная памылка: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] актываваны!</strong>\nГэтая функцыя псуе ўвод зьвестак непрадказальным чынам.\nВы ня можаце ўсталяваць або выкарыстоўваць MediaWiki, пакуль гэтая функцыя ня будзе адключаная.",
+ "config-magic-quotes-runtime": "'''Фатальная памылка: уключаная опцыя PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''\nГэтая опцыя псуе ўводны паток зьвестак непрадказальным чынам.\nПрацяг усталяваньня альбо выкарыстаньне MediaWiki без адключэньня гэтай опцыі немагчымыя.",
+ "config-magic-quotes-sybase": "'''Фатальная памылка: рэжым [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] уключаны!'''\nГэты рэжым шкодзіць уваходныя зьвесткі непрадказальным чынам.\nПрацяг усталяваньня альбо выкарыстаньне MediaWiki немагчымыя, пакуль рэжым ня будзе выключаны.",
+ "config-mbstring": "'''Фатальная памылка: рэжым [http://www.php.net/manual/en/ref.info.php#mbstring.overload mbstring.func_overload] уключаны!'''\nГэты рэжым выклікае памылкі і можа шкодзіць зьвесткі непрадказальным чынам.\nПрацяг усталяваньня альбо выкарыстаньне MediaWiki немагчымыя, пакуль рэжым ня будзе выключаны.",
+ "config-safe-mode": "'''Папярэджаньне:''' [http://www.php.net/features.safe-mode бясьпечны рэжым] PHP уключаны.\nГэта можа выклікаць праблемы, галоўным чынам падчас загрузак файлаў і ў падтрымцы <code>math</code>.",
+ "config-xml-bad": "Ня знойдзены модуль XML для PHP.\nMediaWiki патрэбныя функцыі з гэтага модулю, таму MediaWiki ня будзе працаваць у гэтай канфігурацыі.\nКалі Вы выкарыстоўваеце Mandrake, усталюйце пакет php-xml.",
+ "config-pcre-old": "<strong>Крытычная памылка:</strong> патрэбны PCRE вэрсіі $1 або пазьнейшай.\nPHP-файл, які выконваецца, зьвязаны з PCRE вэрсіі $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Больш інфармацыі].",
+ "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-ctype": "'''Фатальная памылка''': PHP мусіць быць скампіляваны з падтрымкай [http://www.php.net/manual/en/ctype.installation.php пашырэньня Ctype].",
+ "config-iconv": "<strong>Непапраўная памылка:</strong> PHP мусіць быць скампіляваны з падтрымкай [http://www.php.net/manual/en/iconv.installation.php пашырэньня iconv].",
+ "config-json": "<strong>Крытычная памылка:</strong> PHP быў скампіляваны без падтрымкі JSON.\nВы павінныя ўсталяваць або пашырэньне PHP JSON, або пашырэньне [http://pecl.php.net/package/jsonc PECL jsonc] перад усталёўкай MediaWiki.\n* Пашырэньне PHP уваходзіць у Red Hat Enterprise Linux (CentOS) 5 і 6, пры гэтым павінна быць падключана ў <code>/etc/php.ini</code> або <code>/etc/php.d/json.ini</code>.\n* Некаторыя дыстрыбутывы Linux, выдадзеныя пасьля траўня 2013 году, ня маюць пашырэньня PHP, замест яго пакуюць пашырэньне PECL як <code>php5-json</code> або <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] усталяваны",
+ "config-apc": "[http://www.php.net/apc APC] усталяваны",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] усталяваны",
+ "config-no-cache": "'''Папярэджаньне:''' немагчыма знайсьці [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ці [http://www.iis.net/download/WinCacheForPhp WinCache].\nАб’ектнае кэшаваньне ня ўключанае.",
+ "config-mod-security": "'''Папярэджаньне''': на Вашым ўэб-сэрверы ўключаны [http://modsecurity.org/ mod_security]. У выпадку няслушнай наладцы, ён можа стаць прычынай праблемаў для MediaWiki ці іншага праграмнага забесьпячэньня, якое дазваляе ўдзельнікам дасылаць на сэрвэр любы зьмест.\nГлядзіце [http://modsecurity.org/documentation/ дакумэнтацыю mod_security] ці зьвярніцеся ў падтрымку Вашага хосту, калі ў Вас узьнікаюць выпадковыя праблемы.",
+ "config-diff3-bad": "GNU diff3 ня знойдзены.",
+ "config-git": "Знойдзеная сыстэма канстролю вэрсіяў Git: <code>$1</code>",
+ "config-git-bad": "Сыстэма кантролю вэрсіяў Git ня знойдзеная.",
+ "config-imagemagick": "Знойдзены ImageMagick: <code>$1</code>.\nПасьля ўключэньня загрузак будзе ўключанае маштабаваньне выяваў.",
+ "config-gd": "GD падтрымліваецца ўбудавана.\nПасьля ўключэньня загрузак будзе ўключанае маштабаваньне выяваў.",
+ "config-no-scaling": "Ні GD, ні ImageMagick ня знойдзеныя.\nМаштабаваньне выяваў будзе адключанае.",
+ "config-no-uri": "'''Памылка:''' Не магчыма вызначыць цяперашні URI.\nУсталяваньне спыненае.",
+ "config-no-cli-uri": "'''Папярэджаньне''': Не пазначаны <code>--scriptpath</code>, па змоўчваньні выкарыстоўваецца: <code>$1</code>.",
+ "config-using-server": "Выкарыстоўваецца назва сэрвэра «<nowiki>$1</nowiki>».",
+ "config-using-uri": "Выкарыстоўваецца URL-спасылка сэрвэра «<nowiki>$1$2</nowiki>».",
+ "config-uploads-not-safe": "'''Папярэджаньне:''' дырэкторыя для загрузак па змоўчваньні <code>$1</code> уразьлівая да выкананьня адвольнага коду.\nХоць MediaWiki і правярае ўсе файлы перад захаваньнем, вельмі рэкамэндуецца [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security закрыць гэтую ўразьлівасьць] перад уключэньнем магчымасьці загрузкі файлаў.",
+ "config-no-cli-uploads-check": "'''Папярэджаньне:''' Вашая дырэкторыя для загрузак па змоўчваньні (<code>$1</code>), не правераная на ўразьлівасьць да выкананьня адвольных скрыптоў падчас усталяваньня CLI.\n.",
+ "config-brokenlibxml": "У Вашай сыстэме ўсталяваныя PHP і libxml2 зь несумяшчальнымі вэрсіямі, што можа прывесьці да пашкоджаньня зьвестак MediaWiki і іншых вэб-дастасаваньняў.\nАбнавіце libxml2 да вэрсіі 2.7.3 ці больш позьняй ([https://bugs.php.net/bug.php?id=45996 паведамленьне пра памылку на сайце PHP]).\nУсталяваньне перарванае.",
+ "config-suhosin-max-value-length": "Suhosin усталяваны і абмяжоўвае <code>даўжыню</code> парамэтру GET да $1 {{PLURAL:$1|1=байта|байтаў}}.\nResourceLoader, складнік MediaWiki, будзе абходзіць гэтае абмежаваньне, што адаб’ецца на прадукцыйнасьці.\nКалі магчыма, варта ўсталяваць у <code>php.ini</code> значэньне <code>suhosin.get.max_value_length</code> роўным 1024 ці больш, а таксама вызначыць тое ж значэньне для <code>$wgResourceLoaderMaxQueryLength</code> у <code>LocalSettings.php</code>.",
+ "config-db-type": "Тып базы зьвестак:",
+ "config-db-host": "Хост базы зьвестак:",
+ "config-db-host-help": "Калі сэрвэр Вашай базы зьвестак знаходзіцца на іншым сэрвэры, увядзіце тут імя хоста ці IP-адрас.\n\nКалі Вы карыстаецеся shared-хостынгам, Ваш хостынг-правайдэр мусіць даць Вам слушнае імя хоста базы зьвестак у сваёй дакумэнтацыі.\n\nКалі Вы усталёўваеце сэрвэр Windows з выкарыстаньнем MySQL, выкарыстаньне «localhost» можа не працаваць для назвы сэрвэра. У гэтым выпадку паспрабуйце пазначыць «127.0.0.1» для лякальнага IP-адраса.\n\nКалі Вы выкарыстоўваеце PostgreSQL, пакіньце поле пустым, каб далучыцца праз Unix-сокет.",
+ "config-db-host-oracle": "TNS базы зьвестак:",
+ "config-db-host-oracle-help": "Увядзіце слушнае [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm лякальнае імя злучэньня]; файл tnsnames.ora павінен быць бачным для гэтага ўсталяваньня.<br />Калі Вы выкарыстоўваеце кліенцкія бібліятэкі 10g ці больш новыя, Вы можаце таксама выкарыстоўваць мэтад наданьня назваў [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm лёгкае злучэньне].",
+ "config-db-wiki-settings": "Ідэнтыфікацыя гэтай вікі",
+ "config-db-name": "Назва базы зьвестак:",
+ "config-db-name-help": "Выберыце імя для вызначэньня Вашай вікі.\nЯно ня мусіць зьмяшчаць прагалаў.\n\nКалі Вы набываеце shared-хостынг, Ваш хостынг-правайдэр мусіць надаць Вам ці пэўнае імя базы зьвестак для выкарыстаньня, ці магчымасьць ствараць базы зьвестак праз кантрольную панэль.",
+ "config-db-name-oracle": "Схема базы зьвестак:",
+ "config-db-account-oracle-warn": "Існуюць тры сцэнары ўсталяваньня Oracle як базы зьвестак для MediaWiki:\n\nКалі Вы жадаеце стварыць рахунак базы зьвестак як частку працэсу ўсталяваньня, калі ласка, падайце рахунак з роляй SYSDBA як рахунак базы зьвестак для ўсталяваньня і пазначце пажаданыя правы рахунку з доступам да Інтэрнэту, у адваротным выпадку Вы можаце таксама стварыць рахунак з доступам да Інтэрнэту ўручную і падаць толькі гэты рахунак (калі патрабуюцца правы для стварэньня схемы аб’ектаў) ці падайце два розных рахункі, адзін з правамі на стварэньне і адзін з абмежаваньнямі для доступу да Інтэрнэту.\n\nСкрыпт для стварэньня рахунку з патрабуемымі правамі можна знайсьці ў дырэкторыі гэтага ўсталяваньня «maintenance/oracle/». Памятайце, што выкарыстаньне рахунку з абмежаваньнямі адключыць усе падтрымліваемыя магчымасьці даступныя па змоўчваньні.",
+ "config-db-install-account": "Імя карыстальніка для ўсталяваньня",
+ "config-db-username": "Імя карыстальніка базы зьвестак:",
+ "config-db-password": "Пароль базы зьвестак:",
+ "config-db-password-empty": "Калі ласка, увядзіце пароль для новага карыстальніка базы зьвестак: $1.\nМагчыма стварыць карыстальніка без паролю, але гэта небясьпечна.",
+ "config-db-username-empty": "Вы мусіце ўвесьці значэньне парамэтру «{{int:config-db-username}}»",
+ "config-db-install-username": "Увядзіце імя карыстальніка, якое будзе выкарыстоўвацца для злучэньня з базай зьвестак падчас усталяваньня. Гэта не назва рахунку MediaWiki; гэта імя карыстальніка Вашай базы зьвестак.",
+ "config-db-install-password": "Увядзіце пароль, які будзе выкарыстоўвацца для злучэньня з базай зьвестак падчас усталяваньня. Гэта не пароль рахунку MediaWiki; гэта пароль Вашай базы зьвестак.",
+ "config-db-install-help": "Увядзіце імя карыстальніка і пароль, якія будуць выкарыстаныя для далучэньня да базы зьвестак падчас працэсу ўсталяваньня.",
+ "config-db-account-lock": "Выкарыстоўваць тыя ж імя карыстальніка і пароль пасьля ўсталяваньня",
+ "config-db-wiki-account": "Імя карыстальніка для працы",
+ "config-db-wiki-help": "Увядзіце імя карыстальніка і пароль, якія будуць выкарыстаныя для далучэньня да базы зьвестак падчас працы (пасьля ўсталяваньня).\nКалі рахунак ня створаны, а рахунак для ўсталяваньня мае значныя правы, гэты рахунак будзе створаны зь мінімальна патрэбнымі для працы вікі правамі.",
+ "config-db-prefix": "Прэфікс табліцаў базы зьвестак:",
+ "config-db-prefix-help": "Калі Вы разьдзяляеце адну базу зьвестак паміж некалькімі вікі, ці паміж MediaWiki і іншым вэб-дастасаваньнем, можаце вызначыць прэфікс назваў табліцаў для пазьбяганьня канфліктаў.\nПазьбягайце прагалаў.\n\nГэтае поле звычайна пакідаецца пустым.",
+ "config-db-charset": "Кадаваньне базы зьвестак",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 UTF-8 з адваротнай сумяшчальнасьцю",
+ "config-charset-help": "'''Папярэджаньне:''' калі Вы выкарыстоўваеце '''UTF-8 з адваротнай сумяшчальнасьцю''' на MySQL 4.1+ і зробіце рэзэрвовую копію праз <code>mysqldump</code>, ён можа зьнішчыць усе не-ASCII-сымбалі беспаваротна!\n\nУ '''бінарным (binary)''' рэжыме MediaWiki захоўвае тэксты ў UTF-8 у палёх тыпу binary.\nГэты рэжым болей эфэктыўны за рэжым MySQL UTF-8 і дазваляе выкарыстоўваць увесь абсяг сымбаляў Unicode.\nУ рэжыме '''UTF-8''' MySQL будзе ведаць, у якім кадаваньне Вы зьмяшчаеце зьвесткі, і будзе вяртаць іх у адпаведным кадаваньні,\nале MySQL ня можа ўтрымліваць сымбалі па-за [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Стандартным шматмоўным пластом] сымбаляў Unicode.",
+ "config-mysql-old": "Патрабуецца MySQL $1 ці навейшая, усталяваная вэрсія $2.",
+ "config-db-port": "Порт базы зьвестак:",
+ "config-db-schema": "Схема для MediaWiki",
+ "config-db-schema-help": "Гэтая схема слушная ў большасьці выпадкаў.\nЗьмяняйце яе толькі тады, калі Вы ведаеце, што гэта неабходна.",
+ "config-pg-test-error": "Немагчыма далучыцца да базы зьвестак '''$1''': $2",
+ "config-sqlite-dir": "Дырэкторыя зьвестак SQLite:",
+ "config-sqlite-dir-help": "SQLite захоўвае ўсе зьвесткі ў адзіным файле.\n\nПададзеная Вамі дырэкторыя павінна быць даступнай да запісу вэб-сэрвэрам падчас усталяваньня.\n\nЯна '''ня''' мусіць быць даступнай праз Сеціва, вось чаму мы не захоўваем яе ў адным месцы з файламі PHP.\n\nПраграма ўсталяваньня дадаткова створыць файл <code>.htaccess</code>, але калі ён не выкарыстоўваецца, хто заўгодна зможа атрымаць зьвесткі з базы зьвестак.\nГэта ўключае як прыватныя зьвесткі ўдзельнікаў (адрасы электроннай пошты, хэшы пароляў), гэтак і выдаленыя вэрсіі старонак і іншыя зьвесткі, доступ да якіх маецца абмежаваны.\n\nПадумайце над тым, каб зьмяшчаць базу зьвестак у іншым месцы, напрыклад у <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Прастора табліцаў па змоўчваньні:",
+ "config-oracle-temp-ts": "Часовая прастора табліцаў:",
+ "config-type-mysql": "MySQL (або сумяшчальная)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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], якія сумяшчальныя з MySQL. ([http://www.php.net/manual/en/mysqli.installation.php Як скампіляваць PHP з падтрымкай MySQL])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] — папулярная сыстэма базы зьвестак з адкрытым кодам, якая зьяўляецца альтэрнатывай MySQL. Яна можа ўтрымліваць дробныя памылкі, і не рэкамэндуецца выкарыстоўваць яе для працуючых праектаў. ([http://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. ([http://www.php.net/manual/en/sqlsrv.installation.php Як скампіляваць PHP з падтрымкай SQLSRV])",
+ "config-header-mysql": "Налады MySQL",
+ "config-header-postgres": "Налады PostgreSQL",
+ "config-header-sqlite": "Налады SQLite",
+ "config-header-oracle": "Налады Oracle",
+ "config-header-mssql": "Налады Microsoft SQL Server",
+ "config-invalid-db-type": "Няслушны тып базы зьвестак",
+ "config-missing-db-name": "Вы мусіце ўвесьці значэньне парамэтру «{{int:config-db-name}}».",
+ "config-missing-db-host": "Вы мусіце ўвесьці значэньне парамэтру «{{int:config-db-host}}».",
+ "config-missing-db-server-oracle": "Вы мусіце ўвесьці значэньне парамэтру «{{int:config-db-host-oracle}}».",
+ "config-invalid-db-server-oracle": "Няслушнае TNS базы зьвестак «$1».\nВыкарыстоўвайце або «TNS Name», або радок «Easy Connect» ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Мэтады найменьня Oracle])",
+ "config-invalid-db-name": "Няслушная назва базы зьвестак «$1».\nНазва можа ўтрымліваць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), сымбалі падкрэсьліваньня(_) і працяжнікі (-).",
+ "config-invalid-db-prefix": "Няслушны прэфікс базы зьвестак «$1».\nЁн можа зьмяшчаць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), сымбалі падкрэсьліваньня (_) і працяжнікі (-).",
+ "config-connection-error": "$1.\n\nПраверце хост, імя карыстальніка і пароль ніжэй і паспрабуйце зноў.",
+ "config-invalid-schema": "Няслушная схема для MediaWiki «$1».\nВыкарыстоўвайце толькі ASCII-літары (a-z, A-Z), лічбы (0-9) і сымбалі падкрэсьліваньня (_).",
+ "config-db-sys-create-oracle": "Праграма ўсталяваньня падтрымлівае толькі выкарыстаньне рахунку SYSDBA для стварэньня новага рахунку.",
+ "config-db-sys-user-exists-oracle": "Рахунак карыстальніка «$1» ужо існуе. SYSDBA можа выкарыстоўвацца толькі для стварэньня новых рахункаў!",
+ "config-postgres-old": "Патрабуецца PostgreSQL $1 ці навейшая, усталяваная вэрсія $2.",
+ "config-mssql-old": "Патрабуецца Microsoft SQL Server вэрсіі $1 ці больш позьняй. У вас усталяваная вэрсія $2.",
+ "config-sqlite-name-help": "Выберыце назву, якая будзе ідэнтыфікаваць Вашую вікі.\nНе выкарыстоўвайце прагалы ці злучкі.\nНазва будзе выкарыстоўвацца ў назьве файла зьвестак SQLite.",
+ "config-sqlite-parent-unwritable-group": "Немагчыма стварыць дырэкторыю зьвестак <code><nowiki>$1</nowiki></code>, таму што бацькоўская дырэкторыя <code><nowiki>$2</nowiki></code> абароненая ад запісаў вэб-сэрвэра.\n\nПраграма ўсталяваньня вызначыла карыстальніка, які запусьціў вэб-сэрвэр.\nДазвольце запісы ў дырэкторыю <code><nowiki>$3</nowiki></code> для працягу.\nУ сыстэме Unix/Linux зрабіце:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Немагчыма стварыць дырэкторыю зьвестак <code><nowiki>$1</nowiki></code>, таму што бацькоўская дырэкторыя <code><nowiki>$2</nowiki></code> абароненая ад запісаў вэб-сэрвэра.\n\nПраграма ўсталяваньня вызначыла карыстальніка, які запусьціў вэб-сэрвэр.\nДазвольце яму (і іншым) запісы ў дырэкторыю <code><nowiki>$3</nowiki></code> для працягу.\nУ сыстэме Unix/Linux зрабіце:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Памылка падчас стварэньня дырэкторыі «$1».\nПраверце шлях і паспрабуйце зноў.",
+ "config-sqlite-dir-unwritable": "Запіс у дырэкторыю «$1» немагчымы.\nЗьмяніце налады доступу, каб вэб-сэрвэр меў правы на запіс, і паспрабуйце зноў.",
+ "config-sqlite-connection-error": "$1.\n\nПраверце дырэкторыю для зьвестак, назву базы зьвестак і паспрабуйце зноў.",
+ "config-sqlite-readonly": "Файл <code>$1</code> недаступны для запісу.",
+ "config-sqlite-cant-create-db": "Немагчыма стварыць файл базы зьвестак <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP бракуе падтрымкі FTS3 — табліцы пагаршаюцца",
+ "config-can-upgrade": "У гэтай базе зьвестак ёсьць табліцы MediaWiki.\nКаб абнавіць іх да MediaWiki $1, націсьніце '''Працягнуць'''.",
+ "config-upgrade-done": "Абнаўленьне завершанае.\n\nЦяпер Вы можаце [$1 пачаць выкарыстаньне вікі].\n\nКалі Вы жадаеце рэгенэраваць <code>LocalSettings.php</code>, націсьніце кнопку ніжэй.\nГэтае дзеяньне '''не рэкамэндуецца''', калі Вы ня маеце праблемаў у працы вікі.",
+ "config-upgrade-done-no-regenerate": "Абнаўленьне скончанае.\n\nЦяпер Вы можаце [$1 пачаць працу з вікі].",
+ "config-regenerate": "Рэгенэраваць LocalSettings.php →",
+ "config-show-table-status": "Запыт '<code>SHOW TABLE STATUS</code>' не атрымаўся!",
+ "config-unknown-collation": "'''Папярэджаньне:''' база зьвестак выкарыстоўвае нераспазнанае супастаўленьне.",
+ "config-db-web-account": "Рахунак базы зьвестак для вэб-доступу",
+ "config-db-web-help": "Выберыце імя карыстальніка і пароль, які выкарыстоўваецца вэб-сэрвэрам для злучэньня з сэрвэрам базы зьвестак, падчас звычайных апэрацыяў вікі.",
+ "config-db-web-account-same": "Выкарыстоўваць той жа рахунак, што для ўсталяваньня",
+ "config-db-web-create": "Стварыць рахунак, калі ён яшчэ не існуе",
+ "config-db-web-no-create-privs": "Рахунак, які Вы пазначылі для ўсталяваньня ня мае правоў для стварэньня рахунку.\nРахунак, які Вы пазначылі тут, мусіць ужо існаваць.",
+ "config-mysql-engine": "Рухавік сховішча:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Папярэджаньне''': Вы выбралі MyISAM у якасьці рухавіка для захоўваньня зьвестак у MySQL, які не рэкамэндуецца да выкарыстаньня з MediaWiki па прычынах:\n* кепская падтрымка паралельнай апрацоўкі з-за таблічных блякаваньняў;\n* большая імавернасьць пашкоджаньня зьвестак у параўнаньні зь іншымі рухавікамі;\n* код MediaWiki не ва ўсіх выпадках улічвае асаблівасьці MyISAM.\n\nКалі Ваш MySQL-сэрвэр падтрымлівае InnoDB, вельмі рэкамэндуецца выкарыстаньне менавіта гэтага рухавіка.\nКалі MySQL-сэрвэр не падтрымлівае InnoDB, пэўна, настаў час абнавіць яго.",
+ "config-mysql-only-myisam-dep": "<strong>Папярэджаньне:</strong> MyISAM — адзіная даступная сыстэма захоўваньня зьвестак для MySQL на гэтым кампутары, яна не рэкамэндуецца для ўжываньня з MediaWiki, таму што:\n* слаба падтрымлівае паралельнасьць праз блякаваньне табліцаў\n* больш за іншыя сыстэмы схільная да пашкоджаньняў\n* кодавая база MediaWiki не заўсёды належна апрацоўвае MyISAM\n\nВашае ўсталяваньне MySQL не падтрымлівае InnoDB, магчыма, час для абнаўленьня.",
+ "config-mysql-engine-help": "'''InnoDB''' — звычайна найбольш слушны варыянт, таму што добра падтрымлівае паралелізм.\n\n'''MyISAM''' можа быць хутчэйшай у вікі з адным удзельнікам, ці толькі для чытаньня.\nБазы зьвестак на MyISAM вядомыя тым, што ў іх зьвесткі шкодзяцца нашмат часьцей за InnoDB.",
+ "config-mysql-charset": "Кадаваньне базы зьвестак:",
+ "config-mysql-binary": "Двайковае",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "У '''двайковым рэжыме''', MediaWiki захоўвае тэкст у кадаваньні UTF-8 у базе зьвестак у двайковых палях.\nГэта болей эфэктыўна за рэжым MySQL UTF-8, і дазваляе Вам выкарыстоўваць увесь дыяпазон сымбаляў Unicode.\n\nУ '''рэжыме UTF-8''', MySQL ведае, якая табліцы сымбаляў выкарыстоўваецца ў Вашых зьвестках, і можа адпаведна прадстаўляць і канвэртаваць іх, але гэта не дазволіць Вам захоўваць сымбалі па-за межамі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базавага шматмоўнага дыяпазону].",
+ "config-mssql-auth": "Тып аўтэнтыфікацыі:",
+ "config-mssql-install-auth": "Абярыце тып аўтэнтыфікацыі, які будзе выкарыстаны для злучэньня з базай зьвестак падчас працэсу ўсталяваньня.\nКалі вы абярэце «{{int:config-mssql-windowsauth}}», будуць выкарыстаныя ўліковыя зьвесткі карыстальніка, пад якім працуе вэб-сэрвэр.",
+ "config-mssql-web-auth": "Абярыце тып аўтэнтыфікацыі, які вэб-сэрвэр будзе выкарыстоўваць для злучэньня з базай зьвестак падчас звычайнага функцыянаваньня вікі.\nКалі вы абярэце «{{int:config-mssql-windowsauth}}», будуць выкарыстаныя ўліковыя зьвесткі карыстальніка, пад якім працуе вэб-сэрвэр.",
+ "config-mssql-sqlauth": "Аўтэнтыфікацыя SQL-сэрвэра",
+ "config-mssql-windowsauth": "Windows-аўтэнтыфікацыя",
+ "config-site-name": "Назва вікі:",
+ "config-site-name-help": "Назва будзе паказвацца ў загалоўку браўзэра і ў некаторых іншых месцах.",
+ "config-site-name-blank": "Увядзіце назву сайта.",
+ "config-project-namespace": "Прастора назваў праекту:",
+ "config-ns-generic": "Праект",
+ "config-ns-site-name": "Такая ж, як і назва вікі: $1",
+ "config-ns-other": "Іншая (вызначце)",
+ "config-ns-other-default": "MyWiki",
+ "config-project-namespace-help": "Па прыкладу Вікіпэдыі, шматлікія вікі трымаюць уласныя старонкі з правіламі асобна ад старонак са зьместам, у «'''прасторы назваў праекту'''».\nУсе назвы старонак у гэтай прасторы назваў пачынаюцца з прыстаўкі, якую Вы можаце пазначыць тут.\nТрадыцыйна, гэтая прыстаўка вытворная ад назвы вікі, яле яна ня можа ўтрымліваць некаторыя сымбалі, такія як «#» ці «:».",
+ "config-ns-invalid": "Пададзеная няслушная прастора назваў «<nowiki>$1</nowiki>».\nПадайце іншую прастору назваў праекту.",
+ "config-ns-conflict": "Пазначаная прастора назваў «<nowiki>$1</nowiki>» канфліктуе з прасторай назваў MediaWiki па змоўчваньні.\nПазначце іншую прастору назваў праекту.",
+ "config-admin-box": "Рахунак адміністратара",
+ "config-admin-name": "Вашае імя карыстальніка:",
+ "config-admin-password": "Пароль:",
+ "config-admin-password-confirm": "Пароль яшчэ раз:",
+ "config-admin-help": "Увядзіце тут Вашае імя ўдзельніка, напрыклад «Янка Кавалевіч».\nГэтае імя будзе выкарыстоўвацца для ўваходу ў вікі.",
+ "config-admin-name-blank": "Увядзіце імя адміністратара.",
+ "config-admin-name-invalid": "Пададзенае няслушнае імя ўдзельніка «<nowiki>$1</nowiki>».\nПадайце іншае імя ўдзельніка.",
+ "config-admin-password-blank": "Увядзіце пароль рахунку адміністратара.",
+ "config-admin-password-mismatch": "Уведзеныя Вамі паролі не супадаюць.",
+ "config-admin-email": "Адрас электроннай пошты:",
+ "config-admin-email-help": "Увядзіце тут адрас электроннай пошты, каб атрымліваць электронныя лісты ад іншых удзельнікаў вікі, скідваць Ваш пароль і атрымліваць абвешчаньні пра зьмены старонак, якія знаходзяцца ў Вашым сьпісе назіраньня. Вы можаце пакінуць гэтае поле пустым.",
+ "config-admin-error-user": "Унутраная памылка падчас стварэньня рахунку адміністратара зь іменем «<nowiki>$1</nowiki>».",
+ "config-admin-error-password": "Унутраная памылка падчас устаноўкі паролю для адміністратара «<nowiki>$1</nowiki>»: <pre>$2</pre>",
+ "config-admin-error-bademail": "Вы ўвялі няслушны адрас электроннай пошты",
+ "config-subscribe": "Падпісацца на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў].",
+ "config-subscribe-help": "Гэта ня вельмі актыўны сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў, які ўключаючы важныя навіны пра бясьпеку.\nВам неабходна падпісацца на яго і абнавіць Вашае ўсталяваньне MediaWiki, калі зьявяцца новыя вэрсіі.",
+ "config-subscribe-noemail": "Вы спрабавалі падпісацца на рассылку паведамленьняў пра выхад новых вэрсіяў, не пазначыўшы адрас электроннай пошты.\nКалі ласка, падайце слушны адрас, калі Вы жадаеце падпісацца на рассылку.",
+ "config-almost-done": "Вы амаль што скончылі!\nАстатнія налады можна прапусьціць і пачаць усталяваньне вікі.",
+ "config-optional-continue": "Задаць болей пытаньняў.",
+ "config-optional-skip": "Хопіць, проста ўсталяваць вікі.",
+ "config-profile": "Профіль правоў удзельніка:",
+ "config-profile-wiki": "Адкрытая вікі",
+ "config-profile-no-anon": "Патрэбнае стварэньне рахунку",
+ "config-profile-fishbowl": "Толькі для аўтарызаваных рэдактараў",
+ "config-profile-private": "Прыватная вікі",
+ "config-profile-help": "Вікі працуюць лепей, калі Вы дазваляеце як мага большай колькасьці людзей рэдагаваць яе.\nУ MediaWiki вельмі лёгка праглядаць апошнія зьмены і выпраўляць любыя памылкі зробленыя недасьведчанымі ўдзельнікамі альбо вандаламі.\n\nТым ня менш, многія лічаць, што MediaWiki можа быць карыснай у шматлікіх іншых ролях, і часта вельмі нялёгка растлумачыць усім перавагі выкарыстаньня тэхналёгіяў вікі.\nТаму Вы маеце выбар.\n\n<strong>{{int:config-profile-wiki}}</strong> дазваляе рэдагаваць усім, нават без уваходу ў сыстэму.\nВікі з <strong>{{int:config-profile-no-anon}}</strong> дазваляе дадатковую адказнасьць, але можа адштурхнуць некаторых патэнцыйных удзельнікаў.\n\nСцэнар <strong>{{int:config-profile-fishbowl}}</strong> дазваляе рэдагаваць зацьверджаным удзельнікам, але ўсе могуць праглядаць старонкі іх гісторыю.\n<strong>{{int:config-profile-private}}</strong> дазваляе праглядаць і рэдагаваць старонкі толькі зацьверджаным удзельнікам.\n\nБольш складаныя правы ўдзельнікаў даступныя пасьля ўсталяваньня, глядзіце [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights адпаведную старонку дакумэнтацыі].",
+ "config-license": "Аўтарскія правы і ліцэнзія:",
+ "config-license-none": "Без інфармацыі пра ліцэнзію",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (грамадзкі набытак)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 ці болей позьняя",
+ "config-license-pd": "Грамадзкі набытак",
+ "config-license-cc-choose": "Выберыце іншую ліцэнзію Creative Commons",
+ "config-license-help": "Шматлікія адкрытыя вікі публікуюць увесь унёсак у праект на ўмовах [http://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 [http://www.php.net/manual/en/mail.configuration.php адпаведным чынам].\nКалі Вы не жадаеце выкарыстоўваць магчымасьці электроннай пошты, Вы можаце яе адключыць.",
+ "config-email-user": "Дазволіць электронную пошту для сувязі паміж удзельнікамі",
+ "config-email-user-help": "Дазволіць усім удзельнікам дасылаць адзін аднаму электронныя лісты, калі ўключаная адпаведная магчымасьць ў іх наладах.",
+ "config-email-usertalk": "Уключыць абвяшчэньні пра паведамленьні на старонцы абмеркаваньня",
+ "config-email-usertalk-help": "Дазваляе ўдзельнікам атрымліваць абвяшчэньні пра зьмены на старонцы абмеркаваньня, калі гэтая магчымасьць уключаная ў іх наладах.",
+ "config-email-watchlist": "Уключыць абвяшчэньні пра зьмены ў сьпісе назіраньня",
+ "config-email-watchlist-help": "Дазваляе ўдзельнікам атрымліваць абвяшчэньні пра зьмены ў іх сьпісе назіраньня, калі гэтая магчымасьць уключаная ў іх наладах.",
+ "config-email-auth": "Уключыць аўтэнтыфікацыю праз электронную пошту",
+ "config-email-auth-help": "Калі гэтая магчымасьць уключаная, удзельнікі павінны пацьвердзіць іх адрас электроннай пошты праз спасылку, якая дасылаецца ім праз электронную пошту. Яна дасылаецца і падчас зьмены адрасу электроннай пошты.\nТолькі аўтэнтыфікаваныя адрасы электроннай пошты могуць атрымліваць электронныя лісты ад іншых удзельнікаў, ці зьмяняць абвяшчэньні дасылаемыя праз электронную пошту.\nУключэньне гэтай магчымасьці '''рэкамэндуецца''' для адкрытых вікі, з-за магчымых злоўжываньняў магчымасьцямі электроннай пошты.",
+ "config-email-sender": "Адрас электроннай пошты для вяртаньня:",
+ "config-email-sender-help": "Увядзіце адрас электроннай пошты для вяртаньня ў якасьці адрасу дасылаемых электронных лістоў.\nСюды будуць дасылацца неатрыманыя электронныя лісты.\nШматлікія паштовыя сэрвэры патрабуюць, каб хаця б назва дамэну была слушнай.",
+ "config-upload-settings": "Загрузкі выяваў і файлаў",
+ "config-upload-enable": "Дазволіць загрузку файлаў",
+ "config-upload-help": "Дазвол загрузкі файлаў можа патэнцыйна пагражаць бясьпекі сэрвэра.\nДадатковую інфармацыю можна атрымаць ў [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security разьдзеле бясьпекі].\n\nКаб дазволіць загрузку файлаў, зьмяніце рэжым падкаталёга <code>images</code> у карэннай дырэкторыі MediaWiki так, каб ўэб-сэрвэр меў доступ на запіс.\nПотым дазвольце гэтую магчымасьць.",
+ "config-upload-deleted": "Дырэкторыя для выдаленых файлаў:",
+ "config-upload-deleted-help": "Выберыце дырэкторыю, у якой будуць захоўвацца выдаленыя файлы.\nУ ідэальным выпадку, яна не павінна мець доступу з Інтэрнэту.",
+ "config-logo": "URL-адрас лягатыпу:",
+ "config-logo-help": "Афармленьне MediaWiki па змоўчваньні ўключае прастору для лягатыпу памерам 135×160 піксэляў у верхнім левым куце.\nЗагрузіце выяву адпаведнага памеру і ўвядзіце тут URL-адрас.\n\nВы можаце ўжыць <code>$wgStylePath</code> або <code>$wgScriptPath</code>, калі ваш лягатып знаходзіцца адносна гэтых шляхоў.\n\nКалі Вы не жадаеце мець ніякага лягатыпу, пакіньце поле пустым.",
+ "config-instantcommons": "Дазволіць Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — магчымасьць, якая дазваляе вікі выкарыстоўваць выявы, гукі і іншыя мэдыя, якія знаходзяцца на сайце [//commons.wikimedia.org/ Wikimedia Commons].\nКаб гэта зрабіць, MediaWiki патрабуе доступу да Інтэрнэту.\n\nКаб даведацца болей пра гэтую магчымасьць, уключаючы інструкцыю пра тое, як яе ўстанавіць ў любой вікі, акрамя Wikimedia Commons, глядзіце [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos дакумэнтацыю].",
+ "config-cc-error": "Выбар ліцэнзіі Creative Commons ня даў вынікаў.\nУвядзіце назву ліцэнзіі ўручную.",
+ "config-cc-again": "Выберыце яшчэ раз…",
+ "config-cc-not-chosen": "Выберыце, якую ліцэнзію Creative Commons Вы жадаеце выкарыстоўваць і націсьніце «працягваць».",
+ "config-advanced-settings": "Дадатковыя налады",
+ "config-cache-options": "Налады кэшаваньня аб’ектаў:",
+ "config-cache-help": "Кэшаваньне аб’ектаў павялічвае хуткасьць працы MediaWiki праз кэшаваньне зьвестак, якія часта выкарыстоўваюцца.\nВельмі рэкамэндуем уключыць гэта для сярэдніх і буйных сайтаў, таксама будзе карысна для дробных сайтаў.",
+ "config-cache-none": "Без кэшаваньня (ніякія магчымасьці не страчваюцца, але хуткасьць працы буйных сайтаў можа зьнізіцца)",
+ "config-cache-accel": "Кэшаваньне аб’ектаў PHP (APC, XCache ці WinCache)",
+ "config-cache-memcached": "Выкарыстоўваць Memcached (патрабуе дадатковай канфігурацыі)",
+ "config-memcached-servers": "Сэрвэры memcached:",
+ "config-memcached-help": "Сьпіс IP-адрасоў, якія будуць выкарыстоўвацца Memcached.\nАдрасы павінны быць у асобным радку з пазначэньнем порту, які будзе выкарыстоўвацца. Напрыклад:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Вы выбралі Memcached у якасьці тыпу Вашага кэша, але не пазначылі ніякага сэрвэра",
+ "config-memcache-badip": "Вы ўвялі няслушны IP-адрас для Memcached: $1",
+ "config-memcache-noport": "Вы не пазначылі порт для выкарыстаньня сэрвэрам Memcached: $1.\nКалі Вы ня ведаеце порт, то па змоўчваньні выкарыстоўваецца 11211",
+ "config-memcache-badport": "Нумар порту Memcached павінен быць паміж $1 і $2",
+ "config-extensions": "Пашырэньні",
+ "config-extensions-help": "Пашырэньні пададзеныя вышэй, былі знойдзеныя ў Вашай дырэкторыі <code>./extensions</code>.\n\nЯны могуць патрабаваць дадатковых наладаў, але іх можна ўключыць зараз",
+ "config-skins": "Тэмы афармленьня",
+ "config-skins-help": "Пералічаныя вышэй тэмы афармленьня знойдзеныя ў вашай тэчцы <code>./skins</code>. Вы мусіце ўключыць як мінімум адну, а таксама абраць тэму па змоўчаньні.",
+ "config-skins-use-as-default": "Выкарыстоўваць па змоўчаньні гэтую тэму афармленьня",
+ "config-skins-missing": "Тэмы афармленьня ня знойдзеныя; MediaWiki будзе ўжываць рэзэрвовую тэму афармленьня, пакуль вы не ўсталюеце нешта адпаведнае.",
+ "config-skins-must-enable-some": "Вы павінны ўключыць як мінімум адну тэму афармленьня.",
+ "config-skins-must-enable-default": "Тэма афармленьня, абраная па змоўчаньні, мусіць быць уключаная.",
+ "config-install-alreadydone": "'''Папярэджаньне:''' здаецца, што Вы ўжо ўсталёўвалі MediaWiki і спрабуеце зрабіць гэтай зноў.\nКалі ласка, перайдзіце на наступную старонку.",
+ "config-install-begin": "Пасьля націску кнопкі «{{int:config-continue}}» пачнецца ўсталяваньне MediaWiki.\nКалі Вы жадаеце што-небудзь зьмяніць, націсьніце кнопку «{{int:config-back}}».",
+ "config-install-step-done": "зроблена",
+ "config-install-step-failed": "не атрымалася",
+ "config-install-extensions": "Уключаючы пашырэньні",
+ "config-install-database": "Налада базы зьвестак",
+ "config-install-schema": "Стварэньне схемы",
+ "config-install-pg-schema-not-exist": "Схема PostgreSQL не існуе",
+ "config-install-pg-schema-failed": "Немагчыма стварыць табліцу.\nУпэўніцеся, што карыстальнік «$1» можа пісаць у схему «$2».",
+ "config-install-pg-commit": "Захаваньне зьменаў",
+ "config-install-pg-plpgsql": "Праверка падтрымкі мовы PL/pgSQL",
+ "config-pg-no-plpgsql": "Вам неабходна ўсталяваць падтрымку мовы PL/pgSQL у базе зьвестак $1",
+ "config-pg-no-create-privs": "Рахунак, які Вы пазначылі для ўсталяваньня ня мае дастаткова правоў для стварэньня рахунку.",
+ "config-pg-not-in-role": "Пазначаны Вамі рахунак для ўэб-карыстальніка ўжо існуе.\nПазначаны Вамі рахунак для ўсталяваньня ня мае правоў і не зьяўляецца сябрам ролі ўэб-карыстальніка, таму немагчыма стварыць аб’екты, якія належаць ўэб-карыстальніку.\n\nЦяпер MediaWiki патрабуе, каб табліцы належалі да ўэб-карыстальніку. Калі ласка, пазначце іншы рахунак, ці націсьніце кнопку «Вярнуцца» і пазначце карыстальніка з неабходнымі для ўсталяваньня правамі.",
+ "config-install-user": "Стварэньне карыстальніка базы зьвестак",
+ "config-install-user-alreadyexists": "Удзельнік «$1» ужо існуе",
+ "config-install-user-create-failed": "Немагчыма стварыць ўдзельніка «$1»: $2",
+ "config-install-user-grant-failed": "Немагчыма даць правы удзельніку «$1»: $2",
+ "config-install-user-missing": "Пазначаны карыстальнік «$1» не існуе.",
+ "config-install-user-missing-create": "Пазначаны карыстальнік «$1» не існуе.\nКалі ласка, пазначце «стварыць рахунак», калі Вы жадаеце яго стварыць.",
+ "config-install-tables": "Стварэньне табліцаў",
+ "config-install-tables-exist": "'''Папярэджаньне''': Выглядае, што табліцы MediaWiki ужо існуюць.\nСтварэньне прапушчанае.",
+ "config-install-tables-failed": "'''Памылка''': немагчыма стварыць табліцы з-за наступнай памылкі: $1",
+ "config-install-interwiki": "Запаўненьне табліцы інтэрвікі па змоўчваньні",
+ "config-install-interwiki-list": "Немагчыма знайсьці файл <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Папярэджаньне''': выглядае, што табліца інтэрвікі ўжо запоўненая.\nСьпіс па змоўчваньні прапушчаны.",
+ "config-install-stats": "Ініцыялізацыі статыстыкі",
+ "config-install-keys": "Стварэньне сакрэтных ключоў",
+ "config-insecure-keys": "<strong>Папярэджаньне:</strong> {{PLURAL:$2|1=Ключ бясьпекі $1 створаны|Ключы бясьпекі $1 створаныя}} падчас усталяваньня, {{PLURAL:$2|1=не зьяўляецца паўнасьцю бясьпечным|не зьяўляюцца поўнасьцю бясьпечнымі}}. Рэкамэндуецца зьмяніць {{PLURAL:$2|1=яго ўручную|іх уручную}}.",
+ "config-install-updates": "Прадухіленьне запуску непатрэбных абнаўленьняў",
+ "config-install-updates-failed": "<strong>Памылка</strong>: устаўка ключоў абнаўленьня ў табліцы завершылася наступнай памылкай: $1",
+ "config-install-sysop": "Стварэньне рахунку адміністратара",
+ "config-install-subscribe-fail": "Немагчыма падпісацца на «mediawiki-announce»: $1",
+ "config-install-subscribe-notpossible": "cURL не ўсталяваны, <code>allow_url_fopen</code> недаступны.",
+ "config-install-mainpage": "Стварэньне галоўнай старонкі са зьместам па змоўчваньні",
+ "config-install-extension-tables": "Стварэньне табліцаў для ўключаных пашырэньняў",
+ "config-install-mainpage-failed": "Немагчыма ўставіць галоўную старонку: $1",
+ "config-install-done": "'''Віншуем!'''\nВы пасьпяхова ўсталявалі MediaWiki.\n\nПраграма ўсталяваньня стварыла файл <code>LocalSettings.php</code>.\nЁн утрымлівае ўсе Вашыя налады.\n\nВам неабходна загрузіць яго і захаваць у карэнную дырэкторыю Вашай вікі (у тую ж самую дырэкторыю, дзе знаходзіцца index.php). Загрузка павінна пачацца аўтаматычна.\n\nКалі загрузка не пачалася, ці Вы яе адмянілі, Вы можаце перазапусьціць яе націснуўшы на спасылку ніжэй:\n\n$3\n\n'''Заўвага''': калі Вы гэтага ня зробіце зараз, то створаны файл ня будзе даступны Вам потым, калі Вы выйдзеце з праграмы ўсталяваньня без яго загрузкі.\n\nКалі Вы гэта зробіце, Вы можаце '''[$2 ўвайсьці ў Вашую вікі]'''.",
+ "config-download-localsettings": "Загрузіць <code>LocalSettings.php</code>",
+ "config-help": "дапамога",
+ "config-help-tooltip": "націсьніце, каб разгарнуць",
+ "config-nofile": "Файл «$1» ня знойдзены. Ці быў ён выдалены?",
+ "config-extension-link": "Ці ведаеце вы, што вашая вікі падтрымлівае [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions пашырэньні]?\n\nВы можаце праглядзець [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category пашырэньні паводле катэгорыяў].",
+ "mainpagetext": "'''MediaWiki пасьпяхова ўсталяваная.'''",
+ "mainpagedocfooter": "Глядзіце [//meta.wikimedia.org/wiki/Help:Contents дапаможнік карыстальніка] для атрыманьня інфармацыі па карыстаньні вікі-праграмамі.\n\n== З чаго пачаць ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Сьпіс парамэтраў канфігурацыі]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Частыя пытаньні MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка паведамленьняў пра зьяўленьне новых вэрсіяў MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Пераклад MediaWiki на вашую мову]"
+}
diff --git a/includes/installer/i18n/be.json b/includes/installer/i18n/be.json
new file mode 100644
index 00000000..028ef628
--- /dev/null
+++ b/includes/installer/i18n/be.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Чаховіч Уладзіслаў"
+ ]
+ },
+ "config-desc": "Інсталятар MediaWiki",
+ "config-information": "Інфармацыя",
+ "config-localsettings-key": "Ключ абнаўлення:",
+ "config-your-language": "Ваша мова:",
+ "config-wiki-language": "Мова Вікі:",
+ "config-back": "← Назад",
+ "config-page-language": "Мова",
+ "config-page-welcome": "Сардэчна запрашаем у MediaWiki!",
+ "config-page-name": "Назва",
+ "config-page-options": "Настройкі",
+ "config-upload-settings": "Загрузка выяў і файлаў",
+ "mainpagetext": "'''MediaWiki паспяхова ўсталяваная.'''",
+ "mainpagedocfooter": "Гл. [//meta.wikimedia.org/wiki/Help:Contents Дапаможнік карыстальніка (англ.)] па далейшыя звесткі аб карыстанні вікі-праграмамі.\n\n== З чаго пачаць ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Пералік параметраў канфігурацыі (англ.)]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧАПЫ MediaWiki (англ.)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ліставанне аб выпусках MediaWiki (англ.)]"
+}
diff --git a/includes/installer/i18n/bg.json b/includes/installer/i18n/bg.json
new file mode 100644
index 00000000..32e4c397
--- /dev/null
+++ b/includes/installer/i18n/bg.json
@@ -0,0 +1,293 @@
+{
+ "@metadata": {
+ "authors": [
+ "DCLXVI",
+ "아라",
+ "StanProg"
+ ]
+ },
+ "config-desc": "Инсталатор на МедияУики",
+ "config-title": "Инсталиране на МедияУики $1",
+ "config-information": "Информация",
+ "config-localsettings-upgrade": "Беше открит файл <code>LocalSettings.php</code>.\nЗа надграждане на съществуващата инсталация, необходимо е в кутията по-долу да се въведе стойността на <code>$wgUpgradeKey</code>.\nТази информация е налична в <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Беше открит файл <code>LocalSettings.php</code>.\nЗа надграждане на наличната инсталация, необходимо е да се стартира <code>update.php</code>",
+ "config-localsettings-key": "Ключ за надграждане:",
+ "config-localsettings-badkey": "Предоставеният ключ е неправилен.",
+ "config-upgrade-key-missing": "Беше открита съществуваща инсталация на МедияУики.\nЗа надграждане на съществуващата инсталация, необходимо е да се постави следният ред в края на файла <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Съществуващият файл <code>LocalSettings.php</code> изглежда непълен.\nПроменливата $1 не е зададена.\nНеобходимо е да се редактира файлът <code>LocalSettings.php</code> и да се зададе променливата, след което да се натисне „{{int:Config-continue}}“.",
+ "config-localsettings-connection-error": "Възникна грешка при свързване с базата данни чрез посочените настройки в <code>LocalSettings.php</code>. Преди следващ опит за свързване, необходимо е настройките да бъдат коригирани.\n\n$1",
+ "config-session-error": "Грешка при създаване на сесия: $1",
+ "config-session-expired": "Срокът на валидност на данните от сесията са изтекли.\nПродължителността на сесиите е настроена на $1.\nТова може да бъде увеличено чрез настройване на <code>session.gc_maxlifetime</code> в php.ini.\nНеобходимо е рестартиране на инсталационния процес.",
+ "config-no-session": "Данните за сесията бяха загубени!\nПроверете вашия php.ini и се уверете, че на <code>session.save_path</code> е настроена подходящата директория.",
+ "config-your-language": "Вашият език:",
+ "config-your-language-help": "Избиране на език за използване по време на инсталацията.",
+ "config-wiki-language": "Език на уикито:",
+ "config-wiki-language-help": "Избиране на език, на който ще е основното съдържание на уикито.",
+ "config-back": "← Връщане",
+ "config-continue": "Продължаване →",
+ "config-page-language": "Език",
+ "config-page-welcome": "Добре дошли в МедияУики!",
+ "config-page-dbconnect": "Свързване с базата от данни",
+ "config-page-upgrade": "Надграждане на съществуваща инсталация",
+ "config-page-dbsettings": "Настройки на базата от данни",
+ "config-page-name": "Име",
+ "config-page-options": "Настройки",
+ "config-page-install": "Инсталиране",
+ "config-page-complete": "Готово!",
+ "config-page-restart": "Рестартиране на инсталацията",
+ "config-page-readme": "Информация за софтуера",
+ "config-page-releasenotes": "Бележки за версията",
+ "config-page-copying": "Лицензно споразумение",
+ "config-page-upgradedoc": "Надграждане",
+ "config-page-existingwiki": "Съществуващо уики",
+ "config-help-restart": "Необходимо е потвърждение за изтриване на всички въведени и съхранени данни и започване отначало на процеса по инсталация.",
+ "config-restart": "Да, започване отначало",
+ "config-welcome": "=== Проверка на средата ===\nЩе бъдат извършени основни проверки, които да установят дали средата е подходяща за инсталиране на МедияУики.\nАко е необходима помощ по време на инсталацията, резултатите от направените проверки трябва също да бъдат предоставени.",
+ "config-copyright": "=== Авторски права и Условия ===\n\n$1\n\nТази програма е свободен софтуер, който може да се променя и/или разпространява според Общия публичен лиценз на GNU, както е публикуван от Free Software Foundation във версия на Лиценза 2 или по-късна версия.\n\nТази програма се разпространява с надеждата, че ще е полезна, но '''без каквито и да е гаранции'''; без дори косвена гаранция за '''продаваемост''' или '''прогодност за конкретна употреба'''.\nЗа повече подробности се препоръчва преглеждането на Общия публичен лиценз на GNU.\n\nКъм програмата трябва да е приложено <doclink href=Copying>копие на Общия публичен лиценз на GNU</doclink>; ако не, можете да пишете на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. или да [http://www.gnu.org/copyleft/gpl.html го прочетете онлайн].",
+ "config-sidebar": "* [//www.mediawiki.org Сайт на МедияУики]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Наръчник на потребителя]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Наръчник на администратора]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧЗВ]\n----\n* <doclink href=Readme>Документация</doclink>\n* <doclink href=ReleaseNotes>Бележки за версията</doclink>\n* <doclink href=Copying>Авторски права</doclink>\n* <doclink href=UpgradeDoc>Обновяване</doclink>",
+ "config-env-good": "Средата беше проверена.\nИнсталирането на МедияУики е възможно.",
+ "config-env-bad": "Средата беше проверена.\nНе е възможна инсталация на МедияУики.",
+ "config-env-php": "Инсталирана е версия на PHP $1.",
+ "config-unicode-using-utf8": "Използване на utf8_normalize.so от Brion Vibber за нормализация на Уникод.",
+ "config-unicode-using-intl": "Използване на разширението [http://pecl.php.net/intl intl PECL] за нормализация на Уникод.",
+ "config-unicode-pure-php-warning": "'''Предупреждение''': [http://pecl.php.net/intl Разширението intl PECL] не е налично за справяне с нормализацията на Уникод, превключване към по-бавното изпълнение на чист PHP.\nАко сайтът е с голям трафик, препоръчително е запознаването с [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормализацията на Уникод].",
+ "config-unicode-update-warning": "'''Предупреждение''': Инсталираната версия на Обвивката за нормализация на Unicode използва по-старата версия на библиотеката на [http://site.icu-project.org/ проекта ICU].\nНеобходимо е да [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations инсталирате по-нова верия], в случай че сте загрижени за използването на Unicode.",
+ "config-no-db": "Не може да бъде открит подходящ драйвер за база от данни! Необходимо е да се инсталира драйвер за база от данни за PHP.\nПоддържат се следните типове базни от данни: $1.\n\nАко използвате споделен хостинг, помолете доставчика на услугата да инсталира подходящ драйвер за база от данни.\nАко сами сте компилирали PHP, преконфигурирайте го с включен клиент за база от данни, например чрез използване на <code>./configure --with-mysql</code>.\nАко сте инсталирали PHP от пакет за Debian или Убунту, необходимо е, също така, да инсталирате и модула php5-mysql.",
+ "config-outdated-sqlite": "<strong>Предупреждение:</strong> имате инсталиран SQLite $1, а минималната допустима версия е $2. SQLite ще бъде недостъпна за ползване.",
+ "config-no-fts3": "'''Предупреждение''': SQLite е компилирана без [//sqlite.org/fts3.html модула FTS3], затова възможностите за търсене няма да са достъпни.",
+ "config-magic-quotes-runtime": "'''Фатално: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] е активирана!'''\nТова може да повреди непредвидимо въвеждането на данните.\nИнсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
+ "config-magic-quotes-sybase": "'''Фатално: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] е активирана!'''\nТова може да повреди непредвидимо въвеждането на данните.\nИнсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
+ "config-mbstring": "'''Фатално: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] е активирана!'''\nТова може да повреди непредвидимо въвеждането на данните.\nИнсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
+ "config-safe-mode": "'''Предупреждение:''' PHP работи в [http://www.php.net/features.safe-mode безопасен режим].\nТова може да създаде проблеми, особено ако качването на файлове е разрешено, както и при поддръжката на <code>math</code>.",
+ "config-xml-bad": "Липсва XML модулът на PHP.\nМедияУики се нуждае от някои функции от този модул и няма да работи при наличната конфигурация.\nПри Mandrake, необходимо е да се инсталира пакетът php-xml.",
+ "config-pcre-no-utf8": "'''Фатално''': Модулът 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-ctype": "'''Фатално''': Необходимо е PHP да бъде компилиран с поддръжка на [http://www.php.net/manual/en/ctype.installation.php разширението Ctype].",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] е инсталиран",
+ "config-apc": "[http://www.php.net/apc APC] е инсталиран",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] е инсталиран",
+ "config-no-cache": "'''Предупреждение:''' Не бяха открити [http://www.php.net/apc APC] [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].\nОбектното кеширане не е включено.",
+ "config-diff3-bad": "GNU diff3 не беше намерен.",
+ "config-imagemagick": "Открит е ImageMagick: <code>$1</code>.\nПреоразмеряването на картинки ще бъде включено ако качването на файлове бъде разрешено.",
+ "config-gd": "Открита е вградена графичната библиотека GD.\nАко качването на файлове бъде включено, ще бъде включена възможността за преоразмеряване на картинки.",
+ "config-no-scaling": "Не са открити библиотеките GD или ImageMagick.\nПреоразмеряването на картинки ще бъде изключено.",
+ "config-no-uri": "'''Грешка:''' Не може да се определи текущия адрес.\nИнсталация беше прекратена.",
+ "config-using-server": "Използване на сървърното име \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Използване на сървърния адрес (URL) \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Предупреждение:''' Папката по подразбиране за качване <code>$1</code> е уязвима от изпълнение на зловредни скриптове.\nВъпреки че МедияУики извършва проверка за заплахи в сигурността на всички качени файлове, силно препоръчително е да се [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security затвори тази уязвимост в сигурността] преди разрешаване за качване на файлове.",
+ "config-brokenlibxml": "Вашата система използа комбинация от версии на PHP и libxml2, които са с много грешки и могат да причинят скрити повреди на данните в МедияУики или други уеб приложения.\nНеобходимо е обновяване до PHP 5.2.9 или по-нова версия и libxml2 2.7.3 или по-нова версия ([//bugs.php.net/bug.php?id=45996 докладвана грешка при PHP]).\nИнсталацията беше прекратена.",
+ "config-suhosin-max-value-length": "Suhosin е инсталиран и ограничава дължината на параметъра GET на $1 байта. Компонентът на МедияУики ResourceLoader ще може да пренебрегне частично това ограничение, но това ще намали производителността. По възможност е препоръчително да се настрои <code>suhosin.get.max_value_length</code> на 1024 или по-голяма стойност в <code>php.ini</code> и в LocalSettings.php да се настрои <code>$wgResourceLoaderMaxQueryLength</code> със същата стойност.",
+ "config-db-type": "Тип на базата от данни:",
+ "config-db-host": "Хост на базата от данни:",
+ "config-db-host-help": "Ако базата от данни е на друг сървър, в кутията се въвежда името на хоста или IP адреса.\n\nАко се използва споделен уеб хостинг, доставчикът на услугата би трябвало да е предоставил в документацията си коректния хост.\n\nАко инсталацията протича на Windows-сървър и се използва MySQL, използването на \"localhost\" може да е неприемливо. В такива случаи се използва \"127.0.0.1\" за локален IP адрес.\n\nПри използване на PostgreSQL, това поле се оставя празно, за свързване чрез Unix socket.",
+ "config-db-host-oracle": "TNS на базата данни:",
+ "config-db-wiki-settings": "Идентифициране на това уики",
+ "config-db-name": "Име на базата от данни:",
+ "config-db-name-help": "Избира се име, което да идентифицира уикито.\nТо не трябва да съдържа интервали.\n\nАко се използва споделен хостинг, доставчикът на услугата би трябвало да е предоставил или име на базата от данни, която да бъде използвана, или да позволява създаването на бази от данни чрез контролния панел.",
+ "config-db-name-oracle": "Схема на базата от данни:",
+ "config-db-install-account": "Потребителска сметка за инсталацията",
+ "config-db-username": "Потребителско име за базата от данни:",
+ "config-db-password": "Парола за базата от данни:",
+ "config-db-password-empty": "Въведете парола за новия потребител на базата от данни: $1.\nВъпреки че е допустимо да се създават потребители без пароли, това е незащитено действие.",
+ "config-db-install-username": "Въвежда се потребителско име, което ще се използва за свързване с базата от данни по време на процеса по инсталация.\nТова не е потребителско име за сметка в МедияУики; това е потребителско име за базата от данни.",
+ "config-db-install-password": "Въвежда се парола, която ще бъде използвана за свързване с базата от данни по време на инсталационния процес.\nТова не е парола за сметка в МедияУики; това е парола за базата от данни.",
+ "config-db-install-help": "Въвеждат се потребителско име и парола, които ще бъдат използвани за свързване с базата от данни по време на инсталационния процес.",
+ "config-db-account-lock": "Използване на същото потребителско име и парола по време на нормална работа",
+ "config-db-wiki-account": "Потребителска сметка за нормална работа",
+ "config-db-wiki-help": "Въвежда се потребителско име и парола, които ще се използват при нормалното функциониране на уикито.\nАко сметката не съществува и използваната при инсталацията сметка има необходимите права, тази потребителска сметка ще бъде създадена с минималните необходими права за работа с уикито.",
+ "config-db-prefix": "Представка за таблиците в базата от данни:",
+ "config-db-prefix-help": "Ако е необходимо да се сподели базата от данни между няколко уикита или между МедияУики и друго уеб приложение, може да се добави представка пред имената на таблиците, за да се избегнат конфликти.\nНе се използват интервали.\n\nТова поле обикновено се оставя празно.",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 бинарно",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 с обратна съвестимост с UTF-8",
+ "config-mysql-old": "Изисква се MySQL $1 или по-нова версия, наличната версия е $2.",
+ "config-db-port": "Порт на базата от данни:",
+ "config-db-schema": "Схема за МедияУики",
+ "config-db-schema-help": "Схемата по-горе обикновено е коректна.\nПромени се извършват ако наистина е необходимо.",
+ "config-pg-test-error": "Невъзможно свързване с базата данни '''$1''': $2",
+ "config-sqlite-dir": "Директория за данни на SQLite:",
+ "config-sqlite-dir-help": "SQLite съхранява всички данни в един файл.\n\nПо време на инсталацията уеб сървърът трябва да има права за писане в посочената директория.\n\nТя '''не трябва''' да е достъпна през уеб, затова не е там, където са PHP файловете.\n\nИнсталаторът ще съхрани заедно с нея файл <code>.htaccess</code>, но ако този метод пропадне, някой може да придобие даостъп до суровите данни от базата от данни.\nТова включва сурови данни за потребителите (адреси за е-поща, хеширани пароли), както и изтрити версии на страници и друга чувствителна и с ограничен достъп информация от и за уикито.\n\nБазата от данни е препоръчително да се разположи на друго място, например в <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-type-mysql": "MySQL (или съвместима)",
+ "config-type-mssql": "Microsoft SQL Сървър",
+ "config-support-info": "МедияУики поддържа следните системи за бази от данни:\n\n$1\n\nАко не виждате желаната за използване система в списъка по-долу, следвайте инструкциите за активиране на поддръжка по-горе.",
+ "config-dbsupport-mysql": "* $1 е най-добре поддържаната система за база от данни, с най-добра поддръжка от МедияУики ([http://www.php.net/manual/en/mysql.installation.php Как се компилира PHP с поддръжка на MySQL])",
+ "config-dbsupport-postgres": "* $1 е популярна система за бази от данни с отворен изходен код, която е алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php как се компилира PHP с поддръжка на PostgreSQL]). Възможно е все още да има грешки, затова не се препоръчва да се използва в общодостъпна среда.",
+ "config-dbsupport-sqlite": "* $1 е лека система за база от данни, която е много добре поддържана. ([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-header-mysql": "Настройки за MySQL",
+ "config-header-postgres": "Настройки за PostgreSQL",
+ "config-header-sqlite": "Настройки за SQLite",
+ "config-header-oracle": "Настройки за Oracle",
+ "config-header-mssql": "Настройки за Microsoft SQL Сървър",
+ "config-invalid-db-type": "Невалиден тип база от данни",
+ "config-missing-db-name": "Необходимо е да се въведе стойност за \"Име на базата от данни\"",
+ "config-missing-db-host": "Необходимо е да се въведе стойност за \"Хост на базата от данни\"",
+ "config-missing-db-server-oracle": "Необходимо е да се въведе стойност за \"Database TNS\"",
+ "config-invalid-db-server-oracle": "Невалиден TNS на базата от данни \"$1\".\nДопустими са само ASCII букви (a-z, A-Z), цифри (0-9), символите за долна черта (_) и точка (.).",
+ "config-invalid-db-name": "Невалидно име на базата от данни \"$1\".\nИзползват се само ASCII букви (a-z, A-Z), цифри (0-9), долни черти (_) и тирета (-).",
+ "config-invalid-db-prefix": "Невалидна представка за базата от данни \"$1\".\nПозволени са само ASCII букви (a-z, A-Z), цифри (0-9), долни черти (_) и тирета (-).",
+ "config-connection-error": "$1.\n\nНеобходимо е да се проверят хостът, потребителското име и паролата, след което да се опита отново.",
+ "config-invalid-schema": "Невалидна схема за МедияУики \"$1\".\nДопустими са само ASCII букви (a-z, A-Z), цифри (0-9) и долни черти (_).",
+ "config-db-sys-create-oracle": "Инсталаторът поддържа само сметка SYSDBA за създаване на нова сметка.",
+ "config-db-sys-user-exists-oracle": "Потребителската сметка \"$1\" вече съществува. SYSDBA може да се използва само за създаване на нова сметка!",
+ "config-postgres-old": "Изисква се PostgreSQL $1 или по-нова версия, наличната версия е $2.",
+ "config-sqlite-name-help": "Избира се име, което да идентифицира уикито.\nНе се използват интервали или тирета.\nТова име ще се използва за име на файла за данни на SQLite.",
+ "config-sqlite-parent-unwritable-group": "Дикректорията за данни <code><nowiki>$1</nowiki></code> не може да бъде създадена, тъй като уеб сървърът няма права за писане в родителската директория <code><nowiki>$2</nowiki></code>.\n\nИнсталаторът разпознава потребителското име, с което работи уеб сървърът.\nУверете се, че той притежава права за писане в директорията <code><nowiki>$3</nowiki></code> преди да продължите.\nВ Unix/Линукс системи можете да използвате:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Дикректорията за данни <code><nowiki>$1</nowiki></code> не може да бъде създадена, тъй като уеб сървърът няма права за писане в родителската директория <code><nowiki>$2</nowiki></code>.\n\nИнсталаторът не може да определи потребителското име, с което работи уеб сървърът.\nУверете се, че в директория <code><nowiki>$3</nowiki></code> може да бъде писано от уебсървъра (или от други потребители!) преди да продължите.\nНа Unix/Линукс системи можете да използвате:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Грешка при създаване на директорията за данни \"$1\".\nПроверете местоположението ѝ и опитайте отново.",
+ "config-sqlite-dir-unwritable": "Уебсървърът няма права за писане в директория \"$1\".\nПроменете правата му така, че да може да пише в нея, и опитайте отново.",
+ "config-sqlite-connection-error": "$1.\n\nПроверете директорията за данни и името на базата от данни по-долу и опитайте отново.",
+ "config-sqlite-readonly": "Файлът <code>$1</code> няма права за писане.",
+ "config-sqlite-cant-create-db": "Файлът за базата от данни <code>$1</code> не може да бъде създаден.",
+ "config-sqlite-fts3-downgrade": "Липсва поддръжката на FTS3 за PHP, извършен беше downgradе на таблиците",
+ "config-can-upgrade": "В базата от данни има таблици за МедияУики.\nЗа надграждането им за MediaWiki $1, натиска се '''Продължаване'''.",
+ "config-upgrade-done": "Обновяването приключи.\n\nВече е възможно [$1 да използвате уикито].\n\nАко е необходимо, възможно е файлът <code>LocalSettings.php</code> да бъде създаден отново чрез натискане на бутона по-долу.\nТова '''не е препоръчително действие''', освен ако не срещате затруднения с уикито.",
+ "config-upgrade-done-no-regenerate": "Обновяването приключи.\n\nВече е възможно [$1 да използвате уикито].",
+ "config-regenerate": "Създаване на LocalSettings.php →",
+ "config-show-table-status": "Заявката <code>SHOW TABLE STATUS</code> не сполучи!",
+ "config-unknown-collation": "'''Предупреждение:''' Базата от данни използва неразпозната колация.",
+ "config-db-web-account": "Сметка за уеб достъп до базата от данни",
+ "config-db-web-help": "Избиране на потребителско име и парола, които уеб сървърът ще използва да се свързва с базата от данни при обичайната работа на уикито.",
+ "config-db-web-account-same": "Използване на същата сметка като при инсталацията.",
+ "config-db-web-create": "Създаване на сметката ако все още не съществува",
+ "config-db-web-no-create-privs": "Посочената сметка за инсталацията не разполага с достатъчно права за създаване на нова сметка.\nНеобходимо е посочената сметка вече да съществува.",
+ "config-mysql-engine": "Хранилище на данни:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Предупреждение''': Избрана е MyISAM като система за складиране в MySQL, която не се препоръчва за използване с МедияУики, защото:\n* почти не поддържа паралелност заради заключване на таблиците\n* е по-податлива на повреди в сравнение с други системи\n* кодът на МедияУики не винаги поддържа MyISAM коректно\n\nАко инсталацията на MySQL поддържа InnoDB, силно е препоръчително да се използва тя.\nАко инсталацията на MySQL не поддържа InnoDB, вероятно е време за обновяване.",
+ "config-mysql-engine-help": "'''InnoDB''' почти винаги е най-добрата възможност заради навременната си поддръжка.\n\n'''MyISAM''' може да е по-бърза при инсталации с един потребител или само за четене.\nБазите от данни MyISAM се повреждат по-често от InnoDB.",
+ "config-mysql-charset": "Набор от символи в базата от данни:",
+ "config-mysql-binary": "Бинарен",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "В '''бинарен режим''' МедияУики съхранява текстовете в UTF-8 в бинарни полета в базата от данни.\nТова е по-ефективно от UTF-8 режима на MySQL и позволява използването на пълния набор от символи в Уникод.\n\nВ '''UTF-8 режим''' MySQL ще знае в кой набор от символи са данните от уикито и ще може да ги показва и променя по подходящ начин, но няма да позволява складиране на символи извън [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основния многоезичен набор].",
+ "config-site-name": "Име на уикито:",
+ "config-site-name-help": "Това име ще се показва в заглавната лента на браузъра и на различни други места.",
+ "config-site-name-blank": "Необходимо е да се въведе име на уикито.",
+ "config-project-namespace": "Именно пространство на проекта:",
+ "config-ns-generic": "Проект",
+ "config-ns-site-name": "Същото като името на уикито: $1",
+ "config-ns-other": "Друго (уточняване)",
+ "config-ns-other-default": "МоетоУики",
+ "config-project-namespace-help": "Следвайки примера на Уикипедия, много уикита съхраняват страниците си с правила в \"'''именно пространство на проекта'''\", отделно от основното съдържание.\nВсички заглавия на страниците в това именно пространство започват с определена представка, която може да бъде зададена тук.\nОбикновено представката произлиза от името на уикито, но не може да съдържа символи като \"#\" или \":\".",
+ "config-ns-invalid": "Посоченото именно пространство \"<nowiki>$1</nowiki>\" е невалидно.\nНеобходимо е да бъде посочено друго.",
+ "config-ns-conflict": "Посоченото именно пространство \"<nowiki>$1</nowiki>\" е в конфликт с използваното по подразбиране именно пространство MediaWiki.\nНеобходимо е да се посочи друго именно пространство.",
+ "config-admin-box": "Администраторска сметка",
+ "config-admin-name": "Потребителско име:",
+ "config-admin-password": "Парола:",
+ "config-admin-password-confirm": "Парола (повторно):",
+ "config-admin-help": "Въвежда се предпочитаното потребителско име, например \"Иванчо Иванчев\".\nТова ще е потребителското име, което администраторът ще използва за влизане в уикито.",
+ "config-admin-name-blank": "Необходимо е да бъде въведено потребителско име на администратора.",
+ "config-admin-name-invalid": "Посоченото потребителско име \"<nowiki>$1</nowiki>\" е невалидно.\nНеобходимо е да се посочи друго.",
+ "config-admin-password-blank": "Неовходимо е да се въведе парола за администраторската сметка.",
+ "config-admin-password-mismatch": "Двете въведени пароли не съвпадат.",
+ "config-admin-email": "Адрес за електронна поща:",
+ "config-admin-email-help": "Въвеждането на адрес за е-поща позволява получаване на е-писма от другите потребители на уикито, възстановяване на изгубена или забравена парола, оповестяване при промени в страниците от списъка за наблюдение. Това поле може да бъде оставено празно.",
+ "config-admin-error-user": "Възникна вътрешна грешка при създаване на администратор с името \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Възникна вътрешна грешка при задаване на парола за администратора \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Въведен е невалиден адрес за електронна поща",
+ "config-subscribe": "Абониране за [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce пощенския списък за нови версии].",
+ "config-subscribe-help": "Това е пощенски списък с малко трафик, който се използва за съобщения при излизане на нови версии, както и за важни проблеми със сигурността.\nАбонирането е препоръчително, както и надграждането на инсталацията на МедияУики при излизането на нова версия.",
+ "config-subscribe-noemail": "Опитахте да се абонирате за пощенския списък за нови версии без да посочите адрес за електронна поща.\nНеобходимо е да се предостави адрес за електронна поща, в случай че желаете да се абонирате за пощенския списък.",
+ "config-almost-done": "Инсталацията е почти готова!\nВъзможно е пропускане на оставащата конфигурация и моментално инсталиране на уикито.",
+ "config-optional-continue": "Задаване на допълнителни въпроси.",
+ "config-optional-skip": "Достатъчно, инсталиране на уикито.",
+ "config-profile": "Профил на потребителските права:",
+ "config-profile-wiki": "Отворено уики",
+ "config-profile-no-anon": "Необходимо е създаване на сметка",
+ "config-profile-fishbowl": "Само одобрени редактори",
+ "config-profile-private": "Затворено уики",
+ "config-profile-help": "Уикитата функционират най-добре, когато позволяват на възможно най-много хора да ги редактират.\nВ МедияУики лесно се преглеждат последните промени и се възстановяват поражения от недобронамерени потребители.\n\nВъпреки това мнозина смятат МедияУики за полезен софтуер по различни начини и често е трудно да се убедят всички от предимствата на уики модела.\nЗатова се предоставя възможност за избор.\n\nУикитата от типа '''{{int:config-profile-wiki}}''' позволяват на всички потребители да редактират, дори и без регистрация.\nУикитата от типа '''{{int:config-profile-no-anon}}''' позволяват достъп до страниците и редактирането им само след създаване на потребителска сметка.\n\nУики, което е '''{{int:config-profile-fishbowl}}''' позволява на всички да преглеждат страниците, но само предварително одобрени редактори могат да редактират съдържанието.\nВ '''{{int:config-profile-private}}''' само предварително одобрени потребители могат да четат и редактират съдържанието.\n\nДетайлно обяснение на конфигурациите на потребителските права е достъпно след инсталацията в [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights Наръчника за потребителски права].",
+ "config-license": "Авторски права и лиценз:",
+ "config-license-none": "Без лиценз",
+ "config-license-cc-by-sa": "Криейтив Комънс Признание-Споделяне на споделеното",
+ "config-license-cc-by": "Криейтив Комънс Признание",
+ "config-license-cc-by-nc-sa": "Криейтив Комънс Признание-Некомерсиално-Споделяне на споделеното",
+ "config-license-cc-0": "Криейтив Комънс Нула (обществено достояние)",
+ "config-license-gfdl": "Лиценз за свободна документация на GNU 1.3 или по-нов",
+ "config-license-pd": "Обществено достояние",
+ "config-license-cc-choose": "Избиране на друг лиценз от Криейтив Комънс",
+ "config-license-help": "Много публични уикита поставят всички приноси под [http://freedomdefined.org/Definition/Bg свободен лиценз].\nТова помага създаване на усещане за общност и насърчава дългосрочните приноси.\nТова не е необходимо за частни или корпоративни уикита.\n\nАко е необходимо да се използват текстове от Уикипедия, както и Уикипедия да може да използва текстове от уикито, необходимо е да се избере лиценз '''Криейтив Комънс Признание-Споделяне на споделеното'''.\n\nЛицензът за свободна документация на GNU е старият лиценз на съдържанието на Уикипедия.\nТой все още е валиден лиценз, но някои негови условия са трудни за разбиране и правят по-сложни повторното използване и интерпретацията.",
+ "config-email-settings": "Настройки за е-поща",
+ "config-enable-email": "Разрешаване на изходящи е-писма",
+ "config-enable-email-help": "За да работят възможностите за използване на е-поща, необходимо е [http://www.php.net/manual/en/mail.configuration.php настройките за поща на PHP] да бъдат конфигурирани правилно.\nАко няма да се използват услугите за е-поща в уикито, те могат да бъдат изключени тук.",
+ "config-email-user": "Позволяване на потребителите да си изпращат е-писма през уикито",
+ "config-email-user-help": "Позволяване на потребителите да си изпращат е-писма ако са разрешили това в настройките си.",
+ "config-email-usertalk": "Оповестяване при промяна на потребителската беседа",
+ "config-email-usertalk-help": "Позволява на потребителите да получават оповестяване при промяна на беседата им, ако това е разрешено в настройките им.",
+ "config-email-watchlist": "Оповестяване за списъка за наблюдение",
+ "config-email-watchlist-help": "Позволява на потребителите да получават оповестяване за техните наблюдавани страници, ако това е разрешено в настройките им.",
+ "config-email-auth": "Потвърждаване на адреса за електронна поща",
+ "config-email-auth-help": "Ако тази настройка е включена, потребителите трябва да потвърдят адреса си за е-поща чрез препратка, която им се изпраща при настройване или промяна.\nСамо валидните адреси могат да получават е-писма от други потребители или да променят писмата за оповестяване.\nНастройването на това е '''препоръчително''' за публични уикита заради потенциални злоупотреби с възможностите за електронна поща.",
+ "config-email-sender": "Адрес за обратна връзка:",
+ "config-email-sender-help": "Въвежда се адрес за електронна поща, който ще се използва за обратен адрес при изходящи е-писма.\nТова е адресът, на който ще се получават върнатите и неполучени писма.\nМного е-пощенски сървъри изискват поне домейн името да е валидно.",
+ "config-upload-settings": "Картинки и качване на файлове",
+ "config-upload-enable": "Позволяне качването на файлове",
+ "config-upload-help": "Качването на файлове е възможно да доведе до пробели със сигурността на сървъра.\nПовече информация по темата има в [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security раздела за сигурност] в Наръчника.\n\nЗа позволяване качването на файлове, необходимо е уебсървърът да може да записва в поддиректорията на МедияУики <code>images</code>.\nСлед като това условие е изпълнено, функционалността може да бъде активирана.",
+ "config-upload-deleted": "Директория за изтритите файлове:",
+ "config-upload-deleted-help": "Избиране на директория, в която ще се складират изтритите файлове.\nВ най-добрия случай тя не трябва да е достъпна през уеб.",
+ "config-logo": "Адрес на логото:",
+ "config-logo-help": "Обликът по подразбиране на МедияУики вклчва място с размери 135х160 пиксела за лого над страничното меню.\nАко има наличен файл с подходящ размер, неговият адрес може да бъде посочен тук.\n\nМоже да се използва <code>$wgStylePath</code> или <code>$wgScriptPath</code> ако логото е относително към тези пътища.\n\nАко не е необходимо лого, полето може да се остави празно.",
+ "config-instantcommons": "Включване на Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функционалност, която позволява на уикитата да използват картинки, звуци и друга медиа от сайта на Уикимедия [//commons.wikimedia.org/ Общомедия].\nЗа да е възможно това, МедияУики изисква достъп до Интернет.\n\nПовече информация за тази функционалност, както и инструкции за настройване за други уикита, различни от Общомедия, е налична в [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos наръчника].",
+ "config-cc-error": "Избирането на лиценз на Криейтив Комънс не даде резултат.\nНеобходимо е името на лиценза да бъде въведено ръчно.",
+ "config-cc-again": "Повторно избиране...",
+ "config-cc-not-chosen": "Изберете кой лиценз на Криейтив Комънс желаете и щракнете \"proceed\".",
+ "config-advanced-settings": "Разширена конфигурация",
+ "config-cache-options": "Настройки за обектното кеширане:",
+ "config-cache-help": "Обектното кеширане се използва за подобряване на скоростта на МедияУики чрез кеширане на често използваните данни.\nСилно препоръчително е на средните и големите сайтове да включат тази настройка, но малките също могат да се възползват от нея.",
+ "config-cache-none": "Без кеширане (не се премахва от функционалността, но това влияе на скоростта на по-големи уикита)",
+ "config-cache-accel": "PHP обектно кеширане (APC, XCache или WinCache)",
+ "config-cache-memcached": "Използване на Memcached (изисква допълнителни настройки и конфигуриране)",
+ "config-memcached-servers": "Memcached сървъри:",
+ "config-memcached-help": "Списък с IP адреси за използване за Memcached.\nНеобходимо е да бъдат разделени по един на ред, както и да е посочен порта. Пример:\n127.0.0.1:11211\n192.168.1.25:1234",
+ "config-memcache-needservers": "Избран е Memcached като складиращ тип, но не са посочени сървъри.",
+ "config-memcache-badip": "Беше въведен невалиден IP адрес за Memcached: $1.",
+ "config-memcache-noport": "Не е посочен порт за използване за Memcached сървъра: $1.\nВ случай, че не знаете порта, този по подразбиране е 11211.",
+ "config-memcache-badport": "Портовете за Memcached трябва да бъдат между $1 и $2.",
+ "config-extensions": "Разширения",
+ "config-extensions-help": "Разширенията от списъка по-горе бяха открити в директорията <code>./extensions</code>.\n\nВъзможно е те да изискват допълнително конфигуриране, но сега могат да бъдат включени.",
+ "config-skins": "Облици",
+ "config-install-alreadydone": "'''Предупреждение:''' Изглежда вече сте инсталирали МедияУики и се опитвате да го инсталирате отново.\nПродължете към следващата страница.",
+ "config-install-begin": "Инсталацията на МедияУики ще започне след натискане на бутона „{{int:config-continue}}“.\nВ случай, че е необходимо да се направят промени, използва се бутона „{{int:config-back}}“.",
+ "config-install-step-done": "готово",
+ "config-install-step-failed": "неуспешно",
+ "config-install-extensions": "Добавяне на разширенията",
+ "config-install-database": "Създаване на базата от данни",
+ "config-install-schema": "Създаване на схема",
+ "config-install-pg-schema-not-exist": "PostgreSQL схемата не съществува",
+ "config-install-pg-schema-failed": "Създаването на таблиците пропадна.\nНеобходимо е потребител \"$1\" да има права за писане в схемата \"$2\".",
+ "config-install-pg-commit": "Извършване на промени",
+ "config-install-pg-plpgsql": "Проверяване за езика PL/pgSQL",
+ "config-pg-no-plpgsql": "Необходимо е да се инсталира езикът PL/pgSQL в базата от данни $1",
+ "config-pg-no-create-privs": "Посочената сметка за инсталацията не притежава достатъчно права за създаване на сметка.",
+ "config-pg-not-in-role": "Посочената сметка за уеб потребител вече съществува.\nПосочената сметка за инсталация не с права на суперпотребител и не е член на ролите на уеб потребителя и не може да създава обекти, собственост на уеб потребителя.\n\nТекущо МедияУики изисква таблиците да са собственост на уеб потребителя. Необходимо е да се посочи друго потребителско име за уеб или да се натисне \"връщане\" и да се избере друг потребител за инсталацията с подходящите права.",
+ "config-install-user": "Създаване на потребител за базата от данни",
+ "config-install-user-alreadyexists": "Потребител „$1“ вече съществува",
+ "config-install-user-create-failed": "Създаването на потребител „$1“ беше неуспешно: $2",
+ "config-install-user-grant-failed": "Предоставянето на права на потребител \"$1\" беше неуспешно: $2",
+ "config-install-user-missing": "Посоченият потребител \" $1 \"не съществува.",
+ "config-install-user-missing-create": "Посоченият потребител \"$1\" не съществува.\nАко желаете да го създадете, поставете отметка на \"създаване на сметка\".",
+ "config-install-tables": "Създаване на таблиците",
+ "config-install-tables-exist": "'''Предупреждение''': Таблиците за МедияУики изглежда вече съществуват.\nПропускане на създаването им.",
+ "config-install-tables-failed": "'''Грешка''': Създаването на таблиците пропадна и върна следната грешка: $1",
+ "config-install-interwiki": "Попълване на таблицата с междууикитата по подразбиране",
+ "config-install-interwiki-list": "Файлът <code>interwiki.list</code> не можа да бъде открит.",
+ "config-install-interwiki-exists": "'''Предупреждение''': Таблицата с междууикита изглежда вече съдържа данни.\nПропускане на списъка по подразбиране.",
+ "config-install-stats": "Инициализиране на статистиките",
+ "config-install-keys": "Генериране на тайни ключове",
+ "config-insecure-keys": "'''Предупреждение:''' {{PLURAL:$2|Сигурният ключ, създаден по време на инсталацията, не е напълно надежден|Сигурните ключове, създадени по време на инсталацията, не са напълно надеждни}} $1 . Обмислете да {{PLURAL:$2|го|ги}} смените ръчно.",
+ "config-install-sysop": "Създаване на администраторска сметка",
+ "config-install-subscribe-fail": "Невъзможно беше абонирането за mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "не е инсталиран cURL и <code>allow_url_fopen</code> не е налична.",
+ "config-install-mainpage": "Създаване на Началната страница със съдържание по подразбиране",
+ "config-install-extension-tables": "Създаване на таблици за включените разширения",
+ "config-install-mainpage-failed": "Вмъкването на Началната страница беше невъзможно: $1",
+ "config-install-done": "'''Поздравления!'''\nИнсталирането на МедияУики приключи успешно.\n\nИнсталаторът създаде файл <code>LocalSettings.php</code>.\nТой съдържа всичката необходима основна конфигурация на уикито.\n\nНеобходимо е той да бъде изтеглен и поставен в основната директория на уикито (директорията, в която е и index.php). Изтеглянето би трябвало да започне автоматично.\n\nАко изтеглянето не започне автоматично или е било прекратено, файлът може да бъде изтеглен чрез щракване на препратката по-долу:\n\n$3\n\n'''Забележка''': Ако това не бъде извършено сега, генерираният конфигурационен файл няма да е достъпен на по-късен етап ако не бъде изтеглен сега или инсталацията приключи без изтеглянето му.\n\nКогато файлът вече е в основната директория, '''[$2 уикито ще е достъпно на този адрес]'''.",
+ "config-download-localsettings": "Изтегляне на <code>LocalSettings.php</code>",
+ "config-help": "помощ",
+ "config-nofile": "Файлът „$1“ не може да бъде открит. Да не е бил изтрит?",
+ "config-extension-link": "Знаете ли, че това уики поддържа [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions разширения]?\n\nМожете да разгледате [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category разширенията по категория] или [//www.mediawiki.org/wiki/Extension_Matrix Матрицата на разширенията] за пълен списък на разширенията.",
+ "mainpagetext": "'''Уикито беше успешно инсталирано.'''",
+ "mainpagedocfooter": "Разгледайте [//meta.wikimedia.org/wiki/Help:Contents ръководството] за подробна информация относно използването на уики софтуера.\n\n== Първи стъпки ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Настройки за конфигуриране]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧЗВ за МедияУики]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Пощенски списък относно нови версии на МедияУики]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Локализиране на МедияУики]"
+}
diff --git a/includes/installer/i18n/bjn.json b/includes/installer/i18n/bjn.json
new file mode 100644
index 00000000..f123a105
--- /dev/null
+++ b/includes/installer/i18n/bjn.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ezagren",
+ "J Subhi"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki sudah tapasang awan sukses'''.",
+ "mainpagedocfooter": "Carii panjalasan [//meta.wikimedia.org/wiki/Help:Contents Panduan Pamuruk] gasan mamuruk parangkat lunak wiki\n\n== Gasan bamula ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Daptar konpigurasi setélan]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki nang rancak ditakunakan]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki rilis milis]"
+}
diff --git a/includes/installer/i18n/bn.json b/includes/installer/i18n/bn.json
new file mode 100644
index 00000000..212c5995
--- /dev/null
+++ b/includes/installer/i18n/bn.json
@@ -0,0 +1,125 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bellayet",
+ "Wikitanvir",
+ "Aftab1995",
+ "Tauhid16"
+ ]
+ },
+ "config-desc": "মিডিয়াউইকির জন্য ইন্সটলার",
+ "config-title": "মিডিয়াউইকি $1 ইন্সটলেশন",
+ "config-information": "তথ্য",
+ "config-localsettings-key": "হালনাগাদ কি",
+ "config-localsettings-badkey": "আপনি যেই চাবিটি দিয়েছেন তা সঠিক নয়।",
+ "config-session-error": "সেশন শুরুতে ত্রুটি: $1",
+ "config-your-language": "আপনার ভাষা:",
+ "config-your-language-help": "ইন্সটল করা সময় ব্যবহারের জন্য ভাষা নির্বাচন করুন।",
+ "config-wiki-language": "উইকি ভাষা:",
+ "config-back": "← পেছনে",
+ "config-continue": "অব্যাহত →",
+ "config-page-language": "ভাষা",
+ "config-page-welcome": "মিডিয়াউইকিতে স্বাগতম!",
+ "config-page-dbconnect": "ডেটাবেজে সংযোগ দিন",
+ "config-page-upgrade": "ইতিমধ্যেই থাকা ইন্সটলেশন হালনাগাদ করুন",
+ "config-page-dbsettings": "ডেটাবেজ সেটিংস",
+ "config-page-name": "নাম",
+ "config-page-options": "অপশন",
+ "config-page-install": "ইন্সটল",
+ "config-page-complete": "সম্পূর্ণ!",
+ "config-page-restart": "পুনরায় ইন্সটল প্রক্রিয়া চালু করুন",
+ "config-page-readme": "এটি পড়ুন",
+ "config-page-releasenotes": "রিলিজ নোট",
+ "config-page-copying": "অনুলেপন",
+ "config-page-upgradedoc": "হালনাগাদকরণ",
+ "config-page-existingwiki": "ইতিমধ্যেই থাকা উইকি",
+ "config-help-restart": "আপনি কী সকল সংরক্ষিত উপাত্ত পরিষ্কার করতে যা আপনি প্রবেস করিয়েছিলেন এবং ইন্সটালেসন ব্যবস্থা পুনরায় আরম্ভ করতে চান?",
+ "config-restart": "হ্যাঁ, পুনরায় চালু করুন",
+ "config-env-php": "পিএইচপি $1 ইন্সটল করা হয়েছে।",
+ "config-env-hhvm": "HHVM $1 ইনস্টল করা হয়েছে।",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] ইনস্টল করা হয়েছে",
+ "config-apc": "[http://www.php.net/apc এপিসি] ইনস্টল হয়েছে",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] ইনস্টল করা হয়েছে",
+ "config-db-type": "ডেটাবেজের ধরন:",
+ "config-db-host": "ডেটাবেজের হোস্ট:",
+ "config-db-wiki-settings": "এই উইকি সনাক্ত করুন",
+ "config-db-name": "উপাত্তসংগ্রহশালা নামঃ",
+ "config-db-install-account": "ইন্সটলের জন্য ব্যবহারকারী অ্যাকাউন্ট",
+ "config-db-username": "ডেটাবেজের ব্যবহারকারী নাম:",
+ "config-db-password": "ডেটাবেজের শব্দচাবি:",
+ "config-db-username-empty": "আপনাকে অবশ্যই \"{{int:config-db-username}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
+ "config-db-wiki-account": "সাধারণ অভিযানের জন্য ব্যবহারকারী একাউন্ট",
+ "config-db-prefix": "উপাত্তশালা ছক প্রিফিক্স:",
+ "config-db-charset": "ডেটাবেজের অক্ষর সেট",
+ "config-db-port": "ডেটাবেজ পোর্ট:",
+ "config-db-schema": "মিডিয়াউইকির স্কিমা",
+ "config-pg-test-error": "উপাত্তশালা $1-এর সাথে সংযোগ দেয়া সম্ভব হয়নি। কারন:$2",
+ "config-sqlite-dir": "এসকিউলাইট ডেটা ডিরেক্টরি:",
+ "config-oracle-def-ts": "পূর্বনির্ধারিত টেবিলস্পেস",
+ "config-oracle-temp-ts": "সাময়কি টেবিলস্পেস:",
+ "config-type-mssql": "মাইক্রোসফট এসকিউএল সার্ভার",
+ "config-header-mysql": "মাইএসকিউএল সেটিংস",
+ "config-header-postgres": "পোস্টগ্রেএসকিউএল সেটিংস",
+ "config-header-sqlite": "এসকিউলাইট সেটিংস",
+ "config-header-oracle": "ওরাকল সেটিংস",
+ "config-invalid-db-type": "ডেটাবেজের ধরন অগ্রহযোগ্য",
+ "config-missing-db-name": "আপনাকে অবশ্যই \"{{int:config-db-name}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
+ "config-missing-db-host": "আপনাকে অবশ্যই \"{{int:config-db-host}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
+ "config-missing-db-server-oracle": "আপনাকে অবশ্যই \"{{int:config-db-host-oracle}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
+ "config-connection-error": "$1।\n\n\nদয়া করে প্রস্তাবকারী, ব্যবহারকারী নাম ও শব্দচাবি দেখুন এবং পুনরায় চেষ্টা করুন।",
+ "config-mysql-engine": "সংরক্ষণ ইঞ্জিন:",
+ "config-mysql-innodb": "ইনোডিবি",
+ "config-mysql-myisam": "মাইআইএসএএম",
+ "config-mysql-charset": "ডেটাবেজের অক্ষর সেট",
+ "config-mysql-binary": "বাইনারি",
+ "config-mysql-utf8": "ইউটিএফ-৮",
+ "config-mssql-windowsauth": "উইন্ডোজ প্রমাণীকরণ",
+ "config-site-name": "উইকির নাম:",
+ "config-site-name-blank": "একটি সাইটের নাম প্রবেশ করান।",
+ "config-project-namespace": "প্রকল্প নামস্থান:",
+ "config-ns-generic": "প্রকল্প",
+ "config-ns-site-name": "উইকি নামের অনুরুপ: $1",
+ "config-ns-other": "অন্যান্য (নির্দিষ্ট করুন)",
+ "config-ns-other-default": "মাইউইকি",
+ "config-admin-box": "প্রশাসক অ্যাকাউন্ট",
+ "config-admin-name": "আপনার ব্যবহারকারী নাম:",
+ "config-admin-password": "শব্দচাবি:",
+ "config-admin-password-confirm": "শব্দচাবি আবারও প্রবেশ করান:",
+ "config-admin-name-blank": "একটি প্রশাসক ব্যবহারকারী নাম প্রবেশ করান",
+ "config-admin-password-blank": "প্রশাসক অ্যাকাউন্টের জন্য পাসওয়ার্ড প্রবেশ করান।",
+ "config-admin-password-mismatch": "আপনি যে দুটি শব্দচাবি দিয়েছেন তারা পরস্পর মেলেনি।",
+ "config-admin-email": "ইমেইল ঠিকানা:",
+ "config-optional-continue": "আরও প্রশ্ন জিজ্ঞেস করুন।",
+ "config-optional-skip": "আমি ইতিমধ্যেই বিরক্ত হয়ে গেছি, উইকিটি ইন্সটল করো।",
+ "config-profile": "ব্যবহারকারী অধিকার প্রোফাইল:",
+ "config-profile-wiki": "উন্মুক্ত উইকি",
+ "config-profile-no-anon": "অ্যাকাউন্ট তৈরি করা বাধ্যতামূলক",
+ "config-profile-fishbowl": "শুধুমাত্র নির্ধারিত সম্পাদকদের জন্যই",
+ "config-profile-private": "ব্যক্তিগত উইকি",
+ "config-license": "কপিরাইট ও লাইসেন্স:",
+ "config-license-none": "কোনো লাইসেন্স ফুটার নেই",
+ "config-license-cc-by-sa": "ক্রিয়েটিভ কমন্স অ্যাট্রিবিউশন শেয়ার অ্যালাইক",
+ "config-license-cc-by": "ক্রিয়েটিভ কমন্স অ্যাট্রিবিউশন",
+ "config-license-cc-by-nc-sa": "ক্রিয়েটিভ কমন্স অ্যাট্রিবিউশন নন-কমার্শিয়াল শেয়ার অ্যালাইক",
+ "config-license-cc-0": "ক্রিয়েটিভ কমন্স জিরো (পাবলিক ডোমেইন)",
+ "config-license-pd": "পাবলিক ডোমেইন",
+ "config-license-cc-choose": "একটি স্বনির্ধারিত ক্রিয়েটিভ কমন্স লাইসেন্ট নির্বাচন করুন",
+ "config-email-settings": "ই-মেইল সেটিংস",
+ "config-email-user": "ব্যবহারকারী-থেকে-ব্যবহারকারী ই-মেইল সুবিধা সক্রিয় করো",
+ "config-upload-settings": "চিত্র এবং ফাইল আপলোড",
+ "config-upload-enable": "ফাইল আপলোড সক্রিয় করো",
+ "config-upload-deleted": "অপসারণকৃত ফাইলের ডিরেক্টরি:",
+ "config-logo": "লোগো ইউআরএল:",
+ "config-memcached-servers": "মেমক্যাশেকৃত সার্ভারসমূহ:",
+ "config-extensions": "এক্সটেনশন",
+ "config-install-step-done": "সম্পন্ন",
+ "config-install-step-failed": "ব্যর্থ",
+ "config-install-extensions": "এক্সটেনশন সহকারে",
+ "config-install-database": "ডেটাবেজ সেটআপ",
+ "config-install-pg-schema-not-exist": "পোস্টগ্রেএসকিউএল স্কিমা খুঁজে পাওয়া যায়নি।",
+ "config-install-tables": "টেবিল তৈরি",
+ "config-install-keys": "গোপন কি তৈরি",
+ "config-help": "সাহায্য",
+ "mainpagetext": "'''মিডিয়াউইকি সফলভাবে ইন্সটল করা হয়েছে।'''",
+ "mainpagedocfooter": "কী ভাবে উইকি সফটওয়্যারটি ব্যবহারকার করবেন, তা জানতে [//meta.wikimedia.org/wiki/Help:Contents ব্যবহারকারী সহায়িকা] দেখুন।\n\n== কোথা থেকে শুরু করবেন ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings কনফিগারেশন সেটিংস তালিকা]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ প্রশ্নোত্তরে মিডিয়াউইকি]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়াউইকি রিলিজের মেইলিং লিস্ট]"
+}
diff --git a/includes/installer/i18n/bpy.json b/includes/installer/i18n/bpy.json
new file mode 100644
index 00000000..eac6da3b
--- /dev/null
+++ b/includes/installer/i18n/bpy.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''মিডিয়াউইকি হবাবালা ইয়া ইন্সটল ইল.'''",
+ "mainpagedocfooter": "উইকি সফটৱ্যার এহান আতানির বারে দরকার ইলে [//meta.wikimedia.org/wiki/Help:Contents আতাকুরার গাইড]হানর পাঙলাক নেগা।\n\n== অকরানিহান ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings কনফিগারেশন সেটিংর তালিকাহান]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ মিডিয়া উইকি আঙলাক]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়া উইকির ফঙপার বারে মেইলর তালিকাহান]"
+}
diff --git a/includes/installer/i18n/br.json b/includes/installer/i18n/br.json
new file mode 100644
index 00000000..78381bed
--- /dev/null
+++ b/includes/installer/i18n/br.json
@@ -0,0 +1,283 @@
+{
+ "@metadata": {
+ "authors": [
+ "Fohanno",
+ "Fulup",
+ "Gwendal",
+ "Y-M D",
+ "아라"
+ ]
+ },
+ "config-desc": "Poellad staliañ MediaWIki",
+ "config-title": "Staliadur MediaWiki $1",
+ "config-information": "Titouroù",
+ "config-localsettings-upgrade": "Kavet ez eus bet ur restr <code>LocalSettings.php</code>.\nEvit hizivaat ar staliadur-se, merkit an talvoud <code>$wgUpgradeKey</code> er voest dindan.\nE gavout a rit e <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Dinoet ez eus bet ur restr <code>LocalSettings.php</code>.\nEvit lakaat ar staliadur-mañ a-live, implijit <code>update.php</code> e plas",
+ "config-localsettings-key": "Alc'hwez hizivaat :",
+ "config-localsettings-badkey": "Direizh eo an alc'hwez merket ganeoc'h",
+ "config-upgrade-key-missing": "Kavet ez eus bet ur staliadur kent eus MediaWiki.\nEvit hizivaat ar staliadur-se, ouzhpennit al linenn da-heul e traoñ ho restr <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Diglok e seblant bezañ ar restr <code>LocalSettings.php</code> zo anezhi dija.\nAn argemmenn $1 n'eo ket termenet.\nKemmit <code>LocalSettings.php</code> evit ma vo termenet an argemmenn-se, ha klikit war « {{int:Config-continue}} ».",
+ "config-localsettings-connection-error": "C'hoarvezet ez eus ur fazi en ur gevreañ ouzh an diaz roadennoù oc'h implijout an arventennoù diferet e <code>LocalSettings.php</code>. Reizhit an arventennoù-se hag esaeit en-dro.\n\n$1",
+ "config-session-error": "Fazi e-ser loc'hañ an dalc'h : $1",
+ "config-session-expired": "Kloz eo an dalc'h evit doare.\nKefluniet eo an dalc'hoù evit padout $1.\nKreskiñ ar pad-mañ a c'hallit dre e arventenniñ <code>session.gc_maxlifetime</code> e php.ini.\nAdgrogit gant ar staliadur.",
+ "config-no-session": "Kolle teo bet roadennoù ho talc'h !\nGwiriit ar restr php.ini ha bezit sur emañ staliet <code>session.save_path</code> en ur c'havlec'h a zere.",
+ "config-your-language": "Ho yezh :",
+ "config-your-language-help": "Dibabit ur yezh da implijout e-pad an argerzh staliañ.",
+ "config-wiki-language": "Yezh ar wiki :",
+ "config-wiki-language-help": "Diuzañ ar yezh a vo implijet ar muiañ er wiki.",
+ "config-back": "← Distreiñ",
+ "config-continue": "Kenderc'hel →",
+ "config-page-language": "Yezh",
+ "config-page-welcome": "Degemer mat e MediaWiki !",
+ "config-page-dbconnect": "Kevreañ d'an diaz roadennoù",
+ "config-page-upgrade": "Hizivaat ar staliadur a zo dioutañ",
+ "config-page-dbsettings": "Arventennoù an diaz roadennoù",
+ "config-page-name": "Anv",
+ "config-page-options": "Dibarzhioù",
+ "config-page-install": "Staliañ",
+ "config-page-complete": "Graet !",
+ "config-page-restart": "Adlañsañ ar staliadur",
+ "config-page-readme": "Lennit-me",
+ "config-page-releasenotes": "Notennoù stumm",
+ "config-page-copying": "O eilañ",
+ "config-page-upgradedoc": "O hizivaat",
+ "config-page-existingwiki": "Wiki zo anezhañ dija",
+ "config-help-restart": "Ha c'hoant hoc'h eus da ziverkañ an holl roadennoù hoc'h eus ebarzhet ha da adlañsañ an argerzh staliañ ?",
+ "config-restart": "Ya, adloc'hañ anezhañ",
+ "config-welcome": "=== Gwiriadennoù a denn d'an endro ===\nRekis eo un nebeud gwiriadennoù diazez da welet hag azas eo an endro evit gallout staliañ MediaWiki.\nHo pet soñj merkañ disoc'hoù ar gwiriadennoù-se m'ho pez ezhomm skoazell e-pad ar staliadenn.",
+ "config-copyright": "=== Gwiriañ aozer ha Termenoù implijout ===\n\n$1\n\nUr meziant frank eo ar programm-mañ; gallout a rit skignañ anezhañ ha/pe kemmañ anezhañ dindan termenoù ar GNU Aotre-implijout Foran Hollek evel m'emañ embannet gant Diazezadur ar Meziantoù Frank; pe diouzh stumm 2 an aotre-implijout, pe (evel mar karit) diouzh ne vern pe stumm nevesoc'h.\n\nIngalet eo ar programm gant ar spi e vo talvoudus met n'eus '''tamm gwarant ebet'''; hep zoken gwarant empleg ar '''varc'hadusted''' pe an '''azaster ouzh ur pal bennak'''. Gwelet ar GNU Aotre-Implijout Foran Hollek evit muioc'h a ditouroù.\n\nSañset oc'h bezañ resevet <doclink href=Copying>un eilskrid eus ar GNU Aotre-implijout Foran Hollek</doclink> a-gevret gant ar programm-mañ; ma n'hoc'h eus ket, skrivit da Diazezadur ar Meziantoù Frank/Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, SUA pe [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html lennit anezhañ enlinenn].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki degemer]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Sturlevr an implijerien]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Sturlevr ar verourien]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAG]\n----\n* <doclink href=Readme>Lennit-me</doclink>\n* <doclink href=ReleaseNotes>Notennoù embann</doclink>\n* <doclink href=Copying>Oc'h eilañ</doclink>\n* <doclink href=UpgradeDoc>O hizivaat</doclink>",
+ "config-env-good": "Gwiriet eo bet an endro.\nGallout a rit staliañ MediaWiki.",
+ "config-env-bad": "Gwiriet eo bet an endro.\nNe c'hallit ket staliañ MediaWiki.",
+ "config-env-php": "Staliet eo PHP $1.",
+ "config-env-hhvm": "HHVM $1 zo staliet.",
+ "config-unicode-using-utf8": "Oc'h implijout utf8_normalize.so gant Brion Vibber evit ar reolata Unicode.",
+ "config-unicode-using-intl": "Oc'h implijout [http://pecl.php.net/intl an astenn PECL intl] evit ar reolata Unicode.",
+ "config-unicode-pure-php-warning": "'''Diwallit''' : N'haller ket kaout an [http://pecl.php.net/intl intl PECL astenn] evit merañ reoladur Unicode, a zistro d'ar stumm gorrek emplementet e-PHP.\nMa lakait da dreiñ ul lec'hienn darempredet-stank e vo mat deoc'h lenn un tammig bihan diwar-benn se war [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization]. (e saozneg)",
+ "config-unicode-update-warning": "'''Diwallit''': ober a ra stumm staliet endalc'her skoueriekaat Unicode gant ur stumm kozh eus [http://site.icu-project.org/ levraoueg meziantoù ar raktres ICU].\nDleout a rafec'h [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations hizivaat] ma seblant deoc'h bezañ pouezus ober gant Unicode.",
+ "config-no-db": "N'eus ket bet gallet kavout ur sturier diazoù roadennoù a zere ! Ret eo deoc'h staliañ ur sturier diazoù roadennoù evit PHP.\nSkoret eo an diazoù roadennoù da-heul : $1.\n\nMa rit gant un herberc'hiañ kenrannet, goulennit digant ho herberc'hier staliañ ur sturier diaz roadennoù azas.\nMa kempunit PHP c'hwi hoc'h-unan, adkeflugnit-eñ en ur weredekaat un arval diaz roadennoù, da skouer en ur ober gant <code>./configure --mysql</code>.\nM'hoc'h eus staliet PHP adalek ur pakad Debian pe Ubuntu, eo ret deoc'h staliañ ar vodulenn php5-mysql ivez.",
+ "config-no-fts3": "'''Diwallit ''': Kempunet eo SQLite hep ar [//sqlite.org/fts3.html vodulenn FTS3]; ne vo ket posupl ober gant an arc'hwelioù klask er staliadur-mañ",
+ "config-magic-quotes-runtime": "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] !'''\nBreinañ a ra an dibarzh-mañ ar roadennoù en ur mod dic'hortoz.\nN'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
+ "config-magic-quotes-sybase": "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] !'''\nBreinañ a ra an dibarzh-mañ ar roadennoù en ur mod dic'hortoz.\nN'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
+ "config-mbstring": "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] !'''\nDegas a ra an dibarzh-mañ fazioù ha gallout a ra breinañ ar roadennoù en ur mod dic'hortoz.\nN'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
+ "config-safe-mode": "'''Diwallit :''' Gweredekaet eo [http://www.php.net/features.safe-mode mod surentez] PHP.\nKudennoù a c'hall sevel abalamour da gement-se, dreist-holl ma pellgargit restroù ha ma skorit <code>math</code>.",
+ "config-xml-bad": "Mankout a ra modulenn XML PHP.\nEzhomm en deus MediaWiki eus arc'hwelioù zo eus ar vodulenn-se ha ne'z aio ket en-dro gant ar c'hefluniadur zo.\nM'emaoc'h gant Mandrake, stailhit pakad php-xml.",
+ "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-ctype": "<strong>Fazi grevus :</strong> PHP a rank bezañ kempunet gant ar skor evit an [http://www.php.net/manual/en/ctype.installation.php astenn Ctype].",
+ "config-xcache": "Staliet eo [http://xcache.lighttpd.net/ XCache]",
+ "config-apc": "Staliet eo [http://www.php.net/apc APC]",
+ "config-wincache": "Staliet eo [http://www.iis.net/download/WinCacheForPhp WinCache]",
+ "config-no-cache": "'''Diwallit:''' N'eus ket bet gallet kavout [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] pe [http://www.iis.net/download/WinCacheForPhp WinCache].\nN'eo ket gweredekaet ar c'hrubuilhañ traezoù.",
+ "config-diff3-bad": "N'eo ket bet kavet GNU diff3.",
+ "config-imagemagick": "ImageMagick kavet : <code>$1</code>.\nGweredekaet e vo ar bihanaat skeudennoù ma vez gweredekaet ganeoc'h ar pellgargañ restroù.",
+ "config-gd": "Kavet eo bet al levraoueg c'hrafek GD enframmet.\nGweredekaet e vo ar bihanaat skeudennoù ma vez gweredekaet an enporzhiañ restroù.",
+ "config-no-scaling": "N'eus ket bet gallet kavout al levraoueg GD pe ImageMagick.\nDiweredekaet e vo ar bihanaat skeudennoù.",
+ "config-no-uri": "'''Fazi :''' N'eus ket tu da anavezout URI ar skript red.\nStaliadur nullet.",
+ "config-using-server": "Oc'h implijout an anv servijer \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Oc'h implijout ar servijour URL \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Diwallit :'''Bresk eo ho kavlec'h pellgargañ dre ziouer <code>$1</code> rak gallout a ra erounit ne vern pe skript.\nha pa vefe gwiriet gant MediaWiki an holl restroù pellgarget eo erbedet-groñs da [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security serriñ ar breskter surentez-mañ] a-rao gweredekaat ar pellgargañ.",
+ "config-brokenlibxml": "Ur meskad stummoù PHP ha libxml2 dreinek a vez implijet gant ho reizhiad. Gallout a ra breinañ ar roadennoù e MediaWiki hag en arloadoù web all.\nHizivait da libxml2 2.7.3 pe nevesoc'h ([//bugs.php.net/bug.php?id=45996 draen renablet gant PHP]).\nStaliadur c'hwitet.",
+ "config-db-type": "Doare an diaz roadennoù :",
+ "config-db-host": "Anv implijer an diaz roadennoù :",
+ "config-db-host-help": "M'emañ ho servijer roadennoù war ur servijer disheñvel, merkit amañ anv an ostiz pe ar chomlec'h IP.\n\nMa rit gant un herberc'hiañ kenrannet, e tlefe ho herberc'hier bezañ pourchaset deoc'h an anv ostiz reizh en teulioù titouriñ.\n\nM'emaoc'h o staliañ ur servijer Windows ha ma rit gant MySQL, marteze ne'z aio ket en-dro \"localhost\" evel anv servijer. Ma ne dro ket, klaskit ober gant \"127.0.0.1\" da chomlec'h IP lechel.",
+ "config-db-host-oracle": "TNS an diaz roadennoù :",
+ "config-db-wiki-settings": "Anavezout ar wiki-mañ",
+ "config-db-name": "Anv an diaz roadennoù :",
+ "config-db-name-help": "Dibabit un anv evit ho wiki.\nNa lakait ket a esaouennoù ennañ.\n\nMa ri gant un herberc'hiañ kenrannet e vo pourchaset deoc'h un anv diaz roadennoù dibar da vezañ graet gantañ gant ho herberc'hier pe e lezo ac'hanoc'h da grouiñ diazoù roadennoù dre ur banell gontrolliñ.",
+ "config-db-name-oracle": "Brastres diaz roadennoù :",
+ "config-db-install-account": "Kont implijer evit ar staliadur",
+ "config-db-username": "Anv implijer an diaz roadennoù :",
+ "config-db-password": "Ger-tremen an diaz roadennoù :",
+ "config-db-password-empty": "Lakait ur ger-tremen evit kont nevez an diaz roadennoù : $1.\nHa pa vefe posupl da grouiñ kontoù hep ger-tremen, n'eo ket erbedet evit abegoù surentez.",
+ "config-db-username-empty": "Ret eo deoc'h ebarzhiñ un talvoud evit \"{{int:config-db-username}}\".",
+ "config-db-install-username": "Ebarzhit an anv implijer a vo implijet da gevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.\nN'eo ket anv implijer ar gont MediaWiki, an anv implijer evit ho tiaz roadennoù eo.",
+ "config-db-install-password": "Ebarzhit ar ger-tremen a vo implijet da gevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.\nN'eo ket ar ger-tremen evit ar gont MediaWiki, ar ger-tremen evit ho tiaz roadennoù eo.",
+ "config-db-install-help": "Merkañ anv an implijer hag ar ger-tremen a vo implijet evit kevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.",
+ "config-db-account-lock": "Implijout ar memes anv implijer ha ger-tremen e-kerzh oberiadurioù boutin",
+ "config-db-wiki-account": "Kont implijer evit oberiadurioù boutin",
+ "config-db-prefix": "Rakrann taolennoù an diaz roadennoù :",
+ "config-db-charset": "Strobad arouezennoù an diaz roadennoù",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binarel",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 kilkenglotus UTF-8",
+ "config-mysql-old": "Rekis eo MySQL $1 pe ur stumm nevesoc'h; ober a rit gant $2.",
+ "config-db-port": "Porzh an diaz roadennoù :",
+ "config-db-schema": "Brastres evit MediaWiki",
+ "config-db-schema-help": "Peurliesañ e vo digudenn ar chema-mañ.\nArabat cheñch anezho ma n'hoc'h eus ket ezhomm d'en ober.",
+ "config-pg-test-error": "N'haller ket kevreañ ouzh an diaz-titouroù '''$1''' : $2",
+ "config-sqlite-dir": "Kavlec'h roadennoù SQLite :",
+ "config-oracle-def-ts": "Esaouenn stokañ (\"tablespace\") dre ziouer :",
+ "config-oracle-temp-ts": "Esaouenn stokañ (''tablespace'') da c'hortoz :",
+ "config-type-mysql": "MySQL (pe kenglotus)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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": "* $1 eo an dibab kentañ evit MediaWiki hag an hini skoret ar gwellañ ([http://www.php.net/manual/en/mysql.installation.php penaos kempunañ PHP gant skor MySQL])",
+ "config-dbsupport-postgres": "* Ur reizhiad diaz titouroù brudet ha digor eo $1. Gallout a ra ober evit MySQL ([http://www.php.net/manual/en/pgsql.installation.php Penaos kempunañ PHP gant skor PostgreSQL]). Gallout a ra bezañ un nebeud drein bihan enni ha n'eo ket erbedet he implijout en un endro produiñ.",
+ "config-dbsupport-sqlite": "* $1 zo ur reizhiad diaz titouroù 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": "* $1 zo un diaz titouroù kenwerzhel. ([http://www.php.net/manual/en/oci8.installation.php Penaos kempunañ PHP gant skor OCI8])",
+ "config-header-mysql": "Arventennoù MySQL",
+ "config-header-postgres": "Arventennoù PostgreSQL",
+ "config-header-sqlite": "Arventennoù SQLite",
+ "config-header-oracle": "Arventennoù Oracle",
+ "config-header-mssql": "Arventennoù Microsoft SQL Server",
+ "config-invalid-db-type": "Direizh eo ar seurt diaz roadennoù",
+ "config-missing-db-name": "Ret eo deoc'h merkañ un dalvoudenn evit \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Ret eo deoc'h merkañ un dalvoudenn evit \"{{int:config-db-host}}\"",
+ "config-missing-db-server-oracle": "Ret eo deoc'h merkañ un dalvoudenn evit \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "Direizh eo anv TNS an diaz titouroù \"$1\".\nOber hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha pikoù (.).",
+ "config-invalid-db-name": "Direizh eo anv an diaz titouroù \"$1\".\nOber hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha tiredoù (-).",
+ "config-invalid-db-prefix": "Direizh eo rakger an diaz titouroù \"$1\".\nOber hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha tiredoù (-).",
+ "config-connection-error": "$1.\n\nGwiriit anv an ostiz, an anv implijer, ar ger-tremen ha klaskit en-dro.",
+ "config-invalid-schema": "Chema direizh evit MediaWiki \"$1\".\nGrit hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9) hag arouezennoù islinennañ (_).",
+ "config-db-sys-create-oracle": "N'anavez ar stalier nemet ar c'hontoù SYSDBA evit krouiñ kontoù nevez.",
+ "config-db-sys-user-exists-oracle": "Bez' ez eus eus ar gont \"$1\" c'hoazh. N'haller ober gant SYSDBA nemet evit krouiñ kontoù nevez !",
+ "config-postgres-old": "Rekis eo PostgreSQL $1 pe ur stumm nevesoc'h; ober a rit gant $2.",
+ "config-mssql-old": "Stumm $1 Microsoft SQL Server, pe unan nevesoc'h, zo rekis. Ganeoc'h emañ ar stumm $2.",
+ "config-sqlite-name-help": "Dibabit un anv dibar d'ho wiki.\nArabat ober gant esaouennoù pe barrennigoù-stagañ.\nImplijet e vo evit ar restr roadennoù SQLite.",
+ "config-sqlite-mkdir-error": "Ur fazi zo bet e-ser krouiñ ar c'havlec'h roadennoù \"$1\".\nGwiriañ al lec'hiadur ha klask en-dro.",
+ "config-sqlite-dir-unwritable": "Dibosupl skrivañ er c'havlec'h \"$1\".\nCheñchit ar aotreoù evit ma c'hallfe ar servijer web skrivañ ennañ ha klaskit en-dro.",
+ "config-sqlite-connection-error": "$1.\n\nGwiriañ ar c'havlec'h roadennoù hag anv an diaz roadennoù a-is ha klaskit en-dro.",
+ "config-sqlite-readonly": "N'haller ket skrivañ er restr <code>$1</code>.",
+ "config-sqlite-cant-create-db": "N'haller ket krouiñ restr an diaz roadennoù <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "N'eo ket kenglotus ar PHP gant FTS3, o lakaat an taolennoù da glotañ gant ur stumm koshoc'h",
+ "config-can-upgrade": "Taolennoù MediaWiki zo en diaz titouroù.\nDa hizivaat anezho da VediaWiki $1, klikañ war '''Kenderc'hel'''.",
+ "config-upgrade-done-no-regenerate": "Hizivadenn kaset da benn.\n\nGallout a rit [$1 kregiñ da implijout ho wiki].",
+ "config-regenerate": "Adgenel LocalSettings.php →",
+ "config-show-table-status": "C'hwitet ar reked <code>SHOW TABLE STATUS</code> !",
+ "config-unknown-collation": "'''Diwallit :''' Emañ an diaz roadennoù o renkañ an traoù diouzh un urzh lizherennek dianav.",
+ "config-db-web-account": "Kont an diaz roadennoù evit ar voned Kenrouedad",
+ "config-db-web-help": "Diuzañ an anv implijer hag ar ger-tremen a vo implijet gant ar servijer web evit kevreañ ouzh ar servijer diaz roadennoù pa vez ar wiki o vont en-dro war ar pemdez.",
+ "config-db-web-account-same": "Ober gant an hevelep kont hag an hini implijet evit ar staliañ",
+ "config-db-web-create": "Krouiñ ar gont ma n'eus ket anezhi c'hoazh",
+ "config-db-web-no-create-privs": "Ar gont ho peus diferet evit ar staliañ n'he deus ket gwirioù a-walc'h evit krouiñ ur gont.\nRet eo d'ar gont diferet amañ bezañ anezhi dija.",
+ "config-mysql-engine": "Lusker stokañ :",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Strobad arouezennoù an diaz roadennoù :",
+ "config-mysql-binary": "Binarel",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Seut anaoudadur :",
+ "config-mssql-sqlauth": "Anaoudadur SQL Server",
+ "config-mssql-windowsauth": "Anaoudadur Windows",
+ "config-site-name": "Anv ar wiki :",
+ "config-site-name-help": "Dont a raio war wel e barrenn ditl ar merdeer hag e meur a lec'h all c'hoazh.",
+ "config-site-name-blank": "Lakait anv ul lec'hienn .",
+ "config-project-namespace": "Esaouenn anv ar raktres :",
+ "config-ns-generic": "Raktres",
+ "config-ns-site-name": "Hevelep anv hag hini ar wiki : $1",
+ "config-ns-other": "All (spisaat)",
+ "config-ns-other-default": "MaWiki",
+ "config-ns-invalid": "Direizh eo an esaouenn anv \"<nowiki>$1</nowiki>\" spisaet.\nMerkit un esaouenn anv disheñvel evit ar raktres.",
+ "config-ns-conflict": "Tabut zo etre an esaouenn anv spisaet \"<nowiki>$1</nowiki>\" hag un esaouenn anv dre ziouer eus MediaWiki.\nSpisait un anv raktres esaouenn anv all.",
+ "config-admin-box": "Kont merour",
+ "config-admin-name": "Hoc'h anv implijer :",
+ "config-admin-password": "Ger-tremen :",
+ "config-admin-password-confirm": "Adskrivañ ar ger-tremen :",
+ "config-admin-help": "Merkit hoc'h anv implijer amañ, da skouer \"Yann Vlog\".\nHemañ eo an anv a implijot evit kevreañ d'ar wiki-mañ.",
+ "config-admin-name-blank": "Lakait anv ur merour.",
+ "config-admin-name-invalid": "Direizh eo an anv implijer spisaet \"<nowiki>$1</nowiki>\".\nMerkit un anv implijer all.",
+ "config-admin-password-blank": "Reiñ ur ger-tremen evit kont ar merour.",
+ "config-admin-password-mismatch": "Ne glot ket ar gerioù-tremen hoc'h eus merket an eil gant egile.",
+ "config-admin-email": "Chomlec'h postel :",
+ "config-admin-email-help": "Merkit ur chomlec'h postel amañ evit gallout resev posteloù a-berzh implijerien all eus ar wiki, adderaouekaat ho ker-tremen ha bezañ kelaouet eus ar c'hemmoù degaset d'ar pajennoù zo en ho roll evezhiañ. Gallout a rit lezel ar vaezienn-mañ goullo.",
+ "config-admin-error-user": "Fazi diabarzh en ur grouiñ ur merer gant an anv \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Fazi diabarzh o lakaat ur ger-tremen evit ar merour « <nowiki>$1</nowiki> » : <pre>$2</pre>",
+ "config-admin-error-bademail": "Ebarzhet hoc'h eus ur chomlec'h postel direizh.",
+ "config-subscribe": "Koumanantit da [https://lists.wikimedia.org/mailman/listinfo/mediawiki-listenn kemennadoù evit ar stummoù nevez].",
+ "config-almost-done": "Kazi echu eo !\nGellout a rit tremen ar c'hefluniadur nevez ha staliañ ar wiki war-eeun.",
+ "config-optional-continue": "Sevel muioc'h a goulennoù ouzhin.",
+ "config-optional-skip": "Aet on skuizh, staliañ ar wiki hepken.",
+ "config-profile": "Profil ar gwirioù implijer :",
+ "config-profile-wiki": "Wiki digor",
+ "config-profile-no-anon": "Krouidigezh ur gont ret",
+ "config-profile-fishbowl": "Embanner aotreet hepken",
+ "config-profile-private": "Wiki prevez",
+ "config-license": "Copyright hag aotre-implijout:",
+ "config-license-none": "Aotre ebet en traoñ pajenn",
+ "config-license-cc-by-sa": "Creative Commons Deroadenn Kenrannañ heñvel",
+ "config-license-cc-by": "Creative Commons Deroadenn",
+ "config-license-cc-by-nc-sa": "Creative Commons Deroadenn Angenwerzhel Kenrannañ heñvel",
+ "config-license-cc-0": "Creative Commons Zero (Domani foran)",
+ "config-license-pd": "Domani foran",
+ "config-license-cc-choose": "Dibabit un aotre-implijout Creative Commons personelaet",
+ "config-email-settings": "Arventennoù ar postel",
+ "config-enable-email": "Gweredekaat ar posteloù a ya kuit",
+ "config-enable-email-help": "Mar fell deoc'h ober gant ar posteler eo ret deoc'h [http://www.php.net/manual/en/mail.configuration.php kefluniañ arventennoù postel PHP] ervat.\nMar ne fell ket deoc'h ober gant ar servij posteloù e c'hall bezañ diweredekaet amañ.",
+ "config-email-user": "Gweredekaat ar posteloù a implijer da implijer",
+ "config-email-user-help": "Aotren a ra an holl implijerien da gas posteloù an eil d'egile mard eo bet gweredekaet an arc'hwel ganto en ho penndibaboù.",
+ "config-email-usertalk": "Gweredekaat kemennadur pajennoù kaozeal an implijerien",
+ "config-email-usertalk-help": "Talvezout a ra d'an implijerien da resev kemennadennoù ma vez kemmet o fajennoù kaozeal, ma vez gweredekaet en o fenndibaboù.",
+ "config-email-watchlist": "Gweredekaat ar c'hemenn listenn evezhiañ",
+ "config-email-watchlist-help": "Talvezout a ra d'an implijerien da resev kemennadennoù diwar-benn ar pajennoù evezhiet ganto, ma vez gweredekaet en o fenndibaboù.",
+ "config-email-auth": "Gweredekaat an dilesadur dre bostel",
+ "config-email-sender": "Chomlec'h postel respont :",
+ "config-email-sender-help": "Merkit ar chomlec'h postel da vezañ implijet da chomlec'h distreiñ ar posteloù a ya er-maez.\nDi e vo kaset ar posteloù distaolet.\nNiverus eo ar servijerioù postel a c'houlenn da nebeutañ un [http://fr.wikipedia.org/wiki/Nom_de_domaine anv domani] reizh.",
+ "config-upload-settings": "Pellgargañ skeudennoù ha restroù",
+ "config-upload-enable": "Gweredekaat ar pellgargañ restroù",
+ "config-upload-deleted": "Kavlec'h evit ar restroù dilamet :",
+ "config-upload-deleted-help": "Dibab ur c'havlec'h da ziellaouiñ ar restroù diverket.\nAr pep gwellañ e vije ma ne vije ket tu d'e dizhout adalek ar Genrouedad.",
+ "config-logo": "URL al logo :",
+ "config-instantcommons": "Gweredekaat ''InstantCommons''",
+ "config-cc-error": "N'eus deuet disoc'h ebet gant dibaber aotreoù-implijout Creative Commons.\nMerkit anv an aotre-implijout gant an dorn.",
+ "config-cc-again": "Dibabit adarre...",
+ "config-cc-not-chosen": "Dibabit an aotre-implijout Creative Commons a fell deoc'h ober gantañ ha klikit war \"kenderc'hel\".",
+ "config-advanced-settings": "Kefluniadur araokaet",
+ "config-cache-options": "Arventennoù evit krubuilhañ traezoù :",
+ "config-cache-accel": "Krubuilhañ traezoù PHP (APC, XCache pe WinCache)",
+ "config-cache-memcached": "Implijout Memcached (en deus ezhomm bezañ staliet ha kefluniet)",
+ "config-memcached-servers": "Servijerioù Memcached :",
+ "config-memcached-help": "Roll ar chomlec'hioù IP da implijout evit Memcached.\nRet eo spisaat unan dre linenn ha spisaat ar porzh da vezañ implijet. Da skouer :\n127.0.0.1:11211\n192.168.1.25:1234",
+ "config-memcache-needservers": "Diuzet hoc'h eus Memcached evel seurt krubuilh met n'hoc'h eus spisaet servijer ebet.",
+ "config-memcache-badip": "Ur chomlec'h IP direizh hoc'h eus lakaet evit Memcached : $1.",
+ "config-memcache-badport": "Niverennoù porzh Memcached a zlefe bezañ etre $1 ha $2.",
+ "config-extensions": "Astennoù",
+ "config-extensions-help": "N'eo ket bet detektet an astennoù rollet a-us en ho kavlec'h <code>./astennoù</code>.\n\nMarteze e vo ezhomm kefluniañ pelloc'h met gallout a rit o gweredekaat bremañ.",
+ "config-skins": "Gwiskadurioù",
+ "config-skins-use-as-default": "Implijout ar gwiskadur-mañ dre ziouer",
+ "config-skins-must-enable-some": "Ret eo deoc'h dibab da nebautañ ur gwiskadur da weredekaat.",
+ "config-skins-must-enable-default": "Ar gwiskadur dre ziouer dibabet a rank bezañ gweredekaet.",
+ "config-install-alreadydone": "'''Diwallit''': Staliet hoc'h eus MediaWiki dija war a seblant hag emaoc'h o klask e staliañ c'hoazh.\nKit d'ar bajenn war-lerc'h, mar plij.",
+ "config-install-begin": "Pa vo bet pouezet ganeoc'h war \"{{int:config-continue}}\" e krogo staliadur MediaWiki.\nPouezit war \"{{int:config-back}}\" mar fell deoc'h cheñch tra pe dra.",
+ "config-install-step-done": "graet",
+ "config-install-step-failed": "c'hwitet",
+ "config-install-extensions": "En ur gontañ an astennoù",
+ "config-install-database": "Krouiñ an diaz roadennoù",
+ "config-install-schema": "O krouiñ ar chema",
+ "config-install-pg-schema-not-exist": "N'eus ket eus chema PostgreSQL.",
+ "config-install-pg-schema-failed": "C'hwitet eo krouidigezh an taolennoù.\nGwiriit hag-eñ e c'hall an implijer « $1 » skrivañ er brastres « $2 ».",
+ "config-install-pg-commit": "O wiriekaat ar c'hemmoù",
+ "config-install-pg-plpgsql": "O wiriañ ar yezh PL/pgSQL",
+ "config-pg-no-plpgsql": "Ret eo deoc'h staliañ ar yezh PL/pgSQL en diaz roadennoù $1",
+ "config-pg-no-create-privs": "N'eus ket gwirioù a-walc'h gant ar gont hoc'h eus merket evit ar staliadur evit gallout krouiñ ur gont.",
+ "config-install-user": "O krouiñ an diaz roadennoù implijer",
+ "config-install-user-alreadyexists": "An implijer \"$1\" zo anezhañ dija",
+ "config-install-user-create-failed": "Fazi e-ser krouiñ an implijer \"$1\" : $2",
+ "config-install-user-grant-failed": "N'eus ket bet gallet reiñ an aotre d'an implijer \"$1\" : $2",
+ "config-install-user-missing": "N'eus ket eus an implijer \"$1\"",
+ "config-install-user-missing-create": "N'eus ket eus an implijer \"$1\".\nMa fell deoc'h krouiñ anezhañ, klikit war ar voest \"krouiñ ur gont\" amañ dindan.",
+ "config-install-tables": "Krouiñ taolennoù",
+ "config-install-tables-exist": "<strong>Kemenn :</strong> An taolennoù MediaWiki zo anezho dija war a seblant.\nN'eo ket bet graet ar grouidigezh.",
+ "config-install-tables-failed": "'''Fazi :''' c'hwitet eo krouidigezh an daolenn gant ar fazi-mañ : $1",
+ "config-install-interwiki": "O leuniañ dre ziouer an daolenn etrewiki",
+ "config-install-interwiki-list": "Ne c'haller ket kavout ar restr <code>interwiki.list</code>.",
+ "config-install-stats": "O sevel ar stadegoù",
+ "config-install-keys": "Genel an alc'hwezioù kuzh",
+ "config-install-updates": "Mirout da lakaat hizivadennoù diezhomm da vont en-dro",
+ "config-install-sysop": "Krouidigezh kont ar merour",
+ "config-install-subscribe-fail": "N'haller ket koumanantiñ da mediawiki-announce : $1",
+ "config-install-subscribe-notpossible": "cURL n'eo ket staliet ha ne c'haller ket ober gant <code>allow_url_fopen</code>.",
+ "config-install-mainpage": "O krouiñ ar bajenn bennañ gant un endalc'had dre ziouer",
+ "config-install-extension-tables": "O krouiñ taolennoù evit an astennoù gweredekaet",
+ "config-install-mainpage-failed": "Ne c'haller ket ensoc'hañ ar bajenn bennañ: $1",
+ "config-download-localsettings": "Pellgargañ <code>LocalSettings.php</code>",
+ "config-help": "skoazell",
+ "config-help-tooltip": "klikañ evit astenn",
+ "config-nofile": "N'eus ket bet gallet kavout ar restr \"$1\". Daoust ha dilamet eo bet ?",
+ "mainpagetext": "'''Meziant MediaWiki staliet.'''",
+ "mainpagedocfooter": "Sellit ouzh [//meta.wikimedia.org/wiki/Help:Contents Sturlevr an implijerien] evit gouzout hiroc'h war an doare da implijout ar meziant wiki.\n\n== Kregiñ ganti ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Roll an arventennoù kefluniañ]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAG MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Roll ar c'haozeadennoù diwar-benn dasparzhoù MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lec'hiañ MediaWiki en ho yezh"
+}
diff --git a/includes/installer/i18n/bs.json b/includes/installer/i18n/bs.json
new file mode 100644
index 00000000..28b14fae
--- /dev/null
+++ b/includes/installer/i18n/bs.json
@@ -0,0 +1,59 @@
+{
+ "@metadata": {
+ "authors": [
+ "CERminator"
+ ]
+ },
+ "config-desc": "Instalacija za MediaWiki",
+ "config-title": "MediaWiki $1 instalacija",
+ "config-information": "Informacija",
+ "config-localsettings-upgrade": "Otkrivena je datoteka <code>LocalSettings.php</code>.\nDa biste unaprijedili vaš softver, molimo vas upišite vrijednost od <code>$wgUpgradeKey</code> u okvir ispod.\nNaći ćete ga u <code>LocalSettings.php</code>.",
+ "config-localsettings-key": "Ključ za nadgradnju:",
+ "config-session-error": "Greška pri pokretanju sesije: $1",
+ "config-no-session": "Vaši podaci sesije su izgubljeni!\nProvjerite vaš php.ini i provjerite da li je <code>session.save_path</code> postavljen na pravilni direktorijum.",
+ "config-your-language": "Vaš jezik:",
+ "config-your-language-help": "Odaberite jezik koji ćete koristiti tokom procesa instalacije.",
+ "config-wiki-language": "Wiki jezik:",
+ "config-wiki-language-help": "Odaberite jezik na kojem će wiki biti najvećim dijelim pisana.",
+ "config-back": "← Nazad",
+ "config-continue": "Nastavi →",
+ "config-page-language": "Jezik",
+ "config-page-welcome": "Dobrodošli u MediaWiki!",
+ "config-page-dbconnect": "Poveži sa bazom podataka",
+ "config-page-upgrade": "Unaprijedi postojeću instalaciju",
+ "config-page-dbsettings": "Postavke baze podataka",
+ "config-page-name": "Naziv",
+ "config-page-options": "Opcije",
+ "config-page-install": "Instaliraj",
+ "config-page-complete": "Završeno!",
+ "config-page-restart": "Ponovi instalaciju ispočetka",
+ "config-page-readme": "Pročitaj me",
+ "config-page-releasenotes": "Bilješke izdanja",
+ "config-page-copying": "Kopiram",
+ "config-page-upgradedoc": "Nadograđujem",
+ "config-help-restart": "Da li želite očistiti sve spremljene podatke koje ste unijeli i da započnete ponovo proces instalacije?",
+ "config-restart": "Da, pokreni ponovo",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki Početna strana]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Vodič za korisnike]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Vodič za administratore]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ NPP]\n----\n* <doclink href=Readme>Pročitaj me</doclink>\n* <doclink href=ReleaseNotes>Napomene izdanja</doclink>\n* <doclink href=Copying>Kopiranje</doclink>\n* <doclink href=UpgradeDoc>Poboljšavanje</doclink>",
+ "config-env-good": "Okruženje je provjereno.\nMožete instalirati MediaWiki.",
+ "config-env-php": "PHP $1 je instaliran.",
+ "config-no-db": "Nije mogao biti pronađen pogodan driver za bazu podataka! Morate instalirati driver baze podataka za PHP.\nSlijedeće vrste baza podataka su podržane: $1.\n\nAko se na dijeljenom serveru, tražite od vašeg pružaoca usluga da instalira pogodan driver za bazu podataka.\nAko se sami kompajlirali PHP, podesite ga sa omogućenim klijentom baze podataka, koristeći naprimjer <code>./configure --with-mysql</code>.\nAko ste instalirali PHP iz Debian ili Ubuntu paketa, možda morate instalirati i modul php5-mysql.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] je instaliran",
+ "config-apc": "[http://www.php.net/apc APC] je instaliran",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] je instaliran",
+ "config-diff3-bad": "GNU diff3 nije pronađen.",
+ "config-db-type": "Vrsta baze podataka:",
+ "config-db-host": "Domaćin baze podataka:",
+ "config-db-wiki-settings": "Identificiraj ovu wiki",
+ "config-db-name": "Naziv baze podataka:",
+ "config-db-name-oracle": "Šema baze podataka:",
+ "config-header-mysql": "Postavke MySQL",
+ "config-header-postgres": "Postavke PostgreSQL",
+ "config-header-sqlite": "Postavke SQLite",
+ "config-header-oracle": "Postavke Oracle",
+ "config-invalid-db-type": "Nevaljana vrsta baze podataka",
+ "config-upgrade-done": "Nadogradnja završena.\n\nSada možete [$1 početi koristiti vašu wiki].\n\nAko želite regenerisati vašu datoteku <code>LocalSettings.php</code>, kliknite na dugme ispod.\nOvo '''nije preporučeno''' osim ako nemate problema s vašom wiki.",
+ "config-admin-name": "Vaše ime:",
+ "config-admin-password": "Šifra:",
+ "mainpagetext": "'''MediaViki softver is uspješno instaliran.'''",
+ "mainpagedocfooter": "Kontaktirajte [//meta.wikimedia.org/wiki/Help:Contents uputstva za korisnike] za informacije o upotrebi wiki programa.\n\n== Početak ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista postavki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki najčešće postavljana pitanja]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]"
+}
diff --git a/includes/installer/i18n/bto.json b/includes/installer/i18n/bto.json
new file mode 100644
index 00000000..468bcebd
--- /dev/null
+++ b/includes/installer/i18n/bto.json
@@ -0,0 +1,65 @@
+{
+ "@metadata": {
+ "authors": [
+ "Filipinayzd"
+ ]
+ },
+ "config-title": "Pabutang ka MediaWiki $1",
+ "config-information": "Impormasyon",
+ "config-your-language": "A kanimong sarita:",
+ "config-wiki-language": "Sarita ka Wiki:",
+ "config-back": "← Bumalik",
+ "config-continue": "Magpadagos →",
+ "config-page-language": "Sarita",
+ "config-page-welcome": "Dagos sa MediaWiki!",
+ "config-page-name": "Ngaran",
+ "config-page-options": "Mga pipilian",
+ "config-page-install": "Ikabit",
+ "config-page-complete": "Tapos!",
+ "config-page-readme": "Basahon ako",
+ "config-page-copying": "Kinokopya",
+ "config-restart": "Amo, uliton adi",
+ "config-diff3-bad": "Diri nataurakan a GNU diff3.",
+ "config-db-type": "Klase ka database:",
+ "config-db-host": "Host ka database:",
+ "config-db-host-oracle": "Database ka TNS:",
+ "config-db-wiki-settings": "Mibdiron adin wiki",
+ "config-db-name": "Ngaran ka database:",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 backwards-compatible UTF-8",
+ "config-db-port": "Port ka database:",
+ "config-db-schema": "Skema para sa MediaWiki:",
+ "config-sqlite-dir": "Direktoryo ka data sa SQLite:",
+ "config-oracle-def-ts": "Dating tablescape:",
+ "config-oracle-temp-ts": "Temporaryong tablescape:",
+ "config-type-mysql": "MySQL (o compatible)",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-header-mysql": "MySQL settings",
+ "config-header-postgres": "PostgreSQL settings",
+ "config-header-sqlite": "SQLite settings",
+ "config-header-oracle": "Oracle settings",
+ "config-header-mssql": "Microsoft SQL Server settings",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "Binary",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Ngaran ka wiki",
+ "config-site-name-blank": "Ibutang a ngaran ka site.",
+ "config-project-namespace": "Bibutangan ka proyekto:",
+ "config-ns-generic": "Proyekto",
+ "config-ns-other-default": "MyWiki",
+ "config-admin-password": "Password:",
+ "config-admin-password-confirm": "Password ulit:",
+ "config-admin-email": "Email address:",
+ "config-profile-wiki": "Bukas na wiki",
+ "config-profile-private": "Pribadong wiki",
+ "config-license-pd": "Pampublikong Domain",
+ "config-email-sender": "Pabalik na email adres:",
+ "config-logo": "URL ko logo:",
+ "config-cc-again": "Pumili dayday...",
+ "config-install-step-done": "tapus na",
+ "config-install-user-alreadyexists": "Agko na ka user na \"$1\"",
+ "config-install-user-create-failed": "Sala a ginigibong user na \"$1\": $2",
+ "config-help": "tabang"
+}
diff --git a/includes/installer/i18n/ca.json b/includes/installer/i18n/ca.json
new file mode 100644
index 00000000..3f1fcfc0
--- /dev/null
+++ b/includes/installer/i18n/ca.json
@@ -0,0 +1,203 @@
+{
+ "@metadata": {
+ "authors": [
+ "Pitort",
+ "පසිඳු කාවින්ද",
+ "Kippelboy",
+ "Toniher",
+ "Fitoschido"
+ ]
+ },
+ "config-desc": "L'instal·lador del MediaWiki",
+ "config-title": "Instal·lació del MediaWiki $1",
+ "config-information": "Informació",
+ "config-localsettings-upgrade": "S'ha detectat un fitxer <code>LocalSettings.php</code>. \nPer tal d'actualitzar la instal·lació, introduïu el valor de <code>$wgUpgradeKey</code> en el quadre a continuació. El trobareu a <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "S'ha detectat un fitxer <code>LocalSettings.php</code>.\nPer tal d'actualitzar la instal·lació, executeu <code>update.php</code>.",
+ "config-localsettings-key": "Clau d'actualització:",
+ "config-localsettings-badkey": "La clau que heu proporcionat no és correcta.",
+ "config-upgrade-key-missing": "S'ha detectat una instal·lació ja existent del MediaWiki.\nPer actualitzar-la, poseu la línia següent al final de <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "El <code>LocalSettings.php</code> que hi ha sembla incomplet.\nLa variable $1 no està definida.\nCanvieu <code>LocalSettings.php</code> perquè la variable estigui definida i feu clic a «{{int:Config-continue}}».",
+ "config-localsettings-connection-error": "S'ha trobat un error en connectar-se amb la base de dades fent servir els paràmetres especificats a <code>LocalSettings.php</code>. Corregiu aquests paràmetres i torneu-ho a provar.\n\n$1",
+ "config-session-error": "Error en iniciar la sessió: $1",
+ "config-your-language": "La vostra llengua:",
+ "config-your-language-help": "Seleccioneu la llengua que s'utilitzarà durant el procés d'instal·lació.",
+ "config-wiki-language": "Llengua del wiki:",
+ "config-wiki-language-help": "Seleccioneu la llengua principal del wiki.",
+ "config-back": "← Enrere",
+ "config-continue": "Continua →",
+ "config-page-language": "Llengua",
+ "config-page-welcome": "Us donem la benvinguda al MediaWiki!",
+ "config-page-dbconnect": "Connecta a la base de dades",
+ "config-page-upgrade": "Actualitza una instal·lació ja existent",
+ "config-page-dbsettings": "Paràmetres de la base de dades",
+ "config-page-name": "Nom",
+ "config-page-options": "Opcions",
+ "config-page-install": "Instal·la",
+ "config-page-complete": "S'ha completat!",
+ "config-page-restart": "Reinicia la instal·lació",
+ "config-page-readme": "Llegeix-me",
+ "config-page-releasenotes": "Notes de la versió",
+ "config-page-copying": "S'està copiant",
+ "config-page-upgradedoc": "S'està actualitzant",
+ "config-page-existingwiki": "Wiki ja existent",
+ "config-help-restart": "Voleu esborrar totes les dades que heu introduït i tornar a començar el procés d'instal·lació?",
+ "config-restart": "Sí, torna a començar",
+ "config-welcome": "=== Comprovacions de l'entorn ===\nS'efectuaran comprovacions bàsiques per veure si l'entorn és adequat per a la instal·lació del MediaWiki.\nRecordeu d'incloure aquesta informació si heu de demanar ajuda sobre com completar la instal·lació.",
+ "config-env-good": "S'ha comprovat l'entorn.\nPodeu instal·lar el MediaWiki.",
+ "config-env-bad": "S'ha comprovat l'entorn.\nNo podeu instal·lar el MediaWiki.",
+ "config-env-php": "El PHP $1 està instal·lat.",
+ "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’[http://www.php.net/apc APC] està instal·lat",
+ "config-diff3-bad": "No s'ha trobat el GNU diff3.",
+ "config-git": "S'ha trobat el programari de control de versions Git: <code>$1</code>.",
+ "config-git-bad": "No s'ha trobat el programari de control de versions Git.",
+ "config-no-scaling": "No s'ha pogut trobar la biblioteca GD o ImageMagick.\nS'inhabilitaran les miniatures de les imatges.",
+ "config-no-uri": "'''Error:''' No s'ha pogut determinar l'URI actual. S'ha interromput la instal·lació.",
+ "config-no-cli-uri": "'''Avís:''' No s'ha especificat un <code>--scriptpath</code>. S'utilitza el valor per defecte: <code>$1</code>.",
+ "config-using-server": "S'utilitza el nom del servidor «<nowiki>$1</nowiki>».",
+ "config-using-uri": "S'utilitza l'URL del servidor «<nowiki>$1$2</nowiki>».",
+ "config-uploads-not-safe": "<strong>Avís:</strong> El directori de càrregues per defecte <code>$1</code> és vulnerable a l'execució d'scripts arbitraris.\nEncara que el MediaWiki comprova tots els fitxers que es carreguen davant d'amenaces de seguretat, és molt recomanable [//www.mediawiki.org/ wiki/Special:MyLanguage/Manual:Security#Upload_security tancar aquesta vulnerabilitat de seguretat] abans d'habilitar les càrregues.",
+ "config-db-type": "Tipus de base de dades:",
+ "config-db-host": "Servidor de la base de dades:",
+ "config-db-wiki-settings": "Identifica aquest wiki",
+ "config-db-name": "Nom de la base de dades:",
+ "config-db-name-help": "Trieu un nom que identifiqui el wiki.\nNo ha de contenir espais.\n\nSi esteu fent servir un hostatge web compartit, el vostre proveïdor us proporcionarà un nom específic per a la base de dades o us permetrà crear base de dades des d'un tauler de control.",
+ "config-db-name-oracle": "Esquema de la base de dades:",
+ "config-db-install-account": "Compte d'usuari per a la instal·lació",
+ "config-db-username": "Nom d'usuari de la base de dades:",
+ "config-db-password": "Contrasenya de la base de dades:",
+ "config-db-username-empty": "Heu d'introduir un valor per a «{{int:config-db-username}}»",
+ "config-db-account-lock": "Utilitzeu el mateix nom d'usuari i contrasenya durant una operació normal",
+ "config-db-wiki-account": "Compte d'usuari per al funcionament normal",
+ "config-db-wiki-help": "Introduïu el nom d'usuari i la contrasenya que s'utilitzarà per connectar-se a la base de dades durant l'operació normal del wiki.\nSi el compte no existeix, i el compte d'instal·lació té prou privilegis, es crearà aquest compte d'usuari amb els privilegis mínims necessaris per operar el wiki.",
+ "config-db-prefix": "Prefix de la base de dades:",
+ "config-db-prefix-help": "Si heu de compartir una base de dades entre diversos wikis, o entre el MediaWiki i una altra aplicació web, podeu afegir un prefix al tots els noms de taula per tal d'evitar conflictes.\nNo utilitzeu espais.\n\nAquest camp acostuma a quedar en blanc.",
+ "config-db-charset": "Joc de caràcters de la base de dades",
+ "config-charset-mysql5-binary": "Binari de MySQL 4.1/5.0",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-mysql-old": "Cal el MySQL $1 o posterior. Teniu el $2.",
+ "config-db-port": "Port de la base de dades:",
+ "config-db-schema": "Esquema per a MediaWiki:",
+ "config-db-schema-help": "Aquest esquema normalment ja serveix.\nNomés canvieu-lo si sabeu què us feu.",
+ "config-pg-test-error": "No es pot connectar a la base de dades '''$1''': $2",
+ "config-sqlite-dir": "Directori de dades de l'SQLite",
+ "config-oracle-def-ts": "Espai de taules per defecte:",
+ "config-oracle-temp-ts": "Espai de taules temporal:",
+ "config-type-mysql": "MySQL (o compatible)",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-header-mysql": "Paràmetres de MySQL",
+ "config-header-postgres": "Paràmetres del PostgreSQL",
+ "config-header-sqlite": "Paràmetres de l'SQLite",
+ "config-header-oracle": "Paràmetres de l'Oracle",
+ "config-header-mssql": "Paràmetres del Microsoft SQL Server",
+ "config-invalid-db-type": "Tipus de base de dades no vàlid",
+ "config-missing-db-name": "Heu d'introduir un valor per a «{{int:config-db-name}}».",
+ "config-missing-db-host": "Heu d'introduir un valor per a «{{int:config-db-host}}».",
+ "config-missing-db-server-oracle": "Heu d’introduir un valor per a «{{int:config-db-host-oracle}}».",
+ "config-db-sys-user-exists-oracle": "El compte d’usuari «$1» ja existeix. SYSDBA només es pot fer servir per crear comptes nous.",
+ "config-sqlite-readonly": "El fitxer <code>$1</code> no es pot escriure.",
+ "config-sqlite-cant-create-db": "No s'ha pogut crear el fitxer de base de dades <code>$1</code>.",
+ "config-upgrade-done-no-regenerate": "S'ha completat l'actualització.\n\nJa podeu [$1 començar a utilitzar el wiki].",
+ "config-regenerate": "Torna a generar el LocalSettings.php →",
+ "config-show-table-status": "La consulta <code>SHOW TABLE STATUS</code> ha fallat!",
+ "config-db-web-account": "Compte de la base de dades per a l'accés web",
+ "config-db-web-account-same": "Utilitza el mateix compte que a la instal·lació",
+ "config-db-web-create": "Crea el compte si no existeix encara",
+ "config-db-web-no-create-privs": "El compte que heu especificat a la instal·lació no té suficients privilegis per crear un compte. El compte que especifiqueu aquí ja ha d'existir.",
+ "config-mysql-engine": "Motor d'emmagatzemament:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Joc de caràcters de la base de dades:",
+ "config-mysql-binary": "Binari",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Tipus d'autenticació:",
+ "config-mssql-sqlauth": "Autenticació de l’SQL Server",
+ "config-mssql-windowsauth": "Autenticació del Windows",
+ "config-site-name": "Nom del wiki:",
+ "config-site-name-blank": "Introduïu un nom per al lloc.",
+ "config-project-namespace": "Espai de noms del projecte:",
+ "config-ns-generic": "Projecte",
+ "config-ns-site-name": "El mateix que el nom del wiki: $1",
+ "config-ns-other": "Un altre (especifiqueu-lo)",
+ "config-ns-other-default": "MonWiki",
+ "config-admin-box": "Compte de l'administrador",
+ "config-admin-name": "El vostre nom d'usuari:",
+ "config-admin-password": "Contrasenya:",
+ "config-admin-password-confirm": "Repetiu la contrasenya:",
+ "config-admin-help": "Introduïu el vostre nom d'usuari preferit, per exemple «Pep Bloggs».\nAquest és el nom que fareu servir per iniciar una sessió al wiki.",
+ "config-admin-name-blank": "Introduïu un nom d'usuari d'administrador.",
+ "config-admin-name-invalid": "El nom d'usuari especificat «<nowiki>$1</nowiki>» no és vàlid.\nEspecifiqueu un nom d'usuari diferent.",
+ "config-admin-password-blank": "Introduïu una contrasenya per al compte d'administrador.",
+ "config-admin-password-mismatch": "Les dues contrasenyes que heu introduït no coincideixen.",
+ "config-admin-email": "Adreça electrònica:",
+ "config-admin-error-bademail": "Heu introduït una adreça electrònica no vàlida.",
+ "config-almost-done": "Gairebé ja heu acabat!\nPodeu ometre el que queda de la configuració i procedir amb la instal·lació del wiki.",
+ "config-optional-continue": "Fes-me més preguntes.",
+ "config-optional-skip": "Ja estic avorrit. Simplement instal·leu el wiki.",
+ "config-profile": "Perfil de permisos d'usuari:",
+ "config-profile-wiki": "Wiki públic",
+ "config-profile-no-anon": "Cal la creació d'un compte",
+ "config-profile-fishbowl": "Només editors autoritzats",
+ "config-profile-private": "Wiki privat",
+ "config-license": "Copyright i llicència:",
+ "config-license-none": "Sense llicència al peu de pàgina",
+ "config-license-pd": "Domini públic",
+ "config-email-settings": "Paràmetres del correu electrònic",
+ "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",
+ "config-email-watchlist": "Habilita la notificació de la llista de seguiment",
+ "config-email-watchlist-help": "Permet als usuaris rebre notificacions de les pàgines que segueixen si ho han habilitat a les preferències.",
+ "config-email-auth": "Habilita l'autenticació per correu electrònic",
+ "config-email-sender": "Adreça electrònica de retorn:",
+ "config-upload-settings": "Imatges i càrregues de fitxers",
+ "config-upload-enable": "Habilita la càrrega de fitxers",
+ "config-upload-deleted": "Directori pels arxius suprimits:",
+ "config-logo": "URL del logo:",
+ "config-instantcommons": "Habilita Instant Commons",
+ "config-cc-error": "El selector de llicència Creative Commons no ha donat cap resultat.\nIntroduïu la llicència manualment.",
+ "config-cc-again": "Torneu-ho a triar...",
+ "config-cc-not-chosen": "Trieu quina llicència Creative Commons voleu i feu clic a «procedeix».",
+ "config-advanced-settings": "Configuració avançada",
+ "config-cache-options": "Configuració per a la memòria cau dels objectes:",
+ "config-cache-help": "L'encauament d'objectes s'utilitza per a millorar la rapidesa del MediaWiki afegint a la memòria cau les dades que s'utilitzen de forma freqüent. És recomanable que els llocs web mitjans o grans ho habilitin. També els llocs web petits en veuran els beneficis.",
+ "config-cache-none": "Sense encauament (no se suprimeix cap funcionalitat, però la velocitat pot veure's afectada en els llocs wiki més grans)",
+ "config-memcached-servers": "Servidors de Memcache:",
+ "config-extensions": "Extensions",
+ "config-skins": "Aparences",
+ "config-install-step-done": "fet",
+ "config-install-step-failed": "ha fallat",
+ "config-install-extensions": "S'estan incloent les extensions",
+ "config-install-database": "S'està configurant la base de dades",
+ "config-install-schema": "S'està creant l'esquema",
+ "config-install-pg-schema-not-exist": "No existeix un esquema PostgreSQL.",
+ "config-install-pg-schema-failed": "La creació de les taules ha fallat.\nAssegureu-vos que l'usuari «$1» pot escriure a l'esquema «$2».",
+ "config-install-pg-commit": "S'estan trametent els canvis",
+ "config-install-user": "S'està creant l'usuari de la base de dades",
+ "config-install-user-alreadyexists": "L'usuari «$1» ja existeix",
+ "config-install-user-create-failed": "La creació de l'usuari «$1» ha fallat: $2",
+ "config-install-user-grant-failed": "Ha fallat la concessió de permisos a l'usuari «$1»: $2",
+ "config-install-user-missing": "L'usuari «$1» especificat no existeix.",
+ "config-install-user-missing-create": "L'usuari «$1» especificat no existeix.\nFeu clic a la casella «Crea un compte» a continuació si voleu crear-lo.",
+ "config-install-tables": "S'estan creant les taules",
+ "config-install-tables-exist": "'''Avís:''' sembla que les taules del MediaWiki tables ja existeixen. Se n'omet la creació.",
+ "config-install-tables-failed": "'''Error:''' la creació de la taula ha fallat amb l'error següent: $1",
+ "config-install-interwiki": "S'està emplenant la taula per defecte d'interwiki",
+ "config-install-interwiki-list": "No s'ha pogut llegir el fitxer <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Avís:''' La taula d'interwiki sembla que ja té entrades. S'omet la llista per defecte.",
+ "config-install-stats": "S'estan inicialitzant les estadístiques",
+ "config-install-keys": "S'estan generant les claus secretes",
+ "config-install-sysop": "S'està creant un compte d'usuari d'administrador",
+ "config-install-subscribe-fail": "No s'ha pogut subscriure a mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "El cURL no està instal·lat i <code>allow_url_fopen</code> no està disponible.",
+ "config-install-mainpage": "S'està creant la pàgina principal amb el contingut per defecte",
+ "config-install-extension-tables": "S'estan creant taules de les extensions habilitades",
+ "config-install-mainpage-failed": "No s'ha pogut inserir la pàgina principal: $1",
+ "config-download-localsettings": "Baixa <code>LocalSettings.php</code>",
+ "config-help": "ajuda",
+ "config-help-tooltip": "feu clic per ampliar",
+ "config-nofile": "No s'ha pogut trobar el fitxer «$1». S'ha suprimit?",
+ "mainpagetext": "'''El MediaWiki s'ha instal·lat correctament.'''",
+ "mainpagedocfooter": "Consulteu la [//meta.wikimedia.org/wiki/Help:Contents Guia d'Usuari] per a més informació sobre com utilitzar-lo.\n\n== Per a començar ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Llista de característiques configurables]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ PMF del MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de correu (''listserv'') per a anuncis del MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Traduïu MediaWiki en la vostra llengua]"
+}
diff --git a/includes/installer/i18n/ce.json b/includes/installer/i18n/ce.json
new file mode 100644
index 00000000..0cffa94a
--- /dev/null
+++ b/includes/installer/i18n/ce.json
@@ -0,0 +1,92 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sasan700",
+ "Умар",
+ "Seb35"
+ ]
+ },
+ "config-desc": "MediaWiki инсталлятор",
+ "config-title": "ХӀоттор MediaWiki $1",
+ "config-information": "Хаам",
+ "config-localsettings-key": "Карлаяккхаран догӀа:",
+ "config-localsettings-badkey": "Ахьа яздина нийса доцу догӀа",
+ "config-your-language": "Хьан мотт:",
+ "config-wiki-language": "Вики чохь лелор болу мотт",
+ "config-back": "← Юха",
+ "config-continue": "Кхин дӀа →",
+ "config-page-language": "Мотт",
+ "config-page-welcome": "Марша догӀийла MediaWiki чу!",
+ "config-page-name": "ЦӀе",
+ "config-page-options": "Параметраш",
+ "config-page-install": "ХӀоттор",
+ "config-page-complete": "Кийчча ю!",
+ "config-page-restart": "Юху доладе дӀахӀоттор",
+ "config-page-readme": "Еша со",
+ "config-page-releasenotes": "Версех лаьцна хаам",
+ "config-page-copying": "Лицензи",
+ "config-page-upgradedoc": "Карлаяккхар",
+ "config-page-existingwiki": "Йолуш йолу вики",
+ "config-copyright": "=== Авторан бакъонаш а хьал а ===\n\n$1\nMediaWiki ю маьрша программин латораг, шу йиш ю фондас арахецна йолу GNU General Public License лицензица и яржо я хийца а.\n\nMediaWiki яржош ю и шуна пайдане хир яц те аьлла, амма цхьа юкъарахилар доцуш. Хь. кхин. лицензи мадарра GNU General Public License .\n\nШоьга кхача езаш яра [{{SERVER}}{{SCRIPTPATH}}/COPYING копи GNU General Public License] хӀокху программица, кхаьчна яцахь язъе Free Software Foundation, Inc., адрес тӀе: 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA я [//www.gnu.org/licenses/old-licenses/gpl-2.0.html еша и онлайнехь].",
+ "config-no-fts3": "'''Тергам бе''': SQLite гулйина хуттург йоцуш [//sqlite.org/fts3.html FTS3] — лахар болхбеш хир дац оцу бухца.",
+ "config-no-cli-uri": "'''ДӀахьедар''': <code>--scriptpath</code> параметр язйина яц, иза Ӏад йитарца лелош ю: <code>$1</code> .",
+ "config-db-name": "Хаамийн базан цӀе:",
+ "config-db-username-empty": "Ахьа «{{int:config-db-username}}» параметран маьӀна даздан дезаш ду.",
+ "config-db-charset": "Базан хаамийн символийн гулам",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 бинаран",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-header-mssql": "Microsoft SQL Server параметраш",
+ "config-invalid-db-type": "Хаамийн базан нийса йоцу тайп",
+ "config-missing-db-name": "Ахьа «{{int:config-db-name}}» маьӀна даздан дезаш ду.",
+ "config-missing-db-host": "Ахьа «{{int:config-db-host}}» параметран маьӀна даздан дезаш ду.",
+ "config-missing-db-server-oracle": "Ахьа тӀеюза езаш ю «{{int:config-db-host-oracle}}»",
+ "config-invalid-db-server-oracle": "Хаамийн базан «$1» нийса йоцу TNS.\nЛелае «TNS Name», я могӀа «Easy Connect» ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm ЦӀераш техкаран кеп Oracle])",
+ "config-sqlite-fts3-downgrade": "PHPн гӀо до FTS3 яц — кхуссу таблицаш",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Аутентификацин тайп:",
+ "config-site-name": "Викин цӀе:",
+ "config-site-name-blank": "Язъе сайтан цӀе.",
+ "config-project-namespace": "ЦӀерийн ана проектан:",
+ "config-ns-generic": "Проект",
+ "config-admin-password-confirm": "Кхин цӀа пароль:",
+ "config-profile-wiki": "Елин вики",
+ "config-profile-no-anon": "ДӀаяздар кхолла деза",
+ "config-profile-fishbowl": "ДӀаяздарш долу тадархошна бен",
+ "config-profile-private": "ДӀачӀаьгӀна вики",
+ "config-license": "Авторан бакъонаш а лицензи а:",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (юкъаралин рицӀкъ)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 я кхин хьалха хиларг",
+ "config-license-pd": "Юкъараллин хьал",
+ "config-license-cc-choose": "Харжа цхьа лицензи Creative Commons",
+ "config-email-settings": "Электронан почта нисяр",
+ "config-enable-email": "Латае дӀайохьуьйту e-mail",
+ "config-upload-deleted": "ДӀаяхна файлийн директори:",
+ "config-cc-again": "Хьаржа кхин цӀа…",
+ "config-skins": "Кечяран тема",
+ "config-skins-use-as-default": "ХӀара тема Ӏад йитарца лелае",
+ "config-skins-must-enable-some": "Ахьа цхьаъ мукъа тема латина йита езаш ю.",
+ "config-skins-must-enable-default": "Ӏад йитарца йолу тема латина хила еза.",
+ "config-install-user": "Декъашхочун хаамийн база кхоллар",
+ "config-install-user-alreadyexists": "Декъашхо «$1» хӀинцале волуш ву",
+ "config-install-user-create-failed": "Декъашхо «$1» кхолла цаделира: $2",
+ "config-install-user-grant-failed": "Декъашхочун «$1» бакъонаш яларан гӀалат: $2",
+ "config-install-user-missing": "Билгалвина декъашхо «$1» вац.",
+ "config-install-tables": "Таблицаш кхоллар",
+ "config-install-tables-exist": "'''ДӀахьедар''': MediaWiki таблицаш, йолуш хила там бу.\nЮха кхоллар чекхдалийтар.",
+ "config-install-tables-failed": "'''ГӀалат''': Таблица кхолла таро яц гӀалат бахьнехь: $1",
+ "config-install-interwiki-list": "Файл <code>interwiki.list</code> каро цаелира.",
+ "config-install-stats": "Инициализацин статистика",
+ "config-install-keys": "Къайлаха долу догӀанаш кхоллар",
+ "config-install-sysop": "Куьйгалхочун дӀаяздар кхоллар",
+ "config-install-subscribe-notpossible": "cURL дӀахӀоттийна яц я тӀекхочехь яц опци <code>allow_url_fopen</code>.",
+ "config-install-mainpage-failed": "Коьрта агӀо йилла цатарло: $1",
+ "config-download-localsettings": "Чуяккха <code>LocalSettings.php</code>",
+ "config-help": "гӀо",
+ "config-nofile": "Файл \"$1\" каро цаелира. И дӀаяьккхина ярий?",
+ "mainpagetext": "'''Вики-белха гlирс «MediaWiki» кхочуш дика дlахlоттийна.'''",
+ "mainpagedocfooter": "Викийца болх бан хаамаш карор бу хӀокху чохь [//meta.wikimedia.org/wiki/Help:Contents нисвохааман куьйгаллица].\n\n== Цхьаболу пайде гӀирсаш ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings ГӀирс нисбан тарлушболу могӀам];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Сих сиха лушдолу хаттарш а жоьпаш оцу MediaWiki];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Хаам бохьуьйту араяларца башхонца керла MediaWiki].\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/ceb.json b/includes/installer/i18n/ceb.json
new file mode 100644
index 00000000..3bdbf050
--- /dev/null
+++ b/includes/installer/i18n/ceb.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Malamposon ang pag-instalar sa MediaWiki.'''",
+ "mainpagedocfooter": "Konsultaha ang [//meta.wikimedia.org/wiki/Help:Contents Giya sa mga gumagamit] alang sa impormasyon unsaon paggamit niining wiki nga software.\n\n== Pagsugod ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Listahan sa mga setting sa kompigurasyon]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ sa MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list sa mga release sa MediaWiki]"
+}
diff --git a/includes/installer/i18n/ckb.json b/includes/installer/i18n/ckb.json
new file mode 100644
index 00000000..d0db0849
--- /dev/null
+++ b/includes/installer/i18n/ckb.json
@@ -0,0 +1,42 @@
+{
+ "@metadata": {
+ "authors": [
+ "Asoxor",
+ "Calak",
+ "Muhammed taha"
+ ]
+ },
+ "config-information": "زانیاری",
+ "config-your-language": "زمانەکەت:",
+ "config-wiki-language": "زمانی ویکی:",
+ "config-back": "→ گەڕانەوە",
+ "config-continue": "بەردەوامبوون ←",
+ "config-page-language": "زمان",
+ "config-page-welcome": "بەخێربێیت بۆ میدیاویکی!",
+ "config-page-dbconnect": "پەیوەندی دەکات بەبنکەی زانیارییەکان",
+ "config-page-upgrade": "نويکردنەوەی دابەزاندنی پێشوو",
+ "config-page-dbsettings": "ڕێکخستنەکانی بنکەی زانیارییەکان",
+ "config-page-name": "ناو",
+ "config-page-options": "ھەڵبژاردەکان",
+ "config-page-install": "دابەزاندن",
+ "config-page-complete": "تەواو!",
+ "config-page-restart": "دەست پێکردنەوە بەدابەزاندن",
+ "config-page-readme": "بمخوێنەوە",
+ "config-page-copying": "لەبەردەگیرێتەوە",
+ "config-page-upgradedoc": "نوێدەکرێتەوە",
+ "config-page-existingwiki": "ویکی پێشوو",
+ "config-restart": "بەڵێ، دەستی پێ بکەرەوە",
+ "config-env-php": "PHP $1 دابەزێندرا.",
+ "config-env-php-toolow": "PHP $1 دابەزێندرا.\nھەرچۆنێک بێت میدیاویکی پێویستی بە PHP $2 یان بەرزتر ھەیە.",
+ "config-db-name": "ناوی بنکەدراوە:",
+ "config-db-username": "ناوی بەکارھێنەری بنکەدراوە:",
+ "config-db-password": "تێپەڕوشەی بنکەدراوە",
+ "config-site-name": "ناوی ویکی:",
+ "config-ns-generic": "پرۆژە",
+ "config-admin-password": "تێپەڕوشە:",
+ "config-admin-email": "ناونیشانی ئیمەیل:",
+ "config-install-step-done": "کرا",
+ "config-help": "یارمەتی",
+ "mainpagetext": "'''میدیاویکی بە سەرکەوتوویی دامەزرا.'''",
+ "mainpagedocfooter": "لە [//meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی کەڵک وەربگرە.\n\n== دەستپێکردن ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings پێرستی ڕێکخستنەکانی شێوەپێدان]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسیارە دووپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce پێرستی ئیمەیلی وەشانەکانی میدیاویکی]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources خۆماڵیکردنی ویکیمیدیا بۆ زمانەکەت]"
+}
diff --git a/includes/installer/i18n/cps.json b/includes/installer/i18n/cps.json
new file mode 100644
index 00000000..38dbab19
--- /dev/null
+++ b/includes/installer/i18n/cps.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Oxyzen",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''Madalag-on nga na-install ang MediaWiki.'''",
+ "mainpagedocfooter": "Kunsultahon ang [//meta.wikimedia.org/wiki/Help:Contents sa Manug-usar] para sa impormasyon sa paggamit sang wiki nga \"software\".\n\n==Pag-umpisa==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista sang mga setting sang konpigurayon]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Mga perme napangkot sa MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat sang MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/crh-cyrl.json b/includes/installer/i18n/crh-cyrl.json
new file mode 100644
index 00000000..75bc85b9
--- /dev/null
+++ b/includes/installer/i18n/crh-cyrl.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki мувафакъиетнен къурулды.'''",
+ "mainpagedocfooter": "Бу викининъ ёл-ёругъыны [//meta.wikimedia.org/wiki/Help:Contents User's Guide къулланыджы къылавузындан] огренип оласынъыз.\n\n== Базы файдалы сайтлар ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Олуджы сазламалар джедвели];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki боюнджа сыкъ берильген суаллернен джеваплар];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-нинъ янъы версияларынынъ чыкъувындан хабер йиберюв]."
+}
diff --git a/includes/installer/i18n/crh-latn.json b/includes/installer/i18n/crh-latn.json
new file mode 100644
index 00000000..91421686
--- /dev/null
+++ b/includes/installer/i18n/crh-latn.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki muvafaqiyetnen quruldı.'''",
+ "mainpagedocfooter": "Bu vikiniñ yol-yoruğını [//meta.wikimedia.org/wiki/Help:Contents User's Guide qullanıcı qılavuzından] ögrenip olasıñız.\n\n== Bazı faydalı saytlar ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Olucı sazlamalar cedveli];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki boyunca sıq berilgen suallernen cevaplar];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-niñ yañı versiyalarınıñ çıquvından haber yiberüv]."
+}
diff --git a/includes/installer/i18n/cs.json b/includes/installer/i18n/cs.json
new file mode 100644
index 00000000..a783cfe2
--- /dev/null
+++ b/includes/installer/i18n/cs.json
@@ -0,0 +1,334 @@
+{
+ "@metadata": {
+ "authors": [
+ "Danny B.",
+ "Jezevec",
+ "Mormegil",
+ "아라",
+ "Matěj Grabovský",
+ "Paxt",
+ "Matěj Suchánek"
+ ]
+ },
+ "config-desc": "Instalační program pro MediaWiki",
+ "config-title": "Instalace MediaWiki $1",
+ "config-information": "Informace",
+ "config-localsettings-upgrade": "Byl nalezen soubor <code>LocalSettings.php</code>.\nPokud chcete stávající instalaci aktualizovat, zadejte hodnotu <code>$wgUpgradeKey</code>, kterou naleznete v souboru <code>LocalSettings.php</code>, do následujícího rámečku.",
+ "config-localsettings-cli-upgrade": "Byl detekován soubor <code><code>LocalSettings.php</code></code>\nPro aktualizaci spusťte místo instalace skript <code>update.php</code>.",
+ "config-localsettings-key": "Klíč pro aktualizaci:",
+ "config-localsettings-badkey": "Zadaný klíč je nesprávný.",
+ "config-upgrade-key-missing": "Byla detekována existující instalace MediaWiki.\nPokud ji chcete aktualizovat, přidejte následující řádku na konec souboru <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Existující soubor <code>LocalSettings.php</code> vypadá neúplný.\nNení nastavena proměnná $1.\nUpravte soubor <code>LocalSettings.php</code> tak, aby tuto proměnnou obsahoval, a klikněte na „{{int:Config-continue}}“.",
+ "config-localsettings-connection-error": "Při připojování k databázi s využitím nastavení uvedených v <code>LocalSettings.php</code> došlo k chybě. Opravte tato nastavení a zkuste to znovu.\n\n$1",
+ "config-session-error": "Nepodařilo se inicializovat relaci: $1",
+ "config-session-expired": "Platnost dat vašeho sezení patrně vypršela.\nSezení má nastavenu životnost $1.\nProdloužit ji můžete nastavením <code>session.gc_maxlifetime</code> v php.ini.\nSpusťte instalační proces od začátku.",
+ "config-no-session": "Data vašeho sezení se ztratila!\nZkontrolujte svůj soubor php.ini a ujistěte se, že <code>session.save_path</code> je nastaveno na odpovídající adresář.",
+ "config-your-language": "Váš jazyk:",
+ "config-your-language-help": "Zvolte jazyk, který se má použít v průběhu instalace.",
+ "config-wiki-language": "Jazyk wiki:",
+ "config-wiki-language-help": "Zvolte jazyk, ve kterém bude většina obsahu wiki.",
+ "config-back": "← Zpět",
+ "config-continue": "Pokračovat →",
+ "config-page-language": "Jazyk",
+ "config-page-welcome": "Vítejte v MediaWiki!",
+ "config-page-dbconnect": "Připojení k databázi",
+ "config-page-upgrade": "Aktualizace existující instalace",
+ "config-page-dbsettings": "Nastavení databáze",
+ "config-page-name": "Název",
+ "config-page-options": "Nastavení",
+ "config-page-install": "Instalovat",
+ "config-page-complete": "Hotovo!",
+ "config-page-restart": "Restartovat instalaci",
+ "config-page-readme": "Soubor Čti mě",
+ "config-page-releasenotes": "Poznámky k vydání",
+ "config-page-copying": "Licence",
+ "config-page-upgradedoc": "Upgrade",
+ "config-page-existingwiki": "Existující wiki",
+ "config-help-restart": "Chcete smazat všechny údaje, které jste zadali, a spustit proces instalace znovu od začátku?",
+ "config-restart": "Ano, restartovat",
+ "config-welcome": "=== Kontrola prostředí ===\nNyní se provedou základní kontroly, aby se zjistilo, zda je toto prostředí použitelné k instalaci MediaWiki.\nPokud budete potřebovat k dokončení instalace pomoc, nezapomeňte sdělit výsledky těchto testů.",
+ "config-copyright": "=== Licence a podmínky ===\n$1\n\nTento program je svobodný software; můžete jej šířit nebo modifikovat podle podmínek GNU General Public License, vydávané Free Software Foundation; buď verze 2 této licence anebo (podle vašeho uvážení) kterékoli pozdější verze.\n\nTento program je distribuován v naději, že bude užitečný, avšak '''bez jakékoli záruky'''; neposkytují se ani odvozené záruky '''prodejnosti''' anebo '''vhodnosti pro určitý účel'''.\nPodrobnosti se dočtete v textu GNU General Public License.\n\n<doclink href=Copying>Kopii GNU General Public License</doclink> jste měli obdržet spolu s tímto programem; pokud ne, napište na Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA nebo [http://www.gnu.org/copyleft/gpl.html si ji přečtěte online].",
+ "config-sidebar": "* [//www.mediawiki.org Oficiální web MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Uživatelská příručka]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administrátorská příručka]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Čti mě</doclink>\n* <doclink href=ReleaseNotes>Poznámky k vydání</doclink>\n* <doclink href=Copying>Licence</doclink>\n* <doclink href=UpgradeDoc>Upgrade</doclink>",
+ "config-env-good": "Prostředí bylo zkontrolováno.\nMůžete nainstalovat MediaWiki.",
+ "config-env-bad": "Prostředí bylo zkontrolováno.\nMediaWiki nelze nainstalovat.",
+ "config-env-php": "Je nainstalováno PHP $1.",
+ "config-env-hhvm": "Je nainstalováno HHVM $1.",
+ "config-unicode-using-utf8": "Pro normalizaci Unicode se používá utf8_normalize.so Briona Vibbera.",
+ "config-unicode-using-intl": "Pro normalizaci Unicode se používá [http://pecl.php.net/intl PECL rozšíření intl].",
+ "config-unicode-pure-php-warning": "'''Upozornění''': Není dostupné [http://pecl.php.net/intl PECL rozšíření intl] pro normalizaci Unicode, bude se využívat pomalá implementace v čistém PHP.\nPokud provozujete wiki s velkou návštěvností, měli byste si přečíst něco o [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizaci Unicode].",
+ "config-unicode-update-warning": "'''Upozornění''': Nainstalovaná verze vrstvy pro normalizaci Unicode používá starší verzi knihovny [http://site.icu-project.org/ projektu ICU].\nPokud vám aspoň trochu záleží na používání Unicode, měli byste [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations ji aktualizovat].",
+ "config-no-db": "Nepodařilo se nalézt vhodný databázový ovladač! Musíte do PHP nainstalovat databázový ovladač.\nJsou podporovány následující typy databází: $1.\n\nPokud jste si PHP přeložili sami, překonfigurujte ho se zapnutým databázovým klientem, například pomocí <code>./configure --with-mysql</code>.\nPokud jste PHP nainstalovali z balíčku Debian či Ubuntu, potřebujete nainstalovat také modul php5-mysql.",
+ "config-outdated-sqlite": "'''Upozornění''': Máte SQLite $1, které je starší než minimálně vyžadovaná verze $2. SQLite nebude dostupné.",
+ "config-no-fts3": "'''Upozornění''': SQLite bylo přeloženo bez [//sqlite.org/fts3.html modulu FTS3], funkce pro vyhledávání zde nebudou dostupné.",
+ "config-register-globals-error": "<strong>Chyba: PHP nastavení <code>[http://php.net/register_globals register_globals]</code> je zapnuto. Pro pokračování v instalaci musí být vypnuto.</strong>\nRady, jak toho dosáhnout, najdete na [https://www.mediawiki.org/wiki/Register_globals https://www.mediawiki.org/wiki/register_globals].",
+ "config-magic-quotes-gpc": "<strong>Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc]!</strong>\nToto nastavení nepředvídatelně poškozuje vstupní data.\nMediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
+ "config-magic-quotes-runtime": "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''\nToto nastavení nepředvídatelně poškozuje vstupní data.\nMediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
+ "config-magic-quotes-sybase": "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''\nToto nastavení nepředvídatelně poškozuje vstupní data.\nMediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
+ "config-mbstring": "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''\nToto nastavení způsobuje chyby a může nepředvídatelně poškozovat vstupní data.\nMediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
+ "config-safe-mode": "'''Upozornění:''' Je aktivní [http://www.php.net/features.safe-mode bezpečný režim] PHP.\nMůže způsobovat potíže, zejména při použití načítání souborů a podpory <code>math</code>.",
+ "config-xml-bad": "Chybí XML modul pro PHP.\nMediaWiki potřebuje funkce v tomto modulu a v této konfiguraci nebude fungovat.\nPokud běžíte na Mandrake, nainstalujte balíček php-xml.",
+ "config-pcre-old": "'''Kritická chyba:''' Je vyžadováno PCRE verze $1 nebo novější.\nVaše binárka PHP obsahuje PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Více informací.]",
+ "config-pcre-no-utf8": "'''Kritická chyba''': 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": "'''Upozornění:''' <code>memory_limit</code> je v PHP nastaven na $1.\nTo je pravděpodobně příliš málo.\nInstalace může selhat!",
+ "config-ctype": "'''Kritická chyba''': PHP musí být přeloženo s podporou pro [http://www.php.net/manual/en/ctype.installation.php rozšíření Ctype].",
+ "config-iconv": "'''Kritická chyba''': PHP musí být přeloženo s podporou pro [http://www.php.net/manual/en/iconv.installation.php rozšíření iconv].",
+ "config-json": "'''Kritická chyba:''' PHP bylo přeloženo bez podpory JSON.\nPřed instalací MediaWiki musíte buď nainstalovat rozšíření PHP JSON nebo rozšíření [http://pecl.php.net/package/jsonc PECL jsonc].\n* Rozšíření PHP je součástí Red Hat Enterprise Linux (CentOS) 5 a 6, avšak musí se povolit v <code>/etc/php.ini</code> nebo <code>/etc/php.d/json.ini</code>.\n* V některých linuxových distribucích vydaných po květnu 2013 může toto rozšíření PHP chybět a místo toho mohou používat rozšíření PECL jako <code>php5-json</code> nebo <code>php-pecl-jsonc</code>.",
+ "config-xcache": "Je nainstalována [http://xcache.lighttpd.net/ XCache]",
+ "config-apc": "Je nainstalováno [http://www.php.net/apc APC]",
+ "config-wincache": "Je nainstalována [http://www.iis.net/download/WinCacheForPhp WinCache]",
+ "config-no-cache": "'''Upozornění:''' Nebylo nalezeno [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], ani [http://www.iis.net/download/WinCacheForPhp WinCache].\nKešování objektů bude vypnuto.",
+ "config-mod-security": "'''Upozornění''': váš webový server má zapnuto [http://modsecurity.org/ mod_security]. Při chybné konfiguraci může způsobovat potíže MediaWiki či dalším programům, které umožňují ukládat libovolný obsah.\nPokud narazíte na náhodné chyby, podívejte se do [http://modsecurity.org/documentation/ dokumentace mod_security] nebo kontaktujte technickou podporu vašeho poskytovatele.",
+ "config-diff3-bad": "Nebyl nalezen GNU diff3.",
+ "config-git": "Nalezen software pro správu verzí Git: <code>$1</code>.",
+ "config-git-bad": "Software pro správu verzí Git nebyl nalezen.",
+ "config-imagemagick": "Nalezen ImageMagick: <code>$1</code>.\nPokud povolíte načítání souborů, bude zapnuto vytváření náhledů.",
+ "config-gd": "Nalezena vestavěná grafická knihovna GD.\nPokud povolíte načítání souborů, bude zapnuto vytváření náhledů.",
+ "config-no-scaling": "Nebyla nalezena knihovna GD ani ImageMagick.\nVytváření náhledů bude vypnuto.",
+ "config-no-uri": "'''Chyba:''' Nepodařilo se určit aktuální URI.\nInstalace přerušena.",
+ "config-no-cli-uri": "<strong>Upozornění</strong>: Nebylo uvedeno <code>--scriptpath</code>, používá se implicitní hodnota: <code>$1</code>.",
+ "config-using-server": "Použito jméno serveru „<nowiki>$1</nowiki>“.",
+ "config-using-uri": "Použito URL serveru „<nowiki>$1$2</nowiki>“.",
+ "config-uploads-not-safe": "'''Upozornění:''' Váš výchozí adresář pro načítání souborů <code>$1</code> umožňuje spouštění libovolných skriptů.\nPřestože MediaWiki všechny načítané soubory kontroluje proti bezpečnostním hrozbám, je důrazně doporučeno [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security tuto bezpečnostní díru zacelit] před povolením načítání souborů.",
+ "config-no-cli-uploads-check": "'''Upozornění:''' Váš výchozí adresář pro načítané soubory (<code>$1</code>) se při instalaci z příkazového řádku nekontroluje na bezpečnostní hrozbu provádění libovolných skriptů.",
+ "config-brokenlibxml": "Váš systém obsahuje kombinaci verzí PHP a libxml2, která je chybná a může v MediaWiki a dalších webových aplikacích způsobovat skryté poškozování dat.\nAktualizujte na libxml2 2.7.3 nebo novější ([https://bugs.php.net/bug.php?id=45996 chyba evidovaná u PHP]).\nInstalace přerušena.",
+ "config-suhosin-max-value-length": "Je nainstalován Suhosin, který omezuje délku parametrů GET na $1 bajtů.\nKomponenta ResourceLoader z MediaWiki dokáže s tímto omezením pracovat, ale sníží to výkon.\nPokud to je alespoň trochu možné, měli byste v <code>php.ini</code> nastavit <code>suhosin.get.max_value_length</code> na 1024 nebo vyšší a na stejnou hodnotu nastavit v <code>LocalSettings.php</code> proměnnou <code>$wgResourceLoaderMaxQueryLength</code>.",
+ "config-db-type": "Typ databáze:",
+ "config-db-host": "Databázový server:",
+ "config-db-host-help": "Pokud je váš databázový server na jiném počítači, zadejte zde jméno stroje nebo IP adresu.\n\nPokud používáte sdílený webový hosting, váš poskytovatel by vám měl v dokumentaci sdělit správné jméno stroje.\n\nPokud instalujete na server běžící na Windows a používáte MySQL, jméno „localhost“ nemusí fungovat. V takovém případě zkuste jako místní IP adresu zadat „127.0.0.1“.\n\nPokud používáte PostgreSQL, můžete se připojit Unixovými sockety tak, že toto pole necháte prázdné.",
+ "config-db-host-oracle": "Databázové TNS:",
+ "config-db-host-oracle-help": "Zadejte platné [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; tato instalace musí vidět soubor tnsnames.ora.<br />Pokud používáte klientské knihovny verze 10g nebo novější, můžete také používat názvy [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identifikace této wiki",
+ "config-db-name": "Jméno databáze:",
+ "config-db-name-help": "Zvolte jméno, které označuje vaši wiki.\nNemělo by obsahovat mezery.\n\nPokud používáte sdílený webový hosting, váš poskytovatel vám buď sdělí konkrétní jméno databáze, nebo vás nechá vytvářet databáze pomocí nějakého ovládacího panelu.",
+ "config-db-name-oracle": "Databázové schéma:",
+ "config-db-account-oracle-warn": "Existují tři podporované možnosti pro instalaci s použitím databáze Oracle.\n\nPokud chcete v rámci instalace založit databázový účet, zadejte jako databázový účet pro instalaci účet s rolí SYSDBA a uveďte požadované údaje pro účet pro webový přístup, jinak můžete vytvořit účet pro webový přístup ručně a zadat pouze tento účet (pokud má dostatečná oprávnění k zakládání objektů schématu) nebo poskytnout dva různé účty, jeden s oprávněními k zakládání, druhý omezený pro webový přístup.\n\nSkript pro založení účtu s potřebnými privilegii můžete v této instalaci nalézt v adresáři „maintenance/oracle/“. Nezapomeňte, že použití omezeného účtu znepřístupní veškeré možnosti údržby přes implicitní účet.",
+ "config-db-install-account": "Uživatelský účet pro instalaci",
+ "config-db-username": "Databázové uživatelské jméno:",
+ "config-db-password": "Databázové heslo:",
+ "config-db-password-empty": "Zadejte heslo pro nového databázového uživatele: $1.\nPřestože může jít zakládat nové uživatele i bez hesel, není to bezpečné.",
+ "config-db-username-empty": "Musíte zadat hodnotu pro „{{int:config-db-username}}“.",
+ "config-db-install-username": "Zadejte uživatelské jméno, které se použije pro připojení k databázi v průběhu instalace.\nToto není jméno uživatelského účtu MediaWiki; toto je uživatelské jméno k vaší databázi.",
+ "config-db-install-password": "Zadejte heslo, které se použije pro připojení k databázi v průběhu instalace.\nToto není heslo uživatelského účtu MediaWiki; toto je heslo k vaší databázi.",
+ "config-db-install-help": "Zadejte uživatelské jméno a heslo, které se použijí pro připojení k databázi v průběhu instalace.",
+ "config-db-account-lock": "Použít stejné uživatelské jméno a heslo pro běžnou činnost",
+ "config-db-wiki-account": "Uživatelský účet pro běžnou činnost",
+ "config-db-wiki-help": "Zadejte uživatelské jméno a heslo, které se bude používat pro připojení k databázi za běžného provozu wiki.\nPokud účet neexistuje a instalační účet má dostatečná oprávnění, bude tento uživatelský účet založen s minimálními oprávněními potřebnými k provozu wiki.",
+ "config-db-prefix": "Prefix databázových tabulek:",
+ "config-db-prefix-help": "Pokud potřebujete sdílet jednu databázi mezi vícero wiki, případně mezi MediaWiki a další webovou aplikací, můžete přidat k názvu každé tabulky prefix, abyste se vyhnuli konfliktům.\nNepoužívejte mezery.\n\nToto pole se zpravidla ponechává prázdné.",
+ "config-db-charset": "Znaková sada databáze",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binární",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 zpětně kompatibilní UTF-8",
+ "config-charset-help": "'''Upozornění:''' Pokud použijete '''zpětně kompatibilní UTF-8''' na MySQL 4.1+ a následně zazálohujete databázi pomocí <code>mysqldump</code>, může to zničit všechny ne-ASCII znaky, což nevratně poškodí vaše zálohy!\n\nV '''binárním režimu''' ukládá MediaWiki text v UTF-8 do databáze v binárních sloupcích.\nTo je výkonnější než UTF-8 režim MySQL a umožňuje využít plný rozsah znaků Unicode.\nV '''režimu UTF-8''' bude MySQL znát znakovou sadu vašich dat a může je příslušně zobrazovat a převádět,\nale neumožní vám uložit znaky mimo [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mysql-old": "Je vyžadováno MySQL $1 nebo novější, vy máte $2.",
+ "config-db-port": "Databázový port:",
+ "config-db-schema": "Schéma pro MediaWiki:",
+ "config-db-schema-help": "Toto schéma zpravidla stačí.\nMěňte ho, jen pokud víte, že je to potřeba.",
+ "config-pg-test-error": "Nelze se připojit k databázi '''$1''': $2",
+ "config-sqlite-dir": "Adresář pro data SQLite:",
+ "config-sqlite-dir-help": "SQLite ukládá veškerá data v jediném souboru.\n\nZadaný adresář musí být v průběhu instalace být přístupný pro zápis.\n\n'''Neměl by''' být dostupný z webu, proto ho nedáváme tam, kde jsou vaše PHP soubory.\n\nInstalátor do adresáře přidá soubor <code>.htaccess</code>, ale pokud to selže, mohl by někdo získat přístup k vaší holé databázi.\nTo zahrnuje syrová uživatelská data (e-mailové adresy, hašovaná hesla), jako i smazané revize a další data s omezeným přístupem z vaší wiki.\n\nZvažte umístění databáze někam zcela jinam, například do <code>/var/lib/mediawiki/mojewiki</code>.",
+ "config-oracle-def-ts": "Implicitní tabulkový prostor:",
+ "config-oracle-temp-ts": "Dočasný tabulkový prostor:",
+ "config-type-mysql": "MySQL (nebo kompatibilní)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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-mysql-url}} MySQL] je pro MediaWiki hlavní platformou a je podporováno nejlépe. MediaWiki pracuje také s [{{int:version-db-mariadb-url}} MariaDB] a [{{int:version-db-percona-url}} Percona Server], které jsou s MySQL kompatibilní. ([http://www.php.net/manual/en/mysql.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. Mohou se vyskytnout ještě nějaké menší chyby, použití ve výrobním prostředí se nedoporučuje. ([http://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. ([http://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. ([http://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. ([http://www.php.net/manual/en/sqlsrv.installation.php Jak přeložit PHP s podporou SQLSRV])",
+ "config-header-mysql": "Nastavení MySQL",
+ "config-header-postgres": "Nastavení PostgreSQL",
+ "config-header-sqlite": "Nastavení SQLite",
+ "config-header-oracle": "Nastavení Oracle",
+ "config-header-mssql": "Nastavení Microsoft SQL Serveru",
+ "config-invalid-db-type": "Chybný typ databáze",
+ "config-missing-db-name": "Musíte zadat hodnotu pro „{{int:config-db-name}}“.",
+ "config-missing-db-host": "Musíte zadat hodnotu pro „{{int:config-db-host}}“.",
+ "config-missing-db-server-oracle": "Musíte zadat hodnotu pro „{{int:config-db-host-oracle}}“.",
+ "config-invalid-db-server-oracle": "Chybné databázové TNS „$1“.\nPoužívejte buď „TNS Name“ nebo „Easy Connect“ (vizte [http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]).",
+ "config-invalid-db-name": "Chybné jméno databáze „$1“.\nPoužívejte pouze ASCII písmena (a-z, A-Z), čísla (0-9), podtržítko (_) a spojovník (-).",
+ "config-invalid-db-prefix": "Chybný databázový prefix „$1“.\nPoužívejte pouze ASCII písmena (a-z, A-Z), čísla (0-9), podtržítko (_) a spojovník (-).",
+ "config-connection-error": "$1.\n\nZkontrolujte server, uživatelské jméno a heslo a zkuste to znovu.",
+ "config-invalid-schema": "Neplatné schéma pro MediaWiki „$1“.\nPoužívejte pouze ASCII písmena (a-z, A-Z), čísla (0-9) a podtržítko (_).",
+ "config-db-sys-create-oracle": "Instalátor podporuje zakládání nového účtu pouze prostřednictvím účtu SYSDBA.",
+ "config-db-sys-user-exists-oracle": "Uživatelský účet „$1“ již existuje. SYSDBA lze použít pouze pro založení nového účtu!",
+ "config-postgres-old": "Je vyžadován PostgreSQL $1 nebo novější, vy máte $2.",
+ "config-mssql-old": "Je vyžadován Microsoft SQL Server $1 nebo novější. Vy máte $2.",
+ "config-sqlite-name-help": "Zvolte jméno, které označuje vaši wiki.\nNepoužívejte mezery a spojovníky.\nPoužije se jako název souboru s daty SQLite.",
+ "config-sqlite-parent-unwritable-group": "Nelze vytvořit datový adresář <code><nowiki>$1</nowiki></code>, protože do nadřazeného adresáře <code><nowiki>$2</nowiki></code> nemá webový server právo zapisovat.\n\nInstalátor zjistil uživatele, pod kterým váš webový server běží.\nAbyste mohli pokračovat, umožněte mu zapisovat do adresáře <code><nowiki>$3</nowiki></code>.\nNa systémech Unix/Linux proveďte:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Nelze vytvořit datový adresář <code><nowiki>$1</nowiki></code>, protože do nadřazeného adresáře <code><nowiki>$2</nowiki></code> nemá webový server právo zapisovat.\n\nInstalátoru se nepodařilo zjistit uživatele, pod kterým váš webový server běží.\nAbyste mohli pokračovat, umožněte zápis do <code><nowiki>$3</nowiki></code> všem uživatelům.\nNa systémech Unix/Linux proveďte:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Chyba při vytváření datového adresáře „$1“.\nZkontrolujte umístění a zkuste to znovu.",
+ "config-sqlite-dir-unwritable": "Nelze zapisovat do adresáře „$1“.\nZměňte na něm oprávnění, aby do něj mohl webový server zapisovat, a zkuste to znovu.",
+ "config-sqlite-connection-error": "$1.\n\nZkontrolujte datový adresář a jméno databáze níže a zkuste to znovu.",
+ "config-sqlite-readonly": "Do souboru <code>$1</code> nelze zapisovat.",
+ "config-sqlite-cant-create-db": "Nepodařilo se vytvořit databázový soubor <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP neobsahuje podporu FTS3, downgradují se tabulky",
+ "config-can-upgrade": "V této databázi jsou tabulky MediaWiki.\nPokud je chcete aktualizovat na MediaWiki $1, klikněte na '''Pokračovat'''.",
+ "config-upgrade-done": "Aktualizace byla dokončena.\n\nSvou wiki teď můžete [$1 začít používat].\n\nPokud chcete přegenerovat soubor <code>LocalSettings.php</code>, klikněte na tlačítko níže.\nTo se ale '''nedoporučuje''', pokud s wiki nemáte problémy.",
+ "config-upgrade-done-no-regenerate": "Aktualizace byla dokončena.\n\nSvou wiki teď můžete [$1 začít používat].",
+ "config-regenerate": "Přegenerovat LocalSettings.php →",
+ "config-show-table-status": "Dotaz <code>SHOW TABLE STATUS</code> se nezdařil!",
+ "config-unknown-collation": "'''Upozornění:''' Databáze používá nerozpoznané řazení.",
+ "config-db-web-account": "Databázový účet pro webový přístup",
+ "config-db-web-help": "Zvolte uživatelské jméno a heslo, které bude webový server používat pro připojení k databázovému serveru při běžném provozu wiki.",
+ "config-db-web-account-same": "Použít stejný účet jako pro instalaci",
+ "config-db-web-create": "Založit účet, pokud zatím neexistuje",
+ "config-db-web-no-create-privs": "Účet uvedený pro instalaci nemá oprávnění dostatečná pro založení nového účtu.\nÚčet, který zde uvedete, již musí existovat.",
+ "config-mysql-engine": "Typ úložiště:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Upozornění''': Jako typ úložiště pro MySQL jste zvolili MyISAM, které není pro použití v MediaWiki doporučeno, neboť:\n* stěží podporuje současný přístup kvůli zamykání tabulek,\n* je náchylnější na poškození dat než jiná úložiště,\n* kód MediaWiki nepodporuje MyISAM vždy tak dobře, jak by měl.\n\nPokud vaše instalace MySQL podporuje InnoDB, důrazně doporučujeme použít spíše to.\nPokud vaše instalace MySQL InnoDB nepodporuje, možná je čas na aktualizaci.",
+ "config-mysql-only-myisam-dep": "'''Upozornění:''' Jediným dostupným úložištěm dat pro MySQL je MyISAM, který se k užití s MediaWiki nedoporučuje, neboť:\n* téměř nepodporuje paralelní přístup kvůli zamykání tabulek,\n* oproti jiným formátům je náchylnější k poškození,\n* MediaWiki nepodporuje MyISAM tak dobře, jak by bylo třeba.\n\nVaše instalace MySQL nepodporuje InnoDB, možná je na čase upgradovat.",
+ "config-mysql-engine-help": "'''InnoDB''' je téměř vždy nejlepší volba, neboť má dobrou podporu současného přístupu.\n\n'''MyISAM''' může být rychlejší u instalací pro jednoho uživatele nebo jen pro čtení.\nDatabáze MyISAM bývají poškozeny častěji než databáze InnoDB.",
+ "config-mysql-charset": "Znaková sada databáze:",
+ "config-mysql-binary": "Binární",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "V '''binárním režimu''' ukládá MediaWiki text v UTF-8 do databáze v binárních sloupcích.\nTo je výkonnější než UTF-8 režim MySQL a umožňuje využít plný rozsah znaků Unicode.\n\nV '''režimu UTF-8''' bude MySQL znát znakovou sadu vašich dat a může je příslušně zobrazovat a převádět, ale neumožní vám uložit znaky mimo [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mssql-auth": "Typ autentizace:",
+ "config-mssql-install-auth": "Zvolte si typ autentizace, který se bude používat pro připojení k databázi v průběhu instalace.\nPokud zvolíte možnost „{{int:config-mssql-windowsauth}}“, použijí se přihlašovací údaje uživatele, pod kterým běží webový server.",
+ "config-mssql-web-auth": "Zvolte si typ autentizace, který se bude používat pro připojení k databázi za běžného provozu wiki.\nPokud zvolíte možnost „{{int:config-mssql-windowsauth}}“, použijí se přihlašovací údaje uživatele, pod kterým běží webový server.",
+ "config-mssql-sqlauth": "Autentizace SQL serveru",
+ "config-mssql-windowsauth": "Windows autentizace",
+ "config-site-name": "Název wiki:",
+ "config-site-name-help": "Bude se zobrazovat v titulku prohlížeče a na dalších místech.",
+ "config-site-name-blank": "Zadejte název serveru.",
+ "config-project-namespace": "Jmenný prostor projektu:",
+ "config-ns-generic": "Projekt",
+ "config-ns-site-name": "Stejný jako název wiki: $1",
+ "config-ns-other": "Jiný (uveďte)",
+ "config-ns-other-default": "MojeWiki",
+ "config-project-namespace-help": "Po vzoru Wikipedie udržuje mnoho wiki stránky se svými pravidly odděleně od stránek s vlastním obsahem, v „'''jmenném prostoru projektu'''“.\nNázvy všech stránek v tomto jmenném prostoru začínají jistým prefixem, který zde můžete nastavit.\nZvykem je odvozovat tento prefix z názvu wiki, ale nesmí obsahovat jisté interpunkční znaky jako „#“ nebo „:“.",
+ "config-ns-invalid": "Uvedený jmenný prostor „<nowiki>$1</nowiki>“ je neplatný.\nZadejte jiný jmenný prostor projektu.",
+ "config-ns-conflict": "Uvedený jmenný prostor „<nowiki>$1</nowiki>“ koliduje se standardním jmenným prostorem MediaWiki.\nZadejte jiný jmenný prostor projektu.",
+ "config-admin-box": "Správcovský účet",
+ "config-admin-name": "Vaše uživatelské jméno:",
+ "config-admin-password": "Heslo:",
+ "config-admin-password-confirm": "Heslo ještě jednou:",
+ "config-admin-help": "Zde zadejte své požadované uživatelské jméno, například „Pepa Novák“.\nTímto jménem se budete do wiki hlásit.",
+ "config-admin-name-blank": "Zadejte uživatelské jméno správce.",
+ "config-admin-name-invalid": "Uvedené uživatelské jméno „<nowiki>$1</nowiki>“ není platné.\nZadejte jiné uživatelské jméno.",
+ "config-admin-password-blank": "Zadejte heslo ke správcovskému účtu.",
+ "config-admin-password-mismatch": "Uvedená hesla se neshodují.",
+ "config-admin-email": "E-mailová adresa:",
+ "config-admin-email-help": "Zde zadejte e-mailovou adresu, která vám umožní přijímat e-maily od ostatních uživatelů wiki, získat nové heslo a přijímat notifikace o změnách sledovaných stránek. Tohle pole můžete nechat prázdné.",
+ "config-admin-error-user": "Vnitřní chyba při vytváření správce se jménem „<nowiki>$1</nowiki>“.",
+ "config-admin-error-password": "Vnitřní chyba při nastavování hesla správci se jménem „<nowiki>$1</nowiki>“: <pre>$2</pre>",
+ "config-admin-error-bademail": "Zadali jste neplatnou e-mailovou adresu.",
+ "config-subscribe": "Přihlásit se k odběru [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-mailové konference pro oznamování nových verzí].",
+ "config-subscribe-help": "Tohle je e-mailová konference s nízkým provozem, na které se oznamují nové verze, včetně důležitých bezpečnostních oznámení.\nMěli byste se do ní přihlásit a při vydání nových verzí aktualizovat svou instalaci MediaWiki.",
+ "config-subscribe-noemail": "Pokusili jste se přihlásit k odběru e-mailové konference pro oznamování nových verzí, aniž byste poskytli e-mailovou adresu.\nPokud se chcete přihlásit k odběru, zadejte e-mailovou adresu.",
+ "config-almost-done": "Už jsme skoro hotovi!\nZbývající konfiguraci už můžete přeskočit a nainstalovat wiki hned teď.",
+ "config-optional-continue": "Ptejte se mě dál.",
+ "config-optional-skip": "Už mě to nudí, prostě nainstalujte wiki.",
+ "config-profile": "Profil uživatelských práv:",
+ "config-profile-wiki": "Otevřená wiki",
+ "config-profile-no-anon": "Vyžadována registrace uživatelů",
+ "config-profile-fishbowl": "Editace jen pro vybrané",
+ "config-profile-private": "Soukromá wiki",
+ "config-profile-help": "Wiki fungují nejlépe, když je necháte editovat co největším možným počtem lidí.\nV MediaWiki můžete snadno kontrolovat poslední změny a vracet zpět libovolnou škodu způsobenou hloupými nebo zlými uživateli.\n\nMnoho lidí však zjistilo, že je MediaWiki užitečné v širokém spektru rolí a někdy není snadné všechny přesvědčit o výhodách wikizvyklostí.\nTakže si můžete vybrat.\n\nModel '''{{int:config-profile-wiki}}''' dovoluje editovat všem, aniž by se museli přihlašovat.\nNa wiki, kde je '''{{int:config-profile-no-anon}}''', se lépe řídí zodpovědnost, ale může to odradit náhodné přispěvatele.\n\nProfil '''{{int:config-profile-fishbowl}}''' umožňuje schváleným uživatelům editovat, ale veřejnost si může stránky prohlížet včetně jejich historie.\n'''{{int:config-profile-private}}''' dovoluje stránky prohlížet jen schváleným uživatelům, kteří je i mohou editovat.\n\nPo instalaci je možná komplexní konfigurace uživatelských práv; vizte [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights odpovídající stránku příručky].",
+ "config-license": "Autorská práva a licence:",
+ "config-license-none": "Bez patičky s licencí",
+ "config-license-cc-by-sa": "Creative Commons Uveďte autora-Zachovejte licenci",
+ "config-license-cc-by": "Creative Commons Uveďte autora",
+ "config-license-cc-by-nc-sa": "Creative Commons Uveďte autora-Nevyužívejte dílo komerčně-Zachovejte licenci",
+ "config-license-cc-0": "Creative Commons Zero (volné dílo)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 nebo novější",
+ "config-license-pd": "Volné dílo",
+ "config-license-cc-choose": "Zvolit vlastní licenci Creative Commons",
+ "config-license-help": "Mnoho veřejných wiki všechny příspěvky zveřejňuje pod některou [http://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 [http://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",
+ "config-email-usertalk-help": "Umožní uživatelům přijímat notifikace o změnách uživatelských diskusí, pokud si to zapnou v nastavení.",
+ "config-email-watchlist": "Umožnit notifikace ke sledovaným stránkám",
+ "config-email-watchlist-help": "Umožní uživatelům přijímat notifikace o změnách sledovaných stránek, pokud si to zapnou v nastavení.",
+ "config-email-auth": "Zapnout ověřování e-mailů",
+ "config-email-auth-help": "Pokud je tato volba vybrána, uživatelé musí potvrdit svou e-mailovou adresu pomocí odkazu, který je jim poslán, kdykoli si ji nastaví nebo změní.\nJen potvrzené e-mailové adresy mohou přijímat e-maily od ostatních uživatelů a e-maily s notifikacemi o změnách.\nNastavení této volby je '''doporučeno''' pro veřejné wiki kvůli možnosti zneužití e-mailových funkcí.",
+ "config-email-sender": "Návratová e-mailová adresa:",
+ "config-email-sender-help": "Zadejte e-mailovou adresu, která se má použít jako návratová na odchozích e-mailech.\nSem budou zasílány nedoručitelné zprávy.\nMnoho mailových serverů vyžaduje, aby byla přinejmenším část s doménovým jménem platná.",
+ "config-upload-settings": "Obrázky a načítání souborů",
+ "config-upload-enable": "Povolit načítání souborů",
+ "config-upload-help": "Načítání souborů potenciálně vystavuje váš server bezpečnostním rizikům.\nVíce informací naleznete v [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security části o bezpečnosti] v příručce.\n\nPro umožnění načítání souborů změňte práva na podadresáři <code>images</code> pod kořenovým adresářem MediaWiki, aby do něj mohl webový server zapisovat.\nPoté zapněte tuto volbu.",
+ "config-upload-deleted": "Adresář pro smazané soubory:",
+ "config-upload-deleted-help": "Zvolte adresář, do kterého se mají archivovat smazané soubory.\nTento adresář by ideálně neměl být dostupný z webu.",
+ "config-logo": "URL loga:",
+ "config-logo-help": "Základní vzhled MediaWiki zahrnuje místo pro logo o velikosti 135×160 pixelů nad bočním menu.\nNačtěte obrázek odpovídající velikosti a zadejte sem jeho URL.\n\nPokud je vaše logo umístěno relativně vůči <code>$wgStylePath</code> nebo <code>$wgScriptPath</code>, můžete zde tyto proměnné použít.\n\nPokud logo nechcete, ponechte toto pole prázdné.",
+ "config-instantcommons": "Zapnout Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] je funkce, která umožňuje wiki používat obrázky, zvuky a další mediální soubory ze serveru [//commons.wikimedia.org/wiki/Hlavn%C3%AD_strana Wikimedia Commons].\nAby to bylo možné, potřebuje mít MediaWiki přístup k internetu.\n\nVíce informací o této funkci, včetně instrukcí, jak ji nastavit pro jiné wiki než Wikimedia Commons, najdete v [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos příručce].",
+ "config-cc-error": "Volič licence Creative Commons nevrátil žádný výsledek.\nZadejte název licence ručně.",
+ "config-cc-again": "Zvolit znovu…",
+ "config-cc-not-chosen": "Zvolte si požadovanou licenci Creative Commons a klikněte na tlačítko.",
+ "config-advanced-settings": "Pokročilá konfigurace",
+ "config-cache-options": "Nastavení cachování objektů:",
+ "config-cache-help": "Cachování objektů se používá pro vylepšení rychlosti MediaWiki tím, že se cachují často používaná data.\nStředním až velkým serverům se jeho zapnutí důrazně doporučuje, i menší servery pocítí jeho výhody.",
+ "config-cache-none": "Bez cachování (o žádnou funkcionalitu nepřijdete, na větších wiki však může dojít ke zhoršení rychlosti)",
+ "config-cache-accel": "Cachování PHP objektů (APC, XCache nebo WinCache)",
+ "config-cache-memcached": "Použít Memcached (vyžaduje další nastavení a konfiguraci)",
+ "config-memcached-servers": "Servery Memcached:",
+ "config-memcached-help": "Seznam IP adres, které se mají používat pro Memcached.\nUveďte jednu na řádek spolu s portem. Například:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Jako typ cache jste zvolili Memcached, ale neuvedli jste žádné servery.",
+ "config-memcache-badip": "Zadali jste neplatnou IP adresu pro Memcached: $1.",
+ "config-memcache-noport": "Nezadali jste port serveru Memcached: $1.\nPokud port neznáte, implicitní je 11211.",
+ "config-memcache-badport": "Čísla portů pro Memcached by měla být mezi $1 a $2.",
+ "config-extensions": "Rozšíření",
+ "config-extensions-help": "Výše uvedená rozšíření byla nalezena ve vašem adresáři <code>./extensions</code>.\n\nMohou vyžadovat dodatečnou konfiguraci, ale teď je můžete povolit.",
+ "config-skins": "Vzhledy",
+ "config-skins-help": "Ve vašem adresáři <code>./skins</code> byly nalezeny výše uvedené vzhledy. Musíte nejméně jeden z nich povolit a některý vybrat jako výchozí.",
+ "config-skins-use-as-default": "Tento vzhled používat jako výchozí",
+ "config-skins-missing": "Nebyly nalezeny žádné vzhledy; MediaWiki bude používat nouzový vzhled, dokud nenainstalujete nějaké plnohodnotné.",
+ "config-skins-must-enable-some": "Musíte povolit alespoň jeden vzhled.",
+ "config-skins-must-enable-default": "Vzhled vybraný jako výchozí musí být povolen.",
+ "config-install-alreadydone": "'''Upozornění:''' Vypadá to, že jste MediaWiki již nainstalovali a teď se o to pokoušíte znovu.\nPokračujte na další stránku.",
+ "config-install-begin": "Stisknutím „{{int:config-continue}}“ spustíte instalaci MediaWiki.\nPokud ještě chcete udělat nějaké změny, stiskněte „{{int:config-back}}“.",
+ "config-install-step-done": "hotovo",
+ "config-install-step-failed": "selhaly",
+ "config-install-extensions": "Vkládají se rozšíření",
+ "config-install-database": "Připravuje se databáze",
+ "config-install-schema": "Vytváří se schéma",
+ "config-install-pg-schema-not-exist": "Schéma PostgreSQL neexistuje.",
+ "config-install-pg-schema-failed": "Založení tabulek se nezdařilo.\nUjistěte se, že uživatel „$1“ může zapisovat do schématu „$2“.",
+ "config-install-pg-commit": "Potvrzují se změny",
+ "config-install-pg-plpgsql": "Kontroluje se jazyk PL/pgSQL",
+ "config-pg-no-plpgsql": "Musíte do databáze $1 nainstalovat jazyk PL/pgSQL",
+ "config-pg-no-create-privs": "Účet zadaný pro instalaci nemá oprávnění k založení uživatelského účtu.",
+ "config-pg-not-in-role": "Účet zadaný pro webového uživatele již existuje\nÚčet zadaný pro instalaci není superuživatelský a není členem role webového uživatele, takže nemůže zakládat objekty vlastněné webovým uživatelem.\n\nMediaWiki v současné době vyžaduje, aby byl vlastníkem tabulek webový uživatel. Uveďte jiný název účtu webového uživatele nebo klikněte na „zpět“ a zadejte instalačního uživatele s odpovídajícími oprávněními.",
+ "config-install-user": "Vytváří se databázový uživatel",
+ "config-install-user-alreadyexists": "Uživatel „$1“ už existuje",
+ "config-install-user-create-failed": "Vytváření uživatele „$1“ selhalo: $2",
+ "config-install-user-grant-failed": "Uživateli „$1“ se nepodařilo přidělit oprávnění: $2",
+ "config-install-user-missing": "Zadaný uživatel „$1“ neexistuje.",
+ "config-install-user-missing-create": "Zadaný uživatel „$1“ neexistuje.\nPokud ho chcete založit, zaškrtněte možnost „založit účet“ níže.",
+ "config-install-tables": "Vytvářejí se tabulky",
+ "config-install-tables-exist": "'''Upozornění''': Vypadá to, že tabulky MediaWiki již existují.\nPřeskakuje se jejich zakládání.",
+ "config-install-tables-failed": "'''Chyba''': Vytvoření tabulek selhalo s následující chybou: $1",
+ "config-install-interwiki": "Tabulka interwiki se plní implicitními položkami",
+ "config-install-interwiki-list": "Nelze přečíst soubor <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Upozornění''': Vypadá to, že tabulka interwiki již obsahuje nějaké záznamy.\nPřeskakuje se implicitní seznam.",
+ "config-install-stats": "Inicializují se statistiky",
+ "config-install-keys": "Vytvářejí se tajné klíče",
+ "config-insecure-keys": "'''Upozornění:''' {{PLURAL:$2|Tajný klíč|Tajné klíče}} ($1) vytvořené v průběhu instalace {{PLURAL:$2|není|nejsou}} zcela {{PLURAL:$2|bezpečný|bezpečné}}. Zvažte {{PLURAL:$2|jeho|jejich}} ruční změnu.",
+ "config-install-updates": "Ruší se spuštění nepotřebných aktualizací",
+ "config-install-updates-failed": "<strong>Chyba:</strong> Vložení aktualizačních klíčů do tabulek selhalo s následující chybou: $1",
+ "config-install-sysop": "Zakládá se uživatelský účet správce",
+ "config-install-subscribe-fail": "Nelze se přihlásit k odběru mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "Není nainstalován cURL a není dostupné <code>allow_url_fopen</code>.",
+ "config-install-mainpage": "Vytváří se počáteční obsah hlavní strany",
+ "config-install-extension-tables": "Vytvářejí se tabulky pro zapnutá rozšíření",
+ "config-install-mainpage-failed": "Nepodařilo se vložit hlavní stranu: $1",
+ "config-install-done": "'''Gratulujeme!'''\nÚspěšně jste nainstalovali MediaWiki.\n\nInstalátor vytvořil soubor <code>LocalSettings.php</code>.\nTen obsahuje veškerou vaši konfiguraci.\n\nBudete si ho muset stáhnout a uložit do základního adresáře vaší instalace wiki (do stejného adresáře jako soubor index.php). Stažení souboru se mělo spustit automaticky.\n\nPokud se vám stažení nenabídlo nebo jste ho zrušili, můžete ho spustit znovu kliknutím na následující odkaz:\n\n$3\n\n'''Poznámka''': Pokud to neuděláte hned, tento vygenerovaný konfigurační soubor nebude později dostupný, pokud instalaci opustíte, aniž byste si ho stáhli.\n\nAž to dokončíte, můžete '''[$2 vstoupit do své wiki]'''.",
+ "config-download-localsettings": "Stáhnout <code>LocalSettings.php</code>",
+ "config-help": "nápověda",
+ "config-help-tooltip": "rozbalíte kliknutím",
+ "config-nofile": "Soubor „$1“ nelze nalézt. Byl smazán?",
+ "config-extension-link": "Věděli jste, že vaše wiki podporuje [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions rozšíření]?\n\nMůžete si prohlédnout [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category seznam rozšíření po kategoriích].",
+ "mainpagetext": "'''MediaWiki byla úspěšně nainstalována.'''",
+ "mainpagedocfooter": "[//meta.wikimedia.org/wiki/Help:Contents Uživatelská příručka] vám napoví, jak MediaWiki používat.\n\n== Začínáme ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Nastavení konfigurace]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Často kladené otázky o MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-mailová konference oznámení MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Překlad MediaWiki do vašeho jazyka]"
+}
diff --git a/includes/installer/i18n/csb.json b/includes/installer/i18n/csb.json
new file mode 100644
index 00000000..2ba47f15
--- /dev/null
+++ b/includes/installer/i18n/csb.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki òsta zainstalowónô.'''"
+}
diff --git a/includes/installer/i18n/cu.json b/includes/installer/i18n/cu.json
new file mode 100644
index 00000000..1bd12bb5
--- /dev/null
+++ b/includes/installer/i18n/cu.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "ОйЛ"
+ ]
+ },
+ "config-page-language": "ѩꙁꙑкъ",
+ "config-page-name": "имѧ",
+ "config-help": "помощь"
+}
diff --git a/includes/installer/i18n/cv.json b/includes/installer/i18n/cv.json
new file mode 100644
index 00000000..adf128e8
--- /dev/null
+++ b/includes/installer/i18n/cv.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''«MediaWiki» вики-движока лартасси ăнăçлă вĕçленчĕ.'''",
+ "mainpagedocfooter": "Ку википе ĕçлеме пулăшакан информацине [//meta.wikimedia.org/wiki/Help:Contents/ru усăç руководствинче] тупма пултаратăр.\n\n== Пулăшма пултарĕç ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Ĕнерлевсен списокĕ];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki тăрăх час-часах ыйтакан ыйтусемпе хуравсем];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki çĕнĕ верси тухнине пĕлтерекен рассылка].\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/cy.json b/includes/installer/i18n/cy.json
new file mode 100644
index 00000000..5ead1dca
--- /dev/null
+++ b/includes/installer/i18n/cy.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lloffiwr",
+ "Xxglennxx",
+ "Robin Owain"
+ ]
+ },
+ "config-desc": "Y gosodwr ar gyfer MediaWiki",
+ "mainpagetext": "'''Wedi llwyddo gosod meddalwedd MediaWiki yma'''",
+ "mainpagedocfooter": "Ceir cymorth (yn Saesneg) ar ddefnyddio meddalwedd wici yn y [//meta.wikimedia.org/wiki/Help:Contents Canllaw Defnyddwyr] ar wefan Wikimedia.\n\n==Cychwyn arni==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Rhestr osodiadau wrth gyflunio]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Cwestiynau poblogaidd ar MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rhestr postio datganiadau MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Cyfieithu MediaWici i'ch iaith chi]"
+}
diff --git a/includes/installer/i18n/da.json b/includes/installer/i18n/da.json
new file mode 100644
index 00000000..b9de2cfb
--- /dev/null
+++ b/includes/installer/i18n/da.json
@@ -0,0 +1,38 @@
+{
+ "@metadata": {
+ "authors": [
+ "Peter Alberti",
+ "Christian List",
+ "Tjernobyl",
+ "Thomsen"
+ ]
+ },
+ "config-page-language": "Sprog",
+ "config-page-welcome": "Velkommen til MediaWiki!",
+ "config-page-dbconnect": "Forbind til database",
+ "config-page-upgrade": "Opgrader eksisterende installation",
+ "config-page-dbsettings": "Databaseindstillinger",
+ "config-page-name": "Navn",
+ "config-page-options": "Indstillinger",
+ "config-page-install": "Installer",
+ "config-page-complete": "Færdig!",
+ "config-page-restart": "Genstarte installation",
+ "config-page-readme": "Læs mig",
+ "config-page-copying": "Kopiering",
+ "config-page-upgradedoc": "Opgraderer",
+ "config-page-existingwiki": "Eksisterende wiki",
+ "config-help-restart": "Vil du rydde alle gemte data, du har indtastet og genstarte installationen?",
+ "config-restart": "Ja, genstart den",
+ "config-env-php": "PHP $1 er installeret.",
+ "config-db-type": "Databasetype:",
+ "config-db-host": "Databasevært:",
+ "config-db-name": "Databasenavn:",
+ "config-mysql-old": "MySQL $1 eller nyere kræves. Du har $2.",
+ "config-header-mysql": "MySQL-indstillinger",
+ "config-header-postgres": "PostgreSQL-indstillinger",
+ "config-header-sqlite": "SQLite-indstillinger",
+ "config-header-oracle": "Oracle-indstillinger",
+ "config-invalid-db-type": "Ugyldig databasetype",
+ "mainpagetext": "'''MediaWiki er nu installeret.'''",
+ "mainpagedocfooter": "Se [//meta.wikimedia.org/wiki/Help:Contents brugervejledningen] for oplysninger om brugen af wikiprogrammellet.\n\n== At komme i gang ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Listen over opsætningsmuligheder]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki ofte stillede spørgsmål]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Postliste angående udgivelser af MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Oversæt MediaWiki til dit sprog]"
+}
diff --git a/includes/installer/i18n/de-ch.json b/includes/installer/i18n/de-ch.json
new file mode 100644
index 00000000..3a73c3ed
--- /dev/null
+++ b/includes/installer/i18n/de-ch.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Geitost"
+ ]
+ },
+ "config-license-help": "Viele öffentliche Wikis publizieren alle Beiträge unter einer [http://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 ''Creative-Commons''-Lizenz „Namensnennung – Weitergabe unter gleichen Bedingungen“ 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äss dieser Lizenz lizenzierte Inhalte wiederzuverwenden."
+}
diff --git a/includes/installer/i18n/de-formal.json b/includes/installer/i18n/de-formal.json
new file mode 100644
index 00000000..944ed618
--- /dev/null
+++ b/includes/installer/i18n/de-formal.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "MichaelFrey",
+ "Kghbln",
+ "Umherirrender"
+ ]
+ },
+ "config-welcome": "=== Prüfung der Installationsumgebung ===\nDie Basisprüfungen werden jetzt durchgeführt, um festzustellen, ob die Installationsumgebung für MediaWiki geeignet ist.\nNotieren Sie diese Informationen und geben Sie sie an, sofern Sie Hilfe beim Installieren benötigen.",
+ "config-extension-link": "Wussten Sie, dass Ihr Wiki die Nutzung von [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions Erweiterungen] unterstützt?\n\nSie können [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Erweiterungen nach Kategorie] durchsuchen oder die [//www.mediawiki.org/wiki/Extension_Matrix Matrix der Erweiterungen] ansehen, um eine Übersicht zu verfügbaren Erweiterungen zu erhalten.",
+ "mainpagedocfooter": "Hilfe zur Benutzung und Konfiguration der Wiki-Software finden Sie im [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].\n\n== Starthilfen ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste der Konfigurationsvariablen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokalisieren Sie MediaWiki für Ihre Sprache]"
+}
diff --git a/includes/installer/i18n/de.json b/includes/installer/i18n/de.json
new file mode 100644
index 00000000..f329db9b
--- /dev/null
+++ b/includes/installer/i18n/de.json
@@ -0,0 +1,340 @@
+{
+ "@metadata": {
+ "authors": [
+ "Geitost",
+ "Kghbln",
+ "LWChris",
+ "Metalhead64",
+ "Purodha",
+ "Rillke",
+ "The Evil IP address",
+ "Umherirrender",
+ "Wikinaut",
+ "아라",
+ "Se4598",
+ "Suriyaa Kudo",
+ "Das Schäfchen"
+ ]
+ },
+ "config-desc": "Das MediaWiki-Installationsprogramm",
+ "config-title": "Installation von MediaWiki $1",
+ "config-information": "Informationen",
+ "config-localsettings-upgrade": "Eine Datei <code>LocalSettings.php</code> wurde gefunden.\nUm die vorhandene Installation aktualisieren zu können, muss der Wert des Parameters <code>$wgUpgradeKey</code> im folgenden Eingabefeld angegeben werden.\nDer Parameterwert befindet sich in der Datei <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Eine Datei <code>LocalSettings.php</code> wurde gefunden.\nUm die vorhandene Installation zu aktualisieren, muss die Datei <code>update.php</code> ausgeführt werden.",
+ "config-localsettings-key": "Aktualisierungsschlüssel:",
+ "config-localsettings-badkey": "Der angegebene Aktualisierungsschlüssel ist falsch.",
+ "config-upgrade-key-missing": "Eine MediaWiki-Installation wurde gefunden.\nUm die vorhandene Installation aktualisieren zu können, muss die unten angegebene Codezeile in die Datei <code>LocalSettings.php</code> an deren Ende eingefügt werden:\n\n$1",
+ "config-localsettings-incomplete": "Die vorhandene Datei <code>LocalSettings.php</code> scheint unvollständig zu sein.\nDie Variable <code>$1</code> wurde nicht definiert.\nDie Datei <code>LocalSettings.php</code> muss entsprechend geändert werden, so dass sie definiert ist. Klicke danach auf „{{int:Config-continue}}“.",
+ "config-localsettings-connection-error": "Beim Verbindungsversuch zur Datenbank ist, unter Verwendung der in der Datei <code>LocalSettings.php</code> hinterlegten Einstellungen, ein Fehler aufgetreten. Diese Einstellungen müssen korrigiert werden. Danach kann ein erneuter Versuch unternommen werden.\n\n$1",
+ "config-session-error": "Fehler beim Starten der Sitzung: $1",
+ "config-session-expired": "Die Sitzungsdaten scheinen abgelaufen zu sein.\nSitzungen sind für einen Zeitraum von $1 konfiguriert.\nDieser kann durch Anhebung des Parameters <code>session.gc_maxlifetime</code> in der Datei <code>php.ini</code> erhöht werden.\nDen Installationsvorgang erneut starten.",
+ "config-no-session": "Die Sitzungsdaten sind verloren gegangen!\nDie Datei <code>php.ini</code> muss geprüft und es muss dabei sichergestellt werden, dass der Parameter <code>session.save_path</code> auf das richtige Verzeichnis verweist.",
+ "config-your-language": "Sprache während des Installierens:",
+ "config-your-language-help": "Bitte die Sprache auswählen, die während des Installationsvorgangs verwendet werden soll.",
+ "config-wiki-language": "Sprache des Wikis:",
+ "config-wiki-language-help": "Bitte die Sprache auswählen, die überwiegend beim Erstellen der Inhalte verwendet werden soll.",
+ "config-back": "← Zurück",
+ "config-continue": "Weiter →",
+ "config-page-language": "Sprache",
+ "config-page-welcome": "Willkommen bei MediaWiki!",
+ "config-page-dbconnect": "Mit der Datenbank verbinden",
+ "config-page-upgrade": "Eine vorhandene Installation aktualisieren",
+ "config-page-dbsettings": "Einstellungen zur Datenbank",
+ "config-page-name": "Name",
+ "config-page-options": "Optionen",
+ "config-page-install": "Installieren",
+ "config-page-complete": "Fertig!",
+ "config-page-restart": "Installationsvorgang erneut starten",
+ "config-page-readme": "Lies mich",
+ "config-page-releasenotes": "Versionsinfos (en)",
+ "config-page-copying": "Kopie der Lizenz",
+ "config-page-upgradedoc": "Aktualisiere",
+ "config-page-existingwiki": "Vorhandenes Wiki",
+ "config-help-restart": "Sollen alle bereits eingegebenen Daten gelöscht und der Installationsvorgang erneut gestartet werden?",
+ "config-restart": "Ja, erneut starten",
+ "config-welcome": "=== Prüfung der Installationsumgebung ===\nDie Basisprüfungen werden jetzt durchgeführt, um festzustellen, ob die Installationsumgebung für MediaWiki geeignet ist.\nNotiere diese Informationen und gib sie an, sofern du Hilfe beim Installieren benötigst.",
+ "config-copyright": "=== Lizenz und Nutzungsbedingungen ===\n\n$1\n\nDieses Programm ist freie Software, d. h. es kann, gemäß den Bedingungen der von der Free Software Foundation veröffentlichten ''GNU General Public License'', weiterverteilt und/oder modifiziert werden. Dabei kann die Version 2, oder nach eigenem Ermessen, jede neuere Version der Lizenz verwendet werden.\n\nDieses Programm wird in der Hoffnung verteilt, dass es nützlich sein wird, allerdings '''ohne jegliche Garantie''' und sogar ohne die implizierte Garantie einer '''Marktgängigkeit''' oder '''Eignung für einen bestimmten Zweck'''. Hierzu sind weitere Hinweise in der ''GNU General Public License'' enthalten.\n\nEine <doclink href=Copying>Kopie der GNU General Public License</doclink> sollte zusammen mit diesem Programm verteilt worden sein. Sofern dies nicht der Fall war, kann eine Kopie bei der Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftlich angefordert oder auf deren Website [http://www.gnu.org/copyleft/gpl.html online gelesen] werden.",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/de Website von MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/de Benutzeranleitung]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/de Administratorenanleitung]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/de Häufig gestellte Fragen]\n----\n* <doclink href=Readme>Lies mich</doclink>\n* <doclink href=ReleaseNotes>Versionsinformationen</doclink>\n* <doclink href=Copying>Lizenzbestimmungen</doclink>\n* <doclink href=UpgradeDoc>Aktualisierung</doclink>",
+ "config-env-good": "Die Installationsumgebung wurde geprüft.\nMediaWiki kann installiert werden.",
+ "config-env-bad": "Die Installationsumgebung wurde geprüft.\nMediaWiki kann nicht installiert werden.",
+ "config-env-php": "Die Skriptsprache „PHP“ ($1) ist installiert.",
+ "config-env-hhvm": "HHVM $1 ist installiert.",
+ "config-unicode-using-utf8": "Zur Unicode-Normalisierung wird Brion Vibbers <code>utf8_normalize.so</code> eingesetzt.",
+ "config-unicode-using-intl": "Zur Unicode-Normalisierung wird die [http://pecl.php.net/intl PECL-Erweiterung intl] eingesetzt.",
+ "config-unicode-pure-php-warning": "'''Warnung:''' Die [http://pecl.php.net/intl PECL-Erweiterung intl] ist für die Unicode-Normalisierung nicht verfügbar, so dass stattdessen die langsame pure-PHP-Implementierung genutzt wird.\nSofern eine Website mit großer Benutzeranzahl betrieben wird, sollten weitere Informationen auf der Webseite [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-Normalisierung (en)] gelesen werden.",
+ "config-unicode-update-warning": "'''Warnung:''' Die installierte Version des Unicode-Normalisierungswrappers nutzt einer ältere Version der Bibliothek des [http://site.icu-project.org/ ICU-Projekts].\nDiese sollte [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations aktualisiert] werden, sofern auf die Verwendung von Unicode Wert gelegt wird.",
+ "config-no-db": "Es konnte kein adäquater Datenbanktreiber gefunden werden. Es muss daher ein Datenbanktreiber für PHP installiert werden.\nDie folgenden Datenbanksysteme werden unterstützt: $1\n\nWenn du PHP selbst kompiliert hast, konfiguriere es erneut mit einem aktivierten Datenbankclient, zum Beispiel durch Verwendung von <code>./configure --with-mysqli</code>.\nWenn du PHP von einem Debian- oder Ubuntu-Paket installiert hast, dann musst du auch beispielsweise das <code>php5-mysql</code>-Paket installieren.",
+ "config-outdated-sqlite": "'''Warnung:''' SQLite $1 ist installiert. Allerdings benötigt MediaWiki SQLite $2 oder höher. SQLite wird daher nicht verfügbar sein.",
+ "config-no-fts3": "'''Warnung:''' SQLite wurde ohne das [//sqlite.org/fts3.html FTS3-Modul] kompiliert, sodass keine Suchfunktionen für dieses Datenbanksystem zur Verfügung stehen werden.",
+ "config-register-globals-error": "<strong>Fehler: Die PHP-Option <code>[http://php.net/register_globals register_globals]</code> ist aktiviert.\nSie muss deaktiviert sein, um mit der Installation fortzufahren.</strong>\nSiehe [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] für Hilfe.",
+ "config-magic-quotes-gpc": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] ist aktiv!</strong>\nDiese Option beschädigt eingegebene Daten unvorhersehbar.\nDu kannst MediaWiki nicht installieren oder verwenden, bis diese Option deaktiviert ist.",
+ "config-magic-quotes-runtime": "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/function.set-magic-quotes-runtime.php set_magic_quotes_runtime]</code> von PHP ist aktiviert!'''\nDiese Einstellung führt zu unvorhersehbaren Problemen bei der Dateneingabe.\nMediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
+ "config-magic-quotes-sybase": "<strong>Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/sybase.configuration.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> von PHP ist aktiviert!</strong>\nDiese Einstellung führt zu unvorhersehbaren Problemen bei der Dateneingabe.\nMediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
+ "config-mbstring": "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> von PHP ist aktiviert!'''\nDiese Einstellung verursacht Fehler und führt zu unvorhersehbaren Problemen bei der Dateneingabe.\nMediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
+ "config-safe-mode": "'''Warnung:''' Der Funktion <code>[http://www.php.net/features.safe-mode Safe Mode]</code> von PHP ist aktiviert.\nDies kann zu Problemen führen, insbesondere wenn das Hochladen von Dateien möglich sein, bzw. der Auszeichner <code>math</code> genutzt werden soll.",
+ "config-xml-bad": "Das XML-Modul von PHP fehlt.\nMediaWiki benötigt Funktionen, die dieses Modul bereitstellt und wird in der bestehenden Konfiguration nicht funktionieren.\nSofern Mandriva genutzt wird, muss noch das „php-xml“-Paket installiert werden.",
+ "config-pcre-old": "<strong>Fataler Fehler:</strong> PCRE $1 oder neuer ist erforderlich!\nDie vorhandene PHP-Binärdatei ist mit PCRE $2 verknüpft.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Weitere Informationen].",
+ "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-ctype": "'''Fataler Fehler:''' PHP muss mit Unterstützung für das [http://www.php.net/manual/de/ctype.installation.php Modul ctype] kompiliert werden.",
+ "config-iconv": "<strong>Fatal:</strong> PHP muss mit Support für die [http://www.php.net/manual/en/iconv.installation.php iconv-Erweiterung] kompiliert werden.",
+ "config-json": "<strong>Fataler Fehler:</strong> PHP wurde ohne Unterstützung für JSON kompiliert.\nVor der Installation von MediaWiki muss entweder die PHP-JSON- oder die [http://pecl.php.net/package/jsonc PECL-jsonc]-Erweiterung installieren werden.\n* Die PHP-Erweiterung ist in Red Hat Enterprise Linux (CentOS) 5 und 6 enthalten, muss jedoch in <code>/etc/php.ini</code> oder <code>/etc/php.d/json.ini</code> aktiviert werden.\n* Einige Linux-Distributionen, die nach Mai 2013 veröffentlicht wurden, nutzen nicht mehr die PHP-Erweiterung, sondern stattdessen die PECL-Erweiterung als <code>php5-json</code> oder <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] ist installiert",
+ "config-apc": "[http://www.php.net/apc APC] ist installiert",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] ist installiert",
+ "config-no-cache": "'''Warnung:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] wurden nicht gefunden.\nDas Objektcaching kann daher nicht aktiviert werden.",
+ "config-mod-security": "'''Warnung:''' Auf dem Webserver wurde [http://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 [http://modsecurity.org/documentation/ Dokumentation zu ModSecurity] oder den Kontakt zum Hoster, sofern Fehler auftreten.",
+ "config-diff3-bad": "GNU diff3 wurde nicht gefunden.",
+ "config-git": "Die Versionsverwaltungssoftware „Git“ wurde gefunden: <code>$1</code>.",
+ "config-git-bad": "Die Versionsverwaltungssoftware „Git“ wurde nicht gefunden.",
+ "config-imagemagick": "Die Bildverarbeitungssoftware „ImageMagick“ wurde gefunden: <code>$1</code>.\nMiniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dateien aktiviert wurde.",
+ "config-gd": "Die im System integrierte GD-Grafikbibliothek wurde gefunden.\nMiniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dateien aktiviert wurde.",
+ "config-no-scaling": "Weder die GD-Grafikbibliothek noch ImageMagick wurden gefunden.\nMiniaturansichten von Bildern sind daher nicht möglich.",
+ "config-no-uri": "'''Fehler:''' Die aktuelle URL konnte nicht ermittelt werden.\nDer Installationsvorgang wurde daher abgebrochen.",
+ "config-no-cli-uri": "'''Warnung''': Es wurde kein Pfad zum Skipt (<code>--scriptpath</code>) angegeben. Daher wird der Standardpfad genutzt: <code>$1</code>.",
+ "config-using-server": "Der Servername „<nowiki>$1</nowiki>“ wird verwendet.",
+ "config-using-uri": "Die Server-URL „<nowiki>$1$2</nowiki>“ wird verwendet.",
+ "config-uploads-not-safe": "'''Warnung:''' Das Standardverzeichnis für hochgeladene Dateien <code>$1</code> ist für die willkürliche Ausführung von Skripten anfällig.\nObwohl MediaWiki die hochgeladenen Dateien auf Sicherheitsrisiken überprüft, wird dennoch dringend empfohlen, diese [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security Sicherheitslücke] zu schließen, bevor das Hochladen von Dateien aktiviert wird.",
+ "config-no-cli-uploads-check": "'''Warnung''': Das Standardverzeichnis für hochgeladene Dateien (<code>$1</code>) wird, während der Installation über die Kommandozeile, nicht auf Sicherheitsanfälligkeiten hinsichtlich willkürlicher Skriptausführungen geprüft.",
+ "config-brokenlibxml": "Das System nutzt eine Kombination aus PHP- und libxml2-Versionen, die fehleranfällig ist und versteckte Datenfehler bei MediaWiki und anderen Webanwendungen verursachen kann.\nAktualisiere auf libxml2 2.7.3 oder später, um das Problem zu lösen. Installationsabbruch ([https://bugs.php.net/bug.php?id=45996 siehe hierzu die Fehlermeldung bei PHP]).",
+ "config-suhosin-max-value-length": "Suhosin ist installiert und beschränkt die Länge des GET-Parameters auf $1 Bytes.\nDer ResouceLoader von MediaWiki wird zwar unter diesen Bedingungen funktionieren, allerdings nur mit verminderter Leistungsfähigkeit.\nSofern möglich, sollte der Parameter <code>suhosin.get.max_value_length</code> in der Datei <code>php.ini</code> auf 1024 oder höher festgelegt werden.\nGleichzeitig muss der Parameter <code>$wgResourceLoaderMaxQueryLength</code> in der Datei <code>LocalSettings.php</code> auf den selben Wert eingestellt werden.",
+ "config-db-type": "Datenbanksystem:",
+ "config-db-host": "Datenbankserver:",
+ "config-db-host-help": "Sofern sich die Datenbank auf einem anderen Server befindet, ist hier der Servername oder die entsprechende IP-Adresse anzugeben.\n\nSofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster den zutreffenden Servernamen in seiner Dokumentation angegeben haben.\n\nSofern auf einem Windows-Server installiert und MySQL genutzt wird, funktioniert der Servername „localhost“ voraussichtlich nicht. Wenn nicht, sollte „127.0.0.1“ oder die lokale IP-Adresse angegeben werden.\n\nSofern PostgresQL genutzt wird, muss dieses Feld leer gelassen werden, um über ein Unix-Socket zu verbinden.",
+ "config-db-host-oracle": "Datenbank-TNS:",
+ "config-db-host-oracle-help": "Einen gültigen [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „Local Connect“-Namen] angeben. Die „tnsnames.ora“-Datei muss von dieser Installation erkannt werden können.<br />Sofern die Client-Bibliotheken für Version 10g oder neuer verwendet werden, kann auch [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „Easy Connect“] zur Namensgebung genutzt werden.",
+ "config-db-wiki-settings": "Bitte Daten zur eindeutigen Identifikation dieses Wikis angeben",
+ "config-db-name": "Datenbankname:",
+ "config-db-name-help": "Bitte einen Namen angeben, mit dem das Wiki identifiziert werden kann.\nDabei sollten keine Leerzeichen verwendet werden.\n\nSofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster den Datenbanknamen angegeben oder aber die Erstellung einer Datenbank über ein entsprechendes Interface gestattet haben.",
+ "config-db-name-oracle": "Datenbankschema:",
+ "config-db-account-oracle-warn": "Es gibt drei von MediaWiki unterstützte Möglichkeiten, Oracle als Datenbank einzurichten:\n\nSofern das Datenbankbenutzerkonto während des Installationsvorgangs erstellt werden soll, muss ein Datenbankbenutzerkonto mit der SYSDBA-Berechtigung zusammen mit den entsprechenden Anmeldeinformationen angegeben werden, mit dem dann über das Web auf die Datenbank zugegriffen werden kann. Alternativ kann man auch lediglich ein einzelnes manuell angelegtes Datenbankbenutzerkonto angeben, mit dem über das Web auf die Datenbank zugegriffen werden kann, sofern dieses über die Berechtigung zur Erstellung von Datenbankschemen verfügt. Zudem ist es möglich, zwei Datenbankbenutzerkonten anzugeben, von denen eines die Berechtigung zur Erstellung von Datenbankschemen hat und das andere, um mit ihm über das Web auf die Datenbank zuzugreifen.\n\nEin Skript zum Anlegen eines Datenbankbenutzerkontos mit den notwendigen Berechtigungen findet man unter dem Pfad „…/maintenance/oracle/“ dieser MediaWiki-Installation. Es ist dabei zu bedenken, dass die Verwendung eines Datenbankbenutzerkontos mit beschränkten Berechtigungen die Nutzung der Wartungsfunktionen für das Standarddatenbankbenutzerkonto deaktiviert.",
+ "config-db-install-account": "Benutzerkonto für die Installation",
+ "config-db-username": "Name des Datenbankbenutzers:",
+ "config-db-password": "Passwort des Datenbankbenutzers:",
+ "config-db-password-empty": "Bitte ein Passwort für den neuen Datenbankbenutzer angeben: $1\nObzwar es möglich ist, Datenbankbenutzer ohne Passwort anzulegen, so ist dies aber nicht sicher.",
+ "config-db-username-empty": "Du musst einen Wert für „{{int:config-db-username}}“ eingeben",
+ "config-db-install-username": "Den Benutzernamen angeben, der für die Verbindung mit der Datenbank während des Installationsvorgangs genutzt werden soll. Es handelt sich dabei nicht um den Benutzernamen für das MediaWiki-Konto, sondern um den Benutzernamen der vorgesehenen Datenbank.",
+ "config-db-install-password": "Das Passwort angeben, das für die Verbindung mit der Datenbank während des Installationsvorgangs genutzt werden soll. Es handelt sich dabei nicht um das Passwort für das MediaWiki-Konto, sondern um das Passwort der vorgesehenen Datenbank.",
+ "config-db-install-help": "Benutzername und Passwort, die während des Installationsvorgangs, für die Verbindung mit der Datenbank, genutzt werden sollen, sind nun anzugeben.",
+ "config-db-account-lock": "Derselbe Benutzername und das Passwort müssen während des Normalbetriebs des Wikis verwendet werden.",
+ "config-db-wiki-account": "Benutzerkonto für den normalen Betrieb",
+ "config-db-wiki-help": "Bitte Benutzernamen und Passwort angeben, die der Webserver während des Normalbetriebes dazu verwenden soll, eine Verbindung zum Datenbankserver herzustellen.\nSofern ein entsprechendes Benutzerkonto nicht vorhanden ist und das Benutzerkonto für den Installationsvorgang über ausreichende Berechtigungen verfügt, wird dieses Benutzerkonto automatisch mit den Mindestberechtigungen zum Normalbetrieb des Wikis angelegt.",
+ "config-db-prefix": "Datenbanktabellenpräfix:",
+ "config-db-prefix-help": "Sofern eine Datenbank für mehrere Wikiinstallationen oder eine Wikiinstallation und eine andere Programminstallation genutzt werden soll, muss ein Datenbanktabellenpräfix angegeben werden, um Datenbankprobleme zu vermeiden.\nEs können keine Leerzeichen verwendet werden.\n\nGewöhnlich bleibt dieses Feld leer.",
+ "config-db-charset": "Datenbankzeichensatz",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binär",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 abwärtskompatibles UTF-8",
+ "config-charset-help": "'''Warnung:''' Sofern '''abwärtskompatibles UTF-8''' bei MySQL 4.1+ verwendet und anschließend die Datenbank mit <code>mysqldump</code> gesichert wird, könnten alle nicht mit ASCII-codierten Zeichen beschädigt werden, was zu irreversiblen Schäden der Datensicherung führt!\n\nIm '''binären Modus''' speichert MediaWiki UTF-8 Texte in der Datenbank in binär kodierte Datenfelder.\nDies ist effizienter als der UTF-8-Modus von MySQL und ermöglicht so die Verwendung jeglicher Unicode-Zeichen.\nIm '''UTF-8-Modus''' wird MySQL den Zeichensatz der Daten erkennen und sie richtig anzeigen und konvertieren.\nEs können allerdings keine Zeichen außerhalb des [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
+ "config-mysql-old": "MySQL $1 oder höher wird benötigt. MySQL $2 ist momentan vorhanden.",
+ "config-db-port": "Datenbankport:",
+ "config-db-schema": "Datenschema für MediaWiki",
+ "config-db-schema-help": "Dieses Datenschema ist in der Regel allgemein verwendbar.\nNur Änderungen daran vornehmen, sofern es gute Gründe dafür gibt.",
+ "config-pg-test-error": "Es kann keine Verbindung zur Datenbank '''$1''' hergestellt werden: $2",
+ "config-sqlite-dir": "SQLite-Datenverzeichnis:",
+ "config-sqlite-dir-help": "SQLite speichert alle Daten in einer einzigen Datei.\n\nDas für sie vorgesehene Verzeichnis muss während des Installationsvorgangs beschreibbar sein.\n\nEs sollte '''nicht''' über das Web zugänglich sein, was der Grund ist, warum die Datei nicht dort abgelegt wird, wo sich die PHP-Dateien befinden.\n\nDas Installationsprogramm wird mit der Datei zusammen eine zusätzliche <code>.htaccess</code>-Datei erstellen. Sofern dies scheitert, können Dritte auf die Datendatei zugreifen.\nDies umfasst die Nutzerdaten (E-Mail-Adressen, Passwörter, etc.) wie auch gelöschte Seitenversionen und andere vertrauliche Daten, die im Wiki gespeichert sind.\n\nEs ist daher zu erwägen, die Datendatei an gänzlich anderer Stelle abzulegen, beispielsweise im Verzeichnis <code>./var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Standardtabellenraum:",
+ "config-oracle-temp-ts": "Temporärer Tabellenraum:",
+ "config-type-mysql": "MySQL (oder kompatible Datenbanksysteme)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-support-info": "MediaWiki unterstützt die folgenden Datenbanksysteme:\n\n$1\n\nSofern nicht das Datenbanksystem angezeigt wird, das verwendet werden soll, gibt es oben einen Link zur Anleitung mit Informationen, wie dieses aktiviert werden kann.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] ist das von MediaWiki primär unterstützte Datenbanksystem. MediaWiki funktioniert auch mit [{{int:version-db-mariadb-url}} MariaDB] und [{{int:version-db-percona-url}} Percona Server], die MySQL-kompatibel sind. ([http://www.php.net/manual/en/mysqli.installation.php Anleitung zur Kompilierung von PHP mit MySQL-Unterstützung] [englische Sprache])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ist ein beliebtes Open-Source-Datenbanksystem und eine Alternative zu MySQL. Es gibt allerdings einige kleinere Implementierungsfehler, so dass von der Nutzung in einer Produktivumgebung abgeraten wird. ([http://www.php.net/manual/de/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 ([http://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 ([http://www.php.net/manual/en/oci8.installation.php Anleitung zur Kompilierung von PHP mit OCI8-Unterstützung (en)])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ist eine gewerbliche Unternehmensdatenbank für Windows. ([http://www.php.net/manual/de/sqlsrv.installation.php Anleitung zur Kompilierung von PHP mithilfe SQLSRV-Unterstützung])",
+ "config-header-mysql": "MySQL-Einstellungen",
+ "config-header-postgres": "PostgreSQL-Einstellungen",
+ "config-header-sqlite": "SQLite-Einstellungen",
+ "config-header-oracle": "Oracle-Einstellungen",
+ "config-header-mssql": "Einstellungen von Microsoft SQL Server",
+ "config-invalid-db-type": "Unzulässiges Datenbanksystem",
+ "config-missing-db-name": "Bei „{{int:config-db-name}}“ muss ein Wert angegeben werden.",
+ "config-missing-db-host": "Bei „{{int:config-db-host}}“ muss ein Wert angegeben werden.",
+ "config-missing-db-server-oracle": "Für „{{int:config-db-host-oracle}}“ muss ein Wert eingegeben werden.",
+ "config-invalid-db-server-oracle": "Ungültiges Datenbank-TNS „$1“.\nEntweder „TNS Name“ oder eine „Easy Connect“-Zeichenfolge verwenden ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle-Benennungsmethoden])",
+ "config-invalid-db-name": "Ungültiger Datenbankname „$1“.\nEs dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9), Unter- (_) sowie Bindestriche (-) verwendet werden.",
+ "config-invalid-db-prefix": "Ungültiger Datenbanktabellenpräfix „$1“.\nEs dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9), Unter- (_) sowie Bindestriche (-) verwendet werden.",
+ "config-connection-error": "$1.\n\nBitte unten angegebenen Servernamen, Benutzernamen sowie das Passwort überprüfen und es danach erneut versuchen.",
+ "config-invalid-schema": "Ungültiges Datenschema für MediaWiki „$1“.\nEs dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9) und Unterstriche (_) verwendet werden.",
+ "config-db-sys-create-oracle": "Das Installationsprogramm unterstützt nur die Verwendung eines Datenbankbenutzerkontos mit SYSDBA-Berechtigung zum Anlegen eines neuen Datenbankbenutzerkontos.",
+ "config-db-sys-user-exists-oracle": "Das Datenbankbenutzerkonto „$1“ ist bereits vorhanden. Ein Datenbankbenutzerkontos mit SYSDBA-Berechtigung kann nur zum Anlegen eines neuen Datenbankbenutzerkontos genutzt werden.",
+ "config-postgres-old": "PostgreSQL $1 oder höher wird benötigt. PostgreSQL $2 ist momentan vorhanden.",
+ "config-mssql-old": "Es wird Microsoft SQL Server $1 oder später benötigt. Deine Version ist $2.",
+ "config-sqlite-name-help": "Bitten einen Namen angeben, mit dem das Wiki identifiziert werden kann.\nDabei bitte keine Leerzeichen oder Bindestriche verwenden.\nDieser Name wird für die SQLite-Datendateinamen genutzt.",
+ "config-sqlite-parent-unwritable-group": "Das Datenverzeichnis <code><nowiki>$1</nowiki></code> kann nicht erzeugt werden, da das übergeordnete Verzeichnis <code><nowiki>$2</nowiki></code> nicht für den Webserver beschreibbar ist.\n\nDas Installationsprogramm konnte den Benutzer bestimmen, mit dem Webserver ausgeführt wird.\nSchreibzugriff auf das <code><nowiki>$3</nowiki></code>-Verzeichnis muss für diesen ermöglicht werden, um den Installationsvorgang fortsetzen zu können.\n\nAuf einem Unix- oder Linux-System:\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Das Datenverzeichnis <code><nowiki>$1</nowiki></code> kann nicht erzeugt werden, da das übergeordnete Verzeichnis <code><nowiki>$2</nowiki></code> nicht für den Webserver beschreibbar ist.\n\nDas Installationsprogramm konnte den Benutzer bestimmen, mit dem Webserver ausgeführt wird.\nSchreibzugriff auf das <code><nowiki>$3</nowiki></code>-Verzeichnis muss global für diesen und andere Benutzer ermöglicht werden, um den Installationsvorgang fortsetzen zu können.\n\nAuf einem Unix- oder Linux-System:\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Fehler beim Erstellen des Datenverzeichnisses „$1“.\n\nBitte den Speicherort überprüfen und es danach erneut versuchen.",
+ "config-sqlite-dir-unwritable": "Das Verzeichnis „$1“ ist nicht beschreibbar.\nBitte die Zugriffsberechtigungen so ändern, dass dieses Verzeichnis für den Webserver beschreibbar ist und es danach erneut versuchen.",
+ "config-sqlite-connection-error": "$1.\n\nBitte unten angegebenes Datenverzeichnis sowie den Datenbanknamen überprüfen und es danach erneut versuchen.",
+ "config-sqlite-readonly": "Die Datei <code>$1</code> ist nicht beschreibbar.",
+ "config-sqlite-cant-create-db": "Die Datenbankdatei <code>$1</code> konnte nicht erzeugt werden.",
+ "config-sqlite-fts3-downgrade": "PHP verfügt nicht über FTS3-Unterstützung. Die Tabellen wurden zurückgestuft.",
+ "config-can-upgrade": "Es wurden MediaWiki-Tabellen in dieser Datenbank gefunden.\nUm sie auf MediaWiki $1 zu aktualisieren, bitte auf '''Weiter''' klicken.",
+ "config-upgrade-done": "Die Aktualisierung ist nun abgeschlossen.\n\nDas Wiki kann nun [$1 genutzt werden].\n\nSofern die Datei <code>LocalSettings.php</code> neu erzeugt werden soll, bitte auf die Schaltfläche unten klicken.\nDies wird '''nicht empfohlen''', es sei denn, es treten Probleme mit dem Wiki auf.",
+ "config-upgrade-done-no-regenerate": "Die Aktualisierung ist abgeschlossen.\n\nDas Wiki kann nun [$1 genutzt werden].",
+ "config-regenerate": "LocalSettings.php neu erstellen →",
+ "config-show-table-status": "Die Abfrage <code>SHOW TABLE STATUS</code> ist gescheitert!",
+ "config-unknown-collation": "'''Warnung:''' Die Datenbank nutzt eine unbekannte Kollation.",
+ "config-db-web-account": "Datenbankkonto für den Webzugriff",
+ "config-db-web-help": "Bitte Benutzernamen und Passwort auswählen, die der Webserver während des Normalbetriebes dazu verwenden soll, eine Verbindung zum Datenbankserver herzustellen.",
+ "config-db-web-account-same": "Dasselbe Datenbankkonto wie während des Installationsvorgangs verwenden",
+ "config-db-web-create": "Sofern nicht bereits vorhanden, muss nun das Konto erstellt werden",
+ "config-db-web-no-create-privs": "Das angegebene und für den Installationsvorgang vorgesehene Datenbankkonto verfügt nicht über ausreichend Berechtigungen, um ein weiteres Datenbankkonto zu erstellen.\nDas hier angegebene Datenbankkonto muss daher bereits vorhanden sein.",
+ "config-mysql-engine": "Speicher-Engine:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Warnung:''' Es wurde MyISAM als Speicher-Engine für MySQL ausgewählt, die aus folgenden Gründen nicht für den Einsatz mit MediaWiki empfohlen ist:\n* Sie unterstützt aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen.\n* Sie ist anfälliger für Datenprobleme.\n* Sie wird von MediaWiki nicht immer adäquat unterstützt.\n\nSofern die vorhandene MySQL-Installation die Speicher-Engine InnoDB unterstützt, wird deren Verwendung eindringlich empfohlen.\nSofern sie sie nicht unterstützt, sollte eine entsprechende Aktualisierung nunmehr Erwägung gezogen werden.",
+ "config-mysql-only-myisam-dep": "'''Warnung:''' MyISAM ist die einzige verfügbare Speicher-Engine für MySQL auf diesem Rechner, und dies wird nicht für die Verwendung mit MediaWiki empfohlen, da sie\n* aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen unterstützt,\n* anfälliger für Datenprobleme ist und\n* von MediaWiki nicht immer adäquat unterstützt wird.\n\nDeine MySQL-Installation unterstützt nicht InnoDB. Eventuell muss eine Aktualisierung durchgeführt werden.",
+ "config-mysql-engine-help": "'''InnoDB''' ist fast immer die bessere Wahl, da es gleichzeitige Zugriffe gut unterstützt.\n\n'''MyISAM''' ist in Einzelnutzerumgebungen sowie bei schreibgeschützten Wikis schneller.\nBei MyISAM-Datenbanken treten tendenziell häufiger Fehler auf als bei InnoDB-Datenbanken.",
+ "config-mysql-charset": "Datenbankzeichensatz:",
+ "config-mysql-binary": "binär",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "Im '''binären Modus''' speichert MediaWiki UTF-8 Texte in der Datenbank in binär kodierte Datenfelder.\nDies ist effizienter als der UTF-8-Modus von MySQL und ermöglicht so die Verwendung jeglicher Unicode-Zeichen.\n\nIm '''UTF-8-Modus''' wird MySQL den Zeichensatz der Daten erkennen und sie richtig anzeigen und konvertieren,\nallerdings können keine Zeichen außerhalb des [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
+ "config-mssql-auth": "Authentifikationstyp:",
+ "config-mssql-install-auth": "Wähle den Authentifikationstyp aus, der zur Verbindung mit der Datenbank während des Installationsprozesses verwendet wird.\nFalls du „{{int:config-mssql-windowsauth}}“ auswählst, werden die Anmeldeinformationen eines beliebigen Benutzers verwendet, der den Webserver ausführt.",
+ "config-mssql-web-auth": "Wähle den Authentifikationstyp aus, der vom Webserver zur Verbindung mit dem Datenbankserver während des gewöhnlichen Betriebs des Wikis verwendet wird.\nFalls du „{{int:config-mssql-windowsauth}}“ auswählst, werden die Anmeldeinformationen eines beliebigen Benutzers verwendet, der den Webserver ausführt.",
+ "config-mssql-sqlauth": "SQL-Server-Authentifikation",
+ "config-mssql-windowsauth": "Windows-Authentifikation",
+ "config-site-name": "Name des Wikis:",
+ "config-site-name-help": "Er wird in der Titelleiste des Browsers, wie auch verschiedenen anderen Stellen, genutzt.",
+ "config-site-name-blank": "Den Namen des Wikis angeben.",
+ "config-project-namespace": "Name des Projektnamensraums:",
+ "config-ns-generic": "Projekt",
+ "config-ns-site-name": "Entspricht dem Namen des Wikis: $1",
+ "config-ns-other": "Anderer Name (bitte angeben)",
+ "config-ns-other-default": "MeinWiki",
+ "config-project-namespace-help": "Dem Beispiel von Wikipedia folgend, unterscheiden viele Wikis zwischen den Seiten für Inhalte und denen für Richtlinien. Letztere werden im „'''Projektnamensraum'''“ hinterlegt.\nAlle Seiten dieses Namensraumes verfügen über einen Seitenpräfix, der nun an dieser Stelle angegeben werden kann.\nTraditionell steht dieser Seitenpräfix mit dem Namen des Wikis in einem engen Zusammenhang. Dabei können bestimmte Sonderzeichen wie „#“ oder „:“ nicht verwendet werden.",
+ "config-ns-invalid": "Der angegebene Namensraum „<nowiki>$1</nowiki>“ ist ungültig.\nBitte einen abweichenden Projektnamensraum angeben.",
+ "config-ns-conflict": "Der angegebene Namensraum „<nowiki>$1</nowiki>“ verursacht Problem mit dem Standardnamensraum von MediaWiki.\nBitte einen abweichenden Projektnamensraum angeben.",
+ "config-admin-box": "Administratorkonto",
+ "config-admin-name": "Dein Benutzername:",
+ "config-admin-password": "Passwort:",
+ "config-admin-password-confirm": "Passwort wiederholen:",
+ "config-admin-help": "Bitte den bevorzugten Benutzernamen angeben, beispielsweise „Knut Wuchtig“.\nDies ist der Name, der benötigt wird, um sich im Wiki anzumelden.",
+ "config-admin-name-blank": "Bitte den Benutzernamen für den Administratoren angeben.",
+ "config-admin-name-invalid": "Der angegebene Benutzername „<nowiki>$1</nowiki>“ ist ungültig.\nBitte einen abweichenden Benutzernamen angeben.",
+ "config-admin-password-blank": "Bitte das Passwort für das Administratorkonto angeben.",
+ "config-admin-password-mismatch": "Die beiden Passwörter stimmen nicht überein.",
+ "config-admin-email": "E-Mail-Adresse:",
+ "config-admin-email-help": "Bitte hier eine E-Mail-Adresse angeben, die den E-Mail-Empfang von anderen Benutzern des Wikis, das Zurücksetzen des Passwortes sowie Benachrichtigungen zu Änderungen an beobachteten Seiten ermöglicht. Diese Feld kann leer gelassen werden.",
+ "config-admin-error-user": "Es ist beim Erstellen des Administrators mit dem Namen „<nowiki>$1</nowiki>“ ein interner Fehler aufgetreten.",
+ "config-admin-error-password": "Es ist beim Setzen des Passworts für den Administrator „<nowiki>$1</nowiki>“ ein interner Fehler aufgetreten: <pre>$2</pre>",
+ "config-admin-error-bademail": "Es wurde eine ungültige E-Mail-Adresse angegeben.",
+ "config-subscribe": "Bitte die Mailingliste [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mitteilungen zu Versionsveröffentlichungen] abonnieren.",
+ "config-subscribe-help": "Es handelt sich hierbei um eine Mailingliste mit wenigen Aussendungen, die für Mitteilungen zu Versionsveröffentlichungen, einschließlich wichtiger Sicherheitsveröffentlichungen, genutzt wird.\nDiese Mailingliste sollte abonniert werden. Zudem sollte die MediaWiki-Installation stets aktualisiert werden, sobald eine neue Programmversion veröffentlicht wurde.",
+ "config-subscribe-noemail": "Beim Abonnieren der Mailingliste mit Mitteilungen zu Versionsveröffentlichungen wurde keine E-Mail-Adresse angegeben.\nBitte eine E-Mail-Adresse angeben, sofern die Mailingliste abonniert werden soll.",
+ "config-almost-done": "Der Vorgang ist fast abgeschlossen!\nDie verbleibenden Konfigurationseinstellungen können übersprungen und das Wiki umgehend installiert werden.",
+ "config-optional-continue": "Ja, es sollen weitere Konfigurationseinstellungen vorgenommen werden.",
+ "config-optional-skip": "Nein, das Wiki soll nun installiert werden.",
+ "config-profile": "Profil der Benutzerberechtigungen:",
+ "config-profile-wiki": "offenes Wiki",
+ "config-profile-no-anon": "Erstellung eines Benutzerkontos erforderlich",
+ "config-profile-fishbowl": "ausschließlich berechtigte Bearbeiter",
+ "config-profile-private": "geschlossenes Wiki",
+ "config-profile-help": "Wikis sind am nützlichsten, wenn so viele Menschen als möglich Bearbeitungen vornehmen können.\nMit MediaWiki ist es einfach die letzten Änderungen nachzuvollziehen und unbrauchbare Bearbeitungen, beispielsweise von unbedarften oder böswilligen Benutzern, rückgängig zu machen.\n\nAllerdings finden etliche Menschen Wikis auch mit anderen Bearbeitungskonzepten sinnvoll. Manchmal ist es zudem nicht einfach alle Beteiligten von den Vorteilen des „Wiki-Prinzips” zu überzeugen. Darum ist diese Auswahl möglich.\n\nDas Modell „'''{{int:config-profile-wiki}}'''“ ermöglicht es jedermann, sogar ohne über ein Benutzerkonto zu verfügen, Bearbeitungen vorzunehmen.\nEin Wiki bei dem die '''{{int:config-profile-no-anon}}''' ist, fordert von den Benutzern eine höhere Verantwortung für ihre Bearbeitungen ein, könnte allerdings Personen abschrecken, die nur gelegentlich Bearbeitungen vornehmen wollen. Ein Wiki für '''{{int:config-profile-fishbowl}}''' gestattet es nur bestimmten Benutzern, Bearbeitungen vorzunehmen. Allerdings kann dabei die Allgemeinheit die Seiten immer noch betrachten und Änderungen nachvollziehen. Ein '''{{int:config-profile-private}}''' gestattet es nur ausgewählten Benutzern, Seiten zu betrachten sowie zu bearbeiten.\n\nKomplexere Konzepte zur Zugriffssteuerung können erst nach abgeschlossenem Installationsvorgang eingerichtet werden. Hierzu gibt es weitere Informationen auf der Website mit der [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights entsprechenden Anleitung].",
+ "config-license": "Lizenz:",
+ "config-license-none": "Keine Lizenzangabe in der Fußzeile",
+ "config-license-cc-by-sa": "''Creative Commons'' „Namensnennung – Weitergabe unter gleichen Bedingungen“",
+ "config-license-cc-by": "''Creative Commons'' „Namensnennung“",
+ "config-license-cc-by-nc-sa": "''Creative Commons'' „Namensnennung – nicht kommerziell – Weitergabe unter gleichen Bedingungen“",
+ "config-license-cc-0": "''Creative Commons'' „Zero“ (Gemeinfreiheit)",
+ "config-license-gfdl": "GNU-Lizenz für freie Dokumentation 1.3 oder höher",
+ "config-license-pd": "Gemeinfreiheit",
+ "config-license-cc-choose": "Eine benutzerdefinierte Creative-Commons-Lizenz auswählen",
+ "config-license-help": "Viele öffentliche Wikis publizieren alle Beiträge unter einer [http://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 [http://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",
+ "config-email-usertalk-help": "Ermöglicht es Benutzern, Benachrichtigungen zu Änderungen an ihren Benutzerdiskussionsseiten zu erhalten, sofern sie dies in ihren Einstellungen aktiviert haben.",
+ "config-email-watchlist": "Benachrichtigungen zu Änderungen an Seiten auf der Beobachtungsliste ermöglichen",
+ "config-email-watchlist-help": "Ermöglicht es Benutzern, Benachrichtigungen zu Änderungen an Seiten auf ihrer Beobachtungsliste zu erhalten, sofern sie dies in ihren Einstellungen aktiviert haben.",
+ "config-email-auth": "E-Mail-Authentifizierung ermöglichen",
+ "config-email-auth-help": "Sofern diese Funktion aktiviert ist, müssen Benutzer ihre E-Mail-Adresse bestätigen, indem sie den Bestätigungslink nutzen, der ihnen immer dann zugesandt wird, wenn sie ihre E-Mail-Adresse angeben oder ändern.\nNur bestätigte E-Mail-Adressen können Nachrichten von anderen Benutzer oder Benachrichtigungsmitteilungen erhalten.\nDie Aktivierung dieser Funktion wird bei offenen Wikis, mit Hinblick auf möglichen Missbrauch der E-Mail-Funktionen, '''empfohlen.'''",
+ "config-email-sender": "E-Mail-Adresse für Antworten:",
+ "config-email-sender-help": "Bitte hier die E-Mail-Adresse angeben, die als Absenderadresse bei ausgehenden E-Mails eingesetzt werden soll.\nRücklaufende E-Mails werden an diese E-Mail-Adresse gesandt.\nBei vielen E-Mail-Servern muss der Teil der E-Mail-Adresse mit der Domainangabe korrekt sein.",
+ "config-upload-settings": "Hochladen von Bildern und Dateien",
+ "config-upload-enable": "Das Hochladen von Dateien ermöglichen",
+ "config-upload-help": "Das Hochladen von Dateien macht den Server für potentielle Sicherheitsprobleme anfällig.\nWeitere Informationen hierzu können im [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security Abschnitt Sicherheit] der Anleitung nachgelesen werden.\n\nUm das Hochladen von Dateien zu ermöglichen, muss der Zugriff auf das Unterverzeichnis <code>./images</code> so geändert werden, das dieses für den Webserver beschreibbar ist.\nHernach kann diese Option aktiviert werden.",
+ "config-upload-deleted": "Verzeichnis für gelöschte Dateien:",
+ "config-upload-deleted-help": "Bitte ein Verzeichnis auswählen, in dem gelöschte Dateien archiviert werden sollen.\nIdealerweise sollte es nicht über das Internet zugänglich sein.",
+ "config-logo": "URL des Logos:",
+ "config-logo-help": "Die Standardoberfläche von MediaWiki verfügt links oberhalb der Seitenleiste über Platz für ein Logo mit den Maßen 135x160 Pixel.\nBitte ein Logo in entsprechender Größe hochladen und die zugehörige URL an dieser Stelle angeben.\n\nDu kannst <code>$wgStylePath</code> oder <code>$wgScriptPath</code> verwenden, falls dein Logo relativ zu diesen Pfaden ist.\n\nSofern kein Logo benötigt wird, kann dieses Datenfeld leer bleiben.",
+ "config-instantcommons": "„InstantCommons“ aktivieren",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons InstantCommons] ist eine Funktion, die es Wikis ermöglicht, Bild-, Klang- und andere Mediendateien zu nutzen, die auf der Website [//commons.wikimedia.org/ Wikimedia Commons] verfügbar sind.\nUm diese Funktion nutzen zu können, muss das Wiki über eine Verbindung zum Internet verfügen.\n\nWeitere Informationen zu dieser Funktion, einschließlich der Anleitung, wie hierfür andere Wikis als Wikimedia Commons eingerichtet werden können, gibt es im [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos Handbuch].",
+ "config-cc-error": "Der Creativ-Commons-Lizenzassistent konnte keine Lizenz ermitteln.\nDie Lizenz ist daher jetzt manuell einzugeben.",
+ "config-cc-again": "Erneut auswählen …",
+ "config-cc-not-chosen": "Die gewünschte Creative-Commons-Lizenz auswählen und dann auf „weiter“ klicken.",
+ "config-advanced-settings": "Erweiterte Konfiguration",
+ "config-cache-options": "Einstellungen für die Zwischenspeicherung von Objekten:",
+ "config-cache-help": "Das Objektcaching wird dazu genutzt, die Geschwindigkeit von MediaWiki zu verbessern, indem häufig genutzte Daten zwischengespeichert werden.\nEs wird sehr empfohlen, es für mittelgroße bis große Wikis zu nutzen, aber auch für kleine Wikis ergeben sich erkennbare Geschwindigkeitsverbesserungen.",
+ "config-cache-none": "Kein Objektcaching (es wird keine Funktion entfernt, allerdings kann dies die Leistungsfähigkeit größerer Wikis negativ beeinflussen)",
+ "config-cache-accel": "Objektcaching von PHP (APC, XCache oder WinCache)",
+ "config-cache-memcached": "Memcached Cacheserver (erfordert einen zusätzlichen Installationsvorgang mitsamt Konfiguration)",
+ "config-memcached-servers": "Memcached Cacheserver",
+ "config-memcached-help": "Liste der für Memcached nutzbaren IP-Adressen.\nEs sollte eine je Zeile mitsamt des vorgesehenen Ports angegeben werden. Beispiele:\n127.0.0.1:11211 oder\n192.168.1.25:1234 usw.",
+ "config-memcache-needservers": "Memcached wurde als Cacheserver ausgewählt. Dabei wurde allerdings kein Server angegeben.",
+ "config-memcache-badip": "Es wurde für Memcached eine ungültige IP-Adresse angegeben: $1",
+ "config-memcache-noport": "Es wurde kein Port zur Nutzung durch den Memcached Cacheserver angegeben: $1\nSofern der Port unbekannt ist, ist 11211 die Standardangabe.",
+ "config-memcache-badport": "Der Ports für den Memcached Cacheserver sollten zwischen $1 und $2 liegen",
+ "config-extensions": "Erweiterungen",
+ "config-extensions-help": "Die obig angegebenen Erweiterungen wurden im Verzeichnis <code>./extensions</code> gefunden.\n\nEs könnten zusätzliche Konfigurierungen zu einzelnen Erweiterungen erforderlich sein, dennoch können sie aber bereits jetzt aktiviert werden.",
+ "config-skins": "Benutzeroberflächen",
+ "config-skins-help": "Die oben aufgeführten Benutzeroberflächen wurden im Verzeichnis <code>./skins</code> gefunden. Du musst mindestens eine aktivieren und als Standard auswählen.",
+ "config-skins-use-as-default": "Diese Benutzeroberfläche als Standard verwenden",
+ "config-skins-missing": "Es wurden keine Benutzeroberflächen gefunden. MediaWiki wird eine Fallback-Benutzeroberfläche verwenden, bis du andere Benutzeroberflächen installierst.",
+ "config-skins-must-enable-some": "Du musst mindestens eine zu aktivierende Benutzeroberfläche auswählen.",
+ "config-skins-must-enable-default": "Die ausgewählte Standard-Benutzeroberfläche muss aktiviert sein.",
+ "config-install-alreadydone": "'''Warnung:''' Es wurde eine vorhandene MediaWiki-Installation gefunden.\nEs muss daher mit den nächsten Seite weitergemacht werden.",
+ "config-install-begin": "Durch Drücken von „{{int:config-continue}}“ wird die Installation von MediaWiki gestartet.\nSofern Änderungen vorgenommen werden sollen, kann man auf „{{int:config-back}}“ klicken.",
+ "config-install-step-done": "erledigt",
+ "config-install-step-failed": "gescheitert",
+ "config-install-extensions": "Programmerweiterungen",
+ "config-install-database": "Datenbank wird eingerichtet",
+ "config-install-schema": "Datenschema wird erstellt",
+ "config-install-pg-schema-not-exist": "Das PostgesSQL-Datenschema ist nicht vorhanden",
+ "config-install-pg-schema-failed": "Das Erstellen der Datentabellen ist gescheitert.\nEs muss sichergestellt sein, dass der Benutzer „$1“ Schreibzugriff auf das Datenschema „$2“ hat.",
+ "config-install-pg-commit": "Änderungen anwenden",
+ "config-install-pg-plpgsql": "Suche nach der Datenbanksprache PL/pgSQL",
+ "config-pg-no-plpgsql": "Für Datenbank $1 muss die Datenbanksprache PL/pgSQL installiert werden",
+ "config-pg-no-create-privs": "Das für die Installation angegeben Konto verfügt nicht über ausreichende Berechtigungen, um ein Datenbanknutzerkonto zu erstellen.",
+ "config-pg-not-in-role": "Das für den Webbenutzer angegebene Benutzerkonto ist bereits vorhanden.\nDas für den Installationsvorgang angegebene Benutzerkonto ist kein Superbenutzer und nicht Mitglied der Benutzergruppe der Webbenutzer, so dass keine dem Webbenutzer zugeordneten Datenobjekte erstellt werden können.\n\nFür MediaWiki ist es momentan erforderlich, dass die Tabellen dem Webbenutzer rechtemäßig zugeordnet sind. Bitte einen anderen Namen für den Wikibenutzer angeben oder „← Zurück“ anklicken, um einen ausreichend berechtigten Benutzer für den Installationsvorgang anzugeben.",
+ "config-install-user": "Datenbankbenutzer wird erstellt",
+ "config-install-user-alreadyexists": "Datenbankbenutzer „$1“ ist bereits vorhanden",
+ "config-install-user-create-failed": "Das Anlegen des Datenbankbenutzers „$1“ ist gescheitert: $2",
+ "config-install-user-grant-failed": "Die Gewährung der Berechtigung für Datenbankbenutzer „$1“ ist gescheitert: $2",
+ "config-install-user-missing": "Der angegebene Benutzer „$1“ ist nicht vorhanden.",
+ "config-install-user-missing-create": "Der angegebene Benutzer „$1“ ist nicht vorhanden.\nBitte das Auswahlkästchen „Benutzerkonto erstellen“ anklicken, sofern dieser erstellt werden soll.",
+ "config-install-tables": "Datentabellen werden erstellt",
+ "config-install-tables-exist": "'''Warnung:''' Es wurden MediaWiki-Datentabellen gefunden.\nDie Erstellung wurde übersprungen.",
+ "config-install-tables-failed": "'''Fehler:''' Die Erstellung der Datentabellen ist aufgrund des folgenden Fehlers gescheitert: $1",
+ "config-install-interwiki": "Interwikitabellen werden eingerichtet",
+ "config-install-interwiki-list": "Die Datei <code>interwiki.list</code> konnte nicht gelesen werden.",
+ "config-install-interwiki-exists": "'''Warnung:''' Es wurden Interwikitabellen mit Daten gefunden.\nDie Standardliste wird übersprungen.",
+ "config-install-stats": "Statistiken werden initialisiert",
+ "config-install-keys": "Geheimschlüssel werden erstellt",
+ "config-insecure-keys": "'''Warnung:''' {{PLURAL:$2|Der Geheimschlüssel|Die Geheimschlüssel}} $1, {{PLURAL:$2|der|die}} während des Installationsvorgangs generiert {{PLURAL:$2|wurde, ist|wurden, sind}} nicht sehr sicher. {{PLURAL:$2|Er sollte|Sie sollten}} manuell geändert werden.",
+ "config-install-updates": "Unnötige Aktualisierungen nicht ausführen",
+ "config-install-updates-failed": "<strong>Fehler:</strong> Das Einfügen von Aktualisierungsschlüssel in die Tabellen ist mit dem folgenden Fehler fehlgeschlagen: $1",
+ "config-install-sysop": "Administratorkonto wird erstellt",
+ "config-install-subscribe-fail": "Abonnieren von „mediawiki-announce“ ist gescheitert: $1",
+ "config-install-subscribe-notpossible": "cURL ist nicht installiert und <code>allow_url_fopen</code> ist nicht verfügbar.",
+ "config-install-mainpage": "Erstellung der Hauptseite mit Standardinhalten",
+ "config-install-extension-tables": "Erstellung der Tabellen für die aktivierten Erweiterungen",
+ "config-install-mainpage-failed": "Die Hauptseite konnte nicht erstellt werden: $1",
+ "config-install-done": "'''Herzlichen Glückwunsch!'''\nMediaWiki wurde erfolgreich installiert.\n\nDas Installationsprogramm hat die Datei <code>LocalSettings.php</code> erzeugt.\nSie enthält alle vorgenommenen Konfigurationseinstellungen.\n\nDiese Datei muss nun heruntergeladen und anschließend in das Stammverzeichnis der MediaWiki-Installation hochgeladen werden. Dies ist dasselbe Verzeichnis, in dem sich auch die Datei <code>index.php</code> befindet. Das Herunterladen sollte inzwischen automatisch gestartet worden sein.\n\nSofern dies nicht der Fall war, oder das Herunterladen unterbrochen wurde, kann der Vorgang durch einen Klick auf den folgenden Link erneut gestartet werden:\n\n$3\n\n'''Hinweis:''' Die Konfigurationsdatei sollte jetzt unbedingt heruntergeladen werden. Sie wird nach Beenden des Installationsprogramms, nicht mehr zur Verfügung stehen.\n\nSobald alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''. Wir wünschen viel Spaß und Erfolg mit dem Wiki.",
+ "config-download-localsettings": "<code>LocalSettings.php</code> herunterladen",
+ "config-help": "Hilfe",
+ "config-help-tooltip": "Zum Expandieren klicken",
+ "config-nofile": "Die Datei „$1“ konnte nicht gefunden werden. Wurde sie gelöscht?",
+ "config-extension-link": "Wusstest du, dass dein Wiki die Nutzung von [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions Erweiterungen] unterstützt?\n\nDu kannst [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Erweiterungen nach Kategorie] durchsuchen.",
+ "mainpagetext": "'''MediaWiki wurde erfolgreich installiert.'''",
+ "mainpagedocfooter": "Hilfe zur Benutzung und Konfiguration der Wiki-Software findest du im [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].\n\n== Starthilfen ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste der Konfigurationsvariablen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokalisiere MediaWiki für deine Sprache]"
+}
diff --git a/includes/installer/i18n/diq.json b/includes/installer/i18n/diq.json
new file mode 100644
index 00000000..843fe2f3
--- /dev/null
+++ b/includes/installer/i18n/diq.json
@@ -0,0 +1,67 @@
+{
+ "@metadata": {
+ "authors": [
+ "Erdemaslancan",
+ "Mirzali",
+ "Marmase"
+ ]
+ },
+ "config-desc": "Qandé MediaWiki sazi",
+ "config-title": "MediaWiki $1 sazkerdış",
+ "config-information": "Melumat",
+ "config-localsettings-key": "Kesay berzkerdin:",
+ "config-your-language": "Zıwanê şıma:",
+ "config-wiki-language": "Wiki zıwan:",
+ "config-back": "← Peyser",
+ "config-continue": "Dewam ke",
+ "config-page-language": "Zıwan",
+ "config-page-welcome": "Şıma xeyr ameyê MediaWiki!",
+ "config-page-dbconnect": "Database rê grêdey",
+ "config-page-name": "Name",
+ "config-page-options": "Weçinegi",
+ "config-page-install": "Barine",
+ "config-page-complete": "Temamyayo",
+ "config-page-readme": "Mı bıwane",
+ "config-page-copying": "Kopyayeno",
+ "config-page-upgradedoc": "Berzkerdış",
+ "config-restart": "E, fına dest pekê",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki keye]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Şınasiya Karberi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Şınasiya İdarekaran]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Peşti]\n----\n* <doclink href=Readme>Mı buwanê</doclink>\n* <doclink href=ReleaseNotes>Notê elaqeyıni</doclink>\n* <doclink href=Copying>Kopyakerdış</doclink>\n* <doclink href=UpgradeDoc>Zêdekerdış</doclink>",
+ "config-env-php": "PHP $1 i biyo saz.",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 dılet",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-db-port": "Portê database:",
+ "config-header-mysql": "Eyarê MySQL",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "Dılet",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Namey wiki:",
+ "config-site-name-blank": "Yew nameyê sita cıkewe.",
+ "config-project-namespace": "Wareyê nameyê procey:",
+ "config-ns-generic": "Proce",
+ "config-ns-other": "Zewbi (keyfiyo)",
+ "config-ns-other-default": "MyWiki",
+ "config-admin-box": "Hesabê Administratori",
+ "config-admin-name": "Namey karberdé to:",
+ "config-admin-password": "Parola:",
+ "config-admin-password-confirm": "Fına parola:",
+ "config-admin-email": "Adresa e-postey:",
+ "config-profile-wiki": "Wiki Ak",
+ "config-profile-private": "Bexse wiki",
+ "config-license": "Heqa telifi û lisans:",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-pd": "Malê Şari",
+ "config-email-settings": "Sazê e-posta",
+ "config-logo": "URL'ey Logoy:",
+ "config-extensions": "Olekeni",
+ "config-install-step-done": "qeyd ke",
+ "config-install-step-failed": "nêbı",
+ "config-install-schema": "Şema dek",
+ "config-install-pg-commit": "Vırnayışa cemaati",
+ "config-install-tables": "Tabloy dek",
+ "config-help": "peşti",
+ "mainpagetext": "'''MediaWiki vıst ra ser, vıraziya.'''",
+ "mainpagedocfooter": "Qandé ğebtiyayışi u sazkerdeışi Wiki-Softwarey [//meta.wikimedia.org/wiki/Help:Contents İdarê karberi] de mıracaet ke.\n\n== Destega sergendi ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista eyaranê vıraştışi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki de ÇZP]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-dayışê postey]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Zıwandé şıma de Lokal MediaWiki]"
+}
diff --git a/includes/installer/i18n/dsb.json b/includes/installer/i18n/dsb.json
new file mode 100644
index 00000000..0b2e1a8d
--- /dev/null
+++ b/includes/installer/i18n/dsb.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Michawiki"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki jo se wuspěšnje instalěrowało.'''",
+ "mainpagedocfooter": "Pomoc pśi wužywanju softwary wiki namakajoš pód [//meta.wikimedia.org/wiki/Help:Contents User's Guide].\n\nPomoc pśi wužywanju softwary wiki namakajoš pód [//meta.wikimedia.org/wiki/Help:Contents User's Guide].\n\n== Na zachopjenje ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Konfiguracija lisćiny połoženjow]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ (pšašanja a wótegrona)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lisćina e-mailowych nakładow MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki za twóju rěc lokalizěrowaś]"
+}
diff --git a/includes/installer/i18n/dtp.json b/includes/installer/i18n/dtp.json
new file mode 100644
index 00000000..da75bd6a
--- /dev/null
+++ b/includes/installer/i18n/dtp.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "FRANELYA"
+ ]
+ },
+ "mainpagetext": "'''Nopongo no do popodokot ot ModiaWiki.'''",
+ "mainpagedocfooter": "Rujuko hilo [//meta.wikimedia.org/wiki/Help:Contents Ponudukan Momomoguno] kokomoi koilaan do momoguno posusuang-suangon wiki.\n\n== Kopotimpuunan ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lis papatantu nuludan]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Ponguhatan Koinsoruan om Simbar ModiaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis pininsuratan pinolabus do ModiaWiki]"
+}
diff --git a/includes/installer/i18n/el.json b/includes/installer/i18n/el.json
new file mode 100644
index 00000000..325ee986
--- /dev/null
+++ b/includes/installer/i18n/el.json
@@ -0,0 +1,111 @@
+{
+ "@metadata": {
+ "authors": [
+ "Glavkos",
+ "Protnet",
+ "ZaDiak",
+ "Astralnet"
+ ]
+ },
+ "config-desc": "Το πρόγραμμα εγκατάστασης για το MediaWiki",
+ "config-title": "Εγκατάσταση MediaWiki $1",
+ "config-information": "Πληροφορίες",
+ "config-localsettings-key": "Κλειδί αναβάθμισης:",
+ "config-localsettings-badkey": "Το κλειδί που δώσατε είναι εσφαλμένο.",
+ "config-upgrade-key-missing": "Έχει εντοπιστεί μια υπάρχουσα εγκατάσταση του MediaWiki.\nΓια να αναβαθμίσετε αυτήν την εγκατάσταση, παρακαλούμε να βάλετε την ακόλουθη γραμμή στο κάτω μέρος του <code>LocalSettings.php</code> σας:\n\n$1",
+ "config-your-language": "Η γλώσσα σας:",
+ "config-wiki-language": "Γλώσσα του wiki:",
+ "config-back": "← Πίσω",
+ "config-continue": "Συνέχεια →",
+ "config-page-language": "Γλώσσα",
+ "config-page-welcome": "Καλώς ήλθατε στο MediaWiki!",
+ "config-page-dbconnect": "Σύνδεση με βάση δεδομένων",
+ "config-page-upgrade": "Αναβάθμιση υπάρχουσας εγκατάστασης",
+ "config-page-dbsettings": "Ρυθμίσεις της βάσης δεδομένων",
+ "config-page-name": "Όνομα",
+ "config-page-options": "Επιλογές",
+ "config-page-install": "Εγκατάσταση",
+ "config-page-complete": "Ολοκληρώθηκε!",
+ "config-page-restart": "Επανεκκίνηση εγκατάστασης",
+ "config-page-readme": "Διαβάστε με",
+ "config-page-releasenotes": "Σημειώσεις έκδοσης",
+ "config-page-copying": "Αντιγραφή",
+ "config-page-upgradedoc": "Αναβάθμιση",
+ "config-page-existingwiki": "Υπάρχον wiki",
+ "config-help-restart": "Θέλετε να καταργήσετε όλα τα αποθηκευμένα δεδομένα που έχετε εισαγάγει και να επανεκκινήσετε τη διαδικασία εγκατάστασης;",
+ "config-restart": "Ναι, κάντε επανεκκίνηση",
+ "config-env-good": "Το περιβάλλον έχει ελεγχθεί.\nΜπορείτε να εγκαταστήσετε το MediaWiki.",
+ "config-env-bad": "Το περιβάλλον έχει ελεγχθεί.\nΔεν μπορείτε να εγκαταστήσετε το MediaWiki.",
+ "config-env-php": "H PHP $1 είναι εγκατεστημένη.",
+ "config-env-php-toolow": "Η PHP $1 είναι εγκατεστημένη.\nΩστόσο, το MediaWiki απαιτεί την PHP $2 ή μεταγενέστερη έκδοση.",
+ "config-apc": "Το [http://www.php.net/apc APC] είναι εγκατεστημένο",
+ "config-diff3-bad": "Το GNU diff3 δεν βρέθηκε.",
+ "config-db-type": "Τύπος βάσης δεδομένων:",
+ "config-db-host": "Φιλοξενία βάσης δεδομένων:",
+ "config-db-host-oracle": "Βάση δεδομένων TNS:",
+ "config-db-wiki-settings": "Αναγνώριση αυτού του wiki",
+ "config-db-name": "Όνομα βάσης δεδομένων:",
+ "config-db-install-account": "Λογαριασμός χρήστη για την εγκατάσταση",
+ "config-db-username": "Όνομα χρήστη βάσης δεδομένων:",
+ "config-db-password": "Κωδικός πρόσβασης βάσης δεδομένων:",
+ "config-db-wiki-account": "Λογαριασμός χρήστη για κανονική λειτουργία",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 δυαδικό",
+ "config-db-port": "Θύρα βάσης δεδομένων:",
+ "config-header-mysql": "Ρυθμίσεις MySQL",
+ "config-header-postgres": "Ρυθμίσεις PostgreSQL",
+ "config-header-sqlite": "Ρυθμίσεις SQLite",
+ "config-header-oracle": "Ρυθμίσεις Oracle",
+ "config-header-mssql": "Ρυθμίσεις του Microsoft SQL Server",
+ "config-invalid-db-type": "Μη έγκυρος τύπος βάσης δεδομένων",
+ "config-missing-db-name": "Πρέπει να εισαγάγετε μια τιμή για \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Πρέπει να εισαγάγετε μια τιμή για \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Πρέπει να εισαγάγετε μια τιμή για \"{{int:config-db-host-oracle}}\".",
+ "config-connection-error": "$1.\n\nΕλέγξτε τη διεύθυνση, το όνομα χρήστη και τον κωδικό πρόσβασης και προσπαθήστε ξανά.",
+ "config-sqlite-readonly": "Το αρχείο <code>$1</code> δεν είναι εγγράψιμο.",
+ "config-regenerate": "Αναδημιουργία LocalSettings.php →",
+ "config-mysql-engine": "Μηχανή αποθήκευσης:",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Τύπος ελέγχου ταυτότητας:",
+ "config-mssql-windowsauth": "Έλεγχος ταυτότητας των Windows",
+ "config-site-name": "Όνομα του wiki:",
+ "config-site-name-help": "Αυτό θα εμφανίζεται στη γραμμή τίτλου του προγράμματος περιήγησης και σε διάφορα άλλα μέρη.",
+ "config-site-name-blank": "Εισαγάγετε όνομα ιστοχώρου.",
+ "config-project-namespace": "Ονοματοχώρος εγχειρήματος:",
+ "config-ns-generic": "Εγχείρημα",
+ "config-ns-site-name": "Ίδιο με το όνομα του wiki: $1",
+ "config-ns-other": "Άλλο (προσδιορίστε)",
+ "config-admin-box": "Λογαριασμός διαχειριστή",
+ "config-admin-name": "Το όνομα χρήστη σας:",
+ "config-admin-password": "Κωδικός πρόσβασης:",
+ "config-admin-password-confirm": "Επανάληψη κωδικού πρόσβασης:",
+ "config-admin-password-mismatch": "Οι δύο κωδικοί πρόσβασης που εισηγάγατε δεν ταιριάζουν.",
+ "config-admin-email": "Διεύθυνση ηλεκτρονικού ταχυδρομείου:",
+ "config-admin-error-bademail": "Έχετε εισαγάγει μη έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου.",
+ "config-optional-continue": "Να ερωτηθώ περισσότερες ερωτήσεις.",
+ "config-optional-skip": "Βαρέθηκα ήδη, απλά εγκαταστήστε το wiki.",
+ "config-profile": "Προφίλ δικαιωμάτων χρήστη:",
+ "config-profile-wiki": "Ανοικτό wiki",
+ "config-profile-no-anon": "Απαιτείται η δημιουργία λογαριασμού",
+ "config-profile-fishbowl": "Εξουσιοδοτημένοι συντάκτες μόνο",
+ "config-profile-private": "Ιδιωτικό wiki",
+ "config-license-cc-choose": "Επιλέξτε μια προσαρμοσμένη άδεια Creative Commons",
+ "config-email-settings": "Ρυθμίσεις ηλεκτρονικού ταχυδρομείου",
+ "config-email-usertalk": "Ενεργοποίηση ειδοποίησης σελίδας συζήτησης χρήστη",
+ "config-email-auth": "Ενεργοποίηση ταυτοποίησης μέσω ηλεκτρονικού ταχυδρομείου",
+ "config-upload-settings": "Ανέβασμα εικόνων και άλλων αρχείων",
+ "config-upload-enable": "Ενεργοποιήστε το ανέβασμα αρχείων",
+ "config-logo": "Διεύθυνση URL λογότυπου:",
+ "config-cc-again": "Επιλέξτε ξανά...",
+ "config-advanced-settings": "Προηγμένες ρυθμίσεις παραμέτρων",
+ "config-extensions": "Επεκτάσεις",
+ "config-install-step-done": "έγινε",
+ "config-install-step-failed": "απέτυχε",
+ "config-install-user-alreadyexists": "Ο χρήστης \"$1\" υπάρχει ήδη",
+ "config-install-tables": "Γίνεται δημιουργία πινάκων",
+ "config-install-tables-failed": "<strong>Σφάλμα:</strong>Η δημιουργία πινάκων απέτυχε με το ακόλουθο μήνυμα λάθους: $1",
+ "config-install-interwiki": "Γίνεται συμπλήρωση του προεπιλεγμένου πίνακα interwiki",
+ "config-install-interwiki-list": "Αδυναμία ανάγνωσης του αρχείου <code>interwiki.list</code>.",
+ "config-help": "βοήθεια",
+ "mainpagetext": "<strong>To MediaWiki εγκαταστάθηκε με επιτυχία.</strong>",
+ "mainpagedocfooter": "Περισσότερες πληροφορίες σχετικά με τη χρήση και με τη ρύθμιση παραμέτρων θα βρείτε στους συνδέσμους: [//meta.wikimedia.org/wiki/MediaWiki_localisation Οδηγίες για τροποποίηση του περιβάλλοντος εργασίας] και [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Εγχειρίδιο χρήστη]."
+}
diff --git a/includes/installer/i18n/en-gb.json b/includes/installer/i18n/en-gb.json
new file mode 100644
index 00000000..6b9f5901
--- /dev/null
+++ b/includes/installer/i18n/en-gb.json
@@ -0,0 +1,22 @@
+{
+ "@metadata": {
+ "authors": [
+ "Shirayuki"
+ ]
+ },
+ "config-copyright": "=== Copyright and Terms ===\n\n$1\n\nThis program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public Licence as published by the Free Software Foundation; either version 2 of the Licence, or (at your option) any later version.\n\nThis 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'''.\nSee the GNU General Public Licence for more details.\n\nYou should have received <doclink href=Copying>a copy of the GNU General Public Licence</doclink> along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. or [http://www.gnu.org/copyleft/gpl.html read it online].",
+ "config-unicode-using-utf8": "Using Brion Vibber's utf8_normalize.so for Unicode normalisation.",
+ "config-unicode-using-intl": "Using the [http://pecl.php.net/intl intl PECL extension] for Unicode normalisation.",
+ "config-unicode-pure-php-warning": "'''Warning:''' The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalisation, falling back to slow pure-PHP implementation.\nIf you run a high-traffic site, you should read a little on [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalisation].",
+ "config-unicode-update-warning": "'''Warning:''' The installed version of the Unicode normalisation wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.\nYou should [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
+ "config-unknown-collation": "'''Warning:''' Database is using unrecognised collation.",
+ "config-profile-fishbowl": "Authorised editors only",
+ "config-license": "Copyright and licence:",
+ "config-license-none": "No licence footer",
+ "config-license-gfdl": "GNU Free Documentation Licence 1.3 or later",
+ "config-license-cc-choose": "Select a custom Creative Commons licence",
+ "config-license-help": "Many public wikis put all contributions under a [http://freedomdefined.org/Definition free licence].\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 Licence.\nThe GFDL is a valid licence, but it is difficult to understand.\nIt is also difficult to reuse content licenced under the GFDL.",
+ "config-cc-error": "The Creative Commons licence chooser gave no result.\nEnter the licence name manually.",
+ "config-cc-not-chosen": "Choose which Creative Commons licence you want and click \"proceed\".",
+ "config-install-stats": "Initialising statistics"
+}
diff --git a/includes/installer/i18n/en.json b/includes/installer/i18n/en.json
new file mode 100644
index 00000000..1e1c2da7
--- /dev/null
+++ b/includes/installer/i18n/en.json
@@ -0,0 +1,326 @@
+{
+ "@metadata": {
+ "authors": []
+ },
+ "config-desc": "The installer for MediaWiki",
+ "config-title": "MediaWiki $1 installation",
+ "config-information": "Information",
+ "config-localsettings-upgrade": "A <code>LocalSettings.php</code> file has been detected.\nTo upgrade this installation, please enter the value of <code>$wgUpgradeKey</code> in the box below.\nYou will find it in <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "A <code>LocalSettings.php</code> file has been detected.\nTo upgrade this installation, please run <code>update.php</code> instead",
+ "config-localsettings-key": "Upgrade key:",
+ "config-localsettings-badkey": "The key you provided is incorrect.",
+ "config-upgrade-key-missing": "An existing installation of MediaWiki has been detected.\nTo upgrade this installation, please put the following line at the bottom of your <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "The existing <code>LocalSettings.php</code> appears to be incomplete.\nThe $1 variable is not set.\nPlease change <code>LocalSettings.php</code> so that this variable is set, and click \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "An error was encountered when connecting to the database using the settings specified in <code>LocalSettings.php</code>. Please fix these settings and try again.\n\n$1",
+ "config-session-error": "Error starting session: $1",
+ "config-session-expired": "Your session data seems to have expired.\nSessions are configured for a lifetime of $1.\nYou can increase this by setting <code>session.gc_maxlifetime</code> in php.ini.\nRestart the installation process.",
+ "config-no-session": "Your session data was lost!\nCheck your php.ini and make sure <code>session.save_path</code> is set to an appropriate directory.",
+ "config-your-language": "Your language:",
+ "config-your-language-help": "Select a language to use during the installation process.",
+ "config-wiki-language": "Wiki language:",
+ "config-wiki-language-help": "Select the language that the wiki will predominantly be written in.",
+ "config-back": "← Back",
+ "config-continue": "Continue →",
+ "config-page-language": "Language",
+ "config-page-welcome": "Welcome to MediaWiki!",
+ "config-page-dbconnect": "Connect to database",
+ "config-page-upgrade": "Upgrade existing installation",
+ "config-page-dbsettings": "Database settings",
+ "config-page-name": "Name",
+ "config-page-options": "Options",
+ "config-page-install": "Install",
+ "config-page-complete": "Complete!",
+ "config-page-restart": "Restart installation",
+ "config-page-readme": "Read me",
+ "config-page-releasenotes": "Release notes",
+ "config-page-copying": "Copying",
+ "config-page-upgradedoc": "Upgrading",
+ "config-page-existingwiki": "Existing wiki",
+ "config-help-restart": "Do you want to clear all saved data that you have entered and restart the installation process?",
+ "config-restart": "Yes, restart it",
+ "config-welcome": "=== Environmental checks ===\nBasic checks will now be performed to see if this environment is suitable for MediaWiki installation.\nRemember to include this information if you seek support on how to complete the installation.",
+ "config-copyright": "=== Copyright and Terms ===\n\n$1\n\nThis 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.\n\nThis program is distributed in the hope that it will be useful, but <strong>without any warranty</strong>; without even the implied warranty of <strong>merchantability</strong> or <strong>fitness for a particular purpose</strong>.\nSee the GNU General Public License for more details.\n\nYou should have received <doclink href=Copying>a copy of the GNU General Public License</doclink> along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, or [http://www.gnu.org/copyleft/gpl.html read it online].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki home]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administrator's Guide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Release notes</doclink>\n* <doclink href=Copying>Copying</doclink>\n* <doclink href=UpgradeDoc>Upgrading</doclink>",
+ "config-env-good": "The environment has been checked.\nYou can install MediaWiki.",
+ "config-env-bad": "The environment has been checked.\nYou cannot install MediaWiki.",
+ "config-env-php": "PHP $1 is installed.",
+ "config-env-hhvm": "HHVM $1 is installed.",
+ "config-unicode-using-utf8": "Using Brion Vibber's utf8_normalize.so for Unicode normalization.",
+ "config-unicode-using-intl": "Using the [http://pecl.php.net/intl intl PECL extension] for Unicode normalization.",
+ "config-unicode-pure-php-warning": "<strong>Warning:</strong> The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalization, falling back to slow pure-PHP implementation.\nIf you run a high-traffic site, you should read a little on [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].",
+ "config-unicode-update-warning": "<strong>Warning:</strong> The installed version of the Unicode normalization wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.\nYou should [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
+ "config-no-db": "Could not find a suitable database driver! You need to install a database driver for PHP.\nThe following database types are supported: $1.\n\nIf you compiled PHP yourself, reconfigure it with a database client enabled, for example, using <code>./configure --with-mysqli</code>.\nIf you installed PHP from a Debian or Ubuntu package, then you also need to install, for example, the <code>php5-mysql</code> package.",
+ "config-outdated-sqlite": "<strong>Warning:</strong> you have SQLite $1, which is lower than minimum required version $2. SQLite will be unavailable.",
+ "config-no-fts3": "<strong>Warning:</strong> SQLite is compiled without the [//sqlite.org/fts3.html FTS3 module], search features will be unavailable on this backend.",
+ "config-register-globals-error": "<strong>Error: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.\nIt must be disabled to continue with the installation.</strong>\nSee [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] for help on how to do so.",
+ "config-magic-quotes-gpc": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] is active!</strong>\nThis option corrupts data input unpredictably.\nYou cannot install or use MediaWiki unless this option is disabled.",
+ "config-magic-quotes-runtime": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is active!'</strong>\nThis option corrupts data input unpredictably.\nYou cannot install or use MediaWiki unless this option is disabled.",
+ "config-magic-quotes-sybase": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] is active!</strong>\nThis option corrupts data input unpredictably.\nYou cannot install or use MediaWiki unless this option is disabled.",
+ "config-mbstring": "<strong>Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is active!</strong>\nThis option causes errors and may corrupt data unpredictably.\nYou cannot install or use MediaWiki unless this option is disabled.",
+ "config-safe-mode": "<strong>Warning:</strong> PHP's [http://www.php.net/features.safe-mode safe mode] is active.\nIt may cause problems, particularly if using file uploads and <code>math</code> support.",
+ "config-xml-bad": "PHP's XML module is missing.\nMediaWiki requires functions in this module and will not work in this configuration.\nIf you're running Mandrake, install the php-xml package.",
+ "config-pcre-old": "<strong>Fatal:</strong> PCRE $1 or later is required.\nYour PHP binary is linked with PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE More information].",
+ "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-ctype": "<strong>Fatal:</strong> PHP must be compiled with support for the [http://www.php.net/manual/en/ctype.installation.php Ctype extension].",
+ "config-iconv": "<strong>Fatal:</strong> PHP must be compiled with support for the [http://www.php.net/manual/en/iconv.installation.php iconv extension].",
+ "config-json": "<strong>Fatal:</strong> PHP was compiled without JSON support.\nYou must install either the PHP JSON extension or the [http://pecl.php.net/package/jsonc PECL jsonc] extension before installing MediaWiki.\n* The PHP extension is included in Red Hat Enterprise Linux (CentOS) 5 and 6, though must be enabled in <code>/etc/php.ini</code> or <code>/etc/php.d/json.ini</code>.\n* Some Linux distributions released after May 2013 omit the PHP extension, instead packaging the PECL extension as <code>php5-json</code> or <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] is installed",
+ "config-apc": "[http://www.php.net/apc APC] is installed",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] is installed",
+ "config-no-cache": "<strong>Warning:</strong> Could not find [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].\nObject caching is not enabled.",
+ "config-mod-security": "<strong>Warning:</strong> Your web server has [http://modsecurity.org/ mod_security] enabled. If misconfigured, it can cause problems for MediaWiki or other software that allows users to post arbitrary content.\nRefer to [http://modsecurity.org/documentation/ mod_security documentation] or contact your host's support if you encounter random errors.",
+ "config-diff3-bad": "GNU diff3 not found.",
+ "config-git": "Found the Git version control software: <code>$1</code>.",
+ "config-git-bad": "Git version control software not found.",
+ "config-imagemagick": "Found ImageMagick: <code>$1</code>.\nImage thumbnailing will be enabled if you enable uploads.",
+ "config-gd": "Found GD graphics library built-in.\nImage thumbnailing will be enabled if you enable uploads.",
+ "config-no-scaling": "Could not find GD library or ImageMagick.\nImage thumbnailing will be disabled.",
+ "config-no-uri": "<strong>Error:</strong> Could not determine the current URI.\nInstallation aborted.",
+ "config-no-cli-uri": "<strong>Warning:</strong> No <code>--scriptpath</code> specified, using default: <code>$1</code>.",
+ "config-using-server": "Using server name \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Using server URL \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "<strong>Warning:</strong> Your default directory for uploads <code>$1</code> is vulnerable to arbitrary scripts execution.\nAlthough MediaWiki checks all uploaded files for security threats, it is highly recommended to [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security close this security vulnerability] before enabling uploads.",
+ "config-no-cli-uploads-check": "<strong>Warning:</strong> Your default directory for uploads (<code>$1</code>) is not checked for vulnerability\nto arbitrary script execution during the CLI install.",
+ "config-brokenlibxml": "Your system has a combination of PHP and libxml2 versions that is buggy and can cause hidden data corruption in MediaWiki and other web applications.\nUpgrade to libxml2 2.7.3 or later ([https://bugs.php.net/bug.php?id=45996 bug filed with PHP]).\nInstallation aborted.",
+ "config-suhosin-max-value-length": "Suhosin is installed and limits the GET parameter <code>length</code> to $1 bytes.\nMediaWiki's ResourceLoader component will work around this limit, but that will degrade performance.\nIf at all possible, you should set <code>suhosin.get.max_value_length</code> to 1024 or higher in <code>php.ini</code>, and set <code>$wgResourceLoaderMaxQueryLength</code> to the same value in <code>LocalSettings.php</code>.",
+ "config-db-type": "Database type:",
+ "config-db-host": "Database host:",
+ "config-db-host-help": "If your database server is on different server, enter the host name or IP address here.\n\nIf you are using shared web hosting, your hosting provider should give you the correct host name in their documentation.\n\nIf you are installing on a Windows server and using MySQL, using \"localhost\" may not work for the server name. If it does not, try \"127.0.0.1\" for the local IP address.\n\nIf you are using PostgreSQL, leave this field blank to connect via a Unix socket.",
+ "config-db-host-oracle": "Database TNS:",
+ "config-db-host-oracle-help": "Enter a valid [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; a tnsnames.ora file must be visible to this installation.<br />If you are using client libraries 10g or newer you can also use the [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] naming method.",
+ "config-db-wiki-settings": "Identify this wiki",
+ "config-db-name": "Database name:",
+ "config-db-name-help": "Choose a name that identifies your wiki.\nIt should not contain spaces.\n\nIf you are using shared web hosting, your hosting provider will either give you a specific database name to use or let you create databases via a control panel.",
+ "config-db-name-oracle": "Database schema:",
+ "config-db-account-oracle-warn": "There are three supported scenarios for installing Oracle as database backend:\n\nIf you wish to create database account as part of the installation process, please supply an account with SYSDBA role as database account for installation and specify the desired credentials for the web-access account, otherwise you can either create the web-access account manually and supply only that account (if it has required permissions to create the schema objects) or supply two different accounts, one with create privileges and a restricted one for web access.\n\nScript for creating an account with required privileges can be found in \"maintenance/oracle/\" directory of this installation. Keep in mind that using a restricted account will disable all maintenance capabilities with the default account.",
+ "config-db-install-account": "User account for installation",
+ "config-db-username": "Database username:",
+ "config-db-password": "Database password:",
+ "config-db-password-empty": "Please enter a password for the new database user: $1.\nWhile it may be possible to create users with no passwords, it is not secure.",
+ "config-db-username-empty": "You must enter a value for \"{{int:config-db-username}}\".",
+ "config-db-install-username": "Enter the username that will be used to connect to the database during the installation process.\nThis is not the username of the MediaWiki account; this is the username for your database.",
+ "config-db-install-password": "Enter the password that will be used to connect to the database during the installation process.\nThis is not the password for the MediaWiki account; this is the password for your database.",
+ "config-db-install-help": "Enter the username and password that will be used to connect to the database during the installation process.",
+ "config-db-account-lock": "Use the same username and password during normal operation",
+ "config-db-wiki-account": "User account for normal operation",
+ "config-db-wiki-help": "Enter the username and password that will be used to connect to the database during normal wiki operation.\nIf the account does not exist, and the installation account has sufficient privileges, this user account will be created with the minimum privileges required to operate the wiki.",
+ "config-db-prefix": "Database table prefix:",
+ "config-db-prefix-help": "If you need to share one database between multiple wikis, or between MediaWiki and another web application, you may choose to add a prefix to all the table names to avoid conflicts.\nDo not use spaces.\n\nThis field is usually left empty.",
+ "config-db-charset": "Database character set",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 backwards-compatible UTF-8",
+ "config-charset-help": "<strong>Warning:</strong> If you use <strong>backwards-compatible UTF-8</strong> on MySQL 4.1+, and subsequently back up the database with <code>mysqldump</code>, it may destroy all non-ASCII characters, irreversibly corrupting your backups!\n\nIn <strong>binary mode</strong>, MediaWiki stores UTF-8 text to the database in binary fields.\nThis is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters.\nIn <strong>UTF-8 mode</strong>, MySQL will know what character set your data is in, and can present and convert it appropriately,\nbut it will not let you store characters above the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mysql-old": "MySQL $1 or later is required. You have $2.",
+ "config-db-port": "Database port:",
+ "config-db-schema": "Schema for MediaWiki:",
+ "config-db-schema-help": "This schema will usually be fine.\nOnly change it if you know you need to.",
+ "config-pg-test-error": "Cannot connect to database <strong>$1</strong>: $2",
+ "config-sqlite-dir": "SQLite data directory:",
+ "config-sqlite-dir-help": "SQLite stores all data in a single file.\n\nThe directory you provide must be writable by the webserver during installation.\n\nIt should <strong>not</strong> be accessible via the web; this is why we're not putting it where your PHP files are.\n\nThe installer will write a <code>.htaccess</code> file along with it, but if that fails someone can gain access to your raw database.\nThat includes raw user data (email addresses, hashed passwords) as well as deleted revisions and other restricted data on the wiki.\n\nConsider putting the database somewhere else altogether, for example in <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Default tablespace:",
+ "config-oracle-temp-ts": "Temporary tablespace:",
+ "config-type-mysql": "MySQL (or compatible)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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-mysql-url}} MySQL] is the primary target for MediaWiki and is best supported. MediaWiki also works with [{{int:version-db-mariadb-url}} MariaDB] and [{{int:version-db-percona-url}} Percona Server], which are MySQL compatible. ([http://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. There may be some minor outstanding bugs, and it is not recommended for use in a production environment. ([http://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. ([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] is a commercial enterprise database. ([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] is a commercial enterprise database for Windows. ([http://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
+ "config-header-mysql": "MySQL settings",
+ "config-header-postgres": "PostgreSQL settings",
+ "config-header-sqlite": "SQLite settings",
+ "config-header-oracle": "Oracle settings",
+ "config-header-mssql": "Microsoft SQL Server settings",
+ "config-invalid-db-type": "Invalid database type.",
+ "config-missing-db-name": "You must enter a value for \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "You must enter a value for \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "You must enter a value for \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "Invalid database TNS \"$1\".\nUse either \"TNS Name\" or an \"Easy Connect\" string ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]).",
+ "config-invalid-db-name": "Invalid database name \"$1\".\nUse only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and hyphens (-).",
+ "config-invalid-db-prefix": "Invalid database prefix \"$1\".\nUse only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and hyphens (-).",
+ "config-connection-error": "$1.\n\nCheck the host, username and password and try again.",
+ "config-invalid-schema": "Invalid schema for MediaWiki \"$1\".\nUse only ASCII letters (a-z, A-Z), numbers (0-9) and underscores (_).",
+ "config-db-sys-create-oracle": "Installer only supports using a SYSDBA account for creating a new account.",
+ "config-db-sys-user-exists-oracle": "User account \"$1\" already exists. SYSDBA can only be used for creating of a new account!",
+ "config-postgres-old": "PostgreSQL $1 or later is required. You have $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 or later is required. You have $2.",
+ "config-sqlite-name-help": "Choose a name that identifies your wiki.\nDo not use spaces or hyphens.\nThis will be used for the SQLite data file name.",
+ "config-sqlite-parent-unwritable-group": "Cannot create the data directory <code><nowiki>$1</nowiki></code>, because the parent directory <code><nowiki>$2</nowiki></code> is not writable by the webserver.\n\nThe installer has determined the user your webserver is running as.\nMake the <code><nowiki>$3</nowiki></code> directory writable by it to continue.\nOn a Unix/Linux system do:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Cannot create the data directory <code><nowiki>$1</nowiki></code>, because the parent directory <code><nowiki>$2</nowiki></code> is not writable by the webserver.\n\nThe installer could not determine the user your webserver is running as.\nMake the <code><nowiki>$3</nowiki></code> directory globally writable by it (and others!) to continue.\nOn a Unix/Linux system do:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Error creating the data directory \"$1\".\nCheck the location and try again.",
+ "config-sqlite-dir-unwritable": "Unable to write to the directory \"$1\".\nChange its permissions so that the webserver can write to it, and try again.",
+ "config-sqlite-connection-error": "$1.\n\nCheck the data directory and database name below and try again.",
+ "config-sqlite-readonly": "The file <code>$1</code> is not writeable.",
+ "config-sqlite-cant-create-db": "Could not create database file <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP is missing FTS3 support, downgrading tables.",
+ "config-can-upgrade": "There are MediaWiki tables in this database.\nTo upgrade them to MediaWiki $1, click <strong>Continue</strong>.",
+ "config-upgrade-done": "Upgrade complete.\n\nYou can now [$1 start using your wiki].\n\nIf you want to regenerate your <code>LocalSettings.php</code> file, click the button below.\nThis is <strong>not recommended</strong> unless you are having problems with your wiki.",
+ "config-upgrade-done-no-regenerate": "Upgrade complete.\n\nYou can now [$1 start using your wiki].",
+ "config-regenerate": "Regenerate LocalSettings.php →",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code> query failed!",
+ "config-unknown-collation": "<strong>Warning:</strong> Database is using unrecognized collation.",
+ "config-db-web-account": "Database account for web access",
+ "config-db-web-help": "Select the username and password that the web server will use to connect to the database server, during ordinary operation of the wiki.",
+ "config-db-web-account-same": "Use the same account as for installation",
+ "config-db-web-create": "Create the account if it does not already exist",
+ "config-db-web-no-create-privs": "The account you specified for installation does not have enough privileges to create an account.\nThe account you specify here must already exist.",
+ "config-mysql-engine": "Storage engine:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>Warning:</strong> You have selected MyISAM as storage engine for MySQL, which is not recommended for use with MediaWiki, because:\n* it barely supports concurrency due to table locking\n* it is more prone to corruption than other engines\n* the MediaWiki codebase does not always handle MyISAM as it should\n\nIf your MySQL installation supports InnoDB, it is highly recommended that you choose that instead.\nIf your MySQL installation does not support InnoDB, maybe it's time for an upgrade.",
+ "config-mysql-only-myisam-dep": "<strong>Warning:</strong> MyISAM is the only available storage engine for MySQL on this machine, and this is not recommended for use with MediaWiki, because:\n* it barely supports concurrency due to table locking\n* it is more prone to corruption than other engines\n* the MediaWiki codebase does not always handle MyISAM as it should\n\nYour MySQL installation does not support InnoDB, maybe it's time for an upgrade.",
+ "config-mysql-engine-help": "<strong>InnoDB</strong> is almost always the best option, since it has good concurrency support.\n\n<strong>MyISAM</strong> may be faster in single-user or read-only installations.\nMyISAM databases tend to get corrupted more often than InnoDB databases.",
+ "config-mysql-charset": "Database character set:",
+ "config-mysql-binary": "Binary",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "In <strong>binary mode</strong>, MediaWiki stores UTF-8 text to the database in binary fields.\nThis is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters.\n\nIn <strong>UTF-8 mode</strong>, MySQL will know what character set your data is in, and can present and convert it appropriately, but it will not let you store characters above the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mssql-auth": "Authentication type:",
+ "config-mssql-install-auth": "Select the authentication type that will be used to connect to the database during the installation process.\nIf you select \"{{int:config-mssql-windowsauth}}\", the credentials of whatever user the webserver is running as will be used.",
+ "config-mssql-web-auth": "Select the authentication type that the web server will use to connect to the database server, during ordinary operation of the wiki.\nIf you select \"{{int:config-mssql-windowsauth}}\", the credentials of whatever user the webserver is running as will be used.",
+ "config-mssql-sqlauth": "SQL Server Authentication",
+ "config-mssql-windowsauth": "Windows Authentication",
+ "config-site-name": "Name of wiki:",
+ "config-site-name-help": "This will appear in the title bar of the browser and in various other places.",
+ "config-site-name-blank": "Enter a site name.",
+ "config-project-namespace": "Project namespace:",
+ "config-ns-generic": "Project",
+ "config-ns-site-name": "Same as the wiki name: $1",
+ "config-ns-other": "Other (specify)",
+ "config-ns-other-default": "MyWiki",
+ "config-project-namespace-help": "Following Wikipedia's example, many wikis keep their policy pages separate from their content pages, in a \"'''project namespace'''\".\nAll page titles in this namespace start with a certain prefix, which you can specify here.\nUsually, this prefix is derived from the name of the wiki, but it cannot contain punctuation characters such as \"#\" or \":\".",
+ "config-ns-invalid": "The specified namespace \"<nowiki>$1</nowiki>\" is invalid.\nSpecify a different project namespace.",
+ "config-ns-conflict": "The specified namespace \"<nowiki>$1</nowiki>\" conflicts with a default MediaWiki namespace.\nSpecify a different project namespace.",
+ "config-admin-box": "Administrator account",
+ "config-admin-name": "Your username:",
+ "config-admin-password": "Password:",
+ "config-admin-password-confirm": "Password again:",
+ "config-admin-help": "Enter your preferred username here, for example \"Joe Bloggs\".\nThis is the name you will use to log in to the wiki.",
+ "config-admin-name-blank": "Enter an administrator username.",
+ "config-admin-name-invalid": "The specified username \"<nowiki>$1</nowiki>\" is invalid.\nSpecify a different username.",
+ "config-admin-password-blank": "Enter a password for the administrator account.",
+ "config-admin-password-mismatch": "The two passwords you entered do not match.",
+ "config-admin-email": "Email address:",
+ "config-admin-email-help": "Enter an email address here to allow you to receive email from other users on the wiki, reset your password, and be notified of changes to pages on your watchlist. You can leave this field empty.",
+ "config-admin-error-user": "Internal error when creating an admin with the name \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Internal error when setting a password for the admin \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "You have entered an invalid email address.",
+ "config-subscribe": "Subscribe to the [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release announcements mailing list].",
+ "config-subscribe-help": "This is a low-volume mailing list used for release announcements, including important security announcements.\nYou should subscribe to it and update your MediaWiki installation when new versions come out.",
+ "config-subscribe-noemail": "You tried to subscribe to the release announcements mailing list without providing an email address.\nPlease provide an email address if you wish to subscribe to the mailing list.",
+ "config-almost-done": "You are almost done!\nYou can now skip the remaining configuration and install the wiki right now.",
+ "config-optional-continue": "Ask me more questions.",
+ "config-optional-skip": "I'm bored already, just install the wiki.",
+ "config-profile": "User rights profile:",
+ "config-profile-wiki": "Open wiki",
+ "config-profile-no-anon": "Account creation required",
+ "config-profile-fishbowl": "Authorized editors only",
+ "config-profile-private": "Private wiki",
+ "config-profile-help": "Wikis work best when you let as many people edit them as possible.\nIn MediaWiki, it is easy to review the recent changes, and to revert any damage that is done by naive or malicious users.\n\nHowever, many have found MediaWiki to be useful in a wide variety of roles, and sometimes it is not easy to convince everyone of the benefits of the wiki way.\nSo you have the choice.\n\nThe <strong>{{int:config-profile-wiki}}</strong> model allows anyone to edit, without even logging in.\nA wiki with <strong>{{int:config-profile-no-anon}}</strong> provides extra accountability, but may deter casual contributors.\n\nThe <strong>{{int:config-profile-fishbowl}}</strong> scenario allows approved users to edit, but the public can view the pages, including history.\nA <strong>{{int:config-profile-private}}</strong> only allows approved users to view pages, with the same group allowed to edit.\n\nMore complex user rights configurations are available after installation, see the [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights relevant manual entry].",
+ "config-license": "Copyright and license:",
+ "config-license-none": "No license footer",
+ "config-license-cc-by-sa": "Creative Commons Attribution-ShareAlike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution-NonCommercial-ShareAlike",
+ "config-license-cc-0": "Creative Commons Zero (Public Domain)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 or later",
+ "config-license-pd": "Public Domain",
+ "config-license-cc-choose": "Select a custom Creative Commons license",
+ "config-license-help": "Many public wikis put all contributions under a [http://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, [http://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",
+ "config-email-usertalk-help": "Allow users to receive notifications on user talk page changes, if they have enabled it in their preferences.",
+ "config-email-watchlist": "Enable watchlist notification",
+ "config-email-watchlist-help": "Allow users to receive notifications about their watched pages if they have enabled it in their preferences.",
+ "config-email-auth": "Enable email authentication",
+ "config-email-auth-help": "If this option is enabled, users have to confirm their email address using a link sent to them whenever they set or change it.\nOnly authenticated email addresses can receive emails from other users or change notification emails.\nSetting this option is <strong>recommended</strong> for public wikis because of potential abuse of the email features.",
+ "config-email-sender": "Return email address:",
+ "config-email-sender-help": "Enter the email address to use as the return address on outbound email.\nThis is where bounces will be sent.\nMany mail servers require at least the domain name part to be valid.",
+ "config-upload-settings": "Images and file uploads",
+ "config-upload-enable": "Enable file uploads",
+ "config-upload-help": "File uploads potentially expose your server to security risks.\nFor more information, read the [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security security section] in the manual.\n\nTo enable file uploads, change the mode on the <code>images</code> subdirectory under MediaWiki's root directory so that the web server can write to it.\nThen enable this option.",
+ "config-upload-deleted": "Directory for deleted files:",
+ "config-upload-deleted-help": "Choose a directory in which to archive deleted files.\nIdeally, this should not be accessible from the web.",
+ "config-logo": "Logo URL:",
+ "config-logo-help": "MediaWiki's default skin includes space for a 135x160 pixel logo above the sidebar menu.\nUpload an image of the appropriate size, and enter the URL here.\n\nYou can use <code>$wgStylePath</code> or <code>$wgScriptPath</code> if your logo is relative to those paths.\n\nIf you do not want a logo, leave this box blank.",
+ "config-instantcommons": "Enable Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] is a feature that allows wikis to use images, sounds and other media found on the [//commons.wikimedia.org/ Wikimedia Commons] site.\nIn order to do this, MediaWiki requires access to the Internet.\n\nFor more information on this feature, including instructions on how to set it up for wikis other than the Wikimedia Commons, consult [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos the manual].",
+ "config-cc-error": "The Creative Commons license chooser gave no result.\nEnter the license name manually.",
+ "config-cc-again": "Pick again...",
+ "config-cc-not-chosen": "Choose which Creative Commons license you want and click \"proceed\".",
+ "config-advanced-settings": "Advanced configuration",
+ "config-cache-options": "Settings for object caching:",
+ "config-cache-help": "Object caching is used to improve the speed of MediaWiki by caching frequently used data.\nMedium to large sites are highly encouraged to enable this, and small sites will see benefits as well.",
+ "config-cache-none": "No caching (no functionality is removed, but speed may be impacted on larger wiki sites)",
+ "config-cache-accel": "PHP object caching (APC, XCache or WinCache)",
+ "config-cache-memcached": "Use Memcached (requires additional setup and configuration)",
+ "config-memcached-servers": "Memcached servers:",
+ "config-memcached-help": "List of IP addresses to use for Memcached.\nShould specify one per line and specify the port to be used. For example:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "You selected Memcached as your cache type but did not specify any servers.",
+ "config-memcache-badip": "You have entered an invalid IP address for Memcached: $1.",
+ "config-memcache-noport": "You did not specify a port to use for Memcached server: $1.\nIf you do not know the port, the default is 11211.",
+ "config-memcache-badport": "Memcached port numbers should be between $1 and $2.",
+ "config-extensions": "Extensions",
+ "config-extensions-help": "The extensions listed above were detected in your <code>./extensions</code> directory.\n\nThey may require additional configuration, but you can enable them now.",
+ "config-skins": "Skins",
+ "config-skins-help": "The skins listed above were detected in your <code>./skins</code> directory. You must enable at least one, and choose the default.",
+ "config-skins-use-as-default": "Use this skin as default",
+ "config-skins-missing": "No skins were found; MediaWiki will use a fallback skin until you install some proper ones.",
+ "config-skins-must-enable-some": "You must choose at least one skin to enable.",
+ "config-skins-must-enable-default": "The skin chosen as default must be enabled.",
+ "config-install-alreadydone": "<strong>Warning:</strong> You seem to have already installed MediaWiki and are trying to install it again.\nPlease proceed to the next page.",
+ "config-install-begin": "By pressing \"{{int:config-continue}}\", you will begin the installation of MediaWiki.\nIf you still want to make changes, press \"{{int:config-back}}\".",
+ "config-install-step-done": "done",
+ "config-install-step-failed": "failed",
+ "config-install-extensions": "Including extensions",
+ "config-install-database": "Setting up database",
+ "config-install-schema": "Creating schema",
+ "config-install-pg-schema-not-exist": "PostgreSQL schema does not exist.",
+ "config-install-pg-schema-failed": "Tables creation failed.\nMake sure that the user \"$1\" can write to the schema \"$2\".",
+ "config-install-pg-commit": "Committing changes",
+ "config-install-pg-plpgsql": "Checking for language PL/pgSQL",
+ "config-pg-no-plpgsql": "You need to install the language PL/pgSQL in the database $1",
+ "config-pg-no-create-privs": "The account you specified for installation does not have enough privileges to create an account.",
+ "config-pg-not-in-role": "The account you specified for the web user already exists.\nThe account you specified for installation is not a superuser and is not a member of the web user's role, so it is unable to create objects owned by the web user.\n\nMediaWiki currently requires that the tables be owned by the web user. Please specify another web account name, or click \"back\" and specify a suitably privileged install user.",
+ "config-install-user": "Creating database user",
+ "config-install-user-alreadyexists": "User \"$1\" already exists",
+ "config-install-user-create-failed": "Creating user \"$1\" failed: $2",
+ "config-install-user-grant-failed": "Granting permission to user \"$1\" failed: $2",
+ "config-install-user-missing": "The specified user \"$1\" does not exist.",
+ "config-install-user-missing-create": "The specified user \"$1\" does not exist.\nPlease click the \"create account\" checkbox below if you want to create it.",
+ "config-install-tables": "Creating tables",
+ "config-install-tables-exist": "<strong>Warning:</strong> MediaWiki tables seem to already exist.\nSkipping creation.",
+ "config-install-tables-failed": "<strong>Error:</strong> Table creation failed with the following error: $1",
+ "config-install-interwiki": "Populating default interwiki table",
+ "config-install-interwiki-list": "Could not read file <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "<strong>Warning:</strong> The interwiki table seems to already have entries.\nSkipping default list.",
+ "config-install-stats": "Initializing statistics",
+ "config-install-keys": "Generating secret keys",
+ "config-insecure-keys": "<strong>Warning:</strong> {{PLURAL:$2|A secure key|Secure keys}} ($1) generated during installation {{PLURAL:$2|is|are}} not completely safe. Consider changing {{PLURAL:$2|it|them}} manually.",
+ "config-install-updates": "Prevent running unneeded updates",
+ "config-install-updates-failed": "<strong>Error:</strong> Inserting update keys into tables failed with the following error: $1",
+ "config-install-sysop": "Creating administrator user account",
+ "config-install-subscribe-fail": "Unable to subscribe to mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL is not installed and <code>allow_url_fopen</code> is not available.",
+ "config-install-mainpage": "Creating main page with default content",
+ "config-install-extension-tables": "Creating tables for enabled extensions",
+ "config-install-mainpage-failed": "Could not insert main page: $1",
+ "config-install-done": "<strong>Congratulations!</strong>\nYou have successfully installed MediaWiki.\n\nThe installer has generated a <code>LocalSettings.php</code> file.\nIt contains all your configuration.\n\nYou will need to download it and put it in the base of your wiki installation (the same directory as index.php). The download should have started automatically.\n\nIf the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:\n\n$3\n\n<strong>Note:</strong> If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.\n\nWhen that has been done, you can <strong>[$2 enter your wiki]</strong>.",
+ "config-download-localsettings": "Download <code>LocalSettings.php</code>",
+ "config-help": "help",
+ "config-help-tooltip": "click to expand",
+ "config-nofile": "File \"$1\" could not be found. Has it been deleted?",
+ "config-extension-link": "Did you know that your wiki supports [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensions]?\n\nYou can browse [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions by category] or the [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] to see the full list of extensions.",
+ "mainpagetext": "<strong>MediaWiki has been successfully installed.</strong>",
+ "mainpagedocfooter": "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/eo.json b/includes/installer/i18n/eo.json
new file mode 100644
index 00000000..250816a3
--- /dev/null
+++ b/includes/installer/i18n/eo.json
@@ -0,0 +1,50 @@
+{
+ "@metadata": {
+ "authors": [
+ "Airon90",
+ "Yekrats",
+ "KuboF"
+ ]
+ },
+ "config-desc": "Instalilo de MediaWiki",
+ "config-title": "Instalado de MediaWiki $1",
+ "config-information": "Informo",
+ "config-localsettings-badkey": "Provizita ŝlosilo estas malĝusta.",
+ "config-your-language": "Via lingvo:",
+ "config-your-language-help": "Elekti lingvon uzi dum la instalada procezo.",
+ "config-wiki-language": "Lingvo de la vikio:",
+ "config-wiki-language-help": "Elekti la ĉefe skribotan lingvon de la vikio.",
+ "config-back": "← Reen",
+ "config-continue": "Daŭrigi →",
+ "config-page-language": "Lingvo",
+ "config-page-welcome": "Bonvenon al MediaWiki!",
+ "config-page-dbconnect": "Konekto al datumbazo",
+ "config-page-dbsettings": "Agordoj de la datumbazo",
+ "config-page-name": "Nomo",
+ "config-page-options": "Agordoj",
+ "config-page-install": "Instali",
+ "config-page-complete": "Farita!",
+ "config-page-restart": "Restarti instaladon",
+ "config-page-readme": "Legu min",
+ "config-page-releasenotes": "Eldonaj notoj",
+ "config-page-existingwiki": "Ekzistanta vikio",
+ "config-restart": "Jes, restarti ĝin",
+ "config-env-good": "La medio estis kontrolita.\nVi povas instali MediaWiki.",
+ "config-env-bad": "La medio estis kontrolita.\nNe eblas instali MediaWiki.",
+ "config-env-php": "PHP $1 estas instalita.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] estas instalita.",
+ "config-apc": "[http://www.php.net/apc APC] estas instalita",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] estas instalita",
+ "config-diff3-bad": "GNU diff3 ne estis trovita.",
+ "config-db-type": "Tipo de datumbazo:",
+ "config-db-wiki-settings": "Identigu ĉi tiun vikion",
+ "config-db-name": "Nomo de datumbazo:",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-type-mysql": "MySQL (aŭ kongrua)",
+ "config-admin-password": "Pasvorto:",
+ "config-admin-password-confirm": "Retajpu pasvorton:",
+ "config-admin-name-blank": "Enigu salutnomon de administranto.",
+ "config-admin-email": "Retpoŝtadreso:",
+ "mainpagetext": "'''MediaWiki estis sukcese instalita.'''",
+ "mainpagedocfooter": "Konsultu la [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Gvidilon por uzantoj de MediaWiki] por informoj pri uzado de vikia programaro.\n\n==Kiel komenci==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Listo de konfiguraĵoj] (angle)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki Oftaj Demandoj] (angle)\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Anonco-dissendolisto pri MediaWiki] (angle)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Preklad MediaWiki do tvojho jazyka]"
+}
diff --git a/includes/installer/i18n/es-formal.json b/includes/installer/i18n/es-formal.json
new file mode 100644
index 00000000..3e8c7e59
--- /dev/null
+++ b/includes/installer/i18n/es-formal.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dferg",
+ "Seb35"
+ ]
+ },
+ "mainpagedocfooter": "Consulte usted la [//meta.wikimedia.org/wiki/Help:Contents/es Guía de usuario] para obtener información sobre el uso del software wiki.\n\n== Empezando ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de ajustes de configuración]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/es FAQ de MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de distribución de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Regionalizar MediaWiki para su idioma]"
+}
diff --git a/includes/installer/i18n/es.json b/includes/installer/i18n/es.json
new file mode 100644
index 00000000..ee1a2f46
--- /dev/null
+++ b/includes/installer/i18n/es.json
@@ -0,0 +1,352 @@
+{
+ "@metadata": {
+ "authors": [
+ "Armando-Martin",
+ "Ciencia Al Poder",
+ "Crazymadlover",
+ "Danke7",
+ "Fitoschido",
+ "Locos epraix",
+ "Od1n",
+ "Platonides",
+ "Sanbec",
+ "Translationista",
+ "Vivaelcelta",
+ "아라",
+ "Amire80",
+ "Benfutbol10",
+ "Carlitosag",
+ "Chocolate con galleta",
+ "Csbotero",
+ "Sporeunai",
+ "Ihojose",
+ "Seb35",
+ "McDutchie",
+ "Miguel2706",
+ "Macofe",
+ "AVIADOR",
+ "FuzzyDice"
+ ]
+ },
+ "config-desc": "El instalador de MediaWiki",
+ "config-title": "Instalación de MediaWiki $1",
+ "config-information": "Información",
+ "config-localsettings-upgrade": "Se ha encontrado un archivo <code>LocalSettings.php</code>.\nPara actualizar esta instalación, por favor ingresa el valor de <code>$wgUpgradeKey</code> en el cuadro de abajo.\nLo encontrarás en <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Se ha detectado un archivo <code>LocalSettings.php</code>.\nPara actualizar la instalación, vuelva a ejecutar <code>update.php</code>",
+ "config-localsettings-key": "Clave de actualización:",
+ "config-localsettings-badkey": "La clave proporcionada es incorrecta.",
+ "config-upgrade-key-missing": "Se ha detectado una instalación existente de MediaWiki.\nPara actualizar la instalación, por favor, ponga la siguiente línea al final de su archivo <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "El archivo <code>LocalSettings.php</code> existente parece estar incompleto.\nLa variable $1 no está definida.\nCambie el archivo <code>LocalSettings.php</code> para que esta variable quede establecida y haga clic en \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Se ha producido un error al conectar a la base de datos utilizando la configuración especificada en <code>LocalSettings.php</code> . Por favor arreglar estos ajustes e inténtelo de nuevo.\n\n$1",
+ "config-session-error": "Error al iniciar la sesión: $1",
+ "config-session-expired": "Tus datos de sesión parecen haber expirado.\nLas sesiones están configuradas por una duración de $1.\nPuedes incrementar esto configurando <code>session.gc_maxlifetime</code> en php.ini.\nReiniciar el proceso de instalación.",
+ "config-no-session": "Se han perdido los datos de sesión.\nVerifica tu php.ini y comprueba que <code>session.save_path</code> está establecido en un directorio apropiado.",
+ "config-your-language": "Tu idioma:",
+ "config-your-language-help": "Selecciona un idioma para usar durante el proceso de instalación.",
+ "config-wiki-language": "Idioma del wiki:",
+ "config-wiki-language-help": "Selecciona el idioma en el que se escribirá predominantemente el wiki.",
+ "config-back": "← Atrás",
+ "config-continue": "Continuar →",
+ "config-page-language": "Idioma",
+ "config-page-welcome": "Te damos la bienvenida a MediaWiki.",
+ "config-page-dbconnect": "Conectar a la base de datos",
+ "config-page-upgrade": "Actualizar instalación existente",
+ "config-page-dbsettings": "Configuración de la base de datos",
+ "config-page-name": "Nombre",
+ "config-page-options": "Opciones",
+ "config-page-install": "Instalar",
+ "config-page-complete": "Hecho.",
+ "config-page-restart": "Reiniciar instalación",
+ "config-page-readme": "Léeme",
+ "config-page-releasenotes": "Notas de la versión",
+ "config-page-copying": "Copiando",
+ "config-page-upgradedoc": "Actualizando",
+ "config-page-existingwiki": "Wiki existente",
+ "config-help-restart": "¿Deseas borrar todos los datos que has ingresado hasta ahora y reiniciar el proceso de instalación desde el principio?",
+ "config-restart": "Sí, reiniciarlo",
+ "config-welcome": "=== Comprobación del entorno ===\nAhora se van a realizar comprobaciones básicas para ver si el entorno es adecuado para la instalación de MediaWiki.\nRecuerda suministrar los resultados de tales comprobaciones si necesitas ayuda para completar la instalación.",
+ "config-copyright": "=== Derechos de autor y Términos de uso ===\n\n$1\n\nEste programa es software libre; puedes redistribuirlo y/o modificarlo en los términos de la Licencia Pública General de GNU, tal como aparece publicada por la Fundación para el Software Libre, tanto la versión 2 de la Licencia, como cualquier versión posterior (según prefiera).\n\nEste programa es distribuido en la esperanza de que sea útil, pero '''sin cualquier garantía'''; inclusive, sin la garantía implícita de la '''posibilidad de ser comercializado''' o de '''idoneidad para cualquier finalidad específica'''.\nConsulte la licencia *GNU General *Public *License para más detalles.\n\nEn conjunto con este programa debe haber recibido <doclink href=Copying>una copia de la Licencia Pública General de GNU</doclink>; si no la recibió, pídala por escrito a Fundación para el Software Libre, Inc., 51 Franklin Street, Fifth Floor, Boston, ME La 02110-1301, USA o [http://www.gnu.org/copyleft/gpl.html léala en internet].",
+ "config-sidebar": "* [//www.mediawiki.org Página principal de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guía del usuario]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guía del administrador]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Preguntas frecuentes]\n----\n* <doclink href=Readme>Léeme</doclink>\n* <doclink href=ReleaseNotes>Notas de la versión</doclink>\n* <doclink href=Copying>Copia</doclink>\n* <doclink href=UpgradeDoc>Actualización</doclink>",
+ "config-env-good": "El entorno ha sido comprobado.\nPuedes instalar MediaWiki.",
+ "config-env-bad": "El entorno ha sido comprobado.\nNo puedes instalar MediaWiki.",
+ "config-env-php": "PHP $1 está instalado.",
+ "config-env-hhvm": "HHVM $1 está instalado.",
+ "config-unicode-using-utf8": "Usando utf8_normalize.so de Brion Vibber para la normalización Unicode.",
+ "config-unicode-using-intl": "Usando la [http://pecl.php.net/intl extensión intl PECL] para la normalización Unicode.",
+ "config-unicode-pure-php-warning": "'''Advertencia''': La [http://pecl.php.net/intl extensión intl] no está disponible para efectuar la normalización Unicode. Utilizando la implementación más lenta en PHP.\nSi tu web tiene mucho tráfico, te recomendamos leer acerca de la [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].",
+ "config-unicode-update-warning": "'''Warning''': La versión instalada del contenedor de normalización Unicode usa una versión anterior de la biblioteca del [http://site.icu-project.org/ proyecto ICU].\nDeberás [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations actualizar] si realmente deseas usar Unicode.",
+ "config-no-db": "¡No fue posible encontrar un controlador adecuado para la base de datos! Necesitas instalar un controlador de base de datos para PHP.\nLos siguientes sistemas gestores de bases de datos están soportados: $1.\n\nSi compilaste PHP tú mismo, debes reconfigurarlo habilitando un cliente de base de datos, por ejemplo, usando <code>./configure --with-mysqli</code>.\nSi instalaste PHP desde un paquete Debian o Ubuntu, entonces también necesitas instalar, por ejemplo, el paquete <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "''' Advertencia ''': tiene la versión SQLite $1, que es inferior a la mínima versión requerida: $2 . SQLite no estará disponible.",
+ "config-no-fts3": "'''Advertencia''': SQLite está compilado sin el [//sqlite.org/fts3.html módulo FTS3]. Las funcionalidades de búsqueda no estarán disponibles en esta instalación.",
+ "config-register-globals-error": "<strong>Error: la opción de PHP <code>[http://php.net/register_globals register_globals]</code> está activada.\nDebe estar desactivada para continuar con la instalación.</strong>\nVea [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] para obtener ayuda sobre cómo hacerlo.",
+ "config-magic-quotes-gpc": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] está activa!</strong>\nEsta opción corrompe la entrada de datos de forma impredecible.\nUsted no puede instalar o utilizar MediaWiki a menos que esta opción esté deshabilitada.",
+ "config-magic-quotes-runtime": "'''Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está activada!'''\nEsta opción causa la imprevisible corrupción de la entrada de datos.\nNo puedes instalar o utilizar MediaWiki a menos que esta opción esté inhabilitada.",
+ "config-magic-quotes-sybase": "'''Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está activada!'''\nEsta opción causa la imprevisible corrupción de la entrada de datos.\nNo puedes instalar o utilizar MediaWiki a menos que esta opción esté inhabilitada.",
+ "config-mbstring": "'''Fatal: La opción [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está activada!'''\nEsta opción causa errores y puede corromper los datos de una forma imprevisible.\nNo se puede instalar o usar MediaWiki a menos que esta opción sea desactivada.",
+ "config-safe-mode": "'''Advertencia:''' El [http://www.php.net/features.safe-mode modo seguro] de PHP está activado.\nEste modo puede causar problemas, especialmente en la carga de archivosy en compatibilidad con <code>math</code>.",
+ "config-xml-bad": "Falta el módulo XML de PHP.\nMediaWiki necesita funciones en este módulo y no funcionará con esta configuración.\nSi está ejecutando Mandrake, instale el paquete php-xml.",
+ "config-pcre-old": "'''Fatal:''' Se requiere PCRE $1 o posterior.\nSu PHP binario está enlazado con PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Más información].",
+ "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, aumentada a $2.",
+ "config-memory-bad": "'''Advertencia:''' El parámetro <code>memory_limit</code> de PHP es $1.\nProbablemente este valor es demasiado bajo.\n¡La instalación podrá fallar!",
+ "config-ctype": "'''Fatal''': Se necesita compilar PHP con compatibilidad para la [http://www.php.net/manual/en/ctype.installation.php extensión Ctype].",
+ "config-iconv": "<strong>Fatal:</strong> PHP debe ser compilado con soporte para la [http://www.php.net/manual/en/iconv.installation.php extensión iconv].",
+ "config-json": "'''Fatal:''' PHP fue compilado sin soporte para JSON.\nDebes instalar la extensión JSON o la extensión [http://pecl.php.net/package/jsonc PECL jsonc] antes de instalar MediaWiki.\n* La extensión PHP se incluye en Red Hat Enterprise Linux (CentOS) 5 y 6, aunque debe habilitarse en <code>/etc/php.ini</code> o <code>/etc/php.d/json.ini</code>.\n* Algunas distribuciones Linux liberadas después de mayo del 2013 omiten la extensión PHP, y en su lugar disponen de la extensión PECL en el paquete <code>php5-json</code> o <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] está instalado",
+ "config-apc": "[http://www.php.net/apc APC] está instalado",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] está instalado",
+ "config-no-cache": "'''Advertencia:''' No pudo encontrarse [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nEl caché de objetos no está habilitado.",
+ "config-mod-security": "''' Advertencia ''': Su servidor web tiene [http://modsecurity.org/ mod_security] habilitado. Si la configuración es incorrecta, puede causar problemas a MediaWiki u otro software que permita a los usuarios publicar contenido arbitrarios.\nConsulte la [http://modsecurity.org/documentation/ documentación de mod_security] o contacte con el soporte de su servidor (''host'') si encuentra 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-git-bad": "No se encontró el software de control de versiones Git.",
+ "config-imagemagick": "ImageMagick encontrado: <code>$1</code>.\nLa miniaturización de imágenes se habilitará si habilitas las cargas.",
+ "config-gd": "Se ha encontrado una biblioteca de gráficos GD integrada.\nLa miniaturización de imágenes se habilitará si habilitas las subidas.",
+ "config-no-scaling": "No se ha encontrado ninguma biblioteca GD o ImageMagik.\nSe inhabilitará la miniaturización de imágenes.",
+ "config-no-uri": "<strong>Error:</strong> no se pudo determinar el URI actual.\nSe interrumpió la instalación.",
+ "config-no-cli-uri": "<strong>Aviso:</strong> No se especificó <code>--scriptpath</code>; se usa el valor predeterminado: <code>$1</code>.",
+ "config-using-server": "Utilizando el nombre de servidor \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Utilizando la URL del servidor \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Atención:''' Su directorio por defecto para las cargas, <code>$1</code>, es vulnerable a la ejecución de scripts arbitrarios.\nAunque MediaWiki comprueba todos los archivos cargados por si hubiese amenazas de seguridad, es altamente recomendable [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security cerrar esta vulnerabilidad de seguridad] antes de activar las cargas.",
+ "config-no-cli-uploads-check": "'''Atención:''' Su directorio predeterminado para cargas (<code>$1</code>) no está comprobado para la vulnerabilidad\n de ejecución arbitraria de comandos script durante la instalación de CLI.",
+ "config-brokenlibxml": "El sistema tiene una combinación de versiones de PHP y de libxml2 que es poco confiable y puede provocar corrupción oculta en los datos de MediaWiki y otras aplicaciones web.\nActualizar a PHP 5.2.9 o posterior y a libxml2 2.7.3 o posterior ([//bugs.php.net/bug.php?id=45996 bug reportado con PHP]).\nInstalación abortada.",
+ "config-suhosin-max-value-length": "Suhosin está instalado y limita el parámetro <code>length</code> GET a $1 bytes.\nEl componente ResourceLoader (gestor de recursos) de MediaWiki trabajará en este límite, pero eso perjudicará el rendimiento.\nSi es posible, deberías establecer <code>suhosin.get.max_value_length</code> en el valor 1024 o superior en <code>php.ini</code> y establecer <code>$wgResourceLoaderMaxQueryLength</code> en el mismo valor en <code>php.ini</code>.",
+ "config-db-type": "Tipo de base de datos:",
+ "config-db-host": "Servidor de la base de datos:",
+ "config-db-host-help": "Si su servidor de base de datos está en otro servidor, escriba el nombre del host o su dirección IP aquí.\nSi está utilizando alojamiento web compartido, su proveedor de alojamiento debería darle el nombre correcto del servidor de alojamiento (host) en su documentación.\nSi va a instalarlo en un servidor Windows y utiliza MySQL, el uso de \"localhost\" como nombre del servidor puede no funcionar. Si no es así, intente poner \"127.0.0.1\" como dirección IP local.\nSi utiliza PostgreSQL, deje este campo en blanco para conectarse a través de un socket de Unix.",
+ "config-db-host-oracle": "TNS de la base de datos:",
+ "config-db-host-oracle-help": "Introduzca un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nombre de conexión local] válido; un archivo tnsnames.ora debe ser visible para esta instalación.<br />Si está utilizando bibliotecas de cliente 10g o más recientes también puede utilizar el método de asignación de nombres [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identifica este wiki",
+ "config-db-name": "Nombre de la base de datos:",
+ "config-db-name-help": "Elija un nombre que identifique su wiki.\nNo debe contener espacios.\n\nSi está utilizando alojamiento web compartido, su proveedor de alojamiento le dará un nombre específico de base de datos para que lo utilice, o bien le permitirá crear bases de datos a través de un panel de control.",
+ "config-db-name-oracle": "Esquema de la base de datos:",
+ "config-db-account-oracle-warn": "Hay tres escenarios compatibles para la instalación de Oracle como base de datos back-end:\n\nSi desea crear una cuenta de base de datos como parte del proceso de instalación, por favor suministre una cuenta con función SYSDBA como cuenta de base de datos para la instalación y especifique las credenciales deseadas de la cuenta de acceso al web, de lo contrario puede crear manualmente la cuenta de acceso al web y suministrar sólo esa cuenta (si tiene los permisos necesarios para crear los objetos de esquema) o suministrar dos cuentas diferentes, una con privilegios de creación y otra con acceso restringido a la web\n\nLa secuencia de comandos (script) para crear una cuenta con los privilegios necesarios puede encontrarse en el directorio \"maintenance/oracle/\" de esta instalación. Tenga en cuenta que utilizando una cuenta restringida desactivará todas las capacidades de mantenimiento con la cuenta predeterminada.",
+ "config-db-install-account": "Cuenta de usuario para instalación",
+ "config-db-username": "Nombre de usuario de la base de datos:",
+ "config-db-password": "Contraseña de la base de datos:",
+ "config-db-password-empty": "Introduzca una contraseña para el nuevo usuario de base de datos: $1.\nAunque es posible crear usuarios sin contraseña, esto no es seguro.",
+ "config-db-username-empty": "Debe introducir un valor para \"{{int:config-db-username}}\"",
+ "config-db-install-username": "Introduzca el nombre de usuario que se utilizará para conectarse a la base de datos durante el proceso de instalación.\nEste no es el nombre de usuario de la cuenta de MediaWiki; Este es el nombre de usuario para la base de datos.",
+ "config-db-install-password": "Introduzca la contraseña que se utilizará para conectarse a la base de datos durante el proceso de instalación.\nEsta no es la contraseña para la cuenta de MediaWiki; esta es la contraseña para la base de datos.",
+ "config-db-install-help": "Ingresar el nombre de usuario y la contraseña que será usada para conectar a la base de datos durante el proceso de instalación.",
+ "config-db-account-lock": "Usar el mismo nombre de usuario y contraseña durante operación normal",
+ "config-db-wiki-account": "Cuenta de usuario para operación normal",
+ "config-db-wiki-help": "Introduce el nombre de usuario y la contraseña que serán usados para acceder a la base de datos durante la operación normal del wiki.\nSi esta cuenta no existe y la cuenta de instalación tiene suficientes privilegios, se creará esta cuenta de usuario con los privilegios mínimos necesarios para la operación normal del wiki.",
+ "config-db-prefix": "Prefijo de tablas de la base de datos:",
+ "config-db-prefix-help": "Si necesita compartir una base de datos entre múltiples wikis, o entre MediaWiki y otra aplicación web, puede optar por agregar un prefijo a todos los nombres de tabla para evitar conflictos.\nNo utilice espacios.\n\nNormalmente se deja este campo vacío.",
+ "config-db-charset": "Conjunto de caracteres de la base de datos",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binario",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 retrocompatible UTF-8",
+ "config-charset-help": "'''Atención:''' Si emplea '''backwards-compatible UTF-8''' en MySQL 4.1+ y posteriormente hace copia de seguridad de la base de datos con <code>mysqldump</code> , puede destruir todos los caracteres no-ASCII, ¡dañando irreversiblemente sus copias de seguridad!\n\nEn '''modo binario''', MediaWiki almacena texto UTF-8 en la base de datos en campos binarios.\nEsto es más eficiente que el modo UTF-8 de MySQL, y le permite utilizar la gama completa de caracteres Unicode.\nEn ''' modo UTF-8'' ', MySQL sabrá el juego de caracteres de sus datos y puede presentarlos y convertirlos apropiadamente,\npero no le permitirá almacenar caracteres por encima del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plano multilingüe básico].",
+ "config-mysql-old": "Se necesita MySQL $1 o posterior. Tienes $2.",
+ "config-db-port": "Puerto de la base de datos:",
+ "config-db-schema": "Esquema para MediaWiki",
+ "config-db-schema-help": "Estos esquemas usualmente estarán bien.\nAltéralos sólo si tienes la seguridad de que necesitas hacerlo.",
+ "config-pg-test-error": "No se puede conectar a la base de datos '''$1''': $2",
+ "config-sqlite-dir": "Directorio de datos SQLite:",
+ "config-sqlite-dir-help": "SQLite almacena todos los datos en un único archivo.\n\nEl directorio que proporcione debe ser escribible por el servidor Web durante la instalación.\n\n'''No''' debería ser accesible a través de Internet, por eso no vamos a ponerlo en el sitio donde están los archivos PHP.\n\nEl instalador escribirá un archivo <code>.htaccess</code> junto con él, pero si falla alguien podría tener acceso a la base de datos en bloque.\nEso incluye los datos de usuario en bloque (direcciones de correo electrónico, las contraseñas con hash) así como revisiones eliminadas y otros datos restringidos del wiki.\n\nConsidere la posibilidad de poner la base de datos en algún otro sitio, por ejemplo en <code>/var/lib/mediawiki/yourwiki</code> .",
+ "config-oracle-def-ts": "Espacio de tablas por defecto:",
+ "config-oracle-temp-ts": "Espacio de tablas temporal:",
+ "config-type-mysql": "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 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 vinculadas arriba para habilitar la compatibilidad.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] es la base de datos mayoritaria para MediaWiki y la que goza de mayor compatibilidad. MediaWiki también funciona con [{{int:version-db-mariadb-url}} MariaDB] y [{{int:version-db-percona-url}} Percona Server], que son compatibles con MySQL. ([http://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. Pueden haber algunos fallos menores destacables, y no es recomendable para su uso en un entorno de producción. ([http://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. ([http://www.php.net/manual/es/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. ([http://www.php.net/manual/es/oci8.installation.php Cómo compilar PHP con compatibilidad con OCI8])",
+ "config-dbsupport-mssql": "* [{{int:version-db-oracle-url}} Oracle] es una base de datos comercial a nivel empresarial. ([http://www.php.net/manual/es/oci8.installation.php Cómo compilar PHP con compatibilidad con OCI8])",
+ "config-header-mysql": "Configuración de MySQL",
+ "config-header-postgres": "Configuración de PostgreSQL",
+ "config-header-sqlite": "Configuración de SQLite",
+ "config-header-oracle": "Configuración de Oracle",
+ "config-header-mssql": "Configuración de Microsoft SQL Server",
+ "config-invalid-db-type": "El tipo de base de datos no es válido",
+ "config-missing-db-name": "Debe introducir un valor para \"{{int:config-db-nombre}}\".",
+ "config-missing-db-host": "Debe introducir un valor para \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Debe introducir un valor para \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "El TNS de la base de datos «$1» es inválido.\nDebes usar un \"TNS Name\" o una cadena \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Nomenclatura de Oracle]).",
+ "config-invalid-db-name": "El nombre de la base de datos \"$1\" es inválido.\nUsa sólo caracteres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_)y guiones (-).",
+ "config-invalid-db-prefix": "El prefijo de la base de datos \"$1\" es inválido.\nUse sólo carateres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_) y guiones (-).",
+ "config-connection-error": "$1.\n\nVerifique el servidor, el nombre de usuario y la contraseña, e intente de nuevo.",
+ "config-invalid-schema": "El esquema de la base de datos \"$1\" es inválido.\nUse sólo carateres ASCII: letras (a-z, A-Z), guarismos (0-9) y guiones bajos (_).",
+ "config-db-sys-create-oracle": "El instalador sólo admite el empleo de cuentas SYSDBA como método para crear una cuenta nueva.",
+ "config-db-sys-user-exists-oracle": "La cuenta de usuario \"$1\" ya existe. ¡SYSDBA sólo puede utilizarse para crear una nueva cuenta!",
+ "config-postgres-old": "Se necesita PostgreSQL $1 o una versión más reciente; tienes la versión $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 o posterior es necesario. Tienes $2 .",
+ "config-sqlite-name-help": "Elige el nombre que identificará tu wiki.\nNo uses espacios o guiones.\nEste nombre será usado como nombre del archivo de datos de SQLite.",
+ "config-sqlite-parent-unwritable-group": "No se puede crear el directorio de datos <code><nowiki>$1</nowiki></code> , porque el directorio padre <code><nowiki>$2</nowiki></code> no es accesible en escritura por el servidor Web.\n\nEl instalador ha determinado el usuario cuyo servidor Web se está ejecutando.\nConceda permisos de escritura en el directorio <code><nowiki>$3</nowiki></code> para continuar.\nEn un sistema Unix/Linux haga:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "No se puede crear el directorio de datos <code><nowiki>$1</nowiki></code> , porque el directorio padre <code><nowiki>$2</nowiki></code> no es accesible en escritura por el servidor Web.\n\nEl programa de instalación no pudo determinar el usuario que se ejecuta en el servidor Web\nConceda permisos de escritura en el directorio <code><nowiki>$3</nowiki></code> para continuar.\nEn un sistema Unix/Linux haga:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Error al crear el directorio de datos \"$1\".\nComprueba la ubicación e inténtalo de nuevo.",
+ "config-sqlite-dir-unwritable": "No se puede escribir en el directorio \"$1\".\nModifica los permisos para que el servidor web pueda escribir en él y vuelve a intentarlo.",
+ "config-sqlite-connection-error": "$1.\n\nVerifique el directório de datos y el nombre de la base de datos mostrada a continuación e inténtalo nuevamente.",
+ "config-sqlite-readonly": "El archivo <code>$1</code> no se puede escribir.",
+ "config-sqlite-cant-create-db": "No fue posible crear el archivo de la base de datos <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "El PHP no tiene compatibilidad FTS3. actualizando tablas a una versión anterior",
+ "config-can-upgrade": "Esta base de datos contiene tablas de MediaWiki.\nPara actualizarlas a MediaWiki $1, haz clic en '''Continuar'''.",
+ "config-upgrade-done": "Actualización completa.\n\nUsted puede ahora [ $1 empezar a usar su wiki].\n\nSi desea regenerar su archivo <code>LocalSettings.php</code> de archivo, haga clic en el botón de abajo.\nEsto '''no se recomienda''' a menos que esté teniendo problemas con su wiki.",
+ "config-upgrade-done-no-regenerate": "Actualización completa.\n\nUsted puede ahora [$1 empezar a usar su wiki].",
+ "config-regenerate": "Regenerar LocalSettings.php →",
+ "config-show-table-status": "¡Falló la consulta <code>SHOW TABLE STATUS</code>!",
+ "config-unknown-collation": "'''Advertencia:''' La base de datos está utilizando una intercalación no reconocida.",
+ "config-db-web-account": "Cuenta de la base de datos para acceso web",
+ "config-db-web-help": "Elige el usuario y contraseña que el servidor Web usará para conectarse al servidor de la base de datos durante el fincionamiento normal del wiki.",
+ "config-db-web-account-same": "Utilizar la misma cuenta que en la instalación",
+ "config-db-web-create": "Crear la cuenta si no existe",
+ "config-db-web-no-create-privs": "La cuenta que has especificado para la instalación no tiene privilegios suficientes para crear una cuenta.\nLa cuenta que especifiques aquí debe existir.",
+ "config-mysql-engine": "Motor de almacenamiento:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Atención''': Ha seleccionado MyISAM como motor de almacenamiento de MySQL, el cual no está recomendado para su uso con MediaWiki, porque:\n * apenas soporta accesos simultáneos debido al bloqueo de tablas\n * es más propenso a la corrupción que otros motores\n * el código MediaWiki no siempre controla MyISAM como debiera\n\nSi su instalación de MySQL soporta InnoDB, es muy recomendable que lo elija en su lugar.\nSi la instalación de MySQL no admite InnoDB, quizás es el momento de una actualización.",
+ "config-mysql-only-myisam-dep": "'''Advertencia:''' Solo se ha encontrado el motor de almacenamiento MyISAM para MySQL en esta máquina, y no se recomienda su uso con MediaWiki, porque:\n* apenas soporta concurrencia debido a los bloqueos de tablas\n* es más propenso a sufrir corrupción de datos que otros motores\n* el código MediaWiki no siempre maneja MyISAM como debería\n\nTu instalación de MySQL no soporta InnoDB, quizá vaya siendo hora de actualizarla.",
+ "config-mysql-engine-help": "'''InnoDB''' es casi siempre la mejor opción, dado que soporta bien los accesos simultáneos.\n\n'''MyISAM''' es más rápido en instalaciones de usuario único o de sólo lectura.\nLas bases de datos MyISAM tienden a corromperse más a menudo que las bases de datos InnoDB.",
+ "config-mysql-charset": "Conjunto de caracteres de la base de datos:",
+ "config-mysql-binary": "Binario",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "En '''modo binario''', MediaWiki almacena texto UTF-8 para la base de datos en campos binarios.\nEsto es más eficiente que el modo UTF-8 de MySQL y le permite utilizar la gama completa de caracteres Unicode.\n\nEn '''modo UTF-8''', MySQL sabrá qué conjunto de caracteres emplean sus datos y puede presentarlos y convertirlos adecuadamente, pero no le permitirá almacenar caracteres por encima del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plano multilingüe básico].",
+ "config-mssql-auth": "Tipo de autenticación:",
+ "config-mssql-install-auth": "Seleccione el tipo de autenticación que se utilizará para conectarse a la base de datos durante el proceso de instalación.\nSi selecciona \"{{int:config-mssql-windowsauth}}\", las credenciales de cualquier usuario de el servidor web que se está ejecutando van a ser utilizadas.",
+ "config-mssql-web-auth": "Seleccione el tipo de autenticación que utilizará el servidor web para conectarse al servidor de base de datos, durante el funcionamiento normal de la wiki.\nSi selecciona \"{{int:config-mssql-windowsauth}}\", las credenciales del usuario que sea cual sea el servidor Web se ejecuta como será utilizado.",
+ "config-mssql-sqlauth": "Autenticación de SQL Server",
+ "config-mssql-windowsauth": "Autentificación de Windows",
+ "config-site-name": "Nombre del wiki:",
+ "config-site-name-help": "Esto aparecerá en la barra de título del navegador y en varios otros lugares.",
+ "config-site-name-blank": "Ingresar un nombre de sitio.",
+ "config-project-namespace": "Espacio de nombre de proyecto:",
+ "config-ns-generic": "Proyecto",
+ "config-ns-site-name": "Igual al nombre del wiki: $1",
+ "config-ns-other": "Otro (especificar)",
+ "config-ns-other-default": "MiWiki",
+ "config-project-namespace-help": "Siguiendo el ejemplo de Wikipedia, muchos wikis mantienen sus páginas de políticas separadas de sus páginas de contenido, en un \"'''espacio de nombres del proyecto'''\".\n\nTodos los títulos de página en este espacio de nombres comienzan con un determinado prefijo, que usted puede especificar aquí.\nTradicionalmente, este prefijo se deriva del nombre del wiki, pero no puede contener caracteres de puntuación como \"#\" o \":\".",
+ "config-ns-invalid": "El espacio de nombre especificado \"<nowiki>$1</nowiki>\" no es válido.\nEspecifica un espacio de nombre de proyecto diferente.",
+ "config-ns-conflict": "El espacio de nombres especificado \"<nowiki>$1</nowiki>\" entra en conflicto con un espacio de nombres predeterminado de MediaWiki.\nEspecifique un espacio de nombres de proyecto diferente.",
+ "config-admin-box": "Cuenta de administrador",
+ "config-admin-name": "Tu nombre de usuario:",
+ "config-admin-password": "Contraseña:",
+ "config-admin-password-confirm": "Repite la contraseña:",
+ "config-admin-help": "Escribe aquí el nombre de usuario que desees, como por ejemplo \"Pedro Bloggs\".\nEste es el nombre que usarás para entrar al wiki.",
+ "config-admin-name-blank": "Introduce un nombre de usuario de administrador.",
+ "config-admin-name-invalid": "El nombre de usuario especificado \"<nowiki>$1</nowiki>\" no es válido.\nEspecifique un nombre de usuario diferente.",
+ "config-admin-password-blank": "Introduzca una contraseña para la cuenta de administrador.",
+ "config-admin-password-mismatch": "Las dos contraseñas que ingresaste no coinciden.",
+ "config-admin-email": "Dirección de correo electrónico:",
+ "config-admin-email-help": "Introduce aquí un correo electrónico que te permita recibir mensajes de otros usuarios del wiki, vuelve a configurar tu contraseña y recibe notificaciones de cambios realizados a tus páginas vigiladas. Puedes dejar este campo vacío.",
+ "config-admin-error-user": "Error interno al crear un administrador con el nombre \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Error interno al establecer una contraseña para el administrador \" <nowiki>$1</nowiki> \": <pre>$2</pre>",
+ "config-admin-error-bademail": "Ha introducido una dirección de correo electrónico inválida.",
+ "config-subscribe": "Suscribirse para recibir [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce avisos de nuevas versiones].",
+ "config-subscribe-help": "Esta es una lista de divulgación de bajo volumen para anuncios de lanzamiento de versiones nuevas, incluyendo anuncios de seguridad importantes.\nTe recomendamos suscribirte y actualizar tu instalación MediaWiki cada vez que se lance una nueva versión.",
+ "config-subscribe-noemail": "Ha intentado suscribirse a la lista de correo de anuncios de nuevos lanzamientos sin proporcionar una dirección de correo electrónico.\nProporcione una dirección de correo electrónico si desea suscribirse a la lista de correo.",
+ "config-almost-done": "¡Ya casi has terminado!\nAhora puedes saltarte el resto de pasos e instalar el wiki con valores predeterminados.",
+ "config-optional-continue": "Hazme más preguntas.",
+ "config-optional-skip": "Ya estoy aburrido, sólo instala el wiki.",
+ "config-profile": "Perfil de derechos de usuario:",
+ "config-profile-wiki": "Wiki abierto",
+ "config-profile-no-anon": "Creación de cuenta requerida",
+ "config-profile-fishbowl": "Sólo editores autorizados",
+ "config-profile-private": "Wiki privado",
+ "config-profile-help": "Los wikis funcionan mejor cuando dejas que los edite tanta gente como sea posible.\nEn MediaWiki, es fácil revisar los cambios recientes y revertir los daños realizados por usuarios malintencionados o novatos.\nSin embargo, muchos han encontrado que MediaWiki es útil para una amplia variedad de funciones, y a veces no es fácil convencer a todos de los beneficios de la forma wiki.\nPor lo tanto tienes la elección.\n\nEl modelo '''{{int:config-profile-wiki}}''' permite que cualquiera pueda editar, sin siquiera iniciar sesión.\nUn wiki con '''{{int:config-profile-no-anon}}''' ofrece rendición de cuentas adicional, pero puede disuadir a colaboradores.\n\nEl modelo '''{{int:config-profile-fishbowl}}''' permite editar a los usuarios autorizados, pero el público puede ver las páginas, incluyendo el historial.\nUn '''{{int:config-profile-private}}''' sólo permite ver páginas a los usuarios autorizados, el mismo grupo al que le está permitido editar.\n\nConfiguraciones más complejas de derechos de usuario están disponibles después de la instalación, consulte [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights esta entrada en el manual].",
+ "config-license": "Derechos de autor y licencia:",
+ "config-license-none": "Pie sin licencia",
+ "config-license-cc-by-sa": "Creative Commons Atribución-CompartirIgual",
+ "config-license-cc-by": "Creative Commons Atribución",
+ "config-license-cc-by-nc-sa": "Creative Commons Atribución-NoComercial-CompartirIgual",
+ "config-license-cc-0": "Creative Commons Zero (dominio público)",
+ "config-license-gfdl": "Licencia de documentación libre de GNU 1.3 o posterior",
+ "config-license-pd": "Dominio público",
+ "config-license-cc-choose": "Selecciona una licencia personalizada de Creative Commons",
+ "config-license-help": "Muchos wikis públicos ponen todas las contribuciones bajo una [http://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 [http://www.php.net/manual/en/mail.configuration.php configuración PHP de correo electrónico] debe ser la correcta.\nSi no quieres la funcionalidad de correo electrónico, puedes desactivarla aquí.",
+ "config-email-user": "Habilitar 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",
+ "config-email-usertalk-help": "Permitir a los usuarios recibir notificaciones de cambios en la página de discusión de usuario, si lo han activado en sus preferencias.",
+ "config-email-watchlist": "Activar notificación de alteraciones a la páginas vigiladas",
+ "config-email-watchlist-help": "Permitir a los usuarios recibir notificaciones de cambios en la páginas que vigilan, si lo han activado en sus preferencias.",
+ "config-email-auth": "Activar autenticación del correo electrónico",
+ "config-email-auth-help": "Si esta opción está habilitada, los usuarios tienen que confirmar su dirección de correo electrónico mediante un enlace que se les envía a ellos cuando éstos lo establecen o lo cambian.\nSolo las direcciones de correo electrónico autenticadas pueden recibir correos electrónicos de otros usuarios o correos electrónicos de notificación de cambios.\nEsta opción está '''recomendada''' para wikis públicos debido a posibles abusos de las características del correo electrónico.",
+ "config-email-sender": "Dirección de correo electrónico de retorno:",
+ "config-email-sender-help": "Introduce la dirección de correo electrónico que será usada como dirección de retorno en los mensajes electrónicos de salida.\nAquí llegarán los correos electrónicos que no lleguen a su destino.\nMuchos servidores de correo electrónico exigen que por lo menos la parte del nombre del dominio sea válida.",
+ "config-upload-settings": "Cargas de imágenes y archivos",
+ "config-upload-enable": "Habilitar la subida de archivos",
+ "config-upload-help": "La carga de archivos expone potencialmente su servidor a riesgos de seguridad.\nPara obtener más información, lea la [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sección de seguridad] en el manual.\n\nPara habilitar la carga de archivos, cambie el modo en el subdirectorio <code>images</code> bajo el directorio raíz de MediaWiki para que el servidor web pueda escribir en él.\nA continuación, habilite esta opción.",
+ "config-upload-deleted": "*Directorio para los archivos eliminados:",
+ "config-upload-deleted-help": "Elige un directorio en el que guardar los archivos eliminados.\nLo ideal es una carpeta no accesible desde la red.",
+ "config-logo": "URL del logo :",
+ "config-logo-help": "La apariencia por defecto de MediaWiki incluye espacio para un logotipo de 135x160 píxeles encima del menú de la barra lateral.\nCargua una imagen de tamaño adecuado e introduce la dirección URL aquí.\n\nPuedes usar <code>$wgStylePath</code> o <code>$wgScriptPath</code> si tu logotipo es relativo a esas rutas.\n\nSi no deseas un logotipo, deja esta casilla en blanco.",
+ "config-instantcommons": "Habilitar Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] es una característica que permite que los wikis puedan utilizar imágenes, sonidos y otros archivos multimedia que se encuentran en el sitio [//commons.wikimedia.org/ Wikimedia Commons].\nPara ello, MediaWiki requiere acceso a Internet.\n\nPara obtener más información sobre esta función, incluidas las instrucciones sobre cómo configurarlo para otras wikis distintas de Wikimedia Commons, consulte [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos el manual].",
+ "config-cc-error": "El selector de licencia de Creative Commons no dio resultado.\nEscribe el nombre de la licencia manualmente.",
+ "config-cc-again": "Elegir otra vez...",
+ "config-cc-not-chosen": "Elige la licencia Creative Commons que desees y haz clic en \"continuar\".",
+ "config-advanced-settings": "Configuración avanzada",
+ "config-cache-options": "Configuración de la caché de objetos:",
+ "config-cache-help": "El almacenamiento en caché de objetos se utiliza para mejorar la velocidad de MediaWiki mediante el almacenamiento en caché los datos usados más frecuentemente.\nA los sitios medianos y grandes se les recomienda que permitirlo. También es beneficioso para los sitios pequeños.",
+ "config-cache-none": "Sin almacenamiento en caché (no se pierde ninguna funcionalidad, pero la velocidad puede resentirse en sitios grandes)",
+ "config-cache-accel": "Almacenamiento en caché de objetos PHP (APC, XCache o WinCache)",
+ "config-cache-memcached": "Utilizar Memcached (necesita ser instalado y configurado aparte)",
+ "config-memcached-servers": "Servidores Memcached:",
+ "config-memcached-help": "Lista de direcciones IP que serán usadas por Memcached.\nDeben especificarse una por cada línea y especificar el puerto a utilizar. Por ejemplo:\n127.0.0.1:11211\n192.168.1.25:1234",
+ "config-memcache-needservers": "Ha seleccionado Memcached como su tipo de caché pero no especificó ninguno de los servidores.",
+ "config-memcache-badip": "Ha introducido una dirección IP no válida para Memcached: $1 .",
+ "config-memcache-noport": "No ha especificado un puerto a usar en el servidor Memcached: $1 .\nSi no conoce el puerto, el valor predeterminado es 11211.",
+ "config-memcache-badport": "Los números de puerto de Memcached deben estar entre $1 y $2.",
+ "config-extensions": "Extensiones",
+ "config-extensions-help": "Se ha detectado en tu directorio <code>./extensions</code> las extensiones listadas arriba.\n\nPuede que necesiten configuraciones adicionales, pero puedes habilitarlas ahora.",
+ "config-skins": "Apariencias",
+ "config-skins-help": "Las apariencias mencionadas anteriormente fueron detectadas en tu directorio <code>./skins</code>. Debes habilitar al menos una y elegir la predeterminada.",
+ "config-skins-use-as-default": "Utilizar esta apariencia como predeterminada",
+ "config-skins-missing": "No se encontró ninguna apariencia; MediaWiki utilizará una apariencia anterior hasta que instales unas apariencias adecuadas.",
+ "config-skins-must-enable-some": "Debes seleccionar al menos una apariencia para activar.",
+ "config-skins-must-enable-default": "La apariencia elegida como predeterminada debe estar habilitada.",
+ "config-install-alreadydone": "'''Aviso:''' Parece que ya habías instalado MediaWiki y estás intentando instalarlo nuevamente.\nPasa a la próxima página, por favor.",
+ "config-install-begin": "Al pulsar en «{{int:config-continue}}» comenzará el proceso de instalación de MediaWiki.\nSi quieres realizar algún cambio, pulsa en «{{int:config-back}}».",
+ "config-install-step-done": "hecho",
+ "config-install-step-failed": "falló",
+ "config-install-extensions": "Extensiones inclusive",
+ "config-install-database": "Configurando la base de datos",
+ "config-install-schema": "Creando el esquema",
+ "config-install-pg-schema-not-exist": "El esquema PostgreSQL no existe.",
+ "config-install-pg-schema-failed": "La creación de las tablas ha fallado.\nAsegúrate de que el usuario \"$1\" puede escribir en el esquema \"$2\".",
+ "config-install-pg-commit": "Validando los cambios",
+ "config-install-pg-plpgsql": "Comprobación de lenguaje PL/pgSQL",
+ "config-pg-no-plpgsql": "Necesita instalar el lenguaje PL/pgSQL en la base de datos $1",
+ "config-pg-no-create-privs": "La cuenta especificada para la instalación no tiene suficientes privilegios para crear una cuenta.",
+ "config-pg-not-in-role": "La cuenta especificada para el usuario web ya existe.\nLa cuenta especificada para la instalación no es de un superusuario y no es miembro del grupo de usuarios con acceso a la web, por lo que es incapaz de crear objetos pertenecientes al usuario web.\n\nMediaWiki requiere actualmente que las tablas sean propiedad del usuario web. Especifique otro nombre de cuenta web, o haga clic en \"atrás\" y especifique un usuario de instalación con los privilegios convenientes.",
+ "config-install-user": "Creando el usuario de la base de datos",
+ "config-install-user-alreadyexists": "El usuario \"$1\" ya existe",
+ "config-install-user-create-failed": "La creación del usuario \"$1\" falló: $2",
+ "config-install-user-grant-failed": "La concesión de permisos al usuario \"$1\" falló: $2",
+ "config-install-user-missing": "El usuario especificado \"$1\" no existe.",
+ "config-install-user-missing-create": "El usuario especificado \"$1\" no existe.\nPor favor, haga clic en la casilla \"Crear cuenta\" que aparece a continuación si desea crearlo.",
+ "config-install-tables": "Creando tablas",
+ "config-install-tables-exist": "'''Advertencia''': Al parecer, las tablas de MediaWiki ya existen. Saltándose su creación.",
+ "config-install-tables-failed": "'''Error''': La creación de las tablas falló con el siguiente error: $1",
+ "config-install-interwiki": "Llenando la tabla interwiki predeterminada",
+ "config-install-interwiki-list": "No se pudo encontrar el archivo <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Advertencia''': La tabla de interwikis parece ya contener entradas.\nSe omitirá la lista predeterminada.",
+ "config-install-stats": "Iniciando las estadísticas",
+ "config-install-keys": "Generando claves secretas",
+ "config-insecure-keys": "''' Atención:'' ' {{PLURAL:$2|Una clave de seguridad generada|Las claves de seguridad generadas}} ($1) durante la instalación no {{PLURAL:$2|es totalmente segura|son totalmente seguras}}. Considere {{PLURAL:$2| cambiarla|cambiarlas}} manualmente.",
+ "config-install-updates": "Evitar ejecutar actualizaciones innecesarias",
+ "config-install-updates-failed": "<strong>Error:</strong> falló la inserción de claves de actualización en las tablas con el siguiente error: $1",
+ "config-install-sysop": "Creando la cuenta de usuario del administrador",
+ "config-install-subscribe-fail": "No se ha podido suscribir a mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL no está instalado y <code>allow_url_fopen</code> no está disponible.",
+ "config-install-mainpage": "Creando página principal con contenido predeterminado",
+ "config-install-extension-tables": "Creando las tablas para las extensiones habilitadas",
+ "config-install-mainpage-failed": "No se pudo insertar la página principal: $1",
+ "config-install-done": "<strong>¡Felicidades!</strong>\nHas instalado MediaWiki correctamente.\n\nEl instalador ha generado un archivo <code>LocalSettings.php</code>.\nEste contiene toda su configuración.\n\nDeberás descargarlo y ponerlo en la base de la instalación de wiki (el mismo directorio que index.php). La descarga debería haber comenzado automáticamente.\n\nSi no comenzó la descarga, o si se ha cancelado, puedes reiniciar la descarga haciendo clic en el siguiente enlace:\n\n$3\n\n<strong>Nota</strong>: Si no haces esto ahora, este archivo de configuración generado no estará disponible más tarde si sales de la instalación sin descargarlo.\n\nCuando lo hayas hecho, podrás <strong>[$2 entrar en tu wiki]</strong>.",
+ "config-download-localsettings": "Descargar archivo <code>LocalSettings.php</code>",
+ "config-help": "ayuda",
+ "config-help-tooltip": "haz clic para ampliar",
+ "config-nofile": "El archivo \"$1\" no se pudo encontrar. ¿Se ha eliminado?",
+ "config-extension-link": "¿Sabías que tu wiki admite [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensiones]?\n\nPuedes navegar por las [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category categorías] o visitar el [//www.mediawiki.org/wiki/Extension_Matrix centro de extensiones] para ver una lista completa.",
+ "mainpagetext": "<strong>MediaWiki se ha instalado con éxito.<strong>",
+ "mainpagedocfooter": "Consulta la [//meta.wikimedia.org/wiki/Help:Contents/es guía del usuario] para obtener información sobre el uso del software wiki.\n\n== Primeros pasos ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de ajustes de configuración]\n* [//www.mediawiki.org/wiki/Manual:FAQ/es Preguntas frecuentes sobre MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de publicación de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Traducir MediaWiki en tu idioma]"
+}
diff --git a/includes/installer/i18n/et.json b/includes/installer/i18n/et.json
new file mode 100644
index 00000000..8fae026e
--- /dev/null
+++ b/includes/installer/i18n/et.json
@@ -0,0 +1,68 @@
+{
+ "@metadata": {
+ "authors": [
+ "Avjoska",
+ "Pikne",
+ "Boxmein"
+ ]
+ },
+ "config-information": "Teave",
+ "config-session-error": "Tõrge seansi alustamisel: $1",
+ "config-your-language": "Oma keel:",
+ "config-wiki-language": "Viki keel:",
+ "config-back": "← Tagasi",
+ "config-continue": "Jätka →",
+ "config-page-language": "Keel",
+ "config-page-welcome": "Tere tulemast MediaWikisse!",
+ "config-page-dbconnect": "Andmebaasiga ühendamine",
+ "config-page-upgrade": "Olemasoleva installi uuendus",
+ "config-page-dbsettings": "Andmebaasi sätted",
+ "config-page-name": "Nimi",
+ "config-page-options": "Seaded",
+ "config-page-install": "Paigaldamine",
+ "config-page-complete": "Valmis!",
+ "config-page-restart": "Alusta installimist uuesti",
+ "config-page-readme": "Loe mind",
+ "config-page-copying": "Kopeerimine",
+ "config-page-upgradedoc": "Uuendamine",
+ "config-page-existingwiki": "Olemasolev viki",
+ "config-restart": "Jah, tee taaskäivitus",
+ "config-env-hhvm": "HHVM $1 on installitud.",
+ "config-db-name": "Andmebaasi nimi:",
+ "config-db-username": "Andmebaasi kasutajanimi:",
+ "config-db-password": "Andmebaasi parool:",
+ "config-db-port": "Andmebaasi port:",
+ "config-invalid-db-type": "Vigane andmebaasi tüüp",
+ "config-site-name": "Viki nimi:",
+ "config-site-name-blank": "Sisesta võrgukoha nimi.",
+ "config-project-namespace": "Projekti nimeruum:",
+ "config-ns-generic": "Projekt",
+ "config-admin-box": "Administraatorikonto",
+ "config-admin-name": "Sinu nimi:",
+ "config-admin-password": "Parool:",
+ "config-admin-password-confirm": "Parool uuesti:",
+ "config-admin-name-blank": "Sisesta administraatori kasutajanimi.",
+ "config-admin-password-blank": "Sisesta administraatorikonto parool.",
+ "config-admin-password-mismatch": "Sisestatud kaks parooli ei lange kokku.",
+ "config-admin-email": "E-posti aadress:",
+ "config-admin-error-bademail": "Sisestasid vigase e-posti aadressi.",
+ "config-optional-continue": "Küsi minult veel küsimusi.",
+ "config-profile-private": "Eraviki",
+ "config-license": "Autoriõigus ja litsents:",
+ "config-license-none": "Litsentsijaluseta",
+ "config-license-cc-by-sa": "Creative Commonsi litsents \"Autorile viitamine + jagamine samadel tingimustel\"",
+ "config-license-cc-by": "Creative Commonsi litsents \"Autorile viitamine\"",
+ "config-license-cc-by-nc-sa": "Creative Commonsi litsents \"Autorile viitamine + mitteäriline eesmärk + jagamine samadel tingimustel\"",
+ "config-email-settings": "E-posti sätted",
+ "config-email-sender": "Saatja e-aadress:",
+ "config-logo": "Logo internetiaadress:",
+ "config-cc-again": "Vali uuesti...",
+ "config-extensions": "Lisad",
+ "config-install-step-done": "valmis",
+ "config-install-step-failed": "ebaõnnestus",
+ "config-install-user-alreadyexists": "Kasutaja \"$1\" on juba olemas",
+ "config-install-tables": "Tabelite loomine",
+ "config-help": "abi",
+ "mainpagetext": "'''MediaWiki tarkvara on edukalt paigaldatud.'''",
+ "mainpagedocfooter": "Vikitarkvara kasutamise kohta leiad lisateavet [//meta.wikimedia.org/wiki/Help:Contents juhendist].\n\n== Alustamine ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Häälestussätete loend]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki KKK]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki versiooniuuenduste postiloend]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki lokaliseerimine]"
+}
diff --git a/includes/installer/i18n/eu.json b/includes/installer/i18n/eu.json
new file mode 100644
index 00000000..4b32743c
--- /dev/null
+++ b/includes/installer/i18n/eu.json
@@ -0,0 +1,78 @@
+{
+ "@metadata": {
+ "authors": [
+ "An13sa",
+ "පසිඳු කාවින්ද",
+ "Subi"
+ ]
+ },
+ "config-desc": "MediaWiki instalatzailea",
+ "config-title": "MediaWiki $1 instalazioa",
+ "config-information": "Informazioa",
+ "config-session-error": "Saio hasierako errorea: $1",
+ "config-your-language": "Zure hizkuntza:",
+ "config-your-language-help": "Aukeratu instalazio prozesuan erabiliko den hizkuntza",
+ "config-wiki-language": "Wiki hizkuntza:",
+ "config-back": "← Atzera",
+ "config-continue": "Jarraitu →",
+ "config-page-language": "Hizkuntza",
+ "config-page-welcome": "Ongi etorri MediaWikira!",
+ "config-page-dbconnect": "Datu-basera konektatu",
+ "config-page-dbsettings": "Datu-basearen ezarpenak",
+ "config-page-name": "Izena",
+ "config-page-options": "Aukerak",
+ "config-page-install": "Instalatu",
+ "config-page-complete": "Bukatua!",
+ "config-page-restart": "Instalazioa berriz hasi",
+ "config-page-readme": "Irakur nazazu",
+ "config-page-copying": "Kopiatzea",
+ "config-page-upgradedoc": "Eguneratu",
+ "config-restart": "Bai, berriz hasi",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki nagusia]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Erabiltzaileentzako Gida]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administratzaileentzako Gida]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MEG]\n----\n* <doclink href=Readme>Irakur nazazu</doclink>\n* <doclink href=ReleaseNotes>Oharren argitalpena</doclink>\n* <doclink href=Copying>Kopiaketa</doclink>\n* <doclink href=UpgradeDoc>Eguneratzea</doclink>",
+ "config-env-php": "PHP $1 instalatuta dago.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] instalatuta dago",
+ "config-apc": "[http://www.php.net/apc APC] instalatuta dago",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] instalatuta dago",
+ "config-diff3-bad": "GNU diff3 ez da aurkitu.",
+ "config-db-type": "Datu-base mota:",
+ "config-db-wiki-settings": "Wiki hau identifikatu",
+ "config-db-name": "Datu-base izena:",
+ "config-db-username": "Datu-base lankide izena:",
+ "config-db-password": "Datu-base pasahitza:",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 bitarra",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-type-mysql": "MySQL",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-header-mysql": "MySQL hobespenak",
+ "config-header-postgres": "PostgreSQL hobespenak",
+ "config-header-sqlite": "SQLite hobespenak",
+ "config-header-oracle": "Oracle hobespenak",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "Bitarra",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Wikiaren izena:",
+ "config-project-namespace": "Proiektuaren izen-tartea:",
+ "config-ns-generic": "Proiektua",
+ "config-ns-other": "Bestelakoa (zehaztu)",
+ "config-ns-other-default": "MyWiki",
+ "config-admin-box": "Administratzaile kontua",
+ "config-admin-name": "Zure erabiltzaile-izena:",
+ "config-admin-password": "Pasahitza:",
+ "config-admin-password-confirm": "Pasahitza berriz:",
+ "config-admin-password-mismatch": "Sartutako bi pasahitzak ez datoz bat.",
+ "config-admin-email": "E-posta helbidea:",
+ "config-profile-wiki": "Wikia ireki",
+ "config-profile-private": "Wiki pribatua",
+ "config-license": "Copyright eta lizentzia:",
+ "config-license-pd": "Domeinu Askea",
+ "config-email-settings": "E-posta hobespenak",
+ "config-logo": "Logo URL:",
+ "config-extensions": "Luzapenak",
+ "config-install-step-done": "egina",
+ "config-help": "Laguntza",
+ "mainpagetext": "'''MediaWiki arrakastaz instalatu da.'''",
+ "mainpagedocfooter": "Ikus [//meta.wikimedia.org/wiki/Help:Contents Erabiltzaile Gida] wiki softwarea erabiltzen hasteko informazio gehiagorako.\n\n== Nola hasi ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Konfigurazio balioen zerrenda]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ (Maiz egindako galderak)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWikiren argitalpenen posta zerrenda]"
+}
diff --git a/includes/installer/i18n/ext.json b/includes/installer/i18n/ext.json
new file mode 100644
index 00000000..5259ea12
--- /dev/null
+++ b/includes/installer/i18n/ext.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MeyaGüiqui s'á istalau satihatoriamenti.'''",
+ "mainpagedocfooter": "Consurta la [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pa sabel mas al tentu el huncionamientu el software güiqui.\n\n== Esminciandu ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/fa.json b/includes/installer/i18n/fa.json
new file mode 100644
index 00000000..02ffd0d1
--- /dev/null
+++ b/includes/installer/i18n/fa.json
@@ -0,0 +1,331 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mjbmr",
+ "Armin1392",
+ "Ebraminio",
+ "Omidh",
+ "Pouyana",
+ "Reza1615",
+ "Alirezaaa"
+ ]
+ },
+ "config-desc": "نصب کنندهٔ ویکی‌مدیا",
+ "config-title": "نصب ویکی‌مدیا $1",
+ "config-information": "اطلاعات",
+ "config-localsettings-upgrade": "یک پرونده <code>LocalSettings.php</code> شناسایی شده‌است.\nبرای ارتقاء این نصب لطفاً مقدار <code>$wgUpgradeKey</code> در جعبه زیر وارد کنید.\nشما می‌توانید آن را در <code>LocalSettings.php</code> پیدا کنید.",
+ "config-localsettings-cli-upgrade": "یک پرونده <code>LocalSettings.php</code> شناسایی شده است.\nبرای ارتقاء این نصب، لطفاً <code>update.php</code> را اجرا کنید.",
+ "config-localsettings-key": "کلید ارتقا:",
+ "config-localsettings-badkey": "کلیدی که شما ارائه کردید نادرست است.",
+ "config-upgrade-key-missing": "نصب موجود مدیاویکی شناسایی شده‌است.\nبرای بروزرسانی این نصب، لطفاً خط زیر را در آخر کد \nقرار دادن به نصب ارتقاء داده شده، به خط زیر لطفاً در پایین خود را <code>LocalSettings.php</code> قرار دهید:\n\n$1",
+ "config-localsettings-incomplete": "وجود <code>LocalSettings.php</code> به نظر ناقص می‌رسد.\nمتغیر $1 تنظیم نشده‌است.\nبرای اینکه این متغیر تنظیم شود لطفاً <code>LocalSettings.php</code> را تغییر دهید، و \"{{int:Config-continue}}\" را کلیک کنید.",
+ "config-localsettings-connection-error": "هنگام اتصال به پایگاه اطلاعاتی که ازتنظیمات مشخص شده در<code>LocalSettings.php</code> استفاده می‌کند، خطایی رخ داد. لطفاً این تنظیمات را نصب کنید و دوباره تلاش کنید.\n$1",
+ "config-session-error": "خطا در شروع جلسه: $1",
+ "config-session-expired": "به نظر می‌رسد اطلاعات جلسهٔ شما منقضی شده‌است.\nجلسات برای مادام العمر $1 پیکربندی شده‌اند.\nشما می‌توانید این پیکربندی را با تنظیم <code>session.gc_maxlifetime</code> در php.ini افزایش دهید.\nروند نصب را دوباره شروع کنید.",
+ "config-no-session": "اطلاعات دورهٔ شما از دست رفته‌ است!\nphp.ini خود را بررسی کنید و مطمئن شوید <code>session.save_path</code> برای یک فهرست مناسب تنظیم شده‌است.",
+ "config-your-language": "زبان شما:",
+ "config-your-language-help": "یک زبان را برای استفاده در طی روند نصب انتخاب کنید.",
+ "config-wiki-language": "زبان ویکی:",
+ "config-wiki-language-help": "زبانی را انتخاب کنید که ویکی بیشتر در آن نوشته خواهد شد.",
+ "config-back": "→ بازگشت",
+ "config-continue": "ادامه ←",
+ "config-page-language": "زبان",
+ "config-page-welcome": "به مدیاویکی خوش آمدید!",
+ "config-page-dbconnect": "اتصال به پایگاه داده",
+ "config-page-upgrade": "ارتقای نصب موجود",
+ "config-page-dbsettings": "تنظیمات پایگاه داده",
+ "config-page-name": "نام",
+ "config-page-options": "گزینه‌ها",
+ "config-page-install": "نصب",
+ "config-page-complete": "کامل!",
+ "config-page-restart": "راه‌اندازی دوباره نصب",
+ "config-page-readme": "مرا بخوان",
+ "config-page-releasenotes": "یادداشت‌های انتشار",
+ "config-page-copying": "تکثیر",
+ "config-page-upgradedoc": "ارتقاء",
+ "config-page-existingwiki": "ویکی موجود",
+ "config-help-restart": "آیا می‌خواهید همهٔ اطلاعات ذخیره شده‌ای که وارد کرده‌اید را پاک کنید و دوباره روند نصب را شروع کنید؟",
+ "config-restart": "بله، دوباره راه‌اندازی کن",
+ "config-welcome": "===بررسی‌های محیطی===\nبرای فهمیدن اینکه این محیط برای نصب مدیاویکی مناسب است، اکنون بررسی‌های اساسی انجام خواهد‌شد.\nاگر به دنبال پشتیبانی در چگونگی تکمیل نصب هستید،به یاد داشته باشید این اطلاعات را بگنجانید.",
+ "config-copyright": "===حق چاپ و شرایط===\n$1\nاین برنامه،نرم‌افزاری آزاد است;شما می‌توانید این برنامه را دوباره توزیع کنید و/یا تحت شرایط مجوز عمومی کلی جی‌ان‌یو که توسط بنیاد نرم‌افزار آزاد منتشر شده،اصلاح کنید;یا نسخهٔ 2 مجوز، یا (به انتخاب خود) هر نسخهٔ پس از این.\nاین برنامه به امید اینکه مفید واقع‌ شود توزیع شده‌است،اما '''بدون هیچ ضمانتی'''; حتی بدون اشارهٔ ضمانتی از '''قابلیت عرضه''' یا ''' صلاحیت برای یک هدف خاص'''.\nبرای جزئیات بیشتر مجوز عمومی کلی جی‌ان‌یو را مشاهده کنید.\nشما باید <doclink href=Copying> یک چاپ ازمجوز عمومی کلی </doclink> همراه این برنامه دریافت کنید; اگر دریافت نکردید،به بنیاد نرم‌افزار آزاد بنویسید،Inc.،خیابان فرانکلین۵۱،طبقه پنجم،بوستون، MA۰۲۱۱۰-۱۳۰،آمریکا،یا [http://www.gnu.org/copyleft/gpl.html read it online].",
+ "config-sidebar": "* [//www.mediawiki.org صفحهٔ اصلی مدیاویکی]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents راهنمای کاربر]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents راهنمای مدیر]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسش‌های رایج]\n----\n* <doclink href=Readme>مرا بخوان</doclink>\n* <doclink href=ReleaseNotes>یادداشت‌های انتشار</doclink>\n* <doclink href=Copying>نسخه برداری</doclink>\n* <doclink href=UpgradeDoc>ارتقا</doclink>",
+ "config-env-good": "محیط بررسی شده‌است.\nشما می‌توانید مدیاویکی را نصب کنید.",
+ "config-env-bad": "محیط بررسی شده‌است.\nشما نمی‌توانید مدیاویکی را نصب کنید.",
+ "config-env-php": "پی‌اچ‌پی $1 نصب شده‌است.",
+ "config-env-hhvm": "HHVM $1 نصب شده‌است.",
+ "config-unicode-using-utf8": "برای یونیکد عادی از Brion Vibber's utf8_normalize.so استفاده کنید.",
+ "config-unicode-using-intl": "برای یونیکد عادی از [http://pecl.php.net/intl intl PECL extension] استفاده کنید.",
+ "config-unicode-pure-php-warning": "'''هشدار:''' [http://pecl.php.net/intl intl PECL extension] برای کنترل یونیکد عادی در دسترس نیست،اجرای کاملاً آهسته به تعویق می‌افتد.\nاگر شما یک سایت پر‌ ترافیک را اجرا می‌کنید، باید کمی [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization] را بخوانید.",
+ "config-unicode-update-warning": "'''هشدار:''' نسخهٔ نصب شدهٔ پوشهٔ یونیکد عادی از ورژن قدیمی‌تر کتابخانه [http://site.icu-project.org/ the ICU project's] استفاده می‌کند.\nاگر کلاً علاقه‌مند به استفاده از یونیکد هستید باید [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations upgrade].",
+ "config-no-db": "درایور پایگاه اطلاعاتی مناسب پیدا نشد! شما لازم دارید یک درایور پایگاه اطلاعاتی برای پی‌اچ‌پی نصب کنید.انواع پایگاه اطلاعاتی زیر پشتیبانی شده‌اند:$1.\nاگر شما در گروه اشتراک‌گذاری هستید، از تهیه کنندهٔ گروه خود برای نصب یک درایور پایگاه اطلاعاتی مناسب سوأل کنید.\nاگر خود، پی‌اچ‌پی را تهیه کرده‌اید، با یک پردازشگر فعال دوباره پیکربندی کنید، برای مثال از <code>./configure --with-mysql</code> استفاده کنید.\nاگر پی‌اچ‌پی را از یک بستهٔ دبیان یا آبونتو نصب کرده‌اید، بنابراین لازم دارید بخش php5-mysql را نصب کنید.",
+ "config-outdated-sqlite": "''' هشدار:''' شما اس‌کیولایت $1 دارید، که پایین‌تر از حداقل نسخهٔ $2 مورد نیاز است.اس‌کیولایت در دسترس نخواهد بود.",
+ "config-no-fts3": "'''هشدار:''' اس‌کیولایت بدون [//sqlite.org/fts3.html FTS3 module] تهیه شده‌است ، جستجوی ویژگی‌ها در این بخش پیشین در دسترس نخواهد‌بود.",
+ "config-register-globals-error": "<strong>خطا: پی‌اچ‌پی<code>[http://php.net/register_globals register_globals]</code> گزینه فعال است.\nبرای ادامه نصب باید غیر فعال باشد.</strong>\n[Https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] را برای کمک در مورد نحوه انجام این کار ببینید.",
+ "config-magic-quotes-gpc": "<strong>هشدار مهم: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] فعال شده‌است!</strong>\nاین گزینه اطلاعات داده شده به رایانه را به طور غیر‌قابل پیش‌بینی از بین می‌برد.\nشما نمی‌توانید مدیاویکی را نصب یا استفاده کنید مگر اینکه این گزینه غیر‌فعال باشد.",
+ "config-magic-quotes-runtime": "'''مخرب: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] فعال است.\nاین گزینه اطلاعات داده شده به رایانه را به طور غیر‌قابل پیش‌بینی از بین می‌برد.\nشما نمی‌توانید مدیاویکی را نصب یا استفاده کنید مگر اینکه این گزینه غیر‌فعال باشد.",
+ "config-magic-quotes-sybase": "'''مخرب: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] فعال است.\nاین گزینه اطلاعات داده شده به رایانه را به طور غیر‌قابل پیش‌بینی از بین می‌برد.\nشما نمی‌توانید مدیاویکی را نصب یا استفاده کنید مگر اینکه این گزینه غیر‌فعال باشد.",
+ "config-mbstring": "''' مخرب:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] فعال است.\nاین گزینه باعث ایجاد خطا می‌شود و ممکن است اطلاعات را به طور غیر‌قابل پیش‌بینی از بین ببرد.\nشما نمی‌توانید مدیاویکی را نصب یا استفاده کنید مگر اینکه این گزینه غیر‌فعال باشد.",
+ "config-safe-mode": "'''هشدار:''' PHP's [http://www.php.net/features.safe-mode safe mode] فعال است.\nممکن است باعث ایجاد مشکلاتی شود، مخصوصاً اگر از ارسال پرونده استفاده شود و <code>math</code> پشتیبانی شود.",
+ "config-xml-bad": "ماژول اکس‌ام‌ال پی‌اچ‌پی کار نمی‌کند.\nمدیاویکی نیازمند عملیاتی در این ماژول است و در این پیکربندی کار نخواهد‌کرد.\nاگر مان‌دریک را اجرا می‌کنید, بستهٔ نرم افزاری پی‌اچ‌پی-ایکس‌ام‌ال را نصب کنید.",
+ "config-pcre-old": "''' خطای اساسی:'' ' PCRE $1 یا بعدا مورد نیاز است.\nکد باینری پی‌اچ‌پی‌تان با PCRE $2 پیوند دارد.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE اطلاعات بیشتر].",
+ "config-pcre-no-utf8": "'''مخرب:''' به‌ نظر می‌رسد ماژول پی‌سی‌آرایی پی‌اچ‌پی بدون پشتیبانی پی‌سی‌آرایی_یو‌تی‌اف۸ تهیه شده‌است.\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-ctype": "'''مخرب:''' پی‌اچ‌پی باید با پشتیبانی برای [http://www.php.net/manual/en/ctype.installation.php Ctype extension] تهیه شده‌باشد.",
+ "config-iconv": "<strong>خطای اساسی:</strong> پی‌اچ‌پی باید کامپایل‌شده باشد برای پشتیبانی از [http://www.php.net/manual/en/iconv.installation.php افزونهٔ iconv].",
+ "config-json": "'''مخرب:''' پی‌اچ‌پی بدون پشتیبانی جِی‌اس‌اُ‌ان تهیه شده‌بود.\nشما باید قبل از نصب مدیاویکی یا بسط جِی‌اس‌اُ‌ان پی‌اچ‌پی یا بسط [http://pecl.php.net/package/jsonc PECL jsonc] را نصب کنید.\n* بسط پی‌اچ‌پی شامل لینوکس اینترپرایز رد هت (سِنت‌اُاِس) 5 یا 6 است، هرچند باید در <code>/etc/php.ini</code> یا <code>/etc/php.d/json.ini</code> فعال باشد.\n* به‌جای بسته‌بندی کردن بسط پی‌ایی‌سی‌اِل مانند <code>php5-json</code> یا <code>php-pecl-jsonc</code>، توزیع‌های برخی لینوکس پس از ماه می ۲۰۱۳ با حذف بسط پی‌اچ‌پی افزایش پیدا کرد.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] نصب شده‌است.",
+ "config-apc": "[http://www.php.net/apc APC] نصب شده‌است.",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] نصب شده‌است.",
+ "config-no-cache": "'''هشدار:''' [http://www.php.net/apc APC],[http://xcache.lighttpd.net/ XCache] یا [http://www.iis.net/download/WinCacheForPhp WinCache] را نتوانست پیدا کند.\nذخیره شی فعال نیست.",
+ "config-mod-security": "'''هشدار:''' وب سرور شما [http://modsecurity.org/ mod_security] فعال است.اگر اشتباه پیکربندی شده‌‌ باشد،می تواند باعث ایجاد مشکلاتی برای مدیاویکی یا دیگر نرم‌افزاری شود که به کاربران اجازه می‌دهد پیام دلخواه ارسال کنند.\nبه [http://modsecurity.org/documentation/ mod_security documentation] مراجعه کنید یا اگر با خطاهای اتفاقی مواجه شدید با پشتیبانی میزبان خود در تماس باشید.",
+ "config-diff3-bad": "جی‌ان‌یو دیف۳ پیدا نشد.",
+ "config-git": "کنترل نسخهٔ نرم‌افزار گیت پیدا شد: <code>$1</code>.",
+ "config-git-bad": "کنترل نسخهٔ نرم‌افزار گیت پیدا نشد.",
+ "config-imagemagick": "ایمیج‌مجیک پیدا شد: <code>$1</code>.\nاگر ارسال‌ها را فعال کنید،تصویر کوچک فعال خواهد‌شد.",
+ "config-gd": "گرافیک‌های جی‌دی ساخته‌‌ شده در کتابخانه پیدا شد.\nاگر ارسال‌ها را فعال کنید تصویر کوچک فعال خواهد‌شد.",
+ "config-no-scaling": "کتابخانهٔ جی‌دی یا ایمیج‌مجیک نتوانست پیدا شود.\nتصویر کوچک غیر‌فعال خواهد‌شد.",
+ "config-no-uri": "'''خطا:''' یوآرآی فعلی را نتوانست مشخص کند.\nنصب نافرجام ماند.",
+ "config-no-cli-uri": "<strong>هشدار:</strong> هیچ اسکریپت‌پتی تعیین نشده، از پیش‌فرض استفاده کنید:\n<code>$1</code>.",
+ "config-using-server": "از اسم سرور \"<nowiki>$1</nowiki>\" استفاده کنید.",
+ "config-using-uri": "از اسم سرور \"<nowiki>$1$2</nowiki>\" استفاده کنید.",
+ "config-uploads-not-safe": "'''هشدار:''' فهرست پیش‌فرض ارسال‌های <code>$1</code> شما برای اجرای متون دلخواه آسیب‌پذیر است.\nاگرچه مدیاویکی همهٔ پوشه‌های ارسال‌ شده را برای خطرات امنیتی بررسی می‌کند، پیش از فعال‌سازی ارسال‌ها، بسیار به [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security close this security vulnerability] توصیه شده‌است.",
+ "config-no-cli-uploads-check": "'''هشدار:''' فهرست پیش‌فرض ارسال‌های شما (<code>$1</code>) برای آسیب‌پذیری اجرای متن دلخواه در طول نصب سی‌ال آی بررسی نشده‌است.",
+ "config-brokenlibxml": "دستگاه شما دارای تلفیقی از نسخه‌های پی‌اچ‌پی و لیبکسمل۲ است که ناقص است و می‌تواند دلیل از بین رفتن اطلاعات مخفی در مدیاویکی و دیگر برنامه‌های کاربردی شبکه باشد.\nارتقاء به لیبکسمل۲ ۲.۷.۳ یا بالاتر ([//bugs.php.net/bug.php?id=45996 bug filed with PHP]) ارتفاء دهید.\nنصب نافرجام ماند.",
+ "config-suhosin-max-value-length": "سوهُسین نصب شده‌است و پارامتر جت <code>length</code> را به $1 بایت محدود می‌کند.\n قسمت بارکنندهٔ منبع مدیاویکی پیرامون این محدوده کار خواهد‌کرد، اما عملکرد آن را پایین می‌آورد. اگر به هیچ وجه ممکن نیست، باید <code>suhosin.get.max_value_length</code> را به ۱۰۲۴ یا بالاتر در <code>php.ini</code> تنظیم کنید، و \n<code>$wgResourceLoaderMaxQueryLength</code> را به مقدار مشابه در <code>LocalSettings.php</code> تنظیم کنید.",
+ "config-db-type": "نوع پایگاه اطلاعات:",
+ "config-db-host": "میزبان پایگاه اطلاعات:",
+ "config-db-host-help": "اگر سرور پایگاه اطلاعاتی شما در سرور دیگری است، نام گروه و آدرس آی‌پی را اینجا وارد کنید.\nاگر از گروه شبکهٔ اشتراک گذاری استفاده می‌کنید، تهیه‌کنندهٔ گروه‌تان باید نام گروه صحیح در اسنادومدارک را به شما بدهد.\nاگر در حال نصب یک سرور ویندوز هستید و از مای‌اس‌کیو‌ال استفاده می‌کنید، ممکن است استفاده از \"گروه داخلی\" برای نام سرور کار نکند.اگر کار نکرد، \"۱۲۷.۰.۰.۱\" را برای آدرس آی‌پی داخلی امتحان کنید.\nاگر از پستگِرِاس‌کیوال استفاده می‌کنید،برای اتصال از طریق یک سوکت یونیکس این زمینه را خالی رها کنید.",
+ "config-db-host-oracle": "پایگاه اطلاعاتی تی‌ان‌اس:",
+ "config-db-host-oracle-help": "یک [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] معتبر وارد کنید؛ پوشهٔ tnsnames.ora باید برای این نصب نمایان باشد.<br /> اگر از کتابخانه‌های پردازشگر ۱۰جی یا جدیدتر استفاده می‌کنید،همچنین می‌توانید از روش نامبردهٔ [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] استفاده کنید.",
+ "config-db-wiki-settings": "این ویکی را شناسایی کنید.",
+ "config-db-name": "نام پایگاه اطلاعاتی:",
+ "config-db-name-help": "نامی را انتخاب کنید که ویکی شما را شناسایی کند.\nنباید شامل فاصله باشد.\nاگر از گروه شبکهٔ اشتراک‌گذاری استفاده می‌کنید، تهیه‌کنندهٔ گروهتان یا باید به شما نام یک پایگاه اطلاعاتی مشخص برای استفاده بدهد یا برای ایجاد پایگاه‌های اطلاعاتی از طریق یک کنترل پنل به شما اجازه بدهد.",
+ "config-db-name-oracle": "طرح کلی پایگاه اطلاعاتی:",
+ "config-db-account-oracle-warn": "برای نصب برنامهٔ اوراکل به‌عنوان پایگاه اطلاعاتی در بخش گذشته،سه سناریو پشتیبانی شده است:\nاگر مایل به ایجاد حساب پایگاه اطلاعاتی به عنوان بخشی از روند نصب هستید، لطفاً یک حساب با نقش اس‌وای‌اس‌دی‌بی‌ای به عنوان حساب پایگاه اطلاعاتی برای نصب تهیه کنید و اعتبارنامه‌های مطلوبی را برای حساب دردسترس شبکه تعیین کنید، به عبارتی دیگر یا می‌توانید حساب دردسترس شبکه را به طور دستی ایجاد کنید و تنها آن حساب را تهیه کنید (اگر مستلزم مجوزهایی برای ایجاد موضوعات طرح کلی باشد) یا دو حساب دیگر تهیه کنید،یکی با ایجاد مزایا و یک حساب محدود برای دسترسی شبکه.\nمتنی برای ایجاد یک حساب با مزایای لازم بنویسید که می‌تواند در فهرست\"نگهداری/برنامهٔ اوراکل\" این نصب یافت شود. به یاد داشته باشید که استفاده از یک حساب محدود،همهٔ قابلیت‌های نگهداری با حساب پیش‌فرض را غیرفعال خواهد کرد.",
+ "config-db-install-account": "حساب کاربری برای نصب",
+ "config-db-username": "نام کاربری پایگاه اطلاعات:",
+ "config-db-password": "گذرواژه پایگاه داده‌ها:",
+ "config-db-password-empty": "لطفاً یک رمز عبور برای کاربر تازه پایگاه اطلا‌عاتی وارد کنید: $1\nدر صورتی که ممکن است کاربران بدون رمز عبور به وجود آیند،امن نیست.",
+ "config-db-username-empty": "شما باید یک مقدار برای \"نام کاربری {{int:config-db-username}}\" وارد کنید",
+ "config-db-install-username": "نام کاربری را وارد کنید که برای اتصال به پایگاه اطلاعاتی در طول روند نصب استفاده خواهد‌شد.\nاین نام کاربری حساب مدیاویکی نیست; نام کاربری برای پایگاه اطلاعاتی شما است.",
+ "config-db-install-password": "رمز عبوری را وارد کنید که برای اتصال به پایگاه اطلاعاتی در طول روند نصب استفاده خواهد‌شد.\nاین رمز عبور برای حساب مدیاویکی نیست;رمز عبور برای پایگاه اطلاعاتی شما است.",
+ "config-db-install-help": "نام کاربری و رمز عبوری را وارد کنید که برای اتصال به پایگاه اطلاعاتی در طول روند نصب استفاده خواهد‌ٰشد.",
+ "config-db-account-lock": "در طی عملیات عادی از نام کاربری و رمز عبور یکسان استفاده کنید",
+ "config-db-wiki-account": "حساب کاربری برای عملیات عادی",
+ "config-db-wiki-help": "نام کاربری و رمز عبوری را وارد کنید که برای اتصال به پایگاه اطلاعاتی در طی عملیات عادی ویکی استفاده خواهید‌کرد.\nاگر حساب وجود ندارد، و نصب حساب مزایای کافی را داراست، این حساب کاربر با حداقل مزایای مورد نیاز برای عمل کردن ویکی به‌وجود خواهد‌آمد.",
+ "config-db-prefix": "جدول پیشوند پایگاه اطلاعاتی",
+ "config-db-prefix-help": "اگر نیاز دارید یک اطلاعات پایگاهی را بین چندین ویکی یا بین مدیاویکی و برنامهٔ کاربردی وب دیگری به اشتراک بگذارید،مجاز به انتخاب برای اضافه کردن یک پیشوند به همهٔ نام‌های جدول برای جلوگیری از اختلاف‌ها هستید.\nاز فاصله‌ها استفاده نکنید.\nاغلب این زمینه خالی مانده‌است.",
+ "config-db-charset": "مجموعه‌ٔ خصوصیات پایگاه اطلاعاتی",
+ "config-charset-mysql5-binary": "می‌اسکیوئل ۴.۱/۵.۰ باینری",
+ "config-charset-mysql5": "می‌اسکیوئل ۴.۱/۵.۰ یوتی‌اف-۸",
+ "config-charset-mysql4": "یوتی‌اف-۸ سازگار قدیمی مای‌اس‌کیو‌ال ۴.۰",
+ "config-charset-help": "'''هشدار:''' اگر از '''یوتی‌اف-۸ سازگار قدیمی''' در مای‌اس‌کیو‌ال ۴.۱+ استفاده می‌کنید، و متعاقباً پایگاه اطلاعاتی با <code>mysqldump</code> پشتیبانی می‌شود، ممکن است همهٔ علامت‌های غیر ای‌اس‌سی‌آی‌آی بزرگ را نابود کند، پشتیبانی شما را به طور غیر قابل برگشتی از بین ببرد!\nدر '''روش دو واحدی'''، مدیاویکی متن یوتی‌اف-۸ را به پایگاه اطلاعاتی در زمینه‌های دو واحدی ذخیره می‌کند.\nاین روش کارآمدتر از روش یوتی‌اف-۸ مای‌اس‌کیو‌ال است، و به شما اجازه می دهد که از همهٔ محدودهٔ علائم یونیکد استفاده کنید.\nدر '''روش یوتی‌اف-۸'''، مای‌اس‌کیو‌ال باخبر خواهدبود چه علامتی اطلاعات شما را در آن تنظیم می‌کند، و می‌تواند ارائه کند و به طور صحیح تبدیل کند، اما به شما اجازه نخواهد داد که علائم بالای [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] را ذخیره کنید.",
+ "config-mysql-old": "مای‌اس‌کیو‌ال نسخهٔ $1 و یا بالاتر نیاز است، شما نسخهٔ $2 را دارید.",
+ "config-db-port": "درگاه پایگاه‌داده:",
+ "config-db-schema": "طرح کلی برای مدیاویکی:",
+ "config-db-schema-help": "طرح کلی اغلب بی‌نقص خواهد بود.\nاگر می‌دانید نیاز دارید که تغییرش دهید،آن را تغییر دهید.",
+ "config-pg-test-error": "نمی‌توان به پایگاه اطلاعاتی '''$1''' وصل شد: $2",
+ "config-sqlite-dir": "فهرست اطلاعات اس‌کیو‌لایت:",
+ "config-sqlite-dir-help": "اس‌کیولایت همهٔ اطلاعات را در یک پوشهٔ جداگانه ذخیره می‌کند.\nفهرستی را که به وجود‌ آوردید باید در طی نصب به‌ وسیلهٔ وب‌سرور قابل نوشتن باشد.\nنباید از طریق وب در دسترس باشد،به همین دلیل ما آن را در جایی که پوشه‌های پی‌اچ‌پی شما هست، قرار نمی‌دهیم.\nنصب کننده یک پوشهٔ <code>.htaccess</code> همراه آن خواهدآورد،اما اگر این کار را انجام ندهد،کسی می‌تواند به پایگاه اطلاعاتی شما دسترسی پیدا کند.\nاطلاعات خام کاربر شامل (آدرس‌های رایانامه،علامت‌‌ها با شماره‌های رمز عبور) به خوبی پاک کردن تغییرات و دیگر اطلاعات محرمانه در ویکی.\nقرار دادن پایگاه اطلاعاتی باهم را در جایی دیگر در نظر بگیرید، برای مثال در <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "جدول پیش فرض:",
+ "config-oracle-temp-ts": "جدول موقت:",
+ "config-type-mysql": "مای‌اس‌کیو‌ال (یا سازگار)",
+ "config-type-mssql": "سرور مایکروسافت اس‌کیو‌ال",
+ "config-support-info": "مدیاویکی سامانه‌های پایگاه اطلاعاتی زیر را حمایت می‌کند:\n$1\nاگر متوجه سامانه پایگاه اطلاعاتی که سعی دارید از فهرست زیر استفاده کنید، نمی‌شوید، بنابراین دستورالعمل‌های مرتبط در بالا را برای فعال‌کردن پشتیبانی دنبال کنید.",
+ "config-dbsupport-mysql": "*[{{int:version-db-mysql-url}} MySQL] مهم‌ترین هدف برای مدیاویکی است و بهترین پشتیبانی. مدیاویکی همچنین کار می‌کند با [{{int:version-db-mariadb-url}} MariaDB] و [{{int:version-db-percona-url}} Percona Server] که با MySQL سازگار هستند.([http://www.php.net/manual/en/mysqli.installation.php چگونه php را با MySQL کامپایل کنیم])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] یک منبع آزاد پر‌طرفدار دستگاه پایگاه اطلاعاتی به عنوان یک غیرمتعارف برای مای‌اس‌کیوال است.ممکن است عیوب بارز مختصری باشد، و برای استفاده در یک محیط تولیدی توصیه نمی‌شود.([http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support])",
+ "config-dbsupport-sqlite": "*[{{int:version-db-sqlite-url}} اس‌کیولایت] یک سامانه پایگاه اطلاعاتی کم حجمی است که بسیار خوب پشتیبانی شده‌است.\n([http://www.php.net/manual/en/pdo.installation.php چگونگی کامپایل پی‌اچ‌پی با اس‌کیولایت]، از PDO استفاده می‌کند)",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] یک پایگاه اطلاعاتی کار تبلیغاتی است.\n([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] یک پایگاه اطلاعاتی موسسهٔ تبلیغاتی برای وینذوز است. ([http://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
+ "config-header-mysql": "تنظیمات مای‌اس‌کیو‌ال",
+ "config-header-postgres": "تنظیمات پست‌گر‌اس‌کیو‌ال",
+ "config-header-sqlite": "تنظیمات اس‌کیو‌لایت",
+ "config-header-oracle": "تنظیمات اوراکل",
+ "config-header-mssql": "تنظیمات سرور مایکرپسافت اس‌کیو‌ال",
+ "config-invalid-db-type": "نوع پایگاه اطلاعاتی نامعتبر",
+ "config-missing-db-name": "شما باید یک مقدار برای \"نام {{int:config-db-name}}\" وارد کنید",
+ "config-missing-db-host": "شما باید یک مقدار برای \"گروه {{int:config-db-host}}\" وارد کنید",
+ "config-missing-db-server-oracle": "شما باید یک مقدار برای \"تی‌ان‌اس {{int:config-db-host-oracle}}\" وارد کنید",
+ "config-invalid-db-server-oracle": "تی‌ان‌اس پایگاه اطلاعاتی $1 نامعتبر.\nیا از \"نام تی‌ان‌اس\" یا یک سلسله \"ارتباط آسان\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]) استفاده کنید.",
+ "config-invalid-db-name": "نام پایگاه اطلاعاتی نامعتبر \"$1\".\nفقط حروف اِی‌اس‌سی‌آی‌آی بزرگ (اِی-زدکوچک،اِی-زد بزرگ)،اعداد (۰-۹)،آندرلاین (_) و خط تیره کوتاه (-) استفاده کنید.",
+ "config-invalid-db-prefix": "پیشوند پایگاه اطلاعاتی نامعتبر \"$1\".\nفقط حروف اِی‌اس‌سی‌آی‌آی بزرگ (اِی-زدکوچک،اِی-زد بزرگ)،اعداد (۰-۹)،آندرلاین (_) و خط تیره کوتاه (-) استفاده کنید.",
+ "config-connection-error": "$1.\n\nمیزبان، نام کاربری و گذرواژه را بررسی کنید و دوباره امتحان کنید.",
+ "config-invalid-schema": "طرح‌کلی برای مدیاویکی نامعتبر \"$1\".\nفقط حروف اِی‌اس‌سی‌آی‌آی بزرگ (اِی-زدکوچک،اِی-زد بزرگ)،اعداد (۰-۹)،آندرلاین (_) و خط تیره کوتاه (-) استفاده کنید.",
+ "config-db-sys-create-oracle": "نصب‌کننده تنها از استفادهٔ یک حساب اس‌وای‌اس‌دی‌بی‌اِی برای ایجاد یک حساب جدید حمایت می‌کند.",
+ "config-db-sys-user-exists-oracle": "حساب کاربری \"$1\" در‌حال‌حاضر وجود دارد.تنها اس‌وای‌اس‌دی‌بی‌اِی می‌تواند برای ایجاد یک حساب جدید استفاده شود!",
+ "config-postgres-old": "پستگِرِاس‌کیو‌ال نسخهٔ $1 یا بالاتر لازم است. شما نسخهٔ $2 را دارید.",
+ "config-mssql-old": "سرور مایکروسافت اس‌کیو‌ال $1 یا اخیر آن لازم است. شما $2 را دارید.",
+ "config-sqlite-name-help": "نامی را انتخاب کنید که ویکی شما را شناسایی می‌کند.\nاز فاصله‌ها یا خط‌های تیره کوتاه استفاده نکنید.\nاین برای نام پوشهٔ اطلاعات اس‌کیولایت استفاده خواهد‌شد.",
+ "config-sqlite-parent-unwritable-group": "فهرست اطلاعات <code><nowiki>$1</nowiki></code> نمی‌تواند ایجاد شود، چون فهرست منشأ <code><nowiki>$2</nowiki></code> توسط سرور شبکه قابل نوشتن نیست.\nنصب کننده، کاربری را که سرور شبکه شما را اجرا می‌کند، مشخص کرده‌است.\nبرای ادامه دادن،فهرستی قابل نوشتن <code><nowiki>$3</nowiki></code> توسط آن ایجاد کنید.\nدر یک سامانه یونیکس/لینوکس انجام می‌دهد:\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "فهرست اطلاعات <code><nowiki>$1</nowiki></code> نمی‌تواند ایجاد شود، چون فهرست منشأ <code><nowiki>$2</nowiki></code> توسط کارساز شبکه قابل نوشتن نیست.\nنصب کننده، کاربری را که سرور شبکه شما را اجرا می‌کند، نتوانست مشخص کند.\nفهرست کلی قابل نوشتن <code><nowiki>$3</nowiki></code> توسط آن (و دیگران!) برای ادامه دادن،ایجاد کنید.\nدر یک سامانه یونیکس/لینوکس انجام می‌دهد:\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "خطا در ایجاد فهرست اطلاعات \"$1\".\nمکان حافظهٔ رایانه را بررسی کنید و دوباره سعی کنید.",
+ "config-sqlite-dir-unwritable": "قادر نبودن در نوشتن فهرست \"$1\".\nمجوز‌هایی را که وب‌سرور می‌تواند برای آن مطرح کند را تغییر دهید، و دوباره سعی کنید.",
+ "config-sqlite-connection-error": "$1\nفهرست اطلا‌عات‌ و نام پایگاه اطلا‌عاتی زیر را بررسی کنید و دوباره سعی کنید.",
+ "config-sqlite-readonly": "پوشهٔ <code>$1</code> قابل نوشتن نیست.",
+ "config-sqlite-cant-create-db": "پوشه‌ٔ پایگاه اطلا‌عاتی <code>$1</code>نتوانست ایجاد شود.",
+ "config-sqlite-fts3-downgrade": "پی‌اچ‌پی، پشتیبانی اف‌تی‌اس۳ کار نمی‌کند، کاهش جدول‌ها",
+ "config-can-upgrade": "جدول‌های مدیاویکی در این پایگاه اطلاعاتی وجود دارند.\nبرای ارتقاء دادن آنها به مدیاویکی $1، '''ادامه''' را کلیک کنید.",
+ "config-upgrade-done": "تکمیل ارتقاء.\nاکنون شما می‌توانید [$1 start using your wiki].\nاگر می‌خواهید پوشهٔ <code>LocalSettings.php</code> را احیا کنید،دکمهٔ زیر را کلیک کنید.\nاین ''' توصیه نمی‌شود ''' مگر اینکه با ویکی خود مشکل داشته باشید.",
+ "config-upgrade-done-no-regenerate": "ارتقاء کامل شد.\nاکنون شما می‌توانید [$1 start using your wiki].",
+ "config-regenerate": "بازسازی LocalSettings.php ←",
+ "config-show-table-status": "سوأل <code>SHOW TABLE STATUS</code> مردود شد!",
+ "config-unknown-collation": "'''هشدار:''' پایگاه اطلاعاتی درحال استفاده از صفحه‌بندی ناشناخته.",
+ "config-db-web-account": "حساب پایگاه اطلاعاتی برای دسترسی وب",
+ "config-db-web-help": "در طی عملیات عادی ویکی،نام کاربری و رمز عبوری را انتخاب کنیدکه وب سرور برای اتصال به سرور پایگاه اطلاعاتی استفاده کنید.",
+ "config-db-web-account-same": "هنگام نصب از حساب یکسان استفاده کنید",
+ "config-db-web-create": "اگر در حال‌حاضر وجود ندارد،حساب ایجاد کنید",
+ "config-db-web-no-create-privs": "حسابی که شما برای نصب تعیین کردید،مزایای کافی برای ایجاد یک حساب را ندارد.\nحسابی که شما اینجا تعیین کرده‌اید باید در حال حاضر وجود داشته باشد.",
+ "config-mysql-engine": "موتور ذخیره سازی:",
+ "config-mysql-innodb": "اینودی‌بی",
+ "config-mysql-myisam": "می‌ای‌سم",
+ "config-mysql-myisam-dep": "'''هشدار:''' شما مای‌آی‌اس‌ای‌ام را به عنوان موتور ذخیره برای مای‌آی‌اس‌ای‌ام انتخاب کرده‌اید، که برای استفاده با مدیاویکی توصیه نمی‌شود زیرا:\n* به‌علت قفل شدن جدول اجمالاً به طور همزمان پشتیبانی می کند\n* بیشتر از دیگر موتورها برای از بین‌ رفتن مستعد است.\n* مبنای رمز مدیاویکی همیشه مای‌آی‌اس‌ای‌ام را همان طور که باید باشد،کنترل نمی‌کند\nاگر نصب مای‌اس‌کیو‌ال شما اینودی‌بی را پشتیبانی می‌کند،بسیار توصیه می‌شود که در عوض ،آن را انتخاب کنید.\nاگر نصب مای‌اس‌کیو‌ال شما، اینودی‌بی را پشتیبانی نمی‌کند، ممکن است زمان ارتقاء رسیده باشد.",
+ "config-mysql-only-myisam-dep": "'''هشدار:''' مای‌آی‌اس‌ای‌ام تنها موتور ذخیره‌سازی اطلاعات برای مای‌اس‌کیو‌ال در این دستگاه است، و برای استفاده با مدیاویکی توصیه نمی‌شود، زیرا:\n* به‌علت قفل شدن جدول اجمالاً به طور همزمان پشتیبانی می کند\n* بیشتر از دیگر موتورها برای از بین‌ رفتن مستعد است.\n* مبنای رمز مدیاویکی همیشه مای‌آی‌اس‌ای‌ام را همان طور که باید باشد،کنترل نمی‌کند\nنصب مای‌اس‌کیو‌ال شما اینودی‌بی را پشتیبانی نمی‌کند،ممکن است زمان یک ارتقاء رسیده باشد.",
+ "config-mysql-engine-help": "'''اینودی‌بی''' تقریباً همیشه بهترین گزینه است،زیرا پشتیبانی همزمان خوبی دارد.\n'''مای‌آی‌اس‌ای‌ام''' ممکن است در نصب‌های کاربر جداگانه یا فقط خواندنی سریع‌تر باشد.\nپایگاه‌های اطلاعاتی مای‌آی‌اس‌ای‌ام اغلب بیشتر از پایگاه‌های اطلاعاتی اینودی‌بی مستعد ازبین رفتن هستند.",
+ "config-mysql-charset": "مجموعه‌ٔ خصوصیات پایگاه اطلاعاتی:",
+ "config-mysql-binary": "دودویی",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "در حالت باینری مدیاویکی متن را به صورت UTF-8 در پایگاه داده باینری ذخیره می‌کند.\nاین روش بسیار به صرفه‌تر از حالت UTF-8 برای MySQL هست و به شما اجازهٔ استفاده از همه بازهٔ نویسه‌های یونیکد را می دهد.\n\nدر حالت UTF-8 برنامه MySQL به شما اجازه می‌دهد که کدام نویسه‌ها در داده‌های شما باشند و اجازهٔ تبدیل و حضور آنها را به صورت مطلوبی می‌دهد ولی به شما اجازهٔ ذخیرهٔ نویسه‌های بالای [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes نقشه ابتدائی چندزبانی] را می‌دهد.",
+ "config-mssql-auth": "نوع تأیید:",
+ "config-mssql-install-auth": "نوع تأییدی را که برای اتصال به پایگاه اطلاعاتی حین فرآیند نصب مورد استفاده قرار گیرد را انتخاب کنید.\nاگر \"{{int:config-mssql-windowsauth}}\" را انتخاب می‌کنید، اعتبارات هرآنچه کاربر وب سرور به عنوان آن مورد استفاده قرار می‌دهد مورد استفاده قرار خواهد گرفت.",
+ "config-mssql-web-auth": "نوع تأییدی را که کارساز وب به‌وسیلهٔ آن برای کارهای معمولی به پایگاه اطلاعاتی متصل خواهد شد را انتخاب کنید.\nاگر «{{int:config-mssql-windowsauth}}» را انتخاب می‌کنید، اعتبارات هرآنچه کاربر وب سرور به عنوان آن مورد استفاده قرار می‌دهد مورد استفاده قرار خواهد گرفت.",
+ "config-mssql-sqlauth": "تأیید سرور اس‌کیوال",
+ "config-mssql-windowsauth": "تأیید ویندوز",
+ "config-site-name": "نام ویکی:",
+ "config-site-name-help": "این در نوار عنوان مرورگر و در دیگر جاهای مختلف ظاهر خواهد‌شد.",
+ "config-site-name-blank": "نام تارنما را وارد کنید.",
+ "config-project-namespace": "فضای نام پروژه:",
+ "config-ns-generic": "پروژه",
+ "config-ns-site-name": "مشابه نام ویکی: $1",
+ "config-ns-other": "دیگر ( تعیین کنید)",
+ "config-ns-other-default": "ویکی‌من",
+ "config-project-namespace-help": "مثال‌های ویکی‌پدیا. بسیاری از ویکی‌ها سیاست‌هایشان را در فضای نام غیر پروژه ذخیره می‌کنند.\n\nهمه عنوان‌های صفحات در این فضای نام توسط پیشوند متفاوت جدا می‌شوند که شما می‌توانید اینجا مشخص کنید.\nمعمولاً این پیشوند برگرفته از نام ویکی هستند ولی نمی تواند حروف سجاوندی در نام آن باشد مانند \"#\" یا \":\".",
+ "config-ns-invalid": "فضای نامی تعیین شدهٔ \"<nowiki>$1</nowiki>\" نامعتبر است.\nیک برنامهٔ فضای نامی دیگری را تعیین کنید.",
+ "config-ns-conflict": "فضای نامی تعیین شدهٔ \"<nowiki>$1</nowiki>\" با یک فضای نامی پیش‌فرض مدیاویکی مغایرت دارد.\nیک برنامهٔ فضای نامی دیگری را تعیین کنید.",
+ "config-admin-box": "حساب مدیر سامانه",
+ "config-admin-name": "نام کاربری شما:",
+ "config-admin-password": "گذرواژه:",
+ "config-admin-password-confirm": "دوباره گذرواژه:",
+ "config-admin-help": "نام کاربری مورد علاقهٔ خود را اینجا وارد کنید، برای مثال \" جو بلاگز \".\nاین نامی است که شما برای ورود به ویکی استفاده خواهید‌کرد.",
+ "config-admin-name-blank": "نام کاربری سرپرست را وارد کنید.",
+ "config-admin-name-invalid": "نام کاربری تعیین شدهٔ \"<nowiki>$1</nowiki>\" نامعتبر است.\nیک نام کاربری دیگر تعیین کنید.",
+ "config-admin-password-blank": "برای حساب سرپرست یک رمز عبور وارد کنید.",
+ "config-admin-password-mismatch": "دو رمز عبوری که وارد کرده‌اید با هم مطابقت ندارند.",
+ "config-admin-email": "رایانامهٔ شما:",
+ "config-admin-email-help": "یک آدرس رایانامه برای اجازهٔ دریافت رایانامه از دیگر کاربران ویکی،اینجا وارد کنید، رمز عبور خود را دوباره تنظیم کنید، و از تغییرات صفحه در فهرست پیگیریها مطلع باشید.می‌توانید این زمینه را خالی بگذارید.",
+ "config-admin-error-user": "خطای داخلی هنگام ایجاد یک مدیر با نام \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "خطای داخلی هنگام تنظیم یک رمز عبور برای مدیر \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "شما آدرس رایانامهٔ نامعتبر وارد کرده‌اید.",
+ "config-subscribe": "عضویت در [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release announcements mailing list].",
+ "config-subscribe-help": "این یک میلینگ لیست کم حجم است که برای اطلاع‌رسانی کاربرد دارد و شامل اطلاعیه‌های امنیتی می‌شود.\nشما باید در آن ثبت نام کنید و زمانی که نسخهٔ جدید مدیاویکی ارائه شد آن را به‌روز نمائید.",
+ "config-subscribe-noemail": "شما بدون ثبت رایانامه‌تان قصد داشتید در فهرست پستی ثبت‌نام کنید.\nاگر قصد ثبت‌نام دارید لطفاً رایانامه‌ای مشخص کنید.",
+ "config-almost-done": "شما تقریباً عملیات را کامل کرده‌اید.\nاکنون می‌توانید پیکربندی باقیمانده را نخوانده رها کنید و درحال‌حاضر ویکی را نصب کنید.",
+ "config-optional-continue": "سوال‌های بیشتری از من بپرسید.",
+ "config-optional-skip": "درحال‌حاضر خسته‌ام،سریعاً ویکی را نصب کنید.",
+ "config-profile": "شرح‌حال حقوق کاربر:",
+ "config-profile-wiki": "بازکردن ویکی",
+ "config-profile-no-anon": "ساخت کاربری مورد نیاز است",
+ "config-profile-fishbowl": "فقط کاربر تأیید شده",
+ "config-profile-private": "ویکی خصوصی",
+ "config-profile-help": "زمانی ویکی درست کار می کند که شما اجازه دهید تعداد زیادی از مردم آن را ویرایش کنند.\nدر مدیاویکی امکان مشاهدهٔ تغییرات اخیر و واگردانی ویرایش‌های خرابکاری به آسانی وجود دارد.\n\nبا وجودی که مدیا ویکی منافع بسیاری برای مردم دارد ولی متقاعد کردن خیلی از مردم درباره روش کار ویکی‌ها کار آسانی نیست.\n\nدر نتیجه شما دو انتخاب دارید.\n\n'''{{int:config-profile-wiki}}''' به همه کاربرها اجازهٔ ویرایش می دهد حتی بدون ثبت‌نام.\n\nیک ویکی که دارای '''{{int:config-profile-no-anon}}''' باشد امکانات کاربری بیشتری ارائه می‌دهد ولی امکان دارد ویرایشگران عادی را نگران کند.\n\nسناریوی '''{{int:config-profile-fishbowl}}''' به کاربرها اجازهٔ ویرایش می دهد ولی همه می توانند متن و تاریخچه را ببیند.\n\n'''{{int:config-profile-private}}''' فقط به کاربران اجازهٔ مشاهدهٔ مطالب را می‌دهد و فقط آنها می توانند ویرایش کنند.\n\nدسترسی‌های بیشتر کاربری بعد از نصب در [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights راهنماهای مرتبط] موجود است.",
+ "config-license": "حق تکثیر و مجوز:",
+ "config-license-none": "بدون پاورقی مجوز",
+ "config-license-cc-by-sa": "اشتراک گذاری یکجور استناد رایج سازنده",
+ "config-license-cc-by": "استناد رایج سازنده",
+ "config-license-cc-by-nc-sa": "اشتراک گذاری یکجور استناد رایج سازندهٔ غیر تجاری",
+ "config-license-cc-0": "مبداء عوام سازنده (حیطهٔ عمومی)",
+ "config-license-gfdl": "مجوز اسنادومدارک آزاد جی‌ان‌یو ۱.۳ یا بالاتر",
+ "config-license-pd": "مالکیت عمومی",
+ "config-license-cc-choose": "انتخاب یک مجوز سفارشی عوام خلاق",
+ "config-license-help": "بسیاری از وبگاه‌ها ویرایش‌های ها را با [http://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": "اگر برای کار کردن رایانامه می‌خواهید [http://www.php.net/manual/en/mail.configuration.php PHP's mail settings] نیازمند پیکربندی صحیح است.\nاگر هیچ ویژگی رایانامه را نمی‌خواهید، می‌توانید آنها را اینجا غیر‌فعال کنید.",
+ "config-email-user": "فعال‌کردن رایانامهٔ کاربر به کاربر",
+ "config-email-user-help": "به همهٔ کاربرانی که ارسال رایانامه را در اولویت‌های خود فعال کرده‌اند،اجازه خواهد داده‌شد که رایانامه خود را به یکدیگر ارسال کنند.",
+ "config-email-usertalk": "فعال‌کردن اطلاع‌رسانی صفحهٔ بحث کاربر",
+ "config-email-usertalk-help": "به همهٔ کاربرانی که دریافت اطلاعیه را در اولویت‌های خود فعال کرده‌اند،اجازه خواهد داده‌شد که اطلاعیه‌ها را در صفحهٔ تغییر گفت‌وگوی کاربر دریافت کنند.",
+ "config-email-watchlist": "فعال‌کردن اطلاع‌رسانی فهرست پیگیری‌ها",
+ "config-email-watchlist-help": "به همهٔ کاربرانی که مشاهدهٔ صفحه را در اولویت‌های خود فعال کرده‌اند،اجازه خواهد داده‌شد که اطلاعیه‌های در رابطه با صفحات مشاهده شده را دریافت کنند.",
+ "config-email-auth": "فعال‌کردن تأیید اعتبار رایانامه",
+ "config-email-auth-help": "اگر این مورد را فعال کنید، کاربران باید رایانامه خود را با استفاده از پیوند تأییدهٔ که به رایانامه‌یشان فرستاده می‌شود، تأیید کنند. \nدر این صورت تنها رایانامه‌هایی که تأیید شده‌باشند، می‌توانند از سامانه در هنگام تغییرات رایانامه دریافت کنند.\nبرای ویکی‌هایی که به صورت عمومی استفاده می‌شوند، فعال‌کردن این گزینه پیشنهاد می‌شود.",
+ "config-email-sender": "بازگشت به رایانامه:",
+ "config-email-sender-help": "آدرس رایانامه‌ای را وارد کنید که هنگام مراجعت آدرس به رایانامهٔ خارجی استفاده می‌شود.\nبه جایی که پیام‌ها برگشت داده می‌شوند، فرستاده خواهد‌شد.\nبسیاری از سرورهای پستی حداقل به بخش نام عمومی معتبر نیاز دارند.",
+ "config-upload-settings": "بارگذاری‌های پرونده و تصویر",
+ "config-upload-enable": "فعال‌سازی بارگذاری پرونده",
+ "config-upload-help": "بارگذاری پرونده بصورت بالقوه می‌تواند کارساز شما در معرض خطرات امنیتی قرار بدهد. برای کسب اطلاعات بیشتر لطفاً [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security قسمت امنیتی] را مطالعه کنید.\n\nبرای اجازه‌دادن به بارگذاری پرونده‌ها٬ حالت زیر مجموعه <code>images</code> در پوشه ریشه مدیاویکی را تغییر دهید که کارسازهای وب قادر به نوشتن بر روی آن باشند. سپس این قابلیت را فعال کنید.",
+ "config-upload-deleted": "فهرست برای پوشه‌های حذف شده:",
+ "config-upload-deleted-help": "فهرستی برای بایگانی کردن پوشه‌های حذف شده انتخاب کنید.\nبه طور مطلوب،از شبکه نباید در دسترس باشد.",
+ "config-logo": "نشانی نامواره:",
+ "config-logo-help": "پوستهٔ پیش‌فرض مدیاویکی شامل مکانی برای یک آرم ۱۳۵x۱۶۰ پیکسلی بالای منوی نوارکناری است.\nیک عکس با اندازهٔ مناسب ارسال کنید، و یوآرال را اینجا وارد کنید.\nاگر آرم شما با آن راه‌ها مزتبط است،می‌توانید از <code>$wgStylePath</code> یا <code>$wgScriptPath</code> استفاده کنید.\nاگر آرم نمی‌خواهید، این جعبه را خالی رها کنید.",
+ "config-instantcommons": "فعال‌کردن فوری کامنز",
+ "config-instantcommons-help": "[//www.mediawiki.org/ ویکی و InstantCommons ویکی‌انبار فوری] یک ویژگی‌است که به شما اجازه می‌دهد تا تصاویر، صداها یا سایر رسانه‌های یافته شده بر روی [//commons.wikimedia.org/ انبار ویکی مدیا] را استفاده کنید.\n\nبرای استفاده از این ویژگی مدیاویکی نیازمند دسترسی به اینترنت است.\n\nبرای کسب اطلاعات بیشتر درباره این ویژگی٬ شامل دستورالعمل‌های برای چگونگی نصب آن برای سایر ویکی‌های بجز ویکی‌انبار لطفاً از [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos the نصب دستی] استفاده کنید.",
+ "config-cc-error": "مجوز چوزر عوام سازنده بی‌نتیجه ماند.\nنام مجوز را دستی وارد کنید.",
+ "config-cc-again": "انتخاب دوباره...",
+ "config-cc-not-chosen": "مجوز عوام سازنده‌ای را که می‌خواهید انتخاب کنید و \"ادامه\" را کلیک کنید.",
+ "config-advanced-settings": "تنظیمات پیشرفته",
+ "config-cache-options": "تنظیمات برای ذخیره شی:",
+ "config-cache-help": "کش شی برای بهتر شدن سرعت مدیا ویکی، از طریق کش کردن داده‌های با بیشترین استفاده انجام می‌گیرد.\nوبگاه‌های متوسط تا بزرگ به انجام این کار شدیدا توصیه می‌شوند، در عین حال وبگاه‌های کوچک نیز می‌توانند از مزایای ایم مورد بهره ببرند.",
+ "config-cache-none": "بدون ذخیره (هیچ کارآمدی پاک نشده‌است، اما ممکن است سرعت در سایت‌های بزرگتر ویکی تأثیر داشته باشد)",
+ "config-cache-accel": "ذخیرهٔ موضوع پی‌اچ‌پی (ای‌پی‌سی، ایکس‌کیچ یا وین‌کیچ)",
+ "config-cache-memcached": "از ممکچد (که نیازمند تنظیمات اضافی و پیکربندی) استفاده کنید",
+ "config-memcached-servers": "سرورهای دریافت حافظه:",
+ "config-memcached-help": "فهرست آدرس‌های آی‌پی برای استفاده از ممکچد.\nباید هر یک خط را تعیین کند و درگاه مورد استفاده را تعیین کند.برای مثال: \n۱۲۷.۰.۰.۱:۱۱۲۱۱\n۱۹۲.۱۶۸.۱.۲۵:۱۲۳۴",
+ "config-memcache-needservers": "شما ممکچد را به عنوان نوع ذخیرهٔ خود انتخاب کرده‌اید اما هیچ سروری تعیین نشده‌.",
+ "config-memcache-badip": "آدرس آی‌پی نامعتبر برای مِمکَچد وارد کرده‌اید: $1.",
+ "config-memcache-noport": "شما درگاهی برای استفاده از سرور ممکچد تعیین نکرده بودید: $1\nاگر از درگاه مطلع نیستید، پیش‌فرض ۱۱۲۱۱ است.",
+ "config-memcache-badport": "اعداد درگاه ممکچد باید بین $1 و $2 باشد.",
+ "config-extensions": "افزونه‌ها",
+ "config-extensions-help": "لیست وسیع بالا در فهرست <code>./extensions</code> شما یافت شد.\nممکن است نیازمند پیکربندی اضافه باشند، اما اکنون می‌توانید آنها را فعال کنید.",
+ "config-skins": "پوسته‌ها",
+ "config-skins-help": "پوسته های ذکر شده در بالا در <code>./skins</code> پوشهٔ شما شناسایی شده است. شما باید حداقل یکی را فعال و پیش فرض کنید.",
+ "config-skins-use-as-default": "این پوست را به عنوان پیش فرض استفاده کنید",
+ "config-skins-missing": "هیچ پوسته‌ای انتخاب نشده‌است‌‌، تا زمانی که یک پوستهٔ مناسب نصب کنید مدیاویکی از پوسته ذخیره‌شده استفاده می‌کند",
+ "config-skins-must-enable-some": "شما باید حداقل یک پوست برای فعال کردن انتخاب کنید.",
+ "config-skins-must-enable-default": "پوست انتخاب شده به عنوان پیش فرض باید فعال شده باشد.",
+ "config-install-alreadydone": "'''هشدار:''' به نظر می‌رسد در حال حاضر شما مدیاویکی را نصب کرده‌اید و دوباره سعی میکنید آن را نصب کنید.\nلطفاً به صفحهٔ بعدی بروید.",
+ "config-install-begin": "با فشاردادن \"{{int:config-continue}}\"، نصب مدیاویکی را آغاز خواهید‌کرد.\nاگر هنوز می‌خواهید تغییرات ایجاد کنید، \"{{int:config-back}}\" را فشار دهید.",
+ "config-install-step-done": "انجام شد",
+ "config-install-step-failed": "ناموفق بود",
+ "config-install-extensions": "افزونه‌های مشمول",
+ "config-install-database": "نصب پایگاه داده",
+ "config-install-schema": "ایجاد طرح کلی",
+ "config-install-pg-schema-not-exist": "طرح کلی PostgreSQL وجود ندارد.",
+ "config-install-pg-schema-failed": "ایجاد جدول‌ها با شکست روبه‌رو شد.\nمطمئن شوید که کاربر \"$1\" می‌تواند طرح کلی \"$2\" را بنویسد.",
+ "config-install-pg-commit": "ارسال تغییرات",
+ "config-install-pg-plpgsql": "بررسی زبان پی‌ال/پی‌جی‌اس‌کیو‌ال",
+ "config-pg-no-plpgsql": "شما ملزم به نصب زبان پی‌ال/پی‌جی‌اس‌کیو‌ال در پایگاه اطلاعاتی $1 هستید",
+ "config-pg-no-create-privs": "حسابی که شما برای نصب تعیین کردید، مزایای کافی برای ایجاد یک حساب را ندارد.",
+ "config-pg-not-in-role": "حسابی که شما برای کاربر وب مشخص نموده‌اید در حال حاضر موجود است.\nحسابی که شما مشخص نموده‌اید جزو گروه کاربران سوپریوزر نیست و همچین عضو نقش کار وب نیست بنابراین قادر به ایجاد اشیایی تحت مالکیت کاربر وب نیست.\n\nجداول در مدیاویکی همکنون باید تحت مالکیت کاربر وب در بیاییند. لطفاً حساب کاربری وب دیگری را معین کنید و یا بر روی «برگشت» کلیک و کاربری با صحت امتیاز مناسب برای نصب انتخاب کنید.",
+ "config-install-user": "ایجاد بانک اطلاعاتی کاربر",
+ "config-install-user-alreadyexists": "کاربر \"$1\" در حال حاضر موجود است",
+ "config-install-user-create-failed": "ایجاد کاربر \"$1\" انجام نشد:$2",
+ "config-install-user-grant-failed": "اعطای مجوز به کاربر \"$1\" با شکست روبه‌رو شد: $2",
+ "config-install-user-missing": "کاربر تعیین شدهٔ \"$1\" وجود ندارد.",
+ "config-install-user-missing-create": "کاربر تعیین شدهٔ \"$1\" وجود ندارد.\nاگر می‌خواهید حساب ایجاد کنید،لطفاً جعبهٔ بررسی \"ایجاد حساب\" را کلیک کنید.",
+ "config-install-tables": "ایجاد جداول",
+ "config-install-tables-exist": "'''هشدار:''' جداول مدیاویکی به نظر می‌رسد در حال حاضر وجود دارد.\nصرف نظر از ایجاد.",
+ "config-install-tables-failed": "'''خطا:''' ایجاد جدول با خطای زیر به شکست مواجه شد: $1",
+ "config-install-interwiki": "قرار دادن پیش‌فرض جدول ویکی داخلی",
+ "config-install-interwiki-list": "پوشهٔ <code>interwiki.list</code> نتوانست خوانده شود.",
+ "config-install-interwiki-exists": "'''هشدار:''' به نظر می‌رسد جدول ویکی داخلی در حال حاضر دارای مقداری اطلاعات است.\nنادیده گرفتن فهرست پیش‌فرض.",
+ "config-install-stats": "شروع آمار",
+ "config-install-keys": "تولید کلیدهای مخفی",
+ "config-insecure-keys": "'''هشدار:''' {{PLURAL:$2|کلید امن|کلیدهای امن}} ($1) در طی نصب کاملاً ایمن {{PLURAL:$2|نیست|نیستند}}. تغییر دستی {{PLURAL:$2|آن|آنها}} را در نظر بگیرید.",
+ "config-install-updates": "جلوگیری از به روز رسانی‌های غیر ضروری در حال اجرا",
+ "config-install-updates-failed": "<strong>خطا:</strong> قراردادن کلیدهای به روز رسانی به داخل جداول با خطای روبرو مواجه شد: $1",
+ "config-install-sysop": "ایجاد حساب کاربری مدیر",
+ "config-install-subscribe-fail": "قادر تصدیق اعلام مدیاویکی نیست:$1",
+ "config-install-subscribe-notpossible": "سی‌یوآر‌ال نصب نشده‌است و <code>allow_url_fopen</code> در دسترس نیست.",
+ "config-install-mainpage": "ایجاد صفحهٔ اصلی با محتوای پیش‌فرض",
+ "config-install-extension-tables": "ایجاد جداول برای افزونه‌های فعال",
+ "config-install-mainpage-failed": "قادر به درج صفحهٔ اصلی نمی‌باشد:$1",
+ "config-install-done": "'''تبریک!'''\nبا موفقیت مدیاویکی را نصب کردید.\nبرنامه نصب‌کننده پرونده <code>LocalSettings.php</code> را درست کرد.\nکه شامل تمام تنظیمات می‌باشد.\n\nشما نیاز به دریافت آن دارید و آن را در پایهٔ نصب ویکی قرار دهید (همان پوشهٔ index.php). دریافت باید به صورت خودکار شروع شده‌باشد.\n\nاگر دریافت شروع نشد یا اگر آن را لغو کردید با کلیک روی پیوند زیر می‌توانید آن را دریافت کنید:\n\n$3\n\n'''توجه داشته باشید:''' اگر این را الآن انجام ندهید، این پرونده تولیدشده در صورتی که نصب را بدون دریافت آن تمام کردید بعداً در اختیار شما قرار نخواهد گرفت.\n\nوقتی انجام شد شما می‌توانید '''[$2 وارد ویکی شوید]'''.",
+ "config-download-localsettings": "دریافت <code>LocalSettings.php</code>",
+ "config-help": "راهنما",
+ "config-help-tooltip": "برای گسترش کلیک کنید",
+ "config-nofile": "پروندهٔ «$1» یافت نشد. آیا حذف شده‌است؟",
+ "config-extension-link": "آیا می‌دانستید که ویکی شما [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensions] را پشتیبانی می‌کند؟\nشما می‌توانید [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions by category]",
+ "mainpagetext": "'''نرم‌افزار ویکی با موفقیت نصب شد.'''",
+ "mainpagedocfooter": "از [//meta.wikimedia.org/wiki/Help:Contents راهنمای کاربران]\nبرای استفاده از نرم‌افزار ویکی کمک بگیرید.\n\n== آغاز به کار ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings تنظیم پیکربندی]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki پرسش‌های متداول]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce فهرست ارسال نسخه‌های مدیاویکی]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise مدیاویکی برای زبان شما]"
+}
diff --git a/includes/installer/i18n/fi.json b/includes/installer/i18n/fi.json
new file mode 100644
index 00000000..c40544df
--- /dev/null
+++ b/includes/installer/i18n/fi.json
@@ -0,0 +1,220 @@
+{
+ "@metadata": {
+ "authors": [
+ "Beluga",
+ "Centerlink",
+ "Crt",
+ "Nike",
+ "Olli",
+ "Silvonen",
+ "Str4nd",
+ "VezonThunder",
+ "아라",
+ "Elseweyr",
+ "Lliehu",
+ "Syreeni",
+ "Stryn"
+ ]
+ },
+ "config-desc": "MediaWiki-asennin",
+ "config-title": "MediaWikin version $1 asennus",
+ "config-information": "Tiedot",
+ "config-localsettings-upgrade": "<code>LocalSettings.php</code>-tiedosto havaittiin.\nKirjoita muuttujan <code>$wgUpgradeKey</code> arvo alla olevaan kenttään päivittääksesi asennuksen.\nLöydät sen <code>LocalSettings.php</code>-tiedostosta.",
+ "config-localsettings-cli-upgrade": "<code>LocalSettings.php</code>-tiedosto havaittiin.\nPäivitä asennus suorittamalla <code>update.php</code>.",
+ "config-localsettings-key": "Päivitysavain",
+ "config-localsettings-badkey": "Antamasi avain on virheellinen.",
+ "config-upgrade-key-missing": "Havaittiin aiempi MediaWiki-asennus.\nPäivittääksesi tämän asennuksen lisää <code>LocalSettings.php</code>-tiedostosi loppuun seuraava rivi:\n\n$1",
+ "config-localsettings-incomplete": "Nykyinen <code>LocalSettings.php</code>-tiedosto näyttää olevan puutteellinen.\nMuuttujaa $1 ei ole asetettu.\nMuuta <code>LocalSettings.php</code>-tiedostoa siten, että muuttuja on asetettu ja napsauta »{{int:Config-continue}}».",
+ "config-localsettings-connection-error": "Yhteyden muodostaminen tietokantaan epäonnistui tiedostossa <code>LocalSettings.php</code> olevien asetusten takia. Korjaa asetukset ja yritä uudelleen.\n\n$1",
+ "config-session-error": "Istunnon aloittaminen epäonnistui: $1",
+ "config-session-expired": "Istuntotietosi näyttävät olevan vanhentuneita.\nIstuntojen elinajaksi on määritelty $1.\nVoit muuttaa tätä asetusta vaihtamalla kohtaa <code>session.gc_maxlifetime</code> php.ini-tiedostossa.\nKäynnistä asennusprosessi uudelleen.",
+ "config-no-session": "Istuntosi tiedot menetettiin!\nTarkista php.ini-tiedostosi ja varmista, että <code>session.save_path</code> on asetettu sopivaan kansioon.",
+ "config-your-language": "Asennuksen kieli",
+ "config-your-language-help": "Valitse kieli, jota haluat käyttää asennuksen ajan.",
+ "config-wiki-language": "Wikin kieli",
+ "config-wiki-language-help": "Valitse kieli, jota wikissä tullaan etupäässä käyttämään.",
+ "config-back": "← Takaisin",
+ "config-continue": "Jatka →",
+ "config-page-language": "Kieli",
+ "config-page-welcome": "Tervetuloa MediaWikiin!",
+ "config-page-dbconnect": "Tietokantaan yhdistäminen",
+ "config-page-upgrade": "Olemassa olevan asennuksen päivitys",
+ "config-page-dbsettings": "Tietokannan asetukset",
+ "config-page-name": "Nimi",
+ "config-page-options": "Asetukset",
+ "config-page-install": "Asenna",
+ "config-page-complete": "Valmis!",
+ "config-page-restart": "Aloita asennus alusta",
+ "config-page-readme": "Lue minut",
+ "config-page-releasenotes": "Julkaisutiedot",
+ "config-page-copying": "Kopiointi",
+ "config-page-upgradedoc": "Päivittäminen",
+ "config-page-existingwiki": "Aikaisempi asennus",
+ "config-help-restart": "Haluatko poistaa kaikki annetut tiedot ja aloittaa asennuksen alusta?",
+ "config-restart": "Kyllä",
+ "config-welcome": "=== Ympäristön tarkistukset ===\nVarmistetaan MediaWikin asennettavuus tähän ympäristöön.\nMuista antaa nämä tiedot, jos tarvitset apua asennuksen aikana.",
+ "config-sidebar": "* [//www.mediawiki.org MediaWikin kotisivu]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Käyttöopas]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Hallintaopas]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ UKK]\n----\n* <doclink href=Readme>Lue minut</doclink>\n* <doclink href=ReleaseNotes>Julkaisutiedot</doclink>\n* <doclink href=Copying>Kopiointi</doclink>\n* <doclink href=UpgradeDoc>Päivittäminen</doclink>",
+ "config-env-good": "Asennusympäristö on tarkastettu.\nVoit asentaa MediaWikin.",
+ "config-env-bad": "Asennusympäristö on tarkastettu.\nEt voi asentaa MediaWikiä.",
+ "config-env-php": "PHP $1 on asennettu.",
+ "config-env-hhvm": "HHVM $1 on asennettu.",
+ "config-no-db": "Sopivaa tietokanta-ajuria ei löytynyt! Sinun täytyy asentaa tietokanta-ajurit PHP:lle.\nSeuraavat tietokantatyypit ovat tuettuja: $1.",
+ "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-safe-mode": "'''Varoitus:''' PHP:n [http://www.php.net/features.safe-mode safe mode] -tila on aktiivinen.\nSe voi aiheuttaa ongelmia erityisesti tiedostojen tallentamisen ja matemaattisten kaavojen kanssa.",
+ "config-xml-bad": "PHP:n XML-moduulia ei löydy.\nMediaWiki käyttää tämän moduulin funktioita, eikä toimi tässä kokoonpanossa.\nJos käytät Mandrakea, asenna php-xml paketti.",
+ "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-xcache": "[http://xcache.lighttpd.net/ XCache] on asennettu",
+ "config-apc": "[http://www.php.net/apc APC] on asennettu.",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] on asennettu",
+ "config-diff3-bad": "GNU diff3:a ei löytynyt.",
+ "config-imagemagick": "Löydettiin ImageMagick: <code>$1</code>.\nKuvien esikatselukuvat otetaan samalla käyttöön jos otetaan tiedostojen tallennus.",
+ "config-using-server": "Palvelimen nimenä käytetään \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Palvelinen URL-osoitteena käytetään \"<nowiki>$1$2</nowiki>\".",
+ "config-db-type": "Tietokannan tyyppi",
+ "config-db-host": "Tietokantapalvelin",
+ "config-db-wiki-settings": "Identifioi tämä wiki",
+ "config-db-name": "Tietokannan nimi",
+ "config-db-install-account": "Asennuksessa käytettävä käyttäjätili",
+ "config-db-username": "Tietokannan käyttäjätunnus",
+ "config-db-password": "Tietokannan salasana",
+ "config-db-password-empty": "Syötä salasana uudelle tietokannan käyttäjälle: $1.\nVaikka käyttäjä voidaan luoda ilman salasanaa, se ei ole turvallista.",
+ "config-db-install-username": "Syötä käyttäjänimi jota käytetään muodostettaessa yhteys tietokantaan asennuksen aikana.\nTämä ei ole MediaWiki tilin käyttäjänimi; tämä on tietokannan käyttäjänimi.",
+ "config-db-install-password": "Syötä salasana jota käytetään muodostettaessa yhteys tietokantaan asennuksen aikana.\nTämä ei ole MediaWiki tilin salasana; tämä on tietokannan salasana.",
+ "config-db-install-help": "Anna käyttäjätunnus ja salasana, joita käytetään asennuksen aikana.",
+ "config-db-account-lock": "Käytä samaa tunnusta ja salasanaa myös asennuksen jälkeen",
+ "config-db-wiki-account": "Käyttäjätili normaaliin käyttöön",
+ "config-db-wiki-help": "Syötä käyttäjänimi ja salasana joita käytetään muodostettaessa yhteys tietokantaan käytettäessä wikiä normaalisti.\nJos tiliä ei ole olemassa ja asennuksessa käytettävällä tilillä on riittävät käyttöoikeudet, tämä käyttäjätili luodaan käyttöoikeuksilla jotka vähintään tarvitaan wikiä varten.",
+ "config-db-prefix": "Tietokantataulujen etuliite",
+ "config-db-prefix-help": "Jos tietokantaa käytetään useammalle wikille tai MediaWikille ja muille sovelluksille, suositellaan käytettäväksi tauluissa etuliitettä, joilla ne erotetaan toisistaan ja vältetään näin virheitä.\nÄlä käytä välilyöntejä.\n\nYleensä tämä kenttä jätetään tyhjäksi.",
+ "config-db-charset": "Tietokannan merkistö",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0, binääri",
+ "config-charset-mysql5": "MySQL 4.1/5.0, UTF-8",
+ "config-charset-mysql4": "MySQL 4.0, taaksepäin yhteensopiva UTF-8",
+ "config-mysql-old": "MediaWiki tarvitsee MySQL:n version $1 tai uudemman. Nykyinen versio on $2.",
+ "config-db-port": "Tietokannan portti:",
+ "config-pg-test-error": "Tietokantaan <strong>$1 ei voida muodostaa yhteyttä</strong>: $2",
+ "config-type-mysql": "MySQL (tai yhteensopiva)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-header-mysql": "MySQL-asetukset",
+ "config-header-postgres": "PostgreSQL-asetukset",
+ "config-header-sqlite": "SQLite-asetukset",
+ "config-header-oracle": "Oracle-asetukset",
+ "config-header-mssql": "Microsoft SQL Server asetukset",
+ "config-invalid-db-type": "Virheellinen tietokantatyyppi",
+ "config-missing-db-name": "\"{{int:config-db-name}}\" on pakollinen.",
+ "config-missing-db-host": "\"{{int:config-db-host}}\" on pakollinen.",
+ "config-missing-db-server-oracle": "\"{{int:config-db-host-oracle}}\" on pakollinen.",
+ "config-invalid-db-name": "”$1” ei kelpaa tietokannan nimeksi.\nKäytä ainoastaan kirjaimia (a-z, A-Z), numeroita (0-9), alaviivoja (_) ja tavuviivoja (-).",
+ "config-invalid-db-prefix": "”$1” ei kelpaa tietokannan etuliitteeksi.\nKäytä ainoastaan kirjaimia (a-z, A-Z), numeroita (0-9), alaviivoja (_) ja tavuviivoja (-).",
+ "config-connection-error": "$1.\n\nTarkista isäntä, käyttäjänimi, salasana ja yritä uudestaan.",
+ "config-postgres-old": "MediaWiki tarvitsee PostgreSQL:n version $1 tai uudemman. Nykyinen versio on $2.",
+ "config-mssql-old": "Vaaditaan Microsoft SQL Server $1 tai uudempi. Sinulla on käytössä $2.",
+ "config-sqlite-name-help": "Valitse nimi, joka yksilöi tämän wikin.\nÄlä käytä välilyöntejä tai viivoja.\nNimeä käytetään SQLite-tietokannan tiedostonimessä.",
+ "config-sqlite-dir-unwritable": "Hakemistoon ”$1” kirjoittaminen epäonnistui.\nMuuta hakemiston käyttöoikeuksia siten, että palvelinohjelmisto voi kirjoittaa siihen ja yritä uudelleen.",
+ "config-sqlite-readonly": "Tiedostoon <code>$1</code> ei voi kirjoittaa.",
+ "config-sqlite-fts3-downgrade": "PHP:stä puuttuu FTS3-tuki. Poistetaan ominaisuus käytöstä tietokantatauluista.",
+ "config-can-upgrade": "Tietokanta sisältää jo MediaWiki tauluja.\nPäivitä ne MediaWiki-versioon $1 painamalla <strong>Jatka</strong>.",
+ "config-upgrade-done": "Päivitys valmis.\n\nVoit [$1 aloittaa wikin käytön].\n\nNapsauta alla olevaa painiketta, jos haluat luoda uudelleen <code>LocalSettings.php</code>-tiedoston.\nTämä '''ei ole suositeltavaa''', jos wikissäsi ei ole ongelmia.",
+ "config-upgrade-done-no-regenerate": "Päivitys valmis.\n\nVoit [$1 aloittaa wikin käytön].",
+ "config-regenerate": "Luo LocalSettings.php uudelleen →",
+ "config-show-table-status": "Kysely <code>SHOW TABLE STATUS</code> epäonnistui!",
+ "config-db-web-help": "Valitse käyttäjänimi ja salasana joita palvelin käyttää muodostaessaan yhteyttä tietokantapalvelimeen wikin normaalin toiminnan aikana.",
+ "config-db-web-account-same": "Käytä samaa tiliä kuin asennuksessa",
+ "config-db-web-create": "Lisää tili, jos sitä ei ole jo olemassa",
+ "config-db-web-no-create-privs": "Tilillä jota käytetään asennuksessa ei ole oikeuksia luoda uutta tiliä.\nTähän määriteltävä tili täytyy olla jo olemassa.",
+ "config-mysql-engine": "Tallennusmoottori",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Tietokannan merkistökoodaus:",
+ "config-mysql-binary": "Binääri",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Wikin nimi",
+ "config-site-name-help": "Tämä näkyy selaimen otsikkona ja muissa kohdissa.",
+ "config-site-name-blank": "Kirjoita sivuston nimi.",
+ "config-project-namespace": "Projektinimiavaruus",
+ "config-ns-generic": "Projekti",
+ "config-ns-site-name": "Sama kuin wikin nimi: $1",
+ "config-ns-other": "Muu (määritä)",
+ "config-ns-other-default": "MinunWiki",
+ "config-ns-invalid": "Määritelty nimiavaruus \"<nowiki>$1</nowiki>\" on virheellinen.\nSyötä joku muu nimiavaruus.",
+ "config-ns-conflict": "Määritelty nimiavaruus \"<nowiki>$1</nowiki>\" on ristiriidassa MediaWikin oletusnimiavaruuksien kanssa.\nSyötä joku muu nimiavaruus.",
+ "config-admin-box": "Ylläpitäjän tili",
+ "config-admin-name": "Käyttäjänimesi:",
+ "config-admin-password": "Salasana",
+ "config-admin-password-confirm": "Salasana uudelleen",
+ "config-admin-help": "Syötä käyttäjänimi tähän, esimerkiksi \"Matti Meikäläinen\".\nTätä nimeä käytetään kirjauduttaessa wikiin.",
+ "config-admin-name-blank": "Anna ylläpitäjän käyttäjänimi.",
+ "config-admin-name-invalid": "Annettu nimi \"<nowiki>$1</nowiki>\" on virheellinen.\nSyötä toinen nimi.",
+ "config-admin-password-blank": "Syötä ylläpitäjän salasana.",
+ "config-admin-password-mismatch": "Antamasi salasanat eivät täsmää.",
+ "config-admin-email": "Sähköpostiosoite",
+ "config-admin-email-help": "Syötä sähköpostiosoite johon vastaanotetaan viestit muilta wikin käyttäjiltä, nollataan salasana ja ilmoitetaan tarkkailulistalla olevista sivuista. Kenttä voidaan jättää myös tyhjäksi.",
+ "config-admin-error-bademail": "Annoit virheellisen sähköpostiosoitteen.",
+ "config-subscribe": "Liity [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce päivityssähköpostilistalle].",
+ "config-subscribe-help": "Tällä harvoin käytettävällä sähköpostilistalla julkaistaan päivitysilmoituksia ja turvallisuuspäivityksiä.\nLiittymistä listalle suositellaan samoin kuin päivittämään MediaWiki kun uusi versio julkaistaan.",
+ "config-subscribe-noemail": "Yritit liittyä päivityssähköpostilistalle antamatta sähköpostiosoitetta.\nSyötä sähköpostiosoite jos haluat liittyä listalle.",
+ "config-almost-done": "Olet jo lähes valmis!\nVoit ohittaa jäljellä olevat määritykset ja asentaa wikin juuri nyt.",
+ "config-optional-continue": "Säädä lisää asetuksia.",
+ "config-optional-skip": "Nyt riittää, asenna wiki näillä tiedoilla.",
+ "config-profile": "Käyttäjäprofiilin oikeudet:",
+ "config-profile-wiki": "Avoin wiki",
+ "config-profile-no-anon": "Tunnuksen luonti vaaditaan",
+ "config-profile-fishbowl": "Vain hyväksytyt muokkaajat",
+ "config-profile-private": "Yksityinen wiki",
+ "config-license": "Tekijänoikeus ja lisenssi:",
+ "config-license-pd": "Public domain",
+ "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, [http://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-email-usertalk-help": "Salli käyttäjien vastaanottaa ilmoituksia käyttäjän keskustelusivulla tapahtuneista muutoksista jos he ovat ottaneet käyttöön toiminnon asetuksissaan.",
+ "config-email-watchlist": "Ota käyttöön tarkkailulistasta ilmoittaminen",
+ "config-email-watchlist-help": "Salli käyttäjien vastaanottaa ilmoituksia tarkkailulistalla olevilla sivuilla tapahtuneista muutoksista jos he ovat ottaneet käyttöön toiminnon asetuksissaan.",
+ "config-email-auth": "Ota käyttöön sähköpostin vahvistaminen",
+ "config-email-auth-help": "Kun tämä valinta otetaan käyttöön, käyttäjien tulee vahvistaa sähköpostiosoitteensa linkistä, joka lähetetään heille kun he asettavat tai muuttavat sähköpostiaan.\nVain vahvistettuihin sähköpostiosoitteisiin voidaan lähettää sähköposteja muilta käyttäjiltä tai ilmoituksia muutoksista.\nTämän valinnan käyttöönottoa <strong>suositellaan</strong> julkisissa wikeissä, koska se vähentää mahdollista sähköpostien väärinkäyttöä.",
+ "config-email-sender": "Palautusähköpostiosoite:",
+ "config-upload-settings": "Kuvien ja tiedostojen lataaminen",
+ "config-upload-enable": "Ota käyttöön tiedostojen lataaminen",
+ "config-upload-deleted": "Poistettujen tiedostojen hakemisto:",
+ "config-upload-deleted-help": "Valitse hakemisto johon poistetut tiedostot arkistoidaan.\nHakemiston ei tulisi olla käytettävissä internetverkosta.",
+ "config-logo": "Logon URL-osoite",
+ "config-cc-again": "Valitse uudelleen...",
+ "config-extensions": "Laajennukset",
+ "config-extensions-help": "Yllä luetellut laajennukset löytyvät <code>./extensions</code> hakemistosta.\n\nNe saattavat vaatia lisäasetuksia, mutta voit ottaa ne käyttöön nyt.",
+ "config-skins": "Ulkoasut",
+ "config-skins-must-enable-some": "Sinut täytyy valita ainakin yksi ulkoasu.",
+ "config-install-alreadydone": "<strong>Varoitus:</strong> MediaWiki on jo asennettu ja yrität asentaa sitä uudestaan.\nSiirry seuraavalle sivulle.",
+ "config-install-begin": "Painamalla \"{{int:config-continue}}\", aloitetaan MediaWikin asentaminen. \nJos haluat vielä tehdä muutoksia, paina \"{{int:config-back}}\".",
+ "config-install-step-done": "valmis",
+ "config-install-step-failed": "epäonnistui",
+ "config-install-extensions": "Sisällytetään laajennukset",
+ "config-install-database": "Asennetaan tietokantaa",
+ "config-pg-no-create-privs": "Määrittelemälläsi tilillä ei ole riittävästi oikeuksia luoda tiliä.",
+ "config-install-user": "Luodaan tietokannalle käyttäjää",
+ "config-install-user-alreadyexists": "Käyttäjä $1 on jo olemassa",
+ "config-install-user-create-failed": "Käyttäjän \"$1\" luonti epäonnistui: $2",
+ "config-install-user-grant-failed": "Käyttöoikeuksien myöntäminen käyttäjälle \"$1\" epäonnistui: $2",
+ "config-install-user-missing": "Määriteltyä käyttäjää \"$1\" ei ole olemassa.",
+ "config-install-user-missing-create": "Määriteltyä käyttäjää \"$1\" ei ole olemassa.\nValitse alapuolelta \"lisää tili\" jos haluat että se luodaan.",
+ "config-install-tables": "Luodaan tauluja",
+ "config-install-tables-exist": "<strong>Varoitus:</strong> MediaWiki taulut ovat jo olemassa.\nOhitetaan taulujen luonti.",
+ "config-install-tables-failed": "<strong>Virhe:</strong> Taulujen luominen epäonnistui seuraavaan virheen takia: $1",
+ "config-install-interwiki-list": "Tiedostoa <code>interwiki.list</code> ei voitu lukea.",
+ "config-install-keys": "Muodostetaan salausavaimia",
+ "config-install-sysop": "Luodaan ylläpitäjän tiliä",
+ "config-install-subscribe-fail": "Liittyminen mediawiki-announce listalle epäonnistui: $1",
+ "config-install-mainpage": "Luodaan etusivu oletussisällöllä",
+ "config-install-extension-tables": "Luodaan tauluja käyttöönotetuille laajuennuksille",
+ "config-install-mainpage-failed": "Etusivun lisääminen ei onnistunut: $1",
+ "config-download-localsettings": "Lataa <code>LocalSettings.php</code>",
+ "config-help": "ohje",
+ "config-nofile": "Tiedostoa \"$1\" ei löytynyt. Onko se poistettu?",
+ "config-extension-link": "Tiesitkö että wiki tukee [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions laajennuksia]?\n\nLaajennuksia voi hakea myös [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category luokittain].",
+ "mainpagetext": "'''MediaWiki on onnistuneesti asennettu.'''",
+ "mainpagedocfooter": "Lisätietoja käytöstä on sivulla [//meta.wikimedia.org/wiki/Help:Contents User's Guide].\n\n=== Lisäohjeita ===\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Asetusten teko-ohjeita]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWikin FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Sähköpostilista, jolla tiedotetaan MediaWikin uusista versioista]\n\n=== Asetukset ===\n\nTarkista, että alla olevat taivutusmuodot ovat oikein. Jos eivät, tee tarvittavat muutokset tiedostoon LocalSettings.php seuraavasti:\n $wgGrammarForms['fi']['genitive']['{{SITENAME}}'] = '...';\n $wgGrammarForms['fi']['partitive']['{{SITENAME}}'] = '...';\n $wgGrammarForms['fi']['elative']['{{SITENAME}}'] = '...';\n $wgGrammarForms['fi']['inessive']['{{SITENAME}}'] = '...';\n $wgGrammarForms['fi']['illative']['{{SITENAME}}'] = '...';\nTaivutusmuodot: {{GRAMMAR:genitive|{{SITENAME}}}} (yön) – {{GRAMMAR:partitive|{{SITENAME}}}} (yötä) – {{GRAMMAR:elative|{{SITENAME}}}} (yöstä) – {{GRAMMAR:inessive|{{SITENAME}}}} (yössä) – {{GRAMMAR:illative|{{SITENAME}}}} (yöhön)."
+}
diff --git a/includes/installer/i18n/fo.json b/includes/installer/i18n/fo.json
new file mode 100644
index 00000000..d40c45a2
--- /dev/null
+++ b/includes/installer/i18n/fo.json
@@ -0,0 +1,40 @@
+{
+ "@metadata": {
+ "authors": [
+ "EileenSanda"
+ ]
+ },
+ "config-desc": "Innstallasjónsforrit til MediaWiki",
+ "config-title": "Innstallering av MediaWiki $1",
+ "config-information": "Kunning",
+ "config-localsettings-upgrade": "Ein <code>LocalSettings.php</code> fíla er funnin.\nFyri at uppstiga hesa innstallasjón, vinarliga skriva hetta virði <code>$wgUpgradeKey</code> í teigin niðanfyri.\nTú finnur tað í <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Ein <code>LocalSettings.php</code> fíla er funnin.\nFyri at uppstiga hesa innstallasjónina, vinarliga koyr <code>update.php</code> ístaðin",
+ "config-localsettings-key": "Uppstiganarlykil:",
+ "config-localsettings-badkey": "Lykilin ið tú skrivaði er skeivur.",
+ "config-upgrade-key-missing": "Ein núverandi installasjón av MediaWiki er funnin.\nFyri at uppstiga hesa installasjónina, vinarliga set hesa linjuna niðast í tinari <code>LocalSettings.php</code>-fílu:\n\n$1",
+ "config-your-language": "Títt mál:",
+ "config-your-language-help": "Vel eitt mál, sum tú ynskir at nýta meðan tú installerar.",
+ "config-wiki-language": "Wikimál:",
+ "config-wiki-language-help": "Vel tað málið, ið wiki'in fyrst og fremst verður skrivað á.",
+ "config-back": "← Aftur",
+ "config-continue": "Halt fram →",
+ "config-page-language": "Mál",
+ "config-page-welcome": "Vælkomin til MediaWiki!",
+ "config-page-dbconnect": "Fá samband við dátugrunnin",
+ "config-page-upgrade": "Dagfør verandi installasjón",
+ "config-page-dbsettings": "Innstillingar fyri dátugrunnin",
+ "config-page-name": "Navn",
+ "config-page-options": "Møguleikar",
+ "config-page-install": "Innstallera",
+ "config-page-complete": "Liðugt!",
+ "config-page-restart": "Byrja umaftur at installera",
+ "config-page-readme": "Les meg",
+ "config-page-copying": "Avritan",
+ "config-page-upgradedoc": "Dagføring/uppgradering",
+ "config-page-existingwiki": "Verandi wiki",
+ "config-help-restart": "Ynskir tú at sletta øll goymd dáta sum tú hevur skrivað og byrja umaftur at installera?",
+ "config-restart": "Ja, byrja umaftur",
+ "config-env-php": "PHP $1 er innstallerað.",
+ "config-env-php-toolow": "PHP $1 er installerað.\nMen, MediaWiki krevur PHP $2 ella hægri.",
+ "mainpagetext": "'''Innlegging av Wiki-ritbúnaði væleydnað.'''"
+}
diff --git a/includes/installer/i18n/fr.json b/includes/installer/i18n/fr.json
new file mode 100644
index 00000000..9fd6726e
--- /dev/null
+++ b/includes/installer/i18n/fr.json
@@ -0,0 +1,348 @@
+{
+ "@metadata": {
+ "authors": [
+ "Aadri",
+ "Crochet.david",
+ "Gomoko",
+ "Grondin",
+ "Guillom",
+ "Hashar",
+ "IAlex",
+ "Jean-Frédéric",
+ "McDutchie",
+ "Peter17",
+ "Reedy",
+ "Sherbrooke",
+ "Urhixidur",
+ "Verdy p",
+ "Wyz",
+ "Yumeki",
+ "아라",
+ "Maxim21",
+ "Wladek92",
+ "Scoopfinder",
+ "Seb35"
+ ]
+ },
+ "config-desc": "Le programme d’installation de MediaWiki",
+ "config-title": "Installation de MediaWiki $1",
+ "config-information": "Informations",
+ "config-localsettings-upgrade": "Un fichier <code>LocalSettings.php</code> a été détecté.\nPour mettre à jour cette installation, veuillez saisir la valeur de <code>$wgUpgradeKey</code> dans le champ ci-dessous.\nVous la trouverez dans <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Un fichier <code>LocalSettings.php</code> a été détecté.\nPour mettre à niveau cette installation, veuillez exécuter <code>update.php</code> à la place",
+ "config-localsettings-key": "Clé de mise à jour :",
+ "config-localsettings-badkey": "La clé que vous avez fournie est incorrecte.",
+ "config-upgrade-key-missing": "Une installation existante de MediaWiki a été détectée.\n\nPour mettre à jour cette installation, veuillez ajouter la ligne suivante à la fin de votre fichier <code>LocalSettings.php</code>\n\n$1",
+ "config-localsettings-incomplete": "Le fichier <code>LocalSettings.php</code> existant semble être incomplet.\nLa variable $1 n’est pas définie.\nVeuillez modifier <code>LocalSettings.php</code> de sorte que cette variable soit définie, puis cliquer sur « {{int:Config-continue}} ».",
+ "config-localsettings-connection-error": "Une erreur est survenue lors de la connexion à la base de données en utilisant la configuration spécifiée dans <code>LocalSettings.php</code>. Veuillez corriger cette configuration puis réessayer.\n\n$1",
+ "config-session-error": "Erreur lors du démarrage de la session : $1",
+ "config-session-expired": "Les données de votre session semblent avoir expiré.\nLes sessions sont configurées pour une durée de $1.\nVous pouvez l'augmenter en configurant <code>session.gc_maxlifetime</code> dans le fichier php.ini.\nRedémarrer le processus d'installation.",
+ "config-no-session": "Les données de votre session ont été perdues !\nVérifiez votre fichier php.ini et assurez-vous que <code>session.save_path</code> contient le chemin d’un répertoire approprié.",
+ "config-your-language": "Votre langue :",
+ "config-your-language-help": "Sélectionnez la langue à utiliser pendant le processus d'installation.",
+ "config-wiki-language": "Langue du wiki :",
+ "config-wiki-language-help": "Sélectionner la langue dans laquelle le wiki sera principalement écrit.",
+ "config-back": "← Retour",
+ "config-continue": "Continuer →",
+ "config-page-language": "Langue",
+ "config-page-welcome": "Bienvenue sur MediaWiki !",
+ "config-page-dbconnect": "Connexion à la base de données",
+ "config-page-upgrade": "Mettre à jour l’installation existante",
+ "config-page-dbsettings": "Paramètres de la base de données",
+ "config-page-name": "Nom",
+ "config-page-options": "Options",
+ "config-page-install": "Installer",
+ "config-page-complete": "Terminé !",
+ "config-page-restart": "Redémarrer l’installation",
+ "config-page-readme": "Lisez-moi",
+ "config-page-releasenotes": "Notes de version",
+ "config-page-copying": "Copie",
+ "config-page-upgradedoc": "Mise à jour",
+ "config-page-existingwiki": "Wiki existant",
+ "config-help-restart": "Voulez-vous effacer toutes les données enregistrées que vous avez entrées et relancer le processus d'installation ?",
+ "config-restart": "Oui, le relancer",
+ "config-welcome": "=== Vérifications liées à l’environnement ===\nDes vérifications de base vont maintenant être effectuées pour voir si cet environnement est adapté à l’installation de MediaWiki.\nRappelez-vous d’inclure ces informations si vous recherchez de l’aide sur la manière de terminer l’installation.",
+ "config-copyright": "=== Droit d'auteur et conditions ===\n\n$1\n\nCe programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier selon les termes de la Licence Publique Générale GNU telle que publiée par la Free Software Foundation (version 2 de la Licence, ou, à votre choix, toute version ultérieure).\n\nCe programme est distribué dans l’espoir qu’il sera utile, mais '''sans aucune garantie''' : sans même les garanties implicites de '''commerciabilité''' ou d’'''adéquation à un usage particulier'''.\nVoir la Licence Publique Générale GNU pour plus de détails.\n\nVous devriez avoir reçu <doclink href=Copying>une copie de la Licence Publique Générale GNU</doclink> avec ce programme ; dans le cas contraire, écrivez à la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ou [http://www.gnu.org/copyleft/gpl.html lisez-la en ligne].",
+ "config-sidebar": "* [//www.mediawiki.org Accueil MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guide de l’utilisateur]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guide de l’administrateur]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Lisez-moi</doclink>\n* <doclink href=ReleaseNotes>Notes de publication</doclink>\n* <doclink href=Copying>Copie</doclink>\n* <doclink href=UpgradeDoc>Mise à jour</doclink>",
+ "config-env-good": "L’environnement a été vérifié.\nVous pouvez installer MediaWiki.",
+ "config-env-bad": "L’environnement a été vérifié.\nVous ne pouvez pas installer MediaWiki.",
+ "config-env-php": "PHP $1 est installé.",
+ "config-env-hhvm": "HHVM $1 est installé.",
+ "config-unicode-using-utf8": "Utilisation de utf8_normalize.so par Brion Vibber pour la normalisation Unicode.",
+ "config-unicode-using-intl": "Utilisation de [http://pecl.php.net/intl l'extension PECL intl] pour la normalisation Unicode.",
+ "config-unicode-pure-php-warning": "<strong>Attention</strong> : L'[http://pecl.php.net/intl extension PECL intl] n'est pas disponible pour la normalisation d’Unicode, retour à la version lente implémentée en PHP.\nSi votre site web sera très fréquenté, vous devriez lire ceci : [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations ''Unicode normalization''] (en anglais).",
+ "config-unicode-update-warning": "'''Attention''': La version installée du ''wrapper'' de normalisation Unicode utilise une vieille version de la [http://site.icu-project.org/ bibliothèque logicielle ''ICU Project''].\nVous devriez faire une [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations mise à jour] si vous êtes tout à fait concerné par l'usage d'Unicode.",
+ "config-no-db": "Impossible de trouver un pilote de base de données approprié ! Vous devez installer un pilote de base de données pour PHP. Les types de bases de données suivants sont reconnus : $1.\n\nSi vous avez compilé PHP vous-même, reconfigurez-le avec un client de base de données activé, par exemple en utilisant <code>./configure --with-mysqli</code>. Si vous avez installé PHP depuis un paquet Debian ou Ubuntu, alors vous devrez aussi installer, par exemple, le paquet <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Attention''': vous avez SQLite $1, qui est inférieur à la version minimale requise $2. SQLite sera indisponible.",
+ "config-no-fts3": "'''Attention :''' SQLite est compilé sans le module [//sqlite.org/fts3.html FTS3] ; les fonctions de recherche ne seront pas disponibles sur ce moteur.",
+ "config-register-globals-error": "<strong>Erreur : L’option <code>[http://php.net/register_globals register_globals]</code> de PHP est activée.\nElle doit être désactivée pour poursuivre l’installation.</strong>\nVoyez [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] pour avoir de l’aide sur la manière de faire cela.",
+ "config-magic-quotes-gpc": "<strong>Ereur critique : [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] est actif !</strong>\nCette option corrompt les entrées de donnée de façon imprévisible.\nVous ne pouvez pas installer ou utiliser MédiaWiki tant que cette option n’est pas désactivée.",
+ "config-magic-quotes-runtime": "'''Erreur fatale : [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] est activé !'''\nCette option corrompt les données de manière imprévisible.\nVous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
+ "config-magic-quotes-sybase": "'''Erreur fatale : [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybasee] est activé !'''\nCette option corrompt les données de manière imprévisible.\nVous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
+ "config-mbstring": "'''Erreur fatale : [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] est activé !'''\nCette option provoque des erreurs et peut corrompre les données de manière imprévisible.\nVous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
+ "config-safe-mode": "'''Attention : le « [http://www.php.net/features.safe-mode safe mode] » est activé !'''\nCeci peut causer des problèmes, en particulier si vous utilisez le téléversement de fichiers et le support de <code>math</code>.",
+ "config-xml-bad": "Le module XML de PHP est manquant.\nMediaWiki requiert des fonctions de ce module et ne fonctionnera pas avec cette configuration.\nSi vous êtes sous Mandrake, installez le paquet php-xml.",
+ "config-pcre-old": "'''Fatal :''' PCRE $1 ou ultérieur est nécessaire.\nVotre binaire PHP est lié avec PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/Plus d’information sur PCRE].",
+ "config-pcre-no-utf8": "'''Erreur fatale''': Le module PCRE de PHP semble être compilé sans le support PCRE_UTF8.\nMédiaWiki nécessite 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": "'''Attention :''' 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-ctype": "'''Fatal ''': PHP doit être compilé avec le support pour l'[http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
+ "config-iconv": "<strong>Erreur critique :</strong> PHP doit être compilé avec le support de l’[http://www.php.net/manual/en/iconv.installation.php extension iconv].",
+ "config-json": "'''Erreur fatale :''' PHP a été compilé sans le support de JSON.\nVous devez soit installez l’extension JSON de PHP ou l’extension [http://pecl.php.net/package/jsonc PECL jsonc] avant d’installer MediaWiki.\n* L’extension PHP est comprise dans Red Hat Enterprise Linux (CentOS) 5 et 6, mais doit être activée dans <code>/etc/php.ini</code> ou <code>/etc/php.d/json.ini</code>.\n* Certaines distributions Linux après mai 2013 ne comprennent pas l’extension PHP, mais ont mis à la place l’extension PECL sous la forme <code>php5-json</code> ou <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] est installé",
+ "config-apc": "[http://www.php.net/apc APC] est installé",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] est installé",
+ "config-no-cache": "'''Attention :''' Impossible de trouver [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].\nLa mise en cache d'objets n'est pas activée.",
+ "config-mod-security": "'''Attention''': Votre serveur web a [http://modsecurity.org/ mod_security] activé. S'il est mal configuré, cela peut poser des problèmes à MediaWiki ou à d'autres applications qui permettent aux utilisateurs de publier un contenu quelconque.\nReportez-vous à [http://modsecurity.org/documentation/ la documentation de mod_security] ou contactez le support de votre hébergeur si vous rencontrez des erreurs aléatoires.",
+ "config-diff3-bad": "GNU diff3 introuvable.",
+ "config-git": "Logiciel de contrôle de version Git trouvé : <code>$1</code>.",
+ "config-git-bad": "Logiciel de contrôle de version Git non trouvé.",
+ "config-imagemagick": "ImageMagick trouvé : <code>$1</code>.\nLa miniaturisation d'images sera activée si vous activez le téléversement de fichiers.",
+ "config-gd": "La bibliothèque graphique GD intégrée a été trouvée.\nLa miniaturisation d'images sera activée si vous activez le téléversement de fichiers.",
+ "config-no-scaling": "Impossible de trouver la bibliothèque GD ou ImageMagick.\nLa miniaturisation d'images sera désactivée.",
+ "config-no-uri": "'''Erreur :''' Impossible de déterminer l'URI du script actuel.\nInstallation interrompue.",
+ "config-no-cli-uri": "'''Attention''': Aucun <code>--scriptpath</code> n'a été spécifié; <code>$1</code> sera utilisé par défaut",
+ "config-using-server": "Utilisation du nom de serveur \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Utilisation de l'URL de serveur \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Attention:''' Votre répertoire par défaut pour les téléchargements, <code>$1</code>, est vulnérable, car il peut exécuter n'importe quel script.\nBien que MediaWiki vérifie tous les fichiers téléchargés, il est fortement recommandé de [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security fermer cette vulnérabilité de sécurité] (texte en anglais) avant d'activer les téléchargements.",
+ "config-no-cli-uploads-check": "'''Attention:''' Votre répertoire par défaut pour les imports(<code>$1</code>) n'est pas contrôlé concernant la vulnérabilité d'exécution de scripts arbitraires lors de l'installation CLI.",
+ "config-brokenlibxml": "Votre système utilise une combinaison de versions de PHP et libxml2 qui est boguée et peut engendrer des corruptions cachées de données dans MediaWiki et d’autres applications web.\nVeuillez mettre à jour votre système vers libxml2 2.7.3 ou plus récent ([https://bugs.php.net/bug.php?id=45996 bogue déposé auprès de PHP]).\nInstallation interrompue.",
+ "config-suhosin-max-value-length": "Suhosin est installé et limite la <code>longueur</code> du paramètre GET à $1 octets.\nLe composant ResourceLoader de MediaWiki va répondre en respectant cette limite, mais ses performances seront dégradées. Si possible, vous devriez définir <code>suhosin.get.max_value_length</code> à 1024 ou plus dans le fichier <code>php.ini</code>, et fixer <code>$wgResourceLoaderMaxQueryLength</code> à la même valeur dans <code>LocalSettings.php</code>.",
+ "config-db-type": "Type de base de données :",
+ "config-db-host": "Nom d’hôte de la base de données :",
+ "config-db-host-help": "Si votre serveur de base de données est sur un serveur différent, saisissez ici son nom d’hôte ou son adresse IP.\n\nSi vous utilisez un hébergement mutualisé, votre hébergeur doit vous avoir fourni le nom d’hôte correct dans sa documentation.\n\nSi vous installez sur un serveur Windows et utilisez MySQL, « localhost » peut ne pas fonctionner comme nom de serveur. S’il ne fonctionne pas, essayez « 127.0.0.1 » comme adresse IP locale.\n\nSi vous utilisez PostgreSQL, laissez ce champ vide pour vous connecter via un socket Unix.",
+ "config-db-host-oracle": "Nom TNS de la base de données :",
+ "config-db-host-oracle-help": "Entrez un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nom de connexion locale] valide ; un fichier tnsnames.ora doit être visible par cette installation.<br /> Si vous utilisez les bibliothèques clientes version 10g ou plus récentes, vous pouvez également utiliser la méthode de nommage [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identifier ce wiki",
+ "config-db-name": "Nom de la base de données :",
+ "config-db-name-help": "Choisissez un nom qui identifie votre wiki.\nIl ne doit pas contenir d'espaces.\n\nSi vous utilisez un hébergement web partagé, votre hébergeur vous fournira un nom spécifique de base de données à utiliser, ou bien vous permet de créer des bases de données via un panneau de contrôle.",
+ "config-db-name-oracle": "Schéma de base de données :",
+ "config-db-account-oracle-warn": "Il existe trois scénarios pris en charge pour l’installation d'Oracle comme backend de base de données:\n\nSi vous souhaitez créer un compte de base de données dans le cadre de la procédure d’installation, veuillez fournir un compte avec le rôle de SYSDBA comme compte de base de données pour l’installation et spécifier les informations d’identification souhaitées pour le compte d'accès au web, sinon vous pouvez créer le compte d’accès web manuellement et fournir uniquement ce compte (si elle a exigé des autorisations nécessaires pour créer les objets de schéma) ou fournir deux comptes différents, l’un avec les privilèges pour créer et l'autre restreint, pour l’accès web.\n\nUn script pour créer un compte avec des privilèges requis peut être trouvé dans le répertoire « entretien/oracle/ » de cette installation. N’oubliez pas que le fait de l’utilisation d’un compte limité désactive toutes les fonctionnalités d’entretien avec le compte par défaut.",
+ "config-db-install-account": "Compte d'utilisateur pour l'installation",
+ "config-db-username": "Nom d’utilisateur de la base de données :",
+ "config-db-password": "Mot de passe de la base de données :",
+ "config-db-password-empty": "Veuillez entrer un mot de passe pour le nouveau compte de la base de données : $1.\nBien qu'il soit possible de créer un compte sans mot de passe, ce n'est pas recommandé pour des questions de sécurité.",
+ "config-db-username-empty": "Vous devez entrer une valeur pour « {{int:config-db-username}} ».",
+ "config-db-install-username": "Entrez le nom d’utilisateur qui sera utilisé pour se connecter à la base de données pendant le processus d'installation. Il ne s’agit pas du nom d’utilisateur du compte MediaWiki, mais du nom d’utilisateur pour votre base de données.",
+ "config-db-install-password": "Entrez le mot de passe qui sera utilisé pour se connecter à la base de données pendant le processus d'installation. Il ne s’agit pas du mot de passe du compte MediaWiki, mais du mot de passe pour votre base de données.",
+ "config-db-install-help": "Entrez le nom d'utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le processus d'installation.",
+ "config-db-account-lock": "Utiliser le même nom d'utilisateur et le même mot de passe pendant le fonctionnement habituel",
+ "config-db-wiki-account": "Compte d'utilisateur pour le fonctionnement habituel",
+ "config-db-wiki-help": "Entrez le nom d'utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le fonctionnement habituel du wiki.\nSi le compte n'existe pas, et le compte d'installation dispose de privilèges suffisants, ce compte d'utilisateur sera créé avec les privilèges minimum requis pour faire fonctionner le wiki.",
+ "config-db-prefix": "Préfixe des tables de la base de données :",
+ "config-db-prefix-help": "Si vous avez besoin de partager une base de données entre plusieurs wikis, ou entre MediaWiki et une autre application Web, vous pouvez choisir d'ajouter un préfixe à tous les noms de table pour éviter les conflits.\nNe pas utiliser d'espaces.\n\nCe champ est généralement laissé vide.",
+ "config-db-charset": "Jeu de caractères de la base de données",
+ "config-charset-mysql5-binary": "binaire MySQL 4.1/5.0",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 rétrocompatible UTF-8",
+ "config-charset-help": "'''Attention:''' Si vous utilisez ''backwards-compatible UTF-8'' sur MySQL 4.1+, et ensuite sauvegardez la base de données avec <code>mysqldump</code>, cela peut détruire tous les caractères non-ASCII, ce qui rend inutilisable vos copies de sauvegarde de façon irréversible !\n\nEn ''mode binaire'', MediaWiki stocke le texte UTF-8 dans des champs binaires de la base de données. C'est plus efficace que le ''mode UTF-8'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.\nEn ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pourra présenter et convertir les données de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
+ "config-mysql-old": "MySQL $1 ou version ultérieure est requis. Vous avez $2.",
+ "config-db-port": "Port de la base de données :",
+ "config-db-schema": "Schéma pour MediaWiki :",
+ "config-db-schema-help": "Ce schéma est généralement correct.\nNe le changez que si vous êtes sûr que c'est nécessaire.",
+ "config-pg-test-error": "Impossible de se connecter à la base de données '''$1''' : $2",
+ "config-sqlite-dir": "Dossier des données SQLite :",
+ "config-sqlite-dir-help": "SQLite stocke toutes les données dans un fichier unique.\n\nLe répertoire que vous fournissez doit être accessible en écriture par le serveur lors de l'installation.\n\nIl '''ne faut pas''' qu'il soit accessible via le web, c'est pourquoi il n'est pas à l'endroit où sont vos fichiers PHP.\n\nL'installateur écrira un fichier <code>.htaccess</code> en même temps, mais s'il y a échec, quelqu'un peut accéder à votre base de données.\nCela comprend les données des utilisateurs (adresses de courriel, mots de passe hachés) ainsi que des révisions supprimées et d'autres données confidentielles du wiki.\n\nEnvisagez de placer la base de données ailleurs, par exemple dans <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Espace de stockage (''tablespace'') par défaut :",
+ "config-oracle-temp-ts": "Espace de stockage (''tablespace'') temporaire :",
+ "config-type-mysql": "MySQL (ou compatible)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-support-info": "MediaWiki supporte 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 le support.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] est le premier choix pour MediaWiki et est le mieux pris en charge. MediaWiki fonctionne aussi avec [{{int:version-db-mariadb-url}} MariaDB] et [{{int:version-db-percona-url}} Percona Server], qui sont compatibles avec MySQL. ([http://www.php.net/manual/en/mysqli.installation.php Comment compiler PHP avec le support MySQL])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] est un système de base de données populaire et ''open source'' qui peut être une alternative à MySQL. Son support peut contenir quelques bogues mineurs et n'est pas recommandé dans un environnement de production. ([http://www.php.net/manual/en/pgsql.installation.php Comment compiler PHP avec le support de PostgreSQL])",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] est un système de base de données léger bien supporté. ([http://www.php.net/manual/en/pdo.installation.php Comment compiler PHP avec le support 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. ([http://www.php.net/manual/en/oci8.installation.php Comment compiler PHP avec le support OCI8])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] est une base de données commerciale d’entreprise pour Windows. ([http://www.php.net/manual/en/sqlsrv.installation.php Comment compiler PHP avec le support de SQLSRV])",
+ "config-header-mysql": "Paramètres de MySQL",
+ "config-header-postgres": "Paramètres de PostgreSQL",
+ "config-header-sqlite": "Paramètres de SQLite",
+ "config-header-oracle": "Paramètres d’Oracle",
+ "config-header-mssql": "Paramètres de Microsoft SQL Server",
+ "config-invalid-db-type": "Type de base de données non valide",
+ "config-missing-db-name": "Vous devez entrer une valeur pour « {{int:config-db-name}} ».",
+ "config-missing-db-host": "Vous devez entrer une valeur pour « {{int:config-db-host}} ».",
+ "config-missing-db-server-oracle": "Vous devez entrer une valeur pour « {{int:config-db-host-oracle}} ».",
+ "config-invalid-db-server-oracle": "Le nom TNS de la base de données (« $1 ») est invalide.\nUtilisez uniquement la chaîne \"TNS Name\" ou \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Méthodes de nommage Oracle])",
+ "config-invalid-db-name": "Nom de la base de données invalide (« $1 »).\nUtiliser seulement les lettres ASCII (a-z, A-Z), les chiffres (0-9), les caractères de soulignement (_) et les tirets (-).",
+ "config-invalid-db-prefix": "Préfixe de la base de données non valide « $1 ».\nUtiliser seulement les lettres ASCII (a-z, A-Z), les chiffres (0-9), les caractères de soulignement (_) et les tirets (-).",
+ "config-connection-error": "$1.\n\nVérifier le nom d’hôte, le nom d’utilisateur et le mot de passe ci-dessous puis réessayer.",
+ "config-invalid-schema": "Schéma invalide pour MediaWiki « $1 ».\nUtiliser seulement les lettres ASCII (a-z, A-Z), les chiffres (0-9) et les caractères de soulignement (_).",
+ "config-db-sys-create-oracle": "L'installateur ne reconnaît que le compte SYSDBA lors de la création d'un nouveau compte.",
+ "config-db-sys-user-exists-oracle": "Le compte « $1 » existe déjà. Seul SYSDBA peut être utilisé pour créer un nouveau compte.",
+ "config-postgres-old": "PostgreSQL $1 ou version ultérieure est requis. Vous avez $2.",
+ "config-mssql-old": "Microsoft SQL Server version $1 ou supérieur est requis. Vous avez la version $2.",
+ "config-sqlite-name-help": "Choisir un nom qui identifie votre wiki.\nNe pas utiliser d'espaces ni de traits d'union.\nIl sera utilisé pour le fichier de données SQLite.",
+ "config-sqlite-parent-unwritable-group": "Impossible de créer le répertoire de données <nowiki><code>$1</code></nowiki>, parce que le répertoire parent <nowiki><code>$2</code></nowiki> n'est pas accessible en écriture par le serveur Web.\n\nL'installateur a détecté sous quel nom d'utilisateur, le serveur web est actif.\nRendre le répertoire <nowiki><code>$3</code></nowiki> accessible en écriture pour continuer.\nSur un système UNIX/Linux, saisir :\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Impossible de créer le répertoire de données <nowiki><code>$1</code></nowiki>, parce que le répertoire parent <nowiki><code>$2</code></nowiki> n'est pas accessible en écriture par le serveur Web.\n\nL'installateur n'a pas pu déterminer le nom de l'utilisateur sous lequel le serveur s'exécute.\nRendre le répertoire <nowiki><code>$3</code></nowiki> globalement accessible en écriture pour continuer.\nSur un système UNIX/Linux, saisir :\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Erreur de création du répertoire de données « $1 ».\nVérifiez l'emplacement et essayez à nouveau.",
+ "config-sqlite-dir-unwritable": "Impossible d'écrire dans le répertoire « $1 ».\nChanger les permissions de sorte que le serveur puisse y écrire et essayez à nouveau.",
+ "config-sqlite-connection-error": "$1.\n\nVérifier le répertoire des données et le nom de la base de données ci-dessous et réessayer.",
+ "config-sqlite-readonly": "Le fichier <code>$1</code> n'est pas accessible en écriture.",
+ "config-sqlite-cant-create-db": "Impossible de créer le fichier de base de données <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP n'a pas trouvé le support FTS3, les tables sont restreintes.",
+ "config-can-upgrade": "Il y a des tables MediaWiki dans cette base de données.\nPour les mettre au niveau de MediaWiki $1, cliquez sur '''Continuer'''.",
+ "config-upgrade-done": "Mise à jour terminée.\n\nVous pouvez maintenant [$1 commencer à utiliser votre wiki].\n\nSi vous souhaitez régénérer votre fichier <code>LocalSettings.php</code>, cliquez sur le bouton ci-dessous.\nCeci '''n'est pas recommandé''' sauf si vous rencontrez des problèmes avec votre wiki.",
+ "config-upgrade-done-no-regenerate": "Mise à jour terminée.\n\nVous pouvez maintenant [$1 commencer à utiliser votre wiki].",
+ "config-regenerate": "Regénérer LocalSettings.php →",
+ "config-show-table-status": "Échec de la requête <code>SHOW TABLE STATUS</code> !",
+ "config-unknown-collation": "'''Attention:''' La base de données utilise un classement alphabétique (''collation'') inconnu.",
+ "config-db-web-account": "Compte de la base de données pour l'accès Web",
+ "config-db-web-help": "Sélectionnez le nom d'utilisateur et le mot de passe que le serveur web utilisera pour se connecter au serveur de base de données pendant le fonctionnement habituel du wiki.",
+ "config-db-web-account-same": "Utilisez le même compte que pour l'installation",
+ "config-db-web-create": "Créez le compte s'il n'existe pas déjà",
+ "config-db-web-no-create-privs": "Le compte que vous avez spécifié pour l'installation n'a pas de privilèges suffisants pour créer un compte.\nLe compte que vous spécifiez ici doit déjà exister.",
+ "config-mysql-engine": "Moteur de stockage :",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "''' Avertissement ''': vous avez sélectionné MyISAM comme moteur de stockage pour MySQL, ce qui n'est pas recommandé pour une utilisation avec MediaWiki, parce que:\n * il supporte à peine la simultanéité en raison de verrouillage de table\n * il est plus sujet à la corruption que les autres moteurs\n * le codebase MediaWiki ne gère pas toujours MyISAM comme il se doit\n\nSi votre installation MySQL supporte InnoDB, il est fortement recommandé que vous le choisissiez plutôt. \nSi votre installation MySQL ne supporte pas les tables InnoDB, il est peut-être temps de faire une mise à niveau.",
+ "config-mysql-only-myisam-dep": "'''Attention :''' MyISAM est le seul moteur de stockage disponible pour MySQL sur cette machine, et cela n’est pas recommandé pour une utilisation avec MédiaWiki, car :\n* il supporte très peu les accès concurrents à cause du verrouillage des tables\n* il est plus sujet à corruption que les autres moteurs\n* le code de base de MédiaWiki ne gère pas toujours MyISAM comme il faudrait\n\nVotre installation MySQL ne supporte pas InnoDB ; il est peut-être temps de la mettre à jour.",
+ "config-mysql-engine-help": "'''InnoDB''' est presque toujours la meilleure option, car il supporte bien les accès concurrents.\n\n'''MyISAM''' peut être plus rapide dans les installations monoposte ou en lecture seule. \nLes bases de données MyISAM ont tendance à se corrompre plus souvent que les bases d'InnoDB.",
+ "config-mysql-charset": "Jeu de caractères de la base de données :",
+ "config-mysql-binary": "Binaire",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "En ''mode binaire'', MediaWiki stocke le texte au format UTF-8 dans la base de données dans des champs binaires.\nC'est plus efficace que le ''UTF-8 mode'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.\n\nEn ''mode UTF-8'', MySQL reconnaîtra le jeu de caractères dans lequel sont vos données et pourra les présenter et les convertir de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
+ "config-mssql-auth": "Type d’authentification :",
+ "config-mssql-install-auth": "Sélectionner le type d’authentification qui sera utilisé pour se connecter à la base de données pendant le processus d’installation.\nSi vous sélectionnez « {{int:config-mssql-windowsauth}} », les informations d’identification de l’utilisateur faisant tourner le serveur seront utilisées.",
+ "config-mssql-web-auth": "Sélectionner le type d’authentification que le serveur web utilisera pour se connecter au serveur de base de données lors des opérations habituelles du wiki.\nSi vous sélectionnez « {{int:config-mssql-windowsauth}} », les informations d’identification de l’utilisateur sous lequel tourne le serveur web seront utilisées.",
+ "config-mssql-sqlauth": "Authentification de SQL Server",
+ "config-mssql-windowsauth": "Authentification Windows",
+ "config-site-name": "Nom du wiki :",
+ "config-site-name-help": "Ceci apparaîtra dans la barre de titre du navigateur et en divers autres endroits.",
+ "config-site-name-blank": "Entrez un nom de site.",
+ "config-project-namespace": "Espace de noms du projet :",
+ "config-ns-generic": "Projet",
+ "config-ns-site-name": "Même nom que le wiki : $1",
+ "config-ns-other": "Autre (préciser)",
+ "config-ns-other-default": "MonWiki",
+ "config-project-namespace-help": "Suivant l'exemple de Wikipédia, plusieurs wikis gardent leurs pages de politique séparées de leurs pages de contenu, dans un ''espace de noms de niveau projet'' propre.\nTous les titres de page de cet espace de noms commence par un préfixe défini, que vous pouvez spécifier ici.\nTraditionnellement, ce préfixe est dérivé du nom du wiki, et ne peut contenir de caractères de ponctuation tels que « # » ou « : ».",
+ "config-ns-invalid": "L'espace de noms spécifié « <nowiki>$1</nowiki> » n'est pas valide.\nSpécifiez un espace de noms différent pour le projet.",
+ "config-ns-conflict": "L'espace de noms spécifié « <nowiki>$1</nowiki> » est en conflit avec un espace de noms par défaut de MediaWiki.\nChoisir un autre espace de noms pour le projet.",
+ "config-admin-box": "Compte administrateur",
+ "config-admin-name": "Votre nom d’utilisateur :",
+ "config-admin-password": "Mot de passe :",
+ "config-admin-password-confirm": "Saisir à nouveau le mot de passe :",
+ "config-admin-help": "Entrez votre nom d'utilisateur préféré ici, par exemple « Jean Blogue ».\nC'est le nom que vous utiliserez pour vous connecter au wiki.",
+ "config-admin-name-blank": "Entrez un nom d'administrateur.",
+ "config-admin-name-invalid": "Le nom d'utilisateur spécifié « <nowiki>$1</nowiki> » n'est pas valide.\nIndiquez un nom d'utilisateur différent.",
+ "config-admin-password-blank": "Entrez un mot de passe pour le compte administrateur.",
+ "config-admin-password-mismatch": "Les deux mots de passe que vous avez saisis ne correspondent pas.",
+ "config-admin-email": "Adresse de courriel :",
+ "config-admin-email-help": "Entrez une adresse de courriel ici pour vous permettre de recevoir des courriels d'autres utilisateurs du wiki, réinitialiser votre mot de passe, et être informé des modifications apportées aux pages de votre liste de suivi. Vous pouvez laisser ce champ vide.",
+ "config-admin-error-user": "Erreur interne lors de la création d'un administrateur avec le nom « <nowiki>$1</nowiki> ».",
+ "config-admin-error-password": "Erreur interne lors de l'inscription d'un mot de passe pour l'administrateur « <nowiki>$1</nowiki> » : <pre>$2</pre>",
+ "config-admin-error-bademail": "Vous avez entré une adresse de courriel invalide",
+ "config-subscribe": "Abonnez-vous à la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce liste d'annonce des nouvelles versions]",
+ "config-subscribe-help": "Il s'agit d'une liste de diffusion à faible volume utilisée servant à annoncer les nouvelles versions, y compris les versions améliorant la sécurité du logiciel.\nVous devriez y souscrire et mettre à jour votre version de MediaWiki lorsque de nouvelles versions sont publiées.",
+ "config-subscribe-noemail": "Vous avez essayé de vous abonner à la liste de diffusion des communiqués, sans fournir une adresse courriel ! S'il vous plaît, fournir une adresse électronique si vous souhaitez vous abonner à la liste de diffusion.",
+ "config-almost-done": "Vous avez presque fini !\nVous pouvez passer la configuration restante et installer immédiatement le wiki.",
+ "config-optional-continue": "Me poser davantage de questions.",
+ "config-optional-skip": "J’en ai assez, installer simplement le wiki.",
+ "config-profile": "Profil des droits d’utilisateurs :",
+ "config-profile-wiki": "Wiki ouvert",
+ "config-profile-no-anon": "Création de compte requise",
+ "config-profile-fishbowl": "Éditeurs autorisés seulement",
+ "config-profile-private": "Wiki privé",
+ "config-profile-help": "Les wikis fonctionnent mieux lorsque vous laissez le plus de personnes possible les modifier.\nAvec MediaWiki, il est facile de vérifier les modifications récentes et de révoquer tout dommage créé par des utilisateurs débutants ou mal intentionnés.\n\nCependant, de nombreuses autres utilisations ont été trouvées au logiciel et il n’est pas toujours facile de convaincre tout le monde des bénéfices de l’esprit wiki.\nVous avez donc le choix.\n\nLe modèle '''{{int:config-profile-wiki}}''' autorise toute personne à modifier, y compris sans s’identifier.\n'''{{int:config-profile-no-anon}}''' fournit plus de contrôle, mais peut rebuter les contributeurs occasionnels.\n\n'''{{int:config-profile-fishbowl}}''' autorise la modification par les utilisateurs approuvés, mais le public peut toujours lire les pages et leur historique.\n'''{{int:config-profile-private}}''' n’autorise que les utilisateurs approuvés à voir les pages dans le même groupe que les utilisateurs autorisés à y écrire.\n\nDes configurations de droits d’utilisateurs plus complexes sont disponibles après l'installation, voir la [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights page correspondante du manuel].",
+ "config-license": "Droits d'auteur et licence :",
+ "config-license-none": "Aucune licence en bas de page",
+ "config-license-cc-by-sa": "Creative Commons attribution partage à l'identique",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons paternité – non commercial – partage à l’identique",
+ "config-license-cc-0": "Creative Commons Zero (domaine public)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 ou ultérieure",
+ "config-license-pd": "Domaine public",
+ "config-license-cc-choose": "Sélectionner une licence Creative Commons personnalisée",
+ "config-license-help": "Beaucoup de wikis publics mettent l’ensemble des contributions sous une [http://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 [http://www.php.net/manual/en/mail.configuration.php configurer des paramètres PHP] (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",
+ "config-email-usertalk-help": "Permet aux utilisateurs de recevoir une notification en cas de modification de leurs pages de discussion, si cela est activé dans leurs préférences.",
+ "config-email-watchlist": "Activer la notification de la liste de suivi",
+ "config-email-watchlist-help": "Permet aux utilisateurs de recevoir des notifications à propos des pages qu'ils ont en suivi (si cette préférence est activée).",
+ "config-email-auth": "Activer l'authentification par courriel",
+ "config-email-auth-help": "Si cette option est activée, les utilisateurs doivent confirmer leur adresse de courriel en utilisant l'hyperlien envoyé à chaque fois qu'ils la définissent ou la modifient.\nSeules les adresses authentifiées peuvent recevoir des courriels des autres utilisateurs ou lorsqu'il y a des notifications de modification.\nL'activation de cette option est '''recommandée''' pour les wikis publics en raison d'abus potentiels des fonctionnalités de courriels.",
+ "config-email-sender": "Adresse de courriel de retour :",
+ "config-email-sender-help": "Entrez l'adresse de courriel à utiliser comme adresse de retour des courriels sortant.\nLes courriels rejetés y seront envoyés.\nDe nombreux serveurs de courriels exigent au moins un [http://fr.wikipedia.org/wiki/Nom_de_domaine nom de domaine] valide.",
+ "config-upload-settings": "Téléchargement des images et des fichiers",
+ "config-upload-enable": "Activer le téléchargement des fichiers",
+ "config-upload-help": "Le téléchargement des fichiers expose votre serveur à des risques de sécurité.\nPour plus d'informations, lire la section [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security ''Security''] du manuel d'installation (en anglais).\n\nPour autoriser le téléchargement des fichiers, modifier le mode du sous-répertoire <code>images</code> qui se situe sous le répertoire racine de MediaWiki de sorte à ce que le serveur web puisse écrire dedans.\nEnsuite, activez cette option.",
+ "config-upload-deleted": "Répertoire pour les fichiers supprimés :",
+ "config-upload-deleted-help": "Choisissez un répertoire qui servira à archiver les fichiers supprimés.\nIdéalement, il ne devrait pas être accessible depuis le web.",
+ "config-logo": "URL du logo :",
+ "config-logo-help": "L’habillage par défaut de MediaWiki comprend l’espace pour un logo de 135x160 pixels au-dessus de la barre de menu latérale.\nTéléchargez une image de la taille appropriée, et entrez son URL ici.\n\nVous pouvez utiliser <code>$wgStylePath</code> ou <code>$wgScriptPath</code> si votre logo est relatif à ces chemins.\n\nSi vous ne voulez pas de logo, laissez cette case vide.",
+ "config-instantcommons": "Activer ''InstantCommons''",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons InstantCommons] est un service qui permet d'utiliser les images, les sons et les autres médias disponibles sur le site [//commons.wikimedia.org/ Wikimedia Commons].\nPour se faire, il faut que MediaWiki accède à Internet.\n\nPour plus d'informations sur ce service, y compris les instructions sur la façon de le configurer pour d'autres wikis que Wikimedia Commons, consultez le [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos manuel] (en anglais).",
+ "config-cc-error": "Le sélection d'une licence ''Creative Commons'' n'a donné aucun résultat.\nEntrez le nom de la licence manuellement.",
+ "config-cc-again": "Choisissez à nouveau...",
+ "config-cc-not-chosen": "Choisissez une licence ''Creative Commons'' et cliquez sur « Continuer ».",
+ "config-advanced-settings": "Configuration avancée",
+ "config-cache-options": "Paramètres pour la mise en cache des objets:",
+ "config-cache-help": "La mise en cache des objets améliore la vitesse de MediaWiki en mettant en cache les données fréquemment utilisées.\nLes sites de taille moyenne à grande sont fortement encouragés à l'activer. Les petits sites y verront également des avantages.",
+ "config-cache-none": "Pas de mise en cache (aucune fonctionnalité n'a été supprimée, mais la vitesse peut changer sur les wikis importants)",
+ "config-cache-accel": "Mise en cache des objets PHP (APC, XCache ou WinCache)",
+ "config-cache-memcached": "Utiliser Memcached (nécessite une installation et une configuration supplémentaires)",
+ "config-memcached-servers": "serveurs pour Memcached :",
+ "config-memcached-help": "Liste des adresses IP à utiliser pour Memcached.\nUne par ligne, en indiquant le port à utiliser. Par exemple :\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Vous avez sélectionné Memcached comme type de cache, mais n'avez pas précisé de serveurs.",
+ "config-memcache-badip": "Vous avez entré une adresse IP invalide pour Memcached: $1.",
+ "config-memcache-noport": "Vous n'avez pas entré un port pour le serveur Memcached : $1.\nSi vous ne le connaissez pas, la valeur par défaut est 11211.",
+ "config-memcache-badport": "Les numéros de port de Memcached sont situés entre $1 et $2.",
+ "config-extensions": "Extensions",
+ "config-extensions-help": "Les extensions énumérées ci-dessus ont été détectées dans votre répertoire <code>./extensions</code>.\n\nElles peuvent nécessiter une configuration supplémentaire, mais vous pouvez les activer maintenant",
+ "config-skins": "Habillages",
+ "config-skins-help": "Les habillages listés ci-dessous ont été détectés dans votre répertoire <code>./skins</code>. Vous devez en activer au moins un, et choisir celui par défaut.",
+ "config-skins-use-as-default": "Utiliser cet habillage par défaut",
+ "config-skins-missing": "Aucun habillage trouvé ; MédiaWiki utilisera un habillage de secours jusqu’à ce que vous en installiez un approprié.",
+ "config-skins-must-enable-some": "Vous devez choisir au moins un habillage à activer.",
+ "config-skins-must-enable-default": "L’habillage choisi par défaut doit être activé.",
+ "config-install-alreadydone": "'''Attention''': Vous semblez avoir déjà installé MediaWiki et tentez de l'installer à nouveau.\nS'il vous plaît, allez à la page suivante.",
+ "config-install-begin": "En appuyant sur {{int:config-continue}}, vous commencerez l'installation de MediaWiki.\nSi vous voulez encore apporter des modifications, appuyez sur \"{{int:config-back}}\".",
+ "config-install-step-done": "fait",
+ "config-install-step-failed": "échec",
+ "config-install-extensions": "Inclusion des extensions",
+ "config-install-database": "Création de la base de données",
+ "config-install-schema": "Création de schéma",
+ "config-install-pg-schema-not-exist": "Le schéma PostgreSQL n'existe pas",
+ "config-install-pg-schema-failed": "Échec lors de la création des tables.\nAssurez-vous que l'utilisateur « $1 » peut écrire selon le schéma « $2 ».",
+ "config-install-pg-commit": "Validation des modifications",
+ "config-install-pg-plpgsql": "Vérification du langage PL/pgSQL",
+ "config-pg-no-plpgsql": "Vous devez installer le langage PL/pgSQL dans la base de données $1",
+ "config-pg-no-create-privs": "Le compte que vous avez spécifié pour l'installation n'a pas suffisamment de privilèges pour créer un compte.",
+ "config-pg-not-in-role": "Le compte que vous avez spécifié pour l'utilisateur web existe déjà.\nLe compte que vous avez spécifié pour l'installation n'est pas un super-utilisateur et n'est pas un membre du rôle de l'internaute, il est donc incapable de créer des objets appartenant à l'utilisateur web.\n\nMediaWiki exige actuellement que les tables soient possédés par un utilisateur web. S'il vous plaît, spécifiez un autre nom de compte web, ou cliquez sur \"retour\" et spécifiez un utilisateur ayant les privilèges suffisants pour installer.",
+ "config-install-user": "Création d'un utilisateur de la base de données",
+ "config-install-user-alreadyexists": "L'utilisateur « $1 » existe déjà.",
+ "config-install-user-create-failed": "Échec lors de la création de l'utilisateur « $1 » : $2",
+ "config-install-user-grant-failed": "Échec lors de l'ajout de permissions à l'utilisateur « $1 » : $2",
+ "config-install-user-missing": "L'utilisateur \"$1\" n'existe pas.",
+ "config-install-user-missing-create": "L'utilisateur \"$1\" n'existe pas.\nS'il vous plaît, cocher \"Compte de créer\" dans la case ci-dessous si vous voulez le créer.",
+ "config-install-tables": "Création des tables",
+ "config-install-tables-exist": "'''Avertissement:''' Les tables MediaWiki semblent déjà exister.\nCréation omise.",
+ "config-install-tables-failed": "'''Erreur:''' échec lors de la création de table avec l'erreur suivante: $1",
+ "config-install-interwiki": "Remplissage par défaut de la table des interwikis",
+ "config-install-interwiki-list": "Impossible de lire le fichier <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Attention:''' La table des interwikis semble déjà contenir des entrées.\nLa liste par défaut ne sera pas inscrite.",
+ "config-install-stats": "Initialisation des statistiques",
+ "config-install-keys": "Génération de la clé secrète",
+ "config-insecure-keys": "'''Avertissement''' : {{PLURAL:$2|Une clé de sécurité générée ($1) pendant l'installation n'est pas complètement sécuritaire. Envisagez de la modifier manuellement.|Des clés de sécurité générées ($1) pendant l'installation ne sont pas complètement sécuritaires. Envisagez de les modifier manuellement.}}",
+ "config-install-updates": "Empêcher l’exécution des mises à jour inutiles",
+ "config-install-updates-failed": "<strong>Erreur :</strong> L’insertion de clés modifiées dans les tables a échoué avec l’erreur suivante : $1",
+ "config-install-sysop": "Création du compte administrateur",
+ "config-install-subscribe-fail": "Impossible de s'abonner à mediawiki-announce : $1",
+ "config-install-subscribe-notpossible": "cURL n’est pas installé et <code>allow_url_fopen</code> n’est pas disponible.",
+ "config-install-mainpage": "Création de la page principale avec un contenu par défaut",
+ "config-install-extension-tables": "Création de tables pour les extensions activées",
+ "config-install-mainpage-failed": "Impossible d’insérer la page principale : $1",
+ "config-install-done": "'''Félicitations!'''\nVous avez réussi à installer MediaWiki.\n\nLe programme d'installation a généré un fichier <code>LocalSettings.php</code>. Il contient tous les paramètres de votre configuration.\n\nVous devrez le télécharger et le mettre à la racine de votre installation wiki (dans le même répertoire que index.php). Le téléchargement démarre automatiquement.\n\nSi le téléchargement n'a pas été offert, ou que vous l'avez annulé, vous pouvez démarrer à nouveau le téléchargement en cliquant ce lien :\n\n$3\n\n'''Note''': Si vous ne le faites pas maintenant, ce fichier de configuration généré ne sera pas disponible plus tard si vous quittez l'installation sans le télécharger.\n\nLorsque c'est fait, vous pouvez '''[$2 accéder à votre wiki]'''.",
+ "config-download-localsettings": "Télécharger <code>LocalSettings.php</code>",
+ "config-help": "aide",
+ "config-help-tooltip": "cliquer pour agrandir",
+ "config-nofile": "Le fichier « $1 » est introuvable. A-t-il été supprimé ?",
+ "config-extension-link": "Saviez-vous que votre wiki supporte [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions des extensions] ?\n\nVous pouvez consulter les [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions par catégorie].",
+ "mainpagetext": "<strong>MediaWiki a été installé avec succès.</strong>",
+ "mainpagedocfooter": "Consultez le [//meta.wikimedia.org/wiki/Help:Contents/fr Guide de l’utilisateur] pour plus d’informations sur l’utilisation de ce logiciel de wiki.\n\n== Pour démarrer ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste des paramètres de configuration]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/fr Questions courantes sur MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste de discussion sur les distributions de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Adaptez MediaWiki dans votre langue]"
+}
diff --git a/includes/installer/i18n/frc.json b/includes/installer/i18n/frc.json
new file mode 100644
index 00000000..e05cd252
--- /dev/null
+++ b/includes/installer/i18n/frc.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Vous avez bien installé MediaWiki.'''",
+ "mainpagedocfooter": "Lisez la [//meta.wikimedia.org/wiki/Help:Contents Guide des Useurs] pour apprendre à user le wiki software.\n\n== Pour Commencer ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Réglage]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki: Questions Souvent Posées]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki Liste à Malle]"
+}
diff --git a/includes/installer/i18n/frp.json b/includes/installer/i18n/frp.json
new file mode 100644
index 00000000..fe0999f1
--- /dev/null
+++ b/includes/installer/i18n/frp.json
@@ -0,0 +1,153 @@
+{
+ "@metadata": {
+ "authors": [
+ "ChrisPtDe"
+ ]
+ },
+ "config-desc": "La programeria d’enstalacion de MediaWiki",
+ "config-title": "Enstalacion de MediaWiki $1",
+ "config-information": "Enformacions",
+ "config-localsettings-key": "Cllâf de misa a jorn :",
+ "config-session-error": "Èrror pendent l’emmodâ de la sèance : $1",
+ "config-your-language": "Voutra lengoua :",
+ "config-wiki-language": "Lengoua du vouiqui :",
+ "config-back": "← Retôrn",
+ "config-continue": "Continuar →",
+ "config-page-language": "Lengoua",
+ "config-page-welcome": "Benvegnua dessus MediaWiki !",
+ "config-page-dbconnect": "Sè branchiér a la bâsa de balyês",
+ "config-page-upgrade": "Betar a jorn l’enstalacion ègzistenta",
+ "config-page-dbsettings": "Paramètres de la bâsa de balyês",
+ "config-page-name": "Nom",
+ "config-page-options": "Chouèx",
+ "config-page-install": "Enstalar",
+ "config-page-complete": "Chavonâ !",
+ "config-page-restart": "Tornar emmodar l’enstalacion",
+ "config-page-readme": "Liéséd-mè",
+ "config-page-releasenotes": "Notes de publecacion",
+ "config-page-copying": "Copia",
+ "config-page-upgradedoc": "Misa a jorn",
+ "config-page-existingwiki": "Vouiqui ègzistent",
+ "config-env-php": "PHP $1 est enstalâ.",
+ "config-env-php-toolow": "PHP $1 est enstalâ.\nPortant, MediaWiki at fôta de PHP $2 ou ben ples hôt.",
+ "config-memory-raised": "Lo paramètre <code>memory_limit</code> de PHP ére a $1, portâ a $2.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] est enstalâ",
+ "config-apc": "[http://www.php.net/apc APC] est enstalâ",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] est enstalâ",
+ "config-diff3-bad": "GNU diff3 entrovâblo.",
+ "config-db-type": "Tipo de bâsa de balyês :",
+ "config-db-host": "Hôto de la bâsa de balyês :",
+ "config-db-host-oracle": "TNS de la bâsa de balyês :",
+ "config-db-wiki-settings": "Identifiar cél vouiqui",
+ "config-db-name": "Nom de la bâsa de balyês :",
+ "config-db-name-oracle": "Plan de bâsa de balyês :",
+ "config-db-install-account": "Compto usanciér por l’enstalacion",
+ "config-db-username": "Nom d’usanciér de la bâsa de balyês :",
+ "config-db-password": "Contresegno de la bâsa de balyês :",
+ "config-db-wiki-account": "Compto usanciér por l’opèracion normala",
+ "config-db-prefix": "Prèfixo de les trâbles de la bâsa de balyês :",
+ "config-db-charset": "Juè de caractèros de la bâsa de balyês",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binèro",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 rètrocompatiblo UTF-8",
+ "config-mysql-old": "MySQL $1 ou ben ples novél est nècèssèro, vos avéd $2.",
+ "config-db-port": "Pôrt de la bâsa de balyês :",
+ "config-db-schema": "Plan por MediaWiki",
+ "config-pg-test-error": "Empossiblo de sè branchiér a la bâsa de donâs '''$1''' : $2",
+ "config-sqlite-dir": "Dossiér de les balyês SQLite :",
+ "config-oracle-def-ts": "Èspâço de stocâjo (''tablespace'') per dèfôt :",
+ "config-oracle-temp-ts": "Èspâço de stocâjo (''tablespace'') temporèro :",
+ "config-header-mysql": "Paramètres de MySQL",
+ "config-header-postgres": "Paramètres de PostgreSQL",
+ "config-header-sqlite": "Paramètres de SQLite",
+ "config-header-oracle": "Paramètres d’Oracle",
+ "config-invalid-db-type": "Tipo de bâsa de balyês envalido",
+ "config-missing-db-name": "Vos dête buchiér una valor por « Nom de la bâsa de balyês »",
+ "config-missing-db-host": "Vos dête buchiér una valor por « Hôto de la bâsa de balyês »",
+ "config-missing-db-server-oracle": "Vos dête buchiér una valor por « TNS de la bâsa de balyês »",
+ "config-sqlite-readonly": "Lo fichiér <code>$1</code> est pas accèssiblo en ècritura.",
+ "config-regenerate": "Refâre LocalSettings.php →",
+ "config-show-table-status": "Falyita de la requéta <code>SHOW TABLE STATUS</code> !",
+ "config-db-web-account": "Compto de la bâsa de balyês por l’accès vouèbe",
+ "config-db-web-account-same": "Utilisâd lo mémo compto que por l’enstalacion",
+ "config-db-web-create": "Féte lo compto s’ègziste p’oncor",
+ "config-mysql-engine": "Motor de stocâjo :",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Juè de caractèros de la bâsa de balyês :",
+ "config-mysql-binary": "Binèro",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Nom du vouiqui :",
+ "config-site-name-blank": "Buchiéd un nom de seto.",
+ "config-project-namespace": "Èspâço de noms du projèt :",
+ "config-ns-generic": "Projèt",
+ "config-ns-site-name": "Mémo nom que lo vouiqui : $1",
+ "config-ns-other": "Ôtro (spècefiar)",
+ "config-ns-other-default": "MonVouiqui",
+ "config-admin-box": "Compto administrator",
+ "config-admin-name": "Voutron nom :",
+ "config-admin-password": "Contresegno :",
+ "config-admin-password-confirm": "Tornar buchiér lo contresegno :",
+ "config-admin-name-blank": "Buchiéd un nom d’administrator.",
+ "config-admin-password-blank": "Buchiéd un contresegno por lo compto administrator.",
+ "config-admin-email": "Adrèce èlèctronica :",
+ "config-optional-continue": "Mè posar més de quèstions.",
+ "config-profile": "Profil des drêts d’usanciér :",
+ "config-profile-wiki": "Vouiqui tradicionâl",
+ "config-profile-no-anon": "Crèacion de compto nècèssèra",
+ "config-profile-fishbowl": "Solament los èditors ôtorisâs",
+ "config-profile-private": "Vouiqui privâ",
+ "config-license": "Drêts d’ôtor et licence :",
+ "config-license-none": "Gins de licence d’avâl la pâge",
+ "config-license-cc-by-sa": "Creative Commons patèrnitât - partâjo a l’identico",
+ "config-license-cc-by": "Creative Commons patèrnitât",
+ "config-license-cc-by-nc-sa": "Creative Commons patèrnitât pas comèrciâla - partâjo a l’identico",
+ "config-license-cc-0": "Creative Commons Zero (domêno publico)",
+ "config-license-gfdl": "Licence de documentacion libra GNU 1.3 ou ben ples novèla",
+ "config-license-pd": "Domêno publico",
+ "config-license-cc-choose": "Chouèsir una licence Creative Commons pèrsonalisâ",
+ "config-email-settings": "Paramètres de mèssageria èlèctronica",
+ "config-enable-email": "Activar los mèssâjos que sôrtont",
+ "config-email-user": "Activar los mèssâjos d’usanciér a usanciér",
+ "config-email-usertalk": "Activar la notificacion de les pâges de discussion ux usanciérs",
+ "config-email-watchlist": "Activar la notificacion de la lista de survelyence",
+ "config-email-auth": "Activar l’ôtenticacion per mèssageria èlèctronica",
+ "config-email-sender": "Adrèce èlèctronica de retôrn :",
+ "config-upload-settings": "Tèlèchargement de les émâges et des fichiérs",
+ "config-upload-enable": "Activar lo tèlèchargement des fichiérs",
+ "config-upload-deleted": "Dossiér por los fichiérs suprimâs :",
+ "config-logo": "URL du logô :",
+ "config-instantcommons": "Activar Instant Commons",
+ "config-cc-again": "Tornâd chouèsir...",
+ "config-advanced-settings": "Configuracion avanciê",
+ "config-cache-options": "Paramètres por la misa en cache de les chouses :",
+ "config-cache-accel": "Misa en cache de les chouses PHP (APC, XCache ou ben WinCache)",
+ "config-memcached-servers": "Sèrvors por memcached :",
+ "config-extensions": "Èxtensions",
+ "config-install-step-done": "fêt",
+ "config-install-step-failed": "falyita",
+ "config-install-extensions": "Encllusion de les èxtensions",
+ "config-install-database": "Crèacion de la bâsa de balyês",
+ "config-install-schema": "Crèacion de plan",
+ "config-install-pg-schema-not-exist": "Lo plan PostgreSQL ègziste pas",
+ "config-install-pg-commit": "Validacion des changements",
+ "config-install-pg-plpgsql": "Contrôlo du lengâjo PL/pgSQL",
+ "config-install-user": "Crèacion d’un usanciér de la bâsa de balyês",
+ "config-install-user-alreadyexists": "L’usanciér « $1 » ègziste ja",
+ "config-install-user-create-failed": "Falyita pendent la crèacion de l’usanciér « $1 » : $2",
+ "config-install-user-grant-failed": "Falyita pendent l’aponsa de pèrmissions a l’usanciér « $1 » : $2",
+ "config-install-tables": "Crèacion de les trâbles",
+ "config-install-interwiki": "Remplissâjo per dèfôt de la trâbla des entèrvouiquis",
+ "config-install-interwiki-list": "Empossiblo de trovar lo fichiér <code>interwiki.list</code>.",
+ "config-install-stats": "Inicialisacion de les statistiques",
+ "config-install-keys": "G·ènèracion de les cllâfs secrètes",
+ "config-install-sysop": "Crèacion du compto administrator",
+ "config-install-subscribe-fail": "Empossiblo de s’abonar a mediawiki-announce : $1",
+ "config-install-mainpage": "Crèacion de la pâge principâla avouéc un contegnu per dèfôt",
+ "config-install-extension-tables": "Crèacion de trâbles por les èxtensions activâs",
+ "config-install-mainpage-failed": "Empossiblo d’entrebetar la pâge principâla : $1",
+ "config-download-localsettings": "Tèlèchargiér <code>LocalSettings.php</code>",
+ "config-help": "éde",
+ "mainpagetext": "'''MediaWiki at étâ enstalâ avouéc reusséta.'''",
+ "mainpagedocfooter": "Vêde lo [//meta.wikimedia.org/wiki/Aide:Contenu guido d’usanciér] por més d’enformacions sur l’usâjo de la programeria vouiqui.\n\n== Emmodar avouéc MediaWiki ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista des paramètres de configuracion]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/fr FDQ sur MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussion sur les distribucions de MediaWiki]"
+}
diff --git a/includes/installer/i18n/frr.json b/includes/installer/i18n/frr.json
new file mode 100644
index 00000000..b5d7d26c
--- /dev/null
+++ b/includes/installer/i18n/frr.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Murma174",
+ "Pyt"
+ ]
+ },
+ "mainpagetext": "'''Det instaliarin faan MediaWiki hää loket.'''",
+ "mainpagedocfooter": "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/fur.json b/includes/installer/i18n/fur.json
new file mode 100644
index 00000000..fa152d94
--- /dev/null
+++ b/includes/installer/i18n/fur.json
@@ -0,0 +1,15 @@
+{
+ "@metadata": {
+ "authors": [
+ "Tocaibon"
+ ]
+ },
+ "config-desc": "Program di instalazion di Mediawiki",
+ "config-title": "Instalazion MediaWiki $1",
+ "config-information": "Informazions",
+ "config-localsettings-upgrade": "Al è stât cjatât un file <code>LocalSettings.php</code>.\nPar inzornâ cheste instalazion, si à di inserî il valôr di <code>$wgUpgradeKey</code> inte casele sot.\nIl valôr lu si cjate in <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Al è stât cjatât un file <code>LocalSettings.php</code>.\nPar inzornâ cheste instalazion, si à di eseguî <code>update.php</code>.",
+ "config-localsettings-key": "Clâf di inzornament.",
+ "config-localsettings-badkey": "La Clâf metude no jê juste",
+ "mainpagetext": "'''MediaWiki e je stade instalade cun sucès.'''"
+}
diff --git a/includes/installer/i18n/fy.json b/includes/installer/i18n/fy.json
new file mode 100644
index 00000000..1eb0d894
--- /dev/null
+++ b/includes/installer/i18n/fy.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki-program goed ynstallearre.'''",
+ "mainpagedocfooter": "Rieplachtsje de [//meta.wikimedia.org/wiki/Help:Contents Ynhâldsopjefte hantlieding] foar ynformaasje oer it gebrûk fan 'e wikisoftware.\n\n== Mear help oer Mediawiki ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings List mei ynstellings]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Faak stelde fragen (FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglist foar oankundigings fan nije ferzjes]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/ga.json b/includes/installer/i18n/ga.json
new file mode 100644
index 00000000..0d075e5d
--- /dev/null
+++ b/includes/installer/i18n/ga.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "පසිඳු කාවින්ද"
+ ]
+ },
+ "config-page-language": "Teanga",
+ "config-page-name": "Ainm",
+ "config-admin-password": "D'fhocal faire:",
+ "config-help": "Cuidiú",
+ "mainpagetext": "'''D'éirigh le suiteáil MediaWiki.'''",
+ "mainpagedocfooter": "Féach ar [//meta.wikimedia.org/wiki/MediaWiki_localisation doiciméid um conas an chomhéadán a athrú]\nagus an [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Lámhleabhar úsáideora] chun cabhair úsáide agus fíoraíochta a fháil."
+}
diff --git a/includes/installer/i18n/gag.json b/includes/installer/i18n/gag.json
new file mode 100644
index 00000000..1db31e9c
--- /dev/null
+++ b/includes/installer/i18n/gag.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki başarılan kuruldu.'''",
+ "mainpagedocfooter": "Vikilän iş uurunda bilgi almaa için [//meta.wikimedia.org/wiki/Help:Contents User's Guide] sayfasına bakınız\n\n== Eni başlayanlar için ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/gan-hans.json b/includes/installer/i18n/gan-hans.json
new file mode 100644
index 00000000..a7987727
--- /dev/null
+++ b/includes/installer/i18n/gan-hans.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''安装正MediaWiki喽。'''",
+ "mainpagedocfooter": "参看[//meta.wikimedia.org/wiki/Help:Contents 用户指南]里头会话到啷用wiki软件\n\n== 开始使用 ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki 配置设定列表]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki 平常问题解答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 发布email清单]"
+}
diff --git a/includes/installer/i18n/gan-hant.json b/includes/installer/i18n/gan-hant.json
new file mode 100644
index 00000000..87f1df55
--- /dev/null
+++ b/includes/installer/i18n/gan-hant.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Symane"
+ ]
+ },
+ "mainpagetext": "'''安裝正MediaWiki哩。'''",
+ "mainpagedocfooter": "參看[//meta.wikimedia.org/wiki/Help:Contents 用戶指南]裡頭會話到啷用wiki軟件\n\n== 開始使用 ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki 配置設定列表]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki 平常問題解答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈email清單]"
+}
diff --git a/includes/installer/i18n/gd.json b/includes/installer/i18n/gd.json
new file mode 100644
index 00000000..1a01d1f0
--- /dev/null
+++ b/includes/installer/i18n/gd.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Akerbeltz"
+ ]
+ },
+ "mainpagetext": "'''Chaidh MediaWiki a stàladh gu soirbheachail.'''",
+ "mainpagedocfooter": "Cuir sùil air [//meta.wikimedia.org/wiki/Help:Contents treòir nan cleachdaichean] airson fiosrachadh mu chleachdadh a' bhathar-bhog wiki.\n\n== Toiseach tòiseachaidh ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liosta suidheachadh nan roghainnean]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ CÀBHA MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liosta puist nan sgaoilidhean MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Cuir do chànan air MediaWiki]"
+}
diff --git a/includes/installer/i18n/gl.json b/includes/installer/i18n/gl.json
new file mode 100644
index 00000000..a42cb179
--- /dev/null
+++ b/includes/installer/i18n/gl.json
@@ -0,0 +1,331 @@
+{
+ "@metadata": {
+ "authors": [
+ "Elisardojm",
+ "Toliño",
+ "아라",
+ "Vivaelcelta"
+ ]
+ },
+ "config-desc": "O programa de instalación de MediaWiki",
+ "config-title": "Instalación de MediaWiki $1",
+ "config-information": "Información",
+ "config-localsettings-upgrade": "Detectouse un ficheiro <code>LocalSettings.php</code>.\nPara actualizar esta instalación, introduza o valor de <code>$wgUpgradeKey</code> na caixa.\nPode atopalo en <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Detectouse un ficheiro <code>LocalSettings.php</code>.\nPara actualizar esta instalación, execute <code>update.php</code>",
+ "config-localsettings-key": "Clave de actualización:",
+ "config-localsettings-badkey": "A clave dada é incorrecta",
+ "config-upgrade-key-missing": "Detectouse unha instalación existente de MediaWiki.\nPara actualizar esta instalación, inclúa esta liña ao final do ficheiro <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Semella que o ficheiro <code>LocalSettings.php</code> existente está incompleto.\nA variable $1 non está establecida.\nModifique o ficheiro <code>LocalSettings.php</code> de xeito que a variable quede establecida e prema en \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Atopouse un erro ao conectar coa base de datos empregando a configuración especificada no ficheiro <code>LocalSettings.php</code>. Corrixa esta configuración e inténteo de novo.\n\n$1",
+ "config-session-error": "Erro ao iniciar a sesión: $1",
+ "config-session-expired": "Semella que os seus datos da sesión caducaron.\nAs sesións están configuradas para unha duración de $1.\nPode incrementar isto fixando <code>session.gc_maxlifetime</code> en php.ini.\nReinicie o proceso de instalación.",
+ "config-no-session": "Perdéronse os datos da súa sesión!\nComprobe o seu php.ini e asegúrese de que en <code>session.save_path</code> está definido un directorio correcto.",
+ "config-your-language": "A súa lingua:",
+ "config-your-language-help": "Seleccione a lingua que se empregará durante o proceso de instalación.",
+ "config-wiki-language": "Lingua do wiki:",
+ "config-wiki-language-help": "Seleccione a lingua que predominará no wiki.",
+ "config-back": "← Volver",
+ "config-continue": "Continuar →",
+ "config-page-language": "Lingua",
+ "config-page-welcome": "Benvido a MediaWiki!",
+ "config-page-dbconnect": "Conectarse á base de datos",
+ "config-page-upgrade": "Actualizar a instalación actual",
+ "config-page-dbsettings": "Configuración da base de datos",
+ "config-page-name": "Nome",
+ "config-page-options": "Opcións",
+ "config-page-install": "Instalar",
+ "config-page-complete": "Completo!",
+ "config-page-restart": "Reiniciar a instalación",
+ "config-page-readme": "Léame",
+ "config-page-releasenotes": "Notas de lanzamento",
+ "config-page-copying": "Copiar",
+ "config-page-upgradedoc": "Actualizar",
+ "config-page-existingwiki": "Wiki existente",
+ "config-help-restart": "Quere eliminar todos os datos gardados e reiniciar o proceso de instalación?",
+ "config-restart": "Si, reiniciala",
+ "config-welcome": "=== Comprobación da contorna ===\nCómpre realizar agora unhas comprobacións básicas para ver se a contorna é axeitada para a instalación de MediaWiki.\nLembre incluír esta información se necesita axuda para completar a instalación.",
+ "config-copyright": "=== Dereitos de autor e termos de uso ===\n\n$1\n\nEste programa é software libre; pode redistribuílo e/ou modificalo segundo os termos da licenza pública xeral GNU publicada pola Free Software Foundation; versión 2 ou (na súa escolla) calquera outra posterior.\n\nEste programa distribúese coa esperanza de que poida ser útil, pero <strong>sen garantía ningunha</strong>; nin sequera a garantía implícita de <strong>comercialización</strong> ou <strong>adecuación a unha finalidade específica</strong>.\nOlle a licenza pública xeral GNU para obter máis detalles.\n\nDebería recibir <doclink href=Copying>unha copia da licenza pública xeral GNU</doclink> xunto ao programa; se non é así, escriba á Free Software Foundation, Inc., rúa Franklin, número 51, quinto andar, Boston, Massachusetts, 02110-1301, Estados Unidos de América ou [http://www.gnu.org/copyleft/gpl.html lea a licenza en liña].",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/gl Páxina principal de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guía de usuario]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guía de administrador]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Preguntas máis frecuentes]\n----\n* <doclink href=Readme>Léame</doclink>\n* <doclink href=ReleaseNotes>Notas de lanzamento</doclink>\n* <doclink href=Copying>Copia</doclink>\n* <doclink href=UpgradeDoc>Actualizacións</doclink>",
+ "config-env-good": "Rematou a comprobación da contorna.\nPode instalar MediaWiki.",
+ "config-env-bad": "Rematou a comprobación da contorna.\nNon pode instalar MediaWiki.",
+ "config-env-php": "Está instalado o PHP $1.",
+ "config-env-hhvm": "Está instalado o HHVM $1.",
+ "config-unicode-using-utf8": "Usando utf8_normalize.so de Brion Vibber para a normalización Unicode.",
+ "config-unicode-using-intl": "Usando a [http://pecl.php.net/intl extensión intl PECL] para a normalización Unicode.",
+ "config-unicode-pure-php-warning": "<strong>Atención:</strong> A [http://pecl.php.net/intl extensión intl PECL] non está dispoñible para manexar a normalización Unicode; volvendo á implementación lenta de PHP puro.\nSe o seu sitio posúe un alto tráfico de visitantes, debería ler un chisco sobre a [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].",
+ "config-unicode-update-warning": "<strong>Atención:</strong> A versión instalada da envoltura de normalización Unicode emprega unha versión vella da biblioteca [http://site.icu-project.org/ do proxecto ICU].\nDebería [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations actualizar] se o uso de Unicode é importante para vostede.",
+ "config-no-db": "Non se puido atopar un controlador axeitado para a base de datos! Necesita instalar un controlador de base de datos para PHP.\nOs tipos de base de datos admitidos son os seguintes: $1.\n\nSe compilou o PHP vostede mesmo, reconfigúreo activando un cliente de base de datos, por exemplo, usando <code>./configure --with-mysql</code>.\nSe instalou o PHP desde un paquete Debian ou Ubuntu, entón tamén necesita instalar, por exemplo, o módulo <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "<strong>Atención:</strong> Ten o SQLite $1, que é inferior á versión mínima necesaria: $2. O SQLite non estará dispoñible.",
+ "config-no-fts3": "<strong>Atención:</strong> O SQLite está compilado sen o [//sqlite.org/fts3.html módulo FTS3]; as características de procura non estarán dispoñibles nesta instalación.",
+ "config-register-globals-error": "<strong>Erro: A opción <code>[http://php.net/register_globals register_globals]</code> do PHP está activada.\nCómpre desactivala para continuar a instalación.</strong>\nConsulte o enderezo [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] para obter axuda sobre como facelo.",
+ "config-magic-quotes-gpc": "<strong>Erro fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] está activado!</strong>\nEsta opción corrompe os datos de entrada de xeito imprevisible.\nNon pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
+ "config-magic-quotes-runtime": "<strong>Erro fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está activado!</strong>\nEsta opción corrompe os datos de entrada de xeito imprevisible.\nNon pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
+ "config-magic-quotes-sybase": "<strong>Erro fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está activado!</strong>\nEsta opción corrompe os datos de entrada de xeito imprevisible.\nNon pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
+ "config-mbstring": "<strong>Erro fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está activado!</strong>\nEsta opción causa erros e pode corromper os datos de xeito imprevisible.\nNon pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
+ "config-safe-mode": "<strong>Atención:</strong> O [http://www.php.net/features.safe-mode safe mode] do PHP está activado.\nIsto pode causar problemas, particularmente se emprega cargas de ficheiros e soporte de <code>math</code>.",
+ "config-xml-bad": "Falta o módulo XML do PHP.\nMediaWiki necesita funcións neste módulo e non funcionará con esta configuración.\nSe está executando o Mandrake, instale o paquete php-xml.",
+ "config-pcre-old": "<strong>Erro fatal:</strong> Necesítase PCRE $1 ou posterior.\nO seu PHP binario está ligado con PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Máis información].",
+ "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-ctype": "<strong>Erro fatal:</strong> O PHP debe compilarse co soporte para a [http://www.php.net/manual/en/ctype.installation.php extensión Ctype].",
+ "config-iconv": "<strong>Erro fatal:</strong> O PHP debe compilarse co soporte para a [http://www.php.net/manual/en/iconv.installation.php extensión iconv].",
+ "config-json": "<strong>Erro fatal:</strong> O PHP compilouse sen o soporte de JSON.\nDebe instalar ben a extensión JSON do PHP ou a extensión [http://pecl.php.net/package/jsonc PECL jsonc] antes de instalar MediaWiki.\n* A extensión do PHP está incluída en Red Hat Enterprise Linux (CentOS) 5 e 6, mais debe activarse <code>/etc/php.ini</code> ou <code>/etc/php.d/json.ini</code>.\n* Algunhas distribucións do Linux lanzadas despois de maio de 2013 omiten a extensión do PHP, pero inclúen a extensión PECL como <code>php5-json</code> ou <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] está instalado",
+ "config-apc": "[http://www.php.net/apc APC] está instalado",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] está instalado",
+ "config-no-cache": "<strong>Atención:</strong> Non se puido atopar [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].\nA caché de obxectos está desactivada.",
+ "config-mod-security": "<strong>Atención:</strong> O seu servidor web ten o [http://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 [http://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-git-bad": "Non se atopou o software de control da versión de Git.",
+ "config-imagemagick": "ImageMagick atopado: <code>$1</code>.\nAs miniaturas de imaxes estarán dispoñibles se activa as cargas.",
+ "config-gd": "Atopouse a biblioteca gráfica GD integrada.\nAs miniaturas de imaxes estarán dispoñibles se activa as cargas.",
+ "config-no-scaling": "Non se puido atopar a biblioteca GD ou ImageMagick.\nAs miniaturas de imaxes estarán desactivadas.",
+ "config-no-uri": "<strong>Erro:</strong> Non se puido determinar o URI actual.\nInstalación abortada.",
+ "config-no-cli-uri": "<strong>Atención:</strong> Non se especificou ningún <code>--scriptpath</code>; por defecto, usarase: <code>$1</code>.",
+ "config-using-server": "Usando o nome do servidor \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Usando o URL do servidor \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "<strong>Atención:</strong> O seu directorio por defecto para as cargas, <code>$1</code>, é vulnerable a execucións arbitrarias de escrituras.\nAínda que MediaWiki comproba todos os ficheiros cargados por se houbese ameazas de seguridade, é amplamente recomendable [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security pechar esta vulnerabilidade de seguridade] antes de activar as cargas.",
+ "config-no-cli-uploads-check": "<strong>Atención:</strong> Durante a instalación CLI, o seu directorio por defecto para as cargas, <code>$1</code>, non se comproba fronte a posibles vulnerabilidades de execucións arbitrarias de escrituras.",
+ "config-brokenlibxml": "O seu sistema ten unha combinación de versións de PHP e libxml2 que pode ser problemático e causar corrupción de datos en MediaWiki e outras aplicacións web.\nActualice o sistema á versión 2.7.3 ou posterior de libxml2 ([https://bugs.php.net/bug.php?id=45996 erro presentado co PHP]).\nInstalación abortada.",
+ "config-suhosin-max-value-length": "Suhosin está instalado e limita o parámetro GET <code>length</code> a $1 bytes.\nO compoñente ResourceLoader (xestor de recursos) de MediaWiki traballa neste límite, pero este prexudica o rendemento.\nSe é posible, debería establecer <code>suhosin.get.max_value_length</code> no valor 1024 ou superior en <code>php.ini</code> e establecer <code>$wgResourceLoaderMaxQueryLength</code> no mesmo valor en <code>LocalSettings.php</code>.",
+ "config-db-type": "Tipo de base de datos:",
+ "config-db-host": "Servidor da base de datos:",
+ "config-db-host-help": "Se o servidor da súa base de datos está nun servidor diferente, escriba o nome do servidor ou o enderezo IP aquí.\n\nSe está usando un aloxamento web compartido, o seu provedor de hospedaxe debe darlle o nome de servidor correcto na súa documentación.\n\nSe está a realizar a instalación nun servidor de Windows con MySQL, o nome \"localhost\" pode non valer como servidor. Se non funcionase, inténteo con \"127.0.0.1\" como enderezo IP local.\n\nSe está usando PostgreSQL, deixe este campo en branco para facer a conexión a través do conectador Unix.",
+ "config-db-host-oracle": "TNS da base de datos:",
+ "config-db-host-oracle-help": "Insira un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nome de conexión local] válido; cómpre que haxa visible un ficheiro tnsnames.ora para esta instalación.<br />Se está a empregar bibliotecas cliente versión 10g ou máis recentes, tamén pode usar o método de atribución de nomes [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identificar o wiki",
+ "config-db-name": "Nome da base de datos:",
+ "config-db-name-help": "Escolla un nome que identifique o seu wiki.\nNon debe conter espazos.\n\nSe está usando un aloxamento web compartido, o seu provedor de hospedaxe daralle un nome específico para a base de datos ou deixaralle crear unha a través do panel de control.",
+ "config-db-name-oracle": "Esquema da base de datos:",
+ "config-db-account-oracle-warn": "Existen tres escenarios soportados para a instalación de Oracle como fin da base de datos:\n\nSe quere crear unha conta para a base de datos como parte do proceso de instalación, proporcione unha conta co papel SYSDBA e especifique as credenciais desexadas para a conta; senón pode crear a conta manualmente e dar só esa conta (se ten os permisos necesarios para crear os obxectos do esquema) ou fornecer dous contas diferentes, unha con privilexios de creación e outra restrinxida para o acceso á web.\n\nA escritura para crear unha conta cos privilexios necesarios atópase no directorio \"maintenance/oracle/\" desta instalación. Teña en conta que o emprego de contas restrinxidas desactivará todas as operacións de mantemento da conta predeterminada.",
+ "config-db-install-account": "Conta de usuario para a instalación",
+ "config-db-username": "Nome de usuario da base de datos:",
+ "config-db-password": "Contrasinal da base de datos:",
+ "config-db-password-empty": "Introduza un contrasinal para o novo usuario da base de datos: $1.\nMalia que é posible crear usuarios sen contrasinal, esta práctica non é segura.",
+ "config-db-username-empty": "Debe introducir un valor para \"{{int:config-db-username}}\"",
+ "config-db-install-username": "Escriba o nome de usuario que empregará para conectarse á base de datos durante o proceso de instalación. Este non é o nome de usuario da conta de MediaWiki, trátase do nome de usuario para a súa base de datos.",
+ "config-db-install-password": "Escriba o contrasinal que empregará para conectarse á base de datos durante o proceso de instalación. Este non é o contrasinal da conta de MediaWiki, trátase do contrasinal para a súa base de datos.",
+ "config-db-install-help": "Introduza o nome de usuario e contrasinal que se usará para conectar á base de datos durante o proceso de instalación.",
+ "config-db-account-lock": "Use o mesmo nome de usuario e contrasinal despois do proceso de instalación",
+ "config-db-wiki-account": "Conta de usuario para despois do proceso de instalación",
+ "config-db-wiki-help": "Introduza o nome de usuario e mais o contrasinal que se usarán para conectar á base de datos durante o funcionamento habitual do wiki.\nSe a conta non existe e a conta de instalación ten privilexios suficientes, esa conta de usuario será creada cos privilexios mínimos necesarios para o funcionamento do wiki.",
+ "config-db-prefix": "Prefixo das táboas da base de datos:",
+ "config-db-prefix-help": "Se necesita compartir unha base de datos entre varios wikis ou entre MediaWiki e outra aplicación web, pode optar por engadir un prefixo a todos os nomes da táboa para evitar conflitos.\nNon utilice espazos.\n\nO normal é que este campo quede baleiro.",
+ "config-db-charset": "Conxunto de caracteres da base de datos",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binario",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 retrocompatible UTF-8",
+ "config-charset-help": "<strong>Atención:</strong> Se emprega <strong>backwards-compatible UTF-8</strong> no MySQL 4.1+ e posteriormente realiza unha copia de seguridade da base de datos con <code>mysqldump</code>, pode destruír todos os caracteres que non sexan ASCII, corrompendo de xeito irreversible as súas copias!\n\nNo <strong>modo binario</strong>, MediaWiki almacena texto UTF-8 na base de datos en campos binarios.\nIsto é máis eficaz ca o modo UTF-8 de MySQL e permítelle usar o rango completo de caracteres Unicode.\nNo <strong>modo UTF-8</strong>, MySQL saberá o xogo de caracteres dos seus datos e pode presentar e converter os datos de maneira axeitada,\npero non lle deixará gardar caracteres por riba do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
+ "config-mysql-old": "Necesítase MySQL $1 ou posterior. Vostede ten a versión $2.",
+ "config-db-port": "Porto da base de datos:",
+ "config-db-schema": "Esquema para MediaWiki",
+ "config-db-schema-help": "O normal é que este esquema sexa correcto.\nCámbieo soamente se sabe que é necesario.",
+ "config-pg-test-error": "Non se pode conectar coa base de datos <strong>$1</strong>: $2",
+ "config-sqlite-dir": "Directorio de datos SQLite:",
+ "config-sqlite-dir-help": "SQLite recolle todos os datos nun ficheiro único.\n\nO servidor web debe ter permisos sobre o directorio para que poida escribir nel durante a instalación.\n\nAdemais, o servidor <strong>non</strong> debe ser accesible a través da web, motivo polo que non está no mesmo lugar ca os ficheiros PHP.\n\nAsemade, o programa de instalación escribirá un ficheiro <code>.htaccess</code>, pero se erra alguén pode obter acceso á súa base de datos.\nIsto inclúe datos de usuario (enderezos de correo electrónico, contrasinais codificados), así como revisións borradas e outros datos restrinxidos no wiki.\n\nConsidere poñer a base de datos nun só lugar, por exemplo en <code>/var/lib/mediawiki/oseuwiki</code>.",
+ "config-oracle-def-ts": "Espazo de táboas por defecto:",
+ "config-oracle-temp-ts": "Espazo de táboas temporal:",
+ "config-type-mysql": "MySQL (ou compatible)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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-mysql-url}} MySQL] é o obxectivo principal para MediaWiki e está mellor soportado. MediaWiki tamén funciona con [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], que son compatibles con MySQL. ([http://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. É posible que haxa algúns pequenos erros e non se recomenda o seu uso nunha contorna de produción. ([http://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. ([http://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 de nivel empresarial. ([http://www.php.net/manual/en/oci8.installation.php Como compilar o PHP con compatibilidade 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. ([http://www.php.net/manual/en/sqlsrv.installation.php Como compilar o PHP con compatibilidade SQLSRV])",
+ "config-header-mysql": "Configuración do MySQL",
+ "config-header-postgres": "Configuración do PostgreSQL",
+ "config-header-sqlite": "Configuración do SQLite",
+ "config-header-oracle": "Configuración do Oracle",
+ "config-header-mssql": "Configuración de Microsoft SQL Server",
+ "config-invalid-db-type": "Tipo de base de datos incorrecto",
+ "config-missing-db-name": "Debe introducir un valor para \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Debe introducir un valor para \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Debe introducir un valor para \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "O TNS da base de datos, \"$1\", é incorrecto.\nUtilice só \"TNS Name\" ou unha cadea de texto \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm métodos de nomeamento de Oracle])",
+ "config-invalid-db-name": "O nome da base de datos, \"$1\", é incorrecto.\nSó pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e guións (-).",
+ "config-invalid-db-prefix": "O prefixo da base de datos, \"$1\", é incorrecto.\nSó pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e guións (-).",
+ "config-connection-error": "$1.\n\nComprobe o servidor, nome de usuario e contrasinal que hai a continuación e inténteo de novo.",
+ "config-invalid-schema": "O esquema de MediaWiki, \"$1\", é incorrecto.\nSó pode conter letras ASCII (a-z, A-Z), números (0-9) e guións baixos (_).",
+ "config-db-sys-create-oracle": "O programa de instalación soamente soporta o emprego de contas SYSDBA como método para crear unha nova conta.",
+ "config-db-sys-user-exists-oracle": "A conta de usuario \"$1\" xa existe. SYSDBA soamente se pode empregar para a creación dunha nova conta!",
+ "config-postgres-old": "Necesítase PostgreSQL $1 ou posterior. Vostede ten a versión $2.",
+ "config-mssql-old": "Necesítase Microsoft SQL Server $1 ou posterior. Vostede ten a versión $2.",
+ "config-sqlite-name-help": "Escolla un nome que identifique o seu wiki.\nNon utilice espazos ou guións.\nEste nome será utilizado para o ficheiro de datos SQLite.",
+ "config-sqlite-parent-unwritable-group": "Non se puido crear o directorio de datos <code><nowiki>$1</nowiki></code>, porque o servidor web non pode escribir no directorio pai <code><nowiki>$2</nowiki></code>.\n\nO programa de instalación determinou o usuario que executa o seu servidor web.\nPara continuar, faga que se poida escribir no directorio <code><nowiki>$3</nowiki></code>.\nNun sistema Unix/Linux cómpre realizar:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Non se puido crear o directorio de datos <code><nowiki>$1</nowiki></code>, porque o servidor web non pode escribir no directorio pai <code><nowiki>$2</nowiki></code>.\n\nO programa de instalación non puido determinar o usuario que executa o seu servidor web.\nPara continuar, faga que se poida escribir globalmente no directorio <code><nowiki>$3</nowiki></code>.\nNun sistema Unix/Linux cómpre realizar:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Erro ao crear o directorio de datos \"$1\".\nComprobe a localización e inténteo de novo.",
+ "config-sqlite-dir-unwritable": "Non se puido escribir o directorio \"$1\".\nCambie os permisos para que o servidor poida escribir nel e inténteo de novo.",
+ "config-sqlite-connection-error": "$1.\n\nComprobe o directorio de datos e o nome da base de datos que hai a continuación e inténteo de novo.",
+ "config-sqlite-readonly": "Non se pode escribir no ficheiro <code>$1</code>.",
+ "config-sqlite-cant-create-db": "Non se puido crear o ficheiro da base de datos <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "Falta o soporte FTS3 para o PHP; diminuíndo as táboas",
+ "config-can-upgrade": "Existen táboas MediaWiki nesta base de datos.\nPara actualizalas a MediaWiki $1, prema sobre \"<strong>Continuar</strong>\".",
+ "config-upgrade-done": "Actualización completada.\n\nAgora pode [$1 comezar a utilizar o seu wiki].\n\nSe quere rexenerar o seu ficheiro <code>LocalSettings.php</code>, prema no botón que aparece a continuación.\nIsto <strong>non é recomendable</strong> a menos que estea a ter problemas co seu wiki.",
+ "config-upgrade-done-no-regenerate": "Actualización completada.\n\nXa pode [$1 comezar a usar o seu wiki].",
+ "config-regenerate": "Rexenerar LocalSettings.php →",
+ "config-show-table-status": "A pescuda <code>SHOW TABLE STATUS</code> fallou!",
+ "config-unknown-collation": "<strong>Atención:</strong> A base de datos está a empregar unha clasificación alfabética irrecoñecible.",
+ "config-db-web-account": "Conta na base de datos para o acceso á internet",
+ "config-db-web-help": "Seleccione o nome de usuario e contrasinal que o servidor web empregará para se conectar ao servidor da base de datos durante o funcionamento normal do wiki.",
+ "config-db-web-account-same": "Empregar a mesma conta que para a instalación",
+ "config-db-web-create": "Crear a conta se aínda non existe",
+ "config-db-web-no-create-privs": "A conta que especificou para a instalación non ten os privilexios suficientes para crear unha conta.\nA conta que se especifique aquí xa debe existir.",
+ "config-mysql-engine": "Motor de almacenamento:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>Atención:</strong> Seleccionou MyISAM como o motor de almacenamento para MySQL, unha combinación non recomendada para MediaWiki, porque:\n* practicamente non soporta os accesos simultáneos debido ao bloqueo de táboas\n* é máis propenso a corromperse ca outros motores\n* o código base de MediaWiki non sempre manexa o MyISAM como debera\n\nSe a súa instalación MySQL soporta InnoDB, recoméndase elixilo no canto de MyISAM.\nSe a súa instalación MySQL non soporta InnoDB, quizais sexa boa idea realizar unha actualización.",
+ "config-mysql-only-myisam-dep": "<strong>Atención:</strong> MyISAM é o único motor de almacenamento para MySQL nesta máquina, unha combinación non recomendada para MediaWiki, porque:\n* practicamente non soporta os accesos simultáneos debido ao bloqueo de táboas\n* é máis propenso a corromperse ca outros motores\n* o código base de MediaWiki non sempre manexa o MyISAM como debera\n\nA súa instalación MySQL non soporta InnoDB, quizais sexa boa idea realizar unha actualización.",
+ "config-mysql-engine-help": "<strong>InnoDB</strong> é case sempre a mellor opción, dado que soporta ben os accesos simultáneos.\n\n<strong>MyISAM</strong> é máis rápido en instalacións de usuario único e de só lectura.\nAs bases de datos MyISAM tenden a se corromper máis a miúdo ca as bases de datos InnoDB.",
+ "config-mysql-charset": "Conxunto de caracteres da base de datos:",
+ "config-mysql-binary": "Binario",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "No <strong>modo binario</strong>, MediaWiki almacena texto UTF-8 na base de datos en campos binarios.\nIsto é máis eficaz ca o modo UTF-8 de MySQL e permítelle usar o rango completo de caracteres Unicode.\n\nNo <strong>modo UTF-8</strong>, MySQL saberá o xogo de caracteres dos seus datos e pode presentar e converter os datos de maneira axeitada,\npero non lle deixará gardar caracteres por riba do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
+ "config-mssql-auth": "Tipo de autenticación:",
+ "config-mssql-install-auth": "Seleccione o tipo de autenticación que se utilizará para conectarse á base de datos durante o proceso de instalación.\nSe selecciona \"{{int:config-mssql-windowsauth}}\", usaranse as credenciais do usuario co que se está a executar o servidor web.",
+ "config-mssql-web-auth": "Seleccione o tipo de autenticación que utilizará o servidor web para conectarse ao servidor da base de datos durante o funcionamiento normal do wiki.\nSe selecciona \"{{int:config-mssql-windowsauth}}\", usaranse as credenciais do usuario co que se está a executar o servidor web.",
+ "config-mssql-sqlauth": "Autenticación de SQL Server",
+ "config-mssql-windowsauth": "Autenticación de Windows",
+ "config-site-name": "Nome do wiki:",
+ "config-site-name-help": "Isto aparecerá na barra de títulos do navegador e noutros lugares.",
+ "config-site-name-blank": "Escriba o nome do sitio.",
+ "config-project-namespace": "Espazo de nomes do proxecto:",
+ "config-ns-generic": "Proxecto",
+ "config-ns-site-name": "O mesmo nome que o wiki: $1",
+ "config-ns-other": "Outro (especificar)",
+ "config-ns-other-default": "OMeuWiki",
+ "config-project-namespace-help": "Seguindo o exemplo da Wikipedia, moitos wikis manteñen as súas páxinas de políticas separadas das súas páxinas de contido, nun \"'''espazo de nomes do proxecto'''\".\nTodos os títulos presentes neste espazo de nomes comezan cun prefixo determinado, que pode especificar aquí.\nNormalmente, este prefixo deriva do nome do wiki, pero non pode conter caracteres de puntuación como \"#\" ou \":\".",
+ "config-ns-invalid": "O espazo de nomes especificado, \"<nowiki>$1</nowiki>\", é incorrecto.\nEspecifique un espazo de nomes do proxecto diferente.",
+ "config-ns-conflict": "O espazo de nomes especificado, \"<nowiki>$1</nowiki>\", entra en conflito co espazo de nomes MediaWiki por defecto.\nEspecifique un espazo de nomes do proxecto diferente.",
+ "config-admin-box": "Conta de administrador",
+ "config-admin-name": "O seu nome de usuario:",
+ "config-admin-password": "Contrasinal:",
+ "config-admin-password-confirm": "Repita o contrasinal:",
+ "config-admin-help": "Escriba o nome de usuario que queira aquí, por exemplo, \"Joe Bloggs\".\nEste é o nome que usará para acceder ao sistema do wiki.",
+ "config-admin-name-blank": "Escriba un nome de usuario para o administrador.",
+ "config-admin-name-invalid": "O nome de usuario especificado, \"<nowiki>$1</nowiki>\", é incorrecto.\nEspecifique un nome de usuario diferente.",
+ "config-admin-password-blank": "Escriba un contrasinal para a conta de administrador.",
+ "config-admin-password-mismatch": "Os contrasinais non coinciden.",
+ "config-admin-email": "Enderezo de correo electrónico:",
+ "config-admin-email-help": "Escriba aquí un enderezo de correo electrónico para que poida recibir mensaxes doutros usuarios a través do wiki, restablecer o contrasinal e ser notificado das modificacións feitas nas páxinas presentes na súa lista de vixilancia. Pode deixar este campo en branco.",
+ "config-admin-error-user": "Erro interno ao crear un administrador co nome \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Erro interno ao establecer un contrasinal para o administrador \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Escribiu un enderezo de correo electrónico non válido.",
+ "config-subscribe": "Subscríbase á [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de correo de anuncios sobre lanzamentos].",
+ "config-subscribe-help": "Esta é unha lista de correos de baixo volume usada para anuncios sobre lanzamentos de novas versións, incluíndo avisos de seguridade importantes.\nDebería subscribirse a ela e actualizar a súa instalación MediaWiki cando saian as novas versións.",
+ "config-subscribe-noemail": "Intentou subscribirse á lista de correo dos anuncios de novos lanzamentos sen proporcionar o enderezo de correo electrónico.\nDea un enderezo de correo electrónico se quere efectuar a subscrición á lista de correo.",
+ "config-almost-done": "Xa case rematou!\nNeste paso pode saltar o resto da configuración e instalar o wiki agora mesmo.",
+ "config-optional-continue": "Facédeme máis preguntas.",
+ "config-optional-skip": "Xa estou canso. Instalade o wiki.",
+ "config-profile": "Perfil dos dereitos de usuario:",
+ "config-profile-wiki": "Wiki aberto",
+ "config-profile-no-anon": "Necesítase a creación dunha conta",
+ "config-profile-fishbowl": "Só os editores autorizados",
+ "config-profile-private": "Wiki privado",
+ "config-profile-help": "Os wikis funcionan mellor canta máis xente os edite.\nEn MediaWiki, é doado revisar os cambios recentes e reverter calquera dano feito por usuarios novatos ou con malas intencións.\nPorén, moita xente atopa MediaWiki útil nunha ampla variedade de papeis, e ás veces non é fácil convencer a todos dos beneficios que leva consigo o estilo wiki.\nVostede decide.\n\nO modelo <strong>{{int:config-profile-wiki}}</strong> permite a edición por parte de calquera, mesmo sen rexistro.\nA opción <strong>{{int:config-profile-no-anon}}</strong> proporciona un control maior, pero pode desalentar os colaboradores casuais.\n\nO escenario <strong>{{int:config-profile-fishbowl}}</strong> restrinxe a edición aos usuarios aprobados, pero o público pode ollar as páxinas, incluíndo os historiais.\nO tipo <strong>{{int:config-profile-private}}</strong> só deixa que os usuarios aprobados vexan e editen as páxinas.\n\nHai dispoñibles configuracións de dereitos de usuario máis complexas despois da instalación; bótelle un ollo a [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights esta entrada no manual].",
+ "config-license": "Dereitos de autoría e licenza:",
+ "config-license-none": "Sen licenza ao pé",
+ "config-license-cc-by-sa": "Creative Commons recoñecemento compartir igual",
+ "config-license-cc-by": "Creative Commons recoñecemento",
+ "config-license-cc-by-nc-sa": "Creative Commons recoñecemento non comercial compartir igual",
+ "config-license-cc-0": "Creative Commons Zero (dominio público)",
+ "config-license-gfdl": "Licenza de documentación libre de GNU 1.3 ou posterior",
+ "config-license-pd": "Dominio público",
+ "config-license-cc-choose": "Seleccione unha licenza Creative Commons personalizada",
+ "config-license-help": "Moitos wikis públicos liberan todas as súas contribucións baixo unha [http://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 [http://www.php.net/manual/en/mail.configuration.php parámetros PHP] correctamente.\nSe non quere ningunha característica no correo, 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",
+ "config-email-usertalk-help": "Permitir que os usuarios reciban notificacións cando a súa páxina de conversa de usuario sufra modificacións, se o teñen activado nas súas preferencias.",
+ "config-email-watchlist": "Activar a notificación da lista de vixilancia",
+ "config-email-watchlist-help": "Permitir que os usuarios reciban notificacións sobre modificacións nas páxinas que vixían, se o teñen activado nas súas preferencias.",
+ "config-email-auth": "Activar a autenticación do correo electrónico",
+ "config-email-auth-help": "Se esta opción está activada, os usuarios teñen que confirmar o seu correo electrónico mediante unha ligazón enviada ao enderezo cando o definan ou o cambien.\nSó os enderezos autenticados poden recibir correos doutros usuarios ou de notificación.\nÉ <strong>recomendable</strong> establecer esta opción nos wikis públicos para evitar abusos potenciais das características do correo.",
+ "config-email-sender": "Enderezo de correo electrónico de retorno:",
+ "config-email-sender-help": "Introduza o enderezo de correo electrónico a usar como enderezo de retorno dos correos de saída.\nAquí é onde irán parar os correos rexeitados.\nMoitos servidores de correo electrónico esixen que polo menos a parte do nome de dominio sexa válido.",
+ "config-upload-settings": "Imaxes e carga de ficheiros",
+ "config-upload-enable": "Activar a carga de ficheiros",
+ "config-upload-help": "A subida de ficheiros expón potencialmente o servidor a riscos de seguridade.\nPara obter máis información, lea a [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sección de seguridade] no manual.\n\nPara activar a carga de ficheiros, cambie o modo no subdirectorio <code>images</code> que está baixo o directorio raíz de MediaWiki, de xeito que o servidor web poida escribir nel.\nA continuación, active esta opción.",
+ "config-upload-deleted": "Directorio para os ficheiros borrados:",
+ "config-upload-deleted-help": "Escolla un directorio no que arquivar os ficheiros borrados.\nO ideal é que non sexa accesible desde a web.",
+ "config-logo": "URL do logo:",
+ "config-logo-help": "A aparencia de MediaWiki por defecto inclúe espazo para un logo de 135x160 píxeles por riba do menú lateral.\nCargue unha imaxe do tamaño axeitado e introduza o enderezo URL aquí.\n\nPode utilizar <code>$wgStylePath</code> ou <code>$wgScriptPath</code> se o seu logo está relacionado con esas rutas.\n\nSe non quere un logo, deixe esta caixa en branco.",
+ "config-instantcommons": "Activar Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons InstantCommons] é unha característica que permite aos wikis usar imaxes, sons e outros ficheiros multimedia atopados no sitio da [//commons.wikimedia.org/wiki/Portada_galega Wikimedia Commons].\nPara facer isto, MediaWiki necesita acceso á internet.\n\nPara obter máis información sobre esta característica, incluíndo as instrucións sobre como configuralo para outros wikis que non sexan a Wikimedia Commons, consulte [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos o manual].",
+ "config-cc-error": "A escolla da licenza Creative Commons non deu resultados.\nEscriba o nome da licenza manualmente.",
+ "config-cc-again": "Escolla outra vez...",
+ "config-cc-not-chosen": "Escolla a licenza Creative Commons que desexe e prema en \"continuar\".",
+ "config-advanced-settings": "Configuración avanzada",
+ "config-cache-options": "Configuración da caché de obxectos:",
+ "config-cache-help": "A caché de obxectos emprégase para mellorar a velocidade de MediaWiki mediante a memorización de datos usados con frecuencia.\nÉ amplamente recomendable a súa activación nos sitios de tamaño medio e grande; os sitios pequenos obterán tamén beneficios.",
+ "config-cache-none": "Sen caché (non se elimina ningunha funcionalidade, pero pode afectar á velocidade en wikis grandes)",
+ "config-cache-accel": "Caché de obxectos do PHP (APC, XCache ou WinCache)",
+ "config-cache-memcached": "Empregar o Memcached (necesita unha instalación e configuración adicional)",
+ "config-memcached-servers": "Servidores da memoria caché:",
+ "config-memcached-help": "Lista de enderezos IP para Memcached.\nDebe especificarse un por liña, así como o porto a usar. Por exemplo:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Seleccionou Memcached como o seu tipo de caché, pero non especificou ningún servidor.",
+ "config-memcache-badip": "Escribiu un enderezo IP inválido para Memcached: $1.",
+ "config-memcache-noport": "Non especificou o porto a usar no servidor Memcached: $1.\nSe non sabe o porto, o predeterminado é 11211.",
+ "config-memcache-badport": "Os números de porto Memcached deben estar entre $1 e $2.",
+ "config-extensions": "Extensións",
+ "config-extensions-help": "As extensións anteriores detectáronse no seu directorio <code>./extensions</code>.\n\nQuizais necesite algunha configuración adicional, pero pode activalas agora",
+ "config-skins": "Aparencias",
+ "config-skins-help": "As aparencias listadas enriba detectáronse no seu directorio <code>./skins</code>. Debe activar, polo menos, unha e elixir a predeterminada.",
+ "config-skins-use-as-default": "Utilizar esta aparencia por defecto",
+ "config-skins-missing": "Non se atopou aparencia ningunha. MediaWiki ha utilizar unha aparencia de respaldo ata que vostede instale algunha aparencia axeitada.",
+ "config-skins-must-enable-some": "Debe elixir, polo menos, unha aparencia para activala.",
+ "config-skins-must-enable-default": "A aparencia elixida como predeterminada debe estar activada.",
+ "config-install-alreadydone": "<strong>Atención:</strong> Semella que xa instalou MediaWiki e que o está a instalar de novo.\nVaia ata a seguinte páxina.",
+ "config-install-begin": "Ao premer en \"{{int:config-continue}}\", comezará a instalación de MediaWiki.\nSe aínda quere facer algún cambio, prema en \"{{int:config-back}}\".",
+ "config-install-step-done": "feito",
+ "config-install-step-failed": "erro",
+ "config-install-extensions": "Incluíndo as extensións",
+ "config-install-database": "Configurando a base de datos",
+ "config-install-schema": "Creando o esquema",
+ "config-install-pg-schema-not-exist": "O esquema PostgreSQL non existe.",
+ "config-install-pg-schema-failed": "Fallou a creación de táboas.\nAsegúrese de que o usuario \"$1\" pode escribir no esquema \"$2\".",
+ "config-install-pg-commit": "Validando os cambios",
+ "config-install-pg-plpgsql": "Comprobación da lingua PL/pgSQL",
+ "config-pg-no-plpgsql": "Cómpre instalar a lingua PL/pgSQL na base de datos $1",
+ "config-pg-no-create-privs": "A conta especificada para a instalación non ten os privilexios necesarios para crear unha conta.",
+ "config-pg-not-in-role": "A conta especificada para o usuario web xa existe.\nA conta que especificou para a instalación non é un superusuario e non pertence ao grupo de usuarios con acceso á web, polo que non pode crear obxectos pertencentes ao usuario da rede.\n\nActualmente, MediaWiki necesita que as táboas sexan propiedade do usuario da rede. Especifique outro nome de conta web ou prema no botón \"Atrás\" e dea un usuario de instalación cos privilexios axeitados.",
+ "config-install-user": "Creando o usuario da base de datos",
+ "config-install-user-alreadyexists": "O usuario \"$1\" xa existe",
+ "config-install-user-create-failed": "A creación do usuario \"$1\" fallou: $2",
+ "config-install-user-grant-failed": "Fallou a concesión de permisos ao usuario \"$1\": $2",
+ "config-install-user-missing": "O usuario especificado, \"$1\", non existe.",
+ "config-install-user-missing-create": "O usuario especificado, \"$1\", non existe.\nPrema na caixa de verificación \"crear unha conta\" que hai a continuación se quere crear unha.",
+ "config-install-tables": "Creando as táboas",
+ "config-install-tables-exist": "<strong>Atención:</strong> Semella que as táboas de MediaWiki xa existen.\nSaltando a creación.",
+ "config-install-tables-failed": "<strong>Erro:</strong> Fallou a creación da táboa. Descrición do erro: $1",
+ "config-install-interwiki": "Enchendo a táboa de interwiki por defecto",
+ "config-install-interwiki-list": "Non se puido atopar o ficheiro <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "<strong>Atención:</strong> Semella que a táboa de interwiki xa contén entradas.\nSaltando a lista por defecto.",
+ "config-install-stats": "Iniciando as estatísticas",
+ "config-install-keys": "Xerando as claves secretas",
+ "config-insecure-keys": "<strong>Atención:</strong> {{PLURAL:$2|A clave de seguridade|As claves de seguridade}} ($1) {{PLURAL:$2|xerada|xeradas}} durante a instalación non {{PLURAL:$2|é|son}} completamente {{PLURAL:$2|segura|seguras}}. Considere a posibilidade de {{PLURAL:$2|cambiala|cambialas}} manualmente.",
+ "config-install-updates": "Evitar executar actualizacións innecesarias",
+ "config-install-updates-failed": "<strong>Error:</strong> a inserción de claves de actualización nas táboas fallou co seguinte erro: $1",
+ "config-install-sysop": "Creando a conta de usuario de administrador",
+ "config-install-subscribe-fail": "Non se puido subscribir á lista mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL non está instalado e <code>allow_url_fopen</code> non está dispoñible.",
+ "config-install-mainpage": "Creando a páxina principal co contido por defecto",
+ "config-install-extension-tables": "Creando as táboas para as extensións activadas",
+ "config-install-mainpage-failed": "Non se puido inserir a páxina principal: $1",
+ "config-install-done": "<strong>Parabéns!</strong>\nInstalou correctamente MediaWiki.\n\nO programa de instalación xerou un ficheiro <code>LocalSettings.php</code>.\nEste ficheiro contén toda a súa configuración.\n\nTerá que descargalo e poñelo na base da instalación do seu wiki (no mesmo directorio ca index.php). A descarga debería comezar automaticamente.\n\nSe non comezou a descarga ou se a cancelou, pode facer que comece de novo premendo na ligazón que aparece a continuación:\n\n$3\n\n<strong>Nota:</strong> Se non fai iso agora, este ficheiro de configuración xerado non estará dispoñible máis adiante se sae da instalación sen descargalo.\n\nCando faga todo isto, xa poderá <strong>[$2 entrar no seu wiki]</strong>.",
+ "config-download-localsettings": "Descargar o <code>LocalSettings.php</code>",
+ "config-help": "axuda",
+ "config-help-tooltip": "prema para expandir",
+ "config-nofile": "Non se puido atopar o ficheiro \"$1\". Se cadra, foi borrado.",
+ "config-extension-link": "Sabía que o seu wiki soporta [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensións]?\n\nPode explorar as [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensións por categoría] ou a [//www.mediawiki.org/wiki/Extension_Matrix matriz de extensións] para ollar a lista completa de extensións.",
+ "mainpagetext": "<strong>MediaWiki instalouse correctamente.</strong>",
+ "mainpagedocfooter": "Consulte a [//meta.wikimedia.org/wiki/Help:Contents guía de usuario] para obter máis información sobre como usar o software wiki.\n\n== Primeiros pasos ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista das opcións de configuración]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Preguntas máis frecuentes sobre MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo dos lanzamentos de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localice MediaWiki á súa lingua]"
+}
diff --git a/includes/installer/i18n/gom-latn.json b/includes/installer/i18n/gom-latn.json
new file mode 100644
index 00000000..eb1395cb
--- /dev/null
+++ b/includes/installer/i18n/gom-latn.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "The Discoverer"
+ ]
+ },
+ "config-page-language": "Bhas",
+ "mainpagedocfooter": "Wiki software uzar korpache mahiti khatir [//meta.wikimedia.org/wiki/Help:Contents Vapurpeanchi Hath-pustok] polloi\n\n== Suru kortana ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuracaoanchi suchi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki babtint zaite pavtti vicharlele proxn]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki che novem ank bhair sorta tedna email dhadpachi suchi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki tujea bhasen toiar kor]"
+}
diff --git a/includes/installer/i18n/grc.json b/includes/installer/i18n/grc.json
new file mode 100644
index 00000000..44a894b4
--- /dev/null
+++ b/includes/installer/i18n/grc.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Crazymadlover",
+ "Omnipaedista"
+ ]
+ },
+ "config-page-language": "Γλῶττα",
+ "mainpagetext": "'''Ἡ ἐγκατάστασις τῆς MediaWiki ἦν ἐπιτυχής'''",
+ "mainpagedocfooter": "Βουλευθήσεσθε τὰς [//meta.wikimedia.org/wiki/Help:Contents βουλὰς τοῖς Χρωμένοις] ἵνα πληροφορηθῇτε περὶ τοῦ βίκιλογισμικοῦ.\n\n== Ἄρξασθε ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Διαλογή παραμέτρων διαμορφώσεως]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki: τὰ πολλάκις αἰτηθέντα]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Διαλογή διαλέξεων ἐπὶ τῶν διανομῶν τῆς MediaWiki]"
+}
diff --git a/includes/installer/i18n/gsw.json b/includes/installer/i18n/gsw.json
new file mode 100644
index 00000000..f653ca28
--- /dev/null
+++ b/includes/installer/i18n/gsw.json
@@ -0,0 +1,68 @@
+{
+ "@metadata": {
+ "authors": [
+ "Als-Holder"
+ ]
+ },
+ "config-desc": "S MediaWiki-Inschtallationsprogramm",
+ "config-title": "MediaWiki $1 inschtalliere",
+ "config-information": "Information",
+ "config-localsettings-upgrade": "'''Warnig:''' E Datei <code>LocalSettings.php</code> isch gfunde wore.\nFir d Aktualisierig vu dr däre Inschtallation, gib bitte dr Wärt vum Parameter <code>$wgUpgradeKey</code> im Fäld unten yy.\nDu findsch dr Wärt in dr Datei <code>LocalSettings.php</code>.",
+ "config-localsettings-key": "Aktualisierigsschlissel:",
+ "config-localsettings-badkey": "Dr Aktualisierigsschlissel, wu du aagee hesch, isch falsch.",
+ "config-session-error": "Fähler bim Starte vu dr Sitzig: $1",
+ "config-session-expired": "D Sitzigsdate sin schyns abgloffe.\nSitzige sin fir e Zytruum vu $1 konfiguriert.\nDää cha dur Aalupfe vum Parameter <code>session.gc_maxlifetime</code> in dr Datei <code>php.ini</code> greßer gmacht wäre.\nDr Inschtallationsvorgang nomol starte.",
+ "config-no-session": "Dyyni Sitzigsdate sin verlore gange!\nD Datei <code>php.ini</code> mueß prieft wäre un s mueß derby sichergstellt wäre, ass dr Parameter <code>session.save_path</code> uf s richtig Verzeichnis verwyyst.",
+ "config-your-language": "Dyy Sproch:",
+ "config-your-language-help": "Bitte d Sproch uuswehle, wu bim Inschtallationsvorgang soll brucht wäre.",
+ "config-wiki-language": "Wikisproch:",
+ "config-wiki-language-help": "Bitte d Sproch uuswehle, wu s Wiki in dr Hauptsach din gschribe wird.",
+ "config-back": "← Zruck",
+ "config-continue": "Wyter →",
+ "config-page-language": "Sproch",
+ "config-page-welcome": "Willchuu bi MediaWiki!",
+ "config-page-dbconnect": "Mit dr Datebank verbinde",
+ "config-page-upgrade": "E Inschtallition, wu s scho het, aktualisiere",
+ "config-page-dbsettings": "Datebankyystellige",
+ "config-page-name": "Name",
+ "config-page-options": "Optione",
+ "config-page-install": "Inschtalliere",
+ "config-page-complete": "Fertig!",
+ "config-page-restart": "Inschtallation nomol aafange",
+ "config-page-readme": "Liis mi",
+ "config-page-releasenotes": "Hiiwys fir d Vereffentlichung",
+ "config-page-copying": "Am Kopiere",
+ "config-page-upgradedoc": "Am Aktualisiere",
+ "config-help-restart": "Witt alli Date, wu Du yygee hesch, lesche un d Inschtallation nomol aafange?",
+ "config-restart": "Jo, nomol aafange",
+ "config-welcome": "=== Priefig vu dr Inschtallationsumgäbig ===\nBasispriefige wäre durgfiert zum Feschtstelle, eb d Inschtallationsumgäbig fir d Inschtallation vu MediaWiki geignet isch.\nDu sottsch d Ergebnis vu däre Priefig aagee, wänn Du bi dr Inschtallation Hilf bruchsch.",
+ "config-copyright": "=== Copyright un Nutzigsbedingige ===\n\n$1\n\nDes Programm isch e freji Software, d. h. s cha, no dr Bedingige vu dr GNU General Public-Lizänz, wu vu dr Free Software Foundation vereffentligt woren isch, wyterverteilt un/oder modifiziert wäre. Doderbyy cha d Version 2, oder no eigenem Ermässe, jedi nejeri Version vu dr Lizänz brucht wäre.\n\nDes Programm wird in dr Hoffnig verteilt, ass es nitzli isch, aber '''ohni jedi Garanti''' un sogar ohni di impliziert Garanti vun ere '''Märtgängigkeit''' oder '''Eignig fir e bstimmte Zwäck'''. Doderzue git meh Hiiwys in dr GNU General Public-Lizänz.\n\nE <doclink href=Copying>Kopi vu dr GNU General Public-Lizänz</doclink> sott zämme mit däm Programm verteilt wore syy. Wänn des nit eso isch, cha ne Kopi bi dr Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftli aagforderet oder [http://www.gnu.org/copyleft/gpl.html online gläse] wäre.",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki Websyte vu MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Nutzeraaleitig zue MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Adminischtratoreaaleitig zue MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Vilmol gstellti Froge zue MediaWiki]",
+ "config-env-good": "D Inschtallationsumgäbig isch prieft wore.\nDu chasch MediaWiki inschtalliere.",
+ "config-env-bad": "D Inschtallationsumgäbigisch prieft wore.\nDu chasch MediaWiki nit inschtalliere.",
+ "config-env-php": "PHP $1 isch inschtalliert.",
+ "config-unicode-using-utf8": "Fir d Unicode-Normalisierig wird em Brion Vibber syy utf8_normalize.so yygsetzt.",
+ "config-unicode-using-intl": "For d Unicode-Normalisierig wird d [http://pecl.php.net/intl PECL-Erwyterig intl] yygsetzt.",
+ "config-unicode-pure-php-warning": "'''Warnig:''' D [http://pecl.php.net/intl PECL-Erwyterig intl] isch fir d Unicode-Normalisierig nit verfiegbar. Wäge däm wird di langsam pure-PHP-Implementierig brucht.\nWänn Du ne Websyte mit ere große Bsuechrzahl bedrybsch, sottsch e weng ebis läse iber [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-Normalisierig (en)].",
+ "config-unicode-update-warning": "'''Warnig:''' Di inschtalliert Version vum Unicode-Normalisierigswrapper verwändet e elteri Version vu dr Bibliothek vum [http://site.icu-project.org/ ICU-Projäkt].\nDu sottsch si [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations aktualisiere], wänn Dor d Verwändig vu Unicode wichtig isch.",
+ "config-no-db": "S isch kei adäquate Datebanktryyber gfunde wore!",
+ "config-no-fts3": "'''Warnig:''' SQLite isch ohni s [//sqlite.org/fts3.html FTS3-Modul] kumpiliert wore, s stehn kei Suechfunktione z Verfiegig.",
+ "config-register-globals": "'''Warnig: Dr Parameter <code>[http://php.net/register_globals register_globals]</code> vu PHP isch aktiviert.'''\n'''Är sott deaktiviert wäre, wänn des megli isch.'''\nD MediaWiki-Inschtallation lauft einwäg, aber dr Server isch aafällig fi megligi Sicherheitsprobläm.",
+ "config-magic-quotes-runtime": "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]</code> vu PHP isch aktiviert!'''\nDie Yystellig fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.\nMediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
+ "config-magic-quotes-sybase": "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> vu PHP isch aktiviert!'''\nDie Yystellig fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.\nMediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
+ "config-mbstring": "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> vu PHP isch aktiviert!'''\nDie Yystellig verursacht Fähler un fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.\nMediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
+ "config-safe-mode": "'''Warnig:''' D Funktion <code>[http://www.php.net/features.safe-mode Safe Mode]</code> vu PHP isch aktiviert.\nDes cha zue Probläm fiere, vor allem wänn s Uffelade vu Dateie soll megli syy bzw. dr Uuszeichner <code>math</code> soll brucht wäre.",
+ "config-xml-bad": "S XML-Modul vu PHP fählt.\nMediaWiki brucht Funktione, wu au des Modul z Verfiegig stellt, un funktioniert in däre Konfiguration nit.\nWänn Mandriva brucht wird, mueß no s „php-xml“-Paket inschtalliert wäre.",
+ "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-xcache": "[http://xcache.lighttpd.net/ XCache] isch inschtalliert",
+ "config-apc": "[http://www.php.net/apc APC] isch inschtalliert",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] isch inschtalliert",
+ "config-no-cache": "'''Warnig:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] hän nit chenne gfunde wäre.\nS Objäktcaching isch wäge däm nit aktiviert.",
+ "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.",
+ "config-help": "Hilf",
+ "mainpagetext": "'''MediaWiki isch erfolgrich inschtalliert worre.'''",
+ "mainpagedocfooter": "Lueg uf d [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentation fir d Aapassig vu dr Benutzeroberflächi] un s [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuech] fir d Hilf iber d Benutzig un s Yystelle."
+}
diff --git a/includes/installer/i18n/gu.json b/includes/installer/i18n/gu.json
new file mode 100644
index 00000000..83a241ca
--- /dev/null
+++ b/includes/installer/i18n/gu.json
@@ -0,0 +1,45 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ashok modhvadia",
+ "Dineshjk",
+ "KartikMistry"
+ ]
+ },
+ "config-desc": "મીડિઆવિકિ માટે સ્થાપક",
+ "config-title": "મીડિઆવિકિ $1 સ્થાપન",
+ "config-information": "માહિતી",
+ "config-localsettings-badkey": "તમે આપેલી કળ ખોટી છે.",
+ "config-your-language": "તમારી ભાષા:",
+ "config-wiki-language": "વિકિ ભાષા:",
+ "config-back": "← પાછળ",
+ "config-continue": "ચાલુ રાખો →",
+ "config-page-language": "ભાષા",
+ "config-page-welcome": "મિડિઆવિકિમાં તમારું સ્વાગત છે!",
+ "config-page-dbsettings": "ડેટાબેઝ ગોઠવણીઓ",
+ "config-page-name": "નામ",
+ "config-page-options": "વિકલ્પો",
+ "config-page-install": "સ્થાપિત કરો",
+ "config-page-complete": "સમાપ્ત!",
+ "config-page-restart": "સ્થાપન ફરી શરુ કરો",
+ "config-page-readme": "મને વાંચો",
+ "config-db-type": "ડેટાબેઝ પ્રકાર:",
+ "config-db-host": "ડેટાબેઝ હોસ્ટ:",
+ "config-db-name": "ડેટાબેઝ નામ:",
+ "config-db-name-oracle": "ડેટાબેઝ સ્કિમા:",
+ "config-db-username": "ડેટાબેઝ સભ્યનામ:",
+ "config-db-password": "ડેટાબેઝ પાસવર્ડ:",
+ "config-db-port": "ડેટાબેઝ પોર્ટ:",
+ "config-site-name": "વિકિનું નામ:",
+ "config-admin-name": "તમારું સભ્યનામ:",
+ "config-admin-password": "પાસવર્ડ:",
+ "config-admin-password-confirm": "પાસવર્ડ ફરીથી:",
+ "config-admin-email": "ઇમેલ સરનામું:",
+ "config-profile-private": "અંગત વિકિ",
+ "config-email-settings": "ઇમેલ ગોઠવણીઓ",
+ "config-install-step-done": "પૂર્ણ",
+ "config-install-step-failed": "નિષ્ફળ",
+ "config-help": "મદદ",
+ "mainpagetext": "'''મિડીયાવિકિ સફળતાપૂર્વક ઇન્સટોલ થયું છે.'''",
+ "mainpagedocfooter": "વિકિ સોફ્ટવેર વાપરવાની માહીતિ માટે [//meta.wikimedia.org/wiki/Help:Contents સભ્ય માર્ગદર્શિકા] જુઓ.\n\n== શરૂઆતના તબક્કે ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings કોનફીગ્યુરેશન સેટીંગ્સની યાદી]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ વારંવાર પુછાતા પ્રશ્નો]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce મિડીયાવિકિ રીલીઝ મેઇલીંગ લીસ્ટ]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/gv.json b/includes/installer/i18n/gv.json
new file mode 100644
index 00000000..6d9b076e
--- /dev/null
+++ b/includes/installer/i18n/gv.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Ta MediaWiki currit stiagh nish.'''"
+}
diff --git a/includes/installer/i18n/hak.json b/includes/installer/i18n/hak.json
new file mode 100644
index 00000000..b211b86d
--- /dev/null
+++ b/includes/installer/i18n/hak.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Yí-kîn sṳ̀n-kûng ôn-chông MediaWiki.'''",
+ "mainpagedocfooter": "chhiáng fóng-mun [//meta.wikimedia.org/wiki/Help:Contents Yung-fu sú-chhak] yî-khi̍p sṳ́-yung chhṳ́ wiki ngiôn-khien ke sin-sit!\n\n== Ngi̍p-mùn ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki Phi-chṳ sat-thin chhîn-tân]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki Phìn-sòng mun-thì kié-tap]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki fat-phu email chhîn-tân]"
+}
diff --git a/includes/installer/i18n/haw.json b/includes/installer/i18n/haw.json
new file mode 100644
index 00000000..afa7d724
--- /dev/null
+++ b/includes/installer/i18n/haw.json
@@ -0,0 +1,64 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kolonahe"
+ ]
+ },
+ "config-desc": "Ka polokalamu hoʻonoho no MekiaWiki",
+ "config-title": "Ka hoʻonoho MekiaWiki $1",
+ "config-information": "ʻIke",
+ "config-localsettings-upgrade": "Ua hōʻike ʻia kekahi waihona <code>LocalSettings.php</code>.\nNo ka hoʻopuka hou ʻana o kēia hoʻonohona, e ʻoluʻolu, e kikokiko i ka helu o <code>$wgUpgradeKey</code> i loko o ka pahu i lalo.\nAia ia ma <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Ua hōʻike ʻia kekahi waihona <code>LocalSettings.php</code>.\nNo ka hoʻopuka hou ʻana o kēia hoʻonohona, e hana iā <code>update.php</code> ke ʻoluʻolu.",
+ "config-localsettings-key": "Pihi hoʻopuka hou:",
+ "config-localsettings-badkey": "Hewa ka pihi.",
+ "config-upgrade-key-missing": "Loaʻa he hoʻonohona mai mua o MīkiaWiki mai mua.\nNo ka hoʻopuka hou ʻana o kēia hoʻonohona, e ʻoluʻolu, e kau i kēia laina lalo ma lalo o kāu <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "ʻAʻole piha pono kēia <code>LocalSettings.php</code> nei.\nʻAʻole hoʻopaʻa ʻia ka hualau $1.\nE hoʻololi iā <code>LocalSettings.php</code> i hiki ke hoʻopaʻa i ka hualau, a laila e kaomi iā \"{{int:Config-continue}}\" ke ʻoluʻolu.",
+ "config-localsettings-connection-error": "Ua loaʻa i ke kuʻia i ka hoʻokuʻi ʻana i ke hōkeo ʻikepili ma muli o ka hana ʻana o ka makemake e kākau ʻia ma <code>LocalSettings.php</code>. E ʻoluʻolu e hoʻoponopono i kēia makemake a hana hou.\n\n$1",
+ "config-session-error": "Kuʻia ka wā hoʻohana hoʻomaka: $1",
+ "config-session-expired": "Ua pau kāu ʻikepili wā hoʻohana.\nOla ka wā hoʻohana no ka manawa o $1.\nHiki iā ʻoe ke hoʻonui i kēia wā ma o ka hoʻololi ʻana o <code>session.gc_maxlifetime</code> ma php.ini.\nE hana hou i ka hana hoʻoukana.",
+ "config-no-session": "Ua nalo kāu ʻikepili wā hoʻohana!\nE hōʻoia i kāu php.ini a me ka hoʻopaʻa ʻana o <code>session.save_path</code> i ke kumu kūpono.",
+ "config-your-language": "Kāu ʻōlelo:",
+ "config-your-language-help": "E koho i kekahi ʻōlelo no ka hoʻohana ʻana ma loko o ka hana hoʻonohona.",
+ "config-wiki-language": "ʻŌlelo Wiki:",
+ "config-wiki-language-help": "E koho i ka ʻōlelo e kākau pinepine ʻia i ka wiki.",
+ "config-back": "← Kele hope",
+ "config-continue": "Holomua →",
+ "config-page-language": "ʻŌlelo",
+ "config-page-welcome": "E welina mai e MikiaWiki!",
+ "config-page-dbconnect": "E hoʻokuʻi i ka hōkeo ʻikepili",
+ "config-page-upgrade": "Hoʻopuka hou i ka hoʻonohona nei",
+ "config-page-dbsettings": "Makemake hōkeo ʻikepili",
+ "config-page-name": "Inoa",
+ "config-page-options": "Nā Koho",
+ "config-page-install": "Hoʻonoho",
+ "config-page-complete": "Pau hana!",
+ "config-page-restart": "Hōʻano hou i ka hoʻonohona",
+ "config-page-readme": "Heluhelu iaʻu",
+ "config-page-releasenotes": "Noka hāʻawi",
+ "config-page-copying": "Kope",
+ "config-page-upgradedoc": "Ka Hoʻopuka ʻana",
+ "config-page-existingwiki": "Ka wiki nei",
+ "config-help-restart": "He ʻoiaʻiʻo kāu makemake no ka holoi pono o nā ʻikepili mālamalia a pau āu i kikokiko a hoʻomaka hou i ka hana hoʻouka?",
+ "config-restart": "ʻAe, e hōʻano hou",
+ "config-db-type": "ʻAno hōkeo ʻikepili:",
+ "config-db-name": "Inoa hōkeo ʻikepili",
+ "config-db-username": "Inoa hōkeo ʻikepili:",
+ "config-db-password": "ʻŌlelo hūnā hōkeo ʻikepili",
+ "config-mysql-binary": "Baineli",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Inoa wiki:",
+ "config-project-namespace": "Lewainoa papahana:",
+ "config-ns-generic": "Papahana",
+ "config-ns-other-default": "KaʻuWiki",
+ "config-admin-box": "Moʻokāki kahu",
+ "config-admin-name": "Kāu inoa mea hoʻohana:",
+ "config-admin-password": "ʻŌlelo hūnā:",
+ "config-admin-password-confirm": "Kikokiko hou i kā ʻŌlelo hūnā:",
+ "config-admin-name-blank": "E kikokiko i kekahi inoa mea hoʻohana kahu.",
+ "config-profile-private": "Wiki pilikino",
+ "config-cc-again": "Koho hou...",
+ "config-install-step-done": "pau hana",
+ "config-help": "kōkua",
+ "config-nofile": "Loaʻa ʻole ka waihona \"$1.\" Ua holoi paha ʻia?",
+ "mainpagetext": "'''Ua pono ka ho‘ouka ‘ana o MediaWiki.'''"
+}
diff --git a/includes/installer/i18n/he.json b/includes/installer/i18n/he.json
new file mode 100644
index 00000000..dd14e633
--- /dev/null
+++ b/includes/installer/i18n/he.json
@@ -0,0 +1,330 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amire80",
+ "YaronSh",
+ "ערן",
+ "아라",
+ "Inkbug",
+ "Yona b"
+ ]
+ },
+ "config-desc": "תכנית ההתקנה של מדיה־ויקי",
+ "config-title": "התקנת מדיה־ויקי $1",
+ "config-information": "מידע",
+ "config-localsettings-upgrade": "זוהה קובץ <code>LocalSettings.php</code>.\nכדי לשדרג את ההתקנה הזאת, נא להקליד את הערך של <code>$wgUpgradeKey</code> בתיבה להלן.\nאפשר למצוא אותו בקובץ <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "זוהה קובץ <code>LocalSettings.php</code>.\nכדי לשדרג את ההתקנה הזאת, יש להריץ את <code>update.php</code> ולא את התהליך הזה.",
+ "config-localsettings-key": "מפתח השדרוג:",
+ "config-localsettings-badkey": "המפתח שהקלדת שגוי.",
+ "config-upgrade-key-missing": "זוהתה התקנה קיימת של מדיה־ויקי.\nכדי לשדרג את ההתקנה הזאת, יש לכתוב את השורה הבא בתחתית קובץ <code>LocalSettings.php</code> שלך:\n\n$1",
+ "config-localsettings-incomplete": "נראה שקובץ <code>LocalSettings.php</code> הקיים אינו שלם.\nהמשתנה $1 אינו מוגדר.\nנא לשנות את הקובץ <code>LocalSettings.php</code> כך שהמשתנה הזה יהיה מוגדר וללחוץ \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "אירעה שגיאה בעת חיבור למסד נתונים עם הגדרות ב־<code>LocalSettings.php</code>. נא לתקן את ההגדרות האלו ולנסות שוב.\n\n$1",
+ "config-session-error": "שגיאה באתחול שיחה: $1",
+ "config-session-expired": "נראה שנתוני השיחה שלכם פגו.\nהשיחות מוגדרות להיות תקפות לזמן של $1.\nאפשר להגדיל את זה ב־<code>session.gc_maxlifetime</code> בקובץ php.ini.\nיש להתחיל מחדש את תהליך ההתקנה.",
+ "config-no-session": "נתוני השיחה שלכם אבדו!\nיש לבדוק את קובץ php.ini שלכם ולוודא שתיקייה נכונה מוגדרת ב־<code>session.save_path</code>.",
+ "config-your-language": "השפה שלך:",
+ "config-your-language-help": "נא לבחור את השפה שתשמש במהלך ההתקנה.",
+ "config-wiki-language": "שפת הוויקי:",
+ "config-wiki-language-help": "נא לבחור את השפה העיקרית שבה ייכתב ויקי זה.",
+ "config-back": "→ חזרה",
+ "config-continue": "המשך ←",
+ "config-page-language": "שפה",
+ "config-page-welcome": "ברוכים הבאים למדיה־ויקי!",
+ "config-page-dbconnect": "התחברות למסד הנתונים",
+ "config-page-upgrade": "שדרוג התקנה קיימת",
+ "config-page-dbsettings": "הגדרות מסד הנתונים",
+ "config-page-name": "שם",
+ "config-page-options": "אפשרויות",
+ "config-page-install": "התקנה",
+ "config-page-complete": "הושלמה!",
+ "config-page-restart": "הפעלת ההתקנה מחדש",
+ "config-page-readme": "קרא־אותי",
+ "config-page-releasenotes": "הערות גרסה",
+ "config-page-copying": "העתקה",
+ "config-page-upgradedoc": "שדרוג",
+ "config-page-existingwiki": "ויקי קיים",
+ "config-help-restart": "האם ברצונך לנקות את כל הנתונים שהזנת ולהתחיל מחדש את תהליך ההתקנה?",
+ "config-restart": "כן, להפעיל מחדש",
+ "config-welcome": "=== בדיקות סביבה ===\nבדיקות בסיסיות תתבצענה עכשיו כדי לראות אם הסביבה הזאת מתאימה להתקנת מדיה־ויקי.\nנא לזכור לכלול את המידע הזה בעת בקשת תמיכה עם השלמת ההתקנה.",
+ "config-copyright": "=== זכויות יוצרים ותנאים ===\n\n$1\n\nתכנית זו היא תכנה חופשית; באפשרותך להפיצה מחדש ו/או לשנות אותה על פי תנאי הרישיון הציבורי הכללי של GNU כפי שפורסם על ידי קרן התכנה החופשית; בין אם גרסה 2 של הרישיון, ובין אם (לפי בחירתך) כל גרסה מאוחרת שלו.\n\nתכנית זו מופצת בתקווה שתהיה מועילה, אבל '''בלא אחריות כלשהי'''; ואפילו ללא האחריות המשתמעת בדבר '''מסחריותה''' או '''התאמתה למטרה '''מסוימת'''. לפרטים נוספים, ניתן לעיין ברישיון הציבורי הכללי של GNU.\n\nלתכנית זו אמור היה להיות מצורף <doclink href=Copying>עותק של הרישיון הציבורי הכללי של GNU</doclink>; אם לא קיבלת אותו, אפשר לכתוב ל־Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA או [http://www.gnu.org/copyleft/gpl.html לקרוא אותו דרך האינטרנט].",
+ "config-sidebar": "* [//www.mediawiki.org אתר הבית של מדיה־ויקי]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents המדריך למשתמש]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents המדריך למנהל]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ שו״ת]\n----\n* <doclink href=Readme>קרא אותי</doclink>\n* <doclink href=ReleaseNotes>הערות גרסה</doclink>\n* <doclink href=Copying>העתקה</doclink>\n* <doclink href=UpgradeDoc>שדרוג</doclink>",
+ "config-env-good": "הסביבה שלכם נבדקה.\nאפשר להתקין מדיה־ויקי.",
+ "config-env-bad": "הסביבה שלכם נבדקה.\nאי־אפשר להתקין מדיה־ויקי.",
+ "config-env-php": "מותקנת <span dir=\"ltr\">PHP $1</span>.",
+ "config-env-hhvm": "מותקנת <span dir=\"ltr\">HHVM $1</span>.",
+ "config-unicode-using-utf8": "משתמש ב־utf8_normalize.so של בריון ויבר לנרמול יוניקוד.",
+ "config-unicode-using-intl": "משתמש ב[http://pecl.php.net/intl הרחבת intl PECL] לנרמול יוניקוד.",
+ "config-unicode-pure-php-warning": "'''אזהרה''': [http://pecl.php.net/intl הרחבת intl PECL] אינה זמינה לטיפול בנרמול יוניקוד. משתמש ביישום PHP טהור ואטי יותר.\nאם זהו אתר בעל תעבורה גבוהה, כדאי לקרוא את המסמך הבא: [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].",
+ "config-unicode-update-warning": "'''אזהרה''': הגרסה המותקנת של מעטפת נרמול יוניקוד משתמשת בגרסה ישנה של הספרייה של [http://site.icu-project.org/ פרויקט ICU].\nכדאי [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations לעדכן] אם הטיפול ביוניקוד חשוב לך.",
+ "config-no-db": "לא נמצא דרייבר מסד נתונים מתאים. יש להתקין דרייבר מסד נתונים ל־PHP.\nנתמכים הסוגים הבאים של מסדי נתונים: $1.\n\nאם קִמפלת את PHP בעצמך, יש להגדיר אותו מחדש ולהפעיל את לקוח מסד נתונים, למשל באמצעות <code dir=\"ltr\">./configure --with-mysqli</code>.\nאם התקנת את PHP מחבילה של דביאן או של אובונטו, יש להתקין, למשל, גם את המודול <code dir=\"ltr\">php5-mysql</code>.",
+ "config-outdated-sqlite": "'''אזהרה''': במערכת מתוקן SQLite $1. גרסה זו לא נתמכת ולשימוש ב־SQLite נדרשת גרסה $2 לפחות. SQLlite לא יהיה זמין.",
+ "config-no-fts3": "'''אזהרה''': SQLite מקומפל ללא [//sqlite.org/fts3.html מודול FTS]. יכולות חיפוש לא יהיו זמינות בהתקנה הזאת.",
+ "config-register-globals-error": "<strong>שגיאה: האפשרות <code>[http://php.net/register_globals register_globals]</code> של PHP מופעלת.\nצריך לכבות אותה כדי להמשיך בהתקנה.</strong>\nר' [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] להסבר איך לעשות את זה.",
+ "config-magic-quotes-gpc": "<strong>סופני: האפשרות [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] פעילה!</strong>\nהאפשרות הזאת מקלקלת נתוני קלט באופן בלתי־ניתן לחיזוי.\nלא ניתן להתקין את מדיה־ויקי אם האפשרות הזאת אינה כבויה.",
+ "config-magic-quotes-runtime": "<strong>שגיאה סופנית: האפשרות [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] פעילה!</strong>\nהאפשרות הזאת מעוותת את נתוני הקלט באופן בלתי־צפוי.\nלא ניתן להתקין את מדיה־ויקי אלא אם האפשרות הזאת תכובה.",
+ "config-magic-quotes-sybase": "'''שגיאה סופנית''': האפשרות [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] פעילה!'''\nהאפשרות הזאת מעוותת את נתוני הקלט באופן בלתי־צפוי.\nלא ניתן להתקין את מדיה־ויקי או להשתמש בה אלא אם האפשרות הזאת תכובה.",
+ "config-mbstring": "'''שגיאה סופנית''': האפשרות [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] פעילה!'''\nהאפשרות הזאת גורמת לשגיאות ומעוותת את נתוני הקלט באופן בלתי־צפוי.\nלא ניתן להתקין את מדיה־ויקי או להשתמש בה אלא אם האפשרות הזאת תכובה.",
+ "config-safe-mode": "'''אזהרה:''' האפשרות [http://www.php.net/features.safe-mode safe mode] של PHP פעילה.\nהיא יכולה לגרום לבעיות, במיוחד אם אתם משתמשים בהעלאת קבצים או ב־<code>math</code>.",
+ "config-xml-bad": "מודול XML של PHP חסר.\nמדיה־ויקי דורשת פונקציות של המודול ולא תעבוד עם הגדרות כאלו.\nאם מערכת ההפעלה שלהם היא Mandrake, התקינו את החבילה php-xml.",
+ "config-pcre-old": "<strong>שגיאה סופנית:</strong> חובה להתקין PCRE מגרסה $1 או גרסה חדשה יותר.\nקובץ הרצת ה־PHP שלך מקושר עם PCRE מגרסה $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE מידע נוסף].",
+ "config-pcre-no-utf8": "'''שגיאה סופנית''': נראה שמודול 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-ctype": "<strong>שגיאה סופנית</strong>: נדרשת גרסת PHP שתומכת בהרחבה [http://www.php.net/manual/en/ctype.installation.php Ctype].",
+ "config-iconv": "<strong>סופני:</strong> חובה לקמפל את PHP עם תמיכה ב[הרחבה http://www.php.net/manual/en/iconv.installation.php iconv].",
+ "config-json": "'''שגיאה סופנית:''' PHP קומפל ללא תמיכה ב־JSON.\nיש להתקין את ההרחהב JSON ב־PHP או את ההרחבה [http://pecl.php.net/package/jsonc PECL jsonc] לפני התקנת מדיה־ויקי.\n* ההרחבה ל־PHP כלולה ב־Red Hat Enterprise Linux (ו־CentOS), אך יש להפעיל אותה ב־<code dir=\"ltr\">/etc/php.ini</code> או ב־<code dir=\"ltr\">/etc/php.d/json.ini</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] מותקן",
+ "config-apc": "[http://www.php.net/apc APC] מותקן",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] מותקן",
+ "config-no-cache": "'''אזהרה:''' אחת מהתוכנות הבאות לא נמצאה: [http://www.php.net/apc APC]&rlm;, [http://xcache.lighttpd.net/ XCache] או [http://www.iis.net/download/WinCacheForPhp WinCache].\nמטמון עצמים לא מופעל.",
+ "config-mod-security": "'''אזהרה''': בשרת הווב שלך מופעל [http://modsecurity.org/ mod_security]. אם הוא לא מוגדר טוב, זה יכול לגרום לבעיות במדיה־ויקי ובתכנה אחרת שמאפשרת למשתמשים לשלוח תוכן שרירותי.\nיש לקרוא את [http://modsecurity.org/documentation/ התיעוד של mod_security] או ליצור קשר עם אנשי התמיכה של שירותי האירוח שלכם אם מופיעות לך שגיאות אקראיות.",
+ "config-diff3-bad": "GNU diff3 לא נמצא.",
+ "config-git": "נמצאה Git, תכנת בקרת התצורה: <code dir=\"ltr\">$1</code>.",
+ "config-git-bad": "תכנת בקרת התצורה Git לא נמצאה.",
+ "config-imagemagick": "נמצא ImageMagick&rlm;: <code dir=\"ltr\">$1</code>.\nמזעור תמונות יופעל אם תופעל האפשרות להעלות קבצים.",
+ "config-gd": "נמצאה ספריית הגרפיקה GD המובנית.\nמזעור תמונות יופעל אם תופעל האפשרות להעלות קבצים.",
+ "config-no-scaling": "ספריית GD או ImageMagick לא נמצאו.\nמזעור תמונות לא יופעל.",
+ "config-no-uri": "'''שגיאה:''' אי־אפשר לזהות את הכתובת הנוכחית.\nההתקנה בוטלה.",
+ "config-no-cli-uri": "אזהרה: לא הוגדר <span dir=\"ltr\"><code>--scriptpath</code></span>, משתמש בבררת המחדל: <code dir=\"ltr\">$1</code>.",
+ "config-using-server": "שם השרת בשימוש: \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "נעשה שימוש בכתובת השרת \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''אזהרה:''' תיקיית ההעלאות ההתחלתית <code>$1</code> חשופה להרצת סקריפטים שרירותיים.\nאף שמדיה־ויקי בודקת את כל הקבצים המוּעלים לאיומי אבטחה, מומלץ מאוד למנוע את [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security פרצת האבטחה] הזאת לפני הפעלת ההעלאות.",
+ "config-no-cli-uploads-check": "'''אזהרה:''' תיקיית בררת המחדל להעלאות (<code>$1</code>) לא נבדקת לפגיעוּת להרצת תסריטים אקראיים בזמן התקנה דרך CLI.",
+ "config-brokenlibxml": "במערכת שלך יש שילוב של גרסאות של PHP ושל libxml2 שחשוף לבאגים ויכול לגרום לעיוות נתונים נסתר במדיה־ויקי וביישומי רשת אחרים.\nיש לשדרג ל־libxml2 2.7.3 או גרסה חדשה יותר ([https://bugs.php.net/bug.php?id=45996 באג מתויק ב־PHP]).\nההתקנה נעצרה.",
+ "config-suhosin-max-value-length": "מותקן פה Suhosin והוא מגביל את אורך פרמטר GET ל־$1 בתים. רכיב ResourceLoader של מדיה־ויקי יעקוף את המגלבה הזאת, אבל זה יפגע בביצועים. אם זה בכלל אפשרי, כדאי לתקן את הערך של <code>suhosin.get.max_value_length</code> ל־1024 או יותר בקובץ <code>php.ini</code> ולהגדיר את ‎<code>$wgResourceLoaderMaxQueryLength</code> לאותו הערך בקובץ LocalSettings.php.",
+ "config-db-type": "סוג מסד הנתונים:",
+ "config-db-host": "שרת מסד הנתונים:",
+ "config-db-host-help": "אם שרת מסד הנתונים שלך נמצא על שרת אחר, יש להקליד את שם המחשב או את כתובת ה־IP כאן.\n\nאם זה אירוח משותף, ספק האירוח שלכם אמור לתת לך את שם השרת הנכון במסמכים.\n\nאם זוהי התקנה בשרת Windows שמשתמשת ב־MySQL, השימוש ב־localhost עשוי לא לעבוד. אם הוא לא עובד, יש לנסות את \"127.0.0.1\" בתור כתובת ה־IP המקומית.\n\nאם זה PostgreSQL, תשאירו את השדה הזה ריק כדי להתחבר דרך שקע יוניקס.",
+ "config-db-host-oracle": "TNS של מסד הנתונים:",
+ "config-db-host-oracle-help": "יש להקליד [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm שם חיבור מקומי (Local Connect Name)] תקין; הקובץ tnsnames.ora צריך להיות זמין להתקנה הזאת.<br />\nאם יש פה ב־client libraries 10g או בגרסה חדשה יותר, אפשר להשתמש גם בשיטת מתן השמות [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "זיהוי ויקי זה",
+ "config-db-name": "שם מסד הנתונים:",
+ "config-db-name-help": "נא לבחור שם שמזהה את הוויקי שלכם.\nלא צריכים להיות בו רווחים.\n\nאם זהו משתמשים באירוח משותף, ספק האירוח שלכם ייתן לכם שם מסד נתונים מסוים שתוכלו להשתמש בו או יאפשר לכם ליצור מסד נתונים דרך לוח בקרה.",
+ "config-db-name-oracle": "סכמה של מסד נתונים:",
+ "config-db-account-oracle-warn": "קיימים שלושה תרחישים נתמכים עבור התקנת אורקל בתור מסד הנתונים:\n\nאם הרצונך ליצור חשבון מסד נתונים כחלק מתהליך ההתקנה, נא לספק חשבון בעל תפקיד SYSDBA בתור חשבון מסד הנתונים עבור ההתקנה ולציין את האישורים המבוקשים עבור חשבון הגישה לאינטרנט, אחרת ניתן ליצור באופן ידני את חשבון הגישה לאינטרנט, ולספק חשבון זה בלבד (אם יש לו ההרשאות הדרושות ליצירת עצמי סכמה) או לספק שני חשבונות שונים, אחד עם הרשאות יצירה ואחד מוגבלת עבור גישה לאינטרנט.\n\nסקריפט ליצירת חשבון עם ההרשאות הנדרשות ניתן למצוא בתיקייה \"<span dir=\"ltr\">maintenance/oracle/</span>\" של ההתקנה זו. נא לזכור כי שימוש בחשבון מוגבל יגרום להשבתת כל יכולות תחזוקה עם חשבון בררת המחדל.",
+ "config-db-install-account": "חשבון משתמש להתקנה",
+ "config-db-username": "שם המשתמש במסד הנתונים:",
+ "config-db-password": "הססמה במסד הנתונים:",
+ "config-db-password-empty": "נא להזין ססמה למשתמש מסד הנתונים החדש: $1.\nאף־על־פי שאפשר ליצור חשבונות ללא ססמה, זה לא מאובטח.",
+ "config-db-username-empty": "יש להזין ערך עבור \"{{int:config-db-username}}\".",
+ "config-db-install-username": "יש להכניס שם משתמש שישמש לחיבור למסד נתונים במהלך ההתקנה.\nזהו לא שם משתמש לחשבון במדיה־ויקי; זהו שם משתמש בשרת מסד נתונים.",
+ "config-db-install-password": "יש להקליד ססמה שתשמש אותך לצורך חיבור למסד נתונים במהלך ההתקנה.\nזוהי לא ססמה של חשבון במדיה־ויקי; זוהי ססמה לשרת מסד נתונים.",
+ "config-db-install-help": "יש להקליד את שם המשתמש ואת הססמה להתחברות למסד הנתונים במהלך ההתקנה.",
+ "config-db-account-lock": "להשתמש באותו שם המשתמש ובאותה ססמה בזמן הפעלה רגילה",
+ "config-db-wiki-account": "חשבון משתמש להפעלה רגילה",
+ "config-db-wiki-help": "הקלידו את שם המשתמש והססמה לחיבור למסד הנתונים במהלך פעילות רגילה של הוויקי.\nאם החשבון אינו קיים ולחשבון שבו מתבצעת ההתקנה יש הרשאות מספיקות, החשבון הזה ייווצר עם ההרשאות המזעריות הנחוצות להפעלת הוויקי.",
+ "config-db-prefix": "תחילית לטבלאות של מסד נתונים (database table prefix):",
+ "config-db-prefix-help": "אם נחוץ לך לשתף מסד נתונים אחד בין אתרי ויקי שונים או בין מדיה־ויקי ויישום וב אחר, אפשר לבחור להוסיף תחילית לכל שמות הטבלאות כדי להימנע מהתנגשויות.\nאין להשתמש ברווחים.\n\nהשדה הזה בדרך כלל אמור להיות ריק.",
+ "config-db-charset": "קבוצת התווים (character set) של מסד הנתונים",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 backwards-compatible UTF-8",
+ "config-charset-help": "'''אזהרה:''' שימוש ב־'''backwards-compatible UTF-8''' ב־<span dir=\"ltr\">MySQL 4.1+</span>, וגיבוי של מסד הנתונים באמצעות <code>mysqldump</code> במצב כזה יכול להרוס את כל התווים שאינם ASCII ולקלקל באופן בלתי־הפיך את הגיבויים שלך!\n\nב'''מצב בינרי''' (binary mode) מדיה־ויקי שומרת טקסט UTF-8 במסד הנתונים בשדות בינריים.\nזה יעיל יותר ממצב UTF-8 של MySQL ומאפשר לך להשתמש בכל הטווח של תווי יוניקוד.\nב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קידוד התווים (character set) של הטקסט שלך ויציג וימיר אותו בהתאם, אבל לא יאפשר לך לשמור תווים שאינם נמצאים בטווח הרב־לשוני הבסיסי ([//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane]).",
+ "config-mysql-old": "נדרשת גרסה <span dir=\"ltr\">$1</span> של MySQL או גרסה חדשה יותר. הגרסה הנוכחית שלכם היא $2.",
+ "config-db-port": "פִּתְחַת מסד הנתונים (database port):",
+ "config-db-schema": "סכמה למדיה־ויקי:",
+ "config-db-schema-help": "הסְכֵמָה הבאה בדרך כלל מתאימה.\nיש לשנות אותה רק אם אם זה ממש נחוץ.",
+ "config-pg-test-error": "ההתחברות למסד הנתונים '''$1''' לא מצליחה: $2",
+ "config-sqlite-dir": "תיקיית נתונים (data directory) של SQLite:",
+ "config-sqlite-dir-help": "SQLite שומר את כל הנתונים בקובץ אחד.\n\nלשרת הווב צריכה להיות הרשאה לכתוב לתיקייה שמוגדרת כאן.\n\nהיא לא צריכה נגישה לכולם דרך האינטרנט – בגלל זה איננו שמים אותה באותו מקום עם קובצי ה־PHP.\n\nתוכנת ההתקנה תכתוב קובץ <code dir=\"ltr\">.htaccess</code> יחד אִתו, אבל אם זה ייכשל, מישהו יוכל להשיג גישה למסד הנתונים שלכם.\nשם נמצא מידע מפורש של משתמשים (כתובות דוא״ל, ססמאות מגובבות) וגם גרסאות מחוקות של דפים ומידע מוגבל אחר.\n\nכדאי לשקול לשים את מסד הנתונים במקום אחר לגמרי, למשל ב־<code dir=\"ltr\">/var/lib/mediawiki/yourwik</code>.",
+ "config-oracle-def-ts": "מרחב טבלאות לפי בררת מחדל (default tablespace):",
+ "config-oracle-temp-ts": "מרחב טבלאות זמני (temporary tablespace):",
+ "config-type-mysql": "MySQL (או תואם)",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-support-info": "מדיה־ויקי תומכת במערכות מסדי הנתונים הבאות:\n\n$1\n\nאם אינך רואה את מסד הנתונים שלך ברשימה, יש לעקוב אחר ההוראות המקושרות לעיל כדי להפעיל את התמיכה.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] הוא היעד העיקרי עבור מדיה־ויקי ולו התמיכה הטובה ביותר. מדיה־ויקי עובדת גם עם [{{int:version-db-mariadb-url}} MariaDB] ועם [{{int:version-db-percona-url}} Percona Server], שתואמים ל־MySQL. (ר׳ [http://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. ייתכן שיש בתצורה הזאת באגים מסוימים והיא לא מומלצת לסביבות מבצעיות. (ר׳ [http://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], משתמש ב־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-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] הוא מסד נתונים עסקי מסחרי לחלונות. ([http://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV support])",
+ "config-header-mysql": "הגדרות MySQL",
+ "config-header-postgres": "הגדרות PostgreSQL",
+ "config-header-sqlite": "הגדרות SQLite",
+ "config-header-oracle": "הגדרות Oracle",
+ "config-header-mssql": "הגדרות Microsoft SQL Server",
+ "config-invalid-db-type": "סוג מסד הנתונים שגוי",
+ "config-missing-db-name": "יש להזין ערך עבור \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "יש להכניס ערך לשדה \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "יש להכניס ערך לשדה \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "\"$1\" הוא TNS מסד־נתונים בלתי־‏תקין.\nיש להשתמש ב־\"TNS name\" או במחרוזת \"Easy Connect\" (ר' [http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods])",
+ "config-invalid-db-name": "\"$1\" הוא שם מסד נתונים בלתי־תקין.\nיש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9), קווים תחתיים (_) ומינוסים (-).",
+ "config-invalid-db-prefix": "\"$1\" היא תחילית מסד נתונים בלתי תקינה.\nיש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9), קווים תחתיים (_) ומינוסים (-).",
+ "config-connection-error": "<div dir=\"ltr\">$1.</div>\n\nיש לבדוק את שם השרת, את שם המשתמש ואת הססמה בטופס להלן ולנסות שוב.",
+ "config-invalid-schema": "\"$1\" היא סכמה לא תקינה עבור מדיה־ויקי.\nיש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9) וקווים תחתיים (_).",
+ "config-db-sys-create-oracle": "תוכנית ההתקנה תומכת רק בשימוש בחשבון SYSDBA ליצירת חשבון חדש.",
+ "config-db-sys-user-exists-oracle": "חשבון המשתמש \"$1\" כבר קיים. SYSDBA יכול לשמש רק ליצירת חשבון חדש!",
+ "config-postgres-old": "נדרש PostgreSQL $1 או גרסה חדשה יותר, הגרסה הנוכחית שלכם היא $2.",
+ "config-mssql-old": "חובה להשתמש ב־Microsoft SQL Server מגרסה $1 או גרסה חדשה יותר. הגרסה שלך היא $2.",
+ "config-sqlite-name-help": "יש לבחור בשם שמזהה את הוויקי שלכם.\nאין להשתמש ברווחים או במינוסים.\nזה יהיה שם קובץ הנתונים ל־SQLite.",
+ "config-sqlite-parent-unwritable-group": "לא ניתן ליצור את תיקיית הנתונים <code><nowiki>$1</nowiki></code>, כי לשָׁרַת הווב אין הרשאות לכתוב לתיקיית האם <code><nowiki>$2</nowiki></code> .\n\nתוכנת ההתקנה זיהתה את החשבון שתחתיו רץ שרת הווב שלכם.\nיש לאפשר לשָׁרַת הווב לכתוב לתיקייה <code><nowiki>$3</nowiki></code>.\nבמערכת Unix/Linux יש לכתוב:\n\n<div dir=\"ltr\"><pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre></div>",
+ "config-sqlite-parent-unwritable-nogroup": "לא ניתן ליצור את תיקיית הנתונים <code><nowiki>$1</nowiki></code>, כי לשָׁרַת הווב אין הרשאות לכתוב לתיקיית האם <code><nowiki>$2</nowiki></code>.\n\nתוכנת ההתקנה לא זיהתה את החשבון שתחתיו רץ שרת הווב שלכם.\nיש לאפשר לכל החשבונות לכתוב לתיקייה <code><nowiki>$3</nowiki></code> כדי להמשיך.\nבמערכת Unix/Linux יש לכתוב:\n\n<div dir=\"ltr\"><pre>cd $2\nmkdir $3\nchmod a+w $3</pre></div>",
+ "config-sqlite-mkdir-error": "אירעה שגיאה בעת יצירת תיקיית הנתונים \"$1\".\nנא לבדוק את המיקום ולנסות שוב.",
+ "config-sqlite-dir-unwritable": "אי־אפשר לכתוב לתיקייה \"$1\".\nיש לשנות את ההרשאות שלה כך ששרת הווב יוכל לכתוב אליה ולנסות שוב.",
+ "config-sqlite-connection-error": "$1.\n\nיש לבדוק את תיקיית הנתונים את שם מסד הנתונים להלן ולנסות שוב.",
+ "config-sqlite-readonly": "לא ניתן לכתוב אל הקובץ <code>$1</code>.",
+ "config-sqlite-cant-create-db": "לא ניתן ליצור את קובץ מסד הנתונים <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "ב־PHP חסרה תמיכה ב־FTS3, יבתצע שנמוך טבלאות",
+ "config-can-upgrade": "יש טבלאות מדיה־ויקי במסד הנתונים.\nכדי לשדרג אותן למדיה־ויקי $1, יש ללחוץ על '''המשך'''.",
+ "config-upgrade-done": "השדרוג הושלם.\n\nעכשיו אפשר [$1 להשתמש בוויקי שלך].\n\nאם ברצונך ליצור מחדש את קובץ ה־<code>LocalSettings.php</code> שלך, יש ללחוץ על הכפתור להלן.\nזה '''לא מומלץ''', אלא אם כן יש לך בעיות עם הוויקי שלכם.",
+ "config-upgrade-done-no-regenerate": "השדרוג הושלם.\n\nעכשיו אפשר [$1 להתחיל להשתמש בוויקי שלך].",
+ "config-regenerate": "לחולל מחדש את LocalSettings.php ←",
+ "config-show-table-status": "שאילתת <code>SHOW TABLE STATUS</code> נכשלה!",
+ "config-unknown-collation": "'''אזהרה:''' מסד הנתונים משתמש בשיטת מיון שאינה מוּכּרת.",
+ "config-db-web-account": "חשבון במסד הנתונים לגישה מהרשת",
+ "config-db-web-help": "לבחור את שם המשתמש ואת הססמה ששרת הווב ישתמש בו להתחברות לשרת מסד הנתונים בזמן פעילות רגילה של הוויקי.",
+ "config-db-web-account-same": "להשתמש באותו חשבון כמו עבור ההתקנה",
+ "config-db-web-create": "ליצור חשבון אם הוא אינו קיים כבר.",
+ "config-db-web-no-create-privs": "לחשבון שהקלדת להתקנה אין מספיק הרשאות ליצירת חשבון.\nהחשבון שאתם מקלידים כאן צריך להיות קיים.",
+ "config-mysql-engine": "מנוע האחסון:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''אזהרה''': בחרתם ב־MyISAM בתור מנוע אחסון של MySQL, וזה לא מומלץ מהסיבות הבאות:\n* המנוע הזה בקושי תומך בעיבוד מקבילי בגלל נעילת טבלאות\n* הוא פחות עמיד בפני אובדן מידע ממנועים אחרים\n* הקוד של מדיה־ויקי לא תמיד מטפל ב־MyISAM כפי שצריך\n\nאם התקנת MySQL שלכם תומכת ב־InnoDB, מומלץ מאוד שתבחרו באפשרות הזאת.\nאם התקנת MySQL שלכם אינה תומכת ב־InnoDB, אולי זה הזמן לשקול לשדרג אותה.",
+ "config-mysql-only-myisam-dep": "'''אזהרה:''' MyISAM הוא מנוע האחסון היחיד שזמין ל־MySQL במכונה הזאת, וזה לא מומלץ לשימוש עם מדיה־ויקי, כי:\n* הוא כמעט שאינו תומך בחיבורים מרובים בגלל נעילת טבלאות\n* הוא פגיע יותר לקלקול ממנועים אחרים\n* הקוד של מדיה־ויקי לא תמיד מטפל ב־MyISAM כפי שצריך\n\nהתקנת MySQL אינה תומכת ב־InnoDB, ואולי הגיע הזמן לשדרג אותה.",
+ "config-mysql-engine-help": "'''InnoDB''' היא כמעט תמיד האפשרות הטובה ביותר, כי במנוע הזה יש תמיכה טובה ביותר בעיבוד מקבילי.\n\n'''MyISAM''' עשוי להיות בהתקנות שמיועדות למשתמש אחד ולהתקנות לקריאה בלבד.\nמסדי נתונים עם MyISAM נוטים להיהרס לעתים קרובות יותר מאשר מסדי נתונים עם InnoDB.",
+ "config-mysql-charset": "קידוד התווים של מסד הנתונים:",
+ "config-mysql-binary": "בינרי",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "ב'''מצב בינרי''' (binary mode) מדיה־ויקי שומרת טקסט UTF-8 במסד הנתונים בשדות בינריים.\nזה יעיל יותר ממצב UTF-8 של MySQL ומאפשר לכם להשתמש בכל הטווח של תווי יוניקוד.\n\nב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קידוד התווים (character set) של הטקסט שלכם ויציג וימיר אותו בהתאם, אבל לא יאפשר לכם לשמור תווים שאינם נמצאים בטווח הרב־לשוני הבסיסי ([//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane]).",
+ "config-mssql-auth": "סוג אימות:",
+ "config-mssql-install-auth": "נא לבחור את סוג האימות שישמש להתחברות למסד הנתונים בזמן תהליך ההתקנה. בחירה ב־\"{{int:config-mssql-windowsauth}}\" תשתמש בהרשאות של החשבון שמריץ את השרת הנוכחי.",
+ "config-mssql-web-auth": "נא לבחור את סוג האימות שישמש את השרת להתחברות למסד הנתונים בזמן הריצה הרגילה של הוויקי.\nבחירה ב־\"{{int:config-mssql-windowsauth}}\" תשתמש בהרשאות של החשבון שמריץ את השרת הנוכחי.",
+ "config-mssql-sqlauth": "SQL Server Authentication",
+ "config-mssql-windowsauth": "Windows Authentication",
+ "config-site-name": "שם הוויקי:",
+ "config-site-name-help": "זה יופיע בשורת הכותרת של הדפדפן ובמקומות רבים אחרים.",
+ "config-site-name-blank": "נא להזין שם לאתר.",
+ "config-project-namespace": "מרחב שמות לדפי מיזם:",
+ "config-ns-generic": "מיזם",
+ "config-ns-site-name": "זהה לשם הוויקי: $1",
+ "config-ns-other": "אחר (לציין)",
+ "config-ns-other-default": "הוויקי שלי",
+ "config-project-namespace-help": "בהתאם לדוגמה של ויקיפדיה, אתרי ויקי רבים שומרים על דפי המדיניות שלהם בנפרד מדפי התוכן שלהם ב\"'''מרחב השמות של המיזם'''\" (\"'''project namespace'''\").\nכל שמות הדפים במרחב השמות הזה מתחילים בתחילית מסוימת שאפשר להגדיר כאן.\nבדרך כלל התחילית הזאת מבוססת על שם הוויקי, והיא אינה יכולה להכיל תווי פיסוק כגון \"#\" או \":\".",
+ "config-ns-invalid": "מרחב השמות \"<nowiki>$1</nowiki>\" אינו תקין.\nיש להקליד שם אחר למרחב השמות של המיזם.",
+ "config-ns-conflict": "מרחב השמות שהגדרת \"<nowiki>$1</nowiki>\" מתנגש עם מרחב שמות מובנה של מדיה־ויקי.\nהגדירו מרחב שמות מיזם שונה.",
+ "config-admin-box": "חשבון מפעיל",
+ "config-admin-name": "שם המשתמש שלך:",
+ "config-admin-password": "ססמה:",
+ "config-admin-password-confirm": "הססמה שוב:",
+ "config-admin-help": "הקלידו כאן את שם המשתמש, למשל \"שקד לוי\" או \"Joe Bloggs\".\nזה השם שישמש אותך כדי להיכנס לוויקי.",
+ "config-admin-name-blank": "נא להזין את שם המשתמש של המפעיל.",
+ "config-admin-name-invalid": "שם המשתמש שהוקלד \"<nowiki>$1</nowiki>\" אינו תקין.\nיש להקליד שם משתמש אחר.",
+ "config-admin-password-blank": "הקלידו ססמה לחשבון המפעיל.",
+ "config-admin-password-mismatch": "שתי הססמאות שהוזנו אינן מתאימות.",
+ "config-admin-email": "כתובת הדוא״ל:",
+ "config-admin-email-help": "יש להקליד כתובת דוא״ל כדי שתהיה אפשרות לקבל מכתבים ממשתמשים אחרים בוויקי, לאתחל את הססמה, ולקבל הודעות על שינויים בדפים ברשימת המעקב. אפשר להשאיר את השדה הזה ריק.",
+ "config-admin-error-user": "שגיאה פנימית ביצירת מפעיל בשם \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "שגיאה פנימית בהגדרת ססמה עבור המפעיל \"<nowiki>$1</nowiki>\"&rlm;: <pre>$2</pre>",
+ "config-admin-error-bademail": "הכנסת כתובת דוא״ל לא תקינה.",
+ "config-subscribe": "להירשם ל[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה עם הודעות על גרסאות חדשות].",
+ "config-subscribe-help": "זוהי רשימת תפוצה עם הודעות מעטות שמשמשת להודעות על הוצאת גרסאות, כולל עדכוני אבטחה חשובים.\nמומלץ להירשם אליה ולעדכן את מדיה־ויקי כאשר יוצאות גרסאות חדשות.",
+ "config-subscribe-noemail": "ניסית להירשם לרשימת תפוצה של הודעות בלי לתת כתובת דוא\"ל.\nנא לתת כתובת דוא\"ל אם ברצונך להירשם לרשימת התפוצה.",
+ "config-almost-done": "כמעט סיימת!\nאפשר לדלג על שאר ההגדרות ולהתקין את הוויקי כבר עכשיו.",
+ "config-optional-continue": "הצגת שאלות נוספות.",
+ "config-optional-skip": "משעמם לי, תתקינו לי כבר את הוויקי הזה.",
+ "config-profile": "תסריט הרשאות משתמשים:",
+ "config-profile-wiki": "ויקי פתוח",
+ "config-profile-no-anon": "נדרשת יצירת חשבון",
+ "config-profile-fishbowl": "עורכים מורשים בלבד",
+ "config-profile-private": "ויקי פרטי",
+ "config-profile-help": "אתרי ויקי עובדים הכי טוב כאשר הם מאפשרים לכמה שיותר אנשים לערוך אותם.\nבמדיה־ויקי קל לסקור את השינויים האחרונים ולשחזר כל נזק שעושים משתמשים תמימים או משחיתים.\n\nעם זאת, אנשים שונים מצאו למדיה־ויקי שימושים מגוּונים ולעתים לא קל לשכנע את כולם ביתרונות של \"דרך הוויקי\" המסורתית. ולכן יש לך בררה.\n\nבאתר מסוג '''{{int:config-profile-wiki}}''' – לכולם יש הרשאה לערוך, אפילו בלי להיכנס לחשבון.\nבאתר וויקי מסוג '''{{int:config-profile-no-anon}}''' יש ביטחון גדול יותר, אבל הגדרה כזאת יכולה להרתיע תורמים מזדמנים.\n\nבתסריט '''{{int:config-profile-fishbowl}}''' רק משתמשים שקיבלו אישור יכולים לערוך, אבל כל הגולשים יכולים לקרוא את הדפים ואת גרסאותיהם הקודמות.\nב'''{{int:config-profile-private}}''' רק משתמשים שקיבלו אישור יכולים לקרוא ולערוך דפים.\n\nהגדרות מורכבות של הרשאות אפשריות אחרי ההתקנה, ר׳ את [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights הפרק על הנושא הזה בספר ההדרכה].",
+ "config-license": "זכויות יוצרים ורישיון:",
+ "config-license-none": "ללא כותרת תחתית עם רישיון",
+ "config-license-cc-by-sa": "קריאייטיב קומונז–ייחוס–שיתוף זהה",
+ "config-license-cc-by": "קריאייטיב קומונז–ייחוס",
+ "config-license-cc-by-nc-sa": "קריאייטיב קומונז ייחוס–ללא שימוש מסחרי–שיתוף זהה",
+ "config-license-cc-0": "קריאייטיב קומונז אפס (נחלת הכלל)",
+ "config-license-gfdl": "רישיון חופשי למסמכים של גנו גרסה 1.3 או חדשה יותר",
+ "config-license-pd": "נחלת הכלל",
+ "config-license-cc-choose": "בחירת רישיון קריאייטיב קומונז מותאם אישית",
+ "config-license-help": "אתרי ויקי ציבוריים רבים מפרסמים את כל התרומות [http://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": "אם אתם רוצים שדוא״ל יעבוד, [http://www.php.net/manual/en/mail.configuration.php אפשרויות הדוא״ל של PHP] צריכות להיות מוגדרות נכון.\nאם אינכם רוצים להפעיל שום אפשרויות דוא״ל, כבו אותן כאן ועכשיו.",
+ "config-email-user": "להפעיל שליחת דוא״ל ממשתמש למשתמש",
+ "config-email-user-help": "לאפשר לכל המשתמשים לשלוח אחד לשני דוא״ל אם הם הפעילו את זה בהעדפות שלהם.",
+ "config-email-usertalk": "להפעיל הודעות על דף שיחת משתמש",
+ "config-email-usertalk-help": "לאפשר למשתמשים לקבל הודעות על שינויים בדפי המשתמש שלהם, אם הם הפעילו את זה בהעדפות שלהם.",
+ "config-email-watchlist": "הפעלת התרעה על רשימת המעקב",
+ "config-email-watchlist-help": "לאפשר למשתמשים לקבל הודעות על הדפים ברשימת המעקב שלהם אם הם הפעילו את זה בהעדפות שלהם.",
+ "config-email-auth": "הפעלת התרעה בדוא״ל",
+ "config-email-auth-help": "אם האפשרות הזאת מופעלת, משתמשים יצטרכו לאשר את כתובת הדוא״ל שלהם באמצעות קישור שיישלח אליהם בכל פעם שהם יגדירו או ישנו אותה.\nרק כתובות דוא״ל מאושרות יכולות לקבלת דוא״ל ממשתמשים אחרים או מכתבים עם הודעות על שינויים.\n'''מומלץ''' להגדיר את האפשרות הזאת לאתרי ויקי ציבוריים כי אפשר לעשות שימוש לרעה בתכונות הדוא״ל.",
+ "config-email-sender": "כתובת דוא״ל לתשובות:",
+ "config-email-sender-help": "הכניסו את כתובת הדוא״ל שתשמש ככתובת לתשובה לכל הדואר היוצא.\nלשם יישלחו תגובות שגיאה (bounce).\nשרתי דוא״ל רבים דורשים שלפחות החלק של המתחם יהיה תקין.",
+ "config-upload-settings": "העלאת קבצים ותמונות",
+ "config-upload-enable": "להפעיל העלאת קבצים",
+ "config-upload-help": "העלאות קבצים חושפות את השרת שלכם לסיכוני אבטחה.\nלמידע נוסף, קִראו את [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security חלק האבטחה] בספר ההדרכה.\n\nכדי להפעיל העלאת קבצים שנו את ההרשאות של התיקייה <code>images</code> תחת תיקיית השורש של מדיה־ויקי כך ששרת הווב יוכל לכתוב אליה.\nזה מפעיל את האפשרות הזאת.",
+ "config-upload-deleted": "תיקיית לקבצים שנמחקו:",
+ "config-upload-deleted-help": "בחרו את התיקייה לארכוב קבצים מחוקים.\nכדאי שזה לא יהיה נגיש לכל העולם דרך הרשת.",
+ "config-logo": "כתובת הסמל:",
+ "config-logo-help": "המראה ההתחלתי של מדיה־ויקי מכיל מקום לסמל של 135 על 160 פיקסלים בפינה העליונה מעל תפריט הצד.\nיש להעלות תמונה בגודל מתאים ולהכניס את הכתובת כאן.\n\nבאפשרותך להשתמש ב־<code dir=\"ltr\">$wgStylePath</code> או ב־<code dir=\"ltr\">$wgScriptPath</code> אם הסמל שלך נמצא במקום יחסי לנתיבים האלה.\n\nאם אינכם רוצים סמל, השאירו את התיבה הזאת ריקה.",
+ "config-instantcommons": "להפעיל את Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] היא תכונה שמאפשרת לאתרי ויקי להשתמש בתמונות, בצלילים ובמדיה אחרת שנמצאת באתר [//commons.wikimedia.org/ ויקישיתוף] (Wikimedia Commons).\nכדי לעשות את זה, מדיה־ויקי צריך לגשת לאינטרנט.\n\nלמידע נוסף על התכונה הזאת, כולל הוראות איך להפעיל את זה לאתרי ויקי שאינם ויקישיתוף, ר׳ [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos את ספר ההדרכה].",
+ "config-cc-error": "בורר רישיונות קריאייטיב קומונז לא החזיר שום תוצאה.\nהקלידו את שם הרישיון ידנית.",
+ "config-cc-again": "נא לבחור שוב...",
+ "config-cc-not-chosen": "בחרו באיזה רישיון קריאייטיב קומונז להשתמש ולחצו \"המשך\".",
+ "config-advanced-settings": "הגדרות מתקדמות",
+ "config-cache-options": "הגדרות למטמון עצמים (object caching):",
+ "config-cache-help": "מטמון עצמים משמש לשיפור המהירות של מדיה־ויקי על־ידי שמירה של נתונים שהשימוש בהם נפוץ במטמון.\nלאתרים בינוניים וגדולים כדאי מאוד להפעיל את זה, וגם אתרים קטנים ייהנו מזה.",
+ "config-cache-none": "ללא מטמון (שום יכולת אינה מוּסרת, אבל הביצועים באתרים גדולים ייפגעו)",
+ "config-cache-accel": "מטמון עצמים (object caching) של PHP&rlm; (APC&rlm;, XCache או WinCache)",
+ "config-cache-memcached": "להשתמש ב־Memcached (דורש התקנות והגדרות נוספות)",
+ "config-memcached-servers": "שרתי Memcached:",
+ "config-memcached-help": "רשימת כתובות IP ש־Memcached ישתמש בהן.\nיש לרשום כתובת אחת בכל שורה ולציין את הפִּתְחָה (port), למשל:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "בחרת ב־Memcached בתתור סוג המטמון שלכם, אבל לא הגדרתם שום שרת.",
+ "config-memcache-badip": "כתובת ה־IP שהקלדת עבור Memcached בלתי־תקינה: $1.",
+ "config-memcache-noport": "לא הגדרתם פתחה לשימוש שרת Memcached&rlm;: $1.\nאם אינכם יודעים את מספר הפתחה, בררת המחדל היא 11211.",
+ "config-memcache-badport": "מספרי פתחה של Memcached צריכים להיות בין $1 ל־$2",
+ "config-extensions": "הרחבות",
+ "config-extensions-help": "ההרחבות ברשימה לעיל התגלו בתיקיית <span dir=\"ltr\"><code>./extensions</code></span> שלכם.\n\nייתכן שזה ידרוש הגדרות נוספות, אבל תוכלו להפעיל אותן עכשיו.",
+ "config-skins": "עיצובים",
+ "config-skins-help": "העיצובים לעיל נמצאו בתיקיית ה־<code dir=\"ltr\">./skins</code> שלך. חובה להפעיל לפחות אחת ולבחור בררת מחדל.",
+ "config-skins-use-as-default": "להשתמש בזה בתור בררת מחדל",
+ "config-skins-missing": "לא נמצאו עיצובים; מדיה־ויקי תשתמש בעיצוב גיבוי עד התקנת משהו מתאים.",
+ "config-skins-must-enable-some": "חובה לבחור לפחות עיצוב אחד שיופעל.",
+ "config-skins-must-enable-default": "העיצוב שנבחר בתור בררת מחדל חייב להיות מופעל.",
+ "config-install-alreadydone": "'''אזהרה:''' נראה שכבר התקנתם את מדיה־ויקי ואתם מנסים להתקין אותה שוב.\nאנה התקדמו לדף הבא.",
+ "config-install-begin": "כשתלחצו על \"{{int:config-continue}}\", תתחילו את ההתקנה של מדיה־ויקי.\nאם אתם עדיין רוצים לשנות משהו, לחצו על \"{{int:config-back}}\".",
+ "config-install-step-done": "בוצע",
+ "config-install-step-failed": "נכשל",
+ "config-install-extensions": "כולל הרחבות",
+ "config-install-database": "הקמת מסד נתונים",
+ "config-install-schema": "יצירת סכמה",
+ "config-install-pg-schema-not-exist": "סכמה של PostgreSQL אינה קיימת",
+ "config-install-pg-schema-failed": "יצירת טבלאות נכשלה.\nודאו כי המשתמש \"$1\" יכול לכתוב לסכמה \"$2\".",
+ "config-install-pg-commit": "שמירת שינויים",
+ "config-install-pg-plpgsql": "בדיקת שפת PL/pgSQL",
+ "config-pg-no-plpgsql": "צריך להתקין את שפת PL/pgSQL במסד הנתונים $1",
+ "config-pg-no-create-privs": "לחשבון שהגדרתם להתקנה אין מספיק הרשאות ליצירת חשבון.",
+ "config-pg-not-in-role": "החשבון שציינתם עבור משתמש שרת הווב כבר קיים.\nהחשבון שסיפקתם להתקנה אינו חשבון בעל הרשאות (superuser) ואינו חבר בתפקיד (role) של משתמש שרת הווב, אז אין אפשרות ליצור עצמים בבעלות משתמש שרת הווב.\n\nכעת נדרש במדיה־ויקי שהטבלאות יהיו בבעלות של משתמש שרת הווב. נא לציין שם חשבון שרת וב אחר או ללחוץ על כפתור \"אחורה\" ולציין משתמש התקנה בעל הרשאות מתאימות.",
+ "config-install-user": "יצירת חשבון במסד נתונים",
+ "config-install-user-alreadyexists": "המשתמש \"$1\" כבר קיים",
+ "config-install-user-create-failed": "יצירת משתמש \"$1\" נכשלה: $2",
+ "config-install-user-grant-failed": "מתן הרשאות למשתמש \"$1\" נכשל: $2",
+ "config-install-user-missing": "המשתמש \"$1\" שצוין אינו קיים.",
+ "config-install-user-missing-create": "המשתמש \"$1\" שצוין אינו קיים.\nנא ללחוץ על תיבת בסימון \"יצירת חשבון\" להלן אם אתם רוצים ליצור אותו.",
+ "config-install-tables": "יצירת טבלאות",
+ "config-install-tables-exist": "'''אזהרה:''' נראה שטבלאות מדיה־ויקי כבר קיימות.\nמדלג על יצירתן.",
+ "config-install-tables-failed": "'''שגיאה:''' יצירת הטבלה נכשלה עם השגיאה הבאה: $1",
+ "config-install-interwiki": "אכלוס טבלת בינוויקי התחלתית",
+ "config-install-interwiki-list": "קריאת הקובץ <code>interwiki.list</code> לא הצליחה.",
+ "config-install-interwiki-exists": "'''אזהרה:''' נראה שבטבלת הבינוויקי כבר יש רשומות.\nמדלג על הרשומה ההתחלתית.",
+ "config-install-stats": "אתחול סטטיסטיקות",
+ "config-install-keys": "יצירת מפתחות סודיים",
+ "config-insecure-keys": "'''אזהרה''': {{PLURAL:$2|מפתח|מפתחות}} אבטחה ($1) {{PLURAL:$2|שנוצר|שנוצרו}} במהלך ההתקנה {{PLURAL:$2|אינו בטוח|אינם בטוחים}} מספיק. מומלץ לשקול לשנות {{PLURAL:$2|אותו|אותם}} ידנית.",
+ "config-install-updates": "למנוע הרצת עדכונים מיותרים",
+ "config-install-updates-failed": "<strong>שגיאה:</strong> הוספת מפתחות עדכון לטבלאות נכשל עם השגיאה הבאה: $1",
+ "config-install-sysop": "יצירת חשבון מפעיל",
+ "config-install-subscribe-fail": "הרישום ל־mediawiki-announce לא הצליח: $1",
+ "config-install-subscribe-notpossible": "cURL אינה מותקנת ו־<code>allow_url_fopen</code> אינה זמינה.",
+ "config-install-mainpage": "יצירת דף ראשי עם תוכן התחלתי",
+ "config-install-extension-tables": "יצירת טבלאות להרחבות מופעלות",
+ "config-install-mainpage-failed": "לא הצליחה הכנסת דף ראשי: $1.",
+ "config-install-done": "'''מזל טוב!'''\nהתקנתם בהצלחה את מדיה־ויקי.\n\nתוכנת ההתקנה יצרה את הקובץ <code>LocalSettings.php</code>.\nהוא מכיל את כל ההגדרות שלכם.\n\nתצטרכו להוריד אותו ולשים אותו בבסיס ההתקנה של הוויקי שלכם (אותה התיקייה שבה נמצא הקובץ index.php). ההורדה הייתה אמורה להתחיל באופן אוטומטי.\n\nאם ההורדה לא התחילה, או אם ביטלתם אותה, אפשר להתחיל אותה מחדש בלחיצה על הקישור הבא:\n\n$3\n\n'''שימו לב''': אם לא תעשו זאת עכשיו, קובץ ההגדרות המחוּלל לא יהיה זמין לכם שוב.\n\nאחרי שתעשו את זה, תוכלו '''[$2 להיכנס לוויקי שלכם]'''.",
+ "config-download-localsettings": "הורדת <code>LocalSettings.php</code>",
+ "config-help": "עזרה",
+ "config-help-tooltip": "להרחיב",
+ "config-nofile": "הקובץ \"$1\" לא נמצא. האם הוא נמחק?",
+ "config-extension-link": "הידעת שמדיה־ויקי תומכת ב־[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions הרחבות]?\n\nבאפשרותך לעיין ב־[//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category הרחבות לפי קטגוריה].",
+ "mainpagetext": "'''תוכנת מדיה־ויקי הותקנה בהצלחה.'''",
+ "mainpagedocfooter": "היעזרו ב[//meta.wikimedia.org/wiki/Help:Contents מדריך למשתמש] למידע על שימוש בתוכנת הוויקי.\n\n== קישורים שימושיים ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings רשימת ההגדרות]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ שאלות ותשובות על מדיה־ויקי]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה על השקת גרסאות]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources תרגום מדיה־ויקי לשפה שלך]"
+}
diff --git a/includes/installer/i18n/hi.json b/includes/installer/i18n/hi.json
new file mode 100644
index 00000000..b3779e4f
--- /dev/null
+++ b/includes/installer/i18n/hi.json
@@ -0,0 +1,49 @@
+{
+ "@metadata": {
+ "authors": [
+ "Smtchahal",
+ "Vivek Rai"
+ ]
+ },
+ "config-information": "जानकारी",
+ "config-localsettings-badkey": "आपकी दी गई कुंजी ग़लत है।",
+ "config-your-language": "आपकी भाषा:",
+ "config-wiki-language": "विकी भाषा:",
+ "config-back": "← वापस",
+ "config-continue": "आगे बढ़ें →",
+ "config-page-language": "भाषा",
+ "config-page-welcome": "मीडियाविकी पर आपका स्वागत है!",
+ "config-page-dbconnect": "डेटाबेस से जुड़ें",
+ "config-page-name": "नाम",
+ "config-page-options": "विकल्प",
+ "config-page-install": "स्थापित करें",
+ "config-page-complete": "पूर्ण!",
+ "config-page-restart": "स्थापना को पुनरारंभ करें",
+ "config-page-readme": "मुझे पढ़ें",
+ "config-page-existingwiki": "मौजूदा विकि",
+ "config-restart": "हाँ, इसे पुनः आरंभ करें",
+ "config-env-php": "PHP $1 स्थापित किया गया है।",
+ "config-env-php-toolow": "PHP $1 स्थापित किया गया है।\nतथापि, मीडियाविकि PHP $2 या उच्चतर की आवश्यकता है।",
+ "config-mssql-auth": "प्रमाणन प्रकार:",
+ "config-mssql-sqlauth": "SQL सर्वर प्रमाणन",
+ "config-site-name": "विकि का नाम:",
+ "config-project-namespace": "प्रकल्प नामस्थान:",
+ "config-ns-generic": "प्रकल्प",
+ "config-ns-other": "अन्य (निर्दिष्ट करें)",
+ "config-ns-other-default": "मेरा विकि",
+ "config-admin-password": "कूटशब्द:",
+ "config-admin-password-confirm": "फिर से कूटशब्द:",
+ "config-admin-email": "ईमेल पता:",
+ "config-optional-continue": "मुझसे और सवाल पूछें।",
+ "config-optional-skip": "मैं पहले से ही ऊब चुका हूँ, बस विकि स्थापित करें।",
+ "config-profile-wiki": "खुला विकि",
+ "config-profile-no-anon": "खाता बनाने की आवश्यकता",
+ "config-profile-fishbowl": "केवल प्रमाषित संपादक ही",
+ "config-profile-private": "निजी विकि",
+ "config-email-watchlist": "ध्यानसूची अधिसूचना को सक्षम करें",
+ "config-extensions": "एक्सटेंशन",
+ "config-help": "सहायता",
+ "config-nofile": "फ़ाइल \"$1\" नहीं पाई जा सकी। क्या इसे हटा दिया गया है?",
+ "mainpagetext": "'''मीडियाविकिका इन्स्टॉलेशन पूरा हो गया हैं ।'''",
+ "mainpagedocfooter": "विकि सॉफ्टवेयरके इस्तेमाल के लिये [//meta.wikimedia.org/wiki/Help:Contents उपयोगकर्ता गाईड] देखें ।\n\n== शुरुवात करें ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings कॉन्फिगरेशन सेटींगकी सूची]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ मीडियाविकिके बारे में प्राय: पूछे जाने वाले सवाल]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मीडियाविकि मेलिंग लिस्ट]"
+}
diff --git a/includes/installer/i18n/hif-latn.json b/includes/installer/i18n/hif-latn.json
new file mode 100644
index 00000000..d5d46cee
--- /dev/null
+++ b/includes/installer/i18n/hif-latn.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Thakurji"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki ke safalta se install kar dewa gais hai.'''",
+ "mainpagedocfooter": "Wiki software ke use kare ke aur jaankari ke khatir [//meta.wikimedia.org/wiki/Help:Contents User's Guide] ke dekho.\n\n== Getting started ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/hil.json b/includes/installer/i18n/hil.json
new file mode 100644
index 00000000..f3f54c9e
--- /dev/null
+++ b/includes/installer/i18n/hil.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anjoeli9806"
+ ]
+ },
+ "mainpagetext": "'''Ang MediaWiki madinalag-on nga na-instala.'''",
+ "mainpagedocfooter": " Magkonsulta sa [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para sa mga impormasyon sa paggamit sang wiki nga software.\n\n== Pag-umpisa ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista sang mga konpigorasyon sang pagkay-o]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Mga Masami Pamangkoton sa MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat kon may paguha-on nga MediaWiki]"
+}
diff --git a/includes/installer/i18n/hr.json b/includes/installer/i18n/hr.json
new file mode 100644
index 00000000..b8c51029
--- /dev/null
+++ b/includes/installer/i18n/hr.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Softver MediaWiki je uspješno instaliran.'''",
+ "mainpagedocfooter": "Pogledajte [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentaciju o prilagodbi sučelja]\ni [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Vodič za suradnike] za pomoć pri uporabi i podešavanju."
+}
diff --git a/includes/installer/i18n/hrx.json b/includes/installer/i18n/hrx.json
new file mode 100644
index 00000000..83c373a5
--- /dev/null
+++ b/includes/installer/i18n/hrx.json
@@ -0,0 +1,314 @@
+{
+ "@metadata": {
+ "authors": [
+ "Paul Beppler"
+ ]
+ },
+ "config-desc": "Das MediaWiki-Installationsprogramm",
+ "config-title": "Installation von MediaWiki $1",
+ "config-information": "Informatione",
+ "config-localsettings-upgrade": "En Datei <code>LocalSettings.php</code> woard gefund.\nUm die vorhandne Installation aktualisiere zu könne, muss der Parameter sein Weart <code>$wgUpgradeKey</code> im follichende Ingäbfeld oongeb sin.\nDer Parameterweart befindt sich in der Datei <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "En Datei <code><code>LocalSettings.php</code></code> woard gefund.\nUm die vorhandne Installation se aktualisiere, muss die Datei <code>update.php</code> ausgefüahrt sin.",
+ "config-localsettings-key": "Aktualisierungsschlüssel:",
+ "config-localsettings-badkey": "Der angebne Aktualisierungsschlüssel ist falsch.",
+ "config-upgrade-key-missing": "En MediaWiki-Installation woard gefund.\nUm die vorhandne Installation aktualisiere zu könne, muss die unne oongebne Codezeil in die Datei <code>LocalSettings.php</code> an sein End ingefücht sin:\n\n$1",
+ "config-localsettings-incomplete": "Die vorhandne Datei <code>LocalSettings.php</code> scheint unvollständig zu sin.\nDie Variable <code>$1</code> woard net definiert.\nDie Datei <code>LocalSettings.php</code> muss entsprechend geännert sin, so dass sie definiert ist. Klick donoh uff „{{int:Config-continue}}“.",
+ "config-localsettings-connection-error": "Beim Verbinnungsversuch zur Datenbank ist, unner Verwennung von der in der Datei <code>LocalSettings.php</code> hinnerlehte Instellunge, en Fehler uffgetret. Die Instellunge müsse korrischiert sin. Donoh kann en erneiter Versuch unternomm sin. \n\n$1",
+ "config-session-error": "Fehler bei dem Setzung Oonfänge: $1",
+ "config-session-expired": "Die Setzungsdate scheine abgeloof se sin.\nSetzunge sind für en Zeitraum von $1 konfiguriert.\nDer kann doorrich der Parameter sein Oonhebung <code>session.gc_maxlifetime</code> in der Datei <code>php.ini</code> erhöcht sin.\nDen Installationsvoargang erneit oonfänge.",
+ "config-no-session": "Die Setzungsdate sind verloar gang!\nDie Datei <code>php.ini</code> muss geprüft und es muss dabei sichergestellt sin, dass der Parameter <code>session.save_path</code> uff das richtiche Verzeichnis verweist.",
+ "config-your-language": "Installions Sproch:",
+ "config-your-language-help": "Bittschön die Sproch auswähle, wo verwennet sin soll währed der Installationsvoargang.",
+ "config-wiki-language": "Die Wiki sei Sproch:",
+ "config-wiki-language-help": "Bittschön die Sproch auswähle, wo üwerwiechend bei der Inhalte ehre Erstelle verwennet sin soll.",
+ "config-back": "← Zurück",
+ "config-continue": "Weiter →",
+ "config-page-language": "Sproch",
+ "config-page-welcome": "Willkomme bei MediaWiki!",
+ "config-page-dbconnect": "Mit der Datenbank verbinne",
+ "config-page-upgrade": "En voarhandne Installation aktualisiere",
+ "config-page-dbsettings": "Instellunge zur Datenbank",
+ "config-page-name": "Noome",
+ "config-page-options": "Optione",
+ "config-page-install": "Installiere",
+ "config-page-complete": "Fertich!",
+ "config-page-restart": "Installationsvoargang erneit oonfänge",
+ "config-page-readme": "Les mich",
+ "config-page-releasenotes": "Versionsinfos (en)",
+ "config-page-copying": "Kopie von die Lizenz",
+ "config-page-upgradedoc": "Aktualisiere",
+ "config-page-existingwiki": "Voarhandnes Wiki",
+ "config-help-restart": "Solle all bereits ingebne Daten gelöscht und der Installationsvoargang erneit oogefäng sin?",
+ "config-restart": "Jo, erneit oonfänge",
+ "config-welcome": "=== Prüfung von die Installationsumgebung ===\nDie Basisprüfunge were jetzt doorrichgefüahrt, um festzustelle, ob die Installationsumgebung für MediaWiki geeichnet ist.\nNotier die Informatione und geb se an, sofern du Hellf beim Installiere benötichst.",
+ "config-copyright": "=== Lizenz und Nutzungsbedingunge ===\n\n$1\n\nDas Programm ist freie Software, d. h. es kann, gemäss den Bedingunge der von der Free Software Foundation veröffentlichte ''GNU General Public License'', weiterverteilt und/oder modifiziert sin. Dabei kann die Version 2, orrer noh eichnem Ermess, jede neuire Version von der Lizenz verwennet sin.\n\nDas Programm weard in der Hoffnung verteilt, dass das nützlich sein weard, dennoch '''ohne jechliche Garantie''' und sogoor ohne die implizierte Garantie von ener '''Marrektgängigkeit''' orrer '''Eichnung für en bestimmte Zweck'''. Hierzu sind weitre Hinweise in der ''GNU General Public License'' enthalt.\n\nEn <doclink href=Copying>Kopie von der GNU General Public License</doclink> sollt zusammer mit dem Programm verteilt woard sin. Sofern das net der Fall woar, kann en Kopie bei der Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftlich verlangt sin orrer uff ehre Website [http://www.gnu.org/copyleft/gpl.html online gelesen] sin.",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/de Website von MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/de Benutzeroonleitung]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/de Administratorenoonleitung]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/de Häifig gestellte Frache]\n----\n* <doclink href=Readme>Lies mich</doclink>\n* <doclink href=ReleaseNotes>Versionsinformatione</doclink>\n* <doclink href=Copying>Lizenzbestimmunge</doclink>\n* <doclink href=UpgradeDoc>Aktualisierung</doclink>",
+ "config-env-good": "Die Installationsumgebung woard geprüft.\nMediaWiki kann installiert sin.",
+ "config-env-bad": "Die Installationsumgebung woard geprüft.\nMediaWiki kann net installiert sin.",
+ "config-env-php": "Die Skriptsproch „PHP“ ($1) ist installiert.",
+ "config-env-php-toolow": "PHP $1 ist installiert.\nJedoch benöticht MediaWiki PHP $2 oder höcher.",
+ "config-unicode-using-utf8": "Zur Unicode-Normalisierung weard Brion Vibbers <code>utf8_normalize.so</code> ingesetzt.",
+ "config-unicode-using-intl": "Zur Unicode-Normalisierung weard die [http://pecl.php.net/intl PECL-Erweiterung intl] ingesetzt.",
+ "config-unicode-pure-php-warning": "'''Warnung:''' Die [http://pecl.php.net/intl PECL-Erweiterung intl] ist für die Unicode-Normalisierung net verfüchbar, so dass stattdessen die langsame pure-PHP-Implementierung genutzt weard.\nSofern en Webseit mit grosser Benutzeranzoohl betrieb weard, sollte weitre Informatione uff der Webseite [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-Normalisierung (en)] geles sin.",
+ "config-unicode-update-warning": "'''Warnung:''' Die installierte Version von der Unicode-Normalisierungswrappers nutzt en ältre Version von der [http://site.icu-project.org/ ICU-Projekts] sein Bibliothek.\nDie sollte [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations aktualisiert] sin, sofern uff die Verwennung von Unicode Wert geleht weard.",
+ "config-no-db": "Es konnt ken adäquater Datenbanktreiwer gefund sin. Es muss doher en Datenbanktreiwer für PHP installiert sin.\nDie folchende Datebanksysteme werre unnerstützt: $1\n\nWenn du PHP sellebst kompiliert host, konfigurier es erneit mit en aktiviert Datebankclient, zum Beispiel dorrich Verwennung von <code>./configure --with-mysqli</code>.\nWenn du PHP von en Debian- orrer Ubuntu-Paket installiert host, dann musst du ooch beispielsweis das <code>php5-mysql</code>-Paket installiere.",
+ "config-outdated-sqlite": "'''Warnung:''' SQLite $1 ist installiert. Allerdings benöticht MediaWiki SQLite $2 orrer höcher. SQLite weard doher net verfüchbar sin.",
+ "config-no-fts3": "'''Warnung:''' SQLite woard ohne das [//sqlite.org/fts3.html FTS3-Modul] kompiliert, so dass ken Suchfunktione für das Datenbanksystem zur Verfüchung stehn werre.",
+ "config-register-globals": "'''Warnung: Der Parameter <code>[http://php.net/register_globals register_globals]</code> von PHP ist aktiviert.'''\n'''Die sollt deaktiviert sin, sofern das möchlich ist.'''\nDie MediaWiki-Installation weard zwoor loofe, wobei awer der Server für potentielle Sicherheitsprobleme oonfällig ist.",
+ "config-magic-quotes-runtime": "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/function.set-magic-quotes-runtime.php set_magic_quotes_runtime]</code> von PHP ist aktiviert!'''\nDie Instellung führt zu unvoarhearsehbare Probleme bei der Dateningäb.\nMediaWiki kann net installiert werre, solang der Parameter net deaktiviert woard.",
+ "config-magic-quotes-sybase": "<strong>Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/sybase.configuration.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> von PHP ist aktiviert!</strong>\nDie Instellung führt zu unvoarhearsehbare Probleme bei der Dateningäb.\nMediaWiki kann net installiert sin, solang der Parameter nicht deaktiviert woard.",
+ "config-mbstring": "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> von PHP ist aktiviert!'''\nDie Instellung veruarsacht Fehler und führt zu unvoarhearsehbare Probleme bei der Dateingäb.\nMediaWiki kann net installiert sin, solang der Parameter net deaktiviert woard.",
+ "config-safe-mode": "'''Warnung:''' Der Funktion <code>[http://www.php.net/features.safe-mode Safe Mode]</code> von PHP ist aktiviert.\nDas kann zu Probleme führen, insbesondscht wenn das Hochloode von Dateie möchlich sin, bzw. der Auszeichner <code>math</code> benutzt werre soll.",
+ "config-xml-bad": "Das XML-Modul von PHP fehlt.\nMediaWiki benöticht Funktione, die das Modul bereitstellt und weard in der bestehende Konfiguration net funktioniere.\nSofern Mandriva benutzt weard, muss noch das „php-xml“-Paket installiert sin.",
+ "config-pcre-old": "<strong>Fataler Fehler:</strong> PCRE $1 orrer neier ist notwendich!\nDie vorhandne PHP-Binärdatei ist mit PCRE $2 verknüpft.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Weitre Informatione].",
+ "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-ctype": "'''Fataler Fehler:''' PHP muss mit Unnerstützung für das [http://www.php.net/manual/de/ctype.installation.php Modul ctype] kompiliert sin.",
+ "config-json": "<strong>Fataler Fehler:</strong> PHP woard ohne Unnerstützung für JSON kompiliert.\nVoar der Installation von MediaWiki muss entweder die PHP-JSON- orrer die [http://pecl.php.net/package/jsonc PECL-jsonc]-Erweiterung installieren sin.\n* Die PHP-Erweiterung ist in Red Hat Enterprise Linux (CentOS) 5 und 6 enthalten, muss jedoch in <code>/etc/php.ini</code> oder <code>/etc/php.d/json.ini</code> aktiviert sin.\n* Eniche Linux-Distributione, die nach Mai 2013 veröffentlicht woor, nutze net meh die PHP-Erweitrung, sondscht stattder die PECL-Erweitrung als <code>php5-json</code> orrer <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] ist installiert",
+ "config-apc": "[http://www.php.net/apc APC] ist installiert",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] ist installiert",
+ "config-no-cache": "'''Warnung:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] orrer [http://www.iis.net/download/WinCacheForPhp WinCache] woare net gefund.\nDas Objektcaching kann doher net aktiviert sin.",
+ "config-mod-security": "'''Warnung:''' Uff dem Webserver woard [http://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 [http://modsecurity.org/documentation/ Dokumentation zu ModSecurity] orrer den Kontakt zum Hoster, sofern Fehler ufftrete.",
+ "config-diff3-bad": "GNU diff3 woard net gefund.",
+ "config-git": "Die Versionsverwaltungssoftware „Git“ woard gefund: <code>$1</code>.",
+ "config-git-bad": "Die Versionsverwaltungssoftware „Git“ woard net gefund.",
+ "config-imagemagick": "Die Bildverooweitungssoftware „ImageMagick“ woard gefund: <code>$1</code>.\nMiniaturoonsichte von Bilder werre möchlich sin, sobald das Hochloode von Dateie aktiviert woard.",
+ "config-gd": "Die im System integrierte GD-Grafikbibliothek woard gefund.\nMiniaturoonsichte von Bilder werre möchlich sin, sobald das Hochloode von Dateie aktiviert woard.",
+ "config-no-scaling": "Weder die GD-Grafikbibliothek noch ImageMagick wore gefund.\nMiniaturoonsichte von Bilder sind dohear net möchlich.",
+ "config-no-uri": "'''Fehler:''' Die aktuelle URL konnte net identifiziert sin.\nDer Installationsvoargang woard doher abgebroch.",
+ "config-no-cli-uri": "'''Warnung''': Es woard ken Pad zum Skipt (<code>--scriptpath</code>) oongeb. Doher weard der Standardpad benutzt: <code>$1</code>.",
+ "config-using-server": "Der Servernoome „<nowiki>$1</nowiki>“ weard verwennet.",
+ "config-using-uri": "Der Server URL \"<nowiki>$1$2</nowiki>\" weard benutz.",
+ "config-uploads-not-safe": "'''Warnung:''' Das Standardverzeichnis für hochgeloodne Dateie <code>$1</code> ist für die willkürliche/arbiträer Ausführung von Skripte oonfällich.\nObwohl MediaWiki die hochgeloodne Dateie uff Sicherheitsrisike üwerprüft, weard dennoch dringend empfohl die [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security Sicherheitslücke] zu schliesse, bevor das Hochloode von Dateie aktiviert weard.",
+ "config-no-cli-uploads-check": "'''Warnung''': Das Standardverzeichnis für hochgeloodene Dateie (<code>$1</code>) weard, während der Installation üwer die Kommandozeile, net auf Sicherheitsoonfälligkeite hinsichtlich willkürlicher/arbiträr Skriptausführunge geprüft.",
+ "config-brokenlibxml": "Das System nutzt en Kombination aus PHP- und libxml2-Versione, die fehleroonfällich ist und versteckte Datefehler bei MediaWiki und annere Weboonwennunge verursache kann.\nAktualisier uff libxml2 2.7.3 orrer später, um das Problem zu löse. Installationsabbruch ([https://bugs.php.net/bug.php?id=45996 sieh hierzu die Fehlermeldung bei PHP]).",
+ "config-suhosin-max-value-length": "Suhosin ist installiert und beschränkt die Läng von der GET-Parameters auf $1 Bytes.\nDer ResouceLoader von MediaWiki weard zwoor unner den Bedingunge funktioniere, allerdings nuer mit verminnerter Leistungsfähigkeit.\nSoweit möchlich sollt der Parameter <code>suhosin.get.max_value_length</code> in der Datei <code>php.ini</code> uff 1024 oreer höcher festgeleht werre.\nGleichzeitich muss der Parameter <code>$wgResourceLoaderMaxQueryLength</code> in der Datei <code>LocalSettings.php</code> uff den selwer Weart rengestellt sin.",
+ "config-db-type": "Datebanksystem:",
+ "config-db-host": "Datebankserver:",
+ "config-db-host-help": "Soweit sich die Datebank uff en annre Server befindt, ist hier der Servernoome orrer die entsprechende IP-Adresse oonzugewe.\n\nSoweit en gemeinschaftlich genutzter Server verwendt weard, sollt der Hoster den zutreffend Servernoomen in seiner Dokumentation oongeb hoon.\n\nSoweit uff enem Windows-Server installiert und MySQL genutzt weard, funktioniert der Servername „localhost“ voaraussichtlich net. Wenn net, sollte „127.0.0.1“ orrer die lokale IP-Adress oongeb sin.\n\nSoweit PostgresQL benutzt weard, muss das Feld/Campo leer geloss sin, um üwer en Unix-Socket zu verbinne.",
+ "config-db-host-oracle": "Datebank-TNS:",
+ "config-db-host-oracle-help": "En gültiche [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „Local Connect“-Namen] angeben. Die „tnsnames.ora“-Datei muss von die Installation erkannt werre könne.<br />Sofern die Client-Bibliotheke für Version 10g orrer neier verwennet weare, kann ooch [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „Easy Connect“] zu der Noomensgebung benutzt sin.",
+ "config-db-wiki-settings": "Bitte Date zu der indeitiche Identifikatio von das Wiki oongewe",
+ "config-db-name": "Datebanksystem:",
+ "config-db-name-help": "Bitte en Noome oongewe, mit dem das Wiki identifiziert werre kann.\nDobei sollt ken Leerzeiche verwennet sin.\n\nSoweit en gemeinschaftlich genutzter Server verwennet weard, sollt der Hoster den Datebanknoome oongegewe orrer awer die Erstellung von en Datebank üwer en entsprechendes Interface gestattet hoon.",
+ "config-db-name-oracle": "Datebankschema:",
+ "config-db-account-oracle-warn": "Es gebt drei von MediaWiki unnerstützte Möchlichkeite Oracle als Datebank inzurichte:\n\nSoweit das Datebankbenutzerkonto im Moment (während des) von dem Installationsvoargang erstellt werre soll, muss en Datebankbenutzerkonto mit der SYSDBA-Berechtichung zusammer mit den entsprechende Onnmeldeinformatione oongeb sin, mit dem dann üwer das Web uff die Datebank zugegriff sin kann. Alternativ kann man ooch ledichlich en enzelnes manuell oongelechtes Datebankbenutzerkonto oongewe, mit dem üwer das Web uff die Datebank zugegriff werre kann, soweit das üwer die Berechtichung zur Erstellung von Datebankscheme verfücht. Zudem ist es möchlich zwooi Datebankbenutzerkonte oonzugew von dene enes die Berechtichung zu der Erstellung von Datebankscheme hot und das annere, um mit ihm üwer das Web uff die Datebank zuzugreife.\n\nEn Skript zu dem Oonlehn von en Datebankbenutzerkonto mit den notwendiche Berechtichunge findt man unner dem Pad „…/maintenance/oracle/“ von der MediaWiki-Installation. Das ist dobei zu bedenke, dass die Verwennung von en Datebankbenutzerkonto mit beschränkte Berechtichunge die Nutzung von der Wartungsfunktione für das Standarddatebankbenutzerkonto deaktiviert.",
+ "config-db-install-account": "Benutzerkonto für die Installation",
+ "config-db-username": "Der Datebankbenutzer sein Noome:",
+ "config-db-password": "Der Datebankbenutzer sei Passwort:",
+ "config-db-password-empty": "Bitte en Passwort für den neie Datebankbenutzer oongewe: $1\nObzwoor es möchlich ist Datebankbenutzer ohne Passwort oonzulenn, so ist das awer net sicher.",
+ "config-db-username-empty": "Du musst en Weart für den \"{{int:config-db-username}}\" ingewe",
+ "config-db-install-username": "Den Benutzernoome oongewe, der für die Verbinnung mit der Datebank (während des) wo im Moment von dem Installationsvoargang benutzt sin soll. Das handelt sich dobei net um den Benutzernoome für das MediaWiki-Konto, awer um den Benutzernoomen von der voargesiehne Datebank.",
+ "config-db-install-password": "Das Passwort oongewe, das für die Verbinnung mit der Datebank (während des) im Momento von der Installationsvoargang benutzt sin soll. Das handelt sich dobei net um das Passwort für das MediaWiki-Konto, awer um das Passwort von der voargesiehne Datebank.",
+ "config-db-install-help": "Benutzernoome und Passwort, die (während des) im Moment von der Installationsvoargang, für die Verbinnung mit der Datebank, benutzt werre solle, sind jetzt oonzugewe.",
+ "config-db-account-lock": "Derselwe Benutzernoome und das Passwort müsse (während des) im Moment von der Wiki sein Normalbetrieb verwennt sin.",
+ "config-db-wiki-account": "Benutzerkonto für den normalen Betrieb",
+ "config-db-wiki-help": "Bitte Benutzernoome und Passwort oongewe, wo der Webserver (während des) im Moment von dem Normalbetriebe dozu verwenne soll, en Verbinnung zu dem Datebankserver hearzustelle.\nSoweit en entsprechendes Benutzerkonto net voarhand ist und das Benutzerkonto für den Installationsvoargang üwer ausreichende Berechtichunge verfücht, weard das Benutzerkonto automatisch mit den Mindestberechtichunge zu der Wiki sein Normalbetrieb oongeleht.",
+ "config-db-prefix": "Datebanktabellepräfix:",
+ "config-db-prefix-help": "Soweit en Datebank für mehrer Wikiinstallatione orrer en Wikiinstallatio und en annre Programminstallation benutzt werre soll, muss en Datebanktabellenpräfix oogeb sin, um Datebankprobleme zu vermeide.\nDas könne ken Leerzeiche verwennt sin.\n\nGewöhnlich bleibt das Datefeld (Datecampo) lear.",
+ "config-db-charset": "Datebankzeichesatz",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binär",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 abwäartskompatibles UTF-8",
+ "config-charset-help": "'''Warnung:''' Soweit '''abwäartskompatibles UTF-8''' bei MySQL 4.1+ verwennt und oonschliessend die Datebank mit <code>mysqldump</code> gesichert weard, könnte alle net mit ASCII-codierten Zeiche beschädicht werre, was zu irreversible Schäde von der Datesichrung füahrt!\n\nIm '''binäre Modus''' speichert MediaWiki UTF-8 Texte in der Datebank in binär kodierte Datefelder (Dadecampos).\nDas ist effizienter als der UTF-8-Modus von MySQL und ermöchlicht so die Verwennung jechlicher Unicode-Zeiche.\nIm '''UTF-8-Modus''' weard MySQL den Zeichesatz der Date erkenne und die richtich oonzeiche und konvertiere.\nDas könne awer ken Zeiche ausserhalb von der [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert sin.",
+ "config-mysql-old": "MySQL $1 orrer höcher weard benöticht. MySQL $2 ist momentan voarhand.",
+ "config-db-port": "Datebankserver:",
+ "config-db-schema": "Dateschema für MediaWiki",
+ "config-db-schema-help": "Das Dateschema ist in der Rechel allgemein verwendbar orrer zu der Verwennung geeichnet.\nNuar Ännrunge dron voarnehme, soweit do gute Gründe dofür gebt.",
+ "config-pg-test-error": "Do kann ken Verbinnung zur Datebank '''$1''' heargestellt sin: $2",
+ "config-sqlite-dir": "SQLite-Dateverzeichnis:",
+ "config-sqlite-dir-help": "SQLite speichert alle Date in en enziche Datei.\n\nDas für sie voargesiehn Verzeichnis muss (während des) im Momento von dem Installationsvoargang beschreibbar orrer beschrib fähich sin.\n\nDas sollt '''net''' üwer das Web zugänglich sin, was der Grund ist, warum die Datei net dort abgeleht weard, wo sich die PHP-Dateie befinne.\n\nDas Installationsprogramm weard mit der Datei zusammer en zusätzliche <code>.htaccess</code>-Datei erstelle. Soweit das scheitert, könne Dritte uff die Datedatei zugreife.\nDas umfasst die Nutzerdate (E-Mail-Adresse, Passwörter, und so weiter) wie ooch gelöschte Seiteversione und annere vertrauliche Date, die im Wiki gespeichert sind.\n\nTue konsideriere, erwäch die Datedatei an en gänz anner Platz abzulehn, zum beispiel im Verzeichnis <code>./var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Standardtabelleraum:",
+ "config-oracle-temp-ts": "Temporärer Tabelleraum:",
+ "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. ([http://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. ([http://www.php.net/manual/de/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. ([http://www.php.net/manual/de/sqlsrv.installation.php Oonleitung zur Kompilierung von PHP mithilfe SQLSRV-Unnerstützung])",
+ "config-header-mysql": "MySQL-Instellunge",
+ "config-header-postgres": "PostgreSQL-Instellunge",
+ "config-header-sqlite": "SQLite-Instellunge",
+ "config-header-oracle": "Oracle-Instellunge",
+ "config-header-mssql": "Instellunge von Microsoft SQL Server",
+ "config-invalid-db-type": "Unzulässiges Datebanksystem",
+ "config-missing-db-name": "Bei \"{{int:config-db-name}}\" muss en Weart oongeb sin.",
+ "config-missing-db-host": "Bei \"{{int:config-db-host}}\" muss en Weart oongeb sin.",
+ "config-missing-db-server-oracle": "Für das \"{{int:config-db-host-oracle}}\" muss en Weart ingeb sin.",
+ "config-invalid-db-server-oracle": "Ungültiches Datebank-TNS „$1“.\nEntweder „TNS Noome“ orrer ene „Easy Connect“-Zeichefolliche verwenne ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle-Benennungsmethode])",
+ "config-invalid-db-name": "Ungülticher Datebankname „$1“.\nDo deerfe nuar ASCII-codierte Buchstoobe (a-z, A-Z), Zooahle (0-9), Unner- (_) sowie Binnestriche (-) verwennt sin.",
+ "config-invalid-db-prefix": "Ungülticher Datebanktabellepräfix „$1“.\nEs dürfe nuar ASCII-codierte Buchstoobe (a-z, A-Z), Zoohle (0-9), Unner- (_) sowie Binnestriche (-) verwennt sin.",
+ "config-connection-error": "$1.\n\nBitte unne oongeb Servernoome, Benutzernoome sowie das Passwort üwerprüfe und es dann erneit versuche.",
+ "config-invalid-schema": "Ungültiches Dateschema für MediaWiki „$1“.\nEs dürfe nuar ASCII-codierte Buchstoobe (a-z, A-Z), Zoohle (0-9) und Unnerstriche (_) verwennt sin.",
+ "config-db-sys-create-oracle": "Das Installationsprogramm unnerstützt nuar die Verwennung von en Datebankbenutzerkonto mit SYSDBA-Berechtichung zum oonlehn von en neie Datebankbenutzerkonto.",
+ "config-db-sys-user-exists-oracle": "Das Datebankbenutzerkonto „$1“ ist schoon voarhand. En Datebankbenutzerkontos mit SYSDBA-Berechtichung kann nuar zum oonlehn von en neie Datebankbenutzerkonto benutzt sin.",
+ "config-postgres-old": "MySQL $1 orrer höcher weard benöticht. MySQL $2 ist momentan voarhand.",
+ "config-mssql-old": "Es weard Microsoft SQL Server $1 orrer später benöticht. Dein Version ist $2.",
+ "config-sqlite-name-help": "Bitte en Noome oongewe, mit dem das Wiki identifiziert werre kann.\nDobei bitte ken Leerzeiche orrer Binnestriche verwenne.\nDer Noome weard für die SQLite-Datedateinoome benutzt.",
+ "config-sqlite-parent-unwritable-group": "Das Dateverzeichnis <code><nowiki>$1</nowiki></code> kann net erzeicht werre, weil das üwergeoordnete Verzeichnis <code><nowiki>$2</nowiki></code> net für den Webserver beschreibbar ist.\n\nDas Installationsprogramm konnt den Benutzer bestimme, mit dem Webserver ausgeführt weard.\nSchreibzugriff uff das <code><nowiki>$3</nowiki></code>-Verzeichnis muss für den ermöglicht werre, so das den Installationsvoargang fortgesetz sin kann.\n\nUff enem Unix- orrer Linux-System:\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Das Dateverzeichnis <code><nowiki>$1</nowiki></code> kann net erzeicht sin, weil das üwergeordnete Verzeichnis <code><nowiki>$2</nowiki></code> net für den Webserver beschreibbar ist.\n\nDas Installationsprogramm konnt den Benutzer bestimmen, mit dem Webserver ausgeführt weard.\nSchreibzugriff uff das <code><nowiki>$3</nowiki></code>-Verzeichnis muss global für den und annre Benutzer ermöglicht sin, so das den Installationsvoargang fortgesetzt sin kann.\n\nUff enem Unix- orrer Linux-System:\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Fehler beim Erstelle von dem Dateverzeichnisses „$1“.\n\nBitte den Speicherplatz üwerprüfe und es donoh erneit versuche.",
+ "config-sqlite-dir-unwritable": "Das Verzeichnis „$1“ ist net beschreibbar.\nBitte die Zugriffsberechtigunge so ännre, dass das Verzeichnis für den Webserver beschreibbar ist und es donoh erneit versuche.",
+ "config-sqlite-connection-error": "$1.\n\nBitte unne oongeb Dateverzeichnis sowie den Datebanknoome üwerprüfe und es donoh erneit versuche.",
+ "config-sqlite-readonly": "Die Datei <code>$1</code> ist net beschreibbar.",
+ "config-sqlite-cant-create-db": "Die Datebankdatei <code>$1</code> konnte net erzeicht sin.",
+ "config-sqlite-fts3-downgrade": "PHP verfücht net üwer FTS3-Unnerstützung. Die Tabelle woare zurückgestuft.",
+ "config-can-upgrade": "Es woare MediaWiki-Tabelle in der Datebank gefund.\nUm sie uff MediaWiki $1 zu aktualisiere, bitte uff '''Weiter''' klicke.",
+ "config-upgrade-done": "Die Aktualisierung ist jetzt abgeschloss.\n\nDas Wiki kann jetzt [$1 benutzt sin].\n\nSoweit die Datei <code>LocalSettings.php</code> nei erzeicht werre soll, bitte uff die Schaltfläche unne klicke.\nDas weard '''net rekomendiert''', es sei denn, es trete Probleme mit dem Wiki uff.",
+ "config-upgrade-done-no-regenerate": "Die Aktualisierung ist abgeschloss.\n\nDas Wiki kann jetzt [$1 benutzt sin].",
+ "config-regenerate": "LocalSettings.php nei erstelle →",
+ "config-show-table-status": "Die Abfroch <code>SHOW TABLE STATUS</code> ist gescheitert!",
+ "config-unknown-collation": "'''Warnung:''' Die Datebank nutzt en unbekannte Kollation.",
+ "config-db-web-account": "Datebankkonto für den Webzugriff",
+ "config-db-web-help": "Bitte Benutzernoome und Passwort auswähle, die der Webserver im Verloof von der Normalbetriebe dozu verwenn soll, en Verbinnung zum Datebankserver herzustelle.",
+ "config-db-web-account-same": "Dasselbe Datebankkonto wie im Verloof von der Installationsvoargang verwenne",
+ "config-db-web-create": "Soweit net bereits voarhand, muss jetzt das Konto erstellt sin",
+ "config-db-web-no-create-privs": "Das oongebne und für den Installationsvoargang voargesiehne Datebankkonto verfücht nwt üwer ausreichend Berechtichunge, um en weitres Datebankkonto zu erstelle.\nDas hier oongebne Datebankkonto muss dohear bereits voarhand sin.",
+ "config-mysql-engine": "Speicher-Engine:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Warnung:''' Es woard MyISAM als Speicher-Engine für MySQL ausgewählt, die aus follichend Gründe net für den Insatz mit MediaWiki rekommendiert ist:\n* Sie unnerstützt uffgrund von Tabellesperrunge koom die neweloofiche Ausführung von Aktione.\n* Sie ist oonfällicher für Dateprobleme.\n* Sie weard von MediaWiki net immer adäquat unnerstützt.\n\nSoweit die voarhandne MySQL-Installation die Speicher-Engine InnoDB unnerstützt, weard sei Verwennung eindringlich rekommendiert.\nSoweit sie sie net unnerstützt, sollt en entsprechend Aktualisierung nunmeahr Erwächung gezoh sin.",
+ "config-mysql-only-myisam-dep": "'''Warnung:''' MyISAM ist die einziche verfüchbare Speicher-Engine für MySQL uff dem Rechner, und das weard net für die Verwennung mit MediaWiki rekommendiert, weil sie\n* uffgrund von Tabellesperrunge koom die neweloofiche Ausführung von Aktione unnerstützt,\n* oonfällicher für Dateprobleme ist und\n* von MediaWiki net immer adäquat unnerstützt weard.\n\nDein MySQL-Installation unnerstützt net InnoDB. Eventuell muss en Aktualisierung dorrichgeführt werre.",
+ "config-mysql-engine-help": "'''InnoDB''' ist nächst immer die bessre Wähl, weil es gleichzeitiche Zugriffe gut unnerstützt.\n\n'''MyISAM''' ist in Enzelnutzerumgebunge sowie bei schreibgeschützte Wikis schneller.\nBei MyISAM-Datebanke treten tendenziell häuficher Fehler uff als bei InnoDB-Datebanke.",
+ "config-mysql-charset": "Datebankzeichesatz",
+ "config-mysql-binary": "binär",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "Im '''binäre Modus''' speichert MediaWiki UTF-8 Texte in der Datebank in binär kodierte Datefelder.\nDas ist effizienter als der UTF-8-Modus von MySQL und ermöglicht so die Verwennung jeder Unicode-Zeiche.\n\nIm '''UTF-8-Modus''' weard MySQL den Zeichesatz der Date erkenne und sie richtich oonzeiche und konvertiere,\nawer könne ken Zeiche ausserhalb von der [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert sin.",
+ "config-mssql-auth": "Authentifikationstyp:",
+ "config-mssql-install-auth": "Wähl den Authentifikationstyp aus, der zur Verbinnung mit der Datebank während von der Installationsprozesses verwennt weard.\nFalls du „{{int:config-mssql-windowsauth}}“ auswählst, werre die Oonmeldeinformatione von en beliebiche Benutzer verwennt, wo den Webserver ausführt.",
+ "config-mssql-web-auth": "Wähl den Authentifikationstyp aus, der vom Webserver zur Verbinnung mit dem Datebankserver während / im Verloof von der gewöhnliche Betrieb von der Wiki verwennt weard.\nFalls du „{{int:config-mssql-windowsauth}}“ auswählst, werre die Oonmeldeinformatione von en beliebiche Benutzer verwennt, wo den Webserver ausführt.",
+ "config-mssql-sqlauth": "SQL-Server-Authentifikation",
+ "config-mssql-windowsauth": "Windows-Authentifikation",
+ "config-site-name": "Der Wiki sein Noome:",
+ "config-site-name-help": "Er weard in der Titelleiste von der Browser, wie ooch verschiedne annre Stelle, benutzt.",
+ "config-site-name-blank": "Der Wiki sein Noome oongewe.",
+ "config-project-namespace": "Der Projekt sein noomeraum:",
+ "config-ns-generic": "Projekt",
+ "config-ns-site-name": "Entsprecht der Wiki sein Noome: $1",
+ "config-ns-other": "Annrer Noome (bitte oongewe)",
+ "config-ns-other-default": "MeinWiki",
+ "config-project-namespace-help": "Dem Beispiel von Wikipedia follichend, unnerscheide viele Wikis zwischich den Seite für Inhalte und dene für Richtlinie. Letztre werre im „'''Projekt sein Noomeraum'''“ hinnerleht.\nAll Seite von dem Noomeraume verfüche üwer en Seitepräfix, wo jetzt an der Stell oongeb sinn kann.\nTraditionell steht dieser Seitepräfix mit dem Noome von der Wiki in enem enge Zusammerhang. Dobei könne bestimmte Sonnerzeiche wie „#“ orrer „:“ net verwennt sin.",
+ "config-ns-invalid": "Der oongebne Noomensraum „<nowiki>$1</nowiki>“ ist ungültich.\nBitte en abweichende Projektnoomeraum oongewe.",
+ "config-ns-conflict": "Der oongebne Noomenraum „<nowiki>$1</nowiki>“ verursacht Problem mit dem Standardnppmeraum von MediaWiki.\nBitte en abweichende Projektnoomeraum oongewe.",
+ "config-admin-box": "Administratorkonto",
+ "config-admin-name": "Dein Benutzernoome:",
+ "config-admin-password": "Passwort:",
+ "config-admin-password-confirm": "Passwort repetiere:",
+ "config-admin-help": "Bitte den bevoarzugten Benutzernoome oongewe, beispielsweise \"Friedrich Beppler\".\nDas ist der Noome, wo benöticht weard, um sich im Wiki oonzumelde.",
+ "config-admin-name-blank": "Bitte den Benutzernoome für den Administratore oongewe.",
+ "config-admin-name-invalid": "Der oongebne Noomensraum „<nowiki>$1</nowiki>“ ist ungültich.\nBitte en abweichende Projektnoomeraum oongewe.",
+ "config-admin-password-blank": "Bitte das Passwort für das Administratorkonto oongewe.",
+ "config-admin-password-mismatch": "Die beide Passwörter stimme net doorrichaus und komplet.",
+ "config-admin-email": "E-Mail-Adress:",
+ "config-admin-email-help": "Bitte hier ein E-Mail-Adress oongewe, wo den E-Mail-Emfang von annre Benutzer von der Wiki, das Zurücksetze von das Passworte sowie Benachrichtichunge zu Ändrunge an beobachtete Seite ermöchlicht. Das Feld kann leer geloss sin.",
+ "config-admin-error-user": "Es ist beim Erstelle von der Administrator mit dem Noome „<nowiki>$1</nowiki>“ en interner Fehler uffgetret.",
+ "config-admin-error-password": "Es ist beim Setze von das Passwort für den Administrator „<nowiki>$1</nowiki>“ en interner Fehler uffgetret: <pre>$2</pre>",
+ "config-admin-error-bademail": "Es woard en ungültiche E-Mail-Adress oongeb",
+ "config-subscribe": "Bitte die Mailinglist [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mitteilunge zu Versionsveröffentlichunge] abonniere.",
+ "config-subscribe-help": "Es handelt sich hierbei um en Mailinglist mit weniche Aussendunge, die für Mitteilunge zu Versionsveröffentlichunge, inschliesslich wichticher Sicherheitsveröffentlichungen, benutzt weard.\nDie Mailingliste sollte abonniert werre. Zudem sollte die MediaWiki-Installation stets aktualisiert werre, sobald en neie Programmversion veröffentlicht woard.",
+ "config-subscribe-noemail": "Beim Abonniere (fazer assinatura) von der Mailinglist mit Mitteilunge zu Versionsveröffentlichunge woard ken E-Mail-Adress oongeb.\nBitte en E-Mail-Adress oongewe, soweit die Mailinglist abonniert werre soll.",
+ "config-almost-done": "Der Vorgang ist nächst abgeschloss!\nDie verbleibende Konfigurationsinstellunge könne üwersprung und das Wiki umgehend installiert sin.",
+ "config-optional-continue": "Jo, es solle weitre Konfigurationinstellunge voargenomm sin.",
+ "config-optional-skip": "Nee, das Wiki soll jetzt installiert werre.",
+ "config-profile": "Profil von der Benutzerberechtichunge:",
+ "config-profile-wiki": "Uffnes Wiki",
+ "config-profile-no-anon": "Erstellung von en Benutzerkonto erforderlich",
+ "config-profile-fishbowl": "ausschliesslich berechtichte Beoorbeiter",
+ "config-profile-private": "geschlossnes Wiki",
+ "config-profile-help": "Wikis sind am nützlichste, wenn so viele Mensche wie möchlich droon Bearbeitunge voarnehme könne.\nMit MediaWiki ist das enfach die letzte Ännrunge nohzuvollziehe und unbrauchbare Bearbeitunge, beispielsweise von unbedärfte orrer böswilliche Benutzer, rückgängich zu mache.\n\nAwer finne etliche Mensche Wikis ooch mit annere Beoorbeitungskonzepte sinnvoll. Manchmol ist das zudem net enfach alle Beteilichte von den Voarteile des „Wiki-Prinzips” zu üwerzeiche. Dodrum ist die Auswoohl möchlich.\n\nDas Modell „'''{{int:config-profile-wiki}}'''“ ermöchlicht es jederene, sogoor ohne üwer en Benutzerkonto zu verfüche, Bearbeitunge voarzunehme.\nEn Wiki bei dem die '''{{int:config-profile-no-anon}}''' ist, verlang von den Benutzer en höchre Verantwortung für ehre Beoorbeitunge en, könnt awer Persone abschrecke, die nuar gelechentlich Beoorbeitunge voarnehme wolle. En Wiki für '''{{int:config-profile-fishbowl}}''' gestattet (permitiert) es nuar bestimmte Benutzer, Beoorbeitunge voarzunehme. Awer kann dobei die Allgemeinheit die Seite immer noch betrachte und Ändrunge nohvollziehe. En '''{{int:config-profile-private}}''' gestattet es nur ausgewählte Benutzer, Seite zu betrachte sowie zu beoorbeite.\n\nKomplexre Konzepte zur Zugriffssteierung könne earst noh abgeschlossne Installationsvoargang ingerichtet sin. Hierzu gebts weitre Informatione uff der Website mit der [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights entsprechende Oonleitung].",
+ "config-license": "Lizenz:",
+ "config-license-none": "Ken Lizenzoongäb in der Fusszeile",
+ "config-license-cc-by-sa": "Creative Commons \"Noomenennung, Weitergäb unner gleiche Bedingunge“",
+ "config-license-cc-by": "Creative Commons „Noomenennung“",
+ "config-license-cc-by-nc-sa": "Creative Commons \"Noomenennung, net kommerziell, Weitergäb unner gleiche Bedingunge\"",
+ "config-license-cc-0": "Creative Commons \"Zero\" (Gemeinfreiheit)",
+ "config-license-gfdl": "GNU-Lizenz für freie Dokumentation 1.3 orrer höcher",
+ "config-license-pd": "Gemeinfreiheit",
+ "config-license-cc-choose": "En benutzerdefiniert Creative-Commons-Lizenz auswähle",
+ "config-license-help": "Viele öffentliche Wikis publiziere all Beiträche unner en [http://freedomdefined.org/Definition/De freie Lizenz].\nDas träht dozu bei en Gefühl von Gemeinschaft zu schaffe und ermuticht zu längerfristicher Mitoorweit.\nDahinchege ist im Allgemeinen en freie Lizenz uff geschlossne Wikis net notwennich.\n\nSoweit man Texte aus der Wikipedia verwenne möcht und umgekehrt, sollt die Creative Commons-Lizenz \"Noomenennung, Weitergäb unner gleiche Bedingunge\" gewählt sin.\n\nDie Wikipedia nutzte voarmols die GNU-Lizenz für freie Dokumentation (GFDL).\nDie GFDL ist en gültiche Lizenz, wo awer schwear zu verstehn ist.\nEs ist zudem schwierich gemäss die Lizenz lizenziert Inhalte wiederzuverwenne.",
+ "config-email-settings": "E-Mail-Instellunge",
+ "config-enable-email": "Ausgehende E-Mails ermöchliche",
+ "config-enable-email-help": "Soweit die E-Mail-Funktione benutzt sin solle, müsse die entsprechende [http://www.php.net/manual/en/mail.configuration.php PHP-E-Mail-Einstellungen] richtich konfiguriert sin.\nFür den Fall, dass die E-Mail-Funktione net benöticht sin, könne die dohier deaktiviert sin.",
+ "config-email-user": "E-Mail-Versand von Benutzer zu Benutzer aktiviere",
+ "config-email-user-help": "Alle Benutzer ermöchliche, sich gecheseitich E-Mails zu schicke, soweit die das in ehre Instellunge aktiviert hoon.",
+ "config-email-usertalk": "Benachrichtigunge zu Ändrunge an Benutzerdiskussionsseite ermöchliche",
+ "config-email-usertalk-help": "Ermöglicht es Benutzer, Benachrichtichunge zu Ännrunge an ehre Benutzerdiskussionsseite zu erhalte, soweit sie das in ehre Instellunge aktiviert hoon.",
+ "config-email-watchlist": "Benachrichtichunge zu Ändrunge an Seite uff der Beobachtungslist ermöchliche",
+ "config-email-watchlist-help": "Ermöglicht es Benutzer, Benachrichtichunge zu Ännrunge an ehre Benutzerdiskussionsseite zu erhalte, soweit sie das in ehre Instellunge aktiviert hoon.",
+ "config-email-auth": "E-Mail-Authentifizierung ermöchliche",
+ "config-email-auth-help": "Soweit die Funktion aktiviert ist, müsse Benutzer ehre E-Mail-Adress bestätiche, indem sie den Bestätichungslink nutze, der ehne immer dann zugesandt weard, wenn se ehre E-Mail-Adress oongeb orrer ändern.\nNuar bestätichte E-Mail-Adresse könne Nachrichte von annren Benutzer orrer Benachrichtichungsmitteilunge erhalten.\nDie Aktivierung con der Funktion weard bei offne Wikis, mit Hinblick uff möchliche Missbrauch von der E-Mail-Funktione, (rekommendiert) '''emfohl.'''",
+ "config-email-sender": "E-Mail-Adress für Antworte:",
+ "config-email-sender-help": "Bitte hier die E-Mail-Adress oongewe, die als Absenderadress bei ausgehende E-Mails ingesetzt werre soll.\nRückloofende E-Mails werre an die E-Mail-Adress gesandt.\nBei viele E-Mail-Server muss der Tel der E-Mail-Adress mit der Domainoongäb korrekt sin.",
+ "config-upload-settings": "Hochloode von Bilder und Dateie",
+ "config-upload-enable": "Das Hochloode von Dateie ermöchliche",
+ "config-upload-help": "Das Hochloode von Dateie macht den Server für potentielle Sicherheitsprobleme oonfällich.\nWeitre Informatione hierzu könne im [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security Abschnitt Sicherheit] von der Anleitung nohgeles sin.\n\nUm das Hochloode von Dateie zu ermöglichen, muss der Zugriff uff das Unnerverzeichnis <code>./images</code> so geännert sin, das das für den Webserver beschreibbar ist.\nHernoh kann die Option aktiviert sin.",
+ "config-upload-deleted": "Verzeichnis für gelöschte Dateie:",
+ "config-upload-deleted-help": "Bitte en Verzeichnis auswähle, in dem gelöschte Dateie archiviert werre solle.\nIdealerweise sollt es net üwer das Internet zugänglich sin.",
+ "config-logo": "Das Logo sein URL:",
+ "config-logo-help": "Die Standardoberfläche von MediaWiki verfücht links owerhalleb von der Seiteleiste üwer Platz für en Logo mit den Moaa 135x160 Pixel.\nBitte en Logo in entsprechender Gröss hochloode und die zugehöriche URL an der Stell oongewe.\n\nDu kannst <code>$wgStylePath</code> orrer <code>$wgScriptPath</code> verwenne, falls dein Logo relativ zu den Pade ist.\n\nSofern ken Logo benöticht weard, kann das Datefeld leer bleiwe.",
+ "config-instantcommons": "\"InstantCommons\" aktiviere",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons InstantCommons] ist en Funktion, wos Wikis ermöchlicht, Bild-, Klang- und annre Mediendateie zu nutze, wo uff der Website [//commons.wikimedia.org/ Wikimedia Commons] verfüchbar sind.\nUm die Funktion nutze zu könne, muss das Wiki üwer en Verbinnung zum Internet verfüche.\n\nWeitre Informatione zu der Funktion, inschliesslich von der Oonleitung, wie hierfür annre Wikis als Wikimedia Commons ingerichtet werre könne, gebts im [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos Handbuch].",
+ "config-cc-error": "Der Creativ-Commons-Lizenzassistent konnt ken Lizenz ermittle.\nDie Lizenz ist doher jetzt manuell inzugewe.",
+ "config-cc-again": "Erneit auswähle …",
+ "config-cc-not-chosen": "Die gewünschte Creative-Commons-Lizenz auswähle und dann uff \"weiter\" klicke.",
+ "config-advanced-settings": "Erweiterte Konfiguratio",
+ "config-cache-options": "Instellunge für die Zwischichspeichrung von Objekte:",
+ "config-cache-help": "Das Objektcaching weard dozu benutzt die Geschwindigkeit von MediaWiki zu verbessre, indem häifich genutzte Date zwischichgespeichert werre.\nEs weard seahr emfohl (rekommendiert) das für mittelgrosse bis grosse Wikis zu nutze, awer ooch für klene Wikis ergewe sich erkennbare Geschwindichkeitsverbessrunge.",
+ "config-cache-none": "Ken Objektcaching (es weard ken Funktion entfernt, trotzdem kann das die Geschwindigkeit grössrer Wikis negativ beeinflusse)",
+ "config-cache-accel": "Objektcaching (Objektfeststelle, Objektufffänge) von PHP (APC, XCache orrer WinCache)",
+ "config-cache-memcached": "Memcached Cacheserver nutze (erfordert en zusätzliche Installationsvoargang mitsamt Konfiguration)",
+ "config-memcached-servers": "Memcached Cacheserver",
+ "config-memcached-help": "List der für Memcached nutzboore IP-Adresse.\nEs sollt en je Zeil mitsamt das voargesiehne Ports oongeb sin. Beispiele:\n127.0.0.1:11211 orrer\n192.168.1.25:1234 usw.",
+ "config-memcache-needservers": "Memcached woard als Cacheserver ausgewählt. Dobei woard trotzdem ken Server oongeb.",
+ "config-memcache-badip": "Es woard für Memcached en ungültiche IP-Adress oongeb: $1",
+ "config-memcache-noport": "Es woard ken Port zur Nutzung dorrich den Memcached Cacheserver oongeb: $1\nSoweit der Port unbekannt ist, ist 11211 die Standardoongäb.",
+ "config-memcache-badport": "Der Ports für den Memcached Cacheserver sollte zwischich $1 und $2 leihe",
+ "config-extensions": "Erweitrunge",
+ "config-extensions-help": "Die obich oongebne Erweitrungen woore im Verzeichnis <code>./extensions</code> gefund.\n\nEs könnte zusätzliche Konfigurierunge zu enzelne Erweitrunge erforderlich sin, trotzdem könne sie awer bereits jetzt aktiviert sin.",
+ "config-install-alreadydone": "'''Warnung:''' Es woard en vorhandne MediaWiki-Installation gefund.\nEs muss doher mit den nächste Seit weitergemacht sin.",
+ "config-install-begin": "Doorrich das Drücke von \"{{int:config-continue}}\" weard die Installation von MediaWiki gestartet (oongefang).\nSoweit Ännrunge voargenomm werre solle, kann man uff \"{{int:config-back}}\" klicken.",
+ "config-install-step-done": "erledichht",
+ "config-install-step-failed": "gescheitert",
+ "config-install-extensions": "Programmerweitrunge",
+ "config-install-database": "Datebank weard ingerichtet",
+ "config-install-schema": "Dateschema weard erstellt",
+ "config-install-pg-schema-not-exist": "Das PostgesSQL-Datenschema ist net voarhande",
+ "config-install-pg-schema-failed": "Das Erstelle von der Datetabelle ist gescheitert.\nDas muss sichergestellt sin, dass der Benutzer „$1“ Schreibzugriff uff das Dateschema „$2“ hot.",
+ "config-install-pg-commit": "Ändrunge oonwenne",
+ "config-install-pg-plpgsql": "Such noh der Datebanksprache PL/pgSQL",
+ "config-pg-no-plpgsql": "Für Datebank $1 muss die Datebanksprache PL/pgSQL installiert sin",
+ "config-pg-no-create-privs": "Das für die Installation oongeb Konto verfücht net üwer ausreichende Berechtichunge, um en Datebanknutzerkonto zu erstelle.",
+ "config-pg-not-in-role": "Das für den Webbenutzer oongebne Benutzerkonto ist bereits voarhand.\nDas für den Installationsvoargang oongebne Benutzerkonto ist ken Superbenutzer und net Mitglied von der Webbenutzer sein Benutzergrupp, so dass ken dem Webbenutzer zugeoordnete Dateobjekte erstellt sin könne.\n\nFür MediaWiki ist es momentan erforderlich (rekommendiert), dass die Tabelle dem Webbenutzer rechtemässich zugeoordnet sind. Bitte en annre Noome für den Wikibenutzer oongewe orrer „← Zurück“ oonklicke, um en ausreichend berechtichte Benutzer für den Installationsvoargang oonzugewe.",
+ "config-install-user": "Datebankbenutzer weard erstellt",
+ "config-install-user-alreadyexists": "Datebankbenutzer „$1“ ist bereits voarhand",
+ "config-install-user-create-failed": "Das Oonlehn von der Datebankbenutzers „$1“ ist gescheitert: $2",
+ "config-install-user-grant-failed": "Die Gewährung von der Berechtigung für Datebankbenutzer „$1“ ist gescheitert: $2",
+ "config-install-user-missing": "Der oongebne Benutzer „$1“ ist net voarhand.",
+ "config-install-user-missing-create": "Der oongebne Benutzer „$1“ ist net voarhanden.\nBitte das Auswählkästche „Benutzerkonto erstelle“ oonklicke, soweit der erstellt werre soll.",
+ "config-install-tables": "Datetabellen werre erstellt",
+ "config-install-tables-exist": "'''Warnung:''' Es woare MediaWiki-Datetabelle gefund.\nDie Erstellung woor üwersprung.",
+ "config-install-tables-failed": "'''Fehler:''' Die Erstellung von der Datetabelle ist (weche) uffgrund von der follichende Fehler gescheitert: $1",
+ "config-install-interwiki": "Interwikitabelle werre ingerichtet",
+ "config-install-interwiki-list": "Die Datei <code>interwiki.list</code> konnt net geles sin.",
+ "config-install-interwiki-exists": "'''Warnung:''' Es woare Interwikitabelle mit Date gefund.\nDie Standardliste weard üwersprung.",
+ "config-install-stats": "Statistike werre initialisiert",
+ "config-install-keys": "Geheimschlüssel werre erstellt",
+ "config-insecure-keys": "'''Warnung:''' {{PLURAL:$2|Der Geheimschlüssel|Die Geheimschlüssel}} $1, {{PLURAL:$2|der|die}} (während) im Verloof von der Installationsvoargang generiert {{PLURAL:$2|woard, ist|woare, sind}} net seahr sicher. {{PLURAL:$2|Er sollt|Sie sollte}} manuell geännert sin.",
+ "config-install-sysop": "Administratorkonto weard erstellt",
+ "config-install-subscribe-fail": "Abonniere von „mediawiki-announce“ ist gescheitert: $1",
+ "config-install-subscribe-notpossible": "cURL ist net installiert und <code>allow_url_fopen</code> ist niet verfüchbar.",
+ "config-install-mainpage": "Erstellung von der Hauptseit mit Standardinhalte (padronisierte Inhalte)",
+ "config-install-extension-tables": "Erstellung von der Tabelle für die aktivierte Erweitrunge",
+ "config-install-mainpage-failed": "Die Hauptseite konnt net erstellt sin: $1",
+ "config-install-done": "'''Herzliche Glückwunsch!'''\nMediaWiki woard erfollichreich installiert.\n\nDas Installationsprogramm hot die Datei <code>LocalSettings.php</code> erzeicht.\nSie enthält all voargenommne Konfigurationsinstellunge.\n\nDie Datei muss jetzt herunnergelood und oonschliessend in das Stammverzeichnis von der MediaWiki-Installation hochgelood sin. Das ist dasselwe Verzeichnis, in dem sich ooch die Datei <code>index.php</code> befinnt. Das Herunnerloode sollt inzwische automatisch oongefang sin.\n\nSoweit das net der Fall woar, orrer das Herunnerloode unnerbroch (interrompiert) woard, kann der Voargang doorrich en Klick uff den follichend Link erneit oongefang sinn:\n\n$3\n\n'''Hinweis:''' Die Konfigurationsdatei sollt jetzt unbedingt herunnergelood sin. Sie weard noh Beende von der Installationsprogramms, nemehr zur Verfüchung stehn (net mehr disponivel bleiwe).\n\nSobald alles erledicht woard, kann uff das '''[$2 Wiki zugegriffe werre]'''. Mir wünsche viel Spass und Erfollich mit dem Wiki.",
+ "config-download-localsettings": "<code>LocalSettings.php</code> herunnterlade",
+ "config-help": "Hellef",
+ "config-nofile": "Die Datei „$1“ konnt net gefund sin. Woor sie gelöscht?",
+ "config-extension-link": "Wusst du, dass dein Wiki die Nutzung von [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions Erweiterungen] unnerstützt?\n\nDu kannst [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Erweitrunge noh Kategorie] doorrichsuche orrer die [//www.mediawiki.org/wiki/Extension_Matrix Matrix der Erweiterungen] oonsiehn, um en Üwersicht zu verfüchbare Erweitrunge zu erhalten.",
+ "mainpagetext": "'''MediaWiki woor erfollichreich installiert.'''",
+ "mainpagedocfooter": "Hellef zur Benutzung und Konfiguration von der Wiki-Software finnst du im [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].\n\n== Oonfänghellef ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings List von der Konfigurationsvariable]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglist neier MediaWiki-Versione]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokalisier MediaWiki für dein Sproch]"
+}
diff --git a/includes/installer/i18n/hsb.json b/includes/installer/i18n/hsb.json
new file mode 100644
index 00000000..deae95b6
--- /dev/null
+++ b/includes/installer/i18n/hsb.json
@@ -0,0 +1,256 @@
+{
+ "@metadata": {
+ "authors": [
+ "Michawiki",
+ "아라"
+ ]
+ },
+ "config-desc": "Instalaciski program za MediaWiki",
+ "config-title": "Instalacija MediaWiki $1",
+ "config-information": "Informacije",
+ "config-localsettings-upgrade": "Dataja <code>LocalSettings.php</code> je so wotkryła.\nZo by tutu instalaciju aktualizował, zapodaj prošu hódnotu za parameter <code>$wgUpgradeKey</code> do slědowaceho pola.\nNamakaš tón parameter w dataji <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Dataja <code>LocalSettings.php</code> bu wotkryta.\nZo by tutu instalaciju aktualizował, wuwjedźće <code>update.php</code>",
+ "config-localsettings-key": "Aktualizaciski kluč:",
+ "config-localsettings-badkey": "Kluč, kotryž sy podał, je wopak",
+ "config-upgrade-key-missing": "Eksistowaca instalacija MediaWiki je so wotkryła.\nZo by tutu instalaciju aktualizował, staj prošu slědowacu linku deleka w dataji <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Zda so, zo eksistwoaca dataja <code>LocalSettings.php</code> je njedospołna.\nWariabla $1 njeje nastajena.\nProšu změń dataju <code>LocalSettings.php</code>, zo by so tuta wariabla nastajiła a klikń na \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Při zwjazowanju z datowej banku z pomocu nastajenjow podatych w <code>LocalSettings.php</code> je zmylk wustupił. Prošu skoriguj tute nastajenja a spytaj hišće raz.\n\n$1",
+ "config-session-error": "Zmylk při startowanju posedźenja: $1",
+ "config-session-expired": "Zda so, zo twoje posedźenske daty su spadnjene.\nPosedźenja su za čas žiwjenja $1 skonfigurowane.\nMóžeš jón přez nastajenje <code>session.gc_maxlifetime</code> w php.ini powyšić.\nStartuj instalaciski proces znowa.",
+ "config-no-session": "Twoje posedźenske daty su so zhubili!\nSkontroluj swój php.ini a zawěsć, zo <code>session.save_path</code> je na prawy zapis nastajeny.",
+ "config-your-language": "Twoja rěč:",
+ "config-your-language-help": "Wubjer rěč, kotraž ma so za instalaciski proces wužiwać.",
+ "config-wiki-language": "Wikirěč:",
+ "config-wiki-language-help": "Wubjer rěč, w kotrejž wiki ma so zwjetša pisać.",
+ "config-back": "← Wróćo",
+ "config-continue": "Dale →",
+ "config-page-language": "Rěč",
+ "config-page-welcome": "Witaj do MediaWiki!",
+ "config-page-dbconnect": "Z datowej banku zwjazać",
+ "config-page-upgrade": "Eksistowacu instalaciju aktualizować",
+ "config-page-dbsettings": "Nastajenja datoweje banki",
+ "config-page-name": "Mjeno",
+ "config-page-options": "Opcije",
+ "config-page-install": "Instalować",
+ "config-page-complete": "Dokónčeny!",
+ "config-page-restart": "Instalaciju znowa startować",
+ "config-page-readme": "Čitaj mje",
+ "config-page-releasenotes": "Wersijowe informacije",
+ "config-page-copying": "Kopěrowanje",
+ "config-page-upgradedoc": "Aktualizowanje",
+ "config-page-existingwiki": "Eksistowacy wiki",
+ "config-help-restart": "Chceš wšě składowane daty hašeć, kotrež sy zapodał a instalaciski proces znowa startować?",
+ "config-restart": "Haj, znowa startować",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki Startowa strona MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Nawod za wužiwarjow]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Nawod za administratorow]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Huste prašenja]\n----\n* <doclink href=Readme>Čitaj mje</doclink>\n* <doclink href=ReleaseNotes>Wersijowe informacije</doclink>\n* <doclink href=Copying>Licencne postajenja</doclink>\n* <doclink href=UpgradeDoc>Aktualizacija</doclink>",
+ "config-env-good": "Wokolina je so skontrolowała.\nMóžeš MediaWiki instalować.",
+ "config-env-bad": "Wokolina je so skontrolowała.\nNjemóžeš MediaWiki instalować.",
+ "config-env-php": "PHP $1 je instalowany.",
+ "config-env-php-toolow": "PHP $1 je instalowany.\nAle MediaWiki wužaduje sej PHP $2 abo wyši.",
+ "config-unicode-using-utf8": "Za normalizaciju Unicode so utf8_normalize.so Briona Vibbera wužiwa.",
+ "config-unicode-using-intl": "Za normalizaciju Unicode so [http://pecl.php.net/intl PECL-rozšěrjenje intl] wužiwa.",
+ "config-no-db": "Njeda so přihódny ćěrjak datoweje banki namakać! Dyrbiš ćěrjak datoweje banki za PHP instalować.\nSlědowace typy datoweje banki so podpěruja: $1.\n\nJeli sy PHP sam kompilował, konfiguruj jón znowa z aktiwizowanym programom datoweje banki, na přikład z pomocu <code>./configure --with-mysqli</code>.\nJeli sy PHP z Debianoweho abo Ubuntuoweho paketa instalował, dyrbiš tež paket <code>php5-mysql</code> instalować.",
+ "config-outdated-sqlite": "'''Warnowanje''': maš SQLite $1, kotryž je starši hač minimalna trěbna wersija $2. SQLite njebudźe k dispoziciji stać.",
+ "config-no-fts3": "'''Warnowanje''': SQLite je so bjez [//sqlite.org/fts3.html FTS3-modula] kompilował, pytanske funkcije njebudu k dispoziciji stać.",
+ "config-register-globals": "'''Warnowanje: Funkcija <code>[http://php.net/register_globals register_globals]</code> PHP je zmóžnjena.'''\n'''Znjemóžń ju, jeli móžeš.'''\nMediaWiki budźe fungować, ale twój serwer je potencielnym wěstotnym njedostatkam wustajeny.",
+ "config-safe-mode": "'''Warnowanje:''' [http://www.php.net/features.safe-mode wěsty modus] PHP je aktiwny.\nTo móže problemy zawinować, předewšěm, jeli so datajowe nahraća a podpěra <code>math</code> wužiwaja.",
+ "config-xml-bad": "XML-modul za PHP faluje.\nMediaWiki trjeba funkcije w tutym modulu a njebudźe w tutej konfiguraciji fungować.\nJeli wužiwaš Mandrake, instaluj paket php-xml.",
+ "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-ctype": "'''Ćežki zmylk''': PHP dyrbi so z podpěru za [http://www.php.net/manual/en/ctype.installation.php rozšěrjenje Ctype] kompilować.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] je instalowany",
+ "config-apc": "[http://www.php.net/apc APC] je instalowany",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] je instalowany",
+ "config-diff3-bad": "GNU diff3 njenamakany.",
+ "config-no-uri": "'''Zmylk:''' Aktualny URI njeda so postajić.\nInstalacija bu přetorhnjena.",
+ "config-no-cli-uri": "'''Warnowanje''': Žana skriptowa šćežka (<code>--scriptpath</code>) podata, standard so wužiwa: <code>$1</code>.",
+ "config-using-server": "Serwerowe mjeno \"<nowiki>$1</nowiki>\" so wužiwa.",
+ "config-using-uri": "Serwerowy URL \"<nowiki>$1$2</nowiki>\" so wužiwa.",
+ "config-db-type": "Typ datoweje banki:",
+ "config-db-host": "Serwer datoweje banki:",
+ "config-db-host-oracle": "Datowa banka TNS:",
+ "config-db-wiki-settings": "Tutón wiki identifikować",
+ "config-db-name": "Mjeno datoweje banki:",
+ "config-db-name-oracle": "Šema datoweje banki:",
+ "config-db-install-account": "Wužiwarske konto za instalaciju",
+ "config-db-username": "Wužiwarske mjeno datoweje banki:",
+ "config-db-password": "Hesło datoweje banki:",
+ "config-db-password-empty": "Prošu zapodaj hesło za noweho wužiwarja datoweje banki: $1.\nByrnjež było móžno wužiwarjow bjez hesłow wutworić, njeje to wěste.",
+ "config-db-install-username": "Zapodaj wužiwarske mjeno, kotrež budźe so za zwisk z datowej banku za instalaciski proces wužiwać.\nTo njeje wužiwarske mjeno konta MediaWiki; to je wužiwarske mjeno za twoju datowu banku.",
+ "config-db-install-password": "Zapodaj hesło, kotrež budźe so za zwisk z datowej banku za instalaciski proces wužiwać.\nTo njeje hesło konta MediaWiki; to je hesło za twoju datowu banku.",
+ "config-db-install-help": "Zapodaj wužiwarske mjeno a hesło, kotrejž měłoj so za zwisk z datowej banku za instalaciski proces wužiwać.",
+ "config-db-account-lock": "Samsne wužiwarske mjeno a hesło za normalnu operaciju wužiwać",
+ "config-db-wiki-account": "Wužiwarske konto za normalnu operaciju",
+ "config-db-prefix": "Tabelowy prefiks datoweje banki:",
+ "config-db-charset": "Znamješkowa sadźba datoweje banki",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binarny",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 wróćokompatibelny UTF-8",
+ "config-mysql-old": "MySQL $1 abo nowši trěbny, maš $2.",
+ "config-db-port": "Port datoweje banki:",
+ "config-db-schema": "Šema za MediaWiki",
+ "config-db-schema-help": "Tuta šema da so zwjetša derje wužiwać.\nZměń ju jenož, jeli su přeswědčiwe přičiny za to.",
+ "config-pg-test-error": "Zwisk z datowej banku '''$1''' móžno njeje: $2",
+ "config-sqlite-dir": "Zapis SQLite-datow:",
+ "config-oracle-def-ts": "Standardny tabelowy rum:",
+ "config-oracle-temp-ts": "Nachwilny tabelowy rum:",
+ "config-type-mysql": "MySQL (abo kompatibelny)",
+ "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. ([http://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ć. ([http://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",
+ "config-header-sqlite": "Nastajenja SQLite",
+ "config-header-oracle": "Nastajenja Oracle",
+ "config-invalid-db-type": "Njepłaćiwy typ datoweje banki",
+ "config-missing-db-name": "Dyrbiš hódnotu za \"Mjeno datoweje banki\" zapodać",
+ "config-missing-db-host": "Dyrbiš hódnotu za \"Database host\" zapodać",
+ "config-missing-db-server-oracle": "Dyrbiš hódnotu za \"Database TNS\" zapodać",
+ "config-invalid-db-server-oracle": "Njepłaćiwa datowa banka TNS \"$1\".\nWužij pak \"TNS Name\" pak znamješkowy rjećazk \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle - pomjenowanske metody])",
+ "config-invalid-db-name": "Njepłaćiwe mjeno \"$1\" datoweje banki.\nWužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9),a podsmužki (_) a wjazawki (-).",
+ "config-invalid-db-prefix": "Njepłaćiwy prefiks \"$1\" datoweje banki.\nWužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9), podsmužki (_) a wjazawki (-).",
+ "config-connection-error": "$1.\n\nSkontroluj serwer, wužiwarske a hesło a spytaj hišće raz.",
+ "config-invalid-schema": "Njepłaćiwe šema za MediaWiki \"$1\".\nWužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9) a podsmužki (_).",
+ "config-db-sys-create-oracle": "Instalaciski program podpěruje jenož wužiwanje SYSDBA-konta za zakoženje noweho konta.",
+ "config-db-sys-user-exists-oracle": "Wužiwarske konto \"$1\" hižo eksistuje. SYSDBA hodźi so jenož za załoženje noweho konta wužiwać!",
+ "config-postgres-old": "PostgreSQL $1 abo nowši trěbny, maš $2.",
+ "config-sqlite-name-help": "Wubjer mjeno, kotrež twój wiki identifikuje.\nNjewužij mjezery abo wjazawki.\nTo budźe so za mjeno dataje SQLite-datow wužiwać.",
+ "config-sqlite-mkdir-error": "Zmylk při wutworjenju datoweho zapisa \"$1\".\nSkontroluj městno a spytaj hišće raz.",
+ "config-sqlite-dir-unwritable": "Njeje móžno do zapisa \"$1\" pisać.\nZměń jeho prawa, tak zo webserwer móže do njeho pisać a spytaj hišće raz.",
+ "config-sqlite-connection-error": "$1.\n\nSkontroluj datowy zapis a mjeno datoweje banki kaj spytaj hišće raz.",
+ "config-sqlite-readonly": "Do dataje <code>$1</code> njeda so pisać.",
+ "config-sqlite-cant-create-db": "Dataja <code>$1</code> datoweje banki njeda so wutworić.",
+ "config-sqlite-fts3-downgrade": "PHP wo podpěrje FTS3 k dispoziciji njesteji, table so znižuja",
+ "config-can-upgrade": "Su tabele MediaWiki w tutej datowej bance.\nZo by je na MediaWiki $1 aktualizował, klikń na '''Dale'''.",
+ "config-upgrade-done-no-regenerate": "Aktualizacija dokónčena.\n\nMóžeš nětko [$1 swój wiki wužiwać].",
+ "config-regenerate": "LocalSettings.php znowa wutworić →",
+ "config-show-table-status": "Naprašowanje <code>SHOW TABLE STATUS</code> je so njeporadźiło!",
+ "config-unknown-collation": "'''Warnowanje:''' Datowa banka njeznatu kolaciju wužiwa.",
+ "config-db-web-account": "Konto datoweje banki za webpřistup",
+ "config-db-web-help": "wubjer wužiwarske mjeno a hesło, kotrejž webserwer budźe wužiwać, zo by z serwerom datoweje banki za wšědnu operaciju zwjazać",
+ "config-db-web-account-same": "Samsne konto kaž za instalaciju wužiwać",
+ "config-db-web-create": "Załož konto, jeli hišće njeeksistuje.",
+ "config-db-web-no-create-privs": "Konto, kotrež sy za instalaciju podał, nima dosć woprawnjenjow, zo by konto wutworiło.\nKonto, kotrež tu podawaće, dyrbi hižo eksistować.",
+ "config-mysql-engine": "Składowanska mašina:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Znamješkowa sadźba datoweje banki:",
+ "config-mysql-binary": "Binarny",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Mjeno wikija:",
+ "config-site-name-help": "To zjewi so w titulowej lejstwje wobhladaka kaž tež na wšelakich druhich městnach.",
+ "config-site-name-blank": "Zapodaj sydłowe mjeno.",
+ "config-project-namespace": "Mjenowy rum projekta:",
+ "config-ns-generic": "Projekt",
+ "config-ns-site-name": "Samsne kaž wikimjeno: $1",
+ "config-ns-other": "Druhe (podać)",
+ "config-ns-other-default": "MyWiki",
+ "config-ns-invalid": "Podaty mjenowy rum \"<nowiki>$1</nowiki>\" je njepłaćiwy.\nPodaj druhi projektowy mjenowy rum.",
+ "config-ns-conflict": "Podaty mjenowy rum \"<nowiki>$1</nowiki>\" je w konflikće ze standardnym mjenjowym rumom MediaWiki.\nPodaj druhi projektowy mjenowy rum.",
+ "config-admin-box": "Administratorowe konto",
+ "config-admin-name": "Twoje wužiwarske mjeno:",
+ "config-admin-password": "Hesło:",
+ "config-admin-password-confirm": "Hesło wospjetować:",
+ "config-admin-help": "Zapodaj swoje preferowane wužiwarske mjeno, na přikład \"Jurij Serb\".\nTo je mjeno, kotrež budźeš wužiwać, zo by so do wikija přizjewił.",
+ "config-admin-name-blank": "Zapodaj administratorowe wužiwarske mjeno.",
+ "config-admin-name-invalid": "Podate wužiwarske mjeno \"<nowiki>$1</nowiki>\" je njepłaćiwe.\nPodaj druhe wužiwarske mjeno.",
+ "config-admin-password-blank": "Zapodaj hesło za administratorowe konto.",
+ "config-admin-password-mismatch": "Wobě hesle, kotrejž sy zapodał, njejstej jenakej.",
+ "config-admin-email": "E-mejlowa adresa:",
+ "config-admin-email-help": "Zapodaj tu e-mejlowu adresu, zo by přijimanje e-mejlow wot druhich wužiwarjow w tutym wikiju zmóžnił, swoje hesło wróćo stajił a zdźělenki wo změnach na swojich wobkedźbowanych stronach dostał. Móžeš polo prózdne wostajić.",
+ "config-admin-error-user": "Interny zmylk při wutworjenju administratora z mjenom \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Interny zmylk při nastajenju hesła za administratora \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Sy njepłaćiwu e-mejlowu adresu zapodał.",
+ "config-subscribe": "[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rozesyłansku lisćinu wo připowědźenjach nowych wersijow ].abonować",
+ "config-almost-done": "Sy skoro hotowy!\nMóžeš nětko zbytnu konfiguraciju přeskočić a wiki hnydom instalować.",
+ "config-optional-continue": "Dalše prašenja?",
+ "config-optional-skip": "Instaluj nětko wiki.",
+ "config-profile": "Profil wužiwarskich prawow:",
+ "config-profile-wiki": "Zjawny wiki",
+ "config-profile-no-anon": "Załoženje konto je trěbne",
+ "config-profile-fishbowl": "Jenož awtorizowani wobdźěłarjo",
+ "config-profile-private": "Priwatny wiki",
+ "config-license": "Awtorske prawo a licenca:",
+ "config-license-none": "Žane licencne podaća w nohowej lince",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (zjawnosći přistupny)",
+ "config-license-gfdl": "GNU-licenca za swobodnu dokumentaciju 1.3 abo nowša",
+ "config-license-pd": "Powšitkownosći přistupny",
+ "config-license-cc-choose": "Swójsku licencu Creative Commons wubrać",
+ "config-email-settings": "E-mejlowe nastajenja",
+ "config-enable-email": "Wuchadźace e-mejlki zmóžnić",
+ "config-enable-email-help": "Jeli chceš e-mejl wužiwać, dyrbja so [http://www.php.net/manual/en/mail.configuration.php e-mejlowe nastajenja PHP] prawje konfigurować.\nJeli nochceš e-mejlowe funkcije wužiwać, móžeš je tu znjemóžnić.",
+ "config-email-user": "E-mejl mjez wužiwarjemi zmóžnić",
+ "config-email-user-help": "Wšěm wužiwarjam dowolić, jednomu druhemu e-mejlki pósłać, jeli su tutu funkciju w swojich nastajenjach zmóžnili.",
+ "config-email-usertalk": "Zdźělenja za wužiwarske diskusijne strony zmóžnić",
+ "config-email-usertalk-help": "Wužiwarjam dowolić zdźělenki wo změnach na wužiwarskich diskusijnych stronach dóstać, jeli woni su to w swojich nastajenjach zmóžnili.",
+ "config-email-watchlist": "Zdźělenja za wobkedźbowanki zmóžnić",
+ "config-email-watchlist-help": "Wužiwarjam dowolić zdźělenki wo jich wobked´bowanych stronach dóstać, jeli woni su to w swojich nastajenjach zmóžnili.",
+ "config-email-auth": "E-mejlowu awtentifikaciju zmóžnić",
+ "config-email-sender": "E-mejlowa adresa za wotmołwy:",
+ "config-upload-settings": "Wobrazy a nahraća datajow",
+ "config-upload-enable": "Nahraće datajow zmóžnić",
+ "config-upload-deleted": "Zapis za zhašane dataje:",
+ "config-upload-deleted-help": "Wubjer zapis, w kotrymž zhašene dataje maja so archiwować.\nIdealnje tón njeměł z weba přistupny być.",
+ "config-logo": "URL loga:",
+ "config-instantcommons": "Instant commons zmóžnić",
+ "config-cc-error": "Pytanje za licencu Creative Commons njeje žadyn wuslědk přinjesło.\nZapodaj licencne mjeno manuelnje.",
+ "config-cc-again": "Zaso wubrać...",
+ "config-cc-not-chosen": "Wubjer licencu Creative Commons a klikń na \"dale\".",
+ "config-advanced-settings": "Rozšěrjena konfiguraćija",
+ "config-cache-options": "Nastajenja za objektowe pufrowanje:",
+ "config-cache-none": "Žane pufrowanje (žana funkcionalnosć so njewotstronja, ale spěšnosć móže so na wjetšich wikijowych sydłach wobwliwować)",
+ "config-cache-accel": "Objektowe pufrowanje PHP (APC, XCache abo WinCache)",
+ "config-cache-memcached": "Memcached wužiwać (wužaduje sej přidatnu instalaciju a konfiguraciju)",
+ "config-memcached-servers": "Serwery memcached:",
+ "config-memcached-help": "Lisćina IP-adresow, kotrež maja so za Memcached wužiwać.\nKóžda linka měła jenož jednu IP-adresu a port, kotryž ma so wužiwać, wobsahować. Na přikład:\n127.0.0.1:11211\n192.168.1.25:1234",
+ "config-memcache-needservers": "Sy Memcached jako swój pufrowakowy typ wubrał, ale njejsy žane serwery podał",
+ "config-memcache-badip": "Sy njepłaćiwu IP-adresu za Memcached zapodał: $1",
+ "config-memcache-noport": "Njejsy žadyn port za wužiwanje serwera Memcached podał: $1.\nJeli port njewěš, standard je 11211.",
+ "config-memcache-badport": "Portowe čisła za Memcached měli mjez $1 a $2 być",
+ "config-extensions": "Rozšěrjenja",
+ "config-extensions-help": "Rozšěrjenja podate horjeka buchu w twojim zapisu <code>./extensions</code> namakane.\n\nTo móže sej přidatnu konfiguraciju wužadać, ale móžeš je nětko zmóžnić.",
+ "config-install-alreadydone": "'''Warnowanje:''' Zda so, zo sy hižo MediaWiki instalował a pospytuješ jón znowa instalować.\nProšu pokročuj z přichodnej stronu.",
+ "config-install-begin": "Přez kliknjenje na \"{{int:config-continue}}\" budźe so instalacija MediaWiki startować.\nJeli hišće chceš něšto změnić, klikń na \"{{int:config-back}}\".",
+ "config-install-step-done": "dokónčene",
+ "config-install-step-failed": "njeporadźiło",
+ "config-install-extensions": "Inkluziwnje rozšěrjenja",
+ "config-install-database": "Datowa banka so připrawja",
+ "config-install-schema": "Datowa šema so twori",
+ "config-install-pg-schema-not-exist": "Šema PostgreSQL njeeksistuje",
+ "config-install-pg-schema-failed": "Wutworjenje tabelow je so njeporadźiło.\nZawěsć, zo wužiwar \"$1\" móže do šemy \"$2\" pisać.",
+ "config-install-pg-commit": "Změny so wotesyłaja",
+ "config-install-pg-plpgsql": "Pruwowanje za rěču PL/pgSQL",
+ "config-pg-no-plpgsql": "Dyrbiš rěč PL/pgSQL w datowej bance $1 instalować",
+ "config-pg-no-create-privs": "Konto, kotrež sy za instalaciju podał, nima dosahace prawa za wutworjenje konta.",
+ "config-install-user": "Tworjenje wužiwarja datoweje banki",
+ "config-install-user-alreadyexists": "Wužiwar \"$1\" hižo eksistuje",
+ "config-install-user-create-failed": "Wutworjenje wužiwarja \"$1\" je so njeporadźiło: $2",
+ "config-install-user-grant-failed": "Prawo njeda so wužiwarjej \"$1\" dać: $2",
+ "config-install-user-missing": "Podaty wužiwar \"$1\" njeeksistuje.",
+ "config-install-user-missing-create": "Podaty wužiwar \"$1\" njeeksistuje.\nProšu klikń na slědowacy kontrolny kašćik \"konto załožić\", jeli chceš jo wutworić.",
+ "config-install-tables": "Tworjenje tabelow",
+ "config-install-tables-exist": "'''Warnowanje''': Zda so, zo tabele MediaWiki hižo eksistuja.\nWutworjenje so přeskakuje.",
+ "config-install-tables-failed": "'''Zmylk''': Wutworjenje tabele je so slědowaceho zmylka dla njeporadźiło: $1",
+ "config-install-interwiki": "Standardna tabela interwikijow so pjelni",
+ "config-install-interwiki-list": "<code>interwiki.list</code> njeda so namakać.",
+ "config-install-interwiki-exists": "'''Warnowanje''': Zda so, zo tabela interwikjow hižo zapiski wobsahuje.\nStandardna lisćina sp přeskakuje.",
+ "config-install-stats": "Statistika so inicializuje",
+ "config-install-keys": "Tajne kluče so tworja",
+ "config-install-sysop": "Tworjenje administratoroweho wužiwarskeho konta",
+ "config-install-subscribe-fail": "Abonowanje \"mediawiki-announce\" njemóžno: $1",
+ "config-install-subscribe-notpossible": "cURL njeje instalowany a <code>allow_url_fopen</code> k dispoziciji njesteji.",
+ "config-install-mainpage": "Hłowna strona so ze standardnym wobsahom wutworja",
+ "config-install-extension-tables": "Tabele za zmóžnjene rozšěrjenja so tworja",
+ "config-install-mainpage-failed": "Powěsć njeda so zasunyć: $1",
+ "config-download-localsettings": "<code>LocalSettings.php</code> sćahnyć",
+ "config-help": "pomoc",
+ "config-nofile": "Dataja \"$1\" njeje so namakała. Je so zhašała?",
+ "mainpagetext": "'''MediaWiki bu wuspěšnje instalowany.'''",
+ "mainpagedocfooter": "Prošu hlej [//meta.wikimedia.org/wiki/Help:Contents dokumentaciju] za informacije wo wužiwanju softwary.\n\n== Za nowačkow ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Wo nastajenjach]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki za twoju rěč lokalizować]"
+}
diff --git a/includes/installer/i18n/ht.json b/includes/installer/i18n/ht.json
new file mode 100644
index 00000000..f56f0ad5
--- /dev/null
+++ b/includes/installer/i18n/ht.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Boukman",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MedyaWiki byen enstale l.'''",
+ "mainpagedocfooter": "Konsilte [//meta.wikimedia.org/wiki/Help:Contents Gid Itilizatè] pou enfòmasyon sou kijan pou w itilize logisyèl wiki a.\n\n== Kijan pou kòmanse ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lis paramèt yo pou konfigirasyon]\n* [//www.mediawiki.org/wiki/Manyèl:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis diskisyon ki parèt sou MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/hu-formal.json b/includes/installer/i18n/hu-formal.json
new file mode 100644
index 00000000..88779b17
--- /dev/null
+++ b/includes/installer/i18n/hu-formal.json
@@ -0,0 +1,33 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dani",
+ "Glanthor Reviol",
+ "Tacsipacsi"
+ ]
+ },
+ "config-localsettings-upgrade": "Már létezik a <code>LocalSettings.php</code> fájl.\nA telepített szoftver frissítéséhez írja be az alábbi mezőbe a <code>$wgUpgradeKey</code> beállítás értékét, melyet a <code>LocalSettings.php</code> nevű fájlban találhat meg.",
+ "config-session-expired": "Úgy tűnik, hogy a munkamenetadatok lejártak.\nA munkamenetek élettartama a következőre van beállítva: $1.\nAz érték növelhető a php.ini <code>session.gc_maxlifetime</code> beállításának módosításával.\nIndítsa újra a telepítési folyamatot.",
+ "config-no-session": "Elvesztek a munkamenetadatok!\nEllenőrizze, hogy a php.ini-ben a <code>session.save_path</code> beállítás a megfelelő könyvtárra mutat-e.",
+ "config-your-language-help": "Válassza ki a telepítési folyamat során használandó nyelvet.",
+ "config-wiki-language-help": "Az a nyelv, amin a wiki tartalmának legnagyobb része íródik.",
+ "config-page-welcome": "Üdvözli a MediaWiki!",
+ "config-help-restart": "Szeretné törölni az eddig megadott összes adatot és újraindítani a telepítési folyamatot?",
+ "config-welcome": "=== Környezet ellenőrzése ===\nAlapvető ellenőrzés, ami megmondja, hogy a környezet alkalmas-e a MediaWiki számára.\nHa probléma merülne fel a telepítés során, meg kell adnia mások számára az alább megjelenő információkat.",
+ "config-unicode-pure-php-warning": "<strong>Figyelmeztetés:</strong> Az [http://pecl.php.net/intl intl PECL kiterjesztés] nem érhető el Unicode normalizáláshoz, helyette a lassú, PHP alapú implementáció lesz használva.\nHa nagy látogatottságú oldalt üzemeltet, itt találhat információkat [http://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations a témáról].",
+ "config-register-globals": "'''Figyelmeztetés: A PHP <code>[http://php.net/register_globals register_globals]</code> beállítása engedélyezve van.'''\n'''Tiltsa le, ha van rá lehetősége.'''\nA MediaWiki működőképes a beállítás használata mellett, de a szerver biztonsági kockázatnak lesz kitéve.",
+ "config-imagemagick": "Az ImageMagick megtalálható a rendszeren: <code>$1</code>.\nA bélyegképek készítése engedélyezve lesz, ha engedélyezi a feltöltéseket.",
+ "config-db-name-help": "Válassza ki a wikije azonosítására használt nevet.\nNem tartalmazhat szóközt.\n\nHa megosztott webtárhelyet használ, a szolgáltatója vagy egy konkrét adatbázisnevet ad önnek használatra, vagy létrehozhat egyet a vezérlőpulton keresztül.",
+ "config-db-install-help": "Adja meg a felhasználónevet és jelszót, amivel a telepítő csatlakozhat az adatbázishoz.",
+ "config-db-wiki-help": "Adja meg azt a felhasználónevet és jelszót, amivel a wiki fog csatlakozni az adatbázishoz működés közben.\nHa a fiók nem létezik és a telepítést végző fiók rendelkezik megfelelő jogosultsággal, egy új fiók készül a megadott a névvel, azon minimális jogosultságkörrel, ami a wiki működéséhez szükséges.",
+ "config-charset-help": "'''Figyelmezetés:''' Ha a '''visszafelé kompatibilis UTF-8''' beállítást használja MySQL 4.1 vagy újabb verziók esetén, és utána a <code>mysqldump</code> programmal készít róla biztonsági másolatot, az tönkreteheti az összes nem ASCII-karaktert, visszafordíthatatlanul károsítva a másolatokban tárolt adatokat!\n\n'''Bináris''' módban a MediaWiki az UTF-8-ban kódolt szöveget bináris mezőkben tárolja az adatbázisban.\nEz sokkal hatékonyabb a MySQL UTF-8-módjától, és lehetővé teszi, hogy a teljes Unicode-karakterkészletet használja.\n'''UTF-8-módban''' MySQL tudja, hogy milyen karakterkészlettel van kódolva az adat, megfelelően van megjelenítve és konvertálva, de\nnem használhatja a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] feletti karaktereket.",
+ "config-db-schema-help": "A fenti sémák általában megfelelőek.\nCsak akkor módosítson rajta, ha szükség van rá.",
+ "config-sqlite-parent-unwritable-nogroup": "Nem lehet létrehozni az adatok tárolásához szükséges <code><nowiki>$1</nowiki></code> könyvtárat, mert a webszerver nem írhat a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>).\n\nA telepítő nem tudta megállapíteni, hogy melyik felhasználói fiókon fut a webszerver.\nA folytatáshoz tegye írhatóvá ezen fiók (és más fiókok!) számára a következő könyvtárat: <code><nowiki>$3</nowiki></code>.\nUnix/Linux rendszereken tedd a következőt:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-ns-other": "Más (adja meg)",
+ "config-admin-name-blank": "Adja meg az adminisztrátor felhasználónevét!",
+ "config-admin-name-invalid": "A megadott felhasználónév (<nowiki>$1</nowiki>) érvénytelen.\nAdjon meg egy másik felhasználónevet.",
+ "config-admin-password-blank": "Adja meg az adminisztrátori fiók jelszavát!",
+ "config-instantcommons-help": "Az [//www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [//commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.\nA használatához a MediaWikinek internethozzáférésre van szüksége.\n\nA funkcióról és hogy hogyan állítható be más wikik esetén [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos a kézikönyvben] találhat további információkat.",
+ "config-install-done": "'''Gratulálunk!'''\nSikeresen telepítette a MediaWikit.\n\nA telepítő készített egy <code>LocalSettings.php</code> fájlt.\nEz tartalmazza az összes beállítást.\n\n[$1 Le kell töltenie], és el kell helyeznie a MediaWiki telepítési könyvtárába (az a könyvtár, ahol az index.php van).\n'''Megjegyzés''': Ha ezt most nem teszi meg, és kilép, a generált fájl nem lesz elérhető a későbbiekben.\n\nHa ezzel készen van, '''[$2 beléphet a wikibe]'''.",
+ "mainpagedocfooter": "Ha segítségre van szüksége a wikiszoftver használatához, akkor keresse fel a [//meta.wikimedia.org/wiki/Help:Contents User's Guide] oldalt.\n\n== Alapok (angol nyelven) ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Beállítások listája]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki GyIK]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources A MediaWiki fordítása a saját nyelvére]"
+}
diff --git a/includes/installer/i18n/hu.json b/includes/installer/i18n/hu.json
new file mode 100644
index 00000000..a150fbcd
--- /dev/null
+++ b/includes/installer/i18n/hu.json
@@ -0,0 +1,304 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dani",
+ "Glanthor Reviol",
+ "아라",
+ "Dj",
+ "Misibacsi",
+ "Tacsipacsi"
+ ]
+ },
+ "config-desc": "A MediaWiki telepítője",
+ "config-title": "A MediaWiki $1 telepítése",
+ "config-information": "Információ",
+ "config-localsettings-upgrade": "Már létezik a <code>LocalSettings.php</code> fájl.\nA telepített szoftver frissítéséhez írd be az alábbi mezőbe a <code>$wgUpgradeKey</code> beállítás értékét, melyet a <code>LocalSettings.php</code> nevű fájlban találhatsz meg.",
+ "config-localsettings-cli-upgrade": "A <code>LocalSettings.php</code> fájl megtalálható.\nA telepített rendszer frissítéséhez futtasd az <code>update.php</code>-t.",
+ "config-localsettings-key": "Frissítési kulcs:",
+ "config-localsettings-badkey": "A megadott kulcs érvénytelen.",
+ "config-upgrade-key-missing": "A telepítő a MediaWiki meglévő példányát észlelte.\nA telepített rendszer frissítéséhez helyezd el az alábbi sort a <code>LocalSettings.php</code> végére:\n\n$1",
+ "config-localsettings-incomplete": "A meglévő <code>LocalSettings.php</code> hiányosnak tűnik.\nA(z) $1 változó értéke nincs beállítva.\nMódosítsd a <code>LocalSettings.php</code> fájlt úgy, hogy ez a változó be legyen állítva, majd kattints a „{{int:Config-continue}}” gombra.",
+ "config-localsettings-connection-error": "Nem sikerült csatlakozni az adatbázishoz a <code>LocalSettings.php</code>-ben megadott adatokkal. Ellenőrizd a beállításokat, majd próbáld újra.\n\n$1",
+ "config-session-error": "Nem sikerült elindítani a munkamenetet: $1",
+ "config-session-expired": "Úgy tűnik, hogy a munkamenetadatok lejártak.\nA munkamenetek élettartama a következőre van beállítva: $1.\nAz érték növelhető a php.ini <code>session.gc_maxlifetime</code> beállításának módosításával.\nIndítsd újra a telepítési folyamatot.",
+ "config-no-session": "Elvesztek a munkamenetadatok!\nEllenőrizd, hogy a php.ini-ben a <code>session.save_path</code> a megfelelő könyvtárra mutat-e.",
+ "config-your-language": "Nyelv:",
+ "config-your-language-help": "A telepítési folyamat során használandó nyelv.",
+ "config-wiki-language": "A wiki nyelve:",
+ "config-wiki-language-help": "Az a nyelv, amin a wiki tartalmának legnagyobb része íródik.",
+ "config-back": "← Vissza",
+ "config-continue": "Folytatás →",
+ "config-page-language": "Nyelv",
+ "config-page-welcome": "Üdvözöl a MediaWiki!",
+ "config-page-dbconnect": "Kapcsolódás az adatbázishoz",
+ "config-page-upgrade": "Telepített változat frissítése",
+ "config-page-dbsettings": "Adatbázis-beállítások",
+ "config-page-name": "Név",
+ "config-page-options": "Beállítások",
+ "config-page-install": "Telepítés",
+ "config-page-complete": "Kész!",
+ "config-page-restart": "Telepítés újraindítása",
+ "config-page-readme": "Tudnivalók",
+ "config-page-releasenotes": "Kiadási megjegyzések",
+ "config-page-copying": "Másolás",
+ "config-page-upgradedoc": "Frissítés",
+ "config-page-existingwiki": "Létező wiki",
+ "config-help-restart": "Szeretnéd törölni az eddig megadott összes adatot és újraindítani a telepítési folyamatot?",
+ "config-restart": "Igen, újraindítás",
+ "config-welcome": "=== A környezet ellenőrzése ===\nNéhány alapvető ellenőrzés kerül végrehajtásra, hogy kiderüljön ,hogy ez a környezet alkalmas-e a MediaWiki telepítésére.\nHa telepítéssel kapcsolatos segítségre van szükséged, add meg ezen ellenőrzések eredményét.",
+ "config-copyright": "=== Licenc és feltételek ===\n\n$1\n\nEz a program szabad szoftver; terjeszthető illetve módosítható a Free Software Foundation által kiadott GNU General Public License dokumentumában leírtak; akár a licenc 2-es, akár (tetszőleges) későbbi változata szerint.\n\nEz a program abban a reményben kerül közreadásra, hogy hasznos lesz, de minden egyéb '''garancia nélkül''', az '''eladhatóságra''' vagy '''valamely célra való alkalmazhatóságra''' való származtatott garanciát is beleértve. További részleteket a GNU General Public License tartalmaz.\n\nA felhasználónak a programmal együtt meg kell kapnia a <doclink href=Copying>GNU General Public License egy példányát</doclink>; ha mégsem kapta meg, akkor írjon a Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. címre, vagy [http://www.gnu.org/copyleft/gpl.html tekintse meg online].",
+ "config-sidebar": "* [//www.mediawiki.org A MediaWiki honlapja]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Felhasználói kézikönyv]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Útmutató adminisztrátoroknak]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ GyIK]\n----\n* <doclink href=Readme>Ismertető</doclink>\n* <doclink href=ReleaseNotes>Kiadási megjegyzések</doclink>\n* <doclink href=Copying>Másolás</doclink>\n* <doclink href=UpgradeDoc>Frissítés</doclink>",
+ "config-env-good": "A környezet ellenőrzése befejeződött.\nA MediaWiki telepíthető.",
+ "config-env-bad": "A környezet ellenőrzése befejeződött.\nA MediaWiki nem telepíthető.",
+ "config-env-php": "A PHP verziója: $1",
+ "config-env-php-toolow": "PHP $1 van telepítve,\nazonban a MediaWikinek PHP $2, vagy újabb szükséges.",
+ "config-unicode-using-utf8": "A rendszer Unicode normalizálására Brion Vibber utf8_normalize.so könyvtárát használja.",
+ "config-unicode-using-intl": "A rendszer Unicode normalizálására az [http://pecl.php.net/intl intl PECL kiterjesztést] használja.",
+ "config-unicode-pure-php-warning": "'''Figyelmeztetés''': Az Unicode normalizáláshoz szükséges [http://pecl.php.net/intl intl PECL kiterjesztés] nem érhető el, helyette a lassú, PHP alapú implementáció lesz használva.\nHa nagy látogatottságú oldalt üzemeltetsz, itt találhatsz további információkat [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations a témáról].",
+ "config-unicode-update-warning": "'''Figyelmeztetés''': Az Unicode normalizáláshoz szükséges burkolókönyvtár [http://site.icu-project.org/ ICU projekt] függvénykönyvtárának régebbi változatát használja.\nHa ügyelni kívánsz a Unicode használatára, fontold meg a [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations frissítését].",
+ "config-no-db": "Nem sikerült egyetlen használható adatbázis-illesztőprogramot sem találni. Telepítened kell egyet a PHP-hez.\nA következő adatbázistípusok támogatottak: $1.\n\nHa a PHP-t magad fordítottad, konfiguráld újra úgy, hogy engedélyezve legyen egy adatbáziskliens, pl. a <code>./configure --with-mysql</code> parancs használatával.\nHa a PHP-t Debian vagy Ubuntu csomaggal telepítetted, akkor szükséged lesz a php5-mysql modulra is.",
+ "config-no-fts3": "'''Figyelmeztetés''': Az SQLite [//sqlite.org/fts3.html FTS3 modul] nélkül lett fordítva, a keresési funkciók nem fognak működni ezen a rendszeren.",
+ "config-register-globals": "'''Figyelmeztetés: A PHP <code>[http://php.net/register_globals register_globals]</code> beállítása engedélyezve van.'''\n'''Tiltsd le, ha van rá lehetőséged.'''\nA MediaWiki működőképes a beállítás használata mellett, de a szerver biztonsági kockázatnak lesz kitéve.",
+ "config-magic-quotes-runtime": "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] aktív!'''\nEz a beállítás kiszámíthatatlan károkat okoz a bevitt adatokban.\nA MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
+ "config-magic-quotes-sybase": "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] aktív!'''\nEz a beállítás kiszámíthatatlan károkat okoz a bevitt adatokban.\nA MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
+ "config-mbstring": "'''Kritikus hiba: az [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime mbstring.func_overload] aktív!'''\nEz a beállítás hibákat okoz és kiszámíthatatlanul károsíthatja bevitt adatokat.\nA MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
+ "config-safe-mode": "'''Figyelmeztetés:''' A PHP [http://www.php.net/features.safe-mode safe mode]-ja be van kapcsolva.\nProblémákat okozhat, különösen a fájlfeltöltéseknél és a <code>math</code>-támogatás használatánál.",
+ "config-xml-bad": "A PHP XML-modulja hiányzik.\nEgyes MediaWiki-funkciók, melyek ezt a modult igénylik, nem fognak működni ilyen beállítások mellett.\nHa Madrake-et futtatsz, telepítsd a php-xml csomagot.",
+ "config-pcre-old": "<strong>Kritikus hiba:</strong> PCRE $1 vagy későbbi szükséges.\nA Te PHP binárisod PCRE $2-vel lett linkelve.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE További információ].",
+ "config-pcre-no-utf8": "'''Kritikus hiba''': Ú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": "'''Figyelmeztetés:''' 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-ctype": "<strong>Kritikus hiba:</strong> A PHP-t [http://www.php.net/manual/en/ctype.installation.php Ctype kiterjesztés] támogatással kell fordítani.",
+ "config-xcache": "Az [http://xcache.lighttpd.net/ XCache] telepítve van",
+ "config-apc": "Az [http://www.php.net/apc APC] telepítve van",
+ "config-wincache": "A [http://www.iis.net/download/WinCacheForPhp WinCache] telepítve van",
+ "config-no-cache": "'''Figyelmeztetés:''' Nem található [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] és [http://www.iis.net/download/WinCacheForPhp WinCache] sem.\nObjektum-gyorsítótárazás nem lesz engedélyezve.",
+ "config-diff3-bad": "GNU diff3 nem található.",
+ "config-imagemagick": "Az ImageMagick megtalálható a rendszeren: <code>$1</code>.\nA bélyegképek készítése engedélyezve lesz a feltöltések engedélyezése esetén.",
+ "config-gd": "A GD grafikai könyvtár elérhető.\nBélyegképek készítése működni fog, miután engedélyezted a fájlfeltöltést.",
+ "config-no-scaling": "Nem található a GD könyvtár és az ImageMagick.\nA bélyegképek készítése le lesz tiltva.",
+ "config-no-uri": "'''Hiba:''' Nem sikerült megállapítani a jelenlegi URI-t.\nTelepítés megszakítva.",
+ "config-using-server": "A következő szervernév használata: „<nowiki>$1</nowiki>”.",
+ "config-using-uri": "A következő szerver URL-cím használata: „<nowiki>$1$2</nowiki>”.",
+ "config-uploads-not-safe": "'''Figyelmeztetés:''' a feltöltésekhez használt alapértelmezett könyvtárban (<code>$1</code>) tetszőleges külső szkript futtatható.\nHabár a MediaWiki ellenőrzi a feltöltött fájlokat az efféle biztonsági veszélyek megtalálása érdekében, a feltöltés engedélyezése előtt erősen ajánlott a [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security a sérülékenység megszüntetése].",
+ "config-brokenlibxml": "A rendszereden a PHP és libxml2 verziók olyan kombinációja található meg, ami hibásan működik, és észrevehetetlen adatkárosodást okoz a MediaWikiben és más webalkalmazásokban.\nFrissíts a libxml2 2.7.3 vgy újabb verziójára ([https://bugs.php.net/bug.php?id=45996 A hiba bejelentése a PHP-nél]).\nTelepítés megszakítva.",
+ "config-suhosin-max-value-length": "A Suhosin telepítve van, és a GET paraméter hosszát $1 bájtra korlátozza. A MediaWiki erőforrásbetöltő összetevője megkerüli a problémát, de így csökkenni fog a teljesítmény. Ha lehetséges, állítsd be a <code>suhosin.get.max_value_length</code> értékét legalább 1024-re a <code>php.ini</code>ben, és állítsd be a <code>$wgResourceLoaderMaxQueryLength</code> változót ugyanerre az értékre a LocalSettings.php-ben.",
+ "config-db-type": "Adatbázis típusa:",
+ "config-db-host": "Adatbázis hosztneve:",
+ "config-db-host-help": "Ha az adatbázisszerver másik szerveren található, add meg a hosztnevét vagy az IP-címét.\n\nHa megosztott webtárhelyet használsz, a szolgáltató dokumentációjában megtalálható a helyes hosztnév.\n\nHa Windows-alapú szerverre telepítesz, és MySQL-t használsz, a „localhost” nem biztos, hogy működni fog. Ha így van, próbáld meg a „127.0.0.1” helyi IP-cím használatát.\n\nHa PostgreSQL-t használsz, hagyd ezt a mezőt üresen a Unix-socketon keresztül történő csatlakozáshoz.",
+ "config-db-host-oracle": "Adatbázis TNS:",
+ "config-db-wiki-settings": "A wiki azonosítása",
+ "config-db-name": "Adatbázisnév:",
+ "config-db-name-help": "Válassz egy nevet a wiki azonosítására.\nNe tartalmazzon szóközt.\n\nHa megosztott webtárhelyet használsz, a szolgáltatód vagy egy konkrét adatbázisnevet ad neked használatra, vagy te magad hozhatsz létre adatbázisokat a vezérlőpulton keresztül.",
+ "config-db-name-oracle": "Adatbázisséma:",
+ "config-db-account-oracle-warn": "Oracle adatbázisba való telepítésnek három támogatott módja van:\n\nHa a telepítési folyamat során adatbázisfiókot szeretnél létrehozni, akkor egy olyan fiókot kell használnod, mely rendelkezik SYSDBA jogosultsággal, majd meg kell adnod a létrehozandó, webes hozzáféréshez használt fiók adatait. Emellett a fiók kézzel is létrehozható, ekkor ennek az adatait kell megadni (a fióknak rendelkeznie kell megfelelő jogosul adatbázis-objektumok létrehozásához), vagy megadhatsz két fiókot: egyet a létrehozáshoz szükséges jogosultságokkal, és egy korlátozottat a webes hozzáféréshez.\n\nA megfelelő jogosultságokkal rendelkező fiók létrehozásához használható szkript a szoftver „maintenance/oracle/” könyvtárában található. Ne feledd, hogy korlátozott fiók használatakor az alapértelmezett fiókkal nem végezhetőek el a karbantartási műveletek.",
+ "config-db-install-account": "A telepítéshez használt felhasználói fiók adatai",
+ "config-db-username": "Felhasználónév:",
+ "config-db-password": "Jelszó:",
+ "config-db-password-empty": "Írd be az új adatbázis-felhasználó jelszavát: $1\nVan lehetőség jelszó nélküli felhasználók létrehozására, azonban ez nem ajánlott.",
+ "config-db-install-username": "Írd be az adatbázisrendszerhez való csatlakozáshoz használt felhasználónevet.\nEz nem a MediaWiki fiók felhasználóneve; ez az adatbázisrendszeren használt felhasználóneved.",
+ "config-db-install-password": "Írd be az adatbázisrendszerhez való csatlakozáshoz használt jelszót.\nEz nem a MediaWiki-fiók jelszava; ez az adatbázisrendszeren használt jelszavad.",
+ "config-db-install-help": "Add meg a felhasználónevet és jelszót, amivel a telepítő csatlakozhat az adatbázishoz.",
+ "config-db-account-lock": "Általános működés során is ezen információk használata",
+ "config-db-wiki-account": "Általános működéshez használt felhasználói adatok",
+ "config-db-wiki-help": "Add meg azt a felhasználónevet és jelszót, amivel a wiki fog csatlakozni az adatbázishoz működés közben.\nHa a fiók nem létezik és a telepítést végző fiók rendelkezik megfelelő jogosultsággal, egy új fiók készül a megadott a névvel, azon minimális jogosultságkörrel, ami a wiki működéséhez szükséges.",
+ "config-db-prefix": "Adatbázistáblák nevének előtagja:",
+ "config-db-prefix-help": "Ha egyetlen adatbázison osztozik több wiki, vagy a MediaWiki és más webalkalmazás, választhatsz egy előtagot a táblaneveknek, hogy megelőzd a konfliktusokat.\nNe használj szóközöket.\n\nA mezőt általában üresen kell hagyni.",
+ "config-db-charset": "Az adatbázis karakterkészlete",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0, bináris",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0, visszafelé kompatibilis UTF-8",
+ "config-charset-help": "'''Figyelmezetés:''' Ha a '''visszafelé kompatibilis UTF-8''' beállítást használod MySQL 4.1 vagy újabb verziók esetén, és utána a <code>mysqldump</code> programmal készítesz róla biztonsági másolatot, az tönkreteheti az összes nem ASCII-karaktert, visszafordíthatatlanul károsítva a másolatokban tárolt adatokat!\n\n'''Bináris''' módban a MediaWiki az UTF-8-ban kódolt szöveget bináris mezőkben tárolja az adatbázisban.\nEz sokkal hatékonyabb a MySQL UTF-8-módjától, és lehetővé teszi, hogy a teljes Unicode-karakterkészletet használd.\n'''UTF-8-módban''' MySQL tudja, hogy milyen karakterkészlettel van kódolva az adat, és megfelelően tárolja és konvertálja, de\nnem használhatod a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] feletti karaktereket.",
+ "config-mysql-old": "A MySQL $1 vagy újabb verziója szükséges, a rendszeren $2 van.",
+ "config-db-port": "Adatbázisport:",
+ "config-db-schema": "MediaWiki-séma",
+ "config-db-schema-help": "A fenti sémák általában megfelelőek.\nCsak akkor módosíts rajtuk, ha tudod, hogy szükséges.",
+ "config-pg-test-error": "Nem sikerült csatlakozni a(z) '''$1''' adatbázishoz: $2",
+ "config-sqlite-dir": "SQLite-adatkönyvtár:",
+ "config-sqlite-dir-help": "Az SQLite minden adatot egyetlen fájlban tárol.\n\nA megadott könyvtárban írási jogosultsággal kell rendelkeznie a webszervernek.\n\n'''Nem''' szabad elérhetőnek lennie weben keresztül, ezért nem rakjuk oda, ahol a PHP-fájljaid vannak.\n\nA telepítő készít egy <code>.htaccess</code> fájlt az adatbázis mellé, azonban ha valamilyen okból nem sikerül, akkor akárki hozzáférhet a teljes adatbázisodhoz. Ez a felhasználók adatai (e-mail címek, jelszók hashei) mellett a törölt változatokat és más, korlátozott hozzáférésű információkat is tartalmaz.\n\nFontold meg az adatbázis más helyre történő elhelyezését, például a <code>/var/lib/mediawiki/tewikid</code> könyvtárba.",
+ "config-oracle-def-ts": "Alapértelmezett táblatér:",
+ "config-oracle-temp-ts": "Ideiglenes táblatér:",
+ "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. ([http://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 ([http://www.php.net/manual/en/pgsql.installation.php Hogyan fordítható a PHP PostgreSQL-támogatással]). Több apró, javítatlan hiba is előfordulhat, így nem ajánlott éles környezetben használni. ([http://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-header-mysql": "MySQL-beállítások",
+ "config-header-postgres": "PostgreSQL-beállítások",
+ "config-header-sqlite": "SQLite-beállítások",
+ "config-header-oracle": "Oracle-beállítások",
+ "config-header-mssql": "Microsoft SQL Server beállítások",
+ "config-invalid-db-type": "Érvénytelen adatbázistípus",
+ "config-missing-db-name": "Meg kell adnod a(z) „{{int:config-db-name}}” értékét.",
+ "config-missing-db-host": "Meg kell adnod az „{{int:config-db-host}}” értékét.",
+ "config-missing-db-server-oracle": "Meg kell adnod az „{{int:config-db-host-oracle}}” értékét.",
+ "config-invalid-db-server-oracle": "Érvénytelen adatbázis TNS: „$1”\nCsak ASCII betűk (a-z, A-Z), számok (0-9), alulvonás (_) és pont (.) használható.",
+ "config-invalid-db-name": "Érvénytelen adatbázisnév: „$1”.\nCsak ASCII-karakterek (a-z, A-Z), számok (0-9), alulvonás (_) és kötőjel (-) használható.",
+ "config-invalid-db-prefix": "Érvénytelen adatbázisnév-előtag: „$1”.\nCsak ASCII-karakterek (a-z, A-Z), számok (0-9), alulvonás (_) és kötőjel (-) használható.",
+ "config-connection-error": "$1.\n\nEllenőrizd a hosztot, felhasználónevet és jelszót, majd próbáld újra.",
+ "config-invalid-schema": "Érvénytelen MediaWiki-séma: „$1”.\nCsak ASCII-karakterek (a-z, A-Z), számok (0-9) és alulvonás (_) használható.",
+ "config-db-sys-create-oracle": "A telepítő csak a SYSDBA fiókkal tud új felhasználói fiókot létrehozni.",
+ "config-db-sys-user-exists-oracle": "Már létezik „$1” nevű felhasználói fiók. A SYSDBA csak új fiók létrehozására használható!",
+ "config-postgres-old": "A PostgreSQL $1 vagy újabb verziója szükséges, a rendszeren $2 van.",
+ "config-mssql-old": "Microsoft SQL Server $1 vagy későbbi szükséges. Te verziód: $2.",
+ "config-sqlite-name-help": "Válassz egy nevet a wiki azonosítására.\nNe tartalmazzon szóközt vagy kötőjelet.\nEz lesz az SQLite-adatfájl neve.",
+ "config-sqlite-parent-unwritable-group": "Nem hozható létre a(z) <code><nowiki>$1</nowiki></code> adatkönyvtár, mert a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>) nem írhat a webszerver.\n\nA telepítő megállapította, hogy mely felhasználó futtatja a webszervert.\nA folytatáshoz tedd írhatóvá a(z) <code><nowiki>$3</nowiki></code> könyvtárat.\nUnix/Linux rendszeren tedd a következőt:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Nem lehet létrehozni az adatok tárolásához szükséges <code><nowiki>$1</nowiki></code> könyvtárat, mert a webszerver nem írhat a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>).\n\nA telepítő nem tudta megállapíteni, hogy melyik felhasználói fiókon fut a webszerver.\nA folytatáshoz tedd írhatóvá ezen fiók (és más fiókok!) számára a következő könyvtárat: <code><nowiki>$3</nowiki></code>.\nUnix/Linux rendszereken tedd a következőt:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Nem sikerült létrehozni a következő adatkönyvtárat: „$1”.\nEllenőrizd a helyet, majd próbáld újra.",
+ "config-sqlite-dir-unwritable": "Nem sikerült írni a következő könyvtárba: „$1”.\nMódosítsd a jogosultságokat úgy, hogy a webszerver tudjon oda írni, majd próbáld újra.",
+ "config-sqlite-connection-error": "$1.\n\nEllenőrizd az adatkönyvtárat és az adatbázisnevet, majd próbáld újra.",
+ "config-sqlite-readonly": "A következő fájl nem írható: <code>$1</code>.",
+ "config-sqlite-cant-create-db": "Nem sikerült létrehozni a következő adatbázisfájlt: <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "A PHP nem rendelkezik FTS3-támogatással, táblák visszaminősítése",
+ "config-can-upgrade": "Ebben az adatábizban MediaWiki-táblák találhatóak.\nA MediaWiki $1 verzióra történő frissítéséhez kattints a '''Folytatás''' gombra.",
+ "config-upgrade-done": "A frissítés befejeződött.\n\nMost már '''[$1 beléphetsz a wikibe]'''.\n\nHa újra szeretnéd generálni a <code>LocalSettings.php</code> fájlt, kattints az alábbi gombra.\nEz '''nem ajánlott''', csak akkor, ha problémák vannak a wikivel.",
+ "config-upgrade-done-no-regenerate": "A frissítés befejeződött.\n\nMost már '''[$1 beléphetsz a wikibe]'''.",
+ "config-regenerate": "LocalSettings.php elkészítése újra →",
+ "config-show-table-status": "A <code>SHOW TABLE STATUS</code> lekérdezés nem sikerült!",
+ "config-unknown-collation": "'''Figyelmeztetés:''' az adatbázis ismeretlen egybevetést használ.",
+ "config-db-web-account": "A webes hozzáférésnél használt adatbázisfiók",
+ "config-db-web-help": "Add meg azt a felhasználónevet és jelszót, amit a webszerver a wiki általános működése során használ a csatlakozáshoz.",
+ "config-db-web-account-same": "A telepítéshez használt fiók használata",
+ "config-db-web-create": "Fiók létrehozása, ha még nem létezik.",
+ "config-db-web-no-create-privs": "A telepítéshez megadott fiók nem rendelkezik megfelelő jogosultságokkal új felhasználó létrehozásához.\nAz itt megadott fióknak léteznie kell.",
+ "config-mysql-engine": "Tárolómotor:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Figyelmeztetés''': A MyISAM tárolómotort választottad, ami nem ajánlott a MediaWiki használatánál, mert:\n* nagyon rosszul kezeli a párhuzamos lekéréseket a táblák zárolása miatt\n* sokkal nagyobb az esélye az adatkorrupció kialakulásának\n* a MediaWiki kódbázisa nem mindig úgy kezeli a MyISAM-ot, ahogyan kellene\n\nHa a feltelepített MySQL támogatja az InnoDB-t, erősen ajánlott, hogy inkább azt válaszd.\nHa nem, akkor lehet, hogy itt az ideje a frissítésnek.",
+ "config-mysql-engine-help": "A legtöbb esetben az '''InnoDB''' a legjobb választás, mivel megfelelően támogatja a párhuzamosságot.\n\nA '''MyISAM''' gyorsabb megoldás lehet egyfelhasználós vagy csak olvasható környezetekben, azonban a MyISAM-adatbázisok sokkal gyakrabban sérülnek meg, mint az InnoDB-adatbázisok.",
+ "config-mysql-charset": "Adatbázis karakterkészlete:",
+ "config-mysql-binary": "Bináris",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "'''Bináris módban''' a MediaWiki az UTF-8-as szövegeket bináris mezőkben tárolja az adatbázisban.\nEz sokkal hatékonyabb a MySQL UTF-8-as módjánál, és lehetővé teszi a teljes Unicode-karakterkészlet használatát.\n\n'''UTF-8-as módban''' a MySQL tudni fogja,hogy az adatok milyen karakterkészlettel rendelkeznek, és megfelelően átalakítja őket, azonban nem tárolhatóak olyan karakterek, melyek a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] felett vannak.",
+ "config-mssql-auth": "Hitelesítés típusa:",
+ "config-mssql-sqlauth": "SQL Server hitelesítés",
+ "config-mssql-windowsauth": "Windows hitelesítés",
+ "config-site-name": "A wiki neve:",
+ "config-site-name-help": "A böngésző címsorában és még számos más helyen jelenik meg.",
+ "config-site-name-blank": "Add meg az oldal nevét.",
+ "config-project-namespace": "Projektnévtér:",
+ "config-ns-generic": "Projekt",
+ "config-ns-site-name": "Ugyanaz, mint a wiki neve: $1",
+ "config-ns-other": "Más (meg kell adni)",
+ "config-ns-other-default": "SajátWiki",
+ "config-project-namespace-help": "A Wikipédia példáját követve számos wiki elkülöníti egy '''projekt névtérbe''' az irányelveit a tartalommal rendelkező lapoktól\nAz ebben a névtérben található lapok nevei egy előtaggal kezdődnek, amit itt adhatsz meg.\nÁltalában az előtag a wiki nevéből származik, de nem tartalmazhat írásjeleket, például „#”-t vagy „:”-t.",
+ "config-ns-invalid": "A megadott névtér („<nowiki>$1</nowiki>”) érvénytelen.\nVálassz másik projektnévteret!",
+ "config-ns-conflict": "A megadott névtér („<nowiki>$1</nowiki>”) ütközik az egyik alapértelmezett MediaWiki-névtérrel.\nVálassz másik projektnévteret!",
+ "config-admin-box": "Adminisztrátori fiók",
+ "config-admin-name": "Név:",
+ "config-admin-password": "Jelszó:",
+ "config-admin-password-confirm": "Jelszó újra:",
+ "config-admin-help": "Írd be a kívánt felhasználónevet, például „Kovács János”.\nEzzel a névvel fogsz majd bejelentkezni a wikibe.",
+ "config-admin-name-blank": "Add meg az adminisztrátor felhasználónevét!",
+ "config-admin-name-invalid": "A megadott felhasználónév (<nowiki>$1</nowiki>) érvénytelen.\nAdj meg egy másik felhasználónevet.",
+ "config-admin-password-blank": "Add meg az adminisztrátori fiók jelszavát!",
+ "config-admin-password-mismatch": "A megadott jelszavak nem egyeznek.",
+ "config-admin-email": "E-mail cím:",
+ "config-admin-email-help": "Add meg az e-mail címedet, hogy más felhasználók küldhessenek e-maileket a wikin keresztül, új jelszót tudj kérni, és értesülhess a figyelőlistádon lévő lapokon történt változásokról. Üresen is hagyhatod ezt a mezőt.",
+ "config-admin-error-user": "Belső hiba történt a(z) „<nowiki>$1</nowiki>” nevű adminisztrátor létrehozásakor.",
+ "config-admin-error-password": "Belső hiba történt a(z) „<nowiki>$1</nowiki>” nevű adminisztrátor jelszavának beállításakor: <pre>$2</pre>",
+ "config-admin-error-bademail": "Érvénytelen e-mail címet adtál meg.",
+ "config-subscribe": "Feliratkozás a [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce kiadási bejelentések levelezőlistájára].",
+ "config-subscribe-help": "Ez egy alacsony forgalmú levelezőlista, ahol a kiadásokkal kapcsolatos bejelentések jelennek meg, a fontos biztonsági javításokkal együtt.\nAjánlott feliratkozni rá, és frissíteni a MediaWikit, ha új verzió jön ki.",
+ "config-subscribe-noemail": "Anélkül próbáltál feliratkozni a kiadási bejelentések levelezőlistájára, hogy megadtál volna egy e-mail címet.\nAdj meg egyet, ha fel szeretnél iratkozni a levelezőlistára.",
+ "config-almost-done": "Már majdnem kész!\nA további konfigurációt kihagyhatod, és most azonnal elindíthatod a wiki telepítését.",
+ "config-optional-continue": "További információk megadása.",
+ "config-optional-skip": "Épp elég volt, települjön a wiki!",
+ "config-profile": "Felhasználói jogosultságok profilja:",
+ "config-profile-wiki": "Wiki megnyitása",
+ "config-profile-no-anon": "Felhasználói fiók létrehozása szükséges",
+ "config-profile-fishbowl": "Csak engedélyezett szerkesztők",
+ "config-profile-private": "Privát wiki",
+ "config-profile-help": "A wikik akkor működnek a legjobban, ha minél több felhasználó számára engedélyezett a szerkesztés.\nA MediaWikiben könnyű ellenőrizni a legutóbbi változtatásokat,és visszaállítani a naiv vagy káros felhasználók által okozott károkat.\n\nA MediaWiki azonban számos helyzetben hasznos lehet, és néha nem könnyű mindenkit meggyőzni a wiki előnyeiről.\nVálaszthatsz!\n\n<strong>{{int:config-profile-wiki}}kben</strong> bárki szerkeszthet, akár bejelentkezés nélkül is. A <strong>{{int:config-profile-no-anon}}</strong> beállítás további biztonságot nyújt, azonban elijesztheti az alkalmi szerkesztőket.\n\nLehetőség van arra is, hogy <strong>{{lc:{{int:config-profile-fishbowl}}}}</strong> módosíthassák a lapokat, de a nyilvánosság ekkor megtekintheti a lapokat és azok laptörténetét is. <strong>{{int:config-profile-private}}</strong> esetén csak az engedélyezett szerkesztők tekinthetik meg a lapokat, és ugyanez a csoport szerkeszthet.\n\nTelepítés után jóval összetettebb jogosultságrendszer állítható össze, további információ a [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights kézikönyv kapcsolódó bejegyzésében].",
+ "config-license": "Szerzői jog és licenc:",
+ "config-license-none": "Nincs licencjelzés",
+ "config-license-cc-by-sa": "Creative Commons Nevezd meg! - Így add tovább!",
+ "config-license-cc-by": "Creative Commons Nevezd meg!",
+ "config-license-cc-by-nc-sa": "Creative Commons Nevezd meg! - Ne add el! - Így add tovább!",
+ "config-license-cc-0": "Creative Commons Zero (közkincs)",
+ "config-license-gfdl": "GNU Szabad Dokumentációs Licenc 1.3 vagy újabb",
+ "config-license-pd": "Közkincs",
+ "config-license-cc-choose": "Creative Commons-licenc választása",
+ "config-license-help": "A legtöbb wiki valamilyen [http://freedomdefined.org/Definition szabad licenc] alatt teszi közzé a szerkesztéseit.\nEz erősíti a közösségi tulajdon érzését, és elősegíti a hosszú távú közreműködők megjelenését.\nÁltalában nem szükséges magán- vagy vállalati wiki esetén.\n\nHa a Wikipédiáról szeretnél szövegeket másolni, és a Wikipédián felhasználhassák a wikidben található szöveget, akkor a '''Creative Commons Nevezd meg! - Így add tovább!''' lehetőséget válaszd.\n\nA Wikipédia korábban a GNU Szabad Dokumentációs Licencet használta.\nEz a licenc még ma is használható, azonban nem könnyű megérteni,\ntovábbá a GFDL alatt közzétett tartalom újrafelhasználása nehézkes.",
+ "config-email-settings": "E-mail beállítások",
+ "config-enable-email": "Kimenő e-mailek engedélyezése",
+ "config-enable-email-help": "E-mailek küldéséhez [http://www.php.net/manual/en/mail.configuration.php a PHP mail beállításait] megfelelően meg kell adni.\nHa nem akarsz semmilyen e-mailes funkciót használni, itt tilthatod le őket.",
+ "config-email-user": "A felhasználók küldhetnek egymásnak e-maileket",
+ "config-email-user-help": "Bármelyik felhasználó küldhet másiknak e-mail üzenetet, amennyiben engedélyezték a lehetőséget a beállításaiknál.",
+ "config-email-usertalk": "Vitalapi értesítések engedélyezése",
+ "config-email-usertalk-help": "A felhasználók értesítéseket kapnak a vitalapjuk változásairól, amennyiben engedélyezték ezt a lehetőséget a beállításaiknál.",
+ "config-email-watchlist": "Figyelőlistai értesítések engedélyezése",
+ "config-email-watchlist-help": "A felhasználók értesítéseket kapnak a figyelt lapjaik változásairól, amennyiben engedélyezték ezt a lehetőséget a beállításaiknál.",
+ "config-email-auth": "E-mailes hitelesítés engedélyezése",
+ "config-email-auth-help": "Ha a beállítás engedélyezve van, a felhasználóknak meg kell erősíteniük az e-mail címüket egy kiküldött link segítségével, amikor megadják vagy módosítják azt.\nCsak a megerősített e-mail címmel rendelkezők kaphatnak e-maileket más felhasználóktól vagy értesítéseket.\nA beállítás engedélyezése '''ajánlott''' publikus wikiknél, mivel így megakadályozható az e-mailes funkciókkal való visszaélés.",
+ "config-email-sender": "Válaszcím:",
+ "config-email-sender-help": "Add meg a kimenő e-mail-üzenetek válaszcímét.\nIde lesznek küldve a visszapattant üzenetek is.\nSzámos levelezőszerver számára a cím domainrészének érvényesnek kell lennie.",
+ "config-upload-settings": "Képek és fájlok feltöltése",
+ "config-upload-enable": "Fájlfeltöltés engedélyezése",
+ "config-upload-help": "A fájlfeltöltés lehetséges biztonsági kockázatoknak teszi ki a szerveredet.\nTovábbi információért olvasd el a [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security biztonságról szóló szakaszt] a kézikönyvben.\n\nA fájlfeltöltés engedélyezéséhez változtasd meg a MediaWiki gyökérkönyvtárában található <code>images</code> alkönyvtár jogosultságát úgy, hogy a szerver írhasson oda, majd engedélyezd itt a beállítást.",
+ "config-upload-deleted": "Törölt fájlok könyvtára:",
+ "config-upload-deleted-help": "Válaszd ki azt a könyvtárat, ahol a törölt fájlok lesznek archiválva.\nNormális esetben ennek nem szabad elérhetőnek lennie az internetről.",
+ "config-logo": "A logó URL-címe:",
+ "config-logo-help": "A MediaWiki alapértelmezett felülete helyet ad egy 135×160 pixeles logónak a bal felső sarokban.\nTölts fel egy megfelelő méretű képet, majd írd be ide az URL-címét!\n\nHasználhatsz <code>$wgStylePath</code>-t vagy <code>$wgScriptPath</code>-t, ha más méretű a logód.\n\nHa nem szeretnél logót használni, egyszerűen hagyd üresen a mezőt.",
+ "config-instantcommons": "Instant Commons engedélyezése",
+ "config-instantcommons-help": "Az [//www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [//commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.\nA használatához a MediaWikinek internethozzáférésre van szüksége.\n\nA funkcióról és hogy hogyan állítható be más wikik esetén [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos a kézikönyvben] találhatsz további információkat.",
+ "config-cc-error": "A Creative Commons-licencválasztó nem tért vissza eredménnyel.\nAdd meg kézzel a licencet.",
+ "config-cc-again": "Válassz újra…",
+ "config-cc-not-chosen": "Válaszd ki a kívánt Creative Commons licencet, majd kattints a „Folytatás gombra”!",
+ "config-advanced-settings": "Haladó beállítások",
+ "config-cache-options": "Objektum-gyorsítótárazás beállításai:",
+ "config-cache-help": "Az objektumgyorsítótárazás célja, hogy felgyorsítsa a MediaWiki működését a gyakran használt adatok gyorsítótárazásával.\nKözepes vagy nagyobb oldalak esetén erősen ajánlott a használata, de kisebb oldalak esetén is hasznos lehet.",
+ "config-cache-none": "Nincs gyorsítótárazás (minden funkció működik, de nagyobb wiki esetében lassabb működést eredményezhet)",
+ "config-cache-accel": "PHP-objektumok gyorsítótárazása (APC, XCache or WinCache)",
+ "config-cache-memcached": "Memcached használata (további telepítés és konfigurálás szükséges)",
+ "config-memcached-servers": "Memcached-szerverek:",
+ "config-memcached-help": "Azon IP-címek listája, melyeket a Memcached használhat.\nVesszővel kell elválasztani őket, és meg kell adni a portot is. Például:\n 127.0.0.1:11211\n 192.168.1.25:11211",
+ "config-memcache-needservers": "Memcachedet választottad gyorsítótárnak, de nem adtál meg egyetlen szervert sem.",
+ "config-memcache-badip": "Érvénytelen IP-címet adtál meg a Memcachednek: $1.",
+ "config-memcache-noport": "Nem adtál meg portot a Memcached-szervernek: $1.\nHa nem ismered a portszámot, használd az alapértelmezettet: 11211.",
+ "config-memcache-badport": "A Memcached a(z) $1 és $2 közötti portokat szokta használni.",
+ "config-extensions": "Kiterjesztések",
+ "config-extensions-help": "A fent felsorolt kiterjesztések találhatóak meg az <code>./extensions</code> könyvtárban.\n\nLehetséges, hogy további beállításra lesz szükség hozzájuk, de már most engedélyezheted őket.",
+ "config-install-alreadydone": "'''Figyelmeztetés:''' Úgy tűnik, hogy a MediaWiki telepítve van, és te ismét megpróbálod telepíteni.\nFolytasd a következő oldalon.",
+ "config-install-begin": "A „{{int:config-continue}}” gomb megnyomása elindítja a MediaWiki telepítését.\nHa szeretnél módosítani a beállításokon, kattints a \"{{int:config-back}}\" gombra.",
+ "config-install-step-done": "kész",
+ "config-install-step-failed": "sikertelen",
+ "config-install-extensions": "Kiterjesztések beillesztése",
+ "config-install-database": "Adatbázis felállítása",
+ "config-install-schema": "Adatbázis-szerkezet létrehozása",
+ "config-install-pg-schema-not-exist": "A PostgreSQL-adatbázis nem létezik.",
+ "config-install-pg-schema-failed": "A táblák létrehozása nem sikerült.\nEllenőrizd, hogy „$1” felhasználó írhat-e a következő adatbázisba: „$2”.",
+ "config-install-pg-commit": "Változtatások közzététele",
+ "config-install-pg-plpgsql": "PL/pgSQL nyelv meglétének ellenőrzése",
+ "config-pg-no-plpgsql": "Telepítened kell a PL/pgSQL nyelvet a következő adatbázishoz: $1",
+ "config-pg-no-create-privs": "A telepítéshez megadott felhasználói fiók nem rendelkezik új fiók létrehozásához szükséges jogosultságokkal.",
+ "config-install-user": "Adatbázis-felhasználó létrehozása",
+ "config-install-user-alreadyexists": "Már létezik „$1” nevű felhasználó",
+ "config-install-user-create-failed": "Nem sikerült a(z) „$1” nevű felhasználó létrehozása: $2",
+ "config-install-user-grant-failed": "Nem sikerült jogosultságokkal felruházni a(z) „$1” nevű felhasználót: $2",
+ "config-install-user-missing": "A megadott felhasználó („$1”) nem létezik.",
+ "config-install-user-missing-create": "A megadott felhasználó („$1”) nem létezik.\nPipáld ki a „Fiók létrehozása” dobozt, ha létre szeretnéd hozni.",
+ "config-install-tables": "Táblák létrehozása",
+ "config-install-tables-exist": "'''Figyelmeztetés''': úgy tűnik, hogy a MediaWiki táblái már léteznek.\nLétrehozás kihagyása.",
+ "config-install-tables-failed": "'''Hiba''': a tábla létrehozása nem sikerült a következő miatt: $1",
+ "config-install-interwiki": "Alapértelmezett nyelvközihivatkozás-tábla feltöltése",
+ "config-install-interwiki-list": "Az <code>interwiki.list</code> fájl nem található.",
+ "config-install-interwiki-exists": "'''Figyelmeztetés''': Úgy tűnik, hogy az interwiki táblában már vannak bejegyzések.\nAlapértelmezett lista kihagyása.",
+ "config-install-stats": "Statisztika inicializálása",
+ "config-install-keys": "Titkos kulcsok generálása",
+ "config-insecure-keys": "'''Figyelmeztetés:''' A telepítés során generált $1 {{PLURAL:$2|biztonsági kulcs|biztonsági kulcsok}} nem teljesen $1 {{PLURAL:$2|biztonságos|biztonságosak}}. Érdemes {{PLURAL:$2||őket}} manuálisan megváltoztatni.",
+ "config-install-sysop": "Az adminisztrátor felhasználói fiókjának létrehozása",
+ "config-install-subscribe-fail": "Nem sikerült feliratkozni a mediawiki-announce levelezőlistára: $1",
+ "config-install-subscribe-notpossible": "A cURL nincs telepítve és az <code>allow_url_fopen</code> nem érhető el.",
+ "config-install-mainpage": "Kezdőlap létrehozása az alapértelmezett tartalommal",
+ "config-install-extension-tables": "Táblák létrehozása az engedélyezett kiterjesztésekhez",
+ "config-install-mainpage-failed": "Nemsikerült létrehozni a kezdőlapot: $1",
+ "config-install-done": "'''Gratulálunk!'''\nA MediaWiki telepítése sikeresen befejeződött.\n\nA telepítő elkészítette a <code>LocalSettings.php</code> fájlt, amely tartalmazza az összes beállítást.\n\nEzt le kell tölteni, majd elhelyezni a wiki telepítési könyvtárába (az a könyvtár, ahol az index.php is található).\n\nA letöltés automatikusan elindul. Ha mégsem indulna el, vagy megszakítottad, az alábbi linkre kattintva újra letöltheted:\n\n$3\n\n'''Megjegyzés''': Ha ezt most nem teszed meg, és kilépsz a telepítésből, az elkészített konfigurációs fájlt nem tudod elérni a későbbiekben.\n\nHa végeztél a fájl elhelyezésével, '''[$2 beléphetsz a wikibe]'''.",
+ "config-download-localsettings": "<code>LocalSettings.php</code> letöltése",
+ "config-help": "segítség",
+ "config-nofile": "\"$1\" fájl nem található. Törölve lett?",
+ "mainpagetext": "'''A MediaWiki telepítése sikeresen befejeződött.'''",
+ "mainpagedocfooter": "Ha segítségre van szükséged a wikiszoftver használatához, akkor keresd fel a [//meta.wikimedia.org/wiki/Help:Contents User's Guide] oldalt.\n\n== Alapok (angol nyelven) ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Beállítások listája]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki GyIK]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources A MediaWiki fordítása a saját nyelvedre]"
+}
diff --git a/includes/installer/i18n/hy.json b/includes/installer/i18n/hy.json
new file mode 100644
index 00000000..76179420
--- /dev/null
+++ b/includes/installer/i18n/hy.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''«MediaWiki» ծրագիրը հաջողությամբ տեղադրվեց։'''",
+ "mainpagedocfooter": "Այցելեք [//meta.wikimedia.org/wiki/Help:Contents User's Guide]՝ վիքի ծրագրային ապահովման օգտագործման մասին տեղեկությունների համար։\n\n== Որոշ օգտակար ռեսուրսներ ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/ia.json b/includes/installer/i18n/ia.json
new file mode 100644
index 00000000..62afb44f
--- /dev/null
+++ b/includes/installer/i18n/ia.json
@@ -0,0 +1,318 @@
+{
+ "@metadata": {
+ "authors": [
+ "McDutchie",
+ "아라"
+ ]
+ },
+ "config-desc": "Le installator de MediaWiki",
+ "config-title": "Installation de MediaWiki $1",
+ "config-information": "Information",
+ "config-localsettings-upgrade": "Un file <code>LocalSettings.php</code> ha essite detegite.\nPro actualisar iste installation, per favor entra le valor de <code>$wgUpgradeKey</code> in le quadro hic infra.\nIste se trova in <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Un file <code>LocalSettings.php</code> file ha essite detegite.\nPro actualisar iste installation, per favor executa <code>update.php</code>.",
+ "config-localsettings-key": "Clave de actualisation:",
+ "config-localsettings-badkey": "Le clave que tu forniva es incorrecte",
+ "config-upgrade-key-missing": "Un installation existente de MediaWiki ha essite detegite.\nPro actualisar iste installation, es necessari adjunger le sequente linea al fin del file <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Le file <code>LocalSettings.php</code> existente pare esser incomplete.\nLe variabile $1 non es definite.\nPer favor cambia <code>LocalSettings.php</code> de sorta que iste variabile es definite, e clicca \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Un error ha essite incontrate durante le connexion al base de datos usante le configuration specificate in <code>LocalSettings.php</code>. Per favor corrige iste configuration e reproba.\n\n$1",
+ "config-session-error": "Error al comenciamento del session: $1",
+ "config-session-expired": "Le datos de tu session pare haber expirate.\nLe sessiones es configurate pro un duration de $1.\nTu pote augmentar isto per definir <code>session.gc_maxlifetime</code> in php.ini.\nReinitia le processo de installation.",
+ "config-no-session": "Le datos de tu session es perdite!\nVerifica tu php.ini e assecura te que un directorio appropriate es definite in <code>session.save_path</code>.",
+ "config-your-language": "Tu lingua:",
+ "config-your-language-help": "Selige un lingua a usar durante le processo de installation.",
+ "config-wiki-language": "Lingua del wiki:",
+ "config-wiki-language-help": "Selige le lingua in que le wiki essera predominantemente scribite.",
+ "config-back": "← Retro",
+ "config-continue": "Continuar →",
+ "config-page-language": "Lingua",
+ "config-page-welcome": "Benvenite a MediaWiki!",
+ "config-page-dbconnect": "Connecter al base de datos",
+ "config-page-upgrade": "Actualisar le installation existente",
+ "config-page-dbsettings": "Configuration del base de datos",
+ "config-page-name": "Nomine",
+ "config-page-options": "Optiones",
+ "config-page-install": "Installar",
+ "config-page-complete": "Complete!",
+ "config-page-restart": "Reinitiar installation",
+ "config-page-readme": "Lege me",
+ "config-page-releasenotes": "Notas del version",
+ "config-page-copying": "Copiar",
+ "config-page-upgradedoc": "Actualisar",
+ "config-page-existingwiki": "Wiki existente",
+ "config-help-restart": "Vole tu rader tote le datos salveguardate que tu ha entrate e reinitiar le processo de installation?",
+ "config-restart": "Si, reinitia lo",
+ "config-welcome": "=== Verificationes del ambiente ===\nVerificationes de base essera ora exequite pro determinar si iste ambiente es apte pro le installation de MediaWiki.\nNon oblida de includer iste information si tu cerca adjuta pro completar le installation.",
+ "config-copyright": "=== Copyright and Terms ===\n\n$1\n\nIste programma es software libere; vos pote redistribuer lo e/o modificar lo sub le conditiones del Licentia Public General de GNU publicate per le Free Software Foundation; version 2 del Licentia, o (a vostre option) qualcunque version posterior.\n\nIste programma es distribuite in le sperantia que illo sia utile, ma '''sin garantia''', sin mesmo le implicite garantia de '''commercialisation''' o '''aptitude pro un proposito particular'''.\nVide le Licentia Public General de GNU pro plus detalios.\n\nVos deberea haber recipite <doclink href=Copying>un exemplar del Licentia Public General de GNU</doclink> con iste programma; si non, scribe al Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [http://www.gnu.org/copyleft/gpl.html lege lo in linea].",
+ "config-sidebar": "* [//www.mediawiki.org Pagina principal de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guida pro usatores]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guida pro administratores]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Lege me</doclink>\n* <doclink href=ReleaseNotes>Notas de iste version</doclink>\n* <doclink href=Copying>Conditiones de copia</doclink>\n* <doclink href=UpgradeDoc>Actualisation</doclink>",
+ "config-env-good": "Le ambiente ha essite verificate.\nTu pote installar MediaWiki.",
+ "config-env-bad": "Le ambiente ha essite verificate.\nTu non pote installar MediaWiki.",
+ "config-env-php": "PHP $1 es installate.",
+ "config-unicode-using-utf8": "utf8_normalize.so per Brion Vibber es usate pro le normalisation Unicode.",
+ "config-unicode-using-intl": "Le [http://pecl.php.net/intl extension PECL intl] es usate pro le normalisation Unicode.",
+ "config-unicode-pure-php-warning": "'''Aviso''': Le [http://pecl.php.net/intl extension PECL intl] non es disponibile pro exequer le normalisation Unicode; le systema recurre al implementation lente in PHP pur.\nSi tu sito ha un alte volumine de traffico, tu deberea informar te un poco super le [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalisation Unicode].",
+ "config-unicode-update-warning": "'''Aviso''': Le version installate del bibliotheca inveloppante pro normalisation Unicode usa un version ancian del bibliotheca del [http://site.icu-project.org/ projecto ICU].\nTu deberea [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations actualisar lo] si le uso de Unicode importa a te.",
+ "config-no-db": "Non poteva trovar un driver appropriate pro le base de datos! Es necessari installar un driver de base de datos pro PHP.\nLe sequente typos de base de datos es supportate: $1.\n\nSi tu compilava PHP tu mesme, reconfigura lo con un cliente de base de datos activate, per exemplo usante <code>./configure --with-mysqli</code>.\nSi tu installava PHP ex un pacchetto Debian o Ubuntu, tu debe installar equalmente, per exemplo, le modulo <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Attention''': tu ha SQLite $1, que es inferior al version minimal requirite, $2. SQLite essera indisponibile.",
+ "config-no-fts3": "'''Attention''': SQLite es compilate sin [//sqlite.org/fts3.html modulo FTS3]; functionalitate de recerca non essera disponibile in iste back-end.",
+ "config-register-globals-error": "<strong>Error: Le option <code>[http://php.net/register_globals register_globals]</code> de PHP es active.\nIllo debe esser disactivate pro continuar le installation.</strong>\nVide [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] pro obtener adjuta sur como facer lo.",
+ "config-magic-quotes-runtime": "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] es active!'''\nIste option corrumpe le entrata de datos imprevisibilemente.\nTu non pote installar o usar MediaWiki si iste option non es disactivate.",
+ "config-magic-quotes-sybase": "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] es active!'''\nIste option corrumpe le entrata de datos imprevisibilemente.\nTu non pote installar o usar MediaWiki si iste option non es disactivate.",
+ "config-mbstring": "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] es active!'''\nIste option causa errores e pote corrumper datos imprevisibilemente.\nTu non pote installar o usar MediaWiki si iste option non es disactivate.",
+ "config-safe-mode": "'''Aviso:''' Le [http://www.php.net/features.safe-mode modo secur] de PHP es active.\nIsto pote causar problemas, particularmente si es usate le incargamento de files e le supporto de <code>math</code>.",
+ "config-xml-bad": "Le modulo XML de PHP es mancante.\nMediaWiki require functiones de iste modulo e non functionara in iste configuration.\nSi tu usa Mandrake, installa le pacchetto php-xml.",
+ "config-pcre-old": "<strong>Fatal:</strong> PCRE $1 o plus tarde es necessari.\nTu binario de PHP binary es ligate con PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Plus information].",
+ "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-ctype": "'''Fatal''': PHP debe esser compilate con supporto pro le [http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
+ "config-json": "'''Fatal:''' PHP ha essite compilate sin supporto de JSON.\nTu debe installar le extension JSON de PHP o le extension [http://pecl.php.net/package/jsonc PECL jsonc] extension ante de installar MediaWiki.\n* Le extension de PHP es includite in Red Hat Enterprise Linux (CentOS) 5 e 6, ma debe esser activate in <code>/etc/php.ini</code> o <code>/etc/php.d/json.ini</code>.\n* Alcun distributiones de Linux liberate post maio 2013 omitte iste extension de PHP, forniente in su loco le extension PECL como <code>php5-json</code> o <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] es installate",
+ "config-apc": "[http://www.php.net/apc APC] es installate",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] es installate",
+ "config-no-cache": "'''Aviso:''' Non poteva trovar [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nLe cache de objectos non es activate.",
+ "config-mod-security": "'''Attention''': [http://modsecurity.org/ mod_security] es active in tu servitor web. Si mal configurate, isto pote causar problemas pro MediaWiki o altere software que permitte al usatores de publicar contento arbitrari.\nConsulta le [http://modsecurity.org/documentation/ documentation de mod_security] o contacta le servicio de adjuta de tu host si tu incontra estranie errores.",
+ "config-diff3-bad": "GNU diff3 non trovate.",
+ "config-git": "Systema de controlo de version Git trovate: <code>$1</code>",
+ "config-git-bad": "Systema de controlo de version Git non trovate.",
+ "config-imagemagick": "ImageMagick trovate: <code>$1</code>.\nLe miniaturas de imagines essera activate si tu activa le incargamento de files.",
+ "config-gd": "Le bibliotheca graphic GD se trova integrate in le systema.\nLe miniaturas de imagines essera activate si tu activa le incargamento de files.",
+ "config-no-scaling": "Non poteva trovar le bibliotheca GD ni ImageMagick.\nLe miniaturas de imagines essera disactivate.",
+ "config-no-uri": "'''Error:''' Non poteva determinar le URI actual.\nInstallation abortate.",
+ "config-no-cli-uri": "'''Attention''': Cammino al script (<code>--scriptpath</code>) non specificate. Le predefinition es usate: <code>$1</code>.",
+ "config-using-server": "Es usate le nomine de servitor \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Le URL de servitor \"<nowiki>$1$2</nowiki>\" es usate.",
+ "config-uploads-not-safe": "'''Aviso:''' Le directorio predefinite pro files incargate <code>$1</code> es vulnerabile al execution arbitrari de scripts.\nBen que MediaWiki verifica tote le files incargate contra le menacias de securitate, il es altemente recommendate [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security remediar iste vulnerabilitate de securitate] ante de activar le incargamento de files.",
+ "config-no-cli-uploads-check": "'''Attention:''' Le directorio predefinite pro files incargate (<code>$1</code>) non es verificate contra le vulnerabilitate\nal execution arbitrari de scripts durante le installation de CLI.",
+ "config-brokenlibxml": "Vostre systema ha un combination de versiones de PHP e libxml2 que es defectuose e pote causar corruption celate de datos in MediaWiki e altere applicationes web.\nActualisa a libxml2 2.7.3 o plus recente ([https://bugs.php.net/bug.php?id=45996 problema reportate presso PHP]).\nInstallation abortate.",
+ "config-suhosin-max-value-length": "Suhosin es installate e limita parametro <code>length</code> de GET a $1 bytes.\nLe componente ResourceLoader de MediaWiki va contornar iste limite, ma isto prejudicara le rendimento.\nSi possibile, tu deberea mitter <code>suhosin.get.max_value_length</code> a 1024 o superior in <code>php.ini</code>, e mitter <code>$wgResourceLoaderMaxQueryLength</code> al mesme valor in <code>LocalSettings.php</code>.",
+ "config-db-type": "Typo de base de datos:",
+ "config-db-host": "Servitor de base de datos:",
+ "config-db-host-help": "Si tu servitor de base de datos es in un altere servitor, entra hic le nomine o adresse IP del servitor.\n\nSi tu usa un servitor web usate in commun, tu providitor deberea dar te le correcte nomine de servitor in su documentation.\n\nSi tu face le installation in un servitor Windows e usa MySQL, le nomine \"localhost\" possibilemente non functiona como nomine de servitor. In tal caso, essaya \"127.0.0.1\", i.e. le adresse IP local.\n\nSi tu usa PostgreSQL, lassa iste campo vacue pro connecter via un \"socket\" de Unix.",
+ "config-db-host-oracle": "TNS del base de datos:",
+ "config-db-host-oracle-help": "Entra un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nomine Local Connect] valide; un file tnsnames.ora debe esser visibile a iste installation.<br />Si tu usa bibliothecas de cliente 10g o plus recente, tu pote anque usar le methodo de nomination [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identificar iste wiki",
+ "config-db-name": "Nomine del base de datos:",
+ "config-db-name-help": "Selige un nomine que identifica tu wiki.\nIllo non pote continer spatios.\n\nSi tu usa un servitor web usate in commun, tu providitor te fornira le nomine specific de un base de datos a usar, o te permitte crear un base de datos via un pannello de controlo.",
+ "config-db-name-oracle": "Schema del base de datos:",
+ "config-db-account-oracle-warn": "Il ha tres scenarios supportate pro le installation de Oracle como le base de datos de iste systema:\n\nSi tu vole crear un conto del base de datos como parte del processo de installation, per favor specifica un conto con le rolo SYSDBA como le conto del base de datos pro installation, e specifica le nomine e contrasigno desirate pro le conto de accesso per web. Alteremente tu pote crear le conto de accesso per web manualmente e specificar solmente iste conto (si illo ha le permissiones requisite pro crear le objectos de schema) o specifica duo contos differente, un con privilegios de creation e un conto restringite pro accesso per web.\n\nUn script pro crear un conto con le privilegios requisite se trova in le directorio \"maintenance/oracle/\" de iste installation. Non oblida que le uso de un conto restringite disactiva tote le capacitates de mantenentia in le conto predefinite.",
+ "config-db-install-account": "Conto de usator pro installation",
+ "config-db-username": "Nomine de usator del base de datos:",
+ "config-db-password": "Contrasigno del base de datos:",
+ "config-db-password-empty": "Per favor entra un contrasigno pro le nove usator del base de datos: $1.\nBen que il es possibile crear usatores sin contrasigno, isto non es secur.",
+ "config-db-username-empty": "Es necessari entrar un valor pro \"{{int:config-db-username}}\".",
+ "config-db-install-username": "Entra le nomine de usator que essera usate pro connecter al base de datos durante le processo de installation. Isto non es le nomine de usator del conto MediaWiki; isto es le nomine de usator pro tu base de datos.",
+ "config-db-install-password": "Entra le contrasigno que essera usate pro connecter al base de datos durante le processo de installation. Isto non es le contrasigno del conto MediaWiki; isto es le contrasigno pro tu base de datos.",
+ "config-db-install-help": "Entra le nomine de usator e contrasigno que essera usate pro connecter al base de datos durante le processo de installation.",
+ "config-db-account-lock": "Usar le mesme nomine de usator e contrasigno durante le operation normal",
+ "config-db-wiki-account": "Conto de usator pro operation normal",
+ "config-db-wiki-help": "Entra le nomine de usator e contrasigno que essera usate pro connecter al base de datos durante le operation normal del wiki.\nSi le conto non existe, e si le conto de installation possede sufficiente privilegios, iste conto de usator essera create con le minime privilegios necessari pro operar le wiki.",
+ "config-db-prefix": "Prefixo de tabella del base de datos:",
+ "config-db-prefix-help": "Si il es necessari usar un base de datos in commun inter multiple wikis, o inter MediaWiki e un altere application web, tu pote optar pro adder un prefixo a tote le nomines de tabella pro evitar conflictos.\nNon usa spatios.\n\nIste campo usualmente resta vacue.",
+ "config-db-charset": "Codification de characteres in le base de datos",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binari",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 retrocompatibile UTF-8",
+ "config-charset-help": "'''Aviso:''' Si tu usa '''UTF-8 retrocompatibile''' sur MySQL 4.1+, e postea face un copia de reserva del base de datos con <code>mysqldump</code>, tote le characteres non ASCII pote esser destruite, resultante in corruption irreversibile de tu copias de reserva!\n\nIn '''modo binari''', MediaWiki immagazina texto in UTF-8 in le base de datos in campos binari.\nIsto es plus efficiente que le modo UTF-8 de MySQL, e permitte usar le rango complete de characteres de Unicode.\nIn '''modo UTF-8''', MySQL sapera in qual codification de characteres tu datos es, e pote presentar e converter lo appropriatemente,\nma non te permittera immagazinar characteres supra le [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
+ "config-mysql-old": "MySQL $1 o plus recente es requirite, tu ha $2.",
+ "config-db-port": "Porto de base de datos:",
+ "config-db-schema": "Schema pro MediaWiki",
+ "config-db-schema-help": "Iste schema es generalmente correcte.\nSolmente cambia lo si tu es secur que es necessari.",
+ "config-pg-test-error": "Impossibile connecter al base de datos '''$1''': $2",
+ "config-sqlite-dir": "Directorio pro le datos de SQLite:",
+ "config-sqlite-dir-help": "SQLite immagazina tote le datos in un sol file.\n\nLe directorio que tu forni debe permitter le accesso de scriptura al servitor web durante le installation.\n\nIllo '''non''' debe esser accessibile via web. Pro isto, nos non lo pone ubi tu files PHP es.\n\nLe installator scribera un file <code>.htaccess</code> insimul a illo, ma si isto falli, alcuno pote ganiar accesso directe a tu base de datos.\nIsto include le crude datos de usator (adresses de e-mail, contrasignos codificate) assi como versiones delite e altere datos restringite super le wiki.\n\nConsidera poner le base de datos in un loco completemente differente, per exemplo in <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Spatio de tabellas predefinite:",
+ "config-oracle-temp-ts": "Spatio de tabellas temporari:",
+ "config-type-mysql": "MySQL (o compatibile)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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-mysql-url}} MySQL] es le systema primari pro MediaWiki e le melio supportate. MediaWiki functiona anque con [{{int:version-db-mariadb-url}} MariaDB] e con [{{int:version-db-percona-url}} Percona Server], le quales es compatibile con MySQL. ([http://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. Es possibile que resta alcun minor defectos non resolvite, dunque illo non es recommendate pro uso in un ambiente de production. ([http://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. ([http://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. ([http://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. ([http://www.php.net/manual/en/sqlsrv.installation.php Como compilar PHP con supporto de SQLSRV])",
+ "config-header-mysql": "Configuration de MySQL",
+ "config-header-postgres": "Configuration de PostgreSQL",
+ "config-header-sqlite": "Configuration de SQLite",
+ "config-header-oracle": "Configuration de Oracle",
+ "config-header-mssql": "Configuration de Microsoft SQL Server",
+ "config-invalid-db-type": "Typo de base de datos invalide",
+ "config-missing-db-name": "Es necessari entrar un valor pro \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Es necessari entrar un valor pro \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Es necessari entrar un valor pro \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "TNS de base de datos \"$1\" invalide.\nUsa o \"TNS Name\" o un catena \"Easy Connect\". ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Methodos de nomenclatura de Oracle])",
+ "config-invalid-db-name": "Nomine de base de datos \"$1\" invalide.\nUsa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e tractos de union (-).",
+ "config-invalid-db-prefix": "Prefixo de base de datos \"$1\" invalide.\nUsa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e tractos de union (-).",
+ "config-connection-error": "$1.\n\nVerifica le servitor, nomine de usator e contrasigno hic infra e reproba.",
+ "config-invalid-schema": "Schema invalide pro MediaWiki \"$1\".\nUsa solmente litteras ASCII (a-z, A-Z), numeros (0-9) e characteres de sublineamento (_).",
+ "config-db-sys-create-oracle": "Le installator supporta solmente le uso de un conto SYSDBA pro le creation de un nove conto.",
+ "config-db-sys-user-exists-oracle": "Le conto de usator \"$1\" ja existe. SYSDBA pote solmente esser usate pro le creation de un nove conto!",
+ "config-postgres-old": "PostgreSQL $1 o plus recente es requirite, tu ha $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 o plus recente es necessari. Tu ha $2.",
+ "config-sqlite-name-help": "Selige un nomine que identifica tu wiki.\nNon usar spatios o tractos de union.\nIsto essera usate pro le nomine del file de datos de SQLite.",
+ "config-sqlite-parent-unwritable-group": "Impossibile crear le directorio de datos <code><nowiki>$1</nowiki></code>, proque le directorio superjacente <code><nowiki>$2</nowiki></code> non concede le accesso de scriptura al servitor web.\n\nLe installator ha determinate le usator sub que le servitor web es executate.\nConcede le accesso de scriptura in le directorio <code><nowiki>$3</nowiki></code> a iste usator pro continuar.\nIn un systema Unix/Linux:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Impossibile crear le directorio de datos <code><nowiki>$1</nowiki></code>, proque le directorio superjacente <code><nowiki>$2</nowiki></code> non concede le accesso de scriptura al servitor web.\n\nLe installator non poteva determinar le usator sub que le servitor web es executate.\nConcede le accesso de scriptura in le directorio <code><nowiki>$3</nowiki></code> a iste usator (e alteres!) pro continuar.\nIn un systema Unix/Linux:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Error al creation del directorio de datos \"$1\".\nVerifica le loco e reproba.",
+ "config-sqlite-dir-unwritable": "Impossibile scriber in le directorio \"$1\".\nCambia su permissiones de sorta que le servitor web pote scriber in illo, e reproba.",
+ "config-sqlite-connection-error": "$1.\n\nVerifica le directorio de datos e le nomine de base de datos hic infra e reproba.",
+ "config-sqlite-readonly": "Le file <code>$1</code> non es accessibile pro scriptura.",
+ "config-sqlite-cant-create-db": "Non poteva crear le file de base de datos <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP non ha supporto pro FTS3. Le tabellas es retrogradate.",
+ "config-can-upgrade": "Il ha tabellas MediaWiki in iste base de datos.\nPro actualisar los a MediaWiki $1, clicca super '''Continuar'''.",
+ "config-upgrade-done": "Actualisation complete.\n\nTu pote ora [$1 comenciar a usar tu wiki].\n\nSi tu vole regenerar tu file <code>LocalSettings.php</code>, clicca super le button hic infra.\nIsto '''non es recommendate''' si tu non ha problemas con tu wiki.",
+ "config-upgrade-done-no-regenerate": "Actualisation complete.\n\nTu pote ora [$1 comenciar a usar tu wiki].",
+ "config-regenerate": "Regenerar LocalSettings.php →",
+ "config-show-table-status": "Le consulta <code>SHOW TABLE STATUS</code> falleva!",
+ "config-unknown-collation": "'''Aviso:''' Le base de datos usa un collation non recognoscite.",
+ "config-db-web-account": "Conto de base de datos pro accesso via web",
+ "config-db-web-help": "Selige le nomine de usator e contrasigno que le servitor web usara pro connecter al servitor de base de datos, durante le operation ordinari del wiki.",
+ "config-db-web-account-same": "Usar le mesme conto que pro le installation",
+ "config-db-web-create": "Crear le conto si illo non jam existe",
+ "config-db-web-no-create-privs": "Le conto que tu specificava pro installation non ha sufficiente privilegios pro crear un conto.\nLe conto que tu specifica hic debe jam exister.",
+ "config-mysql-engine": "Motor de immagazinage:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "* '''Attention:''' Tu ha seligite MyISAM como motor de immagazinage pro MySQL, lo que non es recommendate pro uso con MediaWiki, perque:\n* illo a pena supporta le processamento simultanee a causa del blocada le tabulas\n* illo es plus susceptibile al corruption que altere motores\n* le base de codice de MediaWiki non sempre manea MyISAM como illo deberea\n\nSi tu installation de MySQL supporta InnoDB, es multo recommendate que tu selige iste in su loco.\nSi tu installation de MySQL non supporta InnoDB, forsan isto es un bon occasion pro actualisar lo.",
+ "config-mysql-only-myisam-dep": "'''Attention:''' MyISAM es le unic motor de immagazinage disponibile pro MySQL in iste machina, ma isto non es recommendate pro le uso con MediaWiki, perque:\n* a pena supporto le accesso simultanee a causa del blocage de tabellas\n* es plus propense a corrumper se que altere motores\n* le codice base de MediaWiki non sempre gere MyISAM como deberea\n\nTu installation de MySQL non supporta InnoDB; forsan il es tempore de actualisar lo.",
+ "config-mysql-engine-help": "'''InnoDB''' es quasi sempre le melior option, post que illo ha bon supporto pro simultaneitate.\n\n'''MyISAM''' pote esser plus rapide in installationes a usator singule o a lectura solmente.\nLe bases de datos MyISAM tende a esser corrumpite plus frequentemente que le base de datos InnoDB.",
+ "config-mysql-charset": "Codification de characteres in le base de datos:",
+ "config-mysql-binary": "Binari",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "In '''modo binari''', MediaWiki immagazina le texto UTF-8 in le base de datos in campos binari.\nIsto es plus efficiente que le modo UTF-8 de MySQL, e permitte usar le rango complete de characteres Unicode.\n\nIn '''modo UTF-8''', MySQL cognoscera le codification de characteres usate pro tu dats, e pote presentar e converter lo appropriatemente, ma illo non permittera immagazinar characteres supra le [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
+ "config-mssql-auth": "Typo de authentication:",
+ "config-mssql-install-auth": "Selige le typo de authentication a usar pro connecter al base de datos durante le processo de installation.\nSi tu selige \"{{int:config-mssql-windowsauth}}\", le credentiales del usator que executa le servitor web essera usate.",
+ "config-mssql-web-auth": "Selige le typo de authentication que le servitor web usara pro connecter al base de datos durante le operation ordinari del wiki.\nSi tu selige \"{{int:config-mssql-windowsauth}}\", le credentiales del usator que executa le servitor web essera usate.",
+ "config-mssql-sqlauth": "Authentication per SQL Server",
+ "config-mssql-windowsauth": "Authentication per Windows",
+ "config-site-name": "Nomine del wiki:",
+ "config-site-name-help": "Isto apparera in le barra de titulo del navigator e in varie altere locos.",
+ "config-site-name-blank": "Entra un nomine de sito.",
+ "config-project-namespace": "Spatio de nomines del projecto:",
+ "config-ns-generic": "Projecto",
+ "config-ns-site-name": "Mesme nomine que le wiki: $1",
+ "config-ns-other": "Altere (specifica)",
+ "config-ns-other-default": "MiWiki",
+ "config-project-namespace-help": "Sequente le exemplo de Wikipedia, multe wikis tene lor paginas de politica separate de lor paginas de contento, in un \"'''spatio de nomines de projecto'''\".\nTote le titulos de pagina in iste spatio de nomines comencia con un certe prefixo, le qual tu pote specificar hic.\nTraditionalmente, iste prefixo deriva del nomine del wiki, ma illo non pote continer characteres de punctuation como \"#\" o \":\".",
+ "config-ns-invalid": "Le spatio de nomines specificate \"<nowiki>$1</nowiki>\" es invalide.\nSpecifica un altere spatio de nomines de projecto.",
+ "config-ns-conflict": "Le spatio de nomines specificate \"<nowiki>$1</nowiki>\" conflige con un spatio de nomines predefinite de MediaWiki.\nSpecifica un altere spatio de nomines pro le projecto.",
+ "config-admin-box": "Conto de administrator",
+ "config-admin-name": "Tu nomine de usator:",
+ "config-admin-password": "Contrasigno:",
+ "config-admin-password-confirm": "Repete contrasigno:",
+ "config-admin-help": "Entra hic tu nomine de usator preferite, per exemplo \"Julio Cesare\".\nIsto es le nomine que tu usara pro aperir session in le wiki.",
+ "config-admin-name-blank": "Entra un nomine de usator pro administrator.",
+ "config-admin-name-invalid": "Le nomine de usator specificate \"<nowiki>$1</nowiki>\" es invalide.\nSpecifica un altere nomine de usator.",
+ "config-admin-password-blank": "Entra un contrasigno pro le conto de administrator.",
+ "config-admin-password-mismatch": "Le duo contrasignos que tu scribeva non es identic.",
+ "config-admin-email": "Adresse de e-mail:",
+ "config-admin-email-help": "Entra un adresse de e-mail hic pro permitter le reception de e-mail ab altere usatores del wiki, pro poter reinitialisar tu contrasigno, e pro reciper notification de cambios a paginas in tu observatorio. Iste campo pote esser lassate vacue.",
+ "config-admin-error-user": "Error interne durante le creation de un administrator con le nomine \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Error interne durante le definition de un contrasigno pro le administrator \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Tu ha entrate un adresse de e-mail invalide",
+ "config-subscribe": "Subscribe al [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de diffusion pro annuncios de nove versiones].",
+ "config-subscribe-help": "Isto es un lista de e-mail a basse volumine pro annuncios de nove versiones, includente importante annuncios de securitate.\nTu deberea subscriber a illo e actualisar tu installation de MediaWiki quando nove versiones es editate.",
+ "config-subscribe-noemail": "Tu tentava abonar te al lista de diffusion pro annunciamento de nove versiones sin fornir un adresse de e-mail.\nPer favor specifica un adresse de e-mail si tu vole abonar te al lista de diffusion.",
+ "config-almost-done": "Tu ha quasi finite!\nTu pote ora saltar le configuration remanente e installar le wiki immediatemente.",
+ "config-optional-continue": "Pone me plus questiones.",
+ "config-optional-skip": "Isto me es jam tediose. Simplemente installa le wiki.",
+ "config-profile": "Profilo de derectos de usator:",
+ "config-profile-wiki": "Wiki aperte",
+ "config-profile-no-anon": "Creation de conto obligatori",
+ "config-profile-fishbowl": "Modificatores autorisate solmente",
+ "config-profile-private": "Wiki private",
+ "config-profile-help": "Le wikis functiona melio si tu permitte a tante personas como possibile de modificar los.\nIn MediaWiki, il es facile revider le modificationes recente, e reverter omne damno facite per usatores naive o malitiose.\n\nNonobstante, multes ha trovate MediaWiki utile in un grande varietate de rolos, e alcun vices il non es facile convincer omnes del beneficios del principio wiki.\nDunque, a te le option.\n\nLe modello '''{{int:config-profile-wiki}}''' permitte a omnes de modificar, sin mesmo aperir un session.\nUn wiki con '''{{int:config-profile-no-anon}}''' attribue additional responsabilitate, ma pote dissuader contributores occasional.\n\nLe scenario '''{{int:config-profile-fishbowl}}''' permitte al usatores approbate de modificar, ma le publico pote vider le paginas, includente lor historia.\nUn '''{{int:config-profile-private}}''' permitte solmente al usatores approbate de vider le paginas e de modificar los.\n\nConfigurationes de derectos de usator plus complexe es disponibile post installation, vide le [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights pertinente section del manual].",
+ "config-license": "Copyright e licentia:",
+ "config-license-none": "Nulle licentia in pede de paginas",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (dominio public)",
+ "config-license-gfdl": "Licentia GNU pro Documentation Libere 1.3 o plus recente",
+ "config-license-pd": "Dominio public",
+ "config-license-cc-choose": "Seliger un licentia Creative Commons personalisate",
+ "config-license-help": "Multe wikis public pone tote le contributiones sub un [http://freedomdefined.org/Definition/Ia?uselang=ia licentia libere].\nIsto adjuta a crear un senso de proprietate communitari e incoragia le contribution in longe termino.\nIsto non es generalmente necessari pro un wiki private o de interprisa.\n\nSi tu vole poter usar texto de Wikipedia, e si tu vole que Wikipedia pote acceptar texto copiate de tu wiki, tu debe seliger '''Creative Commons Attribution Share Alike'''.\n\nWikipedia usava anteriormente le Licentia GNU pro Documentation Libere (GFDL).\nIste es un licentia valide, ma es difficile a comprender.\nIl es anque difficile reusar le contento licentiate sub GFDL.",
+ "config-email-settings": "Configuration de e-mail",
+ "config-enable-email": "Activar le e-mail sortiente",
+ "config-enable-email-help": "Si tu vole que e-mail functiona, [http://www.php.net/manual/en/mail.configuration.php le optiones de e-mail de PHP] debe esser configurate correctemente.\nSi tu non vole functiones de e-mail, tu pote disactivar los hic.",
+ "config-email-user": "Activar le e-mail de usator a usator",
+ "config-email-user-help": "Permitter a tote le usatores de inviar e-mail inter se, si illes lo ha activate in lor preferentias.",
+ "config-email-usertalk": "Activar notification de cambios in paginas de discussion de usatores",
+ "config-email-usertalk-help": "Permitter al usatores de reciper notification de modificationes in lor paginas de discussion personal, si illes lo ha activate in lor preferentias.",
+ "config-email-watchlist": "Activar notification de observatorio",
+ "config-email-watchlist-help": "Permitter al usatores de reciper notification super lor paginas sub observation, si illes lo ha activate in lor preferentias.",
+ "config-email-auth": "Activar authentication de e-mail",
+ "config-email-auth-help": "Si iste option es activate, le usatores debe confirmar lor adresse de e-mail usante un ligamine inviate a illes, quandocunque illes lo defini o cambia.\nSolmente le adresses de e-mail authenticate pote reciper e-mail de altere usatores o alterar le e-mails de notification.\nEs '''recommendate''' activar iste option pro wikis public a causa de abuso potential del functionalitate de e-mail.",
+ "config-email-sender": "Adresse de e-mail de retorno:",
+ "config-email-sender-help": "Entra le adresse de e-mail a usar como adresse de retorno in e-mail sortiente.\nHic es recipite le notificationes de non-livration.\nMulte servitores de e-mail require que al minus le parte de nomine de dominio sia valide.",
+ "config-upload-settings": "Incargamento de imagines e files",
+ "config-upload-enable": "Activar le incargamento de files",
+ "config-upload-help": "Le incargamento de files potentialmente expone tu servitor a riscos de securitate.\nPro plus information, lege le [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security section de securitate] in le manual.\n\nPro activar le incargamento de files, cambia le modo in le subdirectorio <code>images</code> sub le directorio-radice de MediaWiki de sorta que le servitor web pote scriber in illo.\nPostea activa iste option.",
+ "config-upload-deleted": "Directorio pro files delite:",
+ "config-upload-deleted-help": "Selige un directorio in le qual archivar le files delite.\nIdealmente, isto non debe esser accessibile ab le web.",
+ "config-logo": "URL del logotypo:",
+ "config-logo-help": "Le apparentia predefinite de MediaWiki include spatio pro un logotypo de 135×160 pixels supra le menu del barra lateral.\nIncarga un imagine con le dimensiones appropriate, e entra le URL hic.\n\nTu pote usar <code>$wgStylePath</code> o <code>$wgScriptPath</code> si le loco de tu logotypo es relative a iste camminos.\n\nSi tu non vole un logotypo, lassa iste quadro vacue.",
+ "config-instantcommons": "Activar \"Instant Commons\"",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] es un function que permitte a wikis de usar imagines, sonos e altere multimedia trovate in le sito [//commons.wikimedia.org/ Wikimedia Commons].\nPro poter facer isto, MediaWiki require accesso a Internet.\n\nPro plus information super iste function, includente instructiones super como configurar lo pro wikis altere que Wikimedia Commons, consulta [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos le manual].",
+ "config-cc-error": "Le selector de licentia Creative Commons non dava un resultato.\nEntra le nomine del licentia manualmente.",
+ "config-cc-again": "Selige de novo…",
+ "config-cc-not-chosen": "Selige le licentia Creative Commons que tu prefere e clicca \"proceder\".",
+ "config-advanced-settings": "Configuration avantiate",
+ "config-cache-options": "Configuration del cache de objectos:",
+ "config-cache-help": "Le cache de objectos es usate pro meliorar le rapiditate de MediaWiki per immagazinar le datos frequentemente usate.\nLe sitos medie o grande es multo incoragiate de activar isto, ma anque le sitos parve percipera le beneficios.",
+ "config-cache-none": "Nulle cache (nulle functionalitate es removite, ma le rapiditate pote diminuer in grande sitos wiki)",
+ "config-cache-accel": "Cache de objectos de PHP (APC, XCache o WinCache)",
+ "config-cache-memcached": "Usar Memcached (require additional installation e configuration)",
+ "config-memcached-servers": "Servitores Memcached:",
+ "config-memcached-help": "Lista de adresses IP a usar pro Memcached.\nDebe specificar un per linea e specificar le porto a usar. Per exemplo:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Tu seligeva Memcached como typo de cache ma non specificava alcun servitores",
+ "config-memcache-badip": "Tu ha entrate un adresse IP invalide pro Memcached: $1",
+ "config-memcache-noport": "Tu non specificava un porto a usar pro le servitor Memcached: $1.\nSi tu non cognosce le porto, le standard es 11211",
+ "config-memcache-badport": "Le numeros de porto de Memcached debe esser inter $1 e $2",
+ "config-extensions": "Extensiones",
+ "config-extensions-help": "Le extensiones listate hic supra esseva detegite in tu directorio <code>./extensions</code>.\n\nIstes pote requirer additional configuration, ma tu pote activar los ora.",
+ "config-install-alreadydone": "'''Aviso:''' Il pare que tu ha jam installate MediaWiki e tenta installar lo de novo.\nPer favor continua al proxime pagina.",
+ "config-install-begin": "Un clic sur \"{{int:config-continue}}\" comencia le installation de MediaWiki.\nPro facer alterationes, clicca sur \"{{int:config-back}}\".",
+ "config-install-step-done": "finite",
+ "config-install-step-failed": "fallite",
+ "config-install-extensions": "Include le extensiones",
+ "config-install-database": "Configura le base de datos",
+ "config-install-schema": "Creation de schema",
+ "config-install-pg-schema-not-exist": "Iste schema de PostgreSQL non existe",
+ "config-install-pg-schema-failed": "Le creation del tabellas falleva.\nAssecura te que le usator \"$1\" pote scriber in le schema \"$2\".",
+ "config-install-pg-commit": "Committer cambiamentos",
+ "config-install-pg-plpgsql": "Verifica le presentia del linguage PL/pgSQL",
+ "config-pg-no-plpgsql": "Es necessari installar le linguage PL/pgSQL in le base de datos $1",
+ "config-pg-no-create-privs": "Le conto que tu specificava pro installation non ha sufficiente privilegios pro crear un conto.",
+ "config-pg-not-in-role": "Le conto que tu specificava pro le usator web ja existe.\nLe conto que tu specificava pro installation non es superusator e non es membro del rolo de usator web, dunque es incapace de crear objectos possedite per le usator web.\n\nMediaWiki require actualmente que le tabellas sia possedite per le usator web. Per favor specifica un altere nomine de conto web, o clicca super \"retornar\" e specifica un usator de installation con sufficiente privilegios.",
+ "config-install-user": "Crea usator pro base de datos",
+ "config-install-user-alreadyexists": "Le usator \"$1\" ja existe",
+ "config-install-user-create-failed": "Le creation del usator \"$1\" ha fallite: $2",
+ "config-install-user-grant-failed": "Le concession de permission al usator \"$1\" falleva: $2",
+ "config-install-user-missing": "Le usator specificate, \"$1\", non existe.",
+ "config-install-user-missing-create": "Le usator specificate, \"$1\", non existe.\nPer favor marca le quadrato \"crear conto\" hic infra si tu vole crear lo.",
+ "config-install-tables": "Crea tabellas",
+ "config-install-tables-exist": "'''Aviso''': Il pare que le tabellas de MediaWiki jam existe.\nLe creation es saltate.",
+ "config-install-tables-failed": "'''Error''': Le creation del tabellas falleva con le sequente error: $1",
+ "config-install-interwiki": "Plena le tabella interwiki predefinite",
+ "config-install-interwiki-list": "Non poteva trovar le file <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Aviso''': Le tabella interwiki pare jam haber entratas.\nLe lista predefinite es saltate.",
+ "config-install-stats": "Initialisation del statisticas",
+ "config-install-keys": "Generation de claves secrete",
+ "config-insecure-keys": "'''Attention:''' {{PLURAL:$2|Un clave|Alcun claves}} secur ($1) generate durante le installation non es completemente secur. Considera cambiar {{PLURAL:$2|lo|los}} manualmente.",
+ "config-install-sysop": "Crea conto de usator pro administrator",
+ "config-install-subscribe-fail": "Impossibile subscriber a mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL non es installate e <code>allow_url_fopen</code> non es disponibile.",
+ "config-install-mainpage": "Crea pagina principal con contento predefinite",
+ "config-install-extension-tables": "Creation de tabellas pro le extensiones activate",
+ "config-install-mainpage-failed": "Non poteva inserer le pagina principal: $1",
+ "config-install-done": "'''Felicitationes!'''\nTu ha installate MediaWiki con successo.\n\nLe installator ha generate un file <code>LocalSettings.php</code>.\nIste contine tote le configuration.\n\nEs necessari discargar lo e poner lo in le base del installation wiki (le mesme directorio que index.php).\nLe discargamento debe haber comenciate automaticamente.\n\nSi le discargamento non ha comenciate, o si illo esseva cancellate, es possibile recomenciar le discargamento con un clic sur le ligamine sequente:\n\n$3\n\n'''Nota''': Si tu non discarga iste file de configuration ora, illo non essera disponibile plus tarde.\n\nPost facer isto, tu pote '''[$2 entrar in tu wiki]'''.",
+ "config-download-localsettings": "Discargar <code>LocalSettings.php</code>",
+ "config-help": "adjuta",
+ "config-help-tooltip": "clicca pro displicar",
+ "config-nofile": "Le file \"$1\" non poteva esser trovate. Ha illo essite delite?",
+ "config-extension-link": "Sapeva tu que tu wiki supporta [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensiones]?\n\nTu pote explorar le [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensiones per category] o le [//www.mediawiki.org/wiki/Extension_Matrix matrice de extensiones] pro vider le lista complete de extensiones.",
+ "mainpagetext": "'''MediaWiki ha essite installate con successo.'''",
+ "mainpagedocfooter": "Consulta le [//meta.wikimedia.org/wiki/Help:Contents Guida del usator] pro informationes super le uso del software wiki.\n\n== Pro initiar ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de configurationes]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ a proposito de MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de diffusion pro annuncios de nove versiones de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Traducer MediaWiki in tu lingua]"
+}
diff --git a/includes/installer/i18n/id.json b/includes/installer/i18n/id.json
new file mode 100644
index 00000000..c2411ef0
--- /dev/null
+++ b/includes/installer/i18n/id.json
@@ -0,0 +1,323 @@
+{
+ "@metadata": {
+ "authors": [
+ "Farras",
+ "IvanLanin",
+ "Kenrick95",
+ "Reedy",
+ "아라",
+ "C5st4wr6ch",
+ "Seb35",
+ "Arifin.wijaya"
+ ]
+ },
+ "config-desc": "Penginstal untuk MediaWiki",
+ "config-title": "Instalasi MediaWiki $1",
+ "config-information": "Informasi",
+ "config-localsettings-upgrade": "Berkas <code>LocalSettings.php</code> sudah ada.\nUntuk memutakhirkan instalasi ini, masukkan nilai <code>$wgUpgradeKey</code> dalam kotak yang tersedia di bawah ini.\nAnda dapat menemukan nilai tersebut dalam <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Berkas <code>LocalSettings.php</code> terdeteksi.\nUntuk meningkatkan versi, harap jalankan <code>update.php</code>.",
+ "config-localsettings-key": "Kunci pemutakhiran:",
+ "config-localsettings-badkey": "Kunci yang Anda berikan tidak benar",
+ "config-upgrade-key-missing": "Suatu instalasi MediaWiki telah terdeteksi.\nUntuk memutakhirkan instalasi ini, silakan masukkan baris berikut di bagian bawah <code>LocalSettings.php</code> Anda:\n\n$1",
+ "config-localsettings-incomplete": "<code>LocalSettings.php</code> yang ada tampaknya tidak lengkap.\nVariabel $1 tidak diatur.\nSilakan ubah <code>LocalSettings.php</code> untuk mengatur variabel ini dan klik \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Ditemukan galat saat menghubungkan ke basis data dengan menggunakan setelan yang ditentukan di <code>LocalSettings.php</code>. Harap perbaiki setelan ini dan coba lagi.\n\n$1",
+ "config-session-error": "Kesalahan sesi mulai: $1",
+ "config-session-expired": "Data sesi tampaknya telah kedaluwarsa.\nSesi dikonfigurasi untuk berlaku selama $1.\nAnda dapat menaikkannya dengan menetapkan <code>session.gc_maxlifetime</code> dalam php.ini.\nUlangi proses instalasi.",
+ "config-no-session": "Data sesi Anda hilang!\nCek php.ini Anda dan pastikan bahwa <code>session.save_path</code> diatur ke direktori yang sesuai.",
+ "config-your-language": "Bahasa Anda:",
+ "config-your-language-help": "Pilih bahasa yang akan digunakan selama proses instalasi.",
+ "config-wiki-language": "Bahasa wiki:",
+ "config-wiki-language-help": "Pilih bahasa yang akan digunakan tulisan-tulisan wiki.",
+ "config-back": "← Kembali",
+ "config-continue": "Lanjut →",
+ "config-page-language": "Bahasa",
+ "config-page-welcome": "Selamat datang di MediaWiki",
+ "config-page-dbconnect": "Hubungkan ke basis data",
+ "config-page-upgrade": "Perbarui instalasi yang ada",
+ "config-page-dbsettings": "Pengaturan basis data",
+ "config-page-name": "Nama",
+ "config-page-options": "Pilihan",
+ "config-page-install": "Instal",
+ "config-page-complete": "Selesai!",
+ "config-page-restart": "Ulangi instalasi",
+ "config-page-readme": "Baca saya",
+ "config-page-releasenotes": "Catatan pelepasan",
+ "config-page-copying": "Menyalin",
+ "config-page-upgradedoc": "Memerbarui",
+ "config-page-existingwiki": "Wiki yang ada",
+ "config-help-restart": "Apakah Anda ingin menghapus semua data tersimpan yang telah Anda masukkan dan mengulang proses instalasi?",
+ "config-restart": "Ya, nyalakan ulang",
+ "config-welcome": "=== Pengecekan lingkungan ===\nPengecekan dasar kini akan dilakukan untuk melihat apakah lingkungan ini memadai untuk instalasi MediaWiki.\nIngatlah untuk menyertakan informasi ini jika Anda mencari bantuan tentang cara menyelesaikan instalasi.",
+ "config-copyright": "=== Hak cipta dan persyaratan ===\n\n$1\n\nProgram ini adalah perangkat lunak bebas; Anda dapat mendistribusikan dan/atau memodifikasi di bawah persyaratan GNU General Public License seperti yang diterbitkan oleh Free Software Foundation; baik versi 2 lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.\n\nProgram ini didistribusikan dengan harapan bahwa itu akan berguna, tetapi '''tanpa jaminan apa pun'''; bahkan tanpa jaminan tersirat untuk '''dapat diperjualbelikan ''' atau '''sesuai untuk tujuan tertentu'''.\nLihat GNU General Public License untuk lebih jelasnya.\n\nAnda seharusnya telah menerima <doclink href=\"Copying\">salinan dari GNU General Public License</doclink> bersama dengan program ini; jika tidak, kirimkan surat untuk Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. atau [http://www.gnu.org/copyleft/gpl.html baca versi daring].",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/id Situs MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/id Pedoman Pengguna]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/id Pedoman Administrator]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/id FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Release notes</doclink>\n* <doclink href=Copying>Copying</doclink>\n* <doclink href=UpgradeDoc>Upgrading</doclink>",
+ "config-env-good": "Kondisi telah diperiksa.\nAnda dapat menginstal MediaWiki.",
+ "config-env-bad": "Kondisi telah diperiksa.\nAnda tidak dapat menginstal MediaWiki.",
+ "config-env-php": "PHP $1 diinstal.",
+ "config-unicode-using-utf8": "Menggunakan utf8_normalize.so Brion Vibber untuk normalisasi Unicode.",
+ "config-unicode-using-intl": "Menggunakan [http://pecl.php.net/intl ekstensi PECL intl] untuk normalisasi Unicode.",
+ "config-unicode-pure-php-warning": "'''Peringatan''': [http://pecl.php.net/intl Ekstensi intl PECL] untuk menangani normalisasi Unicode tidak tersedia, kembali menggunakan implementasi murni PHP yang lambat.\nJika Anda menjalankan situs berlalu lintas tinggi, Anda harus sedikit membaca [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalisasi Unicode].",
+ "config-unicode-update-warning": "'''Peringatan''': Versi terinstal dari pembungkus normalisasi Unicode menggunakan versi lama pustaka [http://site.icu-project.org/ proyek ICU].\nAnda harus [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations memutakhirkannya] jika Anda ingin menggunakan Unicode.",
+ "config-no-db": "Pengandar basis data yang sesuai tidak ditemukan! Anda perlu menginstal pengandar basis data untuk PHP.\nJenis basis data yang didukung: $1.\n\nJika Anda mengompilasi sendiri PHP, ubahlah konfigurasinya dengan mengaktifkan klien basis data, misalnya menggunakan <code>./configure --with-mysql</code>.\nJika Anda menginstal PHP dari paket Debian atau Ubuntu, maka Anda juga perlu menginstal modul php5-mysql.",
+ "config-outdated-sqlite": "<strong>Peringatan:</strong> Anda menggunakan SQLite $1, yang lebih rendah dari versi minimum yang diperlukan $2. SQLite akan tidak tersedia.",
+ "config-no-fts3": "'''Peringatan''': SQLite dikompilasi tanpa [//sqlite.org/fts3.html modul FTS3], fitur pencarian tidak akan tersedia pada konfigurasi ini.",
+ "config-register-globals-error": "<strong>Kesalahan: Pilihan PHP <code>[http://php.net/register_globals register_globals]</code> diaktifkan.\nIni harus dinonaktifkan untuk melanjutkan instalasi.</strong>\nLihat [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] untuk bantuan tentang cara melakukannya.",
+ "config-magic-quotes-runtime": "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] aktif!'''\nPilihan ini dapat merusak masukan data secara tidak terduga.\nAnda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
+ "config-magic-quotes-sybase": "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic_quotes_sybase magic_quotes_sybase] aktif!'''\nPilihan ini dapat merusak masukan data secara tidak terduga.\nAnda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
+ "config-mbstring": "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] aktif!'' '\nPilihan ini dapat menyebabkan kesalahan dan kerusakan data yang tidak terduga.\nAnda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
+ "config-safe-mode": "''' Peringatan:''' [http://www.php.net/features.safe-mode Mode aman] PHP aktif.\nHal ini akan menyebabkan masalah, terutama jika menggunakan pengunggahan berkas dan dukungan <code>math</code>.",
+ "config-xml-bad": "Modul XML PHP hilang.\nMediaWiki membutuhkan fungsi dalam modul ini dan tidak akan bekerja dalam konfigurasi ini.\nJika Anda menggunakan Mandrake, instal paket php-xml.",
+ "config-pcre-old": "<strong>Fatal:</strong> PCRE $1 atau kemudian diperlukan.\nBiner PHP Anda dihubungkan dengan PCRE $2. [https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Selengkapnya].",
+ "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-ctype": "<strong>Fatal:</strong> PHP harus disusun dengan dukungan untuk [http://www.php.net/manual/en/ctype.installation.php ekstensi Ctype].",
+ "config-json": "<strong>Fatal:</strong> PHP dikompilasi tanpa dukungan JSON.\nAnda harus menginstal salah satu pengaya PHP JSON atau pengaya [http://pecl.php.net/package/jsonc PECL jsonc] sebelum menginstal MediaWiki.\n* Pengaya PHP termasuk dalam Red Hat Enterprise Linux (CentOS) 5 dan 6, meskipun harus diaktifkan pada <code>/etc/php.ini</code> atau <code>/etc/php.d/json.ini</code>.\n* Beberapa distribusi Linux dirilis setelah Mei 2013 menghilangkan pengaya PHP, bukan kemasan pengaya PECL sebagai <code>php5-json</code> atau <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] telah diinstal",
+ "config-apc": "[http://www.php.net/apc APC] telah diinstal",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] telah diinstal",
+ "config-no-cache": "'''Peringatan:''' Tidak dapat menemukan [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], atau [http://www.iis.net/download/WinCacheForPhp WinCache]. Pinggahan obyek tidak dinonaktifkan.",
+ "config-mod-security": "<strong>Peringatan:</strong> Server web Anda memiliki [http://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 [http://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-git-bad": "Perangkat lunak kontrol versi Git tidak ditemukan.",
+ "config-imagemagick": "ImageMagick ditemukan: <code>$1</code> .\nPembuatan gambar mini akan diaktifkan jika Anda mengaktifkan pengunggahan.",
+ "config-gd": "Pustaka grafis GD terpasang ditemukan.\nPembuatan gambar mini akan diaktifkan jika Anda mengaktifkan pengunggahan.",
+ "config-no-scaling": "Pustaka GD atau ImageMagick tidak ditemukan.\nPembuatan gambar mini dinonaktifkan.",
+ "config-no-uri": "'''Kesalahan:''' URI saat ini tidak dapat ditentukan.\nInstalasi dibatalkan.",
+ "config-no-cli-uri": "<strong>Peringatan:</strong> Tidak ada <code>--scriptpath</code> yang ditentukan, dengan menggunakan standar: <code>$1</code>.",
+ "config-using-server": "Menggunakan nama server \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Menggunakan URL server \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Peringatan:''' Direktori bawaan pengunggahan <code>$1</code> Anda rentan terhadap eksekusi skrip yang sewenang-wenang.\nMeskipun MediaWiki memeriksa semua berkas unggahan untuk ancaman keamanan, sangat dianjurkan untuk [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security menutup kerentanan keamanan ini] sebelum mengaktifkan pengunggahan.",
+ "config-no-cli-uploads-check": "<strong>Peringatan:</strong> Direktori default Anda untuk unggahan (<code>$1</code>) tidak diperiksa untuk kerentanan terhadap\neksekusi script sewenang-wenang selama instalasi CLI.",
+ "config-brokenlibxml": "Sistem Anda memiliki kombinasi versi PHP dan libxml2 yang memiliki bug dan dapat menyebabkan kerusakan data tersembunyi pada MediaWiki dan aplikasi web lain.\nMutakhirkan ke PHP 5.2.9 atau yang lebih baru dan libxml2 2.7.3 atau yang lebih baru ([https://bugs.php.net/bug.php?id=45996 arsip bug di PHP]).\nInstalasi dibatalkan.",
+ "config-suhosin-max-value-length": "Suhosin terpasang dan membatasi parameter GET <code>length</code> sebesar $1 bita. Komponen ResourceLoader MediaWiki akan berjalan dalam batasan ini, tetapi penanganannya akan menurunkan kinerja. Jika memungkinkan, Anda sebaiknya menetapkan nilai <code>suhosin.get.max_value_length</code> menjadi 1024 atau lebih tinggi dalam <code>php.ini</code> dan menyetel <code>$wgResourceLoaderMaxQueryLength</code> dengan nilai yang sama dalam <code>LocalSettings.php</code>.",
+ "config-db-type": "Jenis basis data:",
+ "config-db-host": "Inang basis data:",
+ "config-db-host-help": "Jika server basis data Anda berada di server yang berbeda, masukkan nama inang atau alamat IP di sini.\n\nJika Anda menggunakan inang web bersama, penyedia inang Anda harus memberikan nama inang yang benar di dokumentasi mereka.\n\nJika Anda menginstal pada server Windows dan menggunakan MySQL, \"localhost\" mungkin tidak dapat digunakan sebagai nama server. Jika demikian, coba \"127.0.0.1\" untuk alamat IP lokal.\n\nJika Anda menggunakan PostgreSQL, biarkan field ini kosong untuk menghubungkan lewat soket Unix.",
+ "config-db-host-oracle": "TNS basis data:",
+ "config-db-host-oracle-help": "Masukkan [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] yang sah; berkas tnsnames.ora harus dapat diakses oleh instalasi ini.<br />Jika Anda menggunakan pustaka klien 10g atau lebih baru, Anda juga dapat menggunakan metode penamaan [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identifikasi wiki ini",
+ "config-db-name": "Nama basis data:",
+ "config-db-name-help": "Pilih nama yang mengidentifikasikan wiki Anda.\nNama tersebut tidak boleh mengandung spasi.\n\nJika Anda menggunakan inang web bersama, penyedia inang Anda dapat memberikan Anda nama basis data khusus untuk digunakan atau mengizinkan Anda membuat basis data melalui panel kontrol.",
+ "config-db-name-oracle": "Skema basis data:",
+ "config-db-account-oracle-warn": "Ada tiga skenario yang didukung untuk instalasi Oracle sebagai basis data pendukung:\n\nJika Anda ingin membuat akun basis data sebagai bagian dari proses instalasi, silakan masukkan akun dengan peran SYSDBA sebagai akun basis data untuk instalasi dan tentukan kredensial yang diinginkan untuk akun akses web. Jika tidak, Anda dapat membuat akun akses web secara manual dan hanya memberikan akun tersebut (jika memiliki izin yang diperlukan untuk membuat objek skema) atau memasukkan dua akun yang berbeda, satu dengan hak membuat objek dan satu dibatasi untuk akses web.\n\nSkrip untuk membuat akun dengan privilese yang diperlukan dapat ditemukan pada direktori \"maintenance/oracle/\" instalasi ini. Harap diingat bahwa penggunaan akun terbatas akan menonaktifkan semua kemampuan pemeliharaan dengan akun bawaan.",
+ "config-db-install-account": "Akun pengguna untuk instalasi",
+ "config-db-username": "Nama pengguna basis data:",
+ "config-db-password": "Kata sandi basis data:",
+ "config-db-password-empty": "Silakan masukkan sandi untuk pengguna basis data baru: $1.\nMeskipun dimungkinkan untuk membuat pengguna tanpa sandi, hal itu tidak aman.",
+ "config-db-username-empty": "Anda harus memasukkan nilai untuk \"{{int:config-db-username}}\".",
+ "config-db-install-username": "Masukkan nama pengguna yang akan digunakan untuk terhubung ke basis data selama proses instalasi.\nIni bukan nama pengguna akun MediaWiki, melainkan nama pengguna untuk basis data Anda.",
+ "config-db-install-password": "Masukkan sandi yang akan digunakan untuk terhubung ke basis data selama proses instalasi.\nIni bukan sandi untuk akun MediaWiki, melainkan sandi untuk basis data Anda.",
+ "config-db-install-help": "Masukkan nama pengguna dan sandi yang akan digunakan untuk terhubung ke basis data pada saat proses instalasi.",
+ "config-db-account-lock": "Gunakan nama pengguna dan kata sandi yang sama selama operasi normal",
+ "config-db-wiki-account": "Akun pengguna untuk operasi normal",
+ "config-db-wiki-help": "Masukkan nama pengguna dan sandi yang akan digunakan untuk terhubung ke basis data wiki selama operasi normal.\nJika akun tidak ada, akun instalasi memiliki hak yang memadai, akun pengguna ini akan dibuat dengan hak akses minimum yang diperlukan untuk mengoperasikan wiki.",
+ "config-db-prefix": "Prefiks tabel basis data:",
+ "config-db-prefix-help": "Jika Anda perlu berbagi satu basis data di antara beberapa wiki, atau antara MediaWiki dan aplikasi web lain, Anda dapat memilih untuk menambahkan prefiks terhadap semua nama tabel demi menghindari konflik.\nJangan gunakan spasi.\n\nPrefiks ini biasanya dibiarkan kosong.",
+ "config-db-charset": "Set karakter basis data",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 biner",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "UTF-8 yang kompatibel balik dengan MySQL 4.0",
+ "config-charset-help": "'''Peringatan:''' Jika Anda menggunakan '''UTF-8 kompatibel balik''' pada MySQL 4.1+, dan kemudian mencadangkan basis data dengan <code>mysqldump</code>, proses itu mungkin menghancurkan semua karakter non-ASCII dan merusak cadangan Anda tanpa dapat dikembalikan!\n\nDalam '''modus biner''', MediaWiki menyimpan teks UTF-8 ke basis data dalam bidang biner.\nIni lebih efisien dibandingkan modus UTF-8 MySQL dan memungkinkan Anda untuk menggunakan berbagai karakter Unicode.\nDalam '''modus UTF-8''', MySQL akan tahu apa set karakter data anda dan dapat menyajikan dan mengubahnya denga tepat, namun tidak akan mengizinkan Anda menyimpan karakter di atas [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mysql-old": "MySQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.",
+ "config-db-port": "Porta basis data:",
+ "config-db-schema": "Skema untuk MediaWiki",
+ "config-db-schema-help": "Skema ini biasanya berjalan baik.\nUbah hanya jika Anda tahu Anda perlu mengubahnya.",
+ "config-pg-test-error": "Tidak dapat terhubung ke basis data <strong>$1</strong>: $2",
+ "config-sqlite-dir": "Direktori data SQLite:",
+ "config-sqlite-dir-help": "SQLite menyimpan semua data dalam satu berkas.\n\nDirektori yang Anda berikan harus dapat ditulisi oleh server web selama instalasi.\n\nDirektori itu '''tidak''' boleh dapat diakses melalui web, inilah sebabnya kami tidak menempatkannya bersama dengan berkas PHP lain.\n\nPenginstal akan membuat berkas <code>.htaccess</code> bersamaan dengan itu, tetapi jika gagal, orang dapat memperoleh akses ke basis data mentah Anda.\nItu termasuk data mentah pengguna (alamat surel, hash sandi) serta revisi yang dihapus dan data lainnya yang dibatasi pada wiki.\n\nPertimbangkan untuk menempatkan basis data di tempat lain, misalnya di <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Tablespace bawaan:",
+ "config-oracle-temp-ts": "Tablespace sementara:",
+ "config-type-mysql": "MySQL (atau yang kompatibel)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "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. ([http://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 untuk MySQL. Mungkin ada beberapa bug terbuka dan alternatif ini tidak direkomendasikan untuk dipakai dalam lingkungan produksi. ([http://www.php.net/manual/en/pgsql.installation.php cara mengompilasi 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. ([http://www.php.net/manual/en/sqlsrv.installation.php Bagaimana cara mengkompilasi PHP dengan dukungan SQLSRV])",
+ "config-header-mysql": "Pengaturan MySQL",
+ "config-header-postgres": "Pengaturan PostgreSQL",
+ "config-header-sqlite": "Pengaturan SQLite",
+ "config-header-oracle": "Pengaturan Oracle",
+ "config-header-mssql": "Setelan Microsoft SQL Server",
+ "config-invalid-db-type": "Jenis basis data tidak sah",
+ "config-missing-db-name": "Anda harus memasukkan nilai untuk \"{{int:config-db-name}}\"",
+ "config-missing-db-host": "Anda harus memasukkan nilai untuk \"{{int:config-db-host}}\"",
+ "config-missing-db-server-oracle": "Anda harus memasukkan nilai untuk \"{{int:config-db-host-oracle}}\"",
+ "config-invalid-db-server-oracle": "TNS basis data \"$1\" tidak sah.\nGunakan baik \"Nama TNS\" atau string \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Metode Penamaan Oracle]).",
+ "config-invalid-db-name": "Nama basis data \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).",
+ "config-invalid-db-prefix": "Prefiks basis data \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).",
+ "config-connection-error": "$1.\n\nPeriksa nama inang, pengguna, dan sandi di bawah ini dan coba lagi.",
+ "config-invalid-schema": "Skema MediaWiki \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), dan garis bawah (_).",
+ "config-db-sys-create-oracle": "Penginstal hanya mendukung penggunaan akun SYSDBA untuk membuat akun baru.",
+ "config-db-sys-user-exists-oracle": "Akun pengguna \"$1\"sudah ada. SYSDBA hanya dapat digunakan untuk membuat akun baru!",
+ "config-postgres-old": "PostgreSQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 atau yang lebih baru dibutuhkan. Anda memiliki versi $2.",
+ "config-sqlite-name-help": "Pilih nama yang mengidentifikasi wiki Anda.\nJangan gunakan spasi atau tanda hubung.\nNama ini akan digunakan untuk nama berkas data SQLite.",
+ "config-sqlite-parent-unwritable-group": "Tidak dapat membuat direktori data <code><nowiki>$1</nowiki></code>, karena direktori induk <code><nowiki>$2</nowiki></code> tidak bisa ditulisi oleh server web.\n\nPenginstal telah menentukan pengguna yang menjalankan server web Anda.\nBuat direktori <code><nowiki>$3</nowiki></code> menjadi dapat ditulisi olehnya.\nPada sistem Unix/Linux lakukan hal berikut:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Tidak dapat membuat direktori data <code><nowiki>$1</nowiki></code>, karena direktori induk <code><nowiki>$2</nowiki></code> tidak bisa ditulisi oleh server web.\n\nPenginstal tidak dapat menentukan pengguna yang menjalankan server web Anda.\nBuat direktori <code><nowiki>$3</nowiki></code> menjadi dapat ditulisi oleh semua orang.\nPada sistem Unix/Linux lakukan hal berikut:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Kesalahan saat membuat direktori data \"$1\".\nPeriksa lokasi dan coba lagi.",
+ "config-sqlite-dir-unwritable": "Tidak dapat menulisi direktori \"$1\".\nUbah hak akses direktori sehingga server web dapat menulis ke sana, dan coba lagi.",
+ "config-sqlite-connection-error": "$1.\n\nPeriksa direktori data dan nama basis data di bawah dan coba lagi.",
+ "config-sqlite-readonly": "Berkas <code>$1</code> tidak dapat ditulisi.",
+ "config-sqlite-cant-create-db": "Tidak dapat membuat berkas basis data <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP tidak memiliki dukungan FTS3, tabel dituruntarafkan.",
+ "config-can-upgrade": "Ada tabel MediaWiki di basis dataini.\nUntuk memperbaruinya ke MediaWiki $1, klik '''Lanjut'''.",
+ "config-upgrade-done": "Pemutakhiran selesai.\n\nAnda sekarang dapat [$1 mulai menggunakan wiki Anda].\n\nJika Anda ingin membuat ulang berkas <code>LocalSettings.php</code>, klik tombol di bawah ini.\nTindakan ini '''tidak dianjurkan''' kecuali jika Anda mengalami masalah dengan wiki Anda.",
+ "config-upgrade-done-no-regenerate": "Pemutakhiran selesai.\n\nAnda sekarang dapat [$1 mulai menggunakan wiki Anda].",
+ "config-regenerate": "Regenerasi LocalSettings.php →",
+ "config-show-table-status": "Kueri <code>SHOW TABLE STATUS</code> gagal!",
+ "config-unknown-collation": "'''Peringatan:''' basis data menggunakan kolasi yang tidak dikenal.",
+ "config-db-web-account": "Akun basis data untuk akses web",
+ "config-db-web-help": "Masukkan nama pengguna dan sandi yang akan digunakan server web untuk terhubung ke server basis data saat operasi normal wiki.",
+ "config-db-web-account-same": "Gunakan akun yang sama seperti untuk instalasi",
+ "config-db-web-create": "Buat akun jika belum ada",
+ "config-db-web-no-create-privs": "Akun Anda berikan untuk instalasi tidak memiliki hak yang cukup untuk membuat akun.\nAkun yang Anda berikan harus sudah ada.",
+ "config-mysql-engine": "Mesin penyimpanan:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>Peringatan:</strong> Anda telah memilih MyISAM sebagai mesin penyimpanan MySQL, yang tidak dianjurkan untuk digunakan dengan MediaWiki, karena:\n * nyaris tidak mendukung operasi bersamaan karena penguncian tabel\n * lebih rentan terhadap korupsi daripada mesin lain\n * basis kode MediaWiki tidak selalu menangani MyISAM sebagaimana mestinya\n\nJika instalasi MySQL Anda mendukung InnoDB, sangat disarankan bagi Anda memilih itu.\nJika instalasi MySQL tidak mendukung InnoDB, mungkin sudah waktunya untuk pemutakhiran.",
+ "config-mysql-only-myisam-dep": "<strong>Peringatan:</strong> MyISAM adalah satu-satunya mesin penyimpanan yang tersedia untuk MySQL pada mesin ini, dan hal ini tidak dianjurkan untuk digunakan dengan MediaWiki, karena:\n* hampir tidak mendukung konkurensi karena penguncian tabel\n* basis kode MediaWiki tidak selalu menangani MyISAM sebagaimana mestinya\n\nInstalasi MySQL Anda tidak mendukung InnoDB, mungkin sudah waktunya untuk peningkatan.",
+ "config-mysql-engine-help": "'''InnoDB''' hampir selalu merupakan pilihan terbaik karena memiliki dukungan konkurensi yang baik.\n\n'''MyISAM''' mungkin lebih cepat dalam instalasi pengguna-tunggal atau hanya-baca.\nBasis data MyISAM cenderung lebih sering rusak daripada basis data InnoDB.",
+ "config-mysql-charset": "Set karakter basis data:",
+ "config-mysql-binary": "Biner",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "Dalam '''modus biner''', MediaWiki menyimpan teks UTF-8 untuk basis data dalam bidang biner.\nIni lebih efisien daripada modus UTF-8 MySQL dan memungkinkan Anda untuk menggunakan ragam penuh karakter Unicode.\n\nDalam '''modus UTF-8''', MySQL akan tahu apa set karakter data dan dapat menampilkan dan mengubahnya sesuai keperluan, tetapi tidak akan mengizinkan Anda menyimpan karakter di atas [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mssql-auth": "Jenis otentikasi:",
+ "config-mssql-install-auth": "Pilih jenis otentikasi yang akan digunakan untuk menyambung ke database selama proses instalasi.\nJika Anda memilih \"{{int:config-mssql-windowsauth}}\", kredensial dari pengguna apapun pada server web yang berjalan akan digunakan.",
+ "config-mssql-web-auth": "Pilih jenis otentikasi yang akan digunakan oleh server web untuk menyambung ke server basis data, selama operasi biasa dari wiki.\nJika Anda memilih \"{{int:config-mssql-windowsauth}}\", kredensial dari pengguna apapun pada server web yang berjalan akan digunakan.",
+ "config-mssql-sqlauth": "Otentikasi Server SQL",
+ "config-mssql-windowsauth": "Otentikasi Windows",
+ "config-site-name": "Nama wiki:",
+ "config-site-name-help": "Ini akan muncul di bilah judul peramban dan di berbagai tempat lainnya.",
+ "config-site-name-blank": "Masukkan nama situs.",
+ "config-project-namespace": "Ruang nama proyek:",
+ "config-ns-generic": "Proyek",
+ "config-ns-site-name": "Sama seperti nama wiki: $1",
+ "config-ns-other": "Lainnya (sebutkan)",
+ "config-ns-other-default": "MyWiki",
+ "config-project-namespace-help": "Mengikuti contoh Wikipedia, banyak wiki menyimpan halaman kebijakan mereka terpisah dari halaman konten mereka, dalam \"'''ruang nama proyek'''\".\nSemua judul halaman dalam ruang nama ini diawali dengan prefiks tertentu yang dapat Anda tetapkan di sini.\nBiasanya, prefiks ini berasal dari nama wiki, tetapi tidak dapat berisi karakter tanda baca seperti \"#\" atau \":\".",
+ "config-ns-invalid": "Ruang nama \"<nowiki>$1</nowiki>\" yang ditentukan tidak sah.\nBerikan ruang nama proyek lain.",
+ "config-ns-conflict": "Ruang nama \"<nowiki>$1</nowiki>\" yang diberikan berkonflik dengan ruang nama bawaan MediaWiki.\nTentukan ruang nama proyek yang berbeda.",
+ "config-admin-box": "Akun pengurus",
+ "config-admin-name": "Nama pengguna:",
+ "config-admin-password": "Kata sandi:",
+ "config-admin-password-confirm": "Kata sandi lagi:",
+ "config-admin-help": "Masukkan nama pengguna pilihan Anda di sini, misalnya \"Udin Wiki\".\nIni adalah nama yang akan Anda gunakan untuk masuk ke wiki.",
+ "config-admin-name-blank": "Masukkan nama pengguna pengurus.",
+ "config-admin-name-invalid": "Nama pengguna \"<nowiki>$1</nowiki>\" yang diberikan tidak sah.\nBerikan nama pengguna lain.",
+ "config-admin-password-blank": "Masukkan kata sandi untuk akun pengurus.",
+ "config-admin-password-mismatch": "Dua kata sandi yang Anda masukkan tidak cocok.",
+ "config-admin-email": "Alamat surel:",
+ "config-admin-email-help": "Masukkan alamat surel untuk memungkinkan Anda menerima surel dari pengguna lain, menyetel ulang sandi, dan mendapat pemberitahuan tentang perubahan atas daftar pantauan Anda. Anda dapat mengosongkan bidang ini.",
+ "config-admin-error-user": "Kesalahan internal saat membuat admin dengan nama \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Kesalahan internal saat membuat sandi untuk admin \"<nowiki>$1</nowiki>\":<pre>$2</pre>",
+ "config-admin-error-bademail": "Anda memasukkan alamat surel yang tidak sah",
+ "config-subscribe": "Berlangganan ke [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce milis pengumuman rilis].",
+ "config-subscribe-help": "Ini adalah milis bervolume rendah yang digunakan untuk pengumuman rilis, termasuk pengumuman keamanan penting.\nAnda sebaiknya berlangganan dan memperbarui instalasi MediaWiki saat versi baru keluar.",
+ "config-subscribe-noemail": "Anda mencoba untuk berlangganan milis pengumuman rilis tanpa menyediakan alamat email.\nHarap berikan alamat surel jika Anda ingin berlangganan ke milis.",
+ "config-almost-done": "Anda hampir selesai!\nAnda sekarang dapat melewati sisa konfigurasi dan menginstal wiki sekarang.",
+ "config-optional-continue": "Berikan saya pertanyaan lagi.",
+ "config-optional-skip": "Saya sudah bosan, instal saja wikinya.",
+ "config-profile": "Profil hak pengguna:",
+ "config-profile-wiki": "Wiki terbuka",
+ "config-profile-no-anon": "Pembuatan akun diperlukan",
+ "config-profile-fishbowl": "Khusus penyunting terdaftar",
+ "config-profile-private": "Wiki pribadi",
+ "config-profile-help": "Wiki paling baik bekerja jika Anda membiarkan sebanyak mungkin orang untuk menyunting. Dengan MediaWiki, sangat mudah meninjau perubahan terbaru dan mengembalikan kerusakan yang dilakukan oleh pengguna naif atau berbahaya.\n\nNamun, berbagai kegunaan lain dari MediaWiki telah ditemukan, dan kadang tidak mudah untuk meyakinkan semua orang manfaat dari cara wiki. Jadi, Anda yang menentukan.\n\n'''{{int:config-profile-wiki}}''' memungkinkan setiap orang untuk menyunting, bahkan tanpa masuk.\n'''{{int:config-profile-no-anon}}''' menyediakan akuntabilitas tambahan, tetapi dapat mencegah kontributor biasa.\n\n'''{{int:config-profile-fishbowl}}''' memungkinkan pengguna yang disetujui untuk menyunting, tetapi publik dapat melihat halaman, termasuk riwayatnya.\n'''{{int:config-profile-private}}''' hanya memungkinkan pengguna yang disetujui untuk melihat dan menyunting halaman.\n\nKonfigurasi hak pengguna yang lebih kompleks tersedia setelah instalasi. Lihat [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights/id entri manual terkait].",
+ "config-license": "Hak cipta dan lisensi:",
+ "config-license-none": "Tidak ada lisensi",
+ "config-license-cc-by-sa": "Creative Commons Atribusi Berbagi Serupa",
+ "config-license-cc-by": "Creative Commons Atribusi",
+ "config-license-cc-by-nc-sa": "Creative Commons Atribusi Nonkomersial Berbagi Serupa",
+ "config-license-cc-0": "Creative Commons Zero (Domain Publik)",
+ "config-license-gfdl": "Lisensi Dokumentasi Bebas GNU 1.3 atau versi terbaru",
+ "config-license-pd": "Domain Umum",
+ "config-license-cc-choose": "Pilih lisensi Creative Commons kustom",
+ "config-license-help": "Banyak wiki publik melisensikan semua kontribusi di bawah [http://freedomdefined.org/Definition lisensi bebas].\nHal ini membantu menciptakan rasa kepemilikan komunitas dan mendorong kontribusi jangka panjang.\nHal ini umumnya tidak diperlukan untuk wiki pribadi atau perusahaan.\n\nJika Anda ingin dapat menggunakan teks dari Wikipedia dan Anda ingin agar Wikipedia dapat menerima teks yang disalin dari wiki Anda, Anda harus memilih'''Creative Commons Attribution Share Alike'''.\n\nWikipedia sebelumnya menggunakan GNU Free Documentation License.\nLisensi ini masih sah, namun sulit dipahami.\nSelain itu, sulit untuk menggunakan ulang konten yang dilisensikan di bawah GFDL.",
+ "config-email-settings": "Pengaturan surel",
+ "config-enable-email": "Aktifkan surel keluar",
+ "config-enable-email-help": "Jika Anda ingin mengaktifkan surel, [http://www.php.net/manual/en/mail.configuration.php setelah surel PHP] perlu dikonfigurasi dengan benar.\nJika Anda tidak perlu fitur surel, Anda dapat menonaktifkannya di sini.",
+ "config-email-user": "Aktifkan surel antarpengguna",
+ "config-email-user-help": "Memungkinkan semua pengguna untuk saling berkirim surel jika mereka mengaktifkan pilihan tersebut dalam preferensi mereka.",
+ "config-email-usertalk": "Aktifkan pemberitahuan perubahan halaman pembicaraan pengguna",
+ "config-email-usertalk-help": "Memungkinkan pengguna untuk menerima pemberitahuan tentang perubahan halaman pembicaraan pengguna, jika pilihan tersebut telah diaktifkan dalam preferensi mereka.",
+ "config-email-watchlist": "Aktifkan pemberitahuan daftar pantau",
+ "config-email-watchlist-help": "Memungkinkan pengguna untuk menerima pemberitahuan tentang perubahan halaman yang ada dalam daftar pantauan mereka, jika pilihan tersebut telah diaktifkan dalam preferensi mereka.",
+ "config-email-auth": "Aktifkan otentikasi surel",
+ "config-email-auth-help": "Jika opsi ini diaktifkan, pengguna harus mengonfirmasi alamat surel dengan menggunakan pranala yang dikirim kepadanya setiap kali mereka mengatur atau mengubahnya.\nHanya alamat surel yang dikonfirmasi yang dapat menerima surel dari pengguna lain atau surel pemberitahuan perubahan.\nPenetapan opsi ini '''direkomendasikan''' untuk wiki publik karena adanya potensi penyalahgunaan fitur surel.",
+ "config-email-sender": "Alamat surel balasan:",
+ "config-email-sender-help": "Masukkan alamat surel untuk digunakan sebagai alamat pengirim pada surel keluar.\nAlamat ini akan menerima pentalan.\nBanyak server surel mensyaratkan paling tidak bagian nama domain yang sah.",
+ "config-upload-settings": "Pengunggahan gambar dan berkas",
+ "config-upload-enable": "Aktifkan pengunggahan berkas",
+ "config-upload-help": "Pengunggahan berkas berpotensi memaparkan server Anda dengan risiko keamanan.\nUntuk informasi lebih lanjut, baca [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security/id manual keamanan].\n\nUntuk mengaktifkan pengunggahan berkas, ubah modus subdirektori <code>images</code> di bawah direktori akar MediaWiki agar server web dapat menulis ke sana.\nKemudian aktifkan opsi ini.",
+ "config-upload-deleted": "Direktori untuk berkas terhapus:",
+ "config-upload-deleted-help": "Pilih direktori tempat mengarsipkan berkas yang dihapus.\nIdealnya, direktori ini tidak boleh dapat diakses dari web.",
+ "config-logo": "URL logo:",
+ "config-logo-help": "Kulit bawaan MediaWiki memberikan ruang untuk logo berukuran 135x160 piksel di atas menu bilah samping.\nUnggah gambar dengan ukuran yang sesuai, lalu masukkan URL di sini.\n\nAnda dapat menggunakan <code>$wgStylePath</code> atau <code>$wgScriptPath</code> jika logo Anda relatif terhadap jalur (path) ini.\n\nJika Anda tidak ingin menyertakan logo, biarkan kotak ini kosong.",
+ "config-instantcommons": "Aktifkan Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] adalah fitur yang memungkinkan wiki untuk menggunakan gambar, suara, dan media lain dari [//commons.wikimedia.org/ Wikimedia Commons].\nUntuk melakukannya, MediaWiki memerlukan akses ke Internet.\n\nUntuk informasi lebih lanjut tentang fitur ini, termasuk petunjuk tentang cara untuk mengatur untuk wiki selain Wikimedia Commons, baca [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos manual].",
+ "config-cc-error": "Pemilih lisensi Creative Commons tidak memberikan hasil.\nMasukkan nama lisensi secara manual.",
+ "config-cc-again": "Pilih lagi...",
+ "config-cc-not-chosen": "Pilih lisensi Creative Commons yang Anda inginkan dan klik \"lanjutkan\".",
+ "config-advanced-settings": "Konfigurasi lebih lanjut",
+ "config-cache-options": "Pengaturan untuk penyinggahan objek:",
+ "config-cache-help": "Penyinggahan objek digunakan untuk meningkatkan kecepatan MediaWiki dengan menyinggahkan data yang sering digunakan.\nSitus berukuran sedang hingga besar sangat dianjurkan untuk mengaktifkan fitur ini, dan situs kecil juga akan merasakan manfaatnya.",
+ "config-cache-none": "Tidak ada penyinggahan (tidak ada fungsi yang dibuang, tetapi kecepatan dapat terpengaruh pada situs wiki yang besar)",
+ "config-cache-accel": "Penyinggahan objek PHP (APC, XCache atau WinCache)",
+ "config-cache-memcached": "Gunakan Memcached (memerlukan setup dan konfigurasi tambahan)",
+ "config-memcached-servers": "Server Memcached:",
+ "config-memcached-help": "Daftar alamat IP yang digunakan untuk Memcached.\nHarus dispesifikasikan per baris berikut porta yang akan digunakan. Contoh:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Anda memilih Memcached sebagai jenis singgahan, tetapi tidak menentukan server apa pun.",
+ "config-memcache-badip": "Anda memasukkan alamat IP yang tidak sah untuk Memcached: $1 .",
+ "config-memcache-noport": "Anda tidak menentukan suatu porta untuk digunakan oleh server Memcached: $1.\nJika Anda tidak tahu porta tersebut, porta bawaan adalah 11211.",
+ "config-memcache-badport": "Nomor porta Memcached harus antara $1 dan $2.",
+ "config-extensions": "Ekstensi",
+ "config-extensions-help": "Ekstensi yang tercantum di atas terdeteksi di direktori <code>./extensions</code>.\n\nEkstensi tersebut mungkin memerlukan konfigurasi tambahan, tetapi Anda dapat mengaktifkannya sekarang.",
+ "config-install-alreadydone": "'''Peringatan:''' Anda tampaknya telah menginstal MediaWiki dan mencoba untuk menginstalnya lagi.\nLanjutkan ke halaman berikutnya.",
+ "config-install-begin": "Dengan menekan \"{{int:config-continue}}\", Anda akan memulai instalasi MediaWiki.\nJika Anda masih ingin membuat perubahan, tekan \"{{int:config-back}}\".",
+ "config-install-step-done": "selesai",
+ "config-install-step-failed": "gagal",
+ "config-install-extensions": "Termasuk ekstensi",
+ "config-install-database": "Menyiapkan basis data",
+ "config-install-schema": "Membuat skema",
+ "config-install-pg-schema-not-exist": "Skema PostgreSQL tidak tersedia.",
+ "config-install-pg-schema-failed": "Pembuatan tabel gagal.\nPastikan bahwa pengguna \"$1\" dapat menulis ke skema \"$2\".",
+ "config-install-pg-commit": "Melakukan perubahan",
+ "config-install-pg-plpgsql": "Memeriksa bahasa PL / pgSQL",
+ "config-pg-no-plpgsql": "Anda perlu menginstal bahasa PL/pgSQL pada basis data $1",
+ "config-pg-no-create-privs": "Akun yang Anda tetapkan untuk instalasi tidak memiliki hak yang cukup untuk membuat akun.",
+ "config-pg-not-in-role": "Akun yang ditentukan untuk pengguna web sudah ada.\nAkun yang ditentukan untuk instalasi tidak superuser dan bukan anggota dari peran pengguna Web, sehingga tidak dapat membuat objek yang dimiliki oleh pengguna web.\n\nMediaWiki saat ini membutuhkan bahwa tabel dimiliki oleh pengguna web. Silakan tentukan nama account web lain, atau klik \"back\" dan tentukan pengguna yang terinstal sesuai istimewa.",
+ "config-install-user": "Membuat pengguna basis data",
+ "config-install-user-alreadyexists": "Pengguna \"$1\" sudah ada",
+ "config-install-user-create-failed": "Pembuatan pengguna \"$1\" gagal: $2",
+ "config-install-user-grant-failed": "Memberikan izin untuk pengguna \"$1\" gagal: $2",
+ "config-install-user-missing": "Pengguna \"$1\" yang dimaksud tidak ditemukan.",
+ "config-install-user-missing-create": "Akun yang ditentukan \"$1\" tidak ada.\nSilahkan klik kotak centang \"Buat akun\" di bawah ini jika Anda ingin membuatnya.",
+ "config-install-tables": "Membuat tabel",
+ "config-install-tables-exist": "'''Peringatan''': Tabel MediaWiki sepertinya sudah ada.\nMelompati pembuatan.",
+ "config-install-tables-failed": "'''Kesalahan''': Pembuatan tabel gagal dengan kesalahan berikut: $1",
+ "config-install-interwiki": "Mengisi tabel bawaan antarwiki",
+ "config-install-interwiki-list": "Tidak dapat menemukan berkas <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Peringatan''': Tabel antarwiki tampaknya sudah memiliki entri.\nMengabaikan daftar bawaan.",
+ "config-install-stats": "Inisialisasi statistik",
+ "config-install-keys": "Membuat kunci rahasia",
+ "config-insecure-keys": "'''Peringatan:''' {{PLURAL:$2|Suatu|Beberapa}} kunci aman ($1) yang dibuat selama instalasi {{PLURAL:$2|tidak|tidak}} benar-benar aman. Pertimbangkan untuk mengubah {{PLURAL:$2|kunci|kunci-kunci}} tersebut secara manual.",
+ "config-install-sysop": "Membuat akun pengguna pengurus",
+ "config-install-subscribe-fail": "Tidak dapat berlangganan mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL tidak diinstal dan <code>allow_url_fopen</code> tidak tersedia.",
+ "config-install-mainpage": "Membuat halaman utama dengan konten bawaan",
+ "config-install-extension-tables": "Pembuatan tabel untuk ekstensi yang diaktifkan",
+ "config-install-mainpage-failed": "Tidak dapat membuat halaman utama: $1",
+ "config-install-done": "'''Selamat!'''\nAnda telah berhasil menginstal MediaWiki.\n\nPenginstal telah membuat berkas <code>LocalSettings.php</code>.\nBerkas itu berisi semua konfigurasi Anda.\n\nAnda perlu mengunduh berkas itu dan meletakkannya di direktori instalasi wiki (direktori yang sama dengan index.php). Pengunduhan akan dimulai secara otomatis.\n\nJika pengunduhan tidak terjadi, atau jika Anda membatalkannya, Anda dapat mengulangi pengunduhan dengan mengeklik tautan berikut:\n\n$3\n\n'''Catatan''': Jika Anda tidak melakukannya sekarang, berkas konfigurasi yang dihasilkan ini tidak akan tersedia lagi setelah Anda keluar dari proses instalasi tanpa mengunduhnya.\n\nSetelah melakukannya, Anda dapat '''[$2 memasuki wiki Anda]'''.",
+ "config-download-localsettings": "Unduh <code>LocalSettings.php</code>",
+ "config-help": "bantuan",
+ "config-help-tooltip": "klik untuk memperluas",
+ "config-nofile": "Berkas \"$1\" tidak dapat ditemukan. Mungkin sudah dihapus?",
+ "config-extension-link": "Tahukah Anda bahwa wiki Anda mendukung [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions ekstensi]?\n\nAnda dapat menjelajahi [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category ekstensi menurut kategori] atau [//www.mediawiki.org/wiki/Extension_Matrix Ekstensi Matriks] untuk melihat daftar lengkap ekstensi.",
+ "mainpagetext": "'''MediaWiki telah terpasang dengan sukses'''.",
+ "mainpagedocfooter": "Silakan baca [//www.mediawiki.org/wiki/Help:Contents Panduan Pengguna] untuk cara penggunaan perangkat lunak wiki ini.\n\n== Memulai penggunaan ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/id Daftar pengaturan konfigurasi]\n* [//www.mediawiki.org/wiki/Manual:FAQ/id Daftar pertanyaan yang sering diajukan mengenai MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Terjemahkan MediaWiki ke bahasa Anda]"
+}
diff --git a/includes/installer/i18n/ie.json b/includes/installer/i18n/ie.json
new file mode 100644
index 00000000..32bcfd8a
--- /dev/null
+++ b/includes/installer/i18n/ie.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Software del wiki installat con successe.'''"
+}
diff --git a/includes/installer/i18n/ig.json b/includes/installer/i18n/ig.json
new file mode 100644
index 00000000..15385d2a
--- /dev/null
+++ b/includes/installer/i18n/ig.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ukabia"
+ ]
+ },
+ "config-back": "← Laàzú",
+ "config-continue": "Gawazie →",
+ "config-page-language": "Ásụ̀sụ̀",
+ "config-page-name": "Áhà",
+ "config-page-install": "Sụ̀ímé",
+ "config-restart": "Eeh, bìdówárí ya.",
+ "config-admin-password": "Okwúngáfè:",
+ "config-admin-password-confirm": "Okwúngáfè mgbe ozor:",
+ "mainpagetext": "'''MediaWiki a banyélé nke oma.'''",
+ "mainpagedocfooter": "Gbàkpó [//meta.wikimedia.org/wiki/Help:Contents Ǹdù Ọ'bànifé] màkà ụmá màkà Í jí ngwa nsónùsòrò bu wiki.\n\n== I bídó ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Ndétu ndósé ihe]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce wéfù ndétu nke ozi MediaWiki]"
+}
diff --git a/includes/installer/i18n/ilo.json b/includes/installer/i18n/ilo.json
new file mode 100644
index 00000000..edb44f16
--- /dev/null
+++ b/includes/installer/i18n/ilo.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Sibaballigi a nainstolar ti MediaWiki.'''"
+}
diff --git a/includes/installer/i18n/io.json b/includes/installer/i18n/io.json
new file mode 100644
index 00000000..97c275c3
--- /dev/null
+++ b/includes/installer/i18n/io.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Wyvernoid"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki instalesis sucese.'''",
+ "mainpagedocfooter": "Videz la [//meta.wikimedia.org/wiki/Help:Contents Guidilo por Uzanti] por informo pri uzar la wiki programo.\n\n== Komencar ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Listo di ''Configuration setting'']\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki OQQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki nova versioni posto-listo]"
+}
diff --git a/includes/installer/i18n/is.json b/includes/installer/i18n/is.json
new file mode 100644
index 00000000..ce15eaca
--- /dev/null
+++ b/includes/installer/i18n/is.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Uppsetning á MediaWiki heppnaðist.'''",
+ "mainpagedocfooter": "Ráðfærðu þig við [//meta.wikimedia.org/wiki/Help:Contents Notandahandbókina] fyrir frekari upplýsingar um notkun wiki-hugbúnaðarins.\n\n== Fyrir byrjendur ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Listi yfir uppsetningarstillingar]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki Algengar spurningar MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Póstlisti MediaWiki-útgáfa]"
+}
diff --git a/includes/installer/i18n/it.json b/includes/installer/i18n/it.json
new file mode 100644
index 00000000..a61fff29
--- /dev/null
+++ b/includes/installer/i18n/it.json
@@ -0,0 +1,334 @@
+{
+ "@metadata": {
+ "authors": [
+ "Beta16",
+ "Darth Kule",
+ "F. Cosoleto",
+ "Gianfranco",
+ "Karika",
+ "아라",
+ "Lucas2",
+ "Ontsed",
+ "Seb35",
+ "Nemo bis"
+ ]
+ },
+ "config-desc": "Il programma di installazione per MediaWiki",
+ "config-title": "Installazione MediaWiki $1",
+ "config-information": "Informazioni",
+ "config-localsettings-upgrade": "È stato rilevato un file <code>LocalSettings.php</code>.\nPer aggiornare questa installazione, si prega di inserire il valore di <code>$wgUpgradeKey</code> nella casella qui sotto.\nLo potete trovare in <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "È stato rilevato un file <code>LocalSettings.php</code>.\nPer aggiornare questa installazione, eseguire <code>update.php</code>",
+ "config-localsettings-key": "Chiave di aggiornamento:",
+ "config-localsettings-badkey": "La chiave che hai fornito non è corretta.",
+ "config-upgrade-key-missing": "È stata rilevata un'installazione esistente di MediaWiki.\nPer aggiornare questa installazione, si prega di inserire la seguente riga nella parte inferiore del tuo <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Il file <code>LocalSettings.php</code> esistente sembra essere incompleto.\nLa variabile $1 non è impostata.\nCambia <code>LocalSettings.php</code> in modo che questa variabile sia impostata e fai clic su \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Si è verificato un errore durante la connessione al database utilizzando le impostazioni specificate in <code>LocalSettings.php</code>. Si prega di correggere queste impostazioni e riprovare.\n\n$1",
+ "config-session-error": "Errore nell'avvio della sessione: $1",
+ "config-session-expired": "I dati della sessione sembrano essere scaduti.\nLe sessioni sono configurate per una durata di $1.\nPuoi aumentarla impostando <code>session.gc_maxlifetime</code> nel file php.ini.\nRiavvia il processo di installazione.",
+ "config-no-session": "I dati della sessione sono andati persi!\nControlla il tuo file php.ini ed assicurati che <code>session.save_path</code> è impostato su una directory appropriata.",
+ "config-your-language": "La tua lingua:",
+ "config-your-language-help": "Seleziona una lingua da utilizzare durante il processo di installazione.",
+ "config-wiki-language": "La lingua del wiki:",
+ "config-wiki-language-help": "Seleziona la lingua che verrà prevalentemente usata nel wiki.",
+ "config-back": "← Indietro",
+ "config-continue": "Continua →",
+ "config-page-language": "Lingua",
+ "config-page-welcome": "Benvenuti in MediaWiki!",
+ "config-page-dbconnect": "Connessione al database",
+ "config-page-upgrade": "Aggiornamento dell'installazione esistente",
+ "config-page-dbsettings": "Impostazioni del database",
+ "config-page-name": "Nome",
+ "config-page-options": "Opzioni",
+ "config-page-install": "Installa",
+ "config-page-complete": "Completa!",
+ "config-page-restart": "Riavvio installazione",
+ "config-page-readme": "Leggimi",
+ "config-page-releasenotes": "Note di versione",
+ "config-page-copying": "Copia",
+ "config-page-upgradedoc": "Aggiornamento",
+ "config-page-existingwiki": "Wiki esistenti",
+ "config-help-restart": "Vuoi cancellare tutti i dati salvati che hai inserito e riavviare il processo di installazione?",
+ "config-restart": "Sì, riavvia",
+ "config-welcome": "=== Controllo dell'ambiente ===\nSaranno eseguiti controlli di base per vedere se questo ambiente è adatto per l'installazione di MediaWiki.\nRicordati di includere queste informazioni se chiedi assistenza su come completare l'installazione.",
+ "config-copyright": "=== Copyright e termini ===\n\n$1\n\nQuesto programma è un software libero; puoi redistribuirlo e/o modificarlo secondo i termini della GNU General Public License, come pubblicata dalla Free Software Foundation; o la versione 2 della Licenza o (a propria scelta) qualunque versione successiva.\n\nQuesto programma è distribuito nella speranza che sia utile, ma SENZA ALCUNA GARANZIA; senza neppure la garanzia implicita di NEGOZIABILITÀ o di APPLICABILITÀ PER UN PARTICOLARE SCOPO.\nSi veda la GNU General Public License per maggiori dettagli.\n\nQuesto programma deve essere distribuito assieme ad <doclink href=Copying>una copia della GNU General Public License</doclink>; in caso contrario, se ne può ottenere una scrivendo alla Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA oppure [http://www.gnu.org/copyleft/gpl.html leggerla in rete].",
+ "config-sidebar": "* [//www.mediawiki.org Pagina principale MediaWiki]\n* [//www.mediawiki.org/wiki/Aiuto:Guida ai contenuti per utenti]\n* [//www.mediawiki.org/wiki/Manuale:Guida ai contenuti per admin]\n* [//www.mediawiki.org/wiki/Manuale:FAQ FAQ]\n----\n* <doclink href=Readme>Leggimi</doclink>\n* <doclink href=ReleaseNotes>Note di versione</doclink>\n* <doclink href=Copying>Copie</doclink>\n* <doclink href=UpgradeDoc>Aggiornamenti</doclink>",
+ "config-env-good": "L'ambiente è stato controllato.\nÈ possibile installare MediaWiki.",
+ "config-env-bad": "L'ambiente è stato controllato.\nNon è possibile installare MediaWiki.",
+ "config-env-php": "PHP $1 è installato.",
+ "config-env-hhvm": "HHVM $1 è installato.",
+ "config-unicode-using-utf8": "Usa Brion Vibber's utf8_normalize.so per la normalizzazione Unicode.",
+ "config-unicode-using-intl": "Usa [http://pecl.php.net/intl l'estensione PECL intl] per la normalizzazione Unicode.",
+ "config-unicode-pure-php-warning": "'''Attenzione:''' [http://pecl.php.net/intl l'estensione PECL intl] non è disponibile per gestire la normalizzazione Unicode, così si usa la lenta implementazione in puro PHP.\nSe esegui un sito ad alto traffico, dovresti leggere alcune considerazioni sulla [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizzazione Unicode].",
+ "config-unicode-update-warning": "'''Attenzione:''' La versione installata del gestore per la normalizzazione Unicode usa una vecchia versione della libreria [http://site.icu-project.org/ del progetto ICU].\nDovresti [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations aggiornare] se ti interessa usare l'Unicode.",
+ "config-no-db": "Impossibile trovare un driver adatto per il database! È necessario installare un driver per PHP.\nI seguenti formati di database sono supportati: $1.\n\nSe compili PHP autonomamente, riconfiguralo attivando un client database, per esempio utilizzando <code>./configure --with-mysqli</code>.\nQualora avessi installato PHP per mezzo di un pacchetto Debian o Ubuntu, allora devi installare anche il pacchetto <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Attenzione''': è presente SQLite $1 mentre è richiesta la versione $2, SQLite non sarà disponibile.",
+ "config-no-fts3": "'''Attenzione''': SQLite è compilato senza il [//sqlite.org/fts3.html modulo FTS3], le funzionalità di ricerca non saranno disponibili su questo backend.",
+ "config-register-globals-error": "<strong>Errore: l'opzione PHP <code>[http://php.net/register_globals register_globals]</code> è abilitata.\nDeve essere disabilitata per continuare con l'installazione.</strong>\nVedi [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] per un aiuto su come farlo.",
+ "config-magic-quotes-gpc": "<strong>Fatale: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] è attivo!</strong>\nQuesta opzione danneggia i dati di input in modo imprevedibile.\nNon puoi installare o utilizzare MediaWiki, a meno che questa opzione sia disabilitata.",
+ "config-magic-quotes-runtime": "'''Errore: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] è attivato!''' Questa opzione interferisce in modo imprevedibile con l'inserimento dei dati. Non è possibile installare o utilizzare MediaWiki a meno che questa opzione non sia disabilitata.",
+ "config-magic-quotes-sybase": "'''Errore: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] è attivato!''' Questa opzione interferisce in modo imprevedibile con l'inserimento dei dati. Non è possibile installare o utilizzare MediaWiki a meno che questa opzione non sia disabilitata.",
+ "config-mbstring": "'''Errore: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] è attivato!''' Questa opzione causa errori e può interferire in modo imprevedibile coi dati. Non è possibile installare o utilizzare MediaWiki a meno che questa opzione non sia disabilitata.",
+ "config-safe-mode": "'''Attenzione:''' [http://www.php.net/features.safe-mode safe mode] è attivato!\nQuesta opzione potrebbe causare problemi, in particolare nel caricamento di documenti e nel supporto delle funzioni <code>math</code>.",
+ "config-xml-bad": "Il modulo XML di PHP è mancante.\nMediaWIki necessita di funzioni presenti in questo modulo e non funzionerà con la configurazione corrente.\nSe si sta eseguendo Mandrake, installare il paccketto php-xml.",
+ "config-pcre-old": "<strong>Errore fatale:</strong> si richiede PCRE $1 o successivo.\nIl tuo file binario PHP è collegato con PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/Maggiori informazioni su PCRE].",
+ "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-ctype": "'''Errore''': PHP deve essere compilato con il supporto per l'[http://www.php.net/manual/it/ctype.installation.php estensione Ctype].",
+ "config-iconv": "<strong>Fatale:</strong> PHP deve essere compilato con il supporto per l'[http://www.php.net/manual/en/iconv.installation.php estensione iconv].",
+ "config-json": "'''Errore:''' PHP è stato compilato senza il supporto per JSON. E' necessario installare l'estensione PHP per JSON o l'estensione [http://pecl.php.net/package/jsonc PECL jsonc] prima di installare MediaWiki.\n* L'estensione PHP è inclusa in Red Hat Enterprise Linux (CentOS) 5 e 6, ma deve essere abilitata in <code>/etc/php.ini</code> o <code>/etc/php.d/json.ini</code>.\n* Alcune distribuzioni di Linux pubblicate dopo il maggio 2013 omettono l'estensione PHP, e al posto utilizzano l'estensione PECL come <code>php5-json</code> o <code>php-pecl-jsonc</code>",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] è installato",
+ "config-apc": "[http://www.php.net/apc APC] è installato",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] è installato",
+ "config-no-cache": "'''Attenzione:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache] non sono stati trovati.\nLa caching degli oggetti non è attivata.",
+ "config-mod-security": "<strong>Attenzione:</strong> Il tuo server web ha il [http://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 [http://modsecurity.org/documentation/ documentazione sul mod_security] o contatta il supporto tecnico del tuo provider di hosting se si verificano errori.",
+ "config-diff3-bad": "GNU diff3 non trovato.",
+ "config-git": "Trovato software di controllo della versione Git: <code>$1</code>.",
+ "config-git-bad": "Software di controllo della versione Git non trovato.",
+ "config-imagemagick": "Trovato ImageMagick: <code>$1</code>.\nLe miniature delle immagini saranno presenti se gli upload vengono abilitati.",
+ "config-gd": "Trovata la GD Graphics Library built-in.\nLe miniature delle immagini saranno presenti se gli upload vengono abilitati.",
+ "config-no-scaling": "Impossibile trovare GD library o ImageMagick.\nLe miniature delle immagini saranno disabilitate.",
+ "config-no-uri": "'''Errore:''' Impossibile determinare l'URI attuale.\nInstallazione interrotta.",
+ "config-no-cli-uri": "'''Attenzione''': <code>--scriptpath</code> non specificato, si utilizza il valore predefinito: <code>$1</code>.",
+ "config-using-server": "Nome server in uso \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "URL del server in uso \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "<strong>Attenzione:</strong> la directory predefinita per i caricamenti <code>$1</code> è vulnerabile all'esecuzione arbitraria di script.\nAnche se MediaWiki controlla tutti i file caricati per rischi alla sicurezza, è fortemente raccomandato di [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security chiudere questa vulnerabilità di sicurezza] prima di abilitare i caricamenti.",
+ "config-no-cli-uploads-check": "<strong>Attenzione:</strong> la directory predefinita per i caricamenti (<code>$1</code>) non è stata verificata per la vulnerabilità sull'esecuzione arbitraria di script durante l'installazione da linea di comando.",
+ "config-brokenlibxml": "Il tuo sistema ha una combinazione di versioni di PHP e libxml2 che è difettosa e che può provocare un danneggiamento non visibile di dati in MediaWiki ed in altre applicazioni per il web.\nAggiorna a libxml2 2.7.3 o successivo ([https://bugs.php.net/bug.php?id=45996 il bug è studiato dal lato PHP]).\nInstallazione interrotta.",
+ "config-suhosin-max-value-length": "Suhosin è installato e limita il parametro GET <code>length</code> a $1 byte.\nIl componente MediaWiki ResourceLoader funzionerà aggirando questo limite, ma riducendo le prestazioni.\nSe possibile, dovresti impostare <code>suhosin.get.max_value_length</code> a 1024 o superiore in <code>php.ini</code>, ed impostare <code>$wgResourceLoaderMaxQueryLength</code> allo stesso valore in <code>LocalSettings.php</code>.",
+ "config-db-type": "Tipo di database:",
+ "config-db-host": "Host del database:",
+ "config-db-host-help": "Se il server del tuo database è su un server diverso, immetti qui il nome dell'host o il suo indirizzo IP.\n\nSe stai utilizzando un web hosting condiviso, il tuo hosting provider dovrebbe fornirti il nome host corretto nella sua documentazione.\n\nSe stai installando su un server Windows con uso di MySQL, l'uso di \"localhost\" potrebbe non funzionare correttamente come nome del server. In caso di problemi, prova a impostare \"127.0.0.1\" come indirizzo IP locale.\n\nSe usi PostgreSQL, lascia questo campo vuoto per consentire di connettersi tramite un socket Unix.",
+ "config-db-host-oracle": "TNS del database:",
+ "config-db-host-oracle-help": "Inserisci un valido [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; un file tnsnames.ora deve essere visibile a questa installazione.<br />Se stai usando la libreria cliente 10g o più recente puoi anche usare il metodo di denominazione [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identifica questo wiki",
+ "config-db-name": "Nome del database:",
+ "config-db-name-help": "Scegli un nome che identifica il tuo wiki.\nNon deve contenere spazi.\n\nSe utilizzi un web hosting condiviso, il tuo hosting provider o ti fornisce uno specifico nome di database da utilizzare, oppure ti consentirà di creare il database tramite un pannello di controllo.",
+ "config-db-name-oracle": "Schema del database:",
+ "config-db-account-oracle-warn": "Ci sono tre scenari supportati per l'installazione di Oracle come database di backend:\n\nSe vuoi creare un'utenza di database come parte del processo di installazione, fornisci un account con ruolo SYSDBA come utenza di database per l'installazione e specifica le credenziali volute per l'utenza di accesso web, altrimenti è possibile creare manualmente l'utenza di accesso web e fornire solo quell'account (se dispone delle autorizzazioni necessario per creare gli oggetti dello schema) o fornire due diverse utenze, una con i permessi di creazione e una per l'accesso web.\n\nScript per la creazione di un'utenza con le autorizzazioni necessarie può essere trovato nella directory \"maintenance/oracle/\" di questa installazione. Tieni presente che l'uso di un'utenza con restrizioni disabiliterà tutte le funzionalità di manutenzione con l'account predefinito.",
+ "config-db-install-account": "Account utente per l'installazione",
+ "config-db-username": "Nome utente del database:",
+ "config-db-password": "Password del database:",
+ "config-db-password-empty": "Inserire una password per il nuovo utente del database: $1.\nAnche se può essere possibile creare utenti senza password, questo non è sicuro.",
+ "config-db-username-empty": "È necessario immettere un valore per \"{{int:config-db-username}}\"",
+ "config-db-install-username": "Inserisci il nome utente che verrà utilizzato per connettersi al database durante il processo di installazione.\nQuesto non è il nome utente dell'account MediaWiki; ma quello per il tuo database.",
+ "config-db-install-password": "Inserisci la password che verrà utilizzato per connettersi al database durante il processo di installazione.\nQuesta non è la password dell'account MediaWiki; ma quella per il tuo database.",
+ "config-db-install-help": "Inserire il nome utente e la password che verranno usate per la connessione al database durante il processo d'installazione.",
+ "config-db-account-lock": "Utilizza lo stesso nome utente e password durante il normale funzionamento",
+ "config-db-wiki-account": "Account utente per il normale funzionamento",
+ "config-db-wiki-help": "Inserisci il nome utente e la password che verrà utilizzato per connettersi al database durante il normale funzionamento del wiki.\nSe l'account non esiste, e l'account di installazione dispone di privilegi sufficienti, verrà creato con privilegi minimi necessari per operare sul wiki.",
+ "config-db-prefix": "Prefisso tabella del database:",
+ "config-db-prefix-help": "Se hai bisogno di condividere un database tra più wiki, o tra MediaWiki e un'altra applicazione web, puoi scegliere di aggiungere un prefisso a tutti i nomi di tabella, per evitare conflitti.\nNon utilizzare spazi.\n\nSolitamente, questo campo viene lasciato vuoto.",
+ "config-db-charset": "Set di caratteri del database",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binario",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 con compatibilità UTF-8",
+ "config-charset-help": "<strong>Attenzione:</strong> se si utilizza <strong>backwards-compatible UTF-8</strong> su MySQL 4.1+, e successivamente si esegue il backup del database con <code>mysqldump</code>, si può distriggere tutti i caratteri non ASCII, danneggiando irreversibilmente i backup!\n\nIn <strong>modalità binaria</strong>, MediaWiki archivia il testo UTF-8 nel database in cambi binari.\nQuesto è più efficiente rispetto alla modalità UTF-8 di MySQL, e consente di utilizzare la gamma completa di caratteri Unicode.\nIn <strong>modalità UTF-8</strong>, MySQL conoscerà in quale set di caratteri sono i tuoi dati, e può presentarli e convertirli in modo appropriato,\nma non ti permetterà di memorizzare i caratteri al di sopra del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mysql-old": "MySQL $1 o una versione successiva è necessaria, rilevata la $2.",
+ "config-db-port": "Porta del database:",
+ "config-db-schema": "Schema per MediaWiki:",
+ "config-db-schema-help": "Questo schema in genere andrà bene.\nDa cambiare solamente se si è sicuri di averne bisogno.",
+ "config-pg-test-error": "Impossibile connettersi al database '''$1''': $2",
+ "config-sqlite-dir": "Directory data di SQLite:",
+ "config-sqlite-dir-help": "SQLite memorizza tutti i dati in un unico file.\n\nLa directory che indicherai deve essere scrivibile dal server web durante l'installazione.\n\nDovrebbe essere <strong>non accessibile via web</strong>, è per questo che non la stiamo mettendo dove ci sono i file PHP.\n\nL'installatore scriverà insieme ad essa un file <code>.htaccess</code>, ma se il tentativo fallisse qualcuno potrebbe avere accesso al database grezzo.\nQuesto include dati utente grezzi (indirizzi, password cifrate) così come versioni eliminate e altri dati ad accesso limitato del wiki.\n\nConsidera l'opportunità di sistemare allo stesso tempo il database da qualche altra parte, per esempio in <code>/var/lib/mediawiki/tuowiki</code>.",
+ "config-oracle-def-ts": "Tablespace di default:",
+ "config-oracle-temp-ts": "Tablespace temporaneo:",
+ "config-type-mysql": "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-mysql-url}} MySQL] è la configurazione preferibile per MediaWiki ed è quella meglio supportata. MediaWiki funziona anche con [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], che sono compatibili con MySQL.([http://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. Ci possono essere alcuni bug minori in sospeso, e non è raccomandato per l'uso in un ambiente di produzione. ([http://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. ([http://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. ([http://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. ([http://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-header-oracle": "Impostazioni Oracle",
+ "config-header-mssql": "Impostazioni di Microsoft SQL Server",
+ "config-invalid-db-type": "Tipo di database non valido",
+ "config-missing-db-name": "È necessario immettere un valore per \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "È necessario immettere un valore per \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "È necessario immettere un valore per \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "TNS database \"$1\" non valido.\nUsa \"TNS Name\" o una stringa \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]).",
+ "config-invalid-db-name": "Nome di database \"$1\" non valido.\nUtilizza soltanto caratteri ASCII come lettere (a-z, A-Z), numeri (0-9), sottolineatura (_) e trattini (-).",
+ "config-invalid-db-prefix": "Prefisso database \"$1\" non valido.\nUtilizza soltanto caratteri ASCII come lettere (a-z, A-Z), numeri (0-9), sottolineatura (_) e trattini (-).",
+ "config-connection-error": "$1.\n\nControlla host, nome utente e password e prova ancora.",
+ "config-invalid-schema": "Schema MediaWiki \"$1\" non valido.\nUsa solo lettere ASCII (a-z, A-Z), numeri (0-9) e caratteri di sottolineatura (_).",
+ "config-db-sys-create-oracle": "Il programma di installazione supporta solo l'utilizzo di un account SYSDBA per la creazione di un nuovo account.",
+ "config-db-sys-user-exists-oracle": "L'account utente \"$1\" esiste già. SYSDBA può essere usato solo per la creazione di un nuovo account!",
+ "config-postgres-old": "PostgreSQL $1 o una versione successiva è necessaria, rilevata la $2.",
+ "config-mssql-old": "Si richiede Microsoft SQL Server $1 o successivo. Tu hai la versione $2.",
+ "config-sqlite-name-help": "Scegli un nome che identifichi il tuo wiki.\nNon utilizzare spazi o trattini.\nQuesto servirà per il nome del file di dati SQLite.",
+ "config-sqlite-parent-unwritable-group": "Non è possibile creare la directory dati <code><nowiki>$1</nowiki></code>, perché la directory superiore <code><nowiki>$2</nowiki></code> non è scrivibile dal webserver.\n\nIl programma di installazione ha determinato l'utente con cui il server web è in esecuzione.\nForniscigli la possibilità di scrivere nella directory <code><nowiki>$3</nowiki></code> per continuare.\nSu un sistema Unix/Linux:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Non è possibile creare la directory dati <code><nowiki>$1</nowiki></code>, perché la directory superiore <code><nowiki>$2</nowiki></code> non è scrivibile dal webserver.\n\nIl programma di installazione non ha potuto determinare l'utente con cui il server web è in esecuzione.\nFornisci ad esso (ed altri!) la possibilità di scrivere globalmente nella directory <code><nowiki>$3</nowiki></code> per continuare.\nSu un sistema Unix/Linux:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Errore durante la creazione della directory dati \"$1\".\nControlla la posizione e riprova.",
+ "config-sqlite-dir-unwritable": "Impossibile scrivere nella directory \"$1\".\nModifica le autorizzazioni in modo che il webserver possa scrivere in essa e riprova.",
+ "config-sqlite-connection-error": "$1.\n\nControlla la directory dati e il nome del database qui sotto, poi riprova.",
+ "config-sqlite-readonly": "Il file <code>$1</code> non è scrivibile.",
+ "config-sqlite-cant-create-db": "Impossibile creare il file di database <code>$1</code> .",
+ "config-sqlite-fts3-downgrade": "Il PHP è mancante del supporto FTS3, declassamento tabelle in corso",
+ "config-can-upgrade": "Ci sono tabelle di MediaWiki in questo database.\nPer aggiornarle a MediaWiki $1, fai clic su '''continua'''.",
+ "config-upgrade-done": "Aggiornamento completo.\n\nPuoi [$1 iniziare ad usare il tuo wiki].\n\nSe vuoi rigenerare il tuo file <code>LocalSettings.php</code>, clicca sul pulsante sotto. Questa operazione '''non è raccomandata''', a meno che non hai problemi con il tuo wiki.",
+ "config-upgrade-done-no-regenerate": "Aggiornamento completo.\n\nPuoi [$1 iniziare ad usare il tuo wiki].",
+ "config-regenerate": "Rigenera LocalSettings.php →",
+ "config-show-table-status": "La query <code>SHOW TABLE STATUS</code> è fallita!",
+ "config-unknown-collation": "'''Attenzione:''' il database utilizza regole di confronto non riconosciute.",
+ "config-db-web-account": "Account del database per l'accesso web",
+ "config-db-web-help": "Seleziona il nome utente e la password che il server web utilizzerà per connettersi al server di database, durante il normale funzionamento del wiki.",
+ "config-db-web-account-same": "Utilizza lo stesso account dell'installazione",
+ "config-db-web-create": "Crea l'account se non esiste già",
+ "config-db-web-no-create-privs": "L'account usato per l'installazione non dispone dei privilegi necessari per creare un altro account.\nL'account indicato qui deve già esistere.",
+ "config-mysql-engine": "Storage engine:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>Attenzione:</strong> hai selezionato MyISAM come motore di archiviazione per MySQL, che non è raccomandato per l'uso con MediaWiki, perché:\n* supporta debolmente la concorrenza per il blocco della tabella\n* è più incline alla corruzione di altri motori\n* il codice di base MediaWiki non gestisce sempre MyISAM come dovrebbe\n\nSe la tua installazione MySQL supporta InnoDB, è altamente raccomandato che lo si scelga al suo posto.\nSe la tua installazione MySQL non supporta InnoDB, forse è il momento per un aggiornamento.",
+ "config-mysql-only-myisam-dep": "<strong>Attenzione:</strong> MyISAM è l'unico motore di archiviazione disponibile per MySQL su questa macchina, e questo non è consigliato per l'uso con MediaWiki, perché:\n* supporta debolmente la concorrenza per il blocco della tabella\n* è più incline alla corruzione di altri motori\n* il codice di base MediaWiki non gestisce sempre MyISAM come dovrebbe\n\nSe la tua installazione MySQL non supporta InnoDB, forse è il momento per un aggiornamento.",
+ "config-mysql-engine-help": "<strong>InnoDB</strong> è quasi sempre l'opzione migliore, in quanto ha un buon supporto della concorrenza.\n\n<strong>MyISAM</strong> potrebbe essere più veloce nelle installazioni monoutente o in sola lettura.\nI database MyISAM tendono a danneggiarsi più spesso dei database InnoDB.",
+ "config-mysql-charset": "Set di caratteri del database:",
+ "config-mysql-binary": "Binario",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "In <strong>modalità binaria</strong>, MediaWiki archivia il testo UTF-8 nel database in cambi binari.\nQuesto è più efficiente rispetto alla modalità UTF-8 di MySQL, e consente di utilizzare la gamma completa di caratteri Unicode.\n\nIn <strong>modalità UTF-8</strong>, MySQL conoscerà in quale set di caratteri sono i tuoi dati, e può presentarli e convertirli in modo appropriato, ma non ti permetterà di memorizzare i caratteri al di sopra del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mssql-auth": "Tipo di autenticazione:",
+ "config-mssql-install-auth": "Seleziona il tipo di autenticazione che verrà utilizzato per connettersi al database durante il processo di installazione.\nSe si seleziona \"{{int:config-mssql-windowsauth}}\", saranno utilizzate le credenziali dell'utente con cui viene eseguito il server web, qualunque esso sia.",
+ "config-mssql-web-auth": "Seleziona il tipo di autenticazione che il server web utilizzerà per connettersi al database, durante il normale funzionamento del wiki.\nSe si seleziona \"{{int:config-mssql-windowsauth}}\", saranno utilizzate le credenziali dell'utente con cui viene eseguito il server web, qualunque esso sia.",
+ "config-mssql-sqlauth": "Autenticazione di SQL Server",
+ "config-mssql-windowsauth": "Autenticazione di Windows",
+ "config-site-name": "Nome del wiki:",
+ "config-site-name-help": "Questo verrà visualizzato nella barra del titolo del browser e in vari altri posti.",
+ "config-site-name-blank": "Inserisci il nome del sito.",
+ "config-project-namespace": "Namespace del progetto:",
+ "config-ns-generic": "Progetto",
+ "config-ns-site-name": "Stesso nome del wiki: $1",
+ "config-ns-other": "Altro (specificare)",
+ "config-ns-other-default": "MyWiki",
+ "config-project-namespace-help": "Seguendo l'esempio di Wikipedia, molti wiki tengono le loro pagine con le regole separate dalle pagine di contenuto, in un \"'''namespace di progetto'''\".\nTutti i titoli delle pagine in questo namespace iniziano con un certo prefisso, che puoi indicare qui.\nSolitamente, questo prefisso deriva dal nome del wiki, ma non può contenere caratteri di punteggiatura come \"#\" o \":\".",
+ "config-ns-invalid": "Il namespace indicato \"<nowiki>$1</nowiki>\" non è valido.\nSpecificare un diverso namespace di progetto.",
+ "config-ns-conflict": "Il namespace indicato \"<nowiki>$1</nowiki>\" è in conflitto con un namespace predefinito MediaWiki.\nSpecificare un diverso namespace di progetto.",
+ "config-admin-box": "Account amministratore",
+ "config-admin-name": "Il tuo nome utente:",
+ "config-admin-password": "Password:",
+ "config-admin-password-confirm": "Ripeti la password:",
+ "config-admin-help": "Inserisci il tuo nome utente scelto qui, ad esempio \"Mario Rossi\".\nQuesto è il nome che userai per accedere al wiki.",
+ "config-admin-name-blank": "Inserisci un nome utente per l'amministratore.",
+ "config-admin-name-invalid": "Il nome utente specificato \"<nowiki>$1</nowiki>\" non è valido.\nSpecificare un nome utente diverso.",
+ "config-admin-password-blank": "Inserisci una password per l'account di amministratore.",
+ "config-admin-password-mismatch": "Le password inserite non coincidono tra loro.",
+ "config-admin-email": "Indirizzo email:",
+ "config-admin-email-help": "Inserisci qui un indirizzo email per poter ricevere email dagli altri utenti del wiki, reimpostare la tua password, ed essere informato delle modifiche apportate alle pagine tuoi osservati speciali. Se non ti interessa, puoi lasciare vuoto questo campo.",
+ "config-admin-error-user": "Errore interno durante la creazione di un amministratore con il nome \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Errore interno durante l'impostazione di una password per amministratore \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "È stato inserito un indirizzo email non valido.",
+ "config-subscribe": "Sottoscrivi la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailing list degli annunci di rilasci].",
+ "config-subscribe-help": "Si tratta di una mailing list a basso traffico dedicata agli annunci di nuove versioni, compresi importanti segnalazioni riguardanti la sicurezza.\nÈ consigliato iscriversi e aggiornare la proprio installazione di MediaWiki quando una nuova versione viene resa pubblica.",
+ "config-subscribe-noemail": "Hai provato ad iscriverti alla mailing list dedicata agli annunci delle nuove versioni senza fornire un indirizzo email.\nInserire un indirizzo email se si desidera effettuare l'iscrizione alla mailing list.",
+ "config-almost-done": "Hai quasi finito!\nAdesso puoi saltare la rimanente parte della configurazione e semplicemente installare la wiki.",
+ "config-optional-continue": "Fammi altre domande.",
+ "config-optional-skip": "Sono già stanco, installa solo il wiki.",
+ "config-profile": "Profilo dei diritti utente:",
+ "config-profile-wiki": "Wiki aperto",
+ "config-profile-no-anon": "Creazione utenza obbligatoria",
+ "config-profile-fishbowl": "Solo editori autorizzati",
+ "config-profile-private": "Wiki privato",
+ "config-profile-help": "I wiki funzionano meglio se si lascia che molte persone li possano modificare.\nIn MediaWiki, è semplice rivedere le ultime modifiche, e ripristinare i danni causati da utenti ingenui o malintenzionati.\n\nTuttavia, molti hanno trovato MediaWiki essere utile in un'ampia varietà di ruoli, e a volte non è facile convincere tutti i vantaggi della modalità wiki.\nPerciò, fai la tua scelta.\n\nIl modello <strong>{{int:config-profile-wiki}}</strong> consente a chiunque di modificare, anche senza effettuare l'accesso.\nUn wiki con <strong>{{int:config-profile-no-anon}}</strong> offre una maggiore responsabilità, ma potrebbe scoraggiare i contributori occasionali.\n\nLo scenario <strong>{{int:config-profile-fishbowl}}</strong> consente agli utenti autorizzati di modificare, ma il pubblico può visualizzare le pagine, compresa la cronologia.\nUn <strong>{{int:config-profile-private}}</strong> consente solo agli utenti autorizzati di visualizzare le pagine, lo stesso gruppo può modificarle.\n\nConfigurazioni di diritti utente più complesse sono disponibili dopo l'installazione, vedi la [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights parte relativa del manuale].",
+ "config-license": "Copyright e licenza:",
+ "config-license-none": "Nessun piè di pagina per la licenza",
+ "config-license-cc-by-sa": "Creative Commons Attribuzione-Condividi allo stesso modo",
+ "config-license-cc-by": "Creative Commons Attribuzione",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribuzione-Non commerciale-Condividi allo stesso modo",
+ "config-license-cc-0": "Creative Commons Zero (pubblico dominio)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 o versioni successive",
+ "config-license-pd": "Pubblico dominio",
+ "config-license-cc-choose": "Seleziona una delle licenze Creative Commons",
+ "config-license-help": "Molti wiki pubblici rilasciano i loro contributi con una [http://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 [http://www.php.net/manual/en/mail.configuration.php PHP's 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",
+ "config-email-usertalk-help": "Consente agli utenti di ricevere notifiche per le modifiche delle loro pagine di discussione, se lo hanno abilitato nelle loro preferenze.",
+ "config-email-watchlist": "Abilita le notifiche per gli osservati speciali",
+ "config-email-watchlist-help": "Consente agli utenti di ricevere notifiche per pagine tra gli osservati speciali, se lo hanno abilitato nelle loro preferenze.",
+ "config-email-auth": "Abilita autenticazione via email",
+ "config-email-auth-help": "Se questa opzione è attivata, gli utenti dovranno confermare il loro indirizzo email utilizzando un collegamento che viene inviato ogni volta che lo impostano o lo modificano.\nSolo gli indirizzi di posta elettronica autenticati possono ricevere email da altri utenti o modificare le email di notifica.\nImpostare questa opzione è <strong>raccomandato</strong> per wiki pubblici a causa del potenziale abuso delle funzioni di posta elettronica.",
+ "config-email-sender": "Indirizzo email di ritorno:",
+ "config-email-sender-help": "Inserisci l'indirizzo email da utilizzare come indirizzo di ritorno per la posta in uscita.\nQuesto è dove verranno inviati gli eventuali errori.\nMolti server di posta richiedono che almeno la parte del nome di dominio sia valido.",
+ "config-upload-settings": "Caricamenti di immagini e file",
+ "config-upload-enable": "Consentire il caricamento di file",
+ "config-upload-help": "Il caricamento di file può potenzialmente esporre il tuo server a rischi di sicurezza.\nPer ulteriori informazioni, leggi la [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sezione sulla sicurezza] nel manuale.\n\nPer consentire il caricamento di file, modificare la modalità nella sottodirectory <code>images</code> della directory principale di MediaWiki affinché il server web possa scriverci.\nPoi attivare questa opzione.",
+ "config-upload-deleted": "Directory per i file cancellati:",
+ "config-upload-deleted-help": "Scegli una directory in cui archiviare i file cancellati.\nIdealmente, questa non dovrebbe essere accessibile dal web.",
+ "config-logo": "URL del logo:",
+ "config-logo-help": "La skin predefinita di MediaWiki include lo spazio per un logo di 135 x 160 pixel sopra il menu laterale.\nCarica un'immagine di dimensioni appropriate e inserisci l'URL qui.\n\nÈ possibile utilizzare <code>$wgStylePath</code> o <code>$wgScriptPath</code> se il logo è relativo a tali percorsi.\n\nSe non si desidera un logo, lascia vuota questa casella.",
+ "config-instantcommons": "Abilita Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] è una funzionalità che consente ai wiki di usare immagini, suoni e altri file multimediali che trovate sul sito [//commons.wikimedia.org/ Wikimedia Commons].\nPer fare questo, MediaWiki richiede l'accesso a Internet.\n\nPer ulteriori informazioni su questa funzionalità, incluse le istruzioni su come configurarlo per wiki diversi da Wikimedia Commons, consultare [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos il manuale].",
+ "config-cc-error": "Il selettore di licenze Creative Commons non ha dato alcun risultato.\nInserisci manualmente il nome della licenza.",
+ "config-cc-again": "Seleziona di nuovo...",
+ "config-cc-not-chosen": "Scegliere quale licenza Creative Commons si desidera e cliccare su \"procedi\".",
+ "config-advanced-settings": "Configurazione avanzata",
+ "config-cache-options": "Impostazioni per la cache di oggetti:",
+ "config-cache-help": "La memorizzazione di oggetti nella cache è utilizzata per migliorare la velocità di MediaWiki attraverso l'allocazione nella cache dei dati utilizzati di frequente.\nPer siti di dimensioni medie e grandi, è caldamente consigliato attivare la cache, ma anche per piccoli siti se ne vedranno i benefici.",
+ "config-cache-none": "Nessuna memorizzazione in cache (nessuna funzionalità viene impedita, ma sui siti wiki più grandi la velocità potrebbe risentirne)",
+ "config-cache-accel": "Mettere in cache oggetti PHP (APC, XCache o WinCache)",
+ "config-cache-memcached": "Usa Memcached (richiede ulteriori attività di installazione e configurazione)",
+ "config-memcached-servers": "Server di memcached:",
+ "config-memcached-help": "Elenco di indirizzi IP da utilizzare per Memcached.\nDovresti specificarne uno per riga e indicare la porta da utilizzare. Per esempio:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "È stato selezionato il tipo di caching Memcached, ma non è stato impostato alcun server.",
+ "config-memcache-badip": "È stato inserito un indirizzo IP non valido per Memcached: $1.",
+ "config-memcache-noport": "Non è stata specificata una porta da utilizzare per il server Memcached: $1.\nSe non sai qual'è la porta, il valore di default è 11211.",
+ "config-memcache-badport": "I numeri di porta per memcached dovrebbero essere tra $1 e $2.",
+ "config-extensions": "Estensioni",
+ "config-extensions-help": "Le estensioni elencate sopra sono state rilevate nella tua directory <code>./extensions</code>.\n\nQueste potrebbero richiedere ulteriore configurazione, ma è possibile attivarle ora",
+ "config-skins": "Skin",
+ "config-skins-help": "Le skin elencate sopra sono state rilevate nella tua directory <code>./skins</code>. Devi attivarne almeno una e scegliere quella predefinita.",
+ "config-skins-use-as-default": "Usa questa skin come predefinita",
+ "config-skins-missing": "Non è stata trovata alcuna skin, MediaWiki userà una soluzione di ripiego finché non ne installerai una appropriata.",
+ "config-skins-must-enable-some": "Devi scegliere almeno una skin da attivare.",
+ "config-skins-must-enable-default": "La skin scelta come predefinita deve essere attivata.",
+ "config-install-alreadydone": "'''Attenzione:''' sembra che hai già installato MediaWiki e stai tentando di installarlo nuovamente.\nProcedi alla pagina successiva.",
+ "config-install-begin": "Premendo \"{{int:config-continue}}\", si avvierà l'installazione di MediaWiki.\nSe prima desideri apportare altre modifiche, premi \"{{int:config-back}}\".",
+ "config-install-step-done": "fatto",
+ "config-install-step-failed": "non riuscito",
+ "config-install-extensions": "Comprese le estensioni",
+ "config-install-database": "Configurazione database",
+ "config-install-schema": "Creazione dello schema",
+ "config-install-pg-schema-not-exist": "Lo schema PostgreSQL non esiste.",
+ "config-install-pg-schema-failed": "Creazione tabelle non riuscita.\nAssicurati che l'utente \"$1\" può scrivere nello schema \"$2\".",
+ "config-install-pg-commit": "Applica le modifiche",
+ "config-install-pg-plpgsql": "Controllo il linguaggio PL/pgSQL",
+ "config-pg-no-plpgsql": "È necessario installare il linguaggio PL/pgSQL nel database $1",
+ "config-pg-no-create-privs": "L'account indicato per l'installazione non dispone dei permessi necessari per creare un'utenza.",
+ "config-pg-not-in-role": "L'account indicato per l'utente web esiste già.\nL'account indicato per l'installazione non è un utente avanzato e non è un membro del ruolo degli utente web, quindi non è in grado di creare oggetti di proprietà dell'utente web.\n\nMediaWiki attualmente richiede che le tabelle siano di proprietà dell'utente web. Indica un altro account web, o fai click su \"indietro\" e specifica un utente per l'installazione opportunamente privilegiato.",
+ "config-install-user": "Creazione di utente del database",
+ "config-install-user-alreadyexists": "L'utente \"$1\" è già presente",
+ "config-install-user-create-failed": "Creazione dell'utente \"$1\" non riuscita: $2",
+ "config-install-user-grant-failed": "Errore durante la concessione delle autorizzazione all'utente \"$1\": $2",
+ "config-install-user-missing": "L'utente indicato \"$1\" non esiste.",
+ "config-install-user-missing-create": "L'utente indicato \"$1\" non esiste.\nSeleziona la casella \"crea utenza\" qui sotto se vuoi crearla.",
+ "config-install-tables": "Creazione tabelle",
+ "config-install-tables-exist": "'''Attenzione:''' sembra che le tabelle di MediaWiki esistono già.\nSalto la creazione.",
+ "config-install-tables-failed": "'''Errore''': La creazione della tabella non è riuscita: $1",
+ "config-install-interwiki": "Riempimento della tabella interwiki predefinita",
+ "config-install-interwiki-list": "Impossibile leggere il file <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Attenzione:''' la tabella interwiki sembra che contiene già elementi.\nSalto l'elenco predefinito.",
+ "config-install-stats": "Inizializzazione delle statistiche",
+ "config-install-keys": "Generazione delle chiavi segrete",
+ "config-insecure-keys": "'''Attenzione:''' {{PLURAL:$2|Una chiave sicura|Delle chiavi sicure}} ($1) {{PLURAL:$2|generata|generate}} durante l'installazione non {{PLURAL:$2|è|sono}} completamente {{PLURAL:$2|sicura|sicure}}. Considera di {{PLURAL:$2|cambiarla|cambiarle}} manualmente.",
+ "config-install-updates": "Impedire l'esecuzione di aggiornamenti non necessari",
+ "config-install-updates-failed": "<strong>Errore:</strong> l'inserimento delle chiavi di aggiornamento nelle tabelle non è riuscito con il seguente errore: $1",
+ "config-install-sysop": "Creazione dell'account utente per l'amministratore",
+ "config-install-subscribe-fail": "Impossibile sottoscrivere mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL non è installato e <code>allow_url_fopen</code> non è disponibile.",
+ "config-install-mainpage": "Creazione della pagina principale con contenuto predefinito",
+ "config-install-extension-tables": "Creazione delle tabelle per le estensioni attivate",
+ "config-install-mainpage-failed": "Impossibile inserire la pagina principale: $1",
+ "config-install-done": "<strong>Complimenti!</strong>\nHai installato correttamente MediaWiki.\n\nIl programma di installazione ha generato un file <code>LocalSettings.php</code> che contiene tutte le impostazioni.\n\nDevi scaricarlo ed inserirlo nella directory base del tuo wiki (la stessa dove è presente index.php). Il download dovrebbe partire automaticamente.\n\nSe il download non si avvia, o se è stato annullato, puoi riavviarlo cliccando sul collegamento di seguito:\n\n$3\n\n<strong>Nota:</strong> se esci ora dall'installazione senza scaricare il file di configurazione che è stato generato, questo poi non sarà più disponibile in seguito.\n\nQuando hai fatto, puoi <strong>[$2 entrare nel tuo wiki]</strong>.",
+ "config-download-localsettings": "Scarica <code>LocalSettings.php</code>",
+ "config-help": "aiuto",
+ "config-help-tooltip": "fai clic per espandere",
+ "config-nofile": "Il file \"$1\" non può essere trovato. È stato eliminato?",
+ "config-extension-link": "Sapevi che il tuo wiki supporta le [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions estensioni]?\n\nPuoi navigare tra le [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category estensioni per categoria].",
+ "mainpagetext": "'''Installazione di MediaWiki completata correttamente.'''",
+ "mainpagedocfooter": "Consulta la [//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Contents Guida utente] per maggiori informazioni sull'uso di questo software wiki.\n\n== Per iniziare ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Impostazioni di configurazione]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Domande frequenti su MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annunci MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localizza MediaWiki nella tua lingua]"
+}
diff --git a/includes/installer/i18n/ja.json b/includes/installer/i18n/ja.json
new file mode 100644
index 00000000..21fcb9d7
--- /dev/null
+++ b/includes/installer/i18n/ja.json
@@ -0,0 +1,338 @@
+{
+ "@metadata": {
+ "authors": [
+ "Aphaia",
+ "Fryed-peach",
+ "Iwai.masaharu",
+ "Mizusumashi",
+ "Ninomy",
+ "Ohgi",
+ "Shirayuki",
+ "Whym",
+ "Yanajin66",
+ "青子守歌",
+ "아라",
+ "Shield-9",
+ "Takot"
+ ]
+ },
+ "config-desc": "MediaWiki のインストーラー",
+ "config-title": "MediaWiki $1 のインストール",
+ "config-information": "情報",
+ "config-localsettings-upgrade": "ファイル <code>LocalSettings.php</code> を検出しました。\nインストールされているものをアップグレードするには、<code>$wgUpgradeKey</code> の値を以下の欄に入力してください。\nこの値は <code>LocalSettings.php</code> 内にあります。",
+ "config-localsettings-cli-upgrade": "ファイル <code>LocalSettings.php</code> を検出しました。\nインストールされているものをアップグレードするには、<code>update.php</code> を実行してください",
+ "config-localsettings-key": "アップグレード キー:",
+ "config-localsettings-badkey": "与えられたキーが間違っています",
+ "config-upgrade-key-missing": "MediaWiki が既にインストールされていることを検出しました。\nインストールされているものをアップグレードするために、以下の行を <code>LocalSettings.php</code> の末尾に挿入してください:\n\n$1",
+ "config-localsettings-incomplete": "既存の <code>LocalSettings.php</code> の内容は不完全のようです。\n変数 $1 が設定されていません。\n<code>LocalSettings.php</code> 内でこの変数を設定して、「{{int:Config-continue}}」をクリックしてください。",
+ "config-localsettings-connection-error": "<code>LocalSettings.php</code> で指定した設定を使用してデータベースに接続する際にエラーが発生しました。\n設定を修正してから再度試してください。\n\n$1",
+ "config-session-error": "セッションの開始エラー: $1",
+ "config-session-expired": "セッションの有効期限が切れたようです。\nセッションの有効期間は$1に設定されています。\nphp.iniの<code>session.gc_maxlifetime</code>を設定することで、この問題を改善できます。\nインストール作業を再起動させてください。",
+ "config-no-session": "セッションのデータが消失しました!\nphp.ini 内で <code>session.save_path</code> が適切なディレクトリに設定されていることを確認してください。",
+ "config-your-language": "あなたの言語:",
+ "config-your-language-help": "インストール作業に使用する言語を選択してください。",
+ "config-wiki-language": "ウィキの言語:",
+ "config-wiki-language-help": "ウィキで主に書き込まれる言語を選択してください。",
+ "config-back": "← 戻る",
+ "config-continue": "続行 →",
+ "config-page-language": "言語",
+ "config-page-welcome": "MediaWiki へようこそ!",
+ "config-page-dbconnect": "データベースに接続",
+ "config-page-upgrade": "既存のインストールを更新",
+ "config-page-dbsettings": "データベースの設定",
+ "config-page-name": "名前",
+ "config-page-options": "オプション",
+ "config-page-install": "インストール",
+ "config-page-complete": "完了!",
+ "config-page-restart": "インストールを再起動",
+ "config-page-readme": "お読みください",
+ "config-page-releasenotes": "リリースノート",
+ "config-page-copying": "複製",
+ "config-page-upgradedoc": "アップグレード",
+ "config-page-existingwiki": "既存のウィキ",
+ "config-help-restart": "入力した保存データをすべて消去して、インストール作業を再起動しますか?",
+ "config-restart": "はい、再起動します",
+ "config-welcome": "=== 環境の確認 ===\n基本的な確認では、現在の環境が MediaWiki のインストールに適しているかを確認します。\nインストール方法について助けが必要になった場合は、必ずこの確認結果を添えてください。",
+ "config-copyright": "=== 著作権および規約 ===\n$1\n\nこの作品はフリーソフトウェアです。あなたは、フリーソフトウェア財団の発行する GNU 一般公衆利用許諾書 (GNU General Public License) (バージョン 2、またはそれ以降のライセンス) の規約に基づき、このライブラリを再配布および改変できます。\n\nこの作品は、有用であることを期待して配布されていますが、<strong>商用または特定の目的に適するかどうか</strong>も含めて、暗黙的にも、<strong>一切保証されません</strong>。\n詳しくは、 GNU 一般公衆利用許諾書をご覧ください。\n\nあなたはこのプログラムと共に、<doclink href=Copying>GNU 一般公衆利用許諾契約書の複製</doclink>を受け取ったはずです。受け取っていない場合は、フリーソフトウェア財団 (宛先は the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA) まで請求するか、または[http://www.gnu.org/copyleft/gpl.html オンラインでお読みください]。",
+ "config-sidebar": "* [//www.mediawiki.org MediaWikiのホーム]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 利用者向け案内]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents 管理者向け案内]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>お読みください</doclink>\n* <doclink href=ReleaseNotes>リリースノート</doclink>\n* <doclink href=Copying>コピー</doclink>\n* <doclink href=UpgradeDoc>アップグレード</doclink>",
+ "config-env-good": "環境を確認しました。\nMediaWiki をインストールできます。",
+ "config-env-bad": "環境を確認しました。\nMediaWiki のインストールはできません。",
+ "config-env-php": "PHP $1がインストールされています。",
+ "config-env-hhvm": "HHVM $1 がインストールされています。",
+ "config-unicode-using-utf8": "Unicode正規化に、Brion Vibberのutf8_normalize.soを使用。",
+ "config-unicode-using-intl": "Unicode正規化に[http://pecl.php.net/intl intl PECL 拡張機能]を使用。",
+ "config-unicode-pure-php-warning": "<strong>警告:</strong> Unicode 正規化の処理に [http://pecl.php.net/intl intl PECL 拡張機能]を利用できないため、処理が遅いピュア PHP の実装を代わりに使用しています。\n高トラフィックのサイトを運営する場合は、[//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode 正規化]をお読みください。",
+ "config-unicode-update-warning": "<strong>警告:</strong> インストールされているバージョンの Unicode 正規化ラッパーは、[http://site.icu-project.org/ ICU プロジェクト]のライブラリの古いバージョンを使用しています。\nUnicode を少しでも利用する可能性がある場合は、[//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations アップグレード]してください。",
+ "config-no-db": "適切なデータベース ドライバーが見つかりませんでした! PHP にデータベース ドライバーをインストールする必要があります。\n以下の種類のデータベースに対応しています: $1\n\nPHP を自分でコンパイルした場合は、例えば <code>./configure --with-mysqli</code> を実行して、データベース クライアントを使用できるように再設定してください。\nDebian または Ubuntu のパッケージから PHP をインストールした場合は、モジュール (例: <code>php5-mysql</code>) もインストールする必要があります。",
+ "config-outdated-sqlite": "<strong>警告:</strong> あなたは SQLite $1 を使用していますが、最低限必要なバージョン $2 より古いバージョンです。SQLite は利用できません。",
+ "config-no-fts3": "<strong>警告:</strong> SQLite は [//sqlite.org/fts3.html FTS3] モジュールなしでコンパイルされており、このバックエンドでは検索機能は利用できなくなります。",
+ "config-register-globals-error": "<strong>エラー: PHPの <code>[http://php.net/register_globals register_globals]</code> オプションが有効になっています。\nインストールを進めるには無効にしなければなりません。</strong>\nやり方については[https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] をご覧ください。",
+ "config-magic-quotes-gpc": "<strong>致命的なエラー: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] が有効になっています!</strong>\nこのオプションは予期せずしてデータ入力を破壊します。\nこのオプションを無効にするまで MediaWiki はインストールしたり使用したりはできません。",
+ "config-magic-quotes-runtime": "<strong>致命的エラー: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] が動作しています!</strong>\nこのオプションは、予期せずデータ入力を破壊します。\nこのオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
+ "config-magic-quotes-sybase": "<strong>致命的エラー: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] が動作しています!</strong>\nこのオプションは、予期せずデータ入力を破壊します。\nこのオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
+ "config-mbstring": "<strong>致命的エラー: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] が動作しています!</strong>\nこのオプションは、エラーを引き起こし、予期せずデータを破壊するおそれがあります。\nこのオプションを無効化しない限り、MediaWiki のインストールや使用はできません。",
+ "config-safe-mode": "<strong>警告:</strong> PHPの[http://www.php.net/features.safe-mode セーフモード]が有効になっています。\n特に、ファイルのアップロードや<code>math</code>機能で、問題が発生するおそれがあります。",
+ "config-xml-bad": "PHPのXMLモジュールが不足しています。\nMediaWikiは、このモジュールの関数を必要としているため、この構成では動作しません。\nMandrakeを実行している場合、php-xmlパッケージをインストールしてください。",
+ "config-pcre-old": "<strong>致命的エラー:</strong> PCRE $1 以降が必要です。\nご使用中の PHP のバイナリは PCRE $2 とリンクされています。\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE 詳細情報]",
+ "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-ctype": "<strong>致命的エラー:</strong> PHP は [http://www.php.net/manual/en/ctype.installation.php Ctype 拡張モジュール]のサポート付きでコンパイルされている必要があります。",
+ "config-iconv": "<strong>致命的なエラー:</strong> PHPは[http://www.php.net/manual/en/iconv.installation.php iconv 拡張機能]のサポートを有効にしてコンパイルされている必要があります。",
+ "config-json": "<strong>致命的エラー:</strong> PHP は JSON サポートなしでコンパイルされています。\nPHP に JSON 拡張モジュールまたは [http://pecl.php.net/package/jsonc PECL jsonc] 拡張モジュールをインストールしてから、MediaWiki をインストールしてください。\n* Red Hat Enterprise Linux (CentOS) 5 および 6には PHP の拡張機能が含まれているため、<code>/etc/php.ini</code> または <code>/etc/php.d/json.ini</code> から有効にしてください。\n* 2013年5月以降にリリースされた一部の Linux ディストリビューションでは、PHP 拡張モジュールの代わりに、<code>php5-json</code> または <code>php-pecl-jsonc</code> として PECL が同梱されています。",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] がインストール済み",
+ "config-apc": "[http://www.php.net/apc APC] がインストール済み",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] がインストール済み",
+ "config-no-cache": "<strong>警告:</strong> [http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache]、[http://www.iis.net/download/WinCacheForPhp WinCache] のいずれも見つかりませんでした。\nオブジェクトのキャッシュは有効化されません。",
+ "config-mod-security": "<strong>警告:</strong> あなたのウェブサーバーでは [http://modsecurity.org/ mod_security] が有効になっています。正しく構成されていない場合は、MediaWiki や利用者にコンテンツの投稿を許可するその他のソフトウェアに問題が発生する場合があります。\n[http://modsecurity.org/documentation/ mod_security の説明文書]を確認するか、ランダムなエラーが発生した場合はあなたのホストのサポートにお問い合わせください。",
+ "config-diff3-bad": "GNU diff3 が見つかりません。",
+ "config-git": "バージョン管理ソフトウェア Git が見つかりました: <code>$1</code>",
+ "config-git-bad": "バージョン管理ソフトウェア Git が見つかりません。",
+ "config-imagemagick": "ImageMagickが見つかりました: <code>$1</code>。\nアップロードが有効であれば、画像のサムネイルを利用できます。",
+ "config-gd": "GD画像ライブラリが内蔵されていることが確認されました。\nアップロードが有効なら、画像のサムネイルが利用できます。",
+ "config-no-scaling": "GDライブラリもImageMagickも見つかりませんでした。\n画像のサムネイル生成は無効になります。",
+ "config-no-uri": "<strong>エラー:</strong> 現在のURIを決定できませんでした。\nインストールは中止されました。",
+ "config-no-cli-uri": "<strong>警告:</strong> <code>--scriptpath</code> が指定されていないため、既定値 <code>$1</code> を使用します。",
+ "config-using-server": "サーバー名「<nowiki>$1</nowiki>」を使用しています。",
+ "config-using-uri": "サーバー URL「<nowiki>$1$2</nowiki>」を使用しています。",
+ "config-uploads-not-safe": "<strong>警告:</strong> アップロードの既定ディレクトリ <code>$1</code> に、任意のスクリプト実行に関する脆弱性があります。\nMediaWiki はアップロードされたファイルのセキュリティ上の脅威を確認しますが、アップロードを有効化する前に、[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security このセキュリティ上の脆弱性を解決する]ことを強く推奨します。",
+ "config-no-cli-uploads-check": "<strong>警告:</strong> アップロード用のデフォルトディレクトリ (<code>$1</code>) が、CLIでのインストール中に任意のスクリプト実行の脆弱性チェックを受けていません。",
+ "config-brokenlibxml": "このシステムで使われているPHPとlibxml2のバージョンのこの組み合わせにはバグがあります。具体的には、MediaWikiやその他のウェブアプリケーションでhiddenデータが破損する可能性があります。\nlibxml2を2.7.3以降のバージョンにアップグレードしてください([//bugs.php.net/bug.php?id=45996 PHPでのバグ情報])。\nインストールを終了します。",
+ "config-suhosin-max-value-length": "Suhosin がインストールされており、GET パラメーターの <code>length</code> を $1 バイトに制限しています。\nMediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。\n可能な限り、<code>php.ini</code> で <code>suhosin.get.max_value_length</code> を 1024 以上に設定し、同じ値を <code>LocalSettings.php</code> 内で <code>$wgResourceLoaderMaxQueryLength</code> に設定してください。",
+ "config-db-type": "データベースの種類:",
+ "config-db-host": "データベースのホスト:",
+ "config-db-host-help": "異なるサーバー上にデータベースサーバーがある場合、ホスト名またはIPアドレスをここに入力してください。\n\nもし、共有されたウェブホスティングを使用している場合、ホスティングプロバイダーは正確なホスト名を解説しているはずです。\n\nWindowsでMySQLを使用している場合に、「localhost」は、サーバー名としてはうまく働かないでしょう。もしそのような場合は、ローカルIPアドレスとして「127.0.0.1」を試してみてください。\n\nPostgreSQLを使用している場合、UNIXソケットで接続するにはこの欄を空欄のままにしてください。",
+ "config-db-host-oracle": "データベース TNS:",
+ "config-db-host-oracle-help": "有効な[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm ローカル接続名]を入力してください。tnsnames.ora ファイルは、このインストール先から参照できる場所に置いてください。<br />ご使用中のクライアント ライブラリが 10g 以降の場合、[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] ネーミング メソッドを使用できます。",
+ "config-db-wiki-settings": "このウィキの識別情報",
+ "config-db-name": "データベース名:",
+ "config-db-name-help": "このウィキを識別する名前を入力してください。\n空白を含めることはできません。\n\n共有ウェブホストを利用している場合、ホスティングプロバイダーが特定の使用可能なデータベース名を提供するか、あるいは管理パネルからデータベースを作成できるようにしているでしょう。",
+ "config-db-name-oracle": "データベースのスキーマ:",
+ "config-db-account-oracle-warn": "バックエンドのデータベースとして Oracle をインストールする場合、3つのシナリオが考えられます。\n\nデータベース用のアカウントをインストールのプロセス途中で作成したい場合、インストールに使うデータベース用のアカウントしては SYSDBAロール付きのアカウントを指定し、ウェブアクセス用アカウントには必要なログイン情報を指定してください。あるいは、ウェブアクセス用のアカウントを手動で作成して、そのアカウント(スキーマオブジェクトの作成のパーミッションを要求する場合)だけを使うか、二つの異なるアカウントを用意して一つは特権を付与できるもの、もう一つをウェブアクセス用の制限アカウントとしてください。\n\n要求された特権でアカウントを作成するスクリプトは、このインストール環境では、\"maintenance/oracle/\" にあります。制限アカウントを使用することは、デフォルトアカウントでのすべてのメンテナンス特権を無効にすることにご注意ください。",
+ "config-db-install-account": "インストールで使用する利用者アカウント",
+ "config-db-username": "データベースのユーザー名:",
+ "config-db-password": "データベースのパスワード:",
+ "config-db-password-empty": "新しいデータベースの利用者名 $1 のパスワードを入力してください。\nパスワードを設定せずにユーザーを作成できる場合もありますが、安全ではありません。",
+ "config-db-username-empty": "「{{int:config-db-username}}」を入力してください。",
+ "config-db-install-username": "インストール中にデータベースへの接続で使用するユーザー名を入力してください。\nこれは MediaWiki アカウントの利用者名のことではありません。あなたのデータベースでのユーザー名です。",
+ "config-db-install-password": "インストール中にデータベースへの接続で使用するパスワードを入力してください。\nこれは MediaWiki アカウントのパスワードのことではありません。あなたのデータベースでのパスワードです。",
+ "config-db-install-help": "インストール作業中にデータベースに接続するための利用者名とパスワードを入力してください。",
+ "config-db-account-lock": "インストール作業終了後も同じ利用者名とパスワードを使用する",
+ "config-db-wiki-account": "インストール作業終了後の利用者アカウント",
+ "config-db-wiki-help": "通常のウィキ操作中にデータベースへの接続する時に利用する利用者名とパスワードを入力してください。\nアカウントが存在せず、インストールのアカウントに十分な権限がある場合は、この利用者アカウントは、ウィキを操作する上で最小限の権限を持った状態で作成されます。",
+ "config-db-prefix": "データベース テーブルの接頭辞:",
+ "config-db-prefix-help": "データベースを複数のウィキ間、あるいはMediaWikiと他のウェブアプリケーションで共有する必要がある場合、衝突を避けるために、すべてのテーブル名に接頭辞を付ける必要があります。\n空白は使用できません。\n\nこのフィールドは、通常は空のままです。",
+ "config-db-charset": "データベースの文字セット",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 バイナリ",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 後方互換 UTF-8",
+ "config-charset-help": "<strong>警告:</strong> MySQL 4.1+ で<strong>後方互換 UTF-8</strong> を使用している状態で、<code>mysqldump</code> でデータベースをバックアップすると、すべての非 ASCII 文字が破壊されてしまい、バックアップが不可逆的に破損してしまいます!\n\n<strong>バイナリ モード</strong>では、MediaWiki は、UTF-8 テキストをデータベースのバイナリ フィールドに格納します。\nこれは、MySQL の UTF-8 モードより効率的で、Unicode 文字の全範囲を利用できるようになります。\n<strong>UTF-8 モード</strong>では、MySQL は、データ内で使用している文字集合を知っているため、適切に表現や変換ができますが、\n[//ja.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E8%A8%80%E8%AA%9E%E9%9D%A2 基本多言語面]の外にある文字を格納できません。",
+ "config-mysql-old": "MySQL $1 以降が必要です。ご使用中の MySQL は $2 です。",
+ "config-db-port": "データベースのポート:",
+ "config-db-schema": "MediaWiki のスキーマ:",
+ "config-db-schema-help": "通常はこのスキーマで問題ありません。\n必要な場合のみ変更してください。",
+ "config-pg-test-error": "データベース <strong>$1</strong> に接続できません: $2",
+ "config-sqlite-dir": "SQLite データ ディレクトリ:",
+ "config-sqlite-dir-help": "SQLite は単一のファイル内にすべてのデータを格納しています。\n\n指定したディレクトリは、インストール時にウェブ サーバーが書き込めるようにしておく必要があります。\n\nこのディレクトリはウェブからアクセス<strong>不可能</strong>である必要があります。PHP ファイルがある場所には配置できないのはこのためです。\n\nインストーラーは <code>.htaccess</code> ファイルにも書き込みます。しかし、これが失敗した場合は、誰かが生のデータベースにアクセスできてしまいます。\nデータベースは、生のデータ (メールアドレス、パスワードのハッシュ値) の他、削除された版、その他ウィキ上の制限されているデータを含んでいます。\n\n例えば <code>/var/lib/mediawiki/yourwiki</code> のように、別の場所にデータベースを配置することを検討してください。",
+ "config-oracle-def-ts": "既定のテーブル領域:",
+ "config-oracle-temp-ts": "一時的なテーブル領域:",
+ "config-type-mysql": "MySQL(または互換製品)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-type-mssql": "マイクロソフト 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]でも動きます。 ([http://www.php.net/manual/ja/mysqli.installation.php PHPをMySQLサポート付きでコンパイルする方法])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] は、MySQLの代替として人気があるオープンソースのデータベースシステムです。細部の未解消バグがある場合があるため、プロダクション環境での使用は推奨されません。 ([http://www.php.net/manual/en/pgsql.installation.php PHPをPostgreSQLサポート付きでコンパイルする方法])",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]は、良くサポートされている、軽量データベースシステムです。([http://www.php.net/manual/ja/pdo.installation.php SQLiteに対応したPHPをコンパイルする方法]、PDOを使用)",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle]は商業企業のデータベースです。([http://www.php.net/manual/en/oci8.installation.php OCI8サポートなPHPをコンパイルする方法])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server]は商業企業のWindows用データベースです。([http://www.php.net/manual/en/sqlsrv.installation.php SQLSRVサポートなPHPをコンパイルする方法])",
+ "config-header-mysql": "MySQL の設定",
+ "config-header-postgres": "PostgreSQL の設定",
+ "config-header-sqlite": "SQLite の設定",
+ "config-header-oracle": "Oracle の設定",
+ "config-header-mssql": "Microsoft SQL Server の設定",
+ "config-invalid-db-type": "データベースの種類が無効です。",
+ "config-missing-db-name": "「{{int:config-db-name}}」を入力してください",
+ "config-missing-db-host": "「{{int:config-db-host}}」を入力してください。",
+ "config-missing-db-server-oracle": "「{{int:config-db-host-oracle}}」の値を入力してください",
+ "config-invalid-db-server-oracle": "「$1」は無効なデータベース TNS です。\n「TNS 名」「Easy Connect」文字列のいずれかを使用してください ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle ネーミング メソッド])。",
+ "config-invalid-db-name": "「$1」は無効なデータベース名です。\n半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_)、ハイフン (-) のみを使用してください。",
+ "config-invalid-db-prefix": "「$1」は無効なデータベース接頭辞です。\n半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_)、ハイフン (-) のみを使用してください。",
+ "config-connection-error": "$1。\n\n以下のホスト名、ユーザー名、パスワードを確認してから再度試してください。",
+ "config-invalid-schema": "「$1」は MediaWiki のスキーマとして無効です。\n半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_) のみを使用してください。",
+ "config-db-sys-create-oracle": "インストーラーは、新規アカウント作成にはSYSDBAアカウントの利用のみをサポートしています。",
+ "config-db-sys-user-exists-oracle": "利用者アカウント「$1」は既に存在します。SYSDBA は新しいアカウントの作成のみに使用できます!",
+ "config-postgres-old": "PostgreSQL $1 以降が必要です。ご使用中の PostgreSQL は $2 です。",
+ "config-mssql-old": "Microsoft SQL Server $1 以降が必要です。ご使用中の Microsoft SQL Server は $2 です。",
+ "config-sqlite-name-help": "あなたのウェキと同一性のある名前を選んでください。\n空白およびハイフンは使用しないでください。\nSQLiteのデータファイル名として使用されます。",
+ "config-sqlite-parent-unwritable-group": "データ ディレクトリ <code><nowiki>$1</nowiki></code> を作成できません。ウェブ サーバーは親ディレクトリ <code><nowiki>$2</nowiki></code> に書き込めませんでした。\n\nインストーラーは、ウェブ サーバーの実行ユーザーを特定しました。\n続行するには、ディレクトリ <code><nowiki>$3</nowiki></code> に書き込めるようにしてください。\nUnix または Linux であれば、以下を実行してください:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "データ ディレクトリ <code><nowiki>$1</nowiki></code> を作成できません。ウェブ サーバーは、親ディレクトリ <code><nowiki>$2</nowiki></code> に書き込めませんでした。\n\nインストーラーは、ウェブ サーバーの実行ユーザーを特定できませんでした。\n続行するには、ディレクトリ <code><nowiki>$3</nowiki></code> に、ウェブ サーバー (と、あらゆる人々!) がグローバルに書き込めるようにしてください。\nUnix または Linux では、以下を実行してください:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "データ ディレクトリ「$1」を作成する際にエラーが発生しました。\n場所を確認してから、再度試してください。",
+ "config-sqlite-dir-unwritable": "ディレクトリ「$1」に書き込めません。\nウェブ サーバーが書き込めるようにパーミッションを変更してから、再度試してください。",
+ "config-sqlite-connection-error": "$1。\n\nデータ ディレクトリおよびデータベース名を確認してから、再度試してください。",
+ "config-sqlite-readonly": "ファイル <code>$1</code> に書き込めません。",
+ "config-sqlite-cant-create-db": "データベース ファイル <code>$1</code> を作成できませんでした。",
+ "config-sqlite-fts3-downgrade": "PHP が FTS3 に対応していないため、テーブルをダウングレードしています。",
+ "config-can-upgrade": "このデータベースには MediaWiki テーブルがあります。\nこれらのテーブルを MediaWiki $1 にアップグレードするには、<strong>続行</strong>をクリックしてください。",
+ "config-upgrade-done": "更新は完了しました。\n\n[$1 ウィキを使い始める]ことができます。\n\n<code>LocalSettings.php</code> ファイルを再生成したい場合は、下のボタンを押してください。\nウィキに問題がある場合を除き、再生成は<strong>推奨されません</strong>。",
+ "config-upgrade-done-no-regenerate": "アップグレードが完了しました。\n\n[$1 ウィキの使用を開始]することができます。",
+ "config-regenerate": "LocalSettings.php を再生成→",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code> クエリが失敗しました!",
+ "config-unknown-collation": "<strong>警告:</strong> データベースは認識されない照合を使用しています。",
+ "config-db-web-account": "ウェブアクセスのためのデータベースアカウント",
+ "config-db-web-help": "ウィキの通常の操作の際に、ウェブ サーバーがデータベース サーバーに接続できるように、ユーザー名とパスワードを指定してください。",
+ "config-db-web-account-same": "インストール作業と同じアカウントを使用する",
+ "config-db-web-create": "アカウントが存在しない場合は作成する",
+ "config-db-web-no-create-privs": "あなたがインストールのために定義したアカウントは、アカウント作成のための特権としては不充分です。\nあなたがここで指定したアカウントは既に存在している必要があります。",
+ "config-mysql-engine": "ストレージ エンジン:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>警告:</strong> MySQLのストレージエンジンとして MyISAM を選択していますが、これをMediaWikiで利用するのは推奨されていません。その理由は: \n* テーブルロックによる並列性をほとんどサポートしていない\n* 他のエンジンに比べて壊れやすい\n* MediaWiki のコードベースは必ずしも MyISAM を本来あるべきほどには扱っていない\n\nあなたがインストールした MySQL が InnoDB をサポートしている場合、代わりにそちらをお使いになることを強くお勧めします。\nあなたがインストールした MySQL が InnoDB をサポートしていない場合、アップグレードした方がいいでしょう。",
+ "config-mysql-only-myisam-dep": "<strong>警告:</strong> MyISAM がこのマシンの MySQL の唯一のストレージエンジンですが、これをMediaWikiで利用するのは推奨されていません。その理由は: \n* テーブルロックによる並列性をほとんどサポートしていない\n* 他のエンジンに比べて壊れやすい\n* MediaWiki のコードベースは必ずしも MyISAM を本来あるべきほどには扱っていない\n\nあなたがインストールした MySQL が InnoDB をサポートしていない場合、アップグレードした方がいいでしょう。",
+ "config-mysql-engine-help": "<strong>InnoDB</strong>は、並行処理のサポートに優れているので、ほとんどの場合において最良の選択肢です。\n\n<strong>MyISAM</strong>は、利用者が1人の場合、あるいは読み込み専用でインストールする場合に、より処理が早くなるでしょう。\nただし、MyISAMのデータベースは、InnoDBより高頻度で破損する傾向があります。",
+ "config-mysql-charset": "データベースの文字セット:",
+ "config-mysql-binary": "バイナリ",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "<strong>バイナリ モード</strong>では、MediaWiki は、UTF-8 テキストをデータベースのバイナリ フィールドに格納します。\nこれは、MySQL の UTF-8 モードより効率的で、Unicode 文字の全範囲を利用できるようになります。\n\n<strong>UTF-8 モード</strong>では、MySQL は、データ内で使用している文字集合を知っているため、適切に表現や変換ができますが、\n[//ja.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E8%A8%80%E8%AA%9E%E9%9D%A2 基本多言語面]の外にある文字を格納できません。",
+ "config-mssql-auth": "認証の種類:",
+ "config-mssql-install-auth": "インストール過程でデータベースに接続するために使用する認証の種類を選択してください。\n「{{int:config-mssql-windowsauth}}」を選択した場合、ウェブサーバーを実行しているユーザーの認証情報が使用されます。",
+ "config-mssql-web-auth": "ウィキの通常の操作の際にウェブサーバーがデータベースサーバーに接続するために使用する認証の種類を選択してください。\n「{{int:config-mssql-windowsauth}}」を選択した場合、ウェブサーバーを実行しているユーザーの認証情報が使用されます。",
+ "config-mssql-sqlauth": "SQL Server 認証",
+ "config-mssql-windowsauth": "Windows 認証",
+ "config-site-name": "ウィキ名:",
+ "config-site-name-help": "この事象はブラウザーのタイトルバーと他のさまざまな場所に現れる。",
+ "config-site-name-blank": "サイト名を入力してください。",
+ "config-project-namespace": "プロジェクト名前空間:",
+ "config-ns-generic": "プロジェクト",
+ "config-ns-site-name": "ウィキ名と同じ: $1",
+ "config-ns-other": "その他 (指定してください)",
+ "config-ns-other-default": "マイウィキ",
+ "config-project-namespace-help": "ウィキペディアの例に従い、多くのウィキは、コンテンツのページとは分離したポリシーページを「'''プロジェクトの名前空間'''」に持っています。\nこの名前空間内のページのページ名はすべて特定の接頭辞で始まります。それをここで指定できます。\n通常、この接頭辞はウィキ名に基づきますが、「#」や「:」のような区切り文字を含めることはできません。",
+ "config-ns-invalid": "指定した名前空間「<nowiki>$1</nowiki>」は無効です。\n別のプロジェクト名前空間を指定してください。",
+ "config-ns-conflict": "指定された名前空間「\"<nowiki>$1</nowiki>\" 」は、MediaWikiのデフォルト名前空間と衝突しています。\n他のプロジェクト名前空間を指定してください。",
+ "config-admin-box": "管理アカウント",
+ "config-admin-name": "利用者名:",
+ "config-admin-password": "パスワード:",
+ "config-admin-password-confirm": "パスワードの再入力:",
+ "config-admin-help": "希望するユーザー名をここに入力してください (例:「Joe Bloggs」)。\nこの名前でこのウィキにログインすることになります。",
+ "config-admin-name-blank": "管理者のユーザー名を入力してください。",
+ "config-admin-name-invalid": "指定したユーザー名「<nowiki>$1</nowiki>」は無効です。\n別のユーザー名を指定してください。",
+ "config-admin-password-blank": "管理者アカウントのパスワードを入力してください。",
+ "config-admin-password-mismatch": "入力された2つのパスワードが一致しません。",
+ "config-admin-email": "メールアドレス:",
+ "config-admin-email-help": "メールアドレスを入力してください。他の利用者からのメールの受け取り、パスワードのリセット、ウォッチリストに登録したページの更新通知に使用します。空欄のままにすることもできます。",
+ "config-admin-error-user": "「<nowiki>$1</nowiki>」という名前の管理者を作成する際に内部エラーが発生しました。",
+ "config-admin-error-password": "管理者「<nowiki>$1</nowiki>」のパスワードを設定する際に内部エラーが発生しました: <pre>$2</pre>",
+ "config-admin-error-bademail": "無効なメールアドレスを入力しました。",
+ "config-subscribe": "[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce リリース告知のメーリングリスト]を購読する。",
+ "config-subscribe-help": "これは、リリースの告知 (重要なセキュリティに関する案内を含む) に使用される、流量が少ないメーリングリストです。\nこのメーリングリストを購読して、新しいバージョンが出た場合にMediaWikiを更新してください。",
+ "config-subscribe-noemail": "メールアドレスなしでリリースアナウンスのメーリングリストを購読しようとしています。\nメーリングリストを購読する場合にはメールアドレスを入力してください。",
+ "config-almost-done": "これでほぼ終わりました!\n残りの設定を飛ばして、ウィキを今すぐインストールできます。",
+ "config-optional-continue": "私にもっと質問してください。",
+ "config-optional-skip": "もう飽きてしまったので、とにかくウィキをインストールしてください。",
+ "config-profile": "利用者権限のプロファイル:",
+ "config-profile-wiki": "公開ウィキ",
+ "config-profile-no-anon": "アカウントの作成が必要",
+ "config-profile-fishbowl": "承認された編集者のみ",
+ "config-profile-private": "非公開ウィキ",
+ "config-profile-help": "ウィキは、できるだけ多くの人が編集できるようにすると最も優れた働きをします。\nMediaWikiでは、最近の更新を確認しやすく、神経質な、または悪意を持った利用者からの損害を簡単に差し戻せます。\n\nしかし一方で、MediaWikiは、さらにさまざまな形態での利用も優れていると言われています。また、時には、すべての人にウィキ手法の利点を説得させるのは容易ではないかもしれません。\nそこで、選択肢があります。\n\n「<strong>{{int:config-profile-wiki}}</strong>」モデルでは、ログインしなくても、誰でも編集できます。\n「<strong>{{int:config-profile-no-anon}}</strong>」なウィキでは、各編集に対してより強い説明責任を付与しますが、気軽な投稿を阻害するかもしれません。\n\n「<strong>{{int:config-profile-fishbowl}}</strong>」シナリオでは、承認された利用者のみが編集でき、一般の人はページ (とその履歴) を閲覧できます。\n「<strong>{{int:config-profile-private}}</strong>」では、承認された利用者のみがページを閲覧でき、そのグループが編集できます。\n\nより複雑な利用者権限の設定は、インストール後に設定できます。詳細は[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights 関連するマニュアル]をご覧ください。",
+ "config-license": "著作権とライセンス:",
+ "config-license-none": "ライセンスのフッターを付けない",
+ "config-license-cc-by-sa": "クリエイティブ・コモンズ 表示-継承",
+ "config-license-cc-by": "クリエイティブ・コモンズ 表示",
+ "config-license-cc-by-nc-sa": "クリエイティブ・コモンズ 表示-非営利-継承",
+ "config-license-cc-0": "クリエイティブ・コモンズ・ゼロ(パブリックドメイン)",
+ "config-license-gfdl": "GNU フリー文書利用許諾契約書 1.3 以降",
+ "config-license-pd": "パブリック・ドメイン",
+ "config-license-cc-choose": "その他のクリエイティブ・コモンズ・ライセンスを選択する",
+ "config-license-help": "多くの公開ウィキでは、すべての寄稿物が[http://freedomdefined.org/Definition フリーライセンス]のもとに置かれています。\nこうすることにより、コミュニティによる共有の感覚が生まれ、長期的な寄稿が促されます。\n私的ウィキや企業のウィキでは、通常、フリーライセンスにする必要はありません。\n\nウィキペディアにあるテキストをあなたのウィキで利用し、逆にあなたのウィキにあるテキストをウィキペディアに複製することを許可したい場合には、<strong>{{int:config-license-cc-by-sa}}</strong>を選択するべきです。\n\nウィキペディアは以前、GNUフリー文書利用許諾契約書(GFDL)を使用していました。\nGFDLは有効なライセンスですが、内容を理解するのは困難です。\nまた、GFDLのもとに置かれているコンテンツの再利用も困難です。",
+ "config-email-settings": "メールの設定",
+ "config-enable-email": "メール送信を有効にする",
+ "config-enable-email-help": "メールを使用したい場合は、[http://www.php.net/manual/en/mail.configuration.php PHP のメール設定]が正しく設定されている必要があります。\nメールの機能を使用しない場合は、ここで無効にすることができます。",
+ "config-email-user": "利用者間のメールを有効にする",
+ "config-email-user-help": "設定で有効になっている場合、すべてのユーザーがお互いにメールのやりとりを行うことを許可する。",
+ "config-email-usertalk": "ユーザーのトークページでの通知を有効にする",
+ "config-email-usertalk-help": "設定で有効にしている場合は、ユーザーのトークページの変更の通知を受けることをユーザーに許可する。",
+ "config-email-watchlist": "ウォッチリストの通知を有効にする",
+ "config-email-watchlist-help": "利用者が設定で有効にしている場合、閲覧されたページに関する通知を受け取ることを許可する。",
+ "config-email-auth": "メールの認証を有効にする",
+ "config-email-auth-help": "この選択肢を有効にすると、利用者がメールアドレスを設定あるいは変更したときに送信されるリンクにより、そのアドレスを確認しなければならなくなります。\n認証済みのアドレスだけが、他の利用者からのメールや、変更通知のメールを受け取ることができます。\n公開ウィキでは、メール機能による潜在的な不正利用の防止のため、この選択肢を設定することが<strong>推奨</strong>されます。",
+ "config-email-sender": "返信先メールアドレス:",
+ "config-email-sender-help": "送信メールで返信先として使用するメールアドレスを入力してください。\nこのアドレスは、宛先不明の場合の通知の宛先になります。\n多くのメールサーバーでは、少なくともドメイン名部分は有効である必要があります。",
+ "config-upload-settings": "画像およびファイルのアップロード",
+ "config-upload-enable": "ファイルのアップロードを有効にする",
+ "config-upload-help": "ファイルのアップロードは、あなたのサーバーをセキュリティ上の潜在的な危険に晒します。\nこの詳細は、マニュアルの [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security security section] をお読みください。\n\nファイルのアップロードを有効にするには、MediaWiki のルート ディレクトリ内の <code>images</code> サブ ディレクトリのモードを変更します。これにより、ウェブ サーバーがそこに書き込めるようになります。\nそして、このオプションを有効にしてください。",
+ "config-upload-deleted": "削除されたファイルのためのディレクトリ:",
+ "config-upload-deleted-help": "削除されるファイルを保存するためのディレクトリを選択してください。\nこれがウェブからアクセスできないことが理想です。",
+ "config-logo": "ロゴ のURL:",
+ "config-logo-help": "MediaWiki の既定の外装では、サイドバー上部に135x160ピクセルのロゴ用の余白があります。\n適切なサイズの画像をアップロードして、その URL をここに入力してください。\n\nロゴが相対パスの場合は、<code>$wgStylePath</code> や <code>$wgScriptPath</code> を使用できます。\n\nロゴが不要の場合は、この欄を空白のままにしてください。",
+ "config-instantcommons": "Instant Commons 機能を有効にする",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] は、[//commons.wikimedia.org/ ウィキメディア・コモンズ]のサイトにある画像、音声、その他のメディアをウィキ上で利用できるようにする機能です。\nこれを使用するには、MediaWiki がインターネットに接続できる必要があります。\n\nウィキメディア・コモンズ以外のウィキを同様に設定する手順など、この機能に関する詳細な情報は、[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos マニュアル]をご覧ください。",
+ "config-cc-error": "クリエイティブ・コモンズ・ライセンスの選択器から結果が得られませんでした。\nライセンスの名前を手動で入力してください。",
+ "config-cc-again": "もう一度選択してください...",
+ "config-cc-not-chosen": "希望するクリエイティブ・コモンズのライセンスを選択して、「続行」をクリックしてください。",
+ "config-advanced-settings": "高度な設定",
+ "config-cache-options": "オブジェクトのキャッシュの設定:",
+ "config-cache-help": "オブジェクトのキャッシュを使用すると、頻繁に使用するデータをキャッシュするため MediaWiki の動作速度を改善できます。\n中〜大規模サイトではこれを有効にすることを強くお勧めします。小規模サイトでも同様に効果があります。",
+ "config-cache-none": "キャッシングしない(機能は取り払われます、しかもより大きなウィキサイト上でスピードの問題が発生します)",
+ "config-cache-accel": "PHP オブジェクト キャッシュ (APC、XCache、WinCache のいずれか)",
+ "config-cache-memcached": "memcached を使用 (追加の設定が必要)",
+ "config-memcached-servers": "memcached サーバー:",
+ "config-memcached-help": "Memcachedを使用するIPアドレスの一覧。\nカンマ区切りで、利用する特定のポートの指定が必要です。例:\n127.0.0.1:11211\n192.168.1.25:1234",
+ "config-memcache-needservers": "キャッシュタイプに Memcached を選択しましたが、サーバーを指定していません。",
+ "config-memcache-badip": "Memcached用に無効な IPアドレス ($1) を入力しています。",
+ "config-memcache-noport": "Memcached サーバー $1 で使用するポート番号を指定していません。\nポート番号が分からない場合、既定値は 11211 です。",
+ "config-memcache-badport": "Memcached のポート番号は $1 から $2 の範囲にしてください。",
+ "config-extensions": "拡張機能",
+ "config-extensions-help": "<code>./extensions</code> ディレクトリ内で、上に列挙した拡張機能を検出しました。\n\nこれらの拡張機能には追加の設定が必要な場合がありますが、今すぐ有効化できます。",
+ "config-skins": "外装",
+ "config-skins-help": "以上に挙げたスキンは<code>./skins</code>で検出されたものです。\n少なくともひとつを有効にして、デフォルトを選択してください。",
+ "config-skins-use-as-default": "この外装をデフォルトとして使う",
+ "config-skins-missing": "外装が見つかりませんでした。適切なものをいくつかインストールするまで、MediaWikiはフォールバック外装を使用します。",
+ "config-skins-must-enable-some": "少なくとも1つの有効化する外装を選択する必要があります。",
+ "config-skins-must-enable-default": "デフォルトとして選択された外装は有効である必要があります。",
+ "config-install-alreadydone": "<strong>警告:</strong> 既にMediaWikiがインストール済みで、再びインストールし直そうとしています。\n次のページへ進んでください。",
+ "config-install-begin": "「{{int:config-continue}}」を押すと、MediaWiki のインストールを開始できます。\n変更したい設定がある場合は、「{{int:config-back}}」を押してください。",
+ "config-install-step-done": "実行",
+ "config-install-step-failed": "失敗した",
+ "config-install-extensions": "拡張機能を含む",
+ "config-install-database": "データベースの構築",
+ "config-install-schema": "スキーマの作成",
+ "config-install-pg-schema-not-exist": "PostgreSQL スキーマがありません。",
+ "config-install-pg-schema-failed": "テーブルの作成に失敗しました。\n利用者「$1」がスキーマ「$2」に書き込めるようにしてください。",
+ "config-install-pg-commit": "変更を送信",
+ "config-install-pg-plpgsql": "PL/pgSQLの言語をチェックしています",
+ "config-pg-no-plpgsql": "データベース $1 内に PL/pgSQL 言語をインストールする必要があります。",
+ "config-pg-no-create-privs": "インストール用に指定したアカウントには、アカウントを作成するのに十分な特権がありません。",
+ "config-pg-not-in-role": "ウェブユーザー用に指定したアカウントはすでに存在しています。\nインストール用に指定したアカウントはスーパーユーザーでなく、またウェブユーザーのロールを持ったものでもありません。そのためウェブユーザーが所有するオブジェクトを作成することができません。\n\nMediaWikiは現状では、ウェブユーザーが所有するテーブルを要求します。別のウェブアカウント名を指定するか、「戻る」をクリックして適切な権限を持つインストール用ユーザーを指定してください。",
+ "config-install-user": "データベースユーザーの作成",
+ "config-install-user-alreadyexists": "ユーザー「$1」は既に存在します",
+ "config-install-user-create-failed": "ユーザー「$1」の作成に失敗しました: $2",
+ "config-install-user-grant-failed": "ユーザー「$1」に許可を与えることに失敗しました: $2",
+ "config-install-user-missing": "指定したユーザー「$1」は存在しません。",
+ "config-install-user-missing-create": "指定したユーザー「$1」は存在しません。\nアカウントを作成する場合は、下の「アカウント作成」をクリックしてください。",
+ "config-install-tables": "テーブルの作成",
+ "config-install-tables-exist": "<strong>警告:</strong> MediaWiki テーブルは既に存在するようです。\n作成を省略します。",
+ "config-install-tables-failed": "<strong>エラー:</strong> テーブルの作成が、以下のエラーにより失敗しました: $1",
+ "config-install-interwiki": "既定のウィキ間テーブルの導入",
+ "config-install-interwiki-list": "ファイル <code>interwiki.list</code> から読み取れませんでした。",
+ "config-install-interwiki-exists": "<strong>警告:</strong> ウィキ間テーブルは既に登録されているようです。\n既定のテーブルを無視します。",
+ "config-install-stats": "統計情報の初期化",
+ "config-install-keys": "秘密鍵の生成",
+ "config-insecure-keys": "<strong>警告:</strong> インストール中に生成されたセキュアキー ($1) は完璧に安全ではありません。手動で変更することを検討してください。",
+ "config-install-sysop": "管理者のアカウントの作成",
+ "config-install-subscribe-fail": "mediawiki-announce を購読できませんでした: $1",
+ "config-install-subscribe-notpossible": "cURL がインストールされていないため、<code>allow_url_fopen</code> を利用できません。",
+ "config-install-mainpage": "メインページを既定の内容で作成",
+ "config-install-extension-tables": "有効にした拡張機能のためのテーブルを作成しています",
+ "config-install-mainpage-failed": "メインページを挿入できませんでした: $1",
+ "config-install-done": "<strong>おめでとうございます!</strong>\nMediaWikiのインストールに成功しました。\n\n<code>LocalSettings.php</code>ファイルが生成されました。\nこのファイルはすべての設定を含んでいます。\n\nこれをダウンロードして、ウィキをインストールした基準ディレクトリ (index.phpと同じディレクトリ) に設置する必要があります。ダウンロードは自動的に開始されるはずです。\n\nダウンロードが開始されていない場合、またはダウンロードをキャンセルした場合は、下記のリンクをクリックしてダウンロードを再開できます:\n\n$3\n\n<strong>注意:</strong> この生成された設定ファイルをダウンロードせずにインストールを終了すると、このファイルは利用できなくなります。\n\n上記の作業が完了すると、<strong>[$2 ウィキに入る]</strong>ことができます。",
+ "config-download-localsettings": "<code>LocalSettings.php</code> をダウンロード",
+ "config-help": "ヘルプ",
+ "config-help-tooltip": "クリックで展開",
+ "config-nofile": "ファイル「$1」が見つかりませんでした。削除された可能性があります。",
+ "config-extension-link": "あなたのウィキは[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions 拡張機能]をサポートしていることをご存知ですか?\n\n[//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category カテゴリ別で拡張機能を見る]か[//www.mediawiki.org/wiki/Extension_Matrix 拡張機能のマトリックス]で拡張機能すべてのリストをご覧になれます。",
+ "mainpagetext": "<strong>MediaWiki のインストールに成功しました。</strong>",
+ "mainpagedocfooter": "ウィキソフトウェアの使い方に関する情報は[//meta.wikimedia.org/wiki/Help:Contents 利用者案内]を参照してください。\n\n== はじめましょう ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/ja 設定の一覧]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/ja MediaWiki よくある質問と回答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki リリース情報メーリングリスト]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation/ja MediaWiki のあなたの言語へのローカライズ]"
+}
diff --git a/includes/installer/i18n/jam.json b/includes/installer/i18n/jam.json
new file mode 100644
index 00000000..c8850924
--- /dev/null
+++ b/includes/installer/i18n/jam.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Yocahuna"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki don instaal soksesful.'''",
+ "mainpagedocfooter": "Kansolt di [//meta.wikimedia.org/wiki/Help:Contents User's Guide] fi infamieshan ou fi yuuz di wiki saafwier.\n\n== Taatop ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/jut.json b/includes/installer/i18n/jut.json
new file mode 100644
index 00000000..10119bea
--- /dev/null
+++ b/includes/installer/i18n/jut.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Huslåke"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki er nu installeret.'''",
+ "mainpagedocfooter": "Se vores engelskspråĝede [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentåsje tilpasnenge'm åf æ brugergrænseflade] og [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide æ brugervejlednenge] før åplysnenger åpsætnenge'm og anvendelse."
+}
diff --git a/includes/installer/i18n/jv.json b/includes/installer/i18n/jv.json
new file mode 100644
index 00000000..4f53087a
--- /dev/null
+++ b/includes/installer/i18n/jv.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Prangkat empuk wiki wis suksès dipasang.'''",
+ "mainpagedocfooter": "Mangga maca [//meta.wikimedia.org/wiki/Help:Contents User's Guide] kanggo katrangan luwih langkung prakara panggunan prangkat empuk wiki\n== Miwiti panggunan ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Daftar pangaturan préférènsi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]"
+}
diff --git a/includes/installer/i18n/ka.json b/includes/installer/i18n/ka.json
new file mode 100644
index 00000000..726b74da
--- /dev/null
+++ b/includes/installer/i18n/ka.json
@@ -0,0 +1,97 @@
+{
+ "@metadata": {
+ "authors": [
+ "David1010"
+ ]
+ },
+ "config-information": "ინფორმაცია",
+ "config-your-language": "თქვენი ენა:",
+ "config-wiki-language": "ვიკის ენა:",
+ "config-back": "← უკან",
+ "config-continue": "გაგრძელება →",
+ "config-page-language": "ენა",
+ "config-page-welcome": "კეთილი იყოს თქვენი მობრძანება მედიავიკიში!",
+ "config-page-dbconnect": "მონაცემთა ბაზასთან დაკავშირება",
+ "config-page-dbsettings": "მონაცემთა ბაზის კონფიგურაცია",
+ "config-page-name": "სახელი",
+ "config-page-options": "პარამეტრები",
+ "config-page-install": "ინსტალაცია",
+ "config-page-complete": "დასრულებულია!",
+ "config-page-restart": "ინსტალაციის თავიდან დაწყება",
+ "config-page-readme": "წამიკითხე",
+ "config-page-copying": "ლიცენზია",
+ "config-page-upgradedoc": "განახლება",
+ "config-page-existingwiki": "არსებული ვიკი",
+ "config-restart": "დიახ, თავიდან დაიწყეთ",
+ "config-sidebar": "* [//www.mediawiki.org მედიავიკის ვებ-გვერდი]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/ka მომხმარებლების დახმარება]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/ka ადმინისტრატორების დახმარება]\n* [//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-xcache": "[http://xcache.lighttpd.net/ XCache] დაყენდა",
+ "config-apc": "[http://www.php.net/apc APC] დაყენდა",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] დაყენდა",
+ "config-diff3-bad": "GNU diff3 ვერ მოიძებნა.",
+ "config-db-type": "მონაცემთა ბაზის ტიპი:",
+ "config-db-host-oracle": "მონაცემთა ბაზის TNS:",
+ "config-db-name": "მონაცემთა ბაზის სახელი:",
+ "config-db-name-oracle": "მონაცემთა ბაზის სქემა:",
+ "config-db-username": "მონაცემთა ბაზის მომხმარებლის სახელი:",
+ "config-db-password": "მონაცემთა ბაზის პაროლი:",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 ორობითი",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-db-port": "მონაცემთა ბაზის პორტი:",
+ "config-db-schema": "მედიავიკის სქემა:",
+ "config-header-mysql": "MySQL-ის პარამეტრები",
+ "config-header-postgres": "PostgreSQL-ის პარამეტრები",
+ "config-header-sqlite": "SQLite-ის პარამეტრები",
+ "config-header-oracle": "Oracle-ის პარამეტრები",
+ "config-invalid-db-type": "არასწორი მონაცემთა ბაზის ტიპი",
+ "config-sqlite-readonly": "ფაილი <code>$1</code> ჩასაწერად მიუწვდომელია.",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "ორობითი",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "ვიკის სახელი:",
+ "config-site-name-blank": "შეიყვანეთ ვებ-გვერდის სახელი.",
+ "config-project-namespace": "პროექტის სახელთა სივრცე:",
+ "config-ns-generic": "პროექტი",
+ "config-ns-other": "სხვა (მიუთითეთ)",
+ "config-ns-other-default": "ჩემი ვიკი",
+ "config-admin-box": "ადმინისტრატორის ანგარიში",
+ "config-admin-name": "თქვენი მომხმარებლის სახელი:",
+ "config-admin-password": "პაროლი:",
+ "config-admin-password-confirm": "პაროლი ხელმეორედ:",
+ "config-admin-name-blank": "შეიყვანეთ ადმინისტრატორის მომხმარებლის სახელი.",
+ "config-admin-password-blank": "შეიყვანეთ ადმინისტრატორის ანგარიშის პაროლი.",
+ "config-admin-password-mismatch": "თქვენ მიერ შეყვანილი პაროლები ერთმანეთს არ ემთხვევა.",
+ "config-admin-email": "ელ. ფოსტის მისამართი:",
+ "config-admin-error-bademail": "თქვენ მიერ შეყვანილი ელ.ფოსტა არასწორია.",
+ "config-subscribe": "გამოიწერეთ [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce მედიავიკის ახალი ვერსიის გამოსვლის სიახლეები].",
+ "config-profile": "მომხმარებელთა უფლებების პროფილი:",
+ "config-profile-wiki": "ღია ვიკი",
+ "config-profile-no-anon": "საჭიროა ანგარიშის შექმნა",
+ "config-profile-fishbowl": "მხოლოდ ავტორიზებული რედაქტორებისათვის",
+ "config-profile-private": "დახურული ვიკი",
+ "config-license": "საავტორო უფლები და ლიცენზია:",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (საზოგადოებრივი საკუთრება)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 ან უფრო გვიანი",
+ "config-license-pd": "საზოგადოებრივი საკუთრება",
+ "config-license-cc-choose": "აირჩიეთ Creative Commons-ის ლიცენზიიდან ერთ-ერთი",
+ "config-email-settings": "ელ. ფოსტის პარამეტრები",
+ "config-upload-settings": "სურათებისა და ფაილების ატვირთვა",
+ "config-upload-enable": "ფაილების ატვირთვის ჩართვა",
+ "config-logo": "ლოგოს URL:",
+ "config-cc-again": "აირჩიეთ კიდევ ერთხელ...",
+ "config-advanced-settings": "დამატებითი კონფიგურაცია",
+ "config-extensions": "გაფართოებები",
+ "config-install-step-done": "შესრულდა",
+ "config-install-step-failed": "ვერ მოხერხდა",
+ "config-install-schema": "სქემის შექმნა",
+ "config-install-tables": "ცხრილების შექმნა",
+ "config-install-interwiki-list": "ვერ მოიძებნა ფაილი <code>interwiki.list</code>.",
+ "config-download-localsettings": "<code>LocalSettings.php</code>-ის გადმოწერა",
+ "config-help": "დახმარება",
+ "config-help-tooltip": "გასაშლელად დააწკაპუნეთ",
+ "mainpagetext": "'''მედიავიკი წარმატებით ჩაიტვირთა.'''",
+ "mainpagedocfooter": "ვიკი პროგრამის გამოყენების ინფორმაციისთვის იხილეთ [//meta.wikimedia.org/wiki/Help:Contents მომხმარებლის მეგზური].\n\n== დაწყება ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings კონფიგურაციის მაჩვენებლების სია]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce მედიავიკის გამოცემის დაგზავნის სია]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources მედიავიკის ლოკალიზება თქვენ ენაზე]"
+}
diff --git a/includes/installer/i18n/kaa.json b/includes/installer/i18n/kaa.json
new file mode 100644
index 00000000..a2052c20
--- /dev/null
+++ b/includes/installer/i18n/kaa.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki tabıslı ornatıldı.'''",
+ "mainpagedocfooter": "Wiki bag'darlamasın qollanıw haqqındag'i mag'lıwmat usın [//meta.wikimedia.org/wiki/Help:Contents Paydalanıwshılar qollanbasınan] ken'es alın'.\n\n== Baslaw ushın ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Konfiguratsiya sazlaw dizimi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWikidin' Ko'p Soralatug'ın Sorawları]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki haqqında xat tarqatıw dizimi]"
+}
diff --git a/includes/installer/i18n/kbd-cyrl.json b/includes/installer/i18n/kbd-cyrl.json
new file mode 100644
index 00000000..b08674f6
--- /dev/null
+++ b/includes/installer/i18n/kbd-cyrl.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bogups",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''«MediaWiki» узыншу хэгъува.'''",
+ "mainpagedocfooter": "Мы виким и лэжьыгъэ хъыбархэр здэбгъуэтыфынур [//meta.wikimedia.org/wiki/Help:Contents/ru дэӀэпыкъуэгъу тхылъым].\n\n== Къыщхьэпэгъуэ хъуфынухэр ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Зэгъэзэхуэгъуэ гуэрэхэм я тхылъ];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-м упщӀэ нахъыбу ятхэмрэ я жэуапхэмрэ];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-м и версиэ щӀэуэ къэжахэм я къэӀохугъуэ].\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/khw.json b/includes/installer/i18n/khw.json
new file mode 100644
index 00000000..a2e90b6e
--- /dev/null
+++ b/includes/installer/i18n/khw.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rachitrali"
+ ]
+ },
+ "mainpagetext": "'''میڈیاوکیو کامیابیو سورا چالو کورونو بیتی شیر۔.'''"
+}
diff --git a/includes/installer/i18n/kiu.json b/includes/installer/i18n/kiu.json
new file mode 100644
index 00000000..f322db51
--- /dev/null
+++ b/includes/installer/i18n/kiu.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mirzali"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki fist ra ser, vıraziya.'''",
+ "mainpagedocfooter": "Serba melumatê gurenaena ''wiki software''i [//meta.wikimedia.org/wiki/Help:Contents İdarê karberi] de mıracaet ke.\n\n== Gamê verêni ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista ayarunê vırastene]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki de ÇZP]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-daena postey]"
+}
diff --git a/includes/installer/i18n/kk-arab.json b/includes/installer/i18n/kk-arab.json
new file mode 100644
index 00000000..15cb54a4
--- /dev/null
+++ b/includes/installer/i18n/kk-arab.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''مەدىياۋىيكىي بۋماسى ٴساتتى ورناتىلدى.'''",
+ "mainpagedocfooter": "ۋىيكىي باعدارلامالىق جاساقتاماسىن قالاي قولداناتىن اقپاراتى ٴۇشىن [//meta.wikimedia.org/wiki/Help:Contents پايدالانۋشىلىق نۇسقاۋلارىنان] كەڭەس الىڭىز.\n\n== باستاۋ ٴۇشىن ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings باپتالىم قالاۋلارىنىڭ ٴتىزىمى]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ مەدىياۋىيكىيدىڭ جىيى قويىلعان ساۋالدارى]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce مەدىياۋىيكىي شىعۋ تۋرالى حات تاراتۋ ٴتىزىمى]"
+}
diff --git a/includes/installer/i18n/kk-cyrl.json b/includes/installer/i18n/kk-cyrl.json
new file mode 100644
index 00000000..08ff57f0
--- /dev/null
+++ b/includes/installer/i18n/kk-cyrl.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''МедиаУики бумасы сәтті орнатылды.'''",
+ "mainpagedocfooter": "Уики бағдарламалық жасақтамасын қалай қолданатын ақпараты үшін [//meta.wikimedia.org/wiki/Help:Contents Пайдаланушылық нұсқауларынан] кеңес алыңыз.\n\n== Бастау үшін ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Бапталым қалауларының тізімі]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ МедиаУикидің Жиы Қойылған Сауалдары]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаУики шығу туралы хат тарату тізімі]"
+}
diff --git a/includes/installer/i18n/kk-latn.json b/includes/installer/i18n/kk-latn.json
new file mode 100644
index 00000000..fb261bd2
--- /dev/null
+++ b/includes/installer/i18n/kk-latn.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MedïaWïkï bwması sätti ornatıldı.'''",
+ "mainpagedocfooter": "Wïkï bağdarlamalıq jasaqtamasın qalaý qoldanatın aqparatı üşin [//meta.wikimedia.org/wiki/Help:Contents Paýdalanwşılıq nusqawlarınan] keñes alıñız.\n\n== Bastaw üşin ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Baptalım qalawlarınıñ tizimi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MedïaWïkïdiñ Jïı Qoýılğan Sawaldarı]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MedïaWïkï şığw twralı xat taratw tizimi]"
+}
diff --git a/includes/installer/i18n/km.json b/includes/installer/i18n/km.json
new file mode 100644
index 00000000..8318e9cc
--- /dev/null
+++ b/includes/installer/i18n/km.json
@@ -0,0 +1,32 @@
+{
+ "@metadata": {
+ "authors": [
+ "Thearith",
+ "គីមស៊្រុន",
+ "Sovichet",
+ "Seb35"
+ ]
+ },
+ "config-desc": "កម្មវិធី​ដំឡើង​សម្រាប់ MediaWiki",
+ "config-title": "ការ​ដំឡើង MediaWiki $1",
+ "config-information": "ព័ត៌មាន",
+ "config-localsettings-badkey": "អ្នក​បាន​ផ្ដល់​សោ​មិន​ត្រឹម​ត្រូវ។",
+ "config-your-language": "ភាសារបស់អ្នក៖",
+ "config-your-language-help": "ជ្រើសយកភាសាដើម្បីប្រើក្នុងពេលតំលើង។",
+ "config-wiki-language": "ភាសាវិគី៖",
+ "config-wiki-language-help": "ជ្រើសរើសភាសាដែលវិគីនេះប្រើជាចំបង។",
+ "config-back": "← ត្រលប់ក្រោយ",
+ "config-continue": "បន្ត →",
+ "config-page-language": "ភាសា",
+ "config-page-welcome": "មេឌាវិគីសូមស្វាគមន៍!",
+ "config-page-dbconnect": "ភ្ជាប់ទៅមូលដ្ឋានទិន្នន័យ",
+ "config-page-dbsettings": "ការកំណត់​មូលដ្ឋាន​ទិន្នន័យ",
+ "config-page-name": "ឈ្មោះ",
+ "config-page-options": "ជំរើស",
+ "config-page-install": "តំលើង",
+ "config-page-complete": "បញ្ចប់!",
+ "config-page-restart": "តំលើងឡើងវិញ",
+ "config-help": "ជំនួយ",
+ "mainpagetext": "'''មេឌាវិគីត្រូវបានដំឡើងសំរេចហើយ​។'''",
+ "mainpagedocfooter": "សូមពិនិត្យមើល [//meta.wikimedia.org/wiki/Help:Contents ខ្លឹមសារ​ណែនាំ​ប្រើប្រាស់]សម្រាប់​ព័ត៌មាន​​បន្ថែមអំពី​ការប្រើប្រាស់សូហ្វវែរវិគី​។\n\n== ការចាប់ផ្ដើម ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings បញ្ជីការកំណត់នានា]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/km សំណួរញឹកញាប់​ក្នុងមេឌាវិគី]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce បញ្ជី​អ៊ីមែលផ្សព្វផ្សាយ​របស់​មេឌាវិគី]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources ការប្រែសម្រួលមេឌាវិគីសម្រាប់ភាសារបស់អ្នក]"
+}
diff --git a/includes/installer/i18n/kn.json b/includes/installer/i18n/kn.json
new file mode 100644
index 00000000..2c84dc3c
--- /dev/null
+++ b/includes/installer/i18n/kn.json
@@ -0,0 +1,46 @@
+{
+ "@metadata": {
+ "authors": [
+ "VASANTH S.N."
+ ]
+ },
+ "config-information": "ಮಾಹಿತಿ",
+ "config-localsettings-key": "ಉನ್ನತೀಕರಣ ಕೀಲಿ",
+ "config-localsettings-badkey": "ನೀವು ನೀಡಿದ ಕೀಲಿ ಸರಿಯಾಗಿಲ್ಲ",
+ "config-session-error": "ಅವಧಿ ಪ್ರಾರಂಭ ದೋಷ: $1",
+ "config-your-language": "ನಿಮ್ಮ ಭಾಷೆ:",
+ "config-wiki-language": "ವಿಕಿ ಭಾಷೆ:",
+ "config-back": "← ವಾಪಸು",
+ "config-continue": "ಮುಂದುವರೆಸಿ →",
+ "config-page-language": "ಭಾಷೆ",
+ "config-page-welcome": "ಮಾಧ್ಯಮವಿಕಿಗೆ ಸ್ವಾಗತ",
+ "config-page-dbconnect": "ದತ್ತಾಂಶಸಂಚಯಕ್ಕೆ ಸಂಪರ್ಕನೀಡಿ",
+ "config-page-name": "ಹೆಸರು",
+ "config-page-options": "ಆಯ್ಕೆಗಳು",
+ "config-page-install": "ಸ್ಥಾಪಿಸು",
+ "config-page-complete": "ಪೂರ್ಣ!",
+ "config-page-readme": "ನನ್ನನ್ನು ಓದಿ",
+ "config-page-copying": "ನಕಲಿಸುತ್ತಿದೆ..",
+ "config-page-upgradedoc": "ಪರಿಷ್ಕರಿಸಲ್ಪಡುತ್ತಿದೆ",
+ "config-page-existingwiki": "ಪ್ರಸ್ತುತ ವಿಕಿ",
+ "config-restart": "ಸರಿ,ಪುನಃ ಪ್ರಾರಂಭಿಸಿ",
+ "config-db-type": "ದತ್ತಾಂಶಸಂಚಯ ಮಾದರಿ:",
+ "config-db-host-oracle": "ದತ್ತಾಂಶಸಂಚಯ TNS:",
+ "config-db-wiki-settings": "ಈ ವಿಕಿಯನ್ನು ಗುರುತಿಸಿ",
+ "config-db-name": "ದತ್ತಾಂಶಸಂಚಯ ಹೆಸರು:",
+ "config-db-username": "ದತ್ತಾಂಶಸಂಚಯ ಬಳಕೆದಾರಹೆಸರು:",
+ "config-db-password": "ದತ್ತಾಂಶಸಂಚಯ ಪ್ರವೇಶಪದ:",
+ "config-ns-generic": "ಯೋಜನೆ",
+ "config-admin-name": "ನಿಮ್ಮ ಬಳಕೆದಾರಹೆಸರು:",
+ "config-admin-password": "ಪ್ರವೇಶಪದ:",
+ "config-admin-password-confirm": "ಪುನಃ ಪ್ರವೇಶಪದ:",
+ "config-admin-password-mismatch": "ನೀವು ಕೊಟ್ಟ ಪ್ರವೇಶಪದಗಳು ಬೇರೆಬೇರೆಯಾಗಿವೆ.",
+ "config-admin-email": "ಮಿಂಚಂಚೆ ವಿಳಾಸ:",
+ "config-optional-continue": "ನನ್ನಲ್ಲಿ ಹೆಚ್ಚಿನ ಪ್ರಶ್ನೆಗಳನ್ನು ಕೇಳಿ.",
+ "config-license": "ಕೃತಿಸ್ವಾಮ್ಯ ಮತ್ತು ಪರವಾನಗಿ:",
+ "config-extensions": "ವಿಸ್ತರಣೆಗಳು",
+ "config-install-step-failed": "ವಿಫಲವಾಗಿದೆ",
+ "config-help": "ಸಹಾಯ",
+ "mainpagetext": "'''ವಿಕಿ ತಂತ್ರಾಂಶವನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಅನುಸ್ಥಾಪಿಸಲಾಯಿತು.'''",
+ "mainpagedocfooter": "ವಿಕಿ ತಂತ್ರಾಂಶವನ್ನು ಬಳಸುವ ಬಗ್ಗೆ ಮಾಹಿತಿಗೆ [//meta.wikimedia.org/wiki/Help:Contents ಬಳಕೆದಾರರಿಗೆ ನಿರ್ದೇಶನ ಪುಟ] ನೋಡಿ.\n\n== ಪ್ರಾರಂಭಿಸುವುದು ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ಮೀಡಿಯವಿಕಿ FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/ko.json b/includes/installer/i18n/ko.json
new file mode 100644
index 00000000..bc828a54
--- /dev/null
+++ b/includes/installer/i18n/ko.json
@@ -0,0 +1,323 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kwj2772",
+ "아라",
+ "Hym411",
+ "Priviet",
+ "Namoroka"
+ ]
+ },
+ "config-desc": "미디어위키 설치 프로그램",
+ "config-title": "미디어위키 $1 설치",
+ "config-information": "정보",
+ "config-localsettings-upgrade": "<code>LocalSettings.php</code> 파일을 감지했습니다.\n이 설치를 업그레이드하려면 아래 상자에 <code>$wgUpgradeKey</code>의 값을 입력하세요.\n<code>LocalSettings.php</code>에서 찾을 수 있습니다.",
+ "config-localsettings-cli-upgrade": "<code>LocalSettings.php</code> 파일을 감지했습니다.\n이 설치를 업그레이드하려면 <code>update.php</code>를 대신 실행하세요",
+ "config-localsettings-key": "업그레이드 키:",
+ "config-localsettings-badkey": "입력한 키가 잘못되었습니다.",
+ "config-upgrade-key-missing": "기존에 설치 했던 미디어위키를 감지했습니다.\n이 설치를 업그레이드하려면 <code>LocalSettings.php</code>의 아래에 다음 줄을 넣으세요:\n\n$1",
+ "config-localsettings-incomplete": "기존 <code>LocalSettings.php</code>가 완전하지 않은 것 같습니다.\n$1 변수가 설정되어 있지 않습니다.\n이 변수가 설정되도록 <code>LocalSettings.php</code>를 바꾸고 \"{{int:Config-continue}}\"을 클릭하세요.",
+ "config-localsettings-connection-error": "<code>LocalSettings.php</code>에 지정한 설정을 사용하여 데이터베이스에 연결할 때 오류가 발생했습니다. 이러한 설정을 고치고 다시 시도하세요.\n\n$1",
+ "config-session-error": "세션 시작 오류: $1",
+ "config-session-expired": "세션 데이터가 만료된 것 같습니다.\n세션은 $1의 작동 시간 동안 구성됩니다.\nphp.ini에 있는 <code>session.gc_maxlifetime</code>에서 설정해 이를 증가시킬 수 있습니다.\n설치 과정을 다시 시작하세요.",
+ "config-no-session": "세션 데이터가 없어졌습니다!\nphp.ini를 확인하고 <code>session.save_path</code>가 적절한 디렉토리로 설정되어 있는지 확인하세요.",
+ "config-your-language": "설치 언어:",
+ "config-your-language-help": "설치 과정에서 사용할 언어를 선택하세요.",
+ "config-wiki-language": "위키 언어:",
+ "config-wiki-language-help": "위키에 주로 작성될 언어를 선택하세요.",
+ "config-back": "← 뒤로",
+ "config-continue": "계속 →",
+ "config-page-language": "언어",
+ "config-page-welcome": "미디어위키에 오신 것을 환영합니다!",
+ "config-page-dbconnect": "데이터베이스에 연결",
+ "config-page-upgrade": "기존 설치 업그레이드",
+ "config-page-dbsettings": "데이터베이스 설정",
+ "config-page-name": "이름",
+ "config-page-options": "설정",
+ "config-page-install": "설치",
+ "config-page-complete": "완료!",
+ "config-page-restart": "설치 다시 시작",
+ "config-page-readme": "읽어보기",
+ "config-page-releasenotes": "릴리스 노트",
+ "config-page-copying": "전문",
+ "config-page-upgradedoc": "업그레이드하기",
+ "config-page-existingwiki": "기존 위키",
+ "config-help-restart": "입력한 모든 저장된 데이터를 지우고 설치 과정을 다시 시작하겠습니까?",
+ "config-restart": "예, 다시 시작합니다",
+ "config-welcome": "=== 사용 환경 검사 ===\n기본 검사는 지금 이 환경이 미디어위키 설치에 적합한지 수행합니다.\n설치를 완료하는 방법에 대한 지원을 찾는다면 이 정보를 포함해야 하는 것을 기억하세요.",
+ "config-copyright": "=== 저작권 및 이용 약관 ===\n\n$1\n\n이 프로그램은 자유 소프트웨어입니다. 당신은 자유 소프트웨어 재단이 발표한 GNU 일반 공중 사용 허가서 버전 2나 그 이후 버전에 따라 이 프로그램을 재배포하거나 수정할 수 있습니다.\n\n이 프로그램이 유용하게 사용될 수 있기를 바라지만 '''상용으로 사용'''되거나 '''특정 목적에 맞을 것'''이라는 것을 '''보증하지 않습니다'''.\n자세한 내용은 GNU 일반 공중 사용 허가서를 참고하십시오.\n\n당신은 이 프로그램을 통해 <doclink href=Copying>GNU 일반 공중 사용 허가서 전문</doclink>을 받았습니다. 그렇지 않다면, Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA로 편지를 보내주시거나 [http://www.gnu.org/copyleft/gpl.html 온라인으로 읽어보시기] 바랍니다.",
+ "config-sidebar": "* [//www.mediawiki.org 미디어위키 홈]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 사용자 가이드]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents 관리자 가이드]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>읽어보기</doclink>\n* <doclink href=ReleaseNotes>릴리스 노트</doclink>\n* <doclink href=Copying>전문</doclink>\n* <doclink href=UpgradeDoc>업그레이드하기</doclink>",
+ "config-env-good": "환경이 확인되었습니다.\n미디어위키를 설치할 수 있습니다.",
+ "config-env-bad": "환경이 확인되었습니다.\n미디어위키를 설치할 수 없습니다.",
+ "config-env-php": "PHP $1(이)가 설치되었습니다.",
+ "config-unicode-using-utf8": "유니코드 정규화에 Brion Vibber의 utf8_normalize.so를 사용합니다.",
+ "config-unicode-using-intl": "유니코드 정규화에 [http://pecl.php.net/intl intl PECL 확장 기능]을 사용합니다.",
+ "config-unicode-pure-php-warning": "'''경고''': 유니코드 정규화를 처리할 [http://pecl.php.net/intl intl PECL 확장 기능]을 사용할 수 없기 때문에 느린 pure-PHP 구현을 대신 사용합니다.\n트래픽이 높은 사이트에서 실행하시려면 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 유니코드 정규화]를 읽어보시기 바랍니다.",
+ "config-unicode-update-warning": "'''경고''': 유니코드 정규화 래퍼의 설치된 버전은 [http://site.icu-project.org/ ICU 프로젝트]의 라이브러리의 이전 버전을 사용합니다.\n만약 유니코드를 사용하는 것에 대해 우려가 된다면 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 업그레이드]해야합니다.",
+ "config-no-db": "적절한 데이터베이스 드라이버를 찾을 수 없습니다! PHP용 데이터베이스 드라이버를 설치해야 합니다.\n다음 데이터베이스 유형을 지원합니다: $1.\n\nPHP를 직접 컴파일했다면, 예를 들어 <code>./configure --with-mysql</code>을 사용하여, 데이터베이스 클라이언트를 활성화하도록 다시 설정하세요.\n데비안이나 우분투 패키지에서 PHP를 설치했다면 <code>php5-mysql</code> 모듈도 설치해야 합니다.",
+ "config-outdated-sqlite": "'''경고''': 최소인 $2 버전보다 낮은 SQLite $1(이)가 있습니다. SQLite를 사용할 수 없습니다.",
+ "config-no-fts3": "'''경고''': SQLite를 [//sqlite.org/fts3.html FTS3 모듈] 없이 컴파일하며, 검색 기능은 백엔드에 사용할 수 없습니다.",
+ "config-register-globals-error": "<strong>오류: PHP의 <code>[http://php.net/register_globals register_globals]</code> 옵션이 활성화되어 있습니다.\n설치를 계속하려면 비활성화해야 합니다.</strong>\n어떻게 하는지에 대한 도움말에 대해서는 [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals]를 보세요.",
+ "config-magic-quotes-runtime": "'''치명: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]이 활성화됩니다!'''\n이 옵션은 데이터를 입력하는 데 예기치 않는 손상이 일으킵니다.\n이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
+ "config-magic-quotes-sybase": "'''치명: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]이 활성화됩니다!'''\n이 옵션은 데이터를 입력하는 데 예기치 않는 손상을 일으킵니다.\n이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
+ "config-mbstring": "'''치명: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]이 활성화됩니다!'''\n이 옵션은 오류가 발생하고 데이터를 입력하는 데 예기치 않는 손상을 일으킬 수 있습니다.\n이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
+ "config-safe-mode": "'''경고:''' [http://www.php.net/features.safe-mode 안전 모드]가 활성화됩니다!\n특히 파일을 올리거나 <code>math</code>를 지원하는 데 문제가 발생할 수 있습니다.",
+ "config-xml-bad": "PHP의 XML 모듈이 없습니다.\n미디어위키는 이 모듈의 기능이 필요하며 이 설정에서는 작동하지 않습니다.\nMandrake를 실행하고 있다면 php-xml 패키지를 설치하세요.",
+ "config-pcre-old": "'''치명적인 오류:''' PCRE $1 또는 이후의 것들이 필요합니다. 당신의 PHP 바이너리는 PCRE $2와 연결되어 있습니다. [https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE 더 많은 정보].",
+ "config-pcre-no-utf8": "'''치명''': PHP의 PCRE 모듈은 RCRE_UTF8 지원 없이 컴파일된 것 같습니다.\n미디어위키가 제대로 작동하려면 UTF-8을 지원해야 합니다.",
+ "config-memory-raised": "PHP의 <code>memory_limit</code>는 $1이며 $2(으)로 늘렸습니다.",
+ "config-memory-bad": "'''경고:''' PHP의 <code>memory_limit</code>는 $1입니다.\n아마도 너무 낮은 것 같습니다.\n설치가 실패할 수 있습니다!",
+ "config-ctype": "'''치명''': PHP는 [http://www.php.net/manual/en/ctype.installation.php Ctype 확장 기능]을 지원하도록 하여 컴파일해야 합니다.",
+ "config-json": "'''치명:''' PHP가 JSON 지원이 없이 컴파일되었습니다.\n미디어위키를 설치하기 전에 PHP JSON 확장 기능이나 [http://pecl.php.net/package/jsonc PECL jsonc] 확장 기능 중 하나를 설치해야 합니다.\n* PHP 확장 기능은 Red Hat Enterprise Linux (CentOS) 5와 6에 포함되어 있지만, <code>/etc/php.ini</code>나 <code>/etc/php.d/json.ini</code>에서 활성화해야 합니다.\n* 2013년 5월 이후에 출시된 일부 리눅스 배포판은 PHP 확장 기능이 생략된 대신, <code>php5-json</code>이나 <code>php-pecl-jsonc</code>로 PECL 확장 기능이 포장되어 있습니다.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache]가 설치되었습니다",
+ "config-apc": "[http://www.php.net/apc APC]가 설치되었습니다",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache]가 설치되었습니다",
+ "config-no-cache": "'''경고:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] 또는 [http://www.iis.net/download/WinCacheForPhp WinCache]를 찾을 수 없습니다.\n개체 캐싱을 활성화하지 않습니다.",
+ "config-mod-security": "'''경고''': 웹 서버에 [http://modsecurity.org/ mod_security]가 허용되었습니다. 잘못 설정된 경우 미디어위키나 사용자가 임의의 내용을 게시할 수 있는 다른 소프트웨어에 대한 문제를 일으킬 수 있습니다.\n[http://modsecurity.org/documentation/ mod_security] 문서를 참고하거나 임의의 오류가 발생할 경우 호스트의 지원 요청에 문의하십시오.",
+ "config-diff3-bad": "GNU diff3를 찾을 수 없습니다.",
+ "config-git": "Git 버전 관리 소프트웨어를 찾았습니다: <code>$1</code>.",
+ "config-git-bad": "Git 버전 관리 소프트웨어를 찾을 수 없습니다.",
+ "config-imagemagick": "ImageMagick를 찾았습니다: <code>$1</code>.\n올리기를 활성화할 경우 그림 섬네일이 활성화됩니다.",
+ "config-gd": "내장된 GD 그래픽 라이브러리를 찾았습니다.\n올리기를 활성화할 경우 그림 섬네일이 활성화됩니다.",
+ "config-no-scaling": "GD 라이브러리나 ImageMagick를 찾을 수 없습니다.\n그림 섬네일이 비활성화됩니다.",
+ "config-no-uri": "'''오류:''' 현재 URI를 확인할 수 없습니다.\n설치가 중단되었습니다.",
+ "config-no-cli-uri": "'''경고''': 기본값을 사용하여 <code>--scriptpath</code>를 지정하지 않았습니다: <code>$1</code>.",
+ "config-using-server": "\"<nowiki>$1</nowiki>\"(을)를 서버 이름으로 사용합니다.",
+ "config-using-uri": "\"<nowiki>$1$2</nowiki>\"(을)를 서버 URL로 사용합니다.",
+ "config-uploads-not-safe": "'''경고:''' 올리기에 대한 기본 디렉터리(<code>$1</code>)는 임의의 스크립트 실행에 취약합니다.\n미디어위키는 보안 위협 때문에 모든 올려진 파일을 검사하지만, 올리기를 활성화하기 전에 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security 이 보안 취약점을 해결할 것]을 매우 권장합니다.",
+ "config-no-cli-uploads-check": "'''경고:''' 올리기를 위한 기본 디렉터리(<code>$1</code>)는 CLI를 설치하는 동안 임의의 스크립트 실행에 대한 취약점에 대해 검사되지 않습니다.",
+ "config-brokenlibxml": "시스템에 버그가 있는 PHP와 libxml2의 조합이 있으며 미디어위키나 다른 웹 어플리케이션에 숨겨진 데이터 손상을 일으킬 수 있습니다.\nlibxml2 2.7.3 이후 버전으로 업그레이드하세요. ([https://bugs.php.net/bug.php?id=45996 PHP에 제기한 버그])\n설치가 중단되었습니다.",
+ "config-suhosin-max-value-length": "수호신(Suhosin)이 설치되고 $1 바이트로 GET 매개 변수 <code>length</code>를 제한하고 있습니다.\n미디어위키의 ResourceLoader 구성 요소는 이 제한을 회피하지만 성능이 저하됩니다.\n가능하면 <code>php.ini</code>의 <code>suhosin.get.max_value_length</code>를 1024 이상으로 설정하고 <code>LocalSettings.php</code>의 <code>$wgResourceLoaderMaxQueryLength</code>를 같은 값으로 설정해야 합니다.",
+ "config-db-type": "데이터베이스 종류:",
+ "config-db-host": "데이터베이스 호스트:",
+ "config-db-host-help": "데이터베이스 서버가 다른 서버에 있으면 여기에 호스트 이름이나 IP 주소를 입력하세요.\n\n공유하는 웹 호스팅을 사용하고 있으면 호스팅 제공 업체는 정확한 호스트 이름을 설명하고 있을 것입니다.\n\n윈도 서버에 설치하고 MySQL을 사용하면 \"localhost\"가 해당 서버 이름으로는 작동하지 않을 수 있습니다. 그렇게 된다면 로컬 IP 주소로 \"127.0.0.1\"을 시도하세요.\n\nPostgreSQL을 사용하면 유닉스 소켓을 통해 연결되도록 입력란을 비워두세요.",
+ "config-db-host-oracle": "데이터베이스 TNS:",
+ "config-db-host-oracle-help": "올바른 [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 로컬 연결 이름]을 입력하세요. tnsnames.ora 파일이 이 설치 위치에서 참조할 수 있는 곳에 있어야 합니다.<br />10g 이후의 클라이언트 라이브러리를 사용하는 경우 [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 쉬운 연결] 네이밍 메서드도 사용할 수 있습니다.",
+ "config-db-wiki-settings": "이 위키 식별",
+ "config-db-name": "데이터베이스 이름:",
+ "config-db-name-help": "위키를 식별하기 위한 이름을 선택하세요.\n공백이 없어야 합니다.\n\n공유하는 웹 호스팅 사용하면 호스팅 제공 업체가 특정 데이터베이스 이름을 제공하거나 제어판에서 데이터베이스를 만들 수 있습니다.",
+ "config-db-name-oracle": "데이터베이스 스키마:",
+ "config-db-account-oracle-warn": "데이터베이스 백엔드로 오라클을 설치하기 위해 지원하는 세 가지 시나리오가 있습니다:\n\n설치 과정의 일부로 데이터베이스 계정을 만들려면 설치를 위해 데이터베이스 계정으로 SYSDBA 역할을 가진 계정을 제공하고 웹 접근 계정에 대해 원하는 자격 증명을 지정하세요, 그렇지 않으면 수동으로 웹 접근 계정을 만들 수 있으며 (스키마 개체를 만들 권한이 필요한 경우) 또는 생성 권한으 가진 계정과 웹 접근이 제한된 계정의 두 가지 다른 계정을 제공할 수도 있습니다\n\n필요한 권한을 가진 계정을 만드는 스크립트는 이 설치 위치의 \"maintenance/oracle/\" 디렉터리에서 찾을 수 있습니다. 제한된 계정을 사용하면 기본 계정의 모든 유지 관리 기능이 비활성화된다는 점에 유의하십시오.",
+ "config-db-install-account": "설치를 위한 사용자 계정",
+ "config-db-username": "데이터베이스 사용자 이름:",
+ "config-db-password": "데이터베이스 비밀번호:",
+ "config-db-password-empty": "새 데이터베이스 사용자의 비밀번호를 입력하세요: $1.\n비밀번호 없이 사용자를 만들 수도 있지만 안전하지 않습니다.",
+ "config-db-username-empty": "\"{{int:config-db-username}}\"에 대한 값을 입력해야 합니다.",
+ "config-db-install-username": "설치 과정 도중 데이터베이스에 연결할 때 사용할 사용자 이름을 입력하세요.\n미디어위키 계정의 사용자 이름이 아닌 데이터베이스의 사용자 이름입니다.",
+ "config-db-install-password": "설치 과정 도중 데이터베이스에 연결할 때 사용할 비밀번호을 입력하세요.\n미디어위키 계정의 비밀번호가 아닌 데이터베이스의 비밀번호입니다.",
+ "config-db-install-help": "설치 과정 중에 데이터베이스에 연결할 때 사용할 사용자 이름과 비밀번호를 입력하세요.",
+ "config-db-account-lock": "정상적으로 작동하는 동안 같은 사용자 이름과 비밀번호를 사용함",
+ "config-db-wiki-account": "정상적인 작동을 위한 사용자 계정",
+ "config-db-wiki-help": "정상적인 위키 작업 동안 데이터베이스에 연결하는 데 사용할 사용자 이름과 비밀번호를 입력하세요.\n계정이 존재하지 않고 설치 계정에 충분한 권한이 있는 경우 이 사용자 계정은 위키를 작동하는 데 필요한 최소 권한으로 만들어집니다.",
+ "config-db-prefix": "데이터베이스 테이블 접두어:",
+ "config-db-prefix-help": "여러 위키 사이 또는 미디어위키와 다른 웹 애플리케이션 사이에 하나의 데이터베이스를 공유해야 하는 경우, 충돌을 피하기 위해 모든 테이블 이름에 접두어를 추가하도록 선택할 수 있습니다.\n공백을 사용하지 마세요.\n\n이 필드는 일반적으로 비어 있습니다.",
+ "config-db-charset": "데이터베이스 문자 집합",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 바이너리",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 UTF-8 하위 호환성",
+ "config-charset-help": "'''경고:''' MySQL 4.1에서 '''하위 호환 가능 UTF-8'''을 사용하고 나서 <code>mysqldump</code>로 데이터베이스를 백업하면 ASCII가 아닌 모든 문자를 파괴하고 손상된 백업을 되돌릴 수 없습니다!\n\n'''바이너리 모드'''에서 미디어위키는 바이너리 필드의 데이터베이스에 UTF-8 텍스트를 저장합니다.\nMySQL의 UTF-8 모드를 보다 더 효율적이고 유니코드 문자의 전체 범위를 사용할 수 있습니다.\n'''UTF-8 모드'''에서는 MySQL이 데이터를 사용하는 문자 집합을 알고 있기 때문에 적절하게 표현하고 변환할 수 있지만\n[//ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_%ED%8F%89%EB%A9%B4#.EA.B8.B0.EB.B3.B8_.EB.8B.A4.EA.B5.AD.EC.96.B4_.ED.8F.89.EB.A9.B4 기본 다국어 평면] 밖에 있는 문자를 저장할 수 없습니다.",
+ "config-mysql-old": "MySQL $1 이상이 필요하나 $2(이)가 있습니다.",
+ "config-db-port": "데이터베이스 포트:",
+ "config-db-schema": "미디어위키에 대한 스키마:",
+ "config-db-schema-help": "보통 이 스키마는 문제가 없습니다.\n필요한 경우에만 바꾸세요.",
+ "config-pg-test-error": "'''$1''' 데이터베이스에 연결할 수 없습니다: $2",
+ "config-sqlite-dir": "SQLite 데이터 디렉터리:",
+ "config-sqlite-dir-help": "SQLite는 하나의 파일에 모든 데이터를 저장합니다.\n\n입력한 디렉토리는 설치하는 동안 웹 서버가 쓸 수 있어야 합니다.\n\n이 디렉토리는 웹을 통해 접근할 수 '''없어야''' 하는데, PHP 파일이 있는 곳에 넣을 수 없는 것은 이 때문입니다.\n\n설치 프로그램은 <code>.htaccess</code> 파일을 작성하지만, 이것이 실패하면 누군가가 원본 데이터베이스에 접근할 수 있습니다.\n데이터베이스는 원본 사용자 데이터(이메일 주소, 해시한 비밀번호)뿐만 아니라 삭제된 판과 위키의 다른 제한된 데이터를 포함합니다.\n\n예를 들어 <code>/var/lib/mediawiki/yourwiki</code>와 같이 다른 곳에 데이터베이스를 넣는 것이 좋습니다.",
+ "config-oracle-def-ts": "기본 테이블공간:",
+ "config-oracle-temp-ts": "임시 테이블공간:",
+ "config-type-mysql": "MySQL (또는 호환되는 데이터베이스 시스템)",
+ "config-type-oracle": "Oracle",
+ "config-type-mssql": "마이크로소프트 SQL 서버",
+ "config-support-info": "미디어위키는 다음의 데이터베이스 시스템을 지원합니다:\n\n$1\n\n데이터베이스 시스템이 표시되지 않을 때 아래에 나열된 다음 지원을 활성화하려면 위의 링크된 지시에 따라 설치해볼 수 있습니다.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL]은 미디어위키의 기본 대상이며 가장 잘 지원됩니다. 미디어위키는 또한 MySQL와 호환되는 [{{int:version-db-mariadb-url}} MariaDB]와 [{{int:version-db-percona-url}} Percona 서버]에서도 작동합니다. \n\n([http://www.php.net/manual/en/mysql.installation.php MySQL을 지원하여 PHP를 컴파일하는 방법])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL]은 MySQL의 대안으로서 인기 있는 오픈 소스 데이터베이스 시스템입니다. ([http://www.php.net/manual/en/pgsql.installation.php PostgreSQL을 지원하여 PHP를 컴파일하는 방법]) 몇 가지 해결하지 못한 사소한 버그가 있을 수 있으며, 이를 제작 환경에서 사용하지 않는 것이 좋습니다.",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]는 매우 잘 지원되고 가벼운 데이터베이스 시스템입니다. ([http://www.php.net/manual/en/pdo.installation.php SQLite를 지원하여 PHP를 컴파일하는 방법], PDO 사용)",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} 오라클]은 상용 엔터프라이스 데이터베이스입니다. ([http://www.php.net/manual/en/oci8.installation.php OCI8을 지원하여 PHP를 컴파일하는 방법])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} 마이크로소프트 SQL 서버]는 윈도용 상용 기업 데이터베이스입니다.([http://www.php.net/manual/en/sqlsrv.installation.php SQLSRV 지원으로 PHP를 컴파일하는 방법])",
+ "config-header-mysql": "MySQL 설정",
+ "config-header-postgres": "PostgreSQL 설정",
+ "config-header-sqlite": "SQLite 설정",
+ "config-header-oracle": "Oracle 설정",
+ "config-header-mssql": "마이크로소프트 SQL 서버 설정",
+ "config-invalid-db-type": "잘못된 데이터베이스 종류",
+ "config-missing-db-name": "\"{{int:config-db-name}}\"에 대한 값을 입력해야 합니다.",
+ "config-missing-db-host": "\"{{int:config-db-host}}\"에 대한 값을 입력해야 합니다.",
+ "config-missing-db-server-oracle": "\"{{int:config-db-host-oracle}}\"에 대한 값을 입력해야 합니다.",
+ "config-invalid-db-server-oracle": "\"$1\" 데이터베이스 TNS가 잘못됐습니다.\n\"TNS Name\"이나 \"Easy Connect\" 문자열 중 하나를 사용하세요 ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm 오라클 네이밍 메서드])",
+ "config-invalid-db-name": "\"$1\" 데이터베이스 이름이 잘못되었습니다.\nASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하세요.",
+ "config-invalid-db-prefix": "\"$1\" 데이터베이스 접두어가 잘못됐습니다.\nASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하세요.",
+ "config-connection-error": "$1.\n\n호스트, 계정 이름과 비밀번호를 확인하고 다시 시도하세요.",
+ "config-invalid-schema": "미디어위키 \"$1\"에 대한 스키마가 잘못됐습니다.\nASCII 글자 (a-z, A-Z), 숫자 (0-9), 밑줄 (_)과 하이픈 (-)만 사용하세요.",
+ "config-db-sys-create-oracle": "설치 프로그램은 새 계정을 만들기 위한 SYSDBA 계정만을 지원합니다.",
+ "config-db-sys-user-exists-oracle": "\"$1\" 사용자 계정이 이미 존재합니다. SYSDBA는 새 계정을 만드는 데에만 사용할 수 있습니다!",
+ "config-postgres-old": "PostgreSQL $1 이상이 필요하나 $2(이)가 있습니다.",
+ "config-mssql-old": "마이크로소프트 SQL 서버 $1 이상의 버전이 필요합니다. 현재 버전은 $2입니다.",
+ "config-sqlite-name-help": "위키를 식별하기 위한 이름을 선택하세요.\n공백이나 하이픈을 사용하지 마십시오.\nSQLite 데이터 파일 이름에 사용됩니다.",
+ "config-sqlite-parent-unwritable-group": "<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며, 이는 웹 서버는 상위 디렉토리인 <code><nowiki>$2</nowiki></code>에 쓸 수 없기 때문입니다.\n\n설치 프로그램은 웹 서버로 실행 중인 사용자를 지정할 수 없습니다.\n계속하려면 웹 서버가 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.\n유닉스/리눅스 시스템에서의 수행:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "<code><nowiki>$1</nowiki></code> 데이터 디렉토리를 만들 수 없으며, 이는 웹 서버가 상위 디렉토리인 <code><nowiki>$2</nowiki></code>에 쓸 수 없기 때문입니다.\n\n설치 프로그램은 웹 서버로 실행 중인 사용자를 지정할 수 없습니다.\n계속하려면 웹 서버(와 그 외 서버!)가 전역으로 쓸 수 있는 <code><nowiki>$3</nowiki></code> 디렉토리를 만드세요.\n유닉스/리눅스 시스템에서의 수행:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "\"$1\" 데이터 디렉터리를 만드는 도중 오류가 발생했습니다.\n경로를 확인하고 다시 시도하세요.",
+ "config-sqlite-dir-unwritable": "\"$1\" 디렉토리에 쓸 수 없습니다.\n웹 서버를 쓸 수 있도록 권한을 바꾸고 다시 시도하세요.",
+ "config-sqlite-connection-error": "$1.\n\n호스트, 계정 이름과 비밀번호를 확인하고 다시 시도하세요.",
+ "config-sqlite-readonly": "<code>$1</code> 파일은 쓸 수 없습니다.",
+ "config-sqlite-cant-create-db": "<code>$1</code> 데이터베이스 파일을 만들 수 없습니다.",
+ "config-sqlite-fts3-downgrade": "PHP가 FTS3 지원이 없어졌습니다. 테이블을 다운그레이드합니다",
+ "config-can-upgrade": "이 데이터베이스에 미디어위키 테이블이 있습니다.\n미디어위키 $1(으)로 업그레이드하려면 '''계속'''을 클릭하세요.",
+ "config-upgrade-done": "업그레이드가 완료되었습니다.\n\n이제 [$1 위키를 시작]할 수 있습니다.\n\n만약 <code>LocalSettings.php</code> 파일을 다시 만들고 싶다면 아래의 버튼을 클릭하세요.\n위키에 문제가 있지 않는 한 '''권장하지 않습니다'''.",
+ "config-upgrade-done-no-regenerate": "업그레이드가 완료되었습니다.\n\n이제 [$1 위키를 시작]할 수 있습니다.",
+ "config-regenerate": "LocalSettings.php 다시 생성 →",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code> 쿼리를 실패했습니다!",
+ "config-unknown-collation": "'''경고:''' 데이터베이스가 인식하지 않는 정렬을 사용하고 있습니다.",
+ "config-db-web-account": "웹 접근을 위한 데이터베이스 계정",
+ "config-db-web-help": "위키의 일반적인 작업을 수행하는 동안 데이터베이스 서버에 연결하는 데 사용할 웹 서버의 계정 이름과 비밀번호를 선택하세요.",
+ "config-db-web-account-same": "설치를 위해 같은 계정 사용",
+ "config-db-web-create": "이 계정이 아직 존재하지 않을 경우 계정 만들기",
+ "config-db-web-no-create-privs": "설치를 위해 지정한 계정이 계정을 만들 수 있는 충분한 권한이 없습니다.\n여기서 지정한 계정은 이미 존재해야 합니다.",
+ "config-mysql-engine": "저장소 엔진:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''경고''': MySQL을 위한 저장소 엔진으로 MyISAM을 선택하였습니다. MyISAM을 미디어위키에 사용하는 것은 좋지 않습니다. 이유는:\n* 테이블 잠금 때문에 동시 실행을 지원하지 않습니다\n* 다른 엔진보다 더 손상되는 경향이 있습니다\n* 미디어위키 코드베이스가 항상 정상적으로 MyISAM을 처리하지 않습니다\n\nMySQL이 InnoDB를 지원한다면, InnoDB를 선택할 것을 매우 권장합니다.\nMySQL이 InnoDB를 지원하지 않는다면, 업그레이드를 하시는 편이 좋습니다.",
+ "config-mysql-only-myisam-dep": "'''경고''': MyISAM은 이 기계에 유일하게 사용할 수 있는 MySQL용 저장소 엔진이며, 미디어위키에 사용하는 것은 좋지 않습니다. 이유는:\n* 테이블 잠금 때문에 동시 실행을 지원하지 않습니다\n* 다른 엔진보다 더 손상시키는 경향이 있습니다\n* 미디어위키 코드베이스가 항상 정상적으로 MyISAM을 처리하지 않습니다\n\n당신의 MySQL은 InnoDB를 지원하지 않으며, 업그레이드를 하는 것이 좋습니다.",
+ "config-mysql-engine-help": "'''InnoDB'''는 동시 실행 지원이 우수하기 때문에 대부분의 경우 최고의 옵션입니다.\n\n'''MyISAM'''은 단일 사용자나 읽기 전용 설치에서 더 빠를 수 있습니다.\nMyISAM 데이터베이스는 InnoDB 데이터베이스보다 더 자주 손실될 수 있습니다.",
+ "config-mysql-charset": "데이터베이스 문자 집합:",
+ "config-mysql-binary": "바이너리",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "'''바이너리 모드'''에서 미디어위키는 바이너리 필드의 데이터베이스에 UTF-8 텍스트를 저장합니다.\nMySQL의 UTF-8 모드보다 더 효율적이고, 유니코드 문자의 전체 범위를 사용할 수 있습니다.\n'''UTF-8 모드'''에서는 MySQL이 데이터를 사용하는 문자 집합을 알고 있기 때문에 적절하게 표현하고 변환할 수 있지만\n[//ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_%ED%8F%89%EB%A9%B4#.EA.B8.B0.EB.B3.B8_.EB.8B.A4.EA.B5.AD.EC.96.B4_.ED.8F.89.EB.A9.B4 기본 다국어 평면] 밖에 있는 문자를 저장할 수 없습니다.",
+ "config-mssql-auth": "인증 형식:",
+ "config-mssql-install-auth": "설치 과정 중 데이터베이스에 연결하는 데 사용할 인증 형식을 선택하세요.\n\"{{int:config-mssql-windowsauth}}\"을 선택하시면 웹서버를 실행 중인 아무 사용자의 자격 증명이 사용됩니다.",
+ "config-mssql-web-auth": "위키가 일반적인 작업을 수행하는 동안 데이터베이스 서버에 연결하는 데 사용할 인증 형식을 선택하세요.\n\n\"{{int:config-mssql-windowsauth}}\"을 선택하시면 웹서버를 실행 중인 아무 사용자의 자격 증명이 사용됩니다.",
+ "config-mssql-sqlauth": "SQL 서버 인증",
+ "config-mssql-windowsauth": "Windows 인증",
+ "config-site-name": "위키 이름:",
+ "config-site-name-help": "브라우저 제목 표시줄과 다른 여러 곳에 나타납니다.",
+ "config-site-name-blank": "사이트 이름을 입력하세요.",
+ "config-project-namespace": "프로젝트 이름공간:",
+ "config-ns-generic": "프로젝트",
+ "config-ns-site-name": "위키 이름과 같은 이름: $1",
+ "config-ns-other": "기타 (지정)",
+ "config-ns-other-default": "내위키",
+ "config-project-namespace-help": "위키백과의 예에 따르면, 많은 위키는 정책 문서를 일반 문서와는 별도로 \"'''프로젝트 이름공간'''\"에 보관합니다.\n이 이름공간에 있는 모든 문서의 제목은 여기서 지정할 수 있는 특정 접두어로 시작합니다.\n보통 이 접두어는 위키의 이름에서 파생되지만, \"#\" 또는 \":\"와 같은 특수 문자를 포함할 수 없습니다.",
+ "config-ns-invalid": "특정 \"<nowiki>$1</nowiki>\" 이름공간이 잘못되었습니다.\n다른 프로젝트 이름공간을 지정하세요.",
+ "config-ns-conflict": "특정 \"<nowiki>$1</nowiki>\" 이름공간이 기본 미디어위키 이름공간과 충돌합니다.\n다른 프로젝트 이름공간을 지정하세요.",
+ "config-admin-box": "관리자 계정",
+ "config-admin-name": "내 사용자 이름:",
+ "config-admin-password": "비밀번호:",
+ "config-admin-password-confirm": "비밀번호 확인:",
+ "config-admin-help": "\"홍길동\"과 같이 여기에 원하는 사용자 이름을 입력하세요.\n위키에 로그인하는 데 사용되는 이름입니다.",
+ "config-admin-name-blank": "관리자의 사용자 이름을 입력하세요.",
+ "config-admin-name-invalid": "특정 \"<nowiki>$1</nowiki>\" 사용자 이름이 잘못되었습니다.\n다른 사용자 이름을 지정하세요.",
+ "config-admin-password-blank": "관리자 계정의 비밀번호를 입력하세요.",
+ "config-admin-password-mismatch": "입력한 비밀번호 두 개가 일치하지 않습니다.",
+ "config-admin-email": "이메일 주소:",
+ "config-admin-email-help": "여기에 이메일 주소를 입력하여 위키의 다른 사용자로부터 이메일을 전달받거나 비밀번호를 재설정하고 주시문서 목록에 대한 바뀜 알림을 받으세요. 이 필드를 비워 둘 수 있습니다.",
+ "config-admin-error-user": "\"<nowiki>$1</nowiki>\" 이름의 관리자를 만드는 중 내부 오류가 발생했습니다.",
+ "config-admin-error-password": "\"<nowiki>$1</nowiki>\" 관리자의 비밀번호를 설정하는 중 내부 오류가 발생했습니다: <pre>$2</pre>",
+ "config-admin-error-bademail": "이메일 주소를 잘못 입력하였습니다.",
+ "config-subscribe": "[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 릴리스 발표 메일링 리스트]를 구독합니다.",
+ "config-subscribe-help": "중요한 보안 알림을 포함한 릴리스 알림에 사용되는 저용량 메일링 리스트입니다.\n이 리스트를 구독하고 새 버전이 나올 때 미디어위키 설치를 업데이트해야 합니다.",
+ "config-subscribe-noemail": "이메일 주소를 입력하지 않고 릴리스 발표 메일링 리스트에 가입하려 합니다.\n메일링 리스트에 가입하고자 할 경우 이메일 주소를 입력하세요.",
+ "config-almost-done": "거의 다 완료했습니다!\n이제 남은 설정을 생략하고 지금 바로 위키를 설치할 수 있습니다.",
+ "config-optional-continue": "더 많은 질문을 물어보세요.",
+ "config-optional-skip": "지겨워요, 그냥 위키를 설치할래요.",
+ "config-profile": "사용자 권한 프로필:",
+ "config-profile-wiki": "열린 위키",
+ "config-profile-no-anon": "계정 만들기 필요",
+ "config-profile-fishbowl": "승인된 편집자만",
+ "config-profile-private": "비공개 위키",
+ "config-profile-help": "위키는 가능한 많은 사람들이 편집할 수 있도록 할 때 가장 뛰어난 역할을 합니다.\n미디어위키에서는 최근 바뀜을 검토하기 쉽고, 미숙하거나 악의적인 사용자의 어떠한 손실을 되돌리는 것이 쉽습니다.\n\n그러나 많은 사람이 미디어위키가 다양한 역할을 수행하는 데 유용하다는 것을 알고 있지만, 때로는 모든 사람에게 위키 방식의 장점을 설득하기 쉽지 않을 지도 모릅니다.\n그래서 선택할 수 있습니다.\n\n'''{{int:config-profile-wiki}}''' 모델은 로그인하지 않고도 누구나 편집할 수 있습니다.\n'''{{int:config-profile-no-anon}}'''인 위키에서는 편집자에게 추가적인 책임을 부여하지만, 부담 없는 기여를 저해할 수도 있습니다.\n\n'''{{int:config-profile-fishbowl}}''' 시나리오에서는 승인된 사용자만 편집할 수 있지만, 일반 사용자도 문서(문서 역사 포함)는 볼 수 있습니다.\n'''{{int:config-profile-private}}'''는 승인된 사용자만 문서를 볼 수 있으며, 승인된 사용자 그룹이 편집할 수 있습니다.\n\n더 복잡한 사용자 권한 설정은 설치한 후 사용할 수 있으며 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights 관련 설명서 항목]을 참고하세요.",
+ "config-license": "저작권 및 라이선스:",
+ "config-license-none": "라이선스 바닥글 없음",
+ "config-license-cc-by-sa": "크리에이티브 커먼즈 저작자표시-동일조건변경허락",
+ "config-license-cc-by": "크리에이티브 커먼즈 저작자표시",
+ "config-license-cc-by-nc-sa": "크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락",
+ "config-license-cc-0": "크리에이티브 커먼즈 제로 (퍼블릭 도메인)",
+ "config-license-gfdl": "GNU 자유 문서 사용 허가서 1.3 이상",
+ "config-license-pd": "퍼블릭 도메인",
+ "config-license-cc-choose": "다른 크리에이티브 커먼즈 라이선스 선택",
+ "config-license-help": "많은 공개 위키는 모든 기여를 [http://freedomdefined.org/Definition 자유 라이선스]에 따르도록 합니다.\n이렇게 하면 커뮤니티에 대한 소유권을 이해할 수 있도록 하고 장기적인 기여를 장려합니다.\n일반적으로 개인 또는 회사 위키에게는 필요하지 않습니다.\n\n위키백과의 텍스트를 사용할 수 있도록 하고 위키백과가 위키에서 복사한 텍스트를 사용할 수 있도록 원한다면 <strong>{{int:config-license-cc-by-sa}}</strong>으로 선택해야 합니다.\n\n위키백과는 이전에 GNU 자유 문서 사용 허가서(GFDL)를 사용했습니다.\nGFDL은 유효한 라이선스이지만 내용을 이해하기 어렵습니다.\nGFDL에 따라 사용이 허가된 내용을 재사용하는 것도 어렵습니다.",
+ "config-email-settings": "이메일 설정",
+ "config-enable-email": "발신 이메일 활성화",
+ "config-enable-email-help": "이메일을 작동하려면 [http://www.php.net/manual/en/mail.configuration.php PHP의 메일 설정]을 올바르게 설정해야 합니다.\n이메일 기능을 사용하지 않으려면 이를 비활성화할 수 있습니다.",
+ "config-email-user": "사용자와 사용자 간 이메일 활성화",
+ "config-email-user-help": "환경 설정에서 활성화한 경우 모든 사용자가 이메일을 서로 보내도록 활성화합니다.",
+ "config-email-usertalk": "사용자 토론 문서 알림 활성화",
+ "config-email-usertalk-help": "환경 설정에서 활성화한 경우 사용자는 사용자 토론 문서의 바뀜 알림을 받도록 활성화합니다.",
+ "config-email-watchlist": "주시문서 목록 알림 활성화",
+ "config-email-watchlist-help": "환경 설정에서 활성화한 경우 사용자가 주시한 문서에 대한 알림을 받도록 활성화합니다.",
+ "config-email-auth": "이메일 인증 활성화",
+ "config-email-auth-help": "이 설정이 활성화되어 있으면 사용자는 이메일 주소를 설정하거나 바꿀 때마다 링크를 사용하여 이메일 주소를 확인해야 합니다.\n인증된 이메일 주소만 다른 사용자로부터의 이메일이나 바뀜 알림 이메일을 받을 수 있습니다.\n이메일 기능의 남용 가능성이 있기 때문에 공개 위키에서는 이 옵션을 설정할 것을 '''권장'''합니다.",
+ "config-email-sender": "반송 이메일 주소",
+ "config-email-sender-help": "발신한 이메일에 대한 반송 주소로 사용할 이메일 주소를 입력하세요.\n반송할 때 보내는 주소입니다.\n대부분의 메일 서버는 적어도 도메인 이름 부분은 유효합니다.",
+ "config-upload-settings": "그림과 파일 올리기",
+ "config-upload-enable": "파일 올리기 활성화",
+ "config-upload-help": "파일 올리기는 서버에 잠재적인 보안 위험에 쉽게 노출될 수 있습니다.\n자세한 내용은 매뉴얼의 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security 보안 문단]을 참고하세요.\n\n파일 올리기를 활성화하려면 미디어위키의 루트 디렉토리에 있는 <code>images</code> 하위 디렉토리에서 웹 서버가 기록할 수 있도록 모드를 바꿉니다.\n그 다음 이 옵션을 활성화합니다.",
+ "config-upload-deleted": "삭제된 파일에 대한 디렉터리:",
+ "config-upload-deleted-help": "삭제된 파일을 보관할 디렉토리를 선택하세요.\n이상적으로 웹에서 접근할 수 없게 해야 합니다.",
+ "config-logo": "로고 URL:",
+ "config-logo-help": "미디어위키의 기본 스킨은 사이드바 메뉴 위에 135×160 픽셀의 로고의 공간을 포함하고 있습니다.\n적당한 크기로 그림을 올리고 여기에 URL을 입력하세요.\n\n로고가 상대적인 경로에 있으면 <code>$wgStylePath</code>나 <code>$wgScriptPath</code>를 사용할 수 있습니다.\n\n로고 사용을 원하지 않으면 이 상자를 비우세요.",
+ "config-instantcommons": "인스턴트 공용 기능 활성화",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons 인스턴트 공용]은 [//commons.wikimedia.org/ 위키미디어 공용] 사이트에서 찾을 수 있는 그림, 소리 및 다른 미디어를 위키에서 사용할 수 있도록 하는 기능입니다.\n이렇게 하려면 미디어위키가 인터넷에 접근해야합니다.\n\n위키미디어 공용 외에 기타 위키를 설정하는 방법에 대한 지침을 포함한, 기능에 대한 자세한 내용은 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos 매뉴얼]을 참고하세요.",
+ "config-cc-error": "크리에이티브 커먼즈 라이선스 선택기에 결과가 없습니다.\n수동으로 라이선스 이름을 입력하세요.",
+ "config-cc-again": "다시 선택...",
+ "config-cc-not-chosen": "원하는 크리에이티브 커먼즈 라이선스를 선택하고 \"진행\"을 클릭하세요.",
+ "config-advanced-settings": "고급 설정",
+ "config-cache-options": "개체 캐싱을 위한 설정:",
+ "config-cache-help": "개체 캐싱은 자주 사용하는 데이터를 캐싱하여 미디어위키의 속도를 개선하는 데 사용합니다.\n큰 규모의 사이트는 이를 많이 사용하도록 권장하고 있으며, 소규모 사이트들도 물론 혜택을 볼 수 있습니다.",
+ "config-cache-none": "캐시하지 않음 (기능이 삭제되지는 않지만 큰 위키 사이트에 속도가 영향을 받을 수 있습니다)",
+ "config-cache-accel": "PHP 개체 캐싱 (APC, XCache 또는 WinCache)",
+ "config-cache-memcached": "Memcached 사용 (추가적인 설치와 설정이 필요합니다)",
+ "config-memcached-servers": "Memcached 서버:",
+ "config-memcached-help": "Memcached의 사용하기 위한 IP 주소 목록입니다.\n한 줄에 하나씩 사용할 포트를 지정해야 합니다. 예를 들어:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "캐시 종류로 Memcached를 선택했지만 어떠한 서버도 지정하지 않았습니다.",
+ "config-memcache-badip": "Memcached에 대해 잘못된 IP 주소를 입력했습니다: $1.",
+ "config-memcache-noport": "Memcached 서버에 사용할 포트를 지정하지 않았습니다: $1.\n포트를 모를 경우 기본 값은 11211입니다.",
+ "config-memcache-badport": "Memcached 포트 번호는 $1(와)과 $2 사이여야 합니다.",
+ "config-extensions": "확장 기능",
+ "config-extensions-help": "위에 나열된 확장 기능이 <code>./extensions</code>에서 발견되었습니다.\n\n추가적인 설정이 필요할 수 있습니다만 지금 활성화시킬 수 있습니다.",
+ "config-skins": "스킨",
+ "config-skins-use-as-default": "이 스킨을 기본값으로 사용",
+ "config-skins-must-enable-some": "적어도 활성화활 스킨 하나를 선택해야 합니다.",
+ "config-skins-must-enable-default": "기본값으로 설정한 스킨은 반드시 활성화해야 합니다.",
+ "config-install-alreadydone": "'''경고:''' 이미 미디어위키를 설치했고 다시 설치하려고 합니다.\n다음 페이지로 진행하세요.",
+ "config-install-begin": "\"{{int:config-continue}}\"을 누르면 미디어위키의 설치를 시작합니다.\n그래도 바꾸는 것을 원한다면 \"{{int:config-back}}\"를 누르세요.",
+ "config-install-step-done": "완료",
+ "config-install-step-failed": "실패",
+ "config-install-extensions": "확장 기능을 포함하는 중",
+ "config-install-database": "데이터베이스를 설정하는 중",
+ "config-install-schema": "스키마를 만드는 중",
+ "config-install-pg-schema-not-exist": "PostgreSQL 스키마가 존재하지 않습니다.",
+ "config-install-pg-schema-failed": "테이블을 만드는 데 실패했습니다.\n\"$2\" 스키마에 쓸 수 있는 \"$1\" 사용자가 있는지 확인하세요.",
+ "config-install-pg-commit": "바뀐 사항을 적용하는 중",
+ "config-install-pg-plpgsql": "PL/pgSQL 언어에 대해 확인하는 중",
+ "config-pg-no-plpgsql": "$1 데이터베이스에 PL/pgSQL 언어를 설치해야 합니다",
+ "config-pg-no-create-privs": "설치를 위한 지정한 계정에 계정을 만드는 데 충분한 권한이 없습니다,",
+ "config-pg-not-in-role": "웹 사용자로 지정한 계정이 이미 존재합니다.\n설치를 위해 지정한 사용자는 슈퍼 사용자와 웹 사용자의 역할 구성원이 아니므로 웹 사용자가 소유한 개체를 만들 수 없습니다.\n\n현재 미디어위키는 테이블을 웹 사용자가 소유해야 합니다. 다른 웹 계정 이름을 지정하거나 \"뒤로\"를 클릭하고 적절한 권한의 설치할 사용자를 지정하세요.",
+ "config-install-user": "데이터베이스 사용자를 만드는 중",
+ "config-install-user-alreadyexists": "\"$1\" 사용자가 이미 존재합니다",
+ "config-install-user-create-failed": "\"$1\" 사용자 만드는 데 실패: $2",
+ "config-install-user-grant-failed": "\"$1\" 사용자에 대한 권한 부여 실패: $2",
+ "config-install-user-missing": "지정한 \"$1\" 사용자가 존재하지 않습니다.",
+ "config-install-user-missing-create": "지정된 \"$1\" 사용자가 존재하지 않습니다.\n사용자를 만드려면 아래의 \"계정 만들기\" 확인 상자를 클릭하세요.",
+ "config-install-tables": "테이블을 만드는 중",
+ "config-install-tables-exist": "'''경고''': 미디어위키 테이블이 이미 있는 것 같습니다.\n테이블 만들기를 생략합니다.",
+ "config-install-tables-failed": "'''오류''': 다음 오류로 인해 테이블 만들기에 실패했습니다: $1",
+ "config-install-interwiki": "기본 인터위키 테이블을 채우는 중",
+ "config-install-interwiki-list": "<code>interwiki.list</code> 파일을 불러올 수 없습니다.",
+ "config-install-interwiki-exists": "'''경고''': 인터위키 테이블이 이미 항목을 갖고 있는 것 같습니다.\n기본 목록을 건너뜁니다.",
+ "config-install-stats": "통계를 초기화하는 중",
+ "config-install-keys": "보안 키를 만드는 중",
+ "config-insecure-keys": "'''경고:''' 설치 중에 생성한 {{PLURAL:$2|보안 키}} ($1)는 완전히 안전하지 {{PLURAL:$2|않습니다}}. 직접 바꾸는 것을 고려하세요.",
+ "config-install-sysop": "관리자 사용자 계정을 만드는 중",
+ "config-install-subscribe-fail": "미디어위키 알림을 구독할 수 없습니다: $1",
+ "config-install-subscribe-notpossible": "cURL이 설치되어 있지 않고 <code>allow_url_fopen</code>을 사용할 수 없습니다.",
+ "config-install-mainpage": "기본 내용으로 대문을 만드는 중",
+ "config-install-extension-tables": "활성화된 확장 기능을 위한 테이블을 만드는 중",
+ "config-install-mainpage-failed": "대문을 삽입할 수 없습니다: $1",
+ "config-install-done": "'''축하합니다!'''\n미디어위키가 성공적으로 설치되었습니다.\n\n설치 프로그램이 <code>LocalSettings.php</code> 파일을 만들었습니다.\n모든 설정이 포함되어 있습니다.\n\n파일을 다운로드하여 위키 설치의 거점에 넣어야 합니다. (index.php와 같은 디렉터리) 다운로드가 자동으로 시작됩니다.\n\n다운로드가 제공되지 않을 경우나 그것을 취소한 경우에는 아래의 링크를 클릭하여 다운로드를 다시 시작할 수 있습니다:\n\n$3\n\n'''참고:''' 이 생성한 설정 파일을 다운로드하지 않고 설치를 끝내면 이 파일은 나중에 사용할 수 없습니다.\n\n완료되었으면 '''[$2 위키에 들어갈 수 있습니다]'''.",
+ "config-download-localsettings": "<code>LocalSettings.php</code> 다운로드",
+ "config-help": "도움말",
+ "config-help-tooltip": "확장하려면 클릭",
+ "config-nofile": "\"$1\" 파일을 찾을 수 없습니다. 이미 삭제되었나요?",
+ "config-extension-link": "당신의 위키가 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions 확장 기능]을 지원한다는 것을 알고 계십니까?\n\n[//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category 분류별 확장 기능]을 찾아보실 수 있습니다.",
+ "mainpagetext": "'''미디어위키가 성공적으로 설치되었습니다.'''",
+ "mainpagedocfooter": "[//meta.wikimedia.org/wiki/Help:Contents 이곳]에서 위키 소프트웨어에 대한 정보를 얻을 수 있습니다.\n\n== 시작하기 ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 설정하기 목록]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ 미디어위키 FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 미디어위키 릴리스 메일링 리스트]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 내 언어로 미디어위키 지역화]"
+}
diff --git a/includes/installer/i18n/krc.json b/includes/installer/i18n/krc.json
new file mode 100644
index 00000000..d1d7d4c6
--- /dev/null
+++ b/includes/installer/i18n/krc.json
@@ -0,0 +1,29 @@
+{
+ "@metadata": {
+ "authors": [
+ "Iltever"
+ ]
+ },
+ "config-desc": "MediaWiki инсталлятор",
+ "config-title": "MediaWiki $1 инсталляциясы",
+ "config-information": "Информация",
+ "config-localsettings-key": "Джангыртыу ачхыч:",
+ "config-session-error": "Сессияны башланыу халат: $1",
+ "config-your-language": "Тилигиз:",
+ "config-wiki-language": "Викини тили:",
+ "config-back": "← Артха",
+ "config-continue": "Баргъаны →",
+ "config-page-language": "Тил",
+ "config-db-host-oracle": "Билгиле базаны TNS'и:",
+ "config-db-wiki-settings": "Бу Викини идентификациясы",
+ "config-db-name": "Билгиле базаны аты:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "Экили",
+ "config-mysql-utf8": "UTF-8",
+ "config-ns-generic": "Проект",
+ "config-ns-other-default": "MyWiki",
+ "config-profile-private": "Джабыкъ вики",
+ "mainpagetext": "'''«MediaWiki» тыйыншлы салынды.'''",
+ "mainpagedocfooter": "Бу вики бла къалай ишлерге ангылатхан информацияны [//meta.wikimedia.org/wiki/Help:Contents_User's_Guide къошулуучугъа юретиуде] табаргъа боллукъду.\n\n== Файдалы ресурсла ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings тюрлендириулени списогу (ингил.)];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-ни юсюнден кёб берилген соруула];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-ни джангы версиясыны чыкъгъанын билдириу письмола]."
+}
diff --git a/includes/installer/i18n/ksh.json b/includes/installer/i18n/ksh.json
new file mode 100644
index 00000000..785b2b5e
--- /dev/null
+++ b/includes/installer/i18n/ksh.json
@@ -0,0 +1,319 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mormegil",
+ "Purodha",
+ "Reedy"
+ ]
+ },
+ "config-desc": "Et Projramm för Mediwiki opzesäze.",
+ "config-title": "MediaWiki $1 opsäze",
+ "config-information": "Enfomazjuhn",
+ "config-localsettings-upgrade": "De Dattei <code lang=\"en\"><code>LocalSettings.php</code></code> es ald doh.\nDe Projramme vum Wiki künne op der neußte Shtand jebraat wääde:\nDonn doför dä Wäät vum <code lang=\"en\">$wgUpgradeKey</code> en dat heh Feld enjävve.\nDo fenggs_et en dä Dattei <code lang=\"en\"><code>LocalSettings.php</code></code> om ẞööver.",
+ "config-localsettings-cli-upgrade": "En Dattei <code lang=\"en\"><code>LocalSettings.php</code></code> es jefonge woode.\nÖm et Wiki_Projramm op ene neue Shtand ze bränge, donn <code lang=\"en\">update.php</code> oproofe.",
+ "config-localsettings-key": "Der Schlößel för et Projramm op ene neue Schtand ze bränge:",
+ "config-localsettings-badkey": "Dinge Schlößel paß nit.",
+ "config-upgrade-key-missing": "Mer han jefonge, dat MediaWiki ald enschtalleed es.\nÜm de Projramme un Daate o der neue Schtand bränge ze künne, dunn aan et Engk vun dä Dattei <code lang=\"en\"><code>LocalSettings.php</code></code> op dämm ẞööver:\n\n$1\n\naanhange.",
+ "config-localsettings-incomplete": "Mer han en Dattei <code lang=\"en\"><code>LocalSettings.php</code>:</code> jefonge, ävver di schingk nit kumplätt ze sin.\nDe Varijable <code lang=\"en\">$1</code> es nit jesatz.\nBes esu joot, un donn di Dattei esu aanpaße, dat se jesaz ea, un dann donn op „{{int:config-continue}}“ klecke.",
+ "config-localsettings-connection-error": "Ene Fähler es opjetrodde wi mer en Verbendong noh de Datebangk opmaache wullte met dä Enschtällonge uß dä Dattei <code lang=\"en\">LocalSettings</code> un et hät nit jeflupp. Bes esu joot un don dat repareere un versöhg et dann norr_ens.\n\n$1\n\n$1",
+ "config-session-error": "Ene Fähler es opjetrodde beim Aanmelde för en Sezung: $1",
+ "config-session-expired": "De Daate för Ding Setzung sinn wall övverholld of afjeloufe.\nDe Setzungunge sin esu enjeshtallt, nit mieh wi $1 ze doore.\nDat kanns De verlängere, endämm dat De de <code lang=\"en\">session.gc_maxlifetime</code> en dä Dattei <code>php.ini</code> jrüüßer määß.\nDon dat Projramm för et Opsäze norr_ens aanschmiiße.",
+ "config-no-session": "De Daate för Ding Setzung sinn verschött jejange.\nDonn en dä Dattei <code>php.ini</code> nohloore, ov dä <code lang=\"en\">session.save_path</code> op e zopaß Verzeijschneß zeisch.",
+ "config-your-language": "Ding Schprooch:",
+ "config-your-language-help": "Donn heh di Shprooch ußsöhke, di dat Enshtallzjuhnsprojramm kalle sull.",
+ "config-wiki-language": "Dem Wiki sing Shprooch:",
+ "config-wiki-language-help": "Donn heh di Shprooch ußsöhke, di et Wiki shtandattmääßesch kalle sull.",
+ "config-back": "← Retuur",
+ "config-continue": "Wigger →",
+ "config-page-language": "Schprooch",
+ "config-page-welcome": "Wellkumme beim MediaWiki!",
+ "config-page-dbconnect": "Met dä Daatebangk Verbenge",
+ "config-page-upgrade": "En Inshtallzjuhn op der neuste Shtand bränge",
+ "config-page-dbsettings": "Parrameeter för de Daatebangk",
+ "config-page-name": "Name",
+ "config-page-options": "Ennställunge",
+ "config-page-install": "Opsäzze",
+ "config-page-complete": "Fäädesch!",
+ "config-page-restart": "Et Opsäze norr_ens neu aanfange",
+ "config-page-readme": "Donn mesch lässe! (<i lang=\"en\">read me</i>)",
+ "config-page-releasenotes": "Henwies för heh di Version vum Projramm (<i lang=\"en\">Release notes</i>)",
+ "config-page-copying": "Ben aam Kopeere",
+ "config-page-upgradedoc": "Ben op der neuste Stand aam bränge",
+ "config-page-existingwiki": "Mer han ald e Wiki!",
+ "config-help-restart": "Wells De all Ding enjejovve Sachee fottjeschmesse han, un dä janze Vörjang vun fürre aan neu aanfange?",
+ "config-restart": "Joh, neu aanfange!",
+ "config-welcome": "=== Ömjevong Pröhfe ===\nMer maache en Aanzahl jrundlääje Pröhvunge, öm erus ze fenge, ov di Ömjävvong heh paß för Mediawiki opzesäze.\nWann de Hölp bem Opsäze hölls, saach wigger, wat heh erus kohm, alsu wat heh schteiht.",
+ "config-copyright": "=== Urhävverrääsch un Lizänzbedengunge ===\n\n$1\n\nDat Projramm heh es frei, mer kann et wiggerjävve un verdeijle un och verändere onger dä Bedengunge vun de GNU <i lang=\"en\">General Public License</i> (Alljemeine öffentlesche Lizänz) wi se vun de <i lang=\"en\">Free Software Foundation</i> (de Schteftung för frei Projramme) veröffentlesch woode es. Dobei kanns De Der de Version 2 vun dä Lizanz ußsöhke, udder jeede Version donoh, wi et Der jefällt.\n\nDat Projramm weed wigger jejovve met dä Hoffnung, dat et jät nöz, ävver <strong>der ohne Jarrantie</strong>, sujaa der ohne de onußjeshproche Jarantie, <strong>verkoufbaa</strong> ze sin, udder <strong>för öhnds_ene beshtemmpte Zweck ze bruche</strong> ze sin.\nLiß de GNU <i lang=\"en\">General Public License</i> sellver, öm mieh ze erfahre.\n\nDo sullts en <doclink href=Copying>Kopie vun dä alljemene öffentlesche Lizänz vun dä GNU</doclink> (<i lang=\"en\">GNU General Public License</i>) zosamme met heh däm Projramm krääje han. Wann dat nit esu es, schrief aan de <i lang=\"en\">Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA</i>, udder [http://www.gnu.org/copyleft/gpl.html liß se online övver et Internet].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki sing Hompäjdsch]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Handbooch för Aanwender]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Handbooch för Administratore un Wiki_Köbesse]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Öff jeshtallte Froore met Antwoote]\n----\n* <doclink href=Readme>Liß Mesch! (<i lang=\"en\">Read me</i>)</doclink>\n* <doclink href=ReleaseNotes><i lang=\"en\">Release notes</i> Övver heh di Projrammversion</doclink>\n* <doclink href=Copying><i lang=\"en\">Copying</i> — Lizänzbeshtemmunge</doclink>\n* <doclink href=UpgradeDoc><i lang=\"en\">Upgrading</i> — Ob en neu Projrammversion jonn</doclink>",
+ "config-env-good": "De Ömjävung es jeprööf.\nDo kanns MediaWiki opsäze.",
+ "config-env-bad": "De Ömjävung es jeprööf.\nDo kanns MediaWiki nit opsäze.",
+ "config-env-php": "PHP $1 es doh.",
+ "config-env-php-toolow": "PHP $1 es enshtalleert.\nÄvver MediaWiki bruch PHP $2 udder hühter.",
+ "config-unicode-using-utf8": "För et <i lang=\"en\">Unicode</i>-Nommaliseere dom_mer däm <i lang=\"en\">Brion Vibber</i> sing Projramm <code lang=\"en\">utf8_normalize.so</code> nämme.",
+ "config-unicode-using-intl": "För et <i lang=\"en\">Unicode</i>-Nommaliseere dom_mer dä [http://pecl.php.net/intl Zohsaz <code lang=\"en\">intl</code> uss em <code lang=\"en\">PECL</code>] nämme.",
+ "config-unicode-pure-php-warning": "'''Opjepaß:''' Mer kunnte dä [http://pecl.php.net/intl Zohsaz <code lang=\"en\">intl</code> uss em <code lang=\"en\">PECL</code>] för et <i lang=\"en\">Unicode</i>-Nommaliseere nit fenge. Dröm nämme mer dat eijfache, ävver ärsh lahme, <i lang=\"en\">PHP</i>-Projrammshtöck doför.\nFör jruuße Wikis met vill Metmaachere doht Üsch die Sigg övver et [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations <i lang=\"en\">Unicode</i>-Nommaliseere] (es op Änglesch) aanloore.",
+ "config-unicode-update-warning": "'''Opjepaß:''' Dat Projramm för der <i lang=\"en\">Unicode</i> zo normaliseere boud em Momang op en ählter Version vun dä Bibliothek vum [http://site.icu-project.org/ ICU-Projäk] op.\nDoht di [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations op der neuste Shtand bränge], wann auf dat Wiki em Äänz <i lang=\"en\">Unicode</i> bruche sull.",
+ "config-no-db": "Mer kunnte kei zopaß Daatebangk-Driiverprojamm fenge.\nMer bruche e Daatebangk-Driiverprojamm för PHP. Dat moß enjeresht wääde.\nMer künne met heh dä Daatebangke ömjonn: $1.\n\nWann De nit om eijene Rääshner bes, moß De Dinge <i lang=\"en\">provider</i> bedde, dat hä Der ene zopaß Driiver enresht.\nWann de PHP sellver övversaz häs, donn e Zohjangsprojramm för en Daatebangk enbenge, för e Beishpell met: <code lang=\"en\">./configure --with-mysql</code>.\nWann De PHP uss enem <i lang=\"en\">Debian</i> udder <i lang=\"en\">Ubuntu</i> Pakätt enjeresht häs, moß De dann och noch et <code lang=\"en\">php5-mysql</code> op Dinge Räschner bränge.",
+ "config-outdated-sqlite": "'''Opjepaß:''' <i lang=\"en\">SQLite</i> $1 es enschtaleert. Avver MediaWiki bruch <i lang=\"en\">SQLite</i> $2 udder hühter. <i lang=\"en\">SQLite</i> kann dröm nit enjesaz wääde.",
+ "config-no-fts3": "'''Opjepaß:''' De Projramme vum <i lang=\"en\">SQLite</i> sin der ohne et [//sqlite.org/fts3.html FTS3-Modul] övversaz, dröm wääde de Funxjohne för et Söhke fähle.",
+ "config-register-globals": "'''Opjepaß:''' dem PHP singe Schallder <code lang=\"en\">[http://php.net/register_globals register_globals]</code> es enjeschalldt.\n'''Donn dä ußmaache, wann De kann.'''\nMediaWiki löp och esu, dä künnt ävver Sesherheitslöcke opmaache, di mer noch nit jefonge un eruß jemaat hät.",
+ "config-magic-quotes-runtime": "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]</code> es enjeschalldt.\nDä määt enjejovve Daate kapott, un doh draan kam_mer dann nix mieh repareere.\nDomet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.\nDat heiß, mer moß en affschallde, söns jeiht nix.",
+ "config-magic-quotes-sybase": "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> es enjeschalldt.\nDä määt enjejovve Daate kapott, un doh draan kam_mer dann nix mieh repareere.\nDomet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.\nDat heiß, mer moß en affschallde, söns jeiht nix.",
+ "config-mbstring": "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> es enjeschalldt.\nDat sorresch för Fähler un kann enjejovve Daate esu kapott maach, dat doh draan nix mieh ze repareere es.\nDomet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.\nDat heiß, mer moß en affschallde, söns jeiht nix.",
+ "config-safe-mode": "'''Opjepaß:''' Dem PHP singe <code lang=\"en\">[http://www.php.net/features.safe-mode safe mode]</code> es aanjeschalldt. Dat kann Ärjer maache, besönders beim Datteie Huhlaade bei de Ongershtözung för <code lang=\"en\">math</code>-Befähle.",
+ "config-xml-bad": "Dem PHP sing XML-Modul es nit ze fenge.\nMediaWiki bruch Funxjohne en däm Modul un deiht et esu nit.\nWann De <i lang=\"en\">Mandrake</i> aam loufehäs, donn dat Pakätt <code lang=\"en\">php-xml</code> enstalleere.",
+ "config-pcre-old": "<strong>Fähler:</strong> PCRE $1 udder neuer es nüüdesch.\nPHP es jäz ävver met PCRE $2 zesamme jebonge.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Mieh dohzoh].",
+ "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-ctype": "'''Fähler:''' <i lang=\"en\">PHP</i> moß met dä Ongerschtözong för der [http://www.php.net/manual/en/ctype.installation.php <code lang=\"en\">Ctype</code> Zohsaz] övversaz woode sin.",
+ "config-json": "'''Dä!:''' PHP wood der ohne <i lang=\"en\" xml:lang=\"en\">JSON</i> övversaz.\nJäz moß de äntweeder dä PHP-<i lang=\"en\" xml:lang=\"en\">JSON</i>-Zohsaz enschtallere udder der <i lang=\"en\" xml:lang=\"en\">[http://pecl.php.net/package/jsonc PECL jsonc]</i>-Zohsaz, ih dat de MedijaWikki enschtallere kanns.\n* Dä PHP-Zohsaz es em <i lang=\"en\" xml:lang=\"en\">Red Hat Enterprise Linux (CentOS)</i> 5 un 6 änthallde, moß ävver en de <code lang=\"en\" xml:lang=\"en\">/etc/php.ini</code> udder <code lang=\"en\" xml:lang=\"en\">/etc/php.d/json.ini</code> enjeschalldt wääde.\n* E paa Linux Destrebuzjohne lohß zigg_em Mai 2013 dä PHP-Zohsaz fott un packe doför der PECL-Zohsaz als <code lang=\"en\" xml:lang=\"en\">php5-json</code> udder <code lang=\"en\" xml:lang=\"en\">php-pecl-jsonc</code> med ein.",
+ "config-xcache": "Dä <code lang=\"en\">[http://xcache.lighttpd.net/ XCache]</code> es ennjeresht.",
+ "config-apc": "Dä <code lang=\"en\">[http://www.php.net/apc APC]</code> es ennjeresht.",
+ "config-wincache": "Dä <code lang=\"en\">[http://www.iis.net/download/WinCacheForPhp WinCache]</code> es ennjeresht.",
+ "config-no-cache": "'''Opjepaß:''' Mer kunnte dä <code lang=\"en\">[http://www.php.net/apc APC]</code>, dä <code lang=\"en\">[http://xcache.lighttpd.net/ XCache]</code> un dä <code lang=\"en\">[http://www.iis.net/download/WinCacheForPhp WinCache]</code> nit fenge.\nEt <i lang=\"en\">object caching</i> es nit müjjelesh un ußjeschalldt.",
+ "config-mod-security": "'''Opjepaß''': Dinge Webßööver hät <code lang=\"en\">[http://modsecurity.org/ mod_security]</code> enjeschalldt. Wann doh derbei en Enschtällong nit janz akeraat paßß, dann kann et goot sin, dat mer Probleme met MeedijaWiki un oc met ander Projramme kritt, die zohlööt, dat vun ußerhallef öhndsene Krohm op dä Webßööver jebraat wääde künnt.Beloor Der di Sigg <code lang=\"en\">[http://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 bemerke 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-git-bad": "Dat Väsjohnsverwalldongsprojamm <i lang=\"en\">Git</i> ham_mer nit jefonge.",
+ "config-imagemagick": "Mer han <i lang=\"en\">ImageMagick</i> jefonge: <code>$1</code>.\nEt Ömrääschne en Minni-Beldsche weed müjjelesch sin, wann De et Belder Huhlaade zohlöhß.",
+ "config-gd": "Mer han de ennjeboute GD-Jrafik-Projramm-Biblijotheek jefonge.\nEt Ömrääschne en Minni-Beldsche weed müjjelesch sin, wann De et Belder Huhlaade zohlöhß.",
+ "config-no-scaling": "Mer han weeder de GD-Jrafik-Projramm-Biblijotheek, noch <i lang=\"en\">ImageMagick</i> jefonge.\nEt Ömrääschne en Minni-Beldsche weed ußjeschalldt.",
+ "config-no-uri": "'''Fähler:''' Mer kunnte der aktoälle <i lang=\"en\">URI</i> nit erusfenge.\nEt Enreeschte es domet heh aam Engk.",
+ "config-no-cli-uri": "'''Opjepaß''': <code lang=\"en\"><code>--scriptpath</code></code> es nit aanjejovve, mer nämme der Schtandatt: <code>$1</code>.",
+ "config-using-server": "Mer nämmen dem ẞööver singe Name: „<nowiki>$1</nowiki>“.",
+ "config-using-uri": "Mer nämmen dem ẞööver singe <i lang=\"en\">URL</i>: „<nowiki>$1$2</nowiki>“.",
+ "config-uploads-not-safe": "'''Opjepaß:''' Uß däm jewöhnlijje Verzeichnes för de huhjelaade Datteie, dat es <code>$1</code>, künnte öhnzwällsche Skrepte un Projramme ußjeföhrt wääde. Och wann MediaWiki de huhjelaade Datteie prööf, dat kein bekannte Risike dren sin, sullt mer doch dat [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security Sesherheitsloch] zoh maache, ih dat mer et Dattei Huhlaade zohlöht.",
+ "config-no-cli-uploads-check": "'''Opjepaß''': <code>$1</code> es dat Schtandatt-Verzeijschneß för et Datteije-Huhlaade. Beim Opsäze met <abbr lang=\"en\" title=\"Call Level Interface\">CLI</abbr> donn mer ävver nit övverpröhve, dat dat jeschöz es dojääje, dat Skrepte vun doh loufe künne, di mer nit loufe han well.",
+ "config-brokenlibxml": "Op Dingem Rääschner loufe Versione vun PHP un <code lang=\"en\">libxml2</code> zosamme, di ävver nit zosamme paßße, un onbimärk de Daate em MediaWiki un ander Web_Aanwändunge [//bugs.php.net/bug.php?id=45996 bug kapott maache].\nJangk op <code lang=\"en\">libxml2</code> 2.7.3 udder dohnoh.\nHeh jeihd et nit wigger.",
+ "config-suhosin-max-value-length": "<i lang=\"en\">Suhosin</i> es enschtalleet. Dröm kann ene <code lang=\"en\">GET</code>-Parrameeter nit övver {{PLURAL:$1|ei Byte|$1 Bytes|noll Byte}} lang wääde. Dem MediaWiki singe <i lang=\"en\">ResourceLoader</i> kütt doh zwa drömeröm, ävver dat bräms. Wann müjelesch, doht <code lang=\"en\">suhosin.get.max_value_length</code> en dä Dattei <code lang=\"en\">php.ini</code> op 1024 Bytes udder drövver enschtälle, un dann moß <code lang=\"en\">$wgResourceLoaderMaxQueryLength</code> en dä Dattei <code lang=\"en\">LocalSettings.php</code> op däsälve Wäät jesaz wääde.",
+ "config-db-type": "De Zoot Daatebangk:",
+ "config-db-host": "Dä Name vun däm Rääschner met dä Daatebangk:",
+ "config-db-host-help": "Wann Dinge ẞööver för de Daatebangk ob enem andere Rääschner es, donn heh dämm singe Name udder dämm sing <i lang=\"en\">IP</i>-Addräß enjävve.\n\nWann De ob enem Meetẞööver beß, weet Der Dinge Provaider odder däm sing Dokemäntazjuhn saare, wat De endraare moß.\n\nWann De ob enem ẞööver onger <i lang=\"en\">Windows</i> am enshtalleere bes un en <i lang=\"en\">MySQL</i>-Daatebangk häs, künnd_et sin, dat „<code lang=\"en\">localhost</code>“ nit douch för der Name vum ẞööver. Wann dad-esu es, versöhg et ens met „<code lang=\"en\">127.0.0.1</code>“ als <i lang=\"en\">IP</i>-Addräß vum eije Rääschner.\n\nWann De ene <i lang=\"en\">PostgreSQL</i>-ẞööver häs, donn dat Fäld läddesch lohße, öm en Verbendung övver e <i lang=\"en\">Unix socket</i> opzemaache.",
+ "config-db-host-oracle": "Dä Daatebangk ier <i lang=\"en\" title=\"Transparent Network Substrate\">TNS</i>:",
+ "config-db-host-oracle-help": "Donn ene jöltije [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „<i lang=\"en\">Local Connect</i>“-Name] aanjävve. De Dattei „<code lang=\"en\">tnsnames.ora</code>“ moß för heh dat Projamm seschbaa un ze Lässe sin.<br />Wann heh de Projamm_Biblijoteeke für de Aanwänderprojramme för de Version 10g udder neuer enjesaz wääde, kam_mer och et [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „<i lang=\"en\">Easy Connect</i>“] jenumme wääde för der Name ze verjävve.",
+ "config-db-wiki-settings": "De Daate vum Wiki",
+ "config-db-name": "Dä Name vun dä Daatebangk:",
+ "config-db-name-help": "Jiff ene Name aan, dä för Ding Wiki passe deiht.\nDoh sullte kei Zweschrereum un kein Stresche dren sin.\n\nWann De nit op Dingem eije Rääschner bes, künnt et sin, dat Dinge Provaider Der extra ene beshtemmpte Name för de Daatebangk jejovve hät, uffr dat de dä drom froore moß udder dat De de Daatebangke övver e Fommulaa selver enreeschte moß.",
+ "config-db-name-oracle": "Schema för de Daatebangk:",
+ "config-db-account-oracle-warn": "Mer han drei Aate, wi mer <i lang=\"en\">Oracle</i> als Daatebangk aanbenge künne.\n\nWann De ene neue Zohjang op de Daatenbangk met Naame un Paßwoot mem Projramm för et Opsäze aanlääje wells, dann jif ene Zohjang met däm Rääsch „<i lang=\"en\">SYSDBA</i>“ aan, dä et alld jitt, un jif däm di Daate aan för dä neue Zohjang aanzelääje.\nDo kanns och dä neue Zohjang vun Hand aanlääje un heh beim Opsäze nur dää aanjävve — wann dä dat Rääsch hät, en de Daatebangk Schema_Objäkte aanzelääje.\nUdder De jiß zwei ongerscheidlijje Zohjäng op de Daatenbangk aan, woh eine vun dat Rääsch zom Aanlääje hät un dä andere moß dat nit un es för der nomaale Bedrief zohshtändesch.\n\nEn Skrep, wat ene Zohjang op de Daatenbangk aanlääsch met all dä nüüdejje Rääschde, fengks De em Verzeishneß <code lang=\"en\">maintenance/oracle/</code> vun Dingem MediaWiki. Donn draan dengke, dat ene Zohjang met beschrängkte Rääschde all di Müjjeleschkeite för et Waade un Repareere nit hät, di de jewöhnlejje Zoot Zohjang met sesh brängk.",
+ "config-db-install-account": "Der Zohjang för en Enreeschte",
+ "config-db-username": "Dä Name vun däm Aanwender för dä Zohjref op de Daatebangk:",
+ "config-db-password": "Et Paßwoot vun däm Aanwender för dä Zohjref op de Daatebangk:",
+ "config-db-password-empty": "Jiv e Paßwoot aan, för dä neue Aanwender för dä Zohjref op de Daatebangk, $1.\nEd es zwa müjjelesch, Aanwender för dä Zohjref op de Daatebangk der ohne e Paßwoot aanzelääje,\nävver dat wöhr en schwere Jevah för de Sescherheit vum Wiki.",
+ "config-db-username-empty": "Do moß jäd aanjävve för \"{{int:config-db-username}}\".",
+ "config-db-install-username": "Jiv ene Name aan för dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere.\nDat es keine Metmaacher_Name em Wiki — heh dä Name es alleins en der Daatebangk bikannt.",
+ "config-db-install-password": "Jiv e Paßwoot aan för dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere.\nDat es kei Paßwoot för ene Metmaacher em Wiki — et es alleins en der Daatebangk bikannt.",
+ "config-db-install-help": "Donn dä Name un et Paßwoot vun däm Aanwänder för der Zohjreff op de Daatebangk jäz för et Enreeshte aanjävve.",
+ "config-db-account-lock": "Donn dersälve Name un et sälve Paßwoot för der nomaale Bedrief vum Wiki bruche",
+ "config-db-wiki-account": "Dä Name vun däm Aanwender för dä Zohjref op de Daatebangk em nomaale Bedrief:",
+ "config-db-wiki-help": "Jiv ene Name un e Paßwoot aan, för dä Aanwender för dä Zohjref op de Daatebangk, wann et wiki nommaal aam Loufe es.\nWan et dä Name en der Daatebangk noch it jit, un dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere\njenooch Beräschtijunge hät, läät dä heh dä Aanwender en der Daatebangk aan un jidd_em di Rääschde, di dä nüüdesch hät, ävver nit mieh.",
+ "config-db-prefix": "Vörsaz för de Name vun de Tabälle en de Daatebangk:",
+ "config-db-prefix-help": "Wann ein Daatebangk för mieh wi ein Wiki udder e Wiki uns söns jät zosamme jebruch weed, dann kam_mer noch jet vör de Tabälle ier Name säze. Esu ene Vörsaz sull dubblte Tabällename vermeide hälfe.\nDonn kein Zwescheräum enjävve!\n\nJewöhnlesch bliev dat Feld heh ävver läddesch.",
+ "config-db-charset": "Dä Daatebangk iere Zeischesaz",
+ "config-charset-mysql5-binary": "MySQL (4.1 udder 5.0) binär",
+ "config-charset-mysql5": "MySQL (4.1 udder 5.0) UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 röckwääts kompatibel UTF-8",
+ "config-charset-help": "<strong>Opjepaß:</strong>\nWann De et <strong>röckwääts kompatibel UTF-8 Fommaat</strong> nemmps, met dem <i lang=\"en\">MySQL</i> singe Version4.1 udder hüüter, dann künnt dat all di Zeische kappott maache, die nit em <i lang=\"en\" title=\"American Standard Code for Information Interchange\">ASCII</i> sen, un domet all ding Sescherungskopieje kapott maache, wat mer nieh mieh retuur krijje kann.\n\nBeim Schpeischere em <strong>binäre Fomaat</strong> deiht MediaWiki de Täx, dä em UTF-8 Fommaat küdd, en dä Daatebangk en binär kodeerte Daatefälder faßhallde.\nDat es flöcker un spaasaamer wi et UTF-8 Fommaat vum <i lang=\"en\">MySQL</i> un määd et müjjelesch, all un jeedes <i lang=\"en\">Unicode</i>-Zeische met faßzehallde.\n\nBeim Schpeischere em <strong>UTF-8 Fomaat</strong> deiht et <i lang=\"en\">MySQL</i> der Zeischesaz un de Kodeerung vun dä Daate känne, un kann se akeraat aanzeije un ömwandelle,\nallerdengs künne kein Zeische ußerhalv vum [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Schprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeschpeischert wääde.",
+ "config-mysql-old": "Mer bruche <i lang=\"en\">MySQL</i> $1 udder neuer. Em Momang es <i lang=\"en\">MySQL</i> $2 aam Loufe.",
+ "config-db-port": "De Pooz-Nommer (<i lang=\"en\">port</i>) för de Daatebangk:",
+ "config-db-schema": "Et Schema en de Datebangk för MediaWiki:",
+ "config-db-schema-help": "För jewöhnlesch es dat Schema en Odenong.\nDonn bloß jät draan ändere, wann De sescher weiß, dat dat nüüdesch es.",
+ "config-pg-test-error": "Mer krijje kein Verbendung zor Daatebank '''$1''': $2",
+ "config-sqlite-dir": "Dem <i lang=\"en\">SQLite</i> sing Daateverzeishnes:",
+ "config-sqlite-dir-help": "<i lang=\"en\">SQLite</i> hät all sing Daate zosamme en en einzel Dattei.\n\nEn dat Verzeishneß, wat De aanjiß, moß dat Web_ẞööver_Projramm beim Opsäze eren schriive dörrve.\n\nDat Verzeishneß sullt '''nit''' övver et Web zohjänglesch sin, dröm dom_mer et nit dohen, woh de <i lang=\"en\">PHP</i>-Datteije sin.\n\nMer donn beim Opsäze zwa uß Vöörssh en <code lang=\"en\">.htaccess</code> Dattei dobei, ävver wann di nit werrek, künnte Lück vun ußerhallef aan Ding Daatebangk_Dattei eraan kumme.\nDoh shtonn Saache dren, wi de Addräße för de Metmaacher ier <i lang=\"en\">e-mail</i> un de verschlößelte Paßwööter un de vershtoche un de fottjeschmeße Sigge un ander Saache ussem Wiki, di mer nit öffentlesch maache darref.\n\nDonn Ding Daatebangk et beß janz woh anders hen, noh <code lang=\"en\">/var/lib/mediawiki/''wikiname''</code> för e Beishpell.",
+ "config-oracle-def-ts": "Tabälleroum för der Shtandattjebruch:",
+ "config-oracle-temp-ts": "Tabälleroum för der Jebruch zweschedorsh:",
+ "config-type-mysql": "<i lang=\"en\">MySQL</i> (udder en jlischwääteje)",
+ "config-type-postgres": "<i lang=\"en\">PostgreSQL</i>",
+ "config-type-sqlite": "<i lang=\"en\">SQLite</i>",
+ "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>. ([http://www.php.net/manual/de/mysql.installation.php Aanleidung för et Övversäze un Enreeschte von PHP 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. ([http://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 Daatebangksüßtehm för Rääschner met <i lang=\"en\" xml:lang=\"en\">Windows</i>. ([http://www.php.net/manual/de/sqlsrv.installation.php Aanleidong för et Övversäze un Enreeschte von PHP 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",
+ "config-header-oracle": "De Enshtällunge för de <i lang=\"en\">Oracle</i> Daatebangk",
+ "config-header-mssql": "Enschtällonge för der <i lang=\"en\" xml:lang=\"en\">SQL</i>-ẞööver vun <i lang=\"en\" xml:lang=\"en\">Microsoft</i>",
+ "config-invalid-db-type": "Dat es en onjöltijje Zoot Daatebangk.",
+ "config-missing-db-name": "Do moß jäd enjävve för \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Do moß jät enjävve för \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Do moß jät enjävve för \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "Dä Daatebangk ier <i lang=\"en\" title=\"Transparent Network Substrate\">TNS</i> kann nit „$1“ sin, dat es esu nit jöltesch.\nNemm en „TNS-Nahme“ udder ene „<i lang=\"en\" xml:lang=\"en\">Easy-Connect</i>“-<i lang=\"en\" xml:lang=\"en\">Easy-Connect</i>String</i>(Lor noh dädohwähje noh de <i lang=\"en\" xml:lang=\"en\">[http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]</i>)",
+ "config-invalid-db-name": "Dä Daatebangk iere Name kann nit „$1“ sin, dä es esu nit jöltesch.\nDöh dörve bloß <i lang=\"en\" title=\"American Standard Code for Information Interchange\">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), Ongerstresh (_), un Bendeshtresh (-) dren vörkumme.",
+ "config-invalid-db-prefix": "Dä Vörsaz för de Name vun de Tabälle en de Daatebangk kann nit „$1“ sin, dä es esu nit jöltesch.\nDöh dörve bloß <i lang=\"en\" title=\"American Standard Code for Information Interchange\">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), Ongerstreshe (_), un Bendeshtreshe (-) dren vörkumme.",
+ "config-connection-error": "$1.\n\nDonn de Name för dä Rääschner, vun däm Aanwender för dä Zohjref op de Daatebangk, un et Paßwoot prööfe, repareere, un dann versöhg et norr_ens.",
+ "config-invalid-schema": "Dat Schema för MediaWiki kann nit „$1“ sin, dä Name wöhr esu nit jöltesch.\nDöh dörve bloß <i lang=\"en\" title=\"American Standard Code for Information Interchange\">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), un Ongerstreshe (_) dren vörkumme.",
+ "config-db-sys-create-oracle": "Dat Projramm för MediaWiki opzesäze kann bloß <i lang=\"en\">SYSDBA</i> bruche för ene neue Zohjang zor Daatebangk enzereeschte!",
+ "config-db-sys-user-exists-oracle": "Dä Aanwender „$1“ för dä Zohjref op de Daatebangk jidd_et ald. <i lang=\"en\">SYSDBA</i> kam_mer bloß bruche, för ene neue Zohjang enzereeschte!",
+ "config-postgres-old": "Mer bruche <i lang=\"en\">PostgreSQL</i> $1 udder neuer. Em Momang es <i lang=\"en\">PostgreSQL</i> $2 aam Loufe.",
+ "config-mssql-old": "Dä <i lang=\"en\" xml:lang=\"en\">SQL</i>-ẞööver vun <i lang=\"en\" xml:lang=\"en\">Microsoft</i> aff de Väsjohn $1 es nüüdesch. Heh es bloß d Väsjohn $2 ze fenge.",
+ "config-sqlite-name-help": "Söhk enen Name uß, dä Ding Wiki beschrief.\nDonn kein Bendeschresch un Zweschräum en däm Name bruche.\nDä Name weed för der Dateiname för de <i lang=\"en\">SQLite</i> Daatebangk jenumme.",
+ "config-sqlite-parent-unwritable-group": "Mer kunnte dat Verzeischneß för de Daate, <code lang=\"en\"><nowiki>$1</nowiki></code>, nit enreeschte, weil dat Projramm fö dä Web_ẞööver en dat Verzeischneß doh drövver, <code><nowiki>$2</nowiki></code>, nix erin donn darref.\n\nMer han dä Name vun däm Zohjang op et Süßteem eruß jefonge, onger dämm dat Web_ẞööver_Projramm läuf. Jez moß De bloß doför sorrje, dat dä en dat Verzeischneß <code><nowiki>$3</nowiki></code> schrieve kann, öm heh wigger maache ze künne.\nOb enem Süßteem met <i lang=\"en\">Unix</i>- oder <i lang=\"en\">Linux</i> jeiht dat esu:\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Mer kunnte dat Verzeischneß för de Daate, <code lang=\"en\"><nowiki>$1</nowiki></code>, nit enreeschte, weil dat Projramm fö dä Web_ẞööver en dat Verzeischneß doh drövver, <code><nowiki>$2</nowiki></code>, nix erin donn darref.\n\nMer han dä Name vun däm Zohjang op et Süßteem nit eruß fenge künne, onger dämm dat Web_ẞööver_Projramm läuf. Jez moß De bloß doför sorrje, dat dä en dat Verzeischneß <code><nowiki>$3</nowiki></code> schrieve kann, öm heh wigger maache ze künne. Wann De dä Name och nit weiß, maach, dat jeeder_ein doh schrieve kann.\nOb enem Süßteem met <i lang=\"en\">Unix</i>- oder <i lang=\"en\">Linux</i> jeiht dat esu:\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Ene Fähler es opjetrodde beim Aanlääje vum Daate_Verzeishneß „$1“.\nDon dä Plaz för et Shpeishere prööfe un Repareere, dann versöhg et norr_ens.",
+ "config-sqlite-dir-unwritable": "Mer künne nit en dat Verzeishneß „$1“ schrieeve\nDonn dohvun de Zohjreffs_Rääschde esu verändere, dat der Webßööver doh dren schrieeve kann, un dann versöhg et norr_ens.",
+ "config-sqlite-connection-error": "$1.\n\nDonn onge dat Verzeishnes un der Name vun der Daatebangk prööfe un repareere, un dann versöhg_et norr-ens.",
+ "config-sqlite-readonly": "En di Dattei <code lang=\"en\">$1</code> künne mer nit schrieve.",
+ "config-sqlite-cant-create-db": "Mer kunnte di Dattei <code lang=\"en\">$1</code> för de Daatebangk nit aanlääje.",
+ "config-sqlite-fts3-downgrade": "Dat PHP heh hät kein Ongershtözong för FTS3, dröm donn mer de Daatebangktabälle eronger shtoofe.",
+ "config-can-upgrade": "Et sinn-er ald Daatebangktabelle vum MediaWiki en dä Daatebangk.\nÖm di op der Shtand vum MediaWiki $1 ze bränge, donn jäz op „{{int:config-continue}}“ klecke.",
+ "config-upgrade-done": "Alles es jäz om neue Schtand.\n\nMer kann dat Wiki jäz [$1 bruche].\n\nWann De Ding Dattei <code lang=\"en\">LocalSettings.php</code> neu schrieve wells, donn onge op dä Knopp kleke.\nDat dom_mer ävver '''nit vörschlonn''' — em Jääjedeil — ußer, wann et Problehme mem Wiki jitt.",
+ "config-upgrade-done-no-regenerate": "Alles es jäz om neue Shtand.\n\nMer kann dat Wiki jäz [$1 bruche].",
+ "config-regenerate": "Donn de Dattei <code lang=\"en\">LocalSettings.php</code> neu opsäze →",
+ "config-show-table-status": "Et Kommando <code lang=\"en\"><code>SHOW TABLE STATUS</code></code> aan de Daatebangk es donävve jejange!",
+ "config-unknown-collation": "'''Opjepaß:''' De Daatabangk deiht en onbikannte Reijefollsch bruche, för Booshtaabe un Zeishe ze verjliishe un ze zotteere.",
+ "config-db-web-account": "Dä Zohjang zor Daatebangk för et Wiki",
+ "config-db-web-help": "Donn ene Name un e Paßwoot för der Zohjang zor Daatebangk för et Wiki em nomaale Bedrief aanjävve.",
+ "config-db-web-account-same": "Donn dersällve Zohjang nämme, wi heh beim Opsäze.",
+ "config-db-web-create": "Donn dä Zohjang aanlääje, wann dä noch nit doh es.",
+ "config-db-web-no-create-privs": "Dä Zohjang för et Opsäze es nit berääschtesch, ene ander Zohjan enzereeschte.\nDä aanjejovve Zohjang för der Nomaalbedrief moß dröm schunn enjersht sen!",
+ "config-mysql-engine": "De Zoot udder et Fommaat vun de Tabälle:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Opjepaß:''' <i lang=\"en\">MyISAM</i> es als Speicher för <i lang=\"en\">MySQL</i> nit besönders joot för et Zosammeschpell met MediaWiki zo bruche:\n* Dorj_et kumplätte Sperre vun Tabälle, künne koum ens Saache parrallel en dä Daatebangk jedonn wääde.\n* Dat Fomaat es anfällesch för Probleme met de Daate.\n* Et weed vun MediaWiki nit ėmmer zopaß ongerschtöz.\n\nWann Ding <i lang=\"en\">MySQL</i> et Schpeischere en <i lang=\"en\">InnoDB</i>-Datteije ongerschtöze deiht, dom_mer dat nohdröcklesch ämfähle.\nKann dä ẞööver dat nit, künnd et joode jelääjeheit sin, dä ens op der neuste Schtand ze bränge.",
+ "config-mysql-only-myisam-dep": "'''Opjepaß:''' <i lang=\"en\" xml:lang=\"en\">MyISAM</i> es de einzeje Zoot Schpeischerprojramm för <i lang=\"en\" xml:lang=\"en\">MySQL</i> op dä Maschiin. Di es nit för MediaWiki ze ämfähle es, weil:\n* wääje dem Schpärre vun jannze Tabälle sin koum paralleele Axjuhne en dä Daatebangk möjjelesch,\n* ed es aanfällesch för Probleeme met de Daate es, un\n* et weed vun MediaWiki nit emmer jood ongerschtöz.\n\nDing Enschtallazjuhn vum <i lang=\"en\" xml:lang=\"en\">MySQL</i> kann nit met <i lang=\"en\" xml:lang=\"en\">InnoDB</i> ömjonn.\nWi wöhr et med ene neuere Väsjohn vum <i lang=\"en\" xml:lang=\"en\">MySQL</i>?",
+ "config-mysql-engine-help": "'''InnoDB''' es fö jewöhnlesch et beß, weil vill Zohjreffe op eijmohl joot ongershtöz wääde.\n\n'''MyISAM''' es flöcker op Rääschnere met bloß einem Minsch draan, un bei Wikis, di mer bloß lässe un nit schrieeve kann.\nMyISAM-Daatebangke han em Schnett mieh Fähler un jon flöcker kappott, wi InnoDB-Daatebangke.",
+ "config-mysql-charset": "Dä Daatebangk iere Zeischesaz:",
+ "config-mysql-binary": "binär",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "Beim Schpeishere em <strong>binäre Fomaat</strong> deiht MediaWiki Täxt, dä em UTF-8 Fommaat kütt, en dä Daatebangk en binär kodeerte Daatefälder faßhallde.\nDat es flöcker un spaasaamer wi et UTF-8 Fommaat vum <i lang=\"en\">MySQL</i> un määd et müjjelesch, all un jeedes <i lang=\"en\">Unicode</i>-Zeische met faßzehallde.\n\nBeim Schpeishere em <strong>UTF-8 Fomaat<strong> deiht et <i lang=\"en\">MySQL</i> der Zeischesaz un de Kodeerung vun dä Daate känne, un kann se akeraat aanzeije un ömwandelle,\nallerdengs künne kein Zeische ußerhalv vum [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Schprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeschpeischert wääde.",
+ "config-mssql-auth": "De Zoot Aanmäldong:",
+ "config-mssql-install-auth": "Söhk us, wi dat Aanmälde aan dä Daatebangk vor sesch jonn sull för de Enschtallazjuhn.\nWann De <em>{{int:Config-mssql-windowsauth}}</em> nemms, weed jenumme, met wat emmer dä Wäbßööver aam loufe es.",
+ "config-mssql-web-auth": "Söhk us, wi dat Aanmälde aan dä Daatebangk vör sesch jonn sull för de nommaale Ärbeid vum Wiki.\nWann De <em>{{int:Config-mssql-windowsauth}}</em> nemms, weed dat jenumme, wohmet dä Wäbßööver aam loufe es.",
+ "config-mssql-sqlauth": "De Aanmäldong bemm <i lang=\"en\" xml:lang=\"en\">SQL</i>-ẞööver vun <i lang=\"en\" xml:lang=\"en\">Microsoft</i>",
+ "config-mssql-windowsauth": "De Annmäldong bemm <i lang=\"en\" xml:lang=\"en\">Windows</i>",
+ "config-site-name": "Däm Wiki singe Name:",
+ "config-site-name-help": "Dä douch em Tettel vun de Brauserfinstere un aan ätlije andere Schtälle op.",
+ "config-site-name-blank": "Donn ene Name för di Sait aanjävve.",
+ "config-project-namespace": "Dä Name för et Appachtemang övver et Projäk:",
+ "config-ns-generic": "Projäk",
+ "config-ns-site-name": "Et sällve wi däm Wiki singe Name: $1",
+ "config-ns-other": "Andere (jiff aan wälshe)",
+ "config-ns-other-default": "MingWiki",
+ "config-project-namespace-help": "Noh dämm Vörbeld vun de Wikipeedija, donn vill Wikis dänne ier Sigge övver et Wiki un sing Rääjelle vun dä Sigge mem Enhald vum Wiki tränne, un en enem extra Appachtemang för et „'''Projäk'''“ afflääje.\nSigge en däm Appachtemang fange all med enem beshtemmpte Vörsaz aan, däm Name vum Appachtemang, un dä moß De heh faßlääje.\nDä Name kann beshtemmpte Zeiche nit enthallde, wi „#“ un „:“ un et es Tradizjuhn, dat hä vum Name vum Wiki her kütt.",
+ "config-ns-invalid": "Dat aanjejovve Appachtemang „<nowiki>$1</nowiki>“ es nit jöltesch.\nNemm ene andere Name för däm Wiki sing eije Appachtemang.",
+ "config-ns-conflict": "Dat aanjejovve Appachtemang „<nowiki>$1</nowiki>“ kütt ald als Standatt-Appachtemang em MediaWiki vör.\nNemm ene andere Name för däm Wiki sing eije Appachtemang.",
+ "config-admin-box": "Der Zohjang för der eezte Wiki_Köbes",
+ "config-admin-name": "Dinge Metmaacher_Name:",
+ "config-admin-password": "Et Paßwoot:",
+ "config-admin-password-confirm": "Norrens dat Paßwoot:",
+ "config-admin-help": "Jif Dinge leevste Name als Metmaacher för Desch aan, för e Beishpell „Schmitzens Pitter“\n— Dat weed dä Name wääde, met dämm De Desch enlogge deihs.",
+ "config-admin-name-blank": "Jiv ene Metmaacher_Name en för dä Wiki-Köbes.",
+ "config-admin-name-invalid": "„<nowiki>$1</nowiki>“ es keine jöltijje Metmaacher_Name.\nJiv ene joode Name en!",
+ "config-admin-password-blank": "Do mos_e Paßwoot för dä Wiki_Köbes aanjävve!",
+ "config-admin-password-mismatch": "Di Paßwööter sin ongerscheidlesh!",
+ "config-admin-email": "Addräß för de <i lang=\"en\">e-mail</i>:",
+ "config-admin-email-help": "Jiv heh di Adräß för de <i lang=\"en\">e-mail</i> aan, woh De <i lang=\"en\">e-mail</i> vun ander Metmaacher uss_em Wiki hen krijje wells, di et Der müjjelesh määt, Ding Paßwoot automatetsch truusche ze lohße, un woh Nohreeshte övver veränderte Sigge op Dinge Oppaßleß hen jescheck wääde sulle.\nDe kanns dat Fäld ävver och läddesch lohße.",
+ "config-admin-error-user": "Beim Enreeshte vum Zohjang för dä Wiki_Köbes „<nowiki>$1</nowiki>“ es ene Fähler em Wiki opjetrodde.",
+ "config-admin-error-password": "Beim Paßwoot-Säze för dä Wiki_Köbes „<nowiki>$1</nowiki>“ es ene Fähler em Wiki opjetrodde.: <pre>$2</pre>",
+ "config-admin-error-bademail": "Do häs_en onjöltijje Addräß för de <i lang=\"en\">e-mail</i> aanjejovve.",
+ "config-subscribe": "Donn de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce <i lang=\"en\">e-mail</i>-Leß met de Aanköndijunge vum MediaWiki] abonnere.",
+ "config-subscribe-help": "Do kumme bloß winnish Meddeilunge un di jonn övver neu Versiohne vom MediaWiki un weeshtejje Saache vun däm sing Sesherheit.\nDo sullts se abbonneere, un Ding MediWiki_Projramme op der neue Shtand bränge, wann neu Version eruß kumme.",
+ "config-subscribe-noemail": "Do has versöhk, der ohne en Addräß för Ding <i lang=\"en\">e-mail<i> aanzejävve, de Aanköndijonge för Aanköndijunge för neue Versione ze abboneere. Jivv en Addräß aan, wann De di Aanköndijonge hann wells.",
+ "config-almost-done": "Do bes beinah dorsh!\nDo künnts jez der Räß vun de einzel Enshtellunge övverjonn, un et Wiki tiräktemang fäädesch opsäze.",
+ "config-optional-continue": "De wells noch mieh Frore jeshtallt krijje un noch mieh Enshtällunge maache?",
+ "config-optional-skip": "Nä, lohß dä Ömshtand, donn eifarr_et Wiki opsäze.",
+ "config-profile": "Enshtällunge för de Metmaacher ier Rääschte:",
+ "config-profile-wiki": "En offe Wiki",
+ "config-profile-no-anon": "Schriever möße enlogge",
+ "config-profile-fishbowl": "Bloß ußdröcklesch zohjelohße Schriever",
+ "config-profile-private": "E jeschloße Privat_Wiki",
+ "config-profile-help": "Wikis loufe et bäß, wam_mer esu vill Lück wi möjjelesch draan metmaache un schrieve löht.\nMet MediaWiki es et ejfach, de neuste Änderonge ze beloore un wat ahnungslose udder fiese Lück kapott jemaat han wider retuur ze maache.\n\nBloß, mänsch eine häd_eruß jefonge, dat mer MediaWiki jood en en jruuße Zahl ongerscheidlijje Rolle bruche kann, un nit emmer es et leisch, ene vum onverfälschte Wiki_Wääsch ze övverzeuje.\nEsu häß De de Wahl:\n\n'''{{int:config-profile-wiki}}''' löht jeder_ein metschrieve, och ohne sesch enzelogge.\n\n'''{{int:config-profile-no-anon}}''', dat sorsch för mieh seeschbaa Verantwootlischkeite, künnt ävver zohfällije Methellefer verschrecke.\n\n'''{{int:config-profile-fishbowl}}''' löht nor de ußjesöhk Metmaacher schrieve, ävver de janze Öffentleshkeit kann et lässe un süht och de ällder Versione, un wat wää wann draan jedonn hät.\n\n'''{{int:config-profile-private}}''' kann nur lässe, wäh en et Wiki zohjelohße es, un desellve Jropp kann uch schrieve.\n\nNoch ander un un opwändijere Enschtellunge för de Rääschte sin möjjelesch, wann et Wiki ens aam Loufe es. Loor Der doför de [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights zopaß Hölp em Handbooch] aan.",
+ "config-license": "Urhävverrääsch un Lizänz:",
+ "config-license-none": "Kein Fooßreih övver de Lizänz",
+ "config-license-cc-by-sa": "<i lang=\"en\">Creative Commons</i> Der Name moß jenannt sin, et Wiggerjävve es zohjelohße onger dersellve Bedengunge",
+ "config-license-cc-by": "De <i lang=\"en\">Creative Commons</i> ier Lizänz met Namensnännong",
+ "config-license-cc-by-nc-sa": "<i lang=\"en\">Creative Commons</i> Nit för e Jeschäff ze maache, et Wiggerjävve es zohjelohße unger dersellve Bedengunge",
+ "config-license-cc-0": "<i lang=\"en\">Creative Commons</i> „Noll“ (jemeinfrei udder Pablic Domain)",
+ "config-license-gfdl": "De <i lang=\"en\">GNU</i>-Lizänz för frei Dokemäntazjuhne, Version 1.3 udder en späädere",
+ "config-license-pd": "Allmende (jemeinfrei, <i lang=\"en\">public domain</i>)",
+ "config-license-cc-choose": "En <i lang=\"en\">Creative Commons</i> Lizänz, sellver ußjesöhk:",
+ "config-license-help": "Ättlijje öffentleje Wikis donn iehr Beidrääsh onger en [http://freedomdefined.org/Definition frei Lizänz] shtelle.\nDat hellef, e Jeföhl vun Jemeinsamkeid opzeboue, un op lange Seesh emmer wider Beidrääsch ze krijje.\nDat es nit onbedengk nüüdesh för e Jeschäffs- udder Privaat_Wiki.\n\nWä Stöcke uß de Wikipedia bruche well, un han well, dat de Wikipedia uss_em eije Wiki jät övvernämme kann, sullt „'''<i lang=\"en\">Creative Commons</i>, dem Schriever singe Name moß jenannt wääde, un Wiggerjävve zoh dersellve Bedengunge es zohjelohße'''“ ußwähle.\n\nDe su jenannte '''<i lang=\"en\">GNU Free Documentation License</i>''' (de freije Lizänz för Dokemäntazjuhne vun dä GNU) sen de ahle Lizänzbedenonge vun de Wikipedia. Se es emmer noch in Odenong un jöltesch, ävver se es schwer ze vershtonn un et Wiggerjävve un widder Verwände es manshmool schwieeresch domet.",
+ "config-email-settings": "Enschtellunge för de <i lang=\"en\">e-mail</i>",
+ "config-enable-email": "De <i lang=\"en\">e-mail</i> noh druße zohlohße",
+ "config-enable-email-help": "Sulle <i lang=\"en\">e-mails</i> zohjelohße sin, moß mer, domet et noher flupp, de [http://www.php.net/manual/en/mail.configuration.php Enschtellunge em PHP för de <i lang=\"en\">e-mails</i>] zopaß jemaat han.\nWann kein <i lang=\"en\">e-mails</i> nüüdesch sin, kam_mer se heh afschallde.",
+ "config-email-user": "<i lang=\"en\">e-mails</i> zwesche de Metmaacher zohlohße",
+ "config-email-user-help": "Määt et müjjelesch, dat sesch de Metmaacher jääjesiggesch <i lang=\"en\">e-mails</i> schecke künne, wann se dat en iehre eije Enschtellunge och enjeschalldt han.",
+ "config-email-usertalk": "<i lang=\"en\">e-mails</i> mem Bescheid zohlohße, dat einem sing Klaafsigg verändert woodt",
+ "config-email-usertalk-help": "Maach et müjjelesch, dat Metmaaacher en iere Enstellunge <i lang=\"en\">e-mails</i> mem Bescheid zohlohße, dat einem sing Klaafsigg verändert woodt.",
+ "config-email-watchlist": "Nohreeschte övver Änderonge aan Sigg op de Opaßleßte zohlohße",
+ "config-email-watchlist-help": "Lohß Metmaacher Nohreeshte övver de Sigge op dänne iehr Oppaßleß krijje, wann se et en iehre Enschtellonge ußjewählt han.",
+ "config-email-auth": "Donn de Övverprööfung för Zohjangsberääschtejunge övver de <i lang=\"en\">e-mail</i> zohlohße",
+ "config-email-auth-help": "Wann dat aanjeschald es, möße Metmaacher, di iehr Adräß för de <i lang=\"en\">e-mail</i> neu aanjävve udder ändere, di Addräß övver ene Lengk beschtäätejje, dä se met de <i lang=\"en\">e-mail</i> jescheck krijje.\nBloß aan esu beschtääteschte Adräße deiht et Wiki <i lang=\"en\">e-mails</i> schecke, Di künne vun annder Metmaachere kumme, udder vum Wiki sellver, wann en Sigg en däm Metmaacher singe Oppaßleß verändert woode es.\nMer '''schlonn vör, dat aanzeschallde''' för öffentlesch Wikis, weil sönß zoh leisch Driß mem Wiki singe <i lang=\"en\">e-mail</i> jemaat wääde künnt.",
+ "config-email-sender": "De Adräß för de Antwoote op <i lang=\"en\">e-mails</i>:",
+ "config-email-sender-help": "Jiff de Adräß för de <i lang=\"en\">e-mail</i> en, woh Antwoote ob em Wiki singe <i lang=\"en\">e-mails</i> hen jonn sulle.\nDat es och de Adräß, woh de <i lang=\"en\">e-mails</i> met Fählermäldonge hen jon.\nVill ẞöövere för de <i lang=\"en\">e-mail</i> welle winnischßdens ene jöltijje Domain en dä Adräß han.",
+ "config-upload-settings": "Belder un Datteie huh laade",
+ "config-upload-enable": "Belder un Datteie huh laade zohlohße",
+ "config-upload-help": "Datteije huh ze laade künnt e Risiko för dem ẞööver singe Sescherheit sin.\nMieh doh drövver kam_mer em [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security Kapitel övver de Sescherheit] em Handbooch lässe.\n\nÖm et Huhlaade zohzelohße donn de Rääschde för der Zohjreff op dat Ongerverzeischneß <code lang=\"en\">images</code> em MediaWiki singem Houpverzeischneß esu enshtälle, dat et Webßööverprojramm doh Datteije un Verzeischneße eren schrieve kann.\nDonoh donn heh di Saach zohlohße.",
+ "config-upload-deleted": "Dat Verzeishneß för fottjeschmeße Datteije:",
+ "config-upload-deleted-help": "Söhk e Verzeijschneß uß för de fottjeschmeße Datteije vum Wiki dren afzelääje.\nEt bäß es, wam_mer vum <i lang=\"en\">world wide web</i> doh nit drahn kumme kann.",
+ "config-logo": "Dem Wiki singem Logo sing <i lang=\"en\">URL</i>:",
+ "config-logo-help": "De Schtandart_Bedeen_Bovverfläsch vum MediaWiki hät e Logo bovve en der Eck met 135x160 Pixele.\nDonn e zopaß Logo huh laade, un donn däm sing URL heh endraare.\n\nDo kanns <code lang=\"en\">$wgStylePath</code> udder <code lang=\"en\">$wgScriptPath</code> nämme, wann Ding Logo en einem vun dänne Pahde litt.\n\nWells De kei Logo han, draach heh nix en.",
+ "config-instantcommons": "Donn <i lang=\"en\">InstantCommons</i> zohlohße.",
+ "config-instantcommons-help": "<i lang=\"en\">[//www.mediawiki.org/wiki/InstantCommons InstantCommons]</i> es en Eijeschaff, di et för Wikis müjjelesch määt, Belder, Tondatteie un ander Meedijedatteie enzebenge, di op dä Webßait vun de <i lang=\"en\">[//commons.wikimedia.org/ Wikimedia Commons]</i> ongerjebraat sin. Öm dat noze ze künne, moß dä ẞööver vum MediaWiki en Verbendung nohm Internet opnämme künne.\n\nMieh Aanjaabe doh drövver un en Aanleidung, wi mer och ander Wikis ußer de <i lang=\"en\">Wikimedia Commons</i> doför enreeschte kann, fengk mer em [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos Handbooch].",
+ "config-cc-error": "Et Ußsöhke övver de <i lang=\"en\">Creative Commons</i> iehr Projramm zum Lizänzbeshtemme hät nix jebraat.\nDonn de Lizänz sellver beshtemme.",
+ "config-cc-again": "Noch ens neu ußsöhke&nbsp;…",
+ "config-cc-not-chosen": "Söhk uß, wat för en Lizänz vun de <i lang=\"en\">Creative Commons</i> De han wells, un donn dann op „<i lang=\"en\">proceed</i>“ klecke.",
+ "config-advanced-settings": "Fottjeschredde Enshtellunge",
+ "config-cache-options": "Enshtällunge för et Faßhallde vun Objäkte em Zweschsheisher:",
+ "config-cache-help": "Objäkte em Zwescheshpeisher faßhallde, dat heiß öff jebruchte Daate en der <i lang=\"en\">cache</i> donn, bruche mer, öm MediaWiki flöcker ze maache,\nMeddlere un jruuße Wiki-ẞaits sullte dat onbedengk ußnoze, un och bei klein Wikis weed mer et jood merke.",
+ "config-cache-none": "Keine Zweschshpeijsher (Et jeid_em Wiki nix verloore, ußer velleish Schnälleshkeid wann vill loss es)",
+ "config-cache-accel": "Ene Objäk<i lang=\"en\">cache</i> vum PHP (<i lang=\"en\">APC</i>, <i lang=\"en\">XCache</i>, udder <i lang=\"en\">WinCache</i>)",
+ "config-cache-memcached": "Donn der <code lang=\"en\">memcached</code> ẞööver nämme (Määt extra Enshtellunge un Opsäze nüüdesch)",
+ "config-memcached-servers": "De <code lang=\"en\">memcached</code> ßöövere:",
+ "config-memcached-help": "Donn de Leß aanhjävve, met de <i lang=\"en\">IP</i>-Addräße för der <code lang=\"en\">memcached</code> ẞööver ze bruche.\nSe sullte ein pro Reih opjeschrevve sin, un en Pooz (<i lang=\"en\">port</i>) ier Nommer han, För e Beishpell, esu:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Do häss der <code lang=\"en\">memcached</code> als Dinge Zoot vun Zwescheshpeijscher aanjejovve, ävver nit eine ẞööver doför.",
+ "config-memcache-badip": "Do häss en onjöltijje <i lang=\"en\">IP</i>-Addräß för der <code lang=\"en\">memcached</code> ẞööver aanjejovve: $1.",
+ "config-memcache-noport": "Do has kein Pooz (<code lang=\"en\">port</code>) Nommer aanjejovve för mem <code lang=\"en\">memcached</code> ẞööver ze bruche: $1.\nWann De di Nommer nit weiß, der Shtandatt es 11211.",
+ "config-memcache-badport": "Dem <code lang=\"en\">memcached</code> ẞööver singe Pooz (<code lang=\"en\">port</code>) Nommere sullte zwesche $1 un $2 sin.",
+ "config-extensions": "Projramm-Zohsäz (<i lang=\"en\">Extensions</i>)",
+ "config-extensions-help": "Di bovve opjeleß Zohsazprojramme för et MediaWiki sin em Verzeischneß <code lang=\"en\">./extensions</code> ald ze fenge.\n\nDo kann se heh un jez aanschallde, ävver se künnte noch zohsäzlesch Enshtellunge bruche.",
+ "config-install-alreadydone": "'''Opjepaß:'''\nEt sühd esu uß, wi wann De MediaWiki ald enshtalleet hätß, un wöhrs aam Versöhke, dat norr_ens ze donn.\nJang wigger op de näähßte Sigg.",
+ "config-install-begin": "Wann De op „{{int:config-continue}}“ klecks, jeiht de Enshtallazjuhn vum MediaWiki loßß.\nWann De noch Änderonge maache wells, dann kleck op „{{int:config-back}}“.",
+ "config-install-step-done": "jedonn",
+ "config-install-step-failed": "donävve jejange",
+ "config-install-extensions": "Zohsazprojramme enjeschloße",
+ "config-install-database": "Ben de Daatebangk aam ennreeschte.",
+ "config-install-schema": "Dat Schema en dä Daatebank weed aanjelaat.",
+ "config-install-pg-schema-not-exist": "Dat Scheema för <i lang=\"en\">PostgreSQL</i> es nit doh.",
+ "config-install-pg-schema-failed": "Et Tabälle-Opsäze es donävve jejange.\nDonn doför sorrje, dat dä Daatebangk-Aanwänder „$1“ en dämm Daatebangkscheema „$2“ schrieve kann.",
+ "config-install-pg-commit": "Ben de Änderonge aam ennbränge.",
+ "config-install-pg-plpgsql": "Ben noh dä Daatebangkshprooch <code lang=\"en\">PL/pgSQL</code> aam söhke.",
+ "config-pg-no-plpgsql": "Do moß de Daatebangkshprooch <code lang=\"en\">PL/pgSQL</code> en dä Daatebangk $1 enreeschte.",
+ "config-pg-no-create-privs": "Dä Daatebangk-Aanwänder för et Enreeschte hät nit jenooch Rääschde, öm ene andere Daatebangk-Aanwänder en dä Daatebangk aanzelääje.",
+ "config-pg-not-in-role": "Dä aanjejovve Zohjang för et Web jiddet ald.\nDä aanjejovve Zohjang för et Enschtalleere es keine <i lang=\"en\">superuser<i> un es nit en de Web-Jropp, dröm kam_mer domet kein Dateije aanlääje, di däm Zohjang för et Web jehüüre.\n\nFör MeedijaWiki mößße dämm ävver em Momang di Tabälle jehüüre.\nDröm donn ene andere Name för dä Zohjang zom Wäb nämme, udder donn „retuur“ klicke, un jivv ene Zohjang för et Enschtalleere aan, dä jenooch Rääschte hät.",
+ "config-install-user": "Ben unse Daatebangk-Aanwänder en de Daatebangk am aanlääje.",
+ "config-install-user-alreadyexists": "Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kann nit aanjelaat wääde, et jidd_en alld.",
+ "config-install-user-create-failed": "Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kunnt nit aanjelaat wääde, wäje: <code lang=\"en\">$2</code>",
+ "config-install-user-grant-failed": "Däm Daatebangk-Aanwänder sing Beräschtijunge ze säze däät nit fluppe wääje: $2",
+ "config-install-user-missing": "Dä aanjejovve Metmaacher „$1“ jidd_et nit.",
+ "config-install-user-missing-create": "{{int:Config-install-user-missing}}<!-- $1 -->\nDonn e Höhksche en et Käßje „{{int:Createaccount}}“ onge, wann De dä aanlääje wells.",
+ "config-install-tables": "Ben de Daatebangk-Tabälle aam aanlääje.",
+ "config-install-tables-exist": "'''Opjepaß''': Et schingk, dem MediaWiki sing Tabälle sin alt doh.\nDoh dom_mer nix aanlääje.",
+ "config-install-tables-failed": "'''Fähler''': De Tabälle kunnte nit aanjelaat wääde, wääje: $1",
+ "config-install-interwiki": "Ben de Engerwiki-Tabäll met de shtandattmääßejje Daate aam fölle.",
+ "config-install-interwiki-list": "Mer kunnte de Dattei <code lang=\"en\">interwiki.list</code> nit fenge.",
+ "config-install-interwiki-exists": "'''Opjepaß''': En der Engewiki-Tabäll schingk alt jät dren ze shtonn.\nDoh dom_mer nix dobei.",
+ "config-install-stats": "De Shtatestek-Zahle wääde op Aanfang jeshtallt.",
+ "config-install-keys": "Jeheime Schlößel wääde opjebout.",
+ "config-insecure-keys": "'''Opjepaß:''' {{PLURAL:$2|Ene jeheime Schlößel|Jeheim Schlößele|Keine jeheime Schlößel}} ($1) {{PLURAL:$2|es|sin|es}} automattesch aanjelaat woode. {{PLURAL:$2|Dä es|Di sin|Hä es}} ävver nit onbedengk janz sescher. Övverlääsch Der, {{PLURAL:$2|dä|di|en}} norr_ens vun Hand ze ändere.",
+ "config-install-sysop": "Dä Zohjang för der Wiki-Köbes weed aanjelaat.",
+ "config-install-subscribe-fail": "Mer künne de <i lang=\"en\">e-mail</i>-Leß <code lang=\"en\">mediawiki-announce</code> nit abonneere: $1",
+ "config-install-subscribe-notpossible": "<code lang=\"en\">cURL</code> es nit enstalleed un <code lang=\"en\">allow_url_fopen</code>es nit doh.",
+ "config-install-mainpage": "Ben de Houpsigg med enem shtandatmääßeje Enhald aam aanlääje",
+ "config-install-extension-tables": "Ben Datebangk-Tabälle för de Zohsazprojramme aam ennreschte",
+ "config-install-mainpage-failed": "Kunnt de Houpsigg nit afshpeishere: $1",
+ "config-install-done": "'''Jlöckwonsch!'''\nMediaWiki es jetz enstalleet.\n\nEt Projramm zom Enreeschte hät en Dattei <code lang=\"en\">LocalSettings.php</code> aanjelaat.\nDoh sin de Enstellunge vum Wiki dren.\n\nDo weeß se eronge laade möße un dann en dem Wiki sing Aanfangsverzeishnes donn möße, et sellve Verzeisneß, woh di Dattei <code lang=\"en\">index.php</code> dren litt. Dat Erongerlaade sullt automattesch aanjefange han.\n\nWann domet jet nit jeflupp hät, udder De di Dattei norr_ens han wells, donn op dä Lengk heh dronger klecke:\n\n$3\n\n'''Opjepaß''': Wann De dat jez nit deihß, es alles verschött, wat De bes jöz enjejovve häs, weil di Dattei fott es en däm Momang, woh heh dat Projamm aam Engk es.\n\nWann De mem Ronger- un widder Huhlaade fäädesh bes, kanns De '''[$2 en Ding Wiki jonn]'''.",
+ "config-download-localsettings": "Donn di Dattei <code lang=\"en\">LocalSettings.php</code> eronger laade",
+ "config-help": "Hölp",
+ "config-nofile": "De Dattei „$1“ ham_mer nit jefonge. Es di fottjeschmeße?",
+ "config-extension-link": "Häs De jewoß, dat et Wiki [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions Zohsazprojramme] hann kann?\n\nDo kanns [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Zohsazprojramme noh Saachjroppe] söhke udder en de [//www.mediawiki.org/wiki/Extension_Matrix Tabäll met de Zohsazprojramme] kike, öm de kumplätte Leß met de Zohsazprojramme ze krijje.",
+ "mainpagetext": "'''MediaWiki es jäz enschtalleht.'''",
+ "mainpagedocfooter": "Luur en et (änglesche) [//meta.wikimedia.org/wiki/Help:Contents Handbooch] wann De wesse wells wie de Wiki-Soffwär jebruch un bedeent wääde moß.\n\nLuur en et (änglesche) [//meta.wikimedia.org/wiki/Help:Contents Handbooch] wann De weße wells wi de Wiki-Soffwähr jebruch un bedehnt wääde moß.\n\n== För der Aanfang ==\nDat es och all op Änglesch:\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Donn MediaWiki op Ding Schprohch aanpaße]"
+}
diff --git a/includes/installer/i18n/ku-latn.json b/includes/installer/i18n/ku-latn.json
new file mode 100644
index 00000000..a08c83ac
--- /dev/null
+++ b/includes/installer/i18n/ku-latn.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "George Animal"
+ ]
+ },
+ "config-information": "Agahî",
+ "config-your-language": "Zimanê te:",
+ "config-page-language": "Ziman",
+ "config-page-name": "Nav",
+ "config-page-options": "Vebijêrk",
+ "config-ns-generic": "Proje",
+ "config-install-step-done": "çêbû",
+ "config-help": "alîkarî",
+ "mainpagetext": "'''MediaWiki serketî hate çêkirin.'''",
+ "mainpagedocfooter": "Alîkarî ji bo bikaranîn û guherandin yê datayê Wîkî tu di bin [//meta.wikimedia.org/wiki/Help:Contents pirtûka alîkarîyê ji bikarhêneran] da dikarê bibînê.\n\n== Alîkarî ji bo destpêkê ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lîsteya varîyablên konfîgûrasîyonê]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lîsteya e-nameyên versyonên nuh yê MediaWiki]"
+}
diff --git a/includes/installer/i18n/lad.json b/includes/installer/i18n/lad.json
new file mode 100644
index 00000000..d8708c49
--- /dev/null
+++ b/includes/installer/i18n/lad.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Universal Life",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MedyaViki ya se kureó con reuxitá.'''",
+ "mainpagedocfooter": "Konsulta la [//meta.wikimedia.org/wiki/Help:Contents/es Guía de usador] para tomar enformasyones encima de como usar el lojikal viki.\n\n== En Empeçando ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings La lista de los arreglamientos de la konfiggurasyón]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/lad DDS de MedyaViki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce La lista de las letrales (e-mail) de MedyaViki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/lb.json b/includes/installer/i18n/lb.json
new file mode 100644
index 00000000..bb1c8295
--- /dev/null
+++ b/includes/installer/i18n/lb.json
@@ -0,0 +1,212 @@
+{
+ "@metadata": {
+ "authors": [
+ "Robby",
+ "Soued031",
+ "아라"
+ ]
+ },
+ "config-desc": "Den Installatiounsprogramm vu MediaWiki",
+ "config-title": "MediaWiki $1 Installatioun",
+ "config-information": "Informatioun",
+ "config-localsettings-upgrade": "'''Opgepasst''': E Fichier <code>LocalSettings.php</code> gouf fonnt.\nÄr Software kann aktualiséiert ginn, setzt w.e.g. de Wäert vum <code>$wgUpgradeKey</code> an d'Këscht.\nDir fannt en am <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "E Fichier <code>LocalSettings.php</code> gouf fonnt.\nFir dës Installatioun z'aktuaéliséieren start w.e.g. <code>update.php</code>",
+ "config-localsettings-key": "Aktualisatiounsschlëssel:",
+ "config-localsettings-badkey": "De Schlëssel deen Dir aginn hutt ass net korrekt",
+ "config-localsettings-incomplete": "De Fichier <code>LocalSettings.php</code> schéngt net komplett ze sinn.\nD'Variabel $1 ass net definéiert.\nÄnnert w.e.g. de Fichier <code>LocalSettings.php</code> sou datt déi Variabel definéiert ass a klickt op \"{{int:Config-continue}}\".",
+ "config-session-error": "Feeler beim Starte vun der Sessioun: $1",
+ "config-no-session": "D'Donnéeë vun ärer Sessioun si verluergaangen!\nKuckt Är php.ini no a vergewëssert Iech datt <code>session.save_path</code> op adequate REpertoire agestallt ass.",
+ "config-your-language": "Är Sprooch",
+ "config-your-language-help": "Sicht déi Sprooch eraus déi Dir während der Installatioun benotze wëllt",
+ "config-wiki-language": "Sprooch vun der Wiki:",
+ "config-wiki-language-help": "Sicht d'Sprooch eraus an där d'Wiki haaptsächlech geschriwwe gëtt.",
+ "config-back": "← Zréck",
+ "config-continue": "Weider →",
+ "config-page-language": "Sprooch",
+ "config-page-welcome": "Wëllkomm bei MediaWiki!",
+ "config-page-dbconnect": "Mat der Datebank verbannen",
+ "config-page-upgrade": "Eng Installatioun déi besteet aktualiséieren",
+ "config-page-dbsettings": "Astellunge vun der Datebank",
+ "config-page-name": "Numm",
+ "config-page-options": "Optiounen",
+ "config-page-install": "Installéieren",
+ "config-page-complete": "Fäerdeg!",
+ "config-page-restart": "Installatioun neistarten",
+ "config-page-readme": "Liest dëst",
+ "config-page-releasenotes": "Informatiounen zur Versioun",
+ "config-page-copying": "Kopéieren",
+ "config-page-upgradedoc": "Aktualiséieren",
+ "config-page-existingwiki": "Wiki déi et gëtt",
+ "config-help-restart": "Wëllt dir all gespäichert Donnéeë läschen déi dir bis elo aginn hutt an den Installatiounsprozess nei starten?",
+ "config-restart": "Jo, neistarten",
+ "config-welcome": "=== Iwwerpréifung vum Installatiounsenvironnement ===\nEt gi grondsätzlech Iwwerpréifunge gemaach fir ze kucken ob den Environnment gëeegent ass fir MediaWiki z'installéieren.\nDir sollt d'Resultater vun dëser Iwwerpréifung ugi wann Dir während der Installatioun Hëllef frot wéi Dir D'Installatioun ofschléisse kënnt.",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki Haaptsäit]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Benotzerguide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guide fir Administrateuren]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Liest dëst</doclink>\n* <doclink href=ReleaseNotes>Informatioune vun der aktueller Versioun</doclink>\n* <doclink href=Copying>Lizenzbedingungen</doclink>\n* <doclink href=UpgradeDoc>Aktualisatioun</doclink>",
+ "config-env-good": "Den Environement gouf nogekuckt.\nDir kënnt MediaWiki installéieren.",
+ "config-env-bad": "Den Environnement gouf iwwerpréift.\nDir kënnt MediWiki net installéieren.",
+ "config-env-php": "PHP $1 ass installéiert.",
+ "config-env-hhvm": "HHVM $1 ass installéiert.",
+ "config-unicode-using-utf8": "Fir d'Unicode-Normalisatioun gëtt dem Brion Vibber säin <code>utf8_normalize.so</code> benotzt.",
+ "config-no-db": "Et konnt kee passenden Datebank-Driver fonnt ginn! Dir musst een Datebank-Driver fir PHP installéieren.\nDë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-mysql</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-iconv": "<strong>Fatal:</strong> PHP muss mat Support fir d'[http://www.php.net/manual/en/iconv.installation.php iconv-Erweiderung] kompiléiert ginn.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] ass installéiert",
+ "config-apc": "[http://www.php.net/apc APC] ass installéiert",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp 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>.",
+ "config-git-bad": "D'Software fir d'Kontroll vun de Versiounen 'Git' gouf net fonnt.",
+ "config-no-uri": "'''Feeler:''' Déi aktuell URI konnt net festgestallt ginn.\nInstallatioun ofgebrach.",
+ "config-using-server": "De Servernumm \"<nowiki>$1</nowiki>\" gëtt benotzt.",
+ "config-using-uri": "D'Server URL \"<nowiki>$1$2</nowiki>\" gëtt benotzt.",
+ "config-db-type": "Datebanktyp:",
+ "config-db-host": "Host vun der Datebank:",
+ "config-db-host-oracle": "Datebank-TNS:",
+ "config-db-wiki-settings": "Dës Wiki identifizéieren",
+ "config-db-name": "Numm vun der Datebank:",
+ "config-db-name-oracle": "Datebankschema:",
+ "config-db-install-account": "Benotzerkont fir d'Installatioun",
+ "config-db-username": "Datebank-Benotzernumm:",
+ "config-db-password": "Passwuert vun der Datebank:",
+ "config-db-username-empty": "Dir musst e Wäert fir \"{{int:config-db-username}}\" aginn",
+ "config-db-install-help": "Gitt de Benotzernumm an Passwuert an dat wàhrend der Installatioun benotzt gëtt fir sech mat der Datebank ze verbannen.",
+ "config-db-account-lock": "De selwechte Benotzernumm a Passwuert fir déi normal Operatioune benotzen",
+ "config-db-wiki-account": "Benotzerkont fir normal Operatiounen",
+ "config-db-wiki-help": "Gitt de Benotzernumm an d'Passwuert an dat benotzt wäert gi fir sech bei den normale Wiki-Operatiounen mat der Datebank ze connectéieren.\nWann et de Kont net gëtt, a wann den Installatiouns-Kont genuch Rechter huet, gëtt dëse Benotzerkont opgemaach mat dem Minimum vu Rechter déi gebraucht gi fir dës Wiki bedreiwen ze kënnen.",
+ "config-db-charset": "Zeechesaz (character set) vun der Datebank",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binair",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-mysql-old": "MySQL $1 oder eng méi nei Versioun gëtt gebraucht, Dir hutt $2.",
+ "config-db-port": "Port vun der Datebank:",
+ "config-db-schema": "Schema fir MediaWiki",
+ "config-db-schema-help": "D'Schemaen hei driwwer si gewéinlech korrekt.\nÄnnert se nëmme wann Dir wësst datt et néideg ass.",
+ "config-pg-test-error": "Et ass net méiglech d'Datebank '''$1''' ze kontaktéieren: $2",
+ "config-sqlite-dir": "Repertoire vun den SQLite-Donnéeën",
+ "config-oracle-def-ts": "Standard 'tablespace':",
+ "config-oracle-temp-ts": "Temporären 'tablespace':",
+ "config-type-mysql": "MySQL (oder kompatibel)",
+ "config-type-postgres": "PostgreSQL",
+ "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. Et gëtt awer e puer kleng Implementatiounsfeeler, dofir gëtt vun der Notzung an engem Produktivsystem ofgeroden. ([http://www.php.net/manual/de/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. ([http://www.php.net/manual/en/sqlsrv.installation.php Wéi PHP mat SQLSRV Ënnerstëtzung kompiléieren])",
+ "config-header-mysql": "MySQL-Astellungen",
+ "config-header-postgres": "PostgreSQL-Astellungen",
+ "config-header-sqlite": "SQLite-Astellungen",
+ "config-header-oracle": "Oracle-Astellungen",
+ "config-header-mssql": "Microsoft SQL Server Astellungen",
+ "config-invalid-db-type": "Net valabelen Datebank-Typ",
+ "config-missing-db-name": "Dir musst e Wäert fir \"{{int:config-db-name}}\" aginn",
+ "config-missing-db-host": "Dir musst e Wäert fir \"{{int:config-db-host}}\" aginn.",
+ "config-missing-db-server-oracle": "Dir musst e Wäert fir \"{{int:config-db-host-oracle}}\" aginn",
+ "config-connection-error": "$1.\n\nKuckt den Numm vum Server, de Benotzernumm an d'Passwuert no a probéiert et nach eng Kéier.",
+ "config-db-sys-user-exists-oracle": "De Benotzerkont \"$1\" gëtt et schonn. SYSDBA kann nëmme benotzt gi fir en neie Benotzerkont opzemaachen.",
+ "config-postgres-old": "PostgreSQL $1 oder eng méi nei Versioun gëtt gebraucht, Dir hutt $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 oder eng méi rezent Versioun gëtt gebraucht. Dir hutt d'Versioun $2.",
+ "config-sqlite-name-help": "Sicht en Numm deen Är wiki identifizéiert.\nBenotzt keng Espacen a Bindestrécher.\nE gëtt fir den Numm vum SQLite Date-Fichier benotzt.",
+ "config-sqlite-readonly": "An de Fichier <code>$1</code> Kann net geschriwwe ginn.",
+ "config-sqlite-cant-create-db": "Den Datebank-Fichier <code>$1</code> konnt net ugeluecht ginn.",
+ "config-can-upgrade": "Et si MediaWiki Tabellen an dëser Datebank.\nFir se op MediaWiki $1 z'aktualiséiere klickt op <strong>Virufueren</strong>.",
+ "config-upgrade-done-no-regenerate": "D'Aktualisatioun ass ofgeschloss.\n\nDir kënnt elo [$1 ufänken Är Wiki ze benotzen]",
+ "config-regenerate": "LocalSettings.php regeneréieren →",
+ "config-db-web-account": "Datebankkont fir den Accès iwwer de Web",
+ "config-db-web-account-same": "Dee selwechte Kont wéi bei der Installatioun benotzen",
+ "config-db-web-create": "De Kont uleeë wann et e net scho gëtt",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "binär",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Typ vun der Authentifikatioun:",
+ "config-mssql-sqlauth": "SOL-Server-Authentifikatioun",
+ "config-mssql-windowsauth": "Windows-Authentifikatioun",
+ "config-site-name": "Numm vun der Wiki:",
+ "config-site-name-help": "Dësen daucht an der Titelleescht vum Browser an op verschiddenen anere Plazen op.",
+ "config-site-name-blank": "Gitt den Numm vum Site un.",
+ "config-project-namespace": "Projet Nummraum:",
+ "config-ns-generic": "Projet",
+ "config-ns-site-name": "Deeselwechte wéi den Numm vun der Wiki: $1",
+ "config-ns-other": "Anerer (spezifizéieren)",
+ "config-ns-other-default": "MyWiki",
+ "config-admin-box": "Administrateurs-Kont",
+ "config-admin-name": "Äre Benotzernumm:",
+ "config-admin-password": "Passwuert:",
+ "config-admin-password-confirm": "Passwuert confirméieren:",
+ "config-admin-help": "Gitt w.e.g. Äre gewënschte Benotzernumm hei an, zum Beispill \"Jang Muller\".\nDësen Numm gëtt da gebraucht fir sech an d'Wiki anzeloggen.",
+ "config-admin-name-blank": "Gitt e Benotzernumm fir den Administrateur an.",
+ "config-admin-name-invalid": "De spezifizéierte Benotzernumm \"<nowiki>$1</nowiki>\" ass net valabel.\nSpezifizéiert en anere Benotzernumm.",
+ "config-admin-password-blank": "Gitt e Passwuert fir den Adminstateur-Kont an.",
+ "config-admin-password-mismatch": "Déi zwee Passwierder Déi Dir aginn hutt stëmmen net iwwereneen.",
+ "config-admin-email": "E-Mail-Adress:",
+ "config-admin-error-user": "Interne Feeler beim uleeë vun engem Administrateur mam Numm \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Interne Feeler beim Setze vum Passwuert fir den Admin \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Dir hutt eng E-Mail-Adress aginn déi net valabel ass",
+ "config-subscribe": "Sech op d'[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ukënnegunge vun neie Versiounen] abonnéieren.",
+ "config-almost-done": "Dir sidd bal fäerdeg!\nDir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki elo direkt installéieren.",
+ "config-optional-continue": "Stellt mir méi Froen.",
+ "config-optional-skip": "Ech hunn es genuch, installéier just d'Wiki.",
+ "config-profile": "Profil vun de Benotzerrechter:",
+ "config-profile-wiki": "Oppe Wiki",
+ "config-profile-no-anon": "Uleeë vun engem Benotzerkont verlaangt",
+ "config-profile-fishbowl": "Nëmmen autoriséiert Editeuren",
+ "config-profile-private": "Privat Wiki",
+ "config-license": "Copyright a Lizenz:",
+ "config-license-none": "Keng Lizenz ënnen op der Säit",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-gfdl": "GNU-Lizenz fir Fräi Dokumentatioun 1.3 oder méi rezent",
+ "config-license-pd": "Ëffentlechen Domaine",
+ "config-license-cc-choose": "Eng personaliséiert Creative Common Lizenz eraussichen",
+ "config-email-settings": "E-Mail-Astellungen",
+ "config-enable-email": "E-Mailen déi no bausse ginn aschalten",
+ "config-email-user": "Benotzer-op-Benotzer E-Mail aschalten",
+ "config-email-usertalk": "Benoriichtege bei Ännerung vun der Benotzerdiskussiounssäit aschalten",
+ "config-email-watchlist": "Benoriichtigung vun der Iwwerwaachungslëscht aschalten",
+ "config-email-watchlist-help": "Erlaabt et de Benotzer fir Notifikatioune vun hiren iwwerwaachte Säiten ze kréie wa si dat an hiren Astellungen aktivéiert hunn.",
+ "config-email-auth": "E-Mail-Authentifizéierung aschalten",
+ "config-email-sender": "E-Mailadress fir Äntwerten:",
+ "config-upload-settings": "Eropgeluede Biller a Fichieren",
+ "config-upload-enable": "Eropluede vu Fichieren aschalten",
+ "config-upload-deleted": "Repertoire fir geläscht Fichieren:",
+ "config-logo": "URL vum Logo:",
+ "config-instantcommons": "\"Instant Commons\" aktivéieren",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] ass eng Funktioun déi et Wikien erlaabt fir Biller, Téin an aner Medien vu [//commons.wikimedia.org/ Wikimedia Commons] ze benotzen.\nFir datt dat funktionéiert brauch MediaWiki Zougang zum Internet.\n\nFir méi Informatiounen iwwer dës Funktioun, inklusiv Instruktioune wéi Dir se fir aner Wikie wéi Wikimedia Commons astelle musst, kuckt [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos d'Handbuch].",
+ "config-cc-again": "Nach eng kéier eraussichen...",
+ "config-advanced-settings": "Erweidert Astellungen",
+ "config-extensions": "Erweiderungen",
+ "config-skins": "Ausgesinn",
+ "config-skins-help": "D'Ausgesinn déi hei driwwer stinn goufen am Repertoire <code>./skins</code> fonnt. Dir musst mindestens eent aktivéieren an de Standard eraussichen.",
+ "config-skins-use-as-default": "Dëst Ausgesinn als Standard benotzen",
+ "config-skins-missing": "Et goufe keen Ausgesinn (Skin) fonnt; MediaWiki benotzt e Fallback-Ausgesinnbis Dir anerer installéiert.",
+ "config-skins-must-enable-some": "Dir musst mindestens een Ausgesinn fir z'aktivéieren eraussichen.",
+ "config-skins-must-enable-default": "Dat als Standard erausgesichten Ausgesinn muss aktivéiert sinn.",
+ "config-install-step-done": "fäerdeg",
+ "config-install-step-failed": "huet net funktionéiert",
+ "config-install-extensions": "Mat den Ereiderungen",
+ "config-install-database": "Datebank gëtt installéiert",
+ "config-install-pg-commit": "Ännerungen applizéieren",
+ "config-install-pg-plpgsql": "No der Sprooch PL/pgSQL sichen",
+ "config-pg-no-plpgsql": "Fir d'Datebank $1 muss d'Datebanksprooch PL/pgSQL installéiert ginn",
+ "config-install-user": "Datebank Benotzer uleeën",
+ "config-install-user-alreadyexists": "De Benotzer \"$1\" gëtt et schonn!",
+ "config-install-user-create-failed": "D'Opmaache vum Benotzer \"$1\" huet net funktionéiert: $2",
+ "config-install-user-grant-failed": "D'Bäisetze vu Rechter fir de Benotzer \"$1\" huet net funktionéiert: $2",
+ "config-install-user-missing": "De Benotzer \"$1\" deen ugi gouf gëtt et net.",
+ "config-install-user-missing-create": "De spezifizéierte Benotzer \"$1\" gëtt et net.\nKlickt d'Checkbox \"Benotzerkont uleeën\" wann Dir dee Benotzer uleeë wëllt.",
+ "config-install-tables": "Tabelle ginn ugeluecht",
+ "config-install-interwiki": "Standard Interwiki-Tabell gëtt ausgefëllt",
+ "config-install-interwiki-list": "De Fichier <code>interwiki.list</code> gouf net fonnt.",
+ "config-install-stats": "Initialisatioun vun de Statistiken",
+ "config-install-keys": "Generéiere vum Geheimschlëssel",
+ "config-install-updates": "Net néideg Aktualiséierungen net maachen",
+ "config-install-sysop": "Administrateur Benotzerkont gëtt ugeluecht",
+ "config-install-mainpage": "Haaptsäit mat Standard-Inhalt gëtt ugeluecht",
+ "config-install-extension-tables": "D'Tabelle fir déi aktivéiert Erweiderunge ginn ugeluecht",
+ "config-install-mainpage-failed": "D'Haaptsäit konnt net dragesat ginn: $1",
+ "config-download-localsettings": "<code>LocalSettings.php</code> eroflueden",
+ "config-help": "Hëllef",
+ "config-help-tooltip": "klickt fir opzeklappen",
+ "config-nofile": "De Fichier \"$1\" gouf net fonnt. Gouf e geläscht?",
+ "mainpagetext": "<strong>MediaWiki gouf installéiert.</strong>",
+ "mainpagedocfooter": "Kuckt w.e.g. [//meta.wikimedia.org/wiki/Help:Contents d'Benotzerhandbuch] fir Informatiounen iwwer de Gebruach vun der Wiki Software.\n\n== Fir unzefänken ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Hëllef bei der Konfiguratioun]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglëscht vun neie MediaWiki-Versiounen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokaliséiert MediaWiki fir Är Sprooch]"
+}
diff --git a/includes/installer/i18n/lez.json b/includes/installer/i18n/lez.json
new file mode 100644
index 00000000..953a7df0
--- /dev/null
+++ b/includes/installer/i18n/lez.json
@@ -0,0 +1,27 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lezgia"
+ ]
+ },
+ "config-desc": "Инстайлер MediaWiki-диз",
+ "config-title": "Эцигун $1 MediaWiki",
+ "config-information": "Хабар",
+ "config-localsettings-key": "Цийиладин кюлег",
+ "config-localsettings-badkey": "Кюне тъунвай кюлег чlурудия",
+ "config-session-error": "Гъалатl ахъагъдала сессиа",
+ "config-your-language": "Кю чlал",
+ "config-your-language-help": "Хкягъа чlaл, эцигунин юзун тхудай",
+ "config-wiki-language": "Wiki-дин чlaл",
+ "config-wiki-language-help": "Хкягъа чlал викидин акунар къалурдай",
+ "config-back": "Кьулухъ",
+ "config-continue": "Яргъахъ",
+ "config-page-language": "Чlал",
+ "config-page-welcome": "Хийирдиз ша MediaWiki-диз!",
+ "config-page-name": "Тlор",
+ "config-page-options": "Тькlюрнар",
+ "config-page-install": "Эцигун",
+ "config-page-complete": "Гьазур я",
+ "config-page-restart": "Гатlунин эцигун сифтедла",
+ "config-page-readme": "Кlела зу"
+}
diff --git a/includes/installer/i18n/lfn.json b/includes/installer/i18n/lfn.json
new file mode 100644
index 00000000..b4ed71ba
--- /dev/null
+++ b/includes/installer/i18n/lfn.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki es aora instalada.'''",
+ "mainpagedocfooter": "Atenda la [//meta.wikimedia.org/wiki/Help:Contents Gida per Usores] per informa supra la usa de la programa de vici.\n\n== Comensa ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de ajustas de la desinia]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Demandas comun de MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista per receta anunsias de novas supra MediaWiki]"
+}
diff --git a/includes/installer/i18n/lg.json b/includes/installer/i18n/lg.json
new file mode 100644
index 00000000..27b43895
--- /dev/null
+++ b/includes/installer/i18n/lg.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kizito"
+ ]
+ },
+ "mainpagetext": "MediaWiki kati ewangidwa ku sisitemu yo",
+ "mainpagedocfooter": "Okuyiga ku nkozesa ya sofutiweya owa wiki, kebera [//meta.wikimedia.org/wiki/Help:Contents Okulagirira Abakozesa].\n\n== Amagezi agakuyamba okutandika ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lukalala lw'eby'enteekateeka yo]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Ebiter'okubuuzibwa ku MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Wewandise ofunenga amawulire aga email ag'ebifa ku MediaWiki]"
+}
diff --git a/includes/installer/i18n/li.json b/includes/installer/i18n/li.json
new file mode 100644
index 00000000..ab9c0b83
--- /dev/null
+++ b/includes/installer/i18n/li.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki software succesvol geïnsjtalleerd.'''",
+ "mainpagedocfooter": "Raodpleeg de [//meta.wikimedia.org/wiki/Help:Contents Inhoudsopgave handjleiding] veur informatie euver 't gebroek van de wikisoftware.\n\n== Mieë hölp ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lies mit instellinge]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki VGV (FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki mailinglies veur nuuj versies]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/lo.json b/includes/installer/i18n/lo.json
new file mode 100644
index 00000000..9d964449
--- /dev/null
+++ b/includes/installer/i18n/lo.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''ຕິດຕັ້ງມີເດຍວິກິນີ້ສຳເລັດແລ້ວ.'''"
+}
diff --git a/includes/installer/i18n/lrc.json b/includes/installer/i18n/lrc.json
new file mode 100644
index 00000000..607fc404
--- /dev/null
+++ b/includes/installer/i18n/lrc.json
@@ -0,0 +1,24 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bonevarluri",
+ "Mogoeilor"
+ ]
+ },
+ "config-information": "دونسمنيا",
+ "config-your-language": "زون شما:",
+ "config-wiki-language": "زون ویکی:",
+ "config-page-language": "زون",
+ "config-page-welcome": "د ویکی رسانه خوش اومایت!",
+ "config-page-name": "نوم",
+ "config-page-options": "گزينه يا هنی:",
+ "config-page-install": "پورنیئن",
+ "config-page-complete": "تموم بيه!",
+ "config-page-readme": "منه بحون",
+ "config-page-copying": "د حال ورداشتن",
+ "config-page-upgradedoc": "د حالت نو کردن",
+ "config-page-existingwiki": "ویکی یایی که هئن",
+ "config-restart": "هری، دواره رئش بون",
+ "config-env-php": "پی اچ پی $1 پورسته.",
+ "config-install-pg-plpgsql": "وارسی سی زون پی ال/پی جی اس کیو ال"
+}
diff --git a/includes/installer/i18n/lt.json b/includes/installer/i18n/lt.json
new file mode 100644
index 00000000..f7e4e677
--- /dev/null
+++ b/includes/installer/i18n/lt.json
@@ -0,0 +1,89 @@
+{
+ "@metadata": {
+ "authors": [
+ "Eitvys200",
+ "Mantak111"
+ ]
+ },
+ "config-desc": "MediaWiki diegimas",
+ "config-title": "MediaWiki $1 diegimas",
+ "config-information": "Informacija",
+ "config-localsettings-key": "Naujinimo raktas:",
+ "config-localsettings-badkey": "Raktą, kurį pateikėte yra neteisingas.",
+ "config-your-language": "Jūsų kalba:",
+ "config-wiki-language": "Viki kalba:",
+ "config-back": "← Atgal",
+ "config-continue": "Toliau →",
+ "config-page-language": "Kalba",
+ "config-page-welcome": "Sveiki atvykę į MediaWiki!",
+ "config-page-dbconnect": "Prisijungti prie duomenų bazės",
+ "config-page-dbsettings": "Duomenų bazės nustatymai",
+ "config-page-name": "Vardas",
+ "config-page-options": "Parinktys",
+ "config-page-install": "Įdiegti",
+ "config-page-complete": "Baigta!",
+ "config-page-restart": "Iš naujo paleiskite diegimą",
+ "config-page-readme": "Perskaityk manę",
+ "config-page-copying": "Kopijuojama",
+ "config-page-upgradedoc": "Atnaujinama",
+ "config-page-existingwiki": "Esamas wiki",
+ "config-restart": "Taip, paleiskite jį iš naujo",
+ "config-env-php": "PHP $1 yra įdiegtas.",
+ "config-env-php-toolow": "PHP $1 įdiegta.\nTačiau, MediaWiki reikia PHP $2 ar naujesnės.",
+ "config-db-type": "Duomenų bazės tipas:",
+ "config-db-host": "Duomenų bazės serveris:",
+ "config-db-name": "Duomenų bazės pavadinimas:",
+ "config-db-name-oracle": "Duomenų bazės schema:",
+ "config-db-install-account": "Vartotojo paskyra diegimui",
+ "config-db-username": "Duomenų bazės vartotojo vardas:",
+ "config-db-password": "Duomenų bazės slaptažodis:",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-db-port": "Duomenų bazės prievadas:",
+ "config-db-schema": "MediaWiki schema:",
+ "config-type-mssql": "Microsoft SQL serveris",
+ "config-header-mysql": "MySQL nustatymai",
+ "config-header-postgres": "PostgreSQL nustatymai",
+ "config-header-sqlite": "SQLite nustatymai",
+ "config-header-oracle": "Oracle nustatymai",
+ "config-invalid-db-type": "Neteisingas duomenų bazės tipas",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-windowsauth": "Windows autentifikavimas",
+ "config-site-name": "Viki pavadinimas:",
+ "config-site-name-blank": "Įveskite svetainės pavadinimą.",
+ "config-project-namespace": "Projekto pavadinimas:",
+ "config-ns-generic": "Projektas",
+ "config-ns-site-name": "Toks pat kaip viki pavadinimas: $1",
+ "config-ns-other-default": "ManoWiki",
+ "config-admin-box": "Administratoriaus paskyra",
+ "config-admin-name": "Jūsų vardas:",
+ "config-admin-password": "Slaptažodis:",
+ "config-admin-password-confirm": "Slaptažodis dar kartą:",
+ "config-admin-name-blank": "Įveskite administratoriaus vartotojo vardą.",
+ "config-admin-password-blank": "Įvesti administratoriaus paskyros slaptažodį.",
+ "config-admin-password-mismatch": "Įvesti slaptažodžiai nesutampa.",
+ "config-admin-email": "El. pašto adresas:",
+ "config-optional-continue": "Paklausti daugiau klausimų.",
+ "config-optional-skip": "Man jau nuobodu, tiesiog įdiekite viki.",
+ "config-profile": "Vartotojo teisių paskyra:",
+ "config-profile-wiki": "Tradicinė viki",
+ "config-profile-private": "Privati viki",
+ "config-license-pd": "Viešas Domenas",
+ "config-email-settings": "El. pašto nustatymai",
+ "config-upload-enable": "Įgalinti failų įkėlimus",
+ "config-logo": "Logotipo URL:",
+ "config-cc-again": "Pasirinkti dar kartą...",
+ "config-extensions": "Plėtiniai",
+ "config-install-step-done": "atlikta",
+ "config-install-step-failed": "nepavyko",
+ "config-install-schema": "Kuriama schema",
+ "config-install-tables": "Kuriamos lentelės",
+ "config-install-stats": "Inicijuojamos statistikos",
+ "config-install-keys": "Generuojami slapti raktai",
+ "config-install-done": "'''Sveikiname!'''\nJūs sėkmingai įdiegėte MediaWiki.\n\nĮdiegimo programa sukūrė <code>LocalSettings.php</code> failą.\nJame yra visos jūsų konfigūracijos.\n\nJums reikės atsisiųsti ir įdėti jį į savo wiki įdiegimo bazę (pačiame kataloge, kaip index.php). Atsisiuntimas turėtų prasidėti automatiškai.\n\nJei atsisiuntimas nebuvo pasiūlytas, arba jį atšaukėte, galite iš naujo atsisiųsti paspaudę žemiau esančią nuorodą:\n\n$3\n\n'''Pastaba:''' Jei jūs to nepadarysite dabar, tada šis sukurtas konfigūracijos failas nebus galimas vėliau, jei išeisite iš įdiegimo be atsisiuntimo.\n\nKai baigsite, jūs galėsite '''[$2 įeiti į savo wiki]'''.",
+ "config-download-localsettings": "Atsisiųsti <code>LocalSettings.php</code>",
+ "config-help": "pagalba",
+ "mainpagetext": "'''MediaWiki sėkmingai įdiegta.'''",
+ "mainpagedocfooter": "Informacijos apie wiki programinės įrangos naudojimą, ieškokite [//meta.wikimedia.org/wiki/Help:Contents žinyne].\n\n== Pradžiai ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Konfigūracijos nustatymų sąrašas]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki DUK]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki pranešimai paštu apie naujas versijas]"
+}
diff --git a/includes/installer/i18n/lv.json b/includes/installer/i18n/lv.json
new file mode 100644
index 00000000..eb66bbb6
--- /dev/null
+++ b/includes/installer/i18n/lv.json
@@ -0,0 +1,45 @@
+{
+ "@metadata": {
+ "authors": [
+ "GreenZeb",
+ "Papuass"
+ ]
+ },
+ "config-information": "Informācija",
+ "config-your-language": "Jūsu valoda:",
+ "config-wiki-language": "Wiki valoda:",
+ "config-back": "← Atpakaļ",
+ "config-continue": "Turpināt →",
+ "config-page-language": "Valoda",
+ "config-page-welcome": "Laipni lūdzam MediaWiki!",
+ "config-page-dbconnect": "Savienoties ar datubāzi",
+ "config-page-upgrade": "Atjaunināt pašreizējo instalāciju",
+ "config-page-dbsettings": "Datubāzes iestatījumi",
+ "config-page-name": "Vārds",
+ "config-page-options": "Iespējas",
+ "config-page-install": "Instalēt",
+ "config-page-complete": "Pabeigts!",
+ "config-page-restart": "Pārstartēt instalāciju",
+ "config-page-readme": "Lasīt mani",
+ "config-page-releasenotes": "Informācija par laidienu",
+ "config-page-copying": "Kopē",
+ "config-env-php": "PHP $1 ir uzstādīts.",
+ "config-diff3-bad": "GNU diff3 nav atrasts.",
+ "config-db-charset": "Datubāzes rakstzīmju kopa",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binārs",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 atpakaļsaderīgs UTF-8",
+ "config-db-port": "Datubāzes ports:",
+ "config-db-schema": "MediaWiki shēma:",
+ "config-db-schema-help": "Šī shēma derēs vairumā gadījumu.\nMainiet to tikai, ja zināt, ka tas nepieciešams.",
+ "config-type-mysql": "MySQL (vai saderīga)",
+ "config-header-mysql": "MySQL iestatījumi",
+ "config-header-postgres": "PostgreSQL iestatījumi",
+ "config-header-sqlite": "SQLite iestatījumi",
+ "config-header-oracle": "Oracle iestatījumi",
+ "config-header-mssql": "Microsoft SQL servera iestatījumi",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "mainpagetext": "'''MediaWiki veiksmīgi ieinstalēts'''",
+ "mainpagedocfooter": "Izlasi [//meta.wikimedia.org/wiki/Help:Contents Lietotāja pamācību], lai iegūtu vairāk informācijas par Wiki programmatūras lietošanu.\n\n== Pirmie soļi ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Konfigurācijas iespēju saraksts]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki J&A]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Parakstīties uz paziņojumiem par jaunām MediaWiki versijām]"
+}
diff --git a/includes/installer/i18n/lzh.json b/includes/installer/i18n/lzh.json
new file mode 100644
index 00000000..190ee047
--- /dev/null
+++ b/includes/installer/i18n/lzh.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jason924tw"
+ ]
+ },
+ "config-information": "文訊",
+ "mainpagetext": "'''共筆臺已立'''",
+ "mainpagedocfooter": "欲識維基,見[//meta.wikimedia.org/wiki/Help:Contents User's Guide]\n\n== 始 ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/lzz.json b/includes/installer/i18n/lzz.json
new file mode 100644
index 00000000..9de6ff91
--- /dev/null
+++ b/includes/installer/i18n/lzz.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bombola"
+ ]
+ },
+ "mainpagetext": "'''Mediawiki dido k'ai ik'idu.'''",
+ "mainpagedocfooter": "Vik'i şeni muç'o ixmarinen ya mutxanepe oguru şeni [//meta.wikimedia.org/wiki/Help:Contents oxmaruşi rexberis] o3'k'edit.\n\n== Ağani na gyoç’k’u maxmarepe ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Ok'iduşi ayarepeşi liste]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki P'anda Na-k'itxu K'itxalape]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-mailepeşiş liste]"
+}
diff --git a/includes/installer/i18n/mai.json b/includes/installer/i18n/mai.json
new file mode 100644
index 00000000..b52cef79
--- /dev/null
+++ b/includes/installer/i18n/mai.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Umeshberma"
+ ]
+ },
+ "mainpagetext": "'''मीडियाविकी नीक जकाँ प्रस्थापित भेल।'''",
+ "mainpagedocfooter": "सम्पर्क करू [//meta.wikimedia.org/wiki/Help:Contents User's Guide] विकी तंत्रांशक प्रयोगक जानकारी लेल।\n\n==प्रारम्भ कोना करी==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/mdf.json b/includes/installer/i18n/mdf.json
new file mode 100644
index 00000000..c6f40671
--- /dev/null
+++ b/includes/installer/i18n/mdf.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''МедиаВикить арафтозь лац.'''",
+ "mainpagedocfooter": "Ванк [//meta.wikimedia.org/wiki/Help:Contents Ветямовал Тиинди] тяса ули кода содамс Вики програпнень эрявикснень колга.\n\n== Эрявикс сюлмафксне ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Васьфневи арафнематнень кярькссь]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ МедиаВикить Сидеста Кеподеви Кизефксне]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаВикить од верзиятнень колга кулянь пачфтема]"
+}
diff --git a/includes/installer/i18n/mg.json b/includes/installer/i18n/mg.json
new file mode 100644
index 00000000..53324c6c
--- /dev/null
+++ b/includes/installer/i18n/mg.json
@@ -0,0 +1,65 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jagwar",
+ "Seb35"
+ ]
+ },
+ "config-desc": "Fandaharana mametraka an'i MediaWiki",
+ "config-title": "Fametrahana an'i MediaWiki $1",
+ "config-information": "Fampahalalana",
+ "config-localsettings-upgrade": "Hita ny <code>LocalSettings.php</code>.\nMba hanavao ity fametrahana ity, atsofohy ny sandan'i <code>$wgUpgradeKey</code> amin'ny saha eo ambany.\nHo hitanao eo amin'i <code>LocalSettings.php</code> ilay izy.",
+ "config-localsettings-key": "Lakile fanavaozana:",
+ "config-localsettings-badkey": "Diso ilay lakile fanavaozana natsofokao.",
+ "config-session-error": "Hadisoana teo am-panombohana ny fidirana : $1",
+ "config-your-language": "Ny fiteninao :",
+ "config-wiki-language": "Fiteny ho ampiasain'ny wiki :",
+ "config-back": "← Miverina",
+ "config-continue": "Manohy →",
+ "config-page-language": "Fiteny",
+ "config-page-welcome": "Tonga soa eto amin'i MediaWiki !",
+ "config-page-dbconnect": "Hiditra eo amin'i banky angona",
+ "config-page-name": "Anarana",
+ "config-page-options": "Safidy",
+ "config-page-install": "Apetraka",
+ "config-page-complete": "Tapitra!",
+ "config-page-restart": "Hamerina ny fametrahana",
+ "config-page-readme": "Vakio aho",
+ "config-page-releasenotes": "Resaka mikasika ilay versiona",
+ "config-page-copying": "Hala-tahaka",
+ "config-page-upgradedoc": "Fanavaozina",
+ "config-page-existingwiki": "Wiki efa misy",
+ "config-help-restart": "Tianao hofafana avokoa ve ny data voaangona natsofokao ary hamerina ny fizotran'ny fametrahana ?",
+ "config-restart": "Eny, avereno atao",
+ "config-db-username": "Anaram-pikamban'ny banky angona :",
+ "config-db-password": "Tenimiafin'ny banky angona :",
+ "config-header-mysql": "Parametatr'i MySQL",
+ "config-header-sqlite": "Parametatr'i SQLite",
+ "config-header-oracle": "Parametatr'i Oracle",
+ "config-mysql-innodb": "innoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-ns-generic": "Tetikasa",
+ "config-ns-other": "Hafa (lazao)",
+ "config-admin-name": "Ny anaranao :",
+ "config-admin-password": "Tenimiafina :",
+ "config-admin-email": "Adiresy imailaka :",
+ "config-profile-wiki": "Wiki tsotra",
+ "config-profile-no-anon": "Mila mamorona kaonty",
+ "config-profile-fishbowl": "Mpanova mahazo alalana ihany",
+ "config-profile-private": "Wiki tsy sarababem-bahoaka",
+ "config-license": "Zom-pamorona ary lisansa :",
+ "config-license-none": "Tsy misy lisansa any an-tongom-pejy",
+ "config-email-user": "Avela mifandefa imailaka ny mpikambana",
+ "config-email-user-help": "Hahafahan'ny mpikambana mifandefa imailaka raha omen'ny mpikambana alalana ao amin'ny safidiny.",
+ "config-upload-deleted": "Petra-drakitra ho an'ny rakitra voafafa :",
+ "config-extensions": "Fanitarana",
+ "config-install-step-done": "vita",
+ "config-install-step-failed": "hadisoana",
+ "config-install-user": "Famoronana mpapiasan'ny banky angona",
+ "config-install-tables": "Famoronana tabilao",
+ "config-install-stats": "Fanombohana ny statistika",
+ "config-install-keys": "Fanamboarana lakile miafina",
+ "config-help": "fanoroana",
+ "mainpagetext": "'''Tafajoro soa aman-tsara ny rindrankajy Wiki.'''",
+ "mainpagedocfooter": "Vangio ny [//meta.wikimedia.org/wiki/Help:Contents/fr Fanoroana ho an'ny mpampiasa] ra te hitady fanoroana momba ny fampiasan'ity rindrankajy ity.\n\n== Hanomboka amin'ny MediaWiki ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lisitra ny paramètre de configuration]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/fr FAQ momba ny MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Resaka momba ny fizaràn'ny MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/mhr.json b/includes/installer/i18n/mhr.json
new file mode 100644
index 00000000..269f0aab
--- /dev/null
+++ b/includes/installer/i18n/mhr.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki сай шындыме.'''"
+}
diff --git a/includes/installer/i18n/min.json b/includes/installer/i18n/min.json
new file mode 100644
index 00000000..9af4911a
--- /dev/null
+++ b/includes/installer/i18n/min.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Iwan Novirion",
+ "Luthfi94",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki alah tapasang jo sukses'''.",
+ "mainpagedocfooter": "Konsultasian [//meta.wikimedia.org/wiki/Help:Contents Panduan Panggunoan] untuak informasi caro panggunoan parangkaik lunak wiki.\n\n== Mamulai panggunoan ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/id Daftar pangaturan konfigurasi]\n* [//www.mediawiki.org/wiki/Manual:FAQ/id Daftar patanyoan nan acok diajukan manganai MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Palokalan MediaWiki untuak bahaso Sanak]"
+}
diff --git a/includes/installer/i18n/mk.json b/includes/installer/i18n/mk.json
new file mode 100644
index 00000000..8784521e
--- /dev/null
+++ b/includes/installer/i18n/mk.json
@@ -0,0 +1,329 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bjankuloski06",
+ "아라"
+ ]
+ },
+ "config-desc": "Воспоставувачот на МедијаВики",
+ "config-title": "Воспоставка на МедијаВики $1",
+ "config-information": "Информации",
+ "config-localsettings-upgrade": "Востановена е податотека <code>LocalSettings.php</code>.\nЗа да ја надградите инсталцијава, внесете ја вредноста на <code>$wgUpgradeKey</code> во полето подолу.\nТоа е го најдете во <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Утврдено е присуството на податотеката „<code>LocalSettings.php</code>“.\nЗа да ја надградите воспоставката, пуштете ја „<code>update.php</code>“ наместо горенаведената.",
+ "config-localsettings-key": "Надградбен клуч:",
+ "config-localsettings-badkey": "Клучот што го наведовте е погрешен",
+ "config-upgrade-key-missing": "Востановена е постоечка воспоставка на МедијаВики.\nЗа да ја надградите, вметнете го следниов ред на дното од вашата страница <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Постоечката страница <code>LocalSettings.php</code> е нецелосна.\nНе е поставена променливата $1.\nИзменете ја страницата <code>LocalSettings.php</code> така што ќе ѝ зададете вредност на променливата, па стиснете на „{{int:Config-continue}}“.",
+ "config-localsettings-connection-error": "Се појави грешка при поврзувањето со базата користејќи ги поставките назначени во <code>LocalSettings.php</code>. Исправете ги овие поставки и обидете се повторно.\n\n$1",
+ "config-session-error": "Грешка при започнување на седницата: $1",
+ "config-session-expired": "Вашите податоци од седницата истекоа.\nПоставките на седниците траат $1.\nНивниот рок можете да го зголемите со задавање на <code>session.gc_maxlifetime</code> во php.ini.\nПочнете ја воспоставката одново.",
+ "config-no-session": "Податоците од седницата се изгубени!\nПогледајте во php.ini дали <code>session.save_path</code> е поставен во правилна папка.",
+ "config-your-language": "Вашиот јазик:",
+ "config-your-language-help": "Одберете на кој јазик да се одвива воспоставката.",
+ "config-wiki-language": "Јазик на викито:",
+ "config-wiki-language-help": "Одберете на кој јазик ќе бидат содржините на викито.",
+ "config-back": "← Назад",
+ "config-continue": "Продолжи →",
+ "config-page-language": "Јазик",
+ "config-page-welcome": "Добредојдовте на МедијаВики!",
+ "config-page-dbconnect": "Поврзување со базата",
+ "config-page-upgrade": "Надградба на постоечката воспоставка",
+ "config-page-dbsettings": "Нагодувања на базата",
+ "config-page-name": "Назив",
+ "config-page-options": "Поставки",
+ "config-page-install": "Воспостави",
+ "config-page-complete": "Готово!",
+ "config-page-restart": "Пушти ја воспоставката одново",
+ "config-page-readme": "Прочитај ме",
+ "config-page-releasenotes": "Белешки за изданието",
+ "config-page-copying": "Копирање",
+ "config-page-upgradedoc": "Надградба",
+ "config-page-existingwiki": "Постоечко вики",
+ "config-help-restart": "Дали сакате да ги исчистите сите зачувани податоци што ги внесовте и да ја започнете воспоставката одново?",
+ "config-restart": "Да, почни одново",
+ "config-welcome": "=== Проверки на околината ===\nСега ќе се извршиме основни проверки за да се востанови дали околината е погодна за воспоставкa на МедијаВики. Не заборавајте да ги приложите овие информации ако барате помош со довршување на воспоставката.",
+ "config-copyright": "=== Авторски права и услови ===\n\n$1\n\nОва е слободна програмска опрема (free software); можете да го редистрибуирате и/или менувате согласно условите на ГНУ-овата општа јавна лиценца (GNU General Public License) на Фондацијата за слободна програмска опрема (Free Software Foundation); верзија 2 или било која понова верзија на лиценцата (по ваш избор).\n\nОвој програм се нуди со надеж дека ќе биде корисен, но '''без никаква гаранција'''; дури ни подразбраната гаранција за '''продажна способност''' или '''погодност за определена цел'''.\nПовеќе информации ќе најдете во текстот на ГНУ-овата општа јавна лиценца.\n\nБи требало да имате добиено <doclink href=Copying>примерок од ГНУ-овата општа јавна лиценца</doclink> заедно со програмов; ако немате добиено, тогаш пишете ни на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. или [http://www.gnu.org/copyleft/gpl.html прочитајте ја тука].",
+ "config-sidebar": "* [//www.mediawiki.org Домашна страница на МедијаВики]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Водич за корисници]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Водич за администратори]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧПП]\n----\n* <doclink href=Readme>Прочитај ме</doclink>\n* <doclink href=ReleaseNotes>Белешки за изданието</doclink>\n* <doclink href=Copying>Копирање</doclink>\n* <doclink href=UpgradeDoc>Надградување</doclink>",
+ "config-env-good": "Околината е проверена.\nМожете да го воспоставите МедијаВики.",
+ "config-env-bad": "Околината е проверена.\nНе можете да го воспоставите МедијаВики.",
+ "config-env-php": "PHP $1 е воспоставен.",
+ "config-env-hhvm": "HHVM $1 е воспоставен.",
+ "config-unicode-using-utf8": "Со utf8_normalize.so за уникодна нормализација од Брајон Вибер (Brion Vibber).",
+ "config-unicode-using-intl": "Со додатокот [http://pecl.php.net/intl intl PECL] за уникодна нормализација.",
+ "config-unicode-pure-php-warning": "'''Предупредување''': Додатокот [http://pecl.php.net/intl intl PECL] не е достапен за врши уникодна нормализација, враќајќи се на бавна примена на чист PHP.\n\nАко имате високопрометно мрежно место, тогаш ќе треба да прочитате повеќе за [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations уникодната нормализација].",
+ "config-unicode-update-warning": "'''Предупредување:''' Воспоставената верзија на обвивката за уникодна нормализација користи постара верзија на библиотеката на [http://site.icu-project.org/ проектот ICU].\nЗа да користите Уникод, ќе треба да направите [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations надградба].",
+ "config-no-db": "Не можев да најдам соодветен двигател за базата на податоци! Ќе треба да воспоставите двигател за PHP-база.\nПоддржани се следниве видови бази: $1.\n\nДоколку самите го срочивте овој PHP, овозможете го базниот клиент во поставките — на пр. со <code>./configure --with-mysqli</code>.\nАко овој PHP го воспоставите од пакет на Debian или Ubuntu, тогаш ќе треба исто така да го воспоставите, на пр., пакетот <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Предупредување''': имате SQLite $1. Најстарата допуштена верзија е $2. Затоа, SQLite ќе биде недостапен.",
+ "config-no-fts3": "'''Предупредување''': SQLite iе составен без модулот [//sqlite.org/fts3.html FTS3] - за оваа база нема да има можност за пребарување.",
+ "config-register-globals-error": "<strong>Грешка: Вклучена е можноста <code>[http://php.net/register_globals register_globals]</code> за PHP.\nМора да се исклучи за да продолжите со воспоставката.</strong>\nКако да го направите тоа можете да прочитате на [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals].",
+ "config-magic-quotes-gpc": "<strong>Кобно: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] е ективно!</strong>\nОваа можност непредвидливо го расипува вносот на податоци.\nОваа можност мора да е исклучена. Во спротивно нема да можете да го воспоставите и користите МедијаВики.",
+ "config-magic-quotes-runtime": "'''Кобно: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] е активно!'''\nОваа можност непредвидливо го расипува вносот на податоци.\nОваа можност мора да е исклучена. Во спротивно нема да можете да го воспоставите и користите МедијаВики.",
+ "config-magic-quotes-sybase": "'''Кобно: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] е активно!'''\nОваа можност непредвидливо го расипува вносот на податоци.\nОваа можност мора да е исклучена. Во спротивно нема да можете да го воспоставите и користите МедијаВики.",
+ "config-mbstring": "'''Кобно: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] е активно!'''\nОваа можност предизвикува грешки и може непредвидиво да го расипува вносот на податоци.\nОваа можност мора да е исклучена. Во спротивно нема да можете да го воспоставите и користите МедијаВики.",
+ "config-safe-mode": "'''Предупредување:''' [http://www.php.net/features.safe-mode безбедниот режим] на PHP е активен.\nОва може да предизвика проблеми, особено ако користите подигања и поддршка за <code>math</code>.",
+ "config-xml-bad": "XML-модулот за PHP недостасува.\nМедијаВики има потреба од функции во овој модул и нема да работи со овие поставки.\nАко работите со Mandrake, воспоставете го пакетот php-xml.",
+ "config-pcre-old": "'''Кобно:''' Се бара PCRE $1 или понова верзија.\nВашиот PHP-бинарен е сврзан со PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Повеќе информации].",
+ "config-pcre-no-utf8": "'''Фатално''': 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-ctype": "'''Фатална грешка''': PHP мора да се состави со поддршка за [http://www.php.net/manual/en/ctype.installation.php додатокот Ctype].",
+ "config-iconv": "<strong>Кобно:</strong> PHP мора да се срочува со поддршка за [http://www.php.net/manual/en/iconv.installation.php додатокот iconv].",
+ "config-json": "'''Кобно:''' PHP беше срочен без поддршка од JSON.\nЌе мора да го воспоставите додатокот за JSON во PHP, или додатокот [http://pecl.php.net/package/jsonc PECL jsonc] пред да го воспоставите МедијаВики.\n* Додатокот за PHP е вклучен во верзиите 5 и 6 на Linux (од Red Hat Enterprise) (CentOS), но мора да се активира во <code>/etc/php.ini</code> или <code>/etc/php.d/json.ini</code>.\n* Некои варијанти на Linux излезени по мај 2013 г. не го содржат додатокот за PHP, туку го пакуваат додатокот PECL како <code>php5-json</code> или <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] е воспоставен",
+ "config-apc": "[http://www.php.net/apc APC] е воспоставен",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] е воспоставен",
+ "config-no-cache": "<strong>Предупредување:</strong> Не можев да го најдам [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].\nМеѓускладирањето на објекти не е овозможено.",
+ "config-mod-security": "'''Предупредување''': на вашиот опслужувач има овозможено [http://modsecurity.org/ mod_security]. Ако не е поставено како што треба, ова може да предизвика проблеми кај МедијаВики и други програми што им овозможуваат на корисниците да објавуваат произволни содржини.\nПогледнете ја [http://modsecurity.org/documentation/ mod_security документацијата] или обратете се кај домаќинот ако наидете на случајни грешки.",
+ "config-diff3-bad": "GNU diff3 не е пронајден.",
+ "config-git": "Го пронајдов Git програмот за контрола на верзии: <code>$1</code>.",
+ "config-git-bad": "Не го пронајдов Git-програмот за контрола на верзии.",
+ "config-imagemagick": "Пронајден е ImageMagick: <code>$1</code>.\nАко овозможите подигање, тогаш ќе биде овозможена минијатуризација на сликите.",
+ "config-gd": "Утврдив дека има вградена GD графичка библиотека.\nАко овозможите подигање, тогаш ќе биде овозможена минијатураизација на сликите.",
+ "config-no-scaling": "Не можев да пронајдам GD-библиотека или ImageMagick.\nМинијатуризацијата на сликите ќе биде оневозможена.",
+ "config-no-uri": "'''Грешка:''' Не можев да го утврдам тековниот URI.\nВоспоставката е откажана.",
+ "config-no-cli-uri": "'''Предупредување''': Нема наведено <code>--scriptpath</code>. Ќе се користи основниот: <code>$1</code>.",
+ "config-using-server": "Користите опслужувач под името „<nowiki>$1</nowiki>“.",
+ "config-using-uri": "Користите опслужувач со URL-адреса „<nowiki>$1$2</nowiki>“.",
+ "config-uploads-not-safe": "'''Предупредување:''' Вашата матична папка за подигање <code>$1</code> е подложна на извршување (пуштање) на произволни скрипти.\nИако МедијаВики врши безбедносни проверки на сите подигнати податотеки, ве советуваме [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security да ја затворите оваа безбедносна дупка] пред да овозможите подигање.",
+ "config-no-cli-uploads-check": "'''Предупредување:''' Вашата основна папка за подигања (<code>$1</code>) не е проверена дали е подложна\nпроизволно извршување на скрипти во текот на воспоставката на посредникот на повикувачко ниво (CLI).",
+ "config-brokenlibxml": "Вашиот систем има комбинација од PHP и libxml2 верзии и затоа има грешки и може да предизвика скриено расипување на податоците кај МедијаВики и други мрежни програми.\nНадградете го на libxml2 2.7.3 или нивни понови верзии! ([https://bugs.php.net/bug.php?id=45996 грешката е заведена во PHP]). Воспоставката е откажана.",
+ "config-suhosin-max-value-length": "Suhosin е воспоставен и ја ограничува должината на параметарот GET на $1 бајти. Делот ResourceLoader на МедијаВики ќе ја заобиколува ова граница, но со тоа ќе се влоши делотворноста. Ако е воопшто можно, на <code>suhosin.get.max_value_length</code> треба да го наместите на 1024 или повеќе во <code>php.ini</code>, и да му ја зададете истата вредност на <code>$wgResourceLoaderMaxQueryLength</code> во <code>LocalSettings.php</code>.",
+ "config-db-type": "Тип на база:",
+ "config-db-host": "Домаќин на базата:",
+ "config-db-host-help": "Ако вашата база е на друг опслужувач, тогаш тука внесете го името на домаќинот или IP-адресата.\n\nАко користите заедничко (споделено) вдомување, тогаш вашиот вдомител треба да го наведе точното име на домаќинот во неговата документација.\n\nАко воспоставувате на опслужувач на Windows и користите MySQL, можноста „localhost“ може да не функционира за опслужувачкото име. Во тој случај, обидете се со внесување на „127.0.0.1“ како локална IP-адреса.\n\nАко користите PostgreSQL, оставете го полево празно за да се поврзете преку Unix-приклучок.",
+ "config-db-host-oracle": "TNS на базата:",
+ "config-db-host-oracle-help": "Внесете важечко [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm локално име за поврзување]. На оваа воспоставка мора да ѝ биде видлива податотеката tnsnames.ora.<br />Ако користите клиентски библиотеки 10g или понови, тогаш можете да го користите и методот на иметнување на [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Идентификувај го викиво",
+ "config-db-name": "Име на базата:",
+ "config-db-name-help": "Одберете име што ќе го претставува вашето вики.\nИмето не смее да содржи празни места.\n\nАко користите заедничко (споделено) вдомување, тогаш вашиот вдомител ќе ви даде конкретно име на база за користење, или пак ќе ви даде да создавате бази преку контролната табла.",
+ "config-db-name-oracle": "Шема на базата:",
+ "config-db-account-oracle-warn": "Постојат три поддржани сценарија за воспоставка на Oracle како базен услужник:\n\nАко сакате да создадете сметка на базата како дел од постапката за воспоставка, наведете сметка со SYSDBA-улога како сметка за базата што ќе се воспостави и наведете ги саканите податоци за сметката за мрежен пристап. Во друг случај, можете да создадете сметка за мрежен пристап рачно и да ја наведете само таа сметка (ако има дозволи за создавање на шематски објекти) или пак да наведете две различни сметки, една со привилегии за создавање, а друга (ограничена) за мрежен пристап.\n\nСкриптата за создавање сметка со задолжителни привилегии ќе ја најдете во папката „maintenance/oracle/“ од оваа воспоставка. Имајте на ум дека ако користите ограничена сметка ќе ги оневозможите сите функции за одржување со основната сметка.",
+ "config-db-install-account": "Корисничка смета за воспоставка",
+ "config-db-username": "Корисничко име за базата:",
+ "config-db-password": "Лозинка за базата:",
+ "config-db-password-empty": "Внесете лозинка за новиот корисник на базата: $1.\nИако може да се создаваат корисници без лозинка, тоа не е безбедно.",
+ "config-db-username-empty": "Мора да внесете вредност за „{{int:config-db-username}}“.",
+ "config-db-install-username": "Внесете корисничко име што ќе се користи за поврзување со базата во текот на воспоставката. Ова не е корисничкото име од сметката на МедијаВики, туку посебно корисничко име за вашата база на податоци.",
+ "config-db-install-password": "Внесете клозинка што ќе се користи за поврзување со базата во текот на воспоставката. Ова не е лозинката од сметката на МедијаВики, туку посебна лозинка за вашата база на податоци.",
+ "config-db-install-help": "Внесете го корисничкото име и лозинката што ќе се користи за поврзување со базата на податоци во текот на воспоставката.",
+ "config-db-account-lock": "Користи го истото корисничко име и лозинка за редовна работа",
+ "config-db-wiki-account": "Корисничко име за редовна работа",
+ "config-db-wiki-help": "Внесете корисничко име и лозинка што ќе се користат за поврзување со базата на податоци во текот на редовната работа со викито.\nАко сметката не постои, а инсталационата сметка има доволно привилегии, тогаш оваа корисничка сметка ќе биде создадена со минималните привилегии потребни за работа со викито.",
+ "config-db-prefix": "Претставка на табелата на базата:",
+ "config-db-prefix-help": "Ако треба да делите една база на податоци со повеќе викија, или со МедијаВики и друг мрежен програм, тогаш можете да додадете претставка на сите називи на табелите за да спречите проблематични ситуации.\nНе користете празни простори.\n\nОва поле обично се остава празно.",
+ "config-db-charset": "Збир знаци за базата",
+ "config-charset-mysql5-binary": "Бинарен за MySQL 4.1/5.0",
+ "config-charset-mysql5": "UTF-8 за MySQL 4.1/5.0",
+ "config-charset-mysql4": "Назадно-соодветен UTF-8 за MySQL 4.0",
+ "config-charset-help": "'''ПРЕДУПРЕДУВАЊЕ:''' Ако користите '''назадно-соодветен UTF-8''' во MySQL 4.1+, а потоа направите резервен примерок на базата со <code>mysqldump</code>, ова може да ги опустоши сите не-ASCII знаци, и со тоа неповратно да ја расипе целата зачувана резерва!\n\nВо '''бинарен режим''', во базата МедијаВики го складира UTF-8 текстот во бинарни полиња.\nОва е поефикансно отколку UTF-8 режимот на MySQL бидејќи ви овозможува да го користите целиот спектар на уникодни знаци.\nВо '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори,\nно нема да ви дозволи да складирате знаци над [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
+ "config-mysql-old": "Се бара MySQL $1 или поново, а вие имате $2.",
+ "config-db-port": "Порта на базата:",
+ "config-db-schema": "Шема за МедијаВики",
+ "config-db-schema-help": "Оваа шема обично по правило ќе работи нормално.\nСменете ја само ако знаете дека треба да се смени.",
+ "config-pg-test-error": "Не можам да се поврзам со базата '''$1''': $2",
+ "config-sqlite-dir": "Папка на SQLite-податоци:",
+ "config-sqlite-dir-help": "SQLite ги складира сите податоци во една податотека.\n\nПапката што ќе ја наведете мора да е запислива од мрежниот опслужувач во текот на воспоставката.\n\nТаа '''не''' смее да биде достапна преку семрежјето, и затоа не ја ставаме кајшто ви се наоѓаат PHP-податотеките.\n\nВоспоставувачот воедно ќе создаде податотека <code>.htaccess</code>, но ако таа не функционира како што треба, тогаш некој ќе може да ви влезе во вашата необработена (сирова) база на податоци.\nТука спаѓаат необработени кориснички податоци (е-поштенски адреси, хеширани лозинки) како и избришани преработки и други податоци за викито до кои се има ограничен пристап.\n\nСе препорачува целата база да ја сместите некаде, како на пр. <code>/var/lib/mediawiki/вашетовики</code>.",
+ "config-oracle-def-ts": "Стандарден таблеарен простор:",
+ "config-oracle-temp-ts": "Привремен табеларен простор:",
+ "config-type-mysql": "MySQL (или складно)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-support-info": "МедијаВики ги поддржува следниве системи на бази на податоци:\n\n$1\n\nАко системот што сакате да го користите не е наведен подолу, тогаш проследете ја горенаведената врска со инструкции за да овозможите поддршка за тој систем.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] е главната цел на МедијаВики и најдобро е поддржан. МедијаВики работи и со [{{int:version-db-mariadb-url}} MariaDB] и [{{int:version-db-percona-url}} Percona], кои се складни со MySQL. ([http://www.php.net/manual/en/mysqli.installation.php Како да срочите PHP со поддршка за MySQL])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] е популарен систем на бази на податоци со отворен код кој претставува алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php како да составите PHP со поддршка за PostgreSQL]). Може сè уште да има некои грешки. па затоа не се препорачува за употреба во производна средина. ([http://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] е база на податоци на комерцијално претпријатиe за Windows ([http://www.php.net/manual/en/sqlsrv.installation.php How to compile PHP with SQLSRV поддршка])",
+ "config-header-mysql": "Нагодувања на MySQL",
+ "config-header-postgres": "Нагодувања на PostgreSQL",
+ "config-header-sqlite": "Нагодувања на SQLite",
+ "config-header-oracle": "Нагодувања на Oracle",
+ "config-header-mssql": "Нагодувања за Microsoft SQL Server",
+ "config-invalid-db-type": "Неважечки тип на база",
+ "config-missing-db-name": "Мора да внесете значење за параметарот „{{int:config-db-name}}“.",
+ "config-missing-db-host": "Мора да внесете вредност за „{{int:config-db-host}}“.",
+ "config-missing-db-server-oracle": "Мора да внесете вредност за „{{int:config-db-host-oracle}}“.",
+ "config-invalid-db-server-oracle": "Неважечки TNS „$1“.\nКористете или „TNS Name“ или низата „Easy Connect“ ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методи на именување за Oracle])",
+ "config-invalid-db-name": "Неважечко име на базата „$1“.\nКористете само ASCII-букви (a-z, A-Z), бројки (0-9), долни црти (_) и цртички (-).",
+ "config-invalid-db-prefix": "Неважечка претставка за базата „$1“.\nКористете само ASCII-букви (a-z, A-Z), бројки (0-9), долни црти (_) и цртички (-).",
+ "config-connection-error": "$1.\n\nПроверете го долунаведениот домаќин, корисничко име и лозинка и обидете се повторно.",
+ "config-invalid-schema": "Неважечка шема за МедијаВики „$1“.\nКористете само букви, бројки и долни црти.",
+ "config-db-sys-create-oracle": "Воспоставувачот поддржува само употреба на SYSDBA-сметка за создавање на нова сметка.",
+ "config-db-sys-user-exists-oracle": "Корисничката сметка „$1“ веќе постои. SYSDBA служи само за создавање на нова сметка!",
+ "config-postgres-old": "Се бара PostgreSQL $1 или поново, а вие имате $2.",
+ "config-mssql-old": "Се бара Microsoft SQL Server $1 или понова верзија. Вие имате $2.",
+ "config-sqlite-name-help": "Одберете име кое ќе го претставува вашето вики.\nНе користете празни простори и црти.\nОва ќе се користи за податотечното име на SQLite-податоците.",
+ "config-sqlite-parent-unwritable-group": "Не можам да ја создадам папката <code><nowiki>$1</nowiki></code> бидејќи мрежниот опслужувач не може да запише во матичната папка <code><nowiki>$2</nowiki></code>.\n\nВоспоставувачот го утврди корисникот под кој работи вашиот мрежен опслужувач.\nЗа да продолжите, наместете да може да запишува во папката <code><nowiki>$3</nowiki></code>.\nНа Unix/Linux систем направете го следново:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Не можам да ја создадам папката <code><nowiki>$1</nowiki></code> бидејќи мрежниот опслужувач не може да запише во матичната папка <code><nowiki>$2</nowiki></code>.\n\nВоспоставувачот не можеше го утврди корисникот под кој работи вашиот мрежен опслужувач.\nЗа да продолжите, наместете тој (и други!) да може глобално да запишува во папката <code><nowiki>$3</nowiki></code>\nНа Unix/Linux систем направете го следново:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Грешка при создавањето на податочната папка „$1“.\nПроверете каде се наоѓа и обидете се повторно.",
+ "config-sqlite-dir-unwritable": "Не можам да запишам во папката „$1“.\nВо дозволите за неа, овозможете му на мрежниот опслужувач да запишува во неа и обидете се повторно.",
+ "config-sqlite-connection-error": "$1.\n\nПроверете ја податочната папка и името на базата, и обидете се повторно.",
+ "config-sqlite-readonly": "Податотеката <code>$1</code> е незапислива.",
+ "config-sqlite-cant-create-db": "Не можев да ја создадам податотеката <code>$1</code> за базата.",
+ "config-sqlite-fts3-downgrade": "PHP нема поддршка за FTS3 — ја поништувам надградбата за табелите",
+ "config-can-upgrade": "Во оваа база има табели на МедијаВики.\nЗа да ги надградите на МедијаВики $1, стиснете на '''Продолжи'''.",
+ "config-upgrade-done": "Надградбата заврши.\n\nСега можете да [$1 почнете да го користите вашето вики].\n\nАко сакате да ја пресоздадете вашата податотека <code>LocalSettings.php</code>, тогаш стиснете на копчето подолу.\nОва '''не се препорачува''' освен во случај на проблеми со викито.",
+ "config-upgrade-done-no-regenerate": "Надградбата заврши.\n\nСега можете да [$1 почнете да го користите викито].",
+ "config-regenerate": "Пресоздај LocalSettings.php →",
+ "config-show-table-status": "Барањето <code>SHOW TABLE STATUS</code> не успеа!",
+ "config-unknown-collation": "'''Предупредување:''' Базата корисни непрепознаена упатна споредба.",
+ "config-db-web-account": "Сметка на базата за мрежен пристап",
+ "config-db-web-help": "Одберете корисничко име и лозинка што ќе ги користи мрежниот опслужувач за поврзување со опслужувачот на базта на податоци во текот на редовната работа со викито.",
+ "config-db-web-account-same": "Користи ја истата сметка од воспоставката",
+ "config-db-web-create": "Создај ја сметката ако веќе не постои",
+ "config-db-web-no-create-privs": "Сметката што ја назначивте за воспоставка нема доволно привилегии за да може да создаде сметка.\nТука мора да назначите постоечка сметка.",
+ "config-mysql-engine": "Складишен погон:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Предупредување:''' Го одбравте MyISAM како складишен погон за MySQL. Но тој не се препорачува за МедијаВики бидејќи:\n* одвај поддржува едновременост поради заклучување на табелите\n* поподложен на расипување од другите погони\n* кодната база на МедијаВики не секогаш може да работи со MyISAM како што треба\n\nАко вашата воспоставка на MySQL поддржува InnoDB, тогаш сериозно препорачуваме да го користите него наместо MyISAM.\nАко вашата воспоставка на MySQL не поддржува InnoDB, веројатно дошло време за надградба.",
+ "config-mysql-only-myisam-dep": "'''Предупредување:''' MyISAM е единствениот достапен складишен погон за MySQL на оваа машина, а ова не се препорачува за употреба со МедијаВики, бидејќи:\n* речиси не поддржува истовремено извршување на задачите поради заклучувањето на табелите\n* поподложен е на расипувања од другите погони\n* кодната база на МедијаВИки не секогаш работи исправно со MyISAM\nВашата воспоставка на MySQL не поддржува InnoDB. Можеби е време да ја надградите.",
+ "config-mysql-engine-help": "'''InnoDB''' речиси секогаш е најдобар избор, бидејќи има добра поддршка за едновременост.\n\n'''MyISAM''' може да е побрз кај воспоставките наменети за само еден корисник или незаписни воспоставки (само читање).\nБазите на податоци од MyISAM почесто се расипуваат од базите на InnoDB.",
+ "config-mysql-charset": "Збир знаци за базата:",
+ "config-mysql-binary": "Бинарен",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "Во '''бинарен режим''', во базата на податоци МедијаВики складира UTF-8 текст во бинарни полиња.\nОва е поефикасно отколку TF-8 режимот на MySQL, и ви овозможува да ја користите целата палета на уникодни знаци.\n\nВо '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори, но нема да ви дозволи да складиратезнаци над [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
+ "config-mssql-auth": "Тип на заверка:",
+ "config-mssql-install-auth": "Изберете го типот на заверка што ќе се користи за поврзување со базата на податоци во текот на воспоставката.\nАко изберете „{{int:config-mssql-windowsauth}}“, ќе се користат најавните податоци или корисникот како кој работи мрежниот опслужувач.",
+ "config-mssql-web-auth": "Изберете го типот на заверка што мрежниот послужувач ќе го користи за поврзување со опслужувачот на базата во текот на редовната работа на викито.\nАко изберете „{{int:config-mssql-windowsauth}}“, ќе се користат најавните податоци или корисникот како кој работи мрежниот опслужувач.",
+ "config-mssql-sqlauth": "Заверка за SQL Server",
+ "config-mssql-windowsauth": "Заверка за Windows",
+ "config-site-name": "Име на викито:",
+ "config-site-name-help": "Ова ќе се појавува во заглавната лента на прелистувачот и на разни други места.",
+ "config-site-name-blank": "Внесете име на мрежното место.",
+ "config-project-namespace": "Проектен именски простор:",
+ "config-ns-generic": "Проект",
+ "config-ns-site-name": "Исто име како викито: $1",
+ "config-ns-other": "Друго (наведете)",
+ "config-ns-other-default": "МоеВики",
+ "config-project-namespace-help": "По примерот на Википедија, многу викија ги чуваат страниците со правила на посебно место од самите содржини, т.е. во „'''проектен именски простор'''“.\nСите наслови на страниците во овој именски простор почнуваат со извесна претставка, којшто можете да го укажете тука.\nПо традиција претставката произлегува од името на викито, но не смее да содржи интерпункциски знаци како „#“ или „:“.",
+ "config-ns-invalid": "Назначениот именски простор „<nowiki>$1</nowiki>“ е неважечки.\nНазначете друг проектен именски простор.",
+ "config-ns-conflict": "Наведениот именски простор „<nowiki>$1</nowiki>“ се коси со основниот именски простор на МедијаВики.\nНаведете друг именски простор за проектот.",
+ "config-admin-box": "Администратоска сметка",
+ "config-admin-name": "Вашето корисничко име:",
+ "config-admin-password": "Лозинка:",
+ "config-admin-password-confirm": "Пак лозинката:",
+ "config-admin-help": "Тука внесете го вашето корисничко име, на пр. „Петар Петровски“.\nОва име ќесе користи за најава во викито.",
+ "config-admin-name-blank": "Внесете администраторско корисничко име.",
+ "config-admin-name-invalid": "Назначенотго корисничко име „<nowiki>$1</nowiki>“ е неважечко.\nНазначете друго.",
+ "config-admin-password-blank": "Внесете лозинка за администраторската сметка",
+ "config-admin-password-mismatch": "Лозинките што ги внесовте не се совпаѓаат.",
+ "config-admin-email": "Е-поштенска адреса:",
+ "config-admin-email-help": "Тука внесете е-поштенска адреса за да можете да добивате е-пошта од други корисници на викито, да ја менувате лозинката, и да бидете известувани за промени во страниците на вашиот список на набљудувања. Можете и да го оставите празно.",
+ "config-admin-error-user": "Се појави внатрешна грешка при создавањето на администраторот со име „<nowiki>$1</nowiki>“.",
+ "config-admin-error-password": "Се појави внатрешна грешка при задавање на лозинката за администраторот „<nowiki>$1</nowiki>“: <pre>$2</pre>",
+ "config-admin-error-bademail": "Внесовте неважечка е-поштенска адреса",
+ "config-subscribe": "Претплатете се на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release поштенскиот список за известувања].",
+ "config-subscribe-help": "Ова е нископрометен поштенски список кој се користи за соопштувања во врска со изданија, вклучувајќи важни безбедносни соопштенија.\nТреба да се претплатите и да ја надградувате вашата воспоставка на МедијаВики кога излегуваат нови верзии.",
+ "config-subscribe-noemail": "Се обидовте да се претплатите на поштенскиот список со известувања за нови изданија без да наведете е-пошта.\nНаведете е-поштенска адреса ако сакате да се претплатите на списокот.",
+ "config-almost-done": "Уште малку сте готови!\nСега можете да ги прескокнете преостанатите поставувања и веднаш да го воспоставите викито.",
+ "config-optional-continue": "Постави ми повеќе прашања.",
+ "config-optional-skip": "Веќе ми здосади, дај само воспостави го викито.",
+ "config-profile": "Профил на кориснички права:",
+ "config-profile-wiki": "Отворено вики",
+ "config-profile-no-anon": "Задолжително отворање сметка",
+ "config-profile-fishbowl": "Само овластени уредници",
+ "config-profile-private": "Лично вики",
+ "config-profile-help": "Викијата функционираат најдобро кога имаат што повеќе уредници.\nВо МедијаВики лесно се проверуваат скорешните промени, и лесно се исправа (технички: „враќа“) штетата направена од неупатени или злонамерни корисници.\n\nМногумина имаат најдено најразлични полезни примени за МедијаВики, но понекогаш не е лесно да убедите некого во предностите на вики-концептот.\nЗначи имате избор.\n\n'''{{int:config-profile-wiki}}''' — модел според кој секој може да уредува, дури и без најавување.\nАко имате вики со '''задолжително отворање на сметка''', тогаш добивате повеќе контрола, но ова може даги одврати спонтаните учесници.\n\n'''{{int:config-profile-fishbowl}}''' — може да уредуваат само уредници што имаат добиено дозвола за тоа, но јавноста може да ги гледа страниците, вклучувајќи ја нивната историја.\n'''{{int:config-profile-private}}''' — страниците се видливи и уредливи само за овластени корисници.\n\nПо воспоставката имате на избор и посложени кориснички права и поставки. Погледајте во [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights прирачникот].",
+ "config-license": "Авторски права и лиценца:",
+ "config-license-none": "Без подножје за лиценца",
+ "config-license-cc-by-sa": "Криејтив комонс НаведиИзвор СподелиПодИстиУслови",
+ "config-license-cc-by": "Криејтив комонс НаведиИзвор",
+ "config-license-cc-by-nc-sa": "Криејтив комонс НаведиИзвор-Некомерцијално-СподелиПодИстиУслови",
+ "config-license-cc-0": "Криејтив комонс Нула (јавна сопственост)",
+ "config-license-gfdl": "ГНУ-ова лиценца за слободна документација 1.3 или понова",
+ "config-license-pd": "Јавна сопственост",
+ "config-license-cc-choose": "Одберете друга лиценца на Криејтив комонс по ваш избор",
+ "config-license-help": "Многу јавни викија ги ставаат сите придонеси под [http://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": "Ако сакате да работи е-поштата, [http://www.php.net/manual/en/mail.configuration.php поштенските нагодувања на PHP] треба да се правилно наместени.\nАко воопшто не сакате никакви функции за е-пошта, тогаш можете да ги оневозможите тука.",
+ "config-email-user": "Овозможи е-пошта од корисник до корисник",
+ "config-email-user-help": "Дозволи сите корисници да можат да си праќаат е-пошта ако ја имаат овозможено во нагодувањата.",
+ "config-email-usertalk": "Овозможи известувања за промени во кориснички страници за разговор",
+ "config-email-usertalk-help": "Овозможи корисниците да добиваат известувања за промени во нивните кориснички страници за разговор ако ги имаат овозможено во нагодувањата.",
+ "config-email-watchlist": "Овозможи известувања за список на набљудувања",
+ "config-email-watchlist-help": "Овозможи корисниците да добиваат известувања за нивните набљудувани страници ако ги имаат овозможено во нагодувањата.",
+ "config-email-auth": "Овозможи потврдување на е-пошта",
+ "config-email-auth-help": "Ако оваа можност е вклучена, тогаш корисниците ќе мора да ја потврдат нивната е-поштенска адреса преку врска испратена до нив кога ја укажуваат или менуваат е-поштенската адреса.\nСамо корисници со потврдена е-пошта можат да добиваат е-пошта од други корисници или да ги менуваат писмата за известување.\nОваа можност е '''препорачана''' за јавни викија поради можни злоупотреби на е-поштенската функција.",
+ "config-email-sender": "Повратна е-поштенска адреса:",
+ "config-email-sender-help": "Внесете ја е-поштенската адреса што ќе се користи како повратна адреса за излезна е-пошта.\nТаму ќе се испраќаат вратените (непримени) писма.\nМногу поштенски опслужувачи бараат барем делот за доменско име да биде важечки.",
+ "config-upload-settings": "Подигање на слики и податотеки",
+ "config-upload-enable": "Овозможи подигање на податотеки",
+ "config-upload-help": "Подигањето на податотеки потенцијално го изложуваат вашиот опслужувач на безбедносни ризици.\nЗа повеќе информации, прочитајте го [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security поглавието за безбедност] во прирачникот.\n\nЗа да овозможите подигање на податотеки, сменете го режимот на потпапката <code>images</code> во основната папка на МедијаВики, за да му овозможите на мрежниот опслужувач да запишува во неа.\nПотоа овозможете ја оваа функција.",
+ "config-upload-deleted": "Папка за избришаните податотеки:",
+ "config-upload-deleted-help": "Одберете во која папка да се архивираат избришаните податотеки.\nНајдобро би било ако таа не е достапна преку семрежјето.",
+ "config-logo": "URL за логото:",
+ "config-logo-help": "Матичното руво на МедијаВики има простор за лого од 135x160 пиксели над страничната лента.\n\nМожете да употребите <code>$wgStylePath</code> или <code>$wgScriptPath</code> ако вашето лого е релативно на тие патеки.\n\nАко не сакате да имате лого, тогаш оставете го ова поле празно.",
+ "config-instantcommons": "Овозможи Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функција која им овозможува на викијата да користат слики, звучни записи и други мултимедијални содржини од [//commons.wikimedia.org/ Ризницата].\nЗа да може ова да работи, МедијаВики бара пристап до семрежјето.\n\nЗа повеќе информации за оваа функција и напатствија за нејзино поставување на вики (сите други освен Ризницата), коносултирајте го [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos прирачникот].",
+ "config-cc-error": "Изборникот на лиценци од Криејтив комонс не даде резултати.\nВнесете го името на лиценцата рачно.",
+ "config-cc-again": "Одберете повторно...",
+ "config-cc-not-chosen": "Одберете ја саканата лиценца од Криејтив комонс и стиснете на „продолжи“.",
+ "config-advanced-settings": "Напредни нагодувања",
+ "config-cache-options": "Нагодувања за меѓускладирање на објекти:",
+ "config-cache-help": "Меѓускладирањето на објекти се користи за зголемување на брзината на МедијаВики со меѓускладирање на често употребуваните податоци.\nОва многу се препорачува на средни до големи викија, но од тоа ќе имаат полза и малите викија.",
+ "config-cache-none": "Без меѓускладирање (не се остранува ниедна функција, но може да влијае на брзината кај поголеми викија)",
+ "config-cache-accel": "Меѓускладирање на PHP-објекти (APC, XCache или WinCache)",
+ "config-cache-memcached": "Користи Memcached (бара дополнително поставување и нагодување)",
+ "config-memcached-servers": "Memcached-опслужувачи:",
+ "config-memcached-help": "Список на IP-адреси за употреба во Memcached.\nТреба да се наведе по една во секој ред, како и портата што ќе се користи. На пример:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Го одбравте Memcached како ваш тип на меѓусклад (кеш), но не наведовте ниеден опслужувач.",
+ "config-memcache-badip": "Внесовте неважечка IP-адреса за Memcached: $1",
+ "config-memcache-noport": "Не ја наведовте портата за опслужувачот на Memcached: $1.\nАко не знаете која порта треба да се користи, основната е 11211",
+ "config-memcache-badport": "Бројките за портата на Memcached треба да бидат помеѓу $1 и $2",
+ "config-extensions": "Додатоци",
+ "config-extensions-help": "Во вашата папка <code>./extensions</code> беа востановени горенаведените додатоци.\n\nЗа ова може да треба дополнително нагодување, но можете да ги овозможите сега",
+ "config-skins": "Рува",
+ "config-skins-help": "Во вашата папка <code>./skins</code> се утврдени горенаведените рува. Ќе мора да овозможите барем едно и да го изберете основното.",
+ "config-skins-use-as-default": "Користи го како основно",
+ "config-skins-missing": "Не пронајдов ниедно руво. МедијаВики ќе користи резервно руво сè додека не воспоставите други.",
+ "config-skins-must-enable-some": "Ќе треба да изберете барем едно руво.",
+ "config-skins-must-enable-default": "Рувото што го избравте како основно мора да се овозможи.",
+ "config-install-alreadydone": "'''Предупредување:''' Изгледа дека веќе го имате воспоставено МедијаВики и сега сакате да го воспоставите повторно.\nПродолжете на следната страница.",
+ "config-install-begin": "Стискајќи на „{{int:config-continue}}“ ќе ја започнете воспоставката на МедијаВики.\nАко сакате да направите измени во досегашното, стиснете на „{{int:config-back}}“.",
+ "config-install-step-done": "готово",
+ "config-install-step-failed": "не успеа",
+ "config-install-extensions": "Вклучувам додатоци",
+ "config-install-database": "Ја поставувам базата на податоци",
+ "config-install-schema": "Создавам шема",
+ "config-install-pg-schema-not-exist": "PostgreSQL-шемата не постои",
+ "config-install-pg-schema-failed": "Создавањето натабелите не успеа.\nПроверете дали корисникот „$1“ може да запишува во шемата „$2“.",
+ "config-install-pg-commit": "Спроведување на промени",
+ "config-install-pg-plpgsql": "Проверувам јазик PL/pgSQL",
+ "config-pg-no-plpgsql": "Ќе треба да го воспоставите јазикот PL/pgSQL во базата $1",
+ "config-pg-no-create-privs": "Сметката што ја наведовте за воспоставката нема доволно привилегии за да создаде друга сметка.",
+ "config-pg-not-in-role": "Сметката што ја наведовте за мрежниот корисник веќе постои.\nСметката што ја наведовте за воспоставка не е суперкорисник и не ѝ припаѓа на улогата на мрежниот корисник, па затоа не може да создава објекти во негова сопственост.\n\nМедијаВики налага дека табелите мора да се во сопственост на мрежниот корисник. Наведете друга мрежна сметка, или стиснете на „назад“ и наведете соодветно привилегиран корисник за инталацијата.",
+ "config-install-user": "Создавам корисник за базата",
+ "config-install-user-alreadyexists": "Корисникот „$1“ веќе постои",
+ "config-install-user-create-failed": "Создавањето на корисникот „$1“ не успеа: $2",
+ "config-install-user-grant-failed": "Доделувањето на дозвола на корисникот „$1“ не успеа: $2",
+ "config-install-user-missing": "Наведениот корисник „$1“ не постои.",
+ "config-install-user-missing-create": "Наведениот корисник „$1“ не постои.\nАко сакате да го создадете, штиклирајте ја можноста „создај сметка“.",
+ "config-install-tables": "Создавам табели",
+ "config-install-tables-exist": "'''Предупредување''': Изгледа дека табелите за МедијаВики веќе постојат.\nГо прескокнувам создавањето.",
+ "config-install-tables-failed": "'''Грешка''': Создавањето на табелата не успеа поради следнава грешка: $1",
+ "config-install-interwiki": "Ги пополнувам основно зададените меѓувики-табели",
+ "config-install-interwiki-list": "Не можев да ја пронајдам податотеката <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Предупредување''': Табелата со интервикија веќе содржи ставки.\nГо прескокнувам основно-зададениот список.",
+ "config-install-stats": "Ги подготвувам статистиките",
+ "config-install-keys": "Создавање на тајни клучеви",
+ "config-insecure-keys": "'''Предупредување:''' {{PLURAL:$2|Безбедносниот клуч $1 создаден во текот на воспоставката не е сосем безбеден|Безбедносните клучеви $1 создадени во текот на воспоставката не се сосем безбедни}}. Ви препорачуваме да {{PLURAL:$2|го|ги}} смените рачно.",
+ "config-install-updates": "Спречи вршење на непотребни поднови",
+ "config-install-updates-failed": "<strong>Грешка:</strong> Вметнувањето на подновни клучеви во табелите не успеа, со следнава грешка: $1",
+ "config-install-sysop": "Создавање на администраторска корисничка сметка",
+ "config-install-subscribe-fail": "Не можам да ве претплатам на известувањето mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL не е воспоставен, а <code>allow_url_fopen</code> не е достапно.",
+ "config-install-mainpage": "Создавам главна страница со стандардна содржина",
+ "config-install-extension-tables": "Изработка на табели за овозможени додатоци",
+ "config-install-mainpage-failed": "Не можев да вметнам главна страница: $1",
+ "config-install-done": "'''Честитаме!'''\nУспешно го воспоставивте МедијаВики.\n\nВоспоставувачот создаде податотека <code>LocalSettings.php</code>.\nТаму се содржат сите ваши нагодувања.\n\nЌе треба да ја преземете и да ја ставите во основата на воспоставката (истата папка во која се наоѓа index.php). Преземањето треба да е започнато автоматски.\n\nАко не ви е понудено преземање, или пак ако сте го откажале, можете да го почнете одново стискајќи на следнава врска:\n\n$3\n\n'''Напомена''': Ако ова не го направите сега, податотеката со поставки повеќе нема да биде на достапна.\n\nОткога ќе завршите со тоа, можете да '''[$2 влезете на вашето вики]'''.",
+ "config-download-localsettings": "Преземи го <code>LocalSettings.php</code>",
+ "config-help": "помош",
+ "config-help-tooltip": "стиснете да расклопите",
+ "config-nofile": "Податотеката „$1“ не е пронајдена. Да не е избришана?",
+ "config-extension-link": "Дали сте знаеле дека вашето вики поддржува [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions додатоци]?\n\nМожете да ги прелистате [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category по категории]",
+ "mainpagetext": "'''МедијаВики е успешно воспоставен.'''",
+ "mainpagedocfooter": "Погледнете го [//meta.wikimedia.org/wiki/Help:Contents Упатството за корисници] за подетални иформации како се користи вики-програмот.\n\n==Од каде да почнете==\n* [//meta.wikimedia.org/wiki/Manual:Configuration_settings Список на нагодувања]\n* [//meta.wikimedia.org/wiki/Manual:FAQ ЧПП (често поставувани прашања) за МедијаВики].\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Поштенски список на МедијаВики за нови верзии]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Локализирајте го МедијаВики на вашиот јазик]"
+}
diff --git a/includes/installer/i18n/ml.json b/includes/installer/i18n/ml.json
new file mode 100644
index 00000000..04c28908
--- /dev/null
+++ b/includes/installer/i18n/ml.json
@@ -0,0 +1,120 @@
+{
+ "@metadata": {
+ "authors": [
+ "Praveenp",
+ "Sadik Khalid"
+ ]
+ },
+ "config-desc": "മീഡിയവിക്കി ഇൻസ്റ്റോളർ",
+ "config-title": "മീഡിയവിക്കി $1 ഇൻസ്റ്റലേഷൻ",
+ "config-information": "വിവരങ്ങൾ",
+ "config-localsettings-upgrade": "'''അറിയിപ്പ്''': ഒരു <code>LocalSettings.php</code> ഫയൽ കാണുന്നു.\nസോഫ്റ്റ്‌വേർ അപ്‌ഗ്രേഡ് ചെയ്യുക സാദ്ധ്യമാണ്.\nദയവായി പെട്ടിയിൽ <code>$wgUpgradeKey</code> എന്നതിന്റെ വില നൽകുക.",
+ "config-localsettings-key": "അപ്‌ഗ്രേഡ് ചാവി:",
+ "config-localsettings-badkey": "താങ്കൾ നൽകിയ ചാവി തെറ്റാണ്",
+ "config-session-error": "സെഷൻ തുടങ്ങുന്നതിൽ പിഴവ്: $1",
+ "config-your-language": "താങ്കളുടെ ഭാഷ:",
+ "config-your-language-help": "ഇൻസ്റ്റലേഷൻ പ്രക്രിയയിൽ ഉപയോഗിക്കേണ്ട ഭാഷ തിരഞ്ഞെടുക്കുക.",
+ "config-wiki-language": "വിക്കി ഭാഷ:",
+ "config-wiki-language-help": "വിക്കിയിൽ പ്രധാനമായി ഉപയോഗിക്കേണ്ട ഭാഷ തിരഞ്ഞെടുക്കുക.",
+ "config-back": "← പിന്നിലേയ്ക്ക്",
+ "config-continue": "തുടരുക →",
+ "config-page-language": "ഭാഷ",
+ "config-page-welcome": "മീഡിയവിക്കിയിലേയ്ക്ക് സ്വാഗതം!",
+ "config-page-dbconnect": "ഡേറ്റാബേസുമായി ബന്ധപ്പെടുക",
+ "config-page-upgrade": "നിലവിലുള്ള ഇൻസ്റ്റലേഷൻ അപ്‌ഗ്രേഡ് ചെയ്യുക",
+ "config-page-dbsettings": "ഡേറ്റാബേസ് സജ്ജീകരണങ്ങൾ",
+ "config-page-name": "പേര്",
+ "config-page-options": "ഐച്ഛികങ്ങൾ",
+ "config-page-install": "ഇൻസ്റ്റോൾ",
+ "config-page-complete": "സമ്പൂർണ്ണം!",
+ "config-page-restart": "ഇൻസ്റ്റലേഷൻ അടച്ച ശേഷം പുനർപ്രവർത്തിപ്പിക്കുക",
+ "config-page-readme": "ഇത് വായിക്കൂ",
+ "config-page-releasenotes": "പ്രകാശന കുറിപ്പുകൾ",
+ "config-page-copying": "പകർത്തൽ",
+ "config-page-upgradedoc": "അപ്‌ഗ്രേഡിങ്",
+ "config-help-restart": "ഇതുവരെ ഉൾപ്പെടുത്തിയ എല്ലാവിവരങ്ങളും ഒഴിവാക്കാനും ഇൻസ്റ്റലേഷൻ പ്രക്രിയ നിർത്തി-വീണ്ടുമാരംഭിക്കാനും താങ്കളാഗ്രഹിക്കുന്നുണ്ടോ?",
+ "config-restart": "അതെ, പുനർപ്രവർത്തിപ്പിക്കുക",
+ "config-sidebar": "* [//www.mediawiki.org മീഡിയവിക്കി പ്രധാനതാൾ]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents ഉപയോക്തൃസഹായി]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents കാര്യനിർവഹണസഹായി]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ പതിവുചോദ്യങ്ങൾ]",
+ "config-env-php": "പി.എച്ച്.പി. $1 ഇൻസ്റ്റോൾ ചെയ്തിട്ടുണ്ട്.",
+ "config-no-db": "അനുയോജ്യമായ ഡേറ്റാബേസ് ഡ്രൈവർ കണ്ടെത്താനായില്ല!",
+ "config-memory-raised": "പി.എച്ച്.പി.യുടെ <code>memory_limit</code> $1 ആണ്, $2 ആയി ഉയർത്തിയിരിക്കുന്നു.",
+ "config-memory-bad": "'''മുന്നറിയിപ്പ്:''' പി.എച്ച്.പി.യുടെ <code>memory_limit</code> $1 ആണ്.\nഇത് മിക്കവാറും വളരെ കുറവാണ്.\nഇൻസ്റ്റലേഷൻ പരാജയപ്പെട്ടേക്കാം!",
+ "config-db-type": "ഡേറ്റാബേസ് തരം:",
+ "config-db-host": "ഡേറ്റാബേസ് ഹോസ്റ്റ്:",
+ "config-db-name": "ഡേറ്റാബേസിന്റെ പേര്:",
+ "config-db-name-oracle": "ഡേറ്റാബേസ് സ്കീമ:",
+ "config-db-install-account": "ഇൻസ്റ്റലേഷനുള്ള ഉപയോക്തൃ അംഗത്വം",
+ "config-db-username": "ഡേറ്റാബേസ് ഉപയോക്തൃനാമം:",
+ "config-db-password": "ഡേറ്റാബേസ് രഹസ്യവാക്ക്:",
+ "config-mysql-old": "മൈഎസ്‌ക്യൂഎൽ $1 അഥവാ അതിലും പുതിയത് ആവശ്യമാണ്, താങ്കളുടെ പക്കൽ ഉള്ളത് $2 ആണ്.",
+ "config-db-port": "ഡേറ്റാബേസ് പോർട്ട്:",
+ "config-db-schema": "മീഡിയവിക്കിയ്ക്കായുള്ള സ്കീമ",
+ "config-support-info": "മീഡിയവിക്കി താഴെ പറയുന്ന ഡേറ്റാബേസ് സിസ്റ്റംസ് പിന്തുണയ്ക്കുന്നു:\n\n$1\n\nതാങ്കൾ ഉപയോഗിക്കാനാഗ്രഹിക്കുന്ന ഡേറ്റാബേസ് സിസ്റ്റം പട്ടികയിലില്ലെങ്കിൽ, ദയവായി പിന്തുണ സജ്ജമാക്കാനായി മുകളിൽ നൽകിയിട്ടുള്ള ലിങ്കിലെ നിർദ്ദേശങ്ങൾ ചെയ്യുക.",
+ "config-header-mysql": "മൈഎസ്‌ക്യൂഎൽ സജ്ജീകരണങ്ങൾ",
+ "config-invalid-db-type": "അസാധുവായ ഡേറ്റാബേസ് തരം",
+ "config-missing-db-name": "\"ഡേറ്റാബേസിന്റെ പേരി\"ന് ഒരു വില നിർബന്ധമായും നൽകിയിരിക്കണം",
+ "config-connection-error": "$1.\n\nതാഴെ നൽകിയിരിക്കുന്ന ഹോസ്റ്റ്, ഉപയോക്തൃനാമം, രഹസ്യവാക്ക് എന്നിവ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.",
+ "config-regenerate": "LocalSettings.php പുനഃസൃഷ്ടിക്കുക →",
+ "config-mysql-engine": "സ്റ്റോറേജ് എൻജിൻ:",
+ "config-site-name": "വിക്കിയുടെ പേര്:",
+ "config-site-name-help": "ഇത് ബ്രൗസറിന്റെ ടൈറ്റിൽ ബാറിലും മറ്റനേകം ഇടങ്ങളിലും പ്രദർശിപ്പിക്കപ്പെടും.",
+ "config-site-name-blank": "സൈറ്റിന്റെ പേര് നൽകുക.",
+ "config-project-namespace": "പദ്ധതി നാമമേഖല:",
+ "config-ns-generic": "പദ്ധതി",
+ "config-ns-site-name": "വിക്കിയുടെ പേര് തന്നെ: $1",
+ "config-ns-other": "ഇതരം (വ്യക്തമാക്കുക)",
+ "config-ns-other-default": "എന്റെ‌വിക്കി",
+ "config-admin-box": "കാര്യനിർവാഹക അംഗത്വം",
+ "config-admin-name": "താങ്കളുടെ പേര്:",
+ "config-admin-password": "രഹസ്യവാക്ക്:",
+ "config-admin-password-confirm": "രഹസ്യവാക്ക് ഒരിക്കൽക്കൂടി:",
+ "config-admin-help": "ഇവിടെ താങ്കളുടെ ഇച്ഛാനുസരണമുള്ള ഉപയോക്തൃനാമം നൽകുക, ഉദാഹരണം \"ശശി കൊട്ടാരത്തിൽ\".\nഈ പേരായിരിക്കണം വിക്കിയിൽ പ്രവേശിക്കാൻ താങ്കൾ ഉപയോഗിക്കേണ്ടത്.",
+ "config-admin-name-blank": "ഒരു കാര്യനിർവാഹക ഉപയോക്തൃനാമം നൽകുക.",
+ "config-admin-name-invalid": "നൽകിയിട്ടുള്ള ഉപയോക്തൃനാമം \"<nowiki>$1</nowiki>\" അസാധുവാണ്.\nമറ്റൊരു ഉപയോക്തൃനാമം നൽകുക.",
+ "config-admin-password-blank": "കാര്യനിർവാഹക അംഗത്വത്തിനുള്ള രഹസ്യവാക്ക് നൽകുക.",
+ "config-admin-password-mismatch": "താങ്കൾ നൽകിയ രഹസ്യവാക്കുകൾ രണ്ടും തമ്മിൽ യോജിക്കുന്നില്ല.",
+ "config-admin-email": "ഇമെയിൽ വിലാസം:",
+ "config-admin-error-user": "\"<nowiki>$1</nowiki>\" എന്ന പേരിലുള്ള കാര്യനിർവഹണ അംഗത്വ നിർമ്മിതിയ്ക്കിടെ ആന്തരികമായ പിഴവുണ്ടായി.",
+ "config-admin-error-password": "\"<nowiki>$1</nowiki>\" എന്ന പേരിലുള്ള കാര്യനിർവാഹക അംഗത്വത്തിനു രഹസ്യവാക്ക് സജ്ജീകരിച്ചപ്പോൾ ആന്തരികമായ പിഴവുണ്ടായി: <pre>$2</pre>",
+ "config-subscribe": "[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce പ്രകാശന അറിയിപ്പ് മെയിലിങ് ലിസ്റ്റിൽ] വരിക്കാരാകുക.",
+ "config-subscribe-help": "പുറത്തിറക്കൽ അറിയിപ്പുകളും, പ്രധാന സുരക്ഷാ അറിയിപ്പുകളും പ്രസിദ്ധീകരിക്കുന്ന വളരെ എഴുത്തുകളൊന്നും ഉണ്ടാകാറില്ലാത്ത മെയിലിങ് ലിസ്റ്റ് ആണിത്.\nപുതിയ പതിപ്പുകൾ പുറത്ത് വരുന്നതനുസരിച്ച് അവയെക്കുറിച്ചറിയാനും മീഡിയവിക്കി ഇൻസ്റ്റലേഷൻ പുതുക്കാനും ഇതിന്റെ വരിക്കാരൻ/വരിക്കാരി ആവുക.",
+ "config-almost-done": "മിക്കവാറും പൂർത്തിയായിരിക്കുന്നു!\nബാക്കിയുള്ളവ അവഗണിച്ച് വിക്കി ഇൻസ്റ്റോൾ ചെയ്യാവുന്നതാണ്.",
+ "config-optional-continue": "കൂടുതൽ ചോദ്യങ്ങൾ ചോദിക്കൂ.",
+ "config-optional-skip": "എനിക്ക് മടുത്തു, ഒന്ന് ഇൻസ്റ്റോൾ ചെയ്ത് തീർക്ക്.",
+ "config-profile-wiki": "പരമ്പരാഗത വിക്കി",
+ "config-profile-no-anon": "അംഗത്വ സൃഷ്ടി ചെയ്യേണ്ടതുണ്ട്",
+ "config-profile-fishbowl": "അനുവാദമുള്ളവർ മാത്രം തിരുത്തുക",
+ "config-profile-private": "സ്വകാര്യ വിക്കി",
+ "config-license": "പകർപ്പവകാശവും അനുമതിയും:",
+ "config-license-cc-by-sa": "ക്രിയേറ്റീവ് കോമൺസ് ആട്രിബ്യൂഷൻ ഷെയർ എലൈക്",
+ "config-license-cc-by-nc-sa": "ക്രിയേറ്റീവ് കോമൺസ് ആട്രിബ്യൂഷൻ നോൺ-കൊമേഴ്സ്യൽ ഷെയർ എലൈക്",
+ "config-license-pd": "പൊതുസഞ്ചയം",
+ "config-email-settings": "ഇമെയിൽ സജ്ജീകരണങ്ങൾ",
+ "config-enable-email-help": "ഇമെയിൽ പ്രവർത്തിക്കണമെങ്കിൽ, [http://www.php.net/manual/en/mail.configuration.php PHP's മെയിൽ സജ്ജീകരണങ്ങൾ] ശരിയായി ക്രമീകരിക്കേണ്ടതുണ്ട്.\nഇമെയിൽ സൗകര്യം ആവശ്യമില്ലെങ്കിൽ, ഇവിടെത്തന്നെ അത് നിർജ്ജീവമാക്കാം.",
+ "config-email-user": "ഉപയോക്താക്കൾ തമ്മിലുള്ള ഇമെയിൽ പ്രവർത്തനസജ്ജമാക്കുക",
+ "config-email-user-help": "സ്വന്തം ക്രമീകരണങ്ങളിൽ ഇമെയിൽ സജ്ജമാക്കിയിട്ടുണ്ടെങ്കിൽ ഉപയോക്താക്കളെ മറ്റുള്ളവർക്ക് ഇമെയിൽ അയയ്ക്കാൻ അനുവദിക്കുക.",
+ "config-email-usertalk": "ഉപയോക്തൃസംവാദം താളിൽ മാറ്റങ്ങളുണ്ടായാൽ അറിയിക്കുക",
+ "config-email-watchlist": "ശ്രദ്ധിക്കുന്നവയിൽ മാറ്റം വന്നാൽ അറിയിക്കുക",
+ "config-email-auth": "ഇമെയിലിന്റെ സാധുതാപരിശോധന സജ്ജമാക്കുക",
+ "config-email-sender": "മറുപടിയ്ക്കുള്ള ഇമെയിൽ വിലാസം:",
+ "config-upload-settings": "ചിത്രങ്ങളും പ്രമാണങ്ങളും അപ്‌ലോഡ് ചെയ്യൽ",
+ "config-upload-enable": "പ്രമാണ അപ്‌ലോഡുകൾ സജ്ജമാക്കുക",
+ "config-upload-deleted": "മായ്ക്കപ്പെട്ട ഫയലുകൾക്കുള്ള ഡയറക്റ്ററി:",
+ "config-logo": "ലോഗോയുടെ യൂ.ആർ.എൽ.:",
+ "config-logo-help": "മീഡിയവിക്കിയിൽ സ്വതേയുള്ള ദൃശ്യരൂപത്തിൽ 135x160 പിക്സലുള്ള ലോഗോ മുകളിൽ ഇടത് മൂലയിൽ കാണാം.\nഅനുയോജ്യമായ വലിപ്പമുള്ള ഒരു ചിത്രം അപ്‌ലോഡ് ചെയ്തിട്ട്, അതിന്റെ യൂ.ആർ.എൽ. ഇവിടെ നൽകുക.\n\nതാങ്കൾക്ക് ലോഗോ ആവശ്യമില്ലെങ്കിൽ, ഈ പെട്ടി ശൂന്യമായിടുക.",
+ "config-cc-again": "ഒന്നുകൂടി എടുക്കൂ...",
+ "config-advanced-settings": "വിപുലീകൃത ക്രമീകരണങ്ങൾ",
+ "config-extensions": "അനുബന്ധങ്ങൾ",
+ "config-install-step-done": "ചെയ്തു കഴിഞ്ഞു",
+ "config-install-step-failed": "പരാജയപ്പെട്ടു",
+ "config-install-extensions": "അനുബന്ധങ്ങൾ ഉൾപ്പെടുത്തുന്നു",
+ "config-install-database": "ഡേറ്റാബേസ് സജ്ജമാക്കുന്നു",
+ "config-install-pg-commit": "മാറ്റങ്ങൾ സ്വീകരിക്കുന്നു",
+ "config-install-user": "ഡേറ്റാബേസ് ഉപയോക്താവിനെ സൃഷ്ടിക്കുന്നു",
+ "config-install-sysop": "കാര്യനിർവാഹക അംഗത്വം സൃഷ്ടിക്കുന്നു",
+ "config-install-mainpage": "സ്വാഭാവിക ഉള്ളടക്കത്തോടുകൂടി പ്രധാനതാൾ സൃഷ്ടിക്കുന്നു",
+ "config-install-mainpage-failed": "പ്രധാന താൾ ഉൾപ്പെടുത്താൻ കഴിഞ്ഞില്ല: $1",
+ "config-install-done": "'''അഭിനന്ദനങ്ങൾ!'''\nതാങ്കൾ വിജയകരമായി മീഡിയവിക്കി സജ്ജീകരിച്ചിരിക്കുന്നു.\n\nഇൻസ്റ്റോളർ താങ്കളുടെ എല്ലാ ക്രമീകരണങ്ങളുമടങ്ങുന്ന <code>LocalSettings.php</code> ഫയൽ സൃഷ്ടിച്ചിട്ടുണ്ട്.\n\nപ്രസ്തുത പ്രമാണം ഡൗൺലോഡ് ചെയ്ത് താങ്കളുടെ വിക്കി സജ്ജീകരണത്തിന്റെ അടിസ്ഥാന ഡയറക്റ്ററിയിൽ ഇടേണ്ടതാണ് (index.php കിടക്കുന്ന അതേ ഡയറക്റ്ററിയിൽ). ഡൗൺലോഡിങ്ങ് സ്വയം ആരംഭിക്കുന്നതാണ്. ഡൗൺലോഡിങ്ങ് സ്വയം തുടങ്ങാതിരിക്കുകയോ, താങ്കൾ റദ്ദാക്കുകയോ ചെയ്ത പക്ഷം താഴെ കാണുന്ന കണ്ണിയിൽ ഞെക്കുക:\n$3\n\n'''ശ്രദ്ധിക്കുക''': താങ്കൾ ഇപ്പോൾ ചെയ്തില്ലെങ്കിൽ, ഫയൽ എടുക്കാതെ ഇൻസ്റ്റലേഷൻ പ്രക്രിയയിൽ നിന്ന് പുറത്തിറങ്ങിയാൽ, സൃഷ്ടിക്കപ്പെട്ട ക്രമീകരണങ്ങളടങ്ങുന്ന പ്രമാണം പിന്നീട് ലഭ്യമായിരിക്കില്ല.\n\nമുകളിൽ പറഞ്ഞ പ്രകാരം ചെയ്തു കഴിഞ്ഞാൽ, താങ്കൾക്ക് '''[$2 വിക്കിയിൽ പ്രവേശിക്കാവുന്നതാണ്]'''.",
+ "mainpagetext": "'''മീഡിയവിക്കി വിജയകരമായി സജ്ജീകരിച്ചിരിക്കുന്നു.'''",
+ "mainpagedocfooter": "വിക്കി സോഫ്റ്റ്‌വെയർ ഉപയോഗിക്കുന്നതിനെ കുറിച്ചുള്ള വിശദാംശങ്ങൾക്ക് [//meta.wikimedia.org/wiki/Help:Contents സോഫ്റ്റ്‌വെയർ സഹായി] കാണുക.\n\n== പ്രാരംഭസഹായികൾ ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings ക്രമീകരണങ്ങളുടെ പട്ടിക]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ മീഡിയവിക്കി പതിവുചോദ്യങ്ങൾ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce മീഡിയവിക്കി പ്രകാശന മെയിലിങ് ലിസ്റ്റ്]"
+}
diff --git a/includes/installer/i18n/mn.json b/includes/installer/i18n/mn.json
new file mode 100644
index 00000000..c3a72899
--- /dev/null
+++ b/includes/installer/i18n/mn.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chinneeb"
+ ]
+ },
+ "config-page-language": "Хэл",
+ "mainpagetext": "'''МедиаВики амжилттай суулаа.'''",
+ "mainpagedocfooter": "Вики программыг хэрэглэх талаар заавар авахын тулд [//meta.wikimedia.org/wiki/Help:Contents хэрэглэгчийн гарын авлага]-г үзнэ үү.\n\n== Эхлэх ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Тохиргоо]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ МедиаВикигийн тогтмол тавигддаг асуултууд]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаВикигийн мэдээний мэйл явуулах жагсаалт]"
+}
diff --git a/includes/installer/i18n/mr.json b/includes/installer/i18n/mr.json
new file mode 100644
index 00000000..632951b7
--- /dev/null
+++ b/includes/installer/i18n/mr.json
@@ -0,0 +1,62 @@
+{
+ "@metadata": {
+ "authors": [
+ "V.narsikar"
+ ]
+ },
+ "config-information": "माहिती",
+ "config-localsettings-key": "दर्जोन्नती कळ:",
+ "config-localsettings-badkey": "आपण दिलेली कळ चुकीची आहे.",
+ "config-session-error": "सत्र सुरू करण्यात त्रूटी:$1",
+ "config-your-language": "आपली भाषा:",
+ "config-your-language-help": "उभारणी प्रक्रियेत वापरावयाची भाषा निवडा.",
+ "config-wiki-language": "विकी भाषा:",
+ "config-back": "← परत",
+ "config-continue": "चालू ठेवा →",
+ "config-page-language": "भाषा",
+ "config-page-welcome": "मिडियाविकीवर स्वागत आहे!",
+ "config-page-upgrade": "सध्याच्या उभारणीची(इन्स्टॉलेशन) दर्जोन्नती करा",
+ "config-page-name": "नाव",
+ "config-page-options": "पर्याय",
+ "config-page-install": "उभारा(इन्स्टॉल)",
+ "config-page-complete": "पूर्ण!",
+ "config-page-readme": "हे वाचा",
+ "config-page-releasenotes": "विमोचन टिप्पण्या",
+ "config-page-existingwiki": "साध्याचा विकि",
+ "config-pg-test-error": "विदागाराशी अनुबंधन करता येत नाही <strong>$1</strong>: $2",
+ "config-type-mssql": "मायक्रोसॉफ्ट एसक्युएल सर्व्हर",
+ "config-header-mssql": "मायक्रोसॉफ्ट एसक्युएल सर्व्हर मांडणावळ",
+ "config-mssql-old": "मायक्रोसॉफ्ट एसक्युएल सर्व्हर $1 किंवा त्यानंतरची आवृत्ती हवी. आपणापाशी $2 आहे.",
+ "config-mssql-auth": "अधिप्रमाणन प्रकार:",
+ "config-mssql-install-auth": "उभारणीच्या(इन्स्टॉलेशन) प्रक्रियेदरम्यान,'अधिप्रमाणन प्रकार'( ऑथेंटीकेशन टाईप) निवडा, ज्याचा वापर डाटाबेसशी अनुबंधनात करण्यात येईल.जर आपण \"विंडोज ऑथेंटीकेशन\" निवडले तर,ज्याकोणत्याही सदस्याची अधिकारपत्रे(क्रेडेंटियल्स) वेबसर्व्हरवर सुरू असतील,तशीच वापरल्या जातील.",
+ "config-mssql-web-auth": "या विकिचे सामन्य चालनादरम्यान,'अधिप्रमाणन प्रकार'( ऑथेंटीकेशन टाईप) निवडा, ज्याचा वापर डाटाबेसशी अनुबंधनात करण्यात येईल.जर आपण \"विंडोज ऑथेंटीकेशन\" निवडले तर,ज्याकोणत्याही सदस्याची अधिकारपत्रे(क्रेडेंटियल्स) वेबसर्व्हरवर सुरू असतील,तशीच वापरल्या जातील.",
+ "config-mssql-sqlauth": "एसक्युएल सर्व्हर अधिप्रमाणन",
+ "config-mssql-windowsauth": "विंडोजचे अधिप्रमाणन",
+ "config-site-name": "विकिचे नाव:",
+ "config-site-name-blank": "संकेतस्थळाचे नाव टाका.",
+ "config-project-namespace": "प्रकल्प नामविश्व:",
+ "config-ns-generic": "प्रकल्प",
+ "config-admin-name": "आपले सदस्यनाव:",
+ "config-admin-password": "परवलीचा शब्द:",
+ "config-admin-password-confirm": "परवलीचा शब्द पुन्हा टाका:",
+ "config-admin-email": "विपत्र पत्ता:",
+ "config-admin-error-bademail": "आपण अवैध विपत्रपत्ता टाकला आहे.",
+ "config-profile-no-anon": "खाते तयार करणे आवश्यक",
+ "config-profile-fishbowl": "फक्त प्रमाणित संपादक",
+ "config-profile-private": "खाजगी विकि",
+ "config-license": "प्रताधिकार व परवाना",
+ "config-email-user": "सदस्य ते सदस्य विपत्र पाठविणे सक्षम करा",
+ "config-email-usertalk": "सदस्य चर्चा पान अधिसूचना सक्षम करा",
+ "config-email-watchlist": "निरीक्षणसूची अधिसूचना सक्षम करा",
+ "config-email-sender": "प्रत्युत्तराचा विपत्रपत्ता:",
+ "config-upload-settings": "संचिका अपभारणे",
+ "config-upload-enable": "संचिका अपभारणे सक्षम करा",
+ "config-extensions": "विस्तारके",
+ "config-install-step-done": "झाले",
+ "config-install-extensions": "विस्तारके अंतर्भूत करून",
+ "config-install-tables": "सारण्या बनवित आहे",
+ "config-install-tables-failed": "<strong>त्रूटी:</strong>खालील त्रूटीमुळे सारणी बनविणे अयशस्वी:$1",
+ "config-help": "साहाय्य",
+ "mainpagetext": "'''मीडियाविकीचे इन्स्टॉलेशन पूर्ण.'''",
+ "mainpagedocfooter": "विकी सॉफ्टवेअर वापरण्याकरिता [//meta.wikimedia.org/wiki/Help:Contents यूजर गाईड] पहा.\n\n== सुरुवात ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings कॉन्फिगरेशन सेटींगची यादी]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ मीडियाविकी नेहमी विचारले जाणारे प्रश्न]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मीडियाविकि मेलिंग लिस्ट]"
+}
diff --git a/includes/installer/i18n/ms.json b/includes/installer/i18n/ms.json
new file mode 100644
index 00000000..879a330b
--- /dev/null
+++ b/includes/installer/i18n/ms.json
@@ -0,0 +1,151 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anakmalaysia",
+ "Pizza1016",
+ "SNN95",
+ "MaxSem",
+ "Aviator"
+ ]
+ },
+ "config-desc": "Pemasang MediaWiki",
+ "config-title": "Pemasangan MediaWiki $1",
+ "config-information": "Maklumat",
+ "config-localsettings-upgrade": "Fail <code>LocalSettings.php</code> telah dikesan.\nUntuk menaik taraf pemasangan, sila masukkan nilai <code>$wgUpgradeKey</code> dalam kotak di bawah.\nAnda akan menjumpainya di <code>LocalSettings.php</code> .",
+ "config-localsettings-cli-upgrade": "Fail <code>LocalSettings.php</code> telah dikesan.\nUntuk menaik taraf pemasangan, sila jalankan <code>update.php</code> sebaliknya",
+ "config-localsettings-key": "Kunci naik taraf:",
+ "config-localsettings-badkey": "Kunci yang anda berikan tidak betul.",
+ "config-upgrade-key-missing": "Pemasangan yang sedia ada MediaWiki telah dikesan.\nUntuk menaik taraf pemasangan, Sila letakkan baris berikut di bahagian bawah <code>LocalSettings.php</code> anda:\n\n$1",
+ "config-localsettings-incomplete": "<code>LocalSettings.php</code> sedia ada nampaknya tidak lengkap.\nPemboleh ubah $1 tidak disetkan.\nSila tukar <code>LocalSettings.php</code> supaya pemboleh ubah ini disetkan, dan klik \"{{int:Config-terus}}\".",
+ "config-localsettings-connection-error": "Ralat berlaku semasa menyambung ke pangkalan data dengan menggunakan tetapan yang dinyatakan dalam <code>LocalSettings.php</code>. Sila betulkan tetapan tersebut dan cuba lagi.\n\n$1",
+ "config-session-error": "Ralat ketika memulakan sesi: $1",
+ "config-session-expired": "Data sesi anda seolah-olah telah tamat tempoh.\nSesi dikonfigurasi untuk seumur hidup sebanyak $1.\nAnda boleh menambah ini dengan menetapkan <code>session.gc_maxlifetime</code> di php.ini.\nMemulakan semula proses pemasangan.",
+ "config-no-session": "Data sesi anda telah hilang!\nSemak php.ini anda dan pastikan <code>session.save_path</code> disetkan kepada satu direktori yang sesuai.",
+ "config-your-language": "Bahasa anda:",
+ "config-your-language-help": "Pilihkan bahasa untuk digunakan dalam proses pemasangan ini.",
+ "config-wiki-language": "Bahasa wiki:",
+ "config-wiki-language-help": "Pilih bahasa utama wiki yang bakal dicipta ini.",
+ "config-back": "← Undur",
+ "config-continue": "Teruskan →",
+ "config-page-language": "Bahasa",
+ "config-page-welcome": "Selamat datang ke MediaWiki!",
+ "config-page-dbconnect": "Bersambung dengan pangkalan data",
+ "config-page-upgrade": "Naik taraf pemasangan sedia ada",
+ "config-page-dbsettings": "Tetapan pangkalan data",
+ "config-page-name": "Nama",
+ "config-page-options": "Pilihan",
+ "config-page-install": "Pasang",
+ "config-page-complete": "Selesai!",
+ "config-page-restart": "Mulakan semula pemasangan",
+ "config-page-readme": "Baca saya",
+ "config-page-releasenotes": "Catatan keluaran",
+ "config-page-copying": "Sedang menyalin",
+ "config-page-upgradedoc": "Sedang menaik taraf",
+ "config-page-existingwiki": "Wiki sedia ada",
+ "config-help-restart": "Adakah anda ingin untuk membersihkan semua data yang disimpan yang anda telah masukkan dan memulakan semula proses pemasangan?",
+ "config-restart": "Ya, mula semula",
+ "config-welcome": "=== Pemeriksaan persekitaran ===\nPemeriksaan asas kini boleh dilakukan untuk melihat jika persekitaran ini adalah sesuai untuk pemasangan MediaWiki.\nIngat untuk memasukkan maklumat ini jika anda mahukan sokongan tentang bagaimana untuk menyelesaikan pemasangan.",
+ "config-copyright": "=== Hakcipta dan Syarat-Syarat ===\n\n$1\n\nProgram ini merupakan perisian bebas; anda boleh mengedarkannya semula dan/atau mengubahsuainya di bawah syarat-syarat Lesen Awam GNU seperti yang diterbitkan oleh Yayasan Perisian Bebas; sama ada versi 2 Lesen ini atau (mengikut pilihan anda) mana-mana versi selepas ini.\n\nProgram ini diedarkan dengan harapan bahawa ia akan menjadi berguna, tetapi '''tanpa sebarang waranti'''; tanpa jaminan yang tersirat '''kebolehdagangan''' atau '''kesesuaian untuk tujuan tertentu'''.\nLihat Lesen Awam GNU untuk maklumat lanjut.\n\nAnda sepatutnya telah menerima <doclink href=Copying> satu salinan Lesen Awam GNU </doclink> bersama-sama dengan program ini, jika tidak, menulis surat kepada Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, atau [http://www.gnu.org/copyleft/gpl.html membacanya dalam talian].",
+ "config-sidebar": "* [//www.mediawiki.org Laman utama MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Panduan Pengguna]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Panduan Penyelia]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Soalan lazim]\n----\n* <doclink href=Readme>Baca saya</doclink>\n* <doclink href=ReleaseNotes>Nota keluaran</doclink>\n* <doclink href=Copying>Menyalin</doclink>\n* <doclink href=UpgradeDoc>Menaik taraf</doclink>",
+ "config-env-good": "Persekitaran telah diperiksa.\nAnda boleh memasang MediaWiki.",
+ "config-env-bad": "Persekitaran telah diperiksa. \nAnda tidak boleh memasang MediaWiki.",
+ "config-env-php": "PHP $1 dipasang.",
+ "config-unicode-using-utf8": "utf8_normalize.so oleh Brion Vibber digunakan untuk penormalan Unicode.",
+ "config-unicode-using-intl": "[http://pecl.php.net/intl Sambungan intl PECL] digunakan untuk penormalan Unicode.",
+ "config-unicode-update-warning": "<strong>Amaran:</strong> Versi pembalut penormalan Unicode yang terpasang menggunakan perpustakaan [http://site.icu-project.org/ projek ICU] dalam versi yang lampau.\nAnda harus [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations menaik taraf] jika Unicode penting bagi anda.",
+ "config-outdated-sqlite": "<strong>Amaran:</strong> anda mempunyai SQLite $1, yang lebih rendah daripada versi keperluan minimum $1. SQLite tidak akan disediakan.",
+ "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-mbstring": "<strong>Amaran keras: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] sedang aktif!</strong>\nOpsyen ini menyebabkan ralat dan mungkin mencemari data secara tanpa diduga.\nAnda tidak boleh memasang atau menggunakan MediaWiki melainkan opsyen ini dinyahdayakan.",
+ "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 adalah $1.\nIni mungkin terlalu rendah.\nPemasangan mungkin akan gagal!",
+ "config-ctype": "<strong>Amaran keras:</strong> PHP mesti disusun dengan sokongan untuk [http://www.php.net/manual/en/ctype.installation.php sambungan Ctype].",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] dipasang",
+ "config-apc": "[http://www.php.net/apc APC] dipasang",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] dipasang",
+ "config-diff3-bad": "GNU diff3 tidak dijumpai.",
+ "config-git": "Perisian kawalan versi Git dijumpai: <code>$1</code>.",
+ "config-git-bad": "Perisian kawalan versi Git tidak dijumpai.",
+ "config-no-cli-uri": "<strong>Amaran:</strong> Tiada <code>--scriptpath</code> dinyatakan, maka digunakannya yang asali: <code>$1</code>.",
+ "config-using-server": "Sedang menggunakan nama pelayan \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Sedang menggunakan URL pelayan \"<nowiki>$1$2</nowiki>\".",
+ "config-no-cli-uploads-check": "<strong>Amaran:</strong> Direktori asali anda untuk muat naikan (<code>$1</code>) belum diperiksa untuk kerentanan\nkepada pelaksanaan skrip yang menyeleweng sewaktu pemasangan CLI.",
+ "config-db-type": "Jenis pangkalan data:",
+ "config-db-host": "Hos pangkalan data:",
+ "config-db-host-oracle": "TNS pangkalan data:",
+ "config-db-name": "Nama pangkalan data:",
+ "config-db-name-oracle": "Skema pangkalan data:",
+ "config-db-username": "Nama pengguna pangkalan data:",
+ "config-db-password": "Kata laluan pangkalan data:",
+ "config-db-prefix": "Awalan jadual pangkalan data:",
+ "config-db-charset": "Peranggu aksara pangkalan data",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 dedua",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-db-port": "Port pangkalan data:",
+ "config-db-schema": "Skema untuk MediaWiki:",
+ "config-pg-test-error": "Tidak boleh bersambung dengan pangkalan data <strong>$1</strong>: $2",
+ "config-sqlite-dir": "Direktori data SQLite:",
+ "config-oracle-def-ts": "Ruang jadual lalai:",
+ "config-oracle-temp-ts": "Ruang jadual sementara:",
+ "config-type-mysql": "MySQL (atau yang serasi)",
+ "config-header-mysql": "Keutamaan MySQL",
+ "config-header-postgres": "Keutamaan PostgreSQL",
+ "config-header-sqlite": "Keutamaan SQLite",
+ "config-header-oracle": "Keutamaan Oracle",
+ "config-header-mssql": "Tetapan Microsoft SQL Server",
+ "config-invalid-db-type": "Jenis pangkalan data tidak sah",
+ "config-can-upgrade": "Terdapat jadual MediaWiki dalam pangkalan data ini. Untuk menaik tarafnya kepada MediaWiki $1, klik <strong>Teruskan</strong>.",
+ "config-unknown-collation": "<strong>Amaran:</strong> Pangkalan data sedang menggunakan kolasi yang tidak dikenali.",
+ "config-db-web-account-same": "Gunakan akaun yang sama seperti dalam pemasangan",
+ "config-db-web-create": "Ciptakan akaun jika belum wujud",
+ "config-mysql-engine": "Enjin storan:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-only-myisam-dep": "<strong>Amaran:</strong> MyISAM adalah satu-satunya enjin storan yang terdapat untuk MySQL di mesin ini, dan penggunaannya dengan MediaWiki tidak digalakkan kerana:\n* ia tidak menyokong keserempakan (''concurrency'') disebabkan penguncian jadual\n* ia lebih terdedah kepada korupsi daripada enjin-enjin lain\n* pangkalan kod MediaWiki tidak sentiasa mengendalikan MyISAM seperti yang diharapkan\n\nPemasangan MySQL anda tidak menyokong InnoDB. Mungkin tiba masanya untuk naik taraf.",
+ "config-mysql-charset": "Peranggu aksara pangkalan data:",
+ "config-mysql-binary": "Perduaan",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Nama wiki:",
+ "config-site-name-help": "Ini akan dipaparkan pada bar tajuk perisian pelayar dan tempat-tempat lain yang berkenaan.",
+ "config-site-name-blank": "Isikan nama tapak.",
+ "config-project-namespace": "Ruang nama projek:",
+ "config-ns-generic": "Projek",
+ "config-ns-site-name": "Sama dengan nama wiki: $1",
+ "config-ns-other": "Lain-lain (nyatakan)",
+ "config-ns-other-default": "MyWiki",
+ "config-admin-box": "Akaun penyelia",
+ "config-admin-name": "Nama pengguna anda:",
+ "config-admin-password": "Kata laluan:",
+ "config-admin-password-confirm": "Kata laluan lagi:",
+ "config-admin-name-blank": "Masukkan nama pengguna pentadbir.",
+ "config-admin-password-mismatch": "Kata-kata laluan yang kamu berikan tidak sepadan.",
+ "config-admin-email": "Alamat e-mel:",
+ "config-admin-error-bademail": "Kamu telah memberikan alamat e-mel yang tidak betul.",
+ "config-optional-skip": "Saya sudah bosan, pasangkanlah wiki sahaja.",
+ "config-profile-wiki": "Wiki terbuka",
+ "config-profile-no-anon": "Pembukaan akaun diwajibkan",
+ "config-profile-private": "Wiki tertutup",
+ "config-license": "Hak cipta dan lesen:",
+ "config-license-none": "Tiada pengaki lesen",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (Domain Awam)",
+ "config-license-gfdl": "Lesen Pendokumenan Bebas GNU 1.3 atau ke atas",
+ "config-license-pd": "Domain Awam",
+ "config-email-settings": "Tetapan e-mel",
+ "config-install-step-done": "siap",
+ "config-install-step-failed": "gagal",
+ "config-install-user-alreadyexists": "Pengguna \"$1\" sudah wujud",
+ "config-install-tables": "Mencipta jadual",
+ "config-install-tables-exist": "<strong>Amaran:</strong> Nampaknya sudah terdapat jadual MediaWiki. Penciptaan dilangkau.",
+ "config-install-interwiki": "Mengisi jadual antara wiki lalai",
+ "config-install-interwiki-list": "Fail <code>interwiki.list</code> tidak dapat dibaca.",
+ "config-install-interwiki-exists": "<strong>Amaran:</strong> Jadual antara wiki nampaknya sudah ada entri. Senarai asali dilangkau.",
+ "config-install-keys": "Menjana kunci-kunci rahsia",
+ "config-insecure-keys": "<strong>Amaran:</strong> {{PLURAL:$2|Kunci keselamatan|Kunci-kunci keselamatan}} ($1) yang dihasilkan sewaktu pemasangan itu {{PLURAL:$2|adalah}} tidak selamat sepenuhnya. Oleh itu, {{PLURAL:$2|ia}} wajar ditukar secara manual.",
+ "config-install-sysop": "Membuka akaun pengguna pentadbir",
+ "config-install-mainpage": "Mewujudkan laman utama dengan kandungan lalai",
+ "config-help": "bantuan",
+ "mainpagetext": "'''MediaWiki telah berjaya dipasang.'''",
+ "mainpagedocfooter": "Sila rujuk [//meta.wikimedia.org/wiki/Help:Contents Panduan Penggunaan] untuk maklumat mengenai penggunaan perisian wiki ini.\n\n== Permulaan ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Senarai tetapan konfigurasi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Soalan Lazim MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Senarai surat keluaran MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Terjemahkan MediaWiki ke dalam bahasa anda]"
+}
diff --git a/includes/installer/i18n/mt.json b/includes/installer/i18n/mt.json
new file mode 100644
index 00000000..262a8378
--- /dev/null
+++ b/includes/installer/i18n/mt.json
@@ -0,0 +1,90 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chrisportelli",
+ "Leli Forte"
+ ]
+ },
+ "config-title": "Installazzjoni ta' MediaWiki $1",
+ "config-information": "Informazzjoni",
+ "config-localsettings-key": "Ċavetta tal-aġġornament:",
+ "config-localsettings-badkey": "Iċ-ċavetta li tajt hija ħażina.",
+ "config-your-language": "Il-lingwa tiegħek:",
+ "config-your-language-help": "Agħżel lingwa li tixtieq tuża' matul il-proċess ta' installazzjoni.",
+ "config-wiki-language": "Lingwi tal-wiki:",
+ "config-wiki-language-help": "Agħżel il-lingwa li l-wiki se tkun l-aktar użata fil-wiki.",
+ "config-back": "← Lura",
+ "config-continue": "Kompli →",
+ "config-page-language": "Lingwa",
+ "config-page-welcome": "Merħba fuq MediaWiki!",
+ "config-page-dbconnect": "Aqbad mad-databażi",
+ "config-page-upgrade": "Aġġorna l-installazzjoni eżistenti",
+ "config-page-dbsettings": "Impostazzjonijiet tad-databażi",
+ "config-page-name": "Isem",
+ "config-page-options": "Għażliet",
+ "config-page-install": "Installa",
+ "config-page-complete": "Lesta!",
+ "config-page-restart": "Erġa' ibda l-installazzjoni",
+ "config-page-readme": "Aqrani",
+ "config-page-releasenotes": "Noti tal-verżjoni",
+ "config-page-upgradedoc": "Aġġornament",
+ "config-page-existingwiki": "Wiki eżistenti",
+ "config-restart": "Iva, erġa' ibda",
+ "config-env-php": "PHP $1 huwa installat.",
+ "config-env-hhvm": "HHVM $1 hu installat.",
+ "config-db-wiki-settings": "Identifika din il-wiki",
+ "config-db-name": "Isem tad-databażi:",
+ "config-db-install-account": "Kont tal-utent għall-installazzjoni",
+ "config-db-username": "Isem tal-utent tad-databażi:",
+ "config-db-password": "Password tad-databażi:",
+ "config-db-port": "Port tad-databażi:",
+ "config-db-schema": "Skema għal MediaWiki:",
+ "config-db-web-create": "Oħloq il-kont jekk għadu ma jeżistix",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Sett ta' karattri tad-databażi:",
+ "config-mysql-binary": "Binarju",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Isem tal-wiki:",
+ "config-site-name-help": "Dan se jidher fil-barra tat-titlu tal-browżer u f'diversi postijiet oħra.",
+ "config-site-name-blank": "Daħħal isem tas-sit.",
+ "config-project-namespace": "Spazju tal-ismijiet tal-proġett:",
+ "config-ns-generic": "Proġett",
+ "config-ns-site-name": "L-istess bħall-isem tal-wiki: $1",
+ "config-ns-other": "Oħrajn (speċifika)",
+ "config-ns-other-default": "MyWiki",
+ "config-ns-invalid": "L-ispazju speċifikat \"<nowiki>$1</nowiki>\" huwa ħażin.\nSpeċifika spazju tal-ismijiet ta' proġett differenti.",
+ "config-ns-conflict": "L-ispazju speċifikat \"<nowiki>$1</nowiki>\" joħloq kunflitt ma' spazju tal-ismijiet tal-MediaWiki predeterminat.\nSpeċifika spazju tal-ismijiet ta' proġett differenti.",
+ "config-admin-box": "Kont tal-amministratur",
+ "config-admin-name": "Ismek:",
+ "config-admin-password": "Password:",
+ "config-admin-password-confirm": "Erġa' daħħal il-password:",
+ "config-admin-help": "Daħħal l-isem tal-utent preferit hawnhekk, per eżempju \"Joe Borg\".\nDan huwa l-isem li se tuża' kull darba li tidħol fil-wiki.",
+ "config-admin-name-blank": "Daħħal isem tal-utent għall-amministratur.",
+ "config-admin-name-invalid": "L-isem tal-utent speċifikat \"<nowiki>$1</nowiki>\" huwa ħażin.\nSpeċifika isem tal-utent differenti.",
+ "config-admin-password-blank": "Daħħal password għall-kont tal-amministratur.",
+ "config-admin-password-mismatch": "Il-passwords li daħħalt ma jaqblux.",
+ "config-admin-email": "Indirizz elettroniku:",
+ "config-admin-error-bademail": "Daħħalt indirizz elettroniku ħażin.",
+ "config-almost-done": "Kważi lest!\nJekk trid tista' taqbeż il-parti li jmiss tal-konfigurazzjoni u sempliċiment tinstalla l-wiki.",
+ "config-optional-continue": "Staqsini aktar mistoqsijiet.",
+ "config-optional-skip": "Xbajt diġà, installa l-wiki.",
+ "config-profile-wiki": "Wiki tradizzjonali",
+ "config-profile-no-anon": "Huwa obbligatorju l-ħolqien tal-kont",
+ "config-profile-fishbowl": "Edituri awtorizzati biss",
+ "config-profile-private": "Wiki privata",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (dominju pubbliku)",
+ "config-license-pd": "Dominju pubbliku",
+ "config-license-cc-choose": "Agħżel waħda mil-liċenzji tal-Creative Commons",
+ "config-upload-deleted": "Direttorju għall-fajls imħassra:",
+ "config-upload-deleted-help": "Agħżel direttorju fejn iżżomm fajls imħassra.\nIdealment, dan m'għandux ikun aċċessibbli mill-web.",
+ "config-logo": "URL tal-logo:",
+ "config-download-localsettings": "Niżżel <code>LocalSettings.php</code>",
+ "config-help": "għajnuna",
+ "config-nofile": "Il-fajl \"$1\" ma setax jinstab. Dan ġie mħassar?",
+ "mainpagetext": "'''MediaWiki ġie installat b'suċċess.'''",
+ "mainpagedocfooter": "Ikkonsulta l-[//meta.wikimedia.org/wiki/Help:Contents Gwida għall-utenti] sabiex tikseb iktar informazzjoni dwar kif tuża' s-softwer tal-wiki.\n\n== Biex tibda ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista ta' preferenzi għall-konfigurazzjoni]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Mistoqsijiet rikorrenti fuq il-MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Il-lista tal-posta tħabbar 'l MediaWiki]"
+}
diff --git a/includes/installer/i18n/my.json b/includes/installer/i18n/my.json
new file mode 100644
index 00000000..1d9a78dd
--- /dev/null
+++ b/includes/installer/i18n/my.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lionslayer"
+ ]
+ },
+ "mainpagetext": "'''မီဒီယာဝီကီကို အောင်မြင်စွာ သွင်းပြီးပါပြီ။'''"
+}
diff --git a/includes/installer/i18n/myv.json b/includes/installer/i18n/myv.json
new file mode 100644
index 00000000..02e8b1cc
--- /dev/null
+++ b/includes/installer/i18n/myv.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Botuzhaleny-sodamo"
+ ]
+ },
+ "config-page-language": "Кель",
+ "config-page-name": "Лемезэ",
+ "config-page-readme": "Ловномак",
+ "config-admin-name": "Совамовалот:",
+ "config-admin-password": "Совамо валот:",
+ "config-admin-password-confirm": "Совамо валот одов:",
+ "config-admin-email": "Е-сёрма паргот:",
+ "config-install-step-done": "теезь",
+ "mainpagetext": "'''МедияВикинь тевс аравтомазо парсте лиссь.'''"
+}
diff --git a/includes/installer/i18n/mzn.json b/includes/installer/i18n/mzn.json
new file mode 100644
index 00000000..c2574818
--- /dev/null
+++ b/includes/installer/i18n/mzn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "محک"
+ ]
+ },
+ "config-help": "راهنما"
+}
diff --git a/includes/installer/i18n/nah.json b/includes/installer/i18n/nah.json
new file mode 100644
index 00000000..03ea59c2
--- /dev/null
+++ b/includes/installer/i18n/nah.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaHuiqui cualli ōmotlahtlāli.'''"
+}
diff --git a/includes/installer/i18n/nan.json b/includes/installer/i18n/nan.json
new file mode 100644
index 00000000..fbc2bbc1
--- /dev/null
+++ b/includes/installer/i18n/nan.json
@@ -0,0 +1,59 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ianbu"
+ ]
+ },
+ "config-desc": "MediaWiki的安裝程式",
+ "config-title": "MediaWiki $1的安裝",
+ "config-information": "資訊",
+ "config-localsettings-upgrade": "有一个<code>LocalSettings.php</code>檔案佇咧。若欲升級,請佇下面的框內底拍<code>$wgUpgradeKey</code>的內容。你會使佇<code>LocalSettings.php</code>內底揣著彼項。",
+ "config-localsettings-cli-upgrade": "有一个<code>LocalSettings.php</code>檔案。若欲升級,請直接執行<code>update.php</code>。",
+ "config-localsettings-key": "升級的密碼:",
+ "config-localsettings-badkey": "你提供的密碼無正確。",
+ "config-upgrade-key-missing": "已經有一个MediaWiki矣。若要升級,請共下面這逝加去<code>LocalSettings.php</code>的下跤:\n\n$1",
+ "config-localsettings-incomplete": "這馬的<code>LocalSettings.php</code>可能無齊全,因為無設變量$1。請佇<code>LocalSettings.php</code>設彼个變量,並且揤「{{int:Config-continue}}」。",
+ "config-localsettings-connection-error": "An error was encountered when connecting to the database 用<code>LocalSettings.php</code>的設定去連接資料庫的時陣有一个錯誤發生,請改遮的設定了,才閣試。\n\n$1",
+ "config-session-error": "連線開始了的錯誤:$1",
+ "config-session-expired": "你連線資料已經過時矣,連線的使用期限是設做$1。你會使改共php.ini的<code>session.gc_maxlifetime</code>改較長,並且重新安裝動作。",
+ "config-no-session": "你連線的資料已經無去矣,看你的php.ini,並且確定<code>session.save_path</code>是正確的目錄。",
+ "config-your-language": "你的話語:",
+ "config-your-language-help": "選一个安裝過程時欲用的話語",
+ "config-wiki-language": "Wiki話語",
+ "config-wiki-language-help": "選一个Wiki大部份用的話",
+ "config-back": "← 倒退",
+ "config-continue": "繼續 →",
+ "config-page-language": "話語",
+ "config-page-welcome": "歡迎來MediaWiki!",
+ "config-page-dbconnect": "連接去資料庫",
+ "config-page-upgrade": "共這馬的安裝升級",
+ "config-page-dbsettings": "資料庫的設定",
+ "config-page-name": "名稱",
+ "config-page-options": "選項",
+ "config-page-install": "安裝",
+ "config-page-complete": "完成",
+ "config-page-restart": "重裝",
+ "config-page-readme": "讀我",
+ "config-page-releasenotes": "發布的說明",
+ "config-page-copying": "複製",
+ "config-page-upgradedoc": "升級",
+ "config-page-existingwiki": "已經裝的Wiki",
+ "config-help-restart": "你敢欲共你拍的佮保存的資料攏清掉,並且重開始安裝的動作?",
+ "config-restart": "是,重來",
+ "config-welcome": "=== 環境檢測 ===\n這馬欲做基本的檢測,看環境是毋是適合裝 MediaWiki。\n若你愛有支援,才裝會起來,請共遮的資訊記起來。",
+ "config-copyright": "=== 版權聲明佮授權條款 ===\n\n$1\n\n本程式是自由軟體;你會當照自由軟體基金會所發表的 GNU 通用公共授權條款規定,共本程式重新發佈抑是修改;無論你是照本授權條款的第二版抑第二版以後的任何版本(你會當家己選) 。\n\n本程式發佈的目的是希望會當提供幫助,但是 <strong>無負任何擔保責任</strong>;抑無表示講對 <strong>販賣性</strong> 抑 <strong>特定用途的適用性</strong> 的情形擔保。詳情請參照 GNU 通用公共授權。\n\n你應該已隨本程式收著 <doclink href=\"Copying\">GNU 通用公共授權條款的副本</doclink>;若無,請寫批通知自由軟體基金會,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA,或 [http://www.gnu.org/copyleft/gpl.html 線頂看]。",
+ "config-sidebar": "* [www.mediawiki.org/wiki/MediaWiki/zh-hant MediaWiki 頭頁]\n* [www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/zh 使用者指南]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/zh 管理者指南]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/zh-hant 四常問題集]\n----\n* <doclink href=Readme>讀我說明</doclink>\n* <doclink href=ReleaseNotes>發行說明</doclink>\n* <doclink href=Copying>版權聲明</doclink>\n* <doclink href=UpgradeDoc>升級</doclink>",
+ "config-env-good": "環境檢查已完成。\n你會當安裝 MediaWiki。",
+ "config-env-bad": "環境檢查已完成。\n你無法度安裝 MediaWiki。",
+ "config-env-php": "PHP $1 已經安裝。",
+ "config-env-php-toolow": "已經安裝 PHP $1。\n但是 MediaWiki 愛 PHP $2 抑較新的版本。",
+ "config-unicode-using-utf8": "用 Brion Vibber 的 utf8_normalize.so 做 Unicode 正規化。",
+ "config-unicode-using-intl": "用 [http://pecl.php.net/intl intl PECL 擴充套件] 做 Unicode 正規化。",
+ "config-unicode-pure-php-warning": "<strong>警告:</strong> 無法度用 [http://pecl.php.net/intl intl PECL 擴充套件] 處理 Unicode 正規化,所以退回用純 PHP 實作的正規化程式,這種方式處理速度較慢。\n\n若你的網站瀏覽人數誠濟,你應該先看 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations/zh Unicode 正規化]。",
+ "config-unicode-update-warning": "<strong>警告</strong>:這馬安裝的 Unicode 正規化包裝程式用舊版 [http://site.icu-project.org/ ICU 計劃] 的程式庫。\n若你需要用 Unicode,你應該先進行 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 升級]。",
+ "config-no-db": "揣無適合的資料庫驅動程式!你需要安裝 PHP 資料庫驅動程式。\n這馬支援下跤類型的資料庫: $1 。\n\n若你是家己編譯 PHP,你需要重新設定並且開資料庫客戶端,譬如:用 <code>./configure --with-mysqli</code> 指令參數。\n如你是用 Debian 或 Ubuntu 的套件安裝,你著需要閣另外安裝,例:<code>php5-mysql</code> 套件。",
+ "config-outdated-sqlite": "<strong>警告:</strong>你已經安裝 SQLite $1,毋閣伊的版本比會當裝的版本 $2閣較舊。所以你無法度用 SQLite。",
+ "config-no-fts3": "<strong>警告:</strong> SQLite 佇編譯的時陣無包括 [//sqlite.org/fts3.html FTS3 模組],後台搜揣功能就會無法度用。",
+ "mainpagetext": "'''MediaWiki已經裝好矣。'''",
+ "mainpagedocfooter": "請查看[//meta.wikimedia.org/wiki/Help:Contents 用者說明書]的資料通使用wiki 軟體\n\n== 入門 ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 配置的設定]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki時常問答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki的公布列單]"
+}
diff --git a/includes/installer/i18n/nap.json b/includes/installer/i18n/nap.json
new file mode 100644
index 00000000..1cbe7d56
--- /dev/null
+++ b/includes/installer/i18n/nap.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "C.R."
+ ]
+ },
+ "config-desc": "'O prugramma d'istallazione 'e MediaWiki",
+ "config-title": "Installazione 'e MediaWiki $1",
+ "config-information": "Nfurmaziune",
+ "config-localsettings-upgrade": "È stato rilevato nu file <code>LocalSettings.php</code>.\nP'agghiurnà sta installazione, pe' piacere nzertàte 'o valore 'e <code>$wgUpgradeKey</code> dint' 'a cascia ccà abbascio.\n'O putite truvà dint'a <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "È stato scummigliato nu file <code>LocalSettings.php</code>.\nPe l'agghiurnà sta installazione, secutate <code>update.php</code>",
+ "config-localsettings-key": "Chiave d'agghiurnamiento:",
+ "config-localsettings-badkey": "'A chiave c'avete dato nun è curretta.",
+ "config-mssql-install-auth": "Sceglie 'o tipo d'autenticazziona ca s'ausarrà pe cunnettà â database, durante ll'operazziona d'istallazziona. Si piglie \"{{int:config-mssql-windowsauth}}\", 'e credenziale 'e qualunque fosse ll'utenza ca 'o webserver sta pruciessanno sarranno ausate.",
+ "config-mssql-web-auth": "Sceglie 'o tipo d'autenticazziona ca 'o web server pigliarrà pe se cunnettà a 'o server 'e bbase 'e dati, durante ll'operazziona nurmale d&#39;'a wiki.\nSi piglie \"{{int:config-mssql-windowsauth}}\", 'e credenziale 'e qualunque fosse ll'utenza ca 'o webserver sta pruciessanno sarranno ausate."
+}
diff --git a/includes/installer/i18n/nb.json b/includes/installer/i18n/nb.json
new file mode 100644
index 00000000..f3cf6454
--- /dev/null
+++ b/includes/installer/i18n/nb.json
@@ -0,0 +1,324 @@
+{
+ "@metadata": {
+ "authors": [
+ "Event",
+ "Nghtwlkr",
+ "아라",
+ "Danmichaelo",
+ "Jeblad"
+ ]
+ },
+ "config-desc": "Installasjonsprogrammet for MediaWiki",
+ "config-title": "Installasjon av MediaWiki $1",
+ "config-information": "Informasjon",
+ "config-localsettings-upgrade": "En <code>LocalSettings.php</code>-fil har blitt oppdaget.\nFor å oppgradere denne installasjonen, skriv inn verdien av <code>$wgUpgradeKey</code> i boksen nedenfor.\nDu finner denne i <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Filen ''<code>LocalSettings.php</code>'' er funnet.\nFor å oppgradere denne installasjonen, vennligst kjør ''update.php'' i stedet",
+ "config-localsettings-key": "Oppgraderingsnøkkel:",
+ "config-localsettings-badkey": "Nøkkelen du oppga er feil.",
+ "config-upgrade-key-missing": "En eksisterende installasjon av MediaWiki er funnet.\nFor å oppgradere denne installasjonen, vær vennlig å legge til følgende linje helt til slutt i din ''<code>LocalSettings.php</code>''-fil:\n\n$1",
+ "config-localsettings-incomplete": "Den eksisterende ''<code>LocalSettings.php</code>'' ser ut til å være ufullstendig.\nVariabelen $1 har ingen verdi.\nVær vennlig å endre ''<code>LocalSettings.php</code>'' slik at variabelen får en verdi, og klikk ''{{int:Config-continue}}''.",
+ "config-localsettings-connection-error": "Det ble funnet en feil ved tilknytning av databasen med innstillingene i ''<code>LocalSettings.php</code>'' eller ''<code>AdminSettings.php</code>''. Vær vennlig å rette opp disse innstillingene og prøv igjen.\n\n$1",
+ "config-session-error": "Feil under oppstart av økt: $1",
+ "config-session-expired": "Dine øktdata ser ut til å ha utløpt.\nØkter er konfigurert for en levetid på $1.\nDu kan øke dette ved å sette <code>session.gc_maxlifetime</code> i php.ini.\nStart installasjonsprosessen på nytt.",
+ "config-no-session": "Dine øktdata ble tapt!\nSjekk din php.ini og sørg for at <code>session.save_path</code> er satt til en passende mappe.",
+ "config-your-language": "Ditt språk:",
+ "config-your-language-help": "Velg et språk å bruke under installasjonsprosessen.",
+ "config-wiki-language": "Wikispråk:",
+ "config-wiki-language-help": "Velg språket som wikien hovedsakelig vil bli skrevet i.",
+ "config-back": "← Tilbake",
+ "config-continue": "Fortsett →",
+ "config-page-language": "Språk",
+ "config-page-welcome": "Velkommen til MediaWiki!",
+ "config-page-dbconnect": "Koble til database",
+ "config-page-upgrade": "Oppgrader eksisterende innstallasjon",
+ "config-page-dbsettings": "Databaseinnstillinger",
+ "config-page-name": "Navn",
+ "config-page-options": "Valg",
+ "config-page-install": "Installer",
+ "config-page-complete": "Ferdig!",
+ "config-page-restart": "Start installasjonen på nytt",
+ "config-page-readme": "Les meg",
+ "config-page-releasenotes": "Utgivelsesnotat",
+ "config-page-copying": "Kopiering",
+ "config-page-upgradedoc": "Oppgradering",
+ "config-page-existingwiki": "Eksisterende wiki",
+ "config-help-restart": "Ønsker du å fjerne alle lagrede data som du har skrevet inn og starte installasjonsprosessen på nytt?",
+ "config-restart": "Ja, start på nytt",
+ "config-welcome": "=== Miljøsjekker ===\nGrunnleggende sjekker utføres for å se om dette miljøet er egnet for en MediaWiki-installasjon.\nDu bør oppgi resultatene fra disse sjekkene om du trenger hjelp under installasjonen.",
+ "config-copyright": "=== Opphavsrett og vilkår ===\n\n$1\n\nMediaWiki er fri programvare; du kan redistribuere det og/eller modifisere det under betingelsene i GNU General Public License som publisert av Free Software Foundation; enten versjon 2 av lisensen, eller (etter eget valg) enhver senere versjon.\n\nDette programmet er distribuert i håp om at det vil være nyttig, men '''uten noen garanti'''; ikke engang implisitt garanti av '''salgbarhet''' eller '''egnethet for et bestemt formål'''.\nSe GNU General Public License for flere detaljer.\n\nDu skal ha mottatt <doclink href=Copying>en kopi av GNU General Public License</doclink> sammen med dette programmet; hvis ikke, skriv til Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA eller [http://www.gnu.org/copyleft/gpl.html les det på nettet].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki hjem]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Brukerguide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administratorguide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ OSS]\n----\n* <doclink href=Readme>Les meg</doclink>\n* <doclink href=ReleaseNotes>Utgivelsesnotater</doclink>\n* <doclink href=Copying>Kopiering</doclink>\n* <doclink href=UpgradeDoc>Oppgradering</doclink>",
+ "config-env-good": "Miljøet har blitt sjekket.\nDu kan installere MediaWiki.",
+ "config-env-bad": "Miljøet har blitt sjekket.\nDu kan installere MediaWiki.",
+ "config-env-php": "PHP $1 er innstallert.",
+ "config-env-hhvm": "HHVM $1 er installert.",
+ "config-unicode-using-utf8": "Bruker Brion Vibbers utf8_normalize.so for Unicode-normalisering.",
+ "config-unicode-using-intl": "Bruker [http://pecl.php.net/intl intl PECL-utvidelsen] for Unicode-normalisering.",
+ "config-unicode-pure-php-warning": "'''Advarsel''': [http://pecl.php.net/intl intl PECL-utvidelsen] er ikke tilgjengelig for å håndtere Unicode-normaliseringen, faller tilbake til en langsommere ren-PHP-implementasjon.\nOm du kjører et nettsted med høy trafikk bør du lese litt om [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-normalisering].",
+ "config-unicode-update-warning": "'''Advarsel''': Den installerte versjonen av Unicode-normalisereren bruker en eldre versjon av [http://site.icu-project.org/ ICU-prosjektets] bibliotek.\nDu bør [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations oppgradere] om du er bekymret for å bruke Unicode.",
+ "config-no-db": "Fant ingen passende databasedriver! Du må installere en databasedriver for PHP.\nFølgende databasetyper støttes: $1\n\nOm du kompilerte PHP selv, rekonfigurer den med en aktivert databaseklient, for eksempel ved å bruke <code>./configure --with-mysql</code>.\nOm du installerte PHP fra en Debian- eller Ubuntu-pakke, må du også installere for eksempel <code>php5-mysql</code>-pakken.",
+ "config-outdated-sqlite": "'''Advarsel''': Du har SQLite $1, som er en eldre versjon enn minimumskravet SQLite $2. SQLite vil ikke være tilgjengelig.",
+ "config-no-fts3": "'''Advarsel''': SQLite er kompilert uten [//sqlite.org/fts3.html FTS3-modulen], søkefunksjoner vil ikke være tilgjengelig på dette bakstykket.",
+ "config-register-globals-error": "<strong>Feil: PHPs <code>[http://php.net/register_globals register_globals]</code>-valg er aktivt.\nDet må deaktiveres for å kunne fortsette med installeringen.</strong>\nSe [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] for å få hjelp til å gjøre dette.",
+ "config-magic-quotes-gpc": "<strong>Fatalt: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] er aktiv!</strong>\nDette valget kan ødelegge inndata på en uforutsigelig måte.\nDu kan ikke installere eller bruke MediaWiki uten at denne valgmuligheten er slått av.",
+ "config-magic-quotes-runtime": "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] er aktiv!'''\nDette alternativet ødelegger inndata på en uforutsigbar måte.\nDu kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ "config-magic-quotes-sybase": "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] er aktiv!'''\nDette alternativet ødelegger inndata på en uforutsigbar måte.\nDu kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ "config-mbstring": "'''Kritisk: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] er aktiv!'''\nDette alternativet fører til feil og kan ødelegge data på en uforutsigbar måte.\nDu kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ "config-safe-mode": "'''Advarsel:''' PHPs [http://www.php.net/features.safe-mode safe mode] er aktiv.\nDet kan føre til problem, spesielt hvis du bruker støtte for filopplastinger og <code>math</code>.",
+ "config-xml-bad": "PHPs XML-modul mangler.\nMediaWiki krever funksjonene i denne modulen og vil ikke virke i denne konfigurasjonen.\nHvis du kjører Mandrak, installer pakken php-xml.",
+ "config-pcre-old": "'''Alvorlig:''' PCRE $1 eller senere kreves.\nDin PHP-kode er lenket med PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Nærmere informasjon].",
+ "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-ctype": "'''Fatal feil''': PHP må kompileres med støtte for [http://www.php.net/manual/en/ctype.installation.php Ctype-utvidelsen].",
+ "config-iconv": "<strong>Kritisk:</strong> PHP må kompileres med støtte for [http://www.php.net/manual/en/iconv.installation.php iconv-utvidelsen].",
+ "config-json": "'''Alvorlig:''' PHP ble kompilert uten JSON-støtte.\nDu må installere enten PHP JSON-utvidelsen eller [http://pecl.php.net/package/jsonc PECL jsonc]-utvidelsen før du installere MediaWiki.\n* PHP-utvidelsen inngår i Red Hat Enterprise Linux (CentOS) 5 and 6, men må aktiveres i <code>/etc/php.ini</code> eller <code>/etc/php.d/json.ini</code>.\n* Noen Linux-distribusjoner sluppet etter mai 2013 har ikke med PHP-utvidelsen, men har i stedet med PECL-utvidelsen <code>php5-json</code> eller <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] er innstallert",
+ "config-apc": "[http://www.php.net/apc APC] er innstallert",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] er installert",
+ "config-no-cache": "'''Advarsel:''' Kunne ikke finne [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].\nObjekthurtiglagring er ikke aktivert.",
+ "config-mod-security": "'''Advarsel''': Din web-tjener har [http://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 [http://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-git-bad": "Git version control software ble ikke funnet.",
+ "config-imagemagick": "Fant ImageMagick: <code>$1</code>.\nBildeminiatyrisering vil aktiveres om du aktiverer opplastinger.",
+ "config-gd": "Fant innebygd GD-grafikkbibliotek.\nBildeminiatyrisering vil aktiveres om du aktiverer opplastinger.",
+ "config-no-scaling": "Kunne ikke finne GD-bibliotek eller ImageMagick.\nBildeminiatyrisering vil være deaktivert.",
+ "config-no-uri": "'''Feil:''' Kunne ikke bestemme gjeldende URI.\nInstallasjon avbrutt.",
+ "config-no-cli-uri": "'''Advarsel''': Ingen <code>--scriptpath</code> er angitt; bruker standard: <code>$1</code>.",
+ "config-using-server": "Bruker servernavnet \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Bruker server-URL \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Advarsel:''' Din standardmappe for opplastinger <code>$1</code> er sårbar for kjøring av vilkårlige skript.\nSelv om MediaWiki sjekker alle opplastede filer for sikkerhetstrusler er det sterkt anbefalt å [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security lukke denne sikkerhetssårbarheten] før du aktiverer opplastinger.",
+ "config-no-cli-uploads-check": "'''Advarsel:''' Din standard-katalog for opplastinger (<code>$1</code>) er ikke kontrollert for sårbarhet overfor vilkårlig skript-kjøring under CLI-installasjonen.",
+ "config-brokenlibxml": "Ditt system bruker en kombinasjon av PHP- og libxml2-versjoner som har feil og kan forårsake skjult dataødeleggelse i MediaWiki og andre web-applikasjoner.\nOppgrader til libxml2 2.7.3 eller nyere ([https://bugs.php.net/bug.php?id=45996 Feil-liste for PHP]).\nInstalleringen ble abortert.",
+ "config-suhosin-max-value-length": "Suhosin er installert og begrenser GET-parameterlengder til $1 bytes. MediaWiki sin ResourceLoader-komponent klarer å komme rundt denne begrensningen, men med redusert ytelse. Om mulig bør du sette <code>suhosin.get.max_value_length</code> til minst 1024 i <code>php.ini</code>, og sette <code>$wgResourceLoaderMaxQueryLength</code> til samme verdi i <code>LocalSettings.php</code>.",
+ "config-db-type": "Databasetype:",
+ "config-db-host": "Databasevert:",
+ "config-db-host-help": "Hvis databasen kjører på en annen tjenermaskin, skriv inn vertsnavnet eller IP-adressen her.\n\nHvis du bruker et webhotell, vil du kunne be om aktuelt vertsnavn fra din leverandør.\n\nHvis du installerer på en Windowstjener og bruker MySQL, kan det hende at «localhost» ikke brukes som tjenernavn. Hvis så er tilfelle, prøv «127.0.0.1» som lokal IP-adresse.\n\nHvis du bruker PostgreSQL, la dette feltet være blankt slik at koplingen gjøres via en \"Unix socket\".",
+ "config-db-host-oracle": "Database TNS:",
+ "config-db-host-oracle-help": "Skriv inn et gyldig [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; en tnsnames.ora-fil må være synlig for installasjonsprosessen.<br />Hvis du bruker klientbibliotek 10g eller nyere kan du også bruke navngivingsmetoden [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identifiser denne wikien",
+ "config-db-name": "Databasenavn:",
+ "config-db-name-help": "Velg et navn som identifiserer wikien din.\nDet bør ikke inneholde mellomrom.\n\nHvis du bruker en delt nettvert vil verten din enten gi deg et spesifikt databasenavn å bruke, eller la deg opprette databaser via kontrollpanelet.",
+ "config-db-name-oracle": "Databaseskjema:",
+ "config-db-account-oracle-warn": "Det finnes tre mulig fremgangsmåter for å installere Oracle som database:\n\nHvis du ønsker å opprette en databasekonto som del av installasjonsprosessen, oppgi da en konto med SYSDBA-rolle som databasekonto for installasjonen og angi påkrevd autentiseringsinformasjon for web-aksesskontoen. Ellers kan du enten opprette web-aksesskontoen manuelt eller kun oppgi den kontoen (hvis den har påkrevede tillatelser for å opprette skjemeobjektene) , alternativt oppgi to ulike kontoer, en med opprettelsesprivilegier (create) og en begrenset konto for web-aksess.\n\nSkript for å opprette en konto med påkrevde privilegier finnes i \"maintenance/oracle/\"-folderen av denne installasjonen. Husk at det å bruke en begrenset konto vil blokkere all vedlikeholdsfunksjonalitet med standard konto.",
+ "config-db-install-account": "Brukerkonto for installasjon",
+ "config-db-username": "Databasebrukernavn:",
+ "config-db-password": "Databasepassord:",
+ "config-db-password-empty": "Skriv inn et passord for den nye databasebrukeren: $1.\nDet er mulig å opprette brukere uten passord, men dette er ikke sikkert.",
+ "config-db-username-empty": "Du må skrive inn en verdi for «{{int:config-db-username}}»",
+ "config-db-install-username": "Skriv inn brukernavnet som vil bli brukt til å koble til databasen under installasjonsprosessen.\nDette er ikke brukernavnet på MediaWiki-kontoen; dette er brukernavnet for databasen din.",
+ "config-db-install-password": "Skriv inn passordet som vil bli brukt til å koble til databasen under installasjonsprosessen.\nDette er ikke passordet på MediaWiki-kontoen; dette er passordet for databasen din.",
+ "config-db-install-help": "Skriv inn brukernavnet og passordet som vil bli brukt for å koble til databasen under installasjonsprosessen.",
+ "config-db-account-lock": "Bruk det samme brukernavnet og passordet under normal drift",
+ "config-db-wiki-account": "Brukerkonto for normal drift",
+ "config-db-wiki-help": "Skriv inn brukernavnet og passordet som vil bli brukt til å koble til databasen under normal wikidrift.\nHvis kontoen ikke finnes, og installasjonskontoen har tilstrekkelige privilegier, vil denne brukerkontoen bli opprettet med et minimum av privilegier, tilstrekkelig for å operere wikien.",
+ "config-db-prefix": "Databasetabellprefiks:",
+ "config-db-prefix-help": "Hvis du trenger å dele en database mellom flere wikier, eller mellom MediaWiki og andre nettapplikasjoner, kan du velge å legge til et prefiks til alle tabellnavnene for å unngå konflikter.\nIkke bruk mellomrom.\n\nDette feltet er vanligvis tomt.",
+ "config-db-charset": "Databasetegnsett",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binær",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 bakoverkompatibel UTF-8",
+ "config-charset-help": "'''Advarsel:''' Hvis du bruker '''bakoverkompatibel UTF-8''' på MySQL 4.1+, og deretter sikkerhetskopierer databasen med <code>mysqldump</code> kan det ødelegge alle ikke-ASCII tegn og irreversibelt ødelegge dine sikkerhetskopier!\n\nI '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.\nDette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.\nI '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,\nmen det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
+ "config-mysql-old": "MySQL $1 eller senere kreves, du har $2.",
+ "config-db-port": "Databaseport:",
+ "config-db-schema": "Skjema for MediaWiki",
+ "config-db-schema-help": "Dette skjemaet er som regel riktig.\nBare endre det hvis du vet at du trenger det.",
+ "config-pg-test-error": "Får ikke kontakt med database '''$1''': $2",
+ "config-sqlite-dir": "SQLite datamappe:",
+ "config-sqlite-dir-help": "SQLite lagrer alle data i en enkelt fil.\n\nMappen du oppgir må være skrivbar for nettjeneren under installasjonen.\n\nDen bør '''ikke''' være tilgjengelig fra nettet, dette er grunnen til at vi ikke legger det der PHP-filene dine er.\n\nInstallasjonsprogrammet vil skrive en <code>.htaccess</code>-fil sammen med det, men om det mislykkes kan noen få tilgang til din råe database. Dette inkluderer rå brukerdata (e-postadresser, hashede passord) samt slettede revisjoner og andre begrensede data på wikien.\n\nVurder å plassere databasen et helt annet sted, for eksempel i <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Standard tabellrom:",
+ "config-oracle-temp-ts": "Midlertidig tabellrom:",
+ "config-type-mysql": "MySQL (eller kompatibelt)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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-mysql-url}} MySQL] er den prefererte databasen for MediaWiki og er derfor best støttet. MediaWiki fungerer også med [{{int:version-db-mariadb-url}} MariaDB] og [{{int:version-db-percona-url}} Percona Server], som begge er MySQL-kompatible. ([http://www.php.net/manual/en/mysqli.installation.php Hvordan kompilere med 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. Det kan ha noen små utestående feil og det anbefales derfor ikke for bruk i et produksjonsmiljø. ([http://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. ([http://www.php.net/manual/en/oci8.installation.php Hvordan kompilere PHP med OCI8-støtte])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQLServer] er en kommersiell enterprise-database for Windows. ([http://www.php.net/manual/en/sqlsrv.installation.php Hvordan kompilere PHP med SQLSRV-støtte])",
+ "config-header-mysql": "MySQL-innstillinger",
+ "config-header-postgres": "PostgreSQL-innstillinger",
+ "config-header-sqlite": "SQLite-innstillinger",
+ "config-header-oracle": "Oracle-innstillinger",
+ "config-header-mssql": "Microsoft SQLServer-innstillinger",
+ "config-invalid-db-type": "Ugyldig databasetype",
+ "config-missing-db-name": "Du må skrive inn en verdi for «{{int:config-db-name}}»",
+ "config-missing-db-host": "Du må skrive inn en verdi for «{{int:config-db-host}}»",
+ "config-missing-db-server-oracle": "Du må skrive inn en verdi for «{{int:config-db-host-oracle}}»",
+ "config-invalid-db-server-oracle": "Ugyldig database-TNS «$1».\nBruk enten \"TNS Name\" eller en \"Easy Connect\"-streng ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods])",
+ "config-invalid-db-name": "Ugyldig databasenavn «$1».\nBruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9), undestreker (_) og bindestreker (-).",
+ "config-invalid-db-prefix": "Ugyldig databaseprefiks «$1».\nBruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9), undestreker (_) og bindestreker (-).",
+ "config-connection-error": "$1.\n\nSjekk verten, brukernavnet og passordet nedenfor og prøv igjen.",
+ "config-invalid-schema": "Ugyldig skjema for MediaWiki «$1».\nBruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).",
+ "config-db-sys-create-oracle": "Installasjonsprogrammet støtter kun bruk av en SYSDBA-konto for opprettelse av en ny konto.",
+ "config-db-sys-user-exists-oracle": "Brukerkontoen «$1» finnes allerede. SYSDBA kan kun brukes for oppretting av nye kontoer!",
+ "config-postgres-old": "PostgreSQL $1 eller senere kreves, du har $2.",
+ "config-mssql-old": "Microsoft SQLServer $1 eller senere kreves. Du har $2.",
+ "config-sqlite-name-help": "Velg et navn som identifiserer wikien din.\nIkke bruk mellomrom eller bindestreker.\nDette vil bli brukt til SQLite-datafilnavnet.",
+ "config-sqlite-parent-unwritable-group": "Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.\n\nInstallasjonsprogrammet har bestemt brukeren nettjeneren din kjører som.\nGjør <code><nowiki>$3</nowiki></code>-mappen skrivbar for denne for å fortsette.\nPå et Unix/Linux-system, gjør:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.\n\nInstallasjonsprogrammet kunne ikke bestemme brukeren nettjeneren din kjører som.\nGjør <code><nowiki>$3</nowiki></code>-mappen globalt skrivbar for denne (og andre!) for å fortsette.\nPå et Unix/Linux-system, gjør:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Feil under oppretting av datamappen «$1».\nSjekk plasseringen og prøv igjen.",
+ "config-sqlite-dir-unwritable": "Kan ikke skrive til mappen «$1».\nEndre dens tilganger slik at nettjeneren kan skrive til den og prøv igjen.",
+ "config-sqlite-connection-error": "$1.\n\nSjekk datamappen og databasenavnet nedenfor og prøv igjen.",
+ "config-sqlite-readonly": "Filen <code>$1</code> er ikke skrivbar.",
+ "config-sqlite-cant-create-db": "Kunne ikke opprette databasefilen <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP mangler FTS3-støtte, nedgraderer tabeller",
+ "config-can-upgrade": "Det er MediaWiki-tabeller i denne databasen.\nFor å oppgradere dem til MediaWiki $1, klikk '''Fortsett'''.",
+ "config-upgrade-done": "Oppgradering fullført.\n\nDu kan nå [$1 begynne å bruke wikien din].\n\nHvis du ønsker å regenerere <code>LocalSettings.php</code>-filen din, klikk på knappen nedenfor.\nDette er '''ikke anbefalt''' med mindre du har problemer med wikien din.",
+ "config-upgrade-done-no-regenerate": "Oppgradering fullført.\n\nDu kan nå [$1 begynne å bruke wikien din].",
+ "config-regenerate": "Regenerer LocalSettings.php →",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code> etterspørselen mislyktes!",
+ "config-unknown-collation": "'''Advarsel:''' Databasen bruker en ukjent sortering.",
+ "config-db-web-account": "Databasekonto for nettilgang",
+ "config-db-web-help": "Velg brukernavnet og passordet som nettjeneren skal bruke for å koble til databasetjeneren under ordinær drift av wikien.",
+ "config-db-web-account-same": "Bruk samme konto som for installasjonen",
+ "config-db-web-create": "Opprett kontoen om den ikke finnes allerede",
+ "config-db-web-no-create-privs": "Kontoen du oppga for installasjonen har ikke nok privilegier til å opprette en konto.\nKontoen du oppgir her må finnes allerede.",
+ "config-mysql-engine": "Lagringsmotor:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Advarsel:''' Du har valgt MyISAM som lagringsmotor for MySQL, noe som ikke er anbefalt for bruk med MediaWiki, fordi:\n* den knapt støtter samtidighet pga. tabell-låsing\n* den har større tilbøyelighet for å bli korrupt enn andre motorer\n* MediaWiki-koden håndterer ikke alltid MyISAM som den burde\n\nHvis din MySQL-installasjon støtter InnoDB, er det sterkt å anbefale at du i stedet velger den.\nHvis din MySQL-installasjon ikke støtter InnoDB, kan det være på tide med en oppgradering.",
+ "config-mysql-only-myisam-dep": "'''Advarsel:''' MyISAM er den eneste tilgjengelig lagringsmotoren for MySQL på denne maskinen, og det er ikke anbefalt brukt for MediaWiki, fordi:\n* den knapt støtter samtidighet pga. tabell-låsing\n* den har større tilbøyelighet for å bli korrupt enn andre motorer\n* MediaWiki-koden håndterer ikke alltid MyISAM som den burde\n\nHvis din MySQL-installasjon ikke støtter InnoDB, kan det være på tide med en oppgradering.",
+ "config-mysql-engine-help": "'''InnoDB''' er nesten alltid det beste alternativet siden den har god støtte for samtidighet («concurrency»).\n\n'''MyISAM''' kan være raskere i enbruker- eller les-bare-installasjoner.\nMyISAM-databaser har en tendens til å bli ødelagt oftere enn InnoDB-databaser.",
+ "config-mysql-charset": "Databasetegnsett:",
+ "config-mysql-binary": "Binær",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.\nDette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.\n\nI '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,\nmen det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
+ "config-mssql-auth": "Autentiseringstype:",
+ "config-mssql-install-auth": "Valg autentiseringstypen som skal brukes for å koble til databasen under installeringsprosessen. Hvis du velger «{{int:config-mssql-windowsauth}}», vil påloggingsinformasjonen for brukeren som kjører webtjeneren blir brukt.",
+ "config-mssql-web-auth": "Velg autentiseringstype som webtjeneren vil bruke for å koble til databasetjeneren under normal kjøring av wikien.\nHvis du velger «{{int:config-mssql-windowsauth}}», vil påloggingsinformasjonen til brukeren som kjører webtjeneren blir brukt.",
+ "config-mssql-sqlauth": "SQLServer-autentisering",
+ "config-mssql-windowsauth": "Windows-autentisering",
+ "config-site-name": "Navn på wiki:",
+ "config-site-name-help": "Dette vil vises i tittellinjen i nettleseren og diverse andre steder.",
+ "config-site-name-blank": "Skriv inn et nettstedsnavn.",
+ "config-project-namespace": "Prosjektnavnerom:",
+ "config-ns-generic": "Prosjekt",
+ "config-ns-site-name": "Samme som wikinavnet: $1",
+ "config-ns-other": "Annet (spesifiser)",
+ "config-ns-other-default": "MyWiki",
+ "config-project-namespace-help": "Etter Wikipedias eksempel holder mange wikier deres sider med retningslinjer atskilt fra sine innholdssider, i et «'''prosjektnavnerom'''».\nAlle sidetitler i dette navnerommet starter med et gitt prefiks som du kan angi her.\nTradisjonelt er dette prefikset avledet fra navnet på wikien, men det kan ikke innholde punkttegn som «#» eller «:».",
+ "config-ns-invalid": "Det angitte navnerommet «<nowiki>$1</nowiki>» er ugyldig.\nAngi et annet prosjektnavnerom.",
+ "config-ns-conflict": "Det angitte navnerommet «<nowiki>$1</nowiki>» er i konflikt med et standard MediaWiki-navnerom.\nAngi et annet prosjekt-navnerom.",
+ "config-admin-box": "Administratorkonto",
+ "config-admin-name": "Ditt navn:",
+ "config-admin-password": "Passord:",
+ "config-admin-password-confirm": "Passord igjen:",
+ "config-admin-help": "Skriv inn ditt ønskede brukernavn her, for eksempel «Joe Bloggs».\nDette er navnet du vil bruke for å logge inn på denne wikien.",
+ "config-admin-name-blank": "Skriv inn et administratorbrukernavn.",
+ "config-admin-name-invalid": "Det angitte brukernavnet «<nowiki>$1</nowiki>» er ugyldig.\nAngi et annet brukernavn.",
+ "config-admin-password-blank": "Skriv inn et passord for administratorkontoen.",
+ "config-admin-password-mismatch": "De to passordene du skrev inn samsvarte ikke.",
+ "config-admin-email": "E-postadresse:",
+ "config-admin-email-help": "Skriv inn en e-postadresse her for at du skal kunne motta e-post fra andre brukere på wikien, tilbakestille passordet ditt, og bli varslet om endringer på sider på overvåkningslisten din. Du kan la dette feltet stå tomt.",
+ "config-admin-error-user": "Intern feil ved opprettelse av en admin med navnet «<nowiki>$1</nowiki>».",
+ "config-admin-error-password": "Intern feil ved opprettelse av passord for admin «<nowiki>$1</nowiki>»: <pre>$2</pre>",
+ "config-admin-error-bademail": "Du har skrevet inn en ugyldig e-postadresse.",
+ "config-subscribe": "Abonner på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlisten for utgivelsesannonseringer].",
+ "config-subscribe-help": "Dette er en lav-volums e-postliste brukt til utgivelsesannonseringer, herunder viktige sikkerhetsannonseringer.\nDu bør abonnere på den og oppdatere MediaWikiinstallasjonen din når nye versjoner kommer ut.",
+ "config-subscribe-noemail": "Du prøvde å abonnere på epost-meldinger om nye versjoner uten å oppgi en epost-adresse. Vær vennlig å oppgi en epost-adresse om du ønsker dette abonnementet.",
+ "config-almost-done": "Du er nesten ferdig!\nDu kan hoppe over de resterende konfigurasjonene og installere wikien nå.",
+ "config-optional-continue": "Spør meg flere spørsmål.",
+ "config-optional-skip": "Jeg er lei, bare installer wikien.",
+ "config-profile": "Brukerrettighetsprofil:",
+ "config-profile-wiki": "Åpen wiki",
+ "config-profile-no-anon": "Kontoopprettelse påkrevd",
+ "config-profile-fishbowl": "Kun autoriserte bidragsytere",
+ "config-profile-private": "Privat wiki",
+ "config-profile-help": "Wikier fungerer best om du lar så mange mennesker som mulig redigere den.\nI MediaWiki er det enkelt å se på de siste endringene og tilbakestille eventuell skade som er gjort av naive eller ondsinnede brukere.\n\nImidlertid har mange funnet at MediaWiki er nyttig for mange formål, og av og til er det ikke lett å overbevise alle om fordelene med wiki-funksjonaliteten.\nSå du har valget.\n\nEn '''{{int:config-profile-wiki}}''' tillater enhver å redigere, selv uten å logge inn.\nEn wiki med '''{{int:config-profile-no-anon}}''' tilbyr ekstra ansvarlighet, men kan avskrekke tilfeldige bidragsytere.\n\n'''{{int:config-profile-fishbowl}}'''-scenariet tillater godkjente brukere å redigere, mens publikum kan se sider, og også historikken.\nEn '''{{int:config-profile-private}}''' tillater kun godkjente brukere å se sider, der den samme gruppen også får lov til å redigere dem.\n\nMer komplekse konfigurasjoner av brukerrettigheter er tilgjengelige etter installasjonen, se [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights relevant avsnitt i brukerbeskrivelsen].",
+ "config-license": "Opphavsrett og lisens:",
+ "config-license-none": "Ingen lisensbunntekst",
+ "config-license-cc-by-sa": "Creative Commons Navngivelse Del på samme vilkår",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Navngivelse Ikke-kommersiell Del på samme vilkår",
+ "config-license-cc-0": "Creative Commons Zero (tilgjengelig for alle)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 eller senere",
+ "config-license-pd": "Offentlig rom",
+ "config-license-cc-choose": "Velg en egendefinert Creative Commons-lisens",
+ "config-license-help": "Mange åpne wikier legger alle bidrag under en [http://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 '''Creative Commons Attribution Share Alike'''.\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å [http://www.php.net/manual/en/mail.configuration.php PHPs e-postinnstillinger] bli konfigurert riktig.\nHvis du ikke ønsker noen e-postfunksjoner 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",
+ "config-email-usertalk-help": "Tillat brukere å motta varsler ved endringer på deres brukerdiskusjonsside hvis de har aktivert dette i deres innstillinger.",
+ "config-email-watchlist": "Aktiver overvåkningslistevarsler",
+ "config-email-watchlist-help": "Tillat brukere å motta varsler ved endringer på deres overvåkede sider hvis de har aktivert dette i deres innstillinger.",
+ "config-email-auth": "Aktiver e-postautentisering",
+ "config-email-auth-help": "Om dette alternativet er aktivert må brukere bekrefte sin e-postadresse ved å bruke en lenke som blir sendt til dem når de setter eller endrer adressen sin.\nKun autentiserte e-postadresser kan motta e-post fra andre brukere eller endringsvarsel.\nÅ sette dette valget er '''anbefalt''' for offentlige wikier på grunn av potensiell misbruk av e-postfunksjonene.",
+ "config-email-sender": "Svar-e-postadresse:",
+ "config-email-sender-help": "Skriv inn e-postadressen som skal brukes som svar-adresse ved utgående e-post.\nDet er hit returmeldinger vil bli sendt.\nMange e-posttjenere krever at minst domenenavnet må være gyldig.",
+ "config-upload-settings": "Bilde- og filopplastinger",
+ "config-upload-enable": "Aktiver filopplastinger",
+ "config-upload-help": "Filopplastinger kan potensielt utsette tjeneren din for sikkerhetsrisikoer.\nFor mer informasjon, les [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sikkerhetsseksjonen] i manualen.\n\nFor å aktivere filopplastinger, endre modusen i <code>images</code>-undermappen i MediaWikis rotmappe slik at nettjeneren kan skrive til den.\nAktiver så dette alternativet.",
+ "config-upload-deleted": "Mappe for slettede filer:",
+ "config-upload-deleted-help": "Velg en mappe for å arkivere slettede filer.\nIdeelt burde ikke denne være tilgjengelig for nettet.",
+ "config-logo": "Logo-URL:",
+ "config-logo-help": "MediaWikis standarddrakt har satt av plass til en 135x160 pikslers logo i øvre venstre hjørne av sidepanelet.\nLast opp et bilde i passende størrelse og skriv inn nettadressen her.\n\nDu kan bruke code>$wgStylePath</code> eller <code>$wgScriptPath</code> hvis logoen er relativ til disse stiene.\n\nHvis du ikke ønsker noen logo, la boksen være tom.",
+ "config-instantcommons": "Aktiver Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] er en funksjon som gjør det mulig for wikier å bruke bilder, lyder og andre media funnet på nettstedet [//commons.wikimedia.org/ Wikimedia Commons].\nFor å gjøre dette krever MediaWiki tilgang til internett.\n\nFor mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man setter opp dette for andre wikier enn Wikimedia Commons, konsulter [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos manualen].",
+ "config-cc-error": "Lisensvelgeren for Creative Commons ga ikke noe resultat.\nLegg inn lisensnavnet manuelt.",
+ "config-cc-again": "Velg igjen...",
+ "config-cc-not-chosen": "Velg hvilken Creative Commons-lisens du ønsker og klikk «fortsett».",
+ "config-advanced-settings": "Avansert konfigurasjon",
+ "config-cache-options": "Innstillinger for objekt-mellomlagring:",
+ "config-cache-help": "Objekt-mellomlagring brukes for å forbedre hastigheten for MediaWiki. Ofte forekommende data lagres for gjenbruk.\nMiddels til store nettsteder bør absolutt aktivisere mellomlagring, med også små nettsteder kan ha nytte av dette.",
+ "config-cache-none": "Ingen mellomlagring (ingen funksjonalitet mistes, men hastigheten kan bli dårlig for store wikier-nettsteder)",
+ "config-cache-accel": "Mellomlagring av PHP-objekter (APC, XCache or WinCache)",
+ "config-cache-memcached": "Bruk Memcached (krever tilleggsoppsett og -konfigurering)",
+ "config-memcached-servers": "Memcached-servere:",
+ "config-memcached-help": "Liste av IP-adresser for bruk fra Memcached.\nDet bør angis en per linje sammen med porten som brukes. For eksempel:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Du valgte Memcached som din mellomlagringstype, men anga ingen servere.",
+ "config-memcache-badip": "Du har lagt inn en ugyldig IP-adresse for Memcached: $1.",
+ "config-memcache-noport": "Du spesifiserte ingen port til å bruke for Memcached-server: $1.\nHvis du ikke kjenner porten, så er stardard 11211.",
+ "config-memcache-badport": "Memcached-port-numrene må være mellom $1 og $2.",
+ "config-extensions": "Utvidelser",
+ "config-extensions-help": "Utvidelsene listet over ble oppdaget i din <code>./extensions</code>-folder.\n\nDisse kan trenge ekstra konfigurering, men du kan aktivisere dem nå.",
+ "config-install-alreadydone": "'''Advarsel:''' Det ser ut til at allerede har installert MediaWiki og prøver å installere denne må nytt.\nVær vennlig å fortsette til neste side.",
+ "config-install-begin": "Ved å trykke \"{{int:config-continue}}\", starter du installeringen av MediaWiki.\nHvis du først ønsker å endre på noe, trykk\"{{int:config-back}}\".",
+ "config-install-step-done": "ferdig",
+ "config-install-step-failed": "mislyktes",
+ "config-install-extensions": "Inkludert utvidelser",
+ "config-install-database": "Setter opp database",
+ "config-install-schema": "Opprette \"schema\"",
+ "config-install-pg-schema-not-exist": "PostgreSQL \"schema\" finnes ikke.",
+ "config-install-pg-schema-failed": "Opprettelse av tabell var mislykket.\nPass på at bruker \"$1\" kan skrive til schema \"$2\".",
+ "config-install-pg-commit": "Beslutte endringer",
+ "config-install-pg-plpgsql": "Sjekk om språket er PL/pgSQL",
+ "config-pg-no-plpgsql": "Du må installere språket PL/pgSQL i database $1",
+ "config-pg-no-create-privs": "Brukerkontoen du anga for installering har ikke nok privilegier for å opprette annen brukerkonto.",
+ "config-pg-not-in-role": "Brukerkontoen du anga for web-brukeren finnes allerede.\nBrukerkontoen du anga for installering er ikke superbruker og er ikke medlem av webbrukerens rolle, så den kan ikke opprette objekter eid av webbrukeren.\n\nMediaWiki krever nå at tabellen eies av webbrukeren. Vær vennlig å angi en annen webbrukerkonto, eller klikk \"tilbake\" og angi en installeringsbruker med nødvendige privilegier.",
+ "config-install-user": "Oppretter databasebruker",
+ "config-install-user-alreadyexists": "Brukeren «$1» finnes allerede",
+ "config-install-user-create-failed": "Opprettelse av brukeren «$1» mislyktes: $2",
+ "config-install-user-grant-failed": "Å gi tillatelse til brukeren «$1» mislyktes: $2",
+ "config-install-user-missing": "Den angitte brukeren \"$1\" finnes ikke.",
+ "config-install-user-missing-create": "Den angitte brukeren \"$1\" finnes ikke.\nVær vennlig å klikke i avkrysningsboksen \"opprett konto\" under hvis du ønsker å opprette denne.",
+ "config-install-tables": "Oppretter tabeller",
+ "config-install-tables-exist": "'''Advarsel:''' MediaWiki-tabellen ser ut til å finnes allerede.\nHopper derfor over opprettelsen.",
+ "config-install-tables-failed": "<strong>Feil:</strong> Opprettelsen av tabellen feilet med følgende melding: $1",
+ "config-install-interwiki": "Populerer standard interwiki-tabell",
+ "config-install-interwiki-list": "Det var ikke mulig å lese filen <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "<strong>Advarsel:</strong> Interwiki-tabellen ser allerede ut til å ha innhold.\nLegger derfor ikke inn standardlisten.",
+ "config-install-stats": "Initialiserer statisikk",
+ "config-install-keys": "Genererer hemmelige nøkler",
+ "config-insecure-keys": "<strong>Advarsel:</strong> {{PLURAL:$2|En sikker nøkkel|Sikre nøkler}} ($1) generert under installeringen er ikke helt {{PLURAL:$2|trygg|trygge}}. Vurder å endre {{PLURAL:$2|den|dem}} manuelt.",
+ "config-install-sysop": "Oppretter brukerkonto for administrator",
+ "config-install-subscribe-fail": "Ikke mulig å abonnere på mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL er ikke installert og <code>allow_url_fopen</code> er ikke tilgjengelig.",
+ "config-install-mainpage": "Oppretter hovedside med standard innhold",
+ "config-install-extension-tables": "Oppretter tabeller for aktiviserte utvidelser",
+ "config-install-mainpage-failed": "Kunne ikke sette inn hovedside: $1",
+ "config-install-done": "<strong>Gratulrerer!</strong>\nDu har lykkes i å installere MediaWiki.\n\nInstallasjonsprogrammet har generert en <code>LocalSettings.php</code>-fil.\nDen inneholder alle dine konfigureringer.\n\nDu må laste den ned og legge den på hovedfolderen for din wiki-installasjon (der index.php ligger). Nedlastingen skulle ha startet automatisk.\n\nHvis ingen nedlasting ble tilbudt, eller du avbrøt den, kan du få den i gang ved å klikke på lenken under:\n\n$3\n\n<strong>OBS:</strong> Hvis du ikke gjør dette nå, vil den genererte konfigurasjonsfilen ikke være tilgjengelig for deg senere.\n\nNår dette er gjort, kan du <strong>[$2 gå inn i wikien]</strong>.",
+ "config-download-localsettings": "Last ned <code>LocalSettings.php</code>",
+ "config-help": "hjelp",
+ "config-help-tooltip": "klikk for å utvide",
+ "config-nofile": "Filen \"$1\" ble ikke funnet. Kan den være blitt slettet?",
+ "config-extension-link": "Visste du at wikien din kan brukes sammen med en mengde [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions utvidelser]?\n\nDu kan sjekke gjennom [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category utvidelser per kategori] eller [//www.mediawiki.org/wiki/Extension_Matrix utvidelsesmatrisen] for å se den komplette listen av utvidelser.",
+ "mainpagetext": "'''MediaWiki-programvaren er nå installert.'''",
+ "mainpagedocfooter": "Sjekk [//meta.wikimedia.org/wiki/Help:Contents brukerveiledningen] for å få informasjon om hvordan du bruker wiki-programvaren.\n\n==Hvordan komme igang==\n*[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Innstillingsliste]\n*[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Ofte stilte spørsmål om MediaWiki]\n*[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-postliste]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Tilpass MediaWiki for ditt språk]"
+}
diff --git a/includes/installer/i18n/nds-nl.json b/includes/installer/i18n/nds-nl.json
new file mode 100644
index 00000000..c33ae7b3
--- /dev/null
+++ b/includes/installer/i18n/nds-nl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Servien"
+ ]
+ },
+ "mainpagetext": "'''’t Installeren van de MediaWiki programmatuur is succesvol.'''",
+ "mainpagedocfooter": "Bekiek de [//meta.wikimedia.org/wiki/Help:Contents haandleiding] veur informasie over t gebruuk van de wikiprogrammatuur.\n\n== Meer hulpe ==\n* [//www.mediawiki.org/wiki/Help:Configuration_settings Lieste mit instellingen]\n* [//www.mediawiki.org/wiki/Help:FAQ MediaWiki-vragen die vake esteld wörden]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-postlieste veur nieje versies]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Maak MediaWiki beschikbaor in joew taal]"
+}
diff --git a/includes/installer/i18n/nds.json b/includes/installer/i18n/nds.json
new file mode 100644
index 00000000..bc71d935
--- /dev/null
+++ b/includes/installer/i18n/nds.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Joachim Mos"
+ ]
+ },
+ "config-page-name": "Naam",
+ "config-ns-generic": "Projekt",
+ "config-admin-name": "Dien Naam:",
+ "config-admin-password": "Passwoord:",
+ "config-help": "Hülp",
+ "mainpagetext": "'''De MediaWiki-Software is mit Spood installeert worrn.'''",
+ "mainpagedocfooter": "Kiek de [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentatschoon för dat Anpassen vun de Brukerböversiet]\nun dat [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Brukerhandbook] för Hülp to de Bruuk un Konfiguratschoon."
+}
diff --git a/includes/installer/i18n/ne.json b/includes/installer/i18n/ne.json
new file mode 100644
index 00000000..836e0fcb
--- /dev/null
+++ b/includes/installer/i18n/ne.json
@@ -0,0 +1,26 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bhawani Gautam",
+ "RajeshPandey",
+ "सरोज कुमार ढकाल",
+ "Ganesh Paudel",
+ "बिप्लब आनन्द"
+ ]
+ },
+ "config-information": "जानकारी",
+ "config-localsettings-badkey": "तपाइले दिनु भएको कुन्जी गलत छ ।",
+ "config-your-language": "तपाईंको भाषा:",
+ "config-your-language-help": "इन्स्टल गर्दा उपयोग गर्ने भाषा छान्नुहोस् ।",
+ "config-wiki-language": "विकि भाषाहरू",
+ "config-page-name": "नाम",
+ "config-page-options": "विकल्पहरु",
+ "config-page-install": "स्थापना गर्ने",
+ "config-page-complete": "पूरा भयो !",
+ "config-page-restart": "स्थापना फेरि सुरु गर्ने",
+ "config-page-readme": "पढ्नुहोस्",
+ "config-page-releasenotes": "प्रकाशन टिप्पणी",
+ "config-help-tooltip": "विस्तार गर्न क्लीक गर्नुहोस्",
+ "mainpagetext": "'''मीडिया सफलतापूर्वक कम्प्यूटरमा स्थापित भयो ।'''",
+ "mainpagedocfooter": " विकी अनुप्रयोग कसरी प्रयोग गर्ने भन्ने जानकारीको लागि [//meta.wikimedia.org/wiki/Help:Contents प्रयोगकर्ता सहायता] हेर्नुहोस्\n\n== सुरू गर्नको लागि ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings विन्यास सेटिङ्ग सूची]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ मेडियाविकि सामान्य प्रश्नका उत्तरहरु]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मेडियाविकि सुचना मेलिङ्ग सूची]"
+}
diff --git a/includes/installer/i18n/nl-informal.json b/includes/installer/i18n/nl-informal.json
new file mode 100644
index 00000000..e2bef3f7
--- /dev/null
+++ b/includes/installer/i18n/nl-informal.json
@@ -0,0 +1,79 @@
+{
+ "@metadata": {
+ "authors": [
+ "Siebrand",
+ "Seb35"
+ ]
+ },
+ "config-localsettings-badkey": "De sleutel die je hebt opgegeven is onjuist",
+ "config-upgrade-key-missing": "Er is een bestaande installatie van MediaWiki aangetroffen.\nPlaats de volgende regel onderaan je <code>LocalSettings.php</code> om deze installatie bij te werken:\n\n$1",
+ "config-session-expired": "Je sessiegegevens zijn verlopen.\nSessies zijn ingesteld om een levensduur van $1 te hebben.\nJe kunt deze wijzigen via de instelling <code>session.gc_maxlifetime</code> in php.ini.\nBegin het installatieproces opnieuw.",
+ "config-no-session": "Je sessiegegevens zijn verloren gegaan.\nControleer je php.ini en zorg dat er een juiste map is ingesteld voor <code>session.save_path</code>.",
+ "config-your-language": "Jouw taal:",
+ "config-help-restart": "Wil je alle opgeslagen gegevens die je hebt ingevoerd wissen en het installatieproces opnieuw starten?",
+ "config-welcome": "=== Controle omgeving ===\nEr worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.\nAls je hulp nodig hebt bij de installatie, lever deze gegevens dan ook aan.",
+ "config-copyright": "=== Auteursrechten en voorwaarden ===\n\n$1\n\nDit programma is vrije software. Je mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar eigen keuze - enige latere versie.\n\nDit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.\nZie de GNU General Public License voor meer informatie.\n\nSamen met dit programma hoor je een <doclink href=Copying>exemplaar van de GNU General Public License</doclink> ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [http://www.gnu.org/copyleft/gpl.html lees de licentie online].",
+ "config-env-good": "De omgeving is gecontroleerd.\nJe kunt MediaWiki installeren.",
+ "config-env-bad": "De omgeving is gecontroleerd.\nJe kunt MediaWiki niet installeren.",
+ "config-unicode-pure-php-warning": "'''Waarschuwing''': de [http://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicodenormalisatie af te handelen en daarom wordt de langzame PHP-implementatie gebruikt.\nAls je MediaWiki voor een website met veel verkeer installeert, lees je dan in over [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicodenormalisatie].",
+ "config-unicode-update-warning": "'''Waarschuwing''': de geïnstalleerde versie van de Unicodenormalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].\nJe moet [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations bijwerken] als Unicode voor jou van belang is.",
+ "config-no-db": "Het was niet mogelijk een geschikte databasedriver te vinden voor PHP.\nJe moet een databasedriver installeren voor PHP.\nDe volgende databases worden ondersteund: $1.\n\nAls je op een gedeelde omgeving zit, vraag dan aan je hostingprovider een geschikte databasedriver te installeren.\nAls je PHP zelf hebt gecompileerd, wijzig dan je instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.\nAls je PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.",
+ "config-outdated-sqlite": "''' Waarschuwing:''' je gebruikt SQLite $1. SQLite is niet beschikbaar omdat de minimaal vereiste versie $2 is.",
+ "config-register-globals": "'''Waarschuwing: de PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''\n'''Schakel deze uit als dat mogelijk is.'''\nMediaWiki kan ermee werken, maar je server is dan meer kwetsbaar voor beveiligingslekken.",
+ "config-magic-quotes-runtime": "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is actief!'''\nDeze instelling zorgt voor gegevenscorruptie.\nJe kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ "config-magic-quotes-sybase": "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] is actief!'''\nDeze instelling zorgt voor gegevenscorruptie.\nJe kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ "config-mbstring": "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is actief!'''\nDeze instelling zorgt voor gegevenscorruptie.\nJe kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ "config-xml-bad": "De XML-module van PHP ontbreekt.\nMediaWiki heeft de functies van deze module nodig en werkt niet zonder deze module.\nAls je gebruik maakt van Mandrake, installeer dan het package php-xml.",
+ "config-mod-security": "'''Waarschuwing:''' je webserver heeft de module [http://modsecurity.org/ mod_security] ingeschakeld. Als deze onjuist is ingesteld, kan dit problemen geven in combinatie met MediaWiki of andere software die gebruikers in staat stelt willekeurige inhoud te posten.\nLees de [http://modsecurity.org/documentation/ documentatie over mod_security] of neem contact op met de helpdesk van je provider als je tegen problemen aanloopt.",
+ "config-imagemagick": "ImageMagick aangetroffen: <code>$1</code>.\nHet aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als je uploaden inschakelt.",
+ "config-gd": "Ingebouwde GD grafische bibliotheek aangetroffen.\nHet aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als je uploaden inschakelt.",
+ "config-uploads-not-safe": "'''Waarschuwing:''' je uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.\nHoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
+ "config-no-cli-uploads-check": "''Waarschuwing:'' je standaardmap voor uploads (<code>$1</code>) wordt niet gecontroleerd op kwetsbaarheden voor het uitvoeren van willekeurige scripts gedurende de CLI-installatie.",
+ "config-brokenlibxml": "Je systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.\nUpgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger([//bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).\nDe installatie wordt afgebroken.",
+ "config-db-host-help": "Als je databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.\n\nAls je gebruik maakt van gedeelde webhosting, hoort je provider je de juiste hostnaam te hebben verstrekt.\n\nAls je MediaWiki op een Windowsserver installeert en MySQL gebruikt, dan werkt \"localhost\" mogelijk niet als servernaam.\nAls het inderdaad niet werkt, probeer dan \"127.0.0.1\" te gebruiken als lokaal IP-adres.\n\nAls je PostgreSQL gebruikt, laat dit veld dan leeg om via een Unix-socket te verbinden.",
+ "config-db-host-oracle-help": "Voer een geldige [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] in; een tnsnames.ora-bestand moet zichtbaar zijn voor deze installatie.<br />Als je gebruik maakt van clientlibraries 10g of een latere versie, kan je ook gebruik maken van de naamgevingsmethode [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-name-help": "Kies een naam die je wiki identificeert.\nEr mogen geen spaties gebruikt worden.\nAls je gebruik maakt van gedeelde webhosting, dan hoort je provider ofwel jou een te gebruiken databasenaam gegeven te hebben, of je aangegeven te hebben hoe je databases kunt aanmaken.",
+ "config-db-account-oracle-warn": "Er zijn drie ondersteunde scenario's voor het installeren van Oracle als databasebackend:\n\nAls je een databasegebruiker wilt aanmaken als onderdeel van het installatieproces, geef dan de gegevens op van een databasegebruiker in met de rol SYSDBA voor de installatie en voer de gewenste aanmeldgegevens in voor de gebruiker met webtoegang. Je kunt ook de gebruiker met webtoegang handmatig aanmaken en alleen van die gebruiker de aanmeldgegevens opgeven als deze de vereiste rechten heeft om schemaobjecten aan te maken. Als laatste is het mogelijk om aanmeldgegevens van twee verschillende gebruikers op te geven; een met de rechten om schemaobjecten aan te maken, en een met alleen webtoegang.\n\nEen script voor het aanmaken van een gebruiker met de vereiste rechten is te vinden in de map \"maintenance/oracle/\" van deze installatie. Onthoud dat het gebruiken van een gebruiker met beperkte rechten alle mogelijkheden om beheerscripts uit te voeren met de standaard gebruiker onmogelijk maakt.",
+ "config-db-prefix-help": "Als je een database moet gebruiken voor meerdere wiki's, of voor MediaWiki en een andere toepassing, dan kan je ervoor kiezen om een voorvoegsel toe te voegen aan de tabelnamen om conflicten te voorkomen.\nGebruik geen spaties.\n\nDit veld wordt meestal leeg gelaten.",
+ "config-charset-help": "'''Waarschuwing:''' als je '''achterwaarts compatibel met UTF-8''' gebruikt met MySQL 4.1+ en een back-up van de database maakt met <code>mysqldump</code>, dan kunnen alle niet-ASCII-tekens in je back-ups onherstelbaar beschadigd raken.\n\nIn '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.\nDit is efficiënter dan de UTF-8-modus van MySQL en stelt je in staat de volledige reeks Unicode-tekens te gebruiken.\nIn '''UTF-8-modus''' kent MySQL de tekenset van je gegevens en kan de databaseserver ze juist weergeven en converteren.\nHet is dan niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ "config-mysql-old": "Je moet MySQL $1 of later gebruiken.\nJij gebruikt $2.",
+ "config-db-schema-help": "Dit schema klopt meestal.\nWijzig het alleen als je weet dat dit nodig is.",
+ "config-sqlite-dir-help": "SQLite slaat alle gegevens op in een enkel bestand.\n\nDe map die je opgeeft moet beschrijfbaar zijn voor de webserver tijdens de installatie.\n\nDeze mag '''niet toegankelijk''' zijn via het web en het bestand mag dus niet tussen de PHP-bestanden staan.\n\nHet installatieprogramma schrijft het bestand <code>.htaccess</code> weg met het databasebestand, maar als dat niet werkt kan iemand zich toegang tot het ruwe databasebestand verschaffen.\nOok de gebruikersgegevens (e-mailadressen, wachtwoordhashes) en verwijderde versies en overige gegevens met beperkte toegang via MediaWiki zijn dan onbeschermd.\n\nOverweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld in <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-support-info": "MediaWiki ondersteunt de volgende databasesystemen:\n\n$1\n\nAls je het databasesysteem dat je wilt gebruiken niet in de lijst terugvindt, volg dan de handleiding waarnaar hierboven wordt verwezen om ondersteuning toe te voegen.",
+ "config-missing-db-name": "Je moet een waarde opgeven voor \"Databasenaam\"",
+ "config-missing-db-host": "Je moet een waarde invoeren voor \"Databaseserver\"",
+ "config-missing-db-server-oracle": "Je moet een waarde opgeven voor \"Database-TNS\"",
+ "config-postgres-old": "PostgreSQL $1 of hoger is vereist.\nJij gebruikt $2.",
+ "config-sqlite-name-help": "Kies een naam die je wiki identificeert.\nGebruik geen spaties of koppeltekens.\nDeze naam wordt gebruikt voor het gegevensbestand van SQLite.",
+ "config-upgrade-done": "Het bijwerken is afgerond.\n\nJe kunt [$1 je wiki nu gebruiken].\n\nAls je je <code>LocalSettings.php</code> opnieuw wilt aanmaken, klik dan op de knop hieronder.\nDit is '''niet aan te raden''' tenzij je problemen hebt met je wiki.",
+ "config-upgrade-done-no-regenerate": "Het bijwerken is afgerond.\n\nJe kunt nu [$1 je wiki gebruiken].",
+ "config-db-web-no-create-privs": "De gebruiker die je hebt opgegeven voor de installatie heeft niet voldoende rechten om een gebruiker aan te maken.\nDe gebruiker die je hier opgeeft moet al bestaan.",
+ "config-mysql-myisam-dep": "'''Waarschuwing''': je hebt MyISAM geselecteerd als opslagengine voor MySQL. Dit is niet aan te raden voor MediaWiki omdat:\n* het nauwelijks ondersteuning biedt voor gebruik door meerdere gebruikers tegelijkertijd door het locken van tabellen;\n* het meer vatbaar is voor corruptie dan andere engines;\n* de code van MediaWiki niet alstijd omgaat met MyISAM zoals dat zou moeten.\n\nAls je installatie van MySQL InnoDB ondersteunt, gebruik dat dan vooral.\nAls je installatie van MySQL geen ondersteuning heeft voor InnoDB, denk dan na over upgraden.",
+ "config-mysql-charset-help": "In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.\nDit is efficiënter dan de UTF-8-modus van MySQL en stelt je in staat de volledige reeks Unicodetekens te gebruiken.\n\nIn '''UTF-8-modus''' kent MySQL de tekenset van je gegevens en kan de databaseserver ze juist weergeven en converteren.\nHet is dat niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ "config-project-namespace-help": "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".\nAlle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat je hier kunt opgeven.\nDit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
+ "config-admin-name": "Je naam:",
+ "config-admin-password-mismatch": "De twee door jou ingevoerde wachtwoorden komen niet overeen.",
+ "config-admin-email-help": "Voer hier een e-mailadres in om e-mail te kunnen ontvangen van andere gebruikers op de wiki, je wachtwoord opnieuw in te kunnen stellen en op de hoogte te worden gehouden van wijzigingen van pagina's op uw volglijst. Je kunt het veld leeg laten.",
+ "config-admin-error-bademail": "Je hebt een ongeldig e-mailadres opgegeven",
+ "config-subscribe-help": "Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.\nAbonneer jezelf erop en werk je MediaWiki-installatie bij als er nieuwe versies uitkomen.",
+ "config-subscribe-noemail": "Je hebt geprobeerd je te abonneren op de mailinglijst voor release-aankondigingen zonder een e-mailadres op te geven.\nGeef een e-mailadres op als je je wilt abonneren op de mailinglijst.",
+ "config-almost-done": "Je bent bijna klaar!\nAls je wilt kan je de overige instellingen overslaan en de wiki nu installeren.",
+ "config-profile-help": "Wiki's werken het beste als ze door zoveel mogelijk gebruikers worden bewerkt.\nIn MediaWiki is het eenvoudig om de recente wijzigingen te controleren en eventuele foutieve of kwaadwillende bewerkingen terug te draaien.\n\nDaarnaast vinden velen MediaWiki goed inzetbaar in vele andere rollen, en soms is het niet handig om helemaal \"op de wikimanier\" te werken.\nDaarom biedt dit installatieprogramma je de volgende keuzes voor de basisinstelling van gebruikersvrijheden:\n\nEen '''{{int:config-profile-wiki}}''' staat iedereen toe te bewerken, zonder zelfs aan te melden.\nEen wiki met '''{{int:config-profile-no-anon}}\" biedt extra verantwoordelijkheid, maar kan afschrikken toevallige gebruikers afschrikken.\n\nHet scenario '''{{int:config-profile-fishbowl}}''' laat gebruikers waarvoor dat is ingesteld bewerkt, maar andere gebruikers kunnen alleen pagina's bekijken, inclusief de bewerkingsgeschiedenis.\nIn een '''{{int:config-profile-private}}''' kunnen alleen goedgekeurde gebruikers pagina's bekijken en bewerken.\n\nMeer complexe instellingen voor gebruikersrechten zijn te maken na de installatie; hierover is meer te lezen in de [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights handleiding].",
+ "config-license-help": "In veel openbare wiki's zijn alle bijdragen beschikbaar onder een [http://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 je teksten uit Wikipedia wilt kunnen gebruiken en je wilt het mogelijk maken teksten uit je wiki naar Wikipedia te kopiëren, kies dan de licentie '''Creative Commons Naamsvermelding-Gelijk delen'''.\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-enable-email-help": "Als je wilt dat e-mailen mogelijk is, dan moeten de [http://www.php.net/manual/en/mail.configuration.php e-mailinstellingen van PHP] correct zijn.\nAls je niet wilt dat e-mailen mogelijk is, dan kan je de instellingen hier uitschakelen.",
+ "config-upload-help": "Het toestaan van het uploaden van bestanden stelt je server mogelijk bloot aan beveiligingsrisico's.\nEr is meer [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security informatie over beveiliging] beschikbaar in de handleiding.\n\nOm het bestandsuploads mogelijk te maken kan je de rechten op de submap <code>images</code> onder de hoofdmap van MediaWiki aanpassen, zodat de webserver erin kan schrijven.\nDaarmee wordt deze functie ingeschakeld.",
+ "config-logo-help": "Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x160 pixels boven het menu.\nUpload een afbeelding met de juiste afmetingen en voer de URL hier in.\n\nAls je geen logo wilt gebruiken, kan je dit veld leeg laten.",
+ "config-cc-not-chosen": "Kies de Creative Commonslicentie die je wilt gebruiken en klik op \"doorgaan\".",
+ "config-memcache-needservers": "Je hebt Memcached geselecteerd als je cache, maar je hebt geen servers opgegeven.",
+ "config-memcache-badip": "Je hebt een ongeldig IP-adres ingevoerd voor Memcached: $1.",
+ "config-memcache-noport": "Je hebt geen poort opgegeven voor de Memcachedserver: $1.\nDe standaardpoort is 11211.",
+ "config-extensions-help": "De bovenstaande uitbreidingen zijn aangetroffen in de map <code>./extensions</code>.\n\nMogelijk moet je aanvullende instellingen maken, maar je kunt deze uitbreidingen nu inschakelen.",
+ "config-install-alreadydone": "'''Waarschuwing:''' het lijkt alsof je MediaWiki al hebt geïnstalleerd en probeert het programma opnieuw te installeren.\nGa door naar de volgende pagina.",
+ "config-install-begin": "Als je nu op \"{{int:config-continue}}\" klikt, begint de installatie van MediaWiki.\nAls je nog wijzigingen wilt maken, klik dan op \"Terug\".",
+ "config-pg-no-plpgsql": "Je moet de taal PL/pgSQL installeren in de database $1",
+ "config-pg-no-create-privs": "De gebruiker die je hebt opgegeven door de installatie heeft niet voldoende rechten om een gebruiker aan te maken.",
+ "config-pg-not-in-role": "De gebruiker die je hebt opgegeven voor de webgebruiker bestaat al.\nDe gebruiker die je hebt opgegeven voor installatie is geen superuser en geen lid van de rol van de webgebruiker, en kan het dus geen objecten aanmaken die van de webgebruiker zijn.\n\nMediaWiki vereist momenteel dat de tabellen van de webgebruiker zijn. Geef een andere webgebruikersnaam op, of klik op \"terug\" en geef een gebruiker op die voldoende installatierechten heeft.",
+ "config-install-user-missing-create": "De opgegeven gebruiker \"$1\" bestaat niet.\nKlik op \"registreren\" onderaan als je de gebruiker wilt aanmaken.",
+ "config-install-done": "'''Gefeliciteerd!'''\nJe hebt MediaWiki met geïnstalleerd.\n\nHet installatieprogramma heeft het bestand <code>LocalSettings.php</code> aangemaakt.\nDit bevat al je instellingen.\n\nJe moet het bestand downloaden en in de hoofdmap van uw wikiinstallatie plaatsten; in dezelfde map als index.php.\nDe download moet je automatisch zijn aangeboden.\n\nAls de download niet is aangeboden of als je de download hebt geannuleerd, dan kan je de download opnieuw starten door op de onderstaande koppeling te klikken:\n\n$3\n\n'''Let op''': als je dit niet nu doet, dan het is bestand als u later de installatieprocedure afsluit zonder het bestand te downloaden niet meer beschikbaar.\n\nNa het plaatsen van het bestand met instellingen kan je '''[$2 je wiki betreden]'''.",
+ "mainpagedocfooter": "Raadpleeg de [//meta.wikimedia.org/wiki/Help:Contents Inhoudsopgave handleiding] voor informatie over het gebruik van de wikisoftware.\n\n== Meer hulp over MediaWiki ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lijst met instellingen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veelgestelde vragen (FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglijst voor aankondigingen van nieuwe versies]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Maak MediaWiki beschikbaar in jouw taal]"
+}
diff --git a/includes/installer/i18n/nl.json b/includes/installer/i18n/nl.json
new file mode 100644
index 00000000..5bf16456
--- /dev/null
+++ b/includes/installer/i18n/nl.json
@@ -0,0 +1,330 @@
+{
+ "@metadata": {
+ "authors": [
+ "Catrope",
+ "McDutchie",
+ "Purodha",
+ "SPQRobin",
+ "Siebrand",
+ "Tjcool007",
+ "아라",
+ "Arent",
+ "JurgenNL",
+ "Southparkfan",
+ "Seb35",
+ "Mar(c)",
+ "Sjoerddebruin"
+ ]
+ },
+ "config-desc": "Het installatieprogramma voor MediaWiki",
+ "config-title": "Installatie MediaWiki $1",
+ "config-information": "Gegevens",
+ "config-localsettings-upgrade": "Er is een bestaand instellingenbestand <code>LocalSettings.php</code> gevonden.\nVoer de waarde van <code>$wgUpgradeKey</code> in in onderstaande invoerveld om deze installatie bij te werken.\nDe instelling is terug te vinden in <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Het bestand <code>LocalSettings.php</code> is al aanwezig.\nVoer <code>update.php</code> uit om deze installatie bij te werken.",
+ "config-localsettings-key": "Upgradesleutel:",
+ "config-localsettings-badkey": "De sleutel die u hebt opgegeven is onjuist",
+ "config-upgrade-key-missing": "Er is een bestaande installatie van MediaWiki aangetroffen.\nPlaats de volgende regel onderaan uw <code>LocalSettings.php</code> om deze installatie bij te werken:\n\n$1",
+ "config-localsettings-incomplete": "De bestaande inhoud van <code>LocalSettings.php</code> lijkt incompleet.\nDe variabele $1 is niet ingesteld.\nWijzig <code>LocalSettings.php</code> zodat deze variabele is ingesteld en klik op \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Er is een fout opgetreden tijdens het verbinden met de database met de instellingen uit <code>LocalSettings.php</code>. Los het probleem met de instellingen op en probeer het daarna opnieuw.\n\n$1",
+ "config-session-error": "Fout bij het starten van sessie: $1",
+ "config-session-expired": "Uw sessiegegevens zijn verlopen.\nSessies zijn ingesteld om een levensduur van $1 te hebben.\nU kunt deze wijzigen via de instelling <code>session.gc_maxlifetime</code> in php.ini.\nBegin het installatieproces opnieuw.",
+ "config-no-session": "Uw sessiegegevens zijn verloren gegaan.\nControleer uw php.ini en zorg dat er een juiste map is ingesteld voor <code>session.save_path</code>.",
+ "config-your-language": "Uw taal:",
+ "config-your-language-help": "Selecteer een taal om tijdens het installatieproces te gebruiken.",
+ "config-wiki-language": "Wikitaal:",
+ "config-wiki-language-help": "Selecteer de taal waar de wiki voornamelijk in wordt geschreven.",
+ "config-back": "← Terug",
+ "config-continue": "Doorgaan →",
+ "config-page-language": "Taal",
+ "config-page-welcome": "Welkom bij MediaWiki!",
+ "config-page-dbconnect": "Verbinding maken met database",
+ "config-page-upgrade": "Bestaande installatie bijwerken",
+ "config-page-dbsettings": "Databaseinstellingen",
+ "config-page-name": "Naam",
+ "config-page-options": "Opties",
+ "config-page-install": "Installeren",
+ "config-page-complete": "Voltooid!",
+ "config-page-restart": "Installatie herstarten",
+ "config-page-readme": "Lees mij",
+ "config-page-releasenotes": "Release notes",
+ "config-page-copying": "Kopiëren",
+ "config-page-upgradedoc": "Bijwerken",
+ "config-page-existingwiki": "Bestaande wiki",
+ "config-help-restart": "Wilt u alle opgeslagen gegevens die u hebt ingevoerd wissen en het installatieproces opnieuw starten?",
+ "config-restart": "Ja, opnieuw starten",
+ "config-welcome": "=== Controle omgeving ===\nEr worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.\nLever deze gegevens dan ook aan indien u support vraagt bij de installatie.",
+ "config-copyright": "=== Auteursrechten en voorwaarden ===\n\n$1\n\nDit programma is vrije software. U mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar uw keuze - enige latere versie.\n\nDit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.\nZie de GNU General Public License voor meer informatie.\n\nSamen met dit programma hoort u een <doclink href=Copying>exemplaar van de GNU General Public License</doclink> ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [http://www.gnu.org/copyleft/gpl.html lees de licentie online].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki thuispagina]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Gebruikershandleiding] (Engelstalig)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Beheerdershandleiding] (Engelstalig)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veel gestelde vragen] (Engelstalig)\n----\n* <doclink href=Readme>Leesmij</doclink> (Engelstalig)\n* <doclink href=ReleaseNotes>Release notes</doclink> (Engelstalig)\n* <doclink href=Copying>Kopiëren</doclink> (Engelstalig)\n* <doclink href=UpgradeDoc>Versie bijwerken</doclink> (Engelstalig)",
+ "config-env-good": "De omgeving is gecontroleerd.\nU kunt MediaWiki installeren.",
+ "config-env-bad": "De omgeving is gecontroleerd.\nU kunt MediaWiki niet installeren.",
+ "config-env-php": "PHP $1 is op dit moment geïnstalleerd.",
+ "config-unicode-using-utf8": "Voor Unicode-normalisatie wordt utf8_normalize.so van Brion Vibber gebruikt.",
+ "config-unicode-using-intl": "Voor Unicode-normalisatie wordt de [http://pecl.php.net/intl PECL-extensie intl] gebruikt.",
+ "config-unicode-pure-php-warning": "'''Waarschuwing''': de [http://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicodenormalisatie af te handelen en daarom wordt de langzame PHP-implementatie gebruikt.\nAls u MediaWiki voor een website met veel verkeer installeert, lees u dan in over [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicodenormalisatie].",
+ "config-unicode-update-warning": "'''Waarschuwing''': de geïnstalleerde versie van de Unicodenormalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].\nU moet [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations bijwerken] als Unicode voor u van belang is.",
+ "config-no-db": "Het was niet mogelijk een geschikte databasedriver te vinden voor PHP. U moet een databasedriver installeren voor PHP.\nDe volgende databases worden ondersteund: $1.\n\nAls u een gedeelde omgeving gebruikt, vraag dan aan uw hostingprovider een geschikte databasedriver te installeren.\nAls u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.\nAls u PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.",
+ "config-outdated-sqlite": "''' Waarschuwing:''' u gebruikt SQLite $1. SQLite is niet beschikbaar omdat de minimaal vereiste versie $2 is.",
+ "config-no-fts3": "'''Waarschuwing''': SQLite is gecompileerd zonder de module [//sqlite.org/fts3.html FTS3]; zoekfuncties zijn niet beschikbaar.",
+ "config-magic-quotes-runtime": "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is actief!'''\nDeze instelling zorgt voor onvoorspelbare gegevenscorruptie.\nU kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ "config-magic-quotes-sybase": "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] is actief!'''\nDeze instelling zorgt voor onvoorspelbare gegevenscorruptie.\nU kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ "config-mbstring": "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is actief!'''\nDeze instelling zorgt voor onvoorspelbare gegevenscorruptie.\nU kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ "config-safe-mode": "'''Waarschuwing:'''\n'''PHP's [http://www.php.net/features.safe-mode veilige modus] is actief.'''\nDit kan problemen veroorzaken, vooral bij het uploaden van bestanden en ondersteuning van <code>math</code>.",
+ "config-xml-bad": "De XML-module van PHP ontbreekt.\nMediaWiki heeft de functies van deze module nodig en werkt niet zonder deze module.\nAls u gebruik maakt van Mandrake, installeer dan het package php-xml.",
+ "config-pcre-old": "'''Onherstelbare fout:''' PCRE $1 of een latere versie is vereist.\nUw uitvoerbare versie van PHP is gekoppeld met PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Meer informatie].",
+ "config-pcre-no-utf8": "'''Fataal:''' 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-ctype": "'''Fataal:''' PHP moet gecompileerd zijn met ondersteuning voor de [http://www.php.net/manual/en/ctype.installation.php extensie Ctype].",
+ "config-json": "'''Fatale fout:''' PHP is gecompileerd zonder ondersteuning voor JSON.\nU moet de PHP-extensie JSON installeren of de extensie [http://pecl.php.net/package/jsonc PECL jsonc] voordat u MediaWiki installeert.\n* De PHP-extensie is beschikbaar in Red Hat Enterprise Linux (CentOS) 5 en 6, maar moet ingeschakeld worden <code>/etc/php.ini</code> or <code>/etc/php.d/json.ini</code>.\n* Sommige Linuxdistributies die zijn uitgebracht na mei 2013 hebben de PHP-extensie niet, maar hebben een package voor de PECL-extensie als <code>php5-json</code> of <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] is op dit moment geïnstalleerd",
+ "config-apc": "[http://www.php.net/apc APC] is op dit moment geïnstalleerd",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] is op dit moment geïnstalleerd",
+ "config-no-cache": "'''Waarschuwing:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] of [http://www.iis.net/download/WinCacheForPhp WinCache] is niet aangetroffen.\nHet cachen van objecten is niet ingeschakeld.",
+ "config-mod-security": "'''Waarschuwing:''' uw webserver heeft de module [http://modsecurity.org/ mod_security] ingeschakeld. Als deze onjuist is ingesteld, kan dit problemen geven in combinatie met MediaWiki of andere software die gebruikers in staat stelt willekeurige inhoud te posten.\nLees de [http://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.",
+ "config-git": "Versiecontrolesoftware git is aangetroffen: <code>$1</code>",
+ "config-git-bad": "Geen git versiecontrolesoftware aangetroffen.",
+ "config-imagemagick": "ImageMagick aangetroffen: <code>$1</code>.\nHet aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.",
+ "config-gd": "Ingebouwde GD grafische bibliotheek aangetroffen.\nHet aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.",
+ "config-no-scaling": "Noch de GD-bibliotheek noch ImageMagick zijn aangetroffen.\nHet maken van miniaturen van afbeeldingen wordt uitgeschakeld.",
+ "config-no-uri": "'''Fout:''' de huidige URI kon niet vastgesteld worden.\nDe installatie is afgebroken.",
+ "config-no-cli-uri": "'''Waarschuwing:''' de parameter <code>--scriptpath</code> is niet opgegeven. De standaardwaarde wordt gebruikt: <code>$1</code>.",
+ "config-using-server": "De servernaam \"<nowiki>$1</nowiki>\" wordt gebruikt.",
+ "config-using-uri": "De server-URL \"<nowiki>$1$2</nowiki>\" wordt gebruikt.",
+ "config-uploads-not-safe": "'''Waarschuwing:''' uw uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.\nHoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
+ "config-no-cli-uploads-check": "''Waarschuwing:'' uw standaardmap voor uploads (<code>$1</code>) wordt niet gecontroleerd op kwetsbaarheden voor het uitvoeren van willekeurige scripts gedurende de CLI-installatie.",
+ "config-brokenlibxml": "Uw systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.\nUpgrade naar libxml2 2.7.3 of hoger([https://bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).\nDe installatie wordt afgebroken.",
+ "config-suhosin-max-value-length": "Suhosin is geïnstalleerd en beperkt de GET-parameter <code>length</code> tot $1 bytes.\nDe ResourceLoader van MediaWiki omzeilt deze beperking, maar dat is slecht voor de prestaties.\nAls het mogelijk is, moet u de waarde \"<code>suhosin.get.max_value_length</code>\" in <code>php.ini</code> instellen op 1024 of hoger en <code>$wgResourceLoaderMaxQueryLength</code> in LocalSettings.php op dezelfde waarde instellen.",
+ "config-db-type": "Databasetype:",
+ "config-db-host": "Databasehost:",
+ "config-db-host-help": "Als uw databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.\n\nAls u gebruik maakt van gedeelde webhosting, hoort uw provider u de juiste hostnaam te hebben verstrekt.\n\nAls u MediaWiki op een Windowsserver installeert en MySQL gebruikt, dan werkt \"localhost\" mogelijk niet als servernaam.\nAls het inderdaad niet werkt, probeer dan \"127.0.0.1\" te gebruiken als lokaal IP-adres.\n\nAls u PostgreSQL gebruikt, laat dit veld dan leeg om via een Unix-socket te verbinden.",
+ "config-db-host-oracle": "Database-TNS:",
+ "config-db-host-oracle-help": "Voer een geldige [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] in; een tnsnames.ora-bestand moet zichtbaar zijn voor deze installatie.<br />Als u gebruik maakt van clientlibraries 10g of een latere versie, kunt u ook gebruik maken van de naamgevingsmethode [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identificeer deze wiki",
+ "config-db-name": "Databasenaam:",
+ "config-db-name-help": "Kies een naam die uw wiki identificeert.\nEr mogen geen spaties gebruikt worden.\nAls u gebruik maakt van gedeelde webhosting, dan hoort uw provider ofwel u een te gebruiken databasenaam gegeven te hebben, of u aangegeven te hebben hoe u databases kunt aanmaken.",
+ "config-db-name-oracle": "Databaseschema:",
+ "config-db-account-oracle-warn": "Er zijn drie ondersteunde scenario's voor het installeren van Oracle als databasebackend:\n\nAls u een databasegebruiker wilt aanmaken als onderdeel van het installatieproces, geef dan de gegevens op van een databasegebruiker in met de rol SYSDBA voor de installatie en voer de gewenste aanmeldgegevens in voor de gebruiker met webtoegang. U kunt ook de gebruiker met webtoegang handmatig aanmaken en alleen van die gebruiker de aanmeldgegevens opgeven als deze de vereiste rechten heeft om schemaobjecten aan te maken. Als laatste is het mogelijk om aanmeldgegevens van twee verschillende gebruikers op te geven; een met de rechten om schemaobjecten aan te maken, en een met alleen webtoegang.\n\nEen script voor het aanmaken van een gebruiker met de vereiste rechten is te vinden in de map \"maintenance/oracle/\" van deze installatie. Onthoud dat het gebruiken van een gebruiker met beperkte rechten alle mogelijkheden om beheerscripts uit te voeren met de standaard gebruiker onmogelijk maakt.",
+ "config-db-install-account": "Gebruiker voor installatie",
+ "config-db-username": "Gebruikersnaam voor database:",
+ "config-db-password": "Wachtwoord voor database:",
+ "config-db-password-empty": "Voer een wachtwoord in voor de nieuwe databasegebruiker: $1.\nHoewel het wellicht mogelijk is gebruikers aan te maken zonder wachtwoord, is dit niet veilig.",
+ "config-db-username-empty": "U moet een waarde invoeren voor \"{{int:config-db-username}}\".",
+ "config-db-install-username": "Voer de gebruikersnaam in die gebruikt moet worden om te verbinden met de database tijdens het installatieproces. Dit is niet de gebruikersnaam van de MediaWikigebruiker. Dit is de gebruikersnaam voor de database.",
+ "config-db-install-password": "Voer het wachtwoord in dat gebruikt moet worden om te verbinden met de database tijdens het installatieproces. Dit is niet het wachtwoord van de MediaWikigebruiker. Dit is het wachtwoord voor de database.",
+ "config-db-install-help": "Voer de gebruikersnaam en het wachtwoord in die worden gebruikt voor de databaseverbinding tijdens het installatieproces.",
+ "config-db-account-lock": "Dezelfde gebruiker en wachwoord gebruiken na de installatie",
+ "config-db-wiki-account": "Gebruiker voor na de installatie",
+ "config-db-wiki-help": "Voer de gebruikersnaam en het wachtwoord in die gebruikt worden om verbinding te maken met de database na de installatie.\nAls de gebruiker niet bestaat en de gebruiker die tijdens de installatie gebruikt wordt voldoende rechten heeft, wordt deze gebruiker aangemaakt met de minimaal benodigde rechten voor het laten werken van de wiki.",
+ "config-db-prefix": "Databasetabelvoorvoegsel:",
+ "config-db-prefix-help": "Als u een database moet gebruiken voor meerdere wiki's, of voor MediaWiki en een andere toepassing, dan kunt u ervoor kiezen om een voorvoegsel toe te voegen aan de tabelnamen om conflicten te voorkomen.\nGebruik geen spaties.\n\nDit veld wordt meestal leeg gelaten.",
+ "config-db-charset": "Tekenset voor de database",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binair",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 UTF-8-compatibel",
+ "config-charset-help": "'''Waarschuwing:''' als u '''achterwaarts compatibel met UTF-8''' gebruikt met MySQL 4.1+ en een back-up van de database maakt met <code>mysqldump</code>, dan kunnen alle niet-ASCII-tekens in uw back-ups onherstelbaar beschadigd raken.\n\nIn '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.\nDit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledige reeks Unicode-tekens te gebruiken.\nIn '''UTF-8-modus''' kent MySQL de tekenset van uw gegevens en kan de databaseserver ze juist weergeven en converteren.\nHet is dan niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ "config-mysql-old": "U moet MySQL $1 of later gebruiken.\nU gebruikt $2.",
+ "config-db-port": "Databasepoort:",
+ "config-db-schema": "Schema voor MediaWiki",
+ "config-db-schema-help": "Dit schema klopt meestal.\nWijzig het alleen als u weet dat dit nodig is.",
+ "config-pg-test-error": "Kan geen verbinding maken met database '''$1''': $2",
+ "config-sqlite-dir": "Gegevensmap voor SQLite:",
+ "config-sqlite-dir-help": "SQLite slaat alle gegevens op in een enkel bestand.\n\nDe map die u opgeeft moet beschrijfbaar zijn voor de webserver tijdens de installatie.\n\nDeze mag '''niet toegankelijk''' zijn via het web en het bestand mag dus niet tussen de PHP-bestanden staan.\n\nHet installatieprogramma schrijft het bestand <code>.htaccess</code> weg met het databasebestand, maar als dat niet werkt kan iemand zich toegang tot het ruwe databasebestand verschaffen.\nOok de gebruikersgegevens (e-mailadressen, wachtwoordhashes) en verwijderde versies en overige gegevens met beperkte toegang via MediaWiki zijn dan onbeschermd.\n\nOverweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld in <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Standaard tablespace:",
+ "config-oracle-temp-ts": "Tijdelijke tablespace:",
+ "config-type-mysql": "MySQL (of compatibel)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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-mysql-url}} MySQL] is de primaire database voor MediaWiki en wordt het best ondersteund. MediaWiki werkt ook met [{{int:version-db-mariadb-url}} MariaDB] en [{{int:version-db-percona-url}} Percona Server], die MySQL compatibel zijn. ([http://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. Het is mogelijk dat er een aantal bekende kleinere problemen zijn met MediaWiki in combinatie met deze database en daarom wordt PostgreSQL niet aanbevolen voor een productieomgeving.([http://www.php.net/manual/en/pgsql.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor PostgreSQL]).",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] is een zeer goed ondersteund lichtgewicht databasesysteem ([http://www.php.net/manual/en/pdo.installation.php hoe PHP gecompileerd zijn met ondersteuning voor SQLite]; gebruikt PDO)",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] is een commerciële database voor grote bedrijven ([http://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. Zie ook [http://www.php.net/manual/en/sqlsrv.installation.php PHP compileren met ondersteuning voor SQLSRV].",
+ "config-header-mysql": "MySQL-instellingen",
+ "config-header-postgres": "PostgreSQL-instellingen",
+ "config-header-sqlite": "SQLite-instellingen",
+ "config-header-oracle": "Oracle-instellingen",
+ "config-header-mssql": "Instellingen voor Microsoft SQL Server",
+ "config-invalid-db-type": "Ongeldig databasetype.",
+ "config-missing-db-name": "U moet een waarde opgeven voor \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "U moet een waarde invoeren voor \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "U moet een waarde opgeven voor \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "Ongeldige database-TNS \"$1\".\nGebruik \"TNS Names\" of een \"Easy Connect\" tekst ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle naamgevingsmethoden])",
+ "config-invalid-db-name": "Ongeldige databasenaam \"$1\".\nGebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en streepjes (-).",
+ "config-invalid-db-prefix": "Ongeldig databasevoorvoegsel \"$1\".\nGebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en streepjes (-).",
+ "config-connection-error": "$1.\n\nControleer de host, gebruikersnaam en wachtwoord en probeer het opnieuw.",
+ "config-invalid-schema": "Ongeldig schema voor MediaWiki \"$1\".\nGebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_).",
+ "config-db-sys-create-oracle": "Het installatieprogramma biedt alleen de mogelijkheid een nieuwe gebruiker aan te maken met de SYSDBA-gebruiker.",
+ "config-db-sys-user-exists-oracle": "De gebruiker \"$1\" bestaat al. SYSDBA kan alleen gebruikt worden voor het aanmaken van een nieuwe gebruiker!",
+ "config-postgres-old": "PostgreSQL $1 of hoger is vereist.\nU gebruikt $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 of hoger is vereist. U hebt $2.",
+ "config-sqlite-name-help": "Kies een naam die uw wiki identificeert.\nGebruik geen spaties of koppeltekens.\nDeze naam wordt gebruikt voor het gegevensbestand van SQLite.",
+ "config-sqlite-parent-unwritable-group": "Het was niet mogelijk de gegevensmap <code><nowiki>$1</nowiki></code> te maken omdat in de bovenliggende map <code><nowiki>$2</nowiki></code> niet geschreven mag worden door de webserver.\n\nHet installatieprogramma heeft vast kunnen stellen onder welke gebruiker de webserver draait.\nMaak de map <code><nowiki>$3</nowiki></code> beschrijfbaar om door te kunnen gaan.\nVoer op een Linux-systeem de volgende opdrachten uit:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Het was niet mogelijk de gegevensmap <code><nowiki>$1</nowiki></code> te maken omdat in de bovenliggende map <code><nowiki>$2</nowiki></code> niet geschreven mag worden door de webserver.\n\nHet installatieprogramma heeft niet vast kunnen stellen onder welke gebruiker de webserver draait.\nMaak de map <code><nowiki>$3</nowiki></code> beschrijfbaar voor de webserver (en anderen!) om door te kunnen gaan.\nVoer op een Linux-systeem de volgende opdrachten uit:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Er is een fout opgetreden tijdens het aanmaken van de gegevensmap \"$1\".\nControleer de locatie en probeer het opnieuw.",
+ "config-sqlite-dir-unwritable": "Het was niet mogelijk in de map \"$1\" te schrijven.\nWijzig de rechten zodat de webserver erin kan schrijven en probeer het opnieuw.",
+ "config-sqlite-connection-error": "$1.\n\nControleer de map voor gegevens en de databasenaam hieronder en probeer het opnieuw.",
+ "config-sqlite-readonly": "Er kan niet naar bestand <code>$1</code> worden geschreven.",
+ "config-sqlite-cant-create-db": "Het was niet mogelijk het databasebestand <code>$1</code> aan te maken.",
+ "config-sqlite-fts3-downgrade": "PHP heeft geen ondersteuning voor FTS3.\nDe tabellen worden gedowngrade.",
+ "config-can-upgrade": "Er staan al tabellen voor MediaWiki in deze database.\nKlik op '''Doorgaan''' om ze bij te werken naar MediaWiki $1.",
+ "config-upgrade-done": "Het bijwerken is afgerond.\n\nUw kunt [$1 uw wiki nu gebruiken].\n\nAls u uw <code>LocalSettings.php</code> opnieuw wilt aanmaken, klik dan op de knop hieronder.\nDit is '''niet aan te raden''' tenzij u problemen hebt met uw wiki.",
+ "config-upgrade-done-no-regenerate": "Het bijwerken is afgerond.\n\nU kunt nu [$1 uw wiki gebruiken].",
+ "config-regenerate": "LocalSettings.php opnieuw aanmaken →",
+ "config-show-table-status": "Het uitvoeren van <code>SHOW TABLE STATUS</code> is mislukt!",
+ "config-unknown-collation": "'''Waarschuwing:''' de database gebruikt een collatie die niet wordt herkend.",
+ "config-db-web-account": "Databasegebruiker voor webtoegang",
+ "config-db-web-help": "Selecteer de gebruikersnaam en het wachtwoord die de webserver gebruikt om verbinding te maken met de databaseserver na de installatie.",
+ "config-db-web-account-same": "Dezelfde gebruiker gebruiken als voor de installatie",
+ "config-db-web-create": "Maak de gebruiker aan als deze nog niet bestaat",
+ "config-db-web-no-create-privs": "De gebruiker die u hebt opgegeven voor de installatie heeft niet voldoende rechten om een gebruiker aan te maken.\nDe gebruiker die u hier opgeeft moet al bestaan.",
+ "config-mysql-engine": "Opslagmethode:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Waarschuwing''': u hebt MyISAM geselecteerd als opslagengine voor MySQL. Dit is niet aan te raden voor MediaWiki omdat:\n* het nauwelijks ondersteuning biedt voor gebruik door meerdere gebruikers tegelijkertijd door het locken van tabellen;\n* het meer vatbaar is voor corruptie dan andere engines;\n* de code van MediaWiki niet alstijd omgaat met MyISAM zoals dat zou moeten.\n\nAls uw installatie van MySQL InnoDB ondersteunt, gebruik dat dan vooral.\nAls uw installatie van MySQL geen ondersteuning heeft voor InnoDB, denk dan na over upgraden.",
+ "config-mysql-only-myisam-dep": "'''Waarschuwing:''' MyISAM is enige beschikbare opslagmethode voor MySQL in deze omgeving, en deze wordt niet aangeraden voor gebruik met MediaWiki, omdat:\n* er nauwelijks ondersteuning is voor meerdere gelijktijdige transacties omdat tabellen op slot gezet worden;\n* tabellen makkelijker stuk kunnen gaan;\n* de code van MediaWiki niet altijd op de juiste wijze omgaat met MyISAM.\n\nUw installatie van MySQL heeft geen ondersteuning voor InnoDB. We raden u aan om een meer recente versie te gebruiken.",
+ "config-mysql-engine-help": "'''InnoDB''' is vrijwel altijd de beste instelling, omdat deze goed omgaat met meerdere verzoeken tegelijkertijd.\n\n'''MyISAM''' is bij een zeer beperkt aantal gebruikers mogelijk sneller, of als de wiki alleen-lezen is.\nMyISAM-databases raken vaker corrupt dan InnoDB-databases.",
+ "config-mysql-charset": "Tekenset voor de database:",
+ "config-mysql-binary": "Binair",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.\nDit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledige reeks Unicodetekens te gebruiken.\n\nIn '''UTF-8-modus''' kent MySQL de tekenset van uw gegevens en kan de databaseserver ze juist weergeven en converteren.\nHet is dat niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ "config-mssql-auth": "Authenticatietype:",
+ "config-mssql-install-auth": "Selecteer de authenticatiemethode die wordt gebruikt om met de database te verbinden tijdens het installatieproces.\nAls u \"{{int:config-mssql-windowsauth}}\" selecteert, dan worden de aanmeldgegevens van de gebruiker waaronder de webserver draait voor authenticatie gebruikt.",
+ "config-mssql-web-auth": "Selecteer de authenticatiemethode die de webserver gebruikt om met de database te verbinden tijdens het installatieproces.\nAls u \"{{int:config-mssql-windowsauth}}\" selecteert, dan worden de aanmeldgegevens van de gebruiker waaronder de webserver draait voor authenticatie gebruikt.",
+ "config-mssql-sqlauth": "SQL Server Authenticatie",
+ "config-mssql-windowsauth": "Windowsauthenticatie",
+ "config-site-name": "Naam van de wiki:",
+ "config-site-name-help": "Deze naam verschijnt in de titelbalk van browsers en op andere plaatsen.",
+ "config-site-name-blank": "Geef een naam op voor de site.",
+ "config-project-namespace": "Projectnaamruimte:",
+ "config-ns-generic": "Project",
+ "config-ns-site-name": "Zelfde als de wiki: $1",
+ "config-ns-other": "Andere (geef aan welke)",
+ "config-ns-other-default": "MijnWiki",
+ "config-project-namespace-help": "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".\nAlle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat u hier kunt opgeven.\nDit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
+ "config-ns-invalid": "De opgegeven naamruimte \"<nowiki>$1</nowiki>\" is ongeldig.\nGeef een andere naamruimte op.",
+ "config-ns-conflict": "De opgegeven naamruimte \"<nowiki>$1</nowiki>\" conflicteert met een standaard naamruimte in MediaWiki.\nGeef een andere naam op voor de projectnaamruimte.",
+ "config-admin-box": "Beheerdersgebruiker",
+ "config-admin-name": "Uw gebruikersnaam:",
+ "config-admin-password": "Wachtwoord:",
+ "config-admin-password-confirm": "Wachtwoord opnieuw:",
+ "config-admin-help": "Voer de gebruikersnaam hier in, bijvoorbeeld \"Jan Jansen\".\nDit is de naam die wordt gebruikt om aan de melden bij de wiki.",
+ "config-admin-name-blank": "Geef een gebruikersnaam op voor de beheerder.",
+ "config-admin-name-invalid": "De opgegeven gebruikersnaam \"<nowiki>$1</nowiki>\" is ongeldig.\nKies een andere gebruikersnaam.",
+ "config-admin-password-blank": "Voer een wachtwoord voor de beheerder in.",
+ "config-admin-password-mismatch": "De twee door u ingevoerde wachtwoorden komen niet overeen.",
+ "config-admin-email": "E-mailadres:",
+ "config-admin-email-help": "Voer hier een e-mailadres in om e-mail te kunnen ontvangen van andere gebruikers op de wiki, uw wachtwoord opnieuw in te kunnen stellen en op de hoogte te worden gehouden van wijzigingen van pagina's op uw volglijst. U kunt het veld leeg laten.",
+ "config-admin-error-user": "Interne fout bij het aanmaken van een beheerder met de naam \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Interne fout bij het instellen van een wachtwoord voor de beheerder \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "U hebt een ongeldig e-mailadres opgegeven.",
+ "config-subscribe": "Abonneren op de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailinglijst releaseaankondigen].",
+ "config-subscribe-help": "Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.\nAbonneer uzelf erop en werk uw MediaWiki-installatie bij als er nieuwe versies uitkomen.",
+ "config-subscribe-noemail": "U hebt geprobeerd zich te abonneren op de mailinglijst voor release-aankondigingen zonder een e-mailadres op te geven.\nGeef een e-mailadres op als u zich wilt abonneren op de mailinglijst.",
+ "config-almost-done": "U bent bijna klaar!\nAls u wilt kunt u de overige instellingen overslaan en de wiki nu installeren.",
+ "config-optional-continue": "Stel me meer vragen.",
+ "config-optional-skip": "Laat maar zitten, installeer gewoon de wiki.",
+ "config-profile": "Gebruikersrechtenprofiel:",
+ "config-profile-wiki": "Open wiki",
+ "config-profile-no-anon": "Gebruiker aanmaken verplicht",
+ "config-profile-fishbowl": "Alleen voor geautoriseerde bewerkers",
+ "config-profile-private": "Privéwiki",
+ "config-profile-help": "Wiki's werken het beste als ze door zoveel mogelijk gebruikers worden bewerkt.\nIn MediaWiki is het eenvoudig om de recente wijzigingen te controleren en eventuele foutieve of kwaadwillende bewerkingen terug te draaien.\n\nDaarnaast vinden velen MediaWiki goed inzetbaar in vele andere rollen, en soms is het niet handig om helemaal \"op de wikimanier\" te werken.\nDaarom biedt dit installatieprogramma u de volgende keuzes voor de basisinstelling van gebruikersvrijheden:\n\nHet profiel '''{{int:config-profile-wiki}}''' staat iedereen toe te bewerken, zonder zelfs aan te melden.\nEen wiki met '''{{int:config-profile-no-anon}}\" biedt extra verantwoordelijkheid, maar kan afschrikken toevallige gebruikers afschrikken.\n\nHet scenario '''{{int:config-profile-fishbowl}}''' laat gebruikers waarvoor dat is ingesteld bewerkt, maar andere gebruikers kunnen alleen pagina's bekijken, inclusief de bewerkingsgeschiedenis.\nIn een '''{{int:config-profile-private}}''' kunnen alleen goedgekeurde gebruikers pagina's bekijken en bewerken.\n\nMeer complexe instellingen voor gebruikersrechten zijn te maken na de installatie; hierover is meer te lezen in de [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights handleiding].",
+ "config-license": "Auteursrechten en licentie:",
+ "config-license-none": "Geen licentie in de voettekst",
+ "config-license-cc-by-sa": "Creative Commons Naamsvermelding-Gelijk delen",
+ "config-license-cc-by": "Creative Commons Naamsvermelding",
+ "config-license-cc-by-nc-sa": "Creative Commons Naamsvermelding-Niet Commercieel-Gelijk delen",
+ "config-license-cc-0": "Creative Commons Zero (Publiek domein)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 of hoger",
+ "config-license-pd": "Publiek domein",
+ "config-license-cc-choose": "Een Creative Commons-licentie selecteren",
+ "config-license-help": "In veel openbare wiki's zijn alle bijdragen beschikbaar onder een [http://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 '''Creative Commons Naamsvermelding-Gelijk delen'''.\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 [http://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",
+ "config-email-usertalk-help": "Gebruikers toestaan meldingen te ontvangen bij wijzigingen op de eigen overlegpagina, als dit in de voorkeuren is ingesteld.",
+ "config-email-watchlist": "Volglijstmeldingen inschakelen",
+ "config-email-watchlist-help": "Gebruikers toestaan meldingen te ontvangen bij wijzigingen van pagina's op hun volglijst, als dit in de voorkeuren is ingesteld.",
+ "config-email-auth": "E-mailbevestiging inschakelen",
+ "config-email-auth-help": "Als deze instelling actief is, moeten gebruikers hun e-mailadres bevestigen via een verwijziging die zij per e-mail wordt toegezonden.\nAlleen bevestigde e-mailadressen kunnen e-mail ontvangen van andere gebruikers of wijzigingsnotificaties ontvangen.\nHet inschakelen van deze instelling wordt '''aangeraden''' voor openbare wiki's vanwege de mogelijkheden voor misbruik van e-mailmogelijkheden.",
+ "config-email-sender": "E-mailadres voor antwoorden:",
+ "config-email-sender-help": "Voer het e-mailadres in dat u wilt gebruiken als antwoordadres voor uitgaande e-mail.\nAls een e-mail niet bezorgd kan worden, wordt dat op dit e-mailadres gemeld.\nVeel mailservers vereisen dat tenminste het domein bestaat.",
+ "config-upload-settings": "Afbeeldingen en bestanden uploaden",
+ "config-upload-enable": "Uploaden van bestanden inschakelen",
+ "config-upload-help": "Het toestaan van het uploaden van bestanden stelt uw server mogelijk bloot aan beveiligingsrisico's.\nEr is meer [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security informatie over beveiliging] beschikbaar in de handleiding.\n\nOm het bestandsuploads mogelijk te maken kunt u de rechten op de submap <code>images</code> onder de hoofdmap van MediaWiki aanpassen, zodat de webserver erin kan schrijven.\nDaarmee wordt deze functie ingeschakeld.",
+ "config-upload-deleted": "Map voor verwijderde bestanden:",
+ "config-upload-deleted-help": "Kies een map waarin verwijderde bestanden gearchiveerd kunnen worden.\nIdealiter is deze map niet via het web te benaderen.",
+ "config-logo": "URL voor logo:",
+ "config-logo-help": "Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x160 pixels boven het menu.\nUpload een afbeelding met de juiste afmetingen en voer de URL hier in.\n\nU kunt <code>$wgStylePath</code> of <code>$wgScriptPath</code> gebruiken als uw logo relatief is aan een van deze paden.\n\nAls u geen logo wilt gebruiken, kunt u dit veld leeg laten.",
+ "config-instantcommons": "Instant Commons inschakelen",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] is functie die het mogelijk maakt om afbeeldingen, geluidsbestanden en andere mediabestanden te gebruiken van de website [//commons.wikimedia.org/ Wikimedia Commons].\nHiervoor heeft MediaWiki toegang nodig tot Internet.\n\nMeer informatie over deze functie en hoe deze in te stellen voor andere wiki's dan Wikimedia Commons is te vinden in de [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos handleiding].",
+ "config-cc-error": "De licentiekiezer van Creative Commons heeft geen resultaat opgeleverd.\nVoer de licentie handmatig in.",
+ "config-cc-again": "Opnieuw kiezen...",
+ "config-cc-not-chosen": "Kies de Creative Commonslicentie die u wilt gebruiken en klik op \"doorgaan\".",
+ "config-advanced-settings": "Gevorderde instellingen",
+ "config-cache-options": "Instellingen voor het cachen van objecten:",
+ "config-cache-help": "Het cachen van objecten wordt gebruikt om de snelheid van MediaWiki te verbeteren door vaak gebruikte gegevens te bewaren.\nMiddelgrote tot grote websites wordt geadviseerd dit in te schakelen en ook kleine sites merken de voordelen.",
+ "config-cache-none": "Niets cachen.\nEr gaat geen functionaliteit verloren, maar dit kan invloed hebben op de prestaties.",
+ "config-cache-accel": "Cachen van objecten via PHP (APC, XCache of WinCache)",
+ "config-cache-memcached": "Memcached gebruiken (dit vereist aanvullende instellingen)",
+ "config-memcached-servers": "Memcachedservers:",
+ "config-memcached-help": "Lijst met IP-adressen te gebruiken voor Memcached.\nEén IP-adres per regel met een poortnummer.\nBijvoorbeeld:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "U hebt Memcached geselecteerd als uw cache, maar u hebt geen servers opgegeven.",
+ "config-memcache-badip": "U hebt een ongeldig IP-adres ingevoerd voor Memcached: $1.",
+ "config-memcache-noport": "U hebt geen poort opgegeven voor de Memcachedserver: $1.\nDe standaardpoort is 11211.",
+ "config-memcache-badport": "Poortnummers voor Memcached moeten tussen $1 en $2 liggen.",
+ "config-extensions": "Uitbreidingen",
+ "config-extensions-help": "De bovenstaande uitbreidingen zijn aangetroffen in de map <code>./extensions</code>.\n\nMogelijk moet u aanvullende instellingen maken, maar u kunt deze uitbreidingen nu inschakelen.",
+ "config-skins": "Vormgevingen",
+ "config-skins-use-as-default": "Als standaard vormgeving instellen",
+ "config-install-alreadydone": "'''Waarschuwing:''' het lijkt alsof u MediaWiki al hebt geïnstalleerd en probeert het programma opnieuw te installeren.\nGa door naar de volgende pagina.",
+ "config-install-begin": "Als u nu op \"{{int:config-continue}}\" klikt, begint de installatie van MediaWiki.\nAls u nog wijzigingen wilt maken, klik dan op \"{{int:config-back}}\".",
+ "config-install-step-done": "afgerond",
+ "config-install-step-failed": "mislukt",
+ "config-install-extensions": "Inclusief uitbreidingen",
+ "config-install-database": "Database inrichten",
+ "config-install-schema": "Het schema wordt aangemaakt",
+ "config-install-pg-schema-not-exist": "Het schema voor PostgreSQL bestaat niet",
+ "config-install-pg-schema-failed": "Het aanmaken van de tabellen is mislukt.\nZorg dat de gebruiker \"$1\" in het schema \"$2\" mag schrijven.",
+ "config-install-pg-commit": "Wijzigingen worden doorgevoerd",
+ "config-install-pg-plpgsql": "Controle op de taal PL/pgSQL",
+ "config-pg-no-plpgsql": "U moet de taal PL/pgSQL installeren in de database $1",
+ "config-pg-no-create-privs": "De gebruiker die u hebt opgegeven door de installatie heeft niet voldoende rechten om een gebruiker aan te maken.",
+ "config-pg-not-in-role": "De gebruiker die u hebt opgegeven voor de webgebruiker bestaat al.\nDe gebruiker die u hebt opgegeven voor installatie is geen superuser en geen lid van de rol van de webgebruiker, en kan het dus geen objecten aanmaken die van de webgebruiker zijn.\n\nMediaWiki vereist momenteel dat de tabellen van de webgebruiker zijn. Geef een andere webgebruikersnaam op, of klik op \"terug\" en geef een gebruiker op die voldoende installatierechten heeft.",
+ "config-install-user": "Databasegebruiker aan het aanmaken",
+ "config-install-user-alreadyexists": "Gebruiker \"$1\" bestaat al",
+ "config-install-user-create-failed": "Het aanmaken van de gebruiker \"$1\" is mislukt: $2",
+ "config-install-user-grant-failed": "Het geven van rechten aan gebruiker \"$1\" is mislukt: $2",
+ "config-install-user-missing": "De opgegeven gebruiker \"$1\" bestaat niet.",
+ "config-install-user-missing-create": "De opgegeven gebruiker \"$1\" bestaat niet.\nKlik op \"registreren\" onderaan als u de gebruiker wilt aanmaken.",
+ "config-install-tables": "Tabellen aanmaken",
+ "config-install-tables-exist": "'''Waarschuwing''': de MediaWikitabellen lijken al te bestaan.\nHet aanmaken wordt overgeslagen.",
+ "config-install-tables-failed": "'''Fout''': het aanmaken van een tabel is mislukt met de volgende foutmelding: $1",
+ "config-install-interwiki": "Bezig met het vullen van de interwikitabel",
+ "config-install-interwiki-list": "Het bestand <code>interwiki.list</code> is niet aangetroffen",
+ "config-install-interwiki-exists": "'''Waarschuwing''': de interwikitabel heeft al inhoud.\nDe standaardlijst wordt overgeslagen.",
+ "config-install-stats": "Statistieken initialiseren",
+ "config-install-keys": "Bezig met aanmaken van geheime sleutels",
+ "config-insecure-keys": "'''Waarschuwing:''' De {{PLURAL:$2|sleutel die is aangemaakt|sleutels die zijn aangemaakt}} ($1) tijdens de installatie {{PLURAL:$2|is|zijn}} niet volledig veilig. Overweeg deze handmatig te wijzigen.",
+ "config-install-sysop": "Gebruiker voor beheerder aanmaken",
+ "config-install-subscribe-fail": "Het is niet mogelijk te abonneren op mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL is niet geïnstalleerd en <code>allow_url_fopen</code> is niet beschikbaar.",
+ "config-install-mainpage": "Hoofdpagina aanmaken met standaard inhoud",
+ "config-install-extension-tables": "Tabellen voor ingeschakelde uitbreidingen worden aangemaakt",
+ "config-install-mainpage-failed": "Het was niet mogelijk de hoofdpagina in te voegen: $1",
+ "config-install-done": "'''Gefeliciteerd!'''\nU hebt MediaWiki met succes geïnstalleerd.\n\nHet installatieprogramma heeft het bestand <code>LocalSettings.php</code> aangemaakt.\nDit bevat al uw instellingen.\n\nU moet het bestand downloaden en in de hoofdmap van uw wiki-installatie plaatsten, in dezelfde map als index.php.\nDe download moet u automatisch zijn aangeboden.\n\nAls de download niet is aangeboden of als u de download hebt geannuleerd, dan kunt u de download opnieuw starten door op de onderstaande koppeling te klikken:\n\n$3\n\n'''Let op''': als u dit niet nu doet, dan is het bestand als u later de installatieprocedure afsluit zonder het bestand te downloaden niet meer beschikbaar.\n\nNa het plaatsen van het bestand met instellingen kunt u '''[$2 uw wiki gebruiken]'''.",
+ "config-download-localsettings": "<code>LocalSettings.php</code> downloaden",
+ "config-help": "hulp",
+ "config-help-tooltip": "klik om uit te klappen",
+ "config-nofile": "Het bestand \"$1\" is niet gevonden. Is het verwijderd?",
+ "config-extension-link": "Weet u dat u [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions uitbreidingen] kunt gebruiken voor uw wiki?\nU kunt [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category uitbreidingen op categorie] bekijken of ga naar de [//www.mediawiki.org/wiki/Extension_Matrix uitbreidingenmatrix] om de volledige lijst met uitbreidingen te bekijken.",
+ "mainpagetext": "'''De installatie van MediaWiki is geslaagd.'''",
+ "mainpagedocfooter": "Raadpleeg de [//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Contents handleiding] voor informatie over het gebruik van de wikisoftware.\n\n== Meer hulp over MediaWiki ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lijst met instellingen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veelgestelde vragen (FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglijst voor aankondigingen van nieuwe versies]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Maak MediaWiki beschikbaar in uw taal]"
+}
diff --git a/includes/installer/i18n/nn.json b/includes/installer/i18n/nn.json
new file mode 100644
index 00000000..5d75be70
--- /dev/null
+++ b/includes/installer/i18n/nn.json
@@ -0,0 +1,41 @@
+{
+ "@metadata": {
+ "authors": [
+ "Harald Khan",
+ "Nghtwlkr"
+ ]
+ },
+ "config-your-language": "Språket ditt:",
+ "config-wiki-language": "Wikispråk:",
+ "config-back": "← Attende",
+ "config-continue": "Hald fram →",
+ "config-page-language": "Språk",
+ "config-memory-raised": "PHPs <code>memory_limit</code> er $1, auka til $2.",
+ "config-memory-bad": "'''Advarsel:''' PHPs <code>memory_limit</code> er $1.\nDette er sannsynlegvis for lågt.\nInstallasjonen kan mislukkast!",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] er innstallert",
+ "config-apc": "[http://www.php.net/apc APC] er innstallert",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] er installert",
+ "config-db-name": "Databasenamn:",
+ "config-db-username": "Databasebrukarnamn:",
+ "config-db-password": "Databasepassord:",
+ "config-db-charset": "Databaseteiknsett",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binær",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 bakoverkompatibel UTF-8",
+ "config-mysql-old": "MySQL $1 eller seinare krevst, du har $2.",
+ "config-db-port": "Databaseport:",
+ "config-db-schema": "Skjema for MediaWiki",
+ "config-header-mysql": "MySQL-innstillingar",
+ "config-header-postgres": "PostgreSQL-innstillingar",
+ "config-header-sqlite": "SQLite-innstillingar",
+ "config-header-oracle": "Oracle-innstillingar",
+ "config-invalid-db-type": "Ugyldig databasetype",
+ "config-invalid-db-name": "Ugyldig databasenamn «$1».\nBerre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).",
+ "config-invalid-db-prefix": "Ugyldig databaseprefiks «$1».\nBerre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).",
+ "config-invalid-schema": "Ugyldig skjema for MediaWiki «$1».\nBerre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).",
+ "config-postgres-old": "PostgreSQL $1 eller seinare krevst, du har $2.",
+ "config-email-settings": "E-postinnstillingar",
+ "config-logo": "Logo-URL:",
+ "mainpagetext": "'''MediaWiki er no installert.'''",
+ "mainpagedocfooter": "Sjå [//meta.wikimedia.org/wiki/Help:Contents brukarmanualen] for informasjon om bruk og oppsettshjelp for wikiprogramvara.\n\n==Kome i gang==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste over oppsettsinnstillingar]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Spørsmål og svar om MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postliste med informasjon om nye MediaWiki-versjonar]"
+}
diff --git a/includes/installer/i18n/oc.json b/includes/installer/i18n/oc.json
new file mode 100644
index 00000000..372058f8
--- /dev/null
+++ b/includes/installer/i18n/oc.json
@@ -0,0 +1,181 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cedric31",
+ "Jfblanc",
+ "Seb35"
+ ]
+ },
+ "config-desc": "Lo programa d’installacion de MediaWiki",
+ "config-title": "Installacion de MediaWiki $1",
+ "config-information": "Informacions",
+ "config-localsettings-key": "Clau de mesa a jorn :",
+ "config-localsettings-badkey": "La clau qu'avètz provesida es incorrècta",
+ "config-session-error": "Error al moment de l'aviada de la sesilha : $1",
+ "config-your-language": "Vòstra lenga :",
+ "config-your-language-help": "Seleccionatz la lenga d'utilizar pendent lo processus d'installacion.",
+ "config-wiki-language": "Lenga del wiki :",
+ "config-wiki-language-help": "Seleccionar la lenga dins la quala lo wiki serà principalament escrich.",
+ "config-back": "← Retorn",
+ "config-continue": "Contunhar →",
+ "config-page-language": "Lenga",
+ "config-page-welcome": "Benvenguda sus MediaWiki !",
+ "config-page-dbconnect": "Se connectar a la banca de donadas",
+ "config-page-upgrade": "Metre a jorn l’installacion existenta",
+ "config-page-dbsettings": "Paramètres de la banca de donadas",
+ "config-page-name": "Nom",
+ "config-page-options": "Opcions",
+ "config-page-install": "Installar",
+ "config-page-complete": "Acabat !",
+ "config-page-restart": "Reaviar l’installacion",
+ "config-page-readme": "Legissètz-me",
+ "config-page-releasenotes": "Nòtas de version",
+ "config-page-copying": "Còpia",
+ "config-page-upgradedoc": "Mesa a jorn",
+ "config-page-existingwiki": "Wiki existent",
+ "config-restart": "Òc, lo reaviar",
+ "config-env-good": "L’environament es estat verificat.\nPodètz installar MediaWiki.",
+ "config-env-bad": "L’environament es estat verificat.\nPodètz pas installar MediaWiki.",
+ "config-env-php": "PHP $1 es installat.",
+ "config-env-hhvm": "HHVM $1 es installat.",
+ "config-unicode-using-utf8": "Utilizacion de utf8_normalize.so per Brion Vibber per la normalizacion Unicode.",
+ "config-unicode-using-intl": "Utilizacion de [http://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-xcache": "[http://xcache.lighttpd.net/ XCache] es installat",
+ "config-apc": "[http://www.php.net/apc APC] es installat",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] es installat",
+ "config-diff3-bad": "GNU diff3 pas trobat.",
+ "config-git": "Logicial de contraròtle de version Git trobat : <code>$1</code>.",
+ "config-git-bad": "Logicial de contraròtle de version Git pas trobat.",
+ "config-imagemagick": "ImageMagick trobat : <code>$1</code>.\nLa miniaturizacion d'imatges serà activada se activatz lo telecargament de fichièrs.",
+ "config-gd": "La bibliotèca grafica GD integrada es estada trobada.\nLa miniaturizacion d'imatges serà activada se activatz lo telecargament de fichièrs.",
+ "config-using-server": "Utilizacion del nom de servidor \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Utilizacion de l'URL de servidor \"<nowiki>$1$2</nowiki>\".",
+ "config-db-type": "Tipe de banca de donadas :",
+ "config-db-host": "Nom d’òste de la banca de donadas :",
+ "config-db-host-oracle": "Nom TNS de la banca de donadas :",
+ "config-db-wiki-settings": "Identificar aqueste wiki",
+ "config-db-name": "Nom de la banca de donadas :",
+ "config-db-name-oracle": "Esquèma de banca de donadas :",
+ "config-db-install-account": "Compte d'utilizaire per l'installacion",
+ "config-db-username": "Nom d'utilizaire de la banca de donadas :",
+ "config-db-password": "Senhal de la banca de donadas :",
+ "config-db-username-empty": "Vos cal entrar una valor per « {{int:config-db-username}} ».",
+ "config-db-account-lock": "Utilizar lo meteis nom d'utilizaire e lo meteis senhal pendent lo foncionament abitual",
+ "config-db-wiki-account": "Compte d'utilizaire pel foncionament abitual",
+ "config-db-prefix": "Préfix de las taulas de la banca de donadas :",
+ "config-db-charset": "Jòc de caractèrs de la banca de donadas",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binari",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 retrocompatible UTF-8",
+ "config-mysql-old": "MySQL $1 o version ulteriora es requesit, avètz $2.",
+ "config-db-port": "Pòrt de la banca de donadas :",
+ "config-db-schema": "Esquèma per MediaWiki",
+ "config-pg-test-error": "Impossible de se connectar a la banca de donadas '''$1''' : $2",
+ "config-sqlite-dir": "Dorsièr de las donadas SQLite :",
+ "config-oracle-def-ts": "Espaci d'emmagazinatge (''tablespace'') per defaut :",
+ "config-oracle-temp-ts": "Espaci d'emmagazinatge (''tablespace'') temporari :",
+ "config-type-mysql": "MySQL (o compatible)",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-header-mysql": "Paramètres de MySQL",
+ "config-header-postgres": "Paramètres de PostgreSQL",
+ "config-header-sqlite": "Paramètres de SQLite",
+ "config-header-oracle": "Paramètres d’Oracle",
+ "config-header-mssql": "Paramètres de Microsoft SQL Server",
+ "config-invalid-db-type": "Tipe de banca de donadas invalid",
+ "config-missing-db-name": "Vos cal entrar una valor per « {{int:config-db-name}} ».",
+ "config-missing-db-host": "Vos cal entrar una valor per « {{int:config-db-host}} ».",
+ "config-missing-db-server-oracle": "Vos cal entrar una valor per « {{int:config-db-oracle}} ».",
+ "config-postgres-old": "PostgreSQL $1 o version ulteriora es requesit, avètz $2.",
+ "config-sqlite-readonly": "Lo fichièr <code>$1</code> es pas accessible en escritura.",
+ "config-sqlite-cant-create-db": "Impossible de crear lo fichièr de banca de donadas <code>$1</code>.",
+ "config-regenerate": "Regenerar LocalSettings.php →",
+ "config-show-table-status": "Fracàs de la requèsta <code>SHOW TABLE STATUS</code> !",
+ "config-db-web-account": "Compte de la banca de donadas per l'accès Web",
+ "config-db-web-account-same": "Utilizatz lo meteis compte que per l'installacion",
+ "config-db-web-create": "Creatz lo compte se existís pas ja",
+ "config-mysql-engine": "Motor d'emmagazinatge :",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Jòc de caractèrs de la banca de donadas :",
+ "config-mysql-binary": "Binari",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Tipe d’autentificacion :",
+ "config-mssql-sqlauth": "Autentificacion de SQL Server",
+ "config-mssql-windowsauth": "Autentificacion Windows",
+ "config-site-name": "Nom del wiki :",
+ "config-site-name-blank": "Entratz un nom de site.",
+ "config-project-namespace": "Espaci de noms del projècte :",
+ "config-ns-generic": "Projècte",
+ "config-ns-site-name": "Meteis nom que lo wiki : $1",
+ "config-ns-other": "Autre (precisar)",
+ "config-ns-other-default": "MonWiki",
+ "config-admin-box": "Compte d'administrator",
+ "config-admin-name": "Vòstre nom d'utilizaire :",
+ "config-admin-password": "Senhal :",
+ "config-admin-password-confirm": "Picatz lo senhal tornarmai :",
+ "config-admin-name-blank": "Entratz un nom d'administrator.",
+ "config-admin-password-blank": "Entratz un senhal pel compte d'administrator.",
+ "config-admin-password-mismatch": "Los dos senhals qu'avètz picats correspondon pas.",
+ "config-admin-email": "Adreça de corrièr electronic :",
+ "config-admin-error-user": "Error intèrna al moment de la creacion d'un administrator amb lo nom « <nowiki>$1</nowiki> ».",
+ "config-admin-error-bademail": "Avètz entrat una adreça de corrièr electronic invalida",
+ "config-optional-continue": "Me pausar mai de questions.",
+ "config-optional-skip": "N'ai pro, installar simplament lo wiki.",
+ "config-profile": "Perfil dels dreches d’utilizaires :",
+ "config-profile-wiki": "Wiki dobèrt",
+ "config-profile-no-anon": "Creacion de compte requesida",
+ "config-profile-fishbowl": "Editors autorizats solament",
+ "config-profile-private": "Wiki privat",
+ "config-license": "Dreches d'autor e licéncia :",
+ "config-license-none": "Pas cap de licéncia en bas de pagina",
+ "config-license-cc-by-sa": "Creative Commons atribucion partiment a l'identic",
+ "config-license-cc-by": "Creative Commons Atribucion",
+ "config-license-cc-by-nc-sa": "Creative Commons paternitat – non comercial – partiment a l’identic",
+ "config-license-cc-0": "Creative Commons Zero (domeni public)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 o ulteriora",
+ "config-license-pd": "Domeni public",
+ "config-license-cc-choose": "Seleccionar una licéncia Creative Commons personalizada",
+ "config-email-settings": "Paramètres de corrièr electronic",
+ "config-enable-email": "Activar los corrièls sortents",
+ "config-email-user": "Activar los corrièrs electronics d'utilizaire a utilizaire",
+ "config-email-usertalk": "Activar la notificacion de las paginas de discussion dels utilizaires",
+ "config-email-watchlist": "Activar la notificacion de la lista de seguiment",
+ "config-email-auth": "Activar l'autentificacion per corrièr electronic",
+ "config-email-sender": "Entrar una adreça electronica de retorn :",
+ "config-upload-settings": "Telecargament dels imatges e dels fichièrs",
+ "config-upload-enable": "Activar lo telecargament dels fichièrs",
+ "config-upload-deleted": "Repertòri pels fichièrs suprimits :",
+ "config-logo": "URL del lògo :",
+ "config-instantcommons": "Activar ''InstantCommons''",
+ "config-cc-again": "Causissètz tornarmai...",
+ "config-advanced-settings": "Configuracion avançada",
+ "config-cache-options": "Paramètres per la mesa en escondedor dels objèctes :",
+ "config-memcached-servers": "servidors per Memcached :",
+ "config-extensions": "Extensions",
+ "config-skins": "Abilhatges",
+ "config-skins-use-as-default": "Utilizar aqueste abilhatge per defaut",
+ "config-install-step-done": "fach",
+ "config-install-step-failed": "fracàs",
+ "config-install-extensions": "Inclusion de las extensions",
+ "config-install-database": "Creacion de la banca de donadas",
+ "config-install-schema": "Creacion d'esquèma",
+ "config-install-pg-schema-not-exist": "L'esquèma PostgreSQL existís pas",
+ "config-install-pg-commit": "Validacion de las modificacions",
+ "config-install-pg-plpgsql": "Verificacion del lengatge PL/pgSQL",
+ "config-install-user": "Creacion d'un utilizaire de la banca de donadas",
+ "config-install-user-alreadyexists": "L'utilizaire « $1 » existís ja.",
+ "config-install-user-create-failed": "Fracàs al moment de la creacion de l'utilizaire « $1 » : $2",
+ "config-install-user-missing": "L'utilizaire «$1» existís pas.",
+ "config-install-tables": "Creacion de las taulas",
+ "config-install-stats": "Inicializacion de las estatisticas",
+ "config-install-keys": "Generacion de la clau secreta",
+ "config-install-updates": "Empachar l’execucion de las mesas a jorn inutilas",
+ "config-install-sysop": "Creacion del compte administrator",
+ "config-install-mainpage-failed": "Impossible d’inserir la pagina principala : $1",
+ "config-download-localsettings": "Telecargar <code>LocalSettings.php</code>",
+ "config-help": "ajuda",
+ "config-help-tooltip": "clicar per agrandir",
+ "mainpagetext": "'''MediaWiki es estat installat amb succès.'''",
+ "mainpagedocfooter": "Consultatz lo [//meta.wikimedia.org/wiki/Help:Contents/fr Guida de l'utilizaire] per mai d'entresenhas sus l'utilizacion d'aqueste logicial de wiki.\n\n== Per començar ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista dels paramètres de configuracion]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/oc FAQ MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussions de las distribucions de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Adaptatz MediaWiki dins vòstra lenga]"
+}
diff --git a/includes/installer/i18n/or.json b/includes/installer/i18n/or.json
new file mode 100644
index 00000000..83fb3d83
--- /dev/null
+++ b/includes/installer/i18n/or.json
@@ -0,0 +1,33 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jnanaranjan Sahu",
+ "Psubhashish"
+ ]
+ },
+ "config-session-error": "ଅଧିବେଶନ ଆରମ୍ଭରେ ଅସୁବିଧା: $1",
+ "config-your-language": "ଆପଣଙ୍କ ଭାଷା:",
+ "config-your-language-help": "ଇନଷ୍ଟଲ କରିବା ବେଳେ ବ୍ୟବହାର ପାଇଁ ଏକ ଭାଷା ବାଛନ୍ତୁ ।",
+ "config-wiki-language": "ଉଇକି ଭାଷା:",
+ "config-back": "← ପଛକୁ",
+ "config-continue": "ଚାଲୁରଖିବେ →",
+ "config-page-language": "ଭାଷା",
+ "config-page-welcome": "ମେଡିଆଉଇକିକୁ ଆପଣଙ୍କୁ ସ୍ଵାଗତ",
+ "config-page-dbconnect": "ଡାଟାବେସ ସହ ଯୋଡ଼ନ୍ତୁ",
+ "config-page-upgrade": "ଏବେର ଇନଷ୍ଟଲେସନଟିକୁ ଅପଗ୍ରେଡ଼ କରନ୍ତୁ",
+ "config-page-dbsettings": "ଡାଟାବେସ ସଂରଚନା",
+ "config-page-name": "ନାମ",
+ "config-page-options": "ପସନ୍ଦସମୂହ",
+ "config-page-install": "ଇନଷ୍ଟଲ",
+ "config-page-complete": "ଶେଷ ହେଲା!",
+ "config-page-restart": "ଇନଷ୍ଟଲେସନ ପୁନଃଆରମ୍ଭ କରନ୍ତୁ",
+ "config-page-readme": "ପଢ଼ନ୍ତୁ",
+ "config-page-releasenotes": "ପ୍ରକାଶନ ସୂଚନା",
+ "config-page-copying": "ନକଲ କରୁଛି",
+ "config-page-upgradedoc": "ଅପଗ୍ରେଡ଼ କରୁଛି",
+ "config-page-existingwiki": "ଏବେକାର ଉଇକି",
+ "config-restart": "ହଁ, ଏହାକୁ ପୁନରାରମ୍ଭ କରନ୍ତୁ",
+ "config-env-php": "PHP $1 ଇନଷ୍ଟଲ ହେଲା ।",
+ "config-license-cc-by-sa": "କ୍ରିଏଟିଭ କମନ୍ସ ଆଟ୍ରିବ୍ୟୁସନ-ସେଆର ଏଲାଇକ",
+ "config-license-cc-by-nc-sa": "କ୍ରିଏଟିଭ କମନ୍ସ ଆଟ୍ରିବ୍ୟୁସନ-ନନକମର୍ସିଆଲ ସେଆର ଏଲାଇକ"
+}
diff --git a/includes/installer/i18n/os.json b/includes/installer/i18n/os.json
new file mode 100644
index 00000000..0df9aa1d
--- /dev/null
+++ b/includes/installer/i18n/os.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amikeco"
+ ]
+ },
+ "config-page-language": "Æвзаг",
+ "mainpagetext": "'''Вики-скрипт «MediaWiki» æнтыстджынæй æвæрд æрцыд.'''"
+}
diff --git a/includes/installer/i18n/pa.json b/includes/installer/i18n/pa.json
new file mode 100644
index 00000000..c129ab96
--- /dev/null
+++ b/includes/installer/i18n/pa.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Aalam"
+ ]
+ },
+ "config-information": "ਜਾਣਕਾਰੀ",
+ "config-your-language": "ਤੁਹਾਡੀ ਭਾਸ਼ਾ:",
+ "config-back": "← ਪਿੱਛੇ",
+ "config-continue": "ਜਾਰੀ ਰੱਖੋ →",
+ "config-page-language": "ਭਾਸ਼ਾ",
+ "mainpagetext": "'''ਮੀਡਿਆਵਿਕਿ ਠੀਕ ਤਰ੍ਹਾਂ ਇੰਸਟਾਲ ਹੋ ਗਿਆ ਹੈ।'''"
+}
diff --git a/includes/installer/i18n/pam.json b/includes/installer/i18n/pam.json
new file mode 100644
index 00000000..c04a510e
--- /dev/null
+++ b/includes/installer/i18n/pam.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Melaus ing pamipalyari ning MediaWiki.'''",
+ "mainpagedocfooter": "Basan me ing [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para king impormasiun keng pamangamit ning wiki software.\n\n== Pamagumpisa ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/pcd.json b/includes/installer/i18n/pcd.json
new file mode 100644
index 00000000..84f9d3c3
--- /dev/null
+++ b/includes/installer/i18n/pcd.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki o té instalé aveuc victoère.'''"
+}
diff --git a/includes/installer/i18n/pdc.json b/includes/installer/i18n/pdc.json
new file mode 100644
index 00000000..2a052239
--- /dev/null
+++ b/includes/installer/i18n/pdc.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Xqt"
+ ]
+ },
+ "config-continue": "Weider →",
+ "config-page-language": "Schprooch",
+ "config-admin-password": "Paesswatt:",
+ "config-install-step-done": "geduh",
+ "config-help": "Hilf",
+ "mainpagedocfooter": "Hilf fer's Yuuse unn Konfiguriere vun de Wiki-Software kansch finne im [//meta.wikimedia.org/wiki/Help:Contents Handbuch fer Yuuser].\n\n== Hilf zum Schtaerte ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lischt vun Gnepp zum Konfiguriere]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Eposchde-Lischt fer neie MediaWiki-Versione]"
+}
diff --git a/includes/installer/i18n/pl.json b/includes/installer/i18n/pl.json
new file mode 100644
index 00000000..2e7e0233
--- /dev/null
+++ b/includes/installer/i18n/pl.json
@@ -0,0 +1,340 @@
+{
+ "@metadata": {
+ "authors": [
+ "Beau",
+ "BeginaFelicysym",
+ "Chrumps",
+ "Holek",
+ "Matma Rex",
+ "Michał Roszka",
+ "Saper",
+ "Sp5uhe",
+ "Woytecr",
+ "아라",
+ "Amire80",
+ "Jacenty359",
+ "Pan Cube",
+ "WTM",
+ "Alan ffm",
+ "Matik7",
+ "Pio387"
+ ]
+ },
+ "config-desc": "Instalator MediaWiki",
+ "config-title": "Instalacja MediaWiki $1",
+ "config-information": "Informacja",
+ "config-localsettings-upgrade": "Plik <code>LocalSettings.php</code> istnieje.\nAby oprogramowanie zostało zaktualizowane musisz wstawić wartość <code>$wgUpgradeKey</code> w poniższe pole.\nOdnajdziesz ją w <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Wykryto obecność pliku <code>LocalSettings.php</code>.\nAktualizację należy wykonać poprzez uruchomienie <code>update.php</code>",
+ "config-localsettings-key": "Klucz aktualizacji:",
+ "config-localsettings-badkey": "Podany klucz jest nieprawidłowy",
+ "config-upgrade-key-missing": "Wykryto zainstalowane wcześniej MediaWiki.\nJeśli chcesz je zaktualizować dodaj na koniec pliku <code>LocalSettings.php</code> poniższą linię tekstu.\n\n$1",
+ "config-localsettings-incomplete": "Istniejący plik <code>LocalSettings.php</code> wygląda na niekompletny.\nBrak wartości zmiennej $1.\nZmień plik <code>LocalSettings.php</code>, tak by zawierał deklarację wartości tej zmiennej, a następnie kliknij „{{int:Config-continue}}”.",
+ "config-localsettings-connection-error": "Wystąpił błąd podczas łączenia z bazą danych używając ustawień podanych w <code>LocalSettings.php</code>\nNapraw te ustawienia i spróbuj ponownie.\n\n$1",
+ "config-session-error": "Błąd uruchomienia sesji – $1",
+ "config-session-expired": "Wygląda na to, że Twoja sesja wygasła.\nCzas życia sesji został skonfigurowany na $1.\nMożesz go wydłużyć zmieniając <code>session.gc_maxlifetime</code> w pliku php.ini.\nUruchom ponownie proces instalacji.",
+ "config-no-session": "Dane sesji zostały utracone.\nSprawdź plik php.ini i upewnij się, że <code>session.save_path</code> wskazuje na odpowiedni katalog.",
+ "config-your-language": "Twój język:",
+ "config-your-language-help": "Wybierz język używany podczas procesu instalacji.",
+ "config-wiki-language": "Język wiki:",
+ "config-wiki-language-help": "Wybierz język, w którym będzie tworzona większość treści wiki.",
+ "config-back": "← Wstecz",
+ "config-continue": "Dalej →",
+ "config-page-language": "Język",
+ "config-page-welcome": "Witamy w MediaWiki!",
+ "config-page-dbconnect": "Połączenie z bazą danych",
+ "config-page-upgrade": "Uaktualnienie istniejącej instalacji",
+ "config-page-dbsettings": "Ustawienia bazy danych",
+ "config-page-name": "Nazwa",
+ "config-page-options": "Opcje",
+ "config-page-install": "Instaluj",
+ "config-page-complete": "Zakończono!",
+ "config-page-restart": "Rozpoczęcie instalacji od nowa",
+ "config-page-readme": "Podstawowe informacje",
+ "config-page-releasenotes": "Informacje o wersji",
+ "config-page-copying": "Kopiowanie",
+ "config-page-upgradedoc": "Uaktualnienie",
+ "config-page-existingwiki": "Istniejąca wiki",
+ "config-help-restart": "Czy chcesz usunąć wszystkie zapisane dane i uruchomić ponownie proces instalacji?",
+ "config-restart": "Tak, zacznij od nowa",
+ "config-welcome": "=== Sprawdzenie środowiska instalacji ===\nTeraz zostaną wykonane podstawowe testy sprawdzające czy to środowisko jest odpowiednie dla instalacji MediaWiki.\nJeśli potrzebujesz pomocy podczas instalacji, załącz wyniki tych testów.",
+ "config-copyright": "=== Prawa autorskie i warunki użytkowania ===\n\n$1\n\nTo oprogramowanie jest wolne; możesz je rozprowadzać dalej i modyfikować zgodnie z warunkami licencji GNU General Public License opublikowanej przez Free Software Foundation w wersji 2 tej licencji lub (według Twojego wyboru) którejś z późniejszych jej wersji.\n\nNiniejsze oprogramowanie jest rozpowszechniane w nadziei, że będzie użyteczne, ale '''bez żadnej gwarancji'''; nawet bez domniemanej gwarancji '''handlowej''' lub '''przydatności do określonego celu'''.\nZobacz treść licencji GNU General Public License, aby uzyskać więcej szczegółów.\n\nRazem z oprogramowaniem powinieneś otrzymać <doclink href=Copying>kopię licencji GNU General Public License</doclink>. Jeśli jej nie otrzymałeś, napisz do Free Software Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. lub [http://www.gnu.org/copyleft/gpl.html przeczytaj ją online].",
+ "config-sidebar": "* [//www.mediawiki.org Strona domowa MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Podręcznik użytkownika]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Podręcznik administratora]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Odpowiedzi na często zadawane pytania]\n----\n* <doclink href=Readme>Przeczytaj to</doclink>\n* <doclink href=ReleaseNotes>Informacje o tej wersji</doclink>\n* <doclink href=Copying>Kopiowanie</doclink>\n* <doclink href=UpgradeDoc>Aktualizacja</doclink>",
+ "config-env-good": "Środowisko oprogramowania zostało sprawdzone.\nMożesz teraz zainstalować MediaWiki.",
+ "config-env-bad": "Środowisko oprogramowania zostało sprawdzone.\nNie możesz zainstalować MediaWiki.",
+ "config-env-php": "Zainstalowane jest PHP w wersji $1.",
+ "config-env-hhvm": "Zainstalowany jest HHVM $1.",
+ "config-unicode-using-utf8": "Korzystanie z normalizacji Unicode utf8_normalize.so napisanej przez Brion Vibbera.",
+ "config-unicode-using-intl": "Korzystanie z [http://pecl.php.net/intl rozszerzenia intl PECL] do normalizacji Unicode.",
+ "config-unicode-pure-php-warning": "'''Uwaga!''' [http://pecl.php.net/intl Rozszerzenie intl PECL] do obsługi normalizacji Unicode nie jest dostępne. Użyta zostanie mało wydajna zwykła implementacja w PHP.\nJeśli prowadzisz stronę o dużym natężeniu ruchu, powinieneś zapoznać się z informacjami o [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizacji Unicode].",
+ "config-unicode-update-warning": "'''Uwaga''' – zainstalowana wersja normalizacji Unicode korzysta z nieaktualnej biblioteki [http://site.icu-project.org/ projektu ICU].\nPowinieneś [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations zrobić aktualizację] jeśli chcesz korzystać w pełni z Unicode.",
+ "config-no-db": "Nie można odnaleźć właściwego sterownika bazy danych! Musisz zainstalować sterownik bazy danych dla PHP.\nMożna użyć następujących typów baz danych: $1.\n\nJeśli skompilowałeś PHP samodzielnie, skonfiguruj je ponownie z włączonym klientem bazy danych, na przykład za pomocą polecenia <code>./configure --with-mysqli</code>.\nJeśli zainstalowałeś PHP jako pakiet Debiana lub Ubuntu, musisz również zainstalować np. moduł <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Ostrzeżenie''': masz SQLite $1, która jest niższa od minimalnej wymaganej wersji $2 . SQLite będzie niedostępne.",
+ "config-no-fts3": "'''Uwaga''' – SQLite został skompilowany bez [//sqlite.org/fts3.html modułu FTS3] – funkcje wyszukiwania nie będą dostępne.",
+ "config-register-globals-error": "<strong>Błąd: dyrektywa PHP <code>[http://php.net/register_globals register_globals]</code> jest włączona.\nAby kontynuować instalację musi zostać wyłączona.</strong>\nPrzeczytaj [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals], aby dowiedzieć się, jak to zrobić.",
+ "config-magic-quotes-gpc": "<strong>Błąd krytyczny – dyrektywa [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] jest włączona!</strong>\nTa opcja powoduje nieprzewidywalne uszkodzenia wprowadzanych danych.\nNie możesz instalować lub korzystać z MediaWiki, dopóki ta opcja nie zostanie wyłączona.",
+ "config-magic-quotes-runtime": "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''\nTa opcja powoduje nieprzewidywalne uszkodzenia wprowadzanych danych.\nZainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
+ "config-magic-quotes-sybase": "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''\nTa opcja powoduje nieprzewidywalne uszkodzenia wprowadzanych danych.\nZainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
+ "config-mbstring": "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''\nTa opcja powoduje błędy i może wywołać nieprzewidywalne uszkodzenia wprowadzanych danych.\nZainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
+ "config-safe-mode": "'''Ostrzeżenie''' – uaktywniono [http://www.php.net/features.safe-mode tryb awaryjny] PHP.\nOpcja ta może powodować problemy, szczególnie w przypadku korzystania z przesyłania plików i używania znacznika <code>math</code>.",
+ "config-xml-bad": "Brak modułu XML dla PHP.\nMediaWiki wymaga funkcji z tego modułu i nie może działać w tej konfiguracji.\nJeśli korzystasz z Mandrake, zainstaluj pakiet php-xml.",
+ "config-pcre-old": "<strong>Błąd krytyczny:</strong> Wymagany jest PCRE w wersji $1 lub nowszej.\nTwój plik wykonywalny PHP jest powiązany z wersją PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Więcej informacji].",
+ "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-ctype": "''' Krytyczny ''': PHP musi być skompilowany z obsługą [http://www.php.net/manual/en/ctype.installation.php rozszerzenia Ctype].",
+ "config-iconv": "<strong>Błąd krytyczny:</strong> PHP musi być skompilowane z obsługą [http://www.php.net/manual/en/iconv.installation.php rozszerzenia iconv].",
+ "config-json": "'''Błąd krytyczny:''' PHP skompilowano bez obsługa JSON.\nPrzed zainstalowaniem oprogramowania MediaWiki musisz zainstalować rozszerzenie PHP JSON albo rozszerzenie [http://pecl.php.net/package/jsonc PECL jsonc].\n* Rozszerzenie PHP jest zawarte w Red Hat Enterprise Linux (CentOS) 5 i 6, jednak musi zostać włączone w <code>/etc/php.ini</code> or <code>/etc/php.d/json.ini</code>.\n* Niektóre dystrybucje Linuksa, wydane po maju 2013, nie używają rozszerzenia PHP, lecz rozszerzenie PECL, jako <code>php5-json</code> lub <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[Http://trac.lighttpd.net/xcache/ XCache] jest zainstalowany",
+ "config-apc": "[Http://www.php.net/apc APC] jest zainstalowany",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] jest zainstalowany",
+ "config-no-cache": "'''Uwaga:''' Pamięć podręczna dla kodu MediaWiki nie będzie uruchomiona., gdyż nie ma żadnego z następujących narzędzi: [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] lub [http://www.iis.net/download/WinCacheForPhp WinCache].",
+ "config-mod-security": "''' Ostrzeżenie ''': Serwer sieci web ma włączone [http://modsecurity.org/ mod_security]. Jeśli 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 [http://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 GNU diff3.",
+ "config-git": "Znaleziono oprogramowanie kontroli wersji Git: <code>$1</code>.",
+ "config-git-bad": "Oprogramowanie systemu kontroli wersji Git nie zostało znalezione.",
+ "config-imagemagick": "Mamy zainstalowany ImageMagick <code>$1</code>, dzięki czemu będzie można pomniejszać załadowane grafiki.",
+ "config-gd": "Mamy wbudowaną bibliotekę graficzną GD, dzięki czemu będzie można pomniejszać załadowane grafiki.",
+ "config-no-scaling": "Nie odnaleziono biblioteki GD lub ImageMagick. Możliwość zmniejszania załadowywanych grafik zostanie wyłączona.",
+ "config-no-uri": "'''Błąd:''' Nie można określić aktualnego URI.\nInstalacja została przerwana.",
+ "config-no-cli-uri": "<strong>Ostrzeżenie:</strong> Nie wskazano <code>--scriptpath</code>, użycie wartości domyślnej: <code>$1</code>.",
+ "config-using-server": "„<nowiki>$1</nowiki>“ jest adresem serwera, na którym instalowana jest wiki.",
+ "config-using-uri": "Wiki będzie zainstalowana pod adresem \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Uwaga''' – domyślny katalog do którego zapisywane są przesyłane pliki <code>$1</code> jest podatny na wykonanie dowolnego skryptu.\nChociaż MediaWiki sprawdza wszystkie przesłane pliki pod kątem bezpieczeństwa, zaleca się jednak, aby [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security zamknąć tę lukę w zabezpieczeniach] przed włączeniem przesyłania plików.",
+ "config-no-cli-uploads-check": "'''Ostrzeżenie:''' Katalog domyślny przesyłanych plików ( <code>$1</code> ) nie jest sprawdzona względem luki\n wykonania dowolnego skryptu podczas instalacji CLI w zabezpieczeniach.",
+ "config-brokenlibxml": "Twój system jest kombinacją wersji PHP i libxml2, która zawiera błędy mogące powodować ukryte uszkodzenia danych w MediaWiki i innych aplikacjach sieci web.\nWykonaj aktualizację libxml2 do wersji 2.7.3 lub późniejszej ([https://bugs.php.net/bug.php?id=45996 bug filed with PHP]).\nInstalacja została przerwana.",
+ "config-suhosin-max-value-length": "Jest zainstalowany Suhosin i ogranicza długość parametru GET <code>length</code> do $1 bajtów. Komponent ResourceLoader w MediaWiki wykona obejście tego ograniczenia, ale kosztem wydajności.\nJeśli to możliwe, należy ustawić <code>suhosin.get.max_value_length</code> na 1024 lub więcej w <code>php.ini</code> oraz ustawić <code>$wgResourceLoaderMaxQueryLength</code> w <code>LocalSettings.php</code> na tę samą wartość.",
+ "config-db-type": "Typ bazy danych:",
+ "config-db-host": "Adres serwera bazy danych:",
+ "config-db-host-help": "Jeśli serwer bazy danych jest na innej maszynie, wprowadź jej nazwę domenową lub adres IP.\n\nJeśli korzystasz ze współdzielonego hostingu, operator serwera powinien podać Ci prawidłową nazwę serwera w swojej dokumentacji.\n\nJeśli instalujesz oprogramowanie na serwerze Windows i korzystasz z MySQL, użycie „localhost” może nie zadziałać jako nazwa hosta. Jeśli wystąpi ten problem, użyj „127.0.0.1” jako lokalnego adresu IP.\n\nJeżeli korzystasz z PostgreSQL, pozostaw to pole puste, aby połączyć się poprzez gniazdo Unixa.",
+ "config-db-host-oracle": "Nazwa instancji bazy danych (TNS):",
+ "config-db-host-oracle-help": "Wprowadź prawidłową [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nazwę połączenia lokalnego]. Plik „tnsnames.ora” musi być widoczny dla instalatora.<br />Jeśli używasz biblioteki klienckiej 10g lub nowszej możesz również skorzystać z metody nazw [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm łatwego łączenia].",
+ "config-db-wiki-settings": "Zidentyfikuj tę wiki",
+ "config-db-name": "Nazwa bazy danych:",
+ "config-db-name-help": "Wybierz nazwę, która zidentyfikuje Twoją wiki.\nNie może ona zawierać spacji.\n\nJeśli korzystasz ze współdzielonego hostingu, dostawca usługi hostingowej może wymagać użycia konkretnej nazwy bazy danych lub pozwalać na tworzenie baz danych za pośrednictwem panelu użytkownika.",
+ "config-db-name-oracle": "Nazwa schematu bazy danych:",
+ "config-db-account-oracle-warn": "Bazę danych Oracle można przygotować do pracy z MediaWiki na trzy sposoby:\n\nMożesz utworzyć konto użytkownika bazy danych podczas instalacji MediaWiki. Wówczas należy podać nazwę i hasło użytkownika z rolą SYSDBA w celu użycia go przez instalator do utworzenia nowe konta użytkownika, z którego korzystać będzie MediaWiki.\n\nMożesz również skorzystać z konta użytkownika bazy danych utworzonego bezpośrednio w Oracle i wówczas wystarczy podać tylko nazwę i hasło tego użytkownika. Konto z rolą SYSDBA nie będzie potrzebne, jednak konto użytkownika powinno mieć uprawnienia do utworzenia obiektów w schemacie bazy danych. Możesz też podać dwa konta - konto dla instalatora, z pomocą którego zostaną obiekty w schemacie bazy danych i drugie konto, z którego będzie MediaWiki korzystać będzie do pracy.\n\nW podkatalogu \"maintenance/oracle\" znajduje się skrypt do tworzenia konta użytkownika. Korzystanie z konta użytkownika z ograniczonymi uprawnieniami spowoduje wyłączenie funkcji związanych z aktualizacją oprogramowania MediaWiki.",
+ "config-db-install-account": "Konto użytkownika dla instalatora",
+ "config-db-username": "Nazwa użytkownika bazy danych:",
+ "config-db-password": "Hasło bazy danych:",
+ "config-db-password-empty": "Wprowadź hasło dla nowego użytkownika bazy danych: $1.\nChoć istnieje możliwość tworzenia użytkowników bez hasła, nie jest to bezpieczne.",
+ "config-db-username-empty": "Należy podać wartość parametru \"{{int:config-db-username}}\".",
+ "config-db-install-username": "Wprowadź nazwę użytkownika, który będzie używany do łączenia się z bazą danych podczas procesu instalacji.\nNie jest to nazwa konta MediaWiki, a użytkownika bazy danych.",
+ "config-db-install-password": "Wprowadź hasło, które będzie wykorzystywane do łączenia się z bazą danych w procesie instalacji.\nTo nie jest hasło konta MediaWiki, lecz hasło do bazy danych.",
+ "config-db-install-help": "Podaj nazwę użytkownika i jego hasło, które zostaną użyte do połączenia z bazą danych w czasie procesu instalacji.",
+ "config-db-account-lock": "Użyj tej samej nazwy użytkownika i hasła w czasie normalnej pracy.",
+ "config-db-wiki-account": "Konto użytkownika do normalnej pracy",
+ "config-db-wiki-help": "Wprowadź nazwę użytkownika i hasło, które będą używane do połączenia z bazą danych podczas normalnej pracy wiki.\nJeśli konto nie istnieje, a konto instalacji ma wystarczające uprawnienia, to zostanie utworzone konto użytkownika z minimalnymi uprawnieniami wymaganymi do działania wiki.",
+ "config-db-prefix": "Przedrostek tabel bazy danych:",
+ "config-db-prefix-help": "Jeśli zachodzi potrzeba współdzielenia jednej bazy danych między wieloma wiki, lub między MediaWiki i inną aplikacją sieciową, można dodać przedrostek do wszystkich nazw tabel w celu uniknięcia konfliktów.\nNie należy używać spacji.\n\nTo pole zwykle pozostawiane jest puste.",
+ "config-db-charset": "Zestaw znaków bazy danych",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binarny",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 kompatybilny wstecz UTF-8",
+ "config-charset-help": "'''Ostrzeżenie:''' W przypadku użycia opcji '''UTF-8 zgodnego ze starszymi wersjami''' podczas korzystania z wersji MySQL nowszych niż 4.1 kopie zapasowe wykonane przy użyciu programu <code>mysqldump</code> będą bezużyteczne - wszystkie znaki inne niż ASCII zostaną zapisane nieprawidłowo.\n\nW '''trybie binarnym''', MediaWiki zapisuje tekst UTF-8 do bazy danych w polach binarnych.\nTo jest bardziej wydajne niż tryb UTF-8 w MySQL i pozwala na używanie pełnego zakresu znaków Unicode.\nW ''' trybie UTF-8''', MySQL będzie wiedzieć, w jakim zestawie znaków są dane i umożliwia ich prezentowanie i odpowiednią konwersję, ale nie pozwoli Ci przechowywać znaków spoza [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes podstawowej płaszczyzny wielojęzyczności].",
+ "config-mysql-old": "Wymagany jest MySQL $1 lub nowszy; korzystasz z $2.",
+ "config-db-port": "Port bazy danych:",
+ "config-db-schema": "Nazwa schematu bazy danych, z którego ma korzystać MediaWiki:",
+ "config-db-schema-help": "Zaproponowana nazwa schematu jest odpowiednia dla większości sytuacji i przeważnie nie trzeba jej zmieniać.",
+ "config-pg-test-error": "Nie można połączyć się z bazą danych''' $1 ''': $2",
+ "config-sqlite-dir": "Katalog danych SQLite:",
+ "config-sqlite-dir-help": "SQLite przechowuje wszystkie dane w pojedynczym pliku.\n\nWskazany katalog musi być dostępny do zapisu przez webserver podczas instalacji.\n\nPowinien '''nie''' być dostępny za z sieci web, dlatego nie umieszczamy ich tam, gdzie znajdują się pliki PHP.\n\nInstalator zapisze plik <code>.htaccess</code> obokniego, ale jeśli to zawiedzie, ktoś może uzyskać dostęp do nieprzetworzonej bazy danych.\nZawiera ona nieopracowane dane użytkownika (adresy e-mail, zahaszowane hasła) jak również usunięte wersje oraz inne dane o ograniczonym dostępie na wiki.\n\nWarto rozważyć umieszczenie w bazie danych zupełnie gdzie indziej, na przykład w <code>/var/lib/mediawiki/yourwiki</code> .",
+ "config-oracle-def-ts": "Domyślna przestrzeń tabel:",
+ "config-oracle-temp-ts": "Przestrzeń tabel tymczasowych:",
+ "config-type-mysql": "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-mysql-url}} MySQL] jest bazą danych, na której rozwijane jest oprogramowanie MediaWiki. MediaWiki działa również z [{{int:version-db-mariadb-url}} MariaDB] i [{{int:version-db-percona-url}} Percona Server], które są zgodne z MySQL. ([http://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 systemem baz danych, często stosowanym zamiast MySQL. Z powodu możliwości wystąpienia drobnych błędów, nie jest zalecana do wymagających wdrożeń. ([http://www.php.net/manual/en/pgsql.installation.php Zobacz, jak skompilować PHP ze wsparciem dla PostgreSQL])",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] jest niewielkim systemem bazy danych, z którym MediaWiki bardzo dobrze współpracuje. ([http://www.php.net/manual/en/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. ([http://www.php.net/manual/en/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. ([http://www.php.net/manual/pl/sqlsrv.installation.php Jak skompilować PHP ze wsparciem dla SQLSRV])",
+ "config-header-mysql": "Ustawienia MySQL",
+ "config-header-postgres": "Ustawienia PostgreSQL",
+ "config-header-sqlite": "Ustawienia SQLite",
+ "config-header-oracle": "Ustawienia Oracle",
+ "config-header-mssql": "Ustawienia Microsoft SQL Server",
+ "config-invalid-db-type": "Nieprawidłowy typ bazy danych",
+ "config-missing-db-name": "Należy wpisać wartość w polu „{{int:config-db-name}}”.",
+ "config-missing-db-host": "Należy wpisać wartość w polu „{{int:config-db-host}}”.",
+ "config-missing-db-server-oracle": "Należy wpisać wartość w polu „{{int:config-db-host-oracle}}”.",
+ "config-invalid-db-server-oracle": "Nieprawidłowa nazwa instancji bazy danych (TNS) „$1”.\nUżyj \"TNS Name\" lub \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods])",
+ "config-invalid-db-name": "Nieprawidłowa nazwa bazy danych „$1”.\nUżywaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) lub znaku odejmowania (-).",
+ "config-invalid-db-prefix": "Nieprawidłowy prefiks bazy danych „$1”.\nUżywaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) lub znaku odejmowania (-).",
+ "config-connection-error": "$1.\n\nSprawdź adres serwera, nazwę użytkownika i hasło, a następnie spróbuj ponownie.",
+ "config-invalid-schema": "Nieprawidłowa nazwa schematu dla MediaWiki „$1”.\nNazwa może zawierać wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9) i podkreślenia (_).",
+ "config-db-sys-create-oracle": "Instalator może wykorzystać wyłącznie konto SYSDBA do tworzenia nowych kont użytkowników.",
+ "config-db-sys-user-exists-oracle": "Konto użytkownika „$1“ już istnieje. SYSDBA można użyć tylko do utworzenia nowego konta!",
+ "config-postgres-old": "Korzystasz z wersji $2 oprogramowania PostgreSQL, a potrzebna jest wersja co najmniej $1.",
+ "config-mssql-old": "Wymagany jest Microsoft SQL Server w wersji $1 lub nowszej. Masz zainstalowaną wersję $2.",
+ "config-sqlite-name-help": "Wybierz nazwę, która będzie identyfikować Twoją wiki.\nNie wolno używać spacji ani myślników.\nZostanie ona użyta jako nazwa pliku danych SQLite.",
+ "config-sqlite-parent-unwritable-group": "Nie można utworzyć katalogu danych <code><nowiki>$1</nowiki></code> , ponieważ katalog nadrzędny <code><nowiki>$2</nowiki></code> nie jest dostępny do zapisu przez webserwer.\n\nInstalator nie może określić, jako kttóry użytkownik działa webserwer.\nZezwól by katalog <code><nowiki>$3</nowiki></code> był dostępny do zapisu przez niego, aby przejść dalej.\nW systemie Unix/Linux wykonaj:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Nie można utworzyć katalogu danych <code><nowiki>$1</nowiki></code> , ponieważ katalog nadrzędny <code><nowiki>$2</nowiki></code> nie jest dostępny do zapisu przez webserwer.\n\nInstalator nie może określić, jako kttóry użytkownik działa webserwer.\nZezwól by katalog <code><nowiki>$3</nowiki></code> był globalnie modyfikowalny przez niego (i innych!) aby przejść dalej.\nW systemie Unix/Linux wykonaj:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Błąd podczas tworzenia katalogu dla danych „$1”.\nSprawdź lokalizację i spróbuj ponownie.",
+ "config-sqlite-dir-unwritable": "Nie można zapisać do katalogu „$1”.\nZmień uprawnienia dostępu do katalogu tak, aby serwer WWW mógł pisać do niego, a następnie spróbuj ponownie.",
+ "config-sqlite-connection-error": "$1.\n\nSprawdź katalog danych oraz nazwę bazy danych, a następnie spróbuj ponownie.",
+ "config-sqlite-readonly": "Plik <code>$1</code> nie jest zapisywalny.",
+ "config-sqlite-cant-create-db": "Nie można utworzyć pliku bazy danych <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "Brak wsparcia FTS3 dla PHP. Tabele zostały cofnięte",
+ "config-can-upgrade": "W bazie danych są już tabele MediaWiki.\nAby uaktualnić je do MediaWiki $1, kliknij '''Dalej'''.",
+ "config-upgrade-done": "Uaktualnienie kompletne.\n\nMożna teraz [$1 rozpocząć korzystanie z wiki].\n\nJeśli chcesz ponownie wygenerować plik <code>LocalSettings.php</code>, kliknij przycisk poniżej.\nJest to <strong>niezalecane</strong>, chyba że występują problemy z twoją wiki.",
+ "config-upgrade-done-no-regenerate": "Aktualizacja zakończona.\n\nMożesz teraz [$1 zacząć korzystać ze swojej wiki].",
+ "config-regenerate": "Ponowne generowanie LocalSettings.php →",
+ "config-show-table-status": "Zapytanie „<code>SHOW TABLE STATUS</code>” nie powiodło się!",
+ "config-unknown-collation": "'''Uwaga''' – bazy danych używa nierozpoznanej metody porównywania.",
+ "config-db-web-account": "Konto bazy danych dla dostępu przez WWW",
+ "config-db-web-help": "Wybierz nazwę użytkownika i hasło, z których korzystać będzie serwer WWW do łączenia się z serwerem baz danych, podczas zwykłej pracy z wiki.",
+ "config-db-web-account-same": "Użyj tego samego konta, co dla instalacji",
+ "config-db-web-create": "Utwórz konto, jeśli jeszcze nie istnieje",
+ "config-db-web-no-create-privs": "Konto podane do wykonania instalacji nie ma wystarczających uprawnień, aby utworzyć nowe konto.\nKonto, które wskazałeś tutaj musi już istnieć.",
+ "config-mysql-engine": "Silnik przechowywania",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Ostrzeżenie''': wybrano MyISIAM jako silnik składowania danych MySQL, co nie jest zalecane do użytku w MediaWiki, ponieważ:\n * ledwo obsługuje współbieżnośći ze względu na blokowanie tabel\n * jest bardziej podatna na uszkodzenie niż inne silniki\n * kod źródłowy MediaWiki nie zawsze obsługuje MyISAM tak, jak powinien\n\nJeśli instalacja MySQL obsługuje InnoDB, jest wysoce zalecane, by to je wybrać.\nJeśli instalacja MySQL nie obsługuje InnoDB, być może nadszedł czas na jej uaktualnienie.",
+ "config-mysql-only-myisam-dep": "'''Ostrzeżenie:''' MyISAM jest jedynym dostępnym na tym komputerze mechanizmem składowania dla MySQL, który jednak nie jest zalecany do używania z MediaWiki, ponieważ:\n* słabo obsługuje współbieżność z powodu blokowania tabel\n* jest bardziej skłonny do uszkodzeń niż inne silniki\n* kod MediaWiki nie zawsze traktuje MyISAM jak powinien\n\nTwoja instalacja MySQL nie obsługuje InnoDB, być może jest to czas na aktualizację.",
+ "config-mysql-engine-help": "'''InnoDB''' jest prawie zawsze najlepszą opcją, ponieważ posiada dobrą obsługę współbieżności.\n\n'''MyISAM''' może być szybsze w instalacjach pojedynczego użytkownika lub tylko do odczytu.\nBazy danych MyISAM mają tendencję do ulegania uszkodzeniom częściej niż bazy InnoDB.",
+ "config-mysql-charset": "Zestaw znaków bazy danych:",
+ "config-mysql-binary": "binarny",
+ "config-mysql-utf8": "UTF‐8",
+ "config-mysql-charset-help": "W '''trybie binarnym''', MediaWiki zapisuje tekst UTF-8 do bazy danych w polach binarnych.\nJest on bardziej wydajny niż tryb UTF-8 w MySQL i pozwala na używanie znaków pełnego zakresu Unicode.\n\nW '''trybie UTF-8''', MySQL będzie znać zestaw znaków w jakim zakodowano dane, można też przedstawić i przekonwertuj je odpowiednio, ale nie pozwoli Ci przechowywać znaków spoza [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes podstawowej płaszczyzny wielojęzyczności].",
+ "config-mssql-auth": "Typ uwierzytelniania:",
+ "config-mssql-install-auth": "Wybierz typ uwierzytelniania, który będzie używany do łączenia się z bazą danych w trakcie procesu instalacji.\nJeśli wybierzesz „{{int:config-mssql-windowsauth}}”, będą wykorzystywane dane konta użytkownika, pod którym działa serwer www.",
+ "config-mssql-web-auth": "Wybierz typ uwierzytelniania, który będzie używany przez serwer www do łączenia się z bazą danych podczas normalnego funkcjonowania wiki.\nJeśli wybierzesz „{{int:config-mssql-windowsauth}}”, użyte zostaną dane konta użytkownika, pod którym działa serwer www.",
+ "config-mssql-sqlauth": "Uwierzytelnianie serwera SQL",
+ "config-mssql-windowsauth": "Autoryzacja Windows",
+ "config-site-name": "Nazwa wiki:",
+ "config-site-name-help": "Ten napis pojawi się w pasku tytułowym przeglądarki oraz w różnych innych miejscach.",
+ "config-site-name-blank": "Wprowadź nazwę witryny.",
+ "config-project-namespace": "Przestrzeń nazw projektu:",
+ "config-ns-generic": "Projekt",
+ "config-ns-site-name": "Taka sama jak nazwa wiki: $1",
+ "config-ns-other": "Inna (należy określić)",
+ "config-ns-other-default": "MojaWiki",
+ "config-project-namespace-help": "Według przykładu Wikipedii wiele wiki przechowuje swoje strony zasad oddzielnie od stron z zawartością, w \"'''przestrzeni nazw projektu'''\".\nWszystkie tytuły stron w tej przestrzeni nazw zaczynają się od pewnego przedrostka, który można tutaj określić.\nTradycyjnie ten przedrostek wywodzi się od nazwy wiki, ale nie może zawierać pewnych znaków przestankowych takich jak \"#\" lub \":\".",
+ "config-ns-invalid": "Podana przestrzeń nazw „<nowiki>$1</nowiki>” jest nieprawidłowa.\nPodaj inną przestrzeń nazw projektu.",
+ "config-ns-conflict": "Określona przestrzeń nazw \"<nowiki>$1</nowiki>\" powoduje konflikt z domyślną przestrzenią nazw MediaWiki.\nWskaż inną przestrzeń nazw projektu.",
+ "config-admin-box": "Konto administratora",
+ "config-admin-name": "Twoja nazwa użytkownika:",
+ "config-admin-password": "Hasło:",
+ "config-admin-password-confirm": "Hasło powtórnie:",
+ "config-admin-help": "Wprowadź preferowaną nazwę użytkownika, na przykład „Jan Kowalski”.\nTej nazwy będziesz używać do logowania się do wiki.",
+ "config-admin-name-blank": "Wpisz nazwę użytkownika, który będzie administratorem.",
+ "config-admin-name-invalid": "Podana nazwa użytkownika „<nowiki>$1</nowiki>” jest nieprawidłowa.\nPodaj inną nazwę.",
+ "config-admin-password-blank": "Wprowadź hasło dla konta administratora.",
+ "config-admin-password-mismatch": "Wprowadzone dwa hasła różnią się między sobą.",
+ "config-admin-email": "Adres e‐mail:",
+ "config-admin-email-help": "Wpisz adres e‐mail, aby mieć możliwość odbierania e‐maili od innych użytkowników wiki, zresetowania hasła oraz otrzymywania powiadomień o zmianach na stronach z listy obserwowanych. Możesz pozostawić to pole niewypełnione.",
+ "config-admin-error-user": "Błąd wewnętrzny podczas tworzenia konta administratora o nazwie „<nowiki>$1</nowiki>”.",
+ "config-admin-error-password": "Wewnętrzny błąd podczas ustawiania hasła dla administratora „<nowiki>$1</nowiki>”: <pre>$2</pre>",
+ "config-admin-error-bademail": "Wpisałeś nieprawidłowy adres e‐mail.",
+ "config-subscribe": "Zapisz się na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce listę pocztową z ogłoszeniami o nowych wersjach].",
+ "config-subscribe-help": "Jest to lista o małej liczbie wiadomości, wykorzystywana do przesyłania informacji o udostępnieniu nowej wersji oraz istotnych sprawach dotyczących bezpieczeństwa.\nPowinieneś zapisać się na tę listę i aktualizować zainstalowane oprogramowanie MediaWiki gdy pojawia się nowa wersja.",
+ "config-subscribe-noemail": "Próbowano subskrybować listę mailingową ogłoszeń wersji bez podania adresu e-mail.\nProszę podać adres e-mail, jeśli chcesz subskrybować listę wysyłkową.",
+ "config-almost-done": "To już prawie koniec!\nMożesz pominąć pozostałe czynności konfiguracyjne i zainstalować wiki.",
+ "config-optional-continue": "Zadaj mi więcej pytań.",
+ "config-optional-skip": "Jestem już znudzony, po prostu zainstaluj wiki.",
+ "config-profile": "Profil uprawnień użytkowników",
+ "config-profile-wiki": "Otwarte wiki",
+ "config-profile-no-anon": "Wymagane utworzenie konta",
+ "config-profile-fishbowl": "Wyłącznie zatwierdzeni edytorzy",
+ "config-profile-private": "Prywatna wiki",
+ "config-profile-help": "Strony typu wiki działają najlepiej, gdy pozwolisz je edytować tak wielu osobom, jak to możliwie.\nW MediaWiki, można łatwo sprawdzić ostatnie zmiany i wycofać szkody, które są spowodowane przez naiwnych lub złośliwych użytkowników.\n\nJednakże wielu uznało MediaWiki użytecznym w różnorodnych rolach, a czasami nie jest łatwo przekonać wszystkich do korzyści ze sposobu działania wiki. Masz więc wybór.\n\nUstawienie '''{{int:config-profile-wiki}}''' pozwala każdemu na edycję, nawet bez logowania się.\nWiki z '''{{int:config-profile-no-anon}}''' zawiera dodatkowe możliwości ale może powstrzymywać potencjalnych edytorów.\n\nScenariusz '''{{int:config-profile-fishbowl}}''' umożliwia zatwierdzonym użytkownikom edycję, ale wyświetlanie stron jest powszechnie dostępne, włącznie z historią.\nUstawienie '''{{int:config-profile-private}}'' ' pozwala na wyświetlanie stron tylko zatwierdzonym użytkownikom, ta sama grupa może je edytować.\n\nBardziej skomplikowane konfiguracje uprawnień użytkowników są dostępne po zakończeniu instalacji, zobacz [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights odpowiednią część podręcznika].",
+ "config-license": "Prawa autorskie i licencja:",
+ "config-license-none": "Brak stopki z licencją",
+ "config-license-cc-by-sa": "Creative Commons – za uznaniem autora, na tych samych zasadach",
+ "config-license-cc-by": "Creative Commons – za podaniem autora",
+ "config-license-cc-by-nc-sa": "Creative Commons – za uznaniem autora, bez użycia komercyjnego, na tych samych zasadach",
+ "config-license-cc-0": "Creative Commons Zero (domena publiczna)",
+ "config-license-gfdl": "GNU licencja wolnej dokumentacji 1.3 lub nowsza",
+ "config-license-pd": "Domena publiczna",
+ "config-license-cc-choose": "Wybierz własną licencję Creative Commons",
+ "config-license-help": "Wiele publicznych wiki umieszcza wszystkie dopisane treści na [http://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, [http://www.php.net/manual/en/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-email-usertalk-help": "Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronie dyskusji użytkownika, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.",
+ "config-email-watchlist": "Włącz powiadomienie o zmianach stron obserwowanych",
+ "config-email-watchlist-help": "Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronach obserwowanych, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.",
+ "config-email-auth": "Włącz uwierzytelnianie e‐mailem",
+ "config-email-auth-help": "Jeśli ta opcja jest włączona, użytkownicy będą musieli potwierdzić swoje adresy e-mail przy użyciu wysłanego do nich łącza, gdy będą je ustawiać lub zmieniać.\nTylko uwierzytelnione adresy e-mail mogą otrzymywać wiadomości od innych użytkowników lub mailowe powiadomienia o zmianach.\nUstawienie tej opcji jest'''zalecane''' na publicznych wiki ze względu na potencjalne nadużycia funkcji poczty e-mail.",
+ "config-email-sender": "Zwrotny adres e‐mail",
+ "config-email-sender-help": "Wprowadź adres e-mail używany jako adres zwrotny wiadomości wychodzących.\nTo tam będą wysyłane szturchnięcia.\nWiele serwerów poczty wymaga, by co najmniej część nazwy domeny była prawidłowa.",
+ "config-upload-settings": "Przesyłanie obrazków i plików",
+ "config-upload-enable": "Włącz przesyłanie plików na serwer",
+ "config-upload-help": "Przesyłanie plików potencjalnie wystawia serwer na zagrożenia.\nWięcej informacji na ten temat można znaleźć w [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sekcji zabezpieczeń] podręcznika.\n\nAby włączyć przesyłanie plików, zmień właściwości podkatalogu <code>images</code> katalogu głównego MediaWiki tak, aby serwer sieci web mógł zapisywać do niego.\nNastępnie włącz tę opcję.",
+ "config-upload-deleted": "Katalog dla usuniętych plików",
+ "config-upload-deleted-help": "Wybierz katalog, w którym będzie archiwum usuniętych plików.\nNajlepiej, aby nie był on dostępny z internetu.",
+ "config-logo": "Adres URL logo:",
+ "config-logo-help": "Domyślny motyw MediaWiki zawiera miejsce na logo wielkości 135 x 160 pikseli powyżej menu na pasku bocznym.\nPrześlij obrazek o odpowiednim rozmiarze, a następnie wpisz jego URL tutaj.\n\nMożesz użyć <code>$wgStylePath</code> lub <code>$wgScriptPath</code> jeżeli twoje logo jest relatywne do tych ścieżek.\n\nJeśli nie chcesz logo, pozostaw to pole puste.",
+ "config-instantcommons": "Włącz Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] jest funkcją, która pozwala wiki używać obrazów, dźwięków i innych mediów znalezionych na witrynie [//commons.wikimedia.org/ Wikimedia Commons].\nAby to zrobić, MediaWiki wymaga dostępu do internetu.\n\nAby uzyskać więcej informacji na temat tej funkcji, w tym instrukcje dotyczące sposobu ustawiania go na wiki innych niż Wikimedia Commons, sprawdź w [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos podręczniku].",
+ "config-cc-error": "Wybieranie licencji Creative Commons nie dało wyniku.\nWpisz nazwę licencji ręcznie.",
+ "config-cc-again": "Wybierz jeszcze raz...",
+ "config-cc-not-chosen": "Wybierz którą chcesz licencję Creative Commons i kliknij „Dalej”.",
+ "config-advanced-settings": "Konfiguracja zaawansowana",
+ "config-cache-options": "Ustawienia buforowania obiektów:",
+ "config-cache-help": "Buforowanie obiekto jest używane aby przyspieszyć MediaWiki przez trzymanie w pamięci podręcznej często używanych danych.\nŚrednie oraz duże witryny są wysoce zachęcane by je włączyć, a małe witryny także dostrzegą korzyści.",
+ "config-cache-none": "Brak buforowania (wszystkie funkcje będą działać, ale mogą wystąpić kłopoty z wydajnością na dużych witrynach wiki)",
+ "config-cache-accel": "Buforowania obiektów PHP (APC, XCache lub WinCache)",
+ "config-cache-memcached": "Użyj Memcached (wymaga dodatkowej instalacji i konfiguracji)",
+ "config-memcached-servers": "Serwery Memcached:",
+ "config-memcached-help": "Lista adresów IP do wykorzystania przez Memcached.\nAdresy powinny być umieszczane po jednym w linii i określać również wykorzystywany port. Na przykład:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Został wybrany Memcached jako typ pamięci podręcznej, ale nie określono żadnych serwerów.",
+ "config-memcache-badip": "Wprowadzono nieprawidłowy adres IP dla Memcached: $1.",
+ "config-memcache-noport": "Nie określono portu dla serwera Memcached: $1.\nJeśli nie znasz numeru portu, wartością domyślną jest 11211.",
+ "config-memcache-badport": "Numery portu Memcached powinny zawierać się pomiędzy $1 i $2.",
+ "config-extensions": "Rozszerzenia",
+ "config-extensions-help": "Rozszerzenia wyżej wymienione zostały wykryte w katalogu <code>./extensions</code>.\n\nMogą one wymagać dodatkowych czynności konfiguracyjnych, ale można je teraz włączyć",
+ "config-skins": "Skórki",
+ "config-skins-help": "Powyższe skórki zostały wykryte w twoim katalogi <code>./skins</code>. Należy włączyć co najmniej jedną i wybrać domyślną.",
+ "config-skins-use-as-default": "Użyj tej skórki jako domyślnej",
+ "config-skins-missing": "Nie znaleziono skórki; MediaWiki będzie używać rezerwowej skórki do czasu zainstalowania odpowiednich.",
+ "config-skins-must-enable-some": "Musisz wybrać co najmniej jedną skórkę, aby ją włączyć.",
+ "config-skins-must-enable-default": "Skórka wybrana jako domyślna musi być włączona.",
+ "config-install-alreadydone": "'''Uwaga''' – wydaje się, że MediaWiki jest już zainstalowane, a obecnie próbujesz zainstalować je ponownie.\nPrzejdź do następnej strony.",
+ "config-install-begin": "Po naciśnięciu \"{{int:config-continue}}\", rozpocznie się instalacja MediaWiki.\nJeśli nadal chcesz dokonać zmian, naciśnij \"{{int:config-back}}\".",
+ "config-install-step-done": "gotowe",
+ "config-install-step-failed": "nieudane",
+ "config-install-extensions": "Włącznie z rozszerzeniami",
+ "config-install-database": "Konfigurowanie bazy danych",
+ "config-install-schema": "Tworzenie schematu",
+ "config-install-pg-schema-not-exist": "Schemat PostgreSQL nie istnieje.",
+ "config-install-pg-schema-failed": "Utworzenie tabel nie powiodło się.\nUpewnij się, że użytkownik „$1” może zapisywać do schematu „$2”.",
+ "config-install-pg-commit": "Zatwierdzanie zmian",
+ "config-install-pg-plpgsql": "Sprawdzanie języka PL/pgSQL",
+ "config-pg-no-plpgsql": "Musisz zainstalować język PL/pgSQL w bazie danych $1",
+ "config-pg-no-create-privs": "Konto, które zostało określone dla instalacji nie ma wystarczających uprawnień, aby utworzyć konto.",
+ "config-pg-not-in-role": "Konto określone dla użytkownika sieci już istnieje.\nKonto określone dla instalacji nie ma uprawnień administratora ani nie jest przynależy do roli użytkownika sieci web, więc nie można utworzyć obiektów stanowiących własność użytkownika sieci.\n\nMediaWiki wymaga obecnie, by tabele były własnością konta zwykłego użytkownika. Podaj inną nazwę konta użytkownika, lub kliknij przycisk \"Wstecz\" i podaj nazwę konta użytkownika instalatora, które posiada odpowiednie uprawnienia.",
+ "config-install-user": "Tworzenie użytkownika bazy danych",
+ "config-install-user-alreadyexists": "Konto użytkownika „$1“ już istnieje",
+ "config-install-user-create-failed": "Tworzenie użytkownika \"$1\" nie powiodło się: $2",
+ "config-install-user-grant-failed": "Przyznanie uprawnień użytkownikowi „$1” nie powiodło się – $2",
+ "config-install-user-missing": "Nie istnieje konto użytkownika „$1“.",
+ "config-install-user-missing-create": "Określony użytkownik \"$1\" nie istnieje.\nKliknij poniższe pole wyboru „utwórz konto\" jeśli chcesz go utworzyć.",
+ "config-install-tables": "Tworzenie tabel",
+ "config-install-tables-exist": "'''Uwaga''' – wygląda na to, że tabele MediaWiki już istnieją.\nPomijam tworzenie tabel.",
+ "config-install-tables-failed": "'''Błąd''' – tworzenie tabeli nie powiodło się z powodu błędu – $1",
+ "config-install-interwiki": "Wypełnianie tabeli domyślnymi interwiki",
+ "config-install-interwiki-list": "Nie można odnaleźć pliku <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Uwaga''' – wygląda na to, że tabela interwiki ma już jakieś wpisy.\nTworzenie domyślnej listy pominięto.",
+ "config-install-stats": "Inicjowanie statystyki",
+ "config-install-keys": "Generowanie tajnych kluczy",
+ "config-insecure-keys": "'''Ostrzeżenie:''' {{PLURAL:$2|Klucz bezpieczeństwa|Klucze bezpieczeństwa|Klucze bezpieczeństwa}} ($1) utworzone podczas instalacji {{PLURAL:$2|utworzony podczas instalacji nie jest|utworzone podczas instalacji nie są|utworzone podczas instalacji nie są}} w pełni bezpieczne. Być może warto wygenerować {{PLURAL:$2|własny klucz|własne klucze|własne klucze}}.",
+ "config-install-updates": "Zapobieganie uruchamianiu niepotrzebnych aktualizacji",
+ "config-install-sysop": "Tworzenie konta administratora",
+ "config-install-subscribe-fail": "Nie można zapisać na listę „mediawiki-announce“ – $1",
+ "config-install-subscribe-notpossible": "cURL nie jest zainstalowany, więc <code>allow_url_fopen</code> nie jest dostępne.",
+ "config-install-mainpage": "Tworzenie strony głównej z domyślną zawartością",
+ "config-install-extension-tables": "Tworzenie tabel dla aktywnych rozszerzeń",
+ "config-install-mainpage-failed": "Nie udało się wstawić strony głównej: $1",
+ "config-install-done": "'''Gratulacje!'''\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'''Uwaga''': 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 '''[$2 wejść na wiki]'''.",
+ "config-download-localsettings": "Pobierz <code>LocalSettings.php</code>",
+ "config-help": "pomoc",
+ "config-help-tooltip": "kliknij, aby rozwinąć",
+ "config-nofile": "Nie udało się odnaleźć pliku \"$1\". Czy nie został usunięty?",
+ "config-extension-link": "Czy wiesz, że twoja wiki obsługuje [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions/pl rozszerzenia]?\n\nMożesz przejrzeć [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category rozszerzenia według kategorii] lub [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] aby zobaczyć pełną listę rozszerzeń.",
+ "mainpagetext": "'''Instalacja MediaWiki powiodła się.'''",
+ "mainpagedocfooter": "Zobacz [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] w celu uzyskania informacji o działaniu oprogramowania wiki.\n\n== Na początek ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/pl Lista ustawień konfiguracyjnych]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/pl MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Przetłumacz MediaWiki na swój język]"
+}
diff --git a/includes/installer/i18n/pms.json b/includes/installer/i18n/pms.json
new file mode 100644
index 00000000..671671bc
--- /dev/null
+++ b/includes/installer/i18n/pms.json
@@ -0,0 +1,301 @@
+{
+ "@metadata": {
+ "authors": [
+ "Borichèt",
+ "Dragonòt",
+ "Krinkle",
+ "아라",
+ "Amire80"
+ ]
+ },
+ "config-desc": "L'instalador për mediaWiki",
+ "config-title": "Anstalassion ëd MediaWiki $1",
+ "config-information": "Anformassion",
+ "config-localsettings-upgrade": "A l'é stàit trovà n'archivi <code>LocalSettings.php</code>.\nPër agiorné cost'anstalassion, ch'a anserissa ël valor ëd <code>$wgUpgradeKey</code> ant la casela sì-sota.\nA la trovrà an LocalSetting.php.",
+ "config-localsettings-cli-upgrade": "N'archivi <code>LocalSettings.php</code> a l'é stàit trovà.\nPër agiorné sta instalassion, për piasì fà anvece giré <code>update.php</code>",
+ "config-localsettings-key": "Ciav d'agiornament:",
+ "config-localsettings-badkey": "La ciav ch'it l'has dàit a l'é pa giusta.",
+ "config-upgrade-key-missing": "A l'é stàita trovà n'istalassion esistenta ëd MediaWiki.\nPër agiorné soa istalassion, për piasì ch'a buta la linia sì-sota al fond ëd sò <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "L'esistent <code>LocalSettings.php</code> a smija esse ancomplet.\nLa variàbil $1 a l'é nen ampostà.\nPër piasì, ch'a modìfica <code>LocalSettings.php</code> ëd fasson che costa variàbil a sia ampostà, e ch'a sgnaca «{{int:Config-continue}}».",
+ "config-localsettings-connection-error": "A l'é ancapitaje n'eror an colegand-se a la base ëd dàit an dovrand j'ampostassion specificà an <code>LocalSettings.php</code> o <code>AdminSettings.php</code>. Për piasì, ch'a coregia cost'ampostassion e ch'a preuva torna.\n\n$1",
+ "config-session-error": "Eror an fasend parte la session: $1",
+ "config-session-expired": "Ij sò dat ëd session a smijo scadù.\nLe session a son configurà për na durà ëd $1.\nA peul aumenté sòn an ampostand <code>session.gc_maxlifetime</code> an php.ini.\nCh'a anandia torna ël process d'instalassion.",
+ "config-no-session": "Ij sò dat ëd session a son përdù!\nCh'a contròla sò php.ini e ch'as sigura che <code>session.save_path</code> a sia ampostà ant ël dossié giust.",
+ "config-your-language": "Toa lenga:",
+ "config-your-language-help": "Selessioné na lenga da dovré durant ël process d'instalassion.",
+ "config-wiki-language": "Lenga dla Wiki:",
+ "config-wiki-language-help": "Selession-a la lenga dont la wiki a sarà prevalentement scrivùa.",
+ "config-back": "← André",
+ "config-continue": "Continua →",
+ "config-page-language": "Lenga",
+ "config-page-welcome": "Bin ëvnù a MediaWiki!",
+ "config-page-dbconnect": "Coleghesse a la base ëd dàit",
+ "config-page-upgrade": "Agiorné l'instalassion esistenta",
+ "config-page-dbsettings": "Ampostassion dla base ëd dàit",
+ "config-page-name": "Nòm",
+ "config-page-options": "Opsion",
+ "config-page-install": "Instala",
+ "config-page-complete": "Completa!",
+ "config-page-restart": "Fé torna parte l'instalassion",
+ "config-page-readme": "Lesme",
+ "config-page-releasenotes": "Nòte ëd publicassion",
+ "config-page-copying": "Copié",
+ "config-page-upgradedoc": "Agiorné",
+ "config-page-existingwiki": "Wiki esistenta",
+ "config-help-restart": "Veul-lo scancelé tùit ij dat salvà ch'a l'ha anserì e anandié torna ël process d'instalassion?",
+ "config-restart": "É!, felo torna parte",
+ "config-welcome": "=== Contròj d'ambient ===\nDle verìfiche ëd base a son fàite për vëdde se st'ambient a va bin për l'instalassion ëd MediaWiki.\nS'a l'ha da manca d'agiut durant l'anstalassion, a dovrìa fornì j'arzultà dë sti contròj.",
+ "config-copyright": "=== Drit d'Autor e Condission ===\n\n$1\n\nCost-sì a l'é un programa lìber e a gràtis: a peul ridistribuilo e/o modifichelo sota le condission dla licensa pùblica general GNU com publicà da la Free Software Foundation; la version 2 dla Licensa, o (a toa sèrnìa) qualsëssìa version pi recenta.\n\nCost programa a l'é distribuì ant la speransa ch'a sia ùtil, ma '''sensa gnun-e garansìe'''; sensa gnanca la garansia implìssita ëd '''comersiabilità''' o '''d'esse adat a un but particolar'''.\n\nA dovrìa avèj arseivù <doclink href=Copying>na còpia ëd la licensa pùblica general GNU</doclink> ansema a sto programa; dësnò, ch'a scriva a la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA opura [http://www.gnu.org/copyleft/gpl.html ch'a la lesa an linia].",
+ "config-sidebar": "* [//www.mediawiki.org Intrada MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guida dl'Utent]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guida dl'Aministrator]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Soens an ciamo]\n----\n* <doclink href=Readme>Ch'am lesa</doclink>\n* <doclink href=ReleaseNotes>Nòte ëd publicassion</doclink>\n* <doclink href=Copying>Còpia</doclink>\n* <doclink href=UpgradeDoc>Agiornament</doclink>",
+ "config-env-good": "L'ambient a l'é stàit controlà.\nIt peule instalé MediaWiki.",
+ "config-env-bad": "L'ambient a l'é stàit controlà.\nIt peule pa instalé MediaWiki.",
+ "config-env-php": "PHP $1 a l'é instalà.",
+ "config-env-php-toolow": "PHP $1 a l'é instalà.\nAnt tùit ij cas, MediaWiki a ciama PHP $2 o pi neuv.",
+ "config-unicode-using-utf8": "As deuvra utf8_normalize.so ëd Brion Vibber për la normalisassion Unicode.",
+ "config-unicode-using-intl": "As deuvra l'[http://pecl.php.net/intl estension intl PECL] për la normalisassion Unicode.",
+ "config-unicode-pure-php-warning": "'''Avis:''' L'[http://pecl.php.net/intl estension intl PECL] a l'é pa disponìbil për gestì la normalisassion Unicode, da già che l'implementassion an PHP pur a faliss për lentëssa.\nS'a gestiss un sit a àut tràfich, a dovrìa lese cheicòs an sla [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalisassion Unicode].",
+ "config-unicode-update-warning": "'''Avis:''' La version instalà dlë spassiador ëd normalisassion Unicode a deuvra na version veja ëd la librarìa dël [http://site.icu-project.org/ proget ICU].\nA dovrìa fé n'[//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations agiornament] s'a l'é anteressà a dovré Unicode.",
+ "config-no-db": "Impossìbil trové un pilòta ëd base ëd dàit bon! A dev instalé un pilòta ëd base ëd dàit për PHP.\nLe sòrt ëd database ch'a ven-o a son apogià: $1.\n\nS'a l'é ansima a 'n servissi partagià, ch'a ciama a sò fornidor ëd servissi d'instalé un pilòta ëd base ëd dàit compatìbil.\nS'a l'é compilasse PHP chiel-midem, ch'a lo configura torna con un client ëd base ëd dàit abilità, për esempi an dovrand <code>./configure --with-mysql</code>.\nS'a l'ha instalà PHP dai pachèt Debian o Ubuntu, antlora a dev ëdcò istalé ël mòdul php5-mysql.",
+ "config-outdated-sqlite": "'''Avis''': chiel a l'ha SQLite $1, che a l'é pi vej che la version mìnima dont a-i é damanca $2. SQLite a sarà pa disponìbil.",
+ "config-no-fts3": "'''Avis''': SQLite a l'é compilà sensa ël mòdul [//sqlite.org/fts3.html FTS3], le funsion d'arserca a saran pa disponìbij su cost motor.",
+ "config-register-globals": "'''Avis: L'opsion <code>[http://php.net/register_globals register_globals]</code> ëd PHP a l'é abilità.'''\n'''Ch'a la disabìlita s'a peul.'''\nMediaWiki a marcërà, ma sò servent a l'é espòst a 'd possìbij vunerabilità ëd sicurëssa.",
+ "config-magic-quotes-runtime": "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] a l'é ativ!'''\nCosta opsion a danegia ij dat d'intrada an manera pa prevedìbil.\nA peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
+ "config-magic-quotes-sybase": "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] a l'é ativ!'''\nCosta opsion a danegia ij dat d'intrada an manera pa prevedìbil.\nA peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
+ "config-mbstring": "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] a l'é ativ!'''\nCosta opsion a càusa d'eror e a peul danegié ij dat d'intrada an manera pa prevedìbil.\nA peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
+ "config-safe-mode": "'''Avis:''' [http://www.php.net/features.safe-mode Safe mode] ëd PHP a l'é ativ.\nA peul causé ëd problema, dzortut s'as deuvro ël cariament d'archivi e ël manteniment ëd <code>math</code>.",
+ "config-xml-bad": "Mòdul XML ed PHP mancant.\nMediaWiki a l'ha da manca dle funsion an sto mòdul e a travajërà pa an costa configurassion.\nS'a fa giré mandrake, ch'a instala ël pachet php-xml.",
+ "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-ctype": "'''Fatal''': PHP a dev esse compilà con l'apògg për l'[http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] a l'é instalà",
+ "config-apc": "[http://www.php.net/apc APC] a l'é instalà",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] a l'é instalà",
+ "config-no-cache": "'''Avis:''' As treuva pa [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache]. Ël buté d'oget an memòria local a l'é pa abilità.",
+ "config-mod-security": "'''Avis''': Sò servent për l'aragnà a l'ha [http://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 [http://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-imagemagick": "Trovà ImageMagick: <code>$1</code>.\nLa miniaturisassion ëd figure a sarà abilità s'it abìlite le carie.",
+ "config-gd": "Trovà la librarìa gràfica antëgrà GD.\nLa miniaturisassion ëd figure a sarà abilità s'a abìlita ij cariament.",
+ "config-no-scaling": "As treuva pa la librarìa GD o ImageMagick.\nLa miniaturisassion ëd figure a sarà disabilità.",
+ "config-no-uri": "'''Eror:''' As peul pa determiné l'URI corenta.\nInstalassion abortìa.",
+ "config-no-cli-uri": "'''Avis''': pa gnun <code>--scriptpath</code> specificà, a sarà dovrà ël predefinì: <code>$1</code>.",
+ "config-using-server": "Utilisassion dël nòm ëd servent \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Utilisassion ëd l'anliura ëd servent «<nowiki>$1$2</nowiki>».",
+ "config-uploads-not-safe": "'''Avis:''' Sò dossié stàndard për carié <code>$1</code> a l'é vulneràbil a l'esecussion ëd qualsëssìa senari.\nBele che MediaWiki a contròla j'aspet ëd sicurëssa ëd tùit j'archivi carià, a l'é motobin arcomandà ëd [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security saré ës përtus ëd sicurëssa] prima d'abilité ij cariament.",
+ "config-no-cli-uploads-check": "'''Avis:''' Toa cartela predefinìa për j-amportassion (<code>$1</code>) a l'é nen controlà a propòsit ëd la vulnerabilità\nd'esecussion ëd senari arbitrari durant l'istalassion CLI.",
+ "config-brokenlibxml": "Sò sistema a l'ha na combinassion ëd version PHP e libxml2 che a l'ha dij bigat e a peul provoché la corussion ëd dat ëstërmà an MediaWiki e d'àutre aplicassion për l'aragnà.\nCh'a agiorna a PHP 5.2.9 o pi neuv e libxml2 2.7.3 o pi neuv ([//bugs.php.net/bug.php?id=45996 bug filed with PHP]).\nIstalassion abortìa.",
+ "config-suhosin-max-value-length": "Suhosin a l'é instalà e a lìmita la longheur dël paràmetr GET a $1 byte. Ël component ResourceLoader ëd MediaWiki a travajerà an rispetand ës lìmit, ma sòn a degraderà le prestassion. Se possìbil, a dovrìa amposté suhosin.get.max_value_lenght a 1024 o pi àut an <code>php.ini</code>, e amposté <code>$wgResourceLoaderMaxQueryLength</code> al midem valor an LocalSettings.php .",
+ "config-db-type": "Sòrt ëd base ëd dàit:",
+ "config-db-host": "Ospitant ëd la base ëd dàit:",
+ "config-db-host-help": "Se sò servent ëd base ëd dàit a l'é su un servent diferent, ch'a anserissa ambelessì ël nòm dl'ospitant o l'adrëssa IP.\n\nS'a deuvra n'ospitalità partagià, sò fornidor d'ospitalità a dovrìa deje ël nòm dl'ospitant giust ant soa documentassion.\n\nSe a anstala su un servent Windows e a deuvra MySQL, dovré «localhost» a podrìa funsioné nen com nòm dël servent. S'a marcia nen, ch'a preuva «127.0.0.1» com adrëssa IP local.\n\nS'a deuvra PostgresSQL, ch'a lassa sto camp bianch për coleghesse a travers un socket UNIX.",
+ "config-db-host-oracle": "TNS dla base ëd dàit:",
+ "config-db-host-oracle-help": "Anserì un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nòm ëd conession local] bon; n'archivi tnsnames.ora a dev esse visìbil da costa anstalassion..<br />S'a deuvra le librarìe cliente 10g o pi neuve a peul ëdcò dovré ël métod ëd nominassion [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identìfica sta wiki",
+ "config-db-name": "Nòm dla base ëd dàit:",
+ "config-db-name-help": "Ch'a serna un nòm ch'a identìfica soa wiki.\nA dovrìa conten-e gnun ëspassi.\n\nS'a deuvra n'ospitalità partagià, sò fornidor ëd l'ospitalità a-j darà un nòm ëd base ëd dàit specìfich da dovré o a lassrà ch'a lo crea via un panel ëd contròl.",
+ "config-db-name-oracle": "Schema dla base ëd dàit:",
+ "config-db-account-oracle-warn": "A-i é tre possibilità mantnùe për istalé Oracle tanme terminal ëd base ëd dàit:\n\nS'a veul creé un cont ëd base ëd dàit com part dël process d'istalassion, për piasì ch'a fornissa un cont con ël ròl SYSDBA com cont ëd base ëd dàit për l'istalassion e ch'a specìfica le credensiaj vorsùe për ël cont d'acess an sl'aragnà, dësnò a peul ëdcò creé ël cont d'acess an sl'aragnà manualment e mach fornì col cont (se a l'ha ij përmess necessari për creé j'oget dë schema) o fornì doi cont diferent, un con ij privilegi ëd creé e un limità për l'acess an sla Ragnà.\n\nIj senari për creé un cont con ij privilegi necessari a peul esse trovà ant la cartela «manutension/oracol/» ëd costa istalassion. Ch'a ten-a da ment che dovrand un cont limità a disabiliterà tute le funsion ëd manutension con ël cont predefinì.",
+ "config-db-install-account": "Cont d'utent për l'instalassion.",
+ "config-db-username": "Nòm d'utent dla base ëd dàit:",
+ "config-db-password": "Ciav dla base ëd dàit:",
+ "config-db-password-empty": "Për piasì, ch'a anserissa na ciav për ël neuv utent ëd base ëd dàit: $1.\nCon tut ch'a sia possìbil creé d'utent sensa ciav, a l'é pa na ròba sigura.",
+ "config-db-install-username": "Ch'a nserissa lë stranòm che a sarà dovrà për coleghesse a la base ëd dàit durant ël process d'istalassion.\nCost-sì a l'é nen lë stranòm dël cont MediaWiki; a l'é lë stranòm për soa base ëd dàit.",
+ "config-db-install-password": "Ch'a anserissa la ciav che a sarà dovrà për coleghesse a la base ëd dàit durant ël process d'istalassion.\nCosta-sì a l'é nen la ciav dël cont MediaWiki; a l'é la ciav për soa base ëd dàit.",
+ "config-db-install-help": "Ch'a anserissa lë stranòm d'utent e la ciav che a saran dovrà për coleghesse a la base ëd dàit durant ël process d'instalassion.",
+ "config-db-account-lock": "Dovré ij midem stranòm d'utent e ciav durant j'operassion normaj",
+ "config-db-wiki-account": "Cont d'utent për j'operassion normaj",
+ "config-db-wiki-help": "Ch'a anseriss lë stranòm d'utent e la ciav che a saran dovrà për coleghesse a la base ëd dàit durant j'operassion normaj dla wiki.\nS'ël cont a esist pa, e ël cont d'instalassion a l'ha ij privilegi ch'a-i van, sto cont utent a sarà creà con ij privilegi mìnin për fé marcé la wiki.",
+ "config-db-prefix": "Prefiss dle tàule dla base ëd dàit:",
+ "config-db-prefix-help": "S'a l'ha dabzògn ëd partagé na base ëd dàit an tra vàire wiki, o tra MediaWiki e n'àutra aplicassion dl'aragnà, a peul serne ëd gionté un prefiss a tùit ij nòm ëd le tàule për evité ëd conflit.\nCh'a deuvra pa dë spassi.\n\nCost camp a l'é lassà normalment veuid.",
+ "config-db-charset": "Ansema dij caràter dla base ëd dàit",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binari",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 compatìbil a l'andaré con UTF-8",
+ "config-charset-help": "'''Avis:''' S'a deuvra '''UTF-8 compatìbil a l'andaré''' su MySQL 4.1+, e peui a fa na còpia con <code>mysqldump</code>, a podrìa scancelé tùit ij caràter nen-ASCII, dësbland sensa speranse soe còpie!\n\nAn '''manera binaria''', MediaWiki a memorisa ël test UTF-8 an dij camp binari ant la base ëd dàit.\nSossì a l'é pi eficient che la manera UTF-8 ëd MySQL, e a përmët ëd dovré tut l'ansema ëd caràter Unicode.\nAn '''manera UTF-8''', MySQL a arconòss an che ansema ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassrà pa memorisé ij caràter dzora al [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghe ëd base].",
+ "config-mysql-old": "A-i é da manca ëd MySQL $1 o pi recent, chiel a l'ha $2.",
+ "config-db-port": "Porta dla base ëd dàit:",
+ "config-db-schema": "Schema për MediaWiki",
+ "config-db-schema-help": "Lë schema sì-sota a l'é ëd sòlit giust.\nCh'a lo cangia mach s'a sa ch'a n'ha da manca.",
+ "config-pg-test-error": "Impossìbil coleghesse a la base ëd dàit '''$1'''; $2",
+ "config-sqlite-dir": "Dossié dij dat SQLite:",
+ "config-sqlite-dir-help": "SQLite a memorisa tùit ij dat ant n'archivi ùnich.\n\nËl dossié che chiel a forniss a dev esse scrivìbil dal servent durant l'instalassion.\n\nA dovrìa '''pa''' esse acessìbil da l'aragnà, sossì a l'é për sòn ch'i l'oma pa butalo andova a-i son ij sò file PHP.\n\nL'instalador a scriverà n'archivi <code>.htaccess</code> ansema con chiel, ma se lòn a faliss quaidun a peul intré an soa base ëd dàit originaria.\nLòn a comprend ij dat brut ëd l'utent (adrëssa ëd pòsta eletrònica, ciav tërbola) e ëdcò le revision scancelà e d'àutri dat segret ëd la wiki.\n\nCh'a consìdera ëd buté la base ëd dàit tuta antrega da n'àutra part, për esempi an <code>/var/lib/mediawiki/yourwiki</code>.",
+ "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ù ([http://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 ([http://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",
+ "config-header-postgres": "Ampostassion PostgreSQL",
+ "config-header-sqlite": "Ampostassion SQLite",
+ "config-header-oracle": "Ampostassion Oracle",
+ "config-invalid-db-type": "Sòrt ëd ëd base ëd dàit pa bon-a",
+ "config-missing-db-name": "A dev buteje un valor për \"Nòm ëd la base ëd dàit\"",
+ "config-missing-db-host": "A dev buteje un valor për \"l'òspit ëd la base ëd dàit\"",
+ "config-missing-db-server-oracle": "A dev buteje un valor për \"TNS ëd la base ëd dat\"",
+ "config-invalid-db-server-oracle": "TNS ëd la base ëd dat pa bon \"$1\".\nDovré mach dle litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e pontin (.).",
+ "config-invalid-db-name": "Nòm ëd la base ëd dàit pa bon \"$1\".\nDovré mach litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e tratin (-).",
+ "config-invalid-db-prefix": "Prefiss dla base ëd dàit pa bon \"$1\".\nDovré mach litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e tratin (-).",
+ "config-connection-error": "$1.\n\nControla l'ospitant, lë stranòm d'utent e la ciav sì-sota e prové torna.",
+ "config-invalid-schema": "Schema pa bon për MediaWiki \"$1\".\nDovré mach litre ASCII (a-z, A-Z), nùmer (0-9) e sotlignadure (_).",
+ "config-db-sys-create-oracle": "L'istalador a arconòss mach ij cont SYSDBA durant la creassion d'un cont neuv.",
+ "config-db-sys-user-exists-oracle": "Ël cont utent \"$1\" a esist già. SYSDBA a peul mach esse dovrà për creé un cont neuv!",
+ "config-postgres-old": "A-i é da manca ëd PostgreSQL $1 o pi recent, chiel a l'ha $2.",
+ "config-sqlite-name-help": "Serne un nòm ch'a identìfica soa wiki.\nDovré nì dë spassi nì ëd tratin.\nSòn a sarà dovrà për ël nòm ëd l'archivi ëd dat SQLite.",
+ "config-sqlite-parent-unwritable-group": "As peul pa creesse ël dossié ëd dat <code><nowiki>$1</nowiki></code>, përchè ël dossié a mont <code><nowiki>$2</nowiki></code> a l'é pa scrivìbil dal servent.\n\nL'instalador a l'ha determinà sota che utent a gira sò servent.\nFé an manera che ël dossié <code><nowiki>$3</nowiki></code> a sia scrivìbil da chiel për continué.\nSu un sistema Unix/Linux buté:\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "As peul pa creesse ël dossié ëd dat <code><nowiki>$1</nowiki></code>, përchè ël dossié a mont <code><nowiki>$2</nowiki></code> a l'é pa scrivìbil dal servent.\n\nL'instalador a peul pa determiné l'utent sota ël qual a gira sò servent.\nFé an manera che ël dossié <code><nowiki>$3</nowiki></code> a sia scrivìbil globalment da chiel (e da d'àutri) për continué.\nSu un sistema Unix/Linux buté:\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Eror an creand ël dossié ëd dat \"$1\".\nCh'a contròla la locassion e ch'a preuva torna.",
+ "config-sqlite-dir-unwritable": "As peul pa scrivse an sël dossié \"$1\".\nModifiché ij sò përmess an manera che ël servent a peula scrivje ansima, e prové torna.",
+ "config-sqlite-connection-error": "$1.\n\nControlé ël dossié ëd dat e ël nòm ëd la base ëd dàit ambelessì-sota e prové torna.",
+ "config-sqlite-readonly": "L'archivi <code>$1</code> a l'é nen scrivìbil.",
+ "config-sqlite-cant-create-db": "As peul pa cresse l'archivi ëd base ëd dàit <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP a l'ha pa ël supòrt ëd FTS3, le tàule a son degradà",
+ "config-can-upgrade": "A-i é dle tàule MediaWiki an costa base ëd dàit.\nPër agiorneje a MediaWiki $1, ch'a sgnaca su '''Continué'''.",
+ "config-upgrade-done": "Agiornament completà.\n\nAdess a peule [$1 ancaminé a dovré soa wiki].\n\nS'a veul generé torna sò archivi <code>LocalSettings.php</code>, ch'a sgnaca ël boton sì-sota.\nSòn a l'è '''pa arcomandà''' gavà ch'a rancontra dij problema con soa wiki.",
+ "config-upgrade-done-no-regenerate": "Agiornament complet.\n\nIt peule adess [$1 ancaminé a dovré toa wiki].",
+ "config-regenerate": "Generé torna LocalSettings.php →",
+ "config-show-table-status": "Arcesta <code>SHOW TABLE STATUS</code> falìa!",
+ "config-unknown-collation": "'''Avis:''' La base ëd dàit a deuvra na classificassion pa arconossùa.",
+ "config-db-web-account": "Cont dla base ëd dàit për l'acess a l'aragnà",
+ "config-db-web-help": "Ch'a selession-a lë stranòm d'utent e la ciav che ël servent ëd l'aragnà a dovrërà për coleghesse al servent dle base ëd dàit, durant j'operassion ordinarie dla wiki.",
+ "config-db-web-account-same": "Ch'a deuvra ël midem cont com për l'istalassion",
+ "config-db-web-create": "Crea ël cont se a esist pa anco'",
+ "config-db-web-no-create-privs": "Ël cont ch'a l'ha specificà për l'instalassion a l'ha pa basta 'd privilegi për creé un cont.\nËl cont ch'a spessìfica ambelessì a dev già esiste.",
+ "config-mysql-engine": "Motor ëd memorisassion:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Avis''': A l'ha selessionà MyISAM com motor ëd memorisassion për MySQL, che a l'é pa arcomandà da dovré con MediaWiki, përchè:\n* a sopòrta a pen-a la contemporanità për via ëd saradure ëd tàula\n* a l'é pi soget a la corussion che j'àutri motor\n* ël còdes bas ëd MediaWiki pa sempe a gestiss MyISAM com a dovrìa\n\nSe soa istalassion MySQL a manten InnoDB, a l'é fortement arcomandà ch'a serna pitòst col-lì.\nSe soa istalassion MySQL a manten nen InnoDB, a peul esse ch'a sia ël moment ëd n'agiornament.",
+ "config-mysql-engine-help": "'''InnoDB''' a l'é scasi sempe la mej opsion, da già ch'a l'ha un bon manteniment dla concorensa.\n\n'''MyISAM''' a peul esse pi lest an instalassion për n'utent sol o mach an letura.\nLa base ëd dàit MyISAM a tira a corompse pi 'd soens che la base ëd dàit InnoDB.",
+ "config-mysql-charset": "Ansem ëd caràter dla base ëd dàit:",
+ "config-mysql-binary": "Binari",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "An '''manera binaria''', MediaWiki a memorisa ël test UTF-8 ant la base ëd dàit an camp binari.\nSòn a l'é pi eficient che la manera UTF-8 ëd MySQL, e a-j përmët ëd dovré l'ansema antregh ëd caràter Unicode.\n\nAn '''manera UTF-8''', MySQL a conossrà an che ansem ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassa pa memorisé ij caràter ëdzora al [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghìstich ëd base].",
+ "config-site-name": "Nòm ëd la wiki:",
+ "config-site-name-help": "Sòn a comparirà ant la bara dël tìtol dël navigador e an vàire d'àutri pòst.",
+ "config-site-name-blank": "Ch'a buta un nòm ëd sit.",
+ "config-project-namespace": "Spassi nominal dël proget:",
+ "config-ns-generic": "Proget",
+ "config-ns-site-name": "Midem com ël nom dla wiki: $1",
+ "config-ns-other": "Àutr (specìfica)",
+ "config-ns-other-default": "MyWiki",
+ "config-project-namespace-help": "Andasend daré a l'esempi ëd Wikipedia, vàire wiki a manten-o soe pàgine ëd regolament separà da soe pàgine ëd contnù, ant në \"'''spassi nominal ëd proget'''\".\nTùit ij tìtoj ëd pàgina ant cost ëspassi nominal a parto con un sert prefiss, che a peul specifiché ambelessì.\nTradissionalment, sto prefiss a l'é derivà dal nòm ëd la wiki, ma a peul pa conten-e caràter ëd pontegiatura coma \"#\" o \":\".",
+ "config-ns-invalid": "Lë spassi nominal specificà \"<nowiki>$1</nowiki>\" a l'é pa bon.\nSpecìfica në spassi nominal ëd proget diferent.",
+ "config-ns-conflict": "Lë spassi nominal specificà \"<nowiki>$1</nowiki>\" a và contra në spassi nominal predefinì ëd MediaWiki.\nSpecìfica në spassi nominal ëd proget diferent.",
+ "config-admin-box": "Cont ëd l'Aministrator",
+ "config-admin-name": "Tò nòm:",
+ "config-admin-password": "Ciav:",
+ "config-admin-password-confirm": "Buté torna la ciav:",
+ "config-admin-help": "Ch'a butà ambelessì tò stranòm d'utent preferì, për esempi \"Gioann Scriv\".\nCost-sì a l'é lë stranòm ch'a dovrërà për intré ant la wiki.",
+ "config-admin-name-blank": "Ch'a anserissa në stranòm d'aministrator.",
+ "config-admin-name-invalid": "Ël nòm utent specificà \"<nowiki>$1</nowiki>\" a l'é pa bon.\nSpecìfica un nòm utent diferent.",
+ "config-admin-password-blank": "Ch'a anserissa na ciav për ël cont d'aministrator.",
+ "config-admin-password-mismatch": "Le doe ciav che a l'ha scrivù a son diferente antra 'd lor.",
+ "config-admin-email": "Adrëssa ëd pòsta eletrònica:",
+ "config-admin-email-help": "Ch'a anserissa ambelessì n'adrëssa ëd pòsta eletrònica për përmëtt-je d'arsèive ëd mëssagi da d'àutri utent an sla wiki, riamposté soa ciav, e esse anformà dle modìfiche a le pàgine ch'a ten sot-euj. A peule lassé ës camp veuid.",
+ "config-admin-error-user": "Eror antern an creand n'aministrator con lë stranòm \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Eror antern an ampostand na ciav për l'admin \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "A l'ha butà n'adrëssa ëd pòsta eletrònica pa bon-a.",
+ "config-subscribe": "Ch'a sot-scriva la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista ëd discussion ëd j'anonsi ëd publicassion].",
+ "config-subscribe-help": "Costa a l'é na lista ëd discussion a bass tràfich dovrà për j'anonsi ëd publicassion, comprèis d'amportant anonsi ëd sicurëssa.\nA dovrìa sot-ëscrivla e agiorné soa instalassion mediaWiki quand che ëd version neuve a rivo.",
+ "config-subscribe-noemail": "A l'ha provà a abonesse a la lista ëd difusion dij comunicà sensa dé n'adrëssa ëd pòsta eletrònica.\nPër piasì, ch'a fornissa n'adrëssa ëd pòsta eletrònica s'a veul abonesse a la lista ëd pòsta.",
+ "config-almost-done": "A l'ha bele che fàit!\nA peul adess sauté la configurassion rimanenta e instalé dlongh la wiki.",
+ "config-optional-continue": "Ciameme d'àutre chestion.",
+ "config-optional-skip": "I son già anojà, instala mach la wiki.",
+ "config-profile": "Profil dij drit d'utent:",
+ "config-profile-wiki": "Wiki duverta",
+ "config-profile-no-anon": "A venta creé un cont",
+ "config-profile-fishbowl": "Mach editor autorisà",
+ "config-profile-private": "Wiki privà",
+ "config-profile-help": "Le wiki a marcio mej quand ch'a lassa che pì përsone possìbij a-j modìfico.\nAn MediaWiki, a l'é bel fé revisioné j'ùltime modìfiche, e buté andré qualsëssìa dann che a sia fàit da dj'utent noviss o malissios.\n\nAn tùit ij cas, an tanti a l'han trovà che MediaWiki a sia ùtil ant na gran varietà ëd manere, e dle vire a l'é pa bel fé convince cheidun dij vantagi dla wiki.\nParèj a l'ha doe possibilità.\n\nËl model '''{{int:config-profile-wiki}}''' a përmët a chicassìa ëd modifiché, bele sensa intré ant ël sistema.\nNa wiki con '''{{int:config-profile-no-anon}}''' a dà pì 'd contròl, ma a peul slontané dij contributor ocasionaj.\n\nËl senari '''{{int:config-profile-fishbowl}}''' a përmët a j'utent aprovà ëd modifiché, ma ël pùblich a peul vëdde le pàgine, comprèisa la stòria.\nUn '''{{int:config-profile-private}}''' a përmët mach a j'utent aprovà ëd vëdde le pàgine, con la midema partìa ch'a peul modifiché.\n\nConfigurassion ëd drit d'utent pi complicà a son disponìbij apress l'instalassion, vëdde la [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights pàgina a pòsta dël manual].",
+ "config-license": "Drit d'autor e licensa",
+ "config-license-none": "Gnun-a licensa an nòta an bass",
+ "config-license-cc-by-sa": "Creative Commons atribussion an part uguaj",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons atribussion nen comersial an part uguaj",
+ "config-license-cc-0": "Creative Commons Zero (domini pùblich)",
+ "config-license-gfdl": "Licensa GNU Free Documentation 1.3 o pi neuva",
+ "config-license-pd": "Domini Pùblich",
+ "config-license-cc-choose": "Selessioné na licensa Creative Commons përsonalisà",
+ "config-license-help": "Vàire wiki pùbliche a buto tute le contribussion sota na [http://freedomdefined.org/Definition licensa lìbera]. Sòn a giuta a creé un sens d'apartenensa a la comunità e a ancoragia ëd contribussion ëd longa durà.\nA l'é generalment nen necessari për na wiki privà o d'asienda.\n\nS'a veul podèj dovré dij test da Wikipedia, e a veul che Wikipedia a aceta dij test copià da soa wiki, a dovrìa serne '''Creative Commons Attribution Share Alike'''.\n\nWikipedia prima a dovrava la GNU Free Documentation License.\nLa GDFL a l'é anco' na licensa bon-a, ma a l'é malfé da capila.\nA l'é ëdcò mal fé riutilisé dël contnù licensià sota la GDFL.",
+ "config-email-settings": "Ampostassion ëd pòsta eletrònica",
+ "config-enable-email": "Abilité ij mëssagi ëd pòsta eletrònica an surtìa",
+ "config-enable-email-help": "S'a veul che la pòsta eletrònica a marcia, j'[http://www.php.net/manual/en/mail.configuration.php ampostassion ëd pòsta eletrònica PHP] a devo esse configurà për da bin.\nS'a veul pa 'd funsion ëd pòsta eletrònica, a dev disabiliteje ambelessì.",
+ "config-email-user": "Abilité ij mëssagi ëd pòsta eletrònica da utent a utent",
+ "config-email-user-help": "A përmët a tùit j'utent ëd mandesse ëd mëssagi ëd pòsta eletrònica se lor a l'han abilità sòn an soe preferense.",
+ "config-email-usertalk": "Abilité notìfica dle pàgine ëd discussion dj'utent",
+ "config-email-usertalk-help": "A përmët a j'utent d'arsèive na notìfica dle modìfiche dle pàgine ëd discussion d'utent, s'a l'han abilitalo ant soe preferense.",
+ "config-email-watchlist": "Abilité la notìfica ëd lòn ch'as ten sot euj",
+ "config-email-watchlist-help": "A përmët a j'utent d'arsèive dle notificassion a propòsit dle pàgine ch'a ten-o sot euj s'a l'han abilitalo ant soe preferense.",
+ "config-email-auth": "Abilité l'autenticassion për pòsta eletrònica",
+ "config-email-auth-help": "Se st'opsion a l'é abilità, j'utent a devo confirmé soe adrësse ëd pòsta eletrònica an dovrand un colegament mandà a lor quand ch'a l'han ampostala o cambiala.\nMach j'adrësse ëd pòsta eletrònica autenticà a peulo arsèive ëd mëssagi da j'àutri utent o cangé adrëssa ëd notìfica.\nAmposté st'opsion a l'é '''arcomandà''' për le wiki pùbliche a càusa ëd possìbij abus ëd le funsion ëd pòsta eletrònica.",
+ "config-email-sender": "Adrëssa ëd pòsta eletrònica ëd ritorn:",
+ "config-email-sender-help": "Ch'a anserissa l'adrëssa ëd pòsta eletrònica da dovré com adrëssa d'artorn dij mëssagi an surtìa.\nAmbelessì a l'é andova j'arspòste a saran mandà.\nMotobin ëd servent ëd pòsta a ciamo che almanch la part dël nòm ëd domini a sia bon-a.",
+ "config-upload-settings": "Cariament ëd figure e archivi",
+ "config-upload-enable": "Abilité ël cariament d'archivi",
+ "config-upload-help": "Carié d'archivi potensialment a espon sò servent a d'arzigh ëd sicurëssa.\nPer pi d'anformassion, ch'a lesa la [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security session ëd sicurëssa] d'ës manual.\n\nPër abilité ël cariament d'archivi, ch'a modìfica la manera dël sot-dossié dle <code>figure</code> sota al dossié rèis ëd MediaWiki an manera che ël servent dl'aragnà a peussa scrivlo.\nPeui ch'a abìlita costa opsion.",
+ "config-upload-deleted": "Dossié për j'archivi scancelà:",
+ "config-upload-deleted-help": "ch'a serna un dossié andova goerné j'archivi scancelà.\nIdealment, sòn a dovrìa pa esse acessìbil an sl'aragnà.",
+ "config-logo": "Anliura dla marca:",
+ "config-logo-help": "La pel dë stàndard ëd MediaWiki a comprend lë spassi për na marca ëd 135x160 pontin dzora la lista dla bara lateral.\nCh'a dëscaria na figura ëd la dimension aproprià, e ch'a anserissa l'anliura ambelessì.\n\nS'a veul gnun-e marche, ch'a lassa ës camp bianch.",
+ "config-instantcommons": "Abìlita Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] a l'é na funsion ch'a përmët a le wiki ëd dovré dle figure, dij son e d'àutri mojen trovà an sël sit [//commons.wikimedia.org/ Wikimedia Commons].\nPër dovré sossì, MediaWiki a l'ha da manca dl'acess a la ragnà.\n\nPër pi d'anformassion su sta funsion, comprèise j'istrussion ëd com ampostela për wiki diferente da Wikimedia Commons, ch'a consulta [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos ël manual].",
+ "config-cc-error": "La selession ëd la licensa Creative Commons a l'ha dàit gnun arzultà.\nCh'a anserissa ël nòm dla licensa a man.",
+ "config-cc-again": "Torna cheuje...",
+ "config-cc-not-chosen": "Sern che licensa Creative Commons it veule e sgnaca \"anans\".",
+ "config-advanced-settings": "Configurassion avansà",
+ "config-cache-options": "Ampostassion për la memorisassion local d'oget:",
+ "config-cache-help": "La memorisassion loca d'oget a l'é dovrà për amelioré l'andi ëd MediaWiki an butant an local dij dat dovrà 'd soens.\nIj sit da mesan a gròss a son motobin ancoragià a abilité sòn, e ij sit cit a l'avran ëdcò dij benefissi.",
+ "config-cache-none": "Gnun-a memorisassion local (gnun-a funsionalità gavà, ma l'andi a peul esse anfluensà an sij sit ëd wiki gròsse)",
+ "config-cache-accel": "Memorisassion local d'oget PHP (APC, XCache o WinCache)",
+ "config-cache-memcached": "Dovré Memcached (a ciama n'ampostassion e na configurassion adissionaj)",
+ "config-memcached-servers": "Servent Memcached:",
+ "config-memcached-help": "Lista d'adrësse IP da dovré për Memcached.\nA dovrìa specifichene un-a për linia e specifiché la pòrta da dovré. Për esempi:\n127.0.0.1:11211\n192.168.1.25:11211",
+ "config-memcache-needservers": "A l'ha selessionà Memcached com soa sòrt ëd memorisassion local ma a l'ha specificà gnun servent.",
+ "config-memcache-badip": "It l'ha anserì n'adrëssa IP pa bon-a për Memcached: $1.",
+ "config-memcache-noport": "A l'ha pa specificà na pòrta da dovré për ël servent Memcached: $1.\nS'a conòsse nen la pòrta, cola predefinìa a l'é 11211.",
+ "config-memcache-badport": "Ij nùmer ëd pòrta ëd Memcached a dovrìo esse tra $1 e $2.",
+ "config-extensions": "Estension",
+ "config-extensions-help": "J'estension listà dì-sota a son ëstàite trovà ant sò dossié <code>./extensions</code>.\n\nA peulo avèj da manca ëd configurassion adissionaj, ma a peul abiliteje adess",
+ "config-install-alreadydone": "'''Avis''' A smija ch'a l'abie già instalà MediaWiki e ch'a preuva a instalelo torna.\nPër piasì, ch'a vada a la pàgina ch'a-i ven.",
+ "config-install-begin": "An sgnacand su «{{int:config-continue}}», a anandiërà l'istalassion ëd MediaWiki.\nS'a veul anco' fé dle modìfiche, ch'a sgnaca su «{{int:config-back}}».",
+ "config-install-step-done": "fàit",
+ "config-install-step-failed": "falì",
+ "config-install-extensions": "Comprende j'estension",
+ "config-install-database": "Creassion ëd la base ëd dàit",
+ "config-install-schema": "Creassion dë schema",
+ "config-install-pg-schema-not-exist": "Lë schema postgreSQL a esist pa.",
+ "config-install-pg-schema-failed": "Creassion dle tàule falìa.\nSigurte che l'utent \"$1\" a peussa scrive lë schema \"$2\".",
+ "config-install-pg-commit": "Salvé ij cambi.",
+ "config-install-pg-plpgsql": "Contròl dël langagi PL/pgSQL",
+ "config-pg-no-plpgsql": "A dev istalé ël langage PL/pgSQL ant la base ëd dàit $1",
+ "config-pg-no-create-privs": "Ël cont ch'a l'ha specificà për l'istalassion a l'ha pa basta 'd privilegi për creé un cont.",
+ "config-pg-not-in-role": "Ël cont ch'a l'ha specificà për l'utent ëd la ragnà a esist già.\nËl cont ch'a l'has specificà për l'istalassion a l'é pa un superutent e a l'é pa un mémber dla partìa dj'utent dla Ragnà, parèj a peul pa creé dj'oget ch'a apartenent a l'utent dla Ragnà.\n\nMediaWiki al moment a ciama che le tàule a sia possedùe da n'utent dla Ragnà. Për piasì, ch'a specìfica n'àutr nòm ëd cont dla Ragnà, o ch'a sgnaca ansima a \"andré\" e ch'a specìfica n'utent ch'a l'ha ij privilegi ch'a basto për l'anstalassion.",
+ "config-install-user": "Creassion ëd n'utent ëd la base ëd dàit",
+ "config-install-user-alreadyexists": "L'utent \"$1\" a esist già",
+ "config-install-user-create-failed": "Faliment ant la creassion ëd l'utent «$1»: $2",
+ "config-install-user-grant-failed": "Falì a dé ij përmess a l'utent \"$1\": $2",
+ "config-install-user-missing": "L'utent specificà \"$1\" a esist pa.",
+ "config-install-user-missing-create": "L'utent specificà «$1» a esist pa.\nPër piasì, ch'a selession-a la casela «cont da creé» sì-sota s'a veul creelo.",
+ "config-install-tables": "Creassion dle tàule",
+ "config-install-tables-exist": "'''Avis''': A smija che le tàule ëd mediaWiki a esisto già.\nSauté la creassion.",
+ "config-install-tables-failed": "'''Eror''': Creassion ëd le tàule falìa con l'eror sì-dapress: $1",
+ "config-install-interwiki": "Ampiniment dë stàndard ëd le tàule dj'anliure interwiki",
+ "config-install-interwiki-list": "As peul pa trovesse l'archivi <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Avis''': La tàula interwiki a smija ch'a l'abia già dj'element.\nPër stàndard, la lista a sarà sautà.",
+ "config-install-stats": "Inissialisassion dle statìstiche",
+ "config-install-keys": "Generassion ëd le ciav segrete",
+ "config-insecure-keys": "'''Avis:''' {{PLURAL:$2|Na ciav sigura|Dle ciav sigure}} ($1) generà durant l'istalassion {{PLURAL:$2|a l'é|a son}} pa completament sigure. Ch'a consìdera ëd modifiche{{PLURAL:$2|la|je}} manualment.",
+ "config-install-sysop": "Creassion dël cont ëd l'utent aministrator",
+ "config-install-subscribe-fail": "As peul pa sot-scrivse mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL a l'é pa istalà e <code>allow_url_fopen</code> a l'é pa disponìbil.",
+ "config-install-mainpage": "Creassion ëd la pàgina prinsipal con un contnù predefinì",
+ "config-install-extension-tables": "Creassion ëd tàule për j'estension abilità",
+ "config-install-mainpage-failed": "As peul pa inserisse la pàgina prinsipal: $1",
+ "config-install-done": "'''Congratulassion!'''\nA l'ha instalà për da bin mediaWiki.\n\nL'instalador a l'ha generà n'archivi <code>LocalSettings.php</code>.\nA conten tuta soa configurassion.\n\nA dovrà dëscarielo e butelo ant la bas ëd l'instalassion ëd soa wiki (ël midem dossié d'index.php). La dëscaria a dovrìa esse ancaminà automaticament.\n\nSe la dëscaria a l'é pa disponìbil, o s'a l'ha scancelala, a peul torna ancaminé la dëscaria an sgnacand an sla liura sì-sota:\n\n$3\n\n'''Nòta''': S'a lo fa nen adess, cost archivi ëd configurassion generà a sarà pa disponìbil për chiel pi tard s'a chita l'instalassion sensa dëscarielo.\n\nQuand che a l'é stàit fàit, a peul '''[$2 intré an soa wiki]'''.",
+ "config-download-localsettings": "Dëscarié <code>LocalSettings.php</code>",
+ "config-help": "agiut",
+ "config-nofile": "L'archivi «$1» as treuva nen. A l'é stàit ëscancelà?",
+ "mainpagetext": "'''MediaWiki a l'é staita anstalà a la përfession.'''",
+ "mainpagedocfooter": "Che a varda la [//meta.wikimedia.org/wiki/Help:Contents User's Guide] për avèj dj'anformassion ant sël coma dovré ël programa dla wiki.\n\n== Për anandiesse a travajé ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista dij paràmeter ëd configurassion]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki Chestion frequente]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista ëd discussion an sla distribussion ëd MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localisa MediaWiki për toa lenga]"
+}
diff --git a/includes/installer/i18n/pnt.json b/includes/installer/i18n/pnt.json
new file mode 100644
index 00000000..4c40116d
--- /dev/null
+++ b/includes/installer/i18n/pnt.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sinopeus"
+ ]
+ },
+ "mainpagetext": "'''To λογισμικόν MediaWiki εθέκεν.'''"
+}
diff --git a/includes/installer/i18n/prg.json b/includes/installer/i18n/prg.json
new file mode 100644
index 00000000..cd9a3f9d
--- /dev/null
+++ b/includes/installer/i18n/prg.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Nertiks"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki's instalaciōni izpalla.'''",
+ "mainpagedocfooter": "Wīdais [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] kāi gaūlai infōrmaciōnei ezze wiki prōgramijas tērpausnan.\n\n== En pagaūseņu ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/ps.json b/includes/installer/i18n/ps.json
new file mode 100644
index 00000000..23d30277
--- /dev/null
+++ b/includes/installer/i18n/ps.json
@@ -0,0 +1,65 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ahmed-Najib-Biabani-Ibrahimkhel"
+ ]
+ },
+ "config-information": "مالومات",
+ "config-localsettings-key": "کونجۍ نومهالول:",
+ "config-localsettings-badkey": "کومه کونجۍ مو چې ورکړه ناسمه ده.",
+ "config-your-language": "ستاسې ژبه:",
+ "config-wiki-language": "د ويکي ژبه:",
+ "config-back": "← پر شا تلل",
+ "config-continue": "پر مخ تلل →",
+ "config-page-language": "ژبه",
+ "config-page-welcome": "مېډياويکي ته ښه راغلاست!",
+ "config-page-dbconnect": "توکبنسټ سره اړيکه نيول",
+ "config-page-dbsettings": "د توکبنسټ امستنې",
+ "config-page-name": "نوم",
+ "config-page-options": "خوښنې",
+ "config-page-install": "لگول",
+ "config-page-complete": "بشپړ!",
+ "config-page-readme": "ما ولوله",
+ "config-page-copying": "لمېسنه",
+ "config-page-upgradedoc": "نومهالېدنه",
+ "config-page-existingwiki": "شته ويکي",
+ "config-restart": "هو، سر له نوي يې پيل کړه",
+ "config-env-php": "د $1 PHP نصب شو.",
+ "config-db-type": "د توکبنسټ ډول:",
+ "config-db-host": "د توکبنسټ کوربه:",
+ "config-db-host-oracle": "د توکبنسټ TNS:",
+ "config-db-wiki-settings": "دا ويکي پېژندل",
+ "config-db-name": "د توکبنسټ نوم:",
+ "config-db-username": "د توکبنسټ کارن-نوم:",
+ "config-db-password": "د توکبنسټ پټنوم:",
+ "config-header-mysql": "د MySQL امستنې",
+ "config-header-postgres": "د PostgreSQL امستنې",
+ "config-header-sqlite": "د SQLite امستنې",
+ "config-header-oracle": "د اورېکل امستنې",
+ "config-sqlite-readonly": "د <code>$1</code> دوتنه د ليکلو وړ نه ده.",
+ "config-sqlite-cant-create-db": "د توکبنسټ دوتنه <code>$1</code> جوړه نه شوه.",
+ "config-site-name": "د ويکي نوم:",
+ "config-site-name-blank": "د وېبځي نوم وليکۍ.",
+ "config-project-namespace": "د پروژې نوم-تشيال:",
+ "config-ns-generic": "پروژه",
+ "config-ns-other-default": "زما ويکي",
+ "config-admin-box": "د پازوال گڼون",
+ "config-admin-name": "ستاسې کارن نوم:",
+ "config-admin-password": "پټنوم:",
+ "config-admin-password-confirm": "پټنوم يو ځل بيا:",
+ "config-admin-email": "برېښليک پته:",
+ "config-optional-continue": "نورې پوښتنې راڅخه وپوښتئ.",
+ "config-profile": "د کارن رښتو پېژنليک:",
+ "config-profile-wiki": "پرانيستې ويکي",
+ "config-profile-private": "شخصي ويکي",
+ "config-license-pd": "ټولگړی شپول",
+ "config-email-settings": "د برېښليک امستنې",
+ "config-email-user": "کارن تر کارن برېښليک چارنول",
+ "config-install-step-done": "ترسره شو",
+ "config-install-user-alreadyexists": "د \"$1\" کارن له پخوا څخه شته",
+ "config-install-tables": "لښتيالونه جوړول",
+ "config-download-localsettings": "ښکته کول <code>LocalSettings.php</code>",
+ "config-help": "لارښود",
+ "mainpagetext": "'''MediaWiki په برياليتوب سره نصب شو.'''",
+ "mainpagedocfooter": "د ويکي ساوترې د کارولو د مالوماتو په اړه [//meta.wikimedia.org/wiki/Help:Contents د کارن لارښود] سره سلا وکړۍ.\n\n== پيلول ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings د امستنو د سازونې لړليک]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ د ميډياويکي ډېرځليزې پوښتنې]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce د مېډياويکي د برېښليکونو لړليک]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources خپلې ژبې لپاره MediaWiki ځايتابول]"
+}
diff --git a/includes/installer/i18n/pt-br.json b/includes/installer/i18n/pt-br.json
new file mode 100644
index 00000000..68a94e83
--- /dev/null
+++ b/includes/installer/i18n/pt-br.json
@@ -0,0 +1,244 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cainamarques",
+ "Giro720",
+ "Gustavo",
+ "Luckas",
+ "Marcionunes",
+ "555",
+ "Amgauna",
+ "Anaclaudiaml",
+ "Cybermandrake",
+ "Fabsouza1",
+ "Rodrigo codignoli",
+ "Tuliouel"
+ ]
+ },
+ "config-desc": "O instalador do MediaWiki",
+ "config-title": "Instalação do MediaWiki $1",
+ "config-information": "Informações",
+ "config-localsettings-upgrade": "Foi detectada a existência do arquivo <code>LocalSettings.php</code>.\nPara atualizar esta instalação, insira na caixa abaixo o valor de <code>$wgUpgradeKey</code>.\nEssa informação pode ser encontrada no arquivo <code>LocalSettings.php</code>",
+ "config-localsettings-cli-upgrade": "Foi detectada a existência do arquivo <code><code>LocalSettings.php</code></code>.\nAtualize esta instalação executando o arquivo <code>update.php</code>",
+ "config-localsettings-key": "Chave de atualização:",
+ "config-localsettings-badkey": "A chave fornecida está incorreta.",
+ "config-upgrade-key-missing": "Foi detectada uma instalação existente do MediaWiki.\nPara atualizar esta instalação, insira a seguinte linha ao final do seu <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "O arquivo <code>LocalSettings.php</code> parece incompleto.\nA variável $1 não está definida.\nPor favor, altere seu <code>LocalSettings.php</code> e defina esta variável e clique em \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Ocorreu um erro ao conectar ao banco de dados através das configurações presentes no arquivo <code>LocalSettings.php</code>. Por favor, corrija essas configurações e tente novamente.\n\n$1",
+ "config-session-error": "Erro ao iniciar a sessão: $1",
+ "config-session-expired": "Os dados da sua sessão parecem ter expirado.\nAs sessões estão configuradas para uma duração de $1.\nVocê pode aumentar esta duração configurando <code>session.gc_maxlifetime</code> no php.ini.\nReinicie o processo de instalação.",
+ "config-no-session": "Os dados da sua sessão foram perdidos!\nVerifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um diretório apropriado.",
+ "config-your-language": "Seu idioma:",
+ "config-your-language-help": "Selecione o idioma que será usado durante o processo de instalação.",
+ "config-wiki-language": "Idioma da wiki:",
+ "config-wiki-language-help": "Selecione o idioma no qual a wiki será predominantemente escrita.",
+ "config-back": "← Voltar",
+ "config-continue": "Continuar →",
+ "config-page-language": "Idioma",
+ "config-page-welcome": "Bem-vindo(a) ao MediaWiki!",
+ "config-page-dbconnect": "Conectar ao banco de dados",
+ "config-page-upgrade": "Atualizar a instalação existente",
+ "config-page-dbsettings": "Configurações do banco de dados",
+ "config-page-name": "Nome",
+ "config-page-options": "Opções",
+ "config-page-install": "Instalar",
+ "config-page-complete": "Concluído!",
+ "config-page-restart": "Reiniciar a instalação",
+ "config-page-readme": "Leia-me",
+ "config-page-releasenotes": "Notas de lançamento",
+ "config-page-copying": "Copiando",
+ "config-page-upgradedoc": "Atualizando",
+ "config-page-existingwiki": "Wiki existente",
+ "config-help-restart": "Deseja limpar todos os dados salvos que você introduziu e reiniciar o processo de instalação?",
+ "config-restart": "Sim, reiniciar",
+ "config-welcome": "=== Verificações de ambiente ===\nSerão realizadas verificações básicas para determinar se este ambiente é apropriado para a instalação do MediaWiki.\nLembre-se de incluir estas informações se for procurar por suporte para como concluir a instalação.",
+ "config-copyright": "=== Direitos autorais e Termos de uso ===\n\n$1\n\nEste programa é software livre; você pode redistribuí-lo e/ou modificá-lo nos termos da licença GNU General Public License tal como publicada pela Free Software Foundation; tanto a versão 2 da Licença, como (por opção sua) qualquer versão posterior.\n\nEste programa é distribuído na esperança de que seja útil, mas <strong>sem qualquer garantia</strong>; inclusive, sem a garantia implícita da <strong>possibilidade de ser comercializado</strong> ou de <strong>adequação para qualquer finalidade específica</strong>.\nConsulte a licença GNU General Public License para mais detalhes.\n\nEm conjunto com este programa você deve ter recebido <doclink href=Copying>uma cópia da licença GNU General Public License</doclink>; se não a recebeu, peça-a por escrito para Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA ou [http://www.gnu.org/copyleft/gpl.html leia-a na internet].",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki Página principal do MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Manual do usuário]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Manual do administrador]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Leia-me</doclink>\n* <doclink href=ReleaseNotes>Notas de lançamento</doclink>\n* <doclink href=Copying>Licença</doclink>\n* <doclink href=UpgradeDoc>Atualizando</doclink>",
+ "config-env-good": "O ambiente foi verificado.\nVocê pode instalar o MediaWiki.",
+ "config-env-bad": "O ambiente foi verificado.\nVocê não pode instalar o MediaWiki.",
+ "config-env-php": "O PHP $1 está instalado.",
+ "config-unicode-using-utf8": "Usando o utf8_normalize.so, de Brion Vibber, para a normalização Unicode.",
+ "config-unicode-using-intl": "Usando a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.",
+ "config-unicode-pure-php-warning": "<strong>Aviso</strong>: A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode, abortando e passando para a lenta implementação de PHP puro.\nSe o seu site tem um alto volume de tráfego, informe-se sobre a [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalização Unicode].",
+ "config-unicode-update-warning": "<strong>Aviso:</strong> A versão instalada do wrapper de normalização Unicode usa uma versão mais antiga da biblioteca do [//www.site.icu-project.org/projeto ICU].\nVocê deve [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations atualizar] se você tem quaisquer preocupações com o uso do Unicode.",
+ "config-no-db": "Não foi possível encontrar um driver de banco de dados adequado! É necessário instalar um driver de banco de dados para o PHP.\nSão suportados os seguintes tipos de bancos de dados: $1.\n\nSe você mesmo tiver compilado o PHP, reconfigure-o com um cliente de banco de dados ativado usando, por exemplo <code>./configure --with-mysqli</code>.\nSe você instalou o PHP a partir de um pacote do Debian ou do Ubuntu, então será também necessário instalar, por exemplo, o pacote <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "<strong>Aviso:</strong> você tem o SQLite versão $1, que é menor do que a versão mínima necessária $2. O SQLite não estará disponível.",
+ "config-no-fts3": "<strong>Aviso</strong> O SQLite foi compilado sem o [//sqlite.org/fts3.html módulo FTS3], as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
+ "config-magic-quotes-runtime": "<strong>Erro fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está ativada!</strong>\nEsta opção causa corrupção dos dados de entrada de forma imprevisível.\nVocê não pode instalar ou utilizar o MediaWiki a menos que esta opção seja desativada.",
+ "config-magic-quotes-sybase": "<strong>Erro fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está ativada!</strong>\nEsta opção corrompe os dados de entrada de forma imprevisível.\nVocê não pode instalar ou utilizar o MediaWiki a menos que esta opção seja desativada.",
+ "config-mbstring": "<strong>Erro fatal: A opção [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está ativada!</strong>\nEsta opção causa erros e pode corromper os dados de forma imprevisível.\nVocê não pode instalar ou utilizar o MediaWiki a menos que esta opção seja desativada.",
+ "config-safe-mode": "<strong>Aviso:</strong> O [http://www.php.net/features.safe-mode safe mode] do PHP está ativado.\nEste modo pode causar problemas, especialmente no upload de arquivos e no suporte a <code>math</code>.",
+ "config-xml-bad": "O módulo XML do PHP está ausente.\nO MediaWiki necessita de funções deste módulo e não funcionará com esta configuração.\nSe está utilizando o Mandrake, instale o pacote php-xml.",
+ "config-pcre-old": "<strong>Erro fatal:</strong> É necessário o PCRE $1 ou versão posterior.\nO binário do seu PHP foi vinculado com o PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Mais informações].",
+ "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-ctype": "<strong>Erro fatal:</strong> O PHP deve ser compilado com suporte para a [http://www.php.net/manual/en/ctype.installation.php extensão Ctype].",
+ "config-json": "<strong>Erro fatal:</strong> O PHP foi compilado sem suporte a JSON.\nVocê deve instalar a extensão PHP JSON ou a extensão [http://pecl.php.net/package/jsonc PECL jsonc] antes de instalar o MediaWiki.\n* A extensão JSON do PHP já está incluída no Red Hat Enterprise Linux (CentOS) 5 e 6, mas deve ser habilitado no <code>/etc/php.ini</code> ou no <code>/etc/php.d/json.ini</code>.\n* Algumas distribuições Linux lançadas após maio de 2013 omitem a extensão PHP, oferecendo em seu lugar a extensão PECL como parte do pacote <code>php5-json</code> ou do <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] está instalado",
+ "config-apc": "[http://www.php.net/apc APC] está instalado",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] está instalado",
+ "config-no-cache": "<strong>Aviso:</strong> Não foi possível encontrar o [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].\nO cache de objetos não está habilitado.",
+ "config-mod-security": "<strong>Aviso:</strong> Seu servidor web tem [http://modsecurity.org/ mod_security] habilitado. Se configurado incorretamente, pode causar problemas para o MediaWiki ou outro software que permite aos usuários postar conteúdo arbitrário.\nConsulte a [http://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": "O GNU diff3 não foi encontrado.",
+ "config-git": "Foi encontrado o software de controle de versão Git: <code>$1</code>.",
+ "config-git-bad": "Não foi encontrado o software de controle de versão Git.",
+ "config-imagemagick": "ImageMagick encontrado: <code>$1</code> .\nRedimensionamento de imagem será ativado se você permitir uploads.",
+ "config-gd": "Encontrada biblioteca gráfica GD embutida\nO redimensionamento de imagens será habilitado se você permitir uploads.",
+ "config-no-scaling": "Não foi possível encontrar biblioteca GD ou ImageMagick. \nO redimensionamento de imagens será desabilitado.",
+ "config-no-uri": "<strong>Erro:</strong> Não foi possível determinar a URI atual. A instalação foi abortada.",
+ "config-no-cli-uri": "<strong>Aviso:</strong> Nenhum <code>--scriptpath</code> foi especificado, usando o padrão: <code>$1</code>.",
+ "config-using-server": "Utilizando o nome do servidor \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Usando URL do servidor \"<nowiki>$1$2</nowiki>\".",
+ "config-db-type": "Tipo de base de dados:",
+ "config-db-host": "Servidor da base de dados:",
+ "config-db-host-help": "Se a base de dados do seu servidor está em um servidor diferente, digite o nome do hospedeiro ou o endereço IP aqui.\n\nSe você está utilizando um hospedeiro web compartilhado, o seu provedor de hospedagem deverá fornecer o nome do hospedeiro correto na sua documentação.\n\nSe você está instalando em um servidor Windows e usando o MySQL, usar \"localhost\" pode não funcionar para o nome de servidor. Se não funcionar, tente \"127.0.01\" para o endereço de IP local.\n\nSe você está usando PostgreSQl, deixe este campo em branco para se conectar através de um socket Unix.",
+ "config-db-host-oracle": "TNS da base de dados:",
+ "config-db-wiki-settings": "Identifique esta wiki",
+ "config-db-name": "Nome da base de dados:",
+ "config-db-name-help": "Escolha um nome que identifique a sua wiki.\nEle não deve conter espaços.\n\nSe você está utilizando um hospedeiro web compartilhado, o provedor de hospedagem lhe dará um nome especifico de base de dados para usar ou o deixará criar a partir do painel de controle.",
+ "config-db-name-oracle": "Esquema de base de dados:",
+ "config-db-install-account": "Conta de usuário para instalação",
+ "config-db-username": "Nome de usuário do banco de dados:",
+ "config-db-password": "Senha do banco de dados:",
+ "config-db-password-empty": "Por favor digite uma senha para o novo usuário do banco de dados: $1. Embora seja possível criar usuários sem senha, isto não é seguro.",
+ "config-db-install-username": "Digite o nome de usuário que será utilizado para conectar com o banco de dados durante o processo de instalação.\nEste não é a conta de usuário do MediaWiki; este é o nome de usuário para sua base de dados.",
+ "config-db-install-password": "Digite a senha que será utilizada para conectar com o banco de dados durante o processo de instalação.\nEsta não é a senha de usuário da conta do MediaWiki; esta será a senha para seu banco de dados.",
+ "config-db-install-help": "Digite o nome de usuário e a senha que serão utilizados para conectar com o banco de dados durante o processo de instalação.",
+ "config-db-wiki-account": "Conta de usuário para operação normal",
+ "config-db-wiki-help": "Digite o nome de usuário e senha que será usada para se conectar ao banco de dados durante a operação normal wiki.\nSe a conta não existir, e a conta de instalação tiver privilégios suficientes, a conta do usuário será criada com os privilégios mínimos necessários para o funcionamento do wiki.",
+ "config-db-prefix": "Prefixo da tabela de banco de dados:",
+ "config-db-prefix-help": "Se você precisar compartilhar a base de dados entre várias wikis, ou entre o MediaWiki e uma outra aplicação web, você pode deve escolher adicionar um prefixo ao nome de todas as tabelas para evitar conflitos.\nNão utilize espaços.\n\nEste campo é habitualmente deixado em branco.",
+ "config-db-charset": "Conjunto de caracteres do banco de dados",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 compatível com versões anteriores UTF-8",
+ "config-mysql-old": "MySQL $1 ou posterior é necessário. Você tem $2.",
+ "config-db-port": "Porta da base de dados:",
+ "config-db-schema": "Esquema para MediaWiki",
+ "config-db-schema-help": "Este esquema geralmente estará correto.\nSó o modifique se você tiver certeza que precisa.",
+ "config-pg-test-error": "Não foi possível se conectar com a base de dados <strong>$1</strong>: $2",
+ "config-sqlite-dir": "Diretório de dados do SQLite:",
+ "config-sqlite-dir-help": "O SQLite armazena todos os dados em um único arquivo.\n\nO diretório que você fornecer deve permitir a sua escrita pelo servidor web durante a instalação.\n\nO diretório <strong>não</strong> deve ser acessível pela web, por isso não estamos colocando onde estão os seus arquivos PHP.\n\nO instalador escreverá um arquivo <code>.htaccess</code>, mas se isso falhar alguém poderá ganhar acesso a toda sua base de dados.\nIsso inclui dados brutos dos usuários (endereços de email, senhas criptografadas) assim como todas revisões deletadas e outros dados restritos na wiki.\n\nConsidere colocar a base de dados em algum outro lugar, por exemplo <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Espaço de tabela padrão:",
+ "config-oracle-temp-ts": "Tablespace temporário:",
+ "config-type-mysql": "MySQL (ou compatível)",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] é um sistema de banco de dados leve que é muito bem suportado. ([http://www.php.net/manual/en/pdo.installation.php como compilar o PHP com suporte a SQLite], usa DOP)",
+ "config-header-mysql": "Configurações MySQL",
+ "config-header-postgres": "Configurações PostgreSQL",
+ "config-header-sqlite": "Configurações SQLite",
+ "config-header-oracle": "Configurações Oracle",
+ "config-header-mssql": "Configurações Microsoft SQL Server",
+ "config-invalid-db-type": "Tipo de base de dados inválido.",
+ "config-missing-db-name": "Você deve inserir um valor para \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Você deve inserir um valor para \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Você deve inserir um valor para \"{{int:config-db-host-oracle}}\".",
+ "config-connection-error": "$1\n\nVerifique o servidor, nome de usuário e senha e tente novamente.",
+ "config-db-sys-user-exists-oracle": "A conta de usuário $1 já existe. SYSDBA somente pode ser utilizado na criação de uma nova conta!",
+ "config-postgres-old": "PostgreSQL $1 ou posterior é necessário. Você tem $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 ou posterior é necessário.Você tem $2.",
+ "config-sqlite-name-help": "Escolha um nome que identifique a sua wiki.\nNão utilize espaços ou hifens.\nIsto será utilizado como nome do arquivo de dados do SQLite.",
+ "config-sqlite-mkdir-error": "Ocorreu um erro na criação do diretório de dados \"$1\".\nVerifique a localização e tente de novo.",
+ "config-sqlite-dir-unwritable": "Não foi possível escrever no diretório \"$1\".\nModifique as permissões de modo que o servidor web possa escrever no diretório e tente novamente.",
+ "config-sqlite-connection-error": "$1\n\nVerifique o diretório de dados e nome da base de dados abaixo e tente novamente.",
+ "config-sqlite-readonly": "Não é possível escrever no arquivo <code>$1</code>.",
+ "config-sqlite-cant-create-db": "Não foi possível criar o arquivo da base de dados <code>$1</code>.",
+ "config-regenerate": "Regenerar arquivo LocalSettings.php →",
+ "config-show-table-status": "Consulta <code>SHOW TABLE STATUS</code> falhou!",
+ "config-unknown-collation": "<strong>Aviso:</strong> O banco de dados está usando agrupamento não reconhecido.",
+ "config-db-web-account": "Conta da base de dados para acesso web",
+ "config-db-web-help": "Escolha um nome de usuário e uma senha que o servidor web irá utilizar para se conectar ao servidor da base de dados durante o funcionamento normal da wiki.",
+ "config-db-web-account-same": "Use a mesma conta usada na instalação",
+ "config-db-web-create": "Crie a conta se esta ainda não existir.",
+ "config-db-web-no-create-privs": "A conta que você especificou para a instalação não possui privilégios suficientes para criar uma conta.\nA conta que especificar aqui deve já existir.",
+ "config-mysql-engine": "Mecanismo de armazenamento:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Conjunto de caracteres da base de dados:",
+ "config-mysql-binary": "Binary",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Tipo de autenticação:",
+ "config-mssql-sqlauth": "Autenticação do SQL Server",
+ "config-mssql-windowsauth": "Autenticação do Windows",
+ "config-site-name": "Nome da wiki:",
+ "config-site-name-help": "Isto aparecerá na barra de títulos do navegador e em vários outros lugares.",
+ "config-site-name-blank": "Digite o nome do site.",
+ "config-project-namespace": "Domínio do projeto:",
+ "config-ns-generic": "Projeto",
+ "config-ns-site-name": "O mesmo que o nome da wiki: $1",
+ "config-ns-other": "Outro (especifique)",
+ "config-ns-other-default": "AMinhaWiki",
+ "config-ns-invalid": "O domínio especificado \"<nowiki>$1</nowiki>\" é inválido. Especifique um domínio do projeto diferente.",
+ "config-ns-conflict": "O domínio especificado \"<nowiki>$1</nowiki>\" conflita com um domínio padrão do MediaWiki.\nEspecifique um domínio do projeto diferente.",
+ "config-admin-box": "Conta de administrador",
+ "config-admin-name": "Seu nome de usuário:",
+ "config-admin-password": "Senha:",
+ "config-admin-password-confirm": "Repita a senha:",
+ "config-admin-help": "Digite o seu nome de usuário preferido aqui, por exemplo \"José Silveira\". Este será o nome que você usará para entrar na wiki.",
+ "config-admin-name-blank": "Digite um nome de usuário administrador.",
+ "config-admin-name-invalid": "O nome de usuário especificado \"<nowiki>$1</nowiki>\" é inválido.\nEspecifique um nome de usuário diferente.",
+ "config-admin-password-blank": "Digite uma senha para a conta de administrador.",
+ "config-admin-password-mismatch": "As duas senhas que você digitou não são iguais.",
+ "config-admin-email": "Endereço de email:",
+ "config-admin-email-help": "Digite aqui um endereço de email para permitir que você receba email de outros usuários na wiki, refaça a sua senha, e seja notificado das mudanças das páginas que você vigia. Você pode deixar esse campo vazio.",
+ "config-admin-error-user": "Erro interno ao criar um administrador com o nome \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Erro interno ao configurar uma senha para o administrador \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Você digitou um endereço de email inválido.",
+ "config-almost-done": "Você está quase terminando!\nVocê agora pode pular as configurações restantes e instalar a wiki agora mesmo.",
+ "config-optional-continue": "Faça-me mais perguntas.",
+ "config-optional-skip": "Já estou aborrecido, apenas instale a wiki.",
+ "config-profile": "Perfil de permissões do usuário:",
+ "config-profile-wiki": "Wiki aberta",
+ "config-profile-no-anon": "Criação de conta exigida",
+ "config-profile-fishbowl": "Somente editores autorizados",
+ "config-profile-private": "Wiki privada",
+ "config-license": "Direitos autorais e licenças:",
+ "config-license-none": "Sem rodapé com a licença",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 ou posterior",
+ "config-license-pd": "Domínio público",
+ "config-license-cc-choose": "Selecionar uma licença personalizada da organização Creative Commons",
+ "config-email-settings": "Configurações de email",
+ "config-enable-email-help": "Se você quer que o email funcione, estas [http://www.php.net/manual/en/mail.configuration.php configurações de email PHP] precisam ser configuradas corretamente. \nSe você não quiser usar nenhuma das funcionalidades, você pode desabilitá-las aqui.",
+ "config-email-user": "Ativar emails entre usuários",
+ "config-email-user-help": "Permitir que todos os usuários enviem email entre si se eles tiverem habilitado este recurso em suas preferências.",
+ "config-email-usertalk": "Ativar notificações de alterações à página de discussão de usuário",
+ "config-email-usertalk-help": "Permitir que os usuários recebam notificações quando suas páginas de discussão forem modificadas se eles tiverem habilitado as notificações em suas preferências.",
+ "config-email-watchlist": "Ativar notificação de alterações às páginas vigiadas",
+ "config-email-watchlist-help": "Permitir que os usuários recebam notificações sobre suas páginas vigiadas se eles tiverem habilitado as notificações em suas preferências.",
+ "config-email-auth": "Ativar autenticação de email",
+ "config-email-sender": "Endereço de email para resposta:",
+ "config-upload-settings": "Carregamento de imagens e arquivos",
+ "config-upload-enable": "Permitir o carregamento de arquivos",
+ "config-upload-deleted": "Diretório para arquivos excluídos:",
+ "config-upload-deleted-help": "Escolha um diretório no qual serão armazenados os arquivos excluídos. \nIdealmente, este não deveria ser acessível pela web.",
+ "config-logo": "URL do logotipo:",
+ "config-logo-help": "O tema padrão do MediaWiki inclui espaço para um logotipo de 135x160 acima do menu lateral. Carregue uma imagem do tamanho apropriado e insira o URL aqui.\n\nVocê pode utilizar <code>$wgStylePath</code> ou <code>$wgScriptPath</code> se seu logotipo esta relacionado a estes caminhos.\n\nSe não pretende usar um logótipo, deixe esta caixa em branco.",
+ "config-instantcommons": "Ativar o Instant Commons",
+ "config-cc-again": "Escolha novamente...",
+ "config-advanced-settings": "Configuração avançada",
+ "config-extensions": "Extensões",
+ "config-install-step-done": "feito",
+ "config-install-extensions": "Incluindo extensões",
+ "config-install-database": "Criando base de dados",
+ "config-install-schema": "Criando esquema",
+ "config-install-pg-commit": "Enviando alterações",
+ "config-install-user": "Criando usuário de banco de dados",
+ "config-install-user-alreadyexists": "O usuário \"$1\" já existe!",
+ "config-install-user-missing-create": "O usuário especificado \" $1 \" não existe.\nPor favor, clique na opção de \"criar conta\" abaixo se você deseja criá-lo.",
+ "config-install-tables": "Criando tabelas",
+ "config-install-tables-exist": "'''Aviso''': As tabelas do MediaWiki parecem já existir.\nA criação das tabelas será pulada.",
+ "config-install-keys": "Gerando senhas secretas",
+ "config-install-sysop": "Criando conta de usuário administrador",
+ "config-install-subscribe-notpossible": "cURL não está instalada e <code>allow_url_fopen</code> não está disponível.",
+ "config-install-mainpage": "Criando página principal com o conteúdo padrão",
+ "config-install-extension-tables": "Criando tabelas para extensões habilitadas",
+ "config-install-mainpage-failed": "Não foi possível inserir a página principal: $1",
+ "config-install-done": "<strong>Parabéns!</strong>\nVocê concluiu a instalação do MediaWiki.\n\nO instalador gerou um arquivo <code>LocalSettings.php</code>.\nEste arquivo contém todas as suas configurações.\n\nVocê precisa fazer o download desse arquivo e colocá-lo na raiz da sua instalação (o mesmo diretório onde está o arquivo <code>index.php</code>). Este download deve ter sido iniciado automaticamente.\n\nSe o download não foi iniciado, ou se ele foi cancelado, pode recomeçá-lo clicando no link abaixo:\n\n$3\n\n<strong>Nota</strong>: Se não fizer isto agora, o arquivo que foi gerado não estará disponível depois que você sair do processo de instalação sem baixá-lo.\n\nQuando isso tiver sido feito, pode <strong>[$2 entrar na sua wiki]</strong>.",
+ "config-download-localsettings": "Baixar <code>LocalSettings.php</code>",
+ "config-help": "ajuda",
+ "config-nofile": "O arquivo \"$1\" não foi encontrado. Ele foi apagado?",
+ "config-extension-link": "Você sabia que sua wiki suporta [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensões]?\n\nVocê pode explorar as [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensões por categoria] ou visitar a [//www.mediawiki.org/wiki/Extension_Matrix Matriz de Extensões] para ver a lista completa.",
+ "mainpagetext": "<strong>O MediaWiki foi instalado com sucesso.</strong>",
+ "mainpagedocfooter": "Consulte o [//meta.wikimedia.org/wiki/Help:Contents Manual de Usuário] para informações de como usar o software wiki.\n\n== Começando ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de opções de configuração]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ do MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussão com avisos de novas versões do MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Traduza o MediaWiki para seu idioma]"
+}
diff --git a/includes/installer/i18n/pt.json b/includes/installer/i18n/pt.json
new file mode 100644
index 00000000..835cf9b4
--- /dev/null
+++ b/includes/installer/i18n/pt.json
@@ -0,0 +1,335 @@
+{
+ "@metadata": {
+ "authors": [
+ "Crazymadlover",
+ "Hamilton Abreu",
+ "Luckas",
+ "Mormegil",
+ "Platonides",
+ "SandroHc",
+ "Waldir",
+ "아라",
+ "555",
+ "Fúlvio",
+ "Giro720",
+ "Imperadeiro98",
+ "Cainamarques",
+ "Vitorvicentevalente"
+ ]
+ },
+ "config-desc": "O instalador do MediaWiki",
+ "config-title": "Instalação da MediaWiki $1",
+ "config-information": "Informação",
+ "config-localsettings-upgrade": "Foi detectado um ficheiro <code>LocalSettings.php</code>.\nPara atualizar esta instalação, por favor introduza o valor de <code>$wgUpgradeKey</code> na caixa abaixo.\nEncontra este valor em <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Foi detectado um ficheiro <code>LocalSettings.php</code>.\nPara atualizar esta instalação, execute o <code>update.php</code>, por favor",
+ "config-localsettings-key": "Chave de atualização:",
+ "config-localsettings-badkey": "A chave que forneceu está incorreta.",
+ "config-upgrade-key-missing": "Foi detectada uma instalação existente do MediaWiki.\nPara atualizar esta instalação, por favor coloque a seguinte linha no final do seu <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "O ficheiro <code>LocalSettings.php</code> existente parece estar incompleto.\nA variável $1 não está definida.\nPor favor, defina esta variável no <code>LocalSettings.php</code> e clique \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Ocorreu um erro ao ligar à base de dados usando as configurações especificadas no <code>LocalSettings.php</code>. Por favor corrija essas configurações e tente novamente.\n\n$1",
+ "config-session-error": "Erro ao iniciar a sessão: $1",
+ "config-session-expired": "Os seus dados de sessão parecem ter expirado.\nAs sessões estão configuradas para uma duração de $1.\nPode aumentar esta duração configurando <code>session.gc_maxlifetime</code> no php.ini.\nReinicie o processo de instalação.",
+ "config-no-session": "Os seus dados de sessão foram perdidos!\nVerifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um diretório apropriado.",
+ "config-your-language": "O seu idioma:",
+ "config-your-language-help": "Selecione o idioma que será usado durante o processo de instalação.",
+ "config-wiki-language": "Idioma da wiki:",
+ "config-wiki-language-help": "Selecione o idioma que será predominante na wiki.",
+ "config-back": "← Voltar",
+ "config-continue": "Continuar →",
+ "config-page-language": "Idioma",
+ "config-page-welcome": "Bem-vindo(a) ao MediaWiki!",
+ "config-page-dbconnect": "Ligar à base de dados",
+ "config-page-upgrade": "Atualizar a instalação existente",
+ "config-page-dbsettings": "Configurações da base de dados",
+ "config-page-name": "Nome",
+ "config-page-options": "Opções",
+ "config-page-install": "Instalar",
+ "config-page-complete": "Terminado!",
+ "config-page-restart": "Reiniciar a instalação",
+ "config-page-readme": "Leia-me",
+ "config-page-releasenotes": "Notas de lançamento",
+ "config-page-copying": "A copiar",
+ "config-page-upgradedoc": "A atualizar",
+ "config-page-existingwiki": "Wiki existente",
+ "config-help-restart": "Deseja limpar todos os dados gravados que introduziu e reiniciar o processo de instalação?",
+ "config-restart": "Sim, reiniciar",
+ "config-welcome": "=== Verificações do ambiente ===\nSão realizadas verificações básicas para determinar se este ambiente é apropriado para instalação do MediaWiki.\nSe necessitar de pedir ajuda durante a instalação, deve fornecer os resultados destas verificações.",
+ "config-copyright": "=== Direitos de autor e Condições de uso ===\n\n$1\n\nEste programa é software livre; pode redistribuí-lo e/ou modificá-lo nos termos da licença GNU General Public License, tal como publicada pela Free Software Foundation; tanto a versão 2 da Licença, como (por opção sua) qualquer versão posterior.\n\nEste programa é distribuído na esperança de que seja útil, mas '''sem qualquer garantia'''; inclusive, sem a garantia implícita da '''possibilidade de ser comercializado''' ou de '''adequação para qualquer finalidade específica'''.\nConsulte a licença GNU General Public License para mais detalhes.\n\nEm conjunto com este programa deve ter recebido <doclink href=Copying>uma cópia da licença GNU General Public License</doclink>; se não a recebeu, peça-a por escrito a Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA ou [http://www.gnu.org/copyleft/gpl.html leia-a na internet].",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/pt Página principal do MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/pt Ajuda]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/pt Manual técnico]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Leia-me</doclink>\n* <doclink href=ReleaseNotes>Notas de lançamento</doclink>\n* <doclink href=Copying>Cópia</doclink>\n* <doclink href=UpgradeDoc>Atualização</doclink>",
+ "config-env-good": "O ambiente foi verificado.\nPode instalar o MediaWiki.",
+ "config-env-bad": "O ambiente foi verificado.\nNão pode instalar o MediaWiki.",
+ "config-env-php": "O PHP $1 está instalado.",
+ "config-env-hhvm": "HHVM $1 está instalado.",
+ "config-unicode-using-utf8": "A usar o utf8_normalize.so, por Brion Vibber, para a normalização Unicode.",
+ "config-unicode-using-intl": "A usar a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.",
+ "config-unicode-pure-php-warning": "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode. Irá recorrer-se à implementação em PHP puro, que é mais lenta.\nSe o seu site tem alto volume de tráfego, devia informar-se um pouco sobre a [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations/pt normalização Unicode].",
+ "config-unicode-update-warning": "'''Aviso''': A versão instalada do wrapper de normalização Unicode usa uma versão mais antiga da biblioteca do [http://site.icu-project.org/ projeto ICU].\nDevia [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations atualizá-la] se tem quaisquer preocupações sobre o uso do Unicode.",
+ "config-no-db": "Não foi possível encontrar um controlador ''(driver)'' apropriado da base de dados! Precisa de instalar um controlador da base de dados para o PHP. São aceites os seguintes tipos de base de dados: $1.\n\nSe fez a compilação do PHP, reconfigure-o com um cliente de base de dados ativado; por exemplo, usando <code>./configure --with-mysql</code>.\nSe instalou o PHP a partir de um pacote Debian ou Ubuntu, então precisa de instalar também, por exemplo, o pacote <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Aviso''': Tem a versão $1 do SQLite, que é anterior à versão mínima necessária, a $2. O SQLite não estará disponível.",
+ "config-no-fts3": "'''Aviso''': O SQLite foi compilado sem o módulo [//sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
+ "config-magic-quotes-runtime": "'''Erro fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está ativa!'''\nEsta opção causa corrupção dos dados de entrada, de uma forma imprevisível.\nNão pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
+ "config-magic-quotes-sybase": "'''Erro fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está ativa!'''\nEsta opção causa corrupção dos dados de entrada, de uma forma imprevisível.\nNão pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
+ "config-mbstring": "'''Erro fatal: A opção [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está ativa!'''\nEsta opção causa erros e pode corromper os dados de uma forma imprevisível.\nNão pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
+ "config-safe-mode": "'''Aviso:''' O [http://www.php.net/features.safe-mode safe mode] do PHP está ativo.\nEste modo pode causar problemas, especialmente no upload de ficheiros e no suporte a <code>math</code>.",
+ "config-xml-bad": "Falta o módulo XML do PHP.\nO MediaWiki necessita de funções deste módulo e não funcionará com esta configuração.\nSe está a executar o Mandrake, instale o pacote php-xml.",
+ "config-pcre-old": "<strong>Erro fatal:</strong> É necessário o PCRE $1 ou versão posterior.\nO <i>link</i> do seu binário PHP foi feito com o PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Mais informações].",
+ "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": "'''Aviso:''' A configuração <code>memory_limit</code> do PHP é $1.\nIsto é provavelmente demasiado baixo.\nA instalação poderá falhar!",
+ "config-ctype": "'''Erro fatal''': O PHP tem de ser compilado com suporte para a [http://www.php.net/manual/en/ctype.installation.php extensão Ctype].",
+ "config-json": "<strong>Erro fatal:</strong> O PHP foi compilado sem suporte de JSON.\nTem de instalar a extensão JSON do PHP (incluída no PHP 5.2 ou posterior) ou a extensão [http://pecl.php.net/package/jsonc PECL jsonc] antes de instalar o MediaWiki.\n* A extensão JSON do PHP está incluída nas distribuções 5 e 6 do Red Hat Enterprise Linux (CentOS), mas tem de estar ativa nos ficheiros <code>/etc/php.ini</code> ou <code>/etc/php.d/json.ini</code>.\n* Algumas distribuições do Linux posteriores a maio de 2013 omitem a extensão JSON do PHP e substituem-na pela extensão PECL chamando-lhe <code>php5-json</code> ou <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] instalada",
+ "config-apc": "[http://www.php.net/apc APC] instalada",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] instalada",
+ "config-no-cache": "'''Aviso:''' Não foi possível encontrar: [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], nem [http://www.iis.net/download/WinCacheForPhp WinCache].\nA cache de objetos não está ativada.",
+ "config-mod-security": "'''Aviso''': O seu servidor de internet tem o [http://modsecurity.org/ mod_security] ativado. Se este estiver mal configurado, pode causar problemas ao MediaWiki ou a outros programas, permitindo que os utilizadores publiquem conteúdos arbitrários.\nConsulte a [http://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 GNU diff3 não foi encontrado.",
+ "config-git": "Foi encontrado o software de controlo de versões Git: <code>$1</code>.",
+ "config-git-bad": "Não foi encontrado o software de controlo de versões Git.",
+ "config-imagemagick": "Foi encontrado o ImageMagick: <code>$1</code>.\nSe possibilitar uploads, a miniaturização de imagens será ativada.",
+ "config-gd": "Foi encontrada a biblioteca gráfica GD.\nSe possibilitar uploads, a miniaturização de imagens será ativada.",
+ "config-no-scaling": "Não foi encontrada a biblioteca gráfica GD nem o ImageMagick.\nA miniaturização de imagens será desativada.",
+ "config-no-uri": "'''Erro:''' Não foi possível determinar a URI atual.\nA instalação foi abortada.",
+ "config-no-cli-uri": "'''Aviso''': Não foi especificado um <code>--scriptpath</code>; por omissão, será usado: <code>$1</code>.",
+ "config-using-server": "Será usado o nome do servidor \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Será usada a URL do servidor \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Aviso:''' O diretório por omissão para carregamentos <code>$1</code>, está vulnerável à execução arbitrária de scripts.\nEmbora o MediaWiki verifique a existência de ameaças de segurança em todos os ficheiros enviados, é altamente recomendado que [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security vede esta vulnerabilidade de segurança] antes de possibilitar uploads.",
+ "config-no-cli-uploads-check": "'''Aviso:''' O diretório por omissão para carregamentos, <code>$1</code>, não é verificado para determinar se é vulnerável à execução de código arbitrário durante a instalação por CLI (\"Command-line Interface\").",
+ "config-brokenlibxml": "O seu sistema tem uma combinação de versões do PHP e do libxml2 conhecida por ser problemática, podendo causar corrupção de dados no MediaWiki e noutras aplicações da internet.\nAtualize para a versão 2.7.3 ou posterior do libxml2 ([https://bugs.php.net/bug.php?id=45996 incidência reportada no PHP]).\nInstalação cancelada.",
+ "config-suhosin-max-value-length": "O Suhosin está instalado e limita o parâmetro GET <code>length</code> a $1 bytes.\nO componente ResourceLoader do MediaWiki consegue exceder este limite, mas prejudicando o desempenho.\nSe lhe for possível, deve atribuir ao parâmetro <code>suhosin.get.max_value_length</code> o valor 1024 ou maior no ficheiro <code>php.ini</code>, e definir o mesmo valor para <code>$wgResourceLoaderMaxQueryLength</code> no ficheiro LocalSettings.php.",
+ "config-db-type": "Tipo da base de dados:",
+ "config-db-host": "Servidor da base de dados:",
+ "config-db-host-help": "Se a base de dados estiver num servidor separado, introduza aqui o nome ou o endereço IP desse servidor.\n\nSe estiver a usar um servidor partilhado, o fornecedor do alojamento deve fornecer o nome do servidor na documentação.\n\nSe está a fazer a instalação num servidor Windows com MySQL, usar como nome do servidor \"localhost\" poderá não funcionar. Se não funcionar, tente usar \"127.0.0.1\" como endereço IP local.\n\nSe estiver a usar PostgreSQL, deixe este campo em branco para fazer a ligação através de um socket Unix.",
+ "config-db-host-oracle": "TNS (Transparent Network Substrate) da base de dados:",
+ "config-db-host-oracle-help": "Introduza um [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Nome Local de Ligação] válido; tem de estar visível para esta instalação um ficheiro tnsnames.ora.<br />Se está a usar bibliotecas cliente versão 10g ou posterior, também pode usar o método [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Ligação Fácil] de atribuição do nome.",
+ "config-db-wiki-settings": "Identifique esta wiki",
+ "config-db-name": "Nome da base de dados:",
+ "config-db-name-help": "Escolha um nome para identificar a sua wiki.\nO nome não deve conter espaços.\n\nSe estiver a usar um servidor partilhado, o fornecedor do alojamento deve poder fornecer-lhe o nome de uma base de dados que possa usar, ou permite-lhe criar bases de dados através de um painel de controle.",
+ "config-db-name-oracle": "Esquema ''(schema)'' da base de dados:",
+ "config-db-account-oracle-warn": "Há três cenários suportados na instalação do servidor de base de dados Oracle:\n\nSe pretende criar a conta de acesso pela internet na base de dados durante o processo de instalação, forneça como conta para a instalação uma conta com o papel de SYSDBA na base de dados e especifique as credenciais desejadas para a conta de acesso pela internet. Se não pretende criar a conta de acesso pela internet durante a instalação, pode criá-la manualmente e fornecer só essa conta para a instalação (se ela tiver as permissões necessárias para criar os objetos do esquema ''(schema)''). A terceira alternativa é fornecer duas contas diferentes; uma com privilégios de criação e outra com privilégios limitados para o acesso pela internet.\n\nExiste um script para criação de uma conta com os privilégios necessários no diretório \"maintenance/oracle/\" desta instalação. Mantenha em mente que usar uma conta com privilégios limitados impossibilita todas as operações de manutenção com a conta padrão.",
+ "config-db-install-account": "Conta do utilizador para a instalação",
+ "config-db-username": "Nome do utilizador da base de dados:",
+ "config-db-password": "Palavra-chave do utilizador da base de dados:",
+ "config-db-password-empty": "Introduza a palavra-chave do novo utilizador da base de dados: $1.\nEmbora seja possível criar utilizadores sem palavra-chave, fazê-lo não é seguro.",
+ "config-db-username-empty": "Tem de introduzir um valor para \"{{int:config-db-username}}\"",
+ "config-db-install-username": "Introduza o nome de utilizador que será usado para aceder à base de dados durante o processo de instalação. Este utilizador não é o do MediaWiki; é o utilizador da base de dados.",
+ "config-db-install-password": "Introduza a palavra-chave do utilizador que será usado para aceder à base de dados durante o processo de instalação. Esta palavra-chave não é a do utilizador do MediaWiki; é a palavra-chave do utilizador da base de dados.",
+ "config-db-install-help": "Introduza o nome de utilizador e a palavra-chave que serão usados para aceder à base de dados durante o processo de instalação.",
+ "config-db-account-lock": "Usar o mesmo nome de utilizador e palavra-chave durante a operação normal",
+ "config-db-wiki-account": "Conta de utilizador para a operação normal",
+ "config-db-wiki-help": "Introduza o nome de utilizador e a palavra-chave que serão usados para aceder à base de dados durante a operação normal da wiki.\nSe o utilizador não existir na base de dados, mas a conta de instalação tiver privilégios suficientes, o utilizador que introduzir será criado na base de dados com os privilégios mínimos necessários para a operação normal da wiki.",
+ "config-db-prefix": "Prefixo para as tabelas da base de dados:",
+ "config-db-prefix-help": "Se necessitar de partilhar uma só base de dados entre várias wikis, ou entre o MediaWiki e outra aplicação, pode escolher adicionar um prefixo ao nome de todas as tabelas desta instalação, para evitar conflitos.\nNão use espaços.\n\nNormalmente, este campo deve ficar vazio.",
+ "config-db-charset": "Conjunto de caracteres da base de dados",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 backwards-compatible UTF-8",
+ "config-charset-help": "'''Aviso:''' Se usar '''backwards-compatible UTF-8''' (\"UTF-8 compatível com versões anteriores\") no MySQL 4.1+, e depois fizer cópias de segurança da base de dados usando <code>mysqldump</code>, poderá destruir todos os caracteres que não fazem parte do conjunto ASCII, corrompendo assim, de forma irreversível, as suas cópias de segurança!\n\nNo modo '''binary''' (\"binário\"), o MediaWiki armazena o texto UTF-8 na base de dados em campos binários.\nIsto é mais eficiente do que o modo UTF-8 do MySQL e permite que sejam usados todos os caracteres Unicode.\nNo modo '''UTF-8''', o MySQL saberá em que conjunto de caracteres os seus dados estão e pode apresentá-los e convertê-los da forma mais adequada,\nmas não lhe permitirá armazenar caracteres acima do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
+ "config-mysql-old": "É necessário o MySQL $1 ou posterior; tem a versão $2.",
+ "config-db-port": "Porta da base de dados:",
+ "config-db-schema": "Esquema ''(schema)'' do MediaWiki",
+ "config-db-schema-help": "Normalmente, este esquema estará correto.\nAltere-o só se souber que precisa de o fazer.",
+ "config-pg-test-error": "Não foi possível criar uma ligação à base de dados '''$1''': $2",
+ "config-sqlite-dir": "Diretório de dados do SQLite:",
+ "config-sqlite-dir-help": "O SQLite armazena todos os dados num único ficheiro.\n\nDurante a instalação, o servidor de internet precisa de ter permissão de escrita no diretório que especificar.\n\nEste diretório '''não''' deve poder ser acedido diretamente da internet, por isso está a ser colocado onde estão os seus ficheiros PHP.\n\nJuntamente com o diretório, o instalador irá criar um ficheiro <code>.htaccess</code>, mas se esta operação falhar é possível que alguém venha a ter acesso direto à base de dados.\nIsto inclui acesso aos dados dos utilizadores (endereços de correio eletrónico, palavras-chave encriptadas), às revisões eliminadas e a outros dados de acesso restrito na wiki.\n\nConsidere colocar a base de dados num local completamente diferente, como, por exemplo, em <code>/var/lib/mediawiki/asuawiki</code>.",
+ "config-oracle-def-ts": "Tablespace padrão:",
+ "config-oracle-temp-ts": "Tablespace temporário:",
+ "config-type-mysql": "MySQL (ou compatível)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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 nos links acima para ativar o suporte.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] é a plataforma primária do MediaWiki e é a melhor suportada. O MediaWiki também trabalha com [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], que são compatíveis com MySQL. ([http://www.php.net/manual/en/mysql.installation.php Como compilar PHP com suporte a MySQL])",
+ "config-dbsupport-postgres": "* O [{{int:version-db-postgres-url}} PostgreSQL] é uma plataforma popular de base de dados de fonte aberta, alternativa ao MySQL. Poderá conter pequenos defeitos e o seu uso em ambientes de exploração/produção não é recomendado. ([http://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. ([http://www.php.net/manual/en/pdo.installation.php Como compilar o PHP com suporte SQLite], usa PDO)",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] é uma base de dados comercial para empresas. ([http://www.php.net/manual/en/oci8.installation.php Como compilar o PHP com suporte OCI8])",
+ "config-dbsupport-mssql": "* O [{{int:version-db-mssql-url}} Microsoft SQL Server] é uma base de dados comercial do Windows para empresas. ([http://www.php.net/manual/en/sqlsrv.installation.php Como compilar o PHP com suporte SQLSRV])",
+ "config-header-mysql": "Definições MySQL",
+ "config-header-postgres": "Definições PostgreSQL",
+ "config-header-sqlite": "Definições SQLite",
+ "config-header-oracle": "Definições Oracle",
+ "config-header-mssql": "Configurações do Microsoft SQL Server",
+ "config-invalid-db-type": "O tipo de base de dados é inválido",
+ "config-missing-db-name": "Tem de introduzir um valor para \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Tem de introduzir um valor para \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Tem de introduzir um valor para \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "O TNS da base de dados, \"$1\", é inválido.\nUse \"TNS Name\" ou o método \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Métodos de Configuração da Conectividade em Oracle])",
+ "config-invalid-db-name": "O nome da base de dados, \"$1\", é inválido.\nUse só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e hífens (-) dos caracteres ASCII.",
+ "config-invalid-db-prefix": "O prefixo da base de dados, \"$1\", é inválido.\nUse só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e hífens (-) dos caracteres ASCII.",
+ "config-connection-error": "$1.\n\nVerifique o servidor, o nome do utilizador e a palavra-chave abaixo e tente novamente.",
+ "config-invalid-schema": "O esquema ''(schema)'' do MediaWiki, \"$1\", é inválido.\nUse só letras (a-z, A-Z), algarismos (0-9) e sublinhados (_) dos caracteres ASCII.",
+ "config-db-sys-create-oracle": "O instalador só permite criar uma conta nova usando uma conta SYSDBA.",
+ "config-db-sys-user-exists-oracle": "A conta \"$1\" já existe. A conta SYSDBA só pode criar uma conta nova!",
+ "config-postgres-old": "É necessário o PostgreSQL $1 ou posterior; tem a versão $2.",
+ "config-mssql-old": "É necessário o Microsoft SQL Server $1 ou posterior. Tem a versão $2.",
+ "config-sqlite-name-help": "Escolha o nome que identificará a sua wiki.\nNão use espaços ou hífens.\nEste nome será usado como nome do ficheiro de dados do SQLite.",
+ "config-sqlite-parent-unwritable-group": "Não é possível criar o diretório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no diretório que o contém <code><nowiki>$2</nowiki></code>.\n\nO instalador determinou em que nome de utilizador o seu servidor de internet está a correr.\nPara continuar, configure o diretório <code><nowiki>$3</nowiki></code> para poder ser escrito por este utilizador.\nPara fazê-lo em sistemas Unix ou Linux, use:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Não é possível criar o diretório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no diretório que o contém <code><nowiki>$2</nowiki></code>.\n\nNão foi possível determinar em que nome de utilizador o seu servidor de internet está a correr.\nPara continuar, configure o diretório <code><nowiki>$3</nowiki></code> para que este possa ser globalmente escrito por esse utilizador (e por outros!).\nPara fazê-lo em sistemas Unix ou Linux, use:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Ocorreu um erro ao criar o diretório de dados \"$1\".\nVerifique a localização e tente novamente.",
+ "config-sqlite-dir-unwritable": "Não foi possível escrever no diretório \"$1\".\nAltere as permissões para que ele possa ser escrito pelo servidor de internet e tente novamente.",
+ "config-sqlite-connection-error": "$1.\n\nVerifique o diretório de dados e o nome da base de dados abaixo e tente novamente.",
+ "config-sqlite-readonly": "Não é possivel escrever no ficheiro <code>$1</code>.",
+ "config-sqlite-cant-create-db": "Não foi possível criar o ficheiro da base de dados <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "O PHP não tem suporte FTS3; a reverter o esquema das tabelas para o anterior",
+ "config-can-upgrade": "Esta base de dados contém tabelas do MediaWiki.\nPara atualizá-las para o MediaWiki $1, clique '''Continuar'''.",
+ "config-upgrade-done": "Atualização terminada.\n\nAgora pode [$1 começar a usar a sua wiki].\n\nSe quiser regenerar o seu ficheiro <code>LocalSettings.php</code>, clique o botão abaixo.\nEsta operação '''não é recomendada''' a menos que esteja a ter problemas com a sua wiki.",
+ "config-upgrade-done-no-regenerate": "Atualização terminada.\n\nAgora pode [$1 começar a usar a sua wiki].",
+ "config-regenerate": "Regenerar o LocalSettings.php →",
+ "config-show-table-status": "A consulta <code>SHOW TABLE STATUS</code> falhou!",
+ "config-unknown-collation": "'''Aviso:''' A base de dados está a utilizar uma colação ''(collation)'' desconhecida.",
+ "config-db-web-account": "Conta na base de dados para acesso pela internet",
+ "config-db-web-help": "Selecione o nome de utilizador e a palavra-chave que o servidor de internet irá utilizar para aceder ao servidor da base de dados, durante a operação normal da wiki.",
+ "config-db-web-account-same": "Usar a mesma conta usada na instalação",
+ "config-db-web-create": "Criar a conta se ainda não existir",
+ "config-db-web-no-create-privs": "A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.\nA conta que especificar aqui já tem de existir.",
+ "config-mysql-engine": "Motor de armazenamento:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>Aviso:</strong> Selecionou o MyISAM para motor de armazenamento do MySQL, uma combinação desaconselhada para usar com o MediaWiki porque:\n* praticamente não permite acessos simultâneos, porque bloqueia tabelas\n* o MyISAM é mais suscetível a perdas da integridade dos dados do que outros motores\n* o código do MediaWiki não trabalha devidamente com o MyISAM\n\nSe a sua instalação do MySQL suporta InnoDB, é altamente recomendado que o escolha em vez do MyISAM.\nSe não suporta o InnoDB, talvez seja uma boa altura para atualizá-la para a versão mais recente.",
+ "config-mysql-only-myisam-dep": "<strong>Aviso:</strong> O único motor de armazenamento para MySQL nesta máquina é o MyISAM e o seu uso com o MediaWiki não é recomendado porque:\n* praticamente não suporta acessos simultâneos, porque bloqueia tabelas\n* o MyISAM é mais suscetível a perdas da integridade dos dados do que outros motores\n* o código do MediaWiki não trabalha devidamente com o MyISAM\n\nA sua instalação MySQL não suporta InnoDB, talvez seja uma boa altura para atualizá-la para a versão mais recente.",
+ "config-mysql-engine-help": "<strong>InnoDB</strong> é quase sempre a melhor opção, porque suporta bem acessos simultâneos <i>(concurrency)</i>.\n\n<strong>MyISAM</strong> pode ser mais rápido no modo de utilizador único ou em instalações somente para leitura.\nAs bases de dados MyISAM tendem a perder integridade de dados com mais frequência do que as bases de dados InnoDB.",
+ "config-mysql-charset": "Conjunto de caracteres da base de dados:",
+ "config-mysql-binary": "Binário",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "No modo '''binary''' (\"binário\"), o MediaWiki armazena o texto UTF-8 na base de dados em campos binários.\nIsto é mais eficiente do que o modo UTF-8 do MySQL e permite que sejam usados todos os caracteres Unicode.\n\nNo modo '''UTF-8''', o MySQL saberá em que conjunto de caracteres os seus dados estão e pode apresentá-los e convertê-los da forma mais adequada,\nmas não lhe permitirá armazenar caracteres acima do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
+ "config-mssql-auth": "Tipo de autenticação:",
+ "config-mssql-install-auth": "Selecione o tipo de autenticação a usar para ligar à base de dados durante o processo de instalação.\nSe selecionar \"{{int:config-mssql-windowsauth}}\", serão usadas as credenciais do utilizador com que o servidor de Internet está a ser executado.",
+ "config-mssql-web-auth": "Selecione o tipo de autenticação que o servidor de Internet irá usar para se ligar ao servidor da base de dados durante a operação normal da wiki.\nSe selecionar \"{{int:config-mssql-windowsauth}}\", serão usadas as credenciais do utilizador com que o servidor de Internet está a ser executado.",
+ "config-mssql-sqlauth": "Autenticação do SQL Server",
+ "config-mssql-windowsauth": "Autenticação do Windows",
+ "config-site-name": "Nome da wiki:",
+ "config-site-name-help": "Este nome aparecerá no título da janela do seu navegador e em vários outros sítios.",
+ "config-site-name-blank": "Introduza o nome do sítio.",
+ "config-project-namespace": "Espaço nominal do projeto:",
+ "config-ns-generic": "Projeto",
+ "config-ns-site-name": "O mesmo que o nome da wiki: $1",
+ "config-ns-other": "Outro (especifique)",
+ "config-ns-other-default": "AMinhaWiki",
+ "config-project-namespace-help": "Seguindo o exemplo da Wikipédia, muitas wikis mantêm as páginas das suas normas e políticas, separadas das páginas de conteúdo, num \"'''domínio do projeto'''\".\nTodos os nomes das páginas neste domínio começam com um determinado prefixo, que pode especificar aqui.\nTradicionalmente, este prefixo deriva do nome da wiki, mas não pode conter caracteres de pontuação, como \"#\" ou \":\".",
+ "config-ns-invalid": "O espaço nominal especificado \"<nowiki>$1</nowiki>\" é inválido.\nIntroduza um espaço nominal de projeto diferente.",
+ "config-ns-conflict": "O espaço nominal que especificou, \"<nowiki>$1</nowiki>\", cria um conflito com um dos espaços nominais padrão do MediaWiki.\nEspecifique um espaço nominal do projeto diferente.",
+ "config-admin-box": "Conta de administrador",
+ "config-admin-name": "Seu nome de utilizador:",
+ "config-admin-password": "Palavra-chave:",
+ "config-admin-password-confirm": "Repita a palavra-chave:",
+ "config-admin-help": "Introduza aqui o seu nome de utilizador preferido, por exemplo, \"João Beltrão\".\nEste é o nome que irá utilizar para entrar na wiki.",
+ "config-admin-name-blank": "Introduza um nome de utilizador para administrador.",
+ "config-admin-name-invalid": "O nome de utilizador especificado \"<nowiki>$1</nowiki>\" é inválido.\nIntroduza um nome de utilizador diferente.",
+ "config-admin-password-blank": "Introduza uma palavra-chave para a conta de administrador.",
+ "config-admin-password-mismatch": "As duas palavras-chave que introduziu não coincidem.",
+ "config-admin-email": "Correio electrónico:",
+ "config-admin-email-help": "Introduza aqui um correio electrónico que lhe permita receber mensagens de outros utilizadores da wiki, reiniciar a sua palavra-chave e receber notificações de alterações às suas páginas vigiadas. Pode deixar o campo vazio.",
+ "config-admin-error-user": "Ocorreu um erro interno ao criar um administrador com o nome \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Ocorreu um erro interno ao definir uma palavra-chave para o administrador \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Introduziu um correio electrónico inválido",
+ "config-subscribe": "Subscreva a [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de divulgação de anúncios de lançamento].",
+ "config-subscribe-help": "Esta é uma lista de divulgação de baixo volume para anúncios de lançamento de versões novas, incluindo anúncios de segurança importantes.\nDeve subscrevê-la e atualizar a sua instalação MediaWiki quando são lançadas versões novas.",
+ "config-subscribe-noemail": "Tentou subscrever a lista de divulgação dos anúncios de novas versões, sem fornecer um endereço de correio electrónico.\nPara subscrever esta lista de divulgação tem de fornecer um endereço de correio electrónico.",
+ "config-almost-done": "Está quase a terminar!\nAgora pode saltar as configurações restantes e instalar já a wiki.",
+ "config-optional-continue": "Faz-me mais perguntas.",
+ "config-optional-skip": "Já estou aborrecido, instala lá a wiki.",
+ "config-profile": "Perfil de permissões:",
+ "config-profile-wiki": "Wiki aberta",
+ "config-profile-no-anon": "Criação de conta exigida",
+ "config-profile-fishbowl": "Somente utilizadores autorizados",
+ "config-profile-private": "Wiki privada",
+ "config-profile-help": "As wikis funcionam melhor quando se deixa tantas pessoas editá-las quanto possível.\nNo MediaWiki, é fácil rever as alterações recentes e reverter quaisquer estragos causados por utilizadores novatos ou maliciosos.\n\nNo entanto, muitas pessoas consideram o MediaWiki útil de variadas formas e nem sempre é fácil convencer todas as pessoas dos benefícios desta filosofia wiki.\nPor isso pode optar.\n\nUma '''{{int:config-profile-wiki}}''' permite que todos a editem, sem sequer necessitar de autenticação.\nUma wiki com '''{{int:config-profile-no-anon}}''' atribui mais responsabilidade, mas pode afastar os colaboradores ocasionais.\n\nUm cenário '''{{int:config-profile-fishbowl}}''' permite que os utilizadores aprovados editem, mas que o público visione as páginas, incluindo o historial das mesmas.\nUma '''{{int:config-profile-private}}''' só permite que os utilizadores aprovados visionem as páginas e as editem.\n\nApós a instalação, estarão disponíveis mais configurações de privilégios. Consulte [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights a entrada relevante no Manual].",
+ "config-license": "Direitos de autor e licença:",
+ "config-license-none": "Sem rodapé com a licença",
+ "config-license-cc-by-sa": "Creative Commons - Atribuição - Partilha nos Mesmos Termos",
+ "config-license-cc-by": "Creative Commons - Atribuição",
+ "config-license-cc-by-nc-sa": "Creative Commons - Atribuição - Uso Não Comercial - Partilha nos Mesmos Termos",
+ "config-license-cc-0": "Creative Commons Zero (Domínio Público)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 ou posterior",
+ "config-license-pd": "Domínio Público",
+ "config-license-cc-choose": "Selecionar uma licença personalizada Creative Commons",
+ "config-license-help": "Muitas wikis de acesso público licenciam todas as colaborações com uma [http://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 electrónico",
+ "config-enable-email": "Ativar mensagens eletrónicas de saída",
+ "config-enable-email-help": "Se quer que o correio eletrónico funcione, as [http://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",
+ "config-email-usertalk-help": "Permitir que os utilizadores recebam notificações de alterações à sua página de discussão, se tiverem ativado esta funcionalidade nas suas preferências.",
+ "config-email-watchlist": "Ativar notificação de alterações às páginas vigiadas",
+ "config-email-watchlist-help": "Permitir que os utilizadores recebam notificações de alterações às suas páginas vigiadas, se tiverem ativado esta funcionalidade nas suas preferências.",
+ "config-email-auth": "Ativar autenticação do correio eletrónico",
+ "config-email-auth-help": "Se esta opção for ativada, os utilizadores têm de confirmar o seu endereço de correio eletrónico usando um link que lhes é enviado sempre que o definirem ou alterarem.\nSó os endereços de correio eletrónico autenticados podem receber mensagens eletrónicas dos outros utilizadores ou alterar as mensagens de notificação.\nÉ '''recomendado''' que esta opção seja ativada nas wikis de acesso público para impedir o uso abusivo das funcionalidades de correio eletrónico.",
+ "config-email-sender": "Endereço de correio electrónico de retorno:",
+ "config-email-sender-help": "Introduza o endereço de correio electrónico que será usado como endereço de retorno nas mensagens electrónicas de saída.\nÉ para este endereço que serão enviadas as mensagens que não podem ser entregues.\nMuitos servidores de correio electrónico exigem que pelo menos a parte do nome do domínio seja válida. \\",
+ "config-upload-settings": "Carregamento de imagens e ficheiros",
+ "config-upload-enable": "Possibilitar o carregamento de ficheiros",
+ "config-upload-help": "O carregamento de ficheiros expõe o seu servidor a riscos de segurança.\nPara mais informações, leia a [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security seção sobre segurança] do Manual Técnico.\n\nPara permitir o carregamento de ficheiros, altere as permissões do subdiretório <code>images</code> no diretório de raiz do MediaWiki para que o servidor de internet possa escrever nele.\nDepois ative esta opção.",
+ "config-upload-deleted": "Diretório para os ficheiros apagados:",
+ "config-upload-deleted-help": "Escolha um diretório onde serão arquivados os ficheiros apagados.\nO ideal é que este diretório não possa ser diretamente acedido a partir da internet.",
+ "config-logo": "URL do logótipo:",
+ "config-logo-help": "O tema padrão do MediaWiki inclui espaço para um logótipo de 135x160 pixels acima do menu da barra lateral.\nColoque na wiki uma imagem com estas dimensões e introduza aqui a URL dessa imagem.\n\nSe não pretende usar um logótipo, deixe este campo em branco.",
+ "config-instantcommons": "Ativar Instant Commons",
+ "config-instantcommons-help": "O [//www.mediawiki.org/wiki/InstantCommons Instant Commons] é uma funcionalidade que permite que as wikis usem imagens, áudio e outros ficheiros multimédia disponíveis no site [//commons.wikimedia.org/ Wikimedia Commons].\nPara poder usá-los, o MediaWiki necessita de acesso à internet.\n\nPara mais informações sobre esta funcionalidade, incluindo instruções sobre como configurá-la para usar outras wikis em vez da Wikimedia Commons, consulte o [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos Manual Técnico].",
+ "config-cc-error": "O auxiliar de escolha de licenças da Creative Commons não produziu resultados.\nIntroduza o nome da licença manualmente.",
+ "config-cc-again": "Escolha outra vez...",
+ "config-cc-not-chosen": "Escolha a licença da Creative Commons que pretende e clique \"continuar\".",
+ "config-advanced-settings": "Configuração avançada",
+ "config-cache-options": "Configuração da cache de objetos:",
+ "config-cache-help": "A cache de objetos é usada para melhorar o desempenho do MediaWiki. Armazena dados usados com frequência.\nSites de tamanho médio ou grande são altamente encorajados a ativar esta funcionalidade e os sites pequenos também terão alguns benefícios em fazê-lo.",
+ "config-cache-none": "Sem cache (não é removida nenhuma funcionalidade, mas a velocidade de operação pode ser afectada nas wikis grandes)",
+ "config-cache-accel": "Cache de objetos do PHP (APC, XCache ou WinCache)",
+ "config-cache-memcached": "Usar Memcached (requer instalação e configurações adicionais)",
+ "config-memcached-servers": "Servidores Memcached:",
+ "config-memcached-help": "Lista de endereços IP que serão usados para o Memcached.\nDeve-se colocar um por linha e indicar a porta a utilizar. Por exemplo:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Selecionou o Memcached como tipo de chache, mas não especificou nenhum servidor.",
+ "config-memcache-badip": "Introduziu um endereço IP inválido para o Memcached: $1.",
+ "config-memcache-noport": "Não especificou a porta a usar para o servidor Memcached: $1.\nSe não sabe qual é a porta, a predefinida é a 11211.",
+ "config-memcache-badport": "Os números das portas do Memcached devem estar entre $1 e $2.",
+ "config-extensions": "Extensões",
+ "config-extensions-help": "Foi detectada a existência das extensões listadas acima, no seu diretório <code>./extensions</code>.\n\nEstas talvez necessitem de configurações adicionais, mas pode ativá-las agora",
+ "config-skins": "Temas",
+ "config-skins-help": "Os temas listados abaixo foram detetados no seu diretório <code>./skins</code>. Deverá ativar pelo menos um e escolher qual o escolhido por padrão.",
+ "config-skins-use-as-default": "Usar este tema como padrão",
+ "config-skins-must-enable-some": "Deve escolher pelo menos um tema para ativar.",
+ "config-skins-must-enable-default": "O tema escolhido como padrão deve ser ativado.",
+ "config-install-alreadydone": "'''Aviso:''' Parece que já instalou o MediaWiki e está a tentar instalá-lo novamente.\nPasse para a próxima página, por favor.",
+ "config-install-begin": "Ao clicar \"{{int:config-continue}}\", vai iniciar a instalação do MediaWiki.\nSe quiser fazer mais alterações, clique \"{{int:config-back}}\".",
+ "config-install-step-done": "terminado",
+ "config-install-step-failed": "falhou",
+ "config-install-extensions": "A incluir as extensões",
+ "config-install-database": "A preparar a base de dados",
+ "config-install-schema": "A criar o esquema (''schema'') da base de dados",
+ "config-install-pg-schema-not-exist": "O esquema ''(schema)'' PostgreSQL não existe",
+ "config-install-pg-schema-failed": "A criação das tabelas falhou.\nCertifique-se de que o utilizador \"$1\" pode escrever no esquema ''(schema)'' \"$2\".",
+ "config-install-pg-commit": "A gravar as alterações",
+ "config-install-pg-plpgsql": "A verificar a presença da linguagem PL/pgSQL",
+ "config-pg-no-plpgsql": "É preciso instalar a linguagem PL/pgSQL na base de dados $1",
+ "config-pg-no-create-privs": "A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.",
+ "config-pg-not-in-role": "A conta que especificou para o utilizador da internet já existe.\nA conta que especificou para a instalação não é a de um super-utilizador e não pertence ao grupo de utilizadores de acesso pela internet, por isso não pode criar objetos que pertencem ao utilizador da internet.\n\nO MediaWiki necessita que as tabelas pertençam ao utilizador da internet. Especifique outra conta de internet, ou clique \"voltar\" e especifique um utilizador com os privilégios necessários para a instalação.",
+ "config-install-user": "A criar o utilizador da base de dados",
+ "config-install-user-alreadyexists": "O utilizador \"$1\" já existe",
+ "config-install-user-create-failed": "A criação do utilizador \"$1\" falhou: $2",
+ "config-install-user-grant-failed": "A atribuição das permissões ao utilizador \"$1\" falhou: $2",
+ "config-install-user-missing": "O utilizador especificado, \"$1\", não existe.",
+ "config-install-user-missing-create": "O utilizador especificado, \"$1\", não existe.\nMarque a caixa de seleção \"criar conta\" abaixo se pretende criá-la, por favor.",
+ "config-install-tables": "A criar as tabelas",
+ "config-install-tables-exist": "'''Aviso''': As tabelas do MediaWiki parecem já existir.\nA criação das tabelas será saltada.",
+ "config-install-tables-failed": "'''Erro''': A criação das tabelas falhou com o seguinte erro: $1",
+ "config-install-interwiki": "A preencher a tabela padrão de interlínguas",
+ "config-install-interwiki-list": "Não foi possível encontrar o ficheiro <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Aviso''': A tabela de interwikis parece já conter entradas.\nO preenchimento padrão desta tabela será saltado.",
+ "config-install-stats": "A inicializar as estatísticas",
+ "config-install-keys": "A gerar as chaves secretas",
+ "config-insecure-keys": "'''Aviso:''' {{PLURAL:$2|A chave segura|As chaves seguras}} ($1) {{PLURAL:$2|gerada durante a instalação não é completamente segura|geradas durante a instalação não são completamente seguras}}. Considere a possibilidade de {{PLURAL:$2|alterá-la|alterá-las}} manualmente.",
+ "config-install-sysop": "A criar a conta de administrador",
+ "config-install-subscribe-fail": "Não foi possível subscrever a lista mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL não está instalado e <code>allow_url_fopen</code> não está disponível.",
+ "config-install-mainpage": "A criar a página principal com o conteúdo padrão.",
+ "config-install-extension-tables": "A criar as tabelas das extensões ativadas",
+ "config-install-mainpage-failed": "Não foi possível inserir a página principal: $1",
+ "config-install-done": "'''Parabéns!'''\nTerminou a instalação do MediaWiki.\n\nO instalador gerou um ficheiro <code>LocalSettings.php</code>.\nEste ficheiro contém todas as configurações.\n\nPrecisa de fazer o download do ficheiro e colocá-lo no diretório de raiz da sua instalação (o mesmo diretório onde está o ficheiro index.php). Este download deverá ter sido iniciado automaticamente.\n\nSe o download não foi iniciado, ou se o cancelou, pode recomeçá-lo clicando o link abaixo:\n\n$3\n\n'''Nota''': Se não fizer isto agora, o ficheiro que foi gerado deixará de estar disponível quando sair do processo de instalação.\n\nDepois de terminar o passo anterior, pode '''[$2 entrar na wiki]'''.",
+ "config-download-localsettings": "Descarga do <code>LocalSettings.php</code>",
+ "config-help": "ajuda",
+ "config-help-tooltip": "clique para expandir",
+ "config-nofile": "Não foi possível encontrar o ficheiro \"$1\". Terá sido apagado?",
+ "config-extension-link": "Sabia que a sua wiki suporta [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensões]?\n\nPode procurar [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensões por categoria].",
+ "mainpagetext": "'''MediaWiki instalado com sucesso.'''",
+ "mainpagedocfooter": "Consulte o [//meta.wikimedia.org/wiki/Help:Contents Guia de Utilizadores] para informações sobre o uso do software wiki.\n\n== Onde começar ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de opções de configuração]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Perguntas e respostas frequentes sobre o MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Subscreva a lista de divulgação de novas versões do MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Regionalize MediaWiki para seu idioma]"
+}
diff --git a/includes/installer/i18n/qqq.json b/includes/installer/i18n/qqq.json
new file mode 100644
index 00000000..772ce969
--- /dev/null
+++ b/includes/installer/i18n/qqq.json
@@ -0,0 +1,344 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amire80",
+ "Dani",
+ "EugeneZelenko",
+ "Kghbln",
+ "McDutchie",
+ "Mormegil",
+ "Nemo bis",
+ "Nike",
+ "Platonides",
+ "Purodha",
+ "Raymond",
+ "SPQRobin",
+ "Shirayuki",
+ "Siebrand",
+ "Umherirrender",
+ "Waldir",
+ "Jdforrester"
+ ]
+ },
+ "config-desc": "Short description of the installer.",
+ "config-title": "Parameters:\n* $1 is the version of MediaWiki that is being installed.",
+ "config-information": "{{Identical|Information}}",
+ "config-localsettings-upgrade": "{{doc-important|Do not translate <code>LocalSettings.php</code> and <code>$wgUpgradeKey</code>.}}",
+ "config-localsettings-cli-upgrade": "{{doc-important|Do not translate the <code>LocalSettings.php</code> and the <code>update.php</code> parts.}}",
+ "config-localsettings-key": "Label for the upgrade key that confirms a user upgrading through the web UI has access to LocalSettings.php. Details at https://www.mediawiki.org/wiki/Manual:Upgrading#Web_browser.",
+ "config-localsettings-badkey": "Error message when an incorrect upgrade key has been provided while trying to upgrade.",
+ "config-upgrade-key-missing": "Used in info box. Parameters:\n* $1 - the upgrade key, enclosed in <code><nowiki><pre></nowikI></code> tag.",
+ "config-localsettings-incomplete": "{{doc-important|Do not translate <code>LocalSettings.php</code> and <code><nowiki>{{int:Config-continue}}</nowiki><code>.}}\nParameters:\n* $1 - name of variable (any one of required variables or installer-specific global variables)",
+ "config-localsettings-connection-error": "{{doc-important|Do not translate <code>LocalSettings.php</code>.}}\nUsed as error message. Parameters:\n* $1 - (probably empty string)",
+ "config-session-error": "Parameters:\n* $1 is the error that was encountered with the session.",
+ "config-session-expired": "Parameters:\n* $1 is the configured session lifetime.",
+ "config-no-session": "{{doc-important|Do not translate <code>php.ini</code> and <code>session.save_path</code>.}}\nUsed as error message.",
+ "config-your-language": "Label in MediaWiki installer followed by a drop down where a user can select the language to use for the installer.",
+ "config-your-language-help": "Prompt for which language messages should be displayed in during the installation.",
+ "config-wiki-language": "Prompt for the language the wiki should be set to.",
+ "config-wiki-language-help": "Prompt for the language the wiki's content should be set to.",
+ "config-back": "{{Identical|Back}}",
+ "config-continue": "{{Identical|Continue}}",
+ "config-page-language": "{{Identical|Language}}",
+ "config-page-welcome": "Page header in MediaWiki installer.",
+ "config-page-dbconnect": "Page header in MediaWiki installer.",
+ "config-page-upgrade": "Page header in MediaWiki installer.",
+ "config-page-dbsettings": "Page header in MediaWiki installer.",
+ "config-page-name": "{{Identical|Name}}",
+ "config-page-options": "{{Identical|Options}}",
+ "config-page-install": "{{Identical|Install}}",
+ "config-page-complete": "{{Identical|Complete}}",
+ "config-page-restart": "Page header in MediaWiki installer.",
+ "config-page-readme": "Page header in MediaWiki installer.",
+ "config-page-releasenotes": "{{Identical|Release notes}}",
+ "config-page-copying": "This is a link to the full GPL text",
+ "config-page-upgradedoc": "Page header in MediaWiki installer.",
+ "config-page-existingwiki": "Page header in MediaWiki installer.",
+ "config-help-restart": "Message in warning box in MediaWiki installer.",
+ "config-restart": "Button text to confirm the installation procedure has to be restarted.",
+ "config-welcome": "Notice that the installer is about to check as to whether MediaWiki can be installed.",
+ "config-copyright": "This message follows {{msg-mw|config-env-good}}.\n\nParameters:\n* $1 - copyright and author list",
+ "config-sidebar": "Maximum width for words is 24 characters. Only visible part of the translation counts to this limit.",
+ "config-env-good": "See also:\n* {{msg-mw|Config-env-bad}}",
+ "config-env-bad": "See also:\n* {{msg-mw|Config-env-good}}",
+ "config-env-php": "Parameters:\n* $1 - the version of PHP that has been installed\nSee also:\n* {{msg-mw|config-env-php-toolow}}",
+ "config-env-hhvm": "Parameters:\n* $1 - the version of HHVM that has been installed",
+ "config-unicode-using-utf8": "Status message in the MediaWiki installer environment checks.",
+ "config-unicode-using-intl": "Status message in the MediaWiki installer environment checks.",
+ "config-unicode-pure-php-warning": "PECL is the name of a group producing standard pieces of software for PHP, and intl is the name of their library handling some aspects of internationalization.",
+ "config-unicode-update-warning": "ICU is a body producing standard software tools for support of Unicode and other internationalization aspects. This message warns the system administrator installing MediaWiki that the server's software is not up-to-date and MediaWiki will have problems handling some characters.",
+ "config-no-db": "{{doc-important|Do not translate \"<code>./configure --with-mysqli</code>\" and \"<code>php5-mysql</code>\".}}\nParameters:\n* $1 is comma separated list of database types supported by MediaWiki.",
+ "config-outdated-sqlite": "Used as warning. Parameters:\n* $1 - the version of SQLite that has been installed\n* $2 - minimum version",
+ "config-no-fts3": "A \"[[:wikipedia:Front and back ends|backend]]\" is a system or component that ordinary users don't interact with directly and don't need to know about, and that is responsible for a distinct task or service - for example, a storage back-end is a generic system for storing data which other applications can use. Possible alternatives for back-end are \"system\" or \"service\", or (depending on context and language) even leave it untranslated.",
+ "config-register-globals-error": "Error message in the MediaWiki installer environment checks.",
+ "config-magic-quotes-gpc": "{{Related|Config-fatal}}",
+ "config-magic-quotes-runtime": "{{Related|Config-fatal}}",
+ "config-magic-quotes-sybase": "{{Related|Config-fatal}}",
+ "config-mbstring": "{{Related|Config-fatal}}",
+ "config-safe-mode": "Status message in the MediaWiki installer environment checks.",
+ "config-xml-bad": "Status message in the MediaWiki installer environment checks.",
+ "config-pcre-old": "Parameters:\n* $1 - minimum PCRE version number\n* $2 - the installed version of [[wikipedia:PCRE|PCRE]]\n{{Related|Config-fatal}}",
+ "config-pcre-no-utf8": "PCRE is a name of a programmers' library for supporting regular expressions. It can probably be translated without change.\n{{Related|Config-fatal}}",
+ "config-memory-raised": "Parameters:\n* $1 is the configured <code>memory_limit</code>.\n* $2 is the value to which <code>memory_limit</code> was raised.",
+ "config-memory-bad": "Parameters:\n* $1 is the configured <code>memory_limit</code>.",
+ "config-ctype": "Message if support for [http://www.php.net/manual/en/ctype.installation.php Ctype] is missing from PHP.\n{{Related|Config-fatal}}",
+ "config-iconv": "Message if support for [http://www.php.net/manual/en/iconv.installation.php iconv] is missing from PHP.\n{{Related|Config-fatal}}",
+ "config-json": "Message if support for [[wikipedia:JSON|JSON]] is missing from PHP.\n* \"[[wikipedia:Red Hat Enterprise Linux|Red Hat Enterprise Linux]]\" (RHEL) and \"[[wikipedia:CentOS|CentOS]]\" refer to two almost-identical Linux distributions. \"5 and 6\" refers to version 5 or 6 of either distribution. Because RHEL 7 likely will not include the PHP extension, do not translate as \"5 or newer\".\n* \"The [http://www.php.net/json PHP extension]\" is the JSON extension included with PHP 5.2 and newer.\n* \"The [http://pecl.php.net/package/jsonc PECL extension]\" is based on the PHP extension, though excludes code some distributions have found unacceptable (see [[bugzilla:47431]]).\n{{Related|Config-fatal}}",
+ "config-xcache": "Message indicates if this program is available",
+ "config-apc": "Message indicates if this program is available",
+ "config-wincache": "Message indicates if this program is available",
+ "config-no-cache": "Status message in the MediaWiki installer environment checks.",
+ "config-mod-security": "Status message in the MediaWiki installer environment checks.",
+ "config-diff3-bad": "Status message in the MediaWiki installer environment checks.",
+ "config-git": "Message if Git version control software is available.\nParameter:\n* $1 is the <code>Git</code> executable file name.",
+ "config-git-bad": "Message if Git version control software is not found.",
+ "config-imagemagick": "$1 is ImageMagick's <code>convert</code> executable file name.\n\nAdd dir=\"ltr\" to the <nowiki><code></nowiki> for right-to-left languages.",
+ "config-gd": "Status message in the MediaWiki installer environment checks.",
+ "config-no-scaling": "Status message in the MediaWiki installer environment checks.",
+ "config-no-uri": "Status message in the MediaWiki installer environment checks.",
+ "config-no-cli-uri": "Parameters:\n* $1 is the default value for scriptpath.\n\nDo not translate <nowiki><code>--scriptpath</code></nowiki>",
+ "config-using-server": "Used as a part of environment check result. Parameters:\n* $1 - default server name",
+ "config-using-uri": "Used as a part of environment check result. Parameters:\n* $1 - server name\n* $2 - script path",
+ "config-uploads-not-safe": "Used as a part of environment check result. Parameters:\n* $1 - name of directory for images: <code>$IP/images/</code>",
+ "config-no-cli-uploads-check": "CLI = [[w:Command-line interface|command-line interface]] (i.e. the installer runs as a command-line script, not using HTML interface via an internet browser)",
+ "config-brokenlibxml": "Status message in the MediaWiki installer environment checks.",
+ "config-suhosin-max-value-length": "{{doc-important|Do not translate \"length\", \"suhosin.get.max_value_length\", \"php.ini\", \"$wgResourceLoaderMaxQueryLength\" and \"LocalSettings.php\".}}\nMessage shown when PHP parameter <code>suhosin.get.max_value_length</code> is between 0 and 1023 (that max value is hard set in MediaWiki software).",
+ "config-db-type": "Field label in the MediaWiki installer followed by possible database types.",
+ "config-db-host": "Used as label.\n\nAlso used in {{msg-mw|Config-missing-db-host}}.",
+ "config-db-host-help": "{{doc-singularthey}}",
+ "config-db-host-oracle": "TNS = [[w:Transparent Network Substrate]].\n\nUsed as label.\n\nAlso used in {{msg-mw|Config-missing-db-server-oracle}}.",
+ "config-db-host-oracle-help": "See also:\n* {{msg-mw|Config-invalid-db-server-oracle}}",
+ "config-db-wiki-settings": "This is more acurate: \"Enter identifying or distinguishing data for this wiki\" since a MySQL database can host tables of several wikis.",
+ "config-db-name": "Used as label.\n\nAlso used in {{msg-mw|Config-missing-db-name}}.\n{{Identical|Database name}}",
+ "config-db-name-help": "Help box text in the MediaWiki installer.",
+ "config-db-name-oracle": "Field label in the MediaWiki installer where an Oracle database schema can be specified.",
+ "config-db-account-oracle-warn": "A \"[[:wikipedia:Front and back ends|backend]]\" is a system or component that ordinary users don't interact with directly and don't need to know about, and that is responsible for a distinct task or service - for example, a storage back-end is a generic system for storing data which other applications can use. Possible alternatives for back-end are \"system\" or \"service\", or (depending on context and language) even leave it untranslated.",
+ "config-db-install-account": "Legend in the MediaWiki installer for the section where database username and password have to be provided.",
+ "config-db-username": "Used as label.\n\nAlso used in {{msg-mw|Config-db-username-empty}}.",
+ "config-db-password": "Field label in the MediaWiki installer where database password has to be provided.",
+ "config-db-password-empty": "Used as error message. Parameters:\n* $1 - database username",
+ "config-db-username-empty": "Used as error message. Shown when the database username is not entered by the user.\n\nRefers to {{msg-mw|Config-db-username}}.",
+ "config-db-install-username": "Help box text in the MediaWiki installer clarifying the requirement for database username.",
+ "config-db-install-password": "Help box text in the MediaWiki installer clarifying the requirement for database password.",
+ "config-db-install-help": "Help text in MediaWiki installer.",
+ "config-db-account-lock": "It might be easier to translate ''normal operation'' as \"also after the installation process\"",
+ "config-db-wiki-account": "Fieldset label for database user information.",
+ "config-db-wiki-help": "Help text for database user information.",
+ "config-db-prefix": "Field label for database prefix (a piece of text that all tables for a MediaWiki instance are prefixed with).",
+ "config-db-prefix-help": "Help text for database prefix form field.",
+ "config-db-charset": "Fieldset label for MySQL database character set choice.",
+ "config-charset-mysql5-binary": "Option for MySQL database character set choice.",
+ "config-charset-mysql5": "Option for MySQL database character set choice.",
+ "config-charset-mysql4": "Option for MySQL database character set choice.",
+ "config-charset-help": "Help text for database character set.",
+ "config-mysql-old": "Used as error message. Parameters:\n* $1 - minimum version\n* $2 - the version of MySQL that has been installed\n{{Related|Config-old}}",
+ "config-db-port": "Field label in MediaWiki installer for database port.",
+ "config-db-schema": "Field label in MediaWiki installer for database schema.",
+ "config-db-schema-help": "Help text in MediaWiki installer for database schema.",
+ "config-pg-test-error": "Parameters:\n* $1 - database name\n* $2 - error message",
+ "config-sqlite-dir": "Field label for a folder location.",
+ "config-sqlite-dir-help": "{{doc-important|Do not translate <code>.htaccess</code> and <code>/var/lib/mediawiki/yourwiki</code>.}}\nUsed in help box.",
+ "config-oracle-def-ts": "Field label for an Oracle default tablespace.",
+ "config-oracle-temp-ts": "Field label for an Oracle temporary tablespace.",
+ "config-type-mysql": "\"Or compatible\" refers to several database systems that are compatible with MySQL, as explained in {{msg-mw|config-dbsupport-mysql}}, and thus also work with this choice of database type.",
+ "config-type-postgres": "{{optional}}",
+ "config-type-sqlite": "{{optional}}",
+ "config-type-oracle": "{{optional}}",
+ "config-type-mssql": "{{optional}}",
+ "config-support-info": "Parameters:\n* $1 - a list of DBMSs that MediaWiki supports, composed with config-dbsupport-* messages.\nSee also:\n* {{msg-mw|Config-dbsupport-mysql}}\n* {{msg-mw|Config-dbsupport-postgres}}\n* {{msg-mw|Config-dbsupport-oracle}}\n* {{msg-mw|Config-dbsupport-sqlite}}\n* {{msg-mw|Config-dbsupport-mssql}}",
+ "config-dbsupport-mysql": "Used in:\n* {{msg-mw|config-support-info}}\n{{Related|Config-dbsupport}}",
+ "config-dbsupport-postgres": "Used in:\n* {{msg-mw|config-support-info}}\n{{Related|Config-dbsupport}}",
+ "config-dbsupport-sqlite": "Used in:\n* {{msg-mw|config-support-info}}\n{{Related|Config-dbsupport}}",
+ "config-dbsupport-oracle": "Used in:\n* {{msg-mw|Config-support-info}}.\n{{Related|Config-dbsupport}}",
+ "config-dbsupport-mssql": "Used in:\n* {{msg-mw|Config-support-info}}\n{{Related|Config-dbsupport}}",
+ "config-header-mysql": "Header for MySQL database settings in the MediaWiki installer.",
+ "config-header-postgres": "Header for PostgreSQL database settings in the MediaWiki installer.",
+ "config-header-sqlite": "Header for SQLite database settings in the MediaWiki installer.",
+ "config-header-oracle": "Header for Oracle database settings in the MediaWiki installer.",
+ "config-header-mssql": "Used as a section heading on the installer form, inside of a fieldset",
+ "config-invalid-db-type": "Error message in MediaWiki installer when an invalid database type has been provided.",
+ "config-missing-db-name": "Refers to {{msg-mw|Config-db-name}}.\n{{Related|Config-missing}}",
+ "config-missing-db-host": "Refers to {{msg-mw|Config-db-host}}.\n{{Related|Config-missing}}",
+ "config-missing-db-server-oracle": "Refers to {{msg-mw|Config-db-host-oracle}}.\n{{Related|Config-missing}}",
+ "config-invalid-db-server-oracle": "Used as error message. Parameters:\n* $1 - database server name\nSee also:\n* {{msg-mw|Config-db-host-oracle-help}}",
+ "config-invalid-db-name": "Used as error message. Parameters:\n* $1 - database name\nSee also:\n* {{msg-mw|Config-invalid-db-prefix}}",
+ "config-invalid-db-prefix": "Used as error message. Parameters:\n* $1 - database prefix\nSee also:\n* {{msg-mw|Config-invalid-db-name}}",
+ "config-connection-error": "$1 is the external error from the database, such as \"DB connection error: Access denied for user 'dba'@'localhost' (using password: YES) (localhost).\"\n\nIf you're translating this message to a right-to-left language, consider writing <nowiki><div dir=\"ltr\">$1.</div></nowiki>. (When the bidi features for HTML5 will be implemented in the browsers, it will probably be a good idea to write it as <nowiki><div dir=\"auto\">$1.</div></nowiki>.)",
+ "config-invalid-schema": "*$1 - schema name",
+ "config-db-sys-create-oracle": "Error message in the MediaWiki installer when Oracle is used as database and an incorrect user account type has been provided.",
+ "config-db-sys-user-exists-oracle": "Used as error message. Parameters:\n* $1 - database username",
+ "config-postgres-old": "Used as error message. Used as warning. Parameters:\n* $1 - minimum version\n* $2 - the version of PostgreSQL that has been installed\n{{Related|Config-old}}",
+ "config-mssql-old": "Used as an error message. Parameters:\n* $1 - minimum version\n* $2 - the version of Microsoft SQL Server that has been installed\n{{Related|Config-old}}",
+ "config-sqlite-name-help": "Help text for the form field for the SQLite data file name.",
+ "config-sqlite-parent-unwritable-group": "Used as SQLite error message. Parameters:\n* $1 - data directory\n* $2 - \"dirname\" part of $1\n* $3 - \"basename\" part of $1\n* $4 - web server's primary group name\nSee also:\n* {{msg-mw|Config-sqlite-parent-unwritable-nogroup}}",
+ "config-sqlite-parent-unwritable-nogroup": "Used as SQLite error message. Parameters:\n* $1 - data directory\n* $2 - \"dirname\" part of $1\n* $3 - \"basename\" part of $1\nSee also:\n* {{msg-mw|Config-sqlite-parent-unwritable-group}}",
+ "config-sqlite-mkdir-error": "Used as SQLite error message. Parameters:\n* $1 - data directory name",
+ "config-sqlite-dir-unwritable": "webserver refers to a software like Apache or Lighttpd.",
+ "config-sqlite-connection-error": "Used as SQLite error message. Parameters:\n* $1 - error message which SQLite server returned",
+ "config-sqlite-readonly": "Used as SQLite error message. Parameters:\n* $1 - filename",
+ "config-sqlite-cant-create-db": "Used as SQLite error message. Parameters:\n* $1 - filename",
+ "config-sqlite-fts3-downgrade": "Status message in the MediaWiki installer when SQLite is used without the FTS3 module. The FTS3 feature allows users to create special tables with a built-in full-text index.",
+ "config-can-upgrade": "Parameters:\n* $1 - Version or Revision indicator.",
+ "config-upgrade-done": "Used as success message. Parameters:\n* $1 - full URL of index.php\nSee also:\n* {{msg-mw|config-upgrade-done-no-regenerate}}",
+ "config-upgrade-done-no-regenerate": "Used as success message. Parameters:\n* $1 - full URL of index.php\nSee also:\n* {{msg-mw|config-upgrade-done}}",
+ "config-regenerate": "This message appears in a button after LocalSettings.php is generated and downloaded at the end of the MediaWiki installation process.",
+ "config-show-table-status": "{{doc-important|\"<code>SHOW TABLE STATUS</code>\" is a MySQL command. Do not translate this.}}",
+ "config-unknown-collation": "Warning messages in the MediaWiki installer for the database type MySQL when an unrecognised collation is used.",
+ "config-db-web-account": "Fieldset legend in MediaWiki installer",
+ "config-db-web-help": "Help text in MediaWiki installer.",
+ "config-db-web-account-same": "checkbox label",
+ "config-db-web-create": "checkbox label",
+ "config-db-web-no-create-privs": "Error message in the MediaWiki installer.",
+ "config-mysql-engine": "Field label for MySQL storage engine in the MediaWiki installer.",
+ "config-mysql-innodb": "Option for the MySQL storage engine in the MediaWiki installer.",
+ "config-mysql-myisam": "Option for the MySQL storage engine in the MediaWiki installer.",
+ "config-mysql-myisam-dep": "Warning message in the MediaWiki installer when MyISAM is chosen as MySQL storage engine.",
+ "config-mysql-only-myisam-dep": "Used as warning message when mysql does not support the minimum suggested feature set.",
+ "config-mysql-engine-help": "Help text in MediaWiki installer with advice for picking a MySQL storage engine.",
+ "config-mysql-charset": "Field label for the MySQL character set in the MediaWiki installer.",
+ "config-mysql-binary": "{{Identical|Binary}}",
+ "config-mysql-utf8": "Option for the MySQL character set in the MediaWiki installer.",
+ "config-mysql-charset-help": "Help text for the MySQL character set setting in the MediaWiki installer.",
+ "config-mssql-auth": "Radio button group label.\n\nFollowed by the following radio button labels:\n* {{msg-mw|Config-mssql-sqlauth}}\n* {{msg-mw|Config-mssql-windowsauth}}",
+ "config-mssql-install-auth": "Used as the help text for the \"Authentication type\" radio button when typing in database settings for installation.\n\nRefers to {{msg-mw|Config-mssql-windowsauth}}.\n\nSee also:\n* {{msg-mw|Config-mssql-web-auth}}",
+ "config-mssql-web-auth": "Used as the help text for the \"Authentication type\" radio button when typing in database settings for normal wiki usage.\n\nRefers to {{msg-mw|Config-mssql-windowsauth}}.\n\nSee also:\n* {{msg-mw|Config-mssql-install-auth}}",
+ "config-mssql-sqlauth": "Radio button.\n\n\"SQL Server\" refers to \"Microsoft SQL Server\".\n\nSee also:\n* {{msg-mw|Config-mssql-windowsauth}}",
+ "config-mssql-windowsauth": "Radio button. The official term is \"Integrated Windows Authentication\" but Microsoft itself uses \"Windows Authentication\" elsewhere in Microsoft SQL Server as a synonym.\n\nAlso used in:\n* {{msg-mw|Config-mssql-install-auth}}\n* {{msg-mw|Config-mssql-web-auth}}\n\nSee also:\n* {{msg-mw|Config-mssql-sqlauth}}",
+ "config-site-name": "Field label for the form field where a wiki name has to be entered.",
+ "config-site-name-help": "Help text for the form field where a wiki name has to be entered.",
+ "config-site-name-blank": "Error text in the MediaWiki installer when the site name is left empty.",
+ "config-project-namespace": "Field label for the form field where the name of the MediaWiki project namespace has to be entered.",
+ "config-ns-generic": "Used as label for \"namespace type\" radio button.\n\nSee also:\n* {{msg-mw|Config-ns-site-name}}\n* {{msg-mw|Config-ns-other}}\n{{Identical|Project}}",
+ "config-ns-site-name": "Used as label for \"namespace type\" radio button. Parameters:\n* $1 - wiki name\nSee also:\n* {{msg-mw|Config-ns-generic}}\n* {{msg-mw|Config-ns-other}}",
+ "config-ns-other": "Used as label for \"namespace type\" radio button.\n\nThis message is followed by the input box which enables to '''specify''' a namespace name.\n\nSee also:\n* {{msg-mw|Config-ns-site-name}}\n* {{msg-mw|Config-ns-generic}}",
+ "config-ns-other-default": "Default value for the option of a different project namespace name in the MediaWiki installer.",
+ "config-project-namespace-help": "Help text for the MediaWiki project namespace setting.",
+ "config-ns-invalid": "Used as error message. Parameters:\n* $1 - namespace name\nSee also:\n* {{msg-mw|Config-ns-conflict}}",
+ "config-ns-conflict": "Used as error message. Parameters:\n* $1 - namespace name\nSee also:\n* {{msg-mw|Config-ns-invalid}}",
+ "config-admin-box": "Fieldset label for settings for the MediaWiki administrator account that is created by the MediaWiki installer.",
+ "config-admin-name": "{{Identical|Your username}}",
+ "config-admin-password": "{{Identical|Password}}",
+ "config-admin-password-confirm": "{{Identical|Password again}}",
+ "config-admin-help": "Help text for the MediaWiki admin user creation form fields.",
+ "config-admin-name-blank": "Error message when no administrator username was provided.",
+ "config-admin-name-invalid": "Used as error message. Parameters:\n* $1 - username of administrator",
+ "config-admin-password-blank": "Error message when no administrator password was provided.",
+ "config-admin-password-mismatch": "Error message when no two equal administrator passwords were provided.",
+ "config-admin-email": "{{Identical|E-mail address}}",
+ "config-admin-email-help": "Help text for an administrator email address in the MediaWiki installer.",
+ "config-admin-error-user": "Used as error message. Parameters:\n* $1 - username of administrator\nSee also:\n* {{msg-mw|Config-admin-error-password}}",
+ "config-admin-error-password": "Used as error message. Parameters:\n* $1 - username of administrator\n* $2 - error message\nSee also:\n* {{msg-mw|Config-admin-error-user}}",
+ "config-admin-error-bademail": "Error text in the MediaWiki installer when an entered email address does not validate.",
+ "config-subscribe": "Used as label for the installer checkbox",
+ "config-subscribe-help": "\"Low-volume\" in this context means that there will be few e-mails to that mailing list per time period.",
+ "config-subscribe-noemail": "Error text in MediaWiki installer.",
+ "config-almost-done": "Status message in the MediaWiki installer.",
+ "config-optional-continue": "Option in the MediaWiki installer to make a more fine-tuned installation.",
+ "config-optional-skip": "Option in the MediaWiki installer to start executing the actual installation and stop asking questions.",
+ "config-profile": "Field label for the radio button list to pick a standard user rights profile.",
+ "config-profile-wiki": "Option for the radio button list to pick a standard user rights profile.",
+ "config-profile-no-anon": "Option for the radio button list to pick a standard user rights profile.",
+ "config-profile-fishbowl": "Option for the radio button list to pick a standard user rights profile.",
+ "config-profile-private": "Option for the radio button list to pick a standard user rights profile.",
+ "config-profile-help": "Messages referenced:\n* {{msg-mw|config-profile-wiki}}\n* {{msg-mw|config-profile-no-anon}}\n* {{msg-mw|config-profile-fishbowl}}\n* {{msg-mw|config-profile-private}}",
+ "config-license": "Setting for the wiki content license in the MediaWiki installer.",
+ "config-license-none": "Option for the wiki content license in the MediaWiki installer.",
+ "config-license-cc-by-sa": "Option for the wiki content license in the MediaWiki installer.",
+ "config-license-cc-by": "Option for the wiki content license in the MediaWiki installer.",
+ "config-license-cc-by-nc-sa": "Option for the wiki content license in the MediaWiki installer.",
+ "config-license-cc-0": "Option for the wiki content license in the MediaWiki installer.",
+ "config-license-gfdl": "Option for the wiki content license in the MediaWiki installer.",
+ "config-license-pd": "{{Identical|Public domain}}",
+ "config-license-cc-choose": "Option for the wiki content license in the MediaWiki installer.",
+ "config-license-help": "Help text in MediaWiki installer for license selection.\n\nRefers to {{msg-mw|Config-license-cc-by-sa}}.",
+ "config-email-settings": "{{Identical|E-mail setting}}",
+ "config-enable-email": "Checkbox label in the MediaWiki installer to allow the wiki to send email to its users.",
+ "config-enable-email-help": "Help text in the MediaWiki installer to allow the wiki to send email to its users.",
+ "config-email-user": "{{Identical|Enable user-to-user e-mail}}",
+ "config-email-user-help": "Label for user-to-user e-mailing option.",
+ "config-email-usertalk": "Label for user e-mailing notification option.",
+ "config-email-usertalk-help": "Description for user e-mailing notification option.",
+ "config-email-watchlist": "Label for user watchlist notification option.",
+ "config-email-watchlist-help": "Description for user watchlist notification option.",
+ "config-email-auth": "Label for user e-mail authentication requirement.",
+ "config-email-auth-help": "Description for user e-mail authentication requirement.",
+ "config-email-sender": "Prompt for the e-mail address from which the wiki's e-mails will be sent.",
+ "config-email-sender-help": "Explanation for the e-mail address from which the wiki's e-mails will be sent.",
+ "config-upload-settings": "Label for the file and image upload settings section.",
+ "config-upload-enable": "Label for the option to enable the file and image upload system.",
+ "config-upload-help": "The word \"mode\" here refers to the access rights given to various user groups when attempting to create and store files and/or subdiretories in the said directory on the server. It also refers to the <code>mode</code> command used to maipulate said right mask under Unix, Linux, and similar operating systems. A less operating-system-centric translation is fine.",
+ "config-upload-deleted": "Prompt for the server directory into which deleted files should be moved.",
+ "config-upload-deleted-help": "Explanation for {{msg|config-upload-deleted}}.",
+ "config-logo": "Prompt for a link to the logo to use for the wiki.",
+ "config-logo-help": "Help string shown to the user explaining the requirements for the wiki's logo.",
+ "config-instantcommons": "Used as label for the checkbox.\n\nThe help message for this checkbox is:\n* {{msg-mw|Config-instantcommons-help}}",
+ "config-instantcommons-help": "Used as help message for the checkbox which is labeled {{msg-mw|config-instantcommons}}.",
+ "config-cc-error": "Prompt to manually enter a license when the tool fails to match.",
+ "config-cc-again": "Prompt to re-try picking a Creative Commons license.",
+ "config-cc-not-chosen": "{{doc-important|Do not translate the \"<code>proceed</code>\" part.}}\nThis message refers to a block of HTML being embedded into the installer page. It comes from the Creative Commons Web site. The block is in the English language. It is a scripted license chooser. When an individual license has been selected, it asks you to click \"proceed\" so as to return to the MediaWiki installer page.",
+ "config-advanced-settings": "Label for the advanced configuration settings page.",
+ "config-cache-options": "Prompt for the object caching options.",
+ "config-cache-help": "Explanation for what object caching is, next to {{msg|config-cache-options}}.",
+ "config-cache-none": "Label for the object caching disabled option.",
+ "config-cache-accel": "Label for the object caching via PHP option.",
+ "config-cache-memcached": "{{doc-important|Do not translate \"memcached\".}}\nLabel for the object caching via memcached option.",
+ "config-memcached-servers": "{{doc-important|Do not translate \"memcached\".}}\n{{Identical|Memcached server}}",
+ "config-memcached-help": "Prompt for the object caching via Memcached option for the user to define server(s) to be used.",
+ "config-memcache-needservers": "Error message for the object caching via Memcached option when the user has failed to define servers at the above prompt.\n{{doc-important|Do not translate \"memcached\".}}",
+ "config-memcache-badip": "Used as error message. Parameters:\n* $1 - IP address for Memcached\nSee also:\n* {{msg-mw|Config-memcache-noport}}\n* {{msg-mw|Config-memcache-badport}}",
+ "config-memcache-noport": "Used as error message. Parameters:\n* $1 - Memcached server name\nSee also:\n* {{msg-mw|Config-memcache-badip}}\n* {{msg-mw|Config-memcache-badport}}\n{{doc-important|Do not translate \"memcached\".}}",
+ "config-memcache-badport": "Used as error message. Parameters:\n* $1 - 1 (hard-coded)\n* $2 - 65535 (hard-coded)\nSee also:\n* {{msg-mw|Config-memcache-badip}}\n* {{msg-mw|Config-memcache-noport}}\n{{doc-important|Do not translate \"memcached\".}}",
+ "config-extensions": "{{Identical|Extension}}",
+ "config-extensions-help": "{{doc-important|Do not translate <code>./extensions</code>.}}\nUsed in help box.",
+ "config-skins": "{{Identical|Skin}}",
+ "config-skins-help": "{{doc-important|Do not translate <code>./skins</code>.}}\nUsed in help box.",
+ "config-skins-use-as-default": "Label shown next to skin names.",
+ "config-skins-missing": "Warning message shown when there are no skins to install.",
+ "config-skins-must-enable-some": "Error message shown when the user does silly things.",
+ "config-skins-must-enable-default": "Error message shown when the user does silly things.",
+ "config-install-alreadydone": "Error message shown to users visiting the installer when the wiki appears to already be set up.",
+ "config-install-begin": "Prompt at the end of the initial configuration options screen before the wiki software is installed.",
+ "config-install-step-done": "{{Identical|Done}}",
+ "config-install-step-failed": "{{Identical|Failed}}",
+ "config-install-extensions": "Notice shown to the user during the install about progress when extensions are being installed.",
+ "config-install-database": "Message indicates the database is being set up\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-updates}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-schema": "*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-pg-schema-not-exist": "Error message shown to users picking PostgreSQL.",
+ "config-install-pg-schema-failed": "Parameters:\n* $1 = database user name (usernames in the database are unrelated to wiki user names)\n* $2 =",
+ "config-install-pg-commit": "Notice shown to the user during the install about progress with PostgreSQL.",
+ "config-install-pg-plpgsql": "Notice shown to users using PL/pgSQL installation.",
+ "config-pg-no-plpgsql": "Used as error message. Parameters:\n* $1 - database name",
+ "config-pg-no-create-privs": "Error shown to users using PL/pgSQL installation when the system account lacks the ability to install.",
+ "config-pg-not-in-role": "Error shown to users using PL/pgSQL installation when the system account lacks the ability to install.",
+ "config-install-user": "Message indicates that the user is being created\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-user-alreadyexists": "Used as warning. Parameters:\n* $1 - database username",
+ "config-install-user-create-failed": "Used as MySQL warning and as PostgreSQL error. Parameters:\n* $1 - database username\n* $2 - detailed warning/error message",
+ "config-install-user-grant-failed": "Parameters:\n* $1 is the database username for which granting rights failed\n* $2 is the error message",
+ "config-install-user-missing": "Used as PostgreSQL error message. Parameters:\n* $1 - database username\nSee also:\n* {{msg-mw|Config-install-user-missing-create}}",
+ "config-install-user-missing-create": "Used as PostgreSQL error message. Parameters:\n* $1 - database username\nSee also:\n* {{msg-mw|Config-install-user-missing}}",
+ "config-install-tables": "Message indicates that the tables are being created\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-updates}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-tables-exist": "Error notice during the installation saying that the database already seems set up for MediaWiki, so it's continuing without taking that step.",
+ "config-install-tables-failed": "Used as PostgreSQL error message. Parameters:\n* $1 - detailed error message",
+ "config-install-interwiki": "Message indicates that the interwikitables are being populated\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-interwiki-list": "{{doc-important|Do not translate <code>interwiki.list</code>.}}\nUsed as error message.",
+ "config-install-interwiki-exists": "Error notice during the installation saying that one of the database tables is already set up, so it's continuing without taking that step.",
+ "config-install-stats": "*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-keys": "*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-insecure-keys": "Parameters:\n* $1 - A list of names of the secret keys that were generated.\n* $2 - the number of items in the list $1, to be used with PLURAL.",
+ "config-install-updates": "Message indicating that the updatelog table is filled with keys of updates that won't be run when running database updates.\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-updates}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-updates-failed": "Used as error message. Parameters:\n* $1 - detailed error message",
+ "config-install-sysop": "Message indicates that the administrator user account is being created\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-subscribe-fail": "{{doc-important|\"[[m:mail:mediawiki-announce|mediawiki-announce]]\" is the name of a mailing list and should not be translated.}}\nA message displayed if the MediaWiki installer encounters an error making a request to lists.wikimedia.org which hosts the mailing list.\n* $1 - the HTTP error encountered, reproduced as is (English string)",
+ "config-install-subscribe-notpossible": "Error shown when automatically subscribing to the MediaWiki announcements mailing list fails.",
+ "config-install-mainpage": "*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
+ "config-install-extension-tables": "Notice shown to the user during the install about progress.",
+ "config-install-mainpage-failed": "Used as error message. Parameters:\n* $1 - detailed error message",
+ "config-install-done": "Parameters:\n* $1 is the URL to LocalSettings download\n* $2 is a link to the wiki.\n* $3 is a download link with attached download icon. The config-download-localsettings message will be used as the link text.",
+ "config-download-localsettings": "The link text used in the download link in config-install-done.",
+ "config-help": "This is used in help boxes.\n{{Identical|Help}}",
+ "config-help-tooltip": "Tooltip for the 'help' links ({{msg-mw|config-help}}), to make it clear they'll expand in place rather than open a new page",
+ "config-nofile": "Used as failure message. Parameters:\n* $1 - filename",
+ "config-extension-link": "Shown on last page of installation to inform about possible extensions.",
+ "mainpagetext": "Along with {{msg-mw|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.",
+ "mainpagedocfooter": "Along with {{msg-mw|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.\nThis might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example."
+}
diff --git a/includes/installer/i18n/qu.json b/includes/installer/i18n/qu.json
new file mode 100644
index 00000000..2cdf74d7
--- /dev/null
+++ b/includes/installer/i18n/qu.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "AlimanRuna"
+ ]
+ },
+ "config-desc": "MediaWiki tiyachiq",
+ "config-title": "MediaWiki $1 tiyachiy",
+ "config-information": "Willay",
+ "config-your-language": "Rimayniyki:",
+ "config-wiki-language": "Wiki rimay:",
+ "config-back": "← Ñawpaqman",
+ "config-extensions": "Mast'ariykuna",
+ "config-install-step-done": "rurasqañam",
+ "config-install-step-failed": "manam aypasqachu",
+ "config-help": "yanapay",
+ "config-nofile": "\"$1\" sutiyuq willañiqiqa manam tarisqachu. Qullusqachu?",
+ "mainpagetext": "'''MediaWiki nisqa llamp'u kaqqa aypaylla takyachisqañam.'''",
+ "mainpagedocfooter": "Wiki llamp'u kaqmanta willasunaykipaqqa [//meta.wikimedia.org/wiki/Help:Contents Ruraqpaq yanapana] ''(User's Guide)'' sutiyuq p'anqata qhaway.\n\n== Qallarichkaspa ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Kunphigurasyun churanamanta sutisuyu]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki nisqamanta pasaq tapuykuna]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki kachaykuy e-chaski sutisuyu]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki nisqata qampa rimaykiman t'ikray]"
+}
diff --git a/includes/installer/i18n/rgn.json b/includes/installer/i18n/rgn.json
new file mode 100644
index 00000000..0783bde6
--- /dev/null
+++ b/includes/installer/i18n/rgn.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''L'instalaziòn d'MediaWiki l'è andêda ben'''"
+}
diff --git a/includes/installer/i18n/rm.json b/includes/installer/i18n/rm.json
new file mode 100644
index 00000000..debfaf1b
--- /dev/null
+++ b/includes/installer/i18n/rm.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gion-andri"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki è vegnì installà cun success.'''",
+ "mainpagedocfooter": "Consultai il [//meta.wikimedia.org/wiki/Help:Contents manual per utilisaders] per infurmaziuns davart l'utilisaziun da questa software da wiki.\n\n== Cumenzar ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Glista da las opziuns per la configuraziun]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Glista da mail da MediaWiki cun annunzias da novas versiuns]"
+}
diff --git a/includes/installer/i18n/ro.json b/includes/installer/i18n/ro.json
new file mode 100644
index 00000000..77257344
--- /dev/null
+++ b/includes/installer/i18n/ro.json
@@ -0,0 +1,140 @@
+{
+ "@metadata": {
+ "authors": [
+ "Firilacroco",
+ "Minisarm",
+ "Stelistcristi",
+ "XXN"
+ ]
+ },
+ "config-desc": "Programul de instalare pentru MediaWiki",
+ "config-title": "Instalarea MediaWiki $1",
+ "config-information": "Informații",
+ "config-localsettings-key": "Cheie de actualizare:",
+ "config-localsettings-badkey": "Cheia furnizată este incorectă.",
+ "config-upgrade-key-missing": "S-a detectat o instalare existentă de MediaWiki.\nPentru a efectua un upgrade în cazul acestei instalări, vă rugăm să introduceți următorul rând în partea de jos a fișierului <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Fișierul <code>LocalSettings.php</code> deja existent pare a fi incomplet.\nVariabila $1 nu este definită.\nModificați fișierul <code>LocalSettings.php</code> astfel încât această variabilă să fie definită, după care apăsați pe „{{int:Config-continue}}”.",
+ "config-localsettings-connection-error": "A apărut o eroare în timpul conectării la baza de date utilizând setările specificate în <code>LocalSettings.php</code>. Vă rugăm să ajustați aceste setări și încercați din nou.\n\n$1",
+ "config-session-error": "Eroare la pornirea sesiunii: $1",
+ "config-session-expired": "Este posibil ca datele sesiunii dumnevoastră să fi expirat.\nSesiunile sunt configurate pentru o durată de viață de $1.\nO puteți mări configurând parametrul <code>session.gc_maxlifetime</code> din fișierul php.ini.\nReporniți procesul de instalare.",
+ "config-no-session": "Datele sesiunii dumneavoastră s-au pierdut!\nVerificați-vă fișierul php.ini și asigurați-vă că <code>session.save_path</code> conține calea către un director corespunzător.",
+ "config-your-language": "Limba ta:",
+ "config-your-language-help": "Alege o limbă pentru a o utiliza în timpul procesului de instalare.",
+ "config-wiki-language": "Limbă wiki:",
+ "config-wiki-language-help": "Alege limba în care wiki-ul va fi scris predominant.",
+ "config-back": "← Înapoi",
+ "config-continue": "Continuă →",
+ "config-page-language": "Limbă",
+ "config-page-welcome": "Bun venit la MediaWiki!",
+ "config-page-dbconnect": "Conectează la baza de date",
+ "config-page-upgrade": "Extinde instalarea existentă",
+ "config-page-dbsettings": "Setări ale bazei de date",
+ "config-page-name": "Nume",
+ "config-page-options": "Opţiuni",
+ "config-page-install": "Instalare",
+ "config-page-complete": "Finalizat!",
+ "config-page-restart": "Reporneşte instalarea",
+ "config-page-readme": "Citeşte-mă",
+ "config-page-releasenotes": "Note de lansare",
+ "config-page-copying": "Copiere",
+ "config-page-upgradedoc": "Actualizare",
+ "config-page-existingwiki": "Wiki existent",
+ "config-restart": "Da, repornește.",
+ "config-env-php": "PHP $1 este instalat.",
+ "config-env-php-toolow": "PHP $1 este instalat.\nTotuși, MediaWiki necesită PHP $2 sau mai nou.",
+ "config-db-type": "Tipul bazei de date:",
+ "config-db-host": "Gazdă bază de date:",
+ "config-db-host-oracle": "Baza de date TNS:",
+ "config-db-wiki-settings": "Identificați acest wiki",
+ "config-db-name": "Numele bazei de date:",
+ "config-db-name-oracle": "Schema bazei de date:",
+ "config-db-username": "Nume de utilizator pentru baza de date:",
+ "config-db-password": "Parola bazei de date:",
+ "config-db-prefix": "Prefixul tabelelor din baza de date:",
+ "config-db-charset": "Setul de caractere al bazei de date",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binar",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-db-port": "Portul bazei de date:",
+ "config-db-schema": "Schema pentru MediaWiki:",
+ "config-sqlite-dir": "Director de date SQLite:",
+ "config-oracle-def-ts": "Spațiu de stocare („tablespace”) implicit:",
+ "config-oracle-temp-ts": "Spațiu de stocare („tablespace”) temporar:",
+ "config-header-mysql": "Setările MySQL",
+ "config-header-postgres": "Setări PostgreSQL",
+ "config-header-sqlite": "Setări SQLite",
+ "config-header-oracle": "Setări Oracle",
+ "config-header-mssql": "Setări Microsoft SQL Server",
+ "config-invalid-db-type": "Tip de bază de date incorect",
+ "config-missing-db-name": "Trebuie să introduci o valoare pentru „Numele bazei de date”",
+ "config-connection-error": "$1.\n\nVerificați gazda, numele de utilizator și parola și reîncercați.",
+ "config-upgrade-done-no-regenerate": "Actualizare completă.\n\nAcum puteți [$1 începe să vă folosiți wikiul].",
+ "config-regenerate": "Regenerare LocalSettings.php →",
+ "config-unknown-collation": "AVERTISMENT: Baza de date folosește o colaționare nerecunoscută.",
+ "config-db-web-account": "Contul bazei de date pentru accesul web.",
+ "config-db-web-create": "Creați contul dacă nu există deja",
+ "config-mysql-engine": "Motor de stocare:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Setul de caractere al bazei de date:",
+ "config-mysql-binary": "Binar",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Numele wikiului:",
+ "config-site-name-blank": "Introduceți un nume pentru sit.",
+ "config-project-namespace": "Spațiul de nume al proiectului:",
+ "config-ns-generic": "Proiect",
+ "config-ns-site-name": "Același nume ca al wikiului: $1",
+ "config-ns-other": "Altul (specificați)",
+ "config-ns-other-default": "MyWiki",
+ "config-admin-box": "Cont de administrator",
+ "config-admin-name": "Numele dumneavoastră:",
+ "config-admin-password": "Parolă:",
+ "config-admin-password-confirm": "Parola, din nou:",
+ "config-admin-password-blank": "Introduceți o parolă pentru contul de administrator.",
+ "config-admin-password-mismatch": "Cele două parole introduse nu corespund.",
+ "config-admin-email": "Adresa de e-mail:",
+ "config-admin-error-bademail": "Ați introdus o adresă de e-mail incorectă.",
+ "config-almost-done": "Sunteți aproape gata!\nPuteți sări peste configurarea rămasă și să instalați wikiul chiar acum.",
+ "config-optional-continue": "Adresează-mi mai multe întrebări.",
+ "config-optional-skip": "Sunt deja plictisit, doar instalează wikiul.",
+ "config-profile": "Profilul drepturilor de utilizator:",
+ "config-profile-wiki": "Wiki tradițional",
+ "config-profile-no-anon": "Crearea de cont este necesară",
+ "config-profile-fishbowl": "Doar editorii autorizați",
+ "config-profile-private": "Wiki privat",
+ "config-license": "Drepturi de autor și licență:",
+ "config-license-none": "Fără licență în subsolul paginii",
+ "config-license-cc-by-sa": "Creative Commons Atribuire și distribuire în condiții identice",
+ "config-license-cc-by": "Creative Commons Atribuire",
+ "config-license-cc-by-nc-sa": "Creative Commons Atribuire, necomercial și distribuire în condiții identice",
+ "config-license-cc-0": "Creative Commons Zero (domeniu public)",
+ "config-license-gfdl": "Licența GNU pentru Documentare Liberă 1.3 sau ulterioară",
+ "config-license-pd": "Domeniu public",
+ "config-license-cc-choose": "Alegeți o licență Creative Commons personalizată",
+ "config-email-settings": "Setări pentru e-mail",
+ "config-email-usertalk": "Activați notificările pentru pagina de discuții a utilizatorului",
+ "config-upload-deleted": "Director pentru fișierele șterse:",
+ "config-logo": "Adresa URL a siglei:",
+ "config-cc-again": "Alegeți din nou...",
+ "config-advanced-settings": "Configurare avansată",
+ "config-cache-options": "Parametrii pentru stocarea temporară a obiectelor:",
+ "config-extensions": "Extensii",
+ "config-install-step-done": "realizat",
+ "config-install-step-failed": "eșuat",
+ "config-install-extensions": "Se includ extensiile",
+ "config-install-database": "Se creează baza de date",
+ "config-install-schema": "Se creează schema",
+ "config-install-pg-schema-not-exist": "Schema PostgreSQL nu există.",
+ "config-install-pg-commit": "Se validează modificările",
+ "config-install-user": "Se creează utilizatorul pentru baza de date",
+ "config-install-user-alreadyexists": "Utilizatorul „$1” există deja",
+ "config-install-user-create-failed": "Crearea utilizatorului „$1” a eșuat: $2",
+ "config-install-tables": "Se creează tabelele",
+ "config-install-stats": "Se inițializează statisticile",
+ "config-install-keys": "Se generează cheile secrete",
+ "config-install-sysop": "Se creează contul de administrator",
+ "config-install-mainpage-failed": "Nu s-a putut insera pagina principală: $1",
+ "config-download-localsettings": "Descarcă <code>LocalSettings.php</code>",
+ "config-help": "ajutor",
+ "mainpagetext": "'''Programul Wiki a fost instalat cu succes.'''",
+ "mainpagedocfooter": "Consultați [//meta.wikimedia.org/wiki/Help:Contents Ghidul utilizatorului (en)] pentru informații despre utilizarea software-ului wiki.\n\n== Primii pași ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista parametrilor configurabili (en)]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Întrebări frecvente despre MediaWiki (en)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discuții a MediaWiki (en)]"
+}
diff --git a/includes/installer/i18n/roa-tara.json b/includes/installer/i18n/roa-tara.json
new file mode 100644
index 00000000..82203375
--- /dev/null
+++ b/includes/installer/i18n/roa-tara.json
@@ -0,0 +1,56 @@
+{
+ "@metadata": {
+ "authors": [
+ "Joetaras"
+ ]
+ },
+ "config-desc": "'U 'nstallatore de MediaUicchi",
+ "config-title": "Installazzione de MediaUicchi $1",
+ "config-information": "'Mbormaziune",
+ "config-localsettings-key": "Chiave de aggiornamende:",
+ "config-localsettings-badkey": "'A chiave ca è date non g'è corrette.",
+ "config-session-error": "Errore facenne accumenzà 'a sessione: $1",
+ "config-your-language": "'A lènga toje:",
+ "config-your-language-help": "Scacchie 'na lènghe da ausà duranne 'u processe de installazzione:",
+ "config-wiki-language": "Lènga de Uicchi:",
+ "config-back": "← Rrète",
+ "config-continue": "Condinue →",
+ "config-page-language": "Lènghe",
+ "config-page-welcome": "Bovègne jndr'à MediaUicchi!",
+ "config-page-dbconnect": "Collegate a 'u database",
+ "config-page-upgrade": "Aggiorne l'installazzione esistende",
+ "config-page-dbsettings": "'Mbostaziune d'u database",
+ "config-page-name": "Nome",
+ "config-page-options": "Opziune",
+ "config-page-install": "Installe",
+ "config-page-complete": "Combletate!",
+ "config-page-restart": "Riavvie l'installazzione",
+ "config-page-readme": "Liggeme",
+ "config-page-releasenotes": "Note de rilasce",
+ "config-page-copying": "Stoche a copie",
+ "config-page-upgradedoc": "Aggiornamende",
+ "config-page-existingwiki": "Uicchi esistende",
+ "config-db-type": "Tipe de database:",
+ "config-db-charset": "'Nzieme de carattere d'u database",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 backwards-compatible UTF-8",
+ "config-db-port": "Porte d'u database:",
+ "config-db-schema": "Scheme pe MediaUicchi:",
+ "config-type-mysql": "MySQL",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-admin-email": "Indirizze e-mail:",
+ "config-install-step-done": "fatte",
+ "config-install-step-failed": "fallite",
+ "config-install-extensions": "'Ngludenne le estenziune",
+ "config-install-database": "Stoche a 'mboste l'archivije",
+ "config-install-schema": "Stoche a creje 'u scheme",
+ "config-install-pg-schema-not-exist": "'U scheme PostgreSQL non g'esiste.",
+ "config-help": "ajute",
+ "mainpagetext": "'''MediaUicchi ha state 'nstallete.'''",
+ "mainpagedocfooter": "Vè vide [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pe l'mbormaziune sus a cumme s'ause 'u softuer wiki.\n\n== Pe accumenzà ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste pe le configuraziune]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ de MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Elenghe d'a poste de MediaUicchi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localizzazzione de MediaUicchi pa lènga toje]"
+}
diff --git a/includes/installer/i18n/ru.json b/includes/installer/i18n/ru.json
new file mode 100644
index 00000000..b8a36bef
--- /dev/null
+++ b/includes/installer/i18n/ru.json
@@ -0,0 +1,344 @@
+{
+ "@metadata": {
+ "authors": [
+ "Adata80",
+ "DCamer",
+ "Eleferen",
+ "Express2000",
+ "KPu3uC B Poccuu",
+ "Kaganer",
+ "Krinkle",
+ "Lockal",
+ "MaxSem",
+ "Okras",
+ "Yuriy Apostol",
+ "Александр Сигачёв",
+ "Сrower",
+ "아라",
+ "Meshkov.a",
+ "Eroha",
+ "Seb35"
+ ]
+ },
+ "config-desc": "Инсталлятор MediaWiki",
+ "config-title": "Установка MediaWiki $1",
+ "config-information": "Информация",
+ "config-localsettings-upgrade": "Обнаружен файл <code>LocalSettings.php</code>.\nДля обновления этой установки, пожалуйста, введите значение <code>$wgUpgradeKey</code>.\nЕго можно найти в файле <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Обнаружен файл <code>LocalSettings.php</code>.\nДля обновления этой установки, пожалуйста, запустите <code>update.php</code>",
+ "config-localsettings-key": "Ключ обновления:",
+ "config-localsettings-badkey": "Вы указали неправильный ключ",
+ "config-upgrade-key-missing": "Обнаружена существующая установленная копия MediaWiki.\nЧтобы обновить обнаруженную установку, пожалуйста, добавьте следующую строку в конец вашего файла <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Похоже, что существующий файл <code>LocalSettings.php</code> не является полными.\nНе установлена переменная $1.\nПожалуйста, измените <code>LocalSettings.php</code> так, чтобы значение этой переменной было задано, затем нажмите «{{int:Config-continue}}».",
+ "config-localsettings-connection-error": "Произошла ошибка при подключении к базе данных с помощью настроек, указанных в <code>LocalSettings.php</code> или <code>AdminSettings.php</code>. Пожалуйста, исправьте эти настройки и повторите попытку.\n\n$1",
+ "config-session-error": "Ошибка при запуске сессии: $1",
+ "config-session-expired": "Ваша сессия истекла.\nСессии настроены на длительность $1.\nВы её можете увеличить, изменив <code>session.gc_maxlifetime</code> в php.ini.\nПерезапустите процесс установки.",
+ "config-no-session": "Данные сессии потеряны!\nПроверьте ваш php.ini и убедитесь, что <code>session.save_path</code> установлен в соответствующий каталог.",
+ "config-your-language": "Ваш язык:",
+ "config-your-language-help": "Выберите язык, на котором будет происходить процесс установки.",
+ "config-wiki-language": "Язык, который будет использовать вики:",
+ "config-wiki-language-help": "Выберите язык, на котором будут отображаться вики.",
+ "config-back": "← Назад",
+ "config-continue": "Далее →",
+ "config-page-language": "Язык",
+ "config-page-welcome": "Добро пожаловать в MediaWiki!",
+ "config-page-dbconnect": "Подключение к базе данных",
+ "config-page-upgrade": "Обновление существующей установки",
+ "config-page-dbsettings": "Настройки базы данных",
+ "config-page-name": "Название",
+ "config-page-options": "Настройки",
+ "config-page-install": "Установка",
+ "config-page-complete": "Готово!",
+ "config-page-restart": "Начать установку заново",
+ "config-page-readme": "Прочти меня",
+ "config-page-releasenotes": "Информация о версии",
+ "config-page-copying": "Лицензия",
+ "config-page-upgradedoc": "Обновление",
+ "config-page-existingwiki": "Существующая вики",
+ "config-help-restart": "Вы хотите удалить все сохранённые данные, которые вы ввели, и запустить процесс установки заново?",
+ "config-restart": "Да, начать заново",
+ "config-welcome": "=== Проверка окружения ===\nБудут проведены базовые проверки с целью определить, подходит ли данная система для установки MediaWiki.\nНе забудьте включить эту информацию, если вам потребуется помощь для завершения установки.",
+ "config-copyright": "=== Авторские права и условия ===\n\n$1\n\nMediaWiki является свободным программным обеспечением, которое вы можете распространять и/или изменять в соответствии с условиями лицензии GNU General Public License, опубликованной фондом свободного программного обеспечения; второй версии, либо любой более поздней версии.\n\nMediaWiki распространяется в надежде, что она будет полезной, но '''без каких-либо гарантий''', даже без подразумеваемых гарантий '''коммерческой ценности''' или '''пригодности для определённой цели'''. См. лицензию GNU General Public License для более подробной информации.\n\nВы должны были получить <doclink href=Copying>копию GNU General Public License</doclink> вместе с этой программой, если нет, то напишите Free Software Foundation, Inc., по адресу: 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA или [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html прочтите её онлайн].",
+ "config-sidebar": "* [//www.mediawiki.org Сайт MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/ru Справка для пользователей]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/ru Справка для администраторов]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/ru FAQ]\n----\n* <doclink href=Readme>Readme-файл</doclink>\n* <doclink href=ReleaseNotes>Информация о выпуске</doclink>\n* <doclink href=Copying>Лицензия</doclink>\n* <doclink href=UpgradeDoc>Обновление</doclink>",
+ "config-env-good": "Проверка внешней среды была успешно проведена.\nВы можете установить MediaWiki.",
+ "config-env-bad": "Была проведена проверка внешней среды.\nВы не можете установить MediaWiki.",
+ "config-env-php": "Установленная версия PHP: $1.",
+ "config-env-hhvm": "HHVM $1 установлена.",
+ "config-unicode-using-utf8": "Использовать Brion Vibber utf8_normalize.so для нормализации Юникода.",
+ "config-unicode-using-intl": "Будет использовано [http://pecl.php.net/intl расширение «intl» для PECL] для нормализации Юникода.",
+ "config-unicode-pure-php-warning": "'''Внимание!''': [http://pecl.php.net/intl расширение intl из PECL] недоступно для нормализации Юникода, будет использоваться медленная реализация на чистом PHP.\nЕсли ваш сайт работает под высокой нагрузкой, вам следует больше узнать о [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормализации Юникода].",
+ "config-unicode-update-warning": "'''Предупреждение''': установленная версия обёртки нормализации Юникода использует старую версию библиотеки [http://site.icu-project.org/ проекта ICU].\nВы должны [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations обновить версию], если хотите полноценно использовать Юникод.",
+ "config-no-db": "Не удалось найти подходящие драйвера баз данных! Вам необходимо установить драйвера базы данных для PHP.\nПоддерживаются следующие типы баз данных: $1.\nЕсли вы скомпилировали PHP сами, перенастройте его с включением клиента баз данных, например, с помощью <code>./configure --with-mysqli</code>.\nЕсли вы скомпилировали PHP сами, сконфигурируйте его снова с включенным клиентом базы данных, например, с помощью <code>./configure --with-mysql</code>.\nЕсли вы установили PHP из пакетов Debian или Ubuntu, то вам также необходимо установить, например, пакет <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Предупреждение''': у Вас установлен SQLite $1, версия которого ниже требуемой $2 . SQLite будет недоступен.",
+ "config-no-fts3": "'''Внимание''': SQLite собран без модуля [//sqlite.org/fts3.html FTS3] — поиск не будет работать для этой базы данных.",
+ "config-register-globals-error": "<strong>Ошибка: Параметр PHP <code>[http://php.net/register_globals register_globals]</code> включен.\nОн должен быть отключен для того, чтобы можно было продолжить установку.</strong>\nПолучить справку о том, как это сделать, можно по адресу [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals].",
+ "config-magic-quotes-gpc": "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc]!'''\nЭто приводит к непредсказуемой порче вводимых данных.\nУстановка и использование MediaWiki без выключения этой опции невозможно.",
+ "config-magic-quotes-runtime": "'''Проблема: включена опция PHP [http://www.php.net/manual/ru/function.magic-quotes-runtime.php magic_quotes_runtime]!'''\nЭто приводит к непредсказуемой порче вводимых данных.\nУстановка и использование MediaWiki без выключения этой опции невозможно.",
+ "config-magic-quotes-sybase": "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''\nЭто приводит к непредсказуемой порче вводимых данных.\nУстановка и использование MediaWiki без выключения этой опции невозможно.",
+ "config-mbstring": "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''\nЭто приводит к ошибкам и непредсказуемой порче вводимых данных.\nУстановка и использование MediaWiki без выключения этой опции невозможно.",
+ "config-safe-mode": "'''Предупреждение:''' PHP работает в [http://www.php.net/features.safe-mode «безопасном режиме»].\nЭто может привести к проблемам, особенно с загрузкой файлов и вставкой математических формул.",
+ "config-xml-bad": "XML-модуль РНР отсутствует.\nMediaWiki не будет работать в этой конфигурации, так как требуется функционал этого модуля.\nЕсли вы работаете в Mandrake, установите PHP XML-пакет.",
+ "config-pcre-old": "'''Фатальная ошибка:''' требуется PCRE версии $1 или более поздняя.\nВаш исполняемый файл PHP связан с PCRE версии $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Подробнее].",
+ "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-ctype": "'''Фатальная ошибка:''' PHP должен быть скомпилирован с поддержкой [http://www.php.net/manual/ru/ctype.installation.php расширения Ctype].",
+ "config-iconv": "<strong>Фатальная ошибка:</strong> PHP должен быть скомпилирован с поддержкой [http://www.php.net/manual/en/iconv.installation.php расширения iconv].",
+ "config-json": "'''Фатальная ошибка:''' PHP был скомпилирован без поддержка JSON.\nВам необходимо установить либо расширение PHP JSON, либо расширение [http://pecl.php.net/package/jsonc PECL jsonc] перед установкой MediaWiki.\n* PHP-расширение входит в состав Red Hat Enterprise Linux (CentOS) 5 и 6, хотя должна быть включено в <code>/etc/php.ini</code> или <code>/etc/php.d/json.ini</code>.\n* Некоторые дистрибутивы Linux, выпущенные после мая 2013 года, не включают расширение PHP, вместо того, чтобы упаковывать расширение PECL как <code>php5-json</code> или <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] установлен",
+ "config-apc": "[http://www.php.net/apc APC] установлен",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] установлен",
+ "config-no-cache": "'''Внимание:''' Не найдены [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].\nКэширование объектов будет отключено.",
+ "config-mod-security": "'''Внимание''': на вашем веб-сервере включен [http://modsecurity.org/ mod_security]. При неправильной настройке он может вызывать проблемы для MediaWiki или другого ПО, позволяющего пользователям отправлять на сервер произвольный текст.\nОбратитесь к [http://modsecurity.org/documentation/ документации mod_security] или в поддержку вашего хостера, если при работе возникают непонятные ошибки.",
+ "config-diff3-bad": "GNU diff3 не найден.",
+ "config-git": "Найдена система контроля версий Git: <code>$1</code>.",
+ "config-git-bad": "Программное обеспечение по управлению версиями Git не найдено.",
+ "config-imagemagick": "Обнаружен ImageMagick: <code>$1</code>.\nВозможно отображение миниатюр изображений, если вы разрешите закачки файлов.",
+ "config-gd": "Найдена встроенная графическая библиотека GD.\nВозможность использования миниатюр изображений будет включена, если вы включите их загрузку.",
+ "config-no-scaling": "Не удалось найти встроенную библиотеку GD или ImageMagick.\nВозможность использования миниатюр изображений будет отключена.",
+ "config-no-uri": "'''Ошибка:''' Не могу определить текущий URI.\nУстановка прервана.",
+ "config-no-cli-uri": "'''Предупреждение''': нет задан параметр <code>--scriptpath</code>, используется по умолчанию: <code>$1</code> .",
+ "config-using-server": "Используется имя сервера «<nowiki>$1</nowiki>».",
+ "config-using-uri": "Используется имя сервера \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Внимание:''' директория, используемая по умолчанию для загрузок (<code>$1</code>) уязвима к выполнению произвольных скриптов.\nХотя MediaWiki проверяет все загружаемые файлы на наличие угроз, настоятельно рекомендуется [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security закрыть данную уязвимость] перед включением загрузки файлов.",
+ "config-no-cli-uploads-check": "'''Предупреждение:''' каталог для загрузки по умолчанию ( <code>$1</code> ) не проверялся на уязвимости\n на выполнение произвольного сценария во время установки CLI.",
+ "config-brokenlibxml": "В вашей системе имеется сочетание версий PHP и libxml2, которое может привести к скрытым повреждениям данных в MediaWiki и других веб-приложениях.\nОбновите libxml2 до версии 2.7.3 или старше ([https://bugs.php.net/bug.php?id=45996 сведения об ошибке]).\nУстановка прервана.",
+ "config-suhosin-max-value-length": "Suhosin установлен и ограничивает параметр GET <code>length</code> до $1 байт. Компонент MediaWiki ResourceLoader будет обходить это ограничение, но это снизит производительность. Если это возможно, следует установить <code>suhosin.get.max_value_length</code> в значение 1024 или выше в <code>php.ini</code>, а также установить для <code>$wgResourceLoaderMaxQueryLength</code> такое же значение в LocalSettings.php.",
+ "config-db-type": "Тип базы данных:",
+ "config-db-host": "Хост базы данных:",
+ "config-db-host-help": "Если сервер базы данных находится на другом сервере, введите здесь его имя хоста или IP-адрес.\n\nЕсли вы используете виртуальный хостинг, ваш провайдер должен указать правильное имя хоста в своей документации.\n\nЕсли вы устанавливаете систему на сервере под Windows и используете MySQL, имя сервера «localhost» может не работать. В этом случае попробуйте указать 127.0.0.1 локальный IP-адрес.\n\nЕсли вы используете PostgreSQL, оставьте это поле пустым для подключения через сокет Unix.",
+ "config-db-host-oracle": "TNS базы данных:",
+ "config-db-host-oracle-help": "Введите действительный [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; файл tnsnames.ora должен быть видимым для этой инсталляции. <br />При использовании клиентских библиотек версии 10g и старше также возможно использовать метод именования [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Идентификация этой вики",
+ "config-db-name": "Имя базы данных:",
+ "config-db-name-help": "Выберите название-идентификатор для вашей вики.\nОно не должно содержать пробелов.\n\nЕсли вы используете виртуальный хостинг, провайдер или выдаст вам конкретное имя базы данных, или позволит создавать базы данных с помощью панели управления.",
+ "config-db-name-oracle": "Схема базы данных:",
+ "config-db-account-oracle-warn": "Поддерживаются три сценария установки Oracle в качестве базы данных:\n\nЕсли вы хотите создать учётную запись базы данных в процессе установки, пожалуйста, укажите учётную запись роли SYSDBA для установки и укажите желаемые полномочия учётной записи с веб-доступом. вы также можете учётную запись с веб-доступом вручную и указать только её (если у неё есть необходимые разрешения на создание объектов схемы) или указать две учётные записи, одну с правами создания объектов, а другую с ограничениями для веб-доступа.\n\nСценарий для создания учётной записи с необходимыми привилегиями можно найти в папке «maintenance/oracle/» этой программы установки. Имейте в виду, что использование ограниченной учётной записи приведёт к отключению всех возможностей обслуживания с учётной записи по умолчанию.",
+ "config-db-install-account": "Учётная запись для установки",
+ "config-db-username": "Имя пользователя базы данных:",
+ "config-db-password": "Пароль базы данных:",
+ "config-db-password-empty": "Пожалуйста, введите пароль для нового пользователя базы данных «$1».\nХотя и возможно создание пользователей без паролей, это небезопасно.",
+ "config-db-username-empty": "Вы должны ввести значение параметра «{{int:config-db-username}}».",
+ "config-db-install-username": "Введите имя пользователя, которое будет использоваться для подключения к базе данных в процессе установки.\nЭто не имя пользователя MediaWiki, это имя пользователя для базы данных.",
+ "config-db-install-password": "Введите пароль, который будет использоваться для подключения к базе данных в процессе установки.\nЭто не пароль пользователя MediaWiki, это пароль для базы данных.",
+ "config-db-install-help": "Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время процесса установки.",
+ "config-db-account-lock": "Использовать то же имя пользователя и пароль для обычной работы",
+ "config-db-wiki-account": "Учётная запись для обычной работы",
+ "config-db-wiki-help": "Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время обычной работы вики.\nЕсли такой учётной записи не существует, а установочная учётная запись имеет достаточно привилегий, то обычная учётная запись будет создана с минимально необходимыми для работы вики привилегиями.",
+ "config-db-prefix": "Префикс таблиц базы данных:",
+ "config-db-prefix-help": "Если вам нужно делить одну базу данных между несколькими вики, или между MediaWiki и другими веб-приложениями, вы можете добавить префикс для всех имён таблиц.\nНе используйте пробелы.\n\nЭто поле обычно остаётся пустым.",
+ "config-db-charset": "Кодировка базы данных",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 бинарная",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 обратно совместимая с UTF-8",
+ "config-charset-help": "'''Внимание.''' Если вы используете '''обратно совместый UTF-8''' на MySQL 4.1+ и создаёте резервные копии базы данных с помощью <code>mysqldump</code>, то все не-ASCII символы могут быть искажены, а резервная копия окажется негодной!\n\nВ '''бинарном режиме''' MediaWiki хранит юникодный текст в базе в виде двоичных полей.\nЭто более эффективно, чем MySQL в режиме UTF-8, позволяет использовать полный набор символов Юникода.\nВ '''режиме UTF-8''' MySQL будет знать к какому набору символу относятся ваши данные, сможет представлять и преобразовать их надлежащим образом (буква Ё окажется при сортировке после буквы Е, а не после буквы Я, как в бинарном режиме),\nно не позволит вам сохранять символы, выходящие за пределы [//ru.wikipedia.org/wiki/Символы,_представленные_в_Юникоде#.D0.91.D0.B0.D0.B7.D0.BE.D0.B2.D0.B0.D1.8F_.D0.BC.D0.BD.D0.BE.D0.B3.D0.BE.D1.8F.D0.B7.D1.8B.D0.BA.D0.BE.D0.B2.D0.B0.D1.8F_.D0.BF.D0.BB.D0.BE.D1.81.D0.BA.D0.BE.D1.81.D1.82.D1.8C BMP].",
+ "config-mysql-old": "Необходим MySQL $1 или более поздняя версия. У вас установлен MySQL $2.",
+ "config-db-port": "Порт базы данных:",
+ "config-db-schema": "Схема для MediaWiki",
+ "config-db-schema-help": "Эта схема обычно работает хорошо.\nИзменяйте её только если знаете, что Вам это нужно.",
+ "config-pg-test-error": "Не удаётся подключиться к базе данных <strong>$1</strong>: $2",
+ "config-sqlite-dir": "Директория данных SQLite:",
+ "config-sqlite-dir-help": "SQLite хранит все данные в одном файле.\n\nДиректория, указываемая вами, должна быть доступна для записи веб-сервером во время установки.\n\nОна '''не должна''' быть доступна через Интернет, поэтому не должна совпадать с той, где хранятся PHP файлы.\n\nУстановщик запишет в эту директорию файл <code>.htaccess</code>, но если это не сработает, кто-нибудь может получить доступ ко всей базе данных.\nВ этой базе находится в том числе и информация о пользователях (адреса электронной почты, хэши паролей), а также удалённые страницы и другие секретные данные о вики.\n\nПо возможности, расположите базу данных где-нибудь в стороне, например, в <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Пространство таблиц по умолчанию:",
+ "config-oracle-temp-ts": "Временное пространство таблиц:",
+ "config-type-mysql": "MySQL (или совместимая)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "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], которые являются MySQL-совместимым. ([http://www.php.net/manual/ru/mysql.installation.php инструкция, как собрать PHP с поддержкой MySQL])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] — популярная открытая СУБД, альтернатива MySQL\nМогут встречаться небольшие неисправленные ошибки, не рекомендуется для использования в рабочей системе. ([http://www.php.net/manual/ru/pgsql.installation.php инструкция, как собрать 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 Как собрать PHP с поддержкой OCI8])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — это коммерческое база данных база данных для Windows масштаба предприятия. ([http://www.php.net/manual/ru/sqlsrv.installation.php Как собрать PHP с поддержкой SQLSRV])",
+ "config-header-mysql": "Настройки MySQL",
+ "config-header-postgres": "Настройки PostgreSQL",
+ "config-header-sqlite": "Настройки SQLite",
+ "config-header-oracle": "Настройки Oracle",
+ "config-header-mssql": "Параметры Microsoft SQL Server",
+ "config-invalid-db-type": "Неверный тип базы данных",
+ "config-missing-db-name": "Вы должны ввести значение «{{int:config-db-name}}».",
+ "config-missing-db-host": "Необходимо ввести значение параметра «{{int:config-db-host}}».",
+ "config-missing-db-server-oracle": "Вы должны заполнить поле «{{int:config-db-host-oracle}}»",
+ "config-invalid-db-server-oracle": "Неверное TNS базы данных «$1».\nИспользуйте либо «TNS Name», либо строку «Easy Connect» ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методы наименования Oracle])",
+ "config-invalid-db-name": "Неверное имя базы данных «$1».\nИспользуйте только ASCII-символы (a-z, A-Z), цифры (0-9), знак подчёркивания (_) и дефис(-).",
+ "config-invalid-db-prefix": "Неверный префикс базы данных «$1».\nИспользуйте только буквы ASCII (a-z, A-Z), цифры (0-9), знак подчёркивания (_) и дефис (-).",
+ "config-connection-error": "$1.\n\nПроверьте хост, имя пользователя и пароль и попробуйте ещё раз.",
+ "config-invalid-schema": "Неправильная схема для MediaWiki «$1».\nИспользуйте только ASCII символы (a-z, A-Z), цифры(0-9) и знаки подчёркивания(_).",
+ "config-db-sys-create-oracle": "Программа установки поддерживает только использование SYSDBA для создания новой учётной записи.",
+ "config-db-sys-user-exists-oracle": "Учётная запись «$1». SYSDBA может использоваться только для создания новой учётной записи!",
+ "config-postgres-old": "Необходим PostgreSQL $1 или более поздняя версия. У вас установлен PostgreSQL $2.",
+ "config-mssql-old": "Требуется Microsoft SQL Server версии $1 или более поздней. У вас установлена версия $2.",
+ "config-sqlite-name-help": "Выберите имя-идентификатор для вашей вики.\nНе используйте дефисы и пробелы.\nЭта строка будет использоваться в имени файла SQLite.",
+ "config-sqlite-parent-unwritable-group": "Не удалось создать директорию данных <nowiki><code>$1</code></nowiki>, так как у веб-сервера нет прав записи в родительскую директорию <nowiki><code>$2</code></nowiki>.\n\nУстановщик определил пользователя, под которым работает веб-сервер.\nСделайте директорию <nowiki><code>$3</code></nowiki> доступной для записи и продолжите.\nВ Unix/Linux системе выполните:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Не удалось создать директорию для данных <code><nowiki>$1</nowiki></code>, так как у веб-сервера нет прав на запись в родительскую директорию <code><nowiki>$2</nowiki></code>.\n\nПрограмма установки не смогла определить пользователя, под которым работает веб-сервер.\nДля продолжения сделайте каталог <code><nowiki>$3</nowiki></code> глобально доступным для записи серверу (и другим).\nВ Unix/Linux сделайте:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Ошибка при создании директории для данных «$1».\nПроверьте расположение и повторите попытку.",
+ "config-sqlite-dir-unwritable": "Невозможно произвести запись в каталог «$1».\nИзмените настройки доступа так, чтобы веб-сервер мог записывать в этот каталог, и попробуйте ещё раз.",
+ "config-sqlite-connection-error": "$1.\n\nПроверьте название базы данных и директорию с данными и попробуйте ещё раз.",
+ "config-sqlite-readonly": "Файл <code>$1</code> недоступен для записи.",
+ "config-sqlite-cant-create-db": "Не удаётся создать файл базы данных <code>$1</code> .",
+ "config-sqlite-fts3-downgrade": "У PHP отсутствует поддержка FTS3 — сбрасываем таблицы",
+ "config-can-upgrade": "В базе данных найдены таблицы MediaWiki.\nЧтобы обновить их до MediaWiki $1, нажмите на кнопку '''«Продолжить»'''.",
+ "config-upgrade-done": "Обновление завершено.\n\nТеперь вы можете [$1 начать использовать вики].\n\nЕсли вы хотите повторно создать файл <code>LocalSettings.php</code>, нажмите на кнопку ниже.\nЭто действие '''не рекомендуется''', если у вас не возникло проблем при установке.",
+ "config-upgrade-done-no-regenerate": "Обновление завершено.\n\nТеперь вы можете [$1 начать работу с вики].",
+ "config-regenerate": "Создать LocalSettings.php заново →",
+ "config-show-table-status": "Запрос «<code>SHOW TABLE STATUS</code>» не выполнен!",
+ "config-unknown-collation": "'''Внимание:''' База данных использует нераспознанные правила сортировки.",
+ "config-db-web-account": "Учётная запись для доступа к базе данных из веб-сервера",
+ "config-db-web-help": "Выберите имя пользователя и пароль, которые веб-сервер будет использовать для подключения к серверу базы данных при обычной работе вики.",
+ "config-db-web-account-same": "Использовать ту же учётную запись, что и для установки",
+ "config-db-web-create": "Создать учётную запись, если она ещё не существует",
+ "config-db-web-no-create-privs": "Учётная запись, указанная вами для установки, не обладает достаточными правами для создания учётной записи.\nУказанная здесь учётная запись уже должна существовать.",
+ "config-mysql-engine": "Движок базы данных:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "''' Внимание.''' Вы выбрали механизм MyISAM для хранения данных MySQL. Он не рекомендуется к использованию по следующим причинам:\n* он слабо поддерживает параллелизм из-за табличных блокировок;\n* более склонен к потере данных, по сравнению с другими механизмами;\n* код MediaWiki не всегда учитывает особенности MyISAM должным образом.\n\nЕсли ваша установка MySQL поддерживает InnoDB, настоятельно рекомендуется выбрать этот механизм.\nЕсли ваша установка MySQL не поддерживает InnoDB, возможно, настало время обновиться.",
+ "config-mysql-only-myisam-dep": "'''Предупреждение:''' MyISAM является единственной доступной системой хранения данных для MySQL на этом компьютере, и она не рекомендуется для использования с MediaWiki, потому что:\n * он слабо поддерживает параллелизм из-за блокировки таблиц\n * она больше других систем подвержена повреждению\n * кодовая база MediaWiki не всегда обрабатывает MyISAM так, как следует\n\nВаша MySQL не поддерживает InnoDB, так что, возможно, настало время для обновления.",
+ "config-mysql-engine-help": "'''InnoDB''' почти всегда предпочтительнее, так как он лучше справляется с параллельным доступом.\n\n'''MyISAM''' может оказаться быстрее для вики с одним пользователем или с минимальным количеством поступающих правок, однако базы данных на нём портятся чаще, чем на InnoDB.",
+ "config-mysql-charset": "Кодировка базы данных:",
+ "config-mysql-binary": "Двоичный",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "В '''двоичном режиме''' MediaWiki хранит UTF-8 текст в бинарных полях базы данных.\nЭто более эффективно, чем ''UTF-8 режим'' MySQL, и позволяет использовать полный набор символов Unicode.\n\nВ '''режиме UTF-8''' MySQL будет знать в какой кодировке находятся Ваши данные и может отображать и преобразовывать их соответствующим образом, но это не позволит вам хранить символы выше [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базовой Многоязыковой Плоскости].",
+ "config-mssql-auth": "Тип аутентификации:",
+ "config-mssql-install-auth": "Выберите тип проверки подлинности, который будет использоваться для подключения к базе данных во время процесса установки.\nЕсли вы выберите «{{int:config-mssql-windowsauth}}», будут использоваться учётные данные пользователя, под которым работает веб-сервер.",
+ "config-mssql-web-auth": "Выберите тип проверки подлинности, который веб-сервер будет использовать для подключения к серверу базы данных во время обычного функционирования вики.\nЕсли вы выберите «{{int:config-mssql-windowsauth}}», будут использоваться учётные данные пользователя, под которым работает веб-сервер.",
+ "config-mssql-sqlauth": "Проверка подлинности SQL Server",
+ "config-mssql-windowsauth": "Проверка подлинности Windows",
+ "config-site-name": "Название вики:",
+ "config-site-name-help": "Название будет отображаться в заголовке окна браузера и в некоторых других местах вики.",
+ "config-site-name-blank": "Введите название сайта.",
+ "config-project-namespace": "Пространство имён проекта:",
+ "config-ns-generic": "Проект",
+ "config-ns-site-name": "То же, что имя вики: $1",
+ "config-ns-other": "Другое (укажите)",
+ "config-ns-other-default": "MyWiki",
+ "config-project-namespace-help": "Следуя примеру Википедии, многие вики хранят свои страницы правил отдельно от страниц основного содержания, в так называемом '''«пространстве имён проекта»'''.\nВсе названия страниц в этом пространстве имён начинается с определённого префикса, который вы можете задать здесь.\nОбычно, этот префикс происходит от имени вики, но он не может содержать знаки препинания, символы «#» или «:».",
+ "config-ns-invalid": "Указанное пространство имён <nowiki>$1</nowiki> недопустимо.\nУкажите другое пространство имён проекта.",
+ "config-ns-conflict": "Указанное пространство имён «<nowiki>$1</nowiki>» конфликтует со стандартным пространством имён MediaWiki.\nУкажите другое пространство имён проекта.",
+ "config-admin-box": "Учётная запись администратора",
+ "config-admin-name": "Ваше имя участника:",
+ "config-admin-password": "Пароль:",
+ "config-admin-password-confirm": "Пароль ещё раз:",
+ "config-admin-help": "Введите ваше имя пользователя здесь, например, «Иван Иванов».\nЭто имя будет использоваться для входа в вики.",
+ "config-admin-name-blank": "Введите имя пользователя администратора.",
+ "config-admin-name-invalid": "Указанное имя пользователя «<nowiki>$1</nowiki>» недопустимо.\nУкажите другое имя пользователя.",
+ "config-admin-password-blank": "Введите пароль для учётной записи администратора.",
+ "config-admin-password-mismatch": "Введённые вами пароли не совпадают.",
+ "config-admin-email": "Адрес электронной почты:",
+ "config-admin-email-help": "Введите адрес электронной почты, чтобы получать сообщения от других пользователей вики, иметь возможность восстановить пароль, а также получать уведомления об изменениях страниц из списка наблюдения. Вы можете оставить это поле пустым.",
+ "config-admin-error-user": "Внутренняя ошибка при создании учётной записи администратора с именем «<nowiki>$1</nowiki>».",
+ "config-admin-error-password": "Внутренняя ошибка при установке пароля для учётной записи администратора «<nowiki>$1</nowiki>»: <pre>$2</pre>",
+ "config-admin-error-bademail": "Вы ввели неправильный адрес электронной почты",
+ "config-subscribe": "Подписаться на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce рассылку новостей о появлении новых версий MediaWiki].",
+ "config-subscribe-help": "Это список рассылки с малым числом сообщений, используется для анонса новых выпусков и сообщений о проблемах с безопасностью.\nВам следует подписаться на него и обновлять движок MediaWiki, по мере выхода новых версий.",
+ "config-subscribe-noemail": "Вы попытались подписаться на список рассылки уведомлений о новых выпусках без указания адреса электронной почты.\nУкажите адрес электронной почты, если вы хотите подписаться на список рассылки.",
+ "config-almost-done": "Вы почти у цели!\nОстальные настройки можно пропустить и приступить к установке вики.",
+ "config-optional-continue": "Произвести тонкую настройку",
+ "config-optional-skip": "Хватит, установить вики",
+ "config-profile": "Профиль прав прользователей:",
+ "config-profile-wiki": "Открытая вики",
+ "config-profile-no-anon": "Требуется создание учётной записи",
+ "config-profile-fishbowl": "Только для авторизованных редакторов",
+ "config-profile-private": "Закрытая вики",
+ "config-profile-help": "Вики-технология лучше всего работает, когда вы позволяете редактировать сайт максимально широкому кругу лиц.\nВ MediaWiki легко просмотреть последних изменений и, при необходимости, откатить любой ущерб сделанный злоумышленниками или наивными пользователями.\n\nОднако, движок MediaWiki можно использовать и иными способами, и не далеко не всех удаётся убедить в преимуществах открытой вики-работы.\nТак что в вас есть выбор.\n\nМодель '''«{{int:config-profile-wiki}}»''' позволяет всем править страницы даже не регистрируясь на сайте. Конфигурация '''{{int:config-profile-no-anon}}''' обеспечивает дополнительный учёт, но может отсечь случайных участников.\n\nСценарий '''«{{int:config-profile-fishbowl}}»''' разрешает редактирование только определённым участникам, но общедоступным остаётся просмотр страниц, в том числе просмотр истории изменения. В режиме '''«{{int:config-profile-private}}»''' просмотр страниц разрешён только определённым пользователям, какая-то их часть может иметь также права на редактирование.\n\nБолее сложные схемы разграничения прав можно настроить после установки, см. [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights соответствующее руководство].",
+ "config-license": "Авторские права и лицензии:",
+ "config-license-none": "Не указывать лицензию в колонтитуле внизу страницы",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (общественное достояние)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 или более поздняя",
+ "config-license-pd": "Общественное достояние",
+ "config-license-cc-choose": "Выберите одну из лицензий Creative Commons",
+ "config-license-help": "Многие общедоступные вики разрешают использовать свои материалы на условиях [http://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": "Если вы хотите, чтобы электронная почта работала, необходимо выполнить [http://www.php.net/manual/ru/mail.configuration.php соответствующие настройки PHP].\nЕсли вы не хотите использовать возможности электронной почты в вики, вы можете её отключить.",
+ "config-email-user": "Включить электронную почту от участника к участнику",
+ "config-email-user-help": "Разрешить всем пользователям отправлять друг другу электронные письма, если выставлена соответствующая настройка в профиле.",
+ "config-email-usertalk": "Включить уведомления пользователей о сообщениях на их странице обсуждения",
+ "config-email-usertalk-help": "Разрешить пользователям получать уведомления об изменениях своих страниц обсуждения, если они разрешат это в своих настройках.",
+ "config-email-watchlist": "Включить уведомление на электронную почту об изменении списка наблюдения",
+ "config-email-watchlist-help": "Разрешить пользователям получать уведомления об отслеживаемых ими страницах, если они разрешили это в своих настройках.",
+ "config-email-auth": "Включить аутентификацию через электронную почту",
+ "config-email-auth-help": "Если эта опция включена, пользователи должны подтвердить свой адрес электронной почты перейдя по ссылке, которая отправляется на e-mail. Подтверждение требуется каждый раз при смене электронного ящика в настройках пользователя.\nТолько прошедшие проверку подлинности адреса электронной почты, могут получать электронные письма от других пользователей или изменять уведомления, отправляемые по электронной почте.\nВключение этой опции '''рекомендуется''' для открытых вики в целях пресечения потенциальных злоупотреблений возможностями электронной почты.",
+ "config-email-sender": "Обратный адрес электронной почты:",
+ "config-email-sender-help": "Введите адрес электронной почты для использования в качестве обратного адреса исходящей электронной почты.\nНа него будут отправляться отказы.\nМногие почтовые серверы требуют, чтобы по крайней мере доменное имя в нём было правильным.",
+ "config-upload-settings": "Загрузка изображений и файлов",
+ "config-upload-enable": "Разрешить загрузку файлов",
+ "config-upload-help": "Разрешение загрузки файлов, потенциально, может привести к угрозе безопасности сервера.\nДля получения дополнительной информации, прочтите в руководстве [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security раздел, посвящённый безопасности].\n\nЧтобы разрешить загрузку файлов, необходимо изменить права на каталог <code>images</code>, в корневой директории MediaWiki так, чтобы веб-сервер мог записывать в него файлы.\nЗатем включите эту опцию.",
+ "config-upload-deleted": "Директория для удалённых файлов:",
+ "config-upload-deleted-help": "Выберите каталог, в котором будут храниться архивы удалённых файлов.\nВ идеальном случае, в этот каталог не должно быть доступа из сети Интернет.",
+ "config-logo": "URL логотипа:",
+ "config-logo-help": "Стандартная тема оформления MediaWiki содержит над боковой панелью пространство для логотипа размером 135x160 пикселей.\nЗагрузите изображение соответствующего размера, и введите его URL здесь.\n\nВы можете использовать <code>$wgStylePath</code> или <code>$wgScriptPath</code>, если ваш логотип находится относительно к этим путям.\n\nЕсли вам не нужен логотип, оставьте это поле пустым.",
+ "config-instantcommons": "Включить Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — это функция, позволяющая использовать изображения, звуки и другие медиафайлы с Викисклада ([//commons.wikimedia.org/ Wikimedia Commons]).\nДля работы этой функции MediaWiki необходим доступ к Интернету.\n\nДополнительную информацию об Instant Commons, в том числе указания о том, как её настроить для других вики, отличных от Викисклада, можно найти в [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos руководстве].",
+ "config-cc-error": "Механизм выбора лицензии Creative Commons не вернул результата.\nВведите название лицензии вручную.",
+ "config-cc-again": "Выберите ещё раз…",
+ "config-cc-not-chosen": "Выберите, какую лицензию Creative Commons Вы хотите использовать, и нажмите кнопку \"Продолжить\".",
+ "config-advanced-settings": "Дополнительные настройки",
+ "config-cache-options": "Параметры кэширования объектов:",
+ "config-cache-help": "Кэширование объектов используется для повышения скорости MediaWiki путем кэширования часто используемых данных.\nДля средних и больших сайтов кеширование настоятельно рекомендуется включать, а для небольших сайтов кеширование может показать преимущество.",
+ "config-cache-none": "Без кэширования (никакой функционал не теряется, но крупные вики-сайты могут работать медленнее)",
+ "config-cache-accel": "PHP кэширование объектов (APC, XCache или WinCache)",
+ "config-cache-memcached": "Использовать Memcached (требует дополнительной настройки)",
+ "config-memcached-servers": "Сервера Memcached:",
+ "config-memcached-help": "Список IP-адресов, используемых Memcached.\nПеречислите по одному адресу на строку с указанием портов. Например:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Вы выбрали тип кэширования Memcached, но не задали адреса серверов.",
+ "config-memcache-badip": "Вы ввели неверный IP-адрес для Memcached: $1.",
+ "config-memcache-noport": "Не указан порт для сервера Memcached: $1.\nЕсли вы не знаете порт, по умолчанию используется 11211.",
+ "config-memcache-badport": "Номера портов Memcached должны лежать в пределах от $1 до $2.",
+ "config-extensions": "Расширения",
+ "config-extensions-help": "Расширения MediaWiki, перечисленные выше, были найдены в каталоге <code>./extensions</code>.\n\nОни могут потребовать дополнительные настройки, но их можно включить прямо сейчас",
+ "config-skins": "Темы оформления",
+ "config-skins-help": "Перечисленные выше темы оформления были обнаружены в вашем каталоге <code>./skins</code>. Вам необходимо включить по крайней мере один из них и выбрать тот, что будет по умолчанию.",
+ "config-skins-use-as-default": "Использовать по умолчанию эту тему оформления",
+ "config-skins-missing": "Темы оформления не найдены. MediaWiki будет использовать резервную тему до тех пор, пока вы не установите что-нибудь подходящее.",
+ "config-skins-must-enable-some": "Вы должны оставить включённой как минимум одну тему оформления.",
+ "config-skins-must-enable-default": "Тема оформления, выбранная по умолчанию, должна быть включена.",
+ "config-install-alreadydone": "'''Предупреждение:''' Вы, кажется, уже устанавливали MediaWiki и пытаетесь произвести повторную установку.\nПожалуйста, перейдите на следующую страницу.",
+ "config-install-begin": "Нажав «{{int:config-continue}}», вы начнёте установку MediaWiki.\nЕсли вы хотите внести изменения, нажмите «{{int:config-back}}».",
+ "config-install-step-done": "выполнено",
+ "config-install-step-failed": "не удалось",
+ "config-install-extensions": "В том числе расширения",
+ "config-install-database": "Настройка базы данных",
+ "config-install-schema": "Создание схемы",
+ "config-install-pg-schema-not-exist": "Схемы PostgreSQL не существует",
+ "config-install-pg-schema-failed": "Не удалось создать таблицы.\nУбедитесь в том, что пользователь «$1» может писать в схему «$2».",
+ "config-install-pg-commit": "Внесение изменений",
+ "config-install-pg-plpgsql": "Проверка языка PL/pgSQL",
+ "config-pg-no-plpgsql": "Вам необходимо установить поддержку языка PL/pgSQL для базы данных $1",
+ "config-pg-no-create-privs": "Учётная запись, указанная для установки, не обладает достаточными привилегиями для создания учётной записи.",
+ "config-pg-not-in-role": "Указанная учётная запись веб-пользователя уже существует.\nУказанная для установки учётная запись не является записью суперпользователя, и не относится к роли веб-пользователя, поэтому не получается создать объекты, принадлежащие веб-пользователю.\n\nMediaWiki в настоящее время требует, чтобы владельцем таблиц был веб-пользователь. Пожалуйста, укажите другое имя учётной записи для веб, или нажмите кнопку «назад» и укажите пользователя с достаточными для установки правами.",
+ "config-install-user": "Создание базы данных пользователей",
+ "config-install-user-alreadyexists": "Участник «$1» уже существует",
+ "config-install-user-create-failed": "Не получилось создать участника «$1»: $2",
+ "config-install-user-grant-failed": "Ошибка предоставления прав пользователю «$1»: $2",
+ "config-install-user-missing": "Указанного пользователя «$1» не существует.",
+ "config-install-user-missing-create": "Указанного пользователя «$1» не существует.\nПожалуйста поставьте ниже отметку «Создать учётную запись», если вы хотите создать его.",
+ "config-install-tables": "Создание таблиц",
+ "config-install-tables-exist": "'''Предупреждение''': таблицы MediaWiki, возможно, уже существуют.\nПропуск повторного создания.",
+ "config-install-tables-failed": "'''Ошибка''': Таблица не может быть создана из-за ошибки: $1",
+ "config-install-interwiki": "Заполнение таблицы интервики значениями по умолчанию",
+ "config-install-interwiki-list": "Не удалось найти файл <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Предупреждение''': в интервики-таблице, кажется, уже есть записи.\nСоздание стандартного списка пропущено.",
+ "config-install-stats": "Статистика инициализации",
+ "config-install-keys": "Создание секретных ключей",
+ "config-insecure-keys": "'''Предупреждение.''' {{PLURAL:$2|1=Ключ безопасности $1, созданный во время установки, недостаточно надёжен|Ключи безопасности $1, созданные во время установки, недостаточно надёжны}}. Рассмотрите возможность {{PLURAL:$2|1=его|их}} изменения вручную.",
+ "config-install-updates": "Предотвращение запуска ненужных обновлений",
+ "config-install-updates-failed": "<strong>Ошибка:</strong> Вставка ключей обновления в таблицы завершилась со следующей ошибкой: $1",
+ "config-install-sysop": "Создание учётной записи администратора",
+ "config-install-subscribe-fail": "Не удаётся подписаться на mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL не установлен и не доступна опция <code>allow_url_fopen</code>.",
+ "config-install-mainpage": "Создание главной страницы с содержимым по умолчанию",
+ "config-install-extension-tables": "Создание таблиц для включённых расширений",
+ "config-install-mainpage-failed": "Не удаётся вставить главную страницу: $1",
+ "config-install-done": "'''Поздравляем!'''\nВы успешно установили MediaWiki.\n\nВо время установки был создан файл <code>LocalSettings.php</code>.\nОн содержит всю конфигурации вики.\n\nВам необходимо скачать его и положить в корневую директорию вашей вики (ту же директорию, где находится файл index.php). Его загрузка должна начаться автоматически.\n\nЕсли автоматическая загрузка не началась или вы её отменили, вы можете скачать по ссылке ниже:\n\n$3\n\n'''Примечание''': Если вы не сделаете этого сейчас, то сгенерированный файл конфигурации не будет доступен вам в дальнейшем, если вы выйдете из установки, не скачивая его.\n\nПо окончании действий, описанных выше, вы сможете '''[$2 войти в вашу вики]'''.",
+ "config-download-localsettings": "Загрузить <code>LocalSettings.php</code>",
+ "config-help": "справка",
+ "config-help-tooltip": "нажмите, чтобы развернуть",
+ "config-nofile": "Файл \"$1\" не удается найти. Он был удален?",
+ "config-extension-link": "Знаете ли вы, что ваш вики-проект поддерживает [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions расширения]?\n\nВы можете просмотреть [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category расширения по категориям] или [//www.mediawiki.org/wiki/Extension_Matrix матрицу расширений], чтобы увидеть их полный список.",
+ "mainpagetext": "'''Вики-движок «MediaWiki» успешно установлен.'''",
+ "mainpagedocfooter": "Информацию по работе с этой вики можно найти в [//meta.wikimedia.org/wiki/Help:Contents/ru справочном руководстве].\n\n== Некоторые полезные ресурсы ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Список возможных настроек];\n* [//www.mediawiki.org/wiki/Manual:FAQ/ru Часто задаваемые вопросы и ответы по MediaWiki];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка уведомлений о выходе новых версий MediaWiki].\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Перевод MediaWiki на свой язык]"
+}
diff --git a/includes/installer/i18n/rue.json b/includes/installer/i18n/rue.json
new file mode 100644
index 00000000..787175fa
--- /dev/null
+++ b/includes/installer/i18n/rue.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gazeb"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki была успішно наіншталована.'''",
+ "mainpagedocfooter": "[//meta.wikimedia.org/wiki/Help:Contents Мануял хоснователя] Вам порадить, як хосновати MediaWiki.\n\n== Про початок ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Наставлїня конфіґурації]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Часты вопросы о MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розосыланя повідомлїнь про новы верзії MediaWiki]"
+}
diff --git a/includes/installer/i18n/sa.json b/includes/installer/i18n/sa.json
new file mode 100644
index 00000000..71764c9b
--- /dev/null
+++ b/includes/installer/i18n/sa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hemant wikikosh1"
+ ]
+ },
+ "mainpagetext": "मीडियाविकि तु सफलतया अन्तःस्थापितमस्ति"
+}
diff --git a/includes/installer/i18n/sah.json b/includes/installer/i18n/sah.json
new file mode 100644
index 00000000..14233aa8
--- /dev/null
+++ b/includes/installer/i18n/sah.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''«MediaWiki» сөпкө туруорулунна.'''",
+ "mainpagedocfooter": "Биики программатын туһунан [//meta.wikimedia.org/wiki/Help:Contents справочникка] көрүөххүн сөп.\n\n== Саҕаланыыта ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Конфигурация уларытыытын параметрдара]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki релизтарын почтовай испииһэгэ]"
+}
diff --git a/includes/installer/i18n/sc.json b/includes/installer/i18n/sc.json
new file mode 100644
index 00000000..100f3165
--- /dev/null
+++ b/includes/installer/i18n/sc.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Andria",
+ "L2212",
+ "Uharteko",
+ "Taxandru"
+ ]
+ },
+ "config-page-language": "Limba",
+ "config-page-name": "Nùmene",
+ "config-page-options": "Preferèntzias",
+ "mainpagetext": "'''MediaWiki est stadu installadu in modu currèggidu.'''"
+}
diff --git a/includes/installer/i18n/scn.json b/includes/installer/i18n/scn.json
new file mode 100644
index 00000000..31deb718
--- /dev/null
+++ b/includes/installer/i18n/scn.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''Nstallazzioni di MediaWiki cumplitata currettamenti.'''",
+ "mainpagedocfooter": "Pi favuri taliari [//meta.wikimedia.org/wiki/Help:Contents Guida utenti] pi aiutu supra l'usu e la cunfigurazzioni di stu software wiki.\n\n== P'accuminzari ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Alencu di mpustazzioni di cunfigurazzioni]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list dî rilassi di MediaWiki]"
+}
diff --git a/includes/installer/i18n/sco.json b/includes/installer/i18n/sco.json
new file mode 100644
index 00000000..4d447351
--- /dev/null
+++ b/includes/installer/i18n/sco.json
@@ -0,0 +1,314 @@
+{
+ "@metadata": {
+ "authors": [
+ "AmaryllisGardener",
+ "John Reid",
+ "Seb35"
+ ]
+ },
+ "config-desc": "The installer fer MediaWiki",
+ "config-title": "MediaWiki $1 installation.",
+ "config-information": "Information",
+ "config-localsettings-upgrade": "Ae <code>LocalSettings.php</code> file haes been detectit.\nTae upgrade this installation, please enter the vailyie o <code>$wgUpgradeKey</code> in the kist ablo.\nYe'll fynd it in <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Ae <code>LocalSettings.php</code> file haes been detectit.\nTae upgrade this installation, please rin <code>update.php</code> insteid",
+ "config-localsettings-key": "The Upgrade key:",
+ "config-localsettings-badkey": "The key that ye gave is fause.",
+ "config-upgrade-key-missing": "Aen exeestin installation o MediaWiki haes been detectit.\nTae upgrade this installation, please pit the follaein line at the bottom o yer <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "The exeestin <code>LocalSettings.php</code> appears tae be oncompleate.\nThe $1 variable isna set.\nPlease chynge <code>LocalSettings.php</code> sae that this variable is set, n clap \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Ae mistak wis encountered whan connectin til the database uisin the settins specified in <code>LocalSettings.php</code>. Please fix thir settins n try again.\n\n$1",
+ "config-session-error": "mistak in stertin session: $1",
+ "config-session-expired": "Yer session data seems tae'v expired.\nSessions ar configured fer ae lifetime o $1.\nYe can increase this bi settin <code>session.gc_maxlifetime</code> in php.ini.\nRestart the installation process.",
+ "config-no-session": "Yer session data wis tint!\nCheck yer php.ini an mak sair <code>session.save_path</code> is set til aen appropriate directerie.",
+ "config-your-language": "Yer leid:",
+ "config-your-language-help": "Select ae leid tae uise durin the installâtion process.",
+ "config-wiki-language": "Wiki leid:",
+ "config-wiki-language-help": "Select the leid that the wiki will predominantly be wrutten in.",
+ "config-back": "← Laist",
+ "config-continue": "Contînue →",
+ "config-page-language": "Leid",
+ "config-page-welcome": "Weelcome til MediaWiki!",
+ "config-page-dbconnect": "Connect til database",
+ "config-page-upgrade": "Upgrade exeestin installâtion",
+ "config-page-dbsettings": "Database settins",
+ "config-page-name": "Name,",
+ "config-page-options": "Opties",
+ "config-page-install": "Install,",
+ "config-page-complete": "Compleate!",
+ "config-page-restart": "Restart installâtion",
+ "config-page-readme": "Read me,",
+ "config-page-releasenotes": "Release nôtes",
+ "config-page-copying": "Copiein",
+ "config-page-upgradedoc": "Upgradin",
+ "config-page-existingwiki": "Exeestin wiki",
+ "config-help-restart": "Div ye wish tae clear aw hained data that ye'v entered n restairt the instawlation process?",
+ "config-restart": "Ai, restart it",
+ "config-welcome": "=== Environmêntal checks ===\nBasic checks will nou be performed tae see gif this environment is suitable fer MediaWiki installâtion.\nMynd tae inclæde this information gif ye seek heelp oan hou tae complete the installâtion.",
+ "config-copyright": "=== Copiericht n Terms ===\n\n$1\n\nThis program is free saffware; ye can redistreebute it n/or modifie it unner the terms o the GNU General Public License aes published bi the Free Software Foundation; either version 2 o the License, or (yer optie) onie later version.\n\nThis program is distributed in the hope that it will be uiseful, but <strong>wioot onie warrantie</strong>; wioot even the implied warrantie o <strong>merchantabeelity</strong> or <strong>fitness fer ae parteecular purpose</strong>.\nSee the GNU General Public License fer mair details.\n\nYe shid hae receeved <doclink href=Copying> ae copie o the GNU General Publeec License</doclink> alang wi this program; gif naw, write til the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, or [http://www.gnu.org/copyleft/gpl.html read it online].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki home]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administrator's Guide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Release notes</doclink>\n* <doclink href=Copying>Copiein</doclink>\n* <doclink href=UpgradeDoc>Upgradin</doclink>",
+ "config-env-good": "The environment haes been checked.\nYe can install MediaWiki.",
+ "config-env-bad": "The environment haes been checked.\nYe canna install MediaWiki.",
+ "config-env-php": "PHP $1 is instâlled.",
+ "config-unicode-using-utf8": "Uising Brion Vibber's utf8_normalize.so fer Unicode normalization.",
+ "config-unicode-using-intl": "Uising the [http://pecl.php.net/intl intl PECL extension] fer Unicode normalization.",
+ "config-unicode-pure-php-warning": "<strong>Warnishment:</strong> The [http://pecl.php.net/intl intl PECL extension] is no available tae haunle Unicode normalisation, fawin back tae slaw pure-PHP implementation.\nGif ye rin ae hei-traffic steid, ye shid read ae wee bit oan [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].",
+ "config-unicode-update-warning": "<strong>Warnishment:</strong> The instawed version o the Unicode normalization wrapper uises aen aulder version o [http://site.icu-project.org/ the ICU project's] librie.\nYe shid [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations upgrade] gif ye'r concerned aneat uisin Unicode.",
+ "config-no-db": "Coudna fynd ae suitable database driver! Ye need tae instaw ae database driver fer PHP.\nThe follaein database types ar supported: $1.\n\nGif ye compiled PHP yersel, reconfeegure it wi ae database client enabled, fer example, uising <code>./confeegure --wi-mysqli</code>.\nGif ye installed PHP fae ae Debian or Ubuntu package, than ye need tae instaw forby, fer example, the <code>php5-mysql</code> package.",
+ "config-outdated-sqlite": "<strong>Warnishment:</strong> ye have SQLite $1, this is lower than minimum required version $2. SQLite will be onavailable.",
+ "config-no-fts3": "<strong>Warnishment:</strong> SQLite is compiled wioot the [//sqlite.org/fts3.html FTS3 module], rake features will be onavailable oan this backend.",
+ "config-magic-quotes-gpc": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] is active!</strong>\nThis option corrupts data input unpredictably.\nYe cannae install or uise MediaWiki unless this option is disabled.",
+ "config-magic-quotes-runtime": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is active!'</strong>\nThis optie rots data input onpredictably.\nYe canna install or uise MediaWiki onless this optie is disabled.",
+ "config-magic-quotes-sybase": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] is active!</strong>\nThis optie rots data input onpredictably.\nYe canna install or uise MediaWiki onless this optie is disabled.",
+ "config-mbstring": "<strong>Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is active!</strong>\nThis optie causes mistaks an can rot data onpredictably.\nYe canna install or uise MediaWiki onless this optie is disabled.",
+ "config-safe-mode": "<strong>Warnishment:</strong> PHP's [http://www.php.net/features.safe-mode safe mode] is acteeve.\nIt micht cause problems, parteecularlie gif uisin file uplaids n <code>math</code> support.",
+ "config-xml-bad": "PHP's XML module is missin.\nMediaWiki needs functions in this module n will naw wairk in this confeeguration.\nGif ye'r rinnin Mandrake, instaw the php-xml package.",
+ "config-pcre-old": "<strong>Fatal:</strong> PCRE $1 or later is required.\nYer PHP binary is link't wi PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Mair informâtion].",
+ "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-ctype": "<strong>Fatal:</strong> PHP maun be compiled wi support fer the [http://www.php.net/manual/en/ctype.installation.php Ctype extension].",
+ "config-json": "<strong>Fatal:</strong> PHP wis compiled wioot JSON support.\nYe maun instaw either the PHP JSON extension or the [http://pecl.php.net/package/jsonc PECL jsonc] extension afore instawin MediaWiki.\n* The PHP extension is incluided in Red Hat Enterprise Linux (CentOS) 5 n 6, thoogh it maun be enabled in <code>/etc/php.ini</code> or <code>/etc/php.d/json.ini</code>.\n* Some Linux distributions released efter Mey 2013 omit the PHP extension, instead packagin the PECL extension aes <code>php5-json</code> or <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] is installed.",
+ "config-apc": "[http://www.php.net/apc APC] is installed.",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] is instawed.",
+ "config-no-cache": "<strong>Warnishment:</strong> Coudna fynd [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].\nObject cachin isna enabled.",
+ "config-mod-security": "<strong>Warnishment:</strong> Yer wab server haes [http://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 [http://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-git-bad": "Git version control saffware no foond.",
+ "config-imagemagick": "Foond ImageMagick: <code>$1</code>.\nEemage thummnailin will be enabled gif ye enable uplaids.",
+ "config-gd": "Foond GD graphics librie biggit-in.\nEemage thummnailin will be enabled gif ye enable uplaids.",
+ "config-no-scaling": "Coudna fynd GD librie or ImageMagick.\nEemage thummnailin will be disabled.",
+ "config-no-uri": "<strong>Mistak:</strong> Coudna determine the current URI.\nInstallâtion aborted.",
+ "config-no-cli-uri": "<strong>Warnishment:</strong> Naw <code>--scriptpath</code> speceefied, uisin defaut: <code>$1</code>.",
+ "config-using-server": "Uisin server name \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Uisin server URL \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "<strong>Warnishment:</strong> Yer defaut directerie fer uplaids <code>$1</code> is vulnerable til arbitrie scripts execution.\nAathough MediaWiki checks aw uplaided files fer securitie threats, it is heily recommended tae [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security close this security vulnerabeelitie] afore enablin uplaids.",
+ "config-no-cli-uploads-check": "<strong>Warnishment:</strong> Yer defaut directerie fer uplaids (<code>$1</code>) isna checkit fer vulnerabeelitie\ntae arbitrie script execution durin the CLI install.",
+ "config-brokenlibxml": "Yer system haes ae combinâtion o PHP n libxml2 versions that's buggie n can cause skauk't data rottin in MediaWiki n ither wab applicâtions.\nUpgrade til libxml2 2.7.3 or later ([https://bugs.php.net/bug.php?id=45996 bug filed with PHP]).\nInstallâtion aborted.",
+ "config-suhosin-max-value-length": "Suhosin is installed n limits the GET parameter <code>length</code> til $1 bytes.\nMediaWiki's ResoorceLaider component will wark aroonn this limit, but that will lawer performance.\nGif at aw possible, ye shid set <code>suhosin.get.max_value_length</code> til 1024 or heier in <code>php.ini</code>, n set <code>$wgResourceLoaderMaxQueryLength</code> til the same value in <code>LocalSettings.php</code>.",
+ "config-db-type": "Dâtabase type:",
+ "config-db-host": "Dâtabase host:",
+ "config-db-host-help": "Gif yer database server is oan ae different server, enter the host name or IP address here.\n\nGif ye'r uisin shaired wab hostin, yer hostin provider shid gie ye the richt host name in their documentation.\n\nGif ye'r installin oan ae Windows server n uisin MySQL, uisin \"localhost\" michtna wark fer the server name. Gif it disna, try \"127.0.0.1\" fer the local IP address.\n\nGif ye'r uisin PostgreSQL, lea this field blank tae connect bi wa o ae Unix socket.",
+ "config-db-host-oracle": "Dâtabase TNS:",
+ "config-db-host-oracle-help": "Enter ae valid [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; ae tnsnames.ora file maun be veesible til this instawation. <br />Gif ye'r uisin client libries 10g or newer ye can uise forby the [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] namin methyd.",
+ "config-db-wiki-settings": "Identifie this wiki",
+ "config-db-name": "Dâtabase name:",
+ "config-db-name-help": "Chuise ae name that identifies yer wiki.\nIt shidna contain spaces.\n\nGif ye'r uisin shaired wab hoastin, yer hoastin provider will either gie ye ae speceefic database name tae uise or let ye mak databases bi waa o ae control panel.",
+ "config-db-name-oracle": "Dâtabase schema:",
+ "config-db-account-oracle-warn": "Thaur's three supportit scenaríos fer instawin Oracle aes ae database backend:\n\nGif ye wish tae cræft ae database accoont aes pairt o the instawation process, please supplie aen accoont wi SYSDBA role aes database accoont fer instawation n speceefie the desired creedentials fer the wab-access accoont, itherwise ye can eether cræft the wab-access accoont manuallie n supplie yinlie that accoont (gif it haes the needit permeessions tae cræft the schema objects) or supplie twa differant accoonts, yin wi cræft preevileges n ae restreectit yin fer wab access.\n\nScreept fer cræftin aen accoont wi the needit preevileges can be foond in the \"maintenance/oracle/\" directerie o this instawation. Keep in mynd that uisin ae restreectit accoont will disable aw maintenance capabileeties wi the defaut accoont.",
+ "config-db-install-account": "Uiser accoont fer installâtion",
+ "config-db-username": "Database uisername:",
+ "config-db-password": "Database passwaird:",
+ "config-db-password-empty": "Please enter ae passwaird fer the new database uiser: $1.\nWhile it micht be possible tae mak uisers wi naw passwairds, it's naw secure.",
+ "config-db-install-username": "Enter the uisername that will be uised tae connect til the database durin the installâtion process.\nThis isna the uisername o the MediaWiki accont; this is the uisername fr yer database.",
+ "config-db-install-password": "Enter the passwaird that will be uised tae connect til the database durin the installâtion process.\nThis isna the passwaird fer the MediaWiki accoont; this is the passwaird fer yer database.",
+ "config-db-install-help": "Enter the uisername an passwaird that will be uised tae connect til the database durin the installâtion process.",
+ "config-db-account-lock": "Uise the same uisername an passwaird durin normal operation",
+ "config-db-wiki-account": "Uiser accoont fer normal operâtion",
+ "config-db-wiki-help": "Enter the uisername n passwaird that will be uised tae connect til the database durin normal wiki operâtion.\nGif the accoont disna exeest, n the instawlation accoont haes suffeecient preevileges, this uiser accoont will be cræftit wi the least preevileges needed tae operate the wiki.",
+ "config-db-prefix": "Database buird prefix:",
+ "config-db-prefix-help": "Gif ye need tae shair yin database atween multiple wikis, or atween MediaWiki n anither wab appleecation, ye can chuise tae eik ae prefix til aw the buird names tae avoid confleects.\nDinna uise spaces.\n\nThis field is uisuallie left tuim.",
+ "config-db-charset": "Database chairacter set",
+ "config-charset-mysql5-binary": "MaSQL 4.1/5.0 binarie",
+ "config-charset-mysql5": "MaSQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MaSQL 4.0 backwairds-compatible UTF-8",
+ "config-charset-help": "<strong>Warnishment:</strong> Gif ye uise <strong>backwairds-compatible UTF-8</strong> oan MySQL 4.1+, n subsequentlie back up the database wi <code>mysqldump</code>, it micht destroy aw non-ASCII chairacters, onreversiblie rotin yer backups!\n\nIn <strong>binarie mode</strong>, MediaWiki stores UTF-8 tex til the database in binarie fields.\nThis is mair effeecient than MySQL's UTF-8 mode, n permits ye tae uise the ful range o Unicode chairacters.\nIn <strong>UTF-8 mode</strong>, MySQL will ken whit chairacter set yer data is in, n can present n convert it appropriatelie,\nbut it will naw lat ye store chairacters abuin the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mysql-old": "MaSQL $1 or later is required. Ye hae $2.",
+ "config-db-port": "Dâtabase port:",
+ "config-db-schema": "Schema fer MediaWiki:",
+ "config-db-schema-help": "This schema will uisually be fine.\nyinly chynge it gif ye ken ye need tae.",
+ "config-pg-test-error": "Canna connect til database <strong>$1</strong>: $2",
+ "config-sqlite-dir": "SQLite data directerie:",
+ "config-sqlite-dir-help": "SQLite stores aw data in ae single file.\n\nThe directerie ye provide maun be writable bi the wabserver durin instawation.\n\nIt shid <strong>no</strong> be accessible bi waa o the wab, this is why we'r no puttin it whaur yer PHP files ar.\n\nThe instawer will write ae <code>.htaccess</code> file alang wi it, but gif that fails somebodie can gain access til yer raw database.\nThat incluides raw uiser data (wab-mail addresses, hashed passwairds) aes weel aes delytit reveesions n ither restreected data oan the wiki.\n\nConsider puttin the database some ither place awthegether, fer example in <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Defaut buirdspace:",
+ "config-oracle-temp-ts": "Temperie buirdspace:",
+ "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. ([http://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. ([http://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. ([http://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",
+ "config-header-oracle": "Oracle settins",
+ "config-header-mssql": "Microsoft SQL Server settings",
+ "config-invalid-db-type": "Onvalid database type",
+ "config-missing-db-name": "Ye maun enter ae value fer \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Ye maun enter ae value fer \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Ye maun enter ae value fer \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "Onvalid database TNS \"$1\".\nUise either \"TNS Name\" or aen \"Easy Connect\" string ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods])",
+ "config-invalid-db-name": "Onvalid database name \"$1\".\nUise yinly ASCII letters (a-z, A-Z), nummers (0-9), unnerscores (_) an hyphens (-).",
+ "config-invalid-db-prefix": "Onvalid database prefix \"$1\".\nUise yinly ASCII letters (a-z, A-Z), nummers (0-9), unnerscores (_) an hyphens (-).",
+ "config-connection-error": "$1.\n\nCheck the host, uisername n passwaird n gie it anither shot.",
+ "config-invalid-schema": "Onvalid schema fer MediaWiki \"$1\".\nUise yinly ASCII letters (a-z, A-Z), nummers (0-9) an unnerscores (_).",
+ "config-db-sys-create-oracle": "Installer yinly supports usin ae SYSDBA accoont fer makin ae new accoont.",
+ "config-db-sys-user-exists-oracle": "Uiser accoont \"$1\" awreadie exeests. SYSDBA can yinly be uised fer the makin o ae new accoont!",
+ "config-postgres-old": "PostgreSQL $1 or later is required. Ye hae $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 or newer is needed. Ye hae $2.",
+ "config-sqlite-name-help": "Chuise ae name that identifies yer wiki.\nDinna uise spaces or hyphens.\nThis will be uised fer the SQLite data file name.",
+ "config-sqlite-parent-unwritable-group": "Canna mak the data directerie <code><nowiki>$1</nowiki></code>, cause the parent directerie <code><nowiki>$2</nowiki></code> isna writable bi the wabserver.\n\nThe installer haes determined the uiser yer wabserver is runnin aes.\nMak the <code><nowiki>$3</nowiki></code> directerie writable bi it tae continue.\nOan ae Unix/Linux system dae:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Canna cræft the data directerie <code><nowiki>$1</nowiki></code>, cause the pairent directerie <code><nowiki>$2</nowiki></code> isna writable bi the wabserver.\n\nThe instawer coudna determine the uiser yer wabserver is rinnin aes.\nMak the <code><nowiki>$3</nowiki></code> directerie globallie writable bi it (n ithers!) tae continue.\nOan ae Unix/Linux system dae:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Mistak in cræftin the data directerie \"$1\".\nCheck the location n try again.",
+ "config-sqlite-dir-unwritable": "Onable tae write in the directerie \"$1\".\nChynge its permeessions sae that the wabserver can write in it, n gie it anither gae.",
+ "config-sqlite-connection-error": "$1.\n\nCheck the data directerie n database name ablo n try again.",
+ "config-sqlite-readonly": "The file <code>$1</code> isna writeable.",
+ "config-sqlite-cant-create-db": "Coudna make database file <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP is missing FTS3 support, doongradin buirds",
+ "config-can-upgrade": "Thaur's MediaWiki buirds in this database.\nTae upgrade thaim til MediaWiki $1, clap <strong>Continue</strong>.",
+ "config-upgrade-done": "Upgrade compleate.\n\nYe can nou [$1 stert uising yer wiki].\n\nGif ye wish tae regenerate yer <code>LocalSettings.php</code> file, clap the button ablow.\nThis <strong> isna recommended</strong> onless ye'r haein problems wi yer wiki.",
+ "config-upgrade-done-no-regenerate": "Upgrade compleate.\n\nYe can nou [$1 stert uising yer wiki].",
+ "config-regenerate": "Regênerate LocalSettings.php →",
+ "config-show-table-status": "<code>SHAW BUIRD STATUS</code> speirin failed!",
+ "config-unknown-collation": "<strong>Warnishment:</strong> Database is uisin onrecognized collation.",
+ "config-db-web-account": "Database accoont fer wab access",
+ "config-db-web-help": "Select the uisername n passwaird that the wab server will uise tae connect til the database server, durin ordinair operation o the wiki.",
+ "config-db-web-account-same": "Uise the same accoont aes fer installation",
+ "config-db-web-create": "Cræft the accoont gif it disna awreadie exeest",
+ "config-db-web-no-create-privs": "The accoont that ye speceefied fer instawation disna hae enooch preevileges tae cræft aen accoont.\nThe accoont that ye speceefie here maun awreadie exeest.",
+ "config-mysql-engine": "Storage engine:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>Warnishment:</strong> Ye'v selected MyISAM aes storage engine fer MySQL, this isna recommended fer uiss wi MediaWiki, cause:\n* it barelie supports concurrencie cause o buird lockin\n* it's mair prone til rot than ither engines\n* the MediaWiki codebase disna aye haunnle MyISAM aes it shid\n\nGif yer MySQL installâtion supports InnoDB, it is heilie recommended that ye chuise that instead.\nGif yer MySQL installâtion disna support InnoDB, than perhaps it's time fer aen upgrade.",
+ "config-mysql-only-myisam-dep": "<strong>Warnishment:</strong> MyISAM is the yinly available storage engine fer MySQL oan this machine, n this isna recommended fer uiss wi MediaWiki, cause:\n* it barelie supports concurrencie cause o buird lockin\n* it is mair prone til rot than ither engines\n* the MediaWiki codebase disna aye haunnle MyISAM aes it shid\n\nYer MySQL installâtion dina support InnoDB, perhaps it's time fer aen upgrade.",
+ "config-mysql-engine-help": "<strong>InnoDB</strong> is awmaist aye the best optie, aes it haes guid concurrencie support.\n\n<strong>MyISAM</strong> micht be faster in single-uiser or read-yinly installâtions.\nMyISAM databases tend tae rot mair aften than InnoDB databases.",
+ "config-mysql-charset": "Database chairacter set:",
+ "config-mysql-binary": "Binarie",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "In <strong>binarie mode</strong>, MediaWiki stores UTF-8 tex til the database in binarie fields.\nThis is mair effeecient than MySQL's UTF-8 mode, n permits ye tae uise the ful range o Unicode chairacters.\n\nIn <strong>UTF-8 mode</strong>, MySQL will ken whit chairacter set yer data is in, n can present n convert it appropreeatelie, but it'll naw lat ye store chairacters abuin the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mssql-auth": "Authentication type:",
+ "config-mssql-install-auth": "Select the authentication type that's tae be uised tae connect wi the database durin the installation process.\nGif ye select \"{{int:config-mssql-windowsauth}}\", the credeentials o whitever uiser the wabserver is rinnin aes will be uised.",
+ "config-mssql-web-auth": "Select the authentication type that the wab server will uise tae connect wi the database server, durin ordinair operation o the wiki.\nGif ye select \"{{int:config-mssql-winowsauth}}\", the credeentials o whitever uiser the wabserver is rinnin aes will be uised.",
+ "config-mssql-sqlauth": "SQL Server Authentication",
+ "config-mssql-windowsauth": "Windows Authentication",
+ "config-site-name": "Name o wiki:",
+ "config-site-name-help": "This will kyth in the title baur o the brouser n in varioos ither places.",
+ "config-site-name-blank": "Enter ae site name.",
+ "config-project-namespace": "Waurk namespace:",
+ "config-ns-generic": "Waurk",
+ "config-ns-site-name": "Same aes the wiki name: $1",
+ "config-ns-other": "Ither (speceefie)",
+ "config-ns-other-default": "MaWiki",
+ "config-project-namespace-help": "Follaein Wikipedia's example, moni wikis keep their policy pages separate fae thair content pages, in ae \"'''project namespace'''\".\nAw page titles in this namespace stert wi ae certain prefix, that ye can speceefie here.\nUisuallie, this prefix is derived fae the name o the wiki, but it canna contain punctuation chairacters sic like \"#\" or \":\".",
+ "config-ns-invalid": "The speceefied namespace \"<nowiki>$1</nowiki>\" is onvalid.\nSpeceefie ae different project namespace.",
+ "config-ns-conflict": "The speceefied namespace \"<nowiki>$1</nowiki>\" conflicts wi ae defaut MediaWiki namespace.\nSpeceefie ae different project namespace.",
+ "config-admin-box": "Admeenistrater accoont",
+ "config-admin-name": "Yer uisername:",
+ "config-admin-password": "Passwaird:",
+ "config-admin-password-confirm": "Passwaird again:",
+ "config-admin-help": "Enter yer preferred uisername here, fer example \"John Smith\".\nThis is the name ye'll uise tae log in til the wiki.",
+ "config-admin-name-blank": "Enter aen admeenistrater uisername.",
+ "config-admin-name-invalid": "The speceefied uisername \"<nowiki>$1</nowiki>\" is onvalid.\nSpeceefie ae different uisername.",
+ "config-admin-password-blank": "Enter ae passwaird fer the admeenistrater accoont.",
+ "config-admin-password-mismatch": "The twa passwairds ye entered dinna match.",
+ "config-admin-email": "Wab-mail address:",
+ "config-admin-email-help": "Enter ae wab-mail address here tae permit ye tae receive wab-mail fae ither uisers oan the wiki, reset yer passwaird, n be telt o chynges til pages oan yer watchleet. Ye can lea this field tuim.",
+ "config-admin-error-user": "Internal mistak whan makin aen admeen wi the name \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Internal mistak whan settin ae passwaird fer the admeen \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Ye'v entered aen onvalid wab-mail address.",
+ "config-subscribe": "Subscribe til the [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release annooncements mailin leet].",
+ "config-subscribe-help": "This is ae low-volume mailin leet uised fer release annooncements, inclæding important securitie annooncements.\nYe shid subscribe til it an update yer MediaWiki installâtion whan new versions come oot.",
+ "config-subscribe-noemail": "Ye tried tae subscribe til the release annooncements mailin let wioot giein ae wab-mail address.\nPlease gei ae wab-mail address gif ye wish tae subscribe til the mailin leet.",
+ "config-almost-done": "Ye'r awmaist dun!\nYe can nou skip the remainin confeegurâtion n install the wiki stricht awa.",
+ "config-optional-continue": "Speir me mair speirins.",
+ "config-optional-skip": "Ah'm bored awreadie, jyst install the wiki.",
+ "config-profile": "Uiser richts profile:",
+ "config-profile-wiki": "Apen wiki",
+ "config-profile-no-anon": "Please mak aen accoont",
+ "config-profile-fishbowl": "Permited eiditors yinly",
+ "config-profile-private": "Private wiki",
+ "config-profile-help": "Wikis wark best whan ye lat aes monie fawk eedit thaim aes possible.\nIn MediaWiki, it's easie tae luik ower the recent chynges, n tae revert onie damage that's dun bi naeeve or maleecioos uisers.\n\nHouever, monie hae foond MediaWiki tae be uissful in ae wide varietie o roles, n sometimes it's na easie tae conveence awbodie o the beneefits o the wiki wa.\nSae ye hae the choice.\n\nThe <strong>{{int:config-profile-wiki}}</strong> model allous oniebdie tae eedit, wioot even loggin in.\nAe wiki wi <strong>{{int:config-profile-no-anon}}</strong> provides eextra accoontabeelitie, but micht deter casual contreebuters.\n\nThe <strong>{{int:config-profile-fishbowl}}</strong> scenario allous appruived uisers tae eedit, but the publeec can see the pages, incluidin histerie.\nA <strong>{{int:config-profile-private}}</strong> yinlie permits appruived uisers tae see pages, wi the same groop permited tae eedit.\n\nMair complex uiser richts confeegurations ar available efter instawation, see the [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights relevant manual entrie].",
+ "config-license": "Copiericht n license:",
+ "config-license-none": "Nae license fiter",
+ "config-license-cc-by-sa": "Creative Commyns Attribution Share Alike",
+ "config-license-cc-by": "Creative Commyns Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commyns Attribution No-Commercial Shair Alike",
+ "config-license-cc-0": "Creative Commyns Zero (Public Domain)",
+ "config-license-gfdl": "GNU Free Documentâtion License 1.3 or later",
+ "config-license-pd": "Public Domain",
+ "config-license-cc-choose": "Select ae custym Creative Commyns license",
+ "config-license-help": "Monie publeec wikis pit aw contreebutions unner ae [http://freedomdefined.org/Defineetion free license].\nThis heelps tae creaut ae sense o communitie ainership n encoorages lang-term contreebution.\nIt's naw generallie necessair fer ae preevate or corporate wiki.\n\nGif ye wish tae be able tae uise tex fae Wikipædia, n ye want Wikipædia tae be able tae accept tex copied fae yer wiki, than ye shid chuise <strong>Creative Commons Attribution Shair Alike</strong>.\n\nWikipædia preeveeooslie uised the GNU Free Documentation License.\nThe GFDL is ae valid license, but it's difficult tae unnerstaunn.\nMairower, it's difficult tae reuise content licensed unner the GFDL.",
+ "config-email-settings": "Wab-mail settins",
+ "config-enable-email": "Enable ootboond wab-mail",
+ "config-enable-email-help": "Gif ye want wab-mail tae wark, [http://www.php.net/manual/en/mail.configuration.php PHP's mail settins] need tae be confeegured jyst richt.\nGif ye dinna want oni wab-mail features, ye can disable theim here.",
+ "config-email-user": "Enable uiser-til-uiser wab-mail",
+ "config-email-user-help": "Permit aw uisers tae send each ither wab-mail gif they'v enabled it in their preferences.",
+ "config-email-usertalk": "Enable uiser tauk page notifeecâtion",
+ "config-email-usertalk-help": "Permit uisers tae receive notifeecâtions oan uiser tauk page chynges, gif they'v enabled it in their preferences.",
+ "config-email-watchlist": "Enable watchleet notifeecâtion",
+ "config-email-watchlist-help": "Permit uisers tae receive notifeecâtions aneat their watched pages gif they'v enabled it in their preferences.",
+ "config-email-auth": "Enable wab-mail authenticâtion",
+ "config-email-auth-help": "Gif this optie is enabled, uisers hae tae confirm their wab-mail address uising ae link sent til theim whanivir they set or chynge it.\nYinly authenticated wab-mail addresses can receive emails fae ither uisers or chynge notifeecâtion wab-mails.\nSettin this optiei is <strong>recommended</strong> fer public wikis cause o potential abuise o the wab-mail features.",
+ "config-email-sender": "Return wab-mail address:",
+ "config-email-sender-help": "Enter the wab-mail address tae uise aes the return address oan ootboond wab-mail.\nThis is whaur boonces will be sent.\nMonie mail servers need at least the domain name pairt tae be valid.",
+ "config-upload-settings": "Eemages n file uplaids",
+ "config-upload-enable": "Enable file uplaids",
+ "config-upload-help": "File uplaids potentiallie expose yer server til securitie risks.\nFer mair information, read the [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security securitie section] in the manual.\n\nTae enable file uplaids, chynge the mode oan the <code>eemages</code> subdirecterie unner MediaWiki's ruit directerie sae that the wab server can write til it.\nThan enable this optie.",
+ "config-upload-deleted": "Directerie fer delytit files:",
+ "config-upload-deleted-help": "Chuise ae directerie tae archive delytit files in.\nIdeally, this shidna be accessible fae the wab.",
+ "config-logo": "Logo URL:",
+ "config-logo-help": "MediaWiki's defaut skin inclædes space fer ae 135x160 pixel logo abuin the sidebaur menu.\nUplaid aen eemage o the appropriate size, n enter the URL here.\n\nYe can uise <code>$wgStylePath</code> or <code>$wgScriptPath</code> gif yer logo is relative til thae paths.\n\nGif ye dinna want ae logo, lea this kist blank.",
+ "config-instantcommons": "Enable Instant Commyns",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commyns] is ae featur that allous wikis tae uise eemages, soonds n ither media foond oan the [//commons.wikimedia.org/ Wikimedia Commons] steid.\nIn order tae dae this, MediaWiki needs access til the Internet.\n\nFer mair information oan this featur, incluidin instructions oan hou tae set it up fer wikis ither than the Wikimedia Commons, consult [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos the manual].",
+ "config-cc-error": "The Creative Commyns license chuiser gae naw result.\nEnter the license name manually.",
+ "config-cc-again": "Pick again...",
+ "config-cc-not-chosen": "Chuise whit Creative Commyns license ye want an clap oan \"proceed\".",
+ "config-advanced-settings": "Advanced confeegurâtion",
+ "config-cache-options": "Settins fer object cachin:",
+ "config-cache-help": "Object cachin is uised tae impruiv the speed o MediaWiki bi cachin frequentlie uised data.\nMedium til muckle sites ar heilie encooraged tae enable this, n wee sites will see benefits ava.",
+ "config-cache-none": "Naw caching (nae functionâlitie is remuived, but speed mmicht be impacted oan muckler wiki sites)",
+ "config-cache-accel": "PHP object cachin (APC, XCache or WinCache)",
+ "config-cache-memcached": "Uise Memcached (needs addeetional setup n confeegurâtion)",
+ "config-memcached-servers": "Memcached servers:",
+ "config-memcached-help": "Leet o IP addresses tae uise fer Memcached.\nShid speceefie yin per line n speceefie the port tae be uised. Fer example:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Ye selected Memcached aes yer cache type but dinna speceefie oni servers.",
+ "config-memcache-badip": "Ye'v entered aen onvalid IP address fer Memcached: $1.",
+ "config-memcache-noport": "Ye didna speceefie ae port tae uise fer Memcached server: $1.\nGif ye dinna knaw the port, the defauut is 11211.",
+ "config-memcache-badport": "Memcached port nummers shid be atween $1 n $2.",
+ "config-extensions": "Extensions",
+ "config-extensions-help": "The extensions leetit abuin were detected in yer <code>./extensions</code> directerie.\n\nThey micht need addeetional confeeguration, but ye can enable thaim nou.",
+ "config-install-alreadydone": "<strong>Warnishment:</strong> Ye seem tae'v awreadie instawed MediaWiki n ar tryin tae instaw it again.\nPlease proceed til the nex page.",
+ "config-install-begin": "Bi pressin \"{{int:config-continue}}\", ye will begin the installation o MediaWiki.\nGif ye still wish tae mak chynges, press \"{{int:config-back}}\".",
+ "config-install-step-done": "dun",
+ "config-install-step-failed": "failed",
+ "config-install-extensions": "Inclædin extensions",
+ "config-install-database": "Settin up database",
+ "config-install-schema": "Makin schema",
+ "config-install-pg-schema-not-exist": "PostgreSQL schema disna exeest.",
+ "config-install-pg-schema-failed": "Buirds makin failed.\nMak sair that the uiser \"$1\" can write til the schema \"$2\".",
+ "config-install-pg-commit": "Committin chynges",
+ "config-install-pg-plpgsql": "Checkin fer lied PL/pgSQL",
+ "config-pg-no-plpgsql": "Ye need tae install the leid PL/pgSQL in the database $1",
+ "config-pg-no-create-privs": "The accoont ye speceefied fer installâtion disna hae enough preevileges tae mak aen accoont.",
+ "config-pg-not-in-role": "The accoont that ye speceefied fer the wab uiser awreadie exists.\nThe accoont that ye specefied fer installâtion isna ae suiperuiser an isna a memmer o the wab uiser's role, sae it is onable tae mak objects ained bi the wab uiser.\n\nMediaWiki currentlie requires that the buirds be ained bi the wab uiser. Please specefie anither wab accoont name, or clap \"back\" an speceefie ae suitablie preevileged install uiser.",
+ "config-install-user": "Makin database uiser",
+ "config-install-user-alreadyexists": "Uiser \"$1\" awreadie exists",
+ "config-install-user-create-failed": "Makin uiser \"$1\" failed: $2",
+ "config-install-user-grant-failed": "Grantin permission til uiser \"$1\" failed: $2",
+ "config-install-user-missing": "The speceefied uiser \"$1\" disna exeest.",
+ "config-install-user-missing-create": "The speceefied uiser \"$1\" disna exeest.\nPlease clap the \"cræft accoont\" checkkist ablo gif ye wish tae cræft it.",
+ "config-install-tables": "Makin buirds",
+ "config-install-tables-exist": "<strong>Warnishment:</strong> MediaWiki buirds awreadie seem tae exeest.\nSkippin the cræftin.",
+ "config-install-tables-failed": "<strong>Mistak:</strong> Buird cræftin failed wi the follaein mistak: $1",
+ "config-install-interwiki": "Populatin defaut interwiki buird",
+ "config-install-interwiki-list": "Coudna read file <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "<strong>Warnishment:</strong> The interwiki buird awreadie seems tae hae entries.\nSkippin defaut let.",
+ "config-install-stats": "Ineetializin stateestics",
+ "config-install-keys": "Generatin hidlins keys",
+ "config-insecure-keys": "<strong>Warnishment:</strong> {{PLURAL:$2|Ae secure key|Secure keys}} ($1) generated durin instawation {{PLURAL:$2|is|ar}} naw compleatelie safe. Consider chyngin {{PLURAL:$2|it|theim}} manuallie.",
+ "config-install-sysop": "Makin admeenistrâter uiser accoont",
+ "config-install-subscribe-fail": "Onable tae subscribe til mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL isna instawed n <code>allow_url_fopen</code> is na available.",
+ "config-install-mainpage": "Cræftin main page wi defaut content",
+ "config-install-extension-tables": "Makin buirds fer enabled extensions",
+ "config-install-mainpage-failed": "Coudna insert main page: $1",
+ "config-install-done": "<strong>Congratulations!</strong>\nYe'v successfulie instawed MediaWiki.\n\nThe instawer haes generated ae <code>LocalSettings.php</code> file.\nIt contains aw yer confeeguration.\n\nYe'll need tae doonlaid it n pit it in the base o yer wiki instawation (the same directerie aes index.php). The doonlaid shid hae stairted autæmateeclie.\n\nGif the doonlaid wisna affered, or gif ye cancelled it, ye can restairt the doonlaid bi clapin oan the airtin ablo:\n\n$3\n\n<strong>Mynd:</strong> Gif ye dinna dae this the nou, this generated confeeguration file willna be available til ye laiter gif ye exit the instawation wioot doonlaidin it.\n\nWhan that haes been dun, ye can <strong>[$2 enter yer wiki]</strong>.",
+ "config-download-localsettings": "Dounlaid <code>LocalSettings.php</code>",
+ "config-help": "heelp",
+ "config-nofile": "File \"$1\" coudna be foond. Haes it been delytit?",
+ "config-extension-link": "Did ye ken that yer wiki supports [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensions]?\n\nYe can brouse [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions bi categorie] or the [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] tae see the full leet o extensions.",
+ "mainpagetext": "<strong>MediaWiki haes been installit wi speed.</strong>",
+ "mainpagedocfooter": "Consult the [//meta.wikimedia.org/wiki/Help:Contents/sco Uiser's Guide] fer information oan uisin the wiki saffware.\n\n== Gettin stairtit ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Confeeguration settins leet]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailin leet]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki fer yer leid]"
+}
diff --git a/includes/installer/i18n/sdc.json b/includes/installer/i18n/sdc.json
new file mode 100644
index 00000000..9d7835a4
--- /dev/null
+++ b/includes/installer/i18n/sdc.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jun Misugi",
+ "Seb35"
+ ]
+ },
+ "config-title": "Isthallazioni di MediaWiki $1",
+ "config-information": "Infuimmazioni",
+ "config-localsettings-key": "Ciabi di attuarizazioni",
+ "config-your-language": "Linga tòia",
+ "config-wiki-language": "Linga di la Vichi",
+ "config-back": "← Indareddu",
+ "config-continue": "Continuà →",
+ "config-page-language": "Linga",
+ "config-page-welcome": "Binvinuddi in MediaWiki!",
+ "mainpagetext": "'''Isthallazioni di MediaWiki accabadda currentementi.'''",
+ "mainpagedocfooter": "Cunsultha la [//meta.wikimedia.org/wiki/Help:Contents Ghia utenti] pa maggiori infuimmazioni i l'usu di chisthu software wiki.\n\n== Pa ischuminzà ==\nLi sighenti cullegamenti so in linga ingrese:\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Impusthazioni di cunfigurazioni]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Prigonti friquenti i MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annùnzii MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/sei.json b/includes/installer/i18n/sei.json
new file mode 100644
index 00000000..19c8ef32
--- /dev/null
+++ b/includes/installer/i18n/sei.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki coccebj installöx successua zo mii.'''"
+}
diff --git a/includes/installer/i18n/sh.json b/includes/installer/i18n/sh.json
new file mode 100644
index 00000000..da8a16e8
--- /dev/null
+++ b/includes/installer/i18n/sh.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "OC Ripper",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki softver is uspješno instaliran.'''",
+ "mainpagedocfooter": "Kontaktirajte [//meta.wikimedia.org/wiki/Help:Contents uputstva za korisnike] za informacije o upotrebi wiki programa.\n\n== Početak ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista postavki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki najčešće postavljana pitanja]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/shi.json b/includes/installer/i18n/shi.json
new file mode 100644
index 00000000..beecd242
--- /dev/null
+++ b/includes/installer/i18n/shi.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dalinanir",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki tǧizn (tsrbk) bla tamukrist.'''",
+ "mainpagedocfooter": "Ẓr taǧttnn [//meta.wikimedia.org/wiki/Help:Contents/fr Guide de l’utilisateur] bac ad tawit inɣmisn yaḍn f manik sa tswwurt asɣẓan ad.\n\n== Izwir d MediaWiki ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Umuɣ n iɣwwarn n usgadda ]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/fr Isqqsitn f MidyWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Umuɣ n imsgdaln f imbḍitn n MidyaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/si.json b/includes/installer/i18n/si.json
new file mode 100644
index 00000000..7dd0baad
--- /dev/null
+++ b/includes/installer/i18n/si.json
@@ -0,0 +1,142 @@
+{
+ "@metadata": {
+ "authors": [
+ "Singhalawap",
+ "පසිඳු කාවින්ද",
+ "Sahan.ssw"
+ ]
+ },
+ "config-desc": "මාධ්‍යවිකි සඳහා වූ ස්ථාපකය",
+ "config-title": "මාධ්‍යවිකි $1 ස්ථාපනය",
+ "config-information": "තොරතුරු",
+ "config-localsettings-key": "උසස්කිරීම් යතුර:",
+ "config-localsettings-badkey": "ඔබ ඉදිරිපත් කෙරූ යතුර වැරදිය.",
+ "config-session-error": "සැසිය ඇරඹීමේ දෝෂය: $1",
+ "config-your-language": "ඔබේ භාෂාව:",
+ "config-wiki-language": "විකි භාෂාව:",
+ "config-back": "← ආපසු",
+ "config-continue": "ඉදිරියට →",
+ "config-page-language": "භාෂාව",
+ "config-page-welcome": "මාධ්‍යවිකි වෙත පිළිගනිමු!",
+ "config-page-dbconnect": "දත්ත සංචිතයට සම්බන්ධ කරන්න",
+ "config-page-upgrade": "පවත්නා ස්ථාපනය උසස් කරන්න",
+ "config-page-dbsettings": "දත්ත සංචිත සැකසුම්",
+ "config-page-name": "නම",
+ "config-page-options": "විකල්ප",
+ "config-page-install": "ස්ථාපනය",
+ "config-page-complete": "සම්පූර්ණයි!",
+ "config-page-restart": "ස්ථාපනය යළි අරඹන්න",
+ "config-page-readme": "මාව කියවන්න",
+ "config-page-releasenotes": "නිකුතු සටහන්",
+ "config-page-copying": "පිටපත් කරමින්",
+ "config-page-upgradedoc": "උසස් කරමින්",
+ "config-page-existingwiki": "පවත්නා විකිය",
+ "config-env-php": "PHP $1 ස්ථාපිතයි.",
+ "config-db-type": "දත්ත සංචිත වර්ගය:",
+ "config-db-host": "දත්ත සංචිත ධාරක:",
+ "config-db-wiki-settings": "මෙම විකිය හඳුනා ගන්න",
+ "config-db-name": "දත්ත සංචිතයේ නම:",
+ "config-db-name-oracle": "දත්ත සංචිත සංක්ෂිප්ත නිරූපණය:",
+ "config-db-install-account": "ස්ථාපනය සඳහා පරිශීලක ගිණුම",
+ "config-db-username": "දත්ත සංචිතයේ පරිශීලක නාමය:",
+ "config-db-password": "දත්ත සංචිතයේ මුරපදය:",
+ "config-db-wiki-account": "සාමාන්‍ය ක්‍රියාකාරිත්වය සඳහා පරිශීලක ගිණුම",
+ "config-db-prefix": "දත්ත සංචිත වගු උපසර්ගය:",
+ "config-db-charset": "දත්ත සංචිත අක්ෂර කට්ටලය",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 ද්විමය",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 ආපස්සට-ගැළපෙන UTF-8",
+ "config-db-port": "දත්ත සංචිතයේ කවුළුව:",
+ "config-db-schema": "මාධ්‍යවිකි සඳහා සංක්ෂිප්ත නිරූපණය:",
+ "config-pg-test-error": "'''$1''' දත්ත සංචිතය වෙත සම්බන්ධ විය නොහැක: $2",
+ "config-sqlite-dir": "SQLite දත්ත නාමවලිය:",
+ "config-oracle-def-ts": "සාමාන්‍ය වගු අවකාශය:",
+ "config-oracle-temp-ts": "තාවකාලික වගු අවකාශය:",
+ "config-header-mysql": "MySQL සැකසුම්",
+ "config-header-postgres": "PostgreSQL සැකසුම්",
+ "config-header-sqlite": "SQLite සැකසුම්",
+ "config-header-oracle": "ඔරකල් සැකසුම්",
+ "config-invalid-db-type": "වලංගු නොවන දත්ත සංචිත වර්ගය",
+ "config-missing-db-name": "\"දත්ත සංචිත නාමය\" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ",
+ "config-missing-db-host": "\"දත්ත සංචිත ධාරකය\" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ",
+ "config-missing-db-server-oracle": "\"දත්ත සංචිත TNS\" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ",
+ "config-regenerate": "නැවත ජනිත කරන්න LocalSettings.php →",
+ "config-db-web-account": "ජාල ප්‍රවේශනය සඳහා දත්ත සංචිත ගිණුම",
+ "config-mysql-engine": "ආචයන එන්ජිම:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "ද්විමය",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-windowsauth": "windows සහතික කිරීම.",
+ "config-site-name": "විකියෙහි නම:",
+ "config-site-name-blank": "අඩවි නාමයක් යොදන්න.",
+ "config-project-namespace": "ව්‍යාපෘතියේ නාමඅවකාශය:",
+ "config-ns-generic": "ව්‍යාපෘතිය",
+ "config-ns-site-name": "විකියෙහි නම ලෙසම: $1",
+ "config-ns-other": "වෙනත් (විශේෂණය කරන්න)",
+ "config-ns-other-default": "මගේවිකිය",
+ "config-admin-box": "පරිපාලක ගිණුම",
+ "config-admin-name": "ඔබේ පරිශීලක නාමය:",
+ "config-admin-password": "මුරපදය:",
+ "config-admin-password-confirm": "මුරපදය නැවතත්:",
+ "config-admin-name-blank": "පරිපාලක පරිශීලක නාමය යොදන්න.",
+ "config-admin-password-blank": "පරිපාලක ගිණුම සඳහා මුරපදය යොදන්න.",
+ "config-admin-password-mismatch": "ඔබ ඇතුළු කල මුරපද දෙක නොගැලපේ.",
+ "config-admin-email": "විද්‍යුත්-තැපැල් ලිපිනය:",
+ "config-admin-error-bademail": "ඔබ විසින් වලංගු නොවන විද්‍යුත්-ලිපිනයක් යොදා ඇත.",
+ "config-optional-continue": "මගෙන් තව ප්‍රශ්ණ අහන්න.",
+ "config-optional-skip": "මම දැනටමත් කම්මැලි වී ඇත, විකිය ස්ථාපනය කරන්න.",
+ "config-profile": "පරිශීලක හිමිකම් පැතිකඩ:",
+ "config-profile-wiki": "සාම්ප්‍රදායික විකිය",
+ "config-profile-no-anon": "ගිණුම් තැනීම අවශ්‍යයි",
+ "config-profile-fishbowl": "අවසරලත් සංස්කාරකවරුන් පමණි",
+ "config-profile-private": "පුද්ගලික විකිය",
+ "config-license": "කතුහිමිකම සහ බලපත්‍රය:",
+ "config-license-none": "බලපත්‍ර පාද තලයක් නොමැත",
+ "config-license-cc-by-sa": "නිර්මාණාත්මක පොදුජන ආරෝපණය හුවමාරුවට සමානව",
+ "config-license-cc-by": "නිර්මාණාත්මක පොදුජන ආරෝපණය",
+ "config-license-cc-by-nc-sa": "නිර්මාණාත්මක පොදුජන ආරෝපණය වාණිජ්‍ය-නොවන හුවමාරුවට සමානව",
+ "config-license-pd": "පොදු වසම",
+ "config-email-settings": "විද්‍යුත්-තැපැල් සැකසුම්",
+ "config-enable-email": "පිටතට යොමු වූ විද්‍යුත්-තැපෑල සක්‍රිය කරන්න",
+ "config-email-user": "පරිශීලක-වෙත-පරිශීලක විද්‍යුත්-තැපෑල සක්‍රිය කරන්න",
+ "config-email-usertalk": "පරිශීලක කතාබහ පිටු නිවේදනය සක්‍රිය කරන්න",
+ "config-email-watchlist": "මුරලැයිස්තු නිවේදනය සක්‍රිය කරන්න",
+ "config-email-auth": "විද්‍යුත්-තැපැල් සහතික කිරීම සක්‍රිය කරන්න",
+ "config-email-sender": "ප්‍රත්‍යාගමන විද්‍යුත්-තැපැල් ලිපිනය:",
+ "config-upload-settings": "පින්තූර සහ ගොනු උඩුගත කිරීම්",
+ "config-upload-enable": "ගොනු උඩුගත කිරීම් සක්‍රිය කරන්න",
+ "config-upload-deleted": "මැකූ ගොනු සඳහා නාමාවලිය:",
+ "config-logo": "ලාංඡනයේ URL:",
+ "config-instantcommons": "ක්ෂණික කොමන්ස් සක්‍රිය කරන්න",
+ "config-cc-again": "නැවත ඇහිඳගන්න...",
+ "config-advanced-settings": "උසස් වින්‍යාසගතකෙරුම",
+ "config-cache-options": "වස්තු කෑෂය සඳහා සැකසුම්:",
+ "config-memcached-servers": "මතකකෑෂිත සර්වරයන්:",
+ "config-extensions": "විස්තීර්ණ",
+ "config-install-step-done": "සිදුකලා",
+ "config-install-step-failed": "අසාර්ථකයි",
+ "config-install-extensions": "විස්තීර්ණ අඩංගු කරමින්",
+ "config-install-database": "දත්ත සංචිතය සකසමින්",
+ "config-install-schema": "සංක්ෂිප්ත නිරූපණය තනමින්",
+ "config-install-pg-schema-not-exist": "PostgreSQL සංක්ෂිප්ත නිරූපණය නොපවතියි.",
+ "config-install-pg-commit": "වෙනස්කම් ප්‍රයාපනය කරමින්",
+ "config-install-pg-plpgsql": "PL/pgSQL භාෂාව සඳහා පරික්ෂා කරමින්",
+ "config-install-user": "දත්ත සංචිත පරිශීලක තනමින්",
+ "config-install-user-alreadyexists": "\"$1\" පරිශීලක දැනටමත් පවතී",
+ "config-install-user-create-failed": "\"$1\" පරිශීලක තැනීම අසාර්ථකයි: $2",
+ "config-install-user-missing": "විශේෂණය කෙරූ \"$1\" පරිශීලකයා නොපවතියි.",
+ "config-install-tables": "වගු තනමින්",
+ "config-install-interwiki": "සාමාන්‍ය අන්තර්විකි වගුව ගහනය කරමින්",
+ "config-install-interwiki-list": "<code>interwiki.list</code> ගොනුව කියවිය නොහැක.",
+ "config-install-stats": "සංඛ්‍යානය අරඹමින්",
+ "config-install-keys": "රහස් යතුරු ජනිත කරමින්",
+ "config-install-sysop": "පරිපාලක පරිශීලක ගිණුම තනමින්",
+ "config-install-mainpage": "සාමාන්‍ය අන්තර්ගතය සමඟින් ප්‍රධාන පිටුව තනමින්",
+ "config-install-mainpage-failed": "ප්‍රධාන පිටුව ඇතුල් කල නොහැක: $1",
+ "config-download-localsettings": "<code>LocalSettings.php</code> බාගන්න",
+ "config-help": "උදව්",
+ "config-nofile": "\"$1\" ගොනුව සොයාගත නොහැක. එක මැකිලා ගියාවත්ද?",
+ "mainpagetext": "'''මීඩියාවිකි සාර්ථක ලෙස ස්ථාපනය කරන ලදි.'''",
+ "mainpagedocfooter": "විකි මෘදුකාංග භාවිතා කිරීම පිළිබඳ තොරතුරු සඳහා [//meta.wikimedia.org/wiki/Help:Contents පරිශීලකයන් සඳහා නියමුව] හදාරන්න.\n\n== ඇරඹුම ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings වින්‍යාස සැකසුම්]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ මීඩියාවිකි නිති-විමසන-පැන]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce මීඩියාවිකි නිකුතුව තැපැල් ලැයිස්තුව]"
+}
diff --git a/includes/installer/i18n/sk.json b/includes/installer/i18n/sk.json
new file mode 100644
index 00000000..ad6bca7a
--- /dev/null
+++ b/includes/installer/i18n/sk.json
@@ -0,0 +1,78 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kusavica",
+ "KuboF",
+ "Sudo77(new)"
+ ]
+ },
+ "config-desc": "Inštalátor pre MediaWiki",
+ "config-title": "Inštalácia MediaWiki $1",
+ "config-information": "Informácie",
+ "config-localsettings-key": "Aktualizačný kľúč:",
+ "config-localsettings-badkey": "Zadaný kľúč je nesprávny.",
+ "config-your-language": "Váš jazyk:",
+ "config-your-language-help": "Vyberte jazyk, ktorý chcete použiť počas inštalácie.",
+ "config-wiki-language": "Wiki jazyk:",
+ "config-wiki-language-help": "Vyberte jazyk, v ktorom bude wiki napísaná.",
+ "config-back": "← Späť",
+ "config-continue": "Pokračovať →",
+ "config-page-language": "Jazyk",
+ "config-page-welcome": "Vitajte na MediaWiki!",
+ "config-page-dbconnect": "Pripojiť sa k databáze",
+ "config-page-upgrade": "Aktualizovať existujúcu inštaláciu",
+ "config-page-dbsettings": "Nastavenie databázy",
+ "config-page-name": "Názov",
+ "config-page-options": "Možnosti",
+ "config-page-install": "Inštalovať",
+ "config-page-complete": "Dokončené",
+ "config-page-restart": "Reštartovať inštaláciu",
+ "config-page-readme": "Čítaj ma",
+ "config-page-releasenotes": "Poznámky k vydaniu",
+ "config-page-copying": "Licencia",
+ "config-page-upgradedoc": "Aktualizácia",
+ "config-page-existingwiki": "Existujúca wiki",
+ "config-help-restart": "Chcete vymazať všetky uložené dáta, ktoré ste zadali a reštartovať proces inštalácie?",
+ "config-restart": "Áno, reštartovať",
+ "config-env-good": "Prostredie bolo skontrolované.\nMôžete nainštalovať MediaWiki.",
+ "config-env-bad": "Prostredie bolo skontrolované.\nNemôžete nainštalovať MediaWiki.",
+ "config-env-php": "PHP $1 je nainštalované.",
+ "config-env-php-toolow": "PHP $1 je nainštalované. Avšak, MediaWiki vyžaduje PHP $2 alebo vyššie.",
+ "config-db-type": "Typ databázy:",
+ "config-db-host": "Databázový server:",
+ "config-db-host-oracle": "Databázové TNS:",
+ "config-db-wiki-settings": "Identifikácia tejto wiki",
+ "config-db-name": "Názov databázy:",
+ "config-db-name-oracle": "Databázová schéma:",
+ "config-db-install-account": "Používateľský účet pre inštaláciu",
+ "config-db-username": "Databázové používateľské meno:",
+ "config-db-password": "Databázové heslo:",
+ "config-missing-db-name": "Musíte zadať hodnotu pre \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Musíte zadať hodnotu pre \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Musíte zadať hodnotu pre \"{{int:config-db-host-oracle}}\".",
+ "config-admin-name": "Vaše používateľské meno:",
+ "config-admin-password": "Heslo:",
+ "config-admin-password-confirm": "Zopakuj heslo:",
+ "config-admin-name-blank": "Zadajte používateľské meno správcu.",
+ "config-admin-name-invalid": "Zadané používateľské meno \"<nowiki>$1</nowiki>\" je neplatné. \nZadajte iné meno.",
+ "config-admin-password-blank": "Zadajte heslo ku správcovskému účtu.",
+ "config-admin-password-mismatch": "Zadané heslá sa nezhodujú.",
+ "config-admin-email": "Emailová adresa:",
+ "config-admin-error-bademail": "Zadali ste neplatnú emailovú adresu.",
+ "config-optional-continue": "Opýtaj sa ma ďalšie otázky.",
+ "config-optional-skip": "Už ma to nudí, proste nainštaluj wiki.",
+ "config-profile-wiki": "Otvorená wiki",
+ "config-profile-private": "Súkromná wiki",
+ "config-email-settings": "Nastavenia e-mailu",
+ "config-install-step-done": "hotovo",
+ "config-install-step-failed": "zlyhalo",
+ "config-install-extensions": "Inštalujú sa rozšírenia",
+ "config-install-user-alreadyexists": "Používateľ \"$1\" už existuje",
+ "config-install-tables-failed": "<strong>Chyba:</strong> Vytvorenie tabuľky zlyhalo s nasledujúcou chybou: $1",
+ "config-download-localsettings": "Stiahnuť <code>LocalSettings.php</code>",
+ "config-help": "nápoveda",
+ "config-nofile": "Súbor \"$1\" sa nenašiel. Bol zmazaný?",
+ "config-extension-link": "Vedeli ste, že vaša wiki podporuje [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions rozšírenia]?\nMôžete hľadať rozšírenia [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category podľa kategórie] alebo si pozrite [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] - kompletný zoznam rozšírení.",
+ "mainpagetext": "'''Softvér MediaWiki bol úspešne nainštalovaný.'''",
+ "mainpagedocfooter": "Informácie ako používať wiki softvér nájdete v [//meta.wikimedia.org/wiki/Help:Contents Používateľskej príručke].\n\n== Začíname ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Zoznam konfiguračných nastavení]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Časté otázky o MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-poštová konferencia oznámení o MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Preklad MediaWiki do vášho jazyka]"
+}
diff --git a/includes/installer/i18n/sl.json b/includes/installer/i18n/sl.json
new file mode 100644
index 00000000..b27fcdd3
--- /dev/null
+++ b/includes/installer/i18n/sl.json
@@ -0,0 +1,174 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dbc334",
+ "Eleassar",
+ "Yerpo"
+ ]
+ },
+ "config-desc": "Namestitveni program za MediaWiki",
+ "config-title": "Namestitev MediaWiki $1",
+ "config-information": "Informacije",
+ "config-localsettings-upgrade": "Zaznana je bila datoteka <code>LocalSettings.php</code>.\nZa nadgradnjo te inštalacije prosim vnesite vrednost <code>$wgUpgradeKey</code> v polje za vnos spodaj.\nNašli jo boste v <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Zaznana je bila datoteka <code>LocalSettings.php</code>.\nZa nadgradnjo te namestitve zaženite <code>update.php</code>",
+ "config-localsettings-key": "Nadgraditveni ključ:",
+ "config-localsettings-badkey": "Naveden ključ je napačen.",
+ "config-upgrade-key-missing": "Zaznana je bila obstoječa namestitev MediaWiki.\nZa nadgradnjo te namestitve vstavite naslednjo vrstico na dno vaše <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Kaže, da je obstoječa datoteka <code>LocalSettings.php</code> nepopolna. Vrednost $1 ni nastavljena. Prosimo, nastavite to vrednost v <code>LocalSettings.php</code> in kliknite \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Prišlo je do napake pri povezovanju s podatkovno zbirko z nastavitvami, določenimi v <code>LocalSettings.php</code>. Prosimo popravite te nastavitve in poskusite znova.\n\n$1",
+ "config-session-error": "Napaka pri začenjanju seje: $1",
+ "config-session-expired": "Kot kaže, so vaši podatki seje potekli.\nSeje so konfigurirane za dobo $1.\nTo lahko povečate tako, da nastavite <code>session.gc_maxlifetime</code> v php.ini.\nPonovno zaženite postopek namestitve.",
+ "config-no-session": "Vaši podatki seje so bili izgubljeni!\nPreverite vaš php.ini in se prepričajte, da je <code>session.save_path</code> nastavljena na ustrezno mapo.",
+ "config-your-language": "Vaš jezik:",
+ "config-your-language-help": "Izberite jezik, ki bo uporabljen med postopkom namestitve.",
+ "config-wiki-language": "Jezik wikija:",
+ "config-wiki-language-help": "Izberite jezik, v katerem bo wiki večinoma pisan.",
+ "config-back": "← Nazaj",
+ "config-continue": "Nadaljuj →",
+ "config-page-language": "Jezik",
+ "config-page-welcome": "Dobrodošli na MediaWiki!",
+ "config-page-dbconnect": "Vzpostavi povezavo z zbirko podatkov",
+ "config-page-upgrade": "Nadgradi obstoječo namestitev",
+ "config-page-dbsettings": "Nastavitve zbirke podatkov",
+ "config-page-name": "Ime",
+ "config-page-options": "Možnosti",
+ "config-page-install": "Namesti",
+ "config-page-complete": "Končano!",
+ "config-page-restart": "Ponovno zaženi namestitev",
+ "config-page-readme": "Beri me",
+ "config-page-releasenotes": "Opombe ob izidu",
+ "config-page-copying": "Kopiranje",
+ "config-page-upgradedoc": "Nadgrajevanje",
+ "config-page-existingwiki": "Obstoječ wiki",
+ "config-help-restart": "Želite počistiti vse shranjene podatke, ki ste jih vnesti, in ponovno začeti s postopkom namestitve?",
+ "config-restart": "Da, ponovno zaženi",
+ "config-welcome": "=== Pregledi okolja ===\nIzvedli bomo osnovne preglede, da vidimo, če je okolje primerno za namestitev MediaWiki.\nPosredujte rezultate teh pregledov, če med namestitvijo potrebujete pomoč.",
+ "config-sidebar": "* [//www.mediawiki.org Domača stran MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Vodnik za uporabnike]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Vodnik za administratorje]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Pogosto zastavljena vprašanja]\n----\n* <doclink href=Readme>Beri me</doclink>\n* <doclink href=ReleaseNotes>Opombe ob izidu</doclink>\n* <doclink href=Copying>Kopiranje</doclink>\n* <doclink href=UpgradeDoc>Nadgrajevanje</doclink>",
+ "config-env-good": "Okolje je pregledano.\nLahko namestite MediaWiki.",
+ "config-env-bad": "Okolje je pregledano.\nNe morete namestiti MediaWiki.",
+ "config-env-php": "Nameščen je PHP $1.",
+ "config-unicode-using-utf8": "Uporaba utf8_normalize.so Briona Vibberja za normalizacijo unikoda.",
+ "config-unicode-using-intl": "Uporaba [http://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-xcache": "[http://xcache.lighttpd.net/ XCache] je nameščen",
+ "config-apc": "[http://www.php.net/apc APC] je nameščen",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] je nameščen",
+ "config-diff3-bad": "GNU diff3 ni bilo mogoče najti.",
+ "config-db-type": "Vrsta zbirke podatkov:",
+ "config-db-host": "Gostitelj zbirke podatkov:",
+ "config-db-host-oracle": "TNS zbirke podatkov:",
+ "config-db-wiki-settings": "Prepoznaj ta wiki:",
+ "config-db-name": "Ime zbirke podatkov:",
+ "config-db-name-oracle": "Shema zbirke podatkov:",
+ "config-db-username": "Uporabniško ime zbirke podatkov:",
+ "config-db-password": "Geslo zbirke podatkov:",
+ "config-db-prefix": "Predpona tabel zbirke podatkov:",
+ "config-db-charset": "Nabor znakov zbirke podatkov",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 dvojiško",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 nazaj združljiv UTF-8",
+ "config-mysql-old": "Potreben je MySQL $1 ali novejši; vi imate $2.",
+ "config-db-port": "Vrata zbirke podatkov:",
+ "config-db-schema": "Shema MediaWiki",
+ "config-db-schema-help": "Ta shema je po navadi v redu.\nSpremenite jo samo, če veste, da jo morate.",
+ "config-sqlite-dir": "Mapa podatkov SQLite:",
+ "config-support-info": "MediaWiki podpira naslednje sisteme zbirk podatkov:\n\n$1\n\nČe zgoraj ne vidite navedenega sistema zbirk podatkov, ki ga poskušate uporabiti, sledite navodilom na spodnji povezavi, da omogočite podporo.",
+ "config-header-mysql": "Nastavitve MySQL",
+ "config-header-postgres": "Nastavitve PostgreSQL",
+ "config-header-sqlite": "Nastavitve SQLite",
+ "config-header-oracle": "Nastavitve Oracle",
+ "config-invalid-db-type": "Neveljavna vrsta zbirke podatkov",
+ "config-missing-db-name": "Vnesti morate vrednost za »{{int:config-db-name}}«",
+ "config-missing-db-host": "Vnesti morate vrednost za »{{int:config-db-host}}«.",
+ "config-missing-db-server-oracle": "Vnesti morate vrednost za »{{int:config-db-host-oracle}}«.",
+ "config-invalid-db-server-oracle": "Neveljaven TNS zbirke podatkov »$1«.\nUporabite ali \"ime TNS\" ali niz \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Načini poimenovanja Oracle])",
+ "config-invalid-db-name": "Neveljavno ime zbirke podatkov »$1«.\nUporabljajte samo črke ASCII (a-z, A-Z), številke (0-9), podčrtaje (_) in vezaje (-).",
+ "config-invalid-db-prefix": "Neveljavna predpona zbirke podatkov »$1«.\nUporabljajte samo črke ASCII (a-z, A-Z), številke (0-9), podčrtaje (_) in vezaje (-).",
+ "config-connection-error": "$1.\n\nPreverite gostitelja, uporabniško ime in geslo spodaj ter poskusite znova.",
+ "config-postgres-old": "Potreben je PostgreSQL $1 ali novejši; vi imate $2.",
+ "config-sqlite-connection-error": "$1.\n\nPreverite mapo podatkov in ime zbirke podatkov spodaj ter poskusite znova.",
+ "config-sqlite-readonly": "Datoteka <code>$1</code> ni zapisljiva.",
+ "config-sqlite-cant-create-db": "Ne morem ustvariti datoteke zbirke podatkov <code>$1</code>.",
+ "config-upgrade-done-no-regenerate": "Nadgradnja je končana.\n\nSedaj lahko [$1 začnete uporabljati vaš wiki].",
+ "config-regenerate": "Ponovno ustvari LocalSettings.php →",
+ "config-show-table-status": "Poizvedba <code>SHOW TABLE STATUS</code> ni uspela!",
+ "config-unknown-collation": "'''Opozorilo:''' Zbirke podatkov uporablja neprepoznano razvrščanje znakov.",
+ "config-db-web-account": "Račun zbirke podatkov za spletni dostop",
+ "config-db-web-account-same": "Uporabi enak račun kot za namestitev",
+ "config-db-web-create": "Ustvari račun, če že ne obstaja",
+ "config-mysql-engine": "Pogon skladiščenja:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Nabor znakov zbirke podatkov:",
+ "config-mysql-binary": "Dvojiško",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "Ime wikija:",
+ "config-site-name-help": "To bo prikazano v naslovni vrstici brskalnika in na drugih različnih mestih.",
+ "config-site-name-blank": "Vnesite ime strani.",
+ "config-project-namespace": "Imenski prostor projekta:",
+ "config-ns-generic": "Projekt",
+ "config-ns-site-name": "Enako kot ime wikija: $1",
+ "config-ns-other": "Drugo (navedite)",
+ "config-ns-other-default": "MojWiki",
+ "config-ns-invalid": "Naveden imenski prostor »<nowiki>$1</nowiki>« ni veljaven.\nDoločite drug imenski prostor projekta.",
+ "config-ns-conflict": "Naveden imenski prostor »<nowiki>$1</nowiki>« je v sporu s privzetim imenskim prostorom MediaWiki.\nDoločite drug imenski prostor projekta.",
+ "config-admin-box": "Administratorski račun",
+ "config-admin-name": "Vaše uporabniško ime:",
+ "config-admin-password": "Geslo:",
+ "config-admin-password-confirm": "Geslo, ponovno:",
+ "config-admin-help": "Tukaj vnesite želeno uporabniško ime, na primer »Janez Blog«.\nTo je ime, ki ga boste uporabljali za prijavo v wiki.",
+ "config-admin-name-blank": "Vnesite uporabniško ime administratorja.",
+ "config-admin-name-invalid": "Navedeno uporabniško ime »<nowiki>$1</nowiki>« ni veljavno.\nDoločite drugo uporabniško ime.",
+ "config-admin-password-blank": "Vnesite geslo za administratorski račun.",
+ "config-admin-password-mismatch": "Vneseni gesli se ne ujemata.",
+ "config-admin-email": "E-poštni naslov:",
+ "config-admin-error-user": "Med ustvarjanjem administratorja »<nowiki>$1</nowiki>« je prišlo do notranje napake.",
+ "config-admin-error-password": "Med nastavljanjem gesla za administratorja »<nowiki>$1</nowiki>« je prišlo do notranje napake: <pre>$2</pre>",
+ "config-admin-error-bademail": "Vnesli ste neveljaven e-poštni naslov.",
+ "config-subscribe": "Naročite se na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce poštni seznam obvestil o izdajah].",
+ "config-almost-done": "Skoraj ste že končali!\nPreostalo konfiguriranje lahko zdaj preskočite in wiki takoj namestite.",
+ "config-optional-continue": "Zastavi mi več vprašanj.",
+ "config-optional-skip": "Se že dolgočasim; samo namesti wiki.",
+ "config-profile": "Profil uporabniških pravic:",
+ "config-profile-wiki": "Odprti wiki",
+ "config-profile-no-anon": "Zahtevano je ustvarjanje računa",
+ "config-profile-fishbowl": "Samo pooblaščeni urejevalci",
+ "config-profile-private": "Zasebni wiki",
+ "config-license": "Avtorske pravice in licenca:",
+ "config-license-none": "Brez noge dovoljenja",
+ "config-license-cc-by-sa": "Creative Commons Priznanje avtorstva-Deljenje pod enakimi pogoji",
+ "config-license-cc-by": "Creative Commons Priznanje avtorstva",
+ "config-license-cc-by-nc-sa": "Creative Commons Priznanje avtorstva-Nekomercialno-Deljenje pod enakimi pogoji",
+ "config-license-cc-0": "Creative Commons Zero (javna last)",
+ "config-license-pd": "Javna last",
+ "config-license-cc-choose": "Izberite dovoljenje Creative Commons po meri",
+ "config-email-settings": "Nastavitve e-pošte",
+ "config-enable-email": "Omogoči odhodno e-pošto",
+ "config-email-user": "Omogoči e-pošto med uporabniki",
+ "config-email-auth": "Omogoči overitev preko e-pošte",
+ "config-email-sender": "E-poštni naslov za vrnjeno pošto:",
+ "config-upload-settings": "Nalaganje slike in datotek",
+ "config-upload-enable": "Omogoči nalaganje datotek",
+ "config-upload-deleted": "Mapa za izbrisane datoteke:",
+ "config-upload-deleted-help": "Izberite mapo za arhiviranje izbrisanih datotek.\nNajbolje je, da mapa ni dostopna preko spleta.",
+ "config-logo": "URL logotipa:",
+ "config-cc-error": "Izbirnik dovoljenja Creative Commons ni vrnil nobenih rezultatov.\nVnesite ime dovoljenja ročno.",
+ "config-cc-again": "Izberi ponovno ...",
+ "config-cc-not-chosen": "Izberite licenco Creative Commons, ki jo želite uporabiti, in kliknite »proceed«.",
+ "config-advanced-settings": "Napredna konfiguracija",
+ "config-cache-accel": "Predpomnjenje predmetov PHP (APC, XCache ali WinCache)",
+ "config-cache-memcached": "Uporabi Memcached (zahteva dodatno namestitev in konfiguracijo)",
+ "config-memcached-servers": "Strežniki Memcached:",
+ "config-memcache-badip": "Vnesli ste neveljaven IP-naslov za Memcached: $1",
+ "config-extensions": "Razširitve",
+ "config-install-step-done": "končano",
+ "config-install-step-failed": "spodletelo",
+ "config-install-database": "Vzpostavljanje zbirke podatkov",
+ "config-install-pg-schema-not-exist": "Shema PostgreSQL ne obstaja.",
+ "config-install-user-alreadyexists": "Uporabnik »$1« že obstaja",
+ "config-install-tables": "Ustvarjanje tabel",
+ "config-download-localsettings": "Prenesi <code>LocalSettings.php</code>",
+ "config-help": "pomoč",
+ "mainpagetext": "'''Programje MediaWiki je bilo uspešno nameščeno.'''",
+ "mainpagedocfooter": "Oglejte si [//meta.wikimedia.org/wiki/Help:Contents Uporabniški priročnik] za informacije o uporabi programja wiki.\n\n== Kako začeti ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Seznam konfiguracijskih nastavitev]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Poogsto zastavljena vprašanja MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Poštni seznam izdaj MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Prevedite MediaWiki v svoj jezik]"
+}
diff --git a/includes/installer/i18n/sli.json b/includes/installer/i18n/sli.json
new file mode 100644
index 00000000..c8fc25f5
--- /dev/null
+++ b/includes/installer/i18n/sli.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Äberlausitzer"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki wourde erfolgreich installiert.'''",
+ "mainpagedocfooter": "Hilfe zur Benutzung und Konfiguration der Wiki-Software fendest du eim [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbichl].\n\n== Stoarthilfa ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste der Konfigurationsvariablen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]"
+}
diff --git a/includes/installer/i18n/so.json b/includes/installer/i18n/so.json
new file mode 100644
index 00000000..95e36873
--- /dev/null
+++ b/includes/installer/i18n/so.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Maax"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki Si fiican oo kuugu install gareeyay.'''",
+ "mainpagedocfooter": "Meeshaan ka akhriso sidii aad u isticmaali leheed brogramka wiki [//meta.wikimedia.org/wiki/Help:Contents User's Guide] .\n== Bilaaw ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/sq.json b/includes/installer/i18n/sq.json
new file mode 100644
index 00000000..f3bb6dd9
--- /dev/null
+++ b/includes/installer/i18n/sq.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki software u instalua me sukses.'''",
+ "mainpagedocfooter": "Për më shumë informata rreth përdorimit të softwerit wiki , ju lutem shikoni [//meta.wikimedia.org/wiki/Help:Contents dokumentacionin përkatës].\n\n== Sa për fillim==\n* [//www.mediawiki.org/wiki/Help:Configuration_settings Parazgjedhjet e MediaWiki-t]\n* [//www.mediawiki.org/wiki/Help:FAQ Pyetjet e shpeshta rreth MediaWiki-t]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWiki-t]"
+}
diff --git a/includes/installer/i18n/sr-ec.json b/includes/installer/i18n/sr-ec.json
new file mode 100644
index 00000000..950ee44e
--- /dev/null
+++ b/includes/installer/i18n/sr-ec.json
@@ -0,0 +1,73 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rancher",
+ "Михајло Анђелковић",
+ "Milicevic01"
+ ]
+ },
+ "config-desc": "Инсталација за Медијавики",
+ "config-title": "Инсталација Медијавикија $1",
+ "config-information": "Информације",
+ "config-session-error": "Грешка при започињању сесије: $1",
+ "config-session-expired": "Ваши подаци о сесији су истекли.\nСесије су подешене да трају $1.\nЊихов рок можете повећати постављањем <code>session.gc_maxlifetime</code> у php.ini.\nПоново покрените инсталацију.",
+ "config-no-session": "Ваши подаци о сесији су изгубљени!\nПроверите Ваш php.ini и обезбедите да је <code>session.save_path</code> постављен на одговарајући директоријум.",
+ "config-your-language": "Ваш језик:",
+ "config-your-language-help": "Изаберите језик који желите да користите током инсталације.",
+ "config-wiki-language": "Језик викија:",
+ "config-wiki-language-help": "Изаберите језик на ком ће бити садржај викија.",
+ "config-back": "← Назад",
+ "config-continue": "Настави →",
+ "config-page-language": "Језик",
+ "config-page-welcome": "Добро дошли на МедијаВики!",
+ "config-page-dbconnect": "Повезивање са базом података",
+ "config-page-upgrade": "Надоградња постојеће инсталације",
+ "config-page-dbsettings": "Подешавања базе података",
+ "config-page-name": "Назив",
+ "config-page-options": "Поставке",
+ "config-page-install": "Инсталирај",
+ "config-page-complete": "Завршено!",
+ "config-page-restart": "Поновно покретање инсталације",
+ "config-page-readme": "Прочитај ме",
+ "config-page-releasenotes": "Белешке издања",
+ "config-page-copying": "Умножавање",
+ "config-page-upgradedoc": "Надоградња",
+ "config-page-existingwiki": "Постојећи вики",
+ "config-help-restart": "Желите ли да обришете све сачуване податке које сте унели и поново покренете инсталацију?",
+ "config-restart": "Да, покрени поново",
+ "config-env-php": "PHP $1 је инсталиран.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] је инсталиран",
+ "config-apc": "[http://www.php.net/apc APC] је инсталиран",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] је инсталиран",
+ "config-db-type": "Тип базе података:",
+ "config-db-host": "Хост базе података",
+ "config-db-name": "Назив базе података:",
+ "config-type-mysql": "MySQL (или компактибилан)",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-header-mysql": "MySQL подешавања",
+ "config-header-mssql": "Поставке Microsoft SQL Server-а",
+ "config-mssql-old": "Потребан је Microsoft SQL Server $1 или новији. Ви имате $2.",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Тип провере идентитета:",
+ "config-mssql-sqlauth": "Провера идентитета за SQL Server",
+ "config-mssql-windowsauth": "Провера идентитета Windows-а",
+ "config-site-name": "Име викија:",
+ "config-admin-name": "Корисничко име:",
+ "config-admin-password": "Лозинка:",
+ "config-admin-email": "Адреса е-поште:",
+ "config-license": "Ауторска права и лиценца:",
+ "config-license-none": "Без заглавља са лиценцом",
+ "config-license-cc-by-sa": "Creative Commons Ауторство-Делити под истим условима (CC BY-SA)",
+ "config-license-cc-by": "Creative Commons Ауторство (CC BY)",
+ "config-license-cc-by-nc-sa": "Creative Commons Ауторство-Некомерцијално-Делити под истим условима (CC BY-NC-SA)",
+ "config-license-cc-0": "Creative Commons Zero (јавно власништво)",
+ "config-license-gfdl": "ГНУ-ова лиценца за слободну документацију верзија 1.3 или новија верзија",
+ "config-license-pd": "Јавно власништво",
+ "mainpagetext": "'''Медијавики је успешно инсталиран.'''",
+ "mainpagedocfooter": "Погледајте [//meta.wikimedia.org/wiki/Help:Contents кориснички водич] за коришћење програма.\n\n== Увод ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Помоћ у вези са подешавањима]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Често постављена питања]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Дописна листа о издањима Медијавикија]"
+}
diff --git a/includes/installer/i18n/sr-el.json b/includes/installer/i18n/sr-el.json
new file mode 100644
index 00000000..a0cd5f55
--- /dev/null
+++ b/includes/installer/i18n/sr-el.json
@@ -0,0 +1,39 @@
+{
+ "@metadata": {
+ "authors": [
+ "Milicevic01"
+ ]
+ },
+ "config-session-error": "Greška pri započinjanju sesije: $1",
+ "config-session-expired": "Vaši podaci o sesiji su istekli.\nSesije su podešene da traju $1.\nNjihov rok možete povećati postavljanjem <code>session.gc_maxlifetime</code> u php.ini.\nPonovo pokrenite instalaciju.",
+ "config-no-session": "Vaši podaci o sesiji su izgubljeni!\nProverite Vaš php.ini i obezbedite da je <code>session.save_path</code> postavljen na odgovarajući direktorijum.",
+ "config-your-language": "Vaš jezik:",
+ "config-your-language-help": "Izaberite jezik koji želite da koristite tokom instalacije.",
+ "config-wiki-language": "Jezik vikija:",
+ "config-wiki-language-help": "Izaberite jezik na kom će biti sadržaj vikija.",
+ "config-back": "← Nazad",
+ "config-continue": "Nastavi →",
+ "config-page-language": "Jezik",
+ "config-page-welcome": "Dobro došli na MedijaViki!",
+ "config-page-dbconnect": "Povezivanje sa bazom podataka",
+ "config-page-upgrade": "Nadogradnja postojeće instalacije",
+ "config-page-dbsettings": "Podešavanja baze podataka",
+ "config-page-name": "Naziv",
+ "config-page-options": "Postavke",
+ "config-page-install": "Instaliraj",
+ "config-page-complete": "Završeno!",
+ "config-page-restart": "Ponovno pokretanje instalacije",
+ "config-page-copying": "Umnožavanje",
+ "config-page-upgradedoc": "Nadogradnja",
+ "config-page-existingwiki": "Postojeći viki",
+ "config-help-restart": "Želite li da obrišete sve sačuvane podatke koje ste uneli i ponovo pokrenete instalaciju?",
+ "config-restart": "Da, pokreni ponovo",
+ "config-type-mysql": "MySQL",
+ "config-type-postgres": "PostgreSQL",
+ "config-type-sqlite": "SQLite",
+ "config-type-oracle": "Oracle",
+ "config-site-name": "Ime vikija:",
+ "config-license-cc-0": "Creative Commons Zero (javno vlasništvo)",
+ "mainpagetext": "'''MedijaViki je uspešno instaliran.'''",
+ "mainpagedocfooter": "Molimo vidite [//meta.wikimedia.org/wiki/Help:Contents korisnički vodič] za informacije o upotrebi viki softvera.\n\n== Za početak ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Pomoć u vezi sa podešavanjima]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Najčešće postavljena pitanja]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mejling lista o izdanjima MedijaVikija]"
+}
diff --git a/includes/installer/i18n/srn.json b/includes/installer/i18n/srn.json
new file mode 100644
index 00000000..5081d2a2
--- /dev/null
+++ b/includes/installer/i18n/srn.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki seti kon bun.'''",
+ "mainpagedocfooter": "Luku na ini a [//meta.wikimedia.org/wiki/Help:Contents yepibuku] fu si fa fu kebrouki a wikisoftware.\n\n== Moro yepi ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Den seti]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Sani di ben aksi furu (FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Boskopu grupu gi nyun meki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/ss.json b/includes/installer/i18n/ss.json
new file mode 100644
index 00000000..b1d87cd7
--- /dev/null
+++ b/includes/installer/i18n/ss.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''i-MediaWiki seyifakeke ngalokuphelele.'''"
+}
diff --git a/includes/installer/i18n/stq.json b/includes/installer/i18n/stq.json
new file mode 100644
index 00000000..2455e0b1
--- /dev/null
+++ b/includes/installer/i18n/stq.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Maartenvdbent"
+ ]
+ },
+ "mainpagetext": "'''Ju MediaWiki Software wuude mäd Ärfoulch installierd.'''",
+ "mainpagedocfooter": "Sjuch ju [//meta.wikimedia.org/wiki/MediaWiki_localization Dokumentation tou de Anpaasenge fon dän Benutseruurfläche] un dät [//meta.wikimedia.org/wiki/Help:Contents Benutserhondbouk] foar Hälpe tou ju Benutsenge un Konfiguration."
+}
diff --git a/includes/installer/i18n/su.json b/includes/installer/i18n/su.json
new file mode 100644
index 00000000..7a0976b3
--- /dev/null
+++ b/includes/installer/i18n/su.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''''Software'' MediaWiki geus diinstal.'''",
+ "mainpagedocfooter": "Mangga tingal ''[//meta.wikimedia.org/wiki/MediaWiki_localisation documentation on customizing the interface]'' jeung [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Tungtunan Pamaké] pikeun pitulung maké jeung konfigurasi."
+}
diff --git a/includes/installer/i18n/sv.json b/includes/installer/i18n/sv.json
new file mode 100644
index 00000000..2bdfb100
--- /dev/null
+++ b/includes/installer/i18n/sv.json
@@ -0,0 +1,330 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jopparn",
+ "Skalman",
+ "WikiPhoenix",
+ "Josve05a",
+ "Lokal Profil",
+ "Tobulos1"
+ ]
+ },
+ "config-desc": "Installationsprogrammet för MediaWiki",
+ "config-title": "Installation av MediaWiki $1",
+ "config-information": "Information",
+ "config-localsettings-upgrade": "A <code>LocalSettings.php</code>-fil har upptäckts.\nFör att uppgradera den här installationen, vänligen ange värdet för <code>$wgUpgradeKey</code> i rutan nedan.\nDu hittar den i <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "En <code>LocalSettings.php</code>-fil har upptäckts.\nFör att uppgradera denna installation, kör <code>update.php</code> istället",
+ "config-localsettings-key": "Uppgraderingsnyckel:",
+ "config-localsettings-badkey": "Nyckeln du angav är inkorrekt.",
+ "config-upgrade-key-missing": "En existerande installation av MediaWiki har upptäckts.\nFör att uppgradera installationen, lägg till följande rad i slutet av din <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Den befintliga <code>LocalSettings.php</code> verkar vara ofullständig.\nVariabeln $1 är inte inställd.\nÄndra <code>LocalSettings.php</code> så att denna variabel är inställd och klicka på \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Ett fel uppstod vid anslutning till databasen med inställningarna angivna i <code>LocalSettings.php</code>. Vänligen åtgärda dessa inställningar och försök igen.\n\n$1",
+ "config-session-error": "Fel vid uppstart av session: $1",
+ "config-session-expired": "Dina sessionsdata verkar har gått ut.\nSessioner är konfigurerade för en livstid på $1.\nDu kan öka denna genom att ange <code>session.gc_maxlifetime</code> i php.ini.\nStarta om installationen.",
+ "config-no-session": "Din sessionsdata förlorades!\nKolla din php.ini och se till att <code>session.save_path</code> är inställd på en lämplig katalog.",
+ "config-your-language": "Ditt språk:",
+ "config-your-language-help": "Välj ett språk att använda under installationen.",
+ "config-wiki-language": "Wikispråk:",
+ "config-wiki-language-help": "Välj det språk som wikin främst kommer att skrivas i.",
+ "config-back": "← Tillbaka",
+ "config-continue": "Fortsätt →",
+ "config-page-language": "Språk",
+ "config-page-welcome": "Välkommen till MediaWiki!",
+ "config-page-dbconnect": "Anslut till databas",
+ "config-page-upgrade": "Uppgradera existerande installation",
+ "config-page-dbsettings": "Databasinställningar",
+ "config-page-name": "Namn",
+ "config-page-options": "Alternativ",
+ "config-page-install": "Installera",
+ "config-page-complete": "Slutfört!",
+ "config-page-restart": "Starta om installationen",
+ "config-page-readme": "Läs mig",
+ "config-page-releasenotes": "Utgivningsanteckningar",
+ "config-page-copying": "Kopiering",
+ "config-page-upgradedoc": "Uppgradering",
+ "config-page-existingwiki": "Befintlig wiki",
+ "config-help-restart": "Vill du rensa all sparad data som du har angivit och starta om installationen?",
+ "config-restart": "Ja, starta om",
+ "config-welcome": "=== Miljökontroller ===\nGrundläggande kontroller kommer nu att utföras för att se om denna miljö är lämplig för installation av MediaWiki.\nKom ihåg att ta med denna information om du söker stöd för hur du skall slutföra installationen.",
+ "config-copyright": "=== Upphovsrätt och Villkor ===\n\n$1\n\nDetta program är fri programvara; du kan vidaredistribuera den och/eller modifiera det enligt villkoren i GNU General Public License som publicerats av Free Software Foundation; antingen genom version 2 av licensen, eller (på ditt initiativ) någon senare version.\n\nDetta program är distribuerat i hopp om att det kommer att vara användbart, men '''utan någon garanti'''; utan att ens ha en underförstådd garanti om '''säljbarhet''' eller '''lämplighet för ett särskilt ändamål'''.\nSe GNU General Public License för mer detaljer.\n\nDu bör ha fått <doclink href=Copying>en kopia av GNU General Public License</doclink> tillsammans med detta program; om inte, skriv till Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, eller [http://www.gnu.org/copyleft/gpl.html läs den online].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWikis webbplats]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Användarguide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administratörguide]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Frågor och svar]\n----\n* <doclink href=Readme>Läs mig</doclink>\n* <doclink href=ReleaseNotes>Utgivningsanteckningar</doclink>\n* <doclink href=Copying>Kopiering</doclink>\n* <doclink href=UpgradeDoc>Uppgradering</doclink>",
+ "config-env-good": "Miljön har kontrollerats.\nDu kan installera MediaWiki.",
+ "config-env-bad": "Miljön har kontrollerats.\nDu kan inte installera MediaWiki.",
+ "config-env-php": "PHP $1 är installerat.",
+ "config-env-hhvm": "HHVM $1 är installerat.",
+ "config-unicode-using-utf8": "Använder Brion Vibbers utf8_normalize.so för Unicode-normalisering.",
+ "config-unicode-using-intl": "Använder [http://pecl.php.net/intl intl PECL-tillägget] för Unicode-normalisering.",
+ "config-unicode-pure-php-warning": "'''Varning:''' [http://pecl.php.net/intl intl PECL-tillägget] är inte tillgängligt för att hantera Unicode-normalisering, faller tillbaka till en långsamt implementering i ren PHP.\nOm du driver en högtrafikerad webbplats bör du läsa lite om [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-normalisering].",
+ "config-unicode-update-warning": "'''Varning:''' Den installerade versionen av Unicode-normaliserings \"wrappern\" använder en äldre version av [http://site.icu-project.org/ ICU projektets] bibliotek.\nDu bör [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations uppgradera] om är intresserad av att använda Unicode.",
+ "config-no-db": "Kunde inte hitta en lämplig databasdrivrutin! Du måste installera en databasdrivrutin för PHP.\nFöljande databastyper stöds: $1.\n\nI du själv kompilerat din PHP, konfigurera den med en databasklient aktiverad genom att t.ex. använda <code>./configure --with-mysqli</code>.\nOm du installerade PHP från ett Debian- eller Ubuntupaket måste du även installera, t.ex. <code>php5-mysql</code>-paketet.",
+ "config-outdated-sqlite": "'''Varning:''' du har SQLite $1, vilket är lägre än minimikravet version $2. SQLite kommer inte att vara tillgänglig.",
+ "config-no-fts3": "'''Varning:''' SQLite kompileras utan [//sqlite.org/fts3.html FTS3-modulen], sökfunktioner kommer att vara otillgängliga på denna backend.",
+ "config-register-globals-error": "<strong>Fel: PHP-alternativet <code>[http://php.net/register_globals register_globals]</code> är aktiverad.\nDen måste vara inaktiverad för att fortsätta med installationen.</strong>\nSe [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] för hjälp om hur man gör så.",
+ "config-magic-quotes-gpc": "<strong>Kritiskt: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_gpc är aktiv!</strong>\nDetta alternativ korrumperar inmatad data oförutsägbart.\nDu kan inte installera eller använda MediaWiki om detta alternativ är aktiverat.",
+ "config-magic-quotes-runtime": "'''Kritiskt: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] är aktiv!'''\nDetta alternativ korrumperar inmatad data oförutsägbart.\nDu kan inte installera eller använda MediaWiki om detta alternativ är aktiverat.",
+ "config-magic-quotes-sybase": "'''Kritiskt: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] är aktiv!'''\nDetta alternativ korrumperar inmatad data oförutsägbart.\nDu kan inte installera eller använda MediaWiki om detta alternativ är aktiverat.",
+ "config-mbstring": "'''Kritiskt: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] är aktiv!'''\nDetta alternativ orsakar fel och kan korrumpera data oförutsägbart.\nDu kan inte installera eller använda MediaWiki om detta alternativ är aktiverat.",
+ "config-safe-mode": "''' Varning:''' PHP:s [http://www.php.net/features.safe-mode felsäkra läge] är aktivt.\nDet kan orsaka problem, särskilt om du använder filuppladdningar och <code>math</code>-stöd.",
+ "config-xml-bad": "PHP:s XML-modul saknas.\nMediaWiki kräver funktioner i denna modul och kommer inte att fungera i den här konfigurationen.\nOm du kör Mandrake, installera php-xml-paketet.",
+ "config-pcre-old": "'''Kritiskt:''' PCRE $1 eller senare krävs.\nDin PHP-binär är länkad till PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Mer information].",
+ "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-ctype": "'''Kritiskt:''' PHP måste kompileras med stöd för [http://www.php.net/manual/en/ctype.installation.php Ctype-tillägget].",
+ "config-iconv": "<strong>Kritiskt:</strong> PHP måste kompileras med stöd för [http://www.php.net/manual/en/iconv.installation.php iconv-tillägget].",
+ "config-json": "'''Varning:''' PHP kompilerades utan JSON-stöd.\nDu måste antingen installera PHP JSON-tillägget eller [http://pecl.php.net/package/jsonc PECL jsonc]-tillägget före installationen av MediaWiki.\n* PHP-tillägget är inkluderat i Red Hat Enterprise Linux (CentOS) 5 och 6, men måste aktiveras i <code>/etc/php.ini</code> eller <code>/etc/php.d/json.ini</code>.\n* Vissa Linux-distributioner släppta efter maj 2013 har utelämnat PHP-tillägget och har istället inkluderat PECL-tillägget som <code>php5-json</code> eller <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] är installerat",
+ "config-apc": "[http://www.php.net/apc APC] är installerat",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] är installerat",
+ "config-no-cache": "'''Varning:''' Kunde inte hitta [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].\nCachelagring av objekt är inte aktiverat.",
+ "config-mod-security": "'''Varning:''' Din webbserver har [http://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å [http://modsecurity.org/documentation/ mod_security-dokumentationen] eller kontakta din värd om du påträffar slumpmässiga fel.",
+ "config-diff3-bad": "GNU diff3 hittades inte.",
+ "config-git": "Hittade Git-mjukvara för versionskontroll: <code>$1</code>.",
+ "config-git-bad": "Git-mjukvara för versionskontroll hittades inte.",
+ "config-imagemagick": "Hittade ImageMagick: <code>$1</code>.\nMiniatyrvisning av bilder kommer att aktiveras om du aktiverar uppladdningar.",
+ "config-gd": "Hittade ett integrerat GD-grafikbibliotek.\nMiniatyrvisning av bilder kommer att aktiveras om du aktiverar uppladdningar.",
+ "config-no-scaling": "Kunde inte hitta GD-biblioteket eller ImageMagick.\nMiniatyrvisning av bilder kommer att inaktiveras.Miniatyrvisning av bilder",
+ "config-no-uri": "'''Fel:''' Kunde inte fastställa det nuvarande URI:et.\nInstallationen avbröts.",
+ "config-no-cli-uri": "'''Varning:''' Ingen <code>--scriptpath</code> är angiven, använder standarden: <code>$1</code> .",
+ "config-using-server": "Använder servernamn \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Använder server-URL \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Varning:''' Din standardkatalog för uppladdningar <code>$1</code> är sårbar för körning av godtyckliga skript.\nÄven om MediaWiki kontrollerar alla uppladdade filer för säkerhetshot är det ändå starkt rekommenderat att [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security stänga detta säkerhetshål] innan du aktiverar uppladdningar.",
+ "config-no-cli-uploads-check": "'''Varning:''' Din standardkatalog för uppladdningar (<code>$1</code>) har inte kontrollerats för sårbarhet från körning av godtyckliga skript under CLI-installationen.",
+ "config-brokenlibxml": "Ditt system har en kombination av PHP och libxml2 som är buggigt och kan orsaka datakorruption i MediaWiki och andra webbprogram.\nUppgradera till libxml2 2.7.3 eller senare ([https//bugs.php.net/bug.php?id=45996 buggfil med PHP]).\nInstallationen avbröts.",
+ "config-suhosin-max-value-length": "Suhosin är installerat och begränsar GET-parametern <code>length</code> till $1 bytes.\nMediaWikis ResourceLoader-komponent kommer att arbeta runt denna begränsning, men det kommer att försämra prestandan.\nOm möjligt bör du sätta <code>suhosin.get.max_value_length</code> till 1024 eller högre i <code>php.ini</code>, och sätta <code>$wgResourceLoaderMaxQueryLength</code> till samma värde som i <code>LocalSettings.php</code>.",
+ "config-db-type": "Databastyp:",
+ "config-db-host": "Databasvärd:",
+ "config-db-host-help": "Om din databasserver är på en annan server, ange då värdnamnet eller IP-adressen här.\n\nOm du använder ett delat webbhotell, bör din leverantör ge dig rätt värdnamn i deras dokumentation.\n\nOm du installerar på en Windowsserver och använder MySQL, kanske \"localhost\" inte fungerar för servernamnet. Om det inte gör det försök med \"127.0.0.1\" som den lokala IP-adressen.\n\nOm du använder PostgreSQL, lämna detta fält blankt för att ansluta via en Unix-socket.",
+ "config-db-host-oracle": "Databas TNS:",
+ "config-db-host-oracle-help": "Ange ett giltigt [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; en tnsnames.ora-fil måste vara synlig för denna installation.<br />Om du använder klientbibliotek 10g eller nyare kan du också använda [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] namngivningsmetoden.",
+ "config-db-wiki-settings": "Identifiera denna wiki",
+ "config-db-name": "Databasnamn:",
+ "config-db-name-help": "Välj ett namn som identifierar din wiki.\nDet bör inte innehålla mellanslag.\n\nOm du använder ett delat webbhotell kan de antingen ge dig ett särskilt databasnamn att använda eller så kan de låta dig skapa en databas via kontrollpanelen.",
+ "config-db-name-oracle": "Databasschema:",
+ "config-db-account-oracle-warn": "Det finns tre stödda scenarier för installationen av Oracle som en backend-databas:\n\nOm du vill skapa ett databaskonto som en del av installationen, ange ett konto med SYSDBA-roll som databaskonto under installationen och ange de önskade autentiseringsuppgifterna för kontot med webb-åtkomst, annars kan du antingen skapa ett konto med webb-åtkomst manuellt och ange enbart detta konto (om den har behörighet att skapa schema-objekt) eller ange två olika konton, en med create-behörighet och en begränsad för webb-åtkomst.\n\nSkript för att skapa ett konto med de korrekta behörigheterna kan hittas i \"maintenance/oracle/\"-katalogen för denna installation. Tänk på att användningen av ett begränsat konto inaktiverar all underhållsmöjlighet med standardkontot.",
+ "config-db-install-account": "Användarkonto för installation",
+ "config-db-username": "Databas-användarnamn:",
+ "config-db-password": "Databas-lösenord:",
+ "config-db-password-empty": "Ange ett lösenord för den nya databasanvändaren: $1.\nÄven om det kan vara möjligt att skapa användare utan lösenord är det inte säkert.",
+ "config-db-username-empty": "Du måste ange ett värde för \"{{int:config-db-username}}\"",
+ "config-db-install-username": "Ange det användarnamn som ska används för att ansluta till databasen under installationsprocessen.\nDetta är inte användarnamnet för ditt MediaWiki-konto; detta är användarnamnet för din databas.",
+ "config-db-install-password": "Ange det lösenord som ska användas för att ansluta till databasen under installationsprocessen.\nDetta är inte lösenordet för ditt MediaWiki-konto; detta är lösenordet för din databas.",
+ "config-db-install-help": "Ange användarnamnet och lösenordet som kommer att användas för att ansluta till databasen under installationsprocessen.",
+ "config-db-account-lock": "Använda samma användarnamn och lösenord under normal drift",
+ "config-db-wiki-account": "Användarkonto för normal drift",
+ "config-db-wiki-help": "Ange det användarnamn och lösenorde som skall användas för att ansluta till databasen under normal wiki-drift. Om kontot inte existerar, och om installationskontot har tillräcklig behörighet, kommer detta användarkontot att skapas med de minimiprivilegier som krävs för att driva wikin.",
+ "config-db-prefix": "Prefix för tabellerna i databasen:",
+ "config-db-prefix-help": "Om du behöver dela en databas mellan flera olika wikis, eller mellan MediaWiki och en annan webbapplikation, kan du välja att lägga till ett prefix till alla tabellnamn för att undvika konflikter.\nAnvänd inte mellanslag.\n\nDet här fältet lämnas vanligtvis tomt.",
+ "config-db-charset": "Databasteckensuppsättning:",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binär",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 bakåtkompatibel UTF-8",
+ "config-charset-help": "'''Varning:''' Om du använder '''bakåtkompatibel UTF-8''' i MySQL 4.1+, och därefter säkerhetskopierar databasen med <code>mysqldump</code>, kan det förstöra alla icke-ASCII-tecken, vilket oåterkalleligt förstör dina säkerhetskopior!\n\nI '''binärt läge''' lagrar MediaWiki UTF-8-text till databasen i binära fält.\nDetta är mer effektivt än MySQLs UTF-8-läge, och tillåter dig att använda ett fullt utbud av Unicode-tecken.\nI '''UTF-8-läge''' kommer MySQL känna av vilken teckenuppsättning din data är i, och kan presentera och konvertera den på ett lämpligt sätt, men den kommer inte lagra tecken ovanför [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mysql-old": "MySQL $1 eller senare krävs. Du har $2.",
+ "config-db-port": "Databasport:",
+ "config-db-schema": "Schema för MediaWiki",
+ "config-db-schema-help": "Det här schemat blir oftast bra.\nÄndra det endast om du vet att du behöver.",
+ "config-pg-test-error": "Kan inte ansluta till databas '''$1''': $2",
+ "config-sqlite-dir": "SQLite data-katalog:",
+ "config-sqlite-dir-help": "SQLite lagrar all data i en enda fil.\n\nDen katalog du anger måste vara skrivbar av webbservern under installationen.\n\nDet bör <strong>inte</strong> vara tillgänglig via webben; Det är därför vi inte lägger den där dina PHP-filer är.\n\nInstallationsprogrammet kommer att skriva en <code>.htaccess</code>-fil tillsammans med den, men om det misslyckas kan någon få tillgång till den råa databasen.\nVlken innehåller rå användardata (e-postadresser, hashade lösenord) samt borttagna revideringar och annan begränsad data på wiki.\n\nÖverväga att lägga databasen någon helt annanstans, till exempel i <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Standardtabellutrymme (tablespace):",
+ "config-oracle-temp-ts": "Tillfälligt tabellutrymme (tablespace):",
+ "config-type-mysql": "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-mysql-url}} MySQL] är det primära målet för MediaWiki och det stöds bäst. MediaWiki fungerar även med [{{int:version-db-mariadb-url}} MariaDB] och [{{int:version-db-percona-url}} Percona Server], som är kompatibla med MySQL. ([http://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. Det kan finnas några mindre kvarvarande buggar, och den rekommenderas inte för användning i en produktionsmiljö. ([http://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. ([http://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. ([http://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. ([http://www.php.net/manual/en/sqlsrv.installation.php Hur man kompilerar PHP med SQLSRV stöd])",
+ "config-header-mysql": "MySQL-inställningar",
+ "config-header-postgres": "PostgreSQL-inställningar",
+ "config-header-sqlite": "SQLite-inställningar",
+ "config-header-oracle": "Oracle-inställningar",
+ "config-header-mssql": "Inställningar för Microsoft SQL Server",
+ "config-invalid-db-type": "Ogiltig databastyp",
+ "config-missing-db-name": "Du måste ange ett värde för \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Du måste ange ett värde för \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Du måste ange ett värde för \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "Ogiltig databas-TNS \"$1\".\nAnvända antingen \"TNS Name\" eller en \"Easy Connect\"-sträng ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracles namngivningsmetoder]).",
+ "config-invalid-db-name": "\"$1\" är ett ogiltigt databasnamn.\nAnvänd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).",
+ "config-invalid-db-prefix": "\"$1\" är ett ogiltigt databasprefix.\nAnvänd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).",
+ "config-connection-error": "$1.\n\nKontrollera värd, användarnamn och lösenord och försök igen.",
+ "config-invalid-schema": "\"$1\" är ett ogiltigt schema för MediaWiki.\nAnvänd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).",
+ "config-db-sys-create-oracle": "Installationsprogrammet stöder endast användningen av ett SYSDBA-konto för att skapa ett nytt konto.",
+ "config-db-sys-user-exists-oracle": "Användarkontot \"$1\" finns redan. SYSDBA kan endast användas för att skapa ett nytt konto!",
+ "config-postgres-old": "PostgreSQL $1 eller senare krävs, du har $2.",
+ "config-mssql-old": "Microsoft SQL-server $1 eller senare krävs. Du har $2.",
+ "config-sqlite-name-help": "Välja ett namn som identifierar din wiki.\nAnvänd inte mellanslag eller bindestreck.\nDetta kommer att användas för SQLite-data filnamnet.",
+ "config-sqlite-parent-unwritable-group": "Kan inte skapa datakatalogen <code><nowiki>$1</nowiki></code>, då den överordnade katalogen <code><nowiki>$2</nowiki></code> inte är skrivbar för webbservern.\n\nInstallationen har avgjort vilken användare din webbserver körs som.\nGör <code><nowiki>$3</nowiki></code>-katalogen skrivbar för den för att fortsätta.\nPå ett Unix/Linux system gör:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Kan inte skapa datakatalogen <code><nowiki>$1</nowiki></code>, då den överordnade katalogen <code><nowiki>$2</nowiki></code> inte är skrivbar för webbservern.\n\nInstallationen kunde inte avgöra vilken användare din webbserver körs som.\nGör <code><nowiki>$3</nowiki></code>-katalogen skrivbar för den (och andra!) för att fortsätta.\nPå ett Unix/Linux system gör:\n\n<pre>cd $2\nmkdir $3\nchmod g+w $3</pre>",
+ "config-sqlite-mkdir-error": "Fel uppstod när datakatalogen \"$1\" skulle skapas.\nKontrollera platsen och försök igen.",
+ "config-sqlite-dir-unwritable": "Kunde inte skriva till katalogen \"$1\".\nÄndra dess behörighet så att webbservern kan skriva till den och försök igen.",
+ "config-sqlite-connection-error": "$1.\n\nKontrollera datakatalogen och databasnamnet nedan och försök igen.",
+ "config-sqlite-readonly": "Filen <code>$1</code> är inte skrivbar.",
+ "config-sqlite-cant-create-db": "Kunde inte skapa databasfilen <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP saknar stöd för FTS3, nedgraderar tabeller",
+ "config-can-upgrade": "Det finns MediaWiki-tabeller i den här databasen.\nFör att uppgradera dem till MediaWiki $1, klicka på '''Fortsätt'''.",
+ "config-upgrade-done": "Uppgraderingen slutfördes.\n\nDu kan nu [$1 börja använda din wiki].\n\nOm du vill förnya din <code>LocalSettings.php</code>-fil, klicka på knappen nedan.\nDetta '''rekommenderas inte''' om du har problem med din wiki.",
+ "config-upgrade-done-no-regenerate": "Uppgraderingen slutfördes.\n\nDu kan nu [$1 börja använda din wiki].",
+ "config-regenerate": "Återskapa LocalSettings.php →",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code>-förfrågan misslyckades!",
+ "config-unknown-collation": "'''Varning:''' Databasen använder en okänd sortering.",
+ "config-db-web-account": "Databaskonto för webbaccess",
+ "config-db-web-help": "Välj det användarnamn och lösenord som webbservern använder för att ansluta till databasservern, under ordinarie drift av wikin.",
+ "config-db-web-account-same": "Använd samma konto som för installation",
+ "config-db-web-create": "Skapa kontot om det inte redan finns",
+ "config-db-web-no-create-privs": "Det konto som du har angett för installation har inte tillräcklig behörighet för att skapa ett konto.\nDet konto du anger här måste redan finnas.",
+ "config-mysql-engine": "Lagringsmotor:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Varning:''' Du har valt MyISAM som lagringsmotor för MySQL, vilket inte rekommenderas för användning med MediaWiki eftersom:\n* den knappt stöder samtidigt exekvering på grund av låsning av tabeller\n* den är mer benägen att korrumpera data än andra motorer\n* MediaWiki-kodbasen hanterar inte alltid MyISAM som den ska\n\nOm din MySQL-installation stöder InnoDB, är det starkt rekommenderat att du väljer det istället.\nOm din MySQL-installation inte stöder InnoDB, kanske det är dags för en uppgradering.",
+ "config-mysql-only-myisam-dep": "'''Varning:''' MyISAM är den enda tillgängliga lagringsmotorn för MySQL på denna maskin, och den är inte rekommenderad att använda med MediaWiki eftersom:\n* den knappt stöder samtidigt exekvering på grund av låsning av tabeller\n* den är mer benägen att korrumpera data än andra motorer\n* MediaWiki-kodbasen hanterar inte alltid MyISAM som den ska\n\nDin MySQL-installation stöder inte InnoDB, det kanske är dags för en uppgradering.",
+ "config-mysql-engine-help": "'''InnoDB''' är nästan alltid det bästa valet eftersom den har ett bra system för samtidiga arbeten.\n\n'''MyISAM''' kan vara snabbare i enanvändarläge eller skrivskyddade installationer.\nMyISAM-databaser tenderar att bli korrupta oftare än InnoDB-databaser.",
+ "config-mysql-charset": "Databasteckensuppsättning:",
+ "config-mysql-binary": "Binär",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "I '''binärt läge''' lagrar MediaWiki UTF-8 text till databasen i binära fält.\nDetta är mer effektivt än MySQLs UTF-8-läge, och den tillåter dig att använda den fulla uppsättningen av Unicode-tecken.\n\nI '''UTF-8-läge''' vet MySQL vilket teckenuppsättning din data är i och kan presentera och konvertera den på ett lämpligt sätt, men den tillåter dig inte att lagra tecken över [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mssql-auth": "Autentiseringstyp:",
+ "config-mssql-install-auth": "Välj autentiseringstypen som kommer att användas för att ansluta till databasen under installationsprocessen.\nOm du väljer \"{{int:config-mssql-windowsauth}}\", kommer autentiseringsuppgifterna för den användare webbservern körs som att användas.",
+ "config-mssql-web-auth": "Välj autentiseringstypen som kommer att användas för att ansluta till databasen under ordinarie drift av wikin.\nOm du väljer \"{{int:config-mssql-windowsauth}}\", kommer autentiseringsuppgifterna för den användare webbservern körs som att användas.",
+ "config-mssql-sqlauth": "SQL Server-autentisering",
+ "config-mssql-windowsauth": "Windows-autentisering",
+ "config-site-name": "Namnet på wikin:",
+ "config-site-name-help": "Detta visas i titelfältet i webbläsaren och på flera andra platser.",
+ "config-site-name-blank": "Ange ett webbplatsnamn.",
+ "config-project-namespace": "Projektnamnrymd:",
+ "config-ns-generic": "Projekt",
+ "config-ns-site-name": "Samma som wikinamnet: $1",
+ "config-ns-other": "Annan (specificera)",
+ "config-ns-other-default": "MinWiki",
+ "config-project-namespace-help": "Per Wikipedias exempel håller många wikis sina policy-sidor separata från innehållssidorna i en \"'''projektnamnrymd'''\".\nAlla sidtitlar i denna namnrymd startar med ett visst prefix vilket du specificerar här.\nVanligtvis kan detta namn härledas från namnet på wikin, men den får inte innehålla interpunktionstecken som exempelvis \"#\" eller \":\".",
+ "config-ns-invalid": "Den angivna namnrymden \"<nowiki>$1</nowiki>\" är ogiltig.\nAnge en annan namnrymd för projektet.",
+ "config-ns-conflict": "Den angivna namnrymden \"<nowiki>$1</nowiki>\" står i konflikt med en standardnamnrymd för MediaWiki.\nAnge en annan namnrymd för projektet.",
+ "config-admin-box": "Administratörskonto",
+ "config-admin-name": "Ditt användarnamn:",
+ "config-admin-password": "Lösenord:",
+ "config-admin-password-confirm": "Lösenord igen:",
+ "config-admin-help": "Ange ditt önskade användarnamn här, t.ex. \"Sven Svensson\".\nDetta är namnet du kommer att använda för att logga in på wikin.",
+ "config-admin-name-blank": "Ange ett användarnamn för administratörskontot.",
+ "config-admin-name-invalid": "Det angivna användarnamnet \"<nowiki>$1</nowiki>\" är ogiltigt.\nAnge ett annat användarnamn.",
+ "config-admin-password-blank": "Ange ett lösenord för administratörskontot.",
+ "config-admin-password-mismatch": "De två lösenord du angav överensstämmer inte med varandra.",
+ "config-admin-email": "E-postadress:",
+ "config-admin-email-help": "Ange en e-postadress här så att du kan ta emot e-post från andra användare på wikin, för att återställa ditt lösenord och för att informeras om ändringar på sidor du bevakar. Du kan lämna fältet tomt.",
+ "config-admin-error-user": "Internt fel när du skapar en administratör med namnet \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Internt fel lösenordet för administratören \"<nowiki>$1</nowiki>\" ställdes in: <pre>$2</pre>",
+ "config-admin-error-bademail": "Du har angivit en ogiltig e-postadress.",
+ "config-subscribe": "Prenumerera på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlistan för kungörelser av nya versioner].",
+ "config-subscribe-help": "Detta är en e-postlista med låg volym vilken används för meddelanden om nya versionssläpp, inklusive viktiga säkerhetsmeddelanden.\nDu bör prenumerera på den och uppdatera din MediaWiki-installation när nya versioner kommer ut.",
+ "config-subscribe-noemail": "Du försökte att prenumerera på e-postlistan för versionssläppsmeddelanden utan att tillhandahålla en e-postadress.\nAnge en e-postadress om du vill prenumerera på e-postlistan.",
+ "config-almost-done": "Du är nästan färdig!\nDu kan nu hoppa över återstående konfigurationer och installera wikin direkt.",
+ "config-optional-continue": "Ställ fler frågor till mig.",
+ "config-optional-skip": "Jag är redan uttråkad, bara installera wiki.",
+ "config-profile": "Profil för användarrättigheter:",
+ "config-profile-wiki": "Öppen wiki",
+ "config-profile-no-anon": "Kontoskapande krävs",
+ "config-profile-fishbowl": "Endast auktoriserade redigerare",
+ "config-profile-private": "Privat wiki",
+ "config-profile-help": "Wikis fungerar bäst när du låter som många människor som möjligt redigera dem.\nI MediaWiki, är det lätt att granska de senaste ändringarna och återställa alla skador som utförs av naiva eller illvilliga användare.\n\nMen många har funnit MediaWiki användbart i en mängd olika roller, och ibland är det inte lätt att övertyga alla fördelarna med wiki-sättet.\nSå valet är ditt.\n\nModellen <strong>{{int:config-profil-wiki}}</strong> tillåter vem som helst att redigera, utan att ens behöva logga in.\nEn wiki med <strong>{{int:config-profil-ingen-anon}}</strong> ger extra ansvarskänsla, men kan avskräcka tillfälliga bidragsgivare.\n\nScenariot <strong>{{int:config-profil-fishbowl}}</strong> tillåter godkända användare att redigera, men allmänheten kan se sidorna, inklusive historik.\nA <strong>{{int:config-profil-privat}}</strong> tillåter endast godkända användare att se sidor, samma grupp får även redigera.\n\nMer komplexa användarrättighetskonfigurationer finns tillgängliga efter installationen, se [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights avsnittet i manualen].",
+ "config-license": "Upphovsrätt och licens:",
+ "config-license-none": "Ingen licenssidfot",
+ "config-license-cc-by-sa": "Creative Commons Erkännande-DelaLika",
+ "config-license-cc-by": "Creative Commons Erkännande",
+ "config-license-cc-by-nc-sa": "Creative Commons Erkännande-IckeKommersiell-DelaLika",
+ "config-license-cc-0": "Creative Commons Zero (Public Domain)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 eller senare",
+ "config-license-pd": "Public Domain",
+ "config-license-cc-choose": "Välj en anpassad Creative Commons-licens",
+ "config-license-help": "Många publika wikis släpper alla bidrag under en [http://freedomdefined.org/Definition fri licens].\nDetta bidrar till en känsla av gemensamt ägandeskap och uppmuntrar till långsiktiga bidrag.\nDet är i allmänhet inte nödvändigt för en privat eller företagswiki.\n\nOm du vill kunna använda text från Wikipedia, och du vill att Wikipedia ska kunna acceptera text kopierad ifrån din wiki bör du välja <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia använde tidigare GNU Free Documentation License.\nGFDL är en giltig licens, men svår att förstå.\nDet är även svårt att återanvända innehåll som licensierats under GFDL.",
+ "config-email-settings": "E-postinställningar",
+ "config-enable-email": "Aktivera utgående e-post",
+ "config-enable-email-help": "Om du vill att e-post ska fungera behöver,[http://www.php.net/manual/en/mail.configuration.php PHPs e-postinställningar] vara konfigurerad på rätt sätt.\nOm du inte vill ha några e-postfunktioner, kan du inaktivera dem här.",
+ "config-email-user": "Aktivera e-post mellan användare",
+ "config-email-user-help": "Tillåta alla användare att skicka e-post till varandra om de har aktiverat det i sina inställningar.",
+ "config-email-usertalk": "Aktivera meddelanden för användardiskussionssidor",
+ "config-email-usertalk-help": "Tillåt användare att få meddelanden när användardiskussionssidor ändras, om de har aktiverat detta i sina inställningar.",
+ "config-email-watchlist": "Aktivera meddelanden för bevakningslistan",
+ "config-email-watchlist-help": "Tillåt användare att få meddelanden när deras bevakade sidor ändras, om de har aktiverat detta i sina inställningar.",
+ "config-email-auth": "Aktivera autentisering via e-post",
+ "config-email-auth-help": "Om detta alternativ är aktiverat, måste användare bekräfta sin e-postadress via en länk som skickas till dem när de ställer in eller ändra den.\nEndast autentiserade e-postadresser kan ta emot e-post från andra användare eller ändra aviserings-e-post.\nDet här alternativet är <strong>rekommenderat</strong> för offentliga wikis på grund av potentiellt missbruk av e-postfunktionerna.",
+ "config-email-sender": "Returadress för e-post:",
+ "config-email-sender-help": "Ange den e-postadressen som ska användas som returadress på utgående e-post.\nDetta är dit studsar skickas.\nMånga mailservrar kräver att minst domännamndelen är giltigt.",
+ "config-upload-settings": "Bild- och filuppladdningar",
+ "config-upload-enable": "Aktivera filuppladdningar",
+ "config-upload-help": "Filuppladdning utsätter potentiellt din server för säkerhetsrisker.\nFör mer information, Läs [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security säkerhetsavsnittet] i manualen.\n\nFör att aktivera filuppladdning, ändra läget för <code>images</code>-underkatalogen under rotkatalogen för MediaWiki så att webbservern kan skriva till den.\nAktivera sedan detta alternativ.",
+ "config-upload-deleted": "Katalog för raderade filer:",
+ "config-upload-deleted-help": "Välja en katalog i vilken raderade filer arkiveras.\nHelst bör denna inte vara tillgängliga från webben.",
+ "config-logo": "Logotyp-URL:",
+ "config-logo-help": "MediaWikis standardutseende innehåller ett mellanrum för en 135x160 bildpunkter stor logotyp ovanför sidofältsmenyn.\nLadda upp en bild med lämplig storlek och ange webbadressen här.\n\nDu kan använda <code>$wgStylePath</code> eller <code>$wgScriptPath</code> om din logotyp är relativ till dessa sökvägar.\n\nOm du inte vill ha en logotyp kan du lämna detta fält tomt.",
+ "config-instantcommons": "Aktivera Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] är en funktion som gör det möjligt för wikis att använda bilder, ljud och andra media som finns på [//commons.wikimedia.org/ Wikimedia Commons]-webbplatsen.\nFör att göra detta, kräver MediaWiki tillgång till Internet.\n\nFör mer information om denna funktion, inklusive instruktioner om hur man ställer in den för andra wikis än Wikimedia Commons, se [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos manualen].",
+ "config-cc-error": "Creative Commons-licens-väljaren gav inget resultat.\nAnge licensnamnet manuellt.",
+ "config-cc-again": "Välj igen...",
+ "config-cc-not-chosen": "Välj vilken Creative Commons-licens du vill ha och klicka på \"gå vidare\".",
+ "config-advanced-settings": "Avancerad konfiguration",
+ "config-cache-options": "Inställningar för cachelagring av objekt:",
+ "config-cache-help": "Cachelagring av objekt används för att förbättra hastigheten på MediaWiki genom att cachelagra data som används ofta.\nMedelstora till stora webbplatser är starkt uppmuntrade att aktivera detta, och små webbplatser kommer även att se fördelar.",
+ "config-cache-none": "Ingen cachelagring (ingen funktionalitet tas bort, men hastighet kan påverkas på större wiki-webbplatser)",
+ "config-cache-accel": "Cachelagring av PHP-objekt (APC, XCache eller WinCache)",
+ "config-cache-memcached": "Använda Memcached (kräver ytterligare inställningar och konfiguration)",
+ "config-memcached-servers": "Memcached-servrar:",
+ "config-memcached-help": "Lista över IP-adresser som ska användas för Memcached.\nBör ange en per rad och specificera den port som ska användas. Till exempel:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Du valde Memcached som din cachelagringstyp men angav inte några servrar.",
+ "config-memcache-badip": "Du har angett en ogiltig IP-adress för Memcached: $1.",
+ "config-memcache-noport": "Du angav inte en port som ska användas för Memcached-server: $1.\nOm du inte vet porten, är standard 11211.",
+ "config-memcache-badport": "Memcached-portnummer bör vara mellan $1 och $2.",
+ "config-extensions": "Tillägg",
+ "config-extensions-help": "Tilläggen ovan upptäcktes i din <code>./extensions</code>-katalog.\n\nDe kan kräva ytterligare konfiguration, men du kan aktivera dem nu.",
+ "config-skins": "Utseenden",
+ "config-skins-help": "Utseenden som listas upp ovan identifierades i din filkatalog <code>./skins</code>. Du måste aktivera minst en och välja ett som standard.",
+ "config-skins-use-as-default": "Använd detta utseende som standard",
+ "config-skins-missing": "Ingen utseenden hittades; MediaWiki använder ett reservutseende tills du installerar några.",
+ "config-skins-must-enable-some": "Du måste välja minst ett utseende att aktivera.",
+ "config-skins-must-enable-default": "Utseendet som valdes som standard måste aktiveras.",
+ "config-install-alreadydone": "''' Varning:''' Du verkar redan ha installerat MediaWiki och försöker installera det igen.\nVänligen fortsätt till nästa sida.",
+ "config-install-begin": "Genom att trycka på \"{{int:config-continue}}\", påbörjar du installationen av MediaWiki.\nOm du fortfarande vill göra ändringar tryck på \"{{int:config-back}}\".",
+ "config-install-step-done": "klar",
+ "config-install-step-failed": "misslyckades",
+ "config-install-extensions": "Inklusive tillägg",
+ "config-install-database": "Konfigurerar databas",
+ "config-install-schema": "Skapar schema",
+ "config-install-pg-schema-not-exist": "PostgreSQL-schemat finns inte.",
+ "config-install-pg-schema-failed": "Det gick inte att skapa tabeller.\nSe till att användaren \"$1\" kan skriva till schemat \"$2\".",
+ "config-install-pg-commit": "Begår ändringar",
+ "config-install-pg-plpgsql": "Kontroll för språket PL/pgSQL",
+ "config-pg-no-plpgsql": "Du måste installera språket PL/pgSQL i databasen $1",
+ "config-pg-no-create-privs": "Det konto som du har angett för installationen har inte tillräcklig behörighet för att skapa ett konto.",
+ "config-pg-not-in-role": "Det konto du angav för webbanvändaren finns redan.\nKontot du angav för installationen är inte en superanvändare (superuser) och är inte en medlem av webbanvändarens roll, därför kan den inte skapa objekt som ägs av webbanvändaren.\n\nMediaWiki kräver för närvarande att tabellerna ägs av webbanvändaren. Vänligen ange ett annat webbkontonamn, eller klicka \"tillbaka\" och ange en installationsanvändare med passande behörigheter.",
+ "config-install-user": "Skapar databasanvändare",
+ "config-install-user-alreadyexists": "Användaren \"$1\" finns redan",
+ "config-install-user-create-failed": "Misslyckades att skapa användare \"$1\": $2",
+ "config-install-user-grant-failed": "Beviljandet av behörighet till användaren \"$1\" misslyckades: $2",
+ "config-install-user-missing": "Den angivna användaren \"$1\" existerar inte.",
+ "config-install-user-missing-create": "Den angivna användaren \"$1\" existerar inte.\nVänligen klicka på kryssrutan \"skapa konto\" nedan om du vill skapa den.",
+ "config-install-tables": "Skapar tabeller",
+ "config-install-tables-exist": "'''Varning:''' MediaWiki-tabeller verkar redan finnas.\nHoppar över skapandet.",
+ "config-install-tables-failed": "'''Fel:''' Skapandet av tabell misslyckades med följande fel: $1",
+ "config-install-interwiki": "Lägger till standardtabell för interwiki",
+ "config-install-interwiki-list": "Kunde inte läsa filen <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "<strong>Varning:</strong> Interwiki-tabellen verkar redan innehålla poster.\nHoppar över standardlistan.",
+ "config-install-stats": "Initierar statistik",
+ "config-install-keys": "Genererar hemliga nycklar",
+ "config-insecure-keys": "'''Varning:''' {{PLURAL:$2|En säkerhetsnyckel|Säkerhetsnycklar}} ($1) som generades under installationen är inte helt {{PLURAL:$2|säker|säkra}} . Överväg att ändra {{PLURAL:$2|den|dem}} manuellt.",
+ "config-install-updates": "Förhindra att onödiga uppdateringar körs",
+ "config-install-updates-failed": "<strong>Fel:</strong> Infogning av uppdateringsnycklar i tabeller misslyckades med följande fel:$1",
+ "config-install-sysop": "Skapar administratörskonto",
+ "config-install-subscribe-fail": "Det gick inte att prenumerera på mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL är inte installerad och <code>allow_url_fopen</code> är inte tillgänglig.",
+ "config-install-mainpage": "Skapa huvudsida med standardinnehåll",
+ "config-install-extension-tables": "Skapar tabeller för aktiverade tillägg",
+ "config-install-mainpage-failed": "Kunde inte infoga huvudsidan: $1",
+ "config-install-done": "'''Grattis!'''\nDu har installerat MediaWiki.\n\nInstallationsprogrammet har genererat filen <code>LocalSettings.php</code>.\nDet innehåller alla dina konfigurationer.\n\nDu kommer att behöva ladda ner den och placera den i roten för din wiki-installation (samma katalog som index.php). Nedladdningen borde ha startats automatiskt.\n\nOm ingen nedladdning erbjöds, eller om du har avbrutit det kan du starta om nedladdningen genom att klicka på länken nedan:\n\n$3\n\n'''OBS''': Om du inte gör detta nu, kommer denna genererade konfigurationsfil inte vara tillgänglig för dig senare om du avslutar installationen utan att ladda ned den.\n\nNär det är klart, kan du '''[$2 gå in på din wiki]'''.",
+ "config-download-localsettings": "Ladda ner <code>LocalSettings.php</code>",
+ "config-help": "hjälp",
+ "config-help-tooltip": "klicka för att expandera",
+ "config-nofile": "Filen \"$1\" kunde inte hittas. Har den raderats?",
+ "config-extension-link": "Visste du att din wiki stödjer [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions tillägg]?\n\nDu kan bläddra [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category tillägg efter kategori].",
+ "mainpagetext": "'''MediaWiki har installerats utan problem.'''",
+ "mainpagedocfooter": "Information om hur wiki-programvaran används finns i [//meta.wikimedia.org/wiki/Help:Contents användarguiden].\n\n== Att komma igång ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista över konfigurationsinställningar]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postlista för nya versioner av MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokalisera MediaWiki för ditt språk]"
+}
diff --git a/includes/installer/i18n/sw.json b/includes/installer/i18n/sw.json
new file mode 100644
index 00000000..aac7a0f0
--- /dev/null
+++ b/includes/installer/i18n/sw.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lloffiwr"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki imefanikiwa kuingizwa.'''",
+ "mainpagedocfooter": "Shauriana na [//meta.wikimedia.org/wiki/Help:Contents Mwongozo wa Mtumiaji] kwa habari juu ya utumiaji wa bidhaa pepe ya wiki.\n\n== Msaada wa kianzio ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Orodha ya mipangilio ya msingi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ ya MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Orodha ya utoaji wa habari za MediaWiki]"
+}
diff --git a/includes/installer/i18n/szl.json b/includes/installer/i18n/szl.json
new file mode 100644
index 00000000..68c13cc1
--- /dev/null
+++ b/includes/installer/i18n/szl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Djpalar"
+ ]
+ },
+ "mainpagetext": "'''Sztalowańy MediaWiki śe udoło.'''",
+ "mainpagedocfooter": "Uobezdrzij [//meta.wikimedia.org/wiki/Help:Contents przewodńik sprowjacza], kaj sům informacyje uo dźołańu uoprogramowańo MediaWiki.\n\n== Na sztart ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista sztalowań konfiguracyje]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komuńikaty uo nowych wersyjach MediaWiki]"
+}
diff --git a/includes/installer/i18n/ta.json b/includes/installer/i18n/ta.json
new file mode 100644
index 00000000..361cde9e
--- /dev/null
+++ b/includes/installer/i18n/ta.json
@@ -0,0 +1,90 @@
+{
+ "@metadata": {
+ "authors": [
+ "Karthi.dr",
+ "TRYPPN",
+ "மதனாஹரன்",
+ "Jayarathina"
+ ]
+ },
+ "config-title": "மீடியாவிக்கி $1 நிறுவுதல்",
+ "config-information": "தகவல்",
+ "config-localsettings-key": "தரமுயர்த்தல் குறியீடு:",
+ "config-localsettings-badkey": "நீங்கள் தந்த குறியீடு தவறானது.",
+ "config-your-language": "தங்களது மொழி:",
+ "config-your-language-help": "நிறுவல் செயன்முறையின்போது பயன்படுத்துவதற்கு ஒரு மொழியைத் தெரிவு செய்யவும்.",
+ "config-wiki-language": "விக்கி மொழி:",
+ "config-back": "← முந்தைய",
+ "config-continue": "தொடரவும் →",
+ "config-page-language": "மொழி",
+ "config-page-welcome": "மீடியாவிக்கிக்கு வருக !",
+ "config-page-dbconnect": "தரவுத் தளத்துடன் தொடர்பு கொள்ளவும்",
+ "config-page-dbsettings": "தரவுத் தள அமைப்புகள்",
+ "config-page-name": "பெயர்",
+ "config-page-options": "விருப்பத்தேர்வுகள்",
+ "config-page-install": "நிறுவு",
+ "config-page-complete": "நிறைவு!",
+ "config-page-restart": "நிறுவலை மீண்டும் தொடங்கவும்",
+ "config-page-readme": "இதைப் படி",
+ "config-page-releasenotes": "வெளியீட்டு குறிப்புகள்",
+ "config-page-copying": "நகலெடுக்கப்படுகிறது",
+ "config-page-upgradedoc": "தரமுயர்த்தப்படுகிறது",
+ "config-page-existingwiki": "இருக்கின்ற விக்கி",
+ "config-restart": "ஆம், மறுமுறை துவங்கு",
+ "config-sidebar": "* [//www.mediawiki.org மீடியாவிக்கி முகப்பு]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents பயனரின் கையேடு]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents மேலாளரின் கையேடு]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ அகேகே]\n----\n* <doclink href=Readme>என்னை வாசிக்கவும்</doclink>\n* <doclink href=ReleaseNotes>வெளியீட்டுக் குறிப்புகள்</doclink>\n* <doclink href=Copying>படியெடுத்தல்</doclink>\n* <doclink href=UpgradeDoc>நிகழ்நிலைப்படுத்தல்</doclink>",
+ "config-db-type": "தரவுத்தள வகை:",
+ "config-db-wiki-settings": "இந்த விக்கியைக் கண்டுபிடி",
+ "config-db-name": "தரவுதளப் பெயர்:",
+ "config-db-install-account": "நிறுவலுக்கான பயனர் கணக்கு",
+ "config-db-username": "தரவுத்தள பயனர்பெயர்:",
+ "config-db-password": "தரவுத்தள கடவுச்சொல்:",
+ "config-db-prefix": "தரவுத் தள வரிசைப் பட்டியல் முன்னொட்டு:",
+ "config-db-charset": "தரவுத் தள வரியுருத் தொகுதி",
+ "config-invalid-db-type": "செல்லாத தரவுத்தள வகை",
+ "config-upgrade-done-no-regenerate": "தரமுயர்த்தல் முழுமையடைந்தது.\nநீங்கள் தற்போது [$1 உங்கள் விக்கியைப் பயன்படுத்தத் துவங்கலாம்].",
+ "config-db-web-account": "வலை அணுகலுக்கான தரவுத் தளக் கணக்கு",
+ "config-mysql-engine": "சேமிப்பு இயந்திரம்:",
+ "config-mysql-charset": "தரவுத் தள வரியுருத் தொகுதி:",
+ "config-mysql-utf8": "UTF-8",
+ "config-site-name": "விக்கியின் பெயர்:",
+ "config-site-name-blank": "ஒரு தளத்தின் பெயரை உள்ளிடுக.",
+ "config-ns-generic": "திட்டம்",
+ "config-ns-other": "ஏனையவை (குறிப்பிடவும்)",
+ "config-admin-box": "நிருவாகி கணக்கு",
+ "config-admin-name": "உங்களின் பயனர் பெயர்:",
+ "config-admin-password": "கடவுச்சொல்:",
+ "config-admin-password-confirm": "கடவுச்சொல் மறுமுறையும்:",
+ "config-admin-name-blank": "நிருவாக அணுக்கம் உள்ள பயனர் பெயரை இடுக.",
+ "config-admin-password-blank": "நிருவாகி கணக்குக்கு கடவுச்சொல் ஒன்றை உள்ளிடவும்.",
+ "config-admin-password-mismatch": "நீங்கள் பதிந்த கடவுச்சொற்கள் ஒன்றுக்கொன்று பொருந்தவில்லை.",
+ "config-admin-email": "மின்னஞ்சல் முகவரி:",
+ "config-admin-error-bademail": "நீங்கள் செல்லாத ஒரு மின்னஞ்சல் முகவரியைத் தந்துள்ளீர்கள்.",
+ "config-optional-continue": "என்னை இன்னும் அதிகமாக வினவு.",
+ "config-optional-skip": "நான் ஏற்கனவே சோர்வடைந்துள்ளேன், விக்கியை மட்டும் உருவாக்கு.",
+ "config-profile": "பயனர் உரிமைகள் சுயவிவரம்:",
+ "config-profile-wiki": "பாரம்பரிய விக்கி",
+ "config-profile-no-anon": "கணக்கு உருவாக்குதல் அவசியம்",
+ "config-profile-private": "தனியார் விக்கி",
+ "config-license": "பதிப்புரிமை மற்றும் உரிமம்:",
+ "config-license-pd": "பொதுக்களம்",
+ "config-email-settings": "மின்னஞ்சல் அமைப்புகள்",
+ "config-email-user": "பயனர்-பயனர் மின்னஞ்சலைச் செயற்படுத்தவும்",
+ "config-email-usertalk": "பயனர் பேச்சுப் பக்க அறிவிப்பைச் செயற்படுத்தவும்",
+ "config-email-watchlist": "கவனிப்புப் பட்டியல் அறிவிப்பைச் செயற்படுத்தவும்",
+ "config-upload-settings": "படிமம் மற்றும் கோப்பு பதிவேற்றங்கள்",
+ "config-upload-enable": "கோப்புப் பதிவேற்றங்களைச் செயற்படுத்தவும்",
+ "config-upload-deleted": "அழித்த கோப்புகளுக்கான அடைவு:",
+ "config-logo": "அடையாளச் சின்ன உரலி:",
+ "config-extensions": "நீட்சிகள்",
+ "config-install-step-done": "முடிந்தது",
+ "config-install-step-failed": "தோல்வியுற்றது",
+ "config-install-user": "தரவுத் தளப் பயனரை உருவாக்குகிறது",
+ "config-install-user-alreadyexists": "பயனர் \"$1\" ஏற்கனவே உள்ளது",
+ "config-install-tables": "வரிசைப் பட்டியல்களை உருவாக்குகிறது",
+ "config-install-mainpage": "இயல்புநிலை உள்ளடக்கத்துடன் முதற்பக்கத்தை உருவாக்குகிறது",
+ "config-install-extension-tables": "செயற்படுத்தப்பட்ட நீட்சிகளுக்கு வரிசைப் பட்டியல்களை உருவாக்குகிறது",
+ "config-download-localsettings": "<code>LocalSettings.php</code>ஐத் தரவிறக்கவும்",
+ "config-help": "உதவி",
+ "mainpagetext": "'''விக்கி மென்பொருள் வெற்றிகரமாக உள்ளிடப்பட்டது.'''",
+ "mainpagedocfooter": "விக்கி மென்பொருளைப் பயன்படுத்துவது தொடர்பாக [//meta.wikimedia.org/wiki/Help:Contents பயனர் வழிகாட்டியைப்] பார்க்க.\n\n== தொடக்கப்படிகள் ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings அமைப்புக்களை மாற்றம் செய்தல்]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ மிடியாவிக்கி பொதுவான கேள்விகள்]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce மீடியாவிக்கி வெளியீடு மின்னஞ்சல் பட்டியல்]"
+}
diff --git a/includes/installer/i18n/tcy.json b/includes/installer/i18n/tcy.json
new file mode 100644
index 00000000..a8991339
--- /dev/null
+++ b/includes/installer/i18n/tcy.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''ಮೀಡಿಯವಿಕಿ ಯಶಸ್ವಿಯಾದ್ ಇನ್’ಸ್ಟಾಲ್ ಆಂಡ್.'''",
+ "mainpagedocfooter": "ವಿಕಿ ತಂತ್ರಾಂಶನ್ ಉಪಗೋಗ ಮನ್ಪುನ ಬಗ್ಗೆ ಮಾಹಿತಿಗ್ [//meta.wikimedia.org/wiki/Help:Contents ಸದಸ್ಯೆರ್ನ ನಿರ್ದೇಶನ ಪುಟ] ತೂಲೆ.\n\n== ಎಂಚ ಶುರು ಮಲ್ಪುನಿ ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ಮೀಡಿಯವಿಕಿ FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/te.json b/includes/installer/i18n/te.json
new file mode 100644
index 00000000..86b760c8
--- /dev/null
+++ b/includes/installer/i18n/te.json
@@ -0,0 +1,240 @@
+{
+ "@metadata": {
+ "authors": [
+ "Veeven",
+ "Chaduvari",
+ "Ravichandra"
+ ]
+ },
+ "config-desc": "మీడియావికీ కొరకై స్థాపకి",
+ "config-title": "మీడియావికీ $1స్థాపన",
+ "config-information": "సమాచారం",
+ "config-localsettings-upgrade": "ఓ <code>LocalSettings.php</code> ఫైలు కనబడింది.\nఈ స్థాపనను ఉన్నతీకరించడానికి, కింద ఇచ్చిన పెట్టెలో <code>$wgUpgradeKey</code> యొక్క విలువను ఇవ్వండి.\nఅది <code>LocalSettings.php</code> లో ఉంటుంది.",
+ "config-localsettings-cli-upgrade": "ఓ <code>LocalSettings.php</code> ఫైలు కనబడింది.\nఈ స్థాపనను ఉన్నతీకరించడానికి, దాని బదులు <code>update.php</code> ను రన్ చెయ్యండి.",
+ "config-localsettings-key": "ఉన్నతీకరణ కీ:",
+ "config-localsettings-badkey": "మీరిచ్చిన కీ తప్పు.",
+ "config-upgrade-key-missing": "MediaWiki యొక్క ఒక స్థాపన కనబడింది.\nదాన్ని ఉన్నతీకరించడానికి, కింది లైనును <code>LocalSettings.php</code> లో అట్టడుగున ఉంచండి:\n\n$1",
+ "config-localsettings-incomplete": "ఇప్పటి <code>LocalSettings.php</code> అసంపూర్తిగా ఉన్నట్లుగా కనబడుతోంది.\n$1 చరరాశిని సెట్ చెయ్యలేదు.\nఈ చరరాశిని సెట్ చేస్తూ <code>LocalSettings.php</code> ను మార్చి, \"{{int:Config-continue}}\" ను నొక్కండి.",
+ "config-localsettings-connection-error": "<code>LocalSettings.php</code> లో ఇచ్చిన సెట్టింగులను వాడుతూ డేటాబేసుకు కనెక్టు కాబోతే, లోపం తలెత్తింది. ఈ సెట్టింగులను సరిచేసి మళ్ళీ ప్రయత్నించండి.\n\n$1",
+ "config-session-error": "సెషన్ను ప్రారంభించబోతే లోపం జరిగింది: $1",
+ "config-session-expired": "మీ సెషన్ డేటాకు కాలదోషం పట్టినట్లుంది.\nసెషన్ల జీవితకాలం $1 ఉండేలా అమర్చబడ్డాయి.\nphp.ini లో <code>session.gc_maxlifetime</code> ను మార్చి దీన్ని పెంచవచ్చు.\nస్థాపన పనిని తిరిగి మొదలుపెట్టండి.",
+ "config-no-session": "మీ సెషను డేటా పోయింది!\nphp.ini లో <code>session.save_path</code> సరైన డైరెక్టరీకి సెట్ చేసి ఉందో లేదో చూడండి.",
+ "config-your-language": "మీ భాష:",
+ "config-your-language-help": "స్థాపన పనిలో వాడేందుకు ఓ భాషను ఎంచుకోండి.",
+ "config-wiki-language": "వికీ భాష:",
+ "config-wiki-language-help": "వికీని ప్రధానంగా ఏ భాషలో రాయాలో ఎంచుకోండి.",
+ "config-back": "← వెనక్కి",
+ "config-continue": "కొనసాగించు →",
+ "config-page-language": "భాష",
+ "config-page-welcome": "మీడియావికీకి స్వాగతం!",
+ "config-page-dbconnect": "డేటాబేసుకు కనెక్టవు",
+ "config-page-upgrade": "ప్రస్తుత స్థాపనను ఉన్నతీకరించు",
+ "config-page-dbsettings": "డాటాబేసు అమరికలు",
+ "config-page-name": "పేరు",
+ "config-page-options": "ఎంపికలు",
+ "config-page-install": "స్థాపించు",
+ "config-page-complete": "పూర్తయ్యింది!",
+ "config-page-restart": "స్థాపనను తిరిగి ప్రారంభించు",
+ "config-page-readme": "నన్ను చదవండి",
+ "config-page-releasenotes": "విడుదల విశేషాలు",
+ "config-page-copying": "కాపీ చేస్తున్నాం",
+ "config-page-upgradedoc": "ఉన్నతీకరిస్తున్నాం",
+ "config-page-existingwiki": "ప్రస్తుత వికీ",
+ "config-help-restart": "మీరు భద్రపరిచిన డేటా మొత్తాన్ని తీసివేసి స్థాపనను తిరిగి ప్రారంభించాలా?",
+ "config-restart": "ఔను, తిరిగి ప్రారంభించు",
+ "config-welcome": "=== పర్యావరణ పరీక్షలు ===\nఈ పర్యావరణం MediaWiki స్థాపనకు అనుకూలంగా ఉందో లేదో చూసే ప్రాథమిక పరీక్షలు ఇపుడు చేస్తాం.\nస్థాపనను ఎలా పూర్తి చెయ్యాలనే విషయమై మీకు సహాయం అడిగేటపుడు, ఈ సమాచారాన్ని ఇవ్వాలని గుర్తుంచుకోండి.",
+ "config-copyright": "=== కాపీహక్కు, నిబంధనలు===\n\n$1\n\nఇది ఉచిత సాఫ్ట్‌వేరు; ఫ్రీ సాఫ్ట్‌వేర్ ఫౌండేషన్ వారు ప్రచురించిన GNU జనరల్ పబ్లిక్ లైసెన్సును (2వ లేదా తరువాతి వర్షన్) అనుసరించి దీన్ని పంపిణీ చెయ్యవచ్చు లేదా మార్చుకోనూవచ్చు.\n\nదీని వలన ఉపయోగం ఉంటుందనే నమ్మకంతో ప్రచురింపబడింది. కానీ <strong>ఎటువంటి వారంటీ లేదు</strong>; <strong> వర్తకం చేయదగ్గ </strong> లేదా <strong> ఒక అవసరానికి సరిపడే సామర్థ్యం</strong> ఉన్నదనే అంతరార్థ వారంటీ కూడా లేదు.\nమరిన్ని వివరాలకు GNU జనరల్ పబ్లిక్ లైసెన్స్ చూడండి.\n\nమీరు ఈ ప్రోగ్రాముతో పాటు <doclink href=Copying> GNU జనరల్ పబ్లిక్ లైసెన్స్ ప్రతిని </doclink> అందుకుని ఉండాలి; లేకపోతే, Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA కు జాబు రాయండి లేదా [http://www.gnu.org/copyleft/gpl.html ఆన్‌లైన్‌లో చదివండి].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki మొదటిపేజీ]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents వాడుకరుల మార్గదర్శి]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents అధికారుల మార్గదర్శి]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>చదవాల్సినవి</doclink>\n* <doclink href=ReleaseNotes>విడుదల గమనికలు</doclink>\n* <doclink href=Copying>కాపీ చెయ్యడం</doclink>\n* <doclink href=UpgradeDoc>ఉన్నతీకరించడం</doclink>",
+ "config-env-good": "పర్యావరణాన్ని పరీక్షించాం.\nఇక మీరు MediaWiki ని స్థాపించుకోవచ్చు.",
+ "config-env-bad": "పర్యావరణాన్ని పరీక్షించాం.\nమీరు MediaWiki ని స్థాపించలేరు.",
+ "config-env-php": "PHP $1 స్థాపించబడింది.",
+ "config-env-php-toolow": "PHP $1 స్థాపించబడింది.\nఅయితే, MediaWiki కి PHP $2 గానీ ఆ పైది గానీ కావాలి.",
+ "config-unicode-using-utf8": "యూనికోడు నార్మలైజేషన్ కోసం బ్రయాన్ విబర్ గారి utf8_normalize.so ను వాడుతున్నాం.",
+ "config-unicode-using-intl": "యూనికోడు నార్మలైజేషన్ కోసం [http://pecl.php.net/intl intl PECL పొడిగింత] ను వాడుతున్నాం.",
+ "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-xcache": "[http://xcache.lighttpd.net/ XCache] స్థాపించబడింది",
+ "config-apc": "[http://www.php.net/apc APC] స్థాపించబడింది",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] స్థాపించబడింది",
+ "config-diff3-bad": "GNU diff3 కనబడలేదు.",
+ "config-no-uri": "<strong>లోపం:</strong> ప్రస్తుత URI ఏమిటో నిర్ధారించలేకపోయాం.\nస్థాపన ఆగిపోయింది.",
+ "config-using-server": "సర్వరు పేరు \"<nowiki>$1</nowiki>\" ను వాడుతున్నాం.",
+ "config-using-uri": "సర్వరు URL \"<nowiki>$1$2</nowiki>\" ను వాడుతున్నాం.",
+ "config-db-type": "డాటాబేసు రకం:",
+ "config-db-host": "డేటాబేసు హోస్టు:",
+ "config-db-host-help": "మీ డేటాబేసు సర్వరు వేరే సర్వరులో ఉంటే, దాని హోస్ట్ పేరు, ఐపీ చిరునామా ఇక్కడ ఇవ్వండి.\n\nమీరు షేర్‍డ్ వెబ్ హోస్టింగును వాడుతూంటే, మీ హోస్టింగు సేవను అందించేవారు తమ డాక్యుమెంటేషనులో సరైన హోస్ట్ పేరును ఇచ్చి ఉండాలి.\n\nమీరు విండోస్ సర్వరులో స్థాపిస్తూ, MySQL వాడుతూ ఉంటే, సర్వరు పేరుగా \"localhost\" పనిచెయ్యకపోవచ్చు. అపుడు, స్థానిక ఐపీ చిరునామాగా \"127.0.0.1\" వాడండి.\n\nమీరు PostgreSQL వాడుతూ ఉంటే, Unix సాకెట్ ద్వారా కనెక్టయేందుకు ఈ ఫీల్డును ఖాళీగా వదిలెయ్యండి.",
+ "config-db-host-oracle": "డేటాబేసు TNS:",
+ "config-db-wiki-settings": "ఈ వికీ గుర్తింపును ఇవ్వండి",
+ "config-db-name": "డాటాబేసు పేరు:",
+ "config-db-name-help": "మీ వికీని సూచించే విధంగా ఓ పేరును ఎంచుకోండి.\nదానిలో స్పేసులు ఉండరాదు.\n\nమీరు షేర్‍డ్ వెబ్ హోస్టింగును వాడుతూంటే, మీకు హోస్టింగు సేవనందించేవారు మీకు ఓ డేటాబేసు పేరును గాని, లేదా కంట్రోలు ప్యానెలు ద్వారా ఓ డేటాబేసును సృష్టించుకునే వీలునుగానీ ఇస్తారు.",
+ "config-db-name-oracle": "డేటాబేసు స్కీమా:",
+ "config-db-install-account": "స్థాపనకి వాడుకరి ఖాతా",
+ "config-db-username": "డేటాబేసు వాడుకరిపేరు:",
+ "config-db-password": "డేటాబేసు సంకేతపదం:",
+ "config-db-password-empty": "కొత్త డేటాబేసు వాడుకరి $1 కి ఓ సంకేతపదం ఇవ్వండి. \nసంకేతపదాలేమీ లేకుండా వాడుకరులను సృష్టించేవీలున్నప్పటికీ, అది సురక్షితం కాదు.",
+ "config-db-username-empty": "\"{{int:config-db-username}}\" కి మీరు తప్పకుండా ఏదో ఒక విలువ ఇవ్వాలి.",
+ "config-db-install-username": "స్థాపన దశలో డేటాబేసుకు కనెక్టయ్యేందుకు వాడే వాడుకరిపేరును ఇవ్వండి.\nఇది MediaWiki ఖాతా యొక్క వాడుకరిపేరు కాదు; మీ డేటాబేసు కోసం వాడుకరిపేరు.",
+ "config-db-install-password": "స్థాపన దశలో డేటాబేసుకు కనెక్టయ్యేందుకు వాడే సంకేతపదాన్ని ఇవ్వండి.\nఇది MediaWiki ఖాతా యొక్క సంకేతపదం కాదు; మీ డేటాబేసు కోసం సంకేతపదం.",
+ "config-db-install-help": "స్థాపన దశలో డేటాబేసుకు కనెక్టయ్యేందుకు వాడే వాడుకరిపేరు, సంకేతపదం ఇవ్వండి.",
+ "config-db-account-lock": "అదే వాడుకరిపేరును, సంకేతపదాన్ని మామూలు వాడుకలో కూడా వాడు",
+ "config-db-wiki-account": "మామూలు వాడుక కోసం వాడుకరి ఖాతా",
+ "config-db-wiki-help": "మామూలుగా వికీ పనిచేసేటపుడు వాడే డేటాబేసును కనెక్టయేందుకు వాడే వాడుకరిపేరును, సంకేతపదాన్నీ ఎంచుకోండి.\nఒకవేళ ఈ ఖాతా ఉనికిలో లేకపోతే, స్థాపన కోసం వాడుతున్న ఖాతాకు తగు విధమైన అనుమతులు ఉన్న పక్షంలో, ఈ ఖాతాను సృష్టిస్తాం. ఈ కొత్త ఖాతాకు వికీని నడిపేందుకు అవసరమైన అనుమతులను మాత్రం ఇస్తాం.",
+ "config-db-prefix": "డేటాబేసు టెబులు ఆదిపదం:",
+ "config-db-prefix-help": "ఒకటి కంటే ఎక్కువ వికీలను గానీ, లేదా మీడియావికీ తో పాటు మరో వెబ్ అప్లికేషన్ను గానీ ఒకే డేటాబేసు నుండి వాడదలిస్తే, టేబులు పేర్లకు ముందు ఓ ఆదిపదాన్ని (ప్రిఫిక్స్) ను ఎంచుకోండి. ఈ విధంగా పేర్ల ఘర్షణను నివారింవచ్చు. \nస్పేసులను వాడకండి.\n\nఈ ఫీల్డును సాధారణంగా ఖాళీగా ఉంచేస్తారు.",
+ "config-db-charset": "డేేటాబేసు కారెక్టరు సెట్",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 బైనరీ",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 backwards-compatible UTF-8",
+ "config-mysql-old": "MySQL $1 గానీ ఆ తరువాతిది గానీ కావాలి. మీకు $2 ఉంది.",
+ "config-db-port": "డేటాబేసు పోర్టు:",
+ "config-db-schema": "MediaWiki కొరకు స్కీమా:",
+ "config-db-schema-help": "మామూలుగా ఈ స్కీమా సరిపోతుంది.\nఅవసరమని మీకు తెలిస్తేనే మార్చండి.",
+ "config-pg-test-error": "డేటాబేసు <strong>$1</strong> కి కనెక్టు కాలేకపోయాం: $2",
+ "config-sqlite-dir": "SQLite డేటా డైరెక్టరీ:",
+ "config-oracle-def-ts": "డిఫాల్టు టేబుల్‍స్పేసు:",
+ "config-oracle-temp-ts": "తాత్కాలిక టేబుల్‍స్పేసు:",
+ "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] ప్రజామోదం పొందిన ఓపెన్‍సోర్సు డేటాబేసు వ్యవస్థ. దానిలో చిన్న చితకా లోపాలుండే అవకాశం ఉంది. అందుచేత దాన్ని ఉత్పాదక రంగంలో వాడవచ్చని చెప్పలేం. ([http://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 అమరికలు",
+ "config-header-postgres": "PostgreSQL అమరికలు",
+ "config-header-sqlite": "SQLite అమరికలు",
+ "config-header-oracle": "Oracle అమరికలు",
+ "config-header-mssql": "Microsoft SQL Server అమరికలు",
+ "config-invalid-db-type": "తప్పుడు డాటాబేసు రకం",
+ "config-missing-db-name": "\"{{int:config-db-name}}\" ను తప్పకుండా ఇవ్వాలి",
+ "config-missing-db-host": "\"{{int:config-db-host}}\" ను తప్పకుండా ఇవ్వాలి",
+ "config-missing-db-server-oracle": "\"{{int:config-db-host-oracle}}\" ను తప్పకుండా ఇవ్వాలి",
+ "config-invalid-db-name": "డేటాబేసు పేరు సరైనది కాదు \"$1\".\nASCII అక్షరాలు (a-z, A-Z), అంకెలు (0-9), క్రీగీత (_) and హైఫన్ (-) లను మాత్రమే వాడాలి.",
+ "config-invalid-db-prefix": "డేటాబేసు ఆదిపదం (ప్రిఫిక్స్) సరైనది కాదు \"$1\".\nASCII అక్షరాలు (a-z, A-Z), అంకెలు (0-9), క్రీగీత (_) and హైఫన్ (-) లను మాత్రమే వాడాలి.",
+ "config-connection-error": "$1.\n\nక్రింది హోస్టు, వాడుకరిపేరు మరియు సంకేతపదాలను ఒకసారి సరిచూసుకుని అప్పుడు ప్రయత్నించండి.",
+ "config-invalid-schema": "\"$1\" MediaWiki కోసం చెల్లని స్కీమా.\nASCII అక్షరాలు (a-z, A-Z), అంకెలు (0-9) క్రీగీత (_) లను మాత్రమే వాడాలి.",
+ "config-db-sys-user-exists-oracle": "వాడుకరి ఖాతా \"$1\" ఈసరికే ఉంది. కొత్త ఖాతాను సృష్టించేందుకు SYSDBA ను మాత్రమే వాడాలి!",
+ "config-postgres-old": "PostgreSQL $1 గానీ ఆ తరువాతిది గానీ అవసరం. మీకు $2 ఉంది.",
+ "config-mssql-old": "మైక్రోసాఫ్ట్ SQL సర్వర్ $1 లేదీ దాని తరువాతి వర్షన్ ఉండాలి. మీ దగ్గర $2 ఉంది.",
+ "config-sqlite-name-help": "మీ వికీని గుర్తించే పేరు ఒకదాన్ని ఎంచుకోండి.\nస్పేసులు గానీ, హైఫన్‍లు గానీ వాడకండి.\nదాన్ని SQLite డేటాఫైలు పేరు కోసంవాడతాం.",
+ "config-sqlite-mkdir-error": "డేటా డైరెక్టరీని సృష్టించడంలో లోపం \"$1\".\nస్థానాన్ని సరిచూసి మళ్ళీ ప్రయత్నించండి.",
+ "config-sqlite-connection-error": "$1.\n\nకింద ఉన్న డేటా డైరెక్టరీ, డేటాబేసు పేరును సరిచూసి మళ్ళీ ప్రయత్నించండి.",
+ "config-sqlite-readonly": "ఫైలు <code>$1</code> లో రాసే అనుమతి లేదు.",
+ "config-sqlite-cant-create-db": "డేటాబేసు ఫైలు <code>$1</code> సృష్టించలేకపోయాం.",
+ "config-upgrade-done-no-regenerate": ".ఉన్నతీకరణ పూర్తయింది.\n\nఇక మీరు [$1 మీ వికీలో పని మొదలుపెట్టవచ్చు].",
+ "config-regenerate": "LocalSettings.php ని తిరిగి సృజించు →",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code> క్వెరీ విఫలమైంది!",
+ "config-unknown-collation": "<strong>హెచ్చరిక:</strong> డేటాబేసు గుర్తింపులేని కొల్లేషన్ వాడుతున్నది.",
+ "config-db-web-account": "వెబ్ అందుబాటు కోసం డేటాబేసు ఖాతా",
+ "config-db-web-help": "మామూలుగా వికీని నడిపేటపుడు, వెబ్ సర్వరు డేటాబేసును కనెక్టయేందుకు వాడే వాడుకరిపేరు, సంకేతపదాలను ఎంచుకోండి.",
+ "config-db-web-account-same": "స్థాపనకు వాడిన ఖాతానే వాడు",
+ "config-db-web-create": "ఖాతా ఉనికిలో లేకపోతే, సృష్టించు",
+ "config-db-web-no-create-privs": "స్థాపన కోసం మీరిచ్చిన ఖాతాకు ఓ కొత్త ఖాతాను సృష్టించే అనుమతులు లేవు.\nఇక్కడ మీరిచ్చే ఖాతా తప్పనిసరిగా ఈసరికే ఉనికిలో ఉండాలి.",
+ "config-mysql-engine": "స్టోరేజీ ఇంజను:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "డేటాబేసు కారెక్టరు సెట్:",
+ "config-mysql-binary": "బైనరీ",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "ఆథెంటికేషన్ రకం:",
+ "config-mssql-sqlauth": "SQL Server ఆథెంటికేషన్",
+ "config-mssql-windowsauth": "విండోస్ ఆథెంటికేషన్",
+ "config-site-name": "వికీ యొక్క పేరు:",
+ "config-site-name-help": "ఇది బ్రౌజరు టిటిలుబారు లోను, అనేక ఇతర చోట్లా కనిపిస్తుంది.",
+ "config-site-name-blank": "ఓ సైటు పేరును ఇవ్వండి.",
+ "config-project-namespace": "ప్రాజెక్టు పేరుబరి:",
+ "config-ns-generic": "ప్రాజెక్టు",
+ "config-ns-site-name": "వికీ పేరే: $1",
+ "config-ns-other": "ఇతర (ఇవ్వండి)",
+ "config-ns-other-default": "నావికీ",
+ "config-ns-invalid": "ఇచ్చిన పేరుబరి \"<nowiki>$1</nowiki>\" చెల్లనిది.\nవేరే ప్రాజెక్టు పేరుబరిని ఇవ్వండి.",
+ "config-ns-conflict": "ఇచ్చిన పేరుబరి \"<nowiki>$1</nowiki>\" డిఫాల్టు MediaWiki పేరుబరి ఒకదానితో ఘర్షిస్తోంది.\nవేరే ప్రాజెక్టు పేరుబరిని ఇవ్వండి.",
+ "config-admin-box": "నిర్వాహకుని ఖాతా",
+ "config-admin-name": "మీ వాడుకరి పేరు:",
+ "config-admin-password": "సంకేతపదం:",
+ "config-admin-password-confirm": "సంకేతపదం మళ్ళీ:",
+ "config-admin-name-blank": "ఓ నిర్వాహక వాడుకరిపేరును ఇవ్వండి",
+ "config-admin-name-invalid": "ఇచ్చిన వాడుకరిపేరు \"<nowiki>$1</nowiki>\" చెల్లనిది.\nవేరే వాడుకరిపేరును ఇవ్వండి.",
+ "config-admin-password-blank": "నిర్వాహక ఖాతాకు సంకేతపదం ఇవ్వండి.",
+ "config-admin-password-mismatch": "మీరిచ్చిన రెండు సంకేతపదాలు సరిపోలడం లేదు.",
+ "config-admin-email": "ఈ-మెయిలు చిరునామా:",
+ "config-admin-error-user": "\"<nowiki>$1</nowiki>\" పేరుతో నిర్వాహకుణ్ణి సృష్టించబోతే అంతర్గత లోపం దొర్లింది.",
+ "config-admin-error-password": "నిర్వాహకుడు \"<nowiki>$1</nowiki>\" కు సంకేతపదాన్ని ఇవ్వబోతే అంతర్గత లోపం దొర్లింది: <pre>$2</pre>",
+ "config-admin-error-bademail": "మీరు చెల్లని ఈమెయిలు చిరునామా ఇచ్చారు.",
+ "config-subscribe": "[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce విడుదల ప్రకటనల మెయిలింగు జాబితా] కు చందాదారులు కండి.",
+ "config-almost-done": "దాదాపు పూర్తైనట్లే!\nమిగతా కాన్ఫిగరేషన్ను దాటేసి, ఇప్పుడే వికీని స్థాపించుకోవచ్చు.",
+ "config-optional-continue": "నన్ను మరిన్ని ప్రశ్నలు అడుగు.",
+ "config-optional-skip": "నాకు బోరు కొట్టేసింది, ఇక వికీని స్థాపించేయ్.",
+ "config-profile": "వాడుకరి హక్కుల ప్రవర:",
+ "config-profile-wiki": "వికీని తెరువు",
+ "config-profile-no-anon": "ఖాతా సృష్టింపు తప్పనిసరి",
+ "config-profile-fishbowl": "అధీకృత వాడుకరులు మాత్రమే",
+ "config-profile-private": "అంతరంగిక వికీ",
+ "config-license": "కాపీహక్కులు మరియు లైసెన్సు:",
+ "config-license-none": "లైసెన్సు పాదపీఠిక వద్దు",
+ "config-license-pd": "సార్వజనీనం",
+ "config-email-settings": "ఈ-మెయిల్ అమరికలు",
+ "config-enable-email": "ఈమెయిళ్ళు పంపడాన్ని చేతనం చెయ్యి",
+ "config-email-user": "వాడుకరి-నుండి-వాడుకరికి ఈమెయిళ్ళని చేతనం చెయ్యి",
+ "config-email-user-help": "వాడుకరులంతా తమ తమ అభిరుచుల్లో సెట్ చేసుకుని ఉంటే, ప్రతి ఒక్కరూ ప్రతీ ఒక్కరికీ ఈమెయిళ్ళు పంపించుకునే వీలును కల్పించు.",
+ "config-email-usertalk": "వాడుకరి చర్చా పేజీ వార్తాహరిని చేతనం చెయ్యి",
+ "config-email-usertalk-help": "వాడుకరులు వారి ప్రాధాన్యతలలో చేతనం చేస్తే వారి వీక్షించే పేజీలు గురించి నోటిఫికేషన్లు అందుకోవడానికి అనుమతించు.",
+ "config-email-watchlist": "వీక్షణజాబితా నోటిఫికేషన్లను చేతనం చేయి",
+ "config-email-watchlist-help": "వాడుకరులు వారి ప్రాధాన్యతలలో చేతనం చేస్తే వారి వీక్షించే పేజీలు గురించి నోటిఫికేషన్లు అందుకోవడానికి అనుమతించు.",
+ "config-email-auth": "ఈమెయిల్ ప్రమాణీకరణను చేతనం చేయండి",
+ "config-email-auth-help": "దీన్ని ఎంచుకుంటే, వాడుకరులు ఈమెయిలు కొత్తగా ఇచ్చేటపుడు లేదా మార్చేటపుడు తమకు వచ్చిన లింకు నొక్కి తమ చిరునామాను నిర్ధారించాలి.\nనిర్ధారించిన ఈమెయిలు చిరునామాలు మాత్రమే ఇతర వాడుకరుల నుంచి, మార్పు నోటిఫికేషన్లు అందుకునే వీలుంది.\nబహిరంగ వికీలలో దీన్ని ఎంచుకోవడం <strong>ఉత్తమమైన</strong> పద్ధతి. ఎందుకంటే మీ ఈమెయిలును ఎవరూ దుర్వినియోగం చేయలేరు.",
+ "config-email-sender": "తిరుగు టపా చిరునామా:",
+ "config-upload-settings": "బొమ్మలు, ఫైళ్ళ ఎక్కింపులు",
+ "config-upload-enable": "ఫైళ్ళ ఎక్కింపును చేతనం చెయ్యి",
+ "config-upload-deleted": "తొలగించిన దస్త్రాల కొరకు సంచయం:",
+ "config-upload-deleted-help": "తొలగించిన ఫైళ్ళను ఏ డైరెక్టరీలో అటకెక్కించాలో ఎంచుకోండి.\nఇది వెబ్‍లో అందుబాటులో లేకుండా ఉంటే మంచిది.,",
+ "config-logo": "లోగో URL:",
+ "config-instantcommons": "తక్షణ కామన్స్ ను చేతనం చెయ్యి",
+ "config-cc-again": "మళ్ళీ ఎంచుకోండి...",
+ "config-cc-not-chosen": "ఏ Creative Commons లైసెన్సు కావాలో ఎంచుకుని \"కొనసాగు\" ను నొక్కండి.",
+ "config-advanced-settings": "ఉన్నత స్వరూపణం",
+ "config-cache-options": "ఆబ్జక్ట్ క్యాషింగ్ అమరికలు:",
+ "config-cache-help": "ఆబ్జక్ట్ క్యాషింగ్ అనేది తరచు వాడే డేటాను సిద్ధంగా ఉంచడం ద్వారా మీడియావికీ పనితీరును మెరుగుపరచడానికి ఉద్దేశించినది.\nమధ్యతరగతి నుంచి పెద్ద సైట్లలో దీనిని చేతనం చేయడాన్ని ప్రోత్సహిస్తున్నాం. అలాగే చిన్న సైట్లు కూడా దీన్నుంచి ప్రయోజనం పొందగలవు.",
+ "config-memcached-help": "Memcached కోసం వాడాల్సిన ఐపీ చిరునామాలు.\nవరస కొకటి రాయాలి. పోర్టును కూడా సూచించాలి. ఉదాహరణకు:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-badip": "Memcached కోసం ఇచ్చిన ఐపీ చిరునామా చెల్లనిది: $1.",
+ "config-memcache-noport": "Memcached సర్వరు కోసం వాడేందుకు పోర్టును ఇవ్వలేదు: $1.\nమీకు పోర్టు తెలీనట్లైతే, డిఫాల్టు పోర్టు: 11211.",
+ "config-memcache-badport": "Memcached పోర్టు సఖ్యలు $1, $2 ల మధ్య ఉండాలి.",
+ "config-extensions": "పొడిగింతలు",
+ "config-extensions-help": "పైన చూపిన పొడిగింతలు మీ <code>./extensions</code> డైరెక్టరీలో ఉన్నాయి.\n\nవాటికి అదనంగా కాన్ఫిగరేషన్ అవసరం కావచ్చు. అయితే మీరు వాటిని చేతనం చెయ్యవచ్చు.",
+ "config-install-alreadydone": "<strong>హెచ్చరిక:</strong> మీరు ఈసరికే MediaWiki ని స్థాపించినట్లుగా అనిపిస్తోంది. మళ్ళీ స్థాపించే ప్రయత్నం చేస్తున్నట్లున్నారు.\nతరువాత పేజీకి వెళ్ళండి.",
+ "config-install-begin": "\"{{int:config-continue}}\" నొక్కి, MediaWiki స్థాపనను మొదలుపెట్టవచ్చు.\nఇంకా మార్పులు చెయ్యదలిస్తే, \"{{int:config-back}}\" నొక్కండి.",
+ "config-install-step-done": "పూర్తయింది",
+ "config-install-step-failed": "విఫలమైంది",
+ "config-install-extensions": "పొడిగింతలను చేరుస్తున్నాం",
+ "config-install-database": "డేటాబేసును స్థాపిస్తున్నాం",
+ "config-install-schema": "స్కీమాను సృష్టిస్తున్నాం",
+ "config-install-pg-schema-not-exist": "PostgreSQL స్కీమా లేదు.",
+ "config-install-pg-schema-failed": "టేబుళ్ళ సృష్టి విఫలమైంది.\nవాడుకరి \"$1\" కు స్కీమా \"$2\" లో రాసే అనుమతి ఉన్నదని నిర్ధారించుకోండి.",
+ "config-install-pg-commit": "మార్పులను నిర్ధారిస్తున్నాం",
+ "config-install-pg-plpgsql": "PL/pgSQL భాష కోసం పరీక్షిస్తున్నాం",
+ "config-pg-no-plpgsql": "డేటాబేసు $1 లో PL/pgSQL భాషను స్థాపించాల్సిన అవసరం ఉంది.",
+ "config-pg-no-create-privs": "స్థాపన కోసం మీరిచ్చిన ఖాతాకు, ఓ ఖాతా సృష్టించేందుకు అవసరమైన హక్కులు లేవు.",
+ "config-install-user": "డేటాబేసు వాడుకరిని సృష్టిస్తున్నాం",
+ "config-install-user-alreadyexists": "వాడుకరి \"$1\" ఈసరికే ఉన్నారు",
+ "config-install-user-create-failed": "వాడుకరి \"$1\" సృష్టించడం విఫలమైంది: $2",
+ "config-install-user-grant-failed": "వాడుకరి \"$1\" కి అనుమతి ప్రసాదించడం విఫలమైంది: $2",
+ "config-install-user-missing": "సూచించిన వాడుకరి \"$1\" ఉనికిలో లేరు.",
+ "config-install-user-missing-create": "మీరిచ్చిన వాడుకరి \"$1\" ఉనికిలో లేదు.\nదాన్ని సృష్టించదలిస్తే, కింద ఉన్న \"ఖాతాను సృష్టించు\" చెక్‍బాక్సును నొక్కండి.",
+ "config-install-tables": "టేబుళ్ళను సృష్టిస్తున్నాం",
+ "config-install-tables-exist": "<strong>హెచ్చరిక:</strong> MediaWiki టేబుళ్ళు ఈసరికే ఉన్నట్లుగా ఉన్నాయి.\nసృష్టించడాన్ని తప్పిస్తున్నాం.",
+ "config-install-tables-failed": "<strong>లోపం:</strong> టేబుల్ సృష్టి ఈ లోపంతో విఫలమైంది: $1",
+ "config-install-interwiki": "డిఫాల్టు అంతరవికీ టేబులులో డేటాను పెడుతున్నాం",
+ "config-install-interwiki-list": "<code>interwiki.list</code> ఫైలును చదవలేకపోయాం.",
+ "config-install-interwiki-exists": "<strong>హెచ్చరిక:</strong> అంతర్వికీ టేబుల్ లో ఈసరికే ఎంట్రీలున్నట్లుగా ఉన్నాయి.\nడిఫాల్టు జాబితాను దాటేస్తున్నాం.",
+ "config-install-stats": "గణాంకాలను తొలికరిస్తున్నాం (ఇనిషియలైజింగ్)",
+ "config-install-keys": "రహస్య కీలను సృష్టిస్తున్నాం",
+ "config-install-sysop": "అధికారి ఖాతా సృష్టిస్తున్నాము",
+ "config-install-mainpage": "డిఫాల్టు కంటెంటుతో మొదటిపేజీని సృష్టిస్తున్నాం",
+ "config-install-extension-tables": "చేతనం చేసిన పొడిగింతల కోసం టేబుళ్ళను సృష్టిస్తున్నాం",
+ "config-install-mainpage-failed": "మొదటిపేజీని చొప్పించలేకపోయాం: $1",
+ "config-download-localsettings": "<code>LocalSettings.php</code> దించు",
+ "config-help": "సహాయం",
+ "config-nofile": "\"$1\" ఫైలు దొరకలేదు. దాన్ని గానీ తొలగించారా?",
+ "mainpagetext": "'''మీడియా వికీని విజయవంతంగా ప్రతిష్టించాం.'''",
+ "mainpagedocfooter": "వికీ సాఫ్టువేరును వాడటనికి కావలిసిన సమాచారం కోసం [//meta.wikimedia.org/wiki/Help:Contents వాడుకరుల గైడు]ను సందర్శించండి.\n\n== మొదలు పెట్టండి ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings మీడియావికీ పనితీరు, అమరిక మార్చుకునేందుకు వీలుకల్పించే చిహ్నాల జాబితా]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ మీడియావికీపై తరుచుగా అడిగే ప్రశ్నలు]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce మీడియావికీ సాఫ్టువేరు కొత్త వెర్షను విడుదలల గురించి తెలిపే మెయిలింగు లిస్టు]"
+}
diff --git a/includes/installer/i18n/tet.json b/includes/installer/i18n/tet.json
new file mode 100644
index 00000000..1a5ea26f
--- /dev/null
+++ b/includes/installer/i18n/tet.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "MF-Warburg"
+ ]
+ },
+ "config-page-language": "Lian",
+ "config-page-name": "Naran"
+}
diff --git a/includes/installer/i18n/tg-cyrl.json b/includes/installer/i18n/tg-cyrl.json
new file mode 100644
index 00000000..98ac19cc
--- /dev/null
+++ b/includes/installer/i18n/tg-cyrl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ibrahim"
+ ]
+ },
+ "mainpagetext": "'''Нармафзори МедиаВики бо муваффақият насб шуд.'''",
+ "mainpagedocfooter": "Барои иттилоот дар бораи истифода нармафзори вики аз //meta.wikimedia.org/wiki/Help:Contents User's Guide] истифода баред.\n\n== Оғоз ба кор ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Феҳристи танзимоти пайгирбандӣ]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Пурсишҳои МедиаВики]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Феҳристи роҳнамои МедиаВики]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Маҳалликунонии МедиаВики ба забони шумо]"
+}
diff --git a/includes/installer/i18n/tg-latn.json b/includes/installer/i18n/tg-latn.json
new file mode 100644
index 00000000..41d148a5
--- /dev/null
+++ b/includes/installer/i18n/tg-latn.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Liangent"
+ ]
+ },
+ "mainpagetext": "'''Narmafzori MediaViki bo muvaffaqijat nasb şud.'''",
+ "mainpagedocfooter": "Az [//meta.wikimedia.org/wiki/Help:Contents Rohnamoi Korbaron] baroi istifodai narmafzori viki kūmak bigired.\n\n== Oƣoz ba kor ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Fehristi tanzimoti pajgirbandī]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Pursişhoi MediaViki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Fehristi rojnomahoi nusxahoi MediaViki]"
+}
diff --git a/includes/installer/i18n/th.json b/includes/installer/i18n/th.json
new file mode 100644
index 00000000..ec6526be
--- /dev/null
+++ b/includes/installer/i18n/th.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Korrawit",
+ "Horus"
+ ]
+ },
+ "mainpagetext": "<strong>ติดตั้งมีเดียวิกิสำเร็จ</strong>",
+ "mainpagedocfooter": "ศึกษา[//meta.wikimedia.org/wiki/Help:Contents คู่มือการใช้งาน] สำหรับเริ่มต้นใช้งานซอฟต์แวร์วิกิ\n\n== เริ่มต้น ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings รายการการปรับแต่งระบบ] (ภาษาอังกฤษ)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ คำถามที่ถามบ่อยในมีเดียวิกิ] (ภาษาอังกฤษ)\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce เมลลิงลิสต์ของมีเดียวิกิ]"
+}
diff --git a/includes/installer/i18n/tk.json b/includes/installer/i18n/tk.json
new file mode 100644
index 00000000..02922760
--- /dev/null
+++ b/includes/installer/i18n/tk.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hanberke"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki şowlulyk bilen guruldy.'''",
+ "mainpagedocfooter": "Wiki programmasynyň ulanylyşy hakynda maglumat almak üçin [//meta.wikimedia.org/wiki/Help:Contents ulanyjy gollanmasyna] serediň.\n\n== Öwrenjeler ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Konfigurasiýa sazlamalary]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki SSS]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçta sanawy]"
+}
diff --git a/includes/installer/i18n/tl.json b/includes/installer/i18n/tl.json
new file mode 100644
index 00000000..9a894c25
--- /dev/null
+++ b/includes/installer/i18n/tl.json
@@ -0,0 +1,305 @@
+{
+ "@metadata": {
+ "authors": [
+ "AnakngAraw",
+ "Sky Harbor",
+ "아라",
+ "Amire80",
+ "Jojit fb"
+ ]
+ },
+ "config-desc": "Ang tagapagluklok para sa MediaWiki",
+ "config-title": "Instalasyong $1 ng MediaWiki",
+ "config-information": "Kabatiran",
+ "config-localsettings-upgrade": "Napansin ang isang talaksang <code>LocalSettings.php</code>.\nUpang maitaas ang uri ng pagluluklok na ito, paki ipasok ang halaga ng <code>$wgUpgradeKey</code> sa loob ng kahong nasa ibaba.\nMatatagpuan mo ito sa loob ng <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Napansin ang isang talaksan ng <code>LocalSettings.php</code>.\nUpang isapanahon ang pagtatalagang ito, mangyaring patakbuhin sa halip ang <code>update.php</code>",
+ "config-localsettings-key": "Susi ng pagsasapanahon:",
+ "config-localsettings-badkey": "Hindi tama ang susing ibinigay mo.",
+ "config-upgrade-key-missing": "Napansin ang isang umiiral na pagtatalaga ng MediaWiki.\nUpang isapanahon ang katalagahang ito, mangyaring ilagay ang sumusunod na guhit sa ilalim ng iyong <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Lumilitaw na hindi pa buo ang umiiral na <code>LocalSettings.php</code>.\nAng pabagu-bagong $1 ay hindi nakatakda.\nMangyaring baguhin ang <code>LocalSettings.php</code> upang ang maitakda ang pagpapabagu-bagong ito, at pindutin ang \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Isang kamalian ang nakatagpo noong kumakabit sa kalipunan ng dato na ginagamit ang tinukoy na mga katakdaan sa loob ng <code>LocalSettings.php</code> o\n<code>AdminSettings.php</code>. Paki kumpunihin ang mga katakdaang ito at subukang muli.\n\n$1",
+ "config-session-error": "Kamalian sa pagsisimula ng sesyon: $1",
+ "config-session-expired": "Tila nagwakas na ang inilaan sa iyong panahon ng dato.\nAng inilaang mga panahon ay iniayos para sa isang panahon ng buhay na $1.\nMapapataas mo ito sa pamamagitan ng pagtatakda ng <code>session.gc_maxlifetime</code> sa loob ng php.ini.\nMuling simulan ang proseso ng pagluluklok.",
+ "config-no-session": "Nawala ang iyong datos ng sesyon!\nSuriin ang iyong php.ini at tiyakin na ang <code>session.save_path</code> ay nakatakda sa angkop na direktoryo.",
+ "config-your-language": "Ang wika mo:",
+ "config-your-language-help": "Pumili ng isang wikang gagamitin habang isinasagawa ang pagtatalaga.",
+ "config-wiki-language": "Wika ng Wiki:",
+ "config-wiki-language-help": "Piliin ang wika kung saan mangingibabaw na isusulat ang wiki.",
+ "config-back": "← Bumalik",
+ "config-continue": "Magpatuloy →",
+ "config-page-language": "Wika",
+ "config-page-welcome": "Maligayang pagdating sa MediaWiki!",
+ "config-page-dbconnect": "Umugnay sa kalipunan ng datos",
+ "config-page-upgrade": "Itaas ng uri ang umiiral na pagkakatalaga",
+ "config-page-dbsettings": "Mga katakdaan ng kalipunan ng datos",
+ "config-page-name": "Pangalan",
+ "config-page-options": "Mga mapipili",
+ "config-page-install": "Italaga",
+ "config-page-complete": "Buo na!",
+ "config-page-restart": "Simulan muli ang pag-iinstala",
+ "config-page-readme": "Basahin ako",
+ "config-page-releasenotes": "Pakawalan ang mga tala",
+ "config-page-copying": "Kinokopya",
+ "config-page-upgradedoc": "Itinataas ang uri",
+ "config-page-existingwiki": "Umiiral na wiki",
+ "config-help-restart": "Nais mo bang hawiin ang lahat ng nasagip na datong ipinasok mo at muling simulan ang proseso ng pagluluklok?",
+ "config-restart": "Oo, muling simulan ito",
+ "config-welcome": "=== Pagsusuring pangkapaligiran ===\nIsinasagawa ang payak na mga pagsusuri upang makita kung ang kapaligirang ito ay angkop para sa pagluluklok ng MediaWiki.\nDapat mong ibigay ang mga kinalabasan ng mga pagsusuring ito kung kailangan mo ng tulong habang nagluluklok.",
+ "config-copyright": "=== Karapatang-ari at Tadhana ===\n\n$1\n\nAng programang ito ay malayang software; maaari mo itong ipamahagi at/o baguhin sa ilalim ng mga tadhana ng Pangkalahatang Pampublikong Lisensiyang GNU ayon sa pagkakalathala ng Free Software Foundation; na maaaring bersyong 2 ng Lisensiya, o (kung nais mo) anumang susunod na bersyon.\n\nIpinamamahagi ang programang ito na umaasang magiging gamitin, subaliut '''walang anumang katiyakan'''; na walang pahiwatig ng '''pagiging mabenta''' o '''kaangkupan para sa isang tiyak na layunin'''.\nTingnan ang Pangkalahatang Pampublikong Lisensiyang GNU para sa mas maraming detalye.\n\nDapat nakatanggap ka ng <doclink href=Copying>isang sipi ng Pangkalahatang Pampublikong Lisensiyang GNU</doclink> kasama ng programang ito; kung hindi, sumulat sa Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [http://www.gnu.org/licenses//gpl.html basahin ito sa Internet].",
+ "config-sidebar": "* [//www.mediawiki.org Tahanan ng MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Gabay ng Tagagamit]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Gabay ng Tagapangasiwa]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Mga Malimit Itanong]\n----\n* <doclink href=Readme>Basahin ako</doclink>\n* <doclink href=ReleaseNotes>Mga tala ng paglalabas</doclink>\n* <doclink href=Copying>Pagkopya</doclink>\n* <doclink href=UpgradeDoc>Pagsasapanahon</doclink>",
+ "config-env-good": "Nasuri na ang kapaligiran.\nMailuluklok mo ang MediaWiki.",
+ "config-env-bad": "Nasuri na ang kapaligiran.\nHindi mo mailuklok ang MediaWiki.",
+ "config-env-php": "Naitalaga ang PHP na $1.",
+ "config-env-php-toolow": "Naitalaga ang PHP $1.\nSubalit, nangangailangan ang MediaWiki ng PHP $2 o mas mataas pa.",
+ "config-unicode-using-utf8": "Ginagamit ang utf8_normalize.so ni Brion Vibber para sa pagpapanormal ng Unikodigo.",
+ "config-unicode-using-intl": "Ginagamit ang [http://pecl.php.net/intl intl dugtong na PECL] para sa pagsasanormal ng Unikodigo.",
+ "config-unicode-pure-php-warning": "'''Babala''': Ang [http://pecl.php.net/intl dugtong ng internasyunal na PECL] ay hindi makukuha upang makapanghawak ng pagpapanormal ng Unikodigo, na babagsak na pabalik sa mabagal na pagsasakatuparan ng dalisay na PHP.\nKapag nagpapatakbo ka ng isang pook na mataas ang trapiko, dapat kang bumasa ng kaunti hinggil sa [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations pagpapanormal ng Unikodigo].",
+ "config-unicode-update-warning": "'''Babala''': Ang nakaluklok na bersiyon ng pambalot ng pagpapanormal ng Unikodigo ay gumagamit ng isang mas matandang bersiyon ng aklatan ng [http://site.icu-project.org/ proyekto ng ICU].\nDapat kang [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations magtaas ng uri] kung may pag-aalala ka hinggil sa paggamit ng Unikodigo.",
+ "config-no-db": "Hindi matagpuan ang isang angkop na tagapagmaneho ng kalipunan ng datos! Kailangan mong magluklok ng isang tagapagmaneho ng kalipunan ng dato para sa PHP.\nTinatangkilik ang sumusunod na mga uri ng kalipunan ng dato: $1.\n\nKung ikaw ay nasa isang pinagsasaluhang pagpapasinaya, hilingin sa iyong tagapagbigay ng pagpapasinaya na iluklok ang isang angkop na tagapagmaneho ng kalipunan ng dato.\nKung ikaw mismo ang nangalap ng PHP, muling isaayos ito na pinagagana ang isang kliyente ng kalipunan ng dato, halimbawa na ang paggamit ng <code>./configure --with-mysql</code>.\nKung iniluklok mo ang PHP mula sa isang pakete ng Debian o Ubuntu, kung gayon kailangan mo ring magluklok ng modyul na php5-mysql.",
+ "config-outdated-sqlite": "'''Babala''': mayroong kang $1 ng SQLite, na mas mababa kaysa sa pinaka mababang kailangang bersiyon na $2. Magiging hindi makukuha ang SQLite.",
+ "config-no-fts3": "'''Warning''': Ang SQLite ay hindi itinala at tinipon na wala ang [//sqlite.org/fts3.html modulong FTS3], ang mga tampok na panghanap ay magiging hindi makukuha sa ibabaw ng panlikod na dulong ito.",
+ "config-register-globals": "'''Babala: Ang mapipili na <code>[http://php.net/register_globals register_globals]</code> ng PHP ay pinagagana.'''\n'''Huwag paganahin kung kaya mo.'''\nAandar ang MediaWiki, subalit ang tagapaghain mo ay nakalantad sa maaaring maganap na mga kahinaang pangkatiwasayan.",
+ "config-magic-quotes-runtime": "'''Malubha: Masigla ang [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''\nAng piniling ito ay hindi mahuhulaan na pipinsala sa lahok na dato.\nHindi mo maaaring iluklok o gamitin ang MediaWiki maliban na lamang kung hindi na gumagana ang pinili na ito.",
+ "config-magic-quotes-sybase": "'''Malubha: Masigla ang [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''\nHindi mahuhulaan na sinisira ng napiling ito ang lahok na dato.\nHindi mo maaaring iluklok o gamitin ang MediaWiki maliban na lamang kung hindi na pinagagana ang napiling ito.",
+ "config-mbstring": "'''Malubha: Masigla ang [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''\nAng napiling ito ay nagdurulot ng mga kamalian at maaaring sumira nang hindi nahuhulaan ang dato.\nHindi mo maaaring iluklok o gamitin ang MediaWiki maliban na lamang kung hindi na pinagagana ang napiling ito.",
+ "config-safe-mode": "'''Babala:''' Masigla ang [http://www.php.net/features.safe-mode safe mode] ng PHP.\nMaaari itong magdulot ng mga suliranin, partikular na kung gumagamit ng mga ikinargang paitaas na talaksan at ng suporta sa <code>math</code>.",
+ "config-xml-bad": "Nawawala ang modulong XML ng PHP.\nNangangailangan ang MediaWiki ng mga tungkulin sa loob ng modulong ito at hindi aandar sa loob ng ganitong pagkakaayos.\nKung pinapatakbo mo ang Mandrake, iluklok ang pakete ng php-xml.",
+ "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-ctype": "'''Maluba''': Dapat na tipunin ang PHP na mayroong suporta para sa [http://www.php.net/manual/en/ctype.installation.php dugtong Ctype].",
+ "config-xcache": "Ininstala na ang [http://xcache.lighttpd.net/ XCache]",
+ "config-apc": "Ininstala na ang [http://www.php.net/apc APC]",
+ "config-wincache": "Ininstala na ang [http://www.iis.net/download/WinCacheForPhp WinCache]",
+ "config-no-cache": "'''Babala:''' Hindi mahanap ang [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nHindi pinapagana ang pagbabaon ng mga bagay.",
+ "config-mod-security": "'''Babala''': Ang tagapaghain mo ng sangkasaputan ay pinagana na mayroong [http://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 [http://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-imagemagick": "Natagpuan ang ImageMagick: <code>$1</code>.\nPapaganahin ang pagkakagyat ng larawan kapag pinagana mo ang mga pagkakargang paitaas.",
+ "config-gd": "Natagpuan ang pinasadyang nakapaloob na grapiks ng GD.\nPapaganahin ang pagkakagyat ng larawan kapag pinagana mo ang mga pagkakargang paitaas.",
+ "config-no-scaling": "Hindi matagpuan ang aklatang GD o ImageMagick.\nHindi papaganahin ang pagkakagyat ng larawan.",
+ "config-no-uri": "'''Kamalian:''' Hindi matukoy ang kasalukuyang URI.\nPinigilan ang pag-iinstala.",
+ "config-no-cli-uri": "'''Babala''': Walang tinukoy na --landas ng panitik, ginagamit ang likas na katakdaan: <code>$1</code>.",
+ "config-using-server": "Ginagamit ang pangalan ng tagapaghain na \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Ginagamit ang URL ng tagapaghain na \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Babala:''' Ang iyong likas na nakatakdang direktoryo para sa paitaas na mga pagkakarga na <code>$1</code> ay may kahinaan laban sa pagsasagawa ng mga panitiki na hindi makatwiran. Bagaman sinisiyasat ng MediaWiki ang lahat ng paitaas na naikargang mga talaksan para sa mga panganib na pangkatiwasayan, mataas na iminumungkahi na [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security isara ang kahinaang ito na pangkatiwasayan] bago paganahin ang paitaas na mga pagkakarga.",
+ "config-no-cli-uploads-check": "'''Babala:''' Ang iyong likas na nakatakdang direktoryo para sa paitaas na mga pagkakarga (<code>$1</code>) ay hindi nasuri para sa kahinaan laban sa pagsasagawa ng panitik na hindi makatwiran habang iniluluklok ang Ugnayang Mukha ng Guhit ng Kaataasan o Command-Line Interface (CLI).",
+ "config-brokenlibxml": "Ang sistema mo ay mayroong isang pagsasama ng mga bersiyon ng PHP at libxml2 na maaaring masurot at maaaring makapagsanhi ng pagkasira ng datong nakakubli sa loob ng MediaWiki at iba pang mga aplikasyon ng sangkasaputan.\nMagtaas ng uri upang maging PHP 5.2.9 o mas lalong huli at libxml2 2.7.3 o mas lalong huli ([//bugs.php.net/bug.php?id=45996 isinalansan ang surot o ''bug'' na mayroong PHP]). Binigo ang pagluluklok.",
+ "config-suhosin-max-value-length": "Nakaluklok ang Suhosin at hinahanggahan ang haba ng parametro ng GET sa $1 mga byte. Ang sangkap na ResourceLoader ng MediaWiki ay gagana sa paligid ng hangganang ito, subalit pasasamain nito ang pagganap. Kung talagang maaari, dapat mong itakda ang <code>suhosin.get.max_value_length</code> upang maging 1024 o mas mataas sa loob ng <code>php.ini</code>, at itakda ang <code>$wgResourceLoaderMaxQueryLength</code> sa katulad na halaga sa loob ng LocalSettings.php.",
+ "config-db-type": "Uri ng kalipunan ng datos:",
+ "config-db-host": "Tagapagpasinaya ng kalipunan ng datos:",
+ "config-db-host-help": "Kung ang iyong tagapaghain ng kalipunan ng dato ay nasa ibabaw ng isang ibang tagapaghain, ipasok ang pangalan ng tagapagpasinaya o tirahan ng IP dito.\n\nKung gumagamit ka ng pinagsasaluhang pagpapasinaya ng sangkasaputan, dapat ibigay sa iyo ng iyong tagapagbigay ng pagpapasinaya ang tamang pangalan ng tagapagpasinaya sa loob ng kanilang kasulatan.\n\nKapag nagluluklok ka sa ibabaw ng isang tagapaghain ng Windows at gumagamit ng MySQL, maaaring hindi gumana ang paggamit ng \"localhost\" para sa pangalan ng tagapaghain. Kung hindi, subukan ang \"127.0.0.1\" para sa katutubong tirahan ng IP.\n\nKapag gumagamit ka ng PostgreSQL, iwanang walang laman ang hanay na ito upang kumabit sa pamamagitan ng bokilya ng Unix.",
+ "config-db-host-oracle": "TNS ng kalipunan ng dato:",
+ "config-db-host-oracle-help": "Magpasok ng isang katanggap-tanggap na [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Katutubong Pangalan ng Pagkabit]; dapat na nakikita ang isang talaksan ng tnsnames.ora sa pagluluklok na ito.<br />Kung gumagamit ka ng mga aklatan ng kliyente na 10g o mas bago, maaari mo ring gamitin ang pamamaraan ng pagpapangalan ng [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Maginhawang Pagkabit].",
+ "config-db-wiki-settings": "Kilalanin ang wiking ito",
+ "config-db-name": "Pangalan ng kalipunan ng dato:",
+ "config-db-name-help": "Pumili ng isang pangalan na pangkilala sa wiki mo.\nHindi ito dapat maglaman ng mga patlang.\n\nKung gumagamit ka ng pinagsasaluhang pagpapasinaya ng sangkasaputan, ang iyong tagapagbigay ng pagpapasinaya ay maaaring bigyan ka ng isang tiyak na pangalan ng kalipunan ng datong gagamitin o papayagan kang lumikha ng mga kalipunan ng dato sa pamamagitan ng isang entrepanyong pantaban.",
+ "config-db-name-oracle": "Balangkas ng kalipunan ng dato:",
+ "config-db-account-oracle-warn": "Mayroong tatlong suportadong senaryo para sa pag-install ng Oracle bilang database backend:\n\nKung nais mong lumikha ng account ng database bilang bahagi ng proseso ng pag-install, paki magbigay ng isang account na mayroong gampanin ng SYSDBA bilang account ng database para sa pag-install at tukuyin ang ninanais na mga kredensiyal para sa account ng web-access, o di kaya ay maaaring gawing manu-mano ang paglikha ng account ng web access at ibigay lamang ang account na iyan (kung mayroong ito ng kinakailangang mga pahintulot upang malikha ang mga schema object) o magbigay ng dalawang magkaibang mga account, isang mayroong pribilehiyo ng paglikha at isang may pagbabawal para sa web access.\n\nAng script sa paglikha ng isang account na mayroon ng kinakailangang mga pribilehiyo ay matatagpuan sa loob ng directory na \"maintenance/oracle/\" ng pag-install na ito. Pakatandaan na ang paggamit ng isang account na may pagbabawal ay hindi magpapagana sa lahat ng mga kakayahang pampananatili kasama ang nakatakdang account.",
+ "config-db-install-account": "Account ng tagagamit para sa pagluluklok",
+ "config-db-username": "Pangalang pangtagagamit ng kalipunan ng dato:",
+ "config-db-password": "Password sa kalipunan ng dato:",
+ "config-db-password-empty": "Paki magpasok ng isang password para sa bagong tagagamit ng databas: $1.\nHabang maging maaari na makalikha ng mga tagagamit na walang mga passwrod, hindi ito ligtas.",
+ "config-db-install-username": "Ipasok ang pangalan ng tagagamit na gagamitin upang kumabit sa database habang isinasagawa ang pag-install.\nHindi ito ang pangalan ng tagagamit ng account ng MediaWiki; ito ang pangalan ng tagagamit para sa iyong database.",
+ "config-db-install-password": "Ipasok ang password na gagamitin upang maka-connect sa database habang isinasagawa ang pag-install.\nHindi ito ang password para sa account ng MediaWiki; ito ang password para sa iyong database.",
+ "config-db-install-help": "Ipasok ang pangalan ng tagagamit at password na gagamitin upang umugnay sa databasehabang isinasagawa ang pag-install.",
+ "config-db-account-lock": "Gamitin ang kaparehong pangalan at password habang nasa normal na operasyon",
+ "config-db-wiki-account": "Account ng tagagamit para sa pangkaraniwang pagpapaandar",
+ "config-db-wiki-help": "Ipasok ang pangalan ng tagagamit at password na gagamitin upang kumabit sa database habang nasa karaniwang pagtakbo ng wiki.\nKung hindi umiiral ang account, at ang pag-install ng account ay mayroong sapat na mga pribilehiyo, ang account na ito ng tagagamit ay lilikhain na mayroong pinaka mababang mga pribilehiyo na kailangan upang mapatakbo ang wiki.",
+ "config-db-prefix": "Unlapi ng talahanayan ng kalipunan ng dato:",
+ "config-db-prefix-help": "Kung kailangan mong ibahagi ang isang kalipunan ng dato sa pagitan ng maramihang mga wiki, o sa pagitan ng MediaWiki at ibang aplikasyon ng kasaputan, maaaring piliin mo na magdagdag ng isang unlapi sa lahat ng mga pangalan ng talahanayan upang maiwasan ang mga salungatan.\nHuwag gumamit ng mga patlang.\n\nAng hanay na ito ay karaniwang iniiwanang walang laman.",
+ "config-db-charset": "Pangkat ng panitik ng kalipunan ng dato",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binaryo",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 paurong-kabagay UTF-8",
+ "config-charset-help": "'''Babala:''' Kapag ginamit mo ang '''backwards-compatible UTF-8''' o \"nauukol na pabalik na UTF-8\" sa MySQL 4.1+, at may kasunod na pagtatabi ng pansalong kopya ng kalipunan ng dato na mayroong <code>mysqldump</code>, maaaring wasakin nito ang lahat ng mga panitik na hindi ASCII, na hindi na mababawi pa ang mga pansalong kopya.\n\nSa '''gawi na nakahalo sa dalawa (binaryo)''', itinatabi ng MediaWiki ang tekstong UTF-8 sa kalipunan ng dato sa loob ng mga kahanayang binaryo.\nMas kapaki-pakinabang ito kaysa sa gawi na UTF-8 ng MySQL, at nagpapahintulot sa iyo na gamitin ang buong kasaklawan ng mga panitik na Unikodigo.\nSa '''gawi ng UTF-8''', malalaman ng MySQL kung anong pangkat ng panitik ang kinapapalooban ng iyong dato, at may kaangkupang maihaharap at mapapalitan ito, ngunit hindi ka nito papayagan na mag-imbak ng mga panitik sa ibabaw ng [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] o Saligang Patag na Kayas na Pangmaramihang Wika.",
+ "config-mysql-old": "Hindi kailangan ang MySQL na $1 o mas bago, mayroon kang $2.",
+ "config-db-port": "Daungan ng kalipunan ng dato:",
+ "config-db-schema": "Panukala para sa MediaWiki",
+ "config-db-schema-help": "Ang nasa itaas na panukala ay pangkaraniwang magiging maayos.\nBaguhin lamang ito kung alam mong kinakailangan.",
+ "config-pg-test-error": "Hindi makakabit sa kalipunan ng dato na '''$1''': $2",
+ "config-sqlite-dir": "Direktoryo ng dato ng SQLite:",
+ "config-sqlite-dir-help": "Iniimbak ng SQLite ang lahat ng dato sa loob ng isang nag-iisang file.\n\nAng ibibigay mong directory ay dapat na maging masusulatan ng tagapaghain ng kasaputan habang nag-i-install.\n\n'''Hindi''' ito dapat na mapuntahan sa pamamagitan ng web server, ito ang dahilan kung bakit hindi namin ito inilalagay sa kung nasaan ang iyong mga file ng PHP.\n\nAng installer ay magsusulat ng isang file na <code>.htaccess</code> na kasama ito, subalit kapag nabigo iyon mayroong isang tao na maaaring makakuha ng pagka nakakapunta sa iyong hilaw na database.\nKasama riyan ang hilaw na dato ng tagagamit (mga email address, pinaghalong mga password) pati na ang nabura nang mga pagbabago at iba pang may pagbabawal na dato ng wiki.\n\nIsaalang-alang ang paglalagay na magkakasama ang database sa ibang lugar, halimbawa na ang sa loob ng <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Likas na nakatakdang puwang ng talahanayan:",
+ "config-oracle-temp-ts": "Pansamantalang puwang ng talahanayan:",
+ "config-type-mysql": "MySQL",
+ "config-type-postgres": "PostgreSQL",
+ "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": "* Ang $1 ay ang pangunahing puntirya para sa MediaWiki at ang pinaka sinusuportahan ([http://www.php.net/manual/en/mysql.installation.php paano magtipon ng PHP na mayroong suporta ng MySQL])",
+ "config-dbsupport-postgres": "* Ang $1 ay isang bantog na sistema ng kalipunan ng dato na bukas ang pinagmulan na panghalili sa MySQL ([http://www.php.net/manual/en/pgsql.installation.php paano magtipon ng PHP na mayroong suporta ng PostgreSQL]). Maaaring mayroong ilang hindi pangunahing mga surot na natitira pa, at hindi iminumungkahi para gamitin sa loob ng isang kapaligiran ng produksiyon.",
+ "config-dbsupport-sqlite": "Ang $1 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 $1 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 MySQL",
+ "config-header-postgres": "Mga katakdaan ng PostgreSQL",
+ "config-header-sqlite": "Mga katakdaan ng SQLite",
+ "config-header-oracle": "Mga katakdaan ng Oracle",
+ "config-invalid-db-type": "Hindi tanggap na uri ng kalipunan ng dato",
+ "config-missing-db-name": "Dapat kang magpasok ng isang halaga para sa \"Pangalan ng kalipunan ng dato\"",
+ "config-missing-db-host": "Dapat kang magpasok ng isang halaga para sa \"Tagapagpasinaya ng kalipunan ng dato\"",
+ "config-missing-db-server-oracle": "Dapat kang magpasok ng isang halaga para sa \"TNS ng kalipunan ng dato\"",
+ "config-invalid-db-server-oracle": "Hindi katanggap-tanggap na pangalan ng TNSng kalipunan ng dato na \"$1\".\nGumamit lamang ng mga titik ng ASCII (a-z, A-Z), mga bilang (0-9), mga salungguhit (_) at mga tuldok (.).",
+ "config-invalid-db-name": "Hindi tanggap na pangalan ng kalipunan ng dato na \"$1\".\nGumamit lamang ng mga titik ng ASCII (a-z, A-Z), mga bilang (0-9), mga salungguhit (_) at mga gitling (-).",
+ "config-invalid-db-prefix": "Hindi tanggap na unlapi ng kalipunan ng dato na \"$1\".\nGamitin lamang ang mga titik na ASCII (a-z, A-Z), mga bilang (0-9), mga salungguhit (_) at mga gitling (-).",
+ "config-connection-error": "$1.\n\nSuriin ang host, pangalan at password na nasa ibaba at subukan ulit.",
+ "config-invalid-schema": "Hindi katanggap-tanggap na panukala para sa \"$1\" ng MediaWiki.\nGumamit lamang ng mga titik ng ASCII (a-z, A-Z), mga bilang (0-9), at mga salungguhit (_).",
+ "config-db-sys-create-oracle": "Ang installer ay sumusuporta lamang sa paggamit ng isang account ng SYSDBA para sa paglikha ng isang bagong account.",
+ "config-db-sys-user-exists-oracle": "Umiiral na ang account ng tagagamit na \"$1\". Magagamit lamang ang SYSDBA para sa paglikha ng isang bagong account!",
+ "config-postgres-old": "Kailangan ang PostgreSQL $1 o mas bago, mayroon kang $2.",
+ "config-sqlite-name-help": "Pumili ng isang pangalan na pangkilala na wiki mo.\nHuwag gumamit ng mga puwang o mga gitling.\nGagamitin ito para sa pangalan ng talaksan ng dato ng SQLite.",
+ "config-sqlite-parent-unwritable-group": "Hindi malikha ang direktoryo ng dato na <code><nowiki>$1</nowiki></code>, sapagkat ang magulang na direktoryong <code><nowiki>$2</nowiki></code> ay hindi masulatan ng tagapaghain ng kasaputan.\n\nNapag-alaman ng tagapagluklok kung sinong tagagamit ang kinatatakbuhan ng iyong tagapaghain ng kasaputan.\nGawing nasusulatan nito ang <code><nowiki>$3</nowiki></code> ng direktoryo upang makapagpatuloy.\nIto ang gawin sa ibabaw ng isang sistema ng Unix/Linux:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Hindi malikha ang direktoryo ng dato na <code><nowiki>$1</nowiki></code>, sapagkat ang magulang na direktoryong <code><nowiki>$2</nowiki></code> ay hindi masulatan ng tagapaghain ng kasaputan.\n\nHindi malaman ng tagapagluklok kung sinong tagagamit ang kinatatakbuhan ng iyong tagapaghain ng kasaputan.\nGawing nasusulatan nito (at ng mga iba pa) ang <code><nowiki>$3</nowiki></code> ng direktoryo upang makapagpatuloy.\nIto ang gawin sa ibabaw ng isang sistema ng Unix/Linux:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Kamalian sa paglikha ng direktoryo ng datong \"$1\".\nSuriin ang kinalalagyan at subukang muli.",
+ "config-sqlite-dir-unwritable": "Hindi nagawang magsulat sa direktoryong \"$1\".\nBaguhin ang mga kapahintulutan nito upang makapagsulat dito ang tagapaghain ng sapot, at subukang muli.",
+ "config-sqlite-connection-error": "$1.\n\nSurrin ang direktoryo ng dato at pangalan ng kalipunan ng datong nasa ibaba at subukan uli.",
+ "config-sqlite-readonly": "Ang talaksang <code>$1</code> ay hindi maisusulat.",
+ "config-sqlite-cant-create-db": "Hindi malikha ang talaksang <code>$1</code> ng kalipunan ng dato.",
+ "config-sqlite-fts3-downgrade": "Nawawala ang suportang FTS3 ng PHP, ibinababa ang uri ng mga talahanayan",
+ "config-can-upgrade": "Mayroong mga talahanayan ng MediaWiki sa loob ng kalipunan ng datong ito.\nUpang maitaas ang uri ng mga ito upang maging MediaWiki na $1, pindutin ang '''Magpatuloy'''.",
+ "config-upgrade-done": "Buo na ang pagtataas ng uri.\n\nMaaari mo na ngayong [$1 gamitin ang iyong wiki].\n\nKung nais mong muling likhain ang iyong talaksang <code>LocalSettings.php</code>, lagitikin ang pindutang nasa ibaba.\n'''Hindi minumungkahi''' ito maliban na lamang kung nagkakaroon ka ng mga suliranin sa piling ng wiki mo.",
+ "config-upgrade-done-no-regenerate": "Buo na ang pagsasapanahon.\n\nMaaari ka na ngayong [$1 magsimula sa paggamit ng wiki mo].",
+ "config-regenerate": "Muling likhain ang LocalSettings.php →",
+ "config-show-table-status": "Nabigo ang pagtatanong na IPAKITA ANG KALAGAYAN NG TALAHANAYAN!",
+ "config-unknown-collation": "'''Babala:''' Ang kalipunan ng dato ay gumagagamit ng hindi nakikilalang pag-iipon.",
+ "config-db-web-account": "Account ng kalipunan ng dato para sa pagpunta sa web",
+ "config-db-web-help": "Piliin ang pangalan ng tagagamit at password na gagamitin ng tagapaghain ng web upang umugnay sa tagapaghain ng database, habang nasa pangkaraniwang pagtakbo ng wiki.",
+ "config-db-web-account-same": "Gamitin ang gayun din account katulad ng sa pag-install",
+ "config-db-web-create": "Likhain ang account kung hindi pa ito umiiral",
+ "config-db-web-no-create-privs": "Ang tinukoy mong account na iluluklok ay walang sapat na mga pribilehiyo upang makalikha ng isang account.\nAng account na tutukuyin mo rito ay umiiral na dapat.",
+ "config-mysql-engine": "Makinang imbakan:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Babala''': Pinili mo ang MyISAM bilang makinang imbakan para sa MySQL, na hindi iminumungkahi para gamitin sa MediaWiki, sapagkat:\n* bahagya lamang itong sumusuporta ng pagkakasundu-sundo dahil sa pagkakandado ng talahanayan\n* mas malaki ang pagkakataon na kapitan ng sira kaysa sa ibang mga makina\n* ang himpilang kodigo ng MediaWiki ay hindi palaging humahawak ng MyISAM ayon sa nararapat\n\nKung ang iyong nakaluklok na MySQL ay sumusuporta ng InnoDB, higit na iminumungkahi na piliin mo iyon sa halip.\nKung ang iyong nakaluklok na MySQL ay hindi sumusuporta ng InnoDB, marahil ay panahon na para sa isang pagtataas ng uri.",
+ "config-mysql-engine-help": "Ang '''InnoDB''' ay ang halos palaging pinaka mainam na mapipili, dahil mayroon itong mabuting suporta ng pagkakasundu-sundo.\n\nMaaaring mas mabilis ang '''MyISAM''' sa mga pagluluklok na pang-isahang tagagamit o mababasa lamang.\nMay gawi ang mga kalipunan ng dato ng MyISAM na masira nang mas madalas kaysa sa mga kalipunan ng dato ng InnoDB.",
+ "config-mysql-charset": "Pangkat ng panitik ng kalipunan ng dato:",
+ "config-mysql-binary": "Binaryo",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "Sa '''gawi na binaryo''', iniimbak ng MediaWiki ang tekstong UTF-8 sa kalipunan ng dato sa loob ng mga hanay na binaryo.\nMas kapaki-pakinabang ito kaysa sa gawi na UTF-8 ng MySQL, at nagpapahintulot sa iyo upang magamit ang buong kasaklawan ng mga panitik ng Unikodigo.\n\nSa ''gawi na UTF-8''', malalaman ng MySQL kung sa anong pangkat ng panitik nakapaloob ang iyong dato, at angkop na makakapagharap at makapapagpalit nito, subalit hindi ka nito papayagan na mag-imbak ng mga panitik na nasa itaas ng [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] o Saligang Tapyas na Pangmaramihang Wika.",
+ "config-site-name": "Pangalan ng wiki:",
+ "config-site-name-help": "Lilitaw ito sa bareta ng pamagat ng pantingin-tingin at sa samu't saring ibang mga lugar.",
+ "config-site-name-blank": "Magpasok ng isang pangalan ng sityo.",
+ "config-project-namespace": "Puwang na pampangalan ng proyekto:",
+ "config-ns-generic": "Proyekto",
+ "config-ns-site-name": "Katulad ng sa pangalan ng wiki: $1",
+ "config-ns-other": "Iba pa (tukuyin)",
+ "config-ns-other-default": "Wiki Ko",
+ "config-project-namespace-help": "Bilang pagsunod sa halimbawa ng Wikipedia, maraming mga wiki ang nagpapanatili ng kanilang mga pahina ng patakaran na nakahiwalay magmula sa kanilang mga pahina ng nilalaman, na nasa loob ng isang \"'''puwang na pampangalan ng proyekto'''\".\nAng lahat ng mga pamagat ng pahina na nasa loob ng puwang ng pangalang ito ay nagsisimula na mayroong isang partikular na unlapi, na maaari mong tukuyin dito.\nSa nakaugalian, ang unlaping ito ay hinango mula sa pangalan ng wiki, subalit hindi ito maaaring maglaman ng mga panitik ng palabantasan na katulad ng \"#\" o \":\".",
+ "config-ns-invalid": "Ang tinukoy na puwang ng pangalan na \"<nowiki>$1</nowiki>\" ay hindi katanggap-tanggap.\nTumukoy ng isang ibang puwang ng pangalan ng proyekto.",
+ "config-ns-conflict": "Ang tinukoy na puwang ng pangalan na \"<nowiki>$1</nowiki>\" ay sumasalungat sa isang likas na nakatakdang puwang ng pangalan ng MediaWiki.\nTumukoy ng isang ibang puwang ng pangalan ng proyekto.",
+ "config-admin-box": "Account ng tagapangasiwa",
+ "config-admin-name": "Pangalan mo:",
+ "config-admin-password": "Password:",
+ "config-admin-password-confirm": "Password uli:",
+ "config-admin-help": "Ipasok dito ang mas ninanais mong pangalan ng tagagamit, bilang halimbawa na ang \"Joe Bloggs\".\nIto ang pangalang gagamitin mo upang lumagdang papasok sa wiki.",
+ "config-admin-name-blank": "Magpasok ng isang pangalan ng tagagamit na tagapangasiwa.",
+ "config-admin-name-invalid": "Ang tinukoy na pangalan ng tagagamit na \"<nowiki>$1</nowiki>\" ay hindi tanggap.\nTumukoy ng ibang pangalan ng tagagamit.",
+ "config-admin-password-blank": "Magpasok ng isang password para sa account ng tagapangasiwa.",
+ "config-admin-password-mismatch": "Hindi magkatugma ang ipinasok mong dalawang mga password.",
+ "config-admin-email": "Tirahan ng e-liham:",
+ "config-admin-email-help": "Magpasok dito ng isang email address upang mapahintulutan kang makatanggap ng email mula sa iba pang mga tagagamit ng wiki, itakdang muli ang password mo, at mabatid ang mga pagbabago sa mga pahinang nasa ibabaw ng iyong tala ng mga binabantayan. Maiiwanan mo na walang laman ang field na ito.",
+ "config-admin-error-user": "Panloob na kamalian kapag nililikha ang isang tagapangasiwa na may pangalang \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Panloob na kamalian kapag nagtatakda ng isang password na para sa tagapangasiwang \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Nagpasok ka ng isang hindi katanggap-tanggap na tirahan ng e-liham.",
+ "config-subscribe": "Tumanggap mula sa [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce talaan ng mga pinadadalhan ng mga nilalabas na mga pabatid].",
+ "config-subscribe-help": "Isang itong tala ng pagliliham na mababa ang dami na ginagamit para sa pagpapakawala ng mga pahayag, kabilang na ang mahahalagang mga pahayag na pangkatiwasayan. Dapat kang magpasipi nito at isapanahon ang iyong nakaluklok na MediaWiki kapag lumalabas ang bagong mga bersiyon.",
+ "config-subscribe-noemail": "Sinubukan mong magpasipi sa tala ng nililihaman ng pagpapakawala ng mga pahayag na hindi nagbibigay ng isang tirahan ng -eliham. Paki magbigay ng isang tirahan ng e-liham kung nais mong magpasipi sa listahan ng pagliliham.",
+ "config-almost-done": "Halos tapos ka na!\nMaaari mo ngayong laktawan ang natitira pang pag-aayos at iluklok na ang wiki ngayon.",
+ "config-optional-continue": "Magtanong sa akin ng marami pang mga tanong.",
+ "config-optional-skip": "Naiinip na ako, basta iluklok na lang ang wiki.",
+ "config-profile": "Balangkas ng mga karapatan ng tagagamit:",
+ "config-profile-wiki": "Tradisyonal na wiki",
+ "config-profile-no-anon": "Kailangan ang paglikha ng account",
+ "config-profile-fishbowl": "Pinahintulutang mga patnugot lamang",
+ "config-profile-private": "Pribadong wiki",
+ "config-profile-help": "Pinaka mahusay ang pagtakbo ng mga Wiki kapag pinapahintulutan mo ang pinaka maraming mga tao na makapamatnugot ng mga ito hanggang sa maaari.\nSa loob ng MediaWiki, maginhawang masusuring muli ang kamakailang mga pagbabago, at mapanauli sa dati ang anumang nasira na nagawa ng isang walang muwang o may masamang hangarin na mga tagagamit.\n\nSubalit, marami ang nakatagpo na nagagamit ang MediaWiki sa loob ng malawak na sari-saring mga gampanin, at kung minsan ay hindi madaling makumbinsi ang lahat ng mga tao hinggil sa kapakinabangan ng kaparaanan ng wiki.\nKung kaya't nasa iyo ang pagpili.\n\nAng isang '''{{int:config-profile-wiki}}''' ay nagpapahintulot sa sinuman upang makapagbago, na hindi kailangan ang paglagdang papasok.\nAng isang wiki na mayroong '''{{int:config-profile-no-anon}}''' ay nagbibigay ng karagdagang pananagutan, subalit maaaring pumigil sa nagkataon lamang na mga tagapag-ambag.\n\nAng tagpo na '''{{int:config-profile-fishbowl}}''' ay nagpapahintulot lamang sa pinayagang mga tagagamit na makatingin ng mga pahina, na kapiling ang pangkat na pinayagang makapamatnugot.\nAng isang '''{{int:config-profile-private}}''' ay nagpapahintulot lamang sa pinayagang mga tagagamit na makatingin ng mga pahina, na kapiling ang pangkat na pinayagang makapamatnugot.\n\nAng mas masasalimuot na mga kaayusan ng mga karapatan ng tagagamit ay makukuha pagkaraan ng pagluluklok, tingnan ang [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights may kaugnayang kinamay na lahok].",
+ "config-license": "Karapatang-ari at lisensiya:",
+ "config-license-none": "Walang talababa ng lisensiya",
+ "config-license-cc-by-sa": "Malikhaing Pangkaraniwang Pagtukoy Pamamahaging Magkatulad",
+ "config-license-cc-by": "Atribusyon ng Creative Commons",
+ "config-license-cc-by-nc-sa": "Malikhaing Pangkaraniwang Pagtukoy Hindi-Pangkalakal Pamamahaging Magkatulad",
+ "config-license-cc-0": "Sero na Creative Commons (Nasasakop ng Madla)",
+ "config-license-gfdl": "Lisensiyang 1.3 ng Malayang Dokumentasyon ng GNU o mas lalong huli",
+ "config-license-pd": "Nasasakupan ng Madla",
+ "config-license-cc-choose": "Pumili ng isang pasadyang Lisensiya ng Malikhaing mga Pangkaraniwan",
+ "config-license-help": "Maraming mga pangmadlang wiki ang naglalagay ng lahat ng mga ambag sa ilalim ng [http://freedomdefined.org/Definition lisensiyang malaya].\nNakakatulong ito sa paglikha ng isang diwa ng pagmamay-ari ng pamayanan at nakapanghihikayat ng ambag na pangmahabang panahon.\nSa pangkalahatan, hindi kailangan ang isang wiking pribado o pangsamahan.\n\nKung nais mong magamit ang teksto magmula sa Wikipedia, at nais mong makatanggap ang Wikipedia ng tekstong kinopya magmula sa wiki mo, dapat mong piliin ang '''Creative Commons Attribution Share Alike''' (Pagbanggit na Pinagsasaluhang Magkatulad ng Malikhaing Pangkaraniwan).\n\nDating ginamit ng Wikipedia ang Lisensiya ng Kasulatang Malaya ng GNU (GNU Free Documentation License o GFDL).\nIsang katanggap-tanggap na lisensiya ang GFDL, subalit mahirap itong maunawaan.\nMahirap din ang paggamit na muli ng nilalaman na nasa ilalim ng GFDL.",
+ "config-email-settings": "Mga katakdaan ng e-liham",
+ "config-enable-email": "Paganahin ang palabas na e-liham",
+ "config-enable-email-help": "Kung nais mong gumana ang e-liham, ang mga katakdaan ng liham ng [http://www.php.net/manual/en/mail.configuration.php PHP] ay kailangang maging wasto ang pagkakaayos.\nKung ayaw mo nang anumang mga katampukan ng e-liham, maaari mong huwag paganahin ang mga ito rito.",
+ "config-email-user": "Paganahin ang tagagamit-sa-tagagamit na e-liham",
+ "config-email-user-help": "Payagan ang lahat ng mga tagagamit na magpadala ng e-liham sa bawat isa kapag pinagana nila ito sa kanilang mga nais.",
+ "config-email-usertalk": "Paganahin ang pabatid na pampahina ng usapan ng tagagamit",
+ "config-email-usertalk-help": "Payagan ang mga tagagamit na tumanggap ng mga pabatid sa mga pagbabago ng pahina ng usapan ng tagagamit, kapag pinagana nila ito sa kanilang mga nais.",
+ "config-email-watchlist": "Paganahin ang pabatid ng talaan ng bantayan",
+ "config-email-watchlist-help": "Payagan ang mga tagagamit na tumanggap ng mga pabatid tungkol sa kanilang binabantayang mga pahina kapag pinagana nila ito sa kanilang mga nais.",
+ "config-email-auth": "Paganahin ang pagpapatunay ng e-liham",
+ "config-email-auth-help": "Kapag pinagagana ang mapipiling ito, dapat tiyakin ng mga tagagamit ang kanilang tirahan ng e-liham na ginagamit ang isang kawing na ipinadala sa kanila tuwing itinatakda o binabago nila ito.\nTanging napatunayang mga tirahan ng e-liham lamang ang makakatanggap ng mga e-liham magmula sa ibang mga tagagamit o makakapagbago ng mga e-liham ng pagpapabatid.\n'''Iminumungkahi''' ang mapipiling katakdaan na ito para sa mga wiking pangmadla dahil sa maaaring mangyaring pagmamalabis ng mga katampukan ng e-liham.",
+ "config-email-sender": "Pabalik na tirahan ng e-liham:",
+ "config-email-sender-help": "Ipasok ang tirahan ng e-liham na gagamitin bilang tirahang pagsasaulian ng e-liham na papalabas.\nDito ang kung saan ipapadala ang mga pagtalbog.\nMaraming mga tagapaghain ng liham ang nangangailangan ng kahit na bahagi lamang ng pangalan ng nasasakupan upang maging katanggap-tanggap.",
+ "config-upload-settings": "Mga pagkakarga ng mga larawan at talaksan",
+ "config-upload-enable": "Paganahin ang pagkakarga ng talaksan",
+ "config-upload-help": "Ang paitaas na mga pagkakarga ng mga talaksan ay maaaring makapaglantad ng iyong tagapaghain sa mga panganib na pangkatiwasayan.\nPara sa mas marami pang kabatiran, basahin ang [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security seksiyon ng katiwasayan] sa loob ng gabay.\n\nUpang mapagana ang paitaas na mga pagkakarga ng talaksan, baguhin ang gawi roon sa subdirektoryo ng <code>mga imahe</code> sa ilalim ng ugat na direktoryo ng MediaWiki upang ang tagapaghain ng kasaputan ay makapagsulat dito.\nPagkaraan ay paganahin ang pipiliing ito.",
+ "config-upload-deleted": "Direktoryo para sa binurang mga talaksan:",
+ "config-upload-deleted-help": "Pumili ng isang direktoryong pagsusupnayan ng naburang mga talaksan.\nIdeyal na dapat itong hindi mapupuntahan mula sa web.",
+ "config-logo": "URL ng logo:",
+ "config-logo-help": "Ang likas na nakatakdang pabalat ng MediaWiki ay nagsasama ng puwang para sa isang logong 135x160 ang piksel na nasa itaas ng menu ng panggilid na bareta.\nMagkargang papaitaas ng isang imahe na mayroong naaangkop na sukat, at ipasok dito ang URL.\n\nKung ayaw mo ng isang logo, iwanang walang laman ang kahong ito.",
+ "config-instantcommons": "Paganahin ang Mga Pangkaraniwang Biglaan",
+ "config-instantcommons-help": "Ang [//www.mediawiki.org/wiki/InstantCommons Instant Commons] ay isang tampok na nagpapahintulot sa mga wiki upang gumamit ng mga imahe, mga tunog at iba pang mga midyang matatagpuan sa pook ng [//commons.wikimedia.org/ Wikimedia Commons].\nUpang magawa ito, nangangailangan ang MediaWiki ng pagka nakakapunta sa Internet.\n\nPara sa mas marami pang kabatiran hinggil sa tampok na ito, kabilang na ang mga tagubilin sa kung paano ito itakda para sa mga wiki na bukod pa kaysa sa Wikimedia Commons, sumangguni sa [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos gabay].",
+ "config-cc-error": "Hindi nagbigay ng resulta ang pampili ng lisensiya ng Malikhaing Pangkaraniwan.\nIpasok na kinakamay ang pangalan ng lisensiya.",
+ "config-cc-again": "Pumili uli...",
+ "config-cc-not-chosen": "Piliin kung anong lisensiya ng Malikhaing mga Pangkaraniwan ang nais mo at pindutin ang \"magpatuloy\".",
+ "config-advanced-settings": "Mas masulong na pagkakaayos",
+ "config-cache-options": "Mga katakdaan para sa pagtatago ng bagay:",
+ "config-cache-help": "Ang pagtatago ng bagay ay ginagamit upang mapainam ang tulin ng MediaWiki sa pamamagitan ng pagtatago ng madalas gamiting dato.\nAng mga pook na bahagya hanggang malalaki ang sukat ay labis na hinihikayat na paganahin ito, at ang mga pook na maliliit ay makakakita rin ng mga kapakinabangan.",
+ "config-cache-none": "Walang pagtatago (tinanggal ang katungkulan, subalit maaaring maapektuhan ang tulin sa mas malalaking mga pook ng wiki)",
+ "config-cache-accel": "Pagtatago ng bagay ng PHP (APC, XCache o WinCache)",
+ "config-cache-memcached": "Gamitin ang Pagtatago sa Alaala (Memcached) (nangangailangan ng karagdagang kaayusan ng pagkakahanda at pagsasaayos)",
+ "config-memcached-servers": "Mga tagapaghaing itinago sa alaala:",
+ "config-memcached-help": "Listahan ng mga tirahan ng IP na gagamitin para sa Memcached o Itinagong Alaala.\nDapat na tukuyin na isa sa bawat guhit at tukuyin ang daungang gagamitin. Bilang halimbawa:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Pinili mo ang Memcached bilang uri mo ng taguan ngunit hindi tumukoy ng anumang mga tagapaghain.",
+ "config-memcache-badip": "Nagpasok ka ng isang hindi tanggap na tirahan ng IP para sa Memcached: $1.",
+ "config-memcache-noport": "Hindi ka tumukoy ng isang daungan na gagamitin para sa tagapaghain ng Memcached: $1.\nKung hindi mo alam ang daungan, ang likas na nakatakda ay 11211.",
+ "config-memcache-badport": "Ang bilang ng daungan ng Memcached ay dapat na nasa pagitan ng $1 at $2.",
+ "config-extensions": "Mga dugtong",
+ "config-extensions-help": "Ang mga dugtong na nakalista sa ibabaw ay napansin sa loob ng iyong direktoryo ng <code>./extensions</code>.\n\nMaaaring mangailangan ang mga ito ng karagdagang kaayusan, subalit mapapagana mo ngayon ang mga ito",
+ "config-install-alreadydone": "'''Babala:''' Tila nailuklok mo na ang MediaWiki at tinatangka mong iluklok ito ulit.\nPaki magpatuloy sa susunod na pahina.",
+ "config-install-begin": "Sa pamamagitan ng pagpindot sa \"{{int:config-continue}}\", sisimulan mo ang pagluluklok ng MediaWiki.\nKung nais mo paring gumawa ng mga pagbabago, paki pindutin ang bumalik.",
+ "config-install-step-done": "nagawa na",
+ "config-install-step-failed": "nabigo",
+ "config-install-extensions": "Isinasama ang mga karugtong",
+ "config-install-database": "Inihahanda ang kalipunan ng dato",
+ "config-install-schema": "Nililikha ang panukala",
+ "config-install-pg-schema-not-exist": "Hindi umiiral ang panukala ng PostgreSQL.",
+ "config-install-pg-schema-failed": "Nabigo ang paglikha ng mga talahanayan.\nTiyakin na ang tagagamit na \"$1\" ay maaaring makasulat sa balangkas na \"$2\".",
+ "config-install-pg-commit": "Isinasagawa ang mga pagbabago",
+ "config-install-pg-plpgsql": "Sumusuri ng wikang PL/pgSQL",
+ "config-pg-no-plpgsql": "Kailangan mong magtalaga ng wikang PL/pgSQL sa loob ng kalipunan ng datong $1",
+ "config-pg-no-create-privs": "Ang tinukoy mong accountpara sa pagtatalaga ay walang sapat na mga pribilehiyo upang makalikha ng isang account.",
+ "config-pg-not-in-role": "Umiiral na ang account na tinukoy mo para sa tagagamit ng web.\nAng tinukoy mong account para sa pag-install ay hindi isang tagagamit na super at hindi isang kasapi sa gampanin ng tagagamit ng web, kung kaya't hindi nito nagawang makalikha ng mga bagay na pag-aari ng tagagamit ng web.\n\nSa kasalukuyan, nangangailangan ang MediaWiki na ang mga table ay maging pag-aari ng tagagamit ng web. Pakitukoy ng isa pang pangalan ng account na web, o pindutin ang \"bumalik\" at tumukoy ng isang tagagamit na may kaangkupang pribilehiyo ng pag-install.",
+ "config-install-user": "Nililikha ang tagagamit ng kalipunan ng dato",
+ "config-install-user-alreadyexists": "Umiiral na ang tagagamit na \"$1\"",
+ "config-install-user-create-failed": "Nabigo ang paglikha ng tagagamit na \"$1\": $2",
+ "config-install-user-grant-failed": "Nabigo ang pagbibigay ng pahintulot sa tagagamit na \"$1\": $2",
+ "config-install-user-missing": "Hindi umiiral ang tinukoy na tagagamit na si \"$1\".",
+ "config-install-user-missing-create": "Hindi umiiral ang tinukoy na tagagamit na si \"$1\".\nPaki-klik ang nasa ibabang kahong natsetsekan na \"likhain ang account\" kung nais mong likhain ito.",
+ "config-install-tables": "Nililikha ang mga talahanayan",
+ "config-install-tables-exist": "'''Babala''': Tila umiiral na ang mga talahanayan ng MediaWiki.\nNilalaktawan ang paglikha.",
+ "config-install-tables-failed": "'''Kamalian''': Nabigo ang paglikha ng talahanayan na may sumusunod na kamalian: $1",
+ "config-install-interwiki": "Nilalagyan ng laman ang likas na nakatakdang talahanayan ng interwiki",
+ "config-install-interwiki-list": "Hindi matagpuan ang talaksang <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Babala''': Tila may mga laman na ang talahanayan ng interwiki.\nNilalaktawan ang likas na nakatakdang talaan.",
+ "config-install-stats": "Sinisimulan ang estadistika",
+ "config-install-keys": "Ginagawa ang lihim na mga susi",
+ "config-insecure-keys": "'''Babala:''' Nalikha ang {{PLURAL:$2|A secure key|ligtas na mga susi}} ($1) habang ang pagluluklok {{PLURAL:$2|ay|ay}} hindi pa lubos na ligtas. Isaalang-alang ang kinakamay na pagbago {{PLURAL:$2|nito|ng mga ito}}.",
+ "config-install-sysop": "Nililikha ang account ng tagagamit na tagapangasiwa",
+ "config-install-subscribe-fail": "Hindi nagawang magpasipi mula sa mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "Hindi nakalagak ang cURL at hindi makukuha ang <code>allow_url_fopen</code>",
+ "config-install-mainpage": "Nililikha ang pangunahing pahina na may likas na nakatakdang nilalaman",
+ "config-install-extension-tables": "Nililikha ang mga talahanayan para sa pinagaganang mga dugtong",
+ "config-install-mainpage-failed": "Hindi maisingit ang pangunahing pahina: $1",
+ "config-install-done": "'''Maligayang bati!'''\nMatagumpay mong nailuklok ang MediaWiki.\n\nAng tagapagluklok ay nakagawa ng isang talaksan ng <code>LocalSettings.php</code>.\nNaglalaman ito ng lahat ng iyong mga pagsasaayos.\n\nKailangan mo itong ikargang paibaba at ilagay ito sa lipon ng iyong pagluluklok ng wiki (katulad ng direktoryo ng index.php). Ang pagkakargang paibaba ay dapat na kusang magsimula.\n\nKung ang pagkakargang paibaba ay hindi inialok, o kung hindi mo ito itinuloy, maaari mong muling simulan ang pagkakargang paibaba sa pamamagitan ng pagpindot sa kawing na nasa ibaba:\n\n$3\n\n'''Paunawa''': Kapag hindi mo ito ginawa ngayon, ang nagawang talaksang ito ng pagkakaayos ay hindi mo na makukuha mamaya kapag lumabas ka mula sa pagluluklok na hindi ikinakarga itong paibaba.\n\nKapag nagawa na iyan, maaari ka nang '''[$2 pumasok sa wiki mo]'''.",
+ "config-download-localsettings": "Ikargang paibaba ang <code>LocalSettings.php</code>",
+ "config-help": "saklolo",
+ "config-nofile": "Hindi matagpuan ang talaksang \"$1\". Binura na ba ito?",
+ "mainpagetext": "'''Matagumpay na ininstala ang MediaWiki.'''",
+ "mainpagedocfooter": "Silipin ang [//meta.wikimedia.org/wiki/Help:Contents Patnubay sa Tagagamit] (''\"User's Guide\"'') para sa kaalaman sa paggamit ng wiking ''software''.\n\n== Pagsisimula ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Tala ng mga nakatakdang kumpigurasyon]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Mga malimit itanong sa MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Tala ng mga pinadadalhan ng liham ng MediaWiki]"
+}
diff --git a/includes/installer/i18n/tly.json b/includes/installer/i18n/tly.json
new file mode 100644
index 00000000..b0d03f51
--- /dev/null
+++ b/includes/installer/i18n/tly.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Erdemaslancan"
+ ]
+ },
+ "config-page-options": "Кукон"
+}
diff --git a/includes/installer/i18n/tr.json b/includes/installer/i18n/tr.json
new file mode 100644
index 00000000..83c51ee6
--- /dev/null
+++ b/includes/installer/i18n/tr.json
@@ -0,0 +1,201 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cagrix",
+ "Joseph",
+ "Rhinestorm",
+ "SiLveRLeaD",
+ "Trncmvsr",
+ "Sayginer"
+ ]
+ },
+ "config-desc": "MediaWiki yükleyicisi",
+ "config-title": "MediaWiki $1 yüklemesi",
+ "config-information": "Bilgi",
+ "config-localsettings-upgrade": "Bir <code>LocalSettings.php</code> dosyası algılandı.\nBu kurulumu güncelleştirmek için, lütfen <code>$wgUpgradeKey</code> değerini aşağıdaki kutuya girin.\nBunu <code>LocalSettings.php</code> dosyasında bulabilirsiniz.",
+ "config-localsettings-cli-upgrade": "Bir <code>LocalSettings.php</code> dosyası algılandı.\nBu kurulumu güncelleştirmek için, lütfen <code>update.php</code> dosyasını çalıştırın.",
+ "config-localsettings-key": "Yükseltme anahtarı:",
+ "config-localsettings-badkey": "Sağladığınız anahtar doğru değil.",
+ "config-upgrade-key-missing": "Mevcut bir MediaWiki kurulumu algılandı.\nBu kurulumu güncelleştirmek için, lütfen aşağıdaki satırı <code>LocalSettings.php</code> dosyanızın en altına koyun:\n\n$1",
+ "config-localsettings-incomplete": "Mevcut <code>LocalSettings.php</code> eksik gibi görünüyor.\n $1 değişkeni ayarlanmamış.\nLütfen <code>LocalSettings.php</code> dosyasını değiştirin bu değişkenleri kuracak, ve tıklayın \"{{int:Config-cuntinue}}\".",
+ "config-localsettings-connection-error": "<code>LocalSettings.php</code> içinde belirtilen ayarları kullanarak veritabanına bağlanırken bir hatayla karşılaşıldı. Lütfen bu ayarları düzeltin ve yeniden deneyin.\n\n$1",
+ "config-session-error": "Oturum başlatılırken hata: $1",
+ "config-session-expired": "Oturum bilgilerinizin süresi bitmiş.\nOturumların süresi $1 kadardır.\nBu süreyi php.ini' deki <code>session.gc_maxlifetime</code> ayarla arttırabilirsiniz.\nKurulum işlemini yeniden başlatın.",
+ "config-no-session": "Oturum bilgileriniz silinmiş.\nphp.ini dosyanızı kontrol edin ve <code>session.save_path</code> ayarının uygun bir klasöre yönlendiğinden emin olun.",
+ "config-your-language": "Diliniz:",
+ "config-your-language-help": "Yükleme sürecinde kullanılacak bir dil seçin.",
+ "config-wiki-language": "Viki dili:",
+ "config-wiki-language-help": "Vikinin ağırlıklı olarak yazılacağı dili seçin.",
+ "config-back": "← Geri",
+ "config-continue": "Devam →",
+ "config-page-language": "Dil",
+ "config-page-welcome": "MediaWiki'ye hoş geldiniz!",
+ "config-page-dbconnect": "Veritabanına bağlan",
+ "config-page-upgrade": "Varolan yüklemeyi yükselt",
+ "config-page-dbsettings": "Veritabanı ayarları",
+ "config-page-name": "İsim",
+ "config-page-options": "Seçenekler",
+ "config-page-install": "Yükle",
+ "config-page-complete": "Tamamlandı!",
+ "config-page-restart": "Yüklemeyi yeniden başlat",
+ "config-page-readme": "Beni oku",
+ "config-page-releasenotes": "Sürüm notları",
+ "config-page-copying": "Kopyalama",
+ "config-page-upgradedoc": "Yükseltme",
+ "config-page-existingwiki": "Mevcut viki",
+ "config-help-restart": "Girişini yaptığınız tüm kayıtlı verileri silerek, yükleme işlemini yeniden başlatmak ister misiniz?",
+ "config-restart": "Evet, yeniden başlat",
+ "config-welcome": "===Ortam Kontrolleri===\nOrtamın Mediawiki kurulumuna uygun olup olmadığını anlamak için basit kontroller yapılacak.\nKurulumu nasıl tamamlayacağınız konusunda destek isterken bu bilgileri eklemeyi unutmayın.",
+ "config-copyright": "=== Telif Hakları ve Koşulları ===\n\n$1\n\nBu program ücretsiz bir yazılımdır; yeniden dağıtabilir veya Özgür Yazılım Kuruluşu tarafından yayınlanan (GNU) Genel Kamu Lisansı koşulları altında değiştirebilirsiniz; isterseniz ikinci lisans sürümünü veya (sizin seçeneğiniz) herhangi bir sonraki lisans sürümünü kullanabilirsiniz.\n\nBu program, faydalı olacağı umuduyla dağıtılmaktadır, ancak ''' herhangi bir garantisi yoktur '''; ''' uygunluk ''' veya ''' belirli bir amaca uygunluk ''' gibi dolaylı garantileri bile yoktur.\nDaha fazla ayrıntı için (GNU) Genel Kamu Lisansına bakınız.\n\nBu program ile birlikte <doclink href=\"Copying\">bir (GNU) Genel Kamu Lisansının bir kopyasını </doclink> almış olmanız gerekir; bu program (GNU) Genel Kamu Lisansı ile dağıtılmadıysa, Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, ABD adresine yazın veya [http://www.gnu.org/copyleft/gpl.html online olarak okuyun].",
+ "config-sidebar": "* [//www.mediawiki.org MediaWiki ana sayfa]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Kullanıcı Rehberi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Yetkili Rehberi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ SSS]\n----\n* <doclink href=Readme>Beni oku</doclink>\n* <doclink href=ReleaseNotes>Sürüm notları</doclink>\n* <doclink href=Copying>Kopyalama</doclink>\n* <doclink href=UpgradeDoc>Yükseltme</doclink>",
+ "config-env-good": "Ortam kontrol edildi.\nMediaWiki'yi kurabilirsiniz.",
+ "config-env-bad": "Ortam kontrol edildi.\nMediaWiki'yi kuramazsınız.",
+ "config-env-php": "PHP $1 kurulu.",
+ "config-unicode-using-utf8": "Unikod normalleştirmesi için Brion Vibber'in utf8_normalize.so kullanılıyor.",
+ "config-unicode-using-intl": "Unikod normalleştirmesi için [http://pecl.php.net/intl intl PECL uzantısı] kullanılıyor.",
+ "config-xml-bad": "PHP 'nin XML modülü eksik.\nMediaWiki bu modüldeki fonksiyonlara ihtiyaç duyar ve şimdiki kurulumda çalışmayacaktır.\nMandrake kullanıyorsanız php-xml paketini yükleyin.",
+ "config-pcre-old": "<strong>Ağır hata:</strong> PCRE $1 veya daha üst versiyon gerekli.\nSizin PHP kurulumunuz PCRE $2 ile bağlı.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Daha fazla bilgi].",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] kurulu",
+ "config-apc": "[http://www.php.net/apc APC] kurulu",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] kurulu",
+ "config-no-cache": "'''Uyarı:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ya da [http://www.iis.net/download/WinCacheForPhp WinCache] bulunamadı.\nNesne önbelleğe alma etkinleştirilmedi.",
+ "config-mod-security": "'''Uyarı:''' Web sunucunuz [http://modsecurity.org/ mod_security] etkin. 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.\nRastgele hatalar alırsanız [http://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>.",
+ "config-git-bad": "Sürüm kontrol yazılımı Git bulunamadı.",
+ "config-imagemagick": "ImageMagick bulundu: <code>$1</code>.\nEğer yüklemeleri etkinleştirirseniz, küçük resimler etkinleştirilecektir.",
+ "config-db-host": "Veritabanı sunucusu:",
+ "config-db-host-help": "Veritabanı sunucunuz farklı bir sunucu üzerinde ise, ana bilgisayar adını veya IP adresini buraya girin.\n\nPaylaşılan ağ barındırma hizmeti kullanıyorsanız, barındırma sağlayıcınız size doğru bir ana bilgisayar adını kendi belgelerinde vermiştir.\n\nEğer MySQL kullanan bir Windows sunucusuna yükleme yapıyorsanız, sunucu adı olarak \"localhost\" kullanırsanız çalışmayabilir. Çalışmazsa, yerel IP adresi için \"127.0.0.1\" deneyin.\n\nPostgreSQL kullanıyorsanız, bu alanı bir Unix soketi ile bağlanmak için boş bırakın.",
+ "config-db-wiki-settings": "Bu wikiyi tanımla",
+ "config-db-name": "Veritabanı adı:",
+ "config-db-name-oracle": "Veritabanı şeması:",
+ "config-db-install-account": "Yükleme için kullanıcı hesabı",
+ "config-db-username": "Veritabanı kullanıcı adı:",
+ "config-db-password": "Veritabanı parolası:",
+ "config-db-username-empty": "\"{{int:config-db-username}}\" için bir değer girmelisiniz.",
+ "config-db-install-username": "Yükleme sırasında veritabanına bağlanmak için kullanılan kullanıcı adını girin.\nBu MediaWiki hesabının kullanıcı adı değildir; Bu veritabanın kullanıcı adıdır.",
+ "config-db-wiki-account": "Kullanıcı hesabı için normal işlem",
+ "config-db-prefix": "Veritabanı Tablo öneki:",
+ "config-db-charset": "Veritabanı karakter seti",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 geriye doğru uyumlu UTF-8",
+ "config-mysql-old": "MySQL $1 veya daha yenisi gerekir. Sende bulunan $2 .",
+ "config-db-port": "Veritabanı bağlantı noktası:",
+ "config-db-schema": "MediaWiki için şema:",
+ "config-pg-test-error": "Veritabanıyla bağlantı kurulamıyor ''' $1 ''':$2",
+ "config-sqlite-dir": "SQLite veri dizini",
+ "config-oracle-def-ts": "Varsayılan tablo alanı:",
+ "config-header-mysql": "MySQL ayarları",
+ "config-header-postgres": "PostgreSQL ayarları",
+ "config-header-sqlite": "SQLite ayarları",
+ "config-header-oracle": "Oracle ayarları",
+ "config-header-mssql": "Microsoft SQL Server ayarları",
+ "config-invalid-db-type": "Geçersiz veritabanı türü",
+ "config-missing-db-name": "\"Veritabanı adı\" için bir değer girmelisiniz",
+ "config-missing-db-host": "\"Veritabanı host\" için bir değer girmelisiniz",
+ "config-missing-db-server-oracle": "\"Veritabanının TNS\" için bir değer girmelisiniz",
+ "config-invalid-db-name": "Geçersiz veritabanı adı \" $1 \".\nSadece ASCII harf (a-z, A-Z), rakamların (0-9), alt çizgi (_) ve tire (-) kullanın.",
+ "config-connection-error": "$1.\n\nSunucuyu kontrol edin, kullanıcı adı ve parolayı denetleyin ve yeniden deneyin.",
+ "config-invalid-schema": "Geçersiz şema MediaWiki için \" $1 \".\nYalnızca ASCII harf (a-z, A-Z), rakamların (0-9) ve alt çizgi (_) kullanın.",
+ "config-db-sys-user-exists-oracle": "Kullanıcı hesabı \" $1 \" zaten var. SYSDBA sadece yeni bir hesap oluşturmak için kullanılabilir.",
+ "config-postgres-old": "PostgreSQL $1 veya daha yenisi gerekir. Sende $2 sürümü var.",
+ "config-sqlite-mkdir-error": "Veri dizini oluşturulurken bir hata oluştu \" $1 \".\nKonumu denetleyin ve yeniden deneyin.",
+ "config-sqlite-connection-error": "$1.\n\nVeri dizini ve veritabanı adını denetleyin ve yeniden deneyin.",
+ "config-sqlite-readonly": "Dosya <code>$1</code> yazılabilir değil.",
+ "config-sqlite-cant-create-db": "Veritabanı dosyası oluşturamadı <code>$1</code> .",
+ "config-regenerate": "LocalSettings.php yi yeniden oluştur →",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code>sorgu başarısız!",
+ "config-db-web-account-same": "Yükleme için aynı hesabı kullan",
+ "config-db-web-create": "Eğer oluşturulmuş hesap yoksa yeni hesap oluştur",
+ "config-mysql-engine": "Depolama motoru:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Veritabanı karakter seti",
+ "config-mysql-binary": "İkili",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Kimlik doğrulama türü:",
+ "config-mssql-sqlauth": "SQL Server kimlik doğrulaması",
+ "config-mssql-windowsauth": "Windows Kimlik Doğrulama",
+ "config-site-name": "Wiki adı:",
+ "config-site-name-blank": "Bir site adı girin.",
+ "config-project-namespace": "Proje isim alanı:",
+ "config-ns-generic": "Proje",
+ "config-ns-site-name": "Aynı wiki adı:$1",
+ "config-ns-other": "Diğer (belirtin)",
+ "config-ns-other-default": "MyWiki",
+ "config-ns-invalid": "Belirtilen ad \"<nowiki> $1 </nowiki>\" geçersiz.\nFarklı proje isim alanı belirtin.",
+ "config-ns-conflict": "Belirtilen ad \"<nowiki> $1 </nowiki>\" varsayılan MediaWiki ad alanı ile çakışıyor.\nFarklı proje isim alanı belirtin.",
+ "config-admin-box": "Yönetici hesabı",
+ "config-admin-name": "Kullanıcı adınız:",
+ "config-admin-password": "Şifre:",
+ "config-admin-password-confirm": "Şifre tekrar:",
+ "config-admin-help": "Buraya tercih ettiğiniz kullanıcı adını girin; örneğin \"Joe Bloggs\". Bu vikide oturum açmak için kullanacağınız addır.",
+ "config-admin-name-blank": "Bir yönetici kullanıcı adını giriniz.",
+ "config-admin-name-invalid": "Belirtilen ad \"<nowiki> $1 </nowiki>\" geçersiz.\nFarklı bir kullanıcı adı belirtin.",
+ "config-admin-password-blank": "Yönetici hesabı için bir parola girin.",
+ "config-admin-password-mismatch": "Girdiğiniz şifreler birbirleriyle uyuşmuyor.",
+ "config-admin-email": "E-posta adresi:",
+ "config-admin-email-help": "Wiki'de diğer kullanıcılardan e-posta almak, parolanızı sıfırlamak ve sizin izlediğiniz sayfalarda yapılan değişikliklerin bildirilmesini sağlamak için e-posta adresinizi girin. Bu alanı boş bırakabilirsiniz.",
+ "config-admin-error-user": "Bir yönetici adı ile oluşturma sırasında iç hata \"<nowiki> $1 </nowiki>\".",
+ "config-admin-error-bademail": "Geçersiz e-posta adresi girdiniz.",
+ "config-almost-done": "Neredeyse bitti\nŞimdi kalan yapılandırmaları atlayın ve wikiyi şimdi yükleyin.",
+ "config-optional-continue": "Bana daha fazla soru sor.",
+ "config-optional-skip": "Şimdiden sıkıldım, sadece wikiyi yükle.",
+ "config-profile": "Kullanıcı hakları profili:",
+ "config-profile-wiki": "Açık wiki",
+ "config-profile-no-anon": "Hesap oluşturmak gerekli",
+ "config-profile-fishbowl": "Yalnızca yetkili editörler",
+ "config-profile-private": "Özel wiki",
+ "config-profile-help": "Vikiler, mümkün olan en fazla kişiye değişiklik imkânı verdiğinizde, en iyi şekilde çalışır.\nMediaWiki'de son değişiklikleri incelemek ve tecrübesiz veya kötü niyetli kullanıcıların verdiği zararları geri almak kolaydır.\n\nAncak birçok kişi MediaWiki'yi farklı şekillerde kullanışlı bulmaktadır ve bazen herkesi viki yolunun faydalarına ikna etmek zordur.\nYani seçim sizin.\n\n<strong>{{int:config-profile-wiki}}</strong> modeli, giriş yapmamış olsa bile herkese değişiklik izni verir.\n\n<strong>{{int:config-profile-no-anon}}</strong> kullanan bir viki ise daha izlenebilirdir ancak sıradan, basit, gündelik katkı yapan kullanıcıları caydırabilir.\n\n<strong>{{int:config-profile-fishbowl}}</strong> onaylanmış kullanıcıların değişikliklerine izin verir ama herkes sayfaları ve sayfa geçmişlerini görebilir.\n\n<strong>{{int:config-profile-private}}</strong> sadece onaylanmış kullanıcıları değişiklik yapma ve sayfaları görme imkânı tanır.\n\nDaha karmaşık kullanıcı hakkı ayarları, yüklemeden sonra görülebilir; [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights ilgili kılavuza] bakınız.",
+ "config-license": "Telif Hakkı ve Lisans",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 veya üstü",
+ "config-license-pd": "Kamu Malı",
+ "config-license-cc-choose": "Özel bir Creative Commons lisansı seçin",
+ "config-email-settings": "E-posta ayarları",
+ "config-enable-email": "Giden e-posta etkinleştirme",
+ "config-email-user": "Kullanıcıdan kullanıcıya e-posta gönderimini etkinleştir",
+ "config-email-user-help": "Eğer tercihlerinde etkinleştirmişlerse, kullanıcıların birbirlerine e-posta göndermesine izin ver.",
+ "config-email-usertalk": "Kullanıcı mesaj sayfası bildirimlerini etkinleştir",
+ "config-email-watchlist": "Watchlist bildirimini etkinleştirmek",
+ "config-email-auth": "E-posta kimlik doğrulamasını etkinleştir",
+ "config-email-sender": "E-posta adresini ayarlayın",
+ "config-upload-settings": "Resim ve dosya yükleme",
+ "config-upload-enable": "Dosya yüklemeyi etkinleştirin",
+ "config-upload-deleted": "Silinen dosyalar için dizin:",
+ "config-logo": "Logo URL'si:",
+ "config-cc-again": "Tekrar al...",
+ "config-cc-not-chosen": "Hangi Creative Commons lisansı istiyorum ve tıklayın \"proceed\" ı seçin.",
+ "config-advanced-settings": "Gelişmiş yapılandırma",
+ "config-memcached-servers": "Memcached sunucuları:",
+ "config-extensions": "Uzantılar",
+ "config-install-step-done": "Yapıldı",
+ "config-install-step-failed": "Başarısız",
+ "config-install-database": "Veritabanı ayarlama",
+ "config-install-schema": "Şema oluştur",
+ "config-install-pg-schema-not-exist": "PostgreSQL şema yok.",
+ "config-install-pg-commit": "Değişiklikleri yapılıyor",
+ "config-install-user": "Veritabanı kullanıcısı oluşturma",
+ "config-install-user-alreadyexists": "Kullanıcı \" $1 \" zaten var",
+ "config-install-user-create-failed": "Kullanıcı oluşturma \" $1 \" başarısız oldu:$2",
+ "config-install-user-missing": "Belirtilen kullanıcı \" $1 \" adlı biri yok.",
+ "config-install-user-missing-create": "Belirtilen kullanıcı \" $1 \" yok.\nOluşturmak istiyorsanız, lütfen aşağıdaki \"hesap oluştur\" onay kutusunu tıklatın.",
+ "config-install-tables": "Tabloları oluşturma",
+ "config-install-tables-exist": "''' Uyarı:'' ' MediaWiki tabloları zaten var gibi görünüyor.\nOluşturma atlanıyor.",
+ "config-install-tables-failed": "''' Hata:'' ' tablo oluşturma aşağıdaki hata ile başarısız oldu:$1",
+ "config-install-interwiki-list": "Dosya okunamadı <code>interwiki.list</code> .",
+ "config-install-interwiki-exists": "''' Uyarı:'' ' interwiki Tablo girdileri zaten görünüyor.\nVarsayılan liste atlanıyor.",
+ "config-install-stats": "İstatistik başlatılıyor",
+ "config-install-keys": "Gizli anahtar oluşturma",
+ "config-install-subscribe-notpossible": "cURL yüklü değil ve <code>allow_url_fopen</code> kullanılamaz.",
+ "config-install-mainpage": "Varsayılan içerik ile ana sayfa oluşturma",
+ "config-install-extension-tables": "Uzantılar için etkinleştirilmiş tablolar oluşturma",
+ "config-install-mainpage-failed": "Ana sayfa eklenemedi:$1",
+ "config-download-localsettings": "İndir <code>LocalSettings.php</code>",
+ "config-help": "Yardım",
+ "config-help-tooltip": "genişletmek için tıklayın",
+ "config-nofile": "\"$1\" dosyası bulunamadı. Silindi mi?",
+ "config-extension-link": "Vikinizin [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions eklentileri] desteklediğini biliyor musunuz?\n\n[//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Eklentileri kategorilerine göre] inceleyebilir ya da tüm eklentilerin listesini görmek için [//www.mediawiki.org/wiki/Extension_Matrix Eklenti Matrisine] bakabilirsiniz.",
+ "mainpagetext": "'''MediaWiki başarı ile kuruldu.'''",
+ "mainpagedocfooter": "Viki yazılımının kullanımı hakkında bilgi almak için [//meta.wikimedia.org/wiki/Help:Contents kullanıcı rehberine] bakınız.\n\n== Yeni Başlayanlar ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Yapılandırma ayarlarının listesi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki SSS]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]"
+}
diff --git a/includes/installer/i18n/tt-cyrl.json b/includes/installer/i18n/tt-cyrl.json
new file mode 100644
index 00000000..16f2e54a
--- /dev/null
+++ b/includes/installer/i18n/tt-cyrl.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "KhayR",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "«MediaWiki» уңышлы куелды.",
+ "mainpagedocfooter": "Бу вики турында мәгълүматны [//meta.wikimedia.org/wiki/Help:Contents биредә] табып була.\n\n== Кайбер файдалы ресурслар ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Көйләнмәләр исемлеге (инг.)];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki турында еш бирелгән сораулар һәм җаваплар (инг.)];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki'ның яңа версияләре турында хәбәрләр яздырып алу];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]."
+}
diff --git a/includes/installer/i18n/tt-latn.json b/includes/installer/i18n/tt-latn.json
new file mode 100644
index 00000000..b0ee3d45
--- /dev/null
+++ b/includes/installer/i18n/tt-latn.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Don Alessandro",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "«MediaWiki» uñışlı quyıldı.",
+ "mainpagedocfooter": "Bu wiki turında mäğlümatnı [//meta.wikimedia.org/wiki/Help:Contents biredä] tabıp bula.\n\n== Qayber faydalı resurslar ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Köylänmälär isemlege (ing.)];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki turında yış birelgän sorawlar häm cawaplar (ing.)];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki'nıñ yaña versiäläre turında xäbärlär yazdırıp alu];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]."
+}
diff --git a/includes/installer/i18n/tyv.json b/includes/installer/i18n/tyv.json
new file mode 100644
index 00000000..1652bcf7
--- /dev/null
+++ b/includes/installer/i18n/tyv.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Agilight"
+ ]
+ },
+ "config-page-welcome": "MediaWiki-же кирип моорлаңар!"
+}
diff --git a/includes/installer/i18n/udm.json b/includes/installer/i18n/udm.json
new file mode 100644
index 00000000..8a46212a
--- /dev/null
+++ b/includes/installer/i18n/udm.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Andrewboltachev"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki движок азинлыко пуктэмын.'''"
+}
diff --git a/includes/installer/i18n/ug-arab.json b/includes/installer/i18n/ug-arab.json
new file mode 100644
index 00000000..b8d708f2
--- /dev/null
+++ b/includes/installer/i18n/ug-arab.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sahran"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki مۇۋەپپەقىيەتلىك قاچىلاندى.'''",
+ "mainpagedocfooter": "[//meta.wikimedia.org/wiki/Help:Contents ئىشلەتكۈچى قوللانمىسى] نى زىيارەت قىلىپ wiki يۇمشاق دېتالىنى ئىشلىتىش ئۇچۇرىغا ئېرىشىڭ.\n\n== دەسلەپكى ساۋات ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings سەپلىمە تەڭشەك تىزىملىكى]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki كۆپ ئۇچرايدىغان مەسىلىلەرگە جاۋاب]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki تارقاتقان ئېلخەت تىزىملىكى]"
+}
diff --git a/includes/installer/i18n/uk.json b/includes/installer/i18n/uk.json
new file mode 100644
index 00000000..9eee1a7f
--- /dev/null
+++ b/includes/installer/i18n/uk.json
@@ -0,0 +1,332 @@
+{
+ "@metadata": {
+ "authors": [
+ "AS",
+ "Ahonc",
+ "Alex Khimich",
+ "Andriykopanytsia",
+ "Base",
+ "Diemon.ukr",
+ "Ата",
+ "Тест",
+ "아라",
+ "Amire80"
+ ]
+ },
+ "config-desc": "Інсталятор MediaWiki",
+ "config-title": "Встановлення MediaWiki $1",
+ "config-information": "Інформація",
+ "config-localsettings-upgrade": "'''Увага''': було виявлено файл <code>LocalSettings.php</code>.\nВаше програмне забезпечення може бути оновлено.\nБудь-ласка, перемістіть файл <code>LocalSettings.php</code> в іншу безпечну директорію, а потім знову запустіть програму установки.",
+ "config-localsettings-cli-upgrade": "Виявлено файл <code>LocalSettings.php</code>.\nЩоб оновити наявну установку, запустіть <code>update.php</code>",
+ "config-localsettings-key": "Ключ оновлення:",
+ "config-localsettings-badkey": "Ви вказали неправильний ключ.",
+ "config-upgrade-key-missing": "Виявлено наявну установку MediaWiki.\nДля оновлення цієї установки, будь ласка, вставте такий рядок в кінець вашого <code>LocalSettings.php</code>:\n$1",
+ "config-localsettings-incomplete": "Існуючий файл <code>LocalSettings.php</code> виявився неповним.\nНе вказано змінну $1.\nБудь ласка, змініть <code>LocalSettings.php</code> так, щоб цю змінну було задано, і натисніть \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "Сталася помилка при підключення до бази даних з допомогою налаштувань на сторінці <code>LocalSettings.php</code>. Будь ласка, виправте ці налаштування і спробуйте знову.\n\n$1",
+ "config-session-error": "Помилка початку сесії: $1",
+ "config-session-expired": "Час Вашої сесії минув.\nЗадана тривалість сесії — $1.\nВи можете збільшити її, змінивши <code>session.gc_maxlifetime</code> у php.ini.\nПерезапустіть процес встановлення.",
+ "config-no-session": "Дані сесії було втрачено!\nПеревірте Ваш php.ini і переконайтесь, що <code>session.save_path</code> встановлено у відповідну папку.",
+ "config-your-language": "Ваша мова:",
+ "config-your-language-help": "Оберіть мову для використання в процесі установки.",
+ "config-wiki-language": "Мова для вікі:",
+ "config-wiki-language-help": "Виберіть мову, якою буде відображатися вікі.",
+ "config-back": "← Назад",
+ "config-continue": "Далі →",
+ "config-page-language": "Мова",
+ "config-page-welcome": "Ласкаво просимо на MediaWiki!",
+ "config-page-dbconnect": "Підключення до бази даних",
+ "config-page-upgrade": "Оновлення існуючої установки",
+ "config-page-dbsettings": "Налаштування бази даних",
+ "config-page-name": "Назва",
+ "config-page-options": "Параметри",
+ "config-page-install": "Установка",
+ "config-page-complete": "Готово!",
+ "config-page-restart": "Перезапустити установку",
+ "config-page-readme": "Прочитай мене",
+ "config-page-releasenotes": "Інформація про версію",
+ "config-page-copying": "Копіювання",
+ "config-page-upgradedoc": "Оновлення",
+ "config-page-existingwiki": "Існуюча вікі",
+ "config-help-restart": "Ви бажаєте видалити всі введені та збережені вами дані і запустити процес установки спочатку?",
+ "config-restart": "Так, перезапустити установку",
+ "config-welcome": "=== Перевірка оточення ===\nБудуть проведені базові перевірки, щоб виявити, чи можлива установка MediaWiki у даній системі.\nНе забудьте включити цю інформацію, якщо ви звернетеся по підтримку, як завершити установку.",
+ "config-copyright": "=== Авторське право і умови ===\n\n$1\n\nЦя програма є вільним програмним забезпеченням; Ви можете розповсюджувати та/або змінювати її під ліцензією GNU General Public License, опублікованою Фондом вільного програмного забезпечення; версією 2 цієї ліцензії або будь-якою пізнішою на Ваш вибір.\n\nЦя програма поширюється з надією на те, що вона буде корисною, однак '''без жодних гарантій'''; навіть без неявної гарантії '''комерційної цінності''' або '''придатності для певних цілей'''.\nДив. GNU General Public License для детальної інформації.\n\nВи повинні були отримати <doclink href=Copying>копію GNU General Public License</doclink> разом із цією програмою; якщо ж ні, зверніться до Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. або [http://www.gnu.org/copyleft/gpl.html ознайомтесь з нею онлайн].",
+ "config-sidebar": "* [//www.mediawiki.org Сайт MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Посібник користувача]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Посібник адміністратора]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Інформація про випуск</doclink>\n* <doclink href=Copying>Ліцензія</doclink>\n* <doclink href=UpgradeDoc>Оновлення</doclink>",
+ "config-env-good": "Перевірку середовища успішно завершено.\nВи можете встановити MediaWiki.",
+ "config-env-bad": "Було проведено перевірку середовища. Ви не можете встановити MediaWiki.",
+ "config-env-php": "Встановлено версію PHP: $1.",
+ "config-unicode-using-utf8": "Використовувати utf8_normalize.so Брайона Віббера для нормалізації Юнікоду.",
+ "config-unicode-using-intl": "Використовувати [http://pecl.php.net/intl міжнародне розширення PECL] для нормалізації Юнікоду.",
+ "config-unicode-pure-php-warning": "'''Увага''': [http://pecl.php.net/intl міжнародне розширення PECL] не може провести нормалізацію Юнікоду.\nЯкщо ваш сайт має високий трафік, вам варто почитати про [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормалізацію Юнікоду].",
+ "config-unicode-update-warning": "'''Увага''': Встановлена версія обгортки нормалізації Юнікоду використовує стару версію бібліотеки [http://site.icu-project.org/ проекту ICU].\nВи маєте [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations оновити версію], якщо плануєте повноцінно використовувати Юнікод.",
+ "config-no-db": "Не вдалося знайти відповідний драйвер бази даних! Вам необхідно встановити драйвер бази даних для PHP. Підтримуються такі типи баз даних: $1.\n\nЯкщо ви скомпілювали PHP самостійно, переналаштуйте його з включенням клієнта бази даних, наприклад за допомогою <code>./configure --with-mysqli</code>.\n\nЯкщо установлено PHP з пакетів Debian або Ubuntu, тоді ви також повинні встановити, наприклад, пакунок <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Увага''': у Вас встановлена версія SQLite $1, а це нижче, ніж мінімально необхідна версія $2. SQLite буде недоступним.",
+ "config-no-fts3": "'''Увага''': SQLite зібраний без [//sqlite.org/fts3.html модуля FTS3], функції пошуку не будуть працювати у цій системі.",
+ "config-register-globals-error": "<strong>Помилка: Опція PHP <code>[http://php.net/register_globals register_globals]</code> увімкнена.\nЩоб продовжити це встановлення, її треба вимкнути.</strong>\nДив. довідку, як це зробити, на [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals].",
+ "config-magic-quotes-gpc": "'''Фатальна помилка: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] увімкнена!'''\nЦя опція призводить до непередбачуваного пошкодження даних.\nВи не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
+ "config-magic-quotes-runtime": "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] увімкнена!'''\nЦя опція призводить до непередбачуваного пошкодження даних.\nВи не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
+ "config-magic-quotes-sybase": "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] увімкнена!'''\nЦя опція призводить до непередбачуваного пошкодження даних.\nВи не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
+ "config-mbstring": "'''Проблема: Опція PHP [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] увімкнена!'''\nЦя опція призводить до непередбачуваного пошкодження даних.\nВи не можете встановити і використовувати MediaWiki, поки не буде вимкнено цю опцію.",
+ "config-safe-mode": "'''Увага:''' Опція PHP [http://www.php.net/features.safe-mode «безпечний режим»] увімкнена.\nЦе може спричинити проблеми, зокрема із завантаженням файлів та вставкою математичних формул.",
+ "config-xml-bad": "XML-модуть PHP відсутній.\nMediaWiki необхідні його функції, без цього модуля вона працювати не буде.\nЯкщо Ви використовуєте Mandrake, встановіть php-xml пакет.",
+ "config-pcre-old": "'''Фатальна помилка:''' потрібно PCRE версії $1 або пізнішої.\nВаш виконуваний файл PHP пов'язаний з PCRE версії $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Подробиці].",
+ "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-ctype": "'''Помилка''': PHP має бути зібраним з підтримкою [http://www.php.net/manual/en/ctype.installation.php розширення Ctype].",
+ "config-iconv": "'''Фатальна помилка''': PHP має бути зібраним з підтримкою [http://www.php.net/manual/en/iconv.installation.php розширення iconv].",
+ "config-json": "'''Fatal:''' PHP був скомпільований без підтримки JSON.\nВам потрібно встановити або розширення PHP JSON або розширення[http://pecl.php.net/package/jsonc PECL jsonc] перед встановлення Медіавікі.\n* Розширення PHP включено у Red Hat Enterprise Linux (CentOS) 5 та 6, хоча має бути доступним у <code>/etc/php.ini</code> або <code>/etc/php.d/json.ini</code>.\n* Деякі дистрибутиви Лінукса, випущені після травня 2013, пропустили розширення PHP, натомість упакували розширення PECL як <code>php5-json</code> або <code>php-pecl-jsonc</code>.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] встановлено",
+ "config-apc": "[http://www.php.net/apc APC] встановлено",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] встановлено",
+ "config-no-cache": "'''Увага:''' Не вдалося знайти [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] чи [http://www.iis.net/download/WinCacheForPhp WinCache].\nКешування об'єктів не ввімкнено.",
+ "config-mod-security": "'''Увага''': на Вашому веб-сервері увімкнено [http://modsecurity.org/ mod_security]. У разі неправильних налаштувать, він може викликати проблеми MediaWiki або іншого ПЗ, яке дозволяє користувачам надсилати довільний вміст.\nЗверніться до [http://modsecurity.org/documentation/ документації mod_security] або підтримки Вашого хостера, якщо під час роботи виникають незрозумілі помилки.",
+ "config-diff3-bad": "GNU diff3 не знайдено.",
+ "config-git": "Знайшов програму управління версіями Git: <code>$1</code>.",
+ "config-git-bad": "Програму управління версіями Git не знайдено.",
+ "config-imagemagick": "Виявлено ImageMagick: <code>$1</code>.\nБуде ввімкнуто відображення мініатюр, якщо ви дозволите завантаження файлів.",
+ "config-gd": "Виявлено вбудовано графічну бібліотеку GD.\nБуде ввімкнуто відображення мініатюр, якщо ви дозволите завантаження файлів.",
+ "config-no-scaling": "Не вдалося виявити бібліотеку GD чи ImageMagick.\nВідображення мініатюр буде вимкнено.",
+ "config-no-uri": "'''Помилка:''' Не вдалося визначити поточний URI.\nВстановлення перервано.",
+ "config-no-cli-uri": "'''Увага''': Не задано параметр <code>--scriptpath</code>, використовується за замовчуванням: <code>$1</code>.",
+ "config-using-server": "Використовується ім'я сервера \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Використовується URL сервера \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "'''Увага:''' Ваша типова папка для завантажень <code>$1</code> вразлива до виконання довільних скриптів.\nХоча MediaWiki перевіряє усі завантажені файли на наявність загроз, наполегливо рекомендується [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security закрити дану вразливість] перед тим, як дозволяти завантаження файлів.",
+ "config-no-cli-uploads-check": "'''Увага:''' Ваша типова папка для завантажень (<code>$1</code>) не перевірялась на вразливість до виконання довільних скриптів під час встановлення CLI.",
+ "config-brokenlibxml": "У Вашій системі невдале поєднання версій PHP і libxml2, яке може спричинити пошкодження прихованих даних у MediaWiki та інших веб-застосунках.\nОновіть libxml2 до версії 2.7.3 або пізнішої ([https://bugs.php.net/bug.php?id=45996 відомості про помилку]).\nВстановлення перервано.",
+ "config-suhosin-max-value-length": "Suhosin встановлено і обмежує параметра GET <code>length</code> до $1 байта. Компонент MediaWiki ResourceLoader буде обходити це обмеження, однак це зменшить продуктивність. Якщо це можливо, Вам варто встановити значення <code>suhosin.get.max_value_length</code> як 1024 і більше у <code>php.ini</code> і встановити таке ж значення <code>$wgResourceLoaderMaxQueryLength</code> у LocalSettings.php .",
+ "config-db-type": "Тип бази даних:",
+ "config-db-host": "Хост бази даних:",
+ "config-db-host-help": "Якщо сервер бази даних знаходиться на іншому сервері, введіть тут ім'я хосту і IP адресу.\n\nЯкщо Ви використовуєте віртуальний хостинг, Ваш хостинг-провайдер має надати Вам правильне ім'я хосту у його документації.\n\nЯкщо у Вас сервер із Windows Ви використовуєте MySQL, параметр \"localhost\" може не працювати для імені сервера. Якщо не працює, використайте \"127.0.0.1\" як локальну IP-адресу.\n\nЯкщо Ви використовуєте PostgreSQL, залиште це поле пустим, щоб під'єднатись через сокет Unix.",
+ "config-db-host-oracle": "TNS бази даних:",
+ "config-db-host-oracle-help": "Введіть допустиме [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; файл tnsnames.ora має бути видимим для цієї інсталяції. <br />Якщо Ви використовуєте бібліотеки 10g чи новіші, можна також використовувати метод іменування [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Ідентифікувати цю вікі",
+ "config-db-name": "Назва бази даних:",
+ "config-db-name-help": "Виберіть назву, що ідентифікує Вашу вікі.\nВона не повинна містити пробілів.\n\nЯкщо Ви використовуєте віртуальний хостинг, Ваш хостинг-провайдер або надасть Вам конкретну назву бази даних, або дозволить створювати бази даних з допомогою панелі управління.",
+ "config-db-name-oracle": "Схема бази даних:",
+ "config-db-account-oracle-warn": "Є три підтримувані сценарії установки Oracle:\n\nЯкщо Ви хочете створити обліковий запис бази даних у процесі встановлення, будь ласка, вкажіть обліковий запис ролі SYSDBA для установки і бажані повноваження для облікового запису з веб-доступом. В протилежному випадку Ви можете або створити обліковий запис з веб-доступом вручну і вказати тільки цей обліковий запис (якщо він має необхідні дозволи на створення об'єктів-схем), або вказати два різні облікові записи, з яких в одного будуть права на створення, а в другого, обмеженого — права веб-доступу.\n\nСкрипт для створення облікового запису з необхідними повноваженнями можна знайти у папці \"maintenance/oracle/\" цієї інсталяції. Майте на увазі, що використання обмеженого облікового запису вимкне можливість використання технічного обслуговування з облікового запису за замовчуванням.",
+ "config-db-install-account": "Обліковий запис користувача для встановлення",
+ "config-db-username": "Ім'я користувача бази даних:",
+ "config-db-password": "Пароль бази даних:",
+ "config-db-password-empty": "Будь ласка, введіть пароль для нового користувача бази даних: $1.\nХоча можна створювати користувачів без паролів, це не є безпечним.",
+ "config-db-username-empty": "Ви повинні ввести значення для \"{{int:config-db username}}\"",
+ "config-db-install-username": "Введіть ім'я користувача, яке буде використано для підключення до бази даних під час процесу встановлення.\nЦе не ім'я користувача облікового запису MediaWiki; це ім'я користувача для Вашої бази даних.",
+ "config-db-install-password": "Введіть пароль, який буде використано для підключення до бази даних під час процесу встановлення.\nЦе не пароль облікового запису MediaWiki; це пароль для Вашої бази даних.",
+ "config-db-install-help": "Введіть ім'я користувача і пароль, які буде використано для підключення до бази даних у процесі встановлення.",
+ "config-db-account-lock": "Використовувати ті ж ім'я користувача і пароль і для звичайної роботи",
+ "config-db-wiki-account": "Обліковий запис користувача для звичайної роботи",
+ "config-db-wiki-help": "Введіть ім'я користувача і пароль, які будуть використовуватись для з'єднання з базою даних під час звичайної роботи.\nЯкщо обліковий запис не існує, а в облікового запису інсталяції є достатні повноваження, цей обліковий запис користувача буде створено з мінімальними правами, що необхідні для роботи з вікі.",
+ "config-db-prefix": "Префікс таблиць бази даних:",
+ "config-db-prefix-help": "Якщо треба ділити одну базу даних між декількома вікі або між MediaWiki та іншим веб-застосунком, Ви можете додати префікс до усіх назв таблиць для уникнення конфліктів.\nНе використовуйте пробіли.\n\nЦе поле зазвичай залишають пустим.",
+ "config-db-charset": "Кодування бази даних",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 binary",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 зворотно сумісна з UTF-8",
+ "config-charset-help": "'''Увага:''' Якщо Ви використовуєте '''зворотно сумісну UTF-8''' на MySQL 4.1+ і створюєте резервні копії бази даних з допомогою <code>mysqldump</code>, це може викривити усі не-ASCII символи, незворотно пошкодивши резервні копії!\n\nУ '''бінарному режимі''' MediaWiki зберігає текст UTF-8 у базі даних з бінарними полями.\nЦе більш ефективно, ніж UTF-8 режим MySQL, і дозволяє використовувати увесь набір символів Юнікоду.\nУ '''режимі UTF-8''' MySQL буде знати, якого символу стосуються Ваші дані, і могтиме відображати та конвертувати їх належним чином,\nале не дозволятиме зберігати символи, що виходять за межі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mysql-old": "Необхідна MySQL $1 або пізніша, а у Вас $2.",
+ "config-db-port": "Порт бази даних:",
+ "config-db-schema": "Схема для MediaWiki",
+ "config-db-schema-help": "Ця схема зазвичай працює добре.\nЗмінюйте її тільки якщо знаєте, що Вам це потрібно.",
+ "config-pg-test-error": "Не вдається підключитися до бази даних '''$1''': $2",
+ "config-sqlite-dir": "Папка даних SQLite:",
+ "config-sqlite-dir-help": "SQLite зберігає усі дані в єдиному файлі.\n\nПапка, яку Ви вказуєте, має бути доступна серверу для запису під час встановлення.\n\nВона '''не''' повинна бути доступна через інтернет, тому ми і не поміщуємо її туди, де Ваші файли PHP.\n\nІнсталятор пропише у неї файл <code>.htaccess</code>, але якщо це не спрацює, хтось може отримати доступ до Вашої вихідної бази даних, яка містить вихідні дані користувача (адреси електронної пошти, хеші паролів), а також видалені версії та інші обмежені дані на вікі.\n\nЗа можливості розташуйте базу даних десь окремо, наприклад в <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Простір таблиць за замовчуванням:",
+ "config-oracle-temp-ts": "Тимчасовий простір таблиць:",
+ "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. ([http://www.php.net/manual/en/mysqli.installation.php як зібрати PHP з допомогою MySQL])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] — популярна відкрита СУБД, альтернатива MySQL. Можуть зустрічатись деякі невеликі невиправлені помилки, не рекомендується використовувати у робочій системі.([http://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 масштабу підприємства. ([http://www.php.net/manual/ru/sqlsrv.installation.php Як зібрати PHP з підтримкою SQLSRV])",
+ "config-header-mysql": "Налаштування MySQL",
+ "config-header-postgres": "Налаштування PostgreSQL",
+ "config-header-sqlite": "Налаштування SQLite",
+ "config-header-oracle": "Налаштування Oracle",
+ "config-header-mssql": "Параметри Microsoft SQL Server",
+ "config-invalid-db-type": "Невірний тип бази даних",
+ "config-missing-db-name": "Ви повинні ввести значення параметру \"{{int:config-db-name}}\".",
+ "config-missing-db-host": "Ви повинні ввести значення параметру \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Ви повинні ввести значення параметру \"{{int:config-db-host-oracle}}\".",
+ "config-invalid-db-server-oracle": "Неприпустиме TNS бази даних \"$1\".\nВикористовуйте \"TNS Name\" або рядок \"Easy Connect\" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методи найменування Oracle])",
+ "config-invalid-db-name": "Неприпустима назва бази даних \"$1\".\nВикористовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).",
+ "config-invalid-db-prefix": "Неприпустимий префікс бази даних \"$1\".\nВикористовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).",
+ "config-connection-error": "$1.\n\nПеревірте хост, ім'я користувача та пароль і спробуйте ще раз.",
+ "config-invalid-schema": "Неприпустима схема для MediaWiki \"$1\".\nВикористовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9) і знаки підкреслення(_).",
+ "config-db-sys-create-oracle": "Інсталятор підтримує лише використання облікового запису SYSDBA для створення нового облікового запису.",
+ "config-db-sys-user-exists-oracle": "Обліковий запис користувача \"$1\" уже існує. SYSDBA використовується лише для створення новий облікових записів!",
+ "config-postgres-old": "Необхідна PostgreSQL $1 або пізніша, а у Вас $2.",
+ "config-mssql-old": "Вимагається Microsoft SQL Server версії $1 або більш пізнішої. У вас установлена версія $2.",
+ "config-sqlite-name-help": "Виберіть назву, що ідентифікує Вашу вікі.\nНе використовуйте пробіли і дефіси.\nЦе буде використовуватись у назві файлу даних SQLite.",
+ "config-sqlite-parent-unwritable-group": "Не можна створити папку даних <code><nowiki>$1</nowiki></code>, оскільки батьківська папка <code><nowiki>$2</nowiki></code> не доступна веб-серверу для запису.\n\nІнсталятор виявив, під яким користувачем працює Ваш сервер.\nЗробіть папку <code><nowiki>$3</nowiki></code> доступною для запису, щоб продовжити.\nВ ОС Unix/Linux виконайте:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Не можна створити папку даних <code><nowiki>$1</nowiki></code>, оскільки батьківська папка <code><nowiki>$2</nowiki></code> не доступна веб-серверу для запису.\n\nІнсталятор не зміг виявити, під яким користувачем працює Ваш сервер.\nЗробіть папку <code><nowiki>$3</nowiki></code> доступною для запису серверу (і всім!) глобально, щоб продовжити.\nВ ОС Unix/Linux виконайте:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Помилка при створенні папки даних \"$1\".\nПеревірте розташування і спробуйте знову.",
+ "config-sqlite-dir-unwritable": "Не можливо записати до папки \"$1\".\nЗмініть налаштування доступу так, щоб веб-сервер міг писати до неї, і спробуйте ще раз.",
+ "config-sqlite-connection-error": "$1.\n\nПеревірте папку даних і назву бази даних нижче та спробуйте знову.",
+ "config-sqlite-readonly": "Файл <code>$1</code> недоступний для запису.",
+ "config-sqlite-cant-create-db": "Не вдалося створити файл бази даних <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "У PHP немає підтримки FTS3, скидаю таблиці",
+ "config-can-upgrade": "У цій базі даних є таблиці MediaWiki.\nЩоб оновити їх до MediaWiki $1, натисніть '''Продовжити'''.",
+ "config-upgrade-done": "Оновлення завершено.\n\nВи можете зараз [$1 починати використовувати свою вікі].\n\nЯкщо Ви хочете повторно згенерувати файл <code>LocalSettings.php</code>, натисніть на кнопку нижче.\nЦе '''не рекомендується''', якщо тільки у Вас не виникли проблеми з Вашою вікі.",
+ "config-upgrade-done-no-regenerate": "Оновлення завершено.\n\nВи можете зараз [$1 починати використовувати свою вікі].",
+ "config-regenerate": "Повторно згенерувати LocalSettings.php →",
+ "config-show-table-status": "Запит <code>SHOW TABLE STATUS</code> не виконано!",
+ "config-unknown-collation": "'''Увага:''' База даних використовує нерозпізнане сортування.",
+ "config-db-web-account": "Обліковий запис бази даних для інтернет-доступу",
+ "config-db-web-help": "Оберіть ім'я користувача і пароль, які веб-сервер буде використовувати для з'єднання із сервером бази даних під час звичайної роботи вікі.",
+ "config-db-web-account-same": "Використати той же обліковий запис для встановлення",
+ "config-db-web-create": "Створити обліковий запис, якщо його ще не існує",
+ "config-db-web-no-create-privs": "Обліковий запис, вказаний Вами для встановлення, не має достатніх повноважень для створення облікового запису.\nОбліковий запис, який Ви вказуєте тут, уже повинен існувати.",
+ "config-mysql-engine": "Двигун бази даних:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''Увага''': Ви обрали MyISAM для зберігання даних MySQL, що не рекомендовано для роботи з MediaWiki, оскільки:\n* він слабко підтримує паралелізм через блокування таблиць\n* він більш схильний до ушкоджень, ніж інші двигуни\n* база коду MediaWiki не завжди працює з MyISAM так, як мала б.\n\nЯкщо Ваша інсталяція MySQL підтримує InnoDB, дуже рекомендується вибрати цей двигун.\nЯкщо Ваша інсталяція MySQL не підтримує InnoDB, можливо настав час її оновити.",
+ "config-mysql-only-myisam-dep": "\"'Зауваження:\"' MyISAM є єдиним механізмом для зберігання MySQL на цій машині, який не рекомендується для використання з MediaWiki, оскільки:\n* слабо підтримує паралелізм через блокування таблиць\n* більш схильний до пошкоджень, ніж інші двигуни\n* код MediaWiki не завжди розглядає MyISAM, як повинен\n\nТвоє встановлення MySQL не підтримує InnoDB, можливо, потрібно оновити.",
+ "config-mysql-engine-help": "'''InnoDB''' є завжди кращим вибором, оскільки краще підтримує паралельний доступ.\n\n'''MyISAM''' може бути швидшим для одного користувача або в інсталяціях read-only.\nБази даних MyISAM схильні псуватись частіше, ніж бази InnoDB.",
+ "config-mysql-charset": "Кодування бази даних:",
+ "config-mysql-binary": "Двійкове",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "У '''бінарному режимі''' MediaWiki зберігає текст UTF-8 у базі даних з бінарними полями.\nЦе більш ефективно, ніж UTF-8 режим MySQL, і дозволяє використовувати увесь набір символів Юнікоду.\n\nУ '''режимі UTF-8''' MySQL буде знати, якого символу стосуються Ваші дані, і могтиме відображати та конвертувати їх належним чином, але не дозволятиме зберігати символи, що виходять за межі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mssql-auth": "Тип автентифікації:",
+ "config-mssql-install-auth": "Виберіть тип перевірки автентичності, який буде використовуватися для підключення до бази даних під час процесу установки. \nЯкщо ви оберете \"{{int:config-mssql-windowsauth}}\", будуть використовуватися облікові дані користувача, під яким працює веб-сервер.",
+ "config-mssql-web-auth": "Виберіть тип перевірки автентичності, який веб-сервер буде використовувати для підключення до сервера бази даних під час звичайного функціонування вікі. \nЯкщо ви оберете \"{{int:config-mssql-windowsauth}}\", будуть використовуватися облікові дані користувача, під яким працює веб-сервер.",
+ "config-mssql-sqlauth": "Автентифікація сервера SQL",
+ "config-mssql-windowsauth": "Перевірка Достовірності Windows",
+ "config-site-name": "Назва вікі:",
+ "config-site-name-help": "Це буде відображатись у заголовку вікна браузера та у деяких інших місцях.",
+ "config-site-name-blank": "Введіть назву сайту.",
+ "config-project-namespace": "Простір назв проекту:",
+ "config-ns-generic": "Проект",
+ "config-ns-site-name": "Те ж саме, що й назва вікі: $1",
+ "config-ns-other": "Інше (вкажіть)",
+ "config-ns-other-default": "MyWiki",
+ "config-project-namespace-help": "За прикладом Вікіпедії, чимало вікі тримають свої сторінки правил окремо від сторінок основного вмісту, у \"'''просторі імен проекту'''\".\nУсі назви сторінок у цьому просторі імен починаються з певного префікса, який Ви можете вказати тут.\nТрадиційно цей префікс виводиться з назви вікі, але не може містити знаки пунктуація, як-то \"#\" чи \":\".",
+ "config-ns-invalid": "Вказаний простір імен \"<nowiki>$1</nowiki>\" не припустимий.\nВкажіть інший простір імен проекту.",
+ "config-ns-conflict": "Вказаний простір імен \"<nowiki>$1</nowiki>\" конфліктує зі стандартним простором імен MediaWiki.\nВкажіть інший простір імен проекту.",
+ "config-admin-box": "Обліковий запис адміністратора",
+ "config-admin-name": "Ваше ім'я користувача:",
+ "config-admin-password": "Пароль:",
+ "config-admin-password-confirm": "Пароль ще раз:",
+ "config-admin-help": "Введіть бажане ім'я користувача тут, наприклад \"Павло НЛО\".\nЦе ім'я ви будете використовувати про вході у вікі.",
+ "config-admin-name-blank": "Введіть ім'я користувача адміністратора.",
+ "config-admin-name-invalid": "Вказане ім'я користувача \"<nowiki>$1</nowiki>\" не припустиме.\nВкажіть інше ім'я користувача.",
+ "config-admin-password-blank": "Введіть пароль до облікового запису адміністратора.",
+ "config-admin-password-mismatch": "Два введені вами паролі не збігаються.",
+ "config-admin-email": "Адреса електронної пошти:",
+ "config-admin-email-help": "Введіть адресу електронної пошти, щоб мати змогу отримувати електронну пошту від інших користувачів у вікі, відновити пароль і отримувати повідомлення про зміни, внесені до сторінок у Вашому списку спостереження. Ви можете залишити це поле пустим.",
+ "config-admin-error-user": "Внутрішня помилка під час створення адміністратора з ім'ям \"<nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Внутрішня помилка під час встановлення пароля для адміністратора \"<nowiki>$1</nowiki>\":<pre>$2</pre>",
+ "config-admin-error-bademail": "Ви ввели недопустиму адресу електронної пошти.",
+ "config-subscribe": "Підписатися на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce розсилку анонсів нових версій MediaWiki].",
+ "config-subscribe-help": "Це список розсилки з малим обсягом повідомлень, що використовується для анонсування релізів, а також важливих повідомлень про безпеку.\nВам варто підписати і оновлювати інсталяцію MediaWiki, коли з'являтимуться нові версії.",
+ "config-subscribe-noemail": "Ви намагались підписатись на розсилку анонсів релізів, не вказавши адреси електронної пошти.\nБудь ласка, вкажіть адресу електронної пошти, якщо хочете підписатись на розсилку.",
+ "config-almost-done": "Майже готово!\nВи можете зараз пропустити налаштування, що залишилось, і встановити вікі прямо зараз.",
+ "config-optional-continue": "Запитуйте ще.",
+ "config-optional-skip": "Це вже втомлює, просто встановити вікі.",
+ "config-profile": "Профіль прав користувача:",
+ "config-profile-wiki": "Відкрита вікі",
+ "config-profile-no-anon": "Необхідно створити обліковий запис",
+ "config-profile-fishbowl": "Тільки для авторизованих редакторів",
+ "config-profile-private": "Приватна вікі",
+ "config-profile-help": "Вікі краще працюють, коли Ви дозволяєте їх редагувати якомога ширшому колу людей.\nУ MediaWiki легко переглядати останні зміни і відкочувати будь-яку шкоду, спричинену недосвідченими або зловмисними користувачами.\n\nОдначе, MediaWiki може бути корисна по-різному, й інколи важко переконати у вигідності відкритої вікі-роботи.\nТож у Вас є вибір.\n\nМодель '''{{int:config-profile-wiki}}''' дозволяє редагувати будь-кому, навіть без входження в систему.\nВікі з вимогою \"'''{{int:config-profile-no-anon}}'''\" дає певний облік, але може відвернути випадкових дописувачів.\nСпосіб \"'''{{int:config-profile-fishbowl}}'''\" дозволяє редагувати підтвердженим користувачам, а переглядати сторінки і історію можуть усі.\n'''{{int:config-profile-private}}''' дозволяє переглядати сторінки і редагувати лише підтвердженим користувачам.\n\nДетальніші конфігурації прав користувачів доступні після встановлення, див. [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights відповідний розділ посібника].",
+ "config-license": "Авторські права і ліцензія:",
+ "config-license-none": "Без ліцензії у нижньому колонтитулі",
+ "config-license-cc-by-sa": "Creative Commons Attribution Share Alike",
+ "config-license-cc-by": "Creative Commons Attribution",
+ "config-license-cc-by-nc-sa": "Creative Commons Attribution Non-Commercial Share Alike",
+ "config-license-cc-0": "Creative Commons Zero (Суспільне надбання)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 або пізніша",
+ "config-license-pd": "Суспільне надбання (Public Domain)",
+ "config-license-cc-choose": "Виберіть одну з ліцензій Creative Commons",
+ "config-license-help": "Чимало загальнодоступних вікі публікують увесь свій вміст під [http://freedomdefined.org/Definition вільною ліцензією]. Це розвиває відчуття спільної власності і заохочує довготривалу участь. У загальному випадку для приватної чи корпоративної вікі у цьому немає необхідності.\n\nЯкщо Ви хочете мати змогу використовувати текст з Вікіпедії і дати Вікіпедії змогу використовувати текст, скопійований з Вашої вікі, вам необхідно обрати <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nРаніше Вікіпедія використовувала GNU Free Documentation License.\nGFDL — допустима ліцензія, але у ній важко розібратися, а контент під GFDL важко використовувати повторно.",
+ "config-email-settings": "Налаштування електронної пошти",
+ "config-enable-email": "Увімкнути вихідну електронну пошту",
+ "config-enable-email-help": "Якщо Ви хочете, що електронна пошта працювала, необхідно виставити коректні [http://www.php.net/manual/en/mail.configuration.php налаштування пошти у PHP].\nЯкщо Вам не потрібні жодні можливості електронної пошти у вікі, можете тут їх відключити.",
+ "config-email-user": "Увімкнути електронну пошту користувач-користувачеві",
+ "config-email-user-help": "Дозволити усім користувачам надсилати один одному електронну пошту, якщо вони увімкнули цю можливість у своїх налаштуваннях.",
+ "config-email-usertalk": "Увімкнути сповіщення про повідомлення на сторінці обговорення користувача",
+ "config-email-usertalk-help": "Дозволити користувачам отримувати сповіщення про зміни на своїй сторінці обговорення, якщо вони увімкнули цю можливість у своїх налаштуваннях.",
+ "config-email-watchlist": "Увімкнути сповіщення про зміни у списку спостереження",
+ "config-email-watchlist-help": "Дозволити користувачам отримувати сповіщення про сторінки з їхнього списку спостереження, якщо вони увімкнули цю можливість у своїх налаштуваннях.",
+ "config-email-auth": "Увімкнути автентифікацію через електронну пошту",
+ "config-email-auth-help": "Якщо ця опція увімкнена, користувачам треба підтвердити свою адресу електронної пошти з допомогою надісланого їм посилання, коли вони встановлюють чи змінюють її.\nТільки автентифіковані адреси електронної пошти отримують листи від інших користувачів або змінювати поштові сповіщення.\nУвімкнення цієї опції '''рекомендується''' загальнодоступним вікі через можливі зловживання функціями електронної пошти.",
+ "config-email-sender": "Зворотна адреса електронної пошти:",
+ "config-email-sender-help": "Введіть адресу електронної пошти, що буде використовуватись як зворотна адреса для вихідної пошти.\nНа неї будуть надсилатись відмови.\nЧимало поштових серверів вимагають, щоб принаймні доменне ім'я було допустимим.",
+ "config-upload-settings": "Завантаження зображень і файлів",
+ "config-upload-enable": "Дозволити завантаження файлів",
+ "config-upload-help": "Завантаження файлів підставляє Ваш сервер під потенційні загрози.\nДетальнішу інформацію можна почитати у посібнику, [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security розділ про безпеку].\n\nЩоб дозволити завантаження файлів, змініть режим підпапки <code>images</code> у кореневій папці MediaWiki так, щоб сервер міг у неї записувати.\nПотім увімкніть цю опцію.",
+ "config-upload-deleted": "Каталог для вилучених файлів:",
+ "config-upload-deleted-help": "Оберіть папку для архівації видалених файлів.\nВ ідеалі, вона не має бути доступною через інтернет.",
+ "config-logo": "URL логотипу:",
+ "config-logo-help": "Стандартна схема оформлення MediaWiki містить вільне для логотипу місце над бічною панеллю розміром 135x160 пікселів.\n\nЗавантажте зображення відповідного розміру і введіть тут його URL.\n\nВи можете використати <code>$wgStylePath</code> або <code>$wgScriptPath</code>, якщо ваш логотип пов'язаний з цими шляхами.\n\nЯкщо Вам не потрібен логотип, залиште це поле пустим.",
+ "config-instantcommons": "Увімкнути Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] це функція, що дозволяє вікі використовувати зображення, звуки та інші медіа, розміщені на [//commons.wikimedia.org/ Вікісховищі].\nДля цього MediaWiki необхідний доступ до інтернету.\n\nДодаткову інформацію стосовно цієї функції, включаючи інструкції, як її увімкнути у вікі, відмінних від Вікісховища, дивіться у [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos посібнику].",
+ "config-cc-error": "Механізм вибору ліцензії Creative Commons не дав результатів.\nВведіть назву ліцензії вручну.",
+ "config-cc-again": "Виберіть знову ...",
+ "config-cc-not-chosen": "Оберіть, яку ліцензію Creative Commons Ви хочете використовувати, і натисніть \"продовжити\".",
+ "config-advanced-settings": "Розширені налаштування",
+ "config-cache-options": "Налаштування кешування об'єктів:",
+ "config-cache-help": "Кешування об'єктів використовується для покращення швидкодії MediaWiki методом кешування часто використовуваних даних.\nЗаохочується увімкнення цієї можливості для середніх і великих сайтів, малі сайти також можуть відчути її перевагу.",
+ "config-cache-none": "Без кешування (жодні функції не втрачаються, але впливає на швидкодію великих вікі-сайтів)",
+ "config-cache-accel": "PHP кешування об'єктів (APC, XCache чи WinCache)",
+ "config-cache-memcached": "Використовувати Memcached (вимагає додаткової установки і налаштування)",
+ "config-memcached-servers": "Сервери Memcached:",
+ "config-memcached-help": "Список IP-адрес, що викоритовує Memcached.\nВкажіть по одному в рядку, разом з портами. Наприклад:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "Ви обрали тип кешування Memcached, але не вказали ніяких серверів.",
+ "config-memcache-badip": "Ви ввели недопустиму IP-адресу для Memcached: $1.",
+ "config-memcache-noport": "Ви не вказали порт для сервера Memcached: $1.\nЯкщо Ви його не знаєте, за замовчуванням використовується 11211.",
+ "config-memcache-badport": "Номери портів Memcached повинні лежати в межах від $1 до $2.",
+ "config-extensions": "Розширення",
+ "config-extensions-help": "Розширення, перераховані вище, були знайдені у папці <code>./extensions</code>.\n\nВони можуть потребувати додаткових налаштувань, але Ви можете увімкнути їх зараз.",
+ "config-skins": "Оформлення",
+ "config-skins-help": "Перераховані вище теми оформлення було знайдено у Вашій папці <code>./skins</code>. Ви маєте увімкнути хоча б одну, і обрати тему за замовчуванням.",
+ "config-skins-use-as-default": "Використовувати цю тему за замовчуванням",
+ "config-skins-missing": "Не було знайдено жодних тем; MediaWiki буде використовувати резервну тему, поки Ви не встановите власні.",
+ "config-skins-must-enable-some": "Потрібно вибрати принаймні одну тему, щоб увімкнути.",
+ "config-skins-must-enable-default": "Тема, обрана за замовчуванням, повинна бути увімкнена.",
+ "config-install-alreadydone": "'''Увага:''' Здається, Ви вже встановлювали MediaWiki і зараз намагаєтесь встановити її знову.\nБудь ласка, перейдіть на наступну сторінку.",
+ "config-install-begin": "Натискаючи \"{{int:config-continue}}\", Ви розпочинаєте встановлення MediaWiki.\nЯкщо Ви все ще хочете внести зміни, натисніть \"{{int:config-back}}\".",
+ "config-install-step-done": "виконано",
+ "config-install-step-failed": "не вдалося",
+ "config-install-extensions": "У тому числі розширення",
+ "config-install-database": "Налаштування бази даних",
+ "config-install-schema": "Створення схеми",
+ "config-install-pg-schema-not-exist": "Схеми PostgreSQL не існує.",
+ "config-install-pg-schema-failed": "Не вдалось створити таблиці.\nПереконайтесь, що користувач \"$1\" може писати до схеми \"$2\".",
+ "config-install-pg-commit": "Внесення змін",
+ "config-install-pg-plpgsql": "Перевірка мови PL/pgSQL",
+ "config-pg-no-plpgsql": "Вам необхідно встановити мову PL/pgSQL у базі даних $1",
+ "config-pg-no-create-privs": "Обліковий запис, вказаний для встановлення, має недостатньо прав для створення облікового запису.",
+ "config-pg-not-in-role": "Обліковий запис, який Ви вказали для веб-користувача, уже існує.\nОбліковий запис, який Ви вказали для встановлення не є суперюзером і не відноситься до ролі веб-користувача, тому неможливо створити об'єкти, що належать веб-користувачеві.\n\nУ даний час MediaWiki вимагає, щоб усі таблиці належали веб-користувачу. Будь ласка, вкажіть інше ім'я облікового запису або натисніть \"Назад\" та вкажіть користувача з достатніми правами.",
+ "config-install-user": "Створення користувача бази даних",
+ "config-install-user-alreadyexists": "Користувач \"$1\" уже існує",
+ "config-install-user-create-failed": "Не вдалося створити користувача \"$1\": $2",
+ "config-install-user-grant-failed": "Не вдалося надати права користувачеві \"$1\": $2",
+ "config-install-user-missing": "Зазначеного користувача \"$1\" не існує.",
+ "config-install-user-missing-create": "Зазначеного користувача \"$1\" не існує.\nБудь ласка, поставте галочку \"Створити обліковий запис\", якщо хочете його створити.",
+ "config-install-tables": "Створення таблиць",
+ "config-install-tables-exist": "'''Увага''': Таблиці MediaWiki уже, здається, існують.\nПропуск створення.",
+ "config-install-tables-failed": "'''Помилка''': Не вдалося створити таблицю внаслідок такої помилки: $1",
+ "config-install-interwiki": "Заповнення таблиці інтервікі значеннями за замовчуванням",
+ "config-install-interwiki-list": "Не вдалося знайти файл <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Увага''': Таблиця інтервікі уже, здається, має записи.\nСтворення стандартного списку пропускається.",
+ "config-install-stats": "Ініціалізація статистики",
+ "config-install-keys": "Генерація секретних ключів",
+ "config-insecure-keys": "'''Увага:''' {{PLURAL:$2|1=Секретний ключ|Секретні ключі}} ($1), {{PLURAL:$2|1=згенерований в процесі встановлення, недостатньо надійний|згенеровані в процесі встановлення, недостатньо надійні}}. Розгляньте можливість {{PLURAL:$2|1=його|їх}} заміни вручну.",
+ "config-install-updates-failed": "<strong>Помилка:</strong> Вставка оновленних ключів в таблиці не вдалося через таку помилку:$1",
+ "config-install-sysop": "Створення облікового запису адміністратора",
+ "config-install-subscribe-fail": "Не можливо підписатись на mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL не встановлено і опція <code>allow_url_fopen</code> не доступна.",
+ "config-install-mainpage": "Створення головної сторінки із вмістом за замовчуванням",
+ "config-install-extension-tables": "Створення таблиць для увімкнених розширень",
+ "config-install-mainpage-failed": "Не вдається вставити головну сторінку: $1",
+ "config-install-done": "'''Вітаємо!'''\nВи успішно встановили MediaWiki.\n\nІнсталятор згенерував файл <code>LocalSettings.php</code>, який містить усі Ваші налаштування.\n\nВам необхідно завантажити його і помістити у кореневу папку Вашої вікі (туди ж, де index.php). Завантаження мало початись автоматично.\n\nЯкщо завантаження не почалось або Ви його скасували, можете заново його почати, натиснувши на посилання внизу:\n\n$3\n\n'''Примітка''': Якщо Ви не зробите цього зараз, цей файл не буде доступним пізніше, коли Ви вийдете з встановлення, не скачавши його.\n\nПісля виконання дій, описаних вище, Ви зможете '''[$2 увійти у свою вікі]'''.",
+ "config-download-localsettings": "Завантажити <code>LocalSettings.php</code>",
+ "config-help": "допомога",
+ "config-help-tooltip": "натисніть, щоб розгорнути",
+ "config-nofile": "Файл \"$1\" не знайдено. Його видалено?",
+ "config-extension-link": "Чи знаєте ви, що ваше вікі підтримує [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions розширення]?\n\nВи можете переглядати [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category розширення по категорії] або в [//www.mediawiki.org/wiki/Extension_Matrix матрицю розширень] щоб побачити повний список розширень.",
+ "mainpagetext": "Програмне забезпечення «MediaWiki» успішно встановлене.",
+ "mainpagedocfooter": "Інформацію про роботу з цією вікі можна знайти в [//meta.wikimedia.org/wiki/Help:Contents посібнику користувача].\n\n== Деякі корисні ресурси ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Список налаштувань];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Часті питання з приводу MediaWiki];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розсилка повідомлень про появу нових версій MediaWiki];\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Локалізуйте MediaWiki своєю мовою]"
+}
diff --git a/includes/installer/i18n/ur.json b/includes/installer/i18n/ur.json
new file mode 100644
index 00000000..98e8825c
--- /dev/null
+++ b/includes/installer/i18n/ur.json
@@ -0,0 +1,34 @@
+{
+ "@metadata": {
+ "authors": [
+ "Noor2020",
+ "පසිඳු කාවින්ද"
+ ]
+ },
+ "config-information": "معلومات",
+ "config-git": "Git ورژن کنٹرول مصنع لطیف ملا: <code>$1</code> ۔",
+ "config-git-bad": "GIT ورژن کنٹرول مصنع لطیف نہيں ملا ۔",
+ "config-mysql-only-myisam-dep": "' ' تنبیہ: ' '[[MyISAM|مائ اسام]] واحد دستیاب 'ذخیرہ جاتی انجن' ہے جو مائی ایس کیو ایل کے لیے ہے ، جو کہ ناموزوں ہے میڈیا وکی کے لیے ،کیوں کہ :\n* یہ ہموار قطاروں کی سہولت بمشکل فراہم کرتا ہے\n* یہ دوسرے انجنوں کے مقابلے زیادہ بگڑ جاتا ہے\n* میڈیا وکی کوڈ بیس ہمیشہ سنبھال نہيں پاتا مائی اسام کو ۔\n\nآپ کا مائی ایس کیو ایل کا نصب ہمیشہ اننو ڈی بی کی سہولت نہيں دے سکتا ، ہو سکتا ہے یہ مزید ترقیاتی کام چاہے",
+ "config-profile-fishbowl": "صرف مجاز ایڈیٹرز",
+ "config-license-pd": "پبلک ڈومین",
+ "config-email-settings": "ای میل کی ترتیبات",
+ "config-email-user-help": "تمام صارفین ای میل بھیجنے کیلئے ایک دوسرے اگر وہ یہ ان کی ترجیحات میں فعال ہے کی اجازت دیتے ہیں.",
+ "config-email-usertalk": "صارف بات صفحہ کی اطلاع فعال",
+ "config-email-usertalk-help": "اگر وہ یہ ان کی ترجیحات میں فعال ہے صارف بات صفحہ تبدیلی پر اطلاعات حاصل کرنے کے لئے صارفین کی اجازت دیں.",
+ "config-email-watchlist": "دیکھنی والی فہرست کی اطلاع فعال",
+ "config-email-auth": "فعال ای میل کی تصدیق",
+ "config-email-sender": "ای میل ایڈریس پر واپس:",
+ "config-upload-deleted": "ڈائرکٹری خارج کردہ فائلوں کے لیے:",
+ "config-advanced-settings": "اعلی درجے کی ترتیب",
+ "config-cache-options": "اعتراض کیش کے لئے ترتیب دینا:",
+ "config-extensions": "ملانے",
+ "config-install-step-done": "کیا کیا",
+ "config-install-step-failed": "میں ناکام رہے",
+ "config-install-extensions": "سمیت ملانے",
+ "config-install-database": "ڈیٹا بیس کی ترتیب",
+ "config-install-pg-commit": "تبدیلیوں کے ارتکاب",
+ "config-install-keys": "خفیہ چابیاں پیدا",
+ "config-install-sysop": "منتظم کے صارف کے اکاؤنٹ کی تشکیل",
+ "config-install-mainpage": "پہلے سے طے شدہ مواد کے ساتھ سب سے کامیاب کی تشکیل",
+ "mainpagetext": "'''میڈیاوکی کو کامیابی سے چالو کردیا گیا ہے۔.'''"
+}
diff --git a/includes/installer/i18n/uz.json b/includes/installer/i18n/uz.json
new file mode 100644
index 00000000..d040fdec
--- /dev/null
+++ b/includes/installer/i18n/uz.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sociologist"
+ ]
+ },
+ "config-admin-password-blank": "Administrator hisob yozuvi uchun maxfiy soʻz kiriting.",
+ "mainpagetext": "'''MediaWiki muvaffaqiyatli o'rnatildi.'''",
+ "mainpagedocfooter": "Wiki dasturini ishlatish haqida ma'lumot olish uchun [//meta.wikimedia.org/wiki/Help:Contents Foydalanuvchi qo'llanmasi] sahifasiga murojaat qiling.\n\n== Dastlabki qadamlar ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Moslamalar ro'yxati]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki haqida ko'p so'raladigan savollar]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki yangi versiyasi chiqqanda xabar berish ro'yxati]"
+}
diff --git a/includes/installer/i18n/vec.json b/includes/installer/i18n/vec.json
new file mode 100644
index 00000000..dd65180d
--- /dev/null
+++ b/includes/installer/i18n/vec.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vajotwo",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''Instałasion de MediaWiki conpletà coretamente.'''",
+ "mainpagedocfooter": "Varda ła [//meta.wikimedia.org/wiki/Help:Contents Guida utente] par majori informasion so l'uso de sto software wiki.\n\n== Par scumisiar ==\n\nI seguenti cołegamenti i xé en łengua inglese:\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Inpostasion de configurasion]\n* [//www.mediawiki.org/wiki/Manual:FAQ/it Domande frequenti so MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list anunsi MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/vep.json b/includes/installer/i18n/vep.json
new file mode 100644
index 00000000..78c3b7b2
--- /dev/null
+++ b/includes/installer/i18n/vep.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Игорь Бродский",
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki-likutim om seižutadud jügedusita.'''",
+ "mainpagedocfooter": "Kc. [//meta.wikimedia.org/wiki/Help:Contents Kävutajan kirj], miše sada informacijad wikin kävutamižes.\n\n== Erased tarbhaižed resursad ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Järgendusiden nimikirjutez]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce počtnimikirjutez]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/vi.json b/includes/installer/i18n/vi.json
new file mode 100644
index 00000000..1f3f89ef
--- /dev/null
+++ b/includes/installer/i18n/vi.json
@@ -0,0 +1,195 @@
+{
+ "@metadata": {
+ "authors": [
+ "පසිඳු කාවින්ද",
+ "Minh Nguyen",
+ "Withoutaname"
+ ]
+ },
+ "config-desc": "Trình cài đặt MediaWiki",
+ "config-title": "Cài đặt MediaWiki $1",
+ "config-information": "Thông tin",
+ "config-localsettings-key": "Chìa khóa nâng cấp:",
+ "config-localsettings-badkey": "Bạn đã cung cấp một chìa khóa sai.",
+ "config-session-error": "Lỗi khi bắt đầu phiên làm việc: $1",
+ "config-your-language": "Ngôn ngữ của bạn:",
+ "config-your-language-help": "Chọn một ngôn ngữ để sử dụng trong quá trình cài đặt.",
+ "config-wiki-language": "Ngôn ngữ wiki:",
+ "config-wiki-language-help": "Chọn ngôn ngữ chủ yếu của nội dung trong wiki này.",
+ "config-back": "← Lùi",
+ "config-continue": "Tiếp →",
+ "config-page-language": "Ngôn ngữ",
+ "config-page-welcome": "Chào mừng đến với MediaWiki!",
+ "config-page-dbconnect": "Kết nối với cơ sở dữ liệu",
+ "config-page-upgrade": "Nâng cấp một bản cài đặt có sẵn",
+ "config-page-dbsettings": "Thiết lập cơ sở dữ liệu",
+ "config-page-name": "Tên",
+ "config-page-options": "Tùy chọn",
+ "config-page-install": "Cài đặt",
+ "config-page-complete": "Xong rồi!",
+ "config-page-restart": "Bắt đầu cài đặt lại",
+ "config-page-readme": "Đọc trước",
+ "config-page-releasenotes": "Thông báo phát hành",
+ "config-page-copying": "Sao chép",
+ "config-page-upgradedoc": "Nâng cấp",
+ "config-page-existingwiki": "Wiki đã tồn tại",
+ "config-restart": "Có, khởi động lại nó",
+ "config-env-good": "Đã kiểm tra môi trường.\nBạn có thể cài đặt MediaWiki.",
+ "config-env-bad": "Đã kiểm tra môi trường.\nBạn không thể cài đặt MediaWiki.",
+ "config-env-php": "PHP $1 đã được cài đặt.",
+ "config-env-php-toolow": "PHP $1 đã được cài đặt.\nTuy nhiên, MediaWiki cần PHP $2 trở lên.",
+ "config-unicode-using-utf8": "Đang sử dụng utf8_normalize.so của Brion Vibber để chuẩn hóa văn bản Unicode.",
+ "config-unicode-using-intl": "Sẽ sử dụng [http://pecl.php.net/intl phần mở rộng PECL intl] để chuẩn hóa Unicode.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] đã được cài đặt",
+ "config-apc": "[http://www.php.net/apc APC] đã được cài đặt",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] đã được cài đặt",
+ "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-git-bad": "Không tìm thấy phần mềm điều khiển phiên bản Git.",
+ "config-using-server": "Sẽ sử dụng tên máy chủ “<nowiki>$1</nowiki>”.",
+ "config-using-uri": "Sẽ sử dụng URL máy chủ “<nowiki>$1$2</nowiki>”.",
+ "config-db-type": "Kiểu cơ sở dữ liệu:",
+ "config-db-host": "Máy chủ của cơ sở dữ liệu:",
+ "config-db-host-oracle": "TNS cơ sở dữ liệu:",
+ "config-db-wiki-settings": "Dữ liệu để nhận ra wiki này",
+ "config-db-name": "Tên cơ sở dữ liệu:",
+ "config-db-name-oracle": "Giản đồ cơ sở dữ liệu:",
+ "config-db-install-account": "Tài khoản người dùng để cài đặt",
+ "config-db-username": "Tên người dùng cơ sở dữ liệu:",
+ "config-db-password": "Mật khẩu cơ sở dữ liệu:",
+ "config-db-username-empty": "Bạn phải nhập một giá trị cho “{{int:config-db-username}}”",
+ "config-db-wiki-account": "Tài khoản người dùng để hoạt động bình thường",
+ "config-db-prefix": "Tiền tố bảng cơ sở dữ liệu:",
+ "config-db-charset": "Bảng mã cơ sở dữ liệu",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 nhị phân",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 UTF-8 tương thích ngược",
+ "config-mysql-old": "Cần MySQL $1 trở lên; bạn có $2.",
+ "config-db-port": "Cổng cơ sở dữ liệu:",
+ "config-db-schema": "Giản đồ cho MediaWiki:",
+ "config-pg-test-error": "Không thể kết nối với cơ sở dữ liệu '''$1''': $2",
+ "config-sqlite-dir": "Thư mục dữ liệu SQLite:",
+ "config-oracle-def-ts": "Không gian bảng mặc định:",
+ "config-oracle-temp-ts": "Không gian bảng tạm:",
+ "config-type-mysql": "MySQL (hoặc tương hợp)",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-header-mysql": "Thiết lập MySQL",
+ "config-header-postgres": "Thiết lập PostgreSQL",
+ "config-header-sqlite": "Thiết lập SQLite",
+ "config-header-oracle": "Thiết lập Oracle",
+ "config-header-mssql": "Thiết lập Microsoft SQL Server",
+ "config-invalid-db-type": "Loại cơ sở dữ liệu không hợp lệ",
+ "config-missing-db-name": "Bạn phải nhập một giá trị cho “{{int:config-db-name}}”",
+ "config-missing-db-host": "Bạn phải nhập một giá trị cho “{{int:config-db-host}}”",
+ "config-missing-db-server-oracle": "Bạn phải nhập một giá trị cho “{{int:config-db-host-oracle}}”",
+ "config-invalid-schema": "Giản đồ “$1” không hợp lệ cho MediaWiki.\nHãy chỉ sử dụng các chữ cái ASCII (a–z, A–Z), chữ số (0–9), và dấu gạch dưới (_).",
+ "config-postgres-old": "Cần PostgreSQL $1 trở lên; bạn có $2.",
+ "config-mssql-old": "Cần Microsoft SQL Server $1 trở lên. Bạn có $2.",
+ "config-sqlite-readonly": "Không thể ghi vào tập tin <code>$1</code>.",
+ "config-sqlite-cant-create-db": "Không thể tạo ra tập tin cơ sở dữ liệu <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "PHP thiếu sự hỗ trợ cho FTS3; đang giáng cấp các bảng",
+ "config-can-upgrade": "Cơ sở dữ liệu này có bảng MediaWiki.\nĐể nâng cấp các bảng đến MediaWiki $1, bấm <strong>Tiếp tục</strong>.",
+ "config-regenerate": "Tạo lại LocalSettings.php →",
+ "config-show-table-status": "Truy vấn <code>SHOW TABLE STATUS</code> bị thất bại!",
+ "config-db-web-account": "Tài khoản cơ sở dữ liệu để truy cập Web",
+ "config-db-web-account-same": "Sử dụng lại tài khoản cài đặt",
+ "config-db-web-create": "Mở tài khoản nếu chưa tồn tại",
+ "config-mysql-engine": "Máy lưu trữ:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Bảng mã cơ sở dữ liệu:",
+ "config-mysql-binary": "Nhị phân",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Kiểu xác thực:",
+ "config-mssql-sqlauth": "Xác thực SQL Server",
+ "config-mssql-windowsauth": "Xác thực Windows",
+ "config-site-name": "Tên wiki:",
+ "config-site-name-blank": "Nhập tên của trang Web.",
+ "config-project-namespace": "Không gian tên dự án:",
+ "config-ns-generic": "Dự án",
+ "config-ns-site-name": "Cùng với tên wiki: $1",
+ "config-ns-other": "Khác (định rõ)",
+ "config-ns-other-default": "WikiTôi",
+ "config-admin-box": "Tài khoản bảo quản viên",
+ "config-admin-name": "Tên người dùng của bạn:",
+ "config-admin-password": "Mật khẩu:",
+ "config-admin-password-confirm": "Nhập lại mật khẩu:",
+ "config-admin-name-blank": "Nhập tên người dùng của bảo quản viên.",
+ "config-admin-password-blank": "Nhập mật khẩu của tài khoản bảo quản viên.",
+ "config-admin-password-mismatch": "Bạn đã nhập hai mật khẩu không khớp với nhau.",
+ "config-admin-email": "Địa chỉ thư điện tử:",
+ "config-admin-error-bademail": "Bạn đã nhập một địa chỉ thư điện tử không hợp lệ.",
+ "config-subscribe": "Theo dõi [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce danh sách thư thông báo phát hành].",
+ "config-optional-continue": "Hỏi tôi về thêm chi tiết.",
+ "config-optional-skip": "Chán quá, cài đặt wiki rỗi.",
+ "config-profile": "Hồ sơ quyền người dùng:",
+ "config-profile-wiki": "Mở wiki",
+ "config-profile-no-anon": "Bắt buộc mở tài khoản",
+ "config-profile-private": "Wiki riêng tư",
+ "config-license": "Bản quyền và giấy phép:",
+ "config-license-none": "Không hiển thị giấy phép ở chân trang",
+ "config-license-cc-by-sa": "Creative Commons Ghi công–Chia sẻ tương tự",
+ "config-license-cc-by": "Creative Commons Ghi công",
+ "config-license-cc-by-nc-sa": "Creative Commons Ghi công–Phi thương mại–Chia sẻ tương tự",
+ "config-license-cc-0": "Creative Commons CC0 (phạm vi công cộng)",
+ "config-license-gfdl": "Giấy pháp Tài liệu Tự do GNU 1.3 trở lên",
+ "config-license-pd": "Phạm vi công cộng",
+ "config-license-cc-choose": "Chọn một giấy phép Creative Commons tùy biến",
+ "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-email-user": "Cho phép người dùng gửi thư điện tử cho người dùng khác",
+ "config-email-usertalk": "Gửi thư thông báo về tin nhắn mới",
+ "config-email-watchlist": "Gửi thư thông báo về bài theo dõi",
+ "config-email-auth": "Xác minh qua thư điện tử",
+ "config-email-sender": "Địa chỉ thư điện tử trả lại:",
+ "config-upload-settings": "Hình ảnh và tập tin tải lên",
+ "config-upload-enable": "Cho phép tải lên tập tin",
+ "config-upload-deleted": "Thư mục chứa các tập tin đã xóa:",
+ "config-logo": "URL biểu trưng:",
+ "config-instantcommons": "Kích hoạt Instant Commons",
+ "config-cc-again": "Chọn một lần nữa…",
+ "config-cc-not-chosen": "Chọn một giấy phép Creative Commons và bấm “Tiếp tục”.",
+ "config-advanced-settings": "Thiết lập nâng cao",
+ "config-cache-options": "Thiết lập bộ nhớ đệm đối tượng:",
+ "config-cache-accel": "Bộ nhớ đệm đối tượng PHP (APC, XCache, hoặc WinCache)",
+ "config-cache-memcached": "Sử dụng Memcached (cần thiết lập và cấu hình thêm)",
+ "config-memcached-servers": "Máy chủ Memcached:",
+ "config-memcache-needservers": "Bạn đã chọn Memcached là loại bộ nhớ đệm nhưng không định rõ máy chủ nào.",
+ "config-memcache-badip": "Bạn đã nhập một địa chỉ IP không hợp lệ cho Memcached: $1.",
+ "config-memcache-badport": "Số cổng Memcached phải từ $1 đến $2.",
+ "config-extensions": "Phần mở rộng",
+ "config-install-step-done": "hoàn tất",
+ "config-install-step-failed": "thất bại",
+ "config-install-extensions": "Đang bao gồm phần mở rộng",
+ "config-install-database": "Đang thiết lập cơ sở dữ liệu",
+ "config-install-schema": "Đang tạo giản đồ",
+ "config-install-pg-schema-not-exist": "Lược đồ PostgreSQL không tồn tại.",
+ "config-install-pg-schema-failed": "Thất bại khi tạo các bảng.\nHãy chắc chắn rằng người dùng “$1” có thể ghi vào giản đồ “$2”.",
+ "config-install-pg-plpgsql": "Tìm ngôn ngữ PL/pgSQL",
+ "config-pg-no-plpgsql": "Bạn cần phải cài đặt ngôn ngữ PL/pgSQL vào cơ sở dữ liệu $1",
+ "config-install-user": "Đang tạo người dùng trên cơ sở dữ liệu",
+ "config-install-user-alreadyexists": "Người dùng “$1” đã tồn tại",
+ "config-install-user-create-failed": "Thất bại khi tạo người dùng “$1”: $2",
+ "config-install-user-grant-failed": "Thất bại khi cấp quyền cho người dùng “$1”: $2",
+ "config-install-user-missing": "Người dùng chỉ định “$1” không tồn tại.",
+ "config-install-user-missing-create": "Người dùng chỉ định “$1” không tồn tại.\nKiểm hộp “mở tài khoản” ở dưới để tạo ra nó.",
+ "config-install-tables": "Đang tạo các bảng",
+ "config-install-tables-exist": "'''Cảnh báo:''' Các bảng MediaWiki hình như đã tồn tại.\nĐã bỏ qua việc tạo ra các bảng.",
+ "config-install-tables-failed": "'''Lỗi:''' Thất bại khi tạo các bảng với lỗi sau: $1",
+ "config-install-interwiki": "Đang xây dựng bảng liên wiki mặc định",
+ "config-install-interwiki-list": "Không thể đọc tập tin <code>interwiki.list</code>.",
+ "config-install-interwiki-exists": "'''Cảnh báo:''' Hình như đã có mục trong bảng liên wiki.\nĐã bỏ qua danh sách mặc định.",
+ "config-install-stats": "Đang khởi tạo các thống kê",
+ "config-install-keys": "Tạo ra các chìa khóa bí mật",
+ "config-install-sysop": "Đang mở tài khoản người dùng bảo quản viên",
+ "config-install-subscribe-fail": "Không thể theo dõi mediawiki-announce: $1",
+ "config-install-subscribe-notpossible": "cURL không được cài đặt và <code>allow_url_fopen</code> không có sẵn.",
+ "config-install-mainpage": "Đang tạo trang đầu với nội dung mặc định",
+ "config-install-extension-tables": "Đang tạo bảng cho các phần mở rộng được kích hoạt",
+ "config-install-mainpage-failed": "Không thể chèn trang đầu: $1",
+ "config-download-localsettings": "Tải về <code>LocalSettings.php</code>",
+ "config-help": "Trợ giúp",
+ "config-nofile": "Không tìm thấy tập tin “$1”. Nó có phải bị xóa không?",
+ "mainpagetext": "'''MediaWiki đã được cài đặt thành công.'''",
+ "mainpagedocfooter": "Xin đọc [//meta.wikimedia.org/wiki/Help:Contents Hướng dẫn sử dụng] để biết thêm thông tin về cách sử dụng phần mềm wiki.\n\n== Để bắt đầu ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Danh sách các thiết lập cấu hình]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Các câu hỏi thường gặp MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Danh sách gửi thư về việc phát hành MediaWiki]"
+}
diff --git a/includes/installer/i18n/vo.json b/includes/installer/i18n/vo.json
new file mode 100644
index 00000000..62d50402
--- /dev/null
+++ b/includes/installer/i18n/vo.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''El MediaWiki pestiton benosekiko.'''",
+ "mainpagedocfooter": "Konsultolös [//meta.wikimedia.org/wiki/Help:Contents Gebanageidian] ad tuvön nünis dö geb programema vükik.\n\n== Nüdugot ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Parametalised]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki: SSP]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Potalised tefü fomams nulik ela MediaWiki]"
+}
diff --git a/includes/installer/i18n/vro.json b/includes/installer/i18n/vro.json
new file mode 100644
index 00000000..39acc028
--- /dev/null
+++ b/includes/installer/i18n/vro.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki tarkvara paika säet.'''",
+ "mainpagedocfooter": "Vikitarkvara pruukmisõ kotsilõ loeq mano:\n* [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide MediaWiki pruukmisoppus (inglüse keelen)].\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Säädmiisi oppus (inglüse keelen)]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki kõgõ küsütümbäq küsümiseq (inglüse keelen)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postilist, minka andas teedäq MediaWiki vahtsist kujõst]."
+}
diff --git a/includes/installer/i18n/wa.json b/includes/installer/i18n/wa.json
new file mode 100644
index 00000000..a201132b
--- /dev/null
+++ b/includes/installer/i18n/wa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Srtxg"
+ ]
+ },
+ "mainpagetext": "'''Li programe MediaWiki a stî astalé a l' idêye.'''"
+}
diff --git a/includes/installer/i18n/war.json b/includes/installer/i18n/war.json
new file mode 100644
index 00000000..6cde1d82
--- /dev/null
+++ b/includes/installer/i18n/war.json
@@ -0,0 +1,72 @@
+{
+ "@metadata": {
+ "authors": [
+ "Harvzsf",
+ "JinJian"
+ ]
+ },
+ "config-desc": "An pan-installar han MediaWiki",
+ "config-title": "MediaWiki $1 nga pag-installar",
+ "config-information": "Impormasyon",
+ "config-localsettings-upgrade": "Mayda <code>LocalSettings.php</code> nga paypay nga nabilngan. Basi ma-upgrade ini nga pag-installar, alayon pagbutáng han value han <code>$wgUpgradeKey</code> ha kahon ha ubós. Mabibilngan mo ini ha <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Mayda <code>LocalSettings.php</code> nga paypay nga nabilngan. Basi ma-upgrade ini nga pag-installar, alayon pagpadalagan lugod han <code>update.php</code>",
+ "config-upgrade-key-missing": "Mayda daan na ng gin-installar nga MediaWiki nga nabilngan.\nBasi ma-upgrade ini nga pag-instalar, alayon pagbutang han nahasunod nga linya ha ubós han imo <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-connection-error": "May-ada pagsayop an nahitabo han pagpapakabit ngada ha database nga gingagamitan hin mga kamumutangan nga dapat unta ginpapatuman han <code>LocalSettings.php</code>. Alayon ayda ini nga mga kamumutangan ngan utrohon nala.\n\n$1",
+ "config-your-language": "Imo pinulongán",
+ "config-wiki-language": "Pinulongán han wiki",
+ "config-wiki-language-help": "Pilía an pinulongán nga kauróg igsúsurat hit wiki",
+ "config-back": "Bálik",
+ "config-continue": "Padayon",
+ "config-page-language": "Pinulongán",
+ "config-page-dbconnect": "Igsumpay ha database",
+ "config-page-name": "Ngaran",
+ "config-page-readme": "Basaha ako",
+ "config-page-copying": "Nagkokopya",
+ "config-restart": "Oo, utroha patikanga",
+ "config-welcome": "=== Mga pagpanginano panlibong ===\nMagkakamay-ada yano nga panginano para masabtan kun ini nga libong in naaangay para hiton pagtataod hiton MediaWiki. Hinumdomi iton paglakip hinin nga impormasyon kun karuyag mo mangaro hin suporta kun paunan-on humanon an pagtataod.",
+ "config-no-db": "Diri nakakabiling hin naaangay nga database driver! Kinahanglan mo magtaod hin uska database driver para han PHP. An masunod nga mga klase hin database in ginsusuporatahan: $1.\n\nKun ikaw mismo an nag-compile han PHP, kinahanglan ma-reconfigure iton nga para maapandar an database client, pananglitan, han paggamit han <code>./configure --with-mysqli</code>.\nKun gintaod mo an PHP tikang ha uska Debian o Ubuntu nga pakete, kinahanglan nimo magtaod liwat, pananglitan, hiton an <code>php5-mysql</code> nga pakete.",
+ "config-pcre-old": "<strong>Nangangarat-an:</strong> Nagkikinahanglan hin PCRE $1 o mas urhi pa.\nAn imo PHP nga binaryo in nakasumpay hin PCRE $2. [https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE More information].",
+ "config-site-name": "Ngaran han wiki:",
+ "config-ns-generic": "Proyekto",
+ "config-ns-site-name": "Kapareho han wiki nga ngaran: $1",
+ "config-admin-name": "Imo ngaran-gumaramit:",
+ "config-admin-password": "Tigaman panulod:",
+ "config-admin-password-confirm": "Tigaman panulod utro:",
+ "config-admin-name-blank": "Pagbutang hin ngaran-gumaramit hit magdudumara.",
+ "config-admin-password-blank": "Pagbutang hin tigaman-panulod para hit akawnt han magdudumara.",
+ "config-admin-password-mismatch": "An duha nga mga tigaman-panulod nga imo ginbutang in diri magkaparehas.",
+ "config-admin-email": "Address hit Email:",
+ "config-admin-error-bademail": "Nagbutang ka hin diri puydi nga address hit email.",
+ "config-almost-done": "Harani ka na mahuman!\nPuydi nim ilaktaw an nasasalin nga configuration ngan ig-install an wiki yana dayon.",
+ "config-optional-continue": "Pakyana pa hin durudamo nga mga pakiana.",
+ "config-profile": "Profile han mga katungod han gumaramit:",
+ "config-profile-no-anon": "Kinahanglan an paghimo hin akawnt",
+ "config-profile-fishbowl": "Otorisado nga mga editor la",
+ "config-profile-private": "Pribado nga wiki",
+ "config-license-pd": "Dominyo Publiko",
+ "config-email-user": "Igpaandar an gumaramit-ha-gumaramit nga email",
+ "config-email-user-help": "Igtugot an ngatanan nga mga gumaramit nga magpadangat hin email ha tagsa-tagsa kun ira ginpaandar ini ha ira karuyagon.",
+ "config-email-usertalk": "Igpaandar an pagpasabot ha pakli han hiruhimangraw han gumaramit",
+ "config-cc-again": "Pilii utro...",
+ "config-extensions": "Mga panugtong",
+ "config-install-step-done": "human na",
+ "config-install-step-failed": "pakyas",
+ "config-install-extensions": "Lakip an mga panugtong",
+ "config-install-schema": "Naghihimo hin iskima",
+ "config-install-pg-schema-not-exist": "Waray natatad-an nga iskima PostgreSQL.",
+ "config-install-pg-commit": "Nagsasaad hin pagbabag-o",
+ "config-install-user": "Naghihimo hin gumaramit hit database",
+ "config-install-user-alreadyexists": "May-ada na gumaramit nga \"$1\"",
+ "config-install-user-create-failed": "Pakyas an paghimo hin gumaramit nga \"$1\": $2",
+ "config-install-user-grant-failed": "Pakyas an paghatag hin pagtugot han gumaramit \"$1\": $2",
+ "config-install-tables": "Naghihimo hin mga table",
+ "config-install-interwiki-list": "Diri nakakabasa han paypay <code>interwiki.list</code>.",
+ "config-install-sysop": "Naghihimo hin akawnt han gumaramit han magdudurama",
+ "config-install-extension-tables": "Naghihimo hin mga table han pinaandar nga mga panugtong",
+ "config-install-mainpage-failed": "Diri nakakasuksok hin panguna nga pakli: $1",
+ "config-download-localsettings": "Ikarga-paubos an <code>LocalSettings.php</code>",
+ "config-help": "buligi",
+ "config-nofile": "An paypay nga \"$1\" in diri nabibilngan. Ginpara na ini?",
+ "mainpagetext": "'''Malinamposon an pag-instalar han MediaWiki.'''",
+ "mainpagedocfooter": "Kitaa an [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para hin impormasyon ha paggamit han wiki nga softweyr.\n\n== Ha pagtikang==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/wo.json b/includes/installer/i18n/wo.json
new file mode 100644
index 00000000..5cee2765
--- /dev/null
+++ b/includes/installer/i18n/wo.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''Campug MediaWiki gi sotti na . '''",
+ "mainpagedocfooter": "Saytul [//meta.wikimedia.org/wiki/Help:Contents Gindikaayu jëfandikukat bi] ngir yeneeni xibaar ci jëfandiku gu tëriin gi.\n\n== Tambali ak MediaWiki ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Limu jumtukaayi kocc-koccal gi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Limu waxtaan ci liy-génn ci MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/wuu.json b/includes/installer/i18n/wuu.json
new file mode 100644
index 00000000..01052d3b
--- /dev/null
+++ b/includes/installer/i18n/wuu.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Wu-chinese.com"
+ ]
+ },
+ "mainpagetext": "'''MediaWiki安装成功哉!'''",
+ "mainpagedocfooter": "请访问[//meta.wikimedia.org/wiki/Help:Contents 用户手册]以获得使用此维基软件个信息!\n\n== 入门 ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki 配置设置列表]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki 常见问题解答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 发布邮件列表]"
+}
diff --git a/includes/installer/i18n/xal.json b/includes/installer/i18n/xal.json
new file mode 100644
index 00000000..14ea95bd
--- /dev/null
+++ b/includes/installer/i18n/xal.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Huuchin"
+ ]
+ },
+ "mainpagetext": "Йовудта Mediawiki гүүлһүдә тәвллһн.'''",
+ "mainpagedocfooter": "Тер бики закллһна теткүл ю кеһәд олзлх туск [//meta.wikimedia.org/wiki/Help:Contents көтлвр] дастн.\n\n== Туста заавр ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Көгүдә бүрткл]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki туск ЮмБи]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki шинҗллһнә бүрткл]"
+}
diff --git a/includes/installer/i18n/yi.json b/includes/installer/i18n/yi.json
new file mode 100644
index 00000000..47749b74
--- /dev/null
+++ b/includes/installer/i18n/yi.json
@@ -0,0 +1,58 @@
+{
+ "@metadata": {
+ "authors": [
+ "פוילישער",
+ "පසිඳු කාවින්ද"
+ ]
+ },
+ "config-desc": "דער אינסטאלירער פאר מעדיעוויקי",
+ "config-title": "מעדיעוויקי $1 אינסטאלירונג",
+ "config-information": "אינפֿארמאציע",
+ "config-localsettings-badkey": "דעם שליסל וואס איר האט אײַנגעגעבן איז פאלש.",
+ "config-session-error": "פֿעלער ביים אנהייבן סעסיע:$1",
+ "config-your-language": "אײַער שפראך:",
+ "config-your-language-help": "קלויבט א שפראך צו ניצן ביים אינסטאלירונג פראצעס.",
+ "config-wiki-language": "ווקי שפראך:",
+ "config-wiki-language-help": "קלויבט אויס די שפראך מיט וואס בעיקר מען וועט שרײַבן די וויקי.",
+ "config-back": "→ צוריק",
+ "config-continue": "פֿארזעצן ←",
+ "config-page-language": "שפראַך",
+ "config-page-welcome": "ברוכים הבאים צו מעדיעוויקי!",
+ "config-page-dbconnect": "פארבינדן צו דאטנבאזע",
+ "config-page-upgrade": "ראנג־העכערונג פון פארהאנע אינסטאלאציע",
+ "config-page-dbsettings": "דאטנבאזע איינשטעלונגען",
+ "config-page-name": "נאָמען",
+ "config-page-options": "ברירות",
+ "config-page-install": "אינסטאלירן",
+ "config-page-complete": "פארטיק!",
+ "config-page-restart": "ווידער־אנהייבן אינסטאלאציע",
+ "config-page-readme": "לייענט מיך",
+ "config-page-releasenotes": "ווערסיע־הערות",
+ "config-page-copying": "קאפיע",
+ "config-page-upgradedoc": "ראנג־העכערן",
+ "config-page-existingwiki": "עקזיסטירנדע וויקי",
+ "config-help-restart": "צי ווילט איר אפראמען די גארע געשפייכלערטע דאטן וואס איר האט אײַנגעגעבן און ווידער אנהייבן דעם אינסטאלאציע־פראצעס?",
+ "config-restart": "יא, ווידעראמאל אנהייבן",
+ "config-env-php": "PHP $1 איז אינצטאלירט.",
+ "config-apc": "[http://www.php.net/apc APC] איז אינסטאלירט",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] איז אינסטאלירט",
+ "config-diff3-bad": "GNU diff3 נישט געטראפן.",
+ "config-db-type": "דאטנבאזע טיפ:",
+ "config-db-host-oracle": "דאטנבאזע־TNS:",
+ "config-db-name": "דאטנבאזע נאָמען:",
+ "config-db-username": "דאטנבאזע באניצער־נאָמען:",
+ "config-project-namespace": "פראיעקט נאָמענטייל:",
+ "config-ns-generic": "פראיעקט",
+ "config-admin-name": "אײַער באַניצער־נאָמען:",
+ "config-admin-password": "פאַסווארט:",
+ "config-admin-password-mismatch": "די צוויי פאסוועטרט איר האט איינגעגעבן שטימען נישט.",
+ "config-admin-email": "בליצפּאָסט אַדרעס:",
+ "config-install-tables": "שאפן טאבעלעס",
+ "config-install-tables-exist": "'''ווארענונג''': זעט אויס אז די מעדיעוויקי טאבעלעס עקזיסטירן שוין.\nאיבערהיפן שאפֿן.",
+ "config-install-mainpage-failed": "מ'האט נישט געקענט אריינגעבן הויפטבלאט: $1",
+ "config-download-localsettings": "אראפלאדן <code>LocalSettings.php</code>",
+ "config-help": "הילף",
+ "config-nofile": "מ'האט נישט געקענט טרעפן די טעקע \"$1\". צי האט מען זי אויסגעמעקט?",
+ "mainpagetext": "'''מעדיעוויקי אינסטאלירט מיט דערפאלג.'''",
+ "mainpagedocfooter": "גיט זיך אן עצה מיט [//meta.wikimedia.org/wiki/Help:Contents באניצער'ס וועגווײַזער] פֿאר אינפֿארמאציע וויאזוי זיך באנוצן מיט וויקי ווייכוואַרג.\n\n== נוצליכע וועבלינקען פֿאַר אנהייבערס ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings רשימה פון קאנפֿיגוראציעס]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ אפֿט געפֿרעגטע שאלות]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce מעדיעוויקי באפֿרײַאונג פאסטליסטע]* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources איבערזעצן מעדיעוויקי אין אײַער שפראך]"
+}
diff --git a/includes/installer/i18n/yo.json b/includes/installer/i18n/yo.json
new file mode 100644
index 00000000..84f38483
--- /dev/null
+++ b/includes/installer/i18n/yo.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Demmy"
+ ]
+ },
+ "config-your-language": "Èdè yín:",
+ "config-wiki-language": "Èdè wiki:",
+ "config-back": "← Ẹ̀yìn",
+ "config-continue": "Ìtẹ̀síwájú →",
+ "config-page-language": "Èdè",
+ "config-page-welcome": "Ẹ kú àbò sí MediaWiki!",
+ "config-page-dbconnect": "Ìjápọ̀ mọ́ ibùdó dátà",
+ "config-page-name": "Orúkọ",
+ "config-db-type": "Irú ibùdó dátà:",
+ "config-db-host": "Agbàlejò ibùdó dátà:",
+ "mainpagetext": "'''MediaWiki ti jẹ́ gbígbékọ́sínú láyọrísírere.'''",
+ "mainpagedocfooter": "Ẹ ṣàbẹ̀wò sí [//meta.wikimedia.org/wiki/Help:Contents User's Guide] fún ìfitólétí nípa líló atòlànà wíkì.\n\n== Láti bẹ̀rẹ̀ ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]"
+}
diff --git a/includes/installer/i18n/yue.json b/includes/installer/i18n/yue.json
new file mode 100644
index 00000000..bc8df1a6
--- /dev/null
+++ b/includes/installer/i18n/yue.json
@@ -0,0 +1,5 @@
+{
+ "@metadata": [],
+ "mainpagetext": "'''MediaWiki已經裝好。'''",
+ "mainpagedocfooter": "參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶指引](英),裏面有資料講點用wiki軟件。\n\n==開始使用==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 配置設定清單](英)\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki 常見問題](英)\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件名單](英)"
+}
diff --git a/includes/installer/i18n/zea.json b/includes/installer/i18n/zea.json
new file mode 100644
index 00000000..d545ed66
--- /dev/null
+++ b/includes/installer/i18n/zea.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Seb35"
+ ]
+ },
+ "mainpagetext": "'''De installaotie van MediaWiki is geslaegd.'''",
+ "mainpagedocfooter": "Raedpleeg de [//meta.wikimedia.org/wiki/Help:Contents Inhoudsopgaeve andleidieng] voe informatie over 't gebruuk van de wikisoftware.\n\n== Meer ulpe over MediaWiki ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lieste mie instelliengen]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veehestelde vraehen (FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailienglieste voe ankondigiengen van nieuwe versies]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+}
diff --git a/includes/installer/i18n/zh-hans.json b/includes/installer/i18n/zh-hans.json
new file mode 100644
index 00000000..f4dbdd78
--- /dev/null
+++ b/includes/installer/i18n/zh-hans.json
@@ -0,0 +1,345 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anthony Fok",
+ "Cwek",
+ "Hydra",
+ "Hzy980512",
+ "Liangent",
+ "Makecat",
+ "PhiLiP",
+ "Xiaomingyan",
+ "Yfdyh000",
+ "乌拉跨氪",
+ "阿pp",
+ "아라",
+ "Byfserag",
+ "Hudafu",
+ "Liuxinyu970226",
+ "Qiyue2001",
+ "Kuailong",
+ "Zjzengdongyang",
+ "Mywood",
+ "Impersonator 1",
+ "Fengchao"
+ ]
+ },
+ "config-desc": "MediaWiki安装程序",
+ "config-title": "MediaWiki $1配置",
+ "config-information": "信息",
+ "config-localsettings-upgrade": "已检测到<code>LocalSettings.php</code>文件。要升级该配置,请在下面的框中输入<code>$wgUpgradeKey</code>的值。您可以在<code>LocalSettings.php</code>中找到它。",
+ "config-localsettings-cli-upgrade": "已检测到<code>LocalSettings.php</code>文件。要升级该配置,请直接运行<code>update.php</code>。",
+ "config-localsettings-key": "升级密钥:",
+ "config-localsettings-badkey": "您提供的密钥不正确。",
+ "config-upgrade-key-missing": "检测到MediaWiki的配置已经存在。若要升级该配置,请将下面一行文本添加到<code>LocalSettings.php</code>的底部:\n\n$1",
+ "config-localsettings-incomplete": "当前的<code>LocalSettings.php</code>可能并不完整,因为变量$1没有设置。请在<code>LocalSettings.php</code>设置该变量,并单击“{{int:Config-continue}}”。",
+ "config-localsettings-connection-error": "在使用<code>LocalSettings.php</code>中指定的设置连接数据库时发生错误。请修复相应设置并重试。\n\n$1",
+ "config-session-error": "启动会话出错:$1",
+ "config-session-expired": "您的会话数据可能已经过期,当前会话的使用期限被设定为$1。您可以在php.ini中设置<code>session.gc_maxlifetime</code>来延长此期限,并重新启动本配置程序。",
+ "config-no-session": "您的会话数据丢失了!请检查php.ini并确保<code>session.save_path</code>被设置为适当的目录。",
+ "config-your-language": "您使用的语言:",
+ "config-your-language-help": "选择在安装过程中使用的语言。",
+ "config-wiki-language": "Wiki使用的语言:",
+ "config-wiki-language-help": "选择将要安装的wiki在多数情况下使用的语言。",
+ "config-back": "← 后退",
+ "config-continue": "继续 →",
+ "config-page-language": "语言",
+ "config-page-welcome": "欢迎使用MediaWiki!",
+ "config-page-dbconnect": "连接到数据库",
+ "config-page-upgrade": "升级当前配置",
+ "config-page-dbsettings": "数据库设置",
+ "config-page-name": "名称",
+ "config-page-options": "选项",
+ "config-page-install": "安装",
+ "config-page-complete": "完成!",
+ "config-page-restart": "重新开始安装",
+ "config-page-readme": "自述",
+ "config-page-releasenotes": "发布说明",
+ "config-page-copying": "复制",
+ "config-page-upgradedoc": "升级",
+ "config-page-existingwiki": "现有wiki",
+ "config-help-restart": "是否要清除所有已输入且保存的数据,并重新启动安装过程吗?",
+ "config-restart": "是的,重启吧",
+ "config-welcome": "=== 环境检查 ===\n将简单检查当前环境是否适合安装MediaWiki。如果您要寻求安装过程的支持,请记得附上此信息。",
+ "config-copyright": "=== 版权和条款 ===\n\n$1\n\n本程序为自由软件;您可依据自由软件基金会所发表的GNU通用公共授权条款规定,就本程序再为发布与/或修改;无论您依据的是本授权的第二版或(您自行选择的)任一日后发行的版本。\n\n本程序是基于使用目的而加以发布,然而'''不负任何担保责任''';亦无对'''适售性'''或'''特定目的适用性'''所为的默示性担保。详情请参照GNU通用公共授权。\n\n您应已收到附随于本程序的<doclink href=\"Copying\">GNU通用公共授权的副本</doclink>;如果没有,请写信至自由软件基金会:59 Temple Place - Suite 330, Boston, Ma 02111-1307, USA,或[http://www.gnu.org/copyleft/gpl.html 在线阅读]。",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/zh-hans MediaWiki首页]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/zh-hans 用户指南]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents 管理员指南]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/zh-hans 常见问题解答]\n----\n* <doclink href=Readme>自述文件</doclink>\n* <doclink href=ReleaseNotes>发行说明</doclink>\n* <doclink href=Copying>协议副本</doclink>\n* <doclink href=UpgradeDoc>升级</doclink>",
+ "config-env-good": "环境检查已经完成。您可以安装MediaWiki。",
+ "config-env-bad": "环境检查已经完成。您不能安装MediaWiki。",
+ "config-env-php": "PHP $1已安装。",
+ "config-env-hhvm": "HHVM $1已安装。",
+ "config-unicode-using-utf8": "使用Brion Vibber的utf8_normalize.so实现Unicode正常化。",
+ "config-unicode-using-intl": "使用[http://pecl.php.net/intl intl PECL扩展程序]标准化Unicode。",
+ "config-unicode-pure-php-warning": "<strong>警告:</strong>因为尚未安装 [http://pecl.php.net/intl intl PECL 扩展]以处理 Unicode 正常化,故只能退而采用运行较慢的纯 PHP 实现的方法。\n如果您运行着一个高流量的站点,请参阅 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode 正常化]一文。",
+ "config-unicode-update-warning": "'''警告''':Unicode正常化封装器的已安装版本使用了旧版本的[http://site.icu-project.org/ ICU项目]库。如果您需要使用Unicode,请将其[//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 升级]。",
+ "config-no-db": "找不到合适的数据库驱动!您需要为PHP安装数据库驱动。目前支持以下数据库:$1。如果您是自己编译的PHP,请重新配置他与数据库客户端将其启用,诸如,使用<code>./configure --with-mysqli</code>。如果您从Debian或Ubuntu包安装了PHP,之后您仍需要安装诸如<code>php5-mysql</code>包。",
+ "config-outdated-sqlite": "'''警告''':您已安装SQLite $1,但是它的版本低于最低要求版本$2。因此您无法选择SQLite。",
+ "config-no-fts3": "'''警告''':已编译的SQLite不包含[//sqlite.org/fts3.html FTS3模块],后台搜索功能将不可用。",
+ "config-register-globals-error": "<strong>错误:PHP<code>[http://php.net/register_globals register_globals]</code>选项被启用。必须禁用它才能继续安装。</strong>关于如何禁用,参见[https://www.mediawiki.org/wiki/register_globals mediawiki.org此页]。",
+ "config-magic-quotes-gpc": "<strong>致命错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc]已启用!</strong>此选项会无法挽回的破坏输入数据。除非此选项被禁用否则您不能安装或使用MediaWiki。",
+ "config-magic-quotes-runtime": "'''毁灭性错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]已启用!'''\n此选项会无法预测地破坏输入的数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
+ "config-magic-quotes-sybase": "'''毁灭性错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase]已启用!'''\n此选项会无法预测地破坏输入的数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
+ "config-mbstring": "'''毁灭性错误:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]已启用!'''\n此选项会导致错误并不可预测地破坏数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
+ "config-safe-mode": "'''警告:'''PHP的[http://www.php.net/features.safe-mode 安全模式]已启用。它可能会导致一些问题,尤其在对文件上传和数学公式<code>math</code>的支持方面。",
+ "config-xml-bad": "缺少PHP的XML模块。MediaWiki需要使用该模块提供的函数,在当前配置下将无法工作。如果您正在使用Mandrake Linux,请安装php-xml包。",
+ "config-pcre-old": "'' 致命错误: ''需要PCRE $1 或更高版本。\n您的 PHP 二进制文件与 PCRE $2 链接。\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE 详细信息]。",
+ "config-pcre-no-utf8": "'''毁灭性错误''':PHP的PCRE模块在编译时可能没有包含PCRE_UTF8支持。MediaWiki需要UTF-8支持才能正常工作。",
+ "config-memory-raised": "PHP的内存使用上限<code>memory_limit</code>为$1,自动提升到$2。",
+ "config-memory-bad": "'''警告:'''PHP的内存使用上限<code>memory_limit</code>为$1。该设定可能过低,并导致安装失败!",
+ "config-ctype": "'''毁灭性错误''':PHP必须有[http://www.php.net/manual/en/ctype.installation.php Ctype 扩展]来支持编译。",
+ "config-iconv": "<strong>致命错误:</strong>PHP必须编译支持[http://www.php.net/manual/en/iconv.installation.php iconv拓展]。",
+ "config-json": "'''致命问题:''' PHP编译没有附带JSON支持。\n在安装MediaWiki前,你必须安装PHP JSON扩展或者[http://pecl.php.net/package/jsonc PECL jsonc]扩展。\n* PHP扩展已包含在Red Hat Enterprise Linux (CentOS) 5和6中,但必须在<code>/etc/php.ini</code>或<code>/etc/php.d/json.ini</code>中启用。\n* 部分在2013年5月后发行的Linux发行版省略了PHP扩展,而将PECL扩展打包成了<code>php5-json</code>或<code>php-pecl-jsonc</code>。",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache]已安装",
+ "config-apc": "[http://www.php.net/apc APC]已安装",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache]已安装",
+ "config-no-cache": "'''警告:'''找不到[http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache]或[http://www.iis.net/download/WinCacheForPhp WinCache],无法启用对象缓存。\nObject caching is not enabled.",
+ "config-mod-security": "'''警告''':您的服务器已启动[http://modsecurity.org/ mod_security]。若其配置错误, 会导致MediaWiki和其他软件的错误并允许用户任意发布内容。如果您遇到任何错误,请查阅[http://modsecurity.org/documentation/ mod_security文档]或联系您的客服。",
+ "config-diff3-bad": "找不到GNU diff3。",
+ "config-git": "发现Git版本控制软件:<code>$1</code>",
+ "config-git-bad": "Git版本控制软件未找到。",
+ "config-imagemagick": "已找到ImageMagick:<code>$1</code>。如果你启用了上传功能,缩略图功能也将被启用。",
+ "config-gd": "已找到内建的GD图形库。如果你启用了上传功能,缩略图功能也将被启用。",
+ "config-no-scaling": "找不到GD库或ImageMagick。缩略图功能将不可用。",
+ "config-no-uri": "'''错误:'''无法确定当前的URI。安装已中断。",
+ "config-no-cli-uri": "<strong>警告:</strong>未指定<code>--scriptpath</code>参数,使用默认值:<code>$1</code>。",
+ "config-using-server": "使用服务器名“<nowiki>$1</nowiki>”。",
+ "config-using-uri": "使用服务器URL“<nowiki>$1$2</nowiki>”。",
+ "config-uploads-not-safe": "'''警告:'''您的默认上传目录<code>$1</code>存在允许执行任意脚本的漏洞。尽管MediaWiki会对所有已上传的文件进行安全检查,但我们仍然强烈建议您在启用上传功能前[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security 关闭该安全漏洞]。",
+ "config-no-cli-uploads-check": "'''警告''':在CLI安装过程中,没有对您的默认上传目录(<code>$1</code>)进行执行任意脚本的漏洞检查。",
+ "config-brokenlibxml": "您的系统安装的PHP和libxml2版本组合存在故障,并可能在MediaWiki和其他web应用程序中造成隐藏的数据损坏。请将libxml2升级到2.7.3或以上([https://bugs.php.net/bug.php?id=45996 PHP的故障报告])。安装已中断。",
+ "config-suhosin-max-value-length": "Suhosin已经安装并将GET请求的参数长度限制在$1字节。MediaWiki的ResourceLoader部件可以在此限制下正常工作,但其性能会被降低。如果可能,请在<code>php.ini</code>中将<code>suhosin.get.max_value_length</code>设为1024或更高值,并在LocalSettings.php中将<code>$wgResourceLoaderMaxQueryLength</code>设为同一值。",
+ "config-db-type": "数据库类型:",
+ "config-db-host": "数据库主机:",
+ "config-db-host-help": "如果您的数据库在别的服务器上,请在这里输入它的域名或IP地址。\n\n如果您在使用共享网站套餐,您的网站商应该已在他们的控制面板中给您数据库信息了。\n\n如果您在Windows中安装并且使用MySQL,“localhost”可能无效。如果确实无效,请输入“127.0.0.1”作为IP地址。\n\n如果您在使用PostgreSQL,并且要用Unix socket来连接,请留空。",
+ "config-db-host-oracle": "数据库透明网络底层(TNS):",
+ "config-db-host-oracle-help": "请输入合法的[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 本地连接名],并确保tnsnames.ora文件对本安装程序可见。<br />如果您使用的客户端库为10g或更新的版本,您还可以使用[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 简单连接名方法](easy connect naming method)。",
+ "config-db-wiki-settings": "标识本wiki",
+ "config-db-name": "数据库名称:",
+ "config-db-name-help": "请输入一个可以标识您的wiki的名称。请勿使用空格。\n\n如果您正在使用共享web主机,您的主机提供商或会给您指定一个数据库名称,或会让您通过控制面板创建数据库。",
+ "config-db-name-oracle": "数据库模式:",
+ "config-db-account-oracle-warn": "现有三种已支持方案可以将Oracle设置为后端数据库:\n\n如果您希望在安装过程中创建数据库帐户,请为安装程序提供具有SYSDBA角色的数据库帐户,并为web访问帐户指定所需身份证明;否则您可以手动创建web访问的账户并仅须提供该帐户(确保帐户已有创建方案对象(schema object)的所需权限);或提供两个不同的帐户,其一具有创建权限,另一则被限制为web访问。\n\n具有所需权限账户的创建脚本存放于本程序的“maintenance/oracle/”目录下。请注意,使用受限制的帐户将禁用默认帐户的所有维护性功能。",
+ "config-db-install-account": "用于安装的用户帐号",
+ "config-db-username": "数据库用户名:",
+ "config-db-password": "数据库密码:",
+ "config-db-password-empty": "请为新数据库用户$1输入密码。尽管您可以创建不使用密码的用户,但这样做并不安全。",
+ "config-db-username-empty": "您必须输入用于“{{int:config-db-username}}”的值。",
+ "config-db-install-username": "请输入在安装过程中用于连接数据库的用户名。请勿输入MediaWiki帐号的用户名,请输入您数据库的用户名。",
+ "config-db-install-password": "请输入在安装过程中用于连接数据库的密码。请勿输入MediaWiki帐号的密码,请输入您数据库的密码。",
+ "config-db-install-help": "请输入在安装过程中用于连接数据库的用户名和密码。",
+ "config-db-account-lock": "在普通操作中使用相同的用户名和密码",
+ "config-db-wiki-account": "用于普通操作的用户帐号",
+ "config-db-wiki-help": "输入在普通的wiki操作中(安装完成后)将用于连接数据库的用户名和密码。如果该帐号并不存在,而安装帐号具有足够的权限,该用户帐号会被自动创建,并被赋予足以运行此wiki的最低权限。",
+ "config-db-prefix": "数据库表前缀:",
+ "config-db-prefix-help": "如果您需要在多个wiki之间(或在MediaWiki与其他web应用程序之间)共享一个数据库,您可以通过添加前缀的方式来避免出现表名称的冲突。请勿使用空格。\n\n此字段通常可留空。",
+ "config-db-charset": "数据库字符集",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 二进制",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-charset-mysql4": "MySQL 4.0 UTF-8(向后兼容)",
+ "config-charset-help": "'''警告:'''如果您在MySQL 4.1+中使用'''向后兼容的UTF-8'''字符集,并在之后使用<code>mysqldump</code>备份了数据库,则可能损坏所有的非ASCII字符,从而不可逆地破坏您的备份!\n\n在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。\n\n在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[//zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
+ "config-mysql-old": "需要MySQL $1或更新的版本,您的版本为$2。",
+ "config-db-port": "数据库端口:",
+ "config-db-schema": "MediaWiki的数据库模式",
+ "config-db-schema-help": "此数据库模式通常是正确的,请在有明确需求时才改动之。",
+ "config-pg-test-error": "无法连接到数据库'''$1''':$2",
+ "config-sqlite-dir": "SQLite数据目录:",
+ "config-sqlite-dir-help": "SQLite会将所有的数据存储于单一文件中。\n\n您所提供的目录必须在安装过程中对网页服务器可写。\n\n该目录'''不应'''允许通过web访问,因此我们不会将数据文件和PHP文件放在一起。\n\n安装程序在创建数据文件时,亦会在相同目录下创建<code>.htaccess</code>以控制权限。假若此等控制失效,则可能会将您的数据文件暴露于公共空间,让他人可以获取用户数据(电子邮件地址、杂凑后的密码)、被删除的版本以及其他在wiki上被限制访问的数据。\n\n请考虑将数据库统一放置在某处,如<code>/var/lib/mediawiki/yourwiki</code>下。",
+ "config-oracle-def-ts": "默认表空间:",
+ "config-oracle-temp-ts": "临时表空间:",
+ "config-type-mysql": "MySQL(或兼容程序)",
+ "config-type-mssql": "微软SQL服务器",
+ "config-support-info": "MediaWiki支持以下数据库系统:\n\n$1\n\n如果您在下面列出的数据库系统中没有找到您希望使用的系统,请根据上方链向的指引启用支持。",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL]是MediaWiki的首选数据库,对它的支持最为完备([http://www.php.net/manual/en/mysql.installation.php 如何将对MySQL的支持编译进PHP中])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL]是一种流行的开源数据库系统,可作为MySQL的替代([http://www.php.net/manual/en/pgsql.installation.php 如何将对PostgreSQL的支持编译进PHP中])。本程序中可能依然存在一些小而明显的错误,因此并不建议在生产环境中使用该数据库系统。",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite]是一种轻量级的数据库系统,能被良好地支持。([http://www.php.net/manual/en/pdo.installation.php 如何将对SQLite的支持编译进PHP中],须使用PDO)",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle]是一种商用企业级的数据库。([http://www.php.net/manual/en/oci8.installation.php 如何将对OCI8的支持编译进PHP中])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server]是一个适用于Windows的商业性企业数据库。([http://www.php.net/manual/en/sqlsrv.installation.php 如何编译带有SQLSRV支持的PHP])",
+ "config-header-mysql": "MySQL设置",
+ "config-header-postgres": "PostgreSQL设置",
+ "config-header-sqlite": "SQLite设置",
+ "config-header-oracle": "Oracle设置",
+ "config-header-mssql": "Microsoft SQL Server设置",
+ "config-invalid-db-type": "无效的数据库类型",
+ "config-missing-db-name": "您必须为“{{int:config-db-name}}”输入内容。",
+ "config-missing-db-host": "您必须为“{{int:config-db-host}}”输入内容。",
+ "config-missing-db-server-oracle": "您必须为“{{int:config-db-host-oracle}}”输入内容。",
+ "config-invalid-db-server-oracle": "无效的数据库TNS“$1”。请使用“TNS 名称”或者一个“轻松连接”字符串([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle 命名方法])",
+ "config-invalid-db-name": "无效的数据库名称“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)、下划线(_)和连字号(-)。",
+ "config-invalid-db-prefix": "无效的数据库前缀“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)、下划线(_)和连字号(-)。",
+ "config-connection-error": "$1。\n\n请检查下列的主机、用户名和密码设置后重试。",
+ "config-invalid-schema": "无效的MediaWiki数据库模式“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)和下划线(_)。",
+ "config-db-sys-create-oracle": "安装程序仅支持使用SYSDBA帐户创建新帐户。",
+ "config-db-sys-user-exists-oracle": "用户帐户“$1”已经存在。SYSDBA仅可用于创建新帐户!",
+ "config-postgres-old": "需要PostgreSQL $1或更新的版本,您的版本为$2。",
+ "config-mssql-old": "需要 Microsoft SQL Server $1 或者更高版本。您的版本是 $2。",
+ "config-sqlite-name-help": "请为您的wiki指定一个用于标识的名称。请勿使用空格或连字号,该名称将被用作SQLite的数据文件名。",
+ "config-sqlite-parent-unwritable-group": "由于父目录<code><nowiki>$2</nowiki></code>对网页服务器不可写,无法创建数据目录<code><nowiki>$1</nowiki></code>。\n\n安装程序已确定您网页服务器所使用的用户。请将<code><nowiki>$3</nowiki></code>目录设为对该用户可写以继续安装过程。在Unix/Linux系统中,您可以逐行输入下列命令:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "由于父目录<code><nowiki>$2</nowiki></code>对网页服务器不可写,无法创建数据目录<code><nowiki>$1</nowiki></code>。\n\n安装程序无法确定您网页服务器所使用的用户。请将<code><nowiki>$3</nowiki></code>目录设为全局可写(对所有用户)以继续安装过程。在Unix/Linux系统中,您可以逐行输入下列命令:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "创建数据目录“$1”时发生错误。请检查路径后重试。",
+ "config-sqlite-dir-unwritable": "无法写入目录“$1”。请修改该目录的权限,使其对网页服务器可写后重试。",
+ "config-sqlite-connection-error": "$1。\n\n请检查下列的数据目录和数据库名称后重试。",
+ "config-sqlite-readonly": "文件<code>$1</code>不可写。",
+ "config-sqlite-cant-create-db": "无法创建数据文件<code>$1</code>。",
+ "config-sqlite-fts3-downgrade": "PHP缺少FTS3支持,正在降级数据表",
+ "config-can-upgrade": "在数据库中发现了MediaWiki的数据表。要将它们升级至MediaWiki $1,请点击'''继续'''。",
+ "config-upgrade-done": "升级完成。\n\n现在您可以[$1 开始使用您的wiki]了。\n\n如果您需要重新生成<code>LocalSettings.php</code>文件,请点击下面的按钮。除非您的wiki出现了问题,我们'''不推荐'''您执行此操作。",
+ "config-upgrade-done-no-regenerate": "升级完成。\n\n现在您可以[$1 开始使用您的wiki]了。",
+ "config-regenerate": "重新生成LocalSettings.php →",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code>语句执行失败!",
+ "config-unknown-collation": "'''警告:'''数据库使用了无法识别的整理。",
+ "config-db-web-account": "供网页访问使用的数据库帐号",
+ "config-db-web-help": "请指定在wiki执行普通操作时,网页服务器用于连接数据库服务器的用户名和密码。",
+ "config-db-web-account-same": "使用和安装程序相同的帐号",
+ "config-db-web-create": "如果帐号不存在,则自动创建",
+ "config-db-web-no-create-privs": "您指定给安装程序的帐号缺少创建帐号的权限,因此您指定的帐号必须已经存在。",
+ "config-mysql-engine": "存储引擎:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "'''警告''':您选择了MyISAM作为MySQL的存储引擎,MediaWiki并不推荐您这么做,因为:\n* 它仅能通过表锁定来勉强支持并发\n* 与其他引擎相比,它更容易被损坏\n* MediaWiki代码库并不总会去处理MyISAM\n\n如果您的MySQL程序支持InnoDB,我们高度推荐您使用该引擎替代MyISAM。\n如果您的MySQL程序不支持InnoDB,请考虑升级。",
+ "config-mysql-only-myisam-dep": "<strong>警告:</strong>MyISAM是MySQL在此机器上唯一可用的存储引擎,但它不适合用于MediaWiki,因为:\n*因为表级锁定,它几乎不支持并发。\n*它相比其他引擎更容易损坏。\n*MediaWiki代码不能总是按照预期操作MyISAM。\n\n你的MySQL不支持InnoDB,是时候升级了。",
+ "config-mysql-engine-help": "'''InnoDB'''通常是最佳选项,因为它对并发操作有着良好的支持。\n\n'''MyISAM'''在单用户或只读环境下可能会有更快的性能表现。但MyISAM数据库出错的概率一般要大于InnoDB数据库。",
+ "config-mysql-charset": "数据库字符集:",
+ "config-mysql-binary": "二进制",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。\n\n在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[//zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
+ "config-mssql-auth": "身份验证类型:",
+ "config-mssql-install-auth": "选择安装过程中链接数据库时将采用的身份验证方式。\n如果您选择“{{int:config-mssql-windowsauth}}”,将使用运行服务器的用户的身份凭据。",
+ "config-mssql-web-auth": "选择Web服务器在通常wiki操作期间用来连接数据库服务器的身份验证方式。\n如果您选择“{{int:config-mssql-windowsauth}}”,将使用运行Web服务器的用户的凭据。",
+ "config-mssql-sqlauth": "SQL Server 身份验证",
+ "config-mssql-windowsauth": "Windows 身份验证",
+ "config-site-name": "wiki的名称:",
+ "config-site-name-help": "填入的内容会出现在浏览器的标题栏以及其他多处位置中。",
+ "config-site-name-blank": "输入网站的名称。",
+ "config-project-namespace": "项目名字空间:",
+ "config-ns-generic": "项目",
+ "config-ns-site-name": "与wiki名称相同:$1",
+ "config-ns-other": "其他(自定义)",
+ "config-ns-other-default": "我的Wiki",
+ "config-project-namespace-help": "依循维基百科形成的惯例,许多wiki将他们的方针页面存放在与内容页面不同的“'''项目名字空间'''”中。所有位于该名字空间下的页面标题都会被冠以固定的前缀,您可以在此处指定这一前缀。传统上,这一前缀应与wiki的命名保持一致,但请勿在其中使用标点符号,如“#”或“:”。",
+ "config-ns-invalid": "指定的名字空间“<nowiki>$1</nowiki>”无效,请为项目名字空间指定其他名称。",
+ "config-ns-conflict": "指定的名字空间“<nowiki>$1</nowiki>”与默认的MediaWiki名字空间冲突。请指定一个不同的项目名字空间。",
+ "config-admin-box": "管理员帐号",
+ "config-admin-name": "您的用户名:",
+ "config-admin-password": "密码:",
+ "config-admin-password-confirm": "确认密码:",
+ "config-admin-help": "在此输入您想使用的用户名,例如“乔帮主”。您将使用该名称登录本wiki。",
+ "config-admin-name-blank": "输入管理员的用户名。",
+ "config-admin-name-invalid": "指定的用户名“<nowiki>$1</nowiki>”无效,请指定其他用户名。",
+ "config-admin-password-blank": "输入管理员帐号的密码。",
+ "config-admin-password-mismatch": "两次输入的密码并不相同。",
+ "config-admin-email": "电子邮件地址:",
+ "config-admin-email-help": "在这里输入电子邮件地址可以允许你收到来自本wiki其他用户的电子邮件,重置你的密码和收到你的监视列表中的页面的更改通知。你可以将该字段留空。",
+ "config-admin-error-user": "在创建用户名为“<nowiki>$1</nowiki>”的管理员帐号时发生内部错误。",
+ "config-admin-error-password": "在为管理员“<nowiki>$1</nowiki>”设置密码时发生内部错误:<pre>$2</pre>",
+ "config-admin-error-bademail": "您输入了无效的电子邮件地址。",
+ "config-subscribe": "订阅[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 发行公告邮件列表]。",
+ "config-subscribe-help": "此低流量的邮件列表仅用于发行公告,其中包括重要安全公告。请订阅该列表以便在新的版本推出时升级您的MediaWiki。",
+ "config-subscribe-noemail": "您选择了订阅发行公告邮件列表,但没有提供电子邮件地址。请提供一个电子邮件地址以订阅邮件列表。",
+ "config-almost-done": "您几乎已经完成了!现在您可以跳过剩下的配置流程并立即安装wiki。",
+ "config-optional-continue": "多问我一些问题吧。",
+ "config-optional-skip": "我已经不耐烦了,赶紧安装我的wiki。",
+ "config-profile": "用户权限配置:",
+ "config-profile-wiki": "开放wiki",
+ "config-profile-no-anon": "需要注册帐号",
+ "config-profile-fishbowl": "编辑受限",
+ "config-profile-private": "非公开wiki",
+ "config-profile-help": "如果您允许尽量多的人编写wiki,网站上的内容会更加丰富。在MediaWiki中,您可以轻松地审查最近更改,并轻易回退掉新手或破坏者造成的损害。\n\n然而,许多人觉得让MediaWiki存在多种角色将更加好用;同时,要说服所有人都愿以wiki的方式作贡献并非一件易事。因此,您可以有以下选择:\n\n'''{{int:config-profile-wiki}}'''允许包括未登录用户在内的所有人编辑。'''{{int:config-profile-no-anon}}'''的wiki需要额外的注册流程,这有可能会阻碍随意贡献者。\n\n'''{{int:config-profile-fishbowl}}'''模式只允许获批准的用户编辑,但对公众开放页面浏览(包括历史记录)。'''{{int:config-profile-private}}'''则只允许获批准的用户浏览、编辑页面。\n\n安装完成后,您还可以对用户权限进行更多、更复杂的配置,参见[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights 相关的使用手册]。",
+ "config-license": "版权和许可证:",
+ "config-license-none": "页脚无许可证",
+ "config-license-cc-by-sa": "知识共享署名-相同方式共享",
+ "config-license-cc-by": "知识共享署名",
+ "config-license-cc-by-nc-sa": "知识共享署名-非商业性使用-相同方式共享",
+ "config-license-cc-0": "知识共享Zero(公有领域)",
+ "config-license-gfdl": "GNU自由文档许可证1.3或更高版本",
+ "config-license-pd": "公有领域",
+ "config-license-cc-choose": "选择自定义的知识共享许可证",
+ "config-license-help": "许多公共wiki将所有用户贡献置于[http://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": "如果您希望使用电子邮件功能,请正确配置[http://www.php.net/manual/en/mail.configuration.php PHP的邮件设定]。如果您不需要任何电子邮件功能,请在此处禁用它。",
+ "config-email-user": "启用用户到用户的电子邮件",
+ "config-email-user-help": "允许所有用户互发邮件,假若他们启用了该功能。",
+ "config-email-usertalk": "启用用户讨论页面通知",
+ "config-email-usertalk-help": "允许用户收到用户讨论页被修改的通知,假若他们启用了该功能。",
+ "config-email-watchlist": "启用监视列表通知",
+ "config-email-watchlist-help": "允许用户收到与其监视列表有关的通知,假若他们启用了该功能。",
+ "config-email-auth": "启用电子邮件身份验证",
+ "config-email-auth-help": "如果启用此选项,在用户设置或修改电子邮件地址时,就会收到一封邮件,内含确认电子地址的链接。只有经过身份验证的电子邮件地址,才能收到来自其他用户的电子邮件,或任何修改通知的邮件。'''建议'''公开wiki启用本选项,以防对电子邮件功能的滥用。",
+ "config-email-sender": "回复电子邮件地址:",
+ "config-email-sender-help": "输入要用来发送出站电子邮件的地址,该地址将会收到被拒收的邮件。许多邮件服务器要求域名部分必须有效。",
+ "config-upload-settings": "图像和文件上传",
+ "config-upload-enable": "启用文件上传",
+ "config-upload-help": "文件上传可能会将您的服务器暴露在安全风险下。有关更多的信息,请参阅手册的[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security 安全部分]。\n\n要启用文件上传,请先将MediaWiki根目录下的<code>images</code>子目录更改为对web服务器可写,然后再启用此选项。",
+ "config-upload-deleted": "已删除文件的目录:",
+ "config-upload-deleted-help": "指定用于存放被删除文件的目录。理想情况下,该目录不应能通过web访问。",
+ "config-logo": "标志URL:",
+ "config-logo-help": "在MediaWiki的默认外观中,左侧栏菜单之上有一块135x160像素的标志区。请上传一幅相应大小的图像,并在此输入URL。\n\n你可以用<code>$wgStylePath</code>或<code>$wgScriptPath</code>来表示相对于这些位置的路径。\n\n如果您不希望使用标志,请将本处留空。",
+ "config-instantcommons": "启用即时共享资源",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons 即时共享资源]可以让wiki使用来自[//commons.wikimedia.org/ 维基共享资源]网站的图像、音频和其他媒体文件。要启用该功能,MediaWiki必须能够访问互联网。\n\n有关此功能的详细信息,包括如何将其他wiki网站设为具有类似共享功能的方法,请参考[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos 手册]。",
+ "config-cc-error": "知识共享许可证挑选器无法找到结果,请手动输入许可证的名称。",
+ "config-cc-again": "重新挑选……",
+ "config-cc-not-chosen": "选择你想要的知识共享许可协议并单击“proceed”。",
+ "config-advanced-settings": "高级设置",
+ "config-cache-options": "对象缓存设置:",
+ "config-cache-help": "对象缓存可通过缓存频繁使用的数据来提高MediaWiki的速度。高度推荐中到大型的网站启用该功能,小型网站亦能从其中受益。",
+ "config-cache-none": "无缓存(不影响功能,但对较大型的wiki网站会有速度影响)",
+ "config-cache-accel": "PHP对象缓存(APC、XCache或WinCache)",
+ "config-cache-memcached": "使用Memcached(需要另外安装并配置)",
+ "config-memcached-servers": "Memcached服务器:",
+ "config-memcached-help": "用于Memcached的IP地址列表。请保持每行一条,并指定要使用的端口。例如:\n127.0.0.1:11211\n192.168.1.25:1234",
+ "config-memcache-needservers": "您选择了Memcached作为您的缓存,但并未指定任何服务器。",
+ "config-memcache-badip": "您为Memcached输入了无效的IP地址:$1。",
+ "config-memcache-noport": "您没有指定Memcached服务器的端口:$1。如果您不清楚端口是多少,默认值为11211。",
+ "config-memcache-badport": "Memcached的端口号应该在$1到$2之间。",
+ "config-extensions": "扩展程序",
+ "config-extensions-help": "已在您的<code>./extensions</code>目录中发现下列扩展。\n\n您可能要对它们进行额外的配置,但您现在可以启用它们。",
+ "config-skins": "皮肤",
+ "config-skins-help": "在您的<code>./skins</code>目录中检测到上面列出的皮肤。您必须选择至少一个,并选择一个默认值。",
+ "config-skins-use-as-default": "使用此皮肤作为默认皮肤",
+ "config-skins-missing": "没有找到皮肤;MediaWiki将使用备选皮肤直到您自行安装一个后。",
+ "config-skins-must-enable-some": "您必须选择至少一个皮肤以起用。",
+ "config-skins-must-enable-default": "默认选择的皮肤必须启用。",
+ "config-install-alreadydone": "'''警告:'''您似乎已经安装了MediaWiki,并试图重新安装它。请前往下一个页面。",
+ "config-install-begin": "点击“{{int:config-continue}}”后,您将开始安装MediaWiki。如果您还想对配置作一些修改,请点击“{{int:config-back}}”。",
+ "config-install-step-done": "完成",
+ "config-install-step-failed": "失败",
+ "config-install-extensions": "正在包含扩展程序",
+ "config-install-database": "正在配置数据库",
+ "config-install-schema": "创建架构",
+ "config-install-pg-schema-not-exist": "PostgreSQL 架构不存在",
+ "config-install-pg-schema-failed": "创建数据表失败。请确保用户“$1”拥有写入模式“$2”的权限。",
+ "config-install-pg-commit": "正在提交更改",
+ "config-install-pg-plpgsql": "正在检查PL/pgSQL语言",
+ "config-pg-no-plpgsql": "您需要为数据库$1安装PL/pgSQL语言",
+ "config-pg-no-create-privs": "为安装程序指定的帐号缺少创建帐号的权限。",
+ "config-pg-not-in-role": "您指定为web用户的帐户已经存在。\n您给本程序指定的帐户不是超级用户,也不是web用户角色的成员,所以它不能创建web用户所拥有的对象。\n\nMediaWiki当前需要使用由web用户所有的表。请指定另一个web帐户名称,或点击“后退”并指定具有适当权限的安装用户。",
+ "config-install-user": "正在创建数据库用户",
+ "config-install-user-alreadyexists": "用户“$1”已存在",
+ "config-install-user-create-failed": "创建用户“$1”失败:$2",
+ "config-install-user-grant-failed": "授予用户“$1”权限失败:$2",
+ "config-install-user-missing": "指定的用户“$1”不存在。",
+ "config-install-user-missing-create": "指定的用户“$1”不存在。如果您想要创建一名,请点选“创建帐户”下面的复选框。",
+ "config-install-tables": "正在创建数据表",
+ "config-install-tables-exist": "'''警告''':MediaWiki的数据表似乎已经存在,跳过创建。",
+ "config-install-tables-failed": "'''错误''':创建数据表出错,下为错误信息:$1",
+ "config-install-interwiki": "正在填充默认的跨wiki数据表",
+ "config-install-interwiki-list": "找不到文件<code>interwiki.list</code>。",
+ "config-install-interwiki-exists": "'''警告''':跨wiki数据表似乎已有内容,跳过默认列表。",
+ "config-install-stats": "初始化统计",
+ "config-install-keys": "生成密钥中",
+ "config-insecure-keys": "'''警告''':在安装过程中生成的{{PLURAL:$2|安全密钥|安全密钥}}($1){{PLURAL:$2|并|并}}不一定安全。请考虑手动更改{{PLURAL:$2|它|它们}}。",
+ "config-install-updates": "防止运行不需要的更新",
+ "config-install-updates-failed": "<strong>错误:</strong>表格中插入更新关键字失败并出现如下错误:$1",
+ "config-install-sysop": "正在创建管理员用户帐号",
+ "config-install-subscribe-fail": "无法订阅mediawiki-announce:$1",
+ "config-install-subscribe-notpossible": "没有安装cURL,<code>allow_url_fopen</code>也不可用。",
+ "config-install-mainpage": "正在创建显示默认内容的首页",
+ "config-install-extension-tables": "正在创建已启用扩展程序表",
+ "config-install-mainpage-failed": "无法插入首页:$1",
+ "config-install-done": "'''恭喜!'''\n您已经成功地安装了MediaWiki。\n\n安装程序已经生成了<code>LocalSettings.php</code>文件,其中包含了您所有的配置。\n\n您需要下载该文件,并将其放在您wiki的根目录(index.php的同级目录)中。稍后下载将自动开始。\n\n如果浏览器没有提示您下载,或者您取消了下载,您可以点击下面的链接重新开始下载:\n\n$3\n\n'''注意''':如果您现在不完成本步骤,而是没有下载便退出了安装过程,此后您将无法获得自动生成的配置文件。\n\n当本步骤完成后,您可以 '''[$2 进入您的wiki]'''。",
+ "config-download-localsettings": "下载<code>LocalSettings.php</code>",
+ "config-help": "帮助",
+ "config-help-tooltip": "单击展开",
+ "config-nofile": "找不到文件“$1”。它是否已被删除?",
+ "config-extension-link": "您是否知道您的wiki支持[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions 拓展]?\n您可浏览[//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category 拓展分类]。",
+ "mainpagetext": "'''已成功安装MediaWiki。'''",
+ "mainpagedocfooter": "请查阅[//meta.wikimedia.org/wiki/Help:Contents 用户指南]以获取使用本wiki软件的信息!\n\n== 入门 ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki配置设置列表]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/zh-hans MediaWiki常见问题]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki发布邮件列表]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 本地化MediaWiki到您的语言]"
+}
diff --git a/includes/installer/i18n/zh-hant.json b/includes/installer/i18n/zh-hant.json
new file mode 100644
index 00000000..b860dc6f
--- /dev/null
+++ b/includes/installer/i18n/zh-hant.json
@@ -0,0 +1,335 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anthony Fok",
+ "Hzy980512",
+ "Justincheng12345",
+ "Liangent",
+ "Mark85296341",
+ "Simon Shek",
+ "아라",
+ "Liuxinyu970226",
+ "Xiaomingyan",
+ "Cwlin0416",
+ "S8321414",
+ "LNDDYL",
+ "NigelSoft"
+ ]
+ },
+ "config-desc": "MediaWiki 安裝程式",
+ "config-title": "MediaWiki $1 安裝",
+ "config-information": "資訊",
+ "config-localsettings-upgrade": "已偵測到 <code>LocalSettings.php</code> 檔案。\n要升級目前安裝的版本,請在下方輸入框中輸入 <code>$wgUpgradeKey</code> 的值。\n您可以從 <code>LocalSettings.php</code> 檔案中找到。",
+ "config-localsettings-cli-upgrade": "已偵測到 <code>LocalSettings.php</code> 檔案。\n要升級目前安裝的版本,請執行 <code>update.php</code>。",
+ "config-localsettings-key": "升級金鑰:",
+ "config-localsettings-badkey": "你提供的金鑰不正確。",
+ "config-upgrade-key-missing": "已偵測到先前安裝的 MediaWiki。\n要升級目前安裝的版本,請將下列文字附加到 <code>LocalSettings.php</code> 的檔案最下方:\n\n$1",
+ "config-localsettings-incomplete": "目前的 <code>LocalSettings.php</code> 檔案不完整。\n未設定參數 $1。\n請將此參數設定至 <code>LocalSettings.php</code> 中,並點選 \"{{int:Config-continue}}\"。",
+ "config-localsettings-connection-error": "使用 <code>LocalSettings.php</code> 中所指定的資料庫設定連線發生錯誤。 請修復相關設定並再試一次。\n\n$1",
+ "config-session-error": "開始連線階段錯誤:$1",
+ "config-session-expired": "您的連線階段已過期。\n目前設定的工作階段期限為 $1。\n您可以在 php.ini 設定檔中設定 <code>session.gc_maxlifetime</code> 的參數來延長此期限。\n重新開始安裝程序。",
+ "config-no-session": "您的連線階段資料遺失!\n請檢查 php.ini 設定檔並確認 <code>session.save_path</code> 所設定的目錄是否合適。",
+ "config-your-language": "您的語言:",
+ "config-your-language-help": "請選擇接下來安裝程序中要使用的語言。",
+ "config-wiki-language": "Wiki 語言:",
+ "config-wiki-language-help": "選擇將要安裝的 Wiki 多數情況主要使用的語言。",
+ "config-back": "← 返回",
+ "config-continue": "繼續 →",
+ "config-page-language": "語言",
+ "config-page-welcome": "歡迎您來到 MediaWiki!",
+ "config-page-dbconnect": "連線到資料庫",
+ "config-page-upgrade": "升級目前安裝的版本",
+ "config-page-dbsettings": "資料庫設定",
+ "config-page-name": "名稱",
+ "config-page-options": "選項",
+ "config-page-install": "安裝",
+ "config-page-complete": "完成!",
+ "config-page-restart": "重新安裝",
+ "config-page-readme": "讀我說明",
+ "config-page-releasenotes": "發佈說明",
+ "config-page-copying": "複製",
+ "config-page-upgradedoc": "升級",
+ "config-page-existingwiki": "現有 Wiki",
+ "config-help-restart": "是否要清除所有已輸入且儲存的資料,並重新開始安裝程序嗎?",
+ "config-restart": "是的,重新開始",
+ "config-welcome": "=== 環境檢查 ===\n現在會做基本的檢查,檢查環境是否符合 MediaWiki 安裝所需。\n若您要尋求如何完成安裝的協助,請記得提供以下訊息。",
+ "config-copyright": "=== 版權聲明與授權條款 ===\n\n$1\n\n本程式為自由軟體;您可依據自由軟體基金會所發表的 GNU 通用公共授權條款規定,將本程式重新發佈與/或修改;無論您依據的是本授權條款的第二版或 (您可自行選擇) 之後的任何版本。\n\n本程式發佈的目的是希望可以提供幫助,但 <strong>不負任何擔保責任</strong>;亦無隱含對 <strong>適售性</strong> 或 <strong>特定用途的適用性</strong> 的情形擔保。詳情請參照 GNU 通用公共授權。\n\n您應已隨本程式收到 <doclink href=\"Copying\">GNU 通用公共授權條款的副本</doclink>;如果沒有,請信件通知自由軟體基金會,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA,或 [http://www.gnu.org/copyleft/gpl.html 線上閱讀]。",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/zh-hant MediaWiki 首頁]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/zh 使用者指南]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/zh 管理員指南]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/zh 常見問題集]\n----\n* <doclink href=Readme>讀我說明</doclink>\n* <doclink href=ReleaseNotes>發行說明</doclink>\n* <doclink href=Copying>版權聲明</doclink>\n* <doclink href=UpgradeDoc>升級</doclink>",
+ "config-env-good": "環境檢查已完成。\n您可以安裝 MediaWiki。",
+ "config-env-bad": "環境檢查已完成。\n您無法安裝 MediaWiki。",
+ "config-env-php": "PHP $1 已安裝。",
+ "config-env-hhvm": "HHVM $1 已安裝。",
+ "config-unicode-using-utf8": "使用 Brion Vibber 的 utf8_normalize.so 做 Unicode 正規化。",
+ "config-unicode-using-intl": "使用 [http://pecl.php.net/intl intl PECL 擴充套件] 做 Unicode 正規化。",
+ "config-unicode-pure-php-warning": "<strong>警告:</strong> 無法使用 [http://pecl.php.net/intl intl PECL 擴充套件] 處理 Unicode 正規化,故回退使用純 PHP 實作的正規化程式,此方式處理速度較緩慢。\n\n如果您的網站瀏覽人次很高,您應先閱讀 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations/zh Unicode 正規化]。",
+ "config-unicode-update-warning": "<strong>警告</strong>:目前安裝的 Unicode 正規化包裝程式使用了舊版 [http://site.icu-project.org/ ICU 計劃] 的程式庫。\n若您需要使用 Unicode,您應先進行 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 升級]。",
+ "config-no-db": "找不到合適的資料庫驅動程式!您需要安裝 PHP 資料庫驅動程式。\n目前支援以下類型的資料庫: $1 。\n\n如果您是自行編譯 PHP,您必須重新設定並開啟資料庫客戶端,例:使用 <code>./configure --with-mysqli</code> 指令參數。\n如果您是使用 Debian 或 Ubuntu 的套件安裝,您則需要額外安裝,例:<code>php5-mysql</code> 套件。",
+ "config-outdated-sqlite": "<strong>警告:</strong>您已安裝 SQLite $1,但是它的版本低於最低需求版本 $2。 因此您無法使用 SQLite。",
+ "config-no-fts3": "<strong>警告:</strong> SQLite 編譯時未包含 [//sqlite.org/fts3.html FTS3 模組],後台搜尋功能將無法使用。",
+ "config-register-globals-error": "<strong>錯誤:PHP 的 <code>[http://php.net/register_globals register_globals]</code> 選項已開啟。\n要繼續安裝程序必須關閉該選項。</strong>\n請參考 [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] 以取得操作說明。",
+ "config-magic-quotes-gpc": "<strong>嚴重:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] 已開啟!</strong>\n此選項會在無法預期的情況下損壞資料。\n除非您將該選項關閉,否鄍您無法安裝或使用 MediaWiki。",
+ "config-magic-quotes-runtime": "<strong>嚴重:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] 選項被開啟!</strong>\n此選項會導致資料在無法預測的情況下損壞。\n您必須將開選項關閉方可繼續安裝 MediaWiki。",
+ "config-magic-quotes-sybase": "<strong>嚴重:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] 選項被開啟!</strong>\n此選項會導致資料在無法預測的情況下損壞。\n您必須將開選項關閉方可繼續安裝 MediaWiki。",
+ "config-mbstring": "<strong>嚴重:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] 選項被開啟!</strong>\n此選項會導致資料在無法預測的情況下損壞。\n您必須將開選項關閉方可繼續安裝 MediaWiki。",
+ "config-safe-mode": "<strong>警告:</strong>PHP 的 [http://www.php.net/features.safe-mode 安全模式] 選項被開啟。它可能會導致檔案上傳與數學函數 <code>math</code> 的問題。",
+ "config-xml-bad": "PHP 缺少的 XML 模組。\nMediaWiki 需要使用此模組中所提供的函數,且在目前的設定下將無法繼續作業。\n如果您使用的是 Mandrake Linux,請安裝 php-xml 套件。",
+ "config-pcre-old": "<strong>嚴重:</strong> 需要使用 PCRE $1 或更新的版本。\n您的 PHP 執行檔使用的是 PCRE $2。\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE 詳細資訊]。",
+ "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-ctype": "<strong>嚴重:</strong> PHP 編譯時必須包含 [http://www.php.net/manual/en/ctype.installation.php Ctype 擴充套件]。",
+ "config-iconv": "<strong>嚴重:</strong>PHP 編譯時必須包含 [http://www.php.net/manual/en/iconv.installation.php iconv 擴充套件]。",
+ "config-json": "<strong>嚴重:</strong> PHP 編譯時並未包含 JSON 擴充套件。\n在 MediaWiki 繼續安裝之前您必須先安裝 PHP JSON 擴充套件或 [http://pecl.php.net/package/jsonc PECL jsonc] 擴充套件。\n* 此 PHP 擴充套件在 Red Hat Enterprice Linux (CentOS) 5 版與 6 版以有內含,須於 <code>/etc/php.ini</code> 或 <code>/etc/php.d/json.ini</code> 設定檔將該項目開啟。\n* 部分於 2013 年 5 月以後發佈的 Linux 並沒有此 PHP 擴充套件,可透過安裝 PECL 擴充套件 <code>php5-json</code> 或 <code>php-pecl-jsonc</code> 替代。",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] 已安裝",
+ "config-apc": "[http://www.php.net/apc APC] 已安裝",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] 已安裝",
+ "config-no-cache": "<strong>警告:</strong> 找不到 [http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache] 或 [http://www.iis.net/download/WinCacheForPhp WinCache]。\n無法開啟物件快取功能。",
+ "config-mod-security": "<strong>警告:</strong>您的網頁伺服器已開啟 [http://modsecurity.org/ mod_security] 模組,如果設定不恰當會導致使用者可在 MediaWiki 或其他應用程式發佈任意的內容。\n若您遇到任何問題,請參考 [http://modsecurity.org/documentation/ mod_security 文件] 或聯繫您的伺服器技術支援人員。",
+ "config-diff3-bad": "找不到 GNU diff3。",
+ "config-git": "找到 Git 版本控制軟體:<code>$1</code>。",
+ "config-git-bad": "查無 Git 版本控制軟體。",
+ "config-imagemagick": "找到 ImageMagick:<code>$1</code>。\n若您開啟了檔案上傳功能,將可啟用縮圖功能。",
+ "config-gd": "找到內建 GD 圖形程式庫。\n若您開啟了檔案上傳功能,將可啟用縮圖功能。",
+ "config-no-scaling": "找不到 GD 程式庫或 ImageMagick。\n無法使用縮圖功能。",
+ "config-no-uri": "<strong>錯誤:</strong>無法辨識目前的 URI 位置。\n安裝已中止。",
+ "config-no-cli-uri": "<strong>警告:</strong>:未指定 <code>--scriptpath</code> 指令參數,使用預設值:<code>$1</code>。",
+ "config-using-server": "使用伺服器名稱 \"<nowiki>$1</nowiki>\"。",
+ "config-using-uri": "使用伺服器 URL 位置 \"<nowiki>$1$2</nowiki>\"。",
+ "config-uploads-not-safe": "<strong>警告:</strong>您預設的上傳目錄 <code>$1</code> 有可被任意執行 Script 的漏洞。\n雖然 MediaWiki 會對所有上傳的檔案進行安全檢查,但我們仍強烈建議您在開啟上傳功能前了解如何 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security 關閉此安全漏洞]。",
+ "config-no-cli-uploads-check": "<strong>警告:</strong>透過指令介面安不會檢查您預設的上傳目錄 (<code>$1</code>) 是否有可任意執行 Script 的安全性漏洞。",
+ "config-brokenlibxml": "您的系統使用了可能造成 MediaWiki 或其他網頁應用程式資料損毀問題的 PHP 與 limbxml2 版本。\n請升級 libxml2 2.7.3 或更新的版本 ([https://bugs.php.net/bug.php?id=45996 PHP 問題報告])。\n安裝已中止。",
+ "config-suhosin-max-value-length": "Suhosin 已安裝並且限制 GET 參數的長度 <code>length</code> 為 $1 位元組。\nMediaWiki 的 ResourceLoader 元件可以在此限制下正常運作,但仍會降低執行的效能。\n如果可能的情況下,您應該設定 <code>php.ini</code> 設定檔中的項目 <code>suhosin.get.max_value_length</code> 為 1024 或者更高的數值,並且將\n<code>LocalSettings.php</code> 中的設定項目 <code>$wgResourceLoaderMaxQueryLength</code> 設為相同的數值。",
+ "config-db-type": "資料庫類型:",
+ "config-db-host": "資料庫主機:",
+ "config-db-host-help": "如果您的資料庫安裝在其他伺服器上,請在此輸入該主機的名稱或 IP 位址。\n\n如果您使用共用的網頁主機,您的主機提供商應會在說明文件上告訴您正確的主機名稱。\n\n如果您安裝在 Windows 伺服器並且使用 MySQL,伺服器名稱可能無法使用使用 \"localhost\"。若確實無法使用,請改嘗試使用本機的 IP 位址 \"127.0.0.1\"。\n\n如果您使用 PostgreSQL,將此欄位空白以使用 Unix socket 來連線。",
+ "config-db-host-oracle": "資料庫的 TNS:",
+ "config-db-host-oracle-help": "請輸入有效的 [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 本地連線名稱],並確認安裝程式可以讀取 tnsnames.ora 檔案。<br />如果您使用的客戶端程式庫為 10g 或者更新的版本,您也可使用 [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 簡易連線] 的命名方法進行連線。",
+ "config-db-wiki-settings": "此 Wiki 的 ID",
+ "config-db-name": "資料庫名稱:",
+ "config-db-name-help": "請輸入一個可以辨識您的 Wiki 的名稱,\n請勿包含空格。\n\n如果您使用的是共用的網頁主機,您的主機提供商會給您一個指定的資料庫名稱,或者讓您透過管理介面建立資料庫。",
+ "config-db-name-oracle": "資料庫 Schema:",
+ "config-db-account-oracle-warn": "目前有三種支援 Oracle 做為後端資料庫的方案:\n\n如果您希望在安裝的過程中自動建立新的資料庫,請提供具有 SYSDBA 權限的帳號並且提供未來要給網頁存取使用的資料庫帳號及密碼。或者您可以手動建立給網頁存取使用的資料庫帳號 (請確保該帳號有建立 Schema Object 的權限),再不然您可以提供兩組不同的帳號,一組用來建立權限,而另一組用來做為網頁存取使用。\n\n本次安裝建立的帳號以及權限所需要的 Script,可以在 \"maintenance/oracle/\" 中找到。\n請注意,若您使用有限制的帳號將會預設關閉所有維護性功能。",
+ "config-db-install-account": "安裝程式使用的使用者帳號",
+ "config-db-username": "資料庫使用者名稱:",
+ "config-db-password": "資料庫密碼:",
+ "config-db-password-empty": "請輸入新增資料庫使用者 $1 的密碼。\n雖然您可以不設定任何密碼,但這樣做並不安全。",
+ "config-db-username-empty": "您必須輸入 \"{{int:config-db-username}}\" 欄位的內容。",
+ "config-db-install-username": "請輸入在安裝過程中用來連線資料庫的使用者名稱。\n請注意,這不是 MediaWiki 帳號的使用者名稱,這是您資料庫的使用者名稱。",
+ "config-db-install-password": "請輸入在安裝過程中用來連線資料庫的密碼。\n請注意,這不是 MediaWiki 帳號的密碼,這是您資料庫的密碼。",
+ "config-db-install-help": "請輸入在安裝過程中用來連線資料庫的使用者名稱及密碼。",
+ "config-db-account-lock": "在一般操作時使用同樣的使用者名稱及密碼。",
+ "config-db-wiki-account": "用於一般操作的使用者帳號",
+ "config-db-wiki-help": "請輸入一般操作用來連線資料庫的使用者名稱及密碼。\n若您安裝使用的資料庫帳號有足夠的權限,您可以輸入新的帳號,系統會自動幫您以最低權限建立一組專門做為 Wiki 一般操作的帳號。",
+ "config-db-prefix": "資料庫資料表名稱的字首:",
+ "config-db-prefix-help": "如果您需要讓多個 Wiki 共用同一個資料庫,或者與其他網頁應用程式共用一個資料庫,您也許會需要在所有資料表的名稱前面加上字首,可以避免資料表名稱的衝突。\n請勿使用空格。\n\n此欄位可不填。",
+ "config-db-charset": "資料庫字元集",
+ "config-charset-mysql5-binary": "MySQL 4.1/5.0 可用的二進制",
+ "config-charset-mysql5": "MySQL 4.1/5.0 可用的 UTF-8",
+ "config-charset-mysql4": "向下相容 MySQL 4.0 的 UTF-8",
+ "config-charset-help": "<strong>警告:</strong>如果您在 MySQL 4.1+ 使用了<strong>可向下相容的 UTF-8</strong>,並且透過 <code>mysqldump</code> 指令備份資料庫,可能會破壞所有非 ASCII 字元集的文字,這會導致不可還原的資料破壞!\n\n在 <strong>二進制模式</strong> 下,MediaWiki 將 UTF-8 的文字儲存在二進位型態的欄位。\n這個模式比 MySQL 的 UTF-8 模式還要更有效,並且可以讓您使用完整的 Unicode 字元集。\n在 <storng>UTF-8 模式</strong> 下,MySQL 可以知道您的資料使用何種編碼儲存,您可以正常的取得與轉換內容,但此個模式只支援到 Unicode 中的 [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes 基本多文種平面] 字元。",
+ "config-mysql-old": "需要使用 MySQL $1 或更新的版本,您的版本為 $2。",
+ "config-db-port": "資料庫埠號:",
+ "config-db-schema": "MediaWiki 的 Schema:",
+ "config-db-schema-help": "資料庫 Schema 通常不需更動。\n只在有特殊需求時才需修改。",
+ "config-pg-test-error": "無法連線到資料庫 <strong>$1</strong>:$2",
+ "config-sqlite-dir": "SQLite 的資料目錄:",
+ "config-sqlite-dir-help": "SQLite 會將所有的資料存儲於單一檔案中。\n\n您所提供的目錄在安裝過程中必須開啟給網頁伺服器的寫入權限。\n\n該目錄 <strong>不應</strong> 可以被透過網頁所開啟,這也是為什麼我們不將資料與 PHP 檔案放在一起。\n\n安裝程式在建立資料庫檔案時,會同時在目錄下建立 <code>.htaccess</code> 以控制網頁伺服器權限。若此設定失效,則會導致任何人可以直接存取您的原始資料檔案,而資料庫的內容包含原始的使用者資料 (電子郵件地址、加密後的密碼)、刪除後的修訂及其他在 Wiki 上被限制存取的資料。\n\n請考慮將資料庫統一放置在某處,如 <code>/var/lib/mediawiki/yourwiki</code> 底下。",
+ "config-oracle-def-ts": "預設資料表空間:",
+ "config-oracle-temp-ts": "臨時資料表空間:",
+ "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 伺服器],上述這些與 MySQL 相容的資料庫系統。([http://www.php.net/manual/en/mysqli.installation.php 如何編譯支援 MySQL 的 PHP])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] 是一套受歡迎的開源資料庫系統,在開源方案當中,可用來替代 MySQL。目前仍有一些次要的問題需要解決,較不建議使用在上線環境當中。 ([http://www.php.net/manual/en/pgsql.installation.php 如何編譯支援 PostgreSQL 的 PHP])。",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] 是一套輕量級的資料庫系統,MediaWiki 可在此資料庫系統上良好的運作。([http://www.php.net/manual/en/pdo.installation.php 如何編譯支援 SQLite 的 PHP],須透過 PDO)",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] 是一套商用企業級的資料庫。([http://www.php.net/manual/en/oci8.installation.php 如何編譯支援 OCI8 的 PHP])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] 是一套 Windows 專用的商用企業級的資料庫。 ([http://www.php.net/manual/en/sqlsrv.installation.php 如何編譯支援 SQLSRV 的 PHP])",
+ "config-header-mysql": "MySQL 設定",
+ "config-header-postgres": "PostgreSQL 設定",
+ "config-header-sqlite": "SQLite 設定",
+ "config-header-oracle": "Oracle 設定",
+ "config-header-mssql": "Microsoft SQL Server 設定",
+ "config-invalid-db-type": "無效的資料庫類型。",
+ "config-missing-db-name": "您必須輸入 \"{{int:config-db-name}}\" 欄位的內容。",
+ "config-missing-db-host": "您必須輸入 \"{{int:config-db-host}}\" 欄位的內容。",
+ "config-missing-db-server-oracle": "您必須輸入 \"{{int:config-db-host-oracle}}\" 欄位的內容。",
+ "config-invalid-db-server-oracle": "無效的資料庫 TNS \"$1\"。\n請使用符合 \"TNS 名稱\" 或 \"簡易連線\" 規則的字串([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle命名規則])",
+ "config-invalid-db-name": "無效的資料庫名稱 \"$1\"。\n僅允許使用 ASCII 字母(a-z、A-Z)、數字(0-9)、底線(_)與連字號(-)。",
+ "config-invalid-db-prefix": "無效的資料庫字首 \"$1\"。\n僅允許使用 ASCII 字母(a-z、A-Z)、數字(0-9)、底線(_)與連字號(-)。",
+ "config-connection-error": "$1。\n\n請檢查主機、使用者名稱和密碼設定,然後重試。",
+ "config-invalid-schema": "無效的資料庫 Schema \"$1\"。\n僅允許使用 ASCII 字母(a-z、A-Z)、數字(0-9)、底線(_)與連字號(-)。",
+ "config-db-sys-create-oracle": "安裝程式只支援使用 SYSDBA 帳號建立新帳號。",
+ "config-db-sys-user-exists-oracle": "使用者帳號 \"$1\" 已存在。 SYSDBA 只可用來建立新的帳號!",
+ "config-postgres-old": "需要使用 PostgreSQL $1 或更新的版本,您的版本為 $2。",
+ "config-mssql-old": "需要使用 Microsoft SQL Server $1 或更新的版本,您的版本為 $2。",
+ "config-sqlite-name-help": "請為您的 Wiki 設定一個用來辨識的名稱。\n請勿使用空格或連字號,\n該名稱會被用來做為 SQLite 資料檔的名稱。",
+ "config-sqlite-parent-unwritable-group": "無法建立資料目錄 <nowiki>$1</nowiki></code>,因網頁伺服器對該目錄所在的上層目錄 <code><nowiki>$2</nowiki></code> 沒有寫入權限。\n\n安裝程序所使用的身份依據您用來執行網頁伺服器的身份而定,\n請開啟網頁伺服器對 <code><nowiki>$3</nowiki></code> 的寫入權以繼續安裝,\n在 Unix/Linux 系統可以執行以下指令:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "無法建立資料目錄 <nowiki>$1</nowiki></code>,因網頁伺服器對該目錄所在的上層目錄 <code><nowiki>$2</nowiki></code> 沒有寫入權限。\n\n安裝程序所使用的身份依據您用來執行網頁伺服器的身份而定,\n請開啟全部人對 <code><nowiki>$3</nowiki></code> 的寫入權以繼續安裝,\n在 Unix/Linux 系統可以執行以下指令:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "建立資料目錄 \"$1\" 時發生錯誤。\n請檢查路徑後再試一次。",
+ "config-sqlite-dir-unwritable": "無法寫入目錄 \"$1\"。\n請修改該目錄的權限,請開啟網頁伺服器的寫入權限後,再試一次。",
+ "config-sqlite-connection-error": "$1。\n\n請檢查下方資料目錄與資料庫名稱,再試一次。",
+ "config-sqlite-readonly": "檔案 <code>$1</code> 無寫入權限。",
+ "config-sqlite-cant-create-db": "無法建立資料庫檔案 <code>$1</code>。",
+ "config-sqlite-fts3-downgrade": "PHP 不支援 FTS3,正在降級資料表。",
+ "config-can-upgrade": "在資料庫中找到 MediaWiki 的資料表。\n要升級至 MediaWiki $1,請點選 <strong>繼續</strong>。",
+ "config-upgrade-done": "升級完成。\n\n現在您可以 [$1 開始使用您的 Wiki] 了。\n\n如果您需要重新產生 <code>LocalSettings.php</code> 檔案,請點選下方按鈕。\n除非您的 Wiki 出現了問題,否則我們 <strong>不建議</strong> 您執行此操作。",
+ "config-upgrade-done-no-regenerate": "升級完成。\n\n現在您可以 [$1 開始使用您的 Wiki] 了。",
+ "config-regenerate": "重新產生 LocalSettings.php →",
+ "config-show-table-status": "<code>SHOW TABLE STATUS</code> 查詢失敗!",
+ "config-unknown-collation": "<strong>警告:</strong>資料庫使用了無法辨識的字元與排序規則。",
+ "config-db-web-account": "供網頁存取使用的資料庫帳號",
+ "config-db-web-help": "請設定網頁伺服器在一般操作時連線到資料庫使用的使用者名稱及密碼。",
+ "config-db-web-account-same": "使用與安裝程序相同的帳號",
+ "config-db-web-create": "如果帳號不存在則建立新帳號",
+ "config-db-web-no-create-privs": "您指定給安裝程序使用的帳號沒有足夠的權限建立新帳號。\n在此處必須指定已經存在的帳號。",
+ "config-mysql-engine": "儲存引擎:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>警告:</strong>您選擇用來做為 MySQL 的儲存引撆 MyISAM 並不建議使用在 MediaWiki,主要原因為:\n* MyISAM 使用的資料表鎖定較無法承受多人同時連線\n* 比起其他儲存引擎相,它較容易損壞\n* MediaWiki 程式碼並沒有針對 MyISAM 做特別的處理\n\n若您安裝的 MySQL 支援 InnoDB,我們強烈建議您改用 InnoDB。\n若您安裝的 MySQL 不支援 InnoDB,則應考慮升級 MySQL。",
+ "config-mysql-only-myisam-dep": "<strong>警告:</strong>您的伺服器上的 MySQL 唯一可用的儲存引擎是 MyISAM,但並不建議使用,主要原因為:\n* MyISAM 使用的資料表鎖定較無法承受多人同時連線\n* 比起其他儲存引擎相,它較容易損壞\n* MediaWiki 程式碼並沒有針對 MyISAM 做特別的處理\n\n若您安裝的 MySQL 不支援 InnoDB,則應考慮升級 MySQL。",
+ "config-mysql-engine-help": "由於對同時連線有較好的處理能力,<strong>InnoDB</strong> 通常是最佳的選項。\n\n<strong>MyISAM</strong> 只在單人使用或者唯讀作業的情況之下才可能有較快的處理能力。\n相較於 InnoDB,MyISAM 也較容易出現資料損毀的情況。",
+ "config-mysql-charset": "資料庫字元集:",
+ "config-mysql-binary": "二進制",
+ "config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "在 <strong>二進制模式</strong> 下,MediaWiki 將 UTF-8 的文字儲存在二進位型態的欄位。\n這個模式比 MySQL 的 UTF-8 模式還要更有效,並且可以讓您使用完整的 Unicode 字元集。\n\n在 <storng>UTF-8 模式</strong> 下,MySQL 可以知道您的資料使用何種編碼儲存,您可以正常的取得與轉換內容,但此個模式只支援到 Unicode 中的 [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes 基本多文種平面] 字元。",
+ "config-mssql-auth": "身份驗證類型:",
+ "config-mssql-install-auth": "請選擇安裝程序中要用來連線資料庫使用的身份驗證類型。\n若您選擇 \"{{int:config-mssql-windowsauth}}\",不論網頁伺服器是使用何種身份執行都會使用這組驗證資料。",
+ "config-mssql-web-auth": "請選擇一般操作中要用來連線資料庫使用的身份驗證類型。\n若您選擇 \"{{int:config-mssql-windowsauth}}\",不論網頁伺服器是使用何種身份執行都會使用這組驗證資料。",
+ "config-mssql-sqlauth": "SQL Server 身份驗證",
+ "config-mssql-windowsauth": "Windows 身份驗證",
+ "config-site-name": "Wiki 的名稱:",
+ "config-site-name-help": "您所填入的內容會出現在瀏覽器的標題列以及各種其他地方。",
+ "config-site-name-blank": "請輸入網站名稱。",
+ "config-project-namespace": "專案命名空間:",
+ "config-ns-generic": "專案",
+ "config-ns-site-name": "同 Wiki 名稱:$1",
+ "config-ns-other": "其他 (請註明)",
+ "config-ns-other-default": "我的 Wiki",
+ "config-project-namespace-help": "許多 Wiki 以維基百科(Wikipedia)做為範例將政策頁面從內容頁面抽離,放置在 \"'''專案命名空間'''\" 中。\n所有在此命名空間裡的頁面都會有特定的字首,您可以在此處設定。\n通常這些字首是由該 Wiki 的名稱所衍伸出來,但無法使用標點符號,如 \"#\" 或 \":\"。",
+ "config-ns-invalid": "您指定的命名空間 \"<nowiki>$1</nowiki>\" 無效,\n請指定另一個專案命名空間。",
+ "config-ns-conflict": "您指定的命名空間 \"<nowiki>$1</nowiki>\" 與 MediaWiki 預設的命名空間衝突。\n請指定另一個專案命名空間。",
+ "config-admin-box": "管理員帳號",
+ "config-admin-name": "您的使用者名稱:",
+ "config-admin-password": "密碼:",
+ "config-admin-password-confirm": "再次輸入密碼:",
+ "config-admin-help": "在此輸入您想使用的使用者名稱,例如 \"Joe Bloggs\"。\n此名稱將用來登入 Wiki。",
+ "config-admin-name-blank": "輸入管理員的使用者名稱。",
+ "config-admin-name-invalid": "指定的使用者名稱 \"<nowiki>$1</nowiki>\" 無效,請改用其他使用者名稱。",
+ "config-admin-password-blank": "輸入管理員帳號密碼。",
+ "config-admin-password-mismatch": "兩次輸入的密碼並不相同。",
+ "config-admin-email": "電子郵件位址:",
+ "config-admin-email-help": "在此輸入的電子郵件信箱可用來接收 Wiki 上其他使用者所傳送的訊息、重設您的密碼與通知監視清單中頁面更動。您可將此欄位留空。",
+ "config-admin-error-user": "建立管理員帳號 \"<nowiki>$1</nowiki>\" 時發送內部錯誤。",
+ "config-admin-error-password": "設定管理員 \"<nowiki>$1</nowiki>\" 的密碼時發送內部錯誤:<pre>$2</pre>",
+ "config-admin-error-bademail": "您輸入了不正確的電子郵件位址。",
+ "config-subscribe": "訂閱 [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 發佈公告郵寄清單]。",
+ "config-subscribe-help": "這是一個用於發佈公告的低郵件量郵寄清單,內容包括重要的安全公告。\n您應該訂閱它並在 MediaWiki 發佈新版的時候更新系統。",
+ "config-subscribe-noemail": "您正嘗試不填寫電子郵件位址訂閱發佈公告郵寄清單。 \n請如果您希望訂閱郵寄清單,請提供一個有效的電子郵件位址。",
+ "config-almost-done": "您快要完成了!\n您現在可以跳過其餘的設定項目並且立即安裝 Wiki。",
+ "config-optional-continue": "多問我一些問題吧。",
+ "config-optional-skip": "我已經不耐煩了,請趕緊安裝 Wiki。",
+ "config-profile": "使用者權限基本資料:",
+ "config-profile-wiki": "開放式 Wiki",
+ "config-profile-no-anon": "需要註冊帳號",
+ "config-profile-fishbowl": "僅授權的編輯者",
+ "config-profile-private": "封閉式 Wiki",
+ "config-profile-help": "Wiki 最佳的運作方式是盡可能讓大家都可以編輯文件。\n在 MediaWiki,可以很輕易的審查最近做的所有變更動作,並且可以還原由新手或惡意使用者造成的損害。\n\n不論如何,很多人發現 MediaWiki 可以廣泛的運用在各種地方,但並不是很容易可以說服每個人都遵守對 Wiki 有益的方式。\n所以您必須做出以下選擇。\n\n使用 <strong>{{int:config-profile-wiki}}</strong> 模式,允許所有人編輯文章,包含未匿名使用者。\n使用 <strong>{{int:config-profile-no-anon}}</strong> 模式,允許所有人編輯文章,不包含未登入的使用者。此模式較能管理所有使用者的言論,但會扼殺臨時使用者的貢獻機會。\n\n使用 <strong>{{int:config-profile-fishbowl}}</strong> 模式,僅經核准的使用者可以編輯,所有人可以檢視頁面,包含修訂的記錄。\n使用 <strong>{{int:config-profile-private}}</strong> 模式,僅經核准的使用者可以編輯、檢視頁面。\n\n有關更多複雜的使用者權限設定可在安裝程序結束後設定,請參考 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights 相關文件說明]。",
+ "config-license": "版權聲明與授權條款:",
+ "config-license-none": "無授權條款頁腳",
+ "config-license-cc-by-sa": "創作共用 姓名標示-相同方式分享",
+ "config-license-cc-by": "創作共用 Attribution",
+ "config-license-cc-by-nc-sa": "創作共用 Attribution-NonCommercial-ShareAlike",
+ "config-license-cc-0": "創作共用 Zero (公共領域)",
+ "config-license-gfdl": "GNU 自由文件授權條款 1.3 或更高版本",
+ "config-license-pd": "公共領域",
+ "config-license-cc-choose": "請選擇一個自訂的創作共用授權條款",
+ "config-license-help": "許多開放式 Wiki 會以 [http://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": "如果您要使用電子郵件功能,請正確設定 [http://www.php.net/manual/en/mail.configuration.php PHP 的郵件設定]。\n如果您不需要使用電子郵件功能,請在此處關閉。",
+ "config-email-user": "開啟使用者對使用者間的電子郵件互通",
+ "config-email-user-help": "若使用者在個人偏好設定開啟了此功能,則可允許使用者間相互傳送郵件。",
+ "config-email-usertalk": "開啟使用者討論頁面通知",
+ "config-email-usertalk-help": "若使用者在個人偏好開啟了此功能,則可收到使用者討論頁面被修改的通知。",
+ "config-email-watchlist": "開啟監視清單通知",
+ "config-email-watchlist-help": "若使用者在個人偏好開啟了此功能,允許使用者收到與其監視清單有關的通知。",
+ "config-email-auth": "開啟電子郵件身份認證",
+ "config-email-auth-help": "若開啟此選項,使用者不論設定或者更改電子郵件地址,都必須透過收信的方式確認沒有問題。\n只有驗證過的電子郵件地址可以收到來自其他使用者或修改通知的信件。\n公開的 Wiki 會 <strong>建議</strong> 設定此選項,以防使用者濫用電子郵件功能。",
+ "config-email-sender": "電子郵件回覆位址:",
+ "config-email-sender-help": "請輸入要用來做為外寄郵件的電子郵件回覆地址。\n該郵件地址會收到被拒收的信件。\n許多郵件伺服器會要求使用有效的網域名稱。",
+ "config-upload-settings": "圖片和檔案上傳",
+ "config-upload-enable": "開啟檔案上傳",
+ "config-upload-help": "檔案上傳功能會讓您的伺服器暴露在潛藏的安全性風險之下。\n要取得更多相關的資訊,請參考 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security 安全性章節]。\n\n要開啟檔案上傳需要將 MediaWiki 根目錄底下的 <code>images</code> 目錄開啟網頁伺服器的寫入權,\n然後再啟動選項。",
+ "config-upload-deleted": "已刪除檔案的目錄:",
+ "config-upload-deleted-help": "請選擇用來存放已刪除檔案的目錄。\n理想情況下,此目錄不可被網頁直接存取。",
+ "config-logo": "標誌 URL 位置:",
+ "config-logo-help": "在 MediaWiki 的預設介面,側欄選單上方有一塊 135x160 像素用來放置標誌的區域。\n請上傳合適大小的圖片並在此輸入 URL 網址。\n\n您可以透過 <code>$wgStylePath</code> 或者 <code>$wgScriptPath</code> 來表示您的圖片與這些路徑的相對位置。\n\n如果您不想使用標誌,可略過此欄位。",
+ "config-instantcommons": "開啟即時共享資源",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons 即時共享資源] 是允許 Wiki 使用來自 [//commons.wikimedia.org/ Wikimedia Commons] 網站上的圖片、聲音以及其他媒體的一項功能。\n若要開啟此功能,您的 MediaWiki 必須能夠連線網際網路。\n更多有關此功能的訊息,包含如何存除了 Wikimedia Commons 之外其他網站的說明,請參考 \n[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos 操作手冊]。",
+ "config-cc-error": "查無該創作共用授權條款,\n請手動輸入您的授權條款名稱。",
+ "config-cc-again": "請重新選取...",
+ "config-cc-not-chosen": "請選擇您要使用的創作共享授權條款,然後點選 \"繼續\"。",
+ "config-advanced-settings": "進階設定",
+ "config-cache-options": "物件快取設定:",
+ "config-cache-help": "物件快取是用來增進 MediaWiki 速度的一項功能,透過快取經常使用的資料。\n中型到大型的網站我們會建議開啟這個選項,對小型的網站也有一定程度的效果。",
+ "config-cache-none": "不快取 (不會影響功能,但在大型 Wiki 網站可能會有處理速度的問題)",
+ "config-cache-accel": "使用 PHP 物件快取 (APC、XCache 或 WinCache)",
+ "config-cache-memcached": "使用 Memcached (需要額外安裝與設定)",
+ "config-memcached-servers": "Memcached 伺服器:",
+ "config-memcached-help": "請列出 Memcached 伺服器的 IP 位址。\n每一行只指定一個位置並且要註明使用的埠號,例如:\n 127.0.0.1:11211\n 192.168.1.25:1234",
+ "config-memcache-needservers": "您的快取類型選擇使用 Memcached,但並未設定任何的伺服器。",
+ "config-memcache-badip": "您輸入了一筆無效的 Memcached IP 位置:$1。",
+ "config-memcache-noport": "您沒有輸入 Memcached 伺服器的埠號:$1。\n如果您不曉得埠號為多少,預設為 11211。",
+ "config-memcache-badport": "Memcached 埠號應介於 $1 到 $2 之間。",
+ "config-extensions": "擴充套件",
+ "config-extensions-help": "已在您的 <code>./extensions</code> 目錄中發現下列擴充套件。\n\n這些擴充套件可能需要做額外的設定,但您可以現在先開啟功能。",
+ "config-skins": "外觀",
+ "config-skins-help": "系統偵測到您於 <code>./skins</code> 資料夾中含有外觀如上清單。 您必須開啟其中一項並設為預設值。",
+ "config-skins-use-as-default": "使用這種外觀作為預設",
+ "config-skins-missing": "沒有發現任何外觀;MediaWiki在您安裝一些恰當的外觀前將會使用備用外觀。",
+ "config-skins-must-enable-some": "您必須至少選擇一個外觀以啟用。",
+ "config-skins-must-enable-default": "必須啟用選為預設的外觀。",
+ "config-install-alreadydone": "<strong>警告:</strong>您已經安裝 MediaWiki,並且試圖重新安裝。\n請點繼續前往下一個頁面。",
+ "config-install-begin": "請點選 \"{{int:config-continue}}\" 開始安裝 MediaWiki。\n若您還想要修改設定,請點選 \"{{int:config-back}}\"。",
+ "config-install-step-done": "完成",
+ "config-install-step-failed": "失敗",
+ "config-install-extensions": "正在開啟擴充套件",
+ "config-install-database": "正在設定資料庫",
+ "config-install-schema": "建立 Schema",
+ "config-install-pg-schema-not-exist": "PostgreSQL Schema 不存在",
+ "config-install-pg-schema-failed": "資料表建立失敗。\n請確認使用者 \"$1\" 可以寫入 Schema \"$2\"。",
+ "config-install-pg-commit": "提交更改",
+ "config-install-pg-plpgsql": "正在檢查 PL/pgSQL 語言",
+ "config-pg-no-plpgsql": "您需要安裝 PL/pgSQL 到資料庫 $1",
+ "config-pg-no-create-privs": "您所指定用來給安裝程序使用的帳號沒有足夠的權限可以建立新帳號。",
+ "config-pg-not-in-role": "您指定用來給網頁存取的帳號已存在。\n您指定用來給安裝程序使用的的帳號既不是管理者,也不是給網頁存取使用者,因此無法使用網頁存取使用者的權限建立物件。\n\nMediaWiki 目前需要使用由網頁使用者所建立的資料表。請指定另一個網頁使用者的帳號名稱,或點選 \"返回\" 指定具有適當權限使用者給安裝程序使用。",
+ "config-install-user": "正在建立資料庫使用者",
+ "config-install-user-alreadyexists": "使用者 \"$1\" 已存在",
+ "config-install-user-create-failed": "建立使用者 \"$1\" 失敗:$2",
+ "config-install-user-grant-failed": "授序權限給使用者 \"$1\" 失敗:$2",
+ "config-install-user-missing": "指定的使用者 \"$1\" 不存在。",
+ "config-install-user-missing-create": "指定的使用者 \"$1\" 不存在。\n若您想建立帳號,請勾選下方 \"建立帳號\" 核選方塊。",
+ "config-install-tables": "正在建立資料表",
+ "config-install-tables-exist": "<strong>警告:</strong> MediaWiki 資料表已存在,略過建立資料表。",
+ "config-install-tables-failed": "<strong>錯誤:</strong>建立資料表失敗,以下為錯誤訊息:$1",
+ "config-install-interwiki": "正在匯入預設的 interwiki 資料表",
+ "config-install-interwiki-list": "查無檔案 <code>interwiki.list</code>。",
+ "config-install-interwiki-exists": "<strong>警告:</strong> interwiki 資料表內已有資料,略過建立預設資料。",
+ "config-install-stats": "初始化統計資訊",
+ "config-install-keys": "產生秘密金鑰中",
+ "config-insecure-keys": "<strong>警告:</strong>在安裝過程中所產生的 $2 組安全金鑰($1)並不完全安全。請考慮手動更改。",
+ "config-install-sysop": "正在建立管理員使用者帳號",
+ "config-install-subscribe-fail": "無法訂閱 mediawiki-announce:$1",
+ "config-install-subscribe-notpossible": "未安裝 cURL,因此無法使用 <code>allow_url_fopen</code> 設定項目。",
+ "config-install-mainpage": "正在使用預設的內容建立首頁",
+ "config-install-extension-tables": "正在建立已啟動的擴充套件的資料表",
+ "config-install-mainpage-failed": "無法插入首頁:$1",
+ "config-install-done": "<strong>恭喜!</strong>\n您已經成功地安裝了 MediaWiki。\n\n安裝程式已自動產生 <code>LocalSettings.php</code> 檔案,\n該檔案中包含了您所有的設定項目。\n\n您需要下載該檔案,並將其放置在您的 Wiki 的根目錄 (index.php 所在的目錄) 中,下載稍後會自動開始。\n\n若瀏覽器沒有提示您下載,或者您取消了下載,您可以點選下方連結重新下載:\n\n$3\n\n<strong>注意:</strong>若您現在未下載檔案,稍後結束安裝程式之後將無法下載設定檔。\n\n當您完成本步驟後,您可以 <strong>[$2 進入您的 Wiki]</strong>。",
+ "config-download-localsettings": "下載 <code>LocalSettings.php</code>",
+ "config-help": "說明",
+ "config-help-tooltip": "按一下以展開",
+ "config-nofile": "查無檔案 \"$1\",是否已被刪除?",
+ "config-extension-link": "您是否了解您的 Wiki 支援 [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions 擴充套件]?\n\n\n您可以瀏覽 [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category 擴充套件分類] 或 [//www.mediawiki.org/wiki/Extension_Matrix 擴充套件資料表] 以取得相關的資訊。",
+ "mainpagetext": "<strong>已成功安裝 MediaWiki。</strong>",
+ "mainpagedocfooter": "請參閱 [//meta.wikimedia.org/wiki/Help:Contents 使用者手冊] 以取得使用 Wiki 的相關訊息!\n\n== 新手入門 ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings MediaWiki 系統設定]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki 常見問答集]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵寄清單]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources MediaWiki 介面在地化]"
+}
diff --git a/includes/installer/i18n/zh-hk.json b/includes/installer/i18n/zh-hk.json
new file mode 100644
index 00000000..36880f4e
--- /dev/null
+++ b/includes/installer/i18n/zh-hk.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mark85296341"
+ ]
+ },
+ "mainpagedocfooter": "請參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以獲得使用此 wiki 軟件的訊息!\n\n== 入門 ==\n* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]\n* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]"
+}
diff --git a/includes/installer/i18n/zh-tw.json b/includes/installer/i18n/zh-tw.json
new file mode 100644
index 00000000..3926c3b1
--- /dev/null
+++ b/includes/installer/i18n/zh-tw.json
@@ -0,0 +1,4 @@
+{
+ "@metadata": [],
+ "mainpagedocfooter": "請參閱 [//meta.wikimedia.org/wiki/Help:Contents 使用者手冊] 以獲得使用此 wiki 軟體的訊息!\n\n== 入門 ==\n\n* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]\n* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]"
+}
diff --git a/includes/interwiki/Interwiki.php b/includes/interwiki/Interwiki.php
index 4003fa88..55b25069 100644
--- a/includes/interwiki/Interwiki.php
+++ b/includes/interwiki/Interwiki.php
@@ -31,7 +31,25 @@ class Interwiki {
protected static $smCache = array();
const CACHE_LIMIT = 100; // 0 means unlimited, any other value is max number of entries.
- protected $mPrefix, $mURL, $mAPI, $mWikiID, $mLocal, $mTrans;
+ /** @var string The interwiki prefix, (e.g. "Meatball", or the language prefix "de") */
+ protected $mPrefix;
+
+ /** @var string The URL of the wiki, with "$1" as a placeholder for an article name. */
+ protected $mURL;
+
+ /** @var string The URL of the file api.php */
+ protected $mAPI;
+
+ /** @var string The name of the database (for a connection to be established
+ * with wfGetLB( 'wikiid' ))
+ */
+ protected $mWikiID;
+
+ /** @var bool Whether the wiki is in this project */
+ protected $mLocal;
+
+ /** @var bool Whether interwiki transclusions are allowed */
+ protected $mTrans;
public function __construct( $prefix = null, $url = '', $api = '', $wikiId = '', $local = 0,
$trans = 0
@@ -52,6 +70,7 @@ class Interwiki {
*/
public static function isValidInterwiki( $prefix ) {
$result = self::fetch( $prefix );
+
return (bool)$result;
}
@@ -63,13 +82,16 @@ class Interwiki {
*/
public static function fetch( $prefix ) {
global $wgContLang;
+
if ( $prefix == '' ) {
return null;
}
+
$prefix = $wgContLang->lc( $prefix );
if ( isset( self::$smCache[$prefix] ) ) {
return self::$smCache[$prefix];
}
+
global $wgInterwikiCache;
if ( $wgInterwikiCache ) {
$iw = Interwiki::getInterwikiCached( $prefix );
@@ -79,11 +101,14 @@ class Interwiki {
$iw = false;
}
}
+
if ( self::CACHE_LIMIT && count( self::$smCache ) >= self::CACHE_LIMIT ) {
reset( self::$smCache );
unset( self::$smCache[key( self::$smCache )] );
}
+
self::$smCache[$prefix] = $iw;
+
return $iw;
}
@@ -93,7 +118,7 @@ class Interwiki {
* @note More logic is explained in DefaultSettings.
*
* @param string $prefix Interwiki prefix
- * @return Interwiki object
+ * @return Interwiki
*/
protected static function getInterwikiCached( $prefix ) {
$value = self::getInterwikiCacheEntry( $prefix );
@@ -107,6 +132,7 @@ class Interwiki {
} else {
$s = false;
}
+
return $s;
}
@@ -123,28 +149,34 @@ class Interwiki {
static $db, $site;
wfDebug( __METHOD__ . "( $prefix )\n" );
- if ( !$db ) {
- $db = CdbReader::open( $wgInterwikiCache );
- }
- /* Resolve site name */
- if ( $wgInterwikiScopes >= 3 && !$site ) {
- $site = $db->get( '__sites:' . wfWikiID() );
- if ( $site == '' ) {
- $site = $wgInterwikiFallbackSite;
+ $value = false;
+ try {
+ if ( !$db ) {
+ $db = CdbReader::open( $wgInterwikiCache );
+ }
+ /* Resolve site name */
+ if ( $wgInterwikiScopes >= 3 && !$site ) {
+ $site = $db->get( '__sites:' . wfWikiID() );
+ if ( $site == '' ) {
+ $site = $wgInterwikiFallbackSite;
+ }
}
- }
- $value = $db->get( wfMemcKey( $prefix ) );
- // Site level
- if ( $value == '' && $wgInterwikiScopes >= 3 ) {
- $value = $db->get( "_{$site}:{$prefix}" );
- }
- // Global Level
- if ( $value == '' && $wgInterwikiScopes >= 2 ) {
- $value = $db->get( "__global:{$prefix}" );
- }
- if ( $value == 'undef' ) {
- $value = '';
+ $value = $db->get( wfMemcKey( $prefix ) );
+ // Site level
+ if ( $value == '' && $wgInterwikiScopes >= 3 ) {
+ $value = $db->get( "_{$site}:{$prefix}" );
+ }
+ // Global Level
+ if ( $value == '' && $wgInterwikiScopes >= 2 ) {
+ $value = $db->get( "__global:{$prefix}" );
+ }
+ if ( $value == 'undef' ) {
+ $value = '';
+ }
+ } catch ( CdbException $e ) {
+ wfDebug( __METHOD__ . ": CdbException caught, error message was "
+ . $e->getMessage() );
}
return $value;
@@ -154,12 +186,12 @@ class Interwiki {
* Load the interwiki, trying first memcached then the DB
*
* @param string $prefix The interwiki prefix
- * @return bool If $prefix is valid
+ * @return Interwiki|bool Interwiki if $prefix is valid, otherwise false
*/
protected static function load( $prefix ) {
global $wgMemc, $wgInterwikiExpiry;
- $iwData = false;
+ $iwData = array();
if ( !wfRunHooks( 'InterwikiLoadPrefix', array( $prefix, &$iwData ) ) ) {
return Interwiki::loadFromArray( $iwData );
}
@@ -168,11 +200,13 @@ class Interwiki {
$key = wfMemcKey( 'interwiki', $prefix );
$iwData = $wgMemc->get( $key );
if ( $iwData === '!NONEXISTENT' ) {
- return false; // negative cache hit
+ // negative cache hit
+ return false;
}
}
- if ( $iwData && is_array( $iwData ) ) { // is_array is hack for old keys
+ // is_array is hack for old keys
+ if ( $iwData && is_array( $iwData ) ) {
$iw = Interwiki::loadFromArray( $iwData );
if ( $iw ) {
return $iw;
@@ -181,8 +215,13 @@ class Interwiki {
$db = wfGetDB( DB_SLAVE );
- $row = $db->fetchRow( $db->select( 'interwiki', self::selectFields(), array( 'iw_prefix' => $prefix ),
- __METHOD__ ) );
+ $row = $db->fetchRow( $db->select(
+ 'interwiki',
+ self::selectFields(),
+ array( 'iw_prefix' => $prefix ),
+ __METHOD__
+ ) );
+
$iw = Interwiki::loadFromArray( $row );
if ( $iw ) {
$mc = array(
@@ -192,11 +231,13 @@ class Interwiki {
'iw_trans' => $iw->mTrans
);
$wgMemc->add( $key, $mc, $wgInterwikiExpiry );
+
return $iw;
- } else {
- $wgMemc->add( $key, '!NONEXISTENT', $wgInterwikiExpiry ); // negative cache hit
}
+ // negative cache hit
+ $wgMemc->add( $key, '!NONEXISTENT', $wgInterwikiExpiry );
+
return false;
}
@@ -217,6 +258,7 @@ class Interwiki {
return $iw;
}
+
return false;
}
@@ -232,51 +274,55 @@ class Interwiki {
static $db, $site;
wfDebug( __METHOD__ . "()\n" );
- if ( !$db ) {
- $db = CdbReader::open( $wgInterwikiCache );
- }
- /* Resolve site name */
- if ( $wgInterwikiScopes >= 3 && !$site ) {
- $site = $db->get( '__sites:' . wfWikiID() );
- if ( $site == '' ) {
- $site = $wgInterwikiFallbackSite;
- }
- }
-
- // List of interwiki sources
- $sources = array();
- // Global Level
- if ( $wgInterwikiScopes >= 2 ) {
- $sources[] = '__global';
- }
- // Site level
- if ( $wgInterwikiScopes >= 3 ) {
- $sources[] = '_' . $site;
- }
- $sources[] = wfWikiID();
-
$data = array();
-
- foreach ( $sources as $source ) {
- $list = $db->get( "__list:{$source}" );
- foreach ( explode( ' ', $list ) as $iw_prefix ) {
- $row = $db->get( "{$source}:{$iw_prefix}" );
- if ( !$row ) {
- continue;
+ try {
+ if ( !$db ) {
+ $db = CdbReader::open( $wgInterwikiCache );
+ }
+ /* Resolve site name */
+ if ( $wgInterwikiScopes >= 3 && !$site ) {
+ $site = $db->get( '__sites:' . wfWikiID() );
+ if ( $site == '' ) {
+ $site = $wgInterwikiFallbackSite;
}
+ }
- list( $iw_local, $iw_url ) = explode( ' ', $row );
-
- if ( $local !== null && $local != $iw_local ) {
- continue;
+ // List of interwiki sources
+ $sources = array();
+ // Global Level
+ if ( $wgInterwikiScopes >= 2 ) {
+ $sources[] = '__global';
+ }
+ // Site level
+ if ( $wgInterwikiScopes >= 3 ) {
+ $sources[] = '_' . $site;
+ }
+ $sources[] = wfWikiID();
+
+ foreach ( $sources as $source ) {
+ $list = $db->get( "__list:{$source}" );
+ foreach ( explode( ' ', $list ) as $iw_prefix ) {
+ $row = $db->get( "{$source}:{$iw_prefix}" );
+ if ( !$row ) {
+ continue;
+ }
+
+ list( $iw_local, $iw_url ) = explode( ' ', $row );
+
+ if ( $local !== null && $local != $iw_local ) {
+ continue;
+ }
+
+ $data[$iw_prefix] = array(
+ 'iw_prefix' => $iw_prefix,
+ 'iw_url' => $iw_url,
+ 'iw_local' => $iw_local,
+ );
}
-
- $data[$iw_prefix] = array(
- 'iw_prefix' => $iw_prefix,
- 'iw_url' => $iw_url,
- 'iw_local' => $iw_local,
- );
}
+ } catch ( CdbException $e ) {
+ wfDebug( __METHOD__ . ": CdbException caught, error message was "
+ . $e->getMessage() );
}
ksort( $data );
@@ -308,10 +354,12 @@ class Interwiki {
self::selectFields(),
$where, __METHOD__, array( 'ORDER BY' => 'iw_prefix' )
);
+
$retval = array();
foreach ( $res as $row ) {
$retval[] = (array)$row;
}
+
return $retval;
}
@@ -327,9 +375,9 @@ class Interwiki {
if ( $wgInterwikiCache ) {
return self::getAllPrefixesCached( $local );
- } else {
- return self::getAllPrefixesDB( $local );
}
+
+ return self::getAllPrefixesDB( $local );
}
/**
@@ -346,6 +394,7 @@ class Interwiki {
if ( $title !== null ) {
$url = str_replace( "$1", wfUrlencode( $title ), $url );
}
+
return $url;
}
@@ -394,6 +443,7 @@ class Interwiki {
*/
public function getName() {
$msg = wfMessage( 'interwiki-name-' . $this->mPrefix )->inContentLanguage();
+
return !$msg->exists() ? '' : $msg;
}
@@ -404,6 +454,7 @@ class Interwiki {
*/
public function getDescription() {
$msg = wfMessage( 'interwiki-desc-' . $this->mPrefix )->inContentLanguage();
+
return !$msg->exists() ? '' : $msg;
}
diff --git a/includes/job/jobs/HTMLCacheUpdateJob.php b/includes/job/jobs/HTMLCacheUpdateJob.php
deleted file mode 100644
index 44c240bb..00000000
--- a/includes/job/jobs/HTMLCacheUpdateJob.php
+++ /dev/null
@@ -1,263 +0,0 @@
-<?php
-/**
- * HTML cache invalidation of all pages linking to a given title.
- *
- * 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 Cache
- */
-
-/**
- * Job wrapper for HTMLCacheUpdate. Gets run whenever a related
- * job gets called from the queue.
- *
- * This class is designed to work efficiently with small numbers of links, and
- * to work reasonably well with up to ~10^5 links. Above ~10^6 links, the memory
- * and time requirements of loading all backlinked IDs in doUpdate() might become
- * prohibitive. The requirements measured at Wikimedia are approximately:
- *
- * memory: 48 bytes per row
- * time: 16us per row for the query plus processing
- *
- * The reason this query is done is to support partitioning of the job
- * by backlinked ID. The memory issue could be allieviated by doing this query in
- * batches, but of course LIMIT with an offset is inefficient on the DB side.
- *
- * The class is nevertheless a vast improvement on the previous method of using
- * File::getLinksTo() and Title::touchArray(), which uses about 2KB of memory per
- * link.
- *
- * @ingroup JobQueue
- */
-class HTMLCacheUpdateJob extends Job {
- /** @var BacklinkCache */
- protected $blCache;
-
- protected $rowsPerJob, $rowsPerQuery;
-
- /**
- * Construct a job
- * @param $title Title: the title linked to
- * @param array $params job parameters (table, start and end page_ids)
- * @param $id Integer: job id
- */
- function __construct( $title, $params, $id = 0 ) {
- global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
-
- parent::__construct( 'htmlCacheUpdate', $title, $params, $id );
-
- $this->rowsPerJob = $wgUpdateRowsPerJob;
- $this->rowsPerQuery = $wgUpdateRowsPerQuery;
- $this->blCache = $title->getBacklinkCache();
- }
-
- public function run() {
- if ( isset( $this->params['start'] ) && isset( $this->params['end'] ) ) {
- # This is hit when a job is actually performed
- return $this->doPartialUpdate();
- } else {
- # This is hit when the jobs have to be inserted
- return $this->doFullUpdate();
- }
- }
-
- /**
- * Update all of the backlinks
- */
- protected function doFullUpdate() {
- global $wgMaxBacklinksInvalidate;
-
- # Get an estimate of the number of rows from the BacklinkCache
- $max = max( $this->rowsPerJob * 2, $wgMaxBacklinksInvalidate ) + 1;
- $numRows = $this->blCache->getNumLinks( $this->params['table'], $max );
- if ( $wgMaxBacklinksInvalidate !== false && $numRows > $wgMaxBacklinksInvalidate ) {
- wfDebug( "Skipped HTML cache invalidation of {$this->title->getPrefixedText()}." );
- return true;
- }
-
- if ( $numRows > $this->rowsPerJob * 2 ) {
- # Do fast cached partition
- $this->insertPartitionJobs();
- } else {
- # Get the links from the DB
- $titleArray = $this->blCache->getLinks( $this->params['table'] );
- # Check if the row count estimate was correct
- if ( $titleArray->count() > $this->rowsPerJob * 2 ) {
- # Not correct, do accurate partition
- wfDebug( __METHOD__ . ": row count estimate was incorrect, repartitioning\n" );
- $this->insertJobsFromTitles( $titleArray );
- } else {
- $this->invalidateTitles( $titleArray ); // just do the query
- }
- }
-
- return true;
- }
-
- /**
- * Update some of the backlinks, defined by a page ID range
- */
- protected function doPartialUpdate() {
- $titleArray = $this->blCache->getLinks(
- $this->params['table'], $this->params['start'], $this->params['end'] );
- if ( $titleArray->count() <= $this->rowsPerJob * 2 ) {
- # This partition is small enough, do the update
- $this->invalidateTitles( $titleArray );
- } else {
- # Partitioning was excessively inaccurate. Divide the job further.
- # This can occur when a large number of links are added in a short
- # period of time, say by updating a heavily-used template.
- $this->insertJobsFromTitles( $titleArray );
- }
- return true;
- }
-
- /**
- * Partition the current range given by $this->params['start'] and $this->params['end'],
- * using a pre-calculated title array which gives the links in that range.
- * Queue the resulting jobs.
- *
- * @param $titleArray array
- * @param $rootJobParams array
- * @return void
- */
- protected function insertJobsFromTitles( $titleArray, $rootJobParams = array() ) {
- // Carry over any "root job" information
- $rootJobParams = $this->getRootJobParams();
- # We make subpartitions in the sense that the start of the first job
- # will be the start of the parent partition, and the end of the last
- # job will be the end of the parent partition.
- $jobs = array();
- $start = $this->params['start']; # start of the current job
- $numTitles = 0;
- foreach ( $titleArray as $title ) {
- $id = $title->getArticleID();
- # $numTitles is now the number of titles in the current job not
- # including the current ID
- if ( $numTitles >= $this->rowsPerJob ) {
- # Add a job up to but not including the current ID
- $jobs[] = new HTMLCacheUpdateJob( $this->title,
- array(
- 'table' => $this->params['table'],
- 'start' => $start,
- 'end' => $id - 1
- ) + $rootJobParams // carry over information for de-duplication
- );
- $start = $id;
- $numTitles = 0;
- }
- $numTitles++;
- }
- # Last job
- $jobs[] = new HTMLCacheUpdateJob( $this->title,
- array(
- 'table' => $this->params['table'],
- 'start' => $start,
- 'end' => $this->params['end']
- ) + $rootJobParams // carry over information for de-duplication
- );
- wfDebug( __METHOD__ . ": repartitioning into " . count( $jobs ) . " jobs\n" );
-
- if ( count( $jobs ) < 2 ) {
- # I don't think this is possible at present, but handling this case
- # makes the code a bit more robust against future code updates and
- # avoids a potential infinite loop of repartitioning
- wfDebug( __METHOD__ . ": repartitioning failed!\n" );
- $this->invalidateTitles( $titleArray );
- } else {
- JobQueueGroup::singleton()->push( $jobs );
- }
- }
-
- /**
- * @param $rootJobParams array
- * @return void
- */
- protected function insertPartitionJobs( $rootJobParams = array() ) {
- // Carry over any "root job" information
- $rootJobParams = $this->getRootJobParams();
-
- $batches = $this->blCache->partition( $this->params['table'], $this->rowsPerJob );
- if ( !count( $batches ) ) {
- return; // no jobs to insert
- }
-
- $jobs = array();
- foreach ( $batches as $batch ) {
- list( $start, $end ) = $batch;
- $jobs[] = new HTMLCacheUpdateJob( $this->title,
- array(
- 'table' => $this->params['table'],
- 'start' => $start,
- 'end' => $end,
- ) + $rootJobParams // carry over information for de-duplication
- );
- }
-
- JobQueueGroup::singleton()->push( $jobs );
- }
-
- /**
- * Invalidate an array (or iterator) of Title objects, right now
- * @param $titleArray array
- */
- protected function invalidateTitles( $titleArray ) {
- global $wgUseFileCache, $wgUseSquid;
-
- $dbw = wfGetDB( DB_MASTER );
- $timestamp = $dbw->timestamp();
-
- # Get all IDs in this query into an array
- $ids = array();
- foreach ( $titleArray as $title ) {
- $ids[] = $title->getArticleID();
- }
-
- if ( !$ids ) {
- return;
- }
-
- # Don't invalidated pages that were already invalidated
- $touchedCond = isset( $this->params['rootJobTimestamp'] )
- ? array( "page_touched < " .
- $dbw->addQuotes( $dbw->timestamp( $this->params['rootJobTimestamp'] ) ) )
- : array();
-
- # Update page_touched
- $batches = array_chunk( $ids, $this->rowsPerQuery );
- foreach ( $batches as $batch ) {
- $dbw->update( 'page',
- array( 'page_touched' => $timestamp ),
- array( 'page_id' => $batch ) + $touchedCond,
- __METHOD__
- );
- }
-
- # Update squid
- if ( $wgUseSquid ) {
- $u = SquidUpdate::newFromTitles( $titleArray );
- $u->doUpdate();
- }
-
- # Update file cache
- if ( $wgUseFileCache ) {
- foreach ( $titleArray as $title ) {
- HTMLFileCache::clearFileCache( $title );
- }
- }
- }
-}
diff --git a/includes/job/Job.php b/includes/jobqueue/Job.php
index ab7df5d2..ee3f2c2b 100644
--- a/includes/job/Job.php
+++ b/includes/jobqueue/Job.php
@@ -1,6 +1,6 @@
<?php
/**
- * Job queue base code.
+ * Job queue task base code.
*
* 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
@@ -24,31 +24,36 @@
/**
* Class to both describe a background job and handle jobs.
* The queue aspects of this class are now deprecated.
+ * Using the class to push jobs onto queues is deprecated (use JobSpecification).
*
* @ingroup JobQueue
*/
-abstract class Job {
- /**
- * @var Title
- */
- var $title;
+abstract class Job implements IJobSpecification {
+ /** @var string */
+ public $command;
- var $command,
- $params,
- $id,
- $removeDuplicates,
- $error;
+ /** @var array|bool Array of job parameters or false if none */
+ public $params;
- /** @var Array Additional queue metadata */
+ /** @var array Additional queue metadata */
public $metadata = array();
+ /** @var Title */
+ protected $title;
+
+ /** @var bool Expensive jobs may set this to true */
+ protected $removeDuplicates;
+
+ /** @var string Text for error that occurred last */
+ protected $error;
+
/*-------------------------------------------------------------------------
* Abstract functions
*------------------------------------------------------------------------*/
/**
* Run the job
- * @return boolean success
+ * @return bool Success
*/
abstract public function run();
@@ -60,17 +65,17 @@ abstract class Job {
* Create the appropriate object to handle a specific job
*
* @param string $command Job command
- * @param $title Title: Associated title
+ * @param Title $title Associated title
* @param array|bool $params Job parameters
- * @param int $id Job identifier
* @throws MWException
* @return Job
*/
- public static function factory( $command, Title $title, $params = false, $id = 0 ) {
+ public static function factory( $command, Title $title, $params = false ) {
global $wgJobClasses;
if ( isset( $wgJobClasses[$command] ) ) {
$class = $wgJobClasses[$command];
- return new $class( $title, $params, $id );
+
+ return new $class( $title, $params );
}
throw new MWException( "Invalid job command `{$command}`" );
}
@@ -82,12 +87,13 @@ abstract class Job {
* This may add duplicate at insert time, but they will be
* removed later on, when the first one is popped.
*
- * @param array $jobs of Job objects
+ * @param array $jobs Array of Job objects
* @return bool
* @deprecated since 1.21
*/
public static function batchInsert( $jobs ) {
- return JobQueueGroup::singleton()->push( $jobs );
+ JobQueueGroup::singleton()->push( $jobs );
+ return true;
}
/**
@@ -97,12 +103,13 @@ abstract class Job {
* be rolled-back as part of a larger transaction. However,
* large batches of jobs can cause slave lag.
*
- * @param array $jobs of Job objects
+ * @param array $jobs Array of Job objects
* @return bool
* @deprecated since 1.21
*/
public static function safeBatchInsert( $jobs ) {
- return JobQueueGroup::singleton()->push( $jobs, JobQueue::QOS_ATOMIC );
+ JobQueueGroup::singleton()->push( $jobs, JobQueue::QOS_ATOMIC );
+ return true;
}
/**
@@ -110,7 +117,7 @@ abstract class Job {
* actually find a job; it may be adversely affected by concurrent job
* runners.
*
- * @param $type string
+ * @param string $type
* @return Job|bool Returns false if there are no jobs
* @deprecated since 1.21
*/
@@ -122,7 +129,7 @@ abstract class Job {
* Pop a job off the front of the queue.
* This is subject to $wgJobTypesExcludedFromDefaultQueue.
*
- * @return Job or false if there's no jobs
+ * @return Job|bool False if there are no jobs
* @deprecated since 1.21
*/
public static function pop() {
@@ -134,26 +141,17 @@ abstract class Job {
*------------------------------------------------------------------------*/
/**
- * @param $command
- * @param $title
- * @param $params array|bool
- * @param $id int
+ * @param string $command
+ * @param Title $title
+ * @param array|bool $params
*/
- public function __construct( $command, $title, $params = false, $id = 0 ) {
+ public function __construct( $command, $title, $params = false ) {
$this->command = $command;
$this->title = $title;
$this->params = $params;
- $this->id = $id;
-
- $this->removeDuplicates = false; // expensive jobs may set this to true
- }
- /**
- * @return integer May be 0 for jobs stored outside the DB
- * @deprecated since 1.22
- */
- public function getId() {
- return $this->id;
+ // expensive jobs may set this to true
+ $this->removeDuplicates = false;
}
/**
@@ -178,7 +176,7 @@ abstract class Job {
}
/**
- * @return integer|null UNIX timestamp to delay running this job until, otherwise null
+ * @return int|null UNIX timestamp to delay running this job until, otherwise null
* @since 1.22
*/
public function getReleaseTimestamp() {
@@ -203,12 +201,21 @@ abstract class Job {
}
/**
+ * @return int Number of actually "work items" handled in this job
+ * @see $wgJobBackoffThrottling
+ * @since 1.23
+ */
+ public function workItemCount() {
+ return 1;
+ }
+
+ /**
* Subclasses may need to override this to make duplication detection work.
* The resulting map conveys everything that makes the job unique. This is
* only checked if ignoreDuplicates() returns true, meaning that duplicate
* jobs are supposed to be ignored.
*
- * @return Array Map of key/values
+ * @return array Map of key/values
* @since 1.21
*/
public function getDeduplicationInfo() {
@@ -225,13 +232,16 @@ abstract class Job {
// Likewise for jobs with different delay times
unset( $info['params']['jobReleaseTimestamp'] );
}
+
return $info;
}
/**
* @see JobQueue::deduplicateRootJob()
* @param string $key A key that identifies the task
- * @return Array
+ * @return array Map of:
+ * - rootJobSignature : hash (e.g. SHA1) that identifies the task
+ * - rootJobTimestamp : TS_MW timestamp of this instance of the task
* @since 1.21
*/
public static function newRootJobParams( $key ) {
@@ -243,7 +253,7 @@ abstract class Job {
/**
* @see JobQueue::deduplicateRootJob()
- * @return Array
+ * @return array
* @since 1.21
*/
public function getRootJobParams() {
@@ -269,17 +279,26 @@ abstract class Job {
/**
* Insert a single job into the queue.
- * @return bool true on success
+ * @return bool True on success
* @deprecated since 1.21
*/
public function insert() {
- return JobQueueGroup::singleton()->push( $this );
+ JobQueueGroup::singleton()->push( $this );
+ return true;
}
/**
* @return string
*/
public function toString() {
+ $truncFunc = function ( $value ) {
+ $value = (string)$value;
+ if ( mb_strlen( $value ) > 1024 ) {
+ $value = "string(" . mb_strlen( $value ) . ")";
+ }
+ return $value;
+ };
+
$paramString = '';
if ( $this->params ) {
foreach ( $this->params as $key => $value ) {
@@ -287,16 +306,25 @@ abstract class Job {
$paramString .= ' ';
}
if ( is_array( $value ) ) {
- $value = "array(" . count( $value ) . ")";
+ $filteredValue = array();
+ foreach ( $value as $k => $v ) {
+ if ( is_scalar( $v ) ) {
+ $filteredValue[$k] = $truncFunc( $v );
+ } else {
+ $filteredValue = null;
+ break;
+ }
+ }
+ if ( $filteredValue ) {
+ $value = FormatJson::encode( $filteredValue );
+ } else {
+ $value = "array(" . count( $value ) . ")";
+ }
} elseif ( is_object( $value ) && !method_exists( $value, '__toString' ) ) {
$value = "object(" . get_class( $value ) . ")";
}
- $value = (string)$value;
- if ( mb_strlen( $value ) > 1024 ) {
- $value = "string(" . mb_strlen( $value ) . ")";
- }
- $paramString .= "$key=$value";
+ $paramString .= "$key={$truncFunc( $value )}";
}
}
@@ -305,6 +333,7 @@ abstract class Job {
if ( $paramString !== '' ) {
$s .= ' ' . $paramString;
}
+
return $s;
} else {
return "{$this->command} $paramString";
diff --git a/includes/job/JobQueue.php b/includes/jobqueue/JobQueue.php
index 6556ee85..c00d22e9 100644
--- a/includes/job/JobQueue.php
+++ b/includes/jobqueue/JobQueue.php
@@ -29,12 +29,23 @@
* @since 1.21
*/
abstract class JobQueue {
- protected $wiki; // string; wiki ID
- protected $type; // string; job type
- protected $order; // string; job priority for pop()
- protected $claimTTL; // integer; seconds
- protected $maxTries; // integer; maximum number of times to try a job
- protected $checkDelay; // boolean; allow delayed jobs
+ /** @var string Wiki ID */
+ protected $wiki;
+
+ /** @var string Job type */
+ protected $type;
+
+ /** @var string Job priority for pop() */
+ protected $order;
+
+ /** @var int Time to live in seconds */
+ protected $claimTTL;
+
+ /** @var int Maximum number of times to try a job */
+ protected $maxTries;
+
+ /** @var bool Allow delayed jobs */
+ protected $checkDelay;
/** @var BagOStuff */
protected $dupCache;
@@ -44,7 +55,8 @@ abstract class JobQueue {
const ROOTJOB_TTL = 2419200; // integer; seconds to remember root jobs (28 days)
/**
- * @param $params array
+ * @param array $params
+ * @throws MWException
*/
protected function __construct( array $params ) {
$this->wiki = $params['wiki'];
@@ -93,7 +105,7 @@ abstract class JobQueue {
*
* Queue classes should throw an exception if they do not support the options given.
*
- * @param $params array
+ * @param array $params
* @return JobQueue
* @throws MWException
*/
@@ -106,6 +118,7 @@ abstract class JobQueue {
if ( !( $obj instanceof self ) ) {
throw new MWException( "Class '$class' is not a " . __CLASS__ . " class." );
}
+
return $obj;
}
@@ -141,7 +154,7 @@ abstract class JobQueue {
/**
* Get the allowed queue orders for configuration validation
*
- * @return Array Subset of (random, timestamp, fifo, undefined)
+ * @return array Subset of (random, timestamp, fifo, undefined)
*/
abstract protected function supportedOrders();
@@ -155,7 +168,7 @@ abstract class JobQueue {
/**
* Find out if delayed jobs are supported for configuration validation
*
- * @return boolean Whether delayed jobs are supported
+ * @return bool Whether delayed jobs are supported
*/
protected function supportsDelayedJobs() {
return false; // not implemented
@@ -177,6 +190,7 @@ abstract class JobQueue {
wfProfileIn( __METHOD__ );
$res = $this->doIsEmpty();
wfProfileOut( __METHOD__ );
+
return $res;
}
@@ -192,19 +206,20 @@ abstract class JobQueue {
*
* If caching is used, this number might be out of date for a minute.
*
- * @return integer
+ * @return int
* @throws JobQueueError
*/
final public function getSize() {
wfProfileIn( __METHOD__ );
$res = $this->doGetSize();
wfProfileOut( __METHOD__ );
+
return $res;
}
/**
* @see JobQueue::getSize()
- * @return integer
+ * @return int
*/
abstract protected function doGetSize();
@@ -214,19 +229,20 @@ abstract class JobQueue {
*
* If caching is used, this number might be out of date for a minute.
*
- * @return integer
+ * @return int
* @throws JobQueueError
*/
final public function getAcquiredCount() {
wfProfileIn( __METHOD__ );
$res = $this->doGetAcquiredCount();
wfProfileOut( __METHOD__ );
+
return $res;
}
/**
* @see JobQueue::getAcquiredCount()
- * @return integer
+ * @return int
*/
abstract protected function doGetAcquiredCount();
@@ -236,7 +252,7 @@ abstract class JobQueue {
*
* If caching is used, this number might be out of date for a minute.
*
- * @return integer
+ * @return int
* @throws JobQueueError
* @since 1.22
*/
@@ -244,12 +260,13 @@ abstract class JobQueue {
wfProfileIn( __METHOD__ );
$res = $this->doGetDelayedCount();
wfProfileOut( __METHOD__ );
+
return $res;
}
/**
* @see JobQueue::getDelayedCount()
- * @return integer
+ * @return int
*/
protected function doGetDelayedCount() {
return 0; // not implemented
@@ -261,36 +278,37 @@ abstract class JobQueue {
*
* If caching is used, this number might be out of date for a minute.
*
- * @return integer
+ * @return int
* @throws JobQueueError
*/
final public function getAbandonedCount() {
wfProfileIn( __METHOD__ );
$res = $this->doGetAbandonedCount();
wfProfileOut( __METHOD__ );
+
return $res;
}
/**
* @see JobQueue::getAbandonedCount()
- * @return integer
+ * @return int
*/
protected function doGetAbandonedCount() {
return 0; // not implemented
}
/**
- * Push a single jobs into the queue.
+ * Push one or more jobs into the queue.
* This does not require $wgJobClasses to be set for the given job type.
* Outside callers should use JobQueueGroup::push() instead of this function.
*
- * @param $jobs Job|Array
- * @param $flags integer Bitfield (supports JobQueue::QOS_ATOMIC)
- * @return bool Returns false on failure
+ * @param Job|array $jobs A single job or an array of Jobs
+ * @param int $flags Bitfield (supports JobQueue::QOS_ATOMIC)
+ * @return void
* @throws JobQueueError
*/
final public function push( $jobs, $flags = 0 ) {
- return $this->batchPush( is_array( $jobs ) ? $jobs : array( $jobs ), $flags );
+ $this->batchPush( is_array( $jobs ) ? $jobs : array( $jobs ), $flags );
}
/**
@@ -299,13 +317,13 @@ abstract class JobQueue {
* Outside callers should use JobQueueGroup::push() instead of this function.
*
* @param array $jobs List of Jobs
- * @param $flags integer Bitfield (supports JobQueue::QOS_ATOMIC)
- * @return bool Returns false on failure
- * @throws JobQueueError
+ * @param int $flags Bitfield (supports JobQueue::QOS_ATOMIC)
+ * @return void
+ * @throws MWException
*/
final public function batchPush( array $jobs, $flags = 0 ) {
if ( !count( $jobs ) ) {
- return true; // nothing to do
+ return; // nothing to do
}
foreach ( $jobs as $job ) {
@@ -319,14 +337,14 @@ abstract class JobQueue {
}
wfProfileIn( __METHOD__ );
- $ok = $this->doBatchPush( $jobs, $flags );
+ $this->doBatchPush( $jobs, $flags );
wfProfileOut( __METHOD__ );
- return $ok;
}
/**
* @see JobQueue::batchPush()
- * @return bool
+ * @param array $jobs
+ * @param int $flags
*/
abstract protected function doBatchPush( array $jobs, $flags );
@@ -335,8 +353,8 @@ abstract class JobQueue {
* This requires $wgJobClasses to be set for the given job type.
* Outside callers should use JobQueueGroup::pop() instead of this function.
*
+ * @throws MWException
* @return Job|bool Returns false if there are no jobs
- * @throws JobQueueError
*/
final public function pop() {
global $wgJobClasses;
@@ -355,10 +373,12 @@ abstract class JobQueue {
// Flag this job as an old duplicate based on its "root" job...
try {
if ( $job && $this->isRootJobOldDuplicate( $job ) ) {
- JobQueue::incrStats( 'job-pop-duplicate', $this->type );
+ JobQueue::incrStats( 'job-pop-duplicate', $this->type, 1, $this->wiki );
$job = DuplicateJob::newFromJob( $job ); // convert to a no-op
}
- } catch ( MWException $e ) {} // don't lose jobs over this
+ } catch ( MWException $e ) {
+ // don't lose jobs over this
+ }
return $job;
}
@@ -375,23 +395,22 @@ 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
- * @return bool
- * @throws JobQueueError
+ * @param Job $job
+ * @return void
+ * @throws MWException
*/
final public function ack( Job $job ) {
if ( $job->getType() !== $this->type ) {
throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
}
wfProfileIn( __METHOD__ );
- $ok = $this->doAck( $job );
+ $this->doAck( $job );
wfProfileOut( __METHOD__ );
- return $ok;
}
/**
* @see JobQueue::ack()
- * @return bool
+ * @param Job $job
*/
abstract protected function doAck( Job $job );
@@ -409,10 +428,10 @@ abstract class JobQueue {
* that are actually inserted into the queue. For example, "refreshLinks" jobs are
* spawned when a template is edited. One can think of the task as "update links
* of pages that use template X" and an instance of that task as a "root job".
- * However, what actually goes into the queue are potentially many refreshLinks2 jobs.
- * Since these jobs include things like page ID ranges and DB master positions, and morph
- * into smaller refreshLinks2 jobs recursively, simple duplicate detection (like job_sha1)
- * for individual jobs being identical is not useful.
+ * However, what actually goes into the queue are range and leaf job subtypes.
+ * Since these jobs include things like page ID ranges and DB master positions,
+ * and can morph into smaller jobs recursively, simple duplicate detection
+ * for individual jobs being identical (like that of job_sha1) is not useful.
*
* In the case of "refreshLinks", if these jobs are still in the queue when the template
* is edited again, we want all of these old refreshLinks jobs for that template to become
@@ -422,9 +441,9 @@ abstract class JobQueue {
*
* This does nothing for certain queue classes.
*
- * @param $job Job
+ * @param Job $job
+ * @throws MWException
* @return bool
- * @throws JobQueueError
*/
final public function deduplicateRootJob( Job $job ) {
if ( $job->getType() !== $this->type ) {
@@ -433,12 +452,14 @@ abstract class JobQueue {
wfProfileIn( __METHOD__ );
$ok = $this->doDeduplicateRootJob( $job );
wfProfileOut( __METHOD__ );
+
return $ok;
}
/**
* @see JobQueue::deduplicateRootJob()
- * @param $job Job
+ * @param Job $job
+ * @throws MWException
* @return bool
*/
protected function doDeduplicateRootJob( Job $job ) {
@@ -465,9 +486,9 @@ abstract class JobQueue {
/**
* Check if the "root" job of a given job has been superseded by a newer one
*
- * @param $job Job
+ * @param Job $job
+ * @throws MWException
* @return bool
- * @throws JobQueueError
*/
final protected function isRootJobOldDuplicate( Job $job ) {
if ( $job->getType() !== $this->type ) {
@@ -476,6 +497,7 @@ abstract class JobQueue {
wfProfileIn( __METHOD__ );
$isDuplicate = $this->doIsRootJobOldDuplicate( $job );
wfProfileOut( __METHOD__ );
+
return $isDuplicate;
}
@@ -504,26 +526,26 @@ abstract class JobQueue {
*/
protected function getRootJobCacheKey( $signature ) {
list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+
return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, 'rootjob', $signature );
}
/**
* Deleted all unclaimed and delayed jobs from the queue
*
- * @return bool Success
* @throws JobQueueError
* @since 1.22
+ * @return void
*/
final public function delete() {
wfProfileIn( __METHOD__ );
- $res = $this->doDelete();
+ $this->doDelete();
wfProfileOut( __METHOD__ );
- return $res;
}
/**
* @see JobQueue::delete()
- * @return bool Success
+ * @throws MWException
*/
protected function doDelete() {
throw new MWException( "This method is not implemented." );
@@ -547,7 +569,8 @@ abstract class JobQueue {
* @see JobQueue::waitForBackups()
* @return void
*/
- protected function doWaitForBackups() {}
+ protected function doWaitForBackups() {
+ }
/**
* Return a map of task names to task definition maps.
@@ -559,19 +582,20 @@ abstract class JobQueue {
* - callback : a PHP callable that performs the task
* - period : the period in seconds corresponding to the task frequency
*
- * @return Array
+ * @return array
*/
final public function getPeriodicTasks() {
$tasks = $this->doGetPeriodicTasks();
foreach ( $tasks as $name => &$def ) {
$def['name'] = $name;
}
+
return $tasks;
}
/**
* @see JobQueue::getPeriodicTasks()
- * @return Array
+ * @return array
*/
protected function doGetPeriodicTasks() {
return array();
@@ -592,7 +616,8 @@ abstract class JobQueue {
* @see JobQueue::flushCaches()
* @return void
*/
- protected function doFlushCaches() {}
+ protected function doFlushCaches() {
+ }
/**
* Get an iterator to traverse over all available jobs in this queue.
@@ -637,6 +662,7 @@ abstract class JobQueue {
*/
final public function getSiblingQueuesWithJobs( array $types ) {
$section = new ProfileSection( __METHOD__ );
+
return $this->doGetSiblingQueuesWithJobs( $types );
}
@@ -661,6 +687,7 @@ abstract class JobQueue {
*/
final public function getSiblingQueueSizes( array $types ) {
$section = new ProfileSection( __METHOD__ );
+
return $this->doGetSiblingQueueSizes( $types );
}
@@ -678,18 +705,22 @@ abstract class JobQueue {
*
* @param string $key Event type
* @param string $type Job type
- * @param integer $delta
+ * @param int $delta
+ * @param string $wiki Wiki ID (added in 1.23)
* @since 1.22
*/
- public static function incrStats( $key, $type, $delta = 1 ) {
+ public static function incrStats( $key, $type, $delta = 1, $wiki = null ) {
wfIncrStats( $key, $delta );
wfIncrStats( "{$key}-{$type}", $delta );
+ if ( $wiki !== null ) {
+ wfIncrStats( "{$key}-{$type}-{$wiki}", $delta );
+ }
}
/**
* Namespace the queue with a key to isolate it for testing
*
- * @param $key string
+ * @param string $key
* @return void
* @throws MWException
*/
@@ -702,5 +733,8 @@ abstract class JobQueue {
* @ingroup JobQueue
* @since 1.22
*/
-class JobQueueError extends MWException {}
-class JobQueueConnectionError extends JobQueueError {}
+class JobQueueError extends MWException {
+}
+
+class JobQueueConnectionError extends JobQueueError {
+}
diff --git a/includes/job/JobQueueDB.php b/includes/jobqueue/JobQueueDB.php
index c39083df..08873cc1 100644
--- a/includes/job/JobQueueDB.php
+++ b/includes/jobqueue/JobQueueDB.php
@@ -37,7 +37,8 @@ class JobQueueDB extends JobQueue {
/** @var BagOStuff */
protected $cache;
- protected $cluster = false; // string; name of an external DB cluster
+ /** @var bool|string Name of an external DB cluster. False if not set */
+ protected $cluster = false;
/**
* Additional parameters include:
@@ -45,7 +46,7 @@ class JobQueueDB extends JobQueue {
* If not specified, the primary DB cluster for the wiki will be used.
* This can be overridden with a custom cluster so that DB handles will
* be retrieved via LBFactory::getExternalLB() and getConnection().
- * @param $params array
+ * @param array $params
*/
protected function __construct( array $params ) {
global $wgMemc;
@@ -94,7 +95,7 @@ class JobQueueDB extends JobQueue {
/**
* @see JobQueue::doGetSize()
- * @return integer
+ * @return int
*/
protected function doGetSize() {
$key = $this->getCacheKey( 'size' );
@@ -120,7 +121,7 @@ class JobQueueDB extends JobQueue {
/**
* @see JobQueue::doGetAcquiredCount()
- * @return integer
+ * @return int
*/
protected function doGetAcquiredCount() {
if ( $this->claimTTL <= 0 ) {
@@ -150,7 +151,7 @@ class JobQueueDB extends JobQueue {
/**
* @see JobQueue::doGetAbandonedCount()
- * @return integer
+ * @return int
* @throws MWException
*/
protected function doGetAbandonedCount() {
@@ -188,9 +189,9 @@ class JobQueueDB extends JobQueue {
/**
* @see JobQueue::doBatchPush()
* @param array $jobs
- * @param $flags
+ * @param int $flags
* @throws DBError|Exception
- * @return bool
+ * @return void
*/
protected function doBatchPush( array $jobs, $flags ) {
$dbw = $this->getMasterDB();
@@ -198,27 +199,25 @@ class JobQueueDB extends JobQueue {
$that = $this;
$method = __METHOD__;
$dbw->onTransactionIdle(
- function() use ( $dbw, $that, $jobs, $flags, $method ) {
+ function () use ( $dbw, $that, $jobs, $flags, $method ) {
$that->doBatchPushInternal( $dbw, $jobs, $flags, $method );
}
);
-
- return true;
}
/**
* This function should *not* be called outside of JobQueueDB
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param array $jobs
* @param int $flags
* @param string $method
- * @return boolean
- * @throws type
+ * @throws DBError
+ * @return void
*/
public function doBatchPushInternal( IDatabase $dbw, array $jobs, $flags, $method ) {
if ( !count( $jobs ) ) {
- return true;
+ return;
}
$rowSet = array(); // (sha1 => job) map for jobs that are de-duplicated
@@ -257,9 +256,13 @@ class JobQueueDB extends JobQueue {
foreach ( array_chunk( $rows, 50 ) as $rowBatch ) {
$dbw->insert( 'job', $rowBatch, $method );
}
- JobQueue::incrStats( 'job-insert', $this->type, count( $rows ) );
- JobQueue::incrStats( 'job-insert-duplicate', $this->type,
- count( $rowSet ) + count( $rowList ) - count( $rows ) );
+ JobQueue::incrStats( 'job-insert', $this->type, count( $rows ), $this->wiki );
+ JobQueue::incrStats(
+ 'job-insert-duplicate',
+ $this->type,
+ count( $rowSet ) + count( $rowList ) - count( $rows ),
+ $this->wiki
+ );
} catch ( DBError $e ) {
if ( $flags & self::QOS_ATOMIC ) {
$dbw->rollback( $method );
@@ -272,7 +275,7 @@ class JobQueueDB extends JobQueue {
$this->cache->set( $this->getCacheKey( 'empty' ), 'false', JobQueueDB::CACHE_TTL_LONG );
- return true;
+ return;
}
/**
@@ -289,7 +292,7 @@ class JobQueueDB extends JobQueue {
$dbw->commit( __METHOD__, 'flush' ); // flush existing transaction
$autoTrx = $dbw->getFlag( DBO_TRX ); // get current setting
$dbw->clearFlag( DBO_TRX ); // make each query its own transaction
- $scopedReset = new ScopedCallback( function() use ( $dbw, $autoTrx ) {
+ $scopedReset = new ScopedCallback( function () use ( $dbw, $autoTrx ) {
$dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore old setting
} );
@@ -309,18 +312,17 @@ class JobQueueDB extends JobQueue {
$this->cache->set( $this->getCacheKey( 'empty' ), 'true', self::CACHE_TTL_LONG );
break; // nothing to do
}
- JobQueue::incrStats( 'job-pop', $this->type );
+ JobQueue::incrStats( 'job-pop', $this->type, 1, $this->wiki );
// Get the job object from the row...
$title = Title::makeTitleSafe( $row->job_namespace, $row->job_title );
if ( !$title ) {
$dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- wfDebug( "Row has invalid title '{$row->job_title}'." );
+ wfDebug( "Row has invalid title '{$row->job_title}'.\n" );
continue; // try again
}
$job = Job::factory( $row->job_cmd, $title,
self::extractBlob( $row->job_params ), $row->job_id );
$job->metadata['id'] = $row->job_id;
- $job->id = $row->job_id; // XXX: work around broken subclasses
break; // done
} while ( true );
} catch ( DBError $e ) {
@@ -334,9 +336,9 @@ class JobQueueDB extends JobQueue {
* Reserve a row with a single UPDATE without holding row locks over RTTs...
*
* @param string $uuid 32 char hex string
- * @param $rand integer Random unsigned integer (31 bits)
+ * @param int $rand Random unsigned integer (31 bits)
* @param bool $gte Search for job_random >= $random (otherwise job_random <= $random)
- * @return Row|false
+ * @return stdClass|bool Row|false
*/
protected function claimRandom( $uuid, $rand, $gte ) {
$dbw = $this->getMasterDB();
@@ -355,7 +357,7 @@ class JobQueueDB extends JobQueue {
// Instead, this uses job_random to pick a row (possibly checking both directions).
$ineq = $gte ? '>=' : '<=';
$dir = $gte ? 'ASC' : 'DESC';
- $row = $dbw->selectRow( 'job', '*', // find a random job
+ $row = $dbw->selectRow( 'job', self::selectFields(), // find a random job
array(
'job_cmd' => $this->type,
'job_token' => '', // unclaimed
@@ -372,7 +374,7 @@ class JobQueueDB extends JobQueue {
// Bug 42614: "ORDER BY job_random" with a job_random inequality causes high CPU
// in MySQL if there are many rows for some reason. This uses a small OFFSET
// instead of job_random for reducing excess claim retries.
- $row = $dbw->selectRow( 'job', '*', // find a random job
+ $row = $dbw->selectRow( 'job', self::selectFields(), // find a random job
array(
'job_cmd' => $this->type,
'job_token' => '', // unclaimed
@@ -386,6 +388,7 @@ class JobQueueDB extends JobQueue {
continue; // use job_random
}
}
+
if ( $row ) { // claim the job
$dbw->update( 'job', // update by PK
array(
@@ -412,7 +415,7 @@ class JobQueueDB extends JobQueue {
* Reserve a row with a single UPDATE without holding row locks over RTTs...
*
* @param string $uuid 32 char hex string
- * @return Row|false
+ * @return stdClass|bool Row|false
*/
protected function claimOldest( $uuid ) {
$dbw = $this->getMasterDB();
@@ -455,11 +458,11 @@ class JobQueueDB extends JobQueue {
}
// Fetch any row that we just reserved...
if ( $dbw->affectedRows() ) {
- $row = $dbw->selectRow( 'job', '*',
+ $row = $dbw->selectRow( 'job', self::selectFields(),
array( 'job_cmd' => $this->type, 'job_token' => $uuid ), __METHOD__
);
if ( !$row ) { // raced out by duplicate job removal
- wfDebug( "Row deleted as duplicate by another process." );
+ wfDebug( "Row deleted as duplicate by another process.\n" );
}
} else {
break; // nothing to do
@@ -485,7 +488,7 @@ class JobQueueDB extends JobQueue {
$dbw->commit( __METHOD__, 'flush' ); // flush existing transaction
$autoTrx = $dbw->getFlag( DBO_TRX ); // get current setting
$dbw->clearFlag( DBO_TRX ); // make each query its own transaction
- $scopedReset = new ScopedCallback( function() use ( $dbw, $autoTrx ) {
+ $scopedReset = new ScopedCallback( function () use ( $dbw, $autoTrx ) {
$dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore old setting
} );
@@ -520,7 +523,7 @@ class JobQueueDB extends JobQueue {
// jobs to become no-ops without any actual jobs that made them redundant.
$dbw = $this->getMasterDB();
$cache = $this->dupCache;
- $dbw->onTransactionIdle( function() use ( $cache, $params, $key, $dbw ) {
+ $dbw->onTransactionIdle( function () use ( $cache, $params, $key, $dbw ) {
$timestamp = $cache->get( $key ); // current last timestamp of this job
if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
return true; // a newer version of this root job was enqueued
@@ -544,6 +547,7 @@ class JobQueueDB extends JobQueue {
} catch ( DBError $e ) {
$this->throwDBException( $e );
}
+
return true;
}
@@ -556,7 +560,7 @@ class JobQueueDB extends JobQueue {
}
/**
- * @return Array
+ * @return array
*/
protected function doGetPeriodicTasks() {
return array(
@@ -584,17 +588,15 @@ class JobQueueDB extends JobQueue {
$dbr = $this->getSlaveDB();
try {
return new MappedIterator(
- $dbr->select( 'job', '*',
+ $dbr->select( 'job', self::selectFields(),
array( 'job_cmd' => $this->getType(), 'job_token' => '' ) ),
- function( $row ) use ( $dbr ) {
+ function ( $row ) use ( $dbr ) {
$job = Job::factory(
$row->job_cmd,
Title::makeTitle( $row->job_namespace, $row->job_title ),
- strlen( $row->job_params ) ? unserialize( $row->job_params ) : false,
- $row->job_id
+ strlen( $row->job_params ) ? unserialize( $row->job_params ) : false
);
$job->metadata['id'] = $row->job_id;
- $job->id = $row->job_id; // XXX: work around broken subclasses
return $job;
}
);
@@ -618,6 +620,7 @@ class JobQueueDB extends JobQueue {
foreach ( $res as $row ) {
$types[] = $row->job_cmd;
}
+
return $types;
}
@@ -630,13 +633,14 @@ class JobQueueDB extends JobQueue {
foreach ( $res as $row ) {
$sizes[$row->job_cmd] = (int)$row->count;
}
+
return $sizes;
}
/**
* Recycle or destroy any jobs that have been claimed for too long
*
- * @return integer Number of jobs recycled/deleted
+ * @return int Number of jobs recycled/deleted
*/
public function recycleAndDeleteStaleJobs() {
$now = time();
@@ -663,7 +667,7 @@ class JobQueueDB extends JobQueue {
__METHOD__
);
$ids = array_map(
- function( $o ) {
+ function ( $o ) {
return $o->job_id;
}, iterator_to_array( $res )
);
@@ -679,8 +683,9 @@ class JobQueueDB extends JobQueue {
'job_id' => $ids ),
__METHOD__
);
- $count += $dbw->affectedRows();
- JobQueue::incrStats( 'job-recycle', $this->type, $dbw->affectedRows() );
+ $affected = $dbw->affectedRows();
+ $count += $affected;
+ JobQueue::incrStats( 'job-recycle', $this->type, $affected, $this->wiki );
$this->cache->set( $this->getCacheKey( 'empty' ), 'false', self::CACHE_TTL_LONG );
}
}
@@ -699,14 +704,15 @@ class JobQueueDB extends JobQueue {
// the IDs first means that the UPDATE can be done by primary key (less deadlocks).
$res = $dbw->select( 'job', 'job_id', $conds, __METHOD__ );
$ids = array_map(
- function( $o ) {
+ function ( $o ) {
return $o->job_id;
}, iterator_to_array( $res )
);
if ( count( $ids ) ) {
$dbw->delete( 'job', array( 'job_id' => $ids ), __METHOD__ );
- $count += $dbw->affectedRows();
- JobQueue::incrStats( 'job-abandon', $this->type, $dbw->affectedRows() );
+ $affected = $dbw->affectedRows();
+ $count += $affected;
+ JobQueue::incrStats( 'job-abandon', $this->type, $affected, $this->wiki );
}
$dbw->unlock( "jobqueue-recycle-{$this->type}", __METHOD__ );
@@ -718,29 +724,31 @@ class JobQueueDB extends JobQueue {
}
/**
- * @param $job Job
+ * @param IJobSpecification $job
* @return array
*/
- protected function insertFields( Job $job ) {
+ protected function insertFields( IJobSpecification $job ) {
$dbw = $this->getMasterDB();
+
return array(
// Fields that describe the nature of the job
- 'job_cmd' => $job->getType(),
+ 'job_cmd' => $job->getType(),
'job_namespace' => $job->getTitle()->getNamespace(),
- 'job_title' => $job->getTitle()->getDBkey(),
- 'job_params' => self::makeBlob( $job->getParams() ),
+ 'job_title' => $job->getTitle()->getDBkey(),
+ 'job_params' => self::makeBlob( $job->getParams() ),
// Additional job metadata
- 'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
+ 'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
'job_timestamp' => $dbw->timestamp(),
- 'job_sha1' => wfBaseConvert(
+ 'job_sha1' => wfBaseConvert(
sha1( serialize( $job->getDeduplicationInfo() ) ),
16, 36, 31
),
- 'job_random' => mt_rand( 0, self::MAX_JOB_RANDOM )
+ 'job_random' => mt_rand( 0, self::MAX_JOB_RANDOM )
);
}
/**
+ * @throws JobQueueConnectionError
* @return DBConnRef
*/
protected function getSlaveDB() {
@@ -752,6 +760,7 @@ class JobQueueDB extends JobQueue {
}
/**
+ * @throws JobQueueConnectionError
* @return DBConnRef
*/
protected function getMasterDB() {
@@ -763,27 +772,30 @@ class JobQueueDB extends JobQueue {
}
/**
- * @param $index integer (DB_SLAVE/DB_MASTER)
+ * @param int $index (DB_SLAVE/DB_MASTER)
* @return DBConnRef
*/
protected function getDB( $index ) {
$lb = ( $this->cluster !== false )
? wfGetLBFactory()->getExternalLB( $this->cluster, $this->wiki )
: wfGetLB( $this->wiki );
+
return $lb->getConnectionRef( $index, array(), $this->wiki );
}
/**
+ * @param string $property
* @return string
*/
private function getCacheKey( $property ) {
list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
$cluster = is_string( $this->cluster ) ? $this->cluster : 'main';
+
return wfForeignMemcKey( $db, $prefix, 'jobqueue', $cluster, $this->type, $property );
}
/**
- * @param $params
+ * @param array|bool $params
* @return string
*/
protected static function makeBlob( $params ) {
@@ -795,7 +807,7 @@ class JobQueueDB extends JobQueue {
}
/**
- * @param $blob
+ * @param string $blob
* @return bool|mixed
*/
protected static function extractBlob( $blob ) {
@@ -813,4 +825,25 @@ class JobQueueDB extends JobQueue {
protected function throwDBException( DBError $e ) {
throw new JobQueueError( get_class( $e ) . ": " . $e->getMessage() );
}
+
+ /**
+ * Return the list of job fields that should be selected.
+ * @since 1.23
+ * @return array
+ */
+ public static function selectFields() {
+ return array(
+ 'job_id',
+ 'job_cmd',
+ 'job_namespace',
+ 'job_title',
+ 'job_timestamp',
+ 'job_params',
+ 'job_random',
+ 'job_attempts',
+ 'job_token',
+ 'job_token_timestamp',
+ 'job_sha1',
+ );
+ }
}
diff --git a/includes/job/JobQueueFederated.php b/includes/jobqueue/JobQueueFederated.php
index d3ce164a..c4301eed 100644
--- a/includes/job/JobQueueFederated.php
+++ b/includes/jobqueue/JobQueueFederated.php
@@ -47,20 +47,24 @@
* @since 1.22
*/
class JobQueueFederated extends JobQueue {
- /** @var Array (partition name => weight) reverse sorted by weight */
- protected $partitionMap = array();
- /** @var Array (partition name => JobQueue) reverse sorted by weight */
- protected $partitionQueues = array();
+ /** @var HashRing */
+ protected $partitionRing;
/** @var HashRing */
protected $partitionPushRing;
+ /** @var array (partition name => JobQueue) reverse sorted by weight */
+ protected $partitionQueues = array();
+
/** @var BagOStuff */
protected $cache;
+ /** @var int Maximum number of partitions to try */
+ protected $maxPartitionsTry;
+
const CACHE_TTL_SHORT = 30; // integer; seconds to cache info without re-validating
const CACHE_TTL_LONG = 300; // integer; seconds to cache info that is kept up to date
/**
- * @params include:
+ * @param array $params Possible keys:
* - sectionsByWiki : A map of wiki IDs to section names.
* Wikis will default to using the section "default".
* - partitionsBySection : Map of section names to maps of (partition name => weight).
@@ -72,7 +76,11 @@ class JobQueueFederated extends JobQueue {
* the federated queue itself (e.g. 'order' and 'claimTTL').
* - partitionsNoPush : List of partition names that can handle pop() but not push().
* This can be used to migrate away from a certain partition.
- * @param array $params
+ * - maxPartitionsTry : Maximum number of times to attempt job insertion using
+ * different partition queues. This improves availability
+ * during failure, at the cost of added latency and somewhat
+ * less reliable job de-duplication mechanisms.
+ * @throws MWException
*/
protected function __construct( array $params ) {
parent::__construct( $params );
@@ -82,11 +90,14 @@ class JobQueueFederated extends JobQueue {
if ( !isset( $params['partitionsBySection'][$section] ) ) {
throw new MWException( "No configuration for section '$section'." );
}
+ $this->maxPartitionsTry = isset( $params['maxPartitionsTry'] )
+ ? $params['maxPartitionsTry']
+ : 2;
// Get the full partition map
- $this->partitionMap = $params['partitionsBySection'][$section];
- arsort( $this->partitionMap, SORT_NUMERIC );
+ $partitionMap = $params['partitionsBySection'][$section];
+ arsort( $partitionMap, SORT_NUMERIC );
// Get the partitions jobs can actually be pushed to
- $partitionPushMap = $this->partitionMap;
+ $partitionPushMap = $partitionMap;
if ( isset( $params['partitionsNoPush'] ) ) {
foreach ( $params['partitionsNoPush'] as $partition ) {
unset( $partitionPushMap[$partition] );
@@ -94,23 +105,29 @@ class JobQueueFederated extends JobQueue {
}
// Get the config to pass to merge into each partition queue config
$baseConfig = $params;
- foreach ( array( 'class', 'sectionsByWiki',
- 'partitionsBySection', 'configByPartition', 'partitionsNoPush' ) as $o )
- {
- unset( $baseConfig[$o] );
+ foreach ( array( 'class', 'sectionsByWiki', 'maxPartitionsTry',
+ 'partitionsBySection', 'configByPartition', 'partitionsNoPush' ) as $o
+ ) {
+ unset( $baseConfig[$o] ); // partition queue doesn't care about this
}
// Get the partition queue objects
- foreach ( $this->partitionMap as $partition => $w ) {
+ foreach ( $partitionMap as $partition => $w ) {
if ( !isset( $params['configByPartition'][$partition] ) ) {
throw new MWException( "No configuration for partition '$partition'." );
}
$this->partitionQueues[$partition] = JobQueue::factory(
$baseConfig + $params['configByPartition'][$partition] );
}
+ // Ring of all partitions
+ $this->partitionRing = new HashRing( $partitionMap );
// Get the ring of partitions to push jobs into
- $this->partitionPushRing = new HashRing( $partitionPushMap );
+ if ( count( $partitionPushMap ) === count( $partitionMap ) ) {
+ $this->partitionPushRing = clone $this->partitionRing; // faster
+ } else {
+ $this->partitionPushRing = new HashRing( $partitionPushMap );
+ }
// Aggregate cache some per-queue values if there are multiple partition queues
- $this->cache = count( $this->partitionMap ) > 1 ? wfGetMainCache() : new EmptyBagOStuff();
+ $this->cache = count( $partitionMap ) > 1 ? wfGetMainCache() : new EmptyBagOStuff();
}
protected function supportedOrders() {
@@ -136,19 +153,20 @@ class JobQueueFederated extends JobQueue {
return false;
}
+ $empty = true;
+ $failed = 0;
foreach ( $this->partitionQueues as $queue ) {
try {
- if ( !$queue->doIsEmpty() ) {
- $this->cache->add( $key, 'false', self::CACHE_TTL_LONG );
- return false;
- }
+ $empty = $empty && $queue->doIsEmpty();
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
- $this->cache->add( $key, 'true', self::CACHE_TTL_LONG );
- return true;
+ $this->cache->add( $key, $empty ? 'true' : 'false', self::CACHE_TTL_LONG );
+ return $empty;
}
protected function doGetSize() {
@@ -170,48 +188,59 @@ class JobQueueFederated extends JobQueue {
/**
* @param string $type
* @param string $method
- * @return integer
+ * @return int
*/
protected function getCrossPartitionSum( $type, $method ) {
$key = $this->getCacheKey( $type );
$count = $this->cache->get( $key );
- if ( is_int( $count ) ) {
+ if ( $count !== false ) {
return $count;
}
- $count = 0;
+ $failed = 0;
foreach ( $this->partitionQueues as $queue ) {
try {
$count += $queue->$method();
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
$this->cache->set( $key, $count, self::CACHE_TTL_SHORT );
+
return $count;
}
protected function doBatchPush( array $jobs, $flags ) {
- if ( !count( $jobs ) ) {
- return true; // nothing to do
- }
// Local ring variable that may be changed to point to a new ring on failure
$partitionRing = $this->partitionPushRing;
- // Try to insert the jobs and update $partitionsTry on any failures
- $jobsLeft = $this->tryJobInsertions( $jobs, $partitionRing, $flags );
- if ( count( $jobsLeft ) ) { // some jobs failed to insert?
- // Try to insert the remaning jobs once more, ignoring the bad partitions
- return !count( $this->tryJobInsertions( $jobsLeft, $partitionRing, $flags ) );
+ // Try to insert the jobs and update $partitionsTry on any failures.
+ // Retry to insert any remaning jobs again, ignoring the bad partitions.
+ $jobsLeft = $jobs;
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
+ for ( $i = $this->maxPartitionsTry; $i > 0 && count( $jobsLeft ); --$i ) {
+ // @codingStandardsIgnoreEnd
+ try {
+ $partitionRing->getLiveRing();
+ } catch ( UnexpectedValueException $e ) {
+ break; // all servers down; nothing to insert to
+ }
+ $jobsLeft = $this->tryJobInsertions( $jobsLeft, $partitionRing, $flags );
+ }
+ if ( count( $jobsLeft ) ) {
+ throw new JobQueueError(
+ "Could not insert job(s), {$this->maxPartitionsTry} partitions tried." );
}
- return true;
}
/**
* @param array $jobs
* @param HashRing $partitionRing
- * @param integer $flags
+ * @param int $flags
+ * @throws JobQueueError
* @return array List of Job object that could not be inserted
*/
protected function tryJobInsertions( array $jobs, HashRing &$partitionRing, $flags ) {
@@ -221,10 +250,11 @@ class JobQueueFederated extends JobQueue {
// to use a consistent hash to avoid allowing duplicate jobs per partition.
// When inserting a batch of de-duplicated jobs, QOS_ATOMIC is disregarded.
$uJobsByPartition = array(); // (partition name => job list)
+ /** @var Job $job */
foreach ( $jobs as $key => $job ) {
if ( $job->ignoreDuplicates() ) {
$sha1 = sha1( serialize( $job->getDeduplicationInfo() ) );
- $uJobsByPartition[$partitionRing->getLocation( $sha1 )][] = $job;
+ $uJobsByPartition[$partitionRing->getLiveLocation( $sha1 )][] = $job;
unset( $jobs[$key] );
}
}
@@ -240,20 +270,21 @@ class JobQueueFederated extends JobQueue {
// Insert the de-duplicated jobs into the queues...
foreach ( $uJobsByPartition as $partition => $jobBatch ) {
+ /** @var JobQueue $queue */
$queue = $this->partitionQueues[$partition];
try {
- $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
+ $ok = true;
+ $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
} catch ( JobQueueError $e ) {
$ok = false;
MWExceptionHandler::logException( $e );
}
if ( $ok ) {
$key = $this->getCacheKey( 'empty' );
- $this->cache->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
+ $this->cache->set( $key, 'false', self::CACHE_TTL_LONG );
} else {
- $partitionRing = $partitionRing->newWithoutLocation( $partition ); // blacklist
- if ( !$partitionRing ) {
- throw new JobQueueError( "Could not insert job(s), all partitions are down." );
+ if ( !$partitionRing->ejectFromLiveRing( $partition, 5 ) ) { // blacklist
+ throw new JobQueueError( "Could not insert job(s), no partitions available." );
}
$jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
}
@@ -261,21 +292,21 @@ class JobQueueFederated extends JobQueue {
// Insert the jobs that are not de-duplicated into the queues...
foreach ( $nuJobBatches as $jobBatch ) {
- $partition = ArrayUtils::pickRandom( $partitionRing->getLocationWeights() );
+ $partition = ArrayUtils::pickRandom( $partitionRing->getLiveLocationWeights() );
$queue = $this->partitionQueues[$partition];
try {
- $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
+ $ok = true;
+ $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
} catch ( JobQueueError $e ) {
$ok = false;
MWExceptionHandler::logException( $e );
}
if ( $ok ) {
$key = $this->getCacheKey( 'empty' );
- $this->cache->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
+ $this->cache->set( $key, 'false', self::CACHE_TTL_LONG );
} else {
- $partitionRing = $partitionRing->newWithoutLocation( $partition ); // blacklist
- if ( !$partitionRing ) {
- throw new JobQueueError( "Could not insert job(s), all partitions are down." );
+ if ( !$partitionRing->ejectFromLiveRing( $partition, 5 ) ) { // blacklist
+ throw new JobQueueError( "Could not insert job(s), no partitions available." );
}
$jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
}
@@ -285,36 +316,37 @@ class JobQueueFederated extends JobQueue {
}
protected function doPop() {
- $key = $this->getCacheKey( 'empty' );
-
- $isEmpty = $this->cache->get( $key );
- if ( $isEmpty === 'true' ) {
- return false;
- }
-
- $partitionsTry = $this->partitionMap; // (partition => weight)
+ $partitionsTry = $this->partitionRing->getLiveLocationWeights(); // (partition => weight)
+ $failed = 0;
while ( count( $partitionsTry ) ) {
$partition = ArrayUtils::pickRandom( $partitionsTry );
if ( $partition === false ) {
break; // all partitions at 0 weight
}
+
+ /** @var JobQueue $queue */
$queue = $this->partitionQueues[$partition];
try {
$job = $queue->pop();
} catch ( JobQueueError $e ) {
- $job = false;
+ ++$failed;
MWExceptionHandler::logException( $e );
+ $job = false;
}
if ( $job ) {
$job->metadata['QueuePartition'] = $partition;
+
return $job;
} else {
unset( $partitionsTry[$partition] ); // blacklist partition
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
+
+ $key = $this->getCacheKey( 'empty' );
+ $this->cache->set( $key, 'true', self::CACHE_TTL_LONG );
- $this->cache->set( $key, 'true', JobQueueDB::CACHE_TTL_LONG );
return false;
}
@@ -322,62 +354,80 @@ class JobQueueFederated extends JobQueue {
if ( !isset( $job->metadata['QueuePartition'] ) ) {
throw new MWException( "The given job has no defined partition name." );
}
+
return $this->partitionQueues[$job->metadata['QueuePartition']]->ack( $job );
}
protected function doIsRootJobOldDuplicate( Job $job ) {
$params = $job->getRootJobParams();
- $partitions = $this->partitionPushRing->getLocations( $params['rootJobSignature'], 2 );
+ $sigature = $params['rootJobSignature'];
+ $partition = $this->partitionPushRing->getLiveLocation( $sigature );
try {
- return $this->partitionQueues[$partitions[0]]->doIsRootJobOldDuplicate( $job );
+ return $this->partitionQueues[$partition]->doIsRootJobOldDuplicate( $job );
} catch ( JobQueueError $e ) {
- if ( isset( $partitions[1] ) ) { // check fallback partition
- return $this->partitionQueues[$partitions[1]]->doIsRootJobOldDuplicate( $job );
+ if ( $this->partitionPushRing->ejectFromLiveRing( $partition, 5 ) ) {
+ $partition = $this->partitionPushRing->getLiveLocation( $sigature );
+ return $this->partitionQueues[$partition]->doIsRootJobOldDuplicate( $job );
}
}
+
return false;
}
protected function doDeduplicateRootJob( Job $job ) {
$params = $job->getRootJobParams();
- $partitions = $this->partitionPushRing->getLocations( $params['rootJobSignature'], 2 );
+ $sigature = $params['rootJobSignature'];
+ $partition = $this->partitionPushRing->getLiveLocation( $sigature );
try {
- return $this->partitionQueues[$partitions[0]]->doDeduplicateRootJob( $job );
+ return $this->partitionQueues[$partition]->doDeduplicateRootJob( $job );
} catch ( JobQueueError $e ) {
- if ( isset( $partitions[1] ) ) { // check fallback partition
- return $this->partitionQueues[$partitions[1]]->doDeduplicateRootJob( $job );
+ if ( $this->partitionPushRing->ejectFromLiveRing( $partition, 5 ) ) {
+ $partition = $this->partitionPushRing->getLiveLocation( $sigature );
+ return $this->partitionQueues[$partition]->doDeduplicateRootJob( $job );
}
}
+
return false;
}
protected function doDelete() {
+ $failed = 0;
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
try {
$queue->doDelete();
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
+ return true;
}
protected function doWaitForBackups() {
+ $failed = 0;
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
try {
$queue->waitForBackups();
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
}
protected function doGetPeriodicTasks() {
$tasks = array();
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $partition => $queue ) {
foreach ( $queue->getPeriodicTasks() as $task => $def ) {
$tasks["{$partition}:{$task}"] = $def;
}
}
+
return $tasks;
}
@@ -389,9 +439,12 @@ class JobQueueFederated extends JobQueue {
'delayedcount',
'abandonedcount'
);
+
foreach ( $types as $type ) {
$this->cache->delete( $this->getCacheKey( $type ) );
}
+
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
$queue->doFlushCaches();
}
@@ -399,27 +452,36 @@ class JobQueueFederated extends JobQueue {
public function getAllQueuedJobs() {
$iterator = new AppendIterator();
+
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
$iterator->append( $queue->getAllQueuedJobs() );
}
+
return $iterator;
}
public function getAllDelayedJobs() {
$iterator = new AppendIterator();
+
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
$iterator->append( $queue->getAllDelayedJobs() );
}
+
return $iterator;
}
public function getCoalesceLocationInternal() {
return "JobQueueFederated:wiki:{$this->wiki}" .
- sha1( serialize( array_keys( $this->partitionMap ) ) );
+ sha1( serialize( array_keys( $this->partitionQueues ) ) );
}
protected function doGetSiblingQueuesWithJobs( array $types ) {
$result = array();
+
+ $failed = 0;
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
try {
$nonEmpty = $queue->doGetSiblingQueuesWithJobs( $types );
@@ -432,14 +494,19 @@ class JobQueueFederated extends JobQueue {
break; // short-circuit
}
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
+
return array_values( $result );
}
protected function doGetSiblingQueueSizes( array $types ) {
$result = array();
+ $failed = 0;
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
try {
$sizes = $queue->doGetSiblingQueueSizes( $types );
@@ -451,23 +518,42 @@ class JobQueueFederated extends JobQueue {
return null; // not supported on all partitions; bail
}
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
+
return $result;
}
+ /**
+ * Throw an error if no partitions available
+ *
+ * @param int $down The number of up partitions down
+ * @return void
+ * @throws JobQueueError
+ */
+ protected function throwErrorIfAllPartitionsDown( $down ) {
+ if ( $down >= count( $this->partitionQueues ) ) {
+ throw new JobQueueError( 'No queue partitions available.' );
+ }
+ }
+
public function setTestingPrefix( $key ) {
+ /** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
$queue->setTestingPrefix( $key );
}
}
/**
+ * @param string $property
* @return string
*/
private function getCacheKey( $property ) {
list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+
return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, $property );
}
}
diff --git a/includes/job/JobQueueGroup.php b/includes/jobqueue/JobQueueGroup.php
index fa7fee5f..98a78c5e 100644
--- a/includes/job/JobQueueGroup.php
+++ b/includes/jobqueue/JobQueueGroup.php
@@ -28,13 +28,14 @@
* @since 1.21
*/
class JobQueueGroup {
- /** @var Array */
+ /** @var array */
protected static $instances = array();
/** @var ProcessCacheLRU */
protected $cache;
- protected $wiki; // string; wiki ID
+ /** @var string Wiki ID */
+ protected $wiki;
/** @var array Map of (bucket => (queue => JobQueue, types => list of types) */
protected $coalescedQueues;
@@ -43,7 +44,6 @@ class JobQueueGroup {
const TYPE_ANY = 2; // integer; any job
const USE_CACHE = 1; // integer; use process or persistent cache
- const USE_PRIORITY = 2; // integer; respect deprioritization
const PROC_CACHE_TTL = 15; // integer; seconds
@@ -58,7 +58,7 @@ class JobQueueGroup {
}
/**
- * @param string $wiki Wiki ID
+ * @param bool|string $wiki Wiki ID
* @return JobQueueGroup
*/
public static function singleton( $wiki = false ) {
@@ -66,6 +66,7 @@ class JobQueueGroup {
if ( !isset( self::$instances[$wiki] ) ) {
self::$instances[$wiki] = new self( $wiki );
}
+
return self::$instances[$wiki];
}
@@ -81,7 +82,7 @@ class JobQueueGroup {
/**
* Get the job queue object for a given queue type
*
- * @param $type string
+ * @param string $type
* @return JobQueue
*/
public function get( $type ) {
@@ -103,29 +104,28 @@ class JobQueueGroup {
* This inserts the jobs into the queue specified by $wgJobTypeConf
* and updates the aggregate job queue information cache as needed.
*
- * @param $jobs Job|array A single Job or a list of Jobs
+ * @param Job|array $jobs A single Job or a list of Jobs
* @throws MWException
- * @return bool
+ * @return void
*/
public function push( $jobs ) {
$jobs = is_array( $jobs ) ? $jobs : array( $jobs );
+ if ( !count( $jobs ) ) {
+ return;
+ }
$jobsByType = array(); // (job type => list of jobs)
foreach ( $jobs as $job ) {
- if ( $job instanceof Job ) {
+ if ( $job instanceof IJobSpecification ) {
$jobsByType[$job->getType()][] = $job;
} else {
throw new MWException( "Attempted to push a non-Job object into a queue." );
}
}
- $ok = true;
foreach ( $jobsByType as $type => $jobs ) {
- if ( $this->get( $type )->push( $jobs ) ) {
- JobQueueAggregator::singleton()->notifyQueueNonEmpty( $this->wiki, $type );
- } else {
- $ok = false;
- }
+ $this->get( $type )->push( $jobs );
+ JobQueueAggregator::singleton()->notifyQueueNonEmpty( $this->wiki, $type );
}
if ( $this->cache->has( 'queues-ready', 'list' ) ) {
@@ -134,8 +134,6 @@ class JobQueueGroup {
$this->cache->clear( 'queues-ready' );
}
}
-
- return $ok;
}
/**
@@ -144,20 +142,21 @@ class JobQueueGroup {
* This pops a job off a queue as specified by $wgJobTypeConf and
* updates the aggregate job queue information cache as needed.
*
- * @param $qtype integer|string JobQueueGroup::TYPE_DEFAULT or type string
- * @param $flags integer Bitfield of JobQueueGroup::USE_* constants
+ * @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
*/
- public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0 ) {
+ public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0, array $blacklist = array() ) {
+ $job = false;
+
if ( is_string( $qtype ) ) { // specific job type
- if ( ( $flags & self::USE_PRIORITY ) && $this->isQueueDeprioritized( $qtype ) ) {
- return false; // back off
- }
- $job = $this->get( $qtype )->pop();
- if ( !$job ) {
- JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $qtype );
+ if ( !in_array( $qtype, $blacklist ) ) {
+ $job = $this->get( $qtype )->pop();
+ if ( !$job ) {
+ JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $qtype );
+ }
}
- return $job;
} else { // any job in the "default" jobs types
if ( $flags & self::USE_CACHE ) {
if ( !$this->cache->has( 'queues-ready', 'list', self::PROC_CACHE_TTL ) ) {
@@ -171,29 +170,28 @@ class JobQueueGroup {
if ( $qtype == self::TYPE_DEFAULT ) {
$types = array_intersect( $types, $this->getDefaultQueueTypes() );
}
+
+ $types = array_diff( $types, $blacklist ); // avoid selected types
shuffle( $types ); // avoid starvation
foreach ( $types as $type ) { // for each queue...
- if ( ( $flags & self::USE_PRIORITY ) && $this->isQueueDeprioritized( $type ) ) {
- continue; // back off
- }
$job = $this->get( $type )->pop();
if ( $job ) { // found
- return $job;
+ break;
} else { // not found
JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $type );
$this->cache->clear( 'queues-ready' );
}
}
-
- return false; // no jobs found
}
+
+ return $job;
}
/**
* Acknowledge that a job was completed
*
- * @param $job Job
+ * @param Job $job
* @return bool
*/
public function ack( Job $job ) {
@@ -204,7 +202,7 @@ class JobQueueGroup {
* Register the "root job" of a given job into the queue for de-duplication.
* This should only be called right *after* all the new jobs have been inserted.
*
- * @param $job Job
+ * @param Job $job
* @return bool
*/
public function deduplicateRootJob( Job $job ) {
@@ -251,9 +249,34 @@ class JobQueueGroup {
}
/**
+ * Check if there are any queues with jobs (this is cached)
+ *
+ * @param int $type JobQueueGroup::TYPE_* constant
+ * @return bool
+ * @since 1.23
+ */
+ public function queuesHaveJobs( $type = self::TYPE_ANY ) {
+ global $wgMemc;
+
+ $key = wfMemcKey( 'jobqueue', 'queueshavejobs', $type );
+
+ $value = $wgMemc->get( $key );
+ if ( $value === false ) {
+ $queues = $this->getQueuesWithJobs();
+ if ( $type == self::TYPE_DEFAULT ) {
+ $queues = array_intersect( $queues, $this->getDefaultQueueTypes() );
+ }
+ $value = count( $queues ) ? 'true' : 'false';
+ $wgMemc->add( $key, $value, 15 );
+ }
+
+ return ( $value === 'true' );
+ }
+
+ /**
* Get the list of job types that have non-empty queues
*
- * @return Array List of job types that have non-empty queues
+ * @return array List of job types that have non-empty queues
*/
public function getQueuesWithJobs() {
$types = array();
@@ -269,13 +292,14 @@ class JobQueueGroup {
}
}
}
+
return $types;
}
/**
* Get the size of the queus for a list of job types
*
- * @return Array Map of (job type => size)
+ * @return array Map of (job type => size)
*/
public function getQueueSizes() {
$sizeMap = array();
@@ -289,6 +313,7 @@ class JobQueueGroup {
}
}
}
+
return $sizeMap;
}
@@ -323,35 +348,13 @@ class JobQueueGroup {
}
/**
- * Check if jobs should not be popped of a queue right now.
- * This is only used for performance, such as to avoid spamming
- * the queue with many sub-jobs before they actually get run.
- *
- * @param $type string
- * @return bool
- */
- public function isQueueDeprioritized( $type ) {
- if ( $this->cache->has( 'isDeprioritized', $type, 5 ) ) {
- return $this->cache->get( 'isDeprioritized', $type );
- }
- if ( $type === 'refreshLinks2' ) {
- // Don't keep converting refreshLinks2 => refreshLinks jobs if the
- // later jobs have not been done yet. This helps throttle queue spam.
- $deprioritized = !$this->get( 'refreshLinks' )->isEmpty();
- $this->cache->set( 'isDeprioritized', $type, $deprioritized );
- return $deprioritized;
- }
- return false;
- }
-
- /**
* Execute any due periodic queue maintenance tasks for all queues.
*
* A task is "due" if the time ellapsed since the last run is greater than
* the defined run period. Concurrent calls to this function will cause tasks
* to be attempted twice, so they may need their own methods of mutual exclusion.
*
- * @return integer Number of tasks run
+ * @return int Number of tasks run
*/
public function executeReadyPeriodicTasks() {
global $wgMemc;
@@ -368,8 +371,8 @@ class JobQueueGroup {
if ( $definition['period'] <= 0 ) {
continue; // disabled
} elseif ( !isset( $lastRuns[$type][$task] )
- || $lastRuns[$type][$task] < ( time() - $definition['period'] ) )
- {
+ || $lastRuns[$type][$task] < ( time() - $definition['period'] )
+ ) {
try {
if ( call_user_func( $definition['callback'] ) !== null ) {
$tasksRun[$type][$task] = time();
@@ -380,15 +383,23 @@ class JobQueueGroup {
}
}
}
+ // The tasks may have recycled jobs or release delayed jobs into the queue
+ if ( isset( $tasksRun[$type] ) && !$queue->isEmpty() ) {
+ JobQueueAggregator::singleton()->notifyQueueNonEmpty( $this->wiki, $type );
+ }
+ }
+
+ if ( $count === 0 ) {
+ return $count; // nothing to update
}
- $wgMemc->merge( $key, function( $cache, $key, $lastRuns ) use ( $tasksRun ) {
+ $wgMemc->merge( $key, function ( $cache, $key, $lastRuns ) use ( $tasksRun ) {
if ( is_array( $lastRuns ) ) {
foreach ( $tasksRun as $type => $tasks ) {
foreach ( $tasks as $task => $timestamp ) {
if ( !isset( $lastRuns[$type][$task] )
- || $timestamp > $lastRuns[$type][$task] )
- {
+ || $timestamp > $lastRuns[$type][$task]
+ ) {
$lastRuns[$type][$task] = $timestamp;
}
}
@@ -396,6 +407,7 @@ class JobQueueGroup {
} else {
$lastRuns = $tasksRun;
}
+
return $lastRuns;
} );
@@ -403,7 +415,7 @@ class JobQueueGroup {
}
/**
- * @param $name string
+ * @param string $name
* @return mixed
*/
private function getCachedConfigVar( $name ) {
@@ -420,6 +432,7 @@ class JobQueueGroup {
} else {
$value = $wgConf->getConfig( $this->wiki, $name );
$wgMemc->set( $key, array( 'v' => $value ), 86400 + mt_rand( 0, 86400 ) );
+
return $value;
}
}
diff --git a/includes/job/JobQueueRedis.php b/includes/jobqueue/JobQueueRedis.php
index 378e1755..3519eac8 100644
--- a/includes/job/JobQueueRedis.php
+++ b/includes/jobqueue/JobQueueRedis.php
@@ -60,22 +60,29 @@ class JobQueueRedis extends JobQueue {
/** @var RedisConnectionPool */
protected $redisPool;
- protected $server; // string; server address
- protected $compression; // string; compression method to use
+ /** @var string Server address */
+ protected $server;
+ /** @var string Compression method to use */
+ protected $compression;
+ /** @var bool */
+ protected $daemonized;
const MAX_AGE_PRUNE = 604800; // integer; seconds a job can live once claimed (7 days)
- protected $key; // string; key to prefix the queue keys with (used for testing)
+ /** @var string Key to prefix the queue keys with (used for testing) */
+ protected $key;
/**
- * @params include:
+ * @param array $params Possible keys:
* - redisConfig : An array of parameters to RedisConnectionPool::__construct().
- * Note that the serializer option is ignored "none" is always used.
+ * Note that the serializer option is ignored as "none" is always used.
* - redisServer : A hostname/port combination or the absolute path of a UNIX socket.
* If a hostname is specified but no port, the standard port number
* 6379 will be used. Required.
* - compression : The type of compression to use; one of (none,gzip).
- * @param array $params
+ * - daemonized : Set to true if the redisJobRunnerService runs in the background.
+ * This will disable job recycling/undelaying from the MediaWiki side
+ * to avoid redundance and out-of-sync configuration.
*/
public function __construct( array $params ) {
parent::__construct( $params );
@@ -83,6 +90,7 @@ class JobQueueRedis extends JobQueue {
$this->server = $params['redisServer'];
$this->compression = isset( $params['compression'] ) ? $params['compression'] : 'none';
$this->redisPool = RedisConnectionPool::singleton( $params['redisConfig'] );
+ $this->daemonized = !empty( $params['daemonized'] );
}
protected function supportedOrders() {
@@ -108,7 +116,7 @@ class JobQueueRedis extends JobQueue {
/**
* @see JobQueue::doGetSize()
- * @return integer
+ * @return int
* @throws MWException
*/
protected function doGetSize() {
@@ -116,14 +124,14 @@ class JobQueueRedis extends JobQueue {
try {
return $conn->lSize( $this->getQueueKey( 'l-unclaimed' ) );
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
/**
* @see JobQueue::doGetAcquiredCount()
- * @return integer
- * @throws MWException
+ * @return int
+ * @throws JobQueueError
*/
protected function doGetAcquiredCount() {
if ( $this->claimTTL <= 0 ) {
@@ -134,16 +142,17 @@ class JobQueueRedis extends JobQueue {
$conn->multi( Redis::PIPELINE );
$conn->zSize( $this->getQueueKey( 'z-claimed' ) );
$conn->zSize( $this->getQueueKey( 'z-abandoned' ) );
+
return array_sum( $conn->exec() );
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
/**
* @see JobQueue::doGetDelayedCount()
- * @return integer
- * @throws MWException
+ * @return int
+ * @throws JobQueueError
*/
protected function doGetDelayedCount() {
if ( !$this->checkDelay ) {
@@ -153,14 +162,14 @@ class JobQueueRedis extends JobQueue {
try {
return $conn->zSize( $this->getQueueKey( 'z-delayed' ) );
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
/**
* @see JobQueue::doGetAbandonedCount()
- * @return integer
- * @throws MWException
+ * @return int
+ * @throws JobQueueError
*/
protected function doGetAbandonedCount() {
if ( $this->claimTTL <= 0 ) {
@@ -170,16 +179,16 @@ class JobQueueRedis extends JobQueue {
try {
return $conn->zSize( $this->getQueueKey( 'z-abandoned' ) );
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
/**
* @see JobQueue::doBatchPush()
* @param array $jobs
- * @param $flags
- * @return bool
- * @throws MWException
+ * @param int $flags
+ * @return void
+ * @throws JobQueueError
*/
protected function doBatchPush( array $jobs, $flags ) {
// Convert the jobs into field maps (de-duplicated against each other)
@@ -194,7 +203,7 @@ class JobQueueRedis extends JobQueue {
}
if ( !count( $items ) ) {
- return true; // nothing to do
+ return; // nothing to do
}
$conn = $this->getConnection();
@@ -217,22 +226,21 @@ class JobQueueRedis extends JobQueue {
}
if ( $failed > 0 ) {
wfDebugLog( 'JobQueueRedis', "Could not insert {$failed} {$this->type} job(s)." );
- return false;
+
+ throw new RedisException( "Could not insert {$failed} {$this->type} job(s)." );
}
- JobQueue::incrStats( 'job-insert', $this->type, count( $items ) );
+ JobQueue::incrStats( 'job-insert', $this->type, count( $items ), $this->wiki );
JobQueue::incrStats( 'job-insert-duplicate', $this->type,
- count( $items ) - $failed - $pushed );
+ count( $items ) - $failed - $pushed, $this->wiki );
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
-
- return true;
}
/**
* @param RedisConnRef $conn
* @param array $items List of results from JobQueueRedis::getNewJobFields()
- * @return integer Number of jobs inserted (duplicates are ignored)
+ * @return int Number of jobs inserted (duplicates are ignored)
* @throws RedisException
*/
protected function pushBlobs( RedisConnRef $conn, array $items ) {
@@ -245,23 +253,24 @@ class JobQueueRedis extends JobQueue {
}
static $script =
<<<LUA
+ local kUnclaimed, kSha1ById, kIdBySha1, kDelayed, kData = unpack(KEYS)
if #ARGV % 4 ~= 0 then return redis.error_reply('Unmatched arguments') end
local pushed = 0
for i = 1,#ARGV,4 do
local id,sha1,rtimestamp,blob = ARGV[i],ARGV[i+1],ARGV[i+2],ARGV[i+3]
- if sha1 == '' or redis.call('hExists',KEYS[3],sha1) == 0 then
+ if sha1 == '' or redis.call('hExists',kIdBySha1,sha1) == 0 then
if 1*rtimestamp > 0 then
-- Insert into delayed queue (release time as score)
- redis.call('zAdd',KEYS[4],rtimestamp,id)
+ redis.call('zAdd',kDelayed,rtimestamp,id)
else
-- Insert into unclaimed queue
- redis.call('lPush',KEYS[1],id)
+ redis.call('lPush',kUnclaimed,id)
end
if sha1 ~= '' then
- redis.call('hSet',KEYS[2],id,sha1)
- redis.call('hSet',KEYS[3],sha1,id)
+ redis.call('hSet',kSha1ById,id,sha1)
+ redis.call('hSet',kIdBySha1,sha1,id)
end
- redis.call('hSet',KEYS[5],id,blob)
+ redis.call('hSet',kData,id,blob)
pushed = pushed + 1
end
end
@@ -285,7 +294,7 @@ LUA;
/**
* @see JobQueue::doPop()
* @return Job|bool
- * @throws MWException
+ * @throws JobQueueError
*/
protected function doPop() {
$job = false;
@@ -293,7 +302,7 @@ LUA;
// Push ready delayed jobs into the queue every 10 jobs to spread the load.
// This is also done as a periodic task, but we don't want too much done at once.
if ( $this->checkDelay && mt_rand( 0, 9 ) == 0 ) {
- $this->releaseReadyDelayedJobs();
+ $this->recyclePruneAndUndelayJobs();
}
$conn = $this->getConnection();
@@ -302,28 +311,28 @@ LUA;
if ( $this->claimTTL > 0 ) {
// Keep the claimed job list down for high-traffic queues
if ( mt_rand( 0, 99 ) == 0 ) {
- $this->recycleAndDeleteStaleJobs();
+ $this->recyclePruneAndUndelayJobs();
}
$blob = $this->popAndAcquireBlob( $conn );
} else {
$blob = $this->popAndDeleteBlob( $conn );
}
- if ( $blob === false ) {
+ if ( !is_string( $blob ) ) {
break; // no jobs; nothing to do
}
- JobQueue::incrStats( 'job-pop', $this->type );
+ JobQueue::incrStats( 'job-pop', $this->type, 1, $this->wiki );
$item = $this->unserialize( $blob );
if ( $item === false ) {
wfDebugLog( 'JobQueueRedis', "Could not unserialize {$this->type} job." );
continue;
}
- // If $item is invalid, recycleAndDeleteStaleJobs() will cleanup as needed
+ // If $item is invalid, recyclePruneAndUndelayJobs() will cleanup as needed
$job = $this->getJobFromFields( $item ); // may be false
} while ( !$job ); // job may be false if invalid
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
return $job;
@@ -331,22 +340,23 @@ LUA;
/**
* @param RedisConnRef $conn
- * @return array serialized string or false
+ * @return array Serialized string or false
* @throws RedisException
*/
protected function popAndDeleteBlob( RedisConnRef $conn ) {
static $script =
<<<LUA
+ local kUnclaimed, kSha1ById, kIdBySha1, kData = unpack(KEYS)
-- Pop an item off the queue
- local id = redis.call('rpop',KEYS[1])
+ local id = redis.call('rpop',kUnclaimed)
if not id then return false end
-- Get the job data and remove it
- local item = redis.call('hGet',KEYS[4],id)
- redis.call('hDel',KEYS[4],id)
+ local item = redis.call('hGet',kData,id)
+ redis.call('hDel',kData,id)
-- Allow new duplicates of this job
- local sha1 = redis.call('hGet',KEYS[2],id)
- if sha1 then redis.call('hDel',KEYS[3],sha1) end
- redis.call('hDel',KEYS[2],id)
+ local sha1 = redis.call('hGet',kSha1ById,id)
+ if sha1 then redis.call('hDel',kIdBySha1,sha1) end
+ redis.call('hDel',kSha1ById,id)
-- Return the job data
return item
LUA;
@@ -363,23 +373,24 @@ LUA;
/**
* @param RedisConnRef $conn
- * @return array serialized string or false
+ * @return array Serialized string or false
* @throws RedisException
*/
protected function popAndAcquireBlob( RedisConnRef $conn ) {
static $script =
<<<LUA
+ local kUnclaimed, kSha1ById, kIdBySha1, kClaimed, kAttempts, kData = unpack(KEYS)
-- Pop an item off the queue
- local id = redis.call('rPop',KEYS[1])
+ local id = redis.call('rPop',kUnclaimed)
if not id then return false end
-- Allow new duplicates of this job
- local sha1 = redis.call('hGet',KEYS[2],id)
- if sha1 then redis.call('hDel',KEYS[3],sha1) end
- redis.call('hDel',KEYS[2],id)
+ local sha1 = redis.call('hGet',kSha1ById,id)
+ if sha1 then redis.call('hDel',kIdBySha1,sha1) end
+ redis.call('hDel',kSha1ById,id)
-- Mark the jobs as claimed and return it
- redis.call('zAdd',KEYS[4],ARGV[1],id)
- redis.call('hIncrBy',KEYS[5],id,1)
- return redis.call('hGet',KEYS[6],id)
+ redis.call('zAdd',kClaimed,ARGV[1],id)
+ redis.call('hIncrBy',kAttempts,id,1)
+ return redis.call('hGet',kData,id)
LUA;
return $conn->luaEval( $script,
array(
@@ -399,7 +410,7 @@ LUA;
* @see JobQueue::doAck()
* @param Job $job
* @return Job|bool
- * @throws MWException
+ * @throws MWException|JobQueueError
*/
protected function doAck( Job $job ) {
if ( !isset( $job->metadata['uuid'] ) ) {
@@ -410,11 +421,12 @@ LUA;
try {
static $script =
<<<LUA
+ local kClaimed, kAttempts, kData = unpack(KEYS)
-- Unmark the job as claimed
- redis.call('zRem',KEYS[1],ARGV[1])
- redis.call('hDel',KEYS[2],ARGV[1])
+ redis.call('zRem',kClaimed,ARGV[1])
+ redis.call('hDel',kAttempts,ARGV[1])
-- Delete the job data itself
- return redis.call('hDel',KEYS[3],ARGV[1])
+ return redis.call('hDel',kData,ARGV[1])
LUA;
$res = $conn->luaEval( $script,
array(
@@ -428,12 +440,14 @@ LUA;
if ( !$res ) {
wfDebugLog( 'JobQueueRedis', "Could not acknowledge {$this->type} job." );
+
return false;
}
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
+
return true;
}
@@ -441,7 +455,7 @@ LUA;
* @see JobQueue::doDeduplicateRootJob()
* @param Job $job
* @return bool
- * @throws MWException
+ * @throws MWException|JobQueueError
*/
protected function doDeduplicateRootJob( Job $job ) {
if ( !$job->hasRootJobParams() ) {
@@ -457,10 +471,11 @@ LUA;
if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
return true; // a newer version of this root job was enqueued
}
+
// Update the timestamp of the last root job started at the location...
return $conn->set( $key, $params['rootJobTimestamp'], self::ROOTJOB_TTL ); // 2 weeks
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
@@ -468,6 +483,7 @@ LUA;
* @see JobQueue::doIsRootJobOldDuplicate()
* @param Job $job
* @return bool
+ * @throws JobQueueError
*/
protected function doIsRootJobOldDuplicate( Job $job ) {
if ( !$job->hasRootJobParams() ) {
@@ -480,7 +496,7 @@ LUA;
// Get the last time this root job was enqueued
$timestamp = $conn->get( $this->getRootJobCacheKey( $params['rootJobSignature'] ) );
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
// Check if a new root job was started at the location after this one's...
@@ -490,6 +506,7 @@ LUA;
/**
* @see JobQueue::doDelete()
* @return bool
+ * @throws JobQueueError
*/
protected function doDelete() {
static $props = array( 'l-unclaimed', 'z-claimed', 'z-abandoned',
@@ -501,9 +518,10 @@ LUA;
foreach ( $props as $prop ) {
$keys[] = $this->getQueueKey( $prop );
}
+
return ( $conn->delete( $keys ) !== false );
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
@@ -515,15 +533,18 @@ LUA;
$conn = $this->getConnection();
try {
$that = $this;
+
return new MappedIterator(
$conn->lRange( $this->getQueueKey( 'l-unclaimed' ), 0, -1 ),
- function( $uid ) use ( $that, $conn ) {
+ function ( $uid ) use ( $that, $conn ) {
return $that->getJobFromUidInternal( $uid, $conn );
},
- array( 'accept' => function ( $job ) { return is_object( $job ); } )
+ array( 'accept' => function ( $job ) {
+ return is_object( $job );
+ } )
);
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
@@ -535,15 +556,18 @@ LUA;
$conn = $this->getConnection();
try {
$that = $this;
+
return new MappedIterator( // delayed jobs
$conn->zRange( $this->getQueueKey( 'z-delayed' ), 0, -1 ),
- function( $uid ) use ( $that, $conn ) {
+ function ( $uid ) use ( $that, $conn ) {
return $that->getJobFromUidInternal( $uid, $conn );
},
- array( 'accept' => function ( $job ) { return is_object( $job ); } )
+ array( 'accept' => function ( $job ) {
+ return is_object( $job );
+ } )
);
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
}
@@ -558,8 +582,8 @@ LUA;
protected function doGetSiblingQueueSizes( array $types ) {
$sizes = array(); // (type => size)
$types = array_values( $types ); // reindex
+ $conn = $this->getConnection();
try {
- $conn = $this->getConnection();
$conn->multi( Redis::PIPELINE );
foreach ( $types as $type ) {
$conn->lSize( $this->getQueueKey( 'l-unclaimed', $type ) );
@@ -571,18 +595,19 @@ LUA;
}
}
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
+
return $sizes;
}
/**
* This function should not be called outside JobQueueRedis
*
- * @param $uid string
- * @param $conn RedisConnRef
+ * @param string $uid
+ * @param RedisConnRef $conn
* @return Job|bool Returns false if the job does not exist
- * @throws MWException
+ * @throws MWException|JobQueueError
*/
public function getJobFromUidInternal( $uid, RedisConnRef $conn ) {
try {
@@ -597,59 +622,21 @@ LUA;
$title = Title::makeTitle( $item['namespace'], $item['title'] );
$job = Job::factory( $item['type'], $title, $item['params'] );
$job->metadata['uuid'] = $item['uuid'];
- return $job;
- } catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
- }
- }
-
- /**
- * Release any ready delayed jobs into the queue
- *
- * @return integer Number of jobs released
- * @throws MWException
- */
- public function releaseReadyDelayedJobs() {
- $count = 0;
- $conn = $this->getConnection();
- try {
- static $script =
-<<<LUA
- -- Get the list of ready delayed jobs, sorted by readiness
- local ids = redis.call('zRangeByScore',KEYS[1],0,ARGV[1])
- -- Migrate the jobs from the "delayed" set to the "unclaimed" list
- for k,id in ipairs(ids) do
- redis.call('lPush',KEYS[2],id)
- redis.call('zRem',KEYS[1],id)
- end
- return #ids
-LUA;
- $count += (int)$conn->luaEval( $script,
- array(
- $this->getQueueKey( 'z-delayed' ), // KEYS[1]
- $this->getQueueKey( 'l-unclaimed' ), // KEYS[2]
- time() // ARGV[1]; max "delay until" UNIX timestamp
- ),
- 2 # first two arguments are keys
- );
+ return $job;
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
-
- return $count;
}
/**
* Recycle or destroy any jobs that have been claimed for too long
+ * and release any ready delayed jobs into the queue
*
- * @return integer Number of jobs recycled/deleted
- * @throws MWException
+ * @return int Number of jobs recycled/deleted/undelayed
+ * @throws MWException|JobQueueError
*/
- public function recycleAndDeleteStaleJobs() {
- if ( $this->claimTTL <= 0 ) { // sanity
- throw new MWException( "Cannot recycle jobs since acknowledgements are disabled." );
- }
+ public function recyclePruneAndUndelayJobs() {
$count = 0;
// For each job item that can be retried, we need to add it back to the
// main queue and remove it from the list of currenty claimed job items.
@@ -660,36 +647,45 @@ LUA;
$now = time();
static $script =
<<<LUA
- local released,abandoned,pruned = 0,0,0
+ local kClaimed, kAttempts, kUnclaimed, kData, kAbandoned, kDelayed = unpack(KEYS)
+ local released,abandoned,pruned,undelayed = 0,0,0,0
-- Get all non-dead jobs that have an expired claim on them.
-- The score for each item is the last claim timestamp (UNIX).
- local staleClaims = redis.call('zRangeByScore',KEYS[1],0,ARGV[1])
+ local staleClaims = redis.call('zRangeByScore',kClaimed,0,ARGV[1])
for k,id in ipairs(staleClaims) do
- local timestamp = redis.call('zScore',KEYS[1],id)
- local attempts = redis.call('hGet',KEYS[2],id)
+ local timestamp = redis.call('zScore',kClaimed,id)
+ local attempts = redis.call('hGet',kAttempts,id)
if attempts < ARGV[3] then
-- Claim expired and retries left: re-enqueue the job
- redis.call('lPush',KEYS[3],id)
- redis.call('hIncrBy',KEYS[2],id,1)
+ redis.call('lPush',kUnclaimed,id)
+ redis.call('hIncrBy',kAttempts,id,1)
released = released + 1
else
-- Claim expired and no retries left: mark the job as dead
- redis.call('zAdd',KEYS[5],timestamp,id)
+ redis.call('zAdd',kAbandoned,timestamp,id)
abandoned = abandoned + 1
end
- redis.call('zRem',KEYS[1],id)
+ redis.call('zRem',kClaimed,id)
end
-- Get all of the dead jobs that have been marked as dead for too long.
-- The score for each item is the last claim timestamp (UNIX).
- local deadClaims = redis.call('zRangeByScore',KEYS[5],0,ARGV[2])
+ local deadClaims = redis.call('zRangeByScore',kAbandoned,0,ARGV[2])
for k,id in ipairs(deadClaims) do
-- Stale and out of retries: remove any traces of the job
- redis.call('zRem',KEYS[5],id)
- redis.call('hDel',KEYS[2],id)
- redis.call('hDel',KEYS[4],id)
+ redis.call('zRem',kAbandoned,id)
+ redis.call('hDel',kAttempts,id)
+ redis.call('hDel',kData,id)
pruned = pruned + 1
end
- return {released,abandoned,pruned}
+ -- Get the list of ready delayed jobs, sorted by readiness (UNIX timestamp)
+ local ids = redis.call('zRangeByScore',kDelayed,0,ARGV[4])
+ -- Migrate the jobs from the "delayed" set to the "unclaimed" list
+ for k,id in ipairs(ids) do
+ redis.call('lPush',kUnclaimed,id)
+ redis.call('zRem',kDelayed,id)
+ end
+ undelayed = #ids
+ return {released,abandoned,pruned,undelayed}
LUA;
$res = $conn->luaEval( $script,
array(
@@ -698,69 +694,77 @@ LUA;
$this->getQueueKey( 'l-unclaimed' ), # KEYS[3]
$this->getQueueKey( 'h-data' ), # KEYS[4]
$this->getQueueKey( 'z-abandoned' ), # KEYS[5]
+ $this->getQueueKey( 'z-delayed' ), # KEYS[6]
$now - $this->claimTTL, # ARGV[1]
$now - self::MAX_AGE_PRUNE, # ARGV[2]
- $this->maxTries # ARGV[3]
+ $this->maxTries, # ARGV[3]
+ $now # ARGV[4]
),
- 5 # number of first argument(s) that are keys
+ 6 # number of first argument(s) that are keys
);
if ( $res ) {
- list( $released, $abandoned, $pruned ) = $res;
- $count += $released + $pruned;
- JobQueue::incrStats( 'job-recycle', $this->type, $released );
- JobQueue::incrStats( 'job-abandon', $this->type, $abandoned );
+ list( $released, $abandoned, $pruned, $undelayed ) = $res;
+ $count += $released + $pruned + $undelayed;
+ JobQueue::incrStats( 'job-recycle', $this->type, $released, $this->wiki );
+ JobQueue::incrStats( 'job-abandon', $this->type, $abandoned, $this->wiki );
+ JobQueue::incrStats( 'job-undelay', $this->type, $undelayed, $this->wiki );
}
} catch ( RedisException $e ) {
- $this->throwRedisException( $this->server, $conn, $e );
+ $this->throwRedisException( $conn, $e );
}
return $count;
}
/**
- * @return Array
+ * @return array
*/
protected function doGetPeriodicTasks() {
- $tasks = array();
+ if ( $this->daemonized ) {
+ return array(); // managed in the runner loop
+ }
+ $periods = array( 3600 ); // standard cleanup (useful on config change)
if ( $this->claimTTL > 0 ) {
- $tasks['recycleAndDeleteStaleJobs'] = array(
- 'callback' => array( $this, 'recycleAndDeleteStaleJobs' ),
- 'period' => ceil( $this->claimTTL / 2 )
- );
+ $periods[] = ceil( $this->claimTTL / 2 ); // avoid bad timing
}
if ( $this->checkDelay ) {
- $tasks['releaseReadyDelayedJobs'] = array(
- 'callback' => array( $this, 'releaseReadyDelayedJobs' ),
- 'period' => 300 // 5 minutes
- );
+ $periods[] = 300; // 5 minutes
}
- return $tasks;
+ $period = min( $periods );
+ $period = max( $period, 30 ); // sanity
+
+ return array(
+ 'recyclePruneAndUndelayJobs' => array(
+ 'callback' => array( $this, 'recyclePruneAndUndelayJobs' ),
+ 'period' => $period,
+ )
+ );
}
/**
- * @param $job Job
+ * @param IJobSpecification $job
* @return array
*/
- protected function getNewJobFields( Job $job ) {
+ protected function getNewJobFields( IJobSpecification $job ) {
return array(
// Fields that describe the nature of the job
- 'type' => $job->getType(),
- 'namespace' => $job->getTitle()->getNamespace(),
- 'title' => $job->getTitle()->getDBkey(),
- 'params' => $job->getParams(),
+ 'type' => $job->getType(),
+ 'namespace' => $job->getTitle()->getNamespace(),
+ 'title' => $job->getTitle()->getDBkey(),
+ 'params' => $job->getParams(),
// Some jobs cannot run until a "release timestamp"
'rtimestamp' => $job->getReleaseTimestamp() ?: 0,
// Additional job metadata
- 'uuid' => UIDGenerator::newRawUUIDv4( UIDGenerator::QUICK_RAND ),
- 'sha1' => $job->ignoreDuplicates()
+ 'uuid' => UIDGenerator::newRawUUIDv4( UIDGenerator::QUICK_RAND ),
+ 'sha1' => $job->ignoreDuplicates()
? wfBaseConvert( sha1( serialize( $job->getDeduplicationInfo() ) ), 16, 36, 31 )
: '',
- 'timestamp' => time() // UNIX timestamp
+ 'timestamp' => time() // UNIX timestamp
);
}
/**
- * @param $fields array
+ * @param array $fields
* @return Job|bool
*/
protected function getJobFromFields( array $fields ) {
@@ -768,8 +772,10 @@ LUA;
if ( $title ) {
$job = Job::factory( $fields['type'], $title, $fields['params'] );
$job->metadata['uuid'] = $fields['uuid'];
+
return $job;
}
+
return false;
}
@@ -780,10 +786,12 @@ LUA;
protected function serialize( array $fields ) {
$blob = serialize( $fields );
if ( $this->compression === 'gzip'
- && strlen( $blob ) >= 1024 && function_exists( 'gzdeflate' ) )
- {
+ && strlen( $blob ) >= 1024
+ && function_exists( 'gzdeflate' )
+ ) {
$object = (object)array( 'blob' => gzdeflate( $blob ), 'enc' => 'gzip' );
$blobz = serialize( $object );
+
return ( strlen( $blobz ) < strlen( $blob ) ) ? $blobz : $blob;
} else {
return $blob;
@@ -803,37 +811,38 @@ LUA;
$fields = false;
}
}
+
return is_array( $fields ) ? $fields : false;
}
/**
* Get a connection to the server that handles all sub-queues for this queue
*
- * @return Array (server name, Redis instance)
- * @throws MWException
+ * @return RedisConnRef
+ * @throws JobQueueConnectionError
*/
protected function getConnection() {
$conn = $this->redisPool->getConnection( $this->server );
if ( !$conn ) {
throw new JobQueueConnectionError( "Unable to connect to redis server." );
}
+
return $conn;
}
/**
- * @param $server string
- * @param $conn RedisConnRef
- * @param $e RedisException
- * @throws MWException
+ * @param RedisConnRef $conn
+ * @param RedisException $e
+ * @throws JobQueueError
*/
- protected function throwRedisException( $server, RedisConnRef $conn, $e ) {
- $this->redisPool->handleException( $server, $conn, $e );
+ protected function throwRedisException( RedisConnRef $conn, $e ) {
+ $this->redisPool->handleError( $conn, $e );
throw new JobQueueError( "Redis server error: {$e->getMessage()}\n" );
}
/**
- * @param $prop string
- * @param $type string|null
+ * @param string $prop
+ * @param string|null $type
* @return string
*/
private function getQueueKey( $prop, $type = null ) {
@@ -847,7 +856,7 @@ LUA;
}
/**
- * @param $key string
+ * @param string $key
* @return void
*/
public function setTestingPrefix( $key ) {
diff --git a/includes/jobqueue/JobRunner.php b/includes/jobqueue/JobRunner.php
new file mode 100644
index 00000000..8cccedaf
--- /dev/null
+++ b/includes/jobqueue/JobRunner.php
@@ -0,0 +1,350 @@
+<?php
+/**
+ * Job queue runner utility methods
+ *
+ * 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 JobQueue
+ */
+
+/**
+ * Job queue runner utility methods
+ *
+ * @ingroup JobQueue
+ * @since 1.24
+ */
+class JobRunner {
+ /** @var callable|null Debug output handler */
+ protected $debug;
+
+ /**
+ * @param callable $debug Optional debug output handler
+ */
+ public function setDebugHandler( $debug ) {
+ $this->debug = $debug;
+ }
+
+ /**
+ * Run jobs of the specified number/type for the specified time
+ *
+ * The response map has a 'job' field that lists status of each job, including:
+ * - type : the job type
+ * - status : ok/failed
+ * - error : any error message string
+ * - time : the job run time in ms
+ * The response map also has:
+ * - backoffs : the (job type => seconds) map of backoff times
+ * - elapsed : the total time spent running tasks in ms
+ * - reached : the reason the script finished, one of (none-ready, job-limit, time-limit)
+ *
+ * This method outputs status information only if a debug handler was set.
+ * Any exceptions are caught and logged, but are not reported as output.
+ *
+ * @param array $options Map of parameters:
+ * - type : the job type (or false for the default types)
+ * - maxJobs : maximum number of jobs to run
+ * - maxTime : maximum time in seconds before stopping
+ * - throttle : whether to respect job backoff configuration
+ * @return array Summary response that can easily be JSON serialized
+ */
+ public function run( array $options ) {
+ $response = array( 'jobs' => array(), 'reached' => 'none-ready' );
+
+ $type = isset( $options['type'] ) ? $options['type'] : false;
+ $maxJobs = isset( $options['maxJobs'] ) ? $options['maxJobs'] : false;
+ $maxTime = isset( $options['maxTime'] ) ? $options['maxTime'] : false;
+ $noThrottle = isset( $options['throttle'] ) && !$options['throttle'];
+
+ $group = JobQueueGroup::singleton();
+ // Handle any required periodic queue maintenance
+ $count = $group->executeReadyPeriodicTasks();
+ if ( $count > 0 ) {
+ $this->runJobsLog( "Executed $count periodic queue task(s)." );
+ }
+
+ // Flush any pending DB writes for sanity
+ wfGetLBFactory()->commitMasterChanges();
+
+ // Some jobs types should not run until a certain timestamp
+ $backoffs = array(); // map of (type => UNIX expiry)
+ $backoffDeltas = array(); // map of (type => seconds)
+ $wait = 'wait'; // block to read backoffs the first time
+
+ $jobsRun = 0;
+ $timeMsTotal = 0;
+ $flags = JobQueueGroup::USE_CACHE;
+ $startTime = microtime( true ); // time since jobs started running
+ $lastTime = microtime( true ); // time since last slave check
+ do {
+ // Sync the persistent backoffs with concurrent runners
+ $backoffs = $this->syncBackoffDeltas( $backoffs, $backoffDeltas, $wait );
+ $blacklist = $noThrottle ? array() : array_keys( $backoffs );
+ $wait = 'nowait'; // less important now
+
+ if ( $type === false ) {
+ $job = $group->pop( JobQueueGroup::TYPE_DEFAULT, $flags, $blacklist );
+ } elseif ( in_array( $type, $blacklist ) ) {
+ $job = false; // requested queue in backoff state
+ } else {
+ $job = $group->pop( $type ); // job from a single queue
+ }
+
+ if ( $job ) { // found a job
+ $jType = $job->getType();
+
+ // Back off of certain jobs for a while (for throttling and for errors)
+ $ttw = $this->getBackoffTimeToWait( $job );
+ if ( $ttw > 0 ) {
+ // Always add the delta for other runners in case the time running the
+ // job negated the backoff for each individually but not collectively.
+ $backoffDeltas[$jType] = isset( $backoffDeltas[$jType] )
+ ? $backoffDeltas[$jType] + $ttw
+ : $ttw;
+ $backoffs = $this->syncBackoffDeltas( $backoffs, $backoffDeltas, $wait );
+ }
+
+ $this->runJobsLog( $job->toString() . " STARTING" );
+
+ // Run the job...
+ wfProfileIn( __METHOD__ . '-' . get_class( $job ) );
+ $jobStartTime = microtime( true );
+ try {
+ ++$jobsRun;
+ $status = $job->run();
+ $error = $job->getLastError();
+ wfGetLBFactory()->commitMasterChanges();
+ } catch ( MWException $e ) {
+ MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+ $status = false;
+ $error = get_class( $e ) . ': ' . $e->getMessage();
+ MWExceptionHandler::logException( $e );
+ }
+ $timeMs = intval( ( microtime( true ) - $jobStartTime ) * 1000 );
+ wfProfileOut( __METHOD__ . '-' . get_class( $job ) );
+ $timeMsTotal += $timeMs;
+
+ // Mark the job as done on success or when the job cannot be retried
+ if ( $status !== false || !$job->allowRetries() ) {
+ $group->ack( $job ); // done
+ }
+
+ // Back off of certain jobs for a while (for throttling and for errors)
+ if ( $status === false && mt_rand( 0, 49 ) == 0 ) {
+ $ttw = max( $ttw, 30 ); // too many errors
+ $backoffDeltas[$jType] = isset( $backoffDeltas[$jType] )
+ ? $backoffDeltas[$jType] + $ttw
+ : $ttw;
+ }
+
+ if ( $status === false ) {
+ $this->runJobsLog( $job->toString() . " t=$timeMs error={$error}" );
+ } else {
+ $this->runJobsLog( $job->toString() . " t=$timeMs good" );
+ }
+
+ $response['jobs'][] = array(
+ 'type' => $jType,
+ 'status' => ( $status === false ) ? 'failed' : 'ok',
+ 'error' => $error,
+ 'time' => $timeMs
+ );
+
+ // Break out if we hit the job count or wall time limits...
+ if ( $maxJobs && $jobsRun >= $maxJobs ) {
+ $response['reached'] = 'job-limit';
+ break;
+ } elseif ( $maxTime && ( microtime( true ) - $startTime ) > $maxTime ) {
+ $response['reached'] = 'time-limit';
+ break;
+ }
+
+ // Don't let any of the main DB slaves get backed up
+ $timePassed = microtime( true ) - $lastTime;
+ if ( $timePassed >= 5 || $timePassed < 0 ) {
+ wfWaitForSlaves( $lastTime );
+ $lastTime = microtime( true );
+ }
+ // Don't let any queue slaves/backups fall behind
+ if ( $jobsRun > 0 && ( $jobsRun % 100 ) == 0 ) {
+ $group->waitForBackups();
+ }
+
+ // Bail if near-OOM instead of in a job
+ $this->assertMemoryOK();
+ }
+ } while ( $job ); // stop when there are no jobs
+
+ // Sync the persistent backoffs for the next runJobs.php pass
+ if ( $backoffDeltas ) {
+ $this->syncBackoffDeltas( $backoffs, $backoffDeltas, 'wait' );
+ }
+
+ $response['backoffs'] = $backoffs;
+ $response['elapsed'] = $timeMsTotal;
+
+ return $response;
+ }
+
+ /**
+ * @param Job $job
+ * @return int Seconds for this runner to avoid doing more jobs of this type
+ * @see $wgJobBackoffThrottling
+ */
+ private function getBackoffTimeToWait( Job $job ) {
+ global $wgJobBackoffThrottling;
+
+ if ( !isset( $wgJobBackoffThrottling[$job->getType()] ) ||
+ $job instanceof DuplicateJob // no work was done
+ ) {
+ return 0; // not throttled
+ }
+
+ $itemsPerSecond = $wgJobBackoffThrottling[$job->getType()];
+ if ( $itemsPerSecond <= 0 ) {
+ return 0; // not throttled
+ }
+
+ $seconds = 0;
+ if ( $job->workItemCount() > 0 ) {
+ $exactSeconds = $job->workItemCount() / $itemsPerSecond;
+ // use randomized rounding
+ $seconds = floor( $exactSeconds );
+ $remainder = $exactSeconds - $seconds;
+ $seconds += ( mt_rand() / mt_getrandmax() < $remainder ) ? 1 : 0;
+ }
+
+ return (int)$seconds;
+ }
+
+ /**
+ * Get the previous backoff expiries from persistent storage
+ * On I/O or lock acquisition failure this returns the original $backoffs.
+ *
+ * @param array $backoffs Map of (job type => UNIX timestamp)
+ * @param string $mode Lock wait mode - "wait" or "nowait"
+ * @return array Map of (job type => backoff expiry timestamp)
+ */
+ private function loadBackoffs( array $backoffs, $mode = 'wait' ) {
+ $section = new ProfileSection( __METHOD__ );
+
+ $file = wfTempDir() . '/mw-runJobs-backoffs.json';
+ if ( is_file( $file ) ) {
+ $noblock = ( $mode === 'nowait' ) ? LOCK_NB : 0;
+ $handle = fopen( $file, 'rb' );
+ if ( !flock( $handle, LOCK_SH | $noblock ) ) {
+ fclose( $handle );
+ return $backoffs; // don't wait on lock
+ }
+ $content = stream_get_contents( $handle );
+ flock( $handle, LOCK_UN );
+ fclose( $handle );
+ $ctime = microtime( true );
+ $cBackoffs = json_decode( $content, true ) ?: array();
+ foreach ( $cBackoffs as $type => $timestamp ) {
+ if ( $timestamp < $ctime ) {
+ unset( $cBackoffs[$type] );
+ }
+ }
+ } else {
+ $cBackoffs = array();
+ }
+
+ return $cBackoffs;
+ }
+
+ /**
+ * Merge the current backoff expiries from persistent storage
+ *
+ * The $deltas map is set to an empty array on success.
+ * On I/O or lock acquisition failure this returns the original $backoffs.
+ *
+ * @param array $backoffs Map of (job type => UNIX timestamp)
+ * @param array $deltas Map of (job type => seconds)
+ * @param string $mode Lock wait mode - "wait" or "nowait"
+ * @return array The new backoffs account for $backoffs and the latest file data
+ */
+ private function syncBackoffDeltas( array $backoffs, array &$deltas, $mode = 'wait' ) {
+ $section = new ProfileSection( __METHOD__ );
+
+ if ( !$deltas ) {
+ return $this->loadBackoffs( $backoffs, $mode );
+ }
+
+ $noblock = ( $mode === 'nowait' ) ? LOCK_NB : 0;
+ $file = wfTempDir() . '/mw-runJobs-backoffs.json';
+ $handle = fopen( $file, 'wb+' );
+ if ( !flock( $handle, LOCK_EX | $noblock ) ) {
+ fclose( $handle );
+ return $backoffs; // don't wait on lock
+ }
+ $ctime = microtime( true );
+ $content = stream_get_contents( $handle );
+ $cBackoffs = json_decode( $content, true ) ?: array();
+ foreach ( $deltas as $type => $seconds ) {
+ $cBackoffs[$type] = isset( $cBackoffs[$type] ) && $cBackoffs[$type] >= $ctime
+ ? $cBackoffs[$type] + $seconds
+ : $ctime + $seconds;
+ }
+ foreach ( $cBackoffs as $type => $timestamp ) {
+ if ( $timestamp < $ctime ) {
+ unset( $cBackoffs[$type] );
+ }
+ }
+ ftruncate( $handle, 0 );
+ fwrite( $handle, json_encode( $cBackoffs ) );
+ flock( $handle, LOCK_UN );
+ fclose( $handle );
+
+ $deltas = array();
+
+ return $cBackoffs;
+ }
+
+ /**
+ * Make sure that this script is not too close to the memory usage limit.
+ * It is better to die in between jobs than OOM right in the middle of one.
+ * @throws MWException
+ */
+ private function assertMemoryOK() {
+ static $maxBytes = null;
+ if ( $maxBytes === null ) {
+ $m = array();
+ if ( preg_match( '!^(\d+)(k|m|g|)$!i', ini_get( 'memory_limit' ), $m ) ) {
+ list( , $num, $unit ) = $m;
+ $conv = array( 'g' => 1073741824, 'm' => 1048576, 'k' => 1024, '' => 1 );
+ $maxBytes = $num * $conv[strtolower( $unit )];
+ } else {
+ $maxBytes = 0;
+ }
+ }
+ $usedBytes = memory_get_usage();
+ if ( $maxBytes && $usedBytes >= 0.95 * $maxBytes ) {
+ throw new MWException( "Detected excessive memory usage ($usedBytes/$maxBytes)." );
+ }
+ }
+
+ /**
+ * Log the job message
+ * @param string $msg The message to log
+ */
+ private function runJobsLog( $msg ) {
+ if ( $this->debug ) {
+ call_user_func_array( $this->debug, array( wfTimestamp( TS_DB ) . " $msg\n" ) );
+ }
+ wfDebugLog( 'runJobs', $msg );
+ }
+}
diff --git a/includes/jobqueue/JobSpecification.php b/includes/jobqueue/JobSpecification.php
new file mode 100644
index 00000000..9fa7747f
--- /dev/null
+++ b/includes/jobqueue/JobSpecification.php
@@ -0,0 +1,189 @@
+<?php
+/**
+ * Job queue task description base code.
+ *
+ * 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 JobQueue
+ */
+
+/**
+ * Job queue task description interface
+ *
+ * @ingroup JobQueue
+ * @since 1.23
+ */
+interface IJobSpecification {
+ /**
+ * @return string Job type
+ */
+ public function getType();
+
+ /**
+ * @return array
+ */
+ public function getParams();
+
+ /**
+ * @return int|null UNIX timestamp to delay running this job until, otherwise null
+ */
+ public function getReleaseTimestamp();
+
+ /**
+ * @return bool Whether only one of each identical set of jobs should be run
+ */
+ public function ignoreDuplicates();
+
+ /**
+ * Subclasses may need to override this to make duplication detection work.
+ * The resulting map conveys everything that makes the job unique. This is
+ * only checked if ignoreDuplicates() returns true, meaning that duplicate
+ * jobs are supposed to be ignored.
+ *
+ * @return array Map of key/values
+ */
+ public function getDeduplicationInfo();
+
+ /**
+ * @return Title Descriptive title (this can simply be informative)
+ */
+ public function getTitle();
+}
+
+/**
+ * Job queue task description base code
+ *
+ * Example usage:
+ * <code>
+ * $job = new JobSpecification(
+ * 'null',
+ * array( 'lives' => 1, 'usleep' => 100, 'pi' => 3.141569 ),
+ * array( 'removeDuplicates' => 1 ),
+ * Title::makeTitle( NS_SPECIAL, 'nullity' )
+ * );
+ * JobQueueGroup::singleton()->push( $job )
+ * </code>
+ *
+ * @ingroup JobQueue
+ * @since 1.23
+ */
+class JobSpecification implements IJobSpecification {
+ /** @var string */
+ protected $type;
+
+ /** @var array Array of job parameters or false if none */
+ protected $params;
+
+ /** @var Title */
+ protected $title;
+
+ /** @var bool Expensive jobs may set this to true */
+ protected $ignoreDuplicates;
+
+ /**
+ * @param string $type
+ * @param array $params Map of key/values
+ * @param array $opts Map of key/values
+ * @param Title $title Optional descriptive title
+ */
+ public function __construct(
+ $type, array $params, array $opts = array(), Title $title = null
+ ) {
+ $this->validateParams( $params );
+
+ $this->type = $type;
+ $this->params = $params;
+ $this->title = $title ?: Title::newMainPage();
+ $this->ignoreDuplicates = !empty( $opts['removeDuplicates'] );
+ }
+
+ /**
+ * @param array $params
+ */
+ protected function validateParams( array $params ) {
+ foreach ( $params as $p => $v ) {
+ if ( is_array( $v ) ) {
+ $this->validateParams( $v );
+ } elseif ( !is_scalar( $v ) && $v !== null ) {
+ throw new UnexpectedValueException( "Job parameter $p is not JSON serializable." );
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getType() {
+ return $this->type;
+ }
+
+ /**
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->title;
+ }
+
+ /**
+ * @return array
+ */
+ public function getParams() {
+ return $this->params;
+ }
+
+ /**
+ * @return int|null UNIX timestamp to delay running this job until, otherwise null
+ */
+ public function getReleaseTimestamp() {
+ return isset( $this->params['jobReleaseTimestamp'] )
+ ? wfTimestampOrNull( TS_UNIX, $this->params['jobReleaseTimestamp'] )
+ : null;
+ }
+
+ /**
+ * @return bool Whether only one of each identical set of jobs should be run
+ */
+ public function ignoreDuplicates() {
+ return $this->ignoreDuplicates;
+ }
+
+ /**
+ * Subclasses may need to override this to make duplication detection work.
+ * The resulting map conveys everything that makes the job unique. This is
+ * only checked if ignoreDuplicates() returns true, meaning that duplicate
+ * jobs are supposed to be ignored.
+ *
+ * @return array Map of key/values
+ */
+ public function getDeduplicationInfo() {
+ $info = array(
+ 'type' => $this->getType(),
+ 'namespace' => $this->getTitle()->getNamespace(),
+ 'title' => $this->getTitle()->getDBkey(),
+ 'params' => $this->getParams()
+ );
+ if ( is_array( $info['params'] ) ) {
+ // Identical jobs with different "root" jobs should count as duplicates
+ unset( $info['params']['rootJobSignature'] );
+ unset( $info['params']['rootJobTimestamp'] );
+ // Likewise for jobs with different delay times
+ unset( $info['params']['jobReleaseTimestamp'] );
+ }
+
+ return $info;
+ }
+}
diff --git a/includes/job/README b/includes/jobqueue/README
index c11d5a78..c11d5a78 100644
--- a/includes/job/README
+++ b/includes/jobqueue/README
diff --git a/includes/job/aggregator/JobQueueAggregator.php b/includes/jobqueue/aggregator/JobQueueAggregator.php
index a8186abd..8600eed9 100644
--- a/includes/job/aggregator/JobQueueAggregator.php
+++ b/includes/jobqueue/aggregator/JobQueueAggregator.php
@@ -34,9 +34,11 @@ abstract class JobQueueAggregator {
/**
* @param array $params
*/
- protected function __construct( array $params ) {}
+ protected function __construct( array $params ) {
+ }
/**
+ * @throws MWException
* @return JobQueueAggregator
*/
final public static function singleton() {
@@ -74,6 +76,7 @@ abstract class JobQueueAggregator {
wfProfileIn( __METHOD__ );
$ok = $this->doNotifyQueueEmpty( $wiki, $type );
wfProfileOut( __METHOD__ );
+
return $ok;
}
@@ -93,6 +96,7 @@ abstract class JobQueueAggregator {
wfProfileIn( __METHOD__ );
$ok = $this->doNotifyQueueNonEmpty( $wiki, $type );
wfProfileOut( __METHOD__ );
+
return $ok;
}
@@ -104,12 +108,13 @@ abstract class JobQueueAggregator {
/**
* Get the list of all of the queues with jobs
*
- * @return Array (job type => (list of wiki IDs))
+ * @return array (job type => (list of wiki IDs))
*/
final public function getAllReadyWikiQueues() {
wfProfileIn( __METHOD__ );
$res = $this->doGetAllReadyWikiQueues();
wfProfileOut( __METHOD__ );
+
return $res;
}
@@ -127,6 +132,7 @@ abstract class JobQueueAggregator {
wfProfileIn( __METHOD__ );
$res = $this->doPurge();
wfProfileOut( __METHOD__ );
+
return $res;
}
@@ -139,7 +145,7 @@ abstract class JobQueueAggregator {
* Get all databases that have a pending job.
* This poll all the queues and is this expensive.
*
- * @return Array (job type => (list of wiki IDs))
+ * @return array (job type => (list of wiki IDs))
*/
protected function findPendingWikiQueues() {
global $wgLocalDatabases;
diff --git a/includes/job/aggregator/JobQueueAggregatorMemc.php b/includes/jobqueue/aggregator/JobQueueAggregatorMemc.php
index 9434da04..ae266ef3 100644
--- a/includes/job/aggregator/JobQueueAggregatorMemc.php
+++ b/includes/jobqueue/aggregator/JobQueueAggregatorMemc.php
@@ -34,11 +34,10 @@ class JobQueueAggregatorMemc extends JobQueueAggregator {
protected $cacheTTL; // integer; seconds
/**
- * @params include:
+ * @param array $params Possible keys:
* - objectCache : Name of an object cache registered in $wgObjectCaches.
* This defaults to the one specified by $wgMainCacheType.
* - cacheTTL : Seconds to cache the aggregate data before regenerating.
- * @param array $params
*/
protected function __construct( array $params ) {
parent::__construct( $params );
@@ -65,6 +64,7 @@ class JobQueueAggregatorMemc extends JobQueueAggregator {
}
$this->cache->delete( "$key:lock" ); // unlock
}
+
return true;
}
@@ -103,6 +103,7 @@ class JobQueueAggregatorMemc extends JobQueueAggregator {
$this->cache->delete( "$key:rebuild" ); // unlock
}
}
+
return is_array( $pendingDbInfo )
? $pendingDbInfo['pendingDBs']
: array(); // cache is both empty and locked
diff --git a/includes/job/aggregator/JobQueueAggregatorRedis.php b/includes/jobqueue/aggregator/JobQueueAggregatorRedis.php
index c6a799df..db9e764c 100644
--- a/includes/job/aggregator/JobQueueAggregatorRedis.php
+++ b/includes/jobqueue/aggregator/JobQueueAggregatorRedis.php
@@ -32,23 +32,27 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
/** @var RedisConnectionPool */
protected $redisPool;
+ /** @var array List of Redis server addresses */
+ protected $servers;
+
/**
- * @params include:
- * - redisConfig : An array of parameters to RedisConnectionPool::__construct().
- * - redisServer : A hostname/port combination or the absolute path of a UNIX socket.
- * If a hostname is specified but no port, the standard port number
- * 6379 will be used. Required.
- * @param array $params
+ * @param array $params Possible keys:
+ * - redisConfig : An array of parameters to RedisConnectionPool::__construct().
+ * - redisServers : Array of server entries, the first being the primary and the
+ * others being fallback servers. Each entry is either a hostname/port
+ * combination or the absolute path of a UNIX socket.
+ * If a hostname is specified but no port, the standard port number
+ * 6379 will be used. Required.
*/
protected function __construct( array $params ) {
parent::__construct( $params );
- $this->server = $params['redisServer'];
+ $this->servers = isset( $params['redisServers'] )
+ ? $params['redisServers']
+ : array( $params['redisServer'] ); // b/c
+ $params['redisConfig']['serializer'] = 'none';
$this->redisPool = RedisConnectionPool::singleton( $params['redisConfig'] );
}
- /**
- * @see JobQueueAggregator::doNotifyQueueEmpty()
- */
protected function doNotifyQueueEmpty( $wiki, $type ) {
$conn = $this->getConnection();
if ( !$conn ) {
@@ -56,83 +60,85 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
}
try {
$conn->hDel( $this->getReadyQueueKey(), $this->encQueueName( $type, $wiki ) );
+
return true;
} catch ( RedisException $e ) {
$this->handleException( $conn, $e );
+
return false;
}
}
- /**
- * @see JobQueueAggregator::doNotifyQueueNonEmpty()
- */
protected function doNotifyQueueNonEmpty( $wiki, $type ) {
$conn = $this->getConnection();
if ( !$conn ) {
return false;
}
try {
+ $conn->multi( Redis::PIPELINE );
+ $conn->hSetNx( $this->getQueueTypesKey(), $type, 'enabled' );
$conn->hSet( $this->getReadyQueueKey(), $this->encQueueName( $type, $wiki ), time() );
+ $conn->exec();
+
return true;
} catch ( RedisException $e ) {
$this->handleException( $conn, $e );
+
return false;
}
}
- /**
- * @see JobQueueAggregator::doAllGetReadyWikiQueues()
- */
protected function doGetAllReadyWikiQueues() {
$conn = $this->getConnection();
if ( !$conn ) {
return array();
}
try {
- $conn->multi( Redis::PIPELINE );
- $conn->exists( $this->getReadyQueueKey() );
- $conn->hGetAll( $this->getReadyQueueKey() );
- list( $exists, $map ) = $conn->exec();
+ $map = $conn->hGetAll( $this->getReadyQueueKey() );
- if ( $exists ) { // cache hit
+ if ( is_array( $map ) && isset( $map['_epoch'] ) ) {
+ unset( $map['_epoch'] ); // ignore
$pendingDBs = array(); // (type => list of wikis)
foreach ( $map as $key => $time ) {
list( $type, $wiki ) = $this->dencQueueName( $key );
$pendingDBs[$type][] = $wiki;
}
- } else { // cache miss
+ } else {
// Avoid duplicated effort
+ $rand = wfRandomString( 32 );
$conn->multi( Redis::MULTI );
- $conn->setnx( $this->getReadyQueueKey() . ":lock", 1 );
- $conn->expire( $this->getReadyQueueKey() . ":lock", 3600 );
+ $conn->setex( "{$rand}:lock", 3600, 1 );
+ $conn->renamenx( "{$rand}:lock", $this->getReadyQueueKey() . ":lock" );
if ( $conn->exec() !== array( true, true ) ) { // lock
+ $conn->delete( "{$rand}:lock" );
return array(); // already in progress
}
$pendingDBs = $this->findPendingWikiQueues(); // (type => list of wikis)
- $conn->delete( $this->getReadyQueueKey() . ":lock" ); // unlock
-
+ $conn->multi( Redis::PIPELINE );
$now = time();
- $map = array();
+ $map = array( '_epoch' => time() ); // dummy key for empty Redis collections
foreach ( $pendingDBs as $type => $wikis ) {
+ $conn->hSetNx( $this->getQueueTypesKey(), $type, 'enabled' );
foreach ( $wikis as $wiki ) {
$map[$this->encQueueName( $type, $wiki )] = $now;
}
}
$conn->hMSet( $this->getReadyQueueKey(), $map );
+ $conn->exec();
+
+ $conn->delete( $this->getReadyQueueKey() . ":lock" ); // unlock
}
return $pendingDBs;
} catch ( RedisException $e ) {
$this->handleException( $conn, $e );
+
return array();
}
}
- /**
- * @see JobQueueAggregator::doPurge()
- */
protected function doPurge() {
$conn = $this->getConnection();
if ( !$conn ) {
@@ -140,21 +146,32 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
}
try {
$conn->delete( $this->getReadyQueueKey() );
+ // leave key at getQueueTypesKey() alone
} catch ( RedisException $e ) {
$this->handleException( $conn, $e );
+
return false;
}
+
return true;
}
/**
* Get a connection to the server that handles all sub-queues for this queue
*
- * @return Array (server name, Redis instance)
+ * @return RedisConnRef|bool Returns false on failure
* @throws MWException
*/
protected function getConnection() {
- return $this->redisPool->getConnection( $this->server );
+ $conn = false;
+ foreach ( $this->servers as $server ) {
+ $conn = $this->redisPool->getConnection( $server );
+ if ( $conn ) {
+ break;
+ }
+ }
+
+ return $conn;
}
/**
@@ -163,14 +180,21 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
* @return void
*/
protected function handleException( RedisConnRef $conn, $e ) {
- $this->redisPool->handleException( $this->server, $conn, $e );
+ $this->redisPool->handleError( $conn, $e );
}
/**
* @return string
*/
private function getReadyQueueKey() {
- return "jobqueue:aggregator:h-ready-queues:v1"; // global
+ return "jobqueue:aggregator:h-ready-queues:v2"; // global
+ }
+
+ /**
+ * @return string
+ */
+ private function getQueueTypesKey() {
+ return "jobqueue:aggregator:h-queue-types:v2"; // global
}
/**
@@ -188,6 +212,7 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
*/
private function dencQueueName( $name ) {
list( $type, $wiki ) = explode( '/', $name, 2 );
+
return array( rawurldecode( $type ), rawurldecode( $wiki ) );
}
}
diff --git a/includes/job/jobs/AssembleUploadChunksJob.php b/includes/jobqueue/jobs/AssembleUploadChunksJob.php
index 6237e568..9e9bda6f 100644
--- a/includes/job/jobs/AssembleUploadChunksJob.php
+++ b/includes/jobqueue/jobs/AssembleUploadChunksJob.php
@@ -27,8 +27,8 @@
* @ingroup Upload
*/
class AssembleUploadChunksJob extends Job {
- public function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'AssembleUploadChunks', $title, $params, $id );
+ public function __construct( $title, $params ) {
+ parent::__construct( 'AssembleUploadChunks', $title, $params );
$this->removeDuplicates = true;
}
@@ -39,6 +39,7 @@ class AssembleUploadChunksJob extends Job {
$user = $context->getUser();
if ( !$user->isLoggedIn() ) {
$this->setLastError( "Could not load the author user from session." );
+
return false;
}
@@ -47,7 +48,9 @@ class AssembleUploadChunksJob extends Job {
// with the session correctly. Note that being able to load
// the user does not necessarily mean the session was loaded.
// Most likely cause by suhosin.session.encrypt = On.
- $this->setLastError( "Error associating with user session. Try setting suhosin.session.encrypt = Off" );
+ $this->setLastError( "Error associating with user session. " .
+ "Try setting suhosin.session.encrypt = Off" );
+
return false;
}
@@ -71,6 +74,7 @@ class AssembleUploadChunksJob extends Job {
array( 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status )
);
$this->setLastError( $status->getWikiText() );
+
return false;
}
@@ -108,8 +112,12 @@ class AssembleUploadChunksJob extends Job {
)
);
$this->setLastError( get_class( $e ) . ": " . $e->getText() );
+ // To be extra robust.
+ MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+
return false;
}
+
return true;
}
@@ -118,6 +126,7 @@ class AssembleUploadChunksJob extends Job {
if ( is_array( $info['params'] ) ) {
$info['params'] = array( 'filekey' => $info['params']['filekey'] );
}
+
return $info;
}
diff --git a/includes/job/jobs/DoubleRedirectJob.php b/includes/jobqueue/jobs/DoubleRedirectJob.php
index 33e749b8..2561f2f1 100644
--- a/includes/job/jobs/DoubleRedirectJob.php
+++ b/includes/jobqueue/jobs/DoubleRedirectJob.php
@@ -27,17 +27,25 @@
* @ingroup JobQueue
*/
class DoubleRedirectJob extends Job {
- var $reason, $redirTitle;
+ /** @var string Reason for the change, 'maintenance' or 'move'. Suffix fo
+ * message key 'double-redirect-fixed-'.
+ */
+ private $reason;
- /**
- * @var User
+ /** @var Title The title which has changed, redirects pointing to this
+ * title are fixed
*/
- static $user;
+ private $redirTitle;
+
+ /** @var User */
+ private static $user;
/**
* Insert jobs into the job queue to fix redirects to the given title
- * @param string $reason the reason for the fix, see message "double-redirect-fixed-<reason>"
- * @param $redirTitle Title: the title which has changed, redirects pointing to this title are fixed
+ * @param string $reason The reason for the fix, see message
+ * "double-redirect-fixed-<reason>"
+ * @param Title $redirTitle The title which has changed, redirects
+ * pointing to this title are fixed
* @param bool $destTitle Not used
*/
public static function fixRedirects( $reason, $redirTitle, $destTitle = false ) {
@@ -73,8 +81,12 @@ class DoubleRedirectJob extends Job {
JobQueueGroup::singleton()->push( $jobs );
}
- function __construct( $title, $params = false, $id = 0 ) {
- parent::__construct( 'fixDoubleRedirect', $title, $params, $id );
+ /**
+ * @param Title $title
+ * @param array|bool $params
+ */
+ function __construct( $title, $params = false ) {
+ parent::__construct( 'fixDoubleRedirect', $title, $params );
$this->reason = $params['reason'];
$this->redirTitle = Title::newFromText( $params['redirTitle'] );
}
@@ -85,18 +97,21 @@ class DoubleRedirectJob extends Job {
function run() {
if ( !$this->redirTitle ) {
$this->setLastError( 'Invalid title' );
+
return false;
}
$targetRev = Revision::newFromTitle( $this->title, false, Revision::READ_LATEST );
if ( !$targetRev ) {
wfDebug( __METHOD__ . ": target redirect already deleted, ignoring\n" );
+
return true;
}
$content = $targetRev->getContent();
$currentDest = $content ? $content->getRedirectTarget() : null;
if ( !$currentDest || !$currentDest->equals( $this->redirTitle ) ) {
wfDebug( __METHOD__ . ": Redirect has changed since the job was queued\n" );
+
return true;
}
@@ -104,13 +119,16 @@ class DoubleRedirectJob extends Job {
$mw = MagicWord::get( 'staticredirect' );
if ( $content->matchMagicWord( $mw ) ) {
wfDebug( __METHOD__ . ": skipping: suppressed with __STATICREDIRECT__\n" );
+
return true;
}
// Find the current final destination
$newTitle = self::getFinalDestination( $this->redirTitle );
if ( !$newTitle ) {
- wfDebug( __METHOD__ . ": skipping: single redirect, circular redirect or invalid redirect destination\n" );
+ wfDebug( __METHOD__ .
+ ": skipping: single redirect, circular redirect or invalid redirect destination\n" );
+
return true;
}
if ( $newTitle->equals( $this->redirTitle ) ) {
@@ -128,12 +146,14 @@ class DoubleRedirectJob extends Job {
if ( $newContent->equals( $content ) ) {
$this->setLastError( 'Content unchanged???' );
+
return false;
}
$user = $this->getUser();
if ( !$user ) {
$this->setLastError( 'Invalid user' );
+
return false;
}
@@ -156,9 +176,9 @@ class DoubleRedirectJob extends Job {
/**
* Get the final destination of a redirect
*
- * @param $title Title
+ * @param Title $title
*
- * @return bool if the specified title is not a redirect, or if it is a circular redirect
+ * @return bool If the specified title is not a redirect, or if it is a circular redirect
*/
public static function getFinalDestination( $title ) {
$dbw = wfGetDB( DB_MASTER );
@@ -171,11 +191,12 @@ class DoubleRedirectJob extends Job {
$titleText = $title->getPrefixedDBkey();
if ( isset( $seenTitles[$titleText] ) ) {
wfDebug( __METHOD__, "Circular redirect detected, aborting\n" );
+
return false;
}
$seenTitles[$titleText] = true;
- if ( $title->getInterwiki() ) {
+ if ( $title->isExternal() ) {
// If the target is interwiki, we have to break early (bug 40352).
// Otherwise it will look up a row in the local page table
// with the namespace/page of the interwiki target which can cause
@@ -195,9 +216,15 @@ class DoubleRedirectJob extends Job {
# No redirect from here, chain terminates
break;
} else {
- $dest = $title = Title::makeTitle( $row->rd_namespace, $row->rd_title, '', $row->rd_interwiki );
+ $dest = $title = Title::makeTitle(
+ $row->rd_namespace,
+ $row->rd_title,
+ '',
+ $row->rd_interwiki
+ );
}
}
+
return $dest;
}
@@ -210,12 +237,14 @@ class DoubleRedirectJob extends Job {
*/
function getUser() {
if ( !self::$user ) {
- self::$user = User::newFromName( wfMessage( 'double-redirect-fixer' )->inContentLanguage()->text() );
+ $username = wfMessage( 'double-redirect-fixer' )->inContentLanguage()->text();
+ self::$user = User::newFromName( $username );
# User::newFromName() can return false on a badly configured wiki.
if ( self::$user && !self::$user->isLoggedIn() ) {
self::$user->addToDatabase();
}
}
+
return self::$user;
}
}
diff --git a/includes/job/jobs/DuplicateJob.php b/includes/jobqueue/jobs/DuplicateJob.php
index be1bfe5c..1fa6cefe 100644
--- a/includes/job/jobs/DuplicateJob.php
+++ b/includes/jobqueue/jobs/DuplicateJob.php
@@ -30,12 +30,11 @@ final class DuplicateJob extends Job {
/**
* Callers should use DuplicateJob::newFromJob() instead
*
- * @param $title Title
- * @param array $params job parameters
- * @param $id Integer: job id
+ * @param Title $title
+ * @param array $params Job parameters
*/
- function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'duplicate', $title, $params, $id );
+ function __construct( $title, $params ) {
+ parent::__construct( 'duplicate', $title, $params );
}
/**
@@ -45,11 +44,12 @@ final class DuplicateJob extends Job {
* @return Job
*/
public static function newFromJob( Job $job ) {
- $djob = new self( $job->getTitle(), $job->getParams(), $job->id );
+ $djob = new self( $job->getTitle(), $job->getParams() );
$djob->command = $job->getType();
$djob->params = is_array( $djob->params ) ? $djob->params : array();
$djob->params = array( 'isDuplicate' => true ) + $djob->params;
$djob->metadata = $job->metadata;
+
return $djob;
}
diff --git a/includes/job/jobs/EmaillingJob.php b/includes/jobqueue/jobs/EmaillingJob.php
index 9fbf3124..df8ae63e 100644
--- a/includes/job/jobs/EmaillingJob.php
+++ b/includes/jobqueue/jobs/EmaillingJob.php
@@ -28,8 +28,8 @@
* @ingroup JobQueue
*/
class EmaillingJob extends Job {
- function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'sendMail', Title::newMainPage(), $params, $id );
+ function __construct( $title, $params ) {
+ parent::__construct( 'sendMail', Title::newMainPage(), $params );
}
function run() {
@@ -43,5 +43,4 @@ class EmaillingJob extends Job {
return $status->isOK();
}
-
}
diff --git a/includes/job/jobs/EnotifNotifyJob.php b/includes/jobqueue/jobs/EnotifNotifyJob.php
index bbe988d0..1ed99a58 100644
--- a/includes/job/jobs/EnotifNotifyJob.php
+++ b/includes/jobqueue/jobs/EnotifNotifyJob.php
@@ -27,9 +27,8 @@
* @ingroup JobQueue
*/
class EnotifNotifyJob extends Job {
-
- function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'enotifNotify', $title, $params, $id );
+ function __construct( $title, $params ) {
+ parent::__construct( 'enotifNotify', $title, $params );
}
function run() {
@@ -39,7 +38,7 @@ class EnotifNotifyJob extends Job {
$editor = User::newFromId( $this->params['editorID'] );
// B/C, only the name might be given.
} else {
- # FIXME: newFromName could return false on a badly configured wiki.
+ # @todo FIXME: newFromName could return false on a badly configured wiki.
$editor = User::newFromName( $this->params['editor'], false );
}
$enotif->actuallyNotifyOnPageChange(
@@ -52,7 +51,7 @@ class EnotifNotifyJob extends Job {
$this->params['watchers'],
$this->params['pageStatus']
);
+
return true;
}
-
}
diff --git a/includes/jobqueue/jobs/HTMLCacheUpdateJob.php b/includes/jobqueue/jobs/HTMLCacheUpdateJob.php
new file mode 100644
index 00000000..4d1e72c9
--- /dev/null
+++ b/includes/jobqueue/jobs/HTMLCacheUpdateJob.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * HTML cache invalidation of all pages linking to a given title.
+ *
+ * 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 Cache
+ */
+
+/**
+ * Job to purge the cache for all pages that link to or use another page or file
+ *
+ * This job comes in a few variants:
+ * - a) Recursive jobs to purge caches for backlink pages for a given title.
+ * These jobs have have (recursive:true,table:<table>) set.
+ * - b) Jobs to purge caches for a set of titles (the job title is ignored).
+ * These jobs have have (pages:(<page ID>:(<namespace>,<title>),...) set.
+ *
+ * @ingroup JobQueue
+ */
+class HTMLCacheUpdateJob extends Job {
+ function __construct( $title, $params = '' ) {
+ parent::__construct( 'htmlCacheUpdate', $title, $params );
+ // Base backlink purge jobs can be de-duplicated
+ $this->removeDuplicates = ( !isset( $params['range'] ) && !isset( $params['pages'] ) );
+ }
+
+ function run() {
+ global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
+
+ static $expected = array( 'recursive', 'pages' ); // new jobs have one of these
+
+ $oldRangeJob = false;
+ if ( !array_intersect( array_keys( $this->params ), $expected ) ) {
+ // B/C for older job params formats that lack these fields:
+ // a) base jobs with just ("table") and b) range jobs with ("table","start","end")
+ if ( isset( $this->params['start'] ) && isset( $this->params['end'] ) ) {
+ $oldRangeJob = true;
+ } else {
+ $this->params['recursive'] = true; // base job
+ }
+ }
+
+ // Job to purge all (or a range of) backlink pages for a page
+ if ( !empty( $this->params['recursive'] ) ) {
+ // Convert this into no more than $wgUpdateRowsPerJob HTMLCacheUpdateJob per-title
+ // jobs and possibly a recursive HTMLCacheUpdateJob job for the rest of the backlinks
+ $jobs = BacklinkJobUtils::partitionBacklinkJob(
+ $this,
+ $wgUpdateRowsPerJob,
+ $wgUpdateRowsPerQuery, // jobs-per-title
+ // Carry over information for de-duplication
+ array( 'params' => $this->getRootJobParams() )
+ );
+ JobQueueGroup::singleton()->push( $jobs );
+ // Job to purge pages for for a set of titles
+ } elseif ( isset( $this->params['pages'] ) ) {
+ $this->invalidateTitles( $this->params['pages'] );
+ // B/C for job to purge a range of backlink pages for a given page
+ } elseif ( $oldRangeJob ) {
+ $titleArray = $this->title->getBacklinkCache()->getLinks(
+ $this->params['table'], $this->params['start'], $this->params['end'] );
+
+ $pages = array(); // same format BacklinkJobUtils uses
+ foreach ( $titleArray as $tl ) {
+ $pages[$tl->getArticleId()] = array( $tl->getNamespace(), $tl->getDbKey() );
+ }
+
+ $jobs = array();
+ foreach ( array_chunk( $pages, $wgUpdateRowsPerJob ) as $pageChunk ) {
+ $jobs[] = new HTMLCacheUpdateJob( $this->title,
+ array(
+ 'table' => $this->params['table'],
+ 'pages' => $pageChunk
+ ) + $this->getRootJobParams() // carry over information for de-duplication
+ );
+ }
+ JobQueueGroup::singleton()->push( $jobs );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $pages Map of (page ID => (namespace, DB key)) entries
+ */
+ protected function invalidateTitles( array $pages ) {
+ global $wgUpdateRowsPerQuery, $wgUseFileCache, $wgUseSquid;
+
+ // Get all page IDs in this query into an array
+ $pageIds = array_keys( $pages );
+ if ( !$pageIds ) {
+ return;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ // The page_touched field will need to be bumped for these pages.
+ // Only bump it to the present time if no "rootJobTimestamp" was known.
+ // If it is known, it can be used instead, which avoids invalidating output
+ // that was in fact generated *after* the relevant dependency change time
+ // (e.g. template edit). This is particularily useful since refreshLinks jobs
+ // save back parser output and usually run along side htmlCacheUpdate jobs;
+ // their saved output would be invalidated by using the current timestamp.
+ if ( isset( $this->params['rootJobTimestamp'] ) ) {
+ $touchTimestamp = $this->params['rootJobTimestamp'];
+ } else {
+ $touchTimestamp = wfTimestampNow();
+ }
+
+ // Update page_touched (skipping pages already touched since the root job).
+ // Check $wgUpdateRowsPerQuery for sanity; batch jobs are sized by that already.
+ foreach ( array_chunk( $pageIds, $wgUpdateRowsPerQuery ) as $batch ) {
+ $dbw->update( 'page',
+ array( 'page_touched' => $dbw->timestamp( $touchTimestamp ) ),
+ array( 'page_id' => $batch,
+ // don't invalidated pages that were already invalidated
+ "page_touched < " . $dbw->addQuotes( $dbw->timestamp( $touchTimestamp ) )
+ ),
+ __METHOD__
+ );
+ }
+ // Get the list of affected pages (races only mean something else did the purge)
+ $titleArray = TitleArray::newFromResult( $dbw->select(
+ 'page',
+ array( 'page_namespace', 'page_title' ),
+ array( 'page_id' => $pageIds, 'page_touched' => $dbw->timestamp( $touchTimestamp ) ),
+ __METHOD__
+ ) );
+
+ // Update squid
+ if ( $wgUseSquid ) {
+ $u = SquidUpdate::newFromTitles( $titleArray );
+ $u->doUpdate();
+ }
+
+ // Update file cache
+ if ( $wgUseFileCache ) {
+ foreach ( $titleArray as $title ) {
+ HTMLFileCache::clearFileCache( $title );
+ }
+ }
+ }
+
+ public function workItemCount() {
+ return isset( $this->params['pages'] ) ? count( $this->params['pages'] ) : 1;
+ }
+}
diff --git a/includes/job/jobs/NullJob.php b/includes/jobqueue/jobs/NullJob.php
index b6164a5d..66291e9d 100644
--- a/includes/job/jobs/NullJob.php
+++ b/includes/jobqueue/jobs/NullJob.php
@@ -46,12 +46,11 @@
*/
class NullJob extends Job {
/**
- * @param $title Title (can be anything)
- * @param array $params job parameters (lives, usleep)
- * @param $id Integer: job id
+ * @param Title $title
+ * @param array $params Job parameters (lives, usleep)
*/
- function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'null', $title, $params, $id );
+ function __construct( $title, $params ) {
+ parent::__construct( 'null', $title, $params );
if ( !isset( $this->params['lives'] ) ) {
$this->params['lives'] = 1;
}
@@ -71,6 +70,7 @@ class NullJob extends Job {
$job = new self( $this->title, $params );
JobQueueGroup::singleton()->push( $job );
}
+
return true;
}
}
diff --git a/includes/job/jobs/PublishStashedFileJob.php b/includes/jobqueue/jobs/PublishStashedFileJob.php
index 5a24f93c..918a392d 100644
--- a/includes/job/jobs/PublishStashedFileJob.php
+++ b/includes/jobqueue/jobs/PublishStashedFileJob.php
@@ -27,8 +27,8 @@
* @ingroup Upload
*/
class PublishStashedFileJob extends Job {
- public function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'PublishStashedFile', $title, $params, $id );
+ public function __construct( $title, $params ) {
+ parent::__construct( 'PublishStashedFile', $title, $params );
$this->removeDuplicates = true;
}
@@ -39,6 +39,7 @@ class PublishStashedFileJob extends Job {
$user = $context->getUser();
if ( !$user->isLoggedIn() ) {
$this->setLastError( "Could not load the author user from session." );
+
return false;
}
@@ -47,11 +48,12 @@ class PublishStashedFileJob extends Job {
// with the session correctly. Note that being able to load
// the user does not necessarily mean the session was loaded.
// Most likely cause by suhosin.session.encrypt = On.
- $this->setLastError( "Error associating with user session. Try setting suhosin.session.encrypt = Off" );
+ $this->setLastError( "Error associating with user session. " .
+ "Try setting suhosin.session.encrypt = Off" );
+
return false;
}
-
UploadBase::setSessionStatus(
$this->params['filekey'],
array( 'result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood() )
@@ -74,6 +76,7 @@ class PublishStashedFileJob extends Job {
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
);
$this->setLastError( "Could not verify upload." );
+
return false;
}
@@ -90,6 +93,7 @@ class PublishStashedFileJob extends Job {
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
);
$this->setLastError( $status->getWikiText() );
+
return false;
}
@@ -121,8 +125,13 @@ class PublishStashedFileJob extends Job {
)
);
$this->setLastError( get_class( $e ) . ": " . $e->getText() );
+ // To prevent potential database referential integrity issues.
+ // See bug 32551.
+ MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+
return false;
}
+
return true;
}
@@ -131,6 +140,7 @@ class PublishStashedFileJob extends Job {
if ( is_array( $info['params'] ) ) {
$info['params'] = array( 'filekey' => $info['params']['filekey'] );
}
+
return $info;
}
diff --git a/includes/jobqueue/jobs/RefreshLinksJob.php b/includes/jobqueue/jobs/RefreshLinksJob.php
new file mode 100644
index 00000000..f82af273
--- /dev/null
+++ b/includes/jobqueue/jobs/RefreshLinksJob.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * Job to update link tables for pages
+ *
+ * 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 JobQueue
+ */
+
+/**
+ * Job to update link tables for pages
+ *
+ * This job comes in a few variants:
+ * - a) Recursive jobs to update links for backlink pages for a given title.
+ * These jobs have have (recursive:true,table:<table>) set.
+ * - b) Jobs to update links for a set of pages (the job title is ignored).
+ * These jobs have have (pages:(<page ID>:(<namespace>,<title>),...) set.
+ * - c) Jobs to update links for a single page (the job title)
+ * These jobs need no extra fields set.
+ *
+ * @ingroup JobQueue
+ */
+class RefreshLinksJob extends Job {
+ const PARSE_THRESHOLD_SEC = 1.0;
+
+ function __construct( $title, $params = '' ) {
+ parent::__construct( 'refreshLinks', $title, $params );
+ // Base backlink update jobs and per-title update jobs can be de-duplicated.
+ // If template A changes twice before any jobs run, a clean queue will have:
+ // (A base, A base)
+ // The second job is ignored by the queue on insertion.
+ // Suppose, many pages use template A, and that template itself uses template B.
+ // An edit to both will first create two base jobs. A clean FIFO queue will have:
+ // (A base, B base)
+ // When these jobs run, the queue will have per-title and remnant partition jobs:
+ // (titleX,titleY,titleZ,...,A remnant,titleM,titleN,titleO,...,B remnant)
+ // Some these jobs will be the same, and will automatically be ignored by
+ // the queue upon insertion. Some title jobs will run before the duplicate is
+ // inserted, so the work will still be done twice in those cases. More titles
+ // can be de-duplicated as the remnant jobs continue to be broken down. This
+ // works best when $wgUpdateRowsPerJob, and either the pages have few backlinks
+ // and/or the backlink sets for pages A and B are almost identical.
+ $this->removeDuplicates = !isset( $params['range'] )
+ && ( !isset( $params['pages'] ) || count( $params['pages'] ) == 1 );
+ }
+
+ function run() {
+ global $wgUpdateRowsPerJob;
+
+ // Job to update all (or a range of) backlink pages for a page
+ if ( !empty( $this->params['recursive'] ) ) {
+ // Carry over information for de-duplication
+ $extraParams = $this->getRootJobParams();
+ // Avoid slave lag when fetching templates.
+ // When the outermost job is run, we know that the caller that enqueued it must have
+ // committed the relevant changes to the DB by now. At that point, record the master
+ // position and pass it along as the job recursively breaks into smaller range jobs.
+ // Hopefully, when leaf jobs are popped, the slaves will have reached that position.
+ if ( isset( $this->params['masterPos'] ) ) {
+ $extraParams['masterPos'] = $this->params['masterPos'];
+ } elseif ( wfGetLB()->getServerCount() > 1 ) {
+ $extraParams['masterPos'] = wfGetLB()->getMasterPos();
+ } else {
+ $extraParams['masterPos'] = false;
+ }
+ // Convert this into no more than $wgUpdateRowsPerJob RefreshLinks per-title
+ // jobs and possibly a recursive RefreshLinks job for the rest of the backlinks
+ $jobs = BacklinkJobUtils::partitionBacklinkJob(
+ $this,
+ $wgUpdateRowsPerJob,
+ 1, // job-per-title
+ array( 'params' => $extraParams )
+ );
+ JobQueueGroup::singleton()->push( $jobs );
+ // Job to update link tables for for a set of titles
+ } elseif ( isset( $this->params['pages'] ) ) {
+ foreach ( $this->params['pages'] as $pageId => $nsAndKey ) {
+ list( $ns, $dbKey ) = $nsAndKey;
+ $this->runForTitle( Title::makeTitleSafe( $ns, $dbKey ) );
+ }
+ // Job to update link tables for a given title
+ } else {
+ $this->runForTitle( $this->title );
+ }
+
+ return true;
+ }
+
+ protected function runForTitle( Title $title = null ) {
+ $linkCache = LinkCache::singleton();
+ $linkCache->clear();
+
+ if ( is_null( $title ) ) {
+ $this->setLastError( "refreshLinks: Invalid title" );
+ return false;
+ }
+
+ // Wait for the DB of the current/next slave DB handle to catch up to the master.
+ // This way, we get the correct page_latest for templates or files that just changed
+ // milliseconds ago, having triggered this job to begin with.
+ if ( isset( $this->params['masterPos'] ) && $this->params['masterPos'] !== false ) {
+ wfGetLB()->waitFor( $this->params['masterPos'] );
+ }
+
+ $page = WikiPage::factory( $title );
+
+ // Fetch the current revision...
+ $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
+ if ( !$revision ) {
+ $this->setLastError( "refreshLinks: Article not found {$title->getPrefixedDBkey()}" );
+ return false; // XXX: what if it was just deleted?
+ }
+ $content = $revision->getContent( Revision::RAW );
+ if ( !$content ) {
+ // If there is no content, pretend the content is empty
+ $content = $revision->getContentHandler()->makeEmptyContent();
+ }
+
+ $parserOutput = false;
+ $parserOptions = $page->makeParserOptions( 'canonical' );
+ // If page_touched changed after this root job (with a good slave lag skew factor),
+ // then it is likely that any views of the pages already resulted in re-parses which
+ // are now in cache. This can be reused to avoid expensive parsing in some cases.
+ if ( isset( $this->params['rootJobTimestamp'] ) ) {
+ $skewedTimestamp = wfTimestamp( TS_UNIX, $this->params['rootJobTimestamp'] ) + 5;
+ if ( $page->getLinksTimestamp() > wfTimestamp( TS_MW, $skewedTimestamp ) ) {
+ // Something already updated the backlinks since this job was made
+ return true;
+ }
+ if ( $page->getTouched() > wfTimestamp( TS_MW, $skewedTimestamp ) ) {
+ $parserOutput = ParserCache::singleton()->getDirty( $page, $parserOptions );
+ if ( $parserOutput && $parserOutput->getCacheTime() <= $skewedTimestamp ) {
+ $parserOutput = false; // too stale
+ }
+ }
+ }
+ // Fetch the current revision and parse it if necessary...
+ if ( $parserOutput == false ) {
+ $start = microtime( true );
+ // Revision ID must be passed to the parser output to get revision variables correct
+ $parserOutput = $content->getParserOutput(
+ $title, $revision->getId(), $parserOptions, false );
+ $ellapsed = microtime( true ) - $start;
+ // If it took a long time to render, then save this back to the cache to avoid
+ // wasted CPU by other apaches or job runners. We don't want to always save to
+ // cache as this cause cause high cache I/O and LRU churn when a template changes.
+ if ( $ellapsed >= self::PARSE_THRESHOLD_SEC
+ && $page->isParserCacheUsed( $parserOptions, $revision->getId() )
+ && $parserOutput->isCacheable()
+ ) {
+ $ctime = wfTimestamp( TS_MW, (int)$start ); // cache time
+ ParserCache::singleton()->save(
+ $parserOutput, $page, $parserOptions, $ctime, $revision->getId()
+ );
+ }
+ }
+
+ $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
+ DataUpdate::runUpdates( $updates );
+
+ InfoAction::invalidateCache( $title );
+
+ return true;
+ }
+
+ public function getDeduplicationInfo() {
+ $info = parent::getDeduplicationInfo();
+ if ( is_array( $info['params'] ) ) {
+ // Don't let highly unique "masterPos" values ruin duplicate detection
+ unset( $info['params']['masterPos'] );
+ // For per-pages jobs, the job title is that of the template that changed
+ // (or similar), so remove that since it ruins duplicate detection
+ if ( isset( $info['pages'] ) ) {
+ unset( $info['namespace'] );
+ unset( $info['title'] );
+ }
+ }
+
+ return $info;
+ }
+
+ public function workItemCount() {
+ return isset( $this->params['pages'] ) ? count( $this->params['pages'] ) : 1;
+ }
+}
diff --git a/includes/job/jobs/RefreshLinksJob.php b/includes/jobqueue/jobs/RefreshLinksJob2.php
index 4fc8bac6..97405aeb 100644
--- a/includes/job/jobs/RefreshLinksJob.php
+++ b/includes/jobqueue/jobs/RefreshLinksJob2.php
@@ -22,103 +22,22 @@
*/
/**
- * Background job to update links for a given title.
- *
- * @ingroup JobQueue
- */
-class RefreshLinksJob extends Job {
- function __construct( $title, $params = '', $id = 0 ) {
- parent::__construct( 'refreshLinks', $title, $params, $id );
- $this->removeDuplicates = true; // job is expensive
- }
-
- /**
- * Run a refreshLinks job
- * @return boolean success
- */
- function run() {
- $linkCache = LinkCache::singleton();
- $linkCache->clear();
-
- if ( is_null( $this->title ) ) {
- $this->error = "refreshLinks: Invalid title";
- return false;
- }
-
- # Wait for the DB of the current/next slave DB handle to catch up to the master.
- # This way, we get the correct page_latest for templates or files that just changed
- # milliseconds ago, having triggered this job to begin with.
- if ( isset( $this->params['masterPos'] ) && $this->params['masterPos'] !== false ) {
- wfGetLB()->waitFor( $this->params['masterPos'] );
- }
-
- $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
- if ( !$revision ) {
- $this->error = 'refreshLinks: Article not found "' .
- $this->title->getPrefixedDBkey() . '"';
- return false; // XXX: what if it was just deleted?
- }
-
- self::runForTitleInternal( $this->title, $revision, __METHOD__ );
-
- return true;
- }
-
- /**
- * @return Array
- */
- public function getDeduplicationInfo() {
- $info = parent::getDeduplicationInfo();
- // Don't let highly unique "masterPos" values ruin duplicate detection
- if ( is_array( $info['params'] ) ) {
- unset( $info['params']['masterPos'] );
- }
- return $info;
- }
-
- /**
- * @param $title Title
- * @param $revision Revision
- * @param $fname string
- * @return void
- */
- public static function runForTitleInternal( Title $title, Revision $revision, $fname ) {
- wfProfileIn( $fname );
- $content = $revision->getContent( Revision::RAW );
-
- if ( !$content ) {
- // if there is no content, pretend the content is empty
- $content = $revision->getContentHandler()->makeEmptyContent();
- }
-
- // Revision ID must be passed to the parser output to get revision variables correct
- $parserOutput = $content->getParserOutput( $title, $revision->getId(), null, false );
-
- $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
- DataUpdate::runUpdates( $updates );
-
- InfoAction::invalidateCache( $title );
-
- wfProfileOut( $fname );
- }
-}
-
-/**
- * Background job to update links for a given title.
- * Newer version for high use templates.
+ * Background job to update links for titles in certain backlink range by page ID.
+ * Newer version for high use templates. This is deprecated by RefreshLinksPartitionJob.
*
* @ingroup JobQueue
+ * @deprecated since 1.23
*/
class RefreshLinksJob2 extends Job {
- function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'refreshLinks2', $title, $params, $id );
+ function __construct( $title, $params ) {
+ parent::__construct( 'refreshLinks2', $title, $params );
// Base jobs for large templates can easily be de-duplicated
$this->removeDuplicates = !isset( $params['start'] ) && !isset( $params['end'] );
}
/**
* Run a refreshLinks2 job
- * @return boolean success
+ * @return bool Success
*/
function run() {
global $wgUpdateRowsPerJob;
@@ -182,9 +101,9 @@ class RefreshLinksJob2 extends Job {
}
/**
- * @param $table string
- * @param $masterPos mixed
- * @return Array
+ * @param string $table
+ * @param mixed $masterPos
+ * @return array
*/
protected function getSingleTitleJobs( $table, $masterPos ) {
# The "start"/"end" fields are not set for the base jobs
@@ -209,7 +128,7 @@ class RefreshLinksJob2 extends Job {
}
/**
- * @return Array
+ * @return array
*/
public function getDeduplicationInfo() {
$info = parent::getDeduplicationInfo();
diff --git a/includes/job/jobs/UploadFromUrlJob.php b/includes/jobqueue/jobs/UploadFromUrlJob.php
index c993cfb4..a09db15a 100644
--- a/includes/job/jobs/UploadFromUrlJob.php
+++ b/includes/jobqueue/jobs/UploadFromUrlJob.php
@@ -33,18 +33,14 @@
class UploadFromUrlJob extends Job {
const SESSION_KEYNAME = 'wsUploadFromUrlJobData';
- /**
- * @var UploadFromUrl
- */
+ /** @var UploadFromUrl */
public $upload;
- /**
- * @var User
- */
+ /** @var User */
protected $user;
- public function __construct( $title, $params, $id = 0 ) {
- parent::__construct( 'uploadFromUrl', $title, $params, $id );
+ public function __construct( $title, $params ) {
+ parent::__construct( 'uploadFromUrl', $title, $params );
}
public function run() {
@@ -66,6 +62,7 @@ class UploadFromUrlJob extends Job {
$status = $this->upload->fetchFile( $opts );
if ( !$status->isOk() ) {
$this->leaveMessage( $status );
+
return true;
}
@@ -74,6 +71,7 @@ class UploadFromUrlJob extends Job {
if ( $result['status'] != UploadBase::OK ) {
$status = $this->upload->convertVerifyErrorToStatus( $result );
$this->leaveMessage( $status );
+
return true;
}
@@ -85,6 +83,8 @@ class UploadFromUrlJob extends Job {
# Stash the upload
$key = $this->upload->stashFile();
+ // @todo FIXME: This has been broken for a while.
+ // User::leaveUserMessage() does not exist.
if ( $this->params['leaveMessage'] ) {
$this->user->leaveUserMessage(
wfMessage( 'upload-warning-subj' )->text(),
@@ -111,25 +111,27 @@ class UploadFromUrlJob extends Job {
$this->user
);
$this->leaveMessage( $status );
- return true;
+ return true;
}
/**
* Leave a message on the user talk page or in the session according to
* $params['leaveMessage'].
*
- * @param $status Status
+ * @param Status $status
*/
protected function leaveMessage( $status ) {
if ( $this->params['leaveMessage'] ) {
if ( $status->isGood() ) {
+ // @todo FIXME: user->leaveUserMessage does not exist.
$this->user->leaveUserMessage( wfMessage( 'upload-success-subj' )->text(),
wfMessage( 'upload-success-msg',
$this->upload->getTitle()->getText(),
$this->params['url']
)->text() );
} else {
+ // @todo FIXME: user->leaveUserMessage does not exist.
$this->user->leaveUserMessage( wfMessage( 'upload-failure-subj' )->text(),
wfMessage( 'upload-failure-msg',
$status->getWikiText(),
@@ -153,9 +155,9 @@ class UploadFromUrlJob extends Job {
* Store a result in the session data. Note that the caller is responsible
* for appropriate session_start and session_write_close calls.
*
- * @param string $result the result (Success|Warning|Failure)
- * @param string $dataKey the key of the extra data
- * @param $dataValue Mixed: the extra data itself
+ * @param string $result The result (Success|Warning|Failure)
+ * @param string $dataKey The key of the extra data
+ * @param mixed $dataValue The extra data itself
*/
protected function storeResultInSession( $result, $dataKey, $dataValue ) {
$session =& self::getSessionData( $this->params['sessionKey'] );
@@ -172,13 +174,14 @@ class UploadFromUrlJob extends Job {
}
/**
- * @param $key
+ * @param string $key
* @return mixed
*/
public static function &getSessionData( $key ) {
if ( !isset( $_SESSION[self::SESSION_KEYNAME][$key] ) ) {
$_SESSION[self::SESSION_KEYNAME][$key] = array();
}
+
return $_SESSION[self::SESSION_KEYNAME][$key];
}
}
diff --git a/includes/jobqueue/utils/BacklinkJobUtils.php b/includes/jobqueue/utils/BacklinkJobUtils.php
new file mode 100644
index 00000000..c8e5df66
--- /dev/null
+++ b/includes/jobqueue/utils/BacklinkJobUtils.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Job to update links for a given title.
+ *
+ * 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 JobQueue
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class with Backlink related Job helper methods
+ *
+ * @ingroup JobQueue
+ * @since 1.23
+ */
+class BacklinkJobUtils {
+ /**
+ * Break down $job into approximately ($bSize/$cSize) leaf jobs and a single partition
+ * job that covers the remaining backlink range (if needed). Jobs for the first $bSize
+ * titles are collated ($cSize per job) into leaf jobs to do actual work. All the
+ * resulting jobs are of the same class as $job. No partition job is returned if the
+ * range covered by $job was less than $bSize, as the leaf jobs have full coverage.
+ *
+ * The leaf jobs have the 'pages' param set to a (<page ID>:(<namespace>,<DB key>),...)
+ * map so that the run() function knows what pages to act on. The leaf jobs will keep
+ * the same job title as the parent job (e.g. $job).
+ *
+ * The partition jobs have the 'range' parameter set to a map of the format
+ * (start:<integer>, end:<integer>, batchSize:<integer>, subranges:((<start>,<end>),...)),
+ * the 'table' parameter set to that of $job, and the 'recursive' parameter set to true.
+ * This method can be called on the resulting job to repeat the process again.
+ *
+ * The job provided ($job) must have the 'recursive' parameter set to true and the 'table'
+ * parameter must be set to a backlink table. The job title will be used as the title to
+ * find backlinks for. Any 'range' parameter must follow the same format as mentioned above.
+ * This should be managed by recursive calls to this method.
+ *
+ * The first jobs return are always the leaf jobs. This lets the caller use push() to
+ * put them directly into the queue and works well if the queue is FIFO. In such a queue,
+ * the leaf jobs have to get finished first before anything can resolve the next partition
+ * job, which keeps the queue very small.
+ *
+ * $opts includes:
+ * - params : extra job parameters to include in each job
+ *
+ * @param Job $job
+ * @param int $bSize BacklinkCache partition size; usually $wgUpdateRowsPerJob
+ * @param int $cSize Max titles per leaf job; Usually 1 or a modest value
+ * @param array $opts Optional parameter map
+ * @return Job[] List of Job objects
+ */
+ public static function partitionBacklinkJob( Job $job, $bSize, $cSize, $opts = array() ) {
+ $class = get_class( $job );
+ $title = $job->getTitle();
+ $params = $job->getParams();
+
+ if ( isset( $params['pages'] ) || empty( $params['recursive'] ) ) {
+ $ranges = array(); // sanity; this is a leaf node
+ wfWarn( __METHOD__ . " called on {$job->getType()} leaf job (explosive recursion)." );
+ } elseif ( isset( $params['range'] ) ) {
+ // This is a range job to trigger the insertion of partitioned/title jobs...
+ $ranges = $params['range']['subranges'];
+ $realBSize = $params['range']['batchSize'];
+ } else {
+ // This is a base job to trigger the insertion of partitioned jobs...
+ $ranges = $title->getBacklinkCache()->partition( $params['table'], $bSize );
+ $realBSize = $bSize;
+ }
+
+ $extraParams = isset( $opts['params'] ) ? $opts['params'] : array();
+
+ $jobs = array();
+ // Combine the first range (of size $bSize) backlinks into leaf jobs
+ if ( isset( $ranges[0] ) ) {
+ list( $start, $end ) = $ranges[0];
+ $titles = $title->getBacklinkCache()->getLinks( $params['table'], $start, $end );
+ foreach ( array_chunk( iterator_to_array( $titles ), $cSize ) as $titleBatch ) {
+ $pages = array();
+ foreach ( $titleBatch as $tl ) {
+ $pages[$tl->getArticleId()] = array( $tl->getNamespace(), $tl->getDBKey() );
+ }
+ $jobs[] = new $class(
+ $title, // maintain parent job title
+ array( 'pages' => $pages ) + $extraParams
+ );
+ }
+ }
+ // Take all of the remaining ranges and build a partition job from it
+ if ( isset( $ranges[1] ) ) {
+ $jobs[] = new $class(
+ $title, // maintain parent job title
+ array(
+ 'recursive' => true,
+ 'table' => $params['table'],
+ 'range' => array(
+ 'start' => $ranges[1][0],
+ 'end' => $ranges[count( $ranges ) - 1][1],
+ 'batchSize' => $realBSize,
+ 'subranges' => array_slice( $ranges, 1 )
+ ),
+ ) + $extraParams
+ );
+ }
+
+ return $jobs;
+ }
+}
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php
index d6116512..f3e5c76d 100644
--- a/includes/json/FormatJson.php
+++ b/includes/json/FormatJson.php
@@ -24,7 +24,6 @@
* JSON formatter wrapper class
*/
class FormatJson {
-
/**
* Skip escaping most characters above U+007F for readability and compactness.
* This encoding option saves 3 to 8 bytes (uncompressed) for each such character;
@@ -56,6 +55,22 @@ class FormatJson {
const ALL_OK = 3;
/**
+ * If set, treat json objects '{...}' as associative arrays. Without this option,
+ * json objects will be converted to stdClass.
+ * The value is set to 1 to be backward compatible with 'true' that was used before.
+ *
+ * @since 1.24
+ */
+ const FORCE_ASSOC = 0x100;
+
+ /**
+ * If set, attempts to fix invalid json.
+ *
+ * @since 1.24
+ */
+ const TRY_FIXING = 0x200;
+
+ /**
* Regex that matches whitespace inside empty arrays and objects.
*
* This doesn't affect regular strings inside the JSON because those can't
@@ -96,45 +111,127 @@ class FormatJson {
* (cf. FormatJson::XMLMETA_OK). Use Xml::encodeJsVar() instead in such cases.
*
* @param mixed $value The value to encode. Can be any type except a resource.
- * @param bool $pretty If true, add non-significant whitespace to improve readability.
+ * @param string|bool $pretty If a string, add non-significant whitespace to improve
+ * readability, using that string for indentation. If true, use the default indent
+ * string (four spaces).
* @param int $escaping Bitfield consisting of _OK class constants
* @return string|bool: String if successful; false upon failure
*/
public static function encode( $value, $pretty = false, $escaping = 0 ) {
+ if ( !is_string( $pretty ) ) {
+ $pretty = $pretty ? ' ' : false;
+ }
+
if ( defined( 'JSON_UNESCAPED_UNICODE' ) ) {
return self::encode54( $value, $pretty, $escaping );
}
+
return self::encode53( $value, $pretty, $escaping );
}
/**
- * Decodes a JSON string.
+ * Decodes a JSON string. It is recommended to use FormatJson::parse(), which returns more comprehensive
+ * result in case of an error, and has more parsing options.
*
* @param string $value The JSON string being decoded
* @param bool $assoc When true, returned objects will be converted into associative arrays.
*
- * @return mixed: the value encoded in JSON in appropriate PHP type.
- * `null` is returned if the JSON cannot be decoded or if the encoded data is deeper than
- * the recursion limit.
+ * @return mixed The value encoded in JSON in appropriate PHP type.
+ * `null` is returned if $value represented `null`, if $value could not be decoded,
+ * or if the encoded data was deeper than the recursion limit.
+ * Use FormatJson::parse() to distinguish between types of `null` and to get proper error code.
*/
public static function decode( $value, $assoc = false ) {
return json_decode( $value, $assoc );
}
/**
+ * Decodes a JSON string.
+ * Unlike FormatJson::decode(), if $value represents null value, it will be properly decoded as valid.
+ *
+ * @param string $value The JSON string being decoded
+ * @param int $options A bit field that allows FORCE_ASSOC, TRY_FIXING
+ * @return Status If valid JSON, the value is available in $result->getValue()
+ */
+ public static function parse( $value, $options = 0 ) {
+ $assoc = ( $options & self::FORCE_ASSOC ) !== 0;
+ $result = json_decode( $value, $assoc );
+ $code = json_last_error();
+
+ if ( $code === JSON_ERROR_SYNTAX && ( $options & self::TRY_FIXING ) !== 0 ) {
+ // The most common error is the trailing comma in a list or an object.
+ // We cannot simply replace /,\s*[}\]]/ because it could be inside a string value.
+ // But we could use the fact that JSON does not allow multi-line string values,
+ // And remove trailing commas if they are et the end of a line.
+ // JSON only allows 4 control characters: [ \t\r\n]. So we must not use '\s' for matching.
+ // Regex match ,]<any non-quote chars>\n or ,\n] with optional spaces/tabs.
+ $count = 0;
+ $value =
+ preg_replace( '/,([ \t]*[}\]][^"\r\n]*([\r\n]|$)|[ \t]*[\r\n][ \t\r\n]*[}\]])/', '$1',
+ $value, - 1, $count );
+ if ( $count > 0 ) {
+ $result = json_decode( $value, $assoc );
+ if ( JSON_ERROR_NONE === json_last_error() ) {
+ // Report warning
+ $st = Status::newGood( $result );
+ $st->warning( wfMessage( 'json-warn-trailing-comma' )->numParams( $count ) );
+ return $st;
+ }
+ }
+ }
+
+ switch ( $code ) {
+ case JSON_ERROR_NONE:
+ return Status::newGood( $result );
+ default:
+ return Status::newFatal( wfMessage( 'json-error-unknown' )->numParams( $code ) );
+ case JSON_ERROR_DEPTH:
+ $msg = 'json-error-depth';
+ break;
+ case JSON_ERROR_STATE_MISMATCH:
+ $msg = 'json-error-state-mismatch';
+ break;
+ case JSON_ERROR_CTRL_CHAR:
+ $msg = 'json-error-ctrl-char';
+ break;
+ case JSON_ERROR_SYNTAX:
+ $msg = 'json-error-syntax';
+ break;
+ case JSON_ERROR_UTF8:
+ $msg = 'json-error-utf8';
+ break;
+ case JSON_ERROR_RECURSION:
+ $msg = 'json-error-recursion';
+ break;
+ case JSON_ERROR_INF_OR_NAN:
+ $msg = 'json-error-inf-or-nan';
+ break;
+ case JSON_ERROR_UNSUPPORTED_TYPE:
+ $msg = 'json-error-unsupported-type';
+ break;
+ }
+ return Status::newFatal( $msg );
+ }
+
+ /**
* JSON encoder wrapper for PHP >= 5.4, which supports useful encoding options.
*
* @param mixed $value
- * @param bool $pretty
+ * @param string|bool $pretty
* @param int $escaping
* @return string|bool
*/
private static function encode54( $value, $pretty, $escaping ) {
+ static $bug66021;
+ if ( $pretty !== false && $bug66021 === null ) {
+ $bug66021 = json_encode( array(), JSON_PRETTY_PRINT ) !== '[]';
+ }
+
// PHP escapes '/' to prevent breaking out of inline script blocks using '</script>',
// which is hardly useful when '<' and '>' are escaped (and inadequate), and such
// escaping negatively impacts the human readability of URLs and similar strings.
$options = JSON_UNESCAPED_SLASHES;
- $options |= $pretty ? JSON_PRETTY_PRINT : 0;
+ $options |= $pretty !== false ? JSON_PRETTY_PRINT : 0;
$options |= ( $escaping & self::UTF8_OK ) ? JSON_UNESCAPED_UNICODE : 0;
$options |= ( $escaping & self::XMLMETA_OK ) ? 0 : ( JSON_HEX_TAG | JSON_HEX_AMP );
$json = json_encode( $value, $options );
@@ -142,14 +239,28 @@ class FormatJson {
return false;
}
- if ( $pretty ) {
- // Remove whitespace inside empty arrays/objects; different JSON encoders
- // vary on this, and we want our output to be consistent across implementations.
- $json = preg_replace( self::WS_CLEANUP_REGEX, '', $json );
+ if ( $pretty !== false ) {
+ // Workaround for <https://bugs.php.net/bug.php?id=66021>
+ if ( $bug66021 ) {
+ $json = preg_replace( self::WS_CLEANUP_REGEX, '', $json );
+ }
+ if ( $pretty !== ' ' ) {
+ // Change the four-space indent to a tab indent
+ $json = str_replace( "\n ", "\n\t", $json );
+ while ( strpos( $json, "\t " ) !== false ) {
+ $json = str_replace( "\t ", "\t\t", $json );
+ }
+
+ if ( $pretty !== "\t" ) {
+ // Change the tab indent to the provided indent
+ $json = str_replace( "\t", $pretty, $json );
+ }
+ }
}
if ( $escaping & self::UTF8_OK ) {
$json = str_replace( self::$badChars, self::$badCharsEscaped, $json );
}
+
return $json;
}
@@ -158,7 +269,7 @@ class FormatJson {
* Therefore, the missing options are implemented here purely in PHP code.
*
* @param mixed $value
- * @param bool $pretty
+ * @param string|bool $pretty
* @param int $escaping
* @return string|bool
*/
@@ -187,9 +298,10 @@ class FormatJson {
$json = str_replace( self::$badChars, self::$badCharsEscaped, $json );
}
- if ( $pretty ) {
- return self::prettyPrint( $json );
+ if ( $pretty !== false ) {
+ return self::prettyPrint( $json, $pretty );
}
+
return $json;
}
@@ -198,9 +310,10 @@ class FormatJson {
* Only needed for PHP < 5.4, which lacks the JSON_PRETTY_PRINT option.
*
* @param string $json
+ * @param string $indentString
* @return string
*/
- private static function prettyPrint( $json ) {
+ private static function prettyPrint( $json, $indentString ) {
$buf = '';
$indent = 0;
$json = strtr( $json, array( '\\\\' => '\\\\', '\"' => "\x01" ) );
@@ -215,11 +328,11 @@ class FormatJson {
++$indent;
// falls through
case ',':
- $buf .= $json[$i] . "\n" . str_repeat( ' ', $indent );
+ $buf .= $json[$i] . "\n" . str_repeat( $indentString, $indent );
break;
case ']':
case '}':
- $buf .= "\n" . str_repeat( ' ', --$indent ) . $json[$i];
+ $buf .= "\n" . str_repeat( $indentString, --$indent ) . $json[$i];
break;
case '"':
$skip = strcspn( $json, '"', $i + 1 ) + 2;
@@ -231,6 +344,7 @@ class FormatJson {
}
}
$buf = preg_replace( self::WS_CLEANUP_REGEX, '', $buf );
+
return str_replace( "\x01", '\"', $buf );
}
}
diff --git a/includes/libs/CSSJanus.php b/includes/libs/CSSJanus.php
index 5a52fc7c..07a83a54 100644
--- a/includes/libs/CSSJanus.php
+++ b/includes/libs/CSSJanus.php
@@ -60,7 +60,7 @@ class CSSJanus {
'lookahead_not_letter' => '(?![a-zA-Z])',
'lookbehind_not_letter' => '(?<![a-zA-Z])',
'chars_within_selector' => '[^\}]*?',
- 'noflip_annotation' => '\/\*\s*@noflip\s*\*\/',
+ 'noflip_annotation' => '\/\*\!?\s*@noflip\s*\*\/',
'noflip_single' => null,
'noflip_class' => null,
'comment' => '/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//',
@@ -88,11 +88,12 @@ class CSSJanus {
* Build patterns we can't define above because they depend on other patterns.
*/
private static function buildPatterns() {
- if ( !is_null( self::$patterns['escape'] ) ) {
+ if (!is_null(self::$patterns['escape'])) {
// Patterns have already been built
return;
}
+ // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
$patterns =& self::$patterns;
$patterns['escape'] = "(?:{$patterns['unicode']}|\\[^\r\n\f0-9a-f])";
$patterns['nmstart'] = "(?:[_a-z]|{$patterns['nonAscii']}|{$patterns['escape']})";
@@ -102,7 +103,7 @@ class CSSJanus {
$patterns['possibly_negative_quantity'] = "((?:-?{$patterns['quantity']})|(?:inherit|auto))";
$patterns['color'] = "(#?{$patterns['nmchar']}+|(?:rgba?|hsla?)\([ \d.,%-]+\))";
$patterns['url_chars'] = "(?:{$patterns['url_special_chars']}|{$patterns['nonAscii']}|{$patterns['escape']})*";
- $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>|\(|\))*?{)";
+ $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>|\(|\)|\[|\]|=|\*=|~=|\^=|'[^']*'])*?{)";
$patterns['lookahead_not_closing_paren'] = "(?!{$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))";
$patterns['lookahead_for_closing_paren'] = "(?={$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))";
$patterns['noflip_single'] = "/({$patterns['noflip_annotation']}{$patterns['lookahead_not_open_brace']}[^;}]+;?)/i";
@@ -117,16 +118,17 @@ class CSSJanus {
$patterns['rtl_in_url'] = "/{$patterns['lookbehind_not_letter']}(rtl){$patterns['lookahead_for_closing_paren']}/i";
$patterns['cursor_east'] = "/{$patterns['lookbehind_not_letter']}([ns]?)e-resize/";
$patterns['cursor_west'] = "/{$patterns['lookbehind_not_letter']}([ns]?)w-resize/";
- $patterns['four_notation_quantity'] = "/(:\s*){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s*[;}])/i";
- $patterns['four_notation_color'] = "/(-color\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s*[;}])/i";
- $patterns['border_radius'] = "/(border-radius\s*:\s*){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s*[;}])/i";
+ $patterns['four_notation_quantity_props'] = "((?:margin|padding|border-width)\s*:\s*)";
+ $patterns['four_notation_quantity'] = "/{$patterns['four_notation_quantity_props']}{$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s*[;}])/i";
+ $patterns['four_notation_color'] = "/((?:-color|border-style)\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s*[;}])/i";
+ $patterns['border_radius'] = "/(border-radius\s*:\s*)([^;}]*)/";
$patterns['box_shadow'] = "/(box-shadow\s*:\s*(?:inset\s*)?){$patterns['possibly_negative_quantity']}/i";
$patterns['text_shadow1'] = "/(text-shadow\s*:\s*){$patterns['color']}(\s*){$patterns['possibly_negative_quantity']}/i";
$patterns['text_shadow2'] = "/(text-shadow\s*:\s*){$patterns['possibly_negative_quantity']}/i";
- // The two regexes below are parenthesized differently then in the original implementation to make the
- // callback's job more straightforward
- $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)(-?{$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/";
- $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)(-?{$patterns['num']})(%)/";
+ $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*(?:[^:;}\s]+\s+)*?)({$patterns['quantity']})/i";
+ $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)(-?{$patterns['num']}%)/i";
+ // @codingStandardsIgnoreEnd
+
}
/**
@@ -136,46 +138,46 @@ class CSSJanus {
* @param $swapLeftRightInURL Boolean: If true, swap 'left' and 'right' in URLs
* @return string Transformed stylesheet
*/
- public static function transform( $css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false ) {
+ public static function transform($css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false) {
// We wrap tokens in ` , not ~ like the original implementation does.
// This was done because ` is not a legal character in CSS and can only
// occur in URLs, where we escape it to %60 before inserting our tokens.
- $css = str_replace( '`', '%60', $css );
+ $css = str_replace('`', '%60', $css);
self::buildPatterns();
// Tokenize single line rules with /* @noflip */
- $noFlipSingle = new CSSJanus_Tokenizer( self::$patterns['noflip_single'], '`NOFLIP_SINGLE`' );
- $css = $noFlipSingle->tokenize( $css );
+ $noFlipSingle = new CSSJanusTokenizer(self::$patterns['noflip_single'], '`NOFLIP_SINGLE`');
+ $css = $noFlipSingle->tokenize($css);
// Tokenize class rules with /* @noflip */
- $noFlipClass = new CSSJanus_Tokenizer( self::$patterns['noflip_class'], '`NOFLIP_CLASS`' );
- $css = $noFlipClass->tokenize( $css );
+ $noFlipClass = new CSSJanusTokenizer(self::$patterns['noflip_class'], '`NOFLIP_CLASS`');
+ $css = $noFlipClass->tokenize($css);
// Tokenize comments
- $comments = new CSSJanus_Tokenizer( self::$patterns['comment'], '`C`' );
- $css = $comments->tokenize( $css );
+ $comments = new CSSJanusTokenizer(self::$patterns['comment'], '`C`');
+ $css = $comments->tokenize($css);
// LTR->RTL fixes start here
- $css = self::fixDirection( $css );
- if ( $swapLtrRtlInURL ) {
- $css = self::fixLtrRtlInURL( $css );
+ $css = self::fixDirection($css);
+ if ($swapLtrRtlInURL) {
+ $css = self::fixLtrRtlInURL($css);
}
- if ( $swapLeftRightInURL ) {
- $css = self::fixLeftRightInURL( $css );
+ if ($swapLeftRightInURL) {
+ $css = self::fixLeftRightInURL($css);
}
- $css = self::fixLeftAndRight( $css );
- $css = self::fixCursorProperties( $css );
- $css = self::fixFourPartNotation( $css );
- $css = self::fixBorderRadius( $css );
- $css = self::fixBackgroundPosition( $css );
- $css = self::fixShadows( $css );
+ $css = self::fixLeftAndRight($css);
+ $css = self::fixCursorProperties($css);
+ $css = self::fixFourPartNotation($css);
+ $css = self::fixBorderRadius($css);
+ $css = self::fixBackgroundPosition($css);
+ $css = self::fixShadows($css);
// Detokenize stuff we tokenized before
- $css = $comments->detokenize( $css );
- $css = $noFlipClass->detokenize( $css );
- $css = $noFlipSingle->detokenize( $css );
+ $css = $comments->detokenize($css);
+ $css = $noFlipClass->detokenize($css);
+ $css = $noFlipSingle->detokenize($css);
return $css;
}
@@ -187,16 +189,19 @@ class CSSJanus {
* and misses "body\n{\ndirection:ltr;\n}". This function does not have
* these problems.
*
- * See http://code.google.com/p/cssjanus/issues/detail?id=15 and
- * TODO: URL
+ * See https://code.google.com/p/cssjanus/issues/detail?id=15
+ *
* @param $css string
* @return string
*/
- private static function fixDirection( $css ) {
- $css = preg_replace( self::$patterns['direction_ltr'],
- '$1' . self::$patterns['tmpToken'], $css );
- $css = preg_replace( self::$patterns['direction_rtl'], '$1ltr', $css );
- $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css );
+ private static function fixDirection($css) {
+ $css = preg_replace(
+ self::$patterns['direction_ltr'],
+ '$1' . self::$patterns['tmpToken'],
+ $css
+ );
+ $css = preg_replace(self::$patterns['direction_rtl'], '$1ltr', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'rtl', $css);
return $css;
}
@@ -206,10 +211,10 @@ class CSSJanus {
* @param $css string
* @return string
*/
- private static function fixLtrRtlInURL( $css ) {
- $css = preg_replace( self::$patterns['ltr_in_url'], self::$patterns['tmpToken'], $css );
- $css = preg_replace( self::$patterns['rtl_in_url'], 'ltr', $css );
- $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css );
+ private static function fixLtrRtlInURL($css) {
+ $css = preg_replace(self::$patterns['ltr_in_url'], self::$patterns['tmpToken'], $css);
+ $css = preg_replace(self::$patterns['rtl_in_url'], 'ltr', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'rtl', $css);
return $css;
}
@@ -219,10 +224,10 @@ class CSSJanus {
* @param $css string
* @return string
*/
- private static function fixLeftRightInURL( $css ) {
- $css = preg_replace( self::$patterns['left_in_url'], self::$patterns['tmpToken'], $css );
- $css = preg_replace( self::$patterns['right_in_url'], 'left', $css );
- $css = str_replace( self::$patterns['tmpToken'], 'right', $css );
+ private static function fixLeftRightInURL($css) {
+ $css = preg_replace(self::$patterns['left_in_url'], self::$patterns['tmpToken'], $css);
+ $css = preg_replace(self::$patterns['right_in_url'], 'left', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'right', $css);
return $css;
}
@@ -232,10 +237,10 @@ class CSSJanus {
* @param $css string
* @return string
*/
- private static function fixLeftAndRight( $css ) {
- $css = preg_replace( self::$patterns['left'], self::$patterns['tmpToken'], $css );
- $css = preg_replace( self::$patterns['right'], 'left', $css );
- $css = str_replace( self::$patterns['tmpToken'], 'right', $css );
+ private static function fixLeftAndRight($css) {
+ $css = preg_replace(self::$patterns['left'], self::$patterns['tmpToken'], $css);
+ $css = preg_replace(self::$patterns['right'], 'left', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'right', $css);
return $css;
}
@@ -245,11 +250,14 @@ class CSSJanus {
* @param $css string
* @return string
*/
- private static function fixCursorProperties( $css ) {
- $css = preg_replace( self::$patterns['cursor_east'],
- '$1' . self::$patterns['tmpToken'], $css );
- $css = preg_replace( self::$patterns['cursor_west'], '$1e-resize', $css );
- $css = str_replace( self::$patterns['tmpToken'], 'w-resize', $css );
+ private static function fixCursorProperties($css) {
+ $css = preg_replace(
+ self::$patterns['cursor_east'],
+ '$1' . self::$patterns['tmpToken'],
+ $css
+ );
+ $css = preg_replace(self::$patterns['cursor_west'], '$1e-resize', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'w-resize', $css);
return $css;
}
@@ -262,28 +270,38 @@ class CSSJanus {
* the bug where whitespace is not preserved when flipping four-part rules
* and four-part color rules with multiple whitespace characters between
* colors are not recognized.
- * See http://code.google.com/p/cssjanus/issues/detail?id=16
+ * See https://code.google.com/p/cssjanus/issues/detail?id=16
* @param $css string
* @return string
*/
- private static function fixFourPartNotation( $css ) {
- $css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$3$8$5$6$7$4$9', $css );
- $css = preg_replace( self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4$9', $css );
+ private static function fixFourPartNotation($css) {
+ $css = preg_replace(self::$patterns['four_notation_quantity'], '$1$2$3$8$5$6$7$4$9', $css);
+ $css = preg_replace(self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4$9', $css);
return $css;
}
/**
- * Swaps appropriate corners in four-part border-radius rules.
- * Needs to undo the effect of fixFourPartNotation() on those rules, too.
+ * Swaps appropriate corners in border-radius values.
*
* @param $css string
* @return string
*/
- private static function fixBorderRadius( $css ) {
- // Undo four_notation_quantity
- $css = preg_replace( self::$patterns['border_radius'], '$1$2$3$8$5$6$7$4$9', $css );
- // Do the real thing
- $css = preg_replace( self::$patterns['border_radius'], '$1$4$3$2$5$8$7$6$9', $css );
+ private static function fixBorderRadius($css) {
+ $css = preg_replace_callback(self::$patterns['border_radius'], function ($matches) {
+ $pre = $matches[1];
+ $values = $matches[2];
+ $numValues = count(preg_split('/\s+/', trim($values)));
+ switch ($numValues) {
+ case 4:
+ $values = preg_replace('/^(\S+)(\s*)(\S+)(\s*)(\S+)(\s*)(\S+)/', '$3$2$1$4$7$6$5', $values);
+ break;
+ case 3:
+ case 2:
+ $values = preg_replace('/^(\S+)(\s*)(\S+)/', '$3$2$1', $values);
+ break;
+ }
+ return $pre . $values;
+ }, $css);
return $css;
}
@@ -294,31 +312,31 @@ class CSSJanus {
* @param $css string
* @return string
*/
- private static function fixShadows( $css ) {
+ private static function fixShadows($css) {
// Flips the sign of a CSS value, possibly with a unit.
// (We can't just negate the value with unary minus due to the units.)
- $flipSign = function ( $cssValue ) {
+ $flipSign = function ($cssValue) {
// Don't mangle zeroes
- if ( intval( $cssValue ) === 0 ) {
+ if (floatval($cssValue) === 0.0) {
return $cssValue;
- } elseif ( $cssValue[0] === '-' ) {
- return substr( $cssValue, 1 );
+ } elseif ($cssValue[0] === '-') {
+ return substr($cssValue, 1);
} else {
return "-" . $cssValue;
}
};
- $css = preg_replace_callback( self::$patterns['box_shadow'], function ( $matches ) use ( $flipSign ) {
- return $matches[1] . $flipSign( $matches[2] );
- }, $css );
+ $css = preg_replace_callback(self::$patterns['box_shadow'], function ($matches) use ($flipSign) {
+ return $matches[1] . $flipSign($matches[2]);
+ }, $css);
- $css = preg_replace_callback( self::$patterns['text_shadow1'], function ( $matches ) use ( $flipSign ) {
- return $matches[1] . $matches[2] . $matches[3] . $flipSign( $matches[4] );
- }, $css );
+ $css = preg_replace_callback(self::$patterns['text_shadow1'], function ($matches) use ($flipSign) {
+ return $matches[1] . $matches[2] . $matches[3] . $flipSign($matches[4]);
+ }, $css);
- $css = preg_replace_callback( self::$patterns['text_shadow2'], function ( $matches ) use ( $flipSign ) {
- return $matches[1] . $flipSign( $matches[2] );
- }, $css );
+ $css = preg_replace_callback(self::$patterns['text_shadow2'], function ($matches) use ($flipSign) {
+ return $matches[1] . $flipSign($matches[2]);
+ }, $css);
return $css;
}
@@ -328,16 +346,22 @@ class CSSJanus {
* @param $css string
* @return string
*/
- private static function fixBackgroundPosition( $css ) {
- $replaced = preg_replace_callback( self::$patterns['bg_horizontal_percentage'],
- array( 'self', 'calculateNewBackgroundPosition' ), $css );
- if ( $replaced !== null ) {
- // Check for null; sometimes preg_replace_callback() returns null here for some weird reason
+ private static function fixBackgroundPosition($css) {
+ $replaced = preg_replace_callback(
+ self::$patterns['bg_horizontal_percentage'],
+ array('self', 'calculateNewBackgroundPosition'),
+ $css
+ );
+ if ($replaced !== null) {
+ // preg_replace_callback() sometimes returns null
$css = $replaced;
}
- $replaced = preg_replace_callback( self::$patterns['bg_horizontal_percentage_x'],
- array( 'self', 'calculateNewBackgroundPosition' ), $css );
- if ( $replaced !== null ) {
+ $replaced = preg_replace_callback(
+ self::$patterns['bg_horizontal_percentage_x'],
+ array('self', 'calculateNewBackgroundPosition'),
+ $css
+ );
+ if ($replaced !== null) {
$css = $replaced;
}
@@ -345,12 +369,22 @@ class CSSJanus {
}
/**
- * Callback for calculateNewBackgroundPosition()
+ * Callback for fixBackgroundPosition()
* @param $matches array
* @return string
*/
- private static function calculateNewBackgroundPosition( $matches ) {
- return $matches[1] . ( 100 - $matches[2] ) . $matches[3];
+ private static function calculateNewBackgroundPosition($matches) {
+ $value = $matches[2];
+ if (substr($value, -1) === '%') {
+ $idx = strpos($value, '.');
+ if ($idx !== false) {
+ $len = strlen($value) - $idx - 2;
+ $value = number_format(100 - $value, $len) . '%';
+ } else {
+ $value = (100 - $value) . '%';
+ }
+ }
+ return $matches[1] . $value;
}
}
@@ -359,8 +393,9 @@ class CSSJanus {
* to protect from being janused.
* @author Roan Kattouw
*/
-class CSSJanus_Tokenizer {
- private $regex, $token;
+class CSSJanusTokenizer {
+ private $regex;
+ private $token;
private $originals;
/**
@@ -368,7 +403,7 @@ class CSSJanus_Tokenizer {
* @param string $regex Regular expression whose matches to replace by a token.
* @param string $token Token
*/
- public function __construct( $regex, $token ) {
+ public function __construct($regex, $token) {
$this->regex = $regex;
$this->token = $token;
$this->originals = array();
@@ -380,15 +415,15 @@ class CSSJanus_Tokenizer {
* @param string $str to tokenize
* @return string Tokenized string
*/
- public function tokenize( $str ) {
- return preg_replace_callback( $this->regex, array( $this, 'tokenizeCallback' ), $str );
+ public function tokenize($str) {
+ return preg_replace_callback($this->regex, array($this, 'tokenizeCallback'), $str);
}
/**
* @param $matches array
* @return string
*/
- private function tokenizeCallback( $matches ) {
+ private function tokenizeCallback($matches) {
$this->originals[] = $matches[0];
return $this->token;
}
@@ -399,21 +434,24 @@ class CSSJanus_Tokenizer {
* @param string $str previously run through tokenize()
* @return string Original string
*/
- public function detokenize( $str ) {
+ public function detokenize($str) {
// PHP has no function to replace only the first occurrence or to
// replace occurrences of the same string with different values,
// so we use preg_replace_callback() even though we don't really need a regex
- return preg_replace_callback( '/' . preg_quote( $this->token, '/' ) . '/',
- array( $this, 'detokenizeCallback' ), $str );
+ return preg_replace_callback(
+ '/' . preg_quote($this->token, '/') . '/',
+ array($this, 'detokenizeCallback'),
+ $str
+ );
}
/**
* @param $matches
* @return mixed
*/
- private function detokenizeCallback( $matches ) {
- $retval = current( $this->originals );
- next( $this->originals );
+ private function detokenizeCallback($matches) {
+ $retval = current($this->originals);
+ next($this->originals);
return $retval;
}
diff --git a/includes/libs/CSSMin.php b/includes/libs/CSSMin.php
index 4f142fc7..c69e79f5 100644
--- a/includes/libs/CSSMin.php
+++ b/includes/libs/CSSMin.php
@@ -38,11 +38,13 @@ class CSSMin {
* which when base64 encoded will result in a 1/3 increase in size.
*/
const EMBED_SIZE_LIMIT = 24576;
- const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*)(?P<query>\??[^\)\'"]*)[\'"]?\s*\)';
+ const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*?)(?P<query>\?[^\)\'"]*?|)[\'"]?\s*\)';
+ const EMBED_REGEX = '\/\*\s*\@embed\s*\*\/';
+ const COMMENT_REGEX = '\/\*.*?\*\/';
/* Protected Static Members */
- /** @var array List of common image files extensions and mime-types */
+ /** @var array List of common image files extensions and MIME-types */
protected static $mimeTypes = array(
'gif' => 'image/gif',
'jpe' => 'image/jpeg',
@@ -52,6 +54,7 @@ class CSSMin {
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'xbm' => 'image/x-xbitmap',
+ 'svg' => 'image/svg+xml',
);
/* Static Methods */
@@ -59,23 +62,38 @@ class CSSMin {
/**
* Gets a list of local file paths which are referenced in a CSS style sheet
*
+ * This function will always return an empty array if the second parameter is not given or null
+ * for backwards-compatibility.
+ *
* @param string $source CSS data to remap
* @param string $path File path where the source was read from (optional)
* @return array List of local file references
*/
public static function getLocalFileReferences( $source, $path = null ) {
+ if ( $path === null ) {
+ return array();
+ }
+
+ $path = rtrim( $path, '/' ) . '/';
$files = array();
+
$rFlags = PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
if ( preg_match_all( '/' . self::URL_REGEX . '/', $source, $matches, $rFlags ) ) {
foreach ( $matches as $match ) {
- $file = ( isset( $path )
- ? rtrim( $path, '/' ) . '/'
- : '' ) . "{$match['file'][0]}";
+ $url = $match['file'][0];
- // Only proceed if we can access the file
- if ( !is_null( $path ) && file_exists( $file ) ) {
- $files[] = $file;
+ // Skip fully-qualified and protocol-relative URLs and data URIs
+ if ( substr( $url, 0, 2 ) === '//' || parse_url( $url, PHP_URL_SCHEME ) ) {
+ break;
}
+
+ $file = $path . $url;
+ // Skip non-existent files
+ if ( file_exists( $file ) ) {
+ break;
+ }
+
+ $files[] = $file;
}
}
return $files;
@@ -95,7 +113,9 @@ class CSSMin {
* instead. If $sizeLimit is false, no limit is enforced.
* @return string|bool: Image contents encoded as a data URI or false.
*/
- public static function encodeImageAsDataURI( $file, $type = null, $sizeLimit = self::EMBED_SIZE_LIMIT ) {
+ public static function encodeImageAsDataURI( $file, $type = null,
+ $sizeLimit = self::EMBED_SIZE_LIMIT
+ ) {
if ( $sizeLimit !== false && filesize( $file ) >= $sizeLimit ) {
return false;
}
@@ -115,124 +135,214 @@ class CSSMin {
*/
public static function getMimeType( $file ) {
$realpath = realpath( $file );
- // Try a couple of different ways to get the mime-type of a file, in order of
- // preference
if (
$realpath
&& function_exists( 'finfo_file' )
&& function_exists( 'finfo_open' )
&& defined( 'FILEINFO_MIME_TYPE' )
) {
- // As of PHP 5.3, this is how you get the mime-type of a file; it uses the Fileinfo
- // PECL extension
return finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $realpath );
- } elseif ( function_exists( 'mime_content_type' ) ) {
- // Before this was deprecated in PHP 5.3, this was how you got the mime-type of a file
- return mime_content_type( $file );
- } else {
- // Worst-case scenario has happened, use the file extension to infer the mime-type
- $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
- if ( isset( self::$mimeTypes[$ext] ) ) {
- return self::$mimeTypes[$ext];
- }
}
+
+ // Infer the MIME-type from the file extension
+ $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
+ if ( isset( self::$mimeTypes[$ext] ) ) {
+ return self::$mimeTypes[$ext];
+ }
+
return false;
}
/**
- * Remaps CSS URL paths and automatically embeds data URIs for URL rules
- * preceded by an /* @embed * / comment
+ * Build a CSS 'url()' value for the given URL, quoting parentheses (and other funny characters)
+ * and escaping quotes as necessary.
+ *
+ * See http://www.w3.org/TR/css-syntax-3/#consume-a-url-token
+ *
+ * @param string $url URL to process
+ * @return string 'url()' value, usually just `"url($url)"`, quoted/escaped if necessary
+ */
+ public static function buildUrlValue( $url ) {
+ // The list below has been crafted to match URLs such as:
+ // scheme://user@domain:port/~user/fi%20le.png?query=yes&really=y+s
+ // data:image/png;base64,R0lGODlh/+==
+ if ( preg_match( '!^[\w\d:@/~.%+;,?&=-]+$!', $url ) ) {
+ return "url($url)";
+ } else {
+ return 'url("' . strtr( $url, array( '\\' => '\\\\', '"' => '\\"' ) ) . '")';
+ }
+ }
+
+ /**
+ * Remaps CSS URL paths and automatically embeds data URIs for CSS rules
+ * or url() values preceded by an / * @embed * / comment.
*
* @param string $source CSS data to remap
* @param string $local File path where the source was read from
* @param string $remote URL path to the file
- * @param bool $embedData If false, never do any data URI embedding, even if / * @embed * / is found
+ * @param bool $embedData If false, never do any data URI embedding,
+ * even if / * @embed * / is found.
* @return string Remapped CSS data
*/
public static function remap( $source, $local, $remote, $embedData = true ) {
- $pattern = '/((?P<embed>\s*\/\*\s*\@embed\s*\*\/)(?P<pre>[^\;\}]*))?' .
- self::URL_REGEX . '(?P<post>[^;]*)[\;]?/';
- $offset = 0;
- while ( preg_match( $pattern, $source, $match, PREG_OFFSET_CAPTURE, $offset ) ) {
- // Skip fully-qualified URLs and data URIs
- $urlScheme = parse_url( $match['file'][0], PHP_URL_SCHEME );
- if ( $urlScheme ) {
- // Move the offset to the end of the match, leaving it alone
- $offset = $match[0][1] + strlen( $match[0][0] );
- continue;
- }
- // URLs with absolute paths like /w/index.php need to be expanded
- // to absolute URLs but otherwise left alone
- if ( $match['file'][0] !== '' && $match['file'][0][0] === '/' ) {
- // Replace the file path with an expanded (possibly protocol-relative) URL
- // ...but only if wfExpandUrl() is even available.
- // This will not be the case if we're running outside of MW
- $lengthIncrease = 0;
- if ( function_exists( 'wfExpandUrl' ) ) {
- $expanded = wfExpandUrl( $match['file'][0], PROTO_RELATIVE );
- $origLength = strlen( $match['file'][0] );
- $lengthIncrease = strlen( $expanded ) - $origLength;
- $source = substr_replace( $source, $expanded,
- $match['file'][1], $origLength
+ // High-level overview:
+ // * For each CSS rule in $source that includes at least one url() value:
+ // * Check for an @embed comment at the start indicating that all URIs should be embedded
+ // * For each url() value:
+ // * Check for an @embed comment directly preceding the value
+ // * If either @embed comment exists:
+ // * Embedding the URL as data: URI, if it's possible / allowed
+ // * Otherwise remap the URL to work in generated stylesheets
+
+ // Guard against trailing slashes, because "some/remote/../foo.png"
+ // resolves to "some/remote/foo.png" on (some?) clients (bug 27052).
+ if ( substr( $remote, -1 ) == '/' ) {
+ $remote = substr( $remote, 0, -1 );
+ }
+
+ // Replace all comments by a placeholder so they will not interfere with the remapping.
+ // Warning: This will also catch on anything looking like the start of a comment between
+ // quotation marks (e.g. "foo /* bar").
+ $comments = array();
+ $placeholder = uniqid( '', true );
+
+ $pattern = '/(?!' . CSSMin::EMBED_REGEX . ')(' . CSSMin::COMMENT_REGEX . ')/s';
+
+ $source = preg_replace_callback(
+ $pattern,
+ function ( $match ) use ( &$comments, $placeholder ) {
+ $comments[] = $match[ 0 ];
+ return $placeholder . ( count( $comments ) - 1 ) . 'x';
+ },
+ $source
+ );
+
+ // Note: This will not correctly handle cases where ';', '{' or '}'
+ // appears in the rule itself, e.g. in a quoted string. You are advised
+ // not to use such characters in file names. We also match start/end of
+ // the string to be consistent in edge-cases ('@import url(…)').
+ $pattern = '/(?:^|[;{])\K[^;{}]*' . CSSMin::URL_REGEX . '[^;}]*(?=[;}]|$)/';
+
+ $source = preg_replace_callback(
+ $pattern,
+ function ( $matchOuter ) use ( $local, $remote, $embedData, $placeholder ) {
+ $rule = $matchOuter[0];
+
+ // Check for global @embed comment and remove it. Allow other comments to be present
+ // before @embed (they have been replaced with placeholders at this point).
+ $embedAll = false;
+ $rule = preg_replace( '/^((?:\s+|' . $placeholder . '(\d+)x)*)' . CSSMin::EMBED_REGEX . '\s*/', '$1', $rule, 1, $embedAll );
+
+ // Build two versions of current rule: with remapped URLs
+ // and with embedded data: URIs (where possible).
+ $pattern = '/(?P<embed>' . CSSMin::EMBED_REGEX . '\s*|)' . CSSMin::URL_REGEX . '/';
+
+ $ruleWithRemapped = preg_replace_callback(
+ $pattern,
+ function ( $match ) use ( $local, $remote ) {
+ $remapped = CSSMin::remapOne( $match['file'], $match['query'], $local, $remote, false );
+
+ return CSSMin::buildUrlValue( $remapped );
+ },
+ $rule
+ );
+
+ if ( $embedData ) {
+ $ruleWithEmbedded = preg_replace_callback(
+ $pattern,
+ function ( $match ) use ( $embedAll, $local, $remote ) {
+ $embed = $embedAll || $match['embed'];
+ $embedded = CSSMin::remapOne(
+ $match['file'],
+ $match['query'],
+ $local,
+ $remote,
+ $embed
+ );
+
+ return CSSMin::buildUrlValue( $embedded );
+ },
+ $rule
);
}
- // Move the offset to the end of the match, leaving it alone
- $offset = $match[0][1] + strlen( $match[0][0] ) + $lengthIncrease;
- continue;
- }
- // Guard against double slashes, because "some/remote/../foo.png"
- // resolves to "some/remote/foo.png" on (some?) clients (bug 27052).
- if ( substr( $remote, -1 ) == '/' ) {
- $remote = substr( $remote, 0, -1 );
- }
+ if ( $embedData && $ruleWithEmbedded !== $ruleWithRemapped ) {
+ // Build 2 CSS properties; one which uses a base64 encoded data URI in place
+ // of the @embed comment to try and retain line-number integrity, and the
+ // other with a remapped an versioned URL and an Internet Explorer hack
+ // making it ignored in all browsers that support data URIs
+ return "$ruleWithEmbedded;$ruleWithRemapped!ie";
+ } else {
+ // No reason to repeat twice
+ return $ruleWithRemapped;
+ }
+ }, $source );
- // Shortcuts
- $embed = $match['embed'][0];
- $pre = $match['pre'][0];
- $post = $match['post'][0];
- $query = $match['query'][0];
- $url = "{$remote}/{$match['file'][0]}";
- $file = "{$local}/{$match['file'][0]}";
+ // Re-insert comments
+ $pattern = '/' . $placeholder . '(\d+)x/';
+ $source = preg_replace_callback( $pattern, function( $match ) use ( &$comments ) {
+ return $comments[ $match[1] ];
+ }, $source );
- $replacement = false;
+ return $source;
- if ( $local !== false && file_exists( $file ) ) {
+ }
+
+ /**
+ * Remap or embed a CSS URL path.
+ *
+ * @param string $file URL to remap/embed
+ * @param string $query
+ * @param string $local File path where the source was read from
+ * @param string $remote URL path to the file
+ * @param bool $embed Whether to do any data URI embedding
+ * @return string Remapped/embedded URL data
+ */
+ public static function remapOne( $file, $query, $local, $remote, $embed ) {
+ // The full URL possibly with query, as passed to the 'url()' value in CSS
+ $url = $file . $query;
+
+ // Skip fully-qualified and protocol-relative URLs and data URIs
+ if ( substr( $url, 0, 2 ) === '//' || parse_url( $url, PHP_URL_SCHEME ) ) {
+ return $url;
+ }
+
+ // URLs with absolute paths like /w/index.php need to be expanded
+ // to absolute URLs but otherwise left alone
+ if ( $url !== '' && $url[0] === '/' ) {
+ // Replace the file path with an expanded (possibly protocol-relative) URL
+ // ...but only if wfExpandUrl() is even available.
+ // This will not be the case if we're running outside of MW
+ if ( function_exists( 'wfExpandUrl' ) ) {
+ return wfExpandUrl( $url, PROTO_RELATIVE );
+ } else {
+ return $url;
+ }
+ }
+
+ if ( $local === false ) {
+ // Assume that all paths are relative to $remote, and make them absolute
+ return $remote . '/' . $url;
+ } else {
+ // We drop the query part here and instead make the path relative to $remote
+ $url = "{$remote}/{$file}";
+ // Path to the actual file on the filesystem
+ $localFile = "{$local}/{$file}";
+ if ( file_exists( $localFile ) ) {
// Add version parameter as a time-stamp in ISO 8601 format,
// using Z for the timezone, meaning GMT
- $url .= '?' . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) );
- // Embedding requires a bit of extra processing, so let's skip that if we can
- if ( $embedData && $embed && $match['embed'][1] > 0 ) {
- $data = self::encodeImageAsDataURI( $file );
+ $url .= '?' . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $localFile ), -2 ) );
+ if ( $embed ) {
+ $data = self::encodeImageAsDataURI( $localFile );
if ( $data !== false ) {
- // Build 2 CSS properties; one which uses a base64 encoded data URI in place
- // of the @embed comment to try and retain line-number integrity, and the
- // other with a remapped an versioned URL and an Internet Explorer hack
- // making it ignored in all browsers that support data URIs
- $replacement = "{$pre}url({$data}){$post};{$pre}url({$url}){$post}!ie;";
+ return $data;
}
}
- if ( $replacement === false ) {
- // Assume that all paths are relative to $remote, and make them absolute
- $replacement = "{$embed}{$pre}url({$url}){$post};";
- }
- } elseif ( $local === false ) {
- // Assume that all paths are relative to $remote, and make them absolute
- $replacement = "{$embed}{$pre}url({$url}{$query}){$post};";
}
- if ( $replacement !== false ) {
- // Perform replacement on the source
- $source = substr_replace(
- $source, $replacement, $match[0][1], strlen( $match[0][0] )
- );
- // Move the offset to the end of the replacement in the source
- $offset = $match[0][1] + strlen( $replacement );
- continue;
- }
- // Move the offset to the end of the match, leaving it alone
- $offset = $match[0][1] + strlen( $match[0][0] );
+ // If any of these conditions failed (file missing, we don't want to embed it
+ // or it's not embeddable), return the URL (possibly with ?timestamp part)
+ return $url;
}
- return $source;
}
/**
diff --git a/includes/libs/GenericArrayObject.php b/includes/libs/GenericArrayObject.php
index d77d8ad6..db8a7ecf 100644
--- a/includes/libs/GenericArrayObject.php
+++ b/includes/libs/GenericArrayObject.php
@@ -33,7 +33,6 @@
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
abstract class GenericArrayObject extends ArrayObject {
-
/**
* Returns the name of an interface/class that the element should implement/extend.
*
@@ -144,7 +143,8 @@ abstract class GenericArrayObject extends ArrayObject {
protected function setElement( $index, $value ) {
if ( !$this->hasValidType( $value ) ) {
throw new InvalidArgumentException(
- 'Can only add ' . $this->getObjectType() . ' implementing objects to ' . get_called_class() . '.'
+ 'Can only add ' . $this->getObjectType() . ' implementing objects to '
+ . get_called_class() . '.'
);
}
@@ -237,5 +237,4 @@ abstract class GenericArrayObject extends ArrayObject {
public function isEmpty() {
return $this->count() === 0;
}
-
}
diff --git a/includes/HashRing.php b/includes/libs/HashRing.php
index 930f8c0a..2022b225 100644
--- a/includes/HashRing.php
+++ b/includes/libs/HashRing.php
@@ -32,15 +32,24 @@ class HashRing {
/** @var Array (location => (start, end)) */
protected $ring = array();
+ /** @var Array (location => (start, end)) */
+ protected $liveRing;
+ /** @var Array (location => UNIX timestamp) */
+ protected $ejectionExpiries = array();
+ /** @var integer UNIX timestamp */
+ protected $ejectionNextExpiry = INF;
+
const RING_SIZE = 268435456; // 2^28
/**
* @param array $map (location => weight)
*/
public function __construct( array $map ) {
- $map = array_filter( $map, function( $w ) { return $w > 0; } );
+ $map = array_filter( $map, function ( $w ) {
+ return $w > 0;
+ } );
if ( !count( $map ) ) {
- throw new MWException( "Ring is empty or all weights are zero." );
+ throw new UnexpectedValueException( "Ring is empty or all weights are zero." );
}
$this->sourceMap = $map;
// Sort the locations based on the hash of their names
@@ -77,11 +86,12 @@ class HashRing {
*/
public function getLocation( $item ) {
$locations = $this->getLocations( $item, 1 );
+
return $locations[0];
}
/**
- * Get the location of an item on the ring, as well as the next clockwise locations
+ * Get the location of an item on the ring, as well as the next locations
*
* @param string $item
* @param integer $limit Maximum number of locations to return
@@ -113,6 +123,7 @@ class HashRing {
}
$locations[] = $location;
}
+
return $locations;
}
@@ -134,9 +145,95 @@ class HashRing {
public function newWithoutLocation( $location ) {
$map = $this->sourceMap;
unset( $map[$location] );
- if ( count( $map ) ) {
- return new self( $map );
+
+ return count( $map ) ? new self( $map ) : false;
+ }
+
+ /**
+ * Remove a location from the "live" hash ring
+ *
+ * @param string $location
+ * @param integer $ttl Seconds
+ * @return bool Whether some non-ejected locations are left
+ */
+ public function ejectFromLiveRing( $location, $ttl ) {
+ if ( !isset( $this->sourceMap[$location] ) ) {
+ throw new UnexpectedValueException( "No location '$location' in the ring." );
+ }
+ $expiry = time() + $ttl;
+ $this->liveRing = null; // stale
+ $this->ejectionExpiries[$location] = $expiry;
+ $this->ejectionNextExpiry = min( $expiry, $this->ejectionNextExpiry );
+
+ return ( count( $this->ejectionExpiries ) < count( $this->sourceMap ) );
+ }
+
+ /**
+ * Get the "live" hash ring (which does not include ejected locations)
+ *
+ * @return HashRing
+ * @throws UnexpectedValueException
+ */
+ public function getLiveRing() {
+ $now = time();
+ if ( $this->liveRing === null || $this->ejectionNextExpiry <= $now ) {
+ $this->ejectionExpiries = array_filter(
+ $this->ejectionExpiries,
+ function( $expiry ) use ( $now ) {
+ return ( $expiry > $now );
+ }
+ );
+ if ( count( $this->ejectionExpiries ) ) {
+ $map = array_diff_key( $this->sourceMap, $this->ejectionExpiries );
+ $this->liveRing = count( $map ) ? new self( $map ) : false;
+
+ $this->ejectionNextExpiry = min( $this->ejectionExpiries );
+ } else { // common case; avoid recalculating ring
+ $this->liveRing = clone $this;
+ $this->liveRing->ejectionExpiries = array();
+ $this->liveRing->ejectionNextExpiry = INF;
+ $this->liveRing->liveRing = null;
+
+ $this->ejectionNextExpiry = INF;
+ }
}
- return false;
+ if ( !$this->liveRing ) {
+ throw new UnexpectedValueException( "The live ring is currently empty." );
+ }
+
+ return $this->liveRing;
+ }
+
+ /**
+ * Get the location of an item on the "live" ring
+ *
+ * @param string $item
+ * @return string Location
+ * @throws UnexpectedValueException
+ */
+ public function getLiveLocation( $item ) {
+ return $this->getLiveRing()->getLocation( $item );
+ }
+
+ /**
+ * Get the location of an item on the "live" ring, as well as the next locations
+ *
+ * @param string $item
+ * @param integer $limit Maximum number of locations to return
+ * @return array List of locations
+ * @throws UnexpectedValueException
+ */
+ public function getLiveLocations( $item ) {
+ return $this->getLiveRing()->getLocations( $item );
+ }
+
+ /**
+ * Get the map of "live" locations to weight (ignores 0-weight items)
+ *
+ * @return array
+ * @throws UnexpectedValueException
+ */
+ public function getLiveLocationWeights() {
+ return $this->getLiveRing()->getLocationWeights();
}
}
diff --git a/includes/libs/HttpStatus.php b/includes/libs/HttpStatus.php
index 4f626b23..809bfdf5 100644
--- a/includes/libs/HttpStatus.php
+++ b/includes/libs/HttpStatus.php
@@ -28,8 +28,6 @@ class HttpStatus {
/**
* Get the message associated with HTTP response code $code
*
- * Replace OutputPage::getStatusMessage( $code )
- *
* @param $code Integer: status code
* @return String or null: message or null if $code is not in the list of
* messages
@@ -75,13 +73,17 @@ class HttpStatus {
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
- 507 => 'Insufficient Storage'
+ 507 => 'Insufficient Storage',
+ 511 => 'Network Authentication Required',
);
return isset( $statusMessage[$code] ) ? $statusMessage[$code] : null;
}
diff --git a/includes/libs/IEContentAnalyzer.php b/includes/libs/IEContentAnalyzer.php
index 7f461a03..c31a3527 100644
--- a/includes/libs/IEContentAnalyzer.php
+++ b/includes/libs/IEContentAnalyzer.php
@@ -333,7 +333,7 @@ class IEContentAnalyzer {
* @param string $chunk the first 256 bytes of the file
* @param string $proposed the MIME type proposed by the server
*
- * @return Array: map of IE version to detected mime type
+ * @return Array: map of IE version to detected MIME type
*/
public function getRealMimesFromData( $fileName, $chunk, $proposed ) {
$types = $this->getMimesFromData( $fileName, $chunk, $proposed );
@@ -371,7 +371,7 @@ class IEContentAnalyzer {
* @param string $chunk the first 256 bytes of the file
* @param string $proposed the MIME type proposed by the server
*
- * @return Array: map of IE version to detected mime type
+ * @return Array: map of IE version to detected MIME type
*/
public function getMimesFromData( $fileName, $chunk, $proposed ) {
$types = array();
@@ -712,8 +712,9 @@ class IEContentAnalyzer {
$xbmMagic2 = '_width';
$xbmMagic3 = '_bits';
$binhexMagic = 'converted with BinHex';
+ $chunkLength = strlen( $chunk );
- for ( $offset = 0; $offset < strlen( $chunk ); $offset++ ) {
+ for ( $offset = 0; $offset < $chunkLength; $offset++ ) {
$curChar = $chunk[$offset];
if ( $curChar == "\x0a" ) {
$counters['lf']++;
diff --git a/includes/libs/IPSet.php b/includes/libs/IPSet.php
new file mode 100644
index 00000000..ae593785
--- /dev/null
+++ b/includes/libs/IPSet.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * @section LICENSE
+ * 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 Brandon Black <blblack@gmail.com>
+ */
+
+/**
+ * Matches IP addresses against a set of CIDR specifications
+ *
+ * Usage:
+ * // At startup, calculate the optimized data structure for the set:
+ * $ipset = new IPSet( $wgSquidServersNoPurge );
+ * // runtime check against cached set (returns bool):
+ * $allowme = $ipset->match( $ip );
+ *
+ * In rough benchmarking, this takes about 80% more time than
+ * in_array() checks on a short (a couple hundred at most) array
+ * of addresses. It's fast either way at those levels, though,
+ * and IPSet would scale better than in_array if the array were
+ * much larger.
+ *
+ * For mixed-family CIDR sets, however, this code gives well over
+ * 100x speedup vs iterating IP::isInRange() over an array
+ * of CIDR specs.
+ *
+ * The basic implementation is two separate binary trees
+ * (IPv4 and IPv6) as nested php arrays with keys named 0 and 1.
+ * The values false and true are terminal match-fail and match-success,
+ * otherwise the value is a deeper node in the tree.
+ *
+ * A simple depth-compression scheme is also implemented: whole-byte
+ * tree compression at whole-byte boundaries only, where no branching
+ * occurs during that whole byte of depth. A compressed node has
+ * keys 'comp' (the byte to compare) and 'next' (the next node to
+ * recurse into if 'comp' matched successfully).
+ *
+ * For example, given these inputs:
+ * 25.0.0.0/9
+ * 25.192.0.0/10
+ *
+ * The v4 tree would look like:
+ * root4 => array(
+ * 'comp' => 25,
+ * 'next' => array(
+ * 0 => true,
+ * 1 => array(
+ * 0 => false,
+ * 1 => true,
+ * ),
+ * ),
+ * );
+ *
+ * (multi-byte compression nodes were attempted as well, but were
+ * a net loss in my test scenarios due to additional match complexity)
+ *
+ * @since 1.24
+ */
+class IPSet {
+ /** @var array $root4: the root of the IPv4 matching tree */
+ private $root4 = array( false, false );
+
+ /** @var array $root6: the root of the IPv6 matching tree */
+ private $root6 = array( false, false );
+
+ /**
+ * __construct() instantiate the object from an array of CIDR specs
+ *
+ * @param array $cfg array of IPv[46] CIDR specs as strings
+ * @return IPSet new IPSet object
+ *
+ * Invalid input network/mask values in $cfg will result in issuing
+ * E_WARNING and/or E_USER_WARNING and the bad values being ignored.
+ */
+ public function __construct( array $cfg ) {
+ foreach ( $cfg as $cidr ) {
+ $this->addCidr( $cidr );
+ }
+
+ self::recOptimize( $this->root4 );
+ self::recCompress( $this->root4, 0, 24 );
+ self::recOptimize( $this->root6 );
+ self::recCompress( $this->root6, 0, 120 );
+ }
+
+ /**
+ * Add a single CIDR spec to the internal matching trees
+ *
+ * @param string $cidr string CIDR spec, IPv[46], optional /mask (def all-1's)
+ */
+ private function addCidr( $cidr ) {
+ // v4 or v6 check
+ if ( strpos( $cidr, ':' ) === false ) {
+ $node =& $this->root4;
+ $defMask = '32';
+ } else {
+ $node =& $this->root6;
+ $defMask = '128';
+ }
+
+ // Default to all-1's mask if no netmask in the input
+ if ( strpos( $cidr, '/' ) === false ) {
+ $net = $cidr;
+ $mask = $defMask;
+ } else {
+ list( $net, $mask ) = explode( '/', $cidr, 2 );
+ if ( !ctype_digit( $mask ) || intval( $mask ) > $defMask ) {
+ trigger_error( "IPSet: Bad mask '$mask' from '$cidr', ignored", E_USER_WARNING );
+ return;
+ }
+ }
+ $mask = intval( $mask ); // explicit integer convert, checked above
+
+ // convert $net to an array of integer bytes, length 4 or 16:
+ $raw = inet_pton( $net );
+ if ( $raw === false ) {
+ return; // inet_pton() sends an E_WARNING for us
+ }
+ $rawOrd = array_map( 'ord', str_split( $raw ) );
+
+ // special-case: zero mask overwrites the whole tree with a pair of terminal successes
+ if ( $mask == 0 ) {
+ $node = array( true, true );
+ return;
+ }
+
+ // iterate the bits of the address while walking the tree structure for inserts
+ $curBit = 0;
+ while ( 1 ) {
+ $maskShift = 7 - ( $curBit & 7 );
+ $node =& $node[( $rawOrd[$curBit >> 3] & ( 1 << $maskShift ) ) >> $maskShift];
+ ++$curBit;
+ if ( $node === true ) {
+ // already added a larger supernet, no need to go deeper
+ return;
+ } elseif ( $curBit == $mask ) {
+ // this may wipe out deeper subnets from earlier
+ $node = true;
+ return;
+ } elseif ( $node === false ) {
+ // create new subarray to go deeper
+ $node = array( false, false );
+ }
+ }
+ }
+
+ /**
+ * Match an IP address against the set
+ *
+ * @param string $ip string IPv[46] address
+ * @return boolean true is match success, false is match failure
+ *
+ * If $ip is unparseable, inet_pton may issue an E_WARNING to that effect
+ */
+ public function match( $ip ) {
+ $raw = inet_pton( $ip );
+ if ( $raw === false ) {
+ return false; // inet_pton() sends an E_WARNING for us
+ }
+
+ $rawOrd = array_map( 'ord', str_split( $raw ) );
+ if ( count( $rawOrd ) == 4 ) {
+ $node =& $this->root4;
+ } else {
+ $node =& $this->root6;
+ }
+
+ $curBit = 0;
+ while ( 1 ) {
+ if ( isset( $node['comp'] ) ) {
+ // compressed node, matches 1 whole byte on a byte boundary
+ if ( $rawOrd[$curBit >> 3] != $node['comp'] ) {
+ return false;
+ }
+ $curBit += 8;
+ $node =& $node['next'];
+ } else {
+ // uncompressed node, walk in the correct direction for the current bit-value
+ $maskShift = 7 - ( $curBit & 7 );
+ $node =& $node[( $rawOrd[$curBit >> 3] & ( 1 << $maskShift ) ) >> $maskShift];
+ ++$curBit;
+ }
+
+ if ( $node === true || $node === false ) {
+ return $node;
+ }
+ }
+ }
+
+ /**
+ * Recursively merges adjacent nets into larger supernets
+ *
+ * @param array &$node Tree node to optimize, by-reference
+ *
+ * e.g.: 8.0.0.0/8 + 9.0.0.0/8 -> 8.0.0.0/7
+ */
+ private static function recOptimize( &$node ) {
+ if ( $node[0] !== false && $node[0] !== true && self::recOptimize( $node[0] ) ) {
+ $node[0] = true;
+ }
+ if ( $node[1] !== false && $node[1] !== true && self::recOptimize( $node[1] ) ) {
+ $node[1] = true;
+ }
+ if ( $node[0] === true && $node[1] === true ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Recursively compresses a tree
+ *
+ * @param array &$node Tree node to compress, by-reference
+ * @param integer $curBit current depth in the tree
+ * @param integer $maxCompStart maximum depth at which compression can start, family-specific
+ *
+ * This is a very simplistic compression scheme: if we go through a whole
+ * byte of address starting at a byte boundary with no real branching
+ * other than immediate false-vs-(node|true), compress that subtree down to a single
+ * byte-matching node.
+ * The $maxCompStart check elides recursing the final 7 levels of depth (family-dependent)
+ */
+ private static function recCompress( &$node, $curBit, $maxCompStart ) {
+ if ( !( $curBit & 7 ) ) { // byte boundary, check for depth-8 single path(s)
+ $byte = 0;
+ $cnode =& $node;
+ $i = 8;
+ while ( $i-- ) {
+ if ( $cnode[0] === false ) {
+ $byte |= 1 << $i;
+ $cnode =& $cnode[1];
+ } elseif ( $cnode[1] === false ) {
+ $cnode =& $cnode[0];
+ } else {
+ // partial-byte branching, give up
+ break;
+ }
+ }
+ if ( $i == -1 ) { // means we did not exit the while() via break
+ $node = array(
+ 'comp' => $byte,
+ 'next' => &$cnode,
+ );
+ $curBit += 8;
+ if ( $cnode !== true ) {
+ self::recCompress( $cnode, $curBit, $maxCompStart );
+ }
+ return;
+ }
+ }
+
+ ++$curBit;
+ if ( $curBit <= $maxCompStart ) {
+ if ( $node[0] !== false && $node[0] !== true ) {
+ self::recCompress( $node[0], $curBit, $maxCompStart );
+ }
+ if ( $node[1] !== false && $node[1] !== true ) {
+ self::recCompress( $node[1], $curBit, $maxCompStart );
+ }
+ }
+ }
+}
diff --git a/includes/libs/JavaScriptMinifier.php b/includes/libs/JavaScriptMinifier.php
index 998805ae..2990782c 100644
--- a/includes/libs/JavaScriptMinifier.php
+++ b/includes/libs/JavaScriptMinifier.php
@@ -1,4 +1,5 @@
<?php
+// @codingStandardsIgnoreFile File external to MediaWiki. Ignore coding conventions checks.
/**
* JavaScript Minifier
*
diff --git a/includes/libs/MWMessagePack.php b/includes/libs/MWMessagePack.php
new file mode 100644
index 00000000..cd9aad8f
--- /dev/null
+++ b/includes/libs/MWMessagePack.php
@@ -0,0 +1,189 @@
+<?php
+/**
+ * MessagePack serializer
+ *
+ * MessagePack is a space-efficient binary data interchange format. This
+ * class provides a pack() method that encodes native PHP values as MessagePack
+ * binary strings. The implementation is derived from msgpack-php.
+ *
+ * Copyright (c) 2013 Ori Livneh <ori@wikimedia.org>
+ * Copyright (c) 2011 OnlineCity <https://github.com/onlinecity/msgpack-php>.
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @see <http://msgpack.org/>
+ * @see <http://wiki.msgpack.org/display/MSGPACK/Format+specification>
+ *
+ * @since 1.23
+ * @file
+ */
+class MWMessagePack {
+ /** @var boolean|null Whether current system is bigendian. **/
+ public static $bigendian = null;
+
+ /**
+ * Encode a value using MessagePack
+ *
+ * This method supports null, boolean, integer, float, string and array
+ * (both indexed and associative) types. Object serialization is not
+ * supported.
+ *
+ * @param mixed $value
+ * @return string
+ * @throws InvalidArgumentException if $value is an unsupported type or too long a string
+ */
+ public static function pack( $value ) {
+ if ( self::$bigendian === null ) {
+ self::$bigendian = pack( 'S', 1 ) === pack( 'n', 1 );
+ }
+
+ switch ( gettype( $value ) ) {
+ case 'NULL':
+ return "\xC0";
+
+ case 'boolean':
+ return $value ? "\xC3" : "\xC2";
+
+ case 'double':
+ case 'float':
+ return self::$bigendian
+ ? "\xCB" . pack( 'd', $value )
+ : "\xCB" . strrev( pack( 'd', $value ) );
+
+ case 'string':
+ $length = strlen( $value );
+ if ( $length < 32 ) {
+ return pack( 'Ca*', 0xA0 | $length, $value );
+ } elseif ( $length <= 0xFFFF ) {
+ return pack( 'Cna*', 0xDA, $length, $value );
+ } elseif ( $length <= 0xFFFFFFFF ) {
+ return pack( 'CNa*', 0xDB, $length, $value );
+ }
+ throw new InvalidArgumentException( __METHOD__
+ . ": string too long (length: $length; max: 4294967295)" );
+
+ case 'integer':
+ if ( $value >= 0 ) {
+ if ( $value <= 0x7F ) {
+ // positive fixnum
+ return chr( $value );
+ }
+ if ( $value <= 0xFF ) {
+ // uint8
+ return pack( 'CC', 0xCC, $value );
+ }
+ if ( $value <= 0xFFFF ) {
+ // uint16
+ return pack( 'Cn', 0xCD, $value );
+ }
+ if ( $value <= 0xFFFFFFFF ) {
+ // uint32
+ return pack( 'CN', 0xCE, $value );
+ }
+ if ( $value <= 0xFFFFFFFFFFFFFFFF ) {
+ // uint64
+ $hi = ( $value & 0xFFFFFFFF00000000 ) >> 32;
+ $lo = $value & 0xFFFFFFFF;
+ return self::$bigendian
+ ? pack( 'CNN', 0xCF, $lo, $hi )
+ : pack( 'CNN', 0xCF, $hi, $lo );
+ }
+ } else {
+ if ( $value >= -32 ) {
+ // negative fixnum
+ return pack( 'c', $value );
+ }
+ if ( $value >= -0x80 ) {
+ // int8
+ return pack( 'Cc', 0xD0, $value );
+ }
+ if ( $value >= -0x8000 ) {
+ // int16
+ $p = pack( 's', $value );
+ return self::$bigendian
+ ? pack( 'Ca2', 0xD1, $p )
+ : pack( 'Ca2', 0xD1, strrev( $p ) );
+ }
+ if ( $value >= -0x80000000 ) {
+ // int32
+ $p = pack( 'l', $value );
+ return self::$bigendian
+ ? pack( 'Ca4', 0xD2, $p )
+ : pack( 'Ca4', 0xD2, strrev( $p ) );
+ }
+ if ( $value >= -0x8000000000000000 ) {
+ // int64
+ // pack() does not support 64-bit ints either so pack into two 32-bits
+ $p1 = pack( 'l', $value & 0xFFFFFFFF );
+ $p2 = pack( 'l', ( $value >> 32 ) & 0xFFFFFFFF );
+ return self::$bigendian
+ ? pack( 'Ca4a4', 0xD3, $p1, $p2 )
+ : pack( 'Ca4a4', 0xD3, strrev( $p2 ), strrev( $p1 ) );
+ }
+ }
+ throw new InvalidArgumentException( __METHOD__ . ": invalid integer '$value'" );
+
+ case 'array':
+ $buffer = '';
+ $length = count( $value );
+ if ( $length > 0xFFFFFFFF ) {
+ throw new InvalidArgumentException( __METHOD__
+ . ": array too long (length: $length, max: 4294967295)" );
+ }
+
+ $index = 0;
+ foreach ( $value as $k => $v ) {
+ if ( $index !== $k || $index === $length ) {
+ break;
+ } else {
+ $index++;
+ }
+ }
+ $associative = $index !== $length;
+
+ if ( $associative ) {
+ if ( $length < 16 ) {
+ $buffer .= pack( 'C', 0x80 | $length );
+ } elseif ( $length <= 0xFFFF ) {
+ $buffer .= pack( 'Cn', 0xDE, $length );
+ } else {
+ $buffer .= pack( 'CN', 0xDF, $length );
+ }
+ foreach ( $value as $k => $v ) {
+ $buffer .= self::pack( $k );
+ $buffer .= self::pack( $v );
+ }
+ } else {
+ if ( $length < 16 ) {
+ $buffer .= pack( 'C', 0x90 | $length );
+ } elseif ( $length <= 0xFFFF ) {
+ $buffer .= pack( 'Cn', 0xDC, $length );
+ } else {
+ $buffer .= pack( 'CN', 0xDD, $length );
+ }
+ foreach ( $value as $v ) {
+ $buffer .= self::pack( $v );
+ }
+ }
+ return $buffer;
+
+ default:
+ throw new InvalidArgumentException( __METHOD__ . ': unsupported type ' . gettype( $value ) );
+ }
+ }
+}
diff --git a/includes/MappedIterator.php b/includes/libs/MappedIterator.php
index 70d20327..7fdde8a8 100644
--- a/includes/MappedIterator.php
+++ b/includes/libs/MappedIterator.php
@@ -49,7 +49,7 @@ class MappedIterator extends FilterIterator {
* @param Iterator|Array $iter
* @param callable $vCallback Value transformation callback
* @param array $options Options map (includes "accept") (since 1.22)
- * @throws MWException
+ * @throws UnexpectedValueException
*/
public function __construct( $iter, $vCallback, array $options = array() ) {
if ( is_array( $iter ) ) {
@@ -57,7 +57,7 @@ class MappedIterator extends FilterIterator {
} elseif ( $iter instanceof Iterator ) {
$baseIterator = $iter;
} else {
- throw new MWException( "Invalid base iterator provided." );
+ throw new UnexpectedValueException( "Invalid base iterator provided." );
}
parent::__construct( $baseIterator );
$this->vCallback = $vCallback;
@@ -81,16 +81,19 @@ class MappedIterator extends FilterIterator {
if ( $ok ) {
$this->cache['current'] = $value;
}
+
return $ok;
}
public function key() {
$this->init();
+
return parent::key();
}
public function valid() {
$this->init();
+
return parent::valid();
}
diff --git a/includes/libs/MultiHttpClient.php b/includes/libs/MultiHttpClient.php
new file mode 100644
index 00000000..8c982c43
--- /dev/null
+++ b/includes/libs/MultiHttpClient.php
@@ -0,0 +1,389 @@
+<?php
+/**
+ * HTTP service client
+ *
+ * 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 handle concurrent HTTP requests
+ *
+ * HTTP request maps are arrays that use the following format:
+ * - method : GET/HEAD/PUT/POST/DELETE
+ * - url : HTTP/HTTPS URL
+ * - query : <query parameter field/value associative array> (uses RFC 3986)
+ * - headers : <header name/value associative array>
+ * - body : source to get the HTTP request body from;
+ * this can simply be a string (always), a resource for
+ * PUT requests, and a field/value array for POST request;
+ * array bodies are encoded as multipart/form-data and strings
+ * use application/x-www-form-urlencoded (headers sent automatically)
+ * - stream : resource to stream the HTTP response body to
+ * Request maps can use integer index 0 instead of 'method' and 1 instead of 'url'.
+ *
+ * @author Aaron Schulz
+ * @since 1.23
+ */
+class MultiHttpClient {
+ /** @var resource */
+ protected $multiHandle = null; // curl_multi handle
+ /** @var string|null SSL certificates path */
+ protected $caBundlePath;
+ /** @var integer */
+ protected $connTimeout = 10;
+ /** @var integer */
+ protected $reqTimeout = 300;
+ /** @var bool */
+ protected $usePipelining = false;
+ /** @var integer */
+ protected $maxConnsPerHost = 50;
+
+ /**
+ * @param array $options
+ * - connTimeout : default connection timeout
+ * - reqTimeout : default request timeout
+ * - usePipelining : whether to use HTTP pipelining if possible (for all hosts)
+ * - maxConnsPerHost : maximum number of concurrent connections (per host)
+ */
+ public function __construct( array $options ) {
+ if ( isset( $options['caBundlePath'] ) ) {
+ $this->caBundlePath = $options['caBundlePath'];
+ if ( !file_exists( $this->caBundlePath ) ) {
+ throw new Exception( "Cannot find CA bundle: " . $this->caBundlePath );
+ }
+ }
+ static $opts = array( 'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost' );
+ foreach ( $opts as $key ) {
+ if ( isset( $options[$key] ) ) {
+ $this->$key = $options[$key];
+ }
+ }
+ }
+
+ /**
+ * Execute an HTTP(S) request
+ *
+ * This method returns a response map of:
+ * - code : HTTP response code or 0 if there was a serious cURL error
+ * - reason : HTTP response reason (empty if there was a serious cURL error)
+ * - headers : <header name/value associative array>
+ * - body : HTTP response body or resource (if "stream" was set)
+ * - err : Any cURL error string
+ * The map also stores integer-indexed copies of these values. This lets callers do:
+ * <code>
+ * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $http->run( $req );
+ * </code>
+ * @param array $req HTTP request array
+ * @param array $opts
+ * - connTimeout : connection timeout per request
+ * - reqTimeout : post-connection timeout per request
+ * @return array Response array for request
+ */
+ final public function run( array $req, array $opts = array() ) {
+ $req = $this->runMulti( array( $req ), $opts );
+ return $req[0]['response'];
+ }
+
+ /**
+ * Execute a set of HTTP(S) requests concurrently
+ *
+ * The maps are returned by this method with the 'response' field set to a map of:
+ * - code : HTTP response code or 0 if there was a serious cURL error
+ * - reason : HTTP response reason (empty if there was a serious cURL error)
+ * - headers : <header name/value associative array>
+ * - body : HTTP response body or resource (if "stream" was set)
+ * - err : Any cURL error string
+ * The map also stores integer-indexed copies of these values. This lets callers do:
+ * <code>
+ * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response'];
+ * </code>
+ * All headers in the 'headers' field are normalized to use lower case names.
+ * This is true for the request headers and the response headers. Integer-indexed
+ * method/URL entries will also be changed to use the corresponding string keys.
+ *
+ * @param array $reqs Map of HTTP request arrays
+ * @param array $opts
+ * - connTimeout : connection timeout per request
+ * - reqTimeout : post-connection timeout per request
+ * - usePipelining : whether to use HTTP pipelining if possible
+ * - maxConnsPerHost : maximum number of concurrent connections (per host)
+ * @return array $reqs With response array populated for each
+ */
+ public function runMulti( array $reqs, array $opts = array() ) {
+ $chm = $this->getCurlMulti();
+
+ // Normalize $reqs and add all of the required cURL handles...
+ $handles = array();
+ foreach ( $reqs as $index => &$req ) {
+ $req['response'] = array(
+ 'code' => 0,
+ 'reason' => '',
+ 'headers' => array(),
+ 'body' => '',
+ 'error' => ''
+ );
+ if ( isset( $req[0] ) ) {
+ $req['method'] = $req[0]; // short-form
+ unset( $req[0] );
+ }
+ if ( isset( $req[1] ) ) {
+ $req['url'] = $req[1]; // short-form
+ unset( $req[1] );
+ }
+ if ( !isset( $req['method'] ) ) {
+ throw new Exception( "Request has no 'method' field set." );
+ } elseif ( !isset( $req['url'] ) ) {
+ throw new Exception( "Request has no 'url' field set." );
+ }
+ $req['query'] = isset( $req['query'] ) ? $req['query'] : array();
+ $headers = array(); // normalized headers
+ if ( isset( $req['headers'] ) ) {
+ foreach ( $req['headers'] as $name => $value ) {
+ $headers[strtolower( $name )] = $value;
+ }
+ }
+ $req['headers'] = $headers;
+ if ( !isset( $req['body'] ) ) {
+ $req['body'] = '';
+ $req['headers']['content-length'] = 0;
+ }
+ $handles[$index] = $this->getCurlHandle( $req, $opts );
+ if ( count( $reqs ) > 1 ) {
+ // https://github.com/guzzle/guzzle/issues/349
+ curl_setopt( $handles[$index], CURLOPT_FORBID_REUSE, true );
+ }
+ }
+ unset( $req ); // don't assign over this by accident
+
+ $indexes = array_keys( $reqs );
+ if ( function_exists( 'curl_multi_setopt' ) ) { // PHP 5.5
+ if ( isset( $opts['usePipelining'] ) ) {
+ curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$opts['usePipelining'] );
+ }
+ if ( isset( $opts['maxConnsPerHost'] ) ) {
+ // Keep these sockets around as they may be needed later in the request
+ curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$opts['maxConnsPerHost'] );
+ }
+ }
+
+ // @TODO: use a per-host rolling handle window (e.g. CURLMOPT_MAX_HOST_CONNECTIONS)
+ $batches = array_chunk( $indexes, $this->maxConnsPerHost );
+
+ foreach ( $batches as $batch ) {
+ // Attach all cURL handles for this batch
+ foreach ( $batch as $index ) {
+ curl_multi_add_handle( $chm, $handles[$index] );
+ }
+ // Execute the cURL handles concurrently...
+ $active = null; // handles still being processed
+ do {
+ // Do any available work...
+ do {
+ $mrc = curl_multi_exec( $chm, $active );
+ } while ( $mrc == CURLM_CALL_MULTI_PERFORM );
+ // Wait (if possible) for available work...
+ if ( $active > 0 && $mrc == CURLM_OK ) {
+ if ( curl_multi_select( $chm, 10 ) == -1 ) {
+ // PHP bug 63411; http://curl.haxx.se/libcurl/c/curl_multi_fdset.html
+ usleep( 5000 ); // 5ms
+ }
+ }
+ } while ( $active > 0 && $mrc == CURLM_OK );
+ }
+
+ // Remove all of the added cURL handles and check for errors...
+ foreach ( $reqs as $index => &$req ) {
+ $ch = $handles[$index];
+ curl_multi_remove_handle( $chm, $ch );
+ if ( curl_errno( $ch ) !== 0 ) {
+ $req['response']['error'] = "(curl error: " .
+ curl_errno( $ch ) . ") " . curl_error( $ch );
+ }
+ // For convenience with the list() operator
+ $req['response'][0] = $req['response']['code'];
+ $req['response'][1] = $req['response']['reason'];
+ $req['response'][2] = $req['response']['headers'];
+ $req['response'][3] = $req['response']['body'];
+ $req['response'][4] = $req['response']['error'];
+ curl_close( $ch );
+ // Close any string wrapper file handles
+ if ( isset( $req['_closeHandle'] ) ) {
+ fclose( $req['_closeHandle'] );
+ unset( $req['_closeHandle'] );
+ }
+ }
+ unset( $req ); // don't assign over this by accident
+
+ // Restore the default settings
+ if ( function_exists( 'curl_multi_setopt' ) ) { // PHP 5.5
+ curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$this->usePipelining );
+ curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
+ }
+
+ return $reqs;
+ }
+
+ /**
+ * @param array $req HTTP request map
+ * @param array $opts
+ * - connTimeout : default connection timeout
+ * - reqTimeout : default request timeout
+ * @return resource
+ */
+ protected function getCurlHandle( array &$req, array $opts = array() ) {
+ $ch = curl_init();
+
+ curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT,
+ isset( $opts['connTimeout'] ) ? $opts['connTimeout'] : $this->connTimeout );
+ curl_setopt( $ch, CURLOPT_TIMEOUT,
+ isset( $opts['reqTimeout'] ) ? $opts['reqTimeout'] : $this->reqTimeout );
+ curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
+ curl_setopt( $ch, CURLOPT_MAXREDIRS, 4 );
+ curl_setopt( $ch, CURLOPT_HEADER, 0 );
+ if ( !is_null( $this->caBundlePath ) ) {
+ curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
+ curl_setopt( $ch, CURLOPT_CAINFO, $this->caBundlePath );
+ }
+ curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
+
+ $url = $req['url'];
+ // PHP_QUERY_RFC3986 is PHP 5.4+ only
+ $query = str_replace(
+ array( '+', '%7E' ),
+ array( '%20', '~' ),
+ http_build_query( $req['query'], '', '&' )
+ );
+ if ( $query != '' ) {
+ $url .= strpos( $req['url'], '?' ) === false ? "?$query" : "&$query";
+ }
+ curl_setopt( $ch, CURLOPT_URL, $url );
+
+ curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $req['method'] );
+ if ( $req['method'] === 'HEAD' ) {
+ curl_setopt( $ch, CURLOPT_NOBODY, 1 );
+ }
+
+ if ( $req['method'] === 'PUT' ) {
+ curl_setopt( $ch, CURLOPT_PUT, 1 );
+ if ( is_resource( $req['body'] ) ) {
+ curl_setopt( $ch, CURLOPT_INFILE, $req['body'] );
+ if ( isset( $req['headers']['content-length'] ) ) {
+ curl_setopt( $ch, CURLOPT_INFILESIZE, $req['headers']['content-length'] );
+ } elseif ( isset( $req['headers']['transfer-encoding'] ) &&
+ $req['headers']['transfer-encoding'] === 'chunks'
+ ) {
+ curl_setopt( $ch, CURLOPT_UPLOAD, true );
+ } else {
+ throw new Exception( "Missing 'Content-Length' or 'Transfer-Encoding' header." );
+ }
+ } elseif ( $req['body'] !== '' ) {
+ $fp = fopen( "php://temp", "wb+" );
+ fwrite( $fp, $req['body'], strlen( $req['body'] ) );
+ rewind( $fp );
+ curl_setopt( $ch, CURLOPT_INFILE, $fp );
+ curl_setopt( $ch, CURLOPT_INFILESIZE, strlen( $req['body'] ) );
+ $req['_closeHandle'] = $fp; // remember to close this later
+ } else {
+ curl_setopt( $ch, CURLOPT_INFILESIZE, 0 );
+ }
+ curl_setopt( $ch, CURLOPT_READFUNCTION,
+ function ( $ch, $fd, $length ) {
+ $data = fread( $fd, $length );
+ $len = strlen( $data );
+ return $data;
+ }
+ );
+ } elseif ( $req['method'] === 'POST' ) {
+ curl_setopt( $ch, CURLOPT_POST, 1 );
+ curl_setopt( $ch, CURLOPT_POSTFIELDS, $req['body'] );
+ } else {
+ if ( is_resource( $req['body'] ) || $req['body'] !== '' ) {
+ throw new Exception( "HTTP body specified for a non PUT/POST request." );
+ }
+ $req['headers']['content-length'] = 0;
+ }
+
+ $headers = array();
+ foreach ( $req['headers'] as $name => $value ) {
+ if ( strpos( $name, ': ' ) ) {
+ throw new Exception( "Headers cannot have ':' in the name." );
+ }
+ $headers[] = $name . ': ' . trim( $value );
+ }
+ curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
+
+ curl_setopt( $ch, CURLOPT_HEADERFUNCTION,
+ function ( $ch, $header ) use ( &$req ) {
+ $length = strlen( $header );
+ $matches = array();
+ if ( preg_match( "/^(HTTP\/1\.[01]) (\d{3}) (.*)/", $header, $matches ) ) {
+ $req['response']['code'] = (int)$matches[2];
+ $req['response']['reason'] = trim( $matches[3] );
+ return $length;
+ }
+ if ( strpos( $header, ":" ) === false ) {
+ return $length;
+ }
+ list( $name, $value ) = explode( ":", $header, 2 );
+ $req['response']['headers'][strtolower( $name )] = trim( $value );
+ return $length;
+ }
+ );
+
+ if ( isset( $req['stream'] ) ) {
+ // Don't just use CURLOPT_FILE as that might give:
+ // curl_setopt(): cannot represent a stream of type Output as a STDIO FILE*
+ // The callback here handles both normal files and php://temp handles.
+ curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
+ function ( $ch, $data ) use ( &$req ) {
+ return fwrite( $req['stream'], $data );
+ }
+ );
+ } else {
+ curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
+ function ( $ch, $data ) use ( &$req ) {
+ $req['response']['body'] .= $data;
+ return strlen( $data );
+ }
+ );
+ }
+
+ return $ch;
+ }
+
+ /**
+ * @return resource
+ */
+ protected function getCurlMulti() {
+ if ( !$this->multiHandle ) {
+ $cmh = curl_multi_init();
+ if ( function_exists( 'curl_multi_setopt' ) ) { // PHP 5.5
+ curl_multi_setopt( $cmh, CURLMOPT_PIPELINING, (int)$this->usePipelining );
+ curl_multi_setopt( $cmh, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
+ }
+ $this->multiHandle = $cmh;
+ }
+ return $this->multiHandle;
+ }
+
+ function __destruct() {
+ if ( $this->multiHandle ) {
+ curl_multi_close( $this->multiHandle );
+ }
+ }
+}
diff --git a/includes/cache/ProcessCacheLRU.php b/includes/libs/ProcessCacheLRU.php
index 76c76f37..f988207a 100644
--- a/includes/cache/ProcessCacheLRU.php
+++ b/includes/libs/ProcessCacheLRU.php
@@ -35,13 +35,10 @@ class ProcessCacheLRU {
/**
* @param $maxKeys integer Maximum number of entries allowed (min 1).
- * @throws MWException When $maxCacheKeys is not an int or =< 0.
+ * @throws UnexpectedValueException When $maxCacheKeys is not an int or =< 0.
*/
public function __construct( $maxKeys ) {
- if ( !is_int( $maxKeys ) || $maxKeys < 1 ) {
- throw new MWException( __METHOD__ . " must be given an integer and >= 1" );
- }
- $this->maxCacheKeys = $maxKeys;
+ $this->resize( $maxKeys );
}
/**
@@ -79,6 +76,7 @@ class ProcessCacheLRU {
if ( isset( $this->cache[$key][$prop] ) ) {
return ( $maxAge <= 0 || ( time() - $this->cacheTimes[$key][$prop] ) <= $maxAge );
}
+
return false;
}
@@ -119,6 +117,25 @@ class ProcessCacheLRU {
}
/**
+ * Resize the maximum number of cache entries, removing older entries as needed
+ *
+ * @param $maxKeys integer
+ * @return void
+ */
+ public function resize( $maxKeys ) {
+ if ( !is_int( $maxKeys ) || $maxKeys < 1 ) {
+ throw new UnexpectedValueException( __METHOD__ . " must be given an integer >= 1" );
+ }
+ $this->maxCacheKeys = $maxKeys;
+ while ( count( $this->cache ) > $this->maxCacheKeys ) {
+ reset( $this->cache );
+ $evictKey = key( $this->cache );
+ unset( $this->cache[$evictKey] );
+ unset( $this->cacheTimes[$evictKey] );
+ }
+ }
+
+ /**
* Push an entry to the top of the cache
*
* @param $key string
diff --git a/includes/libs/RunningStat.php b/includes/libs/RunningStat.php
new file mode 100644
index 00000000..dda5254e
--- /dev/null
+++ b/includes/libs/RunningStat.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * Compute running mean, variance, and extrema of a stream of numbers.
+ *
+ * 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
+ */
+
+// Needed due to PHP non-bug <https://bugs.php.net/bug.php?id=49828>.
+define( 'NEGATIVE_INF', -INF );
+
+/**
+ * Represents a running summary of a stream of numbers.
+ *
+ * RunningStat instances are accumulator-like objects that provide a set of
+ * continuously-updated summary statistics for a stream of numbers, without
+ * requiring that each value be stored. The measures it provides are the
+ * arithmetic mean, variance, standard deviation, and extrema (min and max);
+ * together they describe the central tendency and statistical dispersion of a
+ * set of values.
+ *
+ * One RunningStat instance can be merged into another; the resultant
+ * RunningStat has the state it would have had if it had accumulated each
+ * individual point. This allows data to be summarized in parallel and in
+ * stages without loss of fidelity.
+ *
+ * Based on a C++ implementation by John D. Cook:
+ * <http://www.johndcook.com/standard_deviation.html>
+ * <http://www.johndcook.com/skewness_kurtosis.html>
+ *
+ * The in-line documentation for this class incorporates content from the
+ * English Wikipedia articles "Variance", "Algorithms for calculating
+ * variance", and "Standard deviation".
+ *
+ * @since 1.23
+ */
+class RunningStat implements Countable {
+
+ /** @var int Number of samples. **/
+ public $n = 0;
+
+ /** @var float The first moment (or mean, or expected value). **/
+ public $m1 = 0.0;
+
+ /** @var float The second central moment (or variance). **/
+ public $m2 = 0.0;
+
+ /** @var float The least value in the the set. **/
+ public $min = INF;
+
+ /** @var float The most value in the set. **/
+ public $max = NEGATIVE_INF;
+
+ /**
+ * Count the number of accumulated values.
+ * @return int Number of values
+ */
+ public function count() {
+ return $this->n;
+ }
+
+ /**
+ * Add a number to the data set.
+ * @param int|float $x Value to add
+ */
+ public function push( $x ) {
+ $x = (float) $x;
+
+ $this->min = min( $this->min, $x );
+ $this->max = max( $this->max, $x );
+
+ $n1 = $this->n;
+ $this->n += 1;
+ $delta = $x - $this->m1;
+ $delta_n = $delta / $this->n;
+ $this->m1 += $delta_n;
+ $this->m2 += $delta * $delta_n * $n1;
+ }
+
+ /**
+ * Get the mean, or expected value.
+ *
+ * The arithmetic mean is the sum of all measurements divided by the number
+ * of observations in the data set.
+ *
+ * @return float Mean
+ */
+ public function getMean() {
+ return $this->m1;
+ }
+
+ /**
+ * Get the estimated variance.
+ *
+ * Variance measures how far a set of numbers is spread out. A small
+ * variance indicates that the data points tend to be very close to the
+ * mean (and hence to each other), while a high variance indicates that the
+ * data points are very spread out from the mean and from each other.
+ *
+ * @return float Estimated variance
+ */
+ public function getVariance() {
+ if ( $this->n === 0 ) {
+ // The variance of the empty set is undefined.
+ return NAN;
+ } elseif ( $this->n === 1 ) {
+ return 0.0;
+ } else {
+ return $this->m2 / ( $this->n - 1.0 );
+ }
+ }
+
+ /**
+ * Get the estimated stanard deviation.
+ *
+ * The standard deviation of a statistical population is the square root of
+ * its variance. It shows shows how much variation from the mean exists. In
+ * addition to expressing the variability of a population, the standard
+ * deviation is commonly used to measure confidence in statistical conclusions.
+ *
+ * @return float Estimated standard deviation
+ */
+ public function getStdDev() {
+ return sqrt( $this->getVariance() );
+ }
+
+ /**
+ * Merge another RunningStat instance into this instance.
+ *
+ * This instance then has the state it would have had if all the data had
+ * been accumulated by it alone.
+ *
+ * @param RunningStat RunningStat instance to merge into this one
+ */
+ public function merge( RunningStat $other ) {
+ // If the other RunningStat is empty, there's nothing to do.
+ if ( $other->n === 0 ) {
+ return;
+ }
+
+ // If this RunningStat is empty, copy values from other RunningStat.
+ if ( $this->n === 0 ) {
+ $this->n = $other->n;
+ $this->m1 = $other->m1;
+ $this->m2 = $other->m2;
+ $this->min = $other->min;
+ $this->max = $other->max;
+ return;
+ }
+
+ $n = $this->n + $other->n;
+ $delta = $other->m1 - $this->m1;
+ $delta2 = $delta * $delta;
+
+ $this->m1 = ( ( $this->n * $this->m1 ) + ( $other->n * $other->m1 ) ) / $n;
+ $this->m2 = $this->m2 + $other->m2 + ( $delta2 * $this->n * $other->n / $n );
+ $this->min = min( $this->min, $other->min );
+ $this->max = max( $this->max, $other->max );
+ $this->n = $n;
+ }
+}
diff --git a/includes/ScopedCallback.php b/includes/libs/ScopedCallback.php
index ef22e0a3..631b6519 100644
--- a/includes/ScopedCallback.php
+++ b/includes/libs/ScopedCallback.php
@@ -31,11 +31,11 @@ class ScopedCallback {
/**
* @param callable $callback
- * @throws MWException
+ * @throws Exception
*/
public function __construct( $callback ) {
if ( !is_callable( $callback ) ) {
- throw new MWException( "Provided callback is not valid." );
+ throw new InvalidArgumentException( "Provided callback is not valid." );
}
$this->callback = $callback;
}
diff --git a/includes/ScopedPHPTimeout.php b/includes/libs/ScopedPHPTimeout.php
index d1493c30..d1493c30 100644
--- a/includes/ScopedPHPTimeout.php
+++ b/includes/libs/ScopedPHPTimeout.php
diff --git a/includes/XmlTypeCheck.php b/includes/libs/XmlTypeCheck.php
index aca857e9..aca857e9 100644
--- a/includes/XmlTypeCheck.php
+++ b/includes/libs/XmlTypeCheck.php
diff --git a/includes/libs/jsminplus.php b/includes/libs/jsminplus.php
index f250217f..ed0382cf 100644
--- a/includes/libs/jsminplus.php
+++ b/includes/libs/jsminplus.php
@@ -1,4 +1,5 @@
<?php
+// @codingStandardsIgnoreFile File external to MediaWiki. Ignore coding conventions checks.
/**
* JSMinPlus version 1.4
*
diff --git a/includes/libs/lessc.inc.php b/includes/libs/lessc.inc.php
index 3dce961e..61ed771a 100644
--- a/includes/libs/lessc.inc.php
+++ b/includes/libs/lessc.inc.php
@@ -1,7 +1,7 @@
<?php
-
+// @codingStandardsIgnoreFile File external to MediaWiki. Ignore coding conventions checks.
/**
- * lessphp v0.4.0@261f1bd28f
+ * lessphp v0.4.0@2cc77e3c7b
* http://leafo.net/lessphp
*
* LESS CSS compiler, adapted from http://lesscss.org
@@ -847,7 +847,7 @@ class lessc {
* The input is expected to be reduced. This function will not work on
* things like expressions and variables.
*/
- protected function compileValue($value) {
+ public function compileValue($value) {
switch ($value[0]) {
case 'list':
// [1] - delimiter
@@ -1011,6 +1011,39 @@ class lessc {
return $this->lib_rgbahex($color);
}
+ /**
+ * Given an url, decide whether to output a regular link or the base64-encoded contents of the file
+ *
+ * @param array $value either an argument list (two strings) or a single string
+ * @return string formatted url(), either as a link or base64-encoded
+ */
+ protected function lib_data_uri($value) {
+ $mime = ($value[0] === 'list') ? $value[2][0][2] : null;
+ $url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
+
+ $fullpath = $this->findImport($url);
+
+ if($fullpath && ($fsize = filesize($fullpath)) !== false) {
+ // IE8 can't handle data uris larger than 32KB
+ if($fsize/1024 < 32) {
+ if(is_null($mime)) {
+ if(class_exists('finfo')) { // php 5.3+
+ $finfo = new finfo(FILEINFO_MIME);
+ $mime = explode('; ', $finfo->file($fullpath));
+ $mime = $mime[0];
+ } elseif(function_exists('mime_content_type')) { // PHP 5.2
+ $mime = mime_content_type($fullpath);
+ }
+ }
+
+ if(!is_null($mime)) // fallback if the MIME type is still unknown
+ $url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
+ }
+ }
+
+ return 'url("'.$url.'")';
+ }
+
// utility func to unquote a string
protected function lib_e($arg) {
switch ($arg[0]) {
@@ -1234,24 +1267,44 @@ class lessc {
}
protected function lib_contrast($args) {
- if ($args[0] != 'list' || count($args[2]) < 3) {
- return array(array('color', 0, 0, 0), 0);
- }
+ $darkColor = array('color', 0, 0, 0);
+ $lightColor = array('color', 255, 255, 255);
+ $threshold = 0.43;
- list($inputColor, $darkColor, $lightColor) = $args[2];
+ if ( $args[0] == 'list' ) {
+ $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0]) : $lightColor;
+ $darkColor = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1]) : $darkColor;
+ $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2]) : $lightColor;
+ $threshold = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
+ }
+ else {
+ $inputColor = $this->assertColor($args);
+ }
- $inputColor = $this->assertColor($inputColor);
- $darkColor = $this->assertColor($darkColor);
- $lightColor = $this->assertColor($lightColor);
- $hsl = $this->toHSL($inputColor);
+ $inputColor = $this->coerceColor($inputColor);
+ $darkColor = $this->coerceColor($darkColor);
+ $lightColor = $this->coerceColor($lightColor);
- if ($hsl[3] > 50) {
- return $darkColor;
- }
+ //Figure out which is actually light and dark!
+ if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
+ $t = $lightColor;
+ $lightColor = $darkColor;
+ $darkColor = $t;
+ }
- return $lightColor;
+ $inputColor_alpha = $this->lib_alpha($inputColor);
+ if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
+ return $lightColor;
+ }
+ return $darkColor;
}
+ protected function lib_luma($color) {
+ $color = $this->coerceColor($color);
+ return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
+ }
+
+
public function assertColor($value, $error = "expected color value") {
$color = $this->coerceColor($value);
if (is_null($color)) $this->throwError($error);
@@ -1475,8 +1528,9 @@ class lessc {
list(, $name, $args) = $value;
if ($name == "%") $name = "_sprintf";
+
$f = isset($this->libFunctions[$name]) ?
- $this->libFunctions[$name] : array($this, 'lib_'.$name);
+ $this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
if (is_callable($f)) {
if ($args[0] == 'list')
@@ -2338,7 +2392,7 @@ class lessc_parser {
$this->throwError();
// TODO report where the block was opened
- if (!is_null($this->env->parent))
+ if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
throw new exception('parse error: unclosed block');
return $this->env;
diff --git a/includes/libs/virtualrest/SwiftVirtualRESTService.php b/includes/libs/virtualrest/SwiftVirtualRESTService.php
new file mode 100644
index 00000000..011dabe0
--- /dev/null
+++ b/includes/libs/virtualrest/SwiftVirtualRESTService.php
@@ -0,0 +1,175 @@
+<?php
+/**
+ * Virtual HTTP service client for Swift
+ *
+ * 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
+ */
+
+/**
+ * Example virtual rest service for OpenStack Swift
+ * @TODO: caching support (APC/memcached)
+ * @since 1.23
+ */
+class SwiftVirtualRESTService extends VirtualRESTService {
+ /** @var array */
+ protected $authCreds;
+ /** @var int UNIX timestamp */
+ protected $authSessionTimestamp = 0;
+ /** @var int UNIX timestamp */
+ protected $authErrorTimestamp = null;
+ /** @var int */
+ protected $authCachedStatus = null;
+ /** @var string */
+ protected $authCachedReason = null;
+
+ /**
+ * @param array $params Key/value map
+ * - swiftAuthUrl : Swift authentication server URL
+ * - swiftUser : Swift user used by MediaWiki (account:username)
+ * - swiftKey : Swift authentication key for the above user
+ * - swiftAuthTTL : Swift authentication TTL (seconds)
+ */
+ public function __construct( array $params ) {
+ parent::__construct( $params );
+ }
+
+ /**
+ * @return int|bool HTTP status on cached failure
+ */
+ protected function needsAuthRequest() {
+ if ( !$this->authCreds ) {
+ return true;
+ }
+ if ( $this->authErrorTimestamp !== null ) {
+ if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
+ return $this->authCachedStatus; // failed last attempt; don't bother
+ } else { // actually retry this time
+ $this->authErrorTimestamp = null;
+ }
+ }
+ // Session keys expire after a while, so we renew them periodically
+ return ( ( time() - $this->authSessionTimestamp ) > $this->params['swiftAuthTTL'] );
+ }
+
+ protected function applyAuthResponse( array $req ) {
+ $this->authSessionTimestamp = 0;
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response'];
+ if ( $rcode >= 200 && $rcode <= 299 ) { // OK
+ $this->authCreds = array(
+ 'auth_token' => $rhdrs['x-auth-token'],
+ 'storage_url' => $rhdrs['x-storage-url']
+ );
+ $this->authSessionTimestamp = time();
+ return true;
+ } elseif ( $rcode === 403 ) {
+ $this->authCachedStatus = 401;
+ $this->authCachedReason = 'Authorization Required';
+ $this->authErrorTimestamp = time();
+ return false;
+ } else {
+ $this->authCachedStatus = $rcode;
+ $this->authCachedReason = $rdesc;
+ $this->authErrorTimestamp = time();
+ return null;
+ }
+ }
+
+ public function onRequests( array $reqs, Closure $idGeneratorFunc ) {
+ $result = array();
+ $firstReq = reset( $reqs );
+ if ( $firstReq && count( $reqs ) == 1 && isset( $firstReq['isAuth'] ) ) {
+ // This was an authentication request for work requests...
+ $result = $reqs; // no change
+ } else {
+ // These are actual work requests...
+ $needsAuth = $this->needsAuthRequest();
+ if ( $needsAuth === true ) {
+ // These are work requests and we don't have any token to use.
+ // Replace the work requests with an authentication request.
+ $result = array(
+ $idGeneratorFunc() => array(
+ 'method' => 'GET',
+ 'url' => $this->params['swiftAuthUrl'] . "/v1.0",
+ 'headers' => array(
+ 'x-auth-user' => $this->params['swiftUser'],
+ 'x-auth-key' => $this->params['swiftKey'] ),
+ 'isAuth' => true,
+ 'chain' => $reqs
+ )
+ );
+ } elseif ( $needsAuth !== false ) {
+ // These are work requests and authentication has previously failed.
+ // It is most efficient to just give failed pseudo responses back for
+ // the original work requests.
+ foreach ( $reqs as $key => $req ) {
+ $req['response'] = array(
+ 'code' => $this->authCachedStatus,
+ 'reason' => $this->authCachedReason,
+ 'headers' => array(),
+ 'body' => '',
+ 'error' => ''
+ );
+ $result[$key] = $req;
+ }
+ } else {
+ // These are work requests and we have a token already.
+ // Go through and mangle each request to include a token.
+ foreach ( $reqs as $key => $req ) {
+ // The default encoding treats the URL as a REST style path that uses
+ // forward slash as a hierarchical delimiter (and never otherwise).
+ // Subclasses can override this, and should be documented in any case.
+ $parts = array_map( 'rawurlencode', explode( '/', $req['url'] ) );
+ $req['url'] = $this->authCreds['storage_url'] . '/' . implode( '/', $parts );
+ $req['headers']['x-auth-token'] = $this->authCreds['auth_token'];
+ $result[$key] = $req;
+ // @TODO: add ETag/Content-Length and such as needed
+ }
+ }
+ }
+ return $result;
+ }
+
+ public function onResponses( array $reqs, Closure $idGeneratorFunc ) {
+ $firstReq = reset( $reqs );
+ if ( $firstReq && count( $reqs ) == 1 && isset( $firstReq['isAuth'] ) ) {
+ $result = array();
+ // This was an authentication request for work requests...
+ if ( $this->applyAuthResponse( $firstReq ) ) {
+ // If it succeeded, we can subsitute the work requests back.
+ // Call this recursively in order to munge and add headers.
+ $result = $this->onRequests( $firstReq['chain'], $idGeneratorFunc );
+ } else {
+ // If it failed, it is most efficient to just give failing
+ // pseudo-responses back for the actual work requests.
+ foreach ( $firstReq['chain'] as $key => $req ) {
+ $req['response'] = array(
+ 'code' => $this->authCachedStatus,
+ 'reason' => $this->authCachedReason,
+ 'headers' => array(),
+ 'body' => '',
+ 'error' => ''
+ );
+ $result[$key] = $req;
+ }
+ }
+ } else {
+ $result = $reqs; // no change
+ }
+ return $result;
+ }
+}
diff --git a/includes/libs/virtualrest/VirtualRESTService.php b/includes/libs/virtualrest/VirtualRESTService.php
new file mode 100644
index 00000000..05c2afc1
--- /dev/null
+++ b/includes/libs/virtualrest/VirtualRESTService.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Virtual HTTP service client
+ *
+ * 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
+ */
+
+/**
+ * Virtual HTTP service instance that can be mounted on to a VirtualRESTService
+ *
+ * Sub-classes manage the logic of either:
+ * - a) Munging virtual HTTP request arrays to have qualified URLs and auth headers
+ * - b) Emulating the execution of virtual HTTP requests (e.g. brokering)
+ *
+ * Authentication information can be cached in instances of the class for performance.
+ * Such information should also be cached locally on the server and auth requests should
+ * have reasonable timeouts.
+ *
+ * @since 1.23
+ */
+abstract class VirtualRESTService {
+ /** @var array Key/value map */
+ protected $params = array();
+
+ /**
+ * @param array $params Key/value map
+ */
+ public function __construct( array $params ) {
+ $this->params = $params;
+ }
+
+ /**
+ * Prepare virtual HTTP(S) requests (for this service) for execution
+ *
+ * This method should mangle any of the $reqs entry fields as needed:
+ * - url : munge the URL to have an absolute URL with a protocol
+ * and encode path components as needed by the backend [required]
+ * - query : include any authentication signatures/parameters [as needed]
+ * - headers : include any authentication tokens/headers [as needed]
+ *
+ * The incoming URL parameter will be relative to the service mount point.
+ *
+ * This method can also remove some of the requests as well as add new ones
+ * (using $idGenerator to set each of the entries' array keys). For any existing
+ * or added request, the 'response' array can be filled in, which will prevent the
+ * client from executing it. If an original request is removed, at some point it
+ * must be added back (with the same key) in onRequests() or onResponses();
+ * it's reponse may be filled in as with other requests.
+ *
+ * @param array $reqs Map of Virtual HTTP request arrays
+ * @param Closure $idGeneratorFunc Method to generate unique keys for new requests
+ * @return array Modified HTTP request array map
+ */
+ public function onRequests( array $reqs, Closure $idGeneratorFunc ) {
+ $result = array();
+ foreach ( $reqs as $key => $req ) {
+ // The default encoding treats the URL as a REST style path that uses
+ // forward slash as a hierarchical delimiter (and never otherwise).
+ // Subclasses can override this, and should be documented in any case.
+ $parts = array_map( 'rawurlencode', explode( '/', $req['url'] ) );
+ $req['url'] = $this->params['baseUrl'] . '/' . implode( '/', $parts );
+ $result[$key] = $req;
+ }
+ return $result;
+ }
+
+ /**
+ * Mangle or replace virtual HTTP(S) requests which have been responded to
+ *
+ * This method may mangle any of the $reqs entry 'response' fields as needed:
+ * - code : perform any code normalization [as needed]
+ * - reason : perform any reason normalization [as needed]
+ * - headers : perform any header normalization [as needed]
+ *
+ * This method can also remove some of the requests as well as add new ones
+ * (using $idGenerator to set each of the entries' array keys). For any existing
+ * or added request, the 'response' array can be filled in, which will prevent the
+ * client from executing it. If an original request is removed, at some point it
+ * must be added back (with the same key) in onRequests() or onResponses();
+ * it's reponse may be filled in as with other requests. All requests added to $reqs
+ * will be passed through onRequests() to handle any munging required as normal.
+ *
+ * The incoming URL parameter will be relative to the service mount point.
+ *
+ * @param array $reqs Map of Virtual HTTP request arrays with 'response' set
+ * @param Closure $idGeneratorFunc Method to generate unique keys for new requests
+ * @return array Modified HTTP request array map
+ */
+ public function onResponses( array $reqs, Closure $idGeneratorFunc ) {
+ return $reqs;
+ }
+}
diff --git a/includes/libs/virtualrest/VirtualRESTServiceClient.php b/includes/libs/virtualrest/VirtualRESTServiceClient.php
new file mode 100644
index 00000000..2d21d3cf
--- /dev/null
+++ b/includes/libs/virtualrest/VirtualRESTServiceClient.php
@@ -0,0 +1,289 @@
+<?php
+/**
+ * Virtual HTTP service client
+ *
+ * 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
+ */
+
+/**
+ * Virtual HTTP service client loosely styled after a Virtual File System
+ *
+ * Services can be mounted on path prefixes so that virtual HTTP operations
+ * against sub-paths will map to those services. Operations can actually be
+ * done using HTTP messages over the wire or may simple be emulated locally.
+ *
+ * Virtual HTTP request maps are arrays that use the following format:
+ * - method : GET/HEAD/PUT/POST/DELETE
+ * - url : HTTP/HTTPS URL or virtual service path with a registered prefix
+ * - query : <query parameter field/value associative array> (uses RFC 3986)
+ * - headers : <header name/value associative array>
+ * - body : source to get the HTTP request body from;
+ * this can simply be a string (always), a resource for
+ * PUT requests, and a field/value array for POST request;
+ * array bodies are encoded as multipart/form-data and strings
+ * use application/x-www-form-urlencoded (headers sent automatically)
+ * - stream : resource to stream the HTTP response body to
+ * Request maps can use integer index 0 instead of 'method' and 1 instead of 'url'.
+ *
+ * @author Aaron Schulz
+ * @since 1.23
+ */
+class VirtualRESTServiceClient {
+ /** @var MultiHttpClient */
+ protected $http;
+ /** @var Array Map of (prefix => VirtualRESTService) */
+ protected $instances = array();
+
+ const VALID_MOUNT_REGEX = '#^/[0-9a-z]+/([0-9a-z]+/)*$#';
+
+ /**
+ * @param MultiHttpClient $http
+ */
+ public function __construct( MultiHttpClient $http ) {
+ $this->http = $http;
+ }
+
+ /**
+ * Map a prefix to service handler
+ *
+ * @param string $prefix Virtual path
+ * @param VirtualRESTService $instance
+ */
+ public function mount( $prefix, VirtualRESTService $instance ) {
+ if ( !preg_match( self::VALID_MOUNT_REGEX, $prefix ) ) {
+ throw new UnexpectedValueException( "Invalid service mount point '$prefix'." );
+ } elseif ( isset( $this->instances[$prefix] ) ) {
+ throw new UnexpectedValueException( "A service is already mounted on '$prefix'." );
+ }
+ $this->instances[$prefix] = $instance;
+ }
+
+ /**
+ * Unmap a prefix to service handler
+ *
+ * @param string $prefix Virtual path
+ */
+ public function unmount( $prefix ) {
+ if ( !preg_match( self::VALID_MOUNT_REGEX, $prefix ) ) {
+ throw new UnexpectedValueException( "Invalid service mount point '$prefix'." );
+ } elseif ( !isset( $this->instances[$prefix] ) ) {
+ throw new UnexpectedValueException( "No service is mounted on '$prefix'." );
+ }
+ unset( $this->instances[$prefix] );
+ }
+
+ /**
+ * Get the prefix and service that a virtual path is serviced by
+ *
+ * @param string $path
+ * @return array (prefix,VirtualRESTService) or (null,null) if none found
+ */
+ public function getMountAndService( $path ) {
+ $cmpFunc = function( $a, $b ) {
+ $al = substr_count( $a, '/' );
+ $bl = substr_count( $b, '/' );
+ if ( $al === $bl ) {
+ return 0; // should not actually happen
+ }
+ return ( $al < $bl ) ? 1 : -1; // largest prefix first
+ };
+
+ $matches = array(); // matching prefixes (mount points)
+ foreach ( $this->instances as $prefix => $service ) {
+ if ( strpos( $path, $prefix ) === 0 ) {
+ $matches[] = $prefix;
+ }
+ }
+ usort( $matches, $cmpFunc );
+
+ // Return the most specific prefix and corresponding service
+ return isset( $matches[0] )
+ ? array( $matches[0], $this->instances[$matches[0]] )
+ : array( null, null );
+ }
+
+ /**
+ * Execute a virtual HTTP(S) request
+ *
+ * This method returns a response map of:
+ * - code : HTTP response code or 0 if there was a serious cURL error
+ * - reason : HTTP response reason (empty if there was a serious cURL error)
+ * - headers : <header name/value associative array>
+ * - body : HTTP response body or resource (if "stream" was set)
+ * - err : Any cURL error string
+ * The map also stores integer-indexed copies of these values. This lets callers do:
+ * <code>
+ * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $client->run( $req );
+ * </code>
+ * @param array $req Virtual HTTP request array
+ * @return array Response array for request
+ */
+ public function run( array $req ) {
+ $req = $this->runMulti( array( $req ) );
+ return $req[0]['response'];
+ }
+
+ /**
+ * Execute a set of virtual HTTP(S) requests concurrently
+ *
+ * A map of requests keys to response maps is returned. Each response map has:
+ * - code : HTTP response code or 0 if there was a serious cURL error
+ * - reason : HTTP response reason (empty if there was a serious cURL error)
+ * - headers : <header name/value associative array>
+ * - body : HTTP response body or resource (if "stream" was set)
+ * - err : Any cURL error string
+ * The map also stores integer-indexed copies of these values. This lets callers do:
+ * <code>
+ * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $responses[0];
+ * </code>
+ *
+ * @param array $req Map of Virtual HTTP request arrays
+ * @return array $reqs Map of corresponding response values with the same keys/order
+ */
+ public function runMulti( array $reqs ) {
+ foreach ( $reqs as $index => &$req ) {
+ if ( isset( $req[0] ) ) {
+ $req['method'] = $req[0]; // short-form
+ unset( $req[0] );
+ }
+ if ( isset( $req[1] ) ) {
+ $req['url'] = $req[1]; // short-form
+ unset( $req[1] );
+ }
+ $req['chain'] = array(); // chain or list of replaced requests
+ }
+ unset( $req ); // don't assign over this by accident
+
+ $curUniqueId = 0;
+ $armoredIndexMap = array(); // (original index => new index)
+
+ $doneReqs = array(); // (index => request)
+ $executeReqs = array(); // (index => request)
+ $replaceReqsByService = array(); // (prefix => index => request)
+ $origPending = array(); // (index => 1) for original requests
+
+ foreach ( $reqs as $origIndex => $req ) {
+ // Re-index keys to consecutive integers (they will be swapped back later)
+ $index = $curUniqueId++;
+ $armoredIndexMap[$origIndex] = $index;
+ $origPending[$index] = 1;
+ if ( preg_match( '#^(http|ftp)s?://#', $req['url'] ) ) {
+ // Absolute FTP/HTTP(S) URL, run it as normal
+ $executeReqs[$index] = $req;
+ } else {
+ // Must be a virtual HTTP URL; resolve it
+ list( $prefix, $service ) = $this->getMountAndService( $req['url'] );
+ if ( !$service ) {
+ throw new UnexpectedValueException( "Path '{$req['url']}' has no service." );
+ }
+ // Set the URL to the mount-relative portion
+ $req['url'] = substr( $req['url'], strlen( $prefix ) );
+ $replaceReqsByService[$prefix][$index] = $req;
+ }
+ }
+
+ // Function to get IDs that won't collide with keys in $armoredIndexMap
+ $idFunc = function() use ( &$curUniqueId ) {
+ return $curUniqueId++;
+ };
+
+ $rounds = 0;
+ do {
+ if ( ++$rounds > 5 ) { // sanity
+ throw new Exception( "Too many replacement rounds detected. Aborting." );
+ }
+ // Resolve the virtual URLs valid and qualified HTTP(S) URLs
+ // and add any required authentication headers for the backend.
+ // Services can also replace requests with new ones, either to
+ // defer the original or to set a proxy response to the original.
+ $newReplaceReqsByService = array();
+ foreach ( $replaceReqsByService as $prefix => $servReqs ) {
+ $service = $this->instances[$prefix];
+ foreach ( $service->onRequests( $servReqs, $idFunc ) as $index => $req ) {
+ // Services use unique IDs for replacement requests
+ if ( isset( $servReqs[$index] ) || isset( $origPending[$index] ) ) {
+ // A current or original request which was not modified
+ } else {
+ // Replacement requests with pre-set responses should not execute
+ $newReplaceReqsByService[$prefix][$index] = $req;
+ }
+ if ( isset( $req['response'] ) ) {
+ // Replacement requests with pre-set responses should not execute
+ unset( $executeReqs[$index] );
+ unset( $origPending[$index] );
+ $doneReqs[$index] = $req;
+ } else {
+ // Original or mangled request included
+ $executeReqs[$index] = $req;
+ }
+ }
+ }
+ // Update index of requests to inspect for replacement
+ $replaceReqsByService = $newReplaceReqsByService;
+ // Run the actual work HTTP requests
+ foreach ( $this->http->runMulti( $executeReqs ) as $index => $ranReq ) {
+ $doneReqs[$index] = $ranReq;
+ unset( $origPending[$index] );
+ }
+ $executeReqs = array();
+ // Services can also replace requests with new ones, either to
+ // defer the original or to set a proxy response to the original.
+ // Any replacement requests executed above will need to be replaced
+ // with new requests (eventually the original). The responses can be
+ // forced instead of having the request sent over the wire.
+ $newReplaceReqsByService = array();
+ foreach ( $replaceReqsByService as $prefix => $servReqs ) {
+ $service = $this->instances[$prefix];
+ // Only the request copies stored in $doneReqs actually have the response
+ $servReqs = array_intersect_key( $doneReqs, $servReqs );
+ foreach ( $service->onResponses( $servReqs, $idFunc ) as $index => $req ) {
+ // Services use unique IDs for replacement requests
+ if ( isset( $servReqs[$index] ) || isset( $origPending[$index] ) ) {
+ // A current or original request which was not modified
+ } else {
+ // Replacement requests with pre-set responses should not execute
+ $newReplaceReqsByService[$prefix][$index] = $req;
+ }
+ if ( isset( $req['response'] ) ) {
+ // Replacement requests with pre-set responses should not execute
+ unset( $origPending[$index] );
+ $doneReqs[$index] = $req;
+ } else {
+ // Update the request in case it was mangled
+ $executeReqs[$index] = $req;
+ }
+ }
+ }
+ // Update index of requests to inspect for replacement
+ $replaceReqsByService = $newReplaceReqsByService;
+ } while ( count( $origPending ) );
+
+ $responses = array();
+ // Update $reqs to include 'response' and normalized request 'headers'.
+ // This maintains the original order of $reqs.
+ foreach ( $reqs as $origIndex => $req ) {
+ $index = $armoredIndexMap[$origIndex];
+ if ( !isset( $doneReqs[$index] ) ) {
+ throw new UnexpectedValueException( "Response for request '$index' is NULL." );
+ }
+ $responses[$origIndex] = $doneReqs[$index]['response'];
+ }
+
+ return $responses;
+ }
+}
diff --git a/includes/limit.sh b/includes/limit.sh
index 2a1545b6..d71e6603 100644
--- a/includes/limit.sh
+++ b/includes/limit.sh
@@ -6,7 +6,40 @@
# and is available on most Linux systems. If Perl was distributed with
# BSD::Resource included, we would happily use that instead, but it isn't.
+# Clean up cgroup
+cleanup() {
+ # First we have to move the current task into a "garbage" group, otherwise
+ # the cgroup will not be empty, and attempting to remove it will fail with
+ # "Device or resource busy"
+ if [ -w "$MW_CGROUP"/tasks ]; then
+ GARBAGE="$MW_CGROUP"
+ else
+ GARBAGE="$MW_CGROUP"/garbage-`id -un`
+ if [ ! -e "$GARBAGE" ]; then
+ mkdir -m 0700 "$GARBAGE"
+ fi
+ fi
+ echo $BASHPID > "$GARBAGE"/tasks
+
+ # Suppress errors in case the cgroup has disappeared due to a release script
+ rmdir "$MW_CGROUP"/$$ 2>/dev/null
+}
+
+updateTaskCount() {
+ # There are lots of ways to count lines in a file in shell script, but this
+ # is one of the few that doesn't create another process, which would
+ # increase the returned number of tasks.
+ readarray < "$MW_CGROUP"/$$/tasks
+ NUM_TASKS=${#MAPFILE[*]}
+}
+
+log() {
+ echo limit.sh: "$*" >&3
+ echo limit.sh: "$*" >&2
+}
+
MW_INCLUDE_STDERR=
+MW_USE_LOG_PIPE=
MW_CPU_LIMIT=0
MW_CGROUP=
MW_MEM_LIMIT=0
@@ -19,6 +52,10 @@ eval "$2"
if [ -n "$MW_INCLUDE_STDERR" ]; then
exec 2>&1
fi
+if [ -z "$MW_USE_LOG_PIPE" ]; then
+ # Open a dummy log FD
+ exec 3>/dev/null
+fi
if [ "$MW_CPU_LIMIT" -gt 0 ]; then
ulimit -t "$MW_CPU_LIMIT"
@@ -27,9 +64,11 @@ if [ "$MW_MEM_LIMIT" -gt 0 ]; then
if [ -n "$MW_CGROUP" ]; then
# Create cgroup
if ! mkdir -m 0700 "$MW_CGROUP"/$$; then
- echo "limit.sh: failed to create the cgroup." 1>&2
- exit 1
+ log "failed to create the cgroup."
+ MW_CGROUP=""
fi
+ fi
+ if [ -n "$MW_CGROUP" ]; then
echo $$ > "$MW_CGROUP"/$$/tasks
if [ -n "$MW_CGROUP_NOTIFY" ]; then
echo "1" > "$MW_CGROUP"/$$/notify_on_release
@@ -37,7 +76,10 @@ if [ "$MW_MEM_LIMIT" -gt 0 ]; then
# Memory
echo $(($MW_MEM_LIMIT*1024)) > "$MW_CGROUP"/$$/memory.limit_in_bytes
# Memory+swap
- echo $(($MW_MEM_LIMIT*1024)) > "$MW_CGROUP"/$$/memory.memsw.limit_in_bytes
+ # This will be missing if there is no swap
+ if [ -e "$MW_CGROUP"/$$/memory.memsw.limit_in_bytes ]; then
+ echo $(($MW_MEM_LIMIT*1024)) > "$MW_CGROUP"/$$/memory.memsw.limit_in_bytes
+ fi
else
ulimit -v "$MW_MEM_LIMIT"
fi
@@ -48,43 +90,16 @@ if [ "$MW_FILE_SIZE_LIMIT" -gt 0 ]; then
ulimit -f "$MW_FILE_SIZE_LIMIT"
fi
if [ "$MW_WALL_CLOCK_LIMIT" -gt 0 -a -x "/usr/bin/timeout" ]; then
- /usr/bin/timeout $MW_WALL_CLOCK_LIMIT /bin/bash -c "$1"
+ /usr/bin/timeout $MW_WALL_CLOCK_LIMIT /bin/bash -c "$1" 3>&-
STATUS="$?"
if [ "$STATUS" == 124 ]; then
- echo "limit.sh: timed out." 1>&2
+ log "timed out executing command \"$1\""
fi
else
- eval "$1"
+ eval "$1" 3>&-
STATUS="$?"
fi
-# Clean up cgroup
-cleanup() {
- # First we have to move the current task into a "garbage" group, otherwise
- # the cgroup will not be empty, and attempting to remove it will fail with
- # "Device or resource busy"
- if [ -w "$MW_CGROUP"/tasks ]; then
- GARBAGE="$MW_CGROUP"
- else
- GARBAGE="$MW_CGROUP"/garbage-"$USER"
- if [ ! -e "$GARBAGE" ]; then
- mkdir -m 0700 "$GARBAGE"
- fi
- fi
- echo $BASHPID > "$GARBAGE"/tasks
-
- # Suppress errors in case the cgroup has disappeared due to a release script
- rmdir "$MW_CGROUP"/$$ 2>/dev/null
-}
-
-updateTaskCount() {
- # There are lots of ways to count lines in a file in shell script, but this
- # is one of the few that doesn't create another process, which would
- # increase the returned number of tasks.
- readarray < "$MW_CGROUP"/$$/tasks
- NUM_TASKS=${#MAPFILE[*]}
-}
-
if [ -n "$MW_CGROUP" ]; then
updateTaskCount
@@ -97,7 +112,7 @@ if [ -n "$MW_CGROUP" ]; then
updateTaskCount
done
cleanup
- ) >&/dev/null < /dev/null &
+ ) >&/dev/null < /dev/null 3>&- &
disown -a
else
cleanup
diff --git a/includes/logging/DeleteLogFormatter.php b/includes/logging/DeleteLogFormatter.php
index 01528b7e..8b30e9ba 100644
--- a/includes/logging/DeleteLogFormatter.php
+++ b/includes/logging/DeleteLogFormatter.php
@@ -36,6 +36,7 @@ class DeleteLogFormatter extends LogFormatter {
return "$key-legacy";
}
}
+
return $key;
}
@@ -47,11 +48,15 @@ class DeleteLogFormatter extends LogFormatter {
$params = parent::getMessageParameters();
$subtype = $this->entry->getSubtype();
if ( in_array( $subtype, array( 'event', 'revision' ) ) ) {
- // $params[3] here is 'revision' for page revisions, 'oldimage' for file versions, or a comma-separated list of log_ids for log entries.
- // $subtype here is 'revision' for page revisions and file versions, or 'event' for log entries.
- if (
- ( $subtype === 'event' && count( $params ) === 6 ) ||
- ( $subtype === 'revision' && isset( $params[3] ) && ( $params[3] === 'revision' || $params[3] === 'oldimage' ) )
+ // $params[3] here is 'revision' or 'archive' for page revisions, 'oldimage' or
+ // 'filearchive' for file versions, or a comma-separated list of log_ids for log
+ // entries. $subtype here is 'revision' for page revisions and file
+ // versions, or 'event' for log entries.
+ if ( ( $subtype === 'event' && count( $params ) === 6 )
+ || ( $subtype === 'revision' && isset( $params[3] )
+ && ( $params[3] === 'revision' || $params[3] === 'oldimage'
+ || $params[3] === 'archive' || $params[3] === 'filearchive' )
+ )
) {
$paramStart = $subtype === 'revision' ? 4 : 3;
@@ -59,9 +64,11 @@ class DeleteLogFormatter extends LogFormatter {
$new = $this->parseBitField( $params[$paramStart + 2] );
list( $hid, $unhid, $extra ) = RevisionDeleter::getChanges( $new, $old );
$changes = array();
+ // messages used: revdelete-content-hid, revdelete-summary-hid, revdelete-uname-hid
foreach ( $hid as $v ) {
$changes[] = $this->msg( "$v-hid" )->plain();
}
+ // messages used: revdelete-content-unhid, revdelete-summary-unhid, revdelete-uname-unhid
foreach ( $unhid as $v ) {
$changes[] = $this->msg( "$v-unhid" )->plain();
}
@@ -74,19 +81,24 @@ class DeleteLogFormatter extends LogFormatter {
$newParams[3] = $changeText;
$count = count( explode( ',', $params[$paramStart] ) );
$newParams[4] = $this->context->getLanguage()->formatNum( $count );
- return $this->parsedParametersDeleteLog = $newParams;
+
+ $this->parsedParametersDeleteLog = $newParams;
+ return $this->parsedParametersDeleteLog;
} else {
- return $this->parsedParametersDeleteLog = array_slice( $params, 0, 3 );
+ $this->parsedParametersDeleteLog = array_slice( $params, 0, 3 );
+ return $this->parsedParametersDeleteLog;
}
}
- return $this->parsedParametersDeleteLog = $params;
+ $this->parsedParametersDeleteLog = $params;
+ return $this->parsedParametersDeleteLog;
}
protected function parseBitField( $string ) {
// Input is like ofield=2134 or just the number
if ( strpos( $string, 'field=' ) === 1 ) {
list( , $field ) = explode( '=', $string );
+
return (int)$field;
} else {
return (int)$string;
@@ -95,102 +107,106 @@ class DeleteLogFormatter extends LogFormatter {
public function getActionLinks() {
$user = $this->context->getUser();
- if ( !$user->isAllowed( 'deletedhistory' ) || $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' )
+ || $this->entry->isDeleted( LogPage::DELETED_ACTION )
+ ) {
return '';
}
switch ( $this->entry->getSubtype() ) {
- case 'delete': // Show undelete link
- if ( $user->isAllowed( 'undelete' ) ) {
- $message = 'undeletelink';
- } else {
- $message = 'undeleteviewlink';
- }
- $revert = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Undelete' ),
- $this->msg( $message )->escaped(),
- array(),
- array( 'target' => $this->entry->getTarget()->getPrefixedDBkey() )
- );
- return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
-
- case 'revision': // If an edit was hidden from a page give a review link to the history
- $params = $this->extractParameters();
- if ( !isset( $params[3] ) || !isset( $params[4] ) ) {
- return '';
- }
-
- // Different revision types use different URL params...
- $key = $params[3];
- // This is a CSV of the IDs
- $ids = explode( ',', $params[4] );
-
- $links = array();
-
- // If there's only one item, we can show a diff link
- if ( count( $ids ) == 1 ) {
- // Live revision diffs...
- if ( $key == 'oldid' || $key == 'revision' ) {
- $links[] = Linker::linkKnown(
- $this->entry->getTarget(),
- $this->msg( 'diff' )->escaped(),
- array(),
- array(
- 'diff' => intval( $ids[0] ),
- 'unhide' => 1
- )
- );
- // Deleted revision diffs...
- } elseif ( $key == 'artimestamp' || $key == 'archive' ) {
- $links[] = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Undelete' ),
- $this->msg( 'diff' )->escaped(),
- array(),
- array(
- 'target' => $this->entry->getTarget()->getPrefixedDBkey(),
- 'diff' => 'prev',
- 'timestamp' => $ids[0]
- )
- );
+ case 'delete': // Show undelete link
+ if ( $user->isAllowed( 'undelete' ) ) {
+ $message = 'undeletelink';
+ } else {
+ $message = 'undeleteviewlink';
+ }
+ $revert = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $this->msg( $message )->escaped(),
+ array(),
+ array( 'target' => $this->entry->getTarget()->getPrefixedDBkey() )
+ );
+
+ return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
+
+ case 'revision': // If an edit was hidden from a page give a review link to the history
+ $params = $this->extractParameters();
+ if ( !isset( $params[3] ) || !isset( $params[4] ) ) {
+ return '';
}
- }
-
- // View/modify link...
- $links[] = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Revisiondelete' ),
- $this->msg( 'revdel-restore' )->escaped(),
- array(),
- array(
- 'target' => $this->entry->getTarget()->getPrefixedText(),
- 'type' => $key,
- 'ids' => implode( ',', $ids ),
- )
- );
- return $this->msg( 'parentheses' )->rawParams(
- $this->context->getLanguage()->pipeList( $links ) )->escaped();
+ // Different revision types use different URL params...
+ $key = $params[3];
+ // This is a CSV of the IDs
+ $ids = explode( ',', $params[4] );
+
+ $links = array();
+
+ // If there's only one item, we can show a diff link
+ if ( count( $ids ) == 1 ) {
+ // Live revision diffs...
+ if ( $key == 'oldid' || $key == 'revision' ) {
+ $links[] = Linker::linkKnown(
+ $this->entry->getTarget(),
+ $this->msg( 'diff' )->escaped(),
+ array(),
+ array(
+ 'diff' => intval( $ids[0] ),
+ 'unhide' => 1
+ )
+ );
+ // Deleted revision diffs...
+ } elseif ( $key == 'artimestamp' || $key == 'archive' ) {
+ $links[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $this->msg( 'diff' )->escaped(),
+ array(),
+ array(
+ 'target' => $this->entry->getTarget()->getPrefixedDBkey(),
+ 'diff' => 'prev',
+ 'timestamp' => $ids[0]
+ )
+ );
+ }
+ }
- case 'event': // Hidden log items, give review link
- $params = $this->extractParameters();
- if ( !isset( $params[3] ) ) {
+ // View/modify link...
+ $links[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $this->msg( 'revdel-restore' )->escaped(),
+ array(),
+ array(
+ 'target' => $this->entry->getTarget()->getPrefixedText(),
+ 'type' => $key,
+ 'ids' => implode( ',', $ids ),
+ )
+ );
+
+ return $this->msg( 'parentheses' )->rawParams(
+ $this->context->getLanguage()->pipeList( $links ) )->escaped();
+
+ case 'event': // Hidden log items, give review link
+ $params = $this->extractParameters();
+ if ( !isset( $params[3] ) ) {
+ return '';
+ }
+ // This is a CSV of the IDs
+ $query = $params[3];
+ // Link to each hidden object ID, $params[1] is the url param
+ $revert = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $this->msg( 'revdel-restore' )->escaped(),
+ array(),
+ array(
+ 'target' => $this->entry->getTarget()->getPrefixedText(),
+ 'type' => 'logging',
+ 'ids' => $query
+ )
+ );
+
+ return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
+ default:
return '';
- }
- // This is a CSV of the IDs
- $query = $params[3];
- // Link to each hidden object ID, $params[1] is the url param
- $revert = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Revisiondelete' ),
- $this->msg( 'revdel-restore' )->escaped(),
- array(),
- array(
- 'target' => $this->entry->getTarget()->getPrefixedText(),
- 'type' => 'logging',
- 'ids' => $query
- )
- );
- return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
- default:
- return '';
}
}
}
diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php
index b2a8e50d..46c55157 100644
--- a/includes/logging/LogEntry.php
+++ b/includes/logging/LogEntry.php
@@ -33,7 +33,6 @@
* @since 1.19
*/
interface LogEntry {
-
/**
* The main log type.
* @return string
@@ -89,8 +88,8 @@ interface LogEntry {
public function getDeleted();
/**
- * @param $field Integer: one of LogPage::DELETED_* bitfield constants
- * @return Boolean
+ * @param int $field One of LogPage::DELETED_* bitfield constants
+ * @return bool
*/
public function isDeleted( $field );
}
@@ -100,7 +99,6 @@ interface LogEntry {
* @since 1.19
*/
abstract class LogEntryBase implements LogEntry {
-
public function getFullType() {
return $this->getType() . '/' . $this->getSubtype();
}
@@ -117,7 +115,6 @@ abstract class LogEntryBase implements LogEntry {
public function isLegacy() {
return false;
}
-
}
/**
@@ -160,12 +157,13 @@ class DatabaseLogEntry extends LogEntryBase {
/**
* Constructs new LogEntry from database result row.
* Supports rows from both logging and recentchanges table.
- * @param $row
+ * @param stdClass|array $row
* @return DatabaseLogEntry
*/
public static function newFromRow( $row ) {
- if ( is_array( $row ) && isset( $row['rc_logid'] ) ) {
- return new RCDatabaseLogEntry( (object)$row );
+ $row = (object)$row;
+ if ( isset( $row->rc_logid ) ) {
+ return new RCDatabaseLogEntry( $row );
} else {
return new self( $row );
}
@@ -173,10 +171,17 @@ class DatabaseLogEntry extends LogEntryBase {
// Non-static->
- /// Database result row.
+ /** @var stdClass Database result row. */
protected $row;
+
+ /** @var User */
protected $performer;
+ /** @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;
}
@@ -202,6 +207,7 @@ class DatabaseLogEntry extends LogEntryBase {
public function isLegacy() {
// This does the check
$this->getParameters();
+
return $this->legacy;
}
@@ -229,6 +235,7 @@ class DatabaseLogEntry extends LogEntryBase {
$this->legacy = true;
}
}
+
return $this->params;
}
@@ -246,6 +253,7 @@ class DatabaseLogEntry extends LogEntryBase {
$this->performer = User::newFromName( $userText, false );
}
}
+
return $this->performer;
}
@@ -253,6 +261,7 @@ class DatabaseLogEntry extends LogEntryBase {
$namespace = $this->row->log_namespace;
$page = $this->row->log_title;
$title = Title::makeTitle( $namespace, $page );
+
return $title;
}
@@ -267,7 +276,6 @@ class DatabaseLogEntry extends LogEntryBase {
public function getDeleted() {
return $this->row->log_deleted;
}
-
}
class RCDatabaseLogEntry extends DatabaseLogEntry {
@@ -301,6 +309,7 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
$this->performer = User::newFromName( $userText, false );
}
}
+
return $this->performer;
}
@@ -308,6 +317,7 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
$namespace = $this->row->rc_namespace;
$page = $this->row->rc_title;
$title = Title::makeTitle( $namespace, $page );
+
return $title;
}
@@ -322,7 +332,6 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
public function getDeleted() {
return $this->row->rc_deleted;
}
-
}
/**
@@ -331,15 +340,35 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
* @since 1.19
*/
class ManualLogEntry extends LogEntryBase {
- protected $type; ///!< @var string
- protected $subtype; ///!< @var string
- protected $parameters = array(); ///!< @var array
- protected $relations = array(); ///!< @var array
- protected $performer; ///!< @var User
- protected $target; ///!< @var Title
- protected $timestamp; ///!< @var string
- protected $comment = ''; ///!< @var string
- protected $deleted; ///!< @var int
+ /** @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 = array();
+
+ /** @var array */
+ protected $relations = array();
+
+ /** @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 Deletion state of the log entry */
+ protected $deleted;
+
+ /** @var int ID of the log entry */
+ protected $id;
/**
* Constructor.
@@ -378,7 +407,7 @@ class ManualLogEntry extends LogEntryBase {
* Declare arbitrary tag/value relations to this log entry.
* These can be used to filter log entries later on.
*
- * @param array Map of (tag => (list of values))
+ * @param array $relations Map of (tag => (list of values|value))
* @since 1.22
*/
public function setRelations( array $relations ) {
@@ -434,7 +463,7 @@ class ManualLogEntry extends LogEntryBase {
*
* @since 1.19
*
- * @param integer $deleted
+ * @param int $deleted
*/
public function setDeleted( $deleted ) {
$this->deleted = $deleted;
@@ -443,7 +472,8 @@ class ManualLogEntry extends LogEntryBase {
/**
* Inserts the entry into the logging table.
* @param IDatabase $dbw
- * @return int If of the log entry
+ * @return int ID of the log entry
+ * @throws MWException
*/
public function insert( IDatabase $dbw = null ) {
global $wgContLang;
@@ -474,6 +504,10 @@ class ManualLogEntry extends LogEntryBase {
'log_comment' => $comment,
'log_params' => serialize( (array)$this->getParameters() ),
);
+ if ( isset( $this->deleted ) ) {
+ $data['log_deleted'] = $this->deleted;
+ }
+
$dbw->insert( 'logging', $data, __METHOD__ );
$this->id = !is_null( $id ) ? $id : $dbw->insertId();
@@ -482,10 +516,15 @@ class ManualLogEntry extends LogEntryBase {
if ( !strlen( $tag ) ) {
throw new MWException( "Got empty log search tag." );
}
+
+ if ( !is_array( $values ) ) {
+ $values = array( $values );
+ }
+
foreach ( $values as $value ) {
$rows[] = array(
- 'ls_field' => $tag,
- 'ls_value' => $value,
+ 'ls_field' => $tag,
+ 'ls_value' => $value,
'ls_log_id' => $this->id
);
}
@@ -494,20 +533,20 @@ class ManualLogEntry extends LogEntryBase {
$dbw->insert( 'log_search', $rows, __METHOD__, 'IGNORE' );
}
+ // Update any bloom filter cache
+ $member = $this->getTarget()->getNamespace() . ':' . $this->getTarget()->getDBkey();
+ BloomCache::get( 'main' )->insert( wfWikiId(), 'TitleHasLogs', $member );
+
return $this->id;
}
/**
- * Publishes the log entry.
- * @param int $newId id of the log entry.
- * @param string $to rcandudp (default), rc, udp
+ * Get a RecentChanges object for the log entry
+ * @param int $newId
+ * @return RecentChange
+ * @since 1.23
*/
- public function publish( $newId, $to = 'rcandudp' ) {
- $log = new LogPage( $this->getType() );
- if ( $log->isRestricted() ) {
- return;
- }
-
+ public function getRecentChange( $newId = 0 ) {
$formatter = LogFormatter::newFromEntry( $this );
$context = RequestContext::newExtraneousContext( $this->getTarget() );
$formatter->setContext( $context );
@@ -524,7 +563,8 @@ class ManualLogEntry extends LogEntryBase {
$ip = $user->getName();
}
}
- $rc = RecentChange::newLogEntry(
+
+ return RecentChange::newLogEntry(
$this->getTimestamp(),
$logpage,
$user,
@@ -538,6 +578,20 @@ class ManualLogEntry extends LogEntryBase {
$newId,
$formatter->getIRCActionComment() // Used for IRC feeds
);
+ }
+
+ /**
+ * Publishes 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' ) {
+ $log = new LogPage( $this->getType() );
+ if ( $log->isRestricted() ) {
+ return;
+ }
+
+ $rc = $this->getRecentChange( $newId );
if ( $to === 'rc' || $to === 'rcandudp' ) {
$rc->save( 'pleasedontudp' );
@@ -578,6 +632,7 @@ class ManualLogEntry extends LogEntryBase {
public function getTimestamp() {
$ts = $this->timestamp !== null ? $this->timestamp : wfTimestampNow();
+
return wfTimestamp( TS_MW, $ts );
}
@@ -588,5 +643,4 @@ class ManualLogEntry extends LogEntryBase {
public function getDeleted() {
return (int)$this->deleted;
}
-
}
diff --git a/includes/logging/LogEventsList.php b/includes/logging/LogEventsList.php
index c27b57af..4dc25ef3 100644
--- a/includes/logging/LogEventsList.php
+++ b/includes/logging/LogEventsList.php
@@ -3,7 +3,7 @@
* Contain classes to list log entries
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>, 2008 Aaron Schulz
- * http://www.mediawiki.org/
+ * 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
@@ -31,7 +31,7 @@ class LogEventsList extends ContextSource {
public $flags;
/**
- * @var Array
+ * @var array
*/
protected $mDefaultQuery;
@@ -40,10 +40,11 @@ class LogEventsList extends ContextSource {
* The first two parameters used to be $skin and $out, but now only a context
* is needed, that's why there's a second unused parameter.
*
- * @param $context IContextSource Context to use; formerly it was Skin object.
- * @param $unused void Unused; used to be an OutputPage object.
- * @param int $flags flags; can be a combinaison of self::NO_ACTION_LINK,
- * self::NO_EXTRA_USER_LINKS or self::USE_REVDEL_CHECKBOXES.
+ * @param IContextSource|Skin $context Context to use; formerly it was
+ * a Skin object. Use of Skin is deprecated.
+ * @param null $unused Unused; used to be an OutputPage object.
+ * @param int $flags Can be a combination of self::NO_ACTION_LINK,
+ * self::NO_EXTRA_USER_LINKS or self::USE_REVDEL_CHECKBOXES.
*/
public function __construct( $context, $unused = null, $flags = 0 ) {
if ( $context instanceof IContextSource ) {
@@ -59,46 +60,29 @@ class LogEventsList extends ContextSource {
/**
* Deprecated alias for getTitle(); do not use.
*
- * @deprecated in 1.20; use getTitle() instead.
- * @return Title object
+ * @deprecated since 1.20; use getTitle() instead.
+ * @return Title
*/
public function getDisplayTitle() {
+ wfDeprecated( __METHOD__, '1.20' );
return $this->getTitle();
}
/**
- * Set page title and show header for this log type
- * @param $type Array
- * @deprecated in 1.19
- */
- public function showHeader( $type ) {
- wfDeprecated( __METHOD__, '1.19' );
- // If only one log type is used, then show a special message...
- $headerType = count( $type ) == 1 ? $type[0] : '';
- $out = $this->getOutput();
- if ( LogPage::isLogType( $headerType ) ) {
- $page = new LogPage( $headerType );
- $out->setPageTitle( $page->getName()->text() );
- $out->addHTML( $page->getDescription()->parseAsBlock() );
- } else {
- $out->addHTML( $this->msg( 'alllogstext' )->parse() );
- }
- }
-
- /**
* Show options for the log list
*
- * @param string $types or Array
- * @param $user String
- * @param $page String
- * @param $pattern String
- * @param $year Integer: year
- * @param $month Integer: month
- * @param $filter: array
- * @param $tagFilter: array?
+ * @param array|string $types
+ * @param string $user
+ * @param string $page
+ * @param string $pattern
+ * @param int $year Year
+ * @param int $month Month
+ * @param array $filter
+ * @param string $tagFilter Tag to select by default
*/
- public function showOptions( $types = array(), $user = '', $page = '', $pattern = '', $year = '',
- $month = '', $filter = null, $tagFilter = '' ) {
+ public function showOptions( $types = array(), $user = '', $page = '', $pattern = '', $year = 0,
+ $month = 0, $filter = null, $tagFilter = ''
+ ) {
global $wgScript, $wgMiserMode;
$title = SpecialPage::getTitleFor( 'Log' );
@@ -122,7 +106,7 @@ class LogEventsList extends ContextSource {
}
// date menu
- $html .= Xml::tags( 'p', null, Xml::dateMenu( $year, $month ) );
+ $html .= Xml::tags( 'p', null, Xml::dateMenu( (int)$year, (int)$month ) );
// Tag filter
if ( $tagSelector ) {
@@ -147,8 +131,8 @@ class LogEventsList extends ContextSource {
}
/**
- * @param $filter Array
- * @return String: Formatted HTML
+ * @param array $filter
+ * @return string Formatted HTML
*/
private function getFilterLinks( $filter ) {
// show/hide links
@@ -176,6 +160,7 @@ class LogEventsList extends ContextSource {
$links[$type] = $this->msg( "log-show-hide-{$type}" )->rawParams( $link )->escaped();
$hiddens .= Html::hidden( "hide_{$type}_log", $val ) . "\n";
}
+
// Build links
return '<small>' . $this->getLanguage()->pipeList( $links ) . '</small>' . $hiddens;
}
@@ -191,17 +176,19 @@ class LogEventsList extends ContextSource {
unset( $this->mDefaultQuery['month'] );
unset( $this->mDefaultQuery['year'] );
}
+
return $this->mDefaultQuery;
}
/**
- * @param $queryTypes Array
- * @return String: Formatted HTML
+ * @param array $queryTypes
+ * @return string Formatted HTML
*/
private function getTypeMenu( $queryTypes ) {
$queryType = count( $queryTypes ) == 1 ? $queryTypes[0] : '';
$selector = $this->getTypeSelector();
$selector->setDefault( $queryType );
+
return $selector->getHtml();
}
@@ -238,27 +225,39 @@ class LogEventsList extends ContextSource {
}
/**
- * @param $user String
- * @return String: Formatted HTML
+ * @param string $user
+ * @return string Formatted HTML
*/
private function getUserInput( $user ) {
- return '<span style="white-space: nowrap">' .
- Xml::inputLabel( $this->msg( 'specialloguserlabel' )->text(), 'user', 'mw-log-user', 15, $user ) .
- '</span>';
+ $label = Xml::inputLabel(
+ $this->msg( 'specialloguserlabel' )->text(),
+ 'user',
+ 'mw-log-user',
+ 15,
+ $user
+ );
+
+ return '<span style="white-space: nowrap">' . $label . '</span>';
}
/**
- * @param $title String
- * @return String: Formatted HTML
+ * @param string $title
+ * @return string Formatted HTML
*/
private function getTitleInput( $title ) {
- return '<span style="white-space: nowrap">' .
- Xml::inputLabel( $this->msg( 'speciallogtitlelabel' )->text(), 'page', 'mw-log-page', 20, $title ) .
- '</span>';
+ $label = Xml::inputLabel(
+ $this->msg( 'speciallogtitlelabel' )->text(),
+ 'page',
+ 'mw-log-page',
+ 20,
+ $title
+ );
+
+ return '<span style="white-space: nowrap">' . $label . '</span>';
}
/**
- * @param $pattern
+ * @param string $pattern
* @return string Checkbox
*/
private function getTitlePattern( $pattern ) {
@@ -268,7 +267,7 @@ class LogEventsList extends ContextSource {
}
/**
- * @param $types
+ * @param array $types
* @return string
*/
private function getExtraInputs( $types ) {
@@ -281,6 +280,7 @@ class LogEventsList extends ContextSource {
return Xml::inputLabel( $this->msg( 'revdelete-offender' )->text(), 'offender',
'mw-log-offender', 20, $offender );
}
+
return '';
}
@@ -299,8 +299,8 @@ class LogEventsList extends ContextSource {
}
/**
- * @param $row Row: a single row from the result set
- * @return String: Formatted HTML list item
+ * @param stdClass $row A single row from the result set
+ * @return string Formatted HTML list item
*/
public function logLine( $row ) {
$entry = DatabaseLogEntry::newFromRow( $row );
@@ -339,12 +339,15 @@ class LogEventsList extends ContextSource {
}
/**
- * @param $row Row
+ * @param stdClass $row Row
* @return string
*/
private function getShowHideLinks( $row ) {
- if ( ( $this->flags == self::NO_ACTION_LINK ) // we don't want to see the links
- || $row->log_type == 'suppress' ) { // no one can hide items from the suppress log
+ // We don't want to see the links and
+ // no one can hide items from the suppress log.
+ if ( ( $this->flags == self::NO_ACTION_LINK )
+ || $row->log_type == 'suppress'
+ ) {
return '';
}
$del = '';
@@ -352,15 +355,26 @@ class LogEventsList extends ContextSource {
// Don't show useless checkbox to people who cannot hide log entries
if ( $user->isAllowed( 'deletedhistory' ) ) {
$canHide = $user->isAllowed( 'deletelogentry' );
+ $canViewSuppressedOnly = $user->isAllowed( 'viewsuppressed' ) &&
+ !$user->isAllowed( 'suppressrevision' );
+ $entryIsSuppressed = self::isDeleted( $row, LogPage::DELETED_RESTRICTED );
+ $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
if ( $row->log_deleted || $canHide ) {
- if ( $canHide && $this->flags & self::USE_REVDEL_CHECKBOXES ) { // Show checkboxes instead of links.
- if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) { // If event was hidden from sysops
+ // Show checkboxes instead of links.
+ if ( $canHide && $this->flags & self::USE_REVDEL_CHECKBOXES && !$canViewThisSuppressedEntry ) {
+ // If event was hidden from sysops
+ if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
$del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
} else {
- $del = Xml::check( 'showhiderevisions', false, array( 'name' => 'ids[' . $row->log_id . ']' ) );
+ $del = Xml::check(
+ 'showhiderevisions',
+ false,
+ array( 'name' => 'ids[' . $row->log_id . ']' )
+ );
}
} else {
- if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) { // If event was hidden from sysops
+ // If event was hidden from sysops
+ if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
$del = Linker::revDeleteLinkDisabled( $canHide );
} else {
$query = array(
@@ -368,20 +382,25 @@ class LogEventsList extends ContextSource {
'type' => 'logging',
'ids' => $row->log_id,
);
- $del = Linker::revDeleteLink( $query, self::isDeleted( $row, LogPage::DELETED_RESTRICTED ), $canHide );
+ $del = Linker::revDeleteLink(
+ $query,
+ $entryIsSuppressed,
+ $canHide && !$canViewThisSuppressedEntry
+ );
}
}
}
}
+
return $del;
}
/**
- * @param $row Row
- * @param $type Mixed: string/array
- * @param $action Mixed: string/array
- * @param $right string
- * @return Boolean
+ * @param stdClass $row Row
+ * @param string|array $type
+ * @param string|array $action
+ * @param string $right
+ * @return bool
*/
public static function typeAction( $row, $type, $action, $right = '' ) {
$match = is_array( $type ) ?
@@ -394,6 +413,7 @@ class LogEventsList extends ContextSource {
$match = $wgUser->isAllowed( $right );
}
}
+
return $match;
}
@@ -401,10 +421,10 @@ class LogEventsList extends ContextSource {
* Determine if the current user is allowed to view a particular
* field of this log row, if it's marked as deleted.
*
- * @param $row Row
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
- * @return Boolean
+ * @param stdClass $row Row
+ * @param int $field
+ * @param User $user User to check, or null to use $wgUser
+ * @return bool
*/
public static function userCan( $row, $field, User $user = null ) {
return self::userCanBitfield( $row->log_deleted, $field, $user );
@@ -414,33 +434,33 @@ class LogEventsList extends ContextSource {
* Determine if the current user is allowed to view a particular
* field of this log row, if it's marked as deleted.
*
- * @param $bitfield Integer (current field)
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
- * @return Boolean
+ * @param int $bitfield Current field
+ * @param int $field
+ * @param User $user User to check, or null to use $wgUser
+ * @return bool
*/
public static function userCanBitfield( $bitfield, $field, User $user = null ) {
if ( $bitfield & $field ) {
- if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
- $permission = 'suppressrevision';
- } else {
- $permission = 'deletedhistory';
- }
- wfDebug( "Checking for $permission due to $field match on $bitfield\n" );
if ( $user === null ) {
global $wgUser;
$user = $wgUser;
}
- return $user->isAllowed( $permission );
- } else {
- return true;
+ if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
+ $permissions = array( 'suppressrevision', 'viewsuppressed' );
+ } else {
+ $permissions = array( 'deletedhistory' );
+ }
+ $permissionlist = implode( ', ', $permissions );
+ wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
+ return call_user_func_array( array( $user, 'isAllowedAny' ), $permissions );
}
+ return true;
}
/**
- * @param $row Row
- * @param $field Integer: one of DELETED_* bitfield constants
- * @return Boolean
+ * @param stdClass $row Row
+ * @param int $field One of DELETED_* bitfield constants
+ * @return bool
*/
public static function isDeleted( $row, $field ) {
return ( $row->log_deleted & $field ) == $field;
@@ -449,7 +469,7 @@ class LogEventsList extends ContextSource {
/**
* Show log extract. Either with text and a box (set $msgKey) or without (don't set $msgKey)
*
- * @param $out OutputPage|String-by-reference
+ * @param OutputPage|string $out By-reference
* @param string|array $types Log types to show
* @param string|Title $page The page title to show log entries for
* @param string $user The user who made the log entries
@@ -466,7 +486,8 @@ class LogEventsList extends ContextSource {
* - wrap String Wrap the message in html (usually something like "<div ...>$1</div>").
* - flags Integer display flags (NO_ACTION_LINK,NO_EXTRA_USER_LINKS)
* - useRequestParams boolean Set true to use Pager-related parameters in the WebRequest
- * @return Integer Number of total log items (not limited by $lim)
+ * - useMaster boolean Use master DB
+ * @return int Number of total log items (not limited by $lim)
*/
public static function showLogExtract(
&$out, $types = array(), $page = '', $user = '', $param = array()
@@ -479,6 +500,7 @@ class LogEventsList extends ContextSource {
'wrap' => "$1",
'flags' => 0,
'useRequestParams' => false,
+ 'useMaster' => false,
);
# The + operator appends elements of remaining keys from the right
# handed array to the left handed, whereas duplicated keys are NOT overwritten.
@@ -511,17 +533,31 @@ class LogEventsList extends ContextSource {
$pager->mOffset = "";
$pager->mIsBackwards = false;
}
+
+ if ( $param['useMaster'] ) {
+ $pager->mDb = wfGetDB( DB_MASTER );
+ }
if ( isset( $param['offset'] ) ) { # Tell pager to ignore WebRequest offset
$pager->setOffset( $param['offset'] );
}
+
if ( $lim > 0 ) {
$pager->mLimit = $lim;
}
+
$logBody = $pager->getBody();
$s = '';
+
if ( $logBody ) {
if ( $msgKey[0] ) {
- $s = '<div class="mw-warning-with-logexcerpt">';
+ $dir = $context->getLanguage()->getDir();
+ $lang = $context->getLanguage()->getCode();
+
+ $s = Xml::openElement( 'div', array(
+ 'class' => "mw-warning-with-logexcerpt mw-content-$dir",
+ 'dir' => $dir,
+ 'lang' => $lang,
+ ) );
if ( count( $msgKey ) == 1 ) {
$s .= $context->msg( $msgKey[0] )->parseAsBlock();
@@ -538,6 +574,7 @@ class LogEventsList extends ContextSource {
$s = Html::rawElement( 'div', array( 'class' => 'mw-warning-logempty' ),
$context->msg( 'logempty' )->parse() );
}
+
if ( $pager->getNumRows() > $pager->mLimit ) { # Show "Full log" link
$urlParam = array();
if ( $page instanceof Title ) {
@@ -545,16 +582,20 @@ class LogEventsList extends ContextSource {
} elseif ( $page != '' ) {
$urlParam['page'] = $page;
}
+
if ( $user != '' ) {
$urlParam['user'] = $user;
}
+
if ( !is_array( $types ) ) { # Make it an array, if it isn't
$types = array( $types );
}
+
# If there is exactly one log type, we can link to Special:Log?type=foo
if ( count( $types ) == 1 ) {
$urlParam['type'] = $types[0];
}
+
$s .= Linker::link(
SpecialPage::getTitleFor( 'Log' ),
$context->msg( 'log-fulllog' )->escaped(),
@@ -562,6 +603,7 @@ class LogEventsList extends ContextSource {
$urlParam
);
}
+
if ( $logBody && $msgKey[0] ) {
$s .= '</div>';
}
@@ -586,10 +628,10 @@ class LogEventsList extends ContextSource {
/**
* SQL clause to skip forbidden log types for this user
*
- * @param $db DatabaseBase
- * @param $audience string, public/user
- * @param $user User object to check, or null to use $wgUser
- * @return Mixed: string or false
+ * @param DatabaseBase $db
+ * @param string $audience Public/user
+ * @param User $user User to check, or null to use $wgUser
+ * @return string|bool String on success, false on failure.
*/
public static function getExcludeClause( $db, $audience = 'public', User $user = null ) {
global $wgLogRestrictions;
@@ -613,6 +655,7 @@ class LogEventsList extends ContextSource {
} elseif ( $hiddenLogs ) {
return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) . ')';
}
+
return false;
}
}
diff --git a/includes/logging/LogFormatter.php b/includes/logging/LogFormatter.php
index 8f60aee1..48a565f2 100644
--- a/includes/logging/LogFormatter.php
+++ b/includes/logging/LogFormatter.php
@@ -39,7 +39,7 @@ class LogFormatter {
/**
* Constructs a new formatter suitable for given entry.
- * @param $entry LogEntry
+ * @param LogEntry $entry
* @return LogFormatter
*/
public static function newFromEntry( LogEntry $entry ) {
@@ -64,7 +64,7 @@ class LogFormatter {
/**
* Handy shortcut for constructing a formatter directly from
* database row.
- * @param $row
+ * @param object $row
* @see DatabaseLogEntry::getSelectQueryData
* @return LogFormatter
*/
@@ -74,13 +74,13 @@ class LogFormatter {
// Nonstatic->
- /// @var LogEntry
+ /** @var LogEntryBase */
protected $entry;
- /// Integer constant for handling log_deleted
+ /** @var int Constant for handling log_deleted */
protected $audience = self::FOR_PUBLIC;
- /// Whether to output user tool links
+ /** @var bool Whether to output user tool links */
protected $linkFlood = false;
/**
@@ -88,10 +88,11 @@ class LogFormatter {
* be included in page history or send to IRC feed. Links are replaced
* with plaintext or with [[pagename]] kind of syntax, that is parsed
* by page histories and IRC feeds.
- * @var boolean
+ * @var string
*/
protected $plaintext = false;
+ /** @var string */
protected $irctext = false;
protected function __construct( LogEntry $entry ) {
@@ -101,7 +102,7 @@ class LogFormatter {
/**
* Replace the default context
- * @param $context IContextSource
+ * @param IContextSource $context
*/
public function setContext( IContextSource $context ) {
$this->context = $context;
@@ -111,7 +112,7 @@ class LogFormatter {
* Set the visibility restrictions for displaying content.
* If set to public, and an item is deleted, then it will be replaced
* with a placeholder even if the context user is allowed to view it.
- * @param $audience integer self::FOR_THIS_USER or self::FOR_PUBLIC
+ * @param int $audience Const self::FOR_THIS_USER or self::FOR_PUBLIC
*/
public function setAudience( $audience ) {
$this->audience = ( $audience == self::FOR_THIS_USER )
@@ -121,7 +122,7 @@ class LogFormatter {
/**
* Check if a log item can be displayed
- * @param $field integer LogPage::DELETED_* constant
+ * @param int $field LogPage::DELETED_* constant
* @return bool
*/
protected function canView( $field ) {
@@ -137,7 +138,7 @@ class LogFormatter {
* If set to true, will produce user tool links after
* the user name. This should be replaced with generic
* CSS/JS solution.
- * @param $value boolean
+ * @param bool $value
*/
public function setShowUserToolLinks( $value ) {
$this->linkFlood = $value;
@@ -148,12 +149,13 @@ class LogFormatter {
* Usually you also want to set extraneous request context
* to avoid formatting for any particular user.
* @see getActionText()
- * @return string text
+ * @return string Plain text
*/
public function getPlainActionText() {
$this->plaintext = true;
$text = $this->getActionText();
$this->plaintext = false;
+
return $text;
}
@@ -161,7 +163,7 @@ class LogFormatter {
* Even uglier hack to maintain backwards compatibilty with IRC bots
* (bug 34508).
* @see getActionText()
- * @return string text
+ * @return string Text
*/
public function getIRCActionComment() {
$actionComment = $this->getIRCActionText();
@@ -182,7 +184,7 @@ class LogFormatter {
* Even uglier hack to maintain backwards compatibilty with IRC bots
* (bug 34508).
* @see getActionText()
- * @return string text
+ * @return string Text
*/
public function getIRCActionText() {
$this->plaintext = true;
@@ -224,15 +226,19 @@ class LogFormatter {
$text = wfMessage( 'undeletedarticle' )
->rawParams( $target )->inContentLanguage()->escaped();
break;
+ // @codingStandardsIgnoreStart Long line
//case 'revision': // Revision deletion
//case 'event': // Log deletion
- // see https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/LogPage.php?&pathrev=97044&r1=97043&r2=97044
+ // see https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/LogPage.php?&pathrev=97044&r1=97043&r2=97044
//default:
+ // @codingStandardsIgnoreEnd
}
break;
case 'patrol':
+ // @codingStandardsIgnoreStart Long line
// https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/PatrolLog.php?&pathrev=97495&r1=97494&r2=97495
+ // @codingStandardsIgnoreEnd
// Create a diff link to the patrolled revision
if ( $entry->getSubtype() === 'patrol' ) {
$diffLink = htmlspecialchars(
@@ -247,18 +253,18 @@ class LogFormatter {
case 'protect':
switch ( $entry->getSubtype() ) {
- case 'protect':
- $text = wfMessage( 'protectedarticle' )
- ->rawParams( $target . ' ' . $parameters[0] )->inContentLanguage()->escaped();
- break;
- case 'unprotect':
- $text = wfMessage( 'unprotectedarticle' )
- ->rawParams( $target )->inContentLanguage()->escaped();
- break;
- case 'modify':
- $text = wfMessage( 'modifiedarticleprotection' )
- ->rawParams( $target . ' ' . $parameters[0] )->inContentLanguage()->escaped();
- break;
+ case 'protect':
+ $text = wfMessage( 'protectedarticle' )
+ ->rawParams( $target . ' ' . $parameters[0] )->inContentLanguage()->escaped();
+ break;
+ case 'unprotect':
+ $text = wfMessage( 'unprotectedarticle' )
+ ->rawParams( $target )->inContentLanguage()->escaped();
+ break;
+ case 'modify':
+ $text = wfMessage( 'modifiedarticleprotection' )
+ ->rawParams( $target . ' ' . $parameters[0] )->inContentLanguage()->escaped();
+ break;
}
break;
@@ -316,8 +322,7 @@ class LogFormatter {
break;
}
break;
-
- // case 'suppress' --private log -- aaron (sign your messages so we know who to blame in a few years :-D)
+ // case 'suppress' --private log -- aaron (so we know who to blame in a few years :-D)
// default:
}
if ( is_null( $text ) ) {
@@ -326,6 +331,7 @@ class LogFormatter {
$this->plaintext = false;
$this->irctext = false;
+
return $text;
}
@@ -353,12 +359,13 @@ class LogFormatter {
/**
* Returns a sentence describing the log action. Usually
* a Message object is returned, but old style log types
- * and entries might return pre-escaped html string.
- * @return Message|string pre-escaped html
+ * and entries might return pre-escaped HTML string.
+ * @return Message|string Pre-escaped HTML
*/
protected function getActionMessage() {
$message = $this->msg( $this->getMessageKey() );
$message->params( $this->getMessageParameters() );
+
return $message;
}
@@ -367,7 +374,7 @@ class LogFormatter {
* Default is logentry-TYPE-SUBTYPE for modern logs. Legacy log
* types will use custom keys, and subclasses can also alter the
* key depending on the entry itself.
- * @return string message key
+ * @return string Message key
*/
protected function getMessageKey() {
$type = $this->entry->getType();
@@ -421,6 +428,7 @@ class LogFormatter {
}
}
}
+
return $params;
}
@@ -446,7 +454,9 @@ class LogFormatter {
// Bad things happens if the numbers are not in correct order
ksort( $params );
- return $this->parsedParameters = $params;
+
+ $this->parsedParameters = $params;
+ return $this->parsedParameters;
}
/**
@@ -472,8 +482,7 @@ class LogFormatter {
* * number: Format value as number
* @param string $value The parameter value that should
* be formated
- * @return string or Message::numParam or Message::rawParam
- * Formated value
+ * @return string|array Formated value
* @since 1.21
*/
protected function formatParameterValue( $type, $value ) {
@@ -524,10 +533,10 @@ class LogFormatter {
/**
* Helper to make a link to the page, taking the plaintext
* value in consideration.
- * @param $title Title the page
- * @param array $parameters query parameters
+ * @param Title $title The page
+ * @param array $parameters Query parameters
* @throws MWException
- * @return String
+ * @return string
*/
protected function makePageLink( Title $title = null, $parameters = array() ) {
if ( !$this->plaintext ) {
@@ -538,6 +547,7 @@ class LogFormatter {
}
$link = '[[' . $title->getPrefixedText() . ']]';
}
+
return $link;
}
@@ -545,7 +555,7 @@ class LogFormatter {
* Provides the name of the user who performed the log action.
* Used as part of log action message or standalone, depending
* which parts of the log entry has been hidden.
- * @return String
+ * @return string
*/
public function getPerformerElement() {
if ( $this->canView( LogPage::DELETED_USER ) ) {
@@ -562,7 +572,7 @@ class LogFormatter {
}
/**
- * Gets the luser provided comment
+ * Gets the user provided comment
* @return string HTML
*/
public function getComment() {
@@ -582,8 +592,8 @@ class LogFormatter {
/**
* Helper method for displaying restricted element.
- * @param $message string
- * @return string HTML or wikitext
+ * @param string $message
+ * @return string HTML or wiki text
*/
protected function getRestrictedElement( $message ) {
if ( $this->plaintext ) {
@@ -592,26 +602,27 @@ class LogFormatter {
$content = $this->msg( $message )->escaped();
$attribs = array( 'class' => 'history-deleted' );
+
return Html::rawElement( 'span', $attribs, $content );
}
/**
* Helper method for styling restricted element.
- * @param $content string
- * @return string HTML or wikitext
+ * @param string $content
+ * @return string HTML or wiki text
*/
protected function styleRestricedElement( $content ) {
if ( $this->plaintext ) {
return $content;
}
$attribs = array( 'class' => 'history-deleted' );
+
return Html::rawElement( 'span', $attribs, $content );
}
/**
* Shortcut for wfMessage which honors local context.
- * @todo Would it be better to require replacing the global context instead?
- * @param $key string
+ * @param string $key
* @return Message
*/
protected function msg( $key ) {
@@ -635,18 +646,19 @@ class LogFormatter {
);
}
}
+
return $element;
}
/**
- * @return Array of titles that should be preloaded with LinkBatch.
+ * @return array Array of titles that should be preloaded with LinkBatch
*/
public function getPreloadTitles() {
return array();
}
/**
- * @return Output of getMessageParameters() for testing
+ * @return array Output of getMessageParameters() for testing
*/
public function getMessageParametersForTesting() {
// This function was added because getMessageParameters() is
@@ -654,7 +666,6 @@ class LogFormatter {
// problems with extensions
return $this->getMessageParameters();
}
-
}
/**
@@ -667,7 +678,6 @@ class LogFormatter {
* @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(),
@@ -727,7 +737,8 @@ class LegacyLogFormatter extends LogFormatter {
}
if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) {
- return $this->revert = '';
+ $this->revert = '';
+ return $this->revert;
}
$title = $this->entry->getTarget();
@@ -735,7 +746,9 @@ class LegacyLogFormatter extends LogFormatter {
$subtype = $this->entry->getSubtype();
// Show unblock/change block link
- if ( ( $type == 'block' || $type == 'suppress' ) && ( $subtype == 'block' || $subtype == 'reblock' ) ) {
+ if ( ( $type == 'block' || $type == 'suppress' )
+ && ( $subtype == 'block' || $subtype == 'reblock' )
+ ) {
if ( !$this->context->getUser()->isAllowed( 'block' ) ) {
return '';
}
@@ -750,10 +763,13 @@ class LegacyLogFormatter extends LogFormatter {
$this->msg( 'change-blocklink' )->escaped()
)
);
+
return $this->msg( 'parentheses' )->rawParams(
$this->context->getLanguage()->pipeList( $links ) )->escaped();
// Show change protection link
- } elseif ( $type == 'protect' && ( $subtype == 'protect' || $subtype == 'modify' || $subtype == 'unprotect' ) ) {
+ } elseif ( $type == 'protect'
+ && ( $subtype == 'protect' || $subtype == 'modify' || $subtype == 'unprotect' )
+ ) {
$links = array(
Linker::link( $title,
$this->msg( 'hist' )->escaped(),
@@ -772,6 +788,7 @@ class LegacyLogFormatter extends LogFormatter {
array( 'action' => 'protect' )
);
}
+
return $this->msg( 'parentheses' )->rawParams(
$this->context->getLanguage()->pipeList( $links ) )->escaped();
// Show unmerge link
@@ -788,9 +805,11 @@ class LegacyLogFormatter extends LogFormatter {
array(
'target' => $params[3],
'dest' => $title->getPrefixedDBkey(),
- 'mergepoint' => $params[4]
+ 'mergepoint' => $params[4],
+ 'submitted' => 1 // show the revisions immediately
)
);
+
return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
}
@@ -813,4 +832,3 @@ class LegacyLogFormatter extends LogFormatter {
return $this->revert;
}
}
-
diff --git a/includes/logging/LogPage.php b/includes/logging/LogPage.php
index cc473c18..ce5b972f 100644
--- a/includes/logging/LogPage.php
+++ b/includes/logging/LogPage.php
@@ -3,7 +3,7 @@
* Contain log classes
*
* Copyright © 2002, 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -34,32 +34,51 @@ class LogPage {
const DELETED_COMMENT = 2;
const DELETED_USER = 4;
const DELETED_RESTRICTED = 8;
+
// Convenience fields
const SUPPRESSED_USER = 12;
const SUPPRESSED_ACTION = 9;
- /* @access private */
- var $type, $action, $comment, $params;
- /**
- * @var User
- */
- var $doer;
+ /** @var bool */
+ public $updateRecentChanges;
- /**
- * @var Title
+ /** @var bool */
+ public $sendToUDP;
+
+ /** @var string Plaintext version of the message for IRC */
+ private $ircActionText;
+
+ /** @var string Plaintext version of the message */
+ private $actionText;
+
+ /** @var string One of '', 'block', 'protect', 'rights', 'delete',
+ * 'upload', 'move'
*/
- var $target;
+ private $type;
+
+ /** @var string One of '', 'block', 'protect', 'rights', 'delete',
+ * 'upload', 'move', 'move_redir' */
+ private $action;
- /* @access public */
- var $updateRecentChanges, $sendToUDP;
+ /** @var string Comment associated with action */
+ private $comment;
+
+ /** @var string Blob made of a parameters array */
+ private $params;
+
+ /** @var User The user doing the action */
+ private $doer;
+
+ /** @var Title */
+ private $target;
/**
* Constructor
*
- * @param string $type one of '', 'block', 'protect', 'rights', 'delete',
- * 'upload', 'move'
- * @param $rc Boolean: whether to update recent changes as well as the logging table
- * @param string $udp pass 'UDP' to send to the UDP feed if NOT sent to RC
+ * @param string $type One of '', 'block', 'protect', 'rights', 'delete',
+ * 'upload', 'move'
+ * @param bool $rc Whether to update recent changes as well as the logging table
+ * @param string $udp Pass 'UDP' to send to the UDP feed if NOT sent to RC
*/
public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
$this->type = $type;
@@ -68,7 +87,7 @@ class LogPage {
}
/**
- * @return int log_id of the inserted log entry
+ * @return int The log_id of the inserted log entry
*/
protected function saveContent() {
global $wgLogRestrictions;
@@ -76,6 +95,7 @@ class LogPage {
$dbw = wfGetDB( DB_MASTER );
$log_id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
+ // @todo FIXME private/protected/public property?
$this->timestamp = $now = wfTimestampNow();
$data = array(
'log_id' => $log_id,
@@ -116,8 +136,9 @@ class LogPage {
$this->type, $this->action, $this->target, $this->comment,
$this->params, $newId, $this->getRcCommentIRC()
);
- $rc->notifyRC2UDP();
+ $rc->notifyRCFeeds();
}
+
return $newId;
}
@@ -163,6 +184,7 @@ class LogPage {
/**
* Get the comment from the last addEntry() call
+ * @return string
*/
public function getComment() {
return $this->comment;
@@ -171,18 +193,19 @@ class LogPage {
/**
* Get the list of valid log types
*
- * @return Array of strings
+ * @return array Array of strings
*/
public static function validTypes() {
global $wgLogTypes;
+
return $wgLogTypes;
}
/**
* Is $type a valid log type
*
- * @param string $type log type to check
- * @return Boolean
+ * @param string $type Log type to check
+ * @return bool
*/
public static function isLogType( $type ) {
return in_array( $type, LogPage::validTypes() );
@@ -191,13 +214,15 @@ class LogPage {
/**
* Get the name for the given log type
*
- * @param string $type logtype
- * @return String: log name
- * @deprecated in 1.19, warnings in 1.21. Use getName()
+ * @param string $type Log type
+ * @return string Log name
+ * @deprecated since 1.19, warnings in 1.21. Use getName()
*/
public static function logName( $type ) {
global $wgLogNames;
+ wfDeprecated( __METHOD__, '1.21' );
+
if ( isset( $wgLogNames[$type] ) ) {
return str_replace( '_', ' ', wfMessage( $wgLogNames[$type] )->text() );
} else {
@@ -210,12 +235,15 @@ class LogPage {
* Get the log header for the given log type
*
* @todo handle missing log types
- * @param string $type logtype
- * @return String: headertext of this logtype
- * @deprecated in 1.19, warnings in 1.21. Use getDescription()
+ * @param string $type Logtype
+ * @return string Header text of this logtype
+ * @deprecated since 1.19, warnings in 1.21. Use getDescription()
*/
public static function logHeader( $type ) {
global $wgLogHeaders;
+
+ wfDeprecated( __METHOD__, '1.21' );
+
return wfMessage( $wgLogHeaders[$type] )->parse();
}
@@ -223,18 +251,18 @@ class LogPage {
* Generate text for a log entry.
* Only LogFormatter should call this function.
*
- * @param string $type log type
- * @param string $action log action
- * @param $title Mixed: Title object or null
- * @param $skin Mixed: Skin object or null. If null, we want to use the wiki
- * content language, since that will go to the IRC feed.
- * @param array $params parameters
- * @param $filterWikilinks Boolean: whether to filter wiki links
- * @return HTML string
+ * @param string $type Log type
+ * @param string $action Log action
+ * @param Title|null $title Title object or null
+ * @param Skin|null $skin Skin object or null. If null, we want to use the wiki
+ * content language, since that will go to the IRC feed.
+ * @param array $params Parameters
+ * @param bool $filterWikilinks Whether to filter wiki links
+ * @return string HTML
*/
public static function actionText( $type, $action, $title = null, $skin = null,
- $params = array(), $filterWikilinks = false )
- {
+ $params = array(), $filterWikilinks = false
+ ) {
global $wgLang, $wgContLang, $wgLogActions;
if ( is_null( $skin ) ) {
@@ -254,7 +282,8 @@ class LogPage {
$titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
if ( count( $params ) == 0 ) {
- $rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )->inLanguage( $langObj )->escaped();
+ $rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )
+ ->inLanguage( $langObj )->escaped();
} else {
$details = '';
array_unshift( $params, $titleLink );
@@ -262,7 +291,12 @@ class LogPage {
// User suppression
if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
if ( $skin ) {
- $params[1] = '<span class="blockExpiry" title="&lrm;' . htmlspecialchars( $params[1] ) . '">' .
+ // Localize the duration, and add a tooltip
+ // in English to help visitors from other wikis.
+ // The lrm is needed to make sure that the number
+ // is shown on the correct side of the tooltip text.
+ $durationTooltip = '&lrm;' . htmlspecialchars( $params[1] );
+ $params[1] = "<span class='blockExpiry' title='$durationTooltip'>" .
$wgLang->translateBlockExpiry( $params[1] ) . '</span>';
} else {
$params[1] = $wgContLang->translateBlockExpiry( $params[1] );
@@ -281,11 +315,16 @@ class LogPage {
// Cascading flag...
if ( $params[2] ) {
- $details .= ' [' . wfMessage( 'protect-summary-cascade' )->inLanguage( $langObj )->text() . ']';
+ $text = wfMessage( 'protect-summary-cascade' )
+ ->inLanguage( $langObj )->text();
+ $details .= ' ';
+ $details .= wfMessage( 'brackets', $text )->inLanguage( $langObj )->text();
+
}
}
- $rv = wfMessage( $wgLogActions[$key] )->rawParams( $params )->inLanguage( $langObj )->escaped() . $details;
+ $rv = wfMessage( $wgLogActions[$key] )->rawParams( $params )
+ ->inLanguage( $langObj )->escaped() . $details;
}
}
} else {
@@ -319,12 +358,12 @@ class LogPage {
}
/**
- * TODO document
- * @param $type String
- * @param $lang Language or null
- * @param $title Title
- * @param $params Array
- * @return String
+ * @todo Document
+ * @param string $type
+ * @param Language|null $lang
+ * @param Title $title
+ * @param array $params
+ * @return string
*/
protected static function getTitleLink( $type, $lang, $title, &$params ) {
if ( !$lang ) {
@@ -402,13 +441,14 @@ class LogPage {
/**
* Add a log entry
*
- * @param string $action one of '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'move_redir'
- * @param $target Title object
- * @param string $comment description associated
- * @param array $params parameters passed later to wfMessage function
- * @param $doer User object: the user doing the action
+ * @param string $action One of '', 'block', 'protect', 'rights', 'delete',
+ * 'upload', 'move', 'move_redir'
+ * @param Title $target Title object
+ * @param string $comment Description associated
+ * @param array $params Parameters passed later to wfMessage function
+ * @param null|int|User $doer The user doing the action. null for $wgUser
*
- * @return int log_id of the inserted log entry
+ * @return int The log_id of the inserted log entry
*/
public function addEntry( $action, $target, $comment, $params = array(), $doer = null ) {
global $wgContLang;
@@ -459,10 +499,10 @@ class LogPage {
/**
* Add relations to log_search table
*
- * @param $field String
- * @param $values Array
- * @param $logid Integer
- * @return Boolean
+ * @param string $field
+ * @param array $values
+ * @param int $logid
+ * @return bool
*/
public function addRelations( $field, $values, $logid ) {
if ( !strlen( $field ) || empty( $values ) ) {
@@ -488,8 +528,8 @@ class LogPage {
/**
* Create a blob from a parameter array
*
- * @param $params Array
- * @return String
+ * @param array $params
+ * @return string
*/
public static function makeParamBlob( $params ) {
return implode( "\n", $params );
@@ -498,8 +538,8 @@ class LogPage {
/**
* Extract a parameter array from a blob
*
- * @param $blob String
- * @return Array
+ * @param string $blob
+ * @return array
*/
public static function extractParams( $blob ) {
if ( $blob === '' ) {
@@ -514,8 +554,8 @@ class LogPage {
* into a more readable (and translated) form
*
* @param string $flags Flags to format
- * @param $lang Language object to use
- * @return String
+ * @param Language $lang
+ * @return string
*/
public static function formatBlockFlags( $flags, $lang ) {
$flags = trim( $flags );
@@ -523,10 +563,12 @@ class LogPage {
return ''; //nothing to do
}
$flags = explode( ',', $flags );
+ $flagsCount = count( $flags );
- for ( $i = 0; $i < count( $flags ); $i++ ) {
+ for ( $i = 0; $i < $flagsCount; $i++ ) {
$flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
}
+
return wfMessage( 'parentheses' )->inLanguage( $lang )
->rawParams( $lang->commaList( $flags ) )->escaped();
}
@@ -535,8 +577,8 @@ class LogPage {
* Translate a block log flag if possible
*
* @param int $flag Flag to translate
- * @param $lang Language object to use
- * @return String
+ * @param Language $lang Language object to use
+ * @return string
*/
public static function formatBlockFlag( $flag, $lang ) {
static $messages = array();
@@ -593,6 +635,7 @@ class LogPage {
} else {
$key = 'log-description-' . $this->type;
}
+
return wfMessage( $key );
}
@@ -609,6 +652,7 @@ class LogPage {
// '' always returns true with $user->isAllowed()
$restriction = '';
}
+
return $restriction;
}
@@ -619,7 +663,7 @@ class LogPage {
*/
public function isRestricted() {
$restriction = $this->getRestriction();
+
return $restriction !== '' && $restriction !== '*';
}
-
}
diff --git a/includes/logging/LogPager.php b/includes/logging/LogPager.php
index 09ae3b8c..256934e4 100644
--- a/includes/logging/LogPager.php
+++ b/includes/logging/LogPager.php
@@ -3,7 +3,7 @@
* Contain classes to list log entries
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>, 2008 Aaron Schulz
- * http://www.mediawiki.org/
+ * 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
@@ -27,22 +27,36 @@
* @ingroup Pager
*/
class LogPager extends ReverseChronologicalPager {
- private $types = array(), $performer = '', $title = '', $pattern = '';
+ /** @var array Log types */
+ private $types = array();
+
+ /** @var string Events limited to those by performer when set */
+ private $performer = '';
+
+ /** @var string|Title Events limited to those about Title when set */
+ private $title = '';
+
+ /** @var string */
+ private $pattern = '';
+
+ /** @var string */
private $typeCGI = '';
+
+ /** @var LogEventsList */
public $mLogEventsList;
/**
* Constructor
*
* @param LogEventsList $list
- * @param string $types or Array: log types to show
- * @param string $performer the user who made the log entries
- * @param string|Title $title the page title the log entries are for
- * @param string $pattern do a prefix search rather than an exact title match
- * @param array $conds extra conditions for the query
- * @param int $year The year to start from
- * @param int $month The month to start from
- * @param string $tagFilter tag
+ * @param string|array $types Log types to show
+ * @param string $performer The user who made the log entries
+ * @param string|Title $title The page title the log entries are for
+ * @param string $pattern Do a prefix search rather than an exact title match
+ * @param array $conds Extra conditions for the query
+ * @param int|bool $year The year to start from. Default: false
+ * @param int|bool $month The month to start from. Default: false
+ * @param string $tagFilter Tag
*/
public function __construct( $list, $types = array(), $performer = '', $title = '', $pattern = '',
$conds = array(), $year = false, $month = false, $tagFilter = '' ) {
@@ -56,6 +70,8 @@ class LogPager extends ReverseChronologicalPager {
$this->limitTitle( $title, $pattern );
$this->getDateCond( $year, $month );
$this->mTagFilter = $tagFilter;
+
+ $this->mDb = wfGetDB( DB_SLAVE, 'logpager' );
}
public function getDefaultQuery() {
@@ -64,6 +80,7 @@ class LogPager extends ReverseChronologicalPager {
$query['user'] = $this->performer;
$query['month'] = $this->mMonth;
$query['year'] = $this->mYear;
+
return $query;
}
@@ -84,6 +101,7 @@ class LogPager extends ReverseChronologicalPager {
}
}
}
+
return $filters;
}
@@ -91,7 +109,7 @@ class LogPager extends ReverseChronologicalPager {
* Set the log reader to return only entries of the given type.
* Type restrictions enforced here
*
- * @param string $types or array: Log types ('upload', 'delete', etc);
+ * @param string|array $types Log types ('upload', 'delete', etc);
* empty string means no restriction
*/
private function limitType( $types ) {
@@ -136,43 +154,42 @@ class LogPager extends ReverseChronologicalPager {
* Set the log reader to return only entries by the given user.
*
* @param string $name (In)valid user name
- * @return bool
+ * @return void
*/
private function limitPerformer( $name ) {
if ( $name == '' ) {
- return false;
+ return;
}
$usertitle = Title::makeTitleSafe( NS_USER, $name );
if ( is_null( $usertitle ) ) {
- return false;
+ return;
}
/* Fetch userid at first, if known, provides awesome query plan afterwards */
$userid = User::idFromName( $name );
if ( !$userid ) {
- /* It should be nicer to abort query at all,
- but for now it won't pass anywhere behind the optimizer */
- $this->mConds[] = "NULL";
+ $this->mConds['log_user_text'] = IP::sanitizeIP( $name );
} else {
$this->mConds['log_user'] = $userid;
- // Paranoia: avoid brute force searches (bug 17342)
- $user = $this->getUser();
- if ( !$user->isAllowed( 'deletedhistory' ) ) {
- $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0';
- } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
- $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) .
- ' != ' . LogPage::SUPPRESSED_USER;
- }
- $this->performer = $usertitle->getText();
}
+ // Paranoia: avoid brute force searches (bug 17342)
+ $user = $this->getUser();
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0';
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) .
+ ' != ' . LogPage::SUPPRESSED_USER;
+ }
+
+ $this->performer = $usertitle->getText();
}
/**
* Set the log reader to return only entries affecting the given page.
* (For the block and rights logs, this is a user page.)
*
- * @param string $page or Title object: Title name
- * @param $pattern String
- * @return bool
+ * @param string|Title $page Title name
+ * @param string $pattern
+ * @return void
*/
private function limitTitle( $page, $pattern ) {
global $wgMiserMode;
@@ -182,7 +199,7 @@ class LogPager extends ReverseChronologicalPager {
} else {
$title = Title::newFromText( $page );
if ( strlen( $page ) == 0 || !$title instanceof Title ) {
- return false;
+ return;
}
}
@@ -190,6 +207,18 @@ class LogPager extends ReverseChronologicalPager {
$ns = $title->getNamespace();
$db = $this->mDb;
+ $doUserRightsLogLike = false;
+ if ( $this->types == array( 'rights' ) ) {
+ global $wgUserrightsInterwikiDelimiter;
+ $parts = explode( $wgUserrightsInterwikiDelimiter, $title->getDBKey() );
+ if ( count( $parts ) == 2 ) {
+ list( $name, $database ) = array_map( 'trim', $parts );
+ if ( strstr( $database, '*' ) ) { // Search for wildcard in database name
+ $doUserRightsLogLike = true;
+ }
+ }
+ }
+
# Using the (log_namespace, log_title, log_timestamp) index with a
# range scan (LIKE) on the first two parts, instead of simple equality,
# makes it unusable for sorting. Sorted retrieval using another index
@@ -201,12 +230,19 @@ class LogPager extends ReverseChronologicalPager {
# use the page_time index. That should have no more than a few hundred
# log entries for even the busiest pages, so it can be safely scanned
# in full to satisfy an impossible condition on user or similar.
- if ( $pattern && !$wgMiserMode ) {
- $this->mConds['log_namespace'] = $ns;
- $this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() );
+ $this->mConds['log_namespace'] = $ns;
+ if ( $doUserRightsLogLike ) {
+ $params = array( $name . $wgUserrightsInterwikiDelimiter );
+ foreach ( explode( '*', $database ) as $databasepart ) {
+ $params[] = $databasepart;
+ $params[] = $db->anyString();
+ }
+ array_pop( $params ); // Get rid of the last % we added.
+ $this->mConds[] = 'log_title' . $db->buildLike( $params );
+ } elseif ( $pattern && !$wgMiserMode ) {
+ $this->mConds[] = 'log_title' . $db->buildLike( $title->getDBkey(), $db->anyString() );
$this->pattern = $pattern;
} else {
- $this->mConds['log_namespace'] = $ns;
$this->mConds['log_title'] = $title->getDBkey();
}
// Paranoia: avoid brute force searches (bug 17342)
@@ -242,8 +278,8 @@ class LogPager extends ReverseChronologicalPager {
$index['log_search'] = 'ls_field_val';
$index['logging'] = 'PRIMARY';
if ( !$this->hasEqualsClause( 'ls_field' )
- || !$this->hasEqualsClause( 'ls_value' ) )
- {
+ || !$this->hasEqualsClause( 'ls_value' )
+ ) {
# Since (ls_field,ls_value,ls_logid) is unique, if the condition is
# to match a specific (ls_field,ls_value) tuple, then there will be
# no duplicate log rows. Otherwise, we need to remove the duplicates.
@@ -266,12 +302,13 @@ class LogPager extends ReverseChronologicalPager {
# Add ChangeTags filter query
ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'],
$info['join_conds'], $info['options'], $this->mTagFilter );
+
return $info;
}
/**
* Checks if $this->mConds has $field matched to a *single* value
- * @param $field
+ * @param string $field
* @return bool
*/
protected function hasEqualsClause( $field ) {
@@ -303,6 +340,7 @@ class LogPager extends ReverseChronologicalPager {
$this->mResult->seek( 0 );
}
wfProfileOut( __METHOD__ );
+
return '';
}
diff --git a/includes/logging/MoveLogFormatter.php b/includes/logging/MoveLogFormatter.php
index 0978f976..39130163 100644
--- a/includes/logging/MoveLogFormatter.php
+++ b/includes/logging/MoveLogFormatter.php
@@ -31,6 +31,7 @@
class MoveLogFormatter extends LogFormatter {
public function getPreloadTitles() {
$params = $this->extractParameters();
+
return array( Title::newFromText( $params[3] ) );
}
@@ -40,6 +41,7 @@ class MoveLogFormatter extends LogFormatter {
if ( isset( $params[4] ) && $params[4] === '1' ) {
$key .= '-noredirect';
}
+
return $key;
}
@@ -49,14 +51,15 @@ class MoveLogFormatter extends LogFormatter {
$newname = $this->makePageLink( Title::newFromText( $params[3] ) );
$params[2] = Message::rawParam( $oldname );
$params[3] = Message::rawParam( $newname );
+
return $params;
}
public function getActionLinks() {
if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden
|| $this->entry->getSubtype() !== 'move'
- || !$this->context->getUser()->isAllowed( 'move' ) )
- {
+ || !$this->context->getUser()->isAllowed( 'move' )
+ ) {
return '';
}
@@ -77,6 +80,7 @@ class MoveLogFormatter extends LogFormatter {
'wpMovetalk' => 0
)
);
+
return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
}
}
diff --git a/includes/logging/NewUsersLogFormatter.php b/includes/logging/NewUsersLogFormatter.php
index 602728b4..c870d519 100644
--- a/includes/logging/NewUsersLogFormatter.php
+++ b/includes/logging/NewUsersLogFormatter.php
@@ -41,6 +41,7 @@ class NewUsersLogFormatter extends LogFormatter {
$params[2] = Message::rawParam( $this->makeUserLink( $target ) );
$params[3] = $target->getName();
}
+
return $params;
}
@@ -51,6 +52,7 @@ class NewUsersLogFormatter extends LogFormatter {
# not needed and can contain incorrect links
return '';
}
+
return parent::getComment();
}
@@ -60,6 +62,7 @@ class NewUsersLogFormatter extends LogFormatter {
//add the user talk to LinkBatch for the userLink
return array( Title::makeTitle( NS_USER_TALK, $this->entry->getTarget()->getText() ) );
}
+
return array();
}
}
diff --git a/includes/logging/PageLangLogFormatter.php b/includes/logging/PageLangLogFormatter.php
new file mode 100644
index 00000000..694fa7f3
--- /dev/null
+++ b/includes/logging/PageLangLogFormatter.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Formatter for changelang 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 Kunal Grover
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.24
+ */
+
+/**
+ * This class formats language change log entries.
+ *
+ * @since 1.24
+ */
+class PageLangLogFormatter extends LogFormatter {
+ protected function getMessageParameters() {
+ // Get the user language for displaying language names
+ $userLang = $this->context->getLanguage()->getCode();
+ $params = parent::getMessageParameters();
+
+ // Get the language codes from log
+ $oldLang = $params[3];
+ $kOld = strrpos( $oldLang, '[' );
+ if ( $kOld ) {
+ $oldLang = substr( $oldLang, 0, $kOld );
+ }
+
+ $newLang = $params[4];
+ $kNew = strrpos( $newLang, '[' );
+ if ( $kNew ) {
+ $newLang = substr( $newLang, 0, $kNew );
+ }
+
+ // Convert language codes to names in user language
+ $logOld = Language::fetchLanguageName( $oldLang, $userLang )
+ . ' (' . $oldLang . ')';
+ $logNew = Language::fetchLanguageName( $newLang, $userLang )
+ . ' (' . $newLang . ')';
+
+ // Add the default message to languages if required
+ $params[3] = !$kOld ? $logOld : $logOld . ' [' . $this->msg( 'default' ) . ']';
+ $params[4] = !$kNew ? $logNew : $logNew . ' [' . $this->msg( 'default' ) . ']';
+ return $params;
+ }
+}
diff --git a/includes/logging/PatrolLog.php b/includes/logging/PatrolLog.php
index bb76d5a9..4f2a565d 100644
--- a/includes/logging/PatrolLog.php
+++ b/includes/logging/PatrolLog.php
@@ -27,13 +27,12 @@
* logs of patrol events
*/
class PatrolLog {
-
/**
* Record a log event for a change being patrolled
*
- * @param $rc Mixed: change identifier or RecentChange object
- * @param $auto Boolean: was this patrol event automatic?
- * @param $user User: user performing the action or null to use $wgUser
+ * @param int|RecentChange $rc Change identifier or RecentChange object
+ * @param bool $auto Was this patrol event automatic?
+ * @param User $user User performing the action or null to use $wgUser
*
* @return bool
*/
@@ -65,15 +64,16 @@ class PatrolLog {
if ( !$auto ) {
$entry->publish( $logid, 'udp' );
}
+
return true;
}
/**
* Prepare log parameters for a patrolled change
*
- * @param $change RecentChange to represent
- * @param $auto Boolean: whether the patrol event was automatic
- * @return Array
+ * @param RecentChange $change RecentChange to represent
+ * @param bool $auto Whether the patrol event was automatic
+ * @return array
*/
private static function buildParams( $change, $auto ) {
return array(
@@ -82,5 +82,4 @@ class PatrolLog {
'6::auto' => (int)$auto
);
}
-
}
diff --git a/includes/logging/PatrolLogFormatter.php b/includes/logging/PatrolLogFormatter.php
index 507039ba..2abaf173 100644
--- a/includes/logging/PatrolLogFormatter.php
+++ b/includes/logging/PatrolLogFormatter.php
@@ -35,6 +35,7 @@ class PatrolLogFormatter extends LogFormatter {
if ( isset( $params[5] ) && $params[5] ) {
$key .= '-auto';
}
+
return $key;
}
@@ -58,6 +59,7 @@ class PatrolLogFormatter extends LogFormatter {
}
$params[3] = Message::rawParam( $revlink );
+
return $params;
}
}
diff --git a/includes/logging/RightsLogFormatter.php b/includes/logging/RightsLogFormatter.php
index d3daf6ee..ac252aeb 100644
--- a/includes/logging/RightsLogFormatter.php
+++ b/includes/logging/RightsLogFormatter.php
@@ -55,6 +55,7 @@ class RightsLogFormatter extends LogFormatter {
if ( !isset( $params[3] ) && !isset( $params[4] ) ) {
$key .= '-legacy';
}
+
return $key;
}
diff --git a/includes/mail/EmailNotification.php b/includes/mail/EmailNotification.php
new file mode 100644
index 00000000..8215403e
--- /dev/null
+++ b/includes/mail/EmailNotification.php
@@ -0,0 +1,512 @@
+<?php
+/**
+ * Classes used to send e-mails
+ *
+ * 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 <brion@pobox.com>
+ * @author <mail@tgries.de>
+ * @author Tim Starling
+ * @author Luke Welling lwelling@wikimedia.org
+ */
+
+/**
+ * This module processes the email notifications when the current page is
+ * changed. It looks up the table watchlist to find out which users are watching
+ * that page.
+ *
+ * The current implementation sends independent emails to each watching user for
+ * the following reason:
+ *
+ * - Each watching user will be notified about the page edit time expressed in
+ * his/her local time (UTC is shown additionally). To achieve this, we need to
+ * find the individual timeoffset of each watching user from the preferences..
+ *
+ * Suggested improvement to slack down the number of sent emails: We could think
+ * of sending out bulk mails (bcc:user1,user2...) for all these users having the
+ * same timeoffset in their preferences.
+ *
+ * Visit the documentation pages under http://meta.wikipedia.com/Enotif
+ */
+class EmailNotification {
+ protected $subject, $body, $replyto, $from;
+ protected $timestamp, $summary, $minorEdit, $oldid, $composed_common, $pageStatus;
+ protected $mailTargets = array();
+
+ /**
+ * @var Title
+ */
+ protected $title;
+
+ /**
+ * @var User
+ */
+ protected $editor;
+
+ /**
+ * @param User $editor The editor that triggered the update. Their notification
+ * timestamp will not be updated(they have already seen it)
+ * @param Title $title The title to update timestamps for
+ * @param string $timestamp Set the upate timestamp to this value
+ * @return int[]
+ */
+ public static function updateWatchlistTimestamp( User $editor, Title $title, $timestamp ) {
+ global $wgEnotifWatchlist, $wgShowUpdatedMarker;
+
+ if ( !$wgEnotifWatchlist && !$wgShowUpdatedMarker ) {
+ return array();
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ $res = $dbw->select( array( 'watchlist' ),
+ array( 'wl_user' ),
+ array(
+ 'wl_user != ' . intval( $editor->getID() ),
+ 'wl_namespace' => $title->getNamespace(),
+ 'wl_title' => $title->getDBkey(),
+ 'wl_notificationtimestamp IS NULL',
+ ), __METHOD__
+ );
+
+ $watchers = array();
+ foreach ( $res as $row ) {
+ $watchers[] = intval( $row->wl_user );
+ }
+
+ if ( $watchers ) {
+ // Update wl_notificationtimestamp for all watching users except the editor
+ $fname = __METHOD__;
+ $dbw->onTransactionIdle(
+ function () use ( $dbw, $timestamp, $watchers, $title, $fname ) {
+ $dbw->update( 'watchlist',
+ array( /* SET */
+ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
+ ), array( /* WHERE */
+ 'wl_user' => $watchers,
+ 'wl_namespace' => $title->getNamespace(),
+ 'wl_title' => $title->getDBkey(),
+ ), $fname
+ );
+ }
+ );
+ }
+
+ return $watchers;
+ }
+
+ /**
+ * Send emails corresponding to the user $editor editing the page $title.
+ * Also updates wl_notificationtimestamp.
+ *
+ * May be deferred via the job queue.
+ *
+ * @param User $editor
+ * @param Title $title
+ * @param string $timestamp
+ * @param string $summary
+ * @param bool $minorEdit
+ * @param bool $oldid (default: false)
+ * @param string $pageStatus (default: 'changed')
+ */
+ public function notifyOnPageChange( $editor, $title, $timestamp, $summary,
+ $minorEdit, $oldid = false, $pageStatus = 'changed'
+ ) {
+ global $wgEnotifUseJobQ, $wgEnotifMinorEdits, $wgUsersNotifiedOnAllChanges, $wgEnotifUserTalk;
+
+ if ( $title->getNamespace() < 0 ) {
+ return;
+ }
+
+ // update wl_notificationtimestamp for watchers
+ $watchers = self::updateWatchlistTimestamp( $editor, $title, $timestamp );
+
+ $sendEmail = true;
+ // If nobody is watching the page, and there are no users notified on all changes
+ // don't bother creating a job/trying to send emails
+ // $watchers deals with $wgEnotifWatchlist
+ if ( !count( $watchers ) && !count( $wgUsersNotifiedOnAllChanges ) ) {
+ $sendEmail = false;
+ // Only send notification for non minor edits, unless $wgEnotifMinorEdits
+ if ( !$minorEdit || ( $wgEnotifMinorEdits && !$editor->isAllowed( 'nominornewtalk' ) ) ) {
+ $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
+ if ( $wgEnotifUserTalk
+ && $isUserTalkPage
+ && $this->canSendUserTalkEmail( $editor, $title, $minorEdit )
+ ) {
+ $sendEmail = true;
+ }
+ }
+ }
+
+ if ( !$sendEmail ) {
+ return;
+ }
+
+ if ( $wgEnotifUseJobQ ) {
+ $params = array(
+ 'editor' => $editor->getName(),
+ 'editorID' => $editor->getID(),
+ 'timestamp' => $timestamp,
+ 'summary' => $summary,
+ 'minorEdit' => $minorEdit,
+ 'oldid' => $oldid,
+ 'watchers' => $watchers,
+ 'pageStatus' => $pageStatus
+ );
+ $job = new EnotifNotifyJob( $title, $params );
+ JobQueueGroup::singleton()->push( $job );
+ } else {
+ $this->actuallyNotifyOnPageChange(
+ $editor,
+ $title,
+ $timestamp,
+ $summary,
+ $minorEdit,
+ $oldid,
+ $watchers,
+ $pageStatus
+ );
+ }
+ }
+
+ /**
+ * Immediate version of notifyOnPageChange().
+ *
+ * Send emails corresponding to the user $editor editing the page $title.
+ * Also updates wl_notificationtimestamp.
+ *
+ * @param User $editor
+ * @param Title $title
+ * @param string $timestamp Edit timestamp
+ * @param string $summary Edit summary
+ * @param bool $minorEdit
+ * @param int $oldid Revision ID
+ * @param array $watchers Array of user IDs
+ * @param string $pageStatus
+ * @throws MWException
+ */
+ public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit,
+ $oldid, $watchers, $pageStatus = 'changed' ) {
+ # we use $wgPasswordSender as sender's address
+ global $wgEnotifWatchlist;
+ global $wgEnotifMinorEdits, $wgEnotifUserTalk;
+
+ wfProfileIn( __METHOD__ );
+
+ # The following code is only run, if several conditions are met:
+ # 1. EmailNotification for pages (other than user_talk pages) must be enabled
+ # 2. minor edits (changes) are only regarded if the global flag indicates so
+
+ $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
+
+ $this->title = $title;
+ $this->timestamp = $timestamp;
+ $this->summary = $summary;
+ $this->minorEdit = $minorEdit;
+ $this->oldid = $oldid;
+ $this->editor = $editor;
+ $this->composed_common = false;
+ $this->pageStatus = $pageStatus;
+
+ $formattedPageStatus = array( 'deleted', 'created', 'moved', 'restored', 'changed' );
+
+ wfRunHooks( 'UpdateUserMailerFormattedPageStatus', array( &$formattedPageStatus ) );
+ if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) {
+ wfProfileOut( __METHOD__ );
+ throw new MWException( 'Not a valid page status!' );
+ }
+
+ $userTalkId = false;
+
+ if ( !$minorEdit || ( $wgEnotifMinorEdits && !$editor->isAllowed( 'nominornewtalk' ) ) ) {
+ if ( $wgEnotifUserTalk
+ && $isUserTalkPage
+ && $this->canSendUserTalkEmail( $editor, $title, $minorEdit )
+ ) {
+ $targetUser = User::newFromName( $title->getText() );
+ $this->compose( $targetUser );
+ $userTalkId = $targetUser->getId();
+ }
+
+ if ( $wgEnotifWatchlist ) {
+ // Send updates to watchers other than the current editor
+ $userArray = UserArray::newFromIDs( $watchers );
+ foreach ( $userArray as $watchingUser ) {
+ if ( $watchingUser->getOption( 'enotifwatchlistpages' )
+ && ( !$minorEdit || $watchingUser->getOption( 'enotifminoredits' ) )
+ && $watchingUser->isEmailConfirmed()
+ && $watchingUser->getID() != $userTalkId
+ ) {
+ if ( wfRunHooks( 'SendWatchlistEmailNotification', array( $watchingUser, $title, $this ) ) ) {
+ $this->compose( $watchingUser );
+ }
+ }
+ }
+ }
+ }
+
+ global $wgUsersNotifiedOnAllChanges;
+ foreach ( $wgUsersNotifiedOnAllChanges as $name ) {
+ if ( $editor->getName() == $name ) {
+ // No point notifying the user that actually made the change!
+ continue;
+ }
+ $user = User::newFromName( $name );
+ $this->compose( $user );
+ }
+
+ $this->sendMails();
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * @param User $editor
+ * @param Title $title
+ * @param bool $minorEdit
+ * @return bool
+ */
+ private function canSendUserTalkEmail( $editor, $title, $minorEdit ) {
+ global $wgEnotifUserTalk;
+ $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
+
+ if ( $wgEnotifUserTalk && $isUserTalkPage ) {
+ $targetUser = User::newFromName( $title->getText() );
+
+ if ( !$targetUser || $targetUser->isAnon() ) {
+ wfDebug( __METHOD__ . ": user talk page edited, but user does not exist\n" );
+ } elseif ( $targetUser->getId() == $editor->getId() ) {
+ wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent\n" );
+ } elseif ( $targetUser->getOption( 'enotifusertalkpages' )
+ && ( !$minorEdit || $targetUser->getOption( 'enotifminoredits' ) )
+ ) {
+ if ( !$targetUser->isEmailConfirmed() ) {
+ wfDebug( __METHOD__ . ": talk page owner doesn't have validated email\n" );
+ } elseif ( !wfRunHooks( 'AbortTalkPageEmailNotification', array( $targetUser, $title ) ) ) {
+ wfDebug( __METHOD__ . ": talk page update notification is aborted for this user\n" );
+ } else {
+ wfDebug( __METHOD__ . ": sending talk page update notification\n" );
+ return true;
+ }
+ } else {
+ wfDebug( __METHOD__ . ": talk page owner doesn't want notifications\n" );
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Generate the generic "this page has been changed" e-mail text.
+ */
+ private function composeCommonMailtext() {
+ global $wgPasswordSender, $wgNoReplyAddress;
+ global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
+ global $wgEnotifImpersonal, $wgEnotifUseRealName;
+
+ $this->composed_common = true;
+
+ # You as the WikiAdmin and Sysops can make use of plenty of
+ # named variables when composing your notification emails while
+ # simply editing the Meta pages
+
+ $keys = array();
+ $postTransformKeys = array();
+ $pageTitleUrl = $this->title->getCanonicalURL();
+ $pageTitle = $this->title->getPrefixedText();
+
+ if ( $this->oldid ) {
+ // Always show a link to the diff which triggered the mail. See bug 32210.
+ $keys['$NEWPAGE'] = "\n\n" . wfMessage( 'enotif_lastdiff',
+ $this->title->getCanonicalURL( array( 'diff' => 'next', 'oldid' => $this->oldid ) ) )
+ ->inContentLanguage()->text();
+
+ if ( !$wgEnotifImpersonal ) {
+ // For personal mail, also show a link to the diff of all changes
+ // since last visited.
+ $keys['$NEWPAGE'] .= "\n\n" . wfMessage( 'enotif_lastvisited',
+ $this->title->getCanonicalURL( array( 'diff' => '0', 'oldid' => $this->oldid ) ) )
+ ->inContentLanguage()->text();
+ }
+ $keys['$OLDID'] = $this->oldid;
+ // Deprecated since MediaWiki 1.21, not used by default. Kept for backwards-compatibility.
+ $keys['$CHANGEDORCREATED'] = wfMessage( 'changed' )->inContentLanguage()->text();
+ } else {
+ # clear $OLDID placeholder in the message template
+ $keys['$OLDID'] = '';
+ $keys['$NEWPAGE'] = '';
+ // Deprecated since MediaWiki 1.21, not used by default. Kept for backwards-compatibility.
+ $keys['$CHANGEDORCREATED'] = wfMessage( 'created' )->inContentLanguage()->text();
+ }
+
+ $keys['$PAGETITLE'] = $this->title->getPrefixedText();
+ $keys['$PAGETITLE_URL'] = $this->title->getCanonicalURL();
+ $keys['$PAGEMINOREDIT'] = $this->minorEdit ?
+ wfMessage( 'minoredit' )->inContentLanguage()->text() : '';
+ $keys['$UNWATCHURL'] = $this->title->getCanonicalURL( 'action=unwatch' );
+
+ if ( $this->editor->isAnon() ) {
+ # real anon (user:xxx.xxx.xxx.xxx)
+ $keys['$PAGEEDITOR'] = wfMessage( 'enotif_anon_editor', $this->editor->getName() )
+ ->inContentLanguage()->text();
+ $keys['$PAGEEDITOR_EMAIL'] = wfMessage( 'noemailtitle' )->inContentLanguage()->text();
+
+ } else {
+ $keys['$PAGEEDITOR'] = $wgEnotifUseRealName && $this->editor->getRealName() !== ''
+ ? $this->editor->getRealName() : $this->editor->getName();
+ $emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $this->editor->getName() );
+ $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getCanonicalURL();
+ }
+
+ $keys['$PAGEEDITOR_WIKI'] = $this->editor->getUserPage()->getCanonicalURL();
+ $keys['$HELPPAGE'] = wfExpandUrl(
+ Skin::makeInternalOrExternalUrl( wfMessage( 'helppage' )->inContentLanguage()->text() )
+ );
+
+ # Replace this after transforming the message, bug 35019
+ $postTransformKeys['$PAGESUMMARY'] = $this->summary == '' ? ' - ' : $this->summary;
+
+ // Now build message's subject and body
+
+ // Messages:
+ // enotif_subject_deleted, enotif_subject_created, enotif_subject_moved,
+ // enotif_subject_restored, enotif_subject_changed
+ $this->subject = wfMessage( 'enotif_subject_' . $this->pageStatus )->inContentLanguage()
+ ->params( $pageTitle, $keys['$PAGEEDITOR'] )->text();
+
+ // Messages:
+ // enotif_body_intro_deleted, enotif_body_intro_created, enotif_body_intro_moved,
+ // enotif_body_intro_restored, enotif_body_intro_changed
+ $keys['$PAGEINTRO'] = wfMessage( 'enotif_body_intro_' . $this->pageStatus )
+ ->inContentLanguage()->params( $pageTitle, $keys['$PAGEEDITOR'], $pageTitleUrl )
+ ->text();
+
+ $body = wfMessage( 'enotif_body' )->inContentLanguage()->plain();
+ $body = strtr( $body, $keys );
+ $body = MessageCache::singleton()->transform( $body, false, null, $this->title );
+ $this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
+
+ # Reveal the page editor's address as REPLY-TO address only if
+ # the user has not opted-out and the option is enabled at the
+ # global configuration level.
+ $adminAddress = new MailAddress( $wgPasswordSender,
+ wfMessage( 'emailsender' )->inContentLanguage()->text() );
+ if ( $wgEnotifRevealEditorAddress
+ && ( $this->editor->getEmail() != '' )
+ && $this->editor->getOption( 'enotifrevealaddr' )
+ ) {
+ $editorAddress = MailAddress::newFromUser( $this->editor );
+ if ( $wgEnotifFromEditor ) {
+ $this->from = $editorAddress;
+ } else {
+ $this->from = $adminAddress;
+ $this->replyto = $editorAddress;
+ }
+ } else {
+ $this->from = $adminAddress;
+ $this->replyto = new MailAddress( $wgNoReplyAddress );
+ }
+ }
+
+ /**
+ * Compose a mail to a given user and either queue it for sending, or send it now,
+ * depending on settings.
+ *
+ * Call sendMails() to send any mails that were queued.
+ * @param User $user
+ */
+ function compose( $user ) {
+ global $wgEnotifImpersonal;
+
+ if ( !$this->composed_common ) {
+ $this->composeCommonMailtext();
+ }
+
+ if ( $wgEnotifImpersonal ) {
+ $this->mailTargets[] = MailAddress::newFromUser( $user );
+ } else {
+ $this->sendPersonalised( $user );
+ }
+ }
+
+ /**
+ * Send any queued mails
+ */
+ function sendMails() {
+ global $wgEnotifImpersonal;
+ if ( $wgEnotifImpersonal ) {
+ $this->sendImpersonal( $this->mailTargets );
+ }
+ }
+
+ /**
+ * Does the per-user customizations to a notification e-mail (name,
+ * timestamp in proper timezone, etc) and sends it out.
+ * Returns true if the mail was sent successfully.
+ *
+ * @param User $watchingUser
+ * @return bool
+ * @private
+ */
+ function sendPersonalised( $watchingUser ) {
+ global $wgContLang, $wgEnotifUseRealName;
+ // From the PHP manual:
+ // Note: The to parameter cannot be an address in the form of
+ // "Something <someone@example.com>". The mail command will not parse
+ // this properly while talking with the MTA.
+ $to = MailAddress::newFromUser( $watchingUser );
+
+ # $PAGEEDITDATE is the time and date of the page change
+ # expressed in terms of individual local time of the notification
+ # recipient, i.e. watching user
+ $body = str_replace(
+ array( '$WATCHINGUSERNAME',
+ '$PAGEEDITDATE',
+ '$PAGEEDITTIME' ),
+ array( $wgEnotifUseRealName && $watchingUser->getRealName() !== ''
+ ? $watchingUser->getRealName() : $watchingUser->getName(),
+ $wgContLang->userDate( $this->timestamp, $watchingUser ),
+ $wgContLang->userTime( $this->timestamp, $watchingUser ) ),
+ $this->body );
+
+ return UserMailer::send( $to, $this->from, $this->subject, $body, $this->replyto );
+ }
+
+ /**
+ * Same as sendPersonalised but does impersonal mail suitable for bulk
+ * mailing. Takes an array of MailAddress objects.
+ * @param MailAddress[] $addresses
+ * @return Status|null
+ */
+ function sendImpersonal( $addresses ) {
+ global $wgContLang;
+
+ if ( empty( $addresses ) ) {
+ return null;
+ }
+
+ $body = str_replace(
+ array( '$WATCHINGUSERNAME',
+ '$PAGEEDITDATE',
+ '$PAGEEDITTIME' ),
+ array( wfMessage( 'enotif_impersonal_salutation' )->inContentLanguage()->text(),
+ $wgContLang->date( $this->timestamp, false, false ),
+ $wgContLang->time( $this->timestamp, false, false ) ),
+ $this->body );
+
+ return UserMailer::send( $addresses, $this->from, $this->subject, $body, $this->replyto );
+ }
+
+}
diff --git a/includes/mail/MailAddress.php b/includes/mail/MailAddress.php
new file mode 100644
index 00000000..68179088
--- /dev/null
+++ b/includes/mail/MailAddress.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Classes used to send e-mails
+ *
+ * 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 <brion@pobox.com>
+ * @author <mail@tgries.de>
+ * @author Tim Starling
+ * @author Luke Welling lwelling@wikimedia.org
+ */
+
+/**
+ * Stores a single person's name and email address.
+ * These are passed in via the constructor, and will be returned in SMTP
+ * header format when requested.
+ */
+class MailAddress {
+ /**
+ * @param string $address String with an email address, or a User object
+ * @param string $name Human-readable name if a string address is given
+ * @param string $realName Human-readable real name if a string address is given
+ */
+ function __construct( $address, $name = null, $realName = null ) {
+ if ( is_object( $address ) && $address instanceof User ) {
+ // Old calling format, now deprecated
+ wfDeprecated( __METHOD__ . ' with a User object' , '1.24' );
+ $this->address = $address->getEmail();
+ $this->name = $address->getName();
+ $this->realName = $address->getRealName();
+ } else {
+ $this->address = strval( $address );
+ $this->name = strval( $name );
+ $this->realName = strval( $realName );
+ }
+ }
+
+ /**
+ * Create a new MailAddress object for the given user
+ *
+ * @since 1.24
+ * @param User $user
+ * @return MailAddress
+ */
+ public static function newFromUser( User $user ) {
+ return new MailAddress( $user->getEmail(), $user->getName(), $user->getRealName() );
+ }
+
+ /**
+ * Return formatted and quoted address to insert into SMTP headers
+ * @return string
+ */
+ function toString() {
+ # PHP's mail() implementation under Windows is somewhat shite, and
+ # can't handle "Joe Bloggs <joe@bloggs.com>" format email addresses,
+ # so don't bother generating them
+ if ( $this->address ) {
+ if ( $this->name != '' && !wfIsWindows() ) {
+ global $wgEnotifUseRealName;
+ $name = ( $wgEnotifUseRealName && $this->realName !== '' ) ? $this->realName : $this->name;
+ $quoted = UserMailer::quotedPrintable( $name );
+ if ( strpos( $quoted, '.' ) !== false || strpos( $quoted, ',' ) !== false ) {
+ $quoted = '"' . $quoted . '"';
+ }
+ return "$quoted <{$this->address}>";
+ } else {
+ return $this->address;
+ }
+ } else {
+ return "";
+ }
+ }
+
+ function __toString() {
+ return $this->toString();
+ }
+}
diff --git a/includes/mail/UserMailer.php b/includes/mail/UserMailer.php
new file mode 100644
index 00000000..b5a57a84
--- /dev/null
+++ b/includes/mail/UserMailer.php
@@ -0,0 +1,425 @@
+<?php
+/**
+ * Classes used to send e-mails
+ *
+ * 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 <brion@pobox.com>
+ * @author <mail@tgries.de>
+ * @author Tim Starling
+ * @author Luke Welling lwelling@wikimedia.org
+ */
+
+/**
+ * Collection of static functions for sending mail
+ */
+class UserMailer {
+ private static $mErrorString;
+
+ /**
+ * Send mail using a PEAR mailer
+ *
+ * @param UserMailer $mailer
+ * @param string $dest
+ * @param string $headers
+ * @param string $body
+ *
+ * @return Status
+ */
+ protected static function sendWithPear( $mailer, $dest, $headers, $body ) {
+ $mailResult = $mailer->send( $dest, $headers, $body );
+
+ # Based on the result return an error string,
+ if ( PEAR::isError( $mailResult ) ) {
+ wfDebug( "PEAR::Mail failed: " . $mailResult->getMessage() . "\n" );
+ return Status::newFatal( 'pear-mail-error', $mailResult->getMessage() );
+ } else {
+ return Status::newGood();
+ }
+ }
+
+ /**
+ * Creates a single string from an associative array
+ *
+ * @param array $headers Associative Array: keys are header field names,
+ * values are ... values.
+ * @param string $endl The end of line character. Defaults to "\n"
+ *
+ * Note RFC2822 says newlines must be CRLF (\r\n)
+ * but php mail naively "corrects" it and requires \n for the "correction" to work
+ *
+ * @return string
+ */
+ static function arrayToHeaderString( $headers, $endl = "\n" ) {
+ $strings = array();
+ foreach ( $headers as $name => $value ) {
+ // Prevent header injection by stripping newlines from value
+ $value = self::sanitizeHeaderValue( $value );
+ $strings[] = "$name: $value";
+ }
+ return implode( $endl, $strings );
+ }
+
+ /**
+ * Create a value suitable for the MessageId Header
+ *
+ * @return string
+ */
+ static function makeMsgId() {
+ global $wgSMTP, $wgServer;
+
+ $msgid = uniqid( wfWikiID() . ".", true ); /* true required for cygwin */
+ if ( is_array( $wgSMTP ) && isset( $wgSMTP['IDHost'] ) && $wgSMTP['IDHost'] ) {
+ $domain = $wgSMTP['IDHost'];
+ } else {
+ $url = wfParseUrl( $wgServer );
+ $domain = $url['host'];
+ }
+ return "<$msgid@$domain>";
+ }
+
+ /**
+ * This function will perform a direct (authenticated) login to
+ * a SMTP Server to use for mail relaying if 'wgSMTP' specifies an
+ * array of parameters. It requires PEAR:Mail to do that.
+ * Otherwise it just uses the standard PHP 'mail' function.
+ *
+ * @param MailAddress|MailAddress[] $to Recipient's email (or an array of them)
+ * @param MailAddress $from Sender's email
+ * @param string $subject Email's subject.
+ * @param string $body Email's text or Array of two strings to be the text and html bodies
+ * @param MailAddress $replyto Optional reply-to email (default: null).
+ * @param string $contentType Optional custom Content-Type (default: text/plain; charset=UTF-8)
+ * @throws MWException
+ * @throws Exception
+ * @return Status
+ */
+ public static function send( $to, $from, $subject, $body, $replyto = null,
+ $contentType = 'text/plain; charset=UTF-8'
+ ) {
+ global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams, $wgAllowHTMLEmail;
+ $mime = null;
+ if ( !is_array( $to ) ) {
+ $to = array( $to );
+ }
+
+ // mail body must have some content
+ $minBodyLen = 10;
+ // arbitrary but longer than Array or Object to detect casting error
+
+ // body must either be a string or an array with text and body
+ if (
+ !(
+ !is_array( $body ) &&
+ strlen( $body ) >= $minBodyLen
+ )
+ &&
+ !(
+ is_array( $body ) &&
+ isset( $body['text'] ) &&
+ isset( $body['html'] ) &&
+ strlen( $body['text'] ) >= $minBodyLen &&
+ strlen( $body['html'] ) >= $minBodyLen
+ )
+ ) {
+ // if it is neither we have a problem
+ return Status::newFatal( 'user-mail-no-body' );
+ }
+
+ if ( !$wgAllowHTMLEmail && is_array( $body ) ) {
+ // HTML not wanted. Dump it.
+ $body = $body['text'];
+ }
+
+ wfDebug( __METHOD__ . ': sending mail to ' . implode( ', ', $to ) . "\n" );
+
+ # Make sure we have at least one address
+ $has_address = false;
+ foreach ( $to as $u ) {
+ if ( $u->address ) {
+ $has_address = true;
+ break;
+ }
+ }
+ if ( !$has_address ) {
+ return Status::newFatal( 'user-mail-no-addy' );
+ }
+
+ # Forge email headers
+ # -------------------
+ #
+ # WARNING
+ #
+ # DO NOT add To: or Subject: headers at this step. They need to be
+ # handled differently depending upon the mailer we are going to use.
+ #
+ # To:
+ # PHP mail() first argument is the mail receiver. The argument is
+ # used as a recipient destination and as a To header.
+ #
+ # PEAR mailer has a recipient argument which is only used to
+ # send the mail. If no To header is given, PEAR will set it to
+ # to 'undisclosed-recipients:'.
+ #
+ # NOTE: To: is for presentation, the actual recipient is specified
+ # by the mailer using the Rcpt-To: header.
+ #
+ # Subject:
+ # PHP mail() second argument to pass the subject, passing a Subject
+ # as an additional header will result in a duplicate header.
+ #
+ # PEAR mailer should be passed a Subject header.
+ #
+ # -- hashar 20120218
+
+ $headers['From'] = $from->toString();
+ $returnPath = $from->address;
+ $extraParams = $wgAdditionalMailParams;
+
+ // Hook to generate custom VERP address for 'Return-Path'
+ wfRunHooks( 'UserMailerChangeReturnPath', array( $to, &$returnPath ) );
+ # Add the envelope sender address using the -f command line option when PHP mail() is used.
+ # Will default to the $from->address when the UserMailerChangeReturnPath hook fails and the
+ # generated VERP address when the hook runs effectively.
+ $extraParams .= ' -f ' . $returnPath;
+
+ $headers['Return-Path'] = $returnPath;
+
+ if ( $replyto ) {
+ $headers['Reply-To'] = $replyto->toString();
+ }
+
+ $headers['Date'] = MWTimestamp::getLocalInstance()->format( 'r' );
+ $headers['Message-ID'] = self::makeMsgId();
+ $headers['X-Mailer'] = 'MediaWiki mailer';
+
+ # Line endings need to be different on Unix and Windows due to
+ # the bug described at http://trac.wordpress.org/ticket/2603
+ if ( wfIsWindows() ) {
+ $endl = "\r\n";
+ } else {
+ $endl = "\n";
+ }
+
+ if ( is_array( $body ) ) {
+ // we are sending a multipart message
+ wfDebug( "Assembling multipart mime email\n" );
+ if ( !stream_resolve_include_path( 'Mail/mime.php' ) ) {
+ wfDebug( "PEAR Mail_Mime package is not installed. Falling back to text email.\n" );
+ // remove the html body for text email fall back
+ $body = $body['text'];
+ } else {
+ require_once 'Mail/mime.php';
+ if ( wfIsWindows() ) {
+ $body['text'] = str_replace( "\n", "\r\n", $body['text'] );
+ $body['html'] = str_replace( "\n", "\r\n", $body['html'] );
+ }
+ $mime = new Mail_mime( array(
+ 'eol' => $endl,
+ 'text_charset' => 'UTF-8',
+ 'html_charset' => 'UTF-8'
+ ) );
+ $mime->setTXTBody( $body['text'] );
+ $mime->setHTMLBody( $body['html'] );
+ $body = $mime->get(); // must call get() before headers()
+ $headers = $mime->headers( $headers );
+ }
+ }
+ if ( $mime === null ) {
+ // sending text only, either deliberately or as a fallback
+ if ( wfIsWindows() ) {
+ $body = str_replace( "\n", "\r\n", $body );
+ }
+ $headers['MIME-Version'] = '1.0';
+ $headers['Content-type'] = ( is_null( $contentType ) ?
+ 'text/plain; charset=UTF-8' : $contentType );
+ $headers['Content-transfer-encoding'] = '8bit';
+ }
+
+ $ret = wfRunHooks( 'AlternateUserMailer', array( $headers, $to, $from, $subject, $body ) );
+ if ( $ret === false ) {
+ // the hook implementation will return false to skip regular mail sending
+ return Status::newGood();
+ } elseif ( $ret !== true ) {
+ // the hook implementation will return a string to pass an error message
+ return Status::newFatal( 'php-mail-error', $ret );
+ }
+
+ if ( is_array( $wgSMTP ) ) {
+ #
+ # PEAR MAILER
+ #
+
+ if ( !stream_resolve_include_path( 'Mail.php' ) ) {
+ throw new MWException( 'PEAR mail package is not installed' );
+ }
+ require_once 'Mail.php';
+
+ wfSuppressWarnings();
+
+ // Create the mail object using the Mail::factory method
+ $mail_object =& Mail::factory( 'smtp', $wgSMTP );
+ if ( PEAR::isError( $mail_object ) ) {
+ wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" );
+ wfRestoreWarnings();
+ return Status::newFatal( 'pear-mail-error', $mail_object->getMessage() );
+ }
+
+ wfDebug( "Sending mail via PEAR::Mail\n" );
+
+ $headers['Subject'] = self::quotedPrintable( $subject );
+
+ # When sending only to one recipient, shows it its email using To:
+ if ( count( $to ) == 1 ) {
+ $headers['To'] = $to[0]->toString();
+ }
+
+ # Split jobs since SMTP servers tends to limit the maximum
+ # number of possible recipients.
+ $chunks = array_chunk( $to, $wgEnotifMaxRecips );
+ foreach ( $chunks as $chunk ) {
+ $status = self::sendWithPear( $mail_object, $chunk, $headers, $body );
+ # FIXME : some chunks might be sent while others are not!
+ if ( !$status->isOK() ) {
+ wfRestoreWarnings();
+ return $status;
+ }
+ }
+ wfRestoreWarnings();
+ return Status::newGood();
+ } else {
+ #
+ # PHP mail()
+ #
+ if ( count( $to ) > 1 ) {
+ $headers['To'] = 'undisclosed-recipients:;';
+ }
+ $headers = self::arrayToHeaderString( $headers, $endl );
+
+ wfDebug( "Sending mail via internal mail() function\n" );
+
+ self::$mErrorString = '';
+ $html_errors = ini_get( 'html_errors' );
+ ini_set( 'html_errors', '0' );
+ set_error_handler( 'UserMailer::errorHandler' );
+
+ try {
+ $safeMode = wfIniGetBool( 'safe_mode' );
+
+ foreach ( $to as $recip ) {
+ if ( $safeMode ) {
+ $sent = mail( $recip, self::quotedPrintable( $subject ), $body, $headers );
+ } else {
+ $sent = mail(
+ $recip,
+ self::quotedPrintable( $subject ),
+ $body,
+ $headers,
+ $extraParams
+ );
+ }
+ }
+ } catch ( Exception $e ) {
+ restore_error_handler();
+ throw $e;
+ }
+
+ restore_error_handler();
+ ini_set( 'html_errors', $html_errors );
+
+ if ( self::$mErrorString ) {
+ wfDebug( "Error sending mail: " . self::$mErrorString . "\n" );
+ return Status::newFatal( 'php-mail-error', self::$mErrorString );
+ } elseif ( !$sent ) {
+ // mail function only tells if there's an error
+ wfDebug( "Unknown error sending mail\n" );
+ return Status::newFatal( 'php-mail-error-unknown' );
+ } else {
+ return Status::newGood();
+ }
+ }
+ }
+
+ /**
+ * Set the mail error message in self::$mErrorString
+ *
+ * @param int $code Error number
+ * @param string $string Error message
+ */
+ static function errorHandler( $code, $string ) {
+ self::$mErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
+ }
+
+ /**
+ * Strips bad characters from a header value to prevent PHP mail header injection attacks
+ * @param string $val String to be santizied
+ * @return string
+ */
+ public static function sanitizeHeaderValue( $val ) {
+ return strtr( $val, array( "\r" => '', "\n" => '' ) );
+ }
+
+ /**
+ * Converts a string into a valid RFC 822 "phrase", such as is used for the sender name
+ * @param string $phrase
+ * @return string
+ */
+ public static function rfc822Phrase( $phrase ) {
+ // Remove line breaks
+ $phrase = self::sanitizeHeaderValue( $phrase );
+ // Remove quotes
+ $phrase = str_replace( '"', '', $phrase );
+ return '"' . $phrase . '"';
+ }
+
+ /**
+ * Converts a string into quoted-printable format
+ * @since 1.17
+ *
+ * From PHP5.3 there is a built in function quoted_printable_encode()
+ * This method does not duplicate that.
+ * This method is doing Q encoding inside encoded-words as defined by RFC 2047
+ * This is for email headers.
+ * The built in quoted_printable_encode() is for email bodies
+ * @param string $string
+ * @param string $charset
+ * @return string
+ */
+ public static function quotedPrintable( $string, $charset = '' ) {
+ # Probably incomplete; see RFC 2045
+ if ( empty( $charset ) ) {
+ $charset = 'UTF-8';
+ }
+ $charset = strtoupper( $charset );
+ $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
+
+ $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
+ $replace = $illegal . '\t ?_';
+ if ( !preg_match( "/[$illegal]/", $string ) ) {
+ return $string;
+ }
+ $out = "=?$charset?Q?";
+ $out .= preg_replace_callback( "/([$replace])/",
+ array( __CLASS__, 'quotedPrintableCallback' ), $string );
+ $out .= '?=';
+ return $out;
+ }
+
+ protected static function quotedPrintableCallback( $matches ) {
+ return sprintf( "=%02X", ord( $matches[1] ) );
+ }
+}
diff --git a/includes/media/BMP.php b/includes/media/BMP.php
index 99b7741a..d8b0ba64 100644
--- a/includes/media/BMP.php
+++ b/includes/media/BMP.php
@@ -28,9 +28,8 @@
* @ingroup Media
*/
class BmpHandler extends BitmapHandler {
-
/**
- * @param $file
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -40,9 +39,9 @@ class BmpHandler extends BitmapHandler {
/**
* Render files as PNG
*
- * @param $text
- * @param $mime
- * @param $params
+ * @param string $text
+ * @param string $mime
+ * @param array $params
* @return array
*/
function getThumbType( $text, $mime, $params = null ) {
@@ -52,8 +51,8 @@ class BmpHandler extends BitmapHandler {
/**
* Get width and height from the bmp header.
*
- * @param $image
- * @param $filename
+ * @param File $image
+ * @param string $filename
* @return array
*/
function getImageSize( $image, $filename ) {
@@ -75,6 +74,7 @@ class BmpHandler extends BitmapHandler {
} catch ( MWException $e ) {
return false;
}
+
return array( $w[1], $h[1] );
}
}
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index 79b0497d..e81b37de 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -26,204 +26,17 @@
*
* @ingroup Media
*/
-class BitmapHandler extends ImageHandler {
- /**
- * @param $image File
- * @param array $params Transform parameters. Entries with the keys 'width'
- * and 'height' are the respective screen width and height, while the keys
- * 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
- * @return bool
- */
- function normaliseParams( $image, &$params ) {
- if ( !parent::normaliseParams( $image, $params ) ) {
- return false;
- }
-
- # Obtain the source, pre-rotation dimensions
- $srcWidth = $image->getWidth( $params['page'] );
- $srcHeight = $image->getHeight( $params['page'] );
-
- # Don't make an image bigger than the source
- if ( $params['physicalWidth'] >= $srcWidth ) {
- $params['physicalWidth'] = $srcWidth;
- $params['physicalHeight'] = $srcHeight;
-
- # Skip scaling limit checks if no scaling is required
- # due to requested size being bigger than source.
- if ( !$image->mustRender() ) {
- return true;
- }
- }
-
- # Check if the file is smaller than the maximum image area for thumbnailing
- $checkImageAreaHookResult = null;
- wfRunHooks( 'BitmapHandlerCheckImageArea', array( $image, &$params, &$checkImageAreaHookResult ) );
- if ( is_null( $checkImageAreaHookResult ) ) {
- global $wgMaxImageArea;
-
- if ( $srcWidth * $srcHeight > $wgMaxImageArea &&
- !( $image->getMimeType() == 'image/jpeg' &&
- self::getScalerType( false, false ) == 'im' ) ) {
- # Only ImageMagick can efficiently downsize jpg images without loading
- # the entire file in memory
- return false;
- }
- } else {
- return $checkImageAreaHookResult;
- }
-
- return true;
- }
-
- /**
- * Extracts the width/height if the image will be scaled before rotating
- *
- * This will match the physical size/aspect ratio of the original image
- * prior to application of the rotation -- so for a portrait image that's
- * stored as raw landscape with 90-degress rotation, the resulting size
- * will be wider than it is tall.
- *
- * @param array $params Parameters as returned by normaliseParams
- * @param int $rotation The rotation angle that will be applied
- * @return array ($width, $height) array
- */
- public function extractPreRotationDimensions( $params, $rotation ) {
- if ( $rotation == 90 || $rotation == 270 ) {
- # We'll resize before rotation, so swap the dimensions again
- $width = $params['physicalHeight'];
- $height = $params['physicalWidth'];
- } else {
- $width = $params['physicalWidth'];
- $height = $params['physicalHeight'];
- }
- return array( $width, $height );
- }
-
- /**
- * @param $image File
- * @param $dstPath
- * @param $dstUrl
- * @param $params
- * @param int $flags
- * @return MediaTransformError|ThumbnailImage|TransformParameterError
- */
- function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
- if ( !$this->normaliseParams( $image, $params ) ) {
- return new TransformParameterError( $params );
- }
- # Create a parameter array to pass to the scaler
- $scalerParams = array(
- # The size to which the image will be resized
- 'physicalWidth' => $params['physicalWidth'],
- 'physicalHeight' => $params['physicalHeight'],
- 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
- # The size of the image on the page
- 'clientWidth' => $params['width'],
- 'clientHeight' => $params['height'],
- # Comment as will be added to the Exif of the thumbnail
- 'comment' => isset( $params['descriptionUrl'] ) ?
- "File source: {$params['descriptionUrl']}" : '',
- # Properties of the original image
- 'srcWidth' => $image->getWidth(),
- 'srcHeight' => $image->getHeight(),
- 'mimeType' => $image->getMimeType(),
- 'dstPath' => $dstPath,
- 'dstUrl' => $dstUrl,
- );
-
- # Determine scaler type
- $scaler = self::getScalerType( $dstPath );
-
- wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath using scaler $scaler\n" );
-
- if ( !$image->mustRender() &&
- $scalerParams['physicalWidth'] == $scalerParams['srcWidth']
- && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] ) {
-
- # normaliseParams (or the user) wants us to return the unscaled image
- wfDebug( __METHOD__ . ": returning unscaled image\n" );
- return $this->getClientScalingThumbnailImage( $image, $scalerParams );
- }
-
- if ( $scaler == 'client' ) {
- # Client-side image scaling, use the source URL
- # Using the destination URL in a TRANSFORM_LATER request would be incorrect
- return $this->getClientScalingThumbnailImage( $image, $scalerParams );
- }
-
- if ( $flags & self::TRANSFORM_LATER ) {
- wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
- $params = array(
- 'width' => $scalerParams['clientWidth'],
- 'height' => $scalerParams['clientHeight']
- );
- return new ThumbnailImage( $image, $dstUrl, false, $params );
- }
-
- # Try to make a target path for the thumbnail
- if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
- wfDebug( __METHOD__ . ": Unable to create thumbnail destination directory, falling back to client scaling\n" );
- return $this->getClientScalingThumbnailImage( $image, $scalerParams );
- }
-
- # Transform functions and binaries need a FS source file
- $scalerParams['srcPath'] = $image->getLocalRefPath();
-
- # Try a hook
- $mto = null;
- wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
- if ( !is_null( $mto ) ) {
- wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" );
- $scaler = 'hookaborted';
- }
-
- switch ( $scaler ) {
- case 'hookaborted':
- # Handled by the hook above
- $err = $mto->isError() ? $mto : false;
- break;
- case 'im':
- $err = $this->transformImageMagick( $image, $scalerParams );
- break;
- case 'custom':
- $err = $this->transformCustom( $image, $scalerParams );
- break;
- case 'imext':
- $err = $this->transformImageMagickExt( $image, $scalerParams );
- break;
- case 'gd':
- default:
- $err = $this->transformGd( $image, $scalerParams );
- break;
- }
-
- # Remove the file if a zero-byte thumbnail was created, or if there was an error
- $removed = $this->removeBadFile( $dstPath, (bool)$err );
- if ( $err ) {
- # transform returned MediaTransforError
- return $err;
- } elseif ( $removed ) {
- # Thumbnail was zero-byte and had to be removed
- return new MediaTransformError( 'thumbnail_error',
- $scalerParams['clientWidth'], $scalerParams['clientHeight'] );
- } elseif ( $mto ) {
- return $mto;
- } else {
- $params = array(
- 'width' => $scalerParams['clientWidth'],
- 'height' => $scalerParams['clientHeight']
- );
- return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
- }
- }
+class BitmapHandler extends TransformationalImageHandler {
/**
* Returns which scaler type should be used. Creates parent directories
* for $dstPath and returns 'client' on error
*
- * @return string client,im,custom,gd
+ * @param string $dstPath
+ * @param bool $checkDstPath
+ * @return string|Callable One of client, im, custom, gd, imext or an array( object, method )
*/
- protected static function getScalerType( $dstPath, $checkDstPath = true ) {
+ protected function getScalerType( $dstPath, $checkDstPath = true ) {
global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
if ( !$dstPath && $checkDstPath ) {
@@ -242,39 +55,21 @@ class BitmapHandler extends ImageHandler {
} else {
$scaler = 'client';
}
- return $scaler;
- }
- /**
- * Get a ThumbnailImage that respresents an image that will be scaled
- * client side
- *
- * @param $image File File associated with this thumbnail
- * @param array $scalerParams Array with scaler params
- * @return ThumbnailImage
- *
- * @todo fixme: no rotation support
- */
- protected function getClientScalingThumbnailImage( $image, $scalerParams ) {
- $params = array(
- 'width' => $scalerParams['clientWidth'],
- 'height' => $scalerParams['clientHeight']
- );
- return new ThumbnailImage( $image, $image->getURL(), null, $params );
+ return $scaler;
}
/**
* Transform an image using ImageMagick
*
- * @param $image File File associated with this thumbnail
+ * @param File $image File associated with this thumbnail
* @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
*/
protected function transformImageMagick( $image, $params ) {
# use ImageMagick
- global $wgSharpenReductionThreshold, $wgSharpenParameter,
- $wgMaxAnimatedGifArea,
+ global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
$wgImageMagickTempDir, $wgImageMagickConvertCommand;
$quality = array();
@@ -284,18 +79,19 @@ class BitmapHandler extends ImageHandler {
$animation_post = array();
$decoderHint = array();
if ( $params['mimeType'] == 'image/jpeg' ) {
- $quality = array( '-quality', '80' ); // 80%
+ $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
+ $quality = array( '-quality', $qualityVal ?: '80' ); // 80%
# Sharpening, see bug 6193
if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
- / ( $params['srcWidth'] + $params['srcHeight'] )
- < $wgSharpenReductionThreshold ) {
+ / ( $params['srcWidth'] + $params['srcHeight'] )
+ < $wgSharpenReductionThreshold
+ ) {
$sharpen = array( '-sharpen', $wgSharpenParameter );
}
if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) {
// JPEG decoder hint to reduce memory, available since IM 6.5.6-2
$decoderHint = array( '-define', "jpeg:size={$params['physicalDimensions']}" );
}
-
} elseif ( $params['mimeType'] == 'image/png' ) {
$quality = array( '-quality', '95' ); // zlib 9, adaptive filtering
@@ -304,7 +100,6 @@ class BitmapHandler extends ImageHandler {
// Extract initial frame only; we're so big it'll
// be a total drag. :P
$scene = 0;
-
} elseif ( $this->isAnimatedImage( $image ) ) {
// Coalesce is needed to scale animated GIFs properly (bug 1017).
$animation_pre = array( '-coalesce' );
@@ -315,7 +110,30 @@ class BitmapHandler extends ImageHandler {
}
}
} elseif ( $params['mimeType'] == 'image/x-xcf' ) {
- $animation_post = array( '-layers', 'merge' );
+ // Before merging layers, we need to set the background
+ // to be transparent to preserve alpha, as -layers merge
+ // merges all layers on to a canvas filled with the
+ // background colour. After merging we reset the background
+ // to be white for the default background colour setting
+ // in the PNG image (which is used in old IE)
+ $animation_pre = array(
+ '-background', 'transparent',
+ '-layers', 'merge',
+ '-background', 'white',
+ );
+ wfSuppressWarnings();
+ $xcfMeta = unserialize( $image->getMetadata() );
+ wfRestoreWarnings();
+ if ( $xcfMeta
+ && isset( $xcfMeta['colorType'] )
+ && $xcfMeta['colorType'] === 'greyscale-alpha'
+ && version_compare( $this->getMagickVersion(), "6.8.9-3" ) < 0
+ ) {
+ // bug 66323 - Greyscale images not rendered properly.
+ // So only take the "red" channel.
+ $channelOnly = array( '-channel', 'R', '-separate' );
+ $animation_pre = array_merge( $animation_pre, $channelOnly );
+ }
}
// Use one thread only, to avoid deadlock bugs on OOM
@@ -358,7 +176,8 @@ class BitmapHandler extends ImageHandler {
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
- return $this->getMediaTransformError( $params, $err );
+
+ return $this->getMediaTransformError( $params, "$err\nError code: $retval" );
}
return false; # No error
@@ -367,7 +186,7 @@ class BitmapHandler extends ImageHandler {
/**
* Transform an image using the Imagick PHP extension
*
- * @param $image File File associated with this thumbnail
+ * @param File $image File associated with this thumbnail
* @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
@@ -382,13 +201,15 @@ class BitmapHandler extends ImageHandler {
if ( $params['mimeType'] == 'image/jpeg' ) {
// Sharpening, see bug 6193
if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
- / ( $params['srcWidth'] + $params['srcHeight'] )
- < $wgSharpenReductionThreshold ) {
+ / ( $params['srcWidth'] + $params['srcHeight'] )
+ < $wgSharpenReductionThreshold
+ ) {
// Hack, since $wgSharpenParamater is written specifically for the command line convert
list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
$im->sharpenImage( $radius, $sigma );
}
- $im->setCompressionQuality( 80 );
+ $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
+ $im->setCompressionQuality( $qualityVal ?: 80 );
} elseif ( $params['mimeType'] == 'image/png' ) {
$im->setCompressionQuality( 95 );
} elseif ( $params['mimeType'] == 'image/gif' ) {
@@ -432,19 +253,17 @@ class BitmapHandler extends ImageHandler {
return $this->getMediaTransformError( $params,
"Unable to write thumbnail to {$params['dstPath']}" );
}
-
} catch ( ImagickException $e ) {
return $this->getMediaTransformError( $params, $e->getMessage() );
}
return false;
-
}
/**
* Transform an image using a custom command
*
- * @param $image File File associated with this thumbnail
+ * @param File $image File associated with this thumbnail
* @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
@@ -468,39 +287,17 @@ class BitmapHandler extends ImageHandler {
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
+
return $this->getMediaTransformError( $params, $err );
}
- return false; # No error
- }
- /**
- * Log an error that occurred in an external process
- *
- * @param $retval int
- * @param $err int
- * @param $cmd string
- */
- protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim( $err ), $cmd ) );
- }
- /**
- * Get a MediaTransformError with error 'thumbnail_error'
- *
- * @param array $params Parameter array as passed to the transform* functions
- * @param string $errMsg Error message
- * @return MediaTransformError
- */
- public function getMediaTransformError( $params, $errMsg ) {
- return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
- $params['clientHeight'], $errMsg );
+ return false; # No error
}
/**
* Transform an image using the built in GD library
*
- * @param $image File File associated with this thumbnail
+ * @param File $image File associated with this thumbnail
* @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
@@ -512,24 +309,28 @@ class BitmapHandler extends ImageHandler {
# input routine for this.
$typemap = array(
- 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
- 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
- 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
- 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
- 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
+ 'image/gif' => array( 'imagecreatefromgif', 'palette', false, 'imagegif' ),
+ 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', true,
+ array( __CLASS__, 'imageJpegWrapper' ) ),
+ 'image/png' => array( 'imagecreatefrompng', 'bits', false, 'imagepng' ),
+ 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', false, 'imagewbmp' ),
+ 'image/xbm' => array( 'imagecreatefromxbm', 'palette', false, 'imagexbm' ),
);
+
if ( !isset( $typemap[$params['mimeType']] ) ) {
$err = 'Image type not supported';
wfDebug( "$err\n" );
$errMsg = wfMessage( 'thumbnail_image-type' )->text();
+
return $this->getMediaTransformError( $params, $errMsg );
}
- list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']];
+ list( $loader, $colorStyle, $useQuality, $saveType ) = $typemap[$params['mimeType']];
if ( !function_exists( $loader ) ) {
$err = "Incomplete GD library configuration: missing function $loader";
wfDebug( "$err\n" );
$errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text();
+
return $this->getMediaTransformError( $params, $errMsg );
}
@@ -537,6 +338,7 @@ class BitmapHandler extends ImageHandler {
$err = "File seems to be missing: {$params['srcPath']}";
wfDebug( "$err\n" );
$errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text();
+
return $this->getMediaTransformError( $params, $errMsg );
}
@@ -574,7 +376,12 @@ class BitmapHandler extends ImageHandler {
imagesavealpha( $dst_image, true );
- call_user_func( $saveType, $dst_image, $params['dstPath'] );
+ $funcParams = array( $dst_image, $params['dstPath'] );
+ if ( $useQuality && isset( $params['quality'] ) ) {
+ $funcParams[] = $params['quality'];
+ }
+ call_user_func_array( $saveType, $funcParams );
+
imagedestroy( $dst_image );
imagedestroy( $src_image );
@@ -582,135 +389,21 @@ class BitmapHandler extends ImageHandler {
}
/**
- * Escape a string for ImageMagick's property input (e.g. -set -comment)
- * See InterpretImageProperties() in magick/property.c
- * @return mixed|string
- */
- function escapeMagickProperty( $s ) {
- // Double the backslashes
- $s = str_replace( '\\', '\\\\', $s );
- // Double the percents
- $s = str_replace( '%', '%%', $s );
- // Escape initial - or @
- if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) {
- $s = '\\' . $s;
- }
- return $s;
- }
-
- /**
- * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
- * and GetPathComponent() in magick/utility.c.
- *
- * This won't work with an initial ~ or @, so input files should be prefixed
- * with the directory name.
- *
- * Glob character unescaping is broken in ImageMagick before 6.6.1-5, but
- * it's broken in a way that doesn't involve trying to convert every file
- * in a directory, so we're better off escaping and waiting for the bugfix
- * to filter down to users.
- *
- * @param string $path The file path
- * @param bool|string $scene The scene specification, or false if there is none
- * @throws MWException
- * @return string
- */
- function escapeMagickInput( $path, $scene = false ) {
- # Die on initial metacharacters (caller should prepend path)
- $firstChar = substr( $path, 0, 1 );
- if ( $firstChar === '~' || $firstChar === '@' ) {
- throw new MWException( __METHOD__ . ': cannot escape this path name' );
- }
-
- # Escape glob chars
- $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path );
-
- return $this->escapeMagickPath( $path, $scene );
- }
-
- /**
- * Escape a string for ImageMagick's output filename. See
- * InterpretImageFilename() in magick/image.c.
- * @return string
- */
- function escapeMagickOutput( $path, $scene = false ) {
- $path = str_replace( '%', '%%', $path );
- return $this->escapeMagickPath( $path, $scene );
- }
-
- /**
- * Armour a string against ImageMagick's GetPathComponent(). This is a
- * helper function for escapeMagickInput() and escapeMagickOutput().
- *
- * @param string $path The file path
- * @param bool|string $scene The scene specification, or false if there is none
- * @throws MWException
- * @return string
- */
- protected function escapeMagickPath( $path, $scene = false ) {
- # Die on format specifiers (other than drive letters). The regex is
- # meant to match all the formats you get from "convert -list format"
- if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) {
- if ( wfIsWindows() && is_dir( $m[0] ) ) {
- // OK, it's a drive letter
- // ImageMagick has a similar exception, see IsMagickConflict()
- } else {
- throw new MWException( __METHOD__ . ': unexpected colon character in path name' );
- }
- }
-
- # If there are square brackets, add a do-nothing scene specification
- # to force a literal interpretation
- if ( $scene === false ) {
- if ( strpos( $path, '[' ) !== false ) {
- $path .= '[0--1]';
- }
- } else {
- $path .= "[$scene]";
- }
- return $path;
- }
-
- /**
- * Retrieve the version of the installed ImageMagick
- * You can use PHPs version_compare() to use this value
- * Value is cached for one hour.
- * @return String representing the IM version.
+ * Callback for transformGd when transforming jpeg images.
*/
- protected function getMagickVersion() {
- global $wgMemc;
-
- $cache = $wgMemc->get( "imagemagick-version" );
- if ( !$cache ) {
- global $wgImageMagickConvertCommand;
- $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version';
- wfDebug( __METHOD__ . ": Running convert -version\n" );
- $retval = '';
- $return = wfShellExec( $cmd, $retval );
- $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches );
- if ( $x != 1 ) {
- wfDebug( __METHOD__ . ": ImageMagick version check failed\n" );
- return null;
- }
- $wgMemc->set( "imagemagick-version", $matches[1], 3600 );
- return $matches[1];
- }
- return $cache;
- }
-
- static function imageJpegWrapper( $dst_image, $thumbPath ) {
+ // FIXME: transformImageMagick() & transformImageMagickExt() uses JPEG quality 80, here it's 95?
+ static function imageJpegWrapper( $dst_image, $thumbPath, $quality = 95 ) {
imageinterlace( $dst_image );
- imagejpeg( $dst_image, $thumbPath, 95 );
+ imagejpeg( $dst_image, $thumbPath, $quality );
}
-
/**
* Returns whether the current scaler supports rotation (im and gd do)
*
* @return bool
*/
- public static function canRotate() {
- $scaler = self::getScalerType( null, false );
+ public function canRotate() {
+ $scaler = $this->getScalerType( null, false );
switch ( $scaler ) {
case 'im':
# ImageMagick supports autorotation
@@ -729,9 +422,24 @@ class BitmapHandler extends ImageHandler {
}
/**
- * @param $file File
+ * @see $wgEnableAutoRotation
+ * @return bool Whether auto rotation is enabled
+ */
+ public function autoRotateEnabled() {
+ global $wgEnableAutoRotation;
+
+ if ( $wgEnableAutoRotation === null ) {
+ // Only enable auto-rotation when we actually can
+ return $this->canRotate();
+ }
+
+ return $wgEnableAutoRotation;
+ }
+
+ /**
+ * @param File $file
* @param array $params Rotate parameters.
- * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
+ * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
* @since 1.21
* @return bool
*/
@@ -741,7 +449,7 @@ class BitmapHandler extends ImageHandler {
$rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
$scene = false;
- $scaler = self::getScalerType( null, false );
+ $scaler = $this->getScalerType( null, false );
switch ( $scaler ) {
case 'im':
$cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " .
@@ -751,12 +459,14 @@ class BitmapHandler extends ImageHandler {
wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
wfProfileIn( 'convert' );
$retval = 0;
- $err = wfShellExecWithStderr( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval );
wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
+
return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
}
+
return false;
case 'imext':
$im = new Imagick();
@@ -770,21 +480,11 @@ class BitmapHandler extends ImageHandler {
return new MediaTransformError( 'thumbnail_error', 0, 0,
"Unable to write image to {$params['dstPath']}" );
}
+
return false;
default:
return new MediaTransformError( 'thumbnail_error', 0, 0,
"$scaler rotation not implemented" );
}
}
-
- /**
- * Rerurns whether the file needs to be rendered. Returns true if the
- * file requires rotation and we are able to rotate it.
- *
- * @param $file File
- * @return bool
- */
- public function mustRender( $file ) {
- return self::canRotate() && $this->getRotation( $file ) != 0;
- }
}
diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php
index 7c39c814..dd41c388 100644
--- a/includes/media/BitmapMetadataHandler.php
+++ b/includes/media/BitmapMetadataHandler.php
@@ -28,12 +28,14 @@
* This sort of acts as an intermediary between MediaHandler::getMetadata
* and the various metadata extractors.
*
- * @todo other image formats.
+ * @todo Other image formats.
* @ingroup Media
*/
class BitmapMetadataHandler {
-
+ /** @var array */
private $metadata = array();
+
+ /** @var array Metadata priority */
private $metaPriority = array(
20 => array( 'other' ),
40 => array( 'native' ),
@@ -44,6 +46,8 @@ class BitmapMetadataHandler {
100 => array( 'iptc-bad-hash' ),
120 => array( 'exif' ),
);
+
+ /** @var string */
private $iptcType = 'iptc-no-hash';
/**
@@ -76,8 +80,8 @@ class BitmapMetadataHandler {
*
* Parameters are passed to the Exif class.
*
- * @param $filename string
- * @param $byteOrder string
+ * @param string $filename
+ * @param string $byteOrder
*/
function getExif( $filename, $byteOrder ) {
global $wgShowEXIF;
@@ -89,11 +93,12 @@ class BitmapMetadataHandler {
}
}
}
+
/** Add misc metadata. Warning: atm if the metadata category
* doesn't have a priority, it will be silently discarded.
*
- * @param array $metaArray array of metadata values
- * @param string $type type. defaults to other. if two things have the same type they're merged
+ * @param array $metaArray Array of metadata values
+ * @param string $type Type. defaults to other. if two things have the same type they're merged
*/
function addMetadata( $metaArray, $type = 'other' ) {
if ( isset( $this->metadata[$type] ) ) {
@@ -111,12 +116,12 @@ class BitmapMetadataHandler {
*
* This function is generally called by the media handlers' getMetadata()
*
- * @return Array metadata array
+ * @return array Metadata array
*/
function getMetadataArray() {
// this seems a bit ugly... This is all so its merged in right order
// based on the MWG recomendation.
- $temp = Array();
+ $temp = array();
krsort( $this->metaPriority );
foreach ( $this->metaPriority as $pri ) {
foreach ( $pri as $type ) {
@@ -138,14 +143,15 @@ class BitmapMetadataHandler {
}
}
}
+
return $temp;
}
/** Main entry point for jpeg's.
*
- * @param string $filename filename (with full path)
- * @return array metadata result array.
- * @throws MWException on invalid file.
+ * @param string $filename Filename (with full path)
+ * @return array Metadata result array.
+ * @throws MWException On invalid file.
*/
static function Jpeg( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
@@ -153,7 +159,7 @@ class BitmapMetadataHandler {
$seg = JpegMetadataExtractor::segmentSplitter( $filename );
if ( isset( $seg['COM'] ) && isset( $seg['COM'][0] ) ) {
- $meta->addMetadata( Array( 'JPEGFileComment' => $seg['COM'] ), 'native' );
+ $meta->addMetadata( array( 'JPEGFileComment' => $seg['COM'] ), 'native' );
}
if ( isset( $seg['PSIR'] ) && count( $seg['PSIR'] ) > 0 ) {
foreach ( $seg['PSIR'] as $curPSIRValue ) {
@@ -168,7 +174,6 @@ class BitmapMetadataHandler {
* is not well tested and a bit fragile.
*/
$xmp->parseExtended( $xmpExt );
-
}
$res = $xmp->getResults();
foreach ( $res as $type => $array ) {
@@ -178,6 +183,7 @@ class BitmapMetadataHandler {
if ( isset( $seg['byteOrder'] ) ) {
$meta->getExif( $filename, $seg['byteOrder'] );
}
+
return $meta->getMetadataArray();
}
@@ -186,15 +192,17 @@ class BitmapMetadataHandler {
* merge the png various tEXt chunks to that
* are interesting, but for now it only does XMP
*
- * @param string $filename full path to file
- * @return Array Array for storage in img_metadata.
+ * @param string $filename Full path to file
+ * @return array Array for storage in img_metadata.
*/
public static function PNG( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
$meta = new self();
$array = PNGMetadataExtractor::getMetadata( $filename );
- if ( isset( $array['text']['xmp']['x-default'] ) && $array['text']['xmp']['x-default'] !== '' && $showXMP ) {
+ if ( isset( $array['text']['xmp']['x-default'] )
+ && $array['text']['xmp']['x-default'] !== '' && $showXMP
+ ) {
$xmp = new XMPReader();
$xmp->parse( $array['text']['xmp']['x-default'] );
$xmpRes = $xmp->getResults();
@@ -207,6 +215,7 @@ class BitmapMetadataHandler {
unset( $array['text'] );
$array['metadata'] = $meta->getMetadataArray();
$array['metadata']['_MW_PNG_VERSION'] = PNGMetadataExtractor::VERSION;
+
return $array;
}
@@ -215,8 +224,8 @@ class BitmapMetadataHandler {
* They don't really have native metadata, so just merges together
* XMP and image comment.
*
- * @param string $filename full path to file
- * @return Array metadata array
+ * @param string $filename Full path to file
+ * @return array Metadata array
*/
public static function GIF( $filename ) {
@@ -234,7 +243,6 @@ class BitmapMetadataHandler {
foreach ( $xmpRes as $type => $xmpSection ) {
$meta->addMetadata( $xmpSection, $type );
}
-
}
unset( $baseArray['comment'] );
@@ -242,6 +250,7 @@ class BitmapMetadataHandler {
$baseArray['metadata'] = $meta->getMetadataArray();
$baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION;
+
return $baseArray;
}
@@ -251,13 +260,12 @@ class BitmapMetadataHandler {
* but needs some further processing because PHP's exif support
* is stupid...)
*
- * @todo Add XMP support, so this function actually makes
- * sense to put here.
+ * @todo Add XMP support, so this function actually makes sense to put here.
*
* The various exceptions this throws are caught later.
- * @param $filename String
+ * @param string $filename
* @throws MWException
- * @return Array The metadata.
+ * @return array The metadata.
*/
public static function Tiff( $filename ) {
if ( file_exists( $filename ) ) {
@@ -269,6 +277,7 @@ class BitmapMetadataHandler {
$data = $exif->getFilteredData();
if ( $data ) {
$data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
return $data;
} else {
throw new MWException( "Could not extract data from tiff file $filename" );
@@ -277,12 +286,13 @@ class BitmapMetadataHandler {
throw new MWException( "File doesn't exist - $filename" );
}
}
+
/**
* Read the first 2 bytes of a tiff file to figure out
* Little Endian or Big Endian. Needed for exif stuff.
*
* @param string $filename The filename
- * @return String 'BE' or 'LE' or false
+ * @return string 'BE' or 'LE' or false
*/
static function getTiffByteOrder( $filename ) {
$fh = fopen( $filename, 'rb' );
diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php
index 63af2552..b91fb8aa 100644
--- a/includes/media/Bitmap_ClientOnly.php
+++ b/includes/media/Bitmap_ClientOnly.php
@@ -29,11 +29,13 @@
*
* @ingroup Media
*/
+// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
class BitmapHandler_ClientOnly extends BitmapHandler {
+ // @codingStandardsIgnoreEnd
/**
- * @param $image File
- * @param $params
+ * @param File $image
+ * @param array $params
* @return bool
*/
function normaliseParams( $image, &$params ) {
@@ -41,10 +43,10 @@ class BitmapHandler_ClientOnly extends BitmapHandler {
}
/**
- * @param $image File
- * @param $dstPath
- * @param $dstUrl
- * @param $params
+ * @param File $image
+ * @param string $dstPath
+ * @param string $dstUrl
+ * @param array $params
* @param int $flags
* @return ThumbnailImage|TransformParameterError
*/
@@ -52,6 +54,7 @@ class BitmapHandler_ClientOnly extends BitmapHandler {
if ( !$this->normaliseParams( $image, $params ) ) {
return new TransformParameterError( $params );
}
+
return new ThumbnailImage( $image, $image->getURL(), $image->getLocalRefPath(), $params );
}
}
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index 9b8116e9..daeb475f 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -27,7 +27,6 @@
* @ingroup Media
*/
class DjVuHandler extends ImageHandler {
-
/**
* @return bool
*/
@@ -35,6 +34,7 @@ class DjVuHandler extends ImageHandler {
global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
+
return false;
} else {
return true;
@@ -42,7 +42,7 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $file
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -50,7 +50,7 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $file
+ * @param File $file
* @return bool
*/
function isMultiPage( $file ) {
@@ -68,11 +68,16 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $name
- * @param $value
+ * @param string $name
+ * @param mixed $value
* @return bool
*/
function validateParam( $name, $value ) {
+ if ( $name === 'page' && trim( $value ) !== (string)intval( $value ) ) {
+ // Extra junk on the end of page, probably actually a caption
+ // e.g. [[File:Foo.djvu|thumb|Page 3 of the document shows foo]]
+ return false;
+ }
if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) {
if ( $value <= 0 ) {
return false;
@@ -85,7 +90,7 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $params
+ * @param array $params
* @return bool|string
*/
function makeParamString( $params ) {
@@ -93,11 +98,12 @@ class DjVuHandler extends ImageHandler {
if ( !isset( $params['width'] ) ) {
return false;
}
+
return "page{$page}-{$params['width']}px";
}
/**
- * @param $str
+ * @param string $str
* @return array|bool
*/
function parseParamString( $str ) {
@@ -110,7 +116,7 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $params
+ * @param array $params
* @return array
*/
function getScriptParams( $params ) {
@@ -121,10 +127,10 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $image File
- * @param $dstPath
- * @param $dstUrl
- * @param $params
+ * @param File $image
+ * @param string $dstPath
+ * @param string $dstUrl
+ * @param array $params
* @param int $flags
* @return MediaTransformError|ThumbnailImage|TransformParameterError
*/
@@ -137,6 +143,7 @@ class DjVuHandler extends ImageHandler {
if ( !$xml ) {
$width = isset( $params['width'] ) ? $params['width'] : 0;
$height = isset( $params['height'] ) ? $params['height'] : 0;
+
return new MediaTransformError( 'thumbnail_error', $width, $height,
wfMessage( 'djvu_no_xml' )->text() );
}
@@ -162,6 +169,7 @@ class DjVuHandler extends ImageHandler {
'height' => $height,
'page' => $page
);
+
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
}
@@ -174,7 +182,33 @@ class DjVuHandler extends ImageHandler {
);
}
- $srcPath = $image->getLocalRefPath();
+ // Get local copy source for shell scripts
+ // Thumbnail extraction is very inefficient for large files.
+ // Provide a way to pool count limit the number of downloaders.
+ if ( $image->getSize() >= 1e7 ) { // 10MB
+ $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $image->getName() ),
+ array(
+ 'doWork' => function () use ( $image ) {
+ return $image->getLocalRefPath();
+ }
+ )
+ );
+ $srcPath = $work->execute();
+ } else {
+ $srcPath = $image->getLocalRefPath();
+ }
+
+ if ( $srcPath === false ) { // Failed to get local copy
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+ wfHostname(), $image->getName() ) );
+
+ return new MediaTransformError( 'thumbnail_error',
+ $params['width'], $params['height'],
+ wfMessage( 'filemissing' )->text()
+ );
+ }
+
# Use a subshell (brackets) to aggregate stderr from both pipeline commands
# before redirecting it to the overall stdout. This works in both Linux and Windows XP.
$cmd = '(' . wfEscapeShellArg(
@@ -195,9 +229,7 @@ class DjVuHandler extends ImageHandler {
$removed = $this->removeBadFile( $dstPath, $retval );
if ( $retval != 0 || $removed ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim( $err ), $cmd ) );
+ $this->logErrorForExternalProcess( $retval, $err, $cmd );
return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
} else {
$params = array(
@@ -205,6 +237,7 @@ class DjVuHandler extends ImageHandler {
'height' => $height,
'page' => $page
);
+
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
}
}
@@ -212,6 +245,8 @@ class DjVuHandler extends ImageHandler {
/**
* Cache an instance of DjVuImage in an Image object, return that instance
*
+ * @param File $image
+ * @param string $path
* @return DjVuImage
*/
function getDjVuImage( $image, $path ) {
@@ -222,23 +257,59 @@ class DjVuHandler extends ImageHandler {
} else {
$deja = $image->dejaImage;
}
+
return $deja;
}
/**
+ * Get metadata, unserializing it if neccessary.
+ *
+ * @param File $file The DjVu file in question
+ * @return string XML metadata as a string.
+ */
+ private function getUnserializedMetadata( File $file ) {
+ $metadata = $file->getMetadata();
+ if ( substr( $metadata, 0, 3 ) === '<?xml' ) {
+ // Old style. Not serialized but instead just a raw string of XML.
+ return $metadata;
+ }
+
+ wfSuppressWarnings();
+ $unser = unserialize( $metadata );
+ wfRestoreWarnings();
+ if ( is_array( $unser ) ) {
+ if ( isset( $unser['error'] ) ) {
+ return false;
+ } elseif ( isset( $unser['xml'] ) ) {
+ return $unser['xml'];
+ } else {
+ // Should never ever reach here.
+ throw new MWException( "Error unserializing DjVu metadata." );
+ }
+ }
+
+ // unserialize failed. Guess it wasn't really serialized after all,
+ return $metadata;
+ }
+
+ /**
* Cache a document tree for the DjVu XML metadata
- * @param $image File
- * @param $gettext Boolean: DOCUMENT (Default: false)
- * @return bool
+ * @param File $image
+ * @param bool $gettext DOCUMENT (Default: false)
+ * @return bool|SimpleXMLElement
*/
function getMetaTree( $image, $gettext = false ) {
- if ( isset( $image->dejaMetaTree ) ) {
+ if ( $gettext && isset( $image->djvuTextTree ) ) {
+ return $image->djvuTextTree;
+ }
+ if ( !$gettext && isset( $image->dejaMetaTree ) ) {
return $image->dejaMetaTree;
}
- $metadata = $image->getMetadata();
+ $metadata = $this->getUnserializedMetadata( $image );
if ( !$this->isMetadataValid( $image, $metadata ) ) {
wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
+
return false;
}
wfProfileIn( __METHOD__ );
@@ -250,8 +321,11 @@ class DjVuHandler extends ImageHandler {
$image->djvuTextTree = false;
$tree = new SimpleXMLElement( $metadata );
if ( $tree->getName() == 'mw-djvu' ) {
+ /** @var SimpleXMLElement $b */
foreach ( $tree->children() as $b ) {
if ( $b->getName() == 'DjVuTxt' ) {
+ // @todo File::djvuTextTree and File::dejaMetaTree are declared
+ // dynamically. Add a public File::$data to facilitate this?
$image->djvuTextTree = $b;
} elseif ( $b->getName() == 'DjVuXML' ) {
$image->dejaMetaTree = $b;
@@ -272,6 +346,11 @@ class DjVuHandler extends ImageHandler {
}
}
+ /**
+ * @param File $image
+ * @param string $path
+ * @return bool|array False on failure
+ */
function getImageSize( $image, $path ) {
return $this->getDjVuImage( $image, $path )->getImageSize();
}
@@ -283,12 +362,20 @@ class DjVuHandler extends ImageHandler {
$magic = MimeMagic::singleton();
$mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension );
}
+
return array( $wgDjvuOutputExtension, $mime );
}
function getMetadata( $image, $path ) {
wfDebug( "Getting DjVu metadata for $path\n" );
- return $this->getDjVuImage( $image, $path )->retrieveMetaData();
+
+ $xml = $this->getDjVuImage( $image, $path )->retrieveMetaData();
+ if ( $xml === false ) {
+ // Special value so that we don't repetitively try and decode a broken file.
+ return serialize( array( 'error' => 'Error extracting metadata' ) );
+ } else {
+ return serialize( array( 'xml' => $xml ) );
+ }
}
function getMetadataType( $image ) {
@@ -304,6 +391,7 @@ class DjVuHandler extends ImageHandler {
if ( !$tree ) {
return false;
}
+
return count( $tree->xpath( '//OBJECT' ) );
}
@@ -324,6 +412,11 @@ class DjVuHandler extends ImageHandler {
}
}
+ /**
+ * @param File $image
+ * @param int $page Page number to get information for
+ * @return bool|string Page text or false when no text found.
+ */
function getPageText( $image, $page ) {
$tree = $this->getMetaTree( $image, true );
if ( !$tree ) {
@@ -333,11 +426,10 @@ class DjVuHandler extends ImageHandler {
$o = $tree->BODY[0]->PAGE[$page - 1];
if ( $o ) {
$txt = $o['value'];
+
return $txt;
} else {
return false;
}
-
}
-
}
diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php
index 54efe7a8..6ff19c90 100644
--- a/includes/media/DjVuImage.php
+++ b/includes/media/DjVuImage.php
@@ -3,7 +3,7 @@
* DjVu image handler.
*
* Copyright © 2006 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -35,6 +35,11 @@
*/
class DjVuImage {
/**
+ * @const DJVUTXT_MEMORY_LIMIT Memory limit for the DjVu description software
+ */
+ const DJVUTXT_MEMORY_LIMIT = 300000;
+
+ /**
* Constructor
*
* @param string $filename The DjVu file name.
@@ -44,22 +49,18 @@ class DjVuImage {
}
/**
- * @const DJVUTXT_MEMORY_LIMIT Memory limit for the DjVu description software
- */
- const DJVUTXT_MEMORY_LIMIT = 300000;
-
- /**
* Check if the given file is indeed a valid DjVu image file
* @return bool
*/
public function isValid() {
$info = $this->getInfo();
+
return $info !== false;
}
/**
* Return data in the style of getimagesize()
- * @return array or false on failure
+ * @return array|bool Array or false on failure
*/
public function getImageSize() {
$data = $this->getInfo();
@@ -71,6 +72,7 @@ class DjVuImage {
return array( $width, $height, 'DjVu',
"width=\"$width\" height=\"$height\"" );
}
+
return false;
}
@@ -82,8 +84,11 @@ class DjVuImage {
function dump() {
$file = fopen( $this->mFilename, 'rb' );
$header = fread( $file, 12 );
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4chunk/NchunkLength', $header ) );
+ /** @var string $chunk
+ * @var string $chunkLength */
echo "$chunk $chunkLength\n";
$this->dumpForm( $file, $chunkLength, 1 );
fclose( $file );
@@ -98,8 +103,11 @@ class DjVuImage {
if ( $chunkHeader == '' ) {
break;
}
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) );
+ /** @var string $chunk
+ * @var string $chunkLength */
echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n";
if ( $chunk == 'FORM' ) {
@@ -120,6 +128,7 @@ class DjVuImage {
wfRestoreWarnings();
if ( $file === false ) {
wfDebug( __METHOD__ . ": missing or failed file read\n" );
+
return false;
}
@@ -129,9 +138,14 @@ class DjVuImage {
if ( strlen( $header ) < 16 ) {
wfDebug( __METHOD__ . ": too short file header\n" );
} else {
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) );
+ /** @var string $magic
+ * @var string $subtype
+ * @var string $formLength
+ * @var string $formType */
if ( $magic != 'AT&T' ) {
wfDebug( __METHOD__ . ": not a DjVu file\n" );
} elseif ( $subtype == 'DJVU' ) {
@@ -145,6 +159,7 @@ class DjVuImage {
}
}
fclose( $file );
+
return $info;
}
@@ -153,8 +168,12 @@ class DjVuImage {
if ( strlen( $header ) < 8 ) {
return array( false, 0 );
} else {
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack( 'a4chunk/Nlength', $header ) );
+
+ /** @var string $chunk
+ * @var string $length */
return array( $chunk, $length );
}
}
@@ -182,6 +201,7 @@ class DjVuImage {
$subtype = fread( $file, 4 );
if ( $subtype == 'DJVU' ) {
wfDebug( __METHOD__ . ": found first subpage\n" );
+
return $this->getPageInfo( $file, $length );
}
$this->skipChunk( $file, $length - 4 );
@@ -192,6 +212,7 @@ class DjVuImage {
} while ( $length != 0 && !feof( $file ) && ftell( $file ) - $start < $formLength );
wfDebug( __METHOD__ . ": multi-page DJVU file contained no pages\n" );
+
return false;
}
@@ -199,20 +220,24 @@ class DjVuImage {
list( $chunk, $length ) = $this->readChunk( $file );
if ( $chunk != 'INFO' ) {
wfDebug( __METHOD__ . ": expected INFO chunk, got '$chunk'\n" );
+
return false;
}
if ( $length < 9 ) {
wfDebug( __METHOD__ . ": INFO should be 9 or 10 bytes, found $length\n" );
+
return false;
}
$data = fread( $file, $length );
if ( strlen( $data ) < $length ) {
wfDebug( __METHOD__ . ": INFO chunk cut off\n" );
+
return false;
}
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack(
'nwidth/' .
'nheight/' .
@@ -220,8 +245,16 @@ class DjVuImage {
'Cmajor/' .
'vresolution/' .
'Cgamma', $data ) );
+
# Newer files have rotation info in byte 10, but we don't use it yet.
+ /** @var string $width
+ * @var string $height
+ * @var string $major
+ * @var string $minor
+ * @var string $resolution
+ * @var string $length
+ * @var string $gamma */
return array(
'width' => $width,
'height' => $height,
@@ -284,17 +317,21 @@ EOR;
}
}
wfProfileOut( __METHOD__ );
+
return $xml;
}
function pageTextCallback( $matches ) {
# Get rid of invalid UTF-8, strip control characters
- return '<PAGE value="' . htmlspecialchars( UtfNormal::cleanUp( $matches[1] ) ) . '" />';
+ $val = htmlspecialchars( UtfNormal::cleanUp( stripcslashes( $matches[1] ) ) );
+ $val = str_replace( array( "\n", '�' ), array( '&#10;', '' ), $val );
+ return '<PAGE value="' . $val . '" />';
}
/**
* Hack to temporarily work around djvutoxml bug
- * @return bool|string
+ * @param string $dump
+ * @return string
*/
function convertDumpToXML( $dump ) {
if ( strval( $dump ) == '' ) {
@@ -334,6 +371,7 @@ EOT;
if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) {
wfDebug( "Indirect multi-page DjVu document, bad for server!\n" );
+
return false;
}
if ( preg_match( '/^ *FORM:DJVU/', $line ) ) {
@@ -352,6 +390,7 @@ EOT;
}
$xml .= "</BODY>\n</DjVuXML>\n";
+
return $xml;
}
@@ -367,8 +406,13 @@ EOT;
break;
}
- if ( preg_match( '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/', $line, $m ) ) {
- $xml .= Xml::tags( 'OBJECT',
+ if ( preg_match(
+ '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/',
+ $line,
+ $m
+ ) ) {
+ $xml .= Xml::tags(
+ 'OBJECT',
array(
#'data' => '',
#'type' => 'image/x.djvu',
@@ -377,13 +421,15 @@ EOT;
#'usemap' => '',
),
"\n" .
- Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" .
- Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n"
+ Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" .
+ Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n"
) . "\n";
+
return true;
}
$line = strtok( "\n" );
}
+
# Not found
return false;
}
diff --git a/includes/media/Exif.php b/includes/media/Exif.php
index 9a2794a5..018b58c5 100644
--- a/includes/media/Exif.php
+++ b/includes/media/Exif.php
@@ -30,87 +30,82 @@
* @ingroup Media
*/
class Exif {
+ /** An 8-bit (1-byte) unsigned integer. */
+ const BYTE = 1;
- const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
- const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
- const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
- const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
- const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
- const SHORT_OR_LONG = 6; //!< A 16-bit (2-byte) or 32-bit (4-byte) unsigned integer.
- const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
- const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
- const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
- const IGNORE = -1; // A fake value for things we don't want or don't support.
-
- //@{
- /* @var array
- * @private
+ /** An 8-bit byte containing one 7-bit ASCII code.
+ * The final byte is terminated with NULL.
*/
+ const ASCII = 2;
- /**
- * Exif tags grouped by category, the tagname itself is the key and the type
- * is the value, in the case of more than one possible value type they are
- * separated by commas.
- */
- var $mExifTags;
+ /** A 16-bit (2-byte) unsigned integer. */
+ const SHORT = 3;
- /**
- * The raw Exif data returned by exif_read_data()
- */
- var $mRawExifData;
+ /** A 32-bit (4-byte) unsigned integer. */
+ const LONG = 4;
- /**
- * A Filtered version of $mRawExifData that has been pruned of invalid
- * tags and tags that contain content they shouldn't contain according
- * to the Exif specification
+ /** Two LONGs. The first LONG is the numerator and the second LONG expresses
+ * the denominator
*/
- var $mFilteredExifData;
+ const RATIONAL = 5;
- /**
- * Filtered and formatted Exif data, see FormatMetadata::getFormattedData()
- */
- var $mFormattedExifData;
+ /** A 16-bit (2-byte) or 32-bit (4-byte) unsigned integer. */
+ const SHORT_OR_LONG = 6;
- //@}
+ /** An 8-bit byte that can take any value depending on the field definition */
+ const UNDEFINED = 7;
- //@{
- /* @var string
- * @private
- */
+ /** A 32-bit (4-byte) signed integer (2's complement notation), */
+ const SLONG = 9;
- /**
- * The file being processed
+ /** Two SLONGs. The first SLONG is the numerator and the second SLONG is
+ * the denominator.
*/
- var $file;
+ const SRATIONAL = 10;
- /**
- * The basename of the file being processed
+ /** A fake value for things we don't want or don't support. */
+ const IGNORE = -1;
+
+ /** @var array Exif tags grouped by category, the tagname itself is the key
+ * and the type is the value, in the case of more than one possible value
+ * type they are separated by commas.
*/
- var $basename;
+ private $mExifTags;
- /**
- * The private log to log to, e.g. 'exif'
+ /** @var array The raw Exif data returned by exif_read_data() */
+ private $mRawExifData;
+
+ /** @var array A Filtered version of $mRawExifData that has been pruned
+ * of invalid tags and tags that contain content they shouldn't contain
+ * according to the Exif specification
*/
- var $log = false;
+ private $mFilteredExifData;
- /**
- * The byte order of the file. Needed because php's
- * extension doesn't fully process some obscure props.
+ /** @var string The file being processed */
+ private $file;
+
+ /** @var string The basename of the file being processed */
+ private $basename;
+
+ /** @var string The private log to log to, e.g. 'exif' */
+ private $log = false;
+
+ /** @var string The byte order of the file. Needed because php's extension
+ * doesn't fully process some obscure props.
*/
private $byteOrder;
- //@}
/**
* Constructor
*
- * @param string $file filename.
- * @param string $byteOrder Type of byte ordering either 'BE' (Big Endian) or 'LE' (Little Endian). Default ''.
+ * @param string $file Filename.
+ * @param string $byteOrder Type of byte ordering either 'BE' (Big Endian)
+ * or 'LE' (Little Endian). Default ''.
* @throws MWException
* @todo FIXME: The following are broke:
- * SubjectArea. Need to test the more obscure tags.
- *
- * DigitalZoomRatio = 0/0 is rejected. need to determine if that's valid.
- * possibly should treat 0/0 = 0. need to read exif spec on that.
+ * SubjectArea. Need to test the more obscure tags.
+ * DigitalZoomRatio = 0/0 is rejected. need to determine if that's valid.
+ * Possibly should treat 0/0 = 0. need to read exif spec on that.
*/
function __construct( $file, $byteOrder = '' ) {
/**
@@ -125,122 +120,123 @@ class Exif {
# TIFF Rev. 6.0 Attribute Information (p22)
'IFD0' => array(
# Tags relating to image structure
- 'ImageWidth' => Exif::SHORT_OR_LONG, # Image width
- 'ImageLength' => Exif::SHORT_OR_LONG, # Image height
- 'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component
+ 'ImageWidth' => Exif::SHORT_OR_LONG, # Image width
+ 'ImageLength' => Exif::SHORT_OR_LONG, # Image height
+ 'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component
# "When a primary image is JPEG compressed, this designation is not"
# "necessary and is omitted." (p23)
- 'Compression' => Exif::SHORT, # Compression scheme #p23
- 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
- 'Orientation' => Exif::SHORT, # Orientation of image #p24
- 'SamplesPerPixel' => Exif::SHORT, # Number of components
- 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
- 'YCbCrSubSampling' => array( Exif::SHORT, 2 ), # Subsampling ratio of Y to C #p24
- 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
- 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
- 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
- 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
+ 'Compression' => Exif::SHORT, # Compression scheme #p23
+ 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
+ 'Orientation' => Exif::SHORT, # Orientation of image #p24
+ 'SamplesPerPixel' => Exif::SHORT, # Number of components
+ 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
+ 'YCbCrSubSampling' => array( Exif::SHORT, 2 ), # Subsampling ratio of Y to C #p24
+ 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
+ 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
+ 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
+ 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
# Tags relating to recording offset
- 'StripOffsets' => Exif::SHORT_OR_LONG, # Image data location
- 'RowsPerStrip' => Exif::SHORT_OR_LONG, # Number of rows per strip
- 'StripByteCounts' => Exif::SHORT_OR_LONG, # Bytes per compressed strip
- 'JPEGInterchangeFormat' => Exif::SHORT_OR_LONG, # Offset to JPEG SOI
- 'JPEGInterchangeFormatLength' => Exif::SHORT_OR_LONG, # Bytes of JPEG data
+ 'StripOffsets' => Exif::SHORT_OR_LONG, # Image data location
+ 'RowsPerStrip' => Exif::SHORT_OR_LONG, # Number of rows per strip
+ 'StripByteCounts' => Exif::SHORT_OR_LONG, # Bytes per compressed strip
+ 'JPEGInterchangeFormat' => Exif::SHORT_OR_LONG, # Offset to JPEG SOI
+ 'JPEGInterchangeFormatLength' => Exif::SHORT_OR_LONG, # Bytes of JPEG data
# Tags relating to image data characteristics
- 'TransferFunction' => Exif::IGNORE, # Transfer function
- 'WhitePoint' => array( Exif::RATIONAL, 2 ), # White point chromaticity
- 'PrimaryChromaticities' => array( Exif::RATIONAL, 6 ), # Chromaticities of primarities
- 'YCbCrCoefficients' => array( Exif::RATIONAL, 3 ), # Color space transformation matrix coefficients #p27
- 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6 ), # Pair of black and white reference values
+ 'TransferFunction' => Exif::IGNORE, # Transfer function
+ 'WhitePoint' => array( Exif::RATIONAL, 2 ), # White point chromaticity
+ 'PrimaryChromaticities' => array( Exif::RATIONAL, 6 ), # Chromaticities of primarities
+ # Color space transformation matrix coefficients #p27
+ 'YCbCrCoefficients' => array( Exif::RATIONAL, 3 ),
+ 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6 ), # Pair of black and white reference values
# Other tags
- 'DateTime' => Exif::ASCII, # File change date and time
- 'ImageDescription' => Exif::ASCII, # Image title
- 'Make' => Exif::ASCII, # Image input equipment manufacturer
- 'Model' => Exif::ASCII, # Image input equipment model
- 'Software' => Exif::ASCII, # Software used
- 'Artist' => Exif::ASCII, # Person who created the image
- 'Copyright' => Exif::ASCII, # Copyright holder
+ 'DateTime' => Exif::ASCII, # File change date and time
+ 'ImageDescription' => Exif::ASCII, # Image title
+ 'Make' => Exif::ASCII, # Image input equipment manufacturer
+ 'Model' => Exif::ASCII, # Image input equipment model
+ 'Software' => Exif::ASCII, # Software used
+ 'Artist' => Exif::ASCII, # Person who created the image
+ 'Copyright' => Exif::ASCII, # Copyright holder
),
# Exif IFD Attribute Information (p30-31)
'EXIF' => array(
- # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
+ # @todo NOTE: Nonexistence of this field is taken to mean nonconformance
# to the Exif 2.1 AND 2.2 standards
- 'ExifVersion' => Exif::UNDEFINED, # Exif version
- 'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
+ 'ExifVersion' => Exif::UNDEFINED, # Exif version
+ 'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
# Tags relating to Image Data Characteristics
- 'ColorSpace' => Exif::SHORT, # Color space information #p32
+ 'ColorSpace' => Exif::SHORT, # Color space information #p32
# Tags relating to image configuration
- 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
- 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
- 'PixelYDimension' => Exif::SHORT_OR_LONG, # Valid image width
- 'PixelXDimension' => Exif::SHORT_OR_LONG, # Valid image height
+ 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
+ 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
+ 'PixelYDimension' => Exif::SHORT_OR_LONG, # Valid image width
+ 'PixelXDimension' => Exif::SHORT_OR_LONG, # Valid image height
# Tags relating to related user information
- 'MakerNote' => Exif::IGNORE, # Manufacturer notes
- 'UserComment' => Exif::UNDEFINED, # User comments #p34
+ 'MakerNote' => Exif::IGNORE, # Manufacturer notes
+ 'UserComment' => Exif::UNDEFINED, # User comments #p34
# Tags relating to related file information
- 'RelatedSoundFile' => Exif::ASCII, # Related audio file
+ 'RelatedSoundFile' => Exif::ASCII, # Related audio file
# Tags relating to date and time
- 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
- 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
- 'SubSecTime' => Exif::ASCII, # DateTime subseconds
- 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
- 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
+ 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
+ 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
+ 'SubSecTime' => Exif::ASCII, # DateTime subseconds
+ 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
+ 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
# Tags relating to picture-taking conditions (p31)
- 'ExposureTime' => Exif::RATIONAL, # Exposure time
- 'FNumber' => Exif::RATIONAL, # F Number
- 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
- 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
- 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
+ 'ExposureTime' => Exif::RATIONAL, # Exposure time
+ 'FNumber' => Exif::RATIONAL, # F Number
+ 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
+ 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
+ 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
'OECF' => Exif::IGNORE,
# Optoelectronic conversion factor. Note: We don't have support for this atm.
- 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
- 'ApertureValue' => Exif::RATIONAL, # Aperture
- 'BrightnessValue' => Exif::SRATIONAL, # Brightness
- 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
- 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
- 'SubjectDistance' => Exif::RATIONAL, # Subject distance
- 'MeteringMode' => Exif::SHORT, # Metering mode #p40
- 'LightSource' => Exif::SHORT, # Light source #p40-41
- 'Flash' => Exif::SHORT, # Flash #p41-42
- 'FocalLength' => Exif::RATIONAL, # Lens focal length
- 'SubjectArea' => array( Exif::SHORT, 4 ), # Subject area
- 'FlashEnergy' => Exif::RATIONAL, # Flash energy
- 'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm.
- 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
- 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
- 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
- 'SubjectLocation' => array( Exif::SHORT, 2 ), # Subject location
- 'ExposureIndex' => Exif::RATIONAL, # Exposure index
- 'SensingMethod' => Exif::SHORT, # Sensing method #p46
- 'FileSource' => Exif::UNDEFINED, # File source #p47
- 'SceneType' => Exif::UNDEFINED, # Scene type #p47
- 'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm.
- 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
- 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
- 'WhiteBalance' => Exif::SHORT, # White Balance #p49
- 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
- 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
- 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
- 'GainControl' => Exif::SHORT, # Scene control #p49-50
- 'Contrast' => Exif::SHORT, # Contrast #p50
- 'Saturation' => Exif::SHORT, # Saturation #p50
- 'Sharpness' => Exif::SHORT, # Sharpness #p50
+ 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
+ 'ApertureValue' => Exif::RATIONAL, # Aperture
+ 'BrightnessValue' => Exif::SRATIONAL, # Brightness
+ 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
+ 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
+ 'SubjectDistance' => Exif::RATIONAL, # Subject distance
+ 'MeteringMode' => Exif::SHORT, # Metering mode #p40
+ 'LightSource' => Exif::SHORT, # Light source #p40-41
+ 'Flash' => Exif::SHORT, # Flash #p41-42
+ 'FocalLength' => Exif::RATIONAL, # Lens focal length
+ 'SubjectArea' => array( Exif::SHORT, 4 ), # Subject area
+ 'FlashEnergy' => Exif::RATIONAL, # Flash energy
+ 'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm.
+ 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
+ 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
+ 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
+ 'SubjectLocation' => array( Exif::SHORT, 2 ), # Subject location
+ 'ExposureIndex' => Exif::RATIONAL, # Exposure index
+ 'SensingMethod' => Exif::SHORT, # Sensing method #p46
+ 'FileSource' => Exif::UNDEFINED, # File source #p47
+ 'SceneType' => Exif::UNDEFINED, # Scene type #p47
+ 'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm.
+ 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
+ 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
+ 'WhiteBalance' => Exif::SHORT, # White Balance #p49
+ 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
+ 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
+ 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
+ 'GainControl' => Exif::SHORT, # Scene control #p49-50
+ 'Contrast' => Exif::SHORT, # Contrast #p50
+ 'Saturation' => Exif::SHORT, # Saturation #p50
+ 'Sharpness' => Exif::SHORT, # Sharpness #p50
'DeviceSettingDescription' => Exif::IGNORE,
# Device settings description. This could maybe be supported. Need to find an
# example file that uses this to see if it has stuff of interest in it.
- 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
+ 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
- 'ImageUniqueID' => Exif::ASCII, # Unique image ID
+ 'ImageUniqueID' => Exif::ASCII, # Unique image ID
),
# GPS Attribute Information (p52)
@@ -248,38 +244,38 @@ class Exif {
'GPSVersion' => Exif::UNDEFINED,
# Should be an array of 4 Exif::BYTE's. However php treats it as an undefined
# Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
- 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
- 'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude
- 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
- 'GPSLongitude' => array( Exif::RATIONAL, 3 ), # Longitude
+ 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
+ 'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude
+ 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
+ 'GPSLongitude' => array( Exif::RATIONAL, 3 ), # Longitude
'GPSAltitudeRef' => Exif::UNDEFINED,
# Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
# but php seems to disagree.
- 'GPSAltitude' => Exif::RATIONAL, # Altitude
- 'GPSTimeStamp' => array( Exif::RATIONAL, 3 ), # GPS time (atomic clock)
- 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
- 'GPSStatus' => Exif::ASCII, # Receiver status #p54
- 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
- 'GPSDOP' => Exif::RATIONAL, # Measurement precision
- 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
- 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
- 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
- 'GPSTrack' => Exif::RATIONAL, # Direction of movement
- 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
- 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
- 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
- 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
- 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ), # Latitude destination
- 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
- 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ), # Longitude of destination
- 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
- 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
- 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
- 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
- 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
- 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
- 'GPSDateStamp' => Exif::ASCII, # GPS date
- 'GPSDifferential' => Exif::SHORT, # GPS differential correction
+ 'GPSAltitude' => Exif::RATIONAL, # Altitude
+ 'GPSTimeStamp' => array( Exif::RATIONAL, 3 ), # GPS time (atomic clock)
+ 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
+ 'GPSStatus' => Exif::ASCII, # Receiver status #p54
+ 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
+ 'GPSDOP' => Exif::RATIONAL, # Measurement precision
+ 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
+ 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
+ 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
+ 'GPSTrack' => Exif::RATIONAL, # Direction of movement
+ 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
+ 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
+ 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
+ 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
+ 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ), # Latitude destination
+ 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
+ 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ), # Longitude of destination
+ 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
+ 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
+ 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
+ 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
+ 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
+ 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
+ 'GPSDateStamp' => Exif::ASCII, # GPS date
+ 'GPSDifferential' => Exif::SHORT, # GPS differential correction
),
);
@@ -302,14 +298,15 @@ class Exif {
$data = exif_read_data( $this->file, 0, true );
wfRestoreWarnings();
} else {
- throw new MWException( "Internal error: exif_read_data not present. \$wgShowEXIF may be incorrectly set or not checked by an extension." );
+ throw new MWException( "Internal error: exif_read_data not present. " .
+ "\$wgShowEXIF may be incorrectly set or not checked by an extension." );
}
/**
* exif_read_data() will return false on invalid input, such as
* when somebody uploads a file called something.jpeg
* containing random gibberish.
*/
- $this->mRawExifData = $data ? $data : array();
+ $this->mRawExifData = $data ?: array();
$this->makeFilteredData();
$this->collapseData();
$this->debugFile( __FUNCTION__, false );
@@ -319,16 +316,16 @@ class Exif {
* Make $this->mFilteredExifData
*/
function makeFilteredData() {
- $this->mFilteredExifData = Array();
+ $this->mFilteredExifData = array();
foreach ( array_keys( $this->mRawExifData ) as $section ) {
- if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
+ if ( !array_key_exists( $section, $this->mExifTags ) ) {
$this->debug( $section, __FUNCTION__, "'$section' is not a valid Exif section" );
continue;
}
foreach ( array_keys( $this->mRawExifData[$section] ) as $tag ) {
- if ( !in_array( $tag, array_keys( $this->mExifTags[$section] ) ) ) {
+ if ( !array_key_exists( $tag, $this->mExifTags[$section] ) ) {
$this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
continue;
}
@@ -371,15 +368,17 @@ class Exif {
$this->exifGPStoNumber( 'GPSLongitude' );
$this->exifGPStoNumber( 'GPSDestLongitude' );
- if ( isset( $this->mFilteredExifData['GPSAltitude'] ) && isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
-
- // We know altitude data is a <num>/<denom> from the validation functions ran earlier.
- // But multiplying such a string by -1 doesn't work well, so convert.
+ if ( isset( $this->mFilteredExifData['GPSAltitude'] )
+ && isset( $this->mFilteredExifData['GPSAltitudeRef'] )
+ ) {
+ // We know altitude data is a <num>/<denom> from the validation
+ // functions ran earlier. But multiplying such a string by -1
+ // doesn't work well, so convert.
list( $num, $denom ) = explode( '/', $this->mFilteredExifData['GPSAltitude'] );
$this->mFilteredExifData['GPSAltitude'] = $num / $denom;
if ( $this->mFilteredExifData['GPSAltitudeRef'] === "\1" ) {
- $this->mFilteredExifData['GPSAltitude'] *= - 1;
+ $this->mFilteredExifData['GPSAltitude'] *= -1;
}
unset( $this->mFilteredExifData['GPSAltitudeRef'] );
}
@@ -397,7 +396,9 @@ class Exif {
if ( isset( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
$val = $this->mFilteredExifData['ComponentsConfiguration'];
$ccVals = array();
- for ( $i = 0; $i < strlen( $val ); $i++ ) {
+
+ $strLen = strlen( $val );
+ for ( $i = 0; $i < $strLen; $i++ ) {
$ccVals[$i] = ord( substr( $val, $i, 1 ) );
}
$ccVals['_type'] = 'ol'; //this is for formatting later.
@@ -414,12 +415,15 @@ class Exif {
if ( isset( $this->mFilteredExifData['GPSVersion'] ) ) {
$val = $this->mFilteredExifData['GPSVersion'];
$newVal = '';
- for ( $i = 0; $i < strlen( $val ); $i++ ) {
+
+ $strLen = strlen( $val );
+ for ( $i = 0; $i < $strLen; $i++ ) {
if ( $i !== 0 ) {
$newVal .= '.';
}
$newVal .= ord( substr( $val, $i, 1 ) );
}
+
if ( $this->byteOrder === 'LE' ) {
// Need to reverse the string
$newVal2 = '';
@@ -432,13 +436,13 @@ class Exif {
}
unset( $this->mFilteredExifData['GPSVersion'] );
}
-
}
+
/**
* Do userComment tags and similar. See pg. 34 of exif standard.
* basically first 8 bytes is charset, rest is value.
* This has not been tested on any shift-JIS strings.
- * @param string $prop prop name.
+ * @param string $prop Prop name
*/
private function charCodeString( $prop ) {
if ( isset( $this->mFilteredExifData[$prop] ) ) {
@@ -448,6 +452,7 @@ class Exif {
$this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, false );
unset( $this->mFilteredExifData[$prop] );
+
return;
}
$charCode = substr( $this->mFilteredExifData[$prop], 0, 8 );
@@ -465,8 +470,6 @@ class Exif {
$charset = "";
break;
}
- // This could possibly check to see if iconv is really installed
- // or if we're using the compatibility wrapper in globalFunctions.php
if ( $charset ) {
wfSuppressWarnings();
$val = iconv( $charset, 'UTF-8//IGNORE', $val );
@@ -488,6 +491,7 @@ class Exif {
//only whitespace.
$this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, "$prop: Is only whitespace" );
unset( $this->mFilteredExifData[$prop] );
+
return;
}
@@ -495,28 +499,32 @@ class Exif {
$this->mFilteredExifData[$prop] = $val;
}
}
+
/**
* Convert an Exif::UNDEFINED from a raw binary string
* to its value. This is sometimes needed depending on
* the type of UNDEFINED field
- * @param string $prop name of property
+ * @param string $prop Name of property
*/
private function exifPropToOrd( $prop ) {
if ( isset( $this->mFilteredExifData[$prop] ) ) {
$this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
}
}
+
/**
* Convert gps in exif form to a single floating point number
* for example 10 degress 20`40`` S -> -10.34444
- * @param string $prop a gps coordinate exif tag name (like GPSLongitude)
+ * @param string $prop A GPS coordinate exif tag name (like GPSLongitude)
*/
private function exifGPStoNumber( $prop ) {
$loc =& $this->mFilteredExifData[$prop];
$dir =& $this->mFilteredExifData[$prop . 'Ref'];
$res = false;
- if ( isset( $loc ) && isset( $dir ) && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' ) ) {
+ if ( isset( $loc ) && isset( $dir )
+ && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' )
+ ) {
list( $num, $denom ) = explode( '/', $loc[0] );
$res = $num / $denom;
list( $num, $denom ) = explode( '/', $loc[1] );
@@ -525,7 +533,7 @@ class Exif {
$res += ( $num / $denom ) * ( 1 / 3600 );
if ( $dir === 'S' || $dir === 'W' ) {
- $res *= - 1; // make negative
+ $res *= -1; // make negative
}
}
@@ -540,17 +548,6 @@ class Exif {
}
}
- /**
- * Use FormatMetadata to create formatted values for display to user
- * (is this ever used?)
- *
- * @deprecated since 1.18
- */
- function makeFormattedData() {
- wfDeprecated( __METHOD__, '1.18' );
- $this->mFormattedExifData = FormatMetadata::getFormattedData(
- $this->mFilteredExifData );
- }
/**#@-*/
/**#@+
@@ -566,26 +563,12 @@ class Exif {
/**
* Get $this->mFilteredExifData
+ * @return array
*/
function getFilteredData() {
return $this->mFilteredExifData;
}
- /**
- * Get $this->mFormattedExifData
- *
- * This returns the data for display to user.
- * Its unclear if this is ever used.
- *
- * @deprecated since 1.18
- */
- function getFormattedData() {
- wfDeprecated( __METHOD__, '1.18' );
- if ( !$this->mFormattedExifData ) {
- $this->makeFormattedData();
- }
- return $this->mFormattedExifData;
- }
/**#@-*/
/**
@@ -604,26 +587,26 @@ class Exif {
return 2; // We don't need no bloddy constants!
}
- /**#@+
+ /**
* Validates if a tag value is of the type it should be according to the Exif spec
*
- * @private
- *
- * @param $in Mixed: the input value to check
+ * @param mixed $in The input value to check
* @return bool
*/
private function isByte( $in ) {
if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 255 ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
} else {
$this->debug( $in, __FUNCTION__, false );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isASCII( $in ) {
@@ -633,11 +616,13 @@ class Exif {
if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
$this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
+
return false;
}
if ( preg_match( '/^\s*$/', $in ) ) {
$this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
+
return false;
}
@@ -645,93 +630,110 @@ class Exif {
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isShort( $in ) {
if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 65536 ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
} else {
$this->debug( $in, __FUNCTION__, false );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isLong( $in ) {
if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
} else {
$this->debug( $in, __FUNCTION__, false );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isRational( $in ) {
$m = array();
- if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
+
+ # Avoid division by zero
+ if ( !is_array( $in )
+ && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
+ ) {
return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
} else {
$this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isUndefined( $in ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isSlong( $in ) {
if ( $this->isLong( abs( $in ) ) ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
} else {
$this->debug( $in, __FUNCTION__, false );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isSrational( $in ) {
$m = array();
- if ( !is_array( $in ) && preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
+
+ # Avoid division by zero
+ if ( !is_array( $in ) &&
+ preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
+ ) {
return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
} else {
$this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
+
return false;
}
}
+
/**#@-*/
/**
* Validates if a tag has a legal value according to the Exif spec
*
- * @private
- * @param string $section section where tag is located.
- * @param string $tag the tag to check.
- * @param $val Mixed: the value of the tag.
- * @param $recursive Boolean: true if called recursively for array types.
+ * @param string $section Section where tag is located.
+ * @param string $tag The tag to check.
+ * @param mixed $val The value of the tag.
+ * @param bool $recursive True if called recursively for array types.
* @return bool
*/
private function validate( $section, $tag, $val, $recursive = false ) {
@@ -747,6 +749,7 @@ class Exif {
$count = count( $val );
if ( $ecount != $count ) {
$this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
+
return false;
}
if ( $count > 1 ) {
@@ -755,42 +758,54 @@ class Exif {
return false;
}
}
+
return true;
}
// Does not work if not typecast
switch ( (string)$etype ) {
case (string)Exif::BYTE:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isByte( $val );
case (string)Exif::ASCII:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isASCII( $val );
case (string)Exif::SHORT:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isShort( $val );
case (string)Exif::LONG:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isLong( $val );
case (string)Exif::RATIONAL:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isRational( $val );
case (string)Exif::SHORT_OR_LONG:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isShort( $val ) || $this->isLong( $val );
case (string)Exif::UNDEFINED:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isUndefined( $val );
case (string)Exif::SLONG:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isSlong( $val );
case (string)Exif::SRATIONAL:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isSrational( $val );
case (string)Exif::IGNORE:
$this->debug( $val, __FUNCTION__, $debug );
+
return false;
default:
$this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
+
return false;
}
}
@@ -798,11 +813,9 @@ class Exif {
/**
* Convenience function for debugging output
*
- * @private
- *
- * @param $in Mixed:
- * @param $fname String:
- * @param $action Mixed: , default NULL.
+ * @param mixed $in Arrays will be processed with print_r().
+ * @param string $fname Function name to log.
+ * @param string|bool|null $action Default null.
*/
private function debug( $in, $fname, $action = null ) {
if ( !$this->log ) {
@@ -815,23 +828,21 @@ class Exif {
}
if ( $action === true ) {
- wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n" );
+ wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)" );
} elseif ( $action === false ) {
- wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n" );
+ wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)" );
} elseif ( $action === null ) {
- wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n" );
+ wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)" );
} else {
- wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n" );
+ wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')" );
}
}
/**
* Convenience function for debugging output
*
- * @private
- *
- * @param string $fname the name of the function calling this function
- * @param $io Boolean: Specify whether we're beginning or ending
+ * @param string $fname The name of the function calling this function
+ * @param bool $io Specify whether we're beginning or ending
*/
private function debugFile( $fname, $io ) {
if ( !$this->log ) {
@@ -839,9 +850,9 @@ class Exif {
}
$class = ucfirst( __CLASS__ );
if ( $io ) {
- wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
+ wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'" );
} else {
- wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
+ wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'" );
}
}
}
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
index d8d0bede..b7657cb3 100644
--- a/includes/media/ExifBitmap.php
+++ b/includes/media/ExifBitmap.php
@@ -28,7 +28,6 @@
* @ingroup Media
*/
class ExifBitmapHandler extends BitmapHandler {
-
const BROKEN_FILE = '-1'; // error extracting metadata
const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
@@ -61,22 +60,30 @@ class ExifBitmapHandler extends BitmapHandler {
. $metadata['Software'][0][1] . ')';
}
+ $formatter = new FormatMetadata;
+
// ContactInfo also has to be dealt with specially
if ( isset( $metadata['Contact'] ) ) {
$metadata['Contact'] =
- FormatMetadata::collapseContactInfo(
+ $formatter->collapseContactInfo(
$metadata['Contact'] );
}
foreach ( $metadata as &$val ) {
if ( is_array( $val ) ) {
- $val = FormatMetadata::flattenArray( $val, 'ul', $avoidHtml );
+ $val = $formatter->flattenArrayReal( $val, 'ul', $avoidHtml );
}
}
$metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
+
return $metadata;
}
+ /**
+ * @param File $image
+ * @param array $metadata
+ * @return bool|int
+ */
function isMetadataValid( $image, $metadata ) {
global $wgShowEXIF;
if ( !$wgShowEXIF ) {
@@ -87,6 +94,7 @@ class ExifBitmapHandler extends BitmapHandler {
# Old special value indicating that there is no Exif data in the file.
# or that there was an error well extracting the metadata.
wfDebug( __METHOD__ . ": back-compat version\n" );
+
return self::METADATA_COMPATIBLE;
}
if ( $metadata === self::BROKEN_FILE ) {
@@ -95,47 +103,57 @@ class ExifBitmapHandler extends BitmapHandler {
wfSuppressWarnings();
$exif = unserialize( $metadata );
wfRestoreWarnings();
- if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
- $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
- {
- if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) &&
- $exif['MEDIAWIKI_EXIF_VERSION'] == 1 )
- {
+ if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
+ || $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version()
+ ) {
+ if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
+ && $exif['MEDIAWIKI_EXIF_VERSION'] == 1
+ ) {
//back-compatible but old
wfDebug( __METHOD__ . ": back-compat version\n" );
+
return self::METADATA_COMPATIBLE;
}
# Wrong (non-compatible) version
wfDebug( __METHOD__ . ": wrong version\n" );
+
return self::METADATA_BAD;
}
+
return self::METADATA_GOOD;
}
/**
- * @param $image File
+ * @param File $image
* @return array|bool
*/
function formatMetadata( $image ) {
- $metadata = $image->getMetadata();
- if ( $metadata === self::OLD_BROKEN_FILE ||
- $metadata === self::BROKEN_FILE ||
- $this->isMetadataValid( $image, $metadata ) === self::METADATA_BAD )
- {
+ $meta = $this->getCommonMetaArray( $image );
+ if ( count( $meta ) === 0 ) {
+ return false;
+ }
+
+ return $this->formatMetadataHelper( $meta );
+ }
+
+ public function getCommonMetaArray( File $file ) {
+ $metadata = $file->getMetadata();
+ if ( $metadata === self::OLD_BROKEN_FILE
+ || $metadata === self::BROKEN_FILE
+ || $this->isMetadataValid( $file, $metadata ) === self::METADATA_BAD
+ ) {
// So we don't try and display metadata from PagedTiffHandler
// for example when using InstantCommons.
- return false;
+ return array();
}
$exif = unserialize( $metadata );
if ( !$exif ) {
- return false;
+ return array();
}
unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
- if ( count( $exif ) == 0 ) {
- return false;
- }
- return $this->formatMetadataHelper( $exif );
+
+ return $exif;
}
function getMetadataType( $image ) {
@@ -151,12 +169,11 @@ class ExifBitmapHandler extends BitmapHandler {
* @return array
*/
function getImageSize( $image, $path ) {
- global $wgEnableAutoRotation;
$gis = parent::getImageSize( $image, $path );
// Don't just call $image->getMetadata(); FSFile::getPropsFromPath() calls us with a bogus object.
// This may mean we read EXIF data twice on initial upload.
- if ( $wgEnableAutoRotation ) {
+ if ( $this->autoRotateEnabled() ) {
$meta = $this->getMetadata( $image, $path );
$rotation = $this->getRotationForExif( $meta );
} else {
@@ -168,6 +185,7 @@ class ExifBitmapHandler extends BitmapHandler {
$gis[0] = $gis[1];
$gis[1] = $width;
}
+
return $gis;
}
@@ -180,16 +198,16 @@ class ExifBitmapHandler extends BitmapHandler {
* the width and height we normally work with is logical, and will match
* any produced output views.
*
- * @param $file File
+ * @param File $file
* @return int 0, 90, 180 or 270
*/
public function getRotation( $file ) {
- global $wgEnableAutoRotation;
- if ( !$wgEnableAutoRotation ) {
+ if ( !$this->autoRotateEnabled() ) {
return 0;
}
$data = $file->getMetadata();
+
return $this->getRotationForExif( $data );
}
@@ -199,8 +217,7 @@ class ExifBitmapHandler extends BitmapHandler {
*
* @param string $data
* @return int 0, 90, 180 or 270
- * @todo FIXME orientation can include flipping as well; see if this is an
- * issue!
+ * @todo FIXME: Orientation can include flipping as well; see if this is an issue!
*/
protected function getRotationForExif( $data ) {
if ( !$data ) {
@@ -222,6 +239,7 @@ class ExifBitmapHandler extends BitmapHandler {
return 0;
}
}
+
return 0;
}
}
diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php
index 1c5136f5..43569539 100644
--- a/includes/media/FormatMetadata.php
+++ b/includes/media/FormatMetadata.php
@@ -43,8 +43,26 @@
* is already a large number of messages using the 'exif' prefix.
*
* @ingroup Media
+ * @since 1.23 the class extends ContextSource and various formerly-public
+ * internal methods are private
*/
-class FormatMetadata {
+class FormatMetadata extends ContextSource {
+ /**
+ * Only output a single language for multi-language fields
+ * @var bool
+ * @since 1.23
+ */
+ protected $singleLang = false;
+
+ /**
+ * Trigger only outputting single language for multilanguage fields
+ *
+ * @param bool $val
+ * @since 1.23
+ */
+ public function setSingleLanguage( $val ) {
+ $this->singleLang = $val;
+ }
/**
* Numbers given by Exif user agents are often magical, that is they
@@ -52,13 +70,34 @@ class FormatMetadata {
* value which most of the time are plain integers. This function
* formats Exif (and other metadata) values into human readable form.
*
- * @param array $tags the Exif data to format ( as returned by
- * Exif::getFilteredData() or BitmapMetadataHandler )
+ * This is the usual entry point for this class.
+ *
+ * @param array $tags The Exif data to format ( as returned by
+ * Exif::getFilteredData() or BitmapMetadataHandler )
+ * @param bool|IContextSource $context Context to use (optional)
* @return array
*/
- public static function getFormattedData( $tags ) {
- global $wgLang;
+ public static function getFormattedData( $tags, $context = false ) {
+ $obj = new FormatMetadata;
+ if ( $context ) {
+ $obj->setContext( $context );
+ }
+ return $obj->makeFormattedData( $tags );
+ }
+
+ /**
+ * Numbers given by Exif user agents are often magical, that is they
+ * should be replaced by a detailed explanation depending on their
+ * value which most of the time are plain integers. This function
+ * formats Exif (and other metadata) values into human readable form.
+ *
+ * @param array $tags The Exif data to format ( as returned by
+ * Exif::getFilteredData() or BitmapMetadataHandler )
+ * @return array
+ * @since 1.23
+ */
+ public function makeFormattedData( $tags ) {
$resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
unset( $tags['ResolutionUnit'] );
@@ -67,7 +106,7 @@ class FormatMetadata {
// This seems ugly to wrap non-array's in an array just to unwrap again,
// especially when most of the time it is not an array
if ( !is_array( $tags[$tag] ) ) {
- $vals = Array( $vals );
+ $vals = array( $vals );
}
// _type is a special value to say what array type
@@ -107,7 +146,7 @@ class FormatMetadata {
$time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] );
// the 1971:01:01 is just a placeholder, and not shown to user.
if ( $time && intval( $time ) > 0 ) {
- $tags[$tag] = $wgLang->time( $time );
+ $tags[$tag] = $this->getLanguage()->time( $time );
}
} catch ( TimestampException $e ) {
// This shouldn't happen, but we've seen bad formats
@@ -121,727 +160,892 @@ class FormatMetadata {
// instead of the other props which are single
// valued (mostly) so handle as a special case.
if ( $tag === 'Contact' ) {
- $vals = self::collapseContactInfo( $vals );
+ $vals = $this->collapseContactInfo( $vals );
continue;
}
foreach ( $vals as &$val ) {
switch ( $tag ) {
- case 'Compression':
- switch ( $val ) {
- case 1: case 2: case 3: case 4:
- case 5: case 6: case 7: case 8:
- case 32773: case 32946: case 34712:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'Compression':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 32773:
+ case 32946:
+ case 34712:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'PhotometricInterpretation':
- switch ( $val ) {
- case 2: case 6:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'PhotometricInterpretation':
+ switch ( $val ) {
+ case 2:
+ case 6:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'Orientation':
- switch ( $val ) {
- case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'Orientation':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'PlanarConfiguration':
- switch ( $val ) {
- case 1: case 2:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
- break;
- }
- break;
-
- // TODO: YCbCrSubSampling
- case 'YCbCrPositioning':
- switch ( $val ) {
- case 1:
- case 2:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'PlanarConfiguration':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
-
- case 'XResolution':
- case 'YResolution':
- switch ( $resolutionunit ) {
- case 2:
- $val = self::msg( 'XYResolution', 'i', self::formatNum( $val ) );
- break;
- case 3:
- $val = self::msg( 'XYResolution', 'c', self::formatNum( $val ) );
- break;
- default:
- /* If not recognized, display as is. */
- break;
- }
- break;
-
- // TODO: YCbCrCoefficients #p27 (see annex E)
- case 'ExifVersion': case 'FlashpixVersion':
- $val = "$val" / 100;
- break;
- case 'ColorSpace':
- switch ( $val ) {
- case 1: case 65535:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ // TODO: YCbCrSubSampling
+ case 'YCbCrPositioning':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'ComponentsConfiguration':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6:
- $val = self::msg( $tag, $val );
+ case 'XResolution':
+ case 'YResolution':
+ switch ( $resolutionunit ) {
+ case 2:
+ $val = $this->exifMsg( 'XYResolution', 'i', $this->formatNum( $val ) );
+ break;
+ case 3:
+ $val = $this->exifMsg( 'XYResolution', 'c', $this->formatNum( $val ) );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ // TODO: YCbCrCoefficients #p27 (see annex E)
+ case 'ExifVersion':
+ case 'FlashpixVersion':
+ $val = "$val" / 100;
break;
- }
- break;
-
- case 'DateTime':
- case 'DateTimeOriginal':
- case 'DateTimeDigitized':
- case 'DateTimeReleased':
- case 'DateTimeExpires':
- case 'GPSDateStamp':
- case 'dc-date':
- case 'DateTimeMetadata':
- if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
- $val = wfMessage( 'exif-unknowndate' )->text();
- } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $val ) ) {
- // Full date.
- $time = wfTimestamp( TS_MW, $val );
- if ( $time && intval( $time ) > 0 ) {
- $val = $wgLang->timeanddate( $time );
- }
- } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
- // No second field. Still format the same
- // since timeanddate doesn't include seconds anyways,
- // but second still available in api
- $time = wfTimestamp( TS_MW, $val . ':00' );
- if ( $time && intval( $time ) > 0 ) {
- $val = $wgLang->timeanddate( $time );
- }
- } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
- // If only the date but not the time is filled in.
- $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
- . substr( $val, 5, 2 )
- . substr( $val, 8, 2 )
- . '000000' );
- if ( $time && intval( $time ) > 0 ) {
- $val = $wgLang->date( $time );
- }
- }
- // else it will just output $val without formatting it.
- break;
- case 'ExposureProgram':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
- $val = self::msg( $tag, $val );
+ case 'ColorSpace':
+ switch ( $val ) {
+ case 1:
+ case 65535:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'ComponentsConfiguration':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'SubjectDistance':
- $val = self::msg( $tag, '', self::formatNum( $val ) );
- break;
+ case 'DateTime':
+ case 'DateTimeOriginal':
+ case 'DateTimeDigitized':
+ case 'DateTimeReleased':
+ case 'DateTimeExpires':
+ case 'GPSDateStamp':
+ case 'dc-date':
+ case 'DateTimeMetadata':
+ if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
+ $val = $this->msg( 'exif-unknowndate' )->text();
+ } elseif ( preg_match(
+ '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D',
+ $val
+ ) ) {
+ // Full date.
+ $time = wfTimestamp( TS_MW, $val );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $this->getLanguage()->timeanddate( $time );
+ }
+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
+ // No second field. Still format the same
+ // since timeanddate doesn't include seconds anyways,
+ // but second still available in api
+ $time = wfTimestamp( TS_MW, $val . ':00' );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $this->getLanguage()->timeanddate( $time );
+ }
+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
+ // If only the date but not the time is filled in.
+ $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
+ . substr( $val, 5, 2 )
+ . substr( $val, 8, 2 )
+ . '000000' );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $this->getLanguage()->date( $time );
+ }
+ }
+ // else it will just output $val without formatting it.
+ break;
- case 'MeteringMode':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
- $val = self::msg( $tag, $val );
+ case 'ExposureProgram':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'SubjectDistance':
+ $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) );
break;
- }
- break;
-
- case 'LightSource':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
- case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
- case 21: case 22: case 23: case 24: case 255:
- $val = self::msg( $tag, $val );
+
+ case 'MeteringMode':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 255:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'LightSource':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ case 255:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
-
- case 'Flash':
- $flashDecode = array(
- 'fired' => $val & bindec( '00000001' ),
- 'return' => ( $val & bindec( '00000110' ) ) >> 1,
- 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
- 'function' => ( $val & bindec( '00100000' ) ) >> 5,
- 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
+
+ case 'Flash':
+ $flashDecode = array(
+ 'fired' => $val & bindec( '00000001' ),
+ 'return' => ( $val & bindec( '00000110' ) ) >> 1,
+ 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
+ 'function' => ( $val & bindec( '00100000' ) ) >> 5,
+ 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
// 'reserved' => ($val & bindec( '10000000' )) >> 7,
- );
- $flashMsgs = array();
- # We do not need to handle unknown values since all are used.
- foreach ( $flashDecode as $subTag => $subValue ) {
- # We do not need any message for zeroed values.
- if ( $subTag != 'fired' && $subValue == 0 ) {
- continue;
+ );
+ $flashMsgs = array();
+ # We do not need to handle unknown values since all are used.
+ foreach ( $flashDecode as $subTag => $subValue ) {
+ # We do not need any message for zeroed values.
+ if ( $subTag != 'fired' && $subValue == 0 ) {
+ continue;
+ }
+ $fullTag = $tag . '-' . $subTag;
+ $flashMsgs[] = $this->exifMsg( $fullTag, $subValue );
}
- $fullTag = $tag . '-' . $subTag;
- $flashMsgs[] = self::msg( $fullTag, $subValue );
- }
- $val = $wgLang->commaList( $flashMsgs );
- break;
-
- case 'FocalPlaneResolutionUnit':
- switch ( $val ) {
- case 2:
- $val = self::msg( $tag, $val );
+ $val = $this->getLanguage()->commaList( $flashMsgs );
break;
- default:
- /* If not recognized, display as is. */
- break;
- }
- break;
- case 'SensingMethod':
- switch ( $val ) {
- case 1: case 2: case 3: case 4: case 5: case 7: case 8:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'FocalPlaneResolutionUnit':
+ switch ( $val ) {
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'FileSource':
- switch ( $val ) {
- case 3:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'SensingMethod':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 7:
+ case 8:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'SceneType':
- switch ( $val ) {
- case 1:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'FileSource':
+ switch ( $val ) {
+ case 3:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'CustomRendered':
- switch ( $val ) {
- case 0: case 1:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'SceneType':
+ switch ( $val ) {
+ case 1:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'ExposureMode':
- switch ( $val ) {
- case 0: case 1: case 2:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'CustomRendered':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'WhiteBalance':
- switch ( $val ) {
- case 0: case 1:
- $val = self::msg( $tag, $val );
+ case 'ExposureMode':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'WhiteBalance':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'SceneCaptureType':
- switch ( $val ) {
- case 0: case 1: case 2: case 3:
- $val = self::msg( $tag, $val );
+ case 'SceneCaptureType':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'GainControl':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'GainControl':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4:
- $val = self::msg( $tag, $val );
+ case 'Contrast':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'Saturation':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'Contrast':
- switch ( $val ) {
- case 0: case 1: case 2:
- $val = self::msg( $tag, $val );
+ case 'Sharpness':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'SubjectDistanceRange':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'Saturation':
- switch ( $val ) {
- case 0: case 1: case 2:
- $val = self::msg( $tag, $val );
+ //The GPS...Ref values are kept for compatibility, probably won't be reached.
+ case 'GPSLatitudeRef':
+ case 'GPSDestLatitudeRef':
+ switch ( $val ) {
+ case 'N':
+ case 'S':
+ $val = $this->exifMsg( 'GPSLatitude', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'GPSLongitudeRef':
+ case 'GPSDestLongitudeRef':
+ switch ( $val ) {
+ case 'E':
+ case 'W':
+ $val = $this->exifMsg( 'GPSLongitude', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'Sharpness':
- switch ( $val ) {
- case 0: case 1: case 2:
- $val = self::msg( $tag, $val );
+ case 'GPSAltitude':
+ if ( $val < 0 ) {
+ $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) );
+ } else {
+ $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) );
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'GPSStatus':
+ switch ( $val ) {
+ case 'A':
+ case 'V':
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'SubjectDistanceRange':
- switch ( $val ) {
- case 0: case 1: case 2: case 3:
- $val = self::msg( $tag, $val );
+ case 'GPSMeasureMode':
+ switch ( $val ) {
+ case 2:
+ case 3:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'GPSTrackRef':
+ case 'GPSImgDirectionRef':
+ case 'GPSDestBearingRef':
+ switch ( $val ) {
+ case 'T':
+ case 'M':
+ $val = $this->exifMsg( 'GPSDirection', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
-
- //The GPS...Ref values are kept for compatibility, probably won't be reached.
- case 'GPSLatitudeRef':
- case 'GPSDestLatitudeRef':
- switch ( $val ) {
- case 'N': case 'S':
- $val = self::msg( 'GPSLatitude', $val );
+
+ case 'GPSLatitude':
+ case 'GPSDestLatitude':
+ $val = $this->formatCoords( $val, 'latitude' );
break;
- default:
- /* If not recognized, display as is. */
+ case 'GPSLongitude':
+ case 'GPSDestLongitude':
+ $val = $this->formatCoords( $val, 'longitude' );
break;
- }
- break;
- case 'GPSLongitudeRef':
- case 'GPSDestLongitudeRef':
- switch ( $val ) {
- case 'E': case 'W':
- $val = self::msg( 'GPSLongitude', $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'GPSSpeedRef':
+ switch ( $val ) {
+ case 'K':
+ case 'M':
+ case 'N':
+ $val = $this->exifMsg( 'GPSSpeed', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'GPSAltitude':
- if ( $val < 0 ) {
- $val = self::msg( 'GPSAltitude', 'below-sealevel', self::formatNum( -$val, 3 ) );
- } else {
- $val = self::msg( 'GPSAltitude', 'above-sealevel', self::formatNum( $val, 3 ) );
- }
- break;
+ case 'GPSDestDistanceRef':
+ switch ( $val ) {
+ case 'K':
+ case 'M':
+ case 'N':
+ $val = $this->exifMsg( 'GPSDestDistance', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
- case 'GPSStatus':
- switch ( $val ) {
- case 'A': case 'V':
- $val = self::msg( $tag, $val );
+ case 'GPSDOP':
+ // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
+ if ( $val <= 2 ) {
+ $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) );
+ } elseif ( $val <= 5 ) {
+ $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) );
+ } elseif ( $val <= 10 ) {
+ $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) );
+ } elseif ( $val <= 20 ) {
+ $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) );
+ } else {
+ $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) );
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ // This is not in the Exif standard, just a special
+ // case for our purposes which enables wikis to wikify
+ // the make, model and software name to link to their articles.
+ case 'Make':
+ case 'Model':
+ $val = $this->exifMsg( $tag, '', $val );
break;
- }
- break;
- case 'GPSMeasureMode':
- switch ( $val ) {
- case 2: case 3:
- $val = self::msg( $tag, $val );
+ case 'Software':
+ if ( is_array( $val ) ) {
+ //if its a software, version array.
+ $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text();
+ } else {
+ $val = $this->exifMsg( $tag, '', $val );
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'ExposureTime':
+ // Show the pretty fraction as well as decimal version
+ $val = $this->msg( 'exif-exposuretime-format',
+ $this->formatFraction( $val ), $this->formatNum( $val ) )->text();
break;
- }
- break;
-
- case 'GPSTrackRef':
- case 'GPSImgDirectionRef':
- case 'GPSDestBearingRef':
- switch ( $val ) {
- case 'T': case 'M':
- $val = self::msg( 'GPSDirection', $val );
+ case 'ISOSpeedRatings':
+ // If its = 65535 that means its at the
+ // limit of the size of Exif::short and
+ // is really higher.
+ if ( $val == '65535' ) {
+ $val = $this->exifMsg( $tag, 'overflow' );
+ } else {
+ $val = $this->formatNum( $val );
+ }
break;
- default:
- /* If not recognized, display as is. */
+ case 'FNumber':
+ $val = $this->msg( 'exif-fnumber-format',
+ $this->formatNum( $val ) )->text();
break;
- }
- break;
-
- case 'GPSLatitude':
- case 'GPSDestLatitude':
- $val = self::formatCoords( $val, 'latitude' );
- break;
- case 'GPSLongitude':
- case 'GPSDestLongitude':
- $val = self::formatCoords( $val, 'longitude' );
- break;
-
- case 'GPSSpeedRef':
- switch ( $val ) {
- case 'K': case 'M': case 'N':
- $val = self::msg( 'GPSSpeed', $val );
+
+ case 'FocalLength':
+ case 'FocalLengthIn35mmFilm':
+ $val = $this->msg( 'exif-focallength-format',
+ $this->formatNum( $val ) )->text();
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'MaxApertureValue':
+ if ( strpos( $val, '/' ) !== false ) {
+ // need to expand this earlier to calculate fNumber
+ list( $n, $d ) = explode( '/', $val );
+ if ( is_numeric( $n ) && is_numeric( $d ) ) {
+ $val = $n / $d;
+ }
+ }
+ if ( is_numeric( $val ) ) {
+ $fNumber = pow( 2, $val / 2 );
+ if ( $fNumber !== false ) {
+ $val = $this->msg( 'exif-maxaperturevalue-value',
+ $this->formatNum( $val ),
+ $this->formatNum( $fNumber, 2 )
+ )->text();
+ }
+ }
break;
- }
- break;
- case 'GPSDestDistanceRef':
- switch ( $val ) {
- case 'K': case 'M': case 'N':
- $val = self::msg( 'GPSDestDistance', $val );
+ case 'iimCategory':
+ switch ( strtolower( $val ) ) {
+ // See pg 29 of IPTC photo
+ // metadata standard.
+ case 'ace':
+ case 'clj':
+ case 'dis':
+ case 'fin':
+ case 'edu':
+ case 'evn':
+ case 'hth':
+ case 'hum':
+ case 'lab':
+ case 'lif':
+ case 'pol':
+ case 'rel':
+ case 'sci':
+ case 'soi':
+ case 'spo':
+ case 'war':
+ case 'wea':
+ $val = $this->exifMsg(
+ 'iimcategory',
+ $val
+ );
+ }
break;
- default:
- /* If not recognized, display as is. */
+ case 'SubjectNewsCode':
+ // Essentially like iimCategory.
+ // 8 (numeric) digit hierarchical
+ // classification. We decode the
+ // first 2 digits, which provide
+ // a broad category.
+ $val = $this->convertNewsCode( $val );
break;
- }
- break;
-
- case 'GPSDOP':
- // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
- if ( $val <= 2 ) {
- $val = self::msg( $tag, 'excellent', self::formatNum( $val ) );
- } elseif ( $val <= 5 ) {
- $val = self::msg( $tag, 'good', self::formatNum( $val ) );
- } elseif ( $val <= 10 ) {
- $val = self::msg( $tag, 'moderate', self::formatNum( $val ) );
- } elseif ( $val <= 20 ) {
- $val = self::msg( $tag, 'fair', self::formatNum( $val ) );
- } else {
- $val = self::msg( $tag, 'poor', self::formatNum( $val ) );
- }
- break;
-
- // This is not in the Exif standard, just a special
- // case for our purposes which enables wikis to wikify
- // the make, model and software name to link to their articles.
- case 'Make':
- case 'Model':
- $val = self::msg( $tag, '', $val );
- break;
-
- case 'Software':
- if ( is_array( $val ) ) {
- //if its a software, version array.
- $val = wfMessage( 'exif-software-version-value', $val[0], $val[1] )->text();
- } else {
- $val = self::msg( $tag, '', $val );
- }
- break;
-
- case 'ExposureTime':
- // Show the pretty fraction as well as decimal version
- $val = wfMessage( 'exif-exposuretime-format',
- self::formatFraction( $val ), self::formatNum( $val ) )->text();
- break;
- case 'ISOSpeedRatings':
- // If its = 65535 that means its at the
- // limit of the size of Exif::short and
- // is really higher.
- if ( $val == '65535' ) {
- $val = self::msg( $tag, 'overflow' );
- } else {
- $val = self::formatNum( $val );
- }
- break;
- case 'FNumber':
- $val = wfMessage( 'exif-fnumber-format',
- self::formatNum( $val ) )->text();
- break;
-
- case 'FocalLength': case 'FocalLengthIn35mmFilm':
- $val = wfMessage( 'exif-focallength-format',
- self::formatNum( $val ) )->text();
- break;
-
- case 'MaxApertureValue':
- if ( strpos( $val, '/' ) !== false ) {
- // need to expand this earlier to calculate fNumber
- list( $n, $d ) = explode( '/', $val );
- if ( is_numeric( $n ) && is_numeric( $d ) ) {
- $val = $n / $d;
- }
- }
- if ( is_numeric( $val ) ) {
- $fNumber = pow( 2, $val / 2 );
- if ( $fNumber !== false ) {
- $val = wfMessage( 'exif-maxaperturevalue-value',
- self::formatNum( $val ),
- self::formatNum( $fNumber, 2 )
- )->text();
+ case 'Urgency':
+ // 1-8 with 1 being highest, 5 normal
+ // 0 is reserved, and 9 is 'user-defined'.
+ $urgency = '';
+ if ( $val == 0 || $val == 9 ) {
+ $urgency = 'other';
+ } elseif ( $val < 5 && $val > 1 ) {
+ $urgency = 'high';
+ } elseif ( $val == 5 ) {
+ $urgency = 'normal';
+ } elseif ( $val <= 8 && $val > 5 ) {
+ $urgency = 'low';
}
- }
- break;
-
- case 'iimCategory':
- switch ( strtolower( $val ) ) {
- // See pg 29 of IPTC photo
- // metadata standard.
- case 'ace': case 'clj':
- case 'dis': case 'fin':
- case 'edu': case 'evn':
- case 'hth': case 'hum':
- case 'lab': case 'lif':
- case 'pol': case 'rel':
- case 'sci': case 'soi':
- case 'spo': case 'war':
- case 'wea':
- $val = self::msg(
- 'iimcategory',
- $val
+
+ if ( $urgency !== '' ) {
+ $val = $this->exifMsg( 'urgency',
+ $urgency, $val
);
- }
- break;
- case 'SubjectNewsCode':
- // Essentially like iimCategory.
- // 8 (numeric) digit hierarchical
- // classification. We decode the
- // first 2 digits, which provide
- // a broad category.
- $val = self::convertNewsCode( $val );
- break;
- case 'Urgency':
- // 1-8 with 1 being highest, 5 normal
- // 0 is reserved, and 9 is 'user-defined'.
- $urgency = '';
- if ( $val == 0 || $val == 9 ) {
- $urgency = 'other';
- } elseif ( $val < 5 && $val > 1 ) {
- $urgency = 'high';
- } elseif ( $val == 5 ) {
- $urgency = 'normal';
- } elseif ( $val <= 8 && $val > 5 ) {
- $urgency = 'low';
- }
+ }
+ break;
- if ( $urgency !== '' ) {
- $val = self::msg( 'urgency',
- $urgency, $val
- );
- }
- break;
-
- // Things that have a unit of pixels.
- case 'OriginalImageHeight':
- case 'OriginalImageWidth':
- case 'PixelXDimension':
- case 'PixelYDimension':
- case 'ImageWidth':
- case 'ImageLength':
- $val = self::formatNum( $val ) . ' ' . wfMessage( 'unit-pixel' )->text();
- break;
-
- // Do not transform fields with pure text.
- // For some languages the formatNum()
- // conversion results to wrong output like
- // foo,bar@example,com or foo٫bar@example٫com.
- // Also some 'numeric' things like Scene codes
- // are included here as we really don't want
- // commas inserted.
- case 'ImageDescription':
- case 'Artist':
- case 'Copyright':
- case 'RelatedSoundFile':
- case 'ImageUniqueID':
- case 'SpectralSensitivity':
- case 'GPSSatellites':
- case 'GPSVersionID':
- case 'GPSMapDatum':
- case 'Keywords':
- case 'WorldRegionDest':
- case 'CountryDest':
- case 'CountryCodeDest':
- case 'ProvinceOrStateDest':
- case 'CityDest':
- case 'SublocationDest':
- case 'WorldRegionCreated':
- case 'CountryCreated':
- case 'CountryCodeCreated':
- case 'ProvinceOrStateCreated':
- case 'CityCreated':
- case 'SublocationCreated':
- case 'ObjectName':
- case 'SpecialInstructions':
- case 'Headline':
- case 'Credit':
- case 'Source':
- case 'EditStatus':
- case 'FixtureIdentifier':
- case 'LocationDest':
- case 'LocationDestCode':
- case 'Writer':
- case 'JPEGFileComment':
- case 'iimSupplementalCategory':
- case 'OriginalTransmissionRef':
- case 'Identifier':
- case 'dc-contributor':
- case 'dc-coverage':
- case 'dc-publisher':
- case 'dc-relation':
- case 'dc-rights':
- case 'dc-source':
- case 'dc-type':
- case 'Lens':
- case 'SerialNumber':
- case 'CameraOwnerName':
- case 'Label':
- case 'Nickname':
- case 'RightsCertificate':
- case 'CopyrightOwner':
- case 'UsageTerms':
- case 'WebStatement':
- case 'OriginalDocumentID':
- case 'LicenseUrl':
- case 'MorePermissionsUrl':
- case 'AttributionUrl':
- case 'PreferredAttributionName':
- case 'PNGFileComment':
- case 'Disclaimer':
- case 'ContentWarning':
- case 'GIFFileComment':
- case 'SceneCode':
- case 'IntellectualGenre':
- case 'Event':
- case 'OrginisationInImage':
- case 'PersonInImage':
-
- $val = htmlspecialchars( $val );
- break;
-
- case 'ObjectCycle':
- switch ( $val ) {
- case 'a': case 'p': case 'b':
- $val = self::msg( $tag, $val );
+ // Things that have a unit of pixels.
+ case 'OriginalImageHeight':
+ case 'OriginalImageWidth':
+ case 'PixelXDimension':
+ case 'PixelYDimension':
+ case 'ImageWidth':
+ case 'ImageLength':
+ $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text();
break;
- default:
+
+ // Do not transform fields with pure text.
+ // For some languages the formatNum()
+ // conversion results to wrong output like
+ // foo,bar@example,com or foo٫bar@example٫com.
+ // Also some 'numeric' things like Scene codes
+ // are included here as we really don't want
+ // commas inserted.
+ case 'ImageDescription':
+ case 'Artist':
+ case 'Copyright':
+ case 'RelatedSoundFile':
+ case 'ImageUniqueID':
+ case 'SpectralSensitivity':
+ case 'GPSSatellites':
+ case 'GPSVersionID':
+ case 'GPSMapDatum':
+ case 'Keywords':
+ case 'WorldRegionDest':
+ case 'CountryDest':
+ case 'CountryCodeDest':
+ case 'ProvinceOrStateDest':
+ case 'CityDest':
+ case 'SublocationDest':
+ case 'WorldRegionCreated':
+ case 'CountryCreated':
+ case 'CountryCodeCreated':
+ case 'ProvinceOrStateCreated':
+ case 'CityCreated':
+ case 'SublocationCreated':
+ case 'ObjectName':
+ case 'SpecialInstructions':
+ case 'Headline':
+ case 'Credit':
+ case 'Source':
+ case 'EditStatus':
+ case 'FixtureIdentifier':
+ case 'LocationDest':
+ case 'LocationDestCode':
+ case 'Writer':
+ case 'JPEGFileComment':
+ case 'iimSupplementalCategory':
+ case 'OriginalTransmissionRef':
+ case 'Identifier':
+ case 'dc-contributor':
+ case 'dc-coverage':
+ case 'dc-publisher':
+ case 'dc-relation':
+ case 'dc-rights':
+ case 'dc-source':
+ case 'dc-type':
+ case 'Lens':
+ case 'SerialNumber':
+ case 'CameraOwnerName':
+ case 'Label':
+ case 'Nickname':
+ case 'RightsCertificate':
+ case 'CopyrightOwner':
+ case 'UsageTerms':
+ case 'WebStatement':
+ case 'OriginalDocumentID':
+ case 'LicenseUrl':
+ case 'MorePermissionsUrl':
+ case 'AttributionUrl':
+ case 'PreferredAttributionName':
+ case 'PNGFileComment':
+ case 'Disclaimer':
+ case 'ContentWarning':
+ case 'GIFFileComment':
+ case 'SceneCode':
+ case 'IntellectualGenre':
+ case 'Event':
+ case 'OrginisationInImage':
+ case 'PersonInImage':
+
$val = htmlspecialchars( $val );
break;
- }
- break;
- case 'Copyrighted':
- switch ( $val ) {
- case 'True': case 'False':
- $val = self::msg( $tag, $val );
+
+ case 'ObjectCycle':
+ switch ( $val ) {
+ case 'a':
+ case 'p':
+ case 'b':
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ $val = htmlspecialchars( $val );
+ break;
+ }
+ break;
+ case 'Copyrighted':
+ switch ( $val ) {
+ case 'True':
+ case 'False':
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ }
+ break;
+ case 'Rating':
+ if ( $val == '-1' ) {
+ $val = $this->exifMsg( $tag, 'rejected' );
+ } else {
+ $val = $this->formatNum( $val );
+ }
break;
- }
- break;
- case 'Rating':
- if ( $val == '-1' ) {
- $val = self::msg( $tag, 'rejected' );
- } else {
- $val = self::formatNum( $val );
- }
- break;
- case 'LanguageCode':
- $lang = Language::fetchLanguageName( strtolower( $val ), $wgLang->getCode() );
- if ( $lang ) {
- $val = htmlspecialchars( $lang );
- } else {
- $val = htmlspecialchars( $val );
- }
- break;
+ case 'LanguageCode':
+ $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() );
+ if ( $lang ) {
+ $val = htmlspecialchars( $lang );
+ } else {
+ $val = htmlspecialchars( $val );
+ }
+ break;
- default:
- $val = self::formatNum( $val );
- break;
+ default:
+ $val = $this->formatNum( $val );
+ break;
}
}
// End formatting values, start flattening arrays.
- $vals = self::flattenArray( $vals, $type );
-
+ $vals = $this->flattenArrayReal( $vals, $type );
}
+
return $tags;
}
/**
+ * Flatten an array, using the content language for any messages.
+ *
+ * @param array $vals Array of values
+ * @param string $type Type of array (either lang, ul, ol).
+ * lang = language assoc array with keys being the lang code
+ * ul = unordered list, ol = ordered list
+ * type can also come from the '_type' member of $vals.
+ * @param bool $noHtml If to avoid returning anything resembling HTML.
+ * (Ugly hack for backwards compatibility with old MediaWiki).
+ * @param bool|IContextSource $context
+ * @return string Single value (in wiki-syntax).
+ * @since 1.23
+ */
+ public static function flattenArrayContentLang( $vals, $type = 'ul',
+ $noHtml = false, $context = false
+ ) {
+ global $wgContLang;
+ $obj = new FormatMetadata;
+ if ( $context ) {
+ $obj->setContext( $context );
+ }
+ $context = new DerivativeContext( $obj->getContext() );
+ $context->setLanguage( $wgContLang );
+ $obj->setContext( $context );
+
+ return $obj->flattenArrayReal( $vals, $type, $noHtml );
+ }
+
+ /**
+ * Flatten an array, using the user language for any messages.
+ *
+ * @param array $vals Array of values
+ * @param string $type Type of array (either lang, ul, ol).
+ * lang = language assoc array with keys being the lang code
+ * ul = unordered list, ol = ordered list
+ * type can also come from the '_type' member of $vals.
+ * @param bool $noHtml If to avoid returning anything resembling HTML.
+ * (Ugly hack for backwards compatibility with old MediaWiki).
+ * @param bool|IContextSource $context
+ * @return string Single value (in wiki-syntax).
+ */
+ public static function flattenArray( $vals, $type = 'ul', $noHtml = false, $context = false ) {
+ $obj = new FormatMetadata;
+ if ( $context ) {
+ $obj->setContext( $context );
+ }
+
+ return $obj->flattenArrayReal( $vals, $type, $noHtml );
+ }
+
+ /**
* A function to collapse multivalued tags into a single value.
* This turns an array of (for example) authors into a bulleted list.
*
* This is public on the basis it might be useful outside of this class.
*
- * @param array $vals array of values
+ * @param array $vals Array of values
* @param string $type Type of array (either lang, ul, ol).
- * lang = language assoc array with keys being the lang code
- * ul = unordered list, ol = ordered list
- * type can also come from the '_type' member of $vals.
- * @param $noHtml Boolean If to avoid returning anything resembling
- * html. (Ugly hack for backwards compatibility with old mediawiki).
- * @return String single value (in wiki-syntax).
+ * lang = language assoc array with keys being the lang code
+ * ul = unordered list, ol = ordered list
+ * type can also come from the '_type' member of $vals.
+ * @param bool $noHtml If to avoid returning anything resembling HTML.
+ * (Ugly hack for backwards compatibility with old mediawiki).
+ * @return string Single value (in wiki-syntax).
+ * @since 1.23
*/
- public static function flattenArray( $vals, $type = 'ul', $noHtml = false ) {
+ public function flattenArrayReal( $vals, $type = 'ul', $noHtml = false ) {
+ if ( !is_array( $vals ) ) {
+ return $vals; // do nothing if not an array;
+ }
+
if ( isset( $vals['_type'] ) ) {
$type = $vals['_type'];
unset( $vals['_type'] );
@@ -849,105 +1053,118 @@ class FormatMetadata {
if ( !is_array( $vals ) ) {
return $vals; // do nothing if not an array;
- }
- elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
+ } elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
return $vals[0];
- }
- elseif ( count( $vals ) === 0 ) {
+ } elseif ( count( $vals ) === 0 ) {
wfDebug( __METHOD__ . " metadata array with 0 elements!\n" );
+
return ""; // paranoia. This should never happen
- }
- /* @todo FIXME: This should hide some of the list entries if there are
- * say more than four. Especially if a field is translated into 20
- * languages, we don't want to show them all by default
- */
- else {
- global $wgContLang;
+ } else {
+ /* @todo FIXME: This should hide some of the list entries if there are
+ * say more than four. Especially if a field is translated into 20
+ * languages, we don't want to show them all by default
+ */
switch ( $type ) {
- case 'lang':
- // Display default, followed by ContLang,
- // followed by the rest in no particular
- // order.
-
- // Todo: hide some items if really long list.
-
- $content = '';
-
- $cLang = $wgContLang->getCode();
- $defaultItem = false;
- $defaultLang = false;
-
- // If default is set, save it for later,
- // as we don't know if it's equal to
- // one of the lang codes. (In xmp
- // you specify the language for a
- // default property by having both
- // a default prop, and one in the language
- // that are identical)
- if ( isset( $vals['x-default'] ) ) {
- $defaultItem = $vals['x-default'];
- unset( $vals['x-default'] );
- }
- // Do contentLanguage.
- if ( isset( $vals[$cLang] ) ) {
- $isDefault = false;
- if ( $vals[$cLang] === $defaultItem ) {
- $defaultItem = false;
- $isDefault = true;
+ case 'lang':
+ // Display default, followed by ContLang,
+ // followed by the rest in no particular
+ // order.
+
+ // Todo: hide some items if really long list.
+
+ $content = '';
+
+ $priorityLanguages = $this->getPriorityLanguages();
+ $defaultItem = false;
+ $defaultLang = false;
+
+ // If default is set, save it for later,
+ // as we don't know if it's equal to
+ // one of the lang codes. (In xmp
+ // you specify the language for a
+ // default property by having both
+ // a default prop, and one in the language
+ // that are identical)
+ if ( isset( $vals['x-default'] ) ) {
+ $defaultItem = $vals['x-default'];
+ unset( $vals['x-default'] );
+ }
+ foreach ( $priorityLanguages as $pLang ) {
+ if ( isset( $vals[$pLang] ) ) {
+ $isDefault = false;
+ if ( $vals[$pLang] === $defaultItem ) {
+ $defaultItem = false;
+ $isDefault = true;
+ }
+ $content .= $this->langItem(
+ $vals[$pLang], $pLang,
+ $isDefault, $noHtml );
+
+ unset( $vals[$pLang] );
+
+ if ( $this->singleLang ) {
+ return Html::rawElement( 'span',
+ array( 'lang' => $pLang ), $vals[$pLang] );
+ }
+ }
}
- $content .= self::langItem(
- $vals[$cLang], $cLang,
- $isDefault, $noHtml );
-
- unset( $vals[$cLang] );
- }
- // Now do the rest.
- foreach ( $vals as $lang => $item ) {
- if ( $item === $defaultItem ) {
- $defaultLang = $lang;
- continue;
+ // Now do the rest.
+ foreach ( $vals as $lang => $item ) {
+ if ( $item === $defaultItem ) {
+ $defaultLang = $lang;
+ continue;
+ }
+ $content .= $this->langItem( $item,
+ $lang, false, $noHtml );
+ if ( $this->singleLang ) {
+ return Html::rawElement( 'span',
+ array( 'lang' => $lang ), $item );
+ }
}
- $content .= self::langItem( $item,
- $lang, false, $noHtml );
- }
- if ( $defaultItem !== false ) {
- $content = self::langItem( $defaultItem,
- $defaultLang, true, $noHtml ) .
- $content;
- }
- if ( $noHtml ) {
- return $content;
- }
- return '<ul class="metadata-langlist">' .
+ if ( $defaultItem !== false ) {
+ $content = $this->langItem( $defaultItem,
+ $defaultLang, true, $noHtml ) .
+ $content;
+ if ( $this->singleLang ) {
+ return $defaultItem;
+ }
+ }
+ if ( $noHtml ) {
+ return $content;
+ }
+
+ return '<ul class="metadata-langlist">' .
$content .
'</ul>';
- case 'ol':
- if ( $noHtml ) {
- return "\n#" . implode( "\n#", $vals );
- }
- return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
- case 'ul':
- default:
- if ( $noHtml ) {
- return "\n*" . implode( "\n*", $vals );
- }
- return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
+ case 'ol':
+ if ( $noHtml ) {
+ return "\n#" . implode( "\n#", $vals );
+ }
+
+ return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
+ case 'ul':
+ default:
+ if ( $noHtml ) {
+ return "\n*" . implode( "\n*", $vals );
+ }
+
+ return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
}
}
}
/** Helper function for creating lists of translations.
*
- * @param string $value value (this is not escaped)
- * @param string $lang lang code of item or false
- * @param $default Boolean if it is default value.
- * @param $noHtml Boolean If to avoid html (for back-compat)
+ * @param string $value Value (this is not escaped)
+ * @param string $lang Lang code of item or false
+ * @param bool $default If it is default value.
+ * @param bool $noHtml If to avoid html (for back-compat)
* @throws MWException
- * @return string language item (Note: despite how this looks,
- * this is treated as wikitext not html).
+ * @return string Language item (Note: despite how this looks, this is
+ * treated as wikitext, not as HTML).
*/
- private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
+ private function langItem( $value, $lang, $default = false, $noHtml = false ) {
if ( $lang === false && $default === false ) {
throw new MWException( '$lang and $default cannot both '
. 'be false.' );
@@ -961,13 +1178,13 @@ class FormatMetadata {
}
if ( $lang === false ) {
+ $msg = $this->msg( 'metadata-langitem-default', $wrappedValue );
if ( $noHtml ) {
- return wfMessage( 'metadata-langitem-default',
- $wrappedValue )->text() . "\n\n";
+ return $msg->text() . "\n\n";
} /* else */
+
return '<li class="mw-metadata-lang-default">'
- . wfMessage( 'metadata-langitem-default',
- $wrappedValue )->text()
+ . $msg->text()
. "</li>\n";
}
@@ -984,9 +1201,9 @@ class FormatMetadata {
}
// else we have a language specified
+ $msg = $this->msg( 'metadata-langitem', $wrappedValue, $langName, $lang );
if ( $noHtml ) {
- return '*' . wfMessage( 'metadata-langitem',
- $wrappedValue, $langName, $lang )->text();
+ return '*' . $msg->text();
} /* else: */
$item = '<li class="mw-metadata-lang-code-'
@@ -995,49 +1212,48 @@ class FormatMetadata {
$item .= ' mw-metadata-lang-default';
}
$item .= '" lang="' . $lang . '">';
- $item .= wfMessage( 'metadata-langitem',
- $wrappedValue, $langName, $lang )->text();
+ $item .= $msg->text();
$item .= "</li>\n";
+
return $item;
}
/**
* Convenience function for getFormattedData()
*
- * @private
- *
- * @param string $tag the tag name to pass on
- * @param string $val the value of the tag
- * @param string $arg an argument to pass ($1)
- * @param string $arg2 a 2nd argument to pass ($2)
- * @return string A wfMessage of "exif-$tag-$val" in lower case
+ * @param string $tag The tag name to pass on
+ * @param string $val The value of the tag
+ * @param string $arg An argument to pass ($1)
+ * @param string $arg2 A 2nd argument to pass ($2)
+ * @return string The text content of "exif-$tag-$val" message in lower case
*/
- static function msg( $tag, $val, $arg = null, $arg2 = null ) {
+ private function exifMsg( $tag, $val, $arg = null, $arg2 = null ) {
global $wgContLang;
if ( $val === '' ) {
$val = 'value';
}
- return wfMessage( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
+
+ return $this->msg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
}
/**
* Format a number, convert numbers from fractions into floating point
* numbers, joins arrays of numbers with commas.
*
- * @param $num Mixed: the value to format
- * @param $round float|int|bool digits to round to or false.
+ * @param mixed $num The value to format
+ * @param float|int|bool $round Digits to round to or false.
* @return mixed A floating point number or whatever we were fed
*/
- static function formatNum( $num, $round = false ) {
- global $wgLang;
+ private function formatNum( $num, $round = false ) {
$m = array();
if ( is_array( $num ) ) {
$out = array();
foreach ( $num as $number ) {
- $out[] = self::formatNum( $number );
+ $out[] = $this->formatNum( $number );
}
- return $wgLang->commaList( $out );
+
+ return $this->getLanguage()->commaList( $out );
}
if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
if ( $m[2] != 0 ) {
@@ -1049,46 +1265,45 @@ class FormatMetadata {
$newNum = $num;
}
- return $wgLang->formatNum( $newNum );
+ return $this->getLanguage()->formatNum( $newNum );
} else {
if ( is_numeric( $num ) && $round !== false ) {
$num = round( $num, $round );
}
- return $wgLang->formatNum( $num );
+
+ return $this->getLanguage()->formatNum( $num );
}
}
/**
* Format a rational number, reducing fractions
*
- * @private
- *
- * @param $num Mixed: the value to format
+ * @param mixed $num The value to format
* @return mixed A floating point number or whatever we were fed
*/
- static function formatFraction( $num ) {
+ private function formatFraction( $num ) {
$m = array();
if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
$numerator = intval( $m[1] );
$denominator = intval( $m[2] );
- $gcd = self::gcd( abs( $numerator ), $denominator );
+ $gcd = $this->gcd( abs( $numerator ), $denominator );
if ( $gcd != 0 ) {
// 0 shouldn't happen! ;)
- return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd );
+ return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
}
}
- return self::formatNum( $num );
+
+ return $this->formatNum( $num );
}
/**
* Calculate the greatest common divisor of two integers.
*
- * @param $a Integer: Numerator
- * @param $b Integer: Denominator
+ * @param int $a Numerator
+ * @param int $b Denominator
* @return int
- * @private
*/
- static function gcd( $a, $b ) {
+ private function gcd( $a, $b ) {
/*
// http://en.wikipedia.org/wiki/Euclidean_algorithm
// Recursive form would be:
@@ -1104,6 +1319,7 @@ class FormatMetadata {
$a = $b;
$b = $remainder;
}
+
return $a;
}
@@ -1119,7 +1335,7 @@ class FormatMetadata {
* @param string $val The 8 digit news code.
* @return string The human readable form
*/
- private static function convertNewsCode( $val ) {
+ private function convertNewsCode( $val ) {
if ( !preg_match( '/^\d{8}$/D', $val ) ) {
// Not a valid news code.
return $val;
@@ -1179,9 +1395,10 @@ class FormatMetadata {
break;
}
if ( $cat !== '' ) {
- $catMsg = self::msg( 'iimcategory', $cat );
- $val = self::msg( 'subjectnewscode', '', $val, $catMsg );
+ $catMsg = $this->exifMsg( 'iimcategory', $cat );
+ $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
}
+
return $val;
}
@@ -1189,11 +1406,11 @@ class FormatMetadata {
* Format a coordinate value, convert numbers from floating point
* into degree minute second representation.
*
- * @param int $coord degrees, minutes and seconds
- * @param string $type latitude or longitude (for if its a NWS or E)
+ * @param int $coord Degrees, minutes and seconds
+ * @param string $type Latitude or longitude (for if its a NWS or E)
* @return mixed A floating point number or whatever we were fed
*/
- static function formatCoords( $coord, $type ) {
+ private function formatCoords( $coord, $type ) {
$ref = '';
if ( $coord < 0 ) {
$nCoord = -$coord;
@@ -1215,28 +1432,28 @@ class FormatMetadata {
$min = floor( ( $nCoord - $deg ) * 60.0 );
$sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
- $deg = self::formatNum( $deg );
- $min = self::formatNum( $min );
- $sec = self::formatNum( $sec );
+ $deg = $this->formatNum( $deg );
+ $min = $this->formatNum( $min );
+ $sec = $this->formatNum( $sec );
- return wfMessage( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
+ return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
}
/**
* Format the contact info field into a single value.
*
- * @param array $vals array with fields of the ContactInfo
- * struct defined in the IPTC4XMP spec. Or potentially
- * an array with one element that is a free form text
- * value from the older iptc iim 1:118 prop.
- *
* This function might be called from
* JpegHandler::convertMetadataVersion which is why it is
* public.
*
- * @return String of html-ish looking wikitext
+ * @param array $vals Array with fields of the ContactInfo
+ * struct defined in the IPTC4XMP spec. Or potentially
+ * an array with one element that is a free form text
+ * value from the older iptc iim 1:118 prop.
+ * @return string HTML-ish looking wikitext
+ * @since 1.23 no longer static
*/
- public static function collapseContactInfo( $vals ) {
+ public function collapseContactInfo( $vals ) {
if ( !( isset( $vals['CiAdrExtadr'] )
|| isset( $vals['CiAdrCity'] )
|| isset( $vals['CiAdrCtry'] )
@@ -1258,7 +1475,8 @@ class FormatMetadata {
foreach ( $vals as &$val ) {
$val = htmlspecialchars( $val );
}
- return self::flattenArray( $vals );
+
+ return $this->flattenArrayReal( $vals );
} else {
// We have a real ContactInfo field.
// Its unclear if all these fields have to be
@@ -1308,10 +1526,10 @@ class FormatMetadata {
$emails[] = $finalEmail;
} else {
$emails[] = '[mailto:'
- . $finalEmail
- . ' <span class="email">'
- . $finalEmail
- . '</span>]';
+ . $finalEmail
+ . ' <span class="email">'
+ . $finalEmail
+ . '</span>]';
}
}
}
@@ -1340,34 +1558,315 @@ class FormatMetadata {
. htmlspecialchars( $vals['CiUrlWork'] )
. '</span>';
}
- return wfMessage( 'exif-contact-value', $email, $url,
+
+ return $this->msg( 'exif-contact-value', $email, $url,
$street, $city, $region, $postal, $country,
$tel )->text();
}
}
-}
-/** For compatability with old FormatExif class
- * which some extensions use.
- *
- * @deprecated since 1.18
- *
- */
-class FormatExif {
- var $meta;
+ /**
+ * Get a list of fields that are visible by default.
+ *
+ * @return array
+ * @since 1.23
+ */
+ public static function getVisibleFields() {
+ $fields = array();
+ $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
+ foreach ( $lines as $line ) {
+ $matches = array();
+ if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
+ $fields[] = $matches[1];
+ }
+ }
+ $fields = array_map( 'strtolower', $fields );
+
+ return $fields;
+ }
+
+ /**
+ * Get an array of extended metadata. (See the imageinfo API for format.)
+ *
+ * @param File $file File to use
+ * @return array [<property name> => ['value' => <value>]], or [] on error
+ * @since 1.23
+ */
+ public function fetchExtendedMetadata( File $file ) {
+ global $wgMemc;
+
+ wfProfileIn( __METHOD__ );
+
+ // If revision deleted, exit immediately
+ if ( $file->isDeleted( File::DELETED_FILE ) ) {
+ wfProfileOut( __METHOD__ );
+
+ return array();
+ }
+
+ $cacheKey = wfMemcKey(
+ 'getExtendedMetadata',
+ $this->getLanguage()->getCode(),
+ (int)$this->singleLang,
+ $file->getSha1()
+ );
+
+ $cachedValue = $wgMemc->get( $cacheKey );
+ if (
+ $cachedValue
+ && wfRunHooks( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) )
+ ) {
+ $extendedMetadata = $cachedValue['data'];
+ } else {
+ $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
+ $fileMetadata = $this->getExtendedMetadataFromFile( $file );
+ $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
+ if ( $this->singleLang ) {
+ $this->resolveMultilangMetadata( $extendedMetadata );
+ }
+ // Make sure the metadata won't break the API when an XML format is used.
+ // This is an API-specific function so it would be cleaner to call it from
+ // outside fetchExtendedMetadata, but this way we don't need to redo the
+ // computation on a cache hit.
+ $this->sanitizeArrayForXml( $extendedMetadata );
+ $valueToCache = array( 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() );
+ $wgMemc->set( $cacheKey, $valueToCache, $maxCacheTime );
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return $extendedMetadata;
+ }
+
+ /**
+ * Get file-based metadata in standardized format.
+ *
+ * Note that for a remote file, this might return metadata supplied by extensions.
+ *
+ * @param File $file File to use
+ * @return array [<property name> => ['value' => <value>]], or [] on error
+ * @since 1.23
+ */
+ protected function getExtendedMetadataFromFile( File $file ) {
+ // If this is a remote file accessed via an API request, we already
+ // have remote metadata so we just ignore any local one
+ if ( $file instanceof ForeignAPIFile ) {
+ // In case of error we pretend no metadata - this will get cached.
+ // Might or might not be a good idea.
+ return $file->getExtendedMetadata() ?: array();
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
+
+ $fileMetadata = array(
+ // This is modification time, which is close to "upload" time.
+ 'DateTime' => array(
+ 'value' => $uploadDate,
+ 'source' => 'mediawiki-metadata',
+ ),
+ );
+
+ $title = $file->getTitle();
+ if ( $title ) {
+ $text = $title->getText();
+ $pos = strrpos( $text, '.' );
+
+ if ( $pos ) {
+ $name = substr( $text, 0, $pos );
+ } else {
+ $name = $text;
+ }
+
+ $fileMetadata['ObjectName'] = array(
+ 'value' => $name,
+ 'source' => 'mediawiki-metadata',
+ );
+ }
+
+ $common = $file->getCommonMetaArray();
+
+ if ( $common !== false ) {
+ foreach ( $common as $key => $value ) {
+ $fileMetadata[$key] = array(
+ 'value' => $value,
+ 'source' => 'file-metadata',
+ );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return $fileMetadata;
+ }
+
+ /**
+ * Get additional metadata from hooks in standardized format.
+ *
+ * @param File $file File to use
+ * @param array $extendedMetadata
+ * @param int $maxCacheTime Hook handlers might use this parameter to override cache time
+ *
+ * @return array [<property name> => ['value' => <value>]], or [] on error
+ * @since 1.23
+ */
+ protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
+ &$maxCacheTime
+ ) {
+ wfProfileIn( __METHOD__ );
+
+ wfRunHooks( 'GetExtendedMetadata', array(
+ &$extendedMetadata,
+ $file,
+ $this->getContext(),
+ $this->singleLang,
+ &$maxCacheTime
+ ) );
+
+ $visible = array_flip( self::getVisibleFields() );
+ foreach ( $extendedMetadata as $key => $value ) {
+ if ( !isset( $visible[strtolower( $key )] ) ) {
+ $extendedMetadata[$key]['hidden'] = '';
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return $extendedMetadata;
+ }
/**
- * @param $meta array
+ * Turns an XMP-style multilang array into a single value.
+ * If the value is not a multilang array, it is returned unchanged.
+ * See mediawiki.org/wiki/Manual:File_metadata_handling#Multi-language_array_format
+ * @param mixed $value
+ * @return mixed Value in best language, null if there were no languages at all
+ * @since 1.23
*/
- function FormatExif( $meta ) {
- wfDeprecated( __METHOD__, '1.18' );
- $this->meta = $meta;
+ protected function resolveMultilangValue( $value ) {
+ if (
+ !is_array( $value )
+ || !isset( $value['_type'] )
+ || $value['_type'] != 'lang'
+ ) {
+ return $value; // do nothing if not a multilang array
+ }
+
+ // choose the language best matching user or site settings
+ $priorityLanguages = $this->getPriorityLanguages();
+ foreach ( $priorityLanguages as $lang ) {
+ if ( isset( $value[$lang] ) ) {
+ return $value[$lang];
+ }
+ }
+
+ // otherwise go with the default language, if set
+ if ( isset( $value['x-default'] ) ) {
+ return $value['x-default'];
+ }
+
+ // otherwise just return any one language
+ unset( $value['_type'] );
+ if ( !empty( $value ) ) {
+ return reset( $value );
+ }
+
+ // this should not happen; signal error
+ return null;
+ }
+
+ /**
+ * Takes an array returned by the getExtendedMetadata* functions,
+ * and resolves multi-language values in it.
+ * @param array $metadata
+ * @since 1.23
+ */
+ protected function resolveMultilangMetadata( &$metadata ) {
+ if ( !is_array( $metadata ) ) {
+ return;
+ }
+ foreach ( $metadata as &$field ) {
+ if ( isset( $field['value'] ) ) {
+ $field['value'] = $this->resolveMultilangValue( $field['value'] );
+ }
+ }
+ }
+
+ /**
+ * Makes sure the given array is a valid API response fragment
+ * (can be transformed into XML)
+ * @param array $arr
+ */
+ protected function sanitizeArrayForXml( &$arr ) {
+ if ( !is_array( $arr ) ) {
+ return;
+ }
+
+ $counter = 1;
+ foreach ( $arr as $key => &$value ) {
+ $sanitizedKey = $this->sanitizeKeyForXml( $key );
+ if ( $sanitizedKey !== $key ) {
+ if ( isset( $arr[$sanitizedKey] ) ) {
+ // Make the sanitized keys hopefully unique.
+ // To make it definitely unique would be too much effort, given that
+ // sanitizing is only needed for misformatted metadata anyway, but
+ // this at least covers the case when $arr is numeric.
+ $sanitizedKey .= $counter;
+ ++$counter;
+ }
+ $arr[$sanitizedKey] = $arr[$key];
+ unset( $arr[$key] );
+ }
+ if ( is_array( $value ) ) {
+ $this->sanitizeArrayForXml( $value );
+ }
+ }
+ }
+
+ /**
+ * Turns a string into a valid XML identifier.
+ * Used to ensure that keys of an associative array in the
+ * API response do not break the XML formatter.
+ * @param string $key
+ * @return string
+ * @since 1.23
+ */
+ protected function sanitizeKeyForXml( $key ) {
+ // drop all characters which are not valid in an XML tag name
+ // a bunch of non-ASCII letters would be valid but probably won't
+ // be used so we take the easy way
+ $key = preg_replace( '/[^a-zA-z0-9_:.-]/', '', $key );
+ // drop characters which are invalid at the first position
+ $key = preg_replace( '/^[\d-.]+/', '', $key );
+
+ if ( $key == '' ) {
+ $key = '_';
+ }
+
+ // special case for an internal keyword
+ if ( $key == '_element' ) {
+ $key = 'element';
+ }
+
+ return $key;
}
/**
+ * Returns a list of languages (first is best) to use when formatting multilang fields,
+ * based on user and site preferences.
* @return array
+ * @since 1.23
*/
- function getFormattedData() {
- return FormatMetadata::getFormattedData( $this->meta );
+ protected function getPriorityLanguages() {
+ $priorityLanguages =
+ Language::getFallbacksIncludingSiteLanguage( $this->getLanguage()->getCode() );
+ $priorityLanguages = array_merge(
+ (array)$this->getLanguage()->getCode(),
+ $priorityLanguages[0],
+ $priorityLanguages[1]
+ );
+
+ return $priorityLanguages;
}
}
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index 608fb257..5992be11 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -27,7 +27,6 @@
* @ingroup Media
*/
class GIFHandler extends BitmapHandler {
-
const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
function getMetadata( $image, $filename ) {
@@ -36,6 +35,7 @@ class GIFHandler extends BitmapHandler {
} catch ( Exception $e ) {
// Broken file?
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
return self::BROKEN_FILE;
}
@@ -43,35 +43,49 @@ class GIFHandler extends BitmapHandler {
}
/**
- * @param $image File
+ * @param File $image
* @return array|bool
*/
function formatMetadata( $image ) {
+ $meta = $this->getCommonMetaArray( $image );
+ if ( count( $meta ) === 0 ) {
+ return false;
+ }
+
+ return $this->formatMetadataHelper( $meta );
+ }
+
+ /**
+ * Return the standard metadata elements for #filemetadata parser func.
+ * @param File $image
+ * @return array|bool
+ */
+ public function getCommonMetaArray( File $image ) {
$meta = $image->getMetadata();
if ( !$meta ) {
- return false;
+ return array();
}
$meta = unserialize( $meta );
- if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
- return false;
+ if ( !isset( $meta['metadata'] ) ) {
+ return array();
}
+ unset( $meta['metadata']['_MW_GIF_VERSION'] );
- if ( isset( $meta['metadata']['_MW_GIF_VERSION'] ) ) {
- unset( $meta['metadata']['_MW_GIF_VERSION'] );
- }
- return $this->formatMetadataHelper( $meta['metadata'] );
+ return $meta['metadata'];
}
/**
- * @param $image File
- * @todo unittests
+ * @todo Add unit tests
+ *
+ * @param File $image
* @return bool
*/
function getImageArea( $image ) {
$ser = $image->getMetadata();
if ( $ser ) {
$metadata = unserialize( $ser );
+
return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
} else {
return $image->getWidth() * $image->getHeight();
@@ -79,7 +93,7 @@ class GIFHandler extends BitmapHandler {
}
/**
- * @param $image File
+ * @param File $image
* @return bool
*/
function isAnimatedImage( $image ) {
@@ -90,6 +104,7 @@ class GIFHandler extends BitmapHandler {
return true;
}
}
+
return false;
}
@@ -101,6 +116,7 @@ class GIFHandler extends BitmapHandler {
function canAnimateThumbnail( $file ) {
global $wgMaxAnimatedGifArea;
$answer = $this->getImageArea( $file ) <= $wgMaxAnimatedGifArea;
+
return $answer;
}
@@ -120,19 +136,23 @@ class GIFHandler extends BitmapHandler {
if ( !$data || !is_array( $data ) ) {
wfDebug( __METHOD__ . " invalid GIF metadata\n" );
+
return self::METADATA_BAD;
}
if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
- || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION ) {
+ || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION
+ ) {
wfDebug( __METHOD__ . " old but compatible GIF metadata\n" );
+
return self::METADATA_COMPATIBLE;
}
+
return self::METADATA_GOOD;
}
/**
- * @param $image File
+ * @param File $image
* @return string
*/
function getLongDesc( $image ) {
diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php
index 887afa3f..178b0bf7 100644
--- a/includes/media/GIFMetadataExtractor.php
+++ b/includes/media/GIFMetadataExtractor.php
@@ -32,9 +32,14 @@
* @ingroup Media
*/
class GIFMetadataExtractor {
- static $gif_frame_sep;
- static $gif_extension_sep;
- static $gif_term;
+ /** @var string */
+ private static $gifFrameSep;
+
+ /** @var string */
+ private static $gifExtensionSep;
+
+ /** @var string */
+ private static $gifTerm;
const VERSION = 1;
@@ -45,13 +50,13 @@ class GIFMetadataExtractor {
/**
* @throws Exception
- * @param $filename string
+ * @param string $filename
* @return array
*/
static function getMetadata( $filename ) {
- self::$gif_frame_sep = pack( "C", ord( "," ) );
- self::$gif_extension_sep = pack( "C", ord( "!" ) );
- self::$gif_term = pack( "C", ord( ";" ) );
+ self::$gifFrameSep = pack( "C", ord( "," ) );
+ self::$gifExtensionSep = pack( "C", ord( "!" ) );
+ self::$gifTerm = pack( "C", ord( ";" ) );
$frameCount = 0;
$duration = 0.0;
@@ -93,7 +98,7 @@ class GIFMetadataExtractor {
while ( !feof( $fh ) ) {
$buf = fread( $fh, 1 );
- if ( $buf == self::$gif_frame_sep ) {
+ if ( $buf == self::$gifFrameSep ) {
// Found a frame
$frameCount++;
@@ -108,7 +113,7 @@ class GIFMetadataExtractor {
self::readGCT( $fh, $bpp );
fread( $fh, 1 );
self::skipBlock( $fh );
- } elseif ( $buf == self::$gif_extension_sep ) {
+ } elseif ( $buf == self::$gifExtensionSep ) {
$buf = fread( $fh, 1 );
if ( strlen( $buf ) < 1 ) {
throw new Exception( "Ran out of input" );
@@ -163,8 +168,8 @@ class GIFMetadataExtractor {
$commentCount = count( $comment );
if ( $commentCount === 0
- || $comment[$commentCount - 1] !== $data )
- {
+ || $comment[$commentCount - 1] !== $data
+ ) {
// Some applications repeat the same comment on each
// frame of an animated GIF image, so if this comment
// is identical to the last, only extract once.
@@ -217,15 +222,14 @@ class GIFMetadataExtractor {
$xmp = self::readBlock( $fh, true );
if ( substr( $xmp, -257, 3 ) !== "\x01\xFF\xFE"
- || substr( $xmp, -4 ) !== "\x03\x02\x01\x00" )
- {
+ || substr( $xmp, -4 ) !== "\x03\x02\x01\x00"
+ ) {
// this is just a sanity check.
throw new Exception( "XMP does not have magic trailer!" );
}
// strip out trailer.
$xmp = substr( $xmp, 0, -257 );
-
} else {
// unrecognized extension block
fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
@@ -235,7 +239,7 @@ class GIFMetadataExtractor {
} else {
self::skipBlock( $fh );
}
- } elseif ( $buf == self::$gif_term ) {
+ } elseif ( $buf == self::$gifTerm ) {
break;
} else {
if ( strlen( $buf ) < 1 ) {
@@ -257,20 +261,21 @@ class GIFMetadataExtractor {
}
/**
- * @param $fh
- * @param $bpp
+ * @param resource $fh
+ * @param int $bpp
* @return void
*/
static function readGCT( $fh, $bpp ) {
if ( $bpp > 0 ) {
- for ( $i = 1; $i <= pow( 2, $bpp ); ++$i ) {
+ $max = pow( 2, $bpp );
+ for ( $i = 1; $i <= $max; ++$i ) {
fread( $fh, 3 );
}
}
}
/**
- * @param $data
+ * @param string $data
* @throws Exception
* @return int
*/
@@ -289,7 +294,7 @@ class GIFMetadataExtractor {
}
/**
- * @param $fh
+ * @param resource $fh
* @throws Exception
*/
static function skipBlock( $fh ) {
@@ -313,8 +318,8 @@ class GIFMetadataExtractor {
* saying how long the sub-block is, followed by the sub-block.
* The entire block is terminated by a sub-block of length
* 0.
- * @param $fh FileHandle
- * @param $includeLengths Boolean Include the length bytes of the
+ * @param resource $fh File handle
+ * @param bool $includeLengths Include the length bytes of the
* sub-blocks in the returned value. Normally this is false,
* except XMP is weird and does a hack where you need to keep
* these length bytes.
@@ -341,7 +346,7 @@ class GIFMetadataExtractor {
$data .= fread( $fh, ord( $subLength ) );
$subLength = fread( $fh, 1 );
}
+
return $data;
}
-
}
diff --git a/includes/media/IPTC.php b/includes/media/IPTC.php
index 544dd211..478249fe 100644
--- a/includes/media/IPTC.php
+++ b/includes/media/IPTC.php
@@ -27,7 +27,6 @@
* @ingroup Media
*/
class IPTC {
-
/**
* This takes the results of iptcparse() and puts it into a
* form that can be handled by mediawiki. Generally called from
@@ -35,14 +34,14 @@ class IPTC {
*
* @see http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
*
- * @param string $rawData app13 block from jpeg containing iptc/iim data
- * @return Array iptc metadata array
+ * @param string $rawData The app13 block from jpeg containing iptc/iim data
+ * @return array IPTC metadata array
*/
static function parse( $rawData ) {
$parsed = iptcparse( $rawData );
- $data = Array();
+ $data = array();
if ( !is_array( $parsed ) ) {
- return $data;
+ return $data;
}
$c = '';
@@ -85,7 +84,8 @@ class IPTC {
$titles = array();
}
- for ( $i = 0; $i < count( $titles ); $i++ ) {
+ $titleCount = count( $titles );
+ for ( $i = 0; $i < $titleCount; $i++ ) {
if ( isset( $bylines[$i] ) ) {
// theoretically this should always be set
// but doesn't hurt to be careful.
@@ -225,7 +225,7 @@ class IPTC {
if ( isset( $parsed['2#060'] ) ) {
$time = $parsed['2#060'];
} else {
- $time = Array();
+ $time = array();
}
$timestamp = self::timeHelper( $val, $time, $c );
if ( $timestamp ) {
@@ -239,7 +239,7 @@ class IPTC {
if ( isset( $parsed['2#063'] ) ) {
$time = $parsed['2#063'];
} else {
- $time = Array();
+ $time = array();
}
$timestamp = self::timeHelper( $val, $time, $c );
if ( $timestamp ) {
@@ -252,7 +252,7 @@ class IPTC {
if ( isset( $parsed['2#035'] ) ) {
$time = $parsed['2#035'];
} else {
- $time = Array();
+ $time = array();
}
$timestamp = self::timeHelper( $val, $time, $c );
if ( $timestamp ) {
@@ -265,7 +265,7 @@ class IPTC {
if ( isset( $parsed['2#038'] ) ) {
$time = $parsed['2#038'];
} else {
- $time = Array();
+ $time = array();
}
$timestamp = self::timeHelper( $val, $time, $c );
if ( $timestamp ) {
@@ -300,7 +300,7 @@ class IPTC {
wfDebugLog( 'iptc', 'IPTC: '
. '2:04 too short. '
. 'Ignoring.' );
- break;
+ break;
}
$extracted = substr( $con[0], 4 );
$data['IntellectualGenre'] = $extracted;
@@ -315,9 +315,7 @@ class IPTC {
foreach ( $codes as $ic ) {
$fields = explode( ':', $ic, 3 );
- if ( count( $fields ) < 2 ||
- $fields[0] !== 'IPTC' )
- {
+ if ( count( $fields ) < 2 || $fields[0] !== 'IPTC' ) {
wfDebugLog( 'IPTC', 'IPTC: '
. 'Invalid 2:12 - ' . $ic );
break;
@@ -341,11 +339,11 @@ class IPTC {
break;
default:
- wfDebugLog( 'iptc', "Unsupported iptc tag: $tag. Value: " . implode( ',', $val ));
+ wfDebugLog( 'iptc', "Unsupported iptc tag: $tag. Value: " . implode( ',', $val ) );
break;
}
-
}
+
return $data;
}
@@ -355,8 +353,8 @@ class IPTC {
* @todo Potentially this should also capture the timezone offset.
* @param array $date The date tag
* @param array $time The time tag
- * @param $c
- * @return String Date in exif format.
+ * @param string $c The charset
+ * @return string Date in EXIF format.
*/
private static function timeHelper( $date, $time, $c ) {
if ( count( $date ) === 1 ) {
@@ -387,12 +385,14 @@ class IPTC {
// April, but the year and day is unknown. We don't process these
// types of incomplete dates atm.
wfDebugLog( 'iptc', "IPTC: invalid time ( $time ) or date ( $date )" );
+
return null;
}
- $unixTS = wfTimestamp( TS_UNIX, $date . substr( $time, 0, 6 ));
+ $unixTS = wfTimestamp( TS_UNIX, $date . substr( $time, 0, 6 ) );
if ( $unixTS === false ) {
wfDebugLog( 'iptc', "IPTC: can't convert date to TS_UNIX: $date $time." );
+
return null;
}
@@ -400,12 +400,13 @@ class IPTC {
+ ( intval( substr( $time, 9, 2 ) ) * 60 );
if ( substr( $time, 6, 1 ) === '-' ) {
- $tz = - $tz;
+ $tz = -$tz;
}
$finalTimestamp = wfTimestamp( TS_EXIF, $unixTS + $tz );
if ( $finalTimestamp === false ) {
wfDebugLog( 'iptc', "IPTC: can't make final timestamp. Date: " . ( $unixTS + $tz ) );
+
return null;
}
if ( $dateOnly ) {
@@ -434,9 +435,10 @@ class IPTC {
return $data;
}
+
/**
* Helper function of a helper function to convert charset for iptc values.
- * @param $data Mixed String or Array: The iptc string
+ * @param string|array $data The IPTC string
* @param string $charset The charset
*
* @return string
@@ -461,13 +463,14 @@ class IPTC {
return self::convIPTCHelper( $oldData, 'Windows-1252' );
}
}
+
return trim( $data );
}
/**
* take the value of 1:90 tag and returns a charset
* @param string $tag 1:90 tag.
- * @return string charset name or "?"
+ * @return string Charset name or "?"
* Warning, this function does not (and is not intended to) detect
* all iso 2022 escape codes. In practise, the code for utf-8 is the
* only code that seems to have wide use. It does detect that code.
diff --git a/includes/media/ImageHandler.php b/includes/media/ImageHandler.php
index 6794e4bf..6dd0453e 100644
--- a/includes/media/ImageHandler.php
+++ b/includes/media/ImageHandler.php
@@ -27,9 +27,8 @@
* @ingroup Media
*/
abstract class ImageHandler extends MediaHandler {
-
/**
- * @param $file File
+ * @param File $file
* @return bool
*/
function canRender( $file ) {
@@ -60,6 +59,7 @@ abstract class ImageHandler extends MediaHandler {
} else {
throw new MWException( 'No width specified to ' . __METHOD__ );
}
+
# Removed for ProofreadPage
#$width = intval( $width );
return "{$width}px";
@@ -79,8 +79,8 @@ abstract class ImageHandler extends MediaHandler {
}
/**
- * @param $image File
- * @param $params
+ * @param File $image
+ * @param array $params
* @return bool
*/
function normaliseParams( $image, &$params ) {
@@ -141,20 +141,22 @@ abstract class ImageHandler extends MediaHandler {
}
if ( !$this->validateThumbParams( $params['physicalWidth'],
- $params['physicalHeight'], $srcWidth, $srcHeight, $mimeType ) ) {
+ $params['physicalHeight'], $srcWidth, $srcHeight, $mimeType )
+ ) {
return false;
}
+
return true;
}
/**
* Validate thumbnail parameters and fill in the correct height
*
- * @param $width Integer: specified width (input/output)
- * @param $height Integer: height (output only)
- * @param $srcWidth Integer: width of the source image
- * @param $srcHeight Integer: height of the source image
- * @param $mimeType
+ * @param int $width Specified width (input/output)
+ * @param int $height Height (output only)
+ * @param int $srcWidth Width of the source image
+ * @param int $srcHeight Height of the source image
+ * @param string $mimeType Unused
* @return bool False to indicate that an error should be returned to the user.
*/
function validateThumbParams( &$width, &$height, $srcWidth, $srcHeight, $mimeType ) {
@@ -163,10 +165,12 @@ abstract class ImageHandler extends MediaHandler {
# Sanity check $width
if ( $width <= 0 ) {
wfDebug( __METHOD__ . ": Invalid destination width: $width\n" );
+
return false;
}
if ( $srcWidth <= 0 ) {
wfDebug( __METHOD__ . ": Invalid source width: $srcWidth\n" );
+
return false;
}
@@ -175,14 +179,15 @@ abstract class ImageHandler extends MediaHandler {
# Force height to be at least 1 pixel
$height = 1;
}
+
return true;
}
/**
- * @param $image File
- * @param $script
- * @param $params
- * @return bool|ThumbnailImage
+ * @param File $image
+ * @param string $script
+ * @param array $params
+ * @return bool|MediaTransformOutput
*/
function getScriptedTransform( $image, $script, $params ) {
if ( !$this->normaliseParams( $image, $params ) ) {
@@ -199,8 +204,10 @@ abstract class ImageHandler extends MediaHandler {
wfSuppressWarnings();
$gis = getimagesize( $path );
wfRestoreWarnings();
+
return $gis;
}
+
/**
* Function that returns the number of pixels to be thumbnailed.
* Intended for animated GIFs to multiply by the number of frames.
@@ -214,21 +221,21 @@ abstract class ImageHandler extends MediaHandler {
return $image->getWidth() * $image->getHeight();
}
-
/**
- * @param $file File
+ * @param File $file
* @return string
*/
function getShortDesc( $file ) {
global $wgLang;
$nbytes = htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
- $widthheight = wfMessage( 'widthheight' )->numParams( $file->getWidth(), $file->getHeight() )->escaped();
+ $widthheight = wfMessage( 'widthheight' )
+ ->numParams( $file->getWidth(), $file->getHeight() )->escaped();
return "$widthheight ($nbytes)";
}
/**
- * @param $file File
+ * @param File $file
* @return string
*/
function getLongDesc( $file ) {
@@ -238,25 +245,44 @@ abstract class ImageHandler extends MediaHandler {
if ( $pages === false || $pages <= 1 ) {
$msg = wfMessage( 'file-info-size' )->numParams( $file->getWidth(),
$file->getHeight() )->params( $size,
- $file->getMimeType() )->parse();
+ $file->getMimeType() )->parse();
} else {
$msg = wfMessage( 'file-info-size-pages' )->numParams( $file->getWidth(),
$file->getHeight() )->params( $size,
- $file->getMimeType() )->numParams( $pages )->parse();
+ $file->getMimeType() )->numParams( $pages )->parse();
}
+
return $msg;
}
/**
- * @param $file File
+ * @param File $file
* @return string
*/
function getDimensionsString( $file ) {
$pages = $file->pageCount();
if ( $pages > 1 ) {
- return wfMessage( 'widthheightpage' )->numParams( $file->getWidth(), $file->getHeight(), $pages )->text();
+ return wfMessage( 'widthheightpage' )
+ ->numParams( $file->getWidth(), $file->getHeight(), $pages )->text();
} else {
- return wfMessage( 'widthheight' )->numParams( $file->getWidth(), $file->getHeight() )->text();
+ return wfMessage( 'widthheight' )
+ ->numParams( $file->getWidth(), $file->getHeight() )->text();
}
}
+
+ public function sanitizeParamsForBucketing( $params ) {
+ $params = parent::sanitizeParamsForBucketing( $params );
+
+ // We unset the height parameters in order to let normaliseParams recalculate them
+ // Otherwise there might be a height discrepancy
+ if ( isset( $params['height'] ) ) {
+ unset( $params['height'] );
+ }
+
+ if ( isset( $params['physicalHeight'] ) ) {
+ unset( $params['physicalHeight'] );
+ }
+
+ return $params;
+ }
}
diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php
index fa763668..fbdbdfe3 100644
--- a/includes/media/Jpeg.php
+++ b/includes/media/Jpeg.php
@@ -32,6 +32,70 @@
*/
class JpegHandler extends ExifBitmapHandler {
+ function normaliseParams( $image, &$params ) {
+ if ( !parent::normaliseParams( $image, $params ) ) {
+ return false;
+ }
+ if ( isset( $params['quality'] ) && !self::validateQuality( $params['quality'] ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ function validateParam( $name, $value ) {
+ if ( $name === 'quality' ) {
+ return self::validateQuality( $value );
+ } else {
+ return parent::validateParam( $name, $value );
+ }
+ }
+
+ /** Validate and normalize quality value to be between 1 and 100 (inclusive).
+ * @param int $value Quality value, will be converted to integer or 0 if invalid
+ * @return bool True if the value is valid
+ */
+ private static function validateQuality( $value ) {
+ return $value === 'low';
+ }
+
+ function makeParamString( $params ) {
+ // Prepend quality as "qValue-". This has to match parseParamString() below
+ $res = parent::makeParamString( $params );
+ if ( $res && isset( $params['quality'] ) ) {
+ $res = "q{$params['quality']}-$res";
+ }
+ return $res;
+ }
+
+ function parseParamString( $str ) {
+ // $str contains "qlow-200px" or "200px" strings because thumb.php would strip the filename
+ // first - check if the string begins with "qlow-", and if so, treat it as quality.
+ // Pass the first portion, or the whole string if "qlow-" not found, to the parent
+ // The parsing must match the makeParamString() above
+ $res = false;
+ $m = false;
+ if ( preg_match( '/q([^-]+)-(.*)$/', $str, $m ) ) {
+ $v = $m[1];
+ if ( self::validateQuality( $v ) ) {
+ $res = parent::parseParamString( $m[2] );
+ if ( $res ) {
+ $res['quality'] = $v;
+ }
+ }
+ } else {
+ $res = parent::parseParamString( $str );
+ }
+ return $res;
+ }
+
+ function getScriptParams( $params ) {
+ $res = parent::getScriptParams( $params );
+ if ( isset( $params['quality'] ) ) {
+ $res['quality'] = $params['quality'];
+ }
+ return $res;
+ }
+
function getMetadata( $image, $filename ) {
try {
$meta = BitmapMetadataHandler::Jpeg( $filename );
@@ -40,10 +104,11 @@ class JpegHandler extends ExifBitmapHandler {
throw new MWException( 'Metadata array is not an array' );
}
$meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
return serialize( $meta );
- }
- catch ( MWException $e ) {
- // BitmapMetadataHandler throws an exception in certain exceptional cases like if file does not exist.
+ } catch ( MWException $e ) {
+ // BitmapMetadataHandler throws an exception in certain exceptional
+ // cases like if file does not exist.
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
/* This used to use 0 (ExifBitmapHandler::OLD_BROKEN_FILE) for the cases
@@ -55,14 +120,15 @@ class JpegHandler extends ExifBitmapHandler {
* Thus switch to using -1 to denote only a broken file, and use an array with only
* MEDIAWIKI_EXIF_VERSION to denote no props.
*/
+
return ExifBitmapHandler::BROKEN_FILE;
}
}
/**
- * @param $file File
+ * @param File $file
* @param array $params Rotate parameters.
- * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
+ * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
* @since 1.21
* @return bool
*/
@@ -79,16 +145,32 @@ class JpegHandler extends ExifBitmapHandler {
wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
wfProfileIn( 'jpegtran' );
$retval = 0;
- $err = wfShellExecWithStderr( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval );
wfProfileOut( 'jpegtran' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
+
return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
}
+
return false;
} else {
return parent::rotate( $file, $params );
}
}
+ public function supportsBucketing() {
+ return true;
+ }
+
+ public function sanitizeParamsForBucketing( $params ) {
+ $params = parent::sanitizeParamsForBucketing( $params );
+
+ // Quality needs to be cleared for bucketing. Buckets need to be default quality
+ if ( isset( $params['quality'] ) ) {
+ unset( $params['quality'] );
+ }
+
+ return $params;
+ }
}
diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php
index c7030eba..8c5b46bb 100644
--- a/includes/media/JpegMetadataExtractor.php
+++ b/includes/media/JpegMetadataExtractor.php
@@ -30,8 +30,8 @@
* @ingroup Media
*/
class JpegMetadataExtractor {
-
const MAX_JPEG_SEGMENTS = 200;
+
// the max segment is a sanity check.
// A jpeg file should never even remotely have
// that many segments. Your average file has about 10.
@@ -43,9 +43,9 @@ class JpegMetadataExtractor {
* but gis doesn't support having multiple app1 segments
* and those can't extract xmp on files containing both exif and xmp data
*
- * @param string $filename name of jpeg file
- * @return Array of interesting segments.
- * @throws MWException if given invalid file.
+ * @param string $filename Name of jpeg file
+ * @return array Array of interesting segments.
+ * @throws MWException If given invalid file.
*/
static function segmentSplitter( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
@@ -83,7 +83,8 @@ class JpegMetadataExtractor {
throw new MWException( 'Too many jpeg segments. Aborting' );
}
if ( $buffer !== "\xFF" ) {
- throw new MWException( "Error reading jpeg file marker. Expected 0xFF but got " . bin2hex( $buffer ) );
+ throw new MWException( "Error reading jpeg file marker. " .
+ "Expected 0xFF but got " . bin2hex( $buffer ) );
}
$buffer = fread( $fh, 1 );
@@ -113,7 +114,6 @@ class JpegMetadataExtractor {
} else {
wfDebug( __METHOD__ . " Ignoring JPEG comment as is garbage.\n" );
}
-
} elseif ( $buffer === "\xE1" ) {
// APP1 section (Exif, XMP, and XMP extended)
// only extract if XMP is enabled.
@@ -160,7 +160,6 @@ class JpegMetadataExtractor {
}
fseek( $fh, $size['int'] - 2, SEEK_CUR );
}
-
}
// shouldn't get here.
throw new MWException( "Reached end of jpeg file unexpectedly" );
@@ -168,9 +167,9 @@ class JpegMetadataExtractor {
/**
* Helper function for jpegSegmentSplitter
- * @param &$fh FileHandle for jpeg file
+ * @param resource &$fh File handle for JPEG file
* @throws MWException
- * @return string data content of segment.
+ * @return string Data content of segment.
*/
private static function jpegExtractMarker( &$fh ) {
$size = wfUnpack( "nint", fread( $fh, 2 ), 2 );
@@ -181,6 +180,7 @@ class JpegMetadataExtractor {
if ( strlen( $segment ) !== $size['int'] - 2 ) {
throw new MWException( "Segment shorter than expected" );
}
+
return $segment;
}
@@ -193,9 +193,10 @@ class JpegMetadataExtractor {
*
* This should generally be called by BitmapMetadataHandler::doApp13()
*
- * @param string $app13 photoshop psir app13 block from jpg.
+ * @param string $app13 Photoshop psir app13 block from jpg.
* @throws MWException (It gets caught next level up though)
- * @return String if the iptc hash is good or not.
+ * @return string If the iptc hash is good or not. One of 'iptc-no-hash',
+ * 'iptc-good-hash', 'iptc-bad-hash'.
*/
public static function doPSIR( $app13 ) {
if ( !$app13 ) {
@@ -275,7 +276,6 @@ class JpegMetadataExtractor {
$lenData['len']++;
}
$offset += $lenData['len'];
-
}
if ( !$realHash || !$recordedHash ) {
diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php
index 779e23c9..64ca0115 100644
--- a/includes/media/MediaHandler.php
+++ b/includes/media/MediaHandler.php
@@ -32,34 +32,46 @@ abstract class MediaHandler {
const METADATA_BAD = false;
const METADATA_COMPATIBLE = 2; // for old but backwards compatible.
/**
- * Instance cache
+ * Max length of error logged by logErrorForExternalProcess()
*/
- static $handlers = array();
+ const MAX_ERR_LOG_SIZE = 65535;
+
+ /** @var MediaHandler[] Instance cache with array of MediaHandler */
+ protected static $handlers = array();
/**
* Get a MediaHandler for a given MIME type from the instance cache
*
- * @param $type string
- *
+ * @param string $type
* @return MediaHandler
*/
static function getHandler( $type ) {
global $wgMediaHandlers;
if ( !isset( $wgMediaHandlers[$type] ) ) {
wfDebug( __METHOD__ . ": no handler found for $type.\n" );
+
return false;
}
$class = $wgMediaHandlers[$type];
if ( !isset( self::$handlers[$class] ) ) {
self::$handlers[$class] = new $class;
if ( !self::$handlers[$class]->isEnabled() ) {
+ wfDebug( __METHOD__ . ": $class is not enabled\n" );
self::$handlers[$class] = false;
}
}
+
return self::$handlers[$class];
}
/**
+ * Resets all static caches
+ */
+ public static function resetCache() {
+ self::$handlers = array();
+ }
+
+ /**
* Get an associative array mapping magic word IDs to parameter names.
* Will be used by the parser to identify parameters.
*/
@@ -70,24 +82,24 @@ abstract class MediaHandler {
* Return true to accept the parameter, and false to reject it.
* If you return false, the parser will do something quiet and forgiving.
*
- * @param $name
- * @param $value
+ * @param string $name
+ * @param mixed $value
*/
abstract function validateParam( $name, $value );
/**
* Merge a parameter array into a string appropriate for inclusion in filenames
*
- * @param $params array Array of parameters that have been through normaliseParams.
- * @return String
+ * @param array $params Array of parameters that have been through normaliseParams.
+ * @return string
*/
abstract function makeParamString( $params );
/**
* Parse a param string made with makeParamString back into an array
*
- * @param $str string The parameter string without file name (e.g. 122px)
- * @return Array|Boolean Array of parameters or false on failure.
+ * @param string $str The parameter string without file name (e.g. 122px)
+ * @return array|bool Array of parameters or false on failure.
*/
abstract function parseParamString( $str );
@@ -95,8 +107,8 @@ abstract class MediaHandler {
* Changes the parameter array as necessary, ready for transformation.
* Should be idempotent.
* Returns false if the parameters are unacceptable and the transform should fail
- * @param $image
- * @param $params
+ * @param File $image
+ * @param array $params
*/
abstract function normaliseParams( $image, &$params );
@@ -104,19 +116,30 @@ abstract class MediaHandler {
* Get an image size array like that returned by getimagesize(), or false if it
* can't be determined.
*
- * @param $image File: the image object, or false if there isn't one
- * @param string $path the filename
- * @return Array Follow the format of PHP getimagesize() internal function. See http://www.php.net/getimagesize
+ * This function is used for determining the width, height and bitdepth directly
+ * from an image. The results are stored in the database in the img_width,
+ * img_height, img_bits fields.
+ *
+ * @note If this is a multipage file, return the width and height of the
+ * first page.
+ *
+ * @param File $image The image object, or false if there isn't one
+ * @param string $path The filename
+ * @return array Follow the format of PHP getimagesize() internal function.
+ * See http://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".
*/
abstract function getImageSize( $image, $path );
/**
* Get handler-specific metadata which will be saved in the img_metadata field.
*
- * @param $image File: the image object, or false if there isn't one.
+ * @param File $image The image object, or false if there isn't one.
* Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
- * @param string $path the filename
- * @return String
+ * @param string $path The filename
+ * @return string A string of metadata in php serialized form (Run through serialize())
*/
function getMetadata( $image, $path ) {
return '';
@@ -127,7 +150,7 @@ abstract class MediaHandler {
*
* This is not used for validating metadata, this is used for the api when returning
* metadata, since api content formats should stay the same over time, and so things
- * using ForiegnApiRepo can keep backwards compatibility
+ * using ForeignApiRepo can keep backwards compatibility
*
* All core media handlers share a common version number, and extensions can
* use the GetMetadataVersion hook to append to the array (they should append a unique
@@ -135,11 +158,12 @@ abstract class MediaHandler {
* version 3 it might add to the end of the array the element 'foo=3'. if the core metadata
* version is 2, the end version string would look like '2;foo=3'.
*
- * @return string version string
+ * @return string Version string
*/
static function getMetadataVersion() {
- $version = Array( '2' ); // core metadata version
- wfRunHooks( 'GetMetadataVersion', Array( &$version ) );
+ $version = array( '2' ); // core metadata version
+ wfRunHooks( 'GetMetadataVersion', array( &$version ) );
+
return implode( ';', $version );
}
@@ -149,9 +173,9 @@ abstract class MediaHandler {
* By default just returns $metadata, but can be used to allow
* media handlers to convert between metadata versions.
*
- * @param $metadata Mixed String or Array metadata array (serialized if string)
- * @param $version Integer target version
- * @return Array serialized metadata in specified version, or $metadata on fail.
+ * @param string|array $metadata Metadata array (serialized if string)
+ * @param int $version Target version
+ * @return array Serialized metadata in specified version, or $metadata on fail.
*/
function convertMetadataVersion( $metadata, $version = 1 ) {
if ( !is_array( $metadata ) ) {
@@ -160,14 +184,18 @@ abstract class MediaHandler {
wfSuppressWarnings();
$ret = unserialize( $metadata );
wfRestoreWarnings();
+
return $ret;
}
+
return $metadata;
}
/**
* Get a string describing the type of metadata, for display purposes.
*
+ * @note This method is currently unused.
+ * @param File $image
* @return string
*/
function getMetadataType( $image ) {
@@ -179,8 +207,15 @@ abstract class MediaHandler {
* If it returns MediaHandler::METADATA_BAD (or false), Image
* will reload the metadata from the file and update the database.
* MediaHandler::METADATA_GOOD for if the metadata is a-ok,
- * MediaHanlder::METADATA_COMPATIBLE if metadata is old but backwards
+ * MediaHandler::METADATA_COMPATIBLE if metadata is old but backwards
* compatible (which may or may not trigger a metadata reload).
+ *
+ * @note Returning self::METADATA_BAD will trigger a metadata reload from
+ * file on page view. Always returning this from a broken file, or suddenly
+ * triggering as bad metadata for a large number of files can cause
+ * performance problems.
+ * @param File $image
+ * @param string $metadata The metadata in serialized form
* @return bool
*/
function isMetadataValid( $image, $metadata ) {
@@ -188,13 +223,52 @@ abstract class MediaHandler {
}
/**
+ * Get an array of standard (FormatMetadata type) metadata values.
+ *
+ * The returned data is largely the same as that from getMetadata(),
+ * but formatted in a standard, stable, handler-independent way.
+ * The idea being that some values like ImageDescription or Artist
+ * are universal and should be retrievable in a handler generic way.
+ *
+ * The specific properties are the type of properties that can be
+ * handled by the FormatMetadata class. These values are exposed to the
+ * user via the filemetadata parser function.
+ *
+ * Details of the response format of this function can be found at
+ * https://www.mediawiki.org/wiki/Manual:File_metadata_handling
+ * tl/dr: the response is an associative array of
+ * properties keyed by name, but the value can be complex. You probably
+ * want to call one of the FormatMetadata::flatten* functions on the
+ * property values before using them, or call
+ * FormatMetadata::getFormattedData() on the full response array, which
+ * transforms all values into prettified, human-readable text.
+ *
+ * Subclasses overriding this function must return a value which is a
+ * valid API response fragment (all associative array keys are valid
+ * XML tagnames).
+ *
+ * Note, if the file simply has no metadata, but the handler supports
+ * this interface, it should return an empty array, not false.
+ *
+ * @param File $file
+ * @return array|bool False if interface not supported
+ * @since 1.23
+ */
+ public function getCommonMetaArray( File $file ) {
+ return false;
+ }
+
+ /**
* Get a MediaTransformOutput object representing an alternate of the transformed
* output which will call an intermediary thumbnail assist script.
*
* Used when the repository has a thumbnailScriptUrl option configured.
*
* Return false to fall back to the regular getTransform().
- * @return bool
+ * @param File $image
+ * @param string $script
+ * @param array $params
+ * @return bool|ThumbnailImage
*/
function getScriptedTransform( $image, $script, $params ) {
return false;
@@ -204,8 +278,8 @@ abstract class MediaHandler {
* Get a MediaTransformOutput object representing the transformed output. Does not
* actually do the transform.
*
- * @param $image File: the image object
- * @param string $dstPath filesystem destination path
+ * @param File $image The image object
+ * @param string $dstPath Filesystem destination path
* @param string $dstUrl Destination URL to use in output HTML
* @param array $params Arbitrary set of parameters validated by $this->validateParam()
* @return MediaTransformOutput
@@ -218,13 +292,12 @@ abstract class MediaHandler {
* Get a MediaTransformOutput object representing the transformed output. Does the
* transform unless $flags contains self::TRANSFORM_LATER.
*
- * @param $image File: the image object
- * @param string $dstPath filesystem destination path
- * @param string $dstUrl destination URL to use in output HTML
- * @param array $params arbitrary set of parameters validated by $this->validateParam()
+ * @param File $image The image object
+ * @param string $dstPath Filesystem destination path
+ * @param string $dstUrl Destination URL to use in output HTML
+ * @param array $params Arbitrary set of parameters validated by $this->validateParam()
* Note: These parameters have *not* gone through $this->normaliseParams()
- * @param $flags Integer: a bitfield, may contain self::TRANSFORM_LATER
- *
+ * @param int $flags A bitfield, may contain self::TRANSFORM_LATER
* @return MediaTransformOutput
*/
abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
@@ -232,31 +305,32 @@ abstract class MediaHandler {
/**
* Get the thumbnail extension and MIME type for a given source MIME type
*
- * @param String $ext Extension of original file
- * @param String $mime Mime type of original file
- * @param Array $params Handler specific rendering parameters
- * @return array thumbnail extension and MIME type
+ * @param string $ext Extension of original file
+ * @param string $mime MIME type of original file
+ * @param array $params Handler specific rendering parameters
+ * @return array Thumbnail extension and MIME type
*/
function getThumbType( $ext, $mime, $params = null ) {
$magic = MimeMagic::singleton();
if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) {
- // The extension is not valid for this mime type and we do
- // recognize the mime type
+ // The extension is not valid for this MIME type and we do
+ // recognize the MIME type
$extensions = $magic->getExtensionsForType( $mime );
if ( $extensions ) {
return array( strtok( $extensions, ' ' ), $mime );
}
}
- // The extension is correct (true) or the mime type is unknown to
+ // The extension is correct (true) or the MIME type is unknown to
// MediaWiki (null)
return array( $ext, $mime );
}
/**
* Get useful response headers for GET/HEAD requests for a file with the given metadata
- * @param $metadata mixed Result of the getMetadata() function of this handler for a file
- * @return Array
+ *
+ * @param mixed $metadata Result of the getMetadata() function of this handler for a file
+ * @return array
*/
public function getStreamHeaders( $metadata ) {
return array();
@@ -264,6 +338,8 @@ abstract class MediaHandler {
/**
* True if the handled types can be transformed
+ *
+ * @param File $file
* @return bool
*/
function canRender( $file ) {
@@ -273,6 +349,8 @@ abstract class MediaHandler {
/**
* True if handled types cannot be displayed directly in a browser
* but can be rendered
+ *
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -281,6 +359,8 @@ abstract class MediaHandler {
/**
* True if the type has multi-page capabilities
+ *
+ * @param File $file
* @return bool
*/
function isMultiPage( $file ) {
@@ -289,6 +369,8 @@ abstract class MediaHandler {
/**
* Page count for a multi-page document, false if unsupported or unknown
+ *
+ * @param File $file
* @return bool
*/
function pageCount( $file ) {
@@ -297,6 +379,8 @@ abstract class MediaHandler {
/**
* The material is vectorized and thus scaling is lossless
+ *
+ * @param File $file
* @return bool
*/
function isVectorized( $file ) {
@@ -307,6 +391,8 @@ abstract class MediaHandler {
* The material is an image, and is animated.
* In particular, video material need not return true.
* @note Before 1.20, this was a method of ImageHandler only
+ *
+ * @param File $file
* @return bool
*/
function isAnimatedImage( $file ) {
@@ -316,6 +402,8 @@ abstract class MediaHandler {
/**
* If the material is animated, we can animate the thumbnail
* @since 1.20
+ *
+ * @param File $file
* @return bool If material is not animated, handler may return any value.
*/
function canAnimateThumbnail( $file ) {
@@ -342,8 +430,8 @@ abstract class MediaHandler {
*
* @note For non-paged media, use getImageSize.
*
- * @param $image File
- * @param $page What page to get dimensions of
+ * @param File $image
+ * @param int $page What page to get dimensions of
* @return array|bool
*/
function getPageDimensions( $image, $page ) {
@@ -361,13 +449,40 @@ abstract class MediaHandler {
/**
* Generic getter for text layer.
* Currently overloaded by PDF and DjVu handlers
- * @return bool
+ * @param File $image
+ * @param int $page Page number to get information for
+ * @return bool|string Page text or false when no text found or if
+ * unsupported.
*/
function getPageText( $image, $page ) {
return false;
}
/**
+ * Get the text of the entire document.
+ * @param File $file
+ * @return bool|string The text of the document or false if unsupported.
+ */
+ public function getEntireText( File $file ) {
+ $numPages = $file->pageCount();
+ if ( !$numPages ) {
+ // Not a multipage document
+ return $this->getPageText( $file, 1 );
+ }
+ $document = '';
+ for ( $i = 1; $i <= $numPages; $i++ ) {
+ $curPage = $this->getPageText( $file, $i );
+ if ( is_string( $curPage ) ) {
+ $document .= $curPage . "\n";
+ }
+ }
+ if ( $document !== '' ) {
+ return $document;
+ }
+ return false;
+ }
+
+ /**
* Get an array structure that looks like this:
*
* array(
@@ -387,12 +502,12 @@ abstract class MediaHandler {
*/
/**
- * @todo FIXME: I don't really like this interface, it's not very flexible
- * I think the media handler should generate HTML instead. It can do
- * all the formatting according to some standard. That makes it possible
- * to do things like visual indication of grouped and chained streams
- * in ogg container files.
- * @return bool
+ * @todo FIXME: This interface is not very flexible. The media handler
+ * should generate HTML instead. It can do all the formatting according
+ * to some standard. That makes it possible to do things like visual
+ * indication of grouped and chained streams in ogg container files.
+ * @param File $image
+ * @return array|bool
*/
function formatMetadata( $image ) {
return false;
@@ -404,8 +519,8 @@ abstract class MediaHandler {
*
* This is used by the media handlers that use the FormatMetadata class
*
- * @param array $metadataArray metadata array
- * @return array for use displaying metadata.
+ * @param array $metadataArray Metadata array
+ * @return array Array for use displaying metadata.
*/
function formatMetadataHelper( $metadataArray ) {
$result = array(
@@ -425,6 +540,7 @@ abstract class MediaHandler {
$value
);
}
+
return $result;
}
@@ -432,20 +548,10 @@ abstract class MediaHandler {
* Get a list of metadata items which should be displayed when
* the metadata table is collapsed.
*
- * @return array of strings
- * @access protected
+ * @return array Array of strings
*/
- function visibleMetadataFields() {
- $fields = array();
- $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
- foreach ( $lines as $line ) {
- $matches = array();
- if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
- $fields[] = $matches[1];
- }
- }
- $fields = array_map( 'strtolower', $fields );
- return $fields;
+ protected function visibleMetadataFields() {
+ return FormatMetadata::getVisibleFields();
}
/**
@@ -453,21 +559,21 @@ abstract class MediaHandler {
* That array is then used to generate the table of metadata values
* on the image page
*
- * @param &$array Array An array containing elements for each type of visibility
- * and each of those elements being an array of metadata items. This function adds
- * a value to that array.
+ * @param array &$array An array containing elements for each type of visibility
+ * and each of those elements being an array of metadata items. This function adds
+ * a value to that array.
* @param string $visibility ('visible' or 'collapsed') if this value is hidden
- * by default.
- * @param string $type type of metadata tag (currently always 'exif')
- * @param string $id the name of the metadata tag (like 'artist' for example).
- * its name in the table displayed is the message "$type-$id" (Ex exif-artist ).
- * @param string $value thingy goes into a wikitext table; it used to be escaped but
- * that was incompatible with previous practise of customized display
- * with wikitext formatting via messages such as 'exif-model-value'.
- * So the escaping is taken back out, but generally this seems a confusing
- * interface.
- * @param string $param value to pass to the message for the name of the field
- * as $1. Currently this parameter doesn't seem to ever be used.
+ * by default.
+ * @param string $type Type of metadata tag (currently always 'exif')
+ * @param string $id The name of the metadata tag (like 'artist' for example).
+ * its name in the table displayed is the message "$type-$id" (Ex exif-artist ).
+ * @param string $value Thingy goes into a wikitext table; it used to be escaped but
+ * that was incompatible with previous practise of customized display
+ * with wikitext formatting via messages such as 'exif-model-value'.
+ * So the escaping is taken back out, but generally this seems a confusing
+ * interface.
+ * @param bool|string $param Value to pass to the message for the name of the field
+ * as $1. Currently this parameter doesn't seem to ever be used.
*
* Note, everything here is passed through the parser later on (!)
*/
@@ -492,58 +598,55 @@ abstract class MediaHandler {
}
/**
- * Used instead of getLongDesc if there is no handler registered for file.
+ * Short description. Shown on Special:Search results.
*
- * @param $file File
+ * @param File $file
* @return string
*/
function getShortDesc( $file ) {
- global $wgLang;
- return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
+ return self::getGeneralShortDesc( $file );
}
/**
- * Short description. Shown on Special:Search results.
+ * Long description. Shown under image on image description page surounded by ().
*
- * @param $file File
+ * @param File $file
* @return string
*/
function getLongDesc( $file ) {
- global $wgLang;
- return wfMessage( 'file-info', htmlspecialchars( $wgLang->formatSize( $file->getSize() ) ),
- $file->getMimeType() )->parse();
+ return self::getGeneralLongDesc( $file );
}
/**
- * Long description. Shown under image on image description page surounded by ().
+ * Used instead of getShortDesc if there is no handler registered for file.
*
- * @param $file File
+ * @param File $file
* @return string
*/
static function getGeneralShortDesc( $file ) {
global $wgLang;
- return $wgLang->formatSize( $file->getSize() );
+
+ return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
}
/**
- * Used instead of getShortDesc if there is no handler registered for file.
+ * Used instead of getLongDesc if there is no handler registered for file.
*
- * @param $file File
+ * @param File $file
* @return string
*/
static function getGeneralLongDesc( $file ) {
- global $wgLang;
- return wfMessage( 'file-info', $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType() )->parse();
+ return wfMessage( 'file-info' )->sizeParams( $file->getSize() )
+ ->params( $file->getMimeType() )->parse();
}
/**
* Calculate the largest thumbnail width for a given original file size
* such that the thumbnail's height is at most $maxHeight.
- * @param $boxWidth Integer Width of the thumbnail box.
- * @param $boxHeight Integer Height of the thumbnail box.
- * @param $maxHeight Integer Maximum height expected for the thumbnail.
- * @return Integer.
+ * @param int $boxWidth Width of the thumbnail box.
+ * @param int $boxHeight Height of the thumbnail box.
+ * @param int $maxHeight Maximum height expected for the thumbnail.
+ * @return int
*/
public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
$idealWidth = $boxWidth * $maxHeight / $boxHeight;
@@ -559,7 +662,7 @@ abstract class MediaHandler {
* Shown in file history box on image description page.
*
* @param File $file
- * @return String Dimensions
+ * @return string Dimensions
*/
function getDimensionsString( $file ) {
return '';
@@ -575,7 +678,8 @@ abstract class MediaHandler {
* @param Parser $parser
* @param File $file
*/
- function parserTransformHook( $parser, $file ) {}
+ function parserTransformHook( $parser, $file ) {
+ }
/**
* File validation hook called on upload.
@@ -585,7 +689,7 @@ abstract class MediaHandler {
* relevant errors.
*
* @param string $fileName The local path to the file.
- * @return Status object
+ * @return Status
*/
function verifyUpload( $fileName ) {
return Status::newGood();
@@ -614,9 +718,11 @@ abstract class MediaHandler {
sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
$thumbstat['size'], $dstPath ) );
}
+
return true;
}
}
+
return false;
}
@@ -637,12 +743,12 @@ abstract class MediaHandler {
// Do nothing
}
- /*
+ /**
* True if the handler can rotate the media
- * @since 1.21
+ * @since 1.24 non-static. From 1.21-1.23 was static
* @return bool
*/
- public static function canRotate() {
+ public function canRotate() {
return false;
}
@@ -657,11 +763,100 @@ abstract class MediaHandler {
*
* For files we don't know, we return 0.
*
- * @param $file File
+ * @param File $file
* @return int 0, 90, 180 or 270
*/
public function getRotation( $file ) {
return 0;
}
+ /**
+ * Log an error that occurred in an external process
+ *
+ * Moved from BitmapHandler to MediaHandler with MediaWiki 1.23
+ *
+ * @since 1.23
+ * @param int $retval
+ * @param string $err Error reported by command. Anything longer than
+ * MediaHandler::MAX_ERR_LOG_SIZE is stripped off.
+ * @param string $cmd
+ */
+ protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
+ # Keep error output limited (bug 57985)
+ $errMessage = trim( substr( $err, 0, self::MAX_ERR_LOG_SIZE ) );
+
+ wfDebugLog( 'thumbnail',
+ sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
+ wfHostname(), $retval, $errMessage, $cmd ) );
+ }
+
+ /**
+ * Get list of languages file can be viewed in.
+ *
+ * @param File $file
+ * @return string[] Array of language codes, or empty array if unsupported.
+ * @since 1.23
+ */
+ public function getAvailableLanguages( File $file ) {
+ return array();
+ }
+
+ /**
+ * On file types that support renderings in multiple languages,
+ * which language is used by default if unspecified.
+ *
+ * If getAvailableLanguages returns a non-empty array, this must return
+ * a valid language code. Otherwise can return null if files of this
+ * type do not support alternative language renderings.
+ *
+ * @param File $file
+ * @return string|null Language code or null if multi-language not supported for filetype.
+ * @since 1.23
+ */
+ public function getDefaultRenderLanguage( File $file ) {
+ return null;
+ }
+
+ /**
+ * If its an audio file, return the length of the file. Otherwise 0.
+ *
+ * File::getLength() existed for a long time, but was calling a method
+ * that only existed in some subclasses of this class (The TMH ones).
+ *
+ * @param File $file
+ * @return float Length in seconds
+ * @since 1.23
+ */
+ public function getLength( $file ) {
+ return 0.0;
+ }
+
+ /**
+ * True if creating thumbnails from the file is large or otherwise resource-intensive.
+ * @param File $file
+ * @return bool
+ */
+ public function isExpensiveToThumbnail( $file ) {
+ return false;
+ }
+
+ /**
+ * Returns whether or not this handler supports the chained generation of thumbnails according
+ * to buckets
+ * @return bool
+ * @since 1.24
+ */
+ public function supportsBucketing() {
+ return false;
+ }
+
+ /**
+ * Returns a normalised params array for which parameters have been cleaned up for bucketing
+ * purposes
+ * @param array $params
+ * @return array
+ */
+ public function sanitizeParamsForBucketing( $params ) {
+ return $params;
+ }
}
diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index c49d3f20..bc9e9173 100644
--- a/includes/media/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -27,46 +27,67 @@
* @ingroup Media
*/
abstract class MediaTransformOutput {
- /**
- * @var File
+ /** @var array Associative array mapping optional supplementary image files
+ * from pixel density (eg 1.5 or 2) to additional URLs.
*/
- var $file;
+ public $responsiveUrls = array();
- var $width, $height, $url, $page, $path, $lang;
+ /** @var File */
+ protected $file;
- /**
- * @var array Associative array mapping optional supplementary image files
- * from pixel density (eg 1.5 or 2) to additional URLs.
- */
- public $responsiveUrls = array();
+ /** @var int Image width */
+ protected $width;
+
+ /** @var int Image height */
+ protected $height;
+
+ /** @var string URL path to the thumb */
+ protected $url;
+ /** @var bool|string */
+ protected $page;
+
+ /** @var bool|string Filesystem path to the thumb */
+ protected $path;
+
+ /** @var bool|string Language code, false if not set */
+ protected $lang;
+
+ /** @var bool|string Permanent storage path */
protected $storagePath = false;
/**
- * @return integer Width of the output box
+ * @return int Width of the output box
*/
public function getWidth() {
return $this->width;
}
/**
- * @return integer Height of the output box
+ * @return int Height of the output box
*/
public function getHeight() {
return $this->height;
}
/**
+ * @return File
+ */
+ public function getFile() {
+ return $this->file;
+ }
+
+ /**
* Get the final extension of the thumbnail.
* Returns false for scripted transformations.
- * @return string|false
+ * @return string|bool
*/
public function getExtension() {
return $this->path ? FileBackend::extensionFromPath( $this->path ) : false;
}
/**
- * @return string|false The thumbnail URL
+ * @return string|bool The thumbnail URL
*/
public function getUrl() {
return $this->url;
@@ -85,6 +106,9 @@ abstract class MediaTransformOutput {
*/
public function setStoragePath( $storagePath ) {
$this->storagePath = $storagePath;
+ if ( $this->path === false ) {
+ $this->path = $storagePath;
+ }
}
/**
@@ -119,11 +143,14 @@ abstract class MediaTransformOutput {
/**
* Check if an output thumbnail file actually exists.
+ *
* This will return false if there was an error, the
* thumbnail is to be handled client-side only, or if
* transformation was deferred via TRANSFORM_LATER.
+ * This file may exist as a new file in /tmp, a file
+ * in permanent storage, or even refer to the original.
*
- * @return Bool
+ * @return bool
*/
public function hasFile() {
// If TRANSFORM_LATER, $this->path will be false.
@@ -135,7 +162,7 @@ abstract class MediaTransformOutput {
* Check if the output thumbnail is the same as the source.
* This can occur if the requested width was bigger than the source.
*
- * @return Bool
+ * @return bool
*/
public function fileIsSource() {
return ( !$this->isError() && $this->path === null );
@@ -156,6 +183,7 @@ abstract class MediaTransformOutput {
$be = $this->file->getRepo()->getBackend();
// The temp file will be process cached by FileBackend
$fsFile = $be->getLocalReference( array( 'src' => $this->path ) );
+
return $fsFile ? $fsFile->getPath() : false;
} else {
return $this->path; // may return false
@@ -166,13 +194,14 @@ abstract class MediaTransformOutput {
* Stream the file if there were no errors
*
* @param array $headers Additional HTTP headers to send on success
- * @return Bool success
+ * @return bool Success
*/
public function streamFile( $headers = array() ) {
if ( !$this->path ) {
return false;
} elseif ( FileBackend::isStoragePath( $this->path ) ) {
$be = $this->file->getRepo()->getBackend();
+
return $be->streamFile( array( 'src' => $this->path, 'headers' => $headers ) )->isOK();
} else { // FS-file
return StreamFile::stream( $this->getLocalCopyPath(), $headers );
@@ -182,9 +211,8 @@ abstract class MediaTransformOutput {
/**
* Wrap some XHTML text in an anchor tag with the given attributes
*
- * @param $linkAttribs array
- * @param $contents string
- *
+ * @param array $linkAttribs
+ * @param string $contents
* @return string
*/
protected function linkWrap( $linkAttribs, $contents ) {
@@ -196,8 +224,8 @@ abstract class MediaTransformOutput {
}
/**
- * @param $title string
- * @param $params string|array Query parameters to add
+ * @param string $title
+ * @param string|array $params Query parameters to add
* @return array
*/
public function getDescLinkAttribs( $title = null, $params = array() ) {
@@ -224,6 +252,7 @@ abstract class MediaTransformOutput {
if ( $title ) {
$attribs['title'] = $title;
}
+
return $attribs;
}
}
@@ -241,11 +270,10 @@ class ThumbnailImage extends MediaTransformOutput {
* $parameters should include, as a minimum, (file) 'width' and 'height'.
* It may also include a 'page' parameter for multipage files.
*
- * @param $file File object
+ * @param File $file
* @param string $url URL path to the thumb
- * @param $path String|bool|null: filesystem path to the thumb
+ * @param string|bool $path Filesystem path to the thumb
* @param array $parameters Associative array of parameters
- * @private
*/
function __construct( $file, $url, $path = false, $parameters = array() ) {
# Previous parameters:
@@ -300,6 +328,8 @@ class ThumbnailImage extends MediaTransformOutput {
* desc-query String, description link query params
* override-width Override width attribute. Should generally not set
* override-height Override height attribute. Should generally not set
+ * no-dimensions Boolean, skip width and height attributes (useful if
+ * set in CSS)
* custom-url-link Custom URL to link to
* custom-title-link Custom Title object to link to
* custom target-link Value of the target attribute, for custom-target-link
@@ -336,13 +366,17 @@ class ThumbnailImage extends MediaTransformOutput {
$linkAttribs['rel'] = $options['parser-extlink-rel'];
}
} elseif ( !empty( $options['custom-title-link'] ) ) {
+ /** @var Title $title */
$title = $options['custom-title-link'];
$linkAttribs = array(
'href' => $title->getLinkURL(),
'title' => empty( $options['title'] ) ? $title->getFullText() : $options['title']
);
} elseif ( !empty( $options['desc-link'] ) ) {
- $linkAttribs = $this->getDescLinkAttribs( empty( $options['title'] ) ? null : $options['title'], $query );
+ $linkAttribs = $this->getDescLinkAttribs(
+ empty( $options['title'] ) ? null : $options['title'],
+ $query
+ );
} elseif ( !empty( $options['file-link'] ) ) {
$linkAttribs = array( 'href' => $this->file->getURL() );
} else {
@@ -352,9 +386,12 @@ class ThumbnailImage extends MediaTransformOutput {
$attribs = array(
'alt' => $alt,
'src' => $this->url,
- 'width' => $this->width,
- 'height' => $this->height
);
+
+ if ( empty( $options['no-dimensions'] ) ) {
+ $attribs['width'] = $this->width;
+ $attribs['height'] = $this->height;
+ }
if ( !empty( $options['valign'] ) ) {
$attribs['style'] = "vertical-align: {$options['valign']}";
}
@@ -377,7 +414,6 @@ class ThumbnailImage extends MediaTransformOutput {
return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
}
-
}
/**
@@ -386,7 +422,11 @@ class ThumbnailImage extends MediaTransformOutput {
* @ingroup Media
*/
class MediaTransformError extends MediaTransformOutput {
- var $htmlMsg, $textMsg, $width, $height, $url, $path;
+ /** @var string HTML formatted version of the error */
+ private $htmlMsg;
+
+ /** @var string Plain text formatted version of the error */
+ private $textMsg;
function __construct( $msg, $width, $height /*, ... */ ) {
$args = array_slice( func_get_args(), 3 );
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
index 98f13861..7b3ddb51 100644
--- a/includes/media/PNG.php
+++ b/includes/media/PNG.php
@@ -27,7 +27,6 @@
* @ingroup Media
*/
class PNGHandler extends BitmapHandler {
-
const BROKEN_FILE = '0';
/**
@@ -41,6 +40,7 @@ class PNGHandler extends BitmapHandler {
} catch ( Exception $e ) {
// Broken file?
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
return self::BROKEN_FILE;
}
@@ -48,28 +48,41 @@ class PNGHandler extends BitmapHandler {
}
/**
- * @param $image File
+ * @param File $image
* @return array|bool
*/
function formatMetadata( $image ) {
+ $meta = $this->getCommonMetaArray( $image );
+ if ( count( $meta ) === 0 ) {
+ return false;
+ }
+
+ return $this->formatMetadataHelper( $meta );
+ }
+
+ /**
+ * Get a file type independent array of metadata.
+ *
+ * @param File $image
+ * @return array The metadata array
+ */
+ public function getCommonMetaArray( File $image ) {
$meta = $image->getMetadata();
if ( !$meta ) {
- return false;
+ return array();
}
$meta = unserialize( $meta );
- if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
- return false;
+ if ( !isset( $meta['metadata'] ) ) {
+ return array();
}
+ unset( $meta['metadata']['_MW_PNG_VERSION'] );
- if ( isset( $meta['metadata']['_MW_PNG_VERSION'] ) ) {
- unset( $meta['metadata']['_MW_PNG_VERSION'] );
- }
- return $this->formatMetadataHelper( $meta['metadata'] );
+ return $meta['metadata'];
}
/**
- * @param $image File
+ * @param File $image
* @return bool
*/
function isAnimatedImage( $image ) {
@@ -80,12 +93,14 @@ class PNGHandler extends BitmapHandler {
return true;
}
}
+
return false;
}
+
/**
* We do not support making APNG thumbnails, so always false
- * @param $image File
- * @return bool false
+ * @param File $image
+ * @return bool False
*/
function canAnimateThumbnail( $image ) {
return false;
@@ -108,19 +123,23 @@ class PNGHandler extends BitmapHandler {
if ( !$data || !is_array( $data ) ) {
wfDebug( __METHOD__ . " invalid png metadata\n" );
+
return self::METADATA_BAD;
}
if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
- || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION ) {
+ || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION
+ ) {
wfDebug( __METHOD__ . " old but compatible png metadata\n" );
+
return self::METADATA_COMPATIBLE;
}
+
return self::METADATA_GOOD;
}
/**
- * @param $image File
+ * @param File $image
* @return string
*/
function getLongDesc( $image ) {
@@ -155,4 +174,7 @@ class PNGHandler extends BitmapHandler {
return $wgLang->commaList( $info );
}
+ public function supportsBucketing() {
+ return true;
+ }
}
diff --git a/includes/media/PNGMetadataExtractor.php b/includes/media/PNGMetadataExtractor.php
index 845d212a..bccd36c1 100644
--- a/includes/media/PNGMetadataExtractor.php
+++ b/includes/media/PNGMetadataExtractor.php
@@ -31,40 +31,45 @@
* @ingroup Media
*/
class PNGMetadataExtractor {
- static $png_sig;
- static $CRC_size;
- static $text_chunks;
+ /** @var string */
+ private static $pngSig;
+
+ /** @var int */
+ private static $crcSize;
+
+ /** @var array */
+ private static $textChunks;
const VERSION = 1;
const MAX_CHUNK_SIZE = 3145728; // 3 megabytes
static function getMetadata( $filename ) {
- self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
- self::$CRC_size = 4;
+ self::$pngSig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
+ self::$crcSize = 4;
/* based on list at http://owl.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData
* and http://www.w3.org/TR/PNG/#11keywords
*/
- self::$text_chunks = array(
+ self::$textChunks = array(
'xml:com.adobe.xmp' => 'xmp',
# Artist is unofficial. Author is the recommended
# keyword in the PNG spec. However some people output
# Artist so support both.
- 'artist' => 'Artist',
- 'model' => 'Model',
- 'make' => 'Make',
- 'author' => 'Artist',
- 'comment' => 'PNGFileComment',
+ 'artist' => 'Artist',
+ 'model' => 'Model',
+ 'make' => 'Make',
+ 'author' => 'Artist',
+ 'comment' => 'PNGFileComment',
'description' => 'ImageDescription',
- 'title' => 'ObjectName',
- 'copyright' => 'Copyright',
+ 'title' => 'ObjectName',
+ 'copyright' => 'Copyright',
# Source as in original device used to make image
# not as in who gave you the image
- 'source' => 'Model',
- 'software' => 'Software',
- 'disclaimer' => 'Disclaimer',
- 'warning' => 'ContentWarning',
- 'url' => 'Identifier', # Not sure if this is best mapping. Maybe WebStatement.
- 'label' => 'Label',
+ 'source' => 'Model',
+ 'software' => 'Software',
+ 'disclaimer' => 'Disclaimer',
+ 'warning' => 'ContentWarning',
+ 'url' => 'Identifier', # Not sure if this is best mapping. Maybe WebStatement.
+ 'label' => 'Label',
'creation time' => 'DateTimeDigitized',
/* Other potentially useful things - Document */
);
@@ -90,7 +95,7 @@ class PNGMetadataExtractor {
// Check for the PNG header
$buf = fread( $fh, 8 );
- if ( $buf != self::$png_sig ) {
+ if ( $buf != self::$pngSig ) {
throw new Exception( __METHOD__ . ": Not a valid PNG file; header: $buf" );
}
@@ -181,9 +186,9 @@ class PNGMetadataExtractor {
// Theoretically should be case-sensitive, but in practise...
$items[1] = strtolower( $items[1] );
- if ( !isset( self::$text_chunks[$items[1]] ) ) {
+ if ( !isset( self::$textChunks[$items[1]] ) ) {
// Only extract textual chunks on our list.
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
@@ -203,26 +208,23 @@ class PNGMetadataExtractor {
if ( $items[5] === false ) {
// decompression failed
wfDebug( __METHOD__ . ' Error decompressing iTxt chunk - ' . $items[1] . "\n" );
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
-
} else {
wfDebug( __METHOD__ . ' Skipping compressed png iTXt chunk due to lack of zlib,'
. " or potentially invalid compression method\n" );
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
}
- $finalKeyword = self::$text_chunks[$items[1]];
+ $finalKeyword = self::$textChunks[$items[1]];
$text[$finalKeyword][$items[3]] = $items[5];
$text[$finalKeyword]['_type'] = 'lang';
-
} else {
// Error reading iTXt chunk
throw new Exception( __METHOD__ . ": Read error on iTXt chunk" );
}
-
} elseif ( $chunk_type == 'tEXt' ) {
$buf = self::read( $fh, $chunk_size );
@@ -238,9 +240,9 @@ class PNGMetadataExtractor {
// Theoretically should be case-sensitive, but in practise...
$keyword = strtolower( $keyword );
- if ( !isset( self::$text_chunks[ $keyword ] ) ) {
+ if ( !isset( self::$textChunks[$keyword] ) ) {
// Don't recognize chunk, so skip.
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
wfSuppressWarnings();
@@ -251,10 +253,9 @@ class PNGMetadataExtractor {
throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
}
- $finalKeyword = self::$text_chunks[$keyword];
+ $finalKeyword = self::$textChunks[$keyword];
$text[$finalKeyword]['x-default'] = $content;
$text[$finalKeyword]['_type'] = 'lang';
-
} elseif ( $chunk_type == 'zTXt' ) {
if ( function_exists( 'gzuncompress' ) ) {
$buf = self::read( $fh, $chunk_size );
@@ -271,16 +272,16 @@ class PNGMetadataExtractor {
// Theoretically should be case-sensitive, but in practise...
$keyword = strtolower( $keyword );
- if ( !isset( self::$text_chunks[ $keyword ] ) ) {
+ if ( !isset( self::$textChunks[$keyword] ) ) {
// Don't recognize chunk, so skip.
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
$compression = substr( $postKeyword, 0, 1 );
$content = substr( $postKeyword, 1 );
if ( $compression !== "\x00" ) {
wfDebug( __METHOD__ . " Unrecognized compression method in zTXt ($keyword). Skipping.\n" );
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
@@ -291,7 +292,7 @@ class PNGMetadataExtractor {
if ( $content === false ) {
// decompression failed
wfDebug( __METHOD__ . ' Error decompressing zTXt chunk - ' . $keyword . "\n" );
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
@@ -303,10 +304,9 @@ class PNGMetadataExtractor {
throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
}
- $finalKeyword = self::$text_chunks[$keyword];
+ $finalKeyword = self::$textChunks[$keyword];
$text[$finalKeyword]['x-default'] = $content;
$text[$finalKeyword]['_type'] = 'lang';
-
} else {
wfDebug( __METHOD__ . " Cannot decompress zTXt chunk due to lack of zlib. Skipping.\n" );
fseek( $fh, $chunk_size, SEEK_CUR );
@@ -332,7 +332,6 @@ class PNGMetadataExtractor {
if ( $exifTime ) {
$text['DateTime'] = $exifTime;
}
-
} elseif ( $chunk_type == 'pHYs' ) {
// how big pixels are (dots per meter).
if ( $chunk_size !== 9 ) {
@@ -359,13 +358,12 @@ class PNGMetadataExtractor {
// 3 = dots per cm (from Exif).
}
}
-
} elseif ( $chunk_type == "IEND" ) {
break;
} else {
fseek( $fh, $chunk_size, SEEK_CUR );
}
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
}
fclose( $fh );
@@ -399,6 +397,7 @@ class PNGMetadataExtractor {
}
}
}
+
return array(
'frameCount' => $frameCount,
'loopCount' => $loopCount,
@@ -407,21 +406,22 @@ class PNGMetadataExtractor {
'bitDepth' => $bitDepth,
'colorType' => $colorType,
);
-
}
+
/**
* Read a chunk, checking to make sure its not too big.
*
- * @param $fh resource The file handle
- * @param $size Integer size in bytes.
- * @throws Exception if too big.
- * @return String The chunk.
+ * @param resource $fh The file handle
+ * @param int $size Size in bytes.
+ * @throws Exception If too big
+ * @return string The chunk.
*/
private static function read( $fh, $size ) {
if ( $size > self::MAX_CHUNK_SIZE ) {
throw new Exception( __METHOD__ . ': Chunk size of ' . $size .
' too big. Max size is: ' . self::MAX_CHUNK_SIZE );
}
+
return fread( $fh, $size );
}
}
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index 72a9696c..74e5e048 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -29,10 +29,22 @@
class SvgHandler extends ImageHandler {
const SVG_METADATA_VERSION = 2;
+ /** @var array A list of metadata tags that can be converted
+ * to the commonly used exif tags. This allows messages
+ * to be reused, and consistent tag names for {{#formatmetadata:..}}
+ */
+ private static $metaConversion = array(
+ 'originalwidth' => 'ImageWidth',
+ 'originalheight' => 'ImageLength',
+ 'description' => 'ImageDescription',
+ 'title' => 'ObjectName',
+ );
+
function isEnabled() {
global $wgSVGConverters, $wgSVGConverter;
if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
+
return false;
} else {
return true;
@@ -48,11 +60,11 @@ class SvgHandler extends ImageHandler {
}
/**
- * @param $file File
+ * @param File $file
* @return bool
*/
function isAnimatedImage( $file ) {
- # TODO: detect animated SVGs
+ # @todo Detect animated SVGs
$metadata = $file->getMetadata();
if ( $metadata ) {
$metadata = $this->unpackMetadata( $metadata );
@@ -60,19 +72,60 @@ class SvgHandler extends ImageHandler {
return $metadata['animated'];
}
}
+
return false;
}
/**
+ * Which languages (systemLanguage attribute) is supported.
+ *
+ * @note This list is not guaranteed to be exhaustive.
+ * To avoid OOM errors, we only look at first bit of a file.
+ * Thus all languages on this list are present in the file,
+ * but its possible for the file to have a language not on
+ * this list.
+ *
+ * @param File $file
+ * @return array Array of language codes, or empty if no language switching supported.
+ */
+ public function getAvailableLanguages( File $file ) {
+ $metadata = $file->getMetadata();
+ $langList = array();
+ if ( $metadata ) {
+ $metadata = $this->unpackMetadata( $metadata );
+ if ( isset( $metadata['translations'] ) ) {
+ foreach ( $metadata['translations'] as $lang => $langType ) {
+ if ( $langType === SvgReader::LANG_FULL_MATCH ) {
+ $langList[] = $lang;
+ }
+ }
+ }
+ }
+ return $langList;
+ }
+
+ /**
+ * What language to render file in if none selected.
+ *
+ * @param File $file
+ * @return string Language code.
+ */
+ public function getDefaultRenderLanguage( File $file ) {
+ return 'en';
+ }
+
+ /**
* We do not support making animated svg thumbnails
+ * @param File $file
+ * @return bool
*/
- function canAnimateThumb( $file ) {
+ function canAnimateThumbnail( $file ) {
return false;
}
/**
- * @param $image File
- * @param $params
+ * @param File $image
+ * @param array $params
* @return bool
*/
function normaliseParams( $image, &$params ) {
@@ -96,14 +149,15 @@ class SvgHandler extends ImageHandler {
$params['physicalHeight'] = $wgSVGMaxSize;
}
}
+
return true;
}
/**
- * @param $image File
- * @param $dstPath
- * @param $dstUrl
- * @param $params
+ * @param File $image
+ * @param string $dstPath
+ * @param string $dstUrl
+ * @param array $params
* @param int $flags
* @return bool|MediaTransformError|ThumbnailImage|TransformParameterError
*/
@@ -115,7 +169,7 @@ class SvgHandler extends ImageHandler {
$clientHeight = $params['height'];
$physicalWidth = $params['physicalWidth'];
$physicalHeight = $params['physicalHeight'];
- $lang = isset( $params['lang'] ) ? $params['lang'] : 'en';
+ $lang = isset( $params['lang'] ) ? $params['lang'] : $this->getDefaultRenderLanguage( $image );
if ( $flags & self::TRANSFORM_LATER ) {
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
@@ -124,6 +178,7 @@ class SvgHandler extends ImageHandler {
$metadata = $this->unpackMetadata( $image->getMetadata() );
if ( isset( $metadata['error'] ) ) { // sanity check
$err = wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
+
return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
}
@@ -133,7 +188,40 @@ class SvgHandler extends ImageHandler {
}
$srcPath = $image->getLocalRefPath();
- $status = $this->rasterize( $srcPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
+ if ( $srcPath === false ) { // Failed to get local copy
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+ wfHostname(), $image->getName() ) );
+
+ return new MediaTransformError( 'thumbnail_error',
+ $params['width'], $params['height'],
+ wfMessage( 'filemissing' )->text()
+ );
+ }
+
+ // Make a temp dir with a symlink to the local copy in it.
+ // This plays well with rsvg-convert policy for external entities.
+ // https://git.gnome.org/browse/librsvg/commit/?id=f01aded72c38f0e18bc7ff67dee800e380251c8e
+ $tmpDir = wfTempDir() . '/svg_' . wfRandomString( 24 );
+ $lnPath = "$tmpDir/" . basename( $srcPath );
+ $ok = mkdir( $tmpDir, 0771 ) && symlink( $srcPath, $lnPath );
+ $cleaner = new ScopedCallback( function () use ( $tmpDir, $lnPath ) {
+ wfSuppressWarnings();
+ unlink( $lnPath );
+ rmdir( $tmpDir );
+ wfRestoreWarnings();
+ } );
+ if ( !$ok ) {
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Thumbnail failed on %s: could not link %s to %s',
+ wfHostname(), $lnPath, $srcPath ) );
+ return new MediaTransformError( 'thumbnail_error',
+ $params['width'], $params['height'],
+ wfMessage( 'thumbnail-temp-create' )->text()
+ );
+ }
+
+ $status = $this->rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
if ( $status === true ) {
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
} else {
@@ -148,7 +236,7 @@ class SvgHandler extends ImageHandler {
* @param string $dstPath
* @param string $width
* @param string $height
- * @param string $lang Language code of the language to render the SVG in
+ * @param bool|string $lang Language code of the language to render the SVG in
* @throws MWException
* @return bool|MediaTransformError
*/
@@ -192,10 +280,10 @@ class SvgHandler extends ImageHandler {
}
$removed = $this->removeBadFile( $dstPath, $retval );
if ( $retval != 0 || $removed ) {
- wfDebugLog( 'thumbnail', sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim( $err ), $cmd ) );
+ $this->logErrorForExternalProcess( $retval, $err, $cmd );
return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
}
+
return true;
}
@@ -214,9 +302,9 @@ class SvgHandler extends ImageHandler {
}
/**
- * @param $file File
- * @param $path
- * @param bool $metadata
+ * @param File $file
+ * @param string $path Unused
+ * @param bool|array $metadata
* @return array
*/
function getImageSize( $file, $path, $metadata = false ) {
@@ -227,7 +315,7 @@ class SvgHandler extends ImageHandler {
if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
return array( $metadata['width'], $metadata['height'], 'SVG',
- "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" );
+ "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" );
} else { // error
return array( 0, 0, 'SVG', "width=\"0\" height=\"0\"" );
}
@@ -243,7 +331,7 @@ class SvgHandler extends ImageHandler {
* a "nominal" resolution, and not a fixed one,
* as well as so animation can be denoted.
*
- * @param $file File
+ * @param File $file
* @return string
*/
function getLongDesc( $file ) {
@@ -267,6 +355,11 @@ class SvgHandler extends ImageHandler {
return $msg->parse();
}
+ /**
+ * @param File $file
+ * @param string $filename
+ * @return string Serialised metadata
+ */
function getMetadata( $file, $filename ) {
$metadata = array( 'version' => self::SVG_METADATA_VERSION );
try {
@@ -279,6 +372,7 @@ class SvgHandler extends ImageHandler {
);
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
}
+
return serialize( $metadata );
}
@@ -306,16 +400,18 @@ class SvgHandler extends ImageHandler {
// Old but compatible
return self::METADATA_COMPATIBLE;
}
+
return self::METADATA_GOOD;
}
- function visibleMetadataFields() {
+ protected function visibleMetadataFields() {
$fields = array( 'objectname', 'imagedescription' );
+
return $fields;
}
/**
- * @param $file File
+ * @param File $file
* @return array|bool
*/
function formatMetadata( $file ) {
@@ -332,7 +428,7 @@ class SvgHandler extends ImageHandler {
return false;
}
- /* TODO: add a formatter
+ /* @todo Add a formatter
$format = new FormatSVG( $metadata );
$formatted = $format->getFormattedData();
*/
@@ -340,19 +436,11 @@ class SvgHandler extends ImageHandler {
// Sort fields into visible and collapsed
$visibleFields = $this->visibleMetadataFields();
- // Rename fields to be compatible with exif, so that
- // the labels for these fields work and reuse existing messages.
- $conversion = array(
- 'originalwidth' => 'imagewidth',
- 'originalheight' => 'imagelength',
- 'description' => 'imagedescription',
- 'title' => 'objectname',
- );
$showMeta = false;
foreach ( $metadata as $name => $value ) {
$tag = strtolower( $name );
- if ( isset( $conversion[$tag] ) ) {
- $tag = $conversion[$tag];
+ if ( isset( self::$metaConversion[$tag] ) ) {
+ $tag = strtolower( self::$metaConversion[$tag] );
} else {
// Do not output other metadata not in list
continue;
@@ -365,13 +453,13 @@ class SvgHandler extends ImageHandler {
$value
);
}
+
return $showMeta ? $result : false;
}
-
/**
* @param string $name Parameter name
- * @param $string $value Parameter value
+ * @param mixed $value Parameter value
* @return bool Validity
*/
function validateParam( $name, $value ) {
@@ -380,18 +468,21 @@ class SvgHandler extends ImageHandler {
return ( $value > 0 );
} elseif ( $name == 'lang' ) {
// Validate $code
- if ( !Language::isValidBuiltinCode( $value ) ) {
+ if ( $value === '' || !Language::isValidBuiltinCode( $value ) ) {
wfDebug( "Invalid user language code\n" );
+
return false;
}
+
return true;
}
+
// Only lang, width and height are acceptable keys
return false;
}
/**
- * @param array $params name=>value pairs of parameters
+ * @param array $params Name=>value pairs of parameters
* @return string Filename to use
*/
function makeParamString( $params ) {
@@ -403,6 +494,7 @@ class SvgHandler extends ImageHandler {
if ( !isset( $params['width'] ) ) {
return false;
}
+
return "$lang{$params['width']}px";
}
@@ -422,13 +514,41 @@ class SvgHandler extends ImageHandler {
}
/**
- * @param $params
+ * @param array $params
* @return array
*/
function getScriptParams( $params ) {
- return array(
- 'width' => $params['width'],
- 'lang' => $params['lang'],
- );
+ $scriptParams = array( 'width' => $params['width'] );
+ if ( isset( $params['lang'] ) ) {
+ $scriptParams['lang'] = $params['lang'];
+ }
+
+ return $scriptParams;
+ }
+
+ public function getCommonMetaArray( File $file ) {
+ $metadata = $file->getMetadata();
+ if ( !$metadata ) {
+ return array();
+ }
+ $metadata = $this->unpackMetadata( $metadata );
+ if ( !$metadata || isset( $metadata['error'] ) ) {
+ return array();
+ }
+ $stdMetadata = array();
+ foreach ( $metadata as $name => $value ) {
+ $tag = strtolower( $name );
+ if ( $tag === 'originalwidth' || $tag === 'originalheight' ) {
+ // Skip these. In the exif metadata stuff, it is assumed these
+ // are measured in px, which is not the case here.
+ continue;
+ }
+ if ( isset( self::$metaConversion[$tag] ) ) {
+ $tag = self::$metaConversion[$tag];
+ $stdMetadata[$tag] = $value;
+ }
+ }
+
+ return $stdMetadata;
}
}
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
index 2e33bb98..2a1091d8 100644
--- a/includes/media/SVGMetadataExtractor.php
+++ b/includes/media/SVGMetadataExtractor.php
@@ -31,6 +31,7 @@
class SVGMetadataExtractor {
static function getMetadata( $filename ) {
$svg = new SVGReader( $filename );
+
return $svg->getMetadata();
}
}
@@ -42,10 +43,19 @@ 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;
- private $metadata = Array();
+
+ /** @var array */
+ private $metadata = array();
+ private $languages = array();
+ private $languagePrefixes = array();
/**
* Constructor
@@ -113,7 +123,7 @@ class SVGReader {
}
/**
- * @return Array with the known metadata
+ * @return array Array with the known metadata
*/
public function getMetadata() {
return $this->metadata;
@@ -148,7 +158,9 @@ class SVGReader {
$this->debug( "$tag" );
- if ( $isSVG && $tag == 'svg' && $type == XmlReader::END_ELEMENT && $this->reader->depth <= $exitDepth ) {
+ if ( $isSVG && $tag == 'svg' && $type == XmlReader::END_ELEMENT
+ && $this->reader->depth <= $exitDepth
+ ) {
break;
} elseif ( $isSVG && $tag == 'title' ) {
$this->readField( $tag, 'title' );
@@ -164,10 +176,8 @@ class SVGReader {
} elseif ( $tag !== '#text' ) {
$this->debug( "Unhandled top-level XML tag $tag" );
- if ( !isset( $this->metadata['animated'] ) ) {
- // Recurse into children of current tag, looking for animation.
- $this->animateFilter( $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).
@@ -176,14 +186,16 @@ class SVGReader {
$this->reader->close();
+ $this->metadata['translations'] = $this->languages + $this->languagePrefixes;
+
return true;
}
/**
* Read a textelement from an element
*
- * @param string $name of the element that we are reading from
- * @param string $metafield that we will fill with the result
+ * @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" );
@@ -192,7 +204,10 @@ class SVGReader {
}
$keepReading = $this->reader->read();
while ( $keepReading ) {
- if ( $this->reader->localName == $name && $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
+ 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 );
@@ -204,7 +219,7 @@ class SVGReader {
/**
* Read an XML snippet from an element
*
- * @param string $metafield that we will fill with the result
+ * @param string $metafield Field that we will fill with the result
* @throws MWException
*/
private function readXml( $metafield = null ) {
@@ -212,21 +227,24 @@ class SVGReader {
if ( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
return;
}
- // TODO: find and store type of xml snippet. metadata['metadataType'] = "rdf"
+ // @todo Find and store type of xml snippet. metadata['metadataType'] = "rdf"
if ( method_exists( $this->reader, 'readInnerXML' ) ) {
$this->metadata[$metafield] = trim( $this->reader->readInnerXML() );
} else {
- throw new MWException( "The PHP XMLReader extension does not come with readInnerXML() method. Your libxml is probably out of date (need 2.6.20 or later)." );
+ throw new MWException( "The PHP XMLReader extension does not come " .
+ "with readInnerXML() method. Your libxml is probably out of " .
+ "date (need 2.6.20 or later)." );
}
$this->reader->next();
}
/**
- * Filter all children, looking for animate elements
+ * Filter all children, looking for animated elements.
+ * Also get a list of languages that can be targeted.
*
- * @param string $name of the element that we are reading from
+ * @param string $name Name of the element that we are reading from
*/
- private function animateFilter( $name ) {
+ private function animateFilterAndLang( $name ) {
$this->debug( "animate filter for tag $name" );
if ( $this->reader->nodeType != XmlReader::ELEMENT ) {
return;
@@ -238,9 +256,38 @@ class SVGReader {
$keepReading = $this->reader->read();
while ( $keepReading ) {
if ( $this->reader->localName == $name && $this->reader->depth <= $exitDepth
- && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
+ && $this->reader->nodeType == XmlReader::END_ELEMENT
+ ) {
break;
- } elseif ( $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::ELEMENT ) {
+ } elseif ( $this->reader->namespaceURI == self::NS_SVG
+ && $this->reader->nodeType == XmlReader::ELEMENT
+ ) {
+
+ $sysLang = $this->reader->getAttribute( 'systemLanguage' );
+ if ( !is_null( $sysLang ) && $sysLang !== '' ) {
+ // See http://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
@@ -261,6 +308,7 @@ class SVGReader {
}
}
+ // @todo FIXME: Unused, remove?
private function throwXmlError( $err ) {
$this->debug( "FAILURE: $err" );
wfDebug( "SVGReader XML error: $err\n" );
@@ -272,10 +320,12 @@ class SVGReader {
}
}
+ // @todo FIXME: Unused, remove?
private function warn( $data ) {
wfDebug( "SVGReader: $data\n" );
}
+ // @todo FIXME: Unused, remove?
private function notice( $data ) {
wfDebug( "SVGReader WARN: $data\n" );
}
@@ -333,8 +383,8 @@ class SVGReader {
* http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
*
* @param string $length CSS/SVG length.
- * @param $viewportSize: Float optional scale for percentage units...
- * @return float: length in pixels
+ * @param float|int $viewportSize Optional scale for percentage units...
+ * @return float Length in pixels
*/
static function scaleSVGUnit( $length, $viewportSize = 512 ) {
static $unitLength = array(
@@ -347,7 +397,7 @@ class SVGReader {
'em' => 16.0, // fake it?
'ex' => 12.0, // fake it?
'' => 1.0, // "User units" pixels by default
- );
+ );
$matches = array();
if ( preg_match( '/^\s*(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)\s*$/', $length, $matches ) ) {
$length = floatval( $matches[1] );
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
index 55acb120..bea6cab3 100644
--- a/includes/media/Tiff.php
+++ b/includes/media/Tiff.php
@@ -27,6 +27,7 @@
* @ingroup Media
*/
class TiffHandler extends ExifBitmapHandler {
+ const EXPENSIVE_SIZE_LIMIT = 10485760; // TIFF files over 10M are considered expensive to thumbnail
/**
* Conversion to PNG for inline display can be disabled here...
@@ -36,12 +37,12 @@ class TiffHandler extends ExifBitmapHandler {
* InstantCommons will have thumbnails managed from the remote instance,
* so we can skip this check.
*
- * @param $file
- *
+ * @param File $file
* @return bool
*/
function canRender( $file ) {
global $wgTiffThumbnailType;
+
return (bool)$wgTiffThumbnailType
|| $file->getRepo() instanceof ForeignAPIRepo;
}
@@ -50,8 +51,7 @@ class TiffHandler extends ExifBitmapHandler {
* Browsers don't support TIFF inline generally...
* For inline display, we need to convert to PNG.
*
- * @param $file
- *
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -59,13 +59,14 @@ class TiffHandler extends ExifBitmapHandler {
}
/**
- * @param $ext
- * @param $mime
- * @param $params
+ * @param string $ext
+ * @param string $mime
+ * @param array $params
* @return bool
*/
function getThumbType( $ext, $mime, $params = null ) {
global $wgTiffThumbnailType;
+
return $wgTiffThumbnailType;
}
@@ -85,16 +86,21 @@ class TiffHandler extends ExifBitmapHandler {
throw new MWException( 'Metadata array is not an array' );
}
$meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
return serialize( $meta );
- }
- catch ( MWException $e ) {
+ } catch ( MWException $e ) {
// BitmapMetadataHandler throws an exception in certain exceptional
// cases like if file does not exist.
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
return ExifBitmapHandler::BROKEN_FILE;
}
} else {
return '';
}
}
+
+ public function isExpensiveToThumbnail( $file ) {
+ return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
+ }
}
diff --git a/includes/media/TransformationalImageHandler.php b/includes/media/TransformationalImageHandler.php
new file mode 100644
index 00000000..3e3be3d1
--- /dev/null
+++ b/includes/media/TransformationalImageHandler.php
@@ -0,0 +1,593 @@
+<?php
+/**
+ * Base class for handlers which require transforming images in a
+ * similar way as BitmapHandler does.
+ *
+ * This was split from BitmapHandler on the basis that some extensions
+ * might want to work in a similar way to BitmapHandler, but for
+ * different formats.
+ *
+ * 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
+ */
+
+/**
+ * Handler for images that need to be transformed
+ *
+ * @since 1.24
+ * @ingroup Media
+ */
+abstract class TransformationalImageHandler extends ImageHandler {
+ /**
+ * @param File $image
+ * @param array $params Transform parameters. Entries with the keys 'width'
+ * and 'height' are the respective screen width and height, while the keys
+ * 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
+ * @return bool
+ */
+ function normaliseParams( $image, &$params ) {
+ if ( !parent::normaliseParams( $image, $params ) ) {
+ return false;
+ }
+
+ # Obtain the source, pre-rotation dimensions
+ $srcWidth = $image->getWidth( $params['page'] );
+ $srcHeight = $image->getHeight( $params['page'] );
+
+ # Don't make an image bigger than the source
+ if ( $params['physicalWidth'] >= $srcWidth ) {
+ $params['physicalWidth'] = $srcWidth;
+ $params['physicalHeight'] = $srcHeight;
+
+ # Skip scaling limit checks if no scaling is required
+ # due to requested size being bigger than source.
+ if ( !$image->mustRender() ) {
+ return true;
+ }
+ }
+
+ # Check if the file is smaller than the maximum image area for thumbnailing
+ # For historical reasons, hook starts with BitmapHandler
+ $checkImageAreaHookResult = null;
+ wfRunHooks(
+ 'BitmapHandlerCheckImageArea',
+ array( $image, &$params, &$checkImageAreaHookResult )
+ );
+
+ if ( is_null( $checkImageAreaHookResult ) ) {
+ global $wgMaxImageArea;
+
+ if ( $srcWidth * $srcHeight > $wgMaxImageArea
+ && !( $image->getMimeType() == 'image/jpeg'
+ && $this->getScalerType( false, false ) == 'im' )
+ ) {
+ # Only ImageMagick can efficiently downsize jpg images without loading
+ # the entire file in memory
+ return false;
+ }
+ } else {
+ return $checkImageAreaHookResult;
+ }
+
+ return true;
+ }
+
+ /**
+ * Extracts the width/height if the image will be scaled before rotating
+ *
+ * This will match the physical size/aspect ratio of the original image
+ * prior to application of the rotation -- so for a portrait image that's
+ * stored as raw landscape with 90-degress rotation, the resulting size
+ * will be wider than it is tall.
+ *
+ * @param array $params Parameters as returned by normaliseParams
+ * @param int $rotation The rotation angle that will be applied
+ * @return array ($width, $height) array
+ */
+ public function extractPreRotationDimensions( $params, $rotation ) {
+ if ( $rotation == 90 || $rotation == 270 ) {
+ # We'll resize before rotation, so swap the dimensions again
+ $width = $params['physicalHeight'];
+ $height = $params['physicalWidth'];
+ } else {
+ $width = $params['physicalWidth'];
+ $height = $params['physicalHeight'];
+ }
+
+ return array( $width, $height );
+ }
+
+ /**
+ * Create a thumbnail.
+ *
+ * This sets up various parameters, and then calls a helper method
+ * based on $this->getScalerType in order to scale the image.
+ *
+ * @param File $image
+ * @param string $dstPath
+ * @param string $dstUrl
+ * @param array $params
+ * @param int $flags
+ * @return MediaTransformError|ThumbnailImage|TransformParameterError
+ */
+ function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+ if ( !$this->normaliseParams( $image, $params ) ) {
+ return new TransformParameterError( $params );
+ }
+
+ # Create a parameter array to pass to the scaler
+ $scalerParams = array(
+ # The size to which the image will be resized
+ 'physicalWidth' => $params['physicalWidth'],
+ 'physicalHeight' => $params['physicalHeight'],
+ 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
+ # The size of the image on the page
+ 'clientWidth' => $params['width'],
+ 'clientHeight' => $params['height'],
+ # Comment as will be added to the Exif of the thumbnail
+ 'comment' => isset( $params['descriptionUrl'] )
+ ? "File source: {$params['descriptionUrl']}"
+ : '',
+ # Properties of the original image
+ 'srcWidth' => $image->getWidth(),
+ 'srcHeight' => $image->getHeight(),
+ 'mimeType' => $image->getMimeType(),
+ 'dstPath' => $dstPath,
+ 'dstUrl' => $dstUrl,
+ );
+
+ if ( isset( $params['quality'] ) && $params['quality'] === 'low' ) {
+ $scalerParams['quality'] = 30;
+ }
+
+ // For subclasses that might be paged.
+ if ( $image->isMultipage() && isset( $params['page'] ) ) {
+ $scalerParams['page'] = intval( $params['page'] );
+ }
+
+ # Determine scaler type
+ $scaler = $this->getScalerType( $dstPath );
+
+ if ( is_array( $scaler ) ) {
+ $scalerName = get_class( $scaler[0] );
+ } else {
+ $scalerName = $scaler;
+ }
+
+ wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} " .
+ "thumbnail at $dstPath using scaler $scalerName\n" );
+
+ if ( !$image->mustRender() &&
+ $scalerParams['physicalWidth'] == $scalerParams['srcWidth']
+ && $scalerParams['physicalHeight'] == $scalerParams['srcHeight']
+ && !isset( $scalerParams['quality'] )
+ ) {
+
+ # normaliseParams (or the user) wants us to return the unscaled image
+ wfDebug( __METHOD__ . ": returning unscaled image\n" );
+
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
+ }
+
+ if ( $scaler == 'client' ) {
+ # Client-side image scaling, use the source URL
+ # Using the destination URL in a TRANSFORM_LATER request would be incorrect
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
+ }
+
+ if ( $flags & self::TRANSFORM_LATER ) {
+ wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
+ $newParams = array(
+ 'width' => $scalerParams['clientWidth'],
+ 'height' => $scalerParams['clientHeight']
+ );
+ if ( isset( $params['quality'] ) ) {
+ $newParams['quality'] = $params['quality'];
+ }
+ if ( isset( $params['page'] ) && $params['page'] ) {
+ $newParams['page'] = $params['page'];
+ }
+ return new ThumbnailImage( $image, $dstUrl, false, $newParams );
+ }
+
+ # Try to make a target path for the thumbnail
+ if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
+ wfDebug( __METHOD__ . ": Unable to create thumbnail destination " .
+ "directory, falling back to client scaling\n" );
+
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
+ }
+
+ # Transform functions and binaries need a FS source file
+ $thumbnailSource = $this->getThumbnailSource( $image, $params );
+
+ $scalerParams['srcPath'] = $thumbnailSource['path'];
+ $scalerParams['srcWidth'] = $thumbnailSource['width'];
+ $scalerParams['srcHeight'] = $thumbnailSource['height'];
+
+ if ( $scalerParams['srcPath'] === false ) { // Failed to get local copy
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+ wfHostname(), $image->getName() ) );
+
+ return new MediaTransformError( 'thumbnail_error',
+ $scalerParams['clientWidth'], $scalerParams['clientHeight'],
+ wfMessage( 'filemissing' )->text()
+ );
+ }
+
+ # Try a hook. Called "Bitmap" for historical reasons.
+ /** @var $mto MediaTransformOutput */
+ $mto = null;
+ wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
+ if ( !is_null( $mto ) ) {
+ wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" );
+ $scaler = 'hookaborted';
+ }
+
+ // $scaler will return a MediaTransformError on failure, or false on success.
+ // If the scaler is succesful, it will have created a thumbnail at the destination
+ // path.
+ if ( is_array( $scaler ) && is_callable( $scaler ) ) {
+ // Allow subclasses to specify their own rendering methods.
+ $err = call_user_func( $scaler, $image, $scalerParams );
+ } else {
+ switch ( $scaler ) {
+ case 'hookaborted':
+ # Handled by the hook above
+ $err = $mto->isError() ? $mto : false;
+ break;
+ case 'im':
+ $err = $this->transformImageMagick( $image, $scalerParams );
+ break;
+ case 'custom':
+ $err = $this->transformCustom( $image, $scalerParams );
+ break;
+ case 'imext':
+ $err = $this->transformImageMagickExt( $image, $scalerParams );
+ break;
+ case 'gd':
+ default:
+ $err = $this->transformGd( $image, $scalerParams );
+ break;
+ }
+ }
+
+ # Remove the file if a zero-byte thumbnail was created, or if there was an error
+ $removed = $this->removeBadFile( $dstPath, (bool)$err );
+ if ( $err ) {
+ # transform returned MediaTransforError
+ return $err;
+ } elseif ( $removed ) {
+ # Thumbnail was zero-byte and had to be removed
+ return new MediaTransformError( 'thumbnail_error',
+ $scalerParams['clientWidth'], $scalerParams['clientHeight'],
+ wfMessage( 'unknown-error' )->text()
+ );
+ } elseif ( $mto ) {
+ return $mto;
+ } else {
+ $newParams = array(
+ 'width' => $scalerParams['clientWidth'],
+ 'height' => $scalerParams['clientHeight']
+ );
+ if ( isset( $params['quality'] ) ) {
+ $newParams['quality'] = $params['quality'];
+ }
+ if ( isset( $params['page'] ) && $params['page'] ) {
+ $newParams['page'] = $params['page'];
+ }
+ return new ThumbnailImage( $image, $dstUrl, $dstPath, $newParams );
+ }
+ }
+
+ /**
+ * Get the source file for the transform
+ *
+ * @param $file File
+ * @param $params Array
+ * @return Array Array with keys width, height and path.
+ */
+ protected function getThumbnailSource( $file, $params ) {
+ return $file->getThumbnailSource( $params );
+ }
+
+ /**
+ * Returns what sort of scaler type should be used.
+ *
+ * Values can be one of client, im, custom, gd, imext, or an array
+ * of object, method-name to call that specific method.
+ *
+ * If specifying a custom scaler command with array( Obj, method ),
+ * the method in question should take 2 parameters, a File object,
+ * and a $scalerParams array with various options (See doTransform
+ * for what is in $scalerParams). On error it should return a
+ * MediaTransformError object. On success it should return false,
+ * and simply make sure the thumbnail file is located at
+ * $scalerParams['dstPath'].
+ *
+ * If there is a problem with the output path, it returns "client"
+ * to do client side scaling.
+ *
+ * @param string $dstPath
+ * @param bool $checkDstPath Check that $dstPath is valid
+ * @return string|Callable One of client, im, custom, gd, imext, or a Callable array.
+ */
+ abstract protected function getScalerType( $dstPath, $checkDstPath = true );
+
+ /**
+ * Get a ThumbnailImage that respresents an image that will be scaled
+ * client side
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $scalerParams Array with scaler params
+ * @return ThumbnailImage
+ *
+ * @todo FIXME: No rotation support
+ */
+ protected function getClientScalingThumbnailImage( $image, $scalerParams ) {
+ $params = array(
+ 'width' => $scalerParams['clientWidth'],
+ 'height' => $scalerParams['clientHeight']
+ );
+
+ return new ThumbnailImage( $image, $image->getURL(), null, $params );
+ }
+
+ /**
+ * Transform an image using ImageMagick
+ *
+ * This is a stub method. The real method is in BitmapHander.
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $params Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
+ */
+ protected function transformImageMagick( $image, $params ) {
+ return $this->getMediaTransformError( $params, "Unimplemented" );
+ }
+
+ /**
+ * Transform an image using the Imagick PHP extension
+ *
+ * This is a stub method. The real method is in BitmapHander.
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $params Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
+ */
+ protected function transformImageMagickExt( $image, $params ) {
+ return $this->getMediaTransformError( $params, "Unimplemented" );
+ }
+
+ /**
+ * Transform an image using a custom command
+ *
+ * This is a stub method. The real method is in BitmapHander.
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $params Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
+ */
+ protected function transformCustom( $image, $params ) {
+ return $this->getMediaTransformError( $params, "Unimplemented" );
+ }
+
+ /**
+ * Get a MediaTransformError with error 'thumbnail_error'
+ *
+ * @param array $params Parameter array as passed to the transform* functions
+ * @param string $errMsg Error message
+ * @return MediaTransformError
+ */
+ public function getMediaTransformError( $params, $errMsg ) {
+ return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
+ $params['clientHeight'], $errMsg );
+ }
+
+ /**
+ * Transform an image using the built in GD library
+ *
+ * This is a stub method. The real method is in BitmapHander.
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $params Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
+ */
+ protected function transformGd( $image, $params ) {
+ return $this->getMediaTransformError( $params, "Unimplemented" );
+ }
+
+ /**
+ * Escape a string for ImageMagick's property input (e.g. -set -comment)
+ * See InterpretImageProperties() in magick/property.c
+ * @param string $s
+ * @return string
+ */
+ function escapeMagickProperty( $s ) {
+ // Double the backslashes
+ $s = str_replace( '\\', '\\\\', $s );
+ // Double the percents
+ $s = str_replace( '%', '%%', $s );
+ // Escape initial - or @
+ if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) {
+ $s = '\\' . $s;
+ }
+
+ return $s;
+ }
+
+ /**
+ * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
+ * and GetPathComponent() in magick/utility.c.
+ *
+ * This won't work with an initial ~ or @, so input files should be prefixed
+ * with the directory name.
+ *
+ * Glob character unescaping is broken in ImageMagick before 6.6.1-5, but
+ * it's broken in a way that doesn't involve trying to convert every file
+ * in a directory, so we're better off escaping and waiting for the bugfix
+ * to filter down to users.
+ *
+ * @param string $path The file path
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @throws MWException
+ * @return string
+ */
+ function escapeMagickInput( $path, $scene = false ) {
+ # Die on initial metacharacters (caller should prepend path)
+ $firstChar = substr( $path, 0, 1 );
+ if ( $firstChar === '~' || $firstChar === '@' ) {
+ throw new MWException( __METHOD__ . ': cannot escape this path name' );
+ }
+
+ # Escape glob chars
+ $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path );
+
+ return $this->escapeMagickPath( $path, $scene );
+ }
+
+ /**
+ * Escape a string for ImageMagick's output filename. See
+ * InterpretImageFilename() in magick/image.c.
+ * @param string $path The file path
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @return string
+ */
+ function escapeMagickOutput( $path, $scene = false ) {
+ $path = str_replace( '%', '%%', $path );
+
+ return $this->escapeMagickPath( $path, $scene );
+ }
+
+ /**
+ * Armour a string against ImageMagick's GetPathComponent(). This is a
+ * helper function for escapeMagickInput() and escapeMagickOutput().
+ *
+ * @param string $path The file path
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @throws MWException
+ * @return string
+ */
+ protected function escapeMagickPath( $path, $scene = false ) {
+ # Die on format specifiers (other than drive letters). The regex is
+ # meant to match all the formats you get from "convert -list format"
+ if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) {
+ if ( wfIsWindows() && is_dir( $m[0] ) ) {
+ // OK, it's a drive letter
+ // ImageMagick has a similar exception, see IsMagickConflict()
+ } else {
+ throw new MWException( __METHOD__ . ': unexpected colon character in path name' );
+ }
+ }
+
+ # If there are square brackets, add a do-nothing scene specification
+ # to force a literal interpretation
+ if ( $scene === false ) {
+ if ( strpos( $path, '[' ) !== false ) {
+ $path .= '[0--1]';
+ }
+ } else {
+ $path .= "[$scene]";
+ }
+
+ return $path;
+ }
+
+ /**
+ * Retrieve the version of the installed ImageMagick
+ * You can use PHPs version_compare() to use this value
+ * Value is cached for one hour.
+ * @return string Representing the IM version.
+ */
+ protected function getMagickVersion() {
+ global $wgMemc;
+
+ $cache = $wgMemc->get( "imagemagick-version" );
+ if ( !$cache ) {
+ global $wgImageMagickConvertCommand;
+ $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version';
+ wfDebug( __METHOD__ . ": Running convert -version\n" );
+ $retval = '';
+ $return = wfShellExec( $cmd, $retval );
+ $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches );
+ if ( $x != 1 ) {
+ wfDebug( __METHOD__ . ": ImageMagick version check failed\n" );
+
+ return null;
+ }
+ $wgMemc->set( "imagemagick-version", $matches[1], 3600 );
+
+ return $matches[1];
+ }
+
+ return $cache;
+ }
+
+ /**
+ * Returns whether the current scaler supports rotation.
+ *
+ * @since 1.24 No longer static
+ * @return bool
+ */
+ public function canRotate() {
+ return false;
+ }
+
+ /**
+ * Should we automatically rotate an image based on exif
+ *
+ * @since 1.24 No longer static
+ * @see $wgEnableAutoRotation
+ * @return bool Whether auto rotation is enabled
+ */
+ public function autoRotateEnabled() {
+ return false;
+ }
+
+ /**
+ * Rotate a thumbnail.
+ *
+ * This is a stub. See BitmapHandler::rotate.
+ *
+ * @param File $file
+ * @param array $params Rotate parameters.
+ * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
+ * @since 1.24 Is non-static. From 1.21 it was static
+ * @return bool
+ */
+ public function rotate( $file, $params ) {
+ return new MediaTransformError( 'thumbnail_error', 0, 0,
+ get_class( $this ) . ' rotation not implemented' );
+ }
+
+ /**
+ * Returns whether the file needs to be rendered. Returns true if the
+ * file requires rotation and we are able to rotate it.
+ *
+ * @param File $file
+ * @return bool
+ */
+ public function mustRender( $file ) {
+ return $this->canRotate() && $this->getRotation( $file ) != 0;
+ }
+}
diff --git a/includes/media/XCF.php b/includes/media/XCF.php
index e77d3842..48b7a47c 100644
--- a/includes/media/XCF.php
+++ b/includes/media/XCF.php
@@ -33,9 +33,8 @@
* @ingroup Media
*/
class XCFHandler extends BitmapHandler {
-
/**
- * @param $file
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -45,9 +44,9 @@ class XCFHandler extends BitmapHandler {
/**
* Render files as PNG
*
- * @param $ext
- * @param $mime
- * @param $params
+ * @param string $ext
+ * @param string $mime
+ * @param array $params
* @return array
*/
function getThumbType( $ext, $mime, $params = null ) {
@@ -57,12 +56,33 @@ class XCFHandler extends BitmapHandler {
/**
* Get width and height from the XCF header.
*
- * @param $image
- * @param $filename
+ * @param File $image
+ * @param string $filename
* @return array
*/
function getImageSize( $image, $filename ) {
- return self::getXCFMetaData( $filename );
+ $header = self::getXCFMetaData( $filename );
+ if ( !$header ) {
+ return false;
+ }
+
+ # Forge a return array containing metadata information just like getimagesize()
+ # See PHP documentation at: http://www.php.net/getimagesize
+ $metadata = array();
+ $metadata[0] = $header['width'];
+ $metadata[1] = $header['height'];
+ $metadata[2] = null; # IMAGETYPE constant, none exist for XCF.
+ $metadata[3] = sprintf(
+ 'height="%s" width="%s"', $header['height'], $header['width']
+ );
+ $metadata['mime'] = 'image/x-xcf';
+ $metadata['channels'] = null;
+ $metadata['bits'] = 8; # Always 8-bits per color
+
+ assert( '7 == count($metadata); ' .
+ '# return array must contains 7 elements just like getimagesize() return' );
+
+ return $metadata;
}
/**
@@ -73,7 +93,7 @@ class XCFHandler extends BitmapHandler {
* @author Hashar
*
* @param string $filename Full path to a XCF file
- * @return bool|array metadata array just like PHP getimagesize()
+ * @return bool|array Metadata Array just like PHP getimagesize()
*/
static function getXCFMetaData( $filename ) {
# Decode master structure
@@ -103,12 +123,12 @@ class XCFHandler extends BitmapHandler {
# (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h)
try {
$header = wfUnpack(
- "A9magic" # A: space padded
- . "/a5version" # a: zero padded
- . "/Nwidth" # \
- . "/Nheight" # N: unsigned long 32bit big endian
- . "/Nbase_type" # /
- , $binaryHeader
+ "A9magic" . # A: space padded
+ "/a5version" . # a: zero padded
+ "/Nwidth" . # \
+ "/Nheight" . # N: unsigned long 32bit big endian
+ "/Nbase_type", # /
+ $binaryHeader
);
} catch ( MWException $mwe ) {
return false;
@@ -117,36 +137,97 @@ class XCFHandler extends BitmapHandler {
# Check values
if ( $header['magic'] !== 'gimp xcf' ) {
wfDebug( __METHOD__ . " '$filename' has invalid magic signature.\n" );
+
return false;
}
# TODO: we might want to check for sane values of width and height
- wfDebug( __METHOD__ . ": canvas size of '$filename' is {$header['width']} x {$header['height']} px\n" );
+ wfDebug( __METHOD__ .
+ ": canvas size of '$filename' is {$header['width']} x {$header['height']} px\n" );
- # Forge a return array containing metadata information just like getimagesize()
- # See PHP documentation at: http://www.php.net/getimagesize
+ return $header;
+ }
+
+ /**
+ * Store the channel type
+ *
+ * Greyscale files need different command line options.
+ *
+ * @param File $file The image object, or false if there isn't one.
+ * Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
+ * @param string $filename The filename
+ * @return string
+ */
+ public function getMetadata( $file, $filename ) {
+ $header = self::getXCFMetadata( $filename );
$metadata = array();
- $metadata[0] = $header['width'];
- $metadata[1] = $header['height'];
- $metadata[2] = null; # IMAGETYPE constant, none exist for XCF.
- $metadata[3] = sprintf(
- 'height="%s" width="%s"', $header['height'], $header['width']
- );
- $metadata['mime'] = 'image/x-xcf';
- $metadata['channels'] = null;
- $metadata['bits'] = 8; # Always 8-bits per color
+ if ( $header ) {
+ // Try to be consistent with the names used by PNG files.
+ // Unclear from base media type if it has an alpha layer,
+ // so just assume that it does since it "potentially" could.
+ switch ( $header['base_type'] ) {
+ case 0:
+ $metadata['colorType'] = 'truecolour-alpha';
+ break;
+ case 1:
+ $metadata['colorType'] = 'greyscale-alpha';
+ break;
+ case 2:
+ $metadata['colorType'] = 'index-coloured';
+ break;
+ default:
+ $metadata['colorType'] = 'unknown';
- assert( '7 == count($metadata); # return array must contains 7 elements just like getimagesize() return' );
+ }
+ } else {
+ // Marker to prevent repeated attempted extraction
+ $metadata['error'] = true;
+ }
+ return serialize( $metadata );
+ }
- return $metadata;
+ /**
+ * Should we refresh the metadata
+ *
+ * @param File $file The file object for the file in question
+ * @param string $metadata Serialized metadata
+ * @return bool One of the self::METADATA_(BAD|GOOD|COMPATIBLE) constants
+ */
+ public function isMetadataValid( $file, $metadata ) {
+ if ( !$metadata ) {
+ // Old metadata when we just put an empty string in there
+ return self::METADATA_BAD;
+ } else {
+ return self::METADATA_GOOD;
+ }
}
/**
* Must use "im" for XCF
*
+ * @param string $dstPath
+ * @param bool $checkDstPath
* @return string
*/
- protected static function getScalerType( $dstPath, $checkDstPath = true ) {
+ protected function getScalerType( $dstPath, $checkDstPath = true ) {
return "im";
}
+
+ /**
+ * Can we render this file?
+ *
+ * Image magick doesn't support indexed xcf files as of current
+ * writing (as of 6.8.9-3)
+ * @param File $file
+ * @return bool
+ */
+ public function canRender( $file ) {
+ wfSuppressWarnings();
+ $xcfMeta = unserialize( $file->getMetadata() );
+ wfRestoreWarnings();
+ if ( isset( $xcfMeta['colorType'] ) && $xcfMeta['colorType'] === 'index-coloured' ) {
+ return false;
+ }
+ return parent::canRender( $file );
+ }
}
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
index 7eb3d19e..cdbd5ab2 100644
--- a/includes/media/XMP.php
+++ b/includes/media/XMP.php
@@ -23,7 +23,7 @@
/**
* Class for reading xmp data containing properties relevant to
- * images, and spitting out an array that FormatExif accepts.
+ * images, and spitting out an array that FormatMetadata accepts.
*
* Note, this is not meant to recognize every possible thing you can
* encode in XMP. It should recognize all the properties we want.
@@ -34,12 +34,12 @@
*
* The public methods one would call in this class are
* - parse( $content )
- * Reads in xmp content.
- * Can potentially be called multiple times with partial data each time.
+ * Reads in xmp content.
+ * Can potentially be called multiple times with partial data each time.
* - parseExtended( $content )
- * Reads XMPExtended blocks (jpeg files only).
+ * Reads XMPExtended blocks (jpeg files only).
* - getResults
- * Outputs a results array.
+ * Outputs a results array.
*
* Note XMP kind of looks like rdf. They are not the same thing - XMP is
* encoded as a specific subset of rdf. This class can read XMP. It cannot
@@ -47,20 +47,38 @@
*
*/
class XMPReader {
+ /** @var array XMP item configuration array */
+ protected $items;
+
+ /** @var array Array to hold the current element (and previous element, and so on) */
+ private $curItem = array();
+
+ /** @var bool|string The structure name when processing nested structures. */
+ private $ancestorStruct = false;
+
+ /** @var bool|string Temporary holder for character data that appears in xmp doc. */
+ private $charContent = false;
+
+ /** @var array Stores the state the xmpreader is in (see MODE_FOO constants) */
+ private $mode = array();
+
+ /** @var array Array to hold results */
+ private $results = array();
+
+ /** @var bool If we're doing a seq or bag. */
+ private $processingArray = false;
- private $curItem = array(); // array to hold the current element (and previous element, and so on)
- private $ancestorStruct = false; // the structure name when processing nested structures.
- private $charContent = false; // temporary holder for character data that appears in xmp doc.
- private $mode = array(); // stores the state the xmpreader is in (see MODE_FOO constants)
- private $results = array(); // array to hold results
- private $processingArray = false; // if we're doing a seq or bag.
- private $itemLang = false; // used for lang alts only
+ /** @var bool|string Used for lang alts only */
+ private $itemLang = false;
+ /** @var resource A resource handle for the XML parser */
private $xmlParser;
+
+ /** @var bool|string Character set like 'UTF-8' */
private $charset = false;
- private $extendedXMPOffset = 0;
- protected $items;
+ /** @var int */
+ private $extendedXMPOffset = 0;
/**
* These are various mode constants.
@@ -105,8 +123,8 @@ class XMPReader {
$this->items = XMPInfo::getItems();
$this->resetXMLParser();
-
}
+
/**
* Main use is if a single item has multiple xmp documents describing it.
* For example in jpeg's with extendedXMP
@@ -141,8 +159,8 @@ class XMPReader {
/** Get the result array. Do some post-processing before returning
* the array, and transform any metadata that is special-cased.
*
- * @return Array array of results as an array of arrays suitable for
- * FormatMetadata::getFormattedData().
+ * @return array Array of results as an array of arrays suitable for
+ * FormatMetadata::getFormattedData().
*/
public function getResults() {
// xmp-special is for metadata that affects how stuff
@@ -155,7 +173,7 @@ class XMPReader {
$data = $this->results;
- wfRunHooks( 'XMPGetResults', Array( &$data ) );
+ wfRunHooks( 'XMPGetResults', array( &$data ) );
if ( isset( $data['xmp-special']['AuthorsPosition'] )
&& is_string( $data['xmp-special']['AuthorsPosition'] )
@@ -237,10 +255,10 @@ class XMPReader {
* debug log, blanks result array and returns false.
*
* @param string $content XMP data
- * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
- * @param $reset Boolean: does xml parser need to be reset. Default false
+ * @param bool $allOfIt If this is all the data (true) or if its split up (false). Default true
+ * @param bool $reset Does xml parser need to be reset. Default false
* @throws MWException
- * @return Boolean success.
+ * @return bool Success.
*/
public function parse( $content, $allOfIt = true, $reset = false ) {
if ( $reset ) {
@@ -301,8 +319,10 @@ class XMPReader {
} catch ( MWException $e ) {
wfDebugLog( 'XMP', 'XMP parse error: ' . $e );
$this->results = array();
+
return false;
}
+
return true;
}
@@ -311,36 +331,43 @@ class XMPReader {
* @todo In serious need of testing
* @see http://www.adobe.ge/devnet/xmp/pdfs/XMPSpecificationPart3.pdf XMP spec part 3 page 20
* @param string $content XMPExtended block minus the namespace signature
- * @return Boolean If it succeeded.
+ * @return bool If it succeeded.
*/
public function parseExtended( $content ) {
// @todo FIXME: This is untested. Hard to find example files
// or programs that make such files..
$guid = substr( $content, 0, 32 );
if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
- || $this->results['xmp-special']['HasExtendedXMP'] !== $guid ) {
- wfDebugLog( 'XMP', __METHOD__ . " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
+ || $this->results['xmp-special']['HasExtendedXMP'] !== $guid
+ ) {
+ wfDebugLog( 'XMP', __METHOD__ .
+ " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
+
return false;
}
$len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
if ( !$len || $len['length'] < 4 || $len['offset'] < 0 || $len['offset'] > $len['length'] ) {
wfDebugLog( 'XMP', __METHOD__ . 'Error reading extended XMP block, invalid length or offset.' );
+
return false;
}
- // we're not very robust here. we should accept it in the wrong order. To quote
- // the xmp standard:
- // "A JPEG writer should write the ExtendedXMP marker segments in order, immediately following the
- // StandardXMP. However, the JPEG standard does not require preservation of marker segment order. A
- // robust JPEG reader should tolerate the marker segments in any order."
+ // we're not very robust here. we should accept it in the wrong order.
+ // To quote the XMP standard:
+ // "A JPEG writer should write the ExtendedXMP marker segments in order,
+ // immediately following the StandardXMP. However, the JPEG standard
+ // does not require preservation of marker segment order. A robust JPEG
+ // reader should tolerate the marker segments in any order."
//
- // otoh the probability that an image will have more than 128k of metadata is rather low...
- // so the probability that it will have > 128k, and be in the wrong order is very low...
+ // otoh the probability that an image will have more than 128k of
+ // metadata is rather low... so the probability that it will have
+ // > 128k, and be in the wrong order is very low...
if ( $len['offset'] !== $this->extendedXMPOffset ) {
wfDebugLog( 'XMP', __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
. $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')' );
+
return false;
}
@@ -361,6 +388,7 @@ class XMPReader {
}
wfDebugLog( 'XMP', __METHOD__ . 'Parsing a XMPExtended block' );
+
return $this->parse( $actualContent, $atEnd );
}
@@ -376,9 +404,9 @@ class XMPReader {
* <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
* and are processing the 0/10 bit.
*
- * @param $parser XMLParser reference to the xml parser
+ * @param XMLParser $parser XMLParser reference to the xml parser
* @param string $data Character data
- * @throws MWException on invalid data
+ * @throws MWException On invalid data
*/
function char( $parser, $data ) {
@@ -407,7 +435,6 @@ class XMPReader {
} else {
$this->charContent .= $data;
}
-
}
/** When we hit a closing element in MODE_IGNORE
@@ -436,7 +463,7 @@ class XMPReader {
* Or it could be if we hit the end element of a property
* of a compound data structure (like a member of an array).
*
- * @param string $elm namespace, space, and tag name.
+ * @param string $elm Namespace, space, and tag name.
*/
private function endElementModeSimple( $elm ) {
if ( $this->charContent !== false ) {
@@ -453,7 +480,6 @@ class XMPReader {
}
array_shift( $this->curItem );
array_shift( $this->mode );
-
}
/**
@@ -471,7 +497,7 @@ class XMPReader {
*
* This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
*
- * @param string $elm namespace . space . tag name.
+ * @param string $elm Namespace . space . tag name.
* @throws MWException
*/
private function endElementNested( $elm ) {
@@ -482,7 +508,8 @@ class XMPReader {
&& !( $elm === self::NS_RDF . ' Description'
&& $this->mode[0] === self::MODE_STRUCT )
) {
- throw new MWException( "nesting mismatch. got a </$elm> but expected a </" . $this->curItem[0] . '>' );
+ throw new MWException( "nesting mismatch. got a </$elm> but expected a </" .
+ $this->curItem[0] . '>' );
}
// Validate structures.
@@ -499,7 +526,6 @@ class XMPReader {
if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
// This can happen if all the members of the struct failed validation.
wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> has no valid members." );
-
} elseif ( is_callable( $validate ) ) {
$val =& $this->results['xmp-' . $info['map_group']][$finalName];
call_user_func_array( $validate, array( $info, &$val, false ) );
@@ -538,7 +564,7 @@ class XMPReader {
* (For comparison, we call endElementModeSimple when we
* hit the "</rdf:li>")
*
- * @param string $elm namespace . ' ' . element name
+ * @param string $elm Namespace . ' ' . element name
* @throws MWException
*/
private function endElementModeLi( $elm ) {
@@ -552,6 +578,7 @@ class XMPReader {
if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
wfDebugLog( 'XMP', __METHOD__ . " Empty compund element $finalName." );
+
return;
}
@@ -564,7 +591,6 @@ class XMPReader {
if ( $info['mode'] === self::MODE_LANG ) {
$this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
}
-
} else {
throw new MWException( __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm." );
}
@@ -578,13 +604,14 @@ class XMPReader {
* Qualifiers aren't all that common, and we don't do anything
* with them.
*
- * @param string $elm namespace and element
+ * @param string $elm Namespace and element
*/
private function endElementModeQDesc( $elm ) {
if ( $elm === self::NS_RDF . ' value' ) {
list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
$this->saveValue( $ns, $tag, $this->charContent );
+
return;
} else {
array_shift( $this->mode );
@@ -601,15 +628,15 @@ class XMPReader {
* Ignores the outer wrapping elements that are optional in
* xmp and have no meaning.
*
- * @param $parser XMLParser
- * @param string $elm namespace . ' ' . element name
+ * @param XMLParser $parser
+ * @param string $elm Namespace . ' ' . element name
* @throws MWException
*/
function endElement( $parser, $elm ) {
if ( $elm === ( self::NS_RDF . ' RDF' )
|| $elm === 'adobe:ns:meta/ xmpmeta'
- || $elm === 'adobe:ns:meta/ xapmeta' )
- {
+ || $elm === 'adobe:ns:meta/ xapmeta'
+ ) {
// ignore these.
return;
}
@@ -626,6 +653,7 @@ class XMPReader {
// that forgets the namespace on some things.
// (Luckily they are unimportant things).
wfDebugLog( 'XMP', __METHOD__ . " Encountered </$elm> which has no namespace. Skipping." );
+
return;
}
@@ -684,7 +712,7 @@ class XMPReader {
* in which case we add it to the item stack, so we can ignore things
* that are nested, correctly.
*
- * @param string $elm namespace . ' ' . tag name
+ * @param string $elm Namespace . ' ' . tag name
*/
private function startElementModeIgnore( $elm ) {
if ( $elm === $this->curItem[0] ) {
@@ -697,8 +725,8 @@ class XMPReader {
* Start element in MODE_BAG (unordered array)
* this should always be <rdf:Bag>
*
- * @param string $elm namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Bag>
+ * @param string $elm Namespace . ' ' . tag
+ * @throws MWException If we have an element that's not <rdf:Bag>
*/
private function startElementModeBag( $elm ) {
if ( $elm === self::NS_RDF . ' Bag' ) {
@@ -706,15 +734,14 @@ class XMPReader {
} else {
throw new MWException( "Expected <rdf:Bag> but got $elm." );
}
-
}
/**
* Start element in MODE_SEQ (ordered array)
* this should always be <rdf:Seq>
*
- * @param string $elm namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Seq>
+ * @param string $elm Namespace . ' ' . tag
+ * @throws MWException If we have an element that's not <rdf:Seq>
*/
private function startElementModeSeq( $elm ) {
if ( $elm === self::NS_RDF . ' Seq' ) {
@@ -727,7 +754,6 @@ class XMPReader {
} else {
throw new MWException( "Expected <rdf:Seq> but got $elm." );
}
-
}
/**
@@ -741,8 +767,8 @@ class XMPReader {
* which are really only used for thumbnails, which
* we don't care about.
*
- * @param string $elm namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Alt>
+ * @param string $elm Namespace . ' ' . tag
+ * @throws MWException If we have an element that's not <rdf:Alt>
*/
private function startElementModeLang( $elm ) {
if ( $elm === self::NS_RDF . ' Alt' ) {
@@ -750,7 +776,6 @@ class XMPReader {
} else {
throw new MWException( "Expected <rdf:Seq> but got $elm." );
}
-
}
/**
@@ -767,7 +792,7 @@ class XMPReader {
*
* This method is called when processing the <rdf:Description> element
*
- * @param string $elm namespace and tag names separated by space.
+ * @param string $elm Namespace and tag names separated by space.
* @param array $attribs Attributes of the element.
* @throws MWException
*/
@@ -784,15 +809,14 @@ class XMPReader {
} elseif ( $elm === self::NS_RDF . ' value' ) {
// This should not be here.
throw new MWException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
-
} else {
// something else we don't recognize, like a qualifier maybe.
- wfDebugLog( 'XMP', __METHOD__ . " Encountered element <$elm> where only expecting character data as value of " . $this->curItem[0] );
+ wfDebugLog( 'XMP', __METHOD__ .
+ " Encountered element <$elm> where only expecting character data as value of " .
+ $this->curItem[0] );
array_unshift( $this->mode, self::MODE_IGNORE );
array_unshift( $this->curItem, $elm );
-
}
-
}
/**
@@ -806,7 +830,7 @@ class XMPReader {
* </exif:DigitalZoomRatio>
* Called when processing the <rdf:value> or <foo:someQualifier>.
*
- * @param string $elm namespace and tag name separated by a space.
+ * @param string $elm Namespace and tag name separated by a space.
*
*/
private function startElementModeQDesc( $elm ) {
@@ -827,8 +851,8 @@ class XMPReader {
* This is generally where most properties start.
*
* @param string $ns Namespace
- * @param string $tag tag name (without namespace prefix)
- * @param array $attribs array of attributes
+ * @param string $tag Tag name (without namespace prefix)
+ * @param array $attribs Array of attributes
* @throws MWException
*/
private function startElementModeInitial( $ns, $tag, $attribs ) {
@@ -846,6 +870,7 @@ class XMPReader {
array_unshift( $this->mode, self::MODE_IGNORE );
array_unshift( $this->curItem, $ns . ' ' . $tag );
+
return;
}
$mode = $this->items[$ns][$tag]['mode'];
@@ -865,9 +890,9 @@ class XMPReader {
wfDebugLog( 'XMP', __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
array_unshift( $this->mode, self::MODE_IGNORE );
array_unshift( $this->curItem, $ns . ' ' . $tag );
+
return;
}
-
}
// process attributes
$this->doAttribs( $attribs );
@@ -887,9 +912,9 @@ class XMPReader {
* <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
* <exif:Mode>1</exif:Mode></exif:Flash>
*
- * @param string $ns namespace
- * @param string $tag tag name (no ns)
- * @param array $attribs array of attribs w/ values.
+ * @param string $ns Namespace
+ * @param string $tag Tag name (no ns)
+ * @param array $attribs Array of attribs w/ values.
* @throws MWException
*/
private function startElementModeStruct( $ns, $tag, $attribs ) {
@@ -897,8 +922,8 @@ class XMPReader {
if ( isset( $this->items[$ns][$tag] ) ) {
if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
- && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] ) )
- {
+ && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
+ ) {
// This assumes that we don't have inter-namespace nesting
// which we don't in all the properties we're interested in.
throw new MWException( " <$tag> appeared nested in <" . $this->ancestorStruct
@@ -909,14 +934,15 @@ class XMPReader {
if ( $this->charContent !== false ) {
// Something weird.
// Should not happen in valid XMP.
- throw new MWException( "tag <$tag> nested in non-whitespace characters (" . $this->charContent . ")." );
+ throw new MWException( "tag <$tag> nested in non-whitespace characters (" .
+ $this->charContent . ")." );
}
} else {
array_unshift( $this->mode, self::MODE_IGNORE );
array_unshift( $this->curItem, $elm );
+
return;
}
-
}
if ( $ns === self::NS_RDF && $tag === 'Description' ) {
@@ -935,9 +961,9 @@ class XMPReader {
* </rdf:Seq> </exif:ISOSpeedRatings>
* This method is called when we hit the <rdf:li> element.
*
- * @param string $elm namespace . ' ' . tagname
+ * @param string $elm Namespace . ' ' . tagname
* @param array $attribs Attributes. (needed for BAGSTRUCTS)
- * @throws MWException if gets a tag other than <rdf:li>
+ * @throws MWException If gets a tag other than <rdf:li>
*/
private function startElementModeLi( $elm, $attribs ) {
if ( ( $elm ) !== self::NS_RDF . ' li' ) {
@@ -965,7 +991,6 @@ class XMPReader {
? $this->items[$curNS][$curTag]['map_name'] : $curTag;
$this->doAttribs( $attribs );
-
} else {
// Normal BAG or SEQ containing simple values.
array_unshift( $this->mode, self::MODE_SIMPLE );
@@ -974,7 +999,6 @@ class XMPReader {
array_unshift( $this->curItem, $this->curItem[0] );
$this->processingArray = true;
}
-
}
/**
@@ -987,17 +1011,17 @@ class XMPReader {
*
* This method is called when we hit the <rdf:li> element.
*
- * @param string $elm namespace . ' ' . tag
- * @param array $attribs array of elements (most importantly xml:lang)
- * @throws MWException if gets a tag other than <rdf:li> or if no xml:lang
+ * @param string $elm Namespace . ' ' . tag
+ * @param array $attribs Array of elements (most importantly xml:lang)
+ * @throws MWException If gets a tag other than <rdf:li> or if no xml:lang
*/
private function startElementModeLiLang( $elm, $attribs ) {
if ( $elm !== self::NS_RDF . ' li' ) {
throw new MWException( __METHOD__ . " <rdf:li> expected but got $elm." );
}
if ( !isset( $attribs[self::NS_XML . ' lang'] )
- || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] ) )
- {
+ || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
+ ) {
throw new MWException( __METHOD__
. " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
}
@@ -1017,17 +1041,17 @@ class XMPReader {
* Generally just calls a helper based on what MODE we're in.
* Also does some initial set up for the wrapper element
*
- * @param $parser XMLParser
- * @param string $elm namespace "<space>" element
- * @param array $attribs attribute name => value
+ * @param XMLParser $parser
+ * @param string $elm Namespace "<space>" element
+ * @param array $attribs Attribute name => value
* @throws MWException
*/
function startElement( $parser, $elm, $attribs ) {
if ( $elm === self::NS_RDF . ' RDF'
|| $elm === 'adobe:ns:meta/ xmpmeta'
- || $elm === 'adobe:ns:meta/ xapmeta' )
- {
+ || $elm === 'adobe:ns:meta/ xapmeta'
+ ) {
/* ignore. */
return;
} elseif ( $elm === self::NS_RDF . ' Description' ) {
@@ -1049,6 +1073,7 @@ class XMPReader {
if ( strpos( $elm, ' ' ) === false ) {
// This probably shouldn't happen.
wfDebugLog( 'XMP', __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
+
return;
}
@@ -1104,23 +1129,24 @@ class XMPReader {
* Often the initial "<rdf:Description>" tag just has all the simple
* properties as attributes.
*
+ * @codingStandardsIgnoreStart Long line that cannot be broken
* @par Example:
* @code
* <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
* @endcode
+ * @codingStandardsIgnoreEnd
*
- * @param array $attribs attribute=>value array.
+ * @param array $attribs Array attribute=>value
* @throws MWException
*/
private function doAttribs( $attribs ) {
-
// first check for rdf:parseType attribute, as that can change
// how the attributes are interperted.
if ( isset( $attribs[self::NS_RDF . ' parseType'] )
&& $attribs[self::NS_RDF . ' parseType'] === 'Resource'
- && $this->mode[0] === self::MODE_SIMPLE )
- {
+ && $this->mode[0] === self::MODE_SIMPLE
+ ) {
// this is equivalent to having an inner rdf:Description
$this->mode[0] = self::MODE_QDESC;
}
@@ -1158,9 +1184,9 @@ class XMPReader {
* $this->processingArray to determine what name to
* save the value under. (in addition to $tag).
*
- * @param string $ns namespace of tag this is for
- * @param string $tag tag name
- * @param string $val value to save
+ * @param string $ns Namespace of tag this is for
+ * @param string $tag Tag name
+ * @param string $val Value to save
*/
private function saveValue( $ns, $tag, $val ) {
@@ -1177,6 +1203,7 @@ class XMPReader {
// is to be consistent between here and validating structures.
if ( is_null( $val ) ) {
wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> failed validation." );
+
return;
}
} else {
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
index f0b2cb5e..7e47ec14 100644
--- a/includes/media/XMPInfo.php
+++ b/includes/media/XMPInfo.php
@@ -27,17 +27,17 @@
* extract.
*/
class XMPInfo {
-
- /** get the items array
- * @return Array XMP item configuration array.
+ /** Get the items array
+ * @return array XMP item configuration array.
*/
public static function getItems() {
if ( !self::$ranHooks ) {
// This is for if someone makes a custom metadata extension.
// For example, a medical wiki might want to decode DICOM xmp properties.
- wfRunHooks( 'XMPGetInfo', Array( &self::$items ) );
+ wfRunHooks( 'XMPGetInfo', array( &self::$items ) );
self::$ranHooks = true; // Only want to do this once.
}
+
return self::$items;
}
@@ -53,382 +53,388 @@ class XMPInfo {
* each containing an array of tags
* each tag is an array of information about the
* tag, including:
- * * map_group - what group (used for precedence during conflicts)
- * * mode - What type of item (self::MODE_SIMPLE usually, see above for all values)
- * * validate - method to validate input. Could also post-process the input. A string value is assumed to be a static method of XMPValidate. Can also take a array( 'className', 'methodName' ).
- * * choices - array of potential values (format of 'value' => true ). Only used with validateClosed
- * * rangeLow and rangeHigh - alternative to choices for numeric ranges. Again for validateClosed only.
- * * children - for MODE_STRUCT items, allowed children.
- * * structPart - Indicates that this element can only appear as a member of a structure.
+ * * map_group - What group (used for precedence during conflicts).
+ * * mode - What type of item (self::MODE_SIMPLE usually, see above for
+ * all values).
+ * * validate - Method to validate input. Could also post-process the
+ * input. A string value is assumed to be a static method of
+ * XMPValidate. Can also take a array( 'className', 'methodName' ).
+ * * choices - Array of potential values (format of 'value' => true ).
+ * Only used with validateClosed.
+ * * rangeLow and rangeHigh - Alternative to choices for numeric ranges.
+ * Again for validateClosed only.
+ * * children - For MODE_STRUCT items, allowed children.
+ * * structPart - Indicates that this element can only appear as a member
+ * of a structure.
*
- * currently this just has a bunch of exif values as this class is only half-done
+ * Currently this just has a bunch of EXIF values as this class is only half-done.
*/
static private $items = array(
'http://ns.adobe.com/exif/1.0/' => array(
'ApertureValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'BrightnessValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'CompressedBitsPerPixel' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'DigitalZoomRatio' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'ExposureBiasValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'ExposureIndex' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'ExposureTime' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'FlashEnergy' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
),
'FNumber' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'FocalLength' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'FocalPlaneXResolution' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'FocalPlaneYResolution' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSAltitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
),
'GPSDestBearing' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSDestDistance' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSDOP' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSImgDirection' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSSpeed' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSTrack' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
- 'MaxApertureValue' => array(
+ 'MaxApertureValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'ShutterSpeedValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
- 'SubjectDistance' => array(
+ 'SubjectDistance' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
/* Flash */
- 'Flash' => array(
- 'mode' => XMPReader::MODE_STRUCT,
- 'children' => array(
- 'Fired' => true,
- 'Function' => true,
- 'Mode' => true,
+ 'Flash' => array(
+ 'mode' => XMPReader::MODE_STRUCT,
+ 'children' => array(
+ 'Fired' => true,
+ 'Function' => true,
+ 'Mode' => true,
'RedEyeMode' => true,
- 'Return' => true,
+ 'Return' => true,
),
- 'validate' => 'validateFlash',
+ 'validate' => 'validateFlash',
'map_group' => 'exif',
),
- 'Fired' => array(
+ 'Fired' => array(
'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
- 'Function' => array(
+ 'Function' => array(
'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
- 'Mode' => array(
+ 'Mode' => array(
'map_group' => 'exif',
- 'validate' => 'validateClosed',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'choices' => array( '0' => true, '1' => true,
- '2' => true, '3' => true ),
- 'structPart'=> true,
+ 'validate' => 'validateClosed',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'choices' => array( '0' => true, '1' => true,
+ '2' => true, '3' => true ),
+ 'structPart' => true,
),
- 'Return' => array(
+ 'Return' => array(
'map_group' => 'exif',
- 'validate' => 'validateClosed',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'choices' => array( '0' => true,
- '2' => true, '3' => true ),
- 'structPart'=> true,
+ 'validate' => 'validateClosed',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'choices' => array( '0' => true,
+ '2' => true, '3' => true ),
+ 'structPart' => true,
),
- 'RedEyeMode' => array(
+ 'RedEyeMode' => array(
'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
/* End Flash */
- 'ISOSpeedRatings' => array(
+ 'ISOSpeedRatings' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger'
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger'
),
/* end rational things */
- 'ColorSpace' => array(
+ 'ColorSpace' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '65535' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '65535' => true ),
),
- 'ComponentsConfiguration' => array(
+ 'ComponentsConfiguration' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true,
- '5' => true, '6' => true )
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true,
+ '5' => true, '6' => true )
),
- 'Contrast' => array(
+ 'Contrast' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true, '2' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true, '2' => true )
),
- 'CustomRendered' => array(
+ 'CustomRendered' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true )
),
- 'DateTimeOriginal' => array(
+ 'DateTimeOriginal' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
),
'DateTimeDigitized' => array( /* xmp:CreateDate */
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
),
/* todo: there might be interesting information in
* exif:DeviceSettingDescription, but need to find an
* example
*/
- 'ExifVersion' => array(
+ 'ExifVersion' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'ExposureMode' => array(
+ 'ExposureMode' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 2,
),
- 'ExposureProgram' => array(
+ 'ExposureProgram' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 8,
),
- 'FileSource' => array(
+ 'FileSource' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '3' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '3' => true )
),
- 'FlashpixVersion' => array(
+ 'FlashpixVersion' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'FocalLengthIn35mmFilm' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
'FocalPlaneResolutionUnit' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '2' => true, '3' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true ),
),
- 'GainControl' => array(
+ 'GainControl' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 4,
),
/* this value is post-processed out later */
- 'GPSAltitudeRef' => array(
+ 'GPSAltitudeRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true ),
),
'GPSAreaInformation' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'GPSDestBearingRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'T' => true, 'M' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true ),
),
'GPSDestDistanceRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'K' => true, 'M' => true,
- 'N' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'K' => true, 'M' => true,
+ 'N' => true ),
),
- 'GPSDestLatitude' => array(
+ 'GPSDestLatitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
),
- 'GPSDestLongitude' => array(
+ 'GPSDestLongitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
),
- 'GPSDifferential' => array(
+ 'GPSDifferential' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true ),
),
'GPSImgDirectionRef' => array(
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'T' => true, 'M' => true ),
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true ),
),
- 'GPSLatitude' => array(
+ 'GPSLatitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
),
- 'GPSLongitude' => array(
+ 'GPSLongitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
),
- 'GPSMapDatum' => array(
+ 'GPSMapDatum' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'GPSMeasureMode' => array(
+ 'GPSMeasureMode' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '2' => true, '3' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true )
),
'GPSProcessingMethod' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'GPSSatellites' => array(
+ 'GPSSatellites' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'GPSSpeedRef' => array(
+ 'GPSSpeedRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'K' => true, 'M' => true,
- 'N' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'K' => true, 'M' => true,
+ 'N' => true ),
),
- 'GPSStatus' => array(
+ 'GPSStatus' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'A' => true, 'V' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'A' => true, 'V' => true )
),
- 'GPSTimeStamp' => array(
+ 'GPSTimeStamp' => array(
'map_group' => 'exif',
// Note: in exif, GPSDateStamp does not include
// the time, where here it does.
- 'map_name' => 'GPSDateStamp',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
+ 'map_name' => 'GPSDateStamp',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
),
- 'GPSTrackRef' => array(
+ 'GPSTrackRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'T' => true, 'M' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true )
),
- 'GPSVersionID' => array(
+ 'GPSVersionID' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'ImageUniqueID' => array(
+ 'ImageUniqueID' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'LightSource' => array(
+ 'LightSource' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
/* can't use a range, as it skips... */
- 'choices' => array( '0' => true, '1' => true,
+ 'choices' => array( '0' => true, '1' => true,
'2' => true, '3' => true, '4' => true,
'9' => true, '10' => true, '11' => true,
'12' => true, '13' => true,
@@ -440,217 +446,217 @@ class XMPInfo {
'255' => true,
),
),
- 'MeteringMode' => array(
+ 'MeteringMode' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 6,
- 'choices' => array( '255' => true ),
+ 'choices' => array( '255' => true ),
),
/* Pixel(X|Y)Dimension are rather useless, but for
* completeness since we do it with exif.
*/
- 'PixelXDimension' => array(
+ 'PixelXDimension' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'PixelYDimension' => array(
+ 'PixelYDimension' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'Saturation' => array(
+ 'Saturation' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 2,
),
- 'SceneCaptureType' => array(
+ 'SceneCaptureType' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 3,
),
- 'SceneType' => array(
+ 'SceneType' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true ),
),
// Note, 6 is not valid SensingMethod.
- 'SensingMethod' => array(
+ 'SensingMethod' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 1,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 1,
'rangeHigh' => 5,
- 'choices' => array( '7' => true, 8 => true ),
+ 'choices' => array( '7' => true, 8 => true ),
),
- 'Sharpness' => array(
+ 'Sharpness' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 2,
),
'SpectralSensitivity' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
// This tag should perhaps be displayed to user better.
- 'SubjectArea' => array(
+ 'SubjectArea' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
),
'SubjectDistanceRange' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 3,
),
- 'SubjectLocation' => array(
+ 'SubjectLocation' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
),
- 'UserComment' => array(
+ 'UserComment' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
+ 'mode' => XMPReader::MODE_LANG,
),
- 'WhiteBalance' => array(
+ 'WhiteBalance' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true )
),
),
'http://ns.adobe.com/tiff/1.0/' => array(
- 'Artist' => array(
+ 'Artist' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'BitsPerSample' => array(
+ 'BitsPerSample' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
),
- 'Compression' => array(
+ 'Compression' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '6' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '6' => true ),
),
/* this prop should not be used in XMP. dc:rights is the correct prop */
- 'Copyright' => array(
+ 'Copyright' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
+ 'mode' => XMPReader::MODE_LANG,
),
- 'DateTime' => array( /* proper prop is xmp:ModifyDate */
+ 'DateTime' => array( /* proper prop is xmp:ModifyDate */
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
),
- 'ImageDescription' => array( /* proper one is dc:description */
+ 'ImageDescription' => array( /* proper one is dc:description */
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
+ 'mode' => XMPReader::MODE_LANG,
),
- 'ImageLength' => array(
+ 'ImageLength' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'ImageWidth' => array(
+ 'ImageWidth' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'Make' => array(
+ 'Make' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'Model' => array(
+ 'Model' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
/**** Do not extract this property
* It interferes with auto exif rotation.
* 'Orientation' => array(
- * 'map_group' => 'exif',
- * 'mode' => XMPReader::MODE_SIMPLE,
- * 'validate' => 'validateClosed',
- * 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
- * '6' => true, '7' => true, '8' => true ),
+ * 'map_group' => 'exif',
+ * 'mode' => XMPReader::MODE_SIMPLE,
+ * 'validate' => 'validateClosed',
+ * 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
+ * '6' => true, '7' => true, '8' => true ),
*),
******/
'PhotometricInterpretation' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '2' => true, '6' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '6' => true ),
),
'PlanerConfiguration' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '2' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true ),
),
'PrimaryChromaticities' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
),
'ReferenceBlackWhite' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
),
- 'ResolutionUnit' => array(
+ 'ResolutionUnit' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '2' => true, '3' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true ),
),
- 'SamplesPerPixel' => array(
+ 'SamplesPerPixel' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'Software' => array( /* see xmp:CreatorTool */
+ 'Software' => array( /* see xmp:CreatorTool */
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
/* ignore TransferFunction */
- 'WhitePoint' => array(
+ 'WhitePoint' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
),
- 'XResolution' => array(
+ 'XResolution' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
),
- 'YResolution' => array(
+ 'YResolution' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
),
'YCbCrCoefficients' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
),
- 'YCbCrPositioning' => array(
+ 'YCbCrPositioning' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '2' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true ),
),
/********
* Disable extracting this property (bug 31944)
@@ -663,170 +669,170 @@ class XMPInfo {
* just disable this prop for now, until such
* XMPReader is more graceful (bug 32172)
* 'YCbCrSubSampling' => array(
- * 'map_group' => 'exif',
- * 'mode' => XMPReader::MODE_SEQ,
- * 'validate' => 'validateClosed',
- * 'choices' => array( '1' => true, '2' => true ),
+ * 'map_group' => 'exif',
+ * 'mode' => XMPReader::MODE_SEQ,
+ * 'validate' => 'validateClosed',
+ * 'choices' => array( '1' => true, '2' => true ),
* ),
*/
),
'http://ns.adobe.com/exif/1.0/aux/' => array(
- 'Lens' => array(
+ 'Lens' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'SerialNumber' => array(
+ 'SerialNumber' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'OwnerName' => array(
+ 'OwnerName' => array(
'map_group' => 'exif',
- 'map_name' => 'CameraOwnerName',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CameraOwnerName',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
),
'http://purl.org/dc/elements/1.1/' => array(
- 'title' => array(
+ 'title' => array(
'map_group' => 'general',
- 'map_name' => 'ObjectName',
- 'mode' => XMPReader::MODE_LANG
+ 'map_name' => 'ObjectName',
+ 'mode' => XMPReader::MODE_LANG
),
- 'description' => array(
+ 'description' => array(
'map_group' => 'general',
- 'map_name' => 'ImageDescription',
- 'mode' => XMPReader::MODE_LANG
+ 'map_name' => 'ImageDescription',
+ 'mode' => XMPReader::MODE_LANG
),
- 'contributor' => array(
+ 'contributor' => array(
'map_group' => 'general',
- 'map_name' => 'dc-contributor',
- 'mode' => XMPReader::MODE_BAG
+ 'map_name' => 'dc-contributor',
+ 'mode' => XMPReader::MODE_BAG
),
- 'coverage' => array(
+ 'coverage' => array(
'map_group' => 'general',
- 'map_name' => 'dc-coverage',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'dc-coverage',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'creator' => array(
+ 'creator' => array(
'map_group' => 'general',
- 'map_name' => 'Artist', //map with exif Artist, iptc byline (2:80)
- 'mode' => XMPReader::MODE_SEQ,
+ 'map_name' => 'Artist', //map with exif Artist, iptc byline (2:80)
+ 'mode' => XMPReader::MODE_SEQ,
),
- 'date' => array(
+ 'date' => array(
'map_group' => 'general',
// Note, not mapped with other date properties, as this type of date is
// non-specific: "A point or period of time associated with an event in
// the lifecycle of the resource"
- 'map_name' => 'dc-date',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateDate',
+ 'map_name' => 'dc-date',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateDate',
),
- /* Do not extract dc:format, as we've got better ways to determine mimetype */
- 'identifier' => array(
+ /* Do not extract dc:format, as we've got better ways to determine MIME type */
+ 'identifier' => array(
'map_group' => 'deprecated',
- 'map_name' => 'Identifier',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'Identifier',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'language' => array(
+ 'language' => array(
'map_group' => 'general',
- 'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
- 'mode' => XMPReader::MODE_BAG,
- 'validate' => 'validateLangCode',
+ 'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
+ 'mode' => XMPReader::MODE_BAG,
+ 'validate' => 'validateLangCode',
),
- 'publisher' => array(
+ 'publisher' => array(
'map_group' => 'general',
- 'map_name' => 'dc-publisher',
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'dc-publisher',
+ 'mode' => XMPReader::MODE_BAG,
),
// for related images/resources
- 'relation' => array(
+ 'relation' => array(
'map_group' => 'general',
- 'map_name' => 'dc-relation',
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'dc-relation',
+ 'mode' => XMPReader::MODE_BAG,
),
- 'rights' => array(
+ 'rights' => array(
'map_group' => 'general',
- 'map_name' => 'Copyright',
- 'mode' => XMPReader::MODE_LANG,
+ 'map_name' => 'Copyright',
+ 'mode' => XMPReader::MODE_LANG,
),
// Note: source is not mapped with iptc source, since iptc
// source describes the source of the image in terms of a person
// who provided the image, where this is to describe an image that the
// current one is based on.
- 'source' => array(
+ 'source' => array(
'map_group' => 'general',
- 'map_name' => 'dc-source',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'dc-source',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'subject' => array(
+ 'subject' => array(
'map_group' => 'general',
- 'map_name' => 'Keywords', /* maps to iptc 2:25 */
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'Keywords', /* maps to iptc 2:25 */
+ 'mode' => XMPReader::MODE_BAG,
),
- 'type' => array(
+ 'type' => array(
'map_group' => 'general',
- 'map_name' => 'dc-type',
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'dc-type',
+ 'mode' => XMPReader::MODE_BAG,
),
),
'http://ns.adobe.com/xap/1.0/' => array(
'CreateDate' => array(
'map_group' => 'general',
'map_name' => 'DateTimeDigitized',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
'validate' => 'validateDate',
),
'CreatorTool' => array(
'map_group' => 'general',
- 'map_name' => 'Software',
- 'mode' => XMPReader::MODE_SIMPLE
+ 'map_name' => 'Software',
+ 'mode' => XMPReader::MODE_SIMPLE
),
'Identifier' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
+ 'mode' => XMPReader::MODE_BAG,
),
'Label' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'ModifyDate' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'DateTime',
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'DateTime',
+ 'validate' => 'validateDate',
),
'MetadataDate' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
// map_name to be consistent with other date names.
- 'map_name' => 'DateTimeMetadata',
- 'validate' => 'validateDate',
+ 'map_name' => 'DateTimeMetadata',
+ 'validate' => 'validateDate',
),
'Nickname' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Rating' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRating',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRating',
),
),
'http://ns.adobe.com/xap/1.0/rights/' => array(
'Certificate' => array(
'map_group' => 'general',
- 'map_name' => 'RightsCertificate',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'RightsCertificate',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Marked' => array(
'map_group' => 'general',
- 'map_name' => 'Copyrighted',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateBoolean',
+ 'map_name' => 'Copyrighted',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateBoolean',
),
'Owner' => array(
'map_group' => 'general',
- 'map_name' => 'CopyrightOwner',
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'CopyrightOwner',
+ 'mode' => XMPReader::MODE_BAG,
),
// this seems similar to dc:rights.
'UsageTerms' => array(
@@ -844,7 +850,7 @@ class XMPInfo {
// as well do this too.
'OriginalDocumentID' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
// It might also be useful to do xmpMM:LastURL
// and xmpMM:DerivedFrom as you can potentially,
@@ -855,31 +861,31 @@ class XMPInfo {
),
'http://creativecommons.org/ns#' => array(
'license' => array(
- 'map_name' => 'LicenseUrl',
+ 'map_name' => 'LicenseUrl',
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'morePermissions' => array(
- 'map_name' => 'MorePermissionsUrl',
+ 'map_name' => 'MorePermissionsUrl',
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'attributionURL' => array(
'map_group' => 'general',
- 'map_name' => 'AttributionUrl',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'AttributionUrl',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'attributionName' => array(
'map_group' => 'general',
- 'map_name' => 'PreferredAttributionName',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'PreferredAttributionName',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
),
//Note, this property affects how jpeg metadata is extracted.
'http://ns.adobe.com/xmp/note/' => array(
'HasExtendedXMP' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
),
/* Note, in iptc schemas, the legacy properties are denoted
@@ -890,41 +896,41 @@ class XMPInfo {
'http://ns.adobe.com/photoshop/1.0/' => array(
'City' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CityDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CityDest',
),
'Country' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CountryDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CountryDest',
),
'State' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'ProvinceOrStateDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'ProvinceOrStateDest',
),
'DateCreated' => array(
'map_group' => 'deprecated',
// marking as deprecated as the xmp prop preferred
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'DateTimeOriginal',
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'DateTimeOriginal',
+ 'validate' => 'validateDate',
// note this prop is an XMP, not IPTC date
),
'CaptionWriter' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'Writer',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'Writer',
),
'Instructions' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'SpecialInstructions',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'SpecialInstructions',
),
'TransmissionReference' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'OriginalTransmissionRef',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'OriginalTransmissionRef',
),
'AuthorsPosition' => array(
/* This corresponds with 2:85
@@ -932,46 +938,46 @@ class XMPInfo {
* handled weirdly to correspond
* with iptc/exif. */
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE
+ 'mode' => XMPReader::MODE_SIMPLE
),
'Credit' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Source' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Urgency' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Category' => array(
// Note, this prop is deprecated, but in general
// group since it doesn't have a replacement.
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'iimCategory',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'iimCategory',
),
'SupplementalCategories' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'iimSupplementalCategory',
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'iimSupplementalCategory',
),
'Headline' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE
+ 'mode' => XMPReader::MODE_SIMPLE
),
),
'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' => array(
'CountryCode' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CountryCodeDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CountryCodeDest',
),
'IntellectualGenre' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
// Note, this is a six digit code.
// See: http://cv.iptc.org/newscodes/scene/
@@ -979,9 +985,9 @@ class XMPInfo {
// we just show the number.
'Scene' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'validate' => 'validateInteger',
- 'map_name' => 'SceneCode',
+ 'mode' => XMPReader::MODE_BAG,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'SceneCode',
),
/* Note: SubjectCode should be an 8 ascii digits.
* it is not really an integer (has leading 0's,
@@ -990,14 +996,14 @@ class XMPInfo {
*/
'SubjectCode' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'SubjectNewsCode',
- 'validate' => 'validateInteger'
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'SubjectNewsCode',
+ 'validate' => 'validateInteger'
),
'Location' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'SublocationDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'SublocationDest',
),
'CreatorContactInfo' => array(
/* Note this maps to 2:118 in iim
@@ -1007,94 +1013,94 @@ class XMPInfo {
* is more structured.
*/
'map_group' => 'general',
- 'mode' => XMPReader::MODE_STRUCT,
- 'map_name' => 'Contact',
- 'children' => array(
+ 'mode' => XMPReader::MODE_STRUCT,
+ 'map_name' => 'Contact',
+ 'children' => array(
'CiAdrExtadr' => true,
- 'CiAdrCity' => true,
- 'CiAdrCtry' => true,
+ 'CiAdrCity' => true,
+ 'CiAdrCtry' => true,
'CiEmailWork' => true,
- 'CiTelWork' => true,
- 'CiAdrPcode' => true,
+ 'CiTelWork' => true,
+ 'CiAdrPcode' => true,
'CiAdrRegion' => true,
- 'CiUrlWork' => true,
+ 'CiUrlWork' => true,
),
),
'CiAdrExtadr' => array( /* address */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiAdrCity' => array( /* city */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiAdrCtry' => array( /* country */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiEmailWork' => array( /* email (possibly separated by ',') */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiTelWork' => array( /* telephone */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiAdrPcode' => array( /* postal code */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiAdrRegion' => array( /* province/state */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiUrlWork' => array( /* url. Multiple may be separated by comma. */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
/* End contact info struct properties */
),
'http://iptc.org/std/Iptc4xmpExt/2008-02-29/' => array(
'Event' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'OrganisationInImageName' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'OrganisationInImage'
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'OrganisationInImage'
),
'PersonInImage' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
+ 'mode' => XMPReader::MODE_BAG,
),
'MaxAvailHeight' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- 'map_name' => 'OriginalImageHeight',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'OriginalImageHeight',
),
'MaxAvailWidth' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- 'map_name' => 'OriginalImageWidth',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'OriginalImageWidth',
),
// LocationShown and LocationCreated are handled
// specially because they are hierarchical, but we
// also want to merge with the old non-hierarchical.
'LocationShown' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_BAGSTRUCT,
- 'children' => array(
+ 'mode' => XMPReader::MODE_BAGSTRUCT,
+ 'children' => array(
'WorldRegion' => true,
'CountryCode' => true, /* iso code */
'CountryName' => true,
@@ -1105,8 +1111,8 @@ class XMPInfo {
),
'LocationCreated' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_BAGSTRUCT,
- 'children' => array(
+ 'mode' => XMPReader::MODE_BAGSTRUCT,
+ 'children' => array(
'WorldRegion' => true,
'CountryCode' => true, /* iso code */
'CountryName' => true,
@@ -1117,35 +1123,35 @@ class XMPInfo {
),
'WorldRegion' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CountryCode' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CountryName' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
- 'map_name' => 'Country',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
+ 'map_name' => 'Country',
),
'ProvinceState' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
- 'map_name' => 'ProvinceOrState',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
+ 'map_name' => 'ProvinceOrState',
),
'City' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'Sublocation' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
/* Other props that might be interesting but
diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php
index 87f8abfe..0fa60117 100644
--- a/includes/media/XMPValidate.php
+++ b/includes/media/XMPValidate.php
@@ -28,7 +28,7 @@
* Each of these functions take the same parameters
* * an info array which is a subset of the XMPInfo::items array
* * A value (passed as reference) to validate. This can be either a
- * simple value or an array
+ * simple value or an array
* * A boolean to determine if this is validating a simple or complex values
*
* It should be noted that when an array is being validated, typically the validation
@@ -42,11 +42,11 @@
*/
class XMPValidate {
/**
- * function to validate boolean properties ( True or False )
+ * Function to validate boolean properties ( True or False )
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateBoolean( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -57,15 +57,14 @@ class XMPValidate {
wfDebugLog( 'XMP', __METHOD__ . " Expected True or False but got $val" );
$val = null;
}
-
}
/**
* function to validate rational properties ( 12/10 )
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateRational( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -76,7 +75,6 @@ class XMPValidate {
wfDebugLog( 'XMP', __METHOD__ . " Expected rational but got $val" );
$val = null;
}
-
}
/**
@@ -85,9 +83,9 @@ class XMPValidate {
* if its outside of range put it into range.
*
* @see MWG spec
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateRating( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -99,6 +97,7 @@ class XMPValidate {
) {
wfDebugLog( 'XMP', __METHOD__ . " Expected rating but got $val" );
$val = null;
+
return;
} else {
$nVal = (float)$val;
@@ -108,11 +107,13 @@ class XMPValidate {
// as -1 is meant as a special reject rating.
wfDebugLog( 'XMP', __METHOD__ . " Rating too low, setting to -1 (Rejected)" );
$val = '-1';
+
return;
}
if ( $nVal > 5 ) {
wfDebugLog( 'XMP', __METHOD__ . " Rating too high, setting to 5" );
$val = '5';
+
return;
}
}
@@ -121,9 +122,9 @@ class XMPValidate {
/**
* function to validate integers
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateInteger( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -134,16 +135,15 @@ class XMPValidate {
wfDebugLog( 'XMP', __METHOD__ . " Expected integer but got $val" );
$val = null;
}
-
}
/**
* function to validate properties with a fixed number of allowed
* choices. (closed choice)
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateClosed( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -171,9 +171,9 @@ class XMPValidate {
/**
* function to validate and modify flash structure
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateFlash( $info, &$val, $standalone ) {
if ( $standalone ) {
@@ -205,9 +205,9 @@ class XMPValidate {
* @see rfc 3066
* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf page 30 (section 8.2.2.5)
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateLangCode( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -219,7 +219,6 @@ class XMPValidate {
wfDebugLog( 'XMP', __METHOD__ . " Expected Lang code but got $val" );
$val = null;
}
-
}
/**
@@ -233,11 +232,11 @@ class XMPValidate {
* YYYY-MM-DDThh:mm:ssTZD
* YYYY-MM-DDThh:mm:ss.sTZD
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate. Converts to TS_EXIF as a side-effect.
- * in cases where there's only a partial date, it will give things like
- * 2011:04.
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate. Converts to TS_EXIF as a side-effect.
+ * in cases where there's only a partial date, it will give things like
+ * 2011:04.
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateDate( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -245,11 +244,14 @@ class XMPValidate {
return;
}
$res = array();
+ // @codingStandardsIgnoreStart Long line that cannot be broken
if ( !preg_match(
/* ahh! scary regex... */
'/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D',
$val, $res )
) {
+ // @codingStandardsIgnoreEnd
+
wfDebugLog( 'XMP', __METHOD__ . " Expected date but got $val" );
$val = null;
} else {
@@ -270,6 +272,7 @@ class XMPValidate {
if ( $res[1] === '0000' ) {
wfDebugLog( 'XMP', __METHOD__ . " Invalid date (year 0): $val" );
$val = null;
+
return;
}
@@ -282,6 +285,7 @@ class XMPValidate {
if ( isset( $res[3] ) ) {
$val .= ':' . $res[3];
}
+
return;
}
@@ -292,6 +296,7 @@ class XMPValidate {
if ( isset( $res[6] ) && $res[6] !== '' ) {
$val .= ':' . $res[6];
}
+
return;
}
@@ -320,7 +325,6 @@ class XMPValidate {
$val = substr( $val, 0, -3 );
}
}
-
}
/** function to validate, and more importantly
@@ -330,10 +334,10 @@ class XMPValidate {
* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf
* section 1.2.7.4 on page 23
*
- * @param array $info unused (info about prop)
- * @param &$val String GPS string in either DDD,MM,SSk or
- * or DDD,MM.mmk form
- * @param $standalone Boolean if its a simple prop (should always be true)
+ * @param array $info Unused (info about prop)
+ * @param string &$val GPS string in either DDD,MM,SSk or
+ * or DDD,MM.mmk form
+ * @param bool $standalone If its a simple prop (should always be true)
*/
public static function validateGPS( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -352,6 +356,7 @@ class XMPValidate {
$coord = -$coord;
}
$val = $coord;
+
return;
} elseif ( preg_match(
'/(\d{1,3}),(\d{1,2}(?:.\d*)?)([NWSE])/D',
@@ -363,12 +368,13 @@ class XMPValidate {
$coord = -$coord;
}
$val = $coord;
- return;
+ return;
} else {
wfDebugLog( 'XMP', __METHOD__
. " Expected GPSCoordinate, but got $val." );
$val = null;
+
return;
}
}
diff --git a/includes/mime.info b/includes/mime.info
index c7981871..07b24954 100644
--- a/includes/mime.info
+++ b/includes/mime.info
@@ -1,5 +1,5 @@
-# Mime type info file.
-# the first mime type in each line is the "main" mime type,
+# MIME type info file.
+# the first MIME type in each line is the "main" MIME type,
# the others are aliases for this type
# the media type is given in upper case and square brackets,
# like [BITMAP], and must indicate a media type as defined by
@@ -13,7 +13,7 @@ image/jpeg [BITMAP]
image/jp2 [BITMAP]
image/xbm [BITMAP]
image/tiff [BITMAP]
-image/x-icon image/x-ico [BITMAP]
+image/x-icon image/x-ico image/vnd.microsoft.icon [BITMAP]
image/x-rgb [BITMAP]
image/x-portable-pixmap [BITMAP]
image/x-portable-graymap image/x-portable-greymap [BITMAP]
@@ -65,6 +65,9 @@ text/plain [TEXT]
text/html application/xhtml+xml [TEXT]
application/xml text/xml [TEXT]
text [TEXT]
+application/json [TEXT]
+text/csv [TEXT]
+text/tab-separated-values [TEXT]
application/zip application/x-zip [ARCHIVE]
application/x-gzip [ARCHIVE]
@@ -108,3 +111,8 @@ application/vnd.ms-excel.template.macroEnabled.12 [OFFICE]
application/vnd.ms-excel.addin.macroEnabled.12 [OFFICE]
application/vnd.ms-excel.sheet.binary.macroEnabled.12 [OFFICE]
application/acad application/x-acad application/autocad_dwg image/x-dwg application/dwg application/x-dwg application/x-autocad image/vnd.dwg drawing/dwg [DRAWING]
+chemical/x-mdl-molfile [DRAWING]
+chemical/x-mdl-sdfile [DRAWING]
+chemical/x-mdl-rxnfile [DRAWING]
+chemical/x-mdl-rdfile [DRAWING]
+chemical/x-mdl-rgfile [DRAWING]
diff --git a/includes/mime.types b/includes/mime.types
index 61f7ff58..75017db2 100644
--- a/includes/mime.types
+++ b/includes/mime.types
@@ -35,6 +35,7 @@ application/x-gzip gz
application/x-hdf hdf
application/x-jar jar
application/x-javascript js
+application/json json
application/x-koan skp skd skt skm
application/x-latex latex
application/x-netcdf nc cdf
@@ -90,6 +91,7 @@ image/png png apng
image/svg+xml svg
image/tiff tiff tif
image/vnd.djvu djvu djv
+image/vnd.microsoft.icon ico
image/vnd.wap.wbmp wbmp
image/webp webp
image/x-cmu-raster ras
@@ -109,6 +111,7 @@ model/mesh msh mesh silo
model/vrml wrl vrml
text/calendar ics ifb
text/css css
+text/csv csv
text/html html htm
text/plain txt
text/richtext rtx
@@ -169,3 +172,8 @@ application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlsb
model/vnd.dwfx+xps dwfx
application/vnd.ms-xpsdocument xps
application/x-opc+zip docx dotx docm dotm potx ppsx pptx ppam pptm potm ppsm xlsx xltx xlsm xltm xlam xlsb dwfx xps
+chemical/x-mdl-molfile mol
+chemical/x-mdl-sdfile sdf
+chemical/x-mdl-rxnfile rxn
+chemical/x-mdl-rdfile rd
+chemical/x-mdl-rgfile rg
diff --git a/includes/normal/Makefile b/includes/normal/Makefile
index 66348ee3..76cb68ba 100644
--- a/includes/normal/Makefile
+++ b/includes/normal/Makefile
@@ -16,14 +16,11 @@ PHP=php
FETCH=wget
#FETCH=fetch
-all : UtfNormalData.inc Utf8Case.php
+all : UtfNormalData.inc
UtfNormalData.inc : UtfNormalGenerate.php UtfNormalUtil.php UnicodeData.txt CompositionExclusions.txt NormalizationCorrections.txt DerivedNormalizationProps.txt
$(PHP) UtfNormalGenerate.php
-Utf8Case.php : Utf8CaseGenerate.php UtfNormalUtil.php UnicodeData.txt
- $(PHP) Utf8CaseGenerate.php
-
test : testutf8 UtfNormalTest.php UtfNormalData.inc NormalizationTest.txt
$(PHP) UtfNormalTest.php
diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php
index 06029868..0604d7bb 100644
--- a/includes/normal/RandomTest.php
+++ b/includes/normal/RandomTest.php
@@ -5,7 +5,7 @@
* difference. Will run forever until it finds one or you kill it.
*
* Copyright (C) 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -26,7 +26,7 @@
* @ingroup UtfNormal
*/
-if( PHP_SAPI != 'cli' ) {
+if ( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
@@ -40,8 +40,9 @@ dl( 'php_utfnormal.so' );
function randomString( $length, $nullOk, $ascii = false ) {
$out = '';
- for( $i = 0; $i < $length; $i++ )
+ for ( $i = 0; $i < $length; $i++ )
$out .= chr( mt_rand( $nullOk ? 0 : 1, $ascii ? 127 : 255 ) );
+
return $out;
}
@@ -66,7 +67,7 @@ function showDiffs( $a, $b ) {
$funky = $formatter->format( $diffs );
$matches = array();
preg_match_all( '/<(?:ins|del) class="diffchange">(.*?)<\/(?:ins|del)>/', $funky, $matches );
- foreach( $matches[1] as $bit ) {
+ foreach ( $matches[1] as $bit ) {
$hex = bin2hex( $bit );
echo "\t$hex\n";
}
@@ -74,28 +75,27 @@ function showDiffs( $a, $b ) {
$size = 16;
$n = 0;
-while( true ) {
+while ( true ) {
$n++;
echo "$n\n";
- $str = randomString( $size, true);
+ $str = randomString( $size, true );
$clean = UtfNormal::cleanUp( $str );
$norm = donorm( $str );
echo strlen( $clean ) . ", " . strlen( $norm );
- if( $clean == $norm ) {
+ if ( $clean == $norm ) {
echo " (match)\n";
} else {
echo " (FAIL)\n";
echo "\traw: " . bin2hex( $str ) . "\n" .
- "\tphp: " . bin2hex( $clean ) . "\n" .
- "\ticu: " . bin2hex( $norm ) . "\n";
+ "\tphp: " . bin2hex( $clean ) . "\n" .
+ "\ticu: " . bin2hex( $norm ) . "\n";
echo "\n\tdiffs:\n";
showDiffs( $clean, $norm );
die();
}
-
$str = '';
$clean = '';
$norm = '';
diff --git a/includes/normal/Utf8Case.php b/includes/normal/Utf8Case.php
deleted file mode 100644
index abc56e4c..00000000
--- a/includes/normal/Utf8Case.php
+++ /dev/null
@@ -1,2109 +0,0 @@
-<?php
-/**
- * Simple 1:1 upper/lowercase switching arrays for utf-8 text.
- * Won't get context-sensitive things yet.
- *
- * Hack for bugs in ucfirst() and company
- *
- * These are pulled from memcached if possible, as this is faster than filling
- * up a big array manually.
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * Translation array to get upper case character
- */
-$wikiUpperChars = array(
- 'a' => 'A',
- 'b' => 'B',
- 'c' => 'C',
- 'd' => 'D',
- 'e' => 'E',
- 'f' => 'F',
- 'g' => 'G',
- 'h' => 'H',
- 'i' => 'I',
- 'j' => 'J',
- 'k' => 'K',
- 'l' => 'L',
- 'm' => 'M',
- 'n' => 'N',
- 'o' => 'O',
- 'p' => 'P',
- 'q' => 'Q',
- 'r' => 'R',
- 's' => 'S',
- 't' => 'T',
- 'u' => 'U',
- 'v' => 'V',
- 'w' => 'W',
- 'x' => 'X',
- 'y' => 'Y',
- 'z' => 'Z',
- 'µ' => 'Μ',
- 'à' => 'À',
- 'á' => 'Á',
- 'â' => 'Â',
- 'ã' => 'Ã',
- 'ä' => 'Ä',
- 'å' => 'Å',
- 'æ' => 'Æ',
- 'ç' => 'Ç',
- 'è' => 'È',
- 'é' => 'É',
- 'ê' => 'Ê',
- 'ë' => 'Ë',
- 'ì' => 'Ì',
- 'í' => 'Í',
- 'î' => 'Î',
- 'ï' => 'Ï',
- 'ð' => 'Ð',
- 'ñ' => 'Ñ',
- 'ò' => 'Ò',
- 'ó' => 'Ó',
- 'ô' => 'Ô',
- 'õ' => 'Õ',
- 'ö' => 'Ö',
- 'ø' => 'Ø',
- 'ù' => 'Ù',
- 'ú' => 'Ú',
- 'û' => 'Û',
- 'ü' => 'Ü',
- 'ý' => 'Ý',
- 'þ' => 'Þ',
- 'ÿ' => 'Ÿ',
- 'ā' => 'Ā',
- 'ă' => 'Ă',
- 'ą' => 'Ą',
- 'ć' => 'Ć',
- 'ĉ' => 'Ĉ',
- 'ċ' => 'Ċ',
- 'č' => 'Č',
- 'ď' => 'Ď',
- 'đ' => 'Đ',
- 'ē' => 'Ē',
- 'ĕ' => 'Ĕ',
- 'ė' => 'Ė',
- 'ę' => 'Ę',
- 'ě' => 'Ě',
- 'ĝ' => 'Ĝ',
- 'ğ' => 'Ğ',
- 'ġ' => 'Ġ',
- 'ģ' => 'Ģ',
- 'ĥ' => 'Ĥ',
- 'ħ' => 'Ħ',
- 'ĩ' => 'Ĩ',
- 'ī' => 'Ī',
- 'ĭ' => 'Ĭ',
- 'į' => 'Į',
- 'ı' => 'I',
- 'ij' => 'IJ',
- 'ĵ' => 'Ĵ',
- 'ķ' => 'Ķ',
- 'ĺ' => 'Ĺ',
- 'ļ' => 'Ļ',
- 'ľ' => 'Ľ',
- 'ŀ' => 'Ŀ',
- 'ł' => 'Ł',
- 'ń' => 'Ń',
- 'ņ' => 'Ņ',
- 'ň' => 'Ň',
- 'ŋ' => 'Ŋ',
- 'ō' => 'Ō',
- 'ŏ' => 'Ŏ',
- 'ő' => 'Ő',
- 'œ' => 'Œ',
- 'ŕ' => 'Ŕ',
- 'ŗ' => 'Ŗ',
- 'ř' => 'Ř',
- 'ś' => 'Ś',
- 'ŝ' => 'Ŝ',
- 'ş' => 'Ş',
- 'š' => 'Š',
- 'ţ' => 'Ţ',
- 'ť' => 'Ť',
- 'ŧ' => 'Ŧ',
- 'ũ' => 'Ũ',
- 'ū' => 'Ū',
- 'ŭ' => 'Ŭ',
- 'ů' => 'Ů',
- 'ű' => 'Ű',
- 'ų' => 'Ų',
- 'ŵ' => 'Ŵ',
- 'ŷ' => 'Ŷ',
- 'ź' => 'Ź',
- 'ż' => 'Ż',
- 'ž' => 'Ž',
- 'ſ' => 'S',
- 'ƀ' => 'Ƀ',
- 'ƃ' => 'Ƃ',
- 'ƅ' => 'Ƅ',
- 'ƈ' => 'Ƈ',
- 'ƌ' => 'Ƌ',
- 'ƒ' => 'Ƒ',
- 'ƕ' => 'Ƕ',
- 'ƙ' => 'Ƙ',
- 'ƚ' => 'Ƚ',
- 'ƞ' => 'Ƞ',
- 'ơ' => 'Ơ',
- 'ƣ' => 'Ƣ',
- 'ƥ' => 'Ƥ',
- 'ƨ' => 'Ƨ',
- 'ƭ' => 'Ƭ',
- 'ư' => 'Ư',
- 'ƴ' => 'Ƴ',
- 'ƶ' => 'Ƶ',
- 'ƹ' => 'Ƹ',
- 'ƽ' => 'Ƽ',
- 'ƿ' => 'Ƿ',
- 'Dž' => 'DŽ',
- 'dž' => 'DŽ',
- 'Lj' => 'LJ',
- 'lj' => 'LJ',
- 'Nj' => 'NJ',
- 'nj' => 'NJ',
- 'ǎ' => 'Ǎ',
- 'ǐ' => 'Ǐ',
- 'ǒ' => 'Ǒ',
- 'ǔ' => 'Ǔ',
- 'ǖ' => 'Ǖ',
- 'ǘ' => 'Ǘ',
- 'ǚ' => 'Ǚ',
- 'ǜ' => 'Ǜ',
- 'ǝ' => 'Ǝ',
- 'ǟ' => 'Ǟ',
- 'ǡ' => 'Ǡ',
- 'ǣ' => 'Ǣ',
- 'ǥ' => 'Ǥ',
- 'ǧ' => 'Ǧ',
- 'ǩ' => 'Ǩ',
- 'ǫ' => 'Ǫ',
- 'ǭ' => 'Ǭ',
- 'ǯ' => 'Ǯ',
- 'Dz' => 'DZ',
- 'dz' => 'DZ',
- 'ǵ' => 'Ǵ',
- 'ǹ' => 'Ǹ',
- 'ǻ' => 'Ǻ',
- 'ǽ' => 'Ǽ',
- 'ǿ' => 'Ǿ',
- 'ȁ' => 'Ȁ',
- 'ȃ' => 'Ȃ',
- 'ȅ' => 'Ȅ',
- 'ȇ' => 'Ȇ',
- 'ȉ' => 'Ȉ',
- 'ȋ' => 'Ȋ',
- 'ȍ' => 'Ȍ',
- 'ȏ' => 'Ȏ',
- 'ȑ' => 'Ȑ',
- 'ȓ' => 'Ȓ',
- 'ȕ' => 'Ȕ',
- 'ȗ' => 'Ȗ',
- 'ș' => 'Ș',
- 'ț' => 'Ț',
- 'ȝ' => 'Ȝ',
- 'ȟ' => 'Ȟ',
- 'ȣ' => 'Ȣ',
- 'ȥ' => 'Ȥ',
- 'ȧ' => 'Ȧ',
- 'ȩ' => 'Ȩ',
- 'ȫ' => 'Ȫ',
- 'ȭ' => 'Ȭ',
- 'ȯ' => 'Ȯ',
- 'ȱ' => 'Ȱ',
- 'ȳ' => 'Ȳ',
- 'ȼ' => 'Ȼ',
- 'ȿ' => 'Ȿ',
- 'ɀ' => 'Ɀ',
- 'ɂ' => 'Ɂ',
- 'ɇ' => 'Ɇ',
- 'ɉ' => 'Ɉ',
- 'ɋ' => 'Ɋ',
- 'ɍ' => 'Ɍ',
- 'ɏ' => 'Ɏ',
- 'ɐ' => 'Ɐ',
- 'ɑ' => 'Ɑ',
- 'ɒ' => 'Ɒ',
- 'ɓ' => 'Ɓ',
- 'ɔ' => 'Ɔ',
- 'ɖ' => 'Ɖ',
- 'ɗ' => 'Ɗ',
- 'ə' => 'Ə',
- 'ɛ' => 'Ɛ',
- 'ɠ' => 'Ɠ',
- 'ɣ' => 'Ɣ',
- 'ɥ' => 'Ɥ',
- 'ɨ' => 'Ɨ',
- 'ɩ' => 'Ɩ',
- 'ɫ' => 'Ɫ',
- 'ɯ' => 'Ɯ',
- 'ɱ' => 'Ɱ',
- 'ɲ' => 'Ɲ',
- 'ɵ' => 'Ɵ',
- 'ɽ' => 'Ɽ',
- 'ʀ' => 'Ʀ',
- 'ʃ' => 'Ʃ',
- 'ʈ' => 'Ʈ',
- 'ʉ' => 'Ʉ',
- 'ʊ' => 'Ʊ',
- 'ʋ' => 'Ʋ',
- 'ʌ' => 'Ʌ',
- 'ʒ' => 'Ʒ',
- 'ͅ' => 'Ι',
- 'ͱ' => 'Ͱ',
- 'ͳ' => 'Ͳ',
- 'ͷ' => 'Ͷ',
- 'ͻ' => 'Ͻ',
- 'ͼ' => 'Ͼ',
- 'ͽ' => 'Ͽ',
- 'ά' => 'Ά',
- 'έ' => 'Έ',
- 'ή' => 'Ή',
- 'ί' => 'Ί',
- 'α' => 'Α',
- 'β' => 'Β',
- 'γ' => 'Γ',
- 'δ' => 'Δ',
- 'ε' => 'Ε',
- 'ζ' => 'Ζ',
- 'η' => 'Η',
- 'θ' => 'Θ',
- 'ι' => 'Ι',
- 'κ' => 'Κ',
- 'λ' => 'Λ',
- 'μ' => 'Μ',
- 'ν' => 'Ν',
- 'ξ' => 'Ξ',
- 'ο' => 'Ο',
- 'π' => 'Π',
- 'ρ' => 'Ρ',
- 'ς' => 'Σ',
- 'σ' => 'Σ',
- 'τ' => 'Τ',
- 'υ' => 'Υ',
- 'φ' => 'Φ',
- 'χ' => 'Χ',
- 'ψ' => 'Ψ',
- 'ω' => 'Ω',
- 'ϊ' => 'Ϊ',
- 'ϋ' => 'Ϋ',
- 'ό' => 'Ό',
- 'ύ' => 'Ύ',
- 'ώ' => 'Ώ',
- 'ϐ' => 'Β',
- 'ϑ' => 'Θ',
- 'ϕ' => 'Φ',
- 'ϖ' => 'Π',
- 'ϗ' => 'Ϗ',
- 'ϙ' => 'Ϙ',
- 'ϛ' => 'Ϛ',
- 'ϝ' => 'Ϝ',
- 'ϟ' => 'Ϟ',
- 'ϡ' => 'Ϡ',
- 'ϣ' => 'Ϣ',
- 'ϥ' => 'Ϥ',
- 'ϧ' => 'Ϧ',
- 'ϩ' => 'Ϩ',
- 'ϫ' => 'Ϫ',
- 'ϭ' => 'Ϭ',
- 'ϯ' => 'Ϯ',
- 'ϰ' => 'Κ',
- 'ϱ' => 'Ρ',
- 'ϲ' => 'Ϲ',
- 'ϵ' => 'Ε',
- 'ϸ' => 'Ϸ',
- 'ϻ' => 'Ϻ',
- 'а' => 'А',
- 'б' => 'Б',
- 'в' => 'В',
- 'г' => 'Г',
- 'д' => 'Д',
- 'е' => 'Е',
- 'ж' => 'Ж',
- 'з' => 'З',
- 'и' => 'И',
- 'й' => 'Й',
- 'к' => 'К',
- 'л' => 'Л',
- 'м' => 'М',
- 'н' => 'Н',
- 'о' => 'О',
- 'п' => 'П',
- 'р' => 'Р',
- 'с' => 'С',
- 'т' => 'Т',
- 'у' => 'У',
- 'ф' => 'Ф',
- 'х' => 'Х',
- 'ц' => 'Ц',
- 'ч' => 'Ч',
- 'ш' => 'Ш',
- 'щ' => 'Щ',
- 'ъ' => 'Ъ',
- 'ы' => 'Ы',
- 'ь' => 'Ь',
- 'э' => 'Э',
- 'ю' => 'Ю',
- 'я' => 'Я',
- 'ѐ' => 'Ѐ',
- 'ё' => 'Ё',
- 'ђ' => 'Ђ',
- 'ѓ' => 'Ѓ',
- 'є' => 'Є',
- 'ѕ' => 'Ѕ',
- 'і' => 'І',
- 'ї' => 'Ї',
- 'ј' => 'Ј',
- 'љ' => 'Љ',
- 'њ' => 'Њ',
- 'ћ' => 'Ћ',
- 'ќ' => 'Ќ',
- 'ѝ' => 'Ѝ',
- 'ў' => 'Ў',
- 'џ' => 'Џ',
- 'ѡ' => 'Ѡ',
- 'ѣ' => 'Ѣ',
- 'ѥ' => 'Ѥ',
- 'ѧ' => 'Ѧ',
- 'ѩ' => 'Ѩ',
- 'ѫ' => 'Ѫ',
- 'ѭ' => 'Ѭ',
- 'ѯ' => 'Ѯ',
- 'ѱ' => 'Ѱ',
- 'ѳ' => 'Ѳ',
- 'ѵ' => 'Ѵ',
- 'ѷ' => 'Ѷ',
- 'ѹ' => 'Ѹ',
- 'ѻ' => 'Ѻ',
- 'ѽ' => 'Ѽ',
- 'ѿ' => 'Ѿ',
- 'ҁ' => 'Ҁ',
- 'ҋ' => 'Ҋ',
- 'ҍ' => 'Ҍ',
- 'ҏ' => 'Ҏ',
- 'ґ' => 'Ґ',
- 'ғ' => 'Ғ',
- 'ҕ' => 'Ҕ',
- 'җ' => 'Җ',
- 'ҙ' => 'Ҙ',
- 'қ' => 'Қ',
- 'ҝ' => 'Ҝ',
- 'ҟ' => 'Ҟ',
- 'ҡ' => 'Ҡ',
- 'ң' => 'Ң',
- 'ҥ' => 'Ҥ',
- 'ҧ' => 'Ҧ',
- 'ҩ' => 'Ҩ',
- 'ҫ' => 'Ҫ',
- 'ҭ' => 'Ҭ',
- 'ү' => 'Ү',
- 'ұ' => 'Ұ',
- 'ҳ' => 'Ҳ',
- 'ҵ' => 'Ҵ',
- 'ҷ' => 'Ҷ',
- 'ҹ' => 'Ҹ',
- 'һ' => 'Һ',
- 'ҽ' => 'Ҽ',
- 'ҿ' => 'Ҿ',
- 'ӂ' => 'Ӂ',
- 'ӄ' => 'Ӄ',
- 'ӆ' => 'Ӆ',
- 'ӈ' => 'Ӈ',
- 'ӊ' => 'Ӊ',
- 'ӌ' => 'Ӌ',
- 'ӎ' => 'Ӎ',
- 'ӏ' => 'Ӏ',
- 'ӑ' => 'Ӑ',
- 'ӓ' => 'Ӓ',
- 'ӕ' => 'Ӕ',
- 'ӗ' => 'Ӗ',
- 'ә' => 'Ә',
- 'ӛ' => 'Ӛ',
- 'ӝ' => 'Ӝ',
- 'ӟ' => 'Ӟ',
- 'ӡ' => 'Ӡ',
- 'ӣ' => 'Ӣ',
- 'ӥ' => 'Ӥ',
- 'ӧ' => 'Ӧ',
- 'ө' => 'Ө',
- 'ӫ' => 'Ӫ',
- 'ӭ' => 'Ӭ',
- 'ӯ' => 'Ӯ',
- 'ӱ' => 'Ӱ',
- 'ӳ' => 'Ӳ',
- 'ӵ' => 'Ӵ',
- 'ӷ' => 'Ӷ',
- 'ӹ' => 'Ӹ',
- 'ӻ' => 'Ӻ',
- 'ӽ' => 'Ӽ',
- 'ӿ' => 'Ӿ',
- 'ԁ' => 'Ԁ',
- 'ԃ' => 'Ԃ',
- 'ԅ' => 'Ԅ',
- 'ԇ' => 'Ԇ',
- 'ԉ' => 'Ԉ',
- 'ԋ' => 'Ԋ',
- 'ԍ' => 'Ԍ',
- 'ԏ' => 'Ԏ',
- 'ԑ' => 'Ԑ',
- 'ԓ' => 'Ԓ',
- 'ԕ' => 'Ԕ',
- 'ԗ' => 'Ԗ',
- 'ԙ' => 'Ԙ',
- 'ԛ' => 'Ԛ',
- 'ԝ' => 'Ԝ',
- 'ԟ' => 'Ԟ',
- 'ԡ' => 'Ԡ',
- 'ԣ' => 'Ԣ',
- 'ԥ' => 'Ԥ',
- 'ԧ' => 'Ԧ',
- 'ա' => 'Ա',
- 'բ' => 'Բ',
- 'գ' => 'Գ',
- 'դ' => 'Դ',
- 'ե' => 'Ե',
- 'զ' => 'Զ',
- 'է' => 'Է',
- 'ը' => 'Ը',
- 'թ' => 'Թ',
- 'ժ' => 'Ժ',
- 'ի' => 'Ի',
- 'լ' => 'Լ',
- 'խ' => 'Խ',
- 'ծ' => 'Ծ',
- 'կ' => 'Կ',
- 'հ' => 'Հ',
- 'ձ' => 'Ձ',
- 'ղ' => 'Ղ',
- 'ճ' => 'Ճ',
- 'մ' => 'Մ',
- 'յ' => 'Յ',
- 'ն' => 'Ն',
- 'շ' => 'Շ',
- 'ո' => 'Ո',
- 'չ' => 'Չ',
- 'պ' => 'Պ',
- 'ջ' => 'Ջ',
- 'ռ' => 'Ռ',
- 'ս' => 'Ս',
- 'վ' => 'Վ',
- 'տ' => 'Տ',
- 'ր' => 'Ր',
- 'ց' => 'Ց',
- 'ւ' => 'Ւ',
- 'փ' => 'Փ',
- 'ք' => 'Ք',
- 'օ' => 'Օ',
- 'ֆ' => 'Ֆ',
- 'ᵹ' => 'Ᵹ',
- 'ᵽ' => 'Ᵽ',
- 'ḁ' => 'Ḁ',
- 'ḃ' => 'Ḃ',
- 'ḅ' => 'Ḅ',
- 'ḇ' => 'Ḇ',
- 'ḉ' => 'Ḉ',
- 'ḋ' => 'Ḋ',
- 'ḍ' => 'Ḍ',
- 'ḏ' => 'Ḏ',
- 'ḑ' => 'Ḑ',
- 'ḓ' => 'Ḓ',
- 'ḕ' => 'Ḕ',
- 'ḗ' => 'Ḗ',
- 'ḙ' => 'Ḙ',
- 'ḛ' => 'Ḛ',
- 'ḝ' => 'Ḝ',
- 'ḟ' => 'Ḟ',
- 'ḡ' => 'Ḡ',
- 'ḣ' => 'Ḣ',
- 'ḥ' => 'Ḥ',
- 'ḧ' => 'Ḧ',
- 'ḩ' => 'Ḩ',
- 'ḫ' => 'Ḫ',
- 'ḭ' => 'Ḭ',
- 'ḯ' => 'Ḯ',
- 'ḱ' => 'Ḱ',
- 'ḳ' => 'Ḳ',
- 'ḵ' => 'Ḵ',
- 'ḷ' => 'Ḷ',
- 'ḹ' => 'Ḹ',
- 'ḻ' => 'Ḻ',
- 'ḽ' => 'Ḽ',
- 'ḿ' => 'Ḿ',
- 'ṁ' => 'Ṁ',
- 'ṃ' => 'Ṃ',
- 'ṅ' => 'Ṅ',
- 'ṇ' => 'Ṇ',
- 'ṉ' => 'Ṉ',
- 'ṋ' => 'Ṋ',
- 'ṍ' => 'Ṍ',
- 'ṏ' => 'Ṏ',
- 'ṑ' => 'Ṑ',
- 'ṓ' => 'Ṓ',
- 'ṕ' => 'Ṕ',
- 'ṗ' => 'Ṗ',
- 'ṙ' => 'Ṙ',
- 'ṛ' => 'Ṛ',
- 'ṝ' => 'Ṝ',
- 'ṟ' => 'Ṟ',
- 'ṡ' => 'Ṡ',
- 'ṣ' => 'Ṣ',
- 'ṥ' => 'Ṥ',
- 'ṧ' => 'Ṧ',
- 'ṩ' => 'Ṩ',
- 'ṫ' => 'Ṫ',
- 'ṭ' => 'Ṭ',
- 'ṯ' => 'Ṯ',
- 'ṱ' => 'Ṱ',
- 'ṳ' => 'Ṳ',
- 'ṵ' => 'Ṵ',
- 'ṷ' => 'Ṷ',
- 'ṹ' => 'Ṹ',
- 'ṻ' => 'Ṻ',
- 'ṽ' => 'Ṽ',
- 'ṿ' => 'Ṿ',
- 'ẁ' => 'Ẁ',
- 'ẃ' => 'Ẃ',
- 'ẅ' => 'Ẅ',
- 'ẇ' => 'Ẇ',
- 'ẉ' => 'Ẉ',
- 'ẋ' => 'Ẋ',
- 'ẍ' => 'Ẍ',
- 'ẏ' => 'Ẏ',
- 'ẑ' => 'Ẑ',
- 'ẓ' => 'Ẓ',
- 'ẕ' => 'Ẕ',
- 'ẛ' => 'Ṡ',
- 'ạ' => 'Ạ',
- 'ả' => 'Ả',
- 'ấ' => 'Ấ',
- 'ầ' => 'Ầ',
- 'ẩ' => 'Ẩ',
- 'ẫ' => 'Ẫ',
- 'ậ' => 'Ậ',
- 'ắ' => 'Ắ',
- 'ằ' => 'Ằ',
- 'ẳ' => 'Ẳ',
- 'ẵ' => 'Ẵ',
- 'ặ' => 'Ặ',
- 'ẹ' => 'Ẹ',
- 'ẻ' => 'Ẻ',
- 'ẽ' => 'Ẽ',
- 'ế' => 'Ế',
- 'ề' => 'Ề',
- 'ể' => 'Ể',
- 'ễ' => 'Ễ',
- 'ệ' => 'Ệ',
- 'ỉ' => 'Ỉ',
- 'ị' => 'Ị',
- 'ọ' => 'Ọ',
- 'ỏ' => 'Ỏ',
- 'ố' => 'Ố',
- 'ồ' => 'Ồ',
- 'ổ' => 'Ổ',
- 'ỗ' => 'Ỗ',
- 'ộ' => 'Ộ',
- 'ớ' => 'Ớ',
- 'ờ' => 'Ờ',
- 'ở' => 'Ở',
- 'ỡ' => 'Ỡ',
- 'ợ' => 'Ợ',
- 'ụ' => 'Ụ',
- 'ủ' => 'Ủ',
- 'ứ' => 'Ứ',
- 'ừ' => 'Ừ',
- 'ử' => 'Ử',
- 'ữ' => 'Ữ',
- 'ự' => 'Ự',
- 'ỳ' => 'Ỳ',
- 'ỵ' => 'Ỵ',
- 'ỷ' => 'Ỷ',
- 'ỹ' => 'Ỹ',
- 'ỻ' => 'Ỻ',
- 'ỽ' => 'Ỽ',
- 'ỿ' => 'Ỿ',
- 'ἀ' => 'Ἀ',
- 'ἁ' => 'Ἁ',
- 'ἂ' => 'Ἂ',
- 'ἃ' => 'Ἃ',
- 'ἄ' => 'Ἄ',
- 'ἅ' => 'Ἅ',
- 'ἆ' => 'Ἆ',
- 'ἇ' => 'Ἇ',
- 'ἐ' => 'Ἐ',
- 'ἑ' => 'Ἑ',
- 'ἒ' => 'Ἒ',
- 'ἓ' => 'Ἓ',
- 'ἔ' => 'Ἔ',
- 'ἕ' => 'Ἕ',
- 'ἠ' => 'Ἠ',
- 'ἡ' => 'Ἡ',
- 'ἢ' => 'Ἢ',
- 'ἣ' => 'Ἣ',
- 'ἤ' => 'Ἤ',
- 'ἥ' => 'Ἥ',
- 'ἦ' => 'Ἦ',
- 'ἧ' => 'Ἧ',
- 'ἰ' => 'Ἰ',
- 'ἱ' => 'Ἱ',
- 'ἲ' => 'Ἲ',
- 'ἳ' => 'Ἳ',
- 'ἴ' => 'Ἴ',
- 'ἵ' => 'Ἵ',
- 'ἶ' => 'Ἶ',
- 'ἷ' => 'Ἷ',
- 'ὀ' => 'Ὀ',
- 'ὁ' => 'Ὁ',
- 'ὂ' => 'Ὂ',
- 'ὃ' => 'Ὃ',
- 'ὄ' => 'Ὄ',
- 'ὅ' => 'Ὅ',
- 'ὑ' => 'Ὑ',
- 'ὓ' => 'Ὓ',
- 'ὕ' => 'Ὕ',
- 'ὗ' => 'Ὗ',
- 'ὠ' => 'Ὠ',
- 'ὡ' => 'Ὡ',
- 'ὢ' => 'Ὢ',
- 'ὣ' => 'Ὣ',
- 'ὤ' => 'Ὤ',
- 'ὥ' => 'Ὥ',
- 'ὦ' => 'Ὦ',
- 'ὧ' => 'Ὧ',
- 'ὰ' => 'Ὰ',
- 'ά' => 'Ά',
- 'ὲ' => 'Ὲ',
- 'έ' => 'Έ',
- 'ὴ' => 'Ὴ',
- 'ή' => 'Ή',
- 'ὶ' => 'Ὶ',
- 'ί' => 'Ί',
- 'ὸ' => 'Ὸ',
- 'ό' => 'Ό',
- 'ὺ' => 'Ὺ',
- 'ύ' => 'Ύ',
- 'ὼ' => 'Ὼ',
- 'ώ' => 'Ώ',
- 'ᾀ' => 'ᾈ',
- 'ᾁ' => 'ᾉ',
- 'ᾂ' => 'ᾊ',
- 'ᾃ' => 'ᾋ',
- 'ᾄ' => 'ᾌ',
- 'ᾅ' => 'ᾍ',
- 'ᾆ' => 'ᾎ',
- 'ᾇ' => 'ᾏ',
- 'ᾐ' => 'ᾘ',
- 'ᾑ' => 'ᾙ',
- 'ᾒ' => 'ᾚ',
- 'ᾓ' => 'ᾛ',
- 'ᾔ' => 'ᾜ',
- 'ᾕ' => 'ᾝ',
- 'ᾖ' => 'ᾞ',
- 'ᾗ' => 'ᾟ',
- 'ᾠ' => 'ᾨ',
- 'ᾡ' => 'ᾩ',
- 'ᾢ' => 'ᾪ',
- 'ᾣ' => 'ᾫ',
- 'ᾤ' => 'ᾬ',
- 'ᾥ' => 'ᾭ',
- 'ᾦ' => 'ᾮ',
- 'ᾧ' => 'ᾯ',
- 'ᾰ' => 'Ᾰ',
- 'ᾱ' => 'Ᾱ',
- 'ᾳ' => 'ᾼ',
- 'ι' => 'Ι',
- 'ῃ' => 'ῌ',
- 'ῐ' => 'Ῐ',
- 'ῑ' => 'Ῑ',
- 'ῠ' => 'Ῠ',
- 'ῡ' => 'Ῡ',
- 'ῥ' => 'Ῥ',
- 'ῳ' => 'ῼ',
- 'ⅎ' => 'Ⅎ',
- 'ⅰ' => 'Ⅰ',
- 'ⅱ' => 'Ⅱ',
- 'ⅲ' => 'Ⅲ',
- 'ⅳ' => 'Ⅳ',
- 'ⅴ' => 'Ⅴ',
- 'ⅵ' => 'Ⅵ',
- 'ⅶ' => 'Ⅶ',
- 'ⅷ' => 'Ⅷ',
- 'ⅸ' => 'Ⅸ',
- 'ⅹ' => 'Ⅹ',
- 'ⅺ' => 'Ⅺ',
- 'ⅻ' => 'Ⅻ',
- 'ⅼ' => 'Ⅼ',
- 'ⅽ' => 'Ⅽ',
- 'ⅾ' => 'Ⅾ',
- 'ⅿ' => 'Ⅿ',
- 'ↄ' => 'Ↄ',
- 'ⓐ' => 'Ⓐ',
- 'ⓑ' => 'Ⓑ',
- 'ⓒ' => 'Ⓒ',
- 'ⓓ' => 'Ⓓ',
- 'ⓔ' => 'Ⓔ',
- 'ⓕ' => 'Ⓕ',
- 'ⓖ' => 'Ⓖ',
- 'ⓗ' => 'Ⓗ',
- 'ⓘ' => 'Ⓘ',
- 'ⓙ' => 'Ⓙ',
- 'ⓚ' => 'Ⓚ',
- 'ⓛ' => 'Ⓛ',
- 'ⓜ' => 'Ⓜ',
- 'ⓝ' => 'Ⓝ',
- 'ⓞ' => 'Ⓞ',
- 'ⓟ' => 'Ⓟ',
- 'ⓠ' => 'Ⓠ',
- 'ⓡ' => 'Ⓡ',
- 'ⓢ' => 'Ⓢ',
- 'ⓣ' => 'Ⓣ',
- 'ⓤ' => 'Ⓤ',
- 'ⓥ' => 'Ⓥ',
- 'ⓦ' => 'Ⓦ',
- 'ⓧ' => 'Ⓧ',
- 'ⓨ' => 'Ⓨ',
- 'ⓩ' => 'Ⓩ',
- 'ⰰ' => 'Ⰰ',
- 'ⰱ' => 'Ⰱ',
- 'ⰲ' => 'Ⰲ',
- 'ⰳ' => 'Ⰳ',
- 'ⰴ' => 'Ⰴ',
- 'ⰵ' => 'Ⰵ',
- 'ⰶ' => 'Ⰶ',
- 'ⰷ' => 'Ⰷ',
- 'ⰸ' => 'Ⰸ',
- 'ⰹ' => 'Ⰹ',
- 'ⰺ' => 'Ⰺ',
- 'ⰻ' => 'Ⰻ',
- 'ⰼ' => 'Ⰼ',
- 'ⰽ' => 'Ⰽ',
- 'ⰾ' => 'Ⰾ',
- 'ⰿ' => 'Ⰿ',
- 'ⱀ' => 'Ⱀ',
- 'ⱁ' => 'Ⱁ',
- 'ⱂ' => 'Ⱂ',
- 'ⱃ' => 'Ⱃ',
- 'ⱄ' => 'Ⱄ',
- 'ⱅ' => 'Ⱅ',
- 'ⱆ' => 'Ⱆ',
- 'ⱇ' => 'Ⱇ',
- 'ⱈ' => 'Ⱈ',
- 'ⱉ' => 'Ⱉ',
- 'ⱊ' => 'Ⱊ',
- 'ⱋ' => 'Ⱋ',
- 'ⱌ' => 'Ⱌ',
- 'ⱍ' => 'Ⱍ',
- 'ⱎ' => 'Ⱎ',
- 'ⱏ' => 'Ⱏ',
- 'ⱐ' => 'Ⱐ',
- 'ⱑ' => 'Ⱑ',
- 'ⱒ' => 'Ⱒ',
- 'ⱓ' => 'Ⱓ',
- 'ⱔ' => 'Ⱔ',
- 'ⱕ' => 'Ⱕ',
- 'ⱖ' => 'Ⱖ',
- 'ⱗ' => 'Ⱗ',
- 'ⱘ' => 'Ⱘ',
- 'ⱙ' => 'Ⱙ',
- 'ⱚ' => 'Ⱚ',
- 'ⱛ' => 'Ⱛ',
- 'ⱜ' => 'Ⱜ',
- 'ⱝ' => 'Ⱝ',
- 'ⱞ' => 'Ⱞ',
- 'ⱡ' => 'Ⱡ',
- 'ⱥ' => 'Ⱥ',
- 'ⱦ' => 'Ⱦ',
- 'ⱨ' => 'Ⱨ',
- 'ⱪ' => 'Ⱪ',
- 'ⱬ' => 'Ⱬ',
- 'ⱳ' => 'Ⱳ',
- 'ⱶ' => 'Ⱶ',
- 'ⲁ' => 'Ⲁ',
- 'ⲃ' => 'Ⲃ',
- 'ⲅ' => 'Ⲅ',
- 'ⲇ' => 'Ⲇ',
- 'ⲉ' => 'Ⲉ',
- 'ⲋ' => 'Ⲋ',
- 'ⲍ' => 'Ⲍ',
- 'ⲏ' => 'Ⲏ',
- 'ⲑ' => 'Ⲑ',
- 'ⲓ' => 'Ⲓ',
- 'ⲕ' => 'Ⲕ',
- 'ⲗ' => 'Ⲗ',
- 'ⲙ' => 'Ⲙ',
- 'ⲛ' => 'Ⲛ',
- 'ⲝ' => 'Ⲝ',
- 'ⲟ' => 'Ⲟ',
- 'ⲡ' => 'Ⲡ',
- 'ⲣ' => 'Ⲣ',
- 'ⲥ' => 'Ⲥ',
- 'ⲧ' => 'Ⲧ',
- 'ⲩ' => 'Ⲩ',
- 'ⲫ' => 'Ⲫ',
- 'ⲭ' => 'Ⲭ',
- 'ⲯ' => 'Ⲯ',
- 'ⲱ' => 'Ⲱ',
- 'ⲳ' => 'Ⲳ',
- 'ⲵ' => 'Ⲵ',
- 'ⲷ' => 'Ⲷ',
- 'ⲹ' => 'Ⲹ',
- 'ⲻ' => 'Ⲻ',
- 'ⲽ' => 'Ⲽ',
- 'ⲿ' => 'Ⲿ',
- 'ⳁ' => 'Ⳁ',
- 'ⳃ' => 'Ⳃ',
- 'ⳅ' => 'Ⳅ',
- 'ⳇ' => 'Ⳇ',
- 'ⳉ' => 'Ⳉ',
- 'ⳋ' => 'Ⳋ',
- 'ⳍ' => 'Ⳍ',
- 'ⳏ' => 'Ⳏ',
- 'ⳑ' => 'Ⳑ',
- 'ⳓ' => 'Ⳓ',
- 'ⳕ' => 'Ⳕ',
- 'ⳗ' => 'Ⳗ',
- 'ⳙ' => 'Ⳙ',
- 'ⳛ' => 'Ⳛ',
- 'ⳝ' => 'Ⳝ',
- 'ⳟ' => 'Ⳟ',
- 'ⳡ' => 'Ⳡ',
- 'ⳣ' => 'Ⳣ',
- 'ⳬ' => 'Ⳬ',
- 'ⳮ' => 'Ⳮ',
- 'ⴀ' => 'Ⴀ',
- 'ⴁ' => 'Ⴁ',
- 'ⴂ' => 'Ⴂ',
- 'ⴃ' => 'Ⴃ',
- 'ⴄ' => 'Ⴄ',
- 'ⴅ' => 'Ⴅ',
- 'ⴆ' => 'Ⴆ',
- 'ⴇ' => 'Ⴇ',
- 'ⴈ' => 'Ⴈ',
- 'ⴉ' => 'Ⴉ',
- 'ⴊ' => 'Ⴊ',
- 'ⴋ' => 'Ⴋ',
- 'ⴌ' => 'Ⴌ',
- 'ⴍ' => 'Ⴍ',
- 'ⴎ' => 'Ⴎ',
- 'ⴏ' => 'Ⴏ',
- 'ⴐ' => 'Ⴐ',
- 'ⴑ' => 'Ⴑ',
- 'ⴒ' => 'Ⴒ',
- 'ⴓ' => 'Ⴓ',
- 'ⴔ' => 'Ⴔ',
- 'ⴕ' => 'Ⴕ',
- 'ⴖ' => 'Ⴖ',
- 'ⴗ' => 'Ⴗ',
- 'ⴘ' => 'Ⴘ',
- 'ⴙ' => 'Ⴙ',
- 'ⴚ' => 'Ⴚ',
- 'ⴛ' => 'Ⴛ',
- 'ⴜ' => 'Ⴜ',
- 'ⴝ' => 'Ⴝ',
- 'ⴞ' => 'Ⴞ',
- 'ⴟ' => 'Ⴟ',
- 'ⴠ' => 'Ⴠ',
- 'ⴡ' => 'Ⴡ',
- 'ⴢ' => 'Ⴢ',
- 'ⴣ' => 'Ⴣ',
- 'ⴤ' => 'Ⴤ',
- 'ⴥ' => 'Ⴥ',
- 'ꙁ' => 'Ꙁ',
- 'ꙃ' => 'Ꙃ',
- 'ꙅ' => 'Ꙅ',
- 'ꙇ' => 'Ꙇ',
- 'ꙉ' => 'Ꙉ',
- 'ꙋ' => 'Ꙋ',
- 'ꙍ' => 'Ꙍ',
- 'ꙏ' => 'Ꙏ',
- 'ꙑ' => 'Ꙑ',
- 'ꙓ' => 'Ꙓ',
- 'ꙕ' => 'Ꙕ',
- 'ꙗ' => 'Ꙗ',
- 'ꙙ' => 'Ꙙ',
- 'ꙛ' => 'Ꙛ',
- 'ꙝ' => 'Ꙝ',
- 'ꙟ' => 'Ꙟ',
- 'ꙡ' => 'Ꙡ',
- 'ꙣ' => 'Ꙣ',
- 'ꙥ' => 'Ꙥ',
- 'ꙧ' => 'Ꙧ',
- 'ꙩ' => 'Ꙩ',
- 'ꙫ' => 'Ꙫ',
- 'ꙭ' => 'Ꙭ',
- 'ꚁ' => 'Ꚁ',
- 'ꚃ' => 'Ꚃ',
- 'ꚅ' => 'Ꚅ',
- 'ꚇ' => 'Ꚇ',
- 'ꚉ' => 'Ꚉ',
- 'ꚋ' => 'Ꚋ',
- 'ꚍ' => 'Ꚍ',
- 'ꚏ' => 'Ꚏ',
- 'ꚑ' => 'Ꚑ',
- 'ꚓ' => 'Ꚓ',
- 'ꚕ' => 'Ꚕ',
- 'ꚗ' => 'Ꚗ',
- 'ꜣ' => 'Ꜣ',
- 'ꜥ' => 'Ꜥ',
- 'ꜧ' => 'Ꜧ',
- 'ꜩ' => 'Ꜩ',
- 'ꜫ' => 'Ꜫ',
- 'ꜭ' => 'Ꜭ',
- 'ꜯ' => 'Ꜯ',
- 'ꜳ' => 'Ꜳ',
- 'ꜵ' => 'Ꜵ',
- 'ꜷ' => 'Ꜷ',
- 'ꜹ' => 'Ꜹ',
- 'ꜻ' => 'Ꜻ',
- 'ꜽ' => 'Ꜽ',
- 'ꜿ' => 'Ꜿ',
- 'ꝁ' => 'Ꝁ',
- 'ꝃ' => 'Ꝃ',
- 'ꝅ' => 'Ꝅ',
- 'ꝇ' => 'Ꝇ',
- 'ꝉ' => 'Ꝉ',
- 'ꝋ' => 'Ꝋ',
- 'ꝍ' => 'Ꝍ',
- 'ꝏ' => 'Ꝏ',
- 'ꝑ' => 'Ꝑ',
- 'ꝓ' => 'Ꝓ',
- 'ꝕ' => 'Ꝕ',
- 'ꝗ' => 'Ꝗ',
- 'ꝙ' => 'Ꝙ',
- 'ꝛ' => 'Ꝛ',
- 'ꝝ' => 'Ꝝ',
- 'ꝟ' => 'Ꝟ',
- 'ꝡ' => 'Ꝡ',
- 'ꝣ' => 'Ꝣ',
- 'ꝥ' => 'Ꝥ',
- 'ꝧ' => 'Ꝧ',
- 'ꝩ' => 'Ꝩ',
- 'ꝫ' => 'Ꝫ',
- 'ꝭ' => 'Ꝭ',
- 'ꝯ' => 'Ꝯ',
- 'ꝺ' => 'Ꝺ',
- 'ꝼ' => 'Ꝼ',
- 'ꝿ' => 'Ꝿ',
- 'ꞁ' => 'Ꞁ',
- 'ꞃ' => 'Ꞃ',
- 'ꞅ' => 'Ꞅ',
- 'ꞇ' => 'Ꞇ',
- 'ꞌ' => 'Ꞌ',
- 'ꞑ' => 'Ꞑ',
- 'ꞡ' => 'Ꞡ',
- 'ꞣ' => 'Ꞣ',
- 'ꞥ' => 'Ꞥ',
- 'ꞧ' => 'Ꞧ',
- 'ꞩ' => 'Ꞩ',
- 'a' => 'A',
- 'b' => 'B',
- 'c' => 'C',
- 'd' => 'D',
- 'e' => 'E',
- 'f' => 'F',
- 'g' => 'G',
- 'h' => 'H',
- 'i' => 'I',
- 'j' => 'J',
- 'k' => 'K',
- 'l' => 'L',
- 'm' => 'M',
- 'n' => 'N',
- 'o' => 'O',
- 'p' => 'P',
- 'q' => 'Q',
- 'r' => 'R',
- 's' => 'S',
- 't' => 'T',
- 'u' => 'U',
- 'v' => 'V',
- 'w' => 'W',
- 'x' => 'X',
- 'y' => 'Y',
- 'z' => 'Z',
- '𐐨' => '𐐀',
- '𐐩' => '𐐁',
- '𐐪' => '𐐂',
- '𐐫' => '𐐃',
- '𐐬' => '𐐄',
- '𐐭' => '𐐅',
- '𐐮' => '𐐆',
- '𐐯' => '𐐇',
- '𐐰' => '𐐈',
- '𐐱' => '𐐉',
- '𐐲' => '𐐊',
- '𐐳' => '𐐋',
- '𐐴' => '𐐌',
- '𐐵' => '𐐍',
- '𐐶' => '𐐎',
- '𐐷' => '𐐏',
- '𐐸' => '𐐐',
- '𐐹' => '𐐑',
- '𐐺' => '𐐒',
- '𐐻' => '𐐓',
- '𐐼' => '𐐔',
- '𐐽' => '𐐕',
- '𐐾' => '𐐖',
- '𐐿' => '𐐗',
- '𐑀' => '𐐘',
- '𐑁' => '𐐙',
- '𐑂' => '𐐚',
- '𐑃' => '𐐛',
- '𐑄' => '𐐜',
- '𐑅' => '𐐝',
- '𐑆' => '𐐞',
- '𐑇' => '𐐟',
- '𐑈' => '𐐠',
- '𐑉' => '𐐡',
- '𐑊' => '𐐢',
- '𐑋' => '𐐣',
- '𐑌' => '𐐤',
- '𐑍' => '𐐥',
- '𐑎' => '𐐦',
- '𐑏' => '𐐧'
-);
-
-/**
- * Translation array to get lower case character
- */
-$wikiLowerChars = array(
- 'A' => 'a',
- 'B' => 'b',
- 'C' => 'c',
- 'D' => 'd',
- 'E' => 'e',
- 'F' => 'f',
- 'G' => 'g',
- 'H' => 'h',
- 'I' => 'i',
- 'J' => 'j',
- 'K' => 'k',
- 'L' => 'l',
- 'M' => 'm',
- 'N' => 'n',
- 'O' => 'o',
- 'P' => 'p',
- 'Q' => 'q',
- 'R' => 'r',
- 'S' => 's',
- 'T' => 't',
- 'U' => 'u',
- 'V' => 'v',
- 'W' => 'w',
- 'X' => 'x',
- 'Y' => 'y',
- 'Z' => 'z',
- 'À' => 'à',
- 'Á' => 'á',
- 'Â' => 'â',
- 'Ã' => 'ã',
- 'Ä' => 'ä',
- 'Å' => 'å',
- 'Æ' => 'æ',
- 'Ç' => 'ç',
- 'È' => 'è',
- 'É' => 'é',
- 'Ê' => 'ê',
- 'Ë' => 'ë',
- 'Ì' => 'ì',
- 'Í' => 'í',
- 'Î' => 'î',
- 'Ï' => 'ï',
- 'Ð' => 'ð',
- 'Ñ' => 'ñ',
- 'Ò' => 'ò',
- 'Ó' => 'ó',
- 'Ô' => 'ô',
- 'Õ' => 'õ',
- 'Ö' => 'ö',
- 'Ø' => 'ø',
- 'Ù' => 'ù',
- 'Ú' => 'ú',
- 'Û' => 'û',
- 'Ü' => 'ü',
- 'Ý' => 'ý',
- 'Þ' => 'þ',
- 'Ā' => 'ā',
- 'Ă' => 'ă',
- 'Ą' => 'ą',
- 'Ć' => 'ć',
- 'Ĉ' => 'ĉ',
- 'Ċ' => 'ċ',
- 'Č' => 'č',
- 'Ď' => 'ď',
- 'Đ' => 'đ',
- 'Ē' => 'ē',
- 'Ĕ' => 'ĕ',
- 'Ė' => 'ė',
- 'Ę' => 'ę',
- 'Ě' => 'ě',
- 'Ĝ' => 'ĝ',
- 'Ğ' => 'ğ',
- 'Ġ' => 'ġ',
- 'Ģ' => 'ģ',
- 'Ĥ' => 'ĥ',
- 'Ħ' => 'ħ',
- 'Ĩ' => 'ĩ',
- 'Ī' => 'ī',
- 'Ĭ' => 'ĭ',
- 'Į' => 'į',
- 'İ' => 'i',
- 'IJ' => 'ij',
- 'Ĵ' => 'ĵ',
- 'Ķ' => 'ķ',
- 'Ĺ' => 'ĺ',
- 'Ļ' => 'ļ',
- 'Ľ' => 'ľ',
- 'Ŀ' => 'ŀ',
- 'Ł' => 'ł',
- 'Ń' => 'ń',
- 'Ņ' => 'ņ',
- 'Ň' => 'ň',
- 'Ŋ' => 'ŋ',
- 'Ō' => 'ō',
- 'Ŏ' => 'ŏ',
- 'Ő' => 'ő',
- 'Œ' => 'œ',
- 'Ŕ' => 'ŕ',
- 'Ŗ' => 'ŗ',
- 'Ř' => 'ř',
- 'Ś' => 'ś',
- 'Ŝ' => 'ŝ',
- 'Ş' => 'ş',
- 'Š' => 'š',
- 'Ţ' => 'ţ',
- 'Ť' => 'ť',
- 'Ŧ' => 'ŧ',
- 'Ũ' => 'ũ',
- 'Ū' => 'ū',
- 'Ŭ' => 'ŭ',
- 'Ů' => 'ů',
- 'Ű' => 'ű',
- 'Ų' => 'ų',
- 'Ŵ' => 'ŵ',
- 'Ŷ' => 'ŷ',
- 'Ÿ' => 'ÿ',
- 'Ź' => 'ź',
- 'Ż' => 'ż',
- 'Ž' => 'ž',
- 'Ɓ' => 'ɓ',
- 'Ƃ' => 'ƃ',
- 'Ƅ' => 'ƅ',
- 'Ɔ' => 'ɔ',
- 'Ƈ' => 'ƈ',
- 'Ɖ' => 'ɖ',
- 'Ɗ' => 'ɗ',
- 'Ƌ' => 'ƌ',
- 'Ǝ' => 'ǝ',
- 'Ə' => 'ə',
- 'Ɛ' => 'ɛ',
- 'Ƒ' => 'ƒ',
- 'Ɠ' => 'ɠ',
- 'Ɣ' => 'ɣ',
- 'Ɩ' => 'ɩ',
- 'Ɨ' => 'ɨ',
- 'Ƙ' => 'ƙ',
- 'Ɯ' => 'ɯ',
- 'Ɲ' => 'ɲ',
- 'Ɵ' => 'ɵ',
- 'Ơ' => 'ơ',
- 'Ƣ' => 'ƣ',
- 'Ƥ' => 'ƥ',
- 'Ʀ' => 'ʀ',
- 'Ƨ' => 'ƨ',
- 'Ʃ' => 'ʃ',
- 'Ƭ' => 'ƭ',
- 'Ʈ' => 'ʈ',
- 'Ư' => 'ư',
- 'Ʊ' => 'ʊ',
- 'Ʋ' => 'ʋ',
- 'Ƴ' => 'ƴ',
- 'Ƶ' => 'ƶ',
- 'Ʒ' => 'ʒ',
- 'Ƹ' => 'ƹ',
- 'Ƽ' => 'ƽ',
- 'DŽ' => 'dž',
- 'Dž' => 'dž',
- 'LJ' => 'lj',
- 'Lj' => 'lj',
- 'NJ' => 'nj',
- 'Nj' => 'nj',
- 'Ǎ' => 'ǎ',
- 'Ǐ' => 'ǐ',
- 'Ǒ' => 'ǒ',
- 'Ǔ' => 'ǔ',
- 'Ǖ' => 'ǖ',
- 'Ǘ' => 'ǘ',
- 'Ǚ' => 'ǚ',
- 'Ǜ' => 'ǜ',
- 'Ǟ' => 'ǟ',
- 'Ǡ' => 'ǡ',
- 'Ǣ' => 'ǣ',
- 'Ǥ' => 'ǥ',
- 'Ǧ' => 'ǧ',
- 'Ǩ' => 'ǩ',
- 'Ǫ' => 'ǫ',
- 'Ǭ' => 'ǭ',
- 'Ǯ' => 'ǯ',
- 'DZ' => 'dz',
- 'Dz' => 'dz',
- 'Ǵ' => 'ǵ',
- 'Ƕ' => 'ƕ',
- 'Ƿ' => 'ƿ',
- 'Ǹ' => 'ǹ',
- 'Ǻ' => 'ǻ',
- 'Ǽ' => 'ǽ',
- 'Ǿ' => 'ǿ',
- 'Ȁ' => 'ȁ',
- 'Ȃ' => 'ȃ',
- 'Ȅ' => 'ȅ',
- 'Ȇ' => 'ȇ',
- 'Ȉ' => 'ȉ',
- 'Ȋ' => 'ȋ',
- 'Ȍ' => 'ȍ',
- 'Ȏ' => 'ȏ',
- 'Ȑ' => 'ȑ',
- 'Ȓ' => 'ȓ',
- 'Ȕ' => 'ȕ',
- 'Ȗ' => 'ȗ',
- 'Ș' => 'ș',
- 'Ț' => 'ț',
- 'Ȝ' => 'ȝ',
- 'Ȟ' => 'ȟ',
- 'Ƞ' => 'ƞ',
- 'Ȣ' => 'ȣ',
- 'Ȥ' => 'ȥ',
- 'Ȧ' => 'ȧ',
- 'Ȩ' => 'ȩ',
- 'Ȫ' => 'ȫ',
- 'Ȭ' => 'ȭ',
- 'Ȯ' => 'ȯ',
- 'Ȱ' => 'ȱ',
- 'Ȳ' => 'ȳ',
- 'Ⱥ' => 'ⱥ',
- 'Ȼ' => 'ȼ',
- 'Ƚ' => 'ƚ',
- 'Ⱦ' => 'ⱦ',
- 'Ɂ' => 'ɂ',
- 'Ƀ' => 'ƀ',
- 'Ʉ' => 'ʉ',
- 'Ʌ' => 'ʌ',
- 'Ɇ' => 'ɇ',
- 'Ɉ' => 'ɉ',
- 'Ɋ' => 'ɋ',
- 'Ɍ' => 'ɍ',
- 'Ɏ' => 'ɏ',
- 'Ͱ' => 'ͱ',
- 'Ͳ' => 'ͳ',
- 'Ͷ' => 'ͷ',
- 'Ά' => 'ά',
- 'Έ' => 'έ',
- 'Ή' => 'ή',
- 'Ί' => 'ί',
- 'Ό' => 'ό',
- 'Ύ' => 'ύ',
- 'Ώ' => 'ώ',
- 'Α' => 'α',
- 'Β' => 'β',
- 'Γ' => 'γ',
- 'Δ' => 'δ',
- 'Ε' => 'ε',
- 'Ζ' => 'ζ',
- 'Η' => 'η',
- 'Θ' => 'θ',
- 'Ι' => 'ι',
- 'Κ' => 'κ',
- 'Λ' => 'λ',
- 'Μ' => 'μ',
- 'Ν' => 'ν',
- 'Ξ' => 'ξ',
- 'Ο' => 'ο',
- 'Π' => 'π',
- 'Ρ' => 'ρ',
- 'Σ' => 'σ',
- 'Τ' => 'τ',
- 'Υ' => 'υ',
- 'Φ' => 'φ',
- 'Χ' => 'χ',
- 'Ψ' => 'ψ',
- 'Ω' => 'ω',
- 'Ϊ' => 'ϊ',
- 'Ϋ' => 'ϋ',
- 'Ϗ' => 'ϗ',
- 'Ϙ' => 'ϙ',
- 'Ϛ' => 'ϛ',
- 'Ϝ' => 'ϝ',
- 'Ϟ' => 'ϟ',
- 'Ϡ' => 'ϡ',
- 'Ϣ' => 'ϣ',
- 'Ϥ' => 'ϥ',
- 'Ϧ' => 'ϧ',
- 'Ϩ' => 'ϩ',
- 'Ϫ' => 'ϫ',
- 'Ϭ' => 'ϭ',
- 'Ϯ' => 'ϯ',
- 'ϴ' => 'θ',
- 'Ϸ' => 'ϸ',
- 'Ϲ' => 'ϲ',
- 'Ϻ' => 'ϻ',
- 'Ͻ' => 'ͻ',
- 'Ͼ' => 'ͼ',
- 'Ͽ' => 'ͽ',
- 'Ѐ' => 'ѐ',
- 'Ё' => 'ё',
- 'Ђ' => 'ђ',
- 'Ѓ' => 'ѓ',
- 'Є' => 'є',
- 'Ѕ' => 'ѕ',
- 'І' => 'і',
- 'Ї' => 'ї',
- 'Ј' => 'ј',
- 'Љ' => 'љ',
- 'Њ' => 'њ',
- 'Ћ' => 'ћ',
- 'Ќ' => 'ќ',
- 'Ѝ' => 'ѝ',
- 'Ў' => 'ў',
- 'Џ' => 'џ',
- 'А' => 'а',
- 'Б' => 'б',
- 'В' => 'в',
- 'Г' => 'г',
- 'Д' => 'д',
- 'Е' => 'е',
- 'Ж' => 'ж',
- 'З' => 'з',
- 'И' => 'и',
- 'Й' => 'й',
- 'К' => 'к',
- 'Л' => 'л',
- 'М' => 'м',
- 'Н' => 'н',
- 'О' => 'о',
- 'П' => 'п',
- 'Р' => 'р',
- 'С' => 'с',
- 'Т' => 'т',
- 'У' => 'у',
- 'Ф' => 'ф',
- 'Х' => 'х',
- 'Ц' => 'ц',
- 'Ч' => 'ч',
- 'Ш' => 'ш',
- 'Щ' => 'щ',
- 'Ъ' => 'ъ',
- 'Ы' => 'ы',
- 'Ь' => 'ь',
- 'Э' => 'э',
- 'Ю' => 'ю',
- 'Я' => 'я',
- 'Ѡ' => 'ѡ',
- 'Ѣ' => 'ѣ',
- 'Ѥ' => 'ѥ',
- 'Ѧ' => 'ѧ',
- 'Ѩ' => 'ѩ',
- 'Ѫ' => 'ѫ',
- 'Ѭ' => 'ѭ',
- 'Ѯ' => 'ѯ',
- 'Ѱ' => 'ѱ',
- 'Ѳ' => 'ѳ',
- 'Ѵ' => 'ѵ',
- 'Ѷ' => 'ѷ',
- 'Ѹ' => 'ѹ',
- 'Ѻ' => 'ѻ',
- 'Ѽ' => 'ѽ',
- 'Ѿ' => 'ѿ',
- 'Ҁ' => 'ҁ',
- 'Ҋ' => 'ҋ',
- 'Ҍ' => 'ҍ',
- 'Ҏ' => 'ҏ',
- 'Ґ' => 'ґ',
- 'Ғ' => 'ғ',
- 'Ҕ' => 'ҕ',
- 'Җ' => 'җ',
- 'Ҙ' => 'ҙ',
- 'Қ' => 'қ',
- 'Ҝ' => 'ҝ',
- 'Ҟ' => 'ҟ',
- 'Ҡ' => 'ҡ',
- 'Ң' => 'ң',
- 'Ҥ' => 'ҥ',
- 'Ҧ' => 'ҧ',
- 'Ҩ' => 'ҩ',
- 'Ҫ' => 'ҫ',
- 'Ҭ' => 'ҭ',
- 'Ү' => 'ү',
- 'Ұ' => 'ұ',
- 'Ҳ' => 'ҳ',
- 'Ҵ' => 'ҵ',
- 'Ҷ' => 'ҷ',
- 'Ҹ' => 'ҹ',
- 'Һ' => 'һ',
- 'Ҽ' => 'ҽ',
- 'Ҿ' => 'ҿ',
- 'Ӏ' => 'ӏ',
- 'Ӂ' => 'ӂ',
- 'Ӄ' => 'ӄ',
- 'Ӆ' => 'ӆ',
- 'Ӈ' => 'ӈ',
- 'Ӊ' => 'ӊ',
- 'Ӌ' => 'ӌ',
- 'Ӎ' => 'ӎ',
- 'Ӑ' => 'ӑ',
- 'Ӓ' => 'ӓ',
- 'Ӕ' => 'ӕ',
- 'Ӗ' => 'ӗ',
- 'Ә' => 'ә',
- 'Ӛ' => 'ӛ',
- 'Ӝ' => 'ӝ',
- 'Ӟ' => 'ӟ',
- 'Ӡ' => 'ӡ',
- 'Ӣ' => 'ӣ',
- 'Ӥ' => 'ӥ',
- 'Ӧ' => 'ӧ',
- 'Ө' => 'ө',
- 'Ӫ' => 'ӫ',
- 'Ӭ' => 'ӭ',
- 'Ӯ' => 'ӯ',
- 'Ӱ' => 'ӱ',
- 'Ӳ' => 'ӳ',
- 'Ӵ' => 'ӵ',
- 'Ӷ' => 'ӷ',
- 'Ӹ' => 'ӹ',
- 'Ӻ' => 'ӻ',
- 'Ӽ' => 'ӽ',
- 'Ӿ' => 'ӿ',
- 'Ԁ' => 'ԁ',
- 'Ԃ' => 'ԃ',
- 'Ԅ' => 'ԅ',
- 'Ԇ' => 'ԇ',
- 'Ԉ' => 'ԉ',
- 'Ԋ' => 'ԋ',
- 'Ԍ' => 'ԍ',
- 'Ԏ' => 'ԏ',
- 'Ԑ' => 'ԑ',
- 'Ԓ' => 'ԓ',
- 'Ԕ' => 'ԕ',
- 'Ԗ' => 'ԗ',
- 'Ԙ' => 'ԙ',
- 'Ԛ' => 'ԛ',
- 'Ԝ' => 'ԝ',
- 'Ԟ' => 'ԟ',
- 'Ԡ' => 'ԡ',
- 'Ԣ' => 'ԣ',
- 'Ԥ' => 'ԥ',
- 'Ԧ' => 'ԧ',
- 'Ա' => 'ա',
- 'Բ' => 'բ',
- 'Գ' => 'գ',
- 'Դ' => 'դ',
- 'Ե' => 'ե',
- 'Զ' => 'զ',
- 'Է' => 'է',
- 'Ը' => 'ը',
- 'Թ' => 'թ',
- 'Ժ' => 'ժ',
- 'Ի' => 'ի',
- 'Լ' => 'լ',
- 'Խ' => 'խ',
- 'Ծ' => 'ծ',
- 'Կ' => 'կ',
- 'Հ' => 'հ',
- 'Ձ' => 'ձ',
- 'Ղ' => 'ղ',
- 'Ճ' => 'ճ',
- 'Մ' => 'մ',
- 'Յ' => 'յ',
- 'Ն' => 'ն',
- 'Շ' => 'շ',
- 'Ո' => 'ո',
- 'Չ' => 'չ',
- 'Պ' => 'պ',
- 'Ջ' => 'ջ',
- 'Ռ' => 'ռ',
- 'Ս' => 'ս',
- 'Վ' => 'վ',
- 'Տ' => 'տ',
- 'Ր' => 'ր',
- 'Ց' => 'ց',
- 'Ւ' => 'ւ',
- 'Փ' => 'փ',
- 'Ք' => 'ք',
- 'Օ' => 'օ',
- 'Ֆ' => 'ֆ',
- 'Ⴀ' => 'ⴀ',
- 'Ⴁ' => 'ⴁ',
- 'Ⴂ' => 'ⴂ',
- 'Ⴃ' => 'ⴃ',
- 'Ⴄ' => 'ⴄ',
- 'Ⴅ' => 'ⴅ',
- 'Ⴆ' => 'ⴆ',
- 'Ⴇ' => 'ⴇ',
- 'Ⴈ' => 'ⴈ',
- 'Ⴉ' => 'ⴉ',
- 'Ⴊ' => 'ⴊ',
- 'Ⴋ' => 'ⴋ',
- 'Ⴌ' => 'ⴌ',
- 'Ⴍ' => 'ⴍ',
- 'Ⴎ' => 'ⴎ',
- 'Ⴏ' => 'ⴏ',
- 'Ⴐ' => 'ⴐ',
- 'Ⴑ' => 'ⴑ',
- 'Ⴒ' => 'ⴒ',
- 'Ⴓ' => 'ⴓ',
- 'Ⴔ' => 'ⴔ',
- 'Ⴕ' => 'ⴕ',
- 'Ⴖ' => 'ⴖ',
- 'Ⴗ' => 'ⴗ',
- 'Ⴘ' => 'ⴘ',
- 'Ⴙ' => 'ⴙ',
- 'Ⴚ' => 'ⴚ',
- 'Ⴛ' => 'ⴛ',
- 'Ⴜ' => 'ⴜ',
- 'Ⴝ' => 'ⴝ',
- 'Ⴞ' => 'ⴞ',
- 'Ⴟ' => 'ⴟ',
- 'Ⴠ' => 'ⴠ',
- 'Ⴡ' => 'ⴡ',
- 'Ⴢ' => 'ⴢ',
- 'Ⴣ' => 'ⴣ',
- 'Ⴤ' => 'ⴤ',
- 'Ⴥ' => 'ⴥ',
- 'Ḁ' => 'ḁ',
- 'Ḃ' => 'ḃ',
- 'Ḅ' => 'ḅ',
- 'Ḇ' => 'ḇ',
- 'Ḉ' => 'ḉ',
- 'Ḋ' => 'ḋ',
- 'Ḍ' => 'ḍ',
- 'Ḏ' => 'ḏ',
- 'Ḑ' => 'ḑ',
- 'Ḓ' => 'ḓ',
- 'Ḕ' => 'ḕ',
- 'Ḗ' => 'ḗ',
- 'Ḙ' => 'ḙ',
- 'Ḛ' => 'ḛ',
- 'Ḝ' => 'ḝ',
- 'Ḟ' => 'ḟ',
- 'Ḡ' => 'ḡ',
- 'Ḣ' => 'ḣ',
- 'Ḥ' => 'ḥ',
- 'Ḧ' => 'ḧ',
- 'Ḩ' => 'ḩ',
- 'Ḫ' => 'ḫ',
- 'Ḭ' => 'ḭ',
- 'Ḯ' => 'ḯ',
- 'Ḱ' => 'ḱ',
- 'Ḳ' => 'ḳ',
- 'Ḵ' => 'ḵ',
- 'Ḷ' => 'ḷ',
- 'Ḹ' => 'ḹ',
- 'Ḻ' => 'ḻ',
- 'Ḽ' => 'ḽ',
- 'Ḿ' => 'ḿ',
- 'Ṁ' => 'ṁ',
- 'Ṃ' => 'ṃ',
- 'Ṅ' => 'ṅ',
- 'Ṇ' => 'ṇ',
- 'Ṉ' => 'ṉ',
- 'Ṋ' => 'ṋ',
- 'Ṍ' => 'ṍ',
- 'Ṏ' => 'ṏ',
- 'Ṑ' => 'ṑ',
- 'Ṓ' => 'ṓ',
- 'Ṕ' => 'ṕ',
- 'Ṗ' => 'ṗ',
- 'Ṙ' => 'ṙ',
- 'Ṛ' => 'ṛ',
- 'Ṝ' => 'ṝ',
- 'Ṟ' => 'ṟ',
- 'Ṡ' => 'ṡ',
- 'Ṣ' => 'ṣ',
- 'Ṥ' => 'ṥ',
- 'Ṧ' => 'ṧ',
- 'Ṩ' => 'ṩ',
- 'Ṫ' => 'ṫ',
- 'Ṭ' => 'ṭ',
- 'Ṯ' => 'ṯ',
- 'Ṱ' => 'ṱ',
- 'Ṳ' => 'ṳ',
- 'Ṵ' => 'ṵ',
- 'Ṷ' => 'ṷ',
- 'Ṹ' => 'ṹ',
- 'Ṻ' => 'ṻ',
- 'Ṽ' => 'ṽ',
- 'Ṿ' => 'ṿ',
- 'Ẁ' => 'ẁ',
- 'Ẃ' => 'ẃ',
- 'Ẅ' => 'ẅ',
- 'Ẇ' => 'ẇ',
- 'Ẉ' => 'ẉ',
- 'Ẋ' => 'ẋ',
- 'Ẍ' => 'ẍ',
- 'Ẏ' => 'ẏ',
- 'Ẑ' => 'ẑ',
- 'Ẓ' => 'ẓ',
- 'Ẕ' => 'ẕ',
- 'ẞ' => 'ß',
- 'Ạ' => 'ạ',
- 'Ả' => 'ả',
- 'Ấ' => 'ấ',
- 'Ầ' => 'ầ',
- 'Ẩ' => 'ẩ',
- 'Ẫ' => 'ẫ',
- 'Ậ' => 'ậ',
- 'Ắ' => 'ắ',
- 'Ằ' => 'ằ',
- 'Ẳ' => 'ẳ',
- 'Ẵ' => 'ẵ',
- 'Ặ' => 'ặ',
- 'Ẹ' => 'ẹ',
- 'Ẻ' => 'ẻ',
- 'Ẽ' => 'ẽ',
- 'Ế' => 'ế',
- 'Ề' => 'ề',
- 'Ể' => 'ể',
- 'Ễ' => 'ễ',
- 'Ệ' => 'ệ',
- 'Ỉ' => 'ỉ',
- 'Ị' => 'ị',
- 'Ọ' => 'ọ',
- 'Ỏ' => 'ỏ',
- 'Ố' => 'ố',
- 'Ồ' => 'ồ',
- 'Ổ' => 'ổ',
- 'Ỗ' => 'ỗ',
- 'Ộ' => 'ộ',
- 'Ớ' => 'ớ',
- 'Ờ' => 'ờ',
- 'Ở' => 'ở',
- 'Ỡ' => 'ỡ',
- 'Ợ' => 'ợ',
- 'Ụ' => 'ụ',
- 'Ủ' => 'ủ',
- 'Ứ' => 'ứ',
- 'Ừ' => 'ừ',
- 'Ử' => 'ử',
- 'Ữ' => 'ữ',
- 'Ự' => 'ự',
- 'Ỳ' => 'ỳ',
- 'Ỵ' => 'ỵ',
- 'Ỷ' => 'ỷ',
- 'Ỹ' => 'ỹ',
- 'Ỻ' => 'ỻ',
- 'Ỽ' => 'ỽ',
- 'Ỿ' => 'ỿ',
- 'Ἀ' => 'ἀ',
- 'Ἁ' => 'ἁ',
- 'Ἂ' => 'ἂ',
- 'Ἃ' => 'ἃ',
- 'Ἄ' => 'ἄ',
- 'Ἅ' => 'ἅ',
- 'Ἆ' => 'ἆ',
- 'Ἇ' => 'ἇ',
- 'Ἐ' => 'ἐ',
- 'Ἑ' => 'ἑ',
- 'Ἒ' => 'ἒ',
- 'Ἓ' => 'ἓ',
- 'Ἔ' => 'ἔ',
- 'Ἕ' => 'ἕ',
- 'Ἠ' => 'ἠ',
- 'Ἡ' => 'ἡ',
- 'Ἢ' => 'ἢ',
- 'Ἣ' => 'ἣ',
- 'Ἤ' => 'ἤ',
- 'Ἥ' => 'ἥ',
- 'Ἦ' => 'ἦ',
- 'Ἧ' => 'ἧ',
- 'Ἰ' => 'ἰ',
- 'Ἱ' => 'ἱ',
- 'Ἲ' => 'ἲ',
- 'Ἳ' => 'ἳ',
- 'Ἴ' => 'ἴ',
- 'Ἵ' => 'ἵ',
- 'Ἶ' => 'ἶ',
- 'Ἷ' => 'ἷ',
- 'Ὀ' => 'ὀ',
- 'Ὁ' => 'ὁ',
- 'Ὂ' => 'ὂ',
- 'Ὃ' => 'ὃ',
- 'Ὄ' => 'ὄ',
- 'Ὅ' => 'ὅ',
- 'Ὑ' => 'ὑ',
- 'Ὓ' => 'ὓ',
- 'Ὕ' => 'ὕ',
- 'Ὗ' => 'ὗ',
- 'Ὠ' => 'ὠ',
- 'Ὡ' => 'ὡ',
- 'Ὢ' => 'ὢ',
- 'Ὣ' => 'ὣ',
- 'Ὤ' => 'ὤ',
- 'Ὥ' => 'ὥ',
- 'Ὦ' => 'ὦ',
- 'Ὧ' => 'ὧ',
- 'ᾈ' => 'ᾀ',
- 'ᾉ' => 'ᾁ',
- 'ᾊ' => 'ᾂ',
- 'ᾋ' => 'ᾃ',
- 'ᾌ' => 'ᾄ',
- 'ᾍ' => 'ᾅ',
- 'ᾎ' => 'ᾆ',
- 'ᾏ' => 'ᾇ',
- 'ᾘ' => 'ᾐ',
- 'ᾙ' => 'ᾑ',
- 'ᾚ' => 'ᾒ',
- 'ᾛ' => 'ᾓ',
- 'ᾜ' => 'ᾔ',
- 'ᾝ' => 'ᾕ',
- 'ᾞ' => 'ᾖ',
- 'ᾟ' => 'ᾗ',
- 'ᾨ' => 'ᾠ',
- 'ᾩ' => 'ᾡ',
- 'ᾪ' => 'ᾢ',
- 'ᾫ' => 'ᾣ',
- 'ᾬ' => 'ᾤ',
- 'ᾭ' => 'ᾥ',
- 'ᾮ' => 'ᾦ',
- 'ᾯ' => 'ᾧ',
- 'Ᾰ' => 'ᾰ',
- 'Ᾱ' => 'ᾱ',
- 'Ὰ' => 'ὰ',
- 'Ά' => 'ά',
- 'ᾼ' => 'ᾳ',
- 'Ὲ' => 'ὲ',
- 'Έ' => 'έ',
- 'Ὴ' => 'ὴ',
- 'Ή' => 'ή',
- 'ῌ' => 'ῃ',
- 'Ῐ' => 'ῐ',
- 'Ῑ' => 'ῑ',
- 'Ὶ' => 'ὶ',
- 'Ί' => 'ί',
- 'Ῠ' => 'ῠ',
- 'Ῡ' => 'ῡ',
- 'Ὺ' => 'ὺ',
- 'Ύ' => 'ύ',
- 'Ῥ' => 'ῥ',
- 'Ὸ' => 'ὸ',
- 'Ό' => 'ό',
- 'Ὼ' => 'ὼ',
- 'Ώ' => 'ώ',
- 'ῼ' => 'ῳ',
- 'Ω' => 'ω',
- 'K' => 'k',
- 'Å' => 'å',
- 'Ⅎ' => 'ⅎ',
- 'Ⅰ' => 'ⅰ',
- 'Ⅱ' => 'ⅱ',
- 'Ⅲ' => 'ⅲ',
- 'Ⅳ' => 'ⅳ',
- 'Ⅴ' => 'ⅴ',
- 'Ⅵ' => 'ⅵ',
- 'Ⅶ' => 'ⅶ',
- 'Ⅷ' => 'ⅷ',
- 'Ⅸ' => 'ⅸ',
- 'Ⅹ' => 'ⅹ',
- 'Ⅺ' => 'ⅺ',
- 'Ⅻ' => 'ⅻ',
- 'Ⅼ' => 'ⅼ',
- 'Ⅽ' => 'ⅽ',
- 'Ⅾ' => 'ⅾ',
- 'Ⅿ' => 'ⅿ',
- 'Ↄ' => 'ↄ',
- 'Ⓐ' => 'ⓐ',
- 'Ⓑ' => 'ⓑ',
- 'Ⓒ' => 'ⓒ',
- 'Ⓓ' => 'ⓓ',
- 'Ⓔ' => 'ⓔ',
- 'Ⓕ' => 'ⓕ',
- 'Ⓖ' => 'ⓖ',
- 'Ⓗ' => 'ⓗ',
- 'Ⓘ' => 'ⓘ',
- 'Ⓙ' => 'ⓙ',
- 'Ⓚ' => 'ⓚ',
- 'Ⓛ' => 'ⓛ',
- 'Ⓜ' => 'ⓜ',
- 'Ⓝ' => 'ⓝ',
- 'Ⓞ' => 'ⓞ',
- 'Ⓟ' => 'ⓟ',
- 'Ⓠ' => 'ⓠ',
- 'Ⓡ' => 'ⓡ',
- 'Ⓢ' => 'ⓢ',
- 'Ⓣ' => 'ⓣ',
- 'Ⓤ' => 'ⓤ',
- 'Ⓥ' => 'ⓥ',
- 'Ⓦ' => 'ⓦ',
- 'Ⓧ' => 'ⓧ',
- 'Ⓨ' => 'ⓨ',
- 'Ⓩ' => 'ⓩ',
- 'Ⰰ' => 'ⰰ',
- 'Ⰱ' => 'ⰱ',
- 'Ⰲ' => 'ⰲ',
- 'Ⰳ' => 'ⰳ',
- 'Ⰴ' => 'ⰴ',
- 'Ⰵ' => 'ⰵ',
- 'Ⰶ' => 'ⰶ',
- 'Ⰷ' => 'ⰷ',
- 'Ⰸ' => 'ⰸ',
- 'Ⰹ' => 'ⰹ',
- 'Ⰺ' => 'ⰺ',
- 'Ⰻ' => 'ⰻ',
- 'Ⰼ' => 'ⰼ',
- 'Ⰽ' => 'ⰽ',
- 'Ⰾ' => 'ⰾ',
- 'Ⰿ' => 'ⰿ',
- 'Ⱀ' => 'ⱀ',
- 'Ⱁ' => 'ⱁ',
- 'Ⱂ' => 'ⱂ',
- 'Ⱃ' => 'ⱃ',
- 'Ⱄ' => 'ⱄ',
- 'Ⱅ' => 'ⱅ',
- 'Ⱆ' => 'ⱆ',
- 'Ⱇ' => 'ⱇ',
- 'Ⱈ' => 'ⱈ',
- 'Ⱉ' => 'ⱉ',
- 'Ⱊ' => 'ⱊ',
- 'Ⱋ' => 'ⱋ',
- 'Ⱌ' => 'ⱌ',
- 'Ⱍ' => 'ⱍ',
- 'Ⱎ' => 'ⱎ',
- 'Ⱏ' => 'ⱏ',
- 'Ⱐ' => 'ⱐ',
- 'Ⱑ' => 'ⱑ',
- 'Ⱒ' => 'ⱒ',
- 'Ⱓ' => 'ⱓ',
- 'Ⱔ' => 'ⱔ',
- 'Ⱕ' => 'ⱕ',
- 'Ⱖ' => 'ⱖ',
- 'Ⱗ' => 'ⱗ',
- 'Ⱘ' => 'ⱘ',
- 'Ⱙ' => 'ⱙ',
- 'Ⱚ' => 'ⱚ',
- 'Ⱛ' => 'ⱛ',
- 'Ⱜ' => 'ⱜ',
- 'Ⱝ' => 'ⱝ',
- 'Ⱞ' => 'ⱞ',
- 'Ⱡ' => 'ⱡ',
- 'Ɫ' => 'ɫ',
- 'Ᵽ' => 'ᵽ',
- 'Ɽ' => 'ɽ',
- 'Ⱨ' => 'ⱨ',
- 'Ⱪ' => 'ⱪ',
- 'Ⱬ' => 'ⱬ',
- 'Ɑ' => 'ɑ',
- 'Ɱ' => 'ɱ',
- 'Ɐ' => 'ɐ',
- 'Ɒ' => 'ɒ',
- 'Ⱳ' => 'ⱳ',
- 'Ⱶ' => 'ⱶ',
- 'Ȿ' => 'ȿ',
- 'Ɀ' => 'ɀ',
- 'Ⲁ' => 'ⲁ',
- 'Ⲃ' => 'ⲃ',
- 'Ⲅ' => 'ⲅ',
- 'Ⲇ' => 'ⲇ',
- 'Ⲉ' => 'ⲉ',
- 'Ⲋ' => 'ⲋ',
- 'Ⲍ' => 'ⲍ',
- 'Ⲏ' => 'ⲏ',
- 'Ⲑ' => 'ⲑ',
- 'Ⲓ' => 'ⲓ',
- 'Ⲕ' => 'ⲕ',
- 'Ⲗ' => 'ⲗ',
- 'Ⲙ' => 'ⲙ',
- 'Ⲛ' => 'ⲛ',
- 'Ⲝ' => 'ⲝ',
- 'Ⲟ' => 'ⲟ',
- 'Ⲡ' => 'ⲡ',
- 'Ⲣ' => 'ⲣ',
- 'Ⲥ' => 'ⲥ',
- 'Ⲧ' => 'ⲧ',
- 'Ⲩ' => 'ⲩ',
- 'Ⲫ' => 'ⲫ',
- 'Ⲭ' => 'ⲭ',
- 'Ⲯ' => 'ⲯ',
- 'Ⲱ' => 'ⲱ',
- 'Ⲳ' => 'ⲳ',
- 'Ⲵ' => 'ⲵ',
- 'Ⲷ' => 'ⲷ',
- 'Ⲹ' => 'ⲹ',
- 'Ⲻ' => 'ⲻ',
- 'Ⲽ' => 'ⲽ',
- 'Ⲿ' => 'ⲿ',
- 'Ⳁ' => 'ⳁ',
- 'Ⳃ' => 'ⳃ',
- 'Ⳅ' => 'ⳅ',
- 'Ⳇ' => 'ⳇ',
- 'Ⳉ' => 'ⳉ',
- 'Ⳋ' => 'ⳋ',
- 'Ⳍ' => 'ⳍ',
- 'Ⳏ' => 'ⳏ',
- 'Ⳑ' => 'ⳑ',
- 'Ⳓ' => 'ⳓ',
- 'Ⳕ' => 'ⳕ',
- 'Ⳗ' => 'ⳗ',
- 'Ⳙ' => 'ⳙ',
- 'Ⳛ' => 'ⳛ',
- 'Ⳝ' => 'ⳝ',
- 'Ⳟ' => 'ⳟ',
- 'Ⳡ' => 'ⳡ',
- 'Ⳣ' => 'ⳣ',
- 'Ⳬ' => 'ⳬ',
- 'Ⳮ' => 'ⳮ',
- 'Ꙁ' => 'ꙁ',
- 'Ꙃ' => 'ꙃ',
- 'Ꙅ' => 'ꙅ',
- 'Ꙇ' => 'ꙇ',
- 'Ꙉ' => 'ꙉ',
- 'Ꙋ' => 'ꙋ',
- 'Ꙍ' => 'ꙍ',
- 'Ꙏ' => 'ꙏ',
- 'Ꙑ' => 'ꙑ',
- 'Ꙓ' => 'ꙓ',
- 'Ꙕ' => 'ꙕ',
- 'Ꙗ' => 'ꙗ',
- 'Ꙙ' => 'ꙙ',
- 'Ꙛ' => 'ꙛ',
- 'Ꙝ' => 'ꙝ',
- 'Ꙟ' => 'ꙟ',
- 'Ꙡ' => 'ꙡ',
- 'Ꙣ' => 'ꙣ',
- 'Ꙥ' => 'ꙥ',
- 'Ꙧ' => 'ꙧ',
- 'Ꙩ' => 'ꙩ',
- 'Ꙫ' => 'ꙫ',
- 'Ꙭ' => 'ꙭ',
- 'Ꚁ' => 'ꚁ',
- 'Ꚃ' => 'ꚃ',
- 'Ꚅ' => 'ꚅ',
- 'Ꚇ' => 'ꚇ',
- 'Ꚉ' => 'ꚉ',
- 'Ꚋ' => 'ꚋ',
- 'Ꚍ' => 'ꚍ',
- 'Ꚏ' => 'ꚏ',
- 'Ꚑ' => 'ꚑ',
- 'Ꚓ' => 'ꚓ',
- 'Ꚕ' => 'ꚕ',
- 'Ꚗ' => 'ꚗ',
- 'Ꜣ' => 'ꜣ',
- 'Ꜥ' => 'ꜥ',
- 'Ꜧ' => 'ꜧ',
- 'Ꜩ' => 'ꜩ',
- 'Ꜫ' => 'ꜫ',
- 'Ꜭ' => 'ꜭ',
- 'Ꜯ' => 'ꜯ',
- 'Ꜳ' => 'ꜳ',
- 'Ꜵ' => 'ꜵ',
- 'Ꜷ' => 'ꜷ',
- 'Ꜹ' => 'ꜹ',
- 'Ꜻ' => 'ꜻ',
- 'Ꜽ' => 'ꜽ',
- 'Ꜿ' => 'ꜿ',
- 'Ꝁ' => 'ꝁ',
- 'Ꝃ' => 'ꝃ',
- 'Ꝅ' => 'ꝅ',
- 'Ꝇ' => 'ꝇ',
- 'Ꝉ' => 'ꝉ',
- 'Ꝋ' => 'ꝋ',
- 'Ꝍ' => 'ꝍ',
- 'Ꝏ' => 'ꝏ',
- 'Ꝑ' => 'ꝑ',
- 'Ꝓ' => 'ꝓ',
- 'Ꝕ' => 'ꝕ',
- 'Ꝗ' => 'ꝗ',
- 'Ꝙ' => 'ꝙ',
- 'Ꝛ' => 'ꝛ',
- 'Ꝝ' => 'ꝝ',
- 'Ꝟ' => 'ꝟ',
- 'Ꝡ' => 'ꝡ',
- 'Ꝣ' => 'ꝣ',
- 'Ꝥ' => 'ꝥ',
- 'Ꝧ' => 'ꝧ',
- 'Ꝩ' => 'ꝩ',
- 'Ꝫ' => 'ꝫ',
- 'Ꝭ' => 'ꝭ',
- 'Ꝯ' => 'ꝯ',
- 'Ꝺ' => 'ꝺ',
- 'Ꝼ' => 'ꝼ',
- 'Ᵹ' => 'ᵹ',
- 'Ꝿ' => 'ꝿ',
- 'Ꞁ' => 'ꞁ',
- 'Ꞃ' => 'ꞃ',
- 'Ꞅ' => 'ꞅ',
- 'Ꞇ' => 'ꞇ',
- 'Ꞌ' => 'ꞌ',
- 'Ɥ' => 'ɥ',
- 'Ꞑ' => 'ꞑ',
- 'Ꞡ' => 'ꞡ',
- 'Ꞣ' => 'ꞣ',
- 'Ꞥ' => 'ꞥ',
- 'Ꞧ' => 'ꞧ',
- 'Ꞩ' => 'ꞩ',
- 'A' => 'a',
- 'B' => 'b',
- 'C' => 'c',
- 'D' => 'd',
- 'E' => 'e',
- 'F' => 'f',
- 'G' => 'g',
- 'H' => 'h',
- 'I' => 'i',
- 'J' => 'j',
- 'K' => 'k',
- 'L' => 'l',
- 'M' => 'm',
- 'N' => 'n',
- 'O' => 'o',
- 'P' => 'p',
- 'Q' => 'q',
- 'R' => 'r',
- 'S' => 's',
- 'T' => 't',
- 'U' => 'u',
- 'V' => 'v',
- 'W' => 'w',
- 'X' => 'x',
- 'Y' => 'y',
- 'Z' => 'z',
- '𐐀' => '𐐨',
- '𐐁' => '𐐩',
- '𐐂' => '𐐪',
- '𐐃' => '𐐫',
- '𐐄' => '𐐬',
- '𐐅' => '𐐭',
- '𐐆' => '𐐮',
- '𐐇' => '𐐯',
- '𐐈' => '𐐰',
- '𐐉' => '𐐱',
- '𐐊' => '𐐲',
- '𐐋' => '𐐳',
- '𐐌' => '𐐴',
- '𐐍' => '𐐵',
- '𐐎' => '𐐶',
- '𐐏' => '𐐷',
- '𐐐' => '𐐸',
- '𐐑' => '𐐹',
- '𐐒' => '𐐺',
- '𐐓' => '𐐻',
- '𐐔' => '𐐼',
- '𐐕' => '𐐽',
- '𐐖' => '𐐾',
- '𐐗' => '𐐿',
- '𐐘' => '𐑀',
- '𐐙' => '𐑁',
- '𐐚' => '𐑂',
- '𐐛' => '𐑃',
- '𐐜' => '𐑄',
- '𐐝' => '𐑅',
- '𐐞' => '𐑆',
- '𐐟' => '𐑇',
- '𐐠' => '𐑈',
- '𐐡' => '𐑉',
- '𐐢' => '𐑊',
- '𐐣' => '𐑋',
- '𐐤' => '𐑌',
- '𐐥' => '𐑍',
- '𐐦' => '𐑎',
- '𐐧' => '𐑏'
-);
diff --git a/includes/normal/Utf8CaseGenerate.php b/includes/normal/Utf8CaseGenerate.php
deleted file mode 100644
index adc3ef22..00000000
--- a/includes/normal/Utf8CaseGenerate.php
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php
-/**
- * This script generates Utf8Case.php from the Unicode Character Database
- * and supplementary files.
- *
- * Copyright © 2004,2008 Brion Vibber <brion@pobox.com>
- * http://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
- * @ingroup UtfNormal
- */
-
-if( PHP_SAPI != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
-
-require_once 'UtfNormalDefines.php';
-require_once 'UtfNormalUtil.php';
-
-$in = fopen("UnicodeData.txt", "rt" );
-if( !$in ) {
- print "Can't open UnicodeData.txt for reading.\n";
- print "If necessary, fetch this file from the internet:\n";
- print "http://www.unicode.org/Public/UNIDATA/UnicodeData.txt\n";
- exit(-1);
-}
-$wikiUpperChars = array();
-$wikiLowerChars = array();
-
-print "Reading character definitions...\n";
-while( false !== ($line = fgets( $in ) ) ) {
- $columns = explode(';', $line);
- $codepoint = $columns[0];
- $name = $columns[1];
- $simpleUpper = $columns[12];
- $simpleLower = $columns[13];
-
- $source = codepointToUtf8( hexdec( $codepoint ) );
- if( $simpleUpper ) {
- $wikiUpperChars[$source] = codepointToUtf8( hexdec( $simpleUpper ) );
- }
- if( $simpleLower ) {
- $wikiLowerChars[$source] = codepointToUtf8( hexdec( $simpleLower ) );
- }
-}
-fclose( $in );
-
-$out = fopen( "Utf8Case.php", "wt" );
-if( $out ) {
- $outUpperChars = escapeArray( $wikiUpperChars );
- $outLowerChars = escapeArray( $wikiLowerChars );
- $outdata = "<" . "?php
-/**
- * Simple 1:1 upper/lowercase switching arrays for utf-8 text.
- * Won't get context-sensitive things yet.
- *
- * Hack for bugs in ucfirst() and company
- *
- * These are pulled from memcached if possible, as this is faster than filling
- * up a big array manually.
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * Translation array to get upper case character
- */
-\$wikiUpperChars = $outUpperChars;
-
-/**
- * Translation array to get lower case character
- */
-\$wikiLowerChars = $outLowerChars;\n";
- fputs( $out, $outdata );
- fclose( $out );
- print "Wrote out Utf8Case.php\n";
-} else {
- print "Can't create file Utf8Case.php\n";
- exit(-1);
-}
-
-
-function escapeArray( $arr ) {
- return "array(\n" .
- implode( ",\n",
- array_map( "escapeLine",
- array_keys( $arr ),
- array_values( $arr ) ) ) .
- "\n)";
-}
-
-function escapeLine( $key, $val ) {
- $encKey = escapeSingleString( $key );
- $encVal = escapeSingleString( $val );
- return "\t'$encKey' => '$encVal'";
-}
diff --git a/includes/normal/Utf8Test.php b/includes/normal/Utf8Test.php
index c5c1be59..f4acc1eb 100644
--- a/includes/normal/Utf8Test.php
+++ b/includes/normal/Utf8Test.php
@@ -4,7 +4,7 @@
* http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -40,27 +40,27 @@ $verbose = false;
#$verbose = true;
$in = fopen( "UTF-8-test.txt", "rt" );
-if( !$in ) {
+if ( !$in ) {
print "Couldn't open UTF-8-test.txt -- can't run tests.\n";
print "If necessary, manually download this file. It can be obtained at\n";
print "http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt\n";
- exit(-1);
+ exit( -1 );
}
$columns = 0;
-while( false !== ( $line = fgets( $in ) ) ) {
+while ( false !== ( $line = fgets( $in ) ) ) {
$matches = array();
- if( preg_match( '/^(Here come the tests:\s*)\|$/', $line, $matches ) ) {
+ if ( preg_match( '/^(Here come the tests:\s*)\|$/', $line, $matches ) ) {
$columns = strpos( $line, '|' );
break;
}
}
-if( !$columns ) {
+if ( !$columns ) {
print "Something seems to be wrong; couldn't extract line length.\n";
print "Check that UTF-8-test.txt was downloaded correctly from\n";
print "http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt\n";
- exit(-1);
+ exit( -1 );
}
# print "$columns\n";
@@ -90,22 +90,26 @@ $test = '';
$failed = 0;
$success = 0;
$total = 0;
-while( false !== ( $line = fgets( $in ) ) ) {
+while ( false !== ( $line = fgets( $in ) ) ) {
$matches = array();
- if( preg_match( '/^(\d+)\s+(.*?)\s*\|/', $line, $matches ) ) {
+ if ( preg_match( '/^(\d+)\s+(.*?)\s*\|/', $line, $matches ) ) {
$section = $matches[1];
print $line;
continue;
}
- if( preg_match( '/^(\d+\.\d+\.\d+)\s*/', $line, $matches ) ) {
+ if ( preg_match( '/^(\d+\.\d+\.\d+)\s*/', $line, $matches ) ) {
$test = $matches[1];
- if( in_array( $test, $ignore ) ) {
+ if ( in_array( $test, $ignore ) ) {
continue;
}
- if( in_array( $test, $longTests ) ) {
+ if ( in_array( $test, $longTests ) ) {
$line = fgets( $in );
- for( $line = fgets( $in ); !preg_match( '/^\s+\|/', $line ); $line = fgets( $in ) ) {
+
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
+ for ( $line = fgets( $in ); !preg_match( '/^\s+\|/', $line ); $line = fgets( $in ) ) {
+ // @codingStandardsIgnoreEnd
+
testLine( $test, $line, $total, $success, $failed, $columns, $exceptions, $verbose );
}
} else {
@@ -114,15 +118,14 @@ while( false !== ( $line = fgets( $in ) ) ) {
}
}
-if( $failed ) {
+if ( $failed ) {
echo "\nFailed $failed tests.\n";
echo "UTF-8 DECODER TEST FAILED\n";
- exit (-1);
+ exit ( -1 );
}
echo "UTF-8 DECODER TEST SUCCESS!\n";
-exit (0);
-
+exit ( 0 );
function testLine( $test, $line, &$total, &$success, &$failed, $columns, $exceptions, $verbose ) {
$stripped = $line;
@@ -130,24 +133,24 @@ function testLine( $test, $line, &$total, &$success, &$failed, $columns, $except
$same = ( $line == $stripped );
$len = mb_strlen( substr( $stripped, 0, strpos( $stripped, '|' ) ) );
- if( $len == 0 ) {
+ if ( $len == 0 ) {
$len = strlen( substr( $stripped, 0, strpos( $stripped, '|' ) ) );
}
- $ok = $same ^ ($test >= 3 );
+ $ok = $same ^ ( $test >= 3 );
$ok ^= in_array( $test, $exceptions );
- $ok &= ($columns == $len);
+ $ok &= ( $columns == $len );
$total++;
- if( $ok ) {
+ if ( $ok ) {
$success++;
} else {
$failed++;
}
- if( $verbose || !$ok ) {
+ if ( $verbose || !$ok ) {
print str_replace( "\n", "$len\n", $stripped );
}
}
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
index 5a091afc..8204f974 100644
--- a/includes/normal/UtfNormal.php
+++ b/includes/normal/UtfNormal.php
@@ -3,7 +3,7 @@
* Unicode normalization routines
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -50,21 +50,20 @@ class UtfNormal {
* For using the ICU wrapper
*/
const UNORM_NONE = 1;
- const UNORM_NFD = 2;
+ const UNORM_NFD = 2;
const UNORM_NFKD = 3;
- const UNORM_NFC = 4;
+ const UNORM_NFC = 4;
const UNORM_NFKC = 5;
- const UNORM_FCD = 6;
+ const UNORM_FCD = 6;
const UNORM_DEFAULT = self::UNORM_NFC;
- static $utfCombiningClass = null;
- static $utfCanonicalComp = null;
- static $utfCanonicalDecomp = null;
+ public static $utfCombiningClass = null;
+ public static $utfCanonicalComp = null;
+ public static $utfCanonicalDecomp = null;
# Load compatibility decompositions on demand if they are needed.
- static $utfCompatibilityDecomp = null;
-
- static $utfCheckNFC;
+ public static $utfCompatibilityDecomp = null;
+ public static $utfCheckNFC;
/**
* The ultimate convenience function! Clean up invalid UTF-8 sequences,
@@ -77,21 +76,21 @@ class UtfNormal {
* @return string a clean, shiny, normalized UTF-8 string
*/
static function cleanUp( $string ) {
- if( NORMALIZE_ICU ) {
+ if ( NORMALIZE_ICU ) {
$string = self::replaceForNativeNormalize( $string );
# UnicodeString constructor fails if the string ends with a
# head byte. Add a junk char at the end, we'll strip it off.
return rtrim( utf8_normalize( $string . "\x01", self::UNORM_NFC ), "\x01" );
- } elseif( NORMALIZE_INTL ) {
+ } elseif ( NORMALIZE_INTL ) {
$string = self::replaceForNativeNormalize( $string );
$norm = normalizer_normalize( $string, Normalizer::FORM_C );
- if( $norm === null || $norm === false ) {
+ if ( $norm === null || $norm === false ) {
# normalizer_normalize will either return false or null
# (depending on which doc you read) if invalid utf8 string.
# quickIsNFCVerify cleans up invalid sequences.
- if( UtfNormal::quickIsNFCVerify( $string ) ) {
+ if ( UtfNormal::quickIsNFCVerify( $string ) ) {
# if that's true, the string is actually already normal.
return $string;
} else {
@@ -101,7 +100,7 @@ class UtfNormal {
} else {
return $norm;
}
- } elseif( UtfNormal::quickIsNFCVerify( $string ) ) {
+ } elseif ( UtfNormal::quickIsNFCVerify( $string ) ) {
# Side effect -- $string has had UTF-8 errors cleaned up.
return $string;
} else {
@@ -118,11 +117,11 @@ class UtfNormal {
* @return string a UTF-8 string in normal form C
*/
static function toNFC( $string ) {
- if( NORMALIZE_INTL )
+ if ( NORMALIZE_INTL )
return normalizer_normalize( $string, Normalizer::FORM_C );
- elseif( NORMALIZE_ICU )
+ elseif ( NORMALIZE_ICU )
return utf8_normalize( $string, self::UNORM_NFC );
- elseif( UtfNormal::quickIsNFC( $string ) )
+ elseif ( UtfNormal::quickIsNFC( $string ) )
return $string;
else
return UtfNormal::NFC( $string );
@@ -136,11 +135,11 @@ class UtfNormal {
* @return string a UTF-8 string in normal form D
*/
static function toNFD( $string ) {
- if( NORMALIZE_INTL )
+ if ( NORMALIZE_INTL )
return normalizer_normalize( $string, Normalizer::FORM_D );
- elseif( NORMALIZE_ICU )
+ elseif ( NORMALIZE_ICU )
return utf8_normalize( $string, self::UNORM_NFD );
- elseif( preg_match( '/[\x80-\xff]/', $string ) )
+ elseif ( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFD( $string );
else
return $string;
@@ -155,11 +154,11 @@ class UtfNormal {
* @return string a UTF-8 string in normal form KC
*/
static function toNFKC( $string ) {
- if( NORMALIZE_INTL )
+ if ( NORMALIZE_INTL )
return normalizer_normalize( $string, Normalizer::FORM_KC );
- elseif( NORMALIZE_ICU )
+ elseif ( NORMALIZE_ICU )
return utf8_normalize( $string, self::UNORM_NFKC );
- elseif( preg_match( '/[\x80-\xff]/', $string ) )
+ elseif ( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFKC( $string );
else
return $string;
@@ -174,11 +173,11 @@ class UtfNormal {
* @return string a UTF-8 string in normal form KD
*/
static function toNFKD( $string ) {
- if( NORMALIZE_INTL )
+ if ( NORMALIZE_INTL )
return normalizer_normalize( $string, Normalizer::FORM_KD );
- elseif( NORMALIZE_ICU )
+ elseif ( NORMALIZE_ICU )
return utf8_normalize( $string, self::UNORM_NFKD );
- elseif( preg_match( '/[\x80-\xff]/', $string ) )
+ elseif ( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFKD( $string );
else
return $string;
@@ -189,7 +188,7 @@ class UtfNormal {
* @private
*/
static function loadData() {
- if( !isset( self::$utfCombiningClass ) ) {
+ if ( !isset( self::$utfCombiningClass ) ) {
require_once __DIR__ . '/UtfNormalData.inc';
}
}
@@ -203,34 +202,35 @@ class UtfNormal {
static function quickIsNFC( $string ) {
# ASCII is always valid NFC!
# If it's pure ASCII, let it through.
- if( !preg_match( '/[\x80-\xff]/', $string ) ) return true;
+ if ( !preg_match( '/[\x80-\xff]/', $string ) ) return true;
UtfNormal::loadData();
$len = strlen( $string );
- for( $i = 0; $i < $len; $i++ ) {
+ for ( $i = 0; $i < $len; $i++ ) {
$c = $string[$i];
$n = ord( $c );
- if( $n < 0x80 ) {
+ if ( $n < 0x80 ) {
continue;
- } elseif( $n >= 0xf0 ) {
+ } elseif ( $n >= 0xf0 ) {
$c = substr( $string, $i, 4 );
$i += 3;
- } elseif( $n >= 0xe0 ) {
+ } elseif ( $n >= 0xe0 ) {
$c = substr( $string, $i, 3 );
$i += 2;
- } elseif( $n >= 0xc0 ) {
+ } elseif ( $n >= 0xc0 ) {
$c = substr( $string, $i, 2 );
$i++;
}
- if( isset( self::$utfCheckNFC[$c] ) ) {
+ if ( isset( self::$utfCheckNFC[$c] ) ) {
# If it's NO or MAYBE, bail and do the slow check.
return false;
}
- if( isset( self::$utfCombiningClass[$c] ) ) {
+ if ( isset( self::$utfCombiningClass[$c] ) ) {
# Combining character? We might have to do sorting, at least.
return false;
}
}
+
return true;
}
@@ -247,10 +247,10 @@ class UtfNormal {
# ASCII is always valid NFC!
# If we're only ever given plain ASCII, we can avoid the overhead
# of initializing the decomposition tables by skipping out early.
- if( !preg_match( '/[\x80-\xff]/', $string ) ) return true;
+ if ( !preg_match( '/[\x80-\xff]/', $string ) ) return true;
static $checkit = null, $tailBytes = null, $utfCheckOrCombining = null;
- if( !isset( $checkit ) ) {
+ if ( !isset( $checkit ) ) {
# Load/build some scary lookup tables...
UtfNormal::loadData();
@@ -258,30 +258,30 @@ class UtfNormal {
# Head bytes for sequences which we should do further validity checks
$checkit = array_flip( array_map( 'chr',
- array( 0xc0, 0xc1, 0xe0, 0xed, 0xef,
- 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
- 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff ) ) );
+ array( 0xc0, 0xc1, 0xe0, 0xed, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff ) ) );
# Each UTF-8 head byte is followed by a certain
# number of tail bytes.
$tailBytes = array();
- for( $n = 0; $n < 256; $n++ ) {
- if( $n < 0xc0 ) {
+ for ( $n = 0; $n < 256; $n++ ) {
+ if ( $n < 0xc0 ) {
$remaining = 0;
- } elseif( $n < 0xe0 ) {
+ } elseif ( $n < 0xe0 ) {
$remaining = 1;
- } elseif( $n < 0xf0 ) {
+ } elseif ( $n < 0xf0 ) {
$remaining = 2;
- } elseif( $n < 0xf8 ) {
+ } elseif ( $n < 0xf8 ) {
$remaining = 3;
- } elseif( $n < 0xfc ) {
+ } elseif ( $n < 0xfc ) {
$remaining = 4;
- } elseif( $n < 0xfe ) {
+ } elseif ( $n < 0xfe ) {
$remaining = 5;
} else {
$remaining = 0;
}
- $tailBytes[chr($n)] = $remaining;
+ $tailBytes[chr( $n )] = $remaining;
}
}
@@ -297,10 +297,10 @@ class UtfNormal {
$looksNormal = true;
$base = 0;
$replace = array();
- foreach( $matches[1] as $str ) {
+ foreach ( $matches[1] as $str ) {
$chunk = strlen( $str );
- if( $str[0] < "\x80" ) {
+ if ( $str[0] < "\x80" ) {
# ASCII chunk: guaranteed to be valid UTF-8
# and in normal form C, so skip over it.
$base += $chunk;
@@ -317,30 +317,30 @@ class UtfNormal {
$head = '';
$len = $chunk + 1; # Counting down is faster. I'm *so* sorry.
- for( $i = -1; --$len; ) {
+ for ( $i = -1; --$len; ) {
$remaining = $tailBytes[$c = $str[++$i]];
- if( $remaining ) {
+ if ( $remaining ) {
# UTF-8 head byte!
$sequence = $head = $c;
do {
# Look for the defined number of tail bytes...
- if( --$len && ( $c = $str[++$i] ) >= "\x80" && $c < "\xc0" ) {
+ if ( --$len && ( $c = $str[++$i] ) >= "\x80" && $c < "\xc0" ) {
# Legal tail bytes are nice.
$sequence .= $c;
} else {
- if( 0 == $len ) {
+ if ( 0 == $len ) {
# Premature end of string!
# Drop a replacement character into output to
# represent the invalid UTF-8 sequence.
$replace[] = array( UTF8_REPLACEMENT,
- $base + $i + 1 - strlen( $sequence ),
- strlen( $sequence ) );
+ $base + $i + 1 - strlen( $sequence ),
+ strlen( $sequence ) );
break 2;
} else {
# Illegal tail byte; abandon the sequence.
$replace[] = array( UTF8_REPLACEMENT,
- $base + $i - strlen( $sequence ),
- strlen( $sequence ) );
+ $base + $i - strlen( $sequence ),
+ strlen( $sequence ) );
# Back up and reprocess this byte; it may itself
# be a legal ASCII or UTF-8 sequence head.
--$i;
@@ -348,59 +348,60 @@ class UtfNormal {
continue 2;
}
}
- } while( --$remaining );
+ } while ( --$remaining );
- if( isset( $checkit[$head] ) ) {
+ if ( isset( $checkit[$head] ) ) {
# Do some more detailed validity checks, for
# invalid characters and illegal sequences.
- if( $head == "\xed" ) {
+ if ( $head == "\xed" ) {
# 0xed is relatively frequent in Korean, which
# abuts the surrogate area, so we're doing
# this check separately to speed things up.
- if( $sequence >= UTF8_SURROGATE_FIRST ) {
+ if ( $sequence >= UTF8_SURROGATE_FIRST ) {
# Surrogates are legal only in UTF-16 code.
# They are totally forbidden here in UTF-8
# utopia.
$replace[] = array( UTF8_REPLACEMENT,
- $base + $i + 1 - strlen( $sequence ),
- strlen( $sequence ) );
+ $base + $i + 1 - strlen( $sequence ),
+ strlen( $sequence ) );
$head = '';
continue;
}
} else {
# Slower, but rarer checks...
$n = ord( $head );
- if(
+ if (
# "Overlong sequences" are those that are syntactically
# correct but use more UTF-8 bytes than are necessary to
# encode a character. Naïve string comparisons can be
# tricked into failing to see a match for an ASCII
# character, for instance, which can be a security hole
# if blacklist checks are being used.
- ($n < 0xc2 && $sequence <= UTF8_OVERLONG_A)
- || ($n == 0xe0 && $sequence <= UTF8_OVERLONG_B)
- || ($n == 0xf0 && $sequence <= UTF8_OVERLONG_C)
+ ( $n < 0xc2 && $sequence <= UTF8_OVERLONG_A )
+ || ( $n == 0xe0 && $sequence <= UTF8_OVERLONG_B )
+ || ( $n == 0xf0 && $sequence <= UTF8_OVERLONG_C )
# U+FFFE and U+FFFF are explicitly forbidden in Unicode.
- || ($n == 0xef &&
- ($sequence == UTF8_FFFE)
- || ($sequence == UTF8_FFFF) )
+ || ( $n == 0xef &&
+ ( $sequence == UTF8_FFFE )
+ || ( $sequence == UTF8_FFFF ) )
# Unicode has been limited to 21 bits; longer
# sequences are not allowed.
- || ($n >= 0xf0 && $sequence > UTF8_MAX) ) {
+ || ( $n >= 0xf0 && $sequence > UTF8_MAX )
+ ) {
$replace[] = array( UTF8_REPLACEMENT,
- $base + $i + 1 - strlen( $sequence ),
- strlen( $sequence ) );
+ $base + $i + 1 - strlen( $sequence ),
+ strlen( $sequence ) );
$head = '';
continue;
}
}
}
- if( isset( $utfCheckOrCombining[$sequence] ) ) {
+ if ( isset( $utfCheckOrCombining[$sequence] ) ) {
# If it's NO or MAYBE, we'll have to rip
# the string apart and put it back together.
# That's going to be mighty slow.
@@ -409,12 +410,12 @@ class UtfNormal {
# The sequence is legal!
$head = '';
- } elseif( $c < "\x80" ) {
+ } elseif ( $c < "\x80" ) {
# ASCII byte.
$head = '';
- } elseif( $c < "\xc0" ) {
+ } elseif ( $c < "\xc0" ) {
# Illegal tail bytes
- if( $head == '' ) {
+ if ( $head == '' ) {
# Out of the blue!
$replace[] = array( UTF8_REPLACEMENT, $base + $i, 1 );
} else {
@@ -431,23 +432,24 @@ class UtfNormal {
}
$base += $chunk;
}
- if( count( $replace ) ) {
+ if ( count( $replace ) ) {
# There were illegal UTF-8 sequences we need to fix up.
$out = '';
$last = 0;
- foreach( $replace as $rep ) {
+ foreach ( $replace as $rep ) {
list( $replacement, $start, $length ) = $rep;
- if( $last < $start ) {
+ if ( $last < $start ) {
$out .= substr( $string, $last, $start - $last );
}
$out .= $replacement;
$last = $start + $length;
}
- if( $last < strlen( $string ) ) {
+ if ( $last < strlen( $string ) ) {
$out .= substr( $string, $last );
}
$string = $out;
}
+
return $looksNormal;
}
@@ -490,14 +492,14 @@ class UtfNormal {
* @private
*/
static function NFKD( $string ) {
- if( !isset( self::$utfCompatibilityDecomp ) ) {
+ if ( !isset( self::$utfCompatibilityDecomp ) ) {
require_once 'UtfNormalDataK.inc';
}
+
return self::fastCombiningSort(
self::fastDecompose( $string, self::$utfCompatibilityDecomp ) );
}
-
/**
* Perform decomposition of a UTF-8 string into either D or KD form
* (depending on which decomposition map is passed to us).
@@ -511,45 +513,45 @@ class UtfNormal {
UtfNormal::loadData();
$len = strlen( $string );
$out = '';
- for( $i = 0; $i < $len; $i++ ) {
+ for ( $i = 0; $i < $len; $i++ ) {
$c = $string[$i];
$n = ord( $c );
- if( $n < 0x80 ) {
+ if ( $n < 0x80 ) {
# ASCII chars never decompose
# THEY ARE IMMORTAL
$out .= $c;
continue;
- } elseif( $n >= 0xf0 ) {
+ } elseif ( $n >= 0xf0 ) {
$c = substr( $string, $i, 4 );
$i += 3;
- } elseif( $n >= 0xe0 ) {
+ } elseif ( $n >= 0xe0 ) {
$c = substr( $string, $i, 3 );
$i += 2;
- } elseif( $n >= 0xc0 ) {
+ } elseif ( $n >= 0xc0 ) {
$c = substr( $string, $i, 2 );
$i++;
}
- if( isset( $map[$c] ) ) {
+ if ( isset( $map[$c] ) ) {
$out .= $map[$c];
continue;
} else {
- if( $c >= UTF8_HANGUL_FIRST && $c <= UTF8_HANGUL_LAST ) {
+ if ( $c >= UTF8_HANGUL_FIRST && $c <= UTF8_HANGUL_LAST ) {
# Decompose a hangul syllable into jamo;
# hardcoded for three-byte UTF-8 sequence.
# A lookup table would be slightly faster,
# but adds a lot of memory & disk needs.
#
- $index = ( (ord( $c[0] ) & 0x0f) << 12
- | (ord( $c[1] ) & 0x3f) << 6
- | (ord( $c[2] ) & 0x3f) )
- - UNICODE_HANGUL_FIRST;
+ $index = ( ( ord( $c[0] ) & 0x0f ) << 12
+ | ( ord( $c[1] ) & 0x3f ) << 6
+ | ( ord( $c[2] ) & 0x3f ) )
+ - UNICODE_HANGUL_FIRST;
$l = intval( $index / UNICODE_HANGUL_NCOUNT );
- $v = intval( ($index % UNICODE_HANGUL_NCOUNT) / UNICODE_HANGUL_TCOUNT);
+ $v = intval( ( $index % UNICODE_HANGUL_NCOUNT ) / UNICODE_HANGUL_TCOUNT );
$t = $index % UNICODE_HANGUL_TCOUNT;
$out .= "\xe1\x84" . chr( 0x80 + $l ) . "\xe1\x85" . chr( 0xa1 + $v );
- if( $t >= 25 ) {
+ if ( $t >= 25 ) {
$out .= "\xe1\x87" . chr( 0x80 + $t - 25 );
- } elseif( $t ) {
+ } elseif ( $t ) {
$out .= "\xe1\x86" . chr( 0xa7 + $t );
}
continue;
@@ -557,6 +559,7 @@ class UtfNormal {
}
$out .= $c;
}
+
return $out;
}
@@ -573,23 +576,23 @@ class UtfNormal {
$out = '';
$combiners = array();
$lastClass = -1;
- for( $i = 0; $i < $len; $i++ ) {
+ for ( $i = 0; $i < $len; $i++ ) {
$c = $string[$i];
$n = ord( $c );
- if( $n >= 0x80 ) {
- if( $n >= 0xf0 ) {
+ if ( $n >= 0x80 ) {
+ if ( $n >= 0xf0 ) {
$c = substr( $string, $i, 4 );
$i += 3;
- } elseif( $n >= 0xe0 ) {
+ } elseif ( $n >= 0xe0 ) {
$c = substr( $string, $i, 3 );
$i += 2;
- } elseif( $n >= 0xc0 ) {
+ } elseif ( $n >= 0xc0 ) {
$c = substr( $string, $i, 2 );
$i++;
}
- if( isset( self::$utfCombiningClass[$c] ) ) {
+ if ( isset( self::$utfCombiningClass[$c] ) ) {
$lastClass = self::$utfCombiningClass[$c];
- if( isset( $combiners[$lastClass] ) ) {
+ if ( isset( $combiners[$lastClass] ) ) {
$combiners[$lastClass] .= $c;
} else {
$combiners[$lastClass] = $c;
@@ -597,7 +600,7 @@ class UtfNormal {
continue;
}
}
- if( $lastClass ) {
+ if ( $lastClass ) {
ksort( $combiners );
$out .= implode( '', $combiners );
$combiners = array();
@@ -605,10 +608,11 @@ class UtfNormal {
$out .= $c;
$lastClass = 0;
}
- if( $lastClass ) {
+ if ( $lastClass ) {
ksort( $combiners );
$out .= implode( '', $combiners );
}
+
return $out;
}
@@ -616,8 +620,10 @@ class UtfNormal {
* Produces canonically composed sequences, i.e. normal form C or KC.
*
* @private
- * @param string $string a valid UTF-8 string in sorted normal form D or KD. Input is not validated.
- * @return string a UTF-8 string with canonical precomposed characters used where possible
+ * @param string $string a valid UTF-8 string in sorted normal form D or KD.
+ * Input is not validated.
+ * @return string a UTF-8 string with canonical precomposed characters used
+ * where possible.
*/
static function fastCompose( $string ) {
UtfNormal::loadData();
@@ -627,12 +633,12 @@ class UtfNormal {
$lastHangul = 0;
$startChar = '';
$combining = '';
- $x1 = ord(substr(UTF8_HANGUL_VBASE, 0, 1));
- $x2 = ord(substr(UTF8_HANGUL_TEND, 0, 1));
- for( $i = 0; $i < $len; $i++ ) {
+ $x1 = ord( substr( UTF8_HANGUL_VBASE, 0, 1 ) );
+ $x2 = ord( substr( UTF8_HANGUL_TEND, 0, 1 ) );
+ for ( $i = 0; $i < $len; $i++ ) {
$c = $string[$i];
$n = ord( $c );
- if( $n < 0x80 ) {
+ if ( $n < 0x80 ) {
# No combining characters here...
$out .= $startChar;
$out .= $combining;
@@ -640,25 +646,26 @@ class UtfNormal {
$combining = '';
$lastClass = 0;
continue;
- } elseif( $n >= 0xf0 ) {
+ } elseif ( $n >= 0xf0 ) {
$c = substr( $string, $i, 4 );
$i += 3;
- } elseif( $n >= 0xe0 ) {
+ } elseif ( $n >= 0xe0 ) {
$c = substr( $string, $i, 3 );
$i += 2;
- } elseif( $n >= 0xc0 ) {
+ } elseif ( $n >= 0xc0 ) {
$c = substr( $string, $i, 2 );
$i++;
}
$pair = $startChar . $c;
- if( $n > 0x80 ) {
- if( isset( self::$utfCombiningClass[$c] ) ) {
+ if ( $n > 0x80 ) {
+ if ( isset( self::$utfCombiningClass[$c] ) ) {
# A combining char; see what we can do with it
$class = self::$utfCombiningClass[$c];
- if( !empty( $startChar ) &&
+ if ( !empty( $startChar ) &&
$lastClass < $class &&
$class > 0 &&
- isset( self::$utfCanonicalComp[$pair] ) ) {
+ isset( self::$utfCanonicalComp[$pair] )
+ ) {
$startChar = self::$utfCanonicalComp[$pair];
$class = 0;
} else {
@@ -670,56 +677,58 @@ class UtfNormal {
}
}
# New start char
- if( $lastClass == 0 ) {
- if( isset( self::$utfCanonicalComp[$pair] ) ) {
+ if ( $lastClass == 0 ) {
+ if ( isset( self::$utfCanonicalComp[$pair] ) ) {
$startChar = self::$utfCanonicalComp[$pair];
$lastHangul = 0;
continue;
}
- if( $n >= $x1 && $n <= $x2 ) {
+ if ( $n >= $x1 && $n <= $x2 ) {
# WARNING: Hangul code is painfully slow.
# I apologize for this ugly, ugly code; however
# performance is even more teh suck if we call
# out to nice clean functions. Lookup tables are
# marginally faster, but require a lot of space.
#
- if( $c >= UTF8_HANGUL_VBASE &&
+ if ( $c >= UTF8_HANGUL_VBASE &&
$c <= UTF8_HANGUL_VEND &&
$startChar >= UTF8_HANGUL_LBASE &&
- $startChar <= UTF8_HANGUL_LEND ) {
+ $startChar <= UTF8_HANGUL_LEND
+ ) {
#
#$lIndex = utf8ToCodepoint( $startChar ) - UNICODE_HANGUL_LBASE;
#$vIndex = utf8ToCodepoint( $c ) - UNICODE_HANGUL_VBASE;
$lIndex = ord( $startChar[2] ) - 0x80;
- $vIndex = ord( $c[2] ) - 0xa1;
+ $vIndex = ord( $c[2] ) - 0xa1;
$hangulPoint = UNICODE_HANGUL_FIRST +
UNICODE_HANGUL_TCOUNT *
- (UNICODE_HANGUL_VCOUNT * $lIndex + $vIndex);
+ ( UNICODE_HANGUL_VCOUNT * $lIndex + $vIndex );
# Hardcode the limited-range UTF-8 conversion:
$startChar = chr( $hangulPoint >> 12 & 0x0f | 0xe0 ) .
- chr( $hangulPoint >> 6 & 0x3f | 0x80 ) .
- chr( $hangulPoint & 0x3f | 0x80 );
+ chr( $hangulPoint >> 6 & 0x3f | 0x80 ) .
+ chr( $hangulPoint & 0x3f | 0x80 );
$lastHangul = 0;
continue;
- } elseif( $c >= UTF8_HANGUL_TBASE &&
- $c <= UTF8_HANGUL_TEND &&
- $startChar >= UTF8_HANGUL_FIRST &&
- $startChar <= UTF8_HANGUL_LAST &&
- !$lastHangul ) {
+ } elseif ( $c >= UTF8_HANGUL_TBASE &&
+ $c <= UTF8_HANGUL_TEND &&
+ $startChar >= UTF8_HANGUL_FIRST &&
+ $startChar <= UTF8_HANGUL_LAST &&
+ !$lastHangul
+ ) {
# $tIndex = utf8ToCodepoint( $c ) - UNICODE_HANGUL_TBASE;
$tIndex = ord( $c[2] ) - 0xa7;
- if( $tIndex < 0 ) $tIndex = ord( $c[2] ) - 0x80 + (0x11c0 - 0x11a7);
+ if ( $tIndex < 0 ) $tIndex = ord( $c[2] ) - 0x80 + ( 0x11c0 - 0x11a7 );
# Increment the code point by $tIndex, without
# the function overhead of decoding and recoding UTF-8
#
$tail = ord( $startChar[2] ) + $tIndex;
- if( $tail > 0xbf ) {
+ if ( $tail > 0xbf ) {
$tail -= 0x40;
$mid = ord( $startChar[1] ) + 1;
- if( $mid > 0xbf ) {
+ if ( $mid > 0xbf ) {
$startChar[0] = chr( ord( $startChar[0] ) + 1 );
$mid -= 0x40;
}
@@ -741,6 +750,7 @@ class UtfNormal {
$lastHangul = 0;
}
$out .= $startChar . $combining;
+
return $out;
}
@@ -753,11 +763,13 @@ class UtfNormal {
static function placebo( $string ) {
$len = strlen( $string );
$out = '';
- for( $i = 0; $i < $len; $i++ ) {
+ for ( $i = 0; $i < $len; $i++ ) {
$out .= $string[$i];
}
+
return $out;
}
+
/**
* Function to replace some characters that we don't want
* but most of the native normalize functions keep.
@@ -772,6 +784,7 @@ class UtfNormal {
$string );
$string = str_replace( UTF8_FFFE, UTF8_REPLACEMENT, $string );
$string = str_replace( UTF8_FFFF, UTF8_REPLACEMENT, $string );
+
return $string;
}
}
diff --git a/includes/normal/UtfNormalBench.php b/includes/normal/UtfNormalBench.php
index 89de9290..bd2bc4e4 100644
--- a/includes/normal/UtfNormalBench.php
+++ b/includes/normal/UtfNormalBench.php
@@ -3,7 +3,7 @@
* Approximate benchmark for some basic operations.
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -24,11 +24,11 @@
* @ingroup UtfNormal
*/
-if( PHP_SAPI != 'cli' ) {
+if ( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
-if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
+if ( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
dl( 'php_utfnormal.so' );
}
@@ -47,7 +47,7 @@ $testfiles = array(
);
$normalizer = new UtfNormal;
UtfNormal::loadData();
-foreach( $testfiles as $file => $desc ) {
+foreach ( $testfiles as $file => $desc ) {
benchmarkTest( $normalizer, $file, $desc );
}
@@ -67,11 +67,12 @@ function benchmarkTest( &$u, $filename, $desc ) {
# 'NFD', 'NFKD',
array( 'fastDecompose', 'fastCombiningSort', 'fastCompose' ),
# 'quickIsNFC', 'quickIsNFCVerify',
- );
- foreach( $forms as $form ) {
- if( is_array( $form ) ) {
+ );
+
+ foreach ( $forms as $form ) {
+ if ( is_array( $form ) ) {
$str = $data;
- foreach( $form as $step ) {
+ foreach ( $form as $step ) {
$str = benchmarkForm( $u, $str, $step );
}
} else {
@@ -80,29 +81,25 @@ function benchmarkTest( &$u, $filename, $desc ) {
}
}
-function benchTime() {
- $st = explode( ' ', microtime() );
- return (float)$st[0] + (float)$st[1];
-}
-
function benchmarkForm( &$u, &$data, $form ) {
- #$start = benchTime();
- for( $i = 0; $i < BENCH_CYCLES; $i++ ) {
- $start = benchTime();
+ #$start = microtime( true );
+ for ( $i = 0; $i < BENCH_CYCLES; $i++ ) {
+ $start = microtime( true );
$out = $u->$form( $data, UtfNormal::$utfCanonicalDecomp );
- $deltas[] = (benchTime() - $start);
+ $deltas[] = ( microtime( true ) - $start );
}
- #$delta = (benchTime() - $start) / BENCH_CYCLES;
+ #$delta = (microtime( true ) - $start) / BENCH_CYCLES;
sort( $deltas );
$delta = $deltas[0]; # Take shortest time
$rate = intval( strlen( $data ) / $delta );
- $same = (0 == strcmp( $data, $out ) );
+ $same = ( 0 == strcmp( $data, $out ) );
printf( " %20s %6.1fms %12s bytes/s (%s)\n",
$form,
- $delta*1000.0,
+ $delta * 1000.0,
number_format( $rate ),
- ($same ? 'no change' : 'changed' ) );
+ ( $same ? 'no change' : 'changed' ) );
+
return $out;
}
diff --git a/includes/normal/UtfNormalData.inc b/includes/normal/UtfNormalData.inc
index 68cc1ef4..5755f6b9 100644
--- a/includes/normal/UtfNormalData.inc
+++ b/includes/normal/UtfNormalData.inc
@@ -5,7 +5,8 @@
*
* @file
*/
-
+// @codingStandardsIgnoreFile
+
UtfNormal::$utfCombiningClass = unserialize( 'a:606:{s:2:"̀";i:230;s:2:"́";i:230;s:2:"̂";i:230;s:2:"̃";i:230;s:2:"̄";i:230;s:2:"̅";i:230;s:2:"̆";i:230;s:2:"̇";i:230;s:2:"̈";i:230;s:2:"̉";i:230;s:2:"̊";i:230;s:2:"̋";i:230;s:2:"̌";i:230;s:2:"̍";i:230;s:2:"̎";i:230;s:2:"̏";i:230;s:2:"̐";i:230;s:2:"̑";i:230;s:2:"̒";i:230;s:2:"̓";i:230;s:2:"̔";i:230;s:2:"̕";i:232;s:2:"̖";i:220;s:2:"̗";i:220;s:2:"̘";i:220;s:2:"̙";i:220;s:2:"̚";i:232;s:2:"̛";i:216;s:2:"̜";i:220;s:2:"̝";i:220;s:2:"̞";i:220;s:2:"̟";i:220;s:2:"̠";i:220;s:2:"̡";i:202;s:2:"̢";i:202;s:2:"̣";i:220;s:2:"̤";i:220;s:2:"̥";i:220;s:2:"̦";i:220;s:2:"̧";i:202;s:2:"̨";i:202;s:2:"̩";i:220;s:2:"̪";i:220;s:2:"̫";i:220;s:2:"̬";i:220;s:2:"̭";i:220;s:2:"̮";i:220;s:2:"̯";i:220;s:2:"̰";i:220;s:2:"̱";i:220;s:2:"̲";i:220;s:2:"̳";i:220;s:2:"̴";i:1;s:2:"̵";i:1;s:2:"̶";i:1;s:2:"̷";i:1;s:2:"̸";i:1;s:2:"̹";i:220;s:2:"̺";i:220;s:2:"̻";i:220;s:2:"̼";i:220;s:2:"̽";i:230;s:2:"̾";i:230;s:2:"̿";i:230;s:2:"̀";i:230;s:2:"́";i:230;s:2:"͂";i:230;s:2:"̓";i:230;s:2:"̈́";i:230;s:2:"ͅ";i:240;s:2:"͆";i:230;s:2:"͇";i:220;s:2:"͈";i:220;s:2:"͉";i:220;s:2:"͊";i:230;s:2:"͋";i:230;s:2:"͌";i:230;s:2:"͍";i:220;s:2:"͎";i:220;s:2:"͐";i:230;s:2:"͑";i:230;s:2:"͒";i:230;s:2:"͓";i:220;s:2:"͔";i:220;s:2:"͕";i:220;s:2:"͖";i:220;s:2:"͗";i:230;s:2:"͘";i:232;s:2:"͙";i:220;s:2:"͚";i:220;s:2:"͛";i:230;s:2:"͜";i:233;s:2:"͝";i:234;s:2:"͞";i:234;s:2:"͟";i:233;s:2:"͠";i:234;s:2:"͡";i:234;s:2:"͢";i:233;s:2:"ͣ";i:230;s:2:"ͤ";i:230;s:2:"ͥ";i:230;s:2:"ͦ";i:230;s:2:"ͧ";i:230;s:2:"ͨ";i:230;s:2:"ͩ";i:230;s:2:"ͪ";i:230;s:2:"ͫ";i:230;s:2:"ͬ";i:230;s:2:"ͭ";i:230;s:2:"ͮ";i:230;s:2:"ͯ";i:230;s:2:"҃";i:230;s:2:"҄";i:230;s:2:"҅";i:230;s:2:"҆";i:230;s:2:"҇";i:230;s:2:"֑";i:220;s:2:"֒";i:230;s:2:"֓";i:230;s:2:"֔";i:230;s:2:"֕";i:230;s:2:"֖";i:220;s:2:"֗";i:230;s:2:"֘";i:230;s:2:"֙";i:230;s:2:"֚";i:222;s:2:"֛";i:220;s:2:"֜";i:230;s:2:"֝";i:230;s:2:"֞";i:230;s:2:"֟";i:230;s:2:"֠";i:230;s:2:"֡";i:230;s:2:"֢";i:220;s:2:"֣";i:220;s:2:"֤";i:220;s:2:"֥";i:220;s:2:"֦";i:220;s:2:"֧";i:220;s:2:"֨";i:230;s:2:"֩";i:230;s:2:"֪";i:220;s:2:"֫";i:230;s:2:"֬";i:230;s:2:"֭";i:222;s:2:"֮";i:228;s:2:"֯";i:230;s:2:"ְ";i:10;s:2:"ֱ";i:11;s:2:"ֲ";i:12;s:2:"ֳ";i:13;s:2:"ִ";i:14;s:2:"ֵ";i:15;s:2:"ֶ";i:16;s:2:"ַ";i:17;s:2:"ָ";i:18;s:2:"ֹ";i:19;s:2:"ֺ";i:19;s:2:"ֻ";i:20;s:2:"ּ";i:21;s:2:"ֽ";i:22;s:2:"ֿ";i:23;s:2:"ׁ";i:24;s:2:"ׂ";i:25;s:2:"ׄ";i:230;s:2:"ׅ";i:220;s:2:"ׇ";i:18;s:2:"ؐ";i:230;s:2:"ؑ";i:230;s:2:"ؒ";i:230;s:2:"ؓ";i:230;s:2:"ؔ";i:230;s:2:"ؕ";i:230;s:2:"ؖ";i:230;s:2:"ؗ";i:230;s:2:"ؘ";i:30;s:2:"ؙ";i:31;s:2:"ؚ";i:32;s:2:"ً";i:27;s:2:"ٌ";i:28;s:2:"ٍ";i:29;s:2:"َ";i:30;s:2:"ُ";i:31;s:2:"ِ";i:32;s:2:"ّ";i:33;s:2:"ْ";i:34;s:2:"ٓ";i:230;s:2:"ٔ";i:230;s:2:"ٕ";i:220;s:2:"ٖ";i:220;s:2:"ٗ";i:230;s:2:"٘";i:230;s:2:"ٙ";i:230;s:2:"ٚ";i:230;s:2:"ٛ";i:230;s:2:"ٜ";i:220;s:2:"ٝ";i:230;s:2:"ٞ";i:230;s:2:"ٟ";i:220;s:2:"ٰ";i:35;s:2:"ۖ";i:230;s:2:"ۗ";i:230;s:2:"ۘ";i:230;s:2:"ۙ";i:230;s:2:"ۚ";i:230;s:2:"ۛ";i:230;s:2:"ۜ";i:230;s:2:"۟";i:230;s:2:"۠";i:230;s:2:"ۡ";i:230;s:2:"ۢ";i:230;s:2:"ۣ";i:220;s:2:"ۤ";i:230;s:2:"ۧ";i:230;s:2:"ۨ";i:230;s:2:"۪";i:220;s:2:"۫";i:230;s:2:"۬";i:230;s:2:"ۭ";i:220;s:2:"ܑ";i:36;s:2:"ܰ";i:230;s:2:"ܱ";i:220;s:2:"ܲ";i:230;s:2:"ܳ";i:230;s:2:"ܴ";i:220;s:2:"ܵ";i:230;s:2:"ܶ";i:230;s:2:"ܷ";i:220;s:2:"ܸ";i:220;s:2:"ܹ";i:220;s:2:"ܺ";i:230;s:2:"ܻ";i:220;s:2:"ܼ";i:220;s:2:"ܽ";i:230;s:2:"ܾ";i:220;s:2:"ܿ";i:230;s:2:"݀";i:230;s:2:"݁";i:230;s:2:"݂";i:220;s:2:"݃";i:230;s:2:"݄";i:220;s:2:"݅";i:230;s:2:"݆";i:220;s:2:"݇";i:230;s:2:"݈";i:220;s:2:"݉";i:230;s:2:"݊";i:230;s:2:"߫";i:230;s:2:"߬";i:230;s:2:"߭";i:230;s:2:"߮";i:230;s:2:"߯";i:230;s:2:"߰";i:230;s:2:"߱";i:230;s:2:"߲";i:220;s:2:"߳";i:230;s:3:"ࠖ";i:230;s:3:"ࠗ";i:230;s:3:"࠘";i:230;s:3:"࠙";i:230;s:3:"ࠛ";i:230;s:3:"ࠜ";i:230;s:3:"ࠝ";i:230;s:3:"ࠞ";i:230;s:3:"ࠟ";i:230;s:3:"ࠠ";i:230;s:3:"ࠡ";i:230;s:3:"ࠢ";i:230;s:3:"ࠣ";i:230;s:3:"ࠥ";i:230;s:3:"ࠦ";i:230;s:3:"ࠧ";i:230;s:3:"ࠩ";i:230;s:3:"ࠪ";i:230;s:3:"ࠫ";i:230;s:3:"ࠬ";i:230;s:3:"࠭";i:230;s:3:"࡙";i:220;s:3:"࡚";i:220;s:3:"࡛";i:220;s:3:"़";i:7;s:3:"्";i:9;s:3:"॑";i:230;s:3:"॒";i:220;s:3:"॓";i:230;s:3:"॔";i:230;s:3:"়";i:7;s:3:"্";i:9;s:3:"਼";i:7;s:3:"੍";i:9;s:3:"઼";i:7;s:3:"્";i:9;s:3:"଼";i:7;s:3:"୍";i:9;s:3:"்";i:9;s:3:"్";i:9;s:3:"ౕ";i:84;s:3:"ౖ";i:91;s:3:"಼";i:7;s:3:"್";i:9;s:3:"്";i:9;s:3:"්";i:9;s:3:"ุ";i:103;s:3:"ู";i:103;s:3:"ฺ";i:9;s:3:"่";i:107;s:3:"้";i:107;s:3:"๊";i:107;s:3:"๋";i:107;s:3:"ຸ";i:118;s:3:"ູ";i:118;s:3:"່";i:122;s:3:"້";i:122;s:3:"໊";i:122;s:3:"໋";i:122;s:3:"༘";i:220;s:3:"༙";i:220;s:3:"༵";i:220;s:3:"༷";i:220;s:3:"༹";i:216;s:3:"ཱ";i:129;s:3:"ི";i:130;s:3:"ུ";i:132;s:3:"ེ";i:130;s:3:"ཻ";i:130;s:3:"ོ";i:130;s:3:"ཽ";i:130;s:3:"ྀ";i:130;s:3:"ྂ";i:230;s:3:"ྃ";i:230;s:3:"྄";i:9;s:3:"྆";i:230;s:3:"྇";i:230;s:3:"࿆";i:220;s:3:"့";i:7;s:3:"္";i:9;s:3:"်";i:9;s:3:"ႍ";i:220;s:3:"፝";i:230;s:3:"፞";i:230;s:3:"፟";i:230;s:3:"᜔";i:9;s:3:"᜴";i:9;s:3:"្";i:9;s:3:"៝";i:230;s:3:"ᢩ";i:228;s:3:"᤹";i:222;s:3:"᤺";i:230;s:3:"᤻";i:220;s:3:"ᨗ";i:230;s:3:"ᨘ";i:220;s:3:"᩠";i:9;s:3:"᩵";i:230;s:3:"᩶";i:230;s:3:"᩷";i:230;s:3:"᩸";i:230;s:3:"᩹";i:230;s:3:"᩺";i:230;s:3:"᩻";i:230;s:3:"᩼";i:230;s:3:"᩿";i:220;s:3:"᬴";i:7;s:3:"᭄";i:9;s:3:"᭫";i:230;s:3:"᭬";i:220;s:3:"᭭";i:230;s:3:"᭮";i:230;s:3:"᭯";i:230;s:3:"᭰";i:230;s:3:"᭱";i:230;s:3:"᭲";i:230;s:3:"᭳";i:230;s:3:"᮪";i:9;s:3:"᯦";i:7;s:3:"᯲";i:9;s:3:"᯳";i:9;s:3:"᰷";i:7;s:3:"᳐";i:230;s:3:"᳑";i:230;s:3:"᳒";i:230;s:3:"᳔";i:1;s:3:"᳕";i:220;s:3:"᳖";i:220;s:3:"᳗";i:220;s:3:"᳘";i:220;s:3:"᳙";i:220;s:3:"᳚";i:230;s:3:"᳛";i:230;s:3:"᳜";i:220;s:3:"᳝";i:220;s:3:"᳞";i:220;s:3:"᳟";i:220;s:3:"᳠";i:230;s:3:"᳢";i:1;s:3:"᳣";i:1;s:3:"᳤";i:1;s:3:"᳥";i:1;s:3:"᳦";i:1;s:3:"᳧";i:1;s:3:"᳨";i:1;s:3:"᳭";i:220;s:3:"᷀";i:230;s:3:"᷁";i:230;s:3:"᷂";i:220;s:3:"᷃";i:230;s:3:"᷄";i:230;s:3:"᷅";i:230;s:3:"᷆";i:230;s:3:"᷇";i:230;s:3:"᷈";i:230;s:3:"᷉";i:230;s:3:"᷊";i:220;s:3:"᷋";i:230;s:3:"᷌";i:230;s:3:"᷍";i:234;s:3:"᷎";i:214;s:3:"᷏";i:220;s:3:"᷐";i:202;s:3:"᷑";i:230;s:3:"᷒";i:230;s:3:"ᷓ";i:230;s:3:"ᷔ";i:230;s:3:"ᷕ";i:230;s:3:"ᷖ";i:230;s:3:"ᷗ";i:230;s:3:"ᷘ";i:230;s:3:"ᷙ";i:230;s:3:"ᷚ";i:230;s:3:"ᷛ";i:230;s:3:"ᷜ";i:230;s:3:"ᷝ";i:230;s:3:"ᷞ";i:230;s:3:"ᷟ";i:230;s:3:"ᷠ";i:230;s:3:"ᷡ";i:230;s:3:"ᷢ";i:230;s:3:"ᷣ";i:230;s:3:"ᷤ";i:230;s:3:"ᷥ";i:230;s:3:"ᷦ";i:230;s:3:"᷼";i:233;s:3:"᷽";i:220;s:3:"᷾";i:230;s:3:"᷿";i:220;s:3:"⃐";i:230;s:3:"⃑";i:230;s:3:"⃒";i:1;s:3:"⃓";i:1;s:3:"⃔";i:230;s:3:"⃕";i:230;s:3:"⃖";i:230;s:3:"⃗";i:230;s:3:"⃘";i:1;s:3:"⃙";i:1;s:3:"⃚";i:1;s:3:"⃛";i:230;s:3:"⃜";i:230;s:3:"⃡";i:230;s:3:"⃥";i:1;s:3:"⃦";i:1;s:3:"⃧";i:230;s:3:"⃨";i:220;s:3:"⃩";i:230;s:3:"⃪";i:1;s:3:"⃫";i:1;s:3:"⃬";i:220;s:3:"⃭";i:220;s:3:"⃮";i:220;s:3:"⃯";i:220;s:3:"⃰";i:230;s:3:"⳯";i:230;s:3:"⳰";i:230;s:3:"⳱";i:230;s:3:"⵿";i:9;s:3:"ⷠ";i:230;s:3:"ⷡ";i:230;s:3:"ⷢ";i:230;s:3:"ⷣ";i:230;s:3:"ⷤ";i:230;s:3:"ⷥ";i:230;s:3:"ⷦ";i:230;s:3:"ⷧ";i:230;s:3:"ⷨ";i:230;s:3:"ⷩ";i:230;s:3:"ⷪ";i:230;s:3:"ⷫ";i:230;s:3:"ⷬ";i:230;s:3:"ⷭ";i:230;s:3:"ⷮ";i:230;s:3:"ⷯ";i:230;s:3:"ⷰ";i:230;s:3:"ⷱ";i:230;s:3:"ⷲ";i:230;s:3:"ⷳ";i:230;s:3:"ⷴ";i:230;s:3:"ⷵ";i:230;s:3:"ⷶ";i:230;s:3:"ⷷ";i:230;s:3:"ⷸ";i:230;s:3:"ⷹ";i:230;s:3:"ⷺ";i:230;s:3:"ⷻ";i:230;s:3:"ⷼ";i:230;s:3:"ⷽ";i:230;s:3:"ⷾ";i:230;s:3:"ⷿ";i:230;s:3:"〪";i:218;s:3:"〫";i:228;s:3:"〬";i:232;s:3:"〭";i:222;s:3:"〮";i:224;s:3:"〯";i:224;s:3:"゙";i:8;s:3:"゚";i:8;s:3:"꙯";i:230;s:3:"꙼";i:230;s:3:"꙽";i:230;s:3:"꛰";i:230;s:3:"꛱";i:230;s:3:"꠆";i:9;s:3:"꣄";i:9;s:3:"꣠";i:230;s:3:"꣡";i:230;s:3:"꣢";i:230;s:3:"꣣";i:230;s:3:"꣤";i:230;s:3:"꣥";i:230;s:3:"꣦";i:230;s:3:"꣧";i:230;s:3:"꣨";i:230;s:3:"꣩";i:230;s:3:"꣪";i:230;s:3:"꣫";i:230;s:3:"꣬";i:230;s:3:"꣭";i:230;s:3:"꣮";i:230;s:3:"꣯";i:230;s:3:"꣰";i:230;s:3:"꣱";i:230;s:3:"꤫";i:220;s:3:"꤬";i:220;s:3:"꤭";i:220;s:3:"꥓";i:9;s:3:"꦳";i:7;s:3:"꧀";i:9;s:3:"ꪰ";i:230;s:3:"ꪲ";i:230;s:3:"ꪳ";i:230;s:3:"ꪴ";i:220;s:3:"ꪷ";i:230;s:3:"ꪸ";i:230;s:3:"ꪾ";i:230;s:3:"꪿";i:230;s:3:"꫁";i:230;s:3:"꯭";i:9;s:3:"ﬞ";i:26;s:3:"︠";i:230;s:3:"︡";i:230;s:3:"︢";i:230;s:3:"︣";i:230;s:3:"︤";i:230;s:3:"︥";i:230;s:3:"︦";i:230;s:4:"𐇽";i:220;s:4:"𐨍";i:220;s:4:"𐨏";i:230;s:4:"𐨸";i:230;s:4:"𐨹";i:1;s:4:"𐨺";i:220;s:4:"𐨿";i:9;s:4:"𑁆";i:9;s:4:"𑂹";i:9;s:4:"𑂺";i:7;s:4:"𝅥";i:216;s:4:"𝅦";i:216;s:4:"𝅧";i:1;s:4:"𝅨";i:1;s:4:"𝅩";i:1;s:4:"𝅭";i:226;s:4:"𝅮";i:216;s:4:"𝅯";i:216;s:4:"𝅰";i:216;s:4:"𝅱";i:216;s:4:"𝅲";i:216;s:4:"𝅻";i:220;s:4:"𝅼";i:220;s:4:"𝅽";i:220;s:4:"𝅾";i:220;s:4:"𝅿";i:220;s:4:"𝆀";i:220;s:4:"𝆁";i:220;s:4:"𝆂";i:220;s:4:"𝆅";i:230;s:4:"𝆆";i:230;s:4:"𝆇";i:230;s:4:"𝆈";i:230;s:4:"𝆉";i:230;s:4:"𝆊";i:220;s:4:"𝆋";i:220;s:4:"𝆪";i:230;s:4:"𝆫";i:230;s:4:"𝆬";i:230;s:4:"𝆭";i:230;s:4:"𝉂";i:230;s:4:"𝉃";i:230;s:4:"𝉄";i:230;}' );
UtfNormal::$utfCanonicalComp = unserialize( 'a:1868:{s:3:"À";s:2:"À";s:3:"Á";s:2:"Á";s:3:"Â";s:2:"Â";s:3:"Ã";s:2:"Ã";s:3:"Ä";s:2:"Ä";s:3:"Å";s:2:"Å";s:3:"Ç";s:2:"Ç";s:3:"È";s:2:"È";s:3:"É";s:2:"É";s:3:"Ê";s:2:"Ê";s:3:"Ë";s:2:"Ë";s:3:"Ì";s:2:"Ì";s:3:"Í";s:2:"Í";s:3:"Î";s:2:"Î";s:3:"Ï";s:2:"Ï";s:3:"Ñ";s:2:"Ñ";s:3:"Ò";s:2:"Ò";s:3:"Ó";s:2:"Ó";s:3:"Ô";s:2:"Ô";s:3:"Õ";s:2:"Õ";s:3:"Ö";s:2:"Ö";s:3:"Ù";s:2:"Ù";s:3:"Ú";s:2:"Ú";s:3:"Û";s:2:"Û";s:3:"Ü";s:2:"Ü";s:3:"Ý";s:2:"Ý";s:3:"à";s:2:"à";s:3:"á";s:2:"á";s:3:"â";s:2:"â";s:3:"ã";s:2:"ã";s:3:"ä";s:2:"ä";s:3:"å";s:2:"å";s:3:"ç";s:2:"ç";s:3:"è";s:2:"è";s:3:"é";s:2:"é";s:3:"ê";s:2:"ê";s:3:"ë";s:2:"ë";s:3:"ì";s:2:"ì";s:3:"í";s:2:"í";s:3:"î";s:2:"î";s:3:"ï";s:2:"ï";s:3:"ñ";s:2:"ñ";s:3:"ò";s:2:"ò";s:3:"ó";s:2:"ó";s:3:"ô";s:2:"ô";s:3:"õ";s:2:"õ";s:3:"ö";s:2:"ö";s:3:"ù";s:2:"ù";s:3:"ú";s:2:"ú";s:3:"û";s:2:"û";s:3:"ü";s:2:"ü";s:3:"ý";s:2:"ý";s:3:"ÿ";s:2:"ÿ";s:3:"Ā";s:2:"Ā";s:3:"ā";s:2:"ā";s:3:"Ă";s:2:"Ă";s:3:"ă";s:2:"ă";s:3:"Ą";s:2:"Ą";s:3:"ą";s:2:"ą";s:3:"Ć";s:2:"Ć";s:3:"ć";s:2:"ć";s:3:"Ĉ";s:2:"Ĉ";s:3:"ĉ";s:2:"ĉ";s:3:"Ċ";s:2:"Ċ";s:3:"ċ";s:2:"ċ";s:3:"Č";s:2:"Č";s:3:"č";s:2:"č";s:3:"Ď";s:2:"Ď";s:3:"ď";s:2:"ď";s:3:"Ē";s:2:"Ē";s:3:"ē";s:2:"ē";s:3:"Ĕ";s:2:"Ĕ";s:3:"ĕ";s:2:"ĕ";s:3:"Ė";s:2:"Ė";s:3:"ė";s:2:"ė";s:3:"Ę";s:2:"Ę";s:3:"ę";s:2:"ę";s:3:"Ě";s:2:"Ě";s:3:"ě";s:2:"ě";s:3:"Ĝ";s:2:"Ĝ";s:3:"ĝ";s:2:"ĝ";s:3:"Ğ";s:2:"Ğ";s:3:"ğ";s:2:"ğ";s:3:"Ġ";s:2:"Ġ";s:3:"ġ";s:2:"ġ";s:3:"Ģ";s:2:"Ģ";s:3:"ģ";s:2:"ģ";s:3:"Ĥ";s:2:"Ĥ";s:3:"ĥ";s:2:"ĥ";s:3:"Ĩ";s:2:"Ĩ";s:3:"ĩ";s:2:"ĩ";s:3:"Ī";s:2:"Ī";s:3:"ī";s:2:"ī";s:3:"Ĭ";s:2:"Ĭ";s:3:"ĭ";s:2:"ĭ";s:3:"Į";s:2:"Į";s:3:"į";s:2:"į";s:3:"İ";s:2:"İ";s:3:"Ĵ";s:2:"Ĵ";s:3:"ĵ";s:2:"ĵ";s:3:"Ķ";s:2:"Ķ";s:3:"ķ";s:2:"ķ";s:3:"Ĺ";s:2:"Ĺ";s:3:"ĺ";s:2:"ĺ";s:3:"Ļ";s:2:"Ļ";s:3:"ļ";s:2:"ļ";s:3:"Ľ";s:2:"Ľ";s:3:"ľ";s:2:"ľ";s:3:"Ń";s:2:"Ń";s:3:"ń";s:2:"ń";s:3:"Ņ";s:2:"Ņ";s:3:"ņ";s:2:"ņ";s:3:"Ň";s:2:"Ň";s:3:"ň";s:2:"ň";s:3:"Ō";s:2:"Ō";s:3:"ō";s:2:"ō";s:3:"Ŏ";s:2:"Ŏ";s:3:"ŏ";s:2:"ŏ";s:3:"Ő";s:2:"Ő";s:3:"ő";s:2:"ő";s:3:"Ŕ";s:2:"Ŕ";s:3:"ŕ";s:2:"ŕ";s:3:"Ŗ";s:2:"Ŗ";s:3:"ŗ";s:2:"ŗ";s:3:"Ř";s:2:"Ř";s:3:"ř";s:2:"ř";s:3:"Ś";s:2:"Ś";s:3:"ś";s:2:"ś";s:3:"Ŝ";s:2:"Ŝ";s:3:"ŝ";s:2:"ŝ";s:3:"Ş";s:2:"Ş";s:3:"ş";s:2:"ş";s:3:"Š";s:2:"Š";s:3:"š";s:2:"š";s:3:"Ţ";s:2:"Ţ";s:3:"ţ";s:2:"ţ";s:3:"Ť";s:2:"Ť";s:3:"ť";s:2:"ť";s:3:"Ũ";s:2:"Ũ";s:3:"ũ";s:2:"ũ";s:3:"Ū";s:2:"Ū";s:3:"ū";s:2:"ū";s:3:"Ŭ";s:2:"Ŭ";s:3:"ŭ";s:2:"ŭ";s:3:"Ů";s:2:"Ů";s:3:"ů";s:2:"ů";s:3:"Ű";s:2:"Ű";s:3:"ű";s:2:"ű";s:3:"Ų";s:2:"Ų";s:3:"ų";s:2:"ų";s:3:"Ŵ";s:2:"Ŵ";s:3:"ŵ";s:2:"ŵ";s:3:"Ŷ";s:2:"Ŷ";s:3:"ŷ";s:2:"ŷ";s:3:"Ÿ";s:2:"Ÿ";s:3:"Ź";s:2:"Ź";s:3:"ź";s:2:"ź";s:3:"Ż";s:2:"Ż";s:3:"ż";s:2:"ż";s:3:"Ž";s:2:"Ž";s:3:"ž";s:2:"ž";s:3:"Ơ";s:2:"Ơ";s:3:"ơ";s:2:"ơ";s:3:"Ư";s:2:"Ư";s:3:"ư";s:2:"ư";s:3:"Ǎ";s:2:"Ǎ";s:3:"ǎ";s:2:"ǎ";s:3:"Ǐ";s:2:"Ǐ";s:3:"ǐ";s:2:"ǐ";s:3:"Ǒ";s:2:"Ǒ";s:3:"ǒ";s:2:"ǒ";s:3:"Ǔ";s:2:"Ǔ";s:3:"ǔ";s:2:"ǔ";s:4:"Ǖ";s:2:"Ǖ";s:4:"ǖ";s:2:"ǖ";s:4:"Ǘ";s:2:"Ǘ";s:4:"ǘ";s:2:"ǘ";s:4:"Ǚ";s:2:"Ǚ";s:4:"ǚ";s:2:"ǚ";s:4:"Ǜ";s:2:"Ǜ";s:4:"ǜ";s:2:"ǜ";s:4:"Ǟ";s:2:"Ǟ";s:4:"ǟ";s:2:"ǟ";s:4:"Ǡ";s:2:"Ǡ";s:4:"ǡ";s:2:"ǡ";s:4:"Ǣ";s:2:"Ǣ";s:4:"ǣ";s:2:"ǣ";s:3:"Ǧ";s:2:"Ǧ";s:3:"ǧ";s:2:"ǧ";s:3:"Ǩ";s:2:"Ǩ";s:3:"ǩ";s:2:"ǩ";s:3:"Ǫ";s:2:"Ǫ";s:3:"ǫ";s:2:"ǫ";s:4:"Ǭ";s:2:"Ǭ";s:4:"ǭ";s:2:"ǭ";s:4:"Ǯ";s:2:"Ǯ";s:4:"ǯ";s:2:"ǯ";s:3:"ǰ";s:2:"ǰ";s:3:"Ǵ";s:2:"Ǵ";s:3:"ǵ";s:2:"ǵ";s:3:"Ǹ";s:2:"Ǹ";s:3:"ǹ";s:2:"ǹ";s:4:"Ǻ";s:2:"Ǻ";s:4:"ǻ";s:2:"ǻ";s:4:"Ǽ";s:2:"Ǽ";s:4:"ǽ";s:2:"ǽ";s:4:"Ǿ";s:2:"Ǿ";s:4:"ǿ";s:2:"ǿ";s:3:"Ȁ";s:2:"Ȁ";s:3:"ȁ";s:2:"ȁ";s:3:"Ȃ";s:2:"Ȃ";s:3:"ȃ";s:2:"ȃ";s:3:"Ȅ";s:2:"Ȅ";s:3:"ȅ";s:2:"ȅ";s:3:"Ȇ";s:2:"Ȇ";s:3:"ȇ";s:2:"ȇ";s:3:"Ȉ";s:2:"Ȉ";s:3:"ȉ";s:2:"ȉ";s:3:"Ȋ";s:2:"Ȋ";s:3:"ȋ";s:2:"ȋ";s:3:"Ȍ";s:2:"Ȍ";s:3:"ȍ";s:2:"ȍ";s:3:"Ȏ";s:2:"Ȏ";s:3:"ȏ";s:2:"ȏ";s:3:"Ȑ";s:2:"Ȑ";s:3:"ȑ";s:2:"ȑ";s:3:"Ȓ";s:2:"Ȓ";s:3:"ȓ";s:2:"ȓ";s:3:"Ȕ";s:2:"Ȕ";s:3:"ȕ";s:2:"ȕ";s:3:"Ȗ";s:2:"Ȗ";s:3:"ȗ";s:2:"ȗ";s:3:"Ș";s:2:"Ș";s:3:"ș";s:2:"ș";s:3:"Ț";s:2:"Ț";s:3:"ț";s:2:"ț";s:3:"Ȟ";s:2:"Ȟ";s:3:"ȟ";s:2:"ȟ";s:3:"Ȧ";s:2:"Ȧ";s:3:"ȧ";s:2:"ȧ";s:3:"Ȩ";s:2:"Ȩ";s:3:"ȩ";s:2:"ȩ";s:4:"Ȫ";s:2:"Ȫ";s:4:"ȫ";s:2:"ȫ";s:4:"Ȭ";s:2:"Ȭ";s:4:"ȭ";s:2:"ȭ";s:3:"Ȯ";s:2:"Ȯ";s:3:"ȯ";s:2:"ȯ";s:4:"Ȱ";s:2:"Ȱ";s:4:"ȱ";s:2:"ȱ";s:3:"Ȳ";s:2:"Ȳ";s:3:"ȳ";s:2:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:4:"̈́";s:2:"̈́";s:2:"ʹ";s:2:"ʹ";s:1:";";s:2:";";s:4:"΅";s:2:"΅";s:4:"Ά";s:2:"Ά";s:2:"·";s:2:"·";s:4:"Έ";s:2:"Έ";s:4:"Ή";s:2:"Ή";s:4:"Ί";s:2:"Ί";s:4:"Ό";s:2:"Ό";s:4:"Ύ";s:2:"Ύ";s:4:"Ώ";s:2:"Ώ";s:4:"ΐ";s:2:"ΐ";s:4:"Ϊ";s:2:"Ϊ";s:4:"Ϋ";s:2:"Ϋ";s:4:"ά";s:2:"ά";s:4:"έ";s:2:"έ";s:4:"ή";s:2:"ή";s:4:"ί";s:2:"ί";s:4:"ΰ";s:2:"ΰ";s:4:"ϊ";s:2:"ϊ";s:4:"ϋ";s:2:"ϋ";s:4:"ό";s:2:"ό";s:4:"ύ";s:2:"ύ";s:4:"ώ";s:2:"ώ";s:4:"ϓ";s:2:"ϓ";s:4:"ϔ";s:2:"ϔ";s:4:"Ѐ";s:2:"Ѐ";s:4:"Ё";s:2:"Ё";s:4:"Ѓ";s:2:"Ѓ";s:4:"Ї";s:2:"Ї";s:4:"Ќ";s:2:"Ќ";s:4:"Ѝ";s:2:"Ѝ";s:4:"Ў";s:2:"Ў";s:4:"Й";s:2:"Й";s:4:"й";s:2:"й";s:4:"ѐ";s:2:"ѐ";s:4:"ё";s:2:"ё";s:4:"ѓ";s:2:"ѓ";s:4:"ї";s:2:"ї";s:4:"ќ";s:2:"ќ";s:4:"ѝ";s:2:"ѝ";s:4:"ў";s:2:"ў";s:4:"Ѷ";s:2:"Ѷ";s:4:"ѷ";s:2:"ѷ";s:4:"Ӂ";s:2:"Ӂ";s:4:"ӂ";s:2:"ӂ";s:4:"Ӑ";s:2:"Ӑ";s:4:"ӑ";s:2:"ӑ";s:4:"Ӓ";s:2:"Ӓ";s:4:"ӓ";s:2:"ӓ";s:4:"Ӗ";s:2:"Ӗ";s:4:"ӗ";s:2:"ӗ";s:4:"Ӛ";s:2:"Ӛ";s:4:"ӛ";s:2:"ӛ";s:4:"Ӝ";s:2:"Ӝ";s:4:"ӝ";s:2:"ӝ";s:4:"Ӟ";s:2:"Ӟ";s:4:"ӟ";s:2:"ӟ";s:4:"Ӣ";s:2:"Ӣ";s:4:"ӣ";s:2:"ӣ";s:4:"Ӥ";s:2:"Ӥ";s:4:"ӥ";s:2:"ӥ";s:4:"Ӧ";s:2:"Ӧ";s:4:"ӧ";s:2:"ӧ";s:4:"Ӫ";s:2:"Ӫ";s:4:"ӫ";s:2:"ӫ";s:4:"Ӭ";s:2:"Ӭ";s:4:"ӭ";s:2:"ӭ";s:4:"Ӯ";s:2:"Ӯ";s:4:"ӯ";s:2:"ӯ";s:4:"Ӱ";s:2:"Ӱ";s:4:"ӱ";s:2:"ӱ";s:4:"Ӳ";s:2:"Ӳ";s:4:"ӳ";s:2:"ӳ";s:4:"Ӵ";s:2:"Ӵ";s:4:"ӵ";s:2:"ӵ";s:4:"Ӹ";s:2:"Ӹ";s:4:"ӹ";s:2:"ӹ";s:4:"آ";s:2:"آ";s:4:"أ";s:2:"أ";s:4:"ؤ";s:2:"ؤ";s:4:"إ";s:2:"إ";s:4:"ئ";s:2:"ئ";s:4:"ۀ";s:2:"ۀ";s:4:"ۂ";s:2:"ۂ";s:4:"ۓ";s:2:"ۓ";s:6:"ऩ";s:3:"ऩ";s:6:"ऱ";s:3:"ऱ";s:6:"ऴ";s:3:"ऴ";s:6:"ো";s:3:"ো";s:6:"ৌ";s:3:"ৌ";s:6:"ୈ";s:3:"ୈ";s:6:"ୋ";s:3:"ୋ";s:6:"ୌ";s:3:"ୌ";s:6:"ஔ";s:3:"ஔ";s:6:"ொ";s:3:"ொ";s:6:"ோ";s:3:"ோ";s:6:"ௌ";s:3:"ௌ";s:6:"ై";s:3:"ై";s:6:"ೀ";s:3:"ೀ";s:6:"ೇ";s:3:"ೇ";s:6:"ೈ";s:3:"ೈ";s:6:"ೊ";s:3:"ೊ";s:6:"ೋ";s:3:"ೋ";s:6:"ൊ";s:3:"ൊ";s:6:"ോ";s:3:"ോ";s:6:"ൌ";s:3:"ൌ";s:6:"ේ";s:3:"ේ";s:6:"ො";s:3:"ො";s:6:"ෝ";s:3:"ෝ";s:6:"ෞ";s:3:"ෞ";s:6:"ཱི";s:3:"ཱི";s:6:"ཱུ";s:3:"ཱུ";s:6:"ཱྀ";s:3:"ཱྀ";s:6:"ဦ";s:3:"ဦ";s:6:"ᬆ";s:3:"ᬆ";s:6:"ᬈ";s:3:"ᬈ";s:6:"ᬊ";s:3:"ᬊ";s:6:"ᬌ";s:3:"ᬌ";s:6:"ᬎ";s:3:"ᬎ";s:6:"ᬒ";s:3:"ᬒ";s:6:"ᬻ";s:3:"ᬻ";s:6:"ᬽ";s:3:"ᬽ";s:6:"ᭀ";s:3:"ᭀ";s:6:"ᭁ";s:3:"ᭁ";s:6:"ᭃ";s:3:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:4:"Ḉ";s:3:"Ḉ";s:4:"ḉ";s:3:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:4:"Ḕ";s:3:"Ḕ";s:4:"ḕ";s:3:"ḕ";s:4:"Ḗ";s:3:"Ḗ";s:4:"ḗ";s:3:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:4:"Ḝ";s:3:"Ḝ";s:4:"ḝ";s:3:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:4:"Ḯ";s:3:"Ḯ";s:4:"ḯ";s:3:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:5:"Ḹ";s:3:"Ḹ";s:5:"ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:4:"Ṍ";s:3:"Ṍ";s:4:"ṍ";s:3:"ṍ";s:4:"Ṏ";s:3:"Ṏ";s:4:"ṏ";s:3:"ṏ";s:4:"Ṑ";s:3:"Ṑ";s:4:"ṑ";s:3:"ṑ";s:4:"Ṓ";s:3:"Ṓ";s:4:"ṓ";s:3:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:5:"Ṝ";s:3:"Ṝ";s:5:"ṝ";s:3:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:4:"Ṥ";s:3:"Ṥ";s:4:"ṥ";s:3:"ṥ";s:4:"Ṧ";s:3:"Ṧ";s:4:"ṧ";s:3:"ṧ";s:5:"Ṩ";s:3:"Ṩ";s:5:"ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:4:"Ṹ";s:3:"Ṹ";s:4:"ṹ";s:3:"ṹ";s:4:"Ṻ";s:3:"Ṻ";s:4:"ṻ";s:3:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:4:"ẛ";s:3:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:4:"Ấ";s:3:"Ấ";s:4:"ấ";s:3:"ấ";s:4:"Ầ";s:3:"Ầ";s:4:"ầ";s:3:"ầ";s:4:"Ẩ";s:3:"Ẩ";s:4:"ẩ";s:3:"ẩ";s:4:"Ẫ";s:3:"Ẫ";s:4:"ẫ";s:3:"ẫ";s:5:"Ậ";s:3:"Ậ";s:5:"ậ";s:3:"ậ";s:4:"Ắ";s:3:"Ắ";s:4:"ắ";s:3:"ắ";s:4:"Ằ";s:3:"Ằ";s:4:"ằ";s:3:"ằ";s:4:"Ẳ";s:3:"Ẳ";s:4:"ẳ";s:3:"ẳ";s:4:"Ẵ";s:3:"Ẵ";s:4:"ẵ";s:3:"ẵ";s:5:"Ặ";s:3:"Ặ";s:5:"ặ";s:3:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:4:"Ế";s:3:"Ế";s:4:"ế";s:3:"ế";s:4:"Ề";s:3:"Ề";s:4:"ề";s:3:"ề";s:4:"Ể";s:3:"Ể";s:4:"ể";s:3:"ể";s:4:"Ễ";s:3:"Ễ";s:4:"ễ";s:3:"ễ";s:5:"Ệ";s:3:"Ệ";s:5:"ệ";s:3:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:4:"Ố";s:3:"Ố";s:4:"ố";s:3:"ố";s:4:"Ồ";s:3:"Ồ";s:4:"ồ";s:3:"ồ";s:4:"Ổ";s:3:"Ổ";s:4:"ổ";s:3:"ổ";s:4:"Ỗ";s:3:"Ỗ";s:4:"ỗ";s:3:"ỗ";s:5:"Ộ";s:3:"Ộ";s:5:"ộ";s:3:"ộ";s:4:"Ớ";s:3:"Ớ";s:4:"ớ";s:3:"ớ";s:4:"Ờ";s:3:"Ờ";s:4:"ờ";s:3:"ờ";s:4:"Ở";s:3:"Ở";s:4:"ở";s:3:"ở";s:4:"Ỡ";s:3:"Ỡ";s:4:"ỡ";s:3:"ỡ";s:4:"Ợ";s:3:"Ợ";s:4:"ợ";s:3:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:4:"Ứ";s:3:"Ứ";s:4:"ứ";s:3:"ứ";s:4:"Ừ";s:3:"Ừ";s:4:"ừ";s:3:"ừ";s:4:"Ử";s:3:"Ử";s:4:"ử";s:3:"ử";s:4:"Ữ";s:3:"Ữ";s:4:"ữ";s:3:"ữ";s:4:"Ự";s:3:"Ự";s:4:"ự";s:3:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:4:"ἀ";s:3:"ἀ";s:4:"ἁ";s:3:"ἁ";s:5:"ἂ";s:3:"ἂ";s:5:"ἃ";s:3:"ἃ";s:5:"ἄ";s:3:"ἄ";s:5:"ἅ";s:3:"ἅ";s:5:"ἆ";s:3:"ἆ";s:5:"ἇ";s:3:"ἇ";s:4:"Ἀ";s:3:"Ἀ";s:4:"Ἁ";s:3:"Ἁ";s:5:"Ἂ";s:3:"Ἂ";s:5:"Ἃ";s:3:"Ἃ";s:5:"Ἄ";s:3:"Ἄ";s:5:"Ἅ";s:3:"Ἅ";s:5:"Ἆ";s:3:"Ἆ";s:5:"Ἇ";s:3:"Ἇ";s:4:"ἐ";s:3:"ἐ";s:4:"ἑ";s:3:"ἑ";s:5:"ἒ";s:3:"ἒ";s:5:"ἓ";s:3:"ἓ";s:5:"ἔ";s:3:"ἔ";s:5:"ἕ";s:3:"ἕ";s:4:"Ἐ";s:3:"Ἐ";s:4:"Ἑ";s:3:"Ἑ";s:5:"Ἒ";s:3:"Ἒ";s:5:"Ἓ";s:3:"Ἓ";s:5:"Ἔ";s:3:"Ἔ";s:5:"Ἕ";s:3:"Ἕ";s:4:"ἠ";s:3:"ἠ";s:4:"ἡ";s:3:"ἡ";s:5:"ἢ";s:3:"ἢ";s:5:"ἣ";s:3:"ἣ";s:5:"ἤ";s:3:"ἤ";s:5:"ἥ";s:3:"ἥ";s:5:"ἦ";s:3:"ἦ";s:5:"ἧ";s:3:"ἧ";s:4:"Ἠ";s:3:"Ἠ";s:4:"Ἡ";s:3:"Ἡ";s:5:"Ἢ";s:3:"Ἢ";s:5:"Ἣ";s:3:"Ἣ";s:5:"Ἤ";s:3:"Ἤ";s:5:"Ἥ";s:3:"Ἥ";s:5:"Ἦ";s:3:"Ἦ";s:5:"Ἧ";s:3:"Ἧ";s:4:"ἰ";s:3:"ἰ";s:4:"ἱ";s:3:"ἱ";s:5:"ἲ";s:3:"ἲ";s:5:"ἳ";s:3:"ἳ";s:5:"ἴ";s:3:"ἴ";s:5:"ἵ";s:3:"ἵ";s:5:"ἶ";s:3:"ἶ";s:5:"ἷ";s:3:"ἷ";s:4:"Ἰ";s:3:"Ἰ";s:4:"Ἱ";s:3:"Ἱ";s:5:"Ἲ";s:3:"Ἲ";s:5:"Ἳ";s:3:"Ἳ";s:5:"Ἴ";s:3:"Ἴ";s:5:"Ἵ";s:3:"Ἵ";s:5:"Ἶ";s:3:"Ἶ";s:5:"Ἷ";s:3:"Ἷ";s:4:"ὀ";s:3:"ὀ";s:4:"ὁ";s:3:"ὁ";s:5:"ὂ";s:3:"ὂ";s:5:"ὃ";s:3:"ὃ";s:5:"ὄ";s:3:"ὄ";s:5:"ὅ";s:3:"ὅ";s:4:"Ὀ";s:3:"Ὀ";s:4:"Ὁ";s:3:"Ὁ";s:5:"Ὂ";s:3:"Ὂ";s:5:"Ὃ";s:3:"Ὃ";s:5:"Ὄ";s:3:"Ὄ";s:5:"Ὅ";s:3:"Ὅ";s:4:"ὐ";s:3:"ὐ";s:4:"ὑ";s:3:"ὑ";s:5:"ὒ";s:3:"ὒ";s:5:"ὓ";s:3:"ὓ";s:5:"ὔ";s:3:"ὔ";s:5:"ὕ";s:3:"ὕ";s:5:"ὖ";s:3:"ὖ";s:5:"ὗ";s:3:"ὗ";s:4:"Ὑ";s:3:"Ὑ";s:5:"Ὓ";s:3:"Ὓ";s:5:"Ὕ";s:3:"Ὕ";s:5:"Ὗ";s:3:"Ὗ";s:4:"ὠ";s:3:"ὠ";s:4:"ὡ";s:3:"ὡ";s:5:"ὢ";s:3:"ὢ";s:5:"ὣ";s:3:"ὣ";s:5:"ὤ";s:3:"ὤ";s:5:"ὥ";s:3:"ὥ";s:5:"ὦ";s:3:"ὦ";s:5:"ὧ";s:3:"ὧ";s:4:"Ὠ";s:3:"Ὠ";s:4:"Ὡ";s:3:"Ὡ";s:5:"Ὢ";s:3:"Ὢ";s:5:"Ὣ";s:3:"Ὣ";s:5:"Ὤ";s:3:"Ὤ";s:5:"Ὥ";s:3:"Ὥ";s:5:"Ὦ";s:3:"Ὦ";s:5:"Ὧ";s:3:"Ὧ";s:4:"ὰ";s:3:"ὰ";s:2:"ά";s:3:"ά";s:4:"ὲ";s:3:"ὲ";s:2:"έ";s:3:"έ";s:4:"ὴ";s:3:"ὴ";s:2:"ή";s:3:"ή";s:4:"ὶ";s:3:"ὶ";s:2:"ί";s:3:"ί";s:4:"ὸ";s:3:"ὸ";s:2:"ό";s:3:"ό";s:4:"ὺ";s:3:"ὺ";s:2:"ύ";s:3:"ύ";s:4:"ὼ";s:3:"ὼ";s:2:"ώ";s:3:"ώ";s:5:"ᾀ";s:3:"ᾀ";s:5:"ᾁ";s:3:"ᾁ";s:5:"ᾂ";s:3:"ᾂ";s:5:"ᾃ";s:3:"ᾃ";s:5:"ᾄ";s:3:"ᾄ";s:5:"ᾅ";s:3:"ᾅ";s:5:"ᾆ";s:3:"ᾆ";s:5:"ᾇ";s:3:"ᾇ";s:5:"ᾈ";s:3:"ᾈ";s:5:"ᾉ";s:3:"ᾉ";s:5:"ᾊ";s:3:"ᾊ";s:5:"ᾋ";s:3:"ᾋ";s:5:"ᾌ";s:3:"ᾌ";s:5:"ᾍ";s:3:"ᾍ";s:5:"ᾎ";s:3:"ᾎ";s:5:"ᾏ";s:3:"ᾏ";s:5:"ᾐ";s:3:"ᾐ";s:5:"ᾑ";s:3:"ᾑ";s:5:"ᾒ";s:3:"ᾒ";s:5:"ᾓ";s:3:"ᾓ";s:5:"ᾔ";s:3:"ᾔ";s:5:"ᾕ";s:3:"ᾕ";s:5:"ᾖ";s:3:"ᾖ";s:5:"ᾗ";s:3:"ᾗ";s:5:"ᾘ";s:3:"ᾘ";s:5:"ᾙ";s:3:"ᾙ";s:5:"ᾚ";s:3:"ᾚ";s:5:"ᾛ";s:3:"ᾛ";s:5:"ᾜ";s:3:"ᾜ";s:5:"ᾝ";s:3:"ᾝ";s:5:"ᾞ";s:3:"ᾞ";s:5:"ᾟ";s:3:"ᾟ";s:5:"ᾠ";s:3:"ᾠ";s:5:"ᾡ";s:3:"ᾡ";s:5:"ᾢ";s:3:"ᾢ";s:5:"ᾣ";s:3:"ᾣ";s:5:"ᾤ";s:3:"ᾤ";s:5:"ᾥ";s:3:"ᾥ";s:5:"ᾦ";s:3:"ᾦ";s:5:"ᾧ";s:3:"ᾧ";s:5:"ᾨ";s:3:"ᾨ";s:5:"ᾩ";s:3:"ᾩ";s:5:"ᾪ";s:3:"ᾪ";s:5:"ᾫ";s:3:"ᾫ";s:5:"ᾬ";s:3:"ᾬ";s:5:"ᾭ";s:3:"ᾭ";s:5:"ᾮ";s:3:"ᾮ";s:5:"ᾯ";s:3:"ᾯ";s:4:"ᾰ";s:3:"ᾰ";s:4:"ᾱ";s:3:"ᾱ";s:5:"ᾲ";s:3:"ᾲ";s:4:"ᾳ";s:3:"ᾳ";s:4:"ᾴ";s:3:"ᾴ";s:4:"ᾶ";s:3:"ᾶ";s:5:"ᾷ";s:3:"ᾷ";s:4:"Ᾰ";s:3:"Ᾰ";s:4:"Ᾱ";s:3:"Ᾱ";s:4:"Ὰ";s:3:"Ὰ";s:2:"Ά";s:3:"Ά";s:4:"ᾼ";s:3:"ᾼ";s:2:"ι";s:3:"ι";s:4:"῁";s:3:"῁";s:5:"ῂ";s:3:"ῂ";s:4:"ῃ";s:3:"ῃ";s:4:"ῄ";s:3:"ῄ";s:4:"ῆ";s:3:"ῆ";s:5:"ῇ";s:3:"ῇ";s:4:"Ὲ";s:3:"Ὲ";s:2:"Έ";s:3:"Έ";s:4:"Ὴ";s:3:"Ὴ";s:2:"Ή";s:3:"Ή";s:4:"ῌ";s:3:"ῌ";s:5:"῍";s:3:"῍";s:5:"῎";s:3:"῎";s:5:"῏";s:3:"῏";s:4:"ῐ";s:3:"ῐ";s:4:"ῑ";s:3:"ῑ";s:4:"ῒ";s:3:"ῒ";s:2:"ΐ";s:3:"ΐ";s:4:"ῖ";s:3:"ῖ";s:4:"ῗ";s:3:"ῗ";s:4:"Ῐ";s:3:"Ῐ";s:4:"Ῑ";s:3:"Ῑ";s:4:"Ὶ";s:3:"Ὶ";s:2:"Ί";s:3:"Ί";s:5:"῝";s:3:"῝";s:5:"῞";s:3:"῞";s:5:"῟";s:3:"῟";s:4:"ῠ";s:3:"ῠ";s:4:"ῡ";s:3:"ῡ";s:4:"ῢ";s:3:"ῢ";s:2:"ΰ";s:3:"ΰ";s:4:"ῤ";s:3:"ῤ";s:4:"ῥ";s:3:"ῥ";s:4:"ῦ";s:3:"ῦ";s:4:"ῧ";s:3:"ῧ";s:4:"Ῠ";s:3:"Ῠ";s:4:"Ῡ";s:3:"Ῡ";s:4:"Ὺ";s:3:"Ὺ";s:2:"Ύ";s:3:"Ύ";s:4:"Ῥ";s:3:"Ῥ";s:4:"῭";s:3:"῭";s:2:"΅";s:3:"΅";s:1:"`";s:3:"`";s:5:"ῲ";s:3:"ῲ";s:4:"ῳ";s:3:"ῳ";s:4:"ῴ";s:3:"ῴ";s:4:"ῶ";s:3:"ῶ";s:5:"ῷ";s:3:"ῷ";s:4:"Ὸ";s:3:"Ὸ";s:2:"Ό";s:3:"Ό";s:4:"Ὼ";s:3:"Ὼ";s:2:"Ώ";s:3:"Ώ";s:4:"ῼ";s:3:"ῼ";s:2:"´";s:3:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:2:"Ω";s:3:"Ω";s:1:"K";s:3:"K";s:2:"Å";s:3:"Å";s:5:"↚";s:3:"↚";s:5:"↛";s:3:"↛";s:5:"↮";s:3:"↮";s:5:"⇍";s:3:"⇍";s:5:"⇎";s:3:"⇎";s:5:"⇏";s:3:"⇏";s:5:"∄";s:3:"∄";s:5:"∉";s:3:"∉";s:5:"∌";s:3:"∌";s:5:"∤";s:3:"∤";s:5:"∦";s:3:"∦";s:5:"≁";s:3:"≁";s:5:"≄";s:3:"≄";s:5:"≇";s:3:"≇";s:5:"≉";s:3:"≉";s:3:"≠";s:3:"≠";s:5:"≢";s:3:"≢";s:5:"≭";s:3:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:5:"≰";s:3:"≰";s:5:"≱";s:3:"≱";s:5:"≴";s:3:"≴";s:5:"≵";s:3:"≵";s:5:"≸";s:3:"≸";s:5:"≹";s:3:"≹";s:5:"⊀";s:3:"⊀";s:5:"⊁";s:3:"⊁";s:5:"⊄";s:3:"⊄";s:5:"⊅";s:3:"⊅";s:5:"⊈";s:3:"⊈";s:5:"⊉";s:3:"⊉";s:5:"⊬";s:3:"⊬";s:5:"⊭";s:3:"⊭";s:5:"⊮";s:3:"⊮";s:5:"⊯";s:3:"⊯";s:5:"⋠";s:3:"⋠";s:5:"⋡";s:3:"⋡";s:5:"⋢";s:3:"⋢";s:5:"⋣";s:3:"⋣";s:5:"⋪";s:3:"⋪";s:5:"⋫";s:3:"⋫";s:5:"⋬";s:3:"⋬";s:5:"⋭";s:3:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:6:"が";s:3:"が";s:6:"ぎ";s:3:"ぎ";s:6:"ぐ";s:3:"ぐ";s:6:"げ";s:3:"げ";s:6:"ご";s:3:"ご";s:6:"ざ";s:3:"ざ";s:6:"じ";s:3:"じ";s:6:"ず";s:3:"ず";s:6:"ぜ";s:3:"ぜ";s:6:"ぞ";s:3:"ぞ";s:6:"だ";s:3:"だ";s:6:"ぢ";s:3:"ぢ";s:6:"づ";s:3:"づ";s:6:"で";s:3:"で";s:6:"ど";s:3:"ど";s:6:"ば";s:3:"ば";s:6:"ぱ";s:3:"ぱ";s:6:"び";s:3:"び";s:6:"ぴ";s:3:"ぴ";s:6:"ぶ";s:3:"ぶ";s:6:"ぷ";s:3:"ぷ";s:6:"べ";s:3:"べ";s:6:"ぺ";s:3:"ぺ";s:6:"ぼ";s:3:"ぼ";s:6:"ぽ";s:3:"ぽ";s:6:"ゔ";s:3:"ゔ";s:6:"ゞ";s:3:"ゞ";s:6:"ガ";s:3:"ガ";s:6:"ギ";s:3:"ギ";s:6:"グ";s:3:"グ";s:6:"ゲ";s:3:"ゲ";s:6:"ゴ";s:3:"ゴ";s:6:"ザ";s:3:"ザ";s:6:"ジ";s:3:"ジ";s:6:"ズ";s:3:"ズ";s:6:"ゼ";s:3:"ゼ";s:6:"ゾ";s:3:"ゾ";s:6:"ダ";s:3:"ダ";s:6:"ヂ";s:3:"ヂ";s:6:"ヅ";s:3:"ヅ";s:6:"デ";s:3:"デ";s:6:"ド";s:3:"ド";s:6:"バ";s:3:"バ";s:6:"パ";s:3:"パ";s:6:"ビ";s:3:"ビ";s:6:"ピ";s:3:"ピ";s:6:"ブ";s:3:"ブ";s:6:"プ";s:3:"プ";s:6:"ベ";s:3:"ベ";s:6:"ペ";s:3:"ペ";s:6:"ボ";s:3:"ボ";s:6:"ポ";s:3:"ポ";s:6:"ヴ";s:3:"ヴ";s:6:"ヷ";s:3:"ヷ";s:6:"ヸ";s:3:"ヸ";s:6:"ヹ";s:3:"ヹ";s:6:"ヺ";s:3:"ヺ";s:6:"ヾ";s:3:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:4:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:4:"廊";s:3:"朗";s:4:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:4:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:4:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:4:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:4:"異";s:3:"北";s:4:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:4:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:4:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:4:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:4:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:4:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:4:"侮";s:3:"僧";s:4:"僧";s:3:"免";s:4:"免";s:3:"勉";s:4:"勉";s:3:"勤";s:4:"勤";s:3:"卑";s:4:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:4:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:4:"屮";s:3:"悔";s:4:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:4:"憎";s:3:"懲";s:4:"懲";s:3:"敏";s:4:"敏";s:3:"既";s:3:"既";s:3:"暑";s:4:"暑";s:3:"梅";s:4:"梅";s:3:"海";s:4:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:4:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:4:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:4:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"著";s:4:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:4:"𤋮";s:3:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:4:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:4:"勇";s:3:"勺";s:4:"勺";s:3:"啕";s:3:"啕";s:3:"喙";s:4:"喙";s:3:"嗢";s:3:"嗢";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:4:"慎";s:3:"愈";s:3:"愈";s:3:"慠";s:3:"慠";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"望";s:4:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"滛";s:3:"滛";s:3:"滋";s:4:"滋";s:3:"瀞";s:4:"瀞";s:3:"瞧";s:3:"瞧";s:3:"爵";s:4:"爵";s:3:"犯";s:3:"犯";s:3:"瑱";s:4:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"盛";s:3:"盛";s:3:"直";s:4:"直";s:3:"睊";s:4:"睊";s:3:"着";s:3:"着";s:3:"磌";s:4:"磌";s:3:"窱";s:3:"窱";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"缾";s:3:"缾";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:4:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"調";s:3:"調";s:3:"請";s:3:"請";s:3:"諭";s:4:"諭";s:3:"變";s:4:"變";s:3:"輸";s:4:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"韛";s:3:"韛";s:3:"頋";s:4:"頋";s:3:"鬒";s:4:"鬒";s:4:"𢡊";s:3:"𢡊";s:4:"𢡄";s:3:"𢡄";s:4:"𣏕";s:3:"𣏕";s:3:"㮝";s:4:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:4:"䀹";s:4:"𥉉";s:3:"𥉉";s:4:"𥳐";s:3:"𥳐";s:4:"𧻓";s:3:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:8:"𑂚";s:4:"𑂚";s:8:"𑂜";s:4:"𑂜";s:8:"𑂫";s:4:"𑂫";s:3:"丽";s:4:"丽";s:3:"丸";s:4:"丸";s:3:"乁";s:4:"乁";s:4:"𠄢";s:4:"𠄢";s:3:"你";s:4:"你";s:3:"侻";s:4:"侻";s:3:"倂";s:4:"倂";s:3:"偺";s:4:"偺";s:3:"備";s:4:"備";s:3:"像";s:4:"像";s:3:"㒞";s:4:"㒞";s:4:"𠘺";s:4:"𠘺";s:3:"兔";s:4:"兔";s:3:"兤";s:4:"兤";s:3:"具";s:4:"具";s:4:"𠔜";s:4:"𠔜";s:3:"㒹";s:4:"㒹";s:3:"內";s:4:"內";s:3:"再";s:4:"再";s:4:"𠕋";s:4:"𠕋";s:3:"冗";s:4:"冗";s:3:"冤";s:4:"冤";s:3:"仌";s:4:"仌";s:3:"冬";s:4:"冬";s:4:"𩇟";s:4:"𩇟";s:3:"凵";s:4:"凵";s:3:"刃";s:4:"刃";s:3:"㓟";s:4:"㓟";s:3:"刻";s:4:"刻";s:3:"剆";s:4:"剆";s:3:"割";s:4:"割";s:3:"剷";s:4:"剷";s:3:"㔕";s:4:"㔕";s:3:"包";s:4:"包";s:3:"匆";s:4:"匆";s:3:"卉";s:4:"卉";s:3:"博";s:4:"博";s:3:"即";s:4:"即";s:3:"卽";s:4:"卽";s:3:"卿";s:4:"卿";s:4:"𠨬";s:4:"𠨬";s:3:"灰";s:4:"灰";s:3:"及";s:4:"及";s:3:"叟";s:4:"叟";s:4:"𠭣";s:4:"𠭣";s:3:"叫";s:4:"叫";s:3:"叱";s:4:"叱";s:3:"吆";s:4:"吆";s:3:"咞";s:4:"咞";s:3:"吸";s:4:"吸";s:3:"呈";s:4:"呈";s:3:"周";s:4:"周";s:3:"咢";s:4:"咢";s:3:"哶";s:4:"哶";s:3:"唐";s:4:"唐";s:3:"啓";s:4:"啓";s:3:"啣";s:4:"啣";s:3:"善";s:4:"善";s:3:"喫";s:4:"喫";s:3:"喳";s:4:"喳";s:3:"嗂";s:4:"嗂";s:3:"圖";s:4:"圖";s:3:"圗";s:4:"圗";s:3:"噑";s:4:"噑";s:3:"噴";s:4:"噴";s:3:"壮";s:4:"壮";s:3:"城";s:4:"城";s:3:"埴";s:4:"埴";s:3:"堍";s:4:"堍";s:3:"型";s:4:"型";s:3:"堲";s:4:"堲";s:3:"報";s:4:"報";s:3:"墬";s:4:"墬";s:4:"𡓤";s:4:"𡓤";s:3:"売";s:4:"売";s:3:"壷";s:4:"壷";s:3:"夆";s:4:"夆";s:3:"多";s:4:"多";s:3:"夢";s:4:"夢";s:3:"奢";s:4:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:3:"姬";s:4:"姬";s:3:"娛";s:4:"娛";s:3:"娧";s:4:"娧";s:3:"姘";s:4:"姘";s:3:"婦";s:4:"婦";s:3:"㛮";s:4:"㛮";s:3:"㛼";s:4:"㛼";s:3:"嬈";s:4:"嬈";s:3:"嬾";s:4:"嬾";s:4:"𡧈";s:4:"𡧈";s:3:"寃";s:4:"寃";s:3:"寘";s:4:"寘";s:3:"寳";s:4:"寳";s:4:"𡬘";s:4:"𡬘";s:3:"寿";s:4:"寿";s:3:"将";s:4:"将";s:3:"当";s:4:"当";s:3:"尢";s:4:"尢";s:3:"㞁";s:4:"㞁";s:3:"屠";s:4:"屠";s:3:"峀";s:4:"峀";s:3:"岍";s:4:"岍";s:4:"𡷤";s:4:"𡷤";s:3:"嵃";s:4:"嵃";s:4:"𡷦";s:4:"𡷦";s:3:"嵮";s:4:"嵮";s:3:"嵫";s:4:"嵫";s:3:"嵼";s:4:"嵼";s:3:"巡";s:4:"巡";s:3:"巢";s:4:"巢";s:3:"㠯";s:4:"㠯";s:3:"巽";s:4:"巽";s:3:"帨";s:4:"帨";s:3:"帽";s:4:"帽";s:3:"幩";s:4:"幩";s:3:"㡢";s:4:"㡢";s:4:"𢆃";s:4:"𢆃";s:3:"㡼";s:4:"㡼";s:3:"庰";s:4:"庰";s:3:"庳";s:4:"庳";s:3:"庶";s:4:"庶";s:4:"𪎒";s:4:"𪎒";s:3:"廾";s:4:"廾";s:4:"𢌱";s:4:"𢌱";s:3:"舁";s:4:"舁";s:3:"弢";s:4:"弢";s:3:"㣇";s:4:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:3:"形";s:4:"形";s:3:"彫";s:4:"彫";s:3:"㣣";s:4:"㣣";s:3:"徚";s:4:"徚";s:3:"忍";s:4:"忍";s:3:"志";s:4:"志";s:3:"忹";s:4:"忹";s:3:"悁";s:4:"悁";s:3:"㤺";s:4:"㤺";s:3:"㤜";s:4:"㤜";s:4:"𢛔";s:4:"𢛔";s:3:"惇";s:4:"惇";s:3:"慈";s:4:"慈";s:3:"慌";s:4:"慌";s:3:"慺";s:4:"慺";s:3:"憲";s:4:"憲";s:3:"憤";s:4:"憤";s:3:"憯";s:4:"憯";s:3:"懞";s:4:"懞";s:3:"成";s:4:"成";s:3:"戛";s:4:"戛";s:3:"扝";s:4:"扝";s:3:"抱";s:4:"抱";s:3:"拔";s:4:"拔";s:3:"捐";s:4:"捐";s:4:"𢬌";s:4:"𢬌";s:3:"挽";s:4:"挽";s:3:"拼";s:4:"拼";s:3:"捨";s:4:"捨";s:3:"掃";s:4:"掃";s:3:"揤";s:4:"揤";s:4:"𢯱";s:4:"𢯱";s:3:"搢";s:4:"搢";s:3:"揅";s:4:"揅";s:3:"掩";s:4:"掩";s:3:"㨮";s:4:"㨮";s:3:"摩";s:4:"摩";s:3:"摾";s:4:"摾";s:3:"撝";s:4:"撝";s:3:"摷";s:4:"摷";s:3:"㩬";s:4:"㩬";s:3:"敬";s:4:"敬";s:4:"𣀊";s:4:"𣀊";s:3:"旣";s:4:"旣";s:3:"書";s:4:"書";s:3:"晉";s:4:"晉";s:3:"㬙";s:4:"㬙";s:3:"㬈";s:4:"㬈";s:3:"㫤";s:4:"㫤";s:3:"冒";s:4:"冒";s:3:"冕";s:4:"冕";s:3:"最";s:4:"最";s:3:"暜";s:4:"暜";s:3:"肭";s:4:"肭";s:3:"䏙";s:4:"䏙";s:3:"朡";s:4:"朡";s:3:"杞";s:4:"杞";s:3:"杓";s:4:"杓";s:4:"𣏃";s:4:"𣏃";s:3:"㭉";s:4:"㭉";s:3:"柺";s:4:"柺";s:3:"枅";s:4:"枅";s:3:"桒";s:4:"桒";s:4:"𣑭";s:4:"𣑭";s:3:"梎";s:4:"梎";s:3:"栟";s:4:"栟";s:3:"椔";s:4:"椔";s:3:"楂";s:4:"楂";s:3:"榣";s:4:"榣";s:3:"槪";s:4:"槪";s:3:"檨";s:4:"檨";s:4:"𣚣";s:4:"𣚣";s:3:"櫛";s:4:"櫛";s:3:"㰘";s:4:"㰘";s:3:"次";s:4:"次";s:4:"𣢧";s:4:"𣢧";s:3:"歔";s:4:"歔";s:3:"㱎";s:4:"㱎";s:3:"歲";s:4:"歲";s:3:"殟";s:4:"殟";s:3:"殻";s:4:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:3:"汎";s:4:"汎";s:4:"𣲼";s:4:"𣲼";s:3:"沿";s:4:"沿";s:3:"泍";s:4:"泍";s:3:"汧";s:4:"汧";s:3:"洖";s:4:"洖";s:3:"派";s:4:"派";s:3:"浩";s:4:"浩";s:3:"浸";s:4:"浸";s:3:"涅";s:4:"涅";s:4:"𣴞";s:4:"𣴞";s:3:"洴";s:4:"洴";s:3:"港";s:4:"港";s:3:"湮";s:4:"湮";s:3:"㴳";s:4:"㴳";s:3:"滇";s:4:"滇";s:4:"𣻑";s:4:"𣻑";s:3:"淹";s:4:"淹";s:3:"潮";s:4:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:3:"濆";s:4:"濆";s:3:"瀹";s:4:"瀹";s:3:"瀛";s:4:"瀛";s:3:"㶖";s:4:"㶖";s:3:"灊";s:4:"灊";s:3:"災";s:4:"災";s:3:"灷";s:4:"灷";s:3:"炭";s:4:"炭";s:4:"𠔥";s:4:"𠔥";s:3:"煅";s:4:"煅";s:4:"𤉣";s:4:"𤉣";s:3:"熜";s:4:"熜";s:4:"𤎫";s:4:"𤎫";s:3:"爨";s:4:"爨";s:3:"牐";s:4:"牐";s:4:"𤘈";s:4:"𤘈";s:3:"犀";s:4:"犀";s:3:"犕";s:4:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:3:"獺";s:4:"獺";s:3:"王";s:4:"王";s:3:"㺬";s:4:"㺬";s:3:"玥";s:4:"玥";s:3:"㺸";s:4:"㺸";s:3:"瑇";s:4:"瑇";s:3:"瑜";s:4:"瑜";s:3:"璅";s:4:"璅";s:3:"瓊";s:4:"瓊";s:3:"㼛";s:4:"㼛";s:3:"甤";s:4:"甤";s:4:"𤰶";s:4:"𤰶";s:3:"甾";s:4:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"𢆟";s:4:"𢆟";s:3:"瘐";s:4:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:3:"㿼";s:4:"㿼";s:3:"䀈";s:4:"䀈";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:3:"眞";s:4:"眞";s:3:"真";s:4:"真";s:3:"瞋";s:4:"瞋";s:3:"䁆";s:4:"䁆";s:3:"䂖";s:4:"䂖";s:4:"𥐝";s:4:"𥐝";s:3:"硎";s:4:"硎";s:3:"䃣";s:4:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:3:"秫";s:4:"秫";s:3:"䄯";s:4:"䄯";s:3:"穊";s:4:"穊";s:3:"穏";s:4:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:3:"竮";s:4:"竮";s:3:"䈂";s:4:"䈂";s:4:"𥮫";s:4:"𥮫";s:3:"篆";s:4:"篆";s:3:"築";s:4:"築";s:3:"䈧";s:4:"䈧";s:4:"𥲀";s:4:"𥲀";s:3:"糒";s:4:"糒";s:3:"䊠";s:4:"䊠";s:3:"糨";s:4:"糨";s:3:"糣";s:4:"糣";s:3:"紀";s:4:"紀";s:4:"𥾆";s:4:"𥾆";s:3:"絣";s:4:"絣";s:3:"䌁";s:4:"䌁";s:3:"緇";s:4:"緇";s:3:"縂";s:4:"縂";s:3:"繅";s:4:"繅";s:3:"䌴";s:4:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:3:"䍙";s:4:"䍙";s:4:"𦋙";s:4:"𦋙";s:3:"罺";s:4:"罺";s:4:"𦌾";s:4:"𦌾";s:3:"羕";s:4:"羕";s:3:"翺";s:4:"翺";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:3:"聠";s:4:"聠";s:4:"𦖨";s:4:"𦖨";s:3:"聰";s:4:"聰";s:4:"𣍟";s:4:"𣍟";s:3:"䏕";s:4:"䏕";s:3:"育";s:4:"育";s:3:"脃";s:4:"脃";s:3:"䐋";s:4:"䐋";s:3:"脾";s:4:"脾";s:3:"媵";s:4:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:3:"舄";s:4:"舄";s:3:"辞";s:4:"辞";s:3:"䑫";s:4:"䑫";s:3:"芑";s:4:"芑";s:3:"芋";s:4:"芋";s:3:"芝";s:4:"芝";s:3:"劳";s:4:"劳";s:3:"花";s:4:"花";s:3:"芳";s:4:"芳";s:3:"芽";s:4:"芽";s:3:"苦";s:4:"苦";s:4:"𦬼";s:4:"𦬼";s:3:"茝";s:4:"茝";s:3:"荣";s:4:"荣";s:3:"莭";s:4:"莭";s:3:"茣";s:4:"茣";s:3:"莽";s:4:"莽";s:3:"菧";s:4:"菧";s:3:"荓";s:4:"荓";s:3:"菊";s:4:"菊";s:3:"菌";s:4:"菌";s:3:"菜";s:4:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:3:"䔫";s:4:"䔫";s:3:"蓱";s:4:"蓱";s:3:"蓳";s:4:"蓳";s:3:"蔖";s:4:"蔖";s:4:"𧏊";s:4:"𧏊";s:3:"蕤";s:4:"蕤";s:4:"𦼬";s:4:"𦼬";s:3:"䕝";s:4:"䕝";s:3:"䕡";s:4:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:3:"䕫";s:4:"䕫";s:3:"虐";s:4:"虐";s:3:"虧";s:4:"虧";s:3:"虩";s:4:"虩";s:3:"蚩";s:4:"蚩";s:3:"蚈";s:4:"蚈";s:3:"蜎";s:4:"蜎";s:3:"蛢";s:4:"蛢";s:3:"蜨";s:4:"蜨";s:3:"蝫";s:4:"蝫";s:3:"螆";s:4:"螆";s:3:"䗗";s:4:"䗗";s:3:"蟡";s:4:"蟡";s:3:"蠁";s:4:"蠁";s:3:"䗹";s:4:"䗹";s:3:"衠";s:4:"衠";s:3:"衣";s:4:"衣";s:4:"𧙧";s:4:"𧙧";s:3:"裗";s:4:"裗";s:3:"裞";s:4:"裞";s:3:"䘵";s:4:"䘵";s:3:"裺";s:4:"裺";s:3:"㒻";s:4:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:3:"䚾";s:4:"䚾";s:3:"䛇";s:4:"䛇";s:3:"誠";s:4:"誠";s:3:"豕";s:4:"豕";s:4:"𧲨";s:4:"𧲨";s:3:"貫";s:4:"貫";s:3:"賁";s:4:"賁";s:3:"贛";s:4:"贛";s:3:"起";s:4:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:3:"跋";s:4:"跋";s:3:"趼";s:4:"趼";s:3:"跰";s:4:"跰";s:4:"𠣞";s:4:"𠣞";s:3:"軔";s:4:"軔";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:3:"邔";s:4:"邔";s:3:"郱";s:4:"郱";s:3:"鄑";s:4:"鄑";s:4:"𨜮";s:4:"𨜮";s:3:"鄛";s:4:"鄛";s:3:"鈸";s:4:"鈸";s:3:"鋗";s:4:"鋗";s:3:"鋘";s:4:"鋘";s:3:"鉼";s:4:"鉼";s:3:"鏹";s:4:"鏹";s:3:"鐕";s:4:"鐕";s:4:"𨯺";s:4:"𨯺";s:3:"開";s:4:"開";s:3:"䦕";s:4:"䦕";s:3:"閷";s:4:"閷";s:4:"𨵷";s:4:"𨵷";s:3:"䧦";s:4:"䧦";s:3:"雃";s:4:"雃";s:3:"嶲";s:4:"嶲";s:3:"霣";s:4:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:3:"䩮";s:4:"䩮";s:3:"䩶";s:4:"䩶";s:3:"韠";s:4:"韠";s:4:"𩐊";s:4:"𩐊";s:3:"䪲";s:4:"䪲";s:4:"𩒖";s:4:"𩒖";s:3:"頩";s:4:"頩";s:4:"𩖶";s:4:"𩖶";s:3:"飢";s:4:"飢";s:3:"䬳";s:4:"䬳";s:3:"餩";s:4:"餩";s:3:"馧";s:4:"馧";s:3:"駂";s:4:"駂";s:3:"駾";s:4:"駾";s:3:"䯎";s:4:"䯎";s:4:"𩬰";s:4:"𩬰";s:3:"鱀";s:4:"鱀";s:3:"鳽";s:4:"鳽";s:3:"䳎";s:4:"䳎";s:3:"䳭";s:4:"䳭";s:3:"鵧";s:4:"鵧";s:4:"𪃎";s:4:"𪃎";s:3:"䳸";s:4:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:3:"麻";s:4:"麻";s:3:"䵖";s:4:"䵖";s:3:"黹";s:4:"黹";s:3:"黾";s:4:"黾";s:3:"鼅";s:4:"鼅";s:3:"鼏";s:4:"鼏";s:3:"鼖";s:4:"鼖";s:3:"鼻";s:4:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
UtfNormal::$utfCanonicalDecomp = unserialize( 'a:2049:{s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:";";s:1:";";s:2:"΅";s:4:"΅";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϓ";s:4:"ϓ";s:2:"ϔ";s:4:"ϔ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẛ";s:4:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"ι";s:2:"ι";s:3:"῁";s:4:"῁";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:"῍";s:3:"῎";s:5:"῎";s:3:"῏";s:5:"῏";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:"῝";s:3:"῞";s:5:"῞";s:3:"῟";s:5:"῟";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:4:"῭";s:3:"΅";s:4:"΅";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:2:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:3:"Ω";s:2:"Ω";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"⫝̸";s:5:"⫝̸";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"ゞ";s:6:"ゞ";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:3:"𤋮";s:4:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:4:"𑂚";s:8:"𑂚";s:4:"𑂜";s:8:"𑂜";s:4:"𑂫";s:8:"𑂫";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
diff --git a/includes/normal/UtfNormalDataK.inc b/includes/normal/UtfNormalDataK.inc
index 661d2cda..dde3effb 100644
--- a/includes/normal/UtfNormalDataK.inc
+++ b/includes/normal/UtfNormalDataK.inc
@@ -5,6 +5,7 @@
*
* @file
*/
+// @codingStandardsIgnoreFile
UtfNormal::$utfCompatibilityDecomp = unserialize( 'a:5559:{s:2:" ";s:1:" ";s:2:"¨";s:3:" ̈";s:2:"ª";s:1:"a";s:2:"¯";s:3:" ̄";s:2:"²";s:1:"2";s:2:"³";s:1:"3";s:2:"´";s:3:" ́";s:2:"µ";s:2:"μ";s:2:"¸";s:3:" ̧";s:2:"¹";s:1:"1";s:2:"º";s:1:"o";s:2:"¼";s:5:"1⁄4";s:2:"½";s:5:"1⁄2";s:2:"¾";s:5:"3⁄4";s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ŀ";s:3:"L·";s:2:"ŀ";s:3:"l·";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"ʼn";s:3:"ʼn";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"ſ";s:1:"s";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"DŽ";s:4:"DŽ";s:2:"Dž";s:4:"Dž";s:2:"dž";s:4:"dž";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"NJ";s:2:"NJ";s:2:"Nj";s:2:"Nj";s:2:"nj";s:2:"nj";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"ʰ";s:1:"h";s:2:"ʱ";s:2:"ɦ";s:2:"ʲ";s:1:"j";s:2:"ʳ";s:1:"r";s:2:"ʴ";s:2:"ɹ";s:2:"ʵ";s:2:"ɻ";s:2:"ʶ";s:2:"ʁ";s:2:"ʷ";s:1:"w";s:2:"ʸ";s:1:"y";s:2:"˘";s:3:" ̆";s:2:"˙";s:3:" ̇";s:2:"˚";s:3:" ̊";s:2:"˛";s:3:" ̨";s:2:"˜";s:3:" ̃";s:2:"˝";s:3:" ̋";s:2:"ˠ";s:2:"ɣ";s:2:"ˡ";s:1:"l";s:2:"ˢ";s:1:"s";s:2:"ˣ";s:1:"x";s:2:"ˤ";s:2:"ʕ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:"ͺ";s:3:" ͅ";s:2:";";s:1:";";s:2:"΄";s:3:" ́";s:2:"΅";s:5:" ̈́";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϐ";s:2:"β";s:2:"ϑ";s:2:"θ";s:2:"ϒ";s:2:"Υ";s:2:"ϓ";s:4:"Ύ";s:2:"ϔ";s:4:"Ϋ";s:2:"ϕ";s:2:"φ";s:2:"ϖ";s:2:"π";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"ρ";s:2:"ϲ";s:2:"ς";s:2:"ϴ";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"և";s:4:"եւ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ٵ";s:4:"اٴ";s:2:"ٶ";s:4:"وٴ";s:2:"ٷ";s:4:"ۇٴ";s:2:"ٸ";s:4:"يٴ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"ำ";s:6:"ํา";s:3:"ຳ";s:6:"ໍາ";s:3:"ໜ";s:6:"ຫນ";s:3:"ໝ";s:6:"ຫມ";s:3:"༌";s:3:"་";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ཷ";s:9:"ྲཱྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཹ";s:9:"ླཱྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ჼ";s:3:"ნ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"ᴬ";s:1:"A";s:3:"ᴭ";s:2:"Æ";s:3:"ᴮ";s:1:"B";s:3:"ᴰ";s:1:"D";s:3:"ᴱ";s:1:"E";s:3:"ᴲ";s:2:"Ǝ";s:3:"ᴳ";s:1:"G";s:3:"ᴴ";s:1:"H";s:3:"ᴵ";s:1:"I";s:3:"ᴶ";s:1:"J";s:3:"ᴷ";s:1:"K";s:3:"ᴸ";s:1:"L";s:3:"ᴹ";s:1:"M";s:3:"ᴺ";s:1:"N";s:3:"ᴼ";s:1:"O";s:3:"ᴽ";s:2:"Ȣ";s:3:"ᴾ";s:1:"P";s:3:"ᴿ";s:1:"R";s:3:"ᵀ";s:1:"T";s:3:"ᵁ";s:1:"U";s:3:"ᵂ";s:1:"W";s:3:"ᵃ";s:1:"a";s:3:"ᵄ";s:2:"ɐ";s:3:"ᵅ";s:2:"ɑ";s:3:"ᵆ";s:3:"ᴂ";s:3:"ᵇ";s:1:"b";s:3:"ᵈ";s:1:"d";s:3:"ᵉ";s:1:"e";s:3:"ᵊ";s:2:"ə";s:3:"ᵋ";s:2:"ɛ";s:3:"ᵌ";s:2:"ɜ";s:3:"ᵍ";s:1:"g";s:3:"ᵏ";s:1:"k";s:3:"ᵐ";s:1:"m";s:3:"ᵑ";s:2:"ŋ";s:3:"ᵒ";s:1:"o";s:3:"ᵓ";s:2:"ɔ";s:3:"ᵔ";s:3:"ᴖ";s:3:"ᵕ";s:3:"ᴗ";s:3:"ᵖ";s:1:"p";s:3:"ᵗ";s:1:"t";s:3:"ᵘ";s:1:"u";s:3:"ᵙ";s:3:"ᴝ";s:3:"ᵚ";s:2:"ɯ";s:3:"ᵛ";s:1:"v";s:3:"ᵜ";s:3:"ᴥ";s:3:"ᵝ";s:2:"β";s:3:"ᵞ";s:2:"γ";s:3:"ᵟ";s:2:"δ";s:3:"ᵠ";s:2:"φ";s:3:"ᵡ";s:2:"χ";s:3:"ᵢ";s:1:"i";s:3:"ᵣ";s:1:"r";s:3:"ᵤ";s:1:"u";s:3:"ᵥ";s:1:"v";s:3:"ᵦ";s:2:"β";s:3:"ᵧ";s:2:"γ";s:3:"ᵨ";s:2:"ρ";s:3:"ᵩ";s:2:"φ";s:3:"ᵪ";s:2:"χ";s:3:"ᵸ";s:2:"н";s:3:"ᶛ";s:2:"ɒ";s:3:"ᶜ";s:1:"c";s:3:"ᶝ";s:2:"ɕ";s:3:"ᶞ";s:2:"ð";s:3:"ᶟ";s:2:"ɜ";s:3:"ᶠ";s:1:"f";s:3:"ᶡ";s:2:"ɟ";s:3:"ᶢ";s:2:"ɡ";s:3:"ᶣ";s:2:"ɥ";s:3:"ᶤ";s:2:"ɨ";s:3:"ᶥ";s:2:"ɩ";s:3:"ᶦ";s:2:"ɪ";s:3:"ᶧ";s:3:"ᵻ";s:3:"ᶨ";s:2:"ʝ";s:3:"ᶩ";s:2:"ɭ";s:3:"ᶪ";s:3:"ᶅ";s:3:"ᶫ";s:2:"ʟ";s:3:"ᶬ";s:2:"ɱ";s:3:"ᶭ";s:2:"ɰ";s:3:"ᶮ";s:2:"ɲ";s:3:"ᶯ";s:2:"ɳ";s:3:"ᶰ";s:2:"ɴ";s:3:"ᶱ";s:2:"ɵ";s:3:"ᶲ";s:2:"ɸ";s:3:"ᶳ";s:2:"ʂ";s:3:"ᶴ";s:2:"ʃ";s:3:"ᶵ";s:2:"ƫ";s:3:"ᶶ";s:2:"ʉ";s:3:"ᶷ";s:2:"ʊ";s:3:"ᶸ";s:3:"ᴜ";s:3:"ᶹ";s:2:"ʋ";s:3:"ᶺ";s:2:"ʌ";s:3:"ᶻ";s:1:"z";s:3:"ᶼ";s:2:"ʐ";s:3:"ᶽ";s:2:"ʑ";s:3:"ᶾ";s:2:"ʒ";s:3:"ᶿ";s:2:"θ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẚ";s:3:"aʾ";s:3:"ẛ";s:3:"ṡ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"᾽";s:3:" ̓";s:3:"ι";s:2:"ι";s:3:"᾿";s:3:" ̓";s:3:"῀";s:3:" ͂";s:3:"῁";s:5:" ̈͂";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:" ̓̀";s:3:"῎";s:5:" ̓́";s:3:"῏";s:5:" ̓͂";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:" ̔̀";s:3:"῞";s:5:" ̔́";s:3:"῟";s:5:" ̔͂";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:5:" ̈̀";s:3:"΅";s:5:" ̈́";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:3:" ́";s:3:"῾";s:3:" ̔";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:"‑";s:3:"‐";s:3:"‗";s:3:" ̳";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:" ";s:1:" ";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"‾";s:3:" ̅";s:3:"⁇";s:2:"??";s:3:"⁈";s:2:"?!";s:3:"⁉";s:2:"!?";s:3:"⁗";s:12:"′′′′";s:3:" ";s:1:" ";s:3:"⁰";s:1:"0";s:3:"ⁱ";s:1:"i";s:3:"⁴";s:1:"4";s:3:"⁵";s:1:"5";s:3:"⁶";s:1:"6";s:3:"⁷";s:1:"7";s:3:"⁸";s:1:"8";s:3:"⁹";s:1:"9";s:3:"⁺";s:1:"+";s:3:"⁻";s:3:"−";s:3:"⁼";s:1:"=";s:3:"⁽";s:1:"(";s:3:"⁾";s:1:")";s:3:"ⁿ";s:1:"n";s:3:"₀";s:1:"0";s:3:"₁";s:1:"1";s:3:"₂";s:1:"2";s:3:"₃";s:1:"3";s:3:"₄";s:1:"4";s:3:"₅";s:1:"5";s:3:"₆";s:1:"6";s:3:"₇";s:1:"7";s:3:"₈";s:1:"8";s:3:"₉";s:1:"9";s:3:"₊";s:1:"+";s:3:"₋";s:3:"−";s:3:"₌";s:1:"=";s:3:"₍";s:1:"(";s:3:"₎";s:1:")";s:3:"ₐ";s:1:"a";s:3:"ₑ";s:1:"e";s:3:"ₒ";s:1:"o";s:3:"ₓ";s:1:"x";s:3:"ₔ";s:2:"ə";s:3:"ₕ";s:1:"h";s:3:"ₖ";s:1:"k";s:3:"ₗ";s:1:"l";s:3:"ₘ";s:1:"m";s:3:"ₙ";s:1:"n";s:3:"ₚ";s:1:"p";s:3:"ₛ";s:1:"s";s:3:"ₜ";s:1:"t";s:3:"₨";s:2:"Rs";s:3:"℀";s:3:"a/c";s:3:"℁";s:3:"a/s";s:3:"ℂ";s:1:"C";s:3:"℃";s:3:"°C";s:3:"℅";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Ɛ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"ℋ";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"ℍ";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"ℏ";s:2:"ħ";s:3:"ℐ";s:1:"I";s:3:"ℑ";s:1:"I";s:3:"ℒ";s:1:"L";s:3:"ℓ";s:1:"l";s:3:"ℕ";s:1:"N";s:3:"№";s:2:"No";s:3:"ℙ";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"ℛ";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"ℝ";s:1:"R";s:3:"℠";s:2:"SM";s:3:"℡";s:3:"TEL";s:3:"™";s:2:"TM";s:3:"ℤ";s:1:"Z";s:3:"Ω";s:2:"Ω";s:3:"ℨ";s:1:"Z";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"ℬ";s:1:"B";s:3:"ℭ";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"ℰ";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"ℴ";s:1:"o";s:3:"ℵ";s:2:"א";s:3:"ℶ";s:2:"ב";s:3:"ℷ";s:2:"ג";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"℻";s:3:"FAX";s:3:"ℼ";s:2:"π";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"ℿ";s:2:"Π";s:3:"⅀";s:3:"∑";s:3:"ⅅ";s:1:"D";s:3:"ⅆ";s:1:"d";s:3:"ⅇ";s:1:"e";s:3:"ⅈ";s:1:"i";s:3:"ⅉ";s:1:"j";s:3:"⅐";s:5:"1⁄7";s:3:"⅑";s:5:"1⁄9";s:3:"⅒";s:6:"1⁄10";s:3:"⅓";s:5:"1⁄3";s:3:"⅔";s:5:"2⁄3";s:3:"⅕";s:5:"1⁄5";s:3:"⅖";s:5:"2⁄5";s:3:"⅗";s:5:"3⁄5";s:3:"⅘";s:5:"4⁄5";s:3:"⅙";s:5:"1⁄6";s:3:"⅚";s:5:"5⁄6";s:3:"⅛";s:5:"1⁄8";s:3:"⅜";s:5:"3⁄8";s:3:"⅝";s:5:"5⁄8";s:3:"⅞";s:5:"7⁄8";s:3:"⅟";s:4:"1⁄";s:3:"Ⅰ";s:1:"I";s:3:"Ⅱ";s:2:"II";s:3:"Ⅲ";s:3:"III";s:3:"Ⅳ";s:2:"IV";s:3:"Ⅴ";s:1:"V";s:3:"Ⅵ";s:2:"VI";s:3:"Ⅶ";s:3:"VII";s:3:"Ⅷ";s:4:"VIII";s:3:"Ⅸ";s:2:"IX";s:3:"Ⅹ";s:1:"X";s:3:"Ⅺ";s:2:"XI";s:3:"Ⅻ";s:3:"XII";s:3:"Ⅼ";s:1:"L";s:3:"Ⅽ";s:1:"C";s:3:"Ⅾ";s:1:"D";s:3:"Ⅿ";s:1:"M";s:3:"ⅰ";s:1:"i";s:3:"ⅱ";s:2:"ii";s:3:"ⅲ";s:3:"iii";s:3:"ⅳ";s:2:"iv";s:3:"ⅴ";s:1:"v";s:3:"ⅵ";s:2:"vi";s:3:"ⅶ";s:3:"vii";s:3:"ⅷ";s:4:"viii";s:3:"ⅸ";s:2:"ix";s:3:"ⅹ";s:1:"x";s:3:"ⅺ";s:2:"xi";s:3:"ⅻ";s:3:"xii";s:3:"ⅼ";s:1:"l";s:3:"ⅽ";s:1:"c";s:3:"ⅾ";s:1:"d";s:3:"ⅿ";s:1:"m";s:3:"↉";s:5:"0⁄3";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"①";s:1:"1";s:3:"②";s:1:"2";s:3:"③";s:1:"3";s:3:"④";s:1:"4";s:3:"⑤";s:1:"5";s:3:"⑥";s:1:"6";s:3:"⑦";s:1:"7";s:3:"⑧";s:1:"8";s:3:"⑨";s:1:"9";s:3:"⑩";s:2:"10";s:3:"⑪";s:2:"11";s:3:"⑫";s:2:"12";s:3:"⑬";s:2:"13";s:3:"⑭";s:2:"14";s:3:"⑮";s:2:"15";s:3:"⑯";s:2:"16";s:3:"⑰";s:2:"17";s:3:"⑱";s:2:"18";s:3:"⑲";s:2:"19";s:3:"⑳";s:2:"20";s:3:"⑴";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"⑶";s:3:"(3)";s:3:"⑷";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"⑻";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"⑿";s:4:"(12)";s:3:"⒀";s:4:"(13)";s:3:"⒁";s:4:"(14)";s:3:"⒂";s:4:"(15)";s:3:"⒃";s:4:"(16)";s:3:"⒄";s:4:"(17)";s:3:"⒅";s:4:"(18)";s:3:"⒆";s:4:"(19)";s:3:"⒇";s:4:"(20)";s:3:"⒈";s:2:"1.";s:3:"⒉";s:2:"2.";s:3:"⒊";s:2:"3.";s:3:"⒋";s:2:"4.";s:3:"⒌";s:2:"5.";s:3:"⒍";s:2:"6.";s:3:"⒎";s:2:"7.";s:3:"⒏";s:2:"8.";s:3:"⒐";s:2:"9.";s:3:"⒑";s:3:"10.";s:3:"⒒";s:3:"11.";s:3:"⒓";s:3:"12.";s:3:"⒔";s:3:"13.";s:3:"⒕";s:3:"14.";s:3:"⒖";s:3:"15.";s:3:"⒗";s:3:"16.";s:3:"⒘";s:3:"17.";s:3:"⒙";s:3:"18.";s:3:"⒚";s:3:"19.";s:3:"⒛";s:3:"20.";s:3:"⒜";s:3:"(a)";s:3:"⒝";s:3:"(b)";s:3:"⒞";s:3:"(c)";s:3:"⒟";s:3:"(d)";s:3:"⒠";s:3:"(e)";s:3:"⒡";s:3:"(f)";s:3:"⒢";s:3:"(g)";s:3:"⒣";s:3:"(h)";s:3:"⒤";s:3:"(i)";s:3:"⒥";s:3:"(j)";s:3:"⒦";s:3:"(k)";s:3:"⒧";s:3:"(l)";s:3:"⒨";s:3:"(m)";s:3:"⒩";s:3:"(n)";s:3:"⒪";s:3:"(o)";s:3:"⒫";s:3:"(p)";s:3:"⒬";s:3:"(q)";s:3:"⒭";s:3:"(r)";s:3:"⒮";s:3:"(s)";s:3:"⒯";s:3:"(t)";s:3:"⒰";s:3:"(u)";s:3:"⒱";s:3:"(v)";s:3:"⒲";s:3:"(w)";s:3:"⒳";s:3:"(x)";s:3:"⒴";s:3:"(y)";s:3:"⒵";s:3:"(z)";s:3:"Ⓐ";s:1:"A";s:3:"Ⓑ";s:1:"B";s:3:"Ⓒ";s:1:"C";s:3:"Ⓓ";s:1:"D";s:3:"Ⓔ";s:1:"E";s:3:"Ⓕ";s:1:"F";s:3:"Ⓖ";s:1:"G";s:3:"Ⓗ";s:1:"H";s:3:"Ⓘ";s:1:"I";s:3:"Ⓙ";s:1:"J";s:3:"Ⓚ";s:1:"K";s:3:"Ⓛ";s:1:"L";s:3:"Ⓜ";s:1:"M";s:3:"Ⓝ";s:1:"N";s:3:"Ⓞ";s:1:"O";s:3:"Ⓟ";s:1:"P";s:3:"Ⓠ";s:1:"Q";s:3:"Ⓡ";s:1:"R";s:3:"Ⓢ";s:1:"S";s:3:"Ⓣ";s:1:"T";s:3:"Ⓤ";s:1:"U";s:3:"Ⓥ";s:1:"V";s:3:"Ⓦ";s:1:"W";s:3:"Ⓧ";s:1:"X";s:3:"Ⓨ";s:1:"Y";s:3:"Ⓩ";s:1:"Z";s:3:"ⓐ";s:1:"a";s:3:"ⓑ";s:1:"b";s:3:"ⓒ";s:1:"c";s:3:"ⓓ";s:1:"d";s:3:"ⓔ";s:1:"e";s:3:"ⓕ";s:1:"f";s:3:"ⓖ";s:1:"g";s:3:"ⓗ";s:1:"h";s:3:"ⓘ";s:1:"i";s:3:"ⓙ";s:1:"j";s:3:"ⓚ";s:1:"k";s:3:"ⓛ";s:1:"l";s:3:"ⓜ";s:1:"m";s:3:"ⓝ";s:1:"n";s:3:"ⓞ";s:1:"o";s:3:"ⓟ";s:1:"p";s:3:"ⓠ";s:1:"q";s:3:"ⓡ";s:1:"r";s:3:"ⓢ";s:1:"s";s:3:"ⓣ";s:1:"t";s:3:"ⓤ";s:1:"u";s:3:"ⓥ";s:1:"v";s:3:"ⓦ";s:1:"w";s:3:"ⓧ";s:1:"x";s:3:"ⓨ";s:1:"y";s:3:"ⓩ";s:1:"z";s:3:"⓪";s:1:"0";s:3:"⨌";s:12:"∫∫∫∫";s:3:"⩴";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"⩶";s:3:"===";s:3:"⫝̸";s:5:"⫝̸";s:3:"ⱼ";s:1:"j";s:3:"ⱽ";s:1:"V";s:3:"ⵯ";s:3:"ⵡ";s:3:"⺟";s:3:"母";s:3:"⻳";s:3:"龟";s:3:"⼀";s:3:"一";s:3:"⼁";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"乙";s:3:"⼅";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"儿";s:3:"⼊";s:3:"入";s:3:"⼋";s:3:"八";s:3:"⼌";s:3:"冂";s:3:"⼍";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"⼏";s:3:"几";s:3:"⼐";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"⼒";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"⼔";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"⼖";s:3:"匸";s:3:"⼗";s:3:"十";s:3:"⼘";s:3:"卜";s:3:"⼙";s:3:"卩";s:3:"⼚";s:3:"厂";s:3:"⼛";s:3:"厶";s:3:"⼜";s:3:"又";s:3:"⼝";s:3:"口";s:3:"⼞";s:3:"囗";s:3:"⼟";s:3:"土";s:3:"⼠";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"⼢";s:3:"夊";s:3:"⼣";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"⼥";s:3:"女";s:3:"⼦";s:3:"子";s:3:"⼧";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"小";s:3:"⼪";s:3:"尢";s:3:"⼫";s:3:"尸";s:3:"⼬";s:3:"屮";s:3:"⼭";s:3:"山";s:3:"⼮";s:3:"巛";s:3:"⼯";s:3:"工";s:3:"⼰";s:3:"己";s:3:"⼱";s:3:"巾";s:3:"⼲";s:3:"干";s:3:"⼳";s:3:"幺";s:3:"⼴";s:3:"广";s:3:"⼵";s:3:"廴";s:3:"⼶";s:3:"廾";s:3:"⼷";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"⼹";s:3:"彐";s:3:"⼺";s:3:"彡";s:3:"⼻";s:3:"彳";s:3:"⼼";s:3:"心";s:3:"⼽";s:3:"戈";s:3:"⼾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"⽀";s:3:"支";s:3:"⽁";s:3:"攴";s:3:"⽂";s:3:"文";s:3:"⽃";s:3:"斗";s:3:"⽄";s:3:"斤";s:3:"⽅";s:3:"方";s:3:"⽆";s:3:"无";s:3:"⽇";s:3:"日";s:3:"⽈";s:3:"曰";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"止";s:3:"⽍";s:3:"歹";s:3:"⽎";s:3:"殳";s:3:"⽏";s:3:"毋";s:3:"⽐";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"⽒";s:3:"氏";s:3:"⽓";s:3:"气";s:3:"⽔";s:3:"水";s:3:"⽕";s:3:"火";s:3:"⽖";s:3:"爪";s:3:"⽗";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"⽙";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"⽛";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"⽝";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"⽠";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"⽢";s:3:"甘";s:3:"⽣";s:3:"生";s:3:"⽤";s:3:"用";s:3:"⽥";s:3:"田";s:3:"⽦";s:3:"疋";s:3:"⽧";s:3:"疒";s:3:"⽨";s:3:"癶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"皮";s:3:"⽫";s:3:"皿";s:3:"⽬";s:3:"目";s:3:"⽭";s:3:"矛";s:3:"⽮";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"⽰";s:3:"示";s:3:"⽱";s:3:"禸";s:3:"⽲";s:3:"禾";s:3:"⽳";s:3:"穴";s:3:"⽴";s:3:"立";s:3:"⽵";s:3:"竹";s:3:"⽶";s:3:"米";s:3:"⽷";s:3:"糸";s:3:"⽸";s:3:"缶";s:3:"⽹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"⽻";s:3:"羽";s:3:"⽼";s:3:"老";s:3:"⽽";s:3:"而";s:3:"⽾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"⾀";s:3:"聿";s:3:"⾁";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"⾅";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"虍";s:3:"⾍";s:3:"虫";s:3:"⾎";s:3:"血";s:3:"⾏";s:3:"行";s:3:"⾐";s:3:"衣";s:3:"⾑";s:3:"襾";s:3:"⾒";s:3:"見";s:3:"⾓";s:3:"角";s:3:"⾔";s:3:"言";s:3:"⾕";s:3:"谷";s:3:"⾖";s:3:"豆";s:3:"⾗";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"⾙";s:3:"貝";s:3:"⾚";s:3:"赤";s:3:"⾛";s:3:"走";s:3:"⾜";s:3:"足";s:3:"⾝";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"辛";s:3:"⾠";s:3:"辰";s:3:"⾡";s:3:"辵";s:3:"⾢";s:3:"邑";s:3:"⾣";s:3:"酉";s:3:"⾤";s:3:"釆";s:3:"⾥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"⾧";s:3:"長";s:3:"⾨";s:3:"門";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"隶";s:3:"⾫";s:3:"隹";s:3:"⾬";s:3:"雨";s:3:"⾭";s:3:"靑";s:3:"⾮";s:3:"非";s:3:"⾯";s:3:"面";s:3:"⾰";s:3:"革";s:3:"⾱";s:3:"韋";s:3:"⾲";s:3:"韭";s:3:"⾳";s:3:"音";s:3:"⾴";s:3:"頁";s:3:"⾵";s:3:"風";s:3:"⾶";s:3:"飛";s:3:"⾷";s:3:"食";s:3:"⾸";s:3:"首";s:3:"⾹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"⾻";s:3:"骨";s:3:"⾼";s:3:"高";s:3:"⾽";s:3:"髟";s:3:"⾾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"⿀";s:3:"鬲";s:3:"⿁";s:3:"鬼";s:3:"⿂";s:3:"魚";s:3:"⿃";s:3:"鳥";s:3:"⿄";s:3:"鹵";s:3:"⿅";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"黍";s:3:"⿊";s:3:"黑";s:3:"⿋";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"⿍";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"⿏";s:3:"鼠";s:3:"⿐";s:3:"鼻";s:3:"⿑";s:3:"齊";s:3:"⿒";s:3:"齒";s:3:"⿓";s:3:"龍";s:3:"⿔";s:3:"龜";s:3:"⿕";s:3:"龠";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"十";s:3:"〹";s:3:"卄";s:3:"〺";s:3:"卅";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"゛";s:4:" ゙";s:3:"゜";s:4:" ゚";s:3:"ゞ";s:6:"ゞ";s:3:"ゟ";s:6:"より";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"ヿ";s:6:"コト";s:3:"ㄱ";s:3:"ᄀ";s:3:"ㄲ";s:3:"ᄁ";s:3:"ㄳ";s:3:"ᆪ";s:3:"ㄴ";s:3:"ᄂ";s:3:"ㄵ";s:3:"ᆬ";s:3:"ㄶ";s:3:"ᆭ";s:3:"ㄷ";s:3:"ᄃ";s:3:"ㄸ";s:3:"ᄄ";s:3:"ㄹ";s:3:"ᄅ";s:3:"ㄺ";s:3:"ᆰ";s:3:"ㄻ";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ㄿ";s:3:"ᆵ";s:3:"ㅀ";s:3:"ᄚ";s:3:"ㅁ";s:3:"ᄆ";s:3:"ㅂ";s:3:"ᄇ";s:3:"ㅃ";s:3:"ᄈ";s:3:"ㅄ";s:3:"ᄡ";s:3:"ㅅ";s:3:"ᄉ";s:3:"ㅆ";s:3:"ᄊ";s:3:"ㅇ";s:3:"ᄋ";s:3:"ㅈ";s:3:"ᄌ";s:3:"ㅉ";s:3:"ᄍ";s:3:"ㅊ";s:3:"ᄎ";s:3:"ㅋ";s:3:"ᄏ";s:3:"ㅌ";s:3:"ᄐ";s:3:"ㅍ";s:3:"ᄑ";s:3:"ㅎ";s:3:"ᄒ";s:3:"ㅏ";s:3:"ᅡ";s:3:"ㅐ";s:3:"ᅢ";s:3:"ㅑ";s:3:"ᅣ";s:3:"ㅒ";s:3:"ᅤ";s:3:"ㅓ";s:3:"ᅥ";s:3:"ㅔ";s:3:"ᅦ";s:3:"ㅕ";s:3:"ᅧ";s:3:"ㅖ";s:3:"ᅨ";s:3:"ㅗ";s:3:"ᅩ";s:3:"ㅘ";s:3:"ᅪ";s:3:"ㅙ";s:3:"ᅫ";s:3:"ㅚ";s:3:"ᅬ";s:3:"ㅛ";s:3:"ᅭ";s:3:"ㅜ";s:3:"ᅮ";s:3:"ㅝ";s:3:"ᅯ";s:3:"ㅞ";s:3:"ᅰ";s:3:"ㅟ";s:3:"ᅱ";s:3:"ㅠ";s:3:"ᅲ";s:3:"ㅡ";s:3:"ᅳ";s:3:"ㅢ";s:3:"ᅴ";s:3:"ㅣ";s:3:"ᅵ";s:3:"ㅤ";s:3:"ᅠ";s:3:"ㅥ";s:3:"ᄔ";s:3:"ㅦ";s:3:"ᄕ";s:3:"ㅧ";s:3:"ᇇ";s:3:"ㅨ";s:3:"ᇈ";s:3:"ㅩ";s:3:"ᇌ";s:3:"ㅪ";s:3:"ᇎ";s:3:"ㅫ";s:3:"ᇓ";s:3:"ㅬ";s:3:"ᇗ";s:3:"ㅭ";s:3:"ᇙ";s:3:"ㅮ";s:3:"ᄜ";s:3:"ㅯ";s:3:"ᇝ";s:3:"ㅰ";s:3:"ᇟ";s:3:"ㅱ";s:3:"ᄝ";s:3:"ㅲ";s:3:"ᄞ";s:3:"ㅳ";s:3:"ᄠ";s:3:"ㅴ";s:3:"ᄢ";s:3:"ㅵ";s:3:"ᄣ";s:3:"ㅶ";s:3:"ᄧ";s:3:"ㅷ";s:3:"ᄩ";s:3:"ㅸ";s:3:"ᄫ";s:3:"ㅹ";s:3:"ᄬ";s:3:"ㅺ";s:3:"ᄭ";s:3:"ㅻ";s:3:"ᄮ";s:3:"ㅼ";s:3:"ᄯ";s:3:"ㅽ";s:3:"ᄲ";s:3:"ㅾ";s:3:"ᄶ";s:3:"ㅿ";s:3:"ᅀ";s:3:"ㆀ";s:3:"ᅇ";s:3:"ㆁ";s:3:"ᅌ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"ᅗ";s:3:"ㆅ";s:3:"ᅘ";s:3:"ㆆ";s:3:"ᅙ";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ㆍ";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㆒";s:3:"一";s:3:"㆓";s:3:"二";s:3:"㆔";s:3:"三";s:3:"㆕";s:3:"四";s:3:"㆖";s:3:"上";s:3:"㆗";s:3:"中";s:3:"㆘";s:3:"下";s:3:"㆙";s:3:"甲";s:3:"㆚";s:3:"乙";s:3:"㆛";s:3:"丙";s:3:"㆜";s:3:"丁";s:3:"㆝";s:3:"天";s:3:"㆞";s:3:"地";s:3:"㆟";s:3:"人";s:3:"㈀";s:5:"(ᄀ)";s:3:"㈁";s:5:"(ᄂ)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(ᄅ)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(ᄋ)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(ᄏ)";s:3:"㈋";s:5:"(ᄐ)";s:3:"㈌";s:5:"(ᄑ)";s:3:"㈍";s:5:"(ᄒ)";s:3:"㈎";s:8:"(가)";s:3:"㈏";s:8:"(나)";s:3:"㈐";s:8:"(다)";s:3:"㈑";s:8:"(라)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(아)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(카)";s:3:"㈙";s:8:"(타)";s:3:"㈚";s:8:"(파)";s:3:"㈛";s:8:"(하)";s:3:"㈜";s:8:"(주)";s:3:"㈝";s:17:"(오전)";s:3:"㈞";s:14:"(오후)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(四)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(六)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(八)";s:3:"㈨";s:5:"(九)";s:3:"㈩";s:5:"(十)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(火)";s:3:"㈬";s:5:"(水)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(日)";s:3:"㈱";s:5:"(株)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(名)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(祝)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(学)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(企)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(協)";s:3:"㉀";s:5:"(祭)";s:3:"㉁";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉄";s:3:"問";s:3:"㉅";s:3:"幼";s:3:"㉆";s:3:"文";s:3:"㉇";s:3:"箏";s:3:"㉐";s:3:"PTE";s:3:"㉑";s:2:"21";s:3:"㉒";s:2:"22";s:3:"㉓";s:2:"23";s:3:"㉔";s:2:"24";s:3:"㉕";s:2:"25";s:3:"㉖";s:2:"26";s:3:"㉗";s:2:"27";s:3:"㉘";s:2:"28";s:3:"㉙";s:2:"29";s:3:"㉚";s:2:"30";s:3:"㉛";s:2:"31";s:3:"㉜";s:2:"32";s:3:"㉝";s:2:"33";s:3:"㉞";s:2:"34";s:3:"㉟";s:2:"35";s:3:"㉠";s:3:"ᄀ";s:3:"㉡";s:3:"ᄂ";s:3:"㉢";s:3:"ᄃ";s:3:"㉣";s:3:"ᄅ";s:3:"㉤";s:3:"ᄆ";s:3:"㉥";s:3:"ᄇ";s:3:"㉦";s:3:"ᄉ";s:3:"㉧";s:3:"ᄋ";s:3:"㉨";s:3:"ᄌ";s:3:"㉩";s:3:"ᄎ";s:3:"㉪";s:3:"ᄏ";s:3:"㉫";s:3:"ᄐ";s:3:"㉬";s:3:"ᄑ";s:3:"㉭";s:3:"ᄒ";s:3:"㉮";s:6:"가";s:3:"㉯";s:6:"나";s:3:"㉰";s:6:"다";s:3:"㉱";s:6:"라";s:3:"㉲";s:6:"마";s:3:"㉳";s:6:"바";s:3:"㉴";s:6:"사";s:3:"㉵";s:6:"아";s:3:"㉶";s:6:"자";s:3:"㉷";s:6:"차";s:3:"㉸";s:6:"카";s:3:"㉹";s:6:"타";s:3:"㉺";s:6:"파";s:3:"㉻";s:6:"하";s:3:"㉼";s:15:"참고";s:3:"㉽";s:12:"주의";s:3:"㉾";s:6:"우";s:3:"㊀";s:3:"一";s:3:"㊁";s:3:"二";s:3:"㊂";s:3:"三";s:3:"㊃";s:3:"四";s:3:"㊄";s:3:"五";s:3:"㊅";s:3:"六";s:3:"㊆";s:3:"七";s:3:"㊇";s:3:"八";s:3:"㊈";s:3:"九";s:3:"㊉";s:3:"十";s:3:"㊊";s:3:"月";s:3:"㊋";s:3:"火";s:3:"㊌";s:3:"水";s:3:"㊍";s:3:"木";s:3:"㊎";s:3:"金";s:3:"㊏";s:3:"土";s:3:"㊐";s:3:"日";s:3:"㊑";s:3:"株";s:3:"㊒";s:3:"有";s:3:"㊓";s:3:"社";s:3:"㊔";s:3:"名";s:3:"㊕";s:3:"特";s:3:"㊖";s:3:"財";s:3:"㊗";s:3:"祝";s:3:"㊘";s:3:"労";s:3:"㊙";s:3:"秘";s:3:"㊚";s:3:"男";s:3:"㊛";s:3:"女";s:3:"㊜";s:3:"適";s:3:"㊝";s:3:"優";s:3:"㊞";s:3:"印";s:3:"㊟";s:3:"注";s:3:"㊠";s:3:"項";s:3:"㊡";s:3:"休";s:3:"㊢";s:3:"写";s:3:"㊣";s:3:"正";s:3:"㊤";s:3:"上";s:3:"㊥";s:3:"中";s:3:"㊦";s:3:"下";s:3:"㊧";s:3:"左";s:3:"㊨";s:3:"右";s:3:"㊩";s:3:"医";s:3:"㊪";s:3:"宗";s:3:"㊫";s:3:"学";s:3:"㊬";s:3:"監";s:3:"㊭";s:3:"企";s:3:"㊮";s:3:"資";s:3:"㊯";s:3:"協";s:3:"㊰";s:3:"夜";s:3:"㊱";s:2:"36";s:3:"㊲";s:2:"37";s:3:"㊳";s:2:"38";s:3:"㊴";s:2:"39";s:3:"㊵";s:2:"40";s:3:"㊶";s:2:"41";s:3:"㊷";s:2:"42";s:3:"㊸";s:2:"43";s:3:"㊹";s:2:"44";s:3:"㊺";s:2:"45";s:3:"㊻";s:2:"46";s:3:"㊼";s:2:"47";s:3:"㊽";s:2:"48";s:3:"㊾";s:2:"49";s:3:"㊿";s:2:"50";s:3:"㋀";s:4:"1月";s:3:"㋁";s:4:"2月";s:3:"㋂";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"㋄";s:4:"5月";s:3:"㋅";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"㋋";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"㋍";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"㋏";s:3:"LTD";s:3:"㋐";s:3:"ア";s:3:"㋑";s:3:"イ";s:3:"㋒";s:3:"ウ";s:3:"㋓";s:3:"エ";s:3:"㋔";s:3:"オ";s:3:"㋕";s:3:"カ";s:3:"㋖";s:3:"キ";s:3:"㋗";s:3:"ク";s:3:"㋘";s:3:"ケ";s:3:"㋙";s:3:"コ";s:3:"㋚";s:3:"サ";s:3:"㋛";s:3:"シ";s:3:"㋜";s:3:"ス";s:3:"㋝";s:3:"セ";s:3:"㋞";s:3:"ソ";s:3:"㋟";s:3:"タ";s:3:"㋠";s:3:"チ";s:3:"㋡";s:3:"ツ";s:3:"㋢";s:3:"テ";s:3:"㋣";s:3:"ト";s:3:"㋤";s:3:"ナ";s:3:"㋥";s:3:"ニ";s:3:"㋦";s:3:"ヌ";s:3:"㋧";s:3:"ネ";s:3:"㋨";s:3:"ノ";s:3:"㋩";s:3:"ハ";s:3:"㋪";s:3:"ヒ";s:3:"㋫";s:3:"フ";s:3:"㋬";s:3:"ヘ";s:3:"㋭";s:3:"ホ";s:3:"㋮";s:3:"マ";s:3:"㋯";s:3:"ミ";s:3:"㋰";s:3:"ム";s:3:"㋱";s:3:"メ";s:3:"㋲";s:3:"モ";s:3:"㋳";s:3:"ヤ";s:3:"㋴";s:3:"ユ";s:3:"㋵";s:3:"ヨ";s:3:"㋶";s:3:"ラ";s:3:"㋷";s:3:"リ";s:3:"㋸";s:3:"ル";s:3:"㋹";s:3:"レ";s:3:"㋺";s:3:"ロ";s:3:"㋻";s:3:"ワ";s:3:"㋼";s:3:"ヰ";s:3:"㋽";s:3:"ヱ";s:3:"㋾";s:3:"ヲ";s:3:"㌀";s:15:"アパート";s:3:"㌁";s:12:"アルファ";s:3:"㌂";s:15:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:15:"イニング";s:3:"㌅";s:9:"インチ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:18:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"㌍";s:12:"カロリー";s:3:"㌎";s:12:"ガロン";s:3:"㌏";s:12:"ガンマ";s:3:"㌐";s:12:"ギガ";s:3:"㌑";s:12:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:18:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:18:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:12:"グラム";s:3:"㌙";s:18:"グラムトン";s:3:"㌚";s:18:"クルゼイロ";s:3:"㌛";s:12:"クローネ";s:3:"㌜";s:9:"ケース";s:3:"㌝";s:9:"コルナ";s:3:"㌞";s:12:"コーポ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンチーム";s:3:"㌡";s:15:"シリング";s:3:"㌢";s:9:"センチ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:12:"ダース";s:3:"㌥";s:9:"デシ";s:3:"㌦";s:9:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ハイツ";s:3:"㌫";s:18:"パーセント";s:3:"㌬";s:12:"パーツ";s:3:"㌭";s:15:"バーレル";s:3:"㌮";s:18:"ピアストル";s:3:"㌯";s:12:"ピクル";s:3:"㌰";s:9:"ピコ";s:3:"㌱";s:9:"ビル";s:3:"㌲";s:18:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:18:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:9:"ペソ";s:3:"㌸";s:12:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:12:"ペンス";s:3:"㌻";s:15:"ページ";s:3:"㌼";s:12:"ベータ";s:3:"㌽";s:15:"ポイント";s:3:"㌾";s:12:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"㍀";s:15:"ポンド";s:3:"㍁";s:9:"ホール";s:3:"㍂";s:9:"ホーン";s:3:"㍃";s:12:"マイクロ";s:3:"㍄";s:9:"マイル";s:3:"㍅";s:9:"マッハ";s:3:"㍆";s:9:"マルク";s:3:"㍇";s:15:"マンション";s:3:"㍈";s:12:"ミクロン";s:3:"㍉";s:6:"ミリ";s:3:"㍊";s:18:"ミリバール";s:3:"㍋";s:9:"メガ";s:3:"㍌";s:15:"メガトン";s:3:"㍍";s:12:"メートル";s:3:"㍎";s:12:"ヤード";s:3:"㍏";s:9:"ヤール";s:3:"㍐";s:9:"ユアン";s:3:"㍑";s:12:"リットル";s:3:"㍒";s:6:"リラ";s:3:"㍓";s:12:"ルピー";s:3:"㍔";s:15:"ルーブル";s:3:"㍕";s:6:"レム";s:3:"㍖";s:18:"レントゲン";s:3:"㍗";s:9:"ワット";s:3:"㍘";s:4:"0点";s:3:"㍙";s:4:"1点";s:3:"㍚";s:4:"2点";s:3:"㍛";s:4:"3点";s:3:"㍜";s:4:"4点";s:3:"㍝";s:4:"5点";s:3:"㍞";s:4:"6点";s:3:"㍟";s:4:"7点";s:3:"㍠";s:4:"8点";s:3:"㍡";s:4:"9点";s:3:"㍢";s:5:"10点";s:3:"㍣";s:5:"11点";s:3:"㍤";s:5:"12点";s:3:"㍥";s:5:"13点";s:3:"㍦";s:5:"14点";s:3:"㍧";s:5:"15点";s:3:"㍨";s:5:"16点";s:3:"㍩";s:5:"17点";s:3:"㍪";s:5:"18点";s:3:"㍫";s:5:"19点";s:3:"㍬";s:5:"20点";s:3:"㍭";s:5:"21点";s:3:"㍮";s:5:"22点";s:3:"㍯";s:5:"23点";s:3:"㍰";s:5:"24点";s:3:"㍱";s:3:"hPa";s:3:"㍲";s:2:"da";s:3:"㍳";s:2:"AU";s:3:"㍴";s:3:"bar";s:3:"㍵";s:2:"oV";s:3:"㍶";s:2:"pc";s:3:"㍷";s:2:"dm";s:3:"㍸";s:3:"dm2";s:3:"㍹";s:3:"dm3";s:3:"㍺";s:2:"IU";s:3:"㍻";s:6:"平成";s:3:"㍼";s:6:"昭和";s:3:"㍽";s:6:"大正";s:3:"㍾";s:6:"明治";s:3:"㍿";s:12:"株式会社";s:3:"㎀";s:2:"pA";s:3:"㎁";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"㎍";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"㎏";s:2:"kg";s:3:"㎐";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:3:"μl";s:3:"㎖";s:2:"ml";s:3:"㎗";s:2:"dl";s:3:"㎘";s:2:"kl";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"㎝";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:3:"mm2";s:3:"㎠";s:3:"cm2";s:3:"㎡";s:2:"m2";s:3:"㎢";s:3:"km2";s:3:"㎣";s:3:"mm3";s:3:"㎤";s:3:"cm3";s:3:"㎥";s:2:"m3";s:3:"㎦";s:3:"km3";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:6:"m∕s2";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:8:"rad∕s2";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"㏀";s:3:"kΩ";s:3:"㏁";s:3:"MΩ";s:3:"㏂";s:4:"a.m.";s:3:"㏃";s:2:"Bq";s:3:"㏄";s:2:"cc";s:3:"㏅";s:2:"cd";s:3:"㏆";s:6:"C∕kg";s:3:"㏇";s:3:"Co.";s:3:"㏈";s:2:"dB";s:3:"㏉";s:2:"Gy";s:3:"㏊";s:2:"ha";s:3:"㏋";s:2:"HP";s:3:"㏌";s:2:"in";s:3:"㏍";s:2:"KK";s:3:"㏎";s:2:"KM";s:3:"㏏";s:2:"kt";s:3:"㏐";s:2:"lm";s:3:"㏑";s:2:"ln";s:3:"㏒";s:3:"log";s:3:"㏓";s:2:"lx";s:3:"㏔";s:2:"mb";s:3:"㏕";s:3:"mil";s:3:"㏖";s:3:"mol";s:3:"㏗";s:2:"PH";s:3:"㏘";s:4:"p.m.";s:3:"㏙";s:3:"PPM";s:3:"㏚";s:2:"PR";s:3:"㏛";s:2:"sr";s:3:"㏜";s:2:"Sv";s:3:"㏝";s:2:"Wb";s:3:"㏞";s:5:"V∕m";s:3:"㏟";s:5:"A∕m";s:3:"㏠";s:4:"1日";s:3:"㏡";s:4:"2日";s:3:"㏢";s:4:"3日";s:3:"㏣";s:4:"4日";s:3:"㏤";s:4:"5日";s:3:"㏥";s:4:"6日";s:3:"㏦";s:4:"7日";s:3:"㏧";s:4:"8日";s:3:"㏨";s:4:"9日";s:3:"㏩";s:5:"10日";s:3:"㏪";s:5:"11日";s:3:"㏫";s:5:"12日";s:3:"㏬";s:5:"13日";s:3:"㏭";s:5:"14日";s:3:"㏮";s:5:"15日";s:3:"㏯";s:5:"16日";s:3:"㏰";s:5:"17日";s:3:"㏱";s:5:"18日";s:3:"㏲";s:5:"19日";s:3:"㏳";s:5:"20日";s:3:"㏴";s:5:"21日";s:3:"㏵";s:5:"22日";s:3:"㏶";s:5:"23日";s:3:"㏷";s:5:"24日";s:3:"㏸";s:5:"25日";s:3:"㏹";s:5:"26日";s:3:"㏺";s:5:"27日";s:3:"㏻";s:5:"28日";s:3:"㏼";s:5:"29日";s:3:"㏽";s:5:"30日";s:3:"㏾";s:5:"31日";s:3:"㏿";s:3:"gal";s:3:"ꝰ";s:3:"ꝯ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:3:"𤋮";s:4:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"ff";s:2:"ff";s:3:"fi";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:2:"st";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"մն";s:3:"ﬔ";s:4:"մե";s:3:"ﬕ";s:4:"մի";s:3:"ﬖ";s:4:"վն";s:3:"ﬗ";s:4:"մխ";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"ﬠ";s:2:"ע";s:3:"ﬡ";s:2:"א";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"ה";s:3:"ﬤ";s:2:"כ";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"ם";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:3:"ﭏ";s:4:"אל";s:3:"ﭐ";s:2:"ٱ";s:3:"ﭑ";s:2:"ٱ";s:3:"ﭒ";s:2:"ٻ";s:3:"ﭓ";s:2:"ٻ";s:3:"ﭔ";s:2:"ٻ";s:3:"ﭕ";s:2:"ٻ";s:3:"ﭖ";s:2:"پ";s:3:"ﭗ";s:2:"پ";s:3:"ﭘ";s:2:"پ";s:3:"ﭙ";s:2:"پ";s:3:"ﭚ";s:2:"ڀ";s:3:"ﭛ";s:2:"ڀ";s:3:"ﭜ";s:2:"ڀ";s:3:"ﭝ";s:2:"ڀ";s:3:"ﭞ";s:2:"ٺ";s:3:"ﭟ";s:2:"ٺ";s:3:"ﭠ";s:2:"ٺ";s:3:"ﭡ";s:2:"ٺ";s:3:"ﭢ";s:2:"ٿ";s:3:"ﭣ";s:2:"ٿ";s:3:"ﭤ";s:2:"ٿ";s:3:"ﭥ";s:2:"ٿ";s:3:"ﭦ";s:2:"ٹ";s:3:"ﭧ";s:2:"ٹ";s:3:"ﭨ";s:2:"ٹ";s:3:"ﭩ";s:2:"ٹ";s:3:"ﭪ";s:2:"ڤ";s:3:"ﭫ";s:2:"ڤ";s:3:"ﭬ";s:2:"ڤ";s:3:"ﭭ";s:2:"ڤ";s:3:"ﭮ";s:2:"ڦ";s:3:"ﭯ";s:2:"ڦ";s:3:"ﭰ";s:2:"ڦ";s:3:"ﭱ";s:2:"ڦ";s:3:"ﭲ";s:2:"ڄ";s:3:"ﭳ";s:2:"ڄ";s:3:"ﭴ";s:2:"ڄ";s:3:"ﭵ";s:2:"ڄ";s:3:"ﭶ";s:2:"ڃ";s:3:"ﭷ";s:2:"ڃ";s:3:"ﭸ";s:2:"ڃ";s:3:"ﭹ";s:2:"ڃ";s:3:"ﭺ";s:2:"چ";s:3:"ﭻ";s:2:"چ";s:3:"ﭼ";s:2:"چ";s:3:"ﭽ";s:2:"چ";s:3:"ﭾ";s:2:"ڇ";s:3:"ﭿ";s:2:"ڇ";s:3:"ﮀ";s:2:"ڇ";s:3:"ﮁ";s:2:"ڇ";s:3:"ﮂ";s:2:"ڍ";s:3:"ﮃ";s:2:"ڍ";s:3:"ﮄ";s:2:"ڌ";s:3:"ﮅ";s:2:"ڌ";s:3:"ﮆ";s:2:"ڎ";s:3:"ﮇ";s:2:"ڎ";s:3:"ﮈ";s:2:"ڈ";s:3:"ﮉ";s:2:"ڈ";s:3:"ﮊ";s:2:"ژ";s:3:"ﮋ";s:2:"ژ";s:3:"ﮌ";s:2:"ڑ";s:3:"ﮍ";s:2:"ڑ";s:3:"ﮎ";s:2:"ک";s:3:"ﮏ";s:2:"ک";s:3:"ﮐ";s:2:"ک";s:3:"ﮑ";s:2:"ک";s:3:"ﮒ";s:2:"گ";s:3:"ﮓ";s:2:"گ";s:3:"ﮔ";s:2:"گ";s:3:"ﮕ";s:2:"گ";s:3:"ﮖ";s:2:"ڳ";s:3:"ﮗ";s:2:"ڳ";s:3:"ﮘ";s:2:"ڳ";s:3:"ﮙ";s:2:"ڳ";s:3:"ﮚ";s:2:"ڱ";s:3:"ﮛ";s:2:"ڱ";s:3:"ﮜ";s:2:"ڱ";s:3:"ﮝ";s:2:"ڱ";s:3:"ﮞ";s:2:"ں";s:3:"ﮟ";s:2:"ں";s:3:"ﮠ";s:2:"ڻ";s:3:"ﮡ";s:2:"ڻ";s:3:"ﮢ";s:2:"ڻ";s:3:"ﮣ";s:2:"ڻ";s:3:"ﮤ";s:4:"ۀ";s:3:"ﮥ";s:4:"ۀ";s:3:"ﮦ";s:2:"ہ";s:3:"ﮧ";s:2:"ہ";s:3:"ﮨ";s:2:"ہ";s:3:"ﮩ";s:2:"ہ";s:3:"ﮪ";s:2:"ھ";s:3:"ﮫ";s:2:"ھ";s:3:"ﮬ";s:2:"ھ";s:3:"ﮭ";s:2:"ھ";s:3:"ﮮ";s:2:"ے";s:3:"ﮯ";s:2:"ے";s:3:"ﮰ";s:4:"ۓ";s:3:"ﮱ";s:4:"ۓ";s:3:"ﯓ";s:2:"ڭ";s:3:"ﯔ";s:2:"ڭ";s:3:"ﯕ";s:2:"ڭ";s:3:"ﯖ";s:2:"ڭ";s:3:"ﯗ";s:2:"ۇ";s:3:"ﯘ";s:2:"ۇ";s:3:"ﯙ";s:2:"ۆ";s:3:"ﯚ";s:2:"ۆ";s:3:"ﯛ";s:2:"ۈ";s:3:"ﯜ";s:2:"ۈ";s:3:"ﯝ";s:4:"ۇٴ";s:3:"ﯞ";s:2:"ۋ";s:3:"ﯟ";s:2:"ۋ";s:3:"ﯠ";s:2:"ۅ";s:3:"ﯡ";s:2:"ۅ";s:3:"ﯢ";s:2:"ۉ";s:3:"ﯣ";s:2:"ۉ";s:3:"ﯤ";s:2:"ې";s:3:"ﯥ";s:2:"ې";s:3:"ﯦ";s:2:"ې";s:3:"ﯧ";s:2:"ې";s:3:"ﯨ";s:2:"ى";s:3:"ﯩ";s:2:"ى";s:3:"ﯪ";s:6:"ئا";s:3:"ﯫ";s:6:"ئا";s:3:"ﯬ";s:6:"ئە";s:3:"ﯭ";s:6:"ئە";s:3:"ﯮ";s:6:"ئو";s:3:"ﯯ";s:6:"ئو";s:3:"ﯰ";s:6:"ئۇ";s:3:"ﯱ";s:6:"ئۇ";s:3:"ﯲ";s:6:"ئۆ";s:3:"ﯳ";s:6:"ئۆ";s:3:"ﯴ";s:6:"ئۈ";s:3:"ﯵ";s:6:"ئۈ";s:3:"ﯶ";s:6:"ئې";s:3:"ﯷ";s:6:"ئې";s:3:"ﯸ";s:6:"ئې";s:3:"ﯹ";s:6:"ئى";s:3:"ﯺ";s:6:"ئى";s:3:"ﯻ";s:6:"ئى";s:3:"ﯼ";s:2:"ی";s:3:"ﯽ";s:2:"ی";s:3:"ﯾ";s:2:"ی";s:3:"ﯿ";s:2:"ی";s:3:"ﰀ";s:6:"ئج";s:3:"ﰁ";s:6:"ئح";s:3:"ﰂ";s:6:"ئم";s:3:"ﰃ";s:6:"ئى";s:3:"ﰄ";s:6:"ئي";s:3:"ﰅ";s:4:"بج";s:3:"ﰆ";s:4:"بح";s:3:"ﰇ";s:4:"بخ";s:3:"ﰈ";s:4:"بم";s:3:"ﰉ";s:4:"بى";s:3:"ﰊ";s:4:"بي";s:3:"ﰋ";s:4:"تج";s:3:"ﰌ";s:4:"تح";s:3:"ﰍ";s:4:"تخ";s:3:"ﰎ";s:4:"تم";s:3:"ﰏ";s:4:"تى";s:3:"ﰐ";s:4:"تي";s:3:"ﰑ";s:4:"ثج";s:3:"ﰒ";s:4:"ثم";s:3:"ﰓ";s:4:"ثى";s:3:"ﰔ";s:4:"ثي";s:3:"ﰕ";s:4:"جح";s:3:"ﰖ";s:4:"جم";s:3:"ﰗ";s:4:"حج";s:3:"ﰘ";s:4:"حم";s:3:"ﰙ";s:4:"خج";s:3:"ﰚ";s:4:"خح";s:3:"ﰛ";s:4:"خم";s:3:"ﰜ";s:4:"سج";s:3:"ﰝ";s:4:"سح";s:3:"ﰞ";s:4:"سخ";s:3:"ﰟ";s:4:"سم";s:3:"ﰠ";s:4:"صح";s:3:"ﰡ";s:4:"صم";s:3:"ﰢ";s:4:"ضج";s:3:"ﰣ";s:4:"ضح";s:3:"ﰤ";s:4:"ضخ";s:3:"ﰥ";s:4:"ضم";s:3:"ﰦ";s:4:"طح";s:3:"ﰧ";s:4:"طم";s:3:"ﰨ";s:4:"ظم";s:3:"ﰩ";s:4:"عج";s:3:"ﰪ";s:4:"عم";s:3:"ﰫ";s:4:"غج";s:3:"ﰬ";s:4:"غم";s:3:"ﰭ";s:4:"فج";s:3:"ﰮ";s:4:"فح";s:3:"ﰯ";s:4:"فخ";s:3:"ﰰ";s:4:"فم";s:3:"ﰱ";s:4:"فى";s:3:"ﰲ";s:4:"في";s:3:"ﰳ";s:4:"قح";s:3:"ﰴ";s:4:"قم";s:3:"ﰵ";s:4:"قى";s:3:"ﰶ";s:4:"قي";s:3:"ﰷ";s:4:"كا";s:3:"ﰸ";s:4:"كج";s:3:"ﰹ";s:4:"كح";s:3:"ﰺ";s:4:"كخ";s:3:"ﰻ";s:4:"كل";s:3:"ﰼ";s:4:"كم";s:3:"ﰽ";s:4:"كى";s:3:"ﰾ";s:4:"كي";s:3:"ﰿ";s:4:"لج";s:3:"ﱀ";s:4:"لح";s:3:"ﱁ";s:4:"لخ";s:3:"ﱂ";s:4:"لم";s:3:"ﱃ";s:4:"لى";s:3:"ﱄ";s:4:"لي";s:3:"ﱅ";s:4:"مج";s:3:"ﱆ";s:4:"مح";s:3:"ﱇ";s:4:"مخ";s:3:"ﱈ";s:4:"مم";s:3:"ﱉ";s:4:"مى";s:3:"ﱊ";s:4:"مي";s:3:"ﱋ";s:4:"نج";s:3:"ﱌ";s:4:"نح";s:3:"ﱍ";s:4:"نخ";s:3:"ﱎ";s:4:"نم";s:3:"ﱏ";s:4:"نى";s:3:"ﱐ";s:4:"ني";s:3:"ﱑ";s:4:"هج";s:3:"ﱒ";s:4:"هم";s:3:"ﱓ";s:4:"هى";s:3:"ﱔ";s:4:"هي";s:3:"ﱕ";s:4:"يج";s:3:"ﱖ";s:4:"يح";s:3:"ﱗ";s:4:"يخ";s:3:"ﱘ";s:4:"يم";s:3:"ﱙ";s:4:"يى";s:3:"ﱚ";s:4:"يي";s:3:"ﱛ";s:4:"ذٰ";s:3:"ﱜ";s:4:"رٰ";s:3:"ﱝ";s:4:"ىٰ";s:3:"ﱞ";s:5:" ٌّ";s:3:"ﱟ";s:5:" ٍّ";s:3:"ﱠ";s:5:" َّ";s:3:"ﱡ";s:5:" ُّ";s:3:"ﱢ";s:5:" ِّ";s:3:"ﱣ";s:5:" ّٰ";s:3:"ﱤ";s:6:"ئر";s:3:"ﱥ";s:6:"ئز";s:3:"ﱦ";s:6:"ئم";s:3:"ﱧ";s:6:"ئن";s:3:"ﱨ";s:6:"ئى";s:3:"ﱩ";s:6:"ئي";s:3:"ﱪ";s:4:"بر";s:3:"ﱫ";s:4:"بز";s:3:"ﱬ";s:4:"بم";s:3:"ﱭ";s:4:"بن";s:3:"ﱮ";s:4:"بى";s:3:"ﱯ";s:4:"بي";s:3:"ﱰ";s:4:"تر";s:3:"ﱱ";s:4:"تز";s:3:"ﱲ";s:4:"تم";s:3:"ﱳ";s:4:"تن";s:3:"ﱴ";s:4:"تى";s:3:"ﱵ";s:4:"تي";s:3:"ﱶ";s:4:"ثر";s:3:"ﱷ";s:4:"ثز";s:3:"ﱸ";s:4:"ثم";s:3:"ﱹ";s:4:"ثن";s:3:"ﱺ";s:4:"ثى";s:3:"ﱻ";s:4:"ثي";s:3:"ﱼ";s:4:"فى";s:3:"ﱽ";s:4:"في";s:3:"ﱾ";s:4:"قى";s:3:"ﱿ";s:4:"قي";s:3:"ﲀ";s:4:"كا";s:3:"ﲁ";s:4:"كل";s:3:"ﲂ";s:4:"كم";s:3:"ﲃ";s:4:"كى";s:3:"ﲄ";s:4:"كي";s:3:"ﲅ";s:4:"لم";s:3:"ﲆ";s:4:"لى";s:3:"ﲇ";s:4:"لي";s:3:"ﲈ";s:4:"ما";s:3:"ﲉ";s:4:"مم";s:3:"ﲊ";s:4:"نر";s:3:"ﲋ";s:4:"نز";s:3:"ﲌ";s:4:"نم";s:3:"ﲍ";s:4:"نن";s:3:"ﲎ";s:4:"نى";s:3:"ﲏ";s:4:"ني";s:3:"ﲐ";s:4:"ىٰ";s:3:"ﲑ";s:4:"ير";s:3:"ﲒ";s:4:"يز";s:3:"ﲓ";s:4:"يم";s:3:"ﲔ";s:4:"ين";s:3:"ﲕ";s:4:"يى";s:3:"ﲖ";s:4:"يي";s:3:"ﲗ";s:6:"ئج";s:3:"ﲘ";s:6:"ئح";s:3:"ﲙ";s:6:"ئخ";s:3:"ﲚ";s:6:"ئم";s:3:"ﲛ";s:6:"ئه";s:3:"ﲜ";s:4:"بج";s:3:"ﲝ";s:4:"بح";s:3:"ﲞ";s:4:"بخ";s:3:"ﲟ";s:4:"بم";s:3:"ﲠ";s:4:"به";s:3:"ﲡ";s:4:"تج";s:3:"ﲢ";s:4:"تح";s:3:"ﲣ";s:4:"تخ";s:3:"ﲤ";s:4:"تم";s:3:"ﲥ";s:4:"ته";s:3:"ﲦ";s:4:"ثم";s:3:"ﲧ";s:4:"جح";s:3:"ﲨ";s:4:"جم";s:3:"ﲩ";s:4:"حج";s:3:"ﲪ";s:4:"حم";s:3:"ﲫ";s:4:"خج";s:3:"ﲬ";s:4:"خم";s:3:"ﲭ";s:4:"سج";s:3:"ﲮ";s:4:"سح";s:3:"ﲯ";s:4:"سخ";s:3:"ﲰ";s:4:"سم";s:3:"ﲱ";s:4:"صح";s:3:"ﲲ";s:4:"صخ";s:3:"ﲳ";s:4:"صم";s:3:"ﲴ";s:4:"ضج";s:3:"ﲵ";s:4:"ضح";s:3:"ﲶ";s:4:"ضخ";s:3:"ﲷ";s:4:"ضم";s:3:"ﲸ";s:4:"طح";s:3:"ﲹ";s:4:"ظم";s:3:"ﲺ";s:4:"عج";s:3:"ﲻ";s:4:"عم";s:3:"ﲼ";s:4:"غج";s:3:"ﲽ";s:4:"غم";s:3:"ﲾ";s:4:"فج";s:3:"ﲿ";s:4:"فح";s:3:"ﳀ";s:4:"فخ";s:3:"ﳁ";s:4:"فم";s:3:"ﳂ";s:4:"قح";s:3:"ﳃ";s:4:"قم";s:3:"ﳄ";s:4:"كج";s:3:"ﳅ";s:4:"كح";s:3:"ﳆ";s:4:"كخ";s:3:"ﳇ";s:4:"كل";s:3:"ﳈ";s:4:"كم";s:3:"ﳉ";s:4:"لج";s:3:"ﳊ";s:4:"لح";s:3:"ﳋ";s:4:"لخ";s:3:"ﳌ";s:4:"لم";s:3:"ﳍ";s:4:"له";s:3:"ﳎ";s:4:"مج";s:3:"ﳏ";s:4:"مح";s:3:"ﳐ";s:4:"مخ";s:3:"ﳑ";s:4:"مم";s:3:"ﳒ";s:4:"نج";s:3:"ﳓ";s:4:"نح";s:3:"ﳔ";s:4:"نخ";s:3:"ﳕ";s:4:"نم";s:3:"ﳖ";s:4:"نه";s:3:"ﳗ";s:4:"هج";s:3:"ﳘ";s:4:"هم";s:3:"ﳙ";s:4:"هٰ";s:3:"ﳚ";s:4:"يج";s:3:"ﳛ";s:4:"يح";s:3:"ﳜ";s:4:"يخ";s:3:"ﳝ";s:4:"يم";s:3:"ﳞ";s:4:"يه";s:3:"ﳟ";s:6:"ئم";s:3:"ﳠ";s:6:"ئه";s:3:"ﳡ";s:4:"بم";s:3:"ﳢ";s:4:"به";s:3:"ﳣ";s:4:"تم";s:3:"ﳤ";s:4:"ته";s:3:"ﳥ";s:4:"ثم";s:3:"ﳦ";s:4:"ثه";s:3:"ﳧ";s:4:"سم";s:3:"ﳨ";s:4:"سه";s:3:"ﳩ";s:4:"شم";s:3:"ﳪ";s:4:"شه";s:3:"ﳫ";s:4:"كل";s:3:"ﳬ";s:4:"كم";s:3:"ﳭ";s:4:"لم";s:3:"ﳮ";s:4:"نم";s:3:"ﳯ";s:4:"نه";s:3:"ﳰ";s:4:"يم";s:3:"ﳱ";s:4:"يه";s:3:"ﳲ";s:6:"ـَّ";s:3:"ﳳ";s:6:"ـُّ";s:3:"ﳴ";s:6:"ـِّ";s:3:"ﳵ";s:4:"طى";s:3:"ﳶ";s:4:"طي";s:3:"ﳷ";s:4:"عى";s:3:"ﳸ";s:4:"عي";s:3:"ﳹ";s:4:"غى";s:3:"ﳺ";s:4:"غي";s:3:"ﳻ";s:4:"سى";s:3:"ﳼ";s:4:"سي";s:3:"ﳽ";s:4:"شى";s:3:"ﳾ";s:4:"شي";s:3:"ﳿ";s:4:"حى";s:3:"ﴀ";s:4:"حي";s:3:"ﴁ";s:4:"جى";s:3:"ﴂ";s:4:"جي";s:3:"ﴃ";s:4:"خى";s:3:"ﴄ";s:4:"خي";s:3:"ﴅ";s:4:"صى";s:3:"ﴆ";s:4:"صي";s:3:"ﴇ";s:4:"ضى";s:3:"ﴈ";s:4:"ضي";s:3:"ﴉ";s:4:"شج";s:3:"ﴊ";s:4:"شح";s:3:"ﴋ";s:4:"شخ";s:3:"ﴌ";s:4:"شم";s:3:"ﴍ";s:4:"شر";s:3:"ﴎ";s:4:"سر";s:3:"ﴏ";s:4:"صر";s:3:"ﴐ";s:4:"ضر";s:3:"ﴑ";s:4:"طى";s:3:"ﴒ";s:4:"طي";s:3:"ﴓ";s:4:"عى";s:3:"ﴔ";s:4:"عي";s:3:"ﴕ";s:4:"غى";s:3:"ﴖ";s:4:"غي";s:3:"ﴗ";s:4:"سى";s:3:"ﴘ";s:4:"سي";s:3:"ﴙ";s:4:"شى";s:3:"ﴚ";s:4:"شي";s:3:"ﴛ";s:4:"حى";s:3:"ﴜ";s:4:"حي";s:3:"ﴝ";s:4:"جى";s:3:"ﴞ";s:4:"جي";s:3:"ﴟ";s:4:"خى";s:3:"ﴠ";s:4:"خي";s:3:"ﴡ";s:4:"صى";s:3:"ﴢ";s:4:"صي";s:3:"ﴣ";s:4:"ضى";s:3:"ﴤ";s:4:"ضي";s:3:"ﴥ";s:4:"شج";s:3:"ﴦ";s:4:"شح";s:3:"ﴧ";s:4:"شخ";s:3:"ﴨ";s:4:"شم";s:3:"ﴩ";s:4:"شر";s:3:"ﴪ";s:4:"سر";s:3:"ﴫ";s:4:"صر";s:3:"ﴬ";s:4:"ضر";s:3:"ﴭ";s:4:"شج";s:3:"ﴮ";s:4:"شح";s:3:"ﴯ";s:4:"شخ";s:3:"ﴰ";s:4:"شم";s:3:"ﴱ";s:4:"سه";s:3:"ﴲ";s:4:"شه";s:3:"ﴳ";s:4:"طم";s:3:"ﴴ";s:4:"سج";s:3:"ﴵ";s:4:"سح";s:3:"ﴶ";s:4:"سخ";s:3:"ﴷ";s:4:"شج";s:3:"ﴸ";s:4:"شح";s:3:"ﴹ";s:4:"شخ";s:3:"ﴺ";s:4:"طم";s:3:"ﴻ";s:4:"ظم";s:3:"ﴼ";s:4:"اً";s:3:"ﴽ";s:4:"اً";s:3:"ﵐ";s:6:"تجم";s:3:"ﵑ";s:6:"تحج";s:3:"ﵒ";s:6:"تحج";s:3:"ﵓ";s:6:"تحم";s:3:"ﵔ";s:6:"تخم";s:3:"ﵕ";s:6:"تمج";s:3:"ﵖ";s:6:"تمح";s:3:"ﵗ";s:6:"تمخ";s:3:"ﵘ";s:6:"جمح";s:3:"ﵙ";s:6:"جمح";s:3:"ﵚ";s:6:"حمي";s:3:"ﵛ";s:6:"حمى";s:3:"ﵜ";s:6:"سحج";s:3:"ﵝ";s:6:"سجح";s:3:"ﵞ";s:6:"سجى";s:3:"ﵟ";s:6:"سمح";s:3:"ﵠ";s:6:"سمح";s:3:"ﵡ";s:6:"سمج";s:3:"ﵢ";s:6:"سمم";s:3:"ﵣ";s:6:"سمم";s:3:"ﵤ";s:6:"صحح";s:3:"ﵥ";s:6:"صحح";s:3:"ﵦ";s:6:"صمم";s:3:"ﵧ";s:6:"شحم";s:3:"ﵨ";s:6:"شحم";s:3:"ﵩ";s:6:"شجي";s:3:"ﵪ";s:6:"شمخ";s:3:"ﵫ";s:6:"شمخ";s:3:"ﵬ";s:6:"شمم";s:3:"ﵭ";s:6:"شمم";s:3:"ﵮ";s:6:"ضحى";s:3:"ﵯ";s:6:"ضخم";s:3:"ﵰ";s:6:"ضخم";s:3:"ﵱ";s:6:"طمح";s:3:"ﵲ";s:6:"طمح";s:3:"ﵳ";s:6:"طمم";s:3:"ﵴ";s:6:"طمي";s:3:"ﵵ";s:6:"عجم";s:3:"ﵶ";s:6:"عمم";s:3:"ﵷ";s:6:"عمم";s:3:"ﵸ";s:6:"عمى";s:3:"ﵹ";s:6:"غمم";s:3:"ﵺ";s:6:"غمي";s:3:"ﵻ";s:6:"غمى";s:3:"ﵼ";s:6:"فخم";s:3:"ﵽ";s:6:"فخم";s:3:"ﵾ";s:6:"قمح";s:3:"ﵿ";s:6:"قمم";s:3:"ﶀ";s:6:"لحم";s:3:"ﶁ";s:6:"لحي";s:3:"ﶂ";s:6:"لحى";s:3:"ﶃ";s:6:"لجج";s:3:"ﶄ";s:6:"لجج";s:3:"ﶅ";s:6:"لخم";s:3:"ﶆ";s:6:"لخم";s:3:"ﶇ";s:6:"لمح";s:3:"ﶈ";s:6:"لمح";s:3:"ﶉ";s:6:"محج";s:3:"ﶊ";s:6:"محم";s:3:"ﶋ";s:6:"محي";s:3:"ﶌ";s:6:"مجح";s:3:"ﶍ";s:6:"مجم";s:3:"ﶎ";s:6:"مخج";s:3:"ﶏ";s:6:"مخم";s:3:"ﶒ";s:6:"مجخ";s:3:"ﶓ";s:6:"همج";s:3:"ﶔ";s:6:"همم";s:3:"ﶕ";s:6:"نحم";s:3:"ﶖ";s:6:"نحى";s:3:"ﶗ";s:6:"نجم";s:3:"ﶘ";s:6:"نجم";s:3:"ﶙ";s:6:"نجى";s:3:"ﶚ";s:6:"نمي";s:3:"ﶛ";s:6:"نمى";s:3:"ﶜ";s:6:"يمم";s:3:"ﶝ";s:6:"يمم";s:3:"ﶞ";s:6:"بخي";s:3:"ﶟ";s:6:"تجي";s:3:"ﶠ";s:6:"تجى";s:3:"ﶡ";s:6:"تخي";s:3:"ﶢ";s:6:"تخى";s:3:"ﶣ";s:6:"تمي";s:3:"ﶤ";s:6:"تمى";s:3:"ﶥ";s:6:"جمي";s:3:"ﶦ";s:6:"جحى";s:3:"ﶧ";s:6:"جمى";s:3:"ﶨ";s:6:"سخى";s:3:"ﶩ";s:6:"صحي";s:3:"ﶪ";s:6:"شحي";s:3:"ﶫ";s:6:"ضحي";s:3:"ﶬ";s:6:"لجي";s:3:"ﶭ";s:6:"لمي";s:3:"ﶮ";s:6:"يحي";s:3:"ﶯ";s:6:"يجي";s:3:"ﶰ";s:6:"يمي";s:3:"ﶱ";s:6:"ممي";s:3:"ﶲ";s:6:"قمي";s:3:"ﶳ";s:6:"نحي";s:3:"ﶴ";s:6:"قمح";s:3:"ﶵ";s:6:"لحم";s:3:"ﶶ";s:6:"عمي";s:3:"ﶷ";s:6:"كمي";s:3:"ﶸ";s:6:"نجح";s:3:"ﶹ";s:6:"مخي";s:3:"ﶺ";s:6:"لجم";s:3:"ﶻ";s:6:"كمم";s:3:"ﶼ";s:6:"لجم";s:3:"ﶽ";s:6:"نجح";s:3:"ﶾ";s:6:"جحي";s:3:"ﶿ";s:6:"حجي";s:3:"ﷀ";s:6:"مجي";s:3:"ﷁ";s:6:"فمي";s:3:"ﷂ";s:6:"بحي";s:3:"ﷃ";s:6:"كمم";s:3:"ﷄ";s:6:"عجم";s:3:"ﷅ";s:6:"صمم";s:3:"ﷆ";s:6:"سخي";s:3:"ﷇ";s:6:"نجي";s:3:"ﷰ";s:6:"صلے";s:3:"ﷱ";s:6:"قلے";s:3:"ﷲ";s:8:"الله";s:3:"ﷳ";s:8:"اكبر";s:3:"ﷴ";s:8:"محمد";s:3:"ﷵ";s:8:"صلعم";s:3:"ﷶ";s:8:"رسول";s:3:"ﷷ";s:8:"عليه";s:3:"ﷸ";s:8:"وسلم";s:3:"ﷹ";s:6:"صلى";s:3:"ﷺ";s:33:"صلى الله عليه وسلم";s:3:"ﷻ";s:15:"جل جلاله";s:3:"﷼";s:8:"ریال";s:3:"︐";s:1:",";s:3:"︑";s:3:"、";s:3:"︒";s:3:"。";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︗";s:3:"〖";s:3:"︘";s:3:"〗";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︱";s:3:"—";s:3:"︲";s:3:"–";s:3:"︳";s:1:"_";s:3:"︴";s:1:"_";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:3:"〔";s:3:"︺";s:3:"〕";s:3:"︻";s:3:"【";s:3:"︼";s:3:"】";s:3:"︽";s:3:"《";s:3:"︾";s:3:"》";s:3:"︿";s:3:"〈";s:3:"﹀";s:3:"〉";s:3:"﹁";s:3:"「";s:3:"﹂";s:3:"」";s:3:"﹃";s:3:"『";s:3:"﹄";s:3:"』";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:3:"﹉";s:3:" ̅";s:3:"﹊";s:3:" ̅";s:3:"﹋";s:3:" ̅";s:3:"﹌";s:3:" ̅";s:3:"﹍";s:1:"_";s:3:"﹎";s:1:"_";s:3:"﹏";s:1:"_";s:3:"﹐";s:1:",";s:3:"﹑";s:3:"、";s:3:"﹒";s:1:".";s:3:"﹔";s:1:";";s:3:"﹕";s:1:":";s:3:"﹖";s:1:"?";s:3:"﹗";s:1:"!";s:3:"﹘";s:3:"—";s:3:"﹙";s:1:"(";s:3:"﹚";s:1:")";s:3:"﹛";s:1:"{";s:3:"﹜";s:1:"}";s:3:"﹝";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"﹠";s:1:"&";s:3:"﹡";s:1:"*";s:3:"﹢";s:1:"+";s:3:"﹣";s:1:"-";s:3:"﹤";s:1:"<";s:3:"﹥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"ﹰ";s:3:" ً";s:3:"ﹱ";s:4:"ـً";s:3:"ﹲ";s:3:" ٌ";s:3:"ﹴ";s:3:" ٍ";s:3:"ﹶ";s:3:" َ";s:3:"ﹷ";s:4:"ـَ";s:3:"ﹸ";s:3:" ُ";s:3:"ﹹ";s:4:"ـُ";s:3:"ﹺ";s:3:" ِ";s:3:"ﹻ";s:4:"ـِ";s:3:"ﹼ";s:3:" ّ";s:3:"ﹽ";s:4:"ـّ";s:3:"ﹾ";s:3:" ْ";s:3:"ﹿ";s:4:"ـْ";s:3:"ﺀ";s:2:"ء";s:3:"ﺁ";s:4:"آ";s:3:"ﺂ";s:4:"آ";s:3:"ﺃ";s:4:"أ";s:3:"ﺄ";s:4:"أ";s:3:"ﺅ";s:4:"ؤ";s:3:"ﺆ";s:4:"ؤ";s:3:"ﺇ";s:4:"إ";s:3:"ﺈ";s:4:"إ";s:3:"ﺉ";s:4:"ئ";s:3:"ﺊ";s:4:"ئ";s:3:"ﺋ";s:4:"ئ";s:3:"ﺌ";s:4:"ئ";s:3:"ﺍ";s:2:"ا";s:3:"ﺎ";s:2:"ا";s:3:"ﺏ";s:2:"ب";s:3:"ﺐ";s:2:"ب";s:3:"ﺑ";s:2:"ب";s:3:"ﺒ";s:2:"ب";s:3:"ﺓ";s:2:"ة";s:3:"ﺔ";s:2:"ة";s:3:"ﺕ";s:2:"ت";s:3:"ﺖ";s:2:"ت";s:3:"ﺗ";s:2:"ت";s:3:"ﺘ";s:2:"ت";s:3:"ﺙ";s:2:"ث";s:3:"ﺚ";s:2:"ث";s:3:"ﺛ";s:2:"ث";s:3:"ﺜ";s:2:"ث";s:3:"ﺝ";s:2:"ج";s:3:"ﺞ";s:2:"ج";s:3:"ﺟ";s:2:"ج";s:3:"ﺠ";s:2:"ج";s:3:"ﺡ";s:2:"ح";s:3:"ﺢ";s:2:"ح";s:3:"ﺣ";s:2:"ح";s:3:"ﺤ";s:2:"ح";s:3:"ﺥ";s:2:"خ";s:3:"ﺦ";s:2:"خ";s:3:"ﺧ";s:2:"خ";s:3:"ﺨ";s:2:"خ";s:3:"ﺩ";s:2:"د";s:3:"ﺪ";s:2:"د";s:3:"ﺫ";s:2:"ذ";s:3:"ﺬ";s:2:"ذ";s:3:"ﺭ";s:2:"ر";s:3:"ﺮ";s:2:"ر";s:3:"ﺯ";s:2:"ز";s:3:"ﺰ";s:2:"ز";s:3:"ﺱ";s:2:"س";s:3:"ﺲ";s:2:"س";s:3:"ﺳ";s:2:"س";s:3:"ﺴ";s:2:"س";s:3:"ﺵ";s:2:"ش";s:3:"ﺶ";s:2:"ش";s:3:"ﺷ";s:2:"ش";s:3:"ﺸ";s:2:"ش";s:3:"ﺹ";s:2:"ص";s:3:"ﺺ";s:2:"ص";s:3:"ﺻ";s:2:"ص";s:3:"ﺼ";s:2:"ص";s:3:"ﺽ";s:2:"ض";s:3:"ﺾ";s:2:"ض";s:3:"ﺿ";s:2:"ض";s:3:"ﻀ";s:2:"ض";s:3:"ﻁ";s:2:"ط";s:3:"ﻂ";s:2:"ط";s:3:"ﻃ";s:2:"ط";s:3:"ﻄ";s:2:"ط";s:3:"ﻅ";s:2:"ظ";s:3:"ﻆ";s:2:"ظ";s:3:"ﻇ";s:2:"ظ";s:3:"ﻈ";s:2:"ظ";s:3:"ﻉ";s:2:"ع";s:3:"ﻊ";s:2:"ع";s:3:"ﻋ";s:2:"ع";s:3:"ﻌ";s:2:"ع";s:3:"ﻍ";s:2:"غ";s:3:"ﻎ";s:2:"غ";s:3:"ﻏ";s:2:"غ";s:3:"ﻐ";s:2:"غ";s:3:"ﻑ";s:2:"ف";s:3:"ﻒ";s:2:"ف";s:3:"ﻓ";s:2:"ف";s:3:"ﻔ";s:2:"ف";s:3:"ﻕ";s:2:"ق";s:3:"ﻖ";s:2:"ق";s:3:"ﻗ";s:2:"ق";s:3:"ﻘ";s:2:"ق";s:3:"ﻙ";s:2:"ك";s:3:"ﻚ";s:2:"ك";s:3:"ﻛ";s:2:"ك";s:3:"ﻜ";s:2:"ك";s:3:"ﻝ";s:2:"ل";s:3:"ﻞ";s:2:"ل";s:3:"ﻟ";s:2:"ل";s:3:"ﻠ";s:2:"ل";s:3:"ﻡ";s:2:"م";s:3:"ﻢ";s:2:"م";s:3:"ﻣ";s:2:"م";s:3:"ﻤ";s:2:"م";s:3:"ﻥ";s:2:"ن";s:3:"ﻦ";s:2:"ن";s:3:"ﻧ";s:2:"ن";s:3:"ﻨ";s:2:"ن";s:3:"ﻩ";s:2:"ه";s:3:"ﻪ";s:2:"ه";s:3:"ﻫ";s:2:"ه";s:3:"ﻬ";s:2:"ه";s:3:"ﻭ";s:2:"و";s:3:"ﻮ";s:2:"و";s:3:"ﻯ";s:2:"ى";s:3:"ﻰ";s:2:"ى";s:3:"ﻱ";s:2:"ي";s:3:"ﻲ";s:2:"ي";s:3:"ﻳ";s:2:"ي";s:3:"ﻴ";s:2:"ي";s:3:"ﻵ";s:6:"لآ";s:3:"ﻶ";s:6:"لآ";s:3:"ﻷ";s:6:"لأ";s:3:"ﻸ";s:6:"لأ";s:3:"ﻹ";s:6:"لإ";s:3:"ﻺ";s:6:"لإ";s:3:"ﻻ";s:4:"لا";s:3:"ﻼ";s:4:"لا";s:3:"!";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"%";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"\'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"-";s:1:"-";s:3:".";s:1:".";s:3:"/";s:1:"/";s:3:"0";s:1:"0";s:3:"1";s:1:"1";s:3:"2";s:1:"2";s:3:"3";s:1:"3";s:3:"4";s:1:"4";s:3:"5";s:1:"5";s:3:"6";s:1:"6";s:3:"7";s:1:"7";s:3:"8";s:1:"8";s:3:"9";s:1:"9";s:3:":";s:1:":";s:3:";";s:1:";";s:3:"<";s:1:"<";s:3:"=";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"@";s:1:"@";s:3:"A";s:1:"A";s:3:"B";s:1:"B";s:3:"C";s:1:"C";s:3:"D";s:1:"D";s:3:"E";s:1:"E";s:3:"F";s:1:"F";s:3:"G";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"M";s:1:"M";s:3:"N";s:1:"N";s:3:"O";s:1:"O";s:3:"P";s:1:"P";s:3:"Q";s:1:"Q";s:3:"R";s:1:"R";s:3:"S";s:1:"S";s:3:"T";s:1:"T";s:3:"U";s:1:"U";s:3:"V";s:1:"V";s:3:"W";s:1:"W";s:3:"X";s:1:"X";s:3:"Y";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"[";s:1:"[";s:3:"\";s:1:"\\";s:3:"]";s:1:"]";s:3:"^";s:1:"^";s:3:"_";s:1:"_";s:3:"`";s:1:"`";s:3:"a";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"e";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"m";s:1:"m";s:3:"n";s:1:"n";s:3:"o";s:1:"o";s:3:"p";s:1:"p";s:3:"q";s:1:"q";s:3:"r";s:1:"r";s:3:"s";s:1:"s";s:3:"t";s:1:"t";s:3:"u";s:1:"u";s:3:"v";s:1:"v";s:3:"w";s:1:"w";s:3:"x";s:1:"x";s:3:"y";s:1:"y";s:3:"z";s:1:"z";s:3:"{";s:1:"{";s:3:"|";s:1:"|";s:3:"}";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"⦆";s:3:"⦆";s:3:"。";s:3:"。";s:3:"「";s:3:"「";s:3:"」";s:3:"」";s:3:"、";s:3:"、";s:3:"・";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ァ";s:3:"ァ";s:3:"ィ";s:3:"ィ";s:3:"ゥ";s:3:"ゥ";s:3:"ェ";s:3:"ェ";s:3:"ォ";s:3:"ォ";s:3:"ャ";s:3:"ャ";s:3:"ュ";s:3:"ュ";s:3:"ョ";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ー";s:3:"ー";s:3:"ア";s:3:"ア";s:3:"イ";s:3:"イ";s:3:"ウ";s:3:"ウ";s:3:"エ";s:3:"エ";s:3:"オ";s:3:"オ";s:3:"カ";s:3:"カ";s:3:"キ";s:3:"キ";s:3:"ク";s:3:"ク";s:3:"ケ";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"サ";s:3:"サ";s:3:"シ";s:3:"シ";s:3:"ス";s:3:"ス";s:3:"セ";s:3:"セ";s:3:"ソ";s:3:"ソ";s:3:"タ";s:3:"タ";s:3:"チ";s:3:"チ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ナ";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ネ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ハ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ヘ";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"マ";s:3:"マ";s:3:"ミ";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"メ";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ヤ";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ヨ";s:3:"ヨ";s:3:"ラ";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ル";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ロ";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ン";s:3:"ン";s:3:"゙";s:3:"゙";s:3:"゚";s:3:"゚";s:3:"ᅠ";s:3:"ᅠ";s:3:"ᄀ";s:3:"ᄀ";s:3:"ᄁ";s:3:"ᄁ";s:3:"ᆪ";s:3:"ᆪ";s:3:"ᄂ";s:3:"ᄂ";s:3:"ᆬ";s:3:"ᆬ";s:3:"ᆭ";s:3:"ᆭ";s:3:"ᄃ";s:3:"ᄃ";s:3:"ᄄ";s:3:"ᄄ";s:3:"ᄅ";s:3:"ᄅ";s:3:"ᆰ";s:3:"ᆰ";s:3:"ᆱ";s:3:"ᆱ";s:3:"ᆲ";s:3:"ᆲ";s:3:"ᆳ";s:3:"ᆳ";s:3:"ᆴ";s:3:"ᆴ";s:3:"ᆵ";s:3:"ᆵ";s:3:"ᄚ";s:3:"ᄚ";s:3:"ᄆ";s:3:"ᄆ";s:3:"ᄇ";s:3:"ᄇ";s:3:"ᄈ";s:3:"ᄈ";s:3:"ᄡ";s:3:"ᄡ";s:3:"ᄉ";s:3:"ᄉ";s:3:"ᄊ";s:3:"ᄊ";s:3:"ᄋ";s:3:"ᄋ";s:3:"ᄌ";s:3:"ᄌ";s:3:"ᄍ";s:3:"ᄍ";s:3:"ᄎ";s:3:"ᄎ";s:3:"ᄏ";s:3:"ᄏ";s:3:"ᄐ";s:3:"ᄐ";s:3:"ᄑ";s:3:"ᄑ";s:3:"ᄒ";s:3:"ᄒ";s:3:"ᅡ";s:3:"ᅡ";s:3:"ᅢ";s:3:"ᅢ";s:3:"ᅣ";s:3:"ᅣ";s:3:"ᅤ";s:3:"ᅤ";s:3:"ᅥ";s:3:"ᅥ";s:3:"ᅦ";s:3:"ᅦ";s:3:"ᅧ";s:3:"ᅧ";s:3:"ᅨ";s:3:"ᅨ";s:3:"ᅩ";s:3:"ᅩ";s:3:"ᅪ";s:3:"ᅪ";s:3:"ᅫ";s:3:"ᅫ";s:3:"ᅬ";s:3:"ᅬ";s:3:"ᅭ";s:3:"ᅭ";s:3:"ᅮ";s:3:"ᅮ";s:3:"ᅯ";s:3:"ᅯ";s:3:"ᅰ";s:3:"ᅰ";s:3:"ᅱ";s:3:"ᅱ";s:3:"ᅲ";s:3:"ᅲ";s:3:"ᅳ";s:3:"ᅳ";s:3:"ᅴ";s:3:"ᅴ";s:3:"ᅵ";s:3:"ᅵ";s:3:"¢";s:2:"¢";s:3:"£";s:2:"£";s:3:"¬";s:2:"¬";s:3:" ̄";s:3:" ̄";s:3:"¦";s:2:"¦";s:3:"¥";s:2:"¥";s:3:"₩";s:3:"₩";s:3:"│";s:3:"│";s:3:"←";s:3:"←";s:3:"↑";s:3:"↑";s:3:"→";s:3:"→";s:3:"↓";s:3:"↓";s:3:"■";s:3:"■";s:3:"○";s:3:"○";s:4:"𑂚";s:8:"𑂚";s:4:"𑂜";s:8:"𑂜";s:4:"𑂫";s:8:"𑂫";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"𝐀";s:1:"A";s:4:"𝐁";s:1:"B";s:4:"𝐂";s:1:"C";s:4:"𝐃";s:1:"D";s:4:"𝐄";s:1:"E";s:4:"𝐅";s:1:"F";s:4:"𝐆";s:1:"G";s:4:"𝐇";s:1:"H";s:4:"𝐈";s:1:"I";s:4:"𝐉";s:1:"J";s:4:"𝐊";s:1:"K";s:4:"𝐋";s:1:"L";s:4:"𝐌";s:1:"M";s:4:"𝐍";s:1:"N";s:4:"𝐎";s:1:"O";s:4:"𝐏";s:1:"P";s:4:"𝐐";s:1:"Q";s:4:"𝐑";s:1:"R";s:4:"𝐒";s:1:"S";s:4:"𝐓";s:1:"T";s:4:"𝐔";s:1:"U";s:4:"𝐕";s:1:"V";s:4:"𝐖";s:1:"W";s:4:"𝐗";s:1:"X";s:4:"𝐘";s:1:"Y";s:4:"𝐙";s:1:"Z";s:4:"𝐚";s:1:"a";s:4:"𝐛";s:1:"b";s:4:"𝐜";s:1:"c";s:4:"𝐝";s:1:"d";s:4:"𝐞";s:1:"e";s:4:"𝐟";s:1:"f";s:4:"𝐠";s:1:"g";s:4:"𝐡";s:1:"h";s:4:"𝐢";s:1:"i";s:4:"𝐣";s:1:"j";s:4:"𝐤";s:1:"k";s:4:"𝐥";s:1:"l";s:4:"𝐦";s:1:"m";s:4:"𝐧";s:1:"n";s:4:"𝐨";s:1:"o";s:4:"𝐩";s:1:"p";s:4:"𝐪";s:1:"q";s:4:"𝐫";s:1:"r";s:4:"𝐬";s:1:"s";s:4:"𝐭";s:1:"t";s:4:"𝐮";s:1:"u";s:4:"𝐯";s:1:"v";s:4:"𝐰";s:1:"w";s:4:"𝐱";s:1:"x";s:4:"𝐲";s:1:"y";s:4:"𝐳";s:1:"z";s:4:"𝐴";s:1:"A";s:4:"𝐵";s:1:"B";s:4:"𝐶";s:1:"C";s:4:"𝐷";s:1:"D";s:4:"𝐸";s:1:"E";s:4:"𝐹";s:1:"F";s:4:"𝐺";s:1:"G";s:4:"𝐻";s:1:"H";s:4:"𝐼";s:1:"I";s:4:"𝐽";s:1:"J";s:4:"𝐾";s:1:"K";s:4:"𝐿";s:1:"L";s:4:"𝑀";s:1:"M";s:4:"𝑁";s:1:"N";s:4:"𝑂";s:1:"O";s:4:"𝑃";s:1:"P";s:4:"𝑄";s:1:"Q";s:4:"𝑅";s:1:"R";s:4:"𝑆";s:1:"S";s:4:"𝑇";s:1:"T";s:4:"𝑈";s:1:"U";s:4:"𝑉";s:1:"V";s:4:"𝑊";s:1:"W";s:4:"𝑋";s:1:"X";s:4:"𝑌";s:1:"Y";s:4:"𝑍";s:1:"Z";s:4:"𝑎";s:1:"a";s:4:"𝑏";s:1:"b";s:4:"𝑐";s:1:"c";s:4:"𝑑";s:1:"d";s:4:"𝑒";s:1:"e";s:4:"𝑓";s:1:"f";s:4:"𝑔";s:1:"g";s:4:"𝑖";s:1:"i";s:4:"𝑗";s:1:"j";s:4:"𝑘";s:1:"k";s:4:"𝑙";s:1:"l";s:4:"𝑚";s:1:"m";s:4:"𝑛";s:1:"n";s:4:"𝑜";s:1:"o";s:4:"𝑝";s:1:"p";s:4:"𝑞";s:1:"q";s:4:"𝑟";s:1:"r";s:4:"𝑠";s:1:"s";s:4:"𝑡";s:1:"t";s:4:"𝑢";s:1:"u";s:4:"𝑣";s:1:"v";s:4:"𝑤";s:1:"w";s:4:"𝑥";s:1:"x";s:4:"𝑦";s:1:"y";s:4:"𝑧";s:1:"z";s:4:"𝑨";s:1:"A";s:4:"𝑩";s:1:"B";s:4:"𝑪";s:1:"C";s:4:"𝑫";s:1:"D";s:4:"𝑬";s:1:"E";s:4:"𝑭";s:1:"F";s:4:"𝑮";s:1:"G";s:4:"𝑯";s:1:"H";s:4:"𝑰";s:1:"I";s:4:"𝑱";s:1:"J";s:4:"𝑲";s:1:"K";s:4:"𝑳";s:1:"L";s:4:"𝑴";s:1:"M";s:4:"𝑵";s:1:"N";s:4:"𝑶";s:1:"O";s:4:"𝑷";s:1:"P";s:4:"𝑸";s:1:"Q";s:4:"𝑹";s:1:"R";s:4:"𝑺";s:1:"S";s:4:"𝑻";s:1:"T";s:4:"𝑼";s:1:"U";s:4:"𝑽";s:1:"V";s:4:"𝑾";s:1:"W";s:4:"𝑿";s:1:"X";s:4:"𝒀";s:1:"Y";s:4:"𝒁";s:1:"Z";s:4:"𝒂";s:1:"a";s:4:"𝒃";s:1:"b";s:4:"𝒄";s:1:"c";s:4:"𝒅";s:1:"d";s:4:"𝒆";s:1:"e";s:4:"𝒇";s:1:"f";s:4:"𝒈";s:1:"g";s:4:"𝒉";s:1:"h";s:4:"𝒊";s:1:"i";s:4:"𝒋";s:1:"j";s:4:"𝒌";s:1:"k";s:4:"𝒍";s:1:"l";s:4:"𝒎";s:1:"m";s:4:"𝒏";s:1:"n";s:4:"𝒐";s:1:"o";s:4:"𝒑";s:1:"p";s:4:"𝒒";s:1:"q";s:4:"𝒓";s:1:"r";s:4:"𝒔";s:1:"s";s:4:"𝒕";s:1:"t";s:4:"𝒖";s:1:"u";s:4:"𝒗";s:1:"v";s:4:"𝒘";s:1:"w";s:4:"𝒙";s:1:"x";s:4:"𝒚";s:1:"y";s:4:"𝒛";s:1:"z";s:4:"𝒜";s:1:"A";s:4:"𝒞";s:1:"C";s:4:"𝒟";s:1:"D";s:4:"𝒢";s:1:"G";s:4:"𝒥";s:1:"J";s:4:"𝒦";s:1:"K";s:4:"𝒩";s:1:"N";s:4:"𝒪";s:1:"O";s:4:"𝒫";s:1:"P";s:4:"𝒬";s:1:"Q";s:4:"𝒮";s:1:"S";s:4:"𝒯";s:1:"T";s:4:"𝒰";s:1:"U";s:4:"𝒱";s:1:"V";s:4:"𝒲";s:1:"W";s:4:"𝒳";s:1:"X";s:4:"𝒴";s:1:"Y";s:4:"𝒵";s:1:"Z";s:4:"𝒶";s:1:"a";s:4:"𝒷";s:1:"b";s:4:"𝒸";s:1:"c";s:4:"𝒹";s:1:"d";s:4:"𝒻";s:1:"f";s:4:"𝒽";s:1:"h";s:4:"𝒾";s:1:"i";s:4:"𝒿";s:1:"j";s:4:"𝓀";s:1:"k";s:4:"𝓁";s:1:"l";s:4:"𝓂";s:1:"m";s:4:"𝓃";s:1:"n";s:4:"𝓅";s:1:"p";s:4:"𝓆";s:1:"q";s:4:"𝓇";s:1:"r";s:4:"𝓈";s:1:"s";s:4:"𝓉";s:1:"t";s:4:"𝓊";s:1:"u";s:4:"𝓋";s:1:"v";s:4:"𝓌";s:1:"w";s:4:"𝓍";s:1:"x";s:4:"𝓎";s:1:"y";s:4:"𝓏";s:1:"z";s:4:"𝓐";s:1:"A";s:4:"𝓑";s:1:"B";s:4:"𝓒";s:1:"C";s:4:"𝓓";s:1:"D";s:4:"𝓔";s:1:"E";s:4:"𝓕";s:1:"F";s:4:"𝓖";s:1:"G";s:4:"𝓗";s:1:"H";s:4:"𝓘";s:1:"I";s:4:"𝓙";s:1:"J";s:4:"𝓚";s:1:"K";s:4:"𝓛";s:1:"L";s:4:"𝓜";s:1:"M";s:4:"𝓝";s:1:"N";s:4:"𝓞";s:1:"O";s:4:"𝓟";s:1:"P";s:4:"𝓠";s:1:"Q";s:4:"𝓡";s:1:"R";s:4:"𝓢";s:1:"S";s:4:"𝓣";s:1:"T";s:4:"𝓤";s:1:"U";s:4:"𝓥";s:1:"V";s:4:"𝓦";s:1:"W";s:4:"𝓧";s:1:"X";s:4:"𝓨";s:1:"Y";s:4:"𝓩";s:1:"Z";s:4:"𝓪";s:1:"a";s:4:"𝓫";s:1:"b";s:4:"𝓬";s:1:"c";s:4:"𝓭";s:1:"d";s:4:"𝓮";s:1:"e";s:4:"𝓯";s:1:"f";s:4:"𝓰";s:1:"g";s:4:"𝓱";s:1:"h";s:4:"𝓲";s:1:"i";s:4:"𝓳";s:1:"j";s:4:"𝓴";s:1:"k";s:4:"𝓵";s:1:"l";s:4:"𝓶";s:1:"m";s:4:"𝓷";s:1:"n";s:4:"𝓸";s:1:"o";s:4:"𝓹";s:1:"p";s:4:"𝓺";s:1:"q";s:4:"𝓻";s:1:"r";s:4:"𝓼";s:1:"s";s:4:"𝓽";s:1:"t";s:4:"𝓾";s:1:"u";s:4:"𝓿";s:1:"v";s:4:"𝔀";s:1:"w";s:4:"𝔁";s:1:"x";s:4:"𝔂";s:1:"y";s:4:"𝔃";s:1:"z";s:4:"𝔄";s:1:"A";s:4:"𝔅";s:1:"B";s:4:"𝔇";s:1:"D";s:4:"𝔈";s:1:"E";s:4:"𝔉";s:1:"F";s:4:"𝔊";s:1:"G";s:4:"𝔍";s:1:"J";s:4:"𝔎";s:1:"K";s:4:"𝔏";s:1:"L";s:4:"𝔐";s:1:"M";s:4:"𝔑";s:1:"N";s:4:"𝔒";s:1:"O";s:4:"𝔓";s:1:"P";s:4:"𝔔";s:1:"Q";s:4:"𝔖";s:1:"S";s:4:"𝔗";s:1:"T";s:4:"𝔘";s:1:"U";s:4:"𝔙";s:1:"V";s:4:"𝔚";s:1:"W";s:4:"𝔛";s:1:"X";s:4:"𝔜";s:1:"Y";s:4:"𝔞";s:1:"a";s:4:"𝔟";s:1:"b";s:4:"𝔠";s:1:"c";s:4:"𝔡";s:1:"d";s:4:"𝔢";s:1:"e";s:4:"𝔣";s:1:"f";s:4:"𝔤";s:1:"g";s:4:"𝔥";s:1:"h";s:4:"𝔦";s:1:"i";s:4:"𝔧";s:1:"j";s:4:"𝔨";s:1:"k";s:4:"𝔩";s:1:"l";s:4:"𝔪";s:1:"m";s:4:"𝔫";s:1:"n";s:4:"𝔬";s:1:"o";s:4:"𝔭";s:1:"p";s:4:"𝔮";s:1:"q";s:4:"𝔯";s:1:"r";s:4:"𝔰";s:1:"s";s:4:"𝔱";s:1:"t";s:4:"𝔲";s:1:"u";s:4:"𝔳";s:1:"v";s:4:"𝔴";s:1:"w";s:4:"𝔵";s:1:"x";s:4:"𝔶";s:1:"y";s:4:"𝔷";s:1:"z";s:4:"𝔸";s:1:"A";s:4:"𝔹";s:1:"B";s:4:"𝔻";s:1:"D";s:4:"𝔼";s:1:"E";s:4:"𝔽";s:1:"F";s:4:"𝔾";s:1:"G";s:4:"𝕀";s:1:"I";s:4:"𝕁";s:1:"J";s:4:"𝕂";s:1:"K";s:4:"𝕃";s:1:"L";s:4:"𝕄";s:1:"M";s:4:"𝕆";s:1:"O";s:4:"𝕊";s:1:"S";s:4:"𝕋";s:1:"T";s:4:"𝕌";s:1:"U";s:4:"𝕍";s:1:"V";s:4:"𝕎";s:1:"W";s:4:"𝕏";s:1:"X";s:4:"𝕐";s:1:"Y";s:4:"𝕒";s:1:"a";s:4:"𝕓";s:1:"b";s:4:"𝕔";s:1:"c";s:4:"𝕕";s:1:"d";s:4:"𝕖";s:1:"e";s:4:"𝕗";s:1:"f";s:4:"𝕘";s:1:"g";s:4:"𝕙";s:1:"h";s:4:"𝕚";s:1:"i";s:4:"𝕛";s:1:"j";s:4:"𝕜";s:1:"k";s:4:"𝕝";s:1:"l";s:4:"𝕞";s:1:"m";s:4:"𝕟";s:1:"n";s:4:"𝕠";s:1:"o";s:4:"𝕡";s:1:"p";s:4:"𝕢";s:1:"q";s:4:"𝕣";s:1:"r";s:4:"𝕤";s:1:"s";s:4:"𝕥";s:1:"t";s:4:"𝕦";s:1:"u";s:4:"𝕧";s:1:"v";s:4:"𝕨";s:1:"w";s:4:"𝕩";s:1:"x";s:4:"𝕪";s:1:"y";s:4:"𝕫";s:1:"z";s:4:"𝕬";s:1:"A";s:4:"𝕭";s:1:"B";s:4:"𝕮";s:1:"C";s:4:"𝕯";s:1:"D";s:4:"𝕰";s:1:"E";s:4:"𝕱";s:1:"F";s:4:"𝕲";s:1:"G";s:4:"𝕳";s:1:"H";s:4:"𝕴";s:1:"I";s:4:"𝕵";s:1:"J";s:4:"𝕶";s:1:"K";s:4:"𝕷";s:1:"L";s:4:"𝕸";s:1:"M";s:4:"𝕹";s:1:"N";s:4:"𝕺";s:1:"O";s:4:"𝕻";s:1:"P";s:4:"𝕼";s:1:"Q";s:4:"𝕽";s:1:"R";s:4:"𝕾";s:1:"S";s:4:"𝕿";s:1:"T";s:4:"𝖀";s:1:"U";s:4:"𝖁";s:1:"V";s:4:"𝖂";s:1:"W";s:4:"𝖃";s:1:"X";s:4:"𝖄";s:1:"Y";s:4:"𝖅";s:1:"Z";s:4:"𝖆";s:1:"a";s:4:"𝖇";s:1:"b";s:4:"𝖈";s:1:"c";s:4:"𝖉";s:1:"d";s:4:"𝖊";s:1:"e";s:4:"𝖋";s:1:"f";s:4:"𝖌";s:1:"g";s:4:"𝖍";s:1:"h";s:4:"𝖎";s:1:"i";s:4:"𝖏";s:1:"j";s:4:"𝖐";s:1:"k";s:4:"𝖑";s:1:"l";s:4:"𝖒";s:1:"m";s:4:"𝖓";s:1:"n";s:4:"𝖔";s:1:"o";s:4:"𝖕";s:1:"p";s:4:"𝖖";s:1:"q";s:4:"𝖗";s:1:"r";s:4:"𝖘";s:1:"s";s:4:"𝖙";s:1:"t";s:4:"𝖚";s:1:"u";s:4:"𝖛";s:1:"v";s:4:"𝖜";s:1:"w";s:4:"𝖝";s:1:"x";s:4:"𝖞";s:1:"y";s:4:"𝖟";s:1:"z";s:4:"𝖠";s:1:"A";s:4:"𝖡";s:1:"B";s:4:"𝖢";s:1:"C";s:4:"𝖣";s:1:"D";s:4:"𝖤";s:1:"E";s:4:"𝖥";s:1:"F";s:4:"𝖦";s:1:"G";s:4:"𝖧";s:1:"H";s:4:"𝖨";s:1:"I";s:4:"𝖩";s:1:"J";s:4:"𝖪";s:1:"K";s:4:"𝖫";s:1:"L";s:4:"𝖬";s:1:"M";s:4:"𝖭";s:1:"N";s:4:"𝖮";s:1:"O";s:4:"𝖯";s:1:"P";s:4:"𝖰";s:1:"Q";s:4:"𝖱";s:1:"R";s:4:"𝖲";s:1:"S";s:4:"𝖳";s:1:"T";s:4:"𝖴";s:1:"U";s:4:"𝖵";s:1:"V";s:4:"𝖶";s:1:"W";s:4:"𝖷";s:1:"X";s:4:"𝖸";s:1:"Y";s:4:"𝖹";s:1:"Z";s:4:"𝖺";s:1:"a";s:4:"𝖻";s:1:"b";s:4:"𝖼";s:1:"c";s:4:"𝖽";s:1:"d";s:4:"𝖾";s:1:"e";s:4:"𝖿";s:1:"f";s:4:"𝗀";s:1:"g";s:4:"𝗁";s:1:"h";s:4:"𝗂";s:1:"i";s:4:"𝗃";s:1:"j";s:4:"𝗄";s:1:"k";s:4:"𝗅";s:1:"l";s:4:"𝗆";s:1:"m";s:4:"𝗇";s:1:"n";s:4:"𝗈";s:1:"o";s:4:"𝗉";s:1:"p";s:4:"𝗊";s:1:"q";s:4:"𝗋";s:1:"r";s:4:"𝗌";s:1:"s";s:4:"𝗍";s:1:"t";s:4:"𝗎";s:1:"u";s:4:"𝗏";s:1:"v";s:4:"𝗐";s:1:"w";s:4:"𝗑";s:1:"x";s:4:"𝗒";s:1:"y";s:4:"𝗓";s:1:"z";s:4:"𝗔";s:1:"A";s:4:"𝗕";s:1:"B";s:4:"𝗖";s:1:"C";s:4:"𝗗";s:1:"D";s:4:"𝗘";s:1:"E";s:4:"𝗙";s:1:"F";s:4:"𝗚";s:1:"G";s:4:"𝗛";s:1:"H";s:4:"𝗜";s:1:"I";s:4:"𝗝";s:1:"J";s:4:"𝗞";s:1:"K";s:4:"𝗟";s:1:"L";s:4:"𝗠";s:1:"M";s:4:"𝗡";s:1:"N";s:4:"𝗢";s:1:"O";s:4:"𝗣";s:1:"P";s:4:"𝗤";s:1:"Q";s:4:"𝗥";s:1:"R";s:4:"𝗦";s:1:"S";s:4:"𝗧";s:1:"T";s:4:"𝗨";s:1:"U";s:4:"𝗩";s:1:"V";s:4:"𝗪";s:1:"W";s:4:"𝗫";s:1:"X";s:4:"𝗬";s:1:"Y";s:4:"𝗭";s:1:"Z";s:4:"𝗮";s:1:"a";s:4:"𝗯";s:1:"b";s:4:"𝗰";s:1:"c";s:4:"𝗱";s:1:"d";s:4:"𝗲";s:1:"e";s:4:"𝗳";s:1:"f";s:4:"𝗴";s:1:"g";s:4:"𝗵";s:1:"h";s:4:"𝗶";s:1:"i";s:4:"𝗷";s:1:"j";s:4:"𝗸";s:1:"k";s:4:"𝗹";s:1:"l";s:4:"𝗺";s:1:"m";s:4:"𝗻";s:1:"n";s:4:"𝗼";s:1:"o";s:4:"𝗽";s:1:"p";s:4:"𝗾";s:1:"q";s:4:"𝗿";s:1:"r";s:4:"𝘀";s:1:"s";s:4:"𝘁";s:1:"t";s:4:"𝘂";s:1:"u";s:4:"𝘃";s:1:"v";s:4:"𝘄";s:1:"w";s:4:"𝘅";s:1:"x";s:4:"𝘆";s:1:"y";s:4:"𝘇";s:1:"z";s:4:"𝘈";s:1:"A";s:4:"𝘉";s:1:"B";s:4:"𝘊";s:1:"C";s:4:"𝘋";s:1:"D";s:4:"𝘌";s:1:"E";s:4:"𝘍";s:1:"F";s:4:"𝘎";s:1:"G";s:4:"𝘏";s:1:"H";s:4:"𝘐";s:1:"I";s:4:"𝘑";s:1:"J";s:4:"𝘒";s:1:"K";s:4:"𝘓";s:1:"L";s:4:"𝘔";s:1:"M";s:4:"𝘕";s:1:"N";s:4:"𝘖";s:1:"O";s:4:"𝘗";s:1:"P";s:4:"𝘘";s:1:"Q";s:4:"𝘙";s:1:"R";s:4:"𝘚";s:1:"S";s:4:"𝘛";s:1:"T";s:4:"𝘜";s:1:"U";s:4:"𝘝";s:1:"V";s:4:"𝘞";s:1:"W";s:4:"𝘟";s:1:"X";s:4:"𝘠";s:1:"Y";s:4:"𝘡";s:1:"Z";s:4:"𝘢";s:1:"a";s:4:"𝘣";s:1:"b";s:4:"𝘤";s:1:"c";s:4:"𝘥";s:1:"d";s:4:"𝘦";s:1:"e";s:4:"𝘧";s:1:"f";s:4:"𝘨";s:1:"g";s:4:"𝘩";s:1:"h";s:4:"𝘪";s:1:"i";s:4:"𝘫";s:1:"j";s:4:"𝘬";s:1:"k";s:4:"𝘭";s:1:"l";s:4:"𝘮";s:1:"m";s:4:"𝘯";s:1:"n";s:4:"𝘰";s:1:"o";s:4:"𝘱";s:1:"p";s:4:"𝘲";s:1:"q";s:4:"𝘳";s:1:"r";s:4:"𝘴";s:1:"s";s:4:"𝘵";s:1:"t";s:4:"𝘶";s:1:"u";s:4:"𝘷";s:1:"v";s:4:"𝘸";s:1:"w";s:4:"𝘹";s:1:"x";s:4:"𝘺";s:1:"y";s:4:"𝘻";s:1:"z";s:4:"𝘼";s:1:"A";s:4:"𝘽";s:1:"B";s:4:"𝘾";s:1:"C";s:4:"𝘿";s:1:"D";s:4:"𝙀";s:1:"E";s:4:"𝙁";s:1:"F";s:4:"𝙂";s:1:"G";s:4:"𝙃";s:1:"H";s:4:"𝙄";s:1:"I";s:4:"𝙅";s:1:"J";s:4:"𝙆";s:1:"K";s:4:"𝙇";s:1:"L";s:4:"𝙈";s:1:"M";s:4:"𝙉";s:1:"N";s:4:"𝙊";s:1:"O";s:4:"𝙋";s:1:"P";s:4:"𝙌";s:1:"Q";s:4:"𝙍";s:1:"R";s:4:"𝙎";s:1:"S";s:4:"𝙏";s:1:"T";s:4:"𝙐";s:1:"U";s:4:"𝙑";s:1:"V";s:4:"𝙒";s:1:"W";s:4:"𝙓";s:1:"X";s:4:"𝙔";s:1:"Y";s:4:"𝙕";s:1:"Z";s:4:"𝙖";s:1:"a";s:4:"𝙗";s:1:"b";s:4:"𝙘";s:1:"c";s:4:"𝙙";s:1:"d";s:4:"𝙚";s:1:"e";s:4:"𝙛";s:1:"f";s:4:"𝙜";s:1:"g";s:4:"𝙝";s:1:"h";s:4:"𝙞";s:1:"i";s:4:"𝙟";s:1:"j";s:4:"𝙠";s:1:"k";s:4:"𝙡";s:1:"l";s:4:"𝙢";s:1:"m";s:4:"𝙣";s:1:"n";s:4:"𝙤";s:1:"o";s:4:"𝙥";s:1:"p";s:4:"𝙦";s:1:"q";s:4:"𝙧";s:1:"r";s:4:"𝙨";s:1:"s";s:4:"𝙩";s:1:"t";s:4:"𝙪";s:1:"u";s:4:"𝙫";s:1:"v";s:4:"𝙬";s:1:"w";s:4:"𝙭";s:1:"x";s:4:"𝙮";s:1:"y";s:4:"𝙯";s:1:"z";s:4:"𝙰";s:1:"A";s:4:"𝙱";s:1:"B";s:4:"𝙲";s:1:"C";s:4:"𝙳";s:1:"D";s:4:"𝙴";s:1:"E";s:4:"𝙵";s:1:"F";s:4:"𝙶";s:1:"G";s:4:"𝙷";s:1:"H";s:4:"𝙸";s:1:"I";s:4:"𝙹";s:1:"J";s:4:"𝙺";s:1:"K";s:4:"𝙻";s:1:"L";s:4:"𝙼";s:1:"M";s:4:"𝙽";s:1:"N";s:4:"𝙾";s:1:"O";s:4:"𝙿";s:1:"P";s:4:"𝚀";s:1:"Q";s:4:"𝚁";s:1:"R";s:4:"𝚂";s:1:"S";s:4:"𝚃";s:1:"T";s:4:"𝚄";s:1:"U";s:4:"𝚅";s:1:"V";s:4:"𝚆";s:1:"W";s:4:"𝚇";s:1:"X";s:4:"𝚈";s:1:"Y";s:4:"𝚉";s:1:"Z";s:4:"𝚊";s:1:"a";s:4:"𝚋";s:1:"b";s:4:"𝚌";s:1:"c";s:4:"𝚍";s:1:"d";s:4:"𝚎";s:1:"e";s:4:"𝚏";s:1:"f";s:4:"𝚐";s:1:"g";s:4:"𝚑";s:1:"h";s:4:"𝚒";s:1:"i";s:4:"𝚓";s:1:"j";s:4:"𝚔";s:1:"k";s:4:"𝚕";s:1:"l";s:4:"𝚖";s:1:"m";s:4:"𝚗";s:1:"n";s:4:"𝚘";s:1:"o";s:4:"𝚙";s:1:"p";s:4:"𝚚";s:1:"q";s:4:"𝚛";s:1:"r";s:4:"𝚜";s:1:"s";s:4:"𝚝";s:1:"t";s:4:"𝚞";s:1:"u";s:4:"𝚟";s:1:"v";s:4:"𝚠";s:1:"w";s:4:"𝚡";s:1:"x";s:4:"𝚢";s:1:"y";s:4:"𝚣";s:1:"z";s:4:"𝚤";s:2:"ı";s:4:"𝚥";s:2:"ȷ";s:4:"𝚨";s:2:"Α";s:4:"𝚩";s:2:"Β";s:4:"𝚪";s:2:"Γ";s:4:"𝚫";s:2:"Δ";s:4:"𝚬";s:2:"Ε";s:4:"𝚭";s:2:"Ζ";s:4:"𝚮";s:2:"Η";s:4:"𝚯";s:2:"Θ";s:4:"𝚰";s:2:"Ι";s:4:"𝚱";s:2:"Κ";s:4:"𝚲";s:2:"Λ";s:4:"𝚳";s:2:"Μ";s:4:"𝚴";s:2:"Ν";s:4:"𝚵";s:2:"Ξ";s:4:"𝚶";s:2:"Ο";s:4:"𝚷";s:2:"Π";s:4:"𝚸";s:2:"Ρ";s:4:"𝚹";s:2:"Θ";s:4:"𝚺";s:2:"Σ";s:4:"𝚻";s:2:"Τ";s:4:"𝚼";s:2:"Υ";s:4:"𝚽";s:2:"Φ";s:4:"𝚾";s:2:"Χ";s:4:"𝚿";s:2:"Ψ";s:4:"𝛀";s:2:"Ω";s:4:"𝛁";s:3:"∇";s:4:"𝛂";s:2:"α";s:4:"𝛃";s:2:"β";s:4:"𝛄";s:2:"γ";s:4:"𝛅";s:2:"δ";s:4:"𝛆";s:2:"ε";s:4:"𝛇";s:2:"ζ";s:4:"𝛈";s:2:"η";s:4:"𝛉";s:2:"θ";s:4:"𝛊";s:2:"ι";s:4:"𝛋";s:2:"κ";s:4:"𝛌";s:2:"λ";s:4:"𝛍";s:2:"μ";s:4:"𝛎";s:2:"ν";s:4:"𝛏";s:2:"ξ";s:4:"𝛐";s:2:"ο";s:4:"𝛑";s:2:"π";s:4:"𝛒";s:2:"ρ";s:4:"𝛓";s:2:"ς";s:4:"𝛔";s:2:"σ";s:4:"𝛕";s:2:"τ";s:4:"𝛖";s:2:"υ";s:4:"𝛗";s:2:"φ";s:4:"𝛘";s:2:"χ";s:4:"𝛙";s:2:"ψ";s:4:"𝛚";s:2:"ω";s:4:"𝛛";s:3:"∂";s:4:"𝛜";s:2:"ε";s:4:"𝛝";s:2:"θ";s:4:"𝛞";s:2:"κ";s:4:"𝛟";s:2:"φ";s:4:"𝛠";s:2:"ρ";s:4:"𝛡";s:2:"π";s:4:"𝛢";s:2:"Α";s:4:"𝛣";s:2:"Β";s:4:"𝛤";s:2:"Γ";s:4:"𝛥";s:2:"Δ";s:4:"𝛦";s:2:"Ε";s:4:"𝛧";s:2:"Ζ";s:4:"𝛨";s:2:"Η";s:4:"𝛩";s:2:"Θ";s:4:"𝛪";s:2:"Ι";s:4:"𝛫";s:2:"Κ";s:4:"𝛬";s:2:"Λ";s:4:"𝛭";s:2:"Μ";s:4:"𝛮";s:2:"Ν";s:4:"𝛯";s:2:"Ξ";s:4:"𝛰";s:2:"Ο";s:4:"𝛱";s:2:"Π";s:4:"𝛲";s:2:"Ρ";s:4:"𝛳";s:2:"Θ";s:4:"𝛴";s:2:"Σ";s:4:"𝛵";s:2:"Τ";s:4:"𝛶";s:2:"Υ";s:4:"𝛷";s:2:"Φ";s:4:"𝛸";s:2:"Χ";s:4:"𝛹";s:2:"Ψ";s:4:"𝛺";s:2:"Ω";s:4:"𝛻";s:3:"∇";s:4:"𝛼";s:2:"α";s:4:"𝛽";s:2:"β";s:4:"𝛾";s:2:"γ";s:4:"𝛿";s:2:"δ";s:4:"𝜀";s:2:"ε";s:4:"𝜁";s:2:"ζ";s:4:"𝜂";s:2:"η";s:4:"𝜃";s:2:"θ";s:4:"𝜄";s:2:"ι";s:4:"𝜅";s:2:"κ";s:4:"𝜆";s:2:"λ";s:4:"𝜇";s:2:"μ";s:4:"𝜈";s:2:"ν";s:4:"𝜉";s:2:"ξ";s:4:"𝜊";s:2:"ο";s:4:"𝜋";s:2:"π";s:4:"𝜌";s:2:"ρ";s:4:"𝜍";s:2:"ς";s:4:"𝜎";s:2:"σ";s:4:"𝜏";s:2:"τ";s:4:"𝜐";s:2:"υ";s:4:"𝜑";s:2:"φ";s:4:"𝜒";s:2:"χ";s:4:"𝜓";s:2:"ψ";s:4:"𝜔";s:2:"ω";s:4:"𝜕";s:3:"∂";s:4:"𝜖";s:2:"ε";s:4:"𝜗";s:2:"θ";s:4:"𝜘";s:2:"κ";s:4:"𝜙";s:2:"φ";s:4:"𝜚";s:2:"ρ";s:4:"𝜛";s:2:"π";s:4:"𝜜";s:2:"Α";s:4:"𝜝";s:2:"Β";s:4:"𝜞";s:2:"Γ";s:4:"𝜟";s:2:"Δ";s:4:"𝜠";s:2:"Ε";s:4:"𝜡";s:2:"Ζ";s:4:"𝜢";s:2:"Η";s:4:"𝜣";s:2:"Θ";s:4:"𝜤";s:2:"Ι";s:4:"𝜥";s:2:"Κ";s:4:"𝜦";s:2:"Λ";s:4:"𝜧";s:2:"Μ";s:4:"𝜨";s:2:"Ν";s:4:"𝜩";s:2:"Ξ";s:4:"𝜪";s:2:"Ο";s:4:"𝜫";s:2:"Π";s:4:"𝜬";s:2:"Ρ";s:4:"𝜭";s:2:"Θ";s:4:"𝜮";s:2:"Σ";s:4:"𝜯";s:2:"Τ";s:4:"𝜰";s:2:"Υ";s:4:"𝜱";s:2:"Φ";s:4:"𝜲";s:2:"Χ";s:4:"𝜳";s:2:"Ψ";s:4:"𝜴";s:2:"Ω";s:4:"𝜵";s:3:"∇";s:4:"𝜶";s:2:"α";s:4:"𝜷";s:2:"β";s:4:"𝜸";s:2:"γ";s:4:"𝜹";s:2:"δ";s:4:"𝜺";s:2:"ε";s:4:"𝜻";s:2:"ζ";s:4:"𝜼";s:2:"η";s:4:"𝜽";s:2:"θ";s:4:"𝜾";s:2:"ι";s:4:"𝜿";s:2:"κ";s:4:"𝝀";s:2:"λ";s:4:"𝝁";s:2:"μ";s:4:"𝝂";s:2:"ν";s:4:"𝝃";s:2:"ξ";s:4:"𝝄";s:2:"ο";s:4:"𝝅";s:2:"π";s:4:"𝝆";s:2:"ρ";s:4:"𝝇";s:2:"ς";s:4:"𝝈";s:2:"σ";s:4:"𝝉";s:2:"τ";s:4:"𝝊";s:2:"υ";s:4:"𝝋";s:2:"φ";s:4:"𝝌";s:2:"χ";s:4:"𝝍";s:2:"ψ";s:4:"𝝎";s:2:"ω";s:4:"𝝏";s:3:"∂";s:4:"𝝐";s:2:"ε";s:4:"𝝑";s:2:"θ";s:4:"𝝒";s:2:"κ";s:4:"𝝓";s:2:"φ";s:4:"𝝔";s:2:"ρ";s:4:"𝝕";s:2:"π";s:4:"𝝖";s:2:"Α";s:4:"𝝗";s:2:"Β";s:4:"𝝘";s:2:"Γ";s:4:"𝝙";s:2:"Δ";s:4:"𝝚";s:2:"Ε";s:4:"𝝛";s:2:"Ζ";s:4:"𝝜";s:2:"Η";s:4:"𝝝";s:2:"Θ";s:4:"𝝞";s:2:"Ι";s:4:"𝝟";s:2:"Κ";s:4:"𝝠";s:2:"Λ";s:4:"𝝡";s:2:"Μ";s:4:"𝝢";s:2:"Ν";s:4:"𝝣";s:2:"Ξ";s:4:"𝝤";s:2:"Ο";s:4:"𝝥";s:2:"Π";s:4:"𝝦";s:2:"Ρ";s:4:"𝝧";s:2:"Θ";s:4:"𝝨";s:2:"Σ";s:4:"𝝩";s:2:"Τ";s:4:"𝝪";s:2:"Υ";s:4:"𝝫";s:2:"Φ";s:4:"𝝬";s:2:"Χ";s:4:"𝝭";s:2:"Ψ";s:4:"𝝮";s:2:"Ω";s:4:"𝝯";s:3:"∇";s:4:"𝝰";s:2:"α";s:4:"𝝱";s:2:"β";s:4:"𝝲";s:2:"γ";s:4:"𝝳";s:2:"δ";s:4:"𝝴";s:2:"ε";s:4:"𝝵";s:2:"ζ";s:4:"𝝶";s:2:"η";s:4:"𝝷";s:2:"θ";s:4:"𝝸";s:2:"ι";s:4:"𝝹";s:2:"κ";s:4:"𝝺";s:2:"λ";s:4:"𝝻";s:2:"μ";s:4:"𝝼";s:2:"ν";s:4:"𝝽";s:2:"ξ";s:4:"𝝾";s:2:"ο";s:4:"𝝿";s:2:"π";s:4:"𝞀";s:2:"ρ";s:4:"𝞁";s:2:"ς";s:4:"𝞂";s:2:"σ";s:4:"𝞃";s:2:"τ";s:4:"𝞄";s:2:"υ";s:4:"𝞅";s:2:"φ";s:4:"𝞆";s:2:"χ";s:4:"𝞇";s:2:"ψ";s:4:"𝞈";s:2:"ω";s:4:"𝞉";s:3:"∂";s:4:"𝞊";s:2:"ε";s:4:"𝞋";s:2:"θ";s:4:"𝞌";s:2:"κ";s:4:"𝞍";s:2:"φ";s:4:"𝞎";s:2:"ρ";s:4:"𝞏";s:2:"π";s:4:"𝞐";s:2:"Α";s:4:"𝞑";s:2:"Β";s:4:"𝞒";s:2:"Γ";s:4:"𝞓";s:2:"Δ";s:4:"𝞔";s:2:"Ε";s:4:"𝞕";s:2:"Ζ";s:4:"𝞖";s:2:"Η";s:4:"𝞗";s:2:"Θ";s:4:"𝞘";s:2:"Ι";s:4:"𝞙";s:2:"Κ";s:4:"𝞚";s:2:"Λ";s:4:"𝞛";s:2:"Μ";s:4:"𝞜";s:2:"Ν";s:4:"𝞝";s:2:"Ξ";s:4:"𝞞";s:2:"Ο";s:4:"𝞟";s:2:"Π";s:4:"𝞠";s:2:"Ρ";s:4:"𝞡";s:2:"Θ";s:4:"𝞢";s:2:"Σ";s:4:"𝞣";s:2:"Τ";s:4:"𝞤";s:2:"Υ";s:4:"𝞥";s:2:"Φ";s:4:"𝞦";s:2:"Χ";s:4:"𝞧";s:2:"Ψ";s:4:"𝞨";s:2:"Ω";s:4:"𝞩";s:3:"∇";s:4:"𝞪";s:2:"α";s:4:"𝞫";s:2:"β";s:4:"𝞬";s:2:"γ";s:4:"𝞭";s:2:"δ";s:4:"𝞮";s:2:"ε";s:4:"𝞯";s:2:"ζ";s:4:"𝞰";s:2:"η";s:4:"𝞱";s:2:"θ";s:4:"𝞲";s:2:"ι";s:4:"𝞳";s:2:"κ";s:4:"𝞴";s:2:"λ";s:4:"𝞵";s:2:"μ";s:4:"𝞶";s:2:"ν";s:4:"𝞷";s:2:"ξ";s:4:"𝞸";s:2:"ο";s:4:"𝞹";s:2:"π";s:4:"𝞺";s:2:"ρ";s:4:"𝞻";s:2:"ς";s:4:"𝞼";s:2:"σ";s:4:"𝞽";s:2:"τ";s:4:"𝞾";s:2:"υ";s:4:"𝞿";s:2:"φ";s:4:"𝟀";s:2:"χ";s:4:"𝟁";s:2:"ψ";s:4:"𝟂";s:2:"ω";s:4:"𝟃";s:3:"∂";s:4:"𝟄";s:2:"ε";s:4:"𝟅";s:2:"θ";s:4:"𝟆";s:2:"κ";s:4:"𝟇";s:2:"φ";s:4:"𝟈";s:2:"ρ";s:4:"𝟉";s:2:"π";s:4:"𝟊";s:2:"Ϝ";s:4:"𝟋";s:2:"ϝ";s:4:"𝟎";s:1:"0";s:4:"𝟏";s:1:"1";s:4:"𝟐";s:1:"2";s:4:"𝟑";s:1:"3";s:4:"𝟒";s:1:"4";s:4:"𝟓";s:1:"5";s:4:"𝟔";s:1:"6";s:4:"𝟕";s:1:"7";s:4:"𝟖";s:1:"8";s:4:"𝟗";s:1:"9";s:4:"𝟘";s:1:"0";s:4:"𝟙";s:1:"1";s:4:"𝟚";s:1:"2";s:4:"𝟛";s:1:"3";s:4:"𝟜";s:1:"4";s:4:"𝟝";s:1:"5";s:4:"𝟞";s:1:"6";s:4:"𝟟";s:1:"7";s:4:"𝟠";s:1:"8";s:4:"𝟡";s:1:"9";s:4:"𝟢";s:1:"0";s:4:"𝟣";s:1:"1";s:4:"𝟤";s:1:"2";s:4:"𝟥";s:1:"3";s:4:"𝟦";s:1:"4";s:4:"𝟧";s:1:"5";s:4:"𝟨";s:1:"6";s:4:"𝟩";s:1:"7";s:4:"𝟪";s:1:"8";s:4:"𝟫";s:1:"9";s:4:"𝟬";s:1:"0";s:4:"𝟭";s:1:"1";s:4:"𝟮";s:1:"2";s:4:"𝟯";s:1:"3";s:4:"𝟰";s:1:"4";s:4:"𝟱";s:1:"5";s:4:"𝟲";s:1:"6";s:4:"𝟳";s:1:"7";s:4:"𝟴";s:1:"8";s:4:"𝟵";s:1:"9";s:4:"𝟶";s:1:"0";s:4:"𝟷";s:1:"1";s:4:"𝟸";s:1:"2";s:4:"𝟹";s:1:"3";s:4:"𝟺";s:1:"4";s:4:"𝟻";s:1:"5";s:4:"𝟼";s:1:"6";s:4:"𝟽";s:1:"7";s:4:"𝟾";s:1:"8";s:4:"𝟿";s:1:"9";s:4:"🄀";s:2:"0.";s:4:"🄁";s:2:"0,";s:4:"🄂";s:2:"1,";s:4:"🄃";s:2:"2,";s:4:"🄄";s:2:"3,";s:4:"🄅";s:2:"4,";s:4:"🄆";s:2:"5,";s:4:"🄇";s:2:"6,";s:4:"🄈";s:2:"7,";s:4:"🄉";s:2:"8,";s:4:"🄊";s:2:"9,";s:4:"🄐";s:3:"(A)";s:4:"🄑";s:3:"(B)";s:4:"🄒";s:3:"(C)";s:4:"🄓";s:3:"(D)";s:4:"🄔";s:3:"(E)";s:4:"🄕";s:3:"(F)";s:4:"🄖";s:3:"(G)";s:4:"🄗";s:3:"(H)";s:4:"🄘";s:3:"(I)";s:4:"🄙";s:3:"(J)";s:4:"🄚";s:3:"(K)";s:4:"🄛";s:3:"(L)";s:4:"🄜";s:3:"(M)";s:4:"🄝";s:3:"(N)";s:4:"🄞";s:3:"(O)";s:4:"🄟";s:3:"(P)";s:4:"🄠";s:3:"(Q)";s:4:"🄡";s:3:"(R)";s:4:"🄢";s:3:"(S)";s:4:"🄣";s:3:"(T)";s:4:"🄤";s:3:"(U)";s:4:"🄥";s:3:"(V)";s:4:"🄦";s:3:"(W)";s:4:"🄧";s:3:"(X)";s:4:"🄨";s:3:"(Y)";s:4:"🄩";s:3:"(Z)";s:4:"🄪";s:7:"〔S〕";s:4:"🄫";s:1:"C";s:4:"🄬";s:1:"R";s:4:"🄭";s:2:"CD";s:4:"🄮";s:2:"WZ";s:4:"🄰";s:1:"A";s:4:"🄱";s:1:"B";s:4:"🄲";s:1:"C";s:4:"🄳";s:1:"D";s:4:"🄴";s:1:"E";s:4:"🄵";s:1:"F";s:4:"🄶";s:1:"G";s:4:"🄷";s:1:"H";s:4:"🄸";s:1:"I";s:4:"🄹";s:1:"J";s:4:"🄺";s:1:"K";s:4:"🄻";s:1:"L";s:4:"🄼";s:1:"M";s:4:"🄽";s:1:"N";s:4:"🄾";s:1:"O";s:4:"🄿";s:1:"P";s:4:"🅀";s:1:"Q";s:4:"🅁";s:1:"R";s:4:"🅂";s:1:"S";s:4:"🅃";s:1:"T";s:4:"🅄";s:1:"U";s:4:"🅅";s:1:"V";s:4:"🅆";s:1:"W";s:4:"🅇";s:1:"X";s:4:"🅈";s:1:"Y";s:4:"🅉";s:1:"Z";s:4:"🅊";s:2:"HV";s:4:"🅋";s:2:"MV";s:4:"🅌";s:2:"SD";s:4:"🅍";s:2:"SS";s:4:"🅎";s:3:"PPV";s:4:"🅏";s:2:"WC";s:4:"🆐";s:2:"DJ";s:4:"🈀";s:6:"ほか";s:4:"🈁";s:6:"ココ";s:4:"🈂";s:3:"サ";s:4:"🈐";s:3:"手";s:4:"🈑";s:3:"字";s:4:"🈒";s:3:"双";s:4:"🈓";s:6:"デ";s:4:"🈔";s:3:"二";s:4:"🈕";s:3:"多";s:4:"🈖";s:3:"解";s:4:"🈗";s:3:"天";s:4:"🈘";s:3:"交";s:4:"🈙";s:3:"映";s:4:"🈚";s:3:"無";s:4:"🈛";s:3:"料";s:4:"🈜";s:3:"前";s:4:"🈝";s:3:"後";s:4:"🈞";s:3:"再";s:4:"🈟";s:3:"新";s:4:"🈠";s:3:"初";s:4:"🈡";s:3:"終";s:4:"🈢";s:3:"生";s:4:"🈣";s:3:"販";s:4:"🈤";s:3:"声";s:4:"🈥";s:3:"吹";s:4:"🈦";s:3:"演";s:4:"🈧";s:3:"投";s:4:"🈨";s:3:"捕";s:4:"🈩";s:3:"一";s:4:"🈪";s:3:"三";s:4:"🈫";s:3:"遊";s:4:"🈬";s:3:"左";s:4:"🈭";s:3:"中";s:4:"🈮";s:3:"右";s:4:"🈯";s:3:"指";s:4:"🈰";s:3:"走";s:4:"🈱";s:3:"打";s:4:"🈲";s:3:"禁";s:4:"🈳";s:3:"空";s:4:"🈴";s:3:"合";s:4:"🈵";s:3:"満";s:4:"🈶";s:3:"有";s:4:"🈷";s:3:"月";s:4:"🈸";s:3:"申";s:4:"🈹";s:3:"割";s:4:"🈺";s:3:"営";s:4:"🉀";s:9:"〔本〕";s:4:"🉁";s:9:"〔三〕";s:4:"🉂";s:9:"〔二〕";s:4:"🉃";s:9:"〔安〕";s:4:"🉄";s:9:"〔点〕";s:4:"🉅";s:9:"〔打〕";s:4:"🉆";s:9:"〔盗〕";s:4:"🉇";s:9:"〔勝〕";s:4:"🉈";s:9:"〔敗〕";s:4:"🉐";s:3:"得";s:4:"🉑";s:3:"可";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
diff --git a/includes/normal/UtfNormalDefines.php b/includes/normal/UtfNormalDefines.php
index b07e3399..18d89f6d 100644
--- a/includes/normal/UtfNormalDefines.php
+++ b/includes/normal/UtfNormalDefines.php
@@ -46,7 +46,6 @@ define( 'UNICODE_SURROGATE_LAST', 0xdfff );
define( 'UNICODE_MAX', 0x10ffff );
define( 'UNICODE_REPLACEMENT', 0xfffd );
-
define( 'UTF8_HANGUL_FIRST', "\xea\xb0\x80" /*codepointToUtf8( UNICODE_HANGUL_FIRST )*/ );
define( 'UTF8_HANGUL_LAST', "\xed\x9e\xa3" /*codepointToUtf8( UNICODE_HANGUL_LAST )*/ );
diff --git a/includes/normal/UtfNormalGenerate.php b/includes/normal/UtfNormalGenerate.php
index f392df52..f57aa6cc 100644
--- a/includes/normal/UtfNormalGenerate.php
+++ b/includes/normal/UtfNormalGenerate.php
@@ -4,7 +4,7 @@
* and supplementary files.
*
* Copyright (C) 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -25,29 +25,37 @@
* @ingroup UtfNormal
*/
-if( PHP_SAPI != 'cli' ) {
+if ( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
-$in = fopen("DerivedNormalizationProps.txt", "rt" );
-if( !$in ) {
+$in = fopen( "DerivedNormalizationProps.txt", "rt" );
+if ( !$in ) {
print "Can't open DerivedNormalizationProps.txt for reading.\n";
print "If necessary, fetch this file from the internet:\n";
print "http://www.unicode.org/Public/UNIDATA/DerivedNormalizationProps.txt\n";
- exit(-1);
+ exit( -1 );
}
print "Initializing normalization quick check tables...\n";
$checkNFC = array();
-while( false !== ($line = fgets( $in ) ) ) {
+while ( false !== ( $line = fgets( $in ) ) ) {
$matches = array();
- if( preg_match( '/^([0-9A-F]+)(?:..([0-9A-F]+))?\s*;\s*(NFC_QC)\s*;\s*([MN])/', $line, $matches ) ) {
+ if ( preg_match(
+ '/^([0-9A-F]+)(?:..([0-9A-F]+))?\s*;\s*(NFC_QC)\s*;\s*([MN])/',
+ $line,
+ $matches )
+ ) {
list( $junk, $first, $last, $prop, $value ) = $matches;
#print "$first $last $prop $value\n";
- if( !$last ) $last = $first;
- for( $i = hexdec( $first ); $i <= hexdec( $last ); $i++) {
+ if ( !$last ) {
+ $last = $first;
+ }
+
+ $lastInDecimal = hexdec( $last );
+ for ( $i = hexdec( $first ); $i <= $lastInDecimal; $i++ ) {
$char = codepointToUtf8( $i );
$checkNFC[$char] = $value;
}
@@ -55,29 +63,29 @@ while( false !== ($line = fgets( $in ) ) ) {
}
fclose( $in );
-$in = fopen("CompositionExclusions.txt", "rt" );
-if( !$in ) {
+$in = fopen( "CompositionExclusions.txt", "rt" );
+if ( !$in ) {
print "Can't open CompositionExclusions.txt for reading.\n";
print "If necessary, fetch this file from the internet:\n";
print "http://www.unicode.org/Public/UNIDATA/CompositionExclusions.txt\n";
- exit(-1);
+ exit( -1 );
}
$exclude = array();
-while( false !== ($line = fgets( $in ) ) ) {
- if( preg_match( '/^([0-9A-F]+)/i', $line, $matches ) ) {
+while ( false !== ( $line = fgets( $in ) ) ) {
+ if ( preg_match( '/^([0-9A-F]+)/i', $line, $matches ) ) {
$codepoint = $matches[1];
$source = codepointToUtf8( hexdec( $codepoint ) );
$exclude[$source] = true;
}
}
-fclose($in);
+fclose( $in );
-$in = fopen("UnicodeData.txt", "rt" );
-if( !$in ) {
+$in = fopen( "UnicodeData.txt", "rt" );
+if ( !$in ) {
print "Can't open UnicodeData.txt for reading.\n";
print "If necessary, fetch this file from the internet:\n";
print "http://www.unicode.org/Public/UNIDATA/UnicodeData.txt\n";
- exit(-1);
+ exit( -1 );
}
$compatibilityDecomp = array();
@@ -89,8 +97,8 @@ $compat = 0;
$canon = 0;
print "Reading character definitions...\n";
-while( false !== ($line = fgets( $in ) ) ) {
- $columns = explode(';', $line);
+while ( false !== ( $line = fgets( $in ) ) ) {
+ $columns = explode( ';', $line );
$codepoint = $columns[0];
$name = $columns[1];
$canonicalCombiningClass = $columns[3];
@@ -98,12 +106,12 @@ while( false !== ($line = fgets( $in ) ) ) {
$source = codepointToUtf8( hexdec( $codepoint ) );
- if( $canonicalCombiningClass != 0 ) {
+ if ( $canonicalCombiningClass != 0 ) {
$combiningClass[$source] = intval( $canonicalCombiningClass );
}
- if( $decompositionMapping === '' ) continue;
- if( preg_match( '/^<(.+)> (.*)$/', $decompositionMapping, $matches ) ) {
+ if ( $decompositionMapping === '' ) continue;
+ if ( preg_match( '/^<(.+)> (.*)$/', $decompositionMapping, $matches ) ) {
# Compatibility decomposition
$canonical = false;
$decompositionMapping = $matches[2];
@@ -116,9 +124,9 @@ while( false !== ($line = fgets( $in ) ) ) {
$dest = hexSequenceToUtf8( $decompositionMapping );
$compatibilityDecomp[$source] = $dest;
- if( $canonical ) {
+ if ( $canonical ) {
$canonicalDecomp[$source] = $dest;
- if( empty( $exclude[$source] ) ) {
+ if ( empty( $exclude[$source] ) ) {
$canonicalComp[$dest] = $source;
}
}
@@ -129,15 +137,15 @@ fclose( $in );
print "Recursively expanding canonical mappings...\n";
$changed = 42;
$pass = 1;
-while( $changed > 0 ) {
+while ( $changed > 0 ) {
print "pass $pass\n";
$changed = 0;
- foreach( $canonicalDecomp as $source => $dest ) {
+ foreach ( $canonicalDecomp as $source => $dest ) {
$newDest = preg_replace_callback(
'/([\xc0-\xff][\x80-\xbf]+)/',
'callbackCanonical',
- $dest);
- if( $newDest === $dest ) continue;
+ $dest );
+ if ( $newDest === $dest ) continue;
$changed++;
$canonicalDecomp[$source] = $newDest;
}
@@ -147,15 +155,15 @@ while( $changed > 0 ) {
print "Recursively expanding compatibility mappings...\n";
$changed = 42;
$pass = 1;
-while( $changed > 0 ) {
+while ( $changed > 0 ) {
print "pass $pass\n";
$changed = 0;
- foreach( $compatibilityDecomp as $source => $dest ) {
+ foreach ( $compatibilityDecomp as $source => $dest ) {
$newDest = preg_replace_callback(
'/([\xc0-\xff][\x80-\xbf]+)/',
'callbackCompat',
- $dest);
- if( $newDest === $dest ) continue;
+ $dest );
+ if ( $newDest === $dest ) continue;
$changed++;
$compatibilityDecomp[$source] = $newDest;
}
@@ -164,8 +172,8 @@ while( $changed > 0 ) {
print "$total decomposition mappings ($canon canonical, $compat compatibility)\n";
-$out = fopen("UtfNormalData.inc", "wt");
-if( $out ) {
+$out = fopen( "UtfNormalData.inc", "wt" );
+if ( $out ) {
$serCombining = escapeSingleString( serialize( $combiningClass ) );
$serComp = escapeSingleString( serialize( $canonicalComp ) );
$serCanon = escapeSingleString( serialize( $canonicalDecomp ) );
@@ -177,6 +185,7 @@ if( $out ) {
*
* @file
*/
+// @codingStandardsIgnoreFile
UtfNormal::\$utfCombiningClass = unserialize( '$serCombining' );
UtfNormal::\$utfCanonicalComp = unserialize( '$serComp' );
@@ -188,12 +197,11 @@ UtfNormal::\$utfCheckNFC = unserialize( '$serCheckNFC' );
print "Wrote out UtfNormalData.inc\n";
} else {
print "Can't create file UtfNormalData.inc\n";
- exit(-1);
+ exit( -1 );
}
-
-$out = fopen("UtfNormalDataK.inc", "wt");
-if( $out ) {
+$out = fopen( "UtfNormalDataK.inc", "wt" );
+if ( $out ) {
$serCompat = escapeSingleString( serialize( $compatibilityDecomp ) );
$outdata = "<" . "?php
/**
@@ -202,32 +210,41 @@ if( $out ) {
*
* @file
*/
+// @codingStandardsIgnoreFile
UtfNormal::\$utfCompatibilityDecomp = unserialize( '$serCompat' );
\n";
fputs( $out, $outdata );
fclose( $out );
print "Wrote out UtfNormalDataK.inc\n";
- exit(0);
+ exit( 0 );
} else {
print "Can't create file UtfNormalDataK.inc\n";
- exit(-1);
+ exit( -1 );
}
# ---------------
function callbackCanonical( $matches ) {
+ // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
global $canonicalDecomp;
- if( isset( $canonicalDecomp[$matches[1]] ) ) {
+ // @codingStandardsIgnoreEnd
+
+ if ( isset( $canonicalDecomp[$matches[1]] ) ) {
return $canonicalDecomp[$matches[1]];
}
+
return $matches[1];
}
function callbackCompat( $matches ) {
+ // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
global $compatibilityDecomp;
- if( isset( $compatibilityDecomp[$matches[1]] ) ) {
+ // @codingStandardsIgnoreEnd
+
+ if ( isset( $compatibilityDecomp[$matches[1]] ) ) {
return $compatibilityDecomp[$matches[1]];
}
+
return $matches[1];
}
diff --git a/includes/normal/UtfNormalMemStress.php b/includes/normal/UtfNormalMemStress.php
index 9732d762..f133e4d5 100644
--- a/includes/normal/UtfNormalMemStress.php
+++ b/includes/normal/UtfNormalMemStress.php
@@ -5,7 +5,7 @@
* to test regression on mem usage (bug 28146)
*
* Copyright © 2004-2011 Brion Vibber <brion@wikimedia.org>
- * http://www.mediawiki.org/
+ * 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
@@ -26,11 +26,11 @@
* @ingroup UtfNormal
*/
-if( PHP_SAPI != 'cli' ) {
+if ( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
-if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
+if ( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
dl( 'php_utfnormal.so' );
}
@@ -39,8 +39,8 @@ require_once 'UtfNormalUtil.php';
require_once 'UtfNormal.php';
define( 'BENCH_CYCLES', 1 );
-define( 'BIGSIZE', 1024 * 1024 * 10); // 10m
-ini_set('memory_limit', BIGSIZE + 120 * 1024 * 1024);
+define( 'BIGSIZE', 1024 * 1024 * 10 ); // 10m
+ini_set( 'memory_limit', BIGSIZE + 120 * 1024 * 1024 );
$testfiles = array(
'testdata/washington.txt' => 'English text',
@@ -51,7 +51,7 @@ $testfiles = array(
);
$normalizer = new UtfNormal;
UtfNormal::loadData();
-foreach( $testfiles as $file => $desc ) {
+foreach ( $testfiles as $file => $desc ) {
benchmarkTest( $normalizer, $file, $desc );
}
@@ -61,19 +61,20 @@ function benchmarkTest( &$u, $filename, $desc ) {
print "Testing $filename ($desc)...\n";
$data = file_get_contents( $filename );
$all = $data;
- while (strlen($all) < BIGSIZE) {
+ while ( strlen( $all ) < BIGSIZE ) {
$all .= $all;
}
$data = $all;
- echo "Data is " . strlen($data) . " bytes.\n";
+ echo "Data is " . strlen( $data ) . " bytes.\n";
$forms = array(
- 'quickIsNFCVerify',
+ 'quickIsNFCVerify',
'cleanUp',
- );
- foreach( $forms as $form ) {
- if( is_array( $form ) ) {
+ );
+
+ foreach ( $forms as $form ) {
+ if ( is_array( $form ) ) {
$str = $data;
- foreach( $form as $step ) {
+ foreach ( $form as $step ) {
$str = benchmarkForm( $u, $str, $step );
}
} else {
@@ -82,29 +83,25 @@ function benchmarkTest( &$u, $filename, $desc ) {
}
}
-function benchTime() {
- $st = explode( ' ', microtime() );
- return (float)$st[0] + (float)$st[1];
-}
-
function benchmarkForm( &$u, &$data, $form ) {
- #$start = benchTime();
- for( $i = 0; $i < BENCH_CYCLES; $i++ ) {
- $start = benchTime();
+ #$start = microtime( true );
+ for ( $i = 0; $i < BENCH_CYCLES; $i++ ) {
+ $start = microtime( true );
$out = $u->$form( $data, UtfNormal::$utfCanonicalDecomp );
- $deltas[] = (benchTime() - $start);
+ $deltas[] = ( microtime( true ) - $start );
}
- #$delta = (benchTime() - $start) / BENCH_CYCLES;
+ #$delta = (microtime( true ) - $start) / BENCH_CYCLES;
sort( $deltas );
$delta = $deltas[0]; # Take shortest time
$rate = intval( strlen( $data ) / $delta );
- $same = (0 == strcmp( $data, $out ) );
+ $same = ( 0 == strcmp( $data, $out ) );
printf( " %20s %6.1fms %12s bytes/s (%s)\n",
$form,
- $delta*1000.0,
+ $delta * 1000.0,
number_format( $rate ),
- ($same ? 'no change' : 'changed' ) );
+ ( $same ? 'no change' : 'changed' ) );
+
return $out;
}
diff --git a/includes/normal/UtfNormalTest.php b/includes/normal/UtfNormalTest.php
index 51183666..10cd0f0c 100644
--- a/includes/normal/UtfNormalTest.php
+++ b/includes/normal/UtfNormalTest.php
@@ -4,7 +4,7 @@
* http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -25,20 +25,21 @@
* @ingroup UtfNormal
*/
-if( PHP_SAPI != 'cli' ) {
+if ( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
$verbose = true;
#define( 'PRETTY_UTF8', true );
-if( defined( 'PRETTY_UTF8' ) ) {
+if ( defined( 'PRETTY_UTF8' ) ) {
function pretty( $string ) {
return strtoupper( bin2hex( $string ) );
}
} else {
/**
* @ignore
+ * @param string $string
* @return string
*/
function pretty( $string ) {
@@ -46,7 +47,7 @@ if( defined( 'PRETTY_UTF8' ) ) {
}
}
-if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
+if ( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
dl( 'php_utfnormal.so' );
}
@@ -54,12 +55,12 @@ require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
require_once 'UtfNormal.php';
-$in = fopen("NormalizationTest.txt", "rt");
-if( !$in ) {
+$in = fopen( "NormalizationTest.txt", "rt" );
+if ( !$in ) {
print "Couldn't open NormalizationTest.txt -- can't run tests.\n";
print "If necessary, manually download this file. It can be obtained at\n";
print "http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt";
- exit(-1);
+ exit( -1 );
}
$normalizer = new UtfNormal;
@@ -69,12 +70,13 @@ $success = 0;
$failure = 0;
$ok = true;
$testedChars = array();
-while( false !== ( $line = fgets( $in ) ) ) {
+
+while ( false !== ( $line = fgets( $in ) ) ) {
list( $data, $comment ) = explode( '#', $line );
- if( $data === '' ) continue;
+ if ( $data === '' ) continue;
$matches = array();
- if( preg_match( '/@Part([\d])/', $data, $matches ) ) {
- if( $matches[1] > 0 ) {
+ if ( preg_match( '/@Part([\d])/', $data, $matches ) ) {
+ if ( $matches[1] > 0 ) {
$ok = reportResults( $total, $success, $failure ) && $ok;
}
print "Part {$matches[1]}: $comment";
@@ -86,56 +88,57 @@ while( false !== ( $line = fgets( $in ) ) ) {
$testedChars[$columns[1]] = true;
$total++;
- if( testNormals( $normalizer, $columns, $comment, $verbose ) ) {
+ if ( testNormals( $normalizer, $columns, $comment, $verbose ) ) {
$success++;
} else {
$failure++;
# print "FAILED: $comment";
}
- if( $total % 100 == 0 ) print "$total ";
+ if ( $total % 100 == 0 ) print "$total ";
}
fclose( $in );
$ok = reportResults( $total, $success, $failure ) && $ok;
-$in = fopen("UnicodeData.txt", "rt" );
-if( !$in ) {
+$in = fopen( "UnicodeData.txt", "rt" );
+if ( !$in ) {
print "Can't open UnicodeData.txt for reading.\n";
print "If necessary, fetch this file from the internet:\n";
print "http://www.unicode.org/Public/UNIDATA/UnicodeData.txt\n";
- exit(-1);
+ exit( -1 );
}
print "Now testing invariants...\n";
-while( false !== ($line = fgets( $in ) ) ) {
+
+while ( false !== ( $line = fgets( $in ) ) ) {
$cols = explode( ';', $line );
$char = codepointToUtf8( hexdec( $cols[0] ) );
$desc = $cols[0] . ": " . $cols[1];
- if( $char < "\x20" || $char >= UTF8_SURROGATE_FIRST && $char <= UTF8_SURROGATE_LAST ) {
+ if ( $char < "\x20" || $char >= UTF8_SURROGATE_FIRST && $char <= UTF8_SURROGATE_LAST ) {
# Can't check NULL with the ICU plugin, as null bytes fail in C land.
# Skip other control characters, as we strip them for XML safety.
# Surrogates are illegal on their own or in UTF-8, ignore.
continue;
}
- if( empty( $testedChars[$char] ) ) {
+ if ( empty( $testedChars[$char] ) ) {
$total++;
- if( testInvariant( $normalizer, $char, $desc, $verbose ) ) {
+ if ( testInvariant( $normalizer, $char, $desc, $verbose ) ) {
$success++;
} else {
$failure++;
}
- if( $total % 100 == 0 ) print "$total ";
+ if ( $total % 100 == 0 ) print "$total ";
}
}
fclose( $in );
$ok = reportResults( $total, $success, $failure ) && $ok;
-if( $ok ) {
+if ( $ok ) {
print "TEST SUCCEEDED!\n";
- exit(0);
+ exit( 0 );
} else {
print "TEST FAILED!\n";
- exit(-1);
+ exit( -1 );
}
## ------
@@ -146,10 +149,11 @@ function reportResults( &$total, &$success, &$failure ) {
print "\n";
print "$success tests successful ($percSucc%)\n";
print "$failure tests failed ($percFail%)\n\n";
- $ok = ($success > 0 && $failure == 0);
+ $ok = ( $success > 0 && $failure == 0 );
$total = 0;
$success = 0;
$failure = 0;
+
return $ok;
}
@@ -160,23 +164,25 @@ function testNormals( &$u, $c, $comment, $verbose, $reportFailure = false ) {
$result = testNFKD( $u, $c, $comment, $reportFailure ) && $result;
$result = testCleanUp( $u, $c, $comment, $reportFailure ) && $result;
- if( $verbose && !$result && !$reportFailure ) {
+ if ( $verbose && !$result && !$reportFailure ) {
print $comment;
testNormals( $u, $c, $comment, $verbose, true );
}
+
return $result;
}
function verbosify( $a, $b, $col, $form, $verbose ) {
#$result = ($a === $b);
- $result = (strcmp( $a, $b ) == 0);
- if( $verbose ) {
+ $result = ( strcmp( $a, $b ) == 0 );
+ if ( $verbose ) {
$aa = pretty( $a );
$bb = pretty( $b );
$ok = $result ? "succeed" : " failed";
$eq = $result ? "==" : "!=";
print " $ok $form c$col '$aa' $eq '$bb'\n";
}
+
return $result;
}
@@ -186,6 +192,7 @@ function testNFC( &$u, $c, $comment, $verbose ) {
$result = verbosify( $c[2], $u->toNFC( $c[3] ), 3, 'NFC', $verbose ) && $result;
$result = verbosify( $c[4], $u->toNFC( $c[4] ), 4, 'NFC', $verbose ) && $result;
$result = verbosify( $c[4], $u->toNFC( $c[5] ), 5, 'NFC', $verbose ) && $result;
+
return $result;
}
@@ -200,6 +207,7 @@ function testCleanUp( &$u, $c, $comment, $verbose ) {
$result = verbosify( $c[4], $u->cleanUp( $x ), 4, 'cleanUp', $verbose ) && $result;
$x = $c[5];
$result = verbosify( $c[4], $u->cleanUp( $x ), 5, 'cleanUp', $verbose ) && $result;
+
return $result;
}
@@ -209,6 +217,7 @@ function testNFD( &$u, $c, $comment, $verbose ) {
$result = verbosify( $c[3], $u->toNFD( $c[3] ), 3, 'NFD', $verbose ) && $result;
$result = verbosify( $c[5], $u->toNFD( $c[4] ), 4, 'NFD', $verbose ) && $result;
$result = verbosify( $c[5], $u->toNFD( $c[5] ), 5, 'NFD', $verbose ) && $result;
+
return $result;
}
@@ -218,6 +227,7 @@ function testNFKC( &$u, $c, $comment, $verbose ) {
$result = verbosify( $c[4], $u->toNFKC( $c[3] ), 3, 'NFKC', $verbose ) && $result;
$result = verbosify( $c[4], $u->toNFKC( $c[4] ), 4, 'NFKC', $verbose ) && $result;
$result = verbosify( $c[4], $u->toNFKC( $c[5] ), 5, 'NFKC', $verbose ) && $result;
+
return $result;
}
@@ -227,6 +237,7 @@ function testNFKD( &$u, $c, $comment, $verbose ) {
$result = verbosify( $c[5], $u->toNFKD( $c[3] ), 3, 'NFKD', $verbose ) && $result;
$result = verbosify( $c[5], $u->toNFKD( $c[4] ), 4, 'NFKD', $verbose ) && $result;
$result = verbosify( $c[5], $u->toNFKD( $c[5] ), 5, 'NFKD', $verbose ) && $result;
+
return $result;
}
@@ -237,9 +248,10 @@ function testInvariant( &$u, $char, $desc, $verbose, $reportFailure = false ) {
$result = verbosify( $char, $u->toNFKD( $char ), 1, 'NFKD', $reportFailure ) && $result;
$result = verbosify( $char, $u->cleanUp( $char ), 1, 'cleanUp', $reportFailure ) && $result;
- if( $verbose && !$result && !$reportFailure ) {
+ if ( $verbose && !$result && !$reportFailure ) {
print $desc;
testInvariant( $u, $char, $desc, $verbose, true );
}
+
return $result;
}
diff --git a/includes/normal/UtfNormalTest2.php b/includes/normal/UtfNormalTest2.php
index 750c0099..53e68c29 100644
--- a/includes/normal/UtfNormalTest2.php
+++ b/includes/normal/UtfNormalTest2.php
@@ -22,7 +22,7 @@
* @ingroup UtfNormal
*/
-if( PHP_SAPI != 'cli' ) {
+if ( PHP_SAPI != 'cli' ) {
die( "Run me from the command line please.\n" );
}
@@ -35,41 +35,47 @@ define ( 'COMMENT', '#' );
// Semicolons are used to separate the columns
define ( 'SEPARATOR', ';' );
-$f = fopen($file, "r");
+$f = fopen( $file, "r" );
/**
* The following section will be used for testing different normalization methods.
* - Pure PHP
- ~ no assertion errors
- ~ 6.25 minutes
-
+ * ~ no assertion errors
+ * ~ 6.25 minutes
* - php_utfnormal.so or intl extension: both are wrappers around
- libicu so we list the version of libicu when making the
- comparison
-
+ * libicu so we list the version of libicu when making the
+ * comparison
* - libicu Ubuntu 3.8.1-3ubuntu1.1 php 5.2.6-3ubuntu4.5
- ~ 2200 assertion errors
- ~ 5 seconds
- ~ output: http://paste2.org/p/921566
-
+ * ~ 2200 assertion errors
+ * ~ 5 seconds
+ * ~ output: http://paste2.org/p/921566
* - libicu Ubuntu 4.2.1-3 php 5.3.2-1ubuntu4.2
- ~ 1384 assertion errors
- ~ 15 seconds
- ~ output: http://paste2.org/p/921435
-
+ * ~ 1384 assertion errors
+ * ~ 15 seconds
+ * ~ output: http://paste2.org/p/921435
* - libicu Debian 4.4.1-5 php 5.3.2-1ubuntu4.2
- ~ no assertion errors
- ~ 13 seconds
-
+ * ~ no assertion errors
+ * ~ 13 seconds
* - Tests comparing pure PHP output with libicu output were added
- later and slow down the runtime.
+ * later and slow down the runtime.
*/
require_once './UtfNormal.php';
-function normalize_form_c($c) { return UtfNormal::toNFC($c); }
-function normalize_form_d($c) { return UtfNormal::toNFD($c); }
-function normalize_form_kc($c) { return UtfNormal::toNFKC($c); }
-function normalize_form_kd($c) { return UtfNormal::toNFKD($c); }
+function normalize_form_c( $c ) {
+ return UtfNormal::toNFC( $c );
+}
+
+function normalize_form_d( $c ) {
+ return UtfNormal::toNFD( $c );
+}
+
+function normalize_form_kc( $c ) {
+ return UtfNormal::toNFKC( $c );
+}
+
+function normalize_form_kd( $c ) {
+ return UtfNormal::toNFKD( $c );
+}
/**
* This set of functions is only useful if youve added a param to the
@@ -78,175 +84,189 @@ function normalize_form_kd($c) { return UtfNormal::toNFKD($c); }
* normalization code just for the sake of these tests. -- hexmode
* @return string
*/
-function normalize_form_c_php($c) { return UtfNormal::toNFC($c, "php"); }
-function normalize_form_d_php($c) { return UtfNormal::toNFD($c, "php"); }
-function normalize_form_kc_php($c) { return UtfNormal::toNFKC($c, "php"); }
-function normalize_form_kd_php($c) { return UtfNormal::toNFKD($c, "php"); }
+function normalize_form_c_php( $c ) {
+ return UtfNormal::toNFC( $c, "php" );
+}
+
+function normalize_form_d_php( $c ) {
+ return UtfNormal::toNFD( $c, "php" );
+}
-assert_options(ASSERT_ACTIVE, 1);
-assert_options(ASSERT_WARNING, 0);
-assert_options(ASSERT_QUIET_EVAL, 1);
-assert_options(ASSERT_CALLBACK, 'my_assert');
+function normalize_form_kc_php( $c ) {
+ return UtfNormal::toNFKC( $c, "php" );
+}
+
+function normalize_form_kd_php( $c ) {
+ return UtfNormal::toNFKD( $c, "php" );
+}
+
+assert_options( ASSERT_ACTIVE, 1 );
+assert_options( ASSERT_WARNING, 0 );
+assert_options( ASSERT_QUIET_EVAL, 1 );
+assert_options( ASSERT_CALLBACK, 'my_assert' );
function my_assert( $file, $line, $code ) {
+ // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
global $col, $lineNo;
+ // @codingStandardsIgnoreEnd
+
echo "Assertion that '$code' failed on line $lineNo ($col[5])\n";
}
$count = 0;
$lineNo = 0;
-if( $f !== false ) {
- while( ( $col = getRow( $f ) ) !== false ) {
+if ( $f !== false ) {
+ while ( ( $col = getRow( $f ) ) !== false ) {
$lineNo++;
- if(count($col) == 6) {
+ if ( count( $col ) == 6 ) {
$count++;
- if( $count % 100 === 0 ) echo "Count: $count\n";
+ if ( $count % 100 === 0 ) echo "Count: $count\n";
} else {
continue;
}
# verify that the pure PHP version is correct
- $NFCc1 = normalize_form_c($col[0]);
- $NFCc1p = normalize_form_c_php($col[0]);
- assert('$NFCc1 === $NFCc1p');
- $NFCc2 = normalize_form_c($col[1]);
- $NFCc2p = normalize_form_c_php($col[1]);
- assert('$NFCc2 === $NFCc2p');
- $NFCc3 = normalize_form_c($col[2]);
- $NFCc3p = normalize_form_c_php($col[2]);
- assert('$NFCc3 === $NFCc3p');
- $NFCc4 = normalize_form_c($col[3]);
- $NFCc4p = normalize_form_c_php($col[3]);
- assert('$NFCc4 === $NFCc4p');
- $NFCc5 = normalize_form_c($col[4]);
- $NFCc5p = normalize_form_c_php($col[4]);
- assert('$NFCc5 === $NFCc5p');
-
- $NFDc1 = normalize_form_d($col[0]);
- $NFDc1p = normalize_form_d_php($col[0]);
- assert('$NFDc1 === $NFDc1p');
- $NFDc2 = normalize_form_d($col[1]);
- $NFDc2p = normalize_form_d_php($col[1]);
- assert('$NFDc2 === $NFDc2p');
- $NFDc3 = normalize_form_d($col[2]);
- $NFDc3p = normalize_form_d_php($col[2]);
- assert('$NFDc3 === $NFDc3p');
- $NFDc4 = normalize_form_d($col[3]);
- $NFDc4p = normalize_form_d_php($col[3]);
- assert('$NFDc4 === $NFDc4p');
- $NFDc5 = normalize_form_d($col[4]);
- $NFDc5p = normalize_form_d_php($col[4]);
- assert('$NFDc5 === $NFDc5p');
-
- $NFKDc1 = normalize_form_kd($col[0]);
- $NFKDc1p = normalize_form_kd_php($col[0]);
- assert('$NFKDc1 === $NFKDc1p');
- $NFKDc2 = normalize_form_kd($col[1]);
- $NFKDc2p = normalize_form_kd_php($col[1]);
- assert('$NFKDc2 === $NFKDc2p');
- $NFKDc3 = normalize_form_kd($col[2]);
- $NFKDc3p = normalize_form_kd_php($col[2]);
- assert('$NFKDc3 === $NFKDc3p');
- $NFKDc4 = normalize_form_kd($col[3]);
- $NFKDc4p = normalize_form_kd_php($col[3]);
- assert('$NFKDc4 === $NFKDc4p');
- $NFKDc5 = normalize_form_kd($col[4]);
- $NFKDc5p = normalize_form_kd_php($col[4]);
- assert('$NFKDc5 === $NFKDc5p');
-
- $NFKCc1 = normalize_form_kc($col[0]);
- $NFKCc1p = normalize_form_kc_php($col[0]);
- assert('$NFKCc1 === $NFKCc1p');
- $NFKCc2 = normalize_form_kc($col[1]);
- $NFKCc2p = normalize_form_kc_php($col[1]);
- assert('$NFKCc2 === $NFKCc2p');
- $NFKCc3 = normalize_form_kc($col[2]);
- $NFKCc3p = normalize_form_kc_php($col[2]);
- assert('$NFKCc3 === $NFKCc3p');
- $NFKCc4 = normalize_form_kc($col[3]);
- $NFKCc4p = normalize_form_kc_php($col[3]);
- assert('$NFKCc4 === $NFKCc4p');
- $NFKCc5 = normalize_form_kc($col[4]);
- $NFKCc5p = normalize_form_kc_php($col[4]);
- assert('$NFKCc5 === $NFKCc5p');
+ $NFCc1 = normalize_form_c( $col[0] );
+ $NFCc1p = normalize_form_c_php( $col[0] );
+ assert( '$NFCc1 === $NFCc1p' );
+ $NFCc2 = normalize_form_c( $col[1] );
+ $NFCc2p = normalize_form_c_php( $col[1] );
+ assert( '$NFCc2 === $NFCc2p' );
+ $NFCc3 = normalize_form_c( $col[2] );
+ $NFCc3p = normalize_form_c_php( $col[2] );
+ assert( '$NFCc3 === $NFCc3p' );
+ $NFCc4 = normalize_form_c( $col[3] );
+ $NFCc4p = normalize_form_c_php( $col[3] );
+ assert( '$NFCc4 === $NFCc4p' );
+ $NFCc5 = normalize_form_c( $col[4] );
+ $NFCc5p = normalize_form_c_php( $col[4] );
+ assert( '$NFCc5 === $NFCc5p' );
+
+ $NFDc1 = normalize_form_d( $col[0] );
+ $NFDc1p = normalize_form_d_php( $col[0] );
+ assert( '$NFDc1 === $NFDc1p' );
+ $NFDc2 = normalize_form_d( $col[1] );
+ $NFDc2p = normalize_form_d_php( $col[1] );
+ assert( '$NFDc2 === $NFDc2p' );
+ $NFDc3 = normalize_form_d( $col[2] );
+ $NFDc3p = normalize_form_d_php( $col[2] );
+ assert( '$NFDc3 === $NFDc3p' );
+ $NFDc4 = normalize_form_d( $col[3] );
+ $NFDc4p = normalize_form_d_php( $col[3] );
+ assert( '$NFDc4 === $NFDc4p' );
+ $NFDc5 = normalize_form_d( $col[4] );
+ $NFDc5p = normalize_form_d_php( $col[4] );
+ assert( '$NFDc5 === $NFDc5p' );
+
+ $NFKDc1 = normalize_form_kd( $col[0] );
+ $NFKDc1p = normalize_form_kd_php( $col[0] );
+ assert( '$NFKDc1 === $NFKDc1p' );
+ $NFKDc2 = normalize_form_kd( $col[1] );
+ $NFKDc2p = normalize_form_kd_php( $col[1] );
+ assert( '$NFKDc2 === $NFKDc2p' );
+ $NFKDc3 = normalize_form_kd( $col[2] );
+ $NFKDc3p = normalize_form_kd_php( $col[2] );
+ assert( '$NFKDc3 === $NFKDc3p' );
+ $NFKDc4 = normalize_form_kd( $col[3] );
+ $NFKDc4p = normalize_form_kd_php( $col[3] );
+ assert( '$NFKDc4 === $NFKDc4p' );
+ $NFKDc5 = normalize_form_kd( $col[4] );
+ $NFKDc5p = normalize_form_kd_php( $col[4] );
+ assert( '$NFKDc5 === $NFKDc5p' );
+
+ $NFKCc1 = normalize_form_kc( $col[0] );
+ $NFKCc1p = normalize_form_kc_php( $col[0] );
+ assert( '$NFKCc1 === $NFKCc1p' );
+ $NFKCc2 = normalize_form_kc( $col[1] );
+ $NFKCc2p = normalize_form_kc_php( $col[1] );
+ assert( '$NFKCc2 === $NFKCc2p' );
+ $NFKCc3 = normalize_form_kc( $col[2] );
+ $NFKCc3p = normalize_form_kc_php( $col[2] );
+ assert( '$NFKCc3 === $NFKCc3p' );
+ $NFKCc4 = normalize_form_kc( $col[3] );
+ $NFKCc4p = normalize_form_kc_php( $col[3] );
+ assert( '$NFKCc4 === $NFKCc4p' );
+ $NFKCc5 = normalize_form_kc( $col[4] );
+ $NFKCc5p = normalize_form_kc_php( $col[4] );
+ assert( '$NFKCc5 === $NFKCc5p' );
# c2 == NFC(c1) == NFC(c2) == NFC(c3)
- assert('$col[1] === $NFCc1');
- assert('$col[1] === $NFCc2');
- assert('$col[1] === $NFCc3');
+ assert( '$col[1] === $NFCc1' );
+ assert( '$col[1] === $NFCc2' );
+ assert( '$col[1] === $NFCc3' );
# c4 == NFC(c4) == NFC(c5)
- assert('$col[3] === $NFCc4');
- assert('$col[3] === $NFCc5');
+ assert( '$col[3] === $NFCc4' );
+ assert( '$col[3] === $NFCc5' );
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
- assert('$col[2] === $NFDc1');
- assert('$col[2] === $NFDc2');
- assert('$col[2] === $NFDc3');
+ assert( '$col[2] === $NFDc1' );
+ assert( '$col[2] === $NFDc2' );
+ assert( '$col[2] === $NFDc3' );
# c5 == NFD(c4) == NFD(c5)
- assert('$col[4] === $NFDc4');
- assert('$col[4] === $NFDc5');
+ assert( '$col[4] === $NFDc4' );
+ assert( '$col[4] === $NFDc5' );
# c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
- assert('$col[3] === $NFKCc1');
- assert('$col[3] === $NFKCc2');
- assert('$col[3] === $NFKCc3');
- assert('$col[3] === $NFKCc4');
- assert('$col[3] === $NFKCc5');
+ assert( '$col[3] === $NFKCc1' );
+ assert( '$col[3] === $NFKCc2' );
+ assert( '$col[3] === $NFKCc3' );
+ assert( '$col[3] === $NFKCc4' );
+ assert( '$col[3] === $NFKCc5' );
# c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
- assert('$col[4] === $NFKDc1');
- assert('$col[4] === $NFKDc2');
- assert('$col[4] === $NFKDc3');
- assert('$col[4] === $NFKDc4');
- assert('$col[4] === $NFKDc5');
+ assert( '$col[4] === $NFKDc1' );
+ assert( '$col[4] === $NFKDc2' );
+ assert( '$col[4] === $NFKDc3' );
+ assert( '$col[4] === $NFKDc4' );
+ assert( '$col[4] === $NFKDc5' );
}
}
echo "done.\n";
// Compare against http://en.wikipedia.org/wiki/UTF-8#Description
-function unichr($c) {
- if ($c <= 0x7F) {
- return chr($c);
- } elseif ($c <= 0x7FF) {
- return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
- } elseif ($c <= 0xFFFF) {
- return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
- . chr(0x80 | $c & 0x3F);
- } elseif ($c <= 0x10FFFF) {
- return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
- . chr(0x80 | $c >> 6 & 0x3F)
- . chr(0x80 | $c & 0x3F);
+function unichr( $c ) {
+ if ( $c <= 0x7F ) {
+ return chr( $c );
+ } elseif ( $c <= 0x7FF ) {
+ return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
+ } elseif ( $c <= 0xFFFF ) {
+ return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F )
+ . chr( 0x80 | $c & 0x3F );
+ } elseif ( $c <= 0x10FFFF ) {
+ return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F )
+ . chr( 0x80 | $c >> 6 & 0x3F )
+ . chr( 0x80 | $c & 0x3F );
} else {
return false;
}
}
-function unistr($c) {
- return implode("", array_map("unichr", array_map("hexdec", explode(" ", $c))));
+function unistr( $c ) {
+ return implode( "", array_map( "unichr", array_map( "hexdec", explode( " ", $c ) ) ) );
}
function getRow( $f ) {
$row = fgets( $f );
- if( $row === false ) return false;
- $row = rtrim($row);
+ if ( $row === false ) return false;
+ $row = rtrim( $row );
$pos = strpos( $row, COMMENT );
$pos2 = strpos( $row, ")" );
- if( $pos === 0 ) return array($row);
+ if ( $pos === 0 ) return array( $row );
$c = "";
- if( $pos ) {
- if($pos2) $c = substr( $row, $pos2 + 2 );
- else $c = substr( $row, $pos );
+ if ( $pos ) {
+ if ( $pos2 ) $c = substr( $row, $pos2 + 2 );
+ else $c = substr( $row, $pos );
$row = substr( $row, 0, $pos );
}
$ret = array();
- foreach( explode( SEPARATOR, $row ) as $ent ) {
- if( trim( $ent ) !== "" ) {
- $ret[] = unistr($ent);
+ foreach ( explode( SEPARATOR, $row ) as $ent ) {
+ if ( trim( $ent ) !== "" ) {
+ $ret[] = unistr( $ent );
}
}
$ret[] = $c;
diff --git a/includes/normal/UtfNormalUtil.php b/includes/normal/UtfNormalUtil.php
index e8fec936..6c925dfa 100644
--- a/includes/normal/UtfNormalUtil.php
+++ b/includes/normal/UtfNormalUtil.php
@@ -4,7 +4,7 @@
* Should probably merge them for consistency.
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -34,16 +34,27 @@
* @public
*/
function codepointToUtf8( $codepoint ) {
- if($codepoint < 0x80) return chr($codepoint);
- if($codepoint < 0x800) return chr($codepoint >> 6 & 0x3f | 0xc0) .
- chr($codepoint & 0x3f | 0x80);
- if($codepoint < 0x10000) return chr($codepoint >> 12 & 0x0f | 0xe0) .
- chr($codepoint >> 6 & 0x3f | 0x80) .
- chr($codepoint & 0x3f | 0x80);
- if($codepoint < 0x110000) return chr($codepoint >> 18 & 0x07 | 0xf0) .
- chr($codepoint >> 12 & 0x3f | 0x80) .
- chr($codepoint >> 6 & 0x3f | 0x80) .
- chr($codepoint & 0x3f | 0x80);
+ if ( $codepoint < 0x80 ) {
+ return chr( $codepoint );
+ }
+
+ if ( $codepoint < 0x800 ) {
+ return chr( $codepoint >> 6 & 0x3f | 0xc0 ) .
+ chr( $codepoint & 0x3f | 0x80 );
+ }
+
+ if ( $codepoint < 0x10000 ) {
+ return chr( $codepoint >> 12 & 0x0f | 0xe0 ) .
+ chr( $codepoint >> 6 & 0x3f | 0x80 ) .
+ chr( $codepoint & 0x3f | 0x80 );
+ }
+
+ if ( $codepoint < 0x110000 ) {
+ return chr( $codepoint >> 18 & 0x07 | 0xf0 ) .
+ chr( $codepoint >> 12 & 0x3f | 0x80 ) .
+ chr( $codepoint >> 6 & 0x3f | 0x80 ) .
+ chr( $codepoint & 0x3f | 0x80 );
+ }
echo "Asked for code outside of range ($codepoint)\n";
die( -1 );
@@ -60,10 +71,11 @@ function codepointToUtf8( $codepoint ) {
*/
function hexSequenceToUtf8( $sequence ) {
$utf = '';
- foreach( explode( ' ', $sequence ) as $hex ) {
+ foreach ( explode( ' ', $sequence ) as $hex ) {
$n = hexdec( $hex );
$utf .= codepointToUtf8( $n );
}
+
return $utf;
}
@@ -80,6 +92,7 @@ function utf8ToHexSequence( $str ) {
foreach ( preg_split( '//u', $str, -1, PREG_SPLIT_NO_EMPTY ) as $cp ) {
$buf .= sprintf( '%04x ', utf8ToCodepoint( $cp ) );
}
+
return rtrim( $buf );
}
@@ -107,6 +120,7 @@ function utf8ToCodepoint( $char ) {
if ( $length != strlen( $char ) ) {
return false;
}
+
if ( $length == 1 ) {
return ord( $char );
}
@@ -116,7 +130,7 @@ function utf8ToCodepoint( $char ) {
$z >>= $length;
# Add in the free bits from subsequent bytes
- for ( $i=1; $i < $length; $i++ ) {
+ for ( $i = 1; $i < $length; $i++ ) {
$z <<= 6;
$z |= ord( $char[$i] ) & 0x3f;
}
@@ -136,5 +150,5 @@ function escapeSingleString( $string ) {
array(
'\\' => '\\\\',
'\'' => '\\\''
- ));
+ ) );
}
diff --git a/includes/objectcache/APCBagOStuff.php b/includes/objectcache/APCBagOStuff.php
index 3fb80835..4cbb32df 100644
--- a/includes/objectcache/APCBagOStuff.php
+++ b/includes/objectcache/APCBagOStuff.php
@@ -28,8 +28,8 @@
*/
class APCBagOStuff extends BagOStuff {
/**
- * @param $key string
- * @param $casToken[optional] int
+ * @param string $key
+ * @param int $casToken [optional]
* @return mixed
*/
public function get( $key, &$casToken = null ) {
@@ -49,9 +49,9 @@ class APCBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function set( $key, $value, $exptime = 0 ) {
@@ -65,10 +65,10 @@ class APCBagOStuff extends BagOStuff {
}
/**
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param mixed $casToken
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function cas( $casToken, $key, $value, $exptime = 0 ) {
@@ -77,8 +77,8 @@ class APCBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $time int
+ * @param string $key
+ * @param int $time
* @return bool
*/
public function delete( $key, $time = 0 ) {
@@ -88,13 +88,13 @@ class APCBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $callback closure Callback method to be executed
+ * @param string $key
+ * @param Closure $callback Callback method to be executed
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @param int $attempts The amount of times to attempt a merge in case of failure
- * @return bool success
+ * @return bool Success
*/
- public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
}
diff --git a/includes/objectcache/BagOStuff.php b/includes/objectcache/BagOStuff.php
index 857943ee..1978c3ea 100644
--- a/includes/objectcache/BagOStuff.php
+++ b/includes/objectcache/BagOStuff.php
@@ -3,7 +3,7 @@
* Classes to cache objects in PHP accelerators, SQL database or DBA files
*
* Copyright © 2003-2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -43,8 +43,16 @@
abstract class BagOStuff {
private $debugMode = false;
+ protected $lastError = self::ERR_NONE;
+
+ /** Possible values for getLastError() */
+ const ERR_NONE = 0; // no error
+ const ERR_NO_RESPONSE = 1; // no response
+ const ERR_UNREACHABLE = 2; // can't connect
+ const ERR_UNEXPECTED = 3; // response gave some error
+
/**
- * @param $bool bool
+ * @param bool $bool
*/
public function setDebug( $bool ) {
$this->debugMode = $bool;
@@ -55,34 +63,34 @@ abstract class BagOStuff {
/**
* Get an item with the given key. Returns false if it does not exist.
- * @param $key string
- * @param $casToken[optional] mixed
+ * @param string $key
+ * @param mixed $casToken [optional]
* @return mixed Returns false on failure
*/
abstract public function get( $key, &$casToken = null );
/**
* Set an item.
- * @param $key string
- * @param $value mixed
+ * @param string $key
+ * @param mixed $value
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
- * @return bool success
+ * @return bool Success
*/
abstract public function set( $key, $value, $exptime = 0 );
/**
* Check and set an item.
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
+ * @param mixed $casToken
+ * @param string $key
+ * @param mixed $value
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
- * @return bool success
+ * @return bool Success
*/
abstract public function cas( $casToken, $key, $value, $exptime = 0 );
/**
* Delete an item.
- * @param $key string
+ * @param string $key
* @param int $time Amount of time to delay the operation (mostly memcached-specific)
* @return bool True if the item was deleted or not found, false on failure
*/
@@ -93,26 +101,26 @@ abstract class BagOStuff {
* The callback function returns the new value given the current value (possibly false),
* and takes the arguments: (this BagOStuff object, cache key, current value).
*
- * @param $key string
- * @param $callback closure Callback method to be executed
+ * @param string $key
+ * @param Closure $callback Callback method to be executed
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @param int $attempts The amount of times to attempt a merge in case of failure
- * @return bool success
+ * @return bool Success
*/
- public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
}
/**
* @see BagOStuff::merge()
*
- * @param $key string
- * @param $callback closure Callback method to be executed
+ * @param string $key
+ * @param Closure $callback Callback method to be executed
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @param int $attempts The amount of times to attempt a merge in case of failure
- * @return bool success
+ * @return bool Success
*/
- protected function mergeViaCas( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ protected function mergeViaCas( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
do {
$casToken = null; // passed by reference
$currentValue = $this->get( $key, $casToken ); // get the old value
@@ -135,14 +143,14 @@ abstract class BagOStuff {
/**
* @see BagOStuff::merge()
*
- * @param $key string
- * @param $callback closure Callback method to be executed
+ * @param string $key
+ * @param Closure $callback Callback method to be executed
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @param int $attempts The amount of times to attempt a merge in case of failure
- * @return bool success
+ * @return bool Success
*/
- protected function mergeViaLock( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
- if ( !$this->lock( $key, 60 ) ) {
+ protected function mergeViaLock( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
+ if ( !$this->lock( $key, 6 ) ) {
return false;
}
@@ -164,14 +172,17 @@ abstract class BagOStuff {
}
/**
- * @param $key string
- * @param $timeout integer [optional]
- * @return bool success
+ * @param string $key
+ * @param int $timeout [optional]
+ * @return bool Success
*/
- public function lock( $key, $timeout = 60 ) {
+ public function lock( $key, $timeout = 6 ) {
+ $this->clearLastError();
$timestamp = microtime( true ); // starting UNIX timestamp
if ( $this->add( "{$key}:lock", 1, $timeout ) ) {
return true;
+ } elseif ( $this->getLastError() ) {
+ return false;
}
$uRTT = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // estimate RTT (us)
@@ -186,15 +197,19 @@ abstract class BagOStuff {
$sleep *= 2;
}
usleep( $sleep ); // back off
+ $this->clearLastError();
$locked = $this->add( "{$key}:lock", 1, $timeout );
+ if ( $this->getLastError() ) {
+ return false;
+ }
} while ( !$locked );
return $locked;
}
/**
- * @param $key string
- * @return bool success
+ * @param string $key
+ * @return bool Success
*/
public function unlock( $key ) {
return $this->delete( "{$key}:lock" );
@@ -203,11 +218,11 @@ abstract class BagOStuff {
/**
* Delete all objects expiring before a certain date.
* @param string $date The reference date in MW format
- * @param $progressCallback callback|bool Optional, a function which will be called
+ * @param callable|bool $progressCallback Optional, a function which will be called
* regularly during long-running operations with the percentage progress
* as the first parameter.
*
- * @return bool on success, false if unimplemented
+ * @return bool Success, false if unimplemented
*/
public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
// stub
@@ -219,7 +234,7 @@ abstract class BagOStuff {
/**
* Get an associative array containing the item for each of the keys that have items.
* @param array $keys List of strings
- * @return Array
+ * @return array
*/
public function getMulti( array $keys ) {
$res = array();
@@ -233,36 +248,40 @@ abstract class BagOStuff {
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exptime integer
- * @return bool success
+ * Batch insertion
+ * @param array $data $key => $value assoc array
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @return bool Success
+ * @since 1.24
*/
- public function add( $key, $value, $exptime = 0 ) {
- if ( $this->get( $key ) === false ) {
- return $this->set( $key, $value, $exptime );
+ public function setMulti( array $data, $exptime = 0 ) {
+ $res = true;
+ foreach ( $data as $key => $value ) {
+ if ( !$this->set( $key, $value, $exptime ) ) {
+ $res = false;
+ }
}
- return false; // key already set
+ return $res;
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
- * @return bool success
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
+ * @return bool Success
*/
- public function replace( $key, $value, $exptime = 0 ) {
- if ( $this->get( $key ) !== false ) {
+ public function add( $key, $value, $exptime = 0 ) {
+ if ( $this->get( $key ) === false ) {
return $this->set( $key, $value, $exptime );
}
- return false; // key not already set
+ return false; // key already set
}
/**
* Increase stored value of $key by $value while preserving its TTL
* @param string $key Key to increase
- * @param $value Integer: Value to add to $key (Default 1)
- * @return integer|bool New value or false on failure
+ * @param int $value Value to add to $key (Default 1)
+ * @return int|bool New value or false on failure
*/
public function incr( $key, $value = 1 ) {
if ( !$this->lock( $key ) ) {
@@ -282,16 +301,59 @@ abstract class BagOStuff {
/**
* Decrease stored value of $key by $value while preserving its TTL
- * @param $key String
- * @param $value Integer
- * @return integer
+ * @param string $key
+ * @param int $value
+ * @return int
*/
public function decr( $key, $value = 1 ) {
return $this->incr( $key, - $value );
}
/**
- * @param $text string
+ * Increase stored value of $key by $value while preserving its TTL
+ *
+ * This will create the key with value $init and TTL $ttl if not present
+ *
+ * @param string $key
+ * @param int $ttl
+ * @param int $value
+ * @param int $init
+ * @return bool
+ * @since 1.24
+ */
+ public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
+ return $this->incr( $key, $value ) ||
+ $this->add( $key, (int)$init, $ttl ) || $this->incr( $key, $value );
+ }
+
+ /**
+ * Get the "last error" registered; clearLastError() should be called manually
+ * @return int ERR_* constant for the "last error" registry
+ * @since 1.23
+ */
+ public function getLastError() {
+ return $this->lastError;
+ }
+
+ /**
+ * Clear the "last error" registry
+ * @since 1.23
+ */
+ public function clearLastError() {
+ $this->lastError = self::ERR_NONE;
+ }
+
+ /**
+ * Set the "last error" registry
+ * @param int $err ERR_* constant
+ * @since 1.23
+ */
+ protected function setLastError( $err ) {
+ $this->lastError = $err;
+ }
+
+ /**
+ * @param string $text
*/
public function debug( $text ) {
if ( $this->debugMode ) {
@@ -302,7 +364,7 @@ abstract class BagOStuff {
/**
* Convert an optionally relative time to an absolute time
- * @param $exptime integer
+ * @param int $exptime
* @return int
*/
protected function convertExpiry( $exptime ) {
@@ -317,8 +379,8 @@ abstract class BagOStuff {
* Convert an optionally absolute expiry time to a relative time. If an
* absolute time is specified which is in the past, use a short expiry time.
*
- * @param $exptime integer
- * @return integer
+ * @param int $exptime
+ * @return int
*/
protected function convertToRelative( $exptime ) {
if ( $exptime >= 86400 * 3650 /* 10 years */ ) {
@@ -335,7 +397,7 @@ abstract class BagOStuff {
/**
* Check if a value is an integer
*
- * @param $value mixed
+ * @param mixed $value
* @return bool
*/
protected function isInteger( $value ) {
diff --git a/includes/objectcache/DBABagOStuff.php b/includes/objectcache/DBABagOStuff.php
deleted file mode 100644
index c82b3aa4..00000000
--- a/includes/objectcache/DBABagOStuff.php
+++ /dev/null
@@ -1,307 +0,0 @@
-<?php
-/**
- * Object caching using DBA 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 Cache
- */
-
-/**
- * Cache that uses DBA as a backend.
- * Slow due to the need to constantly open and close the file to avoid holding
- * writer locks. Intended for development use only, as a memcached workalike
- * for systems that don't have it.
- *
- * On construction you can pass array( 'dir' => '/some/path' ); as a parameter
- * to override the default DBA files directory (wfTempDir()).
- *
- * @ingroup Cache
- */
-class DBABagOStuff extends BagOStuff {
- var $mHandler, $mFile, $mReader, $mWriter, $mDisabled;
-
- /**
- * @param $params array
- */
- public function __construct( $params ) {
- global $wgDBAhandler;
-
- if ( !isset( $params['dir'] ) ) {
- $params['dir'] = wfTempDir();
- }
-
- $this->mFile = $params['dir'] . '/mw-cache-' . wfWikiID() . '.db';
- wfDebug( __CLASS__ . ": using cache file {$this->mFile}\n" );
- $this->mHandler = $wgDBAhandler;
- }
-
- /**
- * Encode value and expiry for storage
- * @param $value
- * @param $expiry
- *
- * @return string
- */
- protected function encode( $value, $expiry ) {
- # Convert to absolute time
- $expiry = $this->convertExpiry( $expiry );
-
- return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value );
- }
-
- /**
- * @param $blob string
- * @return array list containing value first and expiry second
- */
- protected function decode( $blob ) {
- if ( !is_string( $blob ) ) {
- return array( false, 0 );
- } else {
- return array(
- unserialize( substr( $blob, 11 ) ),
- intval( substr( $blob, 0, 10 ) )
- );
- }
- }
-
- /**
- * @return resource
- */
- protected function getReader() {
- if ( file_exists( $this->mFile ) ) {
- $handle = dba_open( $this->mFile, 'rl', $this->mHandler );
- } else {
- $handle = $this->getWriter();
- }
-
- if ( !$handle ) {
- wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
- }
-
- return $handle;
- }
-
- /**
- * @return resource
- */
- protected function getWriter() {
- $handle = dba_open( $this->mFile, 'cl', $this->mHandler );
-
- if ( !$handle ) {
- wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
- }
-
- return $handle;
- }
-
- /**
- * @param $key string
- * @param $casToken[optional] mixed
- * @return mixed
- */
- public function get( $key, &$casToken = null ) {
- wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__ . "($key)\n" );
-
- $handle = $this->getReader();
- if ( !$handle ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $val = dba_fetch( $key, $handle );
- list( $val, $expiry ) = $this->decode( $val );
-
- # Must close ASAP because locks are held
- dba_close( $handle );
-
- if ( $val !== false && $expiry && $expiry < time() ) {
- # Key is expired, delete it
- $handle = $this->getWriter();
- dba_delete( $key, $handle );
- dba_close( $handle );
- wfDebug( __METHOD__ . ": $key expired\n" );
- $val = false;
- }
-
- $casToken = $val;
-
- wfProfileOut( __METHOD__ );
-
- return $val;
- }
-
- /**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
- * @return bool
- */
- public function set( $key, $value, $exptime = 0 ) {
- wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__ . "($key)\n" );
-
- $blob = $this->encode( $value, $exptime );
-
- $handle = $this->getWriter();
- if ( !$handle ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $ret = dba_replace( $key, $blob, $handle );
- dba_close( $handle );
-
- wfProfileOut( __METHOD__ );
- return $ret;
- }
-
- /**
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
- * @param $exptime int
- * @return bool
- */
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
- wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__ . "($key)\n" );
-
- $blob = $this->encode( $value, $exptime );
-
- $handle = $this->getWriter();
- if ( !$handle ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- // DBA is locked to any other write connection, so we can safely
- // compare the current & previous value before saving new value
- $val = dba_fetch( $key, $handle );
- list( $val, $exptime ) = $this->decode( $val );
- if ( $casToken !== $val ) {
- dba_close( $handle );
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $ret = dba_replace( $key, $blob, $handle );
- dba_close( $handle );
-
- wfProfileOut( __METHOD__ );
- return $ret;
- }
-
- /**
- * @param $key string
- * @param $time int
- * @return bool
- */
- public function delete( $key, $time = 0 ) {
- wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__ . "($key)\n" );
-
- $handle = $this->getWriter();
- if ( !$handle ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $ret = !dba_exists( $key, $handle ) || dba_delete( $key, $handle );
- dba_close( $handle );
-
- wfProfileOut( __METHOD__ );
- return $ret;
- }
-
- /**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
- * @return bool
- */
- public function add( $key, $value, $exptime = 0 ) {
- wfProfileIn( __METHOD__ );
-
- $blob = $this->encode( $value, $exptime );
-
- $handle = $this->getWriter();
-
- if ( !$handle ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $ret = dba_insert( $key, $blob, $handle );
-
- # Insert failed, check to see if it failed due to an expired key
- if ( !$ret ) {
- list( , $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
-
- if ( $expiry && $expiry < time() ) {
- # Yes expired, delete and try again
- dba_delete( $key, $handle );
- $ret = dba_insert( $key, $blob, $handle );
- # This time if it failed then it will be handled by the caller like any other race
- }
- }
-
- dba_close( $handle );
-
- wfProfileOut( __METHOD__ );
- return $ret;
- }
-
- /**
- * @param $key string
- * @param $step integer
- * @return integer|bool
- */
- public function incr( $key, $step = 1 ) {
- wfProfileIn( __METHOD__ );
-
- $handle = $this->getWriter();
-
- if ( !$handle ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
- if ( $value !== false ) {
- if ( $expiry && $expiry < time() ) {
- # Key is expired, delete it
- dba_delete( $key, $handle );
- wfDebug( __METHOD__ . ": $key expired\n" );
- $value = false;
- } else {
- $value += $step;
- $blob = $this->encode( $value, $expiry );
-
- $ret = dba_replace( $key, $blob, $handle );
- $value = $ret ? $value : false;
- }
- }
-
- dba_close( $handle );
-
- wfProfileOut( __METHOD__ );
-
- return ( $value === false ) ? false : (int)$value;
- }
-}
diff --git a/includes/objectcache/EhcacheBagOStuff.php b/includes/objectcache/EhcacheBagOStuff.php
deleted file mode 100644
index 960668f5..00000000
--- a/includes/objectcache/EhcacheBagOStuff.php
+++ /dev/null
@@ -1,324 +0,0 @@
-<?php
-/**
- * Object caching using the Ehcache RESTful web service.
- *
- * 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 Cache
- */
-
-/**
- * Client for the Ehcache RESTful web service - http://ehcache.org/documentation/cache_server.html
- * TODO: Simplify configuration and add to the installer.
- *
- * @ingroup Cache
- */
-class EhcacheBagOStuff extends BagOStuff {
- var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions,
- $requestData, $requestDataPos;
-
- var $curls = array();
-
- /**
- * @param $params array
- * @throws MWException
- */
- function __construct( $params ) {
- if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
- throw new MWException( __CLASS__ . ' requires curl version 7.16.2 or later.' );
- }
- if ( !extension_loaded( 'zlib' ) ) {
- throw new MWException( __CLASS__ . ' requires the zlib extension' );
- }
- if ( !isset( $params['servers'] ) ) {
- throw new MWException( __METHOD__ . ': servers parameter is required' );
- }
- $this->servers = $params['servers'];
- $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw';
- $this->connectTimeout = isset( $params['connectTimeout'] )
- ? $params['connectTimeout'] : 1;
- $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1;
- $this->curlOptions = array(
- CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ),
- CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ),
- CURLOPT_RETURNTRANSFER => 1,
- CURLOPT_CUSTOMREQUEST => 'GET',
- CURLOPT_POST => 0,
- CURLOPT_POSTFIELDS => '',
- CURLOPT_HTTPHEADER => array(),
- );
- }
-
- /**
- * @param $key string
- * @param $casToken[optional] mixed
- * @return bool|mixed
- */
- public function get( $key, &$casToken = null ) {
- wfProfileIn( __METHOD__ );
- $response = $this->doItemRequest( $key );
- if ( !$response || $response['http_code'] == 404 ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- if ( $response['http_code'] >= 300 ) {
- wfDebug( __METHOD__ . ": GET failure, got HTTP {$response['http_code']}\n" );
- wfProfileOut( __METHOD__ );
- return false;
- }
- $body = $response['body'];
- $type = $response['content_type'];
- if ( $type == 'application/vnd.php.serialized+deflate' ) {
- $body = gzinflate( $body );
- if ( !$body ) {
- wfDebug( __METHOD__ . ": error inflating $key\n" );
- wfProfileOut( __METHOD__ );
- return false;
- }
- $data = unserialize( $body );
- } elseif ( $type == 'application/vnd.php.serialized' ) {
- $data = unserialize( $body );
- } else {
- wfDebug( __METHOD__ . ": unknown content type \"$type\"\n" );
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $casToken = $body;
-
- wfProfileOut( __METHOD__ );
- return $data;
- }
-
- /**
- * @param $key string
- * @param $value mixed
- * @param $expiry int
- * @return bool
- */
- public function set( $key, $value, $expiry = 0 ) {
- wfProfileIn( __METHOD__ );
- $expiry = $this->convertExpiry( $expiry );
- $ttl = $expiry ? $expiry - time() : 2147483647;
- $blob = serialize( $value );
- if ( strlen( $blob ) > 100 ) {
- $blob = gzdeflate( $blob );
- $contentType = 'application/vnd.php.serialized+deflate';
- } else {
- $contentType = 'application/vnd.php.serialized';
- }
-
- $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
-
- if ( $code == 404 ) {
- // Maybe the cache does not exist yet, let's try creating it
- if ( !$this->createCache( $key ) ) {
- wfDebug( __METHOD__ . ": cache creation failed\n" );
- wfProfileOut( __METHOD__ );
- return false;
- }
- $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
- }
-
- $result = false;
- if ( !$code ) {
- wfDebug( __METHOD__ . ": PUT failure for key $key\n" );
- } elseif ( $code >= 300 ) {
- wfDebug( __METHOD__ . ": PUT failure for key $key: HTTP $code\n" );
- } else {
- $result = true;
- }
-
- wfProfileOut( __METHOD__ );
- return $result;
- }
-
- /**
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
- * @param $exptime int
- * @return bool
- */
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
- // Not sure if we can implement CAS for ehcache. There appears to be CAS-support per
- // http://ehcache.org/documentation/get-started/consistency-options#cas-cache-operations,
- // but I can't find any docs for our current implementation.
- throw new MWException( "CAS is not implemented in " . __CLASS__ );
- }
-
- /**
- * @param $key string
- * @param $time int
- * @return bool
- */
- public function delete( $key, $time = 0 ) {
- wfProfileIn( __METHOD__ );
- $response = $this->doItemRequest( $key,
- array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) );
- $code = isset( $response['http_code'] ) ? $response['http_code'] : 0;
- if ( !$response || ( $code != 404 && $code >= 300 ) ) {
- wfDebug( __METHOD__ . ": DELETE failure for key $key\n" );
- $result = false;
- } else {
- $result = true;
- }
- wfProfileOut( __METHOD__ );
- return $result;
- }
-
- /**
- * @see BagOStuff::merge()
- * @return bool success
- */
- public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
- return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
- }
-
- /**
- * @param $key string
- * @return string
- */
- protected function getCacheUrl( $key ) {
- if ( count( $this->servers ) == 1 ) {
- $server = reset( $this->servers );
- } else {
- // Use consistent hashing
- $hashes = array();
- foreach ( $this->servers as $server ) {
- $hashes[$server] = md5( $server . '/' . $key );
- }
- asort( $hashes );
- reset( $hashes );
- $server = key( $hashes );
- }
- return "http://$server/ehcache/rest/{$this->cacheName}";
- }
-
- /**
- * Get a cURL handle for the given cache URL.
- * We cache the handles to allow keepalive.
- */
- protected function getCurl( $cacheUrl ) {
- if ( !isset( $this->curls[$cacheUrl] ) ) {
- $this->curls[$cacheUrl] = curl_init();
- }
- return $this->curls[$cacheUrl];
- }
-
- /**
- * @param $key string
- * @param $data
- * @param $type
- * @param $ttl
- * @return int
- */
- protected function attemptPut( $key, $data, $type, $ttl ) {
- // In initial benchmarking, it was 30 times faster to use CURLOPT_POST
- // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because
- // CURLOPT_UPLOAD was pushing the request headers first, then waiting
- // for an ACK packet, then sending the data, whereas CURLOPT_POST just
- // sends the headers and the data in a single send().
- $response = $this->doItemRequest( $key,
- array(
- CURLOPT_POST => 1,
- CURLOPT_CUSTOMREQUEST => 'PUT',
- CURLOPT_POSTFIELDS => $data,
- CURLOPT_HTTPHEADER => array(
- 'Content-Type: ' . $type,
- 'ehcacheTimeToLiveSeconds: ' . $ttl
- )
- )
- );
- if ( !$response ) {
- return 0;
- } else {
- return $response['http_code'];
- }
- }
-
- /**
- * @param $key string
- * @return bool
- */
- protected function createCache( $key ) {
- wfDebug( __METHOD__ . ": creating cache for $key\n" );
- $response = $this->doCacheRequest( $key,
- array(
- CURLOPT_POST => 1,
- CURLOPT_CUSTOMREQUEST => 'PUT',
- CURLOPT_POSTFIELDS => '',
- ) );
- if ( !$response ) {
- wfDebug( __CLASS__ . ": failed to create cache for $key\n" );
- return false;
- }
- return ( $response['http_code'] == 201 /* created */
- || $response['http_code'] == 409 /* already there */ );
- }
-
- /**
- * @param $key string
- * @param $curlOptions array
- * @return array|bool|mixed
- */
- protected function doCacheRequest( $key, $curlOptions = array() ) {
- $cacheUrl = $this->getCacheUrl( $key );
- $curl = $this->getCurl( $cacheUrl );
- return $this->doRequest( $curl, $cacheUrl, $curlOptions );
- }
-
- /**
- * @param $key string
- * @param $curlOptions array
- * @return array|bool|mixed
- */
- protected function doItemRequest( $key, $curlOptions = array() ) {
- $cacheUrl = $this->getCacheUrl( $key );
- $curl = $this->getCurl( $cacheUrl );
- $url = $cacheUrl . '/' . rawurlencode( $key );
- return $this->doRequest( $curl, $url, $curlOptions );
- }
-
- /**
- * @param $curl
- * @param $url string
- * @param $curlOptions array
- * @return array|bool|mixed
- * @throws MWException
- */
- protected function doRequest( $curl, $url, $curlOptions = array() ) {
- if ( array_diff_key( $curlOptions, $this->curlOptions ) ) {
- // var_dump( array_diff_key( $curlOptions, $this->curlOptions ) );
- throw new MWException( __METHOD__ . ": to prevent options set in one doRequest() " .
- "call from affecting subsequent doRequest() calls, only options listed " .
- "in \$this->curlOptions may be specified in the \$curlOptions parameter." );
- }
- $curlOptions += $this->curlOptions;
- $curlOptions[CURLOPT_URL] = $url;
-
- curl_setopt_array( $curl, $curlOptions );
- $result = curl_exec( $curl );
- if ( $result === false ) {
- wfDebug( __CLASS__ . ": curl error: " . curl_error( $curl ) . "\n" );
- return false;
- }
- $info = curl_getinfo( $curl );
- $info['body'] = $result;
- return $info;
- }
-}
diff --git a/includes/objectcache/EmptyBagOStuff.php b/includes/objectcache/EmptyBagOStuff.php
index 62060579..9595b83c 100644
--- a/includes/objectcache/EmptyBagOStuff.php
+++ b/includes/objectcache/EmptyBagOStuff.php
@@ -29,8 +29,8 @@
class EmptyBagOStuff extends BagOStuff {
/**
- * @param $key string
- * @param $casToken[optional] mixed
+ * @param string $key
+ * @param mixed $casToken [optional]
* @return bool
*/
function get( $key, &$casToken = null ) {
@@ -38,9 +38,9 @@ class EmptyBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exp int
+ * @param string $key
+ * @param mixed $value
+ * @param int $exp
* @return bool
*/
function set( $key, $value, $exp = 0 ) {
@@ -48,10 +48,10 @@ class EmptyBagOStuff extends BagOStuff {
}
/**
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
- * @param $exp int
+ * @param mixed $casToken
+ * @param string $key
+ * @param mixed $value
+ * @param int $exp
* @return bool
*/
function cas( $casToken, $key, $value, $exp = 0 ) {
@@ -59,8 +59,8 @@ class EmptyBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $time int
+ * @param string $key
+ * @param int $time
* @return bool
*/
function delete( $key, $time = 0 ) {
@@ -68,20 +68,13 @@ class EmptyBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $callback closure Callback method to be executed
+ * @param string $key
+ * @param Closure $callback Callback method to be executed
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @param int $attempts The amount of times to attempt a merge in case of failure
- * @return bool success
+ * @return bool Success
*/
- public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
return true;
}
}
-
-/**
- * Backwards compatibility alias for EmptyBagOStuff
- * @deprecated since 1.18
- */
-class FakeMemCachedClient extends EmptyBagOStuff {
-}
diff --git a/includes/objectcache/HashBagOStuff.php b/includes/objectcache/HashBagOStuff.php
index d061eff0..6e50a8c3 100644
--- a/includes/objectcache/HashBagOStuff.php
+++ b/includes/objectcache/HashBagOStuff.php
@@ -28,14 +28,15 @@
* @ingroup Cache
*/
class HashBagOStuff extends BagOStuff {
- var $bag;
+ /** @var array */
+ protected $bag;
function __construct() {
$this->bag = array();
}
/**
- * @param $key string
+ * @param string $key
* @return bool
*/
protected function expire( $key ) {
@@ -51,8 +52,8 @@ class HashBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $casToken[optional] mixed
+ * @param string $key
+ * @param mixed $casToken [optional]
* @return bool|mixed
*/
function get( $key, &$casToken = null ) {
@@ -64,15 +65,15 @@ class HashBagOStuff extends BagOStuff {
return false;
}
- $casToken = $this->bag[$key][0];
+ $casToken = serialize( $this->bag[$key][0] );
return $this->bag[$key][0];
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
function set( $key, $value, $exptime = 0 ) {
@@ -81,14 +82,14 @@ class HashBagOStuff extends BagOStuff {
}
/**
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param mixed $casToken
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
function cas( $casToken, $key, $value, $exptime = 0 ) {
- if ( $this->get( $key ) === $casToken ) {
+ if ( serialize( $this->get( $key ) ) === $casToken ) {
return $this->set( $key, $value, $exptime );
}
@@ -96,8 +97,8 @@ class HashBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $time int
+ * @param string $key
+ * @param int $time
* @return bool
*/
function delete( $key, $time = 0 ) {
diff --git a/includes/objectcache/MemcachedBagOStuff.php b/includes/objectcache/MemcachedBagOStuff.php
index f1644edb..53edcdde 100644
--- a/includes/objectcache/MemcachedBagOStuff.php
+++ b/includes/objectcache/MemcachedBagOStuff.php
@@ -32,6 +32,8 @@ class MemcachedBagOStuff extends BagOStuff {
/**
* Fill in the defaults for any parameters missing from $params, using the
* backwards-compatible global variables
+ * @param array $params
+ * @return array
*/
protected function applyDefaultParams( $params ) {
if ( !isset( $params['servers'] ) ) {
@@ -56,18 +58,18 @@ class MemcachedBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $casToken[optional] mixed
- * @return Mixed
+ * @param string $key
+ * @param mixed $casToken [optional]
+ * @return mixed
*/
public function get( $key, &$casToken = null ) {
return $this->client->get( $this->encodeKey( $key ), $casToken );
}
/**
- * @param $key string
- * @param $value
- * @param $exptime int
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function set( $key, $value, $exptime = 0 ) {
@@ -76,10 +78,10 @@ class MemcachedBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $casToken mixed
- * @param $value
- * @param $exptime int
+ * @param mixed $casToken
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function cas( $casToken, $key, $value, $exptime = 0 ) {
@@ -88,8 +90,8 @@ class MemcachedBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $time int
+ * @param string $key
+ * @param int $time
* @return bool
*/
public function delete( $key, $time = 0 ) {
@@ -97,10 +99,10 @@ class MemcachedBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value int
+ * @param string $key
+ * @param int $value
* @param int $exptime (default 0)
- * @return Mixed
+ * @return mixed
*/
public function add( $key, $value, $exptime = 0 ) {
return $this->client->add( $this->encodeKey( $key ), $value,
@@ -108,19 +110,9 @@ class MemcachedBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value int
- * @param $exptime
- * @return Mixed
- */
- public function replace( $key, $value, $exptime = 0 ) {
- return $this->client->replace( $this->encodeKey( $key ), $value,
- $this->fixExpiry( $exptime ) );
- }
-
- /**
* Get the underlying client object. This is provided for debugging
* purposes.
+ * @return BagOStuff
*/
public function getClient() {
return $this->client;
@@ -133,7 +125,7 @@ class MemcachedBagOStuff extends BagOStuff {
* the other control characters for compatibility with libmemcached
* verify_key. We leave other punctuation alone, to maximise backwards
* compatibility.
- * @param $key string
+ * @param string $key
* @return string
*/
public function encodeKey( $key ) {
@@ -142,7 +134,7 @@ class MemcachedBagOStuff extends BagOStuff {
}
/**
- * @param $m array
+ * @param array $m
* @return string
*/
protected function encodeKeyCallback( $m ) {
@@ -155,19 +147,21 @@ class MemcachedBagOStuff extends BagOStuff {
* discarded immediately because the expiry is in the past.
* Clamp expiries >30d at 30d, unless they're >=1e9 in which
* case they are likely to really be absolute (1e9 = 2011-09-09)
+ * @param int $expiry
+ * @return int
*/
function fixExpiry( $expiry ) {
if ( $expiry > 2592000 && $expiry < 1000000000 ) {
$expiry = 2592000;
}
- return $expiry;
+ return (int)$expiry;
}
/**
* Decode a key encoded with encodeKey(). This is provided as a convenience
* function for debugging.
*
- * @param $key string
+ * @param string $key
*
* @return string
*/
@@ -177,11 +171,9 @@ class MemcachedBagOStuff extends BagOStuff {
/**
* Send a debug message to the log
+ * @param string $text
*/
protected function debugLog( $text ) {
- if ( substr( $text, -1 ) !== "\n" ) {
- $text .= "\n";
- }
wfDebugLog( 'memcached', $text );
}
}
diff --git a/includes/objectcache/MemcachedClient.php b/includes/objectcache/MemcachedClient.php
index e5f60b55..41eebfb5 100644
--- a/includes/objectcache/MemcachedClient.php
+++ b/includes/objectcache/MemcachedClient.php
@@ -1,4 +1,5 @@
<?php
+// @codingStandardsIgnoreFile It's an external lib and it isn't. Let's not bother.
/**
* Memcached client for PHP.
*
@@ -102,10 +103,10 @@ class MWMemcached {
/**
* Command statistics
*
- * @var array
- * @access public
+ * @var array
+ * @access public
*/
- var $stats;
+ public $stats;
// }}}
// {{{ private
@@ -113,124 +114,124 @@ class MWMemcached {
/**
* Cached Sockets that are connected
*
- * @var array
- * @access private
+ * @var array
+ * @access private
*/
- var $_cache_sock;
+ public $_cache_sock;
/**
* Current debug status; 0 - none to 9 - profiling
*
- * @var boolean
- * @access private
+ * @var bool
+ * @access private
*/
- var $_debug;
+ public $_debug;
/**
* Dead hosts, assoc array, 'host'=>'unixtime when ok to check again'
*
- * @var array
- * @access private
+ * @var array
+ * @access private
*/
- var $_host_dead;
+ public $_host_dead;
/**
* Is compression available?
*
- * @var boolean
- * @access private
+ * @var bool
+ * @access private
*/
- var $_have_zlib;
+ public $_have_zlib;
/**
* Do we want to use compression?
*
- * @var boolean
- * @access private
+ * @var bool
+ * @access private
*/
- var $_compress_enable;
+ public $_compress_enable;
/**
* At how many bytes should we compress?
*
- * @var integer
- * @access private
+ * @var int
+ * @access private
*/
- var $_compress_threshold;
+ public $_compress_threshold;
/**
* Are we using persistent links?
*
- * @var boolean
- * @access private
+ * @var bool
+ * @access private
*/
- var $_persistent;
+ public $_persistent;
/**
* If only using one server; contains ip:port to connect to
*
- * @var string
- * @access private
+ * @var string
+ * @access private
*/
- var $_single_sock;
+ public $_single_sock;
/**
* Array containing ip:port or array(ip:port, weight)
*
- * @var array
- * @access private
+ * @var array
+ * @access private
*/
- var $_servers;
+ public $_servers;
/**
* Our bit buckets
*
- * @var array
- * @access private
+ * @var array
+ * @access private
*/
- var $_buckets;
+ public $_buckets;
/**
* Total # of bit buckets we have
*
- * @var integer
- * @access private
+ * @var int
+ * @access private
*/
- var $_bucketcount;
+ public $_bucketcount;
/**
* # of total servers we have
*
- * @var integer
- * @access private
+ * @var int
+ * @access private
*/
- var $_active;
+ public $_active;
/**
* Stream timeout in seconds. Applies for example to fread()
*
- * @var integer
- * @access private
+ * @var int
+ * @access private
*/
- var $_timeout_seconds;
+ public $_timeout_seconds;
/**
* Stream timeout in microseconds
*
- * @var integer
- * @access private
+ * @var int
+ * @access private
*/
- var $_timeout_microseconds;
+ public $_timeout_microseconds;
/**
* Connect timeout in seconds
*/
- var $_connect_timeout;
+ public $_connect_timeout;
/**
* Number of connection attempts for each server
*/
- var $_connect_attempts;
+ public $_connect_attempts;
// }}}
// }}}
@@ -243,7 +244,7 @@ class MWMemcached {
*
* @param array $args Associative array of settings
*
- * @return mixed
+ * @return mixed
*/
public function __construct( $args ) {
$this->set_servers( isset( $args['servers'] ) ? $args['servers'] : array() );
@@ -271,15 +272,15 @@ class MWMemcached {
* Adds a key/value to the memcache server if one isn't already set with
* that key
*
- * @param string $key key to set with data
- * @param $val Mixed: value to store
- * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * @param string $key Key to set with data
+ * @param mixed $val Value to store
+ * @param int $exp (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
* longer must be the timestamp of the time at which the mapping should expire. It
* is safe to use timestamps in all cases, regardless of expiration
* eg: strtotime("+3 hour")
*
- * @return Boolean
+ * @return bool
*/
public function add( $key, $val, $exp = 0 ) {
return $this->_set( 'add', $key, $val, $exp );
@@ -291,10 +292,10 @@ class MWMemcached {
/**
* Decrease a value stored on the memcache server
*
- * @param string $key key to decrease
- * @param $amt Integer: (optional) amount to decrease
+ * @param string $key Key to decrease
+ * @param int $amt (optional) amount to decrease
*
- * @return Mixed: FALSE on failure, value on success
+ * @return mixed False on failure, value on success
*/
public function decr( $key, $amt = 1 ) {
return $this->_incrdecr( 'decr', $key, $amt );
@@ -306,10 +307,10 @@ class MWMemcached {
/**
* Deletes a key from the server, optionally after $time
*
- * @param string $key key to delete
- * @param $time Integer: (optional) how long to wait before deleting
+ * @param string $key Key to delete
+ * @param int $time (optional) how long to wait before deleting
*
- * @return Boolean: TRUE on success, FALSE on failure
+ * @return bool True on success, false on failure
*/
public function delete( $key, $time = 0 ) {
if ( !$this->_active ) {
@@ -346,8 +347,8 @@ class MWMemcached {
}
/**
- * @param $key
- * @param $timeout int
+ * @param string $key
+ * @param int $timeout
* @return bool
*/
public function lock( $key, $timeout = 0 ) {
@@ -356,7 +357,7 @@ class MWMemcached {
}
/**
- * @param $key
+ * @param string $key
* @return bool
*/
public function unlock( $key ) {
@@ -384,7 +385,7 @@ class MWMemcached {
/**
* Enable / Disable compression
*
- * @param $enable Boolean: TRUE to enable, FALSE to disable
+ * @param bool $enable True to enable, false to disable
*/
public function enable_compress( $enable ) {
$this->_compress_enable = $enable;
@@ -407,9 +408,9 @@ class MWMemcached {
* Retrieves the value associated with the key from the memcache server
*
* @param array|string $key key to retrieve
- * @param $casToken[optional] Float
+ * @param float $casToken [optional]
*
- * @return Mixed
+ * @return mixed
*/
public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
@@ -418,6 +419,12 @@ class MWMemcached {
$this->_debugprint( "get($key)\n" );
}
+ if ( !is_array( $key ) && strval( $key ) === '' ) {
+ $this->_debugprint( "Skipping key which equals to an empty string" );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
if ( !$this->_active ) {
wfProfileOut( __METHOD__ );
return false;
@@ -466,9 +473,9 @@ class MWMemcached {
/**
* Get multiple keys from the server(s)
*
- * @param array $keys keys to retrieve
+ * @param array $keys Keys to retrieve
*
- * @return Array
+ * @return array
*/
public function get_multi( $keys ) {
if ( !$this->_active ) {
@@ -530,10 +537,10 @@ class MWMemcached {
/**
* Increments $key (optionally) by $amt
*
- * @param string $key key to increment
- * @param $amt Integer: (optional) amount to increment
+ * @param string $key Key to increment
+ * @param int $amt (optional) amount to increment
*
- * @return Integer: null if the key does not exist yet (this does NOT
+ * @return int|null Null if the key does not exist yet (this does NOT
* create new mappings if the key does not exist). If the key does
* exist, this returns the new value for that key.
*/
@@ -547,15 +554,15 @@ class MWMemcached {
/**
* Overwrites an existing value for key; only works if key is already set
*
- * @param string $key key to set value as
- * @param $value Mixed: value to store
- * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * @param string $key Key to set value as
+ * @param mixed $value Value to store
+ * @param int $exp (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
* longer must be the timestamp of the time at which the mapping should expire. It
* is safe to use timestamps in all cases, regardless of exipration
* eg: strtotime("+3 hour")
*
- * @return Boolean
+ * @return bool
*/
public function replace( $key, $value, $exp = 0 ) {
return $this->_set( 'replace', $key, $value, $exp );
@@ -568,10 +575,10 @@ class MWMemcached {
* Passes through $cmd to the memcache server connected by $sock; returns
* output as an array (null array if no output)
*
- * @param $sock Resource: socket to send command on
- * @param string $cmd command to run
+ * @param Resource $sock Socket to send command on
+ * @param string $cmd Command to run
*
- * @return Array: output array
+ * @return array Output array
*/
public function run_command( $sock, $cmd ) {
if ( !is_resource( $sock ) ) {
@@ -603,15 +610,15 @@ class MWMemcached {
* Unconditionally sets a key to a given value in the memcache. Returns true
* if set successfully.
*
- * @param string $key key to set value as
- * @param $value Mixed: value to set
- * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * @param string $key Key to set value as
+ * @param mixed $value Value to set
+ * @param int $exp (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
* longer must be the timestamp of the time at which the mapping should expire. It
* is safe to use timestamps in all cases, regardless of exipration
* eg: strtotime("+3 hour")
*
- * @return Boolean: TRUE on success
+ * @return bool True on success
*/
public function set( $key, $value, $exp = 0 ) {
return $this->_set( 'set', $key, $value, $exp );
@@ -624,16 +631,16 @@ class MWMemcached {
* Sets a key to a given value in the memcache if the current value still corresponds
* to a known, given value. Returns true if set successfully.
*
- * @param $casToken Float: current known value
- * @param string $key key to set value as
- * @param $value Mixed: value to set
- * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * @param float $casToken Current known value
+ * @param string $key Key to set value as
+ * @param mixed $value Value to set
+ * @param int $exp (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
* longer must be the timestamp of the time at which the mapping should expire. It
* is safe to use timestamps in all cases, regardless of exipration
* eg: strtotime("+3 hour")
*
- * @return Boolean: TRUE on success
+ * @return bool True on success
*/
public function cas( $casToken, $key, $value, $exp = 0 ) {
return $this->_set( 'cas', $key, $value, $exp, $casToken );
@@ -645,7 +652,7 @@ class MWMemcached {
/**
* Sets the compression threshold
*
- * @param $thresh Integer: threshold to compress if larger than
+ * @param int $thresh Threshold to compress if larger than
*/
public function set_compress_threshold( $thresh ) {
$this->_compress_threshold = $thresh;
@@ -657,9 +664,9 @@ class MWMemcached {
/**
* Sets the debug flag
*
- * @param $dbg Boolean: TRUE for debugging, FALSE otherwise
+ * @param bool $dbg True for debugging, false otherwise
*
- * @see MWMemcached::__construct
+ * @see MWMemcached::__construct
*/
public function set_debug( $dbg ) {
$this->_debug = $dbg;
@@ -671,9 +678,9 @@ class MWMemcached {
/**
* Sets the server list to distribute key gets and puts between
*
- * @param array $list of servers to connect to
+ * @param array $list Array of servers to connect to
*
- * @see MWMemcached::__construct()
+ * @see MWMemcached::__construct()
*/
public function set_servers( $list ) {
$this->_servers = $list;
@@ -690,8 +697,8 @@ class MWMemcached {
/**
* Sets the timeout for new connections
*
- * @param $seconds Integer: number of seconds
- * @param $microseconds Integer: number of microseconds
+ * @param int $seconds Number of seconds
+ * @param int $microseconds Number of microseconds
*/
public function set_timeout( $seconds, $microseconds ) {
$this->_timeout_seconds = $seconds;
@@ -706,9 +713,9 @@ class MWMemcached {
/**
* Close the specified socket
*
- * @param string $sock socket to close
+ * @param string $sock Socket to close
*
- * @access private
+ * @access private
*/
function _close_sock( $sock ) {
$host = array_search( $sock, $this->_cache_sock );
@@ -722,14 +729,14 @@ class MWMemcached {
/**
* Connects $sock to $host, timing out after $timeout
*
- * @param $sock Integer: socket to connect
+ * @param int $sock Socket to connect
* @param string $host Host:IP to connect to
*
- * @return boolean
- * @access private
+ * @return bool
+ * @access private
*/
function _connect_sock( &$sock, $host ) {
- list( $ip, $port ) = explode( ':', $host );
+ list( $ip, $port ) = preg_split( '/:(?=\d)/', $host );
$sock = false;
$timeout = $this->_connect_timeout;
$errno = $errstr = null;
@@ -765,9 +772,9 @@ class MWMemcached {
/**
* Marks a host as dead until 30-40 seconds in the future
*
- * @param string $sock socket to mark as dead
+ * @param string $sock Socket to mark as dead
*
- * @access private
+ * @access private
*/
function _dead_sock( $sock ) {
$host = array_search( $sock, $this->_cache_sock );
@@ -775,7 +782,7 @@ class MWMemcached {
}
/**
- * @param $host
+ * @param string $host
*/
function _dead_host( $host ) {
$parts = explode( ':', $host );
@@ -791,9 +798,9 @@ class MWMemcached {
/**
* get_sock
*
- * @param string $key key to retrieve value for;
+ * @param string $key Key to retrieve value for;
*
- * @return Mixed: resource on success, false on failure
+ * @return Resource|bool Resource on success, false on failure
* @access private
*/
function get_sock( $key ) {
@@ -840,9 +847,9 @@ class MWMemcached {
/**
* Creates a hash integer based on the $key
*
- * @param string $key key to hash
+ * @param string $key Key to hash
*
- * @return Integer: hash value
+ * @return int Hash value
* @access private
*/
function _hashfunc( $key ) {
@@ -858,11 +865,11 @@ class MWMemcached {
/**
* Perform increment/decriment on $key
*
- * @param string $cmd command to perform
- * @param string|array $key key to perform it on
- * @param $amt Integer amount to adjust
+ * @param string $cmd Command to perform
+ * @param string|array $key Key to perform it on
+ * @param int $amt Amount to adjust
*
- * @return Integer: new value of $key
+ * @return int New value of $key
* @access private
*/
function _incrdecr( $cmd, $key, $amt = 1 ) {
@@ -899,10 +906,10 @@ class MWMemcached {
/**
* Load items into $ret from $sock
*
- * @param $sock Resource: socket to read from
+ * @param Resource $sock Socket to read from
* @param array $ret returned values
- * @param $casToken[optional] Float
- * @return boolean True for success, false for failure
+ * @param float $casToken [optional]
+ * @return bool True for success, false for failure
*
* @access private
*/
@@ -985,17 +992,17 @@ class MWMemcached {
/**
* Performs the requested storage operation to the memcache server
*
- * @param string $cmd command to perform
- * @param string $key key to act on
- * @param $val Mixed: what we need to store
- * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * @param string $cmd Command to perform
+ * @param string $key Key to act on
+ * @param mixed $val What we need to store
+ * @param int $exp (optional) Expiration time. This can be a number of seconds
* to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
* longer must be the timestamp of the time at which the mapping should expire. It
* is safe to use timestamps in all cases, regardless of exipration
* eg: strtotime("+3 hour")
- * @param $casToken[optional] Float
+ * @param float $casToken [optional]
*
- * @return Boolean
+ * @return bool
* @access private
*/
function _set( $cmd, $key, $val, $exp, $casToken = null ) {
@@ -1026,9 +1033,9 @@ class MWMemcached {
$len = strlen( $val );
- if ( $this->_have_zlib && $this->_compress_enable &&
- $this->_compress_threshold && $len >= $this->_compress_threshold )
- {
+ if ( $this->_have_zlib && $this->_compress_enable
+ && $this->_compress_threshold && $len >= $this->_compress_threshold
+ ) {
$c_val = gzcompress( $val, 9 );
$c_len = strlen( $c_val );
@@ -1070,7 +1077,7 @@ class MWMemcached {
*
* @param string $host Host:IP to get socket for
*
- * @return Mixed: IO Stream or false
+ * @return Resource|bool IO Stream or false
* @access private
*/
function sock_to_host( $host ) {
@@ -1100,14 +1107,14 @@ class MWMemcached {
}
/**
- * @param $text string
+ * @param string $text
*/
function _debugprint( $text ) {
wfDebugLog( 'memcached', $text );
}
/**
- * @param $text string
+ * @param string $text
*/
function _error_log( $text ) {
wfDebugLog( 'memcached-serious', "Memcached error: $text" );
@@ -1116,8 +1123,8 @@ class MWMemcached {
/**
* Write to a stream. If there is an error, mark the socket dead.
*
- * @param $sock The socket
- * @param $buf The string to write
+ * @param Resource $sock The socket
+ * @param string $buf The string to write
* @return bool True on success, false on failure
*/
function _fwrite( $sock, $buf ) {
@@ -1143,6 +1150,9 @@ class MWMemcached {
/**
* Handle an I/O error. Mark the socket dead and log an error.
+ *
+ * @param Resource $sock
+ * @param string $msg
*/
function _handle_error( $sock, $msg ) {
$peer = stream_socket_get_name( $sock, true /** remote **/ );
@@ -1161,9 +1171,9 @@ class MWMemcached {
* Read the specified number of bytes from a stream. If there is an error,
* mark the socket dead.
*
- * @param $sock The socket
- * @param $len The number of bytes to read
- * @return The string on success, false on failure.
+ * @param Resource $sock The socket
+ * @param int $len The number of bytes to read
+ * @return string|bool The string on success, false on failure.
*/
function _fread( $sock, $len ) {
$buf = '';
@@ -1193,8 +1203,8 @@ class MWMemcached {
* Read a line from a stream. If there is an error, mark the socket dead.
* The \r\n line ending is stripped from the response.
*
- * @param $sock The socket
- * @return The string on success, false on failure
+ * @param Resource $sock The socket
+ * @return string|bool The string on success, false on failure
*/
function _fgets( $sock ) {
$result = fgets( $sock );
@@ -1223,7 +1233,7 @@ class MWMemcached {
/**
* Flush the read buffer of a stream
- * @param $f Resource
+ * @param Resource $f
*/
function _flush_read_buffer( $f ) {
if ( !is_resource( $f ) ) {
diff --git a/includes/objectcache/MemcachedPeclBagOStuff.php b/includes/objectcache/MemcachedPeclBagOStuff.php
index 0c3b228f..c853bcf4 100644
--- a/includes/objectcache/MemcachedPeclBagOStuff.php
+++ b/includes/objectcache/MemcachedPeclBagOStuff.php
@@ -42,6 +42,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* - serializer: May be either "php" or "igbinary". Igbinary produces more compact
* values, but serialization is much slower unless the php.ini option
* igbinary.compact_strings is off.
+ * @param array $params
*/
function __construct( $params ) {
$params = $this->applyDefaultParams( $params );
@@ -113,9 +114,9 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $key string
- * @param $casToken[optional] float
- * @return Mixed
+ * @param string $key
+ * @param float $casToken [optional]
+ * @return mixed
*/
public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
@@ -127,9 +128,9 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $key string
- * @param $value
- * @param $exptime int
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function set( $key, $value, $exptime = 0 ) {
@@ -138,10 +139,10 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $casToken float
- * @param $key string
- * @param $value
- * @param $exptime int
+ * @param float $casToken
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function cas( $casToken, $key, $value, $exptime = 0 ) {
@@ -150,8 +151,8 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $key string
- * @param $time int
+ * @param string $key
+ * @param int $time
* @return bool
*/
public function delete( $key, $time = 0 ) {
@@ -166,10 +167,10 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $key string
- * @param $value int
- * @param $exptime int
- * @return Mixed
+ * @param string $key
+ * @param int $value
+ * @param int $exptime
+ * @return mixed
*/
public function add( $key, $value, $exptime = 0 ) {
$this->debugLog( "add($key)" );
@@ -177,20 +178,9 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $key string
- * @param $value int
- * @param $exptime
- * @return Mixed
- */
- public function replace( $key, $value, $exptime = 0 ) {
- $this->debugLog( "replace($key)" );
- return $this->checkResult( $key, parent::replace( $key, $value, $exptime ) );
- }
-
- /**
- * @param $key string
- * @param $value int
- * @return Mixed
+ * @param string $key
+ * @param int $value
+ * @return mixed
*/
public function incr( $key, $value = 1 ) {
$this->debugLog( "incr($key)" );
@@ -199,9 +189,9 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $key string
- * @param $value int
- * @return Mixed
+ * @param string $key
+ * @param int $value
+ * @return mixed
*/
public function decr( $key, $value = 1 ) {
$this->debugLog( "decr($key)" );
@@ -217,8 +207,8 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* different.
*
* @param string $key The key used by the caller, or false if there wasn't one.
- * @param $result Mixed The return value
- * @return Mixed
+ * @param mixed $result The return value
+ * @return mixed
*/
protected function checkResult( $key, $result ) {
if ( $result !== false ) {
@@ -242,13 +232,14 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
$msg = "Memcached error: $msg";
}
wfDebugLog( 'memcached-serious', $msg );
+ $this->setLastError( BagOStuff::ERR_UNEXPECTED );
}
return $result;
}
/**
- * @param $keys Array
- * @return Array
+ * @param array $keys
+ * @return array
*/
public function getMulti( array $keys ) {
wfProfileIn( __METHOD__ );
@@ -256,11 +247,27 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
$callback = array( $this, 'encodeKey' );
$result = $this->client->getMulti( array_map( $callback, $keys ) );
wfProfileOut( __METHOD__ );
+ $result = $result ?: array(); // must be an array
return $this->checkResult( false, $result );
}
- /* NOTE: there is no cas() method here because it is currently not supported
- * by the BagOStuff interface and other BagOStuff subclasses, such as
- * SqlBagOStuff.
+ /**
+ * @param array $data
+ * @param int $exptime
+ * @return bool
*/
+ public function setMulti( array $data, $exptime = 0 ) {
+ wfProfileIn( __METHOD__ );
+ foreach ( $data as $key => $value ) {
+ $encKey = $this->encodeKey( $key );
+ if ( $encKey !== $key ) {
+ $data[$encKey] = $value;
+ unset( $data[$key] );
+ }
+ }
+ $this->debugLog( 'setMulti(' . implode( ', ', array_keys( $data ) ) . ')' );
+ $result = $this->client->setMulti( $data, $this->fixExpiry( $exptime ) );
+ wfProfileOut( __METHOD__ );
+ return $this->checkResult( false, $result );
+ }
}
diff --git a/includes/objectcache/MemcachedPhpBagOStuff.php b/includes/objectcache/MemcachedPhpBagOStuff.php
index 33a134c7..330d2b52 100644
--- a/includes/objectcache/MemcachedPhpBagOStuff.php
+++ b/includes/objectcache/MemcachedPhpBagOStuff.php
@@ -39,7 +39,7 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
* - timeout: The read timeout in microseconds
* - connect_timeout: The connect timeout in seconds
*
- * @param $params array
+ * @param array $params
*/
function __construct( $params ) {
$params = $this->applyDefaultParams( $params );
@@ -50,15 +50,15 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $debug bool
+ * @param bool $debug
*/
public function setDebug( $debug ) {
$this->client->set_debug( $debug );
}
/**
- * @param $keys Array
- * @return Array
+ * @param array $keys
+ * @return array
*/
public function getMulti( array $keys ) {
$callback = array( $this, 'encodeKey' );
@@ -66,8 +66,8 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $key
- * @param $timeout int
+ * @param string $key
+ * @param int $timeout
* @return bool
*/
public function lock( $key, $timeout = 0 ) {
@@ -75,26 +75,26 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
}
/**
- * @param $key string
- * @return Mixed
+ * @param string $key
+ * @return mixed
*/
public function unlock( $key ) {
return $this->client->unlock( $this->encodeKey( $key ) );
}
/**
- * @param $key string
- * @param $value int
- * @return Mixed
+ * @param string $key
+ * @param int $value
+ * @return mixed
*/
public function incr( $key, $value = 1 ) {
return $this->client->incr( $this->encodeKey( $key ), $value );
}
/**
- * @param $key string
- * @param $value int
- * @return Mixed
+ * @param string $key
+ * @param int $value
+ * @return mixed
*/
public function decr( $key, $value = 1 ) {
return $this->client->decr( $this->encodeKey( $key ), $value );
diff --git a/includes/objectcache/MultiWriteBagOStuff.php b/includes/objectcache/MultiWriteBagOStuff.php
index e550c0d0..6a691379 100644
--- a/includes/objectcache/MultiWriteBagOStuff.php
+++ b/includes/objectcache/MultiWriteBagOStuff.php
@@ -29,7 +29,8 @@
* @ingroup Cache
*/
class MultiWriteBagOStuff extends BagOStuff {
- var $caches;
+ /** @var array BagOStuff[] */
+ protected $caches;
/**
* Constructor. Parameters are:
@@ -38,7 +39,7 @@ class MultiWriteBagOStuff extends BagOStuff {
* structures, in the style required by $wgObjectCaches. See
* the documentation of $wgObjectCaches for more detail.
*
- * @param $params array
+ * @param array $params
* @throws MWException
*/
public function __construct( $params ) {
@@ -53,15 +54,15 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $debug bool
+ * @param bool $debug
*/
public function setDebug( $debug ) {
$this->doWrite( 'setDebug', $debug );
}
/**
- * @param $key string
- * @param $casToken[optional] mixed
+ * @param string $key
+ * @param mixed $casToken [optional]
* @return bool|mixed
*/
public function get( $key, &$casToken = null ) {
@@ -75,10 +76,10 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param mixed $casToken
+ * @param string $key
+ * @param mixed $value
+ * @param mixed $exptime
* @return bool
*/
public function cas( $casToken, $key, $value, $exptime = 0 ) {
@@ -86,9 +87,9 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function set( $key, $value, $exptime = 0 ) {
@@ -96,8 +97,8 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $time int
+ * @param string $key
+ * @param int $time
* @return bool
*/
public function delete( $key, $time = 0 ) {
@@ -105,9 +106,9 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function add( $key, $value, $exptime = 0 ) {
@@ -115,18 +116,8 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
- * @return bool
- */
- public function replace( $key, $value, $exptime = 0 ) {
- return $this->doWrite( 'replace', $key, $value, $exptime );
- }
-
- /**
- * @param $key string
- * @param $value int
+ * @param string $key
+ * @param int $value
* @return bool|null
*/
public function incr( $key, $value = 1 ) {
@@ -134,8 +125,8 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value int
+ * @param string $key
+ * @param int $value
* @return bool
*/
public function decr( $key, $value = 1 ) {
@@ -143,8 +134,8 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $timeout int
+ * @param string $key
+ * @param int $timeout
* @return bool
*/
public function lock( $key, $timeout = 0 ) {
@@ -157,7 +148,7 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $key string
+ * @param string $key
* @return bool
*/
public function unlock( $key ) {
@@ -169,18 +160,28 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $callback closure Callback method to be executed
+ * @param string $key
+ * @param Closure $callback Callback method to be executed
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @param int $attempts The amount of times to attempt a merge in case of failure
- * @return bool success
+ * @return bool Success
*/
- public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
return $this->doWrite( 'merge', $key, $callback, $exptime );
}
+ public function getLastError() {
+ return isset( $this->caches[0] ) ? $this->caches[0]->getLastError() : self::ERR_NONE;
+ }
+
+ public function clearLastError() {
+ if ( isset( $this->caches[0] ) ) {
+ $this->caches[0]->clearLastError();
+ }
+ }
+
/**
- * @param $method string
+ * @param string $method
* @return bool
*/
protected function doWrite( $method /*, ... */ ) {
@@ -200,8 +201,8 @@ class MultiWriteBagOStuff extends BagOStuff {
* Delete objects expiring before a certain date.
*
* Succeed if any of the child caches succeed.
- * @param $date string
- * @param $progressCallback bool|callback
+ * @param string $date
+ * @param bool|callable $progressCallback
* @return bool
*/
public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php
index 6c1433a9..633b34a2 100644
--- a/includes/objectcache/ObjectCache.php
+++ b/includes/objectcache/ObjectCache.php
@@ -27,12 +27,12 @@
* @ingroup Cache
*/
class ObjectCache {
- static $instances = array();
+ public static $instances = array();
/**
* Get a cached instance of the specified type of cache object.
*
- * @param $id string
+ * @param string $id
*
* @return BagOStuff
*/
@@ -56,7 +56,7 @@ class ObjectCache {
/**
* Create a new cache object of the specified type.
*
- * @param $id string
+ * @param string $id
*
* @throws MWException
* @return BagOStuff
@@ -75,7 +75,7 @@ class ObjectCache {
/**
* Create a new cache object from parameters
*
- * @param $params array
+ * @param array $params
*
* @throws MWException
* @return BagOStuff
@@ -87,8 +87,9 @@ class ObjectCache {
$class = $params['class'];
return new $class( $params );
} else {
- throw new MWException( "The definition of cache type \"" . print_r( $params, true ) . "\" lacks both " .
- "factory and class parameters." );
+ throw new MWException( "The definition of cache type \""
+ . print_r( $params, true ) . "\" lacks both "
+ . "factory and class parameters." );
}
}
@@ -101,7 +102,7 @@ class ObjectCache {
* be an alias to the configured cache choice for that.
* If no cache choice is configured (by default $wgMainCacheType is CACHE_NONE),
* then CACHE_ANYTHING will forward to CACHE_DB.
- * @param $params array
+ * @param array $params
* @return BagOStuff
*/
static function newAnything( $params ) {
@@ -118,11 +119,15 @@ class ObjectCache {
/**
* Factory function referenced from DefaultSettings.php for CACHE_ACCEL.
*
- * @param $params array
+ * This will look for any APC style server-local cache.
+ * A fallback cache can be specified if none is found.
+ *
+ * @param array $params
+ * @param int|string $fallback Fallback cache, e.g. (CACHE_NONE, "hash") (since 1.24)
* @throws MWException
* @return BagOStuff
*/
- static function newAccelerator( $params ) {
+ static function newAccelerator( $params, $fallback = null ) {
if ( function_exists( 'apc_fetch' ) ) {
$id = 'apc';
} elseif ( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
@@ -130,6 +135,9 @@ class ObjectCache {
} elseif ( function_exists( 'wincache_ucache_get' ) ) {
$id = 'wincache';
} else {
+ if ( $fallback ) {
+ return self::newFromId( $fallback );
+ }
throw new MWException( "CACHE_ACCEL requested but no suitable object " .
"cache is present. You may want to install APC." );
}
@@ -143,7 +151,7 @@ class ObjectCache {
* hashing scheme and a different interpretation of the flags bitfield, so
* switching between the two clients randomly would be disastrous.
*
- * @param $params array
+ * @param array $params
*
* @return MemcachedPhpBagOStuff
*/
diff --git a/includes/objectcache/ObjectCacheSessionHandler.php b/includes/objectcache/ObjectCacheSessionHandler.php
index 7cf960e7..cdf8da1e 100644
--- a/includes/objectcache/ObjectCacheSessionHandler.php
+++ b/includes/objectcache/ObjectCacheSessionHandler.php
@@ -49,6 +49,7 @@ class ObjectCacheSessionHandler {
/**
* Get the cache storage object to use for session storage
+ * @return ObjectCache
*/
static function getCache() {
global $wgSessionCacheType;
@@ -58,8 +59,8 @@ class ObjectCacheSessionHandler {
/**
* Get a cache key for the given session id.
*
- * @param string $id session id
- * @return String: cache key
+ * @param string $id Session id
+ * @return string Cache key
*/
static function getKey( $id ) {
return wfMemcKey( 'session', $id );
@@ -68,9 +69,9 @@ class ObjectCacheSessionHandler {
/**
* Callback when opening a session.
*
- * @param $save_path String: path used to store session files, unused
- * @param $session_name String: session name
- * @return Boolean: success
+ * @param string $save_path Path used to store session files, unused
+ * @param string $session_name Session name
+ * @return bool Success
*/
static function open( $save_path, $session_name ) {
return true;
@@ -80,7 +81,7 @@ class ObjectCacheSessionHandler {
* Callback when closing a session.
* NOP.
*
- * @return Boolean: success
+ * @return bool Success
*/
static function close() {
return true;
@@ -89,8 +90,8 @@ class ObjectCacheSessionHandler {
/**
* Callback when reading session data.
*
- * @param string $id session id
- * @return Mixed: session data
+ * @param string $id Session id
+ * @return mixed Session data
*/
static function read( $id ) {
$data = self::getCache()->get( self::getKey( $id ) );
@@ -103,9 +104,9 @@ class ObjectCacheSessionHandler {
/**
* Callback when writing session data.
*
- * @param string $id session id
- * @param $data Mixed: session data
- * @return Boolean: success
+ * @param string $id Session id
+ * @param mixed $data Session data
+ * @return bool Success
*/
static function write( $id, $data ) {
global $wgObjectCacheSessionExpiry;
@@ -116,8 +117,8 @@ class ObjectCacheSessionHandler {
/**
* Callback to destroy a session when calling session_destroy().
*
- * @param string $id session id
- * @return Boolean: success
+ * @param string $id Session id
+ * @return bool Success
*/
static function destroy( $id ) {
self::getCache()->delete( self::getKey( $id ) );
@@ -128,8 +129,8 @@ class ObjectCacheSessionHandler {
* Callback to execute garbage collection.
* NOP: Object caches perform garbage collection implicitly
*
- * @param $maxlifetime Integer: maximum session life time
- * @return Boolean: success
+ * @param int $maxlifetime Maximum session life time
+ * @return bool Success
*/
static function gc( $maxlifetime ) {
return true;
diff --git a/includes/objectcache/RedisBagOStuff.php b/includes/objectcache/RedisBagOStuff.php
index 135e0e83..ae8cc5b7 100644
--- a/includes/objectcache/RedisBagOStuff.php
+++ b/includes/objectcache/RedisBagOStuff.php
@@ -23,7 +23,7 @@
class RedisBagOStuff extends BagOStuff {
/** @var RedisConnectionPool */
protected $redisPool;
- /** @var Array List of server names */
+ /** @var array List of server names */
protected $servers;
/** @var bool */
protected $automaticFailover;
@@ -53,9 +53,10 @@ class RedisBagOStuff extends BagOStuff {
* consistent hashing algorithm). True by default. This has the
* potential to create consistency issues if a server is slow enough to
* flap, for example if it is in swap death.
+ * @param array $params
*/
function __construct( $params ) {
- $redisConf = array( 'serializer' => 'php' );
+ $redisConf = array( 'serializer' => 'none' ); // manage that in this class
foreach ( array( 'connectTimeout', 'persistent', 'password' ) as $opt ) {
if ( isset( $params[$opt] ) ) {
$redisConf[$opt] = $params[$opt];
@@ -72,96 +73,88 @@ class RedisBagOStuff extends BagOStuff {
}
public function get( $key, &$casToken = null ) {
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
+
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return false;
}
try {
- $result = $conn->get( $key );
+ $value = $conn->get( $key );
+ $casToken = $value;
+ $result = $this->unserialize( $value );
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $conn, $e );
+ $this->handleException( $conn, $e );
}
- $casToken = $result;
+
$this->logRequest( 'get', $key, $server, $result );
- wfProfileOut( __METHOD__ );
return $result;
}
public function set( $key, $value, $expiry = 0 ) {
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
+
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return false;
}
$expiry = $this->convertToRelative( $expiry );
try {
- if ( !$expiry ) {
- // No expiry, that is very different from zero expiry in Redis
- $result = $conn->set( $key, $value );
+ if ( $expiry ) {
+ $result = $conn->setex( $key, $expiry, $this->serialize( $value ) );
} else {
- $result = $conn->setex( $key, $expiry, $value );
+ // No expiry, that is very different from zero expiry in Redis
+ $result = $conn->set( $key, $this->serialize( $value ) );
}
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $conn, $e );
+ $this->handleException( $conn, $e );
}
$this->logRequest( 'set', $key, $server, $result );
- wfProfileOut( __METHOD__ );
return $result;
}
public function cas( $casToken, $key, $value, $expiry = 0 ) {
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
+
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return false;
}
$expiry = $this->convertToRelative( $expiry );
try {
$conn->watch( $key );
- if ( $this->get( $key ) !== $casToken ) {
- wfProfileOut( __METHOD__ );
+ if ( $this->serialize( $this->get( $key ) ) !== $casToken ) {
+ $conn->unwatch();
return false;
}
+ // multi()/exec() will fail atomically if the key changed since watch()
$conn->multi();
-
- if ( !$expiry ) {
- // No expiry, that is very different from zero expiry in Redis
- $conn->set( $key, $value );
+ if ( $expiry ) {
+ $conn->setex( $key, $expiry, $this->serialize( $value ) );
} else {
- $conn->setex( $key, $expiry, $value );
+ // No expiry, that is very different from zero expiry in Redis
+ $conn->set( $key, $this->serialize( $value ) );
}
-
- /*
- * multi()/exec() (transactional mode) allows multiple values to
- * be set/get at once and will return an array of results, in
- * the order they were set/get. In this case, we only set 1
- * value, which should (in case of success) result in true.
- */
$result = ( $conn->exec() == array( true ) );
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $conn, $e );
+ $this->handleException( $conn, $e );
}
$this->logRequest( 'cas', $key, $server, $result );
- wfProfileOut( __METHOD__ );
return $result;
}
public function delete( $key, $time = 0 ) {
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
+
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return false;
}
try {
@@ -170,15 +163,16 @@ class RedisBagOStuff extends BagOStuff {
$result = true;
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $conn, $e );
+ $this->handleException( $conn, $e );
}
+
$this->logRequest( 'delete', $key, $server, $result );
- wfProfileOut( __METHOD__ );
return $result;
}
public function getMulti( array $keys ) {
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
+
$batches = array();
$conns = array();
foreach ( $keys as $key ) {
@@ -204,78 +198,152 @@ class RedisBagOStuff extends BagOStuff {
}
foreach ( $batchResult as $i => $value ) {
if ( $value !== false ) {
- $result[$batchKeys[$i]] = $value;
+ $result[$batchKeys[$i]] = $this->unserialize( $value );
}
}
} catch ( RedisException $e ) {
- $this->handleException( $server, $conn, $e );
+ $this->handleException( $conn, $e );
}
}
$this->debug( "getMulti for " . count( $keys ) . " keys " .
"returned " . count( $result ) . " results" );
- wfProfileOut( __METHOD__ );
return $result;
}
+ /**
+ * @param array $data
+ * @param int $expiry
+ * @return bool
+ */
+ public function setMulti( array $data, $expiry = 0 ) {
+ $section = new ProfileSection( __METHOD__ );
+
+ $batches = array();
+ $conns = array();
+ foreach ( $data as $key => $value ) {
+ list( $server, $conn ) = $this->getConnection( $key );
+ if ( !$conn ) {
+ continue;
+ }
+ $conns[$server] = $conn;
+ $batches[$server][] = $key;
+ }
+
+ $expiry = $this->convertToRelative( $expiry );
+ $result = true;
+ foreach ( $batches as $server => $batchKeys ) {
+ $conn = $conns[$server];
+ try {
+ $conn->multi( Redis::PIPELINE );
+ foreach ( $batchKeys as $key ) {
+ if ( $expiry ) {
+ $conn->setex( $key, $expiry, $this->serialize( $data[$key] ) );
+ } else {
+ $conn->set( $key, $this->serialize( $data[$key] ) );
+ }
+ }
+ $batchResult = $conn->exec();
+ if ( $batchResult === false ) {
+ $this->debug( "setMulti request to $server failed" );
+ continue;
+ }
+ foreach ( $batchResult as $value ) {
+ if ( $value === false ) {
+ $result = false;
+ }
+ }
+ } catch ( RedisException $e ) {
+ $this->handleException( $server, $conn, $e );
+ $result = false;
+ }
+ }
+
+ return $result;
+ }
+
+
+
public function add( $key, $value, $expiry = 0 ) {
- wfProfileIn( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ );
+
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return false;
}
$expiry = $this->convertToRelative( $expiry );
try {
- $result = $conn->setnx( $key, $value );
- if ( $result && $expiry ) {
+ if ( $expiry ) {
+ $conn->multi();
+ $conn->setnx( $key, $this->serialize( $value ) );
$conn->expire( $key, $expiry );
+ $result = ( $conn->exec() == array( true, true ) );
+ } else {
+ $result = $conn->setnx( $key, $this->serialize( $value ) );
}
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $conn, $e );
+ $this->handleException( $conn, $e );
}
+
$this->logRequest( 'add', $key, $server, $result );
- wfProfileOut( __METHOD__ );
return $result;
}
/**
- * Non-atomic implementation of replace(). Could perhaps be done atomically
- * with WATCH or scripting, but this function is rarely used.
+ * Non-atomic implementation of incr().
+ *
+ * Probably all callers actually want incr() to atomically initialise
+ * values to zero if they don't exist, as provided by the Redis INCR
+ * command. But we are constrained by the memcached-like interface to
+ * return null in that case. Once the key exists, further increments are
+ * atomic.
+ * @param string $key Key to increase
+ * @param int $value Value to add to $key (Default 1)
+ * @return int|bool New value or false on failure
*/
- public function replace( $key, $value, $expiry = 0 ) {
- wfProfileIn( __METHOD__ );
+ public function incr( $key, $value = 1 ) {
+ $section = new ProfileSection( __METHOD__ );
+
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return false;
}
if ( !$conn->exists( $key ) ) {
- wfProfileOut( __METHOD__ );
- return false;
+ return null;
}
-
- $expiry = $this->convertToRelative( $expiry );
try {
- if ( !$expiry ) {
- $result = $conn->set( $key, $value );
- } else {
- $result = $conn->setex( $key, $expiry, $value );
- }
+ $result = $conn->incrBy( $key, $value );
} catch ( RedisException $e ) {
$result = false;
- $this->handleException( $server, $conn, $e );
+ $this->handleException( $conn, $e );
}
- $this->logRequest( 'replace', $key, $server, $result );
- wfProfileOut( __METHOD__ );
+ $this->logRequest( 'incr', $key, $server, $result );
return $result;
}
+ /**
+ * @param mixed $data
+ * @return string
+ */
+ protected function serialize( $data ) {
+ // Serialize anything but integers so INCR/DECR work
+ // Do not store integer-like strings as integers to avoid type confusion (bug 60563)
+ return is_int( $data ) ? $data : serialize( $data );
+ }
+
+ /**
+ * @param string $data
+ * @return mixed
+ */
+ protected function unserialize( $data ) {
+ return ctype_digit( $data ) ? intval( $data ) : unserialize( $data );
+ }
/**
* Get a Redis object with a connection suitable for fetching the specified key
- * @return Array (server, RedisConnRef) or (false, false)
+ * @param string $key
+ * @return array (server, RedisConnRef) or (false, false)
*/
protected function getConnection( $key ) {
if ( count( $this->servers ) === 1 ) {
@@ -294,14 +362,16 @@ class RedisBagOStuff extends BagOStuff {
return array( $server, $conn );
}
}
+ $this->setLastError( BagOStuff::ERR_UNREACHABLE );
return array( false, false );
}
/**
* Log a fatal error
+ * @param string $msg
*/
protected function logError( $msg ) {
- wfDebugLog( 'redis', "Redis error: $msg\n" );
+ wfDebugLog( 'redis', "Redis error: $msg" );
}
/**
@@ -309,13 +379,20 @@ class RedisBagOStuff extends BagOStuff {
* and protocol errors. Sometimes it also closes the connection, sometimes
* not. The safest response for us is to explicitly destroy the connection
* object and let it be reopened during the next request.
+ * @param RedisConnRef $conn
+ * @param Exception $e
*/
- protected function handleException( $server, RedisConnRef $conn, $e ) {
- $this->redisPool->handleException( $server, $conn, $e );
+ protected function handleException( RedisConnRef $conn, $e ) {
+ $this->setLastError( BagOStuff::ERR_UNEXPECTED );
+ $this->redisPool->handleError( $conn, $e );
}
/**
* Send information about a single request to the debug log
+ * @param string $method
+ * @param string $key
+ * @param string $server
+ * @param bool $result
*/
public function logRequest( $method, $key, $server, $result ) {
$this->debug( "$method $key on $server: " .
diff --git a/includes/objectcache/SqlBagOStuff.php b/includes/objectcache/SqlBagOStuff.php
index acf27036..58720790 100644
--- a/includes/objectcache/SqlBagOStuff.php
+++ b/includes/objectcache/SqlBagOStuff.php
@@ -27,22 +27,37 @@
* @ingroup Cache
*/
class SqlBagOStuff extends BagOStuff {
- /**
- * @var LoadBalancer
- */
- var $lb;
+ /** @var LoadBalancer */
+ protected $lb;
+
+ protected $serverInfos;
+
+ /** @var array */
+ protected $serverNames;
+
+ /** @var int */
+ protected $numServers;
+
+ /** @var array */
+ protected $conns;
+
+ /** @var int */
+ protected $lastExpireAll = 0;
+
+ /** @var int */
+ protected $purgePeriod = 100;
- var $serverInfos;
- var $serverNames;
- var $numServers;
- var $conns;
- var $lastExpireAll = 0;
- var $purgePeriod = 100;
- var $shards = 1;
- var $tableName = 'objectcache';
+ /** @var int */
+ protected $shards = 1;
- protected $connFailureTimes = array(); // UNIX timestamps
- protected $connFailureErrors = array(); // exceptions
+ /** @var string */
+ protected $tableName = 'objectcache';
+
+ /** @var array UNIX timestamps */
+ protected $connFailureTimes = array();
+
+ /** @var array Exceptions */
+ protected $connFailureErrors = array();
/**
* Constructor. Parameters are:
@@ -70,7 +85,7 @@ class SqlBagOStuff extends BagOStuff {
* distributed across all tables by key hash. This is for
* MySQL bugs 61735 and 61736.
*
- * @param $params array
+ * @param array $params
*/
public function __construct( $params ) {
if ( isset( $params['servers'] ) ) {
@@ -101,7 +116,7 @@ class SqlBagOStuff extends BagOStuff {
/**
* Get a connection to the specified database
*
- * @param $serverIndex integer
+ * @param int $serverIndex
* @return DatabaseBase
*/
protected function getDB( $serverIndex ) {
@@ -114,8 +129,8 @@ class SqlBagOStuff extends BagOStuff {
# Don't keep timing out trying to connect for each call if the DB is down
if ( isset( $this->connFailureErrors[$serverIndex] )
- && ( time() - $this->connFailureTimes[$serverIndex] ) < 60 )
- {
+ && ( time() - $this->connFailureTimes[$serverIndex] ) < 60
+ ) {
throw $this->connFailureErrors[$serverIndex];
}
@@ -155,8 +170,8 @@ class SqlBagOStuff extends BagOStuff {
/**
* Get the server index and table name for a given key
- * @param $key string
- * @return Array: server index and table name
+ * @param string $key
+ * @return array Server index and table name
*/
protected function getTableByKey( $key ) {
if ( $this->shards > 1 ) {
@@ -178,7 +193,7 @@ class SqlBagOStuff extends BagOStuff {
/**
* Get the table name for a given shard index
- * @param $index int
+ * @param int $index
* @return string
*/
protected function getTableNameByShard( $index ) {
@@ -192,8 +207,8 @@ class SqlBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $casToken[optional] mixed
+ * @param string $key
+ * @param mixed $casToken [optional]
* @return mixed
*/
public function get( $key, &$casToken = null ) {
@@ -206,8 +221,8 @@ class SqlBagOStuff extends BagOStuff {
}
/**
- * @param $keys array
- * @return Array
+ * @param array $keys
+ * @return array
*/
public function getMulti( array $keys ) {
$values = array(); // array of (key => value)
@@ -228,7 +243,15 @@ class SqlBagOStuff extends BagOStuff {
$res = $db->select( $tableName,
array( 'keyname', 'value', 'exptime' ),
array( 'keyname' => $tableKeys ),
- __METHOD__ );
+ __METHOD__,
+ // Approximate write-on-the-fly BagOStuff API via blocking.
+ // This approximation fails if a ROLLBACK happens (which is rare).
+ // We do not want to flush the TRX as that can break callers.
+ $db->trxLevel() ? array( 'LOCK IN SHARE MODE' ) : array()
+ );
+ if ( $res === false ) {
+ continue;
+ }
foreach ( $res as $row ) {
$row->serverIndex = $serverIndex;
$row->tableName = $tableName;
@@ -248,14 +271,11 @@ class SqlBagOStuff extends BagOStuff {
$db = $this->getDB( $row->serverIndex );
if ( $this->isExpired( $db, $row->exptime ) ) { // MISS
$this->debug( "get: key has expired, deleting" );
- $db->begin( __METHOD__ );
# Put the expiry time in the WHERE condition to avoid deleting a
# newly-inserted value
$db->delete( $row->tableName,
array( 'keyname' => $key, 'exptime' => $row->exptime ),
__METHOD__ );
- $db->commit( __METHOD__ );
- $values[$key] = false;
} else { // HIT
$values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
}
@@ -263,7 +283,6 @@ class SqlBagOStuff extends BagOStuff {
$this->handleWriteError( $e, $row->serverIndex );
}
} else { // MISS
- $values[$key] = false;
$this->debug( 'get: no matching rows' );
}
}
@@ -272,9 +291,77 @@ class SqlBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param array $data
+ * @param int $expiry
+ * @return bool
+ */
+ public function setMulti( array $data, $expiry = 0 ) {
+ $keysByTable = array();
+ foreach ( $data as $key => $value ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ $keysByTable[$serverIndex][$tableName][] = $key;
+ }
+
+ $this->garbageCollect(); // expire old entries if any
+
+ $result = true;
+ $exptime = (int)$expiry;
+ foreach ( $keysByTable as $serverIndex => $serverKeys ) {
+ try {
+ $db = $this->getDB( $serverIndex );
+ } catch ( DBError $e ) {
+ $this->handleWriteError( $e, $serverIndex );
+ $result = false;
+ continue;
+ }
+
+ if ( $exptime < 0 ) {
+ $exptime = 0;
+ }
+
+ if ( $exptime == 0 ) {
+ $encExpiry = $this->getMaxDateTime( $db );
+ } else {
+ if ( $exptime < 3.16e8 ) { # ~10 years
+ $exptime += time();
+ }
+ $encExpiry = $db->timestamp( $exptime );
+ }
+ foreach ( $serverKeys as $tableName => $tableKeys ) {
+ $rows = array();
+ foreach ( $tableKeys as $key ) {
+ $rows[] = array(
+ 'keyname' => $key,
+ 'value' => $db->encodeBlob( $this->serialize( $data[$key] ) ),
+ 'exptime' => $encExpiry,
+ );
+ }
+
+ try {
+ $db->replace(
+ $tableName,
+ array( 'keyname' ),
+ $rows,
+ __METHOD__
+ );
+ } catch ( DBError $e ) {
+ $this->handleWriteError( $e, $serverIndex );
+ $result = false;
+ }
+
+ }
+
+ }
+
+ return $result;
+ }
+
+
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function set( $key, $value, $exptime = 0 ) {
@@ -296,7 +383,6 @@ class SqlBagOStuff extends BagOStuff {
$encExpiry = $db->timestamp( $exptime );
}
- $db->begin( __METHOD__ );
// (bug 24425) use a replace if the db supports it instead of
// delete/insert to avoid clashes with conflicting keynames
$db->replace(
@@ -307,7 +393,6 @@ class SqlBagOStuff extends BagOStuff {
'value' => $db->encodeBlob( $this->serialize( $value ) ),
'exptime' => $encExpiry
), __METHOD__ );
- $db->commit( __METHOD__ );
} catch ( DBError $e ) {
$this->handleWriteError( $e, $serverIndex );
return false;
@@ -317,10 +402,10 @@ class SqlBagOStuff extends BagOStuff {
}
/**
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param mixed $casToken
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function cas( $casToken, $key, $value, $exptime = 0 ) {
@@ -341,7 +426,6 @@ class SqlBagOStuff extends BagOStuff {
}
$encExpiry = $db->timestamp( $exptime );
}
- $db->begin( __METHOD__ );
// (bug 24425) use a replace if the db supports it instead of
// delete/insert to avoid clashes with conflicting keynames
$db->update(
@@ -357,7 +441,6 @@ class SqlBagOStuff extends BagOStuff {
),
__METHOD__
);
- $db->commit( __METHOD__ );
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e, $serverIndex );
@@ -368,20 +451,18 @@ class SqlBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $time int
+ * @param string $key
+ * @param int $time
* @return bool
*/
public function delete( $key, $time = 0 ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
try {
$db = $this->getDB( $serverIndex );
- $db->begin( __METHOD__ );
$db->delete(
$tableName,
array( 'keyname' => $key ),
__METHOD__ );
- $db->commit( __METHOD__ );
} catch ( DBError $e ) {
$this->handleWriteError( $e, $serverIndex );
return false;
@@ -391,8 +472,8 @@ class SqlBagOStuff extends BagOStuff {
}
/**
- * @param $key string
- * @param $step int
+ * @param string $key
+ * @param int $step
* @return int|null
*/
public function incr( $key, $step = 1 ) {
@@ -400,7 +481,6 @@ class SqlBagOStuff extends BagOStuff {
try {
$db = $this->getDB( $serverIndex );
$step = intval( $step );
- $db->begin( __METHOD__ );
$row = $db->selectRow(
$tableName,
array( 'value', 'exptime' ),
@@ -409,14 +489,12 @@ class SqlBagOStuff extends BagOStuff {
array( 'FOR UPDATE' ) );
if ( $row === false ) {
// Missing
- $db->commit( __METHOD__ );
return null;
}
$db->delete( $tableName, array( 'keyname' => $key ), __METHOD__ );
if ( $this->isExpired( $db, $row->exptime ) ) {
// Expired, do not reinsert
- $db->commit( __METHOD__ );
return null;
}
@@ -434,7 +512,6 @@ class SqlBagOStuff extends BagOStuff {
// Race condition. See bug 28611
$newValue = null;
}
- $db->commit( __METHOD__ );
} catch ( DBError $e ) {
$this->handleWriteError( $e, $serverIndex );
return null;
@@ -444,7 +521,8 @@ class SqlBagOStuff extends BagOStuff {
}
/**
- * @param $exptime string
+ * @param DatabaseBase $db
+ * @param string $exptime
* @return bool
*/
protected function isExpired( $db, $exptime ) {
@@ -452,6 +530,7 @@ class SqlBagOStuff extends BagOStuff {
}
/**
+ * @param DatabaseBase $db
* @return string
*/
protected function getMaxDateTime( $db ) {
@@ -485,8 +564,8 @@ class SqlBagOStuff extends BagOStuff {
/**
* Delete objects from the database which expire before a certain date.
- * @param $timestamp string
- * @param $progressCallback bool|callback
+ * @param string $timestamp
+ * @param bool|callable $progressCallback
* @return bool
*/
public function deleteObjectsExpiringBefore( $timestamp, $progressCallback = false ) {
@@ -509,7 +588,7 @@ class SqlBagOStuff extends BagOStuff {
$conds,
__METHOD__,
array( 'LIMIT' => 100, 'ORDER BY' => 'exptime' ) );
- if ( !$rows->numRows() ) {
+ if ( $rows === false || !$rows->numRows() ) {
break;
}
$keys = array();
@@ -524,7 +603,6 @@ class SqlBagOStuff extends BagOStuff {
$maxExpTime = $row->exptime;
}
- $db->begin( __METHOD__ );
$db->delete(
$this->getTableNameByShard( $i ),
array(
@@ -533,7 +611,6 @@ class SqlBagOStuff extends BagOStuff {
'keyname' => $keys
),
__METHOD__ );
- $db->commit( __METHOD__ );
if ( $progressCallback ) {
if ( intval( $totalSeconds ) === 0 ) {
@@ -566,9 +643,7 @@ class SqlBagOStuff extends BagOStuff {
try {
$db = $this->getDB( $serverIndex );
for ( $i = 0; $i < $this->shards; $i++ ) {
- $db->begin( __METHOD__ );
$db->delete( $this->getTableNameByShard( $i ), '*', __METHOD__ );
- $db->commit( __METHOD__ );
}
} catch ( DBError $e ) {
$this->handleWriteError( $e, $serverIndex );
@@ -583,7 +658,7 @@ class SqlBagOStuff extends BagOStuff {
* On typical message and page data, this can provide a 3X decrease
* in storage requirements.
*
- * @param $data mixed
+ * @param mixed $data
* @return string
*/
protected function serialize( &$data ) {
@@ -598,7 +673,7 @@ class SqlBagOStuff extends BagOStuff {
/**
* Unserialize and, if necessary, decompress an object.
- * @param $serial string
+ * @param string $serial
* @return mixed
*/
protected function unserialize( $serial ) {
@@ -619,6 +694,9 @@ class SqlBagOStuff extends BagOStuff {
/**
* Handle a DBError which occurred during a read operation.
+ *
+ * @param DBError $exception
+ * @param int $serverIndex
*/
protected function handleReadError( DBError $exception, $serverIndex ) {
if ( $exception instanceof DBConnectionError ) {
@@ -626,14 +704,19 @@ class SqlBagOStuff extends BagOStuff {
}
wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
if ( $exception instanceof DBConnectionError ) {
+ $this->setLastError( BagOStuff::ERR_UNREACHABLE );
wfDebug( __METHOD__ . ": ignoring connection error\n" );
} else {
+ $this->setLastError( BagOStuff::ERR_UNEXPECTED );
wfDebug( __METHOD__ . ": ignoring query error\n" );
}
}
/**
* Handle a DBQueryError which occurred during a write operation.
+ *
+ * @param DBError $exception
+ * @param int $serverIndex
*/
protected function handleWriteError( DBError $exception, $serverIndex ) {
if ( $exception instanceof DBConnectionError ) {
@@ -642,18 +725,24 @@ class SqlBagOStuff extends BagOStuff {
if ( $exception->db && $exception->db->wasReadOnlyError() ) {
try {
$exception->db->rollback( __METHOD__ );
- } catch ( DBError $e ) {}
+ } catch ( DBError $e ) {
+ }
}
wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
if ( $exception instanceof DBConnectionError ) {
+ $this->setLastError( BagOStuff::ERR_UNREACHABLE );
wfDebug( __METHOD__ . ": ignoring connection error\n" );
} else {
+ $this->setLastError( BagOStuff::ERR_UNEXPECTED );
wfDebug( __METHOD__ . ": ignoring query error\n" );
}
}
/**
* Mark a server down due to a DBConnectionError exception
+ *
+ * @param DBError $exception
+ * @param int $serverIndex
*/
protected function markServerDown( $exception, $serverIndex ) {
if ( isset( $this->connFailureTimes[$serverIndex] ) ) {
@@ -677,19 +766,15 @@ class SqlBagOStuff extends BagOStuff {
public function createTables() {
for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
$db = $this->getDB( $serverIndex );
- if ( $db->getType() !== 'mysql'
- || version_compare( $db->getServerVersion(), '4.1.0', '<' ) )
- {
+ if ( $db->getType() !== 'mysql' ) {
throw new MWException( __METHOD__ . ' is not supported on this DB server' );
}
for ( $i = 0; $i < $this->shards; $i++ ) {
- $db->begin( __METHOD__ );
$db->query(
'CREATE TABLE ' . $db->tableName( $this->getTableNameByShard( $i ) ) .
' LIKE ' . $db->tableName( 'objectcache' ),
__METHOD__ );
- $db->commit( __METHOD__ );
}
}
}
@@ -698,4 +783,5 @@ class SqlBagOStuff extends BagOStuff {
/**
* Backwards compatibility alias
*/
-class MediaWikiBagOStuff extends SqlBagOStuff { }
+class MediaWikiBagOStuff extends SqlBagOStuff {
+}
diff --git a/includes/objectcache/WinCacheBagOStuff.php b/includes/objectcache/WinCacheBagOStuff.php
index 6d9b47ad..78a512ce 100644
--- a/includes/objectcache/WinCacheBagOStuff.php
+++ b/includes/objectcache/WinCacheBagOStuff.php
@@ -32,8 +32,8 @@ class WinCacheBagOStuff extends BagOStuff {
/**
* Get a value from the WinCache object cache
*
- * @param string $key cache key
- * @param $casToken[optional] int: cas token
+ * @param string $key Cache key
+ * @param int $casToken [optional] Cas token
* @return mixed
*/
public function get( $key, &$casToken = null ) {
@@ -51,9 +51,9 @@ class WinCacheBagOStuff extends BagOStuff {
/**
* Store a value in the WinCache object cache
*
- * @param string $key cache key
- * @param $value Mixed: object to store
- * @param int $expire expiration time
+ * @param string $key Cache key
+ * @param mixed $value Value to store
+ * @param int $expire Expiration time
* @return bool
*/
public function set( $key, $value, $expire = 0 ) {
@@ -67,10 +67,10 @@ class WinCacheBagOStuff extends BagOStuff {
/**
* Store a value in the WinCache object cache, race condition-safe
*
- * @param int $casToken cas token
- * @param string $key cache key
- * @param int $value object to store
- * @param int $exptime expiration time
+ * @param int $casToken Cas token
+ * @param string $key Cache key
+ * @param int $value Object to store
+ * @param int $exptime Expiration time
* @return bool
*/
public function cas( $casToken, $key, $value, $exptime = 0 ) {
@@ -80,8 +80,8 @@ class WinCacheBagOStuff extends BagOStuff {
/**
* Remove a value from the WinCache object cache
*
- * @param string $key cache key
- * @param int $time not used in this implementation
+ * @param string $key Cache key
+ * @param int $time Not used in this implementation
* @return bool
*/
public function delete( $key, $time = 0 ) {
diff --git a/includes/objectcache/XCacheBagOStuff.php b/includes/objectcache/XCacheBagOStuff.php
index 0f45db73..8e2a160d 100644
--- a/includes/objectcache/XCacheBagOStuff.php
+++ b/includes/objectcache/XCacheBagOStuff.php
@@ -31,8 +31,8 @@ class XCacheBagOStuff extends BagOStuff {
/**
* Get a value from the XCache object cache
*
- * @param string $key cache key
- * @param $casToken mixed: cas token
+ * @param string $key Cache key
+ * @param mixed $casToken Cas token
* @return mixed
*/
public function get( $key, &$casToken = null ) {
@@ -54,9 +54,9 @@ class XCacheBagOStuff extends BagOStuff {
/**
* Store a value in the XCache object cache
*
- * @param string $key cache key
- * @param $value Mixed: object to store
- * @param int $expire expiration time
+ * @param string $key Cache key
+ * @param mixed $value Object to store
+ * @param int $expire Expiration time
* @return bool
*/
public function set( $key, $value, $expire = 0 ) {
@@ -69,10 +69,10 @@ class XCacheBagOStuff extends BagOStuff {
}
/**
- * @param $casToken mixed
- * @param $key string
- * @param $value mixed
- * @param $exptime int
+ * @param mixed $casToken
+ * @param string $key
+ * @param mixed $value
+ * @param int $exptime
* @return bool
*/
public function cas( $casToken, $key, $value, $exptime = 0 ) {
@@ -83,8 +83,8 @@ class XCacheBagOStuff extends BagOStuff {
/**
* Remove a value from the XCache object cache
*
- * @param string $key cache key
- * @param int $time not used in this implementation
+ * @param string $key Cache key
+ * @param int $time Not used in this implementation
* @return bool
*/
public function delete( $key, $time = 0 ) {
@@ -97,13 +97,13 @@ class XCacheBagOStuff extends BagOStuff {
* XCache does not seem to support any way of performing CAS - this however will
* provide a way to perform CAS-like functionality.
*
- * @param $key string
- * @param $callback closure Callback method to be executed
+ * @param string $key
+ * @param Closure $callback Callback method to be executed
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @param int $attempts The amount of times to attempt a merge in case of failure
- * @return bool success
+ * @return bool Success
*/
- public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
}
diff --git a/includes/Article.php b/includes/page/Article.php
index 0b18221a..4e753817 100644
--- a/includes/Article.php
+++ b/includes/page/Article.php
@@ -34,90 +34,52 @@
* @internal documentation reviewed 15 Mar 2010
*/
class Article implements Page {
- /**@{{
- * @private
- */
-
- /**
- * The context this Article is executed in
- * @var IContextSource $mContext
- */
+ /** @var IContextSource The context this Article is executed in */
protected $mContext;
- /**
- * The WikiPage object of this instance
- * @var WikiPage $mPage
- */
+ /** @var WikiPage The WikiPage object of this instance */
protected $mPage;
- /**
- * ParserOptions object for $wgUser articles
- * @var ParserOptions $mParserOptions
- */
+ /** @var ParserOptions ParserOptions object for $wgUser articles */
public $mParserOptions;
/**
- * Text of the revision we are working on
- * @var string $mContent
+ * @var string Text of the revision we are working on
+ * @todo BC cruft
*/
- var $mContent; // !< #BC cruft
+ public $mContent;
/**
- * Content of the revision we are working on
- * @var Content
+ * @var Content Content of the revision we are working on
* @since 1.21
*/
- var $mContentObject; // !<
+ public $mContentObject;
- /**
- * Is the content ($mContent) already loaded?
- * @var bool $mContentLoaded
- */
- var $mContentLoaded = false; // !<
+ /** @var bool Is the content ($mContent) already loaded? */
+ public $mContentLoaded = false;
- /**
- * The oldid of the article that is to be shown, 0 for the
- * current revision
- * @var int|null $mOldId
- */
- var $mOldId; // !<
+ /** @var int|null The oldid of the article that is to be shown, 0 for the current revision */
+ public $mOldId;
- /**
- * Title from which we were redirected here
- * @var Title $mRedirectedFrom
- */
- var $mRedirectedFrom = null;
+ /** @var Title Title from which we were redirected here */
+ public $mRedirectedFrom = null;
- /**
- * URL to redirect to or false if none
- * @var string|false $mRedirectUrl
- */
- var $mRedirectUrl = false; // !<
+ /** @var string|bool URL to redirect to or false if none */
+ public $mRedirectUrl = false;
- /**
- * Revision ID of revision we are working on
- * @var int $mRevIdFetched
- */
- var $mRevIdFetched = 0; // !<
+ /** @var int Revision ID of revision we are working on */
+ public $mRevIdFetched = 0;
- /**
- * Revision we are working on
- * @var Revision $mRevision
- */
- var $mRevision = null;
+ /** @var Revision Revision we are working on */
+ public $mRevision = null;
- /**
- * ParserOutput object
- * @var ParserOutput $mParserOutput
- */
- var $mParserOutput;
-
- /**@}}*/
+ /** @var ParserOutput */
+ public $mParserOutput;
/**
* Constructor and clear the article
- * @param $title Title Reference to a Title object.
- * @param $oldId Integer revision ID, null to fetch from request, zero for current
+ * @param Title $title Reference to a Title object.
+ * @param int $oldId Revision ID, null to fetch from request, zero for current
*/
public function __construct( Title $title, $oldId = null ) {
$this->mOldId = $oldId;
@@ -125,7 +87,7 @@ class Article implements Page {
}
/**
- * @param $title Title
+ * @param Title $title
* @return WikiPage
*/
protected function newPage( Title $title ) {
@@ -134,7 +96,7 @@ class Article implements Page {
/**
* Constructor from a page id
- * @param int $id article ID to load
+ * @param int $id Article ID to load
* @return Article|null
*/
public static function newFromID( $id ) {
@@ -147,9 +109,9 @@ class Article implements Page {
/**
* Create an Article object of the appropriate class for the given page.
*
- * @param $title Title
- * @param $context IContextSource
- * @return Article object
+ * @param Title $title
+ * @param IContextSource $context
+ * @return Article
*/
public static function newFromTitle( $title, IContextSource $context ) {
if ( NS_MEDIA == $title->getNamespace() ) {
@@ -158,7 +120,7 @@ class Article implements Page {
}
$page = null;
- wfRunHooks( 'ArticleFromTitle', array( &$title, &$page ) );
+ wfRunHooks( 'ArticleFromTitle', array( &$title, &$page, $context ) );
if ( !$page ) {
switch ( $title->getNamespace() ) {
case NS_FILE:
@@ -179,9 +141,9 @@ class Article implements Page {
/**
* Create an Article object of the appropriate class for the given page.
*
- * @param $page WikiPage
- * @param $context IContextSource
- * @return Article object
+ * @param WikiPage $page
+ * @param IContextSource $context
+ * @return Article
*/
public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
$article = self::newFromTitle( $page->getTitle(), $context );
@@ -192,7 +154,7 @@ class Article implements Page {
/**
* Tell the page view functions that this view was redirected
* from another page on the wiki.
- * @param $from Title object.
+ * @param Title $from
*/
public function setRedirectedFrom( Title $from ) {
$this->mRedirectedFrom = $from;
@@ -201,7 +163,7 @@ class Article implements Page {
/**
* Get the title object of the article
*
- * @return Title object of this page
+ * @return Title Title object of this page
*/
public function getTitle() {
return $this->mPage->getTitle();
@@ -238,7 +200,7 @@ class Article implements Page {
* This function has side effects! Do not use this function if you
* only want the real revision text if any.
*
- * @deprecated in 1.21; use WikiPage::getContent() instead
+ * @deprecated since 1.21; use WikiPage::getContent() instead
*
* @return string Return the text of this revision
*/
@@ -290,8 +252,7 @@ class Article implements Page {
}
/**
- * @return int The oldid of the article that is to be shown, 0 for the
- * current revision
+ * @return int The oldid of the article that is to be shown, 0 for the current revision
*/
public function getOldID() {
if ( is_null( $this->mOldId ) ) {
@@ -355,7 +316,7 @@ class Article implements Page {
/**
* Load the revision (including text) into this object
*
- * @deprecated in 1.19; use fetchContent()
+ * @deprecated since 1.19; use fetchContent()
*/
function loadContent() {
wfDeprecated( __METHOD__, '1.19' );
@@ -367,12 +328,14 @@ class Article implements Page {
* Does *NOT* follow redirects.
*
* @protected
- * @note this is really internal functionality that should really NOT be used by other functions. For accessing
- * article content, use the WikiPage class, especially WikiBase::getContent(). However, a lot of legacy code
- * uses this method to retrieve page text from the database, so the function has to remain public for now.
+ * @note This is really internal functionality that should really NOT be
+ * used by other functions. For accessing article content, use the WikiPage
+ * class, especially WikiBase::getContent(). However, a lot of legacy code
+ * uses this method to retrieve page text from the database, so the function
+ * has to remain public for now.
*
- * @return mixed string containing article contents, or false if null
- * @deprecated in 1.21, use WikiPage::getContent() instead
+ * @return string|bool String containing article contents, or false if null
+ * @deprecated since 1.21, use WikiPage::getContent() instead
*/
function fetchContent() { #BC cruft!
ContentHandler::deprecated( __METHOD__, '1.21' );
@@ -385,6 +348,11 @@ class Article implements Page {
$content = $this->fetchContentObject();
+ if ( !$content ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
// @todo Get rid of mContent everywhere!
$this->mContent = ContentHandler::getContentText( $content );
ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
@@ -397,11 +365,12 @@ class Article implements Page {
/**
* Get text content object
* Does *NOT* follow redirects.
- * TODO: when is this null?
+ * @todo When is this null?
*
- * @note code that wants to retrieve page content from the database should use WikiPage::getContent().
+ * @note Code that wants to retrieve page content from the database should
+ * use WikiPage::getContent().
*
- * @return Content|null|boolean false
+ * @return Content|null|bool
*
* @since 1.21
*/
@@ -434,7 +403,8 @@ class Article implements Page {
}
} else {
if ( !$this->mPage->getLatest() ) {
- wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
+ wfDebug( __METHOD__ . " failed to find page data for title " .
+ $this->getTitle()->getPrefixedText() . "\n" );
wfProfileOut( __METHOD__ );
return false;
}
@@ -442,7 +412,8 @@ class Article implements Page {
$this->mRevision = $this->mPage->getRevision();
if ( !$this->mRevision ) {
- wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
+ wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " .
+ $this->mPage->getLatest() . "\n" );
wfProfileOut( __METHOD__ );
return false;
}
@@ -450,7 +421,11 @@ class Article implements Page {
// @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
// We should instead work with the Revision object when we need it...
- $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed
+ // Loads if user is allowed
+ $this->mContentObject = $this->mRevision->getContent(
+ Revision::FOR_THIS_USER,
+ $this->getContext()->getUser()
+ );
$this->mRevIdFetched = $this->mRevision->getId();
wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
@@ -461,14 +436,6 @@ class Article implements Page {
}
/**
- * No-op
- * @deprecated since 1.18
- */
- public function forUpdate() {
- wfDeprecated( __METHOD__, '1.18' );
- }
-
- /**
* Returns true if the currently-referenced revision is the current edit
* to this page (and it exists).
* @return bool
@@ -498,7 +465,7 @@ class Article implements Page {
/**
* Use this to fetch the rev ID used on page views
*
- * @return int revision ID of last article revision
+ * @return int Revision ID of last article revision
*/
public function getRevIdFetched() {
if ( $this->mRevIdFetched ) {
@@ -513,7 +480,7 @@ class Article implements Page {
* page of the given title.
*/
public function view() {
- global $wgUseFileCache, $wgUseETag, $wgDebugToolbar;
+ global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects;
wfProfileIn( __METHOD__ );
@@ -575,8 +542,31 @@ class Article implements Page {
$outputPage->setETag( $parserCache->getETag( $this, $parserOptions ) );
}
+ # Use the greatest of the page's timestamp or the timestamp of any
+ # redirect in the chain (bug 67849)
+ $timestamp = $this->mPage->getTouched();
+ if ( isset( $this->mRedirectedFrom ) ) {
+ $timestamp = max( $timestamp, $this->mRedirectedFrom->getTouched() );
+
+ # If there can be more than one redirect in the chain, we have
+ # to go through the whole chain too in case an intermediate
+ # redirect was changed.
+ if ( $wgMaxRedirects > 1 ) {
+ $titles = Revision::newFromTitle( $this->mRedirectedFrom )
+ ->getContent( Revision::FOR_THIS_USER, $user )
+ ->getRedirectChain();
+ $thisTitle = $this->getTitle();
+ foreach ( $titles as $title ) {
+ if ( Title::compare( $title, $thisTitle ) === 0 ) {
+ break;
+ }
+ $timestamp = max( $timestamp, $title->getTouched() );
+ }
+ }
+ }
+
# Is it client cached?
- if ( $outputPage->checkLastModified( $this->mPage->getTouched() ) ) {
+ if ( $outputPage->checkLastModified( $timestamp ) ) {
wfDebug( __METHOD__ . ": done 304\n" );
wfProfileOut( __METHOD__ );
@@ -586,7 +576,7 @@ class Article implements Page {
wfDebug( __METHOD__ . ": done file cache\n" );
# tell wgOut that output is taken care of
$outputPage->disable();
- $this->mPage->doViewUpdates( $user );
+ $this->mPage->doViewUpdates( $user, $oldid );
wfProfileOut( __METHOD__ );
return;
@@ -619,6 +609,7 @@ class Article implements Page {
if ( !$this->mPage->exists() ) {
wfDebug( __METHOD__ . ": showing missing article\n" );
$this->showMissingArticle();
+ $this->mPage->doViewUpdates( $user );
wfProfileOut( __METHOD__ );
return;
}
@@ -684,26 +675,15 @@ class Article implements Page {
# Allow extensions do their own custom view for certain pages
$outputDone = true;
- } else {
- $content = $this->getContentObject();
- $rt = $content ? $content->getRedirectChain() : null;
- if ( $rt ) {
- wfDebug( __METHOD__ . ": showing redirect=no page\n" );
- # Viewing a redirect page (e.g. with parameter redirect=no)
- $outputPage->addHTML( $this->viewRedirect( $rt ) );
- # Parse just to get categories, displaytitle, etc.
- $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false );
- $outputPage->addParserOutputNoText( $this->mParserOutput );
- $outputDone = true;
- }
}
break;
case 4:
# Run the parse, protected by a pool counter
wfDebug( __METHOD__ . ": doing uncached parse\n" );
+ $content = $this->getContentObject();
$poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
- $this->getRevIdFetched(), $useParserCache, $this->getContentObject() );
+ $this->getRevIdFetched(), $useParserCache, $content );
if ( !$poolArticleView->execute() ) {
$error = $poolArticleView->getError();
@@ -722,11 +702,17 @@ class Article implements Page {
$this->mParserOutput = $poolArticleView->getParserOutput();
$outputPage->addParserOutput( $this->mParserOutput );
+ if ( $content->getRedirectTarget() ) {
+ $outputPage->addSubtitle(
+ "<span id=\"redirectsub\">" . wfMessage( 'redirectpagesub' )->parse() . "</span>"
+ );
+ }
# Don't cache a dirty ParserOutput object
if ( $poolArticleView->getIsDirty() ) {
$outputPage->setSquidMaxage( 0 );
- $outputPage->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
+ $outputPage->addHTML( "<!-- parser cache is expired, " .
+ "sending anyway due to pool overload-->\n" );
}
$outputDone = true;
@@ -765,7 +751,7 @@ class Article implements Page {
$outputPage->setFollowPolicy( $policy['follow'] );
$this->showViewFooter();
- $this->mPage->doViewUpdates( $user );
+ $this->mPage->doViewUpdates( $user, $oldid );
$outputPage->addModules( 'mediawiki.action.view.postEdit' );
@@ -774,7 +760,7 @@ class Article implements Page {
/**
* Adjust title for pages with displaytitle, -{T|}- or language conversion
- * @param $pOutput ParserOutput
+ * @param ParserOutput $pOutput
*/
public function adjustDisplayTitle( ParserOutput $pOutput ) {
# Adjust the title if it was set by displaytitle, -{T|}- or language conversion
@@ -809,26 +795,36 @@ class Article implements Page {
}
$contentHandler = $rev->getContentHandler();
- $de = $contentHandler->createDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+ $de = $contentHandler->createDifferenceEngine(
+ $this->getContext(),
+ $oldid,
+ $diff,
+ $rcid,
+ $purge,
+ $unhide
+ );
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage( $diffOnly );
- if ( $diff == 0 || $diff == $this->mPage->getLatest() ) {
- # Run view updates for current revision only
- $this->mPage->doViewUpdates( $user );
- }
+ // Run view updates for the newer revision being diffed (and shown
+ // below the diff if not $diffOnly).
+ list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
+ // New can be false, convert it to 0 - this conveniently means the latest revision
+ $this->mPage->doViewUpdates( $user, (int)$new );
}
/**
* Show a page view for a page formatted as CSS or JavaScript. To be called by
* Article::view() only.
*
- * This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these
- * page views.
+ * This exists mostly to serve the deprecated ShowRawCssJs hook (used to customize these views).
+ * It has been replaced by the ContentGetParserOutput hook, which lets you do the same but with
+ * more flexibility.
*
- * @param bool $showCacheHint whether to show a message telling the user to clear the browser cache (default: true).
+ * @param bool $showCacheHint Whether to show a message telling the user
+ * to clear the browser cache (default: true).
*/
protected function showCssOrJsPage( $showCacheHint = true ) {
$outputPage = $this->getContext()->getOutput();
@@ -837,27 +833,34 @@ class Article implements Page {
$dir = $this->getContext()->getLanguage()->getDir();
$lang = $this->getContext()->getLanguage()->getCode();
- $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
- 'clearyourcache' );
+ $outputPage->wrapWikiMsg(
+ "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
+ 'clearyourcache'
+ );
}
$this->fetchContentObject();
if ( $this->mContentObject ) {
// Give hooks a chance to customise the output
- if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mContentObject, $this->getTitle(), $outputPage ) ) ) {
+ if ( ContentHandler::runLegacyHooks(
+ 'ShowRawCssJs',
+ array( $this->mContentObject, $this->getTitle(), $outputPage ) )
+ ) {
+ // If no legacy hooks ran, display the content of the parser output, including RL modules,
+ // but excluding metadata like categories and language links
$po = $this->mContentObject->getParserOutput( $this->getTitle() );
- $outputPage->addHTML( $po->getText() );
+ $outputPage->addParserOutputContent( $po );
}
}
}
/**
* Get the robot policy to be used for the current view
- * @param string $action the action= GET parameter
- * @param $pOutput ParserOutput|null
- * @return Array the policy that should be set
- * TODO: actions other than 'view'
+ * @param string $action The action= GET parameter
+ * @param ParserOutput|null $pOutput
+ * @return array The policy that should be set
+ * @todo actions other than 'view'
*/
public function getRobotPolicy( $action, $pOutput = null ) {
global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
@@ -935,9 +938,9 @@ class Article implements Page {
/**
* Converts a String robot policy into an associative array, to allow
* merging of several policies using array_merge().
- * @param $policy Mixed, returns empty array on null/false/'', transparent
- * to already-converted arrays, converts String.
- * @return Array: 'index' => \<indexpolicy\>, 'follow' => \<followpolicy\>
+ * @param array|string $policy Returns empty array on null/false/'', transparent
+ * to already-converted arrays, converts string.
+ * @return array 'index' => \<indexpolicy\>, 'follow' => \<followpolicy\>
*/
public static function formatRobotPolicy( $policy ) {
if ( is_array( $policy ) ) {
@@ -966,13 +969,24 @@ class Article implements Page {
* the output. Returns true if the header was needed, false if this is not
* a redirect view. Handles both local and remote redirects.
*
- * @return boolean
+ * @return bool
*/
public function showRedirectedFromHeader() {
global $wgRedirectSources;
$outputPage = $this->getContext()->getOutput();
- $rdfrom = $this->getContext()->getRequest()->getVal( 'rdfrom' );
+ $request = $this->getContext()->getRequest();
+ $rdfrom = $request->getVal( 'rdfrom' );
+
+ // Construct a URL for the current page view, but with the target title
+ $query = $request->getValues();
+ unset( $query['rdfrom'] );
+ unset( $query['title'] );
+ if ( $this->getTitle()->isRedirect() ) {
+ // Prevent double redirects
+ $query['redirect'] = 'no';
+ }
+ $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
if ( isset( $this->mRedirectedFrom ) ) {
// This is an internally redirected page view.
@@ -987,12 +1001,12 @@ class Article implements Page {
$outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
- // Set the fragment if one was specified in the redirect
- if ( strval( $this->getTitle()->getFragment() ) != '' ) {
- $outputPage->addInlineScript( Xml::encodeJsCall(
- 'redirectToFragment', array( $this->getTitle()->getFragmentForURL() )
- ) );
- }
+ // Add the script to update the displayed URL and
+ // set the fragment if one was specified in the redirect
+ $outputPage->addJsConfigVars( array(
+ 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
+ ) );
+ $outputPage->addModules( 'mediawiki.action.view.redirect' );
// Add a <link rel="canonical"> tag
$outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() );
@@ -1009,6 +1023,12 @@ class Article implements Page {
$redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
$outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
+ // Add the script to update the displayed URL
+ $outputPage->addJsConfigVars( array(
+ 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
+ ) );
+ $outputPage->addModules( 'mediawiki.action.view.redirect' );
+
return true;
}
}
@@ -1023,7 +1043,10 @@ class Article implements Page {
public function showNamespaceHeader() {
if ( $this->getTitle()->isTalkPage() ) {
if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
- $this->getContext()->getOutput()->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1\n</div>", array( 'talkpageheader' ) );
+ $this->getContext()->getOutput()->wrapWikiMsg(
+ "<div class=\"mw-talkpageheader\">\n$1\n</div>",
+ array( 'talkpageheader' )
+ );
}
}
}
@@ -1033,7 +1056,9 @@ class Article implements Page {
*/
public function showViewFooter() {
# check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
- if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) {
+ if ( $this->getTitle()->getNamespace() == NS_USER_TALK
+ && IP::isValid( $this->getTitle()->getText() )
+ ) {
$this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
}
@@ -1041,7 +1066,6 @@ class Article implements Page {
$patrolFooterShown = $this->showPatrolFooter();
wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) );
-
}
/**
@@ -1061,7 +1085,9 @@ class Article implements Page {
$cache = wfGetMainCache();
$rc = false;
- if ( !$this->getTitle()->quickUserCan( 'patrol', $user ) || !( $wgUseRCPatrol || $wgUseNPPatrol ) ) {
+ if ( !$this->getTitle()->quickUserCan( 'patrol', $user )
+ || !( $wgUseRCPatrol || $wgUseNPPatrol )
+ ) {
// Patrolling is disabled or the user isn't allowed to
return false;
}
@@ -1080,7 +1106,9 @@ class Article implements Page {
return false;
}
- if ( $this->mRevision && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 ) ) {
+ if ( $this->mRevision
+ && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
+ ) {
// The current revision is already older than what could be in the RC table
// 6h tolerance because the RC might not be cleaned out regularly
wfProfileOut( __METHOD__ );
@@ -1095,7 +1123,9 @@ class Article implements Page {
__METHOD__
);
- if ( $oldestRevisionTimestamp && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 ) ) {
+ if ( $oldestRevisionTimestamp
+ && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
+ ) {
// 6h tolerance because the RC might not be cleaned out regularly
$rc = RecentChange::newFromConds(
array(
@@ -1164,25 +1194,31 @@ class Article implements Page {
*/
public function showMissingArticle() {
global $wgSend404Code;
+
$outputPage = $this->getContext()->getOutput();
// Whether the page is a root user page of an existing user (but not a subpage)
$validUserPage = false;
+ $title = $this->getTitle();
+
# Show info in user (talk) namespace. Does the user exist? Is he blocked?
- if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) {
- $parts = explode( '/', $this->getTitle()->getText() );
+ if ( $title->getNamespace() == NS_USER
+ || $title->getNamespace() == NS_USER_TALK
+ ) {
+ $parts = explode( '/', $title->getText() );
$rootPart = $parts[0];
$user = User::newFromName( $rootPart, false /* allow IP users*/ );
$ip = User::isIP( $rootPart );
+ $block = Block::newFromTarget( $user, $user );
if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
$outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) );
- } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
+ } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked
LogEventsList::showLogExtract(
$outputPage,
'block',
- $user->getUserPage(),
+ MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
'',
array(
'lim' => 1,
@@ -1193,21 +1229,30 @@ class Article implements Page {
)
)
);
- $validUserPage = !$this->getTitle()->isSubpage();
+ $validUserPage = !$title->isSubpage();
} else {
- $validUserPage = !$this->getTitle()->isSubpage();
+ $validUserPage = !$title->isSubpage();
}
}
wfRunHooks( 'ShowMissingArticle', array( $this ) );
+ // Give extensions a chance to hide their (unrelated) log entries
+ $logTypes = array( 'delete', 'move' );
+ $conds = array( "log_action != 'revision'" );
+ wfRunHooks( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) );
+
# Show delete and move logs
- LogEventsList::showLogExtract( $outputPage, array( 'delete', 'move' ), $this->getTitle(), '',
- array( 'lim' => 10,
- 'conds' => array( "log_action != 'revision'" ),
- 'showIfEmpty' => false,
- 'msgKey' => array( 'moveddeleted-notice' ) )
- );
+ $member = $title->getNamespace() . ':' . $title->getDBkey();
+ // @todo: move optimization to showLogExtract()?
+ if ( BloomCache::get( 'main' )->check( wfWikiId(), 'TitleHasLogs', $member ) ) {
+ LogEventsList::showLogExtract( $outputPage, $logTypes, $title, '',
+ array( 'lim' => 10,
+ 'conds' => $conds,
+ 'showIfEmpty' => false,
+ 'msgKey' => array( 'moveddeleted-notice' ) )
+ );
+ }
if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
// If there's no backing content, send a 404 Not Found
@@ -1215,16 +1260,14 @@ class Article implements Page {
$this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" );
}
- if ( $validUserPage ) {
- // Also apply the robot policy for nonexisting user pages (as those aren't served as 404)
- $policy = $this->getRobotPolicy( 'view' );
- $outputPage->setIndexPolicy( $policy['index'] );
- $outputPage->setFollowPolicy( $policy['follow'] );
- }
+ // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
+ $policy = $this->getRobotPolicy( 'view' );
+ $outputPage->setIndexPolicy( $policy['index'] );
+ $outputPage->setFollowPolicy( $policy['follow'] );
$hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
- if ( ! $hookResult ) {
+ if ( !$hookResult ) {
return;
}
@@ -1232,13 +1275,14 @@ class Article implements Page {
$oldid = $this->getOldID();
if ( $oldid ) {
$text = wfMessage( 'missing-revision', $oldid )->plain();
- } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) {
+ } elseif ( $title->getNamespace() === NS_MEDIAWIKI ) {
// Use the default message text
- $text = $this->getTitle()->getDefaultMessageText();
- } elseif ( $this->getTitle()->quickUserCan( 'create', $this->getContext()->getUser() )
- && $this->getTitle()->quickUserCan( 'edit', $this->getContext()->getUser() )
+ $text = $title->getDefaultMessageText();
+ } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
+ && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
) {
- $text = wfMessage( 'noarticletext' )->plain();
+ $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
+ $text = wfMessage( $message )->plain();
} else {
$text = wfMessage( 'noarticletext-nopermission' )->plain();
}
@@ -1251,7 +1295,7 @@ class Article implements Page {
* If the revision requested for view is deleted, check permissions.
* Send either an error message or a warning header to the output.
*
- * @return boolean true if the view is allowed, false if not.
+ * @return bool True if the view is allowed, false if not.
*/
public function showDeletedRevisionHeader() {
if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
@@ -1294,7 +1338,7 @@ class Article implements Page {
* Revision as of \<date\>; view current revision
* \<- Previous version | Next Version -\>
*
- * @param int $oldid revision ID of this article revision
+ * @param int $oldid Revision ID of this article revision
*/
public function setOldSubtitle( $oldid = 0 ) {
if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
@@ -1335,7 +1379,7 @@ class Article implements Page {
$outputPage = $this->getContext()->getOutput();
$outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
$td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
- $tdtime, $revision->getUser() )->parse() . "</div>" );
+ $tdtime, $revision->getUserText() )->rawParams( Linker::revComment( $revision, true, true ) )->parse() . "</div>" );
$lnk = $current
? wfMessage( 'currentrevisionlink' )->escaped()
@@ -1414,64 +1458,72 @@ class Article implements Page {
}
/**
- * View redirect
+ * Return the HTML for the top of a redirect page
*
- * @param $target Title|Array of destination(s) to redirect
- * @param $appendSubtitle Boolean [optional]
- * @param $forceKnown Boolean: should the image be shown as a bluelink regardless of existence?
- * @return string containing HMTL with redirect link
+ * Chances are you should just be using the ParserOutput from
+ * WikitextContent::getParserOutput instead of calling this for redirects.
+ *
+ * @param Title|array $target Destination(s) to redirect
+ * @param bool $appendSubtitle [optional]
+ * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
+ * @return string Containing HTML with redirect link
*/
public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
- global $wgStylePath;
-
- if ( !is_array( $target ) ) {
- $target = array( $target );
- }
-
$lang = $this->getTitle()->getPageLanguage();
- $imageDir = $lang->getDir();
-
+ $out = $this->getContext()->getOutput();
if ( $appendSubtitle ) {
- $out = $this->getContext()->getOutput();
- $out->addSubtitle( wfMessage( 'redirectpagesub' )->escaped() );
+ $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
}
+ $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
+ return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
+ }
- // the loop prepends the arrow image before the link, so the first case needs to be outside
-
- /**
- * @var $title Title
- */
- $title = array_shift( $target );
-
- if ( $forceKnown ) {
- $link = Linker::linkKnown( $title, htmlspecialchars( $title->getFullText() ) );
- } else {
- $link = Linker::link( $title, htmlspecialchars( $title->getFullText() ) );
+ /**
+ * Return the HTML for the top of a redirect page
+ *
+ * Chances are you should just be using the ParserOutput from
+ * WikitextContent::getParserOutput instead of calling this for redirects.
+ *
+ * @since 1.23
+ * @param Language $lang
+ * @param Title|array $target Destination(s) to redirect
+ * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
+ * @return string Containing HTML with redirect link
+ */
+ public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
+ if ( !is_array( $target ) ) {
+ $target = array( $target );
}
- $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
- $alt = $lang->isRTL() ? '←' : '→';
- // Automatically append redirect=no to each link, since most of them are redirect pages themselves.
- foreach ( $target as $rt ) {
- $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) );
- if ( $forceKnown ) {
- $link .= Linker::linkKnown( $rt, htmlspecialchars( $rt->getFullText(), array(), array( 'redirect' => 'no' ) ) );
- } else {
- $link .= Linker::link( $rt, htmlspecialchars( $rt->getFullText() ), array(), array( 'redirect' => 'no' ) );
- }
+ $html = '<ul class="redirectText">';
+ /** @var Title $title */
+ foreach ( $target as $title ) {
+ $html .= '<li>' . Linker::link(
+ $title,
+ htmlspecialchars( $title->getFullText() ),
+ array(),
+ // Automatically append redirect=no to each link, since most of them are
+ // redirect pages themselves.
+ array( 'redirect' => 'no' ),
+ ( $forceKnown ? array( 'known', 'noclasses' ) : array() )
+ ) . '</li>';
}
- $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
+ $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->text();
+
return '<div class="redirectMsg">' .
- Html::element( 'img', array( 'src' => $imageUrl, 'alt' => '#REDIRECT' ) ) .
- '<span class="redirectText">' . $link . '</span></div>';
+ '<p>' . $redirectToText . '</p>' .
+ $html .
+ '</div>';
}
/**
* Handle action=render
*/
public function render() {
+ $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
$this->getContext()->getOutput()->setArticleBodyOnly( true );
+ $this->getContext()->getOutput()->enableSectionEditLinks( false );
$this->view();
}
@@ -1500,9 +1552,9 @@ class Article implements Page {
$user = $this->getContext()->getUser();
# Check permissions
- $permission_errors = $title->getUserPermissionsErrors( 'delete', $user );
- if ( count( $permission_errors ) ) {
- throw new PermissionsError( 'delete', $permission_errors );
+ $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
+ if ( count( $permissionErrors ) ) {
+ throw new PermissionsError( 'delete', $permissionErrors );
}
# Read-only check...
@@ -1546,8 +1598,8 @@ class Article implements Page {
}
if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
- array( 'delete', $this->getTitle()->getPrefixedText() ) ) )
- {
+ array( 'delete', $this->getTitle()->getPrefixedText() ) )
+ ) {
# Flag to hide all contents of the archived revisions
$suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
@@ -1564,7 +1616,8 @@ class Article implements Page {
try {
$reason = $this->generateReason( $hasHistory );
} catch ( MWException $e ) {
- # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here.
+ # if a page is horribly broken, we still want to be able to
+ # delete it. So be lenient about errors here.
wfDebug( "Error while building auto delete summary: $e" );
$reason = '';
}
@@ -1572,7 +1625,20 @@ class Article implements Page {
// If the page has a history, insert a warning
if ( $hasHistory ) {
- $revisions = $this->mTitle->estimateRevisionCount();
+ $title = $this->getTitle();
+
+ // The following can use the real revision count as this is only being shown for users that can delete
+ // this page.
+ // This, as a side-effect, also makes sure that the following query isn't being run for pages with a
+ // larger history, unless the user has the 'bigdelete' right (and is about to delete this page).
+ $dbr = wfGetDB( DB_SLAVE );
+ $revisions = $edits = (int)$dbr->selectField(
+ 'revision',
+ 'COUNT(rev_page)',
+ array( 'rev_page' => $title->getArticleID() ),
+ __METHOD__
+ );
+
// @todo FIXME: i18n issue/patchwork message
$this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' .
wfMessage( 'historywarning' )->numParams( $revisions )->parse() .
@@ -1583,10 +1649,14 @@ class Article implements Page {
'</strong>'
);
- if ( $this->mTitle->isBigDeletion() ) {
+ if ( $title->isBigDeletion() ) {
global $wgDeleteRevisionsLimit;
$this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
- array( 'delete-warning-toobig', $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit ) ) );
+ array(
+ 'delete-warning-toobig',
+ $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
+ )
+ );
}
}
@@ -1596,15 +1666,21 @@ class Article implements Page {
/**
* Output deletion confirmation dialog
* @todo FIXME: Move to another file?
- * @param string $reason prefilled reason
+ * @param string $reason Prefilled reason
*/
public function confirmDelete( $reason ) {
wfDebug( "Article::confirmDelete\n" );
+ $title = $this->getTitle();
$outputPage = $this->getContext()->getOutput();
- $outputPage->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
- $outputPage->addBacklinkSubtitle( $this->getTitle() );
+ $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
+ $outputPage->addBacklinkSubtitle( $title );
$outputPage->setRobotPolicy( 'noindex,nofollow' );
+ $backlinkCache = $title->getBacklinkCache();
+ if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
+ $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+ 'deleting-backlinks-warning' );
+ }
$outputPage->addWikiMsg( 'confirmdeletetext' );
wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) );
@@ -1622,10 +1698,10 @@ class Article implements Page {
} else {
$suppress = '';
}
- $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $this->getTitle() );
+ $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
$form = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
+ 'action' => $title->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) .
Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
@@ -1634,9 +1710,14 @@ class Article implements Page {
Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
"</td>
<td class='mw-input'>" .
- Xml::listDropDown( 'wpDeleteReasonList',
+ Xml::listDropDown(
+ 'wpDeleteReasonList',
wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
- wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(), '', 'wpReasonDropDown', 1 ) .
+ wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
+ '',
+ 'wpReasonDropDown',
+ 1
+ ) .
"</td>
</tr>
<tr id=\"wpDeleteReasonRow\">
@@ -1677,13 +1758,16 @@ class Article implements Page {
</tr>" .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' ) .
- Html::hidden( 'wpEditToken', $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) ) ) .
+ Html::hidden(
+ 'wpEditToken',
+ $user->getEditToken( array( 'delete', $title->getPrefixedText() ) )
+ ) .
Xml::closeElement( 'form' );
if ( $user->isAllowed( 'editinterface' ) ) {
- $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
+ $dropdownTitle = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
$link = Linker::link(
- $title,
+ $dropdownTitle,
wfMessage( 'delete-edit-reasonlist' )->escaped(),
array(),
array( 'action' => 'edit' )
@@ -1695,20 +1779,19 @@ class Article implements Page {
$deleteLogPage = new LogPage( 'delete' );
$outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
- LogEventsList::showLogExtract( $outputPage, 'delete',
- $this->getTitle()
- );
+ LogEventsList::showLogExtract( $outputPage, 'delete', $title );
}
/**
* Perform a deletion and output success or failure messages
- * @param $reason
- * @param $suppress bool
+ * @param string $reason
+ * @param bool $suppress
*/
public function doDelete( $reason, $suppress = false ) {
$error = '';
$outputPage = $this->getContext()->getOutput();
$status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error );
+
if ( $status->isGood() ) {
$deleted = $this->getTitle()->getPrefixedText();
@@ -1720,7 +1803,11 @@ class Article implements Page {
$outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
$outputPage->returnToMain( false );
} else {
- $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $this->getTitle()->getPrefixedText() ) );
+ $outputPage->setPageTitle(
+ wfMessage( 'cannotdelete-title',
+ $this->getTitle()->getPrefixedText() )
+ );
+
if ( $error == '' ) {
$outputPage->addWikiText(
"<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
@@ -1746,7 +1833,7 @@ class Article implements Page {
* output to the client that is necessary for this request.
* (that is, it has sent a cached version of the page)
*
- * @return boolean true if cached version send, false otherwise
+ * @return bool True if cached version send, false otherwise
*/
protected function tryFileCache() {
static $called = false;
@@ -1758,7 +1845,7 @@ class Article implements Page {
$called = true;
if ( $this->isFileCacheable() ) {
- $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'view' );
+ $cache = new HTMLFileCache( $this->getTitle(), 'view' );
if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
wfDebug( "Article::tryFileCache(): about to load file\n" );
$cache->loadFromFileCache( $this->getContext() );
@@ -1802,9 +1889,9 @@ class Article implements Page {
*
* @since 1.16 (r52326) for LiquidThreads
*
- * @param $oldid mixed integer Revision ID or null
- * @param $user User The relevant user
- * @return ParserOutput or false if the given revision ID is not found
+ * @param int|null $oldid Revision ID or null
+ * @param User $user The relevant user
+ * @return ParserOutput|bool ParserOutput or false if the given revision ID is not found
*/
public function getParserOutput( $oldid = null, User $user = null ) {
//XXX: bypasses mParserOptions and thus setParserOptions()
@@ -1822,7 +1909,7 @@ class Article implements Page {
* Override the ParserOptions used to render the primary article wikitext.
*
* @param ParserOptions $options
- * @throws MWException if the parser options where already initialized.
+ * @throws MWException If the parser options where already initialized.
*/
public function setParserOptions( ParserOptions $options ) {
if ( $this->mParserOptions ) {
@@ -1848,7 +1935,7 @@ class Article implements Page {
/**
* Sets the context this Article is executed in
*
- * @param $context IContextSource
+ * @param IContextSource $context
* @since 1.18
*/
public function setContext( $context ) {
@@ -1865,127 +1952,18 @@ class Article implements Page {
if ( $this->mContext instanceof IContextSource ) {
return $this->mContext;
} else {
- wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
+ wfDebug( __METHOD__ . " called and \$mContext is null. " .
+ "Return RequestContext::getMain(); for sanity\n" );
return RequestContext::getMain();
}
}
/**
- * Info about this page
- * @deprecated since 1.19
- */
- public function info() {
- wfDeprecated( __METHOD__, '1.19' );
- Action::factory( 'info', $this )->show();
- }
-
- /**
- * Mark this particular edit/page as patrolled
- * @deprecated since 1.18
- */
- public function markpatrolled() {
- wfDeprecated( __METHOD__, '1.18' );
- Action::factory( 'markpatrolled', $this )->show();
- }
-
- /**
- * Handle action=purge
- * @deprecated since 1.19
- * @return Action|bool|null false if the action is disabled, null if it is not recognised
- */
- public function purge() {
- return Action::factory( 'purge', $this )->show();
- }
-
- /**
- * Handle action=revert
- * @deprecated since 1.19
- */
- public function revert() {
- wfDeprecated( __METHOD__, '1.19' );
- Action::factory( 'revert', $this )->show();
- }
-
- /**
- * Handle action=rollback
- * @deprecated since 1.19
- */
- public function rollback() {
- wfDeprecated( __METHOD__, '1.19' );
- Action::factory( 'rollback', $this )->show();
- }
-
- /**
- * User-interface handler for the "watch" action.
- * Requires Request to pass a token as of 1.18.
- * @deprecated since 1.18
- */
- public function watch() {
- wfDeprecated( __METHOD__, '1.18' );
- Action::factory( 'watch', $this )->show();
- }
-
- /**
- * Add this page to the current user's watchlist
- *
- * This is safe to be called multiple times
- *
- * @return bool true on successful watch operation
- * @deprecated since 1.18
- */
- public function doWatch() {
- wfDeprecated( __METHOD__, '1.18' );
- return WatchAction::doWatch( $this->getTitle(), $this->getContext()->getUser() );
- }
-
- /**
- * User interface handler for the "unwatch" action.
- * Requires Request to pass a token as of 1.18.
- * @deprecated since 1.18
- */
- public function unwatch() {
- wfDeprecated( __METHOD__, '1.18' );
- Action::factory( 'unwatch', $this )->show();
- }
-
- /**
- * Stop watching a page
- * @return bool true on successful unwatch
- * @deprecated since 1.18
- */
- public function doUnwatch() {
- wfDeprecated( __METHOD__, '1.18' );
- return WatchAction::doUnwatch( $this->getTitle(), $this->getContext()->getUser() );
- }
-
- /**
- * Output a redirect back to the article.
- * This is typically used after an edit.
- *
- * @deprecated in 1.18; call OutputPage::redirect() directly
- * @param $noRedir Boolean: add redirect=no
- * @param string $sectionAnchor section to redirect to, including "#"
- * @param string $extraQuery extra query params
- */
- public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
- wfDeprecated( __METHOD__, '1.18' );
- if ( $noRedir ) {
- $query = 'redirect=no';
- if ( $extraQuery ) {
- $query .= "&$extraQuery";
- }
- } else {
- $query = $extraQuery;
- }
-
- $this->getContext()->getOutput()->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor );
- }
-
- /**
* Use PHP's magic __get handler to handle accessing of
* raw WikiPage fields for backwards compatibility.
*
* @param string $fname Field name
+ * @return mixed
*/
public function __get( $fname ) {
if ( property_exists( $this->mPage, $fname ) ) {
@@ -2000,7 +1978,7 @@ class Article implements Page {
* raw WikiPage fields for backwards compatibility.
*
* @param string $fname Field name
- * @param $fvalue mixed New value
+ * @param mixed $fvalue New value
*/
public function __set( $fname, $fvalue ) {
if ( property_exists( $this->mPage, $fname ) ) {
@@ -2033,25 +2011,29 @@ class Article implements Page {
// ****** B/C functions to work-around PHP silliness with __call and references ****** //
/**
- * @param $limit array
- * @param $expiry array
- * @param $cascade bool
- * @param $reason string
- * @param $user User
+ * @param array $limit
+ * @param array $expiry
+ * @param bool $cascade
+ * @param string $reason
+ * @param User $user
* @return Status
*/
- public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
+ public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
+ $reason, User $user
+ ) {
return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
}
/**
- * @param $limit array
- * @param $reason string
- * @param $cascade int
- * @param $expiry array
+ * @param array $limit
+ * @param string $reason
+ * @param int $cascade
+ * @param array $expiry
* @return bool
*/
- public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
+ public function updateRestrictions( $limit = array(), $reason = '',
+ &$cascade = 0, $expiry = array()
+ ) {
return $this->mPage->doUpdateRestrictions(
$limit,
$expiry,
@@ -2062,24 +2044,26 @@ class Article implements Page {
}
/**
- * @param $reason string
- * @param $suppress bool
- * @param $id int
- * @param $commit bool
- * @param $error string
+ * @param string $reason
+ * @param bool $suppress
+ * @param int $id
+ * @param bool $commit
+ * @param string $error
* @return bool
*/
- public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
+ public function doDeleteArticle( $reason, $suppress = false, $id = 0,
+ $commit = true, &$error = ''
+ ) {
return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
}
/**
- * @param $fromP
- * @param $summary
- * @param $token
- * @param $bot
- * @param $resultDetails
- * @param $user User
+ * @param string $fromP
+ * @param string $summary
+ * @param string $token
+ * @param bool $bot
+ * @param array $resultDetails
+ * @param User|null $user
* @return array
*/
public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
@@ -2088,11 +2072,11 @@ class Article implements Page {
}
/**
- * @param $fromP
- * @param $summary
- * @param $bot
- * @param $resultDetails
- * @param $guser User
+ * @param string $fromP
+ * @param string $summary
+ * @param bool $bot
+ * @param array $resultDetails
+ * @param User|null $guser
* @return array
*/
public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
@@ -2101,7 +2085,7 @@ class Article implements Page {
}
/**
- * @param $hasHistory bool
+ * @param bool $hasHistory
* @return mixed
*/
public function generateReason( &$hasHistory ) {
@@ -2114,36 +2098,48 @@ class Article implements Page {
/**
* @return array
+ *
+ * @deprecated since 1.24, use WikiPage::selectFields() instead
*/
public static function selectFields() {
+ wfDeprecated( __METHOD__, '1.24' );
return WikiPage::selectFields();
}
/**
- * @param $title Title
+ * @param Title $title
+ *
+ * @deprecated since 1.24, use WikiPage::onArticleCreate() instead
*/
public static function onArticleCreate( $title ) {
+ wfDeprecated( __METHOD__, '1.24' );
WikiPage::onArticleCreate( $title );
}
/**
- * @param $title Title
+ * @param Title $title
+ *
+ * @deprecated since 1.24, use WikiPage::onArticleDelete() instead
*/
public static function onArticleDelete( $title ) {
+ wfDeprecated( __METHOD__, '1.24' );
WikiPage::onArticleDelete( $title );
}
/**
- * @param $title Title
+ * @param Title $title
+ *
+ * @deprecated since 1.24, use WikiPage::onArticleEdit() instead
*/
public static function onArticleEdit( $title ) {
+ wfDeprecated( __METHOD__, '1.24' );
WikiPage::onArticleEdit( $title );
}
/**
- * @param $oldtext
- * @param $newtext
- * @param $flags
+ * @param string $oldtext
+ * @param string $newtext
+ * @param int $flags
* @return string
* @deprecated since 1.21, use ContentHandler::getAutosummary() instead
*/
diff --git a/includes/CategoryPage.php b/includes/page/CategoryPage.php
index ba71aa01..9abc6a89 100644
--- a/includes/CategoryPage.php
+++ b/includes/page/CategoryPage.php
@@ -30,7 +30,7 @@ class CategoryPage extends Article {
protected $mCategoryViewerClass = 'CategoryViewer';
/**
- * @param $title Title
+ * @param Title $title
* @return WikiCategoryPage
*/
protected function newPage( Title $title ) {
@@ -40,7 +40,7 @@ class CategoryPage extends Article {
/**
* Constructor from a page id
- * @param int $id article ID to load
+ * @param int $id Article ID to load
* @return CategoryPage|null
*/
public static function newFromID( $id ) {
diff --git a/includes/ImagePage.php b/includes/page/ImagePage.php
index d696a17c..d06c8191 100644
--- a/includes/ImagePage.php
+++ b/includes/page/ImagePage.php
@@ -26,21 +26,20 @@
* @ingroup Media
*/
class ImagePage extends Article {
-
- /**
- * @var File
- */
+ /** @var File */
private $displayImg;
- /**
- * @var FileRepo
- */
+
+ /** @var FileRepo */
private $repo;
+
+ /** @var bool */
private $fileLoaded;
- var $mExtraDescription = false;
+ /** @var bool */
+ protected $mExtraDescription = false;
/**
- * @param $title Title
+ * @param Title $title
* @return WikiFilePage
*/
protected function newPage( Title $title ) {
@@ -50,7 +49,7 @@ class ImagePage extends Article {
/**
* Constructor from a page id
- * @param int $id article ID to load
+ * @param int $id Article ID to load
* @return ImagePage|null
*/
public static function newFromID( $id ) {
@@ -61,7 +60,7 @@ class ImagePage extends Article {
}
/**
- * @param $file File:
+ * @param File $file
* @return void
*/
public function setFile( $file ) {
@@ -106,7 +105,10 @@ class ImagePage extends Article {
$out = $this->getContext()->getOutput();
$request = $this->getContext()->getRequest();
$diff = $request->getVal( 'diff' );
- $diffOnly = $request->getBool( 'diffonly', $this->getContext()->getUser()->getOption( 'diffonly' ) );
+ $diffOnly = $request->getBool(
+ 'diffonly',
+ $this->getContext()->getUser()->getOption( 'diffonly' )
+ );
if ( $this->getTitle()->getNamespace() != NS_FILE || ( $diff !== null && $diffOnly ) ) {
parent::view();
@@ -126,9 +128,12 @@ class ImagePage extends Article {
// mTitle is not the same as the redirect target so it is
// probably the redirect page itself. Fake the redirect symbol
$out->setPageTitle( $this->getTitle()->getPrefixedText() );
- $out->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
- /* $appendSubtitle */ true, /* $forceKnown */ true ) );
- $this->mPage->doViewUpdates( $this->getContext()->getUser() );
+ $out->addHTML( $this->viewRedirect(
+ Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
+ /* $appendSubtitle */ true,
+ /* $forceKnown */ true )
+ );
+ $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
return;
}
}
@@ -165,7 +170,7 @@ class ImagePage extends Article {
# Just need to set the right headers
$out->setArticleFlag( true );
$out->setPageTitle( $this->getTitle()->getPrefixedText() );
- $this->mPage->doViewUpdates( $this->getContext()->getUser() );
+ $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
}
# Show shared description, if needed
@@ -227,8 +232,8 @@ class ImagePage extends Article {
/**
* Create the TOC
*
- * @param $metadata Boolean: whether or not to show the metadata link
- * @return String
+ * @param bool $metadata Whether or not to show the metadata link
+ * @return string
*/
protected function showTOC( $metadata ) {
$r = array(
@@ -250,8 +255,8 @@ class ImagePage extends Article {
*
* @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
*
- * @param array $metadata the array containing the Exif data
- * @return String The metadata table. This is treated as Wikitext (!)
+ * @param array $metadata The array containing the Exif data
+ * @return string The metadata table. This is treated as Wikitext (!)
*/
protected function makeMetadataTable( $metadata ) {
$r = "<div class=\"mw-imagepage-section-metadata\">";
@@ -262,7 +267,8 @@ class ImagePage extends Article {
# @todo FIXME: Why is this using escapeId for a class?!
$class = Sanitizer::escapeId( $v['id'] );
if ( $type == 'collapsed' ) {
- $class .= ' collapsable'; // sic
+ // Handled by mediawiki.action.view.metadata module.
+ $class .= ' collapsable';
}
$r .= "<tr class=\"$class\">\n";
$r .= "<th>{$v['name']}</th>\n";
@@ -289,7 +295,7 @@ class ImagePage extends Article {
}
protected function openShowImage() {
- global $wgImageLimits, $wgEnableUploads, $wgSend404Code;
+ global $wgEnableUploads, $wgSend404Code;
$this->loadFile();
$out = $this->getContext()->getOutput();
@@ -314,7 +320,12 @@ class ImagePage extends Article {
$renderLang = $request->getVal( 'lang' );
if ( !is_null( $renderLang ) ) {
- $params['lang'] = $renderLang;
+ $handler = $this->displayImg->getHandler();
+ if ( $handler && $handler->validateParam( 'lang', $renderLang ) ) {
+ $params['lang'] = $renderLang;
+ } else {
+ $renderLang = null;
+ }
}
$width_orig = $this->displayImg->getWidth( $page );
@@ -329,44 +340,40 @@ class ImagePage extends Article {
if ( $this->displayImg->allowInlineDisplay() ) {
# image
-
# "Download high res version" link below the image
- # $msgsize = wfMessage( 'file-info-size', $width_orig, $height_orig, Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
+ # $msgsize = wfMessage( 'file-info-size', $width_orig, $height_orig,
+ # Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
# We'll show a thumbnail of this image
- if ( $width > $maxWidth || $height > $maxHeight ) {
- # Calculate the thumbnail size.
- # First case, the limiting factor is the width, not the height.
- if ( $width / $height >= $maxWidth / $maxHeight ) { // FIXME: Possible division by 0. bug 36911
- $height = round( $height * $maxWidth / $width ); // FIXME: Possible division by 0. bug 36911
- $width = $maxWidth;
- # Note that $height <= $maxHeight now.
- } else {
- $newwidth = floor( $width * $maxHeight / $height ); // FIXME: Possible division by 0. bug 36911
- $height = round( $height * $newwidth / $width ); // FIXME: Possible division by 0. bug 36911
- $width = $newwidth;
- # Note that $height <= $maxHeight now, but might not be identical
- # because of rounding.
- }
+ if ( $width > $maxWidth || $height > $maxHeight || $this->displayImg->isVectorized() ) {
+ list( $width, $height ) = $this->getDisplayWidthHeight(
+ $maxWidth, $maxHeight, $width, $height
+ );
$linktext = wfMessage( 'show-big-image' )->escaped();
- if ( $this->displayImg->getRepo()->canTransformVia404() ) {
- $thumbSizes = $wgImageLimits;
- } else {
- # Creating thumb links triggers thumbnail generation.
- # Just generate the thumb for the current users prefs.
- $thumbSizes = array( $this->getImageLimitsFromOption( $user, 'thumbsize' ) );
- }
+
+ $thumbSizes = $this->getThumbSizes( $width, $height, $width_orig, $height_orig );
# Generate thumbnails or thumbnail links as needed...
$otherSizes = array();
foreach ( $thumbSizes as $size ) {
- if ( $size[0] < $width_orig && $size[1] < $height_orig
- && $size[0] != $width && $size[1] != $height )
- {
+ // We include a thumbnail size in the list, if it is
+ // less than or equal to the original size of the image
+ // asset ($width_orig/$height_orig). We also exclude
+ // the current thumbnail's size ($width/$height)
+ // since that is added to the message separately, so
+ // it can be denoted as the current size being shown.
+ // Vectorized images are "infinitely" big, so all thumb
+ // sizes are shown.
+ if ( ( ( $size[0] <= $width_orig && $size[1] <= $height_orig )
+ || $this->displayImg->isVectorized() )
+ && $size[0] != $width && $size[1] != $height
+ ) {
$sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
if ( $sizeLink ) {
$otherSizes[] = $sizeLink;
}
}
}
+ $otherSizes = array_unique( $otherSizes );
+
$msgsmall = '';
$sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
if ( $sizeLinkBigImagePreview ) {
@@ -385,9 +392,6 @@ class ImagePage extends Article {
# Some sort of audio file that doesn't have dimensions
# Don't output a no hi res message for such a file
$msgsmall = '';
- } elseif ( $this->displayImg->isVectorized() ) {
- # For vectorized images, full size is just the frame size
- $msgsmall = '';
} else {
# Image is small enough to show full size on image page
$msgsmall = wfMessage( 'file-nohires' )->parse();
@@ -396,8 +400,13 @@ class ImagePage extends Article {
$params['width'] = $width;
$params['height'] = $height;
$thumbnail = $this->displayImg->transform( $params );
+ Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params );
- $anchorclose = Html::rawElement( 'div', array( 'class' => 'mw-filepage-resolutioninfo' ), $msgsmall );
+ $anchorclose = Html::rawElement(
+ 'div',
+ array( 'class' => 'mw-filepage-resolutioninfo' ),
+ $msgsmall
+ );
$isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
if ( $isMulti ) {
@@ -428,8 +437,14 @@ class ImagePage extends Article {
array(),
array( 'page' => $page - 1 )
);
- $thumb1 = Linker::makeThumbLinkObj( $this->getTitle(), $this->displayImg, $link, $label, 'none',
- array( 'page' => $page - 1 ) );
+ $thumb1 = Linker::makeThumbLinkObj(
+ $this->getTitle(),
+ $this->displayImg,
+ $link,
+ $label,
+ 'none',
+ array( 'page' => $page - 1 )
+ );
} else {
$thumb1 = '';
}
@@ -442,8 +457,14 @@ class ImagePage extends Article {
array(),
array( 'page' => $page + 1 )
);
- $thumb2 = Linker::makeThumbLinkObj( $this->getTitle(), $this->displayImg, $link, $label, 'none',
- array( 'page' => $page + 1 ) );
+ $thumb2 = Linker::makeThumbLinkObj(
+ $this->getTitle(),
+ $this->displayImg,
+ $link,
+ $label,
+ 'none',
+ array( 'page' => $page + 1 )
+ );
} else {
$thumb2 = '';
}
@@ -494,11 +515,13 @@ class ImagePage extends Article {
// The dirmark, however, must not be immediately adjacent
// to the filename, because it can get copied with it.
// See bug 25277.
+ // @codingStandardsIgnoreStart Ignore long line
$out->addWikiText( <<<EOT
<div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
<div class="mediaWarning">$warning</div>
EOT
- );
+ );
+ // @codingStandardsIgnoreEnd
} else {
$out->addWikiText( <<<EOT
<div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
@@ -507,6 +530,16 @@ EOT
);
}
+ $renderLangOptions = $this->displayImg->getAvailableLanguages();
+ if ( count( $renderLangOptions ) >= 1 ) {
+ $currentLanguage = $renderLang;
+ $defaultLang = $this->displayImg->getDefaultRenderLanguage();
+ if ( is_null( $currentLanguage ) ) {
+ $currentLanguage = $defaultLang;
+ }
+ $out->addHtml( $this->doRenderLangOpt( $renderLangOptions, $currentLanguage, $defaultLang ) );
+ }
+
// Add cannot animate thumbnail warning
if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) {
// Include the extension so wiki admins can
@@ -574,8 +607,8 @@ EOT
/**
* Creates an thumbnail of specified size and returns an HTML link to it
* @param array $params Scaler parameters
- * @param $width int
- * @param $height int
+ * @param int $width
+ * @param int $height
* @return string
*/
private function makeSizeLink( $params, $width, $height ) {
@@ -602,7 +635,7 @@ EOT
$this->loadFile();
$descUrl = $this->mPage->getFile()->getDescriptionUrl();
- $descText = $this->mPage->getFile()->getDescriptionText();
+ $descText = $this->mPage->getFile()->getDescriptionText( $this->getContext()->getLanguage() );
/* Add canonical to head if there is no local page for this shared file */
if ( $descUrl && $this->mPage->getID() == 0 ) {
@@ -655,17 +688,29 @@ EOT
# "Upload a new version of this file" link
$canUpload = $this->getTitle()->userCan( 'upload', $this->getContext()->getUser() );
- if ( $canUpload && UploadBase::userCanReUpload( $this->getContext()->getUser(), $this->mPage->getFile()->name ) ) {
- $ulink = Linker::makeExternalLink( $this->getUploadUrl(), wfMessage( 'uploadnewversion-linktext' )->text() );
- $out->addHTML( "<li id=\"mw-imagepage-reupload-link\"><div class=\"plainlinks\">{$ulink}</div></li>\n" );
+ if ( $canUpload && UploadBase::userCanReUpload(
+ $this->getContext()->getUser(),
+ $this->mPage->getFile()->name )
+ ) {
+ $ulink = Linker::makeExternalLink(
+ $this->getUploadUrl(),
+ wfMessage( 'uploadnewversion-linktext' )->text()
+ );
+ $out->addHTML( "<li id=\"mw-imagepage-reupload-link\">"
+ . "<div class=\"plainlinks\">{$ulink}</div></li>\n" );
} else {
- $out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">" . $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" );
+ $out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">"
+ . $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" );
}
$out->addHTML( "</ul>\n" );
}
- protected function closeShowImage() { } # For overloading
+ /**
+ * For overloading
+ */
+ protected function closeShowImage() {
+ }
/**
* If the page we've just displayed is in the "Image" namespace,
@@ -688,8 +733,8 @@ EOT
}
/**
- * @param $target
- * @param $limit
+ * @param string $target
+ * @param int $limit
* @return ResultWrapper
*/
protected function queryImageLinks( $target, $limit ) {
@@ -697,7 +742,7 @@ EOT
return $dbr->select(
array( 'imagelinks', 'page' ),
- array( 'page_namespace', 'page_title', 'page_is_redirect', 'il_to' ),
+ array( 'page_namespace', 'page_title', 'il_to' ),
array( 'il_to' => $target, 'il_from = page_id' ),
__METHOD__,
array( 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', )
@@ -708,13 +753,19 @@ EOT
$limit = 100;
$out = $this->getContext()->getOutput();
- $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
+
$rows = array();
$redirects = array();
+ foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) {
+ $redirects[$redir->getDBkey()] = array();
+ $rows[] = (object)array(
+ 'page_namespace' => NS_FILE,
+ 'page_title' => $redir->getDBkey(),
+ );
+ }
+
+ $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
foreach ( $res as $row ) {
- if ( $row->page_is_redirect ) {
- $redirects[$row->page_title] = array();
- }
$rows[] = $row;
}
$count = count( $rows );
@@ -875,7 +926,7 @@ EOT
/**
* Display an error with a wikitext description
*
- * @param $description String
+ * @param string $description
*/
function showError( $description ) {
$out = $this->getContext()->getOutput();
@@ -890,9 +941,9 @@ EOT
* Callback for usort() to do link sorts by (namespace, title)
* Function copied from Title::compare()
*
- * @param $a object page to compare with
- * @param $b object page to compare with
- * @return Integer: result of string comparison, or namespace comparison
+ * @param object $a Object page to compare with
+ * @param object $b Object page to compare with
+ * @return int Result of string comparison, or namespace comparison
*/
protected function compare( $a, $b ) {
if ( $a->page_namespace == $b->page_namespace ) {
@@ -905,7 +956,7 @@ EOT
/**
* Returns the corresponding $wgImageLimits entry for the selected user option
*
- * @param $user User
+ * @param User $user
* @param string $optionName Name of a option to check, typically imagesize or thumbsize
* @return array
* @since 1.21
@@ -929,6 +980,155 @@ EOT
? $wgImageLimits[$option]
: array( 800, 600 ); // if nothing is set, fallback to a hardcoded default
}
+
+ /**
+ * Output a drop-down box for language options for the file
+ *
+ * @param array $langChoices Array of string language codes
+ * @param string $curLang Language code file is being viewed in.
+ * @param string $defaultLang Language code that image is rendered in by default
+ * @return string HTML to insert underneath image.
+ */
+ protected function doRenderLangOpt( array $langChoices, $curLang, $defaultLang ) {
+ global $wgScript;
+ sort( $langChoices );
+ $curLang = wfBCP47( $curLang );
+ $defaultLang = wfBCP47( $defaultLang );
+ $opts = '';
+ $haveCurrentLang = false;
+ $haveDefaultLang = false;
+
+ // We make a list of all the language choices in the file.
+ // Additionally if the default language to render this file
+ // is not included as being in this file (for example, in svgs
+ // usually the fallback content is the english content) also
+ // include a choice for that. Last of all, if we're viewing
+ // the file in a language not on the list, add it as a choice.
+ foreach ( $langChoices as $lang ) {
+ $code = wfBCP47( $lang );
+ $name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
+ if ( $name !== '' ) {
+ $display = wfMessage( 'img-lang-opt', $code, $name )->text();
+ } else {
+ $display = $code;
+ }
+ $opts .= "\n" . Xml::option( $display, $code, $curLang === $code );
+ if ( $curLang === $code ) {
+ $haveCurrentLang = true;
+ }
+ if ( $defaultLang === $code ) {
+ $haveDefaultLang = true;
+ }
+ }
+ if ( !$haveDefaultLang ) {
+ // Its hard to know if the content is really in the default language, or
+ // if its just unmarked content that could be in any language.
+ $opts = Xml::option(
+ wfMessage( 'img-lang-default' )->text(),
+ $defaultLang,
+ $defaultLang === $curLang
+ ) . $opts;
+ }
+ if ( !$haveCurrentLang && $defaultLang !== $curLang ) {
+ $name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() );
+ if ( $name !== '' ) {
+ $display = wfMessage( 'img-lang-opt', $curLang, $name )->text();
+ } else {
+ $display = $curLang;
+ }
+ $opts = Xml::option( $display, $curLang, true ) . $opts;
+ }
+
+ $select = Html::rawElement(
+ 'select',
+ array( 'id' => 'mw-imglangselector', 'name' => 'lang' ),
+ $opts
+ );
+ $submit = Xml::submitButton( wfMessage( 'img-lang-go' )->text() );
+
+ $formContents = wfMessage( 'img-lang-info' )->rawParams( $select, $submit )->parse()
+ . Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
+
+ $langSelectLine = Html::rawElement( 'div', array( 'id' => 'mw-imglangselector-line' ),
+ Html::rawElement( 'form', array( 'action' => $wgScript ), $formContents )
+ );
+ return $langSelectLine;
+ }
+
+ /**
+ * Get the width and height to display image at.
+ *
+ * @note This method assumes that it is only called if one
+ * of the dimensions are bigger than the max, or if the
+ * image is vectorized.
+ *
+ * @param int $maxWidth Max width to display at
+ * @param int $maxHeight Max height to display at
+ * @param int $width Actual width of the image
+ * @param int $height Actual height of the image
+ * @throws MWException
+ * @return array Array (width, height)
+ */
+ protected function getDisplayWidthHeight( $maxWidth, $maxHeight, $width, $height ) {
+ if ( !$maxWidth || !$maxHeight ) {
+ // should never happen
+ throw new MWException( 'Using a choice from $wgImageLimits that is 0x0' );
+ }
+
+ if ( !$width || !$height ) {
+ return array( 0, 0 );
+ }
+
+ # Calculate the thumbnail size.
+ if ( $width <= $maxWidth && $height <= $maxHeight ) {
+ // Vectorized image, do nothing.
+ } elseif ( $width / $height >= $maxWidth / $maxHeight ) {
+ # The limiting factor is the width, not the height.
+ $height = round( $height * $maxWidth / $width );
+ $width = $maxWidth;
+ # Note that $height <= $maxHeight now.
+ } else {
+ $newwidth = floor( $width * $maxHeight / $height );
+ $height = round( $height * $newwidth / $width );
+ $width = $newwidth;
+ # Note that $height <= $maxHeight now, but might not be identical
+ # because of rounding.
+ }
+ return array( $width, $height );
+ }
+
+ /**
+ * Get alternative thumbnail sizes.
+ *
+ * @note This will only list several alternatives if thumbnails are rendered on 404
+ * @param int $origWidth Actual width of image
+ * @param int $origHeight Actual height of image
+ * @return array An array of [width, height] pairs.
+ */
+ protected function getThumbSizes( $origWidth, $origHeight ) {
+ global $wgImageLimits;
+ if ( $this->displayImg->getRepo()->canTransformVia404() ) {
+ $thumbSizes = $wgImageLimits;
+ // Also include the full sized resolution in the list, so
+ // that users know they can get it. This will link to the
+ // original file asset if mustRender() === false. In the case
+ // that we mustRender, some users have indicated that they would
+ // find it useful to have the full size image in the rendered
+ // image format.
+ $thumbSizes[] = array( $origWidth, $origHeight );
+ } else {
+ # Creating thumb links triggers thumbnail generation.
+ # Just generate the thumb for the current users prefs.
+ $thumbSizes = array( $this->getImageLimitsFromOption( $this->getContext()->getUser(), 'thumbsize' ) );
+ if ( !$this->displayImg->mustRender() ) {
+ // We can safely include a link to the "full-size" preview,
+ // without actually rendering.
+ $thumbSizes[] = array( $origWidth, $origHeight );
+ }
+ }
+ return $thumbSizes;
+ }
+
}
/**
@@ -989,17 +1189,19 @@ class ImageHistoryList extends ContextSource {
}
/**
- * @param $navLinks string
+ * @param string $navLinks
* @return string
*/
public function beginImageHistoryList( $navLinks = '' ) {
- return Xml::element( 'h2', array( 'id' => 'filehistory' ), $this->msg( 'filehist' )->text() ) . "\n"
+ return Xml::element( 'h2', array( 'id' => 'filehistory' ), $this->msg( 'filehist' )->text() )
+ . "\n"
. "<div id=\"mw-imagepage-section-filehistory\">\n"
. $this->msg( 'filehist-help' )->parseAsBlock()
. $navLinks . "\n"
. Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n"
. '<tr><td></td>'
- . ( $this->current->isLocal() && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' )
+ . ( $this->current->isLocal()
+ && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' )
. '<th>' . $this->msg( 'filehist-datetime' )->escaped() . '</th>'
. ( $this->showThumb ? '<th>' . $this->msg( 'filehist-thumb' )->escaped() . '</th>' : '' )
. '<th>' . $this->msg( 'filehist-dimensions' )->escaped() . '</th>'
@@ -1009,7 +1211,7 @@ class ImageHistoryList extends ContextSource {
}
/**
- * @param $navLinks string
+ * @param string $navLinks
* @return string
*/
public function endImageHistoryList( $navLinks = '' ) {
@@ -1017,8 +1219,8 @@ class ImageHistoryList extends ContextSource {
}
/**
- * @param $iscur
- * @param $file File
+ * @param bool $iscur
+ * @param File $file
* @return string
*/
public function imageHistoryLine( $iscur, $file ) {
@@ -1105,7 +1307,8 @@ class ImageHistoryList extends ContextSource {
$row .= "<td $selected style='white-space: nowrap;'>";
if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
# Don't link to unviewable files
- $row .= '<span class="history-deleted">' . $lang->userTimeAndDate( $timestamp, $user ) . '</span>';
+ $row .= '<span class="history-deleted">'
+ . $lang->userTimeAndDate( $timestamp, $user ) . '</span>';
} elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
if ( $local ) {
$this->preventClickjacking();
@@ -1125,9 +1328,16 @@ class ImageHistoryList extends ContextSource {
$url = $lang->userTimeAndDate( $timestamp, $user );
}
$row .= '<span class="history-deleted">' . $url . '</span>';
+ } elseif ( !$file->exists() ) {
+ $row .= '<span class="mw-file-missing">'
+ . $lang->userTimeAndDate( $timestamp, $user ) . '</span>';
} else {
$url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
- $row .= Xml::element( 'a', array( 'href' => $url ), $lang->userTimeAndDate( $timestamp, $user ) );
+ $row .= Xml::element(
+ 'a',
+ array( 'href' => $url ),
+ $lang->userTimeAndDate( $timestamp, $user )
+ );
}
$row .= "</td>";
@@ -1139,9 +1349,9 @@ class ImageHistoryList extends ContextSource {
// Image dimensions + size
$row .= '<td>';
$row .= htmlspecialchars( $file->getDimensionsString() );
- $row .= $this->msg( 'word-separator' )->plain();
+ $row .= $this->msg( 'word-separator' )->escaped();
$row .= '<span style="white-space: nowrap;">';
- $row .= $this->msg( 'parentheses' )->rawParams( Linker::formatSize( $file->getSize() ) )->plain();
+ $row .= $this->msg( 'parentheses' )->sizeParams( $file->getSize() )->escaped();
$row .= '</span>';
$row .= '</td>';
@@ -1149,11 +1359,12 @@ class ImageHistoryList extends ContextSource {
$row .= '<td>';
// Hide deleted usernames
if ( $file->isDeleted( File::DELETED_USER ) ) {
- $row .= '<span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
+ $row .= '<span class="history-deleted">'
+ . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
} else {
if ( $local ) {
$row .= Linker::userLink( $userId, $userText );
- $row .= $this->msg( 'word-separator' )->plain();
+ $row .= $this->msg( 'word-separator' )->escaped();
$row .= '<span style="white-space: nowrap;">';
$row .= Linker::userToolLinks( $userId, $userText );
$row .= '</span>';
@@ -1165,9 +1376,11 @@ class ImageHistoryList extends ContextSource {
// Don't show deleted descriptions
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
- $row .= '<td><span class="history-deleted">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span></td>';
+ $row .= '<td><span class="history-deleted">' .
+ $this->msg( 'rev-deleted-comment' )->escaped() . '</span></td>';
} else {
- $row .= '<td dir="' . $wgContLang->getDir() . '">' . Linker::formatComment( $description, $this->title ) . '</td>';
+ $row .= '<td dir="' . $wgContLang->getDir() . '">' .
+ Linker::formatComment( $description, $this->title ) . '</td>';
}
$rowClass = null;
@@ -1178,15 +1391,15 @@ class ImageHistoryList extends ContextSource {
}
/**
- * @param $file File
+ * @param File $file
* @return string
*/
protected function getThumbForLine( $file ) {
$lang = $this->getLanguage();
$user = $this->getUser();
if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE, $user )
- && !$file->isDeleted( File::DELETED_FILE ) )
- {
+ && !$file->isDeleted( File::DELETED_FILE )
+ ) {
$params = array(
'width' => '120',
'height' => '120',
@@ -1213,7 +1426,7 @@ class ImageHistoryList extends ContextSource {
}
/**
- * @param $enable bool
+ * @param bool $enable
*/
protected function preventClickjacking( $enable = true ) {
$this->preventClickjacking = $enable;
@@ -1272,7 +1485,7 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
}
/**
- * @param $row object
+ * @param object $row
* @return string
*/
function formatRow( $row ) {
@@ -1386,7 +1599,7 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
}
/**
- * @param $enable bool
+ * @param bool $enable
*/
protected function preventClickjacking( $enable = true ) {
$this->preventClickjacking = $enable;
diff --git a/includes/WikiCategoryPage.php b/includes/page/WikiCategoryPage.php
index d3820016..d3820016 100644
--- a/includes/WikiCategoryPage.php
+++ b/includes/page/WikiCategoryPage.php
diff --git a/includes/WikiFilePage.php b/includes/page/WikiFilePage.php
index fe1ff88a..bfcd4c31 100644
--- a/includes/WikiFilePage.php
+++ b/includes/page/WikiFilePage.php
@@ -40,14 +40,8 @@ class WikiFilePage extends WikiPage {
$this->mRepo = null;
}
- public function getActionOverrides() {
- $overrides = parent::getActionOverrides();
- $overrides['revert'] = 'RevertFileAction';
- return $overrides;
- }
-
/**
- * @param $file File:
+ * @param File $file
*/
public function setFile( $file ) {
$this->mFile = $file;
@@ -85,7 +79,8 @@ class WikiFilePage extends WikiPage {
if ( $from == $to ) {
return null;
}
- return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
+ $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
+ return $this->mRedirectTarget;
}
/**
@@ -142,7 +137,8 @@ class WikiFilePage extends WikiPage {
}
$hash = $this->mFile->getSha1();
if ( !( $hash ) ) {
- return $this->mDupes = array();
+ $this->mDupes = array();
+ return $this->mDupes;
}
$dupes = RepoGroup::singleton()->findBySha1( $hash );
// Remove duplicates with self and non matching file sizes
@@ -178,7 +174,8 @@ class WikiFilePage extends WikiPage {
$this->mFile->upgradeRow();
$this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
} else {
- wfDebug( 'ImagePage::doPurge no image for ' . $this->mFile->getName() . "; limiting purge to cache only\n" );
+ wfDebug( 'ImagePage::doPurge no image for '
+ . $this->mFile->getName() . "; limiting purge to cache only\n" );
// even if the file supposedly doesn't exist, force any cached information
// to be updated (in case the cached information is wrong)
$this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
@@ -189,4 +186,45 @@ class WikiFilePage extends WikiPage {
}
return parent::doPurge();
}
+
+ /**
+ * Get the categories this file is a member of on the wiki where it was uploaded.
+ * For local files, this is the same as getCategories().
+ * For foreign API files (InstantCommons), this is not supported currently.
+ * Results will include hidden categories.
+ *
+ * @return TitleArray|Title[]
+ * @since 1.23
+ */
+ public function getForeignCategories() {
+ $this->loadFile();
+ $title = $this->mTitle;
+ $file = $this->mFile;
+
+ if ( !$file instanceof LocalFile ) {
+ wfDebug( __CLASS__ . '::' . __METHOD__ . " is not supported for this file\n" );
+ return TitleArray::newFromResult( new FakeResultWrapper( array() ) );
+ }
+
+ /** @var LocalRepo $repo */
+ $repo = $file->getRepo();
+ $dbr = $repo->getSlaveDB();
+
+ $res = $dbr->select(
+ array( 'page', 'categorylinks' ),
+ array(
+ 'page_title' => 'cl_to',
+ 'page_namespace' => NS_CATEGORY,
+ ),
+ array(
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey(),
+ ),
+ __METHOD__,
+ array(),
+ array( 'categorylinks' => array( 'INNER JOIN', 'page_id = cl_from' ) )
+ );
+
+ return TitleArray::newFromResult( $res );
+ }
}
diff --git a/includes/WikiPage.php b/includes/page/WikiPage.php
index 099cb897..9ade16e5 100644
--- a/includes/WikiPage.php
+++ b/includes/page/WikiPage.php
@@ -59,7 +59,7 @@ class WikiPage implements Page, IDBAccessObject {
protected $mId = null;
/**
- * @var int; one of the READ_* constants
+ * @var int One of the READ_* constants
*/
protected $mDataLoadedFrom = self::READ_NONE;
@@ -74,7 +74,7 @@ class WikiPage implements Page, IDBAccessObject {
protected $mLastRevision = null;
/**
- * @var string; timestamp of the current revision or empty string if not loaded
+ * @var string Timestamp of the current revision or empty string if not loaded
*/
protected $mTimestamp = '';
@@ -84,13 +84,18 @@ class WikiPage implements Page, IDBAccessObject {
protected $mTouched = '19700101000000';
/**
+ * @var string
+ */
+ protected $mLinksUpdated = '19700101000000';
+
+ /**
* @var int|null
*/
protected $mCounter = null;
/**
* Constructor and clear the article
- * @param $title Title Reference to a Title object.
+ * @param Title $title Reference to a Title object.
*/
public function __construct( Title $title ) {
$this->mTitle = $title;
@@ -99,9 +104,10 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Create a WikiPage object of the appropriate class for the given title.
*
- * @param $title Title
+ * @param Title $title
+ *
* @throws MWException
- * @return WikiPage object of the appropriate type
+ * @return WikiPage Object of the appropriate type
*/
public static function factory( Title $title ) {
$ns = $title->getNamespace();
@@ -129,14 +135,19 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Constructor from a page id
*
- * @param int $id article ID to load
- * @param string|int $from one of the following values:
+ * @param int $id Article ID to load
+ * @param string|int $from One of the following values:
* - "fromdb" or WikiPage::READ_NORMAL to select from a slave database
* - "fromdbmaster" or WikiPage::READ_LATEST to select from the master database
*
* @return WikiPage|null
*/
public static function newFromID( $id, $from = 'fromdb' ) {
+ // page id's are never 0 or negative, see bug 61166
+ if ( $id < 1 ) {
+ return null;
+ }
+
$from = self::convertSelectType( $from );
$db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
$row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
@@ -150,9 +161,8 @@ class WikiPage implements Page, IDBAccessObject {
* Constructor from a database row
*
* @since 1.20
- * @param $row object: database row containing at least fields returned
- * by selectFields().
- * @param string|int $from source of $data:
+ * @param object $row Database row containing at least fields returned by selectFields().
+ * @param string|int $from Source of $data:
* - "fromdb" or WikiPage::READ_NORMAL: from a slave DB
* - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB
* - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE
@@ -167,7 +177,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Convert 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
*
- * @param $type object|string|int
+ * @param object|string|int $type
* @return mixed
*/
private static function convertSelectType( $type ) {
@@ -192,7 +202,7 @@ class WikiPage implements Page, IDBAccessObject {
*
* @todo Move this UI stuff somewhere else
*
- * @return Array
+ * @return array
*/
public function getActionOverrides() {
$content_handler = $this->getContentHandler();
@@ -214,7 +224,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the title object of the article
- * @return Title object of this page
+ * @return Title Title object of this page
*/
public function getTitle() {
return $this->mTitle;
@@ -241,11 +251,13 @@ class WikiPage implements Page, IDBAccessObject {
$this->mRedirectTarget = null; // Title object if set
$this->mLastRevision = null; // Latest revision
$this->mTouched = '19700101000000';
+ $this->mLinksUpdated = '19700101000000';
$this->mTimestamp = '';
$this->mIsRedirect = false;
$this->mLatest = false;
// Bug 57026: do not clear mPreparedEdit since prepareTextForEdit() already checks
- // the requested rev ID and immutable content against the cached one.
+ // the requested rev ID and content against the cached one for equality. For most
+ // content types, the output should not change during the lifetime of this cache.
// Clearing it can cause extra parses on edit for no reason.
}
@@ -265,7 +277,7 @@ class WikiPage implements Page, IDBAccessObject {
* @return array
*/
public static function selectFields() {
- global $wgContentHandlerUseDB;
+ global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
$fields = array(
'page_id',
@@ -277,6 +289,7 @@ class WikiPage implements Page, IDBAccessObject {
'page_is_new',
'page_random',
'page_touched',
+ 'page_links_updated',
'page_latest',
'page_len',
);
@@ -285,15 +298,19 @@ class WikiPage implements Page, IDBAccessObject {
$fields[] = 'page_content_model';
}
+ if ( $wgPageLanguageUseDB ) {
+ $fields[] = 'page_lang';
+ }
+
return $fields;
}
/**
* Fetch a page record with the given conditions
- * @param $dbr DatabaseBase object
- * @param $conditions Array
- * @param $options Array
- * @return mixed Database result resource, or false on failure
+ * @param DatabaseBase $dbr
+ * @param array $conditions
+ * @param array $options
+ * @return object|bool Database result resource, or false on failure
*/
protected function pageData( $dbr, $conditions, $options = array() ) {
$fields = self::selectFields();
@@ -311,10 +328,10 @@ class WikiPage implements Page, IDBAccessObject {
* Fetch a page record matching the Title object's namespace and title
* using a sanitized title string
*
- * @param $dbr DatabaseBase object
- * @param $title Title object
- * @param $options Array
- * @return mixed Database result resource, or false on failure
+ * @param DatabaseBase $dbr
+ * @param Title $title
+ * @param array $options
+ * @return object|bool Database result resource, or false on failure
*/
public function pageDataFromTitle( $dbr, $title, $options = array() ) {
return $this->pageData( $dbr, array(
@@ -325,10 +342,10 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Fetch a page record matching the requested ID
*
- * @param $dbr DatabaseBase
- * @param $id Integer
- * @param $options Array
- * @return mixed Database result resource, or false on failure
+ * @param DatabaseBase $dbr
+ * @param int $id
+ * @param array $options
+ * @return object|bool Database result resource, or false on failure
*/
public function pageDataFromId( $dbr, $id, $options = array() ) {
return $this->pageData( $dbr, array( 'page_id' => $id ), $options );
@@ -338,11 +355,12 @@ class WikiPage implements Page, IDBAccessObject {
* Set the general counter, title etc data loaded from
* some source.
*
- * @param $from object|string|int One of the following:
- * - A DB query result object
- * - "fromdb" or WikiPage::READ_NORMAL to get from a slave DB
- * - "fromdbmaster" or WikiPage::READ_LATEST to get from the master DB
- * - "forupdate" or WikiPage::READ_LOCKING to get from the master DB using SELECT FOR UPDATE
+ * @param object|string|int $from One of the following:
+ * - A DB query result object.
+ * - "fromdb" or WikiPage::READ_NORMAL to get from a slave DB.
+ * - "fromdbmaster" or WikiPage::READ_LATEST to get from the master DB.
+ * - "forupdate" or WikiPage::READ_LOCKING to get from the master DB
+ * using SELECT FOR UPDATE.
*
* @return void
*/
@@ -381,8 +399,7 @@ class WikiPage implements Page, IDBAccessObject {
* Load the object from a database row
*
* @since 1.20
- * @param $data object: database row containing at least fields returned
- * by selectFields()
+ * @param object $data Database row containing at least fields returned by selectFields()
* @param string|int $from One of the following:
* - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
* - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
@@ -404,6 +421,7 @@ class WikiPage implements Page, IDBAccessObject {
$this->mId = intval( $data->page_id );
$this->mCounter = intval( $data->page_counter );
$this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
+ $this->mLinksUpdated = wfTimestampOrNull( TS_MW, $data->page_links_updated );
$this->mIsRedirect = intval( $data->page_is_redirect );
$this->mLatest = intval( $data->page_latest );
// Bug 37225: $latest may no longer match the cached latest Revision object.
@@ -489,7 +507,7 @@ class WikiPage implements Page, IDBAccessObject {
* Will use the revisions actual content model if the page exists,
* and the page's default if the page doesn't exist yet.
*
- * @return String
+ * @return string
*
* @since 1.21
*/
@@ -512,7 +530,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Loads page_touched and returns a value indicating if it should be used
- * @return boolean true if not a redirect
+ * @return bool True if not a redirect
*/
public function checkTouched() {
if ( !$this->mDataLoaded ) {
@@ -523,7 +541,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the page_touched field
- * @return string containing GMT timestamp
+ * @return string Containing GMT timestamp
*/
public function getTouched() {
if ( !$this->mDataLoaded ) {
@@ -533,8 +551,19 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
+ * Get the page_links_updated field
+ * @return string|null Containing GMT timestamp
+ */
+ public function getLinksTimestamp() {
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return $this->mLinksUpdated;
+ }
+
+ /**
* Get the page_latest field
- * @return integer rev_id of current revision
+ * @return int The rev_id of current revision
*/
public function getLatest() {
if ( !$this->mDataLoaded ) {
@@ -612,6 +641,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Set the latest revision
+ * @param Revision $revision
*/
protected function setLastEdit( Revision $revision ) {
$this->mLastRevision = $revision;
@@ -633,12 +663,12 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the content of the current revision. No side-effects...
*
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to $wgUser
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return Content|null The content of the current revision
*
* @since 1.21
@@ -654,16 +684,16 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the text of the current revision. No side-effects...
*
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
- * @return String|false The text of the current revision
- * @deprecated as of 1.21, getContent() should be used instead.
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return string|bool The text of the current revision
+ * @deprecated since 1.21, getContent() should be used instead.
*/
- public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { // @todo deprecated, replace usage!
+ public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
ContentHandler::deprecated( __METHOD__, '1.21' );
$this->loadLastEdit();
@@ -676,8 +706,8 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the text of the current revision. No side-effects...
*
- * @return String|bool The text of the current revision. False on failure
- * @deprecated as of 1.21, getContent() should be used instead.
+ * @return string|bool The text of the current revision. False on failure
+ * @deprecated since 1.21, getContent() should be used instead.
*/
public function getRawText() {
ContentHandler::deprecated( __METHOD__, '1.21' );
@@ -707,13 +737,13 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
- * @return int user ID for the user that made the last article revision
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return int User ID for the user that made the last article revision
*/
public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
@@ -726,12 +756,12 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the User object of the user who created the page
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return User|null
*/
public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
@@ -745,13 +775,13 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
- * @return string username of the user that made the last article revision
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return string Username of the user that made the last article revision
*/
public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
$this->loadLastEdit();
@@ -763,12 +793,12 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * @param $audience Integer: one of:
- * Revision::FOR_PUBLIC to be displayed to all users
- * Revision::FOR_THIS_USER to be displayed to the given user
- * Revision::RAW get the text regardless of permissions
- * @param $user User object to check for, only if FOR_THIS_USER is passed
- * to the $audience parameter
+ * @param int $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to the given user
+ * Revision::RAW get the text regardless of permissions
+ * @param User $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return string Comment stored for the last article revision
*/
public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
@@ -783,7 +813,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Returns true if last revision was marked as "minor edit"
*
- * @return boolean Minor edit indicator for the last article revision.
+ * @return bool Minor edit indicator for the last article revision.
*/
public function getMinorEdit() {
$this->loadLastEdit();
@@ -808,7 +838,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Set the cached timestamp for the last time the page changed.
* This is only used to help handle slave lag by comparing to page_touched.
- * @param $timestamp string
+ * @param string $timestamp
* @return void
*/
public function setCachedLastEditTime( $timestamp ) {
@@ -821,9 +851,9 @@ class WikiPage implements Page, IDBAccessObject {
* Determine whether a page would be suitable for being counted as an
* article in the site_stats table based on the title & its content
*
- * @param $editInfo Object|bool (false): object returned by prepareTextForEdit(),
- * if false, the current database state will be used
- * @return Boolean
+ * @param object|bool $editInfo (false): object returned by prepareTextForEdit(),
+ * if false, the current database state will be used
+ * @return bool
*/
public function isCountable( $editInfo = false ) {
global $wgArticleCountMethod;
@@ -867,7 +897,7 @@ class WikiPage implements Page, IDBAccessObject {
*
* The target will be fetched from the redirect table if possible.
* If this page doesn't have an entry there, call insertRedirect()
- * @return Title|mixed object, or null if this page is not a redirect
+ * @return Title|null Title object, or null if this page is not a redirect
*/
public function getRedirectTarget() {
if ( !$this->mTitle->isRedirect() ) {
@@ -888,20 +918,22 @@ class WikiPage implements Page, IDBAccessObject {
// rd_fragment and rd_interwiki were added later, populate them if empty
if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
- return $this->mRedirectTarget = Title::makeTitle(
+ $this->mRedirectTarget = Title::makeTitle(
$row->rd_namespace, $row->rd_title,
$row->rd_fragment, $row->rd_interwiki );
+ return $this->mRedirectTarget;
}
// This page doesn't have an entry in the redirect table
- return $this->mRedirectTarget = $this->insertRedirect();
+ $this->mRedirectTarget = $this->insertRedirect();
+ return $this->mRedirectTarget;
}
/**
* Insert an entry for this page into the redirect table.
*
* Don't call this function directly unless you know what you're doing.
- * @return Title object or null if not a redirect
+ * @return Title|null Title object or null if not a redirect
*/
public function insertRedirect() {
// recurse through to only get the final target
@@ -917,7 +949,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Insert or update the redirect table entry for this page to indicate
* it redirects to $rt .
- * @param $rt Title redirect target
+ * @param Title $rt Redirect target
*/
public function insertRedirectEntry( $rt ) {
$dbw = wfGetDB( DB_MASTER );
@@ -936,7 +968,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the Title object or URL this page redirects to
*
- * @return mixed false, Title of in-wiki target, or string with URL
+ * @return bool|Title|string False, Title of in-wiki target, or string with URL
*/
public function followRedirect() {
return $this->getRedirectURL( $this->getRedirectTarget() );
@@ -946,8 +978,8 @@ class WikiPage implements Page, IDBAccessObject {
* Get the Title object or URL to use for a redirect. We use Title
* objects for same-wiki, non-special redirects and URLs for everything
* else.
- * @param $rt Title Redirect target
- * @return mixed false, Title object of local target, or string with URL
+ * @param Title $rt Redirect target
+ * @return bool|Title|string False, Title object of local target, or string with URL
*/
public function getRedirectURL( $rt ) {
if ( !$rt ) {
@@ -1039,7 +1071,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the last N authors
* @param int $num Number of revisions to get
- * @param int|string $revLatest the latest rev_id, selected from the master (optional)
+ * @param int|string $revLatest The latest rev_id, selected from the master (optional)
* @return array Array of authors, duplicates not removed
*/
public function getLastNAuthors( $num, $revLatest = 0 ) {
@@ -1091,9 +1123,9 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Should the parser cache be used?
*
- * @param $parserOptions ParserOptions to check
- * @param $oldid int
- * @return boolean
+ * @param ParserOptions $parserOptions ParserOptions to check
+ * @param int $oldid
+ * @return bool
*/
public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
global $wgEnableParserCache;
@@ -1112,9 +1144,9 @@ class WikiPage implements Page, IDBAccessObject {
* @since 1.19
* @param ParserOptions $parserOptions ParserOptions to use for the parse operation
* @param null|int $oldid Revision ID to get the text from, passing null or 0 will
- * get the current revision (default value)
+ * get the current revision (default value)
*
- * @return ParserOutput or false if the revision was not found
+ * @return ParserOutput|bool ParserOutput or false if the revision was not found
*/
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
wfProfileIn( __METHOD__ );
@@ -1146,10 +1178,11 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * Do standard deferred updates after page view
- * @param $user User The relevant user
+ * Do standard deferred updates after page view (existing or missing page)
+ * @param User $user The relevant user
+ * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
*/
- public function doViewUpdates( User $user ) {
+ public function doViewUpdates( User $user, $oldid = 0 ) {
global $wgDisableCounters;
if ( wfReadOnly() ) {
return;
@@ -1162,7 +1195,7 @@ class WikiPage implements Page, IDBAccessObject {
}
// Update newtalk / watchlist notification status
- $user->clearNotification( $this->mTitle );
+ $user->clearNotification( $this->mTitle, $oldid );
}
/**
@@ -1218,7 +1251,7 @@ class WikiPage implements Page, IDBAccessObject {
* or else the record will be left in a funky state.
* Best if all done inside a transaction.
*
- * @param $dbw DatabaseBase
+ * @param DatabaseBase $dbw
* @return int The newly created page_id key, or false if the title already existed
*/
public function insertOn( $dbw ) {
@@ -1254,19 +1287,19 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Update the page record to point to a newly saved revision.
*
- * @param $dbw DatabaseBase: object
- * @param $revision Revision: For ID number, and text used to set
- * length and redirect status fields
- * @param $lastRevision Integer: if given, will not overwrite the page field
- * when different from the currently set value.
- * Giving 0 indicates the new page flag should be set
- * on.
- * @param $lastRevIsRedirect Boolean: if given, will optimize adding and
- * removing rows in redirect table.
- * @return bool true on success, false on failure
- * @private
- */
- public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
+ * @param DatabaseBase $dbw
+ * @param Revision $revision For ID number, and text used to set
+ * length and redirect status fields
+ * @param int $lastRevision If given, will not overwrite the page field
+ * when different from the currently set value.
+ * Giving 0 indicates the new page flag should be set on.
+ * @param bool $lastRevIsRedirect If given, will optimize adding and
+ * removing rows in redirect table.
+ * @return bool True on success, false on failure
+ */
+ public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
+ $lastRevIsRedirect = null
+ ) {
global $wgContentHandlerUseDB;
wfProfileIn( __METHOD__ );
@@ -1319,12 +1352,12 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Add row to the redirect table if this is a redirect, remove otherwise.
*
- * @param $dbw DatabaseBase
- * @param $redirectTitle Title object pointing to the redirect target,
- * or NULL if this is not a redirect
- * @param $lastRevIsRedirect null|bool If given, will optimize adding and
- * removing rows in redirect table.
- * @return bool true on success, false on failure
+ * @param DatabaseBase $dbw
+ * @param Title $redirectTitle Title object pointing to the redirect target,
+ * or NULL if this is not a redirect
+ * @param null|bool $lastRevIsRedirect If given, will optimize adding and
+ * removing rows in redirect table.
+ * @return bool True on success, false on failure
* @private
*/
public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
@@ -1358,9 +1391,11 @@ class WikiPage implements Page, IDBAccessObject {
* If the given revision is newer than the currently set page_latest,
* update the page record. Otherwise, do nothing.
*
- * @param $dbw DatabaseBase object
- * @param $revision Revision object
- * @return mixed
+ * @deprecated since 1.24, use updateRevisionOn instead
+ *
+ * @param DatabaseBase $dbw
+ * @param Revision $revision
+ * @return bool
*/
public function updateIfNewerOn( $dbw, $revision ) {
wfProfileIn( __METHOD__ );
@@ -1396,9 +1431,9 @@ class WikiPage implements Page, IDBAccessObject {
* Get the content that needs to be saved in order to undo all revisions
* between $undo and $undoafter. Revisions must belong to the same page,
* must exist and must not be deleted
- * @param $undo Revision
- * @param $undoafter Revision Must be an earlier revision than $undo
- * @return mixed string on success, false on failure
+ * @param Revision $undo
+ * @param Revision $undoafter Must be an earlier revision than $undo
+ * @return mixed String on success, false on failure
* @since 1.21
* Before we had the Content object, this was done in getUndoText
*/
@@ -1411,9 +1446,9 @@ class WikiPage implements Page, IDBAccessObject {
* Get the text that needs to be saved in order to undo all revisions
* between $undo and $undoafter. Revisions must belong to the same page,
* must exist and must not be deleted
- * @param $undo Revision
- * @param $undoafter Revision Must be an earlier revision than $undo
- * @return mixed string on success, false on failure
+ * @param Revision $undo
+ * @param Revision $undoafter Must be an earlier revision than $undo
+ * @return string|bool String on success, false on failure
* @deprecated since 1.21: use ContentHandler::getUndoContent() instead.
*/
public function getUndoText( Revision $undo, Revision $undoafter = null ) {
@@ -1440,31 +1475,39 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
- * @param string $text new text of the section
- * @param string $sectionTitle new section's subject, only if $section is 'new'
- * @param string $edittime revision timestamp or null to use the current revision
+ * @param string|number|null|bool $sectionId Section identifier as a number or string
+ * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
+ * or 'new' for a new section.
+ * @param string $text New text of the section.
+ * @param string $sectionTitle New section's subject, only if $section is "new".
+ * @param string $edittime Revision timestamp or null to use the current revision.
+ *
* @throws MWException
- * @return String new complete article text, or null if error
+ * @return string New complete article text, or null if error.
*
- * @deprecated since 1.21, use replaceSectionContent() instead
+ * @deprecated since 1.21, use replaceSectionAtRev() instead
*/
- public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
+ public function replaceSection( $sectionId, $text, $sectionTitle = '',
+ $edittime = null
+ ) {
ContentHandler::deprecated( __METHOD__, '1.21' );
- if ( strval( $section ) == '' ) { //NOTE: keep condition in sync with condition in replaceSectionContent!
+ //NOTE: keep condition in sync with condition in replaceSectionContent!
+ if ( strval( $sectionId ) === '' ) {
// Whole-page edit; let the whole text through
return $text;
}
if ( !$this->supportsSections() ) {
- throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+ throw new MWException( "sections not supported for content model " .
+ $this->getContentHandler()->getModelID() );
}
// could even make section title, but that's not required.
$sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
- $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
+ $newContent = $this->replaceSectionContent( $sectionId, $sectionContent, $sectionTitle,
+ $edittime );
return ContentHandler::getContentText( $newContent );
}
@@ -1472,48 +1515,87 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Returns true if this page's content model supports sections.
*
- * @return boolean whether sections are supported.
+ * @return bool
*
- * @todo The skin should check this and not offer section functionality if sections are not supported.
- * @todo The EditPage should check this and not offer section functionality if sections are not supported.
+ * @todo The skin should check this and not offer section functionality if
+ * sections are not supported.
+ * @todo The EditPage should check this and not offer section functionality
+ * if sections are not supported.
*/
public function supportsSections() {
return $this->getContentHandler()->supportsSections();
}
/**
- * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
- * @param $sectionContent Content: new content of the section
- * @param string $sectionTitle new section's subject, only if $section is 'new'
- * @param string $edittime revision timestamp or null to use the current revision
+ * @param string|number|null|bool $sectionId Section identifier as a number or string
+ * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
+ * or 'new' for a new section.
+ * @param Content $sectionContent New content of the section.
+ * @param string $sectionTitle New section's subject, only if $section is "new".
+ * @param string $edittime Revision timestamp or null to use the current revision.
*
* @throws MWException
- * @return Content new complete article content, or null if error
+ * @return Content New complete article content, or null if error.
*
* @since 1.21
+ * @deprecated since 1.24, use replaceSectionAtRev instead
*/
- public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
+ public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
+ $edittime = null ) {
wfProfileIn( __METHOD__ );
- if ( strval( $section ) == '' ) {
+ $baseRevId = null;
+ if ( $edittime && $sectionId !== 'new' ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
+ if ( $rev ) {
+ $baseRevId = $rev->getId();
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
+ }
+
+ /**
+ * @param string|number|null|bool $sectionId Section identifier as a number or string
+ * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
+ * or 'new' for a new section.
+ * @param Content $sectionContent New content of the section.
+ * @param string $sectionTitle New section's subject, only if $section is "new".
+ * @param int|null $baseRevId
+ *
+ * @throws MWException
+ * @return Content New complete article content, or null if error.
+ *
+ * @since 1.24
+ */
+ public function replaceSectionAtRev( $sectionId, Content $sectionContent,
+ $sectionTitle = '', $baseRevId = null
+ ) {
+ wfProfileIn( __METHOD__ );
+
+ if ( strval( $sectionId ) === '' ) {
// Whole-page edit; let the whole text through
$newContent = $sectionContent;
} else {
if ( !$this->supportsSections() ) {
wfProfileOut( __METHOD__ );
- throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+ throw new MWException( "sections not supported for content model " .
+ $this->getContentHandler()->getModelID() );
}
// Bug 30711: always use current version when adding a new section
- if ( is_null( $edittime ) || $section == 'new' ) {
+ if ( is_null( $baseRevId ) || $sectionId === 'new' ) {
$oldContent = $this->getContent();
} else {
+ // TODO: try DB_SLAVE first
$dbw = wfGetDB( DB_MASTER );
- $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
+ $rev = Revision::loadFromId( $dbw, $baseRevId );
if ( !$rev ) {
- wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
- $this->getId() . "; section: $section; edittime: $edittime)\n" );
+ wfDebug( __METHOD__ . " asked for bogus section (page: " .
+ $this->getId() . "; section: $sectionId)\n" );
wfProfileOut( __METHOD__ );
return null;
}
@@ -1521,14 +1603,13 @@ class WikiPage implements Page, IDBAccessObject {
$oldContent = $rev->getContent();
}
- if ( ! $oldContent ) {
+ if ( !$oldContent ) {
wfDebug( __METHOD__ . ": no page text\n" );
wfProfileOut( __METHOD__ );
return null;
}
- // FIXME: $oldContent might be null?
- $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
+ $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
}
wfProfileOut( __METHOD__ );
@@ -1537,10 +1618,10 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
- * @param $flags Int
- * @return Int updated $flags
+ * @param int $flags
+ * @return int Updated $flags
*/
- function checkFlags( $flags ) {
+ public function checkFlags( $flags ) {
if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
if ( $this->exists() ) {
$flags |= EDIT_UPDATE;
@@ -1556,9 +1637,9 @@ class WikiPage implements Page, IDBAccessObject {
* Change an existing article or create a new article. Updates RC and all necessary caches,
* optionally via the deferred update array.
*
- * @param string $text new text
- * @param string $summary edit summary
- * @param $flags Integer bitfield:
+ * @param string $text New text
+ * @param string $summary Edit summary
+ * @param int $flags Bitfield:
* EDIT_NEW
* Article is known or assumed to be non-existent, create a new one
* EDIT_UPDATE
@@ -1574,30 +1655,33 @@ class WikiPage implements Page, IDBAccessObject {
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
*
- * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
- * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
- * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
- * edit-already-exists error will be returned. These two conditions are also possible with
+ * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the
+ * article will be detected. If EDIT_UPDATE is specified and the article
+ * doesn't exist, the function will return an edit-gone-missing error. If
+ * EDIT_NEW is specified and the article does exist, an edit-already-exists
+ * error will be returned. These two conditions are also possible with
* auto-detection due to MediaWiki's performance-optimised locking strategy.
*
- * @param bool|int $baseRevId int the revision ID this edit was based off, if any
- * @param $user User the user doing the edit
+ * @param bool|int $baseRevId The revision ID this edit was based off, if any
+ * @param User $user The user doing the edit
*
* @throws MWException
- * @return Status object. Possible errors:
- * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
- * edit-gone-missing: In update mode, but the article didn't exist
- * edit-conflict: In update mode, the article changed unexpectedly
- * edit-no-change: Warning that the text was the same as before
- * edit-already-exists: In creation mode, but the article already exists
+ * @return Status Possible errors:
+ * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't
+ * set the fatal flag of $status
+ * edit-gone-missing: In update mode, but the article didn't exist.
+ * edit-conflict: In update mode, the article changed unexpectedly.
+ * edit-no-change: Warning that the text was the same as before.
+ * edit-already-exists: In creation mode, but the article already exists.
*
- * Extensions may define additional errors.
+ * Extensions may define additional errors.
*
- * $return->value will contain an associative array with members as follows:
- * new: Boolean indicating if the function attempted to create a new article
- * revision: The revision object for the inserted revision, or null
+ * $return->value will contain an associative array with members as follows:
+ * new: Boolean indicating if the function attempted to create a new article.
+ * revision: The revision object for the inserted revision, or null.
*
- * Compatibility note: this function previously returned a boolean value indicating success/failure
+ * Compatibility note: this function previously returned a boolean value
+ * indicating success/failure
*
* @deprecated since 1.21: use doEditContent() instead.
*/
@@ -1613,9 +1697,9 @@ class WikiPage implements Page, IDBAccessObject {
* Change an existing article or create a new article. Updates RC and all necessary caches,
* optionally via the deferred update array.
*
- * @param $content Content: new content
- * @param string $summary edit summary
- * @param $flags Integer bitfield:
+ * @param Content $content New content
+ * @param string $summary Edit summary
+ * @param int $flags Bitfield:
* EDIT_NEW
* Article is known or assumed to be non-existent, create a new one
* EDIT_UPDATE
@@ -1631,34 +1715,38 @@ class WikiPage implements Page, IDBAccessObject {
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
*
- * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
- * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
- * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
- * edit-already-exists error will be returned. These two conditions are also possible with
+ * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the
+ * article will be detected. If EDIT_UPDATE is specified and the article
+ * doesn't exist, the function will return an edit-gone-missing error. If
+ * EDIT_NEW is specified and the article does exist, an edit-already-exists
+ * error will be returned. These two conditions are also possible with
* auto-detection due to MediaWiki's performance-optimised locking strategy.
*
- * @param bool|int $baseRevId the revision ID this edit was based off, if any
- * @param $user User the user doing the edit
- * @param $serialisation_format String: format for storing the content in the database
+ * @param bool|int $baseRevId The revision ID this edit was based off, if any
+ * @param User $user The user doing the edit
+ * @param string $serialisation_format Format for storing the content in the
+ * database.
*
* @throws MWException
- * @return Status object. Possible errors:
- * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
- * edit-gone-missing: In update mode, but the article didn't exist
- * edit-conflict: In update mode, the article changed unexpectedly
- * edit-no-change: Warning that the text was the same as before
- * edit-already-exists: In creation mode, but the article already exists
+ * @return Status Possible errors:
+ * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't
+ * set the fatal flag of $status.
+ * edit-gone-missing: In update mode, but the article didn't exist.
+ * edit-conflict: In update mode, the article changed unexpectedly.
+ * edit-no-change: Warning that the text was the same as before.
+ * edit-already-exists: In creation mode, but the article already exists.
*
* Extensions may define additional errors.
*
* $return->value will contain an associative array with members as follows:
- * new: Boolean indicating if the function attempted to create a new article
- * revision: The revision object for the inserted revision, or null
+ * new: Boolean indicating if the function attempted to create a new article.
+ * revision: The revision object for the inserted revision, or null.
*
* @since 1.21
*/
public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
- User $user = null, $serialisation_format = null ) {
+ User $user = null, $serialisation_format = null
+ ) {
global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
// Low-level sanity check
@@ -1777,56 +1865,58 @@ class WikiPage implements Page, IDBAccessObject {
}
$dbw->begin( __METHOD__ );
+ try {
- $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
- $status->merge( $prepStatus );
+ $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $status->merge( $prepStatus );
- if ( !$status->isOK() ) {
- $dbw->rollback( __METHOD__ );
-
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ if ( !$status->isOK() ) {
+ $dbw->rollback( __METHOD__ );
- $revisionId = $revision->insertOn( $dbw );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+ $revisionId = $revision->insertOn( $dbw );
- // Update page
- //
- // Note that we use $this->mLatest instead of fetching a value from the master DB
- // during the course of this function. This makes sure that EditPage can detect
- // edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
- // before this function is called. A previous function used a separate query, this
- // creates a window where concurrent edits can cause an ignored edit conflict.
- $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
+ // Update page
+ //
+ // We check for conflicts by comparing $oldid with the current latest revision ID.
+ $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
- if ( !$ok ) {
- // Belated edit conflict! Run away!!
- $status->fatal( 'edit-conflict' );
+ if ( !$ok ) {
+ // Belated edit conflict! Run away!!
+ $status->fatal( 'edit-conflict' );
- $dbw->rollback( __METHOD__ );
+ $dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
- // Update recentchanges
- if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- // Mark as patrolled if the user can do so
- $patrolled = $wgUseRCPatrol && !count(
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
+ // Update recentchanges
+ if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
+ // Mark as patrolled if the user can do so
+ $patrolled = $wgUseRCPatrol && !count(
$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
- // Add RC row to the DB
- $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
- $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
- $revisionId, $patrolled
- );
+ // Add RC row to the DB
+ $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
+ $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
+ $revisionId, $patrolled
+ );
- // Log auto-patrolled edits
- if ( $patrolled ) {
- PatrolLog::record( $rc, true, $user );
+ // Log auto-patrolled edits
+ if ( $patrolled ) {
+ PatrolLog::record( $rc, true, $user );
+ }
}
+ $user->incEditCount();
+ } catch ( MWException $e ) {
+ $dbw->rollback( __METHOD__ );
+ // Question: Would it perhaps be better if this method turned all
+ // exceptions into $status's?
+ throw $e;
}
- $user->incEditCount();
$dbw->commit( __METHOD__ );
} else {
// Bug 32948: revision ID must be set to page {{REVISIONID}} and
@@ -1856,74 +1946,80 @@ class WikiPage implements Page, IDBAccessObject {
$status->value['new'] = true;
$dbw->begin( __METHOD__ );
+ try {
- $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
- $status->merge( $prepStatus );
+ $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $status->merge( $prepStatus );
- if ( !$status->isOK() ) {
- $dbw->rollback( __METHOD__ );
+ if ( !$status->isOK() ) {
+ $dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- $status->merge( $prepStatus );
+ $status->merge( $prepStatus );
- // Add the page record; stake our claim on this title!
- // This will return false if the article already exists
- $newid = $this->insertOn( $dbw );
+ // Add the page record; stake our claim on this title!
+ // This will return false if the article already exists
+ $newid = $this->insertOn( $dbw );
- if ( $newid === false ) {
- $dbw->rollback( __METHOD__ );
- $status->fatal( 'edit-already-exists' );
+ if ( $newid === false ) {
+ $dbw->rollback( __METHOD__ );
+ $status->fatal( 'edit-already-exists' );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- // Save the revision text...
- $revision = new Revision( array(
- 'page' => $newid,
- 'title' => $this->getTitle(), // for determining the default content model
- 'comment' => $summary,
- 'minor_edit' => $isminor,
- 'text' => $serialized,
- 'len' => $newsize,
- 'user' => $user->getId(),
- 'user_text' => $user->getName(),
- 'timestamp' => $now,
- 'content_model' => $content->getModel(),
- 'content_format' => $serialisation_format,
- ) );
- $revisionId = $revision->insertOn( $dbw );
+ // Save the revision text...
+ $revision = new Revision( array(
+ 'page' => $newid,
+ 'title' => $this->getTitle(), // for determining the default content model
+ 'comment' => $summary,
+ 'minor_edit' => $isminor,
+ 'text' => $serialized,
+ 'len' => $newsize,
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $serialisation_format,
+ ) );
+ $revisionId = $revision->insertOn( $dbw );
- // Bug 37225: use accessor to get the text as Revision may trim it
- $content = $revision->getContent(); // sanity; get normalized version
+ // Bug 37225: use accessor to get the text as Revision may trim it
+ $content = $revision->getContent(); // sanity; get normalized version
- if ( $content ) {
- $newsize = $content->getSize();
- }
+ if ( $content ) {
+ $newsize = $content->getSize();
+ }
- // Update the page record with revision data
- $this->updateRevisionOn( $dbw, $revision, 0 );
+ // Update the page record with revision data
+ $this->updateRevisionOn( $dbw, $revision, 0 );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
- // Update recentchanges
- if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- // Mark as patrolled if the user can do so
- $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
- $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
- // Add RC row to the DB
- $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
- '', $newsize, $revisionId, $patrolled );
+ // Update recentchanges
+ if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
+ // Mark as patrolled if the user can do so
+ $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
+ $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
+ // Add RC row to the DB
+ $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
+ '', $newsize, $revisionId, $patrolled );
- // Log auto-patrolled edits
- if ( $patrolled ) {
- PatrolLog::record( $rc, true, $user );
+ // Log auto-patrolled edits
+ if ( $patrolled ) {
+ PatrolLog::record( $rc, true, $user );
+ }
}
+ $user->incEditCount();
+
+ } catch ( MWException $e ) {
+ $dbw->rollback( __METHOD__ );
+ throw $e;
}
- $user->incEditCount();
$dbw->commit( __METHOD__ );
// Update links, etc.
@@ -1951,7 +2047,9 @@ class WikiPage implements Page, IDBAccessObject {
wfRunHooks( 'PageContentSaveComplete', $hook_args );
// Promote user to any groups they meet the criteria for
- $user->addAutopromoteOnceGroups( 'onEdit' );
+ $dbw->onTransactionIdle( function () use ( $user ) {
+ $user->addAutopromoteOnceGroups( 'onEdit' );
+ } );
wfProfileOut( __METHOD__ );
return $status;
@@ -1975,7 +2073,8 @@ class WikiPage implements Page, IDBAccessObject {
$options = $this->getContentHandler()->makeParserOptions( $context );
if ( $this->getTitle()->isConversionTable() ) {
- // @todo ConversionTable should become a separate content model, so we don't need special cases like this one.
+ // @todo ConversionTable should become a separate content model, so
+ // we don't need special cases like this one.
$options->disableContentConversion();
}
@@ -1986,7 +2085,8 @@ class WikiPage implements Page, IDBAccessObject {
* Prepare text which is about to be saved.
* Returns a stdclass with source, pst and output members
*
- * @deprecated in 1.21: use prepareContentForEdit instead.
+ * @deprecated since 1.21: use prepareContentForEdit instead.
+ * @return object
*/
public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
ContentHandler::deprecated( __METHOD__, '1.21' );
@@ -2014,6 +2114,11 @@ class WikiPage implements Page, IDBAccessObject {
$user = is_null( $user ) ? $wgUser : $user;
//XXX: check $user->getId() here???
+ // Use a sane default for $serialization_format, see bug 57026
+ if ( $serialization_format === null ) {
+ $serialization_format = $content->getContentHandler()->getDefaultFormat();
+ }
+
if ( $this->mPreparedEdit
&& $this->mPreparedEdit->newContent
&& $this->mPreparedEdit->newContent->equals( $content )
@@ -2030,12 +2135,15 @@ class WikiPage implements Page, IDBAccessObject {
$edit = (object)array();
$edit->revid = $revid;
+ $edit->timestamp = wfTimestampNow();
$edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
$edit->format = $serialization_format;
$edit->popts = $this->makeParserOptions( 'canonical' );
- $edit->output = $edit->pstContent ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts ) : null;
+ $edit->output = $edit->pstContent
+ ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
+ : null;
$edit->newContent = $content;
$edit->oldContent = $this->getContent( Revision::RAW );
@@ -2055,9 +2163,9 @@ class WikiPage implements Page, IDBAccessObject {
* Purges pages that include this page if the text was changed here.
* Every 100th edit, prune the recent changes table.
*
- * @param $revision Revision object
- * @param $user User object that did the revision
- * @param array $options of options, following indexes are used:
+ * @param Revision $revision
+ * @param User $user User object that did the revision
+ * @param array $options Array of options, following indexes are used:
* - changed: boolean, whether the revision changed the content (default true)
* - created: boolean, whether the revision created the page (default false)
* - oldcountable: boolean or null (default null):
@@ -2087,7 +2195,9 @@ class WikiPage implements Page, IDBAccessObject {
// Save it to the parser cache
if ( $wgEnableParserCache ) {
$parserCache = ParserCache::singleton();
- $parserCache->save( $editInfo->output, $this, $editInfo->popts );
+ $parserCache->save(
+ $editInfo->output, $this, $editInfo->popts, $editInfo->timestamp, $editInfo->revid
+ );
}
// Update the links tables and other secondary data
@@ -2119,19 +2229,17 @@ class WikiPage implements Page, IDBAccessObject {
if ( !$options['changed'] ) {
$good = 0;
- $total = 0;
} elseif ( $options['created'] ) {
$good = (int)$this->isCountable( $editInfo );
- $total = 1;
} elseif ( $options['oldcountable'] !== null ) {
$good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
- $total = 0;
} else {
$good = 0;
- $total = 0;
}
+ $edits = $options['changed'] ? 1 : 0;
+ $total = $options['created'] ? 1 : 0;
- DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, $edits, $good, $total ) );
DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );
// If this is another user's talk page, update newtalk.
@@ -2172,7 +2280,7 @@ class WikiPage implements Page, IDBAccessObject {
if ( $options['created'] ) {
self::onArticleCreate( $this->mTitle );
- } else {
+ } elseif ( $options['changed'] ) { // bug 50785
self::onArticleEdit( $this->mTitle );
}
@@ -2184,10 +2292,10 @@ class WikiPage implements Page, IDBAccessObject {
* The article must already exist; link tables etc
* are not updated, caches are not flushed.
*
- * @param string $text text submitted
- * @param $user User The relevant user
- * @param string $comment comment submitted
- * @param $minor Boolean: whereas it's a minor modification
+ * @param string $text Text submitted
+ * @param User $user The relevant user
+ * @param string $comment Comment submitted
+ * @param bool $minor Whereas it's a minor modification
*
* @deprecated since 1.21, use doEditContent() instead.
*/
@@ -2205,9 +2313,9 @@ class WikiPage implements Page, IDBAccessObject {
*
* @param Content $content Content submitted
* @param User $user The relevant user
- * @param string $comment comment submitted
- * @param string $serialisation_format Format for storing the content in the database
+ * @param string $comment Comment submitted
* @param bool $minor Whereas it's a minor modification
+ * @param string $serialisation_format Format for storing the content in the database
*/
public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
$serialisation_format = null
@@ -2220,6 +2328,8 @@ class WikiPage implements Page, IDBAccessObject {
$revision = new Revision( array(
'title' => $this->getTitle(), // for determining the default content model
'page' => $this->getId(),
+ 'user_text' => $user->getName(),
+ 'user' => $user->getId(),
'text' => $serialized,
'length' => $content->getSize(),
'comment' => $comment,
@@ -2237,22 +2347,24 @@ class WikiPage implements Page, IDBAccessObject {
* Update the article's restriction field, and leave a log entry.
* This works for protection both existing and non-existing pages.
*
- * @param array $limit set of restriction keys
- * @param array $expiry per restriction type expiration
+ * @param array $limit Set of restriction keys
+ * @param array $expiry Per restriction type expiration
* @param int &$cascade Set to false if cascading protection isn't allowed.
* @param string $reason
* @param User $user The user updating the restrictions
* @return Status
*/
- public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
- global $wgCascadingRestrictionLevels;
+ public function doUpdateRestrictions( array $limit, array $expiry,
+ &$cascade, $reason, User $user
+ ) {
+ global $wgCascadingRestrictionLevels, $wgContLang;
if ( wfReadOnly() ) {
return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
}
+ $this->loadPageData( 'fromdbmaster' );
$restrictionTypes = $this->mTitle->getRestrictionTypes();
-
$id = $this->getId();
if ( !$cascade ) {
@@ -2318,13 +2430,21 @@ class WikiPage implements Page, IDBAccessObject {
$logAction = 'protect';
}
+ // Truncate for whole multibyte characters
+ $reason = $wgContLang->truncate( $reason, 255 );
+
+ $logRelationsValues = array();
+ $logRelationsField = null;
+
if ( $id ) { // Protection of existing page
if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
return Status::newGood();
}
// Only certain restrictions can cascade...
- $editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
+ $editrestriction = isset( $limit['edit'] )
+ ? array( $limit['edit'] )
+ : $this->mTitle->getRestrictions( 'edit' );
foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
$editrestriction[$key] = 'editprotected'; // backwards compatibility
}
@@ -2347,16 +2467,37 @@ class WikiPage implements Page, IDBAccessObject {
// insert null revision to identify the page protection change as edit summary
$latest = $this->getLatest();
- $nullRevision = $this->insertProtectNullRevision( $revCommentMsg, $limit, $expiry, $cascade, $reason );
+ $nullRevision = $this->insertProtectNullRevision(
+ $revCommentMsg,
+ $limit,
+ $expiry,
+ $cascade,
+ $reason,
+ $user
+ );
+
if ( $nullRevision === null ) {
return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
}
+ $logRelationsField = 'pr_id';
+
// Update restrictions table
foreach ( $limit as $action => $restrictions ) {
+ $dbw->delete(
+ 'page_restrictions',
+ array(
+ 'pr_page' => $id,
+ 'pr_type' => $action
+ ),
+ __METHOD__
+ );
if ( $restrictions != '' ) {
- $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
- array( 'pr_page' => $id,
+ $dbw->insert(
+ 'page_restrictions',
+ array(
+ 'pr_id' => $dbw->nextSequenceValue( 'page_restrictions_pr_id_seq' ),
+ 'pr_page' => $id,
'pr_type' => $action,
'pr_level' => $restrictions,
'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
@@ -2364,9 +2505,7 @@ class WikiPage implements Page, IDBAccessObject {
),
__METHOD__
);
- } else {
- $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
- 'pr_type' => $action ), __METHOD__ );
+ $logRelationsValues[] = $dbw->insertId();
}
}
@@ -2391,7 +2530,7 @@ class WikiPage implements Page, IDBAccessObject {
'pt_namespace' => $this->mTitle->getNamespace(),
'pt_title' => $this->mTitle->getDBkey(),
'pt_create_perm' => $limit['create'],
- 'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ),
+ 'pt_timestamp' => $dbw->timestamp(),
'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
'pt_user' => $user->getId(),
'pt_reason' => $reason,
@@ -2419,7 +2558,10 @@ class WikiPage implements Page, IDBAccessObject {
// Update the protection log
$log = new LogPage( 'protect' );
- $log->addEntry( $logAction, $this->mTitle, trim( $reason ), $params, $user );
+ $logId = $log->addEntry( $logAction, $this->mTitle, $reason, $params, $user );
+ if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
+ $log->addRelations( $logRelationsField, $logRelationsValues, $logId );
+ }
return Status::newGood();
}
@@ -2427,14 +2569,17 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Insert a new null revision for this page.
*
- * @param string $revCommentMsg comment message key for the revision
- * @param array $limit set of restriction keys
- * @param array $expiry per restriction type expiration
+ * @param string $revCommentMsg Comment message key for the revision
+ * @param array $limit Set of restriction keys
+ * @param array $expiry Per restriction type expiration
* @param int $cascade Set to false if cascading protection isn't allowed.
* @param string $reason
- * @return Revision|null on error
+ * @param User|null $user
+ * @return Revision|null Null on error
*/
- public function insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason ) {
+ public function insertProtectNullRevision( $revCommentMsg, array $limit,
+ array $expiry, $cascade, $reason, $user = null
+ ) {
global $wgContLang;
$dbw = wfGetDB( DB_MASTER );
@@ -2451,7 +2596,8 @@ class WikiPage implements Page, IDBAccessObject {
$protectDescription = $this->protectDescription( $limit, $expiry );
if ( $protectDescription ) {
$editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
- $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text();
+ $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
+ ->inContentLanguage()->text();
}
if ( $cascade ) {
$editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
@@ -2460,7 +2606,7 @@ class WikiPage implements Page, IDBAccessObject {
)->inContentLanguage()->text();
}
- $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true );
+ $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true, $user );
if ( $nullRev ) {
$nullRev->insertOn( $dbw );
@@ -2497,8 +2643,8 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Builds the description to serve as comment for the edit.
*
- * @param array $limit set of restriction keys
- * @param array $expiry per restriction type expiration
+ * @param array $limit Set of restriction keys
+ * @param array $expiry Per restriction type expiration
* @return string
*/
public function protectDescription( array $limit, array $expiry ) {
@@ -2538,8 +2684,8 @@ class WikiPage implements Page, IDBAccessObject {
* protect description text. Keep them in old format to avoid breaking compatibility.
* TODO: Fix protection log to store structured description and format it on-the-fly.
*
- * @param array $limit set of restriction keys
- * @param array $expiry per restriction type expiration
+ * @param array $limit Set of restriction keys
+ * @param array $expiry Per restriction type expiration
* @return string
*/
public function protectDescriptionLog( array $limit, array $expiry ) {
@@ -2558,9 +2704,11 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Take an array of page restrictions and flatten it to a string
* suitable for insertion into the page_restrictions field.
- * @param $limit Array
+ *
+ * @param string[] $limit
+ *
* @throws MWException
- * @return String
+ * @return string
*/
protected static function flattenRestrictions( $limit ) {
if ( !is_array( $limit ) ) {
@@ -2584,14 +2732,14 @@ class WikiPage implements Page, IDBAccessObject {
*
* Deletes the article with database consistency, writes logs, purges caches
*
- * @param string $reason delete reason for deletion log
- * @param $suppress boolean suppress all revisions and log the deletion in
+ * @param string $reason Delete reason for deletion log
+ * @param bool $suppress Suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
- * @param int $id article ID
- * @param $commit boolean defaults to true, triggers transaction end
- * @param &$error Array of errors to append to
- * @param $user User The deleting user
- * @return boolean true if successful
+ * @param int $id Article ID
+ * @param bool $commit Defaults to true, triggers transaction end
+ * @param array &$error Array of errors to append to
+ * @param User $user The deleting user
+ * @return bool True if successful
*/
public function doDeleteArticle(
$reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
@@ -2606,16 +2754,16 @@ class WikiPage implements Page, IDBAccessObject {
*
* @since 1.19
*
- * @param string $reason delete reason for deletion log
- * @param $suppress boolean suppress all revisions and log the deletion in
- * the suppression log instead of the deletion log
- * @param int $id article ID
- * @param $commit boolean defaults to true, triggers transaction end
- * @param &$error Array of errors to append to
- * @param $user User The deleting user
- * @return Status: Status object; if successful, $status->value is the log_id of the
- * deletion log entry. If the page couldn't be deleted because it wasn't
- * found, $status is a non-fatal 'cannotdelete' error
+ * @param string $reason Delete reason for deletion log
+ * @param bool $suppress Suppress all revisions and log the deletion in
+ * the suppression log instead of the deletion log
+ * @param int $id Article ID
+ * @param bool $commit Defaults to true, triggers transaction end
+ * @param array &$error Array of errors to append to
+ * @param User $user The deleting user
+ * @return Status Status object; if successful, $status->value is the log_id of the
+ * deletion log entry. If the page couldn't be deleted because it wasn't
+ * found, $status is a non-fatal 'cannotdelete' error
*/
public function doDeleteArticleReal(
$reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
@@ -2632,7 +2780,7 @@ class WikiPage implements Page, IDBAccessObject {
}
$user = is_null( $user ) ? $wgUser : $user;
- if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
+ if ( !wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
if ( $status->isOK() ) {
// Hook aborted but didn't set a fatal status
$status->fatal( 'delete-hook-aborted' );
@@ -2640,15 +2788,22 @@ class WikiPage implements Page, IDBAccessObject {
return $status;
}
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin( __METHOD__ );
+
if ( $id == 0 ) {
$this->loadPageData( 'forupdate' );
$id = $this->getID();
if ( $id == 0 ) {
+ $dbw->rollback( __METHOD__ );
$status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
return $status;
}
}
+ // we need to remember the old content so we can use it to generate all deletion updates.
+ $content = $this->getContent( Revision::RAW );
+
// Bitfields to further suppress the content
if ( $suppress ) {
$bitfield = 0;
@@ -2661,11 +2816,6 @@ class WikiPage implements Page, IDBAccessObject {
$bitfield = 'rev_deleted';
}
- // we need to remember the old content so we can use it to generate all deletion updates.
- $content = $this->getContent( Revision::RAW );
-
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
// For now, shunt the revision data into the archive table.
// Text is *not* removed from the text table; bulk storage
// is left intact to avoid breaking block-compression or
@@ -2723,22 +2873,29 @@ class WikiPage implements Page, IDBAccessObject {
$dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
}
- $this->doDeleteUpdates( $id, $content );
+ // Clone the title, so we have the information we need when we log
+ $logTitle = clone $this->mTitle;
// Log the deletion, if the page was suppressed, log it at Oversight instead
$logtype = $suppress ? 'suppress' : 'delete';
$logEntry = new ManualLogEntry( $logtype, 'delete' );
$logEntry->setPerformer( $user );
- $logEntry->setTarget( $this->mTitle );
+ $logEntry->setTarget( $logTitle );
$logEntry->setComment( $reason );
$logid = $logEntry->insert();
- $logEntry->publish( $logid );
+
+ $dbw->onTransactionPreCommitOrIdle( function () use ( $dbw, $logEntry, $logid ) {
+ // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
+ $logEntry->publish( $logid );
+ } );
if ( $commit ) {
$dbw->commit( __METHOD__ );
}
+ $this->doDeleteUpdates( $id, $content );
+
wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
$status->value = $logid;
return $status;
@@ -2747,9 +2904,10 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Do some database updates after deletion
*
- * @param int $id page_id value of the page being deleted
- * @param $content Content: optional page content to be used when determining the required updates.
- * This may be needed because $this->getContent() may already return null when the page proper was deleted.
+ * @param int $id The page_id value of the page being deleted
+ * @param Content $content Optional page content to be used when determining
+ * the required updates. This may be needed because $this->getContent()
+ * may already return null when the page proper was deleted.
*/
public function doDeleteUpdates( $id, Content $content = null ) {
// update site status
@@ -2759,6 +2917,14 @@ class WikiPage implements Page, IDBAccessObject {
$updates = $this->getDeletionUpdates( $content );
DataUpdate::runUpdates( $updates );
+ // Reparse any pages transcluding this page
+ LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
+
+ // Reparse any pages including this image
+ if ( $this->mTitle->getNamespace() == NS_FILE ) {
+ LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' );
+ }
+
// Clear caches
WikiPage::onArticleDelete( $this->mTitle );
@@ -2781,14 +2947,14 @@ class WikiPage implements Page, IDBAccessObject {
* @param string $fromP Name of the user whose edits to rollback.
* @param string $summary Custom summary. Set to default summary if empty.
* @param string $token Rollback token.
- * @param $bot Boolean: If true, mark all reverted edits as bot.
+ * @param bool $bot If true, mark all reverted edits as bot.
*
- * @param array $resultDetails contains result-specific array of additional values
+ * @param array $resultDetails Array contains result-specific array of additional values
* 'alreadyrolled' : 'current' (rev)
* success : 'summary' (str), 'current' (rev), 'target' (rev)
*
- * @param $user User The user performing the rollback
- * @return array of errors, each error formatted as
+ * @param User $user The user performing the rollback
+ * @return array Array of errors, each error formatted as
* array(messagekey, param1, param2, ...).
* On success, the array is empty. This array can also be passed to
* OutputPage::showPermissionsErrorPage().
@@ -2829,10 +2995,10 @@ class WikiPage implements Page, IDBAccessObject {
* doRollback() instead.
* @param string $fromP Name of the user whose edits to rollback.
* @param string $summary Custom summary. Set to default summary if empty.
- * @param $bot Boolean: If true, mark all reverted edits as bot.
+ * @param bool $bot If true, mark all reverted edits as bot.
*
- * @param array $resultDetails contains result-specific array of additional values
- * @param $guser User The user performing the rollback
+ * @param array $resultDetails Contains result-specific array of additional values
+ * @param User $guser The user performing the rollback
* @return array
*/
public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
@@ -2878,11 +3044,15 @@ class WikiPage implements Page, IDBAccessObject {
if ( $s === false ) {
// No one else ever edited this page
return array( array( 'cantrollback' ) );
- } elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
+ } elseif ( $s->rev_deleted & Revision::DELETED_TEXT
+ || $s->rev_deleted & Revision::DELETED_USER
+ ) {
// Only admins can see this text
return array( array( 'notvisiblerev' ) );
}
+ // Set patrolling and bot flag on the edits, which gets rollbacked.
+ // This is done before the rollback edit to have patrolling also on failure (bug 62157).
$set = array();
if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
// Mark all reverted edits as bot
@@ -2944,18 +3114,30 @@ class WikiPage implements Page, IDBAccessObject {
}
// Actually store the edit
- $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser );
+ $status = $this->doEditContent(
+ $target->getContent(),
+ $summary,
+ $flags,
+ $target->getId(),
+ $guser
+ );
if ( !$status->isOK() ) {
return $status->getErrorsArray();
}
- if ( !empty( $status->value['revision'] ) ) {
- $revId = $status->value['revision']->getId();
- } else {
- $revId = false;
+ // raise error, when the edit is an edit without a new version
+ if ( empty( $status->value['revision'] ) ) {
+ $resultDetails = array( 'current' => $current );
+ return array( array( 'alreadyrolled',
+ htmlspecialchars( $this->mTitle->getPrefixedText() ),
+ htmlspecialchars( $fromP ),
+ htmlspecialchars( $current->getUserText() )
+ ) );
}
+ $revId = $status->value['revision']->getId();
+
wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
$resultDetails = array(
@@ -2977,7 +3159,7 @@ class WikiPage implements Page, IDBAccessObject {
*
* This is called on page move and undelete, as well as edit
*
- * @param $title Title object
+ * @param Title $title
*/
public static function onArticleCreate( $title ) {
// Update existence markers on article/talk tabs...
@@ -2998,7 +3180,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Clears caches when article is deleted
*
- * @param $title Title
+ * @param Title $title
*/
public static function onArticleDelete( $title ) {
// Update existence markers on article/talk tabs...
@@ -3044,8 +3226,9 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Purge caches on page update etc
*
- * @param $title Title object
- * @todo Verify that $title is always a Title object (and never false or null), add Title hint to parameter $title
+ * @param Title $title
+ * @todo Verify that $title is always a Title object (and never false or
+ * null), add Title hint to parameter $title.
*/
public static function onArticleEdit( $title ) {
// Invalidate caches of articles which include this page
@@ -3091,7 +3274,7 @@ class WikiPage implements Page, IDBAccessObject {
* Returns a list of hidden categories this page is a member of.
* Uses the page_props and categorylinks tables.
*
- * @return Array of Title objects
+ * @return array Array of Title objects
*/
public function getHiddenCategories() {
$result = array();
@@ -3119,15 +3302,16 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Return an applicable autosummary if one exists for the given edit.
- * @param string|null $oldtext the previous text of the page.
+ * @param string|null $oldtext The previous text of the page.
* @param string|null $newtext The submitted text of the page.
- * @param int $flags bitmask: a bitmask of flags submitted for the edit.
+ * @param int $flags Bitmask: a bitmask of flags submitted for the edit.
* @return string An appropriate autosummary, or an empty string.
*
* @deprecated since 1.21, use ContentHandler::getAutosummary() instead
*/
public static function getAutosummary( $oldtext, $newtext, $flags ) {
- // NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
+ // NOTE: stub for backwards-compatibility. assumes the given text is
+ // wikitext. will break horribly if it isn't.
ContentHandler::deprecated( __METHOD__, '1.21' );
@@ -3141,8 +3325,8 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Auto-generates a deletion reason
*
- * @param &$hasHistory Boolean: whether the page has a history
- * @return mixed String containing deletion reason or empty string, or boolean false
+ * @param bool &$hasHistory Whether the page has a history
+ * @return string|bool String containing deletion reason or empty string, or boolean false
* if no revision occurred
*/
public function getAutoDeleteReason( &$hasHistory ) {
@@ -3153,7 +3337,7 @@ class WikiPage implements Page, IDBAccessObject {
* Update all the appropriate counts in the category table, given that
* we've added the categories $added and deleted the categories $deleted.
*
- * @param array $added The names of categories that were added
+ * @param array $added The names of categories that were added
* @param array $deleted The names of categories that were deleted
*/
public function updateCategoryCounts( array $added, array $deleted ) {
@@ -3163,7 +3347,7 @@ class WikiPage implements Page, IDBAccessObject {
// Do this at the end of the commit to reduce lock wait timeouts
$dbw->onTransactionPreCommitOrIdle(
- function() use ( $dbw, $that, $method, $added, $deleted ) {
+ function () use ( $dbw, $that, $method, $added, $deleted ) {
$ns = $that->getTitle()->getNamespace();
$addFields = array( 'cat_pages = cat_pages + 1' );
@@ -3220,24 +3404,24 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Updates cascading protections
*
- * @param $parserOutput ParserOutput object for the current version
+ * @param ParserOutput $parserOutput ParserOutput object for the current version
*/
public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
return;
}
- // templatelinks table may have become out of sync,
+ // templatelinks or imagelinks tables may have become out of sync,
// especially if using variable-based transclusions.
// For paranoia, check if things have changed and if
// so apply updates to the database. This will ensure
// that cascaded protections apply as soon as the changes
// are visible.
- // Get templates from templatelinks
+ // Get templates from templatelinks and images from imagelinks
$id = $this->getId();
- $tlTemplates = array();
+ $dbLinks = array();
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( array( 'templatelinks' ),
@@ -3247,23 +3431,38 @@ class WikiPage implements Page, IDBAccessObject {
);
foreach ( $res as $row ) {
- $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
+ $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true;
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( array( 'imagelinks' ),
+ array( 'il_to' ),
+ array( 'il_from' => $id ),
+ __METHOD__
+ );
+
+ foreach ( $res as $row ) {
+ $dbLinks[NS_FILE . ":{$row->il_to}"] = true;
}
- // Get templates from parser output.
- $poTemplates = array();
+ // Get templates and images from parser output.
+ $poLinks = array();
foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
foreach ( $templates as $dbk => $id ) {
- $poTemplates["$ns:$dbk"] = true;
+ $poLinks["$ns:$dbk"] = true;
}
}
+ foreach ( $parserOutput->getImages() as $dbk => $id ) {
+ $poLinks[NS_FILE . ":$dbk"] = true;
+ }
// Get the diff
- $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
+ $links_diff = array_diff_key( $poLinks, $dbLinks );
- if ( count( $templates_diff ) > 0 ) {
+ if ( count( $links_diff ) > 0 ) {
// Whee, link updates time.
- // Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output.
+ // Note: we are only interested in links here. We don't need to get
+ // other DataUpdate items from the parser output.
$u = new LinksUpdate( $this->mTitle, $parserOutput, false );
$u->doUpdate();
}
@@ -3273,39 +3472,23 @@ class WikiPage implements Page, IDBAccessObject {
* Return a list of templates used by this article.
* Uses the templatelinks table
*
- * @deprecated in 1.19; use Title::getTemplateLinksFrom()
- * @return Array of Title objects
+ * @deprecated since 1.19; use Title::getTemplateLinksFrom()
+ * @return array Array of Title objects
*/
public function getUsedTemplates() {
return $this->mTitle->getTemplateLinksFrom();
}
/**
- * Perform article updates on a special page creation.
- *
- * @param $rev Revision object
- *
- * @todo This is a shitty interface function. Kill it and replace the
- * other shitty functions like doEditUpdates and such so it's not needed
- * anymore.
- * @deprecated since 1.18, use doEditUpdates()
- */
- public function createUpdates( $rev ) {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgUser;
- $this->doEditUpdates( $rev, $wgUser, array( 'created' => true ) );
- }
-
- /**
* This function is called right before saving the wikitext,
* so we can do things like signatures and links-in-context.
*
- * @deprecated in 1.19; use Parser::preSaveTransform() instead
- * @param string $text article contents
- * @param $user User object: user doing the edit
- * @param $popts ParserOptions object: parser options, default options for
- * the user loaded if null given
- * @return string article contents with altered wikitext markup (signatures
+ * @deprecated since 1.19; use Parser::preSaveTransform() instead
+ * @param string $text Article contents
+ * @param User $user User doing the edit
+ * @param ParserOptions $popts Parser options, default options for
+ * the user loaded if null given
+ * @return string Article contents with altered wikitext markup (signatures
* converted, {{subst:}}, templates, etc.)
*/
public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
@@ -3323,37 +3506,15 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
- *
- * @deprecated in 1.19; use Title::isBigDeletion() instead.
- * @return bool
- */
- public function isBigDeletion() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->mTitle->isBigDeletion();
- }
-
- /**
- * Get the approximate revision count of this page.
- *
- * @deprecated in 1.19; use Title::estimateRevisionCount() instead.
- * @return int
- */
- public function estimateRevisionCount() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->mTitle->estimateRevisionCount();
- }
-
- /**
* Update the article's restriction field, and leave a log entry.
*
* @deprecated since 1.19
- * @param array $limit set of restriction keys
- * @param $reason String
- * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
- * @param array $expiry per restriction type expiration
- * @param $user User The user updating the restrictions
- * @return bool true on success
+ * @param array $limit Set of restriction keys
+ * @param string $reason
+ * @param int &$cascade Set to false if cascading protection isn't allowed.
+ * @param array $expiry Per restriction type expiration
+ * @param User $user The user updating the restrictions
+ * @return bool True on success
*/
public function updateRestrictions(
$limit = array(), $reason = '', &$cascade = 0, $expiry = array(), User $user = null
@@ -3366,40 +3527,13 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * @deprecated since 1.18
- */
- public function quickEdit( $text, $comment = '', $minor = 0 ) {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgUser;
- $this->doQuickEdit( $text, $wgUser, $comment, $minor );
- }
-
- /**
- * @deprecated since 1.18
- */
- public function viewUpdates() {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgUser;
- $this->doViewUpdates( $wgUser );
- }
-
- /**
- * @deprecated since 1.18
- * @param $oldid int
- * @return bool
- */
- public function useParserCache( $oldid ) {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgUser;
- return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
- }
-
- /**
- * Returns a list of updates to be performed when this page is deleted. The updates should remove any information
- * about this page from secondary data stores such as links tables.
+ * Returns a list of updates to be performed when this page is deleted. The
+ * updates should remove any information about this page from secondary data
+ * stores such as links tables.
*
- * @param Content|null $content optional Content object for determining the necessary updates
- * @return Array an array of DataUpdates objects
+ * @param Content|null $content Optional Content object for determining the
+ * necessary updates.
+ * @return array An array of DataUpdates objects
*/
public function getDeletionUpdates( Content $content = null ) {
if ( !$content ) {
@@ -3417,205 +3551,4 @@ class WikiPage implements Page, IDBAccessObject {
wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
return $updates;
}
-
-}
-
-class PoolWorkArticleView extends PoolCounterWork {
-
- /**
- * @var Page
- */
- private $page;
-
- /**
- * @var string
- */
- private $cacheKey;
-
- /**
- * @var integer
- */
- private $revid;
-
- /**
- * @var ParserOptions
- */
- private $parserOptions;
-
- /**
- * @var Content|null
- */
- private $content = null;
-
- /**
- * @var ParserOutput|bool
- */
- private $parserOutput = false;
-
- /**
- * @var bool
- */
- private $isDirty = false;
-
- /**
- * @var Status|bool
- */
- private $error = false;
-
- /**
- * Constructor
- *
- * @param $page Page|WikiPage
- * @param $revid Integer: ID of the revision being parsed
- * @param $useParserCache Boolean: whether to use the parser cache
- * @param $parserOptions parserOptions to use for the parse operation
- * @param $content Content|String: content to parse or null to load it; may also be given as a wikitext string, for BC
- */
- function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
- if ( is_string( $content ) ) { // BC: old style call
- $modelId = $page->getRevision()->getContentModel();
- $format = $page->getRevision()->getContentFormat();
- $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
- }
-
- $this->page = $page;
- $this->revid = $revid;
- $this->cacheable = $useParserCache;
- $this->parserOptions = $parserOptions;
- $this->content = $content;
- $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
- parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
- }
-
- /**
- * Get the ParserOutput from this object, or false in case of failure
- *
- * @return ParserOutput
- */
- public function getParserOutput() {
- return $this->parserOutput;
- }
-
- /**
- * Get whether the ParserOutput is a dirty one (i.e. expired)
- *
- * @return bool
- */
- public function getIsDirty() {
- return $this->isDirty;
- }
-
- /**
- * Get a Status object in case of error or false otherwise
- *
- * @return Status|bool
- */
- public function getError() {
- return $this->error;
- }
-
- /**
- * @return bool
- */
- function doWork() {
- global $wgUseFileCache;
-
- // @todo several of the methods called on $this->page are not declared in Page, but present
- // in WikiPage and delegated by Article.
-
- $isCurrent = $this->revid === $this->page->getLatest();
-
- if ( $this->content !== null ) {
- $content = $this->content;
- } elseif ( $isCurrent ) {
- // XXX: why use RAW audience here, and PUBLIC (default) below?
- $content = $this->page->getContent( Revision::RAW );
- } else {
- $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
-
- if ( $rev === null ) {
- $content = null;
- } else {
- // XXX: why use PUBLIC audience here (default), and RAW above?
- $content = $rev->getContent();
- }
- }
-
- if ( $content === null ) {
- return false;
- }
-
- // Reduce effects of race conditions for slow parses (bug 46014)
- $cacheTime = wfTimestampNow();
-
- $time = - microtime( true );
- $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
- $time += microtime( true );
-
- // Timing hack
- if ( $time > 3 ) {
- wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
- $this->page->getTitle()->getPrefixedDBkey() ) );
- }
-
- if ( $this->cacheable && $this->parserOutput->isCacheable() ) {
- ParserCache::singleton()->save(
- $this->parserOutput, $this->page, $this->parserOptions, $cacheTime );
- }
-
- // Make sure file cache is not used on uncacheable content.
- // Output that has magic words in it can still use the parser cache
- // (if enabled), though it will generally expire sooner.
- if ( !$this->parserOutput->isCacheable() || $this->parserOutput->containsOldMagic() ) {
- $wgUseFileCache = false;
- }
-
- if ( $isCurrent ) {
- $this->page->doCascadeProtectionUpdates( $this->parserOutput );
- }
-
- return true;
- }
-
- /**
- * @return bool
- */
- function getCachedWork() {
- $this->parserOutput = ParserCache::singleton()->get( $this->page, $this->parserOptions );
-
- if ( $this->parserOutput === false ) {
- wfDebug( __METHOD__ . ": parser cache miss\n" );
- return false;
- } else {
- wfDebug( __METHOD__ . ": parser cache hit\n" );
- return true;
- }
- }
-
- /**
- * @return bool
- */
- function fallback() {
- $this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions );
-
- if ( $this->parserOutput === false ) {
- wfDebugLog( 'dirty', "dirty missing\n" );
- wfDebug( __METHOD__ . ": no dirty cache\n" );
- return false;
- } else {
- wfDebug( __METHOD__ . ": sending dirty output\n" );
- wfDebugLog( 'dirty', "dirty output {$this->cacheKey}\n" );
- $this->isDirty = true;
- return true;
- }
- }
-
- /**
- * @param $status Status
- * @return bool
- */
- function error( $status ) {
- $this->error = $status;
- return false;
- }
}
diff --git a/includes/pager/AlphabeticPager.php b/includes/pager/AlphabeticPager.php
new file mode 100644
index 00000000..34c78976
--- /dev/null
+++ b/includes/pager/AlphabeticPager.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Efficient paging for SQL queries.
+ *
+ * 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 Pager
+ */
+
+/**
+ * IndexPager with an alphabetic list and a formatted navigation bar
+ * @ingroup Pager
+ */
+abstract class AlphabeticPager extends IndexPager {
+
+ /**
+ * Shamelessly stolen bits from ReverseChronologicalPager,
+ * didn't want to do class magic as may be still revamped
+ *
+ * @return string HTML
+ */
+ function getNavigationBar() {
+ if ( !$this->isNavigationBarShown() ) {
+ return '';
+ }
+
+ if ( isset( $this->mNavigationBar ) ) {
+ return $this->mNavigationBar;
+ }
+
+ $linkTexts = array(
+ 'prev' => $this->msg( 'prevn' )->numParams( $this->mLimit )->escaped(),
+ 'next' => $this->msg( 'nextn' )->numParams( $this->mLimit )->escaped(),
+ 'first' => $this->msg( 'page_first' )->escaped(),
+ 'last' => $this->msg( 'page_last' )->escaped()
+ );
+
+ $lang = $this->getLanguage();
+
+ $pagingLinks = $this->getPagingLinks( $linkTexts );
+ $limitLinks = $this->getLimitLinks();
+ $limits = $lang->pipeList( $limitLinks );
+
+ $this->mNavigationBar = $this->msg( 'parentheses' )->rawParams(
+ $lang->pipeList( array( $pagingLinks['first'],
+ $pagingLinks['last'] ) ) )->escaped() . " " .
+ $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'],
+ $pagingLinks['next'], $limits )->escaped();
+
+ if ( !is_array( $this->getIndexField() ) ) {
+ # Early return to avoid undue nesting
+ return $this->mNavigationBar;
+ }
+
+ $extra = '';
+ $first = true;
+ $msgs = $this->getOrderTypeMessages();
+ foreach ( array_keys( $msgs ) as $order ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $extra .= $this->msg( 'pipe-separator' )->escaped();
+ }
+
+ if ( $order == $this->mOrderType ) {
+ $extra .= $this->msg( $msgs[$order] )->escaped();
+ } else {
+ $extra .= $this->makeLink(
+ $this->msg( $msgs[$order] )->escaped(),
+ array( 'order' => $order )
+ );
+ }
+ }
+
+ if ( $extra !== '' ) {
+ $extra = ' ' . $this->msg( 'parentheses' )->rawParams( $extra )->escaped();
+ $this->mNavigationBar .= $extra;
+ }
+
+ return $this->mNavigationBar;
+ }
+
+ /**
+ * If this supports multiple order type messages, give the message key for
+ * enabling each one in getNavigationBar. The return type is an associative
+ * array whose keys must exactly match the keys of the array returned
+ * by getIndexField(), and whose values are message keys.
+ *
+ * @return array
+ */
+ protected function getOrderTypeMessages() {
+ return null;
+ }
+}
diff --git a/includes/Pager.php b/includes/pager/IndexPager.php
index 4a14c7e0..ce6dc50c 100644
--- a/includes/Pager.php
+++ b/includes/pager/IndexPager.php
@@ -22,19 +22,6 @@
*/
/**
- * @defgroup Pager Pager
- */
-
-/**
- * Basic pager interface.
- * @ingroup Pager
- */
-interface Pager {
- function getNavigationBar();
- function getBody();
-}
-
-/**
* IndexPager is an efficient pager which uses a (roughly unique) index in the
* data set to implement paging, rather than a "LIMIT offset,limit" clause.
* In MySQL, such a limit/offset clause requires counting through the
@@ -69,7 +56,7 @@ interface Pager {
* last page depending on the dir parameter.
*
* Subclassing the pager to implement concrete functionality should be fairly
- * simple, please see the examples in HistoryPage.php and
+ * simple, please see the examples in HistoryAction.php and
* SpecialBlockList.php. You just need to override formatRow(),
* getQueryInfo() and getIndexField(). Don't forget to call the parent
* constructor if you override it.
@@ -77,6 +64,14 @@ interface Pager {
* @ingroup Pager
*/
abstract class IndexPager extends ContextSource implements Pager {
+ /**
+ * Constants for the $mDefaultDirection field.
+ *
+ * These are boolean for historical reasons and should stay boolean for backwards-compatibility.
+ */
+ const DIR_ASCENDING = false;
+ const DIR_DESCENDING = true;
+
public $mRequest;
public $mLimitsShown = array( 20, 50, 100, 250, 500 );
public $mDefaultLimit = 50;
@@ -100,7 +95,7 @@ abstract class IndexPager extends ContextSource implements Pager {
protected $mOrderType;
/**
* $mDefaultDirection gives the direction to use when sorting results:
- * false for ascending, true for descending. If $mIsBackwards is set, we
+ * DIR_ASCENDING or DIR_DESCENDING. If $mIsBackwards is set, we
* start from the opposite end, but we still sort the page itself according
* to $mDefaultDirection. E.g., if $mDefaultDirection is false but we're
* going backwards, we'll display the last page of results, but the last
@@ -203,6 +198,7 @@ abstract class IndexPager extends ContextSource implements Pager {
$fname = __METHOD__ . ' (' . get_class( $this ) . ')';
wfProfileIn( $fname );
+ // @todo This should probably compare to DIR_DESCENDING and DIR_ASCENDING constants
$descending = ( $this->mIsBackwards == $this->mDefaultDirection );
# Plus an extra row so that we can tell the "next" link should be shown
$queryLimit = $this->mLimit + 1;
@@ -244,17 +240,18 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Set the offset from an other source than the request
*
- * @param $offset Int|String
+ * @param int|string $offset
*/
function setOffset( $offset ) {
$this->mOffset = $offset;
}
+
/**
* Set the limit from an other source than the request
*
* Verifies limit is between 1 and 5000
*
- * @param $limit Int|String
+ * @param int|string $limit
*/
function setLimit( $limit ) {
$limit = (int)$limit;
@@ -268,11 +265,20 @@ abstract class IndexPager extends ContextSource implements Pager {
}
/**
+ * Get the current limit
+ *
+ * @return int
+ */
+ function getLimit() {
+ return $this->mLimit;
+ }
+
+ /**
* Set whether a row matching exactly the offset should be also included
* in the result or not. By default this is not the case, but when the
* offset is user-supplied this might be wanted.
*
- * @param $include bool
+ * @param bool $include
*/
public function setIncludeOffset( $include ) {
$this->mIncludeOffset = $include;
@@ -282,10 +288,10 @@ abstract class IndexPager extends ContextSource implements Pager {
* Extract some useful data from the result object for use by
* the navigation bar, put it into $this
*
- * @param $isFirst bool: False if there are rows before those fetched (i.e.
+ * @param bool $isFirst False if there are rows before those fetched (i.e.
* if a "previous" link would make sense)
- * @param $limit Integer: exact query limit
- * @param $res ResultWrapper
+ * @param int $limit Exact query limit
+ * @param ResultWrapper $res
*/
function extractResultInfo( $isFirst, $limit, ResultWrapper $res ) {
$numRows = $res->numRows();
@@ -339,7 +345,7 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Get some text to go in brackets in the "function name" part of the SQL comment
*
- * @return String
+ * @return string
*/
function getSqlComment() {
return get_class( $this );
@@ -349,22 +355,24 @@ abstract class IndexPager extends ContextSource implements Pager {
* Do a query with specified parameters, rather than using the object
* context
*
- * @param string $offset index offset, inclusive
- * @param $limit Integer: exact query limit
- * @param $descending Boolean: query direction, false for ascending, true for descending
+ * @param string $offset Index offset, inclusive
+ * @param int $limit Exact query limit
+ * @param bool $descending Query direction, false for ascending, true for descending
* @return ResultWrapper
*/
public function reallyDoQuery( $offset, $limit, $descending ) {
- list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $descending );
+ list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
+ $this->buildQueryInfo( $offset, $limit, $descending );
+
return $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
}
/**
* Build variables to use by the database wrapper.
*
- * @param string $offset index offset, inclusive
- * @param $limit Integer: exact query limit
- * @param $descending Boolean: query direction, false for ascending, true for descending
+ * @param string $offset Index offset, inclusive
+ * @param int $limit Exact query limit
+ * @param bool $descending Query direction, false for ascending, true for descending
* @return array
*/
protected function buildQueryInfo( $offset, $limit, $descending ) {
@@ -397,15 +405,16 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Pre-process results; useful for performing batch existence checks, etc.
*
- * @param $result ResultWrapper
+ * @param ResultWrapper $result
*/
- protected function preprocessResults( $result ) {}
+ protected function preprocessResults( $result ) {
+ }
/**
* Get the formatted result list. Calls getStartBody(), formatRow() and
* getEndBody(), concatenates the results and returns them.
*
- * @return String
+ * @return string
*/
public function getBody() {
if ( !$this->mQueryDone ) {
@@ -445,11 +454,11 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Make a self-link
*
- * @param string $text text displayed on the link
- * @param array $query associative array of parameter to be in the query string
- * @param string $type value of the "rel" attribute
- *
- * @return String: HTML fragment
+ * @param string $text Text displayed on the link
+ * @param array $query Associative array of parameter to be in the query string
+ * @param string $type Link type used to create additional attributes, like "rel", "class" or
+ * "title". Valid values (non-exhaustive list): 'first', 'last', 'prev', 'next', 'asc', 'desc'.
+ * @return string HTML fragment
*/
function makeLink( $text, array $query = null, $type = null ) {
if ( $query === null ) {
@@ -457,15 +466,19 @@ abstract class IndexPager extends ContextSource implements Pager {
}
$attrs = array();
- if ( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) {
- # HTML5 rel attributes
+ if ( in_array( $type, array( 'prev', 'next' ) ) ) {
$attrs['rel'] = $type;
}
+ if ( in_array( $type, array( 'asc', 'desc' ) ) ) {
+ $attrs['title'] = wfMessage( $type == 'asc' ? 'sort-ascending' : 'sort-descending' )->text();
+ }
+
if ( $type ) {
$attrs['class'] = "mw-{$type}link";
}
+
return Linker::linkKnown(
$this->getTitle(),
$text,
@@ -481,13 +494,14 @@ abstract class IndexPager extends ContextSource implements Pager {
*
* @return void
*/
- protected function doBatchLookups() {}
+ protected function doBatchLookups() {
+ }
/**
* Hook into getBody(), allows text to be inserted at the start. This
* will be called even if there are no rows in the result set.
*
- * @return String
+ * @return string
*/
protected function getStartBody() {
return '';
@@ -496,7 +510,7 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Hook into getBody() for the end of the list
*
- * @return String
+ * @return string
*/
protected function getEndBody() {
return '';
@@ -506,7 +520,7 @@ abstract class IndexPager extends ContextSource implements Pager {
* Hook into getBody(), for the bit between the start and the
* end when there are no rows
*
- * @return String
+ * @return string
*/
protected function getEmptyBody() {
return '';
@@ -536,7 +550,7 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Get the number of rows in the result set
*
- * @return Integer
+ * @return int
*/
function getNumRows() {
if ( !$this->mQueryDone ) {
@@ -548,7 +562,7 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Get a URL query array for the prev, next, first and last links.
*
- * @return Array
+ * @return array
*/
function getPagingQueries() {
if ( !$this->mQueryDone ) {
@@ -587,7 +601,7 @@ abstract class IndexPager extends ContextSource implements Pager {
/**
* Returns whether to show the "navigation bar"
*
- * @return Boolean
+ * @return bool
*/
function isNavigationBarShown() {
if ( !$this->mQueryDone ) {
@@ -603,9 +617,9 @@ abstract class IndexPager extends ContextSource implements Pager {
* $linkTexts will be used. Both $linkTexts and $disabledTexts are arrays
* of HTML.
*
- * @param $linkTexts Array
- * @param $disabledTexts Array
- * @return Array
+ * @param array $linkTexts
+ * @param array $disabledTexts
+ * @return array
*/
function getPagingLinks( $linkTexts, $disabledTexts = array() ) {
$queries = $this->getPagingQueries();
@@ -650,8 +664,8 @@ abstract class IndexPager extends ContextSource implements Pager {
* representing the result row $row. Rows will be concatenated and
* returned by getBody()
*
- * @param $row Object: database row
- * @return String
+ * @param array|stdClass $row Database row
+ * @return string
*/
abstract function formatRow( $row );
@@ -665,7 +679,7 @@ abstract class IndexPager extends ContextSource implements Pager {
* options => option array
* join_conds => JOIN conditions
*
- * @return Array
+ * @return array
*/
abstract function getQueryInfo();
@@ -679,7 +693,7 @@ abstract class IndexPager extends ContextSource implements Pager {
* Needless to say, it's really not a good idea to use a non-unique index
* for this! That won't page right.
*
- * @return string|Array
+ * @return string|array
*/
abstract function getIndexField();
@@ -697,15 +711,15 @@ abstract class IndexPager extends ContextSource implements Pager {
* page_len,page_id avoids temp tables (given a page_len index). This would
* also work if page_id was non-unique but we had a page_len,page_id index.
*
- * @return Array
+ * @return array
*/
protected function getExtraSortFields() {
return array();
}
/**
- * Return the default sorting direction: false for ascending, true for
- * descending. You can also have an associative array of ordertype => dir,
+ * Return the default sorting direction: DIR_ASCENDING or DIR_DESCENDING.
+ * You can also have an associative array of ordertype => dir,
* if multiple order types are supported. In this case getIndexField()
* must return an array, and the keys of that must exactly match the keys
* of this.
@@ -720,594 +734,9 @@ abstract class IndexPager extends ContextSource implements Pager {
* particular instantiation, which is a single value. This is the set of
* all defaults for the class.
*
- * @return Boolean
+ * @return bool
*/
protected function getDefaultDirections() {
- return false;
- }
-}
-
-/**
- * IndexPager with an alphabetic list and a formatted navigation bar
- * @ingroup Pager
- */
-abstract class AlphabeticPager extends IndexPager {
-
- /**
- * Shamelessly stolen bits from ReverseChronologicalPager,
- * didn't want to do class magic as may be still revamped
- *
- * @return String HTML
- */
- function getNavigationBar() {
- if ( !$this->isNavigationBarShown() ) {
- return '';
- }
-
- if ( isset( $this->mNavigationBar ) ) {
- return $this->mNavigationBar;
- }
-
- $linkTexts = array(
- 'prev' => $this->msg( 'prevn' )->numParams( $this->mLimit )->escaped(),
- 'next' => $this->msg( 'nextn' )->numParams( $this->mLimit )->escaped(),
- 'first' => $this->msg( 'page_first' )->escaped(),
- 'last' => $this->msg( 'page_last' )->escaped()
- );
-
- $lang = $this->getLanguage();
-
- $pagingLinks = $this->getPagingLinks( $linkTexts );
- $limitLinks = $this->getLimitLinks();
- $limits = $lang->pipeList( $limitLinks );
-
- $this->mNavigationBar = $this->msg( 'parentheses' )->rawParams(
- $lang->pipeList( array( $pagingLinks['first'],
- $pagingLinks['last'] ) ) )->escaped() . " " .
- $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'],
- $pagingLinks['next'], $limits )->escaped();
-
- if ( !is_array( $this->getIndexField() ) ) {
- # Early return to avoid undue nesting
- return $this->mNavigationBar;
- }
-
- $extra = '';
- $first = true;
- $msgs = $this->getOrderTypeMessages();
- foreach ( array_keys( $msgs ) as $order ) {
- if ( $first ) {
- $first = false;
- } else {
- $extra .= $this->msg( 'pipe-separator' )->escaped();
- }
-
- if ( $order == $this->mOrderType ) {
- $extra .= $this->msg( $msgs[$order] )->escaped();
- } else {
- $extra .= $this->makeLink(
- $this->msg( $msgs[$order] )->escaped(),
- array( 'order' => $order )
- );
- }
- }
-
- if ( $extra !== '' ) {
- $extra = ' ' . $this->msg( 'parentheses' )->rawParams( $extra )->escaped();
- $this->mNavigationBar .= $extra;
- }
-
- return $this->mNavigationBar;
+ return IndexPager::DIR_ASCENDING;
}
-
- /**
- * If this supports multiple order type messages, give the message key for
- * enabling each one in getNavigationBar. The return type is an associative
- * array whose keys must exactly match the keys of the array returned
- * by getIndexField(), and whose values are message keys.
- *
- * @return Array
- */
- protected function getOrderTypeMessages() {
- return null;
- }
-}
-
-/**
- * IndexPager with a formatted navigation bar
- * @ingroup Pager
- */
-abstract class ReverseChronologicalPager extends IndexPager {
- public $mDefaultDirection = true;
- public $mYear;
- public $mMonth;
-
- function getNavigationBar() {
- if ( !$this->isNavigationBarShown() ) {
- return '';
- }
-
- if ( isset( $this->mNavigationBar ) ) {
- return $this->mNavigationBar;
- }
-
- $linkTexts = array(
- 'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
- 'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(),
- 'first' => $this->msg( 'histlast' )->escaped(),
- 'last' => $this->msg( 'histfirst' )->escaped()
- );
-
- $pagingLinks = $this->getPagingLinks( $linkTexts );
- $limitLinks = $this->getLimitLinks();
- $limits = $this->getLanguage()->pipeList( $limitLinks );
- $firstLastLinks = $this->msg( 'parentheses' )->rawParams( "{$pagingLinks['first']}" .
- $this->msg( 'pipe-separator' )->escaped() .
- "{$pagingLinks['last']}" )->escaped();
-
- $this->mNavigationBar = $firstLastLinks . ' ' .
- $this->msg( 'viewprevnext' )->rawParams(
- $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped();
-
- return $this->mNavigationBar;
- }
-
- function getDateCond( $year, $month ) {
- $year = intval( $year );
- $month = intval( $month );
-
- // Basic validity checks
- $this->mYear = $year > 0 ? $year : false;
- $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false;
-
- // Given an optional year and month, we need to generate a timestamp
- // to use as "WHERE rev_timestamp <= result"
- // Examples: year = 2006 equals < 20070101 (+000000)
- // year=2005, month=1 equals < 20050201
- // year=2005, month=12 equals < 20060101
- if ( !$this->mYear && !$this->mMonth ) {
- return;
- }
-
- if ( $this->mYear ) {
- $year = $this->mYear;
- } else {
- // If no year given, assume the current one
- $timestamp = MWTimestamp::getInstance();
- $year = $timestamp->format( 'Y' );
- // If this month hasn't happened yet this year, go back to last year's month
- if ( $this->mMonth > $timestamp->format( 'n' ) ) {
- $year--;
- }
- }
-
- if ( $this->mMonth ) {
- $month = $this->mMonth + 1;
- // For December, we want January 1 of the next year
- if ( $month > 12 ) {
- $month = 1;
- $year++;
- }
- } else {
- // No month implies we want up to the end of the year in question
- $month = 1;
- $year++;
- }
-
- // Y2K38 bug
- if ( $year > 2032 ) {
- $year = 2032;
- }
-
- $ymd = (int)sprintf( "%04d%02d01", $year, $month );
-
- if ( $ymd > 20320101 ) {
- $ymd = 20320101;
- }
-
- $this->mOffset = $this->mDb->timestamp( "${ymd}000000" );
- }
-}
-
-/**
- * Table-based display with a user-selectable sort order
- * @ingroup Pager
- */
-abstract class TablePager extends IndexPager {
- var $mSort;
- var $mCurrentRow;
-
- public function __construct( IContextSource $context = null ) {
- if ( $context ) {
- $this->setContext( $context );
- }
-
- $this->mSort = $this->getRequest()->getText( 'sort' );
- if ( !array_key_exists( $this->mSort, $this->getFieldNames() )
- || !$this->isFieldSortable( $this->mSort )
- ) {
- $this->mSort = $this->getDefaultSort();
- }
- if ( $this->getRequest()->getBool( 'asc' ) ) {
- $this->mDefaultDirection = false;
- } elseif ( $this->getRequest()->getBool( 'desc' ) ) {
- $this->mDefaultDirection = true;
- } /* Else leave it at whatever the class default is */
-
- parent::__construct();
- }
-
- /**
- * @protected
- * @return string
- */
- function getStartBody() {
- global $wgStylePath;
- $sortClass = $this->getSortHeaderClass();
-
- $s = '';
- $fields = $this->getFieldNames();
-
- # Make table header
- foreach ( $fields as $field => $name ) {
- if ( strval( $name ) == '' ) {
- $s .= Html::rawElement( 'th', array(), '&#160;' ) . "\n";
- } elseif ( $this->isFieldSortable( $field ) ) {
- $query = array( 'sort' => $field, 'limit' => $this->mLimit );
- if ( $field == $this->mSort ) {
- # This is the sorted column
- # Prepare a link that goes in the other sort order
- if ( $this->mDefaultDirection ) {
- # Descending
- $image = 'Arr_d.png';
- $query['asc'] = '1';
- $query['desc'] = '';
- $alt = $this->msg( 'descending_abbrev' )->escaped();
- } else {
- # Ascending
- $image = 'Arr_u.png';
- $query['asc'] = '';
- $query['desc'] = '1';
- $alt = $this->msg( 'ascending_abbrev' )->escaped();
- }
- $image = "$wgStylePath/common/images/$image";
- $link = $this->makeLink(
- Html::element( 'img', array( 'width' => 12, 'height' => 12,
- 'alt' => $alt, 'src' => $image ) ) . htmlspecialchars( $name ), $query );
- $s .= Html::rawElement( 'th', array( 'class' => $sortClass ), $link ) . "\n";
- } else {
- $s .= Html::rawElement( 'th', array(),
- $this->makeLink( htmlspecialchars( $name ), $query ) ) . "\n";
- }
- } else {
- $s .= Html::element( 'th', array(), $name ) . "\n";
- }
- }
-
- $tableClass = $this->getTableClass();
- $ret = Html::openElement( 'table', array( 'style' => 'border:1px;', 'class' => "mw-datatable $tableClass" ) );
- $ret .= Html::rawElement( 'thead', array(), Html::rawElement( 'tr', array(), "\n" . $s . "\n" ) );
- $ret .= Html::openElement( 'tbody' ) . "\n";
-
- return $ret;
- }
-
- /**
- * @protected
- * @return string
- */
- function getEndBody() {
- return "</tbody></table>\n";
- }
-
- /**
- * @protected
- * @return string
- */
- function getEmptyBody() {
- $colspan = count( $this->getFieldNames() );
- $msgEmpty = $this->msg( 'table_pager_empty' )->text();
- return Html::rawElement( 'tr', array(),
- Html::element( 'td', array( 'colspan' => $colspan ), $msgEmpty ) );
- }
-
- /**
- * @protected
- * @param stdClass $row
- * @return String HTML
- */
- function formatRow( $row ) {
- $this->mCurrentRow = $row; // In case formatValue etc need to know
- $s = Html::openElement( 'tr', $this->getRowAttrs( $row ) ) . "\n";
- $fieldNames = $this->getFieldNames();
-
- foreach ( $fieldNames as $field => $name ) {
- $value = isset( $row->$field ) ? $row->$field : null;
- $formatted = strval( $this->formatValue( $field, $value ) );
-
- if ( $formatted == '' ) {
- $formatted = '&#160;';
- }
-
- $s .= Html::rawElement( 'td', $this->getCellAttrs( $field, $value ), $formatted ) . "\n";
- }
-
- $s .= Html::closeElement( 'tr' ) . "\n";
-
- return $s;
- }
-
- /**
- * Get a class name to be applied to the given row.
- *
- * @protected
- *
- * @param $row Object: the database result row
- * @return String
- */
- function getRowClass( $row ) {
- return '';
- }
-
- /**
- * Get attributes to be applied to the given row.
- *
- * @protected
- *
- * @param $row Object: the database result row
- * @return Array of attribute => value
- */
- function getRowAttrs( $row ) {
- $class = $this->getRowClass( $row );
- if ( $class === '' ) {
- // Return an empty array to avoid clutter in HTML like class=""
- return array();
- } else {
- return array( 'class' => $this->getRowClass( $row ) );
- }
- }
-
- /**
- * Get any extra attributes to be applied to the given cell. Don't
- * take this as an excuse to hardcode styles; use classes and
- * CSS instead. Row context is available in $this->mCurrentRow
- *
- * @protected
- *
- * @param string $field The column
- * @param string $value The cell contents
- * @return Array of attr => value
- */
- function getCellAttrs( $field, $value ) {
- return array( 'class' => 'TablePager_col_' . $field );
- }
-
- /**
- * @protected
- * @return string
- */
- function getIndexField() {
- return $this->mSort;
- }
-
- /**
- * @protected
- * @return string
- */
- function getTableClass() {
- return 'TablePager';
- }
-
- /**
- * @protected
- * @return string
- */
- function getNavClass() {
- return 'TablePager_nav';
- }
-
- /**
- * @protected
- * @return string
- */
- function getSortHeaderClass() {
- return 'TablePager_sort';
- }
-
- /**
- * A navigation bar with images
- * @return String HTML
- */
- public function getNavigationBar() {
- global $wgStylePath;
-
- if ( !$this->isNavigationBarShown() ) {
- return '';
- }
-
- $path = "$wgStylePath/common/images";
- $labels = array(
- 'first' => 'table_pager_first',
- 'prev' => 'table_pager_prev',
- 'next' => 'table_pager_next',
- 'last' => 'table_pager_last',
- );
- $images = array(
- 'first' => 'arrow_first_25.png',
- 'prev' => 'arrow_left_25.png',
- 'next' => 'arrow_right_25.png',
- 'last' => 'arrow_last_25.png',
- );
- $disabledImages = array(
- 'first' => 'arrow_disabled_first_25.png',
- 'prev' => 'arrow_disabled_left_25.png',
- 'next' => 'arrow_disabled_right_25.png',
- 'last' => 'arrow_disabled_last_25.png',
- );
- if ( $this->getLanguage()->isRTL() ) {
- $keys = array_keys( $labels );
- $images = array_combine( $keys, array_reverse( $images ) );
- $disabledImages = array_combine( $keys, array_reverse( $disabledImages ) );
- }
-
- $linkTexts = array();
- $disabledTexts = array();
- foreach ( $labels as $type => $label ) {
- $msgLabel = $this->msg( $label )->escaped();
- $linkTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$images[$type]}",
- 'alt' => $msgLabel ) ) . "<br />$msgLabel";
- $disabledTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$disabledImages[$type]}",
- 'alt' => $msgLabel ) ) . "<br />$msgLabel";
- }
- $links = $this->getPagingLinks( $linkTexts, $disabledTexts );
-
- $s = Html::openElement( 'table', array( 'class' => $this->getNavClass() ) );
- $s .= Html::openElement( 'tr' ) . "\n";
- $width = 100 / count( $links ) . '%';
- foreach ( $labels as $type => $label ) {
- $s .= Html::rawElement( 'td', array( 'style' => "width:$width;" ), $links[$type] ) . "\n";
- }
- $s .= Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n";
- return $s;
- }
-
- /**
- * Get a "<select>" element which has options for each of the allowed limits
- *
- * @param $attribs String: Extra attributes to set
- * @return String: HTML fragment
- */
- public function getLimitSelect( $attribs = array() ) {
- $select = new XmlSelect( 'limit', false, $this->mLimit );
- $select->addOptions( $this->getLimitSelectList() );
- foreach ( $attribs as $name => $value ) {
- $select->setAttribute( $name, $value );
- }
- return $select->getHTML();
- }
-
- /**
- * Get a list of items to show in a "<select>" element of limits.
- * This can be passed directly to XmlSelect::addOptions().
- *
- * @since 1.22
- * @return array
- */
- public function getLimitSelectList() {
- # Add the current limit from the query string
- # to avoid that the limit is lost after clicking Go next time
- if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) {
- $this->mLimitsShown[] = $this->mLimit;
- sort( $this->mLimitsShown );
- }
- $ret = array();
- foreach ( $this->mLimitsShown as $key => $value ) {
- # The pair is either $index => $limit, in which case the $value
- # will be numeric, or $limit => $text, in which case the $value
- # will be a string.
- if ( is_int( $value ) ) {
- $limit = $value;
- $text = $this->getLanguage()->formatNum( $limit );
- } else {
- $limit = $key;
- $text = $value;
- }
- $ret[$text] = $limit;
- }
- return $ret;
- }
-
- /**
- * Get \<input type="hidden"\> elements for use in a method="get" form.
- * Resubmits all defined elements of the query string, except for a
- * blacklist, passed in the $blacklist parameter.
- *
- * @param array $blacklist parameters from the request query which should not be resubmitted
- * @return String: HTML fragment
- */
- function getHiddenFields( $blacklist = array() ) {
- $blacklist = (array)$blacklist;
- $query = $this->getRequest()->getQueryValues();
- foreach ( $blacklist as $name ) {
- unset( $query[$name] );
- }
- $s = '';
- foreach ( $query as $name => $value ) {
- $s .= Html::hidden( $name, $value ) . "\n";
- }
- return $s;
- }
-
- /**
- * Get a form containing a limit selection dropdown
- *
- * @return String: HTML fragment
- */
- function getLimitForm() {
- global $wgScript;
-
- return Html::rawElement(
- 'form',
- array(
- 'method' => 'get',
- 'action' => $wgScript
- ),
- "\n" . $this->getLimitDropdown()
- ) . "\n";
- }
-
- /**
- * Gets a limit selection dropdown
- *
- * @return string
- */
- function getLimitDropdown() {
- # Make the select with some explanatory text
- $msgSubmit = $this->msg( 'table_pager_limit_submit' )->escaped();
-
- return $this->msg( 'table_pager_limit' )
- ->rawParams( $this->getLimitSelect() )->escaped() .
- "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" .
- $this->getHiddenFields( array( 'limit' ) );
- }
-
- /**
- * Return true if the named field should be sortable by the UI, false
- * otherwise
- *
- * @param $field String
- */
- abstract function isFieldSortable( $field );
-
- /**
- * Format a table cell. The return value should be HTML, but use an empty
- * string not &#160; for empty cells. Do not include the <td> and </td>.
- *
- * The current result row is available as $this->mCurrentRow, in case you
- * need more context.
- *
- * @protected
- *
- * @param string $name the database field name
- * @param string $value the value retrieved from the database
- */
- abstract function formatValue( $name, $value );
-
- /**
- * The database field name used as a default sort order.
- *
- * @protected
- *
- * @return string
- */
- abstract function getDefaultSort();
-
- /**
- * An array mapping database field names to a textual description of the
- * field name, for use in the table header. The description should be plain
- * text, it will be HTML-escaped later.
- *
- * @return Array
- */
- abstract function getFieldNames();
}
diff --git a/includes/pager/Pager.php b/includes/pager/Pager.php
new file mode 100644
index 00000000..edec490c
--- /dev/null
+++ b/includes/pager/Pager.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Efficient paging for SQL queries.
+ *
+ * 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 Pager
+ */
+
+/**
+ * @defgroup Pager Pager
+ */
+
+/**
+ * Basic pager interface.
+ * @ingroup Pager
+ */
+interface Pager {
+ function getNavigationBar();
+ function getBody();
+}
diff --git a/includes/pager/ReverseChronologicalPager.php b/includes/pager/ReverseChronologicalPager.php
new file mode 100644
index 00000000..4f8c438d
--- /dev/null
+++ b/includes/pager/ReverseChronologicalPager.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Efficient paging for SQL queries.
+ *
+ * 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 Pager
+ */
+
+/**
+ * IndexPager with a formatted navigation bar
+ * @ingroup Pager
+ */
+abstract class ReverseChronologicalPager extends IndexPager {
+ public $mDefaultDirection = IndexPager::DIR_DESCENDING;
+ public $mYear;
+ public $mMonth;
+
+ function getNavigationBar() {
+ if ( !$this->isNavigationBarShown() ) {
+ return '';
+ }
+
+ if ( isset( $this->mNavigationBar ) ) {
+ return $this->mNavigationBar;
+ }
+
+ $linkTexts = array(
+ 'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
+ 'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(),
+ 'first' => $this->msg( 'histlast' )->escaped(),
+ 'last' => $this->msg( 'histfirst' )->escaped()
+ );
+
+ $pagingLinks = $this->getPagingLinks( $linkTexts );
+ $limitLinks = $this->getLimitLinks();
+ $limits = $this->getLanguage()->pipeList( $limitLinks );
+ $firstLastLinks = $this->msg( 'parentheses' )->rawParams( "{$pagingLinks['first']}" .
+ $this->msg( 'pipe-separator' )->escaped() .
+ "{$pagingLinks['last']}" )->escaped();
+
+ $this->mNavigationBar = $firstLastLinks . ' ' .
+ $this->msg( 'viewprevnext' )->rawParams(
+ $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped();
+
+ return $this->mNavigationBar;
+ }
+
+ function getDateCond( $year, $month ) {
+ $year = intval( $year );
+ $month = intval( $month );
+
+ // Basic validity checks
+ $this->mYear = $year > 0 ? $year : false;
+ $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false;
+
+ // Given an optional year and month, we need to generate a timestamp
+ // to use as "WHERE rev_timestamp <= result"
+ // Examples: year = 2006 equals < 20070101 (+000000)
+ // year=2005, month=1 equals < 20050201
+ // year=2005, month=12 equals < 20060101
+ if ( !$this->mYear && !$this->mMonth ) {
+ return;
+ }
+
+ if ( $this->mYear ) {
+ $year = $this->mYear;
+ } else {
+ // If no year given, assume the current one
+ $timestamp = MWTimestamp::getInstance();
+ $year = $timestamp->format( 'Y' );
+ // If this month hasn't happened yet this year, go back to last year's month
+ if ( $this->mMonth > $timestamp->format( 'n' ) ) {
+ $year--;
+ }
+ }
+
+ if ( $this->mMonth ) {
+ $month = $this->mMonth + 1;
+ // For December, we want January 1 of the next year
+ if ( $month > 12 ) {
+ $month = 1;
+ $year++;
+ }
+ } else {
+ // No month implies we want up to the end of the year in question
+ $month = 1;
+ $year++;
+ }
+
+ // Y2K38 bug
+ if ( $year > 2032 ) {
+ $year = 2032;
+ }
+
+ $ymd = (int)sprintf( "%04d%02d01", $year, $month );
+
+ if ( $ymd > 20320101 ) {
+ $ymd = 20320101;
+ }
+
+ $this->mOffset = $this->mDb->timestamp( "${ymd}000000" );
+ }
+}
diff --git a/includes/pager/TablePager.php b/includes/pager/TablePager.php
new file mode 100644
index 00000000..80955398
--- /dev/null
+++ b/includes/pager/TablePager.php
@@ -0,0 +1,469 @@
+<?php
+/**
+ * Efficient paging for SQL queries.
+ *
+ * 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 Pager
+ */
+
+/**
+ * Table-based display with a user-selectable sort order
+ * @ingroup Pager
+ */
+abstract class TablePager extends IndexPager {
+ protected $mSort;
+
+ protected $mCurrentRow;
+
+ public function __construct( IContextSource $context = null ) {
+ if ( $context ) {
+ $this->setContext( $context );
+ }
+
+ $this->mSort = $this->getRequest()->getText( 'sort' );
+ if ( !array_key_exists( $this->mSort, $this->getFieldNames() )
+ || !$this->isFieldSortable( $this->mSort )
+ ) {
+ $this->mSort = $this->getDefaultSort();
+ }
+ if ( $this->getRequest()->getBool( 'asc' ) ) {
+ $this->mDefaultDirection = IndexPager::DIR_ASCENDING;
+ } elseif ( $this->getRequest()->getBool( 'desc' ) ) {
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
+ } /* Else leave it at whatever the class default is */
+
+ parent::__construct();
+ }
+
+ /**
+ * Get the formatted result list. Calls getStartBody(), formatRow() and getEndBody(), concatenates
+ * the results and returns them.
+ *
+ * Also adds the required styles to our OutputPage object (this means that if context wasn't
+ * passed to constructor or otherwise set up, you will get a pager with missing styles).
+ *
+ * This method has been made 'final' in 1.24. There's no reason to override it, and if there exist
+ * any subclasses that do, the style loading hack is probably broken in them. Let's fail fast
+ * rather than mysteriously render things wrong.
+ *
+ * @deprecated since 1.24, use getBodyOutput() or getFullOutput() instead
+ * @return string
+ */
+ final public function getBody() {
+ $this->getOutput()->addModuleStyles( $this->getModuleStyles() );
+ return parent::getBody();
+ }
+
+ /**
+ * Get the formatted result list.
+ *
+ * Calls getBody() and getModuleStyles() and builds a ParserOutput object. (This is a bit hacky
+ * but works well.)
+ *
+ * @since 1.24
+ * @return ParserOutput
+ */
+ public function getBodyOutput() {
+ $body = parent::getBody();
+
+ $pout = new ParserOutput;
+ $pout->setText( $body );
+ $pout->addModuleStyles( $this->getModuleStyles() );
+ return $pout;
+ }
+
+ /**
+ * Get the formatted result list, with navigation bars.
+ *
+ * Calls getBody(), getNavigationBar() and getModuleStyles() and
+ * builds a ParserOutput object. (This is a bit hacky but works well.)
+ *
+ * @since 1.24
+ * @return ParserOutput
+ */
+ public function getFullOutput() {
+ $navigation = $this->getNavigationBar();
+ $body = parent::getBody();
+
+ $pout = new ParserOutput;
+ $pout->setText( $navigation . $body . $navigation );
+ $pout->addModuleStyles( $this->getModuleStyles() );
+ return $pout;
+ }
+
+ /**
+ * @protected
+ * @return string
+ */
+ function getStartBody() {
+ $sortClass = $this->getSortHeaderClass();
+
+ $s = '';
+ $fields = $this->getFieldNames();
+
+ // Make table header
+ foreach ( $fields as $field => $name ) {
+ if ( strval( $name ) == '' ) {
+ $s .= Html::rawElement( 'th', array(), '&#160;' ) . "\n";
+ } elseif ( $this->isFieldSortable( $field ) ) {
+ $query = array( 'sort' => $field, 'limit' => $this->mLimit );
+ $linkType = null;
+ $class = null;
+
+ if ( $this->mSort == $field ) {
+ // The table is sorted by this field already, make a link to sort in the other direction
+ // We don't actually know in which direction other fields will be sorted by default…
+ if ( $this->mDefaultDirection == IndexPager::DIR_DESCENDING ) {
+ $linkType = 'asc';
+ $class = "$sortClass TablePager_sort-descending";
+ $query['asc'] = '1';
+ $query['desc'] = '';
+ } else {
+ $linkType = 'desc';
+ $class = "$sortClass TablePager_sort-ascending";
+ $query['asc'] = '';
+ $query['desc'] = '1';
+ }
+ }
+
+ $link = $this->makeLink( htmlspecialchars( $name ), $query, $linkType );
+ $s .= Html::rawElement( 'th', array( 'class' => $class ), $link ) . "\n";
+ } else {
+ $s .= Html::element( 'th', array(), $name ) . "\n";
+ }
+ }
+
+ $tableClass = $this->getTableClass();
+ $ret = Html::openElement( 'table', array(
+ 'class' => "mw-datatable $tableClass" )
+ );
+ $ret .= Html::rawElement( 'thead', array(), Html::rawElement( 'tr', array(), "\n" . $s . "\n" ) );
+ $ret .= Html::openElement( 'tbody' ) . "\n";
+
+ return $ret;
+ }
+
+ /**
+ * @protected
+ * @return string
+ */
+ function getEndBody() {
+ return "</tbody></table>\n";
+ }
+
+ /**
+ * @protected
+ * @return string
+ */
+ function getEmptyBody() {
+ $colspan = count( $this->getFieldNames() );
+ $msgEmpty = $this->msg( 'table_pager_empty' )->text();
+ return Html::rawElement( 'tr', array(),
+ Html::element( 'td', array( 'colspan' => $colspan ), $msgEmpty ) );
+ }
+
+ /**
+ * @protected
+ * @param stdClass $row
+ * @return string HTML
+ */
+ function formatRow( $row ) {
+ $this->mCurrentRow = $row; // In case formatValue etc need to know
+ $s = Html::openElement( 'tr', $this->getRowAttrs( $row ) ) . "\n";
+ $fieldNames = $this->getFieldNames();
+
+ foreach ( $fieldNames as $field => $name ) {
+ $value = isset( $row->$field ) ? $row->$field : null;
+ $formatted = strval( $this->formatValue( $field, $value ) );
+
+ if ( $formatted == '' ) {
+ $formatted = '&#160;';
+ }
+
+ $s .= Html::rawElement( 'td', $this->getCellAttrs( $field, $value ), $formatted ) . "\n";
+ }
+
+ $s .= Html::closeElement( 'tr' ) . "\n";
+
+ return $s;
+ }
+
+ /**
+ * Get a class name to be applied to the given row.
+ *
+ * @protected
+ *
+ * @param object $row The database result row
+ * @return string
+ */
+ function getRowClass( $row ) {
+ return '';
+ }
+
+ /**
+ * Get attributes to be applied to the given row.
+ *
+ * @protected
+ *
+ * @param object $row The database result row
+ * @return array Array of attribute => value
+ */
+ function getRowAttrs( $row ) {
+ $class = $this->getRowClass( $row );
+ if ( $class === '' ) {
+ // Return an empty array to avoid clutter in HTML like class=""
+ return array();
+ } else {
+ return array( 'class' => $this->getRowClass( $row ) );
+ }
+ }
+
+ /**
+ * Get any extra attributes to be applied to the given cell. Don't
+ * take this as an excuse to hardcode styles; use classes and
+ * CSS instead. Row context is available in $this->mCurrentRow
+ *
+ * @protected
+ *
+ * @param string $field The column
+ * @param string $value The cell contents
+ * @return array Array of attr => value
+ */
+ function getCellAttrs( $field, $value ) {
+ return array( 'class' => 'TablePager_col_' . $field );
+ }
+
+ /**
+ * @protected
+ * @return string
+ */
+ function getIndexField() {
+ return $this->mSort;
+ }
+
+ /**
+ * @protected
+ * @return string
+ */
+ function getTableClass() {
+ return 'TablePager';
+ }
+
+ /**
+ * @protected
+ * @return string
+ */
+ function getNavClass() {
+ return 'TablePager_nav';
+ }
+
+ /**
+ * @protected
+ * @return string
+ */
+ function getSortHeaderClass() {
+ return 'TablePager_sort';
+ }
+
+ /**
+ * A navigation bar with images
+ * @return string HTML
+ */
+ public function getNavigationBar() {
+ if ( !$this->isNavigationBarShown() ) {
+ return '';
+ }
+
+ $labels = array(
+ 'first' => 'table_pager_first',
+ 'prev' => 'table_pager_prev',
+ 'next' => 'table_pager_next',
+ 'last' => 'table_pager_last',
+ );
+
+ $linkTexts = array();
+ $disabledTexts = array();
+ foreach ( $labels as $type => $label ) {
+ $msgLabel = $this->msg( $label )->escaped();
+ $linkTexts[$type] = "<div class='TablePager_nav-enabled'>$msgLabel</div>";
+ $disabledTexts[$type] = "<div class='TablePager_nav-disabled'>$msgLabel</div>";
+ }
+ $links = $this->getPagingLinks( $linkTexts, $disabledTexts );
+
+ $s = Html::openElement( 'table', array( 'class' => $this->getNavClass() ) );
+ $s .= Html::openElement( 'tr' ) . "\n";
+ $width = 100 / count( $links ) . '%';
+ foreach ( $labels as $type => $label ) {
+ // We want every cell to have the same width. We could use table-layout: fixed; in CSS,
+ // but it only works if we specify the width of a cell or the table and we don't want to.
+ // There is no better way. <http://www.w3.org/TR/CSS2/tables.html#fixed-table-layout>
+ $s .= Html::rawElement( 'td',
+ array( 'style' => "width: $width;", 'class' => "TablePager_nav-$type" ),
+ $links[$type] ) . "\n";
+ }
+ $s .= Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n";
+ return $s;
+ }
+
+ /**
+ * ResourceLoader modules that must be loaded to provide correct styling for this pager
+ * @since 1.24
+ * @return string[]
+ */
+ public function getModuleStyles() {
+ return array( 'mediawiki.pager.tablePager' );
+ }
+
+ /**
+ * Get a "<select>" element which has options for each of the allowed limits
+ *
+ * @param string $attribs Extra attributes to set
+ * @return string HTML fragment
+ */
+ public function getLimitSelect( $attribs = array() ) {
+ $select = new XmlSelect( 'limit', false, $this->mLimit );
+ $select->addOptions( $this->getLimitSelectList() );
+ foreach ( $attribs as $name => $value ) {
+ $select->setAttribute( $name, $value );
+ }
+ return $select->getHTML();
+ }
+
+ /**
+ * Get a list of items to show in a "<select>" element of limits.
+ * This can be passed directly to XmlSelect::addOptions().
+ *
+ * @since 1.22
+ * @return array
+ */
+ public function getLimitSelectList() {
+ # Add the current limit from the query string
+ # to avoid that the limit is lost after clicking Go next time
+ if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) {
+ $this->mLimitsShown[] = $this->mLimit;
+ sort( $this->mLimitsShown );
+ }
+ $ret = array();
+ foreach ( $this->mLimitsShown as $key => $value ) {
+ # The pair is either $index => $limit, in which case the $value
+ # will be numeric, or $limit => $text, in which case the $value
+ # will be a string.
+ if ( is_int( $value ) ) {
+ $limit = $value;
+ $text = $this->getLanguage()->formatNum( $limit );
+ } else {
+ $limit = $key;
+ $text = $value;
+ }
+ $ret[$text] = $limit;
+ }
+ return $ret;
+ }
+
+ /**
+ * Get \<input type="hidden"\> elements for use in a method="get" form.
+ * Resubmits all defined elements of the query string, except for a
+ * blacklist, passed in the $blacklist parameter.
+ *
+ * @param array $blacklist Parameters from the request query which should not be resubmitted
+ * @return string HTML fragment
+ */
+ function getHiddenFields( $blacklist = array() ) {
+ $blacklist = (array)$blacklist;
+ $query = $this->getRequest()->getQueryValues();
+ foreach ( $blacklist as $name ) {
+ unset( $query[$name] );
+ }
+ $s = '';
+ foreach ( $query as $name => $value ) {
+ $s .= Html::hidden( $name, $value ) . "\n";
+ }
+ return $s;
+ }
+
+ /**
+ * Get a form containing a limit selection dropdown
+ *
+ * @return string HTML fragment
+ */
+ function getLimitForm() {
+ return Html::rawElement(
+ 'form',
+ array(
+ 'method' => 'get',
+ 'action' => wfScript(),
+ ),
+ "\n" . $this->getLimitDropdown()
+ ) . "\n";
+ }
+
+ /**
+ * Gets a limit selection dropdown
+ *
+ * @return string
+ */
+ function getLimitDropdown() {
+ # Make the select with some explanatory text
+ $msgSubmit = $this->msg( 'table_pager_limit_submit' )->escaped();
+
+ return $this->msg( 'table_pager_limit' )
+ ->rawParams( $this->getLimitSelect() )->escaped() .
+ "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" .
+ $this->getHiddenFields( array( 'limit' ) );
+ }
+
+ /**
+ * Return true if the named field should be sortable by the UI, false
+ * otherwise
+ *
+ * @param string $field
+ */
+ abstract function isFieldSortable( $field );
+
+ /**
+ * Format a table cell. The return value should be HTML, but use an empty
+ * string not &#160; for empty cells. Do not include the <td> and </td>.
+ *
+ * The current result row is available as $this->mCurrentRow, in case you
+ * need more context.
+ *
+ * @protected
+ *
+ * @param string $name The database field name
+ * @param string $value The value retrieved from the database
+ */
+ abstract function formatValue( $name, $value );
+
+ /**
+ * The database field name used as a default sort order.
+ *
+ * @protected
+ *
+ * @return string
+ */
+ abstract function getDefaultSort();
+
+ /**
+ * An array mapping database field names to a textual description of the
+ * field name, for use in the table header. The description should be plain
+ * text, it will be HTML-escaped later.
+ *
+ * @return array
+ */
+ abstract function getFieldNames();
+}
diff --git a/includes/parser/CacheTime.php b/includes/parser/CacheTime.php
index 8190a8a0..94abc266 100644
--- a/includes/parser/CacheTime.php
+++ b/includes/parser/CacheTime.php
@@ -27,24 +27,64 @@
* @ingroup Parser
*/
class CacheTime {
+ /** @var array|bool ParserOptions which have been taken into account to
+ * produce output or false if not available.
+ */
+ public $mUsedOptions;
- var $mVersion = Parser::VERSION, # Compatibility check
+ public $mVersion = Parser::VERSION, # Compatibility check
$mCacheTime = '', # Time when this object was generated, or -1 for uncacheable. Used in ParserCache.
$mCacheExpiry = null, # Seconds after which the object should expire, use 0 for uncachable. Used in ParserCache.
- $mContainsOldMagic; # Boolean variable indicating if the input contained variables like {{CURRENTDAY}}
+ $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}}
+ $mCacheRevisionId = null; # Revision ID that was parsed
- function getCacheTime() { return $this->mCacheTime; }
+ /**
+ * @return string TS_MW timestamp
+ */
+ public function getCacheTime() {
+ return wfTimestamp( TS_MW, $this->mCacheTime );
+ }
- function containsOldMagic() { return $this->mContainsOldMagic; }
- function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
+ /**
+ * @return bool
+ */
+ public function containsOldMagic() {
+ return $this->mContainsOldMagic;
+ }
+
+ /**
+ * @param bool $com
+ * @return bool
+ */
+ public function setContainsOldMagic( $com ) {
+ return wfSetVar( $this->mContainsOldMagic, $com );
+ }
/**
* setCacheTime() sets the timestamp expressing when the page has been rendered.
- * This doesn not control expiry, see updateCacheExpiry() for that!
- * @param $t string
+ * This does not control expiry, see updateCacheExpiry() for that!
+ * @param string $t
* @return string
*/
- function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
+ public function setCacheTime( $t ) {
+ return wfSetVar( $this->mCacheTime, $t );
+ }
+
+ /**
+ * @since 1.23
+ * @return int|null Revision id, if any was set
+ */
+ public function getCacheRevisionId() {
+ return $this->mCacheRevisionId;
+ }
+
+ /**
+ * @since 1.23
+ * @param int $id Revision id
+ */
+ public function setCacheRevisionId( $id ) {
+ $this->mCacheRevisionId = $id;
+ }
/**
* Sets the number of seconds after which this object should expire.
@@ -54,9 +94,9 @@ class CacheTime {
* or equal to the smallest number that was provided as an argument to
* updateCacheExpiry().
*
- * @param $seconds number
+ * @param int $seconds
*/
- function updateCacheExpiry( $seconds ) {
+ public function updateCacheExpiry( $seconds ) {
$seconds = (int)$seconds;
if ( $this->mCacheExpiry === null || $this->mCacheExpiry > $seconds ) {
@@ -78,7 +118,7 @@ class CacheTime {
* value of $wgParserCacheExpireTime.
* @return int|mixed|null
*/
- function getCacheExpiry() {
+ public function getCacheExpiry() {
global $wgParserCacheExpireTime;
if ( $this->mCacheTime < 0 ) {
@@ -107,7 +147,7 @@ class CacheTime {
/**
* @return bool
*/
- function isCacheable() {
+ public function isCacheable() {
return $this->getCacheExpiry() > 0;
}
@@ -116,17 +156,35 @@ class CacheTime {
* per-article cache invalidation timestamps, or if it comes from
* an incompatible older version.
*
- * @param string $touched the affected article's last touched timestamp
- * @return Boolean
+ * @param string $touched The affected article's last touched timestamp
+ * @return bool
*/
public function expired( $touched ) {
global $wgCacheEpoch;
- return !$this->isCacheable() || // parser says it's uncacheable
- $this->getCacheTime() < $touched ||
- $this->getCacheTime() <= $wgCacheEpoch ||
- $this->getCacheTime() < wfTimestamp( TS_MW, time() - $this->getCacheExpiry() ) || // expiry period has passed
- !isset( $this->mVersion ) ||
- version_compare( $this->mVersion, Parser::VERSION, "lt" );
+
+ return !$this->isCacheable() // parser says it's uncacheable
+ || $this->getCacheTime() < $touched
+ || $this->getCacheTime() <= $wgCacheEpoch
+ || $this->getCacheTime() <
+ wfTimestamp( TS_MW, time() - $this->getCacheExpiry() ) // expiry period has passed
+ || !isset( $this->mVersion )
+ || version_compare( $this->mVersion, Parser::VERSION, "lt" );
}
+ /**
+ * Return true if this cached output object is for a different revision of
+ * the page.
+ *
+ * @todo We always return false if $this->getCacheRevisionId() is null;
+ * this prevents invalidating the whole parser cache when this change is
+ * deployed. Someday that should probably be changed.
+ *
+ * @since 1.23
+ * @param int $id The affected article's current revision id
+ * @return bool
+ */
+ public function isDifferentRevision( $id ) {
+ $cached = $this->getCacheRevisionId();
+ return $cached !== null && $id !== $cached;
+ }
}
diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index 4b6eeca2..eacbecd4 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -27,97 +27,69 @@
*/
class CoreParserFunctions {
/**
- * @param $parser Parser
+ * @param Parser $parser
* @return void
*/
- static function register( $parser ) {
+ public static function register( $parser ) {
global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
- # Syntax for arguments (see self::setFunctionHook):
+ # Syntax for arguments (see Parser::setFunctionHook):
# "name for lookup in localized magic words array",
# function callback,
# optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
# instead of {{#int:...}})
+ $noHashFunctions = array(
+ 'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
+ 'localurl', 'localurle', 'fullurl', 'fullurle', 'canonicalurl',
+ 'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural',
+ 'numberofpages', 'numberofusers', 'numberofactiveusers',
+ 'numberofarticles', 'numberoffiles', 'numberofadmins',
+ 'numberingroup', 'numberofedits', 'numberofviews', 'language',
+ 'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath',
+ 'pagesincategory', 'pagesize', 'protectionlevel',
+ 'namespacee', 'namespacenumber', 'talkspace', 'talkspacee',
+ 'subjectspace', 'subjectspacee', 'pagename', 'pagenamee',
+ 'fullpagename', 'fullpagenamee', 'rootpagename', 'rootpagenamee',
+ 'basepagename', 'basepagenamee', 'subpagename', 'subpagenamee',
+ 'talkpagename', 'talkpagenamee', 'subjectpagename',
+ 'subjectpagenamee', 'pageid', 'revisionid', 'revisionday',
+ 'revisionday2', 'revisionmonth', 'revisionmonth1', 'revisionyear',
+ 'revisiontimestamp', 'revisionuser', 'cascadingsources',
+ );
+ foreach ( $noHashFunctions as $func ) {
+ $parser->setFunctionHook( $func, array( __CLASS__, $func ), SFH_NO_HASH );
+ }
- $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'ns', array( __CLASS__, 'ns' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'nse', array( __CLASS__, 'nse' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'urlencode', array( __CLASS__, 'urlencode' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'lcfirst', array( __CLASS__, 'lcfirst' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'ucfirst', array( __CLASS__, 'ucfirst' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'lc', array( __CLASS__, 'lc' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'uc', array( __CLASS__, 'uc' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'localurl', array( __CLASS__, 'localurl' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'localurle', array( __CLASS__, 'localurle' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'fullurl', array( __CLASS__, 'fullurl' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'fullurle', array( __CLASS__, 'fullurle' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'canonicalurl', array( __CLASS__, 'canonicalurl' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'canonicalurle', array( __CLASS__, 'canonicalurle' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'formatnum', array( __CLASS__, 'formatnum' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'grammar', array( __CLASS__, 'grammar' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'gender', array( __CLASS__, 'gender' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'plural', array( __CLASS__, 'plural' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberofpages', array( __CLASS__, 'numberofpages' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberofusers', array( __CLASS__, 'numberofusers' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberofactiveusers', array( __CLASS__, 'numberofactiveusers' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberofarticles', array( __CLASS__, 'numberofarticles' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberoffiles', array( __CLASS__, 'numberoffiles' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberofadmins', array( __CLASS__, 'numberofadmins' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberingroup', array( __CLASS__, 'numberingroup' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberofedits', array( __CLASS__, 'numberofedits' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'numberofviews', array( __CLASS__, 'numberofviews' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'language', array( __CLASS__, 'language' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'padleft', array( __CLASS__, 'padleft' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'padright', array( __CLASS__, 'padright' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'anchorencode', array( __CLASS__, 'anchorencode' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'special', array( __CLASS__, 'special' ) );
- $parser->setFunctionHook( 'speciale', array( __CLASS__, 'speciale' ) );
- $parser->setFunctionHook( 'defaultsort', array( __CLASS__, 'defaultsort' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'filepath', array( __CLASS__, 'filepath' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'pagesincategory', array( __CLASS__, 'pagesincategory' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'pagesize', array( __CLASS__, 'pagesize' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'protectionlevel', array( __CLASS__, 'protectionlevel' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'namespacee', array( __CLASS__, 'namespacee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'namespacenumber', array( __CLASS__, 'namespacenumber' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'talkspace', array( __CLASS__, 'talkspace' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'talkspacee', array( __CLASS__, 'talkspacee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'subjectspace', array( __CLASS__, 'subjectspace' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'subjectspacee', array( __CLASS__, 'subjectspacee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'pagename', array( __CLASS__, 'pagename' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'pagenamee', array( __CLASS__, 'pagenamee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'fullpagename', array( __CLASS__, 'fullpagename' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'fullpagenamee', array( __CLASS__, 'fullpagenamee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'rootpagename', array( __CLASS__, 'rootpagename' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'rootpagenamee', array( __CLASS__, 'rootpagenamee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'basepagename', array( __CLASS__, 'basepagename' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'basepagenamee', array( __CLASS__, 'basepagenamee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'subpagename', array( __CLASS__, 'subpagename' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'subpagenamee', array( __CLASS__, 'subpagenamee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'talkpagename', array( __CLASS__, 'talkpagename' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'talkpagenamee', array( __CLASS__, 'talkpagenamee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'subjectpagename', array( __CLASS__, 'subjectpagename' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'subjectpagenamee', array( __CLASS__, 'subjectpagenamee' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), SFH_OBJECT_ARGS );
- $parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) );
+ $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'special', array( __CLASS__, 'special' ) );
+ $parser->setFunctionHook( 'speciale', array( __CLASS__, 'speciale' ) );
+ $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), SFH_OBJECT_ARGS );
+ $parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) );
if ( $wgAllowDisplayTitle ) {
$parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), SFH_NO_HASH );
}
if ( $wgAllowSlowParserFunctions ) {
- $parser->setFunctionHook( 'pagesinnamespace', array( __CLASS__, 'pagesinnamespace' ), SFH_NO_HASH );
+ $parser->setFunctionHook(
+ 'pagesinnamespace',
+ array( __CLASS__, 'pagesinnamespace' ),
+ SFH_NO_HASH
+ );
}
}
/**
- * @param $parser Parser
+ * @param Parser $parser
* @param string $part1
* @return array
*/
- static function intFunction( $parser, $part1 = '' /*, ... */ ) {
+ public static function intFunction( $parser, $part1 = '' /*, ... */ ) {
if ( strval( $part1 ) !== '' ) {
$args = array_slice( func_get_args(), 2 );
- $message = wfMessage( $part1, $args )->inLanguage( $parser->getOptions()->getUserLangObj() )->plain();
+ $message = wfMessage( $part1, $args )
+ ->inLanguage( $parser->getOptions()->getUserLangObj() )->plain();
+
return array( $message, 'noparse' => false );
} else {
return array( 'found' => false );
@@ -125,12 +97,13 @@ class CoreParserFunctions {
}
/**
- * @param $parser Parser
- * @param $date
- * @param null $defaultPref
- * @return mixed|string
+ * @param Parser $parser
+ * @param string $date
+ * @param string $defaultPref
+ *
+ * @return string
*/
- static function formatDate( $parser, $date, $defaultPref = null ) {
+ public static function formatDate( $parser, $date, $defaultPref = null ) {
$lang = $parser->getFunctionLang();
$df = DateFormatter::getInstance( $lang );
@@ -148,7 +121,7 @@ class CoreParserFunctions {
return $date;
}
- static function ns( $parser, $part1 = '' ) {
+ public static function ns( $parser, $part1 = '' ) {
global $wgContLang;
if ( intval( $part1 ) || $part1 == "0" ) {
$index = intval( $part1 );
@@ -162,7 +135,7 @@ class CoreParserFunctions {
}
}
- static function nse( $parser, $part1 = '' ) {
+ public static function nse( $parser, $part1 = '' ) {
$ret = self::ns( $parser, $part1 );
if ( is_string( $ret ) ) {
$ret = wfUrlencode( str_replace( ' ', '_', $ret ) );
@@ -177,12 +150,12 @@ class CoreParserFunctions {
* Or to encode a value for the HTTP "path", spaces are encoded as '%20'.
* For links to "wiki"s, or similar software, spaces are encoded as '_',
*
- * @param $parser Parser object
+ * @param Parser $parser
* @param string $s The text to encode.
* @param string $arg (optional): The type of encoding.
* @return string
*/
- static function urlencode( $parser, $s = '', $arg = null ) {
+ public static function urlencode( $parser, $s = '', $arg = null ) {
static $magicWords = null;
if ( is_null( $magicWords ) ) {
$magicWords = new MagicWordArray( array( 'url_path', 'url_query', 'url_wiki' ) );
@@ -208,44 +181,76 @@ class CoreParserFunctions {
return $parser->markerSkipCallback( $s, $func );
}
- static function lcfirst( $parser, $s = '' ) {
+ public static function lcfirst( $parser, $s = '' ) {
global $wgContLang;
return $wgContLang->lcfirst( $s );
}
- static function ucfirst( $parser, $s = '' ) {
+ public static function ucfirst( $parser, $s = '' ) {
global $wgContLang;
return $wgContLang->ucfirst( $s );
}
/**
- * @param $parser Parser
+ * @param Parser $parser
* @param string $s
- * @return
+ * @return string
*/
- static function lc( $parser, $s = '' ) {
+ public static function lc( $parser, $s = '' ) {
global $wgContLang;
return $parser->markerSkipCallback( $s, array( $wgContLang, 'lc' ) );
}
/**
- * @param $parser Parser
+ * @param Parser $parser
* @param string $s
- * @return
+ * @return string
*/
- static function uc( $parser, $s = '' ) {
+ public static function uc( $parser, $s = '' ) {
global $wgContLang;
return $parser->markerSkipCallback( $s, array( $wgContLang, 'uc' ) );
}
- static function localurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getLocalURL', $s, $arg ); }
- static function localurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeLocalURL', $s, $arg ); }
- static function fullurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getFullURL', $s, $arg ); }
- static function fullurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeFullURL', $s, $arg ); }
- static function canonicalurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getCanonicalURL', $s, $arg ); }
- static function canonicalurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeCanonicalURL', $s, $arg ); }
+ public static function localurl( $parser, $s = '', $arg = null ) {
+ return self::urlFunction( 'getLocalURL', $s, $arg );
+ }
+
+ public static function localurle( $parser, $s = '', $arg = null ) {
+ $temp = self::urlFunction( 'getLocalURL', $s, $arg );
+ if ( !is_string( $temp ) ) {
+ return $temp;
+ } else {
+ return htmlspecialchars( $temp );
+ }
+ }
+
+ public static function fullurl( $parser, $s = '', $arg = null ) {
+ return self::urlFunction( 'getFullURL', $s, $arg );
+ }
+
+ public static function fullurle( $parser, $s = '', $arg = null ) {
+ $temp = self::urlFunction( 'getFullURL', $s, $arg );
+ if ( !is_string( $temp ) ) {
+ return $temp;
+ } else {
+ return htmlspecialchars( $temp );
+ }
+ }
+
+ public static function canonicalurl( $parser, $s = '', $arg = null ) {
+ return self::urlFunction( 'getCanonicalURL', $s, $arg );
+ }
- static function urlFunction( $func, $s = '', $arg = null ) {
+ public static function canonicalurle( $parser, $s = '', $arg = null ) {
+ $temp = self::urlFunction( 'getCanonicalURL', $s, $arg );
+ if ( !is_string( $temp ) ) {
+ return $temp;
+ } else {
+ return htmlspecialchars( $temp );
+ }
+ }
+
+ public static function urlFunction( $func, $s = '', $arg = null ) {
$title = Title::newFromText( $s );
# Due to order of execution of a lot of bits, the values might be encoded
# before arriving here; if that's true, then the title can't be created
@@ -271,12 +276,12 @@ class CoreParserFunctions {
}
/**
- * @param $parser Parser
+ * @param Parser $parser
* @param string $num
* @param string $arg
* @return string
*/
- static function formatnum( $parser, $num = '', $arg = null ) {
+ public static function formatnum( $parser, $num = '', $arg = null ) {
if ( self::matchAgainstMagicword( 'rawsuffix', $arg ) ) {
$func = array( $parser->getFunctionLang(), 'parseFormattedNumber' );
} elseif ( self::matchAgainstMagicword( 'nocommafysuffix', $arg ) ) {
@@ -288,22 +293,22 @@ class CoreParserFunctions {
}
/**
- * @param $parser Parser
+ * @param Parser $parser
* @param string $case
* @param string $word
- * @return
+ * @return string
*/
- static function grammar( $parser, $case = '', $word = '' ) {
+ public static function grammar( $parser, $case = '', $word = '' ) {
$word = $parser->killMarkers( $word );
return $parser->getFunctionLang()->convertGrammar( $word, $case );
}
/**
- * @param $parser Parser
- * @param $username string
- * @return
+ * @param Parser $parser
+ * @param string $username
+ * @return string
*/
- static function gender( $parser, $username ) {
+ public static function gender( $parser, $username ) {
wfProfileIn( __METHOD__ );
$forms = array_slice( func_get_args(), 2 );
@@ -341,11 +346,11 @@ class CoreParserFunctions {
}
/**
- * @param $parser Parser
+ * @param Parser $parser
* @param string $text
- * @return
+ * @return string
*/
- static function plural( $parser, $text = '' ) {
+ public static function plural( $parser, $text = '' ) {
$forms = array_slice( func_get_args(), 2 );
$text = $parser->getFunctionLang()->parseFormattedNumber( $text );
settype( $text, ctype_digit( $text ) ? 'int' : 'float' );
@@ -356,13 +361,20 @@ class CoreParserFunctions {
* Override the title of the page when viewed, provided we've been given a
* title which will normalise to the canonical title
*
- * @param $parser Parser: parent parser
- * @param string $text desired title text
- * @return String
+ * @param Parser $parser Parent parser
+ * @param string $text Desired title text
+ * @param string $uarg
+ * @return string
*/
- static function displaytitle( $parser, $text = '' ) {
+ public static function displaytitle( $parser, $text = '', $uarg = '' ) {
global $wgRestrictDisplayTitle;
+ static $magicWords = null;
+ if ( is_null( $magicWords ) ) {
+ $magicWords = new MagicWordArray( array( 'displaytitle_noerror', 'displaytitle_noreplace' ) );
+ }
+ $arg = $magicWords->matchStartToEnd( $uarg );
+
// parse a limited subset of wiki markup (just the single quote items)
$text = $parser->doQuotes( $text );
@@ -373,7 +385,7 @@ class CoreParserFunctions {
// list of disallowed tags for DISPLAYTITLE
// these will be escaped even though they are allowed in normal wiki text
$bad = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr',
- 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rp', 'br' );
+ 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rtc', 'rp', 'br' );
// disallow some styles that could be used to bypass $wgRestrictDisplayTitle
if ( $wgRestrictDisplayTitle ) {
@@ -399,13 +411,34 @@ class CoreParserFunctions {
// only requested titles that normalize to the actual title are allowed through
// if $wgRestrictDisplayTitle is true (it is by default)
// mimic the escaping process that occurs in OutputPage::setPageTitle
- $text = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $text, $htmlTagsCallback, array(), array(), $bad ) );
+ $text = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags(
+ $text,
+ $htmlTagsCallback,
+ array(),
+ array(),
+ $bad
+ ) );
$title = Title::newFromText( Sanitizer::stripAllTags( $text ) );
- if ( !$wgRestrictDisplayTitle ) {
- $parser->mOutput->setDisplayTitle( $text );
- } elseif ( $title instanceof Title && $title->getFragment() == '' && $title->equals( $parser->mTitle ) ) {
- $parser->mOutput->setDisplayTitle( $text );
+ if ( !$wgRestrictDisplayTitle ||
+ ( $title instanceof Title
+ && !$title->hasFragment()
+ && $title->equals( $parser->mTitle ) )
+ ) {
+ $old = $parser->mOutput->getProperty( 'displaytitle' );
+ if ( $old === false || $arg !== 'displaytitle_noreplace' ) {
+ $parser->mOutput->setDisplayTitle( $text );
+ }
+ if ( $old !== false && $old !== $text && !$arg ) {
+ $converter = $parser->getConverterLanguage()->getConverter();
+ return '<span class="error">' .
+ wfMessage( 'duplicate-displaytitle',
+ // Message should be parsed, but these params should only be escaped.
+ $converter->markNoConversion( wfEscapeWikiText( $old ) ),
+ $converter->markNoConversion( wfEscapeWikiText( $text ) )
+ )->inContentLanguage()->text() .
+ '</span>';
+ }
}
return '';
@@ -414,19 +447,20 @@ class CoreParserFunctions {
/**
* Matches the given value against the value of given magic word
*
- * @param string $magicword magic word key
- * @param mixed $value value to match
- * @return boolean true on successful match
+ * @param string $magicword Magic word key
+ * @param string $value Value to match
+ * @return bool True on successful match
*/
- static private function matchAgainstMagicword( $magicword, $value ) {
- if ( strval( $value ) === '' ) {
+ private static function matchAgainstMagicword( $magicword, $value ) {
+ $value = trim( strval( $value ) );
+ if ( $value === '' ) {
return false;
}
$mwObject = MagicWord::get( $magicword );
- return $mwObject->match( $value );
+ return $mwObject->matchStartToEnd( $value );
}
- static function formatRaw( $num, $raw ) {
+ public static function formatRaw( $num, $raw ) {
if ( self::matchAgainstMagicword( 'rawsuffix', $raw ) ) {
return $num;
} else {
@@ -434,35 +468,35 @@ class CoreParserFunctions {
return $wgContLang->formatNum( $num );
}
}
- static function numberofpages( $parser, $raw = null ) {
+ public static function numberofpages( $parser, $raw = null ) {
return self::formatRaw( SiteStats::pages(), $raw );
}
- static function numberofusers( $parser, $raw = null ) {
+ public static function numberofusers( $parser, $raw = null ) {
return self::formatRaw( SiteStats::users(), $raw );
}
- static function numberofactiveusers( $parser, $raw = null ) {
+ public static function numberofactiveusers( $parser, $raw = null ) {
return self::formatRaw( SiteStats::activeUsers(), $raw );
}
- static function numberofarticles( $parser, $raw = null ) {
+ public static function numberofarticles( $parser, $raw = null ) {
return self::formatRaw( SiteStats::articles(), $raw );
}
- static function numberoffiles( $parser, $raw = null ) {
+ public static function numberoffiles( $parser, $raw = null ) {
return self::formatRaw( SiteStats::images(), $raw );
}
- static function numberofadmins( $parser, $raw = null ) {
+ public static function numberofadmins( $parser, $raw = null ) {
return self::formatRaw( SiteStats::numberingroup( 'sysop' ), $raw );
}
- static function numberofedits( $parser, $raw = null ) {
+ public static function numberofedits( $parser, $raw = null ) {
return self::formatRaw( SiteStats::edits(), $raw );
}
- static function numberofviews( $parser, $raw = null ) {
+ public static function numberofviews( $parser, $raw = null ) {
global $wgDisableCounters;
return !$wgDisableCounters ? self::formatRaw( SiteStats::views(), $raw ) : '';
}
- static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
+ public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
return self::formatRaw( SiteStats::pagesInNs( intval( $namespace ) ), $raw );
}
- static function numberingroup( $parser, $name = '', $raw = null ) {
+ public static function numberingroup( $parser, $name = '', $raw = null ) {
return self::formatRaw( SiteStats::numberingroup( strtolower( $name ) ), $raw );
}
@@ -471,51 +505,53 @@ class CoreParserFunctions {
* corresponding magic word
* Note: function name changed to "mwnamespace" rather than "namespace"
* to not break PHP 5.3
+ * @param Parser $parser
+ * @param string $title
* @return mixed|string
*/
- static function mwnamespace( $parser, $title = null ) {
+ public static function mwnamespace( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return str_replace( '_', ' ', $t->getNsText() );
}
- static function namespacee( $parser, $title = null ) {
+ public static function namespacee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfUrlencode( $t->getNsText() );
}
- static function namespacenumber( $parser, $title = null ) {
+ public static function namespacenumber( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return $t->getNamespace();
}
- static function talkspace( $parser, $title = null ) {
+ public static function talkspace( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) || !$t->canTalk() ) {
return '';
}
return str_replace( '_', ' ', $t->getTalkNsText() );
}
- static function talkspacee( $parser, $title = null ) {
+ public static function talkspacee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) || !$t->canTalk() ) {
return '';
}
return wfUrlencode( $t->getTalkNsText() );
}
- static function subjectspace( $parser, $title = null ) {
+ public static function subjectspace( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return str_replace( '_', ' ', $t->getSubjectNsText() );
}
- static function subjectspacee( $parser, $title = null ) {
+ public static function subjectspacee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
@@ -526,100 +562,102 @@ class CoreParserFunctions {
/**
* Functions to get and normalize pagenames, corresponding to the magic words
* of the same names
- * @return String
+ * @param Parser $parser
+ * @param string $title
+ * @return string
*/
- static function pagename( $parser, $title = null ) {
+ public static function pagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( $t->getText() );
}
- static function pagenamee( $parser, $title = null ) {
+ public static function pagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( $t->getPartialURL() );
}
- static function fullpagename( $parser, $title = null ) {
+ public static function fullpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) || !$t->canTalk() ) {
return '';
}
return wfEscapeWikiText( $t->getPrefixedText() );
}
- static function fullpagenamee( $parser, $title = null ) {
+ public static function fullpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) || !$t->canTalk() ) {
return '';
}
return wfEscapeWikiText( $t->getPrefixedURL() );
}
- static function subpagename( $parser, $title = null ) {
+ public static function subpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( $t->getSubpageText() );
}
- static function subpagenamee( $parser, $title = null ) {
+ public static function subpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( $t->getSubpageUrlForm() );
}
- static function rootpagename( $parser, $title = null ) {
+ public static function rootpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( $t->getRootText() );
}
- static function rootpagenamee( $parser, $title = null ) {
+ public static function rootpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getRootText() ) ) );
}
- static function basepagename( $parser, $title = null ) {
+ public static function basepagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( $t->getBaseText() );
}
- static function basepagenamee( $parser, $title = null ) {
+ public static function basepagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getBaseText() ) ) );
}
- static function talkpagename( $parser, $title = null ) {
+ public static function talkpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) || !$t->canTalk() ) {
return '';
}
return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() );
}
- static function talkpagenamee( $parser, $title = null ) {
+ public static function talkpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) || !$t->canTalk() ) {
return '';
}
return wfEscapeWikiText( $t->getTalkPage()->getPrefixedURL() );
}
- static function subjectpagename( $parser, $title = null ) {
+ public static function subjectpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
}
return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedText() );
}
- static function subjectpagenamee( $parser, $title = null ) {
+ public static function subjectpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) ) {
return '';
@@ -631,9 +669,13 @@ class CoreParserFunctions {
* Return the number of pages, files or subcats in the given category,
* or 0 if it's nonexistent. This is an expensive parser function and
* can't be called too many times per page.
+ * @param Parser $parser
+ * @param string $name
+ * @param string $arg1
+ * @param string $arg2
* @return string
*/
- static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) {
+ public static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) {
global $wgContLang;
static $magicWords = null;
if ( is_null( $magicWords ) ) {
@@ -695,46 +737,29 @@ class CoreParserFunctions {
* Return the size of the given page, or 0 if it's nonexistent. This is an
* expensive parser function and can't be called too many times per page.
*
- * @todo FIXME: Title::getLength() documentation claims that it adds things
- * to the link cache, so the local cache here should be unnecessary, but
- * in fact calling getLength() repeatedly for the same $page does seem to
- * run one query for each call?
- * @todo Document parameters
- *
- * @param $parser Parser
- * @param $page String Name of page to check (Default: empty string)
- * @param $raw String Should number be human readable with commas or just number
+ * @param Parser $parser
+ * @param string $page Name of page to check (Default: empty string)
+ * @param string $raw Should number be human readable with commas or just number
* @return string
*/
- static function pagesize( $parser, $page = '', $raw = null ) {
- static $cache = array();
+ public static function pagesize( $parser, $page = '', $raw = null ) {
$title = Title::newFromText( $page );
if ( !is_object( $title ) ) {
- $cache[$page] = 0;
return self::formatRaw( 0, $raw );
}
- # Normalize name for cache
- $page = $title->getPrefixedText();
-
- $length = 0;
- if ( isset( $cache[$page] ) ) {
- $length = $cache[$page];
- } elseif ( $parser->incrementExpensiveFunctionCount() ) {
- $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
- $pageID = $rev ? $rev->getPage() : 0;
- $revID = $rev ? $rev->getId() : 0;
- $length = $cache[$page] = $rev ? $rev->getSize() : 0;
-
- // Register dependency in templatelinks
- $parser->mOutput->addTemplate( $title, $pageID, $revID );
- }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $title );
+ $length = $rev ? $rev->getSize() : 0;
return self::formatRaw( $length, $raw );
}
/**
- * Returns the requested protection level for the current page
+ * Returns the requested protection level for the current page. This
+ * is an expensive parser function and can't be called too many times
+ * per page, unless the protection levels for the given title have
+ * already been retrieved
*
* @param Parser $parser
* @param string $type
@@ -742,25 +767,28 @@ class CoreParserFunctions {
*
* @return string
*/
- static function protectionlevel( $parser, $type = '', $title = '' ) {
+ public static function protectionlevel( $parser, $type = '', $title = '' ) {
$titleObject = Title::newFromText( $title );
if ( !( $titleObject instanceof Title ) ) {
$titleObject = $parser->mTitle;
}
- $restrictions = $titleObject->getRestrictions( strtolower( $type ) );
- # Title::getRestrictions returns an array, its possible it may have
- # multiple values in the future
- return implode( $restrictions, ',' );
+ if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
+ $restrictions = $titleObject->getRestrictions( strtolower( $type ) );
+ # Title::getRestrictions returns an array, its possible it may have
+ # multiple values in the future
+ return implode( $restrictions, ',' );
+ }
+ return '';
}
/**
* Gives language names.
- * @param $parser Parser
- * @param string $code Language code (of which to get name)
- * @param string $inLanguage Language code (in which to get name)
- * @return String
+ * @param Parser $parser
+ * @param string $code Language code (of which to get name)
+ * @param string $inLanguage Language code (in which to get name)
+ * @return string
*/
- static function language( $parser, $code = '', $inLanguage = '' ) {
+ public static function language( $parser, $code = '', $inLanguage = '' ) {
$code = strtolower( $code );
$inLanguage = strtolower( $inLanguage );
$lang = Language::fetchLanguageName( $code, $inLanguage );
@@ -769,9 +797,14 @@ class CoreParserFunctions {
/**
* Unicode-safe str_pad with the restriction that $length is forced to be <= 500
+ * @param Parser $parser
+ * @param string $string
+ * @param int $length
+ * @param string $padding
+ * @param int $direction
* @return string
*/
- static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) {
+ public static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) {
$padding = $parser->killMarkers( $padding );
$lengthOfPadding = mb_strlen( $padding );
if ( $lengthOfPadding == 0 ) {
@@ -797,25 +830,25 @@ class CoreParserFunctions {
}
}
- static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) {
+ public static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) {
return self::pad( $parser, $string, $length, $padding, STR_PAD_LEFT );
}
- static function padright( $parser, $string = '', $length = 0, $padding = '0' ) {
+ public static function padright( $parser, $string = '', $length = 0, $padding = '0' ) {
return self::pad( $parser, $string, $length, $padding );
}
/**
- * @param $parser Parser
- * @param $text
+ * @param Parser $parser
+ * @param string $text
* @return string
*/
- static function anchorencode( $parser, $text ) {
+ public static function anchorencode( $parser, $text ) {
$text = $parser->killMarkers( $text );
return (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
}
- static function special( $parser, $text ) {
+ public static function special( $parser, $text ) {
list( $page, $subpage ) = SpecialPageFactory::resolveAlias( $text );
if ( $page ) {
$title = SpecialPage::getTitleFor( $page, $subpage );
@@ -827,12 +860,12 @@ class CoreParserFunctions {
}
}
- static function speciale( $parser, $text ) {
+ public static function speciale( $parser, $text ) {
return wfUrlencode( str_replace( ' ', '_', self::special( $parser, $text ) ) );
}
/**
- * @param $parser Parser
+ * @param Parser $parser
* @param string $text The sortkey to use
* @param string $uarg Either "noreplace" or "noerror" (in en)
* both suppress errors, and noreplace does nothing if
@@ -869,8 +902,9 @@ class CoreParserFunctions {
}
}
- // Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}}
- // or {{filepath|300px}}, {{filepath|200x300px}}, {{filepath|nowiki|200x300px}}, {{filepath|200x300px|nowiki}}
+ // Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}}
+ // or {{filepath|300|nowiki}} or {{filepath|300px}}, {{filepath|200x300px}},
+ // {{filepath|nowiki|200x300px}}, {{filepath|200x300px|nowiki}}.
public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
$file = wfFindFile( $name );
@@ -907,6 +941,9 @@ class CoreParserFunctions {
/**
* Parser function to extension tag adaptor
+ * @param Parser $parser
+ * @param PPFrame $frame
+ * @param array $args
* @return string
*/
public static function tagObj( $parser, $frame, $args ) {
@@ -949,4 +986,271 @@ class CoreParserFunctions {
);
return $parser->extensionSubstitution( $params, $frame );
}
+
+ /**
+ * Fetched the current revision of the given title and return this.
+ * Will increment the expensive function count and
+ * add a template link to get the value refreshed on changes.
+ * For a given title, which is equal to the current parser title,
+ * the revision object from the parser is used, when that is the current one
+ *
+ * @param Parser $parser
+ * @param Title $title
+ * @return Revision
+ * @since 1.23
+ */
+ private static function getCachedRevisionObject( $parser, $title = null ) {
+ static $cache = null;
+ if ( $cache == null ) {
+ $cache = new MapCacheLRU( 50 );
+ }
+
+ if ( is_null( $title ) ) {
+ return null;
+ }
+
+ // Use the revision from the parser itself, when param is the current page
+ // and the revision is the current one
+ if ( $title->equals( $parser->getTitle() ) ) {
+ $parserRev = $parser->getRevisionObject();
+ if ( $parserRev && $parserRev->isCurrent() ) {
+ // force reparse after edit with vary-revision flag
+ $parser->getOutput()->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": use current revision from parser, setting vary-revision...\n" );
+ return $parserRev;
+ }
+ }
+
+ // Normalize name for cache
+ $page = $title->getPrefixedDBkey();
+
+ if ( $cache->has( $page ) ) { // cache contains null values
+ return $cache->get( $page );
+ }
+ if ( $parser->incrementExpensiveFunctionCount() ) {
+ $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
+ $pageID = $rev ? $rev->getPage() : 0;
+ $revID = $rev ? $rev->getId() : 0;
+ $cache->set( $page, $rev ); // maybe null
+
+ // Register dependency in templatelinks
+ $parser->getOutput()->addTemplate( $title, $pageID, $revID );
+
+ return $rev;
+ }
+ $cache->set( $page, null );
+ return null;
+ }
+
+ /**
+ * Get the pageid of a specified page
+ * @param Parser $parser
+ * @param string $title Title to get the pageid from
+ * @return int|null|string
+ * @since 1.23
+ */
+ public static function pageid( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // Use title from parser to have correct pageid after edit
+ if ( $t->equals( $parser->getTitle() ) ) {
+ $t = $parser->getTitle();
+ return $t->getArticleID();
+ }
+
+ // These can't have ids
+ if ( !$t->canExist() || $t->isExternal() ) {
+ return 0;
+ }
+
+ // Check the link cache, maybe something already looked it up.
+ $linkCache = LinkCache::singleton();
+ $pdbk = $t->getPrefixedDBkey();
+ $id = $linkCache->getGoodLinkID( $pdbk );
+ if ( $id != 0 ) {
+ $parser->mOutput->addLink( $t, $id );
+ return $id;
+ }
+ if ( $linkCache->isBadLink( $pdbk ) ) {
+ $parser->mOutput->addLink( $t, 0 );
+ return $id;
+ }
+
+ // We need to load it from the DB, so mark expensive
+ if ( $parser->incrementExpensiveFunctionCount() ) {
+ $id = $t->getArticleID();
+ $parser->mOutput->addLink( $t, $id );
+ return $id;
+ }
+ return null;
+ }
+
+ /**
+ * Get the id from the last revision of a specified page.
+ * @param Parser $parser
+ * @param string $title Title to get the id from
+ * @return int|null|string
+ * @since 1.23
+ */
+ public static function revisionid( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $t );
+ return $rev ? $rev->getId() : '';
+ }
+
+ /**
+ * Get the day from the last revision of a specified page.
+ * @param Parser $parser
+ * @param string $title Title to get the day from
+ * @return string
+ * @since 1.23
+ */
+ public static function revisionday( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $t );
+ return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'j' ) : '';
+ }
+
+ /**
+ * Get the day with leading zeros from the last revision of a specified page.
+ * @param Parser $parser
+ * @param string $title Title to get the day from
+ * @return string
+ * @since 1.23
+ */
+ public static function revisionday2( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $t );
+ return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'd' ) : '';
+ }
+
+ /**
+ * Get the month with leading zeros from the last revision of a specified page.
+ * @param Parser $parser
+ * @param string $title Title to get the month from
+ * @return string
+ * @since 1.23
+ */
+ public static function revisionmonth( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $t );
+ return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'm' ) : '';
+ }
+
+ /**
+ * Get the month from the last revision of a specified page.
+ * @param Parser $parser
+ * @param string $title Title to get the month from
+ * @return string
+ * @since 1.23
+ */
+ public static function revisionmonth1( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $t );
+ return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'n' ) : '';
+ }
+
+ /**
+ * Get the year from the last revision of a specified page.
+ * @param Parser $parser
+ * @param string $title Title to get the year from
+ * @return string
+ * @since 1.23
+ */
+ public static function revisionyear( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $t );
+ return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'Y' ) : '';
+ }
+
+ /**
+ * Get the timestamp from the last revision of a specified page.
+ * @param Parser $parser
+ * @param string $title Title to get the timestamp from
+ * @return string
+ * @since 1.23
+ */
+ public static function revisiontimestamp( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $t );
+ return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'YmdHis' ) : '';
+ }
+
+ /**
+ * Get the user from the last revision of a specified page.
+ * @param Parser $parser
+ * @param string $title Title to get the user from
+ * @return string
+ * @since 1.23
+ */
+ public static function revisionuser( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ // fetch revision from cache/database and return the value
+ $rev = self::getCachedRevisionObject( $parser, $t );
+ return $rev ? $rev->getUserText() : '';
+ }
+
+ /**
+ * Returns the sources of any cascading protection acting on a specified page.
+ * Pages will not return their own title unless they transclude themselves.
+ * This is an expensive parser function and can't be called too many times per page,
+ * unless cascading protection sources for the page have already been loaded.
+ *
+ * @param Parser $parser
+ * @param string $title
+ *
+ * @return string
+ * @since 1.23
+ */
+ public static function cascadingsources( $parser, $title = '' ) {
+ $titleObject = Title::newFromText( $title );
+ if ( !( $titleObject instanceof Title ) ) {
+ $titleObject = $parser->mTitle;
+ }
+ if ( $titleObject->areCascadeProtectionSourcesLoaded()
+ || $parser->incrementExpensiveFunctionCount()
+ ) {
+ $names = array();
+ $sources = $titleObject->getCascadeProtectionSources();
+ foreach ( $sources[0] as $sourceTitle ) {
+ $names[] = $sourceTitle->getPrefixedText();
+ }
+ return implode( $names, '|' );
+ }
+ return '';
+ }
+
}
diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php
index a2eb6987..85920cc1 100644
--- a/includes/parser/CoreTagHooks.php
+++ b/includes/parser/CoreTagHooks.php
@@ -27,10 +27,10 @@
*/
class CoreTagHooks {
/**
- * @param $parser Parser
+ * @param Parser $parser
* @return void
*/
- static function register( $parser ) {
+ public static function register( $parser ) {
global $wgRawHtml;
$parser->setHook( 'pre', array( __CLASS__, 'pre' ) );
$parser->setHook( 'nowiki', array( __CLASS__, 'nowiki' ) );
@@ -50,7 +50,7 @@ class CoreTagHooks {
* @param Parser $parser
* @return string HTML
*/
- static function pre( $text, $attribs, $parser ) {
+ public static function pre( $text, $attribs, $parser ) {
// Backwards-compatibility hack
$content = StringUtils::delimiterReplace( '<nowiki>', '</nowiki>', '$1', $text, 'i' );
@@ -69,13 +69,13 @@ class CoreTagHooks {
*
* Uses undocumented extended tag hook return values, introduced in r61913.
*
- * @param $content string
- * @param $attributes array
- * @param $parser Parser
+ * @param string $content
+ * @param array $attributes
+ * @param Parser $parser
* @throws MWException
* @return array
*/
- static function html( $content, $attributes, $parser ) {
+ public static function html( $content, $attributes, $parser ) {
global $wgRawHtml;
if ( $wgRawHtml ) {
return array( $content, 'markerType' => 'nowiki' );
@@ -91,12 +91,12 @@ class CoreTagHooks {
*
* Uses undocumented extended tag hook return values, introduced in r61913.
*
- * @param $content string
- * @param $attributes array
- * @param $parser Parser
+ * @param string $content
+ * @param array $attributes
+ * @param Parser $parser
* @return array
*/
- static function nowiki( $content, $attributes, $parser ) {
+ public static function nowiki( $content, $attributes, $parser ) {
$content = strtr( $content, array( '-{' => '-&#123;', '}-' => '&#125;-' ) );
return array( Xml::escapeTagsOnly( $content ), 'markerType' => 'nowiki' );
}
@@ -107,7 +107,7 @@ class CoreTagHooks {
* Renders a thumbnail list of the given images, with optional captions.
* Full syntax documented on the wiki:
*
- * http://www.mediawiki.org/wiki/Help:Images#Gallery_syntax
+ * https://www.mediawiki.org/wiki/Help:Images#Gallery_syntax
*
* @todo break Parser::renderImageGallery out here too.
*
@@ -116,7 +116,7 @@ class CoreTagHooks {
* @param Parser $parser
* @return string HTML
*/
- static function gallery( $content, $attributes, $parser ) {
+ public static function gallery( $content, $attributes, $parser ) {
return $parser->renderImageGallery( $content, $attributes );
}
}
diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php
index 0a69b045..82f0e9d4 100644
--- a/includes/parser/DateFormatter.php
+++ b/includes/parser/DateFormatter.php
@@ -27,11 +27,11 @@
* @ingroup Parser
*/
class DateFormatter {
- var $mSource, $mTarget;
- var $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD;
+ public $mSource, $mTarget;
+ public $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD;
- var $regexes, $pDays, $pMonths, $pYears;
- var $rules, $xMonths, $preferences;
+ public $regexes, $pDays, $pMonths, $pYears;
+ public $rules, $xMonths, $preferences;
protected $lang;
@@ -49,9 +49,9 @@ class DateFormatter {
const LAST = 8;
/**
- * @param $lang Language In which language to format the date
+ * @param Language $lang In which language to format the date
*/
- function __construct( Language $lang ) {
+ public function __construct( Language $lang ) {
$this->lang = $lang;
$this->monthNames = $this->getMonthRegex();
@@ -120,9 +120,9 @@ class DateFormatter {
/**
* Get a DateFormatter object
*
- * @param $lang Language|string|null In which language to format the date
+ * @param Language|string|null $lang In which language to format the date
* Defaults to the site content language
- * @return DateFormatter object
+ * @return DateFormatter
*/
public static function &getInstance( $lang = null ) {
global $wgMemc, $wgContLang;
@@ -142,10 +142,11 @@ class DateFormatter {
/**
* @param string $preference User preference
* @param string $text Text to reformat
- * @param array $options can contain 'linked' and/or 'match-whole'
- * @return mixed|String
+ * @param array $options Array can contain 'linked' and/or 'match-whole'
+ *
+ * @return string
*/
- function reformat( $preference, $text, $options = array( 'linked' ) ) {
+ public function reformat( $preference, $text, $options = array( 'linked' ) ) {
$linked = in_array( 'linked', $options );
$match_whole = in_array( 'match-whole', $options );
@@ -192,10 +193,10 @@ class DateFormatter {
}
/**
- * @param $matches
+ * @param array $matches
* @return string
*/
- function replace( $matches ) {
+ public function replace( $matches ) {
# Extract information from $matches
$linked = true;
if ( isset( $this->mLinked ) ) {
@@ -204,7 +205,8 @@ class DateFormatter {
$bits = array();
$key = $this->keys[$this->mSource];
- for ( $p = 0; $p < strlen( $key ); $p++ ) {
+ $keyLength = strlen( $key );
+ for ( $p = 0; $p < $keyLength; $p++ ) {
if ( $key[$p] != ' ' ) {
$bits[$key[$p]] = $matches[$p + 1];
}
@@ -214,11 +216,11 @@ class DateFormatter {
}
/**
- * @param $bits array
- * @param $link bool
+ * @param array $bits
+ * @param bool $link
* @return string
*/
- function formatDate( $bits, $link = true ) {
+ public function formatDate( $bits, $link = true ) {
$format = $this->targets[$this->mTarget];
if ( !$link ) {
@@ -253,7 +255,8 @@ class DateFormatter {
$bits['d'] = sprintf( '%02d', $bits['j'] );
}
- for ( $p = 0; $p < strlen( $format ); $p++ ) {
+ $formatLength = strlen( $format );
+ for ( $p = 0; $p < $formatLength; $p++ ) {
$char = $format[$p];
switch ( $char ) {
case 'd': # ISO day of month
@@ -292,6 +295,7 @@ class DateFormatter {
}
}
if ( $fail ) {
+ /** @todo FIXME: $matches doesn't exist here, what's expected? */
$text = $matches[0];
}
@@ -314,7 +318,7 @@ class DateFormatter {
* @todo document
* @return string
*/
- function getMonthRegex() {
+ public function getMonthRegex() {
$names = array();
for ( $i = 1; $i <= 12; $i++ ) {
$names[] = $this->lang->getMonthName( $i );
@@ -325,10 +329,10 @@ class DateFormatter {
/**
* Makes an ISO month, e.g. 02, from a month name
- * @param string $monthName month name
+ * @param string $monthName Month name
* @return string ISO month name
*/
- function makeIsoMonth( $monthName ) {
+ public function makeIsoMonth( $monthName ) {
$n = $this->xMonths[$this->lang->lc( $monthName )];
return sprintf( '%02d', $n );
}
@@ -338,7 +342,7 @@ class DateFormatter {
* @param string $year Year name
* @return string ISO year name
*/
- function makeIsoYear( $year ) {
+ public function makeIsoYear( $year ) {
# 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;
@@ -353,9 +357,10 @@ class DateFormatter {
/**
* @todo document
+ * @param string $iso
* @return int|string
*/
- function makeNormalYear( $iso ) {
+ public function makeNormalYear( $iso ) {
if ( $iso[0] == '-' ) {
$text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC';
} else {
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
index f1a0b258..7794fae4 100644
--- a/includes/parser/LinkHolderArray.php
+++ b/includes/parser/LinkHolderArray.php
@@ -25,19 +25,27 @@
* @ingroup Parser
*/
class LinkHolderArray {
- var $internals = array(), $interwikis = array();
- var $size = 0;
- var $parent;
+ public $internals = array();
+ public $interwikis = array();
+ public $size = 0;
+
+ /**
+ * @var Parser
+ */
+ public $parent;
protected $tempIdOffset;
- function __construct( $parent ) {
+ /**
+ * @param Parser $parent
+ */
+ public function __construct( $parent ) {
$this->parent = $parent;
}
/**
* Reduce memory usage to reduce the impact of circular references
*/
- function __destruct() {
+ public function __destruct() {
foreach ( $this as $name => $value ) {
unset( $this->$name );
}
@@ -49,9 +57,9 @@ class LinkHolderArray {
* serializing at present.
*
* Compact the titles, only serialize the text form.
- * @return array
- */
- function __sleep() {
+ * @return array
+ */
+ public function __sleep() {
foreach ( $this->internals as &$nsLinks ) {
foreach ( $nsLinks as &$entry ) {
unset( $entry['title'] );
@@ -71,7 +79,7 @@ class LinkHolderArray {
/**
* Recreate the Title objects
*/
- function __wakeup() {
+ public function __wakeup() {
foreach ( $this->internals as &$nsLinks ) {
foreach ( $nsLinks as &$entry ) {
$entry['title'] = Title::newFromText( $entry['pdbk'] );
@@ -88,9 +96,9 @@ class LinkHolderArray {
/**
* Merge another LinkHolderArray into this one
- * @param $other LinkHolderArray
+ * @param LinkHolderArray $other
*/
- function merge( $other ) {
+ public function merge( $other ) {
foreach ( $other->internals as $ns => $entries ) {
$this->size += count( $entries );
if ( !isset( $this->internals[$ns] ) ) {
@@ -110,11 +118,11 @@ class LinkHolderArray {
* converted for use in the destination link holder. The resulting array of
* strings will be returned.
*
- * @param $other LinkHolderArray
- * @param array $texts of strings
- * @return Array
+ * @param LinkHolderArray $other
+ * @param array $texts Array of strings
+ * @return array
*/
- function mergeForeign( $other, $texts ) {
+ public function mergeForeign( $other, $texts ) {
$this->tempIdOffset = $idOffset = $this->parent->nextLinkID();
$maxId = 0;
@@ -144,6 +152,10 @@ class LinkHolderArray {
return $texts;
}
+ /**
+ * @param array $m
+ * @return string
+ */
protected function mergeForeignCallback( $m ) {
return $m[1] . ( $m[2] + $this->tempIdOffset ) . $m[3];
}
@@ -151,17 +163,18 @@ class LinkHolderArray {
/**
* Get a subset of the current LinkHolderArray which is sufficient to
* interpret the given text.
+ * @param string $text
* @return LinkHolderArray
*/
- function getSubArray( $text ) {
+ public function getSubArray( $text ) {
$sub = new LinkHolderArray( $this->parent );
# Internal links
$pos = 0;
while ( $pos < strlen( $text ) ) {
if ( !preg_match( '/<!--LINK (\d+):(\d+)-->/',
- $text, $m, PREG_OFFSET_CAPTURE, $pos ) )
- {
+ $text, $m, PREG_OFFSET_CAPTURE, $pos )
+ ) {
break;
}
$ns = $m[1][0];
@@ -187,7 +200,7 @@ class LinkHolderArray {
* Returns true if the memory requirements of this object are getting large
* @return bool
*/
- function isBig() {
+ public function isBig() {
global $wgLinkHolderBatchSize;
return $this->size > $wgLinkHolderBatchSize;
}
@@ -196,7 +209,7 @@ class LinkHolderArray {
* Clear all stored link holders.
* Make sure you don't have any text left using these link holders, before you call this
*/
- function clear() {
+ public function clear() {
$this->internals = array();
$this->interwikis = array();
$this->size = 0;
@@ -208,14 +221,14 @@ class LinkHolderArray {
* parsing of interwiki links, and secondly to allow all existence checks and
* article length checks (for stub links) to be bundled into a single query.
*
- * @param $nt Title
- * @param $text String
+ * @param Title $nt
+ * @param string $text
* @param array $query [optional]
* @param string $trail [optional]
* @param string $prefix [optional]
* @return string
*/
- function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
+ public function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
wfProfileIn( __METHOD__ );
if ( !is_object( $nt ) ) {
# Fail gracefully
@@ -251,15 +264,16 @@ class LinkHolderArray {
}
/**
- * @todo FIXME: Update documentation. makeLinkObj() is deprecated.
* Replace <!--LINK--> link placeholders with actual links, in the buffer
- * Placeholders created in Skin::makeLinkObj()
- * @return array of link CSS classes, indexed by PDBK.
+ *
+ * @param string $text
+ * @return array Array of link CSS classes, indexed by PDBK.
*/
- function replace( &$text ) {
+ public function replace( &$text ) {
wfProfileIn( __METHOD__ );
- $colours = $this->replaceInternal( $text ); // FIXME: replaceInternal doesn't return a value
+ /** @todo FIXME: replaceInternal doesn't return a value */
+ $colours = $this->replaceInternal( $text );
$this->replaceInterwiki( $text );
wfProfileOut( __METHOD__ );
@@ -268,6 +282,7 @@ class LinkHolderArray {
/**
* Replace internal links
+ * @param string $text
*/
protected function replaceInternal( &$text ) {
if ( !$this->internals ) {
@@ -275,93 +290,99 @@ class LinkHolderArray {
}
wfProfileIn( __METHOD__ );
- global $wgContLang;
+ global $wgContLang, $wgContentHandlerUseDB;
$colours = array();
$linkCache = LinkCache::singleton();
$output = $this->parent->getOutput();
- if ( $linkCache->useDatabase() ) {
- wfProfileIn( __METHOD__ . '-check' );
- $dbr = wfGetDB( DB_SLAVE );
- $threshold = $this->parent->getOptions()->getStubThreshold();
+ wfProfileIn( __METHOD__ . '-check' );
+ $dbr = wfGetDB( DB_SLAVE );
+ $threshold = $this->parent->getOptions()->getStubThreshold();
- # Sort by namespace
- ksort( $this->internals );
+ # Sort by namespace
+ ksort( $this->internals );
- $linkcolour_ids = array();
+ $linkcolour_ids = array();
- # Generate query
- $queries = array();
- foreach ( $this->internals as $ns => $entries ) {
- foreach ( $entries as $entry ) {
- $title = $entry['title'];
- $pdbk = $entry['pdbk'];
+ # Generate query
+ $queries = array();
+ foreach ( $this->internals as $ns => $entries ) {
+ foreach ( $entries as $entry ) {
+ /** @var Title $title */
+ $title = $entry['title'];
+ $pdbk = $entry['pdbk'];
- # Skip invalid entries.
- # Result will be ugly, but prevents crash.
- if ( is_null( $title ) ) {
- continue;
- }
+ # Skip invalid entries.
+ # Result will be ugly, but prevents crash.
+ if ( is_null( $title ) ) {
+ continue;
+ }
- # Check if it's a static known link, e.g. interwiki
- if ( $title->isAlwaysKnown() ) {
- $colours[$pdbk] = '';
- } elseif ( $ns == NS_SPECIAL ) {
- $colours[$pdbk] = 'new';
- } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
- $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
- $output->addLink( $title, $id );
- $linkcolour_ids[$id] = $pdbk;
- } elseif ( $linkCache->isBadLink( $pdbk ) ) {
- $colours[$pdbk] = 'new';
- } else {
- # Not in the link cache, add it to the query
- $queries[$ns][] = $title->getDBkey();
- }
+ # Check if it's a static known link, e.g. interwiki
+ if ( $title->isAlwaysKnown() ) {
+ $colours[$pdbk] = '';
+ } elseif ( $ns == NS_SPECIAL ) {
+ $colours[$pdbk] = 'new';
+ } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
+ $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
+ $output->addLink( $title, $id );
+ $linkcolour_ids[$id] = $pdbk;
+ } elseif ( $linkCache->isBadLink( $pdbk ) ) {
+ $colours[$pdbk] = 'new';
+ } else {
+ # Not in the link cache, add it to the query
+ $queries[$ns][] = $title->getDBkey();
}
}
- if ( $queries ) {
- $where = array();
- foreach ( $queries as $ns => $pages ) {
- $where[] = $dbr->makeList(
- array(
- 'page_namespace' => $ns,
- 'page_title' => $pages,
- ),
- LIST_AND
- );
- }
-
- $res = $dbr->select(
- 'page',
- array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ),
- $dbr->makeList( $where, LIST_OR ),
- __METHOD__
+ }
+ if ( $queries ) {
+ $where = array();
+ foreach ( $queries as $ns => $pages ) {
+ $where[] = $dbr->makeList(
+ array(
+ 'page_namespace' => $ns,
+ 'page_title' => array_unique( $pages ),
+ ),
+ LIST_AND
);
+ }
- # Fetch data and form into an associative array
- # non-existent = broken
- foreach ( $res as $s ) {
- $title = Title::makeTitle( $s->page_namespace, $s->page_title );
- $pdbk = $title->getPrefixedDBkey();
- $linkCache->addGoodLinkObjFromRow( $title, $s );
- $output->addLink( $title, $s->page_id );
- # @todo FIXME: Convoluted data flow
- # The redirect status and length is passed to getLinkColour via the LinkCache
- # Use formal parameters instead
- $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
- //add id to the extension todolist
- $linkcolour_ids[$s->page_id] = $pdbk;
- }
- unset( $res );
+ $fields = array( 'page_id', 'page_namespace', 'page_title',
+ 'page_is_redirect', 'page_len', 'page_latest' );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
}
- if ( count( $linkcolour_ids ) ) {
- //pass an array of page_ids to an extension
- wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+
+ $res = $dbr->select(
+ 'page',
+ $fields,
+ $dbr->makeList( $where, LIST_OR ),
+ __METHOD__
+ );
+
+ # Fetch data and form into an associative array
+ # non-existent = broken
+ foreach ( $res as $s ) {
+ $title = Title::makeTitle( $s->page_namespace, $s->page_title );
+ $pdbk = $title->getPrefixedDBkey();
+ $linkCache->addGoodLinkObjFromRow( $title, $s );
+ $output->addLink( $title, $s->page_id );
+ # @todo FIXME: Convoluted data flow
+ # The redirect status and length is passed to getLinkColour via the LinkCache
+ # Use formal parameters instead
+ $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
+ //add id to the extension todolist
+ $linkcolour_ids[$s->page_id] = $pdbk;
}
- wfProfileOut( __METHOD__ . '-check' );
+ unset( $res );
}
+ if ( count( $linkcolour_ids ) ) {
+ //pass an array of page_ids to an extension
+ wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+ }
+ wfProfileOut( __METHOD__ . '-check' );
# Do a second query for different language variants of links and categories
if ( $wgContLang->hasVariants() ) {
@@ -421,6 +442,7 @@ class LinkHolderArray {
/**
* Replace interwiki links
+ * @param string $text
*/
protected function replaceInterwiki( &$text ) {
if ( empty( $this->interwikis ) ) {
@@ -446,9 +468,10 @@ class LinkHolderArray {
/**
* Modify $this->internals and $colours according to language variant linking rules
+ * @param array $colours
*/
protected function doVariants( &$colours ) {
- global $wgContLang;
+ global $wgContLang, $wgContentHandlerUseDB;
$linkBatch = new LinkBatch();
$variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
$output = $this->parent->getOutput();
@@ -486,6 +509,7 @@ class LinkHolderArray {
// Then add variants of links to link batch
$parentTitle = $this->parent->getTitle();
foreach ( $titlesAttrs as $i => $attrs ) {
+ /** @var Title $title */
list( $index, $title ) = $attrs;
$ns = $title->getNamespace();
$text = $title->getText();
@@ -504,7 +528,7 @@ class LinkHolderArray {
// Self-link checking for mixed/different variant titles. At this point, we
// already know the exact title does not exist, so the link cannot be to a
// variant of the current title that exists as a separate page.
- if ( $variantTitle->equals( $parentTitle ) && $title->getFragment() === '' ) {
+ if ( $variantTitle->equals( $parentTitle ) && !$title->hasFragment() ) {
$this->internals[$ns][$index]['selflink'] = true;
continue 2;
}
@@ -536,8 +560,15 @@ class LinkHolderArray {
if ( !$linkBatch->isEmpty() ) {
// construct query
$dbr = wfGetDB( DB_SLAVE );
+ $fields = array( 'page_id', 'page_namespace', 'page_title',
+ 'page_is_redirect', 'page_len', 'page_latest' );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
+
$varRes = $dbr->select( 'page',
- array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ),
+ $fields,
$linkBatch->constructSet( 'page', $dbr ),
__METHOD__
);
@@ -609,10 +640,10 @@ class LinkHolderArray {
* Replace <!--LINK--> link placeholders with plain text of links
* (not HTML-formatted).
*
- * @param $text String
- * @return String
+ * @param string $text
+ * @return string
*/
- function replaceText( $text ) {
+ public function replaceText( $text ) {
wfProfileIn( __METHOD__ );
$text = preg_replace_callback(
@@ -627,11 +658,11 @@ class LinkHolderArray {
/**
* Callback for replaceText()
*
- * @param $matches Array
+ * @param array $matches
* @return string
* @private
*/
- function replaceTextCallback( $matches ) {
+ public function replaceTextCallback( $matches ) {
$type = $matches[1];
$key = $matches[2];
if ( $type == 'LINK' ) {
diff --git a/includes/parser/Tidy.php b/includes/parser/MWTidy.php
index 32b16aaf..b310862f 100644
--- a/includes/parser/Tidy.php
+++ b/includes/parser/MWTidy.php
@@ -50,7 +50,7 @@ class MWTidyWrapper {
}
/**
- * @param $text string
+ * @param string $text
* @return string
*/
public function getWrapped( $text ) {
@@ -65,7 +65,9 @@ class MWTidyWrapper {
// ...and <mw:toc> markers
$wrappedtext = preg_replace_callback( '/\<\\/?mw:toc\>/',
array( &$this, 'replaceCallback' ), $wrappedtext );
-
+ // ... and <math> tags
+ $wrappedtext = preg_replace_callback( '/\<math(.*?)\<\\/math\>/s',
+ array( &$this, 'replaceCallback' ), $wrappedtext );
// Modify inline Microdata <link> and <meta> elements so they say <html-link> and <html-meta> so
// we can trick Tidy into not stripping them out by including them in tidy's new-empty-tags config
$wrappedtext = preg_replace( '!<(link|meta)([^>]*?)(/{0,1}>)!', '<html-$1$2$3', $wrappedtext );
@@ -79,11 +81,11 @@ class MWTidyWrapper {
}
/**
- * @param $m array
+ * @param array $m
*
* @return string
*/
- function replaceCallback( $m ) {
+ public function replaceCallback( $m ) {
$marker = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}" . Parser::MARKER_SUFFIX;
$this->mMarkerIndex++;
$this->mTokens->setPair( $marker, $m[0] );
@@ -91,7 +93,7 @@ class MWTidyWrapper {
}
/**
- * @param $text string
+ * @param string $text
* @return string
*/
public function postprocess( $text ) {
@@ -121,8 +123,8 @@ class MWTidy {
* If tidy isn't able to correct the markup, the original will be
* returned in all its glory with a warning comment appended.
*
- * @param string $text hideous HTML input
- * @return String: corrected HTML output
+ * @param string $text Hideous HTML input
+ * @return string Corrected HTML output
*/
public static function tidy( $text ) {
global $wgTidyInternal;
@@ -153,9 +155,9 @@ class MWTidy {
/**
* Check HTML for errors, used if $wgValidateAllHtml = true.
*
- * @param $text String
- * @param &$errorStr String: return the error string
- * @return Boolean: whether the HTML is valid
+ * @param string $text
+ * @param string &$errorStr Return the error string
+ * @return bool Whether the HTML is valid
*/
public static function checkErrors( $text, &$errorStr = null ) {
global $wgTidyInternal;
@@ -175,9 +177,9 @@ class MWTidy {
* Also called in OutputHandler.php for full page validation
*
* @param string $text HTML to check
- * @param $stderr Boolean: Whether to read result from STDERR rather than STDOUT
- * @param &$retval int Exit code (-1 on internal error)
- * @return mixed String or null
+ * @param bool $stderr Whether to read result from STDERR rather than STDOUT
+ * @param int &$retval Exit code (-1 on internal error)
+ * @return string|null
*/
private static function execExternalTidy( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgTidyBin, $wgTidyOpts;
@@ -206,6 +208,9 @@ class MWTidy {
$process = proc_open(
"$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes );
+ //NOTE: At least on linux, the process will be created even if tidy is not installed.
+ // This means that missing tidy will be treated as a validation failure.
+
if ( is_resource( $process ) ) {
// Theoretically, this style of communication could cause a deadlock
// here. If the stdout buffer fills up, then writes to stdin could
@@ -239,9 +244,9 @@ class MWTidy {
* saving the overhead of spawning a new process.
*
* @param string $text HTML to check
- * @param $stderr Boolean: Whether to read result from error status instead of output
- * @param &$retval int Exit code (-1 on internal error)
- * @return mixed String or null
+ * @param bool $stderr Whether to read result from error status instead of output
+ * @param int &$retval Exit code (-1 on internal error)
+ * @return string|null
*/
private static function execInternalTidy( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgDebugTidy;
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 1f14223d..84bb2243 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -120,62 +120,61 @@ class Parser {
const TOC_END = '</mw:toc>';
# Persistent:
- var $mTagHooks = array();
- var $mTransparentTagHooks = array();
- var $mFunctionHooks = array();
- var $mFunctionSynonyms = array( 0 => array(), 1 => array() );
- var $mFunctionTagHooks = array();
- var $mStripList = array();
- var $mDefaultStripList = array();
- var $mVarCache = array();
- var $mImageParams = array();
- var $mImageParamsMagicArray = array();
- var $mMarkerIndex = 0;
- var $mFirstCall = true;
+ public $mTagHooks = array();
+ public $mTransparentTagHooks = array();
+ public $mFunctionHooks = array();
+ public $mFunctionSynonyms = array( 0 => array(), 1 => array() );
+ public $mFunctionTagHooks = array();
+ public $mStripList = array();
+ public $mDefaultStripList = array();
+ public $mVarCache = array();
+ public $mImageParams = array();
+ public $mImageParamsMagicArray = array();
+ public $mMarkerIndex = 0;
+ public $mFirstCall = true;
# Initialised by initialiseVariables()
/**
* @var MagicWordArray
*/
- var $mVariables;
+ public $mVariables;
/**
* @var MagicWordArray
*/
- var $mSubstWords;
- var $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
+ public $mSubstWords;
+ public $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
# Cleared with clearState():
/**
* @var ParserOutput
*/
- var $mOutput;
- var $mAutonumber, $mDTopen;
+ public $mOutput;
+ public $mAutonumber, $mDTopen;
/**
* @var StripState
*/
- var $mStripState;
+ public $mStripState;
- var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
+ public $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
/**
* @var LinkHolderArray
*/
- var $mLinkHolders;
+ public $mLinkHolders;
- var $mLinkID;
- var $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth;
- var $mDefaultSort;
- var $mTplExpandCache; # empty-frame expansion cache
- var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
- var $mExpensiveFunctionCount; # number of expensive parser function calls
- var $mShowToc, $mForceTocPosition;
+ public $mLinkID;
+ public $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth;
+ public $mDefaultSort;
+ public $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
+ public $mExpensiveFunctionCount; # number of expensive parser function calls
+ public $mShowToc, $mForceTocPosition;
/**
* @var User
*/
- var $mUser; # User object; only used when doing pre-save transform
+ public $mUser; # User object; only used when doing pre-save transform
# Temporary
# These are variables reset at least once per parse regardless of $clearState
@@ -183,38 +182,42 @@ class Parser {
/**
* @var ParserOptions
*/
- var $mOptions;
+ public $mOptions;
/**
* @var Title
*/
- var $mTitle; # Title context, used for self-link rendering and similar things
- var $mOutputType; # Output type, one of the OT_xxx constants
- var $ot; # Shortcut alias, see setOutputType()
- var $mRevisionObject; # The revision object of the specified revision ID
- var $mRevisionId; # ID to display in {{REVISIONID}} tags
- var $mRevisionTimestamp; # The timestamp of the specified revision ID
- var $mRevisionUser; # User to display in {{REVISIONUSER}} tag
- var $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
- var $mRevIdForTs; # The revision ID which was used to fetch the timestamp
- var $mInputSize = false; # For {{PAGESIZE}} on current page.
+ public $mTitle; # Title context, used for self-link rendering and similar things
+ public $mOutputType; # Output type, one of the OT_xxx constants
+ public $ot; # Shortcut alias, see setOutputType()
+ public $mRevisionObject; # The revision object of the specified revision ID
+ public $mRevisionId; # ID to display in {{REVISIONID}} tags
+ public $mRevisionTimestamp; # The timestamp of the specified revision ID
+ public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
+ public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
+ public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
+ public $mInputSize = false; # For {{PAGESIZE}} on current page.
/**
* @var string
*/
- var $mUniqPrefix;
+ public $mUniqPrefix;
/**
- * @var Array with the language name of each language link (i.e. the
+ * @var array Array with the language name of each language link (i.e. the
* interwiki prefix) in the key, value arbitrary. Used to avoid sending
* duplicate language links to the ParserOutput.
*/
- var $mLangLinkLanguages;
+ public $mLangLinkLanguages;
/**
- * Constructor
- *
- * @param $conf array
+ * @var bool Recursive call protection.
+ * This variable should be treated as if it were private.
+ */
+ public $mInParse = false;
+
+ /**
+ * @param array $conf
*/
public function __construct( $conf = array() ) {
$this->mConf = $conf;
@@ -241,7 +244,7 @@ class Parser {
/**
* Reduce memory usage to reduce the impact of circular references
*/
- function __destruct() {
+ public function __destruct() {
if ( isset( $this->mLinkHolders ) ) {
unset( $this->mLinkHolders );
}
@@ -253,14 +256,15 @@ class Parser {
/**
* Allow extensions to clean up when the parser is cloned
*/
- function __clone() {
+ public function __clone() {
+ $this->mInParse = false;
wfRunHooks( 'ParserCloned', array( $this ) );
}
/**
* Do various kinds of initialisation on the first call of the parser
*/
- function firstCallInit() {
+ public function firstCallInit() {
if ( !$this->mFirstCall ) {
return;
}
@@ -281,7 +285,7 @@ class Parser {
*
* @private
*/
- function clearState() {
+ public function clearState() {
wfProfileIn( __METHOD__ );
if ( $this->mFirstCall ) {
$this->firstCallInit();
@@ -316,7 +320,7 @@ class Parser {
$this->mStripState = new StripState( $this->mUniqPrefix );
# Clear these on every parse, bug 4549
- $this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array();
+ $this->mTplRedirCache = $this->mTplDomCache = array();
$this->mShowToc = true;
$this->mForceTocPosition = false;
@@ -345,15 +349,17 @@ class Parser {
* Convert wikitext to HTML
* Do not call this function recursively.
*
- * @param string $text text we want to parse
- * @param $title Title object
- * @param $options ParserOptions
- * @param $linestart boolean
- * @param $clearState boolean
- * @param int $revid number to pass in {{REVISIONID}}
- * @return ParserOutput a ParserOutput
- */
- public function parse( $text, Title $title, ParserOptions $options, $linestart = true, $clearState = true, $revid = null ) {
+ * @param string $text Text we want to parse
+ * @param Title $title
+ * @param ParserOptions $options
+ * @param bool $linestart
+ * @param bool $clearState
+ * @param int $revid Number to pass in {{REVISIONID}}
+ * @return ParserOutput A ParserOutput
+ */
+ public function parse( $text, Title $title, ParserOptions $options,
+ $linestart = true, $clearState = true, $revid = null
+ ) {
/**
* First pass--just handle <nowiki> sections, pass the rest off
* to internalParse() which does all the real work.
@@ -364,6 +370,10 @@ class Parser {
wfProfileIn( __METHOD__ );
wfProfileIn( $fname );
+ if ( $clearState ) {
+ $magicScopeVariable = $this->lock();
+ }
+
$this->startParse( $title, $options, self::OT_HTML, $clearState );
$this->mInputSize = strlen( $text );
@@ -420,8 +430,8 @@ class Parser {
* d) it is an interface message (which is in the user language)
*/
if ( !( $options->getDisableContentConversion()
- || isset( $this->mDoubleUnderscores['nocontentconvert'] ) ) )
- {
+ || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
+ ) {
if ( !$this->mOptions->getInterfaceMessage() ) {
# The position of the convert() call should not be changed. it
# assumes that the links are all replaced and the only thing left
@@ -438,10 +448,10 @@ class Parser {
* automatic link conversion.
*/
if ( !( $options->getDisableTitleConversion()
- || isset( $this->mDoubleUnderscores['nocontentconvert'] )
- || isset( $this->mDoubleUnderscores['notitleconvert'] )
- || $this->mOutput->getDisplayTitle() !== false ) )
- {
+ || isset( $this->mDoubleUnderscores['nocontentconvert'] )
+ || isset( $this->mDoubleUnderscores['notitleconvert'] )
+ || $this->mOutput->getDisplayTitle() !== false )
+ ) {
$convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
if ( $convruletitle ) {
$this->mOutput->setTitleText( $convruletitle );
@@ -541,7 +551,7 @@ class Parser {
}
foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
if ( wfRunHooks( 'ParserLimitReportFormat',
- array( $key, $value, &$limitReport, false, false )
+ array( $key, &$value, &$limitReport, false, false )
) ) {
$keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
$valueMsg = wfMessage( array( "$key-value-text", "$key-value" ) )
@@ -590,12 +600,12 @@ class Parser {
*
* If $frame is not provided, then template variables (e.g., {{{1}}}) within $text are not expanded
*
- * @param string $text text extension wants to have parsed
- * @param $frame PPFrame: The frame to use for expanding any template variables
+ * @param string $text Text extension wants to have parsed
+ * @param bool|PPFrame $frame The frame to use for expanding any template variables
*
* @return string
*/
- function recursiveTagParse( $text, $frame = false ) {
+ public function recursiveTagParse( $text, $frame = false ) {
wfProfileIn( __METHOD__ );
wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
@@ -607,17 +617,24 @@ class Parser {
/**
* Expand templates and variables in the text, producing valid, static wikitext.
* Also removes comments.
+ * Do not call this function recursively.
+ * @param string $text
+ * @param Title $title
+ * @param ParserOptions $options
+ * @param int|null $revid
+ * @param bool|PPFrame $frame
* @return mixed|string
*/
- function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null ) {
+ public function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null, $frame = false ) {
wfProfileIn( __METHOD__ );
+ $magicScopeVariable = $this->lock();
$this->startParse( $title, $options, self::OT_PREPROCESS, true );
if ( $revid !== null ) {
$this->mRevisionId = $revid;
}
wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
- $text = $this->replaceVariables( $text );
+ $text = $this->replaceVariables( $text, $frame );
$text = $this->mStripState->unstripBoth( $text );
wfProfileOut( __METHOD__ );
return $text;
@@ -627,9 +644,9 @@ class Parser {
* Recursive parser entry point that can be called from an extension tag
* hook.
*
- * @param string $text text to be expanded
- * @param $frame PPFrame: The frame to use for expanding any template variables
- * @return String
+ * @param string $text Text to be expanded
+ * @param bool|PPFrame $frame The frame to use for expanding any template variables
+ * @return string
* @since 1.19
*/
public function recursivePreprocess( $text, $frame = false ) {
@@ -647,13 +664,18 @@ class Parser {
* transclusion, comments, templates, arguments, tags hooks and parser
* functions are untouched.
*
- * @param $text String
- * @param $title Title
- * @param $options ParserOptions
- * @return String
+ * @param string $text
+ * @param Title $title
+ * @param ParserOptions $options
+ * @param array $params
+ * @return string
*/
- public function getPreloadText( $text, Title $title, ParserOptions $options ) {
+ public function getPreloadText( $text, Title $title, ParserOptions $options, $params = array() ) {
+ $msg = new RawMessage( $text );
+ $text = $msg->params( $params )->plain();
+
# Parser (re)initialisation
+ $magicScopeVariable = $this->lock();
$this->startParse( $title, $options, self::OT_PLAIN, true );
$flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES;
@@ -676,16 +698,16 @@ class Parser {
* Set the current user.
* Should only be used when doing pre-save transform.
*
- * @param $user Mixed: User object or null (to reset)
+ * @param User|null $user User object or null (to reset)
*/
- function setUser( $user ) {
+ public function setUser( $user ) {
$this->mUser = $user;
}
/**
* Accessor for mUniqPrefix.
*
- * @return String
+ * @return string
*/
public function uniqPrefix() {
if ( !isset( $this->mUniqPrefix ) ) {
@@ -703,14 +725,14 @@ class Parser {
/**
* Set the context title
*
- * @param $t Title
+ * @param Title $t
*/
- function setTitle( $t ) {
- if ( !$t || $t instanceof FakeTitle ) {
+ public function setTitle( $t ) {
+ if ( !$t ) {
$t = Title::newFromText( 'NO TITLE' );
}
- if ( strval( $t->getFragment() ) !== '' ) {
+ if ( $t->hasFragment() ) {
# Strip the fragment to avoid various odd effects
$this->mTitle = clone $t;
$this->mTitle->setFragment( '' );
@@ -722,28 +744,28 @@ class Parser {
/**
* Accessor for the Title object
*
- * @return Title object
+ * @return Title
*/
- function getTitle() {
+ public function getTitle() {
return $this->mTitle;
}
/**
* Accessor/mutator for the Title object
*
- * @param $x Title object or null to just get the current one
- * @return Title object
+ * @param Title $x Title object or null to just get the current one
+ * @return Title
*/
- function Title( $x = null ) {
+ public function Title( $x = null ) {
return wfSetVar( $this->mTitle, $x );
}
/**
* Set the output type
*
- * @param $ot Integer: new value
+ * @param int $ot New value
*/
- function setOutputType( $ot ) {
+ public function setOutputType( $ot ) {
$this->mOutputType = $ot;
# Shortcut alias
$this->ot = array(
@@ -758,51 +780,51 @@ class Parser {
* Accessor/mutator for the output type
*
* @param int|null $x New value or null to just get the current one
- * @return Integer
+ * @return int
*/
- function OutputType( $x = null ) {
+ public function OutputType( $x = null ) {
return wfSetVar( $this->mOutputType, $x );
}
/**
* Get the ParserOutput object
*
- * @return ParserOutput object
+ * @return ParserOutput
*/
- function getOutput() {
+ public function getOutput() {
return $this->mOutput;
}
/**
* Get the ParserOptions object
*
- * @return ParserOptions object
+ * @return ParserOptions
*/
- function getOptions() {
+ public function getOptions() {
return $this->mOptions;
}
/**
* Accessor/mutator for the ParserOptions object
*
- * @param $x ParserOptions New value or null to just get the current one
+ * @param ParserOptions $x New value or null to just get the current one
* @return ParserOptions Current ParserOptions object
*/
- function Options( $x = null ) {
+ public function Options( $x = null ) {
return wfSetVar( $this->mOptions, $x );
}
/**
* @return int
*/
- function nextLinkID() {
+ public function nextLinkID() {
return $this->mLinkID++;
}
/**
- * @param $id int
+ * @param int $id
*/
- function setLinkID( $id ) {
+ public function setLinkID( $id ) {
$this->mLinkID = $id;
}
@@ -810,7 +832,7 @@ class Parser {
* Get a language object for use in parser functions such as {{FORMATNUM:}}
* @return Language
*/
- function getFunctionLang() {
+ public function getFunctionLang() {
return $this->getTargetLanguage();
}
@@ -821,7 +843,7 @@ class Parser {
* @since 1.19
*
* @throws MWException
- * @return Language|null
+ * @return Language
*/
public function getTargetLanguage() {
$target = $this->mOptions->getTargetLanguage();
@@ -839,8 +861,9 @@ class Parser {
/**
* Get the language object for language conversion
+ * @return Language|null
*/
- function getConverterLanguage() {
+ public function getConverterLanguage() {
return $this->getTargetLanguage();
}
@@ -848,9 +871,9 @@ class Parser {
* Get a User object either from $this->mUser, if set, or from the
* ParserOptions object otherwise
*
- * @return User object
+ * @return User
*/
- function getUser() {
+ public function getUser() {
if ( !is_null( $this->mUser ) ) {
return $this->mUser;
}
@@ -860,9 +883,9 @@ class Parser {
/**
* Get a preprocessor object
*
- * @return Preprocessor instance
+ * @return Preprocessor
*/
- function getPreprocessor() {
+ public function getPreprocessor() {
if ( !isset( $this->mPreprocessor ) ) {
$class = $this->mPreprocessorClass;
$this->mPreprocessor = new $class( $this );
@@ -884,11 +907,11 @@ class Parser {
* '<element param="x">tag content</element>' ) )
* @endcode
*
- * @param array $elements list of element names. Comments are always extracted.
+ * @param array $elements List of element names. Comments are always extracted.
* @param string $text Source text string.
* @param array $matches Out parameter, Array: extracted tags
- * @param $uniq_prefix string
- * @return String: stripped text
+ * @param string $uniq_prefix
+ * @return string Stripped text
*/
public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) {
static $n = 1;
@@ -957,7 +980,7 @@ class Parser {
*
* @return array
*/
- function getStripList() {
+ public function getStripList() {
return $this->mStripList;
}
@@ -966,11 +989,11 @@ class Parser {
* Returns the unique tag which must be inserted into the stripped text
* The tag will be replaced with the original text in unstrip()
*
- * @param $text string
+ * @param string $text
*
* @return string
*/
- function insertStripItem( $text ) {
+ public function insertStripItem( $text ) {
$rnd = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
$this->mMarkerIndex++;
$this->mStripState->addGeneral( $rnd, $text );
@@ -981,9 +1004,10 @@ class Parser {
* parse the wiki syntax used to render tables
*
* @private
+ * @param string $text
* @return string
*/
- function doTableStuff( $text ) {
+ public function doTableStuff( $text ) {
wfProfileIn( __METHOD__ );
$lines = StringUtils::explode( "\n", $text );
@@ -1068,7 +1092,10 @@ class Parser {
array_push( $tr_history, false );
array_push( $td_history, false );
array_push( $last_tag_history, '' );
- } elseif ( $first_character === '|' || $first_character === '!' || substr( $line, 0, 2 ) === '|+' ) {
+ } elseif ( $first_character === '|'
+ || $first_character === '!'
+ || substr( $line, 0, 2 ) === '|+'
+ ) {
# This might be cell elements, td, th or captions
if ( substr( $line, 0, 2 ) === '|+' ) {
$first_character = '+';
@@ -1179,13 +1206,13 @@ class Parser {
*
* @private
*
- * @param $text string
- * @param $isMain bool
- * @param $frame bool
+ * @param string $text
+ * @param bool $isMain
+ * @param bool $frame
*
* @return string
*/
- function internalParse( $text, $isMain = true, $frame = false ) {
+ public function internalParse( $text, $isMain = true, $frame = false ) {
wfProfileIn( __METHOD__ );
$origText = $text;
@@ -1213,7 +1240,12 @@ class Parser {
}
wfRunHooks( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) );
- $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), false, array_keys( $this->mTransparentTagHooks ) );
+ $text = Sanitizer::removeHTMLtags(
+ $text,
+ array( &$this, 'attributeStripCallback' ),
+ false,
+ array_keys( $this->mTransparentTagHooks )
+ );
wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
# Tables need to come after variable replacement for things to work
@@ -1249,11 +1281,11 @@ class Parser {
* DML
* @private
*
- * @param $text string
+ * @param string $text
*
* @return string
*/
- function doMagicLinks( $text ) {
+ public function doMagicLinks( $text ) {
wfProfileIn( __METHOD__ );
$prots = wfUrlProtocolsWithoutProtRel();
$urlChar = self::EXT_LINK_URL_CLASS;
@@ -1275,10 +1307,10 @@ class Parser {
/**
* @throws MWException
- * @param $m array
+ * @param array $m
* @return HTML|string
*/
- function magicLinkCallback( $m ) {
+ public function magicLinkCallback( $m ) {
if ( isset( $m[1] ) && $m[1] !== '' ) {
# Skip anchor
return $m[0];
@@ -1293,19 +1325,19 @@ class Parser {
if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
$keyword = 'RFC';
$urlmsg = 'rfcurl';
- $CssClass = 'mw-magiclink-rfc';
+ $cssClass = 'mw-magiclink-rfc';
$id = $m[4];
} elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
$keyword = 'PMID';
$urlmsg = 'pubmedurl';
- $CssClass = 'mw-magiclink-pmid';
+ $cssClass = 'mw-magiclink-pmid';
$id = $m[4];
} else {
throw new MWException( __METHOD__ . ': unrecognised match type "' .
substr( $m[0], 0, 20 ) . '"' );
}
$url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
- return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $CssClass );
+ return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass );
} elseif ( isset( $m[5] ) && $m[5] !== '' ) {
# ISBN
$isbn = $m[5];
@@ -1326,12 +1358,12 @@ class Parser {
/**
* Make a free external link, given a user-supplied URL
*
- * @param $url string
+ * @param string $url
*
* @return string HTML
* @private
*/
- function makeFreeExternalLink( $url ) {
+ public function makeFreeExternalLink( $url ) {
wfProfileIn( __METHOD__ );
$trail = '';
@@ -1370,7 +1402,7 @@ class Parser {
$this->getExternalLinkAttribs( $url ) );
# Register it in the output object...
# Replace unnecessary URL escape codes with their equivalent characters
- $pasteurized = self::replaceUnusualEscapes( $url );
+ $pasteurized = self::normalizeLinkUrl( $url );
$this->mOutput->addExternalLink( $pasteurized );
}
wfProfileOut( __METHOD__ );
@@ -1382,11 +1414,11 @@ class Parser {
*
* @private
*
- * @param $text string
+ * @param string $text
*
* @return string
*/
- function doHeadings( $text ) {
+ public function doHeadings( $text ) {
wfProfileIn( __METHOD__ );
for ( $i = 6; $i >= 1; --$i ) {
$h = str_repeat( '=', $i );
@@ -1400,11 +1432,11 @@ class Parser {
* Replace single quotes with HTML markup
* @private
*
- * @param $text string
+ * @param string $text
*
- * @return string the altered text
+ * @return string The altered text
*/
- function doAllQuotes( $text ) {
+ public function doAllQuotes( $text ) {
wfProfileIn( __METHOD__ );
$outtext = '';
$lines = StringUtils::explode( "\n", $text );
@@ -1419,7 +1451,7 @@ class Parser {
/**
* Helper function for doAllQuotes()
*
- * @param $text string
+ * @param string $text
*
* @return string
*/
@@ -1608,18 +1640,19 @@ class Parser {
*
* @private
*
- * @param $text string
+ * @param string $text
*
* @throws MWException
* @return string
*/
- function replaceExternalLinks( $text ) {
+ public function replaceExternalLinks( $text ) {
wfProfileIn( __METHOD__ );
$bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
if ( $bits === false ) {
wfProfileOut( __METHOD__ );
- throw new MWException( "PCRE needs to be compiled with --enable-unicode-properties in order for MediaWiki to function" );
+ throw new MWException( "PCRE needs to be compiled with "
+ . "--enable-unicode-properties in order for MediaWiki to function" );
}
$s = array_shift( $bits );
@@ -1677,43 +1710,45 @@ class Parser {
# Register link in the output object.
# Replace unnecessary URL escape codes with the referenced character
# This prevents spammers from hiding links from the filters
- $pasteurized = self::replaceUnusualEscapes( $url );
+ $pasteurized = self::normalizeLinkUrl( $url );
$this->mOutput->addExternalLink( $pasteurized );
}
wfProfileOut( __METHOD__ );
return $s;
}
+
/**
* Get the rel attribute for a particular external link.
*
* @since 1.21
- * @param string|bool $url optional URL, to extract the domain from for rel =>
+ * @param string|bool $url Optional URL, to extract the domain from for rel =>
* nofollow if appropriate
- * @param $title Title optional Title, for wgNoFollowNsExceptions lookups
- * @return string|null rel attribute for $url
+ * @param Title $title Optional Title, for wgNoFollowNsExceptions lookups
+ * @return string|null Rel attribute for $url
*/
public static function getExternalLinkRel( $url = false, $title = null ) {
global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
$ns = $title ? $title->getNamespace() : false;
- if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions ) &&
- !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions ) )
- {
+ if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
+ && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
+ ) {
return 'nofollow';
}
return null;
}
+
/**
* Get an associative array of additional HTML attributes appropriate for a
* particular external link. This currently may include rel => nofollow
* (depending on configuration, namespace, and the URL's domain) and/or a
* target attribute (depending on configuration).
*
- * @param string|bool $url optional URL, to extract the domain from for rel =>
+ * @param string|bool $url Optional URL, to extract the domain from for rel =>
* nofollow if appropriate
- * @return Array associative array of HTML attributes
+ * @return array Associative array of HTML attributes
*/
- function getExternalLinkAttribs( $url = false ) {
+ public function getExternalLinkAttribs( $url = false ) {
$attribs = array();
$attribs['rel'] = self::getExternalLinkRel( $url, $this->mTitle );
@@ -1724,52 +1759,86 @@ class Parser {
}
/**
- * Replace unusual URL escape codes with their equivalent characters
- *
- * @param $url String
- * @return String
+ * Replace unusual escape codes in a URL with their equivalent characters
*
- * @todo This can merge genuinely required bits in the path or query string,
- * breaking legit URLs. A proper fix would treat the various parts of
- * the URL differently; as a workaround, just use the output for
- * statistical records, not for actual linking/output.
+ * @deprecated since 1.24, use normalizeLinkUrl
+ * @param string $url
+ * @return string
*/
- static function replaceUnusualEscapes( $url ) {
- return preg_replace_callback( '/%[0-9A-Fa-f]{2}/',
- array( __CLASS__, 'replaceUnusualEscapesCallback' ), $url );
+ public static function replaceUnusualEscapes( $url ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return self::normalizeLinkUrl( $url );
}
/**
- * Callback function used in replaceUnusualEscapes().
- * Replaces unusual URL escape codes with their equivalent character
+ * Replace unusual escape codes in a URL with their equivalent characters
*
- * @param $matches array
+ * This generally follows the syntax defined in RFC 3986, with special
+ * consideration for HTTP query strings.
*
+ * @param string $url
* @return string
*/
- private static function replaceUnusualEscapesCallback( $matches ) {
- $char = urldecode( $matches[0] );
- $ord = ord( $char );
- # Is it an unsafe or HTTP reserved character according to RFC 1738?
- if ( $ord > 32 && $ord < 127 && strpos( '<>"#{}|\^~[]`;/?', $char ) === false ) {
- # No, shouldn't be escaped
- return $char;
- } else {
- # Yes, leave it escaped
- return $matches[0];
+ public static function normalizeLinkUrl( $url ) {
+ # First, make sure unsafe characters are encoded
+ $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
+ function ( $m ) {
+ return rawurlencode( $m[0] );
+ },
+ $url
+ );
+
+ $ret = '';
+ $end = strlen( $url );
+
+ # Fragment part - 'fragment'
+ $start = strpos( $url, '#' );
+ if ( $start !== false && $start < $end ) {
+ $ret = self::normalizeUrlComponent(
+ substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
+ $end = $start;
}
+
+ # Query part - 'query' minus &=+;
+ $start = strpos( $url, '?' );
+ if ( $start !== false && $start < $end ) {
+ $ret = self::normalizeUrlComponent(
+ substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
+ $end = $start;
+ }
+
+ # Scheme and path part - 'pchar'
+ # (we assume no userinfo or encoded colons in the host)
+ $ret = self::normalizeUrlComponent(
+ substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
+
+ return $ret;
+ }
+
+ private static function normalizeUrlComponent( $component, $unsafe ) {
+ $callback = function ( $matches ) use ( $unsafe ) {
+ $char = urldecode( $matches[0] );
+ $ord = ord( $char );
+ if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
+ # Unescape it
+ return $char;
+ } else {
+ # Leave it escaped, but use uppercase for a-f
+ return strtoupper( $matches[0] );
+ }
+ };
+ return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
}
/**
* make an image if it's allowed, either through the global
* option, through the exception, or through the on-wiki whitelist
- * @private
*
- * $param $url string
+ * @param string $url
*
* @return string
*/
- function maybeMakeExternalImage( $url ) {
+ private function maybeMakeExternalImage( $url ) {
$imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
$imagesexception = !empty( $imagesfrom );
$text = false;
@@ -1787,16 +1856,23 @@ class Parser {
} else {
$imagematch = false;
}
+
if ( $this->mOptions->getAllowExternalImages()
- || ( $imagesexception && $imagematch ) ) {
+ || ( $imagesexception && $imagematch )
+ ) {
if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
# Image found
$text = Linker::makeExternalImage( $url );
}
}
if ( !$text && $this->mOptions->getEnableImageWhitelist()
- && preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
- $whitelist = explode( "\n", wfMessage( 'external_image_whitelist' )->inContentLanguage()->text() );
+ && preg_match( self::EXT_IMAGE_REGEX, $url )
+ ) {
+ $whitelist = explode(
+ "\n",
+ wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
+ );
+
foreach ( $whitelist as $entry ) {
# Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
@@ -1815,26 +1891,27 @@ class Parser {
/**
* Process [[ ]] wikilinks
*
- * @param $s string
+ * @param string $s
*
- * @return String: processed text
+ * @return string Processed text
*
* @private
*/
- function replaceInternalLinks( $s ) {
+ public function replaceInternalLinks( $s ) {
$this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
return $s;
}
/**
* Process [[ ]] wikilinks (RIL)
- * @param $s
+ * @param string $s
* @throws MWException
* @return LinkHolderArray
*
* @private
*/
- function replaceInternalLinks2( &$s ) {
+ public function replaceInternalLinks2( &$s ) {
+ global $wgExtraInterlanguageLinkPrefixes;
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-setup' );
@@ -1863,7 +1940,9 @@ class Parser {
if ( $useLinkPrefixExtension ) {
# Match the end of a line for a word that's not followed by whitespace,
# e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
- $e2 = wfMessage( 'linkprefix' )->inContentLanguage()->text();
+ global $wgContLang;
+ $charset = $wgContLang->linkPrefixCharset();
+ $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
}
if ( is_null( $this->mTitle ) ) {
@@ -1887,8 +1966,11 @@ class Parser {
$useSubpages = $this->areSubpagesAllowed();
wfProfileOut( __METHOD__ . '-setup' );
+ // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
# Loop for each link
for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
+ // @codingStandardsIgnoreStart
+
# Check for excessive memory usage
if ( $holders->isBig() ) {
# Too big
@@ -1926,11 +2008,10 @@ class Parser {
# Still some problems for cases where the ] is meant to be outside punctuation,
# and no image is in sight. See bug 2095.
#
- if ( $text !== '' &&
- substr( $m[3], 0, 1 ) === ']' &&
- strpos( $text, '[' ) !== false
- )
- {
+ if ( $text !== ''
+ && substr( $m[3], 0, 1 ) === ']'
+ && strpos( $text, '[' ) !== false
+ ) {
$text .= ']'; # so that replaceExternalLinks($text) works later
$m[3] = substr( $m[3], 1 );
}
@@ -1940,7 +2021,8 @@ class Parser {
$m[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $m[1] ) );
}
$trail = $m[3];
- } elseif ( preg_match( $e1_img, $line, $m ) ) { # Invalid, but might be an image with a link in its caption
+ } elseif ( preg_match( $e1_img, $line, $m ) ) {
+ # Invalid, but might be an image with a link in its caption
$might_be_img = true;
$text = $m[2];
if ( strpos( $m[1], '%' ) !== false ) {
@@ -1955,10 +2037,12 @@ class Parser {
wfProfileOut( __METHOD__ . "-e1" );
wfProfileIn( __METHOD__ . "-misc" );
+ $origLink = $m[1];
+
# Don't allow internal links to pages containing
# PROTO: where PROTO is a valid URL protocol; these
# should be external links.
- if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $m[1] ) ) {
+ if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
$s .= $prefix . '[[' . $line;
wfProfileOut( __METHOD__ . "-misc" );
continue;
@@ -1966,12 +2050,12 @@ class Parser {
# Make subpage if necessary
if ( $useSubpages ) {
- $link = $this->maybeDoSubpageLink( $m[1], $text );
+ $link = $this->maybeDoSubpageLink( $origLink, $text );
} else {
- $link = $m[1];
+ $link = $origLink;
}
- $noforce = ( substr( $m[1], 0, 1 ) !== ':' );
+ $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
if ( !$noforce ) {
# Strip off leading ':'
$link = substr( $link, 1 );
@@ -1987,7 +2071,7 @@ class Parser {
}
$ns = $nt->getNamespace();
- $iw = $nt->getInterWiki();
+ $iw = $nt->getInterwiki();
wfProfileOut( __METHOD__ . "-title" );
if ( $might_be_img ) { # if this is actually an invalid link
@@ -2047,12 +2131,15 @@ class Parser {
}
# Link not escaped by : , create the various objects
- if ( $noforce ) {
+ if ( $noforce && !$nt->wasLocalInterwiki() ) {
# Interwikis
wfProfileIn( __METHOD__ . "-interwiki" );
- if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
- // XXX: the above check prevents links to sites with identifiers that are not language codes
-
+ if (
+ $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
+ Language::fetchLanguageName( $iw, null, 'mw' ) ||
+ in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
+ )
+ ) {
# Bug 24502: filter duplicates
if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
$this->mLangLinkLanguages[$iw] = true;
@@ -2108,7 +2195,6 @@ class Parser {
/**
* Strip the whitespace Category links produce, see bug 87
- * @todo We might want to use trim($tmp, "\n") here.
*/
$s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
@@ -2120,7 +2206,7 @@ class Parser {
# Self-link checking. For some languages, variants of the title are checked in
# LinkHolderArray::doVariants() to allow batching the existence checks necessary
# for linking to a different variant.
- if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && $nt->getFragment() === '' ) {
+ if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
$s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
continue;
}
@@ -2169,14 +2255,14 @@ class Parser {
* breaking URLs in the following text without breaking trails on the
* wiki links, it's been made into a horrible function.
*
- * @param $nt Title
- * @param $text String
- * @param array $query or String
- * @param $trail String
- * @param $prefix String
- * @return String: HTML-wikitext mix oh yuck
+ * @param Title $nt
+ * @param string $text
+ * @param array|string $query
+ * @param string $trail
+ * @param string $prefix
+ * @return string HTML-wikitext mix oh yuck
*/
- function makeKnownLinkHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
+ public function makeKnownLinkHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
list( $inside, $trail ) = Linker::splitTrail( $trail );
if ( is_string( $query ) ) {
@@ -2198,19 +2284,19 @@ class Parser {
* Not needed quite as much as it used to be since free links are a bit
* more sensible these days. But bracketed links are still an issue.
*
- * @param string $text more-or-less HTML
- * @return String: less-or-more HTML with NOPARSE bits
+ * @param string $text More-or-less HTML
+ * @return string Less-or-more HTML with NOPARSE bits
*/
- function armorLinks( $text ) {
+ public function armorLinks( $text ) {
return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
"{$this->mUniqPrefix}NOPARSE$1", $text );
}
/**
* Return true if subpage links should be expanded on this page.
- * @return Boolean
+ * @return bool
*/
- function areSubpagesAllowed() {
+ public function areSubpagesAllowed() {
# Some namespaces don't allow subpages
return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
}
@@ -2218,12 +2304,12 @@ class Parser {
/**
* Handle link to subpage if necessary
*
- * @param string $target the source of the link
- * @param &$text String: the link text, modified as necessary
- * @return string the full name of the link
+ * @param string $target The source of the link
+ * @param string &$text The link text, modified as necessary
+ * @return string The full name of the link
* @private
*/
- function maybeDoSubpageLink( $target, &$text ) {
+ public function maybeDoSubpageLink( $target, &$text ) {
return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
}
@@ -2233,7 +2319,7 @@ class Parser {
*
* @return string
*/
- function closeParagraph() {
+ public function closeParagraph() {
$result = '';
if ( $this->mLastSection != '' ) {
$result = '</' . $this->mLastSection . ">\n";
@@ -2248,12 +2334,12 @@ class Parser {
* of both arguments, starting at the beginning of both.
* @private
*
- * @param $st1 string
- * @param $st2 string
+ * @param string $st1
+ * @param string $st2
*
* @return int
*/
- function getCommon( $st1, $st2 ) {
+ public function getCommon( $st1, $st2 ) {
$fl = strlen( $st1 );
$shorter = strlen( $st2 );
if ( $fl < $shorter ) {
@@ -2273,21 +2359,21 @@ class Parser {
* element appropriate to the prefix character passed into them.
* @private
*
- * @param $char string
+ * @param string $char
*
* @return string
*/
- function openList( $char ) {
+ public function openList( $char ) {
$result = $this->closeParagraph();
if ( '*' === $char ) {
- $result .= "<ul>\n<li>";
+ $result .= "<ul><li>";
} elseif ( '#' === $char ) {
- $result .= "<ol>\n<li>";
+ $result .= "<ol><li>";
} elseif ( ':' === $char ) {
- $result .= "<dl>\n<dd>";
+ $result .= "<dl><dd>";
} elseif ( ';' === $char ) {
- $result .= "<dl>\n<dt>";
+ $result .= "<dl><dt>";
$this->mDTopen = true;
} else {
$result = '<!-- ERR 1 -->';
@@ -2298,12 +2384,12 @@ class Parser {
/**
* TODO: document
- * @param $char String
+ * @param string $char
* @private
*
* @return string
*/
- function nextItem( $char ) {
+ public function nextItem( $char ) {
if ( '*' === $char || '#' === $char ) {
return "</li>\n<li>";
} elseif ( ':' === $char || ';' === $char ) {
@@ -2323,40 +2409,40 @@ class Parser {
}
/**
- * TODO: document
- * @param $char String
+ * @todo Document
+ * @param string $char
* @private
*
* @return string
*/
- function closeList( $char ) {
+ public function closeList( $char ) {
if ( '*' === $char ) {
- $text = "</li>\n</ul>";
+ $text = "</li></ul>";
} elseif ( '#' === $char ) {
- $text = "</li>\n</ol>";
+ $text = "</li></ol>";
} elseif ( ':' === $char ) {
if ( $this->mDTopen ) {
$this->mDTopen = false;
- $text = "</dt>\n</dl>";
+ $text = "</dt></dl>";
} else {
- $text = "</dd>\n</dl>";
+ $text = "</dd></dl>";
}
} else {
return '<!-- ERR 3 -->';
}
- return $text . "\n";
+ return $text;
}
/**#@-*/
/**
* Make lists from lines starting with ':', '*', '#', etc. (DBL)
*
- * @param $text String
- * @param $linestart Boolean: whether or not this is at the start of a line.
+ * @param string $text
+ * @param bool $linestart Whether or not this is at the start of a line.
* @private
- * @return string the lists rendered as HTML
+ * @return string The lists rendered as HTML
*/
- function doBlockLevels( $text, $linestart ) {
+ public function doBlockLevels( $text, $linestart ) {
wfProfileIn( __METHOD__ );
# Parsing through the text line by line. The main thing
@@ -2442,6 +2528,9 @@ class Parser {
}
# Open prefixes where appropriate.
+ if ( $lastPrefix && $prefixLength > $commonPrefixLength ) {
+ $output .= "\n";
+ }
while ( $prefixLength > $commonPrefixLength ) {
$char = substr( $prefix, $commonPrefixLength, 1 );
$output .= $this->openList( $char );
@@ -2455,6 +2544,9 @@ class Parser {
}
++$commonPrefixLength;
}
+ if ( !$prefixLength && $lastPrefix ) {
+ $output .= "\n";
+ }
$lastPrefix = $prefix2;
}
@@ -2463,13 +2555,22 @@ class Parser {
wfProfileIn( __METHOD__ . "-paragraph" );
# No prefix (not in list)--go to paragraph mode
# XXX: use a stack for nestable elements like span, table and div
- $openmatch = preg_match( '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
+ $openmatch = preg_match(
+ '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|'
+ . '<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS',
+ $t
+ );
$closematch = preg_match(
- '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|' .
- '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|' . $this->mUniqPrefix . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS', $t );
+ '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'
+ . '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|'
+ . $this->mUniqPrefix
+ . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS',
+ $t
+ );
+
if ( $openmatch or $closematch ) {
$paragraphStack = false;
- # TODO bug 5718: paragraph closed
+ # @todo bug 5718: paragraph closed
$output .= $this->closeParagraph();
if ( $preOpenMatch and !$preCloseMatch ) {
$this->mInPre = true;
@@ -2481,7 +2582,10 @@ class Parser {
}
$inBlockElem = !$closematch;
} elseif ( !$inBlockElem && !$this->mInPre ) {
- if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' || trim( $t ) != '' ) and !$inBlockquote ) {
+ if ( ' ' == substr( $t, 0, 1 )
+ && ( $this->mLastSection === 'pre' || trim( $t ) != '' )
+ && !$inBlockquote
+ ) {
# pre
if ( $this->mLastSection !== 'pre' ) {
$paragraphStack = false;
@@ -2524,12 +2628,18 @@ class Parser {
$this->mInPre = false;
}
if ( $paragraphStack === false ) {
- $output .= $t . "\n";
+ $output .= $t;
+ if ( $prefixLength === 0 ) {
+ $output .= "\n";
+ }
}
}
while ( $prefixLength ) {
$output .= $this->closeList( $prefix2[$prefixLength - 1] );
--$prefixLength;
+ if ( !$prefixLength ) {
+ $output .= "\n";
+ }
}
if ( $this->mLastSection != '' ) {
$output .= '</' . $this->mLastSection . '>';
@@ -2544,13 +2654,13 @@ class Parser {
* Split up a string on ':', ignoring any occurrences inside tags
* to prevent illegal overlapping.
*
- * @param string $str the string to split
- * @param &$before String set to everything before the ':'
- * @param &$after String set to everything after the ':'
+ * @param string $str The string to split
+ * @param string &$before Set to everything before the ':'
+ * @param string &$after Set to everything after the ':'
* @throws MWException
- * @return String the position of the ':', or false if none found
+ * @return string The position of the ':', or false if none found
*/
- function findColonNoLinks( $str, &$before, &$after ) {
+ public function findColonNoLinks( $str, &$before, &$after ) {
wfProfileIn( __METHOD__ );
$pos = strpos( $str, ':' );
@@ -2712,14 +2822,14 @@ class Parser {
*
* @private
*
- * @param $index integer
- * @param bool|\PPFrame $frame
+ * @param int $index
+ * @param bool|PPFrame $frame
*
* @throws MWException
* @return string
*/
- function getVariableValue( $index, $frame = false ) {
- global $wgContLang, $wgSitename, $wgServer;
+ public function getVariableValue( $index, $frame = false ) {
+ global $wgContLang, $wgSitename, $wgServer, $wgServerName;
global $wgArticlePath, $wgScriptPath, $wgStylePath;
if ( is_null( $this->mTitle ) ) {
@@ -2747,6 +2857,9 @@ class Parser {
$pageLang = $this->getFunctionLang();
switch ( $index ) {
+ case '!':
+ $value = '|';
+ break;
case 'currentmonth':
$value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
break;
@@ -2811,13 +2924,21 @@ class Parser {
$value = wfEscapeWikiText( $this->mTitle->getRootText() );
break;
case 'rootpagenamee':
- $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getRootText() ) ) );
+ $value = wfEscapeWikiText( wfUrlEncode( str_replace(
+ ' ',
+ '_',
+ $this->mTitle->getRootText()
+ ) ) );
break;
case 'basepagename':
$value = wfEscapeWikiText( $this->mTitle->getBaseText() );
break;
case 'basepagenamee':
- $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) ) );
+ $value = wfEscapeWikiText( wfUrlEncode( str_replace(
+ ' ',
+ '_',
+ $this->mTitle->getBaseText()
+ ) ) );
break;
case 'talkpagename':
if ( $this->mTitle->canTalk() ) {
@@ -2928,7 +3049,9 @@ class Parser {
$value = $this->mTitle->getNamespace();
break;
case 'talkspace':
- $value = $this->mTitle->canTalk() ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() ) : '';
+ $value = $this->mTitle->canTalk()
+ ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
+ : '';
break;
case 'talkspacee':
$value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
@@ -2940,7 +3063,7 @@ class Parser {
$value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
break;
case 'currentdayname':
- $value = $pageLang->getWeekdayName( MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
+ $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
break;
case 'currentyear':
$value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
@@ -2960,13 +3083,19 @@ class Parser {
$value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
break;
case 'localdayname':
- $value = $pageLang->getWeekdayName( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1 );
+ $value = $pageLang->getWeekdayName(
+ (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
+ );
break;
case 'localyear':
$value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
break;
case 'localtime':
- $value = $pageLang->time( MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ), false, false );
+ $value = $pageLang->time(
+ MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
+ false,
+ false
+ );
break;
case 'localhour':
$value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
@@ -3020,8 +3149,7 @@ class Parser {
case 'server':
return $wgServer;
case 'servername':
- $serverParts = wfParseUrl( $wgServer );
- return $serverParts && isset( $serverParts['host'] ) ? $serverParts['host'] : $wgServer;
+ return $wgServerName;
case 'scriptpath':
return $wgScriptPath;
case 'stylepath':
@@ -3031,13 +3159,17 @@ class Parser {
case 'contentlanguage':
global $wgLanguageCode;
return $wgLanguageCode;
+ case 'cascadingsources':
+ $value = CoreParserFunctions::cascadingsources( $this );
+ break;
default:
$ret = null;
- if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret, &$frame ) ) ) {
- return $ret;
- } else {
- return null;
- }
+ wfRunHooks(
+ 'ParserGetVariableValueSwitch',
+ array( &$this, &$this->mVarCache, &$index, &$ret, &$frame )
+ );
+
+ return $ret;
}
if ( $index ) {
@@ -3052,7 +3184,7 @@ class Parser {
*
* @private
*/
- function initialiseVariables() {
+ public function initialiseVariables() {
wfProfileIn( __METHOD__ );
$variableIDs = MagicWord::getVariableIDs();
$substIDs = MagicWord::getSubstIDs();
@@ -3067,9 +3199,9 @@ class Parser {
* This is the ghost of replace_variables().
*
* @param string $text The text to parse
- * @param $flags Integer: bitwise combination of:
- * self::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" as if the text is being
- * included. Default is to assume a direct page view.
+ * @param int $flags Bitwise combination of:
+ * - self::PTD_FOR_INCLUSION: Handle "<noinclude>" and "<includeonly>" as if the text is being
+ * included. Default is to assume a direct page view.
*
* The generated DOM tree must depend only on the input text and the flags.
* The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
@@ -3082,11 +3214,9 @@ class Parser {
* cache may be implemented at a later date which takes further advantage of these strict
* dependency requirements.
*
- * @private
- *
* @return PPNode
*/
- function preprocessToDom( $text, $flags = 0 ) {
+ public function preprocessToDom( $text, $flags = 0 ) {
$dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
return $dom;
}
@@ -3094,7 +3224,7 @@ class Parser {
/**
* Return a three-element array: leading whitespace, string contents, trailing whitespace
*
- * @param $s string
+ * @param string $s
*
* @return array
*/
@@ -3121,16 +3251,17 @@ class Parser {
* self::OT_PREPROCESS: templates but not extension tags
* self::OT_HTML: all templates and extension tags
*
- * @param string $text the text to transform
- * @param $frame PPFrame Object describing the arguments passed to the template.
- * Arguments may also be provided as an associative array, as was the usual case before MW1.12.
- * Providing arguments this way may be useful for extensions wishing to perform variable replacement explicitly.
- * @param $argsOnly Boolean only do argument (triple-brace) expansion, not double-brace expansion
- * @private
- *
+ * @param string $text The text to transform
+ * @param bool|PPFrame $frame Object describing the arguments passed to the
+ * template. Arguments may also be provided as an associative array, as
+ * was the usual case before MW1.12. Providing arguments this way may be
+ * useful for extensions wishing to perform variable replacement
+ * explicitly.
+ * @param bool $argsOnly Only do argument (triple-brace) expansion, not
+ * double-brace expansion.
* @return string
*/
- function replaceVariables( $text, $frame = false, $argsOnly = false ) {
+ public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
# Is there any text? Also, Prevent too big inclusions!
if ( strlen( $text ) < 1 || strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) {
return $text;
@@ -3140,7 +3271,8 @@ class Parser {
if ( $frame === false ) {
$frame = $this->getPreprocessor()->newFrame();
} elseif ( !( $frame instanceof PPFrame ) ) {
- wfDebug( __METHOD__ . " called using plain parameters instead of a PPFrame instance. Creating custom frame.\n" );
+ wfDebug( __METHOD__ . " called using plain parameters instead of "
+ . "a PPFrame instance. Creating custom frame.\n" );
$frame = $this->getPreprocessor()->newCustomFrame( $frame );
}
@@ -3155,11 +3287,11 @@ class Parser {
/**
* Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
*
- * @param $args array
+ * @param array $args
*
* @return array
*/
- static function createAssocArgs( $args ) {
+ public static function createAssocArgs( $args ) {
$assocArgs = array();
$index = 1;
foreach ( $args as $arg ) {
@@ -3185,7 +3317,7 @@ class Parser {
* Warn the user when a parser limitation is reached
* Will warn at most once the user per limitation type
*
- * @param string $limitationType should be one of:
+ * @param string $limitationType Should be one of:
* 'expensive-parserfunction' (corresponding messages:
* 'expensive-parserfunction-warning',
* 'expensive-parserfunction-category')
@@ -3201,11 +3333,11 @@ class Parser {
* 'expansion-depth-exceeded' (corresponding messages:
* 'expansion-depth-exceeded-warning',
* 'expansion-depth-exceeded-category')
- * @param int|null $current Current value
- * @param int|null $max Maximum allowed, when an explicit limit has been
+ * @param string|int|null $current Current value
+ * @param string|int|null $max Maximum allowed, when an explicit limit has been
* exceeded, provide the values (optional)
*/
- function limitationWarn( $limitationType, $current = '', $max = '' ) {
+ public function limitationWarn( $limitationType, $current = '', $max = '' ) {
# does no harm if $current and $max are present but are unnecessary for the message
$warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
->inLanguage( $this->mOptions->getUserLangObj() )->text();
@@ -3217,26 +3349,32 @@ class Parser {
* Return the text of a template, after recursively
* replacing any variables or templates within the template.
*
- * @param array $piece the parts of the template
- * $piece['title']: the title, i.e. the part before the |
- * $piece['parts']: the parameter array
- * $piece['lineStart']: whether the brace was at the start of a line
- * @param $frame PPFrame The current frame, contains template arguments
- * @throws MWException
- * @return String: the text of the template
- * @private
+ * @param array $piece The parts of the template
+ * $piece['title']: the title, i.e. the part before the |
+ * $piece['parts']: the parameter array
+ * $piece['lineStart']: whether the brace was at the start of a line
+ * @param PPFrame $frame The current frame, contains template arguments
+ * @throws Exception
+ * @return string The text of the template
*/
- function braceSubstitution( $piece, $frame ) {
+ public function braceSubstitution( $piece, $frame ) {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-setup' );
- # Flags
- $found = false; # $text has been filled
- $nowiki = false; # wiki markup in $text should be escaped
- $isHTML = false; # $text is HTML, armour it against wikitext transformation
- $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered
- $isChildObj = false; # $text is a DOM node needing expansion in a child frame
- $isLocalObj = false; # $text is a DOM node needing expansion in the current frame
+ // Flags
+
+ // $text has been filled
+ $found = false;
+ // wiki markup in $text should be escaped
+ $nowiki = false;
+ // $text is HTML, armour it against wikitext transformation
+ $isHTML = false;
+ // Force interwiki transclusion to be done in raw mode not rendered
+ $forceRawInterwiki = false;
+ // $text is a DOM node needing expansion in a child frame
+ $isChildObj = false;
+ // $text is a DOM node needing expansion in the current frame
+ $isLocalObj = false;
# Title object, where $text came from
$title = false;
@@ -3251,7 +3389,8 @@ class Parser {
$originalTitle = $part1;
# $args is a list of argument nodes, starting from index 0, not including $part1
- # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object
+ # @todo FIXME: If piece['parts'] is null then the call to getLength()
+ # below won't work b/c this $args isn't an object
$args = ( null == $piece['parts'] ) ? array() : $piece['parts'];
wfProfileOut( __METHOD__ . '-setup' );
@@ -3385,13 +3524,14 @@ class Parser {
if ( !$title->isExternal() ) {
if ( $title->isSpecialPage()
&& $this->mOptions->getAllowSpecialInclusion()
- && $this->ot['html'] )
- {
+ && $this->ot['html']
+ ) {
// Pass the template arguments as URL parameters.
// "uselang" will have no effect since the Language object
// is forced to the one defined in ParserOptions.
$pageArgs = array();
- for ( $i = 0; $i < $args->getLength(); $i++ ) {
+ $argsLength = $args->getLength();
+ for ( $i = 0; $i < $argsLength; $i++ ) {
$bits = $args->item( $i )->splitArg();
if ( strval( $bits['index'] ) === '' ) {
$name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
@@ -3416,7 +3556,8 @@ class Parser {
}
} elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
$found = false; # access denied
- wfDebug( __METHOD__ . ": template inclusion denied for " . $title->getPrefixedDBkey() );
+ wfDebug( __METHOD__ . ": template inclusion denied for " .
+ $title->getPrefixedDBkey() . "\n" );
} else {
list( $text, $title ) = $this->getTemplateDom( $title );
if ( $text !== false ) {
@@ -3476,12 +3617,7 @@ class Parser {
$text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
} elseif ( $titleText !== false && $newFrame->isEmpty() ) {
# Expansion is eligible for the empty-frame cache
- if ( isset( $this->mTplExpandCache[$titleText] ) ) {
- $text = $this->mTplExpandCache[$titleText];
- } else {
- $text = $newFrame->expand( $text );
- $this->mTplExpandCache[$titleText] = $text;
- }
+ $text = $newFrame->cachedExpand( $titleText, $text );
} else {
# Uncached expansion
$text = $newFrame->expand( $text );
@@ -3504,8 +3640,8 @@ class Parser {
$text = wfEscapeWikiText( $text );
} elseif ( is_string( $text )
&& !$piece['lineStart']
- && preg_match( '/^(?:{\\||:|;|#|\*)/', $text ) )
- {
+ && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
+ ) {
# Bug 529: if the template begins with a table or block-level
# element, it should be treated as beginning a new line.
# This behavior is somewhat controversial.
@@ -3523,7 +3659,8 @@ class Parser {
preg_replace( '/^:/', '', $originalTitle );
$text = "[[:$originalTitle]]";
}
- $text .= $this->insertStripItem( '<!-- WARNING: template omitted, post-expand include size too large -->' );
+ $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
+ . 'post-expand include size too large -->' );
$this->limitationWarn( 'post-expand-template-inclusion' );
}
@@ -3550,9 +3687,10 @@ class Parser {
* nowiki: bool, wiki markup in $text should be escaped
*
* @since 1.21
- * @param $frame PPFrame The current frame, contains template arguments
- * @param $function string Function name
- * @param $args array Arguments to the function
+ * @param PPFrame $frame The current frame, contains template arguments
+ * @param string $function Function name
+ * @param array $args Arguments to the function
+ * @throws MWException
* @return array
*/
public function callParserFunction( $frame, $function, array $args = array() ) {
@@ -3655,11 +3793,11 @@ class Parser {
* Get the semi-parsed DOM representation of a template with a given title,
* and its redirect destination title. Cached.
*
- * @param $title Title
+ * @param Title $title
*
* @return array
*/
- function getTemplateDom( $title ) {
+ public function getTemplateDom( $title ) {
$cacheTitle = $title;
$titleText = $title->getPrefixedDBkey();
@@ -3694,10 +3832,11 @@ class Parser {
/**
* Fetch the unparsed text of a template and register a reference to it.
* @param Title $title
- * @return Array ( string or false, Title )
+ * @return array ( string or false, Title )
*/
- function fetchTemplateAndTitle( $title ) {
- $templateCb = $this->mOptions->getTemplateCallback(); # Defaults to Parser::statelessFetchTemplate()
+ public function fetchTemplateAndTitle( $title ) {
+ // Defaults to Parser::statelessFetchTemplate()
+ $templateCb = $this->mOptions->getTemplateCallback();
$stuff = call_user_func( $templateCb, $title, $this );
$text = $stuff['text'];
$finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
@@ -3717,9 +3856,9 @@ class Parser {
/**
* Fetch the unparsed text of a template and register a reference to it.
* @param Title $title
- * @return mixed string or false
+ * @return string|bool
*/
- function fetchTemplate( $title ) {
+ public function fetchTemplate( $title ) {
$rv = $this->fetchTemplateAndTitle( $title );
return $rv[0];
}
@@ -3728,12 +3867,12 @@ class Parser {
* Static function to get a template
* Can be overridden via ParserOptions::setTemplateCallback().
*
- * @param $title Title
- * @param $parser Parser
+ * @param Title $title
+ * @param bool|Parser $parser
*
* @return array
*/
- static function statelessFetchTemplate( $title, $parser = false ) {
+ public static function statelessFetchTemplate( $title, $parser = false ) {
$text = $skip = false;
$finalTitle = $title;
$deps = array();
@@ -3817,7 +3956,7 @@ class Parser {
* @param array $options Array of options to RepoGroup::findFile
* @return File|bool
*/
- function fetchFile( $title, $options = array() ) {
+ public function fetchFile( $title, $options = array() ) {
$res = $this->fetchFileAndTitle( $title, $options );
return $res[0];
}
@@ -3827,9 +3966,9 @@ class Parser {
* If 'broken' is a key in $options then the file will appear as a broken thumbnail.
* @param Title $title
* @param array $options Array of options to RepoGroup::findFile
- * @return Array ( File or false, Title of file )
+ * @return array ( File or false, Title of file )
*/
- function fetchFileAndTitle( $title, $options = array() ) {
+ public function fetchFileAndTitle( $title, $options = array() ) {
$file = $this->fetchFileNoRegister( $title, $options );
$time = $file ? $file->getTimestamp() : false;
@@ -3839,12 +3978,7 @@ class Parser {
if ( $file && !$title->equals( $file->getTitle() ) ) {
# Update fetched file title
$title = $file->getTitle();
- if ( is_null( $file->getRedirectedTitle() ) ) {
- # This file was not a redirect, but the title does not match.
- # Register under the new name because otherwise the link will
- # get lost.
- $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
- }
+ $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
}
return array( $file, $title );
}
@@ -3857,7 +3991,7 @@ class Parser {
*
* @param Title $title
* @param array $options Array of options to RepoGroup::findFile
- * @return File or false
+ * @return File|bool
*/
protected function fetchFileNoRegister( $title, $options = array() ) {
if ( isset( $options['broken'] ) ) {
@@ -3873,12 +4007,12 @@ class Parser {
/**
* Transclude an interwiki link.
*
- * @param $title Title
- * @param $action
+ * @param Title $title
+ * @param string $action
*
* @return string
*/
- function interwikiTransclude( $title, $action ) {
+ public function interwikiTransclude( $title, $action ) {
global $wgEnableScaryTranscluding;
if ( !$wgEnableScaryTranscluding ) {
@@ -3894,10 +4028,10 @@ class Parser {
}
/**
- * @param $url string
- * @return Mixed|String
+ * @param string $url
+ * @return mixed|string
*/
- function fetchScaryTemplateMaybeFromCache( $url ) {
+ public function fetchScaryTemplateMaybeFromCache( $url ) {
global $wgTranscludeCacheExpiry;
$dbr = wfGetDB( DB_SLAVE );
$tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
@@ -3911,8 +4045,10 @@ class Parser {
$status = $req->execute(); // Status object
if ( $status->isOK() ) {
$text = $req->getContent();
- } elseif ( $req->getStatus() != 200 ) { // Though we failed to fetch the content, this status is useless.
- return wfMessage( 'scarytranscludefailed-httpstatus', $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
+ } elseif ( $req->getStatus() != 200 ) {
+ // Though we failed to fetch the content, this status is useless.
+ return wfMessage( 'scarytranscludefailed-httpstatus' )
+ ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
} else {
return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
}
@@ -3930,12 +4066,12 @@ class Parser {
* Triple brace replacement -- used for template arguments
* @private
*
- * @param $piece array
- * @param $frame PPFrame
+ * @param array $piece
+ * @param PPFrame $frame
*
* @return array
*/
- function argSubstitution( $piece, $frame ) {
+ public function argSubstitution( $piece, $frame ) {
wfProfileIn( __METHOD__ );
$error = false;
@@ -3945,11 +4081,10 @@ class Parser {
$object = false;
$text = $frame->getArgument( $argName );
if ( $text === false && $parts->getLength() > 0
- && (
- $this->ot['html']
- || $this->ot['pre']
- || ( $this->ot['wiki'] && $frame->isTemplate() )
- )
+ && ( $this->ot['html']
+ || $this->ot['pre']
+ || ( $this->ot['wiki'] && $frame->isTemplate() )
+ )
) {
# No match in frame, use the supplied default
$object = $parts->item( 0 )->getChildren();
@@ -3986,16 +4121,17 @@ class Parser {
* attributes Optional associative array of parsed attributes
* inner Contents of extension element
* noClose Original text did not have a close tag
- * @param $frame PPFrame
+ * @param PPFrame $frame
*
* @throws MWException
* @return string
*/
- function extensionSubstitution( $params, $frame ) {
+ public function extensionSubstitution( $params, $frame ) {
$name = $frame->expand( $params['name'] );
$attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
$content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
- $marker = "{$this->mUniqPrefix}-$name-" . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
+ $marker = "{$this->mUniqPrefix}-$name-"
+ . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
$isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
( $this->ot['html'] || $this->ot['pre'] );
@@ -4070,11 +4206,11 @@ class Parser {
/**
* Increment an include size counter
*
- * @param string $type the type of expansion
- * @param $size Integer: the size of the text
- * @return Boolean: false if this inclusion would take it over the maximum, true otherwise
+ * @param string $type The type of expansion
+ * @param int $size The size of the text
+ * @return bool False if this inclusion would take it over the maximum, true otherwise
*/
- function incrementIncludeSize( $type, $size ) {
+ public function incrementIncludeSize( $type, $size ) {
if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
return false;
} else {
@@ -4086,9 +4222,9 @@ class Parser {
/**
* Increment the expensive function count
*
- * @return Boolean: false if the limit has been exceeded
+ * @return bool False if the limit has been exceeded
*/
- function incrementExpensiveFunctionCount() {
+ public function incrementExpensiveFunctionCount() {
$this->mExpensiveFunctionCount++;
return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
}
@@ -4097,11 +4233,11 @@ class Parser {
* Strip double-underscore items like __NOGALLERY__ and __NOTOC__
* Fills $this->mDoubleUnderscores, returns the modified text
*
- * @param $text string
+ * @param string $text
*
* @return string
*/
- function doDoubleUnderscore( $text ) {
+ public function doDoubleUnderscore( $text ) {
wfProfileIn( __METHOD__ );
# The position of __TOC__ needs to be recorded
@@ -4127,7 +4263,9 @@ class Parser {
if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
$this->mShowToc = false;
}
- if ( isset( $this->mDoubleUnderscores['hiddencat'] ) && $this->mTitle->getNamespace() == NS_CATEGORY ) {
+ if ( isset( $this->mDoubleUnderscores['hiddencat'] )
+ && $this->mTitle->getNamespace() == NS_CATEGORY
+ ) {
$this->addTrackingCategory( 'hidden-category-category' );
}
# (bug 8068) Allow control over whether robots index a page.
@@ -4156,8 +4294,12 @@ class Parser {
* Add a tracking category, getting the title from a system message,
* or print a debug message if the title is invalid.
*
- * @param string $msg message key
- * @return Boolean: whether the addition was successful
+ * Please add any message that you use with this function to
+ * $wgTrackingCategories. That way they will be listed on
+ * Special:TrackingCategories.
+ *
+ * @param string $msg Message key
+ * @return bool Whether the addition was successful
*/
public function addTrackingCategory( $msg ) {
if ( $this->mTitle->getNamespace() === NS_SPECIAL ) {
@@ -4195,13 +4337,13 @@ class Parser {
* It loops through all headlines, collects the necessary data, then splits up the
* string and re-inserts the newly formatted headlines.
*
- * @param $text String
- * @param string $origText original, untouched wikitext
- * @param $isMain Boolean
+ * @param string $text
+ * @param string $origText Original, untouched wikitext
+ * @param bool $isMain
* @return mixed|string
* @private
*/
- function formatHeadings( $text, $origText, $isMain = true ) {
+ public function formatHeadings( $text, $origText, $isMain = true ) {
global $wgMaxTocLevel, $wgExperimentalHtmlIds;
# Inhibit editsection links if requested in the page
@@ -4218,7 +4360,11 @@ class Parser {
# Get all headlines for numbering them and adding funky stuff like [edit]
# links - this is for later, but we need the number of headlines right now
$matches = array();
- $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?' . '>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i', $text, $matches );
+ $numMatches = preg_match_all(
+ '/<H(?P<level>[1-6])(?P<attrib>.*?' . '>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
+ $text,
+ $matches
+ );
# if there are fewer than 4 headlines in the article, do not show TOC
# unless it's been explicitly enabled.
@@ -4367,7 +4513,10 @@ class Parser {
# We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
# to allow setting directionality in toc items.
$tocline = preg_replace(
- array( '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#', '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#' ),
+ array(
+ '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#',
+ '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#'
+ ),
array( '', '<$1>' ),
$safeHeadline
);
@@ -4431,7 +4580,11 @@ class Parser {
# Don't number the heading if it is the only one (looks silly)
if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
# the two are different if the line contains a link
- $headline = Html::element( 'span', array( 'class' => 'mw-headline-number' ), $numbering ) . ' ' . $headline;
+ $headline = Html::element(
+ 'span',
+ array( 'class' => 'mw-headline-number' ),
+ $numbering
+ ) . ' ' . $headline;
}
# Create the anchor for linking from the TOC to the section
@@ -4479,21 +4632,30 @@ class Parser {
if ( $isTemplate ) {
# Put a T flag in the section identifier, to indicate to extractSections()
# that sections inside <includeonly> should be counted.
- $editlinkArgs = array( $titleText, "T-$sectionIndex"/*, null */ );
+ $editsectionPage = $titleText;
+ $editsectionSection = "T-$sectionIndex";
+ $editsectionContent = null;
} else {
- $editlinkArgs = array( $this->mTitle->getPrefixedText(), $sectionIndex, $headlineHint );
+ $editsectionPage = $this->mTitle->getPrefixedText();
+ $editsectionSection = $sectionIndex;
+ $editsectionContent = $headlineHint;
}
- // We use a bit of pesudo-xml for editsection markers. The language converter is run later on
- // Using a UNIQ style marker leads to the converter screwing up the tokens when it converts stuff
- // And trying to insert strip tags fails too. At this point all real inputted tags have already been escaped
- // so we don't have to worry about a user trying to input one of these markers directly.
- // We use a page and section attribute to stop the language converter from converting these important bits
- // of data, but put the headline hint inside a content block because the language converter is supposed to
+ // We use a bit of pesudo-xml for editsection markers. The
+ // language converter is run later on. Using a UNIQ style marker
+ // leads to the converter screwing up the tokens when it
+ // converts stuff. And trying to insert strip tags fails too. At
+ // this point all real inputted tags have already been escaped,
+ // so we don't have to worry about a user trying to input one of
+ // these markers directly. We use a page and section attribute
+ // to stop the language converter from converting these
+ // important bits of data, but put the headline hint inside a
+ // content block because the language converter is supposed to
// be able to convert that piece of data.
- $editlink = '<mw:editsection page="' . htmlspecialchars( $editlinkArgs[0] );
- $editlink .= '" section="' . htmlspecialchars( $editlinkArgs[1] ) . '"';
- if ( isset( $editlinkArgs[2] ) ) {
- $editlink .= '>' . $editlinkArgs[2] . '</mw:editsection>';
+ // Gets replaced with html in ParserOutput::getText
+ $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
+ $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
+ if ( $editsectionContent !== null ) {
+ $editlink .= '>' . $editsectionContent . '</mw:editsection>';
} else {
$editlink .= '/>';
}
@@ -4521,6 +4683,7 @@ class Parser {
$toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
$this->mOutput->setTOCHTML( $toc );
$toc = self::TOC_START . $toc . self::TOC_END;
+ $this->mOutput->addModules( 'mediawiki.toc' );
}
if ( $isMain ) {
@@ -4575,14 +4738,19 @@ class Parser {
* Transform wiki markup when saving a page by doing "\r\n" -> "\n"
* conversion, substitting signatures, {{subst:}} templates, etc.
*
- * @param string $text the text to transform
- * @param $title Title: the Title object for the current article
- * @param $user User: the User object describing the current user
- * @param $options ParserOptions: parsing options
- * @param $clearState Boolean: whether to clear the parser state first
- * @return String: the altered wiki markup
+ * @param string $text The text to transform
+ * @param Title $title The Title object for the current article
+ * @param User $user The User object describing the current user
+ * @param ParserOptions $options Parsing options
+ * @param bool $clearState Whether to clear the parser state first
+ * @return string The altered wiki markup
*/
- public function preSaveTransform( $text, Title $title, User $user, ParserOptions $options, $clearState = true ) {
+ public function preSaveTransform( $text, Title $title, User $user,
+ ParserOptions $options, $clearState = true
+ ) {
+ if ( $clearState ) {
+ $magicScopeVariable = $this->lock();
+ }
$this->startParse( $title, $options, self::OT_WIKI, $clearState );
$this->setUser( $user );
@@ -4602,14 +4770,13 @@ class Parser {
/**
* Pre-save transform helper function
- * @private
*
- * @param $text string
- * @param $user User
+ * @param string $text
+ * @param User $user
*
* @return string
*/
- function pstPass2( $text, $user ) {
+ private function pstPass2( $text, $user ) {
global $wgContLang;
# Note: This is the timestamp saved as hardcoded wikitext to
@@ -4652,10 +4819,14 @@ class Parser {
$tc = '[' . Title::legalChars() . ']';
$nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
- $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
- $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/"; # [[ns:page(context)|]] (double-width brackets, added in r40257)
- $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; # [[ns:page (context), context|]] (using either single or double-width comma)
- $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]] (reverse pipe trick: add context from page title)
+ // [[ns:page (context)|]]
+ $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
+ // [[ns:page(context)|]] (double-width brackets, added in r40257)
+ $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
+ // [[ns:page (context), context|]] (using either single or double-width comma)
+ $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
+ // [[|page]] (reverse pipe trick: add context from page title)
+ $p2 = "/\[\[\\|($tc+)]]/";
# try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
$text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
@@ -4687,13 +4858,13 @@ class Parser {
* Do not reuse this parser instance after calling getUserSig(),
* as it may have changed if it's the $wgParser.
*
- * @param $user User
- * @param string|bool $nickname nickname to use or false to use user's default nickname
- * @param $fancySig Boolean|null whether the nicknname is the complete signature
- * or null to use default value
+ * @param User $user
+ * @param string|bool $nickname Nickname to use or false to use user's default nickname
+ * @param bool|null $fancySig whether the nicknname is the complete signature
+ * or null to use default value
* @return string
*/
- function getUserSig( &$user, $nickname = false, $fancySig = null ) {
+ public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
global $wgMaxSigChars;
$username = $user->getName();
@@ -4732,16 +4903,17 @@ class Parser {
$nickText = wfEscapeWikiText( $nickname );
$msgName = $user->isAnon() ? 'signature-anon' : 'signature';
- return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()->title( $this->getTitle() )->text();
+ return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
+ ->title( $this->getTitle() )->text();
}
/**
* Check that the user's signature contains no bad XML
*
- * @param $text String
- * @return mixed An expanded string, or false if invalid.
+ * @param string $text
+ * @return string|bool An expanded string, or false if invalid.
*/
- function validateSig( $text ) {
+ public function validateSig( $text ) {
return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
}
@@ -4751,13 +4923,14 @@ class Parser {
* 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures @see cleanSigInSig
* 2) Substitute all transclusions
*
- * @param $text String
+ * @param string $text
* @param bool $parsing Whether we're cleaning (preferences save) or parsing
- * @return String: signature text
+ * @return string Signature text
*/
public function cleanSig( $text, $parsing = false ) {
if ( !$parsing ) {
global $wgTitle;
+ $magicScopeVariable = $this->lock();
$this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
}
@@ -4788,8 +4961,8 @@ class Parser {
/**
* Strip ~~~, ~~~~ and ~~~~~ out of signatures
*
- * @param $text String
- * @return String: signature text with /~{3,5}/ removed
+ * @param string $text
+ * @return string Signature text with /~{3,5}/ removed
*/
public static function cleanSigInSig( $text ) {
$text = preg_replace( '/~{3,5}/', '', $text );
@@ -4800,22 +4973,26 @@ class Parser {
* Set up some variables which are usually set up in parse()
* so that an external function can call some class members with confidence
*
- * @param $title Title|null
- * @param $options ParserOptions
- * @param $outputType
- * @param $clearState bool
+ * @param Title|null $title
+ * @param ParserOptions $options
+ * @param int $outputType
+ * @param bool $clearState
*/
- public function startExternalParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
+ public function startExternalParse( Title $title = null, ParserOptions $options,
+ $outputType, $clearState = true
+ ) {
$this->startParse( $title, $options, $outputType, $clearState );
}
/**
- * @param $title Title|null
- * @param $options ParserOptions
- * @param $outputType
- * @param $clearState bool
+ * @param Title|null $title
+ * @param ParserOptions $options
+ * @param int $outputType
+ * @param bool $clearState
*/
- private function startParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
+ private function startParse( Title $title = null, ParserOptions $options,
+ $outputType, $clearState = true
+ ) {
$this->setTitle( $title );
$this->mOptions = $options;
$this->setOutputType( $outputType );
@@ -4827,10 +5004,10 @@ class Parser {
/**
* Wrapper for preprocess()
*
- * @param string $text the text to preprocess
- * @param $options ParserOptions: options
- * @param $title Title object or null to use $wgTitle
- * @return String
+ * @param string $text The text to preprocess
+ * @param ParserOptions $options Options
+ * @param Title|null $title Title object or null to use $wgTitle
+ * @return string
*/
public function transformMsg( $text, $options, $title = null ) {
static $executing = false;
@@ -4873,10 +5050,10 @@ class Parser {
* this interface, as it is not documented and injudicious use could smash
* private variables.**
*
- * @param $tag Mixed: the tag to use, e.g. 'hook' for "<hook>"
- * @param $callback Mixed: the callback function (and object) to use for the tag
+ * @param string $tag The tag to use, e.g. 'hook' for "<hook>"
+ * @param callable $callback The callback function (and object) to use for the tag
* @throws MWException
- * @return Mixed|null The old value of the mTagHooks array associated with the hook
+ * @return callable|null The old value of the mTagHooks array associated with the hook
*/
public function setHook( $tag, $callback ) {
$tag = strtolower( $tag );
@@ -4904,12 +5081,12 @@ class Parser {
* @since 1.10
* @todo better document or deprecate this
*
- * @param $tag Mixed: the tag to use, e.g. 'hook' for "<hook>"
- * @param $callback Mixed: the callback function (and object) to use for the tag
+ * @param string $tag The tag to use, e.g. 'hook' for "<hook>"
+ * @param callable $callback The callback function (and object) to use for the tag
* @throws MWException
- * @return Mixed|null The old value of the mTagHooks array associated with the hook
+ * @return callable|null The old value of the mTagHooks array associated with the hook
*/
- function setTransparentTagHook( $tag, $callback ) {
+ public function setTransparentTagHook( $tag, $callback ) {
$tag = strtolower( $tag );
if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
@@ -4923,7 +5100,7 @@ class Parser {
/**
* Remove all tag hooks
*/
- function clearTagHooks() {
+ public function clearTagHooks() {
$this->mTagHooks = array();
$this->mFunctionTagHooks = array();
$this->mStripList = $this->mDefaultStripList;
@@ -4946,8 +5123,8 @@ class Parser {
* isHTML The returned text is HTML, armour it against wikitext transformation
*
* @param string $id The magic word ID
- * @param $callback Mixed: the callback function (and object) to use
- * @param $flags Integer: a combination of the following flags:
+ * @param callable $callback The callback function (and object) to use
+ * @param int $flags A combination of the following flags:
* SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}}
*
* SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text. This
@@ -4970,7 +5147,7 @@ class Parser {
* about the methods available in PPFrame and PPNode.
*
* @throws MWException
- * @return string|callback The old callback function for this name, if any
+ * @return string|callable The old callback function for this name, if any
*/
public function setFunctionHook( $id, $callback, $flags = 0 ) {
global $wgContLang;
@@ -5008,9 +5185,9 @@ class Parser {
/**
* Get all registered function hook identifiers
*
- * @return Array
+ * @return array
*/
- function getFunctionHooks() {
+ public function getFunctionHooks() {
return array_keys( $this->mFunctionHooks );
}
@@ -5018,13 +5195,13 @@ class Parser {
* Create a tag function, e.g. "<test>some stuff</test>".
* Unlike tag hooks, tag functions are parsed at preprocessor level.
* Unlike parser functions, their content is not preprocessed.
- * @param $tag
- * @param $callback
- * @param $flags
+ * @param string $tag
+ * @param callable $callback
+ * @param int $flags
* @throws MWException
* @return null
*/
- function setFunctionTagHook( $tag, $callback, $flags ) {
+ public function setFunctionTagHook( $tag, $callback, $flags ) {
$tag = strtolower( $tag );
if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
@@ -5045,12 +5222,12 @@ class Parser {
* Replace "<!--LINK-->" link placeholders with actual links, in the buffer
* Placeholders created in Skin::makeLinkObj()
*
- * @param $text string
- * @param $options int
+ * @param string $text
+ * @param int $options
*
- * @return array of link CSS classes, indexed by PDBK.
+ * @return array Array of link CSS classes, indexed by PDBK.
*/
- function replaceLinkHolders( &$text, $options = 0 ) {
+ public function replaceLinkHolders( &$text, $options = 0 ) {
return $this->mLinkHolders->replace( $text );
}
@@ -5058,10 +5235,10 @@ class Parser {
* Replace "<!--LINK-->" link placeholders with plain text of links
* (not HTML-formatted).
*
- * @param $text String
- * @return String
+ * @param string $text
+ * @return string
*/
- function replaceLinkHoldersText( $text ) {
+ public function replaceLinkHoldersText( $text ) {
return $this->mLinkHolders->replaceText( $text );
}
@@ -5078,7 +5255,7 @@ class Parser {
* @param array $params
* @return string HTML
*/
- function renderImageGallery( $text, $params ) {
+ public function renderImageGallery( $text, $params ) {
wfProfileIn( __METHOD__ );
$mode = false;
@@ -5203,7 +5380,7 @@ class Parser {
} else {
$localLinkTitle = Title::newFromText( $linkValue );
if ( $localLinkTitle !== null ) {
- $link = $localLinkTitle->getLocalURL();
+ $link = $localLinkTitle->getLinkURL();
}
}
break;
@@ -5213,7 +5390,7 @@ class Parser {
$handlerOptions[$paramName] = $match;
} else {
// Guess not. Append it to the caption.
- wfDebug( "$parameterMatch failed parameter validation" );
+ wfDebug( "$parameterMatch failed parameter validation\n" );
$label .= '|' . $parameterMatch;
}
}
@@ -5230,15 +5407,16 @@ class Parser {
$ig->add( $title, $label, $alt, $link, $handlerOptions );
}
$html = $ig->toHTML();
+ wfRunHooks( 'AfterParserFetchFileAndTitle', array( $this, $ig, &$html ) );
wfProfileOut( __METHOD__ );
return $html;
}
/**
- * @param $handler
+ * @param string $handler
* @return array
*/
- function getImageParams( $handler ) {
+ public function getImageParams( $handler ) {
if ( $handler ) {
$handlerClass = get_class( $handler );
} else {
@@ -5281,12 +5459,12 @@ class Parser {
/**
* Parse image options text and use it to make an image
*
- * @param $title Title
- * @param $options String
- * @param $holders LinkHolderArray|bool
+ * @param Title $title
+ * @param string $options
+ * @param LinkHolderArray|bool $holders
* @return string HTML
*/
- function makeImage( $title, $options, $holders = false ) {
+ public function makeImage( $title, $options, $holders = false ) {
# Check if the options text is of the form "options|alt text"
# Options are:
# * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
@@ -5336,6 +5514,7 @@ class Parser {
$caption = '';
$params = array( 'frame' => array(), 'handler' => array(),
'horizAlign' => array(), 'vertAlign' => array() );
+ $seenformat = false;
foreach ( $parts as $part ) {
$part = trim( $part );
list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
@@ -5384,7 +5563,7 @@ class Parser {
$paramName = 'no-link';
$value = true;
$validated = true;
- } elseif ( preg_match( "/^(?i)$prots/", $value ) ) {
+ } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
if ( preg_match( "/^((?i)$prots)$chars+$/u", $value, $m ) ) {
$paramName = 'link-url';
$this->mOutput->addExternalLink( $value );
@@ -5403,6 +5582,13 @@ class Parser {
}
}
break;
+ case 'frameless':
+ case 'framed':
+ case 'thumbnail':
+ // use first appearing option, discard others.
+ $validated = ! $seenformat;
+ $seenformat = true;
+ break;
default:
# Most other things appear to be empty or numeric...
$validated = ( $value === false || is_numeric( trim( $value ) ) );
@@ -5430,10 +5616,10 @@ class Parser {
$params['frame']['caption'] = $caption;
# Will the image be presented in a frame, with the caption below?
- $imageIsFramed = isset( $params['frame']['frame'] ) ||
- isset( $params['frame']['framed'] ) ||
- isset( $params['frame']['thumbnail'] ) ||
- isset( $params['frame']['manualthumb'] );
+ $imageIsFramed = isset( $params['frame']['frame'] )
+ || isset( $params['frame']['framed'] )
+ || isset( $params['frame']['thumbnail'] )
+ || isset( $params['frame']['manualthumb'] );
# In the old days, [[Image:Foo|text...]] would set alt text. Later it
# came to also set the caption, ordinary text after the image -- which
@@ -5490,9 +5676,9 @@ class Parser {
}
/**
- * @param $caption
- * @param $holders LinkHolderArray
- * @return mixed|String
+ * @param string $caption
+ * @param LinkHolderArray|bool $holders
+ * @return mixed|string
*/
protected function stripAltText( $caption, $holders ) {
# Strip bad stuff out of the title (tooltip). We can't just use
@@ -5517,7 +5703,7 @@ class Parser {
* Set a flag in the output object indicating that the content is dynamic and
* shouldn't be cached.
*/
- function disableCache() {
+ public function disableCache() {
wfDebug( "Parser output marked as uncacheable.\n" );
if ( !$this->mOutput ) {
throw new MWException( __METHOD__ .
@@ -5531,11 +5717,11 @@ class Parser {
* Callback from the Sanitizer for expanding items found in HTML attribute
* values, so they can be safely tested and escaped.
*
- * @param $text String
- * @param $frame PPFrame
- * @return String
+ * @param string $text
+ * @param bool|PPFrame $frame
+ * @return string
*/
- function attributeStripCallback( &$text, $frame = false ) {
+ public function attributeStripCallback( &$text, $frame = false ) {
$text = $this->replaceVariables( $text, $frame );
$text = $this->mStripState->unstripBoth( $text );
return $text;
@@ -5546,8 +5732,12 @@ class Parser {
*
* @return array
*/
- function getTags() {
- return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ), array_keys( $this->mFunctionTagHooks ) );
+ public function getTags() {
+ return array_merge(
+ array_keys( $this->mTransparentTagHooks ),
+ array_keys( $this->mTagHooks ),
+ array_keys( $this->mFunctionTagHooks )
+ );
}
/**
@@ -5556,11 +5746,11 @@ class Parser {
* Transparent tag hooks are like regular XML-style tag hooks, except they
* operate late in the transformation sequence, on HTML instead of wikitext.
*
- * @param $text string
+ * @param string $text
*
* @return string
*/
- function replaceTransparentTags( $text ) {
+ public function replaceTransparentTags( $text ) {
$matches = array();
$elements = array_keys( $this->mTransparentTagHooks );
$text = self::extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix );
@@ -5570,7 +5760,10 @@ class Parser {
list( $element, $content, $params, $tag ) = $data;
$tagName = strtolower( $element );
if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
- $output = call_user_func_array( $this->mTransparentTagHooks[$tagName], array( $content, $params, $this ) );
+ $output = call_user_func_array(
+ $this->mTransparentTagHooks[$tagName],
+ array( $content, $params, $this )
+ );
} else {
$output = $tag;
}
@@ -5586,7 +5779,7 @@ class Parser {
* External callers should use the getSection and replaceSection methods.
*
* @param string $text Page wikitext
- * @param string $section a section identifier string of the form:
+ * @param string|number $sectionId A section identifier string of the form:
* "<flag1> - <flag2> - ... - <section number>"
*
* Currently the only recognised flag is "T", which means the target section number
@@ -5603,20 +5796,22 @@ class Parser {
* string. If $text is the empty string and section 0 is replaced, $newText is
* returned.
*
- * @param string $mode one of "get" or "replace"
- * @param string $newText replacement text for section data.
- * @return String: for "get", the extracted section text.
- * for "replace", the whole page with the section replaced.
+ * @param string $mode One of "get" or "replace"
+ * @param string $newText Replacement text for section data.
+ * @return string For "get", the extracted section text.
+ * for "replace", the whole page with the section replaced.
*/
- private function extractSections( $text, $section, $mode, $newText = '' ) {
+ private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
global $wgTitle; # not generally used but removes an ugly failure mode
+
+ $magicScopeVariable = $this->lock();
$this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
$outText = '';
$frame = $this->getPreprocessor()->newFrame();
# Process section extraction flags
$flags = 0;
- $sectionParts = explode( '-', $section );
+ $sectionParts = explode( '-', $sectionId );
$sectionIndex = array_pop( $sectionParts );
foreach ( $sectionParts as $part ) {
if ( $part === 'T' ) {
@@ -5724,13 +5919,15 @@ class Parser {
*
* If a section contains subsections, these are also returned.
*
- * @param string $text text to look in
- * @param string $section section identifier
- * @param string $deftext default to return if section is not found
- * @return string text of the requested section
+ * @param string $text Text to look in
+ * @param string|number $sectionId Section identifier as a number or string
+ * (e.g. 0, 1 or 'T-1').
+ * @param string $defaultText Default to return if section is not found
+ *
+ * @return string Text of the requested section
*/
- public function getSection( $text, $section, $deftext = '' ) {
- return $this->extractSections( $text, $section, "get", $deftext );
+ public function getSection( $text, $sectionId, $defaultText = '' ) {
+ return $this->extractSections( $text, $sectionId, 'get', $defaultText );
}
/**
@@ -5738,30 +5935,33 @@ class Parser {
* specified by $section has been replaced with $text. If the target
* section does not exist, $oldtext is returned unchanged.
*
- * @param string $oldtext former text of the article
- * @param int $section section identifier
- * @param string $text replacing text
- * @return String: modified text
+ * @param string $oldText Former text of the article
+ * @param string|number $sectionId Section identifier as a number or string
+ * (e.g. 0, 1 or 'T-1').
+ * @param string $newText Replacing text
+ *
+ * @return string Modified text
*/
- public function replaceSection( $oldtext, $section, $text ) {
- return $this->extractSections( $oldtext, $section, "replace", $text );
+ public function replaceSection( $oldText, $sectionId, $newText ) {
+ return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
}
/**
* Get the ID of the revision we are parsing
*
- * @return Mixed: integer or null
+ * @return int|null
*/
- function getRevisionId() {
+ public function getRevisionId() {
return $this->mRevisionId;
}
/**
* Get the revision object for $this->mRevisionId
*
- * @return Revision|null either a Revision object or null
+ * @return Revision|null Either a Revision object or null
+ * @since 1.23 (public since 1.23)
*/
- protected function getRevisionObject() {
+ public function getRevisionObject() {
if ( !is_null( $this->mRevisionObject ) ) {
return $this->mRevisionObject;
}
@@ -5776,8 +5976,9 @@ class Parser {
/**
* Get the timestamp associated with the current revision, adjusted for
* the default server-local timestamp
+ * @return string
*/
- function getRevisionTimestamp() {
+ public function getRevisionTimestamp() {
if ( is_null( $this->mRevisionTimestamp ) ) {
wfProfileIn( __METHOD__ );
@@ -5802,9 +6003,9 @@ class Parser {
/**
* Get the name of the user that edited the last revision
*
- * @return String: user name
+ * @return string User name
*/
- function getRevisionUser() {
+ public function getRevisionUser() {
if ( is_null( $this->mRevisionUser ) ) {
$revObject = $this->getRevisionObject();
@@ -5822,9 +6023,9 @@ class Parser {
/**
* Get the size of the revision
*
- * @return int|null revision size
+ * @return int|null Revision size
*/
- function getRevisionSize() {
+ public function getRevisionSize() {
if ( is_null( $this->mRevisionSize ) ) {
$revObject = $this->getRevisionObject();
@@ -5872,7 +6073,7 @@ class Parser {
* Accessor for $mDefaultSort
* Unlike getDefaultSort(), will return false if none is set
*
- * @return string or false
+ * @return string|bool
*/
public function getCustomDefaultSort() {
return $this->mDefaultSort;
@@ -5883,7 +6084,7 @@ class Parser {
* presumably extracted from a heading, for example "Header" from
* "== Header ==".
*
- * @param $text string
+ * @param string $text
*
* @return string
*/
@@ -5919,7 +6120,7 @@ class Parser {
* to create valid section anchors by mimicing the output of the
* parser when headings are parsed.
*
- * @param string $text text string to be stripped of wikitext
+ * @param string $text Text string to be stripped of wikitext
* for use in a Section anchor
* @return string Filtered text string
*/
@@ -5930,7 +6131,7 @@ class Parser {
# Strip external link markup
# @todo FIXME: Not tolerant to blank link text
- # I.E. [http://www.mediawiki.org] will render as [1] or something depending
+ # I.E. [https://www.mediawiki.org] will render as [1] or something depending
# on how many empty links there are on the page - need to figure that out.
$text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
@@ -5945,14 +6146,15 @@ class Parser {
/**
* strip/replaceVariables/unstrip for preprocessor regression testing
*
- * @param $text string
- * @param $title Title
- * @param $options ParserOptions
- * @param $outputType int
+ * @param string $text
+ * @param Title $title
+ * @param ParserOptions $options
+ * @param int $outputType
*
* @return string
*/
- function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) {
+ public function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) {
+ $magicScopeVariable = $this->lock();
$this->startParse( $title, $options, $outputType, true );
$text = $this->replaceVariables( $text );
@@ -5962,22 +6164,22 @@ class Parser {
}
/**
- * @param $text string
- * @param $title Title
- * @param $options ParserOptions
+ * @param string $text
+ * @param Title $title
+ * @param ParserOptions $options
* @return string
*/
- function testPst( $text, Title $title, ParserOptions $options ) {
+ public function testPst( $text, Title $title, ParserOptions $options ) {
return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
}
/**
- * @param $text
- * @param $title Title
- * @param $options ParserOptions
+ * @param string $text
+ * @param Title $title
+ * @param ParserOptions $options
* @return string
*/
- function testPreprocess( $text, Title $title, ParserOptions $options ) {
+ public function testPreprocess( $text, Title $title, ParserOptions $options ) {
return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
}
@@ -5992,12 +6194,12 @@ class Parser {
* two strings will be replaced with the value returned by the callback in
* each case.
*
- * @param $s string
- * @param $callback
+ * @param string $s
+ * @param callable $callback
*
* @return string
*/
- function markerSkipCallback( $s, $callback ) {
+ public function markerSkipCallback( $s, $callback ) {
$i = 0;
$out = '';
while ( $i < strlen( $s ) ) {
@@ -6024,10 +6226,10 @@ class Parser {
/**
* Remove any strip markers found in the given text.
*
- * @param $text Input string
+ * @param string $text Input string
* @return string
*/
- function killMarkers( $text ) {
+ public function killMarkers( $text ) {
return $this->mStripState->killMarkers( $text );
}
@@ -6043,11 +6245,11 @@ class Parser {
* unserializeHalfParsedText(). The text can then be safely incorporated into
* the return value of a parser hook.
*
- * @param $text string
+ * @param string $text
*
* @return array
*/
- function serializeHalfParsedText( $text ) {
+ public function serializeHalfParsedText( $text ) {
wfProfileIn( __METHOD__ );
$data = array(
'text' => $text,
@@ -6072,9 +6274,9 @@ class Parser {
*
* @param array $data Serialized data
* @throws MWException
- * @return String
+ * @return string
*/
- function unserializeHalfParsedText( $data ) {
+ public function unserializeHalfParsedText( $data ) {
if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
throw new MWException( __METHOD__ . ': invalid version' );
}
@@ -6095,18 +6297,18 @@ class Parser {
* serializeHalfParsedText(), is compatible with the current version of the
* parser.
*
- * @param $data Array
+ * @param array $data
*
* @return bool
*/
- function isValidHalfParsedText( $data ) {
+ public function isValidHalfParsedText( $data ) {
return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
}
/**
* Parsed a width param of imagelink like 300px or 200x300px
*
- * @param $value String
+ * @param string $value
*
* @return array
* @since 1.20
@@ -6130,4 +6332,68 @@ class Parser {
}
return $parsedWidthParam;
}
+
+ /**
+ * Lock the current instance of the parser.
+ *
+ * This is meant to stop someone from calling the parser
+ * recursively and messing up all the strip state.
+ *
+ * @throws MWException If parser is in a parse
+ * @return ScopedCallback The lock will be released once the return value goes out of scope.
+ */
+ protected function lock() {
+ if ( $this->mInParse ) {
+ throw new MWException( "Parser state cleared while parsing. "
+ . "Did you call Parser::parse recursively?" );
+ }
+ $this->mInParse = true;
+
+ $that = $this;
+ $recursiveCheck = new ScopedCallback( function() use ( $that ) {
+ $that->mInParse = false;
+ } );
+
+ return $recursiveCheck;
+ }
+
+ /**
+ * Strip outer <p></p> tag from the HTML source of a single paragraph.
+ *
+ * Returns original HTML if the <p/> tag has any attributes, if there's no wrapping <p/> tag,
+ * or if there is more than one <p/> tag in the input HTML.
+ *
+ * @param string $html
+ * @return string
+ * @since 1.24
+ */
+ public static function stripOuterParagraph( $html ) {
+ $m = array();
+ if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
+ if ( strpos( $m[1], '</p>' ) === false ) {
+ $html = $m[1];
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Return this parser if it is not doing anything, otherwise
+ * get a fresh parser. You can use this method by doing
+ * $myParser = $wgParser->getFreshParser(), or more simply
+ * $wgParser->getFreshParser()->parse( ... );
+ * if you're unsure if $wgParser is safe to use.
+ *
+ * @since 1.24
+ * @return Parser A parser object that is not parsing anything
+ */
+ public function getFreshParser() {
+ global $wgParserConf;
+ if ( $this->mInParse ) {
+ return new $wgParserConf['class']( $wgParserConf );
+ } else {
+ return $this;
+ }
+ }
}
diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php
index 7053f134..79523003 100644
--- a/includes/parser/ParserCache.php
+++ b/includes/parser/ParserCache.php
@@ -26,9 +26,8 @@
* @todo document
*/
class ParserCache {
+ /** @var MWMemcached */
private $mMemc;
- const try116cache = false; /* Only useful $wgParserCacheExpireTime after updating to 1.17 */
-
/**
* Get an instance of this object
*
@@ -47,7 +46,7 @@ class ParserCache {
* Setup a cache pathway with a given back-end storage mechanism.
* May be a memcached client or a BagOStuff derivative.
*
- * @param $memCached Object
+ * @param MWMemcached $memCached
* @throws MWException
*/
protected function __construct( $memCached ) {
@@ -58,8 +57,8 @@ class ParserCache {
}
/**
- * @param $article Article
- * @param $hash string
+ * @param Article $article
+ * @param string $hash
* @return mixed|string
*/
protected function getParserOutputKey( $article, $hash ) {
@@ -74,7 +73,7 @@ class ParserCache {
}
/**
- * @param $article Article
+ * @param Article $article
* @return mixed|string
*/
protected function getOptionsKey( $article ) {
@@ -92,11 +91,11 @@ class ParserCache {
* English preferences. That's why we take into account *all* user
* options. (r70809 CR)
*
- * @param $article Article
- * @param $popts ParserOptions
+ * @param Article $article
+ * @param ParserOptions $popts
* @return string
*/
- function getETag( $article, $popts ) {
+ public function getETag( $article, $popts ) {
return 'W/"' . $this->getParserOutputKey( $article,
$popts->optionsHash( ParserOptions::legacyOptions(), $article->getTitle() ) ) .
"--" . $article->getTouched() . '"';
@@ -104,8 +103,8 @@ class ParserCache {
/**
* Retrieve the ParserOutput from ParserCache, even if it's outdated.
- * @param $article Article
- * @param $popts ParserOptions
+ * @param Article $article
+ * @param ParserOptions $popts
* @return ParserOutput|bool False on failure
*/
public function getDirty( $article, $popts ) {
@@ -114,15 +113,22 @@ class ParserCache {
}
/**
- * Used to provide a unique id for the PoolCounter.
+ * Generates a key for caching the given article considering
+ * the given parser options.
+ *
+ * @note Which parser options influence the cache key
+ * is controlled via ParserOutput::recordOption() or
+ * ParserOptions::addExtraKey().
+ *
+ * @note Used by Article to provide a unique id for the PoolCounter.
* It would be preferable to have this code in get()
* instead of having Article looking in our internals.
*
* @todo Document parameter $useOutdated
*
- * @param $article Article
- * @param $popts ParserOptions
- * @param $useOutdated Boolean (default true)
+ * @param Article $article
+ * @param ParserOptions $popts
+ * @param bool $useOutdated (default true)
* @return bool|mixed|string
*/
public function getKey( $article, $popts, $useOutdated = true ) {
@@ -139,29 +145,40 @@ class ParserCache {
if ( !$useOutdated && $optionsKey->expired( $article->getTouched() ) ) {
wfIncrStats( "pcache_miss_expired" );
$cacheTime = $optionsKey->getCacheTime();
- wfDebug( "Parser options key expired, touched " . $article->getTouched() . ", epoch $wgCacheEpoch, cached $cacheTime\n" );
+ wfDebug( "Parser options key expired, touched " . $article->getTouched()
+ . ", epoch $wgCacheEpoch, cached $cacheTime\n" );
+ return false;
+ } elseif ( $optionsKey->isDifferentRevision( $article->getLatest() ) ) {
+ wfIncrStats( "pcache_miss_revid" );
+ $revId = $article->getLatest();
+ $cachedRevId = $optionsKey->getCacheRevisionId();
+ wfDebug( "ParserOutput key is for an old revision, latest $revId, cached $cachedRevId\n" );
return false;
}
+ // $optionsKey->mUsedOptions is set by save() by calling ParserOutput::getUsedOptions()
$usedOptions = $optionsKey->mUsedOptions;
wfDebug( "Parser cache options found.\n" );
} else {
- if ( !$useOutdated && !self::try116cache ) {
+ if ( !$useOutdated ) {
return false;
}
$usedOptions = ParserOptions::legacyOptions();
}
- return $this->getParserOutputKey( $article, $popts->optionsHash( $usedOptions, $article->getTitle() ) );
+ return $this->getParserOutputKey(
+ $article,
+ $popts->optionsHash( $usedOptions, $article->getTitle() )
+ );
}
/**
* Retrieve the ParserOutput from ParserCache.
* false if not found or outdated.
*
- * @param $article Article
- * @param $popts ParserOptions
- * @param $useOutdated Boolean (default false)
+ * @param Article $article
+ * @param ParserOptions $popts
+ * @param bool $useOutdated (default false)
*
* @return ParserOutput|bool False on failure
*/
@@ -186,12 +203,6 @@ class ParserCache {
}
$value = $this->mMemc->get( $parserOutputKey );
- if ( self::try116cache && !$value && strpos( $value, '*' ) !== -1 ) {
- wfDebug( "New format parser cache miss.\n" );
- $parserOutputKey = $this->getParserOutputKey( $article,
- $popts->optionsHash( ParserOptions::legacyOptions(), $article->getTitle() ) );
- $value = $this->mMemc->get( $parserOutputKey );
- }
if ( !$value ) {
wfDebug( "ParserOutput cache miss.\n" );
wfIncrStats( "pcache_miss_absent" );
@@ -209,7 +220,14 @@ class ParserCache {
if ( !$useOutdated && $value->expired( $touched ) ) {
wfIncrStats( "pcache_miss_expired" );
$cacheTime = $value->getCacheTime();
- wfDebug( "ParserOutput key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
+ wfDebug( "ParserOutput key expired, touched $touched, "
+ . "epoch $wgCacheEpoch, cached $cacheTime\n" );
+ $value = false;
+ } elseif ( $value->isDifferentRevision( $article->getLatest() ) ) {
+ wfIncrStats( "pcache_miss_revid" );
+ $revId = $article->getLatest();
+ $cachedRevId = $value->getCacheRevisionId();
+ wfDebug( "ParserOutput key is for an old revision, latest $revId, cached $cachedRevId\n" );
$value = false;
} else {
wfIncrStats( "pcache_hit" );
@@ -220,15 +238,20 @@ class ParserCache {
}
/**
- * @param $parserOutput ParserOutput
- * @param $article Article
- * @param $popts ParserOptions
- * @param $cacheTime Time when the cache was generated
+ * @param ParserOutput $parserOutput
+ * @param WikiPage $page
+ * @param ParserOptions $popts
+ * @param string $cacheTime Time when the cache was generated
+ * @param int $revId Revision ID that was parsed
*/
- public function save( $parserOutput, $article, $popts, $cacheTime = null ) {
+ public function save( $parserOutput, $page, $popts, $cacheTime = null, $revId = null ) {
$expire = $parserOutput->getCacheExpiry();
if ( $expire > 0 ) {
$cacheTime = $cacheTime ?: wfTimestampNow();
+ if ( !$revId ) {
+ $revision = $page->getRevision();
+ $revId = $revision ? $revision->getId() : null;
+ }
$optionsKey = new CacheTime;
$optionsKey->mUsedOptions = $parserOutput->getUsedOptions();
@@ -236,23 +259,30 @@ class ParserCache {
$optionsKey->setCacheTime( $cacheTime );
$parserOutput->setCacheTime( $cacheTime );
+ $optionsKey->setCacheRevisionId( $revId );
+ $parserOutput->setCacheRevisionId( $revId );
$optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
- $parserOutputKey = $this->getParserOutputKey( $article,
- $popts->optionsHash( $optionsKey->mUsedOptions, $article->getTitle() ) );
+ $parserOutputKey = $this->getParserOutputKey( $page,
+ $popts->optionsHash( $optionsKey->mUsedOptions, $page->getTitle() ) );
// Save the timestamp so that we don't have to load the revision row on view
- $parserOutput->setTimestamp( $article->getTimestamp() );
+ $parserOutput->setTimestamp( $page->getTimestamp() );
+
+ $msg = "Saved in parser cache with key $parserOutputKey" .
+ " and timestamp $cacheTime" .
+ " and revision id $revId" .
+ "\n";
- $parserOutput->mText .= "\n<!-- Saved in parser cache with key $parserOutputKey and timestamp $cacheTime\n -->\n";
- wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $cacheTime\n" );
+ $parserOutput->mText .= "\n<!-- $msg -->\n";
+ wfDebug( $msg );
// Save the parser output
$this->mMemc->set( $parserOutputKey, $parserOutput, $expire );
// ...and its pointer
- $this->mMemc->set( $this->getOptionsKey( $article ), $optionsKey, $expire );
+ $this->mMemc->set( $this->getOptionsKey( $page ), $optionsKey, $expire );
} else {
wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" );
}
diff --git a/includes/parser/Parser_DiffTest.php b/includes/parser/ParserDiffTest.php
index aeae234a..174c1d61 100644
--- a/includes/parser/Parser_DiffTest.php
+++ b/includes/parser/ParserDiffTest.php
@@ -24,21 +24,21 @@
/**
* @ingroup Parser
*/
-class Parser_DiffTest
+class ParserDiffTest
{
- var $parsers, $conf;
- var $shortOutput = false;
+ public $parsers;
+ public $conf;
+ public $shortOutput = false;
+ public $dtUniqPrefix;
- var $dtUniqPrefix;
-
- function __construct( $conf ) {
+ public function __construct( $conf ) {
if ( !isset( $conf['parsers'] ) ) {
throw new MWException( __METHOD__ . ': no parsers specified' );
}
$this->conf = $conf;
}
- function init() {
+ public function init() {
if ( !is_null( $this->parsers ) ) {
return;
}
@@ -64,7 +64,7 @@ class Parser_DiffTest
}
}
- function __call( $name, $args ) {
+ public function __call( $name, $args ) {
$this->init();
$results = array();
$mismatch = false;
@@ -98,7 +98,7 @@ class Parser_DiffTest
} else {
$diff = '[too many parsers]';
}
- $msg = "Parser_DiffTest: results mismatch on call to $name\n";
+ $msg = "ParserDiffTest: results mismatch on call to $name\n";
if ( !$this->shortOutput ) {
$msg .= 'Arguments: ' . $this->formatArray( $args ) . "\n";
}
@@ -109,7 +109,7 @@ class Parser_DiffTest
return $lastResult;
}
- function formatArray( $array ) {
+ public function formatArray( $array ) {
if ( $this->shortOutput ) {
foreach ( $array as $key => $value ) {
if ( $value instanceof ParserOutput ) {
@@ -120,7 +120,7 @@ class Parser_DiffTest
return var_export( $array, true );
}
- function setFunctionHook( $id, $callback, $flags = 0 ) {
+ public function setFunctionHook( $id, $callback, $flags = 0 ) {
$this->init();
foreach ( $this->parsers as $parser ) {
$parser->setFunctionHook( $id, $callback, $flags );
@@ -128,10 +128,10 @@ class Parser_DiffTest
}
/**
- * @param $parser Parser
+ * @param Parser $parser
* @return bool
*/
- function onClearState( &$parser ) {
+ public function onClearState( &$parser ) {
// hack marker prefixes to get identical output
if ( !isset( $this->dtUniqPrefix ) ) {
$this->dtUniqPrefix = $parser->uniqPrefix();
diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php
index e12f32d8..7e4059b8 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -22,9 +22,10 @@
*/
/**
- * \brief Set options of the Parser
+ * @brief Set options of the Parser
*
- * All member variables are supposed to be private in theory, although in practise this is not the case.
+ * All member variables are supposed to be private in theory, although in
+ * practise this is not the case.
*
* @ingroup Parser
*/
@@ -33,108 +34,108 @@ class ParserOptions {
/**
* Interlanguage links are removed and returned in an array
*/
- var $mInterwikiMagic;
+ public $mInterwikiMagic;
/**
* Allow external images inline?
*/
- var $mAllowExternalImages;
+ public $mAllowExternalImages;
/**
* If not, any exception?
*/
- var $mAllowExternalImagesFrom;
+ public $mAllowExternalImagesFrom;
/**
* If not or it doesn't match, should we check an on-wiki whitelist?
*/
- var $mEnableImageWhitelist;
+ public $mEnableImageWhitelist;
/**
* Date format index
*/
- var $mDateFormat = null;
+ public $mDateFormat = null;
/**
* Create "edit section" links?
*/
- var $mEditSection = true;
+ public $mEditSection = true;
/**
* Allow inclusion of special pages?
*/
- var $mAllowSpecialInclusion;
+ public $mAllowSpecialInclusion;
/**
* Use tidy to cleanup output HTML?
*/
- var $mTidy = false;
+ public $mTidy = false;
/**
* Which lang to call for PLURAL and GRAMMAR
*/
- var $mInterfaceMessage = false;
+ public $mInterfaceMessage = false;
/**
* Overrides $mInterfaceMessage with arbitrary language
*/
- var $mTargetLanguage = null;
+ public $mTargetLanguage = null;
/**
* Maximum size of template expansions, in bytes
*/
- var $mMaxIncludeSize;
+ public $mMaxIncludeSize;
/**
* Maximum number of nodes touched by PPFrame::expand()
*/
- var $mMaxPPNodeCount;
+ public $mMaxPPNodeCount;
/**
* Maximum number of nodes generated by Preprocessor::preprocessToObj()
*/
- var $mMaxGeneratedPPNodeCount;
+ public $mMaxGeneratedPPNodeCount;
/**
* Maximum recursion depth in PPFrame::expand()
*/
- var $mMaxPPExpandDepth;
+ public $mMaxPPExpandDepth;
/**
* Maximum recursion depth for templates within templates
*/
- var $mMaxTemplateDepth;
+ public $mMaxTemplateDepth;
/**
* Maximum number of calls per parse to expensive parser functions
*/
- var $mExpensiveParserFunctionLimit;
+ public $mExpensiveParserFunctionLimit;
/**
* Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
*/
- var $mRemoveComments = true;
+ public $mRemoveComments = true;
/**
* Callback for template fetching. Used as first argument to call_user_func().
*/
- var $mTemplateCallback =
+ public $mTemplateCallback =
array( 'Parser', 'statelessFetchTemplate' );
/**
* Enable limit report in an HTML comment on output
*/
- var $mEnableLimitReport = false;
+ public $mEnableLimitReport = false;
/**
* Timestamp used for {{CURRENTDAY}} etc.
*/
- var $mTimestamp;
+ public $mTimestamp;
/**
* Target attribute for external links
*/
- var $mExternalLinkTarget;
+ public $mExternalLinkTarget;
/**
* Clean up signature texts?
@@ -142,37 +143,32 @@ class ParserOptions {
* 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures
* 2) Substitute all transclusions
*/
- var $mCleanSignatures;
+ public $mCleanSignatures;
/**
* Transform wiki markup when saving the page?
*/
- var $mPreSaveTransform = true;
+ public $mPreSaveTransform = true;
/**
* Whether content conversion should be disabled
*/
- var $mDisableContentConversion;
+ public $mDisableContentConversion;
/**
* Whether title conversion should be disabled
*/
- var $mDisableTitleConversion;
+ public $mDisableTitleConversion;
/**
* Automatically number headings?
*/
- var $mNumberHeadings;
-
- /**
- * User math preference (as integer). Not used (1.19)
- */
- var $mMath;
+ public $mNumberHeadings;
/**
* Thumb size preferred by the user.
*/
- var $mThumbSize;
+ public $mThumbSize;
/**
* Maximum article size of an article to be marked as "stub"
@@ -182,90 +178,176 @@ class ParserOptions {
/**
* Language object of the User language.
*/
- var $mUserLang;
+ public $mUserLang;
/**
* @var User
* Stored user object
*/
- var $mUser;
+ public $mUser;
/**
* Parsing the page for a "preview" operation?
*/
- var $mIsPreview = false;
+ public $mIsPreview = false;
/**
* Parsing the page for a "preview" operation on a single section?
*/
- var $mIsSectionPreview = false;
+ public $mIsSectionPreview = false;
/**
* Parsing the printable version of the page?
*/
- var $mIsPrintable = false;
+ public $mIsPrintable = false;
/**
* Extra key that should be present in the caching key.
*/
- var $mExtraKey = '';
+ public $mExtraKey = '';
/**
* Function to be called when an option is accessed.
*/
protected $onAccessCallback = null;
- function getInterwikiMagic() { return $this->mInterwikiMagic; }
- function getAllowExternalImages() { return $this->mAllowExternalImages; }
- function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; }
- function getEnableImageWhitelist() { return $this->mEnableImageWhitelist; }
- function getEditSection() { return $this->mEditSection; }
- function getNumberHeadings() { $this->optionUsed( 'numberheadings' );
- return $this->mNumberHeadings; }
- function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; }
- function getTidy() { return $this->mTidy; }
- function getInterfaceMessage() { return $this->mInterfaceMessage; }
- function getTargetLanguage() { return $this->mTargetLanguage; }
- function getMaxIncludeSize() { return $this->mMaxIncludeSize; }
- function getMaxPPNodeCount() { return $this->mMaxPPNodeCount; }
- function getMaxGeneratedPPNodeCount() { return $this->mMaxGeneratedPPNodeCount; }
- function getMaxPPExpandDepth() { return $this->mMaxPPExpandDepth; }
- function getMaxTemplateDepth() { return $this->mMaxTemplateDepth; }
+ /**
+ * If the page being parsed is a redirect, this should hold the redirect
+ * target.
+ * @var Title|null
+ */
+ private $redirectTarget = null;
+
+ public function getInterwikiMagic() {
+ return $this->mInterwikiMagic;
+ }
+
+ public function getAllowExternalImages() {
+ return $this->mAllowExternalImages;
+ }
+
+ public function getAllowExternalImagesFrom() {
+ return $this->mAllowExternalImagesFrom;
+ }
+
+ public function getEnableImageWhitelist() {
+ return $this->mEnableImageWhitelist;
+ }
+
+ public function getEditSection() {
+ return $this->mEditSection;
+ }
+
+ public function getNumberHeadings() {
+ $this->optionUsed( 'numberheadings' );
+
+ return $this->mNumberHeadings;
+ }
+
+ public function getAllowSpecialInclusion() {
+ return $this->mAllowSpecialInclusion;
+ }
+
+ public function getTidy() {
+ return $this->mTidy;
+ }
+
+ public function getInterfaceMessage() {
+ return $this->mInterfaceMessage;
+ }
+
+ public function getTargetLanguage() {
+ return $this->mTargetLanguage;
+ }
+
+ public function getMaxIncludeSize() {
+ return $this->mMaxIncludeSize;
+ }
+
+ public function getMaxPPNodeCount() {
+ return $this->mMaxPPNodeCount;
+ }
+
+ public function getMaxGeneratedPPNodeCount() {
+ return $this->mMaxGeneratedPPNodeCount;
+ }
+
+ public function getMaxPPExpandDepth() {
+ return $this->mMaxPPExpandDepth;
+ }
+
+ public function getMaxTemplateDepth() {
+ return $this->mMaxTemplateDepth;
+ }
+
/* @since 1.20 */
- function getExpensiveParserFunctionLimit() { return $this->mExpensiveParserFunctionLimit; }
- function getRemoveComments() { return $this->mRemoveComments; }
- function getTemplateCallback() { return $this->mTemplateCallback; }
- function getEnableLimitReport() { return $this->mEnableLimitReport; }
- function getCleanSignatures() { return $this->mCleanSignatures; }
- function getExternalLinkTarget() { return $this->mExternalLinkTarget; }
- function getDisableContentConversion() { return $this->mDisableContentConversion; }
- function getDisableTitleConversion() { return $this->mDisableTitleConversion; }
- /** @deprecated since 1.22 use User::getOption('math') instead */
- function getMath() { $this->optionUsed( 'math' );
- return $this->mMath; }
- function getThumbSize() { $this->optionUsed( 'thumbsize' );
- return $this->mThumbSize; }
- function getStubThreshold() { $this->optionUsed( 'stubthreshold' );
- return $this->mStubThreshold; }
-
- function getIsPreview() { return $this->mIsPreview; }
- function getIsSectionPreview() { return $this->mIsSectionPreview; }
- function getIsPrintable() { $this->optionUsed( 'printable' );
- return $this->mIsPrintable; }
- function getUser() { return $this->mUser; }
- function getPreSaveTransform() { return $this->mPreSaveTransform; }
-
- /**
- * @param $title Title
- * @return Skin
- * @deprecated since 1.18 Use Linker::* instead
- */
- function getSkin( $title = null ) {
- wfDeprecated( __METHOD__, '1.18' );
- return new DummyLinker;
- }
-
- function getDateFormat() {
+ public function getExpensiveParserFunctionLimit() {
+ return $this->mExpensiveParserFunctionLimit;
+ }
+
+ public function getRemoveComments() {
+ return $this->mRemoveComments;
+ }
+
+ public function getTemplateCallback() {
+ return $this->mTemplateCallback;
+ }
+
+ public function getEnableLimitReport() {
+ return $this->mEnableLimitReport;
+ }
+
+ public function getCleanSignatures() {
+ return $this->mCleanSignatures;
+ }
+
+ public function getExternalLinkTarget() {
+ return $this->mExternalLinkTarget;
+ }
+
+ public function getDisableContentConversion() {
+ return $this->mDisableContentConversion;
+ }
+
+ public function getDisableTitleConversion() {
+ return $this->mDisableTitleConversion;
+ }
+
+ public function getThumbSize() {
+ $this->optionUsed( 'thumbsize' );
+
+ return $this->mThumbSize;
+ }
+
+ public function getStubThreshold() {
+ $this->optionUsed( 'stubthreshold' );
+
+ return $this->mStubThreshold;
+ }
+
+ public function getIsPreview() {
+ return $this->mIsPreview;
+ }
+
+ public function getIsSectionPreview() {
+ return $this->mIsSectionPreview;
+ }
+
+ public function getIsPrintable() {
+ $this->optionUsed( 'printable' );
+
+ return $this->mIsPrintable;
+ }
+
+ public function getUser() {
+ return $this->mUser;
+ }
+
+ public function getPreSaveTransform() {
+ return $this->mPreSaveTransform;
+ }
+
+ public function getDateFormat() {
$this->optionUsed( 'dateformat' );
if ( !isset( $this->mDateFormat ) ) {
$this->mDateFormat = $this->mUser->getDatePreference();
@@ -273,7 +355,7 @@ class ParserOptions {
return $this->mDateFormat;
}
- function getTimestamp() {
+ public function getTimestamp() {
if ( !isset( $this->mTimestamp ) ) {
$this->mTimestamp = wfTimestampNow();
}
@@ -293,10 +375,10 @@ class ParserOptions {
*
* {{int: }} uses this which used to produce inconsistent link tables (bug 14404).
*
- * @return Language object
+ * @return Language
* @since 1.19
*/
- function getUserLangObj() {
+ public function getUserLangObj() {
$this->optionUsed( 'userlang' );
return $this->mUserLang;
}
@@ -304,70 +386,180 @@ class ParserOptions {
/**
* Same as getUserLangObj() but returns a string instead.
*
- * @return String Language code
+ * @return string Language code
* @since 1.17
*/
- function getUserLang() {
+ public function getUserLang() {
return $this->getUserLangObj()->getCode();
}
- function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
- function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
- function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); }
- function setEnableImageWhitelist( $x ) { return wfSetVar( $this->mEnableImageWhitelist, $x ); }
- function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); }
- function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); }
- function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
- function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); }
- function setTidy( $x ) { return wfSetVar( $this->mTidy, $x ); }
-
- /** @deprecated in 1.19 */
- function setSkin( $x ) { wfDeprecated( __METHOD__, '1.19' ); }
- function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x ); }
- function setTargetLanguage( $x ) { return wfSetVar( $this->mTargetLanguage, $x, true ); }
- function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); }
- function setMaxPPNodeCount( $x ) { return wfSetVar( $this->mMaxPPNodeCount, $x ); }
- function setMaxGeneratedPPNodeCount( $x ) { return wfSetVar( $this->mMaxGeneratedPPNodeCount, $x ); }
- function setMaxTemplateDepth( $x ) { return wfSetVar( $this->mMaxTemplateDepth, $x ); }
+ public function setInterwikiMagic( $x ) {
+ return wfSetVar( $this->mInterwikiMagic, $x );
+ }
+
+ public function setAllowExternalImages( $x ) {
+ return wfSetVar( $this->mAllowExternalImages, $x );
+ }
+
+ public function setAllowExternalImagesFrom( $x ) {
+ return wfSetVar( $this->mAllowExternalImagesFrom, $x );
+ }
+
+ public function setEnableImageWhitelist( $x ) {
+ return wfSetVar( $this->mEnableImageWhitelist, $x );
+ }
+
+ public function setDateFormat( $x ) {
+ return wfSetVar( $this->mDateFormat, $x );
+ }
+
+ public function setEditSection( $x ) {
+ return wfSetVar( $this->mEditSection, $x );
+ }
+
+ public function setNumberHeadings( $x ) {
+ return wfSetVar( $this->mNumberHeadings, $x );
+ }
+
+ public function setAllowSpecialInclusion( $x ) {
+ return wfSetVar( $this->mAllowSpecialInclusion, $x );
+ }
+
+ public function setTidy( $x ) {
+ return wfSetVar( $this->mTidy, $x );
+ }
+
+ public function setInterfaceMessage( $x ) {
+ return wfSetVar( $this->mInterfaceMessage, $x );
+ }
+
+ public function setTargetLanguage( $x ) {
+ return wfSetVar( $this->mTargetLanguage, $x, true );
+ }
+
+ public function setMaxIncludeSize( $x ) {
+ return wfSetVar( $this->mMaxIncludeSize, $x );
+ }
+
+ public function setMaxPPNodeCount( $x ) {
+ return wfSetVar( $this->mMaxPPNodeCount, $x );
+ }
+
+ public function setMaxGeneratedPPNodeCount( $x ) {
+ return wfSetVar( $this->mMaxGeneratedPPNodeCount, $x );
+ }
+
+ public function setMaxTemplateDepth( $x ) {
+ return wfSetVar( $this->mMaxTemplateDepth, $x );
+ }
+
/* @since 1.20 */
- function setExpensiveParserFunctionLimit( $x ) { return wfSetVar( $this->mExpensiveParserFunctionLimit, $x ); }
- function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
- function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); }
- function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); }
- function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); }
- function setCleanSignatures( $x ) { return wfSetVar( $this->mCleanSignatures, $x ); }
- function setExternalLinkTarget( $x ) { return wfSetVar( $this->mExternalLinkTarget, $x ); }
- function disableContentConversion( $x = true ) { return wfSetVar( $this->mDisableContentConversion, $x ); }
- function disableTitleConversion( $x = true ) { return wfSetVar( $this->mDisableTitleConversion, $x ); }
- /** @deprecated since 1.22 */
- function setMath( $x ) { return wfSetVar( $this->mMath, $x ); }
- function setUserLang( $x ) {
+ public function setExpensiveParserFunctionLimit( $x ) {
+ return wfSetVar( $this->mExpensiveParserFunctionLimit, $x );
+ }
+
+ public function setRemoveComments( $x ) {
+ return wfSetVar( $this->mRemoveComments, $x );
+ }
+
+ public function setTemplateCallback( $x ) {
+ return wfSetVar( $this->mTemplateCallback, $x );
+ }
+
+ public function enableLimitReport( $x = true ) {
+ return wfSetVar( $this->mEnableLimitReport, $x );
+ }
+
+ public function setTimestamp( $x ) {
+ return wfSetVar( $this->mTimestamp, $x );
+ }
+
+ public function setCleanSignatures( $x ) {
+ return wfSetVar( $this->mCleanSignatures, $x );
+ }
+
+ public function setExternalLinkTarget( $x ) {
+ return wfSetVar( $this->mExternalLinkTarget, $x );
+ }
+
+ public function disableContentConversion( $x = true ) {
+ return wfSetVar( $this->mDisableContentConversion, $x );
+ }
+
+ public function disableTitleConversion( $x = true ) {
+ return wfSetVar( $this->mDisableTitleConversion, $x );
+ }
+
+ public function setUserLang( $x ) {
if ( is_string( $x ) ) {
$x = Language::factory( $x );
}
+
return wfSetVar( $this->mUserLang, $x );
}
- function setThumbSize( $x ) { return wfSetVar( $this->mThumbSize, $x ); }
- function setStubThreshold( $x ) { return wfSetVar( $this->mStubThreshold, $x ); }
- function setPreSaveTransform( $x ) { return wfSetVar( $this->mPreSaveTransform, $x ); }
- function setIsPreview( $x ) { return wfSetVar( $this->mIsPreview, $x ); }
- function setIsSectionPreview( $x ) { return wfSetVar( $this->mIsSectionPreview, $x ); }
- function setIsPrintable( $x ) { return wfSetVar( $this->mIsPrintable, $x ); }
+ public function setThumbSize( $x ) {
+ return wfSetVar( $this->mThumbSize, $x );
+ }
+
+ public function setStubThreshold( $x ) {
+ return wfSetVar( $this->mStubThreshold, $x );
+ }
+
+ public function setPreSaveTransform( $x ) {
+ return wfSetVar( $this->mPreSaveTransform, $x );
+ }
+
+ public function setIsPreview( $x ) {
+ return wfSetVar( $this->mIsPreview, $x );
+ }
+
+ public function setIsSectionPreview( $x ) {
+ return wfSetVar( $this->mIsSectionPreview, $x );
+ }
+
+ public function setIsPrintable( $x ) {
+ return wfSetVar( $this->mIsPrintable, $x );
+ }
+
+ /**
+ * Set the redirect target.
+ *
+ * Note that setting or changing this does not *make* the page a redirect
+ * or change its target, it merely records the information for reference
+ * during the parse.
+ *
+ * @since 1.24
+ * @param Title|null $title
+ */
+ function setRedirectTarget( $title ) {
+ $this->redirectTarget = $title;
+ }
+
+ /**
+ * Get the previously-set redirect target.
+ *
+ * @since 1.24
+ * @return Title|null
+ */
+ function getRedirectTarget() {
+ return $this->redirectTarget;
+ }
/**
* Extra key that should be present in the parser cache key.
+ * @param string $key
*/
- function addExtraKey( $key ) {
+ public function addExtraKey( $key ) {
$this->mExtraKey .= '!' . $key;
}
/**
* Constructor
- * @param $user User object
- * @param $lang Language object
+ * @param User $user
+ * @param Language $lang
*/
- function __construct( $user = null, $lang = null ) {
+ public function __construct( $user = null, $lang = null ) {
if ( $user === null ) {
global $wgUser;
if ( $wgUser === null ) {
@@ -390,8 +582,8 @@ class ParserOptions {
* Get a ParserOptions object from a given user.
* Language will be taken from $wgLang.
*
- * @param $user User object
- * @return ParserOptions object
+ * @param User $user
+ * @return ParserOptions
*/
public static function newFromUser( $user ) {
return new ParserOptions( $user );
@@ -400,9 +592,9 @@ class ParserOptions {
/**
* Get a ParserOptions object from a given user and language
*
- * @param $user User object
- * @param $lang Language object
- * @return ParserOptions object
+ * @param User $user
+ * @param Language $lang
+ * @return ParserOptions
*/
public static function newFromUserAndLang( User $user, Language $lang ) {
return new ParserOptions( $user, $lang );
@@ -411,8 +603,8 @@ class ParserOptions {
/**
* Get a ParserOptions object from a IContextSource object
*
- * @param $context IContextSource object
- * @return ParserOptions object
+ * @param IContextSource $context
+ * @return ParserOptions
*/
public static function newFromContext( IContextSource $context ) {
return new ParserOptions( $context->getUser(), $context->getLanguage() );
@@ -421,8 +613,8 @@ class ParserOptions {
/**
* Get user options
*
- * @param $user User object
- * @param $lang Language object
+ * @param User $user
+ * @param Language $lang
*/
private function initialiseFromUser( $user, $lang ) {
global $wgInterwikiMagic, $wgAllowExternalImages,
@@ -451,7 +643,6 @@ class ParserOptions {
$this->mUser = $user;
$this->mNumberHeadings = $user->getOption( 'numberheadings' );
- $this->mMath = $user->getOption( 'math' );
$this->mThumbSize = $user->getOption( 'thumbsize' );
$this->mStubThreshold = $user->getStubThreshold();
$this->mUserLang = $lang;
@@ -462,15 +653,17 @@ class ParserOptions {
/**
* Registers a callback for tracking which ParserOptions which are used.
* This is a private API with the parser.
+ * @param callable $callback
*/
- function registerWatcher( $callback ) {
+ public function registerWatcher( $callback ) {
$this->onAccessCallback = $callback;
}
/**
* Called when an option is accessed.
+ * @param string $optionName Name of the option
*/
- protected function optionUsed( $optionName ) {
+ public function optionUsed( $optionName ) {
if ( $this->onAccessCallback ) {
call_user_func( $this->onAccessCallback, $optionName );
}
@@ -483,37 +676,39 @@ class ParserOptions {
* @return array
*/
public static function legacyOptions() {
- return array( 'math', 'stubthreshold', 'numberheadings', 'userlang', 'thumbsize', 'editsection', 'printable' );
+ return array(
+ 'stubthreshold',
+ 'numberheadings',
+ 'userlang',
+ 'thumbsize',
+ 'editsection',
+ 'printable'
+ );
}
/**
* Generate a hash string with the values set on these ParserOptions
* for the keys given in the array.
* This will be used as part of the hash key for the parser cache,
- * so users sharign the options with vary for the same page share
+ * so users sharing the options with vary for the same page share
* the same cached data safely.
*
- * Replaces User::getPageRenderingHash()
- *
* Extensions which require it should install 'PageRenderingHash' hook,
* which will give them a chance to modify this key based on their own
* settings.
*
* @since 1.17
- * @param $forOptions Array
- * @param $title Title: used to get the content language of the page (since r97636)
+ * @param array $forOptions
+ * @param Title $title Used to get the content language of the page (since r97636)
* @return string Page rendering hash
*/
public function optionsHash( $forOptions, $title = null ) {
global $wgRenderHashAppend;
- $confstr = '';
-
- if ( in_array( 'math', $forOptions ) ) {
- $confstr .= $this->mMath;
- } else {
- $confstr .= '*';
- }
+ // FIXME: Once the cache key is reorganized this argument
+ // can be dropped. It was used when the math extension was
+ // part of core.
+ $confstr = '*';
// Space assigned for the stubthreshold but unused
// since it disables the parser cache, its value will always
@@ -573,7 +768,7 @@ class ParserOptions {
// Give a chance for extensions to modify the hash, if they have
// extra options or other effects on the parser cache.
- wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
+ wfRunHooks( 'PageRenderingHash', array( &$confstr, $this->getUser(), &$forOptions ) );
// Make it a valid memcached key fragment
$confstr = str_replace( ' ', '_', $confstr );
diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php
index 460f3211..5037ce18 100644
--- a/includes/parser/ParserOutput.php
+++ b/includes/parser/ParserOutput.php
@@ -22,7 +22,7 @@
* @ingroup Parser
*/
class ParserOutput extends CacheTime {
- var $mText, # The output text
+ public $mText, # The output text
$mLanguageLinks, # List of the full text of language links, in the order they appear
$mCategories, # Map of category names to sort keys
$mTitleText, # title text of the chosen language variant
@@ -41,6 +41,7 @@ class ParserOutput extends CacheTime {
$mModuleScripts = array(), # Modules of which only the JS will be loaded by the resource loader
$mModuleStyles = array(), # Modules of which only the CSSS will be loaded by the resource loader
$mModuleMessages = array(), # Modules of which only the messages will be loaded by the resource loader
+ $mJsConfigVars = array(), # JavaScript config variable for mw.config combined with this page
$mOutputHooks = array(), # Hook tags as per $wgParserOutputHooks
$mWarnings = array(), # Warning text to be returned to the user. Wikitext formatted, in the key only
$mSections = array(), # Table of contents
@@ -49,19 +50,20 @@ class ParserOutput extends CacheTime {
$mTOCHTML = '', # HTML of the TOC
$mTimestamp, # Timestamp of the revision
$mTOCEnabled = true; # Whether TOC should be shown, can't override __NOTOC__
- private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change.
- private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
- private $mSecondaryDataUpdates = array(); # List of DataUpdate, used to save info from the page somewhere else.
- private $mExtensionData = array(); # extra data used by extensions
- private $mLimitReportData = array(); # Parser limit report data
- private $mParseStartTime = array(); # Timestamps for getTimeSinceStart()
- private $mPreventClickjacking = false; # Whether to emit X-Frame-Options: DENY
-
- const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
-
- function __construct( $text = '', $languageLinks = array(), $categoryLinks = array(),
- $containsOldMagic = false, $titletext = '' )
- {
+ private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change.
+ private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
+ private $mSecondaryDataUpdates = array(); # List of DataUpdate, used to save info from the page somewhere else.
+ private $mExtensionData = array(); # extra data used by extensions
+ private $mLimitReportData = array(); # Parser limit report data
+ private $mParseStartTime = array(); # Timestamps for getTimeSinceStart()
+ private $mPreventClickjacking = false; # Whether to emit X-Frame-Options: DENY
+
+ const EDITSECTION_REGEX =
+ '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
+
+ public function __construct( $text = '', $languageLinks = array(), $categoryLinks = array(),
+ $containsOldMagic = false, $titletext = ''
+ ) {
$this->mText = $text;
$this->mLanguageLinks = $languageLinks;
$this->mCategories = $categoryLinks;
@@ -69,12 +71,31 @@ class ParserOutput extends CacheTime {
$this->mTitleText = $titletext;
}
- function getText() {
+ public function getText() {
wfProfileIn( __METHOD__ );
$text = $this->mText;
if ( $this->mEditSectionTokens ) {
- $text = preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
- array( &$this, 'replaceEditSectionLinksCallback' ), $text );
+ $text = preg_replace_callback(
+ ParserOutput::EDITSECTION_REGEX,
+ function ( $m ) {
+ global $wgOut, $wgLang;
+ $editsectionPage = Title::newFromText( htmlspecialchars_decode( $m[1] ) );
+ $editsectionSection = htmlspecialchars_decode( $m[2] );
+ $editsectionContent = isset( $m[4] ) ? $m[3] : null;
+
+ if ( !is_object( $editsectionPage ) ) {
+ throw new MWException( "Bad parser output text." );
+ }
+
+ $skin = $wgOut->getSkin();
+ return call_user_func_array(
+ array( $skin, 'doEditSectionLink' ),
+ array( $editsectionPage, $editsectionSection,
+ $editsectionContent, $wgLang->getCode() )
+ );
+ },
+ $text
+ );
} else {
$text = preg_replace( ParserOutput::EDITSECTION_REGEX, '', $text );
}
@@ -84,7 +105,7 @@ class ParserOutput extends CacheTime {
$text = str_replace( array( Parser::TOC_START, Parser::TOC_END ), '', $text );
} else {
$text = preg_replace(
- '#'. preg_quote( Parser::TOC_START ) . '.*?' . preg_quote( Parser::TOC_END ) . '#s',
+ '#' . preg_quote( Parser::TOC_START ) . '.*?' . preg_quote( Parser::TOC_END ) . '#s',
'',
$text
);
@@ -93,97 +114,192 @@ class ParserOutput extends CacheTime {
return $text;
}
- /**
- * callback used by getText to replace editsection tokens
- * @private
- * @param $m
- * @throws MWException
- * @return mixed
- */
- function replaceEditSectionLinksCallback( $m ) {
- global $wgOut, $wgLang;
- $args = array(
- htmlspecialchars_decode( $m[1] ),
- htmlspecialchars_decode( $m[2] ),
- isset( $m[4] ) ? $m[3] : null,
- );
- $args[0] = Title::newFromText( $args[0] );
- if ( !is_object( $args[0] ) ) {
- throw new MWException( "Bad parser output text." );
- }
- $args[] = $wgLang->getCode();
- $skin = $wgOut->getSkin();
- return call_user_func_array( array( $skin, 'doEditSectionLink' ), $args );
- }
-
- function &getLanguageLinks() { return $this->mLanguageLinks; }
- function getInterwikiLinks() { return $this->mInterwikiLinks; }
- function getCategoryLinks() { return array_keys( $this->mCategories ); }
- function &getCategories() { return $this->mCategories; }
- function getTitleText() { return $this->mTitleText; }
- function getSections() { return $this->mSections; }
- function getEditSectionTokens() { return $this->mEditSectionTokens; }
- function &getLinks() { return $this->mLinks; }
- function &getTemplates() { return $this->mTemplates; }
- function &getTemplateIds() { return $this->mTemplateIds; }
- function &getImages() { return $this->mImages; }
- function &getFileSearchOptions() { return $this->mFileSearchOptions; }
- function &getExternalLinks() { return $this->mExternalLinks; }
- function getNoGallery() { return $this->mNoGallery; }
- function getHeadItems() { return $this->mHeadItems; }
- function getModules() { return $this->mModules; }
- function getModuleScripts() { return $this->mModuleScripts; }
- function getModuleStyles() { return $this->mModuleStyles; }
- function getModuleMessages() { return $this->mModuleMessages; }
- function getOutputHooks() { return (array)$this->mOutputHooks; }
- function getWarnings() { return array_keys( $this->mWarnings ); }
- function getIndexPolicy() { return $this->mIndexPolicy; }
- function getTOCHTML() { return $this->mTOCHTML; }
- function getTimestamp() { return $this->mTimestamp; }
- function getLimitReportData() { return $this->mLimitReportData; }
- function getTOCEnabled() { return $this->mTOCEnabled; }
-
- function setText( $text ) { return wfSetVar( $this->mText, $text ); }
- function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
- function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); }
-
- function setTitleText( $t ) { return wfSetVar( $this->mTitleText, $t ); }
- function setSections( $toc ) { return wfSetVar( $this->mSections, $toc ); }
- function setEditSectionTokens( $t ) { return wfSetVar( $this->mEditSectionTokens, $t ); }
- function setIndexPolicy( $policy ) { return wfSetVar( $this->mIndexPolicy, $policy ); }
- function setTOCHTML( $tochtml ) { return wfSetVar( $this->mTOCHTML, $tochtml ); }
- function setTimestamp( $timestamp ) { return wfSetVar( $this->mTimestamp, $timestamp ); }
- function setTOCEnabled( $flag ) { return wfSetVar( $this->mTOCEnabled, $flag ); }
-
- function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; }
- function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; }
- function addWarning( $s ) { $this->mWarnings[$s] = 1; }
-
- function addOutputHook( $hook, $data = false ) {
+ public function &getLanguageLinks() {
+ return $this->mLanguageLinks;
+ }
+
+ public function getInterwikiLinks() {
+ return $this->mInterwikiLinks;
+ }
+
+ public function getCategoryLinks() {
+ return array_keys( $this->mCategories );
+ }
+
+ public function &getCategories() {
+ return $this->mCategories;
+ }
+
+ public function getTitleText() {
+ return $this->mTitleText;
+ }
+
+ public function getSections() {
+ return $this->mSections;
+ }
+
+ public function getEditSectionTokens() {
+ return $this->mEditSectionTokens;
+ }
+
+ public function &getLinks() {
+ return $this->mLinks;
+ }
+
+ public function &getTemplates() {
+ return $this->mTemplates;
+ }
+
+ public function &getTemplateIds() {
+ return $this->mTemplateIds;
+ }
+
+ public function &getImages() {
+ return $this->mImages;
+ }
+
+ public function &getFileSearchOptions() {
+ return $this->mFileSearchOptions;
+ }
+
+ public function &getExternalLinks() {
+ return $this->mExternalLinks;
+ }
+
+ public function getNoGallery() {
+ return $this->mNoGallery;
+ }
+
+ public function getHeadItems() {
+ return $this->mHeadItems;
+ }
+
+ public function getModules() {
+ return $this->mModules;
+ }
+
+ public function getModuleScripts() {
+ return $this->mModuleScripts;
+ }
+
+ public function getModuleStyles() {
+ return $this->mModuleStyles;
+ }
+
+ public function getModuleMessages() {
+ return $this->mModuleMessages;
+ }
+
+ /** @since 1.23 */
+ public function getJsConfigVars() {
+ return $this->mJsConfigVars;
+ }
+
+ public function getOutputHooks() {
+ return (array)$this->mOutputHooks;
+ }
+
+ public function getWarnings() {
+ return array_keys( $this->mWarnings );
+ }
+
+ public function getIndexPolicy() {
+ return $this->mIndexPolicy;
+ }
+
+ public function getTOCHTML() {
+ return $this->mTOCHTML;
+ }
+
+ public function getTimestamp() {
+ return $this->mTimestamp;
+ }
+
+ public function getLimitReportData() {
+ return $this->mLimitReportData;
+ }
+
+ public function getTOCEnabled() {
+ return $this->mTOCEnabled;
+ }
+
+ public function setText( $text ) {
+ return wfSetVar( $this->mText, $text );
+ }
+
+ public function setLanguageLinks( $ll ) {
+ return wfSetVar( $this->mLanguageLinks, $ll );
+ }
+
+ public function setCategoryLinks( $cl ) {
+ return wfSetVar( $this->mCategories, $cl );
+ }
+
+ public function setTitleText( $t ) {
+ return wfSetVar( $this->mTitleText, $t );
+ }
+
+ public function setSections( $toc ) {
+ return wfSetVar( $this->mSections, $toc );
+ }
+
+ public function setEditSectionTokens( $t ) {
+ return wfSetVar( $this->mEditSectionTokens, $t );
+ }
+
+ public function setIndexPolicy( $policy ) {
+ return wfSetVar( $this->mIndexPolicy, $policy );
+ }
+
+ public function setTOCHTML( $tochtml ) {
+ return wfSetVar( $this->mTOCHTML, $tochtml );
+ }
+
+ public function setTimestamp( $timestamp ) {
+ return wfSetVar( $this->mTimestamp, $timestamp );
+ }
+
+ public function setTOCEnabled( $flag ) {
+ return wfSetVar( $this->mTOCEnabled, $flag );
+ }
+
+ public function addCategory( $c, $sort ) {
+ $this->mCategories[$c] = $sort;
+ }
+
+ public function addLanguageLink( $t ) {
+ $this->mLanguageLinks[] = $t;
+ }
+
+ public function addWarning( $s ) {
+ $this->mWarnings[$s] = 1;
+ }
+
+ public function addOutputHook( $hook, $data = false ) {
$this->mOutputHooks[] = array( $hook, $data );
}
- function setNewSection( $value ) {
+ public function setNewSection( $value ) {
$this->mNewSection = (bool)$value;
}
- function hideNewSection( $value ) {
+ public function hideNewSection( $value ) {
$this->mHideNewSection = (bool)$value;
}
- function getHideNewSection() {
+ public function getHideNewSection() {
return (bool)$this->mHideNewSection;
}
- function getNewSection() {
+ public function getNewSection() {
return (bool)$this->mNewSection;
}
/**
* Checks, if a url is pointing to the own server
*
- * @param string $internal the server to check against
- * @param string $url the url to check
+ * @param string $internal The server to check against
+ * @param string $url The url to check
* @return bool
*/
- static function isLinkInternal( $internal, $url ) {
+ public static function isLinkInternal( $internal, $url ) {
return (bool)preg_match( '/^' .
# If server is proto relative, check also for http/https links
( substr( $internal, 0, 2 ) === '//' ? '(?:https?:)?' : '' ) .
@@ -194,7 +310,7 @@ class ParserOutput extends CacheTime {
);
}
- function addExternalLink( $url ) {
+ public function addExternalLink( $url ) {
# We don't register links pointing to our own server, unless... :-)
global $wgServer, $wgRegisterInternalExternals;
@@ -210,10 +326,10 @@ class ParserOutput extends CacheTime {
/**
* Record a local or interwiki inline link for saving in future link tables.
*
- * @param $title Title object
- * @param $id Mixed: optional known page_id so we can skip the lookup
+ * @param Title $title
+ * @param int|null $id Optional known page_id so we can skip the lookup
*/
- function addLink( Title $title, $id = null ) {
+ public function addLink( Title $title, $id = null ) {
if ( $title->isExternal() ) {
// Don't record interwikis in pagelinks
$this->addInterwikiLink( $title );
@@ -245,10 +361,10 @@ class ParserOutput extends CacheTime {
* Register a file dependency for this output
* @param string $name Title dbKey
* @param string $timestamp MW timestamp of file creation (or false if non-existing)
- * @param string $sha1 base 36 SHA-1 of file (or false if non-existing)
+ * @param string $sha1 Base 36 SHA-1 of file (or false if non-existing)
* @return void
*/
- function addImage( $name, $timestamp = null, $sha1 = null ) {
+ public function addImage( $name, $timestamp = null, $sha1 = null ) {
$this->mImages[$name] = 1;
if ( $timestamp !== null && $sha1 !== null ) {
$this->mFileSearchOptions[$name] = array( 'time' => $timestamp, 'sha1' => $sha1 );
@@ -257,12 +373,12 @@ class ParserOutput extends CacheTime {
/**
* Register a template dependency for this output
- * @param $title Title
- * @param $page_id
- * @param $rev_id
+ * @param Title $title
+ * @param int $page_id
+ * @param int $rev_id
* @return void
*/
- function addTemplate( $title, $page_id, $rev_id ) {
+ public function addTemplate( $title, $page_id, $rev_id ) {
$ns = $title->getNamespace();
$dbk = $title->getDBkey();
if ( !isset( $this->mTemplates[$ns] ) ) {
@@ -276,14 +392,14 @@ class ParserOutput extends CacheTime {
}
/**
- * @param $title Title object, must be an interwiki link
- * @throws MWException if given invalid input
+ * @param Title $title Title object, must be an interwiki link
+ * @throws MWException If given invalid input
*/
- function addInterwikiLink( $title ) {
- $prefix = $title->getInterwiki();
- if ( $prefix == '' ) {
+ public function addInterwikiLink( $title ) {
+ if ( !$title->isExternal() ) {
throw new MWException( 'Non-interwiki link passed, internal parser error.' );
}
+ $prefix = $title->getInterwiki();
if ( !isset( $this->mInterwikiLinks[$prefix] ) ) {
$this->mInterwikiLinks[$prefix] = array();
}
@@ -294,8 +410,10 @@ class ParserOutput extends CacheTime {
* Add some text to the "<head>".
* If $tag is set, the section with that tag will only be included once
* in a given page.
+ * @param string $section
+ * @param string|bool $tag
*/
- function addHeadItem( $section, $tag = false ) {
+ public function addHeadItem( $section, $tag = false ) {
if ( $tag !== false ) {
$this->mHeadItems[$tag] = $section;
} else {
@@ -320,15 +438,34 @@ class ParserOutput extends CacheTime {
}
/**
+ * Add one or more variables to be set in mw.config in JavaScript.
+ *
+ * @param string|array $keys Key or array of key/value pairs.
+ * @param mixed $value [optional] Value of the configuration variable.
+ * @since 1.23
+ */
+ public function addJsConfigVars( $keys, $value = null ) {
+ if ( is_array( $keys ) ) {
+ foreach ( $keys as $key => $value ) {
+ $this->mJsConfigVars[$key] = $value;
+ }
+ return;
+ }
+
+ $this->mJsConfigVars[$keys] = $value;
+ }
+
+ /**
* Copy items from the OutputPage object into this one
*
- * @param $out OutputPage object
+ * @param OutputPage $out
*/
public function addOutputPageMetadata( OutputPage $out ) {
$this->addModules( $out->getModules() );
$this->addModuleScripts( $out->getModuleScripts() );
$this->addModuleStyles( $out->getModuleStyles() );
$this->addModuleMessages( $out->getModuleMessages() );
+ $this->addJsConfigVars( $out->getJsConfigVars() );
$this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
$this->mPreventClickjacking = $this->mPreventClickjacking || $out->getPreventClickjacking();
@@ -339,7 +476,7 @@ class ParserOutput extends CacheTime {
* -- this is assumed to have been validated
* (check equal normalisation, etc.)
*
- * @param string $text desired title text
+ * @param string $text Desired title text
*/
public function setDisplayTitle( $text ) {
$this->setTitleText( $text );
@@ -349,7 +486,7 @@ class ParserOutput extends CacheTime {
/**
* Get the title to be used for display
*
- * @return String
+ * @return string
*/
public function getDisplayTitle() {
$t = $this->getTitleText();
@@ -361,6 +498,7 @@ class ParserOutput extends CacheTime {
/**
* Fairly generic flag setter thingy.
+ * @param string $flag
*/
public function setFlag( $flag ) {
$this->mFlags[$flag] = true;
@@ -378,6 +516,9 @@ class ParserOutput extends CacheTime {
* retrieved given the page ID or via a DB join when given the page
* title.
*
+ * Since 1.23, page_props are also indexed by numeric value, to allow
+ * for efficient "top k" queries of pages wrt a given property.
+ *
* setProperty() is thus used to propagate properties from the parsed
* page to request contexts other than a page view of the currently parsed
* article.
@@ -395,10 +536,10 @@ class ParserOutput extends CacheTime {
* Wikimedia Commons.
* This is not actually implemented, yet but would be pretty cool.
*
- * @note: Do not use setProperty() to set a property which is only used
+ * @note Do not use setProperty() to set a property which is only used
* in a context where the ParserOutput object itself is already available,
* for example a normal page view. There is no need to save such a property
- * in the database since it the text is already parsed. You can just hook
+ * in the database since the text is already parsed. You can just hook
* OutputPageParserOutput and get your data out of the ParserOutput object.
*
* If you are writing an extension where you want to set a property in the
@@ -431,10 +572,22 @@ class ParserOutput extends CacheTime {
$this->mProperties[$name] = $value;
}
+ /**
+ * @param string $name The property name to look up.
+ *
+ * @return mixed|bool The value previously set using setProperty(). False if null or no value
+ * was set for the given property name.
+ *
+ * @note You need to use getProperties() to check for boolean and null properties.
+ */
public function getProperty( $name ) {
return isset( $this->mProperties[$name] ) ? $this->mProperties[$name] : false;
}
+ public function unsetProperty( $name ) {
+ unset( $this->mProperties[$name] );
+ }
+
public function getProperties() {
if ( !isset( $this->mProperties ) ) {
$this->mProperties = array();
@@ -445,7 +598,7 @@ class ParserOutput extends CacheTime {
/**
* Returns the options from its ParserOptions which have been taken
* into account to produce this output or false if not available.
- * @return mixed Array
+ * @return array
*/
public function getUsedOptions() {
if ( !isset( $this->mAccessedOptions ) ) {
@@ -455,16 +608,24 @@ class ParserOutput extends CacheTime {
}
/**
- * Callback passed by the Parser to the ParserOptions to keep track of which options are used.
- * @access private
+ * Tags a parser option for use in the cache key for this parser output.
+ * Registered as a watcher at ParserOptions::registerWatcher() by Parser::clearState().
+ *
+ * @see ParserCache::getKey
+ * @see ParserCache::save
+ * @see ParserOptions::addExtraKey
+ * @see ParserOptions::optionsHash
+ * @param string $option
*/
- function recordOption( $option ) {
+ public function recordOption( $option ) {
$this->mAccessedOptions[$option] = true;
}
/**
- * Adds an update job to the output. Any update jobs added to the output will eventually bexecuted in order to
- * store any secondary information extracted from the page's content.
+ * Adds an update job to the output. Any update jobs added to the output will
+ * eventually be executed in order to store any secondary information extracted
+ * from the page's content. This is triggered by calling getSecondaryDataUpdates()
+ * and is used for forward links updates on edit and backlink updates by jobs.
*
* @since 1.20
*
@@ -479,16 +640,16 @@ class ParserOutput extends CacheTime {
* extracted from the page's content, including a LinksUpdate object for all links stored in
* this ParserOutput object.
*
- * @note: Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates() instead! The content
- * handler may provide additional update objects.
+ * @note Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates()
+ * instead! The content handler may provide additional update objects.
*
* @since 1.20
*
- * @param $title Title The title of the page we're updating. If not given, a title object will be created
- * based on $this->getTitleText()
- * @param $recursive Boolean: queue jobs for recursive updates?
+ * @param Title $title The title of the page we're updating. If not given, a title object will
+ * be created based on $this->getTitleText()
+ * @param bool $recursive Queue jobs for recursive updates?
*
- * @return Array. An array of instances of DataUpdate
+ * @return array An array of instances of DataUpdate
*/
public function getSecondaryDataUpdates( Title $title = null, $recursive = true ) {
if ( is_null( $title ) ) {
@@ -535,11 +696,10 @@ class ParserOutput extends CacheTime {
* @since 1.21
*
* @param string $key The key for accessing the data. Extensions should take care to avoid
- * conflicts in naming keys. It is suggested to use the extension's name as a
- * prefix.
+ * conflicts in naming keys. It is suggested to use the extension's name as a prefix.
*
* @param mixed $value The value to set. Setting a value to null is equivalent to removing
- * the value.
+ * the value.
*/
public function setExtensionData( $key, $value ) {
if ( $value === null ) {
@@ -557,7 +717,7 @@ class ParserOutput extends CacheTime {
*
* @param string $key The key to look up.
*
- * @return mixed The value previously set for the given key using setExtensionData( $key ),
+ * @return mixed|null The value previously set for the given key using setExtensionData()
* or null if no value was set for this key.
*/
public function getExtensionData( $key ) {
@@ -573,10 +733,12 @@ class ParserOutput extends CacheTime {
if ( !$clock || $clock === 'wall' ) {
$ret['wall'] = microtime( true );
}
- if ( ( !$clock || $clock === 'cpu' ) && function_exists( 'getrusage' ) ) {
- $ru = getrusage();
- $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
- $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+ if ( !$clock || $clock === 'cpu' ) {
+ $ru = wfGetRusage();
+ if ( $ru ) {
+ $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
+ $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+ }
}
return $ret;
}
@@ -585,7 +747,7 @@ class ParserOutput extends CacheTime {
* Resets the parse start timestamps for future calls to getTimeSinceStart()
* @since 1.22
*/
- function resetParseStartTime() {
+ public function resetParseStartTime() {
$this->mParseStartTime = self::getTimes();
}
@@ -600,7 +762,7 @@ class ParserOutput extends CacheTime {
* @param string $clock
* @return float|null
*/
- function getTimeSinceStart( $clock ) {
+ public function getTimeSinceStart( $clock ) {
if ( !isset( $this->mParseStartTime[$clock] ) ) {
return null;
}
@@ -628,7 +790,7 @@ class ParserOutput extends CacheTime {
* @param string $key Message key
* @param mixed $value Appropriate for Message::params()
*/
- function setLimitReportData( $key, $value ) {
+ public function setLimitReportData( $key, $value ) {
$this->mLimitReportData[$key] = $value;
}
@@ -636,10 +798,21 @@ class ParserOutput extends CacheTime {
* Get or set the prevent-clickjacking flag
*
* @since 1.24
- * @param boolean|null $flag New flag value, or null to leave it unchanged
- * @return boolean Old flag value
+ * @param bool|null $flag New flag value, or null to leave it unchanged
+ * @return bool Old flag value
*/
public function preventClickjacking( $flag = null ) {
return wfSetVar( $this->mPreventClickjacking, $flag );
}
+
+ /**
+ * Save space for for serialization by removing useless values
+ * @return array
+ */
+ public function __sleep() {
+ return array_diff(
+ array_keys( get_object_vars( $this ) ),
+ array( 'mSecondaryDataUpdates', 'mParseStartTime' )
+ );
+ }
}
diff --git a/includes/parser/Preprocessor.php b/includes/parser/Preprocessor.php
index aeacd2e1..b32593c9 100644
--- a/includes/parser/Preprocessor.php
+++ b/includes/parser/Preprocessor.php
@@ -28,42 +28,44 @@ interface Preprocessor {
/**
* Create a new preprocessor object based on an initialised Parser object
*
- * @param $parser Parser
+ * @param Parser $parser
*/
- function __construct( $parser );
+ public function __construct( $parser );
/**
* Create a new top-level frame for expansion of a page
*
* @return PPFrame
*/
- function newFrame();
+ public function newFrame();
/**
- * Create a new custom frame for programmatic use of parameter replacement as used in some extensions
+ * Create a new custom frame for programmatic use of parameter replacement
+ * as used in some extensions.
*
- * @param $args array
+ * @param array $args
*
* @return PPFrame
*/
- function newCustomFrame( $args );
+ public function newCustomFrame( $args );
/**
- * Create a new custom node for programmatic use of parameter replacement as used in some extensions
+ * Create a new custom node for programmatic use of parameter replacement
+ * as used in some extensions.
*
- * @param $values
+ * @param array $values
*/
- function newPartNodeArray( $values );
+ public function newPartNodeArray( $values );
/**
* Preprocess text to a PPNode
*
- * @param $text
- * @param $flags
+ * @param string $text
+ * @param int $flags
*
* @return PPNode
*/
- function preprocessToObj( $text, $flags = 0 );
+ public function preprocessToObj( $text, $flags = 0 );
}
/**
@@ -75,8 +77,9 @@ interface PPFrame {
const STRIP_COMMENTS = 4;
const NO_IGNORE = 8;
const RECOVER_COMMENTS = 16;
+ const NO_TAGS = 32;
- const RECOVER_ORIG = 27; // = 1|2|8|16 no constant expression support in PHP yet
+ const RECOVER_ORIG = 59; // = 1|2|8|16|32 no constant expression support in PHP yet
/** This constant exists when $indexOffset is supported in newChild() */
const SUPPORTS_INDEX_OFFSET = 1;
@@ -84,87 +87,168 @@ interface PPFrame {
/**
* Create a child frame
*
- * @param array $args
- * @param Title $title
+ * @param array|bool $args
+ * @param bool|Title $title
* @param int $indexOffset A number subtracted from the index attributes of the arguments
*
* @return PPFrame
*/
- function newChild( $args = false, $title = false, $indexOffset = 0 );
+ public function newChild( $args = false, $title = false, $indexOffset = 0 );
+
+ /**
+ * Expand a document tree node, caching the result on its parent with the given key
+ * @param string|int $key
+ * @param string|PPNode $root
+ * @param int $flags
+ * @return string
+ */
+ public function cachedExpand( $key, $root, $flags = 0 );
/**
* Expand a document tree node
+ * @param string|PPNode $root
+ * @param int $flags
+ * @return string
*/
- function expand( $root, $flags = 0 );
+ public function expand( $root, $flags = 0 );
/**
* Implode with flags for expand()
+ * @param string $sep
+ * @param int $flags
+ * @param string|PPNode $args,...
+ * @return string
*/
- function implodeWithFlags( $sep, $flags /*, ... */ );
+ public function implodeWithFlags( $sep, $flags /*, ... */ );
/**
* Implode with no flags specified
+ * @param string $sep
+ * @param string|PPNode $args,...
+ * @return string
*/
- function implode( $sep /*, ... */ );
+ public function implode( $sep /*, ... */ );
/**
* Makes an object that, when expand()ed, will be the same as one obtained
* with implode()
+ * @param string $sep
+ * @param string|PPNode $args,...
+ * @return PPNode
*/
- function virtualImplode( $sep /*, ... */ );
+ public function virtualImplode( $sep /*, ... */ );
/**
* Virtual implode with brackets
+ * @param string $start
+ * @param string $sep
+ * @param string $end
+ * @param string|PPNode $args,...
+ * @return PPNode
*/
- function virtualBracketedImplode( $start, $sep, $end /*, ... */ );
+ public function virtualBracketedImplode( $start, $sep, $end /*, ... */ );
/**
* Returns true if there are no arguments in this frame
*
* @return bool
*/
- function isEmpty();
+ public function isEmpty();
/**
* Returns all arguments of this frame
+ * @return array
*/
- function getArguments();
+ public function getArguments();
/**
* Returns all numbered arguments of this frame
+ * @return array
*/
- function getNumberedArguments();
+ public function getNumberedArguments();
/**
* Returns all named arguments of this frame
+ * @return array
*/
- function getNamedArguments();
+ public function getNamedArguments();
/**
* Get an argument to this frame by name
+ * @param string $name
+ * @return bool
*/
- function getArgument( $name );
+ public function getArgument( $name );
/**
* Returns true if the infinite loop check is OK, false if a loop is detected
*
- * @param $title
- *
+ * @param Title $title
* @return bool
*/
- function loopCheck( $title );
+ public function loopCheck( $title );
/**
* Return true if the frame is a template frame
+ * @return bool
*/
- function isTemplate();
+ public function isTemplate();
+
+ /**
+ * Set the "volatile" flag.
+ *
+ * Note that this is somewhat of a "hack" in order to make extensions
+ * with side effects (such as Cite) work with the PHP parser. New
+ * extensions should be written in a way that they do not need this
+ * function, because other parsers (such as Parsoid) are not guaranteed
+ * to respect it, and it may be removed in the future.
+ *
+ * @param bool $flag
+ */
+ public function setVolatile( $flag = true );
+
+ /**
+ * Get the "volatile" flag.
+ *
+ * Callers should avoid caching the result of an expansion if it has the
+ * volatile flag set.
+ *
+ * @see self::setVolatile()
+ * @return bool
+ */
+ public function isVolatile();
+
+ /**
+ * Get the TTL of the frame's output.
+ *
+ * This is the maximum amount of time, in seconds, that this frame's
+ * output should be cached for. A value of null indicates that no
+ * maximum has been specified.
+ *
+ * Note that this TTL only applies to caching frames as parts of pages.
+ * It is not relevant to caching the entire rendered output of a page.
+ *
+ * @return int|null
+ */
+ public function getTTL();
+
+ /**
+ * Set the TTL of the output of this frame and all of its ancestors.
+ * Has no effect if the new TTL is greater than the one already set.
+ * Note that it is the caller's responsibility to change the cache
+ * expiry of the page as a whole, if such behavior is desired.
+ *
+ * @see self::getTTL()
+ * @param int $ttl
+ */
+ public function setTTL( $ttl );
/**
* Get a title of frame
*
* @return Title
*/
- function getTitle();
+ public function getTitle();
}
/**
@@ -184,36 +268,42 @@ interface PPNode {
/**
* Get an array-type node containing the children of this node.
* Returns false if this is not a tree node.
+ * @return PPNode
*/
- function getChildren();
+ public function getChildren();
/**
* Get the first child of a tree node. False if there isn't one.
*
* @return PPNode
*/
- function getFirstChild();
+ public function getFirstChild();
/**
* Get the next sibling of any node. False if there isn't one
+ * @return PPNode
*/
- function getNextSibling();
+ public function getNextSibling();
/**
* Get all children of this tree node which have a given name.
* Returns an array-type node, or false if this is not a tree node.
+ * @param string $type
+ * @return bool|PPNode
*/
- function getChildrenOfType( $type );
+ public function getChildrenOfType( $type );
/**
* Returns the length of the array, or false if this is not an array-type node
*/
- function getLength();
+ public function getLength();
/**
* Returns an item of an array-type node
+ * @param int $i
+ * @return bool|PPNode
*/
- function item( $i );
+ public function item( $i );
/**
* Get the name of this node. The following names are defined here:
@@ -226,25 +316,29 @@ interface PPNode {
* #nodelist An array-type node
*
* The subclass may define various other names for tree and leaf nodes.
+ * @return string
*/
- function getName();
+ public function getName();
/**
* Split a "<part>" node into an associative array containing:
* name PPNode name
* index String index
* value PPNode value
+ * @return array
*/
- function splitArg();
+ public function splitArg();
/**
* Split an "<ext>" node into an associative array containing name, attr, inner and close
* All values in the resulting array are PPNodes. Inner and close are optional.
+ * @return array
*/
- function splitExt();
+ public function splitExt();
/**
* Split an "<h>" node
+ * @return array
*/
- function splitHeading();
+ public function splitHeading();
}
diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php
index 3138f483..2edb79a2 100644
--- a/includes/parser/Preprocessor_DOM.php
+++ b/includes/parser/Preprocessor_DOM.php
@@ -23,19 +23,21 @@
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class Preprocessor_DOM implements Preprocessor {
+ // @codingStandardsIgnoreEnd
/**
* @var Parser
*/
- var $parser;
+ public $parser;
- var $memoryLimit;
+ public $memoryLimit;
const CACHE_VERSION = 1;
- function __construct( $parser ) {
+ public function __construct( $parser ) {
$this->parser = $parser;
$mem = ini_get( 'memory_limit' );
$this->memoryLimit = false;
@@ -51,40 +53,57 @@ class Preprocessor_DOM implements Preprocessor {
/**
* @return PPFrame_DOM
*/
- function newFrame() {
+ public function newFrame() {
return new PPFrame_DOM( $this );
}
/**
- * @param $args array
+ * @param array $args
* @return PPCustomFrame_DOM
*/
- function newCustomFrame( $args ) {
+ public function newCustomFrame( $args ) {
return new PPCustomFrame_DOM( $this, $args );
}
/**
- * @param $values
+ * @param array $values
* @return PPNode_DOM
*/
- function newPartNodeArray( $values ) {
+ public function newPartNodeArray( $values ) {
//NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais)
$xml = "<list>";
foreach ( $values as $k => $val ) {
if ( is_int( $k ) ) {
- $xml .= "<part><name index=\"$k\"/><value>" . htmlspecialchars( $val ) . "</value></part>";
+ $xml .= "<part><name index=\"$k\"/><value>"
+ . htmlspecialchars( $val ) . "</value></part>";
} else {
- $xml .= "<part><name>" . htmlspecialchars( $k ) . "</name>=<value>" . htmlspecialchars( $val ) . "</value></part>";
+ $xml .= "<part><name>" . htmlspecialchars( $k )
+ . "</name>=<value>" . htmlspecialchars( $val ) . "</value></part>";
}
}
$xml .= "</list>";
+ wfProfileIn( __METHOD__ . '-loadXML' );
$dom = new DOMDocument();
- $dom->loadXML( $xml );
- $root = $dom->documentElement;
+ wfSuppressWarnings();
+ $result = $dom->loadXML( $xml );
+ wfRestoreWarnings();
+ if ( !$result ) {
+ // Try running the XML through UtfNormal to get rid of invalid characters
+ $xml = UtfNormal::cleanUp( $xml );
+ // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2
+ // don't barf when the XML is >256 levels deep
+ $result = $dom->loadXML( $xml, 1 << 19 );
+ }
+ wfProfileOut( __METHOD__ . '-loadXML' );
+ if ( !$result ) {
+ throw new MWException( 'Parameters passed to ' . __METHOD__ . ' result in invalid XML' );
+ }
+
+ $root = $dom->documentElement;
$node = new PPNode_DOM( $root->childNodes );
return $node;
}
@@ -93,7 +112,7 @@ class Preprocessor_DOM implements Preprocessor {
* @throws MWException
* @return bool
*/
- function memCheck() {
+ public function memCheck() {
if ( $this->memoryLimit === false ) {
return true;
}
@@ -109,10 +128,11 @@ class Preprocessor_DOM implements Preprocessor {
* Preprocess some wikitext and return the document tree.
* This is the ghost of Parser::replace_variables().
*
- * @param string $text the text to parse
- * @param $flags Integer: bitwise combination of:
- * Parser::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" as if the text is being
- * included. Default is to assume a direct page view.
+ * @param string $text The text to parse
+ * @param int $flags Bitwise combination of:
+ * Parser::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>"
+ * as if the text is being included. Default
+ * is to assume a direct page view.
*
* The generated DOM tree must depend only on the input text and the flags.
* The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
@@ -128,7 +148,7 @@ class Preprocessor_DOM implements Preprocessor {
* @throws MWException
* @return PPNode_DOM
*/
- function preprocessToObj( $text, $flags = 0 ) {
+ public function preprocessToObj( $text, $flags = 0 ) {
wfProfileIn( __METHOD__ );
global $wgMemc, $wgPreprocessorCacheThreshold;
@@ -160,7 +180,6 @@ class Preprocessor_DOM implements Preprocessor {
$xml = $this->preprocessToXml( $text, $flags );
}
-
// Fail if the number of elements exceeds acceptable limits
// Do not attempt to generate the DOM
$this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' );
@@ -181,7 +200,8 @@ class Preprocessor_DOM implements Preprocessor {
if ( !$result ) {
// Try running the XML through UtfNormal to get rid of invalid characters
$xml = UtfNormal::cleanUp( $xml );
- // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 don't barf when the XML is >256 levels deep
+ // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2
+ // don't barf when the XML is >256 levels deep.
$result = $dom->loadXML( $xml, 1 << 19 );
}
if ( $result ) {
@@ -202,11 +222,11 @@ class Preprocessor_DOM implements Preprocessor {
}
/**
- * @param $text string
- * @param $flags int
+ * @param string $text
+ * @param int $flags
* @return string
*/
- function preprocessToXml( $text, $flags = 0 ) {
+ public function preprocessToXml( $text, $flags = 0 ) {
wfProfileIn( __METHOD__ );
$rules = array(
'{' => array(
@@ -234,7 +254,9 @@ class Preprocessor_DOM implements Preprocessor {
$ignoredTags = array( 'includeonly', '/includeonly' );
$ignoredElements = array( 'noinclude' );
$xmlishElements[] = 'noinclude';
- if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) {
+ if ( strpos( $text, '<onlyinclude>' ) !== false
+ && strpos( $text, '</onlyinclude>' ) !== false
+ ) {
$enableOnlyinclude = true;
}
} else {
@@ -250,19 +272,28 @@ class Preprocessor_DOM implements Preprocessor {
$stack = new PPDStack;
$searchBase = "[{<\n"; #}
- $revText = strrev( $text ); // For fast reverse searches
+ // For fast reverse searches
+ $revText = strrev( $text );
$lengthText = strlen( $text );
- $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
- $accum =& $stack->getAccum(); # Current accumulator
+ // Input pointer, starts out pointing to a pseudo-newline before the start
+ $i = 0;
+ // Current accumulator
+ $accum =& $stack->getAccum();
$accum = '<root>';
- $findEquals = false; # True to find equals signs in arguments
- $findPipe = false; # True to take notice of pipe characters
+ // True to find equals signs in arguments
+ $findEquals = false;
+ // True to take notice of pipe characters
+ $findPipe = false;
$headingIndex = 1;
- $inHeading = false; # True if $i is inside a possible heading
- $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i
- $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude>
- $fakeLineStart = true; # Do a line-start run without outputting an LF character
+ // True if $i is inside a possible heading
+ $inHeading = false;
+ // True if there are no more greater-than (>) signs right of $i
+ $noMoreGT = false;
+ // True to ignore all input up to the next <onlyinclude>
+ $findOnlyinclude = $enableOnlyinclude;
+ // Do a line-start run without outputting an LF character
+ $fakeLineStart = true;
while ( true ) {
//$this->memCheck();
@@ -347,7 +378,9 @@ class Preprocessor_DOM implements Preprocessor {
if ( $found == 'angle' ) {
$matches = false;
// Handle </onlyinclude>
- if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) {
+ if ( $enableOnlyinclude
+ && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
+ ) {
$findOnlyinclude = true;
continue;
}
@@ -400,14 +433,14 @@ class Preprocessor_DOM implements Preprocessor {
// the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
// it's a possible beneficial b/c break.
if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
- && substr( $text, $wsEnd + 1, 1 ) == "\n" )
- {
+ && substr( $text, $wsEnd + 1, 1 ) == "\n"
+ ) {
// Remove leading whitespace from the end of the accumulator
// Sanity check first though
$wsLength = $i - $wsStart;
if ( $wsLength > 0
- && strspn( $accum, " \t", -$wsLength ) === $wsLength )
- {
+ && strspn( $accum, " \t", -$wsLength ) === $wsLength
+ ) {
$accum = substr( $accum, 0, -$wsLength );
}
@@ -461,7 +494,9 @@ class Preprocessor_DOM implements Preprocessor {
// Handle ignored tags
if ( in_array( $lowerName, $ignoredTags ) ) {
- $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) ) . '</ignore>';
+ $accum .= '<ignore>'
+ . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) )
+ . '</ignore>';
$i = $tagEndPos + 1;
continue;
}
@@ -476,8 +511,8 @@ class Preprocessor_DOM implements Preprocessor {
$attrEnd = $tagEndPos;
// Find closing tag
if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
- $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
- {
+ $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
+ ) {
$inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
$i = $matches[0][1] + strlen( $matches[0][0] );
$close = '<close>' . htmlspecialchars( $matches[0][0] ) . '</close>';
@@ -521,9 +556,11 @@ class Preprocessor_DOM implements Preprocessor {
$count = strspn( $text, '=', $i, 6 );
if ( $count == 1 && $findEquals ) {
- // DWIM: This looks kind of like a name/value separator
- // Let's let the equals handler have it and break the potential heading
- // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex.
+ // DWIM: This looks kind of like a name/value separator.
+ // Let's let the equals handler have it and break the
+ // potential heading. This is heuristic, but AFAICT the
+ // methods for completely correct disambiguation are very
+ // complex.
} elseif ( $count > 0 ) {
$piece = array(
'open' => "\n",
@@ -542,8 +579,9 @@ class Preprocessor_DOM implements Preprocessor {
// A heading must be open, otherwise \n wouldn't have been in the search list
assert( '$piece->open == "\n"' );
$part = $piece->getCurrentPart();
- // Search back through the input to see if it has a proper close
- // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient
+ // Search back through the input to see if it has a proper close.
+ // Do this using the reversed string since the other solutions
+ // (end anchor, etc.) are inefficient.
$wsLength = strspn( $revText, " \t", $lengthText - $i );
$searchStart = $i - $wsLength;
if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
@@ -737,18 +775,18 @@ class Preprocessor_DOM implements Preprocessor {
* @ingroup Parser
*/
class PPDStack {
- var $stack, $rootAccum;
+ public $stack, $rootAccum;
/**
* @var PPDStack
*/
- var $top;
- var $out;
- var $elementClass = 'PPDStackElement';
+ public $top;
+ public $out;
+ public $elementClass = 'PPDStackElement';
- static $false = false;
+ public static $false = false;
- function __construct() {
+ public function __construct() {
$this->stack = array();
$this->top = false;
$this->rootAccum = '';
@@ -758,15 +796,15 @@ class PPDStack {
/**
* @return int
*/
- function count() {
+ public function count() {
return count( $this->stack );
}
- function &getAccum() {
+ public function &getAccum() {
return $this->accum;
}
- function getCurrentPart() {
+ public function getCurrentPart() {
if ( $this->top === false ) {
return false;
} else {
@@ -774,7 +812,7 @@ class PPDStack {
}
}
- function push( $data ) {
+ public function push( $data ) {
if ( $data instanceof $this->elementClass ) {
$this->stack[] = $data;
} else {
@@ -785,7 +823,7 @@ class PPDStack {
$this->accum =& $this->top->getAccum();
}
- function pop() {
+ public function pop() {
if ( !count( $this->stack ) ) {
throw new MWException( __METHOD__ . ': no elements remaining' );
}
@@ -801,7 +839,7 @@ class PPDStack {
return $temp;
}
- function addPart( $s = '' ) {
+ public function addPart( $s = '' ) {
$this->top->addPart( $s );
$this->accum =& $this->top->getAccum();
}
@@ -809,7 +847,7 @@ class PPDStack {
/**
* @return array
*/
- function getFlags() {
+ public function getFlags() {
if ( !count( $this->stack ) ) {
return array(
'findEquals' => false,
@@ -826,15 +864,15 @@ class PPDStack {
* @ingroup Parser
*/
class PPDStackElement {
- var $open, // Opening character (\n for heading)
+ public $open, // Opening character (\n for heading)
$close, // Matching closing character
$count, // Number of opening characters found (number of "=" for heading)
$parts, // Array of PPDPart objects describing pipe-separated parts.
$lineStart; // True if the open char appeared at the start of the input line. Not set for headings.
- var $partClass = 'PPDPart';
+ public $partClass = 'PPDPart';
- function __construct( $data = array() ) {
+ public function __construct( $data = array() ) {
$class = $this->partClass;
$this->parts = array( new $class );
@@ -843,23 +881,23 @@ class PPDStackElement {
}
}
- function &getAccum() {
+ public function &getAccum() {
return $this->parts[count( $this->parts ) - 1]->out;
}
- function addPart( $s = '' ) {
+ public function addPart( $s = '' ) {
$class = $this->partClass;
$this->parts[] = new $class( $s );
}
- function getCurrentPart() {
+ public function getCurrentPart() {
return $this->parts[count( $this->parts ) - 1];
}
/**
* @return array
*/
- function getFlags() {
+ public function getFlags() {
$partCount = count( $this->parts );
$findPipe = $this->open != "\n" && $this->open != '[';
return array(
@@ -872,9 +910,10 @@ class PPDStackElement {
/**
* Get the output string that would result if the close is not found.
*
+ * @param bool|int $openingCount
* @return string
*/
- function breakSyntax( $openingCount = false ) {
+ public function breakSyntax( $openingCount = false ) {
if ( $this->open == "\n" ) {
$s = $this->parts[0]->out;
} else {
@@ -900,14 +939,14 @@ class PPDStackElement {
* @ingroup Parser
*/
class PPDPart {
- var $out; // Output accumulator string
+ public $out; // Output accumulator string
// Optional member variables:
// eqpos Position of equals sign in output accumulator
// commentEnd Past-the-end input pointer for the last comment encountered
// visualEnd Past-the-end input pointer for the end of the accumulator minus comments
- function __construct( $out = '' ) {
+ public function __construct( $out = '' ) {
$this->out = $out;
}
}
@@ -915,57 +954,71 @@ class PPDPart {
/**
* An expansion frame, used as a context to expand the result of preprocessToObj()
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPFrame_DOM implements PPFrame {
+ // @codingStandardsIgnoreEnd
/**
* @var Preprocessor
*/
- var $preprocessor;
+ public $preprocessor;
/**
* @var Parser
*/
- var $parser;
+ public $parser;
/**
* @var Title
*/
- var $title;
- var $titleCache;
+ public $title;
+ public $titleCache;
/**
* Hashtable listing templates which are disallowed for expansion in this frame,
* having been encountered previously in parent frames.
*/
- var $loopCheckHash;
+ public $loopCheckHash;
/**
* Recursion depth of this frame, top = 0
* Note that this is NOT the same as expansion depth in expand()
*/
- var $depth;
+ public $depth;
+
+ private $volatile = false;
+ private $ttl = null;
+
+ /**
+ * @var array
+ */
+ protected $childExpansionCache;
/**
* Construct a new preprocessor frame.
- * @param $preprocessor Preprocessor The parent preprocessor
+ * @param Preprocessor $preprocessor The parent preprocessor
*/
- function __construct( $preprocessor ) {
+ public function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
$this->parser = $preprocessor->parser;
$this->title = $this->parser->mTitle;
$this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
$this->loopCheckHash = array();
$this->depth = 0;
+ $this->childExpansionCache = array();
}
/**
* Create a new child frame
* $args is optionally a multi-root PPNode or array containing the template arguments
*
+ * @param bool|array $args
+ * @param Title|bool $title
+ * @param int $indexOffset
* @return PPTemplateFrame_DOM
*/
- function newChild( $args = false, $title = false, $indexOffset = 0 ) {
+ public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
$namedArgs = array();
$numberedArgs = array();
if ( $title === false ) {
@@ -980,7 +1033,7 @@ class PPFrame_DOM implements PPFrame {
if ( $arg instanceof PPNode ) {
$arg = $arg->node;
}
- if ( !$xpath ) {
+ if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
$xpath = new DOMXPath( $arg->ownerDocument );
}
@@ -1005,11 +1058,23 @@ class PPFrame_DOM implements PPFrame {
/**
* @throws MWException
- * @param $root
- * @param $flags int
+ * @param string|int $key
+ * @param string|PPNode_DOM|DOMDocument $root
+ * @param int $flags
+ * @return string
+ */
+ public function cachedExpand( $key, $root, $flags = 0 ) {
+ // we don't have a parent, so we don't have a cache
+ return $this->expand( $root, $flags );
+ }
+
+ /**
+ * @throws MWException
+ * @param string|PPNode_DOM|DOMDocument $root
+ * @param int $flags
* @return string
*/
- function expand( $root, $flags = 0 ) {
+ public function expand( $root, $flags = 0 ) {
static $expansionDepth = 0;
if ( is_string( $root ) ) {
return $root;
@@ -1142,17 +1207,16 @@ class PPFrame_DOM implements PPFrame {
# Remove it in HTML, pre+remove and STRIP_COMMENTS modes
if ( $this->parser->ot['html']
|| ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
- || ( $flags & PPFrame::STRIP_COMMENTS ) )
- {
+ || ( $flags & PPFrame::STRIP_COMMENTS )
+ ) {
$out .= '';
- }
- # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
- # Not in RECOVER_COMMENTS mode (extractSections) though
- elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
+ } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
+ # Add a strip marker in PST mode so that pstPass2() can
+ # run some old-fashioned regexes on the result.
+ # Not in RECOVER_COMMENTS mode (extractSections) though.
$out .= $this->parser->insertStripItem( $contextNode->textContent );
- }
- # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
- else {
+ } else {
+ # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
$out .= $contextNode->textContent;
}
} elseif ( $contextNode->nodeName == 'ignore' ) {
@@ -1160,7 +1224,9 @@ class PPFrame_DOM implements PPFrame {
# OT_WIKI will only respect <ignore> in substed templates.
# The other output types respect it unless NO_IGNORE is set.
# extractSections() sets NO_IGNORE and so never respects it.
- if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) {
+ if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
+ || ( $flags & PPFrame::NO_IGNORE )
+ ) {
$out .= $contextNode->textContent;
} else {
$out .= '';
@@ -1172,13 +1238,29 @@ class PPFrame_DOM implements PPFrame {
$attrs = $xpath->query( 'attr', $contextNode );
$inners = $xpath->query( 'inner', $contextNode );
$closes = $xpath->query( 'close', $contextNode );
- $params = array(
- 'name' => new PPNode_DOM( $names->item( 0 ) ),
- 'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null,
- 'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null,
- 'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null,
- );
- $out .= $this->parser->extensionSubstitution( $params, $this );
+ if ( $flags & PPFrame::NO_TAGS ) {
+ $s = '<' . $this->expand( $names->item( 0 ), $flags );
+ if ( $attrs->length > 0 ) {
+ $s .= $this->expand( $attrs->item( 0 ), $flags );
+ }
+ if ( $inners->length > 0 ) {
+ $s .= '>' . $this->expand( $inners->item( 0 ), $flags );
+ if ( $closes->length > 0 ) {
+ $s .= $this->expand( $closes->item( 0 ), $flags );
+ }
+ } else {
+ $s .= '/>';
+ }
+ $out .= $s;
+ } else {
+ $params = array(
+ 'name' => new PPNode_DOM( $names->item( 0 ) ),
+ 'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null,
+ 'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null,
+ 'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null,
+ );
+ $out .= $this->parser->extensionSubstitution( $params, $this );
+ }
} elseif ( $contextNode->nodeName == 'h' ) {
# Heading
$s = $this->expand( $contextNode->childNodes, $flags );
@@ -1231,11 +1313,12 @@ class PPFrame_DOM implements PPFrame {
}
/**
- * @param $sep
- * @param $flags
+ * @param string $sep
+ * @param int $flags
+ * @param string|PPNode_DOM|DOMDocument $args,...
* @return string
*/
- function implodeWithFlags( $sep, $flags /*, ... */ ) {
+ public function implodeWithFlags( $sep, $flags /*, ... */ ) {
$args = array_slice( func_get_args(), 2 );
$first = true;
@@ -1263,9 +1346,11 @@ class PPFrame_DOM 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_DOM|DOMDocument $args,...
* @return string
*/
- function implode( $sep /*, ... */ ) {
+ public function implode( $sep /*, ... */ ) {
$args = array_slice( func_get_args(), 1 );
$first = true;
@@ -1293,9 +1378,11 @@ class PPFrame_DOM implements PPFrame {
* Makes an object that, when expand()ed, will be the same as one obtained
* with implode()
*
+ * @param string $sep
+ * @param string|PPNode_DOM|DOMDocument $args,...
* @return array
*/
- function virtualImplode( $sep /*, ... */ ) {
+ public function virtualImplode( $sep /*, ... */ ) {
$args = array_slice( func_get_args(), 1 );
$out = array();
$first = true;
@@ -1321,9 +1408,13 @@ class PPFrame_DOM implements PPFrame {
/**
* Virtual implode with brackets
+ * @param string $start
+ * @param string $sep
+ * @param string $end
+ * @param string|PPNode_DOM|DOMDocument $args,...
* @return array
*/
- function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
+ public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
$args = array_slice( func_get_args(), 3 );
$out = array( $start );
$first = true;
@@ -1348,11 +1439,11 @@ class PPFrame_DOM implements PPFrame {
return $out;
}
- function __toString() {
+ public function __toString() {
return 'frame{}';
}
- function getPDBK( $level = false ) {
+ public function getPDBK( $level = false ) {
if ( $level === false ) {
return $this->title->getPrefixedDBkey();
} else {
@@ -1363,21 +1454,21 @@ class PPFrame_DOM implements PPFrame {
/**
* @return array
*/
- function getArguments() {
+ public function getArguments() {
return array();
}
/**
* @return array
*/
- function getNumberedArguments() {
+ public function getNumberedArguments() {
return array();
}
/**
* @return array
*/
- function getNamedArguments() {
+ public function getNamedArguments() {
return array();
}
@@ -1386,20 +1477,21 @@ class PPFrame_DOM implements PPFrame {
*
* @return bool
*/
- function isEmpty() {
+ public function isEmpty() {
return true;
}
- function getArgument( $name ) {
+ public function getArgument( $name ) {
return false;
}
/**
* Returns true if the infinite loop check is OK, false if a loop is detected
*
+ * @param Title $title
* @return bool
*/
- function loopCheck( $title ) {
+ public function loopCheck( $title ) {
return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
}
@@ -1408,7 +1500,7 @@ class PPFrame_DOM implements PPFrame {
*
* @return bool
*/
- function isTemplate() {
+ public function isTemplate() {
return false;
}
@@ -1417,32 +1509,75 @@ class PPFrame_DOM implements PPFrame {
*
* @return Title
*/
- function getTitle() {
+ public function getTitle() {
return $this->title;
}
+
+ /**
+ * Set the volatile flag
+ *
+ * @param bool $flag
+ */
+ public function setVolatile( $flag = true ) {
+ $this->volatile = $flag;
+ }
+
+ /**
+ * Get the volatile flag
+ *
+ * @return bool
+ */
+ public function isVolatile() {
+ return $this->volatile;
+ }
+
+ /**
+ * Set the TTL
+ *
+ * @param int $ttl
+ */
+ public function setTTL( $ttl ) {
+ if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
+ $this->ttl = $ttl;
+ }
+ }
+
+ /**
+ * Get the TTL
+ *
+ * @return int|null
+ */
+ public function getTTL() {
+ return $this->ttl;
+ }
}
/**
* Expansion frame with template arguments
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPTemplateFrame_DOM extends PPFrame_DOM {
- var $numberedArgs, $namedArgs;
+ // @codingStandardsIgnoreEnd
+
+ public $numberedArgs, $namedArgs;
/**
* @var PPFrame_DOM
*/
- var $parent;
- var $numberedExpansionCache, $namedExpansionCache;
+ public $parent;
+ public $numberedExpansionCache, $namedExpansionCache;
/**
- * @param $preprocessor
- * @param $parent PPFrame_DOM
- * @param $numberedArgs array
- * @param $namedArgs array
- * @param $title Title
+ * @param Preprocessor $preprocessor
+ * @param bool|PPFrame_DOM $parent
+ * @param array $numberedArgs
+ * @param array $namedArgs
+ * @param bool|Title $title
*/
- function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
+ public function __construct( $preprocessor, $parent = false, $numberedArgs = array(),
+ $namedArgs = array(), $title = false
+ ) {
parent::__construct( $preprocessor );
$this->parent = $parent;
@@ -1460,7 +1595,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
$this->numberedExpansionCache = $this->namedExpansionCache = array();
}
- function __toString() {
+ public function __toString() {
$s = 'tplframe{';
$first = true;
$args = $this->numberedArgs + $this->namedArgs;
@@ -1478,15 +1613,33 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
}
/**
+ * @throws MWException
+ * @param string|int $key
+ * @param string|PPNode_DOM|DOMDocument $root
+ * @param int $flags
+ * @return string
+ */
+ public function cachedExpand( $key, $root, $flags = 0 ) {
+ if ( isset( $this->parent->childExpansionCache[$key] ) ) {
+ return $this->parent->childExpansionCache[$key];
+ }
+ $retval = $this->expand( $root, $flags );
+ if ( !$this->isVolatile() ) {
+ $this->parent->childExpansionCache[$key] = $retval;
+ }
+ return $retval;
+ }
+
+ /**
* Returns true if there are no arguments in this frame
*
* @return bool
*/
- function isEmpty() {
+ public function isEmpty() {
return !count( $this->numberedArgs ) && !count( $this->namedArgs );
}
- function getArguments() {
+ public function getArguments() {
$arguments = array();
foreach ( array_merge(
array_keys( $this->numberedArgs ),
@@ -1496,7 +1649,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
return $arguments;
}
- function getNumberedArguments() {
+ public function getNumberedArguments() {
$arguments = array();
foreach ( array_keys( $this->numberedArgs ) as $key ) {
$arguments[$key] = $this->getArgument( $key );
@@ -1504,7 +1657,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
return $arguments;
}
- function getNamedArguments() {
+ public function getNamedArguments() {
$arguments = array();
foreach ( array_keys( $this->namedArgs ) as $key ) {
$arguments[$key] = $this->getArgument( $key );
@@ -1512,18 +1665,21 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
return $arguments;
}
- function getNumberedArgument( $index ) {
+ public function getNumberedArgument( $index ) {
if ( !isset( $this->numberedArgs[$index] ) ) {
return false;
}
if ( !isset( $this->numberedExpansionCache[$index] ) ) {
# No trimming for unnamed arguments
- $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS );
+ $this->numberedExpansionCache[$index] = $this->parent->expand(
+ $this->numberedArgs[$index],
+ PPFrame::STRIP_COMMENTS
+ );
}
return $this->numberedExpansionCache[$index];
}
- function getNamedArgument( $name ) {
+ public function getNamedArgument( $name ) {
if ( !isset( $this->namedArgs[$name] ) ) {
return false;
}
@@ -1535,7 +1691,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
return $this->namedExpansionCache[$name];
}
- function getArgument( $name ) {
+ public function getArgument( $name ) {
$text = $this->getNumberedArgument( $name );
if ( $text === false ) {
$text = $this->getNamedArgument( $name );
@@ -1548,24 +1704,37 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
*
* @return bool
*/
- function isTemplate() {
+ public function isTemplate() {
return true;
}
+
+ public function setVolatile( $flag = true ) {
+ parent::setVolatile( $flag );
+ $this->parent->setVolatile( $flag );
+ }
+
+ public function setTTL( $ttl ) {
+ parent::setTTL( $ttl );
+ $this->parent->setTTL( $ttl );
+ }
}
/**
* Expansion frame with custom arguments
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPCustomFrame_DOM extends PPFrame_DOM {
- var $args;
+ // @codingStandardsIgnoreEnd
+
+ public $args;
- function __construct( $preprocessor, $args ) {
+ public function __construct( $preprocessor, $args ) {
parent::__construct( $preprocessor );
$this->args = $args;
}
- function __toString() {
+ public function __toString() {
$s = 'cstmframe{';
$first = true;
foreach ( $this->args as $name => $value ) {
@@ -1584,48 +1753,50 @@ class PPCustomFrame_DOM extends PPFrame_DOM {
/**
* @return bool
*/
- function isEmpty() {
+ public function isEmpty() {
return !count( $this->args );
}
- function getArgument( $index ) {
+ public function getArgument( $index ) {
if ( !isset( $this->args[$index] ) ) {
return false;
}
return $this->args[$index];
}
- function getArguments() {
+ public function getArguments() {
return $this->args;
}
}
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPNode_DOM implements PPNode {
+ // @codingStandardsIgnoreEnd
/**
* @var DOMElement
*/
- var $node;
- var $xpath;
+ public $node;
+ public $xpath;
- function __construct( $node, $xpath = false ) {
+ public function __construct( $node, $xpath = false ) {
$this->node = $node;
}
/**
* @return DOMXPath
*/
- function getXPath() {
+ public function getXPath() {
if ( $this->xpath === null ) {
$this->xpath = new DOMXPath( $this->node->ownerDocument );
}
return $this->xpath;
}
- function __toString() {
+ public function __toString() {
if ( $this->node instanceof DOMNodeList ) {
$s = '';
foreach ( $this->node as $node ) {
@@ -1640,37 +1811,37 @@ class PPNode_DOM implements PPNode {
/**
* @return bool|PPNode_DOM
*/
- function getChildren() {
+ public function getChildren() {
return $this->node->childNodes ? new self( $this->node->childNodes ) : false;
}
/**
* @return bool|PPNode_DOM
*/
- function getFirstChild() {
+ public function getFirstChild() {
return $this->node->firstChild ? new self( $this->node->firstChild ) : false;
}
/**
* @return bool|PPNode_DOM
*/
- function getNextSibling() {
+ public function getNextSibling() {
return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false;
}
/**
- * @param $type
+ * @param string $type
*
* @return bool|PPNode_DOM
*/
- function getChildrenOfType( $type ) {
+ public function getChildrenOfType( $type ) {
return new self( $this->getXPath()->query( $type, $this->node ) );
}
/**
* @return int
*/
- function getLength() {
+ public function getLength() {
if ( $this->node instanceof DOMNodeList ) {
return $this->node->length;
} else {
@@ -1679,10 +1850,10 @@ class PPNode_DOM implements PPNode {
}
/**
- * @param $i
+ * @param int $i
* @return bool|PPNode_DOM
*/
- function item( $i ) {
+ public function item( $i ) {
$item = $this->node->item( $i );
return $item ? new self( $item ) : false;
}
@@ -1690,7 +1861,7 @@ class PPNode_DOM implements PPNode {
/**
* @return string
*/
- function getName() {
+ public function getName() {
if ( $this->node instanceof DOMNodeList ) {
return '#nodelist';
} else {
@@ -1707,7 +1878,7 @@ class PPNode_DOM implements PPNode {
* @throws MWException
* @return array
*/
- function splitArg() {
+ public function splitArg() {
$xpath = $this->getXPath();
$names = $xpath->query( 'name', $this->node );
$values = $xpath->query( 'value', $this->node );
@@ -1729,7 +1900,7 @@ class PPNode_DOM implements PPNode {
* @throws MWException
* @return array
*/
- function splitExt() {
+ public function splitExt() {
$xpath = $this->getXPath();
$names = $xpath->query( 'name', $this->node );
$attrs = $xpath->query( 'attr', $this->node );
@@ -1755,7 +1926,7 @@ class PPNode_DOM implements PPNode {
* @throws MWException
* @return array
*/
- function splitHeading() {
+ public function splitHeading() {
if ( $this->getName() !== 'h' ) {
throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
}
diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php
index 2fc5e118..63763967 100644
--- a/includes/parser/Preprocessor_Hash.php
+++ b/includes/parser/Preprocessor_Hash.php
@@ -26,39 +26,42 @@
* * attribute nodes are children
* * "<h>" nodes that aren't at the top are replaced with <possible-h>
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class Preprocessor_Hash implements Preprocessor {
+ // @codingStandardsIgnoreEnd
+
/**
* @var Parser
*/
- var $parser;
+ public $parser;
const CACHE_VERSION = 1;
- function __construct( $parser ) {
+ public function __construct( $parser ) {
$this->parser = $parser;
}
/**
* @return PPFrame_Hash
*/
- function newFrame() {
+ public function newFrame() {
return new PPFrame_Hash( $this );
}
/**
- * @param $args array
+ * @param array $args
* @return PPCustomFrame_Hash
*/
- function newCustomFrame( $args ) {
+ public function newCustomFrame( $args ) {
return new PPCustomFrame_Hash( $this, $args );
}
/**
- * @param $values array
+ * @param array $values
* @return PPNode_Hash_Array
*/
- function newPartNodeArray( $values ) {
+ public function newPartNodeArray( $values ) {
$list = array();
foreach ( $values as $k => $val ) {
@@ -89,10 +92,10 @@ class Preprocessor_Hash implements Preprocessor {
* Preprocess some wikitext and return the document tree.
* This is the ghost of Parser::replace_variables().
*
- * @param string $text the text to parse
- * @param $flags Integer: bitwise combination of:
- * Parser::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" as if the text is being
- * included. Default is to assume a direct page view.
+ * @param string $text The text to parse
+ * @param int $flags Bitwise combination of:
+ * Parser::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" as if the text is being
+ * included. Default is to assume a direct page view.
*
* The generated DOM tree must depend only on the input text and the flags.
* The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
@@ -108,13 +111,15 @@ class Preprocessor_Hash implements Preprocessor {
* @throws MWException
* @return PPNode_Hash_Tree
*/
- function preprocessToObj( $text, $flags = 0 ) {
+ public function preprocessToObj( $text, $flags = 0 ) {
wfProfileIn( __METHOD__ );
// Check cache.
global $wgMemc, $wgPreprocessorCacheThreshold;
- $cacheable = $wgPreprocessorCacheThreshold !== false && strlen( $text ) > $wgPreprocessorCacheThreshold;
+ $cacheable = $wgPreprocessorCacheThreshold !== false
+ && strlen( $text ) > $wgPreprocessorCacheThreshold;
+
if ( $cacheable ) {
wfProfileIn( __METHOD__ . '-cacheable' );
@@ -161,7 +166,9 @@ class Preprocessor_Hash implements Preprocessor {
$ignoredTags = array( 'includeonly', '/includeonly' );
$ignoredElements = array( 'noinclude' );
$xmlishElements[] = 'noinclude';
- if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) {
+ if ( strpos( $text, '<onlyinclude>' ) !== false
+ && strpos( $text, '</onlyinclude>' ) !== false
+ ) {
$enableOnlyinclude = true;
}
} else {
@@ -177,18 +184,27 @@ class Preprocessor_Hash implements Preprocessor {
$stack = new PPDStack_Hash;
$searchBase = "[{<\n";
- $revText = strrev( $text ); // For fast reverse searches
+ // For fast reverse searches
+ $revText = strrev( $text );
$lengthText = strlen( $text );
- $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
- $accum =& $stack->getAccum(); # Current accumulator
- $findEquals = false; # True to find equals signs in arguments
- $findPipe = false; # True to take notice of pipe characters
+ // Input pointer, starts out pointing to a pseudo-newline before the start
+ $i = 0;
+ // Current accumulator
+ $accum =& $stack->getAccum();
+ // True to find equals signs in arguments
+ $findEquals = false;
+ // True to take notice of pipe characters
+ $findPipe = false;
$headingIndex = 1;
- $inHeading = false; # True if $i is inside a possible heading
- $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i
- $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude>
- $fakeLineStart = true; # Do a line-start run without outputting an LF character
+ // True if $i is inside a possible heading
+ $inHeading = false;
+ // True if there are no more greater-than (>) signs right of $i
+ $noMoreGT = false;
+ // True to ignore all input up to the next <onlyinclude>
+ $findOnlyinclude = $enableOnlyinclude;
+ // Do a line-start run without outputting an LF character
+ $fakeLineStart = true;
while ( true ) {
//$this->memCheck();
@@ -273,7 +289,9 @@ class Preprocessor_Hash implements Preprocessor {
if ( $found == 'angle' ) {
$matches = false;
// Handle </onlyinclude>
- if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) {
+ if ( $enableOnlyinclude
+ && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
+ ) {
$findOnlyinclude = true;
continue;
}
@@ -326,15 +344,15 @@ class Preprocessor_Hash implements Preprocessor {
// the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
// it's a possible beneficial b/c break.
if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
- && substr( $text, $wsEnd + 1, 1 ) == "\n" )
- {
+ && substr( $text, $wsEnd + 1, 1 ) == "\n"
+ ) {
// Remove leading whitespace from the end of the accumulator
// Sanity check first though
$wsLength = $i - $wsStart;
if ( $wsLength > 0
&& $accum->lastNode instanceof PPNode_Hash_Text
- && strspn( $accum->lastNode->value, " \t", -$wsLength ) === $wsLength )
- {
+ && strspn( $accum->lastNode->value, " \t", -$wsLength ) === $wsLength
+ ) {
$accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength );
}
@@ -404,8 +422,8 @@ class Preprocessor_Hash implements Preprocessor {
$attrEnd = $tagEndPos;
// Find closing tag
if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
- $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
- {
+ $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
+ ) {
$inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
$i = $matches[0][1] + strlen( $matches[0][0] );
$close = $matches[0][0];
@@ -440,9 +458,7 @@ class Preprocessor_Hash implements Preprocessor {
$extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) );
}
$accum->addNode( $extNode );
- }
-
- elseif ( $found == 'line-start' ) {
+ } elseif ( $found == 'line-start' ) {
// Is this the start of a heading?
// Line break belongs before the heading element in any case
if ( $fakeLineStart ) {
@@ -454,9 +470,10 @@ class Preprocessor_Hash implements Preprocessor {
$count = strspn( $text, '=', $i, 6 );
if ( $count == 1 && $findEquals ) {
- // DWIM: This looks kind of like a name/value separator
- // Let's let the equals handler have it and break the potential heading
- // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex.
+ // DWIM: This looks kind of like a name/value separator.
+ // Let's let the equals handler have it and break the potential
+ // heading. This is heuristic, but AFAICT the methods for
+ // completely correct disambiguation are very complex.
} elseif ( $count > 0 ) {
$piece = array(
'open' => "\n",
@@ -474,8 +491,9 @@ class Preprocessor_Hash implements Preprocessor {
// A heading must be open, otherwise \n wouldn't have been in the search list
assert( '$piece->open == "\n"' );
$part = $piece->getCurrentPart();
- // Search back through the input to see if it has a proper close
- // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient
+ // Search back through the input to see if it has a proper close.
+ // Do this using the reversed string since the other solutions
+ // (end anchor, etc.) are inefficient.
$wsLength = strspn( $revText, " \t", $lengthText - $i );
$searchStart = $i - $wsLength;
if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
@@ -743,9 +761,12 @@ class Preprocessor_Hash implements Preprocessor {
/**
* Stack class to help Preprocessor::preprocessToObj()
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPDStack_Hash extends PPDStack {
- function __construct() {
+ // @codingStandardsIgnoreEnd
+
+ public function __construct() {
$this->elementClass = 'PPDStackElement_Hash';
parent::__construct();
$this->rootAccum = new PPDAccum_Hash;
@@ -754,9 +775,12 @@ class PPDStack_Hash extends PPDStack {
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPDStackElement_Hash extends PPDStackElement {
- function __construct( $data = array() ) {
+ // @codingStandardsIgnoreENd
+
+ public function __construct( $data = array() ) {
$this->partClass = 'PPDPart_Hash';
parent::__construct( $data );
}
@@ -764,9 +788,10 @@ class PPDStackElement_Hash extends PPDStackElement {
/**
* Get the accumulator that would result if the close is not found.
*
+ * @param int|bool $openingCount
* @return PPDAccum_Hash
*/
- function breakSyntax( $openingCount = false ) {
+ public function breakSyntax( $openingCount = false ) {
if ( $this->open == "\n" ) {
$accum = $this->parts[0]->out;
} else {
@@ -791,9 +816,12 @@ class PPDStackElement_Hash extends PPDStackElement {
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPDPart_Hash extends PPDPart {
- function __construct( $out = '' ) {
+ // @codingStandardsIgnoreEnd
+
+ public function __construct( $out = '' ) {
$accum = new PPDAccum_Hash;
if ( $out !== '' ) {
$accum->addLiteral( $out );
@@ -804,18 +832,22 @@ class PPDPart_Hash extends PPDPart {
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPDAccum_Hash {
- var $firstNode, $lastNode;
+ // @codingStandardsIgnoreEnd
+
+ public $firstNode, $lastNode;
- function __construct() {
+ public function __construct() {
$this->firstNode = $this->lastNode = false;
}
/**
* Append a string literal
+ * @param string $s
*/
- function addLiteral( $s ) {
+ public function addLiteral( $s ) {
if ( $this->lastNode === false ) {
$this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s );
} elseif ( $this->lastNode instanceof PPNode_Hash_Text ) {
@@ -828,8 +860,9 @@ class PPDAccum_Hash {
/**
* Append a PPNode
+ * @param PPNode $node
*/
- function addNode( PPNode $node ) {
+ public function addNode( PPNode $node ) {
if ( $this->lastNode === false ) {
$this->firstNode = $this->lastNode = $node;
} else {
@@ -840,18 +873,21 @@ class PPDAccum_Hash {
/**
* Append a tree node with text contents
+ * @param string $name
+ * @param string $value
*/
- function addNodeWithText( $name, $value ) {
+ public function addNodeWithText( $name, $value ) {
$node = PPNode_Hash_Tree::newWithText( $name, $value );
$this->addNode( $node );
}
/**
- * Append a PPAccum_Hash
+ * Append a PPDAccum_Hash
* Takes over ownership of the nodes in the source argument. These nodes may
* subsequently be modified, especially nextSibling.
+ * @param PPDAccum_Hash $accum
*/
- function addAccum( $accum ) {
+ public function addAccum( $accum ) {
if ( $accum->lastNode === false ) {
// nothing to add
} elseif ( $this->lastNode === false ) {
@@ -867,62 +903,72 @@ class PPDAccum_Hash {
/**
* An expansion frame, used as a context to expand the result of preprocessToObj()
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPFrame_Hash implements PPFrame {
+ // @codingStandardsIgnoreEnd
/**
* @var Parser
*/
- var $parser;
+ public $parser;
/**
* @var Preprocessor
*/
- var $preprocessor;
+ public $preprocessor;
/**
* @var Title
*/
- var $title;
- var $titleCache;
+ public $title;
+ public $titleCache;
/**
* Hashtable listing templates which are disallowed for expansion in this frame,
* having been encountered previously in parent frames.
*/
- var $loopCheckHash;
+ public $loopCheckHash;
/**
* Recursion depth of this frame, top = 0
* Note that this is NOT the same as expansion depth in expand()
*/
- var $depth;
+ public $depth;
+
+ private $volatile = false;
+ private $ttl = null;
+
+ /**
+ * @var array
+ */
+ protected $childExpansionCache;
/**
* Construct a new preprocessor frame.
- * @param $preprocessor Preprocessor: the parent preprocessor
+ * @param Preprocessor $preprocessor The parent preprocessor
*/
- function __construct( $preprocessor ) {
+ public function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
$this->parser = $preprocessor->parser;
$this->title = $this->parser->mTitle;
$this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
$this->loopCheckHash = array();
$this->depth = 0;
+ $this->childExpansionCache = array();
}
/**
* Create a new child frame
* $args is optionally a multi-root PPNode or array containing the template arguments
*
- * @param array|bool|\PPNode_Hash_Array $args PPNode_Hash_Array|array
- * @param $title Title|bool
- *
+ * @param array|bool|PPNode_Hash_Array $args
+ * @param Title|bool $title
* @param int $indexOffset
* @throws MWException
* @return PPTemplateFrame_Hash
*/
- function newChild( $args = false, $title = false, $indexOffset = 0 ) {
+ public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
$namedArgs = array();
$numberedArgs = array();
if ( $title === false ) {
@@ -954,11 +1000,23 @@ class PPFrame_Hash implements PPFrame {
/**
* @throws MWException
- * @param $root
- * @param $flags int
+ * @param string|int $key
+ * @param string|PPNode $root
+ * @param int $flags
+ * @return string
+ */
+ public function cachedExpand( $key, $root, $flags = 0 ) {
+ // we don't have a parent, so we don't have a cache
+ return $this->expand( $root, $flags );
+ }
+
+ /**
+ * @throws MWException
+ * @param string|PPNode $root
+ * @param int $flags
* @return string
*/
- function expand( $root, $flags = 0 ) {
+ public function expand( $root, $flags = 0 ) {
static $expansionDepth = 0;
if ( is_string( $root ) ) {
return $root;
@@ -1035,7 +1093,11 @@ class PPFrame_Hash implements PPFrame {
# Double-brace expansion
$bits = $contextNode->splitTemplate();
if ( $flags & PPFrame::NO_TEMPLATES ) {
- $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] );
+ $newIterator = $this->virtualBracketedImplode(
+ '{{', '|', '}}',
+ $bits['title'],
+ $bits['parts']
+ );
} else {
$ret = $this->parser->braceSubstitution( $bits, $this );
if ( isset( $ret['object'] ) ) {
@@ -1048,7 +1110,11 @@ class PPFrame_Hash implements PPFrame {
# Triple-brace expansion
$bits = $contextNode->splitTemplate();
if ( $flags & PPFrame::NO_ARGS ) {
- $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] );
+ $newIterator = $this->virtualBracketedImplode(
+ '{{{', '|', '}}}',
+ $bits['title'],
+ $bits['parts']
+ );
} else {
$ret = $this->parser->argSubstitution( $bits, $this );
if ( isset( $ret['object'] ) ) {
@@ -1062,17 +1128,16 @@ class PPFrame_Hash implements PPFrame {
# Remove it in HTML, pre+remove and STRIP_COMMENTS modes
if ( $this->parser->ot['html']
|| ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
- || ( $flags & PPFrame::STRIP_COMMENTS ) )
- {
+ || ( $flags & PPFrame::STRIP_COMMENTS )
+ ) {
$out .= '';
- }
- # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
- # Not in RECOVER_COMMENTS mode (extractSections) though
- elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
+ } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
+ # Add a strip marker in PST mode so that pstPass2() can
+ # run some old-fashioned regexes on the result.
+ # Not in RECOVER_COMMENTS mode (extractSections) though.
$out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
- }
- # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
- else {
+ } else {
+ # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
$out .= $contextNode->firstChild->value;
}
} elseif ( $contextNode->name == 'ignore' ) {
@@ -1080,7 +1145,9 @@ class PPFrame_Hash implements PPFrame {
# OT_WIKI will only respect <ignore> in substed templates.
# The other output types respect it unless NO_IGNORE is set.
# extractSections() sets NO_IGNORE and so never respects it.
- if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) {
+ if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
+ || ( $flags & PPFrame::NO_IGNORE )
+ ) {
$out .= $contextNode->firstChild->value;
} else {
//$out .= '';
@@ -1088,7 +1155,23 @@ class PPFrame_Hash implements PPFrame {
} elseif ( $contextNode->name == 'ext' ) {
# Extension tag
$bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null );
- $out .= $this->parser->extensionSubstitution( $bits, $this );
+ if ( $flags & PPFrame::NO_TAGS ) {
+ $s = '<' . $bits['name']->firstChild->value;
+ if ( $bits['attr'] ) {
+ $s .= $bits['attr']->firstChild->value;
+ }
+ if ( $bits['inner'] ) {
+ $s .= '>' . $bits['inner']->firstChild->value;
+ if ( $bits['close'] ) {
+ $s .= $bits['close']->firstChild->value;
+ }
+ } else {
+ $s .= '/>';
+ }
+ $out .= $s;
+ } else {
+ $out .= $this->parser->extensionSubstitution( $bits, $this );
+ }
} elseif ( $contextNode->name == 'h' ) {
# Heading
if ( $this->parser->ot['html'] ) {
@@ -1139,11 +1222,12 @@ class PPFrame_Hash implements PPFrame {
}
/**
- * @param $sep
- * @param $flags
+ * @param string $sep
+ * @param int $flags
+ * @param string|PPNode $args,...
* @return string
*/
- function implodeWithFlags( $sep, $flags /*, ... */ ) {
+ public function implodeWithFlags( $sep, $flags /*, ... */ ) {
$args = array_slice( func_get_args(), 2 );
$first = true;
@@ -1170,9 +1254,11 @@ 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,...
* @return string
*/
- function implode( $sep /*, ... */ ) {
+ public function implode( $sep /*, ... */ ) {
$args = array_slice( func_get_args(), 1 );
$first = true;
@@ -1200,9 +1286,11 @@ class PPFrame_Hash implements PPFrame {
* Makes an object that, when expand()ed, will be the same as one obtained
* with implode()
*
+ * @param string $sep
+ * @param string|PPNode $args,...
* @return PPNode_Hash_Array
*/
- function virtualImplode( $sep /*, ... */ ) {
+ public function virtualImplode( $sep /*, ... */ ) {
$args = array_slice( func_get_args(), 1 );
$out = array();
$first = true;
@@ -1229,9 +1317,13 @@ class PPFrame_Hash implements PPFrame {
/**
* Virtual implode with brackets
*
+ * @param string $start
+ * @param string $sep
+ * @param string $end
+ * @param string|PPNode $args,...
* @return PPNode_Hash_Array
*/
- function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
+ public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
$args = array_slice( func_get_args(), 3 );
$out = array( $start );
$first = true;
@@ -1256,15 +1348,15 @@ class PPFrame_Hash implements PPFrame {
return new PPNode_Hash_Array( $out );
}
- function __toString() {
+ public function __toString() {
return 'frame{}';
}
/**
- * @param $level bool
- * @return array|bool|String
+ * @param bool $level
+ * @return array|bool|string
*/
- function getPDBK( $level = false ) {
+ public function getPDBK( $level = false ) {
if ( $level === false ) {
return $this->title->getPrefixedDBkey();
} else {
@@ -1275,21 +1367,21 @@ class PPFrame_Hash implements PPFrame {
/**
* @return array
*/
- function getArguments() {
+ public function getArguments() {
return array();
}
/**
* @return array
*/
- function getNumberedArguments() {
+ public function getNumberedArguments() {
return array();
}
/**
* @return array
*/
- function getNamedArguments() {
+ public function getNamedArguments() {
return array();
}
@@ -1298,26 +1390,26 @@ class PPFrame_Hash implements PPFrame {
*
* @return bool
*/
- function isEmpty() {
+ public function isEmpty() {
return true;
}
/**
- * @param $name
+ * @param string $name
* @return bool
*/
- function getArgument( $name ) {
+ public function getArgument( $name ) {
return false;
}
/**
* Returns true if the infinite loop check is OK, false if a loop is detected
*
- * @param $title Title
+ * @param Title $title
*
* @return bool
*/
- function loopCheck( $title ) {
+ public function loopCheck( $title ) {
return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
}
@@ -1326,7 +1418,7 @@ class PPFrame_Hash implements PPFrame {
*
* @return bool
*/
- function isTemplate() {
+ public function isTemplate() {
return false;
}
@@ -1335,27 +1427,70 @@ class PPFrame_Hash implements PPFrame {
*
* @return Title
*/
- function getTitle() {
+ public function getTitle() {
return $this->title;
}
+
+ /**
+ * Set the volatile flag
+ *
+ * @param bool $flag
+ */
+ public function setVolatile( $flag = true ) {
+ $this->volatile = $flag;
+ }
+
+ /**
+ * Get the volatile flag
+ *
+ * @return bool
+ */
+ public function isVolatile() {
+ return $this->volatile;
+ }
+
+ /**
+ * Set the TTL
+ *
+ * @param int $ttl
+ */
+ public function setTTL( $ttl ) {
+ if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
+ $this->ttl = $ttl;
+ }
+ }
+
+ /**
+ * Get the TTL
+ *
+ * @return int|null
+ */
+ public function getTTL() {
+ return $this->ttl;
+ }
}
/**
* Expansion frame with template arguments
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPTemplateFrame_Hash extends PPFrame_Hash {
- var $numberedArgs, $namedArgs, $parent;
- var $numberedExpansionCache, $namedExpansionCache;
+ // @codingStandardsIgnoreEnd
+
+ public $numberedArgs, $namedArgs, $parent;
+ public $numberedExpansionCache, $namedExpansionCache;
/**
- * @param $preprocessor
- * @param $parent
- * @param $numberedArgs array
- * @param $namedArgs array
- * @param $title Title
+ * @param Preprocessor $preprocessor
+ * @param bool|PPFrame $parent
+ * @param array $numberedArgs
+ * @param array $namedArgs
+ * @param bool|Title $title
*/
- function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
+ public function __construct( $preprocessor, $parent = false, $numberedArgs = array(),
+ $namedArgs = array(), $title = false
+ ) {
parent::__construct( $preprocessor );
$this->parent = $parent;
@@ -1373,7 +1508,7 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
$this->numberedExpansionCache = $this->namedExpansionCache = array();
}
- function __toString() {
+ public function __toString() {
$s = 'tplframe{';
$first = true;
$args = $this->numberedArgs + $this->namedArgs;
@@ -1389,19 +1524,38 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
$s .= '}';
return $s;
}
+
+ /**
+ * @throws MWException
+ * @param string|int $key
+ * @param string|PPNode $root
+ * @param int $flags
+ * @return string
+ */
+ public function cachedExpand( $key, $root, $flags = 0 ) {
+ if ( isset( $this->parent->childExpansionCache[$key] ) ) {
+ return $this->parent->childExpansionCache[$key];
+ }
+ $retval = $this->expand( $root, $flags );
+ if ( !$this->isVolatile() ) {
+ $this->parent->childExpansionCache[$key] = $retval;
+ }
+ return $retval;
+ }
+
/**
* Returns true if there are no arguments in this frame
*
* @return bool
*/
- function isEmpty() {
+ public function isEmpty() {
return !count( $this->numberedArgs ) && !count( $this->namedArgs );
}
/**
* @return array
*/
- function getArguments() {
+ public function getArguments() {
$arguments = array();
foreach ( array_merge(
array_keys( $this->numberedArgs ),
@@ -1414,7 +1568,7 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
/**
* @return array
*/
- function getNumberedArguments() {
+ public function getNumberedArguments() {
$arguments = array();
foreach ( array_keys( $this->numberedArgs ) as $key ) {
$arguments[$key] = $this->getArgument( $key );
@@ -1425,7 +1579,7 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
/**
* @return array
*/
- function getNamedArguments() {
+ public function getNamedArguments() {
$arguments = array();
foreach ( array_keys( $this->namedArgs ) as $key ) {
$arguments[$key] = $this->getArgument( $key );
@@ -1434,25 +1588,28 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
}
/**
- * @param $index
+ * @param int $index
* @return array|bool
*/
- function getNumberedArgument( $index ) {
+ public function getNumberedArgument( $index ) {
if ( !isset( $this->numberedArgs[$index] ) ) {
return false;
}
if ( !isset( $this->numberedExpansionCache[$index] ) ) {
# No trimming for unnamed arguments
- $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS );
+ $this->numberedExpansionCache[$index] = $this->parent->expand(
+ $this->numberedArgs[$index],
+ PPFrame::STRIP_COMMENTS
+ );
}
return $this->numberedExpansionCache[$index];
}
/**
- * @param $name
+ * @param string $name
* @return bool
*/
- function getNamedArgument( $name ) {
+ public function getNamedArgument( $name ) {
if ( !isset( $this->namedArgs[$name] ) ) {
return false;
}
@@ -1465,10 +1622,10 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
}
/**
- * @param $name
+ * @param string $name
* @return array|bool
*/
- function getArgument( $name ) {
+ public function getArgument( $name ) {
$text = $this->getNumberedArgument( $name );
if ( $text === false ) {
$text = $this->getNamedArgument( $name );
@@ -1481,24 +1638,37 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
*
* @return bool
*/
- function isTemplate() {
+ public function isTemplate() {
return true;
}
+
+ public function setVolatile( $flag = true ) {
+ parent::setVolatile( $flag );
+ $this->parent->setVolatile( $flag );
+ }
+
+ public function setTTL( $ttl ) {
+ parent::setTTL( $ttl );
+ $this->parent->setTTL( $ttl );
+ }
}
/**
* Expansion frame with custom arguments
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPCustomFrame_Hash extends PPFrame_Hash {
- var $args;
+ // @codingStandardsIgnoreEnd
- function __construct( $preprocessor, $args ) {
+ public $args;
+
+ public function __construct( $preprocessor, $args ) {
parent::__construct( $preprocessor );
$this->args = $args;
}
- function __toString() {
+ public function __toString() {
$s = 'cstmframe{';
$first = true;
foreach ( $this->args as $name => $value ) {
@@ -1517,38 +1687,41 @@ class PPCustomFrame_Hash extends PPFrame_Hash {
/**
* @return bool
*/
- function isEmpty() {
+ public function isEmpty() {
return !count( $this->args );
}
/**
- * @param $index
+ * @param int $index
* @return bool
*/
- function getArgument( $index ) {
+ public function getArgument( $index ) {
if ( !isset( $this->args[$index] ) ) {
return false;
}
return $this->args[$index];
}
- function getArguments() {
+ public function getArguments() {
return $this->args;
}
}
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPNode_Hash_Tree implements PPNode {
- var $name, $firstChild, $lastChild, $nextSibling;
+ // @codingStandardsIgnoreEnd
+
+ public $name, $firstChild, $lastChild, $nextSibling;
- function __construct( $name ) {
+ public function __construct( $name ) {
$this->name = $name;
$this->firstChild = $this->lastChild = $this->nextSibling = false;
}
- function __toString() {
+ public function __toString() {
$inner = '';
$attribs = '';
for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) {
@@ -1566,17 +1739,17 @@ class PPNode_Hash_Tree implements PPNode {
}
/**
- * @param $name
- * @param $text
+ * @param string $name
+ * @param string $text
* @return PPNode_Hash_Tree
*/
- static function newWithText( $name, $text ) {
+ public static function newWithText( $name, $text ) {
$obj = new self( $name );
$obj->addChild( new PPNode_Hash_Text( $text ) );
return $obj;
}
- function addChild( $node ) {
+ public function addChild( $node ) {
if ( $this->lastChild === false ) {
$this->firstChild = $this->lastChild = $node;
} else {
@@ -1588,7 +1761,7 @@ class PPNode_Hash_Tree implements PPNode {
/**
* @return PPNode_Hash_Array
*/
- function getChildren() {
+ public function getChildren() {
$children = array();
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
$children[] = $child;
@@ -1596,15 +1769,15 @@ class PPNode_Hash_Tree implements PPNode {
return new PPNode_Hash_Array( $children );
}
- function getFirstChild() {
+ public function getFirstChild() {
return $this->firstChild;
}
- function getNextSibling() {
+ public function getNextSibling() {
return $this->nextSibling;
}
- function getChildrenOfType( $name ) {
+ public function getChildrenOfType( $name ) {
$children = array();
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
if ( isset( $child->name ) && $child->name === $name ) {
@@ -1617,22 +1790,22 @@ class PPNode_Hash_Tree implements PPNode {
/**
* @return bool
*/
- function getLength() {
+ public function getLength() {
return false;
}
/**
- * @param $i
+ * @param int $i
* @return bool
*/
- function item( $i ) {
+ public function item( $i ) {
return false;
}
/**
* @return string
*/
- function getName() {
+ public function getName() {
return $this->name;
}
@@ -1645,7 +1818,7 @@ class PPNode_Hash_Tree implements PPNode {
* @throws MWException
* @return array
*/
- function splitArg() {
+ public function splitArg() {
$bits = array();
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
if ( !isset( $child->name ) ) {
@@ -1654,8 +1827,8 @@ class PPNode_Hash_Tree implements PPNode {
if ( $child->name === 'name' ) {
$bits['name'] = $child;
if ( $child->firstChild instanceof PPNode_Hash_Attr
- && $child->firstChild->name === 'index' )
- {
+ && $child->firstChild->name === 'index'
+ ) {
$bits['index'] = $child->firstChild->value;
}
} elseif ( $child->name === 'value' ) {
@@ -1679,7 +1852,7 @@ class PPNode_Hash_Tree implements PPNode {
* @throws MWException
* @return array
*/
- function splitExt() {
+ public function splitExt() {
$bits = array();
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
if ( !isset( $child->name ) ) {
@@ -1707,7 +1880,7 @@ class PPNode_Hash_Tree implements PPNode {
* @throws MWException
* @return array
*/
- function splitHeading() {
+ public function splitHeading() {
if ( $this->name !== 'h' ) {
throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
}
@@ -1734,7 +1907,7 @@ class PPNode_Hash_Tree implements PPNode {
* @throws MWException
* @return array
*/
- function splitTemplate() {
+ public function splitTemplate() {
$parts = array();
$bits = array( 'lineStart' => '' );
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
@@ -1761,101 +1934,178 @@ class PPNode_Hash_Tree implements PPNode {
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPNode_Hash_Text implements PPNode {
- var $value, $nextSibling;
+ // @codingStandardsIgnoreEnd
- function __construct( $value ) {
+ public $value, $nextSibling;
+
+ public function __construct( $value ) {
if ( is_object( $value ) ) {
throw new MWException( __CLASS__ . ' given object instead of string' );
}
$this->value = $value;
}
- function __toString() {
+ public function __toString() {
return htmlspecialchars( $this->value );
}
- function getNextSibling() {
+ public function getNextSibling() {
return $this->nextSibling;
}
- function getChildren() { return false; }
- function getFirstChild() { return false; }
- function getChildrenOfType( $name ) { return false; }
- function getLength() { return false; }
- function item( $i ) { return false; }
- function getName() { return '#text'; }
- function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
+ public function getChildren() {
+ return false;
+ }
+
+ public function getFirstChild() {
+ return false;
+ }
+
+ public function getChildrenOfType( $name ) {
+ return false;
+ }
+
+ public function getLength() {
+ return false;
+ }
+
+ public function item( $i ) {
+ return false;
+ }
+
+ public function getName() {
+ return '#text';
+ }
+
+ public function splitArg() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitExt() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitHeading() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
}
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPNode_Hash_Array implements PPNode {
- var $value, $nextSibling;
+ // @codingStandardsIgnoreEnd
+
+ public $value, $nextSibling;
- function __construct( $value ) {
+ public function __construct( $value ) {
$this->value = $value;
}
- function __toString() {
+ public function __toString() {
return var_export( $this, true );
}
- function getLength() {
+ public function getLength() {
return count( $this->value );
}
- function item( $i ) {
+ public function item( $i ) {
return $this->value[$i];
}
- function getName() { return '#nodelist'; }
+ public function getName() {
+ return '#nodelist';
+ }
- function getNextSibling() {
+ public function getNextSibling() {
return $this->nextSibling;
}
- function getChildren() { return false; }
- function getFirstChild() { return false; }
- function getChildrenOfType( $name ) { return false; }
- function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
+ public function getChildren() {
+ return false;
+ }
+
+ public function getFirstChild() {
+ return false;
+ }
+
+ public function getChildrenOfType( $name ) {
+ return false;
+ }
+
+ public function splitArg() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitExt() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitHeading() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
}
/**
* @ingroup Parser
+ * @codingStandardsIgnoreStart
*/
class PPNode_Hash_Attr implements PPNode {
- var $name, $value, $nextSibling;
+ // @codingStandardsIgnoreEnd
+
+ public $name, $value, $nextSibling;
- function __construct( $name, $value ) {
+ public function __construct( $name, $value ) {
$this->name = $name;
$this->value = $value;
}
- function __toString() {
+ public function __toString() {
return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
}
- function getName() {
+ public function getName() {
return $this->name;
}
- function getNextSibling() {
+ public function getNextSibling() {
return $this->nextSibling;
}
- function getChildren() { return false; }
- function getFirstChild() { return false; }
- function getChildrenOfType( $name ) { return false; }
- function getLength() { return false; }
- function item( $i ) { return false; }
- function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
- function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
+ public function getChildren() {
+ return false;
+ }
+
+ public function getFirstChild() {
+ return false;
+ }
+
+ public function getChildrenOfType( $name ) {
+ return false;
+ }
+
+ public function getLength() {
+ return false;
+ }
+
+ public function item( $i ) {
+ return false;
+ }
+
+ public function splitArg() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitExt() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitHeading() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
}
diff --git a/includes/parser/StripState.php b/includes/parser/StripState.php
index 5f3f18ea..5d1743e6 100644
--- a/includes/parser/StripState.php
+++ b/includes/parser/StripState.php
@@ -37,9 +37,9 @@ class StripState {
const UNSTRIP_RECURSION_LIMIT = 20;
/**
- * @param $prefix string
+ * @param string $prefix
*/
- function __construct( $prefix ) {
+ public function __construct( $prefix ) {
$this->prefix = $prefix;
$this->data = array(
'nowiki' => array(),
@@ -51,26 +51,26 @@ class StripState {
/**
* Add a nowiki strip item
- * @param $marker
- * @param $value
+ * @param string $marker
+ * @param string $value
*/
- function addNoWiki( $marker, $value ) {
+ public function addNoWiki( $marker, $value ) {
$this->addItem( 'nowiki', $marker, $value );
}
/**
- * @param $marker
- * @param $value
+ * @param string $marker
+ * @param string $value
*/
- function addGeneral( $marker, $value ) {
+ public function addGeneral( $marker, $value ) {
$this->addItem( 'general', $marker, $value );
}
/**
* @throws MWException
- * @param $type
- * @param $marker
- * @param $value
+ * @param string $type
+ * @param string $marker
+ * @param string $value
*/
protected function addItem( $type, $marker, $value ) {
if ( !preg_match( $this->regex, $marker, $m ) ) {
@@ -81,34 +81,34 @@ class StripState {
}
/**
- * @param $text
+ * @param string $text
* @return mixed
*/
- function unstripGeneral( $text ) {
+ public function unstripGeneral( $text ) {
return $this->unstripType( 'general', $text );
}
/**
- * @param $text
+ * @param string $text
* @return mixed
*/
- function unstripNoWiki( $text ) {
+ public function unstripNoWiki( $text ) {
return $this->unstripType( 'nowiki', $text );
}
/**
- * @param $text
+ * @param string $text
* @return mixed
*/
- function unstripBoth( $text ) {
+ public function unstripBoth( $text ) {
$text = $this->unstripType( 'general', $text );
$text = $this->unstripType( 'nowiki', $text );
return $text;
}
/**
- * @param $type
- * @param $text
+ * @param string $type
+ * @param string $text
* @return mixed
*/
protected function unstripType( $type, $text ) {
@@ -127,7 +127,7 @@ class StripState {
}
/**
- * @param $m array
+ * @param array $m
* @return array
*/
protected function unstripCallback( $m ) {
@@ -159,11 +159,11 @@ class StripState {
* Get a StripState object which is sufficient to unstrip the given text.
* It will contain the minimum subset of strip items necessary.
*
- * @param $text string
+ * @param string $text
*
* @return StripState
*/
- function getSubState( $text ) {
+ public function getSubState( $text ) {
$subState = new StripState( $this->prefix );
$pos = 0;
while ( true ) {
@@ -195,11 +195,11 @@ class StripState {
* will not be preserved. The strings in the $texts array will have their
* strip markers rewritten, the resulting array of strings will be returned.
*
- * @param $otherState StripState
- * @param $texts Array
- * @return Array
+ * @param StripState $otherState
+ * @param array $texts
+ * @return array
*/
- function merge( $otherState, $texts ) {
+ public function merge( $otherState, $texts ) {
$mergePrefix = Parser::getRandomString();
foreach ( $otherState->data as $type => $items ) {
@@ -215,7 +215,7 @@ class StripState {
}
/**
- * @param $m
+ * @param array $m
* @return string
*/
protected function mergeCallback( $m ) {
@@ -226,10 +226,10 @@ class StripState {
/**
* Remove any strip markers found in the given text.
*
- * @param $text Input string
+ * @param string $text Input string
* @return string
*/
- function killMarkers( $text ) {
+ public function killMarkers( $text ) {
return preg_replace( $this->regex, '', $text );
}
}
diff --git a/includes/password/BcryptPassword.php b/includes/password/BcryptPassword.php
new file mode 100644
index 00000000..dd806e26
--- /dev/null
+++ b/includes/password/BcryptPassword.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Implements the BcryptPassword class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * A Bcrypt-hashed password
+ *
+ * This is a computationally complex password hash for use in modern applications.
+ * The number of rounds can be configured by $wgPasswordConfig['bcrypt']['cost'].
+ *
+ * @since 1.24
+ */
+class BcryptPassword extends ParameterizedPassword {
+ protected function getDefaultParams() {
+ return array(
+ 'rounds' => $this->config['cost'],
+ );
+ }
+
+ protected function getDelimiter() {
+ return '$';
+ }
+
+ protected function parseHash( $hash ) {
+ parent::parseHash( $hash );
+
+ $this->params['rounds'] = (int)$this->params['rounds'];
+ }
+
+ /**
+ * @param string $password Password to encrypt
+ *
+ * @throws PasswordError If bcrypt has an unknown error
+ * @throws MWException If bcrypt is not supported by PHP
+ */
+ public function crypt( $password ) {
+ if ( !defined( 'CRYPT_BLOWFISH' ) ) {
+ throw new MWException( 'Bcrypt is not supported.' );
+ }
+
+ // Either use existing hash or make a new salt
+ // Bcrypt expects 22 characters of base64-encoded salt
+ // Note: bcrypt does not use MIME base64. It uses its own base64 without any '=' padding.
+ // It expects a 128 bit salt, so it will ignore anything after the first 128 bits
+ if ( !isset( $this->args[0] ) ) {
+ $this->args[] = substr(
+ // Replace + with ., because bcrypt uses a non-MIME base64 format
+ strtr(
+ // Random base64 encoded string
+ base64_encode( MWCryptRand::generate( 16, true ) ),
+ '+', '.'
+ ),
+ 0, 22
+ );
+ }
+
+ $hash = crypt( $password,
+ sprintf( '$2y$%02d$%s', (int)$this->params['rounds'], $this->args[0] ) );
+
+ if ( !is_string( $hash ) || strlen( $hash ) <= 13 ) {
+ throw new PasswordError( 'Error when hashing password.' );
+ }
+
+ // Strip the $2y$
+ $parts = explode( $this->getDelimiter(), substr( $hash, 4 ) );
+ $this->params['rounds'] = (int)$parts[0];
+ $this->args[0] = substr( $parts[1], 0, 22 );
+ $this->hash = substr( $parts[1], 22 );
+ }
+}
diff --git a/includes/password/EncryptedPassword.php b/includes/password/EncryptedPassword.php
new file mode 100644
index 00000000..39da32d1
--- /dev/null
+++ b/includes/password/EncryptedPassword.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Implements the EncryptedPassword class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * Helper class for passwords that use another password hash underneath it
+ * and encrypts that hash with a configured secret.
+ *
+ * @since 1.24
+ */
+class EncryptedPassword extends ParameterizedPassword {
+ protected function getDelimiter() {
+ return ':';
+ }
+
+ protected function getDefaultParams() {
+ return array(
+ 'cipher' => $this->config['cipher'],
+ 'secret' => count( $this->config['secrets'] ) - 1
+ );
+ }
+
+ public function crypt( $password ) {
+ $secret = $this->config['secrets'][$this->params['secret']];
+
+ if ( $this->hash ) {
+ $underlyingPassword = $this->factory->newFromCiphertext( openssl_decrypt(
+ base64_decode( $this->hash ), $this->params['cipher'],
+ $secret, 0, base64_decode( $this->args[0] )
+ ) );
+ } else {
+ $underlyingPassword = $this->factory->newFromType( $this->config['underlying'], $this->config );
+ }
+
+ $underlyingPassword->crypt( $password );
+ $iv = MWCryptRand::generate( openssl_cipher_iv_length( $this->params['cipher'] ), true );
+
+ $this->hash = openssl_encrypt(
+ $underlyingPassword->toString(), $this->params['cipher'], $secret, 0, $iv );
+ $this->args = array( base64_encode( $iv ) );
+ }
+
+ /**
+ * Updates the underlying hash by encrypting it with the newest secret.
+ *
+ * @throws MWException If the configuration is not valid
+ * @return bool True if the password was updated
+ */
+ public function update() {
+ if ( count( $this->args ) != 2 || $this->params == $this->getDefaultParams() ) {
+ // Hash does not need updating
+ return false;
+ }
+
+ // Decrypt the underlying hash
+ $underlyingHash = openssl_decrypt(
+ base64_decode( $this->args[1] ),
+ $this->params['cipher'],
+ $this->config['secrets'][$this->params['secret']],
+ 0,
+ base64_decode( $this->args[0] )
+ );
+
+ // Reset the params
+ $this->params = $this->getDefaultParams();
+
+ // Check the key size with the new params
+ $iv = MWCryptRand::generate( openssl_cipher_iv_length( $this->params['cipher'] ), true );
+ $this->hash = base64_encode( openssl_encrypt(
+ $underlyingHash,
+ $this->params['cipher'],
+ $this->config['secrets'][$this->params['secret']],
+ 0,
+ $iv
+ ) );
+ $this->args = array( base64_encode( $iv ) );
+
+ return true;
+ }
+}
diff --git a/includes/password/InvalidPassword.php b/includes/password/InvalidPassword.php
new file mode 100644
index 00000000..e45b7744
--- /dev/null
+++ b/includes/password/InvalidPassword.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Implements the InvalidPassword class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * Represents an invalid password hash. It is represented as the empty string (i.e.,
+ * a password hash with no type).
+ *
+ * No two invalid passwords are equal. Comparing anything to an invalid password will
+ * return false.
+ *
+ * @since 1.24
+ */
+class InvalidPassword extends Password {
+ public function crypt( $plaintext ) {
+ }
+
+ public function toString() {
+ return '';
+ }
+
+ public function equals( $other ) {
+ return false;
+ }
+
+ public function needsUpdate() {
+ return false;
+ }
+}
diff --git a/includes/password/LayeredParameterizedPassword.php b/includes/password/LayeredParameterizedPassword.php
new file mode 100644
index 00000000..5735e282
--- /dev/null
+++ b/includes/password/LayeredParameterizedPassword.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Implements the LayeredParameterizedPassword class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * This password hash type layers one or more parameterized password types
+ * on top of each other.
+ *
+ * The underlying types must be parameterized. This wrapping type accumulates
+ * all the parameters and arguments from each hash and then passes the hash of
+ * the last layer as the password for the next layer.
+ *
+ * @since 1.24
+ */
+class LayeredParameterizedPassword extends ParameterizedPassword {
+ protected function getDelimiter() {
+ return '!';
+ }
+
+ protected function getDefaultParams() {
+ $params = array();
+
+ foreach ( $this->config['types'] as $type ) {
+ $passObj = $this->factory->newFromType( $type );
+
+ if ( !$passObj instanceof ParameterizedPassword ) {
+ throw new MWException( 'Underlying type must be a parameterized password.' );
+ } elseif ( $passObj->getDelimiter() === $this->getDelimiter() ) {
+ throw new MWException( 'Underlying type cannot use same delimiter as encapsulating type.' );
+ }
+
+ $params[] = implode( $passObj->getDelimiter(), $passObj->getDefaultParams() );
+ }
+
+ return $params;
+ }
+
+ public function crypt( $password ) {
+ $lastHash = $password;
+ foreach ( $this->config['types'] as $i => $type ) {
+ // Construct pseudo-hash based on params and arguments
+ /** @var ParameterizedPassword $passObj */
+ $passObj = $this->factory->newFromType( $type );
+
+ $params = '';
+ $args = '';
+ if ( $this->params[$i] !== '' ) {
+ $params = $this->params[$i] . $passObj->getDelimiter();
+ }
+ if ( isset( $this->args[$i] ) && $this->args[$i] !== '' ) {
+ $args = $this->args[$i] . $passObj->getDelimiter();
+ }
+ $existingHash = ":$type:" . $params . $args . $this->hash;
+
+ // Hash the last hash with the next type in the layer
+ $passObj = $this->factory->newFromCiphertext( $existingHash );
+ $passObj->crypt( $lastHash );
+
+ // Move over the params and args
+ $this->params[$i] = implode( $passObj->getDelimiter(), $passObj->params );
+ $this->args[$i] = implode( $passObj->getDelimiter(), $passObj->args );
+ $lastHash = $passObj->hash;
+ }
+
+ $this->hash = $lastHash;
+ }
+
+ /**
+ * Finish the hashing of a partially hashed layered hash
+ *
+ * Given a password hash that is hashed using the first layer of this object's
+ * configuration, perform the remaining layers of password hashing in order to
+ * get an updated hash with all the layers.
+ *
+ * @param ParameterizedPassword $passObj Password hash of the first layer
+ *
+ * @throws MWException If the first parameter is not of the correct type
+ */
+ public function partialCrypt( ParameterizedPassword $passObj ) {
+ $type = $passObj->config['type'];
+ if ( $type !== $this->config['types'][0] ) {
+ throw new MWException( 'Only a hash in the first layer can be finished.' );
+ }
+
+ // Gather info from the existing hash
+ $this->params[0] = implode( $passObj->getDelimiter(), $passObj->params );
+ $this->args[0] = implode( $passObj->getDelimiter(), $passObj->args );
+ $lastHash = $passObj->hash;
+
+ // Layer the remaining types
+ foreach ( $this->config['types'] as $i => $type ) {
+ if ( $i == 0 ) {
+ continue;
+ };
+
+ // Construct pseudo-hash based on params and arguments
+ /** @var ParameterizedPassword $passObj */
+ $passObj = $this->factory->newFromType( $type );
+
+ $params = '';
+ $args = '';
+ if ( $this->params[$i] !== '' ) {
+ $params = $this->params[$i] . $passObj->getDelimiter();
+ }
+ if ( isset( $this->args[$i] ) && $this->args[$i] !== '' ) {
+ $args = $this->args[$i] . $passObj->getDelimiter();
+ }
+ $existingHash = ":$type:" . $params . $args . $this->hash;
+
+ // Hash the last hash with the next type in the layer
+ $passObj = $this->factory->newFromCiphertext( $existingHash );
+ $passObj->crypt( $lastHash );
+
+ // Move over the params and args
+ $this->params[$i] = implode( $passObj->getDelimiter(), $passObj->params );
+ $this->args[$i] = implode( $passObj->getDelimiter(), $passObj->args );
+ $lastHash = $passObj->hash;
+ }
+
+ $this->hash = $lastHash;
+ }
+}
diff --git a/includes/password/MWOldPassword.php b/includes/password/MWOldPassword.php
new file mode 100644
index 00000000..afa5cacc
--- /dev/null
+++ b/includes/password/MWOldPassword.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Implements the MWOldPassword class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * The old style of MediaWiki password hashing. It involves
+ * running MD5 on the password.
+ *
+ * @since 1.24
+ */
+class MWOldPassword extends ParameterizedPassword {
+ protected function getDefaultParams() {
+ return array();
+ }
+
+ protected function getDelimiter() {
+ return ':';
+ }
+
+ public function crypt( $plaintext ) {
+ global $wgPasswordSalt;
+
+ if ( $wgPasswordSalt && count( $this->args ) === 1 ) {
+ $this->hash = md5( $this->args[0] . '-' . md5( $plaintext ) );
+ } else {
+ $this->args = array();
+ $this->hash = md5( $plaintext );
+ }
+ }
+}
diff --git a/includes/password/MWSaltedPassword.php b/includes/password/MWSaltedPassword.php
new file mode 100644
index 00000000..6c6895a2
--- /dev/null
+++ b/includes/password/MWSaltedPassword.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Implements the BcryptPassword class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * The old style of MediaWiki password hashing, with a salt. It involves
+ * running MD5 on the password, and then running MD5 on the salt concatenated
+ * with the first hash.
+ *
+ * @since 1.24
+ */
+class MWSaltedPassword extends ParameterizedPassword {
+ protected function getDefaultParams() {
+ return array();
+ }
+
+ protected function getDelimiter() {
+ return ':';
+ }
+
+ public function crypt( $plaintext ) {
+ if ( count( $this->args ) == 0 ) {
+ $this->args[] = MWCryptRand::generateHex( 8 );
+ }
+
+ $this->hash = md5( $this->args[0] . '-' . md5( $plaintext ) );
+ }
+}
diff --git a/includes/password/ParameterizedPassword.php b/includes/password/ParameterizedPassword.php
new file mode 100644
index 00000000..187f8954
--- /dev/null
+++ b/includes/password/ParameterizedPassword.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Implements the ParameterizedPassword class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * Helper class for password hash types that have a delimited set of parameters
+ * inside of the hash.
+ *
+ * All passwords are in the form of :<TYPE>:... as explained in the main Password
+ * class. This class is for hashes in the form of :<TYPE>:<PARAM1>:<PARAM2>:... where
+ * <PARAM1>, <PARAM2>, etc. are parameters that determine how the password was hashed.
+ * Of course, the internal delimiter (which is : by convention and default), can be
+ * changed by overriding the ParameterizedPassword::getDelimiter() function.
+ *
+ * This class requires overriding an additional function: ParameterizedPassword::getDefaultParams().
+ * See the function description for more details on the implementation.
+ *
+ * @since 1.24
+ */
+abstract class ParameterizedPassword extends Password {
+ /**
+ * Named parameters that have default values for this password type
+ * @var array
+ */
+ protected $params = array();
+
+ /**
+ * Extra arguments that were found in the hash. This may or may not make
+ * the hash invalid.
+ * @var array
+ */
+ protected $args = array();
+
+ protected function parseHash( $hash ) {
+ parent::parseHash( $hash );
+
+ if ( $hash === null ) {
+ $this->params = $this->getDefaultParams();
+ return;
+ }
+
+ $parts = explode( $this->getDelimiter(), $hash );
+ $paramKeys = array_keys( $this->getDefaultParams() );
+
+ if ( count( $parts ) < count( $paramKeys ) ) {
+ throw new PasswordError( 'Hash is missing required parameters.' );
+ }
+
+ if ( $paramKeys ) {
+ $this->args = array_splice( $parts, count( $paramKeys ) );
+ $this->params = array_combine( $paramKeys, $parts );
+ } else {
+ $this->args = $parts;
+ }
+
+ if ( $this->args ) {
+ $this->hash = array_pop( $this->args );
+ } else {
+ $this->hash = null;
+ }
+ }
+
+ public function needsUpdate() {
+ return parent::needsUpdate() || $this->params !== $this->getDefaultParams();
+ }
+
+ public function toString() {
+ $str = ':' . $this->config['type'] . ':';
+
+ if ( count( $this->params ) || count( $this->args ) ) {
+ $str .= implode( $this->getDelimiter(), array_merge( $this->params, $this->args ) );
+ $str .= $this->getDelimiter();
+ }
+
+ return $str . $this->hash;
+ }
+
+ /**
+ * Returns the delimiter for the parameters inside the hash
+ *
+ * @return string
+ */
+ abstract protected function getDelimiter();
+
+ /**
+ * Return an ordered array of default parameters for this password hash
+ *
+ * The keys should be the parameter names and the values should be the default
+ * values. Additionally, the order of the array should be the order in which they
+ * appear in the hash.
+ *
+ * When parsing a password hash, the constructor will split the hash based on
+ * the delimiter, and consume as many parts as it can, matching each to a parameter
+ * in this list. Once all the parameters have been filled, all remaining parts will
+ * be considered extra arguments, except, of course, for the very last part, which
+ * is the hash itself.
+ *
+ * @return array
+ */
+ abstract protected function getDefaultParams();
+}
diff --git a/includes/password/Password.php b/includes/password/Password.php
new file mode 100644
index 00000000..4e395b51
--- /dev/null
+++ b/includes/password/Password.php
@@ -0,0 +1,186 @@
+<?php
+/**
+ * Implements the Password class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * Represents a password hash for use in authentication
+ *
+ * Note: All password types are transparently prefixed with :<TYPE>:, where <TYPE>
+ * is the registered type of the hash. This prefix is stripped in the constructor
+ * and is added back in the toString() function.
+ *
+ * When inheriting this class, there are a couple of expectations
+ * to be fulfilled:
+ * * If Password::toString() is called on an object, and the result is passed back in
+ * to PasswordFactory::newFromCiphertext(), the result will be identical to the original.
+ * * The string representations of two Password objects are equal only if
+ * the original plaintext passwords match. In other words, if the toString() result of
+ * two objects match, the passwords are the same, and the user will be logged in.
+ * Since the string representation of a hash includes its type name (@see Password::toString),
+ * this property is preserved across all classes that inherit Password.
+ * If a hashing scheme does not fulfill this expectation, it must make sure to override the
+ * Password::equals() function and use custom comparison logic. However, this is not
+ * recommended unless absolutely required by the hashing mechanism.
+ * With these two points in mind, when creating a new Password sub-class, there are some functions
+ * you have to override (because they are abstract) and others that you may want to override.
+ *
+ * The abstract functions that must be overridden are:
+ * * Password::crypt(), which takes a plaintext password and hashes it into a string hash suitable
+ * for being passed to the constructor of that class, and then stores that hash (and whatever
+ * other data) into the internal state of the object.
+ * The functions that can optionally be overridden are:
+ * * Password::parseHash(), which can be useful to override if you need to extract values from or
+ * otherwise parse a password hash when it's passed to the constructor.
+ * * Password::needsUpdate(), which can be useful if a specific password hash has different
+ * logic for when the hash needs to be updated.
+ * * Password::toString(), which can be useful if the hash was changed in the constructor and
+ * needs to be re-assembled before being returned as a string. This function is expected to add
+ * the type back on to the hash, so make sure to do that if you override the function.
+ * * Password::equals() - This function compares two Password objects to see if they are equal.
+ * The default is to just do a timing-safe string comparison on the $this->hash values.
+ *
+ * After creating a new password hash type, it can be registered using the static
+ * Password::register() method. The default type is set using the Password::setDefaultType() type.
+ * Types must be registered before they can be set as the default.
+ *
+ * @since 1.24
+ */
+abstract class Password {
+ /**
+ * @var PasswordFactory Factory that created the object
+ */
+ protected $factory;
+
+ /**
+ * String representation of the hash without the type
+ * @var string
+ */
+ protected $hash;
+
+ /**
+ * Array of configuration variables injected from the constructor
+ * @var array
+ */
+ protected $config;
+
+ /**
+ * Construct the Password object using a string hash
+ *
+ * It is strongly recommended not to call this function directly unless you
+ * have a reason to. Use the PasswordFactory class instead.
+ *
+ * @throws MWException If $config does not contain required parameters
+ *
+ * @param PasswordFactory $factory Factory object that created the password
+ * @param array $config Array of engine configuration options for hashing
+ * @param string|null $hash The raw hash, including the type
+ */
+ final public function __construct( PasswordFactory $factory, array $config, $hash = null ) {
+ if ( !isset( $config['type'] ) ) {
+ throw new MWException( 'Password configuration must contain a type name.' );
+ }
+ $this->config = $config;
+ $this->factory = $factory;
+
+ if ( $hash !== null && strlen( $hash ) >= 3 ) {
+ // Strip the type from the hash for parsing
+ $hash = substr( $hash, strpos( $hash, ':', 1 ) + 1 );
+ }
+
+ $this->hash = $hash;
+ $this->parseHash( $hash );
+ }
+
+ /**
+ * Get the type name of the password
+ *
+ * @return string Password type
+ */
+ final public function getType() {
+ return $this->config['type'];
+ }
+
+ /**
+ * Perform any parsing necessary on the hash to see if the hash is valid
+ * and/or to perform logic for seeing if the hash needs updating.
+ *
+ * @param string $hash The hash, with the :<TYPE>: prefix stripped
+ * @throws PasswordError If there is an error in parsing the hash
+ */
+ protected function parseHash( $hash ) {
+ }
+
+ /**
+ * Determine if the hash needs to be updated
+ *
+ * @return bool True if needs update, false otherwise
+ */
+ public function needsUpdate() {
+ }
+
+ /**
+ * Compare one Password object to this object
+ *
+ * By default, do a timing-safe string comparison on the result of
+ * Password::toString() for each object. This can be overridden to do
+ * custom comparison, but it is not recommended unless necessary.
+ *
+ * @param Password|string $other The other password
+ * @return bool True if equal, false otherwise
+ */
+ public function equals( $other ) {
+ if ( !$other instanceof self ) {
+ // No need to use the factory because we're definitely making
+ // an object of the same type.
+ $obj = clone $this;
+ $obj->crypt( $other );
+ $other = $obj;
+ }
+
+ return hash_equals( $this->toString(), $other->toString() );
+ }
+
+ /**
+ * Convert this hash to a string that can be stored in the database
+ *
+ * The resulting string should be considered the seralized representation
+ * of this hash, i.e., if the return value were recycled back into
+ * PasswordFactory::newFromCiphertext, the returned object would be equivalent to
+ * this; also, if two objects return the same value from this function, they
+ * are considered equivalent.
+ *
+ * @return string
+ */
+ public function toString() {
+ return ':' . $this->config['type'] . ':' . $this->hash;
+ }
+
+ /**
+ * Hash a password and store the result in this object
+ *
+ * The result of the password hash should be put into the internal
+ * state of the hash object.
+ *
+ * @param string $password Password to hash
+ * @throws PasswordError If an internal error occurs in hashing
+ */
+ abstract public function crypt( $password );
+}
diff --git a/includes/password/PasswordError.php b/includes/password/PasswordError.php
new file mode 100644
index 00000000..c9707adb
--- /dev/null
+++ b/includes/password/PasswordError.php
@@ -0,0 +1,28 @@
+<?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
+ */
+
+/**
+ * Show an error when any operation involving passwords fails to run.
+ *
+ * @ingroup Exception
+ */
+class PasswordError extends MWException {
+ // NOP
+}
diff --git a/includes/password/PasswordFactory.php b/includes/password/PasswordFactory.php
new file mode 100644
index 00000000..3b4ebb1a
--- /dev/null
+++ b/includes/password/PasswordFactory.php
@@ -0,0 +1,178 @@
+<?php
+/**
+ * Implements the Password class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * Factory class for creating and checking Password objects
+ *
+ * @since 1.24
+ */
+final class PasswordFactory {
+ /**
+ * The default PasswordHash type
+ *
+ * @var string
+ * @see PasswordFactory::setDefaultType
+ */
+ private $default = '';
+
+ /**
+ * Mapping of password types to classes
+ * @var array
+ * @see PasswordFactory::register
+ * @see Setup.php
+ */
+ private $types = array(
+ '' => array( 'type' => '', 'class' => 'InvalidPassword' ),
+ );
+
+ /**
+ * Register a new type of password hash
+ *
+ * @param string $type Unique type name for the hash
+ * @param array $config Array of configuration options
+ */
+ public function register( $type, array $config ) {
+ $config['type'] = $type;
+ $this->types[$type] = $config;
+ }
+
+ /**
+ * Set the default password type
+ *
+ * @throws InvalidArgumentException If the type is not registered
+ * @param string $type Password hash type
+ */
+ public function setDefaultType( $type ) {
+ if ( !isset( $this->types[$type] ) ) {
+ throw new InvalidArgumentException( "Invalid password type $type." );
+ }
+ $this->default = $type;
+ }
+
+ /**
+ * Initialize the internal static variables using the global variables
+ *
+ * @param Config $config Configuration object to load data from
+ */
+ public function init( Config $config ) {
+ foreach ( $config->get( 'PasswordConfig' ) as $type => $options ) {
+ $this->register( $type, $options );
+ }
+
+ $this->setDefaultType( $config->get( 'PasswordDefault' ) );
+ }
+
+ /**
+ * Get the list of types of passwords
+ *
+ * @return array
+ */
+ public function getTypes() {
+ return $this->types;
+ }
+
+ /**
+ * Create a new Hash object from an existing string hash
+ *
+ * Parse the type of a hash and create a new hash object based on the parsed type.
+ * Pass the raw hash to the constructor of the new object. Use InvalidPassword type
+ * if a null hash is given.
+ *
+ * @param string|null $hash Existing hash or null for an invalid password
+ * @return Password
+ * @throws PasswordError If hash is invalid or type is not recognized
+ */
+ public function newFromCiphertext( $hash ) {
+ if ( $hash === null || $hash === false || $hash === '' ) {
+ return new InvalidPassword( $this, array( 'type' => '' ), null );
+ } elseif ( $hash[0] !== ':' ) {
+ throw new PasswordError( 'Invalid hash given' );
+ }
+
+ $type = substr( $hash, 1, strpos( $hash, ':', 1 ) - 1 );
+ if ( !isset( $this->types[$type] ) ) {
+ throw new PasswordError( "Unrecognized password hash type $type." );
+ }
+
+ $config = $this->types[$type];
+
+ return new $config['class']( $this, $config, $hash );
+ }
+
+ /**
+ * Make a new default password of the given type.
+ *
+ * @param string $type Existing type
+ * @return Password
+ * @throws PasswordError If hash is invalid or type is not recognized
+ */
+ public function newFromType( $type ) {
+ if ( !isset( $this->types[$type] ) ) {
+ throw new PasswordError( "Unrecognized password hash type $type." );
+ }
+
+ $config = $this->types[$type];
+
+ return new $config['class']( $this, $config );
+ }
+
+ /**
+ * Create a new Hash object from a plaintext password
+ *
+ * If no existing object is given, make a new default object. If one is given, clone that
+ * object. Then pass the plaintext to Password::crypt().
+ *
+ * @param string $password Plaintext password
+ * @param Password|null $existing Optional existing hash to get options from
+ * @return Password
+ */
+ public function newFromPlaintext( $password, Password $existing = null ) {
+ if ( $existing === null ) {
+ $config = $this->types[$this->default];
+ $obj = new $config['class']( $this, $config );
+ } else {
+ $obj = clone $existing;
+ }
+
+ $obj->crypt( $password );
+
+ return $obj;
+ }
+
+ /**
+ * Determine whether a password object needs updating
+ *
+ * Check whether the given password is of the default type. If it is,
+ * pass off further needsUpdate checks to Password::needsUpdate.
+ *
+ * @param Password $password
+ *
+ * @return bool True if needs update, false otherwise
+ */
+ public function needsUpdate( Password $password ) {
+ if ( $password->getType() !== $this->default ) {
+ return true;
+ } else {
+ return $password->needsUpdate();
+ }
+ }
+}
diff --git a/includes/password/Pbkdf2Password.php b/includes/password/Pbkdf2Password.php
new file mode 100644
index 00000000..080e3b0d
--- /dev/null
+++ b/includes/password/Pbkdf2Password.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Implements the Pbkdf2Password class for the MediaWiki software.
+ *
+ * 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
+ */
+
+/**
+ * A PBKDF2-hashed password
+ *
+ * This is a computationally complex password hash for use in modern applications.
+ * The number of rounds can be configured by $wgPasswordConfig['pbkdf2']['cost'].
+ *
+ * @since 1.24
+ */
+class Pbkdf2Password extends ParameterizedPassword {
+ protected function getDefaultParams() {
+ return array(
+ 'algo' => $this->config['algo'],
+ 'rounds' => $this->config['cost'],
+ 'length' => $this->config['length']
+ );
+ }
+
+ protected function getDelimiter() {
+ return ':';
+ }
+
+ public function crypt( $password ) {
+ if ( count( $this->args ) == 0 ) {
+ $this->args[] = base64_encode( MWCryptRand::generate( 16, true ) );
+ }
+
+ if ( function_exists( 'hash_pbkdf2' ) ) {
+ $hash = hash_pbkdf2(
+ $this->params['algo'],
+ $password,
+ base64_decode( $this->args[0] ),
+ (int)$this->params['rounds'],
+ (int)$this->params['length'],
+ true
+ );
+ } else {
+ $hashLen = strlen( hash( $this->params['algo'], '', true ) );
+ $blockCount = ceil( $this->params['length'] / $hashLen );
+
+ $hash = '';
+ $salt = base64_decode( $this->args[0] );
+ for ( $i = 1; $i <= $blockCount; ++$i ) {
+ $roundTotal = $lastRound = hash_hmac(
+ $this->params['algo'],
+ $salt . pack( 'N', $i ),
+ $password,
+ true
+ );
+
+ for ( $j = 1; $j < $this->params['rounds']; ++$j ) {
+ $lastRound = hash_hmac( $this->params['algo'], $lastRound, $password, true );
+ $roundTotal ^= $lastRound;
+ }
+
+ $hash .= $roundTotal;
+ }
+
+ $hash = substr( $hash, 0, $this->params['length'] );
+ }
+
+ $this->hash = base64_encode( $hash );
+ }
+}
diff --git a/includes/poolcounter/PoolCounter.php b/includes/poolcounter/PoolCounter.php
new file mode 100644
index 00000000..e77ffd7c
--- /dev/null
+++ b/includes/poolcounter/PoolCounter.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Provides of semaphore semantics for restricting the number
+ * of workers that may be concurrently performing the same task.
+ *
+ * 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
+ */
+
+/**
+ * When you have many workers (threads/servers) giving service, and a
+ * cached item expensive to produce expires, you may get several workers
+ * doing the job at the same time.
+ *
+ * Given enough requests and the item expiring fast (non-cacheable,
+ * lots of edits...) that single work can end up unfairly using most (all)
+ * of the cpu of the pool. This is also known as 'Michael Jackson effect'
+ * since this effect triggered on the english wikipedia on the day Michael
+ * Jackson died, the biographical article got hit with several edits per
+ * minutes and hundreds of read hits.
+ *
+ * The PoolCounter provides semaphore semantics for restricting the number
+ * of workers that may be concurrently performing such single task.
+ *
+ * By default PoolCounter_Stub is used, which provides no locking. You
+ * can get a useful one in the PoolCounter extension.
+ */
+abstract class PoolCounter {
+ /* Return codes */
+ const LOCKED = 1; /* Lock acquired */
+ const RELEASED = 2; /* Lock released */
+ const DONE = 3; /* Another worker did the work for you */
+
+ const ERROR = -1; /* Indeterminate error */
+ const NOT_LOCKED = -2; /* Called release() with no lock held */
+ const QUEUE_FULL = -3; /* There are already maxqueue workers on this lock */
+ const TIMEOUT = -4; /* Timeout exceeded */
+ const LOCK_HELD = -5; /* Cannot acquire another lock while you have one lock held */
+
+ /** @var string All workers with the same key share the lock */
+ protected $key;
+ /** @var int Maximum number of workers working on tasks with the same key simultaneously */
+ protected $workers;
+ /**
+ * Maximum number of workers working on this task type, regardless of key.
+ * 0 means unlimited. Max allowed value is 65536.
+ * The way the slot limit is enforced is overzealous - this option should be used with caution.
+ * @var int
+ */
+ protected $slots = 0;
+ /** @var int If this number of workers are already working/waiting, fail instead of wait */
+ protected $maxqueue;
+ /** @var float Maximum time in seconds to wait for the lock */
+ protected $timeout;
+
+ /**
+ * @param array $conf
+ * @param string $type
+ * @param string $key
+ */
+ protected function __construct( $conf, $type, $key ) {
+ $this->workers = $conf['workers'];
+ $this->maxqueue = $conf['maxqueue'];
+ $this->timeout = $conf['timeout'];
+ if ( isset( $conf['slots'] ) ) {
+ $this->slots = $conf['slots'];
+ }
+
+ if ( $this->slots ) {
+ $key = $this->hashKeyIntoSlots( $key, $this->slots );
+ }
+ $this->key = $key;
+ }
+
+ /**
+ * Create a Pool counter. This should only be called from the PoolWorks.
+ *
+ * @param string $type
+ * @param string $key
+ *
+ * @return PoolCounter
+ */
+ public static function factory( $type, $key ) {
+ global $wgPoolCounterConf;
+ if ( !isset( $wgPoolCounterConf[$type] ) ) {
+ return new PoolCounter_Stub;
+ }
+ $conf = $wgPoolCounterConf[$type];
+ $class = $conf['class'];
+
+ return new $class( $conf, $type, $key );
+ }
+
+ /**
+ * @return string
+ */
+ public function getKey() {
+ return $this->key;
+ }
+
+ /**
+ * I want to do this task and I need to do it myself.
+ *
+ * @return Status Value is one of Locked/Error
+ */
+ abstract public function acquireForMe();
+
+ /**
+ * I want to do this task, but if anyone else does it
+ * instead, it's also fine for me. I will read its cached data.
+ *
+ * @return Status Value is one of Locked/Done/Error
+ */
+ abstract public function acquireForAnyone();
+
+ /**
+ * I have successfully finished my task.
+ * Lets another one grab the lock, and returns the workers
+ * waiting on acquireForAnyone()
+ *
+ * @return Status Value is one of Released/NotLocked/Error
+ */
+ abstract public function release();
+
+ /**
+ * Given a key (any string) and the number of lots, returns a slot number (an integer from the [0..($slots-1)] range).
+ * This is used for a global limit on the number of instances of a given type that can acquire a lock.
+ * The hashing is deterministic so that PoolCounter::$workers is always an upper limit of how many instances with
+ * the same key can acquire a lock.
+ *
+ * @param string $key PoolCounter instance key (any string)
+ * @param int $slots The number of slots (max allowed value is 65536)
+ * @return int
+ */
+ protected function hashKeyIntoSlots( $key, $slots ) {
+ return hexdec( substr( sha1( $key ), 0, 4 ) ) % $slots;
+ }
+}
+
+// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
+class PoolCounter_Stub extends PoolCounter {
+ // @codingStandardsIgnoreEnd
+
+ public function __construct() {
+ /* No parameters needed */
+ }
+
+ public function acquireForMe() {
+ return Status::newGood( PoolCounter::LOCKED );
+ }
+
+ public function acquireForAnyone() {
+ return Status::newGood( PoolCounter::LOCKED );
+ }
+
+ public function release() {
+ return Status::newGood( PoolCounter::RELEASED );
+ }
+}
diff --git a/includes/poolcounter/PoolCounterRedis.php b/includes/poolcounter/PoolCounterRedis.php
new file mode 100644
index 00000000..d609f614
--- /dev/null
+++ b/includes/poolcounter/PoolCounterRedis.php
@@ -0,0 +1,417 @@
+<?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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Version of PoolCounter that uses Redis
+ *
+ * There are four main redis keys used to track each pool counter key:
+ * - poolcounter:l-slots-* : A list of available slot IDs for a pool.
+ * - poolcounter:z-renewtime-* : A sorted set of (slot ID, UNIX timestamp as score)
+ * used for tracking the next time a slot should be
+ * released. This is -1 when a slot is created, and is
+ * set when released (expired), locked, and unlocked.
+ * - poolcounter:z-wait-* : A sorted set of (slot ID, UNIX timestamp as score)
+ * used for tracking waiting processes (and wait time).
+ * - poolcounter:l-wakeup-* : A list pushed to for the sake of waking up processes
+ * when a any process in the pool finishes (lasts for 1ms).
+ * For a given pool key, all the redis keys start off non-existing and are deleted if not
+ * used for a while to prevent garbage from building up on the server. They are atomically
+ * re-initialized as needed. The "z-renewtime" key is used for detecting sessions which got
+ * slots but then disappeared. Stale entries from there have their timestamp updated and the
+ * corresponding slots freed up. The "z-wait" key is used for detecting processes registered
+ * as waiting but that disappeared. Stale entries from there are deleted and the corresponding
+ * slots are freed up. The worker count is included in all the redis key names as it does not
+ * vary within each $wgPoolCounterConf type and doing so handles configuration changes.
+ *
+ * This class requires Redis 2.6 as it makes use Lua scripts for fast atomic operations.
+ * Also this should be on a server plenty of RAM for the working set to avoid evictions.
+ * Evictions could temporarily allow wait queues to double in size or temporarily cause
+ * pools to appear as full when they are not. Using volatile-ttl and bumping memory-samples
+ * in redis.conf can be helpful otherwise.
+ *
+ * @ingroup Redis
+ * @since 1.23
+ */
+class PoolCounterRedis extends PoolCounter {
+ /** @var HashRing */
+ protected $ring;
+ /** @var RedisConnectionPool */
+ protected $pool;
+ /** @var array (server label => host) map */
+ protected $serversByLabel;
+ /** @var string SHA-1 of the key */
+ protected $keySha1;
+ /** @var int TTL for locks to expire (work should finish in this time) */
+ protected $lockTTL;
+
+ /** @var RedisConnRef */
+ protected $conn;
+ /** @var string Pool slot value */
+ protected $slot;
+ /** @var int AWAKE_* constant */
+ protected $onRelease;
+ /** @var string Unique string to identify this process */
+ protected $session;
+ /** @var int UNIX timestamp */
+ protected $slotTime;
+
+ const AWAKE_ONE = 1; // wake-up if when a slot can be taken from an existing process
+ const AWAKE_ALL = 2; // wake-up if an existing process finishes and wake up such others
+
+ /** @var array List of active PoolCounterRedis objects in this script */
+ protected static $active = null;
+
+ function __construct( $conf, $type, $key ) {
+ parent::__construct( $conf, $type, $key );
+
+ $this->serversByLabel = $conf['servers'];
+ $this->ring = new HashRing( array_fill_keys( array_keys( $conf['servers'] ), 100 ) );
+
+ $conf['redisConfig']['serializer'] = 'none'; // for use with Lua
+ $this->pool = RedisConnectionPool::singleton( $conf['redisConfig'] );
+
+ $this->keySha1 = sha1( $this->key );
+ $met = ini_get( 'max_execution_time' ); // usually 0 in CLI mode
+ $this->lockTTL = $met ? 2 * $met : 3600;
+
+ if ( self::$active === null ) {
+ self::$active = array();
+ register_shutdown_function( array( __CLASS__, 'releaseAll' ) );
+ }
+ }
+
+ /**
+ * @return Status Uses RediConnRef as value on success
+ */
+ protected function getConnection() {
+ if ( !isset( $this->conn ) ) {
+ $conn = false;
+ $servers = $this->ring->getLocations( $this->key, 3 );
+ ArrayUtils::consistentHashSort( $servers, $this->key );
+ foreach ( $servers as $server ) {
+ $conn = $this->pool->getConnection( $this->serversByLabel[$server] );
+ if ( $conn ) {
+ break;
+ }
+ }
+ if ( !$conn ) {
+ return Status::newFatal( 'pool-servererror', implode( ', ', $servers ) );
+ }
+ $this->conn = $conn;
+ }
+ return Status::newGood( $this->conn );
+ }
+
+ function acquireForMe() {
+ $section = new ProfileSection( __METHOD__ );
+
+ return $this->waitForSlotOrNotif( self::AWAKE_ONE );
+ }
+
+ function acquireForAnyone() {
+ $section = new ProfileSection( __METHOD__ );
+
+ return $this->waitForSlotOrNotif( self::AWAKE_ALL );
+ }
+
+ function release() {
+ $section = new ProfileSection( __METHOD__ );
+
+ if ( $this->slot === null ) {
+ return Status::newGood( PoolCounter::NOT_LOCKED ); // not locked
+ }
+
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ static $script =
+<<<LUA
+ local kSlots,kSlotsNextRelease,kWakeup,kWaiting = unpack(KEYS)
+ local rMaxWorkers,rExpiry,rSlot,rSlotTime,rAwakeAll,rTime = unpack(ARGV)
+ -- Add the slots back to the list (if rSlot is "w" then it is not a slot).
+ -- Treat the list as expired if the "next release" time sorted-set is missing.
+ if rSlot ~= 'w' and redis.call('exists',kSlotsNextRelease) == 1 then
+ if 1*redis.call('zScore',kSlotsNextRelease,rSlot) ~= (rSlotTime + rExpiry) then
+ -- Slot lock expired and was released already
+ elseif redis.call('lLen',kSlots) >= 1*rMaxWorkers then
+ -- Slots somehow got out of sync; reset the list for sanity
+ redis.call('del',kSlots,kSlotsNextRelease)
+ elseif redis.call('lLen',kSlots) == (1*rMaxWorkers - 1) and redis.call('zCard',kWaiting) == 0 then
+ -- Slot list will be made full; clear it to save space (it re-inits as needed)
+ -- since nothing is waiting on being unblocked by a push to the list
+ redis.call('del',kSlots,kSlotsNextRelease)
+ else
+ -- Add slot back to pool and update the "next release" time
+ redis.call('rPush',kSlots,rSlot)
+ redis.call('zAdd',kSlotsNextRelease,rTime + 30,rSlot)
+ -- Always keep renewing the expiry on use
+ redis.call('expireAt',kSlots,math.ceil(rTime + rExpiry))
+ redis.call('expireAt',kSlotsNextRelease,math.ceil(rTime + rExpiry))
+ end
+ end
+ -- Update an ephemeral list to wake up other clients that can
+ -- reuse any cached work from this process. Only do this if no
+ -- slots are currently free (e.g. clients could be waiting).
+ if 1*rAwakeAll == 1 then
+ local count = redis.call('zCard',kWaiting)
+ for i = 1,count do
+ redis.call('rPush',kWakeup,'w')
+ end
+ redis.call('pexpire',kWakeup,1)
+ end
+ return 1
+LUA;
+ try {
+ $res = $conn->luaEval( $script,
+ array(
+ $this->getSlotListKey(),
+ $this->getSlotRTimeSetKey(),
+ $this->getWakeupListKey(),
+ $this->getWaitSetKey(),
+ $this->workers,
+ $this->lockTTL,
+ $this->slot,
+ $this->slotTime, // used for CAS-style sanity check
+ ( $this->onRelease === self::AWAKE_ALL ) ? 1 : 0,
+ microtime( true )
+ ),
+ 4 # number of first argument(s) that are keys
+ );
+ } catch ( RedisException $e ) {
+ return Status::newFatal( 'pool-error-unknown', $e->getMessage() );
+ }
+
+ $this->slot = null;
+ $this->slotTime = null;
+ $this->onRelease = null;
+ unset( self::$active[$this->session] );
+
+ return Status::newGood( PoolCounter::RELEASED );
+ }
+
+ /**
+ * @param int $doWakeup AWAKE_* constant
+ * @return Status
+ */
+ protected function waitForSlotOrNotif( $doWakeup ) {
+ if ( $this->slot !== null ) {
+ return Status::newGood( PoolCounter::LOCK_HELD ); // already acquired
+ }
+
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ $now = microtime( true );
+ try {
+ $slot = $this->initAndPopPoolSlotList( $conn, $now );
+ if ( ctype_digit( $slot ) ) {
+ // Pool slot acquired by this process
+ $slotTime = $now;
+ } elseif ( $slot === 'QUEUE_FULL' ) {
+ // Too many processes are waiting for pooled processes to finish
+ return Status::newGood( PoolCounter::QUEUE_FULL );
+ } elseif ( $slot === 'QUEUE_WAIT' ) {
+ // This process is now registered as waiting
+ $keys = ( $doWakeup == self::AWAKE_ALL )
+ // Wait for an open slot or wake-up signal (preferring the later)
+ ? array( $this->getWakeupListKey(), $this->getSlotListKey() )
+ // Just wait for an actual pool slot
+ : array( $this->getSlotListKey() );
+
+ $res = $conn->blPop( $keys, $this->timeout );
+ if ( $res === array() ) {
+ $conn->zRem( $this->getWaitSetKey(), $this->session ); // no longer waiting
+ return Status::newGood( PoolCounter::TIMEOUT );
+ }
+
+ $slot = $res[1]; // pool slot or "w" for wake-up notifications
+ $slotTime = microtime( true ); // last microtime() was a few RTTs ago
+ // Unregister this process as waiting and bump slot "next release" time
+ $this->registerAcquisitionTime( $conn, $slot, $slotTime );
+ } else {
+ return Status::newFatal( 'pool-error-unknown', "Server gave slot '$slot'." );
+ }
+ } catch ( RedisException $e ) {
+ return Status::newFatal( 'pool-error-unknown', $e->getMessage() );
+ }
+
+ if ( $slot !== 'w' ) {
+ $this->slot = $slot;
+ $this->slotTime = $slotTime;
+ $this->onRelease = $doWakeup;
+ self::$active[$this->session] = $this;
+ }
+
+ return Status::newGood( $slot === 'w' ? PoolCounter::DONE : PoolCounter::LOCKED );
+ }
+
+ /**
+ * @param RedisConnRef $conn
+ * @param float $now UNIX timestamp
+ * @return string|bool False on failure
+ */
+ protected function initAndPopPoolSlotList( RedisConnRef $conn, $now ) {
+ static $script =
+<<<LUA
+ local kSlots,kSlotsNextRelease,kSlotWaits = unpack(KEYS)
+ local rMaxWorkers,rMaxQueue,rTimeout,rExpiry,rSess,rTime = unpack(ARGV)
+ -- Initialize if the "next release" time sorted-set is empty. The slot key
+ -- itself is empty if all slots are busy or when nothing is initialized.
+ -- If the list is empty but the set is not, then it is the later case.
+ -- For sanity, if the list exists but not the set, then reset everything.
+ if redis.call('exists',kSlotsNextRelease) == 0 then
+ redis.call('del',kSlots)
+ for i = 1,1*rMaxWorkers do
+ redis.call('rPush',kSlots,i)
+ redis.call('zAdd',kSlotsNextRelease,-1,i)
+ end
+ -- Otherwise do maintenance to clean up after network partitions
+ else
+ -- Find stale slot locks and add free them (avoid duplicates for sanity)
+ local staleLocks = redis.call('zRangeByScore',kSlotsNextRelease,0,rTime)
+ for k,slot in ipairs(staleLocks) do
+ redis.call('lRem',kSlots,0,slot)
+ redis.call('rPush',kSlots,slot)
+ redis.call('zAdd',kSlotsNextRelease,rTime + 30,slot)
+ end
+ -- Find stale wait slot entries and remove them
+ redis.call('zRemRangeByScore',kSlotWaits,0,rTime - 2*rTimeout)
+ end
+ local slot
+ -- Try to acquire a slot if possible now
+ if redis.call('lLen',kSlots) > 0 then
+ slot = redis.call('lPop',kSlots)
+ -- Update the slot "next release" time
+ redis.call('zAdd',kSlotsNextRelease,rTime + rExpiry,slot)
+ elseif redis.call('zCard',kSlotWaits) >= 1*rMaxQueue then
+ slot = 'QUEUE_FULL'
+ else
+ slot = 'QUEUE_WAIT'
+ -- Register this process as waiting
+ redis.call('zAdd',kSlotWaits,rTime,rSess)
+ redis.call('expireAt',kSlotWaits,math.ceil(rTime + 2*rTimeout))
+ end
+ -- Always keep renewing the expiry on use
+ redis.call('expireAt',kSlots,math.ceil(rTime + rExpiry))
+ redis.call('expireAt',kSlotsNextRelease,math.ceil(rTime + rExpiry))
+ return slot
+LUA;
+ return $conn->luaEval( $script,
+ array(
+ $this->getSlotListKey(),
+ $this->getSlotRTimeSetKey(),
+ $this->getWaitSetKey(),
+ $this->workers,
+ $this->maxqueue,
+ $this->timeout,
+ $this->lockTTL,
+ $this->session,
+ $now
+ ),
+ 3 # number of first argument(s) that are keys
+ );
+ }
+
+ /**
+ * @param RedisConnRef $conn
+ * @param string $slot
+ * @param float $now
+ * @return int|bool False on failure
+ */
+ protected function registerAcquisitionTime( RedisConnRef $conn, $slot, $now ) {
+ static $script =
+<<<LUA
+ local kSlots,kSlotsNextRelease,kSlotWaits = unpack(KEYS)
+ local rSlot,rExpiry,rSess,rTime = unpack(ARGV)
+ -- If rSlot is 'w' then the client was told to wake up but got no slot
+ if rSlot ~= 'w' then
+ -- Update the slot "next release" time
+ redis.call('zAdd',kSlotsNextRelease,rTime + rExpiry,rSlot)
+ -- Always keep renewing the expiry on use
+ redis.call('expireAt',kSlots,math.ceil(rTime + rExpiry))
+ redis.call('expireAt',kSlotsNextRelease,math.ceil(rTime + rExpiry))
+ end
+ -- Unregister this process as waiting
+ redis.call('zRem',kSlotWaits,rSess)
+ return 1
+LUA;
+ return $conn->luaEval( $script,
+ array(
+ $this->getSlotListKey(),
+ $this->getSlotRTimeSetKey(),
+ $this->getWaitSetKey(),
+ $slot,
+ $this->lockTTL,
+ $this->session,
+ $now
+ ),
+ 3 # number of first argument(s) that are keys
+ );
+ }
+
+ /**
+ * @return string
+ */
+ protected function getSlotListKey() {
+ return "poolcounter:l-slots-{$this->keySha1}-{$this->workers}";
+ }
+
+ /**
+ * @return string
+ */
+ protected function getSlotRTimeSetKey() {
+ return "poolcounter:z-renewtime-{$this->keySha1}-{$this->workers}";
+ }
+
+ /**
+ * @return string
+ */
+ protected function getWaitSetKey() {
+ return "poolcounter:z-wait-{$this->keySha1}-{$this->workers}";
+ }
+
+ /**
+ * @return string
+ */
+ protected function getWakeupListKey() {
+ return "poolcounter:l-wakeup-{$this->keySha1}-{$this->workers}";
+ }
+
+ /**
+ * Try to make sure that locks get released (even with exceptions and fatals)
+ */
+ public static function releaseAll() {
+ foreach ( self::$active as $poolCounter ) {
+ try {
+ if ( $poolCounter->slot !== null ) {
+ $poolCounter->release();
+ }
+ } catch ( Exception $e ) {
+ }
+ }
+ }
+}
diff --git a/includes/poolcounter/PoolCounterWork.php b/includes/poolcounter/PoolCounterWork.php
new file mode 100644
index 00000000..c0be7a1b
--- /dev/null
+++ b/includes/poolcounter/PoolCounterWork.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Provides of semaphore semantics for restricting the number
+ * of workers that may be concurrently performing the same task.
+ *
+ * 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 for dealing with PoolCounters using class members
+ */
+abstract class PoolCounterWork {
+ /** @var string */
+ protected $type = 'generic';
+ /** @var bool */
+ protected $cacheable = false; // does this override getCachedWork() ?
+
+ /**
+ * @param string $type The type of PoolCounter to use
+ * @param string $key Key that identifies the queue this work is placed on
+ */
+ public function __construct( $type, $key ) {
+ $this->type = $type;
+ $this->poolCounter = PoolCounter::factory( $type, $key );
+ }
+
+ /**
+ * Actually perform the work, caching it if needed
+ * @return mixed Work result or false
+ */
+ abstract public function doWork();
+
+ /**
+ * Retrieve the work from cache
+ * @return mixed Work result or false
+ */
+ public function getCachedWork() {
+ return false;
+ }
+
+ /**
+ * A work not so good (eg. expired one) but better than an error
+ * message.
+ * @return mixed Work result or false
+ */
+ public function fallback() {
+ return false;
+ }
+
+ /**
+ * Do something with the error, like showing it to the user.
+ *
+ * @param Status $status
+ *
+ * @return bool
+ */
+ public function error( $status ) {
+ return false;
+ }
+
+ /**
+ * Log an error
+ *
+ * @param Status $status
+ * @return void
+ */
+ public function logError( $status ) {
+ $key = $this->poolCounter->getKey();
+
+ wfDebugLog( 'poolcounter', "Pool key '$key' ({$this->type}): "
+ . $status->getMessage()->inLanguage( 'en' )->useDatabase( false )->text() );
+ }
+
+ /**
+ * Get the result of the work (whatever it is), or the result of the error() function.
+ * This returns the result of the first applicable method that returns a non-false value,
+ * where the methods are checked in the following order:
+ * - a) doWork() : Applies if the work is exclusive or no another process
+ * is doing it, and on the condition that either this process
+ * successfully entered the pool or the pool counter is down.
+ * - b) doCachedWork() : Applies if the work is cacheable and this blocked on another
+ * process which finished the work.
+ * - c) fallback() : Applies for all remaining cases.
+ * If these all fall through (by returning false), then the result of error() is returned.
+ *
+ * @param bool $skipcache
+ * @return mixed
+ */
+ public function execute( $skipcache = false ) {
+ if ( $this->cacheable && !$skipcache ) {
+ $status = $this->poolCounter->acquireForAnyone();
+ } else {
+ $status = $this->poolCounter->acquireForMe();
+ }
+
+ if ( !$status->isOK() ) {
+ // Respond gracefully to complete server breakage: just log it and do the work
+ $this->logError( $status );
+ return $this->doWork();
+ }
+
+ switch ( $status->value ) {
+ case PoolCounter::LOCK_HELD:
+ // Better to ignore nesting pool counter limits than to fail.
+ // Assume that the outer pool limiting is reasonable enough.
+ /* no break */
+ case PoolCounter::LOCKED:
+ $result = $this->doWork();
+ $this->poolCounter->release();
+ return $result;
+
+ case PoolCounter::DONE:
+ $result = $this->getCachedWork();
+ if ( $result === false ) {
+ /* That someone else work didn't serve us.
+ * Acquire the lock for me
+ */
+ return $this->execute( true );
+ }
+ return $result;
+
+ case PoolCounter::QUEUE_FULL:
+ case PoolCounter::TIMEOUT:
+ $result = $this->fallback();
+
+ if ( $result !== false ) {
+ return $result;
+ }
+ /* no break */
+
+ /* These two cases should never be hit... */
+ case PoolCounter::ERROR:
+ default:
+ $errors = array(
+ PoolCounter::QUEUE_FULL => 'pool-queuefull',
+ PoolCounter::TIMEOUT => 'pool-timeout' );
+
+ $status = Status::newFatal( isset( $errors[$status->value] )
+ ? $errors[$status->value]
+ : 'pool-errorunknown' );
+ $this->logError( $status );
+ return $this->error( $status );
+ }
+ }
+}
diff --git a/includes/poolcounter/PoolCounterWorkViaCallback.php b/includes/poolcounter/PoolCounterWorkViaCallback.php
new file mode 100644
index 00000000..af83d2e0
--- /dev/null
+++ b/includes/poolcounter/PoolCounterWorkViaCallback.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Provides of semaphore semantics for restricting the number
+ * of workers that may be concurrently performing the same task.
+ *
+ * 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
+ */
+
+/**
+ * Convenience class for dealing with PoolCounters using callbacks
+ * @since 1.22
+ */
+class PoolCounterWorkViaCallback extends PoolCounterWork {
+ /** @var callable */
+ protected $doWork;
+ /** @var callable|null */
+ protected $doCachedWork;
+ /** @var callable|null */
+ protected $fallback;
+ /** @var callable|null */
+ protected $error;
+
+ /**
+ * Build a PoolCounterWork class from a type, key, and callback map.
+ *
+ * The callback map must at least have a callback for the 'doWork' method.
+ * Additionally, callbacks can be provided for the 'doCachedWork', 'fallback',
+ * and 'error' methods. Methods without callbacks will be no-ops that return false.
+ * If a 'doCachedWork' callback is provided, then execute() may wait for any prior
+ * process in the pool to finish and reuse its cached result.
+ *
+ * @param string $type
+ * @param string $key
+ * @param array $callbacks Map of callbacks
+ * @throws MWException
+ */
+ public function __construct( $type, $key, array $callbacks ) {
+ parent::__construct( $type, $key );
+ foreach ( array( 'doWork', 'doCachedWork', 'fallback', 'error' ) as $name ) {
+ if ( isset( $callbacks[$name] ) ) {
+ if ( !is_callable( $callbacks[$name] ) ) {
+ throw new MWException( "Invalid callback provided for '$name' function." );
+ }
+ $this->$name = $callbacks[$name];
+ }
+ }
+ if ( !isset( $this->doWork ) ) {
+ throw new MWException( "No callback provided for 'doWork' function." );
+ }
+ $this->cacheable = isset( $this->doCachedWork );
+ }
+
+ public function doWork() {
+ return call_user_func_array( $this->doWork, array() );
+ }
+
+ public function getCachedWork() {
+ if ( $this->doCachedWork ) {
+ return call_user_func_array( $this->doCachedWork, array() );
+ }
+ return false;
+ }
+
+ public function fallback() {
+ if ( $this->fallback ) {
+ return call_user_func_array( $this->fallback, array() );
+ }
+ return false;
+ }
+
+ public function error( $status ) {
+ if ( $this->error ) {
+ return call_user_func_array( $this->error, array( $status ) );
+ }
+ return false;
+ }
+}
diff --git a/includes/poolcounter/PoolWorkArticleView.php b/includes/poolcounter/PoolWorkArticleView.php
new file mode 100644
index 00000000..5e7e3912
--- /dev/null
+++ b/includes/poolcounter/PoolWorkArticleView.php
@@ -0,0 +1,208 @@
+<?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
+ */
+
+class PoolWorkArticleView extends PoolCounterWork {
+ /** @var Page */
+ private $page;
+
+ /** @var string */
+ private $cacheKey;
+
+ /** @var int */
+ private $revid;
+
+ /** @var ParserOptions */
+ private $parserOptions;
+
+ /** @var Content|null */
+ private $content = null;
+
+ /** @var ParserOutput|bool */
+ private $parserOutput = false;
+
+ /** @var bool */
+ private $isDirty = false;
+
+ /** @var Status|bool */
+ private $error = false;
+
+ /**
+ * @param Page $page
+ * @param ParserOptions $parserOptions ParserOptions to use for the parse
+ * @param int $revid ID of the revision being parsed.
+ * @param bool $useParserCache Whether to use the parser cache.
+ * operation.
+ * @param Content|string $content Content to parse or null to load it; may
+ * also be given as a wikitext string, for BC.
+ */
+ public function __construct( Page $page, ParserOptions $parserOptions,
+ $revid, $useParserCache, $content = null
+ ) {
+ if ( is_string( $content ) ) { // BC: old style call
+ $modelId = $page->getRevision()->getContentModel();
+ $format = $page->getRevision()->getContentFormat();
+ $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
+ }
+
+ $this->page = $page;
+ $this->revid = $revid;
+ $this->cacheable = $useParserCache;
+ $this->parserOptions = $parserOptions;
+ $this->content = $content;
+ $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
+ parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
+ }
+
+ /**
+ * Get the ParserOutput from this object, or false in case of failure
+ *
+ * @return ParserOutput
+ */
+ public function getParserOutput() {
+ return $this->parserOutput;
+ }
+
+ /**
+ * Get whether the ParserOutput is a dirty one (i.e. expired)
+ *
+ * @return bool
+ */
+ public function getIsDirty() {
+ return $this->isDirty;
+ }
+
+ /**
+ * Get a Status object in case of error or false otherwise
+ *
+ * @return Status|bool
+ */
+ public function getError() {
+ return $this->error;
+ }
+
+ /**
+ * @return bool
+ */
+ public function doWork() {
+ global $wgUseFileCache;
+
+ // @todo several of the methods called on $this->page are not declared in Page, but present
+ // in WikiPage and delegated by Article.
+
+ $isCurrent = $this->revid === $this->page->getLatest();
+
+ if ( $this->content !== null ) {
+ $content = $this->content;
+ } elseif ( $isCurrent ) {
+ // XXX: why use RAW audience here, and PUBLIC (default) below?
+ $content = $this->page->getContent( Revision::RAW );
+ } else {
+ $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
+
+ if ( $rev === null ) {
+ $content = null;
+ } else {
+ // XXX: why use PUBLIC audience here (default), and RAW above?
+ $content = $rev->getContent();
+ }
+ }
+
+ if ( $content === null ) {
+ return false;
+ }
+
+ // Reduce effects of race conditions for slow parses (bug 46014)
+ $cacheTime = wfTimestampNow();
+
+ $time = - microtime( true );
+ $this->parserOutput = $content->getParserOutput(
+ $this->page->getTitle(),
+ $this->revid,
+ $this->parserOptions
+ );
+ $time += microtime( true );
+
+ // Timing hack
+ if ( $time > 3 ) {
+ wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
+ $this->page->getTitle()->getPrefixedDBkey() ) );
+ }
+
+ if ( $this->cacheable && $this->parserOutput->isCacheable() && $isCurrent ) {
+ ParserCache::singleton()->save(
+ $this->parserOutput, $this->page, $this->parserOptions, $cacheTime, $this->revid );
+ }
+
+ // Make sure file cache is not used on uncacheable content.
+ // Output that has magic words in it can still use the parser cache
+ // (if enabled), though it will generally expire sooner.
+ if ( !$this->parserOutput->isCacheable() || $this->parserOutput->containsOldMagic() ) {
+ $wgUseFileCache = false;
+ }
+
+ if ( $isCurrent ) {
+ $this->page->doCascadeProtectionUpdates( $this->parserOutput );
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getCachedWork() {
+ $this->parserOutput = ParserCache::singleton()->get( $this->page, $this->parserOptions );
+
+ if ( $this->parserOutput === false ) {
+ wfDebug( __METHOD__ . ": parser cache miss\n" );
+ return false;
+ } else {
+ wfDebug( __METHOD__ . ": parser cache hit\n" );
+ return true;
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function fallback() {
+ $this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions );
+
+ if ( $this->parserOutput === false ) {
+ wfDebugLog( 'dirty', 'dirty missing' );
+ wfDebug( __METHOD__ . ": no dirty cache\n" );
+ return false;
+ } else {
+ wfDebug( __METHOD__ . ": sending dirty output\n" );
+ wfDebugLog( 'dirty', "dirty output {$this->cacheKey}" );
+ $this->isDirty = true;
+ return true;
+ }
+ }
+
+ /**
+ * @param Status $status
+ * @return bool
+ */
+ public function error( $status ) {
+ $this->error = $status;
+ return false;
+ }
+}
diff --git a/includes/profiler/Profiler.php b/includes/profiler/Profiler.php
index 2282a3af..418b5d48 100644
--- a/includes/profiler/Profiler.php
+++ b/includes/profiler/Profiler.php
@@ -19,16 +19,30 @@
*
* @file
* @ingroup Profiler
- * This file is only included if profiling is enabled
+ * @defgroup Profiler Profiler
*/
/**
- * @defgroup Profiler Profiler
+ * Get system resource usage of current request context.
+ * Invokes the getrusage(2) system call, requesting RUSAGE_SELF if on PHP5
+ * or RUSAGE_THREAD if on HHVM. Returns false if getrusage is not available.
+ *
+ * @since 1.24
+ * @return array|bool Resource usage data or false if no data available.
*/
+function wfGetRusage() {
+ if ( !function_exists( 'getrusage' ) ) {
+ return false;
+ } elseif ( defined ( 'HHVM_VERSION' ) ) {
+ return getrusage( 2 /* RUSAGE_THREAD */ );
+ } else {
+ return getrusage( 0 /* RUSAGE_SELF */ );
+ }
+}
/**
* Begin profiling of a function
- * @param string $functionname name of the function we will profile
+ * @param string $functionname Name of the function we will profile
*/
function wfProfileIn( $functionname ) {
if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
@@ -41,7 +55,7 @@ function wfProfileIn( $functionname ) {
/**
* Stop profiling of a function
- * @param string $functionname name of the function we have profiled
+ * @param string $functionname Name of the function we have profiled
*/
function wfProfileOut( $functionname = 'missing' ) {
if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
@@ -91,45 +105,46 @@ class ProfileSection {
}
/**
+ * Profiler base class that defines the interface and some trivial functionality
+ *
* @ingroup Profiler
- * @todo document
*/
-class Profiler {
- protected $mStack = array(), $mWorkStack = array(), $mCollated = array(),
- $mCalls = array(), $mTotals = array();
- protected $mTimeMetric = 'wall';
- protected $mProfileID = false, $mCollateDone = false, $mTemplated = false;
-
- protected $mDBLockThreshold = 5.0; // float; seconds
- /** @var Array DB/server name => (active trx count,timestamp) */
- protected $mDBTrxHoldingLocks = array();
- /** @var Array DB/server name => list of (method, elapsed time) */
- protected $mDBTrxMethodTimes = array();
+abstract class Profiler {
+ /** @var string|bool Profiler ID for bucketing data */
+ protected $mProfileID = false;
+ /** @var bool Whether MediaWiki is in a SkinTemplate output context */
+ protected $mTemplated = false;
- /** @var Profiler */
- public static $__instance = null; // do not call this outside Profiler and ProfileSection
+ /** @var TransactionProfiler */
+ protected $trxProfiler;
- function __construct( $params ) {
- if ( isset( $params['timeMetric'] ) ) {
- $this->mTimeMetric = $params['timeMetric'];
- }
+ // @codingStandardsIgnoreStart PSR2.Classes.PropertyDeclaration.Underscore
+ /** @var Profiler Do not call this outside Profiler and ProfileSection */
+ public static $__instance = null;
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * @param array $params
+ */
+ public function __construct( array $params ) {
if ( isset( $params['profileID'] ) ) {
$this->mProfileID = $params['profileID'];
}
-
- $this->addInitialStack();
+ $this->trxProfiler = new TransactionProfiler();
}
/**
* Singleton
* @return Profiler
*/
- public static function instance() {
+ final public static function instance() {
if ( self::$__instance === null ) {
global $wgProfiler;
if ( is_array( $wgProfiler ) ) {
if ( !isset( $wgProfiler['class'] ) ) {
$class = 'ProfilerStub';
+ } elseif ( $wgProfiler['class'] === 'Profiler' ) {
+ $class = 'ProfilerStub'; // b/c; don't explode
} else {
$class = $wgProfiler['class'];
}
@@ -137,7 +152,7 @@ class Profiler {
} elseif ( $wgProfiler instanceof Profiler ) {
self::$__instance = $wgProfiler; // back-compat
} else {
- self::$__instance = new ProfilerStub( $wgProfiler );
+ self::$__instance = new ProfilerStub( array() );
}
}
return self::$__instance;
@@ -145,35 +160,41 @@ class Profiler {
/**
* Set the profiler to a specific profiler instance. Mostly for dumpHTML
- * @param $p Profiler object
+ * @param Profiler $p
*/
- public static function setInstance( Profiler $p ) {
+ final public static function setInstance( Profiler $p ) {
self::$__instance = $p;
}
/**
* Return whether this a stub profiler
*
- * @return Boolean
+ * @return bool
*/
- public function isStub() {
- return false;
- }
+ abstract public function isStub();
/**
* Return whether this profiler stores data
*
+ * Called by Parser::braceSubstitution. If true, the parser will not
+ * generate per-title profiling sections, to avoid overloading the
+ * profiling data collector.
+ *
* @see Profiler::logData()
- * @return Boolean
+ * @return bool
*/
- public function isPersistent() {
- return true;
- }
+ abstract public function isPersistent();
+ /**
+ * @param string $id
+ */
public function setProfileID( $id ) {
$this->mProfileID = $id;
}
+ /**
+ * @return string
+ */
public function getProfileID() {
if ( $this->mProfileID === false ) {
return wfWikiID();
@@ -183,76 +204,18 @@ class Profiler {
}
/**
- * Add the inital item in the stack.
- */
- protected function addInitialStack() {
- // Push an entry for the pre-profile setup time onto the stack
- $initial = $this->getInitialTime();
- if ( $initial !== null ) {
- $this->mWorkStack[] = array( '-total', 0, $initial, 0 );
- $this->mStack[] = array( '-setup', 1, $initial, 0, $this->getTime(), 0 );
- } else {
- $this->profileIn( '-total' );
- }
- }
-
- /**
* Called by wfProfieIn()
*
- * @param $functionname String
+ * @param string $functionname
*/
- public function profileIn( $functionname ) {
- global $wgDebugFunctionEntry;
- if ( $wgDebugFunctionEntry ) {
- $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
- }
-
- $this->mWorkStack[] = array( $functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage() );
- }
+ abstract public function profileIn( $functionname );
/**
* Called by wfProfieOut()
*
- * @param $functionname String
- */
- public function profileOut( $functionname ) {
- global $wgDebugFunctionEntry;
- $memory = memory_get_usage();
- $time = $this->getTime();
-
- if ( $wgDebugFunctionEntry ) {
- $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
- }
-
- $bit = array_pop( $this->mWorkStack );
-
- if ( !$bit ) {
- $this->debug( "Profiling error, !\$bit: $functionname\n" );
- } else {
- if ( $functionname == 'close' ) {
- $message = "Profile section ended by close(): {$bit[0]}";
- $this->debug( "$message\n" );
- $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
- } elseif ( $bit[0] != $functionname ) {
- $message = "Profiling error: in({$bit[0]}), out($functionname)";
- $this->debug( "$message\n" );
- $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
- }
- $bit[] = $time;
- $bit[] = $memory;
- $this->mStack[] = $bit;
- $this->updateTrxProfiling( $functionname, $time );
- }
- }
-
- /**
- * Close opened profiling sections
+ * @param string $functionname
*/
- public function close() {
- while ( count( $this->mWorkStack ) ) {
- $this->profileOut( 'close' );
- }
- }
+ abstract public function profileOut( $functionname );
/**
* Mark a DB as in a transaction with one or more writes pending
@@ -261,39 +224,10 @@ class Profiler {
*
* @param string $server DB server
* @param string $db DB name
+ * @param string $id Resource ID string of connection
*/
- public function transactionWritingIn( $server, $db ) {
- $name = "{$server} ({$db})";
- if ( isset( $this->mDBTrxHoldingLocks[$name] ) ) {
- ++$this->mDBTrxHoldingLocks[$name]['refs'];
- } else {
- $this->mDBTrxHoldingLocks[$name] = array( 'refs' => 1, 'start' => microtime( true ) );
- $this->mDBTrxMethodTimes[$name] = array();
- }
- }
-
- /**
- * Register the name and time of a method for slow DB trx detection
- *
- * @param string $method Function name
- * @param float $realtime Wal time ellapsed
- */
- protected function updateTrxProfiling( $method, $realtime ) {
- if ( !$this->mDBTrxHoldingLocks ) {
- return; // short-circuit
- // @TODO: hardcoded check is a tad janky (what about FOR UPDATE?)
- } elseif ( !preg_match( '/^query-m: (?!SELECT)/', $method )
- && $realtime < $this->mDBLockThreshold )
- {
- return; // not a DB master query nor slow enough
- }
- $now = microtime( true );
- foreach ( $this->mDBTrxHoldingLocks as $name => $info ) {
- // Hacky check to exclude entries from before the first TRX write
- if ( ( $now - $realtime ) >= $info['start'] ) {
- $this->mDBTrxMethodTimes[$name][] = array( $method, $realtime );
- }
- }
+ public function transactionWritingIn( $server, $db, $id = '' ) {
+ $this->trxProfiler->transactionWritingIn( $server, $db, $id );
}
/**
@@ -305,144 +239,60 @@ class Profiler {
*
* @param string $server DB server
* @param string $db DB name
+ * @param string $id Resource ID string of connection
*/
- public function transactionWritingOut( $server, $db ) {
- $name = "{$server} ({$db})";
- if ( --$this->mDBTrxHoldingLocks[$name]['refs'] <= 0 ) {
- $slow = false;
- foreach ( $this->mDBTrxMethodTimes[$name] as $info ) {
- list( $method, $realtime ) = $info;
- if ( $realtime >= $this->mDBLockThreshold ) {
- $slow = true;
- break;
- }
- }
- if ( $slow ) {
- $dbs = implode( ', ', array_keys( $this->mDBTrxHoldingLocks ) );
- $msg = "Sub-optimal transaction on DB(s) {$dbs}:\n";
- foreach ( $this->mDBTrxMethodTimes[$name] as $i => $info ) {
- list( $method, $realtime ) = $info;
- $msg .= sprintf( "%d\t%.6f\t%s\n", $i, $realtime, $method );
- }
- wfDebugLog( 'DBPerformance', $msg );
- }
- unset( $this->mDBTrxHoldingLocks[$name] );
- unset( $this->mDBTrxMethodTimes[$name] );
- }
+ public function transactionWritingOut( $server, $db, $id = '' ) {
+ $this->trxProfiler->transactionWritingOut( $server, $db, $id );
}
/**
- * Mark this call as templated or not
- *
- * @param $t Boolean
+ * Close opened profiling sections
*/
- function setTemplated( $t ) {
- $this->mTemplated = $t;
- }
+ abstract public function close();
/**
- * Returns a profiling output to be stored in debug file
- *
- * @return String
+ * Log the data to some store or even the page output
*/
- public function getOutput() {
- global $wgDebugFunctionEntry, $wgProfileCallTree;
- $wgDebugFunctionEntry = false;
-
- if ( !count( $this->mStack ) && !count( $this->mCollated ) ) {
- return "No profiling output\n";
- }
-
- if ( $wgProfileCallTree ) {
- return $this->getCallTree();
- } else {
- return $this->getFunctionReport();
- }
- }
+ abstract public function logData();
/**
- * Returns a tree of function call instead of a list of functions
- * @return string
+ * Mark this call as templated or not
+ *
+ * @param bool $t
*/
- function getCallTree() {
- return implode( '', array_map( array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack ) ) );
+ public function setTemplated( $t ) {
+ $this->mTemplated = $t;
}
/**
- * Recursive function the format the current profiling array into a tree
+ * Returns a profiling output to be stored in debug file
*
- * @param array $stack profiling array
- * @return array
+ * @return string
*/
- function remapCallTree( $stack ) {
- if ( count( $stack ) < 2 ) {
- return $stack;
- }
- $outputs = array();
- for ( $max = count( $stack ) - 1; $max > 0; ) {
- /* Find all items under this entry */
- $level = $stack[$max][1];
- $working = array();
- for ( $i = $max -1; $i >= 0; $i-- ) {
- if ( $stack[$i][1] > $level ) {
- $working[] = $stack[$i];
- } else {
- break;
- }
- }
- $working = $this->remapCallTree( array_reverse( $working ) );
- $output = array();
- foreach ( $working as $item ) {
- array_push( $output, $item );
- }
- array_unshift( $output, $stack[$max] );
- $max = $i;
-
- array_unshift( $outputs, $output );
- }
- $final = array();
- foreach ( $outputs as $output ) {
- foreach ( $output as $item ) {
- $final[] = $item;
- }
- }
- return $final;
- }
+ abstract public function getOutput();
/**
- * Callback to get a formatted line for the call tree
- * @return string
+ * @return array
*/
- function getCallTreeLine( $entry ) {
- list( $fname, $level, $start, /* $x */, $end ) = $entry;
- $delta = $end - $start;
- $space = str_repeat( ' ', $level );
- # The ugly double sprintf is to work around a PHP bug,
- # which has been fixed in recent releases.
- return sprintf( "%10s %s %s\n", trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname );
- }
+ abstract public function getRawData();
/**
* Get the initial time of the request, based either on $wgRequestTime or
* $wgRUstart. Will return null if not able to find data.
*
- * @param string|false $metric metric to use, with the following possibilities:
+ * @param string|bool $metric Metric to use, with the following possibilities:
* - user: User CPU time (without system calls)
* - cpu: Total CPU time (user and system calls)
* - wall (or any other string): elapsed time
* - false (default): will fall back to default metric
* @return float|null
*/
- function getTime( $metric = false ) {
- if ( $metric === false ) {
- $metric = $this->mTimeMetric;
- }
-
- if ( $metric === 'cpu' || $this->mTimeMetric === 'user' ) {
- if ( !function_exists( 'getrusage' ) ) {
+ protected function getTime( $metric = 'wall' ) {
+ if ( $metric === 'cpu' || $metric === 'user' ) {
+ $ru = wfGetRusage();
+ if ( !$ru ) {
return 0;
}
- $ru = getrusage();
$time = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
if ( $metric === 'cpu' ) {
# This is the time of system calls, added to the user time
@@ -459,21 +309,17 @@ class Profiler {
* Get the initial time of the request, based either on $wgRequestTime or
* $wgRUstart. Will return null if not able to find data.
*
- * @param string|false $metric metric to use, with the following possibilities:
+ * @param string|bool $metric Metric to use, with the following possibilities:
* - user: User CPU time (without system calls)
* - cpu: Total CPU time (user and system calls)
* - wall (or any other string): elapsed time
* - false (default): will fall back to default metric
* @return float|null
*/
- protected function getInitialTime( $metric = false ) {
+ protected function getInitialTime( $metric = 'wall' ) {
global $wgRequestTime, $wgRUstart;
- if ( $metric === false ) {
- $metric = $this->mTimeMetric;
- }
-
- if ( $metric === 'cpu' || $this->mTimeMetric === 'user' ) {
+ if ( $metric === 'cpu' || $metric === 'user' ) {
if ( !count( $wgRUstart ) ) {
return null;
}
@@ -494,243 +340,130 @@ class Profiler {
}
}
- protected function collateData() {
- if ( $this->mCollateDone ) {
- return;
- }
- $this->mCollateDone = true;
-
- $this->close();
-
- $this->mCollated = array();
- $this->mCalls = array();
- $this->mMemory = array();
-
- # Estimate profiling overhead
- $profileCount = count( $this->mStack );
- self::calculateOverhead( $profileCount );
-
- # First, subtract the overhead!
- $overheadTotal = $overheadMemory = $overheadInternal = array();
- foreach ( $this->mStack as $entry ) {
- $fname = $entry[0];
- $start = $entry[2];
- $end = $entry[4];
- $elapsed = $end - $start;
- $memory = $entry[5] - $entry[3];
-
- if ( $fname == '-overhead-total' ) {
- $overheadTotal[] = $elapsed;
- $overheadMemory[] = $memory;
- } elseif ( $fname == '-overhead-internal' ) {
- $overheadInternal[] = $elapsed;
- }
- }
- $overheadTotal = $overheadTotal ? array_sum( $overheadTotal ) / count( $overheadInternal ) : 0;
- $overheadMemory = $overheadMemory ? array_sum( $overheadMemory ) / count( $overheadInternal ) : 0;
- $overheadInternal = $overheadInternal ? array_sum( $overheadInternal ) / count( $overheadInternal ) : 0;
-
- # Collate
- foreach ( $this->mStack as $index => $entry ) {
- $fname = $entry[0];
- $start = $entry[2];
- $end = $entry[4];
- $elapsed = $end - $start;
-
- $memory = $entry[5] - $entry[3];
- $subcalls = $this->calltreeCount( $this->mStack, $index );
-
- if ( !preg_match( '/^-overhead/', $fname ) ) {
- # Adjust for profiling overhead (except special values with elapsed=0
- if ( $elapsed ) {
- $elapsed -= $overheadInternal;
- $elapsed -= ( $subcalls * $overheadTotal );
- $memory -= ( $subcalls * $overheadMemory );
- }
- }
-
- if ( !array_key_exists( $fname, $this->mCollated ) ) {
- $this->mCollated[$fname] = 0;
- $this->mCalls[$fname] = 0;
- $this->mMemory[$fname] = 0;
- $this->mMin[$fname] = 1 << 24;
- $this->mMax[$fname] = 0;
- $this->mOverhead[$fname] = 0;
- }
-
- $this->mCollated[$fname] += $elapsed;
- $this->mCalls[$fname]++;
- $this->mMemory[$fname] += $memory;
- $this->mMin[$fname] = min( $this->mMin[$fname], $elapsed );
- $this->mMax[$fname] = max( $this->mMax[$fname], $elapsed );
- $this->mOverhead[$fname] += $subcalls;
- }
-
- $this->mCalls['-overhead-total'] = $profileCount;
- arsort( $this->mCollated, SORT_NUMERIC );
- }
-
/**
- * Returns a list of profiled functions.
+ * Add an entry in the debug log file
*
- * @return string
- */
- function getFunctionReport() {
- $this->collateData();
-
- $width = 140;
- $nameWidth = $width - 65;
- $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
- $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
- $prof = "\nProfiling data\n";
- $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
-
- $total = isset( $this->mCollated['-total'] ) ? $this->mCollated['-total'] : 0;
-
- foreach ( $this->mCollated as $fname => $elapsed ) {
- $calls = $this->mCalls[$fname];
- $percent = $total ? 100. * $elapsed / $total : 0;
- $memory = $this->mMemory[$fname];
- $prof .= sprintf( $format,
- substr( $fname, 0, $nameWidth ),
- $calls,
- (float)( $elapsed * 1000 ),
- (float)( $elapsed * 1000 ) / $calls,
- $percent,
- $memory,
- ( $this->mMin[$fname] * 1000.0 ),
- ( $this->mMax[$fname] * 1000.0 ),
- $this->mOverhead[$fname]
- );
- }
- $prof .= "\nTotal: $total\n\n";
-
- return $prof;
- }
-
- /**
- * Dummy calls to wfProfileIn/wfProfileOut to calculate its overhead
+ * @param string $s String to output
*/
- protected static function calculateOverhead( $profileCount ) {
- wfProfileIn( '-overhead-total' );
- for ( $i = 0; $i < $profileCount; $i++ ) {
- wfProfileIn( '-overhead-internal' );
- wfProfileOut( '-overhead-internal' );
+ protected function debug( $s ) {
+ if ( function_exists( 'wfDebug' ) ) {
+ wfDebug( $s );
}
- wfProfileOut( '-overhead-total' );
}
/**
- * Counts the number of profiled function calls sitting under
- * the given point in the call graph. Not the most efficient algo.
+ * Add an entry in the debug log group
*
- * @param $stack Array:
- * @param $start Integer:
- * @return Integer
- * @private
+ * @param string $group Group to send the message to
+ * @param string $s String to output
*/
- function calltreeCount( $stack, $start ) {
- $level = $stack[$start][1];
- $count = 0;
- for ( $i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i-- ) {
- $count ++;
+ protected function debugGroup( $group, $s ) {
+ if ( function_exists( 'wfDebugLog' ) ) {
+ wfDebugLog( $group, $s );
}
- return $count;
}
+}
+
+/**
+ * Helper class that detects high-contention DB queries via profiling calls
+ *
+ * This class is meant to work with a Profiler, as the later already knows
+ * when methods start and finish (which may take place during transactions).
+ *
+ * @since 1.24
+ */
+class TransactionProfiler {
+ /** @var float Seconds */
+ protected $mDBLockThreshold = 3.0;
+ /** @var array DB/server name => (active trx count, time, DBs involved) */
+ protected $mDBTrxHoldingLocks = array();
+ /** @var array DB/server name => list of (function name, elapsed time) */
+ protected $mDBTrxMethodTimes = array();
/**
- * Log the whole profiling data into the database.
+ * Mark a DB as in a transaction with one or more writes pending
+ *
+ * Note that there can be multiple connections to a single DB.
+ *
+ * @param string $server DB server
+ * @param string $db DB name
+ * @param string $id ID string of transaction
*/
- public function logData() {
- global $wgProfilePerHost, $wgProfileToDatabase;
-
- # Do not log anything if database is readonly (bug 5375)
- if ( wfReadOnly() || !$wgProfileToDatabase ) {
- return;
- }
-
- $dbw = wfGetDB( DB_MASTER );
- if ( !is_object( $dbw ) ) {
- return;
+ public function transactionWritingIn( $server, $db, $id ) {
+ $name = "{$server} ({$db}) (TRX#$id)";
+ if ( isset( $this->mDBTrxHoldingLocks[$name] ) ) {
+ wfDebugLog( 'DBPerformance', "Nested transaction for '$name' - out of sync." );
}
+ $this->mDBTrxHoldingLocks[$name] =
+ array( 'start' => microtime( true ), 'conns' => array() );
+ $this->mDBTrxMethodTimes[$name] = array();
- if ( $wgProfilePerHost ) {
- $pfhost = wfHostname();
- } else {
- $pfhost = '';
+ foreach ( $this->mDBTrxHoldingLocks as $name => &$info ) {
+ $info['conns'][$name] = 1; // track all DBs in transactions for this transaction
}
-
- try {
- $this->collateData();
-
- foreach ( $this->mCollated as $name => $elapsed ) {
- $eventCount = $this->mCalls[$name];
- $timeSum = (float)( $elapsed * 1000 );
- $memorySum = (float)$this->mMemory[$name];
- $name = substr( $name, 0, 255 );
-
- // Kludge
- $timeSum = $timeSum >= 0 ? $timeSum : 0;
- $memorySum = $memorySum >= 0 ? $memorySum : 0;
-
- $dbw->update( 'profiling',
- array(
- "pf_count=pf_count+{$eventCount}",
- "pf_time=pf_time+{$timeSum}",
- "pf_memory=pf_memory+{$memorySum}",
- ),
- array(
- 'pf_name' => $name,
- 'pf_server' => $pfhost,
- ),
- __METHOD__ );
-
- $rc = $dbw->affectedRows();
- if ( $rc == 0 ) {
- $dbw->insert( 'profiling', array( 'pf_name' => $name, 'pf_count' => $eventCount,
- 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
- __METHOD__, array( 'IGNORE' ) );
- }
- // When we upgrade to mysql 4.1, the insert+update
- // can be merged into just a insert with this construct added:
- // "ON DUPLICATE KEY UPDATE ".
- // "pf_count=pf_count + VALUES(pf_count), ".
- // "pf_time=pf_time + VALUES(pf_time)";
- }
- } catch ( DBError $e ) {}
}
/**
- * Get the function name of the current profiling section
- * @return
- */
- function getCurrentSection() {
- $elt = end( $this->mWorkStack );
- return $elt[0];
- }
-
- /**
- * Add an entry in the debug log file
+ * Register the name and time of a method for slow DB trx detection
*
- * @param string $s to output
+ * This method is only to be called by the Profiler class as methods finish
+ *
+ * @param string $method Function name
+ * @param float $realtime Wal time ellapsed
*/
- function debug( $s ) {
- if ( function_exists( 'wfDebug' ) ) {
- wfDebug( $s );
+ public function recordFunctionCompletion( $method, $realtime ) {
+ if ( !$this->mDBTrxHoldingLocks ) {
+ return; // short-circuit
+ // @todo hardcoded check is a tad janky (what about FOR UPDATE?)
+ } elseif ( !preg_match( '/^query-m: (?!SELECT)/', $method )
+ && $realtime < $this->mDBLockThreshold
+ ) {
+ return; // not a DB master query nor slow enough
+ }
+ $now = microtime( true );
+ foreach ( $this->mDBTrxHoldingLocks as $name => $info ) {
+ // Hacky check to exclude entries from before the first TRX write
+ if ( ( $now - $realtime ) >= $info['start'] ) {
+ $this->mDBTrxMethodTimes[$name][] = array( $method, $realtime );
+ }
}
}
/**
- * Get the content type sent out to the client.
- * Used for profilers that output instead of store data.
- * @return string
+ * Mark a DB as no longer in a transaction
+ *
+ * This will check if locks are possibly held for longer than
+ * needed and log any affected transactions to a special DB log.
+ * Note that there can be multiple connections to a single DB.
+ *
+ * @param string $server DB server
+ * @param string $db DB name
+ * @param string $id ID string of transaction
*/
- protected function getContentType() {
- foreach ( headers_list() as $header ) {
- if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) {
- return $m[1];
+ public function transactionWritingOut( $server, $db, $id ) {
+ $name = "{$server} ({$db}) (TRX#$id)";
+ if ( !isset( $this->mDBTrxMethodTimes[$name] ) ) {
+ wfDebugLog( 'DBPerformance', "Detected no transaction for '$name' - out of sync." );
+ return;
+ }
+ $slow = false;
+ foreach ( $this->mDBTrxMethodTimes[$name] as $info ) {
+ $realtime = $info[1];
+ if ( $realtime >= $this->mDBLockThreshold ) {
+ $slow = true;
+ break;
+ }
+ }
+ if ( $slow ) {
+ $dbs = implode( ', ', array_keys( $this->mDBTrxHoldingLocks[$name]['conns'] ) );
+ $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
+ foreach ( $this->mDBTrxMethodTimes[$name] as $i => $info ) {
+ list( $method, $realtime ) = $info;
+ $msg .= sprintf( "%d\t%.6f\t%s\n", $i, $realtime, $method );
}
+ wfDebugLog( 'DBPerformance', $msg );
}
- return null;
+ unset( $this->mDBTrxHoldingLocks[$name] );
+ unset( $this->mDBTrxMethodTimes[$name] );
}
}
diff --git a/includes/profiler/ProfilerMwprof.php b/includes/profiler/ProfilerMwprof.php
new file mode 100644
index 00000000..af3c7741
--- /dev/null
+++ b/includes/profiler/ProfilerMwprof.php
@@ -0,0 +1,256 @@
+<?php
+/**
+ * Profiler class for Mwprof.
+ *
+ * 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
+ */
+
+/**
+ * Profiler class for Mwprof.
+ *
+ * Mwprof is a high-performance MediaWiki profiling data collector, designed to
+ * collect profiling data from multiple hosts running in tandem. This class
+ * serializes profiling samples into MessagePack arrays and sends them to an
+ * Mwprof instance via UDP.
+ *
+ * @see https://github.com/wikimedia/operations-software-mwprof
+ * @since 1.23
+ */
+class ProfilerMwprof extends Profiler {
+ /** @var array Queue of open profile calls with start data */
+ protected $mWorkStack = array();
+
+ /** @var array Map of (function name => aggregate data array) */
+ protected $mCollated = array();
+ /** @var array Cache of a standard broken collation entry */
+ protected $mErrorEntry;
+
+ // Message types
+ const TYPE_SINGLE = 1;
+ const TYPE_RUNNING = 2;
+
+ public function isStub() {
+ return false;
+ }
+
+ public function isPersistent() {
+ return true;
+ }
+
+ /**
+ * Start a profiling section.
+ *
+ * Marks the beginning of the function or code-block that should be time
+ * and logged under some specific name.
+ *
+ * @param string $inName Section to start
+ */
+ public function profileIn( $inName ) {
+ $this->mWorkStack[] = array( $inName, count( $this->mWorkStack ),
+ $this->getTime(), $this->getTime( 'cpu' ), 0 );
+ }
+
+ /**
+ * Close a profiling section.
+ *
+ * Marks the end of the function or code-block that should be timed and
+ * logged under some specific name.
+ *
+ * @param string $outName Section to close
+ */
+ public function profileOut( $outName ) {
+ list( $inName, $inCount, $inWall, $inCpu ) = array_pop( $this->mWorkStack );
+
+ // Check for unbalanced profileIn / profileOut calls.
+ // Bad entries are logged but not sent.
+ if ( $inName !== $outName ) {
+ $this->debugGroup( 'ProfilerUnbalanced', json_encode( array( $inName, $outName ) ) );
+ return;
+ }
+
+ $elapsedCpu = $this->getTime( 'cpu' ) - $inCpu;
+ $elapsedWall = $this->getTime() - $inWall;
+ $this->updateRunningEntry( $outName, $elapsedCpu, $elapsedWall );
+ $this->trxProfiler->recordFunctionCompletion( $outName, $elapsedWall );
+ }
+
+ /**
+ * Update an entry with timing data.
+ *
+ * @param string $name Section name
+ * @param float $elapsedCpu Elapsed CPU time
+ * @param float $elapsedWall Elapsed wall-clock time
+ */
+ public function updateRunningEntry( $name, $elapsedCpu, $elapsedWall ) {
+ // If this is the first measurement for this entry, store plain values.
+ // Many profiled functions will only be called once per request.
+ if ( !isset( $this->mCollated[$name] ) ) {
+ $this->mCollated[$name] = array(
+ 'cpu' => $elapsedCpu,
+ 'wall' => $elapsedWall,
+ 'count' => 1,
+ );
+ return;
+ }
+
+ $entry = &$this->mCollated[$name];
+
+ // If it's the second measurement, convert the plain values to
+ // RunningStat instances, so we can push the incoming values on top.
+ if ( $entry['count'] === 1 ) {
+ $cpu = new RunningStat();
+ $cpu->push( $entry['cpu'] );
+ $entry['cpu'] = $cpu;
+
+ $wall = new RunningStat();
+ $wall->push( $entry['wall'] );
+ $entry['wall'] = $wall;
+ }
+
+ $entry['count']++;
+ $entry['cpu']->push( $elapsedCpu );
+ $entry['wall']->push( $elapsedWall );
+ }
+
+ /**
+ * @return array
+ */
+ public function getRawData() {
+ // This method is called before shutdown in the footer method on Skins.
+ // If some outer methods have not yet called wfProfileOut(), work around
+ // that by clearing anything in the work stack to just the "-total" entry.
+ if ( count( $this->mWorkStack ) > 1 ) {
+ $oldWorkStack = $this->mWorkStack;
+ $this->mWorkStack = array( $this->mWorkStack[0] ); // just the "-total" one
+ } else {
+ $oldWorkStack = null;
+ }
+ $this->close();
+ // If this trick is used, then the old work stack is swapped back afterwards.
+ // This means that logData() will still make use of all the method data since
+ // the missing wfProfileOut() calls should be made by the time it is called.
+ if ( $oldWorkStack ) {
+ $this->mWorkStack = $oldWorkStack;
+ }
+
+ $totalWall = 0.0;
+ $profile = array();
+ foreach ( $this->mCollated as $fname => $data ) {
+ if ( $data['count'] == 1 ) {
+ $profile[] = array(
+ 'name' => $fname,
+ 'calls' => $data['count'],
+ 'elapsed' => $data['wall'] * 1000,
+ 'memory' => 0, // not supported
+ 'min' => $data['wall'] * 1000,
+ 'max' => $data['wall'] * 1000,
+ 'overhead' => 0, // not supported
+ 'periods' => array() // not supported
+ );
+ $totalWall += $data['wall'];
+ } else {
+ $profile[] = array(
+ 'name' => $fname,
+ 'calls' => $data['count'],
+ 'elapsed' => $data['wall']->n * $data['wall']->getMean() * 1000,
+ 'memory' => 0, // not supported
+ 'min' => $data['wall']->min * 1000,
+ 'max' => $data['wall']->max * 1000,
+ 'overhead' => 0, // not supported
+ 'periods' => array() // not supported
+ );
+ $totalWall += $data['wall']->n * $data['wall']->getMean();
+ }
+ }
+ $totalWall = $totalWall * 1000;
+
+ foreach ( $profile as &$item ) {
+ $item['percent'] = $totalWall ? 100 * $item['elapsed'] / $totalWall : 0;
+ }
+
+ return $profile;
+ }
+
+ /**
+ * Serialize profiling data and send to a profiling data aggregator.
+ *
+ * Individual entries are represented as arrays and then encoded using
+ * MessagePack, an efficient binary data-interchange format. Encoded
+ * entries are accumulated into a buffer and sent in batch via UDP to the
+ * profiling data aggregator.
+ */
+ public function logData() {
+ global $wgUDPProfilerHost, $wgUDPProfilerPort;
+
+ $this->close();
+
+ if ( !function_exists( 'socket_create' ) ) {
+ return; // avoid fatal
+ }
+
+ $sock = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
+ socket_connect( $sock, $wgUDPProfilerHost, $wgUDPProfilerPort );
+ $bufferLength = 0;
+ $buffer = '';
+ foreach ( $this->mCollated as $name => $entry ) {
+ $count = $entry['count'];
+ $cpu = $entry['cpu'];
+ $wall = $entry['wall'];
+
+ if ( $count === 1 ) {
+ $data = array( self::TYPE_SINGLE, $name, $cpu, $wall );
+ } else {
+ $data = array( self::TYPE_RUNNING, $name, $count,
+ $cpu->m1, $cpu->m2, $cpu->min, $cpu->max,
+ $wall->m1, $wall->m2, $wall->min, $wall->max );
+ }
+
+ $encoded = MWMessagePack::pack( $data );
+ $length = strlen( $encoded );
+
+ // If adding this entry would cause the size of the buffer to
+ // exceed the standard ethernet MTU size less the UDP header,
+ // send all pending data and reset the buffer. Otherwise, continue
+ // accumulating entries into the current buffer.
+ if ( $length + $bufferLength > 1450 ) {
+ socket_send( $sock, $buffer, $bufferLength, 0 );
+ $buffer = '';
+ $bufferLength = 0;
+ }
+ $buffer .= $encoded;
+ $bufferLength += $length;
+ }
+ if ( $bufferLength !== 0 ) {
+ socket_send( $sock, $buffer, $bufferLength, 0 );
+ }
+ }
+
+ /**
+ * Close opened profiling sections
+ */
+ public function close() {
+ while ( count( $this->mWorkStack ) ) {
+ $this->profileOut( 'close' );
+ }
+ }
+
+ public function getOutput() {
+ return ''; // no report
+ }
+}
diff --git a/includes/profiler/ProfilerSimple.php b/includes/profiler/ProfilerSimple.php
deleted file mode 100644
index 805c60f4..00000000
--- a/includes/profiler/ProfilerSimple.php
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-/**
- * Base class for simple profiling.
- *
- * 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
- */
-
-/**
- * Simple profiler base class.
- * @todo document methods (?)
- * @ingroup Profiler
- */
-class ProfilerSimple extends Profiler {
- var $mMinimumTime = 0;
-
- var $zeroEntry = array( 'cpu' => 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0 );
- var $errorEntry;
-
- public function isPersistent() {
- /* Implement in output subclasses */
- return false;
- }
-
- protected function addInitialStack() {
- $this->errorEntry = $this->zeroEntry;
- $this->errorEntry['count'] = 1;
-
- $initialTime = $this->getInitialTime();
- $initialCpu = $this->getInitialTime( 'cpu' );
- if ( $initialTime !== null && $initialCpu !== null ) {
- $this->mWorkStack[] = array( '-total', 0, $initialTime, $initialCpu );
- $this->mWorkStack[] = array( '-setup', 1, $initialTime, $initialCpu );
-
- $this->profileOut( '-setup' );
- } else {
- $this->profileIn( '-total' );
- }
- }
-
- function setMinimum( $min ) {
- $this->mMinimumTime = $min;
- }
-
- function profileIn( $functionname ) {
- global $wgDebugFunctionEntry;
- if ( $wgDebugFunctionEntry ) {
- $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
- }
- $this->mWorkStack[] = array( $functionname, count( $this->mWorkStack ), $this->getTime(), $this->getTime( 'cpu' ) );
- }
-
- function profileOut( $functionname ) {
- global $wgDebugFunctionEntry;
-
- if ( $wgDebugFunctionEntry ) {
- $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
- }
-
- list( $ofname, /* $ocount */, $ortime, $octime ) = array_pop( $this->mWorkStack );
-
- if ( !$ofname ) {
- $this->debug( "Profiling error: $functionname\n" );
- } else {
- if ( $functionname == 'close' ) {
- $message = "Profile section ended by close(): {$ofname}";
- $functionname = $ofname;
- $this->debug( "$message\n" );
- $this->mCollated[$message] = $this->errorEntry;
- }
- elseif ( $ofname != $functionname ) {
- $message = "Profiling error: in({$ofname}), out($functionname)";
- $this->debug( "$message\n" );
- $this->mCollated[$message] = $this->errorEntry;
- }
- $entry =& $this->mCollated[$functionname];
- $elapsedcpu = $this->getTime( 'cpu' ) - $octime;
- $elapsedreal = $this->getTime() - $ortime;
- if ( !is_array( $entry ) ) {
- $entry = $this->zeroEntry;
- $this->mCollated[$functionname] =& $entry;
- }
- $entry['cpu'] += $elapsedcpu;
- $entry['cpu_sq'] += $elapsedcpu * $elapsedcpu;
- $entry['real'] += $elapsedreal;
- $entry['real_sq'] += $elapsedreal * $elapsedreal;
- $entry['count']++;
-
- $this->updateTrxProfiling( $functionname, $elapsedreal );
- }
- }
-
- public function getFunctionReport() {
- /* Implement in output subclasses */
- return '';
- }
-
- public function logData() {
- /* Implement in subclasses */
- }
-
- /**
- * Get the actual CPU time or the initial one if $ru is set.
- *
- * @deprecated in 1.20
- * @return float|null
- */
- function getCpuTime( $ru = null ) {
- wfDeprecated( __METHOD__, '1.20' );
-
- if ( $ru === null ) {
- return $this->getTime( 'cpu' );
- } else {
- # It theory we should use $ru here, but it always $wgRUstart that is passed here
- return $this->getInitialTime( 'cpu' );
- }
- }
-}
diff --git a/includes/profiler/ProfilerSimpleDB.php b/includes/profiler/ProfilerSimpleDB.php
new file mode 100644
index 00000000..7ef0ad05
--- /dev/null
+++ b/includes/profiler/ProfilerSimpleDB.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Profiler storing information in the DB.
+ *
+ * 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
+ */
+
+/**
+ * $wgProfiler['class'] = 'ProfilerSimpleDB';
+ *
+ * @ingroup Profiler
+ */
+class ProfilerSimpleDB extends ProfilerStandard {
+ protected function collateOnly() {
+ return true;
+ }
+
+ public function isPersistent() {
+ return true;
+ }
+
+ /**
+ * Log the whole profiling data into the database.
+ */
+ public function logData() {
+ global $wgProfilePerHost;
+
+ # Do not log anything if database is readonly (bug 5375)
+ if ( wfReadOnly() ) {
+ return;
+ }
+
+ if ( $wgProfilePerHost ) {
+ $pfhost = wfHostname();
+ } else {
+ $pfhost = '';
+ }
+
+ try {
+ $this->collateData();
+
+ $dbw = wfGetDB( DB_MASTER );
+ $useTrx = ( $dbw->getType() === 'sqlite' ); // much faster
+ if ( $useTrx ) {
+ $dbw->startAtomic( __METHOD__ );
+ }
+ foreach ( $this->mCollated as $name => $data ) {
+ $eventCount = $data['count'];
+ $timeSum = (float)( $data['real'] * 1000 );
+ $memorySum = (float)$data['memory'];
+ $name = substr( $name, 0, 255 );
+
+ // Kludge
+ $timeSum = $timeSum >= 0 ? $timeSum : 0;
+ $memorySum = $memorySum >= 0 ? $memorySum : 0;
+
+ $dbw->update( 'profiling',
+ array(
+ "pf_count=pf_count+{$eventCount}",
+ "pf_time=pf_time+{$timeSum}",
+ "pf_memory=pf_memory+{$memorySum}",
+ ),
+ array(
+ 'pf_name' => $name,
+ 'pf_server' => $pfhost,
+ ),
+ __METHOD__ );
+
+ $rc = $dbw->affectedRows();
+ if ( $rc == 0 ) {
+ $dbw->insert( 'profiling',
+ array(
+ 'pf_name' => $name,
+ 'pf_count' => $eventCount,
+ 'pf_time' => $timeSum,
+ 'pf_memory' => $memorySum,
+ 'pf_server' => $pfhost
+ ),
+ __METHOD__,
+ array( 'IGNORE' )
+ );
+ }
+ // When we upgrade to mysql 4.1, the insert+update
+ // can be merged into just a insert with this construct added:
+ // "ON DUPLICATE KEY UPDATE ".
+ // "pf_count=pf_count + VALUES(pf_count), ".
+ // "pf_time=pf_time + VALUES(pf_time)";
+ }
+ if ( $useTrx ) {
+ $dbw->endAtomic( __METHOD__ );
+ }
+ } catch ( DBError $e ) {
+ }
+ }
+}
diff --git a/includes/profiler/ProfilerSimpleText.php b/includes/profiler/ProfilerSimpleText.php
index 1d57ea8d..0ee7aad2 100644
--- a/includes/profiler/ProfilerSimpleText.php
+++ b/includes/profiler/ProfilerSimpleText.php
@@ -31,7 +31,7 @@
*
* @ingroup Profiler
*/
-class ProfilerSimpleText extends ProfilerSimple {
+class ProfilerSimpleText extends ProfilerStandard {
public $visible = false; /* Show as <PRE> or <!-- ? */
static private $out;
@@ -42,6 +42,10 @@ class ProfilerSimpleText extends ProfilerSimple {
parent::__construct( $profileConfig );
}
+ protected function collateOnly() {
+ return true;
+ }
+
public function logData() {
if ( $this->mTemplated ) {
$this->close();
diff --git a/includes/profiler/ProfilerSimpleTrace.php b/includes/profiler/ProfilerSimpleTrace.php
index 5588d1e2..2a444948 100644
--- a/includes/profiler/ProfilerSimpleTrace.php
+++ b/includes/profiler/ProfilerSimpleTrace.php
@@ -22,63 +22,64 @@
*/
/**
- * Execution trace
+ * Execution trace profiler
* @todo document methods (?)
* @ingroup Profiler
*/
-class ProfilerSimpleTrace extends ProfilerSimple {
- var $trace = "Beginning trace: \n";
- var $memory = 0;
+class ProfilerSimpleTrace extends ProfilerStandard {
+ protected $trace = "Beginning trace: \n";
+ protected $memory = 0;
- function profileIn( $functionname ) {
+ protected function collateOnly() {
+ return true;
+ }
+
+ public function profileIn( $functionname ) {
parent::profileIn( $functionname );
+
$this->trace .= " " . sprintf( "%6.1f", $this->memoryDiff() ) .
- str_repeat( " ", count( $this->mWorkStack ) ) . " > " . $functionname . "\n";
+ str_repeat( " ", count( $this->mWorkStack ) ) . " > " . $functionname . "\n";
}
- function profileOut( $functionname ) {
- global $wgDebugFunctionEntry;
-
- if ( $wgDebugFunctionEntry ) {
- $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
- }
+ public function profileOut( $functionname ) {
+ $item = end( $this->mWorkStack );
- list( $ofname, /* $ocount */, $ortime ) = array_pop( $this->mWorkStack );
+ parent::profileOut( $functionname );
- if ( !$ofname ) {
+ if ( !$item ) {
$this->trace .= "Profiling error: $functionname\n";
} else {
+ list( $ofname, /* $ocount */, $ortime ) = $item;
if ( $functionname == 'close' ) {
$message = "Profile section ended by close(): {$ofname}";
$functionname = $ofname;
$this->trace .= $message . "\n";
- }
- elseif ( $ofname != $functionname ) {
+ } elseif ( $ofname != $functionname ) {
$this->trace .= "Profiling error: in({$ofname}), out($functionname)";
}
$elapsedreal = $this->getTime() - $ortime;
$this->trace .= sprintf( "%03.6f %6.1f", $elapsedreal, $this->memoryDiff() ) .
- str_repeat( " ", count( $this->mWorkStack ) + 1 ) . " < " . $functionname . "\n";
-
- $this->updateTrxProfiling( $functionname, $elapsedreal );
+ str_repeat( " ", count( $this->mWorkStack ) + 1 ) . " < " . $functionname . "\n";
}
}
- function memoryDiff() {
+ protected function memoryDiff() {
$diff = memory_get_usage() - $this->memory;
$this->memory = memory_get_usage();
return $diff / 1024;
}
- function logData() {
- if ( PHP_SAPI === 'cli' ) {
- print "<!-- \n {$this->trace} \n -->";
- } elseif ( $this->getContentType() === 'text/html' ) {
- print "<!-- \n {$this->trace} \n -->";
- } elseif ( $this->getContentType() === 'text/javascript' ) {
- print "\n/*\n {$this->trace}\n*/";
- } elseif ( $this->getContentType() === 'text/css' ) {
- print "\n/*\n {$this->trace}\n*/";
+ public function logData() {
+ if ( $this->mTemplated ) {
+ if ( PHP_SAPI === 'cli' ) {
+ print "<!-- \n {$this->trace} \n -->";
+ } elseif ( $this->getContentType() === 'text/html' ) {
+ print "<!-- \n {$this->trace} \n -->";
+ } elseif ( $this->getContentType() === 'text/javascript' ) {
+ print "\n/*\n {$this->trace}\n*/";
+ } elseif ( $this->getContentType() === 'text/css' ) {
+ print "\n/*\n {$this->trace}\n*/";
+ }
}
}
}
diff --git a/includes/profiler/ProfilerSimpleUDP.php b/includes/profiler/ProfilerSimpleUDP.php
index 0a1f3b10..627b4de2 100644
--- a/includes/profiler/ProfilerSimpleUDP.php
+++ b/includes/profiler/ProfilerSimpleUDP.php
@@ -23,10 +23,15 @@
/**
* ProfilerSimpleUDP class, that sends out messages for 'udpprofile' daemon
- * (the one from mediawiki/trunk/udpprofile SVN )
+ * (the one from
+ * http://git.wikimedia.org/tree/operations%2Fsoftware.git/master/udpprofile)
* @ingroup Profiler
*/
-class ProfilerSimpleUDP extends ProfilerSimple {
+class ProfilerSimpleUDP extends ProfilerStandard {
+ protected function collateOnly() {
+ return true;
+ }
+
public function isPersistent() {
return true;
}
@@ -36,11 +41,6 @@ class ProfilerSimpleUDP extends ProfilerSimple {
$this->close();
- if ( isset( $this->mCollated['-total'] ) && $this->mCollated['-total']['real'] < $this->mMinimumTime ) {
- # Less than minimum, ignore
- return;
- }
-
if ( !function_exists( 'socket_create' ) ) {
# Sockets are not enabled
return;
@@ -58,7 +58,8 @@ class ProfilerSimpleUDP extends ProfilerSimple {
continue;
}
$pfline = sprintf( $wgUDPProfilerFormatString, $this->getProfileID(), $pfdata['count'],
- $pfdata['cpu'], $pfdata['cpu_sq'], $pfdata['real'], $pfdata['real_sq'], $entry );
+ $pfdata['cpu'], $pfdata['cpu_sq'], $pfdata['real'], $pfdata['real_sq'], $entry,
+ $pfdata['memory'] );
$length = strlen( $pfline );
/* printf("<!-- $pfline -->"); */
if ( $length + $plength > 1400 ) {
diff --git a/includes/profiler/ProfilerStandard.php b/includes/profiler/ProfilerStandard.php
new file mode 100644
index 00000000..cc134165
--- /dev/null
+++ b/includes/profiler/ProfilerStandard.php
@@ -0,0 +1,559 @@
+<?php
+/**
+ * Common implementation class for profiling.
+ *
+ * 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
+ */
+
+/**
+ * Standard profiler that tracks real time, cpu time, and memory deltas
+ *
+ * This supports profile reports, the debug toolbar, and high-contention
+ * DB query warnings. This does not persist the profiling data though.
+ *
+ * @ingroup Profiler
+ * @since 1.24
+ */
+class ProfilerStandard extends Profiler {
+ /** @var array List of resolved profile calls with start/end data */
+ protected $mStack = array();
+ /** @var array Queue of open profile calls with start data */
+ protected $mWorkStack = array();
+
+ /** @var array Map of (function name => aggregate data array) */
+ protected $mCollated = array();
+ /** @var bool */
+ protected $mCollateDone = false;
+ /** @var bool */
+ protected $mCollateOnly = false;
+ /** @var array Cache of a standard broken collation entry */
+ protected $mErrorEntry;
+
+ /**
+ * @param array $params
+ */
+ public function __construct( array $params ) {
+ parent::__construct( $params );
+
+ $this->mCollateOnly = $this->collateOnly();
+
+ $this->addInitialStack();
+ }
+
+ /**
+ * Return whether this a stub profiler
+ *
+ * @return bool
+ */
+ public function isStub() {
+ return false;
+ }
+
+ /**
+ * Return whether this profiler stores data
+ *
+ * @see Profiler::logData()
+ * @return bool
+ */
+ public function isPersistent() {
+ return false;
+ }
+
+ /**
+ * Whether to internally just track aggregates and ignore the full stack trace
+ *
+ * Only doing collation saves memory overhead but limits the use of certain
+ * features like that of graph generation for the debug toolbar.
+ *
+ * @return bool
+ */
+ protected function collateOnly() {
+ return false;
+ }
+
+ /**
+ * Add the inital item in the stack.
+ */
+ protected function addInitialStack() {
+ $this->mErrorEntry = $this->getErrorEntry();
+
+ $initialTime = $this->getInitialTime( 'wall' );
+ $initialCpu = $this->getInitialTime( 'cpu' );
+ if ( $initialTime !== null && $initialCpu !== null ) {
+ $this->mWorkStack[] = array( '-total', 0, $initialTime, $initialCpu, 0 );
+ if ( $this->mCollateOnly ) {
+ $this->mWorkStack[] = array( '-setup', 1, $initialTime, $initialCpu, 0 );
+ $this->profileOut( '-setup' );
+ } else {
+ $this->mStack[] = array( '-setup', 1, $initialTime, $initialCpu, 0,
+ $this->getTime( 'wall' ), $this->getTime( 'cpu' ), 0 );
+ }
+ } else {
+ $this->profileIn( '-total' );
+ }
+ }
+
+ /**
+ * @return array Initial collation entry
+ */
+ protected function getZeroEntry() {
+ return array(
+ 'cpu' => 0.0,
+ 'cpu_sq' => 0.0,
+ 'real' => 0.0,
+ 'real_sq' => 0.0,
+ 'memory' => 0,
+ 'count' => 0,
+ 'min_cpu' => 0.0,
+ 'max_cpu' => 0.0,
+ 'min_real' => 0.0,
+ 'max_real' => 0.0,
+ 'periods' => array(), // not filled if mCollateOnly
+ 'overhead' => 0 // not filled if mCollateOnly
+ );
+ }
+
+ /**
+ * @return array Initial collation entry for errors
+ */
+ protected function getErrorEntry() {
+ $entry = $this->getZeroEntry();
+ $entry['count'] = 1;
+ return $entry;
+ }
+
+ /**
+ * Update the collation entry for a given method name
+ *
+ * @param string $name
+ * @param float $elapsedCpu
+ * @param float $elapsedReal
+ * @param int $memChange
+ * @param int $subcalls
+ * @param array|null $period Map of ('start','end','memory','subcalls')
+ */
+ protected function updateEntry(
+ $name, $elapsedCpu, $elapsedReal, $memChange, $subcalls = 0, $period = null
+ ) {
+ $entry =& $this->mCollated[$name];
+ if ( !is_array( $entry ) ) {
+ $entry = $this->getZeroEntry();
+ $this->mCollated[$name] =& $entry;
+ }
+ $entry['cpu'] += $elapsedCpu;
+ $entry['cpu_sq'] += $elapsedCpu * $elapsedCpu;
+ $entry['real'] += $elapsedReal;
+ $entry['real_sq'] += $elapsedReal * $elapsedReal;
+ $entry['memory'] += $memChange > 0 ? $memChange : 0;
+ $entry['count']++;
+ $entry['min_cpu'] = $elapsedCpu < $entry['min_cpu'] ? $elapsedCpu : $entry['min_cpu'];
+ $entry['max_cpu'] = $elapsedCpu > $entry['max_cpu'] ? $elapsedCpu : $entry['max_cpu'];
+ $entry['min_real'] = $elapsedReal < $entry['min_real'] ? $elapsedReal : $entry['min_real'];
+ $entry['max_real'] = $elapsedReal > $entry['max_real'] ? $elapsedReal : $entry['max_real'];
+ // Apply optional fields
+ $entry['overhead'] += $subcalls;
+ if ( $period ) {
+ $entry['periods'][] = $period;
+ }
+ }
+
+ /**
+ * Called by wfProfieIn()
+ *
+ * @param string $functionname
+ */
+ public function profileIn( $functionname ) {
+ global $wgDebugFunctionEntry;
+
+ if ( $wgDebugFunctionEntry ) {
+ $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) .
+ 'Entering ' . $functionname . "\n" );
+ }
+
+ $this->mWorkStack[] = array(
+ $functionname,
+ count( $this->mWorkStack ),
+ $this->getTime( 'time' ),
+ $this->getTime( 'cpu' ),
+ memory_get_usage()
+ );
+ }
+
+ /**
+ * Called by wfProfieOut()
+ *
+ * @param string $functionname
+ */
+ public function profileOut( $functionname ) {
+ global $wgDebugFunctionEntry;
+
+ if ( $wgDebugFunctionEntry ) {
+ $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) .
+ 'Exiting ' . $functionname . "\n" );
+ }
+
+ $item = array_pop( $this->mWorkStack );
+ list( $ofname, /* $ocount */, $ortime, $octime, $omem ) = $item;
+
+ if ( $item === null ) {
+ $this->debugGroup( 'profileerror', "Profiling error: $functionname" );
+ } else {
+ if ( $functionname === 'close' ) {
+ if ( $ofname !== '-total' ) {
+ $message = "Profile section ended by close(): {$ofname}";
+ $this->debugGroup( 'profileerror', $message );
+ if ( $this->mCollateOnly ) {
+ $this->mCollated[$message] = $this->mErrorEntry;
+ } else {
+ $this->mStack[] = array( $message, 0, 0.0, 0.0, 0, 0.0, 0.0, 0 );
+ }
+ }
+ $functionname = $ofname;
+ } elseif ( $ofname !== $functionname ) {
+ $message = "Profiling error: in({$ofname}), out($functionname)";
+ $this->debugGroup( 'profileerror', $message );
+ if ( $this->mCollateOnly ) {
+ $this->mCollated[$message] = $this->mErrorEntry;
+ } else {
+ $this->mStack[] = array( $message, 0, 0.0, 0.0, 0, 0.0, 0.0, 0 );
+ }
+ }
+ $realTime = $this->getTime( 'wall' );
+ $cpuTime = $this->getTime( 'cpu' );
+ if ( $this->mCollateOnly ) {
+ $elapsedcpu = $cpuTime - $octime;
+ $elapsedreal = $realTime - $ortime;
+ $memchange = memory_get_usage() - $omem;
+ $this->updateEntry( $functionname, $elapsedcpu, $elapsedreal, $memchange );
+ } else {
+ $this->mStack[] = array_merge( $item,
+ array( $realTime, $cpuTime, memory_get_usage() ) );
+ }
+ $this->trxProfiler->recordFunctionCompletion( $functionname, $realTime - $ortime );
+ }
+ }
+
+ /**
+ * Close opened profiling sections
+ */
+ public function close() {
+ while ( count( $this->mWorkStack ) ) {
+ $this->profileOut( 'close' );
+ }
+ }
+
+ /**
+ * Log the data to some store or even the page output
+ */
+ public function logData() {
+ /* Implement in subclasses */
+ }
+
+ /**
+ * Returns a profiling output to be stored in debug file
+ *
+ * @return string
+ */
+ public function getOutput() {
+ global $wgDebugFunctionEntry, $wgProfileCallTree;
+
+ $wgDebugFunctionEntry = false; // hack
+
+ if ( !count( $this->mStack ) && !count( $this->mCollated ) ) {
+ return "No profiling output\n";
+ }
+
+ if ( $wgProfileCallTree ) {
+ return $this->getCallTree();
+ } else {
+ return $this->getFunctionReport();
+ }
+ }
+
+ /**
+ * Returns a tree of function call instead of a list of functions
+ * @return string
+ */
+ protected function getCallTree() {
+ return implode( '', array_map(
+ array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack )
+ ) );
+ }
+
+ /**
+ * Recursive function the format the current profiling array into a tree
+ *
+ * @param array $stack Profiling array
+ * @return array
+ */
+ protected function remapCallTree( array $stack ) {
+ if ( count( $stack ) < 2 ) {
+ return $stack;
+ }
+ $outputs = array();
+ for ( $max = count( $stack ) - 1; $max > 0; ) {
+ /* Find all items under this entry */
+ $level = $stack[$max][1];
+ $working = array();
+ for ( $i = $max -1; $i >= 0; $i-- ) {
+ if ( $stack[$i][1] > $level ) {
+ $working[] = $stack[$i];
+ } else {
+ break;
+ }
+ }
+ $working = $this->remapCallTree( array_reverse( $working ) );
+ $output = array();
+ foreach ( $working as $item ) {
+ array_push( $output, $item );
+ }
+ array_unshift( $output, $stack[$max] );
+ $max = $i;
+
+ array_unshift( $outputs, $output );
+ }
+ $final = array();
+ foreach ( $outputs as $output ) {
+ foreach ( $output as $item ) {
+ $final[] = $item;
+ }
+ }
+ return $final;
+ }
+
+ /**
+ * Callback to get a formatted line for the call tree
+ * @param array $entry
+ * @return string
+ */
+ protected function getCallTreeLine( $entry ) {
+ list( $fname, $level, $startreal, , , $endreal ) = $entry;
+ $delta = $endreal - $startreal;
+ $space = str_repeat( ' ', $level );
+ # The ugly double sprintf is to work around a PHP bug,
+ # which has been fixed in recent releases.
+ return sprintf( "%10s %s %s\n",
+ trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname );
+ }
+
+ /**
+ * Populate mCollated
+ */
+ protected function collateData() {
+ if ( $this->mCollateDone ) {
+ return;
+ }
+ $this->mCollateDone = true;
+ $this->close(); // set "-total" entry
+
+ if ( $this->mCollateOnly ) {
+ return; // already collated as methods exited
+ }
+
+ $this->mCollated = array();
+
+ # Estimate profiling overhead
+ $profileCount = count( $this->mStack );
+ self::calculateOverhead( $profileCount );
+
+ # First, subtract the overhead!
+ $overheadTotal = $overheadMemory = $overheadInternal = array();
+ foreach ( $this->mStack as $entry ) {
+ // $entry is (name,pos,rtime0,cputime0,mem0,rtime1,cputime1,mem1)
+ $fname = $entry[0];
+ $elapsed = $entry[5] - $entry[2];
+ $memchange = $entry[7] - $entry[4];
+
+ if ( $fname === '-overhead-total' ) {
+ $overheadTotal[] = $elapsed;
+ $overheadMemory[] = max( 0, $memchange );
+ } elseif ( $fname === '-overhead-internal' ) {
+ $overheadInternal[] = $elapsed;
+ }
+ }
+ $overheadTotal = $overheadTotal ?
+ array_sum( $overheadTotal ) / count( $overheadInternal ) : 0;
+ $overheadMemory = $overheadMemory ?
+ array_sum( $overheadMemory ) / count( $overheadInternal ) : 0;
+ $overheadInternal = $overheadInternal ?
+ array_sum( $overheadInternal ) / count( $overheadInternal ) : 0;
+
+ # Collate
+ foreach ( $this->mStack as $index => $entry ) {
+ // $entry is (name,pos,rtime0,cputime0,mem0,rtime1,cputime1,mem1)
+ $fname = $entry[0];
+ $elapsedCpu = $entry[6] - $entry[3];
+ $elapsedReal = $entry[5] - $entry[2];
+ $memchange = $entry[7] - $entry[4];
+ $subcalls = $this->calltreeCount( $this->mStack, $index );
+
+ if ( substr( $fname, 0, 9 ) !== '-overhead' ) {
+ # Adjust for profiling overhead (except special values with elapsed=0
+ if ( $elapsed ) {
+ $elapsed -= $overheadInternal;
+ $elapsed -= ( $subcalls * $overheadTotal );
+ $memchange -= ( $subcalls * $overheadMemory );
+ }
+ }
+
+ $period = array( 'start' => $entry[2], 'end' => $entry[5],
+ 'memory' => $memchange, 'subcalls' => $subcalls );
+ $this->updateEntry( $fname, $elapsedCpu, $elapsedReal, $memchange, $subcalls, $period );
+ }
+
+ $this->mCollated['-overhead-total']['count'] = $profileCount;
+ arsort( $this->mCollated, SORT_NUMERIC );
+ }
+
+ /**
+ * Returns a list of profiled functions.
+ *
+ * @return string
+ */
+ protected function getFunctionReport() {
+ $this->collateData();
+
+ $width = 140;
+ $nameWidth = $width - 65;
+ $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
+ $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
+ $prof = "\nProfiling data\n";
+ $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
+
+ $total = isset( $this->mCollated['-total'] )
+ ? $this->mCollated['-total']['real']
+ : 0;
+
+ foreach ( $this->mCollated as $fname => $data ) {
+ $calls = $data['count'];
+ $percent = $total ? 100 * $data['real'] / $total : 0;
+ $memory = $data['memory'];
+ $prof .= sprintf( $format,
+ substr( $fname, 0, $nameWidth ),
+ $calls,
+ (float)( $data['real'] * 1000 ),
+ (float)( $data['real'] * 1000 ) / $calls,
+ $percent,
+ $memory,
+ ( $data['min_real'] * 1000.0 ),
+ ( $data['max_real'] * 1000.0 ),
+ $data['overhead']
+ );
+ }
+ $prof .= "\nTotal: $total\n\n";
+
+ return $prof;
+ }
+
+ /**
+ * @return array
+ */
+ public function getRawData() {
+ // This method is called before shutdown in the footer method on Skins.
+ // If some outer methods have not yet called wfProfileOut(), work around
+ // that by clearing anything in the work stack to just the "-total" entry.
+ // Collate after doing this so the results do not include profile errors.
+ if ( count( $this->mWorkStack ) > 1 ) {
+ $oldWorkStack = $this->mWorkStack;
+ $this->mWorkStack = array( $this->mWorkStack[0] ); // just the "-total" one
+ } else {
+ $oldWorkStack = null;
+ }
+ $this->collateData();
+ // If this trick is used, then the old work stack is swapped back afterwards
+ // and mCollateDone is reset to false. This means that logData() will still
+ // make use of all the method data since the missing wfProfileOut() calls
+ // should be made by the time it is called.
+ if ( $oldWorkStack ) {
+ $this->mWorkStack = $oldWorkStack;
+ $this->mCollateDone = false;
+ }
+
+ $total = isset( $this->mCollated['-total'] )
+ ? $this->mCollated['-total']['real']
+ : 0;
+
+ $profile = array();
+ foreach ( $this->mCollated as $fname => $data ) {
+ $periods = array();
+ foreach ( $data['periods'] as $period ) {
+ $period['start'] *= 1000;
+ $period['end'] *= 1000;
+ $periods[] = $period;
+ }
+ $profile[] = array(
+ 'name' => $fname,
+ 'calls' => $data['count'],
+ 'elapsed' => $data['real'] * 1000,
+ 'percent' => $total ? 100 * $data['real'] / $total : 0,
+ 'memory' => $data['memory'],
+ 'min' => $data['min_real'] * 1000,
+ 'max' => $data['max_real'] * 1000,
+ 'overhead' => $data['overhead'],
+ 'periods' => $periods
+ );
+ }
+
+ return $profile;
+ }
+
+ /**
+ * Dummy calls to wfProfileIn/wfProfileOut to calculate its overhead
+ * @param int $profileCount
+ */
+ protected static function calculateOverhead( $profileCount ) {
+ wfProfileIn( '-overhead-total' );
+ for ( $i = 0; $i < $profileCount; $i++ ) {
+ wfProfileIn( '-overhead-internal' );
+ wfProfileOut( '-overhead-internal' );
+ }
+ wfProfileOut( '-overhead-total' );
+ }
+
+ /**
+ * Counts the number of profiled function calls sitting under
+ * the given point in the call graph. Not the most efficient algo.
+ *
+ * @param array $stack
+ * @param int $start
+ * @return int
+ */
+ protected function calltreeCount( $stack, $start ) {
+ $level = $stack[$start][1];
+ $count = 0;
+ for ( $i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i-- ) {
+ $count ++;
+ }
+ return $count;
+ }
+
+ /**
+ * Get the content type sent out to the client.
+ * Used for profilers that output instead of store data.
+ * @return string
+ */
+ protected function getContentType() {
+ foreach ( headers_list() as $header ) {
+ if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) {
+ return $m[1];
+ }
+ }
+ return null;
+ }
+}
diff --git a/includes/profiler/ProfilerStub.php b/includes/profiler/ProfilerStub.php
index 3697f352..1d3b65d2 100644
--- a/includes/profiler/ProfilerStub.php
+++ b/includes/profiler/ProfilerStub.php
@@ -30,15 +30,37 @@ class ProfilerStub extends Profiler {
public function isStub() {
return true;
}
+
public function isPersistent() {
return false;
}
- public function profileIn( $fn ) {}
- public function profileOut( $fn ) {}
- public function getOutput() {}
- public function close() {}
- public function logData() {}
- public function getCurrentSection() { return ''; }
- public function transactionWritingIn( $server, $db ) {}
- public function transactionWritingOut( $server, $db ) {}
+
+ public function profileIn( $fn ) {
+ }
+
+ public function profileOut( $fn ) {
+ }
+
+ public function getOutput() {
+ }
+
+ public function close() {
+ }
+
+ public function logData() {
+ }
+
+ public function getCurrentSection() {
+ return '';
+ }
+
+ public function transactionWritingIn( $server, $db, $id = '' ) {
+ }
+
+ public function transactionWritingOut( $server, $db, $id = '' ) {
+ }
+
+ public function getRawData() {
+ return array();
+ }
}
diff --git a/includes/rcfeed/IRCColourfulRCFeedFormatter.php b/includes/rcfeed/IRCColourfulRCFeedFormatter.php
index 507369f3..02a8d7eb 100644
--- a/includes/rcfeed/IRCColourfulRCFeedFormatter.php
+++ b/includes/rcfeed/IRCColourfulRCFeedFormatter.php
@@ -1,11 +1,36 @@
<?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
+ */
+
+/**
+ * Generates a colourful notification intended for humans on IRC.
+ *
+ * @since 1.22
+ */
+
class IRCColourfulRCFeedFormatter implements RCFeedFormatter {
/**
- * Generates a colourful notification intended for humans on IRC.
* @see RCFeedFormatter::getLine
*/
public function getLine( array $feed, RecentChange $rc, $actionComment ) {
- global $wgUseRCPatrol, $wgUseNPPatrol, $wgLocalInterwiki,
+ global $wgUseRCPatrol, $wgUseNPPatrol, $wgLocalInterwikis,
$wgCanonicalServer, $wgScript;
$attribs = $rc->getAttributes();
if ( $attribs['rc_type'] == RC_LOG ) {
@@ -31,7 +56,7 @@ class IRCColourfulRCFeedFormatter implements RCFeedFormatter {
$query .= '&rcid=' . $attribs['rc_id'];
}
// HACK: We need this hook for WMF's secure server setup
- wfRunHooks( 'IRCLineURL', array( &$url, &$query ) );
+ wfRunHooks( 'IRCLineURL', array( &$url, &$query, $rc ) );
$url .= $query;
}
@@ -52,19 +77,27 @@ class IRCColourfulRCFeedFormatter implements RCFeedFormatter {
if ( $attribs['rc_type'] == RC_LOG ) {
$targetText = $rc->getTitle()->getPrefixedText();
- $comment = self::cleanupForIRC( str_replace( "[[$targetText]]", "[[\00302$targetText\00310]]", $actionComment ) );
+ $comment = self::cleanupForIRC( str_replace(
+ "[[$targetText]]",
+ "[[\00302$targetText\00310]]",
+ $actionComment
+ ) );
$flag = $attribs['rc_log_action'];
} else {
$comment = self::cleanupForIRC( $attribs['rc_comment'] );
$flag = '';
- if ( !$attribs['rc_patrolled'] && ( $wgUseRCPatrol || $attribs['rc_type'] == RC_NEW && $wgUseNPPatrol ) ) {
+ if ( !$attribs['rc_patrolled']
+ && ( $wgUseRCPatrol || $attribs['rc_type'] == RC_NEW && $wgUseNPPatrol )
+ ) {
$flag .= '!';
}
- $flag .= ( $attribs['rc_type'] == RC_NEW ? "N" : "" ) . ( $attribs['rc_minor'] ? "M" : "" ) . ( $attribs['rc_bot'] ? "B" : "" );
+ $flag .= ( $attribs['rc_type'] == RC_NEW ? "N" : "" )
+ . ( $attribs['rc_minor'] ? "M" : "" ) . ( $attribs['rc_bot'] ? "B" : "" );
}
- if ( $feed['add_interwiki_prefix'] === true && $wgLocalInterwiki !== false ) {
- $prefix = $wgLocalInterwiki;
+ if ( $feed['add_interwiki_prefix'] === true && $wgLocalInterwikis ) {
+ // we use the first entry in $wgLocalInterwikis in recent changes feeds
+ $prefix = $wgLocalInterwikis[0];
} elseif ( $feed['add_interwiki_prefix'] ) {
$prefix = $feed['add_interwiki_prefix'];
} else {
diff --git a/includes/rcfeed/JSONRCFeedFormatter.php b/includes/rcfeed/JSONRCFeedFormatter.php
index f4cb9921..98d3f025 100644
--- a/includes/rcfeed/JSONRCFeedFormatter.php
+++ b/includes/rcfeed/JSONRCFeedFormatter.php
@@ -1,90 +1,32 @@
<?php
-class JSONRCFeedFormatter implements RCFeedFormatter {
- /**
- * Generates a notification that can be easily interpreted by a machine.
- * @see RCFeedFormatter::getLine
- */
- public function getLine( array $feed, RecentChange $rc, $actionComment ) {
- global $wgCanonicalServer, $wgScriptPath, $wgDBname;
- $attrib = $rc->getAttributes();
-
- $packet = array(
- // Usually, RC ID is exposed only for patrolling purposes,
- // but there is no real reason not to expose it in other cases,
- // and I can see how this may be potentially useful for clients.
- 'id' => $attrib['rc_id'],
- 'type' => $attrib['rc_type'],
- 'namespace' => $rc->getTitle()->getNamespace(),
- 'title' => $rc->getTitle()->getPrefixedText(),
- 'comment' => $attrib['rc_comment'],
- 'timestamp' => (int)wfTimestamp( TS_UNIX, $attrib['rc_timestamp'] ),
- 'user' => $attrib['rc_user_text'],
- 'bot' => (bool)$attrib['rc_bot'],
- );
-
- if ( isset( $feed['channel'] ) ) {
- $packet['channel'] = $feed['channel'];
- }
-
- $type = $attrib['rc_type'];
- if ( $type == RC_EDIT || $type == RC_NEW ) {
- global $wgUseRCPatrol, $wgUseNPPatrol;
-
- $packet['minor'] = $attrib['rc_minor'];
- if ( $wgUseRCPatrol || ( $type == RC_NEW && $wgUseNPPatrol ) ) {
- $packet['patrolled'] = $attrib['rc_patrolled'];
- }
- }
-
- switch ( $type ) {
- case RC_EDIT:
- $packet['length'] = array( 'old' => $attrib['rc_old_len'], 'new' => $attrib['rc_new_len'] );
- $packet['revision'] = array( 'old' => $attrib['rc_last_oldid'], 'new' => $attrib['rc_this_oldid'] );
- break;
-
- case RC_NEW:
- $packet['length'] = array( 'old' => NULL, 'new' => $attrib['rc_new_len'] );
- $packet['revision'] = array( 'old' => NULL, 'new' => $attrib['rc_this_oldid'] );
- break;
-
- case RC_LOG:
- $packet['log_type'] = $attrib['rc_log_type'];
- $packet['log_action'] = $attrib['rc_log_action'];
- if ( $attrib['rc_params'] ) {
- wfSuppressWarnings();
- $params = unserialize( $attrib['rc_params'] );
- wfRestoreWarnings();
- if (
- // If it's an actual serialised false...
- $attrib['rc_params'] == serialize( false ) ||
- // Or if we did not get false back when trying to unserialise
- $params !== false
- ) {
- // From ApiQueryLogEvents::addLogParams
- $logParams = array();
- // Keys like "4::paramname" can't be used for output so we change them to "paramname"
- foreach ( $params as $key => $value ) {
- if ( strpos( $key, ':' ) === false ) {
- $logParams[$key] = $value;
- continue;
- }
- $logParam = explode( ':', $key, 3 );
- $logParams[$logParam[2]] = $value;
- }
- $packet['log_params'] = $logParams;
- } else {
- $packet['log_params'] = explode( "\n", $attrib['rc_params'] );
- }
- }
- $packet['log_action_comment'] = $actionComment;
- break;
- }
-
- $packet['server_url'] = $wgCanonicalServer;
- $packet['server_script_path'] = $wgScriptPath ?: '/';
- $packet['wiki'] = $wgDBname;
-
+/**
+ * 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
+ */
+
+/**
+ * Formats a notification into the JSON format (http://www.json.org)
+ *
+ * @since 1.22
+ */
+class JSONRCFeedFormatter extends MachineReadableRCFeedFormatter {
+
+ protected function formatArray( array $packet ) {
return FormatJson::encode( $packet );
}
}
diff --git a/includes/rcfeed/MachineReadableRCFeedFormatter.php b/includes/rcfeed/MachineReadableRCFeedFormatter.php
new file mode 100644
index 00000000..519606ca
--- /dev/null
+++ b/includes/rcfeed/MachineReadableRCFeedFormatter.php
@@ -0,0 +1,130 @@
+<?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
+ */
+
+/**
+ * Abstract class so there can be multiple formatters outputting the same data
+ *
+ * @since 1.23
+ */
+abstract class MachineReadableRCFeedFormatter implements RCFeedFormatter {
+
+ /**
+ * Take the packet and return the formatted string
+ * @param array $packet
+ * @return string
+ */
+ abstract protected function formatArray( array $packet );
+
+ /**
+ * Generates a notification that can be easily interpreted by a machine.
+ * @see RCFeedFormatter::getLine
+ */
+ public function getLine( array $feed, RecentChange $rc, $actionComment ) {
+ global $wgCanonicalServer, $wgServerName, $wgScriptPath;
+
+ $packet = array(
+ // Usually, RC ID is exposed only for patrolling purposes,
+ // but there is no real reason not to expose it in other cases,
+ // and I can see how this may be potentially useful for clients.
+ 'id' => $rc->getAttribute( 'rc_id' ),
+ 'type' => RecentChange::parseFromRCType( $rc->getAttribute( 'rc_type' ) ),
+ 'namespace' => $rc->getTitle()->getNamespace(),
+ 'title' => $rc->getTitle()->getPrefixedText(),
+ 'comment' => $rc->getAttribute( 'rc_comment' ),
+ 'timestamp' => (int)wfTimestamp( TS_UNIX, $rc->getAttribute( 'rc_timestamp' ) ),
+ 'user' => $rc->getAttribute( 'rc_user_text' ),
+ 'bot' => (bool)$rc->getAttribute( 'rc_bot' ),
+ );
+
+ if ( isset( $feed['channel'] ) ) {
+ $packet['channel'] = $feed['channel'];
+ }
+
+ $type = $rc->getAttribute( 'rc_type' );
+ if ( $type == RC_EDIT || $type == RC_NEW ) {
+ global $wgUseRCPatrol, $wgUseNPPatrol;
+
+ $packet['minor'] = (bool)$rc->getAttribute( 'rc_minor' );
+ if ( $wgUseRCPatrol || ( $type == RC_NEW && $wgUseNPPatrol ) ) {
+ $packet['patrolled'] = (bool)$rc->getAttribute( 'rc_patrolled' );
+ }
+ }
+
+ switch ( $type ) {
+ case RC_EDIT:
+ $packet['length'] = array(
+ 'old' => $rc->getAttribute( 'rc_old_len' ),
+ 'new' => $rc->getAttribute( 'rc_new_len' )
+ );
+ $packet['revision'] = array(
+ 'old' => $rc->getAttribute( 'rc_last_oldid' ),
+ 'new' => $rc->getAttribute( 'rc_this_oldid' )
+ );
+ break;
+
+ case RC_NEW:
+ $packet['length'] = array( 'old' => null, 'new' => $rc->getAttribute( 'rc_new_len' ) );
+ $packet['revision'] = array( 'old' => null, 'new' => $rc->getAttribute( 'rc_this_oldid' ) );
+ break;
+
+ case RC_LOG:
+ $packet['log_id'] = $rc->getAttribute( 'rc_logid' );
+ $packet['log_type'] = $rc->getAttribute( 'rc_log_type' );
+ $packet['log_action'] = $rc->getAttribute( 'rc_log_action' );
+ if ( $rc->getAttribute( 'rc_params' ) ) {
+ wfSuppressWarnings();
+ $params = unserialize( $rc->getAttribute( 'rc_params' ) );
+ wfRestoreWarnings();
+ if (
+ // If it's an actual serialised false...
+ $rc->getAttribute( 'rc_params' ) == serialize( false ) ||
+ // Or if we did not get false back when trying to unserialise
+ $params !== false
+ ) {
+ // From ApiQueryLogEvents::addLogParams
+ $logParams = array();
+ // Keys like "4::paramname" can't be used for output so we change them to "paramname"
+ foreach ( $params as $key => $value ) {
+ if ( strpos( $key, ':' ) === false ) {
+ $logParams[$key] = $value;
+ continue;
+ }
+ $logParam = explode( ':', $key, 3 );
+ $logParams[$logParam[2]] = $value;
+ }
+ $packet['log_params'] = $logParams;
+ } else {
+ $packet['log_params'] = explode( "\n", $rc->getAttribute( 'rc_params' ) );
+ }
+ }
+ $packet['log_action_comment'] = $actionComment;
+ break;
+ }
+
+ $packet['server_url'] = $wgCanonicalServer;
+ $packet['server_name'] = $wgServerName;
+
+ $packet['server_script_path'] = $wgScriptPath ?: '/';
+ $packet['wiki'] = wfWikiID();
+
+ return $this->formatArray( $packet );
+ }
+}
diff --git a/includes/rcfeed/RCFeedEngine.php b/includes/rcfeed/RCFeedEngine.php
index f733bcb7..0b0cd869 100644
--- a/includes/rcfeed/RCFeedEngine.php
+++ b/includes/rcfeed/RCFeedEngine.php
@@ -1,12 +1,37 @@
<?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
+ */
+
+/**
+ * Interface for RC feed engines, which send formatted notifications
+ *
+ * @since 1.22
+ */
interface RCFeedEngine {
/**
* Sends some text to the specified live feed.
*
- * @see RecentChange::cleanupForIRC
- * @param array $feed The feed, as configured in an associative array.
- * @param string $line The text to send.
- * @return boolean success
+ * @see IRCColourfulRCFeedFormatter::cleanupForIRC
+ * @param array $feed The feed, as configured in an associative array
+ * @param string $line The text to send
+ * @return bool Success
*/
public function send( array $feed, $line );
}
diff --git a/includes/rcfeed/RCFeedFormatter.php b/includes/rcfeed/RCFeedFormatter.php
index 6c9f8042..2f156598 100644
--- a/includes/rcfeed/RCFeedFormatter.php
+++ b/includes/rcfeed/RCFeedFormatter.php
@@ -1,7 +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
+ */
+
+/**
+ * Interface for RC feed formatters
+ *
+ * @since 1.22
+ */
interface RCFeedFormatter {
/**
- * Formats the line for the live feed.
+ * Formats the line to be sent by an engine
*
* @param array $feed The feed, as configured in an associative array.
* @param RecentChange $rc The RecentChange object showing what sort
diff --git a/includes/rcfeed/RedisPubSubFeedEngine.php b/includes/rcfeed/RedisPubSubFeedEngine.php
index 4bcc1337..b9023b6b 100644
--- a/includes/rcfeed/RedisPubSubFeedEngine.php
+++ b/includes/rcfeed/RedisPubSubFeedEngine.php
@@ -1,23 +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
+ */
+
+/**
+ * Emit a recent change notification via Redis Pub/Sub
+ *
+ * If the feed URI contains a path component, it will be used to generate a
+ * channel name by stripping the leading slash and replacing any remaining
+ * slashes with '.'. If no path component is present, the channel is set to
+ * 'rc'. If the URI contains a query string, its parameters will be parsed
+ * as RedisConnectionPool options.
+ *
+ * @example
+ * $wgRCFeeds['redis'] = array(
+ * 'formatter' => 'JSONRCFeedFormatter',
+ * 'uri' => "redis://127.0.0.1:6379/rc.$wgDBname",
+ * );
+ *
+ * @since 1.22
+ */
class RedisPubSubFeedEngine implements RCFeedEngine {
+
/**
- * Emit a recent change notification via Redis Pub/Sub
- *
- * If the feed URI contains a path component, it will be used to generate a
- * channel name by stripping the leading slash and replacing any remaining
- * slashes with '.'. If no path component is present, the channel is set to
- * 'rc'. If the URI contains a query string, its parameters will be parsed
- * as RedisConnectionPool options.
- *
- * @example $wgRCFeeds['redis'] = array(
- * 'formatter' => 'JSONRCFeedFormatter',
- * 'uri' => "redis://127.0.0.1:6379/rc.$wgDBname",
- * );
- *
- * @since 1.22
+ * @see RCFeedEngine::send
*/
public function send( array $feed, $line ) {
- $parsed = parse_url( $feed['uri'] );
+ $parsed = wfParseUrl( $feed['uri'] );
$server = $parsed['host'];
$options = array( 'serializer' => 'none' );
$channel = 'rc';
@@ -36,6 +61,11 @@ class RedisPubSubFeedEngine implements RCFeedEngine {
}
$pool = RedisConnectionPool::singleton( $options );
$conn = $pool->getConnection( $server );
- $conn->publish( $channel, $line );
+ if ( $conn !== false ) {
+ $conn->publish( $channel, $line );
+ return true;
+ } else {
+ return false;
+ }
}
}
diff --git a/includes/rcfeed/UDPRCFeedEngine.php b/includes/rcfeed/UDPRCFeedEngine.php
index beeb73bd..8554670e 100644
--- a/includes/rcfeed/UDPRCFeedEngine.php
+++ b/includes/rcfeed/UDPRCFeedEngine.php
@@ -1,7 +1,31 @@
<?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
+ */
+
+/**
+ * Sends the notification to the specified host in a UDP packet.
+ * @since 1.22
+ */
+
class UDPRCFeedEngine implements RCFeedEngine {
/**
- * Sends the notification to the specified host in a UDP packet.
* @see RCFeedEngine::send
*/
public function send( array $feed, $line ) {
diff --git a/includes/rcfeed/XMLRCFeedFormatter.php b/includes/rcfeed/XMLRCFeedFormatter.php
new file mode 100644
index 00000000..d4df91a4
--- /dev/null
+++ b/includes/rcfeed/XMLRCFeedFormatter.php
@@ -0,0 +1,29 @@
+<?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
+ */
+
+/**
+ * @since 1.23
+ */
+class XMLRCFeedFormatter extends MachineReadableRCFeedFormatter {
+
+ protected function formatArray( array $packet ) {
+ return ApiFormatXml::recXmlPrint( 'recentchange', $packet, 0 );
+ }
+}
diff --git a/includes/resourceloader/DerivativeResourceLoaderContext.php b/includes/resourceloader/DerivativeResourceLoaderContext.php
new file mode 100644
index 00000000..d114d7ed
--- /dev/null
+++ b/includes/resourceloader/DerivativeResourceLoaderContext.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Derivative context for resource loader modules.
+ *
+ * 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 Kunal Mehta
+ */
+
+/**
+ * Allows changing specific properties of a context object,
+ * without changing the main one. Inspired by DerivativeContext.
+ *
+ * @since 1.24
+ */
+class DerivativeResourceLoaderContext extends ResourceLoaderContext {
+
+ /**
+ * @var ResourceLoaderContext
+ */
+ private $context;
+ protected $modules;
+ protected $language;
+ protected $direction;
+ protected $skin;
+ protected $user;
+ protected $debug;
+ protected $only;
+ protected $version;
+ protected $hash;
+ protected $raw;
+
+ public function __construct( ResourceLoaderContext $context ) {
+ $this->context = $context;
+ }
+
+ public function getModules() {
+ if ( !is_null( $this->modules ) ) {
+ return $this->modules;
+ } else {
+ return $this->context->getModules();
+ }
+ }
+
+ /**
+ * @param string[] $modules
+ */
+ public function setModules( array $modules ) {
+ $this->modules = $modules;
+ }
+
+ public function getLanguage() {
+ if ( !is_null( $this->language ) ) {
+ return $this->language;
+ } else {
+ return $this->context->getLanguage();
+ }
+ }
+
+ /**
+ * @param string $language
+ */
+ public function setLanguage( $language ) {
+ $this->language = $language;
+ $this->direction = null; // Invalidate direction since it might be based on language
+ $this->hash = null;
+ }
+
+ public function getDirection() {
+ if ( !is_null( $this->direction ) ) {
+ return $this->direction;
+ } else {
+ return $this->context->getDirection();
+ }
+ }
+
+ /**
+ * @param string $direction
+ */
+ public function setDirection( $direction ) {
+ $this->direction = $direction;
+ $this->hash = null;
+ }
+
+ public function getSkin() {
+ if ( !is_null( $this->skin ) ) {
+ return $this->skin;
+ } else {
+ return $this->context->getSkin();
+ }
+ }
+
+ /**
+ * @param string $skin
+ */
+ public function setSkin( $skin ) {
+ $this->skin = $skin;
+ $this->hash = null;
+ }
+
+ public function getUser() {
+ if ( !is_null( $this->user ) ) {
+ return $this->user;
+ } else {
+ return $this->context->getUser();
+ }
+ }
+
+ /**
+ * @param string $user
+ */
+ public function setUser( $user ) {
+ $this->user = $user;
+ $this->hash = null;
+ }
+
+ public function getDebug() {
+ if ( !is_null( $this->debug ) ) {
+ return $this->debug;
+ } else {
+ return $this->context->getDebug();
+ }
+ }
+
+ /**
+ * @param bool $debug
+ */
+ public function setDebug( $debug ) {
+ $this->debug = $debug;
+ $this->hash = null;
+ }
+
+ public function getOnly() {
+ if ( !is_null( $this->only ) ) {
+ return $this->only;
+ } else {
+ return $this->context->getOnly();
+ }
+ }
+
+ /**
+ * @param string $only
+ */
+ public function setOnly( $only ) {
+ $this->only = $only;
+ $this->hash = null;
+ }
+
+ public function getVersion() {
+ if ( !is_null( $this->version ) ) {
+ return $this->version;
+ } else {
+ return $this->context->getVersion();
+ }
+ }
+
+ /**
+ * @param string $version
+ */
+ public function setVersion( $version ) {
+ $this->version = $version;
+ $this->hash = null;
+ }
+
+ public function getRaw() {
+ if ( !is_null( $this->raw ) ) {
+ return $this->raw;
+ } else {
+ return $this->context->getRaw();
+ }
+ }
+
+ /**
+ * @param bool $raw
+ */
+ public function setRaw( $raw ) {
+ $this->raw = $raw;
+ }
+
+ public function getRequest() {
+ return $this->context->getRequest();
+ }
+
+ public function getResourceLoader() {
+ return $this->context->getResourceLoader();
+ }
+
+}
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 6380efcf..4f1414bc 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -25,35 +25,39 @@
/**
* Dynamic JavaScript and CSS resource loading system.
*
- * Most of the documention is on the MediaWiki documentation wiki starting at:
- * http://www.mediawiki.org/wiki/ResourceLoader
+ * Most of the documentation is on the MediaWiki documentation wiki starting at:
+ * https://www.mediawiki.org/wiki/ResourceLoader
*/
class ResourceLoader {
-
- /* Protected Static Members */
+ /** @var int */
protected static $filterCacheVersion = 7;
- protected static $requiredSourceProperties = array( 'loadScript' );
- /** Array: List of module name/ResourceLoaderModule object pairs */
+ /** @var bool */
+ protected static $debugMode = null;
+
+ /** @var array Module name/ResourceLoaderModule object pairs */
protected $modules = array();
- /** Associative array mapping module name to info associative array */
+ /** @var array Associative array mapping module name to info associative array */
protected $moduleInfos = array();
- /** Associative array mapping framework ids to a list of names of test suite modules */
- /** like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. ) */
+ /** @var Config $config */
+ private $config;
+
+ /**
+ * @var array Associative array mapping framework ids to a list of names of test suite modules
+ * like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. )
+ */
protected $testModuleNames = array();
- /** array( 'source-id' => array( 'loadScript' => 'http://.../load.php' ) ) **/
+ /** @var array E.g. array( 'source-id' => 'http://.../load.php' ) */
protected $sources = array();
/** @var bool */
protected $hasErrors = false;
- /* Protected Methods */
-
/**
- * Loads information stored in the database about modules.
+ * Load information stored in the database about modules.
*
* This method grabs modules dependencies from the database and updates modules
* objects.
@@ -64,11 +68,12 @@ class ResourceLoader {
* performance improvement.
*
* @param array $modules List of module names to preload information for
- * @param $context ResourceLoaderContext: Context to load the information within
+ * @param ResourceLoaderContext $context Context to load the information within
*/
public function preloadModuleInfo( array $modules, ResourceLoaderContext $context ) {
if ( !count( $modules ) ) {
- return; // or else Database*::select() will explode, plus it's cheaper!
+ // Or else Database*::select() will explode, plus it's cheaper!
+ return;
}
$dbr = wfGetDB( DB_SLAVE );
$skin = $context->getSkin();
@@ -84,21 +89,26 @@ class ResourceLoader {
// Set modules' dependencies
$modulesWithDeps = array();
foreach ( $res as $row ) {
- $this->getModule( $row->md_module )->setFileDependencies( $skin,
- FormatJson::decode( $row->md_deps, true )
- );
- $modulesWithDeps[] = $row->md_module;
+ $module = $this->getModule( $row->md_module );
+ if ( $module ) {
+ $module->setFileDependencies( $skin, FormatJson::decode( $row->md_deps, true ) );
+ $modulesWithDeps[] = $row->md_module;
+ }
}
// Register the absence of a dependency row too
foreach ( array_diff( $modules, $modulesWithDeps ) as $name ) {
- $this->getModule( $name )->setFileDependencies( $skin, array() );
+ $module = $this->getModule( $name );
+ if ( $module ) {
+ $this->getModule( $name )->setFileDependencies( $skin, array() );
+ }
}
// Get message blob mtimes. Only do this for modules with messages
$modulesWithMessages = array();
foreach ( $modules as $name ) {
- if ( count( $this->getModule( $name )->getMessages() ) ) {
+ $module = $this->getModule( $name );
+ if ( $module && count( $module->getMessages() ) ) {
$modulesWithMessages[] = $name;
}
}
@@ -110,39 +120,43 @@ class ResourceLoader {
), __METHOD__
);
foreach ( $res as $row ) {
- $this->getModule( $row->mr_resource )->setMsgBlobMtime( $lang,
- wfTimestamp( TS_UNIX, $row->mr_timestamp ) );
- unset( $modulesWithoutMessages[$row->mr_resource] );
+ $module = $this->getModule( $row->mr_resource );
+ if ( $module ) {
+ $module->setMsgBlobMtime( $lang, wfTimestamp( TS_UNIX, $row->mr_timestamp ) );
+ unset( $modulesWithoutMessages[$row->mr_resource] );
+ }
}
}
foreach ( array_keys( $modulesWithoutMessages ) as $name ) {
- $this->getModule( $name )->setMsgBlobMtime( $lang, 0 );
+ $module = $this->getModule( $name );
+ if ( $module ) {
+ $module->setMsgBlobMtime( $lang, 0 );
+ }
}
}
/**
- * Runs JavaScript or CSS data through a filter, caching the filtered result for future calls.
+ * Run JavaScript or CSS data through a filter, caching the filtered result for future calls.
*
* Available filters are:
- * - minify-js \see JavaScriptMinifier::minify
- * - minify-css \see CSSMin::minify
+ *
+ * - minify-js \see JavaScriptMinifier::minify
+ * - minify-css \see CSSMin::minify
*
* If $data is empty, only contains whitespace or the filter was unknown,
* $data is returned unmodified.
*
* @param string $filter Name of filter to run
* @param string $data Text to filter, such as JavaScript or CSS text
- * @return String: Filtered data, or a comment containing an error message
+ * @param string $cacheReport Whether to include the cache key report
+ * @return string Filtered data, or a comment containing an error message
*/
- protected function filter( $filter, $data ) {
- global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
+ public function filter( $filter, $data, $cacheReport = true ) {
wfProfileIn( __METHOD__ );
// For empty/whitespace-only data or for unknown filters, don't perform
// any caching or processing
- if ( trim( $data ) === ''
- || !in_array( $filter, array( 'minify-js', 'minify-css' ) ) )
- {
+ if ( trim( $data ) === '' || !in_array( $filter, array( 'minify-js', 'minify-css' ) ) ) {
wfProfileOut( __METHOD__ );
return $data;
}
@@ -165,14 +179,18 @@ class ResourceLoader {
switch ( $filter ) {
case 'minify-js':
$result = JavaScriptMinifier::minify( $data,
- $wgResourceLoaderMinifierStatementsOnOwnLine,
- $wgResourceLoaderMinifierMaxLineLength
+ $this->config->get( 'ResourceLoaderMinifierStatementsOnOwnLine' ),
+ $this->config->get( 'ResourceLoaderMinifierMaxLineLength' )
);
- $result .= "\n/* cache key: $key */";
+ if ( $cacheReport ) {
+ $result .= "\n/* cache key: $key */";
+ }
break;
case 'minify-css':
$result = CSSMin::minify( $data );
- $result .= "\n/* cache key: $key */";
+ if ( $cacheReport ) {
+ $result .= "\n/* cache key: $key */";
+ }
break;
}
@@ -194,26 +212,34 @@ class ResourceLoader {
/* Methods */
/**
- * Registers core modules and runs registration hooks.
+ * Register core modules and runs registration hooks.
+ * @param Config|null $config
*/
- public function __construct() {
- global $IP, $wgResourceModules, $wgResourceLoaderSources, $wgLoadScript, $wgEnableJavaScriptTest;
+ public function __construct( Config $config = null ) {
+ global $IP;
wfProfileIn( __METHOD__ );
+ if ( $config === null ) {
+ wfDebug( __METHOD__ . ' was called without providing a Config instance' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+
+ $this->config = $config;
+
// Add 'local' source first
- $this->addSource( 'local', array( 'loadScript' => $wgLoadScript, 'apiScript' => wfScript( 'api' ) ) );
+ $this->addSource( 'local', wfScript( 'load' ) );
// Add other sources
- $this->addSource( $wgResourceLoaderSources );
+ $this->addSource( $config->get( 'ResourceLoaderSources' ) );
// Register core modules
$this->register( include "$IP/resources/Resources.php" );
// Register extension modules
wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
- $this->register( $wgResourceModules );
+ $this->register( $config->get( 'ResourceModules' ) );
- if ( $wgEnableJavaScriptTest === true ) {
+ if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
$this->registerTestModules();
}
@@ -221,17 +247,24 @@ class ResourceLoader {
}
/**
- * Registers a module with the ResourceLoader system.
+ * @return Config
+ */
+ public function getConfig() {
+ return $this->config;
+ }
+
+ /**
+ * Register a module with the ResourceLoader system.
*
- * @param $name Mixed: Name of module as a string or List of name/object pairs as an array
+ * @param mixed $name Name of module as a string or List of name/object pairs as an array
* @param array $info Module info array. For backwards compatibility with 1.17alpha,
* this may also be a ResourceLoaderModule object. Optional when using
* multiple-registration calling style.
- * @throws MWException: If a duplicate module registration is attempted
- * @throws MWException: If a module name contains illegal characters (pipes or commas)
- * @throws MWException: If something other than a ResourceLoaderModule is being registered
- * @return Boolean: False if there were any errors, in which case one or more modules were not
- * registered
+ * @throws MWException If a duplicate module registration is attempted
+ * @throws MWException If a module name contains illegal characters (pipes or commas)
+ * @throws MWException If something other than a ResourceLoaderModule is being registered
+ * @return bool False if there were any errors, in which case one or more modules were
+ * not registered
*/
public function register( $name, $info = null ) {
wfProfileIn( __METHOD__ );
@@ -252,25 +285,64 @@ class ResourceLoader {
// Check $name for validity
if ( !self::isValidModuleName( $name ) ) {
wfProfileOut( __METHOD__ );
- throw new MWException( "ResourceLoader module name '$name' is invalid, see ResourceLoader::isValidModuleName()" );
+ throw new MWException( "ResourceLoader module name '$name' is invalid, "
+ . "see ResourceLoader::isValidModuleName()" );
}
// Attach module
- if ( is_object( $info ) ) {
- // Old calling convention
- // Validate the input
- if ( !( $info instanceof ResourceLoaderModule ) ) {
- wfProfileOut( __METHOD__ );
- throw new MWException( 'ResourceLoader invalid module error. ' .
- 'Instances of ResourceLoaderModule expected.' );
- }
-
+ if ( $info instanceof ResourceLoaderModule ) {
$this->moduleInfos[$name] = array( 'object' => $info );
$info->setName( $name );
$this->modules[$name] = $info;
- } else {
+ } elseif ( is_array( $info ) ) {
// New calling convention
$this->moduleInfos[$name] = $info;
+ } else {
+ wfProfileOut( __METHOD__ );
+ throw new MWException(
+ 'ResourceLoader module info type error for module \'' . $name .
+ '\': expected ResourceLoaderModule or array (got: ' . gettype( $info ) . ')'
+ );
+ }
+
+ // Last-minute changes
+
+ // Apply custom skin-defined styles to existing modules.
+ if ( $this->isFileModule( $name ) ) {
+ foreach ( $this->config->get( 'ResourceModuleSkinStyles' ) as $skinName => $skinStyles ) {
+ // If this module already defines skinStyles for this skin, ignore $wgResourceModuleSkinStyles.
+ if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
+ continue;
+ }
+
+ // If $name is preceded with a '+', the defined style files will be added to 'default'
+ // skinStyles, otherwise 'default' will be ignored as it normally would be.
+ if ( isset( $skinStyles[$name] ) ) {
+ $paths = (array)$skinStyles[$name];
+ $styleFiles = array();
+ } elseif ( isset( $skinStyles['+' . $name] ) ) {
+ $paths = (array)$skinStyles['+' . $name];
+ $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
+ $this->moduleInfos[$name]['skinStyles']['default'] :
+ array();
+ } else {
+ continue;
+ }
+
+ // Add new file paths, remapping them to refer to our directories and not use settings
+ // from the module we're modifying. These can come from the base definition or be defined
+ // for each module.
+ list( $localBasePath, $remoteBasePath ) =
+ ResourceLoaderFileModule::extractBasePaths( $skinStyles );
+ list( $localBasePath, $remoteBasePath ) =
+ ResourceLoaderFileModule::extractBasePaths( $paths, $localBasePath, $remoteBasePath );
+
+ foreach ( $paths as $path ) {
+ $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
+ }
+
+ $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
+ }
}
}
@@ -280,17 +352,19 @@ class ResourceLoader {
/**
*/
public function registerTestModules() {
- global $IP, $wgEnableJavaScriptTest;
+ global $IP;
- if ( $wgEnableJavaScriptTest !== true ) {
- throw new MWException( 'Attempt to register JavaScript test modules but <code>$wgEnableJavaScriptTest</code> is false. Edit your <code>LocalSettings.php</code> to enable it.' );
+ if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
+ throw new MWException( 'Attempt to register JavaScript test modules '
+ . 'but <code>$wgEnableJavaScriptTest</code> is false. '
+ . 'Edit your <code>LocalSettings.php</code> to enable it.' );
}
wfProfileIn( __METHOD__ );
// Get core test suites
$testModules = array();
- $testModules['qunit'] = include "$IP/tests/qunit/QUnitTestResources.php";
+ $testModules['qunit'] = array();
// Get other test suites (e.g. from extensions)
wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
@@ -301,9 +375,12 @@ class ResourceLoader {
// on document-ready, it will run once and finish. If some tests arrive
// later (possibly after QUnit has already finished) they will be ignored.
$module['position'] = 'top';
- $module['dependencies'][] = 'mediawiki.tests.qunit.testrunner';
+ $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
}
+ $testModules['qunit'] =
+ ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules['qunit'];
+
foreach ( $testModules as $id => $names ) {
// Register test modules
$this->register( $testModules[$id] );
@@ -318,14 +395,12 @@ class ResourceLoader {
/**
* Add a foreign source of modules.
*
- * Source properties:
- * 'loadScript': URL (either fully-qualified or protocol-relative) of load.php for this source
- *
- * @param $id Mixed: source ID (string), or array( id1 => props1, id2 => props2, ... )
- * @param array $properties source properties
+ * @param array|string $id Source ID (string), or array( id1 => loadUrl, id2 => loadUrl, ... )
+ * @param string|array $loadUrl load.php url (string), or array with loadUrl key for
+ * backwards-compatibility.
* @throws MWException
*/
- public function addSource( $id, $properties = null ) {
+ public function addSource( $id, $loadUrl = null ) {
// Allow multiple sources to be registered in one call
if ( is_array( $id ) ) {
foreach ( $id as $key => $value ) {
@@ -342,20 +417,24 @@ class ResourceLoader {
);
}
- // Validate properties
- foreach ( self::$requiredSourceProperties as $prop ) {
- if ( !isset( $properties[$prop] ) ) {
- throw new MWException( "Required property $prop missing from source ID $id" );
+ // Pre 1.24 backwards-compatibility
+ if ( is_array( $loadUrl ) ) {
+ if ( !isset( $loadUrl['loadScript'] ) ) {
+ throw new MWException(
+ __METHOD__ . ' was passed an array with no "loadScript" key.'
+ );
}
+
+ $loadUrl = $loadUrl['loadScript'];
}
- $this->sources[$id] = $properties;
+ $this->sources[$id] = $loadUrl;
}
/**
- * Get a list of module names
+ * Get a list of module names.
*
- * @return Array: List of module names
+ * @return array List of module names
*/
public function getModuleNames() {
return array_keys( $this->moduleInfos );
@@ -363,18 +442,21 @@ class ResourceLoader {
/**
* Get a list of test module names for one (or all) frameworks.
+ *
* If the given framework id is unknkown, or if the in-object variable is not an array,
* then it will return an empty array.
*
- * @param string $framework Optional. Get only the test module names for one
- * particular framework.
- * @return Array
+ * @param string $framework Get only the test module names for one
+ * particular framework (optional)
+ * @return array
*/
public function getTestModuleNames( $framework = 'all' ) {
- /// @todo api siteinfo prop testmodulenames modulenames
+ /** @todo api siteinfo prop testmodulenames modulenames */
if ( $framework == 'all' ) {
return $this->testModuleNames;
- } elseif ( isset( $this->testModuleNames[$framework] ) && is_array( $this->testModuleNames[$framework] ) ) {
+ } elseif ( isset( $this->testModuleNames[$framework] )
+ && is_array( $this->testModuleNames[$framework] )
+ ) {
return $this->testModuleNames[$framework];
} else {
return array();
@@ -384,8 +466,13 @@ class ResourceLoader {
/**
* Get the ResourceLoaderModule object for a given module name.
*
+ * If an array of module parameters exists but a ResourceLoaderModule object has not
+ * yet been instantiated, this method will instantiate and cache that object such that
+ * subsequent calls simply return the same object.
+ *
* @param string $name Module name
- * @return ResourceLoaderModule if module has been registered, null otherwise
+ * @return ResourceLoaderModule|null If module has been registered, return a
+ * ResourceLoaderModule instance. Otherwise, return null.
*/
public function getModule( $name ) {
if ( !isset( $this->modules[$name] ) ) {
@@ -405,7 +492,9 @@ class ResourceLoader {
} else {
$class = $info['class'];
}
+ /** @var ResourceLoaderModule $object */
$object = new $class( $info );
+ $object->setConfig( $this->getConfig() );
}
$object->setName( $name );
$this->modules[$name] = $object;
@@ -415,24 +504,55 @@ class ResourceLoader {
}
/**
- * Get the list of sources
+ * Return whether the definition of a module corresponds to a simple ResourceLoaderFileModule.
*
- * @return Array: array( id => array of properties, .. )
+ * @param string $name Module name
+ * @return bool
+ */
+ protected function isFileModule( $name ) {
+ if ( !isset( $this->moduleInfos[$name] ) ) {
+ return false;
+ }
+ $info = $this->moduleInfos[$name];
+ if ( isset( $info['object'] ) || isset( $info['class'] ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get the list of sources.
+ *
+ * @return array Like array( id => load.php url, .. )
*/
public function getSources() {
return $this->sources;
}
/**
- * Outputs a response to a resource load-request, including a content-type header.
+ * Get the URL to the load.php endpoint for the given
+ * ResourceLoader source
*
- * @param $context ResourceLoaderContext: Context in which a response should be formed
+ * @since 1.24
+ * @param string $source
+ * @throws MWException On an invalid $source name
+ * @return string
*/
- public function respond( ResourceLoaderContext $context ) {
- global $wgCacheEpoch, $wgUseFileCache;
+ public function getLoadScript( $source ) {
+ if ( !isset( $this->sources[$source] ) ) {
+ throw new MWException( "The $source source was never registered in ResourceLoader." );
+ }
+ return $this->sources[$source];
+ }
+ /**
+ * Output a response to a load request, including the content-type header.
+ *
+ * @param ResourceLoaderContext $context Context in which a response should be formed
+ */
+ public function respond( ResourceLoaderContext $context ) {
// Use file cache if enabled and available...
- if ( $wgUseFileCache ) {
+ if ( $this->config->get( 'UseFileCache' ) ) {
$fileCache = ResourceFileCache::newFromContext( $context );
if ( $this->tryRespondFromFileCache( $fileCache, $context ) ) {
return; // output handled
@@ -451,12 +571,12 @@ class ResourceLoader {
wfProfileIn( __METHOD__ );
$errors = '';
- // Split requested modules into two groups, modules and missing
+ // Find out which modules are missing and instantiate the others
$modules = array();
$missing = array();
foreach ( $context->getModules() as $name ) {
- if ( isset( $this->moduleInfos[$name] ) ) {
- $module = $this->getModule( $name );
+ $module = $this->getModule( $name );
+ if ( $module ) {
// Do not allow private modules to be loaded from the web.
// This is a security issue, see bug 34907.
if ( $module->getGroup() === 'private' ) {
@@ -488,7 +608,7 @@ class ResourceLoader {
// To send Last-Modified and support If-Modified-Since, we need to detect
// the last modified time
- $mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch );
+ $mtime = wfTimestamp( TS_UNIX, $this->config->get( 'CacheEpoch' ) );
foreach ( $modules as $module ) {
/**
* @var $module ResourceLoaderModule
@@ -527,7 +647,7 @@ class ResourceLoader {
}
// Save response to file cache unless there are errors
- if ( isset( $fileCache ) && !$errors && !$missing ) {
+ if ( isset( $fileCache ) && !$errors && !count( $missing ) ) {
// Cache single modules...and other requests if there are enough hits
if ( ResourceFileCache::useFileCache( $context ) ) {
if ( $fileCache->isCacheWorthy() ) {
@@ -550,24 +670,24 @@ class ResourceLoader {
/**
* Send content type and last modified headers to the client.
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @param string $mtime TS_MW timestamp to use for last-modified
* @param bool $errors Whether there are commented-out errors in the response
* @return void
*/
protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $errors ) {
- global $wgResourceLoaderMaxage;
+ $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
// If a version wasn't specified we need a shorter expiry time for updates
// to propagate to clients quickly
// If there were errors, we also need a shorter expiry time so we can recover quickly
if ( is_null( $context->getVersion() ) || $errors ) {
- $maxage = $wgResourceLoaderMaxage['unversioned']['client'];
- $smaxage = $wgResourceLoaderMaxage['unversioned']['server'];
+ $maxage = $rlMaxage['unversioned']['client'];
+ $smaxage = $rlMaxage['unversioned']['server'];
// If a version was specified we can use a longer expiry time since changing
// version numbers causes cache misses
} else {
- $maxage = $wgResourceLoaderMaxage['versioned']['client'];
- $smaxage = $wgResourceLoaderMaxage['versioned']['server'];
+ $maxage = $rlMaxage['versioned']['client'];
+ $smaxage = $rlMaxage['versioned']['server'];
}
if ( $context->getOnly() === 'styles' ) {
header( 'Content-Type: text/css; charset=utf-8' );
@@ -588,9 +708,12 @@ class ResourceLoader {
}
/**
+ * Respond with 304 Last Modified if appropiate.
+ *
* If there's an If-Modified-Since header, respond with a 304 appropriately
* and clear out the output buffer. If the client cache is too old then do nothing.
- * @param $context ResourceLoaderContext
+ *
+ * @param ResourceLoaderContext $context
* @param string $mtime The TS_MW timestamp to check the header against
* @return bool True if 304 header sent and output handled
*/
@@ -623,22 +746,22 @@ class ResourceLoader {
}
/**
- * Send out code for a response from file cache if possible
+ * Send out code for a response from file cache if possible.
*
- * @param $fileCache ResourceFileCache: Cache object for this request URL
- * @param $context ResourceLoaderContext: Context in which to generate a response
+ * @param ResourceFileCache $fileCache Cache object for this request URL
+ * @param ResourceLoaderContext $context Context in which to generate a response
* @return bool If this found a cache file and handled the response
*/
protected function tryRespondFromFileCache(
ResourceFileCache $fileCache, ResourceLoaderContext $context
) {
- global $wgResourceLoaderMaxage;
+ $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
// Buffer output to catch warnings.
ob_start();
// Get the maximum age the cache can be
$maxage = is_null( $context->getVersion() )
- ? $wgResourceLoaderMaxage['unversioned']['server']
- : $wgResourceLoaderMaxage['versioned']['server'];
+ ? $rlMaxage['unversioned']['server']
+ : $rlMaxage['versioned']['server'];
// Minimum timestamp the cache file must have
$good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
if ( !$good ) {
@@ -674,10 +797,11 @@ class ResourceLoader {
}
/**
- * Generate a CSS or JS comment block. Only use this for public data,
- * not error message details.
+ * Generate a CSS or JS comment block.
*
- * @param $text string
+ * Only use this for public data, not error message details.
+ *
+ * @param string $text
* @return string
*/
public static function makeComment( $text ) {
@@ -686,10 +810,10 @@ class ResourceLoader {
}
/**
- * Handle exception display
+ * Handle exception display.
*
- * @param Exception $e to be shown to the user
- * @return string sanitized text that can be returned to the user
+ * @param Exception $e Exception to be shown to the user
+ * @return string Sanitized text that can be returned to the user
*/
public static function formatException( $e ) {
global $wgShowExceptionDetails;
@@ -702,30 +826,38 @@ class ResourceLoader {
}
/**
- * Generates code for a response
+ * Generate code for a response.
*
- * @param $context ResourceLoaderContext: Context in which to generate a response
+ * @param ResourceLoaderContext $context Context in which to generate a response
* @param array $modules List of module objects keyed by module name
- * @param array $missing List of unavailable modules (optional)
- * @return String: Response data
+ * @param array $missing List of requested module names that are unregistered (optional)
+ * @return string Response data
*/
public function makeModuleResponse( ResourceLoaderContext $context,
- array $modules, $missing = array()
+ array $modules, array $missing = array()
) {
$out = '';
$exceptions = '';
- if ( $modules === array() && $missing === array() ) {
- return '/* No modules requested. Max made me put this here */';
+ $states = array();
+
+ if ( !count( $modules ) && !count( $missing ) ) {
+ return "/* This file is the Web entry point for MediaWiki's ResourceLoader:
+ <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
+ no modules were requested. Max made me put this here. */";
}
wfProfileIn( __METHOD__ );
+
// Pre-fetch blobs
if ( $context->shouldIncludeMessages() ) {
try {
- $blobs = MessageBlobStore::get( $this, $modules, $context->getLanguage() );
+ $blobs = MessageBlobStore::getInstance()->get( $this, $modules, $context->getLanguage() );
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
- wfDebugLog( 'resourceloader', __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: $e" );
+ wfDebugLog(
+ 'resourceloader',
+ __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: $e"
+ );
$this->hasErrors = true;
// Add exception to the output as a comment
$exceptions .= self::formatException( $e );
@@ -734,6 +866,10 @@ class ResourceLoader {
$blobs = array();
}
+ foreach ( $missing as $name ) {
+ $states[$name] = 'missing';
+ }
+
// Generate output
$isRaw = false;
foreach ( $modules as $name => $module ) {
@@ -753,9 +889,15 @@ class ResourceLoader {
$scripts = $module->getScriptURLsForDebug( $context );
} else {
$scripts = $module->getScript( $context );
- if ( is_string( $scripts ) && strlen( $scripts ) && substr( $scripts, -1 ) !== ';' ) {
- // bug 27054: Append semicolon to prevent weird bugs
- // caused by files not terminating their statements right
+ // rtrim() because there are usually a few line breaks
+ // after the last ';'. A new line at EOF, a new line
+ // added by ResourceLoaderFileModule::readScriptFiles, etc.
+ if ( is_string( $scripts )
+ && strlen( $scripts )
+ && substr( rtrim( $scripts ), -1 ) !== ';'
+ ) {
+ // Append semicolon to prevent weird bugs caused by files not
+ // terminating their statements right (bug 27054)
$scripts .= ";\n";
}
}
@@ -766,7 +908,7 @@ class ResourceLoader {
// Don't create empty stylesheets like array( '' => '' ) for modules
// that don't *have* any stylesheets (bug 38024).
$stylePairs = $module->getStyles( $context );
- if ( count ( $stylePairs ) ) {
+ if ( count( $stylePairs ) ) {
// If we are in debug mode without &only= set, we'll want to return an array of URLs
// See comment near shouldIncludeScripts() for more details
if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
@@ -838,8 +980,8 @@ class ResourceLoader {
// Add exception to the output as a comment
$exceptions .= self::formatException( $e );
- // Register module as missing
- $missing[] = $name;
+ // Respond to client with error-state instead of module implementation
+ $states[$name] = 'error';
unset( $modules[$name] );
}
$isRaw |= $module->isRaw();
@@ -848,14 +990,23 @@ class ResourceLoader {
// Update module states
if ( $context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw ) {
- // Set the state of modules loaded as only scripts to ready
if ( count( $modules ) && $context->getOnly() === 'scripts' ) {
- $out .= self::makeLoaderStateScript(
- array_fill_keys( array_keys( $modules ), 'ready' ) );
+ // Set the state of modules loaded as only scripts to ready as
+ // they don't have an mw.loader.implement wrapper that sets the state
+ foreach ( $modules as $name => $module ) {
+ $states[$name] = 'ready';
+ }
+ }
+
+ // Set the state of modules we didn't respond to with mw.loader.implement
+ if ( count( $states ) ) {
+ $out .= self::makeLoaderStateScript( $states );
}
- // Set the state of modules which were requested but unavailable as missing
- if ( is_array( $missing ) && count( $missing ) ) {
- $out .= self::makeLoaderStateScript( array_fill_keys( $missing, 'missing' ) );
+ } else {
+ if ( count( $states ) ) {
+ $exceptions .= self::makeComment(
+ 'Problematic modules: ' . FormatJson::encode( $states, ResourceLoader::inDebugMode() )
+ );
}
}
@@ -874,23 +1025,21 @@ class ResourceLoader {
/* Static Methods */
/**
- * Returns JS code to call to mw.loader.implement for a module with
- * given properties.
+ * Return JS code that calls mw.loader.implement with given module properties.
*
* @param string $name Module name
- * @param $scripts Mixed: List of URLs to JavaScript files or String of JavaScript code
- * @param $styles Mixed: Array of CSS strings keyed by media type, or an array of lists of URLs to
- * CSS files keyed by media type
- * @param $messages Mixed: List of messages associated with this module. May either be an
- * associative array mapping message key to value, or a JSON-encoded message blob containing
- * the same data, wrapped in an XmlJsCode object.
- *
+ * @param mixed $scripts List of URLs to JavaScript files or String of JavaScript code
+ * @param mixed $styles Array of CSS strings keyed by media type, or an array of lists of URLs
+ * to CSS files keyed by media type
+ * @param mixed $messages List of messages associated with this module. May either be an
+ * associative array mapping message key to value, or a JSON-encoded message blob containing
+ * the same data, wrapped in an XmlJsCode object.
* @throws MWException
* @return string
*/
public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
if ( is_string( $scripts ) ) {
- $scripts = new XmlJsCode( "function () {\n{$scripts}\n}" );
+ $scripts = new XmlJsCode( "function ( $, jQuery ) {\n{$scripts}\n}" );
} elseif ( !is_array( $scripts ) ) {
throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
}
@@ -914,24 +1063,26 @@ class ResourceLoader {
/**
* Returns JS code which, when called, will register a given list of messages.
*
- * @param $messages Mixed: Either an associative array mapping message key to value, or a
- * JSON-encoded message blob containing the same data, wrapped in an XmlJsCode object.
- *
+ * @param mixed $messages Either an associative array mapping message key to value, or a
+ * JSON-encoded message blob containing the same data, wrapped in an XmlJsCode object.
* @return string
*/
public static function makeMessageSetScript( $messages ) {
- return Xml::encodeJsCall( 'mw.messages.set', array( (object)$messages ) );
+ return Xml::encodeJsCall(
+ 'mw.messages.set',
+ array( (object)$messages ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
* Combines an associative array mapping media type to CSS into a
* single stylesheet with "@media" blocks.
*
- * @param array $stylePairs Array keyed by media type containing (arrays of) CSS strings.
- *
- * @return Array
+ * @param array $stylePairs Array keyed by media type containing (arrays of) CSS strings
+ * @return array
*/
- private static function makeCombinedStyles( array $stylePairs ) {
+ public static function makeCombinedStyles( array $stylePairs ) {
$out = array();
foreach ( $stylePairs as $media => $styles ) {
// ResourceLoaderFileModule::getStyle can return the styles
@@ -968,16 +1119,23 @@ class ResourceLoader {
* - ResourceLoader::makeLoaderStateScript( array( $name => $state, ... ) ):
* Set the state of modules with the given names to the given states
*
- * @param $name string
- * @param $state
- *
+ * @param string $name
+ * @param string $state
* @return string
*/
public static function makeLoaderStateScript( $name, $state = null ) {
if ( is_array( $name ) ) {
- return Xml::encodeJsCall( 'mw.loader.state', array( $name ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.state',
+ array( $name ),
+ ResourceLoader::inDebugMode()
+ );
} else {
- return Xml::encodeJsCall( 'mw.loader.state', array( $name, $state ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.state',
+ array( $name, $state ),
+ ResourceLoader::inDebugMode()
+ );
}
}
@@ -988,55 +1146,67 @@ class ResourceLoader {
* and $group as supplied.
*
* @param string $name Module name
- * @param $version Integer: Module version number as a timestamp
+ * @param int $version Module version number as a timestamp
* @param array $dependencies List of module names on which this module depends
* @param string $group Group which the module is in.
* @param string $source Source of the module, or 'local' if not foreign.
* @param string $script JavaScript code
- *
* @return string
*/
- public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $source, $script ) {
+ public static function makeCustomLoaderScript( $name, $version, $dependencies,
+ $group, $source, $script
+ ) {
$script = str_replace( "\n", "\n\t", trim( $script ) );
return Xml::encodeJsCall(
"( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
- array( $name, $version, $dependencies, $group, $source ) );
+ array( $name, $version, $dependencies, $group, $source ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
* Returns JS code which calls mw.loader.register with the given
* parameters. Has three calling conventions:
*
- * - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group, $source ):
- * Register a single module.
+ * - ResourceLoader::makeLoaderRegisterScript( $name, $version,
+ * $dependencies, $group, $source, $skip
+ * ):
+ * Register a single module.
*
* - ResourceLoader::makeLoaderRegisterScript( array( $name1, $name2 ) ):
- * Register modules with the given names.
+ * Register modules with the given names.
*
* - ResourceLoader::makeLoaderRegisterScript( array(
- * array( $name1, $version1, $dependencies1, $group1, $source1 ),
- * array( $name2, $version2, $dependencies1, $group2, $source2 ),
+ * array( $name1, $version1, $dependencies1, $group1, $source1, $skip1 ),
+ * array( $name2, $version2, $dependencies1, $group2, $source2, $skip2 ),
* ...
* ) ):
* Registers modules with the given names and parameters.
*
* @param string $name Module name
- * @param $version Integer: Module version number as a timestamp
+ * @param int $version Module version number as a timestamp
* @param array $dependencies List of module names on which this module depends
- * @param string $group group which the module is in.
- * @param string $source source of the module, or 'local' if not foreign
- *
+ * @param string $group Group which the module is in
+ * @param string $source Source of the module, or 'local' if not foreign
+ * @param string $skip Script body of the skip function
* @return string
*/
public static function makeLoaderRegisterScript( $name, $version = null,
- $dependencies = null, $group = null, $source = null
+ $dependencies = null, $group = null, $source = null, $skip = null
) {
if ( is_array( $name ) ) {
- return Xml::encodeJsCall( 'mw.loader.register', array( $name ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.register',
+ array( $name ),
+ ResourceLoader::inDebugMode()
+ );
} else {
$version = (int)$version > 1 ? (int)$version : 1;
- return Xml::encodeJsCall( 'mw.loader.register',
- array( $name, $version, $dependencies, $group, $source ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.register',
+ array( $name, $version, $dependencies, $group, $source, $skip ),
+ ResourceLoader::inDebugMode()
+ );
}
}
@@ -1047,19 +1217,26 @@ class ResourceLoader {
* - ResourceLoader::makeLoaderSourcesScript( $id, $properties ):
* Register a single source
*
- * - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $props1, $id2 => $props2, ... ) );
+ * - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $loadUrl, $id2 => $loadUrl, ... ) );
* Register sources with the given IDs and properties.
*
- * @param string $id source ID
- * @param array $properties source properties (see addSource())
- *
+ * @param string $id Source ID
+ * @param array $properties Source properties (see addSource())
* @return string
*/
public static function makeLoaderSourcesScript( $id, $properties = null ) {
if ( is_array( $id ) ) {
- return Xml::encodeJsCall( 'mw.loader.addSource', array( $id ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.addSource',
+ array( $id ),
+ ResourceLoader::inDebugMode()
+ );
} else {
- return Xml::encodeJsCall( 'mw.loader.addSource', array( $id, $properties ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.addSource',
+ array( $id, $properties ),
+ ResourceLoader::inDebugMode()
+ );
}
}
@@ -1068,7 +1245,6 @@ class ResourceLoader {
* present.
*
* @param string $script JavaScript code
- *
* @return string
*/
public static function makeLoaderConditionalScript( $script ) {
@@ -1080,11 +1256,14 @@ class ResourceLoader {
* the given value.
*
* @param array $configuration List of configuration values keyed by variable name
- *
* @return string
*/
public static function makeConfigSetScript( array $configuration ) {
- return Xml::encodeJsCall( 'mw.config.set', array( $configuration ), ResourceLoader::inDebugMode() );
+ return Xml::encodeJsCall(
+ 'mw.config.set',
+ array( $configuration ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
@@ -1092,7 +1271,7 @@ class ResourceLoader {
*
* For example, array( 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' )
* becomes 'foo.bar,baz|bar.baz,quux'
- * @param array $modules of module names (strings)
+ * @param array $modules List of module names (strings)
* @return string Packed query string
*/
public static function makePackedModulesString( $modules ) {
@@ -1119,18 +1298,50 @@ class ResourceLoader {
* @return bool
*/
public static function inDebugMode() {
- global $wgRequest, $wgResourceLoaderDebug;
- static $retval = null;
- if ( !is_null( $retval ) ) {
- return $retval;
+ if ( self::$debugMode === null ) {
+ global $wgRequest, $wgResourceLoaderDebug;
+ self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
+ $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
+ );
}
- return $retval = $wgRequest->getFuzzyBool( 'debug',
- $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug ) );
+ return self::$debugMode;
+ }
+
+ /**
+ * Reset static members used for caching.
+ *
+ * Global state and $wgRequest are evil, but we're using it right
+ * now and sometimes we need to be able to force ResourceLoader to
+ * re-evaluate the context because it has changed (e.g. in the test suite).
+ */
+ public static function clearCache() {
+ self::$debugMode = null;
}
/**
* Build a load.php URL
- * @param array $modules of module names (strings)
+ *
+ * @since 1.24
+ * @param string $source Name of the ResourceLoader source
+ * @param ResourceLoaderContext $context
+ * @param array $extraQuery
+ * @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative)
+ */
+ public function createLoaderURL( $source, ResourceLoaderContext $context,
+ $extraQuery = array()
+ ) {
+ $query = self::createLoaderQuery( $context, $extraQuery );
+ $script = $this->getLoadScript( $source );
+
+ // Prevent the IE6 extension check from being triggered (bug 28840)
+ // by appending a character that's invalid in Windows extensions ('*')
+ return wfExpandUrl( wfAppendQuery( $script, $query ) . '&*', PROTO_RELATIVE );
+ }
+
+ /**
+ * Build a load.php URL
+ * @deprecated since 1.24, use createLoaderURL instead
+ * @param array $modules Array of module names (strings)
* @param string $lang Language code
* @param string $skin Skin name
* @param string|null $user User name. If null, the &user= parameter is omitted
@@ -1142,9 +1353,12 @@ class ResourceLoader {
* @param array $extraQuery Extra query parameters to add
* @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative)
*/
- public static function makeLoaderURL( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
- $printable = false, $handheld = false, $extraQuery = array() ) {
+ public static function makeLoaderURL( $modules, $lang, $skin, $user = null,
+ $version = null, $debug = false, $only = null, $printable = false,
+ $handheld = false, $extraQuery = array()
+ ) {
global $wgLoadScript;
+
$query = self::makeLoaderQuery( $modules, $lang, $skin, $user, $version, $debug,
$only, $printable, $handheld, $extraQuery
);
@@ -1155,6 +1369,30 @@ class ResourceLoader {
}
/**
+ * Helper for createLoaderURL()
+ *
+ * @since 1.24
+ * @see makeLoaderQuery
+ * @param ResourceLoaderContext $context
+ * @param array $extraQuery
+ * @return array
+ */
+ public static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = array() ) {
+ return self::makeLoaderQuery(
+ $context->getModules(),
+ $context->getLanguage(),
+ $context->getSkin(),
+ $context->getUser(),
+ $context->getVersion(),
+ $context->getDebug(),
+ $context->getOnly(),
+ $context->getRequest()->getBool( 'printable' ),
+ $context->getRequest()->getBool( 'handheld' ),
+ $extraQuery
+ );
+ }
+
+ /**
* Build a query array (array representation of query string) for load.php. Helper
* function for makeLoaderURL().
*
@@ -1171,8 +1409,10 @@ class ResourceLoader {
*
* @return array
*/
- public static function makeLoaderQuery( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
- $printable = false, $handheld = false, $extraQuery = array() ) {
+ public static function makeLoaderQuery( $modules, $lang, $skin, $user = null,
+ $version = null, $debug = false, $only = null, $printable = false,
+ $handheld = false, $extraQuery = array()
+ ) {
$query = array(
'modules' => self::makePackedModulesString( $modules ),
'lang' => $lang,
@@ -1217,12 +1457,12 @@ class ResourceLoader {
/**
* Returns LESS compiler set up for use with MediaWiki
*
+ * @param Config $config
+ * @throws MWException
* @since 1.22
* @return lessc
*/
- public static function getLessCompiler() {
- global $wgResourceLoaderLESSFunctions, $wgResourceLoaderLESSImportPaths;
-
+ public static function getLessCompiler( Config $config ) {
// When called from the installer, it is possible that a required PHP extension
// is missing (at least for now; see bug 47564). If this is the case, throw an
// exception (caught by the installer) to prevent a fatal error later on.
@@ -1232,9 +1472,9 @@ class ResourceLoader {
$less = new lessc();
$less->setPreserveComments( true );
- $less->setVariables( self::getLESSVars() );
- $less->setImportDir( $wgResourceLoaderLESSImportPaths );
- foreach ( $wgResourceLoaderLESSFunctions as $name => $func ) {
+ $less->setVariables( self::getLessVars( $config ) );
+ $less->setImportDir( $config->get( 'ResourceLoaderLESSImportPaths' ) );
+ foreach ( $config->get( 'ResourceLoaderLESSFunctions' ) as $name => $func ) {
$less->registerFunction( $name, $func );
}
return $less;
@@ -1243,18 +1483,14 @@ class ResourceLoader {
/**
* Get global LESS variables.
*
- * $since 1.22
- * @return array: Map of variable names to string CSS values.
+ * @param Config $config
+ * @since 1.22
+ * @return array Map of variable names to string CSS values.
*/
- public static function getLESSVars() {
- global $wgResourceLoaderLESSVars;
-
- static $lessVars = null;
- if ( $lessVars === null ) {
- $lessVars = $wgResourceLoaderLESSVars;
- // Sort by key to ensure consistent hashing for cache lookups.
- ksort( $lessVars );
- }
+ public static function getLessVars( Config $config ) {
+ $lessVars = $config->get( 'ResourceLoaderLESSVars' );
+ // Sort by key to ensure consistent hashing for cache lookups.
+ ksort( $lessVars );
return $lessVars;
}
}
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
index 22ff6a7e..7af7b898 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -27,7 +27,6 @@
* of a specific loader request
*/
class ResourceLoaderContext {
-
/* Protected Members */
protected $resourceLoader;
@@ -46,12 +45,10 @@ class ResourceLoaderContext {
/* Methods */
/**
- * @param $resourceLoader ResourceLoader
- * @param $request WebRequest
+ * @param ResourceLoader $resourceLoader
+ * @param WebRequest $request
*/
- public function __construct( $resourceLoader, WebRequest $request ) {
- global $wgDefaultSkin, $wgResourceLoaderDebug;
-
+ public function __construct( ResourceLoader $resourceLoader, WebRequest $request ) {
$this->resourceLoader = $resourceLoader;
$this->request = $request;
@@ -62,7 +59,9 @@ class ResourceLoaderContext {
// Various parameters
$this->skin = $request->getVal( 'skin' );
$this->user = $request->getVal( 'user' );
- $this->debug = $request->getFuzzyBool( 'debug', $wgResourceLoaderDebug );
+ $this->debug = $request->getFuzzyBool(
+ 'debug', $resourceLoader->getConfig()->get( 'ResourceLoaderDebug' )
+ );
$this->only = $request->getVal( 'only' );
$this->version = $request->getVal( 'version' );
$this->raw = $request->getFuzzyBool( 'raw' );
@@ -70,7 +69,7 @@ class ResourceLoaderContext {
$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 = $wgDefaultSkin;
+ $this->skin = $resourceLoader->getConfig()->get( 'DefaultSkin' );
}
}
@@ -79,12 +78,10 @@ class ResourceLoaderContext {
* an array of module names like array( 'jquery.foo', 'jquery.bar',
* 'jquery.ui.baz', 'jquery.ui.quux' )
* @param string $modules Packed module name list
- * @return array of module names
+ * @return array Array of module names
*/
public static function expandModuleNames( $modules ) {
$retval = array();
- // For backwards compatibility with an earlier hack, replace ! with .
- $modules = str_replace( '!', '.', $modules );
$exploded = explode( '|', $modules );
foreach ( $exploded as $group ) {
if ( strpos( $group, ',' ) === false ) {
@@ -111,11 +108,14 @@ class ResourceLoaderContext {
}
/**
- * Return a dummy ResourceLoaderContext object suitable for passing into things that don't "really" need a context
+ * Return a dummy ResourceLoaderContext object suitable for passing into
+ * things that don't "really" need a context.
* @return ResourceLoaderContext
*/
public static function newDummyContext() {
- return new self( null, new FauxRequest( array() ) );
+ return new self( new ResourceLoader(
+ ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+ ), new FauxRequest( array() ) );
}
/**
@@ -144,11 +144,8 @@ class ResourceLoaderContext {
*/
public function getLanguage() {
if ( $this->language === null ) {
- global $wgLang;
- $this->language = $this->request->getVal( 'lang' );
- if ( !$this->language ) {
- $this->language = $wgLang->getCode();
- }
+ // Must be a valid language code after this point (bug 62849)
+ $this->language = RequestContext::sanitizeLangCode( $this->request->getVal( 'lang' ) );
}
return $this->language;
}
@@ -160,7 +157,7 @@ class ResourceLoaderContext {
if ( $this->direction === null ) {
$this->direction = $this->request->getVal( 'dir' );
if ( !$this->direction ) {
- # directionality based on user language (see bug 6100)
+ // Determine directionality based on user language (bug 6100)
$this->direction = Language::factory( $this->getLanguage() )->getDir();
}
}
@@ -189,14 +186,14 @@ class ResourceLoaderContext {
}
/**
- * @return String|null
+ * @return string|null
*/
public function getOnly() {
return $this->only;
}
/**
- * @return String|null
+ * @return string|null
*/
public function getVersion() {
return $this->version;
@@ -213,21 +210,21 @@ class ResourceLoaderContext {
* @return bool
*/
public function shouldIncludeScripts() {
- return is_null( $this->only ) || $this->only === 'scripts';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'scripts';
}
/**
* @return bool
*/
public function shouldIncludeStyles() {
- return is_null( $this->only ) || $this->only === 'styles';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'styles';
}
/**
* @return bool
*/
public function shouldIncludeMessages() {
- return is_null( $this->only ) || $this->only === 'messages';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'messages';
}
/**
@@ -236,8 +233,8 @@ class ResourceLoaderContext {
public function getHash() {
if ( !isset( $this->hash ) ) {
$this->hash = implode( '|', array(
- $this->getLanguage(), $this->getDirection(), $this->skin, $this->user,
- $this->debug, $this->only, $this->version
+ $this->getLanguage(), $this->getDirection(), $this->getSkin(), $this->getUser(),
+ $this->getDebug(), $this->getOnly(), $this->getVersion()
) );
}
return $this->hash;
diff --git a/includes/resourceloader/ResourceLoaderEditToolbarModule.php b/includes/resourceloader/ResourceLoaderEditToolbarModule.php
new file mode 100644
index 00000000..2e07911c
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderEditToolbarModule.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * Resource loader module for the edit toolbar.
+ *
+ * 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
+ */
+
+/**
+ * ResourceLoader module for the edit toolbar.
+ *
+ * @since 1.24
+ */
+class ResourceLoaderEditToolbarModule extends ResourceLoaderFileModule {
+ /**
+ * Serialize a string (escape and quote) for use as a CSS string value.
+ * http://www.w3.org/TR/2013/WD-cssom-20131205/#serialize-a-string
+ *
+ * @param string $value
+ * @return string
+ */
+ private static function cssSerializeString( $value ) {
+ if ( strstr( $value, "\0" ) ) {
+ throw new Exception( "Invalid character in CSS string" );
+ }
+ $value = strtr( $value, array( '\\' => '\\\\', '"' => '\\"' ) );
+ $value = preg_replace_callback( '/[\x01-\x1f\x7f-\x9f]/', function ( $match ) {
+ return '\\' . base_convert( ord( $match[0] ), 10, 16 ) . ' ';
+ }, $value );
+ return '"' . $value . '"';
+ }
+
+ /**
+ * Get language-specific LESS variables for this module.
+ *
+ * @return array
+ */
+ private function getLessVars( ResourceLoaderContext $context ) {
+ $language = Language::factory( $context->getLanguage() );
+
+ // This is very conveniently formatted and we can pass it right through
+ $vars = $language->getImageFiles();
+
+ // lessc tries to be helpful and parse our variables as LESS source code
+ foreach ( $vars as $key => &$value ) {
+ $value = self::cssSerializeString( $value );
+ }
+
+ return $vars;
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return max(
+ parent::getModifiedTime( $context ),
+ $this->getHashMtime( $context )
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5(
+ parent::getModifiedHash( $context ) .
+ serialize( $this->getLessVars( $context ) )
+ );
+ }
+
+ /**
+ * Get a LESS compiler instance for this module.
+ *
+ * Set our variables in it.
+ *
+ * @throws MWException
+ * @param ResourceLoaderContext $context
+ * @return lessc
+ */
+ protected function getLessCompiler( ResourceLoaderContext $context = null ) {
+ $compiler = parent::getLessCompiler();
+ $compiler->setVariables( $this->getLessVars( $context ) );
+ return $compiler;
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index 9ed181ed..dc8b14a2 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -26,111 +26,131 @@
* ResourceLoader module based on local JavaScript/CSS files.
*/
class ResourceLoaderFileModule extends ResourceLoaderModule {
-
/* Protected Members */
- /** String: Local base path, see __construct() */
+ /** @var string Local base path, see __construct() */
protected $localBasePath = '';
- /** String: Remote base path, see __construct() */
+
+ /** @var string Remote base path, see __construct() */
protected $remoteBasePath = '';
+
/**
- * Array: List of paths to JavaScript files to always include
+ * @var array List of paths to JavaScript files to always include
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $scripts = array();
+
/**
- * Array: List of JavaScript files to include when using a specific language
+ * @var array List of JavaScript files to include when using a specific language
* @par Usage:
* @code
* array( [language-code] => array( [file-path], [file-path], ... ), ... )
* @endcode
*/
protected $languageScripts = array();
+
/**
- * Array: List of JavaScript files to include when using a specific skin
+ * @var array List of JavaScript files to include when using a specific skin
* @par Usage:
* @code
* array( [skin-name] => array( [file-path], [file-path], ... ), ... )
* @endcode
*/
protected $skinScripts = array();
+
/**
- * Array: List of paths to JavaScript files to include in debug mode
+ * @var array List of paths to JavaScript files to include in debug mode
* @par Usage:
* @code
* array( [skin-name] => array( [file-path], [file-path], ... ), ... )
* @endcode
*/
protected $debugScripts = array();
+
/**
- * Array: List of paths to JavaScript files to include in the startup module
+ * @var array List of paths to JavaScript files to include in the startup module
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $loaderScripts = array();
+
/**
- * Array: List of paths to CSS files to always include
+ * @var array List of paths to CSS files to always include
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $styles = array();
+
/**
- * Array: List of paths to CSS files to include when using specific skins
+ * @var array List of paths to CSS files to include when using specific skins
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $skinStyles = array();
+
/**
- * Array: List of modules this module depends on
+ * @var array List of modules this module depends on
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $dependencies = array();
+
+ /**
+ * @var string File name containing the body of the skip function
+ */
+ protected $skipFunction = null;
+
/**
- * Array: List of message keys used by this module
+ * @var array List of message keys used by this module
* @par Usage:
* @code
* array( [message-key], [message-key], ... )
* @endcode
*/
protected $messages = array();
- /** String: Name of group to load this module in */
+
+ /** @var string Name of group to load this module in */
protected $group;
- /** String: Position on the page to load this module at */
+
+ /** @var string Position on the page to load this module at */
protected $position = 'bottom';
- /** Boolean: Link to raw files in debug mode */
+
+ /** @var bool Link to raw files in debug mode */
protected $debugRaw = true;
- /** Boolean: Whether mw.loader.state() call should be omitted */
+
+ /** @var bool Whether mw.loader.state() call should be omitted */
protected $raw = false;
+
protected $targets = array( 'desktop' );
/**
- * Boolean: Whether getStyleURLsForDebug should return raw file paths,
+ * @var bool Whether getStyleURLsForDebug should return raw file paths,
* or return load.php urls
*/
protected $hasGeneratedStyles = false;
/**
- * Array: Cache for mtime
+ * @var array Cache for mtime
* @par Usage:
* @code
* array( [hash] => [mtime], [hash] => [mtime], ... )
* @endcode
*/
protected $modifiedTime = array();
+
/**
- * Array: Place where readStyleFile() tracks file dependencies
+ * @var array Place where readStyleFile() tracks file dependencies
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
@@ -148,7 +168,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* @param string $localBasePath Base path to prepend to all local paths in $options. Defaults
* to $IP
* @param string $remoteBasePath Base path to prepend to all remote paths in $options. Defaults
- * to $wgScriptPath
+ * to $wgResourceBasePath
*
* Below is a description for the $options array:
* @throws MWException
@@ -157,10 +177,12 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* array(
* // Base path to prepend to all local paths in $options. Defaults to $IP
* 'localBasePath' => [base path],
- * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
+ * // Base path to prepend to all remote paths in $options. Defaults to $wgResourceBasePath
* 'remoteBasePath' => [base path],
* // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
* 'remoteExtPath' => [base path],
+ * // Equivalent of remoteBasePath, but relative to $wgStylePath
+ * 'remoteSkinPath' => [base path],
* // Scripts to always include
* 'scripts' => [file path string or array of file path strings],
* // Scripts to include in specific language contexts
@@ -189,25 +211,24 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* 'group' => [group name string],
* // Position on the page to load this module at
* 'position' => ['bottom' (default) or 'top']
+ * // Function that, if it returns true, makes the loader skip this module.
+ * // The file must contain valid JavaScript for execution in a private function.
+ * // The file must not contain the "function () {" and "}" wrapper though.
+ * 'skipFunction' => [file path]
* )
* @endcode
*/
- public function __construct( $options = array(), $localBasePath = null,
+ public function __construct(
+ $options = array(),
+ $localBasePath = null,
$remoteBasePath = null
) {
- global $IP, $wgScriptPath, $wgResourceBasePath;
- $this->localBasePath = $localBasePath === null ? $IP : $localBasePath;
- if ( $remoteBasePath !== null ) {
- $this->remoteBasePath = $remoteBasePath;
- } else {
- $this->remoteBasePath = $wgResourceBasePath === null ? $wgScriptPath : $wgResourceBasePath;
- }
-
- if ( isset( $options['remoteExtPath'] ) ) {
- global $wgExtensionAssetsPath;
- $this->remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath'];
- }
+ // localBasePath and remoteBasePath both have unbelievably long fallback chains
+ // and need to be handled separately.
+ list( $this->localBasePath, $this->remoteBasePath ) =
+ self::extractBasePaths( $options, $localBasePath, $remoteBasePath );
+ // Extract, validate and normalise remaining options
foreach ( $options as $member => $option ) {
switch ( $member ) {
// Lists of file paths
@@ -241,13 +262,16 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
case 'dependencies':
case 'messages':
case 'targets':
- $this->{$member} = (array)$option;
+ // Normalise
+ $option = array_values( array_unique( (array)$option ) );
+ sort( $option );
+
+ $this->{$member} = $option;
break;
// Single strings
case 'group':
case 'position':
- case 'localBasePath':
- case 'remoteBasePath':
+ case 'skipFunction':
$this->{$member} = (string)$option;
break;
// Single booleans
@@ -257,16 +281,64 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
break;
}
}
+ }
+
+ /**
+ * Extract a pair of local and remote base paths from module definition information.
+ * Implementation note: the amount of global state used in this function is staggering.
+ *
+ * @param array $options Module definition
+ * @param string $localBasePath Path to use if not provided in module definition. Defaults
+ * to $IP
+ * @param string $remoteBasePath Path to use if not provided in module definition. Defaults
+ * to $wgResourceBasePath
+ * @return array Array( localBasePath, remoteBasePath )
+ */
+ public static function extractBasePaths(
+ $options = array(),
+ $localBasePath = null,
+ $remoteBasePath = null
+ ) {
+ global $IP, $wgResourceBasePath;
+
+ // The different ways these checks are done, and their ordering, look very silly,
+ // but were preserved for backwards-compatibility just in case. Tread lightly.
+
+ $localBasePath = $localBasePath === null ? $IP : $localBasePath;
+ if ( $remoteBasePath === null ) {
+ $remoteBasePath = $wgResourceBasePath;
+ }
+
+ if ( isset( $options['remoteExtPath'] ) ) {
+ global $wgExtensionAssetsPath;
+ $remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath'];
+ }
+
+ if ( isset( $options['remoteSkinPath'] ) ) {
+ global $wgStylePath;
+ $remoteBasePath = $wgStylePath . '/' . $options['remoteSkinPath'];
+ }
+
+ if ( array_key_exists( 'localBasePath', $options ) ) {
+ $localBasePath = (string)$options['localBasePath'];
+ }
+
+ if ( array_key_exists( 'remoteBasePath', $options ) ) {
+ $remoteBasePath = (string)$options['remoteBasePath'];
+ }
+
// Make sure the remote base path is a complete valid URL,
// but possibly protocol-relative to avoid cache pollution
- $this->remoteBasePath = wfExpandUrl( $this->remoteBasePath, PROTO_RELATIVE );
+ $remoteBasePath = wfExpandUrl( $remoteBasePath, PROTO_RELATIVE );
+
+ return array( $localBasePath, $remoteBasePath );
}
/**
* Gets all scripts for a given context concatenated together.
*
* @param ResourceLoaderContext $context Context in which to generate script
- * @return string: JavaScript code for $context
+ * @return string JavaScript code for $context
*/
public function getScript( ResourceLoaderContext $context ) {
$files = $this->getScriptFiles( $context );
@@ -293,27 +365,28 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
- * Gets loader script.
+ * Get loader script.
*
- * @return string: JavaScript code to be added to startup module
+ * @return string|bool JavaScript code to be added to startup module
*/
public function getLoaderScript() {
- if ( count( $this->loaderScripts ) == 0 ) {
+ if ( count( $this->loaderScripts ) === 0 ) {
return false;
}
return $this->readScriptFiles( $this->loaderScripts );
}
/**
- * Gets all styles for a given context concatenated together.
+ * Get all styles for a given context.
*
- * @param ResourceLoaderContext $context Context in which to generate styles
- * @return string: CSS code for $context
+ * @param ResourceLoaderContext $context
+ * @return array CSS code for $context as an associative array mapping media type to CSS text.
*/
public function getStyles( ResourceLoaderContext $context ) {
$styles = $this->readStyleFiles(
$this->getStyleFiles( $context ),
- $this->getFlip( $context )
+ $this->getFlip( $context ),
+ $context
);
// Collect referenced files
$this->localFileRefs = array_unique( $this->localFileRefs );
@@ -360,7 +433,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets list of message keys used by this module.
*
- * @return array: List of message keys
+ * @return array List of message keys
*/
public function getMessages() {
return $this->messages;
@@ -369,7 +442,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets the name of the group this module should be loaded in.
*
- * @return string: Group name
+ * @return string Group name
*/
public function getGroup() {
return $this->group;
@@ -385,13 +458,34 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets list of names of modules this module depends on.
*
- * @return array: List of module names
+ * @return array List of module names
*/
public function getDependencies() {
return $this->dependencies;
}
/**
+ * Get the skip function.
+ *
+ * @return string|null
+ */
+ public function getSkipFunction() {
+ if ( !$this->skipFunction ) {
+ return null;
+ }
+
+ $localPath = $this->getLocalPath( $this->skipFunction );
+ if ( !file_exists( $localPath ) ) {
+ throw new MWException( __METHOD__ . ": skip function file not found: \"$localPath\"" );
+ }
+ $contents = file_get_contents( $localPath );
+ if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) {
+ $contents = $this->validateScriptFile( $localPath, $contents );
+ }
+ return $contents;
+ }
+
+ /**
* @return bool
*/
public function isRaw() {
@@ -409,7 +503,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param ResourceLoaderContext $context Context in which to calculate
* the modified time
- * @return int: UNIX timestamp
+ * @return int UNIX timestamp
* @see ResourceLoaderModule::getFileDependencies
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
@@ -425,10 +519,11 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
foreach ( $styles as $styleFiles ) {
$files = array_merge( $files, $styleFiles );
}
- $skinFiles = self::tryForKey(
- self::collateFilePathListByOption( $this->skinStyles, 'media', 'all' ),
- $context->getSkin(),
- 'default'
+
+ $skinFiles = self::collateFilePathListByOption(
+ self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
+ 'media',
+ 'all'
);
foreach ( $skinFiles as $styleFiles ) {
$files = array_merge( $files, $styleFiles );
@@ -443,6 +538,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ),
$this->loaderScripts
);
+ if ( $this->skipFunction ) {
+ $files[] = $this->skipFunction;
+ }
$files = array_map( array( $this, 'getLocalPath' ), $files );
// File deps need to be treated separately because they're already prefixed
$files = array_merge( $files, $this->getFileDependencies( $context->getSkin() ) );
@@ -450,36 +548,82 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
// If a module is nothing but a list of dependencies, we need to avoid
// giving max() an empty array
if ( count( $files ) === 0 ) {
+ $this->modifiedTime[$context->getHash()] = 1;
wfProfileOut( __METHOD__ );
- return $this->modifiedTime[$context->getHash()] = 1;
+ return $this->modifiedTime[$context->getHash()];
}
wfProfileIn( __METHOD__ . '-filemtime' );
$filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) );
wfProfileOut( __METHOD__ . '-filemtime' );
+
$this->modifiedTime[$context->getHash()] = max(
$filesMtime,
- $this->getMsgBlobMtime( $context->getLanguage() ) );
+ $this->getMsgBlobMtime( $context->getLanguage() ),
+ $this->getDefinitionMtime( $context )
+ );
wfProfileOut( __METHOD__ );
return $this->modifiedTime[$context->getHash()];
}
+ /**
+ * Get the definition summary for this module.
+ *
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getDefinitionSummary( ResourceLoaderContext $context ) {
+ $summary = array(
+ 'class' => get_class( $this ),
+ );
+ foreach ( array(
+ 'scripts',
+ 'debugScripts',
+ 'loaderScripts',
+ 'styles',
+ 'languageScripts',
+ 'skinScripts',
+ 'skinStyles',
+ 'dependencies',
+ 'messages',
+ 'targets',
+ 'group',
+ 'position',
+ 'skipFunction',
+ 'localBasePath',
+ 'remoteBasePath',
+ 'debugRaw',
+ 'raw',
+ ) as $member ) {
+ $summary[$member] = $this->{$member};
+ };
+ return $summary;
+ }
+
/* Protected Methods */
/**
- * @param string $path
+ * @param string|ResourceLoaderFilePath $path
* @return string
*/
protected function getLocalPath( $path ) {
+ if ( $path instanceof ResourceLoaderFilePath ) {
+ return $path->getLocalPath();
+ }
+
return "{$this->localBasePath}/$path";
}
/**
- * @param string $path
+ * @param string|ResourceLoaderFilePath $path
* @return string
*/
protected function getRemotePath( $path ) {
+ if ( $path instanceof ResourceLoaderFilePath ) {
+ return $path->getRemotePath();
+ }
+
return "{$this->remoteBasePath}/$path";
}
@@ -488,7 +632,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @since 1.22
* @param string $path
- * @return string: the stylesheet language name
+ * @return string The stylesheet language name
*/
public function getStyleSheetLang( $path ) {
return preg_match( '/\.less$/i', $path ) ? 'less' : 'css';
@@ -499,9 +643,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param array $list List of file paths in any combination of index/path
* or path/options pairs
- * @param string $option option name
- * @param mixed $default default value if the option isn't set
- * @return array: List of file paths, collated by $option
+ * @param string $option Option name
+ * @param mixed $default Default value if the option isn't set
+ * @return array List of file paths, collated by $option
*/
protected static function collateFilePathListByOption( array $list, $option, $default ) {
$collatedFiles = array();
@@ -525,31 +669,31 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
- * Gets a list of element that match a key, optionally using a fallback key.
+ * Get a list of element that match a key, optionally using a fallback key.
*
* @param array $list List of lists to select from
* @param string $key Key to look for in $map
* @param string $fallback Key to look for in $list if $key doesn't exist
- * @return array: List of elements from $map which matched $key or $fallback,
- * or an empty list in case of no match
+ * @return array List of elements from $map which matched $key or $fallback,
+ * or an empty list in case of no match
*/
protected static function tryForKey( array $list, $key, $fallback = null ) {
if ( isset( $list[$key] ) && is_array( $list[$key] ) ) {
return $list[$key];
} elseif ( is_string( $fallback )
&& isset( $list[$fallback] )
- && is_array( $list[$fallback] ) )
- {
+ && is_array( $list[$fallback] )
+ ) {
return $list[$fallback];
}
return array();
}
/**
- * Gets a list of file paths for all scripts in this module, in order of propper execution.
+ * Get a list of file paths for all scripts in this module, in order of proper execution.
*
* @param ResourceLoaderContext $context
- * @return array: List of file paths
+ * @return array List of file paths
*/
protected function getScriptFiles( ResourceLoaderContext $context ) {
$files = array_merge(
@@ -561,39 +705,82 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$files = array_merge( $files, $this->debugScripts );
}
- return array_unique( $files );
+ return array_unique( $files, SORT_REGULAR );
}
/**
- * Gets a list of file paths for all styles in this module, in order of propper inclusion.
+ * Get a list of file paths for all styles in this module, in order of proper inclusion.
*
* @param ResourceLoaderContext $context
- * @return array: List of file paths
+ * @return array List of file paths
*/
- protected function getStyleFiles( ResourceLoaderContext $context ) {
+ public function getStyleFiles( ResourceLoaderContext $context ) {
return array_merge_recursive(
self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
self::collateFilePathListByOption(
- self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), 'media', 'all'
+ self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
+ 'media',
+ 'all'
)
);
}
/**
- * Returns all style files used by this module
+ * Gets a list of file paths for all skin styles in the module used by
+ * the skin.
+ *
+ * @param string $skinName The name of the skin
+ * @return array A list of file paths collated by media type
+ */
+ protected function getSkinStyleFiles( $skinName ) {
+ return self::collateFilePathListByOption(
+ self::tryForKey( $this->skinStyles, $skinName ),
+ 'media',
+ 'all'
+ );
+ }
+
+ /**
+ * Gets a list of file paths for all skin style files in the module,
+ * for all available skins.
+ *
+ * @return array A list of file paths collated by media type
+ */
+ protected function getAllSkinStyleFiles() {
+ $styleFiles = array();
+ $internalSkinNames = array_keys( Skin::getSkinNames() );
+ $internalSkinNames[] = 'default';
+
+ foreach ( $internalSkinNames as $internalSkinName ) {
+ $styleFiles = array_merge_recursive(
+ $styleFiles,
+ $this->getSkinStyleFiles( $internalSkinName )
+ );
+ }
+
+ return $styleFiles;
+ }
+
+ /**
+ * Returns all style files and all skin style files used by this module.
+ *
* @return array
*/
public function getAllStyleFiles() {
- $files = array();
- foreach( (array)$this->styles as $key => $value ) {
- if ( is_array( $value ) ) {
- $path = $key;
- } else {
- $path = $value;
+ $collatedStyleFiles = array_merge_recursive(
+ self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
+ $this->getAllSkinStyleFiles()
+ );
+
+ $result = array();
+
+ foreach ( $collatedStyleFiles as $media => $styleFiles ) {
+ foreach ( $styleFiles as $styleFile ) {
+ $result[] = $this->getLocalPath( $styleFile );
}
- $files[] = $this->getLocalPath( $path );
}
- return $files;
+
+ return $result;
}
/**
@@ -601,21 +788,20 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param array $scripts List of file paths to scripts to read, remap and concetenate
* @throws MWException
- * @return string: Concatenated and remapped JavaScript data from $scripts
+ * @return string Concatenated and remapped JavaScript data from $scripts
*/
protected function readScriptFiles( array $scripts ) {
- global $wgResourceLoaderValidateStaticJS;
if ( empty( $scripts ) ) {
return '';
}
$js = '';
- foreach ( array_unique( $scripts ) as $fileName ) {
+ foreach ( array_unique( $scripts, SORT_REGULAR ) as $fileName ) {
$localPath = $this->getLocalPath( $fileName );
if ( !file_exists( $localPath ) ) {
throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" );
}
$contents = file_get_contents( $localPath );
- if ( $wgResourceLoaderValidateStaticJS ) {
+ if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) {
// Static files don't really need to be checked as often; unlike
// on-wiki module they shouldn't change unexpectedly without
// admin interference.
@@ -631,26 +817,24 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param array $styles List of media type/list of file paths pairs, to read, remap and
* concetenate
- *
* @param bool $flip
+ * @param ResourceLoaderContext $context (optional)
*
- * @return array: List of concatenated and remapped CSS data from $styles,
+ * @throws MWException
+ * @return array List of concatenated and remapped CSS data from $styles,
* keyed by media type
*/
- protected function readStyleFiles( array $styles, $flip ) {
+ public function readStyleFiles( array $styles, $flip, $context = null ) {
if ( empty( $styles ) ) {
return array();
}
foreach ( $styles as $media => $files ) {
- $uniqueFiles = array_unique( $files );
- $styles[$media] = implode(
- "\n",
- array_map(
- array( $this, 'readStyleFile' ),
- $uniqueFiles,
- array_fill( 0, count( $uniqueFiles ), $flip )
- )
- );
+ $uniqueFiles = array_unique( $files, SORT_REGULAR );
+ $styleFiles = array();
+ foreach ( $uniqueFiles as $file ) {
+ $styleFiles[] = $this->readStyleFile( $file, $flip, $context );
+ }
+ $styles[$media] = implode( "\n", $styleFiles );
}
return $styles;
}
@@ -662,20 +846,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param string $path File path of style file to read
* @param bool $flip
+ * @param ResourceLoaderContext $context (optional)
*
- * @return string: CSS data in script file
- * @throws MWException if the file doesn't exist
+ * @return string CSS data in script file
+ * @throws MWException If the file doesn't exist
*/
- protected function readStyleFile( $path, $flip ) {
+ protected function readStyleFile( $path, $flip, $context = null ) {
$localPath = $this->getLocalPath( $path );
+ $remotePath = $this->getRemotePath( $path );
if ( !file_exists( $localPath ) ) {
$msg = __METHOD__ . ": style file not found: \"$localPath\"";
wfDebugLog( 'resourceloader', $msg );
throw new MWException( $msg );
}
- if ( $this->getStyleSheetLang( $path ) === 'less' ) {
- $style = $this->compileLESSFile( $localPath );
+ if ( $this->getStyleSheetLang( $localPath ) === 'less' ) {
+ $compiler = $this->getLessCompiler( $context );
+ $style = $this->compileLessFile( $localPath, $compiler );
$this->hasGeneratedStyles = true;
} else {
$style = file_get_contents( $localPath );
@@ -684,20 +871,15 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
if ( $flip ) {
$style = CSSJanus::transform( $style, true, false );
}
- $dirname = dirname( $path );
- if ( $dirname == '.' ) {
- // If $path doesn't have a directory component, don't prepend a dot
- $dirname = '';
- }
- $dir = $this->getLocalPath( $dirname );
- $remoteDir = $this->getRemotePath( $dirname );
+ $localDir = dirname( $localPath );
+ $remoteDir = dirname( $remotePath );
// Get and register local file references
$this->localFileRefs = array_merge(
$this->localFileRefs,
- CSSMin::getLocalFileReferences( $style, $dir )
+ CSSMin::getLocalFileReferences( $style, $localDir )
);
return CSSMin::remap(
- $style, $dir, $remoteDir, true
+ $style, $localDir, $remoteDir, true
);
}
@@ -713,64 +895,43 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile']
*
- * @return array of strings
+ * @return array Array of strings
*/
public function getTargets() {
return $this->targets;
}
/**
- * Generate a cache key for a LESS file.
+ * Compile a LESS file into CSS.
*
- * The cache key varies on the file name and the names and values of global
- * LESS variables.
+ * Keeps track of all used files and adds them to localFileRefs.
*
* @since 1.22
- * @param string $fileName File name of root LESS file.
- * @return string: Cache key
+ * @throws Exception If lessc encounters a parse error
+ * @param string $fileName File path of LESS source
+ * @param lessc $compiler Compiler to use, if not default
+ * @return string CSS source
*/
- protected static function getLESSCacheKey( $fileName ) {
- $vars = json_encode( ResourceLoader::getLESSVars() );
- $hash = md5( $fileName . $vars );
- return wfMemcKey( 'resourceloader', 'less', $hash );
+ protected function compileLessFile( $fileName, $compiler = null ) {
+ if ( !$compiler ) {
+ $compiler = $this->getLessCompiler();
+ }
+ $result = $compiler->compileFile( $fileName );
+ $this->localFileRefs += array_keys( $compiler->allParsedFiles() );
+ return $result;
}
/**
- * Compile a LESS file into CSS.
+ * Get a LESS compiler instance for this module in given context.
*
- * If invalid, returns replacement CSS source consisting of the compilation
- * error message encoded as a comment. To save work, we cache a result object
- * which comprises the compiled CSS and the names & mtimes of the files
- * that were processed. lessphp compares the cached & current mtimes and
- * recompiles as necessary.
+ * Just calls ResourceLoader::getLessCompiler() by default to get a global compiler.
*
- * @since 1.22
- * @param string $fileName File path of LESS source
- * @return string: CSS source
+ * @param ResourceLoaderContext $context
+ * @throws MWException
+ * @since 1.24
+ * @return lessc
*/
- protected function compileLESSFile( $fileName ) {
- $key = self::getLESSCacheKey( $fileName );
- $cache = wfGetCache( CACHE_ANYTHING );
-
- // The input to lessc. Either an associative array representing the
- // cached results of a previous compilation, or the string file name if
- // no cache result exists.
- $source = $cache->get( $key );
- if ( !is_array( $source ) || !isset( $source['root'] ) ) {
- $source = $fileName;
- }
-
- $compiler = ResourceLoader::getLessCompiler();
- $result = null;
-
- $result = $compiler->cachedCompile( $source );
-
- if ( !is_array( $result ) ) {
- throw new MWException( 'LESS compiler result has type ' . gettype( $result ) . '; array expected.' );
- }
-
- $this->localFileRefs += array_keys( $result['files'] );
- $cache->set( $key, $result );
- return $result['compiled'];
+ protected function getLessCompiler( ResourceLoaderContext $context = null ) {
+ return ResourceLoader::getLessCompiler( $this->getConfig() );
}
}
diff --git a/includes/resourceloader/ResourceLoaderFilePageModule.php b/includes/resourceloader/ResourceLoaderFilePageModule.php
index 61ed5206..8c7fbe76 100644
--- a/includes/resourceloader/ResourceLoaderFilePageModule.php
+++ b/includes/resourceloader/ResourceLoaderFilePageModule.php
@@ -26,7 +26,7 @@
class ResourceLoaderFilePageModule extends ResourceLoaderWikiModule {
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderFilePath.php b/includes/resourceloader/ResourceLoaderFilePath.php
new file mode 100644
index 00000000..dd239d09
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderFilePath.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * An object to represent a path to a JavaScript/CSS file, along with a remote
+ * and local base path, for use with ResourceLoaderFileModule.
+ *
+ * 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
+ */
+
+/**
+ * An object to represent a path to a JavaScript/CSS file, along with a remote
+ * and local base path, for use with ResourceLoaderFileModule.
+ */
+class ResourceLoaderFilePath {
+ /* Protected Members */
+
+ /** @var string Local base path */
+ protected $localBasePath;
+
+ /** @var string Remote base path */
+ protected $remoteBasePath;
+
+ /**
+ * @var string Path to the file */
+ protected $path;
+
+ /* Methods */
+
+ /**
+ * @param string $path Path to the file.
+ * @param string $localBasePath Base path to prepend when generating a local path.
+ * @param string $remoteBasePath Base path to prepend when generating a remote path.
+ */
+ public function __construct( $path, $localBasePath, $remoteBasePath ) {
+ $this->path = $path;
+ $this->localBasePath = $localBasePath;
+ $this->remoteBasePath = $remoteBasePath;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLocalPath() {
+ return "{$this->localBasePath}/{$this->path}";
+ }
+
+ /**
+ * @return string
+ */
+ public function getRemotePath() {
+ return "{$this->remoteBasePath}/{$this->path}";
+ }
+
+ /**
+ * @return string
+ */
+ public function getPath() {
+ return $this->path;
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderLESSFunctions.php b/includes/resourceloader/ResourceLoaderLESSFunctions.php
deleted file mode 100644
index c7570f64..00000000
--- a/includes/resourceloader/ResourceLoaderLESSFunctions.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/**
- * PHP-provided functions for LESS; see docs for $wgResourceLoaderLESSFunctions
- *
- * 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 ResourceLoaderLESSFunctions {
- /**
- * Check if an image file reference is suitable for embedding.
- * An image is embeddable if it (a) exists, (b) has a suitable MIME-type,
- * (c) does not exceed IE<9 size limit of 32kb. This is a LESS predicate
- * function; it returns a LESS boolean value and can thus be used as a
- * mixin guard.
- *
- * @par Example:
- * @code
- * .background-image(@url) when(embeddable(@url)) {
- * background-image: url(@url) !ie;
- * }
- * @endcode
- */
- public static function embeddable( $frame, $less ) {
- $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME );
- $url = $frame[2][0];
- $file = realpath( $base . '/' . $url );
- return $less->toBool( $file
- && strpos( $url, '//' ) === false
- && filesize( $file ) < CSSMin::EMBED_SIZE_LIMIT
- && CSSMin::getMimeType( $file ) !== false );
- }
-
- /**
- * Convert an image URI to a base64-encoded data URI.
- *
- * @par Example:
- * @code
- * .fancy-button {
- * background-image: embed('../images/button-bg.png');
- * }
- * @endcode
- */
- public static function embed( $frame, $less ) {
- $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME );
- $url = $frame[2][0];
- $file = realpath( $base . '/' . $url );
-
- $data = CSSMin::encodeImageAsDataURI( $file );
- $less->addParsedFile( $file );
- return 'url(' . $data . ')';
- }
-}
diff --git a/includes/resourceloader/ResourceLoaderLanguageDataModule.php b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
index fa0fbf85..09d90d6e 100644
--- a/includes/resourceloader/ResourceLoaderLanguageDataModule.php
+++ b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
@@ -27,99 +27,51 @@
*/
class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
- protected $language;
protected $targets = array( 'desktop', 'mobile' );
- /**
- * Get the grammar forms for the site content language.
- *
- * @return array
- */
- protected function getSiteLangGrammarForms() {
- return $this->language->getGrammarForms();
- }
-
- /**
- * Get the plural forms for the site content language.
- *
- * @return array
- */
- protected function getPluralRules() {
- return $this->language->getPluralRules();
- }
-
- /**
- * Get the digit groupin Pattern for the site content language.
- *
- * @return array
- */
- protected function getDigitGroupingPattern() {
- return $this->language->digitGroupingPattern();
- }
-
- /**
- * Get the digit transform table for the content language
- *
- * @return array
- */
- protected function getDigitTransformTable() {
- return $this->language->digitTransformTable();
- }
-
- /**
- * Get seperator transform table required for converting
- * the . and , sign to appropriate forms in site content language.
- *
- * @return array
- */
- protected function getSeparatorTransformTable() {
- return $this->language->separatorTransformTable();
- }
/**
* Get all the dynamic data for the content language to an array.
*
- * NOTE: Before calling this you HAVE to make sure $this->language is set.
- *
+ * @param ResourceLoaderContext $context
* @return array
*/
- protected function getData() {
+ protected function getData( ResourceLoaderContext $context ) {
+ $language = Language::factory( $context->getLanguage() );
return array(
- 'digitTransformTable' => $this->getDigitTransformTable(),
- 'separatorTransformTable' => $this->getSeparatorTransformTable(),
- 'grammarForms' => $this->getSiteLangGrammarForms(),
- 'pluralRules' => $this->getPluralRules(),
- 'digitGroupingPattern' => $this->getDigitGroupingPattern(),
+ 'digitTransformTable' => $language->digitTransformTable(),
+ 'separatorTransformTable' => $language->separatorTransformTable(),
+ 'grammarForms' => $language->getGrammarForms(),
+ 'pluralRules' => $language->getPluralRules(),
+ 'digitGroupingPattern' => $language->digitGroupingPattern(),
+ 'fallbackLanguages' => $language->getFallbackLanguages(),
);
}
/**
- * @param $context ResourceLoaderContext
- * @return string: JavaScript code
+ * @param ResourceLoaderContext $context
+ * @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
- $this->language = Language::factory( $context->getLanguage() );
return Xml::encodeJsCall( 'mw.language.setData', array(
- $this->language->getCode(),
- $this->getData()
+ $context->getLanguage(),
+ $this->getData( $context )
) );
}
/**
- * @param $context ResourceLoaderContext
- * @return int: UNIX timestamp
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
return max( 1, $this->getHashMtime( $context ) );
}
/**
- * @param $context ResourceLoaderContext
- * @return string: Hash
+ * @param ResourceLoaderContext $context
+ * @return string Hash
*/
public function getModifiedHash( ResourceLoaderContext $context ) {
- $this->language = Language::factory( $context->getLanguage() );
-
- return md5( serialize( $this->getData() ) );
+ return md5( serialize( $this->getData( $context ) ) );
}
/**
diff --git a/includes/resourceloader/ResourceLoaderLanguageNamesModule.php b/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
new file mode 100644
index 00000000..fe0c8454
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Resource loader module for providing language names.
+ *
+ * By default these names will be autonyms however other extensions may
+ * provided language names in the context language (e.g. cldr extension)
+ *
+ * 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 Ed Sanders
+ * @author Trevor Parscal
+ */
+
+/**
+ * ResourceLoader module for populating language specific data.
+ */
+class ResourceLoaderLanguageNamesModule extends ResourceLoaderModule {
+
+ protected $targets = array( 'desktop', 'mobile' );
+
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ protected function getData( ResourceLoaderContext $context ) {
+ return Language::fetchLanguageNames(
+ $context->getLanguage(),
+ 'all'
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string JavaScript code
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall( 'mw.language.setData', array(
+ $context->getLanguage(),
+ 'languageNames',
+ $this->getData( $context )
+ ) );
+ }
+
+ public function getDependencies() {
+ return array( 'mediawiki.language.init' );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return max( 1, $this->getHashMtime( $context ) );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5( serialize( $this->getData( $context ) ) );
+ }
+
+}
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
index 11264fc8..45eb70f8 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -26,7 +26,6 @@
* Abstraction for resource loader modules, with name registration and maxage functionality.
*/
abstract class ResourceLoaderModule {
-
# Type of resource
const TYPE_SCRIPTS = 'scripts';
const TYPE_STYLES = 'styles';
@@ -65,13 +64,18 @@ abstract class ResourceLoaderModule {
// In-object cache for message blob mtime
protected $msgBlobMtime = array();
+ /**
+ * @var Config
+ */
+ protected $config;
+
/* Methods */
/**
* Get this module's name. This is set when the module is registered
* with ResourceLoader::register()
*
- * @return mixed: Name (string) or null if no name was set
+ * @return string|null Name (string) or null if no name was set
*/
public function getName() {
return $this->name;
@@ -91,7 +95,7 @@ abstract class ResourceLoaderModule {
* Get this module's origin. This is set when the module is registered
* with ResourceLoader::register()
*
- * @return int: ResourceLoaderModule class constant, the subclass default
+ * @return int ResourceLoaderModule class constant, the subclass default
* if not set manually
*/
public function getOrigin() {
@@ -102,7 +106,7 @@ abstract class ResourceLoaderModule {
* Set this module's origin. This is called by ResourceLoader::register()
* when registering the module. Other code should not call this.
*
- * @param int $origin origin
+ * @param int $origin Origin
*/
public function setOrigin( $origin ) {
$this->origin = $origin;
@@ -123,7 +127,7 @@ abstract class ResourceLoaderModule {
* Includes all relevant JS except loader scripts.
*
* @param ResourceLoaderContext $context
- * @return string: JavaScript code
+ * @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
// Stub, override expected
@@ -131,6 +135,27 @@ abstract class ResourceLoaderModule {
}
/**
+ * @return Config
+ * @since 1.24
+ */
+ public function getConfig() {
+ if ( $this->config === null ) {
+ // Ugh, fall back to default
+ $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+
+ return $this->config;
+ }
+
+ /**
+ * @param Config $config
+ * @since 1.24
+ */
+ public function setConfig( Config $config ) {
+ $this->config = $config;
+ }
+
+ /**
* Get the URL or URLs to load for this module's JS in debug mode.
* The default behavior is to return a load.php?only=scripts URL for
* the module, but file-based modules will want to override this to
@@ -142,20 +167,20 @@ abstract class ResourceLoaderModule {
* MUST return either an only= URL or a non-load.php URL.
*
* @param ResourceLoaderContext $context
- * @return array: Array of URLs
+ * @return array Array of URLs
*/
public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
- $url = ResourceLoader::makeLoaderURL(
- array( $this->getName() ),
- $context->getLanguage(),
- $context->getSkin(),
- $context->getUser(),
- $context->getVersion(),
- true, // debug
- 'scripts', // only
- $context->getRequest()->getBool( 'printable' ),
- $context->getRequest()->getBool( 'handheld' )
+ $resourceLoader = $context->getResourceLoader();
+ $derivative = new DerivativeResourceLoaderContext( $context );
+ $derivative->setModules( array( $this->getName() ) );
+ $derivative->setOnly( 'scripts' );
+ $derivative->setDebug( true );
+
+ $url = $resourceLoader->createLoaderURL(
+ $this->getSource(),
+ $derivative
);
+
return array( $url );
}
@@ -173,7 +198,7 @@ abstract class ResourceLoaderModule {
* Get all CSS for this module for a given skin.
*
* @param ResourceLoaderContext $context
- * @return array: List of CSS strings or array of CSS strings keyed by media type.
+ * @return array List of CSS strings or array of CSS strings keyed by media type.
* like array( 'screen' => '.foo { width: 0 }' );
* or array( 'screen' => array( '.foo { width: 0 }' ) );
*/
@@ -189,20 +214,20 @@ abstract class ResourceLoaderModule {
* load the files directly. See also getScriptURLsForDebug()
*
* @param ResourceLoaderContext $context
- * @return array: array( mediaType => array( URL1, URL2, ... ), ... )
+ * @return array Array( mediaType => array( URL1, URL2, ... ), ... )
*/
public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
- $url = ResourceLoader::makeLoaderURL(
- array( $this->getName() ),
- $context->getLanguage(),
- $context->getSkin(),
- $context->getUser(),
- $context->getVersion(),
- true, // debug
- 'styles', // only
- $context->getRequest()->getBool( 'printable' ),
- $context->getRequest()->getBool( 'handheld' )
+ $resourceLoader = $context->getResourceLoader();
+ $derivative = new DerivativeResourceLoaderContext( $context );
+ $derivative->setModules( array( $this->getName() ) );
+ $derivative->setOnly( 'styles' );
+ $derivative->setDebug( true );
+
+ $url = $resourceLoader->createLoaderURL(
+ $this->getSource(),
+ $derivative
);
+
return array( 'all' => array( $url ) );
}
@@ -211,7 +236,7 @@ abstract class ResourceLoaderModule {
*
* To get a JSON blob with messages, use MessageBlobStore::get()
*
- * @return array: List of message keys. Keys may occur more than once
+ * @return array List of message keys. Keys may occur more than once
*/
public function getMessages() {
// Stub, override expected
@@ -221,7 +246,7 @@ abstract class ResourceLoaderModule {
/**
* Get the group this module is in.
*
- * @return string: Group name
+ * @return string Group name
*/
public function getGroup() {
// Stub, override expected
@@ -231,7 +256,7 @@ abstract class ResourceLoaderModule {
/**
* Get the origin of this module. Should only be overridden for foreign modules.
*
- * @return string: Origin name, 'local' for local modules
+ * @return string Origin name, 'local' for local modules
*/
public function getSource() {
// Stub, override expected
@@ -263,7 +288,7 @@ abstract class ResourceLoaderModule {
/**
* Get the loader JS for this module, if set.
*
- * @return mixed: JavaScript loader code as a string or boolean false if no custom loader set
+ * @return mixed JavaScript loader code as a string or boolean false if no custom loader set
*/
public function getLoaderScript() {
// Stub, override expected
@@ -278,7 +303,7 @@ abstract class ResourceLoaderModule {
*
* To add dependencies dynamically on the client side, use a custom
* loader script, see getLoaderScript()
- * @return array: List of module names as strings
+ * @return array List of module names as strings
*/
public function getDependencies() {
// Stub, override expected
@@ -288,18 +313,36 @@ abstract class ResourceLoaderModule {
/**
* Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile']
*
- * @return array: Array of strings
+ * @return array Array of strings
*/
public function getTargets() {
return $this->targets;
}
/**
+ * Get the skip function.
+ *
+ * Modules that provide fallback functionality can provide a "skip function". This
+ * function, if provided, will be passed along to the module registry on the client.
+ * When this module is loaded (either directly or as a dependency of another module),
+ * then this function is executed first. If the function returns true, the module will
+ * instantly be considered "ready" without requesting the associated module resources.
+ *
+ * The value returned here must be valid javascript for execution in a private function.
+ * It must not contain the "function () {" and "}" wrapper though.
+ *
+ * @return string|null A JavaScript function body returning a boolean value, or null
+ */
+ public function getSkipFunction() {
+ return null;
+ }
+
+ /**
* Get the files this module depends on indirectly for a given skin.
* Currently these are only image files referenced by the module's CSS.
*
* @param string $skin Skin name
- * @return array: List of files
+ * @return array List of files
*/
public function getFileDependencies( $skin ) {
// Try in-object cache first
@@ -335,7 +378,7 @@ abstract class ResourceLoaderModule {
* Get the last modification timestamp of the message blob for this
* module in a given language.
* @param string $lang Language code
- * @return int: UNIX timestamp, or 0 if the module doesn't have messages
+ * @return int UNIX timestamp, or 0 if the module doesn't have messages
*/
public function getMsgBlobMtime( $lang ) {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
@@ -363,7 +406,7 @@ abstract class ResourceLoaderModule {
* Set a preloaded message blob last modification timestamp. Used so we
* can load this information for all modules at once.
* @param string $lang Language code
- * @param $mtime Integer: UNIX timestamp or 0 if there is no such blob
+ * @param int $mtime UNIX timestamp or 0 if there is no such blob
*/
public function setMsgBlobMtime( $lang, $mtime ) {
$this->msgBlobMtime[$lang] = $mtime;
@@ -387,7 +430,7 @@ abstract class ResourceLoaderModule {
* yourself and take its result into consideration.
*
* @param ResourceLoaderContext $context Context object
- * @return integer UNIX timestamp
+ * @return int UNIX timestamp
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
// 0 would mean now
@@ -398,7 +441,8 @@ abstract class ResourceLoaderModule {
* Helper method for calculating when the module's hash (if it has one) changed.
*
* @param ResourceLoaderContext $context
- * @return integer: UNIX timestamp or 0 if there is no hash provided
+ * @return int UNIX timestamp or 0 if no hash was provided
+ * by getModifiedHash()
*/
public function getHashMtime( ResourceLoaderContext $context ) {
$hash = $this->getModifiedHash( $context );
@@ -407,7 +451,7 @@ abstract class ResourceLoaderModule {
}
$cache = wfGetCache( CACHE_ANYTHING );
- $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName() );
+ $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName(), $hash );
$data = $cache->get( $key );
if ( is_array( $data ) && $data['hash'] === $hash ) {
@@ -425,17 +469,101 @@ abstract class ResourceLoaderModule {
}
/**
- * Get the last modification timestamp of the message blob for this
- * module in a given language.
+ * Get the hash for whatever this module may contain.
+ *
+ * This is the method subclasses should implement if they want to make
+ * use of getHashMTime() inside getModifiedTime().
*
* @param ResourceLoaderContext $context
- * @return string|null: Hash
+ * @return string|null Hash
*/
public function getModifiedHash( ResourceLoaderContext $context ) {
return null;
}
/**
+ * Helper method for calculating when this module's definition summary was last changed.
+ *
+ * @since 1.23
+ *
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp or 0 if no definition summary was provided
+ * by getDefinitionSummary()
+ */
+ public function getDefinitionMtime( ResourceLoaderContext $context ) {
+ wfProfileIn( __METHOD__ );
+ $summary = $this->getDefinitionSummary( $context );
+ if ( $summary === null ) {
+ wfProfileOut( __METHOD__ );
+ return 0;
+ }
+
+ $hash = md5( json_encode( $summary ) );
+
+ $cache = wfGetCache( CACHE_ANYTHING );
+
+ // Embed the hash itself in the cache key. This allows for a few nifty things:
+ // - During deployment, servers with old and new versions of the code communicating
+ // with the same memcached will not override the same key repeatedly increasing
+ // the timestamp.
+ // - In case of the definition changing and then changing back in a short period of time
+ // (e.g. in case of a revert or a corrupt server) the old timestamp and client-side cache
+ // url will be re-used.
+ // - If different context-combinations (e.g. same skin, same language or some combination
+ // thereof) result in the same definition, they will use the same hash and timestamp.
+ $key = wfMemcKey( 'resourceloader', 'moduledefinition', $this->getName(), $hash );
+
+ $data = $cache->get( $key );
+ if ( is_int( $data ) && $data > 0 ) {
+ // We've seen this hash before, re-use the timestamp of when we first saw it.
+ wfProfileOut( __METHOD__ );
+ return $data;
+ }
+
+ wfDebugLog( 'resourceloader', __METHOD__ . ": New definition hash for module "
+ . "{$this->getName()} in context {$context->getHash()}: $hash." );
+
+ $timestamp = time();
+ $cache->set( $key, $timestamp );
+
+ wfProfileOut( __METHOD__ );
+ return $timestamp;
+ }
+
+ /**
+ * Get the definition summary for this module.
+ *
+ * This is the method subclasses should implement if they want to make
+ * use of getDefinitionMTime() inside getModifiedTime().
+ *
+ * Return an array containing values from all significant properties of this
+ * module's definition. Be sure to include things that are explicitly ordered,
+ * in their actaul order (bug 37812).
+ *
+ * Avoid including things that are insiginificant (e.g. order of message
+ * keys is insignificant and should be sorted to avoid unnecessary cache
+ * invalidation).
+ *
+ * Avoid including things already considered by other methods inside your
+ * getModifiedTime(), such as file mtime timestamps.
+ *
+ * Serialisation is done using json_encode, which means object state is not
+ * taken into account when building the hash. This data structure must only
+ * contain arrays and scalars as values (avoid object instances) which means
+ * it requires abstraction.
+ *
+ * @since 1.23
+ *
+ * @param ResourceLoaderContext $context
+ * @return array|null
+ */
+ public function getDefinitionSummary( ResourceLoaderContext $context ) {
+ return array(
+ 'class' => get_class( $this ),
+ );
+ }
+
+ /**
* Check whether this module is known to be empty. If a child class
* has an easy and cheap way to determine that this module is
* definitely going to be empty, it should override this method to
@@ -448,7 +576,7 @@ abstract class ResourceLoaderModule {
return false;
}
- /** @var JSParser lazy-initialized; use self::javaScriptParser() */
+ /** @var JSParser Lazy-initialized; use self::javaScriptParser() */
private static $jsParser;
private static $parseCacheVersion = 1;
@@ -458,11 +586,10 @@ abstract class ResourceLoaderModule {
*
* @param string $fileName
* @param string $contents
- * @return string: JS with the original, or a replacement error
+ * @return string JS with the original, or a replacement error
*/
protected function validateScriptFile( $fileName, $contents ) {
- global $wgResourceLoaderValidateJS;
- if ( $wgResourceLoaderValidateJS ) {
+ if ( $this->getConfig()->get( 'ResourceLoaderValidateJS' ) ) {
// Try for cache hit
// Use CACHE_ANYTHING since filtering is very slow compared to DB queries
$key = wfMemcKey( 'resourceloader', 'jsparse', self::$parseCacheVersion, md5( $contents ) );
diff --git a/includes/resourceloader/ResourceLoaderNoscriptModule.php b/includes/resourceloader/ResourceLoaderNoscriptModule.php
index bd026f3f..61927d77 100644
--- a/includes/resourceloader/ResourceLoaderNoscriptModule.php
+++ b/includes/resourceloader/ResourceLoaderNoscriptModule.php
@@ -33,9 +33,9 @@ class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
* Gets list of pages used by this module. Obviously, it makes absolutely no
* sense to include JavaScript files here... :D
*
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
*
- * @return Array: List of pages
+ * @return array List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
return array( 'MediaWiki:Noscript.css' => array( 'type' => 'style' ) );
@@ -46,7 +46,7 @@ class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
/**
* Gets group name
*
- * @return String: Name of group
+ * @return string Name of group
*/
public function getGroup() {
return 'noscript';
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
index 05754d37..1d9721aa 100644
--- a/includes/resourceloader/ResourceLoaderSiteModule.php
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -32,19 +32,17 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
/**
* Gets list of pages used by this module
*
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
*
- * @return Array: List of pages
+ * @return array List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgUseSiteJs, $wgUseSiteCss;
-
$pages = array();
- if ( $wgUseSiteJs ) {
+ if ( $this->getConfig()->get( 'UseSiteJs' ) ) {
$pages['MediaWiki:Common.js'] = array( 'type' => 'script' );
$pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.js'] = array( 'type' => 'script' );
}
- if ( $wgUseSiteCss ) {
+ if ( $this->getConfig()->get( 'UseSiteCss' ) ) {
$pages['MediaWiki:Common.css'] = array( 'type' => 'style' );
$pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.css'] = array( 'type' => 'style' );
@@ -58,7 +56,7 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
/**
* Gets group name
*
- * @return String: Name of group
+ * @return string Name of group
*/
public function getGroup() {
return 'site';
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index 20f6e0ba..78fe8e01 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -27,21 +27,23 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
/* Protected Members */
protected $modifiedTime = array();
+ protected $configVars = array();
protected $targets = array( 'desktop', 'mobile' );
/* Protected Methods */
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
- protected function getConfig( $context ) {
- global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
- $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang,
- $wgVariantArticlePath, $wgActionPaths, $wgVersion,
- $wgEnableAPI, $wgEnableWriteAPI, $wgDBname,
- $wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
- $wgCookiePrefix, $wgResourceLoaderMaxQueryLength;
+ protected function getConfigSettings( $context ) {
+
+ $hash = $context->getHash();
+ if ( isset( $this->configVars[$hash] ) ) {
+ return $this->configVars[$hash];
+ }
+
+ global $wgContLang;
$mainPage = Title::newMainPage();
@@ -59,113 +61,262 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
}
+ $conf = $this->getConfig();
// Build list of variables
$vars = array(
- 'wgLoadScript' => $wgLoadScript,
+ 'wgLoadScript' => wfScript( 'load' ),
'debug' => $context->getDebug(),
'skin' => $context->getSkin(),
- 'stylepath' => $wgStylePath,
+ 'stylepath' => $conf->get( 'StylePath' ),
'wgUrlProtocols' => wfUrlProtocols(),
- 'wgArticlePath' => $wgArticlePath,
- 'wgScriptPath' => $wgScriptPath,
- 'wgScriptExtension' => $wgScriptExtension,
- 'wgScript' => $wgScript,
- 'wgVariantArticlePath' => $wgVariantArticlePath,
+ 'wgArticlePath' => $conf->get( 'ArticlePath' ),
+ 'wgScriptPath' => $conf->get( 'ScriptPath' ),
+ 'wgScriptExtension' => $conf->get( 'ScriptExtension' ),
+ 'wgScript' => wfScript(),
+ 'wgSearchType' => $conf->get( 'SearchType' ),
+ 'wgVariantArticlePath' => $conf->get( 'VariantArticlePath' ),
// Force object to avoid "empty" associative array from
// becoming [] instead of {} in JS (bug 34604)
- 'wgActionPaths' => (object)$wgActionPaths,
- 'wgServer' => $wgServer,
+ 'wgActionPaths' => (object)$conf->get( 'ActionPaths' ),
+ 'wgServer' => $conf->get( 'Server' ),
+ 'wgServerName' => $conf->get( 'ServerName' ),
'wgUserLanguage' => $context->getLanguage(),
'wgContentLanguage' => $wgContLang->getCode(),
- 'wgVersion' => $wgVersion,
- 'wgEnableAPI' => $wgEnableAPI,
- 'wgEnableWriteAPI' => $wgEnableWriteAPI,
+ 'wgVersion' => $conf->get( 'Version' ),
+ 'wgEnableAPI' => $conf->get( 'EnableAPI' ),
+ 'wgEnableWriteAPI' => $conf->get( 'EnableWriteAPI' ),
'wgMainPageTitle' => $mainPage->getPrefixedText(),
'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
'wgNamespaceIds' => $namespaceIds,
- 'wgSiteName' => $wgSitename,
- 'wgFileExtensions' => array_values( array_unique( $wgFileExtensions ) ),
- 'wgDBname' => $wgDBname,
+ 'wgContentNamespaces' => MWNamespace::getContentNamespaces(),
+ 'wgSiteName' => $conf->get( 'Sitename' ),
+ 'wgFileExtensions' => array_values( array_unique( $conf->get( 'FileExtensions' ) ) ),
+ 'wgDBname' => $conf->get( 'DBname' ),
// This sucks, it is only needed on Special:Upload, but I could
// not find a way to add vars only for a certain module
- 'wgFileCanRotate' => BitmapHandler::canRotate(),
+ 'wgFileCanRotate' => SpecialUpload::rotationEnabled(),
'wgAvailableSkins' => Skin::getSkinNames(),
- 'wgExtensionAssetsPath' => $wgExtensionAssetsPath,
+ 'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
// MediaWiki sets cookies to have this prefix by default
- 'wgCookiePrefix' => $wgCookiePrefix,
- 'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
+ 'wgCookiePrefix' => $conf->get( 'CookiePrefix' ),
+ 'wgCookieDomain' => $conf->get( 'CookieDomain' ),
+ 'wgCookiePath' => $conf->get( 'CookiePath' ),
+ 'wgCookieExpiration' => $conf->get( 'CookieExpiration' ),
+ 'wgResourceLoaderMaxQueryLength' => $conf->get( 'ResourceLoaderMaxQueryLength' ),
'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
+ 'wgResourceLoaderStorageVersion' => $conf->get( 'ResourceLoaderStorageVersion' ),
+ 'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
);
wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
- return $vars;
+ $this->configVars[$hash] = $vars;
+ return $this->configVars[$hash];
+ }
+
+ /**
+ * Recursively get all explicit and implicit dependencies for to the given module.
+ *
+ * @param array $registryData
+ * @param string $moduleName
+ * @return array
+ */
+ protected static function getImplicitDependencies( array $registryData, $moduleName ) {
+ static $dependencyCache = array();
+
+ // The list of implicit dependencies won't be altered, so we can
+ // cache them without having to worry.
+ if ( !isset( $dependencyCache[$moduleName] ) ) {
+
+ if ( !isset( $registryData[$moduleName] ) ) {
+ // Dependencies may not exist
+ $dependencyCache[$moduleName] = array();
+ } else {
+ $data = $registryData[$moduleName];
+ $dependencyCache[$moduleName] = $data['dependencies'];
+
+ foreach ( $data['dependencies'] as $dependency ) {
+ // Recursively get the dependencies of the dependencies
+ $dependencyCache[$moduleName] = array_merge(
+ $dependencyCache[$moduleName],
+ self::getImplicitDependencies( $registryData, $dependency )
+ );
+ }
+ }
+ }
+
+ return $dependencyCache[$moduleName];
+ }
+
+ /**
+ * Optimize the dependency tree in $this->modules and return it.
+ *
+ * The optimization basically works like this:
+ * Given we have module A with the dependencies B and C
+ * and module B with the dependency C.
+ * Now we don't have to tell the client to explicitly fetch module
+ * C as that's already included in module B.
+ *
+ * This way we can reasonably reduce the amout of module registration
+ * data send to the client.
+ *
+ * @param array &$registryData Modules keyed by name with properties:
+ * - string 'version'
+ * - array 'dependencies'
+ * - string|null 'group'
+ * - string 'source'
+ * - string|false 'loader'
+ */
+ public static function compileUnresolvedDependencies( array &$registryData ) {
+ foreach ( $registryData as $name => &$data ) {
+ if ( $data['loader'] !== false ) {
+ continue;
+ }
+ $dependencies = $data['dependencies'];
+ foreach ( $data['dependencies'] as $dependency ) {
+ $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency );
+ $dependencies = array_diff( $dependencies, $implicitDependencies );
+ }
+ // Rebuild keys
+ $data['dependencies'] = array_values( $dependencies );
+ }
}
+
/**
- * Gets registration code for all modules
+ * Get registration code for all modules.
*
- * @param $context ResourceLoaderContext object
- * @return String: JavaScript code for registering all modules with the client loader
+ * @param ResourceLoaderContext $context
+ * @return string JavaScript code for registering all modules with the client loader
*/
- public static function getModuleRegistrations( ResourceLoaderContext $context ) {
- global $wgCacheEpoch;
+ public function getModuleRegistrations( ResourceLoaderContext $context ) {
wfProfileIn( __METHOD__ );
- $out = '';
- $registrations = array();
$resourceLoader = $context->getResourceLoader();
$target = $context->getRequest()->getVal( 'target', 'desktop' );
- // Register sources
- $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
+ $out = '';
+ $registryData = array();
- // Register modules
+ // Get registry data
foreach ( $resourceLoader->getModuleNames() as $name ) {
$module = $resourceLoader->getModule( $name );
$moduleTargets = $module->getTargets();
if ( !in_array( $target, $moduleTargets ) ) {
continue;
}
- $deps = $module->getDependencies();
- $group = $module->getGroup();
- $source = $module->getSource();
- // Support module loader scripts
- $loader = $module->getLoaderScript();
- if ( $loader !== false ) {
- $version = wfTimestamp( TS_ISO_8601_BASIC,
- $module->getModifiedTime( $context ) );
- $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $source, $loader );
+
+ if ( $module->isRaw() ) {
+ // Don't register "raw" modules (like 'jquery' and 'mediawiki') client-side because
+ // depending on them is illegal anyway and would only lead to them being reloaded
+ // causing any state to be lost (like jQuery plugins, mw.config etc.)
continue;
}
- // Automatically register module
// getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always
// seem to do that, and custom implementations might forget. Coerce it to TS_UNIX
$moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
- $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
- // Modules without dependencies, a group or a foreign source pass two arguments (name, timestamp) to
- // mw.loader.register()
- if ( !count( $deps ) && $group === null && $source === 'local' ) {
- $registrations[] = array( $name, $mtime );
- }
- // Modules with dependencies but no group or foreign source pass three arguments
- // (name, timestamp, dependencies) to mw.loader.register()
- elseif ( $group === null && $source === 'local' ) {
- $registrations[] = array( $name, $mtime, $deps );
+ $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ) );
+
+ // FIXME: Convert to numbers, wfTimestamp always gives us stings, even for TS_UNIX
+
+ $skipFunction = $module->getSkipFunction();
+ if ( $skipFunction !== null && !ResourceLoader::inDebugMode() ) {
+ $skipFunction = $resourceLoader->filter( 'minify-js',
+ $skipFunction,
+ // There will potentially be lots of these little string in the registrations
+ // manifest, we don't want to blow up the startup module with
+ // "/* cache key: ... */" all over it in non-debug mode.
+ /* cacheReport = */ false
+ );
}
- // Modules with a group but no foreign source pass four arguments (name, timestamp, dependencies, group)
- // to mw.loader.register()
- elseif ( $source === 'local' ) {
- $registrations[] = array( $name, $mtime, $deps, $group );
+
+ $registryData[$name] = array(
+ 'version' => $mtime,
+ 'dependencies' => $module->getDependencies(),
+ 'group' => $module->getGroup(),
+ 'source' => $module->getSource(),
+ 'loader' => $module->getLoaderScript(),
+ 'skip' => $skipFunction,
+ );
+ }
+
+ self::compileUnresolvedDependencies( $registryData );
+
+ // Register sources
+ $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
+
+ // Concatenate module loader scripts and figure out the different call
+ // signatures for mw.loader.register
+ $registrations = array();
+ foreach ( $registryData as $name => $data ) {
+ if ( $data['loader'] !== false ) {
+ $out .= ResourceLoader::makeCustomLoaderScript(
+ $name,
+ wfTimestamp( TS_ISO_8601_BASIC, $data['version'] ),
+ $data['dependencies'],
+ $data['group'],
+ $data['source'],
+ $data['loader']
+ );
+ continue;
}
- // Modules with a foreign source pass five arguments (name, timestamp, dependencies, group, source)
- // to mw.loader.register()
- else {
- $registrations[] = array( $name, $mtime, $deps, $group, $source );
+
+ if (
+ !count( $data['dependencies'] ) &&
+ $data['group'] === null &&
+ $data['source'] === 'local' &&
+ $data['skip'] === null
+ ) {
+ // Modules with no dependencies, group, foreign source or skip function;
+ // call mw.loader.register(name, timestamp)
+ $registrations[] = array( $name, $data['version'] );
+ } elseif (
+ $data['group'] === null &&
+ $data['source'] === 'local' &&
+ $data['skip'] === null
+ ) {
+ // Modules with dependencies but no group, foreign source or skip function;
+ // call mw.loader.register(name, timestamp, dependencies)
+ $registrations[] = array( $name, $data['version'], $data['dependencies'] );
+ } elseif (
+ $data['source'] === 'local' &&
+ $data['skip'] === null
+ ) {
+ // Modules with a group but no foreign source or skip function;
+ // call mw.loader.register(name, timestamp, dependencies, group)
+ $registrations[] = array(
+ $name,
+ $data['version'],
+ $data['dependencies'],
+ $data['group']
+ );
+ } elseif ( $data['skip'] === null ) {
+ // Modules with a foreign source but no skip function;
+ // call mw.loader.register(name, timestamp, dependencies, group, source)
+ $registrations[] = array(
+ $name,
+ $data['version'],
+ $data['dependencies'],
+ $data['group'],
+ $data['source']
+ );
+ } else {
+ // Modules with a skip function;
+ // call mw.loader.register(name, timestamp, dependencies, group, source, skip)
+ $registrations[] = array(
+ $name,
+ $data['version'],
+ $data['dependencies'],
+ $data['group'],
+ $data['source'],
+ $data['skip']
+ );
}
}
+
+ // Register modules
$out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
wfProfileOut( __METHOD__ );
@@ -182,55 +333,75 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
/**
- * @param $context ResourceLoaderContext
+ * Base modules required for the the base environment of ResourceLoader
+ *
+ * @return array
+ */
+ public static function getStartupModules() {
+ return array( 'jquery', 'mediawiki' );
+ }
+
+ /**
+ * Get the load URL of the startup modules.
+ *
+ * This is a helper for getScript(), but can also be called standalone, such
+ * as when generating an AppCache manifest.
+ *
+ * @param ResourceLoaderContext $context
* @return string
*/
- public function getScript( ResourceLoaderContext $context ) {
- global $IP, $wgLegacyJavaScriptGlobals;
+ public static function getStartupModulesUrl( ResourceLoaderContext $context ) {
+ $moduleNames = self::getStartupModules();
- $out = file_get_contents( "$IP/resources/startup.js" );
- if ( $context->getOnly() === 'scripts' ) {
+ // Get the latest version
+ $loader = $context->getResourceLoader();
+ $version = 0;
+ foreach ( $moduleNames as $moduleName ) {
+ $version = max( $version,
+ $loader->getModule( $moduleName )->getModifiedTime( $context )
+ );
+ }
+
+ $query = array(
+ 'modules' => ResourceLoader::makePackedModulesString( $moduleNames ),
+ 'only' => 'scripts',
+ 'lang' => $context->getLanguage(),
+ 'skin' => $context->getSkin(),
+ 'debug' => $context->getDebug() ? 'true' : 'false',
+ 'version' => wfTimestamp( TS_ISO_8601_BASIC, $version )
+ );
+ // Ensure uniform query order
+ ksort( $query );
+ return wfAppendQuery( wfScript( 'load' ), $query );
+ }
- // The core modules:
- $moduleNames = array( 'jquery', 'mediawiki' );
- wfRunHooks( 'ResourceLoaderGetStartupModules', array( &$moduleNames ) );
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ global $IP;
- // Get the latest version
- $loader = $context->getResourceLoader();
- $version = 0;
- foreach ( $moduleNames as $moduleName ) {
- $version = max( $version,
- $loader->getModule( $moduleName )->getModifiedTime( $context )
- );
- }
- // Build load query for StartupModules
- $query = array(
- 'modules' => ResourceLoader::makePackedModulesString( $moduleNames ),
- 'only' => 'scripts',
- 'lang' => $context->getLanguage(),
- 'skin' => $context->getSkin(),
- 'debug' => $context->getDebug() ? 'true' : 'false',
- 'version' => wfTimestamp( TS_ISO_8601_BASIC, $version )
- );
- // Ensure uniform query order
- ksort( $query );
+ $out = file_get_contents( "$IP/resources/src/startup.js" );
+ if ( $context->getOnly() === 'scripts' ) {
// Startup function
- $configuration = $this->getConfig( $context );
- $registrations = self::getModuleRegistrations( $context );
- $registrations = str_replace( "\n", "\n\t", trim( $registrations ) ); // fix indentation
- $out .= "var startUp = function() {\n" .
- "\tmw.config = new " . Xml::encodeJsCall( 'mw.Map', array( $wgLegacyJavaScriptGlobals ) ) . "\n" .
+ $configuration = $this->getConfigSettings( $context );
+ $registrations = $this->getModuleRegistrations( $context );
+ // Fix indentation
+ $registrations = str_replace( "\n", "\n\t", trim( $registrations ) );
+ $out .= "var startUp = function () {\n" .
+ "\tmw.config = new " .
+ Xml::encodeJsCall( 'mw.Map', array( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) ) ) . "\n" .
"\t$registrations\n" .
"\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) .
"};\n";
// Conditional script injection
- $scriptTag = Html::linkedScript( wfAppendQuery( wfScript( 'load' ), $query ) );
+ $scriptTag = Html::linkedScript( self::getStartupModulesUrl( $context ) );
$out .= "if ( isCompatible() ) {\n" .
"\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
- "}\n" .
- "delete isCompatible;";
+ "}";
}
return $out;
@@ -244,11 +415,11 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
- global $IP, $wgCacheEpoch;
+ global $IP;
$hash = $context->getHash();
if ( isset( $this->modifiedTime[$hash] ) ) {
@@ -260,19 +431,44 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$loader = $context->getResourceLoader();
$loader->preloadModuleInfo( $loader->getModuleNames(), $context );
- $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
- // ATTENTION!: Because of the line above, this is not going to cause
+ $time = max(
+ wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ),
+ filemtime( "$IP/resources/src/startup.js" ),
+ $this->getHashMtime( $context )
+ );
+
+ // ATTENTION!: Because of the line below, this is not going to cause
// infinite recursion - think carefully before making changes to this
// code!
- $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
+ // Pre-populate modifiedTime with something because the the loop over
+ // all modules below includes the the startup module (this module).
+ $this->modifiedTime[$hash] = 1;
+
foreach ( $loader->getModuleNames() as $name ) {
$module = $loader->getModule( $name );
$time = max( $time, $module->getModifiedTime( $context ) );
}
- return $this->modifiedTime[$hash] = $time;
+
+ $this->modifiedTime[$hash] = $time;
+ return $this->modifiedTime[$hash];
}
- /* Methods */
+ /**
+ * Hash of all dynamic data embedded in getScript().
+ *
+ * Detect changes to mw.config settings embedded in #getScript (bug 28899).
+ *
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ $data = array(
+ 'vars' => $this->getConfigSettings( $context ),
+ 'wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
+ );
+
+ return md5( serialize( $data ) );
+ }
/**
* @return string
diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
index bda86539..40274c63 100644
--- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
@@ -36,27 +36,27 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
/* Methods */
/**
- * @param $context ResourceLoaderContext
- * @return array|int|Mixed
+ * @param ResourceLoaderContext $context
+ * @return array|int|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
- if ( isset( $this->modifiedTime[$hash] ) ) {
- return $this->modifiedTime[$hash];
+ if ( !isset( $this->modifiedTime[$hash] ) ) {
+ global $wgUser;
+ $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
}
- global $wgUser;
- return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ return $this->modifiedTime[$hash];
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
public function getStyles( ResourceLoaderContext $context ) {
- global $wgAllowUserCssPrefs, $wgUser;
+ global $wgUser;
- if ( !$wgAllowUserCssPrefs ) {
+ if ( !$this->getConfig()->get( 'AllowUserCssPrefs' ) ) {
return array();
}
@@ -71,17 +71,8 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
( $options['underline'] ? 'underline' : 'none' ) . "; }";
} else {
# The scripts of these languages are very hard to read with underlines
- $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(kk-arab), ' .
- 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }';
- }
- if ( $options['justify'] ) {
- $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
- }
- if ( !$options['showtoc'] ) {
- $rules[] = "#toc { display: none; }\n";
- }
- if ( !$options['editsection'] ) {
- $rules[] = ".mw-editsection { display: none; }\n";
+ $rules[] = 'a:lang(ar), a:lang(kk-arab), a:lang(mzn), ' .
+ 'a:lang(ps), a:lang(ur) { text-decoration: none; }';
}
if ( $options['editfont'] !== 'default' ) {
// Double-check that $options['editfont'] consists of safe characters only
diff --git a/includes/resourceloader/ResourceLoaderUserGroupsModule.php b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
index 9064263f..7cf19420 100644
--- a/includes/resourceloader/ResourceLoaderUserGroupsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
@@ -25,21 +25,28 @@
*/
class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
- /* Protected Methods */
+ /* Protected Members */
+
protected $origin = self::ORIGIN_USER_SITEWIDE;
+ protected $targets = array( 'desktop', 'mobile' );
+
+ /* Protected Methods */
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgUser, $wgUseSiteJs, $wgUseSiteCss;
+ global $wgUser;
$userName = $context->getUser();
if ( $userName === null ) {
return array();
}
- if ( !$wgUseSiteJs && !$wgUseSiteCss ) {
+
+ $useSiteJs = $this->getConfig()->get( 'UseSiteJs' );
+ $useSiteCss = $this->getConfig()->get( 'UseSiteCss' );
+ if ( !$useSiteJs && !$useSiteCss ) {
return array();
}
@@ -55,13 +62,13 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
$pages = array();
foreach ( $user->getEffectiveGroups() as $group ) {
- if ( in_array( $group, array( '*', 'user' ) ) ) {
+ if ( $group == '*' ) {
continue;
}
- if ( $wgUseSiteJs ) {
+ if ( $useSiteJs ) {
$pages["MediaWiki:Group-$group.js"] = array( 'type' => 'script' );
}
- if ( $wgUseSiteCss ) {
+ if ( $useSiteCss ) {
$pages["MediaWiki:Group-$group.css"] = array( 'type' => 'style' );
}
}
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
index 7a04e473..1b6d1de0 100644
--- a/includes/resourceloader/ResourceLoaderUserModule.php
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -27,21 +27,27 @@
*/
class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
- /* Protected Methods */
+ /* Protected Members */
+
protected $origin = self::ORIGIN_USER_INDIVIDUAL;
+ /* Protected Methods */
+
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgAllowUserJs, $wgAllowUserCss;
$username = $context->getUser();
if ( $username === null ) {
return array();
}
- if ( !$wgAllowUserJs && !$wgAllowUserCss ) {
+
+ $allowUserJs = $this->getConfig()->get( 'AllowUserJs' );
+ $allowUserCss = $this->getConfig()->get( 'AllowUserCss' );
+
+ if ( !$allowUserJs && !$allowUserCss ) {
return array();
}
@@ -55,11 +61,11 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
$userpage = $userpageTitle->getPrefixedDBkey(); // Needed so $excludepages works
$pages = array();
- if ( $wgAllowUserJs ) {
+ if ( $allowUserJs ) {
$pages["$userpage/common.js"] = array( 'type' => 'script' );
$pages["$userpage/" . $context->getSkin() . '.js'] = array( 'type' => 'script' );
}
- if ( $wgAllowUserCss ) {
+ if ( $allowUserCss ) {
$pages["$userpage/common.css"] = array( 'type' => 'style' );
$pages["$userpage/" . $context->getSkin() . '.css'] = array( 'type' => 'style' );
}
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index 0b7e1964..bd97a8e5 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -33,24 +33,26 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
+ protected $targets = array( 'desktop', 'mobile' );
+
/* Methods */
/**
- * @param $context ResourceLoaderContext
- * @return array|int|Mixed
+ * @param ResourceLoaderContext $context
+ * @return array|int|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
- if ( isset( $this->modifiedTime[$hash] ) ) {
- return $this->modifiedTime[$hash];
+ if ( !isset( $this->modifiedTime[$hash] ) ) {
+ global $wgUser;
+ $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
}
- global $wgUser;
- return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ return $this->modifiedTime[$hash];
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
index 92ebbe93..668467ca 100644
--- a/includes/resourceloader/ResourceLoaderUserTokensModule.php
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php
@@ -30,25 +30,27 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
+ protected $targets = array( 'desktop', 'mobile' );
+
/* Methods */
/**
* Fetch the tokens for the current user.
*
- * @return array: List of tokens keyed by token type
+ * @return array List of tokens keyed by token type
*/
protected function contextUserTokens() {
global $wgUser;
return array(
'editToken' => $wgUser->getEditToken(),
- 'patrolToken' => ApiQueryRecentChanges::getPatrolToken( null, null ),
- 'watchToken' => ApiQueryInfo::getWatchToken( null, null ),
+ 'patrolToken' => $wgUser->getEditToken( 'patrol' ),
+ 'watchToken' => $wgUser->getEditToken( 'watch' ),
);
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index 3f10ae53..de61fc55 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -36,8 +36,8 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
# Origin is user-supplied code
protected $origin = self::ORIGIN_USER_SITEWIDE;
- // In-object cache for title mtimes
- protected $titleMtimes = array();
+ // In-object cache for title info
+ protected $titleInfo = array();
/* Abstract Protected Methods */
@@ -54,7 +54,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* There is an optional media key, the value of which can be the
* medium ('screen', 'print', etc.) of the stylesheet.
*
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
abstract protected function getPages( ResourceLoaderContext $context );
@@ -77,7 +77,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
/**
- * @param $title Title
+ * @param Title $title
* @return null|string
*/
protected function getContent( $title ) {
@@ -96,20 +96,20 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
return null;
}
- $model = $content->getModel();
-
- if ( $model !== CONTENT_MODEL_CSS && $model !== CONTENT_MODEL_JAVASCRIPT ) {
- wfDebugLog( 'resourceloader', __METHOD__ . ': bad content model $model for JS/CSS page!' );
+ if ( $content->isSupportedFormat( CONTENT_FORMAT_JAVASCRIPT ) ) {
+ return $content->serialize( CONTENT_FORMAT_JAVASCRIPT );
+ } elseif ( $content->isSupportedFormat( CONTENT_FORMAT_CSS ) ) {
+ return $content->serialize( CONTENT_FORMAT_CSS );
+ } else {
+ wfDebugLog( 'resourceloader', __METHOD__ . ": bad content model {$content->getModel()} for JS/CSS page!" );
return null;
}
-
- return $content->getNativeData(); //NOTE: this is safe, we know it's JS or CSS
}
/* Methods */
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
@@ -125,22 +125,17 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
$script = $this->getContent( $title );
if ( strval( $script ) !== '' ) {
$script = $this->validateScriptFile( $titleText, $script );
- if ( strpos( $titleText, '*/' ) === false ) {
- $scripts .= "/* $titleText */\n";
- }
- $scripts .= $script . "\n";
+ $scripts .= ResourceLoader::makeComment( $titleText ) . $script . "\n";
}
}
return $scripts;
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
public function getStyles( ResourceLoaderContext $context ) {
- global $wgScriptPath;
-
$styles = array();
foreach ( $this->getPages( $context ) as $titleText => $options ) {
if ( $options['type'] !== 'style' ) {
@@ -158,47 +153,84 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( $this->getFlip( $context ) ) {
$style = CSSJanus::transform( $style, true, false );
}
- $style = CSSMin::remap( $style, false, $wgScriptPath, true );
+ $style = CSSMin::remap( $style, false, $this->getConfig()->get( 'ScriptPath' ), true );
if ( !isset( $styles[$media] ) ) {
$styles[$media] = array();
}
- if ( strpos( $titleText, '*/' ) === false ) {
- $style = "/* $titleText */\n" . $style;
- }
+ $style = ResourceLoader::makeComment( $titleText ) . $style;
$styles[$media][] = $style;
}
return $styles;
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return int|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
$modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
- $mtimes = $this->getTitleMtimes( $context );
- if ( count( $mtimes ) ) {
+ $titleInfo = $this->getTitleInfo( $context );
+ if ( count( $titleInfo ) ) {
+ $mtimes = array_map( function( $value ) {
+ return $value['timestamp'];
+ }, $titleInfo );
$modifiedTime = max( $modifiedTime, max( $mtimes ) );
}
- $modifiedTime = max( $modifiedTime, $this->getMsgBlobMtime( $context->getLanguage() ) );
+ $modifiedTime = max(
+ $modifiedTime,
+ $this->getMsgBlobMtime( $context->getLanguage() ),
+ $this->getDefinitionMtime( $context )
+ );
return $modifiedTime;
}
/**
- * @param $context ResourceLoaderContext
+ * Get the definition summary for this module.
+ *
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getDefinitionSummary( ResourceLoaderContext $context ) {
+ return array(
+ 'class' => get_class( $this ),
+ 'pages' => $this->getPages( $context ),
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
* @return bool
*/
public function isKnownEmpty( ResourceLoaderContext $context ) {
- return count( $this->getTitleMtimes( $context ) ) == 0;
+ $titleInfo = $this->getTitleInfo( $context );
+ // Bug 68488: For modules in the "user" group, we should actually
+ // check that the pages are empty (page_len == 0), but for other
+ // groups, just check the pages exist so that we don't end up
+ // caching temporarily-blank pages without the appropriate
+ // <script> or <link> tag.
+ if ( $this->getGroup() !== 'user' ) {
+ return count( $titleInfo ) === 0;
+ }
+
+ foreach ( $titleInfo as $info ) {
+ if ( $info['length'] !== 0 ) {
+ // At least one non-0-lenth page, not empty
+ return false;
+ }
+ }
+
+ // All pages are 0-length, so it's empty
+ return true;
}
/**
* Get the modification times of all titles that would be loaded for
* a given context.
- * @param $context ResourceLoaderContext: Context object
- * @return array( prefixed DB key => UNIX timestamp ), nonexistent titles are dropped
+ * @param ResourceLoaderContext $context Context object
+ * @return array keyed by page dbkey, with value is an array with 'length' and 'timestamp'
+ * keys, where the timestamp is a unix one
*/
- protected function getTitleMtimes( ResourceLoaderContext $context ) {
+ protected function getTitleInfo( ResourceLoaderContext $context ) {
$dbr = $this->getDB();
if ( !$dbr ) {
// We're dealing with a subclass that doesn't have a DB
@@ -206,11 +238,11 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
$hash = $context->getHash();
- if ( isset( $this->titleMtimes[$hash] ) ) {
- return $this->titleMtimes[$hash];
+ if ( isset( $this->titleInfo[$hash] ) ) {
+ return $this->titleInfo[$hash];
}
- $this->titleMtimes[$hash] = array();
+ $this->titleInfo[$hash] = array();
$batch = new LinkBatch;
foreach ( $this->getPages( $context ) as $titleText => $options ) {
$batch->addObj( Title::newFromText( $titleText ) );
@@ -218,16 +250,18 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( !$batch->isEmpty() ) {
$res = $dbr->select( 'page',
- array( 'page_namespace', 'page_title', 'page_touched' ),
+ array( 'page_namespace', 'page_title', 'page_touched', 'page_len' ),
$batch->constructSet( 'page', $dbr ),
__METHOD__
);
foreach ( $res as $row ) {
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $this->titleMtimes[$hash][$title->getPrefixedDBkey()] =
- wfTimestamp( TS_UNIX, $row->page_touched );
+ $this->titleInfo[$hash][$title->getPrefixedDBkey()] = array(
+ 'timestamp' => wfTimestamp( TS_UNIX, $row->page_touched ),
+ 'length' => $row->page_len,
+ );
}
}
- return $this->titleMtimes[$hash];
+ return $this->titleInfo[$hash];
}
}
diff --git a/includes/revisiondelete/RevDelArchiveItem.php b/includes/revisiondelete/RevDelArchiveItem.php
new file mode 100644
index 00000000..0f1c7f0c
--- /dev/null
+++ b/includes/revisiondelete/RevDelArchiveItem.php
@@ -0,0 +1,105 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * Item class for a archive table row
+ */
+class RevDelArchiveItem extends RevDelRevisionItem {
+ public function __construct( $list, $row ) {
+ RevDelItem::__construct( $list, $row );
+ $this->revision = Revision::newFromArchiveRow( $row,
+ array( 'page' => $this->list->title->getArticleID() ) );
+ }
+
+ public function getIdField() {
+ return 'ar_timestamp';
+ }
+
+ public function getTimestampField() {
+ return 'ar_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'ar_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'ar_user_text';
+ }
+
+ public function getId() {
+ # Convert DB timestamp to MW timestamp
+ return $this->revision->getTimestamp();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'archive',
+ array( 'ar_deleted' => $bits ),
+ array(
+ 'ar_namespace' => $this->list->title->getNamespace(),
+ 'ar_title' => $this->list->title->getDBkey(),
+ // use timestamp for index
+ 'ar_timestamp' => $this->row->ar_timestamp,
+ 'ar_rev_id' => $this->row->ar_rev_id,
+ 'ar_deleted' => $this->getBits()
+ ),
+ __METHOD__ );
+
+ return (bool)$dbw->affectedRows();
+ }
+
+ protected function getRevisionLink() {
+ $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
+ $this->revision->getTimestamp(), $this->list->getUser() ) );
+
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $date;
+ }
+
+ return Linker::link(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $date,
+ array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'timestamp' => $this->revision->getTimestamp()
+ )
+ );
+ }
+
+ protected function getDiffLink() {
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $this->list->msg( 'diff' )->escaped();
+ }
+
+ return Linker::link(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $this->list->msg( 'diff' )->escaped(),
+ array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'diff' => 'prev',
+ 'timestamp' => $this->revision->getTimestamp()
+ )
+ );
+ }
+}
diff --git a/includes/revisiondelete/RevDelArchiveList.php b/includes/revisiondelete/RevDelArchiveList.php
new file mode 100644
index 00000000..e7aed737
--- /dev/null
+++ b/includes/revisiondelete/RevDelArchiveList.php
@@ -0,0 +1,66 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * List for archive table items, i.e. revisions deleted via action=delete
+ */
+class RevDelArchiveList extends RevDelRevisionList {
+ public function getType() {
+ return 'archive';
+ }
+
+ public static function getRelationType() {
+ return 'ar_timestamp';
+ }
+
+ /**
+ * @param DatabaseBase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $timestamps = array();
+ foreach ( $this->ids as $id ) {
+ $timestamps[] = $db->timestamp( $id );
+ }
+
+ return $db->select( 'archive', Revision::selectArchiveFields(),
+ array(
+ 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_timestamp' => $timestamps
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'ar_timestamp DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDelArchiveItem( $this, $row );
+ }
+
+ public function doPreCommitUpdates() {
+ return Status::newGood();
+ }
+
+ public function doPostCommitUpdates() {
+ return Status::newGood();
+ }
+}
diff --git a/includes/revisiondelete/RevDelArchivedFileItem.php b/includes/revisiondelete/RevDelArchivedFileItem.php
new file mode 100644
index 00000000..7c41c180
--- /dev/null
+++ b/includes/revisiondelete/RevDelArchivedFileItem.php
@@ -0,0 +1,129 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * Item class for a filearchive table row
+ */
+class RevDelArchivedFileItem extends RevDelFileItem {
+ public function __construct( $list, $row ) {
+ RevDelItem::__construct( $list, $row );
+ $this->file = ArchivedFile::newFromRow( $row );
+ }
+
+ public function getIdField() {
+ return 'fa_id';
+ }
+
+ public function getTimestampField() {
+ return 'fa_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'fa_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'fa_user_text';
+ }
+
+ public function getId() {
+ return $this->row->fa_id;
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'filearchive',
+ array( 'fa_deleted' => $bits ),
+ array(
+ 'fa_id' => $this->row->fa_id,
+ 'fa_deleted' => $this->getBits(),
+ ),
+ __METHOD__
+ );
+
+ return (bool)$dbw->affectedRows();
+ }
+
+ protected function getLink() {
+ $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
+ $this->file->getTimestamp(), $this->list->getUser() ) );
+
+ # Hidden files...
+ if ( !$this->canViewContent() ) {
+ $link = $date;
+ } else {
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $key = $this->file->getKey();
+ $link = Linker::link( $undelete, $date, array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $key,
+ 'token' => $this->list->getUser()->getEditToken( $key )
+ )
+ );
+ }
+ if ( $this->isDeleted() ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ return $link;
+ }
+
+ public function getApiData( ApiResult $result ) {
+ $file = $this->file;
+ $user = $this->list->getUser();
+ $ret = array(
+ 'title' => $this->list->title->getPrefixedText(),
+ 'timestamp' => wfTimestamp( TS_ISO_8601, $file->getTimestamp() ),
+ 'width' => $file->getWidth(),
+ 'height' => $file->getHeight(),
+ 'size' => $file->getSize(),
+ );
+ $ret += $file->isDeleted( Revision::DELETED_USER ) ? array( 'userhidden' => '' ) : array();
+ $ret += $file->isDeleted( Revision::DELETED_COMMENT ) ? array( 'commenthidden' => '' ) : array();
+ $ret += $this->isDeleted() ? array( 'contenthidden' => '' ) : array();
+ if ( $this->canViewContent() ) {
+ $ret += array(
+ 'url' => SpecialPage::getTitleFor( 'Revisiondelete' )->getLinkURL(
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $file->getKey(),
+ 'token' => $user->getEditToken( $file->getKey() )
+ ),
+ false, PROTO_RELATIVE
+ ),
+ );
+ }
+ if ( $file->userCan( Revision::DELETED_USER, $user ) ) {
+ $ret += array(
+ 'userid' => $file->getUser( 'id' ),
+ 'user' => $file->getUser( 'text' ),
+ );
+ }
+ if ( $file->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $ret += array(
+ 'comment' => $file->getRawDescription(),
+ );
+ }
+
+ return $ret;
+ }
+}
diff --git a/includes/revisiondelete/RevDelArchivedFileList.php b/includes/revisiondelete/RevDelArchivedFileList.php
new file mode 100644
index 00000000..aec51b17
--- /dev/null
+++ b/includes/revisiondelete/RevDelArchivedFileList.php
@@ -0,0 +1,56 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * List for filearchive table items
+ */
+class RevDelArchivedFileList extends RevDelFileList {
+ public function getType() {
+ return 'filearchive';
+ }
+
+ public static function getRelationType() {
+ return 'fa_id';
+ }
+
+ /**
+ * @param DatabaseBase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+
+ return $db->select(
+ 'filearchive',
+ ArchivedFile::selectFields(),
+ array(
+ 'fa_name' => $this->title->getDBkey(),
+ 'fa_id' => $ids
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'fa_id DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDelArchivedFileItem( $this, $row );
+ }
+}
diff --git a/includes/revisiondelete/RevDelArchivedRevisionItem.php b/includes/revisiondelete/RevDelArchivedRevisionItem.php
new file mode 100644
index 00000000..9ec548fb
--- /dev/null
+++ b/includes/revisiondelete/RevDelArchivedRevisionItem.php
@@ -0,0 +1,53 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * Item class for a archive table row by ar_rev_id -- actually
+ * used via RevDelRevisionList.
+ */
+class RevDelArchivedRevisionItem extends RevDelArchiveItem {
+ public function __construct( $list, $row ) {
+ RevDelItem::__construct( $list, $row );
+
+ $this->revision = Revision::newFromArchiveRow( $row,
+ array( 'page' => $this->list->title->getArticleID() ) );
+ }
+
+ public function getIdField() {
+ return 'ar_rev_id';
+ }
+
+ public function getId() {
+ return $this->revision->getId();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'archive',
+ array( 'ar_deleted' => $bits ),
+ array( 'ar_rev_id' => $this->row->ar_rev_id,
+ 'ar_deleted' => $this->getBits()
+ ),
+ __METHOD__ );
+
+ return (bool)$dbw->affectedRows();
+ }
+}
diff --git a/includes/revisiondelete/RevDelFileItem.php b/includes/revisiondelete/RevDelFileItem.php
new file mode 100644
index 00000000..a6517fe9
--- /dev/null
+++ b/includes/revisiondelete/RevDelFileItem.php
@@ -0,0 +1,237 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * Item class for an oldimage table row
+ */
+class RevDelFileItem extends RevDelItem {
+ /** @var File */
+ public $file;
+
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+ }
+
+ public function getIdField() {
+ return 'oi_archive_name';
+ }
+
+ public function getTimestampField() {
+ return 'oi_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'oi_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'oi_user_text';
+ }
+
+ public function getId() {
+ $parts = explode( '!', $this->row->oi_archive_name );
+
+ return $parts[0];
+ }
+
+ public function canView() {
+ return $this->file->userCan( File::DELETED_RESTRICTED, $this->list->getUser() );
+ }
+
+ public function canViewContent() {
+ return $this->file->userCan( File::DELETED_FILE, $this->list->getUser() );
+ }
+
+ public function getBits() {
+ return $this->file->getVisibility();
+ }
+
+ public function setBits( $bits ) {
+ # Queue the file op
+ # @todo FIXME: Move to LocalFile.php
+ if ( $this->isDeleted() ) {
+ if ( $bits & File::DELETED_FILE ) {
+ # Still deleted
+ } else {
+ # Newly undeleted
+ $key = $this->file->getStorageKey();
+ $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
+ $this->list->storeBatch[] = array(
+ $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
+ 'public',
+ $this->file->getRel()
+ );
+ $this->list->cleanupBatch[] = $key;
+ }
+ } elseif ( $bits & File::DELETED_FILE ) {
+ # Newly deleted
+ $key = $this->file->getStorageKey();
+ $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
+ $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
+ }
+
+ # Do the database operations
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'oldimage',
+ array( 'oi_deleted' => $bits ),
+ array(
+ 'oi_name' => $this->row->oi_name,
+ 'oi_timestamp' => $this->row->oi_timestamp,
+ 'oi_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+
+ return (bool)$dbw->affectedRows();
+ }
+
+ public function isDeleted() {
+ return $this->file->isDeleted( File::DELETED_FILE );
+ }
+
+ /**
+ * Get the link to the file.
+ * Overridden by RevDelArchivedFileItem.
+ * @return string
+ */
+ protected function getLink() {
+ $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
+ $this->file->getTimestamp(), $this->list->getUser() ) );
+
+ if ( !$this->isDeleted() ) {
+ # Regular files...
+ return Html::rawElement( 'a', array( 'href' => $this->file->getUrl() ), $date );
+ }
+
+ # Hidden files...
+ if ( !$this->canViewContent() ) {
+ $link = $date;
+ } else {
+ $link = Linker::link(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $date,
+ array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $this->file->getArchiveName(),
+ 'token' => $this->list->getUser()->getEditToken(
+ $this->file->getArchiveName() )
+ )
+ );
+ }
+
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ /**
+ * Generate a user tool link cluster if the current user is allowed to view it
+ * @return string HTML
+ */
+ protected function getUserTools() {
+ if ( $this->file->userCan( Revision::DELETED_USER, $this->list->getUser() ) ) {
+ $uid = $this->file->getUser( 'id' );
+ $name = $this->file->getUser( 'text' );
+ $link = Linker::userLink( $uid, $name ) . Linker::userToolLinks( $uid, $name );
+ } else {
+ $link = $this->list->msg( 'rev-deleted-user' )->escaped();
+ }
+ if ( $this->file->isDeleted( Revision::DELETED_USER ) ) {
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ return $link;
+ }
+
+ /**
+ * Wrap and format the file's comment block, if the current
+ * user is allowed to view it.
+ *
+ * @return string HTML
+ */
+ protected function getComment() {
+ if ( $this->file->userCan( File::DELETED_COMMENT, $this->list->getUser() ) ) {
+ $block = Linker::commentBlock( $this->file->getDescription() );
+ } else {
+ $block = ' ' . $this->list->msg( 'rev-deleted-comment' )->escaped();
+ }
+ if ( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
+ return "<span class=\"history-deleted\">$block</span>";
+ }
+
+ return $block;
+ }
+
+ public function getHTML() {
+ $data =
+ $this->list->msg( 'widthheight' )->numParams(
+ $this->file->getWidth(), $this->file->getHeight() )->text() .
+ ' (' . $this->list->msg( 'nbytes' )->numParams( $this->file->getSize() )->text() . ')';
+
+ return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
+ $data . ' ' . $this->getComment() . '</li>';
+ }
+
+ public function getApiData( ApiResult $result ) {
+ $file = $this->file;
+ $user = $this->list->getUser();
+ $ret = array(
+ 'title' => $this->list->title->getPrefixedText(),
+ 'archivename' => $file->getArchiveName(),
+ 'timestamp' => wfTimestamp( TS_ISO_8601, $file->getTimestamp() ),
+ 'width' => $file->getWidth(),
+ 'height' => $file->getHeight(),
+ 'size' => $file->getSize(),
+ );
+ $ret += $file->isDeleted( Revision::DELETED_USER ) ? array( 'userhidden' => '' ) : array();
+ $ret += $file->isDeleted( Revision::DELETED_COMMENT ) ? array( 'commenthidden' => '' ) : array();
+ $ret += $this->isDeleted() ? array( 'contenthidden' => '' ) : array();
+ if ( !$this->isDeleted() ) {
+ $ret += array(
+ 'url' => $file->getUrl(),
+ );
+ } elseif ( $this->canViewContent() ) {
+ $ret += array(
+ 'url' => SpecialPage::getTitleFor( 'Revisiondelete' )->getLinkURL(
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $file->getArchiveName(),
+ 'token' => $user->getEditToken( $file->getArchiveName() )
+ ),
+ false, PROTO_RELATIVE
+ ),
+ );
+ }
+ if ( $file->userCan( Revision::DELETED_USER, $user ) ) {
+ $ret += array(
+ 'userid' => $file->user,
+ 'user' => $file->user_text,
+ );
+ }
+ if ( $file->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $ret += array(
+ 'comment' => $file->description,
+ );
+ }
+
+ return $ret;
+ }
+}
diff --git a/includes/revisiondelete/RevDelFileList.php b/includes/revisiondelete/RevDelFileList.php
new file mode 100644
index 00000000..57e15d81
--- /dev/null
+++ b/includes/revisiondelete/RevDelFileList.php
@@ -0,0 +1,128 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * List for oldimage table items
+ */
+class RevDelFileList extends RevDelList {
+ /** @var array */
+ public $storeBatch;
+
+ /** @var array */
+ public $deleteBatch;
+
+ /** @var array */
+ public $cleanupBatch;
+
+ public function getType() {
+ return 'oldimage';
+ }
+
+ public static function getRelationType() {
+ return 'oi_archive_name';
+ }
+
+ public static function getRestriction() {
+ return 'deleterevision';
+ }
+
+ public static function getRevdelConstant() {
+ return File::DELETED_FILE;
+ }
+
+ /**
+ * @param DatabaseBase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $archiveNames = array();
+ foreach ( $this->ids as $timestamp ) {
+ $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
+ }
+
+ return $db->select(
+ 'oldimage',
+ OldLocalFile::selectFields(),
+ array(
+ 'oi_name' => $this->title->getDBkey(),
+ 'oi_archive_name' => $archiveNames
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'oi_timestamp DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDelFileItem( $this, $row );
+ }
+
+ public function clearFileOps() {
+ $this->deleteBatch = array();
+ $this->storeBatch = array();
+ $this->cleanupBatch = array();
+ }
+
+ public function doPreCommitUpdates() {
+ $status = Status::newGood();
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ if ( $this->storeBatch ) {
+ $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ if ( $this->deleteBatch ) {
+ $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
+ }
+ if ( !$status->isOK() ) {
+ // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
+ // modified (but destined for rollback) causes data loss
+ return $status;
+ }
+ if ( $this->cleanupBatch ) {
+ $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
+ }
+
+ return $status;
+ }
+
+ public function doPostCommitUpdates() {
+ $file = wfLocalFile( $this->title );
+ $file->purgeCache();
+ $file->purgeDescription();
+ $purgeUrls = array();
+ foreach ( $this->ids as $timestamp ) {
+ $archiveName = $timestamp . '!' . $this->title->getDBkey();
+ $file->purgeOldThumbnails( $archiveName );
+ $purgeUrls[] = $file->getArchiveUrl( $archiveName );
+ }
+ if ( $this->getConfig()->get( 'UseSquid' ) ) {
+ // purge full images from cache
+ SquidUpdate::purge( $purgeUrls );
+ }
+
+ return Status::newGood();
+ }
+
+ public function getSuppressBit() {
+ return File::DELETED_RESTRICTED;
+ }
+}
diff --git a/includes/revisiondelete/RevDelItem.php b/includes/revisiondelete/RevDelItem.php
new file mode 100644
index 00000000..ebdbf3a8
--- /dev/null
+++ b/includes/revisiondelete/RevDelItem.php
@@ -0,0 +1,62 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * Abstract base class for deletable items
+ */
+abstract class RevDelItem extends RevisionItemBase {
+ /**
+ * Returns true if the item is "current", and the operation to set the given
+ * bits can't be executed for that reason
+ * STUB
+ * @param int $newBits
+ * @return bool
+ */
+ public function isHideCurrentOp( $newBits ) {
+ return false;
+ }
+
+ /**
+ * Get the current deletion bitfield value
+ */
+ abstract public function getBits();
+
+ /**
+ * Set the visibility of the item. This should do any necessary DB queries.
+ *
+ * The DB update query should have a condition which forces it to only update
+ * if the value in the DB matches the value fetched earlier with the SELECT.
+ * If the update fails because it did not match, the function should return
+ * false. This prevents concurrency problems.
+ *
+ * @param int $newBits
+ * @return bool Success
+ */
+ abstract public function setBits( $newBits );
+
+ /**
+ * Get the return information about the revision for the API
+ * @since 1.23
+ * @param ApiResult $result API result object
+ * @return array Data for the API result
+ */
+ abstract public function getApiData( ApiResult $result );
+}
diff --git a/includes/revisiondelete/RevisionDeleteAbstracts.php b/includes/revisiondelete/RevDelList.php
index 803467e6..a0ff6672 100644
--- a/includes/revisiondelete/RevisionDeleteAbstracts.php
+++ b/includes/revisiondelete/RevDelList.php
@@ -1,7 +1,5 @@
<?php
/**
- * Interface definition for deletable items.
- *
* 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
@@ -24,10 +22,10 @@
/**
* Abstract base class for a list of deletable items. The list class
* needs to be able to make a query from a set of identifiers to pull
- * relevant rows, to return RevDel_Item subclasses wrapping them, and
+ * relevant rows, to return RevDelItem subclasses wrapping them, and
* to wrap bulk update operations.
*/
-abstract class RevDel_List extends RevisionListBase {
+abstract class RevDelList extends RevisionListBase {
function __construct( IContextSource $context, Title $title, array $ids ) {
parent::__construct( $context, $title );
$this->ids = $ids;
@@ -80,13 +78,16 @@ abstract class RevDel_List extends RevisionListBase {
* transactions are done here.
*
* @param array $params Associative array of parameters. Members are:
- * value: The integer value to set the visibility to
- * comment: The log comment.
+ * value: The integer value to set the visibility to
+ * comment: The log comment.
+ * perItemStatus: Set if you want per-item status reports
* @return Status
+ * @since 1.23 Added 'perItemStatus' param
*/
public function setVisibility( $params ) {
$bitPars = $params['value'];
$comment = $params['comment'];
+ $perItemStatus = isset( $params['perItemStatus'] ) ? $params['perItemStatus'] : false;
$this->res = false;
$dbw = wfGetDB( DB_MASTER );
@@ -98,16 +99,29 @@ abstract class RevDel_List extends RevisionListBase {
$idsForLog = array();
$authorIds = $authorIPs = array();
+ if ( $perItemStatus ) {
+ $status->itemStatuses = array();
+ }
+
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
for ( $this->reset(); $this->current(); $this->next() ) {
+ // @codingStandardsIgnoreEnd
$item = $this->current();
unset( $missing[$item->getId()] );
+ if ( $perItemStatus ) {
+ $itemStatus = Status::newGood();
+ $status->itemStatuses[$item->getId()] = $itemStatus;
+ } else {
+ $itemStatus = $status;
+ }
+
$oldBits = $item->getBits();
// Build the actual new rev_deleted bitfield
$newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits );
if ( $oldBits == $newBits ) {
- $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
+ $itemStatus->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
$status->failCount++;
continue;
} elseif ( $oldBits == 0 && $newBits != 0 ) {
@@ -120,7 +134,7 @@ abstract class RevDel_List extends RevisionListBase {
if ( $item->isHideCurrentOp( $newBits ) ) {
// Cannot hide current version text
- $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
+ $itemStatus->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
$status->failCount++;
continue;
}
@@ -128,13 +142,13 @@ abstract class RevDel_List extends RevisionListBase {
// Cannot access this revision
$msg = ( $opType == 'show' ) ?
'revdelete-show-no-access' : 'revdelete-modify-no-access';
- $status->error( $msg, $item->formatDate(), $item->formatTime() );
+ $itemStatus->error( $msg, $item->formatDate(), $item->formatTime() );
$status->failCount++;
continue;
}
// Cannot just "hide from Sysops" without hiding any fields
if ( $newBits == Revision::DELETED_RESTRICTED ) {
- $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
+ $itemStatus->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
$status->failCount++;
continue;
}
@@ -151,19 +165,22 @@ abstract class RevDel_List extends RevisionListBase {
$authorIPs[] = $item->getAuthorName();
}
} else {
- $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
+ $itemStatus->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
$status->failCount++;
}
}
// Handle missing revisions
foreach ( $missing as $id => $unused ) {
- $status->error( 'revdelete-modify-missing', $id );
+ if ( $perItemStatus ) {
+ $status->itemStatuses[$id] = Status::newFatal( 'revdelete-modify-missing', $id );
+ } else {
+ $status->error( 'revdelete-modify-missing', $id );
+ }
$status->failCount++;
}
if ( $status->successCount == 0 ) {
- $status->ok = false;
$dbw->rollback( __METHOD__ );
return $status;
}
@@ -294,35 +311,3 @@ abstract class RevDel_List extends RevisionListBase {
*/
abstract public function getSuppressBit();
}
-
-/**
- * Abstract base class for deletable items
- */
-abstract class RevDel_Item extends RevisionItemBase {
- /**
- * Returns true if the item is "current", and the operation to set the given
- * bits can't be executed for that reason
- * STUB
- * @return bool
- */
- public function isHideCurrentOp( $newBits ) {
- return false;
- }
-
- /**
- * Get the current deletion bitfield value
- */
- abstract public function getBits();
-
- /**
- * Set the visibility of the item. This should do any necessary DB queries.
- *
- * The DB update query should have a condition which forces it to only update
- * if the value in the DB matches the value fetched earlier with the SELECT.
- * If the update fails because it did not match, the function should return
- * false. This prevents concurrency problems.
- *
- * @return boolean success
- */
- abstract public function setBits( $newBits );
-}
diff --git a/includes/revisiondelete/RevDelLogItem.php b/includes/revisiondelete/RevDelLogItem.php
new file mode 100644
index 00000000..5c8b8c9d
--- /dev/null
+++ b/includes/revisiondelete/RevDelLogItem.php
@@ -0,0 +1,151 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * Item class for a logging table row
+ */
+class RevDelLogItem extends RevDelItem {
+ public function getIdField() {
+ return 'log_id';
+ }
+
+ public function getTimestampField() {
+ return 'log_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'log_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'log_user_text';
+ }
+
+ public function canView() {
+ return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED, $this->list->getUser() );
+ }
+
+ public function canViewContent() {
+ return true; // none
+ }
+
+ public function getBits() {
+ return $this->row->log_deleted;
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'recentchanges',
+ array(
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => 1
+ ),
+ array(
+ 'rc_logid' => $this->row->log_id,
+ 'rc_timestamp' => $this->row->log_timestamp // index
+ ),
+ __METHOD__
+ );
+ $dbw->update( 'logging',
+ array( 'log_deleted' => $bits ),
+ array(
+ 'log_id' => $this->row->log_id,
+ 'log_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+
+ return (bool)$dbw->affectedRows();
+ }
+
+ public function getHTML() {
+ $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
+ $this->row->log_timestamp, $this->list->getUser() ) );
+ $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
+ $formatter = LogFormatter::newFromRow( $this->row );
+ $formatter->setContext( $this->list->getContext() );
+ $formatter->setAudience( LogFormatter::FOR_THIS_USER );
+
+ // Log link for this page
+ $loglink = Linker::link(
+ SpecialPage::getTitleFor( 'Log' ),
+ $this->list->msg( 'log' )->escaped(),
+ array(),
+ array( 'page' => $title->getPrefixedText() )
+ );
+ $loglink = $this->list->msg( 'parentheses' )->rawParams( $loglink )->escaped();
+ // User links and action text
+ $action = $formatter->getActionText();
+ // Comment
+ $comment = $this->list->getLanguage()->getDirMark()
+ . Linker::commentBlock( $this->row->log_comment );
+
+ if ( LogEventsList::isDeleted( $this->row, LogPage::DELETED_COMMENT ) ) {
+ $comment = '<span class="history-deleted">' . $comment . '</span>';
+ }
+
+ return "<li>$loglink $date $action $comment</li>";
+ }
+
+ public function getApiData( ApiResult $result ) {
+ $logEntry = DatabaseLogEntry::newFromRow( $this->row );
+ $user = $this->list->getUser();
+ $ret = array(
+ 'id' => $logEntry->getId(),
+ 'type' => $logEntry->getType(),
+ 'action' => $logEntry->getSubtype(),
+ );
+ $ret += $logEntry->isDeleted( LogPage::DELETED_USER )
+ ? array( 'userhidden' => '' )
+ : array();
+ $ret += $logEntry->isDeleted( LogPage::DELETED_COMMENT )
+ ? array( 'commenthidden' => '' )
+ : array();
+ $ret += $logEntry->isDeleted( LogPage::DELETED_ACTION )
+ ? array( 'actionhidden' => '' )
+ : array();
+
+ if ( LogEventsList::userCan( $this->row, LogPage::DELETED_ACTION, $user ) ) {
+ ApiQueryLogEvents::addLogParams(
+ $result,
+ $ret,
+ $logEntry->getParameters(),
+ $logEntry->getType(),
+ $logEntry->getSubtype(),
+ $logEntry->getTimestamp(),
+ $logEntry->isLegacy()
+ );
+ }
+ if ( LogEventsList::userCan( $this->row, LogPage::DELETED_USER, $user ) ) {
+ $ret += array(
+ 'userid' => $this->row->log_user,
+ 'user' => $this->row->log_user_text,
+ );
+ }
+ if ( LogEventsList::userCan( $this->row, LogPage::DELETED_COMMENT, $user ) ) {
+ $ret += array(
+ 'comment' => $this->row->log_comment,
+ );
+ }
+
+ return $ret;
+ }
+}
diff --git a/includes/revisiondelete/RevDelLogList.php b/includes/revisiondelete/RevDelLogList.php
new file mode 100644
index 00000000..ad040425
--- /dev/null
+++ b/includes/revisiondelete/RevDelLogList.php
@@ -0,0 +1,103 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * List for logging table items
+ */
+class RevDelLogList extends RevDelList {
+ public function getType() {
+ return 'logging';
+ }
+
+ public static function getRelationType() {
+ return 'log_id';
+ }
+
+ public static function getRestriction() {
+ return 'deletelogentry';
+ }
+
+ public static function getRevdelConstant() {
+ return LogPage::DELETED_ACTION;
+ }
+
+ public static function suggestTarget( $target, array $ids ) {
+ $result = wfGetDB( DB_SLAVE )->select( 'logging',
+ 'log_type',
+ array( 'log_id' => $ids ),
+ __METHOD__,
+ array( 'DISTINCT' )
+ );
+ if ( $result->numRows() == 1 ) {
+ // If there's only one type, the target can be set to include it.
+ return SpecialPage::getTitleFor( 'Log', $result->current()->log_type );
+ }
+
+ return SpecialPage::getTitleFor( 'Log' );
+ }
+
+ /**
+ * @param DatabaseBase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+
+ return $db->select( 'logging', array(
+ 'log_id',
+ 'log_type',
+ 'log_action',
+ 'log_timestamp',
+ 'log_user',
+ 'log_user_text',
+ 'log_namespace',
+ 'log_title',
+ 'log_page',
+ 'log_comment',
+ 'log_params',
+ 'log_deleted'
+ ),
+ array( 'log_id' => $ids ),
+ __METHOD__,
+ array( 'ORDER BY' => 'log_id DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDelLogItem( $this, $row );
+ }
+
+ public function getSuppressBit() {
+ return Revision::DELETED_RESTRICTED;
+ }
+
+ public function getLogAction() {
+ return 'event';
+ }
+
+ public function getLogParams( $params ) {
+ return array(
+ implode( ',', $params['ids'] ),
+ "ofield={$params['oldBits']}",
+ "nfield={$params['newBits']}"
+ );
+ }
+}
diff --git a/includes/revisiondelete/RevDelRevisionItem.php b/includes/revisiondelete/RevDelRevisionItem.php
new file mode 100644
index 00000000..300ce6ad
--- /dev/null
+++ b/includes/revisiondelete/RevDelRevisionItem.php
@@ -0,0 +1,187 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * Item class for a live revision table row
+ */
+class RevDelRevisionItem extends RevDelItem {
+ /** @var Revision */
+ public $revision;
+
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->revision = new Revision( $row );
+ }
+
+ public function getIdField() {
+ return 'rev_id';
+ }
+
+ public function getTimestampField() {
+ return 'rev_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'rev_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'rev_user_text';
+ }
+
+ public function canView() {
+ return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->list->getUser() );
+ }
+
+ public function canViewContent() {
+ return $this->revision->userCan( Revision::DELETED_TEXT, $this->list->getUser() );
+ }
+
+ public function getBits() {
+ return $this->revision->getVisibility();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ // Update revision table
+ $dbw->update( 'revision',
+ array( 'rev_deleted' => $bits ),
+ array(
+ 'rev_id' => $this->revision->getId(),
+ 'rev_page' => $this->revision->getPage(),
+ 'rev_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+ if ( !$dbw->affectedRows() ) {
+ // Concurrent fail!
+ return false;
+ }
+ // Update recentchanges table
+ $dbw->update( 'recentchanges',
+ array(
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => 1
+ ),
+ array(
+ 'rc_this_oldid' => $this->revision->getId(), // condition
+ // non-unique timestamp index
+ 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
+ ),
+ __METHOD__
+ );
+
+ return true;
+ }
+
+ public function isDeleted() {
+ return $this->revision->isDeleted( Revision::DELETED_TEXT );
+ }
+
+ public function isHideCurrentOp( $newBits ) {
+ return ( $newBits & Revision::DELETED_TEXT )
+ && $this->list->getCurrent() == $this->getId();
+ }
+
+ /**
+ * Get the HTML link to the revision text.
+ * Overridden by RevDelArchiveItem.
+ * @return string
+ */
+ protected function getRevisionLink() {
+ $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
+ $this->revision->getTimestamp(), $this->list->getUser() ) );
+
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $date;
+ }
+
+ return Linker::linkKnown(
+ $this->list->title,
+ $date,
+ array(),
+ array(
+ 'oldid' => $this->revision->getId(),
+ 'unhide' => 1
+ )
+ );
+ }
+
+ /**
+ * Get the HTML link to the diff.
+ * Overridden by RevDelArchiveItem
+ * @return string
+ */
+ protected function getDiffLink() {
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $this->list->msg( 'diff' )->escaped();
+ } else {
+ return Linker::linkKnown(
+ $this->list->title,
+ $this->list->msg( 'diff' )->escaped(),
+ array(),
+ array(
+ 'diff' => $this->revision->getId(),
+ 'oldid' => 'prev',
+ 'unhide' => 1
+ )
+ );
+ }
+ }
+
+ public function getHTML() {
+ $difflink = $this->list->msg( 'parentheses' )
+ ->rawParams( $this->getDiffLink() )->escaped();
+ $revlink = $this->getRevisionLink();
+ $userlink = Linker::revUserLink( $this->revision );
+ $comment = Linker::revComment( $this->revision );
+ if ( $this->isDeleted() ) {
+ $revlink = "<span class=\"history-deleted\">$revlink</span>";
+ }
+
+ return "<li>$difflink $revlink $userlink $comment</li>";
+ }
+
+ public function getApiData( ApiResult $result ) {
+ $rev = $this->revision;
+ $user = $this->list->getUser();
+ $ret = array(
+ 'id' => $rev->getId(),
+ 'timestamp' => wfTimestamp( TS_ISO_8601, $rev->getTimestamp() ),
+ );
+ $ret += $rev->isDeleted( Revision::DELETED_USER ) ? array( 'userhidden' => '' ) : array();
+ $ret += $rev->isDeleted( Revision::DELETED_COMMENT ) ? array( 'commenthidden' => '' ) : array();
+ $ret += $rev->isDeleted( Revision::DELETED_TEXT ) ? array( 'texthidden' => '' ) : array();
+ if ( $rev->userCan( Revision::DELETED_USER, $user ) ) {
+ $ret += array(
+ 'userid' => $rev->getUser( Revision::FOR_THIS_USER ),
+ 'user' => $rev->getUserText( Revision::FOR_THIS_USER ),
+ );
+ }
+ if ( $rev->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $ret += array(
+ 'comment' => $rev->getComment( Revision::FOR_THIS_USER ),
+ );
+ }
+
+ return $ret;
+ }
+}
diff --git a/includes/revisiondelete/RevDelRevisionList.php b/includes/revisiondelete/RevDelRevisionList.php
new file mode 100644
index 00000000..25450725
--- /dev/null
+++ b/includes/revisiondelete/RevDelRevisionList.php
@@ -0,0 +1,143 @@
+<?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 RevisionDelete
+ */
+
+/**
+ * List for revision table items
+ *
+ * This will check both the 'revision' table for live revisions and the
+ * 'archive' table for traditionally-deleted revisions that have an
+ * ar_rev_id saved.
+ *
+ * See RevDelRevisionItem and RevDelArchivedRevisionItem for items.
+ */
+class RevDelRevisionList extends RevDelList {
+ /** @var int */
+ public $currentRevId;
+
+ public function getType() {
+ return 'revision';
+ }
+
+ public static function getRelationType() {
+ return 'rev_id';
+ }
+
+ public static function getRestriction() {
+ return 'deleterevision';
+ }
+
+ public static function getRevdelConstant() {
+ return Revision::DELETED_TEXT;
+ }
+
+ public static function suggestTarget( $target, array $ids ) {
+ $rev = Revision::newFromId( $ids[0] );
+ return $rev ? $rev->getTitle() : $target;
+ }
+
+ /**
+ * @param DatabaseBase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ $live = $db->select(
+ array( 'revision', 'page', 'user' ),
+ array_merge( Revision::selectFields(), Revision::selectUserFields() ),
+ array(
+ 'rev_page' => $this->title->getArticleID(),
+ 'rev_id' => $ids,
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_id DESC' ),
+ array(
+ 'page' => Revision::pageJoinCond(),
+ 'user' => Revision::userJoinCond() )
+ );
+
+ if ( $live->numRows() >= count( $ids ) ) {
+ // All requested revisions are live, keeps things simple!
+ return $live;
+ }
+
+ // Check if any requested revisions are available fully deleted.
+ $archived = $db->select( array( 'archive' ), Revision::selectArchiveFields(),
+ array(
+ 'ar_rev_id' => $ids
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'ar_rev_id DESC' )
+ );
+
+ if ( $archived->numRows() == 0 ) {
+ return $live;
+ } elseif ( $live->numRows() == 0 ) {
+ return $archived;
+ } else {
+ // Combine the two! Whee
+ $rows = array();
+ foreach ( $live as $row ) {
+ $rows[$row->rev_id] = $row;
+ }
+ foreach ( $archived as $row ) {
+ $rows[$row->ar_rev_id] = $row;
+ }
+ krsort( $rows );
+ return new FakeResultWrapper( array_values( $rows ) );
+ }
+ }
+
+ public function newItem( $row ) {
+ if ( isset( $row->rev_id ) ) {
+ return new RevDelRevisionItem( $this, $row );
+ } elseif ( isset( $row->ar_rev_id ) ) {
+ return new RevDelArchivedRevisionItem( $this, $row );
+ } else {
+ // This shouldn't happen. :)
+ throw new MWException( 'Invalid row type in RevDelRevisionList' );
+ }
+ }
+
+ public function getCurrent() {
+ if ( is_null( $this->currentRevId ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $this->currentRevId = $dbw->selectField(
+ 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
+ }
+ return $this->currentRevId;
+ }
+
+ public function getSuppressBit() {
+ return Revision::DELETED_RESTRICTED;
+ }
+
+ public function doPreCommitUpdates() {
+ $this->title->invalidateCache();
+ return Status::newGood();
+ }
+
+ public function doPostCommitUpdates() {
+ $this->title->purgeSquid();
+ // Extensions that require referencing previous revisions may need this
+ wfRunHooks( 'ArticleRevisionVisibilitySet', array( &$this->title ) );
+ return Status::newGood();
+ }
+}
diff --git a/includes/revisiondelete/RevisionDelete.php b/includes/revisiondelete/RevisionDelete.php
deleted file mode 100644
index 71850877..00000000
--- a/includes/revisiondelete/RevisionDelete.php
+++ /dev/null
@@ -1,964 +0,0 @@
-<?php
-/**
- * Base implementations for deletable items.
- *
- * 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 RevisionDelete
- */
-
-/**
- * List for revision table items
- *
- * This will check both the 'revision' table for live revisions and the
- * 'archive' table for traditionally-deleted revisions that have an
- * ar_rev_id saved.
- *
- * See RevDel_RevisionItem and RevDel_ArchivedRevisionItem for items.
- */
-class RevDel_RevisionList extends RevDel_List {
- var $currentRevId;
-
- public function getType() {
- return 'revision';
- }
-
- public static function getRelationType() {
- return 'rev_id';
- }
-
- public static function getRestriction() {
- return 'deleterevision';
- }
-
- public static function getRevdelConstant() {
- return Revision::DELETED_TEXT;
- }
-
- public static function suggestTarget( $target, array $ids ) {
- $rev = Revision::newFromId( $ids[0] );
- return $rev ? $rev->getTitle() : $target;
- }
-
- /**
- * @param $db DatabaseBase
- * @return mixed
- */
- public function doQuery( $db ) {
- $ids = array_map( 'intval', $this->ids );
- $live = $db->select(
- array( 'revision', 'page', 'user' ),
- array_merge( Revision::selectFields(), Revision::selectUserFields() ),
- array(
- 'rev_page' => $this->title->getArticleID(),
- 'rev_id' => $ids,
- ),
- __METHOD__,
- array( 'ORDER BY' => 'rev_id DESC' ),
- array(
- 'page' => Revision::pageJoinCond(),
- 'user' => Revision::userJoinCond() )
- );
-
- if ( $live->numRows() >= count( $ids ) ) {
- // All requested revisions are live, keeps things simple!
- return $live;
- }
-
- // Check if any requested revisions are available fully deleted.
- $archived = $db->select( array( 'archive' ), '*',
- array(
- 'ar_rev_id' => $ids
- ),
- __METHOD__,
- array( 'ORDER BY' => 'ar_rev_id DESC' )
- );
-
- if ( $archived->numRows() == 0 ) {
- return $live;
- } elseif ( $live->numRows() == 0 ) {
- return $archived;
- } else {
- // Combine the two! Whee
- $rows = array();
- foreach ( $live as $row ) {
- $rows[$row->rev_id] = $row;
- }
- foreach ( $archived as $row ) {
- $rows[$row->ar_rev_id] = $row;
- }
- krsort( $rows );
- return new FakeResultWrapper( array_values( $rows ) );
- }
- }
-
- public function newItem( $row ) {
- if ( isset( $row->rev_id ) ) {
- return new RevDel_RevisionItem( $this, $row );
- } elseif ( isset( $row->ar_rev_id ) ) {
- return new RevDel_ArchivedRevisionItem( $this, $row );
- } else {
- // This shouldn't happen. :)
- throw new MWException( 'Invalid row type in RevDel_RevisionList' );
- }
- }
-
- public function getCurrent() {
- if ( is_null( $this->currentRevId ) ) {
- $dbw = wfGetDB( DB_MASTER );
- $this->currentRevId = $dbw->selectField(
- 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
- }
- return $this->currentRevId;
- }
-
- public function getSuppressBit() {
- return Revision::DELETED_RESTRICTED;
- }
-
- public function doPreCommitUpdates() {
- $this->title->invalidateCache();
- return Status::newGood();
- }
-
- public function doPostCommitUpdates() {
- $this->title->purgeSquid();
- // Extensions that require referencing previous revisions may need this
- wfRunHooks( 'ArticleRevisionVisibilitySet', array( &$this->title ) );
- return Status::newGood();
- }
-}
-
-/**
- * Item class for a live revision table row
- */
-class RevDel_RevisionItem extends RevDel_Item {
- var $revision;
-
- public function __construct( $list, $row ) {
- parent::__construct( $list, $row );
- $this->revision = new Revision( $row );
- }
-
- public function getIdField() {
- return 'rev_id';
- }
-
- public function getTimestampField() {
- return 'rev_timestamp';
- }
-
- public function getAuthorIdField() {
- return 'rev_user';
- }
-
- public function getAuthorNameField() {
- return 'user_name'; // see Revision::selectUserFields()
- }
-
- public function canView() {
- return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->list->getUser() );
- }
-
- public function canViewContent() {
- return $this->revision->userCan( Revision::DELETED_TEXT, $this->list->getUser() );
- }
-
- public function getBits() {
- return $this->revision->getVisibility();
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- // Update revision table
- $dbw->update( 'revision',
- array( 'rev_deleted' => $bits ),
- array(
- 'rev_id' => $this->revision->getId(),
- 'rev_page' => $this->revision->getPage(),
- 'rev_deleted' => $this->getBits()
- ),
- __METHOD__
- );
- if ( !$dbw->affectedRows() ) {
- // Concurrent fail!
- return false;
- }
- // Update recentchanges table
- $dbw->update( 'recentchanges',
- array(
- 'rc_deleted' => $bits,
- 'rc_patrolled' => 1
- ),
- array(
- 'rc_this_oldid' => $this->revision->getId(), // condition
- // non-unique timestamp index
- 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
- ),
- __METHOD__
- );
- return true;
- }
-
- public function isDeleted() {
- return $this->revision->isDeleted( Revision::DELETED_TEXT );
- }
-
- public function isHideCurrentOp( $newBits ) {
- return ( $newBits & Revision::DELETED_TEXT )
- && $this->list->getCurrent() == $this->getId();
- }
-
- /**
- * Get the HTML link to the revision text.
- * Overridden by RevDel_ArchiveItem.
- * @return string
- */
- protected function getRevisionLink() {
- $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
- $this->revision->getTimestamp(), $this->list->getUser() ) );
-
- if ( $this->isDeleted() && !$this->canViewContent() ) {
- return $date;
- }
- return Linker::linkKnown(
- $this->list->title,
- $date,
- array(),
- array(
- 'oldid' => $this->revision->getId(),
- 'unhide' => 1
- )
- );
- }
-
- /**
- * Get the HTML link to the diff.
- * Overridden by RevDel_ArchiveItem
- * @return string
- */
- protected function getDiffLink() {
- if ( $this->isDeleted() && !$this->canViewContent() ) {
- return $this->list->msg( 'diff' )->escaped();
- } else {
- return Linker::linkKnown(
- $this->list->title,
- $this->list->msg( 'diff' )->escaped(),
- array(),
- array(
- 'diff' => $this->revision->getId(),
- 'oldid' => 'prev',
- 'unhide' => 1
- )
- );
- }
- }
-
- public function getHTML() {
- $difflink = $this->list->msg( 'parentheses' )
- ->rawParams( $this->getDiffLink() )->escaped();
- $revlink = $this->getRevisionLink();
- $userlink = Linker::revUserLink( $this->revision );
- $comment = Linker::revComment( $this->revision );
- if ( $this->isDeleted() ) {
- $revlink = "<span class=\"history-deleted\">$revlink</span>";
- }
- return "<li>$difflink $revlink $userlink $comment</li>";
- }
-}
-
-/**
- * List for archive table items, i.e. revisions deleted via action=delete
- */
-class RevDel_ArchiveList extends RevDel_RevisionList {
- public function getType() {
- return 'archive';
- }
-
- public static function getRelationType() {
- return 'ar_timestamp';
- }
-
- /**
- * @param $db DatabaseBase
- * @return mixed
- */
- public function doQuery( $db ) {
- $timestamps = array();
- foreach ( $this->ids as $id ) {
- $timestamps[] = $db->timestamp( $id );
- }
- return $db->select( 'archive', '*',
- array(
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- 'ar_timestamp' => $timestamps
- ),
- __METHOD__,
- array( 'ORDER BY' => 'ar_timestamp DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_ArchiveItem( $this, $row );
- }
-
- public function doPreCommitUpdates() {
- return Status::newGood();
- }
-
- public function doPostCommitUpdates() {
- return Status::newGood();
- }
-}
-
-/**
- * Item class for a archive table row
- */
-class RevDel_ArchiveItem extends RevDel_RevisionItem {
- public function __construct( $list, $row ) {
- RevDel_Item::__construct( $list, $row );
- $this->revision = Revision::newFromArchiveRow( $row,
- array( 'page' => $this->list->title->getArticleID() ) );
- }
-
- public function getIdField() {
- return 'ar_timestamp';
- }
-
- public function getTimestampField() {
- return 'ar_timestamp';
- }
-
- public function getAuthorIdField() {
- return 'ar_user';
- }
-
- public function getAuthorNameField() {
- return 'ar_user_text';
- }
-
- public function getId() {
- # Convert DB timestamp to MW timestamp
- return $this->revision->getTimestamp();
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'archive',
- array( 'ar_deleted' => $bits ),
- array(
- 'ar_namespace' => $this->list->title->getNamespace(),
- 'ar_title' => $this->list->title->getDBkey(),
- // use timestamp for index
- 'ar_timestamp' => $this->row->ar_timestamp,
- 'ar_rev_id' => $this->row->ar_rev_id,
- 'ar_deleted' => $this->getBits()
- ),
- __METHOD__ );
- return (bool)$dbw->affectedRows();
- }
-
- protected function getRevisionLink() {
- $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
- $this->revision->getTimestamp(), $this->list->getUser() ) );
-
- if ( $this->isDeleted() && !$this->canViewContent() ) {
- return $date;
- }
-
- return Linker::link(
- SpecialPage::getTitleFor( 'Undelete' ),
- $date,
- array(),
- array(
- 'target' => $this->list->title->getPrefixedText(),
- 'timestamp' => $this->revision->getTimestamp()
- )
- );
- }
-
- protected function getDiffLink() {
- if ( $this->isDeleted() && !$this->canViewContent() ) {
- return $this->list->msg( 'diff' )->escaped();
- }
-
- return Linker::link(
- SpecialPage::getTitleFor( 'Undelete' ),
- $this->list->msg( 'diff' )->escaped(),
- array(),
- array(
- 'target' => $this->list->title->getPrefixedText(),
- 'diff' => 'prev',
- 'timestamp' => $this->revision->getTimestamp()
- )
- );
- }
-}
-
-/**
- * Item class for a archive table row by ar_rev_id -- actually
- * used via RevDel_RevisionList.
- */
-class RevDel_ArchivedRevisionItem extends RevDel_ArchiveItem {
- public function __construct( $list, $row ) {
- RevDel_Item::__construct( $list, $row );
-
- $this->revision = Revision::newFromArchiveRow( $row,
- array( 'page' => $this->list->title->getArticleID() ) );
- }
-
- public function getIdField() {
- return 'ar_rev_id';
- }
-
- public function getId() {
- return $this->revision->getId();
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'archive',
- array( 'ar_deleted' => $bits ),
- array( 'ar_rev_id' => $this->row->ar_rev_id,
- 'ar_deleted' => $this->getBits()
- ),
- __METHOD__ );
- return (bool)$dbw->affectedRows();
- }
-}
-
-/**
- * List for oldimage table items
- */
-class RevDel_FileList extends RevDel_List {
- public function getType() {
- return 'oldimage';
- }
-
- public static function getRelationType() {
- return 'oi_archive_name';
- }
-
- public static function getRestriction() {
- return 'deleterevision';
- }
-
- public static function getRevdelConstant() {
- return File::DELETED_FILE;
- }
-
- var $storeBatch, $deleteBatch, $cleanupBatch;
-
- /**
- * @param $db DatabaseBase
- * @return mixed
- */
- public function doQuery( $db ) {
- $archiveNames = array();
- foreach ( $this->ids as $timestamp ) {
- $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
- }
- return $db->select(
- 'oldimage',
- OldLocalFile::selectFields(),
- array(
- 'oi_name' => $this->title->getDBkey(),
- 'oi_archive_name' => $archiveNames
- ),
- __METHOD__,
- array( 'ORDER BY' => 'oi_timestamp DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_FileItem( $this, $row );
- }
-
- public function clearFileOps() {
- $this->deleteBatch = array();
- $this->storeBatch = array();
- $this->cleanupBatch = array();
- }
-
- public function doPreCommitUpdates() {
- $status = Status::newGood();
- $repo = RepoGroup::singleton()->getLocalRepo();
- if ( $this->storeBatch ) {
- $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
- }
- if ( !$status->isOK() ) {
- return $status;
- }
- if ( $this->deleteBatch ) {
- $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
- }
- if ( !$status->isOK() ) {
- // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
- // modified (but destined for rollback) causes data loss
- return $status;
- }
- if ( $this->cleanupBatch ) {
- $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
- }
- return $status;
- }
-
- public function doPostCommitUpdates() {
- global $wgUseSquid;
- $file = wfLocalFile( $this->title );
- $file->purgeCache();
- $file->purgeDescription();
- $purgeUrls = array();
- foreach ( $this->ids as $timestamp ) {
- $archiveName = $timestamp . '!' . $this->title->getDBkey();
- $file->purgeOldThumbnails( $archiveName );
- $purgeUrls[] = $file->getArchiveUrl( $archiveName );
- }
- if ( $wgUseSquid ) {
- // purge full images from cache
- SquidUpdate::purge( $purgeUrls );
- }
- return Status::newGood();
- }
-
- public function getSuppressBit() {
- return File::DELETED_RESTRICTED;
- }
-}
-
-/**
- * Item class for an oldimage table row
- */
-class RevDel_FileItem extends RevDel_Item {
-
- /**
- * @var File
- */
- var $file;
-
- public function __construct( $list, $row ) {
- parent::__construct( $list, $row );
- $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
- }
-
- public function getIdField() {
- return 'oi_archive_name';
- }
-
- public function getTimestampField() {
- return 'oi_timestamp';
- }
-
- public function getAuthorIdField() {
- return 'oi_user';
- }
-
- public function getAuthorNameField() {
- return 'oi_user_text';
- }
-
- public function getId() {
- $parts = explode( '!', $this->row->oi_archive_name );
- return $parts[0];
- }
-
- public function canView() {
- return $this->file->userCan( File::DELETED_RESTRICTED, $this->list->getUser() );
- }
-
- public function canViewContent() {
- return $this->file->userCan( File::DELETED_FILE, $this->list->getUser() );
- }
-
- public function getBits() {
- return $this->file->getVisibility();
- }
-
- public function setBits( $bits ) {
- # Queue the file op
- # @todo FIXME: Move to LocalFile.php
- if ( $this->isDeleted() ) {
- if ( $bits & File::DELETED_FILE ) {
- # Still deleted
- } else {
- # Newly undeleted
- $key = $this->file->getStorageKey();
- $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
- $this->list->storeBatch[] = array(
- $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
- 'public',
- $this->file->getRel()
- );
- $this->list->cleanupBatch[] = $key;
- }
- } elseif ( $bits & File::DELETED_FILE ) {
- # Newly deleted
- $key = $this->file->getStorageKey();
- $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
- $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
- }
-
- # Do the database operations
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'oldimage',
- array( 'oi_deleted' => $bits ),
- array(
- 'oi_name' => $this->row->oi_name,
- 'oi_timestamp' => $this->row->oi_timestamp,
- 'oi_deleted' => $this->getBits()
- ),
- __METHOD__
- );
- return (bool)$dbw->affectedRows();
- }
-
- public function isDeleted() {
- return $this->file->isDeleted( File::DELETED_FILE );
- }
-
- /**
- * Get the link to the file.
- * Overridden by RevDel_ArchivedFileItem.
- * @return string
- */
- protected function getLink() {
- $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
- $this->file->getTimestamp(), $this->list->getUser() ) );
-
- if ( !$this->isDeleted() ) {
- # Regular files...
- return Html::rawElement( 'a', array( 'href' => $this->file->getUrl() ), $date );
- }
-
- # Hidden files...
- if ( !$this->canViewContent() ) {
- $link = $date;
- } else {
- $link = Linker::link(
- SpecialPage::getTitleFor( 'Revisiondelete' ),
- $date,
- array(),
- array(
- 'target' => $this->list->title->getPrefixedText(),
- 'file' => $this->file->getArchiveName(),
- 'token' => $this->list->getUser()->getEditToken(
- $this->file->getArchiveName() )
- )
- );
- }
- return '<span class="history-deleted">' . $link . '</span>';
- }
- /**
- * Generate a user tool link cluster if the current user is allowed to view it
- * @return string HTML
- */
- protected function getUserTools() {
- if ( $this->file->userCan( Revision::DELETED_USER, $this->list->getUser() ) ) {
- $link = Linker::userLink( $this->file->user, $this->file->user_text ) .
- Linker::userToolLinks( $this->file->user, $this->file->user_text );
- } else {
- $link = $this->list->msg( 'rev-deleted-user' )->escaped();
- }
- if ( $this->file->isDeleted( Revision::DELETED_USER ) ) {
- return '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
- }
-
- /**
- * Wrap and format the file's comment block, if the current
- * user is allowed to view it.
- *
- * @return string HTML
- */
- protected function getComment() {
- if ( $this->file->userCan( File::DELETED_COMMENT, $this->list->getUser() ) ) {
- $block = Linker::commentBlock( $this->file->description );
- } else {
- $block = ' ' . $this->list->msg( 'rev-deleted-comment' )->escaped();
- }
- if ( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
- return "<span class=\"history-deleted\">$block</span>";
- }
- return $block;
- }
-
- public function getHTML() {
- $data =
- $this->list->msg( 'widthheight' )->numParams(
- $this->file->getWidth(), $this->file->getHeight() )->text() .
- ' (' . $this->list->msg( 'nbytes' )->numParams( $this->file->getSize() )->text() . ')';
-
- return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
- $data . ' ' . $this->getComment() . '</li>';
- }
-}
-
-/**
- * List for filearchive table items
- */
-class RevDel_ArchivedFileList extends RevDel_FileList {
- public function getType() {
- return 'filearchive';
- }
-
- public static function getRelationType() {
- return 'fa_id';
- }
-
- /**
- * @param $db DatabaseBase
- * @return mixed
- */
- public function doQuery( $db ) {
- $ids = array_map( 'intval', $this->ids );
- return $db->select(
- 'filearchive',
- ArchivedFile::selectFields(),
- array(
- 'fa_name' => $this->title->getDBkey(),
- 'fa_id' => $ids
- ),
- __METHOD__,
- array( 'ORDER BY' => 'fa_id DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_ArchivedFileItem( $this, $row );
- }
-}
-
-/**
- * Item class for a filearchive table row
- */
-class RevDel_ArchivedFileItem extends RevDel_FileItem {
- public function __construct( $list, $row ) {
- RevDel_Item::__construct( $list, $row );
- $this->file = ArchivedFile::newFromRow( $row );
- }
-
- public function getIdField() {
- return 'fa_id';
- }
-
- public function getTimestampField() {
- return 'fa_timestamp';
- }
-
- public function getAuthorIdField() {
- return 'fa_user';
- }
-
- public function getAuthorNameField() {
- return 'fa_user_text';
- }
-
- public function getId() {
- return $this->row->fa_id;
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'filearchive',
- array( 'fa_deleted' => $bits ),
- array(
- 'fa_id' => $this->row->fa_id,
- 'fa_deleted' => $this->getBits(),
- ),
- __METHOD__
- );
- return (bool)$dbw->affectedRows();
- }
-
- protected function getLink() {
- $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
- $this->file->getTimestamp(), $this->list->getUser() ) );
-
- # Hidden files...
- if ( !$this->canViewContent() ) {
- $link = $date;
- } else {
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
- $key = $this->file->getKey();
- $link = Linker::link( $undelete, $date, array(),
- array(
- 'target' => $this->list->title->getPrefixedText(),
- 'file' => $key,
- 'token' => $this->list->getUser()->getEditToken( $key )
- )
- );
- }
- if ( $this->isDeleted() ) {
- $link = '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
- }
-}
-
-/**
- * List for logging table items
- */
-class RevDel_LogList extends RevDel_List {
- public function getType() {
- return 'logging';
- }
-
- public static function getRelationType() {
- return 'log_id';
- }
-
- public static function getRestriction() {
- return 'deletelogentry';
- }
-
- public static function getRevdelConstant() {
- return LogPage::DELETED_ACTION;
- }
-
- public static function suggestTarget( $target, array $ids ) {
- $result = wfGetDB( DB_SLAVE )->select( 'logging',
- 'log_type',
- array( 'log_id' => $ids ),
- __METHOD__,
- array( 'DISTINCT' )
- );
- if ( $result->numRows() == 1 ) {
- // If there's only one type, the target can be set to include it.
- return SpecialPage::getTitleFor( 'Log', $result->current()->log_type );
- }
- return SpecialPage::getTitleFor( 'Log' );
- }
-
- /**
- * @param $db DatabaseBase
- * @return mixed
- */
- public function doQuery( $db ) {
- $ids = array_map( 'intval', $this->ids );
- return $db->select( 'logging', '*',
- array( 'log_id' => $ids ),
- __METHOD__,
- array( 'ORDER BY' => 'log_id DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_LogItem( $this, $row );
- }
-
- public function getSuppressBit() {
- return Revision::DELETED_RESTRICTED;
- }
-
- public function getLogAction() {
- return 'event';
- }
-
- public function getLogParams( $params ) {
- return array(
- implode( ',', $params['ids'] ),
- "ofield={$params['oldBits']}",
- "nfield={$params['newBits']}"
- );
- }
-}
-
-/**
- * Item class for a logging table row
- */
-class RevDel_LogItem extends RevDel_Item {
- public function getIdField() {
- return 'log_id';
- }
-
- public function getTimestampField() {
- return 'log_timestamp';
- }
-
- public function getAuthorIdField() {
- return 'log_user';
- }
-
- public function getAuthorNameField() {
- return 'log_user_text';
- }
-
- public function canView() {
- return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED, $this->list->getUser() );
- }
-
- public function canViewContent() {
- return true; // none
- }
-
- public function getBits() {
- return $this->row->log_deleted;
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'recentchanges',
- array(
- 'rc_deleted' => $bits,
- 'rc_patrolled' => 1
- ),
- array(
- 'rc_logid' => $this->row->log_id,
- 'rc_timestamp' => $this->row->log_timestamp // index
- ),
- __METHOD__
- );
- $dbw->update( 'logging',
- array( 'log_deleted' => $bits ),
- array(
- 'log_id' => $this->row->log_id,
- 'log_deleted' => $this->getBits()
- ),
- __METHOD__
- );
- return (bool)$dbw->affectedRows();
- }
-
- public function getHTML() {
- $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
- $this->row->log_timestamp, $this->list->getUser() ) );
- $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
- $formatter = LogFormatter::newFromRow( $this->row );
- $formatter->setContext( $this->list->getContext() );
- $formatter->setAudience( LogFormatter::FOR_THIS_USER );
-
- // Log link for this page
- $loglink = Linker::link(
- SpecialPage::getTitleFor( 'Log' ),
- $this->list->msg( 'log' )->escaped(),
- array(),
- array( 'page' => $title->getPrefixedText() )
- );
- $loglink = $this->list->msg( 'parentheses' )->rawParams( $loglink )->escaped();
- // User links and action text
- $action = $formatter->getActionText();
- // Comment
- $comment = $this->list->getLanguage()->getDirMark() . Linker::commentBlock( $this->row->log_comment );
- if ( LogEventsList::isDeleted( $this->row, LogPage::DELETED_COMMENT ) ) {
- $comment = '<span class="history-deleted">' . $comment . '</span>';
- }
-
- return "<li>$loglink $date $action $comment</li>";
- }
-}
diff --git a/includes/revisiondelete/RevisionDeleteUser.php b/includes/revisiondelete/RevisionDeleteUser.php
index 4505ee10..55c46c5e 100644
--- a/includes/revisiondelete/RevisionDeleteUser.php
+++ b/includes/revisiondelete/RevisionDeleteUser.php
@@ -33,10 +33,10 @@ class RevisionDeleteUser {
/**
* Update *_deleted bitfields in various tables to hide or unhide usernames
- * @param $name String username
- * @param $userId Int user id
- * @param $op String operator '|' or '&'
- * @param $dbw null|DatabaseBase, if you happen to have one lying around
+ * @param string $name Username
+ * @param int $userId User id
+ * @param string $op Operator '|' or '&'
+ * @param null|DatabaseBase $dbw If you happen to have one lying around
* @return bool
*/
private static function setUsernameBitfields( $name, $userId, $op, $dbw ) {
@@ -55,8 +55,8 @@ class RevisionDeleteUser {
$delUser = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
$delAction = LogPage::DELETED_ACTION | Revision::DELETED_RESTRICTED;
if ( $op == '&' ) {
- $delUser = "~{$delUser}";
- $delAction = "~{$delAction}";
+ $delUser = $dbw->bitNot( $delUser );
+ $delAction = $dbw->bitNot( $delAction );
}
# Normalize user name
@@ -66,14 +66,14 @@ class RevisionDeleteUser {
# Hide name from live edits
$dbw->update(
'revision',
- array( "rev_deleted = rev_deleted $op $delUser" ),
+ array( self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ),
array( 'rev_user' => $userId ),
__METHOD__ );
# Hide name from deleted edits
$dbw->update(
'archive',
- array( "ar_deleted = ar_deleted $op $delUser" ),
+ array( self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ),
array( 'ar_user_text' => $name ),
__METHOD__
);
@@ -81,28 +81,28 @@ class RevisionDeleteUser {
# Hide name from logs
$dbw->update(
'logging',
- array( "log_deleted = log_deleted $op $delUser" ),
- array( 'log_user' => $userId, "log_type != 'suppress'" ),
+ array( self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ),
+ array( 'log_user' => $userId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ),
__METHOD__
);
$dbw->update(
'logging',
- array( "log_deleted = log_deleted $op $delAction" ),
+ array( self::buildSetBitDeletedField( 'log_deleted', $op, $delAction, $dbw ) ),
array( 'log_namespace' => NS_USER, 'log_title' => $userDbKey,
- "log_type != 'suppress'" ),
+ 'log_type != ' . $dbw->addQuotes( 'suppress' ) ),
__METHOD__
);
# Hide name from RC
$dbw->update(
'recentchanges',
- array( "rc_deleted = rc_deleted $op $delUser" ),
+ array( self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ),
array( 'rc_user_text' => $name ),
__METHOD__
);
$dbw->update(
'recentchanges',
- array( "rc_deleted = rc_deleted $op $delAction" ),
+ array( self::buildSetBitDeletedField( 'rc_deleted', $op, $delAction, $dbw ) ),
array( 'rc_namespace' => NS_USER, 'rc_title' => $userDbKey, 'rc_logid > 0' ),
__METHOD__
);
@@ -110,7 +110,7 @@ class RevisionDeleteUser {
# Hide name from live images
$dbw->update(
'oldimage',
- array( "oi_deleted = oi_deleted $op $delUser" ),
+ array( self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ),
array( 'oi_user_text' => $name ),
__METHOD__
);
@@ -118,7 +118,7 @@ class RevisionDeleteUser {
# Hide name from deleted images
$dbw->update(
'filearchive',
- array( "fa_deleted = fa_deleted $op $delUser" ),
+ array( self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ),
array( 'fa_user_text' => $name ),
__METHOD__
);
@@ -126,6 +126,12 @@ class RevisionDeleteUser {
return true;
}
+ private static function buildSetBitDeletedField( $field, $op, $value, $dbw ) {
+ return $field . ' = ' . ( $op === '&'
+ ? $dbw->bitAnd( $field, $value )
+ : $dbw->bitOr( $field, $value ) );
+ }
+
public static function suppressUserName( $name, $userId, $dbw = null ) {
return self::setUsernameBitfields( $name, $userId, '|', $dbw );
}
diff --git a/includes/revisiondelete/RevisionDeleter.php b/includes/revisiondelete/RevisionDeleter.php
index dbcb3d7d..d4f81678 100644
--- a/includes/revisiondelete/RevisionDeleter.php
+++ b/includes/revisiondelete/RevisionDeleter.php
@@ -29,11 +29,11 @@
class RevisionDeleter {
/** List of known revdel types, with their corresponding list classes */
private static $allowedTypes = array(
- 'revision' => 'RevDel_RevisionList',
- 'archive' => 'RevDel_ArchiveList',
- 'oldimage' => 'RevDel_FileList',
- 'filearchive' => 'RevDel_ArchivedFileList',
- 'logging' => 'RevDel_LogList',
+ 'revision' => 'RevDelRevisionList',
+ 'archive' => 'RevDelArchiveList',
+ 'oldimage' => 'RevDelFileList',
+ 'filearchive' => 'RevDelArchivedFileList',
+ 'logging' => 'RevDelLogList',
);
/** Type map to support old log entries */
@@ -77,7 +77,7 @@ class RevisionDeleter {
* @param IContextSource $context
* @param Title $title
* @param array $ids
- * @return RevDel_List
+ * @return RevDelList
*/
public static function createList( $typeName, IContextSource $context, Title $title, array $ids ) {
$typeName = self::getCanonicalTypeName( $typeName );
@@ -91,12 +91,12 @@ class RevisionDeleter {
* Checks for a change in the bitfield for a certain option and updates the
* provided array accordingly.
*
- * @param string $desc description to add to the array if the option was
+ * @param string $desc Description to add to the array if the option was
* enabled / disabled.
- * @param $field Integer: the bitmask describing the single option.
- * @param $diff Integer: the xor of the old and new bitfields.
- * @param $new Integer: the new bitfield
- * @param array $arr the array to update.
+ * @param int $field The bitmask describing the single option.
+ * @param int $diff The xor of the old and new bitfields.
+ * @param int $new The new bitfield
+ * @param array $arr The array to update.
*/
protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
if ( $diff & $field ) {
@@ -117,8 +117,8 @@ class RevisionDeleter {
* You can turn the keys in $arr[0] and $arr[1] into message keys by
* appending -hid and and -unhid to the keys respectively.
*
- * @param $n Integer: the new bitfield.
- * @param $o Integer: the old bitfield.
+ * @param int $n The new bitfield.
+ * @param int $o The old bitfield.
* @return array An array as described above.
* @since 1.19 public
*/
@@ -189,7 +189,7 @@ class RevisionDeleter {
* Suggest a target for the revision deletion
* @since 1.22
* @param string $typeName
- * @param Title|null $title User-supplied target
+ * @param Title|null $target User-supplied target
* @param array $ids
* @return Title|null
*/
@@ -201,14 +201,13 @@ class RevisionDeleter {
return call_user_func( array( self::$allowedTypes[$typeName], 'suggestTarget' ), $target, $ids );
}
-
/**
* Checks if a revision still exists in the revision table.
* If it doesn't, returns the corresponding ar_timestamp field
* so that this key can be used instead.
*
- * @param $title Title
- * @param $revid
+ * @param Title $title
+ * @param int $revid
* @return bool|mixed
*/
public static function checkRevisionExistence( $title, $revid ) {
@@ -231,8 +230,8 @@ class RevisionDeleter {
/**
* Put together a rev_deleted bitfield
* @since 1.22
- * @param array $bitPars extractBitParams() params
- * @param int $oldfield current bitfield
+ * @param array $bitPars ExtractBitParams() params
+ * @param int $oldfield Current bitfield
* @return array
*/
public static function extractBitfield( $bitPars, $oldfield ) {
diff --git a/includes/search/SearchDatabase.php b/includes/search/SearchDatabase.php
new file mode 100644
index 00000000..82d09073
--- /dev/null
+++ b/includes/search/SearchDatabase.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Database search engine
+ *
+ * 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 Search
+ */
+
+/**
+ * Base search engine base class for database-backed searches
+ * @ingroup Search
+ * @since 1.23
+ */
+class SearchDatabase extends SearchEngine {
+ /**
+ * @var DatabaseBase Slave database for reading from for results
+ */
+ protected $db;
+
+ /**
+ * Constructor
+ * @param DatabaseBase $db The database to search from
+ */
+ public function __construct( DatabaseBase $db = null ) {
+ if ( $db ) {
+ $this->db = $db;
+ } else {
+ $this->db = wfGetDB( DB_SLAVE );
+ }
+ }
+
+ /**
+ * Return a 'cleaned up' search string
+ *
+ * @param string $text
+ * @return string
+ */
+ protected function filter( $text ) {
+ $lc = $this->legalSearchChars();
+ return trim( preg_replace( "/[^{$lc}]/", " ", $text ) );
+ }
+}
diff --git a/includes/search/SearchEngine.php b/includes/search/SearchEngine.php
index 71c05d8b..0eb87e4a 100644
--- a/includes/search/SearchEngine.php
+++ b/includes/search/SearchEngine.php
@@ -30,35 +30,33 @@
* @ingroup Search
*/
class SearchEngine {
- var $limit = 10;
- var $offset = 0;
- var $prefix = '';
- var $searchTerms = array();
- var $namespaces = array( NS_MAIN );
- var $showRedirects = false;
-
- /// Feature values
- protected $features = array();
+ /** @var string */
+ public $prefix = '';
- /**
- * @var DatabaseBase
- */
- protected $db;
+ /** @var int[] */
+ public $namespaces = array( NS_MAIN );
- function __construct( $db = null ) {
- if ( $db ) {
- $this->db = $db;
- } else {
- $this->db = wfGetDB( DB_SLAVE );
- }
- }
+ /** @var int */
+ protected $limit = 10;
+
+ /** @var int */
+ protected $offset = 0;
+
+ /** @var array|string */
+ protected $searchTerms = array();
+
+ /** @var bool */
+ protected $showSuggestion = true;
+
+ /** @var array Feature values */
+ protected $features = array();
/**
* Perform a full text search query and return a result set.
* If title searches are not supported or disabled, return null.
* STUB
*
- * @param string $term raw search term
+ * @param string $term Raw search term
* @return SearchResultSet|Status|null
*/
function searchText( $term ) {
@@ -70,7 +68,7 @@ class SearchEngine {
* If title searches are not supported or disabled, return null.
* STUB
*
- * @param string $term raw search term
+ * @param string $term Raw search term
* @return SearchResultSet|null
*/
function searchTitle( $term ) {
@@ -78,23 +76,12 @@ class SearchEngine {
}
/**
- * If this search backend can list/unlist redirects
- * @deprecated since 1.18 Call supports( 'list-redirects' );
- * @return bool
- */
- function acceptListRedirects() {
- wfDeprecated( __METHOD__, '1.18' );
- return $this->supports( 'list-redirects' );
- }
-
- /**
* @since 1.18
- * @param $feature String
- * @return Boolean
+ * @param string $feature
+ * @return bool
*/
public function supports( $feature ) {
switch ( $feature ) {
- case 'list-redirects':
case 'search-update':
return true;
case 'title-suffix-filter':
@@ -106,8 +93,8 @@ class SearchEngine {
/**
* Way to pass custom data for engines
* @since 1.18
- * @param $feature String
- * @param $data Mixed
+ * @param string $feature
+ * @param mixed $data
* @return bool
*/
public function setFeatureData( $feature, $data ) {
@@ -130,8 +117,11 @@ class SearchEngine {
}
/**
- * Transform search term in cases when parts of the query came as different GET params (when supported)
- * e.g. for prefix queries: search=test&prefix=Main_Page/Archive -> test prefix:Main Page/Archive
+ * Transform search term in cases when parts of the query came as different
+ * GET params (when supported), e.g. for prefix queries:
+ * search=test&prefix=Main_Page/Archive -> test prefix:Main Page/Archive
+ * @param string $term
+ * @return string
*/
function transformSearchTerm( $term ) {
return $term;
@@ -141,7 +131,7 @@ class SearchEngine {
* If an exact title match can be found, or a very slightly close match,
* return the title. If no match, returns NULL.
*
- * @param $searchterm String
+ * @param string $searchterm
* @return Title
*/
public static function getNearMatch( $searchterm ) {
@@ -155,7 +145,7 @@ class SearchEngine {
* Do a near match (see SearchEngine::getNearMatch) and wrap it into a
* SearchResultSet.
*
- * @param $searchterm string
+ * @param string $searchterm
* @return SearchResultSet
*/
public static function getNearMatchResultSet( $searchterm ) {
@@ -164,6 +154,7 @@ class SearchEngine {
/**
* Really find the title match.
+ * @param string $searchterm
* @return null|Title
*/
private static function getNearMatchInternal( $searchterm ) {
@@ -172,7 +163,10 @@ class SearchEngine {
$allSearchTerms = array( $searchterm );
if ( $wgContLang->hasVariants() ) {
- $allSearchTerms = array_merge( $allSearchTerms, $wgContLang->autoConvertToAllVariants( $searchterm ) );
+ $allSearchTerms = array_merge(
+ $allSearchTerms,
+ $wgContLang->autoConvertToAllVariants( $searchterm )
+ );
}
$titleResult = null;
@@ -286,8 +280,8 @@ class SearchEngine {
* Set the maximum number of results to return
* and how many to skip before returning the first.
*
- * @param $limit Integer
- * @param $offset Integer
+ * @param int $limit
+ * @param int $offset
*/
function setLimitOffset( $limit, $offset = 0 ) {
$this->limit = intval( $limit );
@@ -298,17 +292,28 @@ class SearchEngine {
* Set which namespaces the search should include.
* Give an array of namespace index numbers.
*
- * @param $namespaces Array
+ * @param array $namespaces
*/
function setNamespaces( $namespaces ) {
$this->namespaces = $namespaces;
}
/**
+ * Set whether the searcher should try to build a suggestion. Note: some searchers
+ * don't support building a suggestion in the first place and others don't respect
+ * this flag.
+ *
+ * @param bool $showSuggestion Should the searcher try to build suggestions
+ */
+ function setShowSuggestion( $showSuggestion ) {
+ $this->showSuggestion = $showSuggestion;
+ }
+
+ /**
* Parse some common prefixes: all (search everything)
* or namespace names
*
- * @param $query String
+ * @param string $query
* @return string
*/
function replacePrefixes( $query ) {
@@ -316,7 +321,6 @@ class SearchEngine {
$parsed = $query;
if ( strpos( $query, ':' ) === false ) { // nothing to do
- wfRunHooks( 'SearchEngineReplacePrefixesComplete', array( $this, $query, &$parsed ) );
return $parsed;
}
@@ -325,7 +329,7 @@ class SearchEngine {
$this->namespaces = null;
$parsed = substr( $query, strlen( $allkeyword ) );
} elseif ( strpos( $query, ':' ) !== false ) {
- $prefix = substr( $query, 0, strpos( $query, ':' ) );
+ $prefix = str_replace( ' ', '_', substr( $query, 0, strpos( $query, ':' ) ) );
$index = $wgContLang->getNsIndex( $prefix );
if ( $index !== false ) {
$this->namespaces = array( $index );
@@ -336,14 +340,12 @@ class SearchEngine {
$parsed = $query; // prefix was the whole query
}
- wfRunHooks( 'SearchEngineReplacePrefixesComplete', array( $this, $query, &$parsed ) );
-
return $parsed;
}
/**
* Make a list of searchable namespaces and their canonical names.
- * @return Array
+ * @return array
*/
public static function searchableNamespaces() {
global $wgContLang;
@@ -362,24 +364,12 @@ class SearchEngine {
* Extract default namespaces to search from the given user's
* settings, returning a list of index numbers.
*
- * @param $user User
- * @return Array
+ * @param user $user
+ * @return array
*/
public static function userNamespaces( $user ) {
- global $wgSearchEverythingOnlyLoggedIn;
-
- $searchableNamespaces = SearchEngine::searchableNamespaces();
-
- // get search everything preference, that can be set to be read for logged-in users
- // it overrides other options
- if ( !$wgSearchEverythingOnlyLoggedIn || $user->isLoggedIn() ) {
- if ( $user->getOption( 'searcheverything' ) ) {
- return array_keys( $searchableNamespaces );
- }
- }
-
$arr = array();
- foreach ( $searchableNamespaces as $ns => $name ) {
+ foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) {
if ( $user->getOption( 'searchNs' . $ns ) ) {
$arr[] = $ns;
}
@@ -391,7 +381,7 @@ class SearchEngine {
/**
* Find snippet highlight settings for all users
*
- * @return Array contextlines, contextchars
+ * @return array Contextlines, contextchars
*/
public static function userHighlightPrefs() {
$contextlines = 2; // Hardcode this. Old defaults sucked. :)
@@ -402,7 +392,7 @@ class SearchEngine {
/**
* An array of namespaces indexes to be searched by default
*
- * @return Array
+ * @return array
*/
public static function defaultNamespaces() {
global $wgNamespacesToBeSearchedDefault;
@@ -414,7 +404,7 @@ class SearchEngine {
* Get a list of namespace names useful for showing in tooltips
* and preferences
*
- * @param $namespaces Array
+ * @param array $namespaces
* @return array
*/
public static function namespacesAsText( $namespaces ) {
@@ -430,31 +420,10 @@ class SearchEngine {
}
/**
- * Return the help namespaces to be shown on Special:Search
- *
- * @return Array
- */
- public static function helpNamespaces() {
- global $wgNamespacesToBeSearchedHelp;
-
- return array_keys( $wgNamespacesToBeSearchedHelp, true );
- }
-
- /**
- * Return a 'cleaned up' search string
- *
- * @param $text String
- * @return String
- */
- function filter( $text ) {
- $lc = $this->legalSearchChars();
- return trim( preg_replace( "/[^{$lc}]/", " ", $text ) );
- }
- /**
* Load up the appropriate search engine class for the currently
* active database backend, and return a configured instance.
*
- * @param String $type Type of search backend, if not the default
+ * @param string $type Type of search backend, if not the default
* @return SearchEngine
*/
public static function create( $type = null ) {
@@ -473,7 +442,6 @@ class SearchEngine {
}
$search = new $class( $dbr );
- $search->setLimitOffset( 0, 0 );
return $search;
}
@@ -485,11 +453,10 @@ class SearchEngine {
*/
public static function getSearchTypes() {
global $wgSearchType, $wgSearchTypeAlternatives;
- static $alternatives = null;
- if ( $alternatives === null ) {
- $alternatives = $wgSearchTypeAlternatives ?: array();
- array_unshift( $alternatives, $wgSearchType );
- }
+
+ $alternatives = $wgSearchTypeAlternatives ?: array();
+ array_unshift( $alternatives, $wgSearchType );
+
return $alternatives;
}
@@ -498,9 +465,9 @@ class SearchEngine {
* Title and text should be pre-processed.
* STUB
*
- * @param $id Integer
- * @param $title String
- * @param $text String
+ * @param int $id
+ * @param string $title
+ * @param string $text
*/
function update( $id, $title, $text ) {
// no-op
@@ -511,8 +478,8 @@ class SearchEngine {
* Title should be pre-processed.
* STUB
*
- * @param $id Integer
- * @param $title String
+ * @param int $id
+ * @param string $title
*/
function updateTitle( $id, $title ) {
// no-op
@@ -523,8 +490,8 @@ class SearchEngine {
* Title should be pre-processed.
* STUB
*
- * @param Integer $id Page id that was deleted
- * @param String $title Title of page that was deleted
+ * @param int $id Page id that was deleted
+ * @param string $title Title of page that was deleted
*/
function delete( $id, $title ) {
// no-op
@@ -533,10 +500,11 @@ class SearchEngine {
/**
* Get OpenSearch suggestion template
*
- * @return String
+ * @return string
*/
public static function getOpenSearchTemplate() {
global $wgOpenSearchTemplate, $wgCanonicalServer;
+
if ( $wgOpenSearchTemplate ) {
return $wgOpenSearchTemplate;
} else {
@@ -544,7 +512,9 @@ class SearchEngine {
if ( !$ns ) {
$ns = "0";
}
- return $wgCanonicalServer . wfScript( 'api' ) . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
+
+ return $wgCanonicalServer . wfScript( 'api' )
+ . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
}
}
@@ -575,940 +545,6 @@ class SearchEngine {
}
/**
- * @ingroup Search
- */
-class SearchResultSet {
- /**
- * Fetch an array of regular expression fragments for matching
- * the search terms as parsed by this engine in a text extract.
- * STUB
- *
- * @return Array
- */
- function termMatches() {
- return array();
- }
-
- function numRows() {
- return 0;
- }
-
- /**
- * Return true if results are included in this result set.
- * STUB
- *
- * @return Boolean
- */
- function hasResults() {
- return false;
- }
-
- /**
- * Some search modes return a total hit count for the query
- * in the entire article database. This may include pages
- * in namespaces that would not be matched on the given
- * settings.
- *
- * Return null if no total hits number is supported.
- *
- * @return Integer
- */
- function getTotalHits() {
- return null;
- }
-
- /**
- * Some search modes return a suggested alternate term if there are
- * no exact hits. Returns true if there is one on this set.
- *
- * @return Boolean
- */
- function hasSuggestion() {
- return false;
- }
-
- /**
- * @return String: suggested query, null if none
- */
- function getSuggestionQuery() {
- return null;
- }
-
- /**
- * @return String: HTML highlighted suggested query, '' if none
- */
- function getSuggestionSnippet() {
- return '';
- }
-
- /**
- * Return information about how and from where the results were fetched,
- * should be useful for diagnostics and debugging
- *
- * @return String
- */
- function getInfo() {
- return null;
- }
-
- /**
- * Return a result set of hits on other (multiple) wikis associated with this one
- *
- * @return SearchResultSet
- */
- function getInterwikiResults() {
- return null;
- }
-
- /**
- * Check if there are results on other wikis
- *
- * @return Boolean
- */
- function hasInterwikiResults() {
- return $this->getInterwikiResults() != null;
- }
-
- /**
- * Fetches next search result, or false.
- * STUB
- *
- * @return SearchResult
- */
- function next() {
- return false;
- }
-
- /**
- * Frees the result set, if applicable.
- */
- function free() {
- // ...
- }
-}
-
-/**
- * This class is used for different SQL-based search engines shipped with MediaWiki
- */
-class SqlSearchResultSet extends SearchResultSet {
-
- protected $mResultSet;
-
- function __construct( $resultSet, $terms ) {
- $this->mResultSet = $resultSet;
- $this->mTerms = $terms;
- }
-
- function termMatches() {
- return $this->mTerms;
- }
-
- function numRows() {
- if ( $this->mResultSet === false ) {
- return false;
- }
-
- return $this->mResultSet->numRows();
- }
-
- function next() {
- if ( $this->mResultSet === false ) {
- return false;
- }
-
- $row = $this->mResultSet->fetchObject();
- if ( $row === false ) {
- return false;
- }
-
- return SearchResult::newFromRow( $row );
- }
-
- function free() {
- if ( $this->mResultSet === false ) {
- return false;
- }
-
- $this->mResultSet->free();
- }
-}
-
-/**
- * @ingroup Search
- */
-class SearchResultTooMany {
- # # Some search engines may bail out if too many matches are found
-}
-
-/**
- * @todo FIXME: This class is horribly factored. It would probably be better to
- * have a useful base class to which you pass some standard information, then
- * let the fancy self-highlighters extend that.
- * @ingroup Search
- */
-class SearchResult {
-
- /**
- * @var Revision
- */
- var $mRevision = null;
- var $mImage = null;
-
- /**
- * @var Title
- */
- var $mTitle;
-
- /**
- * @var String
- */
- var $mText;
-
- /**
- * Return a new SearchResult and initializes it with a title.
- *
- * @param $title Title
- * @return SearchResult
- */
- public static function newFromTitle( $title ) {
- $result = new self();
- $result->initFromTitle( $title );
- return $result;
- }
- /**
- * Return a new SearchResult and initializes it with a row.
- *
- * @param $row object
- * @return SearchResult
- */
- public static function newFromRow( $row ) {
- $result = new self();
- $result->initFromRow( $row );
- return $result;
- }
-
- public function __construct( $row = null ) {
- if ( !is_null( $row ) ) {
- // Backwards compatibility with pre-1.17 callers
- $this->initFromRow( $row );
- }
- }
-
- /**
- * Initialize from a database row. Makes a Title and passes that to
- * initFromTitle.
- *
- * @param $row object
- */
- protected function initFromRow( $row ) {
- $this->initFromTitle( Title::makeTitle( $row->page_namespace, $row->page_title ) );
- }
-
- /**
- * Initialize from a Title and if possible initializes a corresponding
- * Revision and File.
- *
- * @param $title Title
- */
- protected function initFromTitle( $title ) {
- $this->mTitle = $title;
- if ( !is_null( $this->mTitle ) ) {
- $id = false;
- wfRunHooks( 'SearchResultInitFromTitle', array( $title, &$id ) );
- $this->mRevision = Revision::newFromTitle(
- $this->mTitle, $id, Revision::READ_NORMAL );
- if ( $this->mTitle->getNamespace() === NS_FILE ) {
- $this->mImage = wfFindFile( $this->mTitle );
- }
- }
- }
-
- /**
- * Check if this is result points to an invalid title
- *
- * @return Boolean
- */
- function isBrokenTitle() {
- if ( is_null( $this->mTitle ) ) {
- return true;
- }
- return false;
- }
-
- /**
- * Check if target page is missing, happens when index is out of date
- *
- * @return Boolean
- */
- function isMissingRevision() {
- return !$this->mRevision && !$this->mImage;
- }
-
- /**
- * @return Title
- */
- function getTitle() {
- return $this->mTitle;
- }
-
- /**
- * @return float|null if not supported
- */
- function getScore() {
- return null;
- }
-
- /**
- * Lazy initialization of article text from DB
- */
- protected function initText() {
- if ( !isset( $this->mText ) ) {
- if ( $this->mRevision != null ) {
- $this->mText = SearchEngine::create()
- ->getTextFromContent( $this->mTitle, $this->mRevision->getContent() );
- } else { // TODO: can we fetch raw wikitext for commons images?
- $this->mText = '';
- }
- }
- }
-
- /**
- * @param array $terms terms to highlight
- * @return String: highlighted text snippet, null (and not '') if not supported
- */
- function getTextSnippet( $terms ) {
- global $wgAdvancedSearchHighlighting;
- $this->initText();
-
- // TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
- list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs();
- $h = new SearchHighlighter();
- if ( $wgAdvancedSearchHighlighting ) {
- return $h->highlightText( $this->mText, $terms, $contextlines, $contextchars );
- } else {
- return $h->highlightSimple( $this->mText, $terms, $contextlines, $contextchars );
- }
- }
-
- /**
- * @param array $terms terms to highlight
- * @return String: highlighted title, '' if not supported
- */
- function getTitleSnippet( $terms ) {
- return '';
- }
-
- /**
- * @param array $terms terms to highlight
- * @return String: highlighted redirect name (redirect to this page), '' if none or not supported
- */
- function getRedirectSnippet( $terms ) {
- return '';
- }
-
- /**
- * @return Title object for the redirect to this page, null if none or not supported
- */
- function getRedirectTitle() {
- return null;
- }
-
- /**
- * @return string highlighted relevant section name, null if none or not supported
- */
- function getSectionSnippet() {
- return '';
- }
-
- /**
- * @return Title object (pagename+fragment) for the section, null if none or not supported
- */
- function getSectionTitle() {
- return null;
- }
-
- /**
- * @return String: timestamp
- */
- function getTimestamp() {
- if ( $this->mRevision ) {
- return $this->mRevision->getTimestamp();
- } elseif ( $this->mImage ) {
- return $this->mImage->getTimestamp();
- }
- return '';
- }
-
- /**
- * @return Integer: number of words
- */
- function getWordCount() {
- $this->initText();
- return str_word_count( $this->mText );
- }
-
- /**
- * @return Integer: size in bytes
- */
- function getByteSize() {
- $this->initText();
- return strlen( $this->mText );
- }
-
- /**
- * @return Boolean if hit has related articles
- */
- function hasRelated() {
- return false;
- }
-
- /**
- * @return String: interwiki prefix of the title (return iw even if title is broken)
- */
- function getInterwikiPrefix() {
- return '';
- }
-}
-/**
- * A SearchResultSet wrapper for SearchEngine::getNearMatch
- */
-class SearchNearMatchResultSet extends SearchResultSet {
- private $fetched = false;
- /**
- * @param $match mixed Title if matched, else null
- */
- public function __construct( $match ) {
- $this->result = $match;
- }
- public function hasResult() {
- return (bool)$this->result;
- }
- public function numRows() {
- return $this->hasResults() ? 1 : 0;
- }
- public function next() {
- if ( $this->fetched || !$this->result ) {
- return false;
- }
- $this->fetched = true;
- return SearchResult::newFromTitle( $this->result );
- }
-}
-
-/**
- * Highlight bits of wikitext
- *
- * @ingroup Search
- */
-class SearchHighlighter {
- var $mCleanWikitext = true;
-
- function __construct( $cleanupWikitext = true ) {
- $this->mCleanWikitext = $cleanupWikitext;
- }
-
- /**
- * Default implementation of wikitext highlighting
- *
- * @param $text String
- * @param array $terms terms to highlight (unescaped)
- * @param $contextlines Integer
- * @param $contextchars Integer
- * @return String
- */
- public function highlightText( $text, $terms, $contextlines, $contextchars ) {
- global $wgContLang;
- global $wgSearchHighlightBoundaries;
- $fname = __METHOD__;
-
- if ( $text == '' ) {
- return '';
- }
-
- // spli text into text + templates/links/tables
- $spat = "/(\\{\\{)|(\\[\\[[^\\]:]+:)|(\n\\{\\|)";
- // first capture group is for detecting nested templates/links/tables/references
- $endPatterns = array(
- 1 => '/(\{\{)|(\}\})/', // template
- 2 => '/(\[\[)|(\]\])/', // image
- 3 => "/(\n\\{\\|)|(\n\\|\\})/" ); // table
-
- // @todo FIXME: This should prolly be a hook or something
- if ( function_exists( 'wfCite' ) ) {
- $spat .= '|(<ref>)'; // references via cite extension
- $endPatterns[4] = '/(<ref>)|(<\/ref>)/';
- }
- $spat .= '/';
- $textExt = array(); // text extracts
- $otherExt = array(); // other extracts
- wfProfileIn( "$fname-split" );
- $start = 0;
- $textLen = strlen( $text );
- $count = 0; // sequence number to maintain ordering
- while ( $start < $textLen ) {
- // find start of template/image/table
- if ( preg_match( $spat, $text, $matches, PREG_OFFSET_CAPTURE, $start ) ) {
- $epat = '';
- foreach ( $matches as $key => $val ) {
- if ( $key > 0 && $val[1] != - 1 ) {
- if ( $key == 2 ) {
- // see if this is an image link
- $ns = substr( $val[0], 2, - 1 );
- if ( $wgContLang->getNsIndex( $ns ) != NS_FILE ) {
- break;
- }
-
- }
- $epat = $endPatterns[$key];
- $this->splitAndAdd( $textExt, $count, substr( $text, $start, $val[1] - $start ) );
- $start = $val[1];
- break;
- }
- }
- if ( $epat ) {
- // find end (and detect any nested elements)
- $level = 0;
- $offset = $start + 1;
- $found = false;
- while ( preg_match( $epat, $text, $endMatches, PREG_OFFSET_CAPTURE, $offset ) ) {
- if ( array_key_exists( 2, $endMatches ) ) {
- // found end
- if ( $level == 0 ) {
- $len = strlen( $endMatches[2][0] );
- $off = $endMatches[2][1];
- $this->splitAndAdd( $otherExt, $count,
- substr( $text, $start, $off + $len - $start ) );
- $start = $off + $len;
- $found = true;
- break;
- } else {
- // end of nested element
- $level -= 1;
- }
- } else {
- // nested
- $level += 1;
- }
- $offset = $endMatches[0][1] + strlen( $endMatches[0][0] );
- }
- if ( ! $found ) {
- // couldn't find appropriate closing tag, skip
- $this->splitAndAdd( $textExt, $count, substr( $text, $start, strlen( $matches[0][0] ) ) );
- $start += strlen( $matches[0][0] );
- }
- continue;
- }
- }
- // else: add as text extract
- $this->splitAndAdd( $textExt, $count, substr( $text, $start ) );
- break;
- }
-
- $all = $textExt + $otherExt; // these have disjunct key sets
-
- wfProfileOut( "$fname-split" );
-
- // prepare regexps
- foreach ( $terms as $index => $term ) {
- // manually do upper/lowercase stuff for utf-8 since PHP won't do it
- if ( preg_match( '/[\x80-\xff]/', $term ) ) {
- $terms[$index] = preg_replace_callback( '/./us', array( $this, 'caseCallback' ), $terms[$index] );
- } else {
- $terms[$index] = $term;
- }
- }
- $anyterm = implode( '|', $terms );
- $phrase = implode( "$wgSearchHighlightBoundaries+", $terms );
-
- // @todo FIXME: A hack to scale contextchars, a correct solution
- // would be to have contextchars actually be char and not byte
- // length, and do proper utf-8 substrings and lengths everywhere,
- // but PHP is making that very hard and unclean to implement :(
- $scale = strlen( $anyterm ) / mb_strlen( $anyterm );
- $contextchars = intval( $contextchars * $scale );
-
- $patPre = "(^|$wgSearchHighlightBoundaries)";
- $patPost = "($wgSearchHighlightBoundaries|$)";
-
- $pat1 = "/(" . $phrase . ")/ui";
- $pat2 = "/$patPre(" . $anyterm . ")$patPost/ui";
-
- wfProfileIn( "$fname-extract" );
-
- $left = $contextlines;
-
- $snippets = array();
- $offsets = array();
-
- // show beginning only if it contains all words
- $first = 0;
- $firstText = '';
- foreach ( $textExt as $index => $line ) {
- if ( strlen( $line ) > 0 && $line[0] != ';' && $line[0] != ':' ) {
- $firstText = $this->extract( $line, 0, $contextchars * $contextlines );
- $first = $index;
- break;
- }
- }
- if ( $firstText ) {
- $succ = true;
- // check if first text contains all terms
- foreach ( $terms as $term ) {
- if ( ! preg_match( "/$patPre" . $term . "$patPost/ui", $firstText ) ) {
- $succ = false;
- break;
- }
- }
- if ( $succ ) {
- $snippets[$first] = $firstText;
- $offsets[$first] = 0;
- }
- }
- if ( ! $snippets ) {
- // match whole query on text
- $this->process( $pat1, $textExt, $left, $contextchars, $snippets, $offsets );
- // match whole query on templates/tables/images
- $this->process( $pat1, $otherExt, $left, $contextchars, $snippets, $offsets );
- // match any words on text
- $this->process( $pat2, $textExt, $left, $contextchars, $snippets, $offsets );
- // match any words on templates/tables/images
- $this->process( $pat2, $otherExt, $left, $contextchars, $snippets, $offsets );
-
- ksort( $snippets );
- }
-
- // add extra chars to each snippet to make snippets constant size
- $extended = array();
- if ( count( $snippets ) == 0 ) {
- // couldn't find the target words, just show beginning of article
- if ( array_key_exists( $first, $all ) ) {
- $targetchars = $contextchars * $contextlines;
- $snippets[$first] = '';
- $offsets[$first] = 0;
- }
- } else {
- // if begin of the article contains the whole phrase, show only that !!
- if ( array_key_exists( $first, $snippets ) && preg_match( $pat1, $snippets[$first] )
- && $offsets[$first] < $contextchars * 2 ) {
- $snippets = array( $first => $snippets[$first] );
- }
-
- // calc by how much to extend existing snippets
- $targetchars = intval( ( $contextchars * $contextlines ) / count ( $snippets ) );
- }
-
- foreach ( $snippets as $index => $line ) {
- $extended[$index] = $line;
- $len = strlen( $line );
- if ( $len < $targetchars - 20 ) {
- // complete this line
- if ( $len < strlen( $all[$index] ) ) {
- $extended[$index] = $this->extract( $all[$index], $offsets[$index], $offsets[$index] + $targetchars, $offsets[$index] );
- $len = strlen( $extended[$index] );
- }
-
- // add more lines
- $add = $index + 1;
- while ( $len < $targetchars - 20
- && array_key_exists( $add, $all )
- && !array_key_exists( $add, $snippets ) ) {
- $offsets[$add] = 0;
- $tt = "\n" . $this->extract( $all[$add], 0, $targetchars - $len, $offsets[$add] );
- $extended[$add] = $tt;
- $len += strlen( $tt );
- $add++;
- }
- }
- }
-
- // $snippets = array_map( 'htmlspecialchars', $extended );
- $snippets = $extended;
- $last = - 1;
- $extract = '';
- foreach ( $snippets as $index => $line ) {
- if ( $last == - 1 ) {
- $extract .= $line; // first line
- } elseif ( $last + 1 == $index && $offsets[$last] + strlen( $snippets[$last] ) >= strlen( $all[$last] ) ) {
- $extract .= " " . $line; // continous lines
- } else {
- $extract .= '<b> ... </b>' . $line;
- }
-
- $last = $index;
- }
- if ( $extract ) {
- $extract .= '<b> ... </b>';
- }
-
- $processed = array();
- foreach ( $terms as $term ) {
- if ( ! isset( $processed[$term] ) ) {
- $pat3 = "/$patPre(" . $term . ")$patPost/ui"; // highlight word
- $extract = preg_replace( $pat3,
- "\\1<span class='searchmatch'>\\2</span>\\3", $extract );
- $processed[$term] = true;
- }
- }
-
- wfProfileOut( "$fname-extract" );
-
- return $extract;
- }
-
- /**
- * Split text into lines and add it to extracts array
- *
- * @param array $extracts index -> $line
- * @param $count Integer
- * @param $text String
- */
- function splitAndAdd( &$extracts, &$count, $text ) {
- $split = explode( "\n", $this->mCleanWikitext ? $this->removeWiki( $text ) : $text );
- foreach ( $split as $line ) {
- $tt = trim( $line );
- if ( $tt ) {
- $extracts[$count++] = $tt;
- }
- }
- }
-
- /**
- * Do manual case conversion for non-ascii chars
- *
- * @param $matches Array
- * @return string
- */
- function caseCallback( $matches ) {
- global $wgContLang;
- if ( strlen( $matches[0] ) > 1 ) {
- return '[' . $wgContLang->lc( $matches[0] ) . $wgContLang->uc( $matches[0] ) . ']';
- } else {
- return $matches[0];
- }
- }
-
- /**
- * Extract part of the text from start to end, but by
- * not chopping up words
- * @param $text String
- * @param $start Integer
- * @param $end Integer
- * @param $posStart Integer: (out) actual start position
- * @param $posEnd Integer: (out) actual end position
- * @return String
- */
- function extract( $text, $start, $end, &$posStart = null, &$posEnd = null ) {
- if ( $start != 0 ) {
- $start = $this->position( $text, $start, 1 );
- }
- if ( $end >= strlen( $text ) ) {
- $end = strlen( $text );
- } else {
- $end = $this->position( $text, $end );
- }
-
- if ( !is_null( $posStart ) ) {
- $posStart = $start;
- }
- if ( !is_null( $posEnd ) ) {
- $posEnd = $end;
- }
-
- if ( $end > $start ) {
- return substr( $text, $start, $end - $start );
- } else {
- return '';
- }
- }
-
- /**
- * Find a nonletter near a point (index) in the text
- *
- * @param $text String
- * @param $point Integer
- * @param $offset Integer: offset to found index
- * @return Integer: nearest nonletter index, or beginning of utf8 char if none
- */
- function position( $text, $point, $offset = 0 ) {
- $tolerance = 10;
- $s = max( 0, $point - $tolerance );
- $l = min( strlen( $text ), $point + $tolerance ) - $s;
- $m = array();
- if ( preg_match( '/[ ,.!?~!@#$%^&*\(\)+=\-\\\|\[\]"\'<>]/', substr( $text, $s, $l ), $m, PREG_OFFSET_CAPTURE ) ) {
- return $m[0][1] + $s + $offset;
- } else {
- // check if point is on a valid first UTF8 char
- $char = ord( $text[$point] );
- while ( $char >= 0x80 && $char < 0xc0 ) {
- // skip trailing bytes
- $point++;
- if ( $point >= strlen( $text ) ) {
- return strlen( $text );
- }
- $char = ord( $text[$point] );
- }
- return $point;
-
- }
- }
-
- /**
- * Search extracts for a pattern, and return snippets
- *
- * @param string $pattern regexp for matching lines
- * @param array $extracts extracts to search
- * @param $linesleft Integer: number of extracts to make
- * @param $contextchars Integer: length of snippet
- * @param array $out map for highlighted snippets
- * @param array $offsets map of starting points of snippets
- * @protected
- */
- function process( $pattern, $extracts, &$linesleft, &$contextchars, &$out, &$offsets ) {
- if ( $linesleft == 0 ) {
- return; // nothing to do
- }
- foreach ( $extracts as $index => $line ) {
- if ( array_key_exists( $index, $out ) ) {
- continue; // this line already highlighted
- }
-
- $m = array();
- if ( !preg_match( $pattern, $line, $m, PREG_OFFSET_CAPTURE ) ) {
- continue;
- }
-
- $offset = $m[0][1];
- $len = strlen( $m[0][0] );
- if ( $offset + $len < $contextchars ) {
- $begin = 0;
- } elseif ( $len > $contextchars ) {
- $begin = $offset;
- } else {
- $begin = $offset + intval( ( $len - $contextchars ) / 2 );
- }
-
- $end = $begin + $contextchars;
-
- $posBegin = $begin;
- // basic snippet from this line
- $out[$index] = $this->extract( $line, $begin, $end, $posBegin );
- $offsets[$index] = $posBegin;
- $linesleft--;
- if ( $linesleft == 0 ) {
- return;
- }
- }
- }
-
- /**
- * Basic wikitext removal
- * @protected
- * @return mixed
- */
- function removeWiki( $text ) {
- $fname = __METHOD__;
- wfProfileIn( $fname );
-
- // $text = preg_replace( "/'{2,5}/", "", $text );
- // $text = preg_replace( "/\[[a-z]+:\/\/[^ ]+ ([^]]+)\]/", "\\2", $text );
- // $text = preg_replace( "/\[\[([^]|]+)\]\]/", "\\1", $text );
- // $text = preg_replace( "/\[\[([^]]+\|)?([^|]]+)\]\]/", "\\2", $text );
- // $text = preg_replace( "/\\{\\|(.*?)\\|\\}/", "", $text );
- // $text = preg_replace( "/\\[\\[[A-Za-z_-]+:([^|]+?)\\]\\]/", "", $text );
- $text = preg_replace( "/\\{\\{([^|]+?)\\}\\}/", "", $text );
- $text = preg_replace( "/\\{\\{([^|]+\\|)(.*?)\\}\\}/", "\\2", $text );
- $text = preg_replace( "/\\[\\[([^|]+?)\\]\\]/", "\\1", $text );
- $text = preg_replace_callback( "/\\[\\[([^|]+\\|)(.*?)\\]\\]/", array( $this, 'linkReplace' ), $text );
- // $text = preg_replace("/\\[\\[([^|]+\\|)(.*?)\\]\\]/", "\\2", $text);
- $text = preg_replace( "/<\/?[^>]+>/", "", $text );
- $text = preg_replace( "/'''''/", "", $text );
- $text = preg_replace( "/('''|<\/?[iIuUbB]>)/", "", $text );
- $text = preg_replace( "/''/", "", $text );
-
- wfProfileOut( $fname );
- return $text;
- }
-
- /**
- * callback to replace [[target|caption]] kind of links, if
- * the target is category or image, leave it
- *
- * @param $matches Array
- */
- function linkReplace( $matches ) {
- $colon = strpos( $matches[1], ':' );
- if ( $colon === false ) {
- return $matches[2]; // replace with caption
- }
- global $wgContLang;
- $ns = substr( $matches[1], 0, $colon );
- $index = $wgContLang->getNsIndex( $ns );
- if ( $index !== false && ( $index == NS_FILE || $index == NS_CATEGORY ) ) {
- return $matches[0]; // return the whole thing
- } else {
- return $matches[2];
- }
- }
-
- /**
- * Simple & fast snippet extraction, but gives completely unrelevant
- * snippets
- *
- * @param $text String
- * @param $terms Array
- * @param $contextlines Integer
- * @param $contextchars Integer
- * @return String
- */
- public function highlightSimple( $text, $terms, $contextlines, $contextchars ) {
- global $wgContLang;
- $fname = __METHOD__;
-
- $lines = explode( "\n", $text );
-
- $terms = implode( '|', $terms );
- $max = intval( $contextchars ) + 1;
- $pat1 = "/(.*)($terms)(.{0,$max})/i";
-
- $lineno = 0;
-
- $extract = "";
- wfProfileIn( "$fname-extract" );
- foreach ( $lines as $line ) {
- if ( 0 == $contextlines ) {
- break;
- }
- ++$lineno;
- $m = array();
- if ( ! preg_match( $pat1, $line, $m ) ) {
- continue;
- }
- --$contextlines;
- // truncate function changes ... to relevant i18n message.
- $pre = $wgContLang->truncate( $m[1], - $contextchars, '...', false );
-
- if ( count( $m ) < 3 ) {
- $post = '';
- } else {
- $post = $wgContLang->truncate( $m[3], $contextchars, '...', false );
- }
-
- $found = $m[2];
-
- $line = htmlspecialchars( $pre . $found . $post );
- $pat2 = '/(' . $terms . ")/i";
- $line = preg_replace( $pat2, "<span class='searchmatch'>\\1</span>", $line );
-
- $extract .= "${line}\n";
- }
- wfProfileOut( "$fname-extract" );
-
- return $extract;
- }
-
-}
-
-/**
* Dummy class to be used when non-supported Database engine is present.
* @todo FIXME: Dummy class should probably try something at least mildly useful,
* such as a LIKE search through titles.
diff --git a/includes/search/SearchHighlighter.php b/includes/search/SearchHighlighter.php
new file mode 100644
index 00000000..c3c3a8f8
--- /dev/null
+++ b/includes/search/SearchHighlighter.php
@@ -0,0 +1,575 @@
+<?php
+/**
+ * Basic search engine highlighting
+ *
+ * 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 Search
+ */
+
+/**
+ * Highlight bits of wikitext
+ *
+ * @ingroup Search
+ */
+class SearchHighlighter {
+ protected $mCleanWikitext = true;
+
+ function __construct( $cleanupWikitext = true ) {
+ $this->mCleanWikitext = $cleanupWikitext;
+ }
+
+ /**
+ * Default implementation of wikitext highlighting
+ *
+ * @param string $text
+ * @param array $terms Terms to highlight (unescaped)
+ * @param int $contextlines
+ * @param int $contextchars
+ * @return string
+ */
+ public function highlightText( $text, $terms, $contextlines, $contextchars ) {
+ global $wgContLang, $wgSearchHighlightBoundaries;
+
+ $fname = __METHOD__;
+
+ if ( $text == '' ) {
+ return '';
+ }
+
+ // spli text into text + templates/links/tables
+ $spat = "/(\\{\\{)|(\\[\\[[^\\]:]+:)|(\n\\{\\|)";
+ // first capture group is for detecting nested templates/links/tables/references
+ $endPatterns = array(
+ 1 => '/(\{\{)|(\}\})/', // template
+ 2 => '/(\[\[)|(\]\])/', // image
+ 3 => "/(\n\\{\\|)|(\n\\|\\})/" ); // table
+
+ // @todo FIXME: This should prolly be a hook or something
+ if ( function_exists( 'wfCite' ) ) {
+ $spat .= '|(<ref>)'; // references via cite extension
+ $endPatterns[4] = '/(<ref>)|(<\/ref>)/';
+ }
+ $spat .= '/';
+ $textExt = array(); // text extracts
+ $otherExt = array(); // other extracts
+ wfProfileIn( "$fname-split" );
+ $start = 0;
+ $textLen = strlen( $text );
+ $count = 0; // sequence number to maintain ordering
+ while ( $start < $textLen ) {
+ // find start of template/image/table
+ if ( preg_match( $spat, $text, $matches, PREG_OFFSET_CAPTURE, $start ) ) {
+ $epat = '';
+ foreach ( $matches as $key => $val ) {
+ if ( $key > 0 && $val[1] != - 1 ) {
+ if ( $key == 2 ) {
+ // see if this is an image link
+ $ns = substr( $val[0], 2, - 1 );
+ if ( $wgContLang->getNsIndex( $ns ) != NS_FILE ) {
+ break;
+ }
+
+ }
+ $epat = $endPatterns[$key];
+ $this->splitAndAdd( $textExt, $count, substr( $text, $start, $val[1] - $start ) );
+ $start = $val[1];
+ break;
+ }
+ }
+ if ( $epat ) {
+ // find end (and detect any nested elements)
+ $level = 0;
+ $offset = $start + 1;
+ $found = false;
+ while ( preg_match( $epat, $text, $endMatches, PREG_OFFSET_CAPTURE, $offset ) ) {
+ if ( array_key_exists( 2, $endMatches ) ) {
+ // found end
+ if ( $level == 0 ) {
+ $len = strlen( $endMatches[2][0] );
+ $off = $endMatches[2][1];
+ $this->splitAndAdd( $otherExt, $count,
+ substr( $text, $start, $off + $len - $start ) );
+ $start = $off + $len;
+ $found = true;
+ break;
+ } else {
+ // end of nested element
+ $level -= 1;
+ }
+ } else {
+ // nested
+ $level += 1;
+ }
+ $offset = $endMatches[0][1] + strlen( $endMatches[0][0] );
+ }
+ if ( !$found ) {
+ // couldn't find appropriate closing tag, skip
+ $this->splitAndAdd( $textExt, $count, substr( $text, $start, strlen( $matches[0][0] ) ) );
+ $start += strlen( $matches[0][0] );
+ }
+ continue;
+ }
+ }
+ // else: add as text extract
+ $this->splitAndAdd( $textExt, $count, substr( $text, $start ) );
+ break;
+ }
+
+ $all = $textExt + $otherExt; // these have disjunct key sets
+
+ wfProfileOut( "$fname-split" );
+
+ // prepare regexps
+ foreach ( $terms as $index => $term ) {
+ // manually do upper/lowercase stuff for utf-8 since PHP won't do it
+ if ( preg_match( '/[\x80-\xff]/', $term ) ) {
+ $terms[$index] = preg_replace_callback(
+ '/./us',
+ array( $this, 'caseCallback' ),
+ $terms[$index]
+ );
+ } else {
+ $terms[$index] = $term;
+ }
+ }
+ $anyterm = implode( '|', $terms );
+ $phrase = implode( "$wgSearchHighlightBoundaries+", $terms );
+
+ // @todo FIXME: A hack to scale contextchars, a correct solution
+ // would be to have contextchars actually be char and not byte
+ // length, and do proper utf-8 substrings and lengths everywhere,
+ // but PHP is making that very hard and unclean to implement :(
+ $scale = strlen( $anyterm ) / mb_strlen( $anyterm );
+ $contextchars = intval( $contextchars * $scale );
+
+ $patPre = "(^|$wgSearchHighlightBoundaries)";
+ $patPost = "($wgSearchHighlightBoundaries|$)";
+
+ $pat1 = "/(" . $phrase . ")/ui";
+ $pat2 = "/$patPre(" . $anyterm . ")$patPost/ui";
+
+ wfProfileIn( "$fname-extract" );
+
+ $left = $contextlines;
+
+ $snippets = array();
+ $offsets = array();
+
+ // show beginning only if it contains all words
+ $first = 0;
+ $firstText = '';
+ foreach ( $textExt as $index => $line ) {
+ if ( strlen( $line ) > 0 && $line[0] != ';' && $line[0] != ':' ) {
+ $firstText = $this->extract( $line, 0, $contextchars * $contextlines );
+ $first = $index;
+ break;
+ }
+ }
+ if ( $firstText ) {
+ $succ = true;
+ // check if first text contains all terms
+ foreach ( $terms as $term ) {
+ if ( !preg_match( "/$patPre" . $term . "$patPost/ui", $firstText ) ) {
+ $succ = false;
+ break;
+ }
+ }
+ if ( $succ ) {
+ $snippets[$first] = $firstText;
+ $offsets[$first] = 0;
+ }
+ }
+ if ( !$snippets ) {
+ // match whole query on text
+ $this->process( $pat1, $textExt, $left, $contextchars, $snippets, $offsets );
+ // match whole query on templates/tables/images
+ $this->process( $pat1, $otherExt, $left, $contextchars, $snippets, $offsets );
+ // match any words on text
+ $this->process( $pat2, $textExt, $left, $contextchars, $snippets, $offsets );
+ // match any words on templates/tables/images
+ $this->process( $pat2, $otherExt, $left, $contextchars, $snippets, $offsets );
+
+ ksort( $snippets );
+ }
+
+ // add extra chars to each snippet to make snippets constant size
+ $extended = array();
+ if ( count( $snippets ) == 0 ) {
+ // couldn't find the target words, just show beginning of article
+ if ( array_key_exists( $first, $all ) ) {
+ $targetchars = $contextchars * $contextlines;
+ $snippets[$first] = '';
+ $offsets[$first] = 0;
+ }
+ } else {
+ // if begin of the article contains the whole phrase, show only that !!
+ if ( array_key_exists( $first, $snippets ) && preg_match( $pat1, $snippets[$first] )
+ && $offsets[$first] < $contextchars * 2 ) {
+ $snippets = array( $first => $snippets[$first] );
+ }
+
+ // calc by how much to extend existing snippets
+ $targetchars = intval( ( $contextchars * $contextlines ) / count ( $snippets ) );
+ }
+
+ foreach ( $snippets as $index => $line ) {
+ $extended[$index] = $line;
+ $len = strlen( $line );
+ if ( $len < $targetchars - 20 ) {
+ // complete this line
+ if ( $len < strlen( $all[$index] ) ) {
+ $extended[$index] = $this->extract(
+ $all[$index],
+ $offsets[$index],
+ $offsets[$index] + $targetchars,
+ $offsets[$index]
+ );
+ $len = strlen( $extended[$index] );
+ }
+
+ // add more lines
+ $add = $index + 1;
+ while ( $len < $targetchars - 20
+ && array_key_exists( $add, $all )
+ && !array_key_exists( $add, $snippets ) ) {
+ $offsets[$add] = 0;
+ $tt = "\n" . $this->extract( $all[$add], 0, $targetchars - $len, $offsets[$add] );
+ $extended[$add] = $tt;
+ $len += strlen( $tt );
+ $add++;
+ }
+ }
+ }
+
+ // $snippets = array_map( 'htmlspecialchars', $extended );
+ $snippets = $extended;
+ $last = - 1;
+ $extract = '';
+ foreach ( $snippets as $index => $line ) {
+ if ( $last == - 1 ) {
+ $extract .= $line; // first line
+ } elseif ( $last + 1 == $index
+ && $offsets[$last] + strlen( $snippets[$last] ) >= strlen( $all[$last] )
+ ) {
+ $extract .= " " . $line; // continous lines
+ } else {
+ $extract .= '<b> ... </b>' . $line;
+ }
+
+ $last = $index;
+ }
+ if ( $extract ) {
+ $extract .= '<b> ... </b>';
+ }
+
+ $processed = array();
+ foreach ( $terms as $term ) {
+ if ( !isset( $processed[$term] ) ) {
+ $pat3 = "/$patPre(" . $term . ")$patPost/ui"; // highlight word
+ $extract = preg_replace( $pat3,
+ "\\1<span class='searchmatch'>\\2</span>\\3", $extract );
+ $processed[$term] = true;
+ }
+ }
+
+ wfProfileOut( "$fname-extract" );
+
+ return $extract;
+ }
+
+ /**
+ * Split text into lines and add it to extracts array
+ *
+ * @param array $extracts Index -> $line
+ * @param int $count
+ * @param string $text
+ */
+ function splitAndAdd( &$extracts, &$count, $text ) {
+ $split = explode( "\n", $this->mCleanWikitext ? $this->removeWiki( $text ) : $text );
+ foreach ( $split as $line ) {
+ $tt = trim( $line );
+ if ( $tt ) {
+ $extracts[$count++] = $tt;
+ }
+ }
+ }
+
+ /**
+ * Do manual case conversion for non-ascii chars
+ *
+ * @param array $matches
+ * @return string
+ */
+ function caseCallback( $matches ) {
+ global $wgContLang;
+ if ( strlen( $matches[0] ) > 1 ) {
+ return '[' . $wgContLang->lc( $matches[0] ) . $wgContLang->uc( $matches[0] ) . ']';
+ } else {
+ return $matches[0];
+ }
+ }
+
+ /**
+ * Extract part of the text from start to end, but by
+ * not chopping up words
+ * @param string $text
+ * @param int $start
+ * @param int $end
+ * @param int $posStart (out) actual start position
+ * @param int $posEnd (out) actual end position
+ * @return string
+ */
+ function extract( $text, $start, $end, &$posStart = null, &$posEnd = null ) {
+ if ( $start != 0 ) {
+ $start = $this->position( $text, $start, 1 );
+ }
+ if ( $end >= strlen( $text ) ) {
+ $end = strlen( $text );
+ } else {
+ $end = $this->position( $text, $end );
+ }
+
+ if ( !is_null( $posStart ) ) {
+ $posStart = $start;
+ }
+ if ( !is_null( $posEnd ) ) {
+ $posEnd = $end;
+ }
+
+ if ( $end > $start ) {
+ return substr( $text, $start, $end - $start );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Find a nonletter near a point (index) in the text
+ *
+ * @param string $text
+ * @param int $point
+ * @param int $offset Offset to found index
+ * @return int Nearest nonletter index, or beginning of utf8 char if none
+ */
+ function position( $text, $point, $offset = 0 ) {
+ $tolerance = 10;
+ $s = max( 0, $point - $tolerance );
+ $l = min( strlen( $text ), $point + $tolerance ) - $s;
+ $m = array();
+
+ if ( preg_match(
+ '/[ ,.!?~!@#$%^&*\(\)+=\-\\\|\[\]"\'<>]/',
+ substr( $text, $s, $l ),
+ $m,
+ PREG_OFFSET_CAPTURE
+ ) ) {
+ return $m[0][1] + $s + $offset;
+ } else {
+ // check if point is on a valid first UTF8 char
+ $char = ord( $text[$point] );
+ while ( $char >= 0x80 && $char < 0xc0 ) {
+ // skip trailing bytes
+ $point++;
+ if ( $point >= strlen( $text ) ) {
+ return strlen( $text );
+ }
+ $char = ord( $text[$point] );
+ }
+
+ return $point;
+
+ }
+ }
+
+ /**
+ * Search extracts for a pattern, and return snippets
+ *
+ * @param string $pattern Regexp for matching lines
+ * @param array $extracts Extracts to search
+ * @param int $linesleft Number of extracts to make
+ * @param int $contextchars Length of snippet
+ * @param array $out Map for highlighted snippets
+ * @param array $offsets Map of starting points of snippets
+ * @protected
+ */
+ function process( $pattern, $extracts, &$linesleft, &$contextchars, &$out, &$offsets ) {
+ if ( $linesleft == 0 ) {
+ return; // nothing to do
+ }
+ foreach ( $extracts as $index => $line ) {
+ if ( array_key_exists( $index, $out ) ) {
+ continue; // this line already highlighted
+ }
+
+ $m = array();
+ if ( !preg_match( $pattern, $line, $m, PREG_OFFSET_CAPTURE ) ) {
+ continue;
+ }
+
+ $offset = $m[0][1];
+ $len = strlen( $m[0][0] );
+ if ( $offset + $len < $contextchars ) {
+ $begin = 0;
+ } elseif ( $len > $contextchars ) {
+ $begin = $offset;
+ } else {
+ $begin = $offset + intval( ( $len - $contextchars ) / 2 );
+ }
+
+ $end = $begin + $contextchars;
+
+ $posBegin = $begin;
+ // basic snippet from this line
+ $out[$index] = $this->extract( $line, $begin, $end, $posBegin );
+ $offsets[$index] = $posBegin;
+ $linesleft--;
+ if ( $linesleft == 0 ) {
+ return;
+ }
+ }
+ }
+
+ /**
+ * Basic wikitext removal
+ * @protected
+ * @param string $text
+ * @return mixed
+ */
+ function removeWiki( $text ) {
+ $fname = __METHOD__;
+ wfProfileIn( $fname );
+
+ // $text = preg_replace( "/'{2,5}/", "", $text );
+ // $text = preg_replace( "/\[[a-z]+:\/\/[^ ]+ ([^]]+)\]/", "\\2", $text );
+ // $text = preg_replace( "/\[\[([^]|]+)\]\]/", "\\1", $text );
+ // $text = preg_replace( "/\[\[([^]]+\|)?([^|]]+)\]\]/", "\\2", $text );
+ // $text = preg_replace( "/\\{\\|(.*?)\\|\\}/", "", $text );
+ // $text = preg_replace( "/\\[\\[[A-Za-z_-]+:([^|]+?)\\]\\]/", "", $text );
+ $text = preg_replace( "/\\{\\{([^|]+?)\\}\\}/", "", $text );
+ $text = preg_replace( "/\\{\\{([^|]+\\|)(.*?)\\}\\}/", "\\2", $text );
+ $text = preg_replace( "/\\[\\[([^|]+?)\\]\\]/", "\\1", $text );
+ $text = preg_replace_callback(
+ "/\\[\\[([^|]+\\|)(.*?)\\]\\]/",
+ array( $this, 'linkReplace' ),
+ $text
+ );
+ // $text = preg_replace("/\\[\\[([^|]+\\|)(.*?)\\]\\]/", "\\2", $text);
+ $text = preg_replace( "/<\/?[^>]+>/", "", $text );
+ $text = preg_replace( "/'''''/", "", $text );
+ $text = preg_replace( "/('''|<\/?[iIuUbB]>)/", "", $text );
+ $text = preg_replace( "/''/", "", $text );
+
+ wfProfileOut( $fname );
+ return $text;
+ }
+
+ /**
+ * callback to replace [[target|caption]] kind of links, if
+ * the target is category or image, leave it
+ *
+ * @param array $matches
+ * @return string
+ */
+ function linkReplace( $matches ) {
+ $colon = strpos( $matches[1], ':' );
+ if ( $colon === false ) {
+ return $matches[2]; // replace with caption
+ }
+ global $wgContLang;
+ $ns = substr( $matches[1], 0, $colon );
+ $index = $wgContLang->getNsIndex( $ns );
+ if ( $index !== false && ( $index == NS_FILE || $index == NS_CATEGORY ) ) {
+ return $matches[0]; // return the whole thing
+ } else {
+ return $matches[2];
+ }
+ }
+
+ /**
+ * Simple & fast snippet extraction, but gives completely unrelevant
+ * snippets
+ *
+ * @param string $text
+ * @param array $terms
+ * @param int $contextlines
+ * @param int $contextchars
+ * @return string
+ */
+ public function highlightSimple( $text, $terms, $contextlines, $contextchars ) {
+ global $wgContLang;
+ $fname = __METHOD__;
+
+ $lines = explode( "\n", $text );
+
+ $terms = implode( '|', $terms );
+ $max = intval( $contextchars ) + 1;
+ $pat1 = "/(.*)($terms)(.{0,$max})/i";
+
+ $lineno = 0;
+
+ $extract = "";
+ wfProfileIn( "$fname-extract" );
+ foreach ( $lines as $line ) {
+ if ( 0 == $contextlines ) {
+ break;
+ }
+ ++$lineno;
+ $m = array();
+ if ( !preg_match( $pat1, $line, $m ) ) {
+ continue;
+ }
+ --$contextlines;
+ // truncate function changes ... to relevant i18n message.
+ $pre = $wgContLang->truncate( $m[1], - $contextchars, '...', false );
+
+ if ( count( $m ) < 3 ) {
+ $post = '';
+ } else {
+ $post = $wgContLang->truncate( $m[3], $contextchars, '...', false );
+ }
+
+ $found = $m[2];
+
+ $line = htmlspecialchars( $pre . $found . $post );
+ $pat2 = '/(' . $terms . ")/i";
+ $line = preg_replace( $pat2, "<span class='searchmatch'>\\1</span>", $line );
+
+ $extract .= "${line}\n";
+ }
+ wfProfileOut( "$fname-extract" );
+
+ return $extract;
+ }
+
+ /**
+ * Returns the first few lines of the text
+ *
+ * @param string $text
+ * @param int $contextlines Max number of returned lines
+ * @param int $contextchars Average number of characters per line
+ * @return string
+ */
+ public function highlightNone( $text, $contextlines, $contextchars ) {
+ $match = array();
+ $text = ltrim( $text ) . "\n"; // make sure the preg_match may find the last line
+ $text = str_replace( "\n\n", "\n", $text ); // remove empty lines
+ preg_match( "/^(.*\n){0,$contextlines}/", $text, $match );
+ $text = htmlspecialchars( substr( trim( $match[0] ), 0, $contextlines * $contextchars ) ); // trim and limit to max number of chars
+ return str_replace( "\n", '<br>', $text );
+ }
+}
diff --git a/includes/search/SearchMssql.php b/includes/search/SearchMssql.php
index cbc1a7a7..0d7970de 100644
--- a/includes/search/SearchMssql.php
+++ b/includes/search/SearchMssql.php
@@ -25,58 +25,35 @@
* Search engine hook base class for Mssql (ConText).
* @ingroup Search
*/
-class SearchMssql extends SearchEngine {
-
- /**
- * Creates an instance of this class
- * @param $db DatabaseMssql: database object
- */
- function __construct( $db ) {
- parent::__construct( $db );
- }
-
+class SearchMssql extends SearchDatabase {
/**
* Perform a full text search query and return a result set.
*
- * @param string $term raw search term
- * @return MssqlSearchResultSet
+ * @param string $term Raw search term
+ * @return SqlSearchResultSet
* @access public
*/
function searchText( $term ) {
- $resultSet = $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), true ) ) );
- return new MssqlSearchResultSet( $resultSet, $this->searchTerms );
+ $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), true ) );
+ return new SqlSearchResultSet( $resultSet, $this->searchTerms );
}
/**
* Perform a title-only search query and return a result set.
*
- * @param string $term raw search term
- * @return MssqlSearchResultSet
+ * @param string $term Raw search term
+ * @return SqlSearchResultSet
* @access public
*/
function searchTitle( $term ) {
- $resultSet = $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), false ) ) );
- return new MssqlSearchResultSet( $resultSet, $this->searchTerms );
- }
-
- /**
- * Return a partial WHERE clause to exclude redirects, if so set
- *
- * @return String
- * @private
- */
- function queryRedirect() {
- if ( $this->showRedirects ) {
- return '';
- } else {
- return 'AND page_is_redirect=0';
- }
+ $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), false ) );
+ return new SqlSearchResultSet( $resultSet, $this->searchTerms );
}
/**
* Return a partial WHERE clause to limit the search to the given namespaces
*
- * @return String
+ * @return string
* @private
*/
function queryNamespaces() {
@@ -90,9 +67,9 @@ class SearchMssql extends SearchEngine {
/**
* Return a LIMIT clause to limit results on the query.
*
- * @param $sql string
+ * @param string $sql
*
- * @return String
+ * @return string
*/
function queryLimit( $sql ) {
return $this->db->limitResult( $sql, $this->limit, $this->offset );
@@ -102,7 +79,9 @@ class SearchMssql extends SearchEngine {
* Does not do anything for generic search engine
* subclasses may define this though
*
- * @return String
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
*/
function queryRanking( $filteredTerm, $fulltext ) {
return ' ORDER BY ftindex.[RANK] DESC'; // return ' ORDER BY score(1)';
@@ -112,13 +91,12 @@ class SearchMssql extends SearchEngine {
* Construct the full SQL query to do the search.
* The guts shoulds be constructed in queryMain()
*
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return String
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
*/
function getQuery( $filteredTerm, $fulltext ) {
return $this->queryLimit( $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
- $this->queryRedirect() . ' ' .
$this->queryNamespaces() . ' ' .
$this->queryRanking( $filteredTerm, $fulltext ) . ' ' );
}
@@ -126,7 +104,7 @@ class SearchMssql extends SearchEngine {
/**
* Picks which field to index on, depending on what type of query.
*
- * @param $fulltext Boolean
+ * @param bool $fulltext
* @return string
*/
function getIndexField( $fulltext ) {
@@ -136,9 +114,9 @@ class SearchMssql extends SearchEngine {
/**
* Get the base part of the search query.
*
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return String
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
* @private
*/
function queryMain( $filteredTerm, $fulltext ) {
@@ -152,11 +130,13 @@ class SearchMssql extends SearchEngine {
}
/** @todo document
+ * @param string $filteredText
+ * @param bool $fulltext
* @return string
*/
function parseQuery( $filteredText, $fulltext ) {
global $wgContLang;
- $lc = SearchEngine::legalSearchChars();
+ $lc = $this->legalSearchChars();
$this->searchTerms = array();
# @todo FIXME: This doesn't handle parenthetical expressions.
@@ -180,18 +160,18 @@ class SearchMssql extends SearchEngine {
}
}
- $searchon = $this->db->strencode( join( ',', $q ) );
+ $searchon = $this->db->addQuotes( join( ',', $q ) );
$field = $this->getIndexField( $fulltext );
- return "$field, '$searchon'";
+ return "$field, $searchon";
}
/**
* Create or update the search index record for the given page.
* Title and text should be pre-processed.
*
- * @param $id Integer
- * @param $title String
- * @param $text String
+ * @param int $id
+ * @param string $title
+ * @param string $text
* @return bool|ResultWrapper
*/
function update( $id, $title, $text ) {
@@ -213,8 +193,8 @@ class SearchMssql extends SearchEngine {
* Update a search index record's title only.
* Title should be pre-processed.
*
- * @param $id Integer
- * @param $title String
+ * @param int $id
+ * @param string $title
* @return bool|ResultWrapper
*/
function updateTitle( $id, $title ) {
@@ -228,29 +208,3 @@ class SearchMssql extends SearchEngine {
return $this->db->query( $sql, 'SearchMssql::updateTitle' );
}
}
-
-/**
- * @ingroup Search
- */
-class MssqlSearchResultSet extends SearchResultSet {
- function __construct( $resultSet, $terms ) {
- $this->mResultSet = $resultSet;
- $this->mTerms = $terms;
- }
-
- function termMatches() {
- return $this->mTerms;
- }
-
- function numRows() {
- return $this->mResultSet->numRows();
- }
-
- function next() {
- $row = $this->mResultSet->fetchObject();
- if ( $row === false ) {
- return false;
- }
- return new SearchResult( $row );
- }
-}
diff --git a/includes/search/SearchMySQL.php b/includes/search/SearchMySQL.php
index b2bc1c26..78eba2d0 100644
--- a/includes/search/SearchMySQL.php
+++ b/includes/search/SearchMySQL.php
@@ -3,7 +3,7 @@
* MySQL search engine
*
* Copyright (C) 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -28,30 +28,24 @@
* Search engine hook for MySQL 4+
* @ingroup Search
*/
-class SearchMySQL extends SearchEngine {
- var $strictMatching = true;
- static $mMinSearchLength;
+class SearchMySQL extends SearchDatabase {
+ protected $strictMatching = true;
- /**
- * Creates an instance of this class
- * @param $db DatabaseMysql: database object
- */
- function __construct( $db ) {
- parent::__construct( $db );
- }
+ private static $mMinSearchLength;
/**
* Parse the user's query and transform it into an SQL fragment which will
* become part of a WHERE clause
*
- * @param $filteredText string
- * @param $fulltext string
+ * @param string $filteredText
+ * @param string $fulltext
*
* @return string
*/
function parseQuery( $filteredText, $fulltext ) {
global $wgContLang;
- $lc = SearchEngine::legalSearchChars(); // Minus format chars
+
+ $lc = $this->legalSearchChars(); // Minus format chars
$searchon = '';
$this->searchTerms = array();
@@ -60,7 +54,9 @@ class SearchMySQL extends SearchEngine {
if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
foreach ( $m as $bits ) {
- @list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
+ wfSuppressWarnings();
+ list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
+ wfRestoreWarnings();
if ( $nonQuoted != '' ) {
$term = $nonQuoted;
@@ -129,9 +125,9 @@ class SearchMySQL extends SearchEngine {
wfDebug( __METHOD__ . ": Can't understand search query '{$filteredText}'\n" );
}
- $searchon = $this->db->strencode( $searchon );
+ $searchon = $this->db->addQuotes( $searchon );
$field = $this->getIndexField( $fulltext );
- return " MATCH($field) AGAINST('$searchon' IN BOOLEAN MODE) ";
+ return " MATCH($field) AGAINST($searchon IN BOOLEAN MODE) ";
}
function regexTerm( $string, $wildcard ) {
@@ -160,8 +156,8 @@ class SearchMySQL extends SearchEngine {
/**
* Perform a full text search query and return a result set.
*
- * @param string $term raw search term
- * @return MySQLSearchResultSet
+ * @param string $term Raw search term
+ * @return SqlSearchResultSet
*/
function searchText( $term ) {
return $this->searchInternal( $term, true );
@@ -170,16 +166,14 @@ class SearchMySQL extends SearchEngine {
/**
* Perform a title-only search query and return a result set.
*
- * @param string $term raw search term
- * @return MySQLSearchResultSet
+ * @param string $term Raw search term
+ * @return SqlSearchResultSet
*/
function searchTitle( $term ) {
return $this->searchInternal( $term, false );
}
protected function searchInternal( $term, $fulltext ) {
- global $wgCountTotalSearchHits;
-
// This seems out of place, why is this called with empty term?
if ( trim( $term ) === '' ) {
return null;
@@ -193,21 +187,19 @@ class SearchMySQL extends SearchEngine {
);
$total = null;
- if ( $wgCountTotalSearchHits ) {
- $query = $this->getCountQuery( $filteredTerm, $fulltext );
- $totalResult = $this->db->select(
- $query['tables'], $query['fields'], $query['conds'],
- __METHOD__, $query['options'], $query['joins']
- );
-
- $row = $totalResult->fetchObject();
- if ( $row ) {
- $total = intval( $row->c );
- }
- $totalResult->free();
+ $query = $this->getCountQuery( $filteredTerm, $fulltext );
+ $totalResult = $this->db->select(
+ $query['tables'], $query['fields'], $query['conds'],
+ __METHOD__, $query['options'], $query['joins']
+ );
+
+ $row = $totalResult->fetchObject();
+ if ( $row ) {
+ $total = intval( $row->c );
}
+ $totalResult->free();
- return new MySQLSearchResultSet( $resultSet, $this->searchTerms, $total );
+ return new SqlSearchResultSet( $resultSet, $this->searchTerms, $total );
}
public function supports( $feature ) {
@@ -221,14 +213,12 @@ class SearchMySQL extends SearchEngine {
/**
* Add special conditions
- * @param $query Array
+ * @param array $query
* @since 1.18
*/
protected function queryFeatures( &$query ) {
foreach ( $this->features as $feature => $value ) {
- if ( $feature === 'list-redirects' && !$value ) {
- $query['conds']['page_is_redirect'] = 0;
- } elseif ( $feature === 'title-suffix-filter' && $value ) {
+ if ( $feature === 'title-suffix-filter' && $value ) {
$query['conds'][] = 'page_title' . $this->db->buildLike( $this->db->anyString(), $value );
}
}
@@ -236,7 +226,7 @@ class SearchMySQL extends SearchEngine {
/**
* Add namespace conditions
- * @param $query Array
+ * @param array $query
* @since 1.18 (changed)
*/
function queryNamespaces( &$query ) {
@@ -250,7 +240,7 @@ class SearchMySQL extends SearchEngine {
/**
* Add limit options
- * @param $query Array
+ * @param array $query
* @since 1.18
*/
protected function limitResult( &$query ) {
@@ -261,9 +251,9 @@ class SearchMySQL extends SearchEngine {
/**
* Construct the SQL query to do the search.
* The guts shoulds be constructed in queryMain()
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return Array
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return array
* @since 1.18 (changed)
*/
function getQuery( $filteredTerm, $fulltext ) {
@@ -285,8 +275,8 @@ class SearchMySQL extends SearchEngine {
/**
* Picks which field to index on, depending on what type of query.
- * @param $fulltext Boolean
- * @return String
+ * @param bool $fulltext
+ * @return string
*/
function getIndexField( $fulltext ) {
return $fulltext ? 'si_text' : 'si_title';
@@ -295,9 +285,9 @@ class SearchMySQL extends SearchEngine {
/**
* Get the base part of the search query.
*
- * @param &$query array Search query array
- * @param $filteredTerm String
- * @param $fulltext Boolean
+ * @param array &$query Search query array
+ * @param string $filteredTerm
+ * @param bool $fulltext
* @since 1.18 (changed)
*/
function queryMain( &$query, $filteredTerm, $fulltext ) {
@@ -313,6 +303,8 @@ class SearchMySQL extends SearchEngine {
/**
* @since 1.18 (changed)
+ * @param string $filteredTerm
+ * @param bool $fulltext
* @return array
*/
function getCountQuery( $filteredTerm, $fulltext ) {
@@ -336,9 +328,9 @@ class SearchMySQL extends SearchEngine {
* Create or update the search index record for the given page.
* Title and text should be pre-processed.
*
- * @param $id Integer
- * @param $title String
- * @param $text String
+ * @param int $id
+ * @param string $title
+ * @param string $text
*/
function update( $id, $title, $text ) {
$dbw = wfGetDB( DB_MASTER );
@@ -355,8 +347,8 @@ class SearchMySQL extends SearchEngine {
* Update a search index record's title only.
* Title should be pre-processed.
*
- * @param $id Integer
- * @param $title String
+ * @param int $id
+ * @param string $title
*/
function updateTitle( $id, $title ) {
$dbw = wfGetDB( DB_MASTER );
@@ -372,8 +364,8 @@ class SearchMySQL extends SearchEngine {
* Delete an indexed page
* Title should be pre-processed.
*
- * @param Integer $id Page id that was deleted
- * @param String $title Title of page that was deleted
+ * @param int $id Page id that was deleted
+ * @param string $title Title of page that was deleted
*/
function delete( $id, $title ) {
$dbw = wfGetDB( DB_MASTER );
@@ -384,6 +376,7 @@ class SearchMySQL extends SearchEngine {
/**
* Converts some characters for MySQL's indexing to grok it correctly,
* and pads short words to overcome limitations.
+ * @param string $string
* @return mixed|string
*/
function normalizeText( $string ) {
@@ -432,6 +425,7 @@ class SearchMySQL extends SearchEngine {
* Armor a case-folded UTF-8 string to get through MySQL's
* fulltext search without being mucked up by funny charset
* settings or anything else of the sort.
+ * @param array $matches
* @return string
*/
protected function stripForSearchCallback( $matches ) {
@@ -462,17 +456,3 @@ class SearchMySQL extends SearchEngine {
return self::$mMinSearchLength;
}
}
-
-/**
- * @ingroup Search
- */
-class MySQLSearchResultSet extends SqlSearchResultSet {
- function __construct( $resultSet, $terms, $totalHits = null ) {
- parent::__construct( $resultSet, $terms );
- $this->mTotalHits = $totalHits;
- }
-
- function getTotalHits() {
- return $this->mTotalHits;
- }
-}
diff --git a/includes/search/SearchOracle.php b/includes/search/SearchOracle.php
index a8479654..58211484 100644
--- a/includes/search/SearchOracle.php
+++ b/includes/search/SearchOracle.php
@@ -3,7 +3,7 @@
* Oracle search engine
*
* Copyright © 2004 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -28,8 +28,7 @@
* Search engine hook base class for Oracle (ConText).
* @ingroup Search
*/
-class SearchOracle extends SearchEngine {
-
+class SearchOracle extends SearchDatabase {
private $reservedWords = array(
'ABOUT' => 1,
'ACCUM' => 1,
@@ -60,17 +59,9 @@ class SearchOracle extends SearchEngine {
);
/**
- * Creates an instance of this class
- * @param $db DatabasePostgres: database object
- */
- function __construct( $db ) {
- parent::__construct( $db );
- }
-
- /**
* Perform a full text search query and return a result set.
*
- * @param string $term raw search term
+ * @param string $term Raw search term
* @return SqlSearchResultSet
*/
function searchText( $term ) {
@@ -78,14 +69,14 @@ class SearchOracle extends SearchEngine {
return new SqlSearchResultSet( false, '' );
}
- $resultSet = $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), true ) ) );
+ $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), true ) );
return new SqlSearchResultSet( $resultSet, $this->searchTerms );
}
/**
* Perform a title-only search query and return a result set.
*
- * @param string $term raw search term
+ * @param string $term Raw search term
* @return SqlSearchResultSet
*/
function searchTitle( $term ) {
@@ -93,25 +84,13 @@ class SearchOracle extends SearchEngine {
return new SqlSearchResultSet( false, '' );
}
- $resultSet = $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), false ) ) );
- return new MySQLSearchResultSet( $resultSet, $this->searchTerms );
- }
-
- /**
- * Return a partial WHERE clause to exclude redirects, if so set
- * @return String
- */
- function queryRedirect() {
- if ( $this->showRedirects ) {
- return '';
- } else {
- return 'AND page_is_redirect=0';
- }
+ $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), false ) );
+ return new SqlSearchResultSet( $resultSet, $this->searchTerms );
}
/**
* Return a partial WHERE clause to limit the search to the given namespaces
- * @return String
+ * @return string
*/
function queryNamespaces() {
if ( is_null( $this->namespaces ) ) {
@@ -128,9 +107,9 @@ class SearchOracle extends SearchEngine {
/**
* Return a LIMIT clause to limit results on the query.
*
- * @param $sql string
+ * @param string $sql
*
- * @return String
+ * @return string
*/
function queryLimit( $sql ) {
return $this->db->limitResult( $sql, $this->limit, $this->offset );
@@ -140,7 +119,9 @@ class SearchOracle extends SearchEngine {
* Does not do anything for generic search engine
* subclasses may define this though
*
- * @return String
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
*/
function queryRanking( $filteredTerm, $fulltext ) {
return ' ORDER BY score(1)';
@@ -149,21 +130,20 @@ class SearchOracle extends SearchEngine {
/**
* Construct the full SQL query to do the search.
* The guts shoulds be constructed in queryMain()
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return String
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
*/
function getQuery( $filteredTerm, $fulltext ) {
return $this->queryLimit( $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
- $this->queryRedirect() . ' ' .
$this->queryNamespaces() . ' ' .
$this->queryRanking( $filteredTerm, $fulltext ) . ' ' );
}
/**
* Picks which field to index on, depending on what type of query.
- * @param $fulltext Boolean
- * @return String
+ * @param bool $fulltext
+ * @return string
*/
function getIndexField( $fulltext ) {
return $fulltext ? 'si_text' : 'si_title';
@@ -172,9 +152,9 @@ class SearchOracle extends SearchEngine {
/**
* Get the base part of the search query.
*
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return String
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
*/
function queryMain( $filteredTerm, $fulltext ) {
$match = $this->parseQuery( $filteredTerm, $fulltext );
@@ -188,11 +168,13 @@ class SearchOracle extends SearchEngine {
/**
* Parse a user input search string, and return an SQL fragment to be used
* as part of a WHERE clause
+ * @param string $filteredText
+ * @param bool $fulltext
* @return string
*/
function parseQuery( $filteredText, $fulltext ) {
global $wgContLang;
- $lc = SearchEngine::legalSearchChars();
+ $lc = $this->legalSearchChars();
$this->searchTerms = array();
# @todo FIXME: This doesn't handle parenthetical expressions.
@@ -238,13 +220,14 @@ class SearchOracle extends SearchEngine {
$t = preg_replace( '/([-&|])/', '\\\\$1', $t );
return $t;
}
+
/**
* Create or update the search index record for the given page.
* Title and text should be pre-processed.
*
- * @param $id Integer
- * @param $title String
- * @param $text String
+ * @param int $id
+ * @param string $title
+ * @param string $text
*/
function update( $id, $title, $text ) {
$dbw = wfGetDB( DB_MASTER );
@@ -271,8 +254,8 @@ class SearchOracle extends SearchEngine {
* Update a search index record's title only.
* Title should be pre-processed.
*
- * @param $id Integer
- * @param $title String
+ * @param int $id
+ * @param string $title
*/
function updateTitle( $id, $title ) {
$dbw = wfGetDB( DB_MASTER );
diff --git a/includes/search/SearchPostgres.php b/includes/search/SearchPostgres.php
index 7f19ed13..59b0c31c 100644
--- a/includes/search/SearchPostgres.php
+++ b/includes/search/SearchPostgres.php
@@ -3,7 +3,7 @@
* PostgreSQL search engine
*
* Copyright © 2006-2007 Greg Sabino Mullane <greg@turnstep.com>
- * http://www.mediawiki.org/
+ * 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
@@ -28,56 +28,36 @@
* Search engine hook base class for Postgres
* @ingroup Search
*/
-class SearchPostgres extends SearchEngine {
-
- /**
- * @var DatabasePostgres
- */
- protected $db;
- /**
- * Creates an instance of this class
- * @param $db DatabaseSqlite: database object
- */
- function __construct( $db ) {
- parent::__construct( $db );
- }
-
+class SearchPostgres extends SearchDatabase {
/**
* Perform a full text search query via tsearch2 and return a result set.
* Currently searches a page's current title (page.page_title) and
* latest revision article text (pagecontent.old_text)
*
- * @param string $term raw search term
- * @return PostgresSearchResultSet
+ * @param string $term Raw search term
+ * @return SqlSearchResultSet
*/
function searchTitle( $term ) {
$q = $this->searchQuery( $term, 'titlevector', 'page_title' );
$olderror = error_reporting( E_ERROR );
- $resultSet = $this->db->resultObject( $this->db->query( $q, 'SearchPostgres', true ) );
+ $resultSet = $this->db->query( $q, 'SearchPostgres', true );
error_reporting( $olderror );
- if ( !$resultSet ) {
- // Needed for "Query requires full scan, GIN doesn't support it"
- return new SearchResultTooMany();
- }
- return new PostgresSearchResultSet( $resultSet, $this->searchTerms );
+ return new SqlSearchResultSet( $resultSet, $this->searchTerms );
}
function searchText( $term ) {
$q = $this->searchQuery( $term, 'textvector', 'old_text' );
$olderror = error_reporting( E_ERROR );
- $resultSet = $this->db->resultObject( $this->db->query( $q, 'SearchPostgres', true ) );
+ $resultSet = $this->db->query( $q, 'SearchPostgres', true );
error_reporting( $olderror );
- if ( !$resultSet ) {
- return new SearchResultTooMany();
- }
- return new PostgresSearchResultSet( $resultSet, $this->searchTerms );
+ return new SqlSearchResultSet( $resultSet, $this->searchTerms );
}
/**
* Transform the user's search string into a better form for tsearch2
* Returns an SQL fragment consisting of quoted text to search for.
*
- * @param $term string
+ * @param string $term
*
* @return string
*/
@@ -143,9 +123,9 @@ class SearchPostgres extends SearchEngine {
/**
* Construct the full SQL query to do the search.
- * @param $term String
- * @param $fulltext String
- * @param $colname
+ * @param string $term
+ * @param string $fulltext
+ * @param string $colname
* @return string
*/
function searchQuery( $term, $fulltext, $colname ) {
@@ -153,8 +133,8 @@ class SearchPostgres extends SearchEngine {
$searchstring = $this->parseQuery( $term );
## We need a separate query here so gin does not complain about empty searches
- $SQL = "SELECT to_tsquery($searchstring)";
- $res = $this->db->query( $SQL );
+ $sql = "SELECT to_tsquery($searchstring)";
+ $res = $this->db->query( $sql );
if ( !$res ) {
## TODO: Better output (example to catch: one 'two)
die( "Sorry, that was not a valid search string. Please go back and try again" );
@@ -162,6 +142,7 @@ class SearchPostgres extends SearchEngine {
$top = $res->fetchRow();
$top = $top[0];
+ $this->searchTerms = array();
if ( $top === "" ) { ## e.g. if only stopwords are used XXX return something better
$query = "SELECT page_id, page_namespace, page_title, 0 AS score " .
"FROM page p, revision r, pagecontent c WHERE p.page_latest = r.rev_id " .
@@ -181,11 +162,6 @@ class SearchPostgres extends SearchEngine {
"AND r.rev_text_id = c.old_id AND $fulltext @@ to_tsquery($searchstring)";
}
- ## Redirects
- if ( !$this->showRedirects ) {
- $query .= ' AND page_is_redirect = 0';
- }
-
## Namespaces - defaults to 0
if ( !is_null( $this->namespaces ) ) { // null -> search all
if ( count( $this->namespaces ) < 1 ) {
@@ -209,10 +185,10 @@ class SearchPostgres extends SearchEngine {
function update( $pageid, $title, $text ) {
## We don't want to index older revisions
- $SQL = "UPDATE pagecontent SET textvector = NULL WHERE old_id IN " .
+ $sql = "UPDATE pagecontent SET textvector = NULL WHERE textvector IS NOT NULL and old_id IN " .
"(SELECT rev_text_id FROM revision WHERE rev_page = " . intval( $pageid ) .
" ORDER BY rev_text_id DESC OFFSET 1)";
- $this->db->query( $SQL );
+ $this->db->query( $sql );
return true;
}
@@ -220,35 +196,4 @@ class SearchPostgres extends SearchEngine {
return true;
}
-} ## end of the SearchPostgres class
-
-/**
- * @ingroup Search
- */
-class PostgresSearchResult extends SearchResult {
- function __construct( $row ) {
- parent::__construct( $row );
- $this->score = $row->score;
- }
- function getScore() {
- return $this->score;
- }
-}
-
-/**
- * @ingroup Search
- */
-class PostgresSearchResultSet extends SqlSearchResultSet {
- function __construct( $resultSet, $terms ) {
- parent::__construct( $resultSet, $terms );
- }
-
- function next() {
- $row = $this->mResultSet->fetchObject();
- if ( $row === false ) {
- return false;
- } else {
- return new PostgresSearchResult( $row );
- }
- }
}
diff --git a/includes/search/SearchResult.php b/includes/search/SearchResult.php
new file mode 100644
index 00000000..aeaba8df
--- /dev/null
+++ b/includes/search/SearchResult.php
@@ -0,0 +1,237 @@
+<?php
+/**
+ * Search engine result
+ *
+ * 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 Search
+ */
+
+/**
+ * @todo FIXME: This class is horribly factored. It would probably be better to
+ * have a useful base class to which you pass some standard information, then
+ * let the fancy self-highlighters extend that.
+ * @ingroup Search
+ */
+class SearchResult {
+
+ /**
+ * @var Revision
+ */
+ protected $mRevision = null;
+
+ /**
+ * @var File
+ */
+ protected $mImage = null;
+
+ /**
+ * @var Title
+ */
+ protected $mTitle;
+
+ /**
+ * @var string
+ */
+ protected $mText;
+
+ /**
+ * Return a new SearchResult and initializes it with a title.
+ *
+ * @param Title $title
+ * @return SearchResult
+ */
+ public static function newFromTitle( $title ) {
+ $result = new self();
+ $result->initFromTitle( $title );
+ return $result;
+ }
+
+ /**
+ * Initialize from a Title and if possible initializes a corresponding
+ * Revision and File.
+ *
+ * @param Title $title
+ */
+ protected function initFromTitle( $title ) {
+ $this->mTitle = $title;
+ if ( !is_null( $this->mTitle ) ) {
+ $id = false;
+ wfRunHooks( 'SearchResultInitFromTitle', array( $title, &$id ) );
+ $this->mRevision = Revision::newFromTitle(
+ $this->mTitle, $id, Revision::READ_NORMAL );
+ if ( $this->mTitle->getNamespace() === NS_FILE ) {
+ $this->mImage = wfFindFile( $this->mTitle );
+ }
+ }
+ }
+
+ /**
+ * Check if this is result points to an invalid title
+ *
+ * @return bool
+ */
+ function isBrokenTitle() {
+ return is_null( $this->mTitle );
+ }
+
+ /**
+ * Check if target page is missing, happens when index is out of date
+ *
+ * @return bool
+ */
+ function isMissingRevision() {
+ return !$this->mRevision && !$this->mImage;
+ }
+
+ /**
+ * @return Title
+ */
+ function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
+ * Get the file for this page, if one exists
+ * @return File|null
+ */
+ function getFile() {
+ return $this->mImage;
+ }
+
+ /**
+ * Lazy initialization of article text from DB
+ */
+ protected function initText() {
+ if ( !isset( $this->mText ) ) {
+ if ( $this->mRevision != null ) {
+ $this->mText = SearchEngine::create()
+ ->getTextFromContent( $this->mTitle, $this->mRevision->getContent() );
+ } else { // TODO: can we fetch raw wikitext for commons images?
+ $this->mText = '';
+ }
+ }
+ }
+
+ /**
+ * @param array $terms Terms to highlight
+ * @return string Highlighted text snippet, null (and not '') if not supported
+ */
+ function getTextSnippet( $terms ) {
+ global $wgAdvancedSearchHighlighting;
+ $this->initText();
+
+ // TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
+ list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs();
+
+ $h = new SearchHighlighter();
+ if ( count( $terms ) > 0 ) {
+ if ( $wgAdvancedSearchHighlighting ) {
+ return $h->highlightText( $this->mText, $terms, $contextlines, $contextchars );
+ } else {
+ return $h->highlightSimple( $this->mText, $terms, $contextlines, $contextchars );
+ }
+ } else {
+ return $h->highlightNone( $this->mText, $contextlines, $contextchars );
+ }
+ }
+
+ /**
+ * @return string Highlighted title, '' if not supported
+ */
+ function getTitleSnippet() {
+ return '';
+ }
+
+ /**
+ * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
+ */
+ function getRedirectSnippet() {
+ return '';
+ }
+
+ /**
+ * @return Title Title object for the redirect to this page, null if none or not supported
+ */
+ function getRedirectTitle() {
+ return null;
+ }
+
+ /**
+ * @return string Highlighted relevant section name, null if none or not supported
+ */
+ function getSectionSnippet() {
+ return '';
+ }
+
+ /**
+ * @return Title Title object (pagename+fragment) for the section, null if none or not supported
+ */
+ function getSectionTitle() {
+ return null;
+ }
+
+ /**
+ * @return string Timestamp
+ */
+ function getTimestamp() {
+ if ( $this->mRevision ) {
+ return $this->mRevision->getTimestamp();
+ } elseif ( $this->mImage ) {
+ return $this->mImage->getTimestamp();
+ }
+ return '';
+ }
+
+ /**
+ * @return int Number of words
+ */
+ function getWordCount() {
+ $this->initText();
+ return str_word_count( $this->mText );
+ }
+
+ /**
+ * @return int Size in bytes
+ */
+ function getByteSize() {
+ $this->initText();
+ return strlen( $this->mText );
+ }
+
+ /**
+ * @return string Interwiki prefix of the title (return iw even if title is broken)
+ */
+ function getInterwikiPrefix() {
+ return '';
+ }
+
+ /**
+ * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
+ */
+ function getInterwikiNamespaceText() {
+ return '';
+ }
+
+ /**
+ * Did this match file contents (eg: PDF/DJVU)?
+ * @return bool
+ */
+ function isFileMatch() {
+ return false;
+ }
+}
diff --git a/includes/search/SearchResultSet.php b/includes/search/SearchResultSet.php
new file mode 100644
index 00000000..406d322d
--- /dev/null
+++ b/includes/search/SearchResultSet.php
@@ -0,0 +1,211 @@
+<?php
+/**
+ * Search result sets
+ *
+ * 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 Search
+ */
+
+/**
+ * @ingroup Search
+ */
+class SearchResultSet {
+ /**
+ * Fetch an array of regular expression fragments for matching
+ * the search terms as parsed by this engine in a text extract.
+ * STUB
+ *
+ * @return array
+ */
+ function termMatches() {
+ return array();
+ }
+
+ function numRows() {
+ return 0;
+ }
+
+ /**
+ * Some search modes return a total hit count for the query
+ * in the entire article database. This may include pages
+ * in namespaces that would not be matched on the given
+ * settings.
+ *
+ * Return null if no total hits number is supported.
+ *
+ * @return int
+ */
+ function getTotalHits() {
+ return null;
+ }
+
+ /**
+ * Some search modes return a suggested alternate term if there are
+ * no exact hits. Returns true if there is one on this set.
+ *
+ * @return bool
+ */
+ function hasSuggestion() {
+ return false;
+ }
+
+ /**
+ * @return string Suggested query, null if none
+ */
+ function getSuggestionQuery() {
+ return null;
+ }
+
+ /**
+ * @return string HTML highlighted suggested query, '' if none
+ */
+ function getSuggestionSnippet() {
+ return '';
+ }
+
+ /**
+ * Return a result set of hits on other (multiple) wikis associated with this one
+ *
+ * @return SearchResultSet
+ */
+ function getInterwikiResults() {
+ return null;
+ }
+
+ /**
+ * Check if there are results on other wikis
+ *
+ * @return bool
+ */
+ function hasInterwikiResults() {
+ return $this->getInterwikiResults() != null;
+ }
+
+ /**
+ * Fetches next search result, or false.
+ * STUB
+ *
+ * @return SearchResult
+ */
+ function next() {
+ return false;
+ }
+
+ /**
+ * Frees the result set, if applicable.
+ */
+ function free() {
+ // ...
+ }
+
+ /**
+ * Did the search contain search syntax? If so, Special:Search won't offer
+ * the user a link to a create a page named by the search string because the
+ * name would contain the search syntax.
+ * @return bool
+ */
+ public function searchContainedSyntax() {
+ return false;
+ }
+}
+
+/**
+ * This class is used for different SQL-based search engines shipped with MediaWiki
+ * @ingroup Search
+ */
+class SqlSearchResultSet extends SearchResultSet {
+ protected $resultSet;
+ protected $terms;
+ protected $totalHits;
+
+ function __construct( $resultSet, $terms, $total = null ) {
+ $this->resultSet = $resultSet;
+ $this->terms = $terms;
+ $this->totalHits = $total;
+ }
+
+ function termMatches() {
+ return $this->terms;
+ }
+
+ function numRows() {
+ if ( $this->resultSet === false ) {
+ return false;
+ }
+
+ return $this->resultSet->numRows();
+ }
+
+ function next() {
+ if ( $this->resultSet === false ) {
+ return false;
+ }
+
+ $row = $this->resultSet->fetchObject();
+ if ( $row === false ) {
+ return false;
+ }
+
+ return SearchResult::newFromTitle(
+ Title::makeTitle( $row->page_namespace, $row->page_title )
+ );
+ }
+
+ function free() {
+ if ( $this->resultSet === false ) {
+ return false;
+ }
+
+ $this->resultSet->free();
+ }
+
+ function getTotalHits() {
+ if ( !is_null( $this->totalHits ) ) {
+ return $this->totalHits;
+ } else {
+ // Special:Search expects a number here.
+ return $this->numRows();
+ }
+ }
+}
+
+/**
+ * A SearchResultSet wrapper for SearchEngine::getNearMatch
+ */
+class SearchNearMatchResultSet extends SearchResultSet {
+ private $fetched = false;
+
+ /**
+ * @param Title|null $match Title if matched, else null
+ */
+ public function __construct( $match ) {
+ $this->result = $match;
+ }
+
+ public function numRows() {
+ return $this->result ? 1 : 0;
+ }
+
+ public function next() {
+ if ( $this->fetched || !$this->result ) {
+ return false;
+ }
+ $this->fetched = true;
+ return SearchResult::newFromTitle( $this->result );
+ }
+}
diff --git a/includes/search/SearchSqlite.php b/includes/search/SearchSqlite.php
index 554181f6..eee69307 100644
--- a/includes/search/SearchSqlite.php
+++ b/includes/search/SearchSqlite.php
@@ -25,24 +25,10 @@
* Search engine hook for SQLite
* @ingroup Search
*/
-class SearchSqlite extends SearchEngine {
-
- /**
- * @var DatabaseSqlite
- */
- protected $db;
-
- /**
- * Creates an instance of this class
- * @param $db DatabaseSqlite: database object
- */
- function __construct( $db ) {
- parent::__construct( $db );
- }
-
+class SearchSqlite extends SearchDatabase {
/**
* Whether fulltext search is supported by current schema
- * @return Boolean
+ * @return bool
*/
function fulltextSearchSupported() {
return $this->db->checkForEnabledSearch();
@@ -52,11 +38,13 @@ class SearchSqlite extends SearchEngine {
* Parse the user's query and transform it into an SQL fragment which will
* become part of a WHERE clause
*
+ * @param string $filteredText
+ * @param bool $fulltext
* @return string
*/
function parseQuery( $filteredText, $fulltext ) {
global $wgContLang;
- $lc = SearchEngine::legalSearchChars(); // Minus format chars
+ $lc = $this->legalSearchChars(); // Minus format chars
$searchon = '';
$this->searchTerms = array();
@@ -64,7 +52,9 @@ class SearchSqlite extends SearchEngine {
if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
foreach ( $m as $bits ) {
- @list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
+ wfSuppressWarnings();
+ list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
+ wfRestoreWarnings();
if ( $nonQuoted != '' ) {
$term = $nonQuoted;
@@ -127,9 +117,9 @@ class SearchSqlite extends SearchEngine {
wfDebug( __METHOD__ . ": Can't understand search query '{$filteredText}'\n" );
}
- $searchon = $this->db->strencode( $searchon );
+ $searchon = $this->db->addQuotes( $searchon );
$field = $this->getIndexField( $fulltext );
- return " $field MATCH '$searchon' ";
+ return " $field MATCH $searchon ";
}
function regexTerm( $string, $wildcard ) {
@@ -158,8 +148,8 @@ class SearchSqlite extends SearchEngine {
/**
* Perform a full text search query and return a result set.
*
- * @param string $term raw search term
- * @return SqliteSearchResultSet
+ * @param string $term Raw search term
+ * @return SqlSearchResultSet
*/
function searchText( $term ) {
return $this->searchInternal( $term, true );
@@ -168,15 +158,15 @@ class SearchSqlite extends SearchEngine {
/**
* Perform a title-only search query and return a result set.
*
- * @param string $term raw search term
- * @return SqliteSearchResultSet
+ * @param string $term Raw search term
+ * @return SqlSearchResultSet
*/
function searchTitle( $term ) {
return $this->searchInternal( $term, false );
}
protected function searchInternal( $term, $fulltext ) {
- global $wgCountTotalSearchHits, $wgContLang;
+ global $wgContLang;
if ( !$this->fulltextSearchSupported() ) {
return null;
@@ -186,33 +176,19 @@ class SearchSqlite extends SearchEngine {
$resultSet = $this->db->query( $this->getQuery( $filteredTerm, $fulltext ) );
$total = null;
- if ( $wgCountTotalSearchHits ) {
- $totalResult = $this->db->query( $this->getCountQuery( $filteredTerm, $fulltext ) );
- $row = $totalResult->fetchObject();
- if ( $row ) {
- $total = intval( $row->c );
- }
- $totalResult->free();
+ $totalResult = $this->db->query( $this->getCountQuery( $filteredTerm, $fulltext ) );
+ $row = $totalResult->fetchObject();
+ if ( $row ) {
+ $total = intval( $row->c );
}
+ $totalResult->free();
- return new SqliteSearchResultSet( $resultSet, $this->searchTerms, $total );
- }
-
- /**
- * Return a partial WHERE clause to exclude redirects, if so set
- * @return String
- */
- function queryRedirect() {
- if ( $this->showRedirects ) {
- return '';
- } else {
- return 'AND page_is_redirect=0';
- }
+ return new SqlSearchResultSet( $resultSet, $this->searchTerms, $total );
}
/**
* Return a partial WHERE clause to limit the search to the given namespaces
- * @return String
+ * @return string
*/
function queryNamespaces() {
if ( is_null( $this->namespaces ) ) {
@@ -228,8 +204,8 @@ class SearchSqlite extends SearchEngine {
/**
* Returns a query with limit for number of results set.
- * @param $sql String:
- * @return String
+ * @param string $sql
+ * @return string
*/
function limitResult( $sql ) {
return $this->db->limitResult( $sql, $this->limit, $this->offset );
@@ -238,22 +214,21 @@ class SearchSqlite extends SearchEngine {
/**
* Construct the full SQL query to do the search.
* The guts shoulds be constructed in queryMain()
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return String
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
*/
function getQuery( $filteredTerm, $fulltext ) {
return $this->limitResult(
$this->queryMain( $filteredTerm, $fulltext ) . ' ' .
- $this->queryRedirect() . ' ' .
$this->queryNamespaces()
);
}
/**
* Picks which field to index on, depending on what type of query.
- * @param $fulltext Boolean
- * @return String
+ * @param bool $fulltext
+ * @return string
*/
function getIndexField( $fulltext ) {
return $fulltext ? 'si_text' : 'si_title';
@@ -262,9 +237,9 @@ class SearchSqlite extends SearchEngine {
/**
* Get the base part of the search query.
*
- * @param $filteredTerm String
- * @param $fulltext Boolean
- * @return String
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
*/
function queryMain( $filteredTerm, $fulltext ) {
$match = $this->parseQuery( $filteredTerm, $fulltext );
@@ -281,8 +256,7 @@ class SearchSqlite extends SearchEngine {
$searchindex = $this->db->tableName( 'searchindex' );
return "SELECT COUNT(*) AS c " .
"FROM $page,$searchindex " .
- "WHERE page_id=$searchindex.rowid AND $match" .
- $this->queryRedirect() . ' ' .
+ "WHERE page_id=$searchindex.rowid AND $match " .
$this->queryNamespaces();
}
@@ -290,9 +264,9 @@ class SearchSqlite extends SearchEngine {
* Create or update the search index record for the given page.
* Title and text should be pre-processed.
*
- * @param $id Integer
- * @param $title String
- * @param $text String
+ * @param int $id
+ * @param string $title
+ * @param string $text
*/
function update( $id, $title, $text ) {
if ( !$this->fulltextSearchSupported() ) {
@@ -316,8 +290,8 @@ class SearchSqlite extends SearchEngine {
* Update a search index record's title only.
* Title should be pre-processed.
*
- * @param $id Integer
- * @param $title String
+ * @param int $id
+ * @param string $title
*/
function updateTitle( $id, $title ) {
if ( !$this->fulltextSearchSupported() ) {
@@ -331,17 +305,3 @@ class SearchSqlite extends SearchEngine {
__METHOD__ );
}
}
-
-/**
- * @ingroup Search
- */
-class SqliteSearchResultSet extends SqlSearchResultSet {
- function __construct( $resultSet, $terms, $totalHits = null ) {
- parent::__construct( $resultSet, $terms );
- $this->mTotalHits = $totalHits;
- }
-
- function getTotalHits() {
- return $this->mTotalHits;
- }
-}
diff --git a/includes/site/MediaWikiSite.php b/includes/site/MediaWikiSite.php
index f3b8a0c7..9711f042 100644
--- a/includes/site/MediaWikiSite.php
+++ b/includes/site/MediaWikiSite.php
@@ -33,15 +33,14 @@
* @ingroup Site
*/
class MediaWikiSite extends Site {
-
const PATH_FILE = 'file_path';
const PATH_PAGE = 'page_path';
/**
* @since 1.21
- * @deprecated Just use the constructor or the factory Site::newForType
+ * @deprecated since 1.21 Just use the constructor or the factory Site::newForType
*
- * @param integer $globalId
+ * @param int $globalId
*
* @return MediaWikiSite
*/
@@ -67,23 +66,25 @@ class MediaWikiSite extends Site {
*
* @since 1.21
*
- * @param string $title the target page's title, in normalized form.
+ * @param string $title The target page's title, in normalized form.
*
- * @return String
+ * @return string
*/
public function toDBKey( $title ) {
return str_replace( ' ', '_', $title );
}
/**
- * Returns the normalized form of the given page title, using the normalization rules of the given site.
- * If the given title is a redirect, the redirect weill be resolved and the redirect target is returned.
+ * Returns the normalized form of the given page title, using the
+ * normalization rules of the given site. If the given title is a redirect,
+ * the redirect weill be resolved and the redirect target is returned.
*
- * @note : This actually makes an API request to the remote site, so beware that this function is slow and depends
- * on an external service.
+ * @note This actually makes an API request to the remote site, so beware
+ * that this function is slow and depends on an external service.
*
- * @note : If MW_PHPUNIT_TEST is defined, the call to the external site is skipped, and the title
- * is normalized using the local normalization rules as implemented by the Title class.
+ * @note If MW_PHPUNIT_TEST is defined, the call to the external site is
+ * skipped, and the title is normalized using the local normalization
+ * rules as implemented by the Title class.
*
* @see Site::normalizePageName
*
@@ -103,8 +104,10 @@ class MediaWikiSite extends Site {
// Go on call the external site
if ( defined( 'MW_PHPUNIT_TEST' ) ) {
- // If the code is under test, don't call out to other sites, just normalize locally.
- // Note: this may cause results to be inconsistent with the actual normalization used by the respective remote site!
+ // If the code is under test, don't call out to other sites, just
+ // normalize locally.
+ // Note: this may cause results to be inconsistent with the actual
+ // normalization used by the respective remote site!
$t = Title::newFromText( $pageName );
return $t->getPrefixedText();
@@ -152,12 +155,14 @@ class MediaWikiSite extends Site {
$page = static::extractPageRecord( $data, $pageName );
if ( isset( $page['missing'] ) ) {
- wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for a missing page title! " . $ret );
+ wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for a missing page title! "
+ . $ret );
return false;
}
if ( isset( $page['invalid'] ) ) {
- wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for an invalid page title! " . $ret );
+ wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for an invalid page title! "
+ . $ret );
return false;
}
@@ -177,7 +182,7 @@ class MediaWikiSite extends Site {
* @param array $externalData A reply from the API on a external server.
* @param string $pageTitle Identifies the page at the external site, needing normalization.
*
- * @return array|boolean a 'page' structure representing the page identified by $pageTitle.
+ * @return array|bool A 'page' structure representing the page identified by $pageTitle.
*/
private static function extractPageRecord( $externalData, $pageTitle ) {
// If there is a special case with only one returned page
@@ -210,7 +215,7 @@ class MediaWikiSite extends Site {
// Filter the substructure down to what we actually are using.
$collectedHits = array_filter(
array_values( $externalData['query'][$listId] ),
- function( $a ) use ( $fieldId, $pageTitle ) {
+ function ( $a ) use ( $fieldId, $pageTitle ) {
return $a[$fieldId] === $pageTitle;
}
);
@@ -309,7 +314,7 @@ class MediaWikiSite extends Site {
*
* @since 1.21
*
- * @param string|boolean $pageName Page name or false (default: false)
+ * @param string|bool $pageName Page name or false (default: false)
*
* @return string
*/
@@ -335,7 +340,7 @@ class MediaWikiSite extends Site {
*
* @since 1.21
*
- * @param string|boolean $path
+ * @param string|bool $path
*
* @return string
*/
@@ -348,5 +353,4 @@ class MediaWikiSite extends Site {
return $filePath;
}
-
}
diff --git a/includes/site/Site.php b/includes/site/Site.php
index 076dc88c..fafb14c7 100644
--- a/includes/site/Site.php
+++ b/includes/site/Site.php
@@ -27,7 +27,6 @@
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
class Site implements Serializable {
-
const TYPE_UNKNOWN = 'unknown';
const TYPE_MEDIAWIKI = 'mediawiki';
@@ -234,7 +233,7 @@ class Site implements Serializable {
*
* @since 1.21
*
- * @return boolean
+ * @return bool
*/
public function shouldForward() {
return $this->forward;
@@ -246,7 +245,7 @@ class Site implements Serializable {
*
* @since 1.21
*
- * @param boolean $shouldForward
+ * @param bool $shouldForward
*
* @throws MWException
*/
@@ -340,11 +339,12 @@ class Site implements Serializable {
}
/**
- * Returns the main path type, that is the type of the path that should generally be used to construct links
- * to the target site.
+ * Returns the main path type, that is the type of the path that should
+ * generally be used to construct links to the target site.
*
- * This default implementation returns Site::PATH_LINK as the default path type. Subclasses can override this
- * to define a different default path type, or return false to disable site links.
+ * This default implementation returns Site::PATH_LINK as the default path
+ * type. Subclasses can override this to define a different default path
+ * type, or return false to disable site links.
*
* @since 1.21
*
@@ -365,9 +365,9 @@ class Site implements Serializable {
*
* @since 1.21
*
- * @param bool|String $pageName
+ * @param bool|string $pageName
*
- * @return string|boolean false
+ * @return string|bool
*/
public function getPageUrl( $pageName = false ) {
$url = $this->getLinkPath();
@@ -541,7 +541,9 @@ class Site implements Serializable {
* @return string[]
*/
public function getInterwikiIds() {
- return array_key_exists( self::ID_INTERWIKI, $this->localIds ) ? $this->localIds[self::ID_INTERWIKI] : array();
+ return array_key_exists( self::ID_INTERWIKI, $this->localIds )
+ ? $this->localIds[self::ID_INTERWIKI]
+ : array();
}
/**
@@ -553,7 +555,9 @@ class Site implements Serializable {
* @return string[]
*/
public function getNavigationIds() {
- return array_key_exists( self::ID_EQUIVALENT, $this->localIds ) ? $this->localIds[self::ID_EQUIVALENT] : array();
+ return array_key_exists( self::ID_EQUIVALENT, $this->localIds )
+ ? $this->localIds[self::ID_EQUIVALENT] :
+ array();
}
/**
@@ -693,10 +697,10 @@ class Site implements Serializable {
$this->setForward( $fields['forward'] );
$this->setInternalId( $fields['internalid'] );
}
-
}
/**
- * @deprecated
+ * @deprecated since 1.21
*/
-class SiteObject extends Site {}
+class SiteObject extends Site {
+}
diff --git a/includes/site/SiteList.php b/includes/site/SiteList.php
index b0d1f95b..2d9f22dd 100644
--- a/includes/site/SiteList.php
+++ b/includes/site/SiteList.php
@@ -27,13 +27,12 @@
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
class SiteList extends GenericArrayObject {
-
/**
* Internal site identifiers pointing to their sites offset value.
*
* @since 1.21
*
- * @var array of integer
+ * @var array Array of integer
*/
protected $byInternalId = array();
@@ -42,11 +41,21 @@ class SiteList extends GenericArrayObject {
*
* @since 1.21
*
- * @var array of string
+ * @var array Array of string
*/
protected $byGlobalId = array();
/**
+ * Navigational site identifiers alias inter-language prefixes
+ * pointing to their sites offset value.
+ *
+ * @since 1.23
+ *
+ * @var array Array of string
+ */
+ protected $byNavigationId = array();
+
+ /**
* @see GenericArrayObject::getObjectType
*
* @since 1.21
@@ -65,7 +74,7 @@ class SiteList extends GenericArrayObject {
* @param int|string $index
* @param Site $site
*
- * @return boolean
+ * @return bool
*/
protected function preSetElement( $index, $site ) {
if ( $this->hasSite( $site->getGlobalId() ) ) {
@@ -75,6 +84,11 @@ class SiteList extends GenericArrayObject {
$this->byGlobalId[$site->getGlobalId()] = $index;
$this->byInternalId[$site->getInternalId()] = $index;
+ $ids = $site->getNavigationIds();
+ foreach ( $ids as $navId ) {
+ $this->byNavigationId[$navId] = $index;
+ }
+
return true;
}
@@ -94,6 +108,11 @@ class SiteList extends GenericArrayObject {
unset( $this->byGlobalId[$site->getGlobalId()] );
unset( $this->byInternalId[$site->getInternalId()] );
+
+ $ids = $site->getNavigationIds();
+ foreach ( $ids as $navId ) {
+ unset( $this->byNavigationId[$navId] );
+ }
}
parent::offsetUnset( $index );
@@ -116,7 +135,7 @@ class SiteList extends GenericArrayObject {
*
* @param string $globalSiteId
*
- * @return boolean
+ * @return bool
*/
public function hasSite( $globalSiteId ) {
return array_key_exists( $globalSiteId, $this->byGlobalId );
@@ -153,7 +172,7 @@ class SiteList extends GenericArrayObject {
*
* @since 1.21
*
- * @return boolean
+ * @return bool
*/
public function isEmpty() {
return $this->byGlobalId === array();
@@ -162,9 +181,9 @@ class SiteList extends GenericArrayObject {
/**
* Returns if the list contains the site with the provided site id.
*
- * @param integer $id
+ * @param int $id
*
- * @return boolean
+ * @return bool
*/
public function hasInternalId( $id ) {
return array_key_exists( $id, $this->byInternalId );
@@ -176,7 +195,7 @@ class SiteList extends GenericArrayObject {
*
* @since 1.21
*
- * @param integer $id
+ * @param int $id
*
* @return Site
*/
@@ -190,13 +209,50 @@ class SiteList extends GenericArrayObject {
*
* @since 1.21
*
- * @param integer $id
+ * @param int $id
*/
public function removeSiteByInternalId( $id ) {
$this->offsetUnset( $this->byInternalId[$id] );
}
/**
+ * Returns if the list contains the site with the provided navigational site id.
+ *
+ * @param string $id
+ *
+ * @return bool
+ */
+ public function hasNavigationId( $id ) {
+ return array_key_exists( $id, $this->byNavigationId );
+ }
+
+ /**
+ * Returns the Site with the provided navigational site id.
+ * The site needs to exist, so if not sure, call has first.
+ *
+ * @since 1.23
+ *
+ * @param string $id
+ *
+ * @return Site
+ */
+ public function getSiteByNavigationId( $id ) {
+ return $this->offsetGet( $this->byNavigationId[$id] );
+ }
+
+ /**
+ * Removes the site with the specified navigational site id.
+ * The site needs to exist, so if not sure, call has first.
+ *
+ * @since 1.23
+ *
+ * @param string $id
+ */
+ public function removeSiteByNavigationId( $id ) {
+ $this->offsetUnset( $this->byNavigationId[$id] );
+ }
+
+ /**
* Sets a site in the list. If the site was not there,
* it will be added. If it was, it will be updated.
*
@@ -221,7 +277,7 @@ class SiteList extends GenericArrayObject {
$group = new self();
/**
- * @var \Site $site
+ * @var Site $site
*/
foreach ( $this as $site ) {
if ( $site->getGroup() === $groupName ) {
@@ -240,7 +296,7 @@ class SiteList extends GenericArrayObject {
* @var string A string uniquely identifying the version of the serialization structure,
* not including any sub-structures.
*/
- const SERIAL_VERSION_ID = '2013-02-07';
+ const SERIAL_VERSION_ID = '2014-03-17';
/**
* Returns the version ID that identifies the serialization structure used by
@@ -270,6 +326,7 @@ class SiteList extends GenericArrayObject {
array(
'internalIds' => $this->byInternalId,
'globalIds' => $this->byGlobalId,
+ 'navigationIds' => $this->byNavigationId
)
);
}
@@ -288,13 +345,14 @@ class SiteList extends GenericArrayObject {
$this->byInternalId = $serializationData['internalIds'];
$this->byGlobalId = $serializationData['globalIds'];
+ $this->byNavigationId = $serializationData['navigationIds'];
return $serializationData;
}
-
}
/**
- * @deprecated
+ * @deprecated since 1.21
*/
-class SiteArray extends SiteList {}
+class SiteArray extends SiteList {
+}
diff --git a/includes/site/SiteSQLStore.php b/includes/site/SiteSQLStore.php
index 11141e07..d1334680 100644
--- a/includes/site/SiteSQLStore.php
+++ b/includes/site/SiteSQLStore.php
@@ -29,7 +29,6 @@
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
class SiteSQLStore implements SiteStore {
-
/**
* @since 1.21
*
@@ -90,7 +89,7 @@ class SiteSQLStore implements SiteStore {
*
* @see SiteList::getSerialVersionId
*
- * @return String The cache key.
+ * @return string The cache key.
*/
protected function getCacheKey() {
wfProfileIn( __METHOD__ );
@@ -115,7 +114,7 @@ class SiteSQLStore implements SiteStore {
*
* @since 1.21
*
- * @param string $source either 'cache' or 'recache'
+ * @param string $source Either 'cache' or 'recache'
*
* @return SiteList
*/
@@ -169,7 +168,10 @@ class SiteSQLStore implements SiteStore {
}
if ( $siteRow->hasField( 'language' ) ) {
- $site->setLanguageCode( $siteRow->getField( 'language' ) === '' ? null : $siteRow->getField( 'language' ) );
+ $site->setLanguageCode( $siteRow->getField( 'language' ) === ''
+ ? null
+ : $siteRow->getField( 'language' )
+ );
}
if ( $siteRow->hasField( 'source' ) ) {
@@ -193,7 +195,7 @@ class SiteSQLStore implements SiteStore {
*
* @since 1.22
*
- * @param Site
+ * @param Site $site
*
* @return ORMRow
*/
@@ -287,7 +289,7 @@ class SiteSQLStore implements SiteStore {
*
* @param Site $site
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function saveSite( Site $site ) {
return $this->saveSites( array( $site ) );
@@ -300,7 +302,7 @@ class SiteSQLStore implements SiteStore {
*
* @param Site[] $sites
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function saveSites( array $sites ) {
wfProfileIn( __METHOD__ );
@@ -312,11 +314,7 @@ class SiteSQLStore implements SiteStore {
$dbw = $this->sitesTable->getWriteDbConnection();
- $trx = $dbw->trxLevel();
-
- if ( $trx == 0 ) {
- $dbw->begin( __METHOD__ );
- }
+ $dbw->startAtomic( __METHOD__ );
$success = true;
@@ -358,9 +356,7 @@ class SiteSQLStore implements SiteStore {
);
}
- if ( $trx == 0 ) {
- $dbw->commit( __METHOD__ );
- }
+ $dbw->endAtomic( __METHOD__ );
// purge cache
$this->reset();
@@ -390,24 +386,16 @@ class SiteSQLStore implements SiteStore {
*
* @see SiteStore::clear()
*
- * @return bool success
+ * @return bool Success
*/
public function clear() {
wfProfileIn( __METHOD__ );
$dbw = $this->sitesTable->getWriteDbConnection();
- $trx = $dbw->trxLevel();
-
- if ( $trx == 0 ) {
- $dbw->begin( __METHOD__ );
- }
-
+ $dbw->startAtomic( __METHOD__ );
$ok = $dbw->delete( 'sites', '*', __METHOD__ );
$ok = $dbw->delete( 'site_identifiers', '*', __METHOD__ ) && $ok;
-
- if ( $trx == 0 ) {
- $dbw->commit( __METHOD__ );
- }
+ $dbw->endAtomic( __METHOD__);
$this->reset();
@@ -458,7 +446,7 @@ class SiteSQLStore implements SiteStore {
}
/**
- * @deprecated
+ * @deprecated since 1.21
*/
class Sites extends SiteSQLStore {
@@ -466,9 +454,9 @@ class Sites extends SiteSQLStore {
* Factory for creating new site objects.
*
* @since 1.21
- * @deprecated
+ * @deprecated since 1.21
*
- * @param string|boolean false $globalId
+ * @param string|bool $globalId
*
* @return Site
*/
@@ -483,7 +471,7 @@ class Sites extends SiteSQLStore {
}
/**
- * @deprecated
+ * @deprecated since 1.21
* @return SiteStore
*/
public static function singleton() {
@@ -497,11 +485,11 @@ class Sites extends SiteSQLStore {
}
/**
- * @deprecated
+ * @deprecated since 1.21
+ * @param string $group
* @return SiteList
*/
public function getSiteGroup( $group ) {
return $this->getSites()->getGroup( $group );
}
-
}
diff --git a/includes/site/SiteStore.php b/includes/site/SiteStore.php
index 52ba8fbf..537f1ccb 100644
--- a/includes/site/SiteStore.php
+++ b/includes/site/SiteStore.php
@@ -35,7 +35,7 @@ interface SiteStore {
*
* @param Site $site
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function saveSite( Site $site );
@@ -46,7 +46,7 @@ interface SiteStore {
*
* @param Site[] $sites
*
- * @return boolean Success indicator
+ * @return bool Success indicator
*/
public function saveSites( array $sites );
@@ -56,7 +56,7 @@ interface SiteStore {
* @since 1.21
*
* @param string $globalId
- * @param string $source either 'cache' or 'recache'.
+ * @param string $source Either 'cache' or 'recache'.
* If 'cache', the values are allowed (but not obliged) to come from a cache.
*
* @return Site|null
@@ -70,7 +70,7 @@ interface SiteStore {
*
* @since 1.21
*
- * @param string $source either 'cache' or 'recache'.
+ * @param string $source Either 'cache' or 'recache'.
* If 'cache', the values are allowed (but not obliged) to come from a cache.
*
* @return SiteList
diff --git a/includes/Skin.php b/includes/skins/Skin.php
index 5801806c..2f6a7101 100644
--- a/includes/Skin.php
+++ b/includes/skins/Skin.php
@@ -26,63 +26,37 @@
/**
* The main skin class which provides methods and properties for all other skins.
- * This base class is also the "Standard" skin.
*
* See docs/skin.txt for more information.
*
* @ingroup Skins
*/
abstract class Skin extends ContextSource {
- protected $skinname = 'standard';
+ protected $skinname = null;
protected $mRelevantTitle = null;
protected $mRelevantUser = null;
/**
+ * @var string Stylesheets set to use. Subdirectory in skins/ where various stylesheets are
+ * located. Only needs to be set if you intend to use the getSkinStylePath() method.
+ */
+ public $stylename = null;
+
+ /**
* Fetch the set of available skins.
- * @return array associative array of strings
+ * @return array Associative array of strings
*/
static function getSkinNames() {
- global $wgValidSkinNames;
- static $skinsInitialised = false;
-
- if ( !$skinsInitialised || !count( $wgValidSkinNames ) ) {
- # Get a list of available skins
- # Build using the regular expression '^(.*).php$'
- # Array keys are all lower case, array value keep the case used by filename
- #
- wfProfileIn( __METHOD__ . '-init' );
-
- global $wgStyleDirectory;
-
- $skinDir = dir( $wgStyleDirectory );
-
- if ( $skinDir !== false && $skinDir !== null ) {
- # while code from www.php.net
- while ( false !== ( $file = $skinDir->read() ) ) {
- // Skip non-PHP files, hidden files, and '.dep' includes
- $matches = array();
-
- if ( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
- $aSkin = $matches[1];
- $wgValidSkinNames[strtolower( $aSkin )] = $aSkin;
- }
- }
- $skinDir->close();
- }
- $skinsInitialised = true;
- wfProfileOut( __METHOD__ . '-init' );
- }
- return $wgValidSkinNames;
+ return SkinFactory::getDefaultInstance()->getSkinNames();
}
/**
* Fetch the skinname messages for available skins.
- * @return array of strings
+ * @return string[]
*/
static function getSkinNameMessages() {
$messages = array();
foreach ( self::getSkinNames() as $skinKey => $skinName ) {
- // Messages: skinname-cologneblue, skinname-monobook, skinname-modern, skinname-vector
$messages[] = "skinname-$skinKey";
}
return $messages;
@@ -92,9 +66,10 @@ abstract class Skin extends ContextSource {
* Fetch the list of user-selectable skins in regards to $wgSkipSkins.
* Useful for Special:Preferences and other places where you
* only want to show skins users _can_ use.
- * @return array of strings
+ * @return string[]
+ * @since 1.23
*/
- public static function getUsableSkins() {
+ public static function getAllowedSkins() {
global $wgSkipSkins;
$allowedSkins = self::getSkinNames();
@@ -107,21 +82,38 @@ abstract class Skin extends ContextSource {
}
/**
+ * @deprecated since 1.23, use getAllowedSkins
+ * @return string[]
+ */
+ public static function getUsableSkins() {
+ wfDeprecated( __METHOD__, '1.23' );
+ return self::getAllowedSkins();
+ }
+
+ /**
* Normalize a skin preference value to a form that can be loaded.
- * If a skin can't be found, it will fall back to the configured
- * default (or the old 'Classic' skin if that's broken).
- * @param string $key 'monobook', 'standard', etc.
+ *
+ * If a skin can't be found, it will fall back to the configured default ($wgDefaultSkin), or the
+ * hardcoded default ($wgFallbackSkin) if the default skin is unavailable too.
+ *
+ * @param string $key 'monobook', 'vector', etc.
* @return string
*/
static function normalizeKey( $key ) {
- global $wgDefaultSkin;
+ global $wgDefaultSkin, $wgFallbackSkin;
$skinNames = Skin::getSkinNames();
+ // Make keys lowercase for case-insensitive matching.
+ $skinNames = array_change_key_case( $skinNames, CASE_LOWER );
+ $key = strtolower( $key );
+ $defaultSkin = strtolower( $wgDefaultSkin );
+ $fallbackSkin = strtolower( $wgFallbackSkin );
+
if ( $key == '' || $key == 'default' ) {
// Don't return the default immediately;
// in a misconfiguration we need to fall back.
- $key = $wgDefaultSkin;
+ $key = $defaultSkin;
}
if ( isset( $skinNames[$key] ) ) {
@@ -131,7 +123,7 @@ abstract class Skin extends ContextSource {
// Older versions of the software used a numeric setting
// in the user preferences.
$fallback = array(
- 0 => $wgDefaultSkin,
+ 0 => $defaultSkin,
2 => 'cologneblue'
);
@@ -141,54 +133,39 @@ abstract class Skin extends ContextSource {
if ( isset( $skinNames[$key] ) ) {
return $key;
- } elseif ( isset( $skinNames[$wgDefaultSkin] ) ) {
- return $wgDefaultSkin;
+ } elseif ( isset( $skinNames[$defaultSkin] ) ) {
+ return $defaultSkin;
} else {
- return 'vector';
+ return $fallbackSkin;
}
}
/**
* Factory method for loading a skin of a given type
- * @param string $key 'monobook', 'standard', etc.
+ * @param string $key 'monobook', 'vector', etc.
* @return Skin
+ * @deprecated since 1.24; Use SkinFactory instead
*/
static function &newFromKey( $key ) {
- global $wgStyleDirectory;
+ wfDeprecated( __METHOD__, '1.24' );
$key = Skin::normalizeKey( $key );
+ $factory = SkinFactory::getDefaultInstance();
- $skinNames = Skin::getSkinNames();
- $skinName = $skinNames[$key];
- $className = "Skin{$skinName}";
-
- # Grab the skin class and initialise it.
- if ( !class_exists( $className ) ) {
-
- require_once "{$wgStyleDirectory}/{$skinName}.php";
-
- # Check if we got if not fallback to default skin
- if ( !class_exists( $className ) ) {
- # DO NOT die if the class isn't found. This breaks maintenance
- # scripts and can cause a user account to be unrecoverable
- # except by SQL manipulation if a previously valid skin name
- # is no longer valid.
- wfDebug( "Skin class does not exist: $className\n" );
- $className = 'SkinVector';
- require_once "{$wgStyleDirectory}/Vector.php";
- }
- }
- $skin = new $className( $key );
+ // normalizeKey() guarantees that a skin with this key will exist.
+ $skin = $factory->makeSkin( $key );
return $skin;
}
- /** @return string skin name */
+ /**
+ * @return string Skin name
+ */
public function getSkinName() {
return $this->skinname;
}
/**
- * @param $out OutputPage
+ * @param OutputPage $out
*/
function initPage( OutputPage $out ) {
wfProfileIn( __METHOD__ );
@@ -202,7 +179,7 @@ abstract class Skin extends ContextSource {
* Defines the ResourceLoader modules that should be added to the skin
* It is recommended that skins wishing to override call parent::getDefaultModules()
* and substitute out any modules they wish to change by using a key to look them up
- * @return Array of modules with helper keys for easy overriding
+ * @return array Array of modules with helper keys for easy overriding
*/
public function getDefaultModules() {
global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
@@ -243,9 +220,7 @@ abstract class Skin extends ContextSource {
$modules['watch'][] = 'mediawiki.page.watch.ajax';
}
- if ( !$user->getOption( 'disablesuggest', false ) ) {
- $modules['search'][] = 'mediawiki.searchSuggest';
- }
+ $modules['search'][] = 'mediawiki.searchSuggest';
}
}
@@ -278,6 +253,8 @@ abstract class Skin extends ContextSource {
$titles[] = $this->getTitle()->getTalkPage();
}
+ wfRunHooks( 'SkinPreloadExistence', array( &$titles, $this ) );
+
$lb = new LinkBatch( $titles );
$lb->setCaller( __METHOD__ );
$lb->execute();
@@ -286,7 +263,7 @@ abstract class Skin extends ContextSource {
/**
* Get the current revision ID
*
- * @return Integer
+ * @return int
*/
public function getRevisionId() {
return $this->getOutput()->getRevisionId();
@@ -295,7 +272,7 @@ abstract class Skin extends ContextSource {
/**
* Whether the revision displayed is the latest revision of the page
*
- * @return Boolean
+ * @return bool
*/
public function isRevisionCurrent() {
$revID = $this->getRevisionId();
@@ -305,7 +282,7 @@ abstract class Skin extends ContextSource {
/**
* Set the "relevant" title
* @see self::getRelevantTitle()
- * @param $t Title object to use
+ * @param Title $t
*/
public function setRelevantTitle( $t ) {
$this->mRelevantTitle = $t;
@@ -331,7 +308,7 @@ abstract class Skin extends ContextSource {
/**
* Set the "relevant" user
* @see self::getRelevantUser()
- * @param $u User object to use
+ * @param User $u
*/
public function setRelevantUser( $u ) {
$this->mRelevantUser = $u;
@@ -367,12 +344,12 @@ abstract class Skin extends ContextSource {
/**
* Outputs the HTML generated by other functions.
- * @param $out OutputPage
+ * @param OutputPage $out
*/
abstract function outputPage( OutputPage $out = null );
/**
- * @param $data array
+ * @param array $data
* @return string
*/
static function makeVariablesScript( $data ) {
@@ -386,21 +363,6 @@ abstract class Skin extends ContextSource {
}
/**
- * Make a "<script>" tag containing global variables
- *
- * @deprecated in 1.19
- * @param $unused
- * @return string HTML fragment
- */
- public static function makeGlobalVariablesScript( $unused ) {
- global $wgOut;
-
- wfDeprecated( __METHOD__, '1.19' );
-
- return self::makeVariablesScript( $wgOut->getJSVars() );
- }
-
- /**
* Get the query to generate a dynamic stylesheet
*
* @return array
@@ -422,15 +384,15 @@ abstract class Skin extends ContextSource {
* Calling this method with an $out of anything but the same OutputPage
* inside ->getOutput() is deprecated. The $out arg is kept
* for compatibility purposes with skins.
- * @param $out OutputPage
+ * @param OutputPage $out
* @todo delete
*/
abstract function setupSkinUserCss( OutputPage $out );
/**
* TODO: document
- * @param $title Title
- * @return String
+ * @param Title $title
+ * @return string
*/
function getPageClasses( $title ) {
$numeric = 'ns-' . $title->getNamespace();
@@ -456,11 +418,24 @@ abstract class Skin extends ContextSource {
}
/**
+ * Return values for <html> element
+ * @return array Array of associative name-to-value elements for <html> element
+ */
+ public function getHtmlElementAttributes() {
+ $lang = $this->getLanguage();
+ return array(
+ 'lang' => $lang->getHtmlCode(),
+ 'dir' => $lang->getDir(),
+ 'class' => 'client-nojs',
+ );
+ }
+
+ /**
* This will be called by OutputPage::headElement when it is creating the
* "<body>" tag, skins can override it if they have a need to add in any
* body attributes or classes of their own.
- * @param $out OutputPage
- * @param $bodyAttrs Array
+ * @param OutputPage $out
+ * @param array $bodyAttrs
*/
function addToBodyAttributes( $out, &$bodyAttrs ) {
// does nothing by default
@@ -468,7 +443,7 @@ abstract class Skin extends ContextSource {
/**
* URL to the logo
- * @return String
+ * @return string
*/
function getLogo() {
global $wgLogo;
@@ -542,8 +517,8 @@ abstract class Skin extends ContextSource {
/**
* Render the array as a series of links.
- * @param array $tree categories tree returned by Title::getParentCategoryTree
- * @return String separated by &gt;, terminate with "\n"
+ * @param array $tree Categories tree returned by Title::getParentCategoryTree
+ * @return string Separated by &gt;, terminate with "\n"
*/
function drawCategoryBrowser( $tree ) {
$return = '';
@@ -599,7 +574,7 @@ abstract class Skin extends ContextSource {
* The output of this function gets processed in SkinTemplate::outputPage() for
* the SkinTemplate based skins, all other skins should directly echo it.
*
- * @return String, empty by default, if not changed by any hook function.
+ * @return string Empty by default, if not changed by any hook function.
*/
protected function afterContentHook() {
$data = '';
@@ -625,7 +600,7 @@ abstract class Skin extends ContextSource {
/**
* Generate debug data HTML for displaying at the bottom of the main content
* area.
- * @return String HTML containing debug data, if enabled (otherwise empty).
+ * @return string HTML containing debug data, if enabled (otherwise empty).
*/
protected function generateDebugHTML() {
return MWDebug::getHTMLDebugLog();
@@ -634,7 +609,7 @@ abstract class Skin extends ContextSource {
/**
* This gets called shortly before the "</body>" tag.
*
- * @return String HTML-wrapped JS code to be put before "</body>"
+ * @return string HTML-wrapped JS code to be put before "</body>"
*/
function bottomScripts() {
// TODO and the suckage continues. This function is really just a wrapper around
@@ -655,16 +630,19 @@ abstract class Skin extends ContextSource {
function printSource() {
$oldid = $this->getRevisionId();
if ( $oldid ) {
- $url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL( 'oldid=' . $oldid ) ) );
+ $canonicalUrl = $this->getTitle()->getCanonicalURL( 'oldid=' . $oldid );
+ $url = htmlspecialchars( wfExpandIRI( $canonicalUrl ) );
} else {
// oldid not available for non existing pages
$url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL() ) );
}
- return $this->msg( 'retrievedfrom', '<a href="' . $url . '">' . $url . '</a>' )->text();
+
+ return $this->msg( 'retrievedfrom', '<a dir="ltr" href="' . $url
+ . '">' . $url . '</a>' )->text();
}
/**
- * @return String
+ * @return string
*/
function getUndeleteLink() {
$action = $this->getRequest()->getVal( 'action', 'view' );
@@ -695,7 +673,6 @@ abstract class Skin extends ContextSource {
* @return string
*/
function subPageSubtitle() {
- global $wgLang;
$out = $this->getOutput();
$subpages = '';
@@ -711,6 +688,7 @@ abstract class Skin extends ContextSource {
$c = 0;
$growinglink = '';
$display = '';
+ $lang = $this->getLanguage();
foreach ( $links as $link ) {
$growinglink .= $link;
@@ -726,7 +704,7 @@ abstract class Skin extends ContextSource {
$c++;
if ( $c > 1 ) {
- $subpages .= $wgLang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
+ $subpages .= $lang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
} else {
$subpages .= '&lt; ';
}
@@ -746,7 +724,7 @@ abstract class Skin extends ContextSource {
/**
* Returns true if the IP should be shown in the header
- * @return Bool
+ * @return bool
*/
function showIPinHeader() {
global $wgShowIPinHeader;
@@ -754,7 +732,7 @@ abstract class Skin extends ContextSource {
}
/**
- * @return String
+ * @return string
*/
function getSearchLink() {
$searchPage = SpecialPage::getTitleFor( 'Search' );
@@ -769,14 +747,16 @@ abstract class Skin extends ContextSource {
}
/**
- * @param $type string
+ * @param string $type
* @return string
*/
function getCopyright( $type = 'detect' ) {
- global $wgRightsPage, $wgRightsUrl, $wgRightsText, $wgContLang;
+ global $wgRightsPage, $wgRightsUrl, $wgRightsText;
if ( $type == 'detect' ) {
- if ( !$this->isRevisionCurrent() && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled() ) {
+ if ( !$this->isRevisionCurrent()
+ && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled()
+ ) {
$type = 'history';
} else {
$type = 'normal';
@@ -802,20 +782,15 @@ abstract class Skin extends ContextSource {
}
// Allow for site and per-namespace customization of copyright notice.
+ // @todo Remove deprecated $forContent param from hook handlers and then remove here.
$forContent = true;
- wfRunHooks( 'SkinCopyrightFooter', array( $this->getTitle(), $type, &$msg, &$link, &$forContent ) );
+ wfRunHooks(
+ 'SkinCopyrightFooter',
+ array( $this->getTitle(), $type, &$msg, &$link, &$forContent )
+ );
- $msgObj = $this->msg( $msg )->rawParams( $link );
- if ( $forContent ) {
- $msg = $msgObj->inContentLanguage()->text();
- if ( $this->getLanguage()->getCode() !== $wgContLang->getCode() ) {
- $msg = Html::rawElement( 'span', array( 'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ), $msg );
- }
- return $msg;
- } else {
- return $msgObj->text();
- }
+ return $this->msg( $msg )->rawParams( $link )->text();
}
/**
@@ -826,7 +801,7 @@ abstract class Skin extends ContextSource {
$out = '';
- if ( isset( $wgCopyrightIcon ) && $wgCopyrightIcon ) {
+ if ( $wgCopyrightIcon ) {
$out = $wgCopyrightIcon;
} elseif ( $wgRightsIcon ) {
$icon = htmlspecialchars( $wgRightsIcon );
@@ -852,10 +827,11 @@ abstract class Skin extends ContextSource {
* @return string
*/
function getPoweredBy() {
- global $wgStylePath;
+ global $wgResourceBasePath;
- $url = htmlspecialchars( "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" );
- $text = '<a href="//www.mediawiki.org/"><img src="' . $url . '" height="31" width="88" alt="Powered by MediaWiki" /></a>';
+ $url = htmlspecialchars( "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png" );
+ $text = '<a href="//www.mediawiki.org/"><img src="' . $url
+ . '" height="31" width="88" alt="Powered by MediaWiki" /></a>';
wfRunHooks( 'SkinGetPoweredBy', array( &$text, $this ) );
return $text;
}
@@ -863,7 +839,7 @@ abstract class Skin extends ContextSource {
/**
* Get the timestamp of the latest revision, formatted in user language
*
- * @return String
+ * @return string
*/
protected function lastModified() {
$timestamp = $this->getOutput()->getRevisionTimestamp();
@@ -889,7 +865,7 @@ abstract class Skin extends ContextSource {
}
/**
- * @param $align string
+ * @param string $align
* @return string
*/
function logoText( $align = '' ) {
@@ -911,9 +887,11 @@ abstract class Skin extends ContextSource {
/**
* Renders a $wgFooterIcons icon according to the method's arguments
- * @param array $icon The icon to build the html for, see $wgFooterIcons for the format of this array
- * @param bool|String $withImage Whether to use the icon's image or output a text-only footericon
- * @return String HTML
+ * @param array $icon The icon to build the html for, see $wgFooterIcons
+ * for the format of this array.
+ * @param bool|string $withImage Whether to use the icon's image or output
+ * a text-only footericon.
+ * @return string HTML
*/
function makeFooterIcon( $icon, $withImage = 'withImage' ) {
if ( is_string( $icon ) ) {
@@ -922,7 +900,8 @@ abstract class Skin extends ContextSource {
$url = isset( $icon["url"] ) ? $icon["url"] : null;
unset( $icon["url"] );
if ( isset( $icon["src"] ) && $withImage === 'withImage' ) {
- $html = Html::element( 'img', $icon ); // do this the lazy way, just pass icon data as an attribute array
+ // do this the lazy way, just pass icon data as an attribute array
+ $html = Html::element( 'img', $icon );
} else {
$html = htmlspecialchars( $icon["alt"] );
}
@@ -947,9 +926,10 @@ abstract class Skin extends ContextSource {
}
/**
- * @param $desc
- * @param $page
- * @return string
+ * Returns an HTML link for use in the footer
+ * @param string $desc The i18n message key for the link text
+ * @param string $page The i18n message key for the page to link to
+ * @return string HTML anchor
*/
public function footerLink( $desc, $page ) {
// if the link description has been set to "-" in the default language,
@@ -971,7 +951,7 @@ abstract class Skin extends ContextSource {
/**
* Gets the link to the wiki's privacy policy page.
- * @return String HTML
+ * @return string HTML
*/
function privacyLink() {
return $this->footerLink( 'privacy', 'privacypage' );
@@ -979,7 +959,7 @@ abstract class Skin extends ContextSource {
/**
* Gets the link to the wiki's about page.
- * @return String HTML
+ * @return string HTML
*/
function aboutLink() {
return $this->footerLink( 'aboutsite', 'aboutpage' );
@@ -987,7 +967,7 @@ abstract class Skin extends ContextSource {
/**
* Gets the link to the wiki's general disclaimers page.
- * @return String HTML
+ * @return string HTML
*/
function disclaimerLink() {
return $this->footerLink( 'disclaimers', 'disclaimerpage' );
@@ -1011,7 +991,7 @@ abstract class Skin extends ContextSource {
}
/**
- * @param $id User|int
+ * @param User|int $id
* @return bool
*/
function showEmailUser( $id ) {
@@ -1020,39 +1000,54 @@ abstract class Skin extends ContextSource {
} else {
$targetUser = User::newFromId( $id );
}
- return $this->getUser()->canSendEmail() && # the sending user must have a confirmed email address
- $targetUser->canReceiveEmail(); # the target user must have a confirmed email address and allow emails from users
+
+ # The sending user must have a confirmed email address and the target
+ # user must have a confirmed email address and allow emails from users.
+ return $this->getUser()->canSendEmail() &&
+ $targetUser->canReceiveEmail();
}
/**
- * Return a fully resolved style path url to images or styles stored in the common folder.
- * This method returns a url resolved using the configured skin style path
- * and includes the style version inside of the url.
+ * This function previously returned a fully resolved style path URL to images or styles stored in
+ * the legacy skins/common/ directory.
+ *
+ * That directory has been removed in 1.24 and the function always returns an empty string.
+ *
+ * @deprecated since 1.24
* @param string $name The name or path of a skin resource file
- * @return String The fully resolved style path url including styleversion
+ * @return string Empty string
*/
function getCommonStylePath( $name ) {
- global $wgStylePath, $wgStyleVersion;
- return "$wgStylePath/common/$name?$wgStyleVersion";
+ wfDeprecated( __METHOD__, '1.24' );
+ return '';
}
/**
* Return a fully resolved style path url to images or styles stored in the current skins's folder.
* This method returns a url resolved using the configured skin style path
* and includes the style version inside of the url.
+ *
+ * Requires $stylename to be set, otherwise throws MWException.
+ *
* @param string $name The name or path of a skin resource file
- * @return String The fully resolved style path url including styleversion
+ * @return string The fully resolved style path url including styleversion
*/
function getSkinStylePath( $name ) {
global $wgStylePath, $wgStyleVersion;
+
+ if ( $this->stylename === null ) {
+ $class = get_class( $this );
+ throw new MWException( "$class::\$stylename must be set to use getSkinStylePath()" );
+ }
+
return "$wgStylePath/{$this->stylename}/$name?$wgStyleVersion";
}
/* these are used extensively in SkinTemplate, but also some other places */
/**
- * @param $urlaction string
- * @return String
+ * @param string $urlaction
+ * @return string
*/
static function makeMainPageUrl( $urlaction = '' ) {
$title = Title::newMainPage();
@@ -1069,8 +1064,8 @@ abstract class Skin extends ContextSource {
*
* @param string $name Name of the Special page
* @param string $urlaction Query to append
- * @param $proto Protocol to use or null for a local URL
- * @return String
+ * @param string|null $proto Protocol to use or null for a local URL
+ * @return string
*/
static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) {
$title = SpecialPage::getSafeTitleFor( $name );
@@ -1082,10 +1077,10 @@ abstract class Skin extends ContextSource {
}
/**
- * @param $name string
- * @param $subpage string
- * @param $urlaction string
- * @return String
+ * @param string $name
+ * @param string $subpage
+ * @param string $urlaction
+ * @return string
*/
static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
$title = SpecialPage::getSafeTitleFor( $name, $subpage );
@@ -1093,9 +1088,9 @@ abstract class Skin extends ContextSource {
}
/**
- * @param $name string
- * @param $urlaction string
- * @return String
+ * @param string $name
+ * @param string $urlaction
+ * @return string
*/
static function makeI18nUrl( $name, $urlaction = '' ) {
$title = Title::newFromText( wfMessage( $name )->inContentLanguage()->text() );
@@ -1104,9 +1099,9 @@ abstract class Skin extends ContextSource {
}
/**
- * @param $name string
- * @param $urlaction string
- * @return String
+ * @param string $name
+ * @param string $urlaction
+ * @return string
*/
static function makeUrl( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
@@ -1118,8 +1113,8 @@ abstract class Skin extends ContextSource {
/**
* If url string starts with http, consider as external URL, else
* internal
- * @param $name String
- * @return String URL
+ * @param string $name
+ * @return string URL
*/
static function makeInternalOrExternalUrl( $name ) {
if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $name ) ) {
@@ -1131,10 +1126,10 @@ abstract class Skin extends ContextSource {
/**
* this can be passed the NS number as defined in Language.php
- * @param $name
- * @param $urlaction string
- * @param $namespace int
- * @return String
+ * @param string $name
+ * @param string $urlaction
+ * @param int $namespace
+ * @return string
*/
static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
$title = Title::makeTitleSafe( $namespace, $name );
@@ -1145,8 +1140,8 @@ abstract class Skin extends ContextSource {
/**
* these return an array with the 'href' and boolean 'exists'
- * @param $name
- * @param $urlaction string
+ * @param string $name
+ * @param string $urlaction
* @return array
*/
static function makeUrlDetails( $name, $urlaction = '' ) {
@@ -1162,8 +1157,8 @@ abstract class Skin extends ContextSource {
/**
* Make URL details where the article exists (or at least it's convenient to think so)
* @param string $name Article name
- * @param $urlaction String
- * @return Array
+ * @param string $urlaction
+ * @return array
*/
static function makeKnownUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
@@ -1178,8 +1173,8 @@ abstract class Skin extends ContextSource {
/**
* make sure we have some title to operate on
*
- * @param $title Title
- * @param $name string
+ * @param Title $title
+ * @param string $name
*/
static function checkTitle( &$title, $name ) {
if ( !is_object( $title ) ) {
@@ -1220,6 +1215,8 @@ abstract class Skin extends ContextSource {
if ( $wgEnableSidebarCache ) {
$cachedsidebar = $wgMemc->get( $key );
if ( $cachedsidebar ) {
+ wfRunHooks( 'SidebarBeforeOutput', array( $this, &$cachedsidebar ) );
+
wfProfileOut( __METHOD__ );
return $cachedsidebar;
}
@@ -1233,17 +1230,20 @@ abstract class Skin extends ContextSource {
$wgMemc->set( $key, $bar, $wgSidebarCacheExpiry );
}
+ wfRunHooks( 'SidebarBeforeOutput', array( $this, &$bar ) );
+
wfProfileOut( __METHOD__ );
return $bar;
}
+
/**
* Add content from a sidebar system message
* Currently only used for MediaWiki:Sidebar (but may be used by Extensions)
*
* This is just a wrapper around addToSidebarPlain() for backwards compatibility
*
- * @param $bar array
- * @param $message String
+ * @param array $bar
+ * @param string $message
*/
function addToSidebar( &$bar, $message ) {
$this->addToSidebarPlain( $bar, wfMessage( $message )->inContentLanguage()->plain() );
@@ -1252,9 +1252,9 @@ abstract class Skin extends ContextSource {
/**
* Add content from plain text
* @since 1.17
- * @param $bar array
- * @param $text string
- * @return Array
+ * @param array $bar
+ * @param string $text
+ * @return array
*/
function addToSidebarPlain( &$bar, $text ) {
$lines = explode( "\n", $text );
@@ -1360,7 +1360,7 @@ abstract class Skin extends ContextSource {
/**
* Gets new talk page messages for the current user and returns an
* appropriate alert message (or an empty string if there are no messages)
- * @return String
+ * @return string
*/
function getNewtalks() {
@@ -1379,61 +1379,58 @@ abstract class Skin extends ContextSource {
if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
$uTalkTitle = $user->getTalkPage();
-
- if ( !$uTalkTitle->equals( $out->getTitle() ) ) {
- $lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
- $nofAuthors = 0;
- if ( $lastSeenRev !== null ) {
- $plural = true; // Default if we have a last seen revision: if unknown, use plural
- $latestRev = Revision::newFromTitle( $uTalkTitle, false, Revision::READ_NORMAL );
- if ( $latestRev !== null ) {
- // Singular if only 1 unseen revision, plural if several unseen revisions.
- $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
- $nofAuthors = $uTalkTitle->countAuthorsBetween(
- $lastSeenRev, $latestRev, 10, 'include_new' );
- }
- } else {
- // Singular if no revision -> diff link will show latest change only in any case
- $plural = false;
+ $lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
+ $nofAuthors = 0;
+ if ( $lastSeenRev !== null ) {
+ $plural = true; // Default if we have a last seen revision: if unknown, use plural
+ $latestRev = Revision::newFromTitle( $uTalkTitle, false, Revision::READ_NORMAL );
+ if ( $latestRev !== null ) {
+ // Singular if only 1 unseen revision, plural if several unseen revisions.
+ $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
+ $nofAuthors = $uTalkTitle->countAuthorsBetween(
+ $lastSeenRev, $latestRev, 10, 'include_new' );
}
- $plural = $plural ? 2 : 1;
- // 2 signifies "more than one revision". We don't know how many, and even if we did,
- // the number of revisions or authors is not necessarily the same as the number of
- // "messages".
- $newMessagesLink = Linker::linkKnown(
- $uTalkTitle,
- $this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
- array(),
- array( 'redirect' => 'no' )
- );
+ } else {
+ // Singular if no revision -> diff link will show latest change only in any case
+ $plural = false;
+ }
+ $plural = $plural ? 999 : 1;
+ // 999 signifies "more than one revision". We don't know how many, and even if we did,
+ // the number of revisions or authors is not necessarily the same as the number of
+ // "messages".
+ $newMessagesLink = Linker::linkKnown(
+ $uTalkTitle,
+ $this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
+ array(),
+ array( 'redirect' => 'no' )
+ );
- $newMessagesDiffLink = Linker::linkKnown(
- $uTalkTitle,
- $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(),
- array(),
- $lastSeenRev !== null
- ? array( 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' )
- : array( 'diff' => 'cur' )
- );
+ $newMessagesDiffLink = Linker::linkKnown(
+ $uTalkTitle,
+ $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(),
+ array(),
+ $lastSeenRev !== null
+ ? array( 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' )
+ : array( 'diff' => 'cur' )
+ );
- if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
- $newMessagesAlert = $this->msg(
- 'youhavenewmessagesfromusers',
- $newMessagesLink,
- $newMessagesDiffLink
- )->numParams( $nofAuthors );
- } else {
- // $nofAuthors === 11 signifies "11 or more" ("more than 10")
- $newMessagesAlert = $this->msg(
- $nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
- $newMessagesLink,
- $newMessagesDiffLink
- );
- }
- $newMessagesAlert = $newMessagesAlert->text();
- # Disable Squid cache
- $out->setSquidMaxage( 0 );
+ if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
+ $newMessagesAlert = $this->msg(
+ 'youhavenewmessagesfromusers',
+ $newMessagesLink,
+ $newMessagesDiffLink
+ )->numParams( $nofAuthors, $plural );
+ } else {
+ // $nofAuthors === 11 signifies "11 or more" ("more than 10")
+ $newMessagesAlert = $this->msg(
+ $nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
+ $newMessagesLink,
+ $newMessagesDiffLink
+ )->numParams( $plural );
}
+ $newMessagesAlert = $newMessagesAlert->text();
+ # Disable Squid cache
+ $out->setSquidMaxage( 0 );
} elseif ( count( $newtalks ) ) {
$sep = $this->msg( 'newtalkseparator' )->escaped();
$msgs = array();
@@ -1455,8 +1452,8 @@ abstract class Skin extends ContextSource {
/**
* Get a cached notice
*
- * @param string $name message name, or 'default' for $wgSiteNotice
- * @return String: HTML fragment
+ * @param string $name Message name, or 'default' for $wgSiteNotice
+ * @return string HTML fragment
*/
private function getCachedNotice( $name ) {
global $wgRenderHashAppend, $parserMemc, $wgContLang;
@@ -1510,7 +1507,7 @@ abstract class Skin extends ContextSource {
/**
* Get a notice based on page's namespace
*
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
function getNamespaceNotice() {
wfProfileIn( __METHOD__ );
@@ -1530,7 +1527,7 @@ abstract class Skin extends ContextSource {
/**
* Get the site notice
*
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
function getSiteNotice() {
wfProfileIn( __METHOD__ );
@@ -1561,14 +1558,14 @@ abstract class Skin extends ContextSource {
* Create a section edit link. This supersedes editSectionLink() and
* editSectionLinkForOther().
*
- * @param $nt Title The title being linked to (may not be the same as
- * $wgTitle, if the section is included from a template)
+ * @param Title $nt The title being linked to (may not be the same as
+ * the current page, if the section is included from a template)
* @param string $section The designation of the section being pointed to,
* to be included in the link, like "&section=$section"
* @param string $tooltip The tooltip to use for the link: will be escaped
* and wrapped in the 'editsectionhint' message
- * @param $lang string Language code
- * @return string HTML to use for edit link
+ * @param string $lang Language code
+ * @return string HTML to use for edit link
*/
public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
// HTML generated here should probably have userlangattributes
diff --git a/includes/skins/SkinException.php b/includes/skins/SkinException.php
new file mode 100644
index 00000000..31ff1437
--- /dev/null
+++ b/includes/skins/SkinException.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright 2014
+ *
+ * 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
+ */
+
+/**
+ * Exceptions for skin-related failures
+ *
+ * @since 1.24
+ */
+class SkinException extends MWException {
+}
diff --git a/includes/skins/SkinFactory.php b/includes/skins/SkinFactory.php
new file mode 100644
index 00000000..fb408577
--- /dev/null
+++ b/includes/skins/SkinFactory.php
@@ -0,0 +1,214 @@
+<?php
+
+/**
+ * Copyright 2014
+ *
+ * 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
+ */
+
+/**
+ * Factory class to create Skin objects
+ *
+ * @since 1.24
+ */
+class SkinFactory {
+
+ /**
+ * Map of name => callback
+ * @var array
+ */
+ private $factoryFunctions = array();
+ /**
+ * Map of name => fallback human-readable name, used when the 'skinname-<skin>' message is not
+ * available
+ *
+ * @var array
+ */
+ private $displayNames = array();
+ /**
+ * Map of name => class name without "Skin" prefix, for legacy skins using the autodiscovery
+ * mechanism
+ *
+ * @var array
+ */
+ private $legacySkins = array();
+
+ /**
+ * @var SkinFactory
+ */
+ private static $self;
+
+ public static function getDefaultInstance() {
+ if ( !self::$self ) {
+ self::$self = new self;
+ }
+
+ return self::$self;
+ }
+
+ /**
+ * Register a new Skin factory function.
+ *
+ * Will override if it's already registered.
+ *
+ * @param string $name Internal skin name. Should be all-lowercase (technically doesn't have
+ * to be, but doing so would change the case of i18n message keys).
+ * @param string $displayName For backwards-compatibility with old skin loading system. This is
+ * the text used as skin's human-readable name when the 'skinname-<skin>' message is not
+ * available. It should be the same as the skin name provided in $wgExtensionCredits.
+ * @param callable $callback Callback that takes the skin name as an argument
+ * @throws InvalidArgumentException If an invalid callback is provided
+ */
+ public function register( $name, $displayName, $callback ) {
+ if ( !is_callable( $callback ) ) {
+ throw new InvalidArgumentException( 'Invalid callback provided' );
+ }
+ $this->factoryFunctions[$name] = $callback;
+ $this->displayNames[$name] = $displayName;
+ }
+
+ /**
+ * @return array
+ */
+ private function getLegacySkinNames() {
+ static $skinsInitialised = false;
+
+ if ( !$skinsInitialised || !count( $this->legacySkins ) ) {
+ # Get a list of available skins
+ # Build using the regular expression '^(.*).php$'
+ # Array keys are all lower case, array value keep the case used by filename
+ #
+ wfProfileIn( __METHOD__ . '-init' );
+
+ global $wgStyleDirectory;
+
+ $skinDir = dir( $wgStyleDirectory );
+
+ if ( $skinDir !== false && $skinDir !== null ) {
+ # while code from www.php.net
+ while ( false !== ( $file = $skinDir->read() ) ) {
+ // Skip non-PHP files, hidden files, and '.dep' includes
+ $matches = array();
+
+ if ( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
+ $aSkin = $matches[1];
+
+ // Explicitly disallow loading core skins via the autodiscovery mechanism.
+ //
+ // They should be loaded already (in a non-autodicovery way), but old files might still
+ // exist on the server because our MW version upgrade process is widely documented as
+ // requiring just copying over all files, without removing old ones.
+ //
+ // This is one of the reasons we should have never used autodiscovery in the first
+ // place. This hack can be safely removed when autodiscovery is gone.
+ if ( in_array( $aSkin, array( 'CologneBlue', 'Modern', 'MonoBook', 'Vector' ) ) ) {
+ wfLogWarning(
+ "An old copy of the $aSkin skin was found in your skins/ directory. " .
+ "You should remove it to avoid problems in the future." .
+ "See https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery for details."
+ );
+ continue;
+ }
+
+ wfLogWarning(
+ "A skin using autodiscovery mechanism, $aSkin, was found in your skins/ directory. " .
+ "The mechanism will be removed in MediaWiki 1.25 and the skin will no longer be recognized. " .
+ "See https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery for information how to fix this."
+ );
+ $this->legacySkins[strtolower( $aSkin )] = $aSkin;
+ }
+ }
+ $skinDir->close();
+ }
+ $skinsInitialised = true;
+ wfProfileOut( __METHOD__ . '-init' );
+ }
+ return $this->legacySkins;
+
+ }
+
+ /**
+ * Returns an associative array of:
+ * skin name => human readable name
+ *
+ * @return array
+ */
+ public function getSkinNames() {
+ return array_merge(
+ $this->getLegacySkinNames(),
+ $this->displayNames
+ );
+ }
+
+ /**
+ * Get a legacy skin which uses the autodiscovery mechanism.
+ *
+ * @param string $name
+ * @return Skin|bool False if the skin couldn't be constructed
+ */
+ private function getLegacySkin( $name ) {
+ $skinNames = $this->getLegacySkinNames();
+ if ( !isset( $skinNames[$name] ) ) {
+ return false;
+ }
+ $skinName = $skinNames[$name];
+ $className = "Skin{$skinName}";
+
+ # Grab the skin class and initialise it.
+ if ( !class_exists( $className ) ) {
+ global $wgStyleDirectory;
+ require_once "{$wgStyleDirectory}/{$skinName}.php";
+
+ # Check if we got it
+ if ( !class_exists( $className ) ) {
+ # DO NOT die if the class isn't found. This breaks maintenance
+ # scripts and can cause a user account to be unrecoverable
+ # except by SQL manipulation if a previously valid skin name
+ # is no longer valid.
+ return false;
+ }
+ }
+ $skin = new $className( $name );
+ return $skin;
+
+ }
+
+ /**
+ * Create a given Skin using the registered callback for $name.
+ * @param string $name Name of the skin you want
+ * @throws SkinException If a factory function isn't registered for $name
+ * @throws UnexpectedValueException If the factory function returns a non-Skin object
+ * @return Skin
+ */
+ public function makeSkin( $name ) {
+ if ( !isset( $this->factoryFunctions[$name] ) ) {
+ // Check the legacy autodiscovery method of skin loading
+ $legacy = $this->getLegacySkin( $name );
+ if ( $legacy ) {
+ return $legacy;
+ }
+ throw new SkinException( "No registered builder available for $name." );
+ }
+ $skin = call_user_func( $this->factoryFunctions[$name], $name );
+ if ( $skin instanceof Skin ) {
+ return $skin;
+ } else {
+ throw new UnexpectedValueException( "The builder for $name returned a non-Skin object." );
+ }
+ }
+}
diff --git a/includes/skins/SkinFallback.php b/includes/skins/SkinFallback.php
new file mode 100644
index 00000000..96ff2285
--- /dev/null
+++ b/includes/skins/SkinFallback.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Skin file for the fallback skin.
+ *
+ * The structure is copied from the example skin (mediawiki/skins/Example).
+ *
+ * @since 1.24
+ * @file
+ */
+
+/**
+ * SkinTemplate class for the fallback skin
+ */
+class SkinFallback extends SkinTemplate {
+
+ public $skinname = 'fallback';
+ public $template = 'SkinFallbackTemplate';
+
+ /**
+ * Add CSS via ResourceLoader
+ *
+ * @param OutputPage $out
+ */
+ public function setupSkinUserCss( OutputPage $out ) {
+ parent::setupSkinUserCss( $out );
+ $out->addModuleStyles( 'mediawiki.skinning.interface' );
+ }
+
+ /**
+ * @param OutputPage $out
+ */
+ public function initPage( OutputPage $out ) {
+ parent::initPage( $out );
+ $out->enableClientCache( false );
+ }
+}
diff --git a/includes/skins/SkinFallbackTemplate.php b/includes/skins/SkinFallbackTemplate.php
new file mode 100644
index 00000000..603ee5c5
--- /dev/null
+++ b/includes/skins/SkinFallbackTemplate.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * Skin template for the fallback skin.
+ *
+ * The structure is copied from the example skin (mediawiki/skins/Example).
+ *
+ * @since 1.24
+ * @file
+ */
+
+/**
+ * BaseTemplate class for the fallback skin
+ */
+class SkinFallbackTemplate extends BaseTemplate {
+ /**
+ * @return array
+ */
+ private function findInstalledSkins() {
+ $styleDirectory = $this->config->get( 'StyleDirectory' ); // @todo we should inject this directly?
+ // Get all subdirectories which might contains skins
+ $possibleSkins = scandir( $styleDirectory );
+ $possibleSkins = array_filter( $possibleSkins, function ( $maybeDir ) use ( $styleDirectory ) {
+ return $maybeDir !== '.' && $maybeDir !== '..' && is_dir( "$styleDirectory/$maybeDir" );
+ } );
+
+ // Only keep the ones that contain a .php file with the same name inside
+ $possibleSkins = array_filter( $possibleSkins, function ( $skinDir ) use ( $styleDirectory ) {
+ return is_file( "$styleDirectory/$skinDir/$skinDir.php" );
+ } );
+
+ return $possibleSkins;
+ }
+
+ /**
+ * Inform the user why they are seeing this skin.
+ *
+ * @return string
+ */
+ private function buildHelpfulInformationMessage() {
+ $defaultSkin = $this->config->get( 'DefaultSkin' );
+ $installedSkins = $this->findInstalledSkins();
+ $enabledSkins = SkinFactory::getDefaultInstance()->getSkinNames();
+ $enabledSkins = array_change_key_case( $enabledSkins, CASE_LOWER );
+
+ if ( $installedSkins ) {
+ $skinsInstalledText = array();
+ $skinsInstalledSnippet = array();
+
+ foreach ( $installedSkins as $skin ) {
+ $normalizedKey = strtolower( $skin );
+ $isEnabled = array_key_exists( $normalizedKey, $enabledSkins );
+ if ( $isEnabled ) {
+ $skinsInstalledText[] = $this->getMsg( 'default-skin-not-found-row-enabled' )
+ ->params( $normalizedKey, $skin )->plain();
+ } else {
+ $skinsInstalledText[] = $this->getMsg( 'default-skin-not-found-row-disabled' )
+ ->params( $normalizedKey, $skin )->plain();
+ $skinsInstalledSnippet[] = "require_once \"\$IP/skins/$skin/$skin.php\";";
+ }
+ }
+
+ return $this->getMsg( 'default-skin-not-found' )->params(
+ $defaultSkin,
+ implode( "\n", $skinsInstalledText ),
+ implode( "\n", $skinsInstalledSnippet )
+ )->parseAsBlock();
+ } else {
+ return $this->getMsg( 'default-skin-not-found-no-skins' )->params(
+ $defaultSkin
+ )->parseAsBlock();
+ }
+ }
+
+ /**
+ * Outputs the entire contents of the page. No navigation (other than search box), just the big
+ * warning message and page content.
+ */
+ public function execute() {
+ $this->html( 'headelement' ) ?>
+
+ <div class="warningbox">
+ <?php echo $this->buildHelpfulInformationMessage() ?>
+ </div>
+
+ <form action="<?php $this->text( 'wgScript' ) ?>">
+ <input type="hidden" name="title" value="<?php $this->text( 'searchtitle' ) ?>" />
+ <h3><label for="searchInput"><?php $this->msg( 'search' ) ?></label></h3>
+ <?php echo $this->makeSearchInput( array( "id" => "searchInput" ) ) ?>
+ <?php echo $this->makeSearchButton( 'go' ) ?>
+ </form>
+
+ <div class="mw-body" role="main">
+ <h1 class="firstHeading">
+ <span dir="auto"><?php $this->html( 'title' ) ?></span>
+ </h1>
+
+ <div class="mw-body-content">
+ <?php $this->html( 'bodytext' ) ?>
+ <?php $this->html( 'catlinks' ) ?>
+ </div>
+ </div>
+
+ <?php $this->printTrail() ?>
+ </body></html>
+
+ <?php
+ }
+}
diff --git a/includes/SkinTemplate.php b/includes/skins/SkinTemplate.php
index 581dbb34..b66862b8 100644
--- a/includes/SkinTemplate.php
+++ b/includes/skins/SkinTemplate.php
@@ -27,11 +27,11 @@
* @private
* @ingroup Skins
*/
-class MediaWiki_I18N {
- var $_context = array();
+class MediaWikiI18N {
+ private $context = array();
function set( $varName, $value ) {
- $this->_context[$varName] = $value;
+ $this->context[$varName] = $value;
}
function translate( $value ) {
@@ -46,7 +46,7 @@ class MediaWiki_I18N {
while ( preg_match( '/\$([0-9]*?)/sm', $value, $m ) ) {
list( $src, $var ) = $m;
wfSuppressWarnings();
- $varValue = $this->_context[$var];
+ $varValue = $this->context[$var];
wfRestoreWarnings();
$value = str_replace( $src, $varValue, $value );
}
@@ -68,43 +68,29 @@ class MediaWiki_I18N {
* @ingroup Skins
*/
class SkinTemplate extends Skin {
- /**#@+
- * @private
- */
-
- /**
- * Name of our skin, it probably needs to be all lower case. Child classes
- * should override the default.
- */
- var $skinname = 'monobook';
-
/**
- * Stylesheets set to use. Subdirectory in skins/ where various stylesheets
- * are located. Child classes should override the default.
+ * @var string Name of our skin, it probably needs to be all lower case.
+ * Child classes should override the default.
*/
- var $stylename = 'monobook';
+ public $skinname = 'monobook';
/**
- * For QuickTemplate, the name of the subclass which will actually fill the
- * template. Child classes should override the default.
+ * @var string For QuickTemplate, the name of the subclass which will
+ * actually fill the template. Child classes should override the default.
*/
- var $template = 'QuickTemplate';
-
- /**
- * Whether this skin use OutputPage::headElement() to generate the "<head>"
- * tag
- */
- var $useHeadElement = false;
-
- /**#@-*/
+ public $template = 'QuickTemplate';
/**
* Add specific styles for this skin
*
- * @param $out OutputPage
+ * @param OutputPage $out
*/
function setupSkinUserCss( OutputPage $out ) {
- $out->addModuleStyles( array( 'mediawiki.legacy.shared', 'mediawiki.legacy.commonPrint' ) );
+ $out->addModuleStyles( array(
+ 'mediawiki.legacy.shared',
+ 'mediawiki.legacy.commonPrint',
+ 'mediawiki.ui.button'
+ ) );
}
/**
@@ -112,62 +98,115 @@ class SkinTemplate extends Skin {
* and eventually it spits out some HTML. Should have interface
* roughly equivalent to PHPTAL 0.7.
*
- * @param $classname String
- * @param string $repository subdirectory where we keep template files
- * @param $cache_dir string
+ * @param string $classname
+ * @param bool|string $repository Subdirectory where we keep template files
+ * @param bool|string $cache_dir
* @return QuickTemplate
* @private
*/
function setupTemplate( $classname, $repository = false, $cache_dir = false ) {
- return new $classname();
+ return new $classname( $this->getConfig() );
}
/**
* Generates array of language links for the current page
*
* @return array
- * @public
*/
public function getLanguages() {
global $wgHideInterlanguageLinks;
- $out = $this->getOutput();
- $userLang = $this->getLanguage();
+ if ( $wgHideInterlanguageLinks ) {
+ return array();
+ }
- # Language links
- $language_urls = array();
+ $userLang = $this->getLanguage();
+ $languageLinks = array();
+
+ foreach ( $this->getOutput()->getLanguageLinks() as $languageLinkText ) {
+ $languageLinkParts = explode( ':', $languageLinkText, 2 );
+ $class = 'interlanguage-link interwiki-' . $languageLinkParts[0];
+ unset( $languageLinkParts );
+
+ $languageLinkTitle = Title::newFromText( $languageLinkText );
+ if ( $languageLinkTitle ) {
+ $ilInterwikiCode = $languageLinkTitle->getInterwiki();
+ $ilLangName = Language::fetchLanguageName( $ilInterwikiCode );
+
+ if ( strval( $ilLangName ) === '' ) {
+ $ilDisplayTextMsg = wfMessage( "interlanguage-link-$ilInterwikiCode" );
+ if ( !$ilDisplayTextMsg->isDisabled() ) {
+ // Use custom MW message for the display text
+ $ilLangName = $ilDisplayTextMsg->text();
+ } else {
+ // Last resort: fallback to the language link target
+ $ilLangName = $languageLinkText;
+ }
+ } else {
+ // Use the language autonym as display text
+ $ilLangName = $this->formatLanguageName( $ilLangName );
+ }
- if ( !$wgHideInterlanguageLinks ) {
- foreach ( $out->getLanguageLinks() as $languageLinkText ) {
- $languageLinkParts = explode( ':', $languageLinkText, 2 );
- $class = 'interwiki-' . $languageLinkParts[0];
- unset( $languageLinkParts );
- $languageLinkTitle = Title::newFromText( $languageLinkText );
- if ( $languageLinkTitle ) {
- $ilInterwikiCode = $languageLinkTitle->getInterwiki();
- $ilLangName = Language::fetchLanguageName( $ilInterwikiCode );
+ // CLDR extension or similar is required to localize the language name;
+ // otherwise we'll end up with the autonym again.
+ $ilLangLocalName = Language::fetchLanguageName(
+ $ilInterwikiCode,
+ $userLang->getCode()
+ );
- if ( strval( $ilLangName ) === '' ) {
- $ilLangName = $languageLinkText;
+ $languageLinkTitleText = $languageLinkTitle->getText();
+ if ( $ilLangLocalName === '' ) {
+ $ilFriendlySiteName = wfMessage( "interlanguage-link-sitename-$ilInterwikiCode" );
+ if ( !$ilFriendlySiteName->isDisabled() ) {
+ if ( $languageLinkTitleText === '' ) {
+ $ilTitle = wfMessage(
+ 'interlanguage-link-title-nonlangonly',
+ $ilFriendlySiteName->text()
+ )->text();
+ } else {
+ $ilTitle = wfMessage(
+ 'interlanguage-link-title-nonlang',
+ $languageLinkTitleText,
+ $ilFriendlySiteName->text()
+ )->text();
+ }
} else {
- $ilLangName = $this->formatLanguageName( $ilLangName );
+ // we have nothing friendly to put in the title, so fall back to
+ // displaying the interlanguage link itself in the title text
+ // (similar to what is done in page content)
+ $ilTitle = $languageLinkTitle->getInterwiki() .
+ ":$languageLinkTitleText";
}
-
- // CLDR extension or similar is required to localize the language name;
- // otherwise we'll end up with the autonym again.
- $ilLangLocalName = Language::fetchLanguageName( $ilInterwikiCode, $userLang->getCode() );
-
- $language_urls[] = array(
- 'href' => $languageLinkTitle->getFullURL(),
- 'text' => $ilLangName,
- 'title' => wfMessage( 'interlanguage-link-title', $languageLinkTitle->getText(), $ilLangLocalName )->text(),
- 'class' => $class,
- 'lang' => wfBCP47( $ilInterwikiCode ),
- 'hreflang' => wfBCP47( $ilInterwikiCode ),
- );
+ } elseif ( $languageLinkTitleText === '' ) {
+ $ilTitle = wfMessage(
+ 'interlanguage-link-title-langonly',
+ $ilLangLocalName
+ )->text();
+ } else {
+ $ilTitle = wfMessage(
+ 'interlanguage-link-title',
+ $languageLinkTitleText,
+ $ilLangLocalName
+ )->text();
}
+
+ $ilInterwikiCodeBCP47 = wfBCP47( $ilInterwikiCode );
+ $languageLink = array(
+ 'href' => $languageLinkTitle->getFullURL(),
+ 'text' => $ilLangName,
+ 'title' => $ilTitle,
+ 'class' => $class,
+ 'lang' => $ilInterwikiCodeBCP47,
+ 'hreflang' => $ilInterwikiCodeBCP47,
+ );
+ wfRunHooks(
+ 'SkinTemplateGetLanguageLink',
+ array( &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() )
+ );
+ $languageLinks[] = $languageLink;
}
}
- return $language_urls;
+
+ return $languageLinks;
}
protected function setupTemplateForOutput() {
@@ -214,18 +253,9 @@ class SkinTemplate extends Skin {
/**
* initialize various variables and generate the template
*
- * @param $out OutputPage
+ * @param OutputPage $out
*/
function outputPage( OutputPage $out = null ) {
- global $wgContLang;
- global $wgScript, $wgStylePath;
- global $wgMimeType, $wgJsMimeType;
- global $wgXhtmlNamespaces, $wgHtml5Version;
- global $wgDisableCounters, $wgSitename, $wgLogo;
- global $wgMaxCredits, $wgShowCreditsIfMax;
- global $wgPageShowWatchingUsers;
- global $wgArticlePath, $wgScriptPath, $wgServer;
-
wfProfileIn( __METHOD__ );
Profiler::instance()->setTemplated( true );
@@ -237,35 +267,44 @@ class SkinTemplate extends Skin {
}
$out = $this->getOutput();
- $request = $this->getRequest();
- $user = $this->getUser();
- $title = $this->getTitle();
wfProfileIn( __METHOD__ . '-init' );
$this->initPage( $out );
wfProfileOut( __METHOD__ . '-init' );
+ $tpl = $this->prepareQuickTemplate( $out );
+ // execute template
+ wfProfileIn( __METHOD__ . '-execute' );
+ $res = $tpl->execute();
+ wfProfileOut( __METHOD__ . '-execute' );
- $tpl = $this->setupTemplateForOutput();
+ // result may be an error
+ $this->printOrError( $res );
- wfProfileIn( __METHOD__ . '-stuff-head' );
- if ( !$this->useHeadElement ) {
- $tpl->set( 'pagecss', false );
- $tpl->set( 'usercss', false );
+ if ( $oldContext ) {
+ $this->setContext( $oldContext );
+ }
- $tpl->set( 'userjs', false );
- $tpl->set( 'userjsprev', false );
+ wfProfileOut( __METHOD__ );
+ }
- $tpl->set( 'jsvarurl', false );
+ /**
+ * initialize various variables and generate the template
+ *
+ * @since 1.23
+ * @return QuickTemplate The template to be executed by outputPage
+ */
+ protected function prepareQuickTemplate() {
+ global $wgContLang, $wgScript, $wgStylePath, $wgMimeType, $wgJsMimeType,
+ $wgDisableCounters, $wgSitename, $wgLogo, $wgMaxCredits,
+ $wgShowCreditsIfMax, $wgPageShowWatchingUsers, $wgArticlePath,
+ $wgScriptPath, $wgServer;
- $tpl->set( 'xhtmldefaultnamespace', 'http://www.w3.org/1999/xhtml' );
- $tpl->set( 'xhtmlnamespaces', $wgXhtmlNamespaces );
- $tpl->set( 'html5version', $wgHtml5Version );
- $tpl->set( 'headlinks', $out->getHeadLinks() );
- $tpl->set( 'csslinks', $out->buildCssLinks() );
- $tpl->set( 'pageclass', $this->getPageClasses( $title ) );
- $tpl->set( 'skinnameclass', ( 'skin-' . Sanitizer::escapeClass( $this->getSkinName() ) ) );
- }
- wfProfileOut( __METHOD__ . '-stuff-head' );
+ wfProfileIn( __METHOD__ );
+
+ $title = $this->getTitle();
+ $request = $this->getRequest();
+ $out = $this->getOutput();
+ $tpl = $this->setupTemplateForOutput();
wfProfileIn( __METHOD__ . '-stuff2' );
$tpl->set( 'title', $out->getPageTitle() );
@@ -319,13 +358,6 @@ class SkinTemplate extends Skin {
$tpl->set( 'handheld', $request->getBool( 'handheld' ) );
$tpl->setRef( 'loggedin', $this->loggedin );
$tpl->set( 'notspecialpage', !$title->isSpecialPage() );
- /* XXX currently unused, might get useful later
- $tpl->set( 'editable', ( !$title->isSpecialPage() ) );
- $tpl->set( 'exists', $title->getArticleID() != 0 );
- $tpl->set( 'watch', $user->isWatched( $title ) ? 'unwatch' : 'watch' );
- $tpl->set( 'protect', count( $title->isProtected() ) ? 'unprotect' : 'protect' );
- $tpl->set( 'helppage', $this->msg( 'helppage' )->text() );
- */
$tpl->set( 'searchaction', $this->escapeSearchLink() );
$tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBkey() );
$tpl->set( 'search', trim( $request->getVal( 'search' ) ) );
@@ -356,6 +388,9 @@ class SkinTemplate extends Skin {
// that interface elements are in a different language.
$tpl->set( 'userlangattributes', '' );
$tpl->set( 'specialpageattributes', '' ); # obsolete
+ // Used by VectorBeta to insert HTML before content but after the
+ // heading for the page title. Defaults to empty string.
+ $tpl->set( 'prebodyhtml', '' );
if ( $userLangCode !== $wgContLang->getHtmlCode() || $userLangDir !== $wgContLang->getDir() ) {
$escUserlang = htmlspecialchars( $userLangCode );
@@ -494,11 +529,7 @@ class SkinTemplate extends Skin {
$tpl->set( 'nav_urls', $this->buildNavUrls() );
// Set the head scripts near the end, in case the above actions resulted in added scripts
- if ( $this->useHeadElement ) {
- $tpl->set( 'headelement', $out->headElement( $this ) );
- } else {
- $tpl->set( 'headscripts', $out->getHeadScripts() . $out->getHeadItems() );
- }
+ $tpl->set( 'headelement', $out->headElement( $this ) );
$tpl->set( 'debug', '' );
$tpl->set( 'debughtml', $this->generateDebugHTML() );
@@ -513,9 +544,14 @@ class SkinTemplate extends Skin {
// and output printfooter and debughtml separately
$tpl->set( 'bodycontent', $tpl->data['bodytext'] );
- // Append printfooter and debughtml onto bodytext so that skins that were already
- // using bodytext before they were split out don't suddenly start not outputting information
- $tpl->data['bodytext'] .= Html::rawElement( 'div', array( 'class' => 'printfooter' ), "\n{$tpl->data['printfooter']}" ) . "\n";
+ // Append printfooter and debughtml onto bodytext so that skins that
+ // were already using bodytext before they were split out don't suddenly
+ // start not outputting information.
+ $tpl->data['bodytext'] .= Html::rawElement(
+ 'div',
+ array( 'class' => 'printfooter' ),
+ "\n{$tpl->data['printfooter']}"
+ ) . "\n";
$tpl->data['bodytext'] .= $tpl->data['debughtml'];
// allow extensions adding stuff after the page content.
@@ -523,18 +559,8 @@ class SkinTemplate extends Skin {
$tpl->set( 'dataAfterContent', $this->afterContentHook() );
wfProfileOut( __METHOD__ . '-stuff5' );
- // execute template
- wfProfileIn( __METHOD__ . '-execute' );
- $res = $tpl->execute();
- wfProfileOut( __METHOD__ . '-execute' );
-
- // result may be an error
- $this->printOrError( $res );
-
- if ( $oldContext ) {
- $this->setContext( $oldContext );
- }
wfProfileOut( __METHOD__ );
+ return $tpl;
}
/**
@@ -568,7 +594,7 @@ class SkinTemplate extends Skin {
* an error object of the appropriate type.
* For the base class, assume strings all around.
*
- * @param $str Mixed
+ * @param string $str
* @private
*/
function printOrError( $str ) {
@@ -677,7 +703,7 @@ class SkinTemplate extends Skin {
'active' => $active
);
$personal_urls['logout'] = array(
- 'text' => $this->msg( 'userlogout' )->text(),
+ 'text' => $this->msg( 'pt-userlogout' )->text(),
'href' => self::makeSpecialUrl( 'Userlogout',
// userlogout link must always contain an & character, otherwise we might not be able
// to detect a buggy precaching proxy (bug 17790)
@@ -689,17 +715,17 @@ class SkinTemplate extends Skin {
$useCombinedLoginLink = $this->useCombinedLoginLink();
$loginlink = $this->getUser()->isAllowed( 'createaccount' ) && $useCombinedLoginLink
? 'nav-login-createaccount'
- : 'login';
+ : 'pt-login';
$is_signup = $request->getText( 'type' ) == 'signup';
- $login_id = $this->showIPinHeader() ? 'anonlogin' : 'login';
$login_url = array(
'text' => $this->msg( $loginlink )->text(),
'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
- 'active' => $title->isSpecial( 'Userlogin' ) && ( $loginlink == 'nav-login-createaccount' || !$is_signup ),
+ 'active' => $title->isSpecial( 'Userlogin' )
+ && ( $loginlink == 'nav-login-createaccount' || !$is_signup ),
);
$createaccount_url = array(
- 'text' => $this->msg( 'createaccount' )->text(),
+ 'text' => $this->msg( 'pt-createaccount' )->text(),
'href' => self::makeSpecialUrl( 'Userlogin', "$returnto&type=signup" ),
'active' => $title->isSpecial( 'Userlogin' ) && $is_signup,
);
@@ -726,10 +752,10 @@ class SkinTemplate extends Skin {
$personal_urls['createaccount'] = $createaccount_url;
}
- $personal_urls[$login_id] = $login_url;
+ $personal_urls['login'] = $login_url;
}
- wfRunHooks( 'PersonalUrls', array( &$personal_urls, &$title ) );
+ wfRunHooks( 'PersonalUrls', array( &$personal_urls, &$title, $this ) );
wfProfileOut( __METHOD__ );
return $personal_urls;
}
@@ -737,11 +763,11 @@ class SkinTemplate extends Skin {
/**
* Builds an array with tab definition
*
- * @param Title $title page where the tab links to
- * @param string|array $message message key or an array of message keys (will fall back)
- * @param boolean $selected display the tab as selected
- * @param string $query query string attached to tab URL
- * @param boolean $checkEdit check if $title exists and mark with .new if one doesn't
+ * @param Title $title Page Where the tab links to
+ * @param string|array $message Message key or an array of message keys (will fall back)
+ * @param bool $selected Display the tab as selected
+ * @param string $query Query string attached to tab URL
+ * @param bool $checkEdit Check if $title exists and mark with .new if one doesn't
*
* @return array
*/
@@ -838,9 +864,10 @@ class SkinTemplate extends Skin {
* links, however these are usually automatically generated by SkinTemplate
* itself and are not necessary when using a hook. The only things these may
* matter to are people modifying content_navigation after it's initial creation:
- * - id: A "preferred" id, most skins are best off outputting this preferred id for best compatibility
+ * - id: A "preferred" id, most skins are best off outputting this preferred
+ * id for best compatibility.
* - tooltiponly: This is set to true for some tabs in cases where the system
- * believes that the accesskey should not be added to the tab.
+ * believes that the accesskey should not be added to the tab.
*
* @return array
*/
@@ -907,8 +934,11 @@ class SkinTemplate extends Skin {
$content_navigation['namespaces'][$talkId]['context'] = 'talk';
if ( $userCanRead ) {
+ $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() &&
+ $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal();
+
// Adds view view link
- if ( $title->exists() ) {
+ if ( $title->exists() || $isForeignFile ) {
$content_navigation['views']['view'] = $this->tabAction(
$isTalk ? $talkPage : $subjectPage,
array( "$skname-view-view", 'view' ),
@@ -918,10 +948,25 @@ class SkinTemplate extends Skin {
$content_navigation['views']['view']['redundant'] = true;
}
+ // If it is a non-local file, show a link to the file in its own repository
+ if ( $isForeignFile ) {
+ $file = $this->getWikiPage()->getFile();
+ $content_navigation['views']['view-foreign'] = array(
+ 'class' => '',
+ 'text' => wfMessageFallback( "$skname-view-foreign", 'view-foreign' )->
+ setContext( $this->getContext() )->
+ params( $file->getRepo()->getDisplayName() )->text(),
+ 'href' => $file->getDescriptionUrl(),
+ 'primary' => false,
+ );
+ }
+
wfProfileIn( __METHOD__ . '-edit' );
// Checks if user can edit the current page if it exists or create it otherwise
- if ( $title->quickUserCan( 'edit', $user ) && ( $title->exists() || $title->quickUserCan( 'create', $user ) ) ) {
+ if ( $title->quickUserCan( 'edit', $user )
+ && ( $title->exists() || $title->quickUserCan( 'create', $user ) )
+ ) {
// Builds CSS class for talk page links
$isTalkClass = $isTalk ? ' istalk' : '';
// Whether the user is editing the page
@@ -932,13 +977,24 @@ class SkinTemplate extends Skin {
&& ( ( $isTalk && $this->isRevisionCurrent() ) || $out->showNewSectionLink() );
$section = $request->getVal( 'section' );
- $msgKey = $title->exists() || ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDefaultMessageText() !== false ) ?
- 'edit' : 'create';
+ if ( $title->exists()
+ || ( $title->getNamespace() == NS_MEDIAWIKI
+ && $title->getDefaultMessageText() !== false
+ )
+ ) {
+ $msgKey = $isForeignFile ? 'edit-local' : 'edit';
+ } else {
+ $msgKey = $isForeignFile ? 'create-local' : 'create';
+ }
$content_navigation['views']['edit'] = array(
- 'class' => ( $isEditing && ( $section !== 'new' || !$showNewSection ) ? 'selected' : '' ) . $isTalkClass,
- 'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey )->setContext( $this->getContext() )->text(),
+ 'class' => ( $isEditing && ( $section !== 'new' || !$showNewSection )
+ ? 'selected'
+ : ''
+ ) . $isTalkClass,
+ 'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey )
+ ->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( $this->editUrlOptions() ),
- 'primary' => true, // don't collapse this in vector
+ 'primary' => !$isForeignFile, // don't collapse this in vector
);
// section link
@@ -947,7 +1003,8 @@ class SkinTemplate extends Skin {
//$content_navigation['actions']['addsection']
$content_navigation['views']['addsection'] = array(
'class' => ( $isEditing && $section == 'new' ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-addsection", 'addsection' )->setContext( $this->getContext() )->text(),
+ 'text' => wfMessageFallback( "$skname-action-addsection", 'addsection' )
+ ->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( 'action=edit&section=new' )
);
}
@@ -956,7 +1013,8 @@ class SkinTemplate extends Skin {
// Adds view source view link
$content_navigation['views']['viewsource'] = array(
'class' => ( $onPage && $action == 'edit' ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-viewsource", 'viewsource' )->setContext( $this->getContext() )->text(),
+ 'text' => wfMessageFallback( "$skname-action-viewsource", 'viewsource' )
+ ->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( $this->editUrlOptions() ),
'primary' => true, // don't collapse this in vector
);
@@ -969,7 +1027,8 @@ class SkinTemplate extends Skin {
// Adds history view link
$content_navigation['views']['history'] = array(
'class' => ( $onPage && $action == 'history' ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-view-history", 'history_short' )->setContext( $this->getContext() )->text(),
+ 'text' => wfMessageFallback( "$skname-view-history", 'history_short' )
+ ->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( 'action=history' ),
'rel' => 'archives',
);
@@ -977,7 +1036,8 @@ class SkinTemplate extends Skin {
if ( $title->quickUserCan( 'delete', $user ) ) {
$content_navigation['actions']['delete'] = array(
'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-delete", 'delete' )->setContext( $this->getContext() )->text(),
+ 'text' => wfMessageFallback( "$skname-action-delete", 'delete' )
+ ->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( 'action=delete' )
);
}
@@ -986,7 +1046,8 @@ class SkinTemplate extends Skin {
$moveTitle = SpecialPage::getTitleFor( 'Movepage', $title->getPrefixedDBkey() );
$content_navigation['actions']['move'] = array(
'class' => $this->getTitle()->isSpecial( 'Movepage' ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-move", 'move' )->setContext( $this->getContext() )->text(),
+ 'text' => wfMessageFallback( "$skname-action-move", 'move' )
+ ->setContext( $this->getContext() )->text(),
'href' => $moveTitle->getLocalURL()
);
}
@@ -996,7 +1057,8 @@ class SkinTemplate extends Skin {
$n = $title->isDeleted();
if ( $n ) {
$undelTitle = SpecialPage::getTitleFor( 'Undelete' );
- // If the user can't undelete but can view deleted history show them a "View .. deleted" tab instead
+ // If the user can't undelete but can view deleted
+ // history show them a "View .. deleted" tab instead.
$msgKey = $user->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted';
$content_navigation['actions']['undelete'] = array(
'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
@@ -1008,11 +1070,14 @@ class SkinTemplate extends Skin {
}
}
- if ( $title->getNamespace() !== NS_MEDIAWIKI && $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() ) {
+ if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() &&
+ MWNamespace::getRestrictionLevels( $title->getNamespace(), $user ) !== array( '' )
+ ) {
$mode = $title->isProtected() ? 'unprotect' : 'protect';
$content_navigation['actions'][$mode] = array(
'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-$mode", $mode )->setContext( $this->getContext() )->text(),
+ 'text' => wfMessageFallback( "$skname-action-$mode", $mode )
+ ->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( "action=$mode" )
);
}
@@ -1024,7 +1089,7 @@ class SkinTemplate extends Skin {
/**
* The following actions use messages which, if made particular to
* the any specific skins, would break the Ajax code which makes this
- * action happen entirely inline. Skin::makeGlobalVariablesScript
+ * action happen entirely inline. OutputPage::getJSVars
* defines a set of messages in a javascript object - and these
* messages are assumed to be global for all skins. Without making
* a change to that procedure these messages will have to remain as
@@ -1132,10 +1197,10 @@ class SkinTemplate extends Skin {
/**
* an array of edit links by default used for the tabs
+ * @param array $content_navigation
* @return array
- * @private
*/
- function buildContentActionUrls( $content_navigation ) {
+ private function buildContentActionUrls( $content_navigation ) {
wfProfileIn( __METHOD__ );
@@ -1146,9 +1211,7 @@ class SkinTemplate extends Skin {
$content_actions = array();
foreach ( $content_navigation as $links ) {
-
foreach ( $links as $key => $value ) {
-
if ( isset( $value['redundant'] ) && $value['redundant'] ) {
// Redundant tabs are dropped from content_actions
continue;
@@ -1163,14 +1226,13 @@ class SkinTemplate extends Skin {
}
if ( isset( $content_actions[$key] ) ) {
- wfDebug( __METHOD__ . ": Found a duplicate key for $key while flattening content_navigation into content_actions." );
+ wfDebug( __METHOD__ . ": Found a duplicate key for $key while flattening " .
+ "content_navigation into content_actions.\n" );
continue;
}
$content_actions[$key] = $value;
-
}
-
}
wfProfileOut( __METHOD__ );
@@ -1181,7 +1243,6 @@ class SkinTemplate extends Skin {
/**
* build array of common navigation links
* @return array
- * @private
*/
protected function buildNavUrls() {
global $wgUploadNavigationUrl;
@@ -1270,6 +1331,7 @@ class SkinTemplate extends Skin {
if ( $this->getUser()->isAllowed( 'block' ) ) {
$nav_urls['blockip'] = array(
+ 'text' => $this->msg( 'blockip', $rootUser )->text(),
'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser )
);
}
@@ -1298,9 +1360,8 @@ class SkinTemplate extends Skin {
/**
* Generate strings used for xml 'id' names
* @return string
- * @private
*/
- function getNameSpaceKey() {
+ protected function getNameSpaceKey() {
return $this->getTitle()->getNamespaceKey();
}
}
@@ -1311,18 +1372,27 @@ class SkinTemplate extends Skin {
* @ingroup Skins
*/
abstract class QuickTemplate {
+
+ /** @var Config $config */
+ protected $config;
+
/**
- * Constructor
+ * @param Config $config
*/
- function __construct() {
+ function __construct( Config $config = null ) {
$this->data = array();
- $this->translator = new MediaWiki_I18N();
+ $this->translator = new MediaWikiI18N();
+ if ( $config === null ) {
+ wfDebug( __METHOD__ . ' was called with no Config instance passed to it' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+ $this->config = $config;
}
/**
* Sets the value $value to $name
- * @param $name
- * @param $value
+ * @param string $name
+ * @param mixed $value
*/
public function set( $name, $value ) {
$this->data[$name] = $value;
@@ -1344,15 +1414,15 @@ abstract class QuickTemplate {
}
/**
- * @param $name
- * @param $value
+ * @param string $name
+ * @param mixed $value
*/
public function setRef( $name, &$value ) {
$this->data[$name] =& $value;
}
/**
- * @param $t
+ * @param MediaWikiI18N $t
*/
public function setTranslator( &$t ) {
$this->translator = &$t;
@@ -1366,6 +1436,8 @@ abstract class QuickTemplate {
/**
* @private
+ * @param string $str
+ * @return string
*/
function text( $str ) {
echo htmlspecialchars( $this->data[$str] );
@@ -1373,15 +1445,8 @@ abstract class QuickTemplate {
/**
* @private
- * @deprecated since 1.21; use Xml::encodeJsVar() or Xml::encodeJsCall() instead
- */
- function jstext( $str ) {
- wfDeprecated( __METHOD__, '1.21' );
- echo Xml::escapeJsString( $this->data[$str] );
- }
-
- /**
- * @private
+ * @param string $str
+ * @return string
*/
function html( $str ) {
echo $this->data[$str];
@@ -1389,6 +1454,8 @@ abstract class QuickTemplate {
/**
* @private
+ * @param string $str
+ * @return string
*/
function msg( $str ) {
echo htmlspecialchars( $this->translator->translate( $str ) );
@@ -1396,6 +1463,8 @@ abstract class QuickTemplate {
/**
* @private
+ * @param string $str
+ * @return string
*/
function msgHtml( $str ) {
echo $this->translator->translate( $str );
@@ -1404,6 +1473,8 @@ abstract class QuickTemplate {
/**
* An ugly, ugly hack.
* @private
+ * @param string $str
+ * @return string
*/
function msgWiki( $str ) {
global $wgOut;
@@ -1414,6 +1485,7 @@ abstract class QuickTemplate {
/**
* @private
+ * @param string $str
* @return bool
*/
function haveData( $str ) {
@@ -1423,6 +1495,7 @@ abstract class QuickTemplate {
/**
* @private
*
+ * @param string $str
* @return bool
*/
function haveMsg( $str ) {
@@ -1433,11 +1506,25 @@ abstract class QuickTemplate {
/**
* Get the Skin object related to this object
*
- * @return Skin object
+ * @return Skin
*/
public function getSkin() {
return $this->data['skin'];
}
+
+ /**
+ * Fetch the output of a QuickTemplate and return it
+ *
+ * @since 1.23
+ * @return string
+ */
+ public function getHTML() {
+ ob_start();
+ $this->execute();
+ $html = ob_get_contents();
+ ob_end_clean();
+ return $html;
+ }
}
/**
@@ -1450,7 +1537,7 @@ abstract class BaseTemplate extends QuickTemplate {
/**
* Get a Message object with its context set
*
- * @param string $name message name
+ * @param string $name Message name
* @return Message
*/
public function getMsg( $name ) {
@@ -1480,11 +1567,15 @@ abstract class BaseTemplate extends QuickTemplate {
wfProfileIn( __METHOD__ );
$toolbox = array();
- if ( isset( $this->data['nav_urls']['whatlinkshere'] ) && $this->data['nav_urls']['whatlinkshere'] ) {
+ if ( isset( $this->data['nav_urls']['whatlinkshere'] )
+ && $this->data['nav_urls']['whatlinkshere']
+ ) {
$toolbox['whatlinkshere'] = $this->data['nav_urls']['whatlinkshere'];
$toolbox['whatlinkshere']['id'] = 't-whatlinkshere';
}
- if ( isset( $this->data['nav_urls']['recentchangeslinked'] ) && $this->data['nav_urls']['recentchangeslinked'] ) {
+ if ( isset( $this->data['nav_urls']['recentchangeslinked'] )
+ && $this->data['nav_urls']['recentchangeslinked']
+ ) {
$toolbox['recentchangeslinked'] = $this->data['nav_urls']['recentchangeslinked'];
$toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
$toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
@@ -1500,7 +1591,9 @@ abstract class BaseTemplate extends QuickTemplate {
$toolbox['feeds']['links'][$key]['class'] = 'feedlink';
}
}
- foreach ( array( 'contributions', 'log', 'blockip', 'emailuser', 'userrights', 'upload', 'specialpages' ) as $special ) {
+ foreach ( array( 'contributions', 'log', 'blockip', 'emailuser',
+ 'userrights', 'upload', 'specialpages' ) as $special
+ ) {
if ( isset( $this->data['nav_urls'][$special] ) && $this->data['nav_urls'][$special] ) {
$toolbox[$special] = $this->data['nav_urls'][$special];
$toolbox[$special]['id'] = "t-$special";
@@ -1558,7 +1651,7 @@ abstract class BaseTemplate extends QuickTemplate {
if ( isset( $plink['active'] ) ) {
$ptool['active'] = $plink['active'];
}
- foreach ( array( 'href', 'class', 'text' ) as $k ) {
+ foreach ( array( 'href', 'class', 'text', 'dir' ) as $k ) {
if ( isset( $plink[$k] ) ) {
$ptool['links'][0][$k] = $plink[$k];
}
@@ -1697,12 +1790,25 @@ abstract class BaseTemplate extends QuickTemplate {
}
/**
+ * @param string $name
+ */
+ protected function renderAfterPortlet( $name ) {
+ $content = '';
+ wfRunHooks( 'BaseTemplateAfterPortlet', array( $this, $name, &$content ) );
+
+ if ( $content !== '' ) {
+ echo "<div class='after-portlet after-portlet-$name'>$content</div>";
+ }
+
+ }
+
+ /**
* Makes a link, usually used by makeListItem to generate a link for an item
* in a list used in navigation lists, portlets, portals, sidebars, etc...
*
- * @param string $key usually a key from the list you are generating this
+ * @param string $key Usually a key from the list you are generating this
* link from.
- * @param array $item contains some of a specific set of keys.
+ * @param array $item Contains some of a specific set of keys.
*
* The text of the link will be generated either from the contents of the
* "text" key in the $item array, if a "msg" key is present a message by
@@ -1723,7 +1829,7 @@ abstract class BaseTemplate extends QuickTemplate {
*
* If you don't want an accesskey, set $item['tooltiponly'] = true;
*
- * @param array $options can be used to affect the output of a link.
+ * @param array $options Can be used to affect the output of a link.
* Possible options are:
* - 'text-wrapper' key to specify a list of elements to wrap the text of
* a link in. This should be an array of arrays containing a 'tag' and
@@ -1755,7 +1861,9 @@ abstract class BaseTemplate extends QuickTemplate {
}
while ( count( $wrapper ) > 0 ) {
$element = array_pop( $wrapper );
- $html = Html::rawElement( $element['tag'], isset( $element['attributes'] ) ? $element['attributes'] : null, $html );
+ $html = Html::rawElement( $element['tag'], isset( $element['attributes'] )
+ ? $element['attributes']
+ : null, $html );
}
}
@@ -1791,7 +1899,9 @@ abstract class BaseTemplate extends QuickTemplate {
$attrs['class'] = $options['link-class'];
}
}
- $html = Html::rawElement( isset( $attrs['href'] ) ? 'a' : $options['link-fallback'], $attrs, $html );
+ $html = Html::rawElement( isset( $attrs['href'] )
+ ? 'a'
+ : $options['link-fallback'], $attrs, $html );
}
return $html;
@@ -1800,12 +1910,12 @@ abstract class BaseTemplate extends QuickTemplate {
/**
* Generates a list item for a navigation, portlet, portal, sidebar... list
*
- * @param $key string, usually a key from the list you are generating this link from.
- * @param $item array, of list item data containing some of a specific set of keys.
- * The "id" and "class" keys will be used as attributes for the list item,
+ * @param string $key Usually a key from the list you are generating this link from.
+ * @param array $item Array of list item data containing some of a specific set of keys.
+ * The "id", "class" and "itemtitle" keys will be used as attributes for the list item,
* if "active" contains a value of true a "active" class will also be appended to class.
*
- * @param $options array
+ * @param array $options
*
* If you want something other than a "<li>" you can pass a tag name such as
* "tag" => "span" in the $options array to change the tag used.
@@ -1819,7 +1929,8 @@ abstract class BaseTemplate extends QuickTemplate {
* list item directly so they will not be passed to makeLink
* (however the link will still support a tooltip and accesskey from it)
* If you need an id or class on a single link you should include a "links"
- * array with just one link item inside of it.
+ * array with just one link item inside of it. If you want to add a title
+ * to the list item itself, you can set "itemtitle" to the value.
* $options is also passed on to makeLink calls
*
* @return string
@@ -1834,7 +1945,7 @@ abstract class BaseTemplate extends QuickTemplate {
} else {
$link = $item;
// These keys are used by makeListItem and shouldn't be passed on to the link
- foreach ( array( 'id', 'class', 'active', 'tag' ) as $k ) {
+ foreach ( array( 'id', 'class', 'active', 'tag', 'itemtitle' ) as $k ) {
unset( $link[$k] );
}
if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
@@ -1859,6 +1970,9 @@ abstract class BaseTemplate extends QuickTemplate {
$attrs['class'] .= ' active';
$attrs['class'] = trim( $attrs['class'] );
}
+ if ( isset( $item['itemtitle'] ) ) {
+ $attrs['title'] = $item['itemtitle'];
+ }
return Html::rawElement( isset( $options['tag'] ) ? $options['tag'] : 'li', $attrs, $html );
}
@@ -1923,6 +2037,7 @@ abstract class BaseTemplate extends QuickTemplate {
* If you pass "flat" as an option then the returned array will be a flat array
* of footer icons instead of a key/value array of footerlinks arrays broken
* up into categories.
+ * @param string $option
* @return array|mixed
*/
function getFooterLinks( $option = null ) {
@@ -1962,7 +2077,8 @@ abstract class BaseTemplate extends QuickTemplate {
* in the list of footer icons. This is mostly useful for skins which only
* display the text from footericons instead of the images and don't want a
* duplicate copyright statement because footerlinks already rendered one.
- * @return
+ * @param string $option
+ * @return string
*/
function getFooterIcons( $option = null ) {
// Generate additional footer icons
@@ -2004,5 +2120,4 @@ abstract class BaseTemplate extends QuickTemplate {
<?php $this->html( 'reporttime' ) ?>
<?php
}
-
}
diff --git a/includes/specialpage/ChangesListSpecialPage.php b/includes/specialpage/ChangesListSpecialPage.php
new file mode 100644
index 00000000..c28aa867
--- /dev/null
+++ b/includes/specialpage/ChangesListSpecialPage.php
@@ -0,0 +1,468 @@
+<?php
+/**
+ * Special page which uses a ChangesList to show query results.
+ *
+ * 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 which uses a ChangesList to show query results.
+ * @todo Way too many public functions, most of them should be protected
+ *
+ * @ingroup SpecialPage
+ */
+abstract class ChangesListSpecialPage extends SpecialPage {
+ /** @var string */
+ protected $rcSubpage;
+
+ /** @var FormOptions */
+ protected $rcOptions;
+
+ /** @var array */
+ protected $customFilters;
+
+ /**
+ * Main execution point
+ *
+ * @param string $subpage
+ */
+ public function execute( $subpage ) {
+ $this->rcSubpage = $subpage;
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $this->addModules();
+
+ $rows = $this->getRows();
+ $opts = $this->getOptions();
+ if ( $rows === false ) {
+ if ( !$this->including() ) {
+ $this->doHeader( $opts, 0 );
+ $this->getOutput()->setStatusCode( 404 );
+ }
+
+ return;
+ }
+
+ $batch = new LinkBatch;
+ foreach ( $rows as $row ) {
+ $batch->add( NS_USER, $row->rc_user_text );
+ $batch->add( NS_USER_TALK, $row->rc_user_text );
+ $batch->add( $row->rc_namespace, $row->rc_title );
+ }
+ $batch->execute();
+
+ $this->webOutput( $rows, $opts );
+
+ $rows->free();
+ }
+
+ /**
+ * Get the database result for this special page instance. Used by ApiFeedRecentChanges.
+ *
+ * @return bool|ResultWrapper Result or false
+ */
+ public function getRows() {
+ $opts = $this->getOptions();
+ $conds = $this->buildMainQueryConds( $opts );
+
+ return $this->doMainQuery( $conds, $opts );
+ }
+
+ /**
+ * Get the current FormOptions for this request
+ *
+ * @return FormOptions
+ */
+ public function getOptions() {
+ if ( $this->rcOptions === null ) {
+ $this->rcOptions = $this->setup( $this->rcSubpage );
+ }
+
+ return $this->rcOptions;
+ }
+
+ /**
+ * Create a FormOptions object with options as specified by the user
+ *
+ * @param array $parameters
+ *
+ * @return FormOptions
+ */
+ public function setup( $parameters ) {
+ $opts = $this->getDefaultOptions();
+ foreach ( $this->getCustomFilters() as $key => $params ) {
+ $opts->add( $key, $params['default'] );
+ }
+
+ $opts = $this->fetchOptionsFromRequest( $opts );
+
+ // Give precedence to subpage syntax
+ if ( $parameters !== null ) {
+ $this->parseParameters( $parameters, $opts );
+ }
+
+ $this->validateOptions( $opts );
+
+ return $opts;
+ }
+
+ /**
+ * Get a FormOptions object containing the default options. By default returns some basic options,
+ * you might want to not call parent method and discard them, or to override default values.
+ *
+ * @return FormOptions
+ */
+ public function getDefaultOptions() {
+ $opts = new FormOptions();
+
+ $opts->add( 'hideminor', false );
+ $opts->add( 'hidebots', false );
+ $opts->add( 'hideanons', false );
+ $opts->add( 'hideliu', false );
+ $opts->add( 'hidepatrolled', false );
+ $opts->add( 'hidemyself', false );
+
+ $opts->add( 'namespace', '', FormOptions::INTNULL );
+ $opts->add( 'invert', false );
+ $opts->add( 'associated', false );
+
+ return $opts;
+ }
+
+ /**
+ * Get custom show/hide filters
+ *
+ * @return array Map of filter URL param names to properties (msg/default)
+ */
+ protected function getCustomFilters() {
+ if ( $this->customFilters === null ) {
+ $this->customFilters = array();
+ wfRunHooks( 'ChangesListSpecialPageFilters', array( $this, &$this->customFilters ) );
+ }
+
+ return $this->customFilters;
+ }
+
+ /**
+ * Fetch values for a FormOptions object from the WebRequest associated with this instance.
+ *
+ * Intended for subclassing, e.g. to add a backwards-compatibility layer.
+ *
+ * @param FormOptions $opts
+ * @return FormOptions
+ */
+ protected function fetchOptionsFromRequest( $opts ) {
+ $opts->fetchValuesFromRequest( $this->getRequest() );
+
+ return $opts;
+ }
+
+ /**
+ * 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 ) {
+ // nothing by default
+ }
+
+ /**
+ * Validate a FormOptions object generated by getDefaultOptions() with values already populated.
+ *
+ * @param FormOptions $opts
+ */
+ public function validateOptions( FormOptions $opts ) {
+ // nothing by default
+ }
+
+ /**
+ * Return an array of conditions depending of options set in $opts
+ *
+ * @param FormOptions $opts
+ * @return array
+ */
+ public function buildMainQueryConds( FormOptions $opts ) {
+ $dbr = $this->getDB();
+ $user = $this->getUser();
+ $conds = array();
+
+ // It makes no sense to hide both anons and logged-in users. When this occurs, try a guess on
+ // what the user meant and either show only bots or force anons to be shown.
+ $botsonly = false;
+ $hideanons = $opts['hideanons'];
+ if ( $opts['hideanons'] && $opts['hideliu'] ) {
+ if ( $opts['hidebots'] ) {
+ $hideanons = false;
+ } else {
+ $botsonly = true;
+ }
+ }
+
+ // Toggles
+ if ( $opts['hideminor'] ) {
+ $conds['rc_minor'] = 0;
+ }
+ if ( $opts['hidebots'] ) {
+ $conds['rc_bot'] = 0;
+ }
+ if ( $user->useRCPatrol() && $opts['hidepatrolled'] ) {
+ $conds['rc_patrolled'] = 0;
+ }
+ if ( $botsonly ) {
+ $conds['rc_bot'] = 1;
+ } else {
+ if ( $opts['hideliu'] ) {
+ $conds[] = 'rc_user = 0';
+ }
+ if ( $hideanons ) {
+ $conds[] = 'rc_user != 0';
+ }
+ }
+ if ( $opts['hidemyself'] ) {
+ if ( $user->getId() ) {
+ $conds[] = 'rc_user != ' . $dbr->addQuotes( $user->getId() );
+ } else {
+ $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $user->getName() );
+ }
+ }
+
+ // Namespace filtering
+ if ( $opts['namespace'] !== '' ) {
+ $selectedNS = $dbr->addQuotes( $opts['namespace'] );
+ $operator = $opts['invert'] ? '!=' : '=';
+ $boolean = $opts['invert'] ? 'AND' : 'OR';
+
+ // Namespace association (bug 2429)
+ if ( !$opts['associated'] ) {
+ $condition = "rc_namespace $operator $selectedNS";
+ } else {
+ // Also add the associated namespace
+ $associatedNS = $dbr->addQuotes(
+ MWNamespace::getAssociated( $opts['namespace'] )
+ );
+ $condition = "(rc_namespace $operator $selectedNS "
+ . $boolean
+ . " rc_namespace $operator $associatedNS)";
+ }
+
+ $conds[] = $condition;
+ }
+
+ return $conds;
+ }
+
+ /**
+ * Process the query
+ *
+ * @param array $conds
+ * @param FormOptions $opts
+ * @return bool|ResultWrapper Result or false
+ */
+ public function doMainQuery( $conds, $opts ) {
+ $tables = array( 'recentchanges' );
+ $fields = RecentChange::selectFields();
+ $query_options = array();
+ $join_conds = array();
+
+ ChangeTags::modifyDisplayQuery(
+ $tables,
+ $fields,
+ $conds,
+ $join_conds,
+ $query_options,
+ ''
+ );
+
+ if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
+ $opts )
+ ) {
+ return false;
+ }
+
+ $dbr = $this->getDB();
+
+ return $dbr->select(
+ $tables,
+ $fields,
+ $conds,
+ __METHOD__,
+ $query_options,
+ $join_conds
+ );
+ }
+
+ protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
+ return wfRunHooks(
+ 'ChangesListSpecialPageQuery',
+ array( $this->getName(), &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts )
+ );
+ }
+
+ /**
+ * Return a DatabaseBase object for reading
+ *
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ return wfGetDB( DB_SLAVE );
+ }
+
+ /**
+ * Send output to the OutputPage object, only called if not used feeds
+ *
+ * @param ResultWrapper $rows Database rows
+ * @param FormOptions $opts
+ */
+ public function webOutput( $rows, $opts ) {
+ if ( !$this->including() ) {
+ $this->outputFeedLinks();
+ $this->doHeader( $opts, $rows->numRows() );
+ }
+
+ $this->outputChangesList( $rows, $opts );
+ }
+
+ /**
+ * Output feed links.
+ */
+ public function outputFeedLinks() {
+ // nothing by default
+ }
+
+ /**
+ * Build and output the actual changes list.
+ *
+ * @param array $rows Database rows
+ * @param FormOptions $opts
+ */
+ abstract public function outputChangesList( $rows, $opts );
+
+ /**
+ * 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 );
+
+ // @todo Lots of stuff should be done here.
+
+ $this->setBottomText( $opts );
+ }
+
+ /**
+ * Send the text to be displayed before the options. Should use $this->getOutput()->addWikiText()
+ * or similar methods to print the text.
+ *
+ * @param FormOptions $opts
+ */
+ function setTopText( FormOptions $opts ) {
+ // nothing by default
+ }
+
+ /**
+ * Send the text to be displayed after the options. Should use $this->getOutput()->addWikiText()
+ * or similar methods to print the text.
+ *
+ * @param FormOptions $opts
+ */
+ function setBottomText( FormOptions $opts ) {
+ // nothing by default
+ }
+
+ /**
+ * Get options to be displayed in a form
+ * @todo This should handle options returned by getDefaultOptions().
+ * @todo Not called by anything, should be called by something… doHeader() maybe?
+ *
+ * @param FormOptions $opts
+ * @return array
+ */
+ function getExtraOptions( $opts ) {
+ return array();
+ }
+
+ /**
+ * Return the legend displayed within the fieldset
+ * @todo This should not be static, then we can drop the parameter
+ * @todo Not called by anything, should be called by doHeader()
+ *
+ * @param IContextSource $context The object available as $this in non-static functions
+ * @return string
+ */
+ public static function makeLegend( IContextSource $context ) {
+ $user = $context->getUser();
+ # The legend showing what the letters and stuff mean
+ $legend = Html::openElement( 'dl' ) . "\n";
+ # Iterates through them and gets the messages for both letter and tooltip
+ $legendItems = $context->getConfig()->get( 'RecentChangesFlags' );
+ if ( !( $user->useRCPatrol() || $user->useNPPatrol() ) ) {
+ unset( $legendItems['unpatrolled'] );
+ }
+ foreach ( $legendItems as $key => $item ) { # generate items of the legend
+ $label = isset( $item['legend'] ) ? $item['legend'] : $item['title'];
+ $letter = $item['letter'];
+ $cssClass = isset( $item['class'] ) ? $item['class'] : $key;
+
+ $legend .= Html::element( 'dt',
+ array( 'class' => $cssClass ), $context->msg( $letter )->text()
+ ) . "\n" .
+ Html::rawElement( 'dd', array(),
+ $context->msg( $label )->parse()
+ ) . "\n";
+ }
+ # (+-123)
+ $legend .= Html::rawElement( 'dt',
+ array( 'class' => 'mw-plusminus-pos' ),
+ $context->msg( 'recentchanges-legend-plusminus' )->parse()
+ ) . "\n";
+ $legend .= Html::element(
+ 'dd',
+ array( 'class' => 'mw-changeslist-legend-plusminus' ),
+ $context->msg( 'recentchanges-label-plusminus' )->text()
+ ) . "\n";
+ $legend .= Html::closeElement( 'dl' ) . "\n";
+
+ # Collapsibility
+ $legend =
+ '<div class="mw-changeslist-legend">' .
+ $context->msg( 'recentchanges-legend-heading' )->parse() .
+ '<div class="mw-collapsible-content">' . $legend . '</div>' .
+ '</div>';
+
+ return $legend;
+ }
+
+ /**
+ * Add page-specific modules.
+ */
+ protected function addModules() {
+ $out = $this->getOutput();
+ // Styles and behavior for the legend box (see makeLegend())
+ $out->addModuleStyles( 'mediawiki.special.changeslist.legend' );
+ $out->addModules( 'mediawiki.special.changeslist.legend.js' );
+ }
+
+ protected function getGroupName() {
+ return 'changes';
+ }
+}
diff --git a/includes/specialpage/FormSpecialPage.php b/includes/specialpage/FormSpecialPage.php
new file mode 100644
index 00000000..bf86ab2e
--- /dev/null
+++ b/includes/specialpage/FormSpecialPage.php
@@ -0,0 +1,193 @@
+<?php
+/**
+ * Special page which uses an HTMLForm to handle processing.
+ *
+ * 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 which uses an HTMLForm to handle processing. This is mostly a
+ * clone of FormAction. More special pages should be built this way; maybe this could be
+ * a new structure for SpecialPages.
+ *
+ * @ingroup SpecialPage
+ */
+abstract class FormSpecialPage extends SpecialPage {
+ /**
+ * The sub-page of the special page.
+ * @var string
+ */
+ protected $par = null;
+
+ /**
+ * Get an HTMLForm descriptor array
+ * @return array
+ */
+ abstract protected function getFormFields();
+
+ /**
+ * Add pre-text to the form
+ * @return string HTML which will be sent to $form->addPreText()
+ */
+ protected function preText() {
+ return '';
+ }
+
+ /**
+ * Add post-text to the form
+ * @return string HTML which will be sent to $form->addPostText()
+ */
+ protected function postText() {
+ return '';
+ }
+
+ /**
+ * Play with the HTMLForm if you need to more substantially
+ * @param HTMLForm $form
+ */
+ protected function alterForm( HTMLForm $form ) {
+ }
+
+ /**
+ * Get message prefix for HTMLForm
+ *
+ * @since 1.21
+ * @return string
+ */
+ protected function getMessagePrefix() {
+ return strtolower( $this->getName() );
+ }
+
+ /**
+ * Get the HTMLForm to control behavior
+ * @return HTMLForm|null
+ */
+ protected function getForm() {
+ $this->fields = $this->getFormFields();
+
+ $form = new HTMLForm( $this->fields, $this->getContext(), $this->getMessagePrefix() );
+ $form->setSubmitCallback( array( $this, 'onSubmit' ) );
+ // If the form is a compact vertical form, then don't output this ugly
+ // fieldset surrounding it.
+ // XXX Special pages can setDisplayFormat to 'vform' in alterForm(), but that
+ // is called after this.
+ if ( !$form->isVForm() ) {
+ $form->setWrapperLegendMsg( $this->getMessagePrefix() . '-legend' );
+ }
+
+ $headerMsg = $this->msg( $this->getMessagePrefix() . '-text' );
+ if ( !$headerMsg->isDisabled() ) {
+ $form->addHeaderText( $headerMsg->parseAsBlock() );
+ }
+
+ // Retain query parameters (uselang etc)
+ $params = array_diff_key(
+ $this->getRequest()->getQueryValues(), array( 'title' => null ) );
+ $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
+
+ $form->addPreText( $this->preText() );
+ $form->addPostText( $this->postText() );
+ $this->alterForm( $form );
+
+ // Give hooks a chance to alter the form, adding extra fields or text etc
+ wfRunHooks( 'SpecialPageBeforeFormDisplay', array( $this->getName(), &$form ) );
+
+ return $form;
+ }
+
+ /**
+ * Process the form on POST submission.
+ * @param array $data
+ * @param HTMLForm $form
+ * @return bool|string|array|Status As documented for HTMLForm::trySubmit.
+ */
+ abstract public function onSubmit( array $data /* $form = null */ );
+
+ /**
+ * Do something exciting on successful processing of the form, most likely to show a
+ * confirmation message
+ * @since 1.22 Default is to do nothing
+ */
+ public function onSuccess() {
+ }
+
+ /**
+ * Basic SpecialPage workflow: get a form, send it to the user; get some data back,
+ *
+ * @param string $par Subpage string if one was specified
+ */
+ public function execute( $par ) {
+ $this->setParameter( $par );
+ $this->setHeaders();
+
+ // This will throw exceptions if there's a problem
+ $this->checkExecutePermissions( $this->getUser() );
+
+ $form = $this->getForm();
+ if ( $form->show() ) {
+ $this->onSuccess();
+ }
+ }
+
+ /**
+ * Maybe do something interesting with the subpage parameter
+ * @param string $par
+ */
+ protected function setParameter( $par ) {
+ $this->par = $par;
+ }
+
+ /**
+ * Called from execute() to check if the given user can perform this action.
+ * Failures here must throw subclasses of ErrorPageError.
+ * @param User $user
+ * @throws UserBlockedError
+ * @return bool True
+ */
+ protected function checkExecutePermissions( User $user ) {
+ $this->checkPermissions();
+
+ if ( $this->requiresUnblock() && $user->isBlocked() ) {
+ $block = $user->getBlock();
+ throw new UserBlockedError( $block );
+ }
+
+ if ( $this->requiresWrite() ) {
+ $this->checkReadOnly();
+ }
+
+ return true;
+ }
+
+ /**
+ * Whether this action requires the wiki not to be locked
+ * @return bool
+ */
+ public function requiresWrite() {
+ return true;
+ }
+
+ /**
+ * Whether this action cannot be executed by a blocked user
+ * @return bool
+ */
+ public function requiresUnblock() {
+ return true;
+ }
+}
diff --git a/includes/ImageQueryPage.php b/includes/specialpage/ImageQueryPage.php
index 75f7ba64..272d5337 100644
--- a/includes/ImageQueryPage.php
+++ b/includes/specialpage/ImageQueryPage.php
@@ -36,23 +36,27 @@ abstract class ImageQueryPage extends QueryPage {
* @param OutputPage $out OutputPage to print to
* @param Skin $skin User skin to use [unused]
* @param DatabaseBase $dbr (read) connection to use
- * @param int $res Result pointer
+ * @param ResultWrapper $res Result pointer
* @param int $num Number of available result rows
* @param int $offset Paging offset
*/
protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
if ( $num > 0 ) {
- $gallery = ImageGalleryBase::factory();
- $gallery->setContext( $this->getContext() );
+ $gallery = ImageGalleryBase::factory( false, $this->getContext() );
# $res might contain the whole 1,000 rows, so we read up to
# $num [should update this to use a Pager]
- for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
+ $i = 0;
+ foreach ( $res as $row ) {
+ $i++;
$namespace = isset( $row->namespace ) ? $row->namespace : NS_FILE;
$title = Title::makeTitleSafe( $namespace, $row->title );
if ( $title instanceof Title && $title->getNamespace() == NS_FILE ) {
$gallery->add( $title, $this->getCellHtml( $row ) );
}
+ if ( $i === $num ) {
+ break;
+ }
}
$out->addHTML( $gallery->toHtml() );
@@ -60,7 +64,8 @@ abstract class ImageQueryPage extends QueryPage {
}
// Gotta override this since it's abstract
- function formatResult( $skin, $result ) { }
+ function formatResult( $skin, $result ) {
+ }
/**
* Get additional HTML to be shown in a results' cell
diff --git a/includes/specialpage/IncludableSpecialPage.php b/includes/specialpage/IncludableSpecialPage.php
new file mode 100644
index 00000000..2f7f69ce
--- /dev/null
+++ b/includes/specialpage/IncludableSpecialPage.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Shortcut to construct an includable special page.
+ *
+ * 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
+ */
+
+/**
+ * Shortcut to construct an includable special page.
+ *
+ * @ingroup SpecialPage
+ */
+class IncludableSpecialPage extends SpecialPage {
+ function __construct(
+ $name, $restriction = '', $listed = true, $function = false, $file = 'default'
+ ) {
+ parent::__construct( $name, $restriction, $listed, $function, $file, true );
+ }
+
+ public function isIncludable() {
+ return true;
+ }
+}
diff --git a/includes/PageQueryPage.php b/includes/specialpage/PageQueryPage.php
index 61a535d6..afc02271 100644
--- a/includes/PageQueryPage.php
+++ b/includes/specialpage/PageQueryPage.php
@@ -28,6 +28,28 @@
*/
abstract class PageQueryPage extends QueryPage {
/**
+ * Run a LinkBatch to pre-cache LinkCache information,
+ * like page existence and information for stub color and redirect hints.
+ * This should be done for live data and cached data.
+ *
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
+ */
+ public function preprocessResults( $db, $res ) {
+ if ( !$res->numRows() ) {
+ return;
+ }
+
+ $batch = new LinkBatch();
+ foreach ( $res as $row ) {
+ $batch->add( $row->namespace, $row->title );
+ }
+ $batch->execute();
+
+ $res->seek( 0 );
+ }
+
+ /**
* Format the result as a simple link to the page
*
* @param Skin $skin
@@ -41,7 +63,7 @@ abstract class PageQueryPage extends QueryPage {
if ( $title instanceof Title ) {
$text = $wgContLang->convert( $title->getPrefixedText() );
- return Linker::linkKnown( $title, htmlspecialchars( $text ) );
+ return Linker::link( $title, htmlspecialchars( $text ) );
} else {
return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
Linker::getInvalidTitleDescription( $this->getContext(), $row->namespace, $row->title ) );
diff --git a/includes/QueryPage.php b/includes/specialpage/QueryPage.php
index 51b5706f..b229e06e 100644
--- a/includes/QueryPage.php
+++ b/includes/specialpage/QueryPage.php
@@ -22,77 +22,20 @@
*/
/**
- * List of query page classes and their associated special pages,
- * for periodic updates.
- *
- * DO NOT CHANGE THIS LIST without testing that
- * maintenance/updateSpecialPages.php still works.
- */
-global $wgQueryPages; // not redundant
-$wgQueryPages = array(
-// QueryPage subclass Special page name Limit (false for none, none for the default)
-// ----------------------------------------------------------------------------
- array( 'AncientPagesPage', 'Ancientpages' ),
- array( 'BrokenRedirectsPage', 'BrokenRedirects' ),
- array( 'DeadendPagesPage', 'Deadendpages' ),
- array( 'DoubleRedirectsPage', 'DoubleRedirects' ),
- array( 'FileDuplicateSearchPage', 'FileDuplicateSearch' ),
- array( 'LinkSearchPage', 'LinkSearch' ),
- array( 'ListredirectsPage', 'Listredirects' ),
- array( 'LonelyPagesPage', 'Lonelypages' ),
- array( 'LongPagesPage', 'Longpages' ),
- array( 'MIMEsearchPage', 'MIMEsearch' ),
- array( 'MostcategoriesPage', 'Mostcategories' ),
- array( 'MostimagesPage', 'Mostimages' ),
- array( 'MostinterwikisPage', 'Mostinterwikis' ),
- array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ),
- array( 'MostlinkedtemplatesPage', 'Mostlinkedtemplates' ),
- array( 'MostlinkedPage', 'Mostlinked' ),
- array( 'MostrevisionsPage', 'Mostrevisions' ),
- array( 'FewestrevisionsPage', 'Fewestrevisions' ),
- array( 'ShortPagesPage', 'Shortpages' ),
- array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ),
- array( 'UncategorizedPagesPage', 'Uncategorizedpages' ),
- array( 'UncategorizedImagesPage', 'Uncategorizedimages' ),
- array( 'UncategorizedTemplatesPage', 'Uncategorizedtemplates' ),
- array( 'UnusedCategoriesPage', 'Unusedcategories' ),
- array( 'UnusedimagesPage', 'Unusedimages' ),
- array( 'WantedCategoriesPage', 'Wantedcategories' ),
- array( 'WantedFilesPage', 'Wantedfiles' ),
- array( 'WantedPagesPage', 'Wantedpages' ),
- array( 'WantedTemplatesPage', 'Wantedtemplates' ),
- array( 'UnwatchedPagesPage', 'Unwatchedpages' ),
- array( 'UnusedtemplatesPage', 'Unusedtemplates' ),
- array( 'WithoutInterwikiPage', 'Withoutinterwiki' ),
-);
-wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) );
-
-global $wgDisableCounters;
-if ( !$wgDisableCounters ) {
- $wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' );
-}
-
-/**
* This is a class for doing query pages; since they're almost all the same,
* we factor out some of the functionality into a superclass, and let
* subclasses derive from it.
* @ingroup SpecialPage
*/
abstract class QueryPage extends SpecialPage {
- /**
- * Whether or not we want plain listoutput rather than an ordered list
- *
- * @var bool
- */
- var $listoutput = false;
+ /** @var bool Whether or not we want plain listoutput rather than an ordered list */
+ protected $listoutput = false;
- /**
- * The offset and limit in use, as passed to the query() function
- *
- * @var int
- */
- var $offset = 0;
- var $limit = 0;
+ /** @var int The offset and limit in use, as passed to the query() function */
+ protected $offset = 0;
+
+ /** @var int */
+ protected $limit = 0;
/**
* The number of rows returned by the query. Reading this variable
@@ -104,11 +47,71 @@ abstract class QueryPage extends SpecialPage {
protected $cachedTimestamp = null;
/**
- * Wheter to show prev/next links
+ * Whether to show prev/next links
*/
protected $shownavigation = true;
/**
+ * Get a list of query page classes and their associated special pages,
+ * for periodic updates.
+ *
+ * DO NOT CHANGE THIS LIST without testing that
+ * maintenance/updateSpecialPages.php still works.
+ * @return array
+ */
+ public static function getPages() {
+ global $wgDisableCounters;
+ static $qp = null;
+
+ if ( $qp === null ) {
+ // QueryPage subclass, Special page name
+ $qp = array(
+ array( 'AncientPagesPage', 'Ancientpages' ),
+ array( 'BrokenRedirectsPage', 'BrokenRedirects' ),
+ array( 'DeadendPagesPage', 'Deadendpages' ),
+ array( 'DoubleRedirectsPage', 'DoubleRedirects' ),
+ array( 'FileDuplicateSearchPage', 'FileDuplicateSearch' ),
+ array( 'ListDuplicatedFilesPage', 'ListDuplicatedFiles'),
+ array( 'LinkSearchPage', 'LinkSearch' ),
+ array( 'ListredirectsPage', 'Listredirects' ),
+ array( 'LonelyPagesPage', 'Lonelypages' ),
+ array( 'LongPagesPage', 'Longpages' ),
+ array( 'MediaStatisticsPage', 'MediaStatistics' ),
+ array( 'MIMEsearchPage', 'MIMEsearch' ),
+ array( 'MostcategoriesPage', 'Mostcategories' ),
+ array( 'MostimagesPage', 'Mostimages' ),
+ array( 'MostinterwikisPage', 'Mostinterwikis' ),
+ array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ),
+ array( 'MostlinkedtemplatesPage', 'Mostlinkedtemplates' ),
+ array( 'MostlinkedPage', 'Mostlinked' ),
+ array( 'MostrevisionsPage', 'Mostrevisions' ),
+ array( 'FewestrevisionsPage', 'Fewestrevisions' ),
+ array( 'ShortPagesPage', 'Shortpages' ),
+ array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ),
+ array( 'UncategorizedPagesPage', 'Uncategorizedpages' ),
+ array( 'UncategorizedImagesPage', 'Uncategorizedimages' ),
+ array( 'UncategorizedTemplatesPage', 'Uncategorizedtemplates' ),
+ array( 'UnusedCategoriesPage', 'Unusedcategories' ),
+ array( 'UnusedimagesPage', 'Unusedimages' ),
+ array( 'WantedCategoriesPage', 'Wantedcategories' ),
+ array( 'WantedFilesPage', 'Wantedfiles' ),
+ array( 'WantedPagesPage', 'Wantedpages' ),
+ array( 'WantedTemplatesPage', 'Wantedtemplates' ),
+ array( 'UnwatchedPagesPage', 'Unwatchedpages' ),
+ array( 'UnusedtemplatesPage', 'Unusedtemplates' ),
+ array( 'WithoutInterwikiPage', 'Withoutinterwiki' ),
+ );
+ wfRunHooks( 'wgQueryPages', array( &$qp ) );
+
+ if ( !$wgDisableCounters ) {
+ $qp[] = array( 'PopularPagesPage', 'Popularpages' );
+ }
+ }
+
+ return $qp;
+ }
+
+ /**
* A mutator for $this->listoutput;
*
* @param bool $bool
@@ -155,7 +158,8 @@ abstract class QueryPage extends SpecialPage {
*/
function getSQL() {
/* Implement getQueryInfo() instead */
- throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor getQuery() properly" );
+ throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor "
+ . "getQuery() properly" );
}
/**
@@ -200,8 +204,7 @@ abstract class QueryPage extends SpecialPage {
* @return bool
*/
function isExpensive() {
- global $wgDisableQueryPages;
- return $wgDisableQueryPages;
+ return $this->getConfig()->get( 'DisableQueryPages' );
}
/**
@@ -222,9 +225,7 @@ abstract class QueryPage extends SpecialPage {
* @return bool
*/
function isCached() {
- global $wgMiserMode;
-
- return $this->isExpensive() && $wgMiserMode;
+ return $this->isExpensive() && $this->getConfig()->get( 'MiserMode' );
}
/**
@@ -294,14 +295,11 @@ abstract class QueryPage extends SpecialPage {
$fname = get_class( $this ) . '::recache';
$dbw = wfGetDB( DB_MASTER );
- $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), __METHOD__, 'vslow' ) );
- if ( !$dbw || !$dbr ) {
+ if ( !$dbw ) {
return false;
}
try {
- # Clear out any old cached data
- $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname );
# Do query
$res = $this->reallyDoQuery( $limit, false );
$num = false;
@@ -309,7 +307,7 @@ abstract class QueryPage extends SpecialPage {
$num = $res->numRows();
# Fetch results
$vals = array();
- while ( $res && $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( isset( $row->value ) ) {
if ( $this->usesTimestamps() ) {
$value = wfTimestamp( TS_UNIX,
@@ -327,6 +325,9 @@ abstract class QueryPage extends SpecialPage {
'qc_value' => $value );
}
+ $dbw->startAtomic( __METHOD__ );
+ # Clear out any old cached data
+ $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname );
# Save results into the querycache table on the master
if ( count( $vals ) ) {
$dbw->insert( 'querycache', $vals, __METHOD__ );
@@ -336,6 +337,7 @@ abstract class QueryPage extends SpecialPage {
$dbw->insert( 'querycache_info',
array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ),
$fname );
+ $dbw->endAtomic( __METHOD__ );
}
} catch ( DBError $e ) {
if ( !$ignoreErrors ) {
@@ -348,6 +350,14 @@ abstract class QueryPage extends SpecialPage {
}
/**
+ * Get a DB connection to be used for slow recache queries
+ * @return DatabaseBase
+ */
+ function getRecacheDB() {
+ return wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) );
+ }
+
+ /**
* Run the query and return the result
* @param int|bool $limit Numerical limit or false for no limit
* @param int|bool $offset Numerical offset or false for no offset
@@ -356,7 +366,7 @@ abstract class QueryPage extends SpecialPage {
*/
function reallyDoQuery( $limit, $offset = false ) {
$fname = get_class( $this ) . "::reallyDoQuery";
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = $this->getRecacheDB();
$query = $this->getQueryInfo();
$order = $this->getOrderFields();
@@ -396,13 +406,13 @@ abstract class QueryPage extends SpecialPage {
$res = $dbr->query( $sql, $fname );
}
- return $dbr->resultObject( $res );
+ return $res;
}
/**
* Somewhat deprecated, you probably want to be using execute()
* @param int|bool $offset
- * @oaram int|bool $limit
+ * @param int|bool $limit
* @return ResultWrapper
*/
function doQuery( $offset = false, $limit = false ) {
@@ -457,12 +467,9 @@ abstract class QueryPage extends SpecialPage {
/**
* This is the actual workhorse. It does everything needed to make a
* real, honest-to-gosh query page.
- * @para $par
- * @return int
+ * @param string $par
*/
function execute( $par ) {
- global $wgQueryCacheLimit, $wgDisableQueryPageUpdate;
-
$user = $this->getUser();
if ( !$this->userCanExecute( $user ) ) {
$this->displayRestrictionError();
@@ -476,7 +483,7 @@ abstract class QueryPage extends SpecialPage {
if ( $this->isCached() && !$this->isCacheable() ) {
$out->addWikiMsg( 'querypage-disabled' );
- return 0;
+ return;
}
$out->setSyndicated( $this->isSyndicated() );
@@ -485,7 +492,7 @@ abstract class QueryPage extends SpecialPage {
list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset();
}
- // TODO: Use doQuery()
+ // @todo Use doQuery()
if ( !$this->isCached() ) {
# select one extra row for navigation
$res = $this->reallyDoQuery( $this->limit + 1, $this->offset );
@@ -497,7 +504,7 @@ abstract class QueryPage extends SpecialPage {
# Fetch the timestamp of this update
$ts = $this->getCachedTimestamp();
$lang = $this->getLanguage();
- $maxResults = $lang->formatNum( $wgQueryCacheLimit );
+ $maxResults = $lang->formatNum( $this->getConfig()->get( 'QueryCacheLimit' ) );
if ( $ts ) {
$updated = $lang->userTimeAndDate( $ts, $user );
@@ -512,8 +519,13 @@ abstract class QueryPage extends SpecialPage {
# If updates on this page have been disabled, let the user know
# that the data set won't be refreshed for now
- if ( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) {
- $out->wrapWikiMsg( "<div class=\"mw-querypage-no-updates\">\n$1\n</div>", 'querypage-no-updates' );
+ if ( is_array( $this->getConfig()->get( 'DisableQueryPageUpdate' ) )
+ && in_array( $this->getName(), $this->getConfig()->get( 'DisableQueryPageUpdate' ) )
+ ) {
+ $out->wrapWikiMsg(
+ "<div class=\"mw-querypage-no-updates\">\n$1\n</div>",
+ 'querypage-no-updates'
+ );
}
}
}
@@ -529,11 +541,11 @@ abstract class QueryPage extends SpecialPage {
if ( $this->shownavigation ) {
$out->addHTML( $this->getPageHeader() );
if ( $this->numRows > 0 ) {
- $out->addHTML( $this->msg( 'showingresults' )->numParams(
+ $out->addHTML( $this->msg( 'showingresultsinrange' )->numParams(
min( $this->numRows, $this->limit ), # do not show the one extra row, if exist
- $this->offset + 1 )->parseAsBlock() );
+ $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() );
# Disable the "next" link when we reach the end
- $paging = $this->getLanguage()->viewPrevNext( $this->getTitle( $par ), $this->offset,
+ $paging = $this->getLanguage()->viewPrevNext( $this->getPageTitle( $par ), $this->offset,
$this->limit, $this->linkParameters(), ( $this->numRows <= $this->limit ) );
$out->addHTML( '<p>' . $paging . '</p>' );
} else {
@@ -561,8 +573,6 @@ abstract class QueryPage extends SpecialPage {
}
$out->addHTML( Xml::closeElement( 'div' ) );
-
- return min( $this->numRows, $this->limit ); # do not return the one extra row, if exist
}
/**
@@ -572,7 +582,7 @@ abstract class QueryPage extends SpecialPage {
* @param OutputPage $out OutputPage to print to
* @param Skin $skin User skin to use
* @param DatabaseBase $dbr Database (read) connection to use
- * @param int $res Result pointer
+ * @param ResultWrapper $res Result pointer
* @param int $num Number of available result rows
* @param int $offset Paging offset
*/
@@ -587,7 +597,9 @@ abstract class QueryPage extends SpecialPage {
# $res might contain the whole 1,000 rows, so we read up to
# $num [should update this to use a Pager]
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) {
+ // @codingStandardsIgnoreEnd
$line = $this->formatResult( $skin, $row );
if ( $line ) {
$attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
@@ -626,7 +638,7 @@ abstract class QueryPage extends SpecialPage {
}
/**
- * @param $offset
+ * @param int $offset
* @return string
*/
function openList( $offset ) {
@@ -645,7 +657,8 @@ abstract class QueryPage extends SpecialPage {
* @param DatabaseBase $db
* @param ResultWrapper $res
*/
- function preprocessResults( $db, $res ) {}
+ function preprocessResults( $db, $res ) {
+ }
/**
* Similar to above, but packaging in a syndicated feed instead of a web page
@@ -654,17 +667,17 @@ abstract class QueryPage extends SpecialPage {
* @return bool
*/
function doFeed( $class = '', $limit = 50 ) {
- global $wgFeed, $wgFeedClasses, $wgFeedLimit;
-
- if ( !$wgFeed ) {
+ if ( !$this->getConfig()->get( 'Feed' ) ) {
$this->getOutput()->addWikiMsg( 'feed-unavailable' );
return false;
}
- $limit = min( $limit, $wgFeedLimit );
+ $limit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) );
- if ( isset( $wgFeedClasses[$class] ) ) {
- $feed = new $wgFeedClasses[$class](
+ $feedClasses = $this->getConfig()->get( 'FeedClasses' );
+ if ( isset( $feedClasses[$class] ) ) {
+ /** @var RSSFeed|AtomFeed $feed */
+ $feed = new $feedClasses[$class](
$this->feedTitle(),
$this->feedDesc(),
$this->feedUrl() );
@@ -725,9 +738,10 @@ abstract class QueryPage extends SpecialPage {
}
function feedTitle() {
- global $wgLanguageCode, $wgSitename;
$desc = $this->getDescription();
- return "$wgSitename - $desc [$wgLanguageCode]";
+ $code = $this->getConfig()->get( 'LanguageCode' );
+ $sitename = $this->getConfig()->get( 'Sitename' );
+ return "$sitename - $desc [$code]";
}
function feedDesc() {
@@ -735,100 +749,6 @@ abstract class QueryPage extends SpecialPage {
}
function feedUrl() {
- return $this->getTitle()->getFullURL();
- }
-}
-
-/**
- * Class definition for a wanted query page like
- * WantedPages, WantedTemplates, etc
- */
-abstract class WantedQueryPage extends QueryPage {
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- /**
- * Cache page existence for performance
- * @param DatabaseBase $db
- * @param ResultWrapper $res
- */
- function preprocessResults( $db, $res ) {
- if ( !$res->numRows() ) {
- return;
- }
-
- $batch = new LinkBatch;
- foreach ( $res as $row ) {
- $batch->add( $row->namespace, $row->title );
- }
- $batch->execute();
-
- // Back to start for display
- $res->seek( 0 );
- }
-
- /**
- * Should formatResult() always check page existence, even if
- * the results are fresh? This is a (hopefully temporary)
- * kluge for Special:WantedFiles, which may contain false
- * positives for files that exist e.g. in a shared repo (bug
- * 6220).
- * @return bool
- */
- function forceExistenceCheck() {
- return false;
- }
-
- /**
- * Format an individual result
- *
- * @param Skin $skin Skin to use for UI elements
- * @param object $result Result row
- * @return string
- */
- public function formatResult( $skin, $result ) {
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( $title instanceof Title ) {
- if ( $this->isCached() || $this->forceExistenceCheck() ) {
- $pageLink = $title->isKnown()
- ? '<del>' . Linker::link( $title ) . '</del>'
- : Linker::link(
- $title,
- null,
- array(),
- array(),
- array( 'broken' )
- );
- } else {
- $pageLink = Linker::link(
- $title,
- null,
- array(),
- array(),
- array( 'broken' )
- );
- }
- return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) );
- } else {
- return $this->msg( 'wantedpages-badtitle', $result->title )->escaped();
- }
- }
-
- /**
- * Make a "what links here" link for a given title
- *
- * @param Title $title Title to make the link for
- * @param object $result Result row
- * @return string
- */
- private function makeWlhLink( $title, $result ) {
- $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
- $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped();
- return Linker::link( $wlh, $label );
+ return $this->getPageTitle()->getFullURL();
}
}
diff --git a/includes/specialpage/RedirectSpecialPage.php b/includes/specialpage/RedirectSpecialPage.php
new file mode 100644
index 00000000..4226ee02
--- /dev/null
+++ b/includes/specialpage/RedirectSpecialPage.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * Shortcuts to construct a special page alias.
+ *
+ * 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
+ */
+
+/**
+ * Shortcut to construct a special page alias.
+ *
+ * @ingroup SpecialPage
+ */
+abstract class RedirectSpecialPage extends UnlistedSpecialPage {
+ // Query parameters that can be passed through redirects
+ protected $mAllowedRedirectParams = array();
+
+ // Query parameters added by redirects
+ protected $mAddedRedirectParams = array();
+
+ public function execute( $par ) {
+ $redirect = $this->getRedirect( $par );
+ $query = $this->getRedirectQuery();
+ // Redirect to a page title with possible query parameters
+ if ( $redirect instanceof Title ) {
+ $url = $redirect->getFullURL( $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 {
+ $class = get_class( $this );
+ throw new MWException( "RedirectSpecialPage $class doesn't redirect!" );
+ }
+ }
+
+ /**
+ * If the special page is a redirect, then get the Title object it redirects to.
+ * False otherwise.
+ *
+ * @param string $par Subpage string
+ * @return Title|bool
+ */
+ abstract public function getRedirect( $par );
+
+ /**
+ * Return part of the request string for a special redirect page
+ * This allows passing, e.g. action=history to Special:Mypage, etc.
+ *
+ * @return string
+ */
+ public function getRedirectQuery() {
+ $params = array();
+ $request = $this->getRequest();
+
+ foreach ( $this->mAllowedRedirectParams as $arg ) {
+ if ( $request->getVal( $arg, null ) !== null ) {
+ $params[$arg] = $request->getVal( $arg );
+ } elseif ( $request->getArray( $arg, null ) !== null ) {
+ $params[$arg] = $request->getArray( $arg );
+ }
+ }
+
+ foreach ( $this->mAddedRedirectParams as $arg => $val ) {
+ $params[$arg] = $val;
+ }
+
+ return count( $params )
+ ? $params
+ : false;
+ }
+}
+
+/**
+ * @ingroup SpecialPage
+ */
+abstract class SpecialRedirectToSpecial extends RedirectSpecialPage {
+ /** @var string Name of redirect target */
+ protected $redirName;
+
+ /** @var string Name of subpage of redirect target */
+ protected $redirSubpage;
+
+ function __construct(
+ $name, $redirName, $redirSubpage = false,
+ $allowedRedirectParams = array(), $addedRedirectParams = array()
+ ) {
+ parent::__construct( $name );
+ $this->redirName = $redirName;
+ $this->redirSubpage = $redirSubpage;
+ $this->mAllowedRedirectParams = $allowedRedirectParams;
+ $this->mAddedRedirectParams = $addedRedirectParams;
+ }
+
+ public function getRedirect( $subpage ) {
+ if ( $this->redirSubpage === false ) {
+ return SpecialPage::getTitleFor( $this->redirName, $subpage );
+ } else {
+ return SpecialPage::getTitleFor( $this->redirName, $this->redirSubpage );
+ }
+ }
+}
+
+/**
+ * Superclass for any RedirectSpecialPage which redirects the user
+ * to a particular article (as opposed to user contributions, logs, etc.).
+ *
+ * For security reasons these special pages are restricted to pass on
+ * the following subset of GET parameters to the target page while
+ * removing all others:
+ *
+ * - useskin, uselang, printable: to alter the appearance of the resulting page
+ *
+ * - redirect: allows viewing one's user page or talk page even if it is a
+ * redirect.
+ *
+ * - rdfrom: allows redirecting to one's user page or talk page from an
+ * external wiki with the "Redirect from..." notice.
+ *
+ * - limit, offset: Useful for linking to history of one's own user page or
+ * user talk page. For example, this would be a link to "the last edit to your
+ * user talk page in the year 2010":
+ * http://en.wikipedia.org/wiki/Special:MyPage?offset=20110000000000&limit=1&action=history
+ *
+ * - feed: would allow linking to the current user's RSS feed for their user
+ * talk page:
+ * http://en.wikipedia.org/w/index.php?title=Special:MyTalk&action=history&feed=rss
+ *
+ * - preloadtitle: Can be used to provide a default section title for a
+ * preloaded new comment on one's own talk page.
+ *
+ * - summary : Can be used to provide a default edit summary for a preloaded
+ * edit to one's own user page or talk page.
+ *
+ * - preview: Allows showing/hiding preview on first edit regardless of user
+ * preference, useful for preloaded edits where you know preview wouldn't be
+ * useful.
+ *
+ * - redlink: Affects the message the user sees if their talk page/user talk
+ * page does not currently exist. Avoids confusion for newbies with no user
+ * pages over why they got a "permission error" following this link:
+ * http://en.wikipedia.org/w/index.php?title=Special:MyPage&redlink=1
+ *
+ * - debug: determines whether the debug parameter is passed to load.php,
+ * which disables reformatting and allows scripts to be debugged. Useful
+ * when debugging scripts that manipulate one's own user page or talk page.
+ *
+ * @par Hook extension:
+ * Extensions can add to the redirect parameters list by using the hook
+ * RedirectSpecialArticleRedirectParams
+ *
+ * This hook allows extensions which add GET parameters like FlaggedRevs to
+ * retain those parameters when redirecting using special pages.
+ *
+ * @par Hook extension example:
+ * @code
+ * $wgHooks['RedirectSpecialArticleRedirectParams'][] =
+ * 'MyExtensionHooks::onRedirectSpecialArticleRedirectParams';
+ * public static function onRedirectSpecialArticleRedirectParams( &$redirectParams ) {
+ * $redirectParams[] = 'stable';
+ * return true;
+ * }
+ * @endcode
+ *
+ * @ingroup SpecialPage
+ */
+abstract class RedirectSpecialArticle extends RedirectSpecialPage {
+ function __construct( $name ) {
+ parent::__construct( $name );
+ $redirectParams = array(
+ 'action',
+ 'redirect', 'rdfrom',
+ # Options for preloaded edits
+ 'preload', 'preloadparams', 'editintro', 'preloadtitle', 'summary', 'nosummary',
+ # Options for overriding user settings
+ 'preview', 'minor', 'watchthis',
+ # Options for history/diffs
+ 'section', 'oldid', 'diff', 'dir',
+ 'limit', 'offset', 'feed',
+ # Misc options
+ 'redlink', 'debug',
+ # Options for action=raw; missing ctype can break JS or CSS in some browsers
+ 'ctype', 'maxage', 'smaxage',
+ );
+
+ wfRunHooks( "RedirectSpecialArticleRedirectParams", array( &$redirectParams ) );
+ $this->mAllowedRedirectParams = $redirectParams;
+ }
+}
diff --git a/includes/specialpage/SpecialPage.php b/includes/specialpage/SpecialPage.php
new file mode 100644
index 00000000..c0a94af1
--- /dev/null
+++ b/includes/specialpage/SpecialPage.php
@@ -0,0 +1,663 @@
+<?php
+/**
+ * Parent class for all special pages.
+ *
+ * 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
+ */
+
+/**
+ * Parent class for all special pages.
+ *
+ * Includes some static functions for handling the special page list deprecated
+ * in favor of SpecialPageFactory.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPage {
+ // The canonical name of this special page
+ // Also used for the default <h1> heading, @see getDescription()
+ protected $mName;
+
+ // The local name of this special page
+ private $mLocalName;
+
+ // Minimum user level required to access this page, or "" for anyone.
+ // Also used to categorise the pages in Special:Specialpages
+ protected $mRestriction;
+
+ // Listed in Special:Specialpages?
+ private $mListed;
+
+ // Whether or not this special page is being included from an article
+ protected $mIncluding;
+
+ // Whether the special page can be included in an article
+ protected $mIncludable;
+
+ /**
+ * Current request context
+ * @var IContextSource
+ */
+ protected $mContext;
+
+ /**
+ * Get a localised Title object for a specified special page name
+ *
+ * @param string $name
+ * @param string|bool $subpage Subpage string, or false to not use a subpage
+ * @param string $fragment The link fragment (after the "#")
+ * @return Title
+ * @throws MWException
+ */
+ public static function getTitleFor( $name, $subpage = false, $fragment = '' ) {
+ $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
+
+ return Title::makeTitle( NS_SPECIAL, $name, $fragment );
+ }
+
+ /**
+ * Get a localised Title object for a page name with a possibly unvalidated subpage
+ *
+ * @param string $name
+ * @param string|bool $subpage Subpage string, or false to not use a subpage
+ * @return Title|null Title object or null if the page doesn't exist
+ */
+ public static function getSafeTitleFor( $name, $subpage = false ) {
+ $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
+ if ( $name ) {
+ return Title::makeTitleSafe( NS_SPECIAL, $name );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Default constructor for special pages
+ * Derivative classes should call this from their constructor
+ * Note that if the user does not have the required level, an error message will
+ * be displayed by the default execute() method, without the global function ever
+ * being called.
+ *
+ * If you override execute(), you can recover the default behavior with userCanExecute()
+ * and displayRestrictionError()
+ *
+ * @param string $name Name of the special page, as seen in links and URLs
+ * @param string $restriction User right required, e.g. "block" or "delete"
+ * @param bool $listed Whether the page is listed in Special:Specialpages
+ * @param callable|bool $function Unused
+ * @param string $file Unused
+ * @param bool $includable Whether the page can be included in normal pages
+ */
+ public function __construct(
+ $name = '', $restriction = '', $listed = true,
+ $function = false, $file = '', $includable = false
+ ) {
+ $this->mName = $name;
+ $this->mRestriction = $restriction;
+ $this->mListed = $listed;
+ $this->mIncludable = $includable;
+ }
+
+ /**
+ * Get the name of this Special Page.
+ * @return string
+ */
+ function getName() {
+ return $this->mName;
+ }
+
+ /**
+ * Get the permission that a user must have to execute this page
+ * @return string
+ */
+ function getRestriction() {
+ return $this->mRestriction;
+ }
+
+ // @todo FIXME: Decide which syntax to use for this, and stick to it
+ /**
+ * Whether this special page is listed in Special:SpecialPages
+ * @since 1.3 (r3583)
+ * @return bool
+ */
+ function isListed() {
+ return $this->mListed;
+ }
+
+ /**
+ * Set whether this page is listed in Special:Specialpages, at run-time
+ * @since 1.3
+ * @param bool $listed
+ * @return bool
+ */
+ function setListed( $listed ) {
+ return wfSetVar( $this->mListed, $listed );
+ }
+
+ /**
+ * Get or set whether this special page is listed in Special:SpecialPages
+ * @since 1.6
+ * @param bool $x
+ * @return bool
+ */
+ function listed( $x = null ) {
+ return wfSetVar( $this->mListed, $x );
+ }
+
+ /**
+ * Whether it's allowed to transclude the special page via {{Special:Foo/params}}
+ * @return bool
+ */
+ public function isIncludable() {
+ return $this->mIncludable;
+ }
+
+ /**
+ * Whether the special page is being evaluated via transclusion
+ * @param bool $x
+ * @return bool
+ */
+ function including( $x = null ) {
+ return wfSetVar( $this->mIncluding, $x );
+ }
+
+ /**
+ * Get the localised name of the special page
+ * @return string
+ */
+ function getLocalName() {
+ if ( !isset( $this->mLocalName ) ) {
+ $this->mLocalName = SpecialPageFactory::getLocalNameFor( $this->mName );
+ }
+
+ return $this->mLocalName;
+ }
+
+ /**
+ * Is this page expensive (for some definition of expensive)?
+ * Expensive pages are disabled or cached in miser mode. Originally used
+ * (and still overridden) by QueryPage and subclasses, moved here so that
+ * Special:SpecialPages can safely call it for all special pages.
+ *
+ * @return bool
+ */
+ public function isExpensive() {
+ return false;
+ }
+
+ /**
+ * Is this page cached?
+ * Expensive pages are cached or disabled in miser mode.
+ * Used by QueryPage and subclasses, moved here so that
+ * Special:SpecialPages can safely call it for all special pages.
+ *
+ * @return bool
+ * @since 1.21
+ */
+ public function isCached() {
+ return false;
+ }
+
+ /**
+ * Can be overridden by subclasses with more complicated permissions
+ * schemes.
+ *
+ * @return bool Should the page be displayed with the restricted-access
+ * pages?
+ */
+ public function isRestricted() {
+ // DWIM: If anons can do something, then it is not restricted
+ return $this->mRestriction != '' && !User::groupHasPermission( '*', $this->mRestriction );
+ }
+
+ /**
+ * Checks if the given user (identified by an object) can execute this
+ * special page (as defined by $mRestriction). Can be overridden by sub-
+ * classes with more complicated permissions schemes.
+ *
+ * @param User $user The user to check
+ * @return bool Does the user have permission to view the page?
+ */
+ public function userCanExecute( User $user ) {
+ return $user->isAllowed( $this->mRestriction );
+ }
+
+ /**
+ * Output an error message telling the user what access level they have to have
+ * @throws PermissionsError
+ */
+ function displayRestrictionError() {
+ throw new PermissionsError( $this->mRestriction );
+ }
+
+ /**
+ * Checks if userCanExecute, and if not throws a PermissionsError
+ *
+ * @since 1.19
+ * @return void
+ * @throws PermissionsError
+ */
+ public function checkPermissions() {
+ if ( !$this->userCanExecute( $this->getUser() ) ) {
+ $this->displayRestrictionError();
+ }
+ }
+
+ /**
+ * If the wiki is currently in readonly mode, throws a ReadOnlyError
+ *
+ * @since 1.19
+ * @return void
+ * @throws ReadOnlyError
+ */
+ public function checkReadOnly() {
+ if ( wfReadOnly() ) {
+ throw new ReadOnlyError;
+ }
+ }
+
+ /**
+ * If the user is not logged in, throws UserNotLoggedIn error
+ *
+ * The user will be redirected to Special:Userlogin with the given message as an error on
+ * the form.
+ *
+ * @since 1.23
+ * @param string $reasonMsg [optional] Message key to be displayed on login page
+ * @param string $titleMsg [optional] Passed on to UserNotLoggedIn constructor
+ * @throws UserNotLoggedIn
+ */
+ public function requireLogin(
+ $reasonMsg = 'exception-nologin-text', $titleMsg = 'exception-nologin'
+ ) {
+ if ( $this->getUser()->isAnon() ) {
+ throw new UserNotLoggedIn( $reasonMsg, $titleMsg );
+ }
+ }
+
+ /**
+ * Return an array of subpages beginning with $search that this special page will accept.
+ *
+ * For example, if a page supports subpages "foo", "bar" and "baz" (as in Special:PageName/foo,
+ * etc.):
+ *
+ * - `prefixSearchSubpages( "ba" )` should return `array( "bar", "baz" )`
+ * - `prefixSearchSubpages( "f" )` should return `array( "foo" )`
+ * - `prefixSearchSubpages( "z" )` should return `array()`
+ * - `prefixSearchSubpages( "" )` should return `array( foo", "bar", "baz" )`
+ *
+ * @param string $search Prefix to search for
+ * @param int $limit Maximum number of results to return
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ return array();
+ }
+
+ /**
+ * Helper function for implementations of prefixSearchSubpages() that
+ * filter the values in memory (as oppposed to making a query).
+ *
+ * @since 1.24
+ * @param string $search
+ * @param int $limit
+ * @param array $subpages
+ * @return string[]
+ */
+ protected static function prefixSearchArray( $search, $limit, array $subpages ) {
+ $escaped = preg_quote( $search, '/' );
+ return array_slice( preg_grep( "/^$escaped/i", $subpages ), 0, $limit );
+ }
+
+ /**
+ * Sets headers - this should be called from the execute() method of all derived classes!
+ */
+ function setHeaders() {
+ $out = $this->getOutput();
+ $out->setArticleRelated( false );
+ $out->setRobotPolicy( $this->getRobotPolicy() );
+ $out->setPageTitle( $this->getDescription() );
+ if ( $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $out->addModuleStyles( array(
+ 'mediawiki.ui.input',
+ 'mediawiki.ui.checkbox',
+ ) );
+ }
+ }
+
+ /**
+ * Entry point.
+ *
+ * @since 1.20
+ *
+ * @param string|null $subPage
+ */
+ final public function run( $subPage ) {
+ /**
+ * Gets called before @see SpecialPage::execute.
+ *
+ * @since 1.20
+ *
+ * @param SpecialPage $this
+ * @param string|null $subPage
+ */
+ wfRunHooks( 'SpecialPageBeforeExecute', array( $this, $subPage ) );
+
+ $this->beforeExecute( $subPage );
+ $this->execute( $subPage );
+ $this->afterExecute( $subPage );
+
+ /**
+ * Gets called after @see SpecialPage::execute.
+ *
+ * @since 1.20
+ *
+ * @param SpecialPage $this
+ * @param string|null $subPage
+ */
+ wfRunHooks( 'SpecialPageAfterExecute', array( $this, $subPage ) );
+ }
+
+ /**
+ * Gets called before @see SpecialPage::execute.
+ *
+ * @since 1.20
+ *
+ * @param string|null $subPage
+ */
+ protected function beforeExecute( $subPage ) {
+ // No-op
+ }
+
+ /**
+ * Gets called after @see SpecialPage::execute.
+ *
+ * @since 1.20
+ *
+ * @param string|null $subPage
+ */
+ protected function afterExecute( $subPage ) {
+ // No-op
+ }
+
+ /**
+ * Default execute method
+ * Checks user permissions
+ *
+ * This must be overridden by subclasses; it will be made abstract in a future version
+ *
+ * @param string|null $subPage
+ */
+ public function execute( $subPage ) {
+ $this->setHeaders();
+ $this->checkPermissions();
+ $this->outputHeader();
+ }
+
+ /**
+ * Outputs a summary message on top of special pages
+ * Per default the message key is the canonical name of the special page
+ * May be overridden, i.e. by extensions to stick with the naming conventions
+ * for message keys: 'extensionname-xxx'
+ *
+ * @param string $summaryMessageKey Message key of the summary
+ */
+ function outputHeader( $summaryMessageKey = '' ) {
+ global $wgContLang;
+
+ if ( $summaryMessageKey == '' ) {
+ $msg = $wgContLang->lc( $this->getName() ) . '-summary';
+ } else {
+ $msg = $summaryMessageKey;
+ }
+ if ( !$this->msg( $msg )->isDisabled() && !$this->including() ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
+ }
+ }
+
+ /**
+ * Returns the name that goes in the \<h1\> in the special page itself, and
+ * also the name that will be listed in Special:Specialpages
+ *
+ * Derived classes can override this, but usually it is easier to keep the
+ * default behavior.
+ *
+ * @return string
+ */
+ function getDescription() {
+ return $this->msg( strtolower( $this->mName ) )->text();
+ }
+
+ /**
+ * Get a self-referential title object
+ *
+ * @param string|bool $subpage
+ * @return Title
+ * @deprecated since 1.23, use SpecialPage::getPageTitle
+ */
+ function getTitle( $subpage = false ) {
+ return $this->getPageTitle( $subpage );
+ }
+
+ /**
+ * Get a self-referential title object
+ *
+ * @param string|bool $subpage
+ * @return Title
+ * @since 1.23
+ */
+ function getPageTitle( $subpage = false ) {
+ return self::getTitleFor( $this->mName, $subpage );
+ }
+
+ /**
+ * Sets the context this SpecialPage is executed in
+ *
+ * @param IContextSource $context
+ * @since 1.18
+ */
+ public function setContext( $context ) {
+ $this->mContext = $context;
+ }
+
+ /**
+ * Gets the context this SpecialPage is executed in
+ *
+ * @return IContextSource|RequestContext
+ * @since 1.18
+ */
+ public function getContext() {
+ if ( $this->mContext instanceof IContextSource ) {
+ return $this->mContext;
+ } else {
+ wfDebug( __METHOD__ . " called and \$mContext is null. " .
+ "Return RequestContext::getMain(); for sanity\n" );
+
+ return RequestContext::getMain();
+ }
+ }
+
+ /**
+ * Get the WebRequest being used for this instance
+ *
+ * @return WebRequest
+ * @since 1.18
+ */
+ public function getRequest() {
+ return $this->getContext()->getRequest();
+ }
+
+ /**
+ * Get the OutputPage being used for this instance
+ *
+ * @return OutputPage
+ * @since 1.18
+ */
+ public function getOutput() {
+ return $this->getContext()->getOutput();
+ }
+
+ /**
+ * Shortcut to get the User executing this instance
+ *
+ * @return User
+ * @since 1.18
+ */
+ public function getUser() {
+ return $this->getContext()->getUser();
+ }
+
+ /**
+ * Shortcut to get the skin being used for this instance
+ *
+ * @return Skin
+ * @since 1.18
+ */
+ public function getSkin() {
+ return $this->getContext()->getSkin();
+ }
+
+ /**
+ * Shortcut to get user's language
+ *
+ * @return Language
+ * @since 1.19
+ */
+ public function getLanguage() {
+ return $this->getContext()->getLanguage();
+ }
+
+ /**
+ * Shortcut to get main config object
+ * @return Config
+ * @since 1.24
+ */
+ public function getConfig() {
+ return $this->getContext()->getConfig();
+ }
+
+ /**
+ * Return the full title, including $par
+ *
+ * @return Title
+ * @since 1.18
+ */
+ public function getFullTitle() {
+ return $this->getContext()->getTitle();
+ }
+
+ /**
+ * Return the robot policy. Derived classes that override this can change
+ * the robot policy set by setHeaders() from the default 'noindex,nofollow'.
+ *
+ * @return string
+ * @since 1.23
+ */
+ protected function getRobotPolicy() {
+ return 'noindex,nofollow';
+ }
+
+ /**
+ * Wrapper around wfMessage that sets the current context.
+ *
+ * @return Message
+ * @see wfMessage
+ */
+ public function msg( /* $args */ ) {
+ $message = call_user_func_array(
+ array( $this->getContext(), 'msg' ),
+ func_get_args()
+ );
+ // RequestContext passes context to wfMessage, and the language is set from
+ // the context, but setting the language for Message class removes the
+ // interface message status, which breaks for example usernameless gender
+ // invocations. Restore the flag when not including special page in content.
+ if ( $this->including() ) {
+ $message->setInterfaceMessageFlag( false );
+ }
+
+ return $message;
+ }
+
+ /**
+ * Adds RSS/atom links
+ *
+ * @param array $params
+ */
+ protected function addFeedLinks( $params ) {
+ $feedTemplate = wfScript( 'api' );
+
+ foreach ( $this->getConfig()->get( 'FeedClasses' ) as $format => $class ) {
+ $theseParams = $params + array( 'feedformat' => $format );
+ $url = wfAppendQuery( $feedTemplate, $theseParams );
+ $this->getOutput()->addFeedLink( $format, $url );
+ }
+ }
+
+ /**
+ * Get the group that the special page belongs in on Special:SpecialPage
+ * Use this method, instead of getGroupName to allow customization
+ * of the group name from the wiki side
+ *
+ * @return string Group of this special page
+ * @since 1.21
+ */
+ public function getFinalGroupName() {
+ $name = $this->getName();
+ $specialPageGroups = $this->getConfig()->get( 'SpecialPageGroups' );
+
+ // Allow overbidding the group from the wiki side
+ $msg = $this->msg( 'specialpages-specialpagegroup-' . strtolower( $name ) )->inContentLanguage();
+ if ( !$msg->isBlank() ) {
+ $group = $msg->text();
+ } else {
+ // Than use the group from this object
+ $group = $this->getGroupName();
+
+ // Group '-' is used as default to have the chance to determine,
+ // if the special pages overrides this method,
+ // if not overridden, $wgSpecialPageGroups is checked for b/c
+ if ( $group === '-' && isset( $specialPageGroups[$name] ) ) {
+ $group = $specialPageGroups[$name];
+ }
+ }
+
+ // never give '-' back, change to 'other'
+ if ( $group === '-' ) {
+ $group = 'other';
+ }
+
+ return $group;
+ }
+
+ /**
+ * Under which header this special page is listed in Special:SpecialPages
+ * See messages 'specialpages-group-*' for valid names
+ * This method defaults to group 'other'
+ *
+ * @return string
+ * @since 1.21
+ */
+ protected function getGroupName() {
+ // '-' used here to determine, if this group is overridden or has a hardcoded 'other'
+ // Needed for b/c in getFinalGroupName
+ return '-';
+ }
+}
diff --git a/includes/specialpage/SpecialPageFactory.php b/includes/specialpage/SpecialPageFactory.php
new file mode 100644
index 00000000..679492ae
--- /dev/null
+++ b/includes/specialpage/SpecialPageFactory.php
@@ -0,0 +1,702 @@
+<?php
+/**
+ * Factory for handling the special page list and generating SpecialPage objects.
+ *
+ * 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
+ * @defgroup SpecialPage SpecialPage
+ */
+
+/**
+ * Factory for handling the special page list and generating SpecialPage objects.
+ *
+ * To add a special page in an extension, add to $wgSpecialPages either
+ * an object instance or an array containing the name and constructor
+ * parameters. The latter is preferred for performance reasons.
+ *
+ * The object instantiated must be either an instance of SpecialPage or a
+ * sub-class thereof. It must have an execute() method, which sends the HTML
+ * for the special page to $wgOut. The parent class has an execute() method
+ * which distributes the call to the historical global functions. Additionally,
+ * execute() also checks if the user has the necessary access privileges
+ * and bails out if not.
+ *
+ * To add a core special page, use the similar static list in
+ * SpecialPageFactory::$list. To remove a core static special page at runtime, use
+ * a SpecialPage_initList hook.
+ *
+ * @ingroup SpecialPage
+ * @since 1.17
+ */
+class SpecialPageFactory {
+ /**
+ * List of special page names to the subclass of SpecialPage which handles them.
+ */
+ private static $list = array(
+ // Maintenance Reports
+ 'BrokenRedirects' => 'BrokenRedirectsPage',
+ 'Deadendpages' => 'DeadendPagesPage',
+ 'DoubleRedirects' => 'DoubleRedirectsPage',
+ 'Longpages' => 'LongPagesPage',
+ 'Ancientpages' => 'AncientPagesPage',
+ 'Lonelypages' => 'LonelyPagesPage',
+ 'Fewestrevisions' => 'FewestrevisionsPage',
+ 'Withoutinterwiki' => 'WithoutInterwikiPage',
+ 'Protectedpages' => 'SpecialProtectedpages',
+ 'Protectedtitles' => 'SpecialProtectedtitles',
+ 'Shortpages' => 'ShortpagesPage',
+ 'Uncategorizedcategories' => 'UncategorizedCategoriesPage',
+ 'Uncategorizedimages' => 'UncategorizedImagesPage',
+ 'Uncategorizedpages' => 'UncategorizedPagesPage',
+ 'Uncategorizedtemplates' => 'UncategorizedTemplatesPage',
+ 'Unusedcategories' => 'UnusedCategoriesPage',
+ 'Unusedimages' => 'UnusedimagesPage',
+ 'Unusedtemplates' => 'UnusedtemplatesPage',
+ 'Unwatchedpages' => 'UnwatchedpagesPage',
+ 'Wantedcategories' => 'WantedCategoriesPage',
+ 'Wantedfiles' => 'WantedFilesPage',
+ 'Wantedpages' => 'WantedPagesPage',
+ 'Wantedtemplates' => 'WantedTemplatesPage',
+
+ // List of pages
+ 'Allpages' => 'SpecialAllpages',
+ 'Prefixindex' => 'SpecialPrefixindex',
+ 'Categories' => 'SpecialCategories',
+ 'Listredirects' => 'ListredirectsPage',
+ 'PagesWithProp' => 'SpecialPagesWithProp',
+ 'TrackingCategories' => 'SpecialTrackingCategories',
+
+ // Login/create account
+ 'Userlogin' => 'LoginForm',
+ 'CreateAccount' => 'SpecialCreateAccount',
+
+ // Users and rights
+ 'Block' => 'SpecialBlock',
+ 'Unblock' => 'SpecialUnblock',
+ 'BlockList' => 'SpecialBlockList',
+ 'ChangePassword' => 'SpecialChangePassword',
+ 'PasswordReset' => 'SpecialPasswordReset',
+ 'DeletedContributions' => 'DeletedContributionsPage',
+ 'Preferences' => 'SpecialPreferences',
+ 'ResetTokens' => 'SpecialResetTokens',
+ 'Contributions' => 'SpecialContributions',
+ 'Listgrouprights' => 'SpecialListGroupRights',
+ 'Listusers' => 'SpecialListUsers',
+ 'Listadmins' => 'SpecialListAdmins',
+ 'Listbots' => 'SpecialListBots',
+ 'Userrights' => 'UserrightsPage',
+ 'EditWatchlist' => 'SpecialEditWatchlist',
+
+ // Recent changes and logs
+ 'Newimages' => 'SpecialNewFiles',
+ 'Log' => 'SpecialLog',
+ 'Watchlist' => 'SpecialWatchlist',
+ 'Newpages' => 'SpecialNewpages',
+ 'Recentchanges' => 'SpecialRecentChanges',
+ 'Recentchangeslinked' => 'SpecialRecentChangesLinked',
+ 'Tags' => 'SpecialTags',
+
+ // Media reports and uploads
+ 'Listfiles' => 'SpecialListFiles',
+ 'Filepath' => 'SpecialFilepath',
+ 'MediaStatistics' => 'MediaStatisticsPage',
+ 'MIMEsearch' => 'MIMEsearchPage',
+ 'FileDuplicateSearch' => 'FileDuplicateSearchPage',
+ 'Upload' => 'SpecialUpload',
+ 'UploadStash' => 'SpecialUploadStash',
+ 'ListDuplicatedFiles' => 'ListDuplicatedFilesPage',
+
+ // Data and tools
+ 'Statistics' => 'SpecialStatistics',
+ 'Allmessages' => 'SpecialAllmessages',
+ 'Version' => 'SpecialVersion',
+ 'Lockdb' => 'SpecialLockdb',
+ 'Unlockdb' => 'SpecialUnlockdb',
+
+ // Redirecting special pages
+ 'LinkSearch' => 'LinkSearchPage',
+ 'Randompage' => 'RandomPage',
+ 'RandomInCategory' => 'SpecialRandomInCategory',
+ 'Randomredirect' => 'SpecialRandomredirect',
+
+ // High use pages
+ 'Mostlinkedcategories' => 'MostlinkedCategoriesPage',
+ 'Mostimages' => 'MostimagesPage',
+ 'Mostinterwikis' => 'MostinterwikisPage',
+ 'Mostlinked' => 'MostlinkedPage',
+ 'Mostlinkedtemplates' => 'MostlinkedTemplatesPage',
+ 'Mostcategories' => 'MostcategoriesPage',
+ 'Mostrevisions' => 'MostrevisionsPage',
+
+ // Page tools
+ 'ComparePages' => 'SpecialComparePages',
+ 'Export' => 'SpecialExport',
+ 'Import' => 'SpecialImport',
+ 'Undelete' => 'SpecialUndelete',
+ 'Whatlinkshere' => 'SpecialWhatLinksHere',
+ 'MergeHistory' => 'SpecialMergeHistory',
+ 'ExpandTemplates' => 'SpecialExpandTemplates',
+
+ // Other
+ 'Booksources' => 'SpecialBookSources',
+
+ // Unlisted / redirects
+ 'Blankpage' => 'SpecialBlankpage',
+ 'Diff' => 'SpecialDiff',
+ 'Emailuser' => 'SpecialEmailUser',
+ 'Movepage' => 'MovePageForm',
+ 'Mycontributions' => 'SpecialMycontributions',
+ 'MyLanguage' => 'SpecialMyLanguage',
+ 'Mypage' => 'SpecialMypage',
+ 'Mytalk' => 'SpecialMytalk',
+ 'Myuploads' => 'SpecialMyuploads',
+ 'AllMyUploads' => 'SpecialAllMyUploads',
+ 'PermanentLink' => 'SpecialPermanentLink',
+ 'Redirect' => 'SpecialRedirect',
+ 'Revisiondelete' => 'SpecialRevisionDelete',
+ 'RunJobs' => 'SpecialRunJobs',
+ 'Specialpages' => 'SpecialSpecialpages',
+ 'Userlogout' => 'SpecialUserlogout',
+ );
+
+ private static $aliases;
+
+ /**
+ * Reset the internal list of special pages. Useful when changing $wgSpecialPages after
+ * the internal list has already been initialized, e.g. during testing.
+ */
+ public static function resetList() {
+ self::$list = null;
+ self::$aliases = null;
+ }
+
+ /**
+ * Returns a list of canonical special page names.
+ * May be used to iterate over all registered special pages.
+ *
+ * @return string[]
+ */
+ public static function getNames() {
+ return array_keys( self::getPageList() );
+ }
+
+ /**
+ * Get the special page list as an array
+ *
+ * @deprecated since 1.24, use getNames() instead.
+ * @return array
+ */
+ public static function getList() {
+ wfDeprecated( __FUNCTION__, '1.24' );
+ return self::getPageList();
+ }
+
+ /**
+ * Get the special page list as an array
+ *
+ * @return array
+ */
+ private static function getPageList() {
+ global $wgSpecialPages;
+ global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
+ global $wgEnableEmail, $wgEnableJavaScriptTest;
+ global $wgPageLanguageUseDB;
+
+ if ( !is_object( self::$list ) ) {
+ wfProfileIn( __METHOD__ );
+
+ if ( !$wgDisableCounters ) {
+ self::$list['Popularpages'] = 'PopularPagesPage';
+ }
+
+ if ( !$wgDisableInternalSearch ) {
+ self::$list['Search'] = 'SpecialSearch';
+ }
+
+ if ( $wgEmailAuthentication ) {
+ self::$list['Confirmemail'] = 'EmailConfirmation';
+ self::$list['Invalidateemail'] = 'EmailInvalidation';
+ }
+
+ if ( $wgEnableEmail ) {
+ self::$list['ChangeEmail'] = 'SpecialChangeEmail';
+ }
+
+ if ( $wgEnableJavaScriptTest ) {
+ self::$list['JavaScriptTest'] = 'SpecialJavaScriptTest';
+ }
+
+ if ( $wgPageLanguageUseDB ) {
+ self::$list['PageLanguage'] = 'SpecialPageLanguage';
+ }
+
+ self::$list['Activeusers'] = 'SpecialActiveUsers';
+
+ // Add extension special pages
+ self::$list = array_merge( self::$list, $wgSpecialPages );
+
+ // Run hooks
+ // This hook can be used to remove undesired built-in special pages
+ wfRunHooks( 'SpecialPage_initList', array( &self::$list ) );
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ return self::$list;
+ }
+
+ /**
+ * Initialise and return the list of special page aliases. Returns an object with
+ * properties which can be accessed $obj->pagename - each property name is an
+ * alias, with the value being the canonical name of the special page. All
+ * registered special pages are guaranteed to map to themselves.
+ * @return object
+ */
+ private static function getAliasListObject() {
+ if ( !is_object( self::$aliases ) ) {
+ global $wgContLang;
+ $aliases = $wgContLang->getSpecialPageAliases();
+
+ self::$aliases = array();
+ $keepAlias = array();
+
+ // Force every canonical name to be an alias for itself.
+ foreach ( self::getPageList() as $name => $stuff ) {
+ $caseFoldedAlias = $wgContLang->caseFold( $name );
+ self::$aliases[$caseFoldedAlias] = $name;
+ $keepAlias[$caseFoldedAlias] = 'canonical';
+ }
+
+ // Check for $aliases being an array since Language::getSpecialPageAliases can return null
+ if ( is_array( $aliases ) ) {
+ foreach ( $aliases as $realName => $aliasList ) {
+ $aliasList = array_values( $aliasList );
+ foreach ( $aliasList as $i => $alias ) {
+ $caseFoldedAlias = $wgContLang->caseFold( $alias );
+
+ if ( isset( self::$aliases[$caseFoldedAlias] ) &&
+ $realName === self::$aliases[$caseFoldedAlias]
+ ) {
+ // Ignore same-realName conflicts
+ continue;
+ }
+
+ if ( !isset( $keepAlias[$caseFoldedAlias] ) ) {
+ self::$aliases[$caseFoldedAlias] = $realName;
+ if ( !$i ) {
+ $keepAlias[$caseFoldedAlias] = 'first';
+ }
+ } elseif ( !$i ) {
+ wfWarn( "First alias '$alias' for $realName conflicts with " .
+ "{$keepAlias[$caseFoldedAlias]} alias for " .
+ self::$aliases[$caseFoldedAlias]
+ );
+ }
+ }
+ }
+ }
+
+ // Cast to object: func()[$key] doesn't work, but func()->$key does
+ self::$aliases = (object)self::$aliases;
+ }
+
+ return self::$aliases;
+ }
+
+ /**
+ * Given a special page name with a possible subpage, return an array
+ * where the first element is the special page name and the second is the
+ * subpage.
+ *
+ * @param string $alias
+ * @return array Array( String, String|null ), or array( null, null ) if the page is invalid
+ */
+ public static function resolveAlias( $alias ) {
+ global $wgContLang;
+ $bits = explode( '/', $alias, 2 );
+
+ $caseFoldedAlias = $wgContLang->caseFold( $bits[0] );
+ $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
+ if ( isset( self::getAliasListObject()->$caseFoldedAlias ) ) {
+ $name = self::getAliasListObject()->$caseFoldedAlias;
+ } else {
+ return array( null, null );
+ }
+
+ if ( !isset( $bits[1] ) ) { // bug 2087
+ $par = null;
+ } else {
+ $par = $bits[1];
+ }
+
+ return array( $name, $par );
+ }
+
+ /**
+ * Add a page to a certain display group for Special:SpecialPages
+ *
+ * @param SpecialPage|string $page
+ * @param string $group
+ * @deprecated since 1.21 Override SpecialPage::getGroupName
+ */
+ public static function setGroup( $page, $group ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
+ global $wgSpecialPageGroups;
+ $name = is_object( $page ) ? $page->getName() : $page;
+ $wgSpecialPageGroups[$name] = $group;
+ }
+
+ /**
+ * Get the group that the special page belongs in on Special:SpecialPage
+ *
+ * @param SpecialPage $page
+ * @return string
+ * @deprecated since 1.21 Use SpecialPage::getFinalGroupName
+ */
+ public static function getGroup( &$page ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
+ return $page->getFinalGroupName();
+ }
+
+ /**
+ * Check if a given name exist as a special page or as a special page alias
+ *
+ * @param string $name Name of a special page
+ * @return bool True if a special page exists with this name
+ */
+ public static function exists( $name ) {
+ list( $title, /*...*/ ) = self::resolveAlias( $name );
+
+ $specialPageList = self::getPageList();
+ return isset( $specialPageList[$title] );
+ }
+
+ /**
+ * Find the object with a given name and return it (or NULL)
+ *
+ * @param string $name Special page name, may be localised and/or an alias
+ * @return SpecialPage|null SpecialPage object or null if the page doesn't exist
+ */
+ public static function getPage( $name ) {
+ list( $realName, /*...*/ ) = self::resolveAlias( $name );
+
+ $specialPageList = self::getPageList();
+
+ if ( isset( $specialPageList[$realName] ) ) {
+ $rec = $specialPageList[$realName];
+
+ if ( is_callable( $rec ) ) {
+ // Use callback to instantiate the special page
+ $page = call_user_func( $rec );
+ } elseif ( is_string( $rec ) ) {
+ $className = $rec;
+ $page = new $className;
+ } elseif ( is_array( $rec ) ) {
+ $className = array_shift( $rec );
+ // @deprecated, officially since 1.18, unofficially since forever
+ wfDeprecated( "Array syntax for \$wgSpecialPages is deprecated ($className), " .
+ "define a subclass of SpecialPage instead.", '1.18' );
+ $page = MWFunction::newObj( $className, $rec );
+ } elseif ( $rec instanceof SpecialPage ) {
+ $page = $rec; //XXX: we should deep clone here
+ } else {
+ $page = null;
+ }
+
+ if ( $page instanceof SpecialPage ) {
+ return $page;
+ } else {
+ // It's not a classname, nor a callback, nor a legacy constructor array,
+ // nor a special page object. Give up.
+ wfLogWarning( "Cannot instantiate special page $realName: bad spec!" );
+ return null;
+ }
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return categorised listable special pages which are available
+ * for the current user, and everyone.
+ *
+ * @param User $user User object to check permissions, $wgUser will be used
+ * if not provided
+ * @return array ( string => Specialpage )
+ */
+ public static function getUsablePages( User $user = null ) {
+ $pages = array();
+ if ( $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
+ foreach ( self::getPageList() as $name => $rec ) {
+ $page = self::getPage( $name );
+ if ( $page ) { // not null
+ $page->setContext( RequestContext::getMain() );
+ if ( $page->isListed()
+ && ( !$page->isRestricted() || $page->userCanExecute( $user ) )
+ ) {
+ $pages[$name] = $page;
+ }
+ }
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Return categorised listable special pages for all users
+ *
+ * @return array ( string => Specialpage )
+ */
+ public static function getRegularPages() {
+ $pages = array();
+ foreach ( self::getPageList() as $name => $rec ) {
+ $page = self::getPage( $name );
+ if ( $page->isListed() && !$page->isRestricted() ) {
+ $pages[$name] = $page;
+ }
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Return categorised listable special pages which are available
+ * for the current user, but not for everyone
+ *
+ * @param User|null $user User object to use or null for $wgUser
+ * @return array ( string => Specialpage )
+ */
+ public static function getRestrictedPages( User $user = null ) {
+ $pages = array();
+ if ( $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
+ foreach ( self::getPageList() as $name => $rec ) {
+ $page = self::getPage( $name );
+ if (
+ $page->isListed()
+ && $page->isRestricted()
+ && $page->userCanExecute( $user )
+ ) {
+ $pages[$name] = $page;
+ }
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Execute a special page path.
+ * The path may contain parameters, e.g. Special:Name/Params
+ * Extracts the special page name and call the execute method, passing the parameters
+ *
+ * Returns a title object if the page is redirected, false if there was no such special
+ * page, and true if it was successful.
+ *
+ * @param Title $title
+ * @param IContextSource $context
+ * @param bool $including Bool output is being captured for use in {{special:whatever}}
+ *
+ * @return bool
+ */
+ public static function executePath( Title &$title, IContextSource &$context, $including = false ) {
+ wfProfileIn( __METHOD__ );
+
+ // @todo FIXME: Redirects broken due to this call
+ $bits = explode( '/', $title->getDBkey(), 2 );
+ $name = $bits[0];
+ if ( !isset( $bits[1] ) ) { // bug 2087
+ $par = null;
+ } else {
+ $par = $bits[1];
+ }
+ $page = self::getPage( $name );
+ // Nonexistent?
+ if ( !$page ) {
+ $context->getOutput()->setArticleRelated( false );
+ $context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
+
+ global $wgSend404Code;
+ if ( $wgSend404Code ) {
+ $context->getOutput()->setStatusCode( 404 );
+ }
+
+ $context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
+ wfProfileOut( __METHOD__ );
+
+ return false;
+ }
+
+ // Page exists, set the context
+ $page->setContext( $context );
+
+ if ( !$including ) {
+ // Redirect to canonical alias for GET commands
+ // Not for POST, we'd lose the post data, so it's best to just distribute
+ // the request. Such POST requests are possible for old extensions that
+ // generate self-links without being aware that their default name has
+ // changed.
+ if ( $name != $page->getLocalName() && !$context->getRequest()->wasPosted() ) {
+ $query = $context->getRequest()->getQueryValues();
+ unset( $query['title'] );
+ $title = $page->getPageTitle( $par );
+ $url = $title->getFullURL( $query );
+ $context->getOutput()->redirect( $url );
+ wfProfileOut( __METHOD__ );
+
+ return $title;
+ } else {
+ $context->setTitle( $page->getPageTitle( $par ) );
+ }
+ } elseif ( !$page->isIncludable() ) {
+ wfProfileOut( __METHOD__ );
+
+ return false;
+ }
+
+ $page->including( $including );
+
+ // Execute special page
+ $profName = 'Special:' . $page->getName();
+ wfProfileIn( $profName );
+ $page->run( $par );
+ wfProfileOut( $profName );
+ wfProfileOut( __METHOD__ );
+
+ return true;
+ }
+
+ /**
+ * Just like executePath() but will override global variables and execute
+ * the page in "inclusion" mode. Returns true if the execution was
+ * successful or false if there was no such special page, or a title object
+ * if it was a redirect.
+ *
+ * Also saves the current $wgTitle, $wgOut, $wgRequest, $wgUser and $wgLang
+ * variables so that the special page will get the context it'd expect on a
+ * normal request, and then restores them to their previous values after.
+ *
+ * @param Title $title
+ * @param IContextSource $context
+ * @return string HTML fragment
+ */
+ public static function capturePath( Title $title, IContextSource $context ) {
+ global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgLang;
+
+ // Save current globals
+ $oldTitle = $wgTitle;
+ $oldOut = $wgOut;
+ $oldRequest = $wgRequest;
+ $oldUser = $wgUser;
+ $oldLang = $wgLang;
+
+ // Set the globals to the current context
+ $wgTitle = $title;
+ $wgOut = $context->getOutput();
+ $wgRequest = $context->getRequest();
+ $wgUser = $context->getUser();
+ $wgLang = $context->getLanguage();
+
+ // The useful part
+ $ret = self::executePath( $title, $context, true );
+
+ // And restore the old globals
+ $wgTitle = $oldTitle;
+ $wgOut = $oldOut;
+ $wgRequest = $oldRequest;
+ $wgUser = $oldUser;
+ $wgLang = $oldLang;
+
+ return $ret;
+ }
+
+ /**
+ * Get the local name for a specified canonical name
+ *
+ * @param string $name
+ * @param string|bool $subpage
+ * @return string
+ */
+ public static function getLocalNameFor( $name, $subpage = false ) {
+ global $wgContLang;
+ $aliases = $wgContLang->getSpecialPageAliases();
+ $aliasList = self::getAliasListObject();
+
+ // Find the first alias that maps back to $name
+ if ( isset( $aliases[$name] ) ) {
+ $found = false;
+ foreach ( $aliases[$name] as $alias ) {
+ $caseFoldedAlias = $wgContLang->caseFold( $alias );
+ $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
+ if ( isset( $aliasList->$caseFoldedAlias ) &&
+ $aliasList->$caseFoldedAlias === $name
+ ) {
+ $name = $alias;
+ $found = true;
+ break;
+ }
+ }
+ if ( !$found ) {
+ wfWarn( "Did not find a usable alias for special page '$name'. " .
+ "It seems all defined aliases conflict?" );
+ }
+ } else {
+ // Check if someone misspelled the correct casing
+ if ( is_array( $aliases ) ) {
+ foreach ( $aliases as $n => $values ) {
+ if ( strcasecmp( $name, $n ) === 0 ) {
+ wfWarn( "Found alias defined for $n when searching for " .
+ "special page aliases for $name. Case mismatch?" );
+ return self::getLocalNameFor( $n, $subpage );
+ }
+ }
+ }
+
+ wfWarn( "Did not find alias for special page '$name'. " .
+ "Perhaps no aliases are defined for it?" );
+ }
+
+ if ( $subpage !== false && !is_null( $subpage ) ) {
+ $name = "$name/$subpage";
+ }
+
+ return $wgContLang->ucfirst( $name );
+ }
+
+ /**
+ * Get a title for a given alias
+ *
+ * @param string $alias
+ * @return Title|null Title or null if there is no such alias
+ */
+ public static function getTitleForAlias( $alias ) {
+ list( $name, $subpage ) = self::resolveAlias( $alias );
+ if ( $name != null ) {
+ return SpecialPage::getTitleFor( $name, $subpage );
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/includes/specialpage/UnlistedSpecialPage.php b/includes/specialpage/UnlistedSpecialPage.php
new file mode 100644
index 00000000..f5e2ccf7
--- /dev/null
+++ b/includes/specialpage/UnlistedSpecialPage.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Shortcut to construct a special page which is unlisted by default.
+ *
+ * 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
+ */
+
+/**
+ * Shortcut to construct a special page which is unlisted by default.
+ *
+ * @ingroup SpecialPage
+ */
+class UnlistedSpecialPage extends SpecialPage {
+ function __construct( $name, $restriction = '', $function = false, $file = 'default' ) {
+ parent::__construct( $name, $restriction, false, $function, $file );
+ }
+
+ public function isListed() {
+ return false;
+ }
+}
diff --git a/includes/specialpage/WantedQueryPage.php b/includes/specialpage/WantedQueryPage.php
new file mode 100644
index 00000000..be2f1e8d
--- /dev/null
+++ b/includes/specialpage/WantedQueryPage.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Class definition for a wanted query page.
+ *
+ * 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 definition for a wanted query page like
+ * WantedPages, WantedTemplates, etc
+ * @ingroup SpecialPage
+ */
+abstract class WantedQueryPage extends QueryPage {
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ /**
+ * Cache page existence for performance
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ if ( !$res->numRows() ) {
+ return;
+ }
+
+ $batch = new LinkBatch;
+ foreach ( $res as $row ) {
+ $batch->add( $row->namespace, $row->title );
+ }
+ $batch->execute();
+
+ // Back to start for display
+ $res->seek( 0 );
+ }
+
+ /**
+ * Should formatResult() always check page existence, even if
+ * the results are fresh? This is a (hopefully temporary)
+ * kluge for Special:WantedFiles, which may contain false
+ * positives for files that exist e.g. in a shared repo (bug
+ * 6220).
+ * @return bool
+ */
+ function forceExistenceCheck() {
+ return false;
+ }
+
+ /**
+ * Format an individual result
+ *
+ * @param Skin $skin Skin to use for UI elements
+ * @param object $result Result row
+ * @return string
+ */
+ public function formatResult( $skin, $result ) {
+ $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( $title instanceof Title ) {
+ if ( $this->isCached() || $this->forceExistenceCheck() ) {
+ $pageLink = $this->existenceCheck( $title )
+ ? '<del>' . Linker::link( $title ) . '</del>'
+ : Linker::link( $title );
+ } else {
+ $pageLink = Linker::link(
+ $title,
+ null,
+ array(),
+ array(),
+ array( 'broken' )
+ );
+ }
+ return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) );
+ } else {
+ return $this->msg( 'wantedpages-badtitle', $result->title )->escaped();
+ }
+ }
+
+ /**
+ * Does the Title currently exists
+ *
+ * This method allows a subclass to override this check
+ * (For example, wantedfiles, would want to check if the file exists
+ * not just that a page in the file namespace exists).
+ *
+ * This will only control if the link is crossed out. Whether or not the link
+ * is blue vs red is controlled by if the title exists.
+ *
+ * @note This will only be run if the page is cached (ie $wgMiserMode = true)
+ * unless forceExistenceCheck() is true.
+ * @since 1.24
+ * @return boolean
+ */
+ protected function existenceCheck( Title $title ) {
+ return $title->isKnown();
+ }
+
+ /**
+ * Make a "what links here" link for a given title
+ *
+ * @param Title $title Title to make the link for
+ * @param object $result Result row
+ * @return string
+ */
+ private function makeWlhLink( $title, $result ) {
+ $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
+ $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped();
+ return Linker::link( $wlh, $label );
+ }
+}
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index 705dab55..ce436525 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -31,33 +31,35 @@
* @ingroup SpecialPage
*/
class ActiveUsersPager extends UsersPager {
-
/**
* @var FormOptions
*/
protected $opts;
/**
- * @var Array
+ * @var array
*/
protected $hideGroups = array();
/**
- * @var Array
+ * @var array
*/
protected $hideRights = array();
/**
- * @param $context IContextSource
- * @param $group null Unused
+ * @var array
+ */
+ private $blockStatusByUid;
+
+ /**
+ * @param IContextSource $context
+ * @param null $group Unused
* @param string $par Parameter passed to the page
*/
function __construct( IContextSource $context = null, $group = null, $par = null ) {
- global $wgActiveUserDays;
-
parent::__construct( $context );
- $this->RCMaxAge = $wgActiveUserDays;
+ $this->RCMaxAge = $this->getConfig()->get( 'ActiveUserDays' );
$un = $this->getRequest()->getText( 'username', $par );
$this->requestedUser = '';
if ( $un != '' ) {
@@ -87,39 +89,36 @@ class ActiveUsersPager extends UsersPager {
}
function getIndexField() {
- return 'rc_user_text';
+ return 'qcc_title';
}
function getQueryInfo() {
$dbr = $this->getDatabase();
- $conds = array( 'rc_user > 0' ); // Users - no anons
- $conds[] = 'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' );
- $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes(
- $dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge * 24 * 3600 ) );
-
+ $activeUserSeconds = $this->getConfig()->get( 'ActiveUserDays' ) * 86400;
+ $timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
+ $conds = array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_namespace' => NS_USER,
+ 'user_name = qcc_title',
+ 'rc_user_text = qcc_title',
+ 'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Don't count wikidata.
+ 'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ),
+ 'rc_timestamp >= ' . $dbr->addQuotes( $timestamp ),
+ );
if ( $this->requestedUser != '' ) {
- $conds[] = 'rc_user_text >= ' . $dbr->addQuotes( $this->requestedUser );
+ $conds[] = 'qcc_title >= ' . $dbr->addQuotes( $this->requestedUser );
}
-
if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
$conds[] = 'NOT EXISTS (' . $dbr->selectSQLText(
- 'ipblocks', '1', array( 'rc_user=ipb_user', 'ipb_deleted' => 1 )
+ 'ipblocks', '1', array( 'ipb_user=user_id', 'ipb_deleted' => 1 )
) . ')';
}
return array(
- 'tables' => array( 'recentchanges' ),
- 'fields' => array(
- 'user_name' => 'rc_user_text', // for Pager inheritance
- 'rc_user_text', // for Pager
- 'user_id' => 'MAX(rc_user)', // Postgres
- 'recentedits' => 'COUNT(*)'
- ),
- 'options' => array(
- 'GROUP BY' => array( 'rc_user_text' ),
- 'USE INDEX' => array( 'recentchanges' => 'rc_user_text' )
- ),
+ 'tables' => array( 'querycachetwo', 'user', 'recentchanges' ),
+ 'fields' => array( 'user_name', 'user_id', 'recentedits' => 'COUNT(*)', 'qcc_title' ),
+ 'options' => array( 'GROUP BY' => array( 'qcc_title' ) ),
'conds' => $conds
);
}
@@ -196,25 +195,34 @@ class ActiveUsersPager extends UsersPager {
}
function getPageHeader() {
- global $wgScript;
-
$self = $this->getTitle();
$limit = $this->mLimit ? Html::hidden( 'limit', $this->mLimit ) : '';
- $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); # Form tag
+ # Form tag
+ $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) );
$out .= Xml::fieldset( $this->msg( 'activeusers' )->text() ) . "\n";
$out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n";
+ # Username field
$out .= Xml::inputLabel( $this->msg( 'activeusers-from' )->text(),
- 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />';# Username field
+ 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />';
$out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(),
'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ), array( 'tabindex' => 2 ) );
- $out .= Xml::checkLabel( $this->msg( 'activeusers-hidesysops' )->text(),
- 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ), array( 'tabindex' => 3 ) ) . '<br />';
-
- $out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text(), array( 'tabindex' => 4 ) ) . "\n";# Submit button and form bottom
+ $out .= Xml::checkLabel(
+ $this->msg( 'activeusers-hidesysops' )->text(),
+ 'hidesysops',
+ 'hidesysops',
+ $this->opts->getValue( 'hidesysops' ),
+ array( 'tabindex' => 3 )
+ ) . '<br />';
+
+ # Submit button and form bottom
+ $out .= Xml::submitButton(
+ $this->msg( 'allpagessubmit' )->text(),
+ array( 'tabindex' => 4 )
+ ) . "\n";
$out .= Xml::closeElement( 'fieldset' );
$out .= Xml::closeElement( 'form' );
@@ -237,17 +245,23 @@ class SpecialActiveUsers extends SpecialPage {
/**
* Show the special page
*
- * @param $par Mixed: parameter passed to the page or null
+ * @param string $par Parameter passed to the page or null
*/
public function execute( $par ) {
- global $wgActiveUserDays;
+ $days = $this->getConfig()->get( 'ActiveUserDays' );
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$out->wrapWikiMsg( "<div class='mw-activeusers-intro'>\n$1\n</div>",
- array( 'activeusers-intro', $this->getLanguage()->formatNum( $wgActiveUserDays ) ) );
+ array( 'activeusers-intro', $this->getLanguage()->formatNum( $days ) ) );
+
+ // Occasionally merge in new updates
+ $seconds = min( self::mergeActiveUsers( 600, $days ), $days * 86400 );
+ // Mention the level of staleness
+ $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl',
+ $this->getLanguage()->formatDuration( $seconds ) );
$up = new ActiveUsersPager( $this->getContext(), null, $par );
@@ -269,4 +283,149 @@ class SpecialActiveUsers extends SpecialPage {
protected function getGroupName() {
return 'users';
}
+
+ /**
+ * @param int $period Seconds (do updates no more often than this)
+ * @param int $days How many days user must be idle before he is considered inactive
+ * @return int How many seconds old the cache is
+ */
+ public static function mergeActiveUsers( $period, $days ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $cTime = $dbr->selectField( 'querycache_info',
+ 'qci_timestamp',
+ array( 'qci_type' => 'activeusers' )
+ );
+
+ if ( !wfReadOnly() ) {
+ if ( !$cTime || ( time() - wfTimestamp( TS_UNIX, $cTime ) ) > $period ) {
+ $dbw = wfGetDB( DB_MASTER );
+ if ( $dbw->estimateRowCount( 'recentchanges' ) <= 10000 ) {
+ $window = $days * 86400; // small wiki
+ } else {
+ $window = $period * 2;
+ }
+ $cTime = self::doQueryCacheUpdate( $dbw, $days, $window ) ?: $cTime;
+ }
+ }
+
+ return ( time() -
+ ( $cTime ? wfTimestamp( TS_UNIX, $cTime ) : $days * 86400 ) );
+ }
+
+ /**
+ * @param DatabaseBase $dbw Passed in from updateSpecialPages.php
+ * @return void
+ */
+ public static function cacheUpdate( DatabaseBase $dbw ) {
+ global $wgActiveUserDays;
+
+ self::doQueryCacheUpdate( $dbw, $wgActiveUserDays, $wgActiveUserDays * 86400 );
+ }
+
+ /**
+ * Update the query cache as needed
+ *
+ * @param DatabaseBase $dbw
+ * @param int $days How many days user must be idle before he is considered inactive
+ * @param int $window Maximum time range of new data to scan (in seconds)
+ * @return int|bool UNIX timestamp the cache is now up-to-date as of (false on error)
+ */
+ protected static function doQueryCacheUpdate( DatabaseBase $dbw, $days, $window ) {
+ $lockKey = wfWikiID() . '-activeusers';
+ if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
+ return false; // exclusive update (avoids duplicate entries)
+ }
+
+ $now = time();
+ $cTime = $dbw->selectField( 'querycache_info',
+ 'qci_timestamp',
+ array( 'qci_type' => 'activeusers' )
+ );
+ $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1;
+
+ // Pick the date range to fetch from. This is normally from the last
+ // update to till the present time, but has a limited window for sanity.
+ // If the window is limited, multiple runs are need to fully populate it.
+ $sTimestamp = max( $cTimeUnix, $now - $days * 86400 );
+ $eTimestamp = min( $sTimestamp + $window, $now );
+
+ // Get all the users active since the last update
+ $res = $dbw->select(
+ array( 'recentchanges' ),
+ array( 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ),
+ array(
+ 'rc_user > 0', // actual accounts
+ 'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata
+ 'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ),
+ 'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ),
+ 'rc_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $eTimestamp ) )
+ ),
+ __METHOD__,
+ array(
+ 'GROUP BY' => array( 'rc_user_text' ),
+ 'ORDER BY' => 'NULL' // avoid filesort
+ )
+ );
+ $names = array();
+ foreach ( $res as $row ) {
+ $names[$row->rc_user_text] = $row->lastedittime;
+ }
+
+ // Rotate out users that have not edited in too long (according to old data set)
+ $dbw->delete( 'querycachetwo',
+ array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_value < ' . $dbw->addQuotes( $now - $days * 86400 ) // TS_UNIX
+ ),
+ __METHOD__
+ );
+
+ // Find which of the recently active users are already accounted for
+ if ( count( $names ) ) {
+ $res = $dbw->select( 'querycachetwo',
+ array( 'user_name' => 'qcc_title' ),
+ array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_namespace' => NS_USER,
+ 'qcc_title' => array_keys( $names ) ),
+ __METHOD__
+ );
+ foreach ( $res as $row ) {
+ unset( $names[$row->user_name] );
+ }
+ }
+
+ // Insert the users that need to be added to the list (which their last edit time
+ if ( count( $names ) ) {
+ $newRows = array();
+ foreach ( $names as $name => $lastEditTime ) {
+ $newRows[] = array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_namespace' => NS_USER,
+ 'qcc_title' => $name,
+ 'qcc_value' => wfTimestamp( TS_UNIX, $lastEditTime ),
+ 'qcc_namespacetwo' => 0, // unused
+ 'qcc_titletwo' => '' // unused
+ );
+ }
+ foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) {
+ $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ );
+ if ( !$dbw->trxLevel() ) {
+ wfWaitForSlaves();
+ }
+ }
+ }
+
+ // Touch the data freshness timestamp
+ $dbw->replace( 'querycache_info',
+ array( 'qci_type' ),
+ array( 'qci_type' => 'activeusers',
+ 'qci_timestamp' => $dbw->timestamp( $eTimestamp ) ), // not always $now
+ __METHOD__
+ );
+
+ $dbw->unlock( $lockKey, __METHOD__ );
+
+ return $eTimestamp;
+ }
}
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllMessages.php
index 35d6a0c0..96be4d03 100644
--- a/includes/specials/SpecialAllmessages.php
+++ b/includes/specials/SpecialAllMessages.php
@@ -27,7 +27,7 @@
* @file
* @ingroup SpecialPage
*/
-class SpecialAllmessages extends SpecialPage {
+class SpecialAllMessages extends SpecialPage {
/**
* @var AllmessagesTablePager
*/
@@ -43,7 +43,7 @@ class SpecialAllmessages extends SpecialPage {
/**
* Show the special page
*
- * @param $par Mixed: parameter passed to the page or null
+ * @param string $par Parameter passed to the page or null
*/
public function execute( $par ) {
$request = $this->getRequest();
@@ -51,15 +51,13 @@ class SpecialAllmessages extends SpecialPage {
$this->setHeaders();
- global $wgUseDatabaseMessages;
- if ( !$wgUseDatabaseMessages ) {
+ if ( !$this->getConfig()->get( 'UseDatabaseMessages' ) ) {
$out->addWikiMsg( 'allmessagesnotsupportedDB' );
return;
- } else {
- $this->outputHeader( 'allmessagestext' );
}
+ $this->outputHeader( 'allmessagestext' );
$out->addModuleStyles( 'mediawiki.special' );
$this->table = new AllmessagesTablePager(
@@ -70,10 +68,8 @@ class SpecialAllmessages extends SpecialPage {
$this->langcode = $this->table->lang->getCode();
- $out->addHTML( $this->table->buildForm() .
- $this->table->getNavigationBar() .
- $this->table->getBody() .
- $this->table->getNavigationBar() );
+ $out->addHTML( $this->table->buildForm() );
+ $out->addParserOutputContent( $this->table->getFullOutput() );
}
protected function getGroupName() {
@@ -85,7 +81,7 @@ class SpecialAllmessages extends SpecialPage {
* Use TablePager for prettified output. We have to pretend that we're
* getting data from a table when in fact not all of it comes from the database.
*/
-class AllmessagesTablePager extends TablePager {
+class AllMessagesTablePager extends TablePager {
protected $filter, $prefix, $langcode, $displayPrefix;
public $mLimitsShown;
@@ -105,7 +101,8 @@ class AllmessagesTablePager extends TablePager {
$this->mIndexField = 'am_title';
$this->mPage = $page;
$this->mConds = $conds;
- $this->mDefaultDirection = true; // always sort ascending
+ // FIXME: Why does this need to be set to DIR_DESCENDING to produce ascending ordering?
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
$this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 );
global $wgContLang;
@@ -114,7 +111,7 @@ class AllmessagesTablePager extends TablePager {
$this->lang = ( $langObj ? $langObj : $wgContLang );
$this->langcode = $this->lang->getCode();
- $this->foreign = $this->langcode != $wgContLang->getCode();
+ $this->foreign = $this->langcode !== $wgContLang->getCode();
$request = $this->getRequest();
@@ -122,11 +119,14 @@ class AllmessagesTablePager extends TablePager {
if ( $this->filter === 'all' ) {
$this->custom = null; // So won't match in either case
} else {
- $this->custom = ( $this->filter == 'unmodified' );
+ $this->custom = ( $this->filter === 'unmodified' );
}
$prefix = $this->getLanguage()->ucfirst( $request->getVal( 'prefix', '' ) );
- $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $request->getVal( 'prefix', null ) ) : null;
+ $prefix = $prefix !== '' ?
+ Title::makeTitleSafe( NS_MEDIAWIKI, $request->getVal( 'prefix', null ) ) :
+ null;
+
if ( $prefix !== null ) {
$this->displayPrefix = $prefix->getDBkey();
$this->prefix = '/^' . preg_quote( $this->displayPrefix ) . '/i';
@@ -145,13 +145,15 @@ class AllmessagesTablePager extends TablePager {
}
function buildForm() {
- global $wgScript;
-
$attrs = array( 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' );
$msg = wfMessage( 'allmessages-language' );
$langSelect = Xml::languageSelector( $this->langcode, false, null, $attrs, $msg );
- $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) .
+ $out = Xml::openElement( 'form', array(
+ 'method' => 'get',
+ 'action' => $this->getConfig()->get( 'Script' ),
+ 'id' => 'mw-allmessages-form'
+ ) ) .
Xml::fieldset( $this->msg( 'allmessages-filter-legend' )->text() ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" .
@@ -160,7 +162,12 @@ class AllmessagesTablePager extends TablePager {
Xml::label( $this->msg( 'allmessages-prefix' )->text(), 'mw-allmessages-form-prefix' ) .
"</td>\n
<td class=\"mw-input\">" .
- Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) .
+ Xml::input(
+ 'prefix',
+ 20,
+ str_replace( '_', ' ', $this->displayPrefix ),
+ array( 'id' => 'mw-allmessages-form-prefix' )
+ ) .
"</td>\n
</tr>
<tr>\n
@@ -172,19 +179,19 @@ class AllmessagesTablePager extends TablePager {
'filter',
'unmodified',
'mw-allmessages-form-filter-unmodified',
- ( $this->filter == 'unmodified' )
+ ( $this->filter === 'unmodified' )
) .
Xml::radioLabel( $this->msg( 'allmessages-filter-all' )->text(),
'filter',
'all',
'mw-allmessages-form-filter-all',
- ( $this->filter == 'all' )
+ ( $this->filter === 'all' )
) .
Xml::radioLabel( $this->msg( 'allmessages-filter-modified' )->text(),
'filter',
'modified',
'mw-allmessages-form-filter-modified',
- ( $this->filter == 'modified' )
+ ( $this->filter === 'modified' )
) .
"</td>\n
</tr>
@@ -241,7 +248,7 @@ class AllmessagesTablePager extends TablePager {
* @param array $messageNames
* @param string $langcode What language code
* @param bool $foreign Whether the $langcode is not the content language
- * @return array: a 'pages' and 'talks' array with the keys of existing pages
+ * @return array A 'pages' and 'talks' array with the keys of existing pages
*/
public static function getCustomisedStatuses( $messageNames, $langcode = 'en', $foreign = false ) {
// FIXME: This function should be moved to Language:: or something.
@@ -260,19 +267,23 @@ class AllmessagesTablePager extends TablePager {
foreach ( $res as $s ) {
$exists = false;
+
if ( $foreign ) {
- $title = explode( '/', $s->page_title );
- if ( count( $title ) === 2 && $langcode == $title[1]
- && isset( $xNames[$title[0]] )
+ $titleParts = explode( '/', $s->page_title );
+ if ( count( $titleParts ) === 2 &&
+ $langcode === $titleParts[1] &&
+ isset( $xNames[$titleParts[0]] )
) {
- $exists = $title[0];
+ $exists = $titleParts[0];
}
} elseif ( isset( $xNames[$s->page_title] ) ) {
$exists = $s->page_title;
}
- if ( $exists && $s->page_namespace == NS_MEDIAWIKI ) {
+
+ $title = Title::newFromRow( $s );
+ if ( $exists && $title->inNamespace( NS_MEDIAWIKI ) ) {
$pageFlags[$exists] = true;
- } elseif ( $exists && $s->page_namespace == NS_MEDIAWIKI_TALK ) {
+ } elseif ( $exists && $title->inNamespace( NS_MEDIAWIKI_TALK ) ) {
$talkFlags[$exists] = true;
}
}
@@ -315,7 +326,7 @@ class AllmessagesTablePager extends TablePager {
$count++;
}
- if ( $count == $limit ) {
+ if ( $count === $limit ) {
break;
}
}
@@ -324,7 +335,12 @@ class AllmessagesTablePager extends TablePager {
}
function getStartBody() {
- return Xml::openElement( 'table', array( 'class' => 'mw-datatable TablePager', 'id' => 'mw-allmessagestable' ) ) . "\n" .
+ $tableClass = $this->getTableClass();
+ return Xml::openElement( 'table', array(
+ 'class' => "mw-datatable $tableClass",
+ 'id' => 'mw-allmessagestable'
+ ) ) .
+ "\n" .
"<thead><tr>
<th rowspan=\"2\">" .
$this->msg( 'allmessagesname' )->escaped() . "
@@ -345,6 +361,17 @@ class AllmessagesTablePager extends TablePager {
case 'am_title' :
$title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
$talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
+ $translation = Linker::makeExternalLink(
+ 'https://translatewiki.net/w/i.php?' . wfArrayToCgi( array(
+ 'title' => 'Special:SearchTranslations',
+ 'group' => 'mediawiki',
+ 'grouppath' => 'mediawiki',
+ 'query' => 'language:' . $this->getLanguage()->getCode() . '^25 ' .
+ 'messageid:"MediaWiki:' . $value . '"^10 "' .
+ $this->msg( $value )->inLanguage( 'en' )->plain() . '"'
+ ) ),
+ $this->msg( 'allmessages-filter-translate' )->text()
+ );
if ( $this->mCurrentRow->am_customised ) {
$title = Linker::linkKnown( $title, $this->getLanguage()->lcfirst( $value ) );
@@ -369,12 +396,16 @@ class AllmessagesTablePager extends TablePager {
);
}
- return $title . ' ' . $this->msg( 'parentheses' )->rawParams( $talk )->escaped();
+ return $title . ' '
+ . $this->msg( 'parentheses' )->rawParams( $talk )->escaped()
+ . ' '
+ . $this->msg( 'parentheses' )->rawParams( $translation )->escaped();
case 'am_default' :
case 'am_actual' :
return Sanitizer::escapeHtmlAllowEntities( $value, ENT_QUOTES );
}
+
return '';
}
@@ -386,9 +417,11 @@ class AllmessagesTablePager extends TablePager {
if ( $row->am_customised ) {
$s .= Xml::openElement( 'tr', $this->getRowAttrs( $row, true ) );
$formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
- if ( $formatted == '' ) {
+
+ if ( $formatted === '' ) {
$formatted = '&#160;';
}
+
$s .= Xml::tags( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
. "</tr>\n";
}
@@ -398,9 +431,11 @@ class AllmessagesTablePager extends TablePager {
function getRowAttrs( $row, $isSecond = false ) {
$arr = array();
+
if ( $row->am_customised ) {
$arr['class'] = 'allmessages-customised';
}
+
if ( !$isSecond ) {
$arr['id'] = Sanitizer::escapeId( 'msg_' . $this->getLanguage()->lcfirst( $row->am_title ) );
}
@@ -409,9 +444,9 @@ class AllmessagesTablePager extends TablePager {
}
function getCellAttrs( $field, $value ) {
- if ( $this->mCurrentRow->am_customised && $field == 'am_title' ) {
+ if ( $this->mCurrentRow->am_customised && $field === 'am_title' ) {
return array( 'rowspan' => '2', 'class' => $field );
- } elseif ( $field == 'am_title' ) {
+ } elseif ( $field === 'am_title' ) {
return array( 'class' => $field );
} else {
return array( 'lang' => $this->langcode, 'dir' => $this->lang->getDir(), 'class' => $field );
diff --git a/includes/specials/SpecialAllPages.php b/includes/specials/SpecialAllPages.php
new file mode 100644
index 00000000..08b8761a
--- /dev/null
+++ b/includes/specials/SpecialAllPages.php
@@ -0,0 +1,384 @@
+<?php
+/**
+ * Implements Special:Allpages
+ *
+ * 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:Allpages
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialAllPages extends IncludableSpecialPage {
+
+ /**
+ * Maximum number of pages to show on single subpage.
+ *
+ * @var int $maxPerPage
+ */
+ protected $maxPerPage = 345;
+
+ /**
+ * Determines, which message describes the input field 'nsfrom'.
+ *
+ * @var string $nsfromMsg
+ */
+ protected $nsfromMsg = 'allpagesfrom';
+
+ /**
+ * Constructor
+ *
+ * @param string $name Name of the special page, as seen in links and URLs (default: 'Allpages')
+ */
+ function __construct( $name = 'Allpages' ) {
+ parent::__construct( $name );
+ }
+
+ /**
+ * Entry point : initialise variables and call subfunctions.
+ *
+ * @param string $par Becomes "FOO" when called like Special:Allpages/FOO (default null)
+ */
+ function execute( $par ) {
+ $request = $this->getRequest();
+ $out = $this->getOutput();
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $out->allowClickjacking();
+
+ # GET values
+ $from = $request->getVal( 'from', null );
+ $to = $request->getVal( 'to', null );
+ $namespace = $request->getInt( 'namespace' );
+ $hideredirects = $request->getBool( 'hideredirects', false );
+
+ $namespaces = $this->getContext()->getLanguage()->getNamespaces();
+
+ $out->setPageTitle(
+ ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) ) ?
+ $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
+ $this->msg( 'allarticles' )
+ );
+ $out->addModuleStyles( 'mediawiki.special' );
+
+ if ( $par !== null ) {
+ $this->showChunk( $namespace, $par, $to, $hideredirects );
+ } elseif ( $from !== null && $to === null ) {
+ $this->showChunk( $namespace, $from, $to, $hideredirects );
+ } else {
+ $this->showToplevel( $namespace, $from, $to, $hideredirects );
+ }
+ }
+
+ /**
+ * HTML for the top form
+ *
+ * @param int $namespace A namespace constant (default NS_MAIN).
+ * @param string $from DbKey we are starting listing at.
+ * @param string $to DbKey we are ending listing at.
+ * @param bool $hideredirects Dont show redirects (default false)
+ * @return string
+ */
+ function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
+ $t = $this->getPageTitle();
+
+ $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
+ $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getConfig()->get( 'Script' ) ) );
+ $out .= Html::hidden( 'title', $t->getPrefixedText() );
+ $out .= Xml::openElement( 'fieldset' );
+ $out .= Xml::element( 'legend', null, $this->msg( 'allpages' )->text() );
+ $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
+ $out .= "<tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'allpagesfrom' )->text(), 'nsfrom' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Xml::input( 'from', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) .
+ " </td>
+</tr>
+<tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'allpagesto' )->text(), 'nsto' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Xml::input( 'to', 30, str_replace( '_', ' ', $to ), array( 'id' => 'nsto' ) ) .
+ " </td>
+</tr>
+<tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Html::namespaceSelector(
+ array( 'selected' => $namespace ),
+ array( 'name' => 'namespace', 'id' => 'namespace' )
+ ) . ' ' .
+ Xml::checkLabel(
+ $this->msg( 'allpages-hide-redirects' )->text(),
+ 'hideredirects',
+ 'hideredirects',
+ $hideredirects
+ ) . ' ' .
+ Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
+ " </td>
+</tr>";
+ $out .= Xml::closeElement( 'table' );
+ $out .= Xml::closeElement( 'fieldset' );
+ $out .= Xml::closeElement( 'form' );
+ $out .= Xml::closeElement( 'div' );
+
+ return $out;
+ }
+
+ /**
+ * @param int $namespace (default NS_MAIN)
+ * @param string $from List all pages from this name
+ * @param string $to List all pages to this name
+ * @param bool $hideredirects Dont show redirects (default false)
+ */
+ function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
+ $from = Title::makeTitleSafe( $namespace, $from );
+ $to = Title::makeTitleSafe( $namespace, $to );
+ $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
+ $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
+
+ $this->showChunk( $namespace, $from, $to, $hideredirects );
+ }
+
+ /**
+ * @param int $namespace Namespace (Default NS_MAIN)
+ * @param string $from List all pages from this name (default false)
+ * @param string $to List all pages to this name (default false)
+ * @param bool $hideredirects Dont show redirects (default false)
+ */
+ function showChunk( $namespace = NS_MAIN, $from = false, $to = false, $hideredirects = false ) {
+ $output = $this->getOutput();
+
+ $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
+ $toList = $this->getNamespaceKeyAndText( $namespace, $to );
+ $namespaces = $this->getContext()->getLanguage()->getNamespaces();
+ $n = 0;
+
+ if ( !$fromList || !$toList ) {
+ $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
+ } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
+ // Show errormessage and reset to NS_MAIN
+ $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
+ $namespace = NS_MAIN;
+ } else {
+ list( $namespace, $fromKey, $from ) = $fromList;
+ list( , $toKey, $to ) = $toList;
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $conds = array(
+ 'page_namespace' => $namespace,
+ 'page_title >= ' . $dbr->addQuotes( $fromKey )
+ );
+
+ if ( $hideredirects ) {
+ $conds['page_is_redirect'] = 0;
+ }
+
+ if ( $toKey !== "" ) {
+ $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey );
+ }
+
+ $res = $dbr->select( 'page',
+ array( 'page_namespace', 'page_title', 'page_is_redirect', 'page_id' ),
+ $conds,
+ __METHOD__,
+ array(
+ 'ORDER BY' => 'page_title',
+ 'LIMIT' => $this->maxPerPage + 1,
+ 'USE INDEX' => 'name_title',
+ )
+ );
+
+ if ( $res->numRows() > 0 ) {
+ $out = Xml::openElement( 'ul', array( 'class' => 'mw-allpages-chunk' ) );
+ while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
+ $t = Title::newFromRow( $s );
+ if ( $t ) {
+ $out .= '<li' .
+ ( $s->page_is_redirect ? ' class="allpagesredirect"' : '' ) .
+ '>' .
+ Linker::link( $t ) .
+ "</li>\n";
+ } else {
+ $out .= '<li>[[' . htmlspecialchars( $s->page_title ) . "]]</li>\n";
+ }
+ $n++;
+ }
+ $out .= Xml::closeElement( 'ul' );
+ } else {
+ $out = '';
+ }
+ }
+
+ if ( $this->including() ) {
+ $output->addHTML( $out );
+ return;
+ }
+
+ if ( $from == '' ) {
+ // First chunk; no previous link.
+ $prevTitle = null;
+ } else {
+ # Get the last title from previous chunk
+ $dbr = wfGetDB( DB_SLAVE );
+ $res_prev = $dbr->select(
+ 'page',
+ 'page_title',
+ array( 'page_namespace' => $namespace, 'page_title < ' . $dbr->addQuotes( $from ) ),
+ __METHOD__,
+ array( 'ORDER BY' => 'page_title DESC',
+ 'LIMIT' => $this->maxPerPage, 'OFFSET' => ( $this->maxPerPage - 1 )
+ )
+ );
+
+ # Get first title of previous complete chunk
+ if ( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) {
+ $pt = $dbr->fetchObject( $res_prev );
+ $prevTitle = Title::makeTitle( $namespace, $pt->page_title );
+ } else {
+ # The previous chunk is not complete, need to link to the very first title
+ # available in the database
+ $options = array( 'LIMIT' => 1 );
+ if ( !$dbr->implicitOrderby() ) {
+ $options['ORDER BY'] = 'page_title';
+ }
+ $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title',
+ array( 'page_namespace' => $namespace ), __METHOD__, $options );
+ # Show the previous link if it s not the current requested chunk
+ if ( $from != $reallyFirstPage_title ) {
+ $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
+ } else {
+ $prevTitle = null;
+ }
+ }
+ }
+
+ $self = $this->getPageTitle();
+
+ $topLinks = array(
+ Linker::link( $self, $this->msg( 'allpages' )->escaped() )
+ );
+ $bottomLinks = array();
+
+ # Do we put a previous link ?
+ if ( $prevTitle && $pt = $prevTitle->getText() ) {
+ $query = array( 'from' => $prevTitle->getText() );
+
+ if ( $namespace ) {
+ $query['namespace'] = $namespace;
+ }
+
+ if ( $hideredirects ) {
+ $query['hideredirects'] = $hideredirects;
+ }
+
+ $prevLink = Linker::linkKnown(
+ $self,
+ $this->msg( 'prevpage', $pt )->escaped(),
+ array(),
+ $query
+ );
+ $topLinks[] = $prevLink;
+ $bottomLinks[] = $prevLink;
+ }
+
+ if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
+ # $s is the first link of the next chunk
+ $t = Title::makeTitle( $namespace, $s->page_title );
+ $query = array( 'from' => $t->getText() );
+
+ if ( $namespace ) {
+ $query['namespace'] = $namespace;
+ }
+
+ if ( $hideredirects ) {
+ $query['hideredirects'] = $hideredirects;
+ }
+
+ $nextLink = Linker::linkKnown(
+ $self,
+ $this->msg( 'nextpage', $t->getText() )->escaped(),
+ array(),
+ $query
+ );
+ $topLinks[] = $nextLink;
+ $bottomLinks[] = $nextLink;
+ }
+
+ $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
+ $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
+ '<tr>
+ <td>' .
+ $nsForm .
+ '</td>
+ <td class="mw-allpages-nav">' .
+ $this->getLanguage()->pipeList( $topLinks ) .
+ '</td></tr></table>';
+
+ $output->addHTML( $out2 . $out );
+
+ if ( count( $bottomLinks ) ) {
+ $output->addHTML(
+ Html::element( 'hr' ) .
+ Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
+ $this->getLanguage()->pipeList( $bottomLinks )
+ )
+ );
+ }
+ }
+
+ /**
+ * @param int $ns The namespace of the article
+ * @param string $text The name of the article
+ * @return array( int namespace, string dbkey, string pagename ) or null on error
+ */
+ protected function getNamespaceKeyAndText( $ns, $text ) {
+ if ( $text == '' ) {
+ # shortcut for common case
+ return array( $ns, '', '' );
+ }
+
+ $t = Title::makeTitleSafe( $ns, $text );
+ if ( $t && $t->isLocal() ) {
+ return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
+ } elseif ( $t ) {
+ return null;
+ }
+
+ # try again, in case the problem was an empty pagename
+ $text = preg_replace( '/(#|$)/', 'X$1', $text );
+ $t = Title::makeTitleSafe( $ns, $text );
+ if ( $t && $t->isLocal() ) {
+ return array( $t->getNamespace(), '', '' );
+ } else {
+ return null;
+ }
+ }
+
+ protected function getGroupName() {
+ return 'pages';
+ }
+}
diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php
deleted file mode 100644
index a0820493..00000000
--- a/includes/specials/SpecialAllpages.php
+++ /dev/null
@@ -1,573 +0,0 @@
-<?php
-/**
- * Implements Special:Allpages
- *
- * 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:Allpages
- *
- * @ingroup SpecialPage
- */
-class SpecialAllpages extends IncludableSpecialPage {
-
- /**
- * Maximum number of pages to show on single subpage.
- *
- * @var int $maxPerPage
- */
- protected $maxPerPage = 345;
-
- /**
- * Maximum number of pages to show on single index subpage.
- *
- * @var int $maxLineCount
- */
- protected $maxLineCount = 100;
-
- /**
- * Maximum number of chars to show for an entry.
- *
- * @var int $maxPageLength
- */
- protected $maxPageLength = 70;
-
- /**
- * Determines, which message describes the input field 'nsfrom'.
- *
- * @var string $nsfromMsg
- */
- protected $nsfromMsg = 'allpagesfrom';
-
- /**
- * Constructor
- *
- * @param string $name name of the special page, as seen in links and URLs (default: 'Allpages')
- */
- function __construct( $name = 'Allpages' ) {
- parent::__construct( $name );
- }
-
- /**
- * Entry point : initialise variables and call subfunctions.
- *
- * @param string $par becomes "FOO" when called like Special:Allpages/FOO (default NULL)
- */
- function execute( $par ) {
- $request = $this->getRequest();
- $out = $this->getOutput();
-
- $this->setHeaders();
- $this->outputHeader();
- $out->allowClickjacking();
-
- # GET values
- $from = $request->getVal( 'from', null );
- $to = $request->getVal( 'to', null );
- $namespace = $request->getInt( 'namespace' );
- $hideredirects = $request->getBool( 'hideredirects', false );
-
- $namespaces = $this->getContext()->getLanguage()->getNamespaces();
-
- $out->setPageTitle(
- ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) ?
- $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
- $this->msg( 'allarticles' )
- );
- $out->addModuleStyles( 'mediawiki.special' );
-
- if ( $par !== null ) {
- $this->showChunk( $namespace, $par, $to, $hideredirects );
- } elseif ( $from !== null && $to === null ) {
- $this->showChunk( $namespace, $from, $to, $hideredirects );
- } else {
- $this->showToplevel( $namespace, $from, $to, $hideredirects );
- }
- }
-
- /**
- * HTML for the top form
- *
- * @param $namespace Integer: a namespace constant (default NS_MAIN).
- * @param string $from dbKey we are starting listing at.
- * @param string $to dbKey we are ending listing at.
- * @param bool $hideredirects dont show redirects (default FALSE)
- * @return string
- */
- function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
- global $wgScript;
- $t = $this->getTitle();
-
- $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
- $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $out .= Html::hidden( 'title', $t->getPrefixedText() );
- $out .= Xml::openElement( 'fieldset' );
- $out .= Xml::element( 'legend', null, $this->msg( 'allpages' )->text() );
- $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
- $out .= "<tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'allpagesfrom' )->text(), 'nsfrom' ) .
- " </td>
- <td class='mw-input'>" .
- Xml::input( 'from', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) .
- " </td>
-</tr>
-<tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'allpagesto' )->text(), 'nsto' ) .
- " </td>
- <td class='mw-input'>" .
- Xml::input( 'to', 30, str_replace( '_', ' ', $to ), array( 'id' => 'nsto' ) ) .
- " </td>
-</tr>
-<tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
- " </td>
- <td class='mw-input'>" .
- Html::namespaceSelector(
- array( 'selected' => $namespace ),
- array( 'name' => 'namespace', 'id' => 'namespace' )
- ) . ' ' .
- Xml::checkLabel(
- $this->msg( 'allpages-hide-redirects' )->text(),
- 'hideredirects',
- 'hideredirects',
- $hideredirects
- ) . ' ' .
- Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
- " </td>
-</tr>";
- $out .= Xml::closeElement( 'table' );
- $out .= Xml::closeElement( 'fieldset' );
- $out .= Xml::closeElement( 'form' );
- $out .= Xml::closeElement( 'div' );
-
- return $out;
- }
-
- /**
- * @param $namespace Integer (default NS_MAIN)
- * @param string $from list all pages from this name
- * @param string $to list all pages to this name
- * @param bool $hideredirects dont show redirects (default FALSE)
- */
- function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
- $output = $this->getOutput();
-
- # TODO: Either make this *much* faster or cache the title index points
- # in the querycache table.
-
- $dbr = wfGetDB( DB_SLAVE );
- $out = "";
- $where = array( 'page_namespace' => $namespace );
-
- if ( $hideredirects ) {
- $where['page_is_redirect'] = 0;
- }
-
- $from = Title::makeTitleSafe( $namespace, $from );
- $to = Title::makeTitleSafe( $namespace, $to );
- $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
- $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
-
- if ( isset( $from ) ) {
- $where[] = 'page_title >= ' . $dbr->addQuotes( $from );
- }
-
- if ( isset( $to ) ) {
- $where[] = 'page_title <= ' . $dbr->addQuotes( $to );
- }
-
- global $wgMemc;
- $key = wfMemcKey( 'allpages', 'ns', $namespace, sha1( $from ), sha1( $to ) );
- $lines = $wgMemc->get( $key );
-
- $count = $dbr->estimateRowCount( 'page', '*', $where, __METHOD__ );
- $maxPerSubpage = intval( $count / $this->maxLineCount );
- $maxPerSubpage = max( $maxPerSubpage, $this->maxPerPage );
-
- if ( !is_array( $lines ) ) {
- $options = array( 'LIMIT' => 1 );
- $options['ORDER BY'] = 'page_title ASC';
- $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options );
- $lastTitle = $firstTitle;
- # This array is going to hold the page_titles in order.
- $lines = array( $firstTitle );
- # If we are going to show n rows, we need n+1 queries to find the relevant titles.
- $done = false;
- while ( !$done ) {
- // Fetch the last title of this chunk and the first of the next
- $chunk = ( $lastTitle === false )
- ? array()
- : array( 'page_title >= ' . $dbr->addQuotes( $lastTitle ) );
- $res = $dbr->select( 'page', /* FROM */
- 'page_title', /* WHAT */
- array_merge( $where, $chunk ),
- __METHOD__,
- array( 'LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC' )
- );
-
- $s = $dbr->fetchObject( $res );
- if ( $s ) {
- array_push( $lines, $s->page_title );
- } else {
- // Final chunk, but ended prematurely. Go back and find the end.
- $endTitle = $dbr->selectField( 'page', 'MAX(page_title)',
- array_merge( $where, $chunk ),
- __METHOD__ );
- array_push( $lines, $endTitle );
- $done = true;
- }
-
- $s = $res->fetchObject();
- if ( $s ) {
- array_push( $lines, $s->page_title );
- $lastTitle = $s->page_title;
- } else {
- // This was a final chunk and ended exactly at the limit.
- // Rare but convenient!
- $done = true;
- }
- $res->free();
- }
- $wgMemc->add( $key, $lines, 3600 );
- }
-
- // If there are only two or less sections, don't even display them.
- // Instead, display the first section directly.
- if ( count( $lines ) <= 2 ) {
- if ( !empty( $lines ) ) {
- $this->showChunk( $namespace, $from, $to, $hideredirects );
- } else {
- $output->addHTML( $this->namespaceForm( $namespace, $from, $to, $hideredirects ) );
- }
-
- return;
- }
-
- # At this point, $lines should contain an even number of elements.
- $out .= Xml::openElement( 'table', array( 'class' => 'allpageslist' ) );
- while ( count( $lines ) > 0 ) {
- $inpoint = array_shift( $lines );
- $outpoint = array_shift( $lines );
- $out .= $this->showline( $inpoint, $outpoint, $namespace, $hideredirects );
- }
- $out .= Xml::closeElement( 'table' );
- $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
-
- # Is there more?
- if ( $this->including() ) {
- $out2 = '';
- } else {
- if ( isset( $from ) || isset( $to ) ) {
- $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
- '<tr>
- <td>' .
- $nsForm .
- '</td>
- <td class="mw-allpages-nav">' .
- Linker::link( $this->getTitle(), $this->msg( 'allpages' )->escaped(),
- array(), array(), 'known' ) .
- "</td>
- </tr>" .
- Xml::closeElement( 'table' );
- } else {
- $out2 = $nsForm;
- }
- }
- $output->addHTML( $out2 . $out );
- }
-
- /**
- * Show a line of "ABC to DEF" ranges of articles
- *
- * @param string $inpoint lower limit of pagenames
- * @param string $outpoint upper limit of pagenames
- * @param $namespace Integer (Default NS_MAIN)
- * @param bool $hideRedirects don't show redirects. Default: false
- * @return string
- */
- function showline( $inpoint, $outpoint, $namespace = NS_MAIN, $hideRedirects = false ) {
- // Use content language since page titles are considered to use content language
- global $wgContLang;
-
- $inpointf = str_replace( '_', ' ', $inpoint );
- $outpointf = str_replace( '_', ' ', $outpoint );
-
- // Don't let the length runaway
- $inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength );
- $outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength );
-
- $queryParams = array(
- 'from' => $inpoint,
- 'to' => $outpoint,
- );
-
- if ( $namespace ) {
- $queryParams['namespace'] = $namespace;
- }
- if ( $hideRedirects ) {
- $queryParams['hideredirects'] = 1;
- }
-
- $url = $this->getTitle()->getLocalURL( $queryParams );
- $inlink = Html::element( 'a', array( 'href' => $url ), $inpointf );
- $outlink = Html::element( 'a', array( 'href' => $url ), $outpointf );
-
- $out = $this->msg( 'alphaindexline' )->rawParams(
- "$inlink</td><td>",
- "</td><td>$outlink"
- )->escaped();
-
- return '<tr><td class="mw-allpages-alphaindexline">' . $out . '</td></tr>';
- }
-
- /**
- * @param int $namespace Namespace (Default NS_MAIN)
- * @param string $from list all pages from this name (default FALSE)
- * @param string $to list all pages to this name (default FALSE)
- * @param bool $hideredirects dont show redirects (default FALSE)
- */
- function showChunk( $namespace = NS_MAIN, $from = false, $to = false, $hideredirects = false ) {
- $output = $this->getOutput();
-
- $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
- $toList = $this->getNamespaceKeyAndText( $namespace, $to );
- $namespaces = $this->getContext()->getLanguage()->getNamespaces();
- $n = 0;
-
- if ( !$fromList || !$toList ) {
- $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
- } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
- // Show errormessage and reset to NS_MAIN
- $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
- $namespace = NS_MAIN;
- } else {
- list( $namespace, $fromKey, $from ) = $fromList;
- list( , $toKey, $to ) = $toList;
-
- $dbr = wfGetDB( DB_SLAVE );
- $conds = array(
- 'page_namespace' => $namespace,
- 'page_title >= ' . $dbr->addQuotes( $fromKey )
- );
-
- if ( $hideredirects ) {
- $conds['page_is_redirect'] = 0;
- }
-
- if ( $toKey !== "" ) {
- $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey );
- }
-
- $res = $dbr->select( 'page',
- array( 'page_namespace', 'page_title', 'page_is_redirect', 'page_id' ),
- $conds,
- __METHOD__,
- array(
- 'ORDER BY' => 'page_title',
- 'LIMIT' => $this->maxPerPage + 1,
- 'USE INDEX' => 'name_title',
- )
- );
-
- if ( $res->numRows() > 0 ) {
- $out = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-chunk' ) );
- while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
- $t = Title::newFromRow( $s );
- if ( $t ) {
- $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
- Linker::link( $t ) .
- ( $s->page_is_redirect ? '</div>' : '' );
- } else {
- $link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
- }
-
- if ( $n % 3 == 0 ) {
- $out .= '<tr>';
- }
-
- $out .= "<td style=\"width:33%\">$link</td>";
- $n++;
- if ( $n % 3 == 0 ) {
- $out .= "</tr>\n";
- }
- }
-
- if ( ( $n % 3 ) != 0 ) {
- $out .= "</tr>\n";
- }
- $out .= Xml::closeElement( 'table' );
- } else {
- $out = '';
- }
- }
-
- if ( $this->including() ) {
- $out2 = '';
- } else {
- if ( $from == '' ) {
- // First chunk; no previous link.
- $prevTitle = null;
- } else {
- # Get the last title from previous chunk
- $dbr = wfGetDB( DB_SLAVE );
- $res_prev = $dbr->select(
- 'page',
- 'page_title',
- array( 'page_namespace' => $namespace, 'page_title < ' . $dbr->addQuotes( $from ) ),
- __METHOD__,
- array( 'ORDER BY' => 'page_title DESC',
- 'LIMIT' => $this->maxPerPage, 'OFFSET' => ( $this->maxPerPage - 1 )
- )
- );
-
- # Get first title of previous complete chunk
- if ( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) {
- $pt = $dbr->fetchObject( $res_prev );
- $prevTitle = Title::makeTitle( $namespace, $pt->page_title );
- } else {
- # The previous chunk is not complete, need to link to the very first title
- # available in the database
- $options = array( 'LIMIT' => 1 );
- if ( !$dbr->implicitOrderby() ) {
- $options['ORDER BY'] = 'page_title';
- }
- $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title',
- array( 'page_namespace' => $namespace ), __METHOD__, $options );
- # Show the previous link if it s not the current requested chunk
- if ( $from != $reallyFirstPage_title ) {
- $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
- } else {
- $prevTitle = null;
- }
- }
- }
-
- $self = $this->getTitle();
-
- $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
- $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
- '<tr>
- <td>' .
- $nsForm .
- '</td>
- <td class="mw-allpages-nav">' .
- Linker::link( $self, $this->msg( 'allpages' )->escaped() );
-
- # Do we put a previous link ?
- if ( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
- $query = array( 'from' => $prevTitle->getText() );
-
- if ( $namespace ) {
- $query['namespace'] = $namespace;
- }
-
- if ( $hideredirects ) {
- $query['hideredirects'] = $hideredirects;
- }
-
- $prevLink = Linker::linkKnown(
- $self,
- $this->msg( 'prevpage', $pt )->escaped(),
- array(),
- $query
- );
- $out2 = $this->getLanguage()->pipeList( array( $out2, $prevLink ) );
- }
-
- if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
- # $s is the first link of the next chunk
- $t = Title::makeTitle( $namespace, $s->page_title );
- $query = array( 'from' => $t->getText() );
-
- if ( $namespace ) {
- $query['namespace'] = $namespace;
- }
-
- if ( $hideredirects ) {
- $query['hideredirects'] = $hideredirects;
- }
-
- $nextLink = Linker::linkKnown(
- $self,
- $this->msg( 'nextpage', $t->getText() )->escaped(),
- array(),
- $query
- );
- $out2 = $this->getLanguage()->pipeList( array( $out2, $nextLink ) );
- }
- $out2 .= "</td></tr></table>";
- }
-
- $output->addHTML( $out2 . $out );
-
- $links = array();
- if ( isset( $prevLink ) ) {
- $links[] = $prevLink;
- }
-
- if ( isset( $nextLink ) ) {
- $links[] = $nextLink;
- }
-
- if ( count( $links ) ) {
- $output->addHTML(
- Html::element( 'hr' ) .
- Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
- $this->getLanguage()->pipeList( $links )
- )
- );
- }
- }
-
- /**
- * @param $ns Integer: the namespace of the article
- * @param string $text the name of the article
- * @return array( int namespace, string dbkey, string pagename ) or NULL on error
- */
- protected function getNamespaceKeyAndText( $ns, $text ) {
- if ( $text == '' ) {
- # shortcut for common case
- return array( $ns, '', '' );
- }
-
- $t = Title::makeTitleSafe( $ns, $text );
- if ( $t && $t->isLocal() ) {
- return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
- } elseif ( $t ) {
- return null;
- }
-
- # try again, in case the problem was an empty pagename
- $text = preg_replace( '/(#|$)/', 'X$1', $text );
- $t = Title::makeTitleSafe( $ns, $text );
- if ( $t && $t->isLocal() ) {
- return array( $t->getNamespace(), '', '' );
- } else {
- return null;
- }
- }
-
- protected function getGroupName() {
- return 'pages';
- }
-}
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
index 3b73a374..3297c17a 100644
--- a/includes/specials/SpecialBlock.php
+++ b/includes/specials/SpecialBlock.php
@@ -28,27 +28,23 @@
* @ingroup SpecialPage
*/
class SpecialBlock extends FormSpecialPage {
- /** The maximum number of edits a user can have and still be hidden
- * TODO: config setting? */
- const HIDEUSER_CONTRIBLIMIT = 1000;
-
- /** @var User user to be blocked, as passed either by parameter (url?wpTarget=Foo)
+ /** @var User User to be blocked, as passed either by parameter (url?wpTarget=Foo)
* or as subpage (Special:Block/Foo) */
protected $target;
- /// @var Block::TYPE_ constant
+ /** @var int Block::TYPE_ constant */
protected $type;
- /// @var User|String the previous block target
+ /** @var User|string The previous block target */
protected $previousTarget;
- /// @var Bool whether the previous submission of the form asked for HideUser
+ /** @var bool Whether the previous submission of the form asked for HideUser */
protected $requestedHideUser;
- /// @var Bool
+ /** @var bool */
protected $alreadyBlocked;
- /// @var Array
+ /** @var array */
protected $preErrors = array();
public function __construct() {
@@ -74,7 +70,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Handle some magic here
*
- * @param $par String
+ * @param string $par
*/
protected function setParameter( $par ) {
# Extract variables from the request. Try not to get into a situation where we
@@ -88,14 +84,15 @@ class SpecialBlock extends FormSpecialPage {
$this->getSkin()->setRelevantUser( $this->target );
}
- list( $this->previousTarget, /*...*/ ) = Block::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
+ list( $this->previousTarget, /*...*/ ) =
+ Block::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
$this->requestedHideUser = $request->getBool( 'wpHideUser' );
}
/**
* Customizes the HTMLForm a bit
*
- * @param $form HTMLForm
+ * @param HTMLForm $form
*/
protected function alterForm( HTMLForm $form ) {
$form->setWrapperLegendMsg( 'blockip-legend' );
@@ -120,7 +117,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Get the HTMLForm descriptor array for the block form
- * @return Array
+ * @return array
*/
protected function getFormFields() {
global $wgBlockAllowsUTEdit;
@@ -132,8 +129,7 @@ class SpecialBlock extends FormSpecialPage {
$a = array(
'Target' => array(
'type' => 'text',
- 'label-message' => 'ipadressorusername',
- 'tabindex' => '1',
+ 'label-message' => 'ipaddressorusername',
'id' => 'mw-bi-target',
'size' => '45',
'autofocus' => true,
@@ -144,7 +140,6 @@ class SpecialBlock extends FormSpecialPage {
'type' => !count( $suggestedDurations ) ? 'text' : 'selectorother',
'label-message' => 'ipbexpiry',
'required' => true,
- 'tabindex' => '2',
'options' => $suggestedDurations,
'other' => $this->msg( 'ipbother' )->text(),
'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
@@ -221,6 +216,9 @@ class SpecialBlock extends FormSpecialPage {
$this->maybeAlterFormDefaults( $a );
+ // Allow extensions to add more fields
+ wfRunHooks( 'SpecialBlockModifyFormFields', array( $this, &$a ) );
+
return $a;
}
@@ -228,7 +226,7 @@ class SpecialBlock extends FormSpecialPage {
* If the user has already been blocked with similar settings, load that block
* and change the defaults for the form fields to match the existing settings.
* @param array $fields HTMLForm descriptor array
- * @return Bool whether fields were altered (that is, whether the target is
+ * @return bool Whether fields were altered (that is, whether the target is
* already blocked)
*/
protected function maybeAlterFormDefaults( &$fields ) {
@@ -293,20 +291,20 @@ class SpecialBlock extends FormSpecialPage {
if ( $this->requestedHideUser ) {
$fields['Confirm']['type'] = 'check';
unset( $fields['Confirm']['default'] );
- $this->preErrors[] = 'ipb-confirmhideuser';
+ $this->preErrors[] = array( 'ipb-confirmhideuser', 'ipb-confirmaction' );
}
# Or if the user is trying to block themselves
if ( (string)$this->target === $this->getUser()->getName() ) {
$fields['Confirm']['type'] = 'check';
unset( $fields['Confirm']['default'] );
- $this->preErrors[] = 'ipb-blockingself';
+ $this->preErrors[] = array( 'ipb-blockingself', 'ipb-confirmaction' );
}
}
/**
* Add header elements like block log entries, etc.
- * @return String
+ * @return string
*/
protected function preText() {
$this->getOutput()->addModules( 'mediawiki.special.block' );
@@ -362,7 +360,10 @@ class SpecialBlock extends FormSpecialPage {
# Link to unblock the specified user, or to a blank unblock form
if ( $this->target instanceof User ) {
- $message = $this->msg( 'ipb-unblock-addr', wfEscapeWikiText( $this->target->getName() ) )->parse();
+ $message = $this->msg(
+ 'ipb-unblock-addr',
+ wfEscapeWikiText( $this->target->getName() )
+ )->parse();
$list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
} else {
$message = $this->msg( 'ipb-unblock' )->parse();
@@ -437,7 +438,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Get a user page target for things like logs.
* This handles account and IP range targets.
- * @param $target User|string
+ * @param User|string $target
* @return Title|null
*/
protected static function getTargetUserTitle( $target ) {
@@ -452,10 +453,10 @@ class SpecialBlock extends FormSpecialPage {
/**
* Determine the target of the block, and the type of target
- * TODO: should be in Block.php?
- * @param string $par subpage parameter passed to setup, or data value from
+ * @todo Should be in Block.php?
+ * @param string $par Subpage parameter passed to setup, or data value from
* the HTMLForm
- * @param $request WebRequest optionally try and get data from a request too
+ * @param WebRequest $request Optionally try and get data from a request too
* @return array( User|string|null, Block::TYPE_ constant|null )
*/
public static function getTargetAndType( $par, WebRequest $request = null ) {
@@ -504,9 +505,9 @@ class SpecialBlock extends FormSpecialPage {
/**
* HTMLForm field validation-callback for Target field.
* @since 1.18
- * @param $value String
- * @param $alldata Array
- * @param $form HTMLForm
+ * @param string $value
+ * @param array $alldata
+ * @param HTMLForm $form
* @return Message
*/
public static function validateTargetField( $value, $alldata, $form ) {
@@ -584,9 +585,9 @@ class SpecialBlock extends FormSpecialPage {
/**
* Submit callback for an HTMLForm object, will simply pass
- * @param $data array
- * @param $form HTMLForm
- * @return Bool|String
+ * @param array $data
+ * @param HTMLForm $form
+ * @return bool|string
*/
public static function processUIForm( array $data, HTMLForm $form ) {
return self::processForm( $data, $form->getContext() );
@@ -594,12 +595,12 @@ class SpecialBlock extends FormSpecialPage {
/**
* Given the form data, actually implement a block
- * @param $data Array
- * @param $context IContextSource
- * @return Bool|String
+ * @param array $data
+ * @param IContextSource $context
+ * @return bool|string
*/
public static function processForm( array $data, IContextSource $context ) {
- global $wgBlockAllowsUTEdit;
+ global $wgBlockAllowsUTEdit, $wgHideUserContribLimit, $wgContLang;
$performer = $context->getUser();
@@ -627,7 +628,7 @@ class SpecialBlock extends FormSpecialPage {
if ( $target === $performer->getName() &&
( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
) {
- return array( 'ipb-blockingself' );
+ return array( 'ipb-blockingself', 'ipb-confirmaction' );
}
} elseif ( $type == Block::TYPE_RANGE ) {
$userId = 0;
@@ -670,12 +671,15 @@ class SpecialBlock extends FormSpecialPage {
} elseif ( !in_array( $data['Expiry'], array( 'infinite', 'infinity', 'indefinite' ) ) ) {
# Bad expiry.
return array( 'ipb_expiry_temp' );
- } elseif ( $user->getEditCount() > self::HIDEUSER_CONTRIBLIMIT ) {
+ } elseif ( $wgHideUserContribLimit !== false
+ && $user->getEditCount() > $wgHideUserContribLimit
+ ) {
# Typically, the user should have a handful of edits.
# Disallow hiding users with many edits for performance.
- return array( 'ipb_hide_invalid' );
+ return array( array( 'ipb_hide_invalid',
+ Message::numParam( $wgHideUserContribLimit ) ) );
} elseif ( !$data['Confirm'] ) {
- return array( 'ipb-confirmhideuser' );
+ return array( 'ipb-confirmhideuser', 'ipb-confirmaction' );
}
}
@@ -683,7 +687,8 @@ class SpecialBlock extends FormSpecialPage {
$block = new Block();
$block->setTarget( $target );
$block->setBlocker( $performer );
- $block->mReason = $data['Reason'][0];
+ # Truncate reason for whole multibyte characters
+ $block->mReason = $wgContLang->truncate( $data['Reason'][0], 255 );
$block->mExpiry = self::parseExpiryInput( $data['Expiry'] );
$block->prevents( 'createaccount', $data['CreateAccount'] );
$block->prevents( 'editownusertalk', ( !$wgBlockAllowsUTEdit || $data['DisableUTEdit'] ) );
@@ -692,8 +697,9 @@ class SpecialBlock extends FormSpecialPage {
$block->isAutoblocking( $data['AutoBlock'] );
$block->mHideName = $data['HideUser'];
- if ( !wfRunHooks( 'BlockIp', array( &$block, &$performer ) ) ) {
- return array( 'hookaborted' );
+ $reason = array( 'hookaborted' );
+ if ( !wfRunHooks( 'BlockIp', array( &$block, &$performer, &$reason ) ) ) {
+ return $reason;
}
# Try to insert block. Is there a conflicting block?
@@ -726,8 +732,17 @@ class SpecialBlock extends FormSpecialPage {
return array( 'cant-see-hidden-user' );
}
- $currentBlock->delete();
- $status = $block->insert();
+ $currentBlock->isHardblock( $block->isHardblock() );
+ $currentBlock->prevents( 'createaccount', $block->prevents( 'createaccount' ) );
+ $currentBlock->mExpiry = $block->mExpiry;
+ $currentBlock->isAutoblocking( $block->isAutoblocking() );
+ $currentBlock->mHideName = $block->mHideName;
+ $currentBlock->prevents( 'sendemail', $block->prevents( 'sendemail' ) );
+ $currentBlock->prevents( 'editownusertalk', $block->prevents( 'editownusertalk' ) );
+ $currentBlock->mReason = $block->mReason;
+
+ $status = $currentBlock->update();
+
$logaction = 'reblock';
# Unset _deleted fields if requested
@@ -753,7 +768,11 @@ class SpecialBlock extends FormSpecialPage {
# Can't watch a rangeblock
if ( $type != Block::TYPE_RANGE && $data['Watch'] ) {
- WatchAction::doWatch( Title::makeTitle( NS_USER, $target ), $performer, WatchedItem::IGNORE_USER_RIGHTS );
+ WatchAction::doWatch(
+ Title::makeTitle( NS_USER, $target ),
+ $performer,
+ WatchedItem::IGNORE_USER_RIGHTS
+ );
}
# Block constructor sanitizes certain block options on insert
@@ -787,9 +806,9 @@ class SpecialBlock extends FormSpecialPage {
* Get an array of suggested block durations from MediaWiki:Ipboptions
* @todo FIXME: This uses a rather odd syntax for the options, should it be converted
* to the standard "**<duration>|<displayname>" format?
- * @param $lang Language|null the language to get the durations in, or null to use
+ * @param Language|null $lang The language to get the durations in, or null to use
* the wiki's content language
- * @return Array
+ * @return array
*/
public static function getSuggestedDurations( $lang = null ) {
$a = array();
@@ -816,8 +835,8 @@ class SpecialBlock extends FormSpecialPage {
/**
* Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
* ("24 May 2034", etc), into an absolute timestamp we can put into the database.
- * @param string $expiry whatever was typed into the form
- * @return String: timestamp or "infinity" string for the DB implementation
+ * @param string $expiry Whatever was typed into the form
+ * @return string Timestamp or "infinity" string for the DB implementation
*/
public static function parseExpiryInput( $expiry ) {
static $infinity;
@@ -842,8 +861,8 @@ class SpecialBlock extends FormSpecialPage {
/**
* Can we do an email block?
- * @param $user User: the sysop wanting to make a block
- * @return Boolean
+ * @param User $user The sysop wanting to make a block
+ * @return bool
*/
public static function canBlockEmail( $user ) {
global $wgEnableUserEmail, $wgSysopEmailBans;
@@ -855,9 +874,9 @@ class SpecialBlock extends FormSpecialPage {
* bug 15810: blocked admins should not be able to block/unblock
* others, and probably shouldn't be able to unblock themselves
* either.
- * @param $user User|Int|String
- * @param $performer User user doing the request
- * @return Bool|String true or error message key
+ * @param User|int|string $user
+ * @param User $performer User doing the request
+ * @return bool|string True or error message key
*/
public static function checkUnblockSelf( $user, User $performer ) {
if ( is_int( $user ) ) {
@@ -889,8 +908,8 @@ class SpecialBlock extends FormSpecialPage {
/**
* Return a comma-delimited list of "flags" to be passed to the log
* reader for this block, to provide more information in the logs
- * @param array $data from HTMLForm data
- * @param $type Block::TYPE_ constant (USER, RANGE, or IP)
+ * @param array $data From HTMLForm data
+ * @param int $type Block::TYPE_ constant (USER, RANGE, or IP)
* @return string
*/
protected static function blockLogFlags( array $data, $type ) {
@@ -935,8 +954,8 @@ class SpecialBlock extends FormSpecialPage {
/**
* Process the form on POST submission.
- * @param $data Array
- * @return Bool|Array true for success, false for didn't-try, array of errors on failure
+ * @param array $data
+ * @return bool|array True for success, false for didn't-try, array of errors on failure
*/
public function onSubmit( array $data ) {
// This isn't used since we need that HTMLForm that's passed in the
@@ -957,7 +976,3 @@ class SpecialBlock extends FormSpecialPage {
return 'users';
}
}
-
-# BC @since 1.18
-class IPBlockForm extends SpecialBlock {
-}
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
index f1992c0f..456f4ecb 100644
--- a/includes/specials/SpecialBlockList.php
+++ b/includes/specials/SpecialBlockList.php
@@ -27,8 +27,9 @@
* @ingroup SpecialPage
*/
class SpecialBlockList extends SpecialPage {
+ protected $target;
- protected $target, $options;
+ protected $options;
function __construct() {
parent::__construct( 'BlockList' );
@@ -37,7 +38,7 @@ class SpecialBlockList extends SpecialPage {
/**
* Main execution point
*
- * @param string $par title fragment
+ * @param string $par Title fragment
*/
public function execute( $par ) {
$this->setHeaders();
@@ -67,7 +68,7 @@ class SpecialBlockList extends SpecialPage {
$fields = array(
'Target' => array(
'type' => 'text',
- 'label-message' => 'ipadressorusername',
+ 'label-message' => 'ipaddressorusername',
'tabindex' => '1',
'size' => '45',
'default' => $this->target,
@@ -83,7 +84,7 @@ class SpecialBlockList extends SpecialPage {
'flatlist' => true,
),
'Limit' => array(
- 'class' => 'HTMLBlockedUsersItemSelect',
+ 'type' => 'limitselect',
'label-message' => 'table_pager_limit_label',
'options' => array(
$lang->formatNum( 20 ) => 20,
@@ -97,7 +98,7 @@ class SpecialBlockList extends SpecialPage {
),
);
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new HTMLForm( $fields, $context );
$form->setMethod( 'get' );
$form->setWrapperLegendMsg( 'ipblocklist-legend' );
@@ -180,11 +181,7 @@ class SpecialBlockList extends SpecialPage {
$pager = new BlockListPager( $this, $conds );
if ( $pager->getNumRows() ) {
- $out->addHTML(
- $pager->getNavigationBar() .
- $pager->getBody() .
- $pager->getNavigationBar()
- );
+ $out->addParserOutputContent( $pager->getFullOutput() );
} elseif ( $this->target ) {
$out->addWikiMsg( 'ipblocklist-no-results' );
} else {
@@ -203,7 +200,11 @@ class SpecialBlockList extends SpecialPage {
foreach ( $otherBlockLink as $link ) {
$list .= Html::rawElement( 'li', array(), $link ) . "\n";
}
- $out->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" );
+ $out->addHTML( Html::rawElement(
+ 'ul',
+ array( 'class' => 'mw-ipblocklist-otherblocks' ),
+ $list
+ ) . "\n" );
}
}
@@ -217,20 +218,20 @@ class BlockListPager extends TablePager {
protected $page;
/**
- * @param $page SpecialPage
- * @param $conds Array
+ * @param SpecialPage $page
+ * @param array $conds
*/
function __construct( $page, $conds ) {
$this->page = $page;
$this->conds = $conds;
- $this->mDefaultDirection = true;
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
parent::__construct( $page->getContext() );
}
function getFieldNames() {
static $headers = null;
- if ( $headers == array() ) {
+ if ( $headers === null ) {
$headers = array(
'ipb_timestamp' => 'blocklist-timestamp',
'ipb_target' => 'blocklist-target',
@@ -404,7 +405,7 @@ class BlockListPager extends TablePager {
}
public function getTableClass() {
- return 'TablePager mw-blocklist';
+ return parent::getTableClass() . ' mw-blocklist';
}
function getIndexField() {
@@ -452,35 +453,3 @@ class BlockListPager extends TablePager {
wfProfileOut( __METHOD__ );
}
}
-
-/**
- * Items per page dropdown. Essentially a crap workaround for bug 32603.
- *
- * @todo Do not release 1.19 with this.
- */
-class HTMLBlockedUsersItemSelect extends HTMLSelectField {
- /**
- * Basically don't do any validation. If it's a number that's fine. Also,
- * add it to the list if it's not there already
- *
- * @param $value
- * @param $alldata
- * @return bool
- */
- function validate( $value, $alldata ) {
- if ( $value == '' ) {
- return true;
- }
-
- // Let folks pick an explicit limit not from our list, as long as it's a real numbr.
- if ( !in_array( $value, $this->mParams['options'] ) && $value == intval( $value ) && $value > 0 ) {
- // This adds the explicitly requested limit value to the drop-down,
- // then makes sure it's sorted correctly so when we output the list
- // later, the custom option doesn't just show up last.
- $this->mParams['options'][$this->mParent->getLanguage()->formatNum( $value )] = intval( $value );
- asort( $this->mParams['options'] );
- }
-
- return true;
- }
-}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index 5ad961c3..72f4e466 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -30,7 +30,6 @@
* @ingroup SpecialPage
*/
class SpecialBookSources extends SpecialPage {
-
/**
* ISBN passed to the page, if any
*/
@@ -55,7 +54,10 @@ class SpecialBookSources extends SpecialPage {
$this->getOutput()->addHTML( $this->makeForm() );
if ( strlen( $this->isbn ) > 0 ) {
if ( !self::isValidISBN( $this->isbn ) ) {
- $this->getOutput()->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' );
+ $this->getOutput()->wrapWikiMsg(
+ "<div class=\"error\">\n$1\n</div>",
+ 'booksources-invalid-isbn'
+ );
}
$this->showList();
}
@@ -115,16 +117,26 @@ class SpecialBookSources extends SpecialPage {
* @return string
*/
private function makeForm() {
- global $wgScript;
-
$form = Html::openElement( 'fieldset' ) . "\n";
- $form .= Html::element( 'legend', array(), $this->msg( 'booksources-search-legend' )->text() ) . "\n";
- $form .= Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . "\n";
- $form .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
- $form .= '<p>' . Xml::inputLabel( $this->msg( 'booksources-isbn' )->text(), 'isbn', 'isbn', 20, $this->isbn, array( 'autofocus' => true ) );
+ $form .= Html::element(
+ 'legend',
+ array(),
+ $this->msg( 'booksources-search-legend' )->text()
+ ) . "\n";
+ $form .= Html::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) . "\n";
+ $form .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) . "\n";
+ $form .= '<p>' . Xml::inputLabel(
+ $this->msg( 'booksources-isbn' )->text(),
+ 'isbn',
+ 'isbn',
+ 20,
+ $this->isbn,
+ array( 'autofocus' => true )
+ );
$form .= '&#160;' . Xml::submitButton( $this->msg( 'booksources-go' )->text() ) . "</p>\n";
$form .= Html::closeElement( 'form' ) . "\n";
$form .= Html::closeElement( 'fieldset' ) . "\n";
+
return $form;
}
@@ -188,6 +200,6 @@ class SpecialBookSources extends SpecialPage {
}
protected function getGroupName() {
- return 'other';
+ return 'wiki';
}
}
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index b2ddc220..1bbdbeab 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -28,7 +28,6 @@
* @ingroup SpecialPage
*/
class BrokenRedirectsPage extends QueryPage {
-
function __construct( $name = 'BrokenRedirects' ) {
parent::__construct( $name );
}
@@ -148,7 +147,8 @@ class BrokenRedirectsPage extends QueryPage {
);
}
- $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $links ) )->escaped();
+ $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
+ ->pipeList( $links ) )->escaped();
$out .= " {$arr} {$to}";
return $out;
diff --git a/includes/specials/SpecialCachedPage.php b/includes/specials/SpecialCachedPage.php
index 39305f01..cb9b07cd 100644
--- a/includes/specials/SpecialCachedPage.php
+++ b/includes/specials/SpecialCachedPage.php
@@ -38,7 +38,6 @@
* @since 1.20
*/
abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
-
/**
* CacheHelper object to which we forward the non-SpecialPage specific caching work.
* Initialized in startCache.
@@ -52,7 +51,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
* If the cache is enabled or not.
*
* @since 1.20
- * @var boolean
+ * @var bool
*/
protected $cacheEnabled = true;
@@ -61,7 +60,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
*
* @since 1.20
*
- * @param $subPage string|null
+ * @param string|null $subPage
*/
protected function afterExecute( $subPage ) {
$this->saveCache();
@@ -73,7 +72,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
* Sets if the cache should be enabled or not.
*
* @since 1.20
- * @param boolean $cacheEnabled
+ * @param bool $cacheEnabled
*/
public function setCacheEnabled( $cacheEnabled ) {
$this->cacheHelper->setCacheEnabled( $cacheEnabled );
@@ -85,8 +84,8 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
*
* @since 1.20
*
- * @param integer|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
- * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
+ * @param int|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
+ * @param bool|null $cacheEnabled Sets if the cache should be enabled or not.
*/
public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
if ( !isset( $this->cacheHelper ) ) {
@@ -142,7 +141,11 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
* @param string|null $key
*/
public function addCachedHTML( $computeFunction, $args = array(), $key = null ) {
- $this->getOutput()->addHTML( $this->cacheHelper->getCachedValue( $computeFunction, $args, $key ) );
+ $this->getOutput()->addHTML( $this->cacheHelper->getCachedValue(
+ $computeFunction,
+ $args,
+ $key
+ ) );
}
/**
@@ -158,11 +161,12 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
}
/**
- * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
+ * Sets the time to live for the cache, in seconds or a unix timestamp
+ * indicating the point of expiry.
*
* @since 1.20
*
- * @param integer $cacheExpiry
+ * @param int $cacheExpiry
*/
public function setExpiry( $cacheExpiry ) {
$this->cacheHelper->setExpiry( $cacheExpiry );
@@ -187,7 +191,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
*
* @since 1.20
*
- * @param boolean $hasCached
+ * @param bool $hasCached
*/
public function onCacheInitialized( $hasCached ) {
if ( $hasCached ) {
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index d01bfd7d..95f9efd2 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -26,18 +26,55 @@
*/
class SpecialCategories extends SpecialPage {
- function __construct() {
+ /**
+ * @var PageLinkRenderer
+ */
+ protected $linkRenderer = null;
+
+ public function __construct() {
parent::__construct( 'Categories' );
+
+ // Since we don't control the constructor parameters, we can't inject services that way.
+ // Instead, we initialize services in the execute() method, and allow them to be overridden
+ // using the initServices() method.
+ }
+
+ /**
+ * Initialize or override the PageLinkRenderer SpecialCategories collaborates with.
+ * Useful mainly for testing.
+ *
+ * @todo the pager should also be injected, and de-coupled from the rendering logic.
+ *
+ * @param PageLinkRenderer $linkRenderer
+ */
+ public function setPageLinkRenderer(
+ PageLinkRenderer $linkRenderer
+ ) {
+ $this->linkRenderer = $linkRenderer;
+ }
+
+ /**
+ * Initialize any services we'll need (unless it has already been provided via a setter).
+ * This allows for dependency injection even though we don't control object creation.
+ */
+ private function initServices() {
+ if ( !$this->linkRenderer ) {
+ $lang = $this->getContext()->getLanguage();
+ $titleFormatter = new MediaWikiTitleCodec( $lang, GenderCache::singleton() );
+ $this->linkRenderer = new MediaWikiPageLinkRenderer( $titleFormatter );
+ }
}
- function execute( $par ) {
+ public function execute( $par ) {
+ $this->initServices();
+
$this->setHeaders();
$this->outputHeader();
$this->getOutput()->allowClickjacking();
$from = $this->getRequest()->getText( 'from', $par );
- $cap = new CategoryPager( $this->getContext(), $from );
+ $cap = new CategoryPager( $this->getContext(), $from, $this->linkRenderer );
$cap->doQuery();
$this->getOutput()->addHTML(
@@ -63,7 +100,19 @@ class SpecialCategories extends SpecialPage {
* @ingroup SpecialPage Pager
*/
class CategoryPager extends AlphabeticPager {
- function __construct( IContextSource $context, $from ) {
+
+ /**
+ * @var PageLinkRenderer
+ */
+ protected $linkRenderer;
+
+ /**
+ * @param IContextSource $context
+ * @param string $from
+ * @param PageLinkRenderer $linkRenderer
+ */
+ public function __construct( IContextSource $context, $from, PageLinkRenderer $linkRenderer
+ ) {
parent::__construct( $context );
$from = str_replace( ' ', '_', $from );
if ( $from !== '' ) {
@@ -71,6 +120,8 @@ class CategoryPager extends AlphabeticPager {
$this->setOffset( $from );
$this->setIncludeOffset( true );
}
+
+ $this->linkRenderer = $linkRenderer;
}
function getQueryInfo() {
@@ -120,19 +171,18 @@ class CategoryPager extends AlphabeticPager {
}
function formatRow( $result ) {
- $title = Title::makeTitle( NS_CATEGORY, $result->cat_title );
- $titleText = Linker::link( $title, htmlspecialchars( $title->getText() ) );
- $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
+ $title = new TitleValue( NS_CATEGORY, $result->cat_title );
+ $text = $title->getText();
+ $link = $this->linkRenderer->renderHtmlLink( $title, $text );
- return Xml::tags( 'li', null, $this->getLanguage()->specialList( $titleText, $count ) ) . "\n";
+ $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
+ return Html::rawElement( 'li', null, $this->getLanguage()->specialList( $link, $count ) ) . "\n";
}
public function getStartForm( $from ) {
- global $wgScript;
-
return Xml::tags(
'form',
- array( 'method' => 'get', 'action' => $wgScript ),
+ array( 'method' => 'get', 'action' => wfScript() ),
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::fieldset(
$this->msg( 'categories' )->text(),
diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php
index aab839fd..e0be838b 100644
--- a/includes/specials/SpecialChangeEmail.php
+++ b/includes/specials/SpecialChangeEmail.php
@@ -26,26 +26,18 @@
*
* @ingroup SpecialPage
*/
-class SpecialChangeEmail extends UnlistedSpecialPage {
-
- /**
- * Users password
- * @var string
- */
- protected $mPassword;
-
+class SpecialChangeEmail extends FormSpecialPage {
/**
- * Users new email address
- * @var string
+ * @var Status
*/
- protected $mNewEmail;
+ private $status;
public function __construct() {
parent::__construct( 'ChangeEmail', 'editmyprivateinfo' );
}
/**
- * @return Bool
+ * @return bool
*/
function isListed() {
global $wgAuth;
@@ -55,40 +47,24 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
/**
* Main execution point
+ * @param string $par
*/
function execute( $par ) {
- global $wgAuth;
-
- $this->setHeaders();
- $this->outputHeader();
-
$out = $this->getOutput();
$out->disallowUserJs();
$out->addModules( 'mediawiki.special.changeemail' );
- if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) {
- $this->error( 'cannotchangeemail' );
-
- return;
- }
-
- $user = $this->getUser();
- $request = $this->getRequest();
-
- if ( !$request->wasPosted() && !$user->isLoggedIn() ) {
- $this->error( 'changeemail-no-info' );
-
- return;
- }
+ return parent::execute( $par );
+ }
- if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
- $this->doReturnTo();
+ protected function checkExecutePermissions( User $user ) {
+ global $wgAuth;
- return;
+ if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) {
+ throw new ErrorPageError( 'changeemail', 'cannotchangeemail' );
}
- $this->checkReadOnly();
- $this->checkPermissions();
+ $this->requireLogin( 'changeemail-no-info' );
// This could also let someone check the current email address, so
// require both permissions.
@@ -96,156 +72,106 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
throw new PermissionsError( 'viewmyprivateinfo' );
}
- $this->mPassword = $request->getVal( 'wpPassword' );
- $this->mNewEmail = $request->getVal( 'wpNewEmail' );
+ parent::checkExecutePermissions( $user );
+ }
- if ( $request->wasPosted()
- && $user->matchEditToken( $request->getVal( 'token' ) )
- ) {
- $info = $this->attemptChange( $user, $this->mPassword, $this->mNewEmail );
- if ( $info === true ) {
- $this->doReturnTo();
- } elseif ( $info === 'eauth' ) {
- # Notify user that a confirmation email has been sent...
- $out->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
- 'eauthentsent', $user->getName() );
- $this->doReturnTo( 'soft' ); // just show the link to go back
- return; // skip form
- }
- }
+ protected function getFormFields() {
+ $user = $this->getUser();
- $this->showForm();
- }
+ $fields = array(
+ 'Name' => array(
+ 'type' => 'info',
+ 'label-message' => 'username',
+ 'default' => $user->getName(),
+ ),
+ 'OldEmail' => array(
+ 'type' => 'info',
+ 'label-message' => 'changeemail-oldemail',
+ 'default' => $user->getEmail() ?: $this->msg( 'changeemail-none' )->text(),
+ ),
+ 'NewEmail' => array(
+ 'type' => 'email',
+ 'label-message' => 'changeemail-newemail',
+ ),
+ );
- /**
- * @param $type string
- */
- protected function doReturnTo( $type = 'hard' ) {
- $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
- if ( !$titleObj instanceof Title ) {
- $titleObj = Title::newMainPage();
+ if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' ) ) {
+ $fields['Password'] = array(
+ 'type' => 'password',
+ 'label-message' => 'changeemail-password',
+ 'autofocus' => true,
+ );
}
- if ( $type == 'hard' ) {
- $this->getOutput()->redirect( $titleObj->getFullURL() );
- } else {
- $this->getOutput()->addReturnTo( $titleObj );
- }
- }
- /**
- * @param $msg string
- */
- protected function error( $msg ) {
- $this->getOutput()->wrapWikiMsg( "<p class='error'>\n$1\n</p>", $msg );
+ return $fields;
}
- protected function showForm() {
- global $wgRequirePasswordforEmailChange;
- $user = $this->getUser();
+ protected function alterForm( HTMLForm $form ) {
+ $form->setDisplayFormat( 'vform' );
+ $form->setId( 'mw-changeemail-form' );
+ $form->setTableId( 'mw-changeemail-table' );
+ $form->setWrapperLegend( false );
+ $form->setSubmitTextMsg( 'changeemail-submit' );
+ $form->addHiddenField( 'returnto', $this->getRequest()->getVal( 'returnto' ) );
+ }
- $oldEmailText = $user->getEmail()
- ? $user->getEmail()
- : $this->msg( 'changeemail-none' )->text();
-
- $this->getOutput()->addHTML(
- Xml::fieldset( $this->msg( 'changeemail-header' )->text() ) .
- Xml::openElement( 'form',
- array(
- 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL(),
- 'id' => 'mw-changeemail-form' ) ) . "\n" .
- Html::hidden( 'token', $user->getEditToken() ) . "\n" .
- Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" .
- $this->msg( 'changeemail-text' )->parseAsBlock() . "\n" .
- Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n"
- );
- $items = array(
- array( 'wpName', 'username', 'text', $user->getName() ),
- array( 'wpOldEmail', 'changeemail-oldemail', 'text', $oldEmailText ),
- array( 'wpNewEmail', 'changeemail-newemail', 'email', $this->mNewEmail ),
- );
- if ( $wgRequirePasswordforEmailChange ) {
- $items[] = array( 'wpPassword', 'changeemail-password', 'password', $this->mPassword );
+ public function onSubmit( array $data ) {
+ if ( $this->getRequest()->getBool( 'wpCancel' ) ) {
+ $status = Status::newGood( true );
+ } else {
+ $password = isset( $data['Password'] ) ? $data['Password'] : null;
+ $status = $this->attemptChange( $this->getUser(), $password, $data['NewEmail'] );
}
- $this->getOutput()->addHTML(
- $this->pretty( $items ) .
- "\n" .
- "<tr>\n" .
- "<td></td>\n" .
- '<td class="mw-input">' .
- Xml::submitButton( $this->msg( 'changeemail-submit' )->text() ) .
- Xml::submitButton( $this->msg( 'changeemail-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
- "</td>\n" .
- "</tr>\n" .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' ) . "\n"
- );
+ $this->status = $status;
+
+ return $status;
}
- /**
- * @param $fields array
- * @return string
- */
- protected function pretty( $fields ) {
- $out = '';
- foreach ( $fields as $list ) {
- list( $name, $label, $type, $value ) = $list;
- if ( $type == 'text' ) {
- $field = htmlspecialchars( $value );
- } else {
- $attribs = array( 'id' => $name );
- if ( $name == 'wpPassword' ) {
- $attribs[] = 'autofocus';
- }
- $field = Html::input( $name, $value, $type, $attribs );
- }
- $out .= "<tr>\n";
- $out .= "\t<td class='mw-label'>";
- if ( $type != 'text' ) {
- $out .= Xml::label( $this->msg( $label )->text(), $name );
- } else {
- $out .= $this->msg( $label )->escaped();
- }
- $out .= "</td>\n";
- $out .= "\t<td class='mw-input'>";
- $out .= $field;
- $out .= "</td>\n";
- $out .= "</tr>";
+ public function onSuccess() {
+ $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
+ if ( !$titleObj instanceof Title ) {
+ $titleObj = Title::newMainPage();
}
- return $out;
+ if ( $this->status->value === true ) {
+ $this->getOutput()->redirect( $titleObj->getFullURL() );
+ } elseif ( $this->status->value === 'eauth' ) {
+ # Notify user that a confirmation email has been sent...
+ $this->getOutput()->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
+ 'eauthentsent', $this->getUser()->getName() );
+ $this->getOutput()->addReturnTo( $titleObj ); // just show the link to go back
+ }
}
/**
- * @param $user User
- * @param $pass string
- * @param $newaddr string
- * @return bool|string true or string on success, false on failure
+ * @param User $user
+ * @param string $pass
+ * @param string $newaddr
+ * @return Status
*/
protected function attemptChange( User $user, $pass, $newaddr ) {
- global $wgAuth, $wgPasswordAttemptThrottle;
+ global $wgAuth;
if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
- $this->error( 'invalidemailaddress' );
-
- return false;
+ return Status::newFatal( 'invalidemailaddress' );
}
$throttleCount = LoginForm::incLoginThrottle( $user->getName() );
if ( $throttleCount === true ) {
$lang = $this->getLanguage();
- $this->error( array( 'login-throttled', $lang->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) ) );
-
- return false;
+ $throttleInfo = $this->getConfig()->get( 'PasswordAttemptThrottle' );
+ return Status::newFatal(
+ 'changeemail-throttled',
+ $lang->formatDuration( $throttleInfo['seconds'] )
+ );
}
- global $wgRequirePasswordforEmailChange;
- if ( $wgRequirePasswordforEmailChange && !$user->checkTemporaryPassword( $pass ) && !$user->checkPassword( $pass ) ) {
- $this->error( 'wrongpassword' );
-
- return false;
+ if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' )
+ && !$user->checkTemporaryPassword( $pass )
+ && !$user->checkPassword( $pass )
+ ) {
+ return Status::newFatal( 'wrongpassword' );
}
if ( $throttleCount ) {
@@ -255,12 +181,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$oldaddr = $user->getEmail();
$status = $user->setEmailWithConfirmation( $newaddr );
if ( !$status->isGood() ) {
- $this->getOutput()->addHTML(
- '<p class="error">' .
- $this->getOutput()->parseInline( $status->getWikiText( 'mailerror' ) ) .
- '</p>' );
-
- return false;
+ return $status;
}
wfRunHooks( 'PrefsEmailAudit', array( $user, $oldaddr, $newaddr ) );
@@ -269,7 +190,11 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$wgAuth->updateExternalDB( $user );
- return $status->value;
+ return $status;
+ }
+
+ public function requiresUnblock() {
+ return false;
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php
index a75e7e83..24664edb 100644
--- a/includes/specials/SpecialChangePassword.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -26,228 +26,216 @@
*
* @ingroup SpecialPage
*/
-class SpecialChangePassword extends UnlistedSpecialPage {
+class SpecialChangePassword extends FormSpecialPage {
+ protected $mUserName;
+ protected $mDomain;
- protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain;
+ // Optional Wikitext Message to show above the password change form
+ protected $mPreTextMessage = null;
+
+ // label for old password input
+ protected $mOldPassMsg = null;
public function __construct() {
parent::__construct( 'ChangePassword', 'editmyprivateinfo' );
+ $this->listed( false );
}
/**
* Main execution point
+ * @param string|null $par
*/
function execute( $par ) {
- global $wgAuth;
-
- $this->setHeaders();
- $this->outputHeader();
$this->getOutput()->disallowUserJs();
- $request = $this->getRequest();
- $this->mUserName = trim( $request->getVal( 'wpName' ) );
- $this->mOldpass = $request->getVal( 'wpPassword' );
- $this->mNewpass = $request->getVal( 'wpNewPassword' );
- $this->mRetype = $request->getVal( 'wpRetype' );
- $this->mDomain = $request->getVal( 'wpDomain' );
-
- $user = $this->getUser();
-
- if ( !$user->isLoggedIn() && !LoginForm::getLoginToken() ) {
- LoginForm::setLoginToken();
- }
-
- if ( !$request->wasPosted() && !$user->isLoggedIn() ) {
- $this->error( $this->msg( 'resetpass-no-info' )->text() );
-
- return;
- }
-
- if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
- $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
- if ( !$titleObj instanceof Title ) {
- $titleObj = Title::newMainPage();
- }
- $query = $request->getVal( 'returntoquery' );
- $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
+ parent::execute( $par );
+ }
- return;
- }
+ protected function checkExecutePermissions( User $user ) {
+ parent::checkExecutePermissions( $user );
- $this->checkReadOnly();
- $this->checkPermissions();
-
- if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) {
- try {
- $this->mDomain = $wgAuth->getDomain();
- if ( !$wgAuth->allowPasswordChange() ) {
- $this->error( $this->msg( 'resetpass_forbidden' )->text() );
-
- return;
- }
-
- if ( !$user->isLoggedIn()
- && $request->getVal( 'wpLoginOnChangeToken' ) !== LoginForm::getLoginToken()
- ) {
- // Potential CSRF (bug 62497)
- $this->error( $this->msg( 'sessionfailure' )->text() );
- return false;
- }
-
- $this->attemptReset( $this->mNewpass, $this->mRetype );
-
- if ( $user->isLoggedIn() ) {
- $this->getOutput()->wrapWikiMsg(
- "<div class=\"successbox\">\n$1\n</div>",
- 'changepassword-success'
- );
- $this->getOutput()->returnToMain();
- } else {
- LoginForm::setLoginToken();
- $token = LoginForm::getLoginToken();
- $data = array(
- 'action' => 'submitlogin',
- 'wpName' => $this->mUserName,
- 'wpDomain' => $this->mDomain,
- 'wpLoginToken' => $token,
- 'wpPassword' => $request->getVal( 'wpNewPassword' ),
- ) + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' );
- $login = new LoginForm( new DerivativeRequest( $request, $data, true ) );
- $login->setContext( $this->getContext() );
- $login->execute( null );
- }
-
- return;
- } catch ( PasswordError $e ) {
- $this->error( $e->getMessage() );
- }
+ if ( !$this->getRequest()->wasPosted() ) {
+ $this->requireLogin( 'resetpass-no-info' );
}
- $this->showForm();
}
/**
- * @param $msg string
+ * Set a message at the top of the Change Password form
+ * @since 1.23
+ * @param Message $msg Message to parse and add to the form header
*/
- function error( $msg ) {
- $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $msg ) );
+ public function setChangeMessage( Message $msg ) {
+ $this->mPreTextMessage = $msg;
}
- function showForm() {
- global $wgCookieExpiration;
+ /**
+ * Set a message at the top of the Change Password form
+ * @since 1.23
+ * @param string $msg Message label for old/temp password field
+ */
+ public function setOldPasswordMessage( $msg ) {
+ $this->mOldPassMsg = $msg;
+ }
+ protected function getFormFields() {
$user = $this->getUser();
- if ( !$this->mUserName ) {
- $this->mUserName = $user->getName();
+ $request = $this->getRequest();
+
+ $oldpassMsg = $this->mOldPassMsg;
+ if ( $oldpassMsg === null ) {
+ $oldpassMsg = $user->isLoggedIn() ? 'oldpassword' : 'resetpass-temp-password';
}
- $rememberMe = '';
- if ( !$user->isLoggedIn() ) {
- $rememberMe = '<tr>' .
- '<td></td>' .
- '<td class="mw-input">' .
- Xml::checkLabel(
- $this->msg( 'remembermypassword' )->numParams( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(),
- 'wpRemember', 'wpRemember',
- $this->getRequest()->getCheck( 'wpRemember' ) ) .
- '</td>' .
- '</tr>';
- $submitMsg = 'resetpass_submit';
- $oldpassMsg = 'resetpass-temp-password';
- } else {
- $oldpassMsg = 'oldpassword';
- $submitMsg = 'resetpass-submit-loggedin';
+
+ $fields = array(
+ 'Name' => array(
+ 'type' => 'info',
+ 'label-message' => 'username',
+ 'default' => $request->getVal( 'wpName', $user->getName() ),
+ ),
+ 'Password' => array(
+ 'type' => 'password',
+ 'label-message' => $oldpassMsg,
+ ),
+ 'NewPassword' => array(
+ 'type' => 'password',
+ 'label-message' => 'newpassword',
+ ),
+ 'Retype' => array(
+ 'type' => 'password',
+ 'label-message' => 'retypenew',
+ ),
+ );
+
+ if ( !$this->getUser()->isLoggedIn() ) {
+ if ( !LoginForm::getLoginToken() ) {
+ LoginForm::setLoginToken();
+ }
+ $fields['LoginOnChangeToken'] = array(
+ 'type' => 'hidden',
+ 'label' => 'Change Password Token',
+ 'default' => LoginForm::getLoginToken(),
+ );
}
+
$extraFields = array();
wfRunHooks( 'ChangePasswordForm', array( &$extraFields ) );
- $prettyFields = array(
- array( 'wpName', 'username', 'text', $this->mUserName ),
- array( 'wpPassword', $oldpassMsg, 'password', $this->mOldpass ),
- array( 'wpNewPassword', 'newpassword', 'password', null ),
- array( 'wpRetype', 'retypenew', 'password', null ),
- );
- $prettyFields = array_merge( $prettyFields, $extraFields );
- $hiddenFields = array(
- 'token' => $user->getEditToken(),
- 'wpName' => $this->mUserName,
- 'wpDomain' => $this->mDomain,
- ) + $this->getRequest()->getValues( 'returnto', 'returntoquery' );
- if ( !$user->isLoggedIn() ) {
- $hiddenFields['wpLoginOnChangeToken'] = LoginForm::getLoginToken();
+ foreach ( $extraFields as $extra ) {
+ list( $name, $label, $type, $default ) = $extra;
+ $fields[$name] = array(
+ 'type' => $type,
+ 'name' => $name,
+ 'label-message' => $label,
+ 'default' => $default,
+ );
}
- $hiddenFieldsStr = '';
- foreach ( $hiddenFields as $fieldname => $fieldvalue ) {
- $hiddenFieldsStr .= Html::hidden( $fieldname, $fieldvalue ) . "\n";
+
+ if ( !$user->isLoggedIn() ) {
+ $fields['Remember'] = array(
+ 'type' => 'check',
+ 'label' => $this->msg( 'remembermypassword' )
+ ->numParams(
+ ceil( $this->getConfig()->get( 'CookieExpiration' ) / ( 3600 * 24 ) )
+ )->text(),
+ 'default' => $request->getVal( 'wpRemember' ),
+ );
}
- $this->getOutput()->addHTML(
- Xml::fieldset( $this->msg( 'resetpass_header' )->text() ) .
- Xml::openElement( 'form',
- array(
- 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL(),
- 'id' => 'mw-resetpass-form' ) ) . "\n" .
- $hiddenFieldsStr .
- $this->msg( 'resetpass_text' )->parseAsBlock() . "\n" .
- Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
- $this->pretty( $prettyFields ) . "\n" .
- $rememberMe .
- "<tr>\n" .
- "<td></td>\n" .
- '<td class="mw-input">' .
- Xml::submitButton( $this->msg( $submitMsg )->text() ) .
- Xml::submitButton( $this->msg( 'resetpass-submit-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
- "</td>\n" .
- "</tr>\n" .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' ) . "\n"
+
+ return $fields;
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setId( 'mw-resetpass-form' );
+ $form->setTableId( 'mw-resetpass-table' );
+ $form->setWrapperLegendMsg( 'resetpass_header' );
+ $form->setSubmitTextMsg(
+ $this->getUser()->isLoggedIn()
+ ? 'resetpass-submit-loggedin'
+ : 'resetpass_submit'
);
+ $form->addButton( 'wpCancel', $this->msg( 'resetpass-submit-cancel' )->text() );
+ $form->setHeaderText( $this->msg( 'resetpass_text' )->parseAsBlock() );
+ if ( $this->mPreTextMessage instanceof Message ) {
+ $form->addPreText( $this->mPreTextMessage->parseAsBlock() );
+ }
+ $form->addHiddenFields(
+ $this->getRequest()->getValues( 'wpName', 'wpDomain', 'returnto', 'returntoquery' ) );
}
- /**
- * @param $fields array
- * @return string
- */
- function pretty( $fields ) {
- $out = '';
- foreach ( $fields as $list ) {
- list( $name, $label, $type, $value ) = $list;
- if ( $type == 'text' ) {
- $field = htmlspecialchars( $value );
- } else {
- $attribs = array( 'id' => $name );
- if ( $name == 'wpNewPassword' || $name == 'wpRetype' ) {
- $attribs = array_merge( $attribs,
- User::passwordChangeInputAttribs() );
- }
- if ( $name == 'wpPassword' ) {
- $attribs[] = 'autofocus';
- }
- $field = Html::input( $name, $value, $type, $attribs );
+ public function onSubmit( array $data ) {
+ global $wgAuth;
+
+ $request = $this->getRequest();
+
+ if ( $request->getCheck( 'wpLoginToken' ) ) {
+ // This comes from Special:Userlogin when logging in with a temporary password
+ return false;
+ }
+
+ if ( !$this->getUser()->isLoggedIn()
+ && $request->getVal( 'wpLoginOnChangeToken' ) !== LoginForm::getLoginToken()
+ ) {
+ // Potential CSRF (bug 62497)
+ return false;
+ }
+
+ if ( $request->getCheck( 'wpCancel' ) ) {
+ $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
+ if ( !$titleObj instanceof Title ) {
+ $titleObj = Title::newMainPage();
}
- $out .= "<tr>\n";
- $out .= "\t<td class='mw-label'>";
+ $query = $request->getVal( 'returntoquery' );
+ $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
+
+ return true;
+ }
+
+ try {
+ $this->mUserName = $request->getVal( 'wpName', $this->getUser()->getName() );
+ $this->mDomain = $wgAuth->getDomain();
- if ( $type != 'text' ) {
- $out .= Xml::label( $this->msg( $label )->text(), $name );
- } else {
- $out .= $this->msg( $label )->escaped();
+ if ( !$wgAuth->allowPasswordChange() ) {
+ throw new ErrorPageError( 'changepassword', 'resetpass_forbidden' );
}
- $out .= "</td>\n";
- $out .= "\t<td class='mw-input'>";
- $out .= $field;
- $out .= "</td>\n";
- $out .= "</tr>";
+ $this->attemptReset( $data['Password'], $data['NewPassword'], $data['Retype'] );
+
+ return true;
+ } catch ( PasswordError $e ) {
+ return $e->getMessage();
}
+ }
- return $out;
+ public function onSuccess() {
+ if ( $this->getUser()->isLoggedIn() ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class=\"successbox\">\n$1\n</div>",
+ 'changepassword-success'
+ );
+ $this->getOutput()->returnToMain();
+ } else {
+ $request = $this->getRequest();
+ LoginForm::setLoginToken();
+ $token = LoginForm::getLoginToken();
+ $data = array(
+ 'action' => 'submitlogin',
+ 'wpName' => $this->mUserName,
+ 'wpDomain' => $this->mDomain,
+ 'wpLoginToken' => $token,
+ 'wpPassword' => $request->getVal( 'wpNewPassword' ),
+ ) + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' );
+ $login = new LoginForm( new DerivativeRequest( $request, $data, true ) );
+ $login->setContext( $this->getContext() );
+ $login->execute( null );
+ }
}
/**
- * @throws PasswordError when cannot set the new password because requirements not met.
+ * @param string $oldpass
+ * @param string $newpass
+ * @param string $retype
+ * @throws PasswordError When cannot set the new password because requirements not met.
*/
- protected function attemptReset( $newpass, $retype ) {
- global $wgPasswordAttemptThrottle;
-
+ protected function attemptReset( $oldpass, $newpass, $retype ) {
$isSelf = ( $this->mUserName === $this->getUser()->getName() );
if ( $isSelf ) {
$user = $this->getUser();
@@ -267,32 +255,40 @@ class SpecialChangePassword extends UnlistedSpecialPage {
$throttleCount = LoginForm::incLoginThrottle( $this->mUserName );
if ( $throttleCount === true ) {
$lang = $this->getLanguage();
- throw new PasswordError( $this->msg( 'login-throttled' )
- ->params( $lang->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
+ $throttleInfo = $this->getConfig()->get( 'PasswordAttemptThrottle' );
+ throw new PasswordError( $this->msg( 'changepassword-throttled' )
+ ->params( $lang->formatDuration( $throttleInfo['seconds'] ) )
->text()
);
}
+ // @todo Make these separate messages, since the message is written for both cases
+ if ( !$user->checkTemporaryPassword( $oldpass ) && !$user->checkPassword( $oldpass ) ) {
+ wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) );
+ throw new PasswordError( $this->msg( 'resetpass-wrong-oldpass' )->text() );
+ }
+
+ // User is resetting their password to their old password
+ if ( $oldpass === $newpass ) {
+ throw new PasswordError( $this->msg( 'resetpass-recycled' )->text() );
+ }
+
+ // Do AbortChangePassword after checking mOldpass, so we don't leak information
+ // by possibly aborting a new password before verifying the old password.
$abortMsg = 'resetpass-abort-generic';
- if ( !wfRunHooks( 'AbortChangePassword', array( $user, $this->mOldpass, $newpass, &$abortMsg ) ) ) {
+ if ( !wfRunHooks( 'AbortChangePassword', array( $user, $oldpass, $newpass, &$abortMsg ) ) ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'abortreset' ) );
throw new PasswordError( $this->msg( $abortMsg )->text() );
}
- if ( !$user->checkTemporaryPassword( $this->mOldpass ) && !$user->checkPassword( $this->mOldpass ) ) {
- wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) );
- throw new PasswordError( $this->msg( 'resetpass-wrong-oldpass' )->text() );
- }
-
// Please reset throttle for successful logins, thanks!
if ( $throttleCount ) {
LoginForm::clearLoginThrottle( $this->mUserName );
}
try {
- $user->setPassword( $this->mNewpass );
+ $user->setPassword( $newpass );
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) );
- $this->mNewpass = $this->mOldpass = $this->mRetype = '';
} catch ( PasswordError $e ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) );
throw new PasswordError( $e->getMessage() );
@@ -301,12 +297,17 @@ class SpecialChangePassword extends UnlistedSpecialPage {
if ( $isSelf ) {
// This is needed to keep the user connected since
// changing the password also modifies the user's token.
- $user->setCookies();
+ $remember = $this->getRequest()->getCookie( 'Token' ) !== null;
+ $user->setCookies( null, null, $remember );
}
-
+ $user->resetPasswordExpiration();
$user->saveSettings();
}
+ public function requiresUnblock() {
+ return false;
+ }
+
protected function getGroupName() {
return 'users';
}
diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php
index fc6b0c58..da1a54cd 100644
--- a/includes/specials/SpecialComparePages.php
+++ b/includes/specials/SpecialComparePages.php
@@ -43,8 +43,8 @@ class SpecialComparePages extends SpecialPage {
/**
* Show a form for filtering namespace and username
*
- * @param $par String
- * @return String
+ * @param string $par
+ * @return string
*/
public function execute( $par ) {
$this->setHeaders();
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index 3828b1c6..d771589d 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -45,6 +45,8 @@ class EmailConfirmation extends UnlistedSpecialPage {
$this->checkReadOnly();
$this->checkPermissions();
+ $this->requireLogin( 'confirmemail_needlogin' );
+
// This could also let someone check the current email address, so
// require both permissions.
if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
@@ -52,22 +54,10 @@ class EmailConfirmation extends UnlistedSpecialPage {
}
if ( $code === null || $code === '' ) {
- if ( $this->getUser()->isLoggedIn() ) {
- if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
- $this->showRequestForm();
- } else {
- $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
- }
+ if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
+ $this->showRequestForm();
} else {
- $llink = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Userlogin' ),
- $this->msg( 'loginreqlink' )->escaped(),
- array(),
- array( 'returnto' => $this->getTitle()->getPrefixedText() )
- );
- $this->getOutput()->addHTML(
- $this->msg( 'confirmemail_needlogin' )->rawParams( $llink )->parse()
- );
+ $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
}
} else {
$this->attemptConfirm( $code );
@@ -90,19 +80,17 @@ class EmailConfirmation extends UnlistedSpecialPage {
} else {
$out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
}
+ } elseif ( $user->isEmailConfirmed() ) {
+ // date and time are separate parameters to facilitate localisation.
+ // $time is kept for backward compat reasons.
+ // 'emailauthenticated' is also used in SpecialPreferences.php
+ $lang = $this->getLanguage();
+ $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
+ $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
+ $d = $lang->userDate( $emailAuthenticated, $user );
+ $t = $lang->userTime( $emailAuthenticated, $user );
+ $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
} else {
- if ( $user->isEmailConfirmed() ) {
- // date and time are separate parameters to facilitate localisation.
- // $time is kept for backward compat reasons.
- // 'emailauthenticated' is also used in SpecialPreferences.php
- $lang = $this->getLanguage();
- $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
- $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
- $d = $lang->userDate( $emailAuthenticated, $user );
- $t = $lang->userTime( $emailAuthenticated, $user );
- $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
- }
-
if ( $user->isEmailConfirmationPending() ) {
$out->wrapWikiMsg(
"<div class=\"error mw-confirmemail-pending\">\n$1\n</div>",
@@ -113,7 +101,7 @@ class EmailConfirmation extends UnlistedSpecialPage {
$out->addWikiMsg( 'confirmemail_text' );
$form = Html::openElement(
'form',
- array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL() )
+ array( 'method' => 'post', 'action' => $this->getPageTitle()->getLocalURL() )
) . "\n";
$form .= Html::hidden( 'token', $user->getEditToken() ) . "\n";
$form .= Xml::submitButton( $this->msg( 'confirmemail_send' )->text() ) . "\n";
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index 1fe98190..32a887c4 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -26,8 +26,7 @@
*
* @ingroup SpecialPage
*/
-
-class SpecialContributions extends SpecialPage {
+class SpecialContributions extends IncludableSpecialPage {
protected $opts;
public function __construct() {
@@ -63,7 +62,9 @@ class SpecialContributions extends SpecialPage {
$this->opts['deletedOnly'] = $request->getBool( 'deletedOnly' );
if ( !strlen( $target ) ) {
- $out->addHTML( $this->getForm() );
+ if ( !$this->including() ) {
+ $out->addHTML( $this->getForm() );
+ }
return;
}
@@ -73,6 +74,7 @@ class SpecialContributions extends SpecialPage {
$this->opts['limit'] = $request->getInt( 'limit', $user->getOption( 'rclimit' ) );
$this->opts['target'] = $target;
$this->opts['topOnly'] = $request->getBool( 'topOnly' );
+ $this->opts['newOnly'] = $request->getBool( 'newOnly' );
$nt = Title::makeTitleSafe( NS_USER, $target );
if ( !$nt ) {
@@ -94,14 +96,14 @@ class SpecialContributions extends SpecialPage {
$out->setHTMLTitle( $this->msg(
'pagetitle',
$this->msg( 'contributions-title', $target )->plain()
- ) );
+ )->inContentLanguage() );
$this->getSkin()->setRelevantUser( $userObj );
} else {
$out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) );
$out->setHTMLTitle( $this->msg(
'pagetitle',
$this->msg( 'sp-contributions-newbies-title' )->plain()
- ) );
+ )->inContentLanguage() );
}
if ( ( $ns = $request->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
@@ -131,34 +133,40 @@ class SpecialContributions extends SpecialPage {
}
$feedType = $request->getVal( 'feed' );
+
+ $feedParams = array(
+ 'action' => 'feedcontributions',
+ 'user' => $target,
+ );
+ if ( $this->opts['topOnly'] ) {
+ $feedParams['toponly'] = true;
+ }
+ if ( $this->opts['newOnly'] ) {
+ $feedParams['newonly'] = true;
+ }
+ if ( $this->opts['deletedOnly'] ) {
+ $feedParams['deletedonly'] = true;
+ }
+ if ( $this->opts['tagfilter'] !== '' ) {
+ $feedParams['tagfilter'] = $this->opts['tagfilter'];
+ }
+ if ( $this->opts['namespace'] !== '' ) {
+ $feedParams['namespace'] = $this->opts['namespace'];
+ }
+ // Don't use year and month for the feed URL, but pass them on if
+ // we redirect to API (if $feedType is specified)
+ if ( $feedType && $this->opts['year'] !== null ) {
+ $feedParams['year'] = $this->opts['year'];
+ }
+ if ( $feedType && $this->opts['month'] !== null ) {
+ $feedParams['month'] = $this->opts['month'];
+ }
+
if ( $feedType ) {
- // Maintain some level of backwards compatability
+ // Maintain some level of backwards compatibility
// If people request feeds using the old parameters, redirect to API
- $apiParams = array(
- 'action' => 'feedcontributions',
- 'feedformat' => $feedType,
- 'user' => $target,
- );
- if ( $this->opts['topOnly'] ) {
- $apiParams['toponly'] = true;
- }
- if ( $this->opts['deletedOnly'] ) {
- $apiParams['deletedonly'] = true;
- }
- if ( $this->opts['tagfilter'] !== '' ) {
- $apiParams['tagfilter'] = $this->opts['tagfilter'];
- }
- if ( $this->opts['namespace'] !== '' ) {
- $apiParams['namespace'] = $this->opts['namespace'];
- }
- if ( $this->opts['year'] !== null ) {
- $apiParams['year'] = $this->opts['year'];
- }
- if ( $this->opts['month'] !== null ) {
- $apiParams['month'] = $this->opts['month'];
- }
-
- $url = wfAppendQuery( wfScript( 'api' ), $apiParams );
+ $feedParams['feedformat'] = $feedType;
+ $url = wfAppendQuery( wfScript( 'api' ), $feedParams );
$out->redirect( $url, '301' );
@@ -166,11 +174,12 @@ class SpecialContributions extends SpecialPage {
}
// Add RSS/atom links
- $this->addFeedLinks( array( 'action' => 'feedcontributions', 'user' => $target ) );
-
- if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) {
- $out->addHTML( $this->getForm() );
+ $this->addFeedLinks( $feedParams );
+ if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id, $userObj, $this ) ) ) {
+ if ( !$this->including() ) {
+ $out->addHTML( $this->getForm() );
+ }
$pager = new ContribsPager( $this->getContext(), array(
'target' => $target,
'contribs' => $this->opts['contribs'],
@@ -180,6 +189,7 @@ class SpecialContributions extends SpecialPage {
'month' => $this->opts['month'],
'deletedOnly' => $this->opts['deletedOnly'],
'topOnly' => $this->opts['topOnly'],
+ 'newOnly' => $this->opts['newOnly'],
'nsInvert' => $this->opts['nsInvert'],
'associated' => $this->opts['associated'],
) );
@@ -193,11 +203,13 @@ class SpecialContributions extends SpecialPage {
$out->showLagWarning( $lag );
}
- $out->addHTML(
- '<p>' . $pager->getNavigationBar() . '</p>' .
- $pager->getBody() .
- '<p>' . $pager->getNavigationBar() . '</p>'
- );
+ $output = $pager->getBody();
+ if ( !$this->including() ) {
+ $output = '<p>' . $pager->getNavigationBar() . '</p>' .
+ $output .
+ '<p>' . $pager->getNavigationBar() . '</p>';
+ }
+ $out->addHTML( $output );
}
$out->preventClickjacking( $pager->getPreventClickjacking() );
@@ -214,10 +226,12 @@ class SpecialContributions extends SpecialPage {
}
if ( $message ) {
- if ( !$this->msg( $message, $target )->isDisabled() ) {
- $out->wrapWikiMsg(
- "<div class='mw-contributions-footer'>\n$1\n</div>",
- array( $message, $target ) );
+ if ( !$this->including() ) {
+ if ( !$this->msg( $message, $target )->isDisabled() ) {
+ $out->wrapWikiMsg(
+ "<div class='mw-contributions-footer'>\n$1\n</div>",
+ array( $message, $target ) );
+ }
}
}
}
@@ -225,13 +239,26 @@ class SpecialContributions extends SpecialPage {
/**
* Generates the subheading with links
- * @param $userObj User object for the target
- * @return String: appropriately-escaped HTML to be output literally
+ * @param User $userObj User object for the target
+ * @return string Appropriately-escaped HTML to be output literally
* @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php.
* Could be combined.
*/
protected function contributionsSub( $userObj ) {
if ( $userObj->isAnon() ) {
+ // Show a warning message that the user being searched for doesn't exists
+ if ( !User::isIP( $userObj->getName() ) ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
+ array(
+ 'contributions-userdoesnotexist',
+ wfEscapeWikiText( $userObj->getName() ),
+ )
+ );
+ if ( !$this->including() ) {
+ $this->getOutput()->setStatusCode( 404 );
+ }
+ }
$user = htmlspecialchars( $userObj->getName() );
} else {
$user = Linker::link( $userObj->getUserPage(), htmlspecialchars( $userObj->getName() ) );
@@ -246,25 +273,32 @@ class SpecialContributions extends SpecialPage {
// Show a note if the user is blocked and display the last block log entry.
// Do not expose the autoblocks, since that may lead to a leak of accounts' IPs,
// and also this will display a totally irrelevant log entry as a current block.
- if ( $userObj->isBlocked() && $userObj->getBlock()->getType() != Block::TYPE_AUTO ) {
- $out = $this->getOutput(); // showLogExtract() wants first parameter by reference
- LogEventsList::showLogExtract(
- $out,
- 'block',
- $nt,
- '',
- array(
- 'lim' => 1,
- 'showIfEmpty' => false,
- 'msgKey' => array(
- $userObj->isAnon() ?
- 'sp-contributions-blocked-notice-anon' :
- 'sp-contributions-blocked-notice',
- $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
- ),
- 'offset' => '' # don't use WebRequest parameter offset
- )
- );
+ if ( !$this->including() ) {
+ $block = Block::newFromTarget( $userObj, $userObj );
+ if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
+ if ( $block->getType() == Block::TYPE_RANGE ) {
+ $nt = MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget();
+ }
+
+ $out = $this->getOutput(); // showLogExtract() wants first parameter by reference
+ LogEventsList::showLogExtract(
+ $out,
+ 'block',
+ $nt,
+ '',
+ array(
+ 'lim' => 1,
+ 'showIfEmpty' => false,
+ 'msgKey' => array(
+ $userObj->isAnon() ?
+ 'sp-contributions-blocked-notice-anon' :
+ 'sp-contributions-blocked-notice',
+ $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
+ ),
+ 'offset' => '' # don't use WebRequest parameter offset
+ )
+ );
+ }
}
}
@@ -273,9 +307,9 @@ class SpecialContributions extends SpecialPage {
/**
* Links to different places.
- * @param $userpage Title: Target user page
- * @param $talkpage Title: Talk page
- * @param $target User: Target user object
+ * @param Title $userpage Target user page
+ * @param Title $talkpage Talk page
+ * @param User $target Target user object
* @return array
*/
public function getUserLinks( Title $userpage, Title $talkpage, User $target ) {
@@ -311,6 +345,16 @@ class SpecialContributions extends SpecialPage {
array(),
array( 'page' => $userpage->getPrefixedText() )
);
+
+ # Suppression log link (bug 59120)
+ if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
+ $tools[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Log', 'suppress' ),
+ $this->msg( 'sp-contributions-suppresslog' )->escaped(),
+ array(),
+ array( 'offender' => $username )
+ );
+ }
}
# Uploads
$tools[] = Linker::linkKnown(
@@ -349,12 +393,10 @@ class SpecialContributions extends SpecialPage {
/**
* Generates the namespace selector form with hidden attributes.
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
protected function getForm() {
- global $wgScript;
-
- $this->opts['title'] = $this->getTitle()->getPrefixedText();
+ $this->opts['title'] = $this->getPageTitle()->getPrefixedText();
if ( !isset( $this->opts['target'] ) ) {
$this->opts['target'] = '';
} else {
@@ -397,11 +439,15 @@ class SpecialContributions extends SpecialPage {
$this->opts['topOnly'] = false;
}
+ if ( !isset( $this->opts['newOnly'] ) ) {
+ $this->opts['newOnly'] = false;
+ }
+
$form = Html::openElement(
'form',
array(
'method' => 'get',
- 'action' => $wgScript,
+ 'action' => wfScript(),
'class' => 'mw-contributions-form'
)
);
@@ -416,6 +462,7 @@ class SpecialContributions extends SpecialPage {
'year',
'month',
'topOnly',
+ 'newOnly',
'associated'
);
@@ -548,10 +595,21 @@ class SpecialContributions extends SpecialPage {
array( 'class' => 'mw-input' )
)
);
+ $checkLabelNewOnly = Html::rawElement(
+ 'span',
+ array( 'style' => 'white-space: nowrap' ),
+ Xml::checkLabel(
+ $this->msg( 'sp-contributions-newonly' )->text(),
+ 'newOnly',
+ 'mw-show-new-only',
+ $this->opts['newOnly'],
+ array( 'class' => 'mw-input' )
+ )
+ );
$extraOptions = Html::rawElement(
'td',
array( 'colspan' => 2 ),
- $deletedOnlyCheck . $checkLabelTopOnly
+ $deletedOnlyCheck . $checkLabelTopOnly . $checkLabelNewOnly
);
$dateSelectionAndSubmit = Xml::tags( 'td', array( 'colspan' => 2 ),
@@ -594,13 +652,16 @@ class SpecialContributions extends SpecialPage {
* @ingroup SpecialPage Pager
*/
class ContribsPager extends ReverseChronologicalPager {
- public $mDefaultDirection = true;
+ public $mDefaultDirection = IndexPager::DIR_DESCENDING;
public $messages;
public $target;
public $namespace = '';
public $mDb;
public $preventClickjacking = false;
+ /** @var DatabaseBase */
+ public $mDbSecondary;
+
/**
* @var array
*/
@@ -632,11 +693,16 @@ class ContribsPager extends ReverseChronologicalPager {
$this->deletedOnly = !empty( $options['deletedOnly'] );
$this->topOnly = !empty( $options['topOnly'] );
+ $this->newOnly = !empty( $options['newOnly'] );
$year = isset( $options['year'] ) ? $options['year'] : false;
$month = isset( $options['month'] ) ? $options['month'] : false;
$this->getDateCond( $year, $month );
+ // Most of this code will use the 'contributions' group DB, which can map to slaves
+ // with extra user based indexes or partioning by user. The additional metadata
+ // queries should use a regular slave since the lookup pattern is not all by user.
+ $this->mDbSecondary = wfGetDB( DB_SLAVE ); // any random slave
$this->mDb = wfGetDB( DB_SLAVE, 'contributions' );
}
@@ -651,9 +717,9 @@ class ContribsPager extends ReverseChronologicalPager {
* This method basically executes the exact same code as the parent class, though with
* a hook added, to allow extentions to add additional queries.
*
- * @param string $offset index offset, inclusive
- * @param $limit Integer: exact query limit
- * @param $descending Boolean: query direction, false for ascending, true for descending
+ * @param string $offset Index offset, inclusive
+ * @param int $limit Exact query limit
+ * @param bool $descending Query direction, false for ascending, true for descending
* @return ResultWrapper
*/
function reallyDoQuery( $offset, $limit, $descending ) {
@@ -725,7 +791,7 @@ class ContribsPager extends ReverseChronologicalPager {
// Paranoia: avoid brute force searches (bug 17342)
if ( !$user->isAllowed( 'deletedhistory' ) ) {
$conds[] = $this->mDb->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0';
- } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
$conds[] = $this->mDb->bitAnd( 'rev_deleted', Revision::SUPPRESSED_USER ) .
' != ' . Revision::SUPPRESSED_USER;
}
@@ -735,6 +801,11 @@ class ContribsPager extends ReverseChronologicalPager {
# Get the current user name for accounts
$join_cond['user'] = Revision::userJoinCond();
+ $options = array();
+ if ( $index ) {
+ $options['USE INDEX'] = array( 'revision' => $index );
+ }
+
$queryInfo = array(
'tables' => $tables,
'fields' => array_merge(
@@ -744,7 +815,7 @@ class ContribsPager extends ReverseChronologicalPager {
'page_latest', 'page_is_redirect', 'page_len' )
),
'conds' => $conds,
- 'options' => array( 'USE INDEX' => array( 'revision' => $index ) ),
+ 'options' => $options,
'join_conds' => $join_cond
);
@@ -766,10 +837,10 @@ class ContribsPager extends ReverseChronologicalPager {
$condition = array();
$join_conds = array();
$tables = array( 'revision', 'page', 'user' );
+ $index = false;
if ( $this->contribs == 'newbie' ) {
$max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ );
$condition[] = 'rev_user >' . (int)( $max - $max / 100 );
- $index = 'user_timestamp';
# ignore local groups with the bot right
# @todo FIXME: Global groups may have 'bot' rights
$groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
@@ -802,6 +873,10 @@ class ContribsPager extends ReverseChronologicalPager {
$condition[] = 'rev_id = page_latest';
}
+ if ( $this->newOnly ) {
+ $condition[] = 'rev_parent_id = 0';
+ }
+
return array( $tables, $index, $condition, $join_conds );
}
@@ -851,7 +926,7 @@ class ContribsPager extends ReverseChronologicalPager {
$batch->add( $row->page_namespace, $row->page_title );
}
}
- $this->mParentLens = Revision::getParentLengths( $this->getDatabase(), $revIds );
+ $this->mParentLens = Revision::getParentLengths( $this->mDbSecondary, $revIds );
$batch->execute();
$this->mResult->seek( 0 );
}
@@ -879,7 +954,7 @@ class ContribsPager extends ReverseChronologicalPager {
* was not written by the target user.
*
* @todo This would probably look a lot nicer in a table.
- * @param $row
+ * @param object $row
* @return string
*/
function formatRow( $row ) {
@@ -896,8 +971,12 @@ class ContribsPager extends ReverseChronologicalPager {
* to extensions to subscribe to the hook to parse the row.
*/
wfSuppressWarnings();
- $rev = new Revision( $row );
- $validRevision = (bool)$rev->getId();
+ try {
+ $rev = new Revision( $row );
+ $validRevision = (bool)$rev->getId();
+ } catch ( MWException $e ) {
+ $validRevision = false;
+ }
wfRestoreWarnings();
if ( $validRevision ) {
@@ -986,7 +1065,8 @@ class ContribsPager extends ReverseChronologicalPager {
# Show user names for /newbies as there may be different users.
# Note that we already excluded rows with hidden user names.
if ( $this->contribs == 'newbie' ) {
- $userlink = ' . . ' . $lang->getDirMark() . Linker::userLink( $rev->getUser(), $rev->getUserText() );
+ $userlink = ' . . ' . $lang->getDirMark()
+ . Linker::userLink( $rev->getUser(), $rev->getUserText() );
$userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' ';
} else {
@@ -1036,7 +1116,7 @@ class ContribsPager extends ReverseChronologicalPager {
wfRunHooks( 'ContributionsLineEnding', array( $this, &$ret, $row, &$classes ) );
if ( $classes === array() && $ret === '' ) {
- wfDebug( 'Dropping Special:Contribution row that could not be formatted' );
+ wfDebug( "Dropping Special:Contribution row that could not be formatted\n" );
$ret = "<!-- Could not format Special:Contribution row. -->\n";
} else {
$ret = Html::rawElement( 'li', array( 'class' => $classes ), $ret ) . "\n";
diff --git a/includes/specials/SpecialCreateAccount.php b/includes/specials/SpecialCreateAccount.php
new file mode 100644
index 00000000..30e3833c
--- /dev/null
+++ b/includes/specials/SpecialCreateAccount.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Redirect page: Special:CreateAccount --> Special:UserLogin/signup.
+ *
+ * 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
+ */
+
+/**
+ * Redirect page: Special:CreateAccount --> Special:UserLogin/signup.
+ * @todo FIXME: This (and the rest of the login frontend) needs to die a horrible painful death
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialCreateAccount extends SpecialRedirectToSpecial {
+ function __construct() {
+ parent::__construct(
+ 'CreateAccount',
+ 'Userlogin',
+ 'signup',
+ array( 'returnto', 'returntoquery', 'uselang' )
+ );
+ }
+
+ // No reason to hide this link on Special:Specialpages
+ public function isListed() {
+ return true;
+ }
+
+ public function isRestricted() {
+ return !User::groupHasPermission( '*', 'createaccount' );
+ }
+
+ public function userCanExecute( User $user ) {
+ return $user->isAllowed( 'createaccount' );
+ }
+
+ protected function getGroupName() {
+ return 'login';
+ }
+}
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index 9b9888ad..68f2c469 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -26,7 +26,7 @@
* @ingroup SpecialPage
*/
class DeletedContribsPager extends IndexPager {
- public $mDefaultDirection = true;
+ public $mDefaultDirection = IndexPager::DIR_DESCENDING;
public $messages;
public $target;
public $namespace = '';
@@ -62,7 +62,7 @@ class DeletedContribsPager extends IndexPager {
// Paranoia: avoid brute force searches (bug 17792)
if ( !$user->isAllowed( 'deletedhistory' ) ) {
$conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::DELETED_USER ) . ' = 0';
- } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
$conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::SUPPRESSED_USER ) .
' != ' . Revision::SUPPRESSED_USER;
}
@@ -147,7 +147,7 @@ class DeletedContribsPager extends IndexPager {
* written by the target user.
*
* @todo This would probably look a lot nicer in a table.
- * @param $row
+ * @param stdClass $row
* @return string
*/
function formatRow( $row ) {
@@ -276,7 +276,7 @@ class DeletedContribsPager extends IndexPager {
class DeletedContributionsPage extends SpecialPage {
function __construct() {
parent::__construct( 'DeletedContributions', 'deletedhistory',
- /*listed*/true, /*function*/false, /*file*/false );
+ /*listed*/true, /*function*/false, /*file*/false );
}
/**
@@ -286,8 +286,6 @@ class DeletedContributionsPage extends SpecialPage {
* @param string $par (optional) user name of the user for which to show the contributions
*/
function execute( $par ) {
- global $wgQueryPageDefaultLimit;
-
$this->setHeaders();
$this->outputHeader();
@@ -317,7 +315,7 @@ class DeletedContributionsPage extends SpecialPage {
return;
}
- $options['limit'] = $request->getInt( 'limit', $wgQueryPageDefaultLimit );
+ $options['limit'] = $request->getInt( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
$options['target'] = $target;
$userObj = User::newFromName( $target, false );
@@ -375,8 +373,8 @@ class DeletedContributionsPage extends SpecialPage {
/**
* Generates the subheading with links
- * @param $userObj User object for the target
- * @return String: appropriately-escaped HTML to be output literally
+ * @param User $userObj User object for the target
+ * @return string Appropriately-escaped HTML to be output literally
* @todo FIXME: Almost the same as contributionsSub in SpecialContributions.php. Could be combined.
*/
function getSubTitle( $userObj ) {
@@ -427,6 +425,15 @@ class DeletedContributionsPage extends SpecialPage {
'page' => $nt->getPrefixedText()
)
);
+ # Suppression log link (bug 59120)
+ if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
+ $tools[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Log', 'suppress' ),
+ $this->msg( 'sp-contributions-suppresslog' )->escaped(),
+ array(),
+ array( 'offender' => $userObj->getName() )
+ );
+ }
}
# Uploads
@@ -463,7 +470,12 @@ class DeletedContributionsPage extends SpecialPage {
$links = $this->getLanguage()->pipeList( $tools );
// Show a note if the user is blocked and display the last block log entry.
- if ( $userObj->isBlocked() ) {
+ $block = Block::newFromTarget( $userObj, $userObj );
+ if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
+ if ( $block->getType() == Block::TYPE_RANGE ) {
+ $nt = MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget();
+ }
+
// LogEventsList::showLogExtract() wants the first parameter by ref
$out = $this->getOutput();
LogEventsList::showLogExtract(
@@ -476,7 +488,7 @@ class DeletedContributionsPage extends SpecialPage {
'showIfEmpty' => false,
'msgKey' => array(
'sp-contributions-blocked-notice',
- $nt->getText() # Support GENDER in 'sp-contributions-blocked-notice'
+ $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
),
'offset' => '' # don't use $this->getRequest() parameter offset
)
@@ -489,13 +501,11 @@ class DeletedContributionsPage extends SpecialPage {
/**
* Generates the namespace selector form with hidden attributes.
- * @param array $options the options to be included.
+ * @param array $options The options to be included.
* @return string
*/
function getForm( $options ) {
- global $wgScript;
-
- $options['title'] = $this->getTitle()->getPrefixedText();
+ $options['title'] = $this->getPageTitle()->getPrefixedText();
if ( !isset( $options['target'] ) ) {
$options['target'] = '';
} else {
@@ -514,7 +524,7 @@ class DeletedContributionsPage extends SpecialPage {
$options['target'] = '';
}
- $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
+ $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) );
foreach ( $options as $name => $value ) {
if ( in_array( $name, array( 'namespace', 'target', 'contribs' ) ) ) {
diff --git a/includes/specials/SpecialDiff.php b/includes/specials/SpecialDiff.php
new file mode 100644
index 00000000..77d23173
--- /dev/null
+++ b/includes/specials/SpecialDiff.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Redirect from Special:Diff/### to index.php?diff=### and
+ * from Special:Diff/###/### to index.php?oldid=###&diff=###.
+ *
+ * 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
+ */
+
+/**
+ * Redirect from Special:Diff/### to index.php?diff=### and
+ * from Special:Diff/###/### to index.php?oldid=###&diff=###.
+ *
+ * All of the following are valid usages:
+ * - [[Special:Diff/12345]] (diff of a revision with the previous one)
+ * - [[Special:Diff/12345/prev]] (diff of a revision with the previous one as well)
+ * - [[Special:Diff/12345/next]] (diff of a revision with the next one)
+ * - [[Special:Diff/12345/cur]] (diff of a revision with the latest one of that page)
+ * - [[Special:Diff/12345/98765]] (diff between arbitrary two revisions)
+ *
+ * @ingroup SpecialPage
+ * @since 1.23
+ */
+class SpecialDiff extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'Diff' );
+ $this->mAllowedRedirectParams = array();
+ }
+
+ function getRedirect( $subpage ) {
+ $parts = explode( '/', $subpage );
+
+ // Try to parse the values given, generating somewhat pretty URLs if possible
+ if ( count( $parts ) === 1 && $parts[0] !== '' ) {
+ $this->mAddedRedirectParams['diff'] = $parts[0];
+ } elseif ( count( $parts ) === 2 ) {
+ $this->mAddedRedirectParams['oldid'] = $parts[0];
+ $this->mAddedRedirectParams['diff'] = $parts[1];
+ } else {
+ // Wrong number of parameters, bail out
+ throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
+ }
+
+ return true;
+ }
+}
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
index 501552e9..3656b9cc 100644
--- a/includes/specials/SpecialEditWatchlist.php
+++ b/includes/specials/SpecialEditWatchlist.php
@@ -36,7 +36,8 @@
*/
class SpecialEditWatchlist extends UnlistedSpecialPage {
/**
- * Editing modes
+ * Editing modes. EDIT_CLEAR is no longer used; the "Clear" link scared people
+ * too much. Now it's passed on to the raw editor, from which it's very easy to clear.
*/
const EDIT_CLEAR = 1;
const EDIT_RAW = 2;
@@ -55,34 +56,21 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
/**
* Main execution point
*
- * @param $mode int
+ * @param int $mode
*/
public function execute( $mode ) {
$this->setHeaders();
- $out = $this->getOutput();
-
# Anons don't get a watchlist
- if ( $this->getUser()->isAnon() ) {
- $out->setPageTitle( $this->msg( 'watchnologin' ) );
- $llink = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Userlogin' ),
- $this->msg( 'loginreqlink' )->escaped(),
- array(),
- array( 'returnto' => $this->getTitle()->getPrefixedText() )
- );
- $out->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() );
+ $this->requireLogin( 'watchlistanontext' );
- return;
- }
+ $out = $this->getOutput();
$this->checkPermissions();
$this->checkReadOnly();
$this->outputHeader();
-
- $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() )
- ->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
+ $this->outputSubtitle();
# B/C: $mode used to be waaay down the parameter list, and the first parameter
# was $wgUser
@@ -95,10 +83,6 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$mode = self::getMode( $this->getRequest(), $mode );
switch ( $mode ) {
- case self::EDIT_CLEAR:
- // The "Clear" link scared people too much.
- // Pass on to the raw editor, from which it's very easy to clear.
-
case self::EDIT_RAW:
$out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) );
$form = $this->getRawForm();
@@ -107,26 +91,73 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
}
break;
-
- case self::EDIT_NORMAL:
- default:
- $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
- $form = $this->getNormalForm();
+ case self::EDIT_CLEAR:
+ $out->setPageTitle( $this->msg( 'watchlistedit-clear-title' ) );
+ $form = $this->getClearForm();
if ( $form->show() ) {
$out->addHTML( $this->successMessage );
$out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
- } elseif ( $this->toc !== false ) {
- $out->prependHTML( $this->toc );
}
break;
+
+ case self::EDIT_NORMAL:
+ default:
+ $this->executeViewEditWatchlist();
+ break;
+ }
+ }
+
+ /**
+ * Renders a subheader on the watchlist page.
+ */
+ protected function outputSubtitle() {
+ $out = $this->getOutput();
+ $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() )
+ ->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
+ }
+
+ /**
+ * Executes an edit mode for the watchlist view, from which you can manage your watchlist
+ *
+ */
+ protected function executeViewEditWatchlist() {
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
+ $form = $this->getNormalForm();
+ if ( $form->show() ) {
+ $out->addHTML( $this->successMessage );
+ $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
+ } elseif ( $this->toc !== false ) {
+ $out->prependHTML( $this->toc );
+ $out->addModules( 'mediawiki.toc' );
}
}
/**
+ * 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
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ return self::prefixSearchArray(
+ $search,
+ $limit,
+ // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added
+ // here and there - no 'edit' here, because that the default for this page
+ array(
+ 'clear',
+ 'raw',
+ )
+ );
+ }
+
+ /**
* Extract a list of titles from a blob of text, returning
* (prefixed) strings; unwatchable titles are ignored
*
- * @param $list String
+ * @param string $list
* @return array
*/
private function extractTitles( $list ) {
@@ -176,14 +207,14 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
if ( count( $toWatch ) > 0 ) {
- $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added'
- )->numParams( count( $toWatch ) )->parse();
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' )
+ ->numParams( count( $toWatch ) )->parse();
$this->showTitles( $toWatch, $this->successMessage );
}
if ( count( $toUnwatch ) > 0 ) {
- $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed'
- )->numParams( count( $toUnwatch ) )->parse();
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' )
+ ->numParams( count( $toUnwatch ) )->parse();
$this->showTitles( $toUnwatch, $this->successMessage );
}
} else {
@@ -204,19 +235,35 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
return true;
}
+ public function submitClear( $data ) {
+ $current = $this->getWatchlist();
+ $this->clearWatchlist();
+ $this->getUser()->invalidateCache();
+ $this->successMessage = $this->msg( 'watchlistedit-clear-done' )->parse();
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-clear-removed' )
+ ->numParams( count( $current ) )->parse();
+ $this->showTitles( $current, $this->successMessage );
+
+ return true;
+ }
+
/**
* Print out a list of linked titles
*
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param array $titles of strings, or Title objects
- * @param $output String
+ * @param array $titles Array of strings, or Title objects
+ * @param string $output
*/
private function showTitles( $titles, &$output ) {
$talk = $this->msg( 'talkpagelinktext' )->escaped();
// Do a batch existence check
$batch = new LinkBatch();
+ if ( count( $titles ) >= 100 ) {
+ $output = wfMessage( 'watchlistedit-too-many' )->parse();
+ return;
+ }
foreach ( $titles as $title ) {
if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
@@ -300,7 +347,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*
* @return array
*/
- private function getWatchlistInfo() {
+ protected function getWatchlistInfo() {
$titles = array();
$dbr = wfGetDB( DB_MASTER );
@@ -332,7 +379,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* @param Title $title
* @param int $namespace
* @param string $dbKey
- * @return bool: Whether this item is valid
+ * @return bool Whether this item is valid
*/
private function checkTitle( $title, $namespace, $dbKey ) {
if ( $title
@@ -403,7 +450,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param array $titles of strings, or Title objects
+ * @param array $titles Array of strings, or Title objects
*/
private function watchTitles( $titles ) {
$dbw = wfGetDB( DB_MASTER );
@@ -439,7 +486,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param array $titles of strings, or Title objects
+ * @param array $titles Array of strings, or Title objects
*/
private function unwatchTitles( $titles ) {
$dbw = wfGetDB( DB_MASTER );
@@ -506,24 +553,35 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$fields = array();
$count = 0;
- foreach ( $this->getWatchlistInfo() as $namespace => $pages ) {
- if ( $namespace >= 0 ) {
- $fields['TitlesNs' . $namespace] = array(
- 'class' => 'EditWatchlistCheckboxSeriesField',
- 'options' => array(),
- 'section' => "ns$namespace",
- );
- }
+ // Allow subscribers to manipulate the list of watched pages (or use it
+ // to preload lots of details at once)
+ $watchlistInfo = $this->getWatchlistInfo();
+ wfRunHooks(
+ 'WatchlistEditorBeforeFormRender',
+ array( &$watchlistInfo )
+ );
+
+ foreach ( $watchlistInfo as $namespace => $pages ) {
+ $options = array();
foreach ( array_keys( $pages ) as $dbkey ) {
$title = Title::makeTitleSafe( $namespace, $dbkey );
if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
$text = $this->buildRemoveLine( $title );
- $fields['TitlesNs' . $namespace]['options'][$text] = $title->getPrefixedText();
+ $options[$text] = $title->getPrefixedText();
$count++;
}
}
+
+ // checkTitle can filter some options out, avoid empty sections
+ if ( count( $options ) > 0 ) {
+ $fields['TitlesNs' . $namespace] = array(
+ 'class' => 'EditWatchlistCheckboxSeriesField',
+ 'options' => $options,
+ 'section' => "ns$namespace",
+ );
+ }
}
$this->cleanupWatchlist();
@@ -548,10 +606,11 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new EditWatchlistNormalHTMLForm( $fields, $context );
$form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
- # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
+ # Used message keys:
+ # 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
$form->setSubmitTooltip( 'watchlistedit-normal-submit' );
$form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
$form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
@@ -563,21 +622,16 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
/**
* Build the label for a checkbox, with a link to the title, and various additional bits
*
- * @param $title Title
+ * @param Title $title
* @return string
*/
private function buildRemoveLine( $title ) {
$link = Linker::link( $title );
- if ( $title->isRedirect() ) {
- // Linker already makes class mw-redirect, so this is redundant
- $link = '<span class="watchlistredir">' . $link . '</span>';
- }
-
- $tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() );
+ $tools['talk'] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() );
if ( $title->exists() ) {
- $tools[] = Linker::linkKnown(
+ $tools['history'] = Linker::linkKnown(
$title,
$this->msg( 'history_short' )->escaped(),
array(),
@@ -586,13 +640,21 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
if ( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
- $tools[] = Linker::linkKnown(
+ $tools['contributions'] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
$this->msg( 'contributions' )->escaped()
);
}
- wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $title->isRedirect(), $this->getSkin() ) );
+ wfRunHooks(
+ 'WatchlistEditorBuildRemoveLine',
+ array( &$tools, $title, $title->isRedirect(), $this->getSkin(), &$link )
+ );
+
+ if ( $title->isRedirect() ) {
+ // Linker already makes class mw-redirect, so this is redundant
+ $link = '<span class="watchlistredir">' . $link . '</span>';
+ }
return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")";
}
@@ -612,7 +674,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
),
);
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle( 'raw' ) ); // Reset subpage
+ $context->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage
$form = new HTMLForm( $fields, $context );
$form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
# Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
@@ -625,11 +687,30 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
/**
+ * Get a form for clearing the watchlist
+ *
+ * @return HTMLForm
+ */
+ protected function getClearForm() {
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getPageTitle( 'clear' ) ); // Reset subpage
+ $form = new HTMLForm( array(), $context );
+ $form->setSubmitTextMsg( 'watchlistedit-clear-submit' );
+ # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit'
+ $form->setSubmitTooltip( 'watchlistedit-clear-submit' );
+ $form->setWrapperLegendMsg( 'watchlistedit-clear-legend' );
+ $form->addHeaderText( $this->msg( 'watchlistedit-clear-explain' )->parse() );
+ $form->setSubmitCallback( array( $this, 'submitClear' ) );
+
+ return $form;
+ }
+
+ /**
* Determine whether we are editing the watchlist, and if so, what
* kind of editing operation
*
- * @param $request WebRequest
- * @param $par mixed
+ * @param WebRequest $request
+ * @param string $par
* @return int
*/
public static function getMode( $request, $par ) {
@@ -654,7 +735,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* Build a set of links for convenient navigation
* between watchlist viewing and editing modes
*
- * @param $unused
+ * @param null $unused
* @return string
*/
public static function buildTools( $unused ) {
@@ -665,6 +746,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
'view' => array( 'Watchlist', false ),
'edit' => array( 'EditWatchlist', false ),
'raw' => array( 'EditWatchlist', 'raw' ),
+ 'clear' => array( 'EditWatchlist', 'clear' ),
);
foreach ( $modes as $mode => $arr ) {
@@ -683,10 +765,6 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
}
-# B/C since 1.18
-class WatchlistEditor extends SpecialEditWatchlist {
-}
-
/**
* Extend HTMLForm purely so we can have a more sane way of getting the section headers
*/
@@ -712,9 +790,9 @@ class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField {
* form is open (bug 32126), but we know that invalid items will
* be harmless so we can override it here.
*
- * @param string $value the value the field was submitted with
- * @param array $alldata the data collected from the form
- * @return Mixed Bool true on success, or String error to display.
+ * @param string $value The value the field was submitted with
+ * @param array $alldata The data collected from the form
+ * @return bool|string Bool true on success, or String error to display.
*/
function validate( $value, $alldata ) {
// Need to call into grandparent to be a good citizen. :)
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index 2e90d996..20532a92 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -113,7 +113,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
// error out if sending user cannot do this
$error = self::getPermissionsError(
$this->getUser(),
- $this->getRequest()->getVal( 'wpEditToken' )
+ $this->getRequest()->getVal( 'wpEditToken' ),
+ $this->getConfig()
);
switch ( $error ) {
@@ -138,18 +139,19 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$ret = self::getTarget( $this->mTarget );
if ( !$ret instanceof User ) {
if ( $this->mTarget != '' ) {
+ // Messages used here: notargettext, noemailtext, nowikiemailtext
$ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
$out->wrapWikiMsg( "<p class='error'>$1</p>", $ret );
}
$out->addHTML( $this->userForm( $this->mTarget ) );
- return false;
+ return;
}
$this->mTargetObj = $ret;
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new HTMLForm( $this->getFormFields(), $context );
// By now we are supposed to be sure that $this->mTarget is a user name
$form->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() );
@@ -159,7 +161,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$form->loadData();
if ( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) {
- return false;
+ return;
}
$result = $form->show();
@@ -174,8 +176,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
/**
* Validate target User
*
- * @param string $target target user name
- * @return User object on success or a string on error
+ * @param string $target Target user name
+ * @return User User object on success or a string on error
*/
public static function getTarget( $target ) {
if ( $target == '' ) {
@@ -205,14 +207,17 @@ class SpecialEmailUser extends UnlistedSpecialPage {
/**
* Check whether a user is allowed to send email
*
- * @param $user User object
- * @param string $editToken edit token
- * @return null on success or string on error
+ * @param User $user
+ * @param string $editToken Edit token
+ * @param Config $config optional for backwards compatibility
+ * @return string|null Null on success or string on error
*/
- public static function getPermissionsError( $user, $editToken ) {
- global $wgEnableEmail, $wgEnableUserEmail;
-
- if ( !$wgEnableEmail || !$wgEnableUserEmail ) {
+ public static function getPermissionsError( $user, $editToken, Config $config = null ) {
+ if ( $config === null ) {
+ wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+ if ( !$config->get( 'EnableEmail' ) || !$config->get( 'EnableUserEmail' ) ) {
return 'usermaildisabled';
}
@@ -251,16 +256,15 @@ class SpecialEmailUser extends UnlistedSpecialPage {
/**
* Form to ask for target user name.
*
- * @param string $name user name submitted.
- * @return String: form asking for user name.
+ * @param string $name User name submitted.
+ * @return string Form asking for user name.
*/
protected function userForm( $name ) {
- global $wgScript;
$string = Xml::openElement(
'form',
- array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' )
+ array( 'method' => 'get', 'action' => wfScript(), 'id' => 'askusername' )
) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
Xml::openElement( 'fieldset' ) .
Html::rawElement( 'legend', null, $this->msg( 'emailtarget' )->parse() ) .
Xml::inputLabel(
@@ -282,8 +286,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
* Submit callback for an HTMLForm object, will simply call submit().
*
* @since 1.20
- * @param $data array
- * @param $form HTMLForm object
+ * @param array $data
+ * @param HTMLForm $form
* @return Status|string|bool
*/
public static function uiSubmit( array $data, HTMLForm $form ) {
@@ -297,19 +301,20 @@ class SpecialEmailUser extends UnlistedSpecialPage {
*
* @param array $data
* @param IContextSource $context
- * @return Mixed: Status object, or potentially a String on error
+ * @return Status|string|bool Status object, or potentially a String on error
* or maybe even true on success if anything uses the EmailUser hook.
*/
public static function submit( array $data, IContextSource $context ) {
- global $wgUserEmailUseReplyTo;
+ $config = $context->getConfig();
$target = self::getTarget( $data['Target'] );
if ( !$target instanceof User ) {
+ // Messages used here: notargettext, noemailtext, nowikiemailtext
return $context->msg( $target . 'text' )->parseAsBlock();
}
- $to = new MailAddress( $target );
- $from = new MailAddress( $context->getUser() );
+ $to = MailAddress::newFromUser( $target );
+ $from = MailAddress::newFromUser( $context->getUser() );
$subject = $data['Subject'];
$text = $data['Text'];
@@ -323,16 +328,15 @@ class SpecialEmailUser extends UnlistedSpecialPage {
return $error;
}
- if ( $wgUserEmailUseReplyTo ) {
+ 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).
- global $wgPasswordSender, $wgPasswordSenderName;
-
- $mailFrom = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
+ $mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
+ wfMessage( 'emailsender' )->inContentLanguage()->text() );
$replyTo = $from;
} else {
// Put the sending user's e-mail address in the From: header.
diff --git a/includes/specials/SpecialExpandTemplates.php b/includes/specials/SpecialExpandTemplates.php
new file mode 100644
index 00000000..62f957fc
--- /dev/null
+++ b/includes/specials/SpecialExpandTemplates.php
@@ -0,0 +1,286 @@
+<?php
+/**
+ * Implements Special:ExpandTemplates
+ *
+ * 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
+ */
+
+/**
+ * A special page that expands submitted templates, parser functions,
+ * and variables, allowing easier debugging of these.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialExpandTemplates extends SpecialPage {
+
+ /** @var bool Whether or not to show the XML parse tree */
+ protected $generateXML;
+
+ /** @var bool Whether or not to show the raw HTML code */
+ protected $generateRawHtml;
+
+ /** @var bool Whether or not to remove comments in the expanded wikitext */
+ protected $removeComments;
+
+ /** @var bool Whether or not to remove <nowiki> tags in the expanded wikitext */
+ protected $removeNowiki;
+
+ /** @var int Maximum size in bytes to include. 50MB allows fixing those huge pages */
+ const MAX_INCLUDE_SIZE = 50000000;
+
+ function __construct() {
+ parent::__construct( 'ExpandTemplates' );
+ }
+
+ /**
+ * Show the special page
+ * @param string|null $subpage
+ */
+ function execute( $subpage ) {
+ global $wgParser;
+
+ $this->setHeaders();
+
+ $request = $this->getRequest();
+ $titleStr = $request->getText( 'wpContextTitle' );
+ $title = Title::newFromText( $titleStr );
+
+ if ( !$title ) {
+ $title = $this->getPageTitle();
+ }
+ $input = $request->getText( 'wpInput' );
+ $this->generateXML = $request->getBool( 'wpGenerateXml' );
+ $this->generateRawHtml = $request->getBool( 'wpGenerateRawHtml' );
+
+ if ( strlen( $input ) ) {
+ $this->removeComments = $request->getBool( 'wpRemoveComments', false );
+ $this->removeNowiki = $request->getBool( 'wpRemoveNowiki', false );
+ $options = ParserOptions::newFromContext( $this->getContext() );
+ $options->setRemoveComments( $this->removeComments );
+ $options->setTidy( true );
+ $options->setMaxIncludeSize( self::MAX_INCLUDE_SIZE );
+
+ if ( $this->generateXML ) {
+ $wgParser->startExternalParse( $title, $options, OT_PREPROCESS );
+ $dom = $wgParser->preprocessToDom( $input );
+
+ if ( method_exists( $dom, 'saveXML' ) ) {
+ $xml = $dom->saveXML();
+ } else {
+ $xml = $dom->__toString();
+ }
+ }
+
+ $output = $wgParser->preprocess( $input, $title, $options );
+ } else {
+ $this->removeComments = $request->getBool( 'wpRemoveComments', true );
+ $this->removeNowiki = $request->getBool( 'wpRemoveNowiki', false );
+ $output = false;
+ }
+
+ $out = $this->getOutput();
+ $out->addWikiMsg( 'expand_templates_intro' );
+ $out->addHTML( $this->makeForm( $titleStr, $input ) );
+
+ if ( $output !== false ) {
+ if ( $this->generateXML && strlen( $output ) > 0 ) {
+ $out->addHTML( $this->makeOutput( $xml, 'expand_templates_xml_output' ) );
+ }
+
+ $tmp = $this->makeOutput( $output );
+
+ if ( $this->removeNowiki ) {
+ $tmp = preg_replace(
+ array( '_&lt;nowiki&gt;_', '_&lt;/nowiki&gt;_', '_&lt;nowiki */&gt;_' ),
+ '',
+ $tmp
+ );
+ }
+
+ $config = $this->getConfig();
+ if ( ( $config->get( 'UseTidy' ) && $options->getTidy() ) || $config->get( 'AlwaysUseTidy' ) ) {
+ $tmp = MWTidy::tidy( $tmp );
+ }
+
+ $out->addHTML( $tmp );
+
+ $pout = $this->generateHtml( $title, $output );
+ $rawhtml = $pout->getText();
+ if ( $this->generateRawHtml && strlen( $rawhtml ) > 0 ) {
+ $out->addHTML( $this->makeOutput( $rawhtml, 'expand_templates_html_output' ) );
+ }
+
+ $this->showHtmlPreview( $title, $pout, $out );
+ }
+ }
+
+ /**
+ * Generate a form allowing users to enter information
+ *
+ * @param string $title Value for context title field
+ * @param string $input Value for input textbox
+ * @return string
+ */
+ private function makeForm( $title, $input ) {
+ $self = $this->getPageTitle();
+ $request = $this->getRequest();
+ $user = $this->getUser();
+
+ $form = Xml::openElement(
+ 'form',
+ array( 'method' => 'post', 'action' => $self->getLocalUrl() )
+ );
+ $form .= "<fieldset><legend>" . $this->msg( 'expandtemplates' )->escaped() . "</legend>\n";
+
+ $form .= '<p>' . Xml::inputLabel(
+ $this->msg( 'expand_templates_title' )->plain(),
+ 'wpContextTitle',
+ 'contexttitle',
+ 60,
+ $title,
+ array( 'autofocus' => true )
+ ) . '</p>';
+ $form .= '<p>' . Xml::label(
+ $this->msg( 'expand_templates_input' )->text(),
+ 'input'
+ ) . '</p>';
+ $form .= Xml::textarea(
+ 'wpInput',
+ $input,
+ 10,
+ 10,
+ array( 'id' => 'input' )
+ );
+
+ $form .= '<p>' . Xml::checkLabel(
+ $this->msg( 'expand_templates_remove_comments' )->text(),
+ 'wpRemoveComments',
+ 'removecomments',
+ $this->removeComments
+ ) . '</p>';
+ $form .= '<p>' . Xml::checkLabel(
+ $this->msg( 'expand_templates_remove_nowiki' )->text(),
+ 'wpRemoveNowiki',
+ 'removenowiki',
+ $this->removeNowiki
+ ) . '</p>';
+ $form .= '<p>' . Xml::checkLabel(
+ $this->msg( 'expand_templates_generate_xml' )->text(),
+ 'wpGenerateXml',
+ 'generate_xml',
+ $this->generateXML
+ ) . '</p>';
+ $form .= '<p>' . Xml::checkLabel(
+ $this->msg( 'expand_templates_generate_rawhtml' )->text(),
+ 'wpGenerateRawHtml',
+ 'generate_rawhtml',
+ $this->generateRawHtml
+ ) . '</p>';
+ $form .= '<p>' . Xml::submitButton(
+ $this->msg( 'expand_templates_ok' )->text(),
+ array( 'accesskey' => 's' )
+ ) . '</p>';
+ $form .= "</fieldset>\n";
+ $form .= Html::hidden( 'wpEditToken', $user->getEditToken( '', $request ) );
+ $form .= Xml::closeElement( 'form' );
+
+ return $form;
+ }
+
+ /**
+ * Generate a nice little box with a heading for output
+ *
+ * @param string $output Wiki text output
+ * @param string $heading
+ * @return string
+ */
+ private function makeOutput( $output, $heading = 'expand_templates_output' ) {
+ $out = "<h2>" . $this->msg( $heading )->escaped() . "</h2>\n";
+ $out .= Xml::textarea(
+ 'output',
+ $output,
+ 10,
+ 10,
+ array( 'id' => 'output', 'readonly' => 'readonly' )
+ );
+
+ return $out;
+ }
+
+ /**
+ * Renders the supplied wikitext as html
+ *
+ * @param Title $title
+ * @param string $text
+ * @return ParserOutput
+ */
+ private function generateHtml( Title $title, $text ) {
+ global $wgParser;
+
+ $popts = ParserOptions::newFromContext( $this->getContext() );
+ $popts->setTargetLanguage( $title->getPageLanguage() );
+ return $wgParser->parse( $text, $title, $popts );
+ }
+
+ /**
+ * Wraps the provided html code in a div and outputs it to the page
+ *
+ * @param Title $title
+ * @param ParserOutput $pout
+ * @param OutputPage $out
+ */
+ private function showHtmlPreview( Title $title, ParserOutput $pout, OutputPage $out ) {
+ $lang = $title->getPageViewLanguage();
+ $out->addHTML( "<h2>" . $this->msg( 'expand_templates_preview' )->escaped() . "</h2>\n" );
+
+ if ( $this->getConfig()->get( 'RawHtml' ) ) {
+ $request = $this->getRequest();
+ $user = $this->getUser();
+
+ // To prevent cross-site scripting attacks, don't show the preview if raw HTML is
+ // allowed and a valid edit token is not provided (bug 71111). However, MediaWiki
+ // does not currently provide logged-out users with CSRF protection; in that case,
+ // do not show the preview unless anonymous editing is allowed.
+ if ( $user->isAnon() && !$user->isAllowed( 'edit' ) ) {
+ $error = array( 'expand_templates_preview_fail_html_anon' );
+ } elseif ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ), '', $request ) ) {
+ $error = array( 'expand_templates_preview_fail_html' );
+ } else {
+ $error = false;
+ }
+
+ if ( $error ) {
+ $out->wrapWikiMsg( "<div class='previewnote'>\n$1\n</div>", $error );
+ return;
+ }
+ }
+
+ $out->addHTML( Html::openElement( 'div', array(
+ 'class' => 'mw-content-' . $lang->getDir(),
+ 'dir' => $lang->getDir(),
+ 'lang' => $lang->getHtmlCode(),
+ ) ) );
+ $out->addParserOutputContent( $pout );
+ $out->addHTML( Html::closeElement( 'div' ) );
+ }
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
+}
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index 61ed34d4..38c52a01 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -37,12 +37,9 @@ class SpecialExport extends SpecialPage {
}
public function execute( $par ) {
- global $wgSitename, $wgExportAllowListContributors, $wgExportFromNamespaces;
- global $wgExportAllowHistory, $wgExportMaxHistory, $wgExportMaxLinkDepth;
- global $wgExportAllowAll;
-
$this->setHeaders();
$this->outputHeader();
+ $config = $this->getConfig();
// Set some variables
$this->curonly = true;
@@ -74,7 +71,7 @@ class SpecialExport extends SpecialPage {
}
}
}
- } elseif ( $request->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
+ } elseif ( $request->getCheck( 'addns' ) && $config->get( 'ExportFromNamespaces' ) ) {
$page = $request->getText( 'pages' );
$nsindex = $request->getText( 'nsindex', '' );
@@ -87,7 +84,7 @@ class SpecialExport extends SpecialPage {
$page .= "\n" . implode( "\n", $nspages );
}
}
- } elseif ( $request->getCheck( 'exportall' ) && $wgExportAllowAll ) {
+ } elseif ( $request->getCheck( 'exportall' ) && $config->get( 'ExportAllowAll' ) ) {
$this->doExport = true;
$exportall = true;
@@ -108,19 +105,20 @@ class SpecialExport extends SpecialPage {
$offset = null;
}
+ $maxHistory = $config->get( 'ExportMaxHistory' );
$limit = $request->getInt( 'limit' );
$dir = $request->getVal( 'dir' );
$history = array(
'dir' => 'asc',
'offset' => false,
- 'limit' => $wgExportMaxHistory,
+ 'limit' => $maxHistory,
);
$historyCheck = $request->getCheck( 'history' );
if ( $this->curonly ) {
$history = WikiExporter::CURRENT;
} elseif ( !$historyCheck ) {
- if ( $limit > 0 && ( $wgExportMaxHistory == 0 || $limit < $wgExportMaxHistory ) ) {
+ if ( $limit > 0 && ( $maxHistory == 0 || $limit < $maxHistory ) ) {
$history['limit'] = $limit;
}
@@ -152,13 +150,13 @@ class SpecialExport extends SpecialPage {
}
}
- if ( !$wgExportAllowHistory ) {
+ if ( !$config->get( 'ExportAllowHistory' ) ) {
// Override
$history = WikiExporter::CURRENT;
}
$list_authors = $request->getCheck( 'listauthors' );
- if ( !$this->curonly || !$wgExportAllowListContributors ) {
+ if ( !$this->curonly || !$config->get( 'ExportAllowListContributors' ) ) {
$list_authors = false;
}
@@ -172,7 +170,7 @@ class SpecialExport extends SpecialPage {
if ( $request->getCheck( 'wpDownload' ) ) {
// Provide a sane filename suggestion
- $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' );
+ $filename = urlencode( $config->get( 'Sitename' ) . '-' . wfTimestampNow() . '.xml' );
$request->response()->header( "Content-disposition: attachment;filename={$filename}" );
}
@@ -185,7 +183,7 @@ class SpecialExport extends SpecialPage {
$out->addWikiMsg( 'exporttext' );
$form = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) );
+ 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ) ) );
$form .= Xml::inputLabel(
$this->msg( 'export-addcattext' )->text(),
'catname',
@@ -197,7 +195,7 @@ class SpecialExport extends SpecialPage {
array( 'name' => 'addcat' )
) . '<br />';
- if ( $wgExportFromNamespaces ) {
+ if ( $config->get( 'ExportFromNamespaces' ) ) {
$form .= Html::namespaceSelector(
array(
'selected' => $nsindex,
@@ -214,7 +212,7 @@ class SpecialExport extends SpecialPage {
) . '<br />';
}
- if ( $wgExportAllowAll ) {
+ if ( $config->get( 'ExportAllowAll' ) ) {
$form .= Xml::checkLabel(
$this->msg( 'exportall' )->text(),
'exportall',
@@ -231,7 +229,7 @@ class SpecialExport extends SpecialPage {
);
$form .= '<br />';
- if ( $wgExportAllowHistory ) {
+ if ( $config->get( 'ExportAllowHistory' ) ) {
$form .= Xml::checkLabel(
$this->msg( 'exportcuronly' )->text(),
'curonly',
@@ -249,7 +247,7 @@ class SpecialExport extends SpecialPage {
$request->wasPosted() ? $request->getCheck( 'templates' ) : false
) . '<br />';
- if ( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) {
+ if ( $config->get( 'ExportMaxLinkDepth' ) || $this->userCanOverrideExportDepth() ) {
$form .= Xml::inputLabel(
$this->msg( 'export-pagelinks' )->text(),
'pagelink-depth',
@@ -259,8 +257,14 @@ class SpecialExport extends SpecialPage {
) . '<br />';
}
- // Enable this when we can do something useful exporting/importing image information. :)
- //$form .= Xml::checkLabel( $this->msg( 'export-images' )->text(), 'images', 'wpExportImages', false ) . '<br />';
+ /* Enable this when we can do something useful exporting/importing image information.
+ $form .= Xml::checkLabel(
+ $this->msg( 'export-images' )->text(),
+ 'images',
+ 'wpExportImages',
+ false
+ ) . '<br />';
+ */
$form .= Xml::checkLabel(
$this->msg( 'export-download' )->text(),
'wpDownload',
@@ -268,7 +272,7 @@ class SpecialExport extends SpecialPage {
$request->wasPosted() ? $request->getCheck( 'wpDownload' ) : true
) . '<br />';
- if ( $wgExportAllowListContributors ) {
+ if ( $config->get( 'ExportAllowListContributors' ) ) {
$form .= Xml::checkLabel(
$this->msg( 'exportlistauthors' )->text(),
'listauthors',
@@ -296,11 +300,11 @@ class SpecialExport extends SpecialPage {
/**
* Do the actual page exporting
*
- * @param string $page user input on what page(s) to export
- * @param $history Mixed: one of the WikiExporter history export constants
- * @param $list_authors Boolean: Whether to add distinct author list (when
- * not returning full history)
- * @param $exportall Boolean: Whether to export everything
+ * @param string $page User input on what page(s) to export
+ * @param int $history One of the WikiExporter history export constants
+ * @param bool $list_authors Whether to add distinct author list (when
+ * not returning full history)
+ * @param bool $exportall Whether to export everything
*/
private function doExport( $page, $history, $list_authors, $exportall ) {
@@ -315,7 +319,7 @@ class SpecialExport extends SpecialPage {
foreach ( explode( "\n", $page ) as $pageName ) {
$pageName = trim( $pageName );
$title = Title::newFromText( $pageName );
- if ( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) {
+ if ( $title && !$title->isExternal() && $title->getText() !== '' ) {
// Only record each page once!
$pageSet[$title->getPrefixedText()] = true;
}
@@ -397,7 +401,7 @@ class SpecialExport extends SpecialPage {
}
/**
- * @param $title Title
+ * @param Title $title
* @return array
*/
private function getPagesFromCategory( $title ) {
@@ -430,7 +434,7 @@ class SpecialExport extends SpecialPage {
}
/**
- * @param $nsindex int
+ * @param int $nsindex
* @return array
*/
private function getPagesFromNamespace( $nsindex ) {
@@ -463,9 +467,9 @@ class SpecialExport extends SpecialPage {
/**
* Expand a list of pages to include templates used in those pages.
- * @param $inputPages array, list of titles to look up
- * @param $pageSet array, associative array indexed by titles for output
- * @return array associative array index by titles
+ * @param array $inputPages List of titles to look up
+ * @param array $pageSet Associative array indexed by titles for output
+ * @return array Associative array index by titles
*/
private function getTemplates( $inputPages, $pageSet ) {
return $this->getLinks( $inputPages, $pageSet,
@@ -477,19 +481,18 @@ class SpecialExport extends SpecialPage {
/**
* Validate link depth setting, if available.
- * @param $depth int
+ * @param int $depth
* @return int
*/
private function validateLinkDepth( $depth ) {
- global $wgExportMaxLinkDepth;
-
if ( $depth < 0 ) {
return 0;
}
if ( !$this->userCanOverrideExportDepth() ) {
- if ( $depth > $wgExportMaxLinkDepth ) {
- return $wgExportMaxLinkDepth;
+ $maxLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' );
+ if ( $depth > $maxLinkDepth ) {
+ return $maxLinkDepth;
}
}
@@ -504,13 +507,15 @@ class SpecialExport extends SpecialPage {
/**
* Expand a list of pages to include pages linked to from that page.
- * @param $inputPages array
- * @param $pageSet array
- * @param $depth int
+ * @param array $inputPages
+ * @param array $pageSet
+ * @param int $depth
* @return array
*/
private function getPageLinks( $inputPages, $pageSet, $depth ) {
+ // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
for ( ; $depth > 0; --$depth ) {
+ // @codingStandardsIgnoreEnd
$pageSet = $this->getLinks(
$inputPages, $pageSet, 'pagelinks',
array( 'namespace' => 'pl_namespace', 'title' => 'pl_title' ),
@@ -525,10 +530,10 @@ class SpecialExport extends SpecialPage {
/**
* Expand a list of pages to include images used in those pages.
*
- * @param $inputPages array, list of titles to look up
- * @param $pageSet array, associative array indexed by titles for output
+ * @param array $inputPages List of titles to look up
+ * @param array $pageSet Associative array indexed by titles for output
*
- * @return array associative array index by titles
+ * @return array Associative array index by titles
*/
private function getImages( $inputPages, $pageSet ) {
return $this->getLinks(
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
index 47a4d75f..dc9d57c2 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -71,7 +71,7 @@ class FewestrevisionsPage extends QueryPage {
/**
* @param Skin $skin
* @param object $result Database row
- * @return String
+ * @return string
*/
function formatResult( $skin, $result ) {
global $wgContLang;
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index 4c6593b2..fc26c903 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -59,7 +59,7 @@ class FileDuplicateSearchPage extends QueryPage {
/**
* Fetch dupes from all connected file repositories.
*
- * @return array of File objects
+ * @return array Array of File objects
*/
function getDupes() {
return RepoGroup::singleton()->findBySha1( $this->hash );
@@ -67,7 +67,7 @@ class FileDuplicateSearchPage extends QueryPage {
/**
*
- * @param array $dupes of File objects
+ * @param array $dupes Array of File objects
*/
function showList( $dupes ) {
$html = array();
@@ -96,12 +96,10 @@ class FileDuplicateSearchPage extends QueryPage {
}
function execute( $par ) {
- global $wgScript;
-
$this->setHeaders();
$this->outputHeader();
- $this->filename = isset( $par ) ? $par : $this->getRequest()->getText( 'filename' );
+ $this->filename = $par !== null ? $par : $this->getRequest()->getText( 'filename' );
$this->file = null;
$this->hash = '';
$title = Title::newFromText( $this->filename, NS_FILE );
@@ -115,9 +113,9 @@ class FileDuplicateSearchPage extends QueryPage {
$out->addHTML(
Html::openElement(
'form',
- array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript )
+ array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => wfScript() )
) . "\n" .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . "\n" .
Html::openElement( 'fieldset' ) . "\n" .
Html::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) . "\n" .
Xml::inputLabel(
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index e7ced52a..5860f636 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -36,7 +36,13 @@ class SpecialFilepath extends RedirectSpecialPage {
// implement by redirecting through Special:Redirect/file
function getRedirect( $par ) {
$file = $par ?: $this->getRequest()->getText( 'file' );
- return SpecialPage::getSafeTitleFor( 'Redirect', 'file/' . $file );
+
+ if ( $file ) {
+ $argument = "file/$file";
+ } else {
+ $argument = 'file';
+ }
+ return SpecialPage::getSafeTitleFor( 'Redirect', $argument );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index d7d860de..eab4784c 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -3,7 +3,7 @@
* Implements Special:Import
*
* Copyright © 2003,2005 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * 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
@@ -31,6 +31,8 @@
*/
class SpecialImport extends SpecialPage {
private $interwiki = false;
+ private $subproject;
+ private $fullInterwikiPrefix;
private $namespace;
private $rootpage = '';
private $frompage = '';
@@ -44,17 +46,19 @@ class SpecialImport extends SpecialPage {
*/
public function __construct() {
parent::__construct( 'Import', 'import' );
- global $wgImportTargetNamespace;
- $this->namespace = $wgImportTargetNamespace;
+ $this->namespace = $this->getConfig()->get( 'ImportTargetNamespace' );
}
/**
* Execute
+ * @param string|null $par
*/
function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
+ $this->getOutput()->addModules( 'mediawiki.special.import' );
+
$user = $this->getUser();
if ( !$user->isAllowedAny( 'import', 'importupload' ) ) {
throw new PermissionsError( 'import' );
@@ -64,11 +68,11 @@ class SpecialImport extends SpecialPage {
# @todo FIXME: Title::checkSpecialsAndNSPermissions() has a very wierd expectation of what
# getUserPermissionsErrors() might actually be used for, hence the 'ns-specialprotected'
$errors = wfMergeErrorArrays(
- $this->getTitle()->getUserPermissionsErrors(
+ $this->getPageTitle()->getUserPermissionsErrors(
'import', $user, true,
array( 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' )
),
- $this->getTitle()->getUserPermissionsErrors(
+ $this->getPageTitle()->getUserPermissionsErrors(
'importupload', $user, true,
array( 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' )
)
@@ -91,15 +95,15 @@ class SpecialImport extends SpecialPage {
* Do the actual import
*/
private function doImport() {
- global $wgImportSources, $wgExportMaxLinkDepth;
-
$isUpload = false;
$request = $this->getRequest();
$this->namespace = $request->getIntOrNull( 'namespace' );
$sourceName = $request->getVal( "source" );
$this->logcomment = $request->getText( 'log-comment' );
- $this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $request->getIntOrNull( 'pagelink-depth' );
+ $this->pageLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' ) == 0
+ ? 0
+ : $request->getIntOrNull( 'pagelink-depth' );
$this->rootpage = $request->getText( 'rootpage' );
$user = $this->getUser();
@@ -116,19 +120,30 @@ class SpecialImport extends SpecialPage {
if ( !$user->isAllowed( 'import' ) ) {
throw new PermissionsError( 'import' );
}
- $this->interwiki = $request->getVal( 'interwiki' );
- if ( !in_array( $this->interwiki, $wgImportSources ) ) {
+ $this->interwiki = $this->fullInterwikiPrefix = $request->getVal( 'interwiki' );
+ // does this interwiki have subprojects?
+ $importSources = $this->getConfig()->get( 'ImportSources' );
+ $hasSubprojects = array_key_exists( $this->interwiki, $importSources );
+ if ( !$hasSubprojects && !in_array( $this->interwiki, $importSources ) ) {
$source = Status::newFatal( "import-invalid-interwiki" );
} else {
- $this->history = $request->getCheck( 'interwikiHistory' );
- $this->frompage = $request->getText( "frompage" );
- $this->includeTemplates = $request->getCheck( 'interwikiTemplates' );
- $source = ImportStreamSource::newFromInterwiki(
- $this->interwiki,
- $this->frompage,
- $this->history,
- $this->includeTemplates,
- $this->pageLinkDepth );
+ if ( $hasSubprojects ) {
+ $this->subproject = $request->getVal( 'subproject' );
+ $this->fullInterwikiPrefix .= ':' . $request->getVal( 'subproject' );
+ }
+ if ( $hasSubprojects && !in_array( $this->subproject, $importSources[$this->interwiki] ) ) {
+ $source = Status::newFatal( "import-invalid-interwiki" );
+ } else {
+ $this->history = $request->getCheck( 'interwikiHistory' );
+ $this->frompage = $request->getText( "frompage" );
+ $this->includeTemplates = $request->getCheck( 'interwikiTemplates' );
+ $source = ImportStreamSource::newFromInterwiki(
+ $this->fullInterwikiPrefix,
+ $this->frompage,
+ $this->history,
+ $this->includeTemplates,
+ $this->pageLinkDepth );
+ }
}
} else {
$source = Status::newFatal( "importunknownsource" );
@@ -166,7 +181,7 @@ class SpecialImport extends SpecialPage {
$reporter = new ImportReporter(
$importer,
$isUpload,
- $this->interwiki,
+ $this->fullInterwikiPrefix,
$this->logcomment
);
$reporter->setContext( $this->getContext() );
@@ -201,11 +216,10 @@ class SpecialImport extends SpecialPage {
}
private function showForm() {
- global $wgImportSources, $wgExportMaxLinkDepth;
-
- $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) );
+ $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) );
$user = $this->getUser();
$out = $this->getOutput();
+ $importSources = $this->getConfig()->get( 'ImportSources' );
if ( $user->isAllowed( 'importupload' ) ) {
$out->addHTML(
@@ -242,7 +256,10 @@ class SpecialImport extends SpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-rootpage' )->text(), 'mw-interwiki-rootpage-upload' ) .
+ Xml::label(
+ $this->msg( 'import-interwiki-rootpage' )->text(),
+ 'mw-interwiki-rootpage-upload'
+ ) .
"</td>
<td class='mw-input'>" .
Xml::input( 'rootpage', 50, $this->rootpage,
@@ -261,15 +278,15 @@ class SpecialImport extends SpecialPage {
Xml::closeElement( 'fieldset' )
);
} else {
- if ( empty( $wgImportSources ) ) {
+ if ( empty( $importSources ) ) {
$out->addWikiMsg( 'importnosources' );
}
}
- if ( $user->isAllowed( 'import' ) && !empty( $wgImportSources ) ) {
+ if ( $user->isAllowed( 'import' ) && !empty( $importSources ) ) {
# Show input field for import depth only if $wgExportMaxLinkDepth > 0
$importDepth = '';
- if ( $wgExportMaxLinkDepth > 0 ) {
+ if ( $this->getConfig()->get( 'ExportMaxLinkDepth' ) > 0 ) {
$importDepth = "<tr>
<td class='mw-label'>" .
$this->msg( 'export-pagelinks' )->parse() .
@@ -297,7 +314,7 @@ class SpecialImport extends SpecialPage {
Xml::openElement( 'table', array( 'id' => 'mw-import-table-interwiki' ) ) .
"<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-source' )->text(), 'interwiki' ) .
+ Xml::label( $this->msg( 'import-interwiki-sourcewiki' )->text(), 'interwiki' ) .
"</td>
<td class='mw-input'>" .
Xml::openElement(
@@ -306,13 +323,63 @@ class SpecialImport extends SpecialPage {
)
);
- foreach ( $wgImportSources as $prefix ) {
- $selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : '';
- $out->addHTML( Xml::option( $prefix, $prefix, $selected ) );
+ $needSubprojectField = false;
+ foreach ( $importSources as $key => $value ) {
+ if ( is_int( $key ) ) {
+ $key = $value;
+ } elseif ( $value !== $key ) {
+ $needSubprojectField = true;
+ }
+
+ $attribs = array(
+ 'value' => $key,
+ );
+ if ( is_array( $value ) ) {
+ $attribs['data-subprojects'] = implode( ' ', $value );
+ }
+ if ( $this->interwiki === $key ) {
+ $attribs['selected'] = 'selected';
+ }
+ $out->addHTML( Html::element( 'option', $attribs, $key ) );
+ }
+
+ $out->addHTML(
+ Xml::closeElement( 'select' )
+ );
+
+ if ( $needSubprojectField ) {
+ $out->addHTML(
+ Xml::openElement(
+ 'select',
+ array( 'name' => 'subproject', 'id' => 'subproject' )
+ )
+ );
+
+ $subprojectsToAdd = array();
+ foreach ( $importSources as $key => $value ) {
+ if ( is_array( $value ) ) {
+ $subprojectsToAdd = array_merge( $subprojectsToAdd, $value );
+ }
+ }
+ $subprojectsToAdd = array_unique( $subprojectsToAdd );
+ sort( $subprojectsToAdd );
+ foreach ( $subprojectsToAdd as $subproject ) {
+ $out->addHTML( Xml::option( $subproject, $subproject, $this->subproject === $subproject ) );
+ }
+
+ $out->addHTML(
+ Xml::closeElement( 'select' )
+ );
}
$out->addHTML(
- Xml::closeElement( 'select' ) .
+ "</td>
+ </tr>
+ <tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'import-interwiki-sourcepage' )->text(), 'frompage' ) .
+ "</td>
+ <td class='mw-input'>" .
Xml::input( 'frompage', 50, $this->frompage, array( 'id' => 'frompage' ) ) .
"</td>
</tr>
@@ -411,11 +478,10 @@ class ImportReporter extends ContextSource {
private $mOriginalPageOutCallback = null;
private $mLogItemCount = 0;
-
/**
* @param WikiImporter $importer
- * @param $upload
- * @param $interwiki
+ * @param bool $upload
+ * @param string $interwiki
* @param string|bool $reason
*/
function __construct( $importer, $upload, $interwiki, $reason = false ) {
@@ -435,7 +501,9 @@ class ImportReporter extends ContextSource {
}
function reportNotice( $msg, array $params ) {
- $this->getOutput()->addHTML( Html::element( 'li', array(), $this->msg( $msg, $params )->text() ) );
+ $this->getOutput()->addHTML(
+ Html::element( 'li', array(), $this->msg( $msg, $params )->text() )
+ );
}
function reportLogItem( /* ... */ ) {
@@ -449,8 +517,8 @@ class ImportReporter extends ContextSource {
* @param Title $title
* @param Title $origTitle
* @param int $revisionCount
- * @param $successCount
- * @param $pageInfo
+ * @param int $successCount
+ * @param array $pageInfo
* @return void
*/
function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
@@ -476,7 +544,8 @@ class ImportReporter extends ContextSource {
$detail = $this->msg( 'import-logentry-upload-detail' )->numParams(
$successCount )->inContentLanguage()->text();
if ( $this->reason ) {
- $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
+ $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
+ . $this->reason;
}
$log->addEntry( 'upload', $title, $detail, array(), $this->getUser() );
} else {
@@ -485,7 +554,8 @@ class ImportReporter extends ContextSource {
$detail = $this->msg( 'import-logentry-interwiki-detail' )->numParams(
$successCount )->params( $interwiki )->inContentLanguage()->text();
if ( $this->reason ) {
- $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
+ $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
+ . $this->reason;
}
$log->addEntry( 'interwiki', $title, $detail, array(), $this->getUser() );
}
@@ -493,13 +563,23 @@ class ImportReporter extends ContextSource {
$comment = $detail; // quick
$dbw = wfGetDB( DB_MASTER );
$latest = $title->getLatestRevID();
- $nullRevision = Revision::newNullRevision( $dbw, $title->getArticleID(), $comment, true );
+ $nullRevision = Revision::newNullRevision(
+ $dbw,
+ $title->getArticleID(),
+ $comment,
+ true,
+ $this->getUser()
+ );
+
if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
$page = WikiPage::factory( $title );
# Update page record
$page->updateRevisionOn( $dbw, $nullRevision );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $page, $nullRevision, $latest, $this->getUser() ) );
+ wfRunHooks(
+ 'NewRevisionFromEditComplete',
+ array( $page, $nullRevision, $latest, $this->getUser() )
+ );
}
} else {
$this->getOutput()->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php
index 7069d527..0efebb3e 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -25,13 +25,12 @@
* @ingroup SpecialPage
*/
class SpecialJavaScriptTest extends SpecialPage {
-
/**
- * @var $frameworks Array: Mapping of framework ids and their initilizer methods
+ * @var array Mapping of framework ids and their initilizer methods
* in this class. If a framework is requested but not in this array,
* the 'unknownframework' error is served.
*/
- static $frameworks = array(
+ private static $frameworks = array(
'qunit' => 'initQUnitTesting',
);
@@ -68,7 +67,7 @@ class SpecialJavaScriptTest extends SpecialPage {
$this->msg( "javascripttest-$framework-name" )->plain()
) );
$out->setSubtitle( $this->msg( 'javascripttest-backlink' )
- ->rawParams( Linker::linkKnown( $this->getTitle() ) ) );
+ ->rawParams( Linker::linkKnown( $this->getPageTitle() ) ) );
$this->{self::$frameworks[$framework]}();
} else {
// Framework not found, display error
@@ -97,7 +96,7 @@ class SpecialJavaScriptTest extends SpecialPage {
'li',
array(),
Linker::link(
- $this->getTitle( $framework ),
+ $this->getPageTitle( $framework ),
// Message: javascripttest-qunit-name
$this->msg( "javascripttest-$framework-name" )->escaped()
)
@@ -135,16 +134,15 @@ class SpecialJavaScriptTest extends SpecialPage {
* Initialize the page for QUnit.
*/
private function initQUnitTesting() {
- global $wgJavaScriptTestConfig;
-
$out = $this->getOutput();
+ $testConfig = $this->getConfig()->get( 'JavaScriptTestConfig' );
- $out->addModules( 'mediawiki.tests.qunit.testrunner' );
+ $out->addModules( 'test.mediawiki.qunit.testrunner' );
$qunitTestModules = $out->getResourceLoader()->getTestModuleNames( 'qunit' );
$out->addModules( $qunitTestModules );
$summary = $this->msg( 'javascripttest-qunit-intro' )
- ->params( $wgJavaScriptTestConfig['qunit']['documentation'] )
+ ->params( $testConfig['qunit']['documentation'] )
->parseAsBlock();
$header = $this->msg( 'javascripttest-qunit-heading' )->escaped();
$userDir = $this->getLanguage()->getDir();
@@ -170,7 +168,22 @@ HTML;
// $wgJavaScriptTestConfig in DefaultSettings.php
$out->addJsConfigVars(
'QUnitTestSwarmInjectJSPath',
- $wgJavaScriptTestConfig['qunit']['testswarm-injectjs']
+ $testConfig['qunit']['testswarm-injectjs']
+ );
+ }
+
+ /**
+ * 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
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ return self::prefixSearchArray(
+ $search,
+ $limit,
+ array_keys( self::$frameworks )
);
}
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 5b0c56e5..371469bb 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -27,6 +27,12 @@
* @ingroup SpecialPage
*/
class LinkSearchPage extends QueryPage {
+
+ /**
+ * @var PageLinkRenderer
+ */
+ protected $linkRenderer = null;
+
function setParams( $params ) {
$this->mQuery = $params['query'];
$this->mNs = $params['namespace'];
@@ -35,6 +41,36 @@ class LinkSearchPage extends QueryPage {
function __construct( $name = 'LinkSearch' ) {
parent::__construct( $name );
+
+ // Since we don't control the constructor parameters, we can't inject services that way.
+ // Instead, we initialize services in the execute() method, and allow them to be overridden
+ // using the setServices() method.
+ }
+
+ /**
+ * Initialize or override the PageLinkRenderer LinkSearchPage collaborates with.
+ * Useful mainly for testing.
+ *
+ * @todo query logic and rendering logic should be split and also injected
+ *
+ * @param PageLinkRenderer $linkRenderer
+ */
+ public function setPageLinkRenderer(
+ PageLinkRenderer $linkRenderer
+ ) {
+ $this->linkRenderer = $linkRenderer;
+ }
+
+ /**
+ * Initialize any services we'll need (unless it has already been provided via a setter).
+ * This allows for dependency injection even though we don't control object creation.
+ */
+ private function initServices() {
+ if ( !$this->linkRenderer ) {
+ $lang = $this->getContext()->getLanguage();
+ $titleFormatter = new MediaWikiTitleCodec( $lang, GenderCache::singleton() );
+ $this->linkRenderer = new MediaWikiPageLinkRenderer( $titleFormatter );
+ }
}
function isCacheable() {
@@ -42,7 +78,7 @@ class LinkSearchPage extends QueryPage {
}
function execute( $par ) {
- global $wgUrlProtocols, $wgMiserMode, $wgScript;
+ $this->initServices();
$this->setHeaders();
$this->outputHeader();
@@ -55,32 +91,26 @@ class LinkSearchPage extends QueryPage {
$namespace = $request->getIntorNull( 'namespace', null );
$protocols_list = array();
- foreach ( $wgUrlProtocols as $prot ) {
+ foreach ( $this->getConfig()->get( 'UrlProtocols' ) as $prot ) {
if ( $prot !== '//' ) {
$protocols_list[] = $prot;
}
}
$target2 = $target;
- $protocol = '';
- $pr_sl = strpos( $target2, '//' );
- $pr_cl = strpos( $target2, ':' );
- if ( $pr_sl ) {
- // For protocols with '//'
- $protocol = substr( $target2, 0, $pr_sl + 2 );
- $target2 = substr( $target2, $pr_sl + 2 );
- } elseif ( !$pr_sl && $pr_cl ) {
- // For protocols without '//' like 'mailto:'
- $protocol = substr( $target2, 0, $pr_cl + 1 );
- $target2 = substr( $target2, $pr_cl + 1 );
- } elseif ( $protocol == '' && $target2 != '' ) {
- // default
- $protocol = 'http://';
- }
- if ( $protocol != '' && !in_array( $protocol, $protocols_list ) ) {
- // unsupported protocol, show original search request
- $target2 = $target;
- $protocol = '';
+ // Get protocol, default is http://
+ $protocol = 'http://';
+ $bits = wfParseUrl( $target );
+ if ( isset( $bits['scheme'] ) && isset( $bits['delimiter'] ) ) {
+ $protocol = $bits['scheme'] . $bits['delimiter'];
+ // Make sure wfParseUrl() didn't make some well-intended correction in the
+ // protocol
+ if ( strcasecmp( $protocol, substr( $target, 0, strlen( $protocol ) ) ) === 0 ) {
+ $target2 = substr( $target, strlen( $protocol ) );
+ } else {
+ // If it did, let LinkFilter::makeLikeArray() handle this
+ $protocol = '';
+ }
}
$out->addWikiMsg(
@@ -90,9 +120,9 @@ class LinkSearchPage extends QueryPage {
);
$s = Html::openElement(
'form',
- array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $wgScript )
+ array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => wfScript() )
) . "\n" .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . "\n" .
Html::openElement( 'fieldset' ) . "\n" .
Html::element( 'legend', array(), $this->msg( 'linksearch' )->text() ) . "\n" .
Xml::inputLabel(
@@ -100,10 +130,14 @@ class LinkSearchPage extends QueryPage {
'target',
'target',
50,
- $target
+ $target,
+ array(
+ // URLs are always ltr
+ 'dir' => 'ltr',
+ )
) . "\n";
- if ( !$wgMiserMode ) {
+ if ( !$this->getConfig()->get( 'MiserMode' ) ) {
$s .= Html::namespaceSelector(
array(
'selected' => $namespace,
@@ -145,18 +179,26 @@ class LinkSearchPage extends QueryPage {
/**
* Return an appropriately formatted LIKE query and the clause
*
- * @param string $query
- * @param string $prot
+ * @param string $query Search pattern to search for
+ * @param string $prot Protocol, e.g. 'http://'
+ *
* @return array
*/
static function mungeQuery( $query, $prot ) {
$field = 'el_index';
- $rv = LinkFilter::makeLikeArray( $query, $prot );
+ $dbr = wfGetDB( DB_SLAVE );
+
+ if ( $query === '*' && $prot !== '' ) {
+ // Allow queries like 'ftp://*' to find all ftp links
+ $rv = array( $prot, $dbr->anyString() );
+ } else {
+ $rv = LinkFilter::makeLikeArray( $query, $prot );
+ }
+
if ( $rv === false ) {
// LinkFilter doesn't handle wildcard in IP, so we'll have to munge here.
$pattern = '/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/';
if ( preg_match( $pattern, $query ) ) {
- $dbr = wfGetDB( DB_SLAVE );
$rv = array( $prot . rtrim( $query, " \t*" ), $dbr->anyString() );
$field = 'el_to';
}
@@ -166,10 +208,9 @@ class LinkSearchPage extends QueryPage {
}
function linkParameters() {
- global $wgMiserMode;
$params = array();
$params['target'] = $this->mProt . $this->mQuery;
- if ( isset( $this->mNs ) && !$wgMiserMode ) {
+ if ( $this->mNs !== null && !$this->getConfig()->get( 'MiserMode' ) ) {
$params['namespace'] = $this->mNs;
}
@@ -177,7 +218,6 @@ class LinkSearchPage extends QueryPage {
}
function getQueryInfo() {
- global $wgMiserMode;
$dbr = wfGetDB( DB_SLAVE );
// strip everything past first wildcard, so that
// index-based-only lookup would be done
@@ -204,7 +244,7 @@ class LinkSearchPage extends QueryPage {
'options' => array( 'USE INDEX' => $clause )
);
- if ( isset( $this->mNs ) && !$wgMiserMode ) {
+ if ( $this->mNs !== null && !$this->getConfig()->get( 'MiserMode' ) ) {
$retval['conds']['page_namespace'] = $this->mNs;
}
@@ -217,9 +257,10 @@ class LinkSearchPage extends QueryPage {
* @return string
*/
function formatResult( $skin, $result ) {
- $title = Title::makeTitle( $result->namespace, $result->title );
+ $title = new TitleValue( (int)$result->namespace, $result->title );
+ $pageLink = $this->linkRenderer->renderHtmlLink( $title );
+
$url = $result->url;
- $pageLink = Linker::linkKnown( $title );
$urlLink = Linker::makeExternalLink( $url, $url );
return $this->msg( 'linksearch-line' )->rawParams( $urlLink, $pageLink )->escaped();
@@ -227,6 +268,9 @@ class LinkSearchPage extends QueryPage {
/**
* Override to check query validity.
+ *
+ * @param mixed $offset Numerical offset or false for no offset
+ * @param mixed $limit Numerical limit or false for no limit
*/
function doQuery( $offset = false, $limit = false ) {
list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
diff --git a/includes/specials/SpecialListDuplicatedFiles.php b/includes/specials/SpecialListDuplicatedFiles.php
new file mode 100644
index 00000000..26672706
--- /dev/null
+++ b/includes/specials/SpecialListDuplicatedFiles.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Implements Special:ListDuplicatedFiles
+ *
+ * Copyright © 2013 Brian Wolff
+ *
+ * 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
+ * @author Brian Wolff
+ */
+
+/**
+ * Special:ListDuplicatedFiles Lists all files where the current version is
+ * a duplicate of the current version of some other file.
+ * @ingroup SpecialPage
+ */
+class ListDuplicatedFilesPage extends QueryPage {
+ function __construct( $name = 'ListDuplicatedFiles' ) {
+ parent::__construct( $name );
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ /**
+ * Get all the duplicates by grouping on sha1s.
+ *
+ * A cheaper (but less useful) version of this
+ * query would be to not care how many duplicates a
+ * particular file has, and do a self-join on image table.
+ * However this version should be no more expensive then
+ * Special:MostLinked, which seems to get handled fine
+ * with however we are doing cached special pages.
+ * @return array
+ */
+ function getQueryInfo() {
+ return array(
+ 'tables' => array( 'image' ),
+ 'fields' => array(
+ 'namespace' => NS_FILE,
+ 'title' => 'MIN(img_name)',
+ 'value' => 'count(*)'
+ ),
+ 'options' => array(
+ 'GROUP BY' => 'img_sha1',
+ 'HAVING' => 'count(*) > 1',
+ ),
+ );
+ }
+
+ /**
+ * Pre-fill the link cache
+ *
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ if ( $res->numRows() > 0 ) {
+ $linkBatch = new LinkBatch();
+
+ foreach ( $res as $row ) {
+ $linkBatch->add( $row->namespace, $row->title );
+ }
+
+ $res->seek( 0 );
+ $linkBatch->execute();
+ }
+ }
+
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ // Future version might include a list of the first 5 duplicates
+ // perhaps separated by an "↔".
+ $image1 = Title::makeTitle( $result->namespace, $result->title );
+ $dupeSearch = SpecialPage::getTitleFor( 'FileDuplicateSearch', $image1->getDBKey() );
+
+ $msg = $this->msg( 'listduplicatedfiles-entry' )
+ ->params( $image1->getText() )
+ ->numParams( $result->value - 1 )
+ ->params( $dupeSearch->getPrefixedDBKey() );
+
+ return $msg->parse();
+ }
+
+ protected function getGroupName() {
+ return 'media';
+ }
+}
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index dff1cf70..04a83c8f 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -33,6 +33,7 @@ class SpecialListFiles extends IncludableSpecialPage {
if ( $this->including() ) {
$userName = $par;
$search = '';
+ $showAll = false;
} else {
$userName = $this->getRequest()->getText( 'user', $par );
$search = $this->getRequest()->getText( 'ilsearch', '' );
@@ -47,15 +48,13 @@ class SpecialListFiles extends IncludableSpecialPage {
$showAll
);
+ $out = $this->getOutput();
if ( $this->including() ) {
- $html = $pager->getBody();
+ $out->addParserOutputContent( $pager->getBodyOutput() );
} else {
- $form = $pager->getForm();
- $body = $pager->getBody();
- $nav = $pager->getNavigationBar();
- $html = "$form<br />\n$body<br />\n$nav";
+ $out->addHTML( $pager->getForm() );
+ $out->addParserOutputContent( $pager->getFullOutput() );
}
- $this->getOutput()->addHTML( $html );
}
protected function getGroupName() {
@@ -67,20 +66,24 @@ class SpecialListFiles extends IncludableSpecialPage {
* @ingroup SpecialPage Pager
*/
class ImageListPager extends TablePager {
- var $mFieldNames = null;
+ protected $mFieldNames = null;
+
// Subclasses should override buildQueryConds instead of using $mQueryConds variable.
- var $mQueryConds = array();
- var $mUserName = null;
- var $mSearch = '';
- var $mIncluding = false;
- var $mShowAll = false;
- var $mTableName = 'image';
+ protected $mQueryConds = array();
+
+ protected $mUserName = null;
+
+ protected $mSearch = '';
+
+ protected $mIncluding = false;
+
+ protected $mShowAll = false;
+
+ protected $mTableName = 'image';
function __construct( IContextSource $context, $userName = null, $search = '',
$including = false, $showAll = false
) {
- global $wgMiserMode;
-
$this->mIncluding = $including;
$this->mShowAll = $showAll;
@@ -91,7 +94,7 @@ class ImageListPager extends TablePager {
}
}
- if ( $search !== '' && !$wgMiserMode ) {
+ if ( $search !== '' && !$this->getConfig()->get( 'MiserMode' ) ) {
$this->mSearch = $search;
$nt = Title::newFromURL( $this->mSearch );
@@ -105,12 +108,12 @@ class ImageListPager extends TablePager {
if ( !$including ) {
if ( $context->getRequest()->getText( 'sort', 'img_date' ) == 'img_date' ) {
- $this->mDefaultDirection = true;
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
} else {
- $this->mDefaultDirection = false;
+ $this->mDefaultDirection = IndexPager::DIR_ASCENDING;
}
} else {
- $this->mDefaultDirection = true;
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
}
parent::__construct( $context );
@@ -120,7 +123,7 @@ class ImageListPager extends TablePager {
* Build the where clause of the query.
*
* Replaces the older mQueryConds member variable.
- * @param $table String Either "image" or "oldimage"
+ * @param string $table Either "image" or "oldimage"
* @return array The query conditions.
*/
protected function buildQueryConds( $table ) {
@@ -128,7 +131,7 @@ class ImageListPager extends TablePager {
$conds = array();
if ( !is_null( $this->mUserName ) ) {
- $conds[ $prefix . '_user_text' ] = $this->mUserName;
+ $conds[$prefix . '_user_text'] = $this->mUserName;
}
if ( $this->mSearch !== '' ) {
@@ -153,20 +156,24 @@ class ImageListPager extends TablePager {
}
/**
- * @return Array
+ * @return array
*/
function getFieldNames() {
if ( !$this->mFieldNames ) {
- global $wgMiserMode;
$this->mFieldNames = array(
'img_timestamp' => $this->msg( 'listfiles_date' )->text(),
'img_name' => $this->msg( 'listfiles_name' )->text(),
'thumb' => $this->msg( 'listfiles_thumb' )->text(),
'img_size' => $this->msg( 'listfiles_size' )->text(),
- 'img_user_text' => $this->msg( 'listfiles_user' )->text(),
- 'img_description' => $this->msg( 'listfiles_description' )->text(),
);
- if ( !$wgMiserMode && !$this->mShowAll ) {
+ if ( is_null( $this->mUserName ) ) {
+ // Do not show username if filtering by username
+ $this->mFieldNames['img_user_text'] = $this->msg( 'listfiles_user' )->text();
+ }
+ // img_description down here, in order so that its still after the username field.
+ $this->mFieldNames['img_description'] = $this->msg( 'listfiles_description' )->text();
+
+ if ( !$this->getConfig()->get( 'MiserMode' ) && !$this->mShowAll ) {
$this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text();
}
if ( $this->mShowAll ) {
@@ -178,7 +185,6 @@ class ImageListPager extends TablePager {
}
function isFieldSortable( $field ) {
- global $wgMiserMode;
if ( $this->mIncluding ) {
return false;
}
@@ -190,14 +196,14 @@ class ImageListPager extends TablePager {
* In particular that means we cannot sort by timestamp when not filtering
* by user and including old images in the results. Which is sad.
*/
- if ( $wgMiserMode && !is_null( $this->mUserName ) ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) && !is_null( $this->mUserName ) ) {
// If we're sorting by user, the index only supports sorting by time.
if ( $field === 'img_timestamp' ) {
return true;
} else {
return false;
}
- } elseif ( $wgMiserMode && $this->mShowAll /* && mUserName === null */ ) {
+ } elseif ( $this->getConfig()->get( 'MiserMode' ) && $this->mShowAll /* && mUserName === null */ ) {
// no oi_timestamp index, so only alphabetical sorting in this case.
if ( $field === 'img_name' ) {
return true;
@@ -214,6 +220,7 @@ class ImageListPager extends TablePager {
// for two different tables, without reimplementing
// the pager class.
$qi = $this->getQueryInfoReal( $this->mTableName );
+
return $qi;
}
@@ -224,7 +231,7 @@ class ImageListPager extends TablePager {
*
* This is a bit hacky.
*
- * @param $table String Either 'image' or 'oldimage'
+ * @param string $table Either 'image' or 'oldimage'
* @return array Query info
*/
protected function getQueryInfoReal( $table ) {
@@ -240,7 +247,7 @@ class ImageListPager extends TablePager {
}
$field = $prefix . substr( $field, 3 ) . ' AS ' . $field;
}
- $fields[array_search('top', $fields)] = "'no' AS top";
+ $fields[array_search( 'top', $fields )] = "'no' AS top";
} else {
if ( $this->mShowAll ) {
$fields[array_search( 'top', $fields )] = "'yes' AS top";
@@ -289,11 +296,16 @@ class ImageListPager extends TablePager {
* @note $asc is named $descending in IndexPager base class. However
* it is true when the order is ascending, and false when the order
* is descending, so I renamed it to $asc here.
+ * @param int $offset
+ * @param int $limit
+ * @param bool $asc
+ * @return array
*/
function reallyDoQuery( $offset, $limit, $asc ) {
$prevTableName = $this->mTableName;
$this->mTableName = 'image';
- list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $asc );
+ list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
+ $this->buildQueryInfo( $offset, $limit, $asc );
$imageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
$this->mTableName = $prevTableName;
@@ -310,7 +322,8 @@ class ImageListPager extends TablePager {
}
$this->mIndexField = 'oi_' . substr( $this->mIndexField, 4 );
- list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $asc );
+ list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
+ $this->buildQueryInfo( $offset, $limit, $asc );
$oldimageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
$this->mTableName = $prevTableName;
@@ -324,10 +337,10 @@ class ImageListPager extends TablePager {
*
* Note: This will throw away some results
*
- * @param $res1 ResultWrapper
- * @param $res2 ResultWrapper
- * @param $limit int
- * @param $ascending boolean See note about $asc in $this->reallyDoQuery
+ * @param ResultWrapper $res1
+ * @param ResultWrapper $res2
+ * @param int $limit
+ * @param bool $ascending See note about $asc in $this->reallyDoQuery
* @return FakeResultWrapper $res1 and $res2 combined
*/
protected function combineResult( $res1, $res2, $limit, $ascending ) {
@@ -337,7 +350,7 @@ class ImageListPager extends TablePager {
$topRes2 = $res2->next();
$resultArray = array();
for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) {
- if ( strcmp( $topRes1->{ $this->mIndexField }, $topRes2->{ $this->mIndexField } ) > 0 ) {
+ if ( strcmp( $topRes1->{$this->mIndexField}, $topRes2->{$this->mIndexField} ) > 0 ) {
if ( !$ascending ) {
$resultArray[] = $topRes1;
$topRes1 = $res1->next();
@@ -355,20 +368,26 @@ class ImageListPager extends TablePager {
}
}
}
+
+ // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
for ( ; $i < $limit && $topRes1; $i++ ) {
+ // @codingStandardsIgnoreEnd
$resultArray[] = $topRes1;
$topRes1 = $res1->next();
}
+
+ // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
for ( ; $i < $limit && $topRes2; $i++ ) {
+ // @codingStandardsIgnoreEnd
$resultArray[] = $topRes2;
$topRes2 = $res2->next();
}
+
return new FakeResultWrapper( $resultArray );
}
function getDefaultSort() {
- global $wgMiserMode;
- if ( $this->mShowAll && $wgMiserMode && is_null( $this->mUserName ) ) {
+ if ( $this->mShowAll && $this->getConfig()->get( 'MiserMode' ) && is_null( $this->mUserName ) ) {
// Unfortunately no index on oi_timestamp.
return 'img_name';
} else {
@@ -386,6 +405,20 @@ class ImageListPager extends TablePager {
UserCache::singleton()->doQuery( $userIds, array( 'userpage' ), __METHOD__ );
}
+ /**
+ * @param string $field
+ * @param string $value
+ * @return Message|string|int The return type depends on the value of $field:
+ * - thumb: string
+ * - img_timestamp: string
+ * - img_name: string
+ * - img_user_text: string
+ * - img_size: string
+ * - img_description: string
+ * - count: int
+ * - top: Message
+ * @throws MWException
+ */
function formatValue( $field, $value ) {
switch ( $field ) {
case 'thumb':
@@ -394,6 +427,7 @@ class ImageListPager extends TablePager {
// If statement for paranoia
if ( $file ) {
$thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) );
+
return $thumb->toHtml( array( 'desc-link' => true ) );
} else {
return htmlspecialchars( $value );
@@ -420,6 +454,19 @@ class ImageListPager extends TablePager {
);
$download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
+ // Add delete links if allowed
+ // From https://github.com/Wikia/app/pull/3859
+ if ( $filePage->userCan( 'delete', $this->getUser() ) ) {
+ $deleteMsg = $this->msg( 'listfiles-delete' )->escaped();
+
+ $delete = Linker::linkKnown(
+ $filePage, $deleteMsg, array(), array( 'action' => 'delete' )
+ );
+ $delete = $this->msg( 'parentheses' )->rawParams( $delete )->escaped();
+
+ return "$link $download $delete";
+ }
+
return "$link $download";
} else {
return htmlspecialchars( $value );
@@ -445,58 +492,80 @@ class ImageListPager extends TablePager {
case 'top':
// Messages: listfiles-latestversion-yes, listfiles-latestversion-no
return $this->msg( 'listfiles-latestversion-' . $value );
+ default:
+ throw new MWException( "Unknown field '$field'" );
}
}
function getForm() {
- global $wgScript, $wgMiserMode;
- $inputForm = array();
- $inputForm['table_pager_limit_label'] = $this->getLimitSelect( array( 'tabindex' => 1 ) );
- if ( !$wgMiserMode ) {
- $inputForm['listfiles_search_for'] = Html::input(
- 'ilsearch',
- $this->mSearch,
- 'text',
- array(
- 'size' => '40',
- 'maxlength' => '255',
- 'id' => 'mw-ilsearch',
- 'tabindex' => 2,
- )
+ $fields = array();
+ $fields['limit'] = array(
+ 'type' => 'select',
+ 'name' => 'limit',
+ 'label-message' => 'table_pager_limit_label',
+ 'options' => $this->getLimitSelectList(),
+ 'default' => $this->mLimit,
+ );
+
+ if ( !$this->getConfig()->get( 'MiserMode' ) ) {
+ $fields['ilsearch'] = array(
+ 'type' => 'text',
+ 'name' => 'ilsearch',
+ 'id' => 'mw-ilsearch',
+ 'label-message' => 'listfiles_search_for',
+ 'default' => $this->mSearch,
+ 'size' => '40',
+ 'maxlength' => '255',
);
}
- $inputForm['username'] = Html::input( 'user', $this->mUserName, 'text', array(
+
+ $fields['user'] = array(
+ 'type' => 'text',
+ 'name' => 'user',
+ 'id' => 'mw-listfiles-user',
+ 'label-message' => 'username',
+ 'default' => $this->mUserName,
'size' => '40',
'maxlength' => '255',
- 'id' => 'mw-listfiles-user',
- 'tabindex' => 3,
- ) );
-
- $inputForm['listfiles-show-all'] = Html::input( 'ilshowall', 1, 'checkbox', array(
- 'checked' => $this->mShowAll,
- 'tabindex' => 4,
- ) );
- return Html::openElement( 'form',
- array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' )
- ) .
- Xml::fieldset( $this->msg( 'listfiles' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::buildForm( $inputForm, 'table_pager_limit_submit', array( 'tabindex' => 5 ) ) .
- $this->getHiddenFields( array( 'limit', 'ilsearch', 'user', 'title', 'ilshowall' ) ) .
- Html::closeElement( 'fieldset' ) .
- Html::closeElement( 'form' ) . "\n";
+ );
+
+ $fields['ilshowall'] = array(
+ 'type' => 'check',
+ 'name' => 'ilshowall',
+ 'id' => 'mw-listfiles-show-all',
+ 'label-message' => 'listfiles-show-all',
+ 'default' => $this->mShowAll,
+ );
+
+ $query = $this->getRequest()->getQueryValues();
+ unset( $query['title'] );
+ unset( $query['limit'] );
+ unset( $query['ilsearch'] );
+ unset( $query['user'] );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+
+ $form->setMethod( 'get' );
+ $form->setTitle( $this->getTitle() );
+ $form->setId( 'mw-listfiles-form' );
+ $form->setWrapperLegendMsg( 'listfiles' );
+ $form->setSubmitTextMsg( 'table_pager_limit_submit' );
+ $form->addHiddenFields( $query );
+
+ $form->prepareForm();
+ $form->displayForm( '' );
}
function getTableClass() {
- return 'listfiles ' . parent::getTableClass();
+ return parent::getTableClass() . ' listfiles';
}
function getNavClass() {
- return 'listfiles_nav ' . parent::getNavClass();
+ return parent::getNavClass() . ' listfiles_nav';
}
function getSortHeaderClass() {
- return 'listfiles_sort ' . parent::getSortHeaderClass();
+ return parent::getSortHeaderClass() . ' listfiles_sort';
}
function getPagingQueries() {
@@ -504,7 +573,9 @@ class ImageListPager extends TablePager {
if ( !is_null( $this->mUserName ) ) {
# Append the username to the query string
foreach ( $queries as &$query ) {
- $query['user'] = $this->mUserName;
+ if ( $query !== false ) {
+ $query['user'] = $this->mUserName;
+ }
}
}
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
index 82a4f70f..5bae28f0 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -29,21 +29,15 @@
* @author Petr Kadlec <mormegil@centrum.cz>
*/
class SpecialListGroupRights extends SpecialPage {
- /**
- * Constructor
- */
function __construct() {
parent::__construct( 'Listgrouprights' );
}
/**
* Show the special page
+ * @param string|null $par
*/
public function execute( $par ) {
- global $wgImplicitGroups;
- global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups;
- global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
-
$this->setHeaders();
$this->outputHeader();
@@ -60,19 +54,26 @@ class SpecialListGroupRights extends SpecialPage {
'</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( $wgGroupPermissions ),
- array_keys( $wgRevokePermissions ),
- array_keys( $wgAddGroups ),
- array_keys( $wgRemoveGroups ),
- array_keys( $wgGroupsAddToSelf ),
- array_keys( $wgGroupsRemoveFromSelf )
+ array_keys( $groupPermissions ),
+ array_keys( $revokePermissions ),
+ array_keys( $addGroups ),
+ array_keys( $removeGroups ),
+ array_keys( $groupsAddToSelf ),
+ array_keys( $groupsRemoveFromSelf )
) );
asort( $allGroups );
foreach ( $allGroups as $group ) {
- $permissions = isset( $wgGroupPermissions[$group] )
- ? $wgGroupPermissions[$group]
+ $permissions = isset( $groupPermissions[$group] )
+ ? $groupPermissions[$group]
: array();
$groupname = ( $group == '*' ) // Replace * with a more descriptive groupname
? 'all'
@@ -102,7 +103,7 @@ class SpecialListGroupRights extends SpecialPage {
SpecialPage::getTitleFor( 'Listusers' ),
$this->msg( 'listgrouprights-members' )->escaped()
);
- } elseif ( !in_array( $group, $wgImplicitGroups ) ) {
+ } elseif ( !in_array( $group, $config->get( 'ImplicitGroups' ) ) ) {
$grouplink = '<br />' . Linker::linkKnown(
SpecialPage::getTitleFor( 'Listusers' ),
$this->msg( 'listgrouprights-members' )->escaped(),
@@ -114,15 +115,16 @@ class SpecialListGroupRights extends SpecialPage {
$grouplink = '';
}
- $revoke = isset( $wgRevokePermissions[$group] ) ? $wgRevokePermissions[$group] : array();
- $addgroups = isset( $wgAddGroups[$group] ) ? $wgAddGroups[$group] : array();
- $removegroups = isset( $wgRemoveGroups[$group] ) ? $wgRemoveGroups[$group] : array();
- $addgroupsSelf = isset( $wgGroupsAddToSelf[$group] ) ? $wgGroupsAddToSelf[$group] : array();
- $removegroupsSelf = isset( $wgGroupsRemoveFromSelf[$group] ) ? $wgGroupsRemoveFromSelf[$group] : array();
+ $revoke = isset( $revokePermissions[$group] ) ? $revokePermissions[$group] : array();
+ $addgroups = isset( $addGroups[$group] ) ? $addGroups[$group] : array();
+ $removegroups = isset( $removeGroups[$group] ) ? $removeGroups[$group] : array();
+ $addgroupsSelf = isset( $groupsAddToSelf[$group] ) ? $groupsAddToSelf[$group] : array();
+ $removegroupsSelf = isset( $groupsRemoveFromSelf[$group] )
+ ? $groupsRemoveFromSelf[$group]
+ : array();
$id = $group == '*' ? false : Sanitizer::escapeId( $group );
- $out->addHTML( Html::rawElement( 'tr', array( 'id' => $id ),
- "
+ $out->addHTML( Html::rawElement( 'tr', array( 'id' => $id ), "
<td>$grouppage$grouplink</td>
<td>" .
$this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups,
@@ -132,17 +134,100 @@ class SpecialListGroupRights extends SpecialPage {
) );
}
$out->addHTML( Xml::closeElement( 'table' ) );
+ $this->outputNamespaceProtectionInfo();
+ }
+
+ private function outputNamespaceProtectionInfo() {
+ global $wgParser, $wgContLang;
+ $out = $this->getOutput();
+ $namespaceProtection = $this->getConfig()->get( 'NamespaceProtection' );
+
+ if ( count( $namespaceProtection ) == 0 ) {
+ return;
+ }
+
+ $header = $this->msg( 'listgrouprights-namespaceprotection-header' )->parse();
+ $out->addHTML(
+ Html::rawElement( 'h2', array(), Html::element( 'span', array(
+ 'class' => 'mw-headline',
+ 'id' => $wgParser->guessSectionNameFromWikiText( $header )
+ ), $header ) ) .
+ Xml::openElement( 'table', array( 'class' => 'wikitable' ) ) .
+ Html::element(
+ 'th',
+ array(),
+ $this->msg( 'listgrouprights-namespaceprotection-namespace' )->text()
+ ) .
+ Html::element(
+ 'th',
+ array(),
+ $this->msg( 'listgrouprights-namespaceprotection-restrictedto' )->text()
+ )
+ );
+
+ ksort( $namespaceProtection );
+ foreach ( $namespaceProtection as $namespace => $rights ) {
+ if ( !in_array( $namespace, MWNamespace::getValidNamespaces() ) ) {
+ continue;
+ }
+
+ if ( $namespace == NS_MAIN ) {
+ $namespaceText = $this->msg( 'blanknamespace' )->text();
+ } else {
+ $namespaceText = $wgContLang->convertNamespace( $namespace );
+ }
+
+ $out->addHTML(
+ Xml::openElement( 'tr' ) .
+ Html::rawElement(
+ 'td',
+ array(),
+ Linker::link(
+ SpecialPage::getTitleFor( 'Allpages' ),
+ $namespaceText,
+ array(),
+ array( 'namespace' => $namespace )
+ )
+ ) .
+ Xml::openElement( 'td' ) . Xml::openElement( 'ul' )
+ );
+
+ if ( !is_array( $rights ) ) {
+ $rights = array( $rights );
+ }
+
+ foreach ( $rights as $right ) {
+ $out->addHTML(
+ Html::rawElement( 'li', array(), $this->msg(
+ 'listgrouprights-right-display',
+ User::getRightDescription( $right ),
+ Html::element(
+ 'span',
+ array( '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 of permission => bool (from $wgGroupPermissions items)
- * @param array $revoke of permission => bool (from $wgRevokePermissions items)
- * @param array $add of groups this group is allowed to add or true
- * @param array $remove of groups this group is allowed to remove or true
- * @param array $addSelf of groups this group is allowed to add to self or true
- * @param array $removeSelf of group this group is allowed to remove from self or true
+ * @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 List of all granted permissions, separated by comma separator
*/
private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
@@ -170,45 +255,54 @@ class SpecialListGroupRights extends SpecialPage {
sort( $r );
$lang = $this->getLanguage();
+ $allGroups = User::getAllGroups();
if ( $add === true ) {
$r[] = $this->msg( 'listgrouprights-addgroup-all' )->escaped();
- } elseif ( is_array( $add ) && count( $add ) ) {
- $add = array_values( array_unique( $add ) );
- $r[] = $this->msg( 'listgrouprights-addgroup',
- $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ),
- count( $add )
- )->parse();
+ } elseif ( is_array( $add ) ) {
+ $add = array_intersect( array_values( array_unique( $add ) ), $allGroups );
+ if ( count( $add ) ) {
+ $r[] = $this->msg( 'listgrouprights-addgroup',
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ),
+ count( $add )
+ )->parse();
+ }
}
if ( $remove === true ) {
$r[] = $this->msg( 'listgrouprights-removegroup-all' )->escaped();
- } elseif ( is_array( $remove ) && count( $remove ) ) {
- $remove = array_values( array_unique( $remove ) );
- $r[] = $this->msg( 'listgrouprights-removegroup',
- $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ),
- count( $remove )
- )->parse();
+ } elseif ( is_array( $remove ) ) {
+ $remove = array_intersect( array_values( array_unique( $remove ) ), $allGroups );
+ if ( count( $remove ) ) {
+ $r[] = $this->msg( 'listgrouprights-removegroup',
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ),
+ count( $remove )
+ )->parse();
+ }
}
if ( $addSelf === true ) {
$r[] = $this->msg( 'listgrouprights-addgroup-self-all' )->escaped();
- } elseif ( is_array( $addSelf ) && count( $addSelf ) ) {
- $addSelf = array_values( array_unique( $addSelf ) );
- $r[] = $this->msg( 'listgrouprights-addgroup-self',
- $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ),
- count( $addSelf )
- )->parse();
+ } elseif ( is_array( $addSelf ) ) {
+ $addSelf = array_intersect( array_values( array_unique( $addSelf ) ), $allGroups );
+ if ( count( $addSelf ) ) {
+ $r[] = $this->msg( 'listgrouprights-addgroup-self',
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ),
+ count( $addSelf )
+ )->parse();
+ }
}
if ( $removeSelf === true ) {
$r[] = $this->msg( 'listgrouprights-removegroup-self-all' )->parse();
- } elseif ( is_array( $removeSelf ) && count( $removeSelf ) ) {
- $removeSelf = array_values( array_unique( $removeSelf ) );
- $r[] = $this->msg( 'listgrouprights-removegroup-self',
- $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ),
- count( $removeSelf )
- )->parse();
+ } elseif ( is_array( $removeSelf ) ) {
+ $removeSelf = array_intersect( array_values( array_unique( $removeSelf ) ), $allGroups );
+ if ( count( $removeSelf ) ) {
+ $r[] = $this->msg( 'listgrouprights-removegroup-self',
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ),
+ count( $removeSelf )
+ )->parse();
+ }
}
if ( empty( $r ) ) {
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index 2c8792ff..de05be41 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -76,20 +76,19 @@ class ListredirectsPage extends QueryPage {
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
- $batch = new LinkBatch;
+ if ( !$res->numRows() ) {
+ return;
+ }
+ $batch = new LinkBatch;
foreach ( $res as $row ) {
$batch->add( $row->namespace, $row->title );
$batch->addObj( $this->getRedirectTarget( $row ) );
}
-
$batch->execute();
// Back to start for display
- if ( $res->numRows() > 0 ) {
- // If there are no rows we get an error seeking.
- $db->dataSeek( $res, 0 );
- }
+ $res->seek( 0 );
}
protected function getRedirectTarget( $row ) {
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index 8cd9173c..dad9074d 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -35,9 +35,9 @@
class UsersPager extends AlphabeticPager {
/**
- * @param $context IContextSource
+ * @param IContextSource $context
* @param array $par (Default null)
- * @param $including boolean Whether this page is being transcluded in
+ * @param bool $including Whether this page is being transcluded in
* another page
*/
function __construct( IContextSource $context = null, $par = null, $including = null ) {
@@ -69,7 +69,9 @@ class UsersPager extends AlphabeticPager {
$this->editsOnly = $request->getBool( 'editsOnly' );
$this->creationSort = $request->getBool( 'creationSort' );
$this->including = $including;
- $this->mDefaultDirection = $request->getBool( 'desc' );
+ $this->mDefaultDirection = $request->getBool( 'desc' )
+ ? IndexPager::DIR_DESCENDING
+ : IndexPager::DIR_ASCENDING;
$this->requestedUser = '';
@@ -92,7 +94,7 @@ class UsersPager extends AlphabeticPager {
}
/**
- * @return Array
+ * @return array
*/
function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
@@ -154,8 +156,8 @@ class UsersPager extends AlphabeticPager {
}
/**
- * @param $row Object
- * @return String
+ * @param stdClass $row
+ * @return string
*/
function formatRow( $row ) {
if ( $row->user_id == 0 ) { #Bug 16487
@@ -191,12 +193,9 @@ class UsersPager extends AlphabeticPager {
}
$edits = '';
- global $wgEdititis;
- if ( !$this->including && $wgEdititis ) {
- // @fixme i18n issue: Hardcoded square brackets.
- $edits = ' [' .
- $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped() .
- ']';
+ if ( !$this->including && $this->getConfig()->get( 'Edititis' ) ) {
+ $count = $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped();
+ $edits = $this->msg( 'word-separator' )->escaped() . $this->msg( 'brackets', $count )->escaped();
}
$created = '';
@@ -232,14 +231,12 @@ class UsersPager extends AlphabeticPager {
* @return string
*/
function getPageHeader() {
- global $wgScript;
-
list( $self ) = explode( '/', $this->getTitle()->getPrefixedDBkey() );
# Form tag
$out = Xml::openElement(
'form',
- array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' )
+ array( 'method' => 'get', 'action' => wfScript(), 'id' => 'mw-listusers-form' )
) .
Xml::fieldset( $this->msg( 'listusers' )->text() ) .
Html::hidden( 'title', $self );
@@ -333,7 +330,7 @@ class UsersPager extends AlphabeticPager {
/**
* Get a list of groups the specified user belongs to
*
- * @param $uid Integer: user id
+ * @param int $uid User id
* @return array
*/
protected static function getGroups( $uid ) {
@@ -346,7 +343,7 @@ class UsersPager extends AlphabeticPager {
/**
* Format a link to a group description page
*
- * @param string $group group name
+ * @param string $group Group name
* @param string $username Username
* @return string
*/
@@ -399,7 +396,41 @@ class SpecialListUsers extends IncludableSpecialPage {
$this->getOutput()->addHTML( $s );
}
+ /**
+ * 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
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ $subpages = User::getAllGroups();
+ return self::prefixSearchArray( $search, $limit, $subpages );
+ }
+
protected function getGroupName() {
return 'users';
}
}
+
+/**
+ * Redirect page: Special:ListAdmins --> Special:ListUsers/sysop.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialListAdmins extends SpecialRedirectToSpecial {
+ function __construct() {
+ parent::__construct( 'Listadmins', 'Listusers', 'sysop' );
+ }
+}
+
+/**
+ * Redirect page: Special:ListBots --> Special:ListUsers/bot.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialListBots extends SpecialRedirectToSpecial {
+ function __construct() {
+ parent::__construct( 'Listbots', 'Listusers', 'bot' );
+ }
+}
diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php
index 95ef9510..1c1f1250 100644
--- a/includes/specials/SpecialLockdb.php
+++ b/includes/specials/SpecialLockdb.php
@@ -27,7 +27,7 @@
* @ingroup SpecialPage
*/
class SpecialLockdb extends FormSpecialPage {
- var $reason = '';
+ protected $reason = '';
public function __construct() {
parent::__construct( 'Lockdb', 'siteadmin' );
@@ -38,11 +38,9 @@ class SpecialLockdb extends FormSpecialPage {
}
public function checkExecutePermissions( User $user ) {
- global $wgReadOnlyFile;
-
parent::checkExecutePermissions( $user );
# If the lock file isn't writable, we can do sweet bugger all
- if ( !is_writable( dirname( $wgReadOnlyFile ) ) ) {
+ if ( !is_writable( dirname( $this->getConfig()->get( 'ReadOnlyFile' ) ) ) ) {
throw new ErrorPageError( 'lockdb', 'lockfilenotwritable' );
}
}
@@ -69,14 +67,14 @@ class SpecialLockdb extends FormSpecialPage {
}
public function onSubmit( array $data ) {
- global $wgContLang, $wgReadOnlyFile;
+ global $wgContLang;
if ( !$data['Confirm'] ) {
return Status::newFatal( 'locknoconfirm' );
}
wfSuppressWarnings();
- $fp = fopen( $wgReadOnlyFile, 'w' );
+ $fp = fopen( $this->getConfig()->get( 'ReadOnlyFile' ), 'w' );
wfRestoreWarnings();
if ( false === $fp ) {
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index 2ffdd89d..dc33801d 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -45,8 +45,6 @@ class SpecialLog extends SpecialPage {
}
public function execute( $par ) {
- global $wgLogRestrictions;
-
$this->setHeaders();
$this->outputHeader();
@@ -77,11 +75,14 @@ class SpecialLog extends SpecialPage {
// If the user doesn't have the right permission to view the specific
// log type, throw a PermissionsError
// If the log type is invalid, just show all public logs
+ $logRestrictions = $this->getConfig()->get( 'LogRestrictions' );
$type = $opts->getValue( 'type' );
if ( !LogPage::isLogType( $type ) ) {
$opts->setValue( 'type', '' );
- } elseif ( isset( $wgLogRestrictions[$type] ) && !$this->getUser()->isAllowed( $wgLogRestrictions[$type] ) ) {
- throw new PermissionsError( $wgLogRestrictions[$type] );
+ } elseif ( isset( $logRestrictions[$type] )
+ && !$this->getUser()->isAllowed( $logRestrictions[$type] )
+ ) {
+ throw new PermissionsError( $logRestrictions[$type] );
}
# Handle type-specific inputs
@@ -98,6 +99,7 @@ class SpecialLog extends SpecialPage {
# Some log types are only for a 'User:' title but we might have been given
# only the username instead of the full title 'User:username'. This part try
# to lookup for a user by that name and eventually fix user input. See bug 1697.
+ wfRunHooks( 'GetLogTypesOnUser', array( &$this->typeOnUser ) );
if ( in_array( $opts->getValue( 'type' ), $this->typeOnUser ) ) {
# ok we have a type of log which expect a user title.
$target = Title::newFromText( $opts->getValue( 'page' ) );
@@ -112,14 +114,26 @@ class SpecialLog extends SpecialPage {
$this->show( $opts, $qc );
}
- private function parseParams( FormOptions $opts, $par ) {
- global $wgLogTypes;
+ /**
+ * 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
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ $subpages = $this->getConfig()->get( 'LogTypes' );
+ $subpages[] = 'all';
+ sort( $subpages );
+ return self::prefixSearchArray( $search, $limit, $subpages );
+ }
+ private function parseParams( FormOptions $opts, $par ) {
# Get parameters
$parms = explode( '/', ( $par = ( $par !== null ) ? $par : '' ) );
$symsForAll = array( '*', 'all' );
if ( $parms[0] != '' &&
- ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) )
+ ( in_array( $par, $this->getConfig()->get( 'LogTypes' ) ) || in_array( $par, $symsForAll ) )
) {
$opts->setValue( 'type', $par );
} elseif ( count( $parms ) == 2 ) {
@@ -193,10 +207,9 @@ class SpecialLog extends SpecialPage {
}
# Show button to hide log entries
- global $wgScript;
$s = Html::openElement(
'form',
- array( 'action' => $wgScript, 'id' => 'mw-log-deleterevision-submit' )
+ array( 'action' => wfScript(), 'id' => 'mw-log-deleterevision-submit' )
) . "\n";
$s .= Html::hidden( 'title', SpecialPage::getTitleFor( 'Revisiondelete' ) ) . "\n";
$s .= Html::hidden( 'target', SpecialPage::getTitleFor( 'Log' ) ) . "\n";
@@ -217,7 +230,7 @@ class SpecialLog extends SpecialPage {
/**
* Set page title and show header for this log type
- * @param $type string
+ * @param string $type
* @since 1.19
*/
protected function addHeader( $type ) {
diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php
index 7c7771d7..f533234f 100644
--- a/includes/specials/SpecialLonelypages.php
+++ b/includes/specials/SpecialLonelypages.php
@@ -49,36 +49,40 @@ class LonelyPagesPage extends PageQueryPage {
}
function getQueryInfo() {
- return array(
- 'tables' => array(
- 'page', 'pagelinks',
- 'templatelinks'
+ $tables = array( 'page', 'pagelinks', 'templatelinks' );
+ $conds = array(
+ 'pl_namespace IS NULL',
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0,
+ 'tl_namespace IS NULL'
+ );
+ $joinConds = array(
+ 'pagelinks' => array(
+ 'LEFT JOIN', array(
+ 'pl_namespace = page_namespace',
+ 'pl_title = page_title'
+ )
),
+ 'templatelinks' => array(
+ 'LEFT JOIN', array(
+ 'tl_namespace = page_namespace',
+ 'tl_title = page_title'
+ )
+ )
+ );
+
+ // Allow extensions to modify the query
+ wfRunHooks( 'LonelyPagesQuery', array( &$tables, &$conds, &$joinConds ) );
+
+ return array(
+ 'tables' => $tables,
'fields' => array(
'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'page_title'
),
- 'conds' => array(
- 'pl_namespace IS NULL',
- 'page_namespace' => MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0,
- 'tl_namespace IS NULL'
- ),
- 'join_conds' => array(
- 'pagelinks' => array(
- 'LEFT JOIN', array(
- 'pl_namespace = page_namespace',
- 'pl_title = page_title'
- )
- ),
- 'templatelinks' => array(
- 'LEFT JOIN', array(
- 'tl_namespace = page_namespace',
- 'tl_title = page_title'
- )
- )
- )
+ 'conds' => $conds,
+ 'join_conds' => $joinConds
);
}
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index 3eeae310..60225ea5 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -28,7 +28,7 @@
* @ingroup SpecialPage
*/
class MIMEsearchPage extends QueryPage {
- protected $major, $minor;
+ protected $major, $minor, $mime;
function __construct( $name = 'MIMEsearch' ) {
parent::__construct( $name );
@@ -51,6 +51,11 @@ class MIMEsearchPage extends QueryPage {
}
public function getQueryInfo() {
+ $minorType = array();
+ if ( $this->minor !== '*' ) {
+ // Allow wildcard searching
+ $minorType['img_minor_mime'] = $this->minor;
+ }
$qi = array(
'tables' => array( 'image' ),
'fields' => array(
@@ -67,7 +72,6 @@ class MIMEsearchPage extends QueryPage {
),
'conds' => array(
'img_major_mime' => $this->major,
- 'img_minor_mime' => $this->minor,
// This is in order to trigger using
// the img_media_mime index in "range" mode.
'img_media_type' => array(
@@ -82,8 +86,9 @@ class MIMEsearchPage extends QueryPage {
MEDIATYPE_EXECUTABLE,
MEDIATYPE_ARCHIVE,
),
- ),
+ ) + $minorType,
);
+
return $qi;
}
@@ -94,38 +99,42 @@ class MIMEsearchPage extends QueryPage {
* that this report gives results in a logical order). As an aditional
* note, mysql seems to by default order things by img_name ASC, which
* is what we ideally want, so everything works out fine anyhow.
+ * @return array
*/
function getOrderFields() {
return array();
}
- function execute( $par ) {
- global $wgScript;
-
- $mime = $par ? $par : $this->getRequest()->getText( 'mime' );
+ /**
+ * Return HTML to put just before the results.
+ */
+ function getPageHeader() {
- $this->setHeaders();
- $this->outputHeader();
- $this->getOutput()->addHTML(
- Xml::openElement(
+ return Xml::openElement(
'form',
- array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgScript )
+ array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => wfScript() )
) .
- Xml::openElement( 'fieldset' ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) .
- Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $mime ) .
- ' ' .
- Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
- );
+ Xml::openElement( 'fieldset' ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
+ Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) .
+ Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $this->mime ) .
+ ' ' .
+ Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' );
+ }
- list( $this->major, $this->minor ) = File::splitMime( $mime );
+ function execute( $par ) {
+ $this->mime = $par ? $par : $this->getRequest()->getText( 'mime' );
+ $this->mime = trim( $this->mime );
+ list( $this->major, $this->minor ) = File::splitMime( $this->mime );
if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
!self::isValidType( $this->major )
) {
+ $this->setHeaders();
+ $this->outputHeader();
+ $this->getOutput()->addHTML( $this->getPageHeader() );
return;
}
@@ -165,7 +174,7 @@ class MIMEsearchPage extends QueryPage {
}
/**
- * @param $type string
+ * @param string $type
* @return bool
*/
protected static function isValidType( $type ) {
@@ -179,7 +188,8 @@ class MIMEsearchPage extends QueryPage {
'video',
'message',
'model',
- 'multipart'
+ 'multipart',
+ 'chemical'
);
return in_array( $type, $types );
diff --git a/includes/specials/SpecialMediaStatistics.php b/includes/specials/SpecialMediaStatistics.php
new file mode 100644
index 00000000..681c332f
--- /dev/null
+++ b/includes/specials/SpecialMediaStatistics.php
@@ -0,0 +1,325 @@
+<?php
+/**
+ * Implements Special:MediaStatistics
+ *
+ * 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
+ * @author Brian Wolff
+ */
+
+/**
+ * @ingroup SpecialPage
+ */
+class MediaStatisticsPage extends QueryPage {
+ protected $totalCount = 0, $totalBytes = 0;
+
+ function __construct( $name = 'MediaStatistics' ) {
+ parent::__construct( $name );
+ // Generally speaking there is only a small number of file types,
+ // so just show all of them.
+ $this->limit = 5000;
+ $this->shownavigation = false;
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ /**
+ * Query to do.
+ *
+ * This abuses the query cache table by storing mime types as "titles".
+ *
+ * This will store entries like [[Media:BITMAP;image/jpeg;200;20000]]
+ * where the form is Media type;mime type;count;bytes.
+ *
+ * This relies on the behaviour that when value is tied, the order things
+ * come out of querycache table is the order they went in. Which is hacky.
+ * However, other special pages like Special:Deadendpages and
+ * Special:BrokenRedirects also rely on this.
+ */
+ public function getQueryInfo() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $fakeTitle = $dbr->buildConcat( array(
+ 'img_media_type',
+ $dbr->addQuotes( ';' ),
+ 'img_major_mime',
+ $dbr->addQuotes( '/' ),
+ 'img_minor_mime',
+ $dbr->addQuotes( ';' ),
+ 'COUNT(*)',
+ $dbr->addQuotes( ';' ),
+ 'SUM( img_size )'
+ ) );
+ return array(
+ 'tables' => array( 'image' ),
+ 'fields' => array(
+ 'title' => $fakeTitle,
+ 'namespace' => NS_MEDIA, /* needs to be something */
+ 'value' => '1'
+ ),
+ 'options' => array(
+ 'GROUP BY' => array(
+ 'img_media_type',
+ 'img_major_mime',
+ 'img_minor_mime',
+ )
+ )
+ );
+ }
+
+ /**
+ * How to sort the results
+ *
+ * It's important that img_media_type come first, otherwise the
+ * tables will be fragmented.
+ * @return Array Fields to sort by
+ */
+ function getOrderFields() {
+ return array( 'img_media_type', 'count(*)', 'img_major_mime', 'img_minor_mime' );
+ }
+
+ /**
+ * Output the results of the query.
+ *
+ * @param $out OutputPage
+ * @param $skin Skin (deprecated presumably)
+ * @param $dbr DatabaseBase
+ * @param $res ResultWrapper Results from query
+ * @param $num integer Number of results
+ * @param $offset integer Paging offset (Should always be 0 in our case)
+ */
+ protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
+ $prevMediaType = null;
+ foreach ( $res as $row ) {
+ list( $mediaType, $mime, $totalCount, $totalBytes ) = $this->splitFakeTitle( $row->title );
+ if ( $prevMediaType !== $mediaType ) {
+ if ( $prevMediaType !== null ) {
+ // We're not at beginning, so we have to
+ // close the previous table.
+ $this->outputTableEnd();
+ }
+ $this->outputMediaType( $mediaType );
+ $this->outputTableStart( $mediaType );
+ $prevMediaType = $mediaType;
+ }
+ $this->outputTableRow( $mime, intval( $totalCount ), intval( $totalBytes ) );
+ }
+ if ( $prevMediaType !== null ) {
+ $this->outputTableEnd();
+ }
+ }
+
+ /**
+ * Output closing </table>
+ */
+ protected function outputTableEnd() {
+ $this->getOutput()->addHtml( Html::closeElement( 'table' ) );
+ }
+
+ /**
+ * Output a row of the stats table
+ *
+ * @param $mime String mime type (e.g. image/jpeg)
+ * @param $count integer Number of images of this type
+ * @param $totalBytes integer Total space for images of this type
+ */
+ protected function outputTableRow( $mime, $count, $bytes ) {
+ $mimeSearch = SpecialPage::getTitleFor( 'MIMEsearch', $mime );
+ $row = Html::rawElement(
+ 'td',
+ array(),
+ Linker::link( $mimeSearch, htmlspecialchars( $mime ) )
+ );
+ $row .= Html::element(
+ 'td',
+ array(),
+ $this->getExtensionList( $mime )
+ );
+ $row .= Html::rawElement(
+ 'td',
+ array(),
+ $this->msg( 'mediastatistics-nfiles' )
+ ->numParams( $count )
+ /** @todo Check to be sure this really should have number formatting */
+ ->numParams( $this->makePercentPretty( $count / $this->totalCount ) )
+ ->parse()
+ );
+ $row .= Html::rawElement(
+ 'td',
+ // Make sure js sorts it in numeric order
+ array( 'data-sort-value' => $bytes ),
+ $this->msg( 'mediastatistics-nbytes' )
+ ->numParams( $bytes )
+ ->sizeParams( $bytes )
+ /** @todo Check to be sure this really should have number formatting */
+ ->numParams( $this->makePercentPretty( $bytes / $this->totalBytes ) )
+ ->parse()
+ );
+
+ $this->getOutput()->addHTML( Html::rawElement( 'tr', array(), $row ) );
+ }
+
+ /**
+ * @param float $decimal A decimal percentage (ie for 12.3%, this would be 0.123)
+ * @return String The percentage formatted so that 3 significant digits are shown.
+ */
+ protected function makePercentPretty( $decimal ) {
+ $decimal *= 100;
+ // Always show three useful digits
+ if ( $decimal == 0 ) {
+ return '0';
+ }
+ $percent = sprintf( "%." . max( 0, 2 - floor( log10( $decimal ) ) ) . "f", $decimal );
+ // Then remove any trailing 0's
+ return preg_replace( '/\.?0*$/', '', $percent );
+ }
+
+ /**
+ * Given a mime type, return a comma separated list of allowed extensions.
+ *
+ * @param $mime String mime type
+ * @return String Comma separated list of allowed extensions (e.g. ".ogg, .oga")
+ */
+ private function getExtensionList( $mime ) {
+ $exts = MimeMagic::singleton()->getExtensionsForType( $mime );
+ if ( $exts === null ) {
+ return '';
+ }
+ $extArray = explode( ' ', $exts );
+ $extArray = array_unique( $extArray );
+ foreach ( $extArray as &$ext ) {
+ $ext = '.' . $ext;
+ }
+
+ return $this->getLanguage()->commaList( $extArray );
+ }
+
+ /**
+ * Output the start of the table
+ *
+ * Including opening <table>, and first <tr> with column headers.
+ */
+ protected function outputTableStart( $mediaType ) {
+ $this->getOutput()->addHTML(
+ Html::openElement(
+ 'table',
+ array( 'class' => array(
+ 'mw-mediastats-table',
+ 'mw-mediastats-table-' . strtolower( $mediaType ),
+ 'sortable',
+ 'wikitable'
+ ))
+ )
+ );
+ $this->getOutput()->addHTML( $this->getTableHeaderRow() );
+ }
+
+ /**
+ * Get (not output) the header row for the table
+ *
+ * @return String the header row of the able
+ */
+ protected function getTableHeaderRow() {
+ $headers = array( 'mimetype', 'extensions', 'count', 'totalbytes' );
+ $ths = '';
+ foreach ( $headers as $header ) {
+ $ths .= Html::rawElement(
+ 'th',
+ array(),
+ // for grep:
+ // mediastatistics-table-mimetype, mediastatistics-table-extensions
+ // tatistics-table-count, mediastatistics-table-totalbytes
+ $this->msg( 'mediastatistics-table-' . $header )->parse()
+ );
+ }
+ return Html::rawElement( 'tr', array(), $ths );
+ }
+
+ /**
+ * Output a header for a new media type section
+ *
+ * @param $mediaType string A media type (e.g. from the MEDIATYPE_xxx constants)
+ */
+ protected function outputMediaType( $mediaType ) {
+ $this->getOutput()->addHTML(
+ Html::element(
+ 'h2',
+ array( 'class' => array(
+ 'mw-mediastats-mediatype',
+ 'mw-mediastats-mediatype-' . strtolower( $mediaType )
+ )),
+ // for grep
+ // mediastatistics-header-unknown, mediastatistics-header-bitmap,
+ // mediastatistics-header-drawing, mediastatistics-header-audio,
+ // mediastatistics-header-video, mediastatistics-header-multimedia,
+ // mediastatistics-header-office, mediastatistics-header-text,
+ // mediastatistics-header-executable, mediastatistics-header-archive,
+ $this->msg( 'mediastatistics-header-' . strtolower( $mediaType ) )->text()
+ )
+ );
+ /** @todo Possibly could add a message here explaining what the different types are.
+ * not sure if it is needed though.
+ */
+ }
+
+ /**
+ * parse the fake title format that this special page abuses querycache with.
+ *
+ * @param $fakeTitle String A string formatted as <media type>;<mime type>;<count>;<bytes>
+ * @return Array The constituant parts of $fakeTitle
+ */
+ private function splitFakeTitle( $fakeTitle ) {
+ return explode( ';', $fakeTitle, 4 );
+ }
+
+ /**
+ * What group to put the page in
+ * @return string
+ */
+ protected function getGroupName() {
+ return 'media';
+ }
+
+ /**
+ * This method isn't used, since we override outputResults, but
+ * we need to implement since abstract in parent class.
+ *
+ * @param $skin Skin
+ * @param $result stdObject Result row
+ */
+ public function formatResult( $skin, $result ) {
+ throw new MWException( "unimplemented" );
+ }
+
+ /**
+ * Initialize total values so we can figure out percentages later.
+ *
+ * @param $dbr DatabaseBase
+ * @param $res ResultWrapper
+ */
+ public function preprocessResults( $dbr, $res ) {
+ $this->totalCount = $this->totalBytes = 0;
+ foreach ( $res as $row ) {
+ list( , , $count, $bytes ) = $this->splitFakeTitle( $row->title );
+ $this->totalCount += $count;
+ $this->totalBytes += $bytes;
+ }
+ $res->seek( 0 );
+ }
+}
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index fb5ea657..43f5a1ba 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -28,12 +28,38 @@
* @ingroup SpecialPage
*/
class SpecialMergeHistory extends SpecialPage {
- var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment;
+ /** @var string */
+ protected $mAction;
- /**
- * @var Title
- */
- var $mTargetObj, $mDestObj;
+ /** @var string */
+ protected $mTarget;
+
+ /** @var string */
+ protected $mDest;
+
+ /** @var string */
+ protected $mTimestamp;
+
+ /** @var int */
+ protected $mTargetID;
+
+ /** @var int */
+ protected $mDestID;
+
+ /** @var string */
+ protected $mComment;
+
+ /** @var bool Was posted? */
+ protected $mMerge;
+
+ /** @var bool Was submitted? */
+ protected $mSubmitted;
+
+ /** @var Title */
+ protected $mTargetObj;
+
+ /** @var Title */
+ protected $mDestObj;
public function __construct() {
parent::__construct( 'MergeHistory', 'mergehistory' );
@@ -57,7 +83,9 @@ class SpecialMergeHistory extends SpecialPage {
}
$this->mComment = $request->getText( 'wpComment' );
- $this->mMerge = $request->wasPosted() && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
+ $this->mMerge = $request->wasPosted()
+ && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
+
// target page
if ( $this->mSubmitted ) {
$this->mTargetObj = Title::newFromURL( $this->mTarget );
@@ -105,7 +133,7 @@ class SpecialMergeHistory extends SpecialPage {
if ( !$this->mTargetObj instanceof Title ) {
$errors[] = $this->msg( 'mergehistory-invalid-source' )->parseAsBlock();
} elseif ( !$this->mTargetObj->exists() ) {
- $errors[] = $this->msg( 'mergehistory-no-source', array( 'parse' ),
+ $errors[] = $this->msg( 'mergehistory-no-source',
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() )
)->parseAsBlock();
}
@@ -113,7 +141,7 @@ class SpecialMergeHistory extends SpecialPage {
if ( !$this->mDestObj instanceof Title ) {
$errors[] = $this->msg( 'mergehistory-invalid-destination' )->parseAsBlock();
} elseif ( !$this->mDestObj->exists() ) {
- $errors[] = $this->msg( 'mergehistory-no-destination', array( 'parse' ),
+ $errors[] = $this->msg( 'mergehistory-no-destination',
wfEscapeWikiText( $this->mDestObj->getPrefixedText() )
)->parseAsBlock();
}
@@ -131,18 +159,16 @@ class SpecialMergeHistory extends SpecialPage {
}
function showMergeForm() {
- global $wgScript;
-
$this->getOutput()->addWikiMsg( 'mergehistory-header' );
$this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'get',
- 'action' => $wgScript ) ) .
+ 'action' => wfScript() ) ) .
'<fieldset>' .
Xml::element( 'legend', array(),
$this->msg( 'mergehistory-box' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
Html::hidden( 'submitted', '1' ) .
Html::hidden( 'mergepoint', $this->mTimestamp ) .
Xml::openElement( 'table' ) .
@@ -171,7 +197,7 @@ class SpecialMergeHistory extends SpecialPage {
$haveRevisions = $revisions && $revisions->getNumRows() > 0;
$out = $this->getOutput();
- $titleObj = $this->getTitle();
+ $titleObj = $this->getPageTitle();
$action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
$top = Xml::openElement(
@@ -203,7 +229,10 @@ class SpecialMergeHistory extends SpecialPage {
<tr>
<td>&#160;</td>
<td class="mw-submit">' .
- Xml::submitButton( $this->msg( 'mergehistory-submit' )->text(), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) .
+ Xml::submitButton(
+ $this->msg( 'mergehistory-submit' )->text(),
+ array( 'name' => 'merge', 'id' => 'mw-merge-submit' )
+ ) .
'</td>
</tr>' .
Xml::closeElement( 'table' ) .
@@ -252,7 +281,7 @@ class SpecialMergeHistory extends SpecialPage {
$last = $this->message['last'];
$ts = wfTimestamp( TS_MW, $row->rev_timestamp );
- $checkBox = Xml::radio( 'mergepoint', $ts, false );
+ $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mTimestamp === $ts ) );
$user = $this->getUser();
@@ -290,9 +319,22 @@ class SpecialMergeHistory extends SpecialPage {
$comment = Linker::revComment( $rev );
return Html::rawElement( 'li', array(),
- $this->msg( 'mergehistory-revisionrow' )->rawParams( $checkBox, $last, $pageLink, $userLink, $stxt, $comment )->escaped() );
+ $this->msg( 'mergehistory-revisionrow' )
+ ->rawParams( $checkBox, $last, $pageLink, $userLink, $stxt, $comment )->escaped() );
}
+ /**
+ * Actually attempt the history move
+ *
+ * @todo if all versions of page A are moved to B and then a user
+ * tries to do a reverse-merge via the "unmerge" log link, then page
+ * A will still be a redirect (as it was after the original merge),
+ * though it will have the old revisions back from before (as expected).
+ * The user may have to "undo" the redirect manually to finish the "unmerge".
+ * Maybe this should delete redirects at the target page of merges?
+ *
+ * @return bool Success
+ */
function merge() {
# Get the titles directly from the IDs, in case the target page params
# were spoofed. The queries are done based on the IDs, so it's best to
@@ -336,7 +378,7 @@ class SpecialMergeHistory extends SpecialPage {
return false;
}
- # Update the revisions
+ # Get the timestamp pivot condition
if ( $this->mTimestamp ) {
$timewhere = "rev_timestamp <= {$this->mTimestamp}";
$timestampLimit = wfTimestamp( TS_MW, $this->mTimestamp );
@@ -344,6 +386,18 @@ class SpecialMergeHistory extends SpecialPage {
$timewhere = "rev_timestamp <= {$maxtimestamp}";
$timestampLimit = wfTimestamp( TS_MW, $lasttimestamp );
}
+ # Check that there are not too many revisions to move
+ $limit = 5000; // avoid too much slave lag
+ $count = $dbw->selectRowCount( 'revision', '1',
+ array( 'rev_page' => $this->mTargetID, $timewhere ),
+ __METHOD__,
+ array( 'LIMIT' => $limit + 1 )
+ );
+ if ( $count > $limit ) {
+ $this->getOutput()->addWikiMsg( 'mergehistory-fail-toobig' );
+
+ return false;
+ }
# Do the moving...
$dbw->update(
'revision',
@@ -396,6 +450,7 @@ class SpecialMergeHistory extends SpecialPage {
$dbw->insert( 'pagelinks',
array(
'pl_from' => $this->mDestID,
+ 'pl_from_namespace' => $destTitle->getNamespace(),
'pl_namespace' => $destTitle->getNamespace(),
'pl_title' => $destTitle->getDBkey() ),
__METHOD__
@@ -420,8 +475,10 @@ class SpecialMergeHistory extends SpecialPage {
array( $destTitle->getPrefixedText(), $timestampLimit ), $this->getUser()
);
- $this->getOutput()->addWikiMsg( 'mergehistory-success',
- $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count );
+ # @todo message should use redirect=no
+ $this->getOutput()->addWikiText( $this->msg( 'mergehistory-success',
+ $targetTitle->getPrefixedText(), $destTitle->getPrefixedText() )->numParams(
+ $count )->text() );
wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) );
@@ -434,9 +491,13 @@ class SpecialMergeHistory extends SpecialPage {
}
class MergeHistoryPager extends ReverseChronologicalPager {
- public $mForm, $mConds;
+ /** @var IContextSource */
+ public $mForm;
+
+ /** @var array */
+ public $mConds;
- function __construct( $form, $conds = array(), $source, $dest ) {
+ function __construct( $form, $conds, $source, $dest ) {
$this->mForm = $form;
$this->mConds = $conds;
$this->title = $source;
@@ -490,7 +551,7 @@ class MergeHistoryPager extends ReverseChronologicalPager {
function getQueryInfo() {
$conds = $this->mConds;
$conds['rev_page'] = $this->articleID;
- $conds[] = "rev_timestamp < {$this->maxTimestamp}";
+ $conds[] = "rev_timestamp < " . $this->mDb->addQuotes( $this->maxTimestamp );
return array(
'tables' => array( 'revision', 'page', 'user' ),
diff --git a/includes/specials/SpecialMostinterwikis.php b/includes/specials/SpecialMostinterwikis.php
index 98dd68e9..30ccbe5a 100644
--- a/includes/specials/SpecialMostinterwikis.php
+++ b/includes/specials/SpecialMostinterwikis.php
@@ -92,8 +92,8 @@ class MostinterwikisPage extends QueryPage {
}
/**
- * @param $skin Skin
- * @param $result
+ * @param Skin $skin
+ * @param object $result
* @return string
*/
function formatResult( $skin, $result ) {
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index 37593bf9..99f0ecf5 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -93,9 +93,9 @@ class MostlinkedPage extends QueryPage {
/**
* Make a link to "what links here" for the specified title
*
- * @param $title Title being queried
- * @param string $caption text to display on the link
- * @return String
+ * @param Title $title Title being queried
+ * @param string $caption Text to display on the link
+ * @return string
*/
function makeWlhLink( $title, $caption ) {
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index 0d4641b1..f61a1158 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -44,6 +44,7 @@ class MostlinkedCategoriesPage extends QueryPage {
'fields' => array( 'title' => 'cat_title',
'namespace' => NS_CATEGORY,
'value' => 'cat_pages' ),
+ 'conds' => array( 'cat_pages > 0' ),
);
}
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index c90acb1f..8e6a596d 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -36,7 +36,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Is this report expensive, i.e should it be cached?
*
- * @return Boolean
+ * @return bool
*/
public function isExpensive() {
return true;
@@ -45,7 +45,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Is there a feed available?
*
- * @return Boolean
+ * @return bool
*/
public function isSyndicated() {
return false;
@@ -54,7 +54,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Sort the results in descending order?
*
- * @return Boolean
+ * @return bool
*/
public function sortDescending() {
return true;
@@ -68,7 +68,6 @@ class MostlinkedTemplatesPage extends QueryPage {
'title' => 'tl_title',
'value' => 'COUNT(*)'
),
- 'conds' => array( 'tl_namespace' => NS_TEMPLATE ),
'options' => array( 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) )
);
}
@@ -76,7 +75,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Pre-cache page existence to speed up link generation
*
- * @param $db DatabaseBase connection
+ * @param DatabaseBase $db
* @param ResultWrapper $res
*/
public function preprocessResults( $db, $res ) {
@@ -125,7 +124,7 @@ class MostlinkedTemplatesPage extends QueryPage {
*
* @param Title $title Title to make the link for
* @param object $result Result row
- * @return String
+ * @return string
*/
private function makeWlhLink( $title, $result ) {
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php
index ad6b788d..0471cafe 100644
--- a/includes/specials/SpecialMostrevisions.php
+++ b/includes/specials/SpecialMostrevisions.php
@@ -23,6 +23,7 @@
* @ingroup SpecialPage
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
*/
+
class MostrevisionsPage extends FewestrevisionsPage {
function __construct( $name = 'Mostrevisions' ) {
parent::__construct( $name );
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index 253e6cc3..ec9593f7 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -27,15 +27,35 @@
* @ingroup SpecialPage
*/
class MovePageForm extends UnlistedSpecialPage {
- /**
- * Objects
- * @var Title
- */
- var $oldTitle, $newTitle;
- // Text input
- var $reason;
+ /** @var Title */
+ protected $oldTitle;
+
+ /** @var Title */
+ protected $newTitle;
+
+
+ /** @var string Text input */
+ protected $reason;
+
// Checks
- var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared;
+
+ /** @var bool */
+ protected $moveTalk;
+
+ /** @var bool */
+ protected $deleteAndMove;
+
+ /** @var bool */
+ protected $moveSubpages;
+
+ /** @var bool */
+ protected $fixRedirects;
+
+ /** @var bool */
+ protected $leaveRedirect;
+
+ /** @var bool */
+ protected $moveOverShared;
private $watch = false;
@@ -106,12 +126,12 @@ class MovePageForm extends UnlistedSpecialPage {
/**
* Show the form
*
- * @param array $err error messages. Each item is an error message.
+ * @param array $err Error messages. Each item is an error message.
* It may either be a string message name or array message name and
* parameters, like the second argument to OutputPage::wrapWikiMsg().
*/
function showForm( $err ) {
- global $wgContLang, $wgFixDoubleRedirects, $wgMaximumMovedPages;
+ global $wgContLang;
$this->getSkin()->setRelevantTitle( $this->oldTitle );
@@ -163,9 +183,14 @@ class MovePageForm extends UnlistedSpecialPage {
"<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>",
'moveuserpage-warning'
);
+ } elseif ( $this->oldTitle->getNamespace() == NS_CATEGORY ) {
+ $out->wrapWikiMsg(
+ "<div class=\"error mw-movecategorypage-warning\">\n$1\n</div>",
+ 'movecategorypage-warning'
+ );
}
- $out->addWikiMsg( $wgFixDoubleRedirects ?
+ $out->addWikiMsg( $this->getConfig()->get( 'FixDoubleRedirects' ) ?
'movepagetext' :
'movepagetext-noredirectfixer'
);
@@ -196,7 +221,7 @@ class MovePageForm extends UnlistedSpecialPage {
|| ( $oldTitleTalkSubpages && $canMoveSubpage ) );
$dbr = wfGetDB( DB_SLAVE );
- if ( $wgFixDoubleRedirects ) {
+ if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
$hasRedirects = $dbr->selectField( 'redirect', '1',
array(
'rd_namespace' => $this->oldTitle->getNamespace(),
@@ -281,7 +306,7 @@ class MovePageForm extends UnlistedSpecialPage {
'form',
array(
'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( 'action=submit' ),
+ 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
'id' => 'movepage'
)
) .
@@ -351,7 +376,16 @@ class MovePageForm extends UnlistedSpecialPage {
);
}
- if ( $user->isAllowed( 'suppressredirect' ) && $handler->supportsRedirects() ) {
+ if ( $user->isAllowed( 'suppressredirect' ) ) {
+ if ( $handler->supportsRedirects() ) {
+ $isChecked = $this->leaveRedirect;
+ $options = array();
+ } else {
+ $isChecked = false;
+ $options = array(
+ 'disabled' => 'disabled'
+ );
+ }
$out->addHTML( "
<tr>
<td></td>
@@ -360,7 +394,8 @@ class MovePageForm extends UnlistedSpecialPage {
$this->msg( 'move-leave-redirect' )->text(),
'wpLeaveRedirect',
'wpLeaveRedirect',
- $this->leaveRedirect
+ $isChecked,
+ $options
) .
"</td>
</tr>"
@@ -384,6 +419,7 @@ class MovePageForm extends UnlistedSpecialPage {
}
if ( $canMoveSubpage ) {
+ $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
$out->addHTML( "
<tr>
<td></td>
@@ -400,7 +436,7 @@ class MovePageForm extends UnlistedSpecialPage {
( $this->oldTitle->hasSubpages()
? 'move-subpages'
: 'move-talk-subpages' )
- )->numParams( $wgMaximumMovedPages )->params( $wgMaximumMovedPages )->parse()
+ )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
) .
"</td>
</tr>"
@@ -445,8 +481,6 @@ class MovePageForm extends UnlistedSpecialPage {
}
function doSubmit() {
- global $wgMaximumMovedPages, $wgFixDoubleRedirects;
-
$user = $this->getUser();
if ( $user->pingLimiter( 'move' ) ) {
@@ -457,7 +491,7 @@ class MovePageForm extends UnlistedSpecialPage {
$nt = $this->newTitle;
# don't allow moving to pages with # in
- if ( !$nt || $nt->getFragment() != '' ) {
+ if ( !$nt || $nt->hasFragment() ) {
$this->showForm( array( array( 'badtitletext' ) ) );
return;
@@ -522,7 +556,7 @@ class MovePageForm extends UnlistedSpecialPage {
return;
}
- if ( $wgFixDoubleRedirects && $this->fixRedirects ) {
+ if ( $this->getConfig()->get( 'FixDoubleRedirects' ) && $this->fixRedirects ) {
DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
}
@@ -532,10 +566,14 @@ class MovePageForm extends UnlistedSpecialPage {
$oldLink = Linker::link(
$ot,
null,
- array(),
+ array( 'id' => 'movepage-oldlink' ),
array( 'redirect' => 'no' )
);
- $newLink = Linker::linkKnown( $nt );
+ $newLink = Linker::linkKnown(
+ $nt,
+ null,
+ array( 'id' => 'movepage-newlink' )
+ );
$oldText = $ot->getPrefixedText();
$newText = $nt->getPrefixedText();
@@ -583,8 +621,8 @@ class MovePageForm extends UnlistedSpecialPage {
$dbr = wfGetDB( DB_MASTER );
if ( $this->moveSubpages && (
MWNamespace::hasSubpages( $nt->getNamespace() ) || (
- $this->moveTalk &&
- MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
+ $this->moveTalk
+ && MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
)
) ) {
$conds = array(
@@ -670,17 +708,21 @@ class MovePageForm extends UnlistedSpecialPage {
);
$newLink = Linker::linkKnown( $newSubpage );
- $extraOutput[] = $this->msg( 'movepage-page-moved' )->rawParams( $oldLink, $newLink )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-page-moved' )
+ ->rawParams( $oldLink, $newLink )->escaped();
++$count;
- if ( $count >= $wgMaximumMovedPages ) {
- $extraOutput[] = $this->msg( 'movepage-max-pages' )->numParams( $wgMaximumMovedPages )->escaped();
+ $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
+ if ( $count >= $maximumMovedPages ) {
+ $extraOutput[] = $this->msg( 'movepage-max-pages' )
+ ->numParams( $maximumMovedPages )->escaped();
break;
}
} else {
$oldLink = Linker::linkKnown( $oldSubpage );
$newLink = Linker::link( $newSubpage );
- $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-page-unmoved' )
+ ->rawParams( $oldLink, $newLink )->escaped();
}
}
}
diff --git a/includes/specials/SpecialMyLanguage.php b/includes/specials/SpecialMyLanguage.php
new file mode 100644
index 00000000..71b18930
--- /dev/null
+++ b/includes/specials/SpecialMyLanguage.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Implements Special:MyLanguage
+ *
+ * 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
+ * @author Siebrand Mazeland
+ * @copyright Copyright © 2010-2013 Niklas Laxström, Siebrand Mazeland
+ */
+
+/**
+ * Unlisted special page just to redirect the user to the translated version of
+ * a page, if it exists.
+ *
+ * Usage: [[Special:MyLanguage/Page name|link text]]
+ *
+ * @since 1.24
+ * @ingroup SpecialPage
+ */
+class SpecialMyLanguage extends RedirectSpecialArticle {
+ public function __construct() {
+ parent::__construct( 'MyLanguage' );
+ }
+
+ /**
+ * If the special page is a redirect, then get the Title object it redirects to.
+ * False otherwise.
+ *
+ * @param string $par Subpage string
+ * @return Title|bool
+ */
+ public function getRedirect( $par ) {
+ $title = $this->findTitle( $par );
+ // Go to the main page if given invalid title.
+ if ( !$title ) {
+ $title = Title::newMainPage();
+ }
+ return $title;
+ }
+
+ /**
+ * Assuming the user's interface language is fi. Given input Page, it
+ * returns Page/fi if it exists, otherwise Page. Given input Page/de,
+ * it returns Page/fi if it exists, otherwise Page/de if it exists,
+ * otherwise Page.
+ *
+ * @param string $par
+ * @return Title|null
+ */
+ public function findTitle( $par ) {
+ // base = title without language code suffix
+ // provided = the title as it was given
+ $base = $provided = Title::newFromText( $par );
+
+ if ( $base && strpos( $par, '/' ) !== false ) {
+ $pos = strrpos( $par, '/' );
+ $basepage = substr( $par, 0, $pos );
+ $code = substr( $par, $pos + 1 );
+ if ( strlen( $code ) && Language::isKnownLanguageTag( $code ) ) {
+ $base = Title::newFromText( $basepage );
+ }
+ }
+
+ if ( !$base ) {
+ return null;
+ }
+
+ $uiCode = $this->getLanguage()->getCode();
+ $proposed = $base->getSubpage( $uiCode );
+ if ( $uiCode !== $this->getConfig()->get( 'LanguageCode' ) && $proposed && $proposed->exists() ) {
+ return $proposed;
+ } elseif ( $provided && $provided->exists() ) {
+ return $provided;
+ } else {
+ return $base;
+ }
+ }
+}
diff --git a/includes/specials/SpecialMyRedirectPages.php b/includes/specials/SpecialMyRedirectPages.php
new file mode 100644
index 00000000..9b8d52bb
--- /dev/null
+++ b/includes/specials/SpecialMyRedirectPages.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Special pages that are used to get user independent links pointing to
+ * current user's pages (user page, talk page, contributions, etc.).
+ * This can let us cache a single copy of some generated content for all
+ * users or be linked in wikitext help pages.
+ *
+ * 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 pointing to current user's user page.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMypage extends RedirectSpecialArticle {
+ function __construct() {
+ parent::__construct( 'Mypage' );
+ }
+
+ function getRedirect( $subpage ) {
+ if ( strval( $subpage ) !== '' ) {
+ return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
+ } else {
+ return Title::makeTitle( NS_USER, $this->getUser()->getName() );
+ }
+ }
+}
+
+/**
+ * Special page pointing to current user's talk page.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMytalk extends RedirectSpecialArticle {
+ function __construct() {
+ parent::__construct( 'Mytalk' );
+ }
+
+ function getRedirect( $subpage ) {
+ if ( strval( $subpage ) !== '' ) {
+ return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
+ } else {
+ return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() );
+ }
+ }
+}
+
+/**
+ * Special page pointing to current user's contributions.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMycontributions extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'Mycontributions' );
+ $this->mAllowedRedirectParams = array( 'limit', 'namespace', 'tagfilter',
+ 'offset', 'dir', 'year', 'month', 'feed' );
+ }
+
+ function getRedirect( $subpage ) {
+ return SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() );
+ }
+}
+
+/**
+ * Special page pointing to current user's uploaded files.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMyuploads extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'Myuploads' );
+ $this->mAllowedRedirectParams = array( 'limit', 'ilshowall', 'ilsearch' );
+ }
+
+ function getRedirect( $subpage ) {
+ return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
+ }
+}
+
+/**
+ * Special page pointing to current user's uploaded files (including old versions).
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialAllMyUploads extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'AllMyUploads' );
+ $this->mAllowedRedirectParams = array( 'limit', 'ilsearch' );
+ }
+
+ function getRedirect( $subpage ) {
+ $this->mAddedRedirectParams['ilshowall'] = 1;
+
+ return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
+ }
+}
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index 37d29734..546c1914 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -20,6 +20,7 @@
* @file
* @ingroup SpecialPage
*/
+
class SpecialNewFiles extends IncludableSpecialPage {
public function __construct() {
parent::__construct( 'Newimages' );
@@ -32,6 +33,7 @@ class SpecialNewFiles extends IncludableSpecialPage {
$pager = new NewFilesPager( $this->getContext(), $par );
if ( !$this->including() ) {
+ $this->setTopText();
$form = $pager->getForm();
$form->prepareForm();
$form->displayForm( '' );
@@ -46,6 +48,25 @@ class SpecialNewFiles extends IncludableSpecialPage {
protected function getGroupName() {
return 'changes';
}
+
+ /**
+ * Send the text to be displayed above the options
+ */
+ function setTopText() {
+ global $wgContLang;
+
+ $message = $this->msg( 'newimagestext' )->inContentLanguage();
+ if ( !$message->isDisabled() ) {
+ $this->getOutput()->addWikiText(
+ Html::rawElement( 'p',
+ array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
+ "\n" . $message->plain() . "\n"
+ ),
+ /* $lineStart */ false,
+ /* $interface */ false
+ );
+ }
+ }
}
/**
@@ -55,7 +76,7 @@ class NewFilesPager extends ReverseChronologicalPager {
/**
* @var ImageGallery
*/
- var $gallery;
+ protected $gallery;
function __construct( IContextSource $context, $par = null ) {
$this->like = $context->getRequest()->getText( 'like' );
@@ -68,7 +89,6 @@ class NewFilesPager extends ReverseChronologicalPager {
}
function getQueryInfo() {
- global $wgMiserMode;
$conds = $jconds = array();
$tables = array( 'image' );
@@ -88,7 +108,7 @@ class NewFilesPager extends ReverseChronologicalPager {
}
}
- if ( !$wgMiserMode && $this->like !== null ) {
+ if ( !$this->getConfig()->get( 'MiserMode' ) && $this->like !== null ) {
$dbr = wfGetDB( DB_SLAVE );
$likeObj = Title::newFromURL( $this->like );
if ( $likeObj instanceof Title ) {
@@ -120,12 +140,11 @@ class NewFilesPager extends ReverseChronologicalPager {
// Note that null for mode is taken to mean use default.
$mode = $this->getRequest()->getVal( 'gallerymode', null );
try {
- $this->gallery = ImageGalleryBase::factory( $mode );
+ $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
} catch ( MWException $e ) {
// User specified something invalid, fallback to default.
- $this->gallery = ImageGalleryBase::factory();
+ $this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
}
- $this->gallery->setContext( $this->getContext() );
}
return '';
@@ -152,8 +171,6 @@ class NewFilesPager extends ReverseChronologicalPager {
}
function getForm() {
- global $wgMiserMode;
-
$fields = array(
'like' => array(
'type' => 'text',
@@ -162,7 +179,7 @@ class NewFilesPager extends ReverseChronologicalPager {
),
'showbots' => array(
'type' => 'check',
- 'label' => $this->msg( 'showhidebots', $this->msg( 'show' )->plain() )->escaped(),
+ 'label-message' => 'newimages-showbots',
'name' => 'showbots',
),
'limit' => array(
@@ -177,7 +194,7 @@ class NewFilesPager extends ReverseChronologicalPager {
),
);
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
unset( $fields['like'] );
}
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index 43d48558..0b70bb7e 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -27,15 +27,12 @@
* @ingroup SpecialPage
*/
class SpecialNewpages extends IncludableSpecialPage {
- // Stored objects
-
/**
* @var FormOptions
*/
protected $opts;
protected $customFilters;
- // Some internal settings
protected $showNavigation = false;
public function __construct() {
@@ -43,8 +40,6 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function setup( $par ) {
- global $wgEnableNewpagesUserFilter;
-
// Options
$opts = new FormOptions();
$this->opts = $opts; // bind
@@ -74,9 +69,6 @@ class SpecialNewpages extends IncludableSpecialPage {
// Validate
$opts->validateIntBounds( 'limit', 0, 5000 );
- if ( !$wgEnableNewpagesUserFilter ) {
- $opts->setValue( 'username', '' );
- }
}
protected function parseParams( $par ) {
@@ -124,8 +116,7 @@ class SpecialNewpages extends IncludableSpecialPage {
/**
* Show a form for filtering namespace and username
*
- * @param $par String
- * @return String
+ * @param string $par
*/
public function execute( $par ) {
$out = $this->getOutput();
@@ -194,7 +185,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$changed = $this->opts->getChangedValues();
unset( $changed['offset'] ); // Reset offset if query type changes
- $self = $this->getTitle();
+ $self = $this->getPageTitle();
foreach ( $filters as $key => $msg ) {
$onoff = 1 - $this->opts->getValue( $key );
$link = Linker::link( $self, $showhide[$onoff], array(),
@@ -207,8 +198,6 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function form() {
- global $wgEnableNewpagesUserFilter, $wgScript;
-
// Consume values
$this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
$namespace = $this->opts->consumeValue( 'namespace' );
@@ -232,8 +221,8 @@ class SpecialNewpages extends IncludableSpecialPage {
list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter;
}
- $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ $form = Xml::openElement( 'form', array( 'action' => wfScript() ) ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
Xml::fieldset( $this->msg( 'newpages' )->text() ) .
Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) .
'<tr>
@@ -268,15 +257,14 @@ class SpecialNewpages extends IncludableSpecialPage {
$tagFilterSelector .
'</td>
</tr>' ) : '' ) .
- ( $wgEnableNewpagesUserFilter ?
- '<tr>
+ '<tr>
<td class="mw-label">' .
Xml::label( $this->msg( 'newpages-username' )->text(), 'mw-np-username' ) .
'</td>
<td class="mw-input">' .
Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) .
'</td>
- </tr>' : '' ) .
+ </tr>' .
'<tr> <td></td>
<td class="mw-submit">' .
Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
@@ -301,7 +289,7 @@ class SpecialNewpages extends IncludableSpecialPage {
* size, user links, and a comment
*
* @param object $result Result row
- * @return String
+ * @return string
*/
public function formatRow( $result ) {
$title = Title::newFromRow( $result );
@@ -394,14 +382,15 @@ class SpecialNewpages extends IncludableSpecialPage {
$oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped();
}
- return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n";
+ return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} "
+ . "{$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n";
}
/**
* Should a specific result row provide "patrollable" links?
*
* @param object $result Result row
- * @return Boolean
+ * @return bool
*/
protected function patrollable( $result ) {
return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled );
@@ -410,32 +399,31 @@ class SpecialNewpages extends IncludableSpecialPage {
/**
* Output a subscription feed listing recent edits to this page.
*
- * @param $type String
+ * @param string $type
*/
protected function feed( $type ) {
- global $wgFeed, $wgFeedClasses, $wgFeedLimit;
-
- if ( !$wgFeed ) {
+ if ( !$this->getConfig()->get( 'Feed' ) ) {
$this->getOutput()->addWikiMsg( 'feed-unavailable' );
return;
}
- if ( !isset( $wgFeedClasses[$type] ) ) {
+ $feedClasses = $this->getConfig()->get( 'FeedClasses' );
+ if ( !isset( $feedClasses[$type] ) ) {
$this->getOutput()->addWikiMsg( 'feed-invalid' );
return;
}
- $feed = new $wgFeedClasses[$type](
+ $feed = new $feedClasses[$type](
$this->feedTitle(),
$this->msg( 'tagline' )->text(),
- $this->getTitle()->getFullURL()
+ $this->getPageTitle()->getFullURL()
);
$pager = new NewPagesPager( $this, $this->opts );
$limit = $this->opts->getValue( 'limit' );
- $pager->mLimit = min( $limit, $wgFeedLimit );
+ $pager->mLimit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) );
$feed->outHeader();
if ( $pager->getNumRows() > 0 ) {
@@ -447,10 +435,11 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function feedTitle() {
- global $wgLanguageCode, $wgSitename;
$desc = $this->getDescription();
+ $code = $this->getConfig()->get( 'LanguageCode' );
+ $sitename = $this->getConfig()->get( 'Sitename' );
- return "$wgSitename - $desc [$wgLanguageCode]";
+ return "$sitename - $desc [$code]";
}
protected function feedItem( $row ) {
@@ -514,7 +503,6 @@ class NewPagesPager extends ReverseChronologicalPager {
}
function getQueryInfo() {
- global $wgEnableNewpagesUserFilter;
$conds = array();
$conds['rc_new'] = 1;
@@ -524,19 +512,17 @@ class NewPagesPager extends ReverseChronologicalPager {
$username = $this->opts->getValue( 'username' );
$user = Title::makeTitleSafe( NS_USER, $username );
+ $rcIndexes = array();
+
if ( $namespace !== false ) {
if ( $this->opts->getValue( 'invert' ) ) {
$conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace );
} else {
$conds['rc_namespace'] = $namespace;
}
- $rcIndexes = array( 'new_name_timestamp' );
- } else {
- $rcIndexes = array( 'rc_timestamp' );
}
- # $wgEnableNewpagesUserFilter - temp WMF hack
- if ( $wgEnableNewpagesUserFilter && $user ) {
+ if ( $user ) {
$conds['rc_user_text'] = $user->getText();
$rcIndexes = 'rc_user_text';
} elseif ( User::groupHasPermission( '*', 'createpage' ) &&
@@ -572,11 +558,17 @@ class NewPagesPager extends ReverseChronologicalPager {
wfRunHooks( 'SpecialNewpagesConditions',
array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) );
+ $options = array();
+
+ if ( $rcIndexes ) {
+ $options = array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) );
+ }
+
$info = array(
'tables' => $tables,
'fields' => $fields,
'conds' => $conds,
- 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ),
+ 'options' => $options,
'join_conds' => $join_conds
);
diff --git a/includes/specials/SpecialPageLanguage.php b/includes/specials/SpecialPageLanguage.php
new file mode 100644
index 00000000..2acf23cd
--- /dev/null
+++ b/includes/specials/SpecialPageLanguage.php
@@ -0,0 +1,195 @@
+<?php
+/**
+ * Implements Special:PageLanguage
+ *
+ * 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
+ * @author Kunal Grover
+ * @since 1.24
+ */
+
+/**
+ * Special page for changing the content language of a page
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPageLanguage extends FormSpecialPage {
+ /**
+ * @var string URL to go to if language change successful
+ */
+ private $goToUrl;
+
+ public function __construct() {
+ parent::__construct( 'PageLanguage', 'pagelang' );
+ }
+
+ protected function preText() {
+ $this->getOutput()->addModules( 'mediawiki.special.pageLanguage' );
+ }
+
+ protected function getFormFields() {
+ // Get default from the subpage of Special page
+ $defaultName = $this->par;
+
+ $page = array();
+ $page['pagename'] = array(
+ 'type' => 'text',
+ 'label-message' => 'pagelang-name',
+ 'default' => $defaultName,
+ );
+
+ // Options for whether to use the default language or select language
+ $selectoptions = array(
+ (string)$this->msg( 'pagelang-use-default' )->escaped() => 1,
+ (string)$this->msg( 'pagelang-select-lang' )->escaped() => 2,
+ );
+ $page['selectoptions'] = array(
+ 'id' => 'mw-pl-options',
+ 'type' => 'radio',
+ 'options' => $selectoptions,
+ 'default' => 1
+ );
+
+ // Building a language selector
+ $userLang = $this->getLanguage()->getCode();
+ $languages = Language::fetchLanguageNames( $userLang, 'mwfile' );
+ ksort( $languages );
+ $options = array();
+ foreach ( $languages as $code => $name ) {
+ $options["$code - $name"] = $code;
+ }
+
+ $page['language'] = array(
+ 'id' => 'mw-pl-languageselector',
+ 'cssclass' => 'mw-languageselector',
+ 'type' => 'select',
+ 'options' => $options,
+ 'label-message' => 'pagelang-language',
+ 'default' => $this->getConfig()->get( 'LanguageCode' ),
+ );
+
+ return $page;
+ }
+
+ protected function postText() {
+ return $this->showLogFragment( $this->par );
+ }
+
+ public function alterForm( HTMLForm $form ) {
+ $form->setDisplayFormat( 'vform' );
+ $form->setWrapperLegend( false );
+ wfRunHooks( 'LanguageSelector', array( $this->getOutput(), 'mw-languageselector' ) );
+ }
+
+ /**
+ *
+ * @param array $data
+ * @return bool
+ */
+ public function onSubmit( array $data ) {
+ $title = Title::newFromText( $data['pagename'] );
+
+ // Check if title is valid
+ if ( !$title ) {
+ return false;
+ }
+
+ // Get the default language for the wiki
+ // Returns the default since the page is not loaded from DB
+ $defLang = $title->getPageLanguage()->getCode();
+
+ $pageId = $title->getArticleID();
+
+ // Check if article exists
+ if ( !$pageId ) {
+ return false;
+ }
+
+ // Load the page language from DB
+ $dbw = wfGetDB( DB_MASTER );
+ $langOld = $dbw->selectField(
+ 'page',
+ 'page_lang',
+ array( 'page_id' => $pageId ),
+ __METHOD__
+ );
+
+ // Url to redirect to after the operation
+ $this->goToUrl = $title->getFullURL();
+
+ // Check if user wants to use default language
+ if ( $data['selectoptions'] == 1 ) {
+ $langNew = null;
+ } else {
+ $langNew = $data['language'];
+ }
+
+ // No change in language
+ if ( $langNew === $langOld ) {
+ return false;
+ }
+
+ // Hardcoded [def] if the language is set to null
+ $logOld = $langOld ? $langOld : $defLang . '[def]';
+ $logNew = $langNew ? $langNew : $defLang . '[def]';
+
+ // Writing new page language to database
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update(
+ 'page',
+ array( 'page_lang' => $langNew ),
+ array(
+ 'page_id' => $pageId,
+ 'page_lang' => $langOld
+ ),
+ __METHOD__
+ );
+
+ if ( !$dbw->affectedRows() ) {
+ return false;
+ }
+
+ // Logging change of language
+ $logParams = array(
+ '4::oldlanguage' => $logOld,
+ '5::newlanguage' => $logNew
+ );
+ $entry = new ManualLogEntry( 'pagelang', 'pagelang' );
+ $entry->setPerformer( $this->getUser() );
+ $entry->setTarget( $title );
+ $entry->setParameters( $logParams );
+
+ $logid = $entry->insert();
+ $entry->publish( $logid );
+
+ return true;
+ }
+
+ public function onSuccess() {
+ // Success causes a redirect
+ $this->getOutput()->redirect( $this->goToUrl );
+ }
+
+ function showLogFragment( $title ) {
+ $moveLogPage = new LogPage( 'pagelang' );
+ $out1 = Xml::element( 'h2', null, $moveLogPage->getName()->text() );
+ $out2 = '';
+ LogEventsList::showLogExtract( $out2, 'pagelang', $title );
+ return $out1 . $out2;
+ }
+}
diff --git a/includes/specials/SpecialPagesWithProp.php b/includes/specials/SpecialPagesWithProp.php
index e22b42a3..f5b19cc6 100644
--- a/includes/specials/SpecialPagesWithProp.php
+++ b/includes/specials/SpecialPagesWithProp.php
@@ -30,6 +30,7 @@
*/
class SpecialPagesWithProp extends QueryPage {
private $propName = null;
+ private $existingPropNames = null;
function __construct( $name = 'PagesWithProp' ) {
parent::__construct( $name );
@@ -47,18 +48,7 @@ class SpecialPagesWithProp extends QueryPage {
$request = $this->getRequest();
$propname = $request->getVal( 'propname', $par );
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select(
- 'page_props',
- 'pp_propname',
- '',
- __METHOD__,
- array( 'DISTINCT', 'ORDER BY' => 'pp_propname' )
- );
- $propnames = array();
- foreach ( $res as $row ) {
- $propnames[$row->pp_propname] = $row->pp_propname;
- }
+ $propnames = $this->getExistingPropNames();
$form = new HTMLForm( array(
'propname' => array(
@@ -89,6 +79,18 @@ class SpecialPagesWithProp extends QueryPage {
}
/**
+ * 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
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ $subpages = array_keys( $this->getExistingPropNames() );
+ return self::prefixSearchArray( $search, $limit, $subpages );
+ }
+
+ /**
* Disable RSS/Atom feeds
* @return bool
*/
@@ -150,6 +152,25 @@ class SpecialPagesWithProp extends QueryPage {
return $ret;
}
+ public function getExistingPropNames() {
+ if ( $this->existingPropNames === null ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ 'page_props',
+ 'pp_propname',
+ '',
+ __METHOD__,
+ array( 'DISTINCT', 'ORDER BY' => 'pp_propname' )
+ );
+ $propnames = array();
+ foreach ( $res as $row ) {
+ $propnames[$row->pp_propname] = $row->pp_propname;
+ }
+ $this->existingPropNames = $propnames;
+ }
+ return $this->existingPropNames;
+ }
+
protected function getGroupName() {
return 'pages';
}
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
index d9faacca..3061c85b 100644
--- a/includes/specials/SpecialPasswordReset.php
+++ b/includes/specials/SpecialPasswordReset.php
@@ -62,9 +62,10 @@ class SpecialPasswordReset extends FormSpecialPage {
}
protected function getFormFields() {
- global $wgPasswordResetRoutes, $wgAuth;
+ global $wgAuth;
+ $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
$a = array();
- if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
+ if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
$a['Username'] = array(
'type' => 'text',
'label-message' => 'passwordreset-username',
@@ -75,14 +76,14 @@ class SpecialPasswordReset extends FormSpecialPage {
}
}
- if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
+ if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
$a['Email'] = array(
'type' => 'email',
'label-message' => 'passwordreset-email',
);
}
- if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
+ if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
$domains = $wgAuth->domainList();
$a['Domain'] = array(
'type' => 'select',
@@ -103,7 +104,7 @@ class SpecialPasswordReset extends FormSpecialPage {
}
public function alterForm( HTMLForm $form ) {
- global $wgPasswordResetRoutes;
+ $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
$form->setDisplayFormat( 'vform' );
// Turn the old-school line around the form off.
@@ -112,14 +113,16 @@ class SpecialPasswordReset extends FormSpecialPage {
// from a FormSpecialPage class.
$form->setWrapperLegend( false );
+ $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
$i = 0;
- if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
+ if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
$i++;
}
- if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
+ if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
$i++;
}
- if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
+ if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
$i++;
}
@@ -133,10 +136,10 @@ class SpecialPasswordReset extends FormSpecialPage {
* Process the form. At this point we know that the user passes all the criteria in
* userCanExecute(), and if the data array contains 'Username', etc, then Username
* resets are allowed.
- * @param $data array
+ * @param array $data
* @throws MWException
* @throws ThrottledError|PermissionsError
- * @return Bool|Array
+ * @return bool|array
*/
public function onSubmit( array $data ) {
global $wgAuth;
@@ -220,19 +223,16 @@ class SpecialPasswordReset extends FormSpecialPage {
// Check against password throttle
foreach ( $users as $user ) {
if ( $user->isPasswordReminderThrottled() ) {
- global $wgPasswordReminderResendTime;
# Round the time in hours to 3 d.p., in case someone is specifying
# minutes or seconds.
return array( array(
'throttled-mailpassword',
- round( $wgPasswordReminderResendTime, 3 )
+ round( $this->getConfig()->get( 'PasswordReminderResendTime' ), 3 )
) );
}
}
- global $wgNewPasswordExpiry;
-
// All the users will have the same email address
if ( $firstUser->getEmail() == '' ) {
// This won't be reachable from the email route, so safe to expose the username
@@ -271,12 +271,12 @@ class SpecialPasswordReset extends FormSpecialPage {
$passwordBlock,
count( $passwords ),
'<' . Title::newMainPage()->getCanonicalURL() . '>',
- round( $wgNewPasswordExpiry / 86400 )
+ round( $this->getConfig()->get( 'NewPasswordExpiry' ) / 86400 )
);
$title = $this->msg( 'passwordreset-emailtitle' );
- $this->result = $firstUser->sendMail( $title->escaped(), $this->email->text() );
+ $this->result = $firstUser->sendMail( $title->text(), $this->email->text() );
if ( isset( $data['Capture'] ) && $data['Capture'] ) {
// Save the user, will be used if an error occurs when sending the email
@@ -318,11 +318,12 @@ class SpecialPasswordReset extends FormSpecialPage {
}
protected function canChangePassword( User $user ) {
- global $wgPasswordResetRoutes, $wgEnableEmail, $wgAuth;
+ global $wgAuth;
+ $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
// Maybe password resets are disabled, or there are no allowable routes
- if ( !is_array( $wgPasswordResetRoutes ) ||
- !in_array( true, array_values( $wgPasswordResetRoutes ) )
+ if ( !is_array( $resetRoutes ) ||
+ !in_array( true, array_values( $resetRoutes ) )
) {
return 'passwordreset-disabled';
}
@@ -333,7 +334,7 @@ class SpecialPasswordReset extends FormSpecialPage {
}
// Maybe email features have been disabled
- if ( !$wgEnableEmail ) {
+ if ( !$this->getConfig()->get( 'EnableEmail' ) ) {
return 'passwordreset-emaildisabled';
}
@@ -348,7 +349,7 @@ class SpecialPasswordReset extends FormSpecialPage {
/**
* Hide the password reset page if resets are disabled.
- * @return Bool
+ * @return bool
*/
function isListed() {
if ( $this->canChangePassword( $this->getUser() ) === true ) {
diff --git a/includes/specials/SpecialPermanentLink.php b/includes/specials/SpecialPermanentLink.php
new file mode 100644
index 00000000..17115e88
--- /dev/null
+++ b/includes/specials/SpecialPermanentLink.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Redirect from Special:PermanentLink/### to index.php?oldid=###.
+ *
+ * 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
+ */
+
+/**
+ * Redirect from Special:PermanentLink/### to index.php?oldid=###.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPermanentLink extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'PermanentLink' );
+ $this->mAllowedRedirectParams = array();
+ }
+
+ function getRedirect( $subpage ) {
+ $subpage = intval( $subpage );
+ if ( $subpage === 0 ) {
+ # throw an error page when no subpage was given
+ throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
+ }
+ $this->mAddedRedirectParams['oldid'] = $subpage;
+
+ return true;
+ }
+}
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index ecee0bb7..cea00fa6 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -37,14 +37,7 @@ class SpecialPreferences extends SpecialPage {
$out = $this->getOutput();
$out->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
- $user = $this->getUser();
- if ( $user->isAnon() ) {
- throw new ErrorPageError(
- 'prefsnologin',
- 'prefsnologintext',
- array( $this->getTitle()->getPrefixedDBkey() )
- );
- }
+ $this->requireLogin( 'prefsnologintext2' );
$this->checkReadOnly();
if ( $par == 'reset' ) {
@@ -62,7 +55,7 @@ class SpecialPreferences extends SpecialPage {
);
}
- $htmlForm = Preferences::getFormObject( $user, $this->getContext() );
+ $htmlForm = Preferences::getFormObject( $this->getUser(), $this->getContext() );
$htmlForm->setSubmitCallback( array( 'Preferences', 'tryUISubmit' ) );
$htmlForm->show();
@@ -76,10 +69,11 @@ class SpecialPreferences extends SpecialPage {
$this->getOutput()->addWikiMsg( 'prefs-reset-intro' );
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle( 'reset' ) ); // Reset subpage
+ $context->setTitle( $this->getPageTitle( 'reset' ) ); // Reset subpage
$htmlForm = new HTMLForm( array(), $context, 'prefs-restore' );
$htmlForm->setSubmitTextMsg( 'restoreprefs' );
+ $htmlForm->setSubmitDestructive();
$htmlForm->setSubmitCallback( array( $this, 'submitReset' ) );
$htmlForm->suppressReset();
@@ -95,7 +89,7 @@ class SpecialPreferences extends SpecialPage {
$user->resetOptions( 'all', $this->getContext() );
$user->saveSettings();
- $url = $this->getTitle()->getFullURL( 'success' );
+ $url = $this->getPageTitle()->getFullURL( 'success' );
$this->getOutput()->redirect( $url );
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 0d065b09..2e67e2b5 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -26,7 +26,7 @@
*
* @ingroup SpecialPage
*/
-class SpecialPrefixindex extends SpecialAllpages {
+class SpecialPrefixindex extends SpecialAllPages {
/**
* Whether to remove the searched prefix from the displayed link. Useful
@@ -36,6 +36,9 @@ class SpecialPrefixindex extends SpecialAllpages {
protected $hideRedirects = false;
+ // number of columns in output table
+ protected $columns = 3;
+
// Inherit $maxPerPage
function __construct() {
@@ -44,7 +47,7 @@ class SpecialPrefixindex extends SpecialAllpages {
/**
* Entry point : initialise variables and call subfunctions.
- * @param string $par becomes "FOO" when called like Special:Prefixindex/FOO (default null)
+ * @param string $par Becomes "FOO" when called like Special:Prefixindex/FOO (default null)
*/
function execute( $par ) {
global $wgContLang;
@@ -63,16 +66,17 @@ class SpecialPrefixindex extends SpecialAllpages {
$namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN).
$this->hideRedirects = $request->getBool( 'hideredirects', $this->hideRedirects );
$this->stripPrefix = $request->getBool( 'stripprefix', $this->stripPrefix );
+ $this->columns = $request->getInt( 'columns', $this->columns );
$namespaces = $wgContLang->getNamespaces();
$out->setPageTitle(
- ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) )
+ ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) )
? $this->msg( 'prefixindex-namespace', str_replace( '_', ' ', $namespaces[$namespace] ) )
: $this->msg( 'prefixindex' )
);
$showme = '';
- if ( isset( $par ) ) {
+ if ( $par !== null ) {
$showme = $par;
} elseif ( $prefix != '' ) {
$showme = $prefix;
@@ -92,16 +96,14 @@ class SpecialPrefixindex extends SpecialAllpages {
/**
* HTML for the top form
- * @param $namespace Integer: a namespace constant (default NS_MAIN).
- * @param string $from dbKey we are starting listing at.
+ * @param int $namespace A namespace constant (default NS_MAIN).
+ * @param string $from DbKey we are starting listing at.
* @return string
*/
protected function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) {
- global $wgScript;
-
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
- $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $out .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
+ $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getConfig()->get( 'Script' ) ) );
+ $out .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
$out .= Xml::openElement( 'fieldset' );
$out .= Xml::element( 'legend', null, $this->msg( 'allpages' )->text() );
$out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
@@ -149,9 +151,9 @@ class SpecialPrefixindex extends SpecialAllpages {
}
/**
- * @param $namespace Integer, default NS_MAIN
- * @param $prefix String
- * @param string $from list all pages from this name (default FALSE)
+ * @param int $namespace Default NS_MAIN
+ * @param string $prefix
+ * @param string $from List all pages from this name (default false)
*/
protected function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) {
global $wgContLang;
@@ -163,10 +165,11 @@ class SpecialPrefixindex extends SpecialAllpages {
$fromList = $this->getNamespaceKeyAndText( $namespace, $from );
$prefixList = $this->getNamespaceKeyAndText( $namespace, $prefix );
$namespaces = $wgContLang->getNamespaces();
+ $res = null;
if ( !$prefixList || !$fromList ) {
$out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
- } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
+ } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
// Show errormessage and reset to NS_MAIN
$out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
$namespace = NS_MAIN;
@@ -203,7 +206,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$n = 0;
if ( $res->numRows() > 0 ) {
- $out = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-list-table' ) );
+ $out = Xml::openElement( 'table', array( 'class' => 'mw-prefixindex-list-table' ) );
$prefixLength = strlen( $prefix );
while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
@@ -224,17 +227,17 @@ class SpecialPrefixindex extends SpecialAllpages {
} else {
$link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
}
- if ( $n % 3 == 0 ) {
+ if ( $n % $this->columns == 0 ) {
$out .= '<tr>';
}
$out .= "<td>$link</td>";
$n++;
- if ( $n % 3 == 0 ) {
+ if ( $n % $this->columns == 0 ) {
$out .= '</tr>';
}
}
- if ( $n % 3 != 0 ) {
+ if ( $n % $this->columns != 0 ) {
$out .= '</tr>';
}
@@ -249,7 +252,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$out2 = '';
} else {
$nsForm = $this->namespacePrefixForm( $namespace, $prefix );
- $self = $this->getTitle();
+ $self = $this->getPageTitle();
$out2 = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-nav-table' ) ) .
'<tr>
<td>' .
@@ -257,14 +260,13 @@ class SpecialPrefixindex extends SpecialAllpages {
'</td>
<td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">';
- if ( isset( $res ) && $res && ( $n == $this->maxPerPage ) &&
- ( $s = $res->fetchObject() )
- ) {
+ if ( $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
$query = array(
'from' => $s->page_title,
'prefix' => $prefix,
'hideredirects' => $this->hideRedirects,
'stripprefix' => $this->stripPrefix,
+ 'columns' => $this->columns,
);
if ( $namespace || $prefix == '' ) {
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index 3de6ea24..0ba73857 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class SpecialProtectedpages extends SpecialPage {
-
protected $IdLevel = 'level';
protected $IdType = 'type';
@@ -38,6 +37,7 @@ class SpecialProtectedpages extends SpecialPage {
public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
+ $this->getOutput()->addModuleStyles( 'mediawiki.special' );
// Purge expired entries on one in every 10 queries
if ( !mt_rand( 0, 10 ) ) {
@@ -52,6 +52,7 @@ class SpecialProtectedpages extends SpecialPage {
$ns = $request->getIntOrNull( 'namespace' );
$indefOnly = $request->getBool( 'indefonly' ) ? 1 : 0;
$cascadeOnly = $request->getBool( 'cascadeonly' ) ? 1 : 0;
+ $noRedirect = $request->getBool( 'noredirect' ) ? 1 : 0;
$pager = new ProtectedPagesPager(
$this,
@@ -62,7 +63,8 @@ class SpecialProtectedpages extends SpecialPage {
$sizetype,
$size,
$indefOnly,
- $cascadeOnly
+ $cascadeOnly,
+ $noRedirect
);
$this->getOutput()->addHTML( $this->showOptions(
@@ -72,137 +74,34 @@ class SpecialProtectedpages extends SpecialPage {
$sizetype,
$size,
$indefOnly,
- $cascadeOnly
+ $cascadeOnly,
+ $noRedirect
) );
if ( $pager->getNumRows() ) {
- $this->getOutput()->addHTML(
- $pager->getNavigationBar() .
- '<ul>' . $pager->getBody() . '</ul>' .
- $pager->getNavigationBar()
- );
+ $this->getOutput()->addParserOutputContent( $pager->getFullOutput() );
} else {
$this->getOutput()->addWikiMsg( 'protectedpagesempty' );
}
}
/**
- * Callback function to output a restriction
- * @param Title $row Protected title
- * @return string Formatted "<li>" element
- */
- public function formatRow( $row ) {
- wfProfileIn( __METHOD__ );
-
- static $infinity = null;
-
- if ( is_null( $infinity ) ) {
- $infinity = wfGetDB( DB_SLAVE )->getInfinity();
- }
-
- $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- if ( !$title ) {
- wfProfileOut( __METHOD__ );
-
- return Html::rawElement(
- 'li',
- array(),
- Html::element(
- 'span',
- array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- $row->page_namespace,
- $row->page_title
- )
- )
- ) . "\n";
- }
-
- $link = Linker::link( $title );
-
- $description_items = array();
-
- // Messages: restriction-level-sysop, restriction-level-autoconfirmed
- $protType = $this->msg( 'restriction-level-' . $row->pr_level )->escaped();
-
- $description_items[] = $protType;
-
- if ( $row->pr_cascade ) {
- $description_items[] = $this->msg( 'protect-summary-cascade' )->text();
- }
-
- $stxt = '';
- $lang = $this->getLanguage();
-
- $expiry = $lang->formatExpiry( $row->pr_expiry, TS_MW );
- if ( $expiry != $infinity ) {
- $user = $this->getUser();
- $description_items[] = $this->msg(
- 'protect-expiring-local',
- $lang->userTimeAndDate( $expiry, $user ),
- $lang->userDate( $expiry, $user ),
- $lang->userTime( $expiry, $user )
- )->escaped();
- }
-
- if ( !is_null( $size = $row->page_len ) ) {
- $stxt = $lang->getDirMark() . ' ' . Linker::formatRevisionSize( $size );
- }
-
- // Show a link to the change protection form for allowed users otherwise
- // a link to the protection log
- if ( $this->getUser()->isAllowed( 'protect' ) ) {
- $changeProtection = Linker::linkKnown(
- $title,
- $this->msg( 'protect_change' )->escaped(),
- array(),
- array( 'action' => 'unprotect' )
- );
- } else {
- $ltitle = SpecialPage::getTitleFor( 'Log' );
- $changeProtection = Linker::linkKnown(
- $ltitle,
- $this->msg( 'protectlogpage' )->escaped(),
- array(),
- array(
- 'type' => 'protect',
- 'page' => $title->getPrefixedText()
- )
- );
- }
-
- $changeProtection = ' ' . $this->msg( 'parentheses' )->rawParams( $changeProtection )
- ->escaped();
-
- wfProfileOut( __METHOD__ );
-
- return Html::rawElement(
- 'li',
- array(),
- $lang->specialList( $link . $stxt, $lang->commaList( $description_items ), false ) .
- $changeProtection
- ) . "\n";
- }
-
- /**
- * @param $namespace Integer
- * @param string $type restriction type
- * @param string $level restriction level
+ * @param int $namespace
+ * @param string $type Restriction type
+ * @param string $level Restriction level
* @param string $sizetype "min" or "max"
- * @param $size Integer
- * @param $indefOnly Boolean: only indefinie protection
- * @param $cascadeOnly Boolean: only cascading protection
- * @return String: input form
+ * @param int $size
+ * @param bool $indefOnly Only indefinite protection
+ * @param bool $cascadeOnly Only cascading protection
+ * @param bool $noRedirect Don't show redirects
+ * @return string Input form
*/
protected function showOptions( $namespace, $type = 'edit', $level, $sizetype,
- $size, $indefOnly, $cascadeOnly
+ $size, $indefOnly, $cascadeOnly, $noRedirect
) {
- global $wgScript;
+ $title = $this->getPageTitle();
- $title = $this->getTitle();
-
- return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
+ return Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), $this->msg( 'protectedpages' )->text() ) .
Html::hidden( 'title', $title->getPrefixedDBkey() ) . "\n" .
@@ -212,6 +111,7 @@ class SpecialProtectedpages extends SpecialPage {
"<br /><span style='white-space: nowrap'>" .
$this->getExpiryCheck( $indefOnly ) . "&#160;\n" .
$this->getCascadeCheck( $cascadeOnly ) . "&#160;\n" .
+ $this->getRedirectCheck( $noRedirect ) . "&#160;\n" .
"</span><br /><span style='white-space: nowrap'>" .
$this->getSizeLimit( $sizetype, $size ) . "&#160;\n" .
"</span>" .
@@ -224,8 +124,8 @@ class SpecialProtectedpages extends SpecialPage {
* Prepare the namespace filter drop-down; standard namespace
* selector, sans the MediaWiki namespace
*
- * @param $namespace Mixed: pre-select namespace
- * @return String
+ * @param string|null $namespace Pre-select namespace
+ * @return string
*/
protected function getNamespaceMenu( $namespace = null ) {
return Html::rawElement( 'span', array( 'style' => 'white-space: nowrap;' ),
@@ -270,6 +170,19 @@ class SpecialProtectedpages extends SpecialPage {
}
/**
+ * @param bool $noRedirect
+ * @return string Formatted HTML
+ */
+ protected function getRedirectCheck( $noRedirect ) {
+ return Xml::checkLabel(
+ $this->msg( 'protectedpages-noredirect' )->text(),
+ 'noredirect',
+ 'noredirect',
+ $noRedirect
+ ) . "\n";
+ }
+
+ /**
* @param string $sizetype "min" or "max"
* @param mixed $size
* @return string Formatted HTML
@@ -300,7 +213,7 @@ class SpecialProtectedpages extends SpecialPage {
/**
* Creates the input label of the restriction type
- * @param $pr_type string Protection type
+ * @param string $pr_type Protection type
* @return string Formatted HTML
*/
protected function getTypeMenu( $pr_type ) {
@@ -329,18 +242,16 @@ class SpecialProtectedpages extends SpecialPage {
/**
* Creates the input label of the restriction level
- * @param $pr_level string Protection level
+ * @param string $pr_level Protection level
* @return string Formatted HTML
*/
protected function getLevelMenu( $pr_level ) {
- global $wgRestrictionLevels;
-
// Temporary array
$m = array( $this->msg( 'restriction-level-all' )->text() => 0 );
$options = array();
// First pass to load the log names
- foreach ( $wgRestrictionLevels as $type ) {
+ foreach ( $this->getConfig()->get( 'RestrictionLevels' ) as $type ) {
// Messages used can be 'restriction-level-sysop' and 'restriction-level-autoconfirmed'
if ( $type != '' && $type != '*' ) {
$text = $this->msg( "restriction-level-$type" )->text();
@@ -370,12 +281,12 @@ class SpecialProtectedpages extends SpecialPage {
* @todo document
* @ingroup Pager
*/
-class ProtectedPagesPager extends AlphabeticPager {
+class ProtectedPagesPager extends TablePager {
public $mForm, $mConds;
- private $type, $level, $namespace, $sizetype, $size, $indefonly;
+ private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
function __construct( $form, $conds = array(), $type, $level, $namespace,
- $sizetype = '', $size = 0, $indefonly = false, $cascadeonly = false
+ $sizetype = '', $size = 0, $indefonly = false, $cascadeonly = false, $noredirect = false
) {
$this->mForm = $form;
$this->mConds = $conds;
@@ -386,28 +297,203 @@ class ProtectedPagesPager extends AlphabeticPager {
$this->size = intval( $size );
$this->indefonly = (bool)$indefonly;
$this->cascadeonly = (bool)$cascadeonly;
+ $this->noredirect = (bool)$noredirect;
parent::__construct( $form->getContext() );
}
- function getStartBody() {
+ function preprocessResults( $result ) {
# Do a link batch query
$lb = new LinkBatch;
- foreach ( $this->mResult as $row ) {
+ $userids = array();
+
+ foreach ( $result as $row ) {
$lb->add( $row->page_namespace, $row->page_title );
+ // field is nullable, maybe null on old protections
+ if ( $row->log_user !== null ) {
+ $userids[] = $row->log_user;
+ }
+ }
+
+ // fill LinkBatch with user page and user talk
+ if ( count( $userids ) ) {
+ $userCache = UserCache::singleton();
+ $userCache->doQuery( $userids, array(), __METHOD__ );
+ foreach ( $userids as $userid ) {
+ $name = $userCache->getProp( $userid, 'name' );
+ if ( $name !== false ) {
+ $lb->add( NS_USER, $name );
+ $lb->add( NS_USER_TALK, $name );
+ }
+ }
}
+
$lb->execute();
+ }
+
+ function getFieldNames() {
+ static $headers = null;
+
+ if ( $headers == array() ) {
+ $headers = array(
+ 'log_timestamp' => 'protectedpages-timestamp',
+ 'pr_page' => 'protectedpages-page',
+ 'pr_expiry' => 'protectedpages-expiry',
+ 'log_user' => 'protectedpages-performer',
+ 'pr_params' => 'protectedpages-params',
+ 'log_comment' => 'protectedpages-reason',
+ );
+ foreach ( $headers as $key => $val ) {
+ $headers[$key] = $this->msg( $val )->text();
+ }
+ }
- return '';
+ return $headers;
}
- function formatRow( $row ) {
- return $this->mForm->formatRow( $row );
+ /**
+ * @param string $field
+ * @param string $value
+ * @return string
+ * @throws MWException
+ */
+ function formatValue( $field, $value ) {
+ /** @var $row object */
+ $row = $this->mCurrentRow;
+
+ $formatted = '';
+
+ switch ( $field ) {
+ case 'log_timestamp':
+ // when timestamp is null, this is a old protection row
+ if ( $value === null ) {
+ $formatted = Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-unknown' ),
+ $this->msg( 'protectedpages-unknown-timestamp' )->escaped()
+ );
+ } else {
+ $formatted = $this->getLanguage()->userTimeAndDate( $value, $this->getUser() );
+ }
+ break;
+
+ case 'pr_page':
+ $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+ if ( !$title ) {
+ $formatted = Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $row->page_namespace,
+ $row->page_title
+ )
+ );
+ } else {
+ $formatted = Linker::link( $title );
+ }
+ if ( !is_null( $row->page_len ) ) {
+ $formatted .= $this->getLanguage()->getDirMark() .
+ ' ' . Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-length' ),
+ Linker::formatRevisionSize( $row->page_len )
+ );
+ }
+ break;
+
+ case 'pr_expiry':
+ $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */true );
+ $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+ if ( $this->getUser()->isAllowed( 'protect' ) && $title ) {
+ $changeProtection = Linker::linkKnown(
+ $title,
+ $this->msg( 'protect_change' )->escaped(),
+ array(),
+ array( 'action' => 'unprotect' )
+ );
+ $formatted .= ' ' . Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-actions' ),
+ $this->msg( 'parentheses' )->rawParams( $changeProtection )->escaped()
+ );
+ }
+ break;
+
+ case 'log_user':
+ // when timestamp is null, this is a old protection row
+ if ( $row->log_timestamp === null ) {
+ $formatted = Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-unknown' ),
+ $this->msg( 'protectedpages-unknown-performer' )->escaped()
+ );
+ } else {
+ $username = UserCache::singleton()->getProp( $value, 'name' );
+ if ( LogEventsList::userCanBitfield(
+ $row->log_deleted,
+ LogPage::DELETED_USER,
+ $this->getUser()
+ ) ) {
+ if ( $username === false ) {
+ $formatted = htmlspecialchars( $value );
+ } else {
+ $formatted = Linker::userLink( $value, $username )
+ . Linker::userToolLinks( $value, $username );
+ }
+ } else {
+ $formatted = $this->msg( 'rev-deleted-user' )->escaped();
+ }
+ if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
+ $formatted = '<span class="history-deleted">' . $formatted . '</span>';
+ }
+ }
+ break;
+
+ case 'pr_params':
+ $params = array();
+ // Messages: restriction-level-sysop, restriction-level-autoconfirmed
+ $params[] = $this->msg( 'restriction-level-' . $row->pr_level )->escaped();
+ if ( $row->pr_cascade ) {
+ $params[] = $this->msg( 'protect-summary-cascade' )->text();
+ }
+ $formatted = $this->getLanguage()->commaList( $params );
+ break;
+
+ case 'log_comment':
+ // when timestamp is null, this is an old protection row
+ if ( $row->log_timestamp === null ) {
+ $formatted = Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-unknown' ),
+ $this->msg( 'protectedpages-unknown-reason' )->escaped()
+ );
+ } else {
+ if ( LogEventsList::userCanBitfield(
+ $row->log_deleted,
+ LogPage::DELETED_COMMENT,
+ $this->getUser()
+ ) ) {
+ $formatted = Linker::formatComment( $value !== null ? $value : '' );
+ } else {
+ $formatted = $this->msg( 'rev-deleted-comment' )->escaped();
+ }
+ if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
+ $formatted = '<span class="history-deleted">' . $formatted . '</span>';
+ }
+ }
+ break;
+
+ default:
+ throw new MWException( "Unknown field '$field'" );
+ }
+
+ return $formatted;
}
function getQueryInfo() {
$conds = $this->mConds;
- $conds[] = '(pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ) .
- 'OR pr_expiry IS NULL)';
+ $conds[] = 'pr_expiry > ' . $this->mDb->addQuotes( $this->mDb->timestamp() ) .
+ 'OR pr_expiry IS NULL';
$conds[] = 'page_id=pr_page';
$conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type );
@@ -424,6 +510,9 @@ class ProtectedPagesPager extends AlphabeticPager {
if ( $this->cascadeonly ) {
$conds[] = 'pr_cascade = 1';
}
+ if ( $this->noredirect ) {
+ $conds[] = 'page_is_redirect = 0';
+ }
if ( $this->level ) {
$conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level );
@@ -433,14 +522,51 @@ class ProtectedPagesPager extends AlphabeticPager {
}
return array(
- 'tables' => array( 'page_restrictions', 'page' ),
- 'fields' => array( 'pr_id', 'page_namespace', 'page_title', 'page_len',
- 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade' ),
- 'conds' => $conds
+ 'tables' => array( 'page', 'page_restrictions', 'log_search', 'logging' ),
+ 'fields' => array(
+ 'pr_id',
+ 'page_namespace',
+ 'page_title',
+ 'page_len',
+ 'pr_type',
+ 'pr_level',
+ 'pr_expiry',
+ 'pr_cascade',
+ 'log_timestamp',
+ 'log_user',
+ 'log_comment',
+ 'log_deleted',
+ ),
+ 'conds' => $conds,
+ 'join_conds' => array(
+ 'log_search' => array(
+ 'LEFT JOIN', array(
+ 'ls_field' => 'pr_id', 'ls_value = pr_id'
+ )
+ ),
+ 'logging' => array(
+ 'LEFT JOIN', array(
+ 'ls_log_id = log_id'
+ )
+ )
+ )
);
}
+ public function getTableClass() {
+ return parent::getTableClass() . ' mw-protectedpages';
+ }
+
function getIndexField() {
return 'pr_id';
}
+
+ function getDefaultSort() {
+ return 'pr_id';
+ }
+
+ function isFieldSortable( $field ) {
+ // no index for sorting exists
+ return false;
+ }
}
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index 078e7b12..a40da87d 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -126,16 +126,15 @@ class SpecialProtectedtitles extends SpecialPage {
}
/**
- * @param $namespace Integer:
- * @param $type string
- * @param $level string
+ * @param int $namespace
+ * @param string $type
+ * @param string $level
* @return string
* @private
*/
function showOptions( $namespace, $type = 'edit', $level ) {
- global $wgScript;
- $action = htmlspecialchars( $wgScript );
- $title = $this->getTitle();
+ $action = htmlspecialchars( wfScript() );
+ $title = $this->getPageTitle();
$special = htmlspecialchars( $title->getPrefixedDBkey() );
return "<form action=\"$action\" method=\"get\">\n" .
@@ -152,7 +151,7 @@ class SpecialProtectedtitles extends SpecialPage {
* Prepare the namespace filter drop-down; standard namespace
* selector, sans the MediaWiki namespace
*
- * @param $namespace Mixed: pre-select namespace
+ * @param string|null $namespace Pre-select namespace
* @return string
*/
function getNamespaceMenu( $namespace = null ) {
@@ -175,14 +174,12 @@ class SpecialProtectedtitles extends SpecialPage {
* @private
*/
function getLevelMenu( $pr_level ) {
- global $wgRestrictionLevels;
-
// Temporary array
$m = array( $this->msg( 'restriction-level-all' )->text() => 0 );
$options = array();
// First pass to load the log names
- foreach ( $wgRestrictionLevels as $type ) {
+ foreach ( $this->getConfig()->get( 'RestrictionLevels' ) as $type ) {
if ( $type != '' && $type != '*' ) {
// Messages: restriction-level-sysop, restriction-level-autoconfirmed
$text = $this->msg( "restriction-level-$type" )->text();
diff --git a/includes/specials/SpecialRandomInCategory.php b/includes/specials/SpecialRandomInCategory.php
index 0e022bfa..570ab3bf 100644
--- a/includes/specials/SpecialRandomInCategory.php
+++ b/includes/specials/SpecialRandomInCategory.php
@@ -46,7 +46,7 @@
*
* @ingroup SpecialPage
*/
-class SpecialRandomInCategory extends SpecialPage {
+class SpecialRandomInCategory extends FormSpecialPage {
protected $extra = array(); // Extra SQL statements
protected $category = false; // Title object of category
protected $maxOffset = 30; // Max amount to fudge randomness by.
@@ -67,12 +67,35 @@ class SpecialRandomInCategory extends SpecialPage {
$this->minTimestamp = null;
}
- public function execute( $par ) {
- global $wgScript;
+ protected function getFormFields() {
+ $form = array(
+ 'category' => array(
+ 'type' => 'text',
+ 'label-message' => 'randomincategory-category',
+ 'required' => true,
+ )
+ );
+
+ return $form;
+ }
+
+ public function requiresWrite() {
+ return false;
+ }
+
+ public function requiresUnblock() {
+ return false;
+ }
+ protected function setParameter( $par ) {
+ // if subpage present, fake form submission
+ $this->onSubmit( array( 'category' => $par ) );
+ }
+
+ public function onSubmit( array $data ) {
$cat = false;
- $categoryStr = $this->getRequest()->getText( 'category', $par );
+ $categoryStr = $data['category'];
if ( $categoryStr ) {
$cat = Title::newFromText( $categoryStr, NS_CATEGORY );
@@ -87,48 +110,31 @@ class SpecialRandomInCategory extends SpecialPage {
$this->setCategory( $cat );
}
-
if ( !$this->category && $categoryStr ) {
- $this->setHeaders();
- $this->getOutput()->addWikiMsg( 'randomincategory-invalidcategory',
+ $msg = $this->msg( 'randomincategory-invalidcategory',
wfEscapeWikiText( $categoryStr ) );
- return;
+ return Status::newFatal( $msg );
+
} elseif ( !$this->category ) {
- $this->setHeaders();
- $input = Html::input( 'category' );
- $submitText = $this->msg( 'randomincategory-selectcategory-submit' )->text();
- $submit = Html::input( '', $submitText, 'submit' );
-
- $msg = $this->msg( 'randomincategory-selectcategory' );
- $form = Html::rawElement( 'form', array( 'action' => $wgScript ),
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- $msg->rawParams( $input, $submit )->parse()
- );
- $this->getOutput()->addHtml( $form );
-
- return;
+ return; // no data sent
}
$title = $this->getRandomTitle();
if ( is_null( $title ) ) {
- $this->setHeaders();
- $this->getOutput()->addWikiMsg( 'randomincategory-nopages',
+ $msg = $this->msg( 'randomincategory-nopages',
$this->category->getText() );
- return;
+ return Status::newFatal( $msg );
}
- $query = $this->getRequest()->getValues();
- unset( $query['title'] );
- unset( $query['category'] );
- $this->getOutput()->redirect( $title->getFullURL( $query ) );
+ $this->getOutput()->redirect( $title->getFullURL() );
}
/**
* Choose a random title.
- * @return Title object (or null if nothing to choose from)
+ * @return Title|null Title object (or null if nothing to choose from)
*/
public function getRandomTitle() {
// Convert to float, since we do math with the random number.
@@ -178,7 +184,7 @@ class SpecialRandomInCategory extends SpecialPage {
* was a large gap in the distribution of cl_timestamp values. This way instead
* of things to the right of the gap being favoured, both sides of the gap
* are favoured.
- * @return Array Query information.
+ * @return array Query information.
*/
protected function getQueryInfo( $rand, $offset, $up ) {
$op = $up ? '>=' : '<=';
@@ -208,6 +214,7 @@ class SpecialRandomInCategory extends SpecialPage {
$qi['conds'][] = 'cl_timestamp ' . $op . ' ' .
$dbr->addQuotes( $dbr->timestamp( $minClTime ) );
}
+
return $qi;
}
@@ -230,6 +237,7 @@ class SpecialRandomInCategory extends SpecialPage {
}
$ts = ( $this->maxTimestamp - $this->minTimestamp ) * $rand + $this->minTimestamp;
+
return intval( $ts );
}
@@ -237,8 +245,8 @@ class SpecialRandomInCategory extends SpecialPage {
* Get the lowest and highest timestamp for a category.
*
* @param Title $category
- * @return Array The lowest and highest timestamp
- * @throws MWException if category has no entries.
+ * @return array The lowest and highest timestamp
+ * @throws MWException If category has no entries.
*/
protected function getMinAndMaxForCat( Title $category ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -259,6 +267,7 @@ class SpecialRandomInCategory extends SpecialPage {
if ( !$res ) {
throw new MWException( 'No entries in category' );
}
+
return array( wfTimestamp( TS_UNIX, $res->low ), wfTimestamp( TS_UNIX, $res->high ) );
}
@@ -266,8 +275,8 @@ class SpecialRandomInCategory extends SpecialPage {
* @param float $rand A random number that is converted to a random timestamp
* @param int $offset A small offset to make the result seem more "random"
* @param bool $up Get the result above the random value
- * @param String $fname The name of the calling method
- * @return Array Info for the title selected.
+ * @param string $fname The name of the calling method
+ * @return array Info for the title selected.
*/
private function selectRandomPageFromDB( $rand, $offset, $up, $fname = __METHOD__ ) {
$dbr = wfGetDB( DB_SLAVE );
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index c94d2b35..6d8f59b5 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -56,7 +56,9 @@ class RandomPage extends SpecialPage {
public function execute( $par ) {
global $wgContLang;
- if ( $par ) {
+ if ( is_string( $par ) ) {
+ // Testing for stringiness since we want to catch
+ // the empty string to mean main namespace only.
$this->setNamespace( $wgContLang->getNsIndex( $par ) );
}
@@ -80,7 +82,7 @@ class RandomPage extends SpecialPage {
/**
* Get a comma-delimited list of namespaces we don't have
* any pages in
- * @return String
+ * @return string
*/
private function getNsList() {
global $wgContLang;
@@ -98,7 +100,7 @@ class RandomPage extends SpecialPage {
/**
* Choose a random title.
- * @return Title object (or null if nothing to choose from)
+ * @return Title|null Title object (or null if nothing to choose from)
*/
public function getRandomTitle() {
$randstr = wfRandom();
@@ -144,7 +146,6 @@ class RandomPage extends SpecialPage {
), $this->extra ),
'options' => array(
'ORDER BY' => 'page_random',
- 'USE INDEX' => 'page_random',
'LIMIT' => 1,
),
'join_conds' => array()
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index a42a2171..e6d8f1c3 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -26,12 +26,38 @@
*
* @ingroup SpecialPage
*/
-class SpecialRecentChanges extends IncludableSpecialPage {
- var $rcOptions, $rcSubpage;
- protected $customFilters;
+class SpecialRecentChanges extends ChangesListSpecialPage {
+ // @codingStandardsIgnoreStart Needed "useless" override to change parameters.
+ public function __construct( $name = 'Recentchanges', $restriction = '' ) {
+ parent::__construct( $name, $restriction );
+ }
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Main execution point
+ *
+ * @param string $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
+ $this->getOutput()->setSquidMaxage( 10 );
+ // Check if the client has a cached version
+ $lastmod = $this->checkLastModified();
+ if ( $lastmod === false ) {
+ return;
+ }
- public function __construct( $name = 'Recentchanges' ) {
- parent::__construct( $name );
+ parent::execute( $subpage );
}
/**
@@ -40,7 +66,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return FormOptions
*/
public function getDefaultOptions() {
- $opts = new FormOptions();
+ $opts = parent::getDefaultOptions();
$user = $this->getUser();
$opts->add( 'days', $user->getIntOption( 'rcdays' ) );
@@ -54,10 +80,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$opts->add( 'hidepatrolled', $user->getBoolOption( 'hidepatrolled' ) );
$opts->add( 'hidemyself', false );
- $opts->add( 'namespace', '', FormOptions::INTNULL );
- $opts->add( 'invert', false );
- $opts->add( 'associated', false );
-
$opts->add( 'categories', '' );
$opts->add( 'categories_any', false );
$opts->add( 'tagfilter', '' );
@@ -66,148 +88,21 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
- * Create a FormOptions object with options as specified by the user
- *
- * @param array $parameters
- *
- * @return FormOptions
- */
- public function setup( $parameters ) {
- $opts = $this->getDefaultOptions();
-
- foreach ( $this->getCustomFilters() as $key => $params ) {
- $opts->add( $key, $params['default'] );
- }
-
- $opts->fetchValuesFromRequest( $this->getRequest() );
-
- // Give precedence to subpage syntax
- if ( $parameters !== null ) {
- $this->parseParameters( $parameters, $opts );
- }
-
- $opts->validateIntBounds( 'limit', 0, 5000 );
-
- return $opts;
- }
-
- /**
* Get custom show/hide filters
*
* @return array Map of filter URL param names to properties (msg/default)
*/
protected function getCustomFilters() {
if ( $this->customFilters === null ) {
- $this->customFilters = array();
- wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) );
+ $this->customFilters = parent::getCustomFilters();
+ wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ), '1.23' );
}
return $this->customFilters;
}
/**
- * Create a FormOptions object specific for feed requests and return it
- *
- * @return FormOptions
- */
- public function feedSetup() {
- global $wgFeedLimit;
- $opts = $this->getDefaultOptions();
- $opts->fetchValuesFromRequest( $this->getRequest() );
- $opts->validateIntBounds( 'limit', 0, $wgFeedLimit );
-
- return $opts;
- }
-
- /**
- * Get the current FormOptions for this request
- */
- public function getOptions() {
- if ( $this->rcOptions === null ) {
- if ( $this->including() ) {
- $isFeed = false;
- } else {
- $isFeed = (bool)$this->getRequest()->getVal( 'feed' );
- }
- $this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage );
- }
-
- return $this->rcOptions;
- }
-
- /**
- * Main execution point
- *
- * @param string $subpage
- */
- public function execute( $subpage ) {
- $this->rcSubpage = $subpage;
- $feedFormat = $this->including() ? null : $this->getRequest()->getVal( 'feed' );
-
- # 10 seconds server-side caching max
- $this->getOutput()->setSquidMaxage( 10 );
- # Check if the client has a cached version
- $lastmod = $this->checkLastModified( $feedFormat );
- if ( $lastmod === false ) {
- return;
- }
-
- $opts = $this->getOptions();
- $this->setHeaders();
- $this->outputHeader();
- $this->addModules();
-
- // Fetch results, prepare a batch link existence check query
- $conds = $this->buildMainQueryConds( $opts );
- $rows = $this->doMainQuery( $conds, $opts );
- if ( $rows === false ) {
- if ( !$this->including() ) {
- $this->doHeader( $opts );
- }
-
- return;
- }
-
- if ( !$feedFormat ) {
- $batch = new LinkBatch;
- foreach ( $rows as $row ) {
- $batch->add( NS_USER, $row->rc_user_text );
- $batch->add( NS_USER_TALK, $row->rc_user_text );
- $batch->add( $row->rc_namespace, $row->rc_title );
- }
- $batch->execute();
- }
- if ( $feedFormat ) {
- list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat );
- /** @var ChangesFeed $changesFeed */
- $changesFeed->execute( $formatter, $rows, $lastmod, $opts );
- } else {
- $this->webOutput( $rows, $opts );
- }
-
- $rows->free();
- }
-
- /**
- * Return an array with a ChangesFeed object and ChannelFeed object
- *
- * @param string $feedFormat Feed's format (either 'rss' or 'atom')
- * @return array
- */
- public function getFeedObject( $feedFormat ) {
- $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' );
- $formatter = $changesFeed->getFeedObject(
- $this->msg( 'recentchanges' )->inContentLanguage()->text(),
- $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(),
- $this->getTitle()->getFullURL()
- );
-
- return array( $changesFeed, $formatter );
- }
-
- /**
- * Process $par and put options found if $opts
- * Mainly used when including the page
+ * Process $par and put options found in $opts. Used when including the page.
*
* @param string $par
* @param FormOptions $opts
@@ -257,25 +152,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
}
- /**
- * 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
- *
- * @param string $feedFormat
- * @return string|bool
- */
- public function checkLastModified( $feedFormat ) {
- $dbr = wfGetDB( DB_SLAVE );
- $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ );
- if ( $feedFormat || !$this->getUser()->useRCPatrol() ) {
- if ( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) {
- # Client cache fresh and headers sent, nothing more to do.
- return false;
- }
- }
-
- return $lastmod;
+ public function validateOptions( FormOptions $opts ) {
+ $opts->validateIntBounds( 'limit', 0, 5000 );
+ parent::validateOptions( $opts );
}
/**
@@ -285,21 +164,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return array
*/
public function buildMainQueryConds( FormOptions $opts ) {
- $dbr = wfGetDB( DB_SLAVE );
- $conds = array();
-
- # It makes no sense to hide both anons and logged-in users
- # Where this occurs, force anons to be shown
- $forcebot = false;
- if ( $opts['hideanons'] && $opts['hideliu'] ) {
- # Check if the user wants to show bots only
- if ( $opts['hidebots'] ) {
- $opts['hideanons'] = false;
- } else {
- $forcebot = true;
- $opts['hidebots'] = false;
- }
- }
+ $dbr = $this->getDB();
+ $conds = parent::buildMainQueryConds( $opts );
// Calculate cutoff
$cutoff_unixtime = time() - ( $opts['days'] * 86400 );
@@ -315,59 +181,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff );
- $hidePatrol = $this->getUser()->useRCPatrol() && $opts['hidepatrolled'];
- $hideLoggedInUsers = $opts['hideliu'] && !$forcebot;
- $hideAnonymousUsers = $opts['hideanons'] && !$forcebot;
-
- if ( $opts['hideminor'] ) {
- $conds['rc_minor'] = 0;
- }
- if ( $opts['hidebots'] ) {
- $conds['rc_bot'] = 0;
- }
- if ( $hidePatrol ) {
- $conds['rc_patrolled'] = 0;
- }
- if ( $forcebot ) {
- $conds['rc_bot'] = 1;
- }
- if ( $hideLoggedInUsers ) {
- $conds[] = 'rc_user = 0';
- }
- if ( $hideAnonymousUsers ) {
- $conds[] = 'rc_user != 0';
- }
-
- if ( $opts['hidemyself'] ) {
- if ( $this->getUser()->getId() ) {
- $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() );
- } else {
- $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() );
- }
- }
-
- # Namespace filtering
- if ( $opts['namespace'] !== '' ) {
- $selectedNS = $dbr->addQuotes( $opts['namespace'] );
- $operator = $opts['invert'] ? '!=' : '=';
- $boolean = $opts['invert'] ? 'AND' : 'OR';
-
- # namespace association (bug 2429)
- if ( !$opts['associated'] ) {
- $condition = "rc_namespace $operator $selectedNS";
- } else {
- # Also add the associated namespace
- $associatedNS = $dbr->addQuotes(
- MWNamespace::getAssociated( $opts['namespace'] )
- );
- $condition = "(rc_namespace $operator $selectedNS "
- . $boolean
- . " rc_namespace $operator $associatedNS)";
- }
-
- $conds[] = $condition;
- }
-
return $conds;
}
@@ -379,37 +192,32 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
*/
public function doMainQuery( $conds, $opts ) {
+ $dbr = $this->getDB();
+ $user = $this->getUser();
+
$tables = array( 'recentchanges' );
+ $fields = RecentChange::selectFields();
+ $query_options = array();
$join_conds = array();
- $query_options = array(
- 'USE INDEX' => array( 'recentchanges' => 'rc_timestamp' )
- );
-
- $uid = $this->getUser()->getId();
- $dbr = wfGetDB( DB_SLAVE );
- $limit = $opts['limit'];
- $namespace = $opts['namespace'];
- $invert = $opts['invert'];
- $associated = $opts['associated'];
- $fields = RecentChange::selectFields();
// JOIN on watchlist for users
- if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
+ if ( $user->getId() && $user->isAllowed( 'viewmywatchlist' ) ) {
$tables[] = 'watchlist';
$fields[] = 'wl_user';
$fields[] = 'wl_notificationtimestamp';
$join_conds['watchlist'] = array( 'LEFT JOIN', array(
- 'wl_user' => $uid,
+ 'wl_user' => $user->getId(),
'wl_title=rc_title',
'wl_namespace=rc_namespace'
) );
}
- if ( $this->getUser()->isAllowed( 'rollback' ) ) {
+
+ if ( $user->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
$fields[] = 'page_latest';
$join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' );
}
- // Tag stuff.
+
ChangeTags::modifyDisplayQuery(
$tables,
$fields,
@@ -419,48 +227,81 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$opts['tagfilter']
);
- if ( !wfRunHooks( 'SpecialRecentChangesQuery',
- array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) )
+ if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
+ $opts )
) {
return false;
}
// 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).
- return $dbr->select(
+ $rows = $dbr->select(
$tables,
$fields,
$conds + array( 'rc_new' => array( 0, 1 ) ),
__METHOD__,
- array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + $query_options,
+ array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $opts['limit'] ) + $query_options,
$join_conds
);
+
+ // Build the final data
+ if ( $this->getConfig()->get( 'AllowCategorizedRecentChanges' ) ) {
+ $this->filterByCategories( $rows, $opts );
+ }
+
+ return $rows;
+ }
+
+ protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
+ return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
+ && wfRunHooks(
+ 'SpecialRecentChangesQuery',
+ array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ),
+ '1.23'
+ );
+ }
+
+ public function outputFeedLinks() {
+ $this->addFeedLinks( $this->getFeedQuery() );
}
/**
- * Send output to the OutputPage object, only called if not used feeds
+ * Get URL query parameters for action=feedrecentchanges API feed of current recent changes view.
+ *
+ * @return array
+ */
+ private 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 array $rows Database rows
* @param FormOptions $opts
*/
- public function webOutput( $rows, $opts ) {
- global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges;
-
- // Build the final data
-
- if ( $wgAllowCategorizedRecentChanges ) {
- $this->filterByCategories( $rows, $opts );
- }
-
+ public function outputChangesList( $rows, $opts ) {
$limit = $opts['limit'];
- $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' );
+ $showWatcherCount = $this->getConfig()->get( 'RCShowWatchingUsers' )
+ && $this->getUser()->getOption( 'shownumberswatching' );
$watcherCache = array();
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = $this->getDB();
$counter = 1;
$list = ChangesList::newFromContext( $this->getContext() );
+ $list->initChangesListRows( $rows );
$rclistOutput = $list->beginRecentChangesList();
foreach ( $rows as $obj ) {
@@ -470,7 +311,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$rc = RecentChange::newFromRow( $obj );
$rc->counter = $counter++;
# Check if the page has been updated since the last visit
- if ( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) {
+ if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) && !empty( $obj->wl_notificationtimestamp ) ) {
$rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp );
} else {
$rc->notificationtimestamp = false; // Default
@@ -501,68 +342,35 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
$rclistOutput .= $list->endRecentChangesList();
- // Print things out
-
- if ( !$this->including() ) {
- // Output options box
- $this->doHeader( $opts );
- }
-
- // And now for the content
- $feedQuery = $this->getFeedQuery();
- if ( $feedQuery !== '' ) {
- $this->getOutput()->setFeedAppendQuery( $feedQuery );
- } else {
- $this->getOutput()->setFeedAppendQuery( false );
- }
-
if ( $rows->numRows() === 0 ) {
- $this->getOutput()->wrapWikiMsg(
- "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
+ $this->getOutput()->addHtml(
+ '<div class="mw-changeslist-empty">' .
+ $this->msg( 'recentchanges-noresult' )->parse() .
+ '</div>'
);
+ if ( !$this->including() ) {
+ $this->getOutput()->setStatusCode( 404 );
+ }
} else {
$this->getOutput()->addHTML( $rclistOutput );
}
}
/**
- * Get the query string to append to feed link URLs.
- *
- * @return string
- */
- public function getFeedQuery() {
- global $wgFeedLimit;
-
- $this->getOptions()->validateIntBounds( 'limit', 0, $wgFeedLimit );
- $options = $this->getOptions()->getChangedValues();
-
- // wfArrayToCgi() omits options set to null or false
- foreach ( $options as &$value ) {
- if ( $value === false ) {
- $value = '0';
- }
- }
- unset( $value );
-
- return wfArrayToCgi( $options );
- }
-
- /**
- * Return the text to be displayed above the changes
+ * Set the text to be displayed above the changes
*
* @param FormOptions $opts
- * @return string XHTML
+ * @param int $numRows Number of rows in the result to show after this header
*/
- public function doHeader( $opts ) {
- global $wgScript;
-
+ public function doHeader( $opts, $numRows ) {
$this->setTopText( $opts );
$defaults = $opts->getAllValues();
$nondefaults = $opts->getChangedValues();
$panel = array();
- $panel[] = $this->optionsPanel( $defaults, $nondefaults );
+ $panel[] = self::makeLegend( $this->getContext() );
+ $panel[] = $this->optionsPanel( $defaults, $nondefaults, $numRows );
$panel[] = '<hr />';
$extraOpts = $this->getExtraOptions( $opts );
@@ -604,9 +412,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$out .= Html::hidden( $key, $value );
}
- $t = $this->getTitle();
+ $t = $this->getPageTitle();
$out .= Html::hidden( 'title', $t->getPrefixedText() );
- $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out );
+ $form = Xml::tags( 'form', array( 'action' => wfScript() ), $out );
$panel[] = $form;
$panelString = implode( "\n", $panel );
@@ -622,6 +430,27 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
+ * Send the text to be displayed above the options
+ *
+ * @param FormOptions $opts Unused
+ */
+ function setTopText( FormOptions $opts ) {
+ global $wgContLang;
+
+ $message = $this->msg( 'recentchangestext' )->inContentLanguage();
+ if ( !$message->isDisabled() ) {
+ $this->getOutput()->addWikiText(
+ Html::rawElement( 'p',
+ array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
+ "\n" . $message->plain() . "\n"
+ ),
+ /* $lineStart */ false,
+ /* $interface */ false
+ );
+ }
+ }
+
+ /**
* Get options to be displayed in a form
*
* @param FormOptions $opts
@@ -635,8 +464,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$extraOpts = array();
$extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
- global $wgAllowCategorizedRecentChanges;
- if ( $wgAllowCategorizedRecentChanges ) {
+ if ( $this->getConfig()->get( 'AllowCategorizedRecentChanges' ) ) {
$extraOpts['category'] = $this->categoryFilterForm( $opts );
}
@@ -654,38 +482,31 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
- * Send the text to be displayed above the options
- *
- * @param FormOptions $opts Unused
+ * Add page-specific modules.
*/
- function setTopText( FormOptions $opts ) {
- global $wgContLang;
-
- $message = $this->msg( 'recentchangestext' )->inContentLanguage();
- if ( !$message->isDisabled() ) {
- $this->getOutput()->addWikiText(
- Html::rawElement( 'p',
- array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
- "\n" . $message->plain() . "\n"
- ),
- /* $lineStart */ false,
- /* $interface */ false
- );
- }
+ protected function addModules() {
+ parent::addModules();
+ $out = $this->getOutput();
+ $out->addModules( 'mediawiki.special.recentchanges' );
}
/**
- * Send the text to be displayed after the options, for use in subclasses.
+ * 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
*
- * @param FormOptions $opts
+ * @return string|bool
*/
- function setBottomText( FormOptions $opts ) {
+ public function checkLastModified() {
+ $dbr = $this->getDB();
+ $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ );
+
+ return $lastmod;
}
/**
* Creates the choose namespace selection
*
- * @todo Uses radio buttons (HASHAR)
* @param FormOptions $opts
* @return string
*/
@@ -710,7 +531,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
- * Create a input to filter changes by categories
+ * Create an input to filter changes by categories
*
* @param FormOptions $opts
* @return array
@@ -728,7 +549,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Filter $rows by categories set in $opts
*
- * @param array $rows Database rows
+ * @param ResultWrapper $rows Database rows
* @param FormOptions $opts
*/
function filterByCategories( &$rows, FormOptions $opts ) {
@@ -774,9 +595,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
# Look up
- $c = new Categoryfinder;
- $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' );
- $match = $c->run();
+ $catFind = new CategoryFinder;
+ $catFind->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' );
+ $match = $catFind->run();
# Filter
$newrows = array();
@@ -815,7 +636,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$text = '<strong>' . $text . '</strong>';
}
- return Linker::linkKnown( $this->getTitle(), $text, array(), $params );
+ return Linker::linkKnown( $this->getPageTitle(), $text, array(), $params );
}
/**
@@ -823,11 +644,10 @@ class SpecialRecentChanges extends IncludableSpecialPage {
*
* @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 ) {
- global $wgRCLinkLimits, $wgRCLinkDays;
-
+ function optionsPanel( $defaults, $nondefaults, $numRows ) {
$options = $nondefaults + $defaults;
$note = '';
@@ -839,19 +659,24 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$lang = $this->getLanguage();
$user = $this->getUser();
if ( $options['from'] ) {
- $note .= $this->msg( 'rcnotefrom' )->numParams( $options['limit'] )->params(
- $lang->userTimeAndDate( $options['from'], $user ),
- $lang->userDate( $options['from'], $user ),
- $lang->userTime( $options['from'], $user ) )->parse() . '<br />';
+ $note .= $this->msg( 'rcnotefrom' )
+ ->numParams( $options['limit'] )
+ ->params(
+ $lang->userTimeAndDate( $options['from'], $user ),
+ $lang->userDate( $options['from'], $user ),
+ $lang->userTime( $options['from'], $user )
+ )
+ ->numParams( $numRows )
+ ->parse() . '<br />';
}
# Sort data for display and make sure it's unique after we've added user data.
- $linkLimits = $wgRCLinkLimits;
+ $linkLimits = $this->getConfig()->get( 'RCLinkLimits' );
$linkLimits[] = $options['limit'];
sort( $linkLimits );
$linkLimits = array_unique( $linkLimits );
- $linkDays = $wgRCLinkDays;
+ $linkDays = $this->getConfig()->get( 'RCLinkDays' );
$linkDays[] = $options['days'];
sort( $linkDays );
$linkDays = array_unique( $linkDays );
@@ -873,7 +698,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$dl = $lang->pipeList( $dl );
// show/hide links
- $showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() );
$filters = array(
'hideminor' => 'rcshowhideminor',
'hidebots' => 'rcshowhidebots',
@@ -882,6 +706,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
'hidepatrolled' => 'rcshowhidepatr',
'hidemyself' => 'rcshowhidemine'
);
+
+ $showhide = array( 'show', 'hide' );
+
foreach ( $this->getCustomFilters() as $key => $params ) {
$filters[$key] = $params['msg'];
}
@@ -892,35 +719,42 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$links = array();
foreach ( $filters as $key => $msg ) {
- $link = $this->makeOptionsLink( $showhide[1 - $options[$key]],
+ // The following messages are used here:
+ // rcshowhideminor-show, rcshowhideminor-hide, rcshowhidebots-show, rcshowhidebots-hide,
+ // rcshowhideanons-show, rcshowhideanons-hide, rcshowhideliu-show, rcshowhideliu-hide,
+ // rcshowhidepatr-show, rcshowhidepatr-hide, rcshowhidemine-show, rcshowhidemine-hide.
+ $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(),
array( $key => 1 - $options[$key] ), $nondefaults );
- $links[] = $this->msg( $msg )->rawParams( $link )->escaped();
+ $links[] = "<span class=\"$msg rcshowhideoption\">" . $this->msg( $msg )->rawParams( $link )->escaped() . '</span>';
}
// show from this onward link
$timestamp = wfTimestampNow();
$now = $lang->userTimeAndDate( $timestamp, $user );
- $tl = $this->makeOptionsLink(
- $now, array( 'from' => $timestamp ), $nondefaults
- );
+ $timenow = $lang->userTime( $timestamp, $user );
+ $datenow = $lang->userDate( $timestamp, $user );
+ $pipedLinks = '<span class="rcshowhide">' . $lang->pipeList( $links ) . '</span>';
- $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )
- ->parse();
- $rclistfrom = $this->msg( 'rclistfrom' )->rawParams( $tl )->parse();
+ $rclinks = '<span class="rclinks">' . $this->msg( 'rclinks' )->rawParams( $cl, $dl, $pipedLinks )
+ ->parse() . '</span>';
- return "{$note}$rclinks<br />$rclistfrom";
- }
+ $rclistfrom = '<span class="rclistfrom">' . $this->makeOptionsLink(
+ $this->msg( 'rclistfrom' )->rawParams( $now, $timenow, $datenow )->parse(),
+ array( 'from' => $timestamp ),
+ $nondefaults
+ ) . '</span>';
- /**
- * Add page-specific modules.
- */
- protected function addModules() {
- $this->getOutput()->addModules( array(
- 'mediawiki.special.recentchanges',
- ) );
+ return "{$note}$rclinks<br />$rclistfrom";
}
- protected function getGroupName() {
- return 'changes';
+ public function isIncludable() {
+ return true;
}
}
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index a8447046..3ad9f0f4 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -26,8 +26,9 @@
*
* @ingroup SpecialPage
*/
-class SpecialRecentchangeslinked extends SpecialRecentChanges {
- var $rclTargetTitle;
+class SpecialRecentChangesLinked extends SpecialRecentChanges {
+ /** @var bool|Title */
+ protected $rclTargetTitle;
function __construct() {
parent::__construct( 'Recentchangeslinked' );
@@ -37,6 +38,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$opts = parent::getDefaultOptions();
$opts->add( 'target', '' );
$opts->add( 'showlinkedto', false );
+
return $opts;
}
@@ -44,23 +46,6 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$opts['target'] = $par;
}
- public function feedSetup() {
- $opts = parent::feedSetup();
- $opts['target'] = $this->getRequest()->getVal( 'target' );
- return $opts;
- }
-
- public function getFeedObject( $feedFormat ) {
- $feed = new ChangesFeed( $feedFormat, false );
- $feedObj = $feed->getFeedObject(
- $this->msg( 'recentchangeslinked-title', $this->getTargetTitle()->getPrefixedText() )
- ->inContentLanguage()->text(),
- $this->msg( 'recentchangeslinked-feed' )->inContentLanguage()->text(),
- $this->getTitle()->getFullURL()
- );
- return array( $feed, $feedObj );
- }
-
public function doMainQuery( $conds, $opts ) {
$target = $opts['target'];
$showlinkedto = $opts['showlinkedto'];
@@ -71,8 +56,10 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
$outputPage = $this->getOutput();
$title = Title::newFromURL( $target );
- if ( !$title || $title->getInterwiki() != '' ) {
- $outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div>", 'allpagesbadtitle' );
+ if ( !$title || $title->isExternal() ) {
+ $outputPage->addHtml( '<div class="errorbox">' . $this->msg( 'allpagesbadtitle' )
+ ->parse() . '</div>' );
+
return false;
}
@@ -106,7 +93,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
'wl_user' => $uid,
'wl_title=rc_title',
'wl_namespace=rc_namespace'
- ));
+ ) );
}
if ( $this->getUser()->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
@@ -122,7 +109,9 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$opts['tagfilter']
);
- if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$select ) ) ) {
+ if ( !$this->runMainQueryHook( $tables, $select, $conds, $query_options, $join_conds,
+ $opts )
+ ) {
return false;
}
@@ -145,14 +134,20 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
// field name prefixes for all the various tables we might want to join with
- $prefix = array( 'pagelinks' => 'pl', 'templatelinks' => 'tl', 'categorylinks' => 'cl', 'imagelinks' => 'il' );
+ $prefix = array(
+ 'pagelinks' => 'pl',
+ 'templatelinks' => 'tl',
+ 'categorylinks' => 'cl',
+ 'imagelinks' => 'il'
+ );
$subsql = array(); // 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
+ // 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' ) {
@@ -225,6 +220,14 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
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
*
@@ -256,13 +259,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$this->rclTargetTitle = false;
}
}
- return $this->rclTargetTitle;
- }
- function setTopText( FormOptions $opts ) {
- $target = $this->getTargetTitle();
- if ( $target ) {
- $this->getOutput()->addBacklinkSubtitle( $target );
- }
+ return $this->rclTargetTitle;
}
}
diff --git a/includes/specials/SpecialRedirect.php b/includes/specials/SpecialRedirect.php
index f05dacbc..2022d748 100644
--- a/includes/specials/SpecialRedirect.php
+++ b/includes/specials/SpecialRedirect.php
@@ -55,6 +55,7 @@ class SpecialRedirect extends FormSpecialPage {
/**
* Set $mType and $mValue based on parsed value of $subpage.
+ * @param string $subpage
*/
function setParameter( $subpage ) {
// parse $subpage to pull out the parts
@@ -66,7 +67,7 @@ class SpecialRedirect extends FormSpecialPage {
/**
* Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY)
*
- * @return string|null url to redirect to, or null if $mValue is invalid.
+ * @return string|null Url to redirect to, or null if $mValue is invalid.
*/
function dispatchUser() {
if ( !ctype_digit( $this->mValue ) ) {
@@ -78,18 +79,19 @@ class SpecialRedirect extends FormSpecialPage {
return null;
}
$userpage = Title::makeTitle( NS_USER, $username );
+
return $userpage->getFullURL( '', false, PROTO_CURRENT );
}
/**
* Handle Special:Redirect/file/xxxx
*
- * @return string|null url to redirect to, or null if $mValue is not found.
+ * @return string|null Url to redirect to, or null if $mValue is not found.
*/
function dispatchFile() {
$title = Title::makeTitleSafe( NS_FILE, $this->mValue );
- if ( ! $title instanceof Title ) {
+ if ( !$title instanceof Title ) {
return null;
}
$file = wfFindFile( $title );
@@ -112,6 +114,7 @@ class SpecialRedirect extends FormSpecialPage {
$url = $mto->getURL();
}
}
+
return $url;
}
@@ -119,7 +122,7 @@ class SpecialRedirect extends FormSpecialPage {
* Handle Special:Redirect/revision/xxx
* (by redirecting to index.php?oldid=xxx)
*
- * @return string|null url to redirect to, or null if $mValue is invalid.
+ * @return string|null Url to redirect to, or null if $mValue is invalid.
*/
function dispatchRevision() {
$oldid = $this->mValue;
@@ -130,46 +133,73 @@ class SpecialRedirect extends FormSpecialPage {
if ( $oldid === 0 ) {
return null;
}
+
return wfAppendQuery( wfScript( 'index' ), array(
'oldid' => $oldid
) );
}
/**
+ * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx)
+ *
+ * @return string|null Url to redirect to, or null if $mValue is invalid.
+ */
+ function dispatchPage() {
+ $curid = $this->mValue;
+ if ( !ctype_digit( $curid ) ) {
+ return null;
+ }
+ $curid = (int)$curid;
+ if ( $curid === 0 ) {
+ return null;
+ }
+
+ return wfAppendQuery( wfScript( 'index' ), array(
+ 'curid' => $curid
+ ) );
+ }
+
+ /**
* Use appropriate dispatch* method to obtain a redirection URL,
* and either: redirect, set a 404 error code and error message,
* or do nothing (if $mValue wasn't set) allowing the form to be
* displayed.
*
- * @return bool true if a redirect was successfully handled.
+ * @return bool True if a redirect was successfully handled.
*/
function dispatch() {
// the various namespaces supported by Special:Redirect
switch ( $this->mType ) {
- case 'user':
- $url = $this->dispatchUser();
- break;
- case 'file':
- $url = $this->dispatchFile();
- break;
- case 'revision':
- $url = $this->dispatchRevision();
- break;
- default:
- $this->getOutput()->setStatusCode( 404 );
- $url = null;
- break;
+ case 'user':
+ $url = $this->dispatchUser();
+ break;
+ case 'file':
+ $url = $this->dispatchFile();
+ break;
+ case 'revision':
+ $url = $this->dispatchRevision();
+ break;
+ case 'page':
+ $url = $this->dispatchPage();
+ break;
+ default:
+ $this->getOutput()->setStatusCode( 404 );
+ $url = null;
+ break;
}
if ( $url ) {
$this->getOutput()->redirect( $url );
+
return true;
}
if ( !is_null( $this->mValue ) ) {
$this->getOutput()->setStatusCode( 404 );
// Message: redirect-not-exists
$msg = $this->getMessagePrefix() . '-not-exists';
+
return Status::newFatal( $msg );
}
+
return false;
}
@@ -177,8 +207,10 @@ class SpecialRedirect extends FormSpecialPage {
$mp = $this->getMessagePrefix();
$ns = array(
// subpage => message
- // Messages: redirect-user, redirect-revision, redirect-file
+ // Messages: redirect-user, redirect-page, redirect-revision,
+ // redirect-file
'user' => $mp . '-user',
+ 'page' => $mp . '-page',
'revision' => $mp . '-revision',
'file' => $mp . '-file',
);
@@ -204,6 +236,7 @@ class SpecialRedirect extends FormSpecialPage {
if ( !empty( $this->mValue ) ) {
$a['value']['default'] = $this->mValue;
}
+
return $a;
}
@@ -211,6 +244,7 @@ class SpecialRedirect extends FormSpecialPage {
if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) {
$this->setParameter( $data['type'] . '/' . $data['value'] );
}
+
/* if this returns false, will show the form */
return $this->dispatch();
}
diff --git a/includes/specials/SpecialResetTokens.php b/includes/specials/SpecialResetTokens.php
index ef2a45da..4add7421 100644
--- a/includes/specials/SpecialResetTokens.php
+++ b/includes/specials/SpecialResetTokens.php
@@ -40,16 +40,15 @@ class SpecialResetTokens extends FormSpecialPage {
* @return array
*/
protected function getTokensList() {
- global $wgHiddenPrefs;
-
if ( !isset( $this->tokensList ) ) {
$tokens = array(
array( 'preference' => 'watchlisttoken', 'label-message' => 'resettokens-watchlist-token' ),
);
wfRunHooks( 'SpecialResetTokensTokens', array( &$tokens ) );
- $tokens = array_filter( $tokens, function ( $tok ) use ( $wgHiddenPrefs ) {
- return !in_array( $tok['preference'], $wgHiddenPrefs );
+ $hiddenPrefs = $this->getConfig()->get( 'HiddenPrefs' );
+ $tokens = array_filter( $tokens, function ( $tok ) use ( $hiddenPrefs ) {
+ return !in_array( $tok['preference'], $hiddenPrefs );
} );
$this->tokensList = $tokens;
@@ -61,6 +60,7 @@ class SpecialResetTokens extends FormSpecialPage {
public function execute( $par ) {
// This is a preferences page, so no user JS for y'all.
$this->getOutput()->disallowUserJs();
+ $this->requireLogin();
parent::execute( $par );
@@ -77,6 +77,7 @@ class SpecialResetTokens extends FormSpecialPage {
/**
* Display appropriate message if there's nothing to do.
* The submit button is also suppressed in this case (see alterForm()).
+ * @return array
*/
protected function getFormFields() {
$user = $this->getUser();
@@ -89,7 +90,7 @@ class SpecialResetTokens extends FormSpecialPage {
->rawParams( $this->msg( $tok['label-message'] )->parse() )
->params( $user->getTokenFromOption( $tok['preference'] ) )
->escaped();
- $tokensForForm[ $label ] = $tok['preference'];
+ $tokensForForm[$label] = $tok['preference'];
}
$desc = array(
@@ -112,6 +113,7 @@ class SpecialResetTokens extends FormSpecialPage {
/**
* Suppress the submit button if there's nothing to do;
* provide additional message on it otherwise.
+ * @param HTMLForm $form
*/
protected function alterForm( HTMLForm $form ) {
if ( $this->getTokensList() ) {
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index 825be6c4..7eea71da 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -28,61 +28,80 @@
* @ingroup SpecialPage
*/
class SpecialRevisionDelete extends UnlistedSpecialPage {
- /** True if the submit button was clicked, and the form was posted */
- var $submitClicked;
+ /** @var bool Was the DB modified in this request */
+ protected $wasSaved = false;
- /** Target ID list */
- var $ids;
+ /** @var bool True if the submit button was clicked, and the form was posted */
+ private $submitClicked;
- /** Archive name, for reviewing deleted files */
- var $archiveName;
+ /** @var array Target ID list */
+ private $ids;
- /** Edit token for securing image views against XSS */
- var $token;
+ /** @var string Archive name, for reviewing deleted files */
+ private $archiveName;
- /** Title object for target parameter */
- var $targetObj;
+ /** @var string Edit token for securing image views against XSS */
+ private $token;
- /** Deletion type, may be revision, archive, oldimage, filearchive, logging. */
- var $typeName;
+ /** @var Title Title object for target parameter */
+ private $targetObj;
- /** Array of checkbox specs (message, name, deletion bits) */
- var $checks;
+ /** @var string Deletion type, may be revision, archive, oldimage, filearchive, logging. */
+ private $typeName;
- /** UI Labels about the current type */
- var $typeLabels;
+ /** @var array Array of checkbox specs (message, name, deletion bits) */
+ private $checks;
- /** The RevDel_List object, storing the list of items to be deleted/undeleted */
- var $list;
+ /** @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.
*/
- static $UILabels = array(
+ private static $UILabels = array(
'revision' => array(
- 'check-label' => 'revdelete-hide-text',
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
+ 'check-label' => 'revdelete-hide-text',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'text' => 'revdelete-text-text',
+ 'selected'=> 'revdelete-selected-text',
),
'archive' => array(
- 'check-label' => 'revdelete-hide-text',
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
+ 'check-label' => 'revdelete-hide-text',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'text' => 'revdelete-text-text',
+ 'selected'=> 'revdelete-selected-text',
),
'oldimage' => array(
- 'check-label' => 'revdelete-hide-image',
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
+ 'check-label' => 'revdelete-hide-image',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'text' => 'revdelete-text-file',
+ 'selected'=> 'revdelete-selected-file',
),
'filearchive' => array(
- 'check-label' => 'revdelete-hide-image',
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
+ 'check-label' => 'revdelete-hide-image',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'text' => 'revdelete-text-file',
+ 'selected'=> 'revdelete-selected-file',
),
'logging' => array(
- 'check-label' => 'revdelete-hide-name',
- 'success' => 'logdelete-success',
- 'failure' => 'logdelete-failure',
+ 'check-label' => 'revdelete-hide-name',
+ 'success' => 'logdelete-success',
+ 'failure' => 'logdelete-failure',
+ 'text' => 'logdelete-text',
+ 'selected' => 'logdelete-selected',
),
);
@@ -113,7 +132,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// $this->ids = array_map( 'intval', $this->ids );
$this->ids = array_unique( array_filter( $this->ids ) );
- if ( $request->getVal( 'action' ) == 'historysubmit' || $request->getVal( 'action' ) == 'revisiondelete' ) {
+ if ( $request->getVal( 'action' ) == 'historysubmit'
+ || $request->getVal( 'action' ) == 'revisiondelete'
+ ) {
// For show/hide form submission from history page
// Since we are access through index.php?title=XXX&action=historysubmit
// getFullTitle() will contain the target title and not our title
@@ -129,6 +150,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->token = $request->getVal( 'token' );
if ( $this->archiveName && $this->targetObj ) {
$this->tryShowFile( $this->archiveName );
+
return;
}
@@ -138,16 +160,29 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
if ( !$this->typeName || count( $this->ids ) == 0 ) {
throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
}
- $this->typeLabels = self::$UILabels[$this->typeName];
- $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) );
# Allow the list type to adjust the passed target
- $this->targetObj = RevisionDeleter::suggestTarget( $this->typeName, $this->targetObj, $this->ids );
+ $this->targetObj = RevisionDeleter::suggestTarget(
+ $this->typeName,
+ $this->targetObj,
+ $this->ids
+ );
+
+ $this->typeLabels = self::$UILabels[$this->typeName];
+ $list = $this->getList();
+ $list->reset();
+ $bitfield = $list->current()->getBits();
+ $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) );
+ $canViewSuppressedOnly = $this->getUser()->isAllowed( 'viewsuppressed' ) &&
+ !$this->getUser()->isAllowed( 'suppressrevision' );
+ $pageIsSuppressed = $bitfield & Revision::DELETED_RESTRICTED;
+ $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
$this->otherReason = $request->getVal( 'wpReason' );
# We need a target page!
if ( is_null( $this->targetObj ) ) {
$output->addWikiMsg( 'undelete-header' );
+
return;
}
# Give a link to the logs/hist for this page
@@ -155,6 +190,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# Initialise checkboxes
$this->checks = array(
+ # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
array( $this->typeLabels['check-label'], 'wpHidePrimary',
RevisionDeleter::getRevdelConstant( $this->typeName )
),
@@ -177,14 +213,24 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# 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, '', array( 'lim' => 25, 'conds' => $qc ) );
+ LogEventsList::showLogExtract(
+ $output,
+ 'delete',
+ $this->targetObj,
+ '', /* user */
+ array( '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, '', array( 'lim' => 25, 'conds' => $qc ) );
+ LogEventsList::showLogExtract(
+ $output,
+ 'suppress',
+ $this->targetObj,
+ '',
+ array( 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved )
+ );
}
}
@@ -194,6 +240,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
protected function showConvenienceLinks() {
# 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 = array();
$links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log' ),
@@ -236,12 +285,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$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.
+ * @todo Mostly copied from Special:Undelete. Refactor.
+ * @param string $archiveName
*/
protected function tryShowFile( $archiveName ) {
$repo = RepoGroup::singleton()->getLocalRepo();
@@ -250,6 +301,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// Check if user is allowed to see this file
if ( !$oimage->exists() ) {
$this->getOutput()->addWikiMsg( 'revdelete-no-file' );
+
return;
}
$user = $this->getUser();
@@ -269,7 +321,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'POST',
- 'action' => $this->getTitle()->getLocalURL( array(
+ 'action' => $this->getPageTitle()->getLocalURL( array(
'target' => $this->targetObj->getPrefixedDBkey(),
'file' => $archiveName,
'token' => $user->getEditToken( $archiveName ),
@@ -279,6 +331,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::submitButton( $this->msg( 'revdelete-show-file-submit' )->text() ) .
'</form>'
);
+
return;
}
$this->getOutput()->disable();
@@ -287,7 +340,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# a user without appropriate permissions can toddle off and
# nab the image, and Squid 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(
+ 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
+ );
$this->getRequest()->response()->header( 'Pragma: no-cache' );
$key = $oimage->getStorageKey();
@@ -297,14 +352,16 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
/**
* Get the list object for this request
+ * @return RevDelList
*/
protected function getList() {
- if ( is_null( $this->list ) ) {
- $this->list = RevisionDeleter::createList(
+ if ( is_null( $this->revDelList ) ) {
+ $this->revDelList = RevisionDeleter::createList(
$this->typeName, $this->getContext(), $this->targetObj, $this->ids
);
}
- return $this->list;
+
+ return $this->revDelList;
}
/**
@@ -312,28 +369,29 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* which will allow the user to choose new visibility settings.
*/
protected function showForm() {
- $UserAllowed = true;
+ $userAllowed = true;
- if ( $this->typeName == 'logging' ) {
- $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLanguage()->formatNum( count( $this->ids ) ) );
- } else {
- $this->getOutput()->addWikiMsg( 'revdelete-selected',
- $this->targetObj->getPrefixedText(), count( $this->ids ) );
- }
+ // Messages: revdelete-selected-text, revdelete-selected-file, logdelete-selected
+ $this->getOutput()->wrapWikiMsg( "<strong>$1</strong>", array( $this->typeLabels['selected'],
+ $this->getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ) );
$this->getOutput()->addHTML( "<ul>" );
$numRevisions = 0;
// Live revisions...
$list = $this->getList();
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
for ( $list->reset(); $list->current(); $list->next() ) {
+ // @codingStandardsIgnoreEnd
$item = $list->current();
+
if ( !$item->canView() ) {
if ( !$this->submitClicked ) {
throw new PermissionsError( 'suppressrevision' );
}
- $UserAllowed = false;
+ $userAllowed = false;
}
+
$numRevisions++;
$this->getOutput()->addHTML( $item->getHTML() );
}
@@ -347,14 +405,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->addUsageText();
// Normal sysops can always see what they did, but can't always change it
- if ( !$UserAllowed ) {
+ if ( !$userAllowed ) {
return;
}
// Show form if the user can submit
if ( $this->mIsAllowed ) {
$out = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ),
+ 'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ),
'id' => 'mw-revdel-form-revisions' ) ) .
Xml::fieldset( $this->msg( 'revdelete-legend' )->text() ) .
$this->buildCheckBoxes() .
@@ -367,7 +425,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::listDropDown( 'wpRevDeleteReasonList',
$this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->text(),
$this->msg( 'revdelete-reasonotherlist' )->inContentLanguage()->text(),
- $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown', 1
+ $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown'
) .
'</td>' .
"</tr><tr>\n" .
@@ -375,7 +433,12 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::label( $this->msg( 'revdelete-otherreason' )->text(), 'wpReason' ) .
'</td>' .
'<td class="mw-input">' .
- Xml::input( 'wpReason', 60, $this->otherReason, array( 'id' => 'wpReason', 'maxlength' => 100 ) ) .
+ Xml::input(
+ 'wpReason',
+ 60,
+ $this->otherReason,
+ array( 'id' => 'wpReason', 'maxlength' => 100 )
+ ) .
'</td>' .
"</tr><tr>\n" .
'<td></td>' .
@@ -389,12 +452,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
Html::hidden( 'type', $this->typeName ) .
Html::hidden( 'ids', implode( ',', $this->ids ) ) .
- Xml::closeElement( 'fieldset' ) . "\n";
- } else {
- $out = '';
- }
- if ( $this->mIsAllowed ) {
- $out .= Xml::closeElement( 'form' ) . "\n";
+ Xml::closeElement( 'fieldset' ) . "\n" .
+ Xml::closeElement( 'form' ) . "\n";
// Show link to edit the dropdown reasons
if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
$title = Title::makeTitle( NS_MEDIAWIKI, 'Revdelete-reason-dropdown' );
@@ -406,6 +465,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
);
$out .= Xml::tags( 'p', array( 'class' => 'mw-revdel-editreasons' ), $link ) . "\n";
}
+ } else {
+ $out = '';
}
$this->getOutput()->addHTML( $out );
}
@@ -415,17 +476,23 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* @todo FIXME: Wikimedia-specific policy text
*/
protected function addUsageText() {
- $this->getOutput()->addWikiMsg( 'revdelete-text' );
+ // 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
+ * @return string HTML
*/
protected function buildCheckBoxes() {
$html = '<table>';
@@ -434,26 +501,42 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
if ( $list->length() == 1 ) {
$list->reset();
$bitfield = $list->current()->getBits(); // existing field
+
if ( $this->submitClicked ) {
- $bitfield = $this->extractBitfield( $this->extractBitParams(), $bitfield );
+ $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 );
+ $innerHTML = Xml::checkLabel(
+ $this->msg( $message )->text(),
+ $name,
+ $name,
+ $bitfield & $field
+ );
+
if ( $field == Revision::DELETED_RESTRICTED ) {
$innerHTML = "<b>$innerHTML</b>";
}
+
$line = Xml::tags( 'td', array( 'class' => 'mw-input' ), $innerHTML );
$html .= "<tr>$line</tr>\n";
}
- // Otherwise, use tri-state radios
} 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 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 ) {
@@ -474,6 +557,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
$html .= '</table>';
+
return $html;
}
@@ -487,30 +571,37 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$token = $this->getRequest()->getVal( 'wpEditToken' );
if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
$this->getOutput()->addWikiMsg( 'sessionfailure' );
+
return false;
}
$bitParams = $this->extractBitParams();
- $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown
+ // from dropdown
+ $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' );
$comment = $listReason;
- if ( $comment != 'other' && $this->otherReason != '' ) {
- // Entry from drop down menu + additional comment
- $comment .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->otherReason;
- } elseif ( $comment == 'other' ) {
+ 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' ) ) {
+ 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, $this->targetObj );
if ( $status->isGood() ) {
$this->success();
+
return true;
- # ...otherwise, bounce back to form...
} else {
+ # ...otherwise, bounce back to form...
$this->failure( $status );
}
+
return false;
}
@@ -518,16 +609,23 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* Report that the submit operation succeeded
*/
protected function success() {
+ // Messages: revdelete-success, logdelete-success
$this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
- $this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeLabels['success'] );
- $this->list->reloadFromMaster();
+ $this->getOutput()->wrapWikiMsg(
+ "<span class=\"success\">\n$1\n</span>",
+ $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()->addWikiText( $status->getWikiText( $this->typeLabels['failure'] ) );
$this->showForm();
@@ -551,26 +649,16 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
if ( !isset( $bitfield[Revision::DELETED_RESTRICTED] ) ) {
$bitfield[Revision::DELETED_RESTRICTED] = 0;
}
- return $bitfield;
- }
- /**
- * Put together a rev_deleted bitfield
- * @deprecated since 1.22, use RevisionDeleter::extractBitfield instead
- * @param array $bitPars extractBitParams() params
- * @param int $oldfield current bitfield
- * @return array
- */
- public static function extractBitfield( $bitPars, $oldfield ) {
- return RevisionDeleter::extractBitfield( $bitPars, $oldfield );
+ return $bitfield;
}
/**
- * Do the write operations. Simple wrapper for RevDel_*List::setVisibility().
- * @param $bitfield
- * @param $reason
- * @param $title
- * @return
+ * Do the write operations. Simple wrapper for RevDel*List::setVisibility().
+ * @param int $bitfield
+ * @param string $reason
+ * @param Title $title
+ * @return Status
*/
protected function save( $bitfield, $reason, $title ) {
return $this->getList()->setVisibility(
diff --git a/includes/specials/SpecialRunJobs.php b/includes/specials/SpecialRunJobs.php
new file mode 100644
index 00000000..d4a06eb5
--- /dev/null
+++ b/includes/specials/SpecialRunJobs.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Implements Special:RunJobs
+ *
+ * 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Special page designed for running background tasks (internal use only)
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialRunJobs extends UnlistedSpecialPage {
+ public function __construct() {
+ parent::__construct( 'RunJobs' );
+ }
+
+ public function execute( $par = '' ) {
+ $this->getOutput()->disable();
+
+ if ( wfReadOnly() ) {
+ header( "HTTP/1.0 423 Locked" );
+ print 'Wiki is in read-only mode';
+
+ return;
+ } elseif ( !$this->getRequest()->wasPosted() ) {
+ header( "HTTP/1.0 400 Bad Request" );
+ print 'Request must be POSTed';
+
+ return;
+ }
+
+ $optional = array( 'maxjobs' => 0, 'maxtime' => 30, 'type' => false, 'async' => true );
+ $required = array_flip( array( 'title', 'tasks', 'signature', 'sigexpiry' ) );
+
+ $params = array_intersect_key( $this->getRequest()->getValues(), $required + $optional );
+ $missing = array_diff_key( $required, $params );
+ if ( count( $missing ) ) {
+ header( "HTTP/1.0 400 Bad Request" );
+ print 'Missing parameters: ' . implode( ', ', array_keys( $missing ) );
+
+ return;
+ }
+
+ $squery = $params;
+ unset( $squery['signature'] );
+ $cSig = self::getQuerySignature( $squery, $this->getConfig()->get( 'SecretKey' ) ); // correct signature
+ $rSig = $params['signature']; // provided signature
+
+ $verified = is_string( $rSig ) && hash_equals( $cSig, $rSig );
+ if ( !$verified || $params['sigexpiry'] < time() ) {
+ header( "HTTP/1.0 400 Bad Request" );
+ print 'Invalid or stale signature provided';
+
+ return;
+ }
+
+ // Apply any default parameter values
+ $params += $optional;
+
+ if ( $params['async'] ) {
+ // Client will usually disconnect before checking the response,
+ // but it needs to know when it is safe to disconnect. Until this
+ // reaches ignore_user_abort(), it is not safe as the jobs won't run.
+ ignore_user_abort( true ); // jobs may take a bit of time
+ header( "HTTP/1.0 202 Accepted" );
+ ob_flush();
+ flush();
+ // Once the client receives this response, it can disconnect
+ }
+
+ // Do all of the specified tasks...
+ if ( in_array( 'jobs', explode( '|', $params['tasks'] ) ) ) {
+ $runner = new JobRunner();
+ $response = $runner->run( array(
+ 'type' => $params['type'],
+ 'maxJobs' => $params['maxjobs'] ? $params['maxjobs'] : 1,
+ 'maxTime' => $params['maxtime'] ? $params['maxjobs'] : 30
+ ) );
+ if ( !$params['async'] ) {
+ print FormatJson::encode( $response, true );
+ }
+ }
+ }
+
+ /**
+ * @param array $query
+ * @param string $secretKey
+ * @return string
+ */
+ public static function getQuerySignature( array $query, $secretKey ) {
+ ksort( $query ); // stable order
+ return hash_hmac( 'sha1', wfArrayToCgi( $query ), $secretKey );
+ }
+}
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index 8609c740..88ab7d82 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -30,25 +30,24 @@
class SpecialSearch extends SpecialPage {
/**
* Current search profile. Search profile is just a name that identifies
- * the active search tab on the search page (content, help, discussions...)
+ * the active search tab on the search page (content, discussions...)
* For users tt replaces the set of enabled namespaces from the query
* string when applicable. Extensions can add new profiles with hooks
* with custom search options just for that profile.
- * null|string
+ * @var null|string
*/
protected $profile;
- function getProfile() { return $this->profile; }
- /// Search engine
+ /** @var SearchEngine Search engine */
protected $searchEngine;
- /// Search engine type, if not default
+ /** @var string Search engine type, if not default */
protected $searchEngineType;
- /// For links
+ /** @var array For links */
protected $extraParams = array();
- /// No idea, apparently used by some other classes
+ /** @var string No idea, apparently used by some other classes */
protected $mPrefix;
/**
@@ -60,12 +59,6 @@ class SpecialSearch extends SpecialPage {
* @var array
*/
protected $namespaces;
- function getNamespaces() { return $this->namespaces; }
-
- /**
- * @var bool
- */
- protected $searchRedirects;
/**
* @var string
@@ -81,14 +74,17 @@ class SpecialSearch extends SpecialPage {
/**
* Entry point
*
- * @param string $par or null
+ * @param string $par
*/
public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$out->allowClickjacking();
- $out->addModuleStyles( 'mediawiki.special' );
+ $out->addModuleStyles( array(
+ 'mediawiki.special', 'mediawiki.special.search', 'mediawiki.ui', 'mediawiki.ui.button',
+ 'mediawiki.ui.input',
+ ) );
// Strip underscores from title parameter; most of the time we'll want
// text form here. But don't strip underscores from actual text params!
@@ -100,13 +96,22 @@ class SpecialSearch extends SpecialPage {
$search = str_replace( "\n", " ", $request->getText( 'search', $titleParam ) );
$this->load();
+ if ( !is_null( $request->getVal( 'nsRemember' ) ) ) {
+ $this->saveNamespaces();
+ // Remove the token from the URL to prevent the user from inadvertently
+ // exposing it (e.g. by pasting it into a public wiki page) or undoing
+ // later settings changes (e.g. by reloading the page).
+ $query = $request->getValues();
+ unset( $query['title'], $query['nsRemember'] );
+ $out->redirect( $this->getPageTitle()->getFullURL( $query ) );
+ return;
+ }
$this->searchEngineType = $request->getVal( 'srbackend' );
if ( $request->getVal( 'fulltext' )
|| !is_null( $request->getVal( 'offset' ) )
- || !is_null( $request->getVal( 'searchx' ) ) )
- {
+ ) {
$this->showResults( $search );
} else {
$this->goResult( $search );
@@ -120,7 +125,7 @@ class SpecialSearch extends SpecialPage {
*/
public function load() {
$request = $this->getRequest();
- list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
+ list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, '' );
$this->mPrefix = $request->getVal( 'prefix', '' );
$user = $this->getUser();
@@ -160,9 +165,6 @@ class SpecialSearch extends SpecialPage {
}
}
- // Redirects defaults to true, but we don't know whether it was ticked of or just missing
- $default = $request->getBool( 'profile' ) ? 0 : 1;
- $this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0;
$this->didYouMeanHtml = ''; # html of did you mean... link
$this->fulltext = $request->getVal( 'fulltext' );
$this->profile = $profile;
@@ -171,57 +173,44 @@ class SpecialSearch extends SpecialPage {
/**
* If an exact title match can be found, jump straight ahead to it.
*
- * @param $term String
+ * @param string $term
*/
public function goResult( $term ) {
$this->setupPage( $term );
# Try to go to page as entered.
- $t = Title::newFromText( $term );
+ $title = Title::newFromText( $term );
# If the string cannot be used to create a title
- if ( is_null( $t ) ) {
+ if ( is_null( $title ) ) {
$this->showResults( $term );
+
return;
}
# If there's an exact or very near match, jump right there.
- $t = SearchEngine::getNearMatch( $term );
+ $title = SearchEngine::getNearMatch( $term );
- if ( !wfRunHooks( 'SpecialSearchGo', array( &$t, &$term ) ) ) {
- # Hook requested termination
- return;
- }
+ if ( !is_null( $title ) ) {
+ $this->getOutput()->redirect( $title->getFullURL() );
- if ( !is_null( $t ) ) {
- $this->getOutput()->redirect( $t->getFullURL() );
return;
}
# No match, generate an edit URL
- $t = Title::newFromText( $term );
- if ( !is_null( $t ) ) {
- global $wgGoToEdit;
- wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
- wfDebugLog( 'nogomatch', $t->getText(), false );
-
- # If the feature is enabled, go straight to the edit page
- if ( $wgGoToEdit ) {
- $this->getOutput()->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
- return;
- }
+ $title = Title::newFromText( $term );
+ if ( !is_null( $title ) ) {
+ wfRunHooks( 'SpecialSearchNogomatch', array( &$title ) );
}
$this->showResults( $term );
}
/**
- * @param $term String
+ * @param string $term
*/
public function showResults( $term ) {
- global $wgDisableTextSearch, $wgSearchForwardUrl, $wgContLang, $wgScript;
- wfProfileIn( __METHOD__ );
+ global $wgContLang;
+ $profile = new ProfileSection( __METHOD__ );
$search = $this->getSearchEngine();
$search->setLimitOffset( $this->limit, $this->offset );
$search->setNamespaces( $this->namespaces );
- $search->showRedirects = $this->searchRedirects; // BC
- $search->setFeatureData( 'list-redirects', $this->searchRedirects );
$search->prefix = $this->mPrefix;
$term = $search->transformSearchTerm( $term );
@@ -231,15 +220,20 @@ class SpecialSearch extends SpecialPage {
$out = $this->getOutput();
- if ( $wgDisableTextSearch ) {
- if ( $wgSearchForwardUrl ) {
- $url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl );
+ if ( $this->getConfig()->get( 'DisableTextSearch' ) ) {
+ $searchFowardUrl = $this->getConfig()->get( 'SearchForwardUrl' );
+ if ( $searchFowardUrl ) {
+ $url = str_replace( '$1', urlencode( $term ), $searchFowardUrl );
$out->redirect( $url );
} else {
$out->addHTML(
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, $this->msg( 'search-external' )->text() ) .
- Xml::element( 'p', array( 'class' => 'mw-searchdisabled' ), $this->msg( 'searchdisabled' )->text() ) .
+ Xml::element(
+ 'p',
+ array( 'class' => 'mw-searchdisabled' ),
+ $this->msg( 'searchdisabled' )->text()
+ ) .
$this->msg( 'googlesearch' )->rawParams(
htmlspecialchars( $term ),
'UTF-8',
@@ -248,19 +242,19 @@ class SpecialSearch extends SpecialPage {
Xml::closeElement( 'fieldset' )
);
}
- wfProfileOut( __METHOD__ );
+
return;
}
- $t = Title::newFromText( $term );
+ $title = Title::newFromText( $term );
+ $showSuggestion = $title === null || !$title->isKnown();
+ $search->setShowSuggestion( $showSuggestion );
// fetch search results
$rewritten = $search->replacePrefixes( $term );
$titleMatches = $search->searchTitle( $rewritten );
- if ( !( $titleMatches instanceof SearchResultTooMany ) ) {
- $textMatches = $search->searchText( $rewritten );
- }
+ $textMatches = $search->searchText( $rewritten );
$textStatus = null;
if ( $textMatches instanceof Status ) {
@@ -269,9 +263,7 @@ class SpecialSearch extends SpecialPage {
}
// did you mean... suggestions
- if ( $textMatches && !$textStatus && $textMatches->hasSuggestion() ) {
- $st = SpecialPage::getTitleFor( 'Search' );
-
+ if ( $showSuggestion && $textMatches && !$textStatus && $textMatches->hasSuggestion() ) {
# mirror Go/Search behavior of original request ..
$didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
@@ -291,18 +283,18 @@ class SpecialSearch extends SpecialPage {
}
$suggestLink = Linker::linkKnown(
- $st,
+ $this->getPageTitle(),
$suggestionSnippet,
array(),
$stParams
);
- $this->didYouMeanHtml = '<div class="searchdidyoumean">' . $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() . '</div>';
+ $this->didYouMeanHtml = '<div class="searchdidyoumean">'
+ . $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() . '</div>';
}
if ( !wfRunHooks( 'SpecialSearchResultsPrepend', array( $this, $out, $term ) ) ) {
# Hook requested termination
- wfProfileOut( __METHOD__ );
return;
}
@@ -313,78 +305,62 @@ class SpecialSearch extends SpecialPage {
array(
'id' => ( $this->profile === 'advanced' ? 'powersearch' : 'search' ),
'method' => 'get',
- 'action' => $wgScript
+ 'action' => wfScript(),
)
)
);
- $out->addHtml(
- Xml::openElement( 'table', array( 'id' => 'mw-search-top-table', 'cellpadding' => 0, 'cellspacing' => 0 ) ) .
- Xml::openElement( 'tr' ) .
- Xml::openElement( 'td' ) . "\n" .
- $this->shortDialog( $term ) .
- Xml::closeElement( 'td' ) .
- Xml::closeElement( 'tr' ) .
- Xml::closeElement( 'table' )
- );
- // Sometimes the search engine knows there are too many hits
- if ( $titleMatches instanceof SearchResultTooMany ) {
- $out->wrapWikiMsg( "==$1==\n", 'toomanymatches' );
- wfProfileOut( __METHOD__ );
- return;
+ // Get number of results
+ $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0;
+ if ( $titleMatches ) {
+ $titleMatchesNum = $titleMatches->numRows();
+ $numTitleMatches = $titleMatches->getTotalHits();
+ }
+ if ( $textMatches ) {
+ $textMatchesNum = $textMatches->numRows();
+ $numTextMatches = $textMatches->getTotalHits();
}
+ $num = $titleMatchesNum + $textMatchesNum;
+ $totalRes = $numTitleMatches + $numTextMatches;
+
+ $out->addHtml(
+ # This is an awful awful ID name. It's not a table, but we
+ # named it poorly from when this was a table so now we're
+ # stuck with it
+ Xml::openElement( 'div', array( 'id' => 'mw-search-top-table' ) ) .
+ $this->shortDialog( $term, $num, $totalRes ) .
+ Xml::closeElement( 'div' ) .
+ $this->formHeader( $term ) .
+ Xml::closeElement( 'form' )
+ );
$filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':';
if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
- $out->addHTML( $this->formHeader( $term, 0, 0 ) );
- $out->addHtml( $this->getProfileForm( $this->profile, $term ) );
- $out->addHTML( '</form>' );
// Empty query -- straight view of search form
- wfProfileOut( __METHOD__ );
return;
}
- // Get number of results
- $titleMatchesNum = $titleMatches ? $titleMatches->numRows() : 0;
- $textMatchesNum = $textMatches ? $textMatches->numRows() : 0;
- // Total initial query matches (possible false positives)
- $num = $titleMatchesNum + $textMatchesNum;
-
- // Get total actual results (after second filtering, if any)
- $numTitleMatches = $titleMatches && !is_null( $titleMatches->getTotalHits() ) ?
- $titleMatches->getTotalHits() : $titleMatchesNum;
- $numTextMatches = $textMatches && !is_null( $textMatches->getTotalHits() ) ?
- $textMatches->getTotalHits() : $textMatchesNum;
-
- // get total number of results if backend can calculate it
- $totalRes = 0;
- if ( $titleMatches && !is_null( $titleMatches->getTotalHits() ) ) {
- $totalRes += $titleMatches->getTotalHits();
- }
- if ( $textMatches && !is_null( $textMatches->getTotalHits() ) ) {
- $totalRes += $textMatches->getTotalHits();
- }
-
- // show number of results and current offset
- $out->addHTML( $this->formHeader( $term, $num, $totalRes ) );
- $out->addHtml( $this->getProfileForm( $this->profile, $term ) );
-
- $out->addHtml( Xml::closeElement( 'form' ) );
$out->addHtml( "<div class='searchresults'>" );
// prev/next links
+ $prevnext = null;
if ( $num || $this->offset ) {
// Show the create link ahead
- $this->showCreateLink( $t );
- $prevnext = $this->getLanguage()->viewPrevNext( $this->getTitle(), $this->offset, $this->limit,
- $this->powerSearchOptions() + array( 'search' => $term ),
- max( $titleMatchesNum, $textMatchesNum ) < $this->limit
- );
- //$out->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
- wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
- } else {
- wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
+ $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
+ if ( $totalRes > $this->limit || $this->offset ) {
+ if ( $this->searchEngineType !== null ) {
+ $this->setExtraParam( 'srbackend', $this->searchEngineType );
+ }
+ $prevnext = $this->getLanguage()->viewPrevNext(
+ $this->getPageTitle(),
+ $this->offset,
+ $this->limit,
+ $this->powerSearchOptions() + array( 'search' => $term ),
+ $this->limit + $this->offset >= $totalRes
+ );
+ }
}
+ wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
$out->parserOptions()->setEditSection( false );
if ( $titleMatches ) {
@@ -399,10 +375,8 @@ class SpecialSearch extends SpecialPage {
if ( $numTextMatches > 0 && $numTitleMatches > 0 ) {
// if no title matches the heading is redundant
$out->wrapWikiMsg( "==$1==\n", 'textmatches' );
- } elseif ( $totalRes == 0 ) {
- # Don't show the 'no text matches' if we received title matches
- # $out->wrapWikiMsg( "==$1==\n", 'notextmatches' );
}
+
// show interwiki results if any
if ( $textMatches->hasInterwikiResults() ) {
$out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
@@ -417,49 +391,60 @@ class SpecialSearch extends SpecialPage {
if ( $num === 0 ) {
if ( $textStatus ) {
$out->addHTML( '<div class="error">' .
- htmlspecialchars( $textStatus->getWikiText( 'search-error' ) ) . '</div>' );
+ $textStatus->getMessage( 'search-error' ) . '</div>' );
} else {
$out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>",
array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
- $this->showCreateLink( $t );
+ $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
}
}
$out->addHtml( "</div>" );
- if ( $num || $this->offset ) {
+ if ( $prevnext ) {
$out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
}
- wfRunHooks( 'SpecialSearchResultsAppend', array( $this, $out, $term ) );
- wfProfileOut( __METHOD__ );
}
/**
- * @param $t Title
+ * @param Title $title
+ * @param int $num The number of search results found
+ * @param null|SearchResultSet $titleMatches Results from title search
+ * @param null|SearchResultSet $textMatches Results from text search
*/
- protected function showCreateLink( $t ) {
+ protected function showCreateLink( $title, $num, $titleMatches, $textMatches ) {
// show direct page/create link if applicable
// Check DBkey !== '' in case of fragment link only.
- if ( is_null( $t ) || $t->getDBkey() === '' ) {
+ if ( is_null( $title ) || $title->getDBkey() === ''
+ || ( $titleMatches !== null && $titleMatches->searchContainedSyntax() )
+ || ( $textMatches !== null && $textMatches->searchContainedSyntax() )
+ ) {
// invalid title
// preserve the paragraph for margins etc...
$this->getOutput()->addHtml( '<p></p>' );
+
return;
}
- if ( $t->isKnown() ) {
+ $linkClass = 'mw-search-createlink';
+ if ( $title->isKnown() ) {
$messageName = 'searchmenu-exists';
- } elseif ( $t->userCan( 'create', $this->getUser() ) ) {
+ $linkClass = 'mw-search-exists';
+ } elseif ( $title->quickUserCan( 'create', $this->getUser() ) ) {
$messageName = 'searchmenu-new';
} else {
$messageName = 'searchmenu-new-nocreate';
}
- $params = array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) );
- wfRunHooks( 'SpecialSearchCreateLink', array( $t, &$params ) );
+ $params = array(
+ $messageName,
+ wfEscapeWikiText( $title->getPrefixedText() ),
+ Message::numParam( $num )
+ );
+ wfRunHooks( 'SpecialSearchCreateLink', array( $title, &$params ) );
// Extensions using the hook might still return an empty $messageName
if ( $messageName ) {
- $this->getOutput()->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", $params );
+ $this->getOutput()->wrapWikiMsg( "<p class=\"$linkClass\">\n$1</p>", $params );
} else {
// preserve the paragraph for margins etc...
$this->getOutput()->addHtml( '<p></p>' );
@@ -467,7 +452,7 @@ class SpecialSearch extends SpecialPage {
}
/**
- * @param $term string
+ * @param string $term
*/
protected function setupPage( $term ) {
# Should advanced UI be used?
@@ -475,9 +460,10 @@ class SpecialSearch extends SpecialPage {
$out = $this->getOutput();
if ( strval( $term ) !== '' ) {
$out->setPageTitle( $this->msg( 'searchresults' ) );
- $out->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams(
- $this->msg( 'searchresults-title' )->rawParams( $term )->text()
- ) );
+ $out->setHTMLTitle( $this->msg( 'pagetitle' )
+ ->rawParams( $this->msg( 'searchresults-title' )->rawParams( $term )->text() )
+ ->inContentLanguage()->text()
+ );
}
// add javascript specific to special:search
$out->addModules( 'mediawiki.special.search' );
@@ -487,8 +473,8 @@ class SpecialSearch extends SpecialPage {
* Extract "power search" namespace settings from the request object,
* returning a list of index numbers to search.
*
- * @param $request WebRequest
- * @return Array
+ * @param WebRequest $request
+ * @return array
*/
protected function powerSearch( &$request ) {
$arr = array();
@@ -504,11 +490,10 @@ class SpecialSearch extends SpecialPage {
/**
* Reconstruct the 'power search' options for links
*
- * @return Array
+ * @return array
*/
protected function powerSearchOptions() {
$opt = array();
- $opt['redirs'] = $this->searchRedirects ? 1 : 0;
if ( $this->profile !== 'advanced' ) {
$opt['profile'] = $this->profile;
} else {
@@ -516,28 +501,58 @@ class SpecialSearch extends SpecialPage {
$opt['ns' . $n] = 1;
}
}
+
return $opt + $this->extraParams;
}
/**
+ * Save namespace preferences when we're supposed to
+ *
+ * @return bool Whether we wrote something
+ */
+ protected function saveNamespaces() {
+ $user = $this->getUser();
+ $request = $this->getRequest();
+
+ if ( $user->isLoggedIn() &&
+ $user->matchEditToken(
+ $request->getVal( 'nsRemember' ),
+ 'searchnamespace',
+ $request
+ )
+ ) {
+ // Reset namespace preferences: namespaces are not searched
+ // when they're not mentioned in the URL parameters.
+ foreach ( MWNamespace::getValidNamespaces() as $n ) {
+ $user->setOption( 'searchNs' . $n, false );
+ }
+ // The request parameters include all the namespaces to be searched.
+ // Even if they're the same as an existing profile, they're not eaten.
+ foreach ( $this->namespaces as $n ) {
+ $user->setOption( 'searchNs' . $n, true );
+ }
+
+ $user->saveSettings();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
* Show whole set of results
*
- * @param $matches SearchResultSet
+ * @param SearchResultSet $matches
*
* @return string
*/
protected function showMatches( &$matches ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
+ $profile = new ProfileSection( __METHOD__ );
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
- $out = "";
- $infoLine = $matches->getInfo();
- if ( !is_null( $infoLine ) ) {
- $out .= "\n<!-- {$infoLine} -->\n";
- }
- $out .= "<ul class='mw-search-results'>\n";
+ $out = "<ul class='mw-search-results'>\n";
$result = $matches->next();
while ( $result ) {
$out .= $this->showHit( $result, $terms );
@@ -547,27 +562,26 @@ class SpecialSearch extends SpecialPage {
// convert the whole thing to desired language variant
$out = $wgContLang->convert( $out );
- wfProfileOut( __METHOD__ );
+
return $out;
}
/**
* Format a single hit result
*
- * @param $result SearchResult
- * @param array $terms terms to highlight
+ * @param SearchResult $result
+ * @param array $terms Terms to highlight
*
* @return string
*/
protected function showHit( $result, $terms ) {
- wfProfileIn( __METHOD__ );
+ $profile = new ProfileSection( __METHOD__ );
if ( $result->isBrokenTitle() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- Broken link in search result -->\n";
+ return '';
}
- $t = $result->getTitle();
+ $title = $result->getTitle();
$titleSnippet = $result->getTitleSnippet( $terms );
@@ -575,10 +589,10 @@ class SpecialSearch extends SpecialPage {
$titleSnippet = null;
}
- $link_t = clone $t;
+ $link_t = clone $title;
wfRunHooks( 'ShowSearchHitTitle',
- array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
+ array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
$link = Linker::linkKnown(
$link_t,
@@ -588,8 +602,7 @@ class SpecialSearch extends SpecialPage {
//If page content is not readable, just return the title.
//This is not quite safe, but better than showing excerpts from non-readable pages
//Note that hiding the entry entirely would screw up paging.
- if ( !$t->userCan( 'read', $this->getUser() ) ) {
- wfProfileOut( __METHOD__ );
+ if ( !$title->userCan( 'read', $this->getUser() ) ) {
return "<li>{$link}</li>\n";
}
@@ -597,8 +610,7 @@ class SpecialSearch extends SpecialPage {
// The least confusing at this point is to drop the result.
// You may get less results, but... oh well. :P
if ( $result->isMissingRevision() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- missing page " . htmlspecialchars( $t->getPrefixedText() ) . "-->\n";
+ return '';
}
// format redirects / relevant sections
@@ -637,16 +649,6 @@ class SpecialSearch extends SpecialPage {
$lang = $this->getLanguage();
- // format score
- if ( is_null( $result->getScore() ) ) {
- // Search engine doesn't report scoring info
- $score = '';
- } else {
- $percent = sprintf( '%2.1f', $result->getScore() * 100 );
- $score = $this->msg( 'search-result-score' )->numParams( $percent )->text()
- . ' - ';
- }
-
// format description
$byteSize = $result->getByteSize();
$wordCount = $result->getWordCount();
@@ -654,8 +656,8 @@ class SpecialSearch extends SpecialPage {
$size = $this->msg( 'search-result-size', $lang->formatSize( $byteSize ) )
->numParams( $wordCount )->escaped();
- if ( $t->getNamespace() == NS_CATEGORY ) {
- $cat = Category::newFromTitle( $t );
+ if ( $title->getNamespace() == NS_CATEGORY ) {
+ $cat = Category::newFromTitle( $title );
$size = $this->msg( 'search-result-category-size' )
->numParams( $cat->getPageCount(), $cat->getSubcatCount(), $cat->getFileCount() )
->escaped();
@@ -663,35 +665,19 @@ class SpecialSearch extends SpecialPage {
$date = $lang->userTimeAndDate( $timestamp, $this->getUser() );
- // link to related articles if supported
- $related = '';
- if ( $result->hasRelated() ) {
- $st = SpecialPage::getTitleFor( 'Search' );
- $stParams = array_merge(
- $this->powerSearchOptions(),
- array(
- 'search' => $this->msg( 'searchrelated' )->inContentLanguage()->text() .
- ':' . $t->getPrefixedText(),
- 'fulltext' => $this->msg( 'search' )->text()
- )
- );
-
- $related = ' -- ' . Linker::linkKnown(
- $st,
- $this->msg( 'search-relatedarticle' )->text(),
- array(),
- $stParams
- );
- }
-
+ $fileMatch = '';
// Include a thumbnail for media files...
- if ( $t->getNamespace() == NS_FILE ) {
- $img = wfFindFile( $t );
+ if ( $title->getNamespace() == NS_FILE ) {
+ $img = $result->getFile();
+ $img = $img ?: wfFindFile( $title );
+ if ( $result->isFileMatch() ) {
+ $fileMatch = "<span class='searchalttitle'>" .
+ $this->msg( 'search-file-match' )->escaped() . "</span>";
+ }
if ( $img ) {
$thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
if ( $thumb ) {
$desc = $this->msg( 'parentheses' )->rawParams( $img->getShortDesc() )->escaped();
- wfProfileOut( __METHOD__ );
// Float doesn't seem to interact well with the bullets.
// Table messes up vertical alignment of the bullets.
// Bullets are therefore disabled (didn't look great anyway).
@@ -702,9 +688,9 @@ class SpecialSearch extends SpecialPage {
$thumb->toHtml( array( 'desc-link' => true ) ) .
'</td>' .
'<td style="vertical-align: top;">' .
- $link .
+ "{$link} {$redirect} {$section} {$fileMatch}" .
$extract .
- "<div class='mw-search-result-data'>{$score}{$desc} - {$date}{$related}</div>" .
+ "<div class='mw-search-result-data'>{$desc} - {$date}</div>" .
'</td>' .
'</tr>' .
'</table>' .
@@ -715,33 +701,33 @@ class SpecialSearch extends SpecialPage {
$html = null;
+ $score = '';
if ( wfRunHooks( 'ShowSearchHit', array(
$this, $result, $terms,
&$link, &$redirect, &$section, &$extract,
&$score, &$size, &$date, &$related,
&$html
) ) ) {
- $html = "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" .
- "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
+ $html = "<li><div class='mw-search-result-heading'>" .
+ "{$link} {$redirect} {$section} {$fileMatch}</div> {$extract}\n" .
+ "<div class='mw-search-result-data'>{$size} - {$date}</div>" .
"</li>\n";
}
- wfProfileOut( __METHOD__ );
return $html;
}
/**
* Show results from other wikis
*
- * @param $matches SearchResultSet
- * @param $query String
+ * @param SearchResultSet|array $matches
+ * @param string $query
*
* @return string
*/
- protected function showInterwiki( &$matches, $query ) {
+ protected function showInterwiki( $matches, $query ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
- $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
+ $profile = new ProfileSection( __METHOD__ );
$out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>" .
$this->msg( 'search-interwiki-caption' )->text() . "</div>\n";
@@ -749,7 +735,8 @@ class SpecialSearch extends SpecialPage {
// work out custom project captions
$customCaptions = array();
- $customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() ); // format per line <iwprefix>:<caption>
+ // format per line <iwprefix>:<caption>
+ $customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() );
foreach ( $customLines as $line ) {
$parts = explode( ":", $line, 2 );
if ( count( $parts ) == 2 ) { // validate line
@@ -757,57 +744,62 @@ class SpecialSearch extends SpecialPage {
}
}
- $prev = null;
- $result = $matches->next();
- while ( $result ) {
- $out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
- $prev = $result->getInterwikiPrefix();
- $result = $matches->next();
+ if ( !is_array( $matches ) ) {
+ $matches = array( $matches );
}
- // TODO: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
+
+ foreach ( $matches as $set ) {
+ $prev = null;
+ $result = $set->next();
+ while ( $result ) {
+ $out .= $this->showInterwikiHit( $result, $prev, $query, $customCaptions );
+ $prev = $result->getInterwikiPrefix();
+ $result = $set->next();
+ }
+ }
+
+ // @todo Should support paging in a non-confusing way (not sure how though, maybe via ajax)..
$out .= "</ul></div>\n";
// convert the whole thing to desired language variant
$out = $wgContLang->convert( $out );
- wfProfileOut( __METHOD__ );
+
return $out;
}
/**
* Show single interwiki link
*
- * @param $result SearchResult
- * @param $lastInterwiki String
- * @param $terms Array
- * @param $query String
- * @param array $customCaptions iw prefix -> caption
+ * @param SearchResult $result
+ * @param string $lastInterwiki
+ * @param string $query
+ * @param array $customCaptions Interwiki prefix -> caption
*
* @return string
*/
- protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions ) {
- wfProfileIn( __METHOD__ );
+ protected function showInterwikiHit( $result, $lastInterwiki, $query, $customCaptions ) {
+ $profile = new ProfileSection( __METHOD__ );
if ( $result->isBrokenTitle() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- Broken link in search result -->\n";
+ return '';
}
- $t = $result->getTitle();
+ $title = $result->getTitle();
- $titleSnippet = $result->getTitleSnippet( $terms );
+ $titleSnippet = $result->getTitleSnippet();
if ( $titleSnippet == '' ) {
$titleSnippet = null;
}
$link = Linker::linkKnown(
- $t,
+ $title,
$titleSnippet
);
// format redirect if any
$redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet( $terms );
+ $redirectText = $result->getRedirectSnippet();
$redirect = '';
if ( !is_null( $redirectTitle ) ) {
if ( $redirectText == '' ) {
@@ -822,18 +814,18 @@ class SpecialSearch extends SpecialPage {
$out = "";
// display project name
- if ( is_null( $lastInterwiki ) || $lastInterwiki != $t->getInterwiki() ) {
- if ( array_key_exists( $t->getInterwiki(), $customCaptions ) ) {
+ if ( is_null( $lastInterwiki ) || $lastInterwiki != $title->getInterwiki() ) {
+ if ( array_key_exists( $title->getInterwiki(), $customCaptions ) ) {
// captions from 'search-interwiki-custom'
- $caption = $customCaptions[$t->getInterwiki()];
+ $caption = $customCaptions[$title->getInterwiki()];
} else {
// default is to show the hostname of the other wiki which might suck
// if there are many wikis on one hostname
- $parsed = wfParseUrl( $t->getFullURL() );
+ $parsed = wfParseUrl( $title->getFullURL() );
$caption = $this->msg( 'search-interwiki-default', $parsed['host'] )->text();
}
// "more results" link (special page stuff could be localized, but we might not know target lang)
- $searchTitle = Title::newFromText( $t->getInterwiki() . ":Special:Search" );
+ $searchTitle = Title::newFromText( $title->getInterwiki() . ":Special:Search" );
$searchLink = Linker::linkKnown(
$searchTitle,
$this->msg( 'search-interwiki-more' )->text(),
@@ -848,36 +840,16 @@ class SpecialSearch extends SpecialPage {
}
$out .= "<li>{$link} {$redirect}</li>\n";
- wfProfileOut( __METHOD__ );
- return $out;
- }
- /**
- * @param $profile
- * @param $term
- * @return String
- */
- protected function getProfileForm( $profile, $term ) {
- // Hidden stuff
- $opts = array();
- $opts['redirs'] = $this->searchRedirects;
- $opts['profile'] = $this->profile;
-
- if ( $profile === 'advanced' ) {
- return $this->powerSearchBox( $term, $opts );
- } else {
- $form = '';
- wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $profile, $term, $opts ) );
- return $form;
- }
+ return $out;
}
/**
* Generates the power search box at [[Special:Search]]
*
- * @param string $term search term
- * @param $opts array
- * @return String: HTML form
+ * @param string $term Search term
+ * @param array $opts
+ * @return string HTML form
*/
protected function powerSearchBox( $term, $opts ) {
global $wgContLang;
@@ -896,9 +868,7 @@ class SpecialSearch extends SpecialPage {
}
$rows[$subject] .=
- Xml::openElement(
- 'td', array( 'style' => 'white-space: nowrap' )
- ) .
+ Xml::openElement( 'td' ) .
Xml::checkLabel(
$name,
"ns{$namespace}",
@@ -929,30 +899,41 @@ class SpecialSearch extends SpecialPage {
$showSections = array( 'namespaceTables' => $namespaceTables );
- // Show redirects check only if backend supports it
- if ( $this->getSearchEngine()->supports( 'list-redirects' ) ) {
- $showSections['redirects'] =
- Xml::checkLabel( $this->msg( 'powersearch-redir' )->text(), 'redirs', 'redirs', $this->searchRedirects );
- }
-
wfRunHooks( 'SpecialSearchPowerBox', array( &$showSections, $term, $opts ) );
$hidden = '';
- unset( $opts['redirs'] );
foreach ( $opts as $key => $value ) {
$hidden .= Html::hidden( $key, $value );
}
+
+ # Stuff to feed saveNamespaces()
+ $remember = '';
+ $user = $this->getUser();
+ if ( $user->isLoggedIn() ) {
+ $remember .= Xml::checkLabel(
+ wfMessage( 'powersearch-remember' )->text(),
+ 'nsRemember',
+ 'mw-search-powersearch-remember',
+ false,
+ // The token goes here rather than in a hidden field so it
+ // is only sent when necessary (not every form submission).
+ array( 'value' => $user->getEditToken(
+ 'searchnamespace',
+ $this->getRequest()
+ ) )
+ );
+ }
+
// Return final output
- return Xml::openElement(
- 'fieldset',
- array( 'id' => 'mw-searchoptions', 'style' => 'margin:0em;' )
- ) .
+ return Xml::openElement( 'fieldset', array( 'id' => 'mw-searchoptions' ) ) .
Xml::element( 'legend', null, $this->msg( 'powersearch-legend' )->text() ) .
Xml::tags( 'h4', null, $this->msg( 'powersearch-ns' )->parse() ) .
- Html::element( 'div', array( 'id' => 'mw-search-togglebox' ) ) .
+ Xml::element( 'div', array( 'id' => 'mw-search-togglebox' ), '', false ) .
Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
implode( Xml::element( 'div', array( 'class' => 'divider' ), '', false ), $showSections ) .
$hidden .
+ Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
+ $remember .
Xml::closeElement( 'fieldset' );
}
@@ -977,14 +958,6 @@ class SpecialSearch extends SpecialPage {
'tooltip' => 'searchprofile-images-tooltip',
'namespaces' => array( NS_FILE ),
),
- 'help' => array(
- 'message' => 'searchprofile-project',
- 'tooltip' => 'searchprofile-project-tooltip',
- 'namespaces' => SearchEngine::helpNamespaces(),
- 'namespace-messages' => SearchEngine::namespacesAsText(
- SearchEngine::helpNamespaces()
- ),
- ),
'all' => array(
'message' => 'searchprofile-everything',
'tooltip' => 'searchprofile-everything-tooltip',
@@ -1010,12 +983,10 @@ class SpecialSearch extends SpecialPage {
}
/**
- * @param $term
- * @param $resultsShown
- * @param $totalNum
+ * @param string $term
* @return string
*/
- protected function formHeader( $term, $resultsShown, $totalNum ) {
+ protected function formHeader( $term ) {
$out = Xml::openElement( 'div', array( 'class' => 'mw-search-formheader' ) );
$bareterm = $term;
@@ -1054,69 +1025,74 @@ class SpecialSearch extends SpecialPage {
}
$out .= Xml::closeElement( 'ul' );
$out .= Xml::closeElement( 'div' );
-
- // Results-info
- if ( $resultsShown > 0 ) {
- if ( $totalNum > 0 ) {
- $top = $this->msg( 'showingresultsheader' )
- ->numParams( $this->offset + 1, $this->offset + $resultsShown, $totalNum )
- ->params( wfEscapeWikiText( $term ) )
- ->numParams( $resultsShown )
- ->parse();
- } elseif ( $resultsShown >= $this->limit ) {
- $top = $this->msg( 'showingresults' )
- ->numParams( $this->limit, $this->offset + 1 )
- ->parse();
- } else {
- $top = $this->msg( 'showingresultsnum' )
- ->numParams( $this->limit, $this->offset + 1, $resultsShown )
- ->parse();
- }
- $out .= Xml::tags( 'div', array( 'class' => 'results-info' ),
- Xml::tags( 'ul', null, Xml::tags( 'li', null, $top ) )
- );
- }
-
$out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
$out .= Xml::closeElement( 'div' );
+ // Hidden stuff
+ $opts = array();
+ $opts['profile'] = $this->profile;
+
+ if ( $this->profile === 'advanced' ) {
+ $out .= $this->powerSearchBox( $term, $opts );
+ } else {
+ $form = '';
+ wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $this->profile, $term, $opts ) );
+ $out .= $form;
+ }
+
return $out;
}
/**
- * @param $term string
+ * @param string $term
+ * @param int $resultsShown
+ * @param int $totalNum
* @return string
*/
- protected function shortDialog( $term ) {
- $out = Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
+ protected function shortDialog( $term, $resultsShown, $totalNum ) {
+ $out = Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
$out .= Html::hidden( 'profile', $this->profile ) . "\n";
// Term box
$out .= Html::input( 'search', $term, 'search', array(
'id' => $this->profile === 'advanced' ? 'powerSearchText' : 'searchText',
'size' => '50',
- 'autofocus'
+ 'autofocus',
+ 'class' => 'mw-ui-input mw-ui-input-inline',
) ) . "\n";
$out .= Html::hidden( 'fulltext', 'Search' ) . "\n";
- $out .= Xml::submitButton( $this->msg( 'searchbutton' )->text() ) . "\n";
+ $out .= Xml::submitButton(
+ $this->msg( 'searchbutton' )->text(),
+ array( 'class' => array( 'mw-ui-button', 'mw-ui-progressive' ) )
+ ) . "\n";
+
+ // Results-info
+ if ( $totalNum > 0 && $this->offset < $totalNum ) {
+ $top = $this->msg( 'search-showingresults' )
+ ->numParams( $this->offset + 1, $this->offset + $resultsShown, $totalNum )
+ ->numParams( $resultsShown )
+ ->parse();
+ $out .= Xml::tags( 'div', array( 'class' => 'results-info' ), $top ) .
+ Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
+ }
+
return $out . $this->didYouMeanHtml;
}
/**
* Make a search link with some target namespaces
*
- * @param $term String
- * @param array $namespaces ignored
- * @param string $label link's text
- * @param string $tooltip link's tooltip
- * @param array $params query string parameters
- * @return String: HTML fragment
+ * @param string $term
+ * @param array $namespaces Ignored
+ * @param string $label Link's text
+ * @param string $tooltip Link's tooltip
+ * @param array $params Query string parameters
+ * @return string HTML fragment
*/
protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) {
$opt = $params;
foreach ( $namespaces as $n ) {
$opt['ns' . $n] = 1;
}
- $opt['redirs'] = $this->searchRedirects;
$stParams = array_merge(
array(
@@ -1129,7 +1105,7 @@ class SpecialSearch extends SpecialPage {
return Xml::element(
'a',
array(
- 'href' => $this->getTitle()->getLocalURL( $stParams ),
+ 'href' => $this->getPageTitle()->getLocalURL( $stParams ),
'title' => $tooltip
),
$label
@@ -1139,33 +1115,35 @@ class SpecialSearch extends SpecialPage {
/**
* Check if query starts with image: prefix
*
- * @param string $term the string to check
- * @return Boolean
+ * @param string $term The string to check
+ * @return bool
*/
protected function startsWithImage( $term ) {
global $wgContLang;
- $p = explode( ':', $term );
- if ( count( $p ) > 1 ) {
- return $wgContLang->getNsIndex( $p[0] ) == NS_FILE;
+ $parts = explode( ':', $term );
+ if ( count( $parts ) > 1 ) {
+ return $wgContLang->getNsIndex( $parts[0] ) == NS_FILE;
}
+
return false;
}
/**
* Check if query starts with all: prefix
*
- * @param string $term the string to check
- * @return Boolean
+ * @param string $term The string to check
+ * @return bool
*/
protected function startsWithAll( $term ) {
$allkeyword = $this->msg( 'searchall' )->inContentLanguage()->text();
- $p = explode( ':', $term );
- if ( count( $p ) > 1 ) {
- return $p[0] == $allkeyword;
+ $parts = explode( ':', $term );
+ if ( count( $parts ) > 1 ) {
+ return $parts[0] == $allkeyword;
}
+
return false;
}
@@ -1179,17 +1157,34 @@ class SpecialSearch extends SpecialPage {
$this->searchEngine = $this->searchEngineType ?
SearchEngine::create( $this->searchEngineType ) : SearchEngine::create();
}
+
return $this->searchEngine;
}
/**
+ * Current search profile.
+ * @return null|string
+ */
+ function getProfile() {
+ return $this->profile;
+ }
+
+ /**
+ * Current namespaces.
+ * @return array
+ */
+ function getNamespaces() {
+ return $this->namespaces;
+ }
+
+ /**
* Users of hook SpecialSearchSetupEngine can use this to
* add more params to links to not lose selection when
* user navigates search results.
* @since 1.18
*
- * @param $key
- * @param $value
+ * @param string $key
+ * @param mixed $value
*/
public function setExtraParam( $key, $value ) {
$this->extraParams[$key] = $value;
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 9b50875a..782d9a17 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -40,12 +40,15 @@ class ShortPagesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_len' ),
- 'conds' => array( 'page_namespace' =>
- MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0 ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_len'
+ ),
+ 'conds' => array(
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ),
'options' => array( 'USE INDEX' => 'page_redirect_namespace_len' )
);
}
@@ -55,7 +58,7 @@ class ShortPagesPage extends QueryPage {
}
/**
- * @param $db DatabaseBase
+ * @param DatabaseBase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
@@ -111,8 +114,8 @@ class ShortPagesPage extends QueryPage {
$size = $this->msg( 'nbytes' )->numParams( $result->value )->escaped();
return $exists
- ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
- : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
+ ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
+ : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index 47c89d0d..eff06f46 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -49,8 +49,6 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
private function getPageGroups() {
- global $wgSortSpecialPages;
-
$pages = SpecialPageFactory::getUsablePages( $this->getUser() );
if ( !count( $pages ) ) {
@@ -68,7 +66,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$groups[$group] = array();
}
$groups[$group][$page->getDescription()] = array(
- $page->getTitle(),
+ $page->getPageTitle(),
$page->isRestricted(),
$page->isCached()
);
@@ -76,10 +74,8 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
/** Sort */
- if ( $wgSortSpecialPages ) {
- foreach ( $groups as $group => $sortedPages ) {
- ksort( $groups[$group] );
- }
+ foreach ( $groups as $group => $sortedPages ) {
+ ksort( $groups[$group] );
}
/** Always move "other" to end */
@@ -103,9 +99,15 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$middle = ceil( $total / 2 );
$count = 0;
- $out->wrapWikiMsg( "<h2 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h2>\n", "specialpages-group-$group" );
+ $out->wrapWikiMsg(
+ "<h2 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h2>\n",
+ "specialpages-group-$group"
+ );
$out->addHTML(
- Html::openElement( 'table', array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' ) ) . "\n" .
+ Html::openElement(
+ 'table',
+ array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' )
+ ) . "\n" .
Html::openElement( 'tr' ) . "\n" .
Html::openElement( 'td', array( 'style' => 'width:30%;vertical-align:top' ) ) . "\n" .
Html::openElement( 'ul' ) . "\n"
@@ -124,7 +126,11 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
$link = Linker::linkKnown( $title, htmlspecialchars( $desc ) );
- $out->addHTML( Html::rawElement( 'li', array( 'class' => implode( ' ', $pageClasses ) ), $link ) . "\n" );
+ $out->addHTML( Html::rawElement(
+ 'li',
+ array( 'class' => implode( ' ', $pageClasses ) ),
+ $link
+ ) . "\n" );
# Split up the larger groups
$count++;
@@ -144,6 +150,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
if ( $includesRestrictedPages || $includesCachedPages ) {
+ $out->wrapWikiMsg( "<h2 class=\"mw-specialpages-note-top\">$1</h2>", 'specialpages-note-top' );
$out->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' );
}
}
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index f26d1a60..f0e360e8 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -28,16 +28,18 @@
* @ingroup SpecialPage
*/
class SpecialStatistics extends SpecialPage {
-
private $views, $edits, $good, $images, $total, $users,
- $activeUsers = 0;
+ $activeUsers = 0;
public function __construct() {
parent::__construct( 'Statistics' );
}
public function execute( $par ) {
- global $wgMemc, $wgDisableCounters, $wgMiserMode;
+ global $wgMemc;
+
+ $disableCounters = $this->getConfig()->get( 'DisableCounters' );
+ $miserMode = $this->getConfig()->get( 'MiserMode' );
$this->setHeaders();
$this->getOutput()->addModuleStyles( 'mediawiki.special' );
@@ -53,12 +55,12 @@ class SpecialStatistics extends SpecialPage {
# Staticic - views
$viewsStats = '';
- if ( !$wgDisableCounters ) {
+ if ( !$disableCounters ) {
$viewsStats = $this->getViewsStats();
}
# Set active user count
- if ( !$wgMiserMode ) {
+ if ( !$miserMode ) {
$key = wfMemcKey( 'sitestats', 'activeusers-updated' );
// Re-calculate the count if the last tally is old...
if ( !$wgMemc->get( $key ) ) {
@@ -84,7 +86,7 @@ class SpecialStatistics extends SpecialPage {
$text .= $viewsStats;
# Statistic - popular pages
- if ( !$wgDisableCounters && !$wgMiserMode ) {
+ if ( !$disableCounters && !$miserMode ) {
$text .= $this->getMostViewedPages();
}
@@ -107,14 +109,16 @@ class SpecialStatistics extends SpecialPage {
/**
* Format a row
- * @param $text String: description of the row
- * @param $number Float: a statistical number
- * @param $trExtraParams Array: params to table row, see Html::elememt
- * @param $descMsg String: message key
+ * @param string $text Description of the row
+ * @param float $number A statistical number
+ * @param array $trExtraParams Params to table row, see Html::elememt
+ * @param string $descMsg Message key
* @param array|string $descMsgParam Message parameters
- * @return string table row in HTML format
+ * @return string Table row in HTML format
*/
- private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) {
+ private function formatRow( $text, $number, $trExtraParams = array(),
+ $descMsg = '', $descMsgParam = ''
+ ) {
if ( $descMsg ) {
$msg = $this->msg( $descMsg, $descMsgParam );
if ( $msg->exists() ) {
@@ -123,6 +127,7 @@ class SpecialStatistics extends SpecialPage {
" $descriptionText" );
}
}
+
return Html::rawElement( 'tr', $trExtraParams,
Html::rawElement( 'td', array(), $text ) .
Html::rawElement( 'td', array( 'class' => 'mw-statistics-numbers' ), $number )
@@ -139,55 +144,59 @@ class SpecialStatistics extends SpecialPage {
Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-pages' )->parse() ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( Linker::linkKnown( SpecialPage::getTitleFor( 'Allpages' ),
- $this->msg( 'statistics-articles' )->parse() ),
- $this->getLanguage()->formatNum( $this->good ),
- array( 'class' => 'mw-statistics-articles' ) ) .
+ $this->msg( 'statistics-articles' )->parse() ),
+ $this->getLanguage()->formatNum( $this->good ),
+ array( 'class' => 'mw-statistics-articles' ) ) .
$this->formatRow( $this->msg( 'statistics-pages' )->parse(),
- $this->getLanguage()->formatNum( $this->total ),
- array( 'class' => 'mw-statistics-pages' ),
- 'statistics-pages-desc' ) .
- $this->formatRow( Linker::linkKnown( SpecialPage::getTitleFor( 'Listfiles' ),
- $this->msg( 'statistics-files' )->parse() ),
- $this->getLanguage()->formatNum( $this->images ),
- array( 'class' => 'mw-statistics-files' ) );
+ $this->getLanguage()->formatNum( $this->total ),
+ array( 'class' => 'mw-statistics-pages' ),
+ 'statistics-pages-desc' ) .
+ $this->formatRow( Linker::linkKnown( SpecialPage::getTitleFor( 'MediaStatistics' ),
+ $this->msg( 'statistics-files' )->parse() ),
+ $this->getLanguage()->formatNum( $this->images ),
+ array( 'class' => 'mw-statistics-files' ) );
}
+
private function getEditStats() {
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-edits' )->parse() ) .
Xml::closeElement( 'tr' ) .
- $this->formatRow( $this->msg( 'statistics-edits' )->parse(),
- $this->getLanguage()->formatNum( $this->edits ),
- array( 'class' => 'mw-statistics-edits' ) ) .
- $this->formatRow( $this->msg( 'statistics-edits-average' )->parse(),
- $this->getLanguage()->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
- array( 'class' => 'mw-statistics-edits-average' ) );
+ $this->formatRow( $this->msg( 'statistics-edits' )->parse(),
+ $this->getLanguage()->formatNum( $this->edits ),
+ array( 'class' => 'mw-statistics-edits' )
+ ) .
+ $this->formatRow( $this->msg( 'statistics-edits-average' )->parse(),
+ $this->getLanguage()
+ ->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
+ array( 'class' => 'mw-statistics-edits-average' )
+ );
}
private function getUserStats() {
- global $wgActiveUserDays;
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-users' )->parse() ) .
Xml::closeElement( 'tr' ) .
- $this->formatRow( $this->msg( 'statistics-users' )->parse(),
- $this->getLanguage()->formatNum( $this->users ),
- array( 'class' => 'mw-statistics-users' ) ) .
- $this->formatRow( $this->msg( 'statistics-users-active' )->parse() . ' ' .
- Linker::linkKnown(
- SpecialPage::getTitleFor( 'Activeusers' ),
- $this->msg( 'listgrouprights-members' )->escaped()
- ),
- $this->getLanguage()->formatNum( $this->activeUsers ),
- array( 'class' => 'mw-statistics-users-active' ),
- 'statistics-users-active-desc',
- $this->getLanguage()->formatNum( $wgActiveUserDays ) );
+ $this->formatRow( $this->msg( 'statistics-users' )->parse(),
+ $this->getLanguage()->formatNum( $this->users ),
+ array( 'class' => 'mw-statistics-users' )
+ ) .
+ $this->formatRow( $this->msg( 'statistics-users-active' )->parse() . ' ' .
+ Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Activeusers' ),
+ $this->msg( 'listgrouprights-members' )->escaped()
+ ),
+ $this->getLanguage()->formatNum( $this->activeUsers ),
+ array( 'class' => 'mw-statistics-users-active' ),
+ 'statistics-users-active-desc',
+ $this->getLanguage()->formatNum( $this->getConfig()->get( 'ActiveUserDays' ) )
+ );
}
private function getGroupStats() {
- global $wgGroupPermissions, $wgImplicitGroups;
$text = '';
- foreach ( $wgGroupPermissions as $group => $permissions ) {
+ foreach ( $this->getConfig()->get( 'GroupPermissions' ) as $group => $permissions ) {
# Skip generic * and implicit groups
- if ( in_array( $group, $wgImplicitGroups ) || $group == '*' ) {
+ if ( in_array( $group, $this->getConfig()->get( 'ImplicitGroups' ) ) || $group == '*' ) {
continue;
}
$groupname = htmlspecialchars( $group );
@@ -224,6 +233,7 @@ class SpecialStatistics extends SpecialPage {
$this->getLanguage()->formatNum( $countUsers ),
array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) );
}
+
return $text;
}
@@ -244,36 +254,43 @@ class SpecialStatistics extends SpecialPage {
$text = '';
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
- 'page',
- array(
- 'page_namespace',
- 'page_title',
- 'page_counter',
- ),
- array(
- 'page_is_redirect' => 0,
- 'page_counter > 0',
- ),
- __METHOD__,
- array(
- 'ORDER BY' => 'page_counter DESC',
- 'LIMIT' => 10,
- )
+ 'page',
+ array(
+ 'page_namespace',
+ 'page_title',
+ 'page_counter',
+ ),
+ array(
+ 'page_is_redirect' => 0,
+ 'page_counter > 0',
+ ),
+ __METHOD__,
+ array(
+ 'ORDER BY' => 'page_counter DESC',
+ 'LIMIT' => 10,
+ )
+ );
+
+ if ( $res->numRows() > 0 ) {
+ $text .= Xml::openElement( 'tr' );
+ $text .= Xml::tags(
+ 'th',
+ array( 'colspan' => '2' ),
+ $this->msg( 'statistics-mostpopular' )->parse()
);
- if ( $res->numRows() > 0 ) {
- $text .= Xml::openElement( 'tr' );
- $text .= Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-mostpopular' )->parse() );
- $text .= Xml::closeElement( 'tr' );
- foreach ( $res as $row ) {
- $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- if ( $title instanceof Title ) {
- $text .= $this->formatRow( Linker::link( $title ),
- $this->getLanguage()->formatNum( $row->page_counter ) );
-
- }
+ $text .= Xml::closeElement( 'tr' );
+
+ foreach ( $res as $row ) {
+ $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+
+ if ( $title instanceof Title ) {
+ $text .= $this->formatRow( Linker::link( $title ),
+ $this->getLanguage()->formatNum( $row->page_counter ) );
}
- $res->free();
}
+ $res->free();
+ }
+
return $text;
}
@@ -301,7 +318,11 @@ class SpecialStatistics extends SpecialPage {
$name = $this->msg( $key )->parse();
$number = htmlspecialchars( $value );
- $return .= $this->formatRow( $name, $this->getLanguage()->formatNum( $number ), array( 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key ) );
+ $return .= $this->formatRow(
+ $name,
+ $this->getLanguage()->formatNum( $number ),
+ array( 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key )
+ );
}
} else {
// Create the legacy header only once
@@ -310,7 +331,8 @@ class SpecialStatistics extends SpecialPage {
}
// Recursively remap the legacy structure
- $return .= $this->getOtherStats( array( 'statistics-header-hooks' => array( $header => $items ) ) );
+ $return .= $this->getOtherStats( array( 'statistics-header-hooks' =>
+ array( $header => $items ) ) );
}
}
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index 077e7cbc..b7627285 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -46,11 +46,11 @@ class SpecialTags extends SpecialPage {
// Write the headers
$html = Xml::tags( 'tr', null, Xml::tags( 'th', null, $this->msg( 'tags-tag' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-display-header' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-description-header' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() )
- );
+ Xml::tags( 'th', null, $this->msg( 'tags-display-header' )->parse() ) .
+ Xml::tags( 'th', null, $this->msg( 'tags-description-header' )->parse() ) .
+ Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
+ Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() )
+ );
// Used in #doTagRow()
$this->definedTags = array_fill_keys( ChangeTags::listDefinedTags(), true );
diff --git a/includes/specials/SpecialTrackingCategories.php b/includes/specials/SpecialTrackingCategories.php
new file mode 100644
index 00000000..552031f1
--- /dev/null
+++ b/includes/specials/SpecialTrackingCategories.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Implements Special:TrackingCategories
+ *
+ * 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
+ */
+
+/**
+ * A special page that displays list of tracking categories
+ * Tracking categories allow pages with certain characteristics to be tracked.
+ * It works by adding any such page to a category automatically.
+ * Category is specified by the tracking category's system message.
+ *
+ * @ingroup SpecialPage
+ * @since 1.23
+ */
+
+class SpecialTrackingCategories extends SpecialPage {
+ function __construct() {
+ parent::__construct( 'TrackingCategories' );
+ }
+
+ function execute( $par ) {
+ $this->setHeaders();
+ $this->outputHeader();
+ $this->getOutput()->allowClickjacking();
+ $this->getOutput()->addHTML(
+ Html::openElement( 'table', array( 'class' => 'mw-datatable',
+ 'id' => 'mw-trackingcategories-table' ) ) . "\n" .
+ "<thead><tr>
+ <th>" .
+ $this->msg( 'trackingcategories-msg' )->escaped() . "
+ </th>
+ <th>" .
+ $this->msg( 'trackingcategories-name' )->escaped() .
+ "</th>
+ <th>" .
+ $this->msg( 'trackingcategories-desc' )->escaped() . "
+ </th>
+ </tr></thead>"
+ );
+
+ foreach ( $this->getConfig()->get( 'TrackingCategories' ) as $catMsg ) {
+ /*
+ * Check if the tracking category varies by namespace
+ * Otherwise only pages in the current namespace will be displayed
+ * If it does vary, show pages considering all namespaces
+ */
+ $msgObj = $this->msg( $catMsg )->inContentLanguage();
+ $allMsgs = array();
+ $catDesc = $catMsg . '-desc';
+ $catMsgTitle = Title::makeTitleSafe( NS_MEDIAWIKI, $catMsg );
+ if ( !$catMsgTitle ) {
+ continue;
+ }
+ $catMsgTitleText = Linker::link(
+ $catMsgTitle,
+ htmlspecialchars( $catMsg )
+ );
+
+ // Match things like {{NAMESPACE}} and {{NAMESPACENUMBER}}.
+ // False positives are ok, this is just an efficiency shortcut
+ if ( strpos( $msgObj->plain(), '{{' ) !== false ) {
+ $ns = MWNamespace::getValidNamespaces();
+ foreach ( $ns as $namesp ) {
+ $tempTitle = Title::makeTitleSafe( $namesp, $catMsg );
+ if ( !$tempTitle ) {
+ continue;
+ }
+ $catName = $msgObj->title( $tempTitle )->text();
+ # Allow tracking categories to be disabled by setting them to "-"
+ if ( $catName !== '-' ) {
+ $catTitle = Title::makeTitleSafe( NS_CATEGORY, $catName );
+ if ( $catTitle ) {
+ $catTitleText = Linker::link(
+ $catTitle,
+ htmlspecialchars( $catName )
+ );
+ $allMsgs[] = $catTitleText;
+ }
+ }
+ }
+ } else {
+ $catName = $msgObj->text();
+ # Allow tracking categories to be disabled by setting them to "-"
+ if ( $catName !== '-' ) {
+ $catTitle = Title::makeTitleSafe( NS_CATEGORY, $catName );
+ if ( $catTitle ) {
+ $catTitleText = Linker::link(
+ $catTitle,
+ htmlspecialchars( $catName )
+ );
+ $allMsgs[] = $catTitleText;
+ }
+ }
+ }
+
+ # Extra message, when no category was found
+ if ( !count( $allMsgs ) ) {
+ $allMsgs[] = $this->msg( 'trackingcategories-disabled' )->parse();
+ }
+
+ /*
+ * Show category description if it exists as a system message
+ * as category-name-desc
+ */
+ $descMsg = $this->msg( $catDesc );
+ if ( $descMsg->isBlank() ) {
+ $descMsg = $this->msg( 'trackingcategories-nodesc' );
+ }
+
+ $this->getOutput()->addHTML(
+ Html::openElement( 'tr' ) .
+ Html::openElement( 'td', array( 'class' => 'mw-trackingcategories-name' ) ) .
+ $this->getLanguage()->commaList( array_unique( $allMsgs ) ) .
+ Html::closeElement( 'td' ) .
+ Html::openElement( 'td', array( 'class' => 'mw-trackingcategories-msg' ) ) .
+ $catMsgTitleText .
+ Html::closeElement( 'td' ) .
+ Html::openElement( 'td', array( 'class' => 'mw-trackingcategories-desc' ) ) .
+ $descMsg->parse() .
+ Html::closeElement( 'td' ) .
+ Html::closeElement( 'tr' )
+ );
+ }
+ $this->getOutput()->addHTML( Html::closeElement( 'table' ) );
+ }
+
+ protected function getGroupName() {
+ return 'pages';
+ }
+}
diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php
index ca93b6d1..244b8894 100644
--- a/includes/specials/SpecialUnblock.php
+++ b/includes/specials/SpecialUnblock.php
@@ -42,6 +42,11 @@ class SpecialUnblock extends SpecialPage {
list( $this->target, $this->type ) = SpecialBlock::getTargetAndType( $par, $this->getRequest() );
$this->block = Block::newFromTarget( $this->target );
+ if ( $this->target instanceof User ) {
+ # Set the 'relevant user' in the skin, so it displays links like Contributions,
+ # User logs, UserRights, etc.
+ $this->getSkin()->setRelevantUser( $this->target );
+ }
$this->setHeaders();
$this->outputHeader();
@@ -58,8 +63,10 @@ class SpecialUnblock extends SpecialPage {
if ( $form->show() ) {
switch ( $this->type ) {
- case Block::TYPE_USER:
case Block::TYPE_IP:
+ $out->addWikiMsg( 'unblocked-ip', wfEscapeWikiText( $this->target ) );
+ break;
+ case Block::TYPE_USER:
$out->addWikiMsg( 'unblocked', wfEscapeWikiText( $this->target ) );
break;
case Block::TYPE_RANGE:
@@ -77,14 +84,14 @@ class SpecialUnblock extends SpecialPage {
$fields = array(
'Target' => array(
'type' => 'text',
- 'label-message' => 'ipadressorusername',
- 'tabindex' => '1',
+ 'label-message' => 'ipaddressorusername',
+ 'autofocus' => true,
'size' => '45',
'required' => true,
),
'Name' => array(
'type' => 'info',
- 'label-message' => 'ipadressorusername',
+ 'label-message' => 'ipaddressorusername',
),
'Reason' => array(
'type' => 'text',
@@ -102,13 +109,18 @@ class SpecialUnblock extends SpecialPage {
if ( $type == Block::TYPE_AUTO && $this->type == Block::TYPE_IP ) {
$fields['Target']['default'] = $this->target;
unset( $fields['Name'] );
-
} else {
$fields['Target']['default'] = $target;
$fields['Target']['type'] = 'hidden';
switch ( $type ) {
- case Block::TYPE_USER:
case Block::TYPE_IP:
+ $fields['Name']['default'] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Contributions', $target->getName() ),
+ $target->getName()
+ );
+ $fields['Name']['raw'] = true;
+ break;
+ case Block::TYPE_USER:
$fields['Name']['default'] = Linker::link(
$target->getUserPage(),
$target->getName()
@@ -127,12 +139,15 @@ class SpecialUnblock extends SpecialPage {
$fields['Target']['default'] = "#{$this->target}";
break;
}
+ // target is hidden, so the reason is the first element
+ $fields['Target']['autofocus'] = false;
+ $fields['Reason']['autofocus'] = true;
}
-
} else {
$fields['Target']['default'] = $this->target;
unset( $fields['Name'] );
}
+
return $fields;
}
@@ -140,7 +155,7 @@ class SpecialUnblock extends SpecialPage {
* Submit callback for an HTMLForm object
* @param array $data
* @param HTMLForm $form
- * @return Array( Array(message key, parameters)
+ * @return array|bool Array(message key, parameters)
*/
public static function processUIUnblock( array $data, HTMLForm $form ) {
return self::processUnblock( $data, $form->getContext() );
@@ -149,10 +164,10 @@ class SpecialUnblock extends SpecialPage {
/**
* Process the form
*
- * @param $data Array
- * @param $context IContextSource
+ * @param array $data
+ * @param IContextSource $context
* @throws ErrorPageError
- * @return Array( Array(message key, parameters) ) on failure, True on success
+ * @return array|bool Array(message key, parameters) on failure, True on success
*/
public static function processUnblock( array $data, IContextSource $context ) {
$performer = $context->getUser();
@@ -176,6 +191,7 @@ class SpecialUnblock extends SpecialPage {
list( $target, $type ) = SpecialBlock::getTargetAndType( $target );
if ( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) {
$range = $block->getTarget();
+
return array( array( 'ipb_blocked_as_range', $target, $range ) );
}
diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php
index 3bfcedec..9060f53a 100644
--- a/includes/specials/SpecialUncategorizedimages.php
+++ b/includes/specials/SpecialUncategorizedimages.php
@@ -26,10 +26,9 @@
* Special page lists images which haven't been categorised
*
* @ingroup SpecialPage
+ * @todo FIXME: Use an instance of UncategorizedPagesPage or something
*/
-// @todo FIXME: Use an instance of UncategorizedPagesPage or something
class UncategorizedImagesPage extends ImageQueryPage {
-
function __construct( $name = 'Uncategorizedimages' ) {
parent::__construct( $name );
}
@@ -50,13 +49,13 @@ class UncategorizedImagesPage extends ImageQueryPage {
return array(
'tables' => array( 'page', 'categorylinks' ),
'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
+ 'title' => 'page_title',
+ 'value' => 'page_title' ),
'conds' => array( 'cl_from IS NULL',
- 'page_namespace' => NS_FILE,
- 'page_is_redirect' => 0 ),
+ 'page_namespace' => NS_FILE,
+ 'page_is_redirect' => 0 ),
'join_conds' => array( 'categorylinks' => array(
- 'LEFT JOIN', 'cl_from=page_id' ) )
+ 'LEFT JOIN', 'cl_from=page_id' ) )
);
}
diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php
index 8bc9e489..8251d5b3 100644
--- a/includes/specials/SpecialUncategorizedpages.php
+++ b/includes/specials/SpecialUncategorizedpages.php
@@ -25,8 +25,8 @@
* A special page looking for page without any category.
*
* @ingroup SpecialPage
+ * @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
*/
-// @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
class UncategorizedPagesPage extends PageQueryPage {
protected $requestedNamespace = false;
@@ -49,16 +49,23 @@ class UncategorizedPagesPage extends PageQueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'categorylinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
// default for page_namespace is all content namespaces (if requestedNamespace is false)
// otherwise, page_namespace is requestedNamespace
- 'conds' => array( 'cl_from IS NULL',
- 'page_namespace' => ( $this->requestedNamespace !== false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ),
- 'page_is_redirect' => 0 ),
- 'join_conds' => array( 'categorylinks' => array(
- 'LEFT JOIN', 'cl_from = page_id' ) )
+ 'conds' => array(
+ 'cl_from IS NULL',
+ 'page_namespace' => $this->requestedNamespace !== false
+ ? $this->requestedNamespace
+ : MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ),
+ 'join_conds' => array(
+ 'categorylinks' => array( 'LEFT JOIN', 'cl_from = page_id' )
+ )
);
}
@@ -68,6 +75,7 @@ class UncategorizedPagesPage extends PageQueryPage {
if ( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 ) {
return array( 'page_namespace', 'page_title' );
}
+
return array( 'page_title' );
}
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index d4aed113..c3e871b8 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -27,26 +27,28 @@
* @ingroup SpecialPage
*/
class PageArchive {
- /**
- * @var Title
- */
+ /** @var Title */
protected $title;
- /**
- * @var Status
- */
+ /** @var Status */
protected $fileStatus;
- /**
- * @var Status
- */
+ /** @var Status */
protected $revisionStatus;
- function __construct( $title ) {
+ /** @var Config */
+ protected $config;
+
+ function __construct( $title, Config $config = null ) {
if ( is_null( $title ) ) {
throw new MWException( __METHOD__ . ' given a null title.' );
}
$this->title = $title;
+ if ( $config === null ) {
+ wfDebug( __METHOD__ . ' did not have a Config object passed to it' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+ $this->config = $config;
}
/**
@@ -58,6 +60,7 @@ class PageArchive {
*/
public static function listAllPages() {
$dbr = wfGetDB( DB_SLAVE );
+
return self::listPages( $dbr, '' );
}
@@ -96,7 +99,7 @@ class PageArchive {
* @return bool|ResultWrapper
*/
protected static function listPages( $dbr, $condition ) {
- return $dbr->resultObject( $dbr->select(
+ return $dbr->select(
array( 'archive' ),
array(
'ar_namespace',
@@ -110,7 +113,7 @@ class PageArchive {
'ORDER BY' => array( 'ar_namespace', 'ar_title' ),
'LIMIT' => 100,
)
- ) );
+ );
}
/**
@@ -120,28 +123,42 @@ class PageArchive {
* @return ResultWrapper
*/
function listRevisions() {
- global $wgContentHandlerUseDB;
-
$dbr = wfGetDB( DB_SLAVE );
+ $tables = array( 'archive' );
+
$fields = array(
'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
);
- if ( $wgContentHandlerUseDB ) {
+ if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
$fields[] = 'ar_content_format';
$fields[] = 'ar_content_model';
}
- $res = $dbr->select( 'archive',
+ $conds = array( 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey() );
+
+ $options = array( 'ORDER BY' => 'ar_timestamp DESC' );
+
+ $join_conds = array();
+
+ ChangeTags::modifyDisplayQuery(
+ $tables,
$fields,
- array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ),
- __METHOD__,
- array( 'ORDER BY' => 'ar_timestamp DESC' ) );
+ $conds,
+ $join_conds,
+ $options
+ );
- return $dbr->resultObject( $res );
+ return $dbr->select( $tables,
+ $fields,
+ $conds,
+ __METHOD__,
+ $options,
+ $join_conds
+ );
}
/**
@@ -158,15 +175,13 @@ class PageArchive {
}
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select(
+ return $dbr->select(
'filearchive',
ArchivedFile::selectFields(),
array( 'fa_name' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' )
);
-
- return $dbr->resultObject( $res );
}
/**
@@ -177,8 +192,6 @@ class PageArchive {
* @return Revision|null
*/
function getRevision( $timestamp ) {
- global $wgContentHandlerUseDB;
-
$dbr = wfGetDB( DB_SLAVE );
$fields = array(
@@ -196,7 +209,7 @@ class PageArchive {
'ar_sha1',
);
- if ( $wgContentHandlerUseDB ) {
+ if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
$fields[] = 'ar_content_format';
$fields[] = 'ar_content_model';
}
@@ -318,7 +331,7 @@ class PageArchive {
/**
* Quick check if any archived revisions are present for the page.
*
- * @return boolean
+ * @return bool
*/
function isDeleted() {
$dbr = wfGetDB( DB_SLAVE );
@@ -336,16 +349,18 @@ class PageArchive {
* Once restored, the items will be removed from the archive tables.
* The deletion log will be updated with an undeletion notice.
*
- * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param array $timestamps Pass an empty array to restore all revisions,
+ * otherwise list the ones to undelete.
* @param string $comment
* @param array $fileVersions
* @param bool $unsuppress
* @param User $user User performing the action, or null to use $wgUser
- *
- * @return array(number of file revisions restored, number of image revisions restored, log message)
- * on success, false on failure
+ * @return array(number of file revisions restored, number of image revisions
+ * restored, log message) on success, false on failure.
*/
- function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false, User $user = null ) {
+ function undelete( $timestamps, $comment = '', $fileVersions = array(),
+ $unsuppress = false, User $user = null
+ ) {
// If both the set of text revisions and file revisions are empty,
// restore everything. Otherwise, just restore the requested items.
$restoreAll = empty( $timestamps ) && empty( $fileVersions );
@@ -388,6 +403,7 @@ class PageArchive {
->inContentLanguage()->text();
} else {
wfDebug( "Undelete: nothing undeleted...\n" );
+
return false;
}
@@ -418,15 +434,14 @@ class PageArchive {
* to the cur/old tables. If the page currently exists, all revisions will
* be stuffed into old, otherwise the most recent will go into cur.
*
- * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param array $timestamps Pass an empty array to restore all revisions,
+ * otherwise list the ones to undelete.
* @param bool $unsuppress Remove all ar_deleted/fa_deleted restrictions of seletected revs
* @param string $comment
* @throws ReadOnlyError
- * @return Status Object containing the number of revisions restored on success
+ * @return Status Status object containing the number of revisions restored on success
*/
private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
- global $wgContentHandlerUseDB;
-
if ( wfReadOnly() ) {
throw new ReadOnlyError();
}
@@ -475,15 +490,12 @@ class PageArchive {
$previousTimestamp = 0;
}
- if ( $restoreAll ) {
- $oldones = '1 = 1'; # All revisions...
- } else {
- $oldts = implode( ',',
- array_map( array( &$dbw, 'addQuotes' ),
- array_map( array( &$dbw, 'timestamp' ),
- $timestamps ) ) );
-
- $oldones = "ar_timestamp IN ( {$oldts} )";
+ $oldWhere = array(
+ 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey(),
+ );
+ if ( !$restoreAll ) {
+ $oldWhere['ar_timestamp'] = array_map( array( &$dbw, 'timestamp' ), $timestamps );
}
$fields = array(
@@ -502,7 +514,7 @@ class PageArchive {
'ar_sha1'
);
- if ( $wgContentHandlerUseDB ) {
+ if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
$fields[] = 'ar_content_format';
$fields[] = 'ar_content_model';
}
@@ -512,27 +524,25 @@ class PageArchive {
*/
$result = $dbw->select( 'archive',
$fields,
- /* WHERE */ array(
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- $oldones ),
+ $oldWhere,
__METHOD__,
/* options */ array( 'ORDER BY' => 'ar_timestamp' )
);
- $ret = $dbw->resultObject( $result );
- $rev_count = $dbw->numRows( $result );
+ $rev_count = $result->numRows();
if ( !$rev_count ) {
wfDebug( __METHOD__ . ": no revisions to restore\n" );
$status = Status::newGood( 0 );
$status->warning( "undelete-no-results" );
+
return $status;
}
- $ret->seek( $rev_count - 1 ); // move to last
- $row = $ret->fetchObject(); // get newest archived rev
- $ret->seek( 0 ); // move back
+ $result->seek( $rev_count - 1 ); // move to last
+ $row = $result->fetchObject(); // get newest archived rev
+ $oldPageId = (int)$row->ar_page_id; // pass this to ArticleUndelete hook
+ $result->seek( 0 ); // move back
// grab the content to check consistency with global state before restoring the page.
$revision = Revision::newFromArchiveRow( $row,
@@ -574,7 +584,7 @@ class PageArchive {
$revision = null;
$restored = 0;
- foreach ( $ret as $row ) {
+ foreach ( $result as $row ) {
// Check for key dupes due to shitty archive integrity.
if ( $row->ar_rev_id ) {
$exists = $dbw->selectField( 'revision', '1',
@@ -599,10 +609,7 @@ class PageArchive {
}
# Now that it's safely stored, take it out of the archive
$dbw->delete( 'archive',
- /* WHERE */ array(
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- $oldones ),
+ $oldWhere,
__METHOD__ );
// Was anything restored at all?
@@ -617,10 +624,14 @@ class PageArchive {
if ( $created || $wasnew ) {
// Update site stats, link tables, etc
$user = User::newFromName( $revision->getRawUserText(), false );
- $article->doEditUpdates( $revision, $user, array( 'created' => $created, 'oldcountable' => $oldcountable ) );
+ $article->doEditUpdates(
+ $revision,
+ $user,
+ array( 'created' => $created, 'oldcountable' => $oldcountable )
+ );
}
- wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment ) );
+ wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment, $oldPageId ) );
if ( $this->title->getNamespace() == NS_FILE ) {
$update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
@@ -652,13 +663,20 @@ class PageArchive {
* @ingroup SpecialPage
*/
class SpecialUndelete extends SpecialPage {
- var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mFilename;
- var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken;
-
- /**
- * @var Title
- */
- var $mTargetObj;
+ private $mAction;
+ private $mTarget;
+ private $mTimestamp;
+ private $mRestore;
+ private $mInvert;
+ private $mFilename;
+ private $mTargetTimestamp;
+ private $mAllowed;
+ private $mCanView;
+ private $mComment;
+ private $mToken;
+
+ /** @var Title */
+ private $mTargetObj;
function __construct() {
parent::__construct( 'Undelete', 'deletedhistory' );
@@ -697,10 +715,10 @@ class SpecialUndelete extends SpecialPage {
$this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' );
$this->mToken = $request->getVal( 'token' );
- if ( $user->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
+ if ( $this->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
$this->mAllowed = true; // user can restore
$this->mCanView = true; // user can view content
- } elseif ( $user->isAllowed( 'deletedtext' ) ) {
+ } elseif ( $this->isAllowed( 'deletedtext' ) ) {
$this->mAllowed = false; // user cannot restore
$this->mCanView = true; // user can view content
$this->mRestore = false;
@@ -729,14 +747,35 @@ class SpecialUndelete extends SpecialPage {
}
}
+ /**
+ * Checks whether a user is allowed the permission for the
+ * specific title if one is set.
+ *
+ * @param string $permission
+ * @param User $user
+ * @return bool
+ */
+ private function isAllowed( $permission, User $user = null ) {
+ $user = $user ? : $this->getUser();
+ if ( $this->mTargetObj !== null ) {
+ return $this->mTargetObj->userCan( $permission, $user );
+ } else {
+ return $user->isAllowed( $permission );
+ }
+ }
+
+ function userCanExecute( User $user ) {
+ return $this->isAllowed( $this->mRestriction, $user );
+ }
+
function execute( $par ) {
- $this->checkPermissions();
$user = $this->getUser();
$this->setHeaders();
$this->outputHeader();
$this->loadRequest( $par );
+ $this->checkPermissions(); // Needs to be after mTargetObj is set
$out = $this->getOutput();
@@ -747,6 +786,7 @@ class SpecialUndelete extends SpecialPage {
if ( $user->isAllowed( 'browsearchive' ) ) {
$this->showSearchForm();
}
+
return;
}
@@ -784,14 +824,12 @@ class SpecialUndelete extends SpecialPage {
}
function showSearchForm() {
- global $wgScript;
-
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'undelete-search-title' ) );
$out->addHTML(
- Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
+ Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) .
Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
Html::rawElement(
'label',
array( 'for' => 'prefix' ),
@@ -826,12 +864,13 @@ class SpecialUndelete extends SpecialPage {
if ( $result->numRows() == 0 ) {
$out->addWikiMsg( 'undelete-no-results' );
+
return false;
}
$out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
- $undelete = $this->getTitle();
+ $undelete = $this->getPageTitle();
$out->addHTML( "<ul>\n" );
foreach ( $result as $row ) {
$title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
@@ -868,7 +907,7 @@ class SpecialUndelete extends SpecialPage {
return;
}
- $archive = new PageArchive( $this->mTargetObj );
+ $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
return;
}
@@ -879,6 +918,7 @@ class SpecialUndelete extends SpecialPage {
if ( !$rev ) {
$out->addWikiMsg( 'undeleterevision-missing' );
+
return;
}
@@ -886,14 +926,17 @@ class SpecialUndelete extends SpecialPage {
if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
$out->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
- 'rev-deleted-text-permission'
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ) ?
+ 'rev-suppressed-text-permission' : 'rev-deleted-text-permission'
);
+
return;
}
$out->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
- 'rev-deleted-text-view'
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ) ?
+ 'rev-suppressed-text-view' : 'rev-deleted-text-view'
);
$out->addHTML( '<br />' );
// and we are allowed to see...
@@ -914,7 +957,7 @@ class SpecialUndelete extends SpecialPage {
}
$link = Linker::linkKnown(
- $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ),
+ $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
htmlspecialchars( $this->mTargetObj->getPrefixedText() )
);
@@ -997,7 +1040,7 @@ class SpecialUndelete extends SpecialPage {
'style' => 'clear: both' ) ) .
Xml::openElement( 'form', array(
'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
+ 'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'target',
@@ -1032,26 +1075,19 @@ class SpecialUndelete extends SpecialPage {
$diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
$diffEngine->showDiffStyle();
- $this->getOutput()->addHTML( "<div>" .
- "<table style='width: 98%;' cellpadding='0' cellspacing='4' class='diff'>" .
- "<col class='diff-marker' />" .
- "<col class='diff-content' />" .
- "<col class='diff-marker' />" .
- "<col class='diff-content' />" .
- "<tr>" .
- "<td colspan='2' style='width: 50%; text-align: center' class='diff-otitle'>" .
- $this->diffHeader( $previousRev, 'o' ) .
- "</td>\n" .
- "<td colspan='2' style='width: 50%; text-align: center' class='diff-ntitle'>" .
- $this->diffHeader( $currentRev, 'n' ) .
- "</td>\n" .
- "</tr>" .
- $diffEngine->generateContentDiffBody(
- $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
- $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) .
- "</table>" .
- "</div>\n"
+
+ $formattedDiff = $diffEngine->generateContentDiffBody(
+ $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
+ $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
);
+
+ $formattedDiff = $diffEngine->addHeader(
+ $formattedDiff,
+ $this->diffHeader( $previousRev, 'o' ),
+ $this->diffHeader( $currentRev, 'n' )
+ );
+
+ $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
}
/**
@@ -1063,7 +1099,7 @@ class SpecialUndelete extends SpecialPage {
$isDeleted = !( $rev->getId() && $rev->getTitle() );
if ( $isDeleted ) {
/// @todo FIXME: $rev->getTitle() is null for deleted revs...?
- $targetPage = $this->getTitle();
+ $targetPage = $this->getPageTitle();
$targetQuery = array(
'target' => $this->mTargetObj->getPrefixedText(),
'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
@@ -1083,6 +1119,18 @@ class SpecialUndelete extends SpecialPage {
$rdel = " $rdel";
}
+ $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
+
+ $tags = wfGetDB( DB_SLAVE )->selectField(
+ 'tag_summary',
+ 'ts_tags',
+ array( 'ts_rev_id' => $rev->getId() ),
+ __METHOD__
+ );
+ $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff' );
+
+ // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
+ // and partially #showDiffPage, but worse
return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
Linker::link(
$targetPage,
@@ -1100,12 +1148,16 @@ class SpecialUndelete extends SpecialPage {
Linker::revUserTools( $rev ) . '<br />' .
'</div>' .
'<div id="mw-diff-' . $prefix . 'title3">' .
- Linker::revComment( $rev ) . $rdel . '<br />' .
+ $minor . Linker::revComment( $rev ) . $rdel . '<br />' .
+ '</div>' .
+ '<div id="mw-diff-' . $prefix . 'title5">' .
+ $tagSummary[0] . '<br />' .
'</div>';
}
/**
* Show a form confirming whether a tokenless user really wants to see a file
+ * @param string $key
*/
private function showFileConfirmationForm( $key ) {
$out = $this->getOutput();
@@ -1119,7 +1171,7 @@ class SpecialUndelete extends SpecialPage {
$out->addHTML(
Xml::openElement( 'form', array(
'method' => 'POST',
- 'action' => $this->getTitle()->getLocalURL( array(
+ 'action' => $this->getPageTitle()->getLocalURL( array(
'target' => $this->mTarget,
'file' => $key,
'token' => $user->getEditToken( $key ),
@@ -1133,6 +1185,7 @@ class SpecialUndelete extends SpecialPage {
/**
* Show a deleted file version requested by the visitor.
+ * @param string $key
*/
private function showFile( $key ) {
$this->getOutput()->disable();
@@ -1161,7 +1214,7 @@ class SpecialUndelete extends SpecialPage {
array( 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) )
);
- $archive = new PageArchive( $this->mTargetObj );
+ $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) );
/*
$text = $archive->getLastRevisionText();
@@ -1207,7 +1260,7 @@ class SpecialUndelete extends SpecialPage {
}
if ( $this->mAllowed ) {
- $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) );
+ $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
$top = Xml::openElement(
'form',
@@ -1243,32 +1296,42 @@ class SpecialUndelete extends SpecialPage {
$unsuppressBox = '';
}
- $table =
- Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
- Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
- "<tr>
- <td colspan='2' class='mw-undelete-extrahelp'>" .
- $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment', 'autofocus' => true ) ) .
- "</td>
- </tr>
- <tr>
- <td>&#160;</td>
- <td class='mw-submit'>" .
- Xml::submitButton( $this->msg( 'undeletebtn' )->text(), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
- Xml::submitButton( $this->msg( 'undeleteinvert' )->text(), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
- "</td>
- </tr>" .
- $unsuppressBox .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' );
+ $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
+ "<tr>
+ <td colspan='2' class='mw-undelete-extrahelp'>" .
+ $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
+ "</td>
+ </tr>
+ <tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
+ "</td>
+ <td class='mw-input'>" .
+ Xml::input(
+ 'wpComment',
+ 50,
+ $this->mComment,
+ array( 'id' => 'wpComment', 'autofocus' => true )
+ ) .
+ "</td>
+ </tr>
+ <tr>
+ <td>&#160;</td>
+ <td class='mw-submit'>" .
+ Xml::submitButton(
+ $this->msg( 'undeletebtn' )->text(),
+ array( 'name' => 'restore', 'id' => 'mw-undelete-submit' )
+ ) . ' ' .
+ Xml::submitButton(
+ $this->msg( 'undeleteinvert' )->text(),
+ array( 'name' => 'invert', 'id' => 'mw-undelete-invert' )
+ ) .
+ "</td>
+ </tr>" .
+ $unsuppressBox .
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'fieldset' );
$out->addHTML( $table );
}
@@ -1338,7 +1401,7 @@ class SpecialUndelete extends SpecialPage {
// Build page & diff links...
$user = $this->getUser();
if ( $this->mCanView ) {
- $titleObj = $this->getTitle();
+ $titleObj = $this->getPageTitle();
# Last link
if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
$pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
@@ -1367,6 +1430,9 @@ class SpecialUndelete extends SpecialPage {
// User links
$userLink = Linker::revUserTools( $rev );
+ // Minor edit
+ $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
+
// Revision text size
$size = $row->ar_len;
if ( !is_null( $size ) ) {
@@ -1376,14 +1442,31 @@ class SpecialUndelete extends SpecialPage {
// Edit summary
$comment = Linker::revComment( $rev );
+ // Tags
+ $attribs = array();
+ list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'deletedhistory' );
+ if ( $classes ) {
+ $attribs['class'] = implode( ' ', $classes );
+ }
+
// Revision delete links
$revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
- $revisionRow = $this->msg( 'undelete-revisionrow' )
- ->rawParams( $checkBox, $revdlink, $last, $pageLink, $userLink, $revTextSize, $comment )
+ $revisionRow = $this->msg( 'undelete-revision-row' )
+ ->rawParams(
+ $checkBox,
+ $revdlink,
+ $last,
+ $pageLink,
+ $userLink,
+ $minor,
+ $revTextSize,
+ $comment,
+ $tagSummary
+ )
->escaped();
- return "<li>$revisionRow</li>";
+ return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
}
private function formatFileRow( $row ) {
@@ -1391,12 +1474,14 @@ class SpecialUndelete extends SpecialPage {
$ts = wfTimestamp( TS_MW, $row->fa_timestamp );
$user = $this->getUser();
- if ( $this->mAllowed && $row->fa_storage_key ) {
- $checkBox = Xml::check( 'fileid' . $row->fa_id );
+ $checkBox = '';
+ if ( $this->mCanView && $row->fa_storage_key ) {
+ if ( $this->mAllowed ) {
+ $checkBox = Xml::check( 'fileid' . $row->fa_id );
+ }
$key = urlencode( $row->fa_storage_key );
- $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key );
+ $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
} else {
- $checkBox = '';
$pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user );
}
$userLink = $this->getFileUser( $file );
@@ -1408,8 +1493,8 @@ class SpecialUndelete extends SpecialPage {
$comment = $this->getFileComment( $file );
// Add show/hide deletion links if available
- $canHide = $user->isAllowed( 'deleterevision' );
- if ( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
+ $canHide = $this->isAllowed( 'deleterevision' );
+ if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
// Revision was hidden from sysops
$revdlink = Linker::revDeleteLinkDisabled( $canHide );
@@ -1468,7 +1553,7 @@ class SpecialUndelete extends SpecialPage {
* @param File|ArchivedFile $file
* @param Title $titleObj
* @param string $ts A timestamp
- * @param string $key a storage key
+ * @param string $key A storage key
*
* @return string HTML fragment
*/
@@ -1543,9 +1628,7 @@ class SpecialUndelete extends SpecialPage {
}
function undelete() {
- global $wgUploadMaintenance;
-
- if ( $wgUploadMaintenance && $this->mTargetObj->getNamespace() == NS_FILE ) {
+ if ( $this->getConfig()->get( 'UploadMaintenance' ) && $this->mTargetObj->getNamespace() == NS_FILE ) {
throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
}
@@ -1554,7 +1637,7 @@ class SpecialUndelete extends SpecialPage {
}
$out = $this->getOutput();
- $archive = new PageArchive( $this->mTargetObj );
+ $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) );
$ok = $archive->undelete(
$this->mTargetTimestamp,
@@ -1580,13 +1663,23 @@ class SpecialUndelete extends SpecialPage {
// Show revision undeletion warnings and errors
$status = $archive->getRevisionStatus();
if ( $status && !$status->isGood() ) {
- $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' );
+ $out->addWikiText( '<div class="error">' .
+ $status->getWikiText(
+ 'cannotundelete',
+ 'cannotundelete'
+ ) . '</div>'
+ );
}
// Show file undeletion warnings and errors
$status = $archive->getFileStatus();
if ( $status && !$status->isGood() ) {
- $out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
+ $out->addWikiText( '<div class="error">' .
+ $status->getWikiText(
+ 'undelete-error-short',
+ 'undelete-error-long'
+ ) . '</div>'
+ );
}
}
diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php
index 35141d80..a8b97d78 100644
--- a/includes/specials/SpecialUnlockdb.php
+++ b/includes/specials/SpecialUnlockdb.php
@@ -37,11 +37,9 @@ class SpecialUnlockdb extends FormSpecialPage {
}
public function checkExecutePermissions( User $user ) {
- global $wgReadOnlyFile;
-
parent::checkExecutePermissions( $user );
# If the lock file isn't writable, we can do sweet bugger all
- if ( !file_exists( $wgReadOnlyFile ) ) {
+ if ( !file_exists( $this->getConfig()->get( 'ReadOnlyFile' ) ) ) {
throw new ErrorPageError( 'lockdb', 'databasenotlocked' );
}
}
@@ -62,20 +60,19 @@ class SpecialUnlockdb extends FormSpecialPage {
}
public function onSubmit( array $data ) {
- global $wgReadOnlyFile;
-
if ( !$data['Confirm'] ) {
return Status::newFatal( 'locknoconfirm' );
}
+ $readOnlyFile = $this->getConfig()->get( 'ReadOnlyFile' );
wfSuppressWarnings();
- $res = unlink( $wgReadOnlyFile );
+ $res = unlink( $readOnlyFile );
wfRestoreWarnings();
if ( $res ) {
return Status::newGood();
} else {
- return Status::newFatal( 'filedeleteerror', $wgReadOnlyFile );
+ return Status::newFatal( 'filedeleteerror', $readOnlyFile );
}
}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index b686a5b5..713823bb 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -25,15 +25,14 @@
* @ingroup SpecialPage
*/
class UnusedCategoriesPage extends QueryPage {
+ function __construct( $name = 'Unusedcategories' ) {
+ parent::__construct( $name );
+ }
function isExpensive() {
return true;
}
- function __construct( $name = 'Unusedcategories' ) {
- parent::__construct( $name );
- }
-
function getPageHeader() {
return $this->msg( 'unusedcategoriestext' )->parseAsBlock();
}
@@ -41,14 +40,17 @@ class UnusedCategoriesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'categorylinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
- 'conds' => array( 'cl_from IS NULL',
- 'page_namespace' => NS_CATEGORY,
- 'page_is_redirect' => 0 ),
- 'join_conds' => array( 'categorylinks' => array(
- 'LEFT JOIN', 'cl_to = page_title' ) )
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
+ 'conds' => array(
+ 'cl_from IS NULL',
+ 'page_namespace' => NS_CATEGORY,
+ 'page_is_redirect' => 0
+ ),
+ 'join_conds' => array( 'categorylinks' => array( 'LEFT JOIN', 'cl_to = page_title' ) )
);
}
@@ -67,6 +69,7 @@ class UnusedCategoriesPage extends QueryPage {
*/
function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_CATEGORY, $result->title );
+
return Linker::link( $title, htmlspecialchars( $title->getText() ) );
}
diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php
index d332db75..36ec09ec 100644
--- a/includes/specials/SpecialUnusedimages.php
+++ b/includes/specials/SpecialUnusedimages.php
@@ -44,31 +44,32 @@ class UnusedimagesPage extends ImageQueryPage {
}
function getQueryInfo() {
- global $wgCountCategorizedImagesAsUsed;
$retval = array(
'tables' => array( 'image', 'imagelinks' ),
- 'fields' => array( 'namespace' => NS_FILE,
- 'title' => 'img_name',
- 'value' => 'img_timestamp',
- 'img_user', 'img_user_text',
- 'img_description' ),
+ 'fields' => array(
+ 'namespace' => NS_FILE,
+ 'title' => 'img_name',
+ 'value' => 'img_timestamp',
+ 'img_user', 'img_user_text',
+ 'img_description'
+ ),
'conds' => array( 'il_to IS NULL' ),
- 'join_conds' => array( 'imagelinks' => array(
- 'LEFT JOIN', 'il_to = img_name' ) )
+ 'join_conds' => array( 'imagelinks' => array( 'LEFT JOIN', 'il_to = img_name' ) )
);
- if ( $wgCountCategorizedImagesAsUsed ) {
+ if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
// Order is significant
$retval['tables'] = array( 'image', 'page', 'categorylinks',
- 'imagelinks' );
+ 'imagelinks' );
$retval['conds']['page_namespace'] = NS_FILE;
$retval['conds'][] = 'cl_from IS NULL';
$retval['conds'][] = 'img_name = page_title';
$retval['join_conds']['categorylinks'] = array(
- 'LEFT JOIN', 'cl_from = page_id' );
+ 'LEFT JOIN', 'cl_from = page_id' );
$retval['join_conds']['imagelinks'] = array(
- 'LEFT JOIN', 'il_to = page_title' );
+ 'LEFT JOIN', 'il_to = page_title' );
}
+
return $retval;
}
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index 1dc9f420..0c2b8707 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -30,7 +30,6 @@
* @ingroup SpecialPage
*/
class UnusedtemplatesPage extends QueryPage {
-
function __construct( $name = 'Unusedtemplates' ) {
parent::__construct( $name );
}
@@ -50,12 +49,16 @@ class UnusedtemplatesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'templatelinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
- 'conds' => array( 'page_namespace' => NS_TEMPLATE,
- 'tl_from IS NULL',
- 'page_is_redirect' => 0 ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
+ 'conds' => array(
+ 'page_namespace' => NS_TEMPLATE,
+ 'tl_from IS NULL',
+ 'page_is_redirect' => 0
+ ),
'join_conds' => array( 'templatelinks' => array(
'LEFT JOIN', array( 'tl_title = page_title',
'tl_namespace = page_namespace' ) ) )
@@ -79,6 +82,7 @@ class UnusedtemplatesPage extends QueryPage {
SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ),
$this->msg( 'unusedtemplateswlh' )->escaped()
);
+
return $this->getLanguage()->specialList( $pageLink, $wlhLink );
}
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index 954e3ffe..bb07c197 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -46,13 +46,16 @@ class UnwatchedpagesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'watchlist' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_namespace' ),
- 'conds' => array( 'wl_title IS NULL',
- 'page_is_redirect' => 0,
- "page_namespace != '" . NS_MEDIAWIKI .
- "'" ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_namespace'
+ ),
+ 'conds' => array(
+ 'wl_title IS NULL',
+ 'page_is_redirect' => 0,
+ "page_namespace != '" . NS_MEDIAWIKI . "'"
+ ),
'join_conds' => array( 'watchlist' => array(
'LEFT JOIN', array( 'wl_title = page_title',
'wl_namespace = page_namespace' ) ) )
@@ -68,6 +71,15 @@ class UnwatchedpagesPage extends QueryPage {
}
/**
+ * Add the JS
+ * @param string|null $par
+ */
+ public function execute( $par ) {
+ parent::execute( $par );
+ $this->getOutput()->addModules( 'mediawiki.special.unwatchedPages' );
+ }
+
+ /**
* @param Skin $skin
* @param object $result Result row
* @return string
@@ -88,7 +100,7 @@ class UnwatchedpagesPage extends QueryPage {
$wlink = Linker::linkKnown(
$nt,
$this->msg( 'watch' )->escaped(),
- array(),
+ array( 'class' => 'mw-watch-link' ),
array( 'action' => 'watch', 'token' => $token )
);
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 09facf4f..55d09dd6 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -32,51 +32,57 @@ class SpecialUpload extends SpecialPage {
/**
* Constructor : initialise object
* Get data POSTed through the form and assign them to the object
- * @param $request WebRequest : data posted.
+ * @param WebRequest $request Data posted.
*/
public function __construct( $request = null ) {
parent::__construct( 'Upload', 'upload' );
}
/** Misc variables **/
- public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle
+
+ /** @var WebRequest|FauxRequest The request this form is supposed to handle */
+ public $mRequest;
public $mSourceType;
- /**
- * @var UploadBase
- */
+ /** @var UploadBase */
public $mUpload;
- /**
- * @var LocalFile
- */
+ /** @var LocalFile */
public $mLocalFile;
public $mUploadClicked;
/** User input variables from the "description" section **/
- public $mDesiredDestName; // The requested target file name
+
+ /** @var string The requested target file name */
+ public $mDesiredDestName;
public $mComment;
public $mLicense;
/** User input variables from the root section **/
+
public $mIgnoreWarning;
- public $mWatchThis;
+ public $mWatchthis;
public $mCopyrightStatus;
public $mCopyrightSource;
/** Hidden variables **/
+
public $mDestWarningAck;
- public $mForReUpload; // The user followed an "overwrite this file" link
- public $mCancelUpload; // The user clicked "Cancel and return to upload form" button
+
+ /** @var bool The user followed an "overwrite this file" link */
+ public $mForReUpload;
+
+ /** @var bool The user clicked "Cancel and return to upload form" button */
+ public $mCancelUpload;
public $mTokenOk;
- public $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded
+
+ /** @var bool Subclasses can use this to determine whether a file was uploaded */
+ public $mUploadSuccessful = false;
/** Text injection points for hooks not using HTMLForm **/
public $uploadFormTextTop;
public $uploadFormTextAfterSummary;
- public $mWatchthis;
-
/**
* Initialize instance variables from request and create an Upload handler
*/
@@ -127,8 +133,8 @@ class SpecialUpload extends SpecialPage {
* Handle permission checking elsewhere in order to be able to show
* custom error messages.
*
- * @param $user User object
- * @return Boolean
+ * @param User $user
+ * @return bool
*/
public function userCanExecute( User $user ) {
return UploadBase::isEnabled() && parent::userCanExecute( $user );
@@ -136,6 +142,7 @@ class SpecialUpload extends SpecialPage {
/**
* Special page entry point
+ * @param string $par
*/
public function execute( $par ) {
$this->setHeaders();
@@ -180,7 +187,8 @@ class SpecialUpload extends SpecialPage {
} else {
# Backwards compatibility hook
if ( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
- wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
+ wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
+
return;
}
$this->showUploadForm( $this->getUploadForm() );
@@ -195,7 +203,7 @@ class SpecialUpload extends SpecialPage {
/**
* Show the main upload form
*
- * @param $form Mixed: an HTMLForm instance or HTML string to show
+ * @param HTMLForm|string $form An HTMLForm instance or HTML string to show
*/
protected function showUploadForm( $form ) {
# Add links if file was previously deleted
@@ -208,21 +216,20 @@ class SpecialUpload extends SpecialPage {
} else {
$this->getOutput()->addHTML( $form );
}
-
}
/**
* Get an UploadForm instance with title and text properly set.
*
* @param string $message HTML string to add to the form
- * @param string $sessionKey session key in case this is a stashed upload
- * @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" check box
+ * @param string $sessionKey Session key in case this is a stashed upload
+ * @param bool $hideIgnoreWarning Whether to hide "ignore warning" check box
* @return UploadForm
*/
protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
# Initialize form
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new UploadForm( array(
'watch' => $this->getWatchCheck(),
'forreupload' => $this->mForReUpload,
@@ -317,23 +324,24 @@ class SpecialUpload extends SpecialPage {
$form->setSubmitText( $this->msg( 'upload-tryagain' )->escaped() );
$this->showUploadForm( $form );
}
+
/**
* Stashes the upload, shows the main form, but adds a "continue anyway button".
* Also checks whether there are actually warnings to display.
*
- * @param $warnings Array
- * @return boolean true if warnings were displayed, false if there are no
- * warnings and it should continue processing
+ * @param array $warnings
+ * @return bool True if warnings were displayed, false if there are no
+ * warnings and it should continue processing
*/
protected function showUploadWarning( $warnings ) {
# If there are no warnings, or warnings we can ignore, return early.
# mDestWarningAck is set when some javascript has shown the warning
# to the user. mForReUpload is set when the user clicks the "upload a
# new version" link.
- if ( !$warnings || ( count( $warnings ) == 1 &&
- isset( $warnings['exists'] ) &&
- ( $this->mDestWarningAck || $this->mForReUpload ) ) )
- {
+ if ( !$warnings || ( count( $warnings ) == 1
+ && isset( $warnings['exists'] )
+ && ( $this->mDestWarningAck || $this->mForReUpload ) )
+ ) {
return false;
}
@@ -350,9 +358,14 @@ class SpecialUpload extends SpecialPage {
} elseif ( $warning == 'duplicate' ) {
$msg = $this->getDupeWarning( $args );
} elseif ( $warning == 'duplicate-archive' ) {
- $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
- Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
- . "</li>\n";
+ if ( $args === '' ) {
+ $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse()
+ . "</li>\n";
+ } else {
+ $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
+ Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
+ . "</li>\n";
+ }
} else {
if ( $args === true ) {
$args = array();
@@ -397,6 +410,7 @@ class SpecialUpload extends SpecialPage {
$status = $this->mUpload->fetchFile();
if ( !$status->isOK() ) {
$this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
+
return;
}
@@ -414,6 +428,7 @@ class SpecialUpload extends SpecialPage {
$details = $this->mUpload->verifyUpload();
if ( $details['status'] != UploadBase::OK ) {
$this->processVerificationError( $details );
+
return;
}
@@ -422,6 +437,7 @@ class SpecialUpload extends SpecialPage {
if ( $permErrors !== true ) {
$code = array_shift( $permErrors[0] );
$this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() );
+
return;
}
@@ -442,9 +458,17 @@ class SpecialUpload extends SpecialPage {
} else {
$pageText = false;
}
- $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $this->getUser() );
+
+ $status = $this->mUpload->performUpload(
+ $this->mComment,
+ $pageText,
+ $this->mWatchthis,
+ $this->getUser()
+ );
+
if ( !$status->isGood() ) {
$this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
+
return;
}
@@ -456,13 +480,16 @@ class SpecialUpload extends SpecialPage {
/**
* Get the initial image page text based on a comment and optional file status information
- * @param $comment string
- * @param $license string
- * @param $copyStatus string
- * @param $source string
+ * @param string $comment
+ * @param string $license
+ * @param string $copyStatus
+ * @param string $source
* @return string
+ * @todo Use Config obj instead of globals
*/
- public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
+ public static function getInitialPageText( $comment = '', $license = '',
+ $copyStatus = '', $source = ''
+ ) {
global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
$msg = array();
@@ -496,6 +523,7 @@ class SpecialUpload extends SpecialPage {
$pageText = $comment;
}
}
+
return $pageText;
}
@@ -509,7 +537,7 @@ class SpecialUpload extends SpecialPage {
*
* Note that the page target can be changed *on the form*, so our check
* state can get out of sync.
- * @return Bool|String
+ * @return bool|string
*/
protected function getWatchCheck() {
if ( $this->getUser()->getOption( 'watchdefault' ) ) {
@@ -517,11 +545,17 @@ class SpecialUpload extends SpecialPage {
return true;
}
+ $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+ if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) {
+ // Already watched, don't change that
+ return true;
+ }
+
$local = wfLocalFile( $this->mDesiredDestName );
if ( $local && $local->exists() ) {
// We're uploading a new version of an existing file.
// No creation, so don't watch it if we're not already.
- return $this->getUser()->isWatched( $local->getTitle() );
+ return false;
} else {
// New page should get watched if that's our option.
return $this->getUser()->getOption( 'watchcreations' );
@@ -531,12 +565,10 @@ class SpecialUpload extends SpecialPage {
/**
* Provides output to the user for a result of UploadBase::verifyUpload
*
- * @param array $details result of UploadBase::verifyUpload
+ * @param array $details Result of UploadBase::verifyUpload
* @throws MWException
*/
protected function processVerificationError( $details ) {
- global $wgFileExtensions;
-
switch ( $details['status'] ) {
/** Statuses that only require name changing **/
@@ -571,7 +603,7 @@ class SpecialUpload extends SpecialPage {
} else {
$msg->params( $details['finalExt'] );
}
- $extensions = array_unique( $wgFileExtensions );
+ $extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) );
$msg->params( $this->getLanguage()->commaList( $extensions ),
count( $extensions ) );
@@ -610,7 +642,7 @@ class SpecialUpload extends SpecialPage {
/**
* Remove a temporarily kept file stashed by saveTempUploadedFile().
*
- * @return Boolean: success
+ * @return bool Success
*/
protected function unsaveUploadedFile() {
if ( !( $this->mUpload instanceof UploadFromStash ) ) {
@@ -619,6 +651,7 @@ class SpecialUpload extends SpecialPage {
$success = $this->mUpload->unsaveUploadedFile();
if ( !$success ) {
$this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() );
+
return false;
} else {
return true;
@@ -631,8 +664,8 @@ class SpecialUpload extends SpecialPage {
* Formats a result of UploadBase::getExistsWarning as HTML
* This check is static and can be done pre-upload via AJAX
*
- * @param array $exists the result of UploadBase::getExistsWarning
- * @return String: empty string if there is no warning or an HTML fragment
+ * @param array $exists The result of UploadBase::getExistsWarning
+ * @return string Empty string if there is no warning or an HTML fragment
*/
public static function getExistsWarning( $exists ) {
if ( !$exists ) {
@@ -683,7 +716,7 @@ class SpecialUpload extends SpecialPage {
/**
* Construct a warning and a gallery from an array of duplicate files.
- * @param $dupes array
+ * @param array $dupes
* @return string
*/
public function getDupeWarning( $dupes ) {
@@ -691,12 +724,12 @@ class SpecialUpload extends SpecialPage {
return '';
}
- $gallery = ImageGalleryBase::factory();
- $gallery->setContext( $this->getContext() );
+ $gallery = ImageGalleryBase::factory( false, $this->getContext() );
$gallery->setShowBytes( false );
foreach ( $dupes as $file ) {
$gallery->add( $file->getTitle() );
}
+
return '<li>' .
wfMessage( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
$gallery->toHtml() . "</li>\n";
@@ -705,6 +738,18 @@ class SpecialUpload extends SpecialPage {
protected function getGroupName() {
return 'media';
}
+
+ /**
+ * Should we rotate images in the preview on Special:Upload.
+ *
+ * This controls js: mw.config.get( 'wgFileCanRotate' )
+ *
+ * @todo What about non-BitmapHandler handled files?
+ */
+ static public function rotationEnabled() {
+ $bitmapHandler = new BitmapHandler();
+ return $bitmapHandler->autoRotateEnabled();
+ }
}
/**
@@ -731,8 +776,7 @@ class UploadForm extends HTMLForm {
public function __construct( array $options = array(), IContextSource $context = null ) {
$this->mWatch = !empty( $options['watch'] );
$this->mForReUpload = !empty( $options['forreupload'] );
- $this->mSessionKey = isset( $options['sessionkey'] )
- ? $options['sessionkey'] : '';
+ $this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : '';
$this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
$this->mDestWarningAck = !empty( $options['destwarningack'] );
$this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
@@ -754,6 +798,18 @@ class UploadForm extends HTMLForm {
wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) );
parent::__construct( $descriptor, $context, 'upload' );
+ # Add a link to edit MediaWik:Licenses
+ if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
+ $licensesLink = Linker::link(
+ Title::makeTitle( NS_MEDIAWIKI, 'Licenses' ),
+ $this->msg( 'licenses-edit' )->escaped(),
+ array(),
+ array( 'action' => 'edit' )
+ );
+ $editLicenses = '<p class="mw-upload-editlicenses">' . $licensesLink . '</p>';
+ $this->addFooterText( $editLicenses, 'description' );
+ }
+
# Set some form properties
$this->setSubmitText( $this->msg( 'uploadbtn' )->text() );
$this->setSubmitName( 'wpUpload' );
@@ -768,18 +824,15 @@ class UploadForm extends HTMLForm {
$this->mSourceIds[] = $field['id'];
}
}
-
}
/**
* Get the descriptor of the fieldset that contains the file source
* selection. The section is 'source'
*
- * @return Array: descriptor array
+ * @return array Descriptor array
*/
protected function getSourceSection() {
- global $wgCopyUploadsFromSpecialUpload;
-
if ( $this->mSessionKey ) {
return array(
'SessionKey' => array(
@@ -794,8 +847,8 @@ class UploadForm extends HTMLForm {
}
$canUploadByUrl = UploadFromUrl::isEnabled()
- && UploadFromUrl::isAllowed( $this->getUser() )
- && $wgCopyUploadsFromSpecialUpload;
+ && ( UploadFromUrl::isAllowed( $this->getUser() ) === true )
+ && $this->getConfig()->get( 'CopyUploadsFromSpecialUpload' );
$radio = $canUploadByUrl;
$selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
@@ -812,7 +865,7 @@ class UploadForm extends HTMLForm {
$this->mMaxUploadSize['file'] = UploadBase::getMaxUploadSize( 'file' );
# Limit to upload_max_filesize unless we are running under HipHop and
# that setting doesn't exist
- if ( !wfIsHipHop() ) {
+ if ( !wfIsHHVM() ) {
$this->mMaxUploadSize['file'] = min( $this->mMaxUploadSize['file'],
wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
wfShorthandToInteger( ini_get( 'post_max_size' ) )
@@ -824,12 +877,13 @@ class UploadForm extends HTMLForm {
'section' => 'source',
'type' => 'file',
'id' => 'wpUploadFile',
+ 'radio-id' => 'wpSourceTypeFile',
'label-message' => 'sourcefilename',
'upload-type' => 'File',
'radio' => &$radio,
'help' => $this->msg( 'upload-maxfilesize',
- $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] ) )
- ->parse() .
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
+ )->parse() .
$this->msg( 'word-separator' )->escaped() .
$this->msg( 'upload_source_file' )->escaped(),
'checked' => $selectedSourceType == 'file',
@@ -841,12 +895,13 @@ class UploadForm extends HTMLForm {
'class' => 'UploadSourceField',
'section' => 'source',
'id' => 'wpUploadFileURL',
+ 'radio-id' => 'wpSourceTypeurl',
'label-message' => 'sourceurl',
'upload-type' => 'url',
'radio' => &$radio,
'help' => $this->msg( 'upload-maxfilesize',
- $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] ) )
- ->parse() .
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
+ )->parse() .
$this->msg( 'word-separator' )->escaped() .
$this->msg( 'upload_source_url' )->escaped(),
'checked' => $selectedSourceType == 'url',
@@ -860,41 +915,57 @@ class UploadForm extends HTMLForm {
'default' => $this->getExtensionsMessage(),
'raw' => true,
);
+
return $descriptor;
}
/**
* Get the messages indicating which extensions are preferred and prohibitted.
*
- * @return String: HTML string containing the message
+ * @return string HTML string containing the message
*/
protected function getExtensionsMessage() {
# Print a list of allowed file extensions, if so configured. We ignore
# MIME type here, it's incomprehensible to most people and too long.
- global $wgCheckFileExtensions, $wgStrictFileExtensions,
- $wgFileExtensions, $wgFileBlacklist;
+ $config = $this->getConfig();
- if ( $wgCheckFileExtensions ) {
- if ( $wgStrictFileExtensions ) {
+ if ( $config->get( 'CheckFileExtensions' ) ) {
+ if ( $config->get( 'StrictFileExtensions' ) ) {
# Everything not permitted is banned
$extensionsList =
'<div id="mw-upload-permitted">' .
- $this->msg( 'upload-permitted', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) ) )->parseAsBlock() .
+ $this->msg(
+ 'upload-permitted',
+ $this->getContext()->getLanguage()->commaList(
+ array_unique( $config->get( 'FileExtensions' ) )
+ )
+ )->parseAsBlock() .
"</div>\n";
} else {
# We have to list both preferred and prohibited
$extensionsList =
'<div id="mw-upload-preferred">' .
- $this->msg( 'upload-preferred', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) ) )->parseAsBlock() .
+ $this->msg(
+ 'upload-preferred',
+ $this->getContext()->getLanguage()->commaList(
+ array_unique( $config->get( 'FileExtensions' ) )
+ )
+ )->parseAsBlock() .
"</div>\n" .
'<div id="mw-upload-prohibited">' .
- $this->msg( 'upload-prohibited', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileBlacklist ) ) )->parseAsBlock() .
+ $this->msg(
+ 'upload-prohibited',
+ $this->getContext()->getLanguage()->commaList(
+ array_unique( $config->get( 'FileBlacklist' ) )
+ )
+ )->parseAsBlock() .
"</div>\n";
}
} else {
# Everything is permitted.
$extensionsList = '';
}
+
return $extensionsList;
}
@@ -902,9 +973,10 @@ class UploadForm extends HTMLForm {
* Get the descriptor of the fieldset that contains the file description
* input. The section is 'description'
*
- * @return Array: descriptor array
+ * @return array Descriptor array
*/
protected function getDescriptionSection() {
+ $config = $this->getConfig();
if ( $this->mSessionKey ) {
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
try {
@@ -977,8 +1049,7 @@ class UploadForm extends HTMLForm {
);
}
- global $wgUseCopyrightUpload;
- if ( $wgUseCopyrightUpload ) {
+ if ( $config->get( 'UseCopyrightUpload' ) ) {
$descriptor['UploadCopyStatus'] = array(
'type' => 'text',
'section' => 'description',
@@ -1000,7 +1071,7 @@ class UploadForm extends HTMLForm {
* Get the descriptor of the fieldset that contains the upload options,
* such as "watch this file". The section is 'options'
*
- * @return Array: descriptor array
+ * @return array Descriptor array
*/
protected function getOptionsSection() {
$user = $this->getUser();
@@ -1011,7 +1082,7 @@ class UploadForm extends HTMLForm {
'id' => 'wpWatchthis',
'label-message' => 'watchthisupload',
'section' => 'options',
- 'default' => $user->getOption( 'watchcreations' ),
+ 'default' => $this->mWatch,
)
);
}
@@ -1053,10 +1124,11 @@ class UploadForm extends HTMLForm {
* Add upload JS to the OutputPage
*/
protected function addUploadJS() {
- global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI, $wgStrictFileExtensions;
+ $config = $this->getConfig();
- $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
- $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI;
+ $useAjaxDestCheck = $config->get( 'UseAjax' ) && $config->get( 'AjaxUploadDestCheck' );
+ $useAjaxLicensePreview = $config->get( 'UseAjax' ) &&
+ $config->get( 'AjaxLicensePreview' ) && $config->get( 'EnableAPI' );
$this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
$scriptVars = array(
@@ -1067,7 +1139,7 @@ class UploadForm extends HTMLForm {
// the wpDestFile textbox
$this->mDestFile === '',
'wgUploadSourceIds' => $this->mSourceIds,
- 'wgStrictFileExtensions' => $wgStrictFileExtensions,
+ 'wgStrictFileExtensions' => $config->get( 'StrictFileExtensions' ),
'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
'wgMaxUploadSize' => $this->mMaxUploadSize,
);
@@ -1077,20 +1149,18 @@ class UploadForm extends HTMLForm {
$out->addModules( array(
'mediawiki.action.edit', // For <charinsert> support
- 'mediawiki.legacy.upload', // Old form stuff...
- 'mediawiki.special.upload', // Newer extras for thumbnail preview.
+ 'mediawiki.special.upload', // Extras for thumbnail and license preview.
) );
}
/**
* Empty function; submission is handled elsewhere.
*
- * @return bool false
+ * @return bool False
*/
function trySubmit() {
return false;
}
-
}
/**
@@ -1099,7 +1169,7 @@ class UploadForm extends HTMLForm {
class UploadSourceField extends HTMLTextField {
/**
- * @param $cellAttributes array
+ * @param array $cellAttributes
* @return string
*/
function getLabelHtml( $cellAttributes = array() ) {
@@ -1107,15 +1177,25 @@ class UploadSourceField extends HTMLTextField {
$label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
if ( !empty( $this->mParams['radio'] ) ) {
+ if ( isset( $this->mParams['radio-id'] ) ) {
+ $radioId = $this->mParams['radio-id'];
+ } else {
+ // Old way. For the benefit of extensions that do not define
+ // the 'radio-id' key.
+ $radioId = 'wpSourceType' . $this->mParams['upload-type'];
+ }
+
$attribs = array(
'name' => 'wpSourceType',
'type' => 'radio',
- 'id' => $id,
+ 'id' => $radioId,
'value' => $this->mParams['upload-type'],
);
+
if ( !empty( $this->mParams['checked'] ) ) {
$attribs['checked'] = 'checked';
}
+
$label .= Html::element( 'input', $attribs );
}
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
index 1373df1a..ddb435d9 100644
--- a/includes/specials/SpecialUploadStash.php
+++ b/includes/specials/SpecialUploadStash.php
@@ -57,8 +57,9 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Execute page -- can output a file directly or show a listing of them.
*
- * @param string $subPage subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
- * @return Boolean: success
+ * @param string $subPage Subpage, e.g. in
+ * http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
+ * @return bool Success
*/
public function execute( $subPage ) {
$this->checkPermissions();
@@ -66,6 +67,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
if ( $subPage === null || $subPage === '' ) {
return $this->showUploads();
}
+
return $this->showUpload( $subPage );
}
@@ -73,7 +75,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* If file available in stash, cats it out to the client as a simple HTTP response.
* n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
*
- * @param string $key the key of a particular requested file
+ * @param string $key The key of a particular requested file
* @throws HttpError
* @return bool
*/
@@ -99,7 +101,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$message = $e->getMessage();
} catch ( SpecialUploadStashTooLargeException $e ) {
$code = 500;
- $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage();
+ $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES .
+ ' bytes. ' . $e->getMessage();
} catch ( Exception $e ) {
$code = 500;
$message = $e->getMessage();
@@ -136,10 +139,11 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$handler = $file->getHandler();
if ( $handler ) {
$params = $handler->parseParamString( $paramString );
+
return array( 'file' => $file, 'type' => $type, 'params' => $params );
} else {
throw new UploadStashBadPathException( 'No handler found for ' .
- "mime {$file->getMimeType()} of file {$file->getPath()}" );
+ "mime {$file->getMimeType()} of file {$file->getPath()}" );
}
}
@@ -149,20 +153,19 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Get a thumbnail for file, either generated locally or remotely, and stream it out
*
- * @param $file
- * @param $params array
+ * @param File $file
+ * @param array $params
*
- * @return boolean success
+ * @return bool Success
*/
private function outputThumbFromStash( $file, $params ) {
-
- // this global, if it exists, points to a "scaler", as you might find in the Wikimedia Foundation cluster. See outputRemoteScaledThumb()
- // this is part of our horrible NFS-based system, we create a file on a mount point here, but fetch the scaled file from somewhere else that
- // happens to share it over NFS
- global $wgUploadStashScalerBaseUrl;
-
$flags = 0;
- if ( $wgUploadStashScalerBaseUrl ) {
+ // this config option, if it exists, points to a "scaler", as you might find in
+ // the Wikimedia Foundation cluster. See outputRemoteScaledThumb(). This
+ // is part of our horrible NFS-based system, we create a file on a mount
+ // point here, but fetch the scaled file from somewhere else that
+ // happens to share it over NFS.
+ if ( $this->getConfig()->get( 'UploadStashScalerBaseUrl' ) ) {
$this->outputRemoteScaledThumb( $file, $params, $flags );
} else {
$this->outputLocallyScaledThumb( $file, $params, $flags );
@@ -170,16 +173,15 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
/**
- * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT.
- * @param $file File
+ * Scale a file (probably with a locally installed imagemagick, or similar)
+ * and output it to STDOUT.
+ * @param File $file
* @param array $params Scaling parameters ( e.g. array( width => '50' ) );
* @param int $flags Scaling flags ( see File:: constants )
- * @throws MWException
- * @throws UploadStashFileNotFoundException
- * @return boolean success
+ * @throws MWException|UploadStashFileNotFoundException
+ * @return bool Success
*/
private function outputLocallyScaledThumb( $file, $params, $flags ) {
-
// n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely
// on HTTP caching to ensure this doesn't happen.
@@ -195,8 +197,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
throw new UploadStashFileNotFoundException( "no local path for scaled item" );
}
- // now we should construct a File, so we can get mime and other such info in a standard way
- // n.b. mimetype may be different from original (ogx original -> jpeg thumb)
+ // now we should construct a File, so we can get MIME and other such info in a standard way
+ // n.b. MIME type may be different from original (ogx original -> jpeg thumb)
$thumbFile = new UnregisteredLocalFile( false,
$this->stash->repo, $thumbnailImage->getStoragePath(), false );
if ( !$thumbFile ) {
@@ -204,28 +206,32 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
return $this->outputLocalFile( $thumbFile );
-
}
/**
- * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster, and output it to STDOUT.
- * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves
- * and cat the results out.
- * Note: We rely on NFS to have propagated the file contents to the scaler. However, we do not rely on the thumbnail being created in NFS and then
- * propagated back to our filesystem. Instead we take the results of the HTTP request instead.
- * Note: no caching is being done here, although we are instructing the client to cache it forever.
- * @param $file: File object
- * @param $params: scaling parameters ( e.g. array( width => '50' ) );
- * @param $flags: scaling flags ( see File:: constants )
+ * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation
+ * cluster, and output it to STDOUT.
+ * Note: Unlike the usual thumbnail process, the web client never sees the
+ * cluster URL; we do the whole HTTP transaction to the scaler ourselves
+ * and cat the results out.
+ * Note: We rely on NFS to have propagated the file contents to the scaler.
+ * However, we do not rely on the thumbnail being created in NFS and then
+ * propagated back to our filesystem. Instead we take the results of the
+ * HTTP request instead.
+ * Note: No caching is being done here, although we are instructing the
+ * client to cache it forever.
+ *
+ * @param File $file
+ * @param array $params Scaling parameters ( e.g. array( width => '50' ) );
+ * @param int $flags Scaling flags ( see File:: constants )
* @throws MWException
- * @return boolean success
+ * @return bool Success
*/
private function outputRemoteScaledThumb( $file, $params, $flags ) {
-
- // this global probably looks something like 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'
- // do not use trailing slash
- global $wgUploadStashScalerBaseUrl;
- $scalerBaseUrl = $wgUploadStashScalerBaseUrl;
+ // This option probably looks something like
+ // 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'. Do not use
+ // trailing slash.
+ $scalerBaseUrl = $this->getConfig()->get( 'UploadStashScalerBaseUrl' );
if ( preg_match( '/^\/\//', $scalerBaseUrl ) ) {
// this is apparently a protocol-relative URL, which makes no sense in this context,
@@ -248,16 +254,17 @@ class SpecialUploadStash extends UnlistedSpecialPage {
);
$req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions );
$status = $req->execute();
- if ( ! $status->isOK() ) {
+ if ( !$status->isOK() ) {
$errors = $status->getErrorsArray();
$errorStr = "Fetching thumbnail failed: " . print_r( $errors, 1 );
$errorStr .= "\nurl = $scalerThumbUrl\n";
throw new MWException( $errorStr );
}
$contentType = $req->getResponseHeader( "content-type" );
- if ( ! $contentType ) {
+ if ( !$contentType ) {
throw new MWException( "Missing content-type header" );
}
+
return $this->outputContents( $req->getContent(), $contentType );
}
@@ -265,7 +272,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* Output HTTP response for file
* Side effect: writes HTTP response to STDOUT.
*
- * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!)
+ * @param File $file File object with a local path (e.g. UnregisteredLocalFile,
+ * LocalFile. Oddly these don't share an ancestor!)
* @throws SpecialUploadStashTooLargeException
* @return bool
*/
@@ -273,6 +281,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException();
}
+
return $file->getRepo()->streamFile( $file->getPath(),
array( 'Content-Transfer-Encoding: binary',
'Expires: Sun, 17-Jan-2038 19:14:07 GMT' )
@@ -282,8 +291,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Output HTTP response of raw content
* Side effect: writes HTTP response to STDOUT.
- * @param string $content content
- * @param string $contentType mime type
+ * @param string $content Content
+ * @param string $contentType MIME type
* @throws SpecialUploadStashTooLargeException
* @return bool
*/
@@ -294,15 +303,18 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
self::outputFileHeaders( $contentType, $size );
print $content;
+
return true;
}
/**
* Output headers for streaming
- * XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is.
+ * @todo Unsure about encoding as binary; if we received from HTTP perhaps
+ * we should use that encoding, concatenated with semicolon to `$contentType` as it
+ * usually is.
* Side effect: preps PHP to write headers to STDOUT.
- * @param string $contentType : string suitable for content-type header
- * @param string $size: length in bytes
+ * @param string $contentType String suitable for content-type header
+ * @param string $size Length in bytes
*/
private static function outputFileHeaders( $contentType, $size ) {
header( "Content-Type: $contentType", true );
@@ -324,11 +336,13 @@ class SpecialUploadStash extends UnlistedSpecialPage {
public static function tryClearStashedUploads( $formData ) {
if ( isset( $formData['Clear'] ) ) {
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
- wfDebug( "stash has: " . print_r( $stash->listFiles(), true ) );
- if ( ! $stash->clear() ) {
+ wfDebug( 'stash has: ' . print_r( $stash->listFiles(), true ) . "\n" );
+
+ if ( !$stash->clear() ) {
return Status::newFatal( 'uploadstash-errclear' );
}
}
+
return Status::newGood();
}
@@ -346,7 +360,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// this design is extremely dubious, but supposedly HTMLForm is our standard now?
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new HTMLForm( array(
'Clear' => array(
'type' => 'hidden',
@@ -362,7 +376,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// show the files + form, if there are any, or just say there are none
$refreshHtml = Html::element( 'a',
- array( 'href' => $this->getTitle()->getLocalURL() ),
+ array( 'href' => $this->getPageTitle()->getLocalURL() ),
$this->msg( 'uploadstash-refresh' )->text() );
$files = $this->stash->listFiles();
if ( $files && count( $files ) ) {
@@ -372,7 +386,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// TODO: Use Linker::link or even construct the list in plain wikitext
$fileListItemsHtml .= Html::rawElement( 'li', array(),
Html::element( 'a', array( 'href' =>
- $this->getTitle( "file/$file" )->getLocalURL() ), $file )
+ $this->getPageTitle( "file/$file" )->getLocalURL() ), $file )
);
}
$this->getOutput()->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) );
@@ -390,4 +404,5 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
}
-class SpecialUploadStashTooLargeException extends MWException {};
+class SpecialUploadStashTooLargeException extends MWException {
+}
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 5ac3e654..6de7c90d 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class LoginForm extends SpecialPage {
-
const SUCCESS = 0;
const NO_NAME = 1;
const ILLEGAL = 2;
@@ -42,26 +41,64 @@ class LoginForm extends SpecialPage {
const USER_BLOCKED = 11;
const NEED_TOKEN = 12;
const WRONG_TOKEN = 13;
+ const USER_MIGRATED = 14;
- var $mUsername, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
- var $mAction, $mCreateaccount, $mCreateaccountMail;
- var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
- var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS;
- var $mType, $mReason, $mRealName;
- var $mAbortLoginErrorMsg = null;
+ /**
+ * Valid error and warning messages
+ *
+ * Special:Userlogin can show an error or warning message on the form when
+ * coming from another page. This is done via the ?error= or ?warning= GET
+ * parameters.
+ *
+ * This array is the list of valid message keys. All other values will be
+ * ignored.
+ *
+ * @since 1.24
+ * @var string[]
+ */
+ public static $validErrorMessages = array(
+ 'exception-nologin-text',
+ 'watchlistanontext',
+ 'changeemail-no-info',
+ 'resetpass-no-info',
+ 'confirmemail_needlogin',
+ 'prefsnologintext2',
+ );
+
+ public $mAbortLoginErrorMsg = null;
+
+ protected $mUsername;
+ protected $mPassword;
+ protected $mRetype;
+ protected $mReturnTo;
+ protected $mCookieCheck;
+ protected $mPosted;
+ protected $mAction;
+ protected $mCreateaccount;
+ protected $mCreateaccountMail;
+ protected $mLoginattempt;
+ protected $mRemember;
+ protected $mEmail;
+ protected $mDomain;
+ protected $mLanguage;
+ protected $mSkipCookieCheck;
+ protected $mReturnToQuery;
+ protected $mToken;
+ protected $mStickHTTPS;
+ protected $mType;
+ protected $mReason;
+ protected $mRealName;
+ protected $mEntryError = '';
+ protected $mEntryErrorType = 'error';
+
+ private $mTempPasswordUsed;
private $mLoaded = false;
private $mSecureLoginUrl;
- /**
- * @ var WebRequest
- */
+ /** @var WebRequest */
private $mOverrideRequest = null;
- /**
- * Effective request; set at the beginning of load
- *
- * @var WebRequest $mRequest
- */
+ /** @var WebRequest Effective request; set at the beginning of load */
private $mRequest = null;
/**
@@ -100,19 +137,53 @@ class LoginForm extends SpecialPage {
$this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
$this->mPosted = $request->wasPosted();
$this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
- && $wgEnableEmail;
+ && $wgEnableEmail;
$this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail;
$this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
$this->mAction = $request->getVal( 'action' );
$this->mRemember = $request->getCheck( 'wpRemember' );
$this->mFromHTTP = $request->getBool( 'fromhttp', false );
- $this->mStickHTTPS = ( !$this->mFromHTTP && $request->detectProtocol() === 'https' ) || $request->getBool( 'wpForceHttps', false );
+ $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
+ || $request->getBool( 'wpForceHttps', false );
$this->mLanguage = $request->getText( 'uselang' );
$this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
- $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' );
+ $this->mToken = $this->mType == 'signup'
+ ? $request->getVal( 'wpCreateaccountToken' )
+ : $request->getVal( 'wpLoginToken' );
$this->mReturnTo = $request->getVal( 'returnto', '' );
$this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
+ // Show an error or warning passed on from a previous page
+ $entryError = $this->msg( $request->getVal( 'error', '' ) );
+ $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
+ // bc: provide login link as a parameter for messages where the translation
+ // was not updated
+ $loginreqlink = Linker::linkKnown(
+ $this->getPageTitle(),
+ $this->msg( 'loginreqlink' )->escaped(),
+ array(),
+ array(
+ 'returnto' => $this->mReturnTo,
+ 'returntoquery' => $this->mReturnToQuery,
+ 'uselang' => $this->mLanguage,
+ 'fromhttp' => $this->mFromHTTP ? '1' : '0',
+ )
+ );
+
+ // Only show valid error or warning messages.
+ if ( $entryError->exists()
+ && in_array( $entryError->getKey(), self::$validErrorMessages )
+ ) {
+ $this->mEntryErrorType = 'error';
+ $this->mEntryError = $entryError->rawParams( $loginreqlink )->escaped();
+
+ } elseif ( $entryWarning->exists()
+ && in_array( $entryWarning->getKey(), self::$validErrorMessages )
+ ) {
+ $this->mEntryErrorType = 'warning';
+ $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->escaped();
+ }
+
if ( $wgEnableEmail ) {
$this->mEmail = $request->getText( 'wpEmail' );
} else {
@@ -133,9 +204,10 @@ class LoginForm extends SpecialPage {
# 2. Do not return to PasswordReset after a successful password change
# but goto Wiki start page (Main_Page) instead ( bug 33997 )
$returnToTitle = Title::newFromText( $this->mReturnTo );
- if ( is_object( $returnToTitle ) && (
- $returnToTitle->isSpecial( 'Userlogout' )
- || $returnToTitle->isSpecial( 'PasswordReset' ) ) ) {
+ if ( is_object( $returnToTitle )
+ && ( $returnToTitle->isSpecial( 'Userlogout' )
+ || $returnToTitle->isSpecial( 'PasswordReset' ) )
+ ) {
$this->mReturnTo = '';
$this->mReturnToQuery = '';
}
@@ -149,8 +221,8 @@ class LoginForm extends SpecialPage {
}
}
- /*
- * @param $subPage string|null
+ /**
+ * @param string|null $subPage
*/
public function execute( $subPage ) {
if ( session_id() == '' ) {
@@ -166,21 +238,43 @@ class LoginForm extends SpecialPage {
}
$this->setHeaders();
+ // In the case where the user is already logged in, and was redirected to the login form from a
+ // page that requires login, do not show the login page. The use case scenario for this is when
+ // a user opens a large number of tabs, is redirected to the login page on all of them, and then
+ // logs in on one, expecting all the others to work properly.
+ //
+ // However, do show the form if it was visited intentionally (no 'returnto' is present). People
+ // who often switch between several accounts have grown accustomed to this behavior.
+ if (
+ $this->mType !== 'signup' &&
+ !$this->mPosted &&
+ $this->getUser()->isLoggedIn() &&
+ ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' )
+ ) {
+ $this->successfulLogin();
+ }
+
// If logging in and not on HTTPS, either redirect to it or offer a link.
global $wgSecureLogin;
- if ( WebRequest::detectProtocol() !== 'https' ) {
+ if ( $this->mRequest->getProtocol() !== 'https' ) {
$title = $this->getFullTitle();
$query = array(
- 'returnto' => $this->mReturnTo,
- 'returntoquery' => $this->mReturnToQuery,
+ 'returnto' => $this->mReturnTo !== '' ? $this->mReturnTo : null,
+ 'returntoquery' => $this->mReturnToQuery !== '' ?
+ $this->mReturnToQuery : null,
'title' => null,
+ ( $this->mEntryErrorType === 'error' ? 'error' : 'warning' ) => $this->mEntryError,
) + $this->mRequest->getQueryValues();
$url = $title->getFullURL( $query, false, PROTO_HTTPS );
- if ( $wgSecureLogin && wfCanIPUseHTTPS( $this->getRequest()->getIP() ) ) {
+ if ( $wgSecureLogin
+ && wfCanIPUseHTTPS( $this->getRequest()->getIP() )
+ && !$this->mFromHTTP ) // Avoid infinite redirect
+ {
$url = wfAppendQuery( $url, 'fromhttp=1' );
$this->getOutput()->redirect( $url );
// Since we only do this redir to change proto, always vary
$this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
+
return;
} else {
// A wiki without HTTPS login support should set $wgServer to
@@ -194,20 +288,24 @@ class LoginForm extends SpecialPage {
if ( !is_null( $this->mCookieCheck ) ) {
$this->onCookieRedirectCheck( $this->mCookieCheck );
+
return;
} elseif ( $this->mPosted ) {
if ( $this->mCreateaccount ) {
$this->addNewAccount();
+
return;
} elseif ( $this->mCreateaccountMail ) {
$this->addNewAccountMailPassword();
+
return;
} elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
$this->processLogin();
+
return;
}
}
- $this->mainLoginForm( '' );
+ $this->mainLoginForm( $this->mEntryError, $this->mEntryErrorType );
}
/**
@@ -216,13 +314,15 @@ class LoginForm extends SpecialPage {
function addNewAccountMailPassword() {
if ( $this->mEmail == '' ) {
$this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() );
+
return;
}
- $status = $this->addNewaccountInternal();
+ $status = $this->addNewAccountInternal();
if ( !$status->isGood() ) {
$error = $status->getMessage();
$this->mainLoginForm( $error->toString() );
+
return;
}
@@ -259,6 +359,7 @@ class LoginForm extends SpecialPage {
if ( !$status->isGood() ) {
$error = $status->getMessage();
$this->mainLoginForm( $error->toString() );
+
return false;
}
@@ -318,10 +419,11 @@ class LoginForm extends SpecialPage {
# Confirm that the account was created
$out->setPageTitle( $this->msg( 'accountcreated' ) );
$out->addWikiMsg( 'accountcreatedtext', $u->getName() );
- $out->addReturnTo( $this->getTitle() );
+ $out->addReturnTo( $this->getPageTitle() );
wfRunHooks( 'AddNewAccount', array( $u, false ) );
$u->addNewUserLogEntry( 'create2', $this->mReason );
}
+
return true;
}
@@ -364,6 +466,7 @@ class LoginForm extends SpecialPage {
# Request forgery checks.
if ( !self::getCreateaccountToken() ) {
self::setCreateaccountToken();
+
return Status::newFatal( 'nocookiesfornew' );
}
@@ -385,14 +488,20 @@ class LoginForm extends SpecialPage {
} elseif ( $creationBlock instanceof Block ) {
// Throws an ErrorPageError.
$this->userBlockedMessage( $creationBlock );
+
// This should never be reached.
return false;
}
# Include checks that will include GlobalBlocking (Bug 38333)
- $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $currentUser, true );
+ $permErrors = $this->getPageTitle()->getUserPermissionsErrors(
+ 'createaccount',
+ $currentUser,
+ true
+ );
+
if ( count( $permErrors ) ) {
- throw new PermissionsError( 'createaccount', $permErrors );
+ throw new PermissionsError( 'createaccount', $permErrors );
}
$ip = $this->getRequest()->getIP();
@@ -400,9 +509,20 @@ class LoginForm extends SpecialPage {
return Status::newFatal( 'sorbs_create_account_reason' );
}
+ // Normalize the name so that silly things don't cause "invalid username"
+ // errors. User::newFromName does some rather strict checking, rejecting
+ // e.g. leading/trailing/multiple spaces. But first we need to reject
+ // usernames that would be treated as titles with a fragment part.
+ if ( strpos( $this->mUsername, '#' ) !== false ) {
+ return Status::newFatal( 'noname' );
+ }
+ $title = Title::makeTitleSafe( NS_USER, $this->mUsername );
+ if ( !is_object( $title ) ) {
+ return Status::newFatal( 'noname' );
+ }
+
# Now create a dummy user ($u) and check if it is valid
- $name = trim( $this->mUsername );
- $u = User::newFromName( $name, 'creatable' );
+ $u = User::newFromName( $title->getText(), 'creatable' );
if ( !is_object( $u ) ) {
return Status::newFatal( 'noname' );
} elseif ( 0 != $u->idForName() ) {
@@ -424,6 +544,7 @@ class LoginForm extends SpecialPage {
if ( !is_array( $valid ) ) {
$valid = array( $valid, $wgMinimalPasswordLength );
}
+
return call_user_func_array( 'Status::newFatal', $valid );
}
}
@@ -444,17 +565,30 @@ class LoginForm extends SpecialPage {
$u->setRealName( $this->mRealName );
$abortError = '';
- if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) {
+ $abortStatus = null;
+ if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError, &$abortStatus ) ) ) {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
- $abortError = new RawMessage( $abortError );
- $abortError->text();
- return Status::newFatal( $abortError );
+ if ( $abortStatus === null ) {
+ // Report back the old string as a raw message status.
+ // This will report the error back as 'createaccount-hook-aborted'
+ // with the given string as the message.
+ // To return a different error code, return a Status object.
+ $abortError = new Message( 'createaccount-hook-aborted', array( $abortError ) );
+ $abortError->text();
+
+ return Status::newFatal( $abortError );
+ } else {
+ // For MediaWiki 1.23+ and updated hooks, return the Status object
+ // returned from the hook.
+ return $abortStatus;
+ }
}
// Hook point to check for exempt from account creation throttle
if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) {
- wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook allowed account creation w/o throttle\n" );
+ wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook " .
+ "allowed account creation w/o throttle\n" );
} else {
if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) {
$key = wfMemcKey( 'acctcreate', 'ip', $ip );
@@ -474,6 +608,7 @@ class LoginForm extends SpecialPage {
}
self::clearCreateaccountToken();
+
return $this->initUser( $u, false );
}
@@ -481,9 +616,9 @@ class LoginForm extends SpecialPage {
* Actually add a user to the database.
* Give it a User object that has been initialised with a name.
*
- * @param $u User object.
- * @param $autocreate boolean -- true if this is an autocreation via auth plugin
- * @return Status object, with the User object in the value member on success
+ * @param User $u
+ * @param bool $autocreate True if this is an autocreation via auth plugin
+ * @return Status Status object, with the User object in the value member on success
* @private
*/
function initUser( $u, $autocreate ) {
@@ -504,12 +639,14 @@ class LoginForm extends SpecialPage {
$wgAuth->initUser( $u, $autocreate );
- $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
$u->saveSettings();
- # Update user count
+ // Update user count
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
+ // Watch user's userpage and talk page
+ $u->addWatch( $u->getUserPage(), WatchedItem::IGNORE_USER_RIGHTS );
+
return Status::newGood( $u );
}
@@ -538,6 +675,7 @@ class LoginForm extends SpecialPage {
// If the user doesn't have a login token yet, set one.
if ( !self::getLoginToken() ) {
self::setLoginToken();
+
return self::NEED_TOKEN;
}
// If the user didn't pass a login token, tell them we need one
@@ -563,10 +701,19 @@ class LoginForm extends SpecialPage {
// will effectively be using stale data.
if ( $this->getUser()->getName() === $this->mUsername ) {
wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
+
return self::SUCCESS;
}
$u = User::newFromName( $this->mUsername );
+
+ // Give extensions a way to indicate the username has been updated,
+ // rather than telling the user the account doesn't exist.
+ if ( !wfRunHooks( 'LoginUserMigrated', array( $u, &$msg ) ) ) {
+ $this->mAbortLoginErrorMsg = $msg;
+ return self::USER_MIGRATED;
+ }
+
if ( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
return self::ILLEGAL;
}
@@ -588,6 +735,7 @@ class LoginForm extends SpecialPage {
$msg = null;
if ( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
$this->mAbortLoginErrorMsg = $msg;
+
return $abort;
}
@@ -618,6 +766,8 @@ class LoginForm extends SpecialPage {
// At this point we just return an appropriate code/ indicating
// that the UI should show a password reset form; bot inter-
// faces etc will probably just fail cleanly here.
+ $this->mAbortLoginErrorMsg = 'resetpass-temp-emailed';
+ $this->mTempPasswordUsed = true;
$retval = self::RESET_PASS;
} else {
$retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
@@ -625,6 +775,10 @@ class LoginForm extends SpecialPage {
} elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
// If we've enabled it, make it so that a blocked user cannot login
$retval = self::USER_BLOCKED;
+ } elseif ( $u->getPasswordExpired() == 'hard' ) {
+ // Force reset now, without logging in
+ $retval = self::RESET_PASS;
+ $this->mAbortLoginErrorMsg = 'resetpass-expired';
} else {
$wgAuth->updateUser( $u );
$wgUser = $u;
@@ -646,6 +800,7 @@ class LoginForm extends SpecialPage {
$retval = self::SUCCESS;
}
wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) );
+
return $retval;
}
@@ -653,7 +808,7 @@ class LoginForm extends SpecialPage {
* Increment the login attempt throttle hit count for the (username,current IP)
* tuple unless the throttle was already reached.
* @param string $username The user name
- * @return Bool|Integer The integer hit count or True if it is already at the limit
+ * @return bool|int The integer hit count or True if it is already at the limit
*/
public static function incLoginThrottle( $username ) {
global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest;
@@ -695,26 +850,32 @@ class LoginForm extends SpecialPage {
* Attempt to automatically create a user on login. Only succeeds if there
* is an external authentication method which allows it.
*
- * @param $user User
+ * @param User $user
*
- * @return integer Status code
+ * @return int Status code
*/
function attemptAutoCreate( $user ) {
global $wgAuth;
if ( $this->getUser()->isBlockedFromCreateAccount() ) {
wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
+
return self::CREATE_BLOCKED;
}
+
if ( !$wgAuth->autoCreate() ) {
return self::NOT_EXISTS;
}
+
if ( !$wgAuth->userExists( $user->getName() ) ) {
wfDebug( __METHOD__ . ": user does not exist\n" );
+
return self::NOT_EXISTS;
}
+
if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
+
return self::WRONG_PLUGIN_PASS;
}
@@ -723,6 +884,7 @@ class LoginForm extends SpecialPage {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
$this->mAbortLoginErrorMsg = $abortError;
+
return self::ABORTED;
}
@@ -732,6 +894,7 @@ class LoginForm extends SpecialPage {
if ( !$status->isOK() ) {
$errors = $status->getErrorsByType( 'error' );
$this->mAbortLoginErrorMsg = $errors[0]['message'];
+
return self::ABORTED;
}
@@ -739,27 +902,23 @@ class LoginForm extends SpecialPage {
}
function processLogin() {
- global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle;
+ global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle,
+ $wgInvalidPasswordReset;
switch ( $this->authenticateUserData() ) {
case self::SUCCESS:
# We've verified now, update the real record
$user = $this->getUser();
- if ( (bool)$this->mRemember != $user->getBoolOption( 'rememberpassword' ) ) {
- $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
- $user->saveSettings();
- } else {
- $user->invalidateCache();
- }
+ $user->invalidateCache();
if ( $user->requiresHTTPS() ) {
$this->mStickHTTPS = true;
}
if ( $wgSecureLogin && !$this->mStickHTTPS ) {
- $user->setCookies( null, false );
+ $user->setCookies( $this->mRequest, false, $this->mRemember );
} else {
- $user->setCookies();
+ $user->setCookies( $this->mRequest, null, $this->mRemember );
}
self::clearLoginToken();
@@ -778,7 +937,18 @@ class LoginForm extends SpecialPage {
$this->getContext()->setLanguage( $userLang );
// Reset SessionID on Successful login (bug 40995)
$this->renewSessionId();
- $this->successfulLogin();
+ if ( $this->getUser()->getPasswordExpired() == 'soft' ) {
+ $this->resetLoginForm( $this->msg( 'resetpass-expired-soft' ) );
+ } elseif ( $wgInvalidPasswordReset
+ && !$user->isValidPassword( $this->mPassword )
+ ) {
+ $status = $user->checkPasswordValidity( $this->mPassword );
+ $this->resetLoginForm(
+ $status->getMessage( 'resetpass-validity-soft' )
+ );
+ } else {
+ $this->successfulLogin();
+ }
} else {
$this->cookieRedirectCheck( 'login' );
}
@@ -822,7 +992,7 @@ class LoginForm extends SpecialPage {
break;
case self::RESET_PASS:
$error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce';
- $this->resetLoginForm( $this->msg( $error )->text() );
+ $this->resetLoginForm( $this->msg( $error ) );
break;
case self::CREATE_BLOCKED:
$this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() );
@@ -830,8 +1000,8 @@ class LoginForm extends SpecialPage {
case self::THROTTLED:
$error = $this->mAbortLoginErrorMsg ?: 'login-throttled';
$this->mainLoginForm( $this->msg( $error )
- ->params ( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
- ->text()
+ ->params( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
+ ->text()
);
break;
case self::USER_BLOCKED:
@@ -840,7 +1010,17 @@ class LoginForm extends SpecialPage {
break;
case self::ABORTED:
$error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic';
- $this->mainLoginForm( $this->msg( $error )->text() );
+ $this->mainLoginForm( $this->msg( $error,
+ wfEscapeWikiText( $this->mUsername ) )->text() );
+ break;
+ case self::USER_MIGRATED:
+ $error = $this->mAbortLoginErrorMsg ?: 'login-migrated-generic';
+ $params = array();
+ if ( is_array( $error ) ) {
+ $error = array_shift( $this->mAbortLoginErrorMsg );
+ $params = $this->mAbortLoginErrorMsg;
+ }
+ $this->mainLoginForm( $this->msg( $error, $params )->text() );
break;
default:
throw new MWException( 'Unhandled case value' );
@@ -848,24 +1028,34 @@ class LoginForm extends SpecialPage {
}
/**
- * @param $error string
+ * Show the Special:ChangePassword form, with custom message
+ * @param Message $msg
*/
- function resetLoginForm( $error ) {
- $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $error ) );
+ protected function resetLoginForm( Message $msg ) {
+ // Allow hooks to explain this password reset in more detail
+ wfRunHooks( 'LoginPasswordResetMessage', array( &$msg, $this->mUsername ) );
$reset = new SpecialChangePassword();
- $reset->setContext( $this->getContext() );
+ $derivative = new DerivativeContext( $this->getContext() );
+ $derivative->setTitle( $reset->getPageTitle() );
+ $reset->setContext( $derivative );
+ if ( !$this->mTempPasswordUsed ) {
+ $reset->setOldPasswordMessage( 'oldpassword' );
+ }
+ $reset->setChangeMessage( $msg );
$reset->execute( null );
}
/**
- * @param $u User object
- * @param $throttle Boolean
- * @param string $emailTitle message name of email title
- * @param string $emailText message name of email text
- * @return Status object
+ * @param User $u
+ * @param bool $throttle
+ * @param string $emailTitle Message name of email title
+ * @param string $emailText Message name of email text
+ * @return Status
*/
- function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) {
- global $wgCanonicalServer, $wgScript, $wgNewPasswordExpiry;
+ function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle',
+ $emailText = 'passwordremindertext'
+ ) {
+ global $wgNewPasswordExpiry;
if ( $u->getEmail() == '' ) {
return Status::newFatal( 'noemail', $u->getName() );
@@ -882,7 +1072,11 @@ class LoginForm extends SpecialPage {
$u->setNewpassword( $np, $throttle );
$u->saveSettings();
$userLanguage = $u->getOption( 'language' );
- $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $wgCanonicalServer . $wgScript . '>',
+
+ $mainPage = Title::newMainPage();
+ $mainPageUrl = $mainPage->getCanonicalURL();
+
+ $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $mainPageUrl . '>',
round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
$result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
@@ -906,7 +1100,7 @@ class LoginForm extends SpecialPage {
wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
if ( $injected_html !== '' ) {
- $this->displaySuccessfulAction( $this->msg( 'loginsuccesstitle' ),
+ $this->displaySuccessfulAction( 'success', $this->msg( 'loginsuccesstitle' ),
'loginsuccess', $injected_html );
} else {
$this->executeReturnTo( 'successredirect' );
@@ -934,18 +1128,22 @@ class LoginForm extends SpecialPage {
*/
wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
- $this->displaySuccessfulAction( $this->msg( 'welcomeuser', $this->getUser()->getName() ),
- $welcome_creation_msg, $injected_html );
+ $this->displaySuccessfulAction(
+ 'signup',
+ $this->msg( 'welcomeuser', $this->getUser()->getName() ),
+ $welcome_creation_msg, $injected_html
+ );
}
/**
- * Display an "successful action" page.
+ * Display a "successful action" page.
*
- * @param string|Message $title page's title
- * @param $msgname string
- * @param $injected_html string
+ * @param string $type Condition of return to; see `executeReturnTo`
+ * @param string|Message $title Page's title
+ * @param string $msgname
+ * @param string $injected_html
*/
- private function displaySuccessfulAction( $title, $msgname, $injected_html ) {
+ private function displaySuccessfulAction( $type, $title, $msgname, $injected_html ) {
$out = $this->getOutput();
$out->setPageTitle( $title );
if ( $msgname ) {
@@ -954,7 +1152,7 @@ class LoginForm extends SpecialPage {
$out->addHTML( $injected_html );
- $this->executeReturnTo( 'success' );
+ $this->executeReturnTo( $type );
}
/**
@@ -962,7 +1160,7 @@ class LoginForm extends SpecialPage {
* there is a block on them or their IP which prevents account creation. Note that
* User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock'
* setting on blocks (bug 13611).
- * @param $block Block the block causing this error
+ * @param Block $block The block causing this error
* @throws ErrorPageError
*/
function userBlockedMessage( Block $block ) {
@@ -973,14 +1171,23 @@ class LoginForm extends SpecialPage {
# haven't bothered to log out before trying to create an account to
# evade it, but we'll leave that to their guilty conscience to figure
# out.
+ $errorParams = array(
+ $block->getTarget(),
+ $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(),
+ $block->getByName()
+ );
+
+ if ( $block->getType() === Block::TYPE_RANGE ) {
+ $errorMessage = 'cantcreateaccount-range-text';
+ $errorParams[] = $this->getRequest()->getIP();
+ } else {
+ $errorMessage = 'cantcreateaccount-text';
+ }
+
throw new ErrorPageError(
'cantcreateaccounttitle',
- 'cantcreateaccount-text',
- array(
- $block->getTarget(),
- $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(),
- $block->getByName()
- )
+ $errorMessage,
+ $errorParams
);
}
@@ -989,8 +1196,9 @@ class LoginForm extends SpecialPage {
* Extensions can use this to reuse the "return to" logic after
* inject steps (such as redirection) into the login process.
*
- * @param $type string, one of the following:
+ * @param string $type One of the following:
* - error: display a return to link ignoring $wgRedirectOnLogin
+ * - signup: display a return to link using $wgRedirectOnLogin if needed
* - success: display a return to link using $wgRedirectOnLogin if needed
* - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
* @param string $returnTo
@@ -1010,8 +1218,9 @@ class LoginForm extends SpecialPage {
/**
* Add a "return to" link or redirect to it.
*
- * @param $type string, one of the following:
+ * @param string $type One of the following:
* - error: display a return to link ignoring $wgRedirectOnLogin
+ * - signup: display a return to link using $wgRedirectOnLogin if needed
* - success: display a return to link using $wgRedirectOnLogin if needed
* - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
*/
@@ -1026,6 +1235,9 @@ class LoginForm extends SpecialPage {
$returnToQuery = wfCgiToArray( $this->mReturnToQuery );
}
+ // Allow modification of redirect behavior
+ wfRunHooks( 'PostLoginRedirect', array( &$returnTo, &$returnToQuery, &$type ) );
+
$returnToTitle = Title::newFromText( $returnTo );
if ( !$returnToTitle ) {
$returnToTitle = Title::newMainPage();
@@ -1051,6 +1263,8 @@ class LoginForm extends SpecialPage {
}
/**
+ * @param string $msg
+ * @param string $msgtype
* @private
*/
function mainLoginForm( $msg, $msgtype = 'error' ) {
@@ -1059,7 +1273,7 @@ class LoginForm extends SpecialPage {
global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
global $wgSecureLogin, $wgPasswordResetRoutes;
- $titleObj = $this->getTitle();
+ $titleObj = $this->getPageTitle();
$user = $this->getUser();
$out = $this->getOutput();
@@ -1072,6 +1286,7 @@ class LoginForm extends SpecialPage {
throw new PermissionsError( 'createaccount', $permErrors );
} elseif ( $user->isBlockedFromCreateAccount() ) {
$this->userBlockedMessage( $user->isBlockedFromCreateAccount() );
+
return;
} elseif ( wfReadOnly() ) {
throw new ReadOnlyError;
@@ -1087,33 +1302,47 @@ class LoginForm extends SpecialPage {
}
}
- if ( $this->mType == 'signup' ) {
- $template = new UsercreateTemplate();
+ // Generic styles and scripts for both login and signup form
+ $out->addModuleStyles( array(
+ 'mediawiki.ui',
+ 'mediawiki.ui.button',
+ 'mediawiki.ui.checkbox',
+ 'mediawiki.ui.input',
+ 'mediawiki.special.userlogin.common.styles'
+ ) );
+ $out->addModules( array(
+ 'mediawiki.special.userlogin.common.js'
+ ) );
- $out->addModuleStyles( array(
- 'mediawiki.ui',
- 'mediawiki.special.createaccount'
- ) );
+ if ( $this->mType == 'signup' ) {
// XXX hack pending RL or JS parse() support for complex content messages
// https://bugzilla.wikimedia.org/show_bug.cgi?id=25349
$out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
$this->msg( 'createacct-imgcaptcha-help' )->parse() );
+
+ // Additional styles and scripts for signup form
$out->addModules( array(
- 'mediawiki.special.createaccount.js'
+ 'mediawiki.special.userlogin.signup.js'
) );
+ $out->addModuleStyles( array(
+ 'mediawiki.special.userlogin.signup.styles'
+ ) );
+
+ $template = new UsercreateTemplate();
+
// Must match number of benefits defined in messages
$template->set( 'benefitCount', 3 );
$q = 'action=submitlogin&type=signup';
$linkq = 'type=login';
} else {
- $template = new UserloginTemplate();
-
+ // Additional styles for login form
$out->addModuleStyles( array(
- 'mediawiki.ui',
- 'mediawiki.special.userlogin'
+ 'mediawiki.special.userlogin.login.styles'
) );
+ $template = new UserloginTemplate();
+
$q = 'action=submitlogin&type=login';
$linkq = 'type=signup';
}
@@ -1167,7 +1396,7 @@ class LoginForm extends SpecialPage {
$template->set( 'resetlink', $resetLink );
$template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
$template->set( 'usereason', $user->isLoggedIn() );
- $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember );
+ $template->set( 'remember', $this->mRemember );
$template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
$template->set( 'stickhttps', (int)$this->mStickHTTPS );
$template->set( 'loggedin', $user->isLoggedIn() );
@@ -1196,7 +1425,7 @@ class LoginForm extends SpecialPage {
$template->set( 'secureLoginUrl', $this->mSecureLoginUrl );
// Use loginend-https for HTTPS requests if it's not blank, loginend otherwise
// Ditto for signupend. New forms use neither.
- $usingHTTPS = WebRequest::detectProtocol() == 'https';
+ $usingHTTPS = $this->mRequest->getProtocol() == 'https';
$loginendHTTPS = $this->msg( 'loginend-https' );
$signupendHTTPS = $this->msg( 'signupend-https' );
if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) {
@@ -1226,7 +1455,7 @@ class LoginForm extends SpecialPage {
* Whether the login/create account form should display a link to the
* other form (in addition to whatever the skin provides).
*
- * @param $user User
+ * @param User $user
* @return bool
*/
private function showCreateOrLoginLink( &$user ) {
@@ -1251,15 +1480,17 @@ class LoginForm extends SpecialPage {
*/
function hasSessionCookie() {
global $wgDisableCookieCheck;
+
return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie();
}
/**
* Get the login token from the current session
- * @return Mixed
+ * @return mixed
*/
public static function getLoginToken() {
global $wgRequest;
+
return $wgRequest->getSessionData( 'wsLoginToken' );
}
@@ -1268,7 +1499,7 @@ class LoginForm extends SpecialPage {
*/
public static function setLoginToken() {
global $wgRequest;
- // Generate a token directly instead of using $user->editToken()
+ // Generate a token directly instead of using $user->getEditToken()
// because the latter reuses $_SESSION['wsEditToken']
$wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) );
}
@@ -1283,10 +1514,11 @@ class LoginForm extends SpecialPage {
/**
* Get the createaccount token from the current session
- * @return Mixed
+ * @return mixed
*/
public static function getCreateaccountToken() {
global $wgRequest;
+
return $wgRequest->getSessionData( 'wsCreateaccountToken' );
}
@@ -1319,6 +1551,7 @@ class LoginForm extends SpecialPage {
}
/**
+ * @param string $type
* @private
*/
function cookieRedirectCheck( $type ) {
@@ -1334,6 +1567,7 @@ class LoginForm extends SpecialPage {
}
/**
+ * @param string $type
* @private
*/
function onCookieRedirectCheck( $type ) {
@@ -1369,6 +1603,7 @@ class LoginForm extends SpecialPage {
$links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
}
}
+
return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
$this->getLanguage()->pipeList( $links ) )->escaped() : '';
} else {
@@ -1403,7 +1638,7 @@ class LoginForm extends SpecialPage {
$attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
return Linker::linkKnown(
- $this->getTitle(),
+ $this->getPageTitle(),
htmlspecialchars( $text ),
$attr,
$query
diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php
index d957e875..d65ac852 100644
--- a/includes/specials/SpecialUserlogout.php
+++ b/includes/specials/SpecialUserlogout.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class SpecialUserlogout extends UnlistedSpecialPage {
-
function __construct() {
parent::__construct( 'Userlogout' );
}
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index 4501736f..cefdad07 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -55,6 +55,7 @@ class UserrightsPage extends SpecialPage {
if ( $user->getId() == 0 ) {
return false;
}
+
return !empty( $available['add'] )
|| !empty( $available['remove'] )
|| ( ( $this->isself || !$checkIfSelf ) &&
@@ -66,7 +67,7 @@ class UserrightsPage extends SpecialPage {
* Manage forms to be shown according to posted data.
* Depending on the submit button used, call a form or a save function.
*
- * @param $par Mixed: string if any subpage provided, else null
+ * @param string|null $par String if any subpage provided, else null
* @throws UserBlockedError|PermissionsError
*/
public function execute( $par ) {
@@ -118,6 +119,7 @@ class UserrightsPage extends SpecialPage {
$out = $this->getOutput();
$out->wrapWikiMsg( "<div class=\"successbox\">\n$1\n</div>", 'userrights-removed-self' );
$out->returnToMain();
+
return;
}
@@ -148,12 +150,18 @@ class UserrightsPage extends SpecialPage {
$status = $this->fetchUser( $this->mTarget );
if ( !$status->isOK() ) {
$this->getOutput()->addWikiText( $status->getWikiText() );
+
return;
}
$targetUser = $status->value;
+ if ( $targetUser instanceof User ) { // UserRightsProxy doesn't have this method (bug 61252)
+ $targetUser->clearInstanceCache(); // bug 38989
+ }
- if ( $request->getVal( 'conflictcheck-originalgroups' ) !== implode( ',', $targetUser->getGroups() ) ) {
+ if ( $request->getVal( 'conflictcheck-originalgroups' )
+ !== implode( ',', $targetUser->getGroups() )
+ ) {
$out->addWikiMsg( 'userrights-conflict' );
} else {
$this->saveUserGroups(
@@ -163,6 +171,7 @@ class UserrightsPage extends SpecialPage {
);
$out->redirect( $this->getSuccessURL() );
+
return;
}
}
@@ -174,15 +183,15 @@ class UserrightsPage extends SpecialPage {
}
function getSuccessURL() {
- return $this->getTitle( $this->mTarget )->getFullURL( array( 'success' => 1 ) );
+ return $this->getPageTitle( $this->mTarget )->getFullURL( array( 'success' => 1 ) );
}
/**
* Save user groups changes in the database.
* Data comes from the editUserGroupsForm() form function
*
- * @param string $username username to apply changes to.
- * @param string $reason reason for group change
+ * @param string $username Username to apply changes to.
+ * @param string $reason Reason for group change
* @param User|UserRightsProxy $user Target user object.
* @return null
*/
@@ -209,11 +218,11 @@ class UserrightsPage extends SpecialPage {
/**
* Save user groups changes in the database.
*
- * @param $user User object
- * @param array $add of groups to add
- * @param array $remove of groups to remove
- * @param string $reason reason for group change
- * @return Array: Tuple of added, then removed groups
+ * @param User $user
+ * @param array $add Array of groups to add
+ * @param array $remove Array of groups to remove
+ * @param string $reason Reason for group change
+ * @return array Tuple of added, then removed groups
*/
function doSaveUserGroups( $user, $add, $remove, $reason = '' ) {
global $wgAuth;
@@ -256,18 +265,23 @@ class UserrightsPage extends SpecialPage {
// update groups in external authentication database
$wgAuth->updateExternalDBGroups( $user, $add, $remove );
- wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
- wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
+ wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) . "\n" );
+ wfDebug( 'newGroups: ' . print_r( $newGroups, true ) . "\n" );
wfRunHooks( 'UserRights', array( &$user, $add, $remove ) );
if ( $newGroups != $oldGroups ) {
$this->addLogEntry( $user, $oldGroups, $newGroups, $reason );
}
+
return array( $add, $remove );
}
/**
* Add a rights log entry for an action.
+ * @param User $user
+ * @param array $oldGroups
+ * @param array $newGroups
+ * @param array $reason
*/
function addLogEntry( $user, $oldGroups, $newGroups, $reason ) {
$logEntry = new ManualLogEntry( 'rights', 'rights' );
@@ -284,12 +298,13 @@ class UserrightsPage extends SpecialPage {
/**
* Edit user groups membership
- * @param string $username name of the user.
+ * @param string $username Name of the user.
*/
function editUserGroupsForm( $username ) {
$status = $this->fetchUser( $username );
if ( !$status->isOK() ) {
$this->getOutput()->addWikiText( $status->getWikiText() );
+
return;
} else {
$user = $status->value;
@@ -310,12 +325,10 @@ class UserrightsPage extends SpecialPage {
*
* Side effects: error output for invalid access
* @param string $username
- * @return Status object
+ * @return Status
*/
public function fetchUser( $username ) {
- global $wgUserrightsInterwikiDelimiter;
-
- $parts = explode( $wgUserrightsInterwikiDelimiter, $username );
+ $parts = explode( $this->getConfig()->get( 'UserrightsInterwikiDelimiter' ), $username );
if ( count( $parts ) < 2 ) {
$name = trim( $username );
$database = '';
@@ -384,8 +397,8 @@ class UserrightsPage extends SpecialPage {
/**
* Make a list of group names to be stored as parameter for log entries
*
- * @deprecated in 1.21; use LogFormatter instead.
- * @param $ids array
+ * @deprecated since 1.21; use LogFormatter instead.
+ * @param array $ids
* @return string
*/
function makeGroupNameListForLog( $ids ) {
@@ -402,12 +415,26 @@ class UserrightsPage extends SpecialPage {
* Output a form to allow searching for a user
*/
function switchForm() {
- global $wgScript;
$this->getOutput()->addHTML(
- Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Html::openElement(
+ 'form',
+ array(
+ 'method' => 'get',
+ 'action' => wfScript(),
+ 'name' => 'uluser',
+ 'id' => 'mw-userrights-form1'
+ )
+ ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
Xml::fieldset( $this->msg( 'userrights-lookup-user' )->text() ) .
- Xml::inputLabel( $this->msg( 'userrights-user-editname' )->text(), 'user', 'username', 30, str_replace( '_', ' ', $this->mTarget ), array( 'autofocus' => true ) ) . ' ' .
+ Xml::inputLabel(
+ $this->msg( 'userrights-user-editname' )->text(),
+ 'user',
+ 'username',
+ 30,
+ str_replace( '_', ' ', $this->mTarget ),
+ array( 'autofocus' => true )
+ ) . ' ' .
Xml::submitButton( $this->msg( 'editusergroup' )->text() ) .
Html::closeElement( 'fieldset' ) .
Html::closeElement( 'form' ) . "\n"
@@ -419,8 +446,8 @@ class UserrightsPage extends SpecialPage {
* form will be able to manipulate based on the current user's system
* permissions.
*
- * @param array $groups list of groups the given user is in
- * @return Array: Tuple of addable, then removable groups
+ * @param array $groups List of groups the given user is in
+ * @return array Tuple of addable, then removable groups
*/
protected function splitGroups( $groups ) {
list( $addable, $removable, $addself, $removeself ) = array_values( $this->changeableGroups() );
@@ -440,8 +467,8 @@ class UserrightsPage extends SpecialPage {
/**
* Show the form to edit group memberships.
*
- * @param $user User or UserRightsProxy you're editing
- * @param $groups Array: Array of groups the user is in
+ * @param User|UserRightsProxy $user User or UserRightsProxy you're editing
+ * @param array $groups Array of groups the user is in
*/
protected function showEditUserGroupsForm( $user, $groups ) {
$list = array();
@@ -476,30 +503,48 @@ class UserrightsPage extends SpecialPage {
$grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse();
$grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n";
}
+
$count = count( $autoList );
if ( $count > 0 ) {
- $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )->parse();
+ $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )
+ ->parse();
$grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n";
}
$userToolLinks = Linker::userToolLinks(
- $user->getId(),
- $user->getName(),
- false, /* default for redContribsWhenNoEdits */
- Linker::TOOL_LINKS_EMAIL /* Add "send e-mail" link */
+ $user->getId(),
+ $user->getName(),
+ false, /* default for redContribsWhenNoEdits */
+ Linker::TOOL_LINKS_EMAIL /* Add "send e-mail" link */
);
$this->getOutput()->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) .
+ Xml::openElement(
+ 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $this->getPageTitle()->getLocalURL(),
+ 'name' => 'editGroup',
+ 'id' => 'mw-userrights-form2'
+ )
+ ) .
Html::hidden( 'user', $this->mTarget ) .
Html::hidden( 'wpEditToken', $this->getUser()->getEditToken( $this->mTarget ) ) .
- Html::hidden( 'conflictcheck-originalgroups', implode( ',', $user->getGroups() ) ) . // Conflict detection
+ Html::hidden(
+ 'conflictcheck-originalgroups',
+ implode( ',', $user->getGroups() )
+ ) . // Conflict detection
Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', array(), $this->msg( 'userrights-editusergroup', $user->getName() )->text() ) .
- $this->msg( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) )->rawParams( $userToolLinks )->parse() .
+ Xml::element(
+ 'legend',
+ array(),
+ $this->msg( 'userrights-editusergroup', $user->getName() )->text()
+ ) .
+ $this->msg( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) )
+ ->rawParams( $userToolLinks )->parse() .
$this->msg( 'userrights-groups-help', $user->getName() )->parse() .
$grouplist .
- Xml::tags( 'p', null, $this->groupCheckboxes( $groups, $user ) ) .
+ $this->groupCheckboxes( $groups, $user ) .
Xml::openElement( 'table', array( 'id' => 'mw-userrights-table-outer' ) ) .
"<tr>
<td class='mw-label'>" .
@@ -514,7 +559,9 @@ class UserrightsPage extends SpecialPage {
<td></td>
<td class='mw-submit'>" .
Xml::submitButton( $this->msg( 'saveusergroups' )->text(),
- array( 'name' => 'saveusergroups' ) + Linker::tooltipAndAccesskeyAttribs( 'userrights-set' ) ) .
+ array( 'name' => 'saveusergroups' ) +
+ Linker::tooltipAndAccesskeyAttribs( 'userrights-set' )
+ ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ) . "\n" .
@@ -526,7 +573,7 @@ class UserrightsPage extends SpecialPage {
/**
* Format a link to a group description page
*
- * @param $group string
+ * @param string $group
* @return string
*/
private static function buildGroupLink( $group ) {
@@ -536,7 +583,7 @@ class UserrightsPage extends SpecialPage {
/**
* Format a link to a group member description page
*
- * @param $group string
+ * @param string $group
* @return string
*/
private static function buildGroupMemberLink( $group ) {
@@ -555,8 +602,8 @@ class UserrightsPage extends SpecialPage {
* Adds a table with checkboxes where you can select what groups to add/remove
*
* @todo Just pass the username string?
- * @param array $usergroups groups the user belongs to
- * @param $user User a user object
+ * @param array $usergroups Groups the user belongs to
+ * @param User $user
* @return string XHTML table element with checkboxes
*/
private function groupCheckboxes( $usergroups, $user ) {
@@ -599,8 +646,13 @@ class UserrightsPage extends SpecialPage {
continue;
}
// Messages: userrights-changeable-col, userrights-unchangeable-col
- $ret .= Xml::element( 'th', null, $this->msg( 'userrights-' . $name . '-col', count( $column ) )->text() );
+ $ret .= Xml::element(
+ 'th',
+ null,
+ $this->msg( 'userrights-' . $name . '-col', count( $column ) )->text()
+ );
}
+
$ret .= "</tr>\n<tr>\n";
foreach ( $columns as $column ) {
if ( $column === array() ) {
@@ -631,28 +683,41 @@ class UserrightsPage extends SpecialPage {
}
/**
- * @param $group String: the name of the group to check
+ * @param string $group The name of the group to check
* @return bool Can we remove the group?
*/
private function canRemove( $group ) {
// $this->changeableGroups()['remove'] doesn't work, of course. Thanks, PHP.
$groups = $this->changeableGroups();
- return in_array( $group, $groups['remove'] ) || ( $this->isself && in_array( $group, $groups['remove-self'] ) );
+
+ return in_array(
+ $group,
+ $groups['remove'] ) || ( $this->isself && in_array( $group, $groups['remove-self'] )
+ );
}
/**
- * @param string $group the name of the group to check
+ * @param string $group The name of the group to check
* @return bool Can we add the group?
*/
private function canAdd( $group ) {
$groups = $this->changeableGroups();
- return in_array( $group, $groups['add'] ) || ( $this->isself && in_array( $group, $groups['add-self'] ) );
+
+ return in_array(
+ $group,
+ $groups['add'] ) || ( $this->isself && in_array( $group, $groups['add-self'] )
+ );
}
/**
* Returns $this->getUser()->changeableGroups()
*
- * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ), 'add-self' => array( addablegroups to self ), 'remove-self' => array( removable groups from self ) )
+ * @return array Array(
+ * 'add' => array( addablegroups ),
+ * 'remove' => array( removablegroups ),
+ * 'add-self' => array( addablegroups to self ),
+ * 'remove-self' => array( removable groups from self )
+ * )
*/
function changeableGroups() {
return $this->getUser()->changeableGroups();
@@ -661,8 +726,8 @@ class UserrightsPage extends SpecialPage {
/**
* Show a rights log fragment for the specified user
*
- * @param $user User to show log for
- * @param $output OutputPage to use
+ * @param User $user User to show log for
+ * @param OutputPage $output OutputPage to use
*/
protected function showLogFragment( $user, $output ) {
$rightsLogPage = new LogPage( 'rights' );
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index 5ba785f5..cb3fc118 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -29,9 +29,13 @@
* @ingroup SpecialPage
*/
class SpecialVersion extends SpecialPage {
-
protected $firstExtOpened = false;
+ /**
+ * Stores the current rev id/SHA hash of MediaWiki core
+ */
+ protected $coreId = '';
+
protected static $extensionTypes = false;
protected static $viewvcUrls = array(
@@ -46,43 +50,95 @@ class SpecialVersion extends SpecialPage {
/**
* main()
+ * @param string|null $par
*/
public function execute( $par ) {
- global $wgSpecialVersionShowHooks, $IP;
+ global $IP, $wgExtensionCredits;
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$out->allowClickjacking();
- if ( $par !== 'Credits' ) {
- $text =
- $this->getMediaWikiCredits() .
- $this->softwareInformation() .
- $this->getEntryPointInfo() .
- $this->getExtensionCredits();
- if ( $wgSpecialVersionShowHooks ) {
- $text .= $this->getWgHooks();
+ // Explode the sub page information into useful bits
+ $parts = explode( '/', (string)$par );
+ $extNode = null;
+ if ( isset( $parts[1] ) ) {
+ $extName = str_replace( '_', ' ', $parts[1] );
+ // Find it!
+ foreach ( $wgExtensionCredits as $group => $extensions ) {
+ foreach ( $extensions as $ext ) {
+ if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
+ $extNode = &$ext;
+ break 2;
+ }
+ }
}
-
- $out->addWikiText( $text );
- $out->addHTML( $this->IPInfo() );
-
- if ( $this->getRequest()->getVal( 'easteregg' ) ) {
- // TODO: put something interesting here
+ if ( !$extNode ) {
+ $out->setStatusCode( 404 );
}
} else {
- // Credits sub page
-
- // Header
- $out->addHTML( wfMessage( 'version-credits-summary' )->parseAsBlock() );
+ $extName = 'MediaWiki';
+ }
- $wikiText = file_get_contents( $IP . '/CREDITS' );
+ // Now figure out what to do
+ switch ( strtolower( $parts[0] ) ) {
+ case 'credits':
+ $wikiText = '{{int:version-credits-not-found}}';
+ if ( $extName === 'MediaWiki' ) {
+ $wikiText = file_get_contents( $IP . '/CREDITS' );
+ } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
+ $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
+ if ( $file ) {
+ $wikiText = file_get_contents( $file );
+ if ( substr( $file, -4 ) === '.txt' ) {
+ $wikiText = Html::element( 'pre', array(), $wikiText );
+ }
+ }
+ }
- // Take everything from the first section onwards, to remove the (not localized) header
- $wikiText = substr( $wikiText, strpos( $wikiText, '==' ) );
+ $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
+ $out->addWikiText( $wikiText );
+ break;
+
+ case 'license':
+ $wikiText = '{{int:version-license-not-found}}';
+ if ( $extName === 'MediaWiki' ) {
+ $wikiText = file_get_contents( $IP . '/COPYING' );
+ } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
+ $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
+ if ( $file ) {
+ $wikiText = file_get_contents( $file );
+ if ( !isset( $extNode['license-name'] ) ) {
+ // If the developer did not explicitly set license-name they probably
+ // are unaware that we're now sucking this file in and thus it's probably
+ // not wikitext friendly.
+ $wikiText = "<pre>$wikiText</pre>";
+ }
+ }
+ }
- $out->addWikiText( $wikiText );
+ $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
+ $out->addWikiText( $wikiText );
+ break;
+
+ default:
+ $out->addModules( 'mediawiki.special.version' );
+ $out->addWikiText(
+ $this->getMediaWikiCredits() .
+ $this->softwareInformation() .
+ $this->getEntryPointInfo()
+ );
+ $out->addHtml(
+ $this->getSkinCredits() .
+ $this->getExtensionCredits() .
+ $this->getParserTags() .
+ $this->getParserFunctionHooks()
+ );
+ $out->addWikiText( $this->getWgHooks() );
+ $out->addHTML( $this->IPInfo() );
+
+ break;
}
}
@@ -92,7 +148,11 @@ class SpecialVersion extends SpecialPage {
* @return string
*/
private static function getMediaWikiCredits() {
- $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMessage( 'version-license' )->text() );
+ $ret = Xml::element(
+ 'h2',
+ array( 'id' => 'mw-version-license' ),
+ wfMessage( 'version-license' )->text()
+ );
// This text is always left-to-right.
$ret .= '<div class="plainlinks">';
@@ -107,18 +167,21 @@ class SpecialVersion extends SpecialPage {
/**
* Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text
*
- * @return String
+ * @return string
*/
public static function getCopyrightAndAuthorList() {
global $wgLang;
if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
- $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' . wfMessage( 'version-poweredby-others' )->text() . ']';
+ $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' .
+ wfMessage( 'version-poweredby-others' )->text() . ']';
} else {
- $othersLink = '[[Special:Version/Credits|' . wfMessage( 'version-poweredby-others' )->text() . ']]';
+ $othersLink = '[[Special:Version/Credits|' .
+ wfMessage( 'version-poweredby-others' )->text() . ']]';
}
- $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' . wfMessage( 'version-poweredby-translators' )->text() . ']';
+ $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
+ wfMessage( 'version-poweredby-translators' )->text() . ']';
$authorList = array(
'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
@@ -141,7 +204,7 @@ class SpecialVersion extends SpecialPage {
*
* @return string
*/
- static function softwareInformation() {
+ public static function softwareInformation() {
$dbr = wfGetDB( DB_SLAVE );
// Put the software in an array of form 'name' => 'version'. All messages should
@@ -149,13 +212,21 @@ class SpecialVersion extends SpecialPage {
// wikimarkup can be used.
$software = array();
$software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
- $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . PHP_SAPI . ")";
+ if ( wfIsHHVM() ) {
+ $software['[http://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")";
+ } else {
+ $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")";
+ }
$software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
// Allow a hook to add/remove items.
wfRunHooks( 'SoftwareInfo', array( &$software ) );
- $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMessage( 'version-software' )->text() ) .
+ $out = Xml::element(
+ 'h2',
+ array( 'id' => 'mw-version-software' ),
+ wfMessage( 'version-software' )->text()
+ ) .
Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) .
"<tr>
<th>" . wfMessage( 'version-software-product' )->text() . "</th>
@@ -175,7 +246,7 @@ class SpecialVersion extends SpecialPage {
/**
* Return a string of the MediaWiki version with SVN revision if available.
*
- * @param $flags String
+ * @param string $flags
* @return mixed
*/
public static function getVersion( $flags = '' ) {
@@ -205,6 +276,7 @@ class SpecialVersion extends SpecialPage {
}
wfProfileOut( __METHOD__ );
+
return $version;
}
@@ -233,11 +305,12 @@ class SpecialVersion extends SpecialPage {
}
wfProfileOut( __METHOD__ );
+
return $v;
}
/**
- * @return string wgVersion + a link to subversion revision of svn BASE
+ * @return string Global wgVersion + a link to subversion revision of svn BASE
*/
private static function getVersionLinkedSvn() {
global $IP;
@@ -273,12 +346,14 @@ class SpecialVersion extends SpecialPage {
preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
$versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
}
+
return "[$versionUrl $wgVersion]";
}
/**
* @since 1.22 Returns the HEAD date in addition to the sha1 and link
- * @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars with link and date, or false on failure
+ * @return bool|string Global wgVersion + HEAD sha1 stripped to the first 7 chars
+ * with link and date, or false on failure
*/
private static function getVersionLinkedGit() {
global $IP, $wgLang;
@@ -298,7 +373,7 @@ class SpecialVersion extends SpecialPage {
$gitHeadCommitDate = $gitInfo->getHeadCommitDate();
if ( $gitHeadCommitDate ) {
- $shortSHA1 .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true );
+ $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
}
return self::getwgVersionLinked() . " $shortSHA1";
@@ -308,9 +383,7 @@ class SpecialVersion extends SpecialPage {
* Returns an array with the base extension types.
* Type is stored as array key, the message as array value.
*
- * TODO: ideally this would return all extension types, including
- * those added by SpecialVersionExtensionTypes. This is not possible
- * since this hook is passing along $this though.
+ * TODO: ideally this would return all extension types.
*
* @since 1.17
*
@@ -340,35 +413,39 @@ class SpecialVersion extends SpecialPage {
*
* @since 1.17
*
- * @param $type String
+ * @param string $type
*
* @return string
*/
public static function getExtensionTypeName( $type ) {
$types = self::getExtensionTypes();
+
return isset( $types[$type] ) ? $types[$type] : $types['other'];
}
/**
- * Generate wikitext showing extensions name, URL, author and description.
+ * Generate wikitext showing the name, URL, author and description of each extension.
*
- * @return String: Wikitext
+ * @return string Wikitext
*/
- function getExtensionCredits() {
- global $wgExtensionCredits, $wgExtensionFunctions, $wgParser;
+ public function getExtensionCredits() {
+ global $wgExtensionCredits;
- if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) ) {
+ if (
+ count( $wgExtensionCredits ) === 0 ||
+ // Skins are displayed separately, see getSkinCredits()
+ ( count( $wgExtensionCredits ) === 1 && isset( $wgExtensionCredits['skin'] ) )
+ ) {
return '';
}
$extensionTypes = self::getExtensionTypes();
- /**
- * @deprecated as of 1.17, use hook ExtensionTypes instead.
- */
- wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
-
- $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), $this->msg( 'version-extensions' )->text() ) .
+ $out = Xml::element(
+ 'h2',
+ array( 'id' => 'mw-version-ext' ),
+ $this->msg( 'version-extensions' )->text()
+ ) .
Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ) );
// Make sure the 'other' type is set to an array.
@@ -383,9 +460,11 @@ class SpecialVersion extends SpecialPage {
}
}
+ $this->firstExtOpened = false;
// Loop through the extension categories to display their extensions in the list.
foreach ( $extensionTypes as $type => $message ) {
- if ( $type != 'other' ) {
+ // Skins have a separate section
+ if ( $type !== 'other' && $type !== 'skin' ) {
$out .= $this->getExtensionCategory( $type, $message );
}
}
@@ -393,24 +472,89 @@ class SpecialVersion extends SpecialPage {
// We want the 'other' type to be last in the list.
$out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
+ $out .= Xml::closeElement( 'table' );
+
+ return $out;
+ }
+
+ /**
+ * Generate wikitext showing the name, URL, author and description of each skin.
+ *
+ * @return string Wikitext
+ */
+ public function getSkinCredits() {
+ global $wgExtensionCredits;
+ if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) {
+ return '';
+ }
+
+ $out = Xml::element(
+ 'h2',
+ array( 'id' => 'mw-version-skin' ),
+ $this->msg( 'version-skins' )->text()
+ ) .
+ Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ) );
+
+ $this->firstExtOpened = false;
+ $out .= $this->getExtensionCategory( 'skin', null );
+
+ $out .= Xml::closeElement( 'table' );
+
+ return $out;
+ }
+
+ /**
+ * Obtains a list of installed parser tags and the associated H2 header
+ *
+ * @return string HTML output
+ */
+ protected function getParserTags() {
+ global $wgParser;
+
$tags = $wgParser->getTags();
- $cnt = count( $tags );
- if ( $cnt ) {
- for ( $i = 0; $i < $cnt; ++$i ) {
- $tags[$i] = "&lt;{$tags[$i]}&gt;";
- }
- $out .= $this->openExtType( $this->msg( 'version-parser-extensiontags' )->text(), 'parser-tags' );
- $out .= '<tr><td colspan="4">' . $this->listToText( $tags ) . "</td></tr>\n";
+ if ( count( $tags ) ) {
+ $out = Html::rawElement(
+ 'h2',
+ array( 'class' => 'mw-headline' ),
+ Linker::makeExternalLink(
+ '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
+ $this->msg( 'version-parser-extensiontags' )->parse(),
+ false /* msg()->parse() already escapes */
+ )
+ );
+
+ array_walk( $tags, function ( &$value ) {
+ $value = '&lt;' . htmlspecialchars( $value ) . '&gt;';
+ } );
+ $out .= $this->listToText( $tags );
+ } else {
+ $out = '';
}
+ return $out;
+ }
+
+ /**
+ * Obtains a list of installed parser function hooks and the associated H2 header
+ *
+ * @return string HTML output
+ */
+ protected function getParserFunctionHooks() {
+ global $wgParser;
+
$fhooks = $wgParser->getFunctionHooks();
if ( count( $fhooks ) ) {
- $out .= $this->openExtType( $this->msg( 'version-parser-function-hooks' )->text(), 'parser-function-hooks' );
- $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
- }
+ $out = Html::rawElement( 'h2', array( 'class' => 'mw-headline' ), Linker::makeExternalLink(
+ '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
+ $this->msg( 'version-parser-function-hooks' )->parse(),
+ false /* msg()->parse() already escapes */
+ ) );
- $out .= Xml::closeElement( 'table' );
+ $out .= $this->listToText( $fhooks );
+ } else {
+ $out = '';
+ }
return $out;
}
@@ -420,8 +564,8 @@ class SpecialVersion extends SpecialPage {
*
* @since 1.17
*
- * @param $type String
- * @param $message String
+ * @param string $type
+ * @param string $message
*
* @return string
*/
@@ -445,11 +589,11 @@ class SpecialVersion extends SpecialPage {
/**
* Callback to sort extensions by type.
- * @param $a array
- * @param $b array
+ * @param array $a
+ * @param array $b
* @return int
*/
- function compare( $a, $b ) {
+ public function compare( $a, $b ) {
if ( $a['name'] === $b['name'] ) {
return 0;
} else {
@@ -460,63 +604,160 @@ class SpecialVersion extends SpecialPage {
}
/**
- * Creates and formats the credits for a single extension and returns this.
+ * Creates and formats a version line for a single extension.
*
- * @param $extension Array
+ * Information for five columns will be created. Parameters required in the
+ * $extension array for part rendering are indicated in ()
+ * - The name of (name), and URL link to (url), the extension
+ * - Official version number (version) and if available version control system
+ * revision (path), link, and date
+ * - If available the short name of the license (license-name) and a linke
+ * to ((LICENSE)|(COPYING))(\.txt)? if it exists.
+ * - Description of extension (descriptionmsg or description)
+ * - List of authors (author) and link to a ((AUTHORS)|(CREDITS))(\.txt)? file if it exists
*
- * @return string
+ * @param array $extension
+ *
+ * @return string Raw HTML
*/
- function getCreditsForExtension( array $extension ) {
- global $wgLang;
+ public function getCreditsForExtension( array $extension ) {
+ $out = $this->getOutput();
- $name = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
+ // We must obtain the information for all the bits and pieces!
+ // ... such as extension names and links
+ if ( isset( $extension['namemsg'] ) ) {
+ // Localized name of extension
+ $extensionName = $this->msg( $extension['namemsg'] )->text();
+ } elseif ( isset( $extension['name'] ) ) {
+ // Non localized version
+ $extensionName = $extension['name'];
+ } else {
+ $extensionName = $this->msg( 'version-no-ext-name' )->text();
+ }
- $vcsText = false;
+ if ( isset( $extension['url'] ) ) {
+ $extensionNameLink = Linker::makeExternalLink(
+ $extension['url'],
+ $extensionName,
+ true,
+ '',
+ array( 'class' => 'mw-version-ext-name' )
+ );
+ } else {
+ $extensionNameLink = $extensionName;
+ }
+
+ // ... and the version information
+ // If the extension path is set we will check that directory for GIT and SVN
+ // metadata in an attempt to extract date and vcs commit metadata.
+ $canonicalVersion = '&ndash;';
+ $extensionPath = null;
+ $vcsVersion = null;
+ $vcsLink = null;
+ $vcsDate = null;
+
+ if ( isset( $extension['version'] ) ) {
+ $canonicalVersion = $out->parseInline( $extension['version'] );
+ }
if ( isset( $extension['path'] ) ) {
- $gitInfo = new GitInfo( dirname( $extension['path'] ) );
- $gitHeadSHA1 = $gitInfo->getHeadSHA1();
- if ( $gitHeadSHA1 !== false ) {
- $vcsText = '(' . substr( $gitHeadSHA1, 0, 7 ) . ')';
- $gitViewerUrl = $gitInfo->getHeadViewUrl();
- if ( $gitViewerUrl !== false ) {
- $vcsText = "[$gitViewerUrl $vcsText]";
+ global $IP;
+ $extensionPath = dirname( $extension['path'] );
+ if ( $this->coreId == '' ) {
+ wfDebug( 'Looking up core head id' );
+ $coreHeadSHA1 = self::getGitHeadSha1( $IP );
+ if ( $coreHeadSHA1 ) {
+ $this->coreId = $coreHeadSHA1;
+ } else {
+ $svnInfo = self::getSvnInfo( $IP );
+ if ( $svnInfo !== false ) {
+ $this->coreId = $svnInfo['checkout-rev'];
+ }
}
- $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
- if ( $gitHeadCommitDate ) {
- $vcsText .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true );
+ }
+ $cache = wfGetCache( CACHE_ANYTHING );
+ $memcKey = wfMemcKey( 'specialversion-ext-version-text', $extension['path'], $this->coreId );
+ list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
+
+ if ( !$vcsVersion ) {
+ wfDebug( "Getting VCS info for extension $extensionName" );
+ $gitInfo = new GitInfo( $extensionPath );
+ $vcsVersion = $gitInfo->getHeadSHA1();
+ if ( $vcsVersion !== false ) {
+ $vcsVersion = substr( $vcsVersion, 0, 7 );
+ $vcsLink = $gitInfo->getHeadViewUrl();
+ $vcsDate = $gitInfo->getHeadCommitDate();
+ } else {
+ $svnInfo = self::getSvnInfo( $extensionPath );
+ if ( $svnInfo !== false ) {
+ $vcsVersion = $this->msg( 'version-svn-revision', $svnInfo['checkout-rev'] )->text();
+ $vcsLink = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : '';
+ }
}
+ $cache->set( $memcKey, array( $vcsVersion, $vcsLink, $vcsDate ), 60 * 60 * 24 );
} else {
- $svnInfo = self::getSvnInfo( dirname( $extension['path'] ) );
- # Make subversion text/link.
- if ( $svnInfo !== false ) {
- $directoryRev = isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : null;
- $vcsText = $this->msg( 'version-svn-revision', $directoryRev, $svnInfo['checkout-rev'] )->text();
- $vcsText = isset( $svnInfo['viewvc-url'] ) ? '[' . $svnInfo['viewvc-url'] . " $vcsText]" : $vcsText;
- }
+ wfDebug( "Pulled VCS info for extension $extensionName from cache" );
}
}
- # Make main link (or just the name if there is no URL).
- if ( isset( $extension['url'] ) ) {
- $mainLink = "[{$extension['url']} $name]";
- } else {
- $mainLink = $name;
- }
+ $versionString = Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-version-ext-version' ),
+ $canonicalVersion
+ );
- if ( isset( $extension['version'] ) ) {
- $versionText = '<span class="mw-version-ext-version">' .
- $this->msg( 'version-version', $extension['version'] )->text() .
- '</span>';
- } else {
- $versionText = '';
+ if ( $vcsVersion ) {
+ if ( $vcsLink ) {
+ $vcsVerString = Linker::makeExternalLink(
+ $vcsLink,
+ $this->msg( 'version-version', $vcsVersion ),
+ true,
+ '',
+ array( 'class' => 'mw-version-ext-vcs-version' )
+ );
+ } else {
+ $vcsVerString = Html::element( 'span',
+ array( 'class' => 'mw-version-ext-vcs-version' ),
+ "({$vcsVersion})"
+ );
+ }
+ $versionString .= " {$vcsVerString}";
+
+ if ( $vcsDate ) {
+ $vcsTimeString = Html::element( 'span',
+ array( 'class' => 'mw-version-ext-vcs-timestamp' ),
+ $this->getLanguage()->timeanddate( $vcsDate, true )
+ );
+ $versionString .= " {$vcsTimeString}";
+ }
+ $versionString = Html::rawElement( 'span',
+ array( 'class' => 'mw-version-ext-meta-version' ),
+ $versionString
+ );
}
- # Make description text.
- $description = isset( $extension['description'] ) ? $extension['description'] : '';
+ // ... and license information; if a license file exists we
+ // will link to it
+ $licenseLink = '';
+ if ( isset( $extension['license-name'] ) ) {
+ $licenseLink = Linker::link(
+ $this->getPageTitle( 'License/' . $extensionName ),
+ $out->parseInline( $extension['license-name'] ),
+ array( 'class' => 'mw-version-ext-license' )
+ );
+ } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
+ $licenseLink = Linker::link(
+ $this->getPageTitle( 'License/' . $extensionName ),
+ $this->msg( 'version-ext-license' ),
+ array( 'class' => 'mw-version-ext-license' )
+ );
+ }
+ // ... and generate the description; which can be a parameterized l10n message
+ // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
+ // up string
if ( isset( $extension['descriptionmsg'] ) ) {
- # Look for a localized description.
+ // Localized description of extension
$descriptionMsg = $extension['descriptionmsg'];
if ( is_array( $descriptionMsg ) ) {
@@ -527,65 +768,80 @@ class SpecialVersion extends SpecialPage {
} else {
$description = $this->msg( $descriptionMsg )->text();
}
- }
-
- if ( $vcsText !== false ) {
- $extNameVer = "<tr>
- <td><em>$mainLink $versionText</em></td>
- <td><em>$vcsText</em></td>";
+ } elseif ( isset( $extension['description'] ) ) {
+ // Non localized version
+ $description = $extension['description'];
} else {
- $extNameVer = "<tr>
- <td colspan=\"2\"><em>$mainLink $versionText</em></td>";
+ $description = '';
}
+ $description = $out->parseInline( $description );
- $author = isset( $extension['author'] ) ? $extension['author'] : array();
- $extDescAuthor = "<td>$description</td>
- <td>" . $this->listAuthors( $author, false ) . "</td>
- </tr>\n";
+ // ... now get the authors for this extension
+ $authors = isset( $extension['author'] ) ? $extension['author'] : array();
+ $authors = $this->listAuthors( $authors, $extensionName, $extensionPath );
- return $extNameVer . $extDescAuthor;
+ // Finally! Create the table
+ $html = Html::openElement( 'tr', array(
+ 'class' => 'mw-version-ext',
+ 'id' => "mw-version-ext-{$extensionName}"
+ )
+ );
+
+ $html .= Html::rawElement( 'td', array(), $extensionNameLink );
+ $html .= Html::rawElement( 'td', array(), $versionString );
+ $html .= Html::rawElement( 'td', array(), $licenseLink );
+ $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-description' ), $description );
+ $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-authors' ), $authors );
+
+ $html .= Html::closeElement( 'td' );
+
+ return $html;
}
/**
* Generate wikitext showing hooks in $wgHooks.
*
- * @return String: wikitext
+ * @return string Wikitext
*/
private function getWgHooks() {
- global $wgHooks;
+ global $wgSpecialVersionShowHooks, $wgHooks;
- if ( count( $wgHooks ) ) {
+ if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
$myWgHooks = $wgHooks;
ksort( $myWgHooks );
- $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), $this->msg( 'version-hooks' )->text() ) .
- Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
- "<tr>
- <th>" . $this->msg( 'version-hook-name' )->text() . "</th>
- <th>" . $this->msg( 'version-hook-subscribedby' )->text() . "</th>
- </tr>\n";
+ $ret = array();
+ $ret[] = '== {{int:version-hooks}} ==';
+ $ret[] = Html::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) );
+ $ret[] = Html::openElement( 'tr' );
+ $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-name' )->text() );
+ $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-subscribedby' )->text() );
+ $ret[] = Html::closeElement( 'tr' );
foreach ( $myWgHooks as $hook => $hooks ) {
- $ret .= "<tr>
- <td>$hook</td>
- <td>" . $this->listToText( $hooks ) . "</td>
- </tr>\n";
+ $ret[] = Html::openElement( 'tr' );
+ $ret[] = Html::element( 'td', array(), $hook );
+ $ret[] = Html::element( 'td', array(), $this->listToText( $hooks ) );
+ $ret[] = Html::closeElement( 'tr' );
}
- $ret .= Xml::closeElement( 'table' );
- return $ret;
+ $ret[] = Html::closeElement( 'table' );
+
+ return implode( "\n", $ret );
} else {
return '';
}
}
- private function openExtType( $text, $name = null ) {
- $opt = array( 'colspan' => 4 );
+ private function openExtType( $text = null, $name = null ) {
$out = '';
+ $opt = array( 'colspan' => 5 );
if ( $this->firstExtOpened ) {
// Insert a spacing line
- $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n";
+ $out .= Html::rawElement( 'tr', array( 'class' => 'sv-space' ),
+ Html::element( 'td', $opt )
+ );
}
$this->firstExtOpened = true;
@@ -593,7 +849,27 @@ class SpecialVersion extends SpecialPage {
$opt['id'] = "sv-$name";
}
- $out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n";
+ if ( $text !== null ) {
+ $out .= Html::rawElement( 'tr', array(),
+ Html::element( 'th', $opt, $text )
+ );
+ }
+
+ $firstHeadingMsg = ( $name === 'credits-skin' )
+ ? 'version-skin-colheader-name'
+ : 'version-ext-colheader-name';
+ $out .= Html::openElement( 'tr' );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( $firstHeadingMsg )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-version' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-license' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-description' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-credits' )->text() );
+ $out .= Html::closeElement( 'tr' );
return $out;
}
@@ -601,64 +877,156 @@ class SpecialVersion extends SpecialPage {
/**
* Get information about client's IP address.
*
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
private function IPInfo() {
$ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
+
return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
}
/**
* Return a formatted unsorted list of authors
*
- * @param $authors mixed: string or array of strings
- * @return String: HTML fragment
+ * 'And Others'
+ * If an item in the $authors array is '...' it is assumed to indicate an
+ * 'and others' string which will then be linked to an ((AUTHORS)|(CREDITS))(\.txt)?
+ * file if it exists in $dir.
+ *
+ * Similarly an entry ending with ' ...]' is assumed to be a link to an
+ * 'and others' page.
+ *
+ * If no '...' string variant is found, but an authors file is found an
+ * 'and others' will be added to the end of the credits.
+ *
+ * @param string|array $authors
+ * @param string $extName Name of the extension for link creation
+ * @param string $extDir Path to the extension root directory
+ *
+ * @return string HTML fragment
*/
- function listAuthors( $authors ) {
+ public function listAuthors( $authors, $extName, $extDir ) {
+ $hasOthers = false;
+
$list = array();
foreach ( (array)$authors as $item ) {
if ( $item == '...' ) {
- $list[] = $this->msg( 'version-poweredby-others' )->text();
+ $hasOthers = true;
+
+ if ( $this->getExtAuthorsFileName( $extDir ) ) {
+ $text = Linker::link(
+ $this->getPageTitle( "Credits/$extName" ),
+ $this->msg( 'version-poweredby-others' )->text()
+ );
+ } else {
+ $text = $this->msg( 'version-poweredby-others' )->text();
+ }
+ $list[] = $text;
} elseif ( substr( $item, -5 ) == ' ...]' ) {
- $list[] = substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]";
+ $hasOthers = true;
+ $list[] = $this->getOutput()->parseInline(
+ substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
+ );
} else {
- $list[] = $item;
+ $list[] = $this->getOutput()->parseInline( $item );
}
}
+
+ if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
+ $list[] = $text = Linker::link(
+ $this->getPageTitle( "Credits/$extName" ),
+ $this->msg( 'version-poweredby-others' )->text()
+ );
+ }
+
return $this->listToText( $list, false );
}
/**
- * Convert an array of items into a list for display.
+ * Obtains the full path of an extensions authors or credits file if
+ * one exists.
+ *
+ * @param string $extDir Path to the extensions root directory
+ *
+ * @since 1.23
*
- * @param array $list of elements to display
- * @param $sort Boolean: whether to sort the items in $list
+ * @return bool|string False if no such file exists, otherwise returns
+ * a path to it.
+ */
+ public static function getExtAuthorsFileName( $extDir ) {
+ if ( !$extDir ) {
+ return false;
+ }
+
+ foreach ( scandir( $extDir ) as $file ) {
+ $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
+ if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt)?$/', $file ) &&
+ is_readable( $fullPath ) &&
+ is_file( $fullPath )
+ ) {
+ return $fullPath;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Obtains the full path of an extensions copying or license file if
+ * one exists.
+ *
+ * @param string $extDir Path to the extensions root directory
*
- * @return String
+ * @since 1.23
+ *
+ * @return bool|string False if no such file exists, otherwise returns
+ * a path to it.
*/
- function listToText( $list, $sort = true ) {
- $cnt = count( $list );
+ public static function getExtLicenseFileName( $extDir ) {
+ if ( !$extDir ) {
+ return false;
+ }
- if ( $cnt == 1 ) {
- // Enforce always returning a string
- return (string)self::arrayToString( $list[0] );
- } elseif ( $cnt == 0 ) {
- return '';
- } else {
- if ( $sort ) {
- sort( $list );
+ foreach ( scandir( $extDir ) as $file ) {
+ $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
+ if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
+ is_readable( $fullPath ) &&
+ is_file( $fullPath )
+ ) {
+ return $fullPath;
}
- return $this->getLanguage()->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
}
+
+ return false;
+ }
+
+ /**
+ * Convert an array of items into a list for display.
+ *
+ * @param array $list List of elements to display
+ * @param bool $sort Whether to sort the items in $list
+ *
+ * @return string
+ */
+ public function listToText( $list, $sort = true ) {
+ if ( !count( $list ) ) {
+ return '';
+ }
+ if ( $sort ) {
+ sort( $list );
+ }
+
+ return $this->getLanguage()
+ ->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
}
/**
* Convert an array or object to a string for display.
*
- * @param $list Mixed: will convert an array to string if given and return
- * the paramater unaltered otherwise
+ * @param mixed $list Will convert an array to string if given and return
+ * the paramater unaltered otherwise
*
- * @return Mixed
+ * @return mixed
*/
public static function arrayToString( $list ) {
if ( is_array( $list ) && count( $list ) == 1 ) {
@@ -666,6 +1034,7 @@ class SpecialVersion extends SpecialPage {
}
if ( is_object( $list ) ) {
$class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
+
return $class;
} elseif ( !is_array( $list ) ) {
return $list;
@@ -675,6 +1044,7 @@ class SpecialVersion extends SpecialPage {
} else {
$class = $list[0];
}
+
return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
}
}
@@ -692,7 +1062,7 @@ class SpecialVersion extends SpecialPage {
* url The subversion URL of the directory
* repo-url The base URL of the repository
* viewvc-url A ViewVC URL pointing to the checked-out revision
- * @param $dir string
+ * @param string $dir
* @return array|bool
*/
public static function getSvnInfo( $dir ) {
@@ -765,9 +1135,9 @@ class SpecialVersion extends SpecialPage {
/**
* Retrieve the revision number of a Subversion working directory.
*
- * @param string $dir directory of the svn checkout
+ * @param string $dir Directory of the svn checkout
*
- * @return Integer: revision number as int
+ * @return int Revision number
*/
public static function getSvnRevision( $dir ) {
$info = self::getSvnInfo( $dir );
@@ -782,15 +1152,25 @@ class SpecialVersion extends SpecialPage {
}
/**
- * @param string $dir directory of the git checkout
- * @return bool|String sha1 of commit HEAD points to
+ * @param string $dir Directory of the git checkout
+ * @return bool|string Sha1 of commit HEAD points to
*/
public static function getGitHeadSha1( $dir ) {
$repo = new GitInfo( $dir );
+
return $repo->getHeadSHA1();
}
/**
+ * @param string $dir Directory of the git checkout
+ * @return bool|string Branch currently checked out
+ */
+ public static function getGitCurrentBranch( $dir ) {
+ $repo = new GitInfo( $dir );
+ return $repo->getCurrentBranch();
+ }
+
+ /**
* Get the list of entry points and their URLs
* @return string Wikitext
*/
@@ -810,7 +1190,11 @@ class SpecialVersion extends SpecialPage {
'dir' => $language->getDir(),
'lang' => $language->getCode()
);
- $out = Html::element( 'h2', array( 'id' => 'mw-version-entrypoints' ), $this->msg( 'version-entrypoints' )->text() ) .
+ $out = Html::element(
+ 'h2',
+ array( 'id' => 'mw-version-entrypoints' ),
+ $this->msg( 'version-entrypoints' )->text()
+ ) .
Html::openElement( 'table',
array(
'class' => 'wikitable plainlinks',
@@ -820,8 +1204,16 @@ class SpecialVersion extends SpecialPage {
)
) .
Html::openElement( 'tr' ) .
- Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-entrypoint' )->text() ) .
- Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-url' )->text() ) .
+ Html::element(
+ 'th',
+ $thAttribures,
+ $this->msg( 'version-entrypoints-header-entrypoint' )->text()
+ ) .
+ Html::element(
+ 'th',
+ $thAttribures,
+ $this->msg( 'version-entrypoints-header-url' )->text()
+ ) .
Html::closeElement( 'tr' );
foreach ( $entryPoints as $message => $value ) {
@@ -835,11 +1227,11 @@ class SpecialVersion extends SpecialPage {
}
$out .= Html::closeElement( 'table' );
+
return $out;
}
protected function getGroupName() {
return 'wiki';
}
-
}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index d2ffdb94..b8c0bb28 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -29,6 +29,7 @@
* @ingroup SpecialPage
*/
class WantedCategoriesPage extends WantedQueryPage {
+ private $currentCategoryCounts;
function __construct( $name = 'Wantedcategories' ) {
parent::__construct( $name );
@@ -37,9 +38,11 @@ class WantedCategoriesPage extends WantedQueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'categorylinks', 'page' ),
- 'fields' => array( 'namespace' => NS_CATEGORY,
- 'title' => 'cl_to',
- 'value' => 'COUNT(*)' ),
+ 'fields' => array(
+ 'namespace' => NS_CATEGORY,
+ 'title' => 'cl_to',
+ 'value' => 'COUNT(*)'
+ ),
'conds' => array( 'page_title IS NULL' ),
'options' => array( 'GROUP BY' => 'cl_to' ),
'join_conds' => array( 'page' => array( 'LEFT JOIN',
@@ -48,6 +51,37 @@ class WantedCategoriesPage extends WantedQueryPage {
);
}
+ function preprocessResults( $db, $res ) {
+ parent::preprocessResults( $db, $res );
+
+ $this->currentCategoryCounts = array();
+
+ if ( !$res->numRows() || !$this->isCached() ) {
+ return;
+ }
+
+ // Fetch (hopefully) up-to-date numbers of pages in each category.
+ // This should be fast enough as we limit the list to a reasonable length.
+
+ $allCategories = array();
+ foreach ( $res as $row ) {
+ $allCategories[] = $row->title;
+ }
+
+ $categoryRes = $db->select(
+ 'category',
+ array( 'cat_title', 'cat_pages' ),
+ array( 'cat_title' => $allCategories ),
+ __METHOD__
+ );
+ foreach ( $categoryRes as $row ) {
+ $this->currentCategoryCounts[$row->cat_title] = intval( $row->cat_pages );
+ }
+
+ // Back to start for display
+ $res->seek( 0 );
+ }
+
/**
* @param Skin $skin
* @param object $result Result row
@@ -59,17 +93,37 @@ class WantedCategoriesPage extends WantedQueryPage {
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = htmlspecialchars( $wgContLang->convert( $nt->getText() ) );
- $plink = $this->isCached() ?
- Linker::link( $nt, $text ) :
- Linker::link(
+ if ( !$this->isCached() ) {
+ // We can assume the freshest data
+ $plink = Linker::link(
$nt,
$text,
array(),
array(),
array( 'broken' )
);
+ $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+ } else {
+ $plink = Linker::link( $nt, $text );
+
+ $currentValue = isset( $this->currentCategoryCounts[$result->title] )
+ ? $this->currentCategoryCounts[$result->title]
+ : 0;
+
+ // If the category has been created or emptied since the list was refreshed, strike it
+ if ( $nt->isKnown() || $currentValue === 0 ) {
+ $plink = "<del>$plink</del>";
+ }
+
+ // Show the current number of category entries if it changed
+ if ( $currentValue !== $result->value ) {
+ $nlinks = $this->msg( 'nmemberschanged' )
+ ->numParams( $result->value, $currentValue )->escaped();
+ } else {
+ $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+ }
+ }
- $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
return $this->getLanguage()->specialList( $plink, $nlinks );
}
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index b5c1fdbe..937a503c 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -49,40 +49,97 @@ class WantedFilesPage extends WantedQueryPage {
$category = false;
}
+ $noForeign = '';
+ if ( !$this->likelyToHaveFalsePositives() ) {
+ // Additional messages for grep:
+ // wantedfiletext-cat-noforeign, wantedfiletext-nocat
+ $noForeign = '-noforeign';
+ }
+
if ( $category ) {
return $this
- ->msg( 'wantedfiletext-cat' )
+ ->msg( 'wantedfiletext-cat' . $noForeign )
->params( $category->getFullText() )
->parseAsBlock();
} else {
return $this
- ->msg( 'wantedfiletext-nocat' )
+ ->msg( 'wantedfiletext-nocat' . $noForeign )
->parseAsBlock();
}
}
/**
+ * Whether foreign repos are likely to cause false positives
+ *
+ * In its own function to allow subclasses to override.
+ * @see SpecialWantedFilesGUOverride in GlobalUsage extension.
+ * @since 1.24
+ */
+ protected function likelyToHaveFalsePositives() {
+ return RepoGroup::singleton()->hasForeignRepos();
+ }
+
+ /**
* KLUGE: The results may contain false positives for files
* that exist e.g. in a shared repo. Setting this at least
* keeps them from showing up as redlinks in the output, even
* if it doesn't fix the real problem (bug 6220).
+ *
+ * @note could also have existing links here from broken file
+ * redirects.
* @return bool
*/
function forceExistenceCheck() {
return true;
}
+ /**
+ * Does the file exist?
+ *
+ * Use wfFindFile so we still think file namespace pages without
+ * files are missing, but valid file redirects and foreign files are ok.
+ *
+ * @return boolean
+ */
+ protected function existenceCheck( Title $title ) {
+ return (bool) wfFindFile( $title );
+ }
+
function getQueryInfo() {
return array(
- 'tables' => array( 'imagelinks', 'image' ),
- 'fields' => array( 'namespace' => NS_FILE,
- 'title' => 'il_to',
- 'value' => 'COUNT(*)' ),
- 'conds' => array( 'img_name IS NULL' ),
+ 'tables' => array(
+ 'imagelinks',
+ 'page',
+ 'redirect',
+ 'img1' => 'image',
+ 'img2' => 'image',
+ ),
+ 'fields' => array(
+ 'namespace' => NS_FILE,
+ 'title' => 'il_to',
+ 'value' => 'COUNT(*)'
+ ),
+ 'conds' => array(
+ 'img1.img_name' => null,
+ // We also need to exclude file redirects
+ 'img2.img_name' => null,
+ ),
'options' => array( 'GROUP BY' => 'il_to' ),
- 'join_conds' => array( 'image' =>
- array( 'LEFT JOIN',
- array( 'il_to = img_name' )
+ 'join_conds' => array(
+ 'img1' => array( 'LEFT JOIN',
+ 'il_to = img1.img_name'
+ ),
+ 'page' => array( 'LEFT JOIN', array(
+ 'il_to = page_title',
+ 'page_namespace' => NS_FILE,
+ ) ),
+ 'redirect' => array( 'LEFT JOIN', array(
+ 'page_id = rd_from',
+ 'rd_namespace' => NS_FILE,
+ 'rd_interwiki' => ''
+ ) ),
+ 'img2' => array( 'LEFT JOIN',
+ 'rd_title = img2.img_name'
)
)
);
diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php
index acec4ea4..38f1808f 100644
--- a/includes/specials/SpecialWantedpages.php
+++ b/includes/specials/SpecialWantedpages.php
@@ -54,8 +54,7 @@ class WantedPagesPage extends WantedQueryPage {
}
function getQueryInfo() {
- global $wgWantedPagesThreshold;
- $count = $wgWantedPagesThreshold - 1;
+ $count = $this->getConfig()->get( 'WantedPagesThreshold' ) - 1;
$query = array(
'tables' => array(
'pagelinks',
@@ -69,8 +68,7 @@ class WantedPagesPage extends WantedQueryPage {
),
'conds' => array(
'pg1.page_namespace IS NULL',
- "pl_namespace NOT IN ( '" . NS_USER .
- "', '" . NS_USER_TALK . "' )",
+ "pl_namespace NOT IN ( '" . NS_USER . "', '" . NS_USER_TALK . "' )",
"pg2.page_namespace != '" . NS_MEDIAWIKI . "'"
),
'options' => array(
@@ -89,6 +87,7 @@ class WantedPagesPage extends WantedQueryPage {
);
// Replacement for the WantedPages::getSQL hook
wfRunHooks( 'WantedPages::getQueryInfo', array( &$this, &$query ) );
+
return $query;
}
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
index d13fa031..a4b9dd84 100644
--- a/includes/specials/SpecialWantedtemplates.php
+++ b/includes/specials/SpecialWantedtemplates.php
@@ -32,7 +32,6 @@
* @ingroup SpecialPage
*/
class WantedTemplatesPage extends WantedQueryPage {
-
function __construct( $name = 'Wantedtemplates' ) {
parent::__construct( $name );
}
@@ -40,16 +39,19 @@ class WantedTemplatesPage extends WantedQueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'templatelinks', 'page' ),
- 'fields' => array( 'namespace' => 'tl_namespace',
- 'title' => 'tl_title',
- 'value' => 'COUNT(*)' ),
- 'conds' => array( 'page_title IS NULL',
- 'tl_namespace' => NS_TEMPLATE ),
- 'options' => array(
- 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) ),
+ 'fields' => array(
+ 'namespace' => 'tl_namespace',
+ 'title' => 'tl_title',
+ 'value' => 'COUNT(*)'
+ ),
+ 'conds' => array(
+ 'page_title IS NULL',
+ 'tl_namespace' => NS_TEMPLATE
+ ),
+ 'options' => array( 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) ),
'join_conds' => array( 'page' => array( 'LEFT JOIN',
- array( 'page_namespace = tl_namespace',
- 'page_title = tl_title' ) ) )
+ array( 'page_namespace = tl_namespace',
+ 'page_title = tl_title' ) ) )
);
}
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index 55dc6efd..8f2f86b9 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -18,192 +18,190 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup SpecialPage Watchlist
+ * @ingroup SpecialPage
*/
-class SpecialWatchlist extends SpecialPage {
- protected $customFilters;
- /**
- * Constructor
- */
+/**
+ * A special page that lists last changes made to the wiki,
+ * limited to user-defined list of titles.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWatchlist extends ChangesListSpecialPage {
public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
parent::__construct( $page, $restriction );
}
/**
- * Execute
- * @param $par Parameter passed to the page
+ * Main execution point
+ *
+ * @param string $subpage
*/
- function execute( $par ) {
- global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
+ function execute( $subpage ) {
+ // Anons don't get a watchlist
+ $this->requireLogin( 'watchlistanontext' );
- $user = $this->getUser();
$output = $this->getOutput();
+ $request = $this->getRequest();
+
+ $mode = SpecialEditWatchlist::getMode( $request, $subpage );
+ if ( $mode !== false ) {
+ if ( $mode === SpecialEditWatchlist::EDIT_RAW ) {
+ $title = SpecialPage::getTitleFor( 'EditWatchlist', 'raw' );
+ } elseif ( $mode === SpecialEditWatchlist::EDIT_CLEAR ) {
+ $title = SpecialPage::getTitleFor( 'EditWatchlist', 'clear' );
+ } else {
+ $title = SpecialPage::getTitleFor( 'EditWatchlist' );
+ }
+
+ $output->redirect( $title->getLocalURL() );
- # Anons don't get a watchlist
- if ( $user->isAnon() ) {
- $output->setPageTitle( $this->msg( 'watchnologin' ) );
- $output->setRobotPolicy( 'noindex,nofollow' );
- $llink = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Userlogin' ),
- $this->msg( 'loginreqlink' )->escaped(),
- array(),
- array( 'returnto' => $this->getTitle()->getPrefixedText() )
- );
- $output->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() );
return;
}
- // Check permissions
$this->checkPermissions();
- // Add feed links
- $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
- if ( $wlToken ) {
- $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
- 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
+ $user = $this->getUser();
+ $opts = $this->getOptions();
+
+ $config = $this->getConfig();
+ if ( ( $config->get( 'EnotifWatchlist' ) || $config->get( 'ShowUpdatedMarker' ) )
+ && $request->getVal( 'reset' )
+ && $request->wasPosted()
+ ) {
+ $user->clearAllNotifications();
+ $output->redirect( $this->getPageTitle()->getFullURL( $opts->getChangedValues() ) );
+
+ return;
}
- $this->setHeaders();
- $this->outputHeader();
+ parent::execute( $subpage );
+ }
- $output->addSubtitle( $this->msg( 'watchlistfor2', $user->getName()
- )->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
+ /**
+ * 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
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ // See also SpecialEditWatchlist::prefixSearchSubpages
+ return self::prefixSearchArray(
+ $search,
+ $limit,
+ array(
+ 'clear',
+ 'edit',
+ 'raw',
+ )
+ );
+ }
- $request = $this->getRequest();
+ /**
+ * Get a FormOptions object containing the default options
+ *
+ * @return FormOptions
+ */
+ public function getDefaultOptions() {
+ $opts = parent::getDefaultOptions();
+ $user = $this->getUser();
- $mode = SpecialEditWatchlist::getMode( $request, $par );
- if ( $mode !== false ) {
- # TODO: localise?
- switch ( $mode ) {
- case SpecialEditWatchlist::EDIT_CLEAR:
- $mode = 'clear';
- break;
- case SpecialEditWatchlist::EDIT_RAW:
- $mode = 'raw';
- break;
- default:
- $mode = null;
- }
- $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode );
- $output->redirect( $title->getLocalURL() );
- return;
- }
+ $opts->add( 'days', $user->getOption( 'watchlistdays' ), FormOptions::FLOAT );
- $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+ $opts->add( 'hideminor', $user->getBoolOption( 'watchlisthideminor' ) );
+ $opts->add( 'hidebots', $user->getBoolOption( 'watchlisthidebots' ) );
+ $opts->add( 'hideanons', $user->getBoolOption( 'watchlisthideanons' ) );
+ $opts->add( 'hideliu', $user->getBoolOption( 'watchlisthideliu' ) );
+ $opts->add( 'hidepatrolled', $user->getBoolOption( 'watchlisthidepatrolled' ) );
+ $opts->add( 'hidemyself', $user->getBoolOption( 'watchlisthideown' ) );
- $nitems = $this->countItems( $dbr );
- if ( $nitems == 0 ) {
- $output->addWikiMsg( 'nowatchlist' );
- return;
- }
+ $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
- // @todo use FormOptions!
- $defaults = array(
- /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ),
- /* bool */ 'hideMinor' => (int)$user->getBoolOption( 'watchlisthideminor' ),
- /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ),
- /* bool */ 'hideAnons' => (int)$user->getBoolOption( 'watchlisthideanons' ),
- /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ),
- /* bool */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ),
- /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ),
- /* bool */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ),
- /* ? */ 'namespace' => '', //means all
- /* ? */ 'invert' => false,
- /* bool */ 'associated' => false,
- );
- $this->customFilters = array();
- wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) );
- foreach ( $this->customFilters as $key => $params ) {
- $defaults[$key] = $params['default'];
- }
+ return $opts;
+ }
- # Extract variables from the request, falling back to user preferences or
- # other default values if these don't exist
- $values = array();
- $values['days'] = floatval( $request->getVal( 'days', $defaults['days'] ) );
- $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $defaults['hideMinor'] );
- $values['hideBots'] = (int)$request->getBool( 'hideBots', $defaults['hideBots'] );
- $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $defaults['hideAnons'] );
- $values['hideLiu'] = (int)$request->getBool( 'hideLiu', $defaults['hideLiu'] );
- $values['hideOwn'] = (int)$request->getBool( 'hideOwn', $defaults['hideOwn'] );
- $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $defaults['hidePatrolled'] );
- $values['extended'] = (int)$request->getBool( 'extended', $defaults['extended'] );
- foreach ( $this->customFilters as $key => $params ) {
- $values[$key] = (int)$request->getBool( $key, $defaults[$key] );
+ /**
+ * Get custom show/hide filters
+ *
+ * @return array Map of filter URL param names to properties (msg/default)
+ */
+ protected function getCustomFilters() {
+ if ( $this->customFilters === null ) {
+ $this->customFilters = parent::getCustomFilters();
+ wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ), '1.23' );
}
- # Get namespace value, if supplied, and prepare a WHERE fragment
- $nameSpace = $request->getIntOrNull( 'namespace' );
- $invert = $request->getBool( 'invert' );
- $associated = $request->getBool( 'associated' );
- if ( !is_null( $nameSpace ) ) {
- $eq_op = $invert ? '!=' : '=';
- $bool_op = $invert ? 'AND' : 'OR';
- $nameSpace = intval( $nameSpace ); // paranioa
- if ( !$associated ) {
- $nameSpaceClause = "rc_namespace $eq_op $nameSpace";
- } else {
- $associatedNS = MWNamespace::getAssociated( $nameSpace );
- $nameSpaceClause =
- "rc_namespace $eq_op $nameSpace " .
- $bool_op .
- " rc_namespace $eq_op $associatedNS";
+ return $this->customFilters;
+ }
+
+ /**
+ * Fetch values for a FormOptions object from the WebRequest associated with this instance.
+ *
+ * Maps old pre-1.23 request parameters Watchlist used to use (different from Recentchanges' ones)
+ * to the current ones.
+ *
+ * @param FormOptions $opts
+ * @return FormOptions
+ */
+ protected function fetchOptionsFromRequest( $opts ) {
+ static $compatibilityMap = array(
+ 'hideMinor' => 'hideminor',
+ 'hideBots' => 'hidebots',
+ 'hideAnons' => 'hideanons',
+ 'hideLiu' => 'hideliu',
+ 'hidePatrolled' => 'hidepatrolled',
+ 'hideOwn' => 'hidemyself',
+ );
+
+ $params = $this->getRequest()->getValues();
+ foreach ( $compatibilityMap as $from => $to ) {
+ if ( isset( $params[$from] ) ) {
+ $params[$to] = $params[$from];
+ unset( $params[$from] );
}
- } else {
- $nameSpace = '';
- $nameSpaceClause = '';
- }
- $values['namespace'] = $nameSpace;
- $values['invert'] = $invert;
- $values['associated'] = $associated;
-
- // Dump everything here
- $nondefaults = array();
- foreach ( $defaults as $name => $defValue ) {
- wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults );
}
- if ( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) &&
- $request->wasPosted() )
- {
- $user->clearAllNotifications();
- $output->redirect( $this->getTitle()->getFullURL( $nondefaults ) );
- return;
- }
+ // Not the prettiest way to achieve this… FormOptions internally depends on data sanitization
+ // methods defined on WebRequest and removing this dependency would cause some code duplication.
+ $request = new DerivativeRequest( $this->getRequest(), $params );
+ $opts->fetchValuesFromRequest( $request );
- # Possible where conditions
- $conds = array();
+ return $opts;
+ }
- if ( $values['days'] > 0 ) {
- $conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $values['days'] * 86400 ) ) );
- }
+ /**
+ * Return an array of conditions depending of options set in $opts
+ *
+ * @param FormOptions $opts
+ * @return array
+ */
+ public function buildMainQueryConds( FormOptions $opts ) {
+ $dbr = $this->getDB();
+ $conds = parent::buildMainQueryConds( $opts );
- # Toggles
- if ( $values['hideOwn'] ) {
- $conds[] = 'rc_user != ' . $user->getId();
- }
- if ( $values['hideBots'] ) {
- $conds[] = 'rc_bot = 0';
- }
- if ( $values['hideMinor'] ) {
- $conds[] = 'rc_minor = 0';
- }
- if ( $values['hideLiu'] ) {
- $conds[] = 'rc_user = 0';
- }
- if ( $values['hideAnons'] ) {
- $conds[] = 'rc_user != 0';
- }
- if ( $user->useRCPatrol() && $values['hidePatrolled'] ) {
- $conds[] = 'rc_patrolled != 1';
- }
- if ( $nameSpaceClause ) {
- $conds[] = $nameSpaceClause;
+ // Calculate cutoff
+ if ( $opts['days'] > 0 ) {
+ $conds[] = 'rc_timestamp > ' .
+ $dbr->addQuotes( $dbr->timestamp( time() - intval( $opts['days'] * 86400 ) ) );
}
+ return $conds;
+ }
+
+ /**
+ * Process the query
+ *
+ * @param array $conds
+ * @param FormOptions $opts
+ * @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
+ */
+ public function doMainQuery( $conds, $opts ) {
+ $dbr = $this->getDB();
+ $user = $this->getUser();
+
# Toggle watchlist content (all recent edits or just the latest)
- if ( $values['extended'] ) {
+ if ( $opts['extended'] ) {
$limitWatchlist = $user->getIntOption( 'wllimit' );
$usePage = false;
} else {
@@ -211,10 +209,6 @@ class SpecialWatchlist extends SpecialPage {
$nonRevisionTypes = array( RC_LOG );
wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
if ( $nonRevisionTypes ) {
- if ( count( $nonRevisionTypes ) === 1 ) {
- // if only one use an equality instead of IN condition
- $nonRevisionTypes = reset( $nonRevisionTypes );
- }
$conds[] = $dbr->makeList(
array(
'rc_this_oldid=page_latest',
@@ -227,51 +221,9 @@ class SpecialWatchlist extends SpecialPage {
$usePage = true;
}
- # Show a message about slave lag, if applicable
- $lag = wfGetLB()->safeGetLag( $dbr );
- if ( $lag > 0 ) {
- $output->showLagWarning( $lag );
- }
-
- # Create output
- $form = '';
-
- # Show watchlist header
- $form .= "<p>";
- $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse() . "\n";
- if ( $wgEnotifWatchlist && $user->getOption( 'enotifwatchlistpages' ) ) {
- $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
- }
- if ( $wgShowUpdatedMarker ) {
- $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
- }
- $form .= "</p>";
-
- if ( $wgShowUpdatedMarker ) {
- $form .= Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL(),
- 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
- Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
- Html::hidden( 'reset', 'all' ) . "\n";
- foreach ( $nondefaults as $key => $value ) {
- $form .= Html::hidden( $key, $value ) . "\n";
- }
- $form .= Xml::closeElement( 'form' ) . "\n";
- }
-
- $form .= Xml::openElement( 'form', array(
- 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL(),
- 'id' => 'mw-watchlist-form'
- ) );
- $form .= Xml::fieldset(
- $this->msg( 'watchlist-options' )->text(),
- false,
- array( 'id' => 'mw-watchlist-options' )
- );
-
$tables = array( 'recentchanges', 'watchlist' );
$fields = RecentChange::selectFields();
+ $query_options = array( 'ORDER BY' => 'rc_timestamp DESC' );
$join_conds = array(
'watchlist' => array(
'INNER JOIN',
@@ -282,12 +234,12 @@ class SpecialWatchlist extends SpecialPage {
),
),
);
- $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
- if ( $wgShowUpdatedMarker ) {
+
+ if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
$fields[] = 'wl_notificationtimestamp';
}
if ( $limitWatchlist ) {
- $options['LIMIT'] = $limitWatchlist;
+ $query_options['LIMIT'] = $limitWatchlist;
}
$rollbacker = $user->isAllowed( 'rollback' );
@@ -303,7 +255,7 @@ class SpecialWatchlist extends SpecialPage {
// the necessary rights.
if ( !$user->isAllowed( 'deletedhistory' ) ) {
$bitmask = LogPage::DELETED_ACTION;
- } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
$bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
} else {
$bitmask = 0;
@@ -315,44 +267,176 @@ class SpecialWatchlist extends SpecialPage {
), LIST_OR );
}
- ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' );
- wfRunHooks( 'SpecialWatchlistQuery', array( &$conds, &$tables, &$join_conds, &$fields, $values ) );
+ ChangeTags::modifyDisplayQuery(
+ $tables,
+ $fields,
+ $conds,
+ $join_conds,
+ $query_options,
+ ''
+ );
+
+ $this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts );
+
+ return $dbr->select(
+ $tables,
+ $fields,
+ $conds,
+ __METHOD__,
+ $query_options,
+ $join_conds
+ );
+ }
+
+ protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
+ return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
+ && wfRunHooks(
+ 'SpecialWatchlistQuery',
+ array( &$conds, &$tables, &$join_conds, &$fields, $opts ),
+ '1.23'
+ );
+ }
+
+ /**
+ * Return a DatabaseBase object for reading
+ *
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ return wfGetDB( DB_SLAVE, 'watchlist' );
+ }
+
+ /**
+ * Output feed links.
+ */
+ public function outputFeedLinks() {
+ $user = $this->getUser();
+ $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
+ if ( $wlToken ) {
+ $this->addFeedLinks( array(
+ 'action' => 'feedwatchlist',
+ 'allrev' => 1,
+ 'wlowner' => $user->getName(),
+ 'wltoken' => $wlToken,
+ ) );
+ }
+ }
+
+ /**
+ * Build and output the actual changes list.
+ *
+ * @param ResultWrapper $rows Database rows
+ * @param FormOptions $opts
+ */
+ public function outputChangesList( $rows, $opts ) {
+ $dbr = $this->getDB();
+ $user = $this->getUser();
+ $output = $this->getOutput();
+
+ # Show a message about slave lag, if applicable
+ $lag = wfGetLB()->safeGetLag( $dbr );
+ if ( $lag > 0 ) {
+ $output->showLagWarning( $lag );
+ }
+
+ # If no rows to display, show message before try to render the list
+ if ( $rows->numRows() == 0 ) {
+ $output->wrapWikiMsg(
+ "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
+ );
+ return;
+ }
+
+ $dbr->dataSeek( $rows, 0 );
+
+ $list = ChangesList::newFromContext( $this->getContext() );
+ $list->setWatchlistDivs();
+ $list->initChangesListRows( $rows );
+ $dbr->dataSeek( $rows, 0 );
+
+ $s = $list->beginRecentChangesList();
+ $counter = 1;
+ foreach ( $rows as $obj ) {
+ # Make RC entry
+ $rc = RecentChange::newFromRow( $obj );
+ $rc->counter = $counter++;
+
+ if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
+ $updated = $obj->wl_notificationtimestamp;
+ } else {
+ $updated = false;
+ }
+
+ if ( $this->getConfig()->get( 'RCShowWatchingUsers' ) && $user->getOption( 'shownumberswatching' ) ) {
+ $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
+ 'COUNT(*)',
+ array(
+ 'wl_namespace' => $obj->rc_namespace,
+ 'wl_title' => $obj->rc_title,
+ ),
+ __METHOD__ );
+ } else {
+ $rc->numberofWatchingusers = 0;
+ }
+
+ $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
+ if ( $changeLine !== false ) {
+ $s .= $changeLine;
+ }
+ }
+ $s .= $list->endRecentChangesList();
+
+ $output->addHTML( $s );
+ }
+
+ /**
+ * 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 ) {
+ $user = $this->getUser();
- $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
- $numRows = $res->numRows();
+ $this->getOutput()->addSubtitle(
+ $this->msg( 'watchlistfor2', $user->getName() )
+ ->rawParams( SpecialEditWatchlist::buildTools( null ) )
+ );
- /* Start bottom header */
+ $this->setTopText( $opts );
$lang = $this->getLanguage();
$wlInfo = '';
- if ( $values['days'] > 0 ) {
+ if ( $opts['days'] > 0 ) {
$timestamp = wfTimestampNow();
- $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $values['days'] * 24 ) )->params(
- $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . "<br />\n";
+ $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $opts['days'] * 24 ) )->params(
+ $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user )
+ )->parse() . "<br />\n";
}
- $cutofflinks = $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n";
+ $nondefaults = $opts->getChangedValues();
+ $cutofflinks = $this->cutoffLinks( $opts['days'], $nondefaults ) . "<br />\n";
# Spit out some control panel links
$filters = array(
- 'hideMinor' => 'rcshowhideminor',
- 'hideBots' => 'rcshowhidebots',
- 'hideAnons' => 'rcshowhideanons',
- 'hideLiu' => 'rcshowhideliu',
- 'hideOwn' => 'rcshowhidemine',
- 'hidePatrolled' => 'rcshowhidepatr'
+ 'hideminor' => 'rcshowhideminor',
+ 'hidebots' => 'rcshowhidebots',
+ 'hideanons' => 'rcshowhideanons',
+ 'hideliu' => 'rcshowhideliu',
+ 'hidemyself' => 'rcshowhidemine',
+ 'hidepatrolled' => 'rcshowhidepatr'
);
- foreach ( $this->customFilters as $key => $params ) {
+ foreach ( $this->getCustomFilters() as $key => $params ) {
$filters[$key] = $params['msg'];
}
// Disable some if needed
if ( !$user->useNPPatrol() ) {
- unset( $filters['hidePatrolled'] );
+ unset( $filters['hidepatrolled'] );
}
$links = array();
foreach ( $filters as $name => $msg ) {
- $links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] );
+ $links[] = $this->showHideLink( $nondefaults, $msg, $name, $opts[$name] );
}
$hiddenFields = $nondefaults;
@@ -360,6 +444,9 @@ class SpecialWatchlist extends SpecialPage {
unset( $hiddenFields['invert'] );
unset( $hiddenFields['associated'] );
+ # Create output
+ $form = '';
+
# Namespace filter and put the whole form together.
$form .= $wlInfo;
$form .= $cutofflinks;
@@ -367,7 +454,7 @@ class SpecialWatchlist extends SpecialPage {
$form .= "<hr />\n<p>";
$form .= Html::namespaceSelector(
array(
- 'selected' => $nameSpace,
+ 'selected' => $opts['namespace'],
'all' => '',
'label' => $this->msg( 'namespace' )->text()
), array(
@@ -380,14 +467,14 @@ class SpecialWatchlist extends SpecialPage {
$this->msg( 'invert' )->text(),
'invert',
'nsinvert',
- $invert,
+ $opts['invert'],
array( 'title' => $this->msg( 'tooltip-invert' )->text() )
) . '&#160;';
$form .= Xml::checkLabel(
$this->msg( 'namespace_association' )->text(),
'associated',
- 'associated',
- $associated,
+ 'nsassociated',
+ $opts['associated'],
array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() )
) . '&#160;';
$form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n";
@@ -396,82 +483,77 @@ class SpecialWatchlist extends SpecialPage {
}
$form .= Xml::closeElement( 'fieldset' ) . "\n";
$form .= Xml::closeElement( 'form' ) . "\n";
- $output->addHTML( $form );
-
- # If there's nothing to show, stop here
- if ( $numRows == 0 ) {
- $output->wrapWikiMsg(
- "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
- );
- return;
- }
+ $this->getOutput()->addHTML( $form );
- /* End bottom header */
-
- /* Do link batch query */
- $linkBatch = new LinkBatch;
- foreach ( $res as $row ) {
- $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text );
- if ( $row->rc_user != 0 ) {
- $linkBatch->add( NS_USER, $userNameUnderscored );
- }
- $linkBatch->add( NS_USER_TALK, $userNameUnderscored );
+ $this->setBottomText( $opts );
+ }
- $linkBatch->add( $row->rc_namespace, $row->rc_title );
- }
- $linkBatch->execute();
- $dbr->dataSeek( $res, 0 );
+ function setTopText( FormOptions $opts ) {
+ $nondefaults = $opts->getChangedValues();
+ $form = "";
+ $user = $this->getUser();
- $list = ChangesList::newFromContext( $this->getContext() );
- $list->setWatchlistDivs();
+ $dbr = $this->getDB();
+ $numItems = $this->countItems( $dbr );
+ $showUpdatedMarker = $this->getConfig()->get( 'ShowUpdatedMarker' );
- $s = $list->beginRecentChangesList();
- $counter = 1;
- foreach ( $res as $obj ) {
- # Make RC entry
- $rc = RecentChange::newFromRow( $obj );
- $rc->counter = $counter++;
-
- if ( $wgShowUpdatedMarker ) {
- $updated = $obj->wl_notificationtimestamp;
- } else {
- $updated = false;
+ // Show watchlist header
+ $form .= "<p>";
+ if ( $numItems == 0 ) {
+ $form .= $this->msg( 'nowatchlist' )->parse() . "\n";
+ } else {
+ $form .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
+ if ( $this->getConfig()->get( 'EnotifWatchlist' ) && $user->getOption( 'enotifwatchlistpages' ) ) {
+ $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
}
-
- if ( $wgRCShowWatchingUsers && $user->getOption( 'shownumberswatching' ) ) {
- $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
- 'COUNT(*)',
- array(
- 'wl_namespace' => $obj->rc_namespace,
- 'wl_title' => $obj->rc_title,
- ),
- __METHOD__ );
- } else {
- $rc->numberofWatchingusers = 0;
+ if ( $showUpdatedMarker ) {
+ $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
}
+ }
+ $form .= "</p>";
- $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
- if ( $changeLine !== false ) {
- $s .= $changeLine;
+ if ( $numItems > 0 && $showUpdatedMarker ) {
+ $form .= Xml::openElement( 'form', array( 'method' => 'post',
+ 'action' => $this->getPageTitle()->getLocalURL(),
+ 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
+ Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
+ Html::hidden( 'reset', 'all' ) . "\n";
+ foreach ( $nondefaults as $key => $value ) {
+ $form .= Html::hidden( $key, $value ) . "\n";
}
+ $form .= Xml::closeElement( 'form' ) . "\n";
}
- $s .= $list->endRecentChangesList();
- $output->addHTML( $s );
+ $form .= Xml::openElement( 'form', array(
+ 'method' => 'post',
+ 'action' => $this->getPageTitle()->getLocalURL(),
+ 'id' => 'mw-watchlist-form'
+ ) );
+ $form .= Xml::fieldset(
+ $this->msg( 'watchlist-options' )->text(),
+ false,
+ array( 'id' => 'mw-watchlist-options' )
+ );
+
+ $form .= SpecialRecentChanges::makeLegend( $this->getContext() );
+
+ $this->getOutput()->addHTML( $form );
}
protected function showHideLink( $options, $message, $name, $value ) {
$label = $this->msg( $value ? 'show' : 'hide' )->escaped();
$options[$name] = 1 - (int)$value;
- return $this->msg( $message )->rawParams( Linker::linkKnown( $this->getTitle(), $label, array(), $options ) )->escaped();
+ return $this->msg( $message )
+ ->rawParams( Linker::linkKnown( $this->getPageTitle(), $label, array(), $options ) )
+ ->escaped();
}
protected function hoursLink( $h, $options = array() ) {
$options['days'] = ( $h / 24.0 );
return Linker::linkKnown(
- $this->getTitle(),
+ $this->getPageTitle(),
$this->getLanguage()->formatNum( $h ),
array(),
$options
@@ -480,10 +562,11 @@ class SpecialWatchlist extends SpecialPage {
protected function daysLink( $d, $options = array() ) {
$options['days'] = $d;
- $message = ( $d ? $this->getLanguage()->formatNum( $d ) : $this->msg( 'watchlistall2' )->escaped() );
+ $message = $d ? $this->getLanguage()->formatNum( $d )
+ : $this->msg( 'watchlistall2' )->escaped();
return Linker::linkKnown(
- $this->getTitle(),
+ $this->getPageTitle(),
$message,
array(),
$options
@@ -508,6 +591,7 @@ class SpecialWatchlist extends SpecialPage {
foreach ( $days as $d ) {
$days[$i++] = $this->daysLink( $d, $options );
}
+
return $this->msg( 'wlshowlast' )->rawParams(
$this->getLanguage()->pipeList( $hours ),
$this->getLanguage()->pipeList( $days ),
@@ -518,19 +602,15 @@ class SpecialWatchlist extends SpecialPage {
* Count the number of items on a user's watchlist
*
* @param DatabaseBase $dbr A database connection
- * @return Integer
+ * @return int
*/
protected function countItems( $dbr ) {
# Fetch the raw count
- $res = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
+ $rows = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ );
- $row = $dbr->fetchObject( $res );
+ $row = $dbr->fetchObject( $rows );
$count = $row->count;
return floor( $count / 2 );
}
-
- protected function getGroupName() {
- return 'changes';
- }
}
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index 05c7dd5f..7dc6da1f 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -26,18 +26,13 @@
*
* @ingroup SpecialPage
*/
-class SpecialWhatLinksHere extends SpecialPage {
-
- /**
- * @var FormOptions
- */
+class SpecialWhatLinksHere extends IncludableSpecialPage {
+ /** @var FormOptions */
protected $opts;
protected $selfTitle;
- /**
- * @var Title
- */
+ /** @var Title */
protected $target;
protected $limits = array( 20, 50, 100, 250, 500 );
@@ -47,7 +42,6 @@ class SpecialWhatLinksHere extends SpecialPage {
}
function execute( $par ) {
- global $wgQueryPageDefaultLimit;
$out = $this->getOutput();
$this->setHeaders();
@@ -57,7 +51,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$opts->add( 'target', '' );
$opts->add( 'namespace', '', FormOptions::INTNULL );
- $opts->add( 'limit', $wgQueryPageDefaultLimit );
+ $opts->add( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
$opts->add( 'from', 0 );
$opts->add( 'back', 0 );
$opts->add( 'hideredirs', false );
@@ -69,7 +63,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$opts->validateIntBounds( 'limit', 0, 5000 );
// Give precedence to subpage syntax
- if ( isset( $par ) ) {
+ if ( $par !== null ) {
$opts->setValue( 'target', $par );
}
@@ -79,18 +73,23 @@ class SpecialWhatLinksHere extends SpecialPage {
$this->target = Title::newFromURL( $opts->getValue( 'target' ) );
if ( !$this->target ) {
$out->addHTML( $this->whatlinkshereForm() );
+
return;
}
$this->getSkin()->setRelevantTitle( $this->target );
- $this->selfTitle = $this->getTitle( $this->target->getPrefixedDBkey() );
+ $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' ) );
+ $this->showIndirectLinks(
+ 0,
+ $this->target,
+ $opts->getValue( 'limit' ),
+ $opts->getValue( 'from' ),
+ $opts->getValue( 'back' )
+ );
}
/**
@@ -101,10 +100,8 @@ class SpecialWhatLinksHere extends SpecialPage {
* @param int $back Display from this article ID at backwards scrolling (default: 0)
*/
function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
- global $wgMaxRedirectLinksRetrieved;
$out = $this->getOutput();
$dbr = wfGetDB( DB_SLAVE );
- $options = array();
$hidelinks = $this->opts->getValue( 'hidelinks' );
$hideredirs = $this->opts->getValue( 'hideredirs' );
@@ -113,95 +110,108 @@ class SpecialWhatLinksHere extends SpecialPage {
$fetchlinks = ( !$hidelinks || !$hideredirs );
- // Make the query
- $plConds = array(
- 'page_id=pl_from',
+ // Build query conds in concert for all three tables...
+ $conds['pagelinks'] = array(
'pl_namespace' => $target->getNamespace(),
'pl_title' => $target->getDBkey(),
);
- if ( $hideredirs ) {
- $plConds['rd_from'] = null;
- } elseif ( $hidelinks ) {
- $plConds[] = 'rd_from is NOT NULL';
- }
-
- $tlConds = array(
- 'page_id=tl_from',
+ $conds['templatelinks'] = array(
'tl_namespace' => $target->getNamespace(),
'tl_title' => $target->getDBkey(),
);
-
- $ilConds = array(
- 'page_id=il_from',
+ $conds['imagelinks'] = array(
'il_to' => $target->getDBkey(),
);
+ $useLinkNamespaceDBFields = $this->getConfig()->get( 'UseLinkNamespaceDBFields' );
$namespace = $this->opts->getValue( 'namespace' );
if ( is_int( $namespace ) ) {
- $plConds['page_namespace'] = $namespace;
- $tlConds['page_namespace'] = $namespace;
- $ilConds['page_namespace'] = $namespace;
+ if ( $useLinkNamespaceDBFields ) {
+ $conds['pagelinks']['pl_from_namespace'] = $namespace;
+ $conds['templatelinks']['tl_from_namespace'] = $namespace;
+ $conds['imagelinks']['il_from_namespace'] = $namespace;
+ } else {
+ $conds['pagelinks']['page_namespace'] = $namespace;
+ $conds['templatelinks']['page_namespace'] = $namespace;
+ $conds['imagelinks']['page_namespace'] = $namespace;
+ }
}
if ( $from ) {
- $tlConds[] = "tl_from >= $from";
- $plConds[] = "pl_from >= $from";
- $ilConds[] = "il_from >= $from";
+ $conds['templatelinks'][] = "tl_from >= $from";
+ $conds['pagelinks'][] = "pl_from >= $from";
+ $conds['imagelinks'][] = "il_from >= $from";
}
- // Read an extra row as an at-end check
- $queryLimit = $limit + 1;
-
- // Enforce join order, sometimes namespace selector may
- // trigger filesorts which are far less efficient than scanning many entries
- $options[] = 'STRAIGHT_JOIN';
-
- $options['LIMIT'] = $queryLimit;
- $fields = array( 'page_id', 'page_namespace', 'page_title', 'rd_from' );
+ if ( $hideredirs ) {
+ $conds['pagelinks']['rd_from'] = null;
+ } elseif ( $hidelinks ) {
+ $conds['pagelinks'][] = 'rd_from is NOT NULL';
+ }
- $joinConds = array( 'redirect' => array( 'LEFT JOIN', array(
- 'rd_from = page_id',
- 'rd_namespace' => $target->getNamespace(),
- 'rd_title' => $target->getDBkey(),
- 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
- )));
+ $queryFunc = function ( $dbr, $table, $fromCol ) use ( $conds, $target, $limit, $useLinkNamespaceDBFields ) {
+ // Read an extra row as an at-end check
+ $queryLimit = $limit + 1;
+ $on = array(
+ "rd_from = $fromCol",
+ 'rd_title' => $target->getDBkey(),
+ 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
+ );
+ if ( $useLinkNamespaceDBFields ) { // migration check
+ $on['rd_namespace'] = $target->getNamespace();
+ }
+ // Inner LIMIT is 2X in case of stale backlinks with wrong namespaces
+ $subQuery = $dbr->selectSqlText(
+ array( $table, 'page', 'redirect' ),
+ array( $fromCol, 'rd_from' ),
+ $conds[$table],
+ __CLASS__ . '::showIndirectLinks',
+ array( 'ORDER BY' => $fromCol, 'LIMIT' => 2 * $queryLimit ),
+ array(
+ 'page' => array( 'INNER JOIN', "$fromCol = page_id" ),
+ 'redirect' => array( 'LEFT JOIN', $on )
+ )
+ );
+ return $dbr->select(
+ array( 'page', 'temp_backlink_range' => "($subQuery)" ),
+ array( 'page_id', 'page_namespace', 'page_title', 'rd_from' ),
+ array(),
+ __CLASS__ . '::showIndirectLinks',
+ array( 'ORDER BY' => 'page_id', 'LIMIT' => $queryLimit ),
+ array( 'page' => array( 'INNER JOIN', "$fromCol = page_id" ) )
+ );
+ };
if ( $fetchlinks ) {
- $options['ORDER BY'] = 'pl_from';
- $plRes = $dbr->select( array( 'pagelinks', 'page', 'redirect' ), $fields,
- $plConds, __METHOD__, $options,
- $joinConds
- );
+ $plRes = $queryFunc( $dbr, 'pagelinks', 'pl_from' );
}
if ( !$hidetrans ) {
- $options['ORDER BY'] = 'tl_from';
- $tlRes = $dbr->select( array( 'templatelinks', 'page', 'redirect' ), $fields,
- $tlConds, __METHOD__, $options,
- $joinConds
- );
+ $tlRes = $queryFunc( $dbr, 'templatelinks', 'tl_from' );
}
if ( !$hideimages ) {
- $options['ORDER BY'] = 'il_from';
- $ilRes = $dbr->select( array( 'imagelinks', 'page', 'redirect' ), $fields,
- $ilConds, __METHOD__, $options,
- $joinConds
- );
+ $ilRes = $queryFunc( $dbr, 'imagelinks', 'il_from' );
}
- if ( ( !$fetchlinks || !$plRes->numRows() ) && ( $hidetrans || !$tlRes->numRows() ) && ( $hideimages || !$ilRes->numRows() ) ) {
+ if ( ( !$fetchlinks || !$plRes->numRows() )
+ && ( $hidetrans || !$tlRes->numRows() )
+ && ( $hideimages || !$ilRes->numRows() )
+ ) {
if ( 0 == $level ) {
- $out->addHTML( $this->whatlinkshereForm() );
-
- // Show filters only if there are links
- if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
- $out->addHTML( $this->getFilterPanel() );
+ if ( !$this->including() ) {
+ $out->addHTML( $this->whatlinkshereForm() );
+
+ // Show filters only if there are links
+ if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
+ $out->addHTML( $this->getFilterPanel() );
+ }
+ $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
+ $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
+ $out->setStatusCode( 404 );
}
-
- $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
- $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
}
+
return;
}
@@ -250,31 +260,34 @@ class SpecialWhatLinksHere extends SpecialPage {
$prevId = $from;
if ( $level == 0 ) {
- $out->addHTML( $this->whatlinkshereForm() );
- $out->addHTML( $this->getFilterPanel() );
- $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
+ if ( !$this->including() ) {
+ $out->addHTML( $this->whatlinkshereForm() );
+ $out->addHTML( $this->getFilterPanel() );
+ $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
- $prevnext = $this->getPrevNext( $prevId, $nextId );
- $out->addHTML( $prevnext );
+ $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, true ) );
- $this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved );
+ $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 ) );
+ $out->addHTML( $this->listItem( $row, $nt, $target ) );
}
}
$out->addHTML( $this->listEnd() );
if ( $level == 0 ) {
- $out->addHTML( $prevnext );
+ if ( !$this->including() ) {
+ $out->addHTML( $prevnext );
+ }
}
}
@@ -282,7 +295,7 @@ class SpecialWhatLinksHere extends SpecialPage {
return Xml::openElement( 'ul', ( $level ? array() : array( 'id' => 'mw-whatlinkshere-list' ) ) );
}
- protected function listItem( $row, $nt, $notClose = false ) {
+ protected function listItem( $row, $nt, $target, $notClose = false ) {
$dirmark = $this->getLanguage()->getDirMark();
# local message cache
@@ -322,13 +335,19 @@ class SpecialWhatLinksHere extends SpecialPage {
$props[] = $msgcache['isimage'];
}
+ wfRunHooks( 'WhatLinksHereProps', array( $row, $nt, $target, &$props ) );
+
if ( count( $props ) ) {
- $propsText = $this->msg( 'parentheses' )->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped();
+ $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'] );
- $wlh = Xml::wrapClass( $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(), 'mw-whatlinkshere-tools' );
+ $wlh = Xml::wrapClass(
+ $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(),
+ 'mw-whatlinkshere-tools'
+ );
return $notClose ?
Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" :
@@ -342,7 +361,7 @@ class SpecialWhatLinksHere extends SpecialPage {
protected function wlhLink( Title $target, $text ) {
static $title = null;
if ( $title === null ) {
- $title = $this->getTitle();
+ $title = $this->getPageTitle();
}
return Linker::linkKnown(
@@ -393,8 +412,6 @@ class SpecialWhatLinksHere extends SpecialPage {
}
function whatlinkshereForm() {
- global $wgScript;
-
// We get nicer value from the title object
$this->opts->consumeValue( 'target' );
// Reset these for new requests
@@ -404,10 +421,10 @@ class SpecialWhatLinksHere extends SpecialPage {
$namespace = $this->opts->consumeValue( 'namespace' );
# Build up the form
- $f = Xml::openElement( 'form', array( 'action' => $wgScript ) );
+ $f = Xml::openElement( 'form', array( 'action' => wfScript() ) );
# Values that should not be forgotten
- $f .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
+ $f .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
$f .= Html::hidden( $name, $value );
}
@@ -416,7 +433,7 @@ class SpecialWhatLinksHere extends SpecialPage {
# Target input
$f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target',
- 'mw-whatlinkshere-target', 40, $target );
+ 'mw-whatlinkshere-target', 40, $target );
$f .= ' ';
@@ -462,7 +479,8 @@ class SpecialWhatLinksHere extends SpecialPage {
$types[] = 'hideimages';
}
- // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans', 'whatlinkshere-hidelinks', 'whatlinkshere-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 );
@@ -471,7 +489,11 @@ class SpecialWhatLinksHere extends SpecialPage {
$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 Xml::fieldset(
+ $this->msg( 'whatlinkshere-filters' )->text(),
+ $this->getLanguage()->pipeList( $links )
+ );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
index 9d23499f..bd014613 100644
--- a/includes/specials/SpecialWithoutinterwiki.php
+++ b/includes/specials/SpecialWithoutinterwiki.php
@@ -41,21 +41,25 @@ class WithoutInterwikiPage extends PageQueryPage {
}
function getPageHeader() {
- global $wgScript;
-
# Do not show useless input form if special page is cached
if ( $this->isCached() ) {
return '';
}
$prefix = $this->prefix;
- $t = $this->getTitle();
+ $t = $this->getPageTitle();
- return Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . "\n" .
+ return Html::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) . "\n" .
Html::openElement( 'fieldset' ) . "\n" .
Html::element( 'legend', null, $this->msg( 'withoutinterwiki-legend' )->text() ) . "\n" .
Html::hidden( 'title', $t->getPrefixedText() ) . "\n" .
- Xml::inputLabel( $this->msg( 'allpagesprefix' )->text(), 'prefix', 'wiprefix', 20, $prefix ) . "\n" .
+ Xml::inputLabel(
+ $this->msg( 'allpagesprefix' )->text(),
+ 'prefix',
+ 'wiprefix',
+ 20,
+ $prefix
+ ) . "\n" .
Xml::submitButton( $this->msg( 'withoutinterwiki-submit' )->text() ) . "\n" .
Html::closeElement( 'fieldset' ) . "\n" .
Html::closeElement( 'form' );
@@ -80,19 +84,23 @@ class WithoutInterwikiPage extends PageQueryPage {
function getQueryInfo() {
$query = array(
'tables' => array( 'page', 'langlinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
- 'conds' => array( 'll_title IS NULL',
- 'page_namespace' => MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0 ),
- 'join_conds' => array( 'langlinks' => array(
- 'LEFT JOIN', 'll_from = page_id' ) )
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
+ 'conds' => array(
+ 'll_title IS NULL',
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ),
+ 'join_conds' => array( 'langlinks' => array( 'LEFT JOIN', 'll_from = page_id' ) )
);
if ( $this->prefix ) {
$dbr = wfGetDB( DB_SLAVE );
$query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
}
+
return $query;
}
diff --git a/includes/templates/NoLocalSettings.php b/includes/templates/NoLocalSettings.php
index 0006df40..5b88dfd1 100644
--- a/includes/templates/NoLocalSettings.php
+++ b/includes/templates/NoLocalSettings.php
@@ -1,4 +1,5 @@
<?php
+// @codingStandardsIgnoreFile
/**
* Template used when there is no LocalSettings.php file.
*
@@ -33,8 +34,8 @@ if ( !isset( $wgVersion ) ) {
$matches = array();
$ext = 'php';
$path = '/';
-foreach( array_filter( explode( '/', $_SERVER['PHP_SELF'] ) ) as $part ) {
- if( !preg_match( '/\.(php5?)$/', $part, $matches ) ) {
+foreach ( array_filter( explode( '/', $_SERVER['PHP_SELF'] ) ) as $part ) {
+ if ( !preg_match( '/\.(php5?)$/', $part, $matches ) ) {
$path .= "$part/";
} else {
$ext = $matches[1] == 'php5' ? 'php5' : 'php';
@@ -71,20 +72,25 @@ if ( !function_exists( 'session_name' ) ) {
</style>
</head>
<body>
- <img src="<?php echo htmlspecialchars( $path ) ?>skins/common/images/mediawiki.png" alt='The MediaWiki logo' />
+ <img src="<?php echo htmlspecialchars( $path ) ?>resources/assets/mediawiki.png" alt='The MediaWiki logo' />
<h1>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></h1>
<div class='error'>
- <p>LocalSettings.php not found.</p>
- <p>
- <?php
- if ( $installerStarted ) {
- echo "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\"> complete the installation</a> and download LocalSettings.php.";
- } else {
- echo "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\"> set up the wiki</a> first.";
- }
- ?>
- </p>
+ <?php if ( !file_exists( MW_CONFIG_FILE ) ) { ?>
+ <p>LocalSettings.php not found.</p>
+ <p>
+ <?php
+ if ( $installerStarted ) {
+ echo "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\">complete the installation</a> and download LocalSettings.php.";
+ } else {
+ echo "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\">set up the wiki</a> first.";
+ }
+ ?>
+ </p>
+ <?php } else { ?>
+ <p>LocalSettings.php not readable.</p>
+ <p>Please correct file permissions and try again.</p>
+ <?php } ?>
</div>
</body>
diff --git a/includes/templates/Usercreate.php b/includes/templates/Usercreate.php
index fcd492cb..01da0bd7 100644
--- a/includes/templates/Usercreate.php
+++ b/includes/templates/Usercreate.php
@@ -1,4 +1,5 @@
<?php
+// @codingStandardsIgnoreFile
/**
* Html form for account creation (since 1.22 with VForm appearance).
*
@@ -22,7 +23,6 @@
*/
class UsercreateTemplate extends BaseTemplate {
-
/**
* Extensions (AntiSpoof and TitleBlacklist) call this in response to
* UserCreateForm hook to add checkboxes to the create account form.
@@ -51,24 +51,29 @@ class UsercreateTemplate extends BaseTemplate {
<div id="signupstart"><?php $this->msgWiki( 'signupstart' ); ?></div>
<?php } ?>
<div id="userloginForm">
- <h2 class="createaccount-join">
- <?php $this->msg( $this->data['loggedin'] ? 'createacct-another-join' : 'createacct-join' ); ?>
- </h2>
<form name="userlogin2" id="userlogin2" class="mw-ui-vform" method="post" action="<?php $this->text( 'action' ); ?>">
<section class="mw-form-header">
<?php $this->html( 'header' ); /* extensions such as ConfirmEdit add form HTML here */ ?>
</section>
+ <!-- This element is used by the mediawiki.special.userlogin.signup.js module. -->
+ <div
+ id="mw-createacct-status-area"
+ <?php if ( $this->data['message'] ) { ?>
+ class="<?php echo $this->data['messagetype']; ?>box"
+ <?php } else { ?>
+ style="display: none;"
+ <?php } ?>
+ >
<?php if ( $this->data['message'] ) { ?>
- <div class="<?php $this->text( 'messagetype' ); ?>box">
<?php if ( $this->data['messagetype'] == 'error' ) { ?>
<strong><?php $this->msg( 'createacct-error' ); ?></strong>
<br />
<?php } ?>
<?php $this->html( 'message' ); ?>
- </div>
<?php } ?>
+ </div>
- <div>
+ <div class="mw-ui-vform-field">
<label for='wpName2'>
<?php $this->msg( 'userlogin-yourname' ); ?>
@@ -76,7 +81,7 @@ class UsercreateTemplate extends BaseTemplate {
</label>
<?php
echo Html::input( 'wpName', $this->data['name'], 'text', array(
- 'class' => 'mw-input loginText',
+ 'class' => 'mw-ui-input loginText',
'id' => 'wpName2',
'tabindex' => '1',
'size' => '20',
@@ -87,24 +92,25 @@ class UsercreateTemplate extends BaseTemplate {
?>
</div>
- <div>
+ <div class="mw-ui-vform-field">
<?php if ( $this->data['createemail'] ) { ?>
- <label class="mw-ui-checkbox-label">
+ <div class="mw-ui-checkbox">
<input name="wpCreateaccountMail" type="checkbox" value="1" id="wpCreateaccountMail" tabindex="2"
<?php if ( $this->data['createemailset'] ) {
echo 'checked="checked"';
} ?>
- >
- <?php $this->msg( 'createaccountmail' ); ?>
- </label>
+ ><label for="wpCreateaccountMail">
+ <?php $this->msg( 'createaccountmail' ); ?>
+ </label>
+ </div>
<?php } ?>
</div>
- <div class="mw-row-password">
+ <div class="mw-ui-vform-field mw-row-password">
<label for='wpPassword2'><?php $this->msg( 'userlogin-yourpassword' ); ?></label>
<?php
echo Html::input( 'wpPassword', null, 'password', array(
- 'class' => 'mw-input loginPassword',
+ 'class' => 'mw-ui-input loginPassword',
'id' => 'wpPassword2',
'tabindex' => '3',
'size' => '20',
@@ -116,26 +122,25 @@ class UsercreateTemplate extends BaseTemplate {
<?php
if ( $this->data['usedomain'] ) {
- $doms = "";
+ $select = new XmlSelect( 'wpDomain', false, $this->data['domain'] );
+ $select->setAttribute( 'tabindex', 4 );
foreach ( $this->data['domainnames'] as $dom ) {
- $doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
+ $select->addOption( $dom );
}
?>
- <div id="mw-user-domain-section">
+ <div class="mw-ui-vform-field" id="mw-user-domain-section">
<label for="wpDomain"><?php $this->msg( 'yourdomainname' ); ?></label>
- <div class="mw-input">
- <select name="wpDomain" value="<?php $this->text( 'domain' ); ?>" tabindex="4">
- <?php echo $doms ?>
- </select>
+ <div>
+ <?php echo $select->getHTML(); ?>
</div>
</div>
<?php } ?>
- <div class="mw-row-password">
+ <div class="mw-ui-vform-field mw-row-password">
<label for='wpRetype'><?php $this->msg( 'createacct-yourpasswordagain' ); ?></label>
<?php
echo Html::input( 'wpRetype', null, 'password', array(
- 'class' => 'mw-input loginPassword',
+ 'class' => 'mw-ui-input loginPassword',
'id' => 'wpRetype',
'tabindex' => '5',
'size' => '20',
@@ -145,7 +150,7 @@ class UsercreateTemplate extends BaseTemplate {
?>
</div>
- <div>
+ <div class="mw-ui-vform-field">
<?php if ( $this->data['useemail'] ) { ?>
<label for='wpEmail'>
<?php
@@ -157,7 +162,7 @@ class UsercreateTemplate extends BaseTemplate {
</label>
<?php
echo Html::input( 'wpEmail', $this->data['email'], 'email', array(
- 'class' => 'mw-input loginText',
+ 'class' => 'mw-ui-input loginText',
'id' => 'wpEmail',
'tabindex' => '6',
'size' => '20',
@@ -170,9 +175,9 @@ class UsercreateTemplate extends BaseTemplate {
</div>
<?php if ( $this->data['userealname'] ) { ?>
- <div>
+ <div class="mw-ui-vform-field">
<label for='wpRealName'><?php $this->msg( 'createacct-realname' ); ?></label>
- <input type='text' class='mw-input loginText' name="wpRealName" id="wpRealName"
+ <input type='text' class='mw-ui-input loginText' name="wpRealName" id="wpRealName"
tabindex="7"
value="<?php $this->text( 'realname' ); ?>" size='20' />
<div class="prefsectiontip">
@@ -182,10 +187,10 @@ class UsercreateTemplate extends BaseTemplate {
<?php } ?>
<?php if ( $this->data['usereason'] ) { ?>
- <div>
+ <div class="mw-ui-vform-field">
<label for='wpReason'><?php $this->msg( 'createacct-reason' ); ?></label>
<?php echo Html::input( 'wpReason', $this->data['reason'], 'text', array(
- 'class' => 'mw-input loginText',
+ 'class' => 'mw-ui-input loginText',
'id' => 'wpReason',
'tabindex' => '8',
'size' => '20',
@@ -198,12 +203,12 @@ class UsercreateTemplate extends BaseTemplate {
$tabIndex = 9;
if ( isset( $this->data['extraInput'] ) && is_array( $this->data['extraInput'] ) ) {
foreach ( $this->data['extraInput'] as $inputItem ) { ?>
- <div>
+ <div class="mw-ui-vform-field">
<?php
// If it's a checkbox, output the whole thing (assume it has a msg).
if ( $inputItem['type'] == 'checkbox' ) {
?>
- <label class="mw-ui-checkbox-label">
+ <div class="mw-ui-checkbox">
<input
name="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
id="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
@@ -212,9 +217,8 @@ class UsercreateTemplate extends BaseTemplate {
<?php if ( !empty( $inputItem['value'] ) ) {
echo 'checked="checked"';
} ?>
- >
- <?php $this->msg( $inputItem['msg'] ); ?>
- </label>
+ ><label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"></label>
+ </div><?php $this->msgHtml( $inputItem['msg'] ); ?>
<?php
} else {
// Not a checkbox.
@@ -227,7 +231,7 @@ class UsercreateTemplate extends BaseTemplate {
<?php } ?>
<input
type="<?php echo htmlspecialchars( $inputItem['type'] ); ?>"
- class="mw-input"
+ class="mw-ui-input"
name="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
tabindex="<?php echo $tabIndex++; ?>"
value="<?php echo htmlspecialchars( $inputItem['value'] ); ?>"
@@ -248,14 +252,14 @@ class UsercreateTemplate extends BaseTemplate {
// so skip one index.
$tabIndex++;
?>
- <div class="mw-submit">
+ <div class="mw-ui-vform-field mw-submit">
<?php
echo Html::input(
'wpCreateaccount',
$this->getMsg( $this->data['loggedin'] ? 'createacct-another-submit' : 'createacct-submit' ),
'submit',
array(
- 'class' => "mw-ui-button mw-ui-big mw-ui-block mw-ui-primary",
+ 'class' => "mw-ui-button mw-ui-big mw-ui-block mw-ui-constructive",
'id' => 'wpCreateaccount',
'tabindex' => $tabIndex++
)
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index 9aedd3c7..8bba4265 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -1,4 +1,5 @@
<?php
+// @codingStandardsIgnoreFile
/**
* Html form for user login (since 1.22 with VForm appearance).
*
@@ -28,6 +29,7 @@ class UserloginTemplate extends BaseTemplate {
$expirationDays = ceil( $wgCookieExpiration / ( 3600 * 24 ) );
?>
<div class="mw-ui-container">
+ <div id="userloginprompt"><?php $this->msgWiki('loginprompt') ?></div>
<?php if ( $this->haveData( 'languages' ) ) { ?>
<div id="languagelinks">
<p><?php $this->html( 'languages' ); ?></p>
@@ -54,7 +56,7 @@ class UserloginTemplate extends BaseTemplate {
</div>
<?php } ?>
- <div>
+ <div class="mw-ui-vform-field">
<label for='wpName1'>
<?php
$this->msg( 'userlogin-yourname' );
@@ -70,7 +72,7 @@ class UserloginTemplate extends BaseTemplate {
<?php
$extraAttrs = array();
echo Html::input( 'wpName', $this->data['name'], 'text', array(
- 'class' => 'loginText',
+ 'class' => 'loginText mw-ui-input',
'id' => 'wpName1',
'tabindex' => '1',
'size' => '20',
@@ -84,7 +86,7 @@ class UserloginTemplate extends BaseTemplate {
?>
</div>
- <div>
+ <div class="mw-ui-vform-field">
<label for='wpPassword1'>
<?php
$this->msg( 'userlogin-yourpassword' );
@@ -100,7 +102,7 @@ class UserloginTemplate extends BaseTemplate {
</label>
<?php
echo Html::input( 'wpPassword', null, 'password', array(
- 'class' => 'loginPassword',
+ 'class' => 'loginPassword mw-ui-input',
'id' => 'wpPassword1',
'tabindex' => '2',
'size' => '20',
@@ -113,16 +115,15 @@ class UserloginTemplate extends BaseTemplate {
<?php
if ( isset( $this->data['usedomain'] ) && $this->data['usedomain'] ) {
- $doms = "";
+ $select = new XmlSelect( 'wpDomain', false, $this->data['domain'] );
+ $select->setAttribute( 'tabindex', 3 );
foreach ( $this->data['domainnames'] as $dom ) {
- $doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
+ $select->addOption( $dom );
}
?>
- <div id="mw-user-domain-section">
+ <div class="mw-ui-vform-field" id="mw-user-domain-section">
<label for='wpDomain'><?php $this->msg( 'yourdomainname' ); ?></label>
- <select name="wpDomain" value="<?php $this->text( 'domain' ); ?>" tabindex="3">
- <?php echo $doms; ?>
- </select>
+ <?php echo $select->getHTML(); ?>
</div>
<?php } ?>
@@ -132,29 +133,30 @@ class UserloginTemplate extends BaseTemplate {
}
?>
- <div>
+ <div class="mw-ui-vform-field">
<?php if ( $this->data['canremember'] ) { ?>
- <label class="mw-ui-checkbox-label">
+ <div class="mw-ui-checkbox">
<input name="wpRemember" type="checkbox" value="1" id="wpRemember" tabindex="4"
<?php if ( $this->data['remember'] ) {
echo 'checked="checked"';
} ?>
- >
- <?php echo $this->getMsg( 'userlogin-remembermypassword' )->numParams( $expirationDays )->escaped(); ?>
- </label>
+ ><label for="wpRemember">
+ <?php echo $this->getMsg( 'userlogin-remembermypassword' )->numParams( $expirationDays )->escaped(); ?></label>
+ </div>
<?php } ?>
</div>
- <div>
+ <div class="mw-ui-vform-field">
<?php
- echo Html::input( 'wpLoginAttempt', $this->getMsg( 'login' )->text(), 'submit', array(
+ echo Html::input( 'wpLoginAttempt', $this->getMsg( 'pt-login-button' )->text(), 'submit', array(
'id' => 'wpLoginAttempt',
'tabindex' => '6',
- 'class' => 'mw-ui-button mw-ui-big mw-ui-block mw-ui-primary'
+ 'class' => 'mw-ui-button mw-ui-big mw-ui-block mw-ui-constructive'
) );
?>
</div>
- <div id="mw-userlogin-help">
+
+ <div class="mw-ui-vform-field" id="mw-userlogin-help">
<?php
echo Html::element(
'a',
@@ -167,14 +169,15 @@ class UserloginTemplate extends BaseTemplate {
);
?>
</div>
+
<?php if ( $this->haveData( 'createOrLoginHref' ) ) { ?>
<?php if ( $this->data['loggedin'] ) { ?>
<div id="mw-createaccount-another">
- <h3 id="mw-userloginlink"><a href="<?php $this->text( 'createOrLoginHref' ); ?>" id="mw-createaccount-join" tabindex="7" class="mw-ui-button"><?php $this->msg( 'userlogin-createanother' ); ?></a></h3>
+ <a href="<?php $this->text( 'createOrLoginHref' ); ?>" id="mw-createaccount-join" tabindex="7" class="mw-ui-button"><?php $this->msg( 'userlogin-createanother' ); ?></a>
</div>
<?php } else { ?>
<div id="mw-createaccount-cta">
- <h3 id="mw-userloginlink"><?php $this->msg( 'userlogin-noaccount' ); ?><a href="<?php $this->text( 'createOrLoginHref' ); ?>" id="mw-createaccount-join" tabindex="7" class="mw-ui-button mw-ui-constructive"><?php $this->msg( 'userlogin-joinproject' ); ?></a></h3>
+ <?php $this->msg( 'userlogin-noaccount' ); ?><a href="<?php $this->text( 'createOrLoginHref' ); ?>" id="mw-createaccount-join" tabindex="7" class="mw-ui-button mw-ui-progressive"><?php $this->msg( 'userlogin-joinproject' ); ?></a>
</div>
<?php } ?>
<?php } ?>
diff --git a/includes/title/MalformedTitleException.php b/includes/title/MalformedTitleException.php
new file mode 100644
index 00000000..a8a5d754
--- /dev/null
+++ b/includes/title/MalformedTitleException.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Representation of a page title within %MediaWiki.
+ *
+ * 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
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+
+/**
+ * MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+class MalformedTitleException extends Exception {
+}
diff --git a/includes/title/MediaWikiPageLinkRenderer.php b/includes/title/MediaWikiPageLinkRenderer.php
new file mode 100644
index 00000000..f46cb5e3
--- /dev/null
+++ b/includes/title/MediaWikiPageLinkRenderer.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * A service for generating links from page titles
+ *
+ * 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
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+
+/**
+ * A service for generating links from page titles.
+ *
+ * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ */
+class MediaWikiPageLinkRenderer implements PageLinkRenderer {
+ /**
+ * @var TitleFormatter
+ */
+ protected $formatter;
+
+ /**
+ * @var string
+ */
+ protected $baseUrl;
+
+ /**
+ * @note $formatter and $baseUrl are currently not used for generating links,
+ * since we still rely on the Linker class to generate the actual HTML.
+ * Once this is reversed so that Linker becomes a legacy interface to
+ * HtmlPageLinkRenderer, we will be using them, so it seems prudent to
+ * already declare the dependency and inject them.
+ *
+ * @param TitleFormatter $formatter Formatter for generating the target title string
+ * @param string $baseUrl (currently unused, pending refactoring of Linker).
+ * Defaults to $wgArticlePath.
+ */
+ public function __construct( TitleFormatter $formatter, $baseUrl = null ) {
+ if ( $baseUrl === null ) {
+ $baseUrl = $GLOBALS['wgArticlePath'];
+ }
+
+ $this->formatter = $formatter;
+ $this->baseUrl = $baseUrl;
+ }
+
+ /**
+ * Returns the (partial) URL for the given page (including any section identifier).
+ *
+ * @param TitleValue $page The link's target
+ * @param array $params Any additional URL parameters.
+ *
+ * @return string
+ */
+ public function getPageUrl( TitleValue $page, $params = array() ) {
+ //TODO: move the code from Linker::linkUrl here!
+ //The below is just a rough estimation!
+
+ $name = $this->formatter->getPrefixedText( $page );
+ $name = str_replace( ' ', '_', $name );
+ $name = wfUrlencode( $name );
+
+ $url = $this->baseUrl . $name;
+
+ if ( $params ) {
+ $separator = ( strpos( $url, '?' ) ) ? '&' : '?';
+ $url .= $separator . wfArrayToCgi( $params );
+ }
+
+ $fragment = $page->getFragment();
+ if ( $fragment !== '' ) {
+ $url = $url . '#' . wfUrlencode( $fragment );
+ }
+
+ return $url;
+ }
+
+ /**
+ * Returns an HTML link to the given page, using the given surface text.
+ *
+ * @param TitleValue $page The link's target
+ * @param string $text The link's surface text (will be derived from $page if not given).
+ *
+ * @return string
+ */
+ public function renderHtmlLink( TitleValue $page, $text = null ) {
+ if ( $text === null ) {
+ $text = $this->formatter->getFullText( $page );
+ }
+
+ // TODO: move the logic implemented by Linker here,
+ // using $this->formatter and $this->baseUrl, and
+ // re-implement Linker to use a HtmlPageLinkRenderer.
+ $title = Title::newFromTitleValue( $page );
+ $link = Linker::link( $title, htmlspecialchars( $text ) );
+
+ return $link;
+ }
+
+ /**
+ * Returns a wikitext link to the given page, using the given surface text.
+ *
+ * @param TitleValue $page The link's target
+ * @param string $text The link's surface text (will be derived from $page if not given).
+ *
+ * @return string
+ */
+ public function renderWikitextLink( TitleValue $page, $text = null ) {
+ if ( $text === null ) {
+ $text = $this->formatter->getFullText( $page );
+ }
+
+ $name = $this->formatter->getFullText( $page );
+
+ return '[[:' . $name . '|' . wfEscapeWikiText( $text ) . ']]';
+ }
+}
diff --git a/includes/title/MediaWikiTitleCodec.php b/includes/title/MediaWikiTitleCodec.php
new file mode 100644
index 00000000..6ca0799c
--- /dev/null
+++ b/includes/title/MediaWikiTitleCodec.php
@@ -0,0 +1,400 @@
+<?php
+/**
+ * A codec for %MediaWiki page titles.
+ *
+ * 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
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+
+/**
+ * A codec for %MediaWiki page titles.
+ *
+ * @note Normalization and validation is applied while parsing, not when formatting.
+ * It's possible to construct a TitleValue with an invalid title, and use MediaWikiTitleCodec
+ * to generate an (invalid) title string from it. TitleValues should be constructed only
+ * via parseTitle() or from a (semi)trusted source, such as the database.
+ *
+ * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ */
+class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
+ /**
+ * @var Language
+ */
+ protected $language;
+
+ /**
+ * @var GenderCache
+ */
+ protected $genderCache;
+
+ /**
+ * @var string[]
+ */
+ protected $localInterwikis;
+
+ /**
+ * @param Language $language The language object to use for localizing namespace names.
+ * @param GenderCache $genderCache The gender cache for generating gendered namespace names
+ * @param string[]|string $localInterwikis
+ */
+ public function __construct( Language $language, GenderCache $genderCache,
+ $localInterwikis = array()
+ ) {
+ $this->language = $language;
+ $this->genderCache = $genderCache;
+ $this->localInterwikis = (array)$localInterwikis;
+ }
+
+ /**
+ * @see TitleFormatter::getNamespaceName()
+ *
+ * @param int $namespace
+ * @param string $text
+ *
+ * @throws InvalidArgumentException If the namespace is invalid
+ * @return string
+ */
+ public function getNamespaceName( $namespace, $text ) {
+ if ( $this->language->needsGenderDistinction() &&
+ MWNamespace::hasGenderDistinction( $namespace )
+ ) {
+
+ //NOTE: we are assuming here that the title text is a user name!
+ $gender = $this->genderCache->getGenderOf( $text, __METHOD__ );
+ $name = $this->language->getGenderNsText( $namespace, $gender );
+ } else {
+ $name = $this->language->getNsText( $namespace );
+ }
+
+ if ( $name === false ) {
+ throw new InvalidArgumentException( 'Unknown namespace ID: ' . $namespace );
+ }
+
+ return $name;
+ }
+
+ /**
+ * @see TitleFormatter::formatTitle()
+ *
+ * @param int|bool $namespace The namespace ID (or false, if the namespace should be ignored)
+ * @param string $text The page title. Should be valid. Only minimal normalization is applied.
+ * Underscores will be replaced.
+ * @param string $fragment The fragment name (may be empty).
+ *
+ * @throws InvalidArgumentException If the namespace is invalid
+ * @return string
+ */
+ public function formatTitle( $namespace, $text, $fragment = '' ) {
+ if ( $namespace !== false ) {
+ $namespace = $this->getNamespaceName( $namespace, $text );
+
+ if ( $namespace !== '' ) {
+ $text = $namespace . ':' . $text;
+ }
+ }
+
+ if ( $fragment !== '' ) {
+ $text = $text . '#' . $fragment;
+ }
+
+ $text = str_replace( '_', ' ', $text );
+
+ return $text;
+ }
+
+ /**
+ * Parses the given text and constructs a TitleValue. Normalization
+ * is applied according to the rules appropriate for the form specified by $form.
+ *
+ * @param string $text The text to parse
+ * @param int $defaultNamespace Namespace to assume per default (usually NS_MAIN)
+ *
+ * @throws MalformedTitleException
+ * @return TitleValue
+ */
+ public function parseTitle( $text, $defaultNamespace ) {
+ // NOTE: this is an ugly cludge that allows this class to share the
+ // code for parsing with the old Title class. The parser code should
+ // be refactored to avoid this.
+ $parts = $this->splitTitleString( $text, $defaultNamespace );
+
+ // Interwiki links are not supported by TitleValue
+ if ( $parts['interwiki'] !== '' ) {
+ throw new MalformedTitleException( 'Title must not contain an interwiki prefix: ' . $text );
+ }
+
+ // Relative fragment links are not supported by TitleValue
+ if ( $parts['dbkey'] === '' ) {
+ throw new MalformedTitleException( 'Title must not be empty: ' . $text );
+ }
+
+ return new TitleValue( $parts['namespace'], $parts['dbkey'], $parts['fragment'] );
+ }
+
+ /**
+ * @see TitleFormatter::getText()
+ *
+ * @param TitleValue $title
+ *
+ * @return string $title->getText()
+ */
+ public function getText( TitleValue $title ) {
+ return $this->formatTitle( false, $title->getText(), '' );
+ }
+
+ /**
+ * @see TitleFormatter::getText()
+ *
+ * @param TitleValue $title
+ *
+ * @return string
+ */
+ public function getPrefixedText( TitleValue $title ) {
+ return $this->formatTitle( $title->getNamespace(), $title->getText(), '' );
+ }
+
+ /**
+ * @see TitleFormatter::getText()
+ *
+ * @param TitleValue $title
+ *
+ * @return string
+ */
+ public function getFullText( TitleValue $title ) {
+ return $this->formatTitle( $title->getNamespace(), $title->getText(), $title->getFragment() );
+ }
+
+ /**
+ * Normalizes and splits a title string.
+ *
+ * This function removes illegal characters, splits off the interwiki and
+ * namespace prefixes, sets the other forms, and canonicalizes
+ * everything.
+ *
+ * @todo this method is only exposed as a temporary measure to ease refactoring.
+ * It was copied with minimal changes from Title::secureAndSplit().
+ *
+ * @todo This method should be split up and an appropriate interface
+ * defined for use by the Title class.
+ *
+ * @param string $text
+ * @param int $defaultNamespace
+ *
+ * @throws MalformedTitleException If $text is not a valid title string.
+ * @return array A mapp with the fields 'interwiki', 'fragment', 'namespace',
+ * 'user_case_dbkey', and 'dbkey'.
+ */
+ public function splitTitleString( $text, $defaultNamespace = NS_MAIN ) {
+ $dbkey = str_replace( ' ', '_', $text );
+
+ # Initialisation
+ $parts = array(
+ 'interwiki' => '',
+ 'local_interwiki' => false,
+ 'fragment' => '',
+ 'namespace' => $defaultNamespace,
+ 'dbkey' => $dbkey,
+ 'user_case_dbkey' => $dbkey,
+ );
+
+ # Strip Unicode bidi override characters.
+ # Sometimes they slip into cut-n-pasted page titles, where the
+ # override chars get included in list displays.
+ $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
+
+ # Clean up whitespace
+ # Note: use of the /u option on preg_replace here will cause
+ # input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
+ # conveniently disabling them.
+ $dbkey = preg_replace(
+ '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u',
+ '_',
+ $dbkey
+ );
+ $dbkey = trim( $dbkey, '_' );
+
+ if ( strpos( $dbkey, UTF8_REPLACEMENT ) !== false ) {
+ # Contained illegal UTF-8 sequences or forbidden Unicode chars.
+ throw new MalformedTitleException( 'Bad UTF-8 sequences found in title: ' . $text );
+ }
+
+ $parts['dbkey'] = $dbkey;
+
+ # Initial colon indicates main namespace rather than specified default
+ # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
+ if ( $dbkey !== '' && ':' == $dbkey[0] ) {
+ $parts['namespace'] = NS_MAIN;
+ $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
+ $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
+ }
+
+ if ( $dbkey == '' ) {
+ throw new MalformedTitleException( 'Empty title: ' . $text );
+ }
+
+ # Namespace or interwiki prefix
+ $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
+ do {
+ $m = array();
+ if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
+ $p = $m[1];
+ if ( ( $ns = $this->language->getNsIndex( $p ) ) !== false ) {
+ # Ordinary namespace
+ $dbkey = $m[2];
+ $parts['namespace'] = $ns;
+ # For Talk:X pages, check if X has a "namespace" prefix
+ if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
+ if ( $this->language->getNsIndex( $x[1] ) ) {
+ # Disallow Talk:File:x type titles...
+ throw new MalformedTitleException( 'Bad namespace prefix: ' . $text );
+ } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
+ //TODO: get rid of global state!
+ # Disallow Talk:Interwiki:x type titles...
+ throw new MalformedTitleException( 'Interwiki prefix found in title: ' . $text );
+ }
+ }
+ } elseif ( Interwiki::isValidInterwiki( $p ) ) {
+ # Interwiki link
+ $dbkey = $m[2];
+ $parts['interwiki'] = $this->language->lc( $p );
+
+ # Redundant interwiki prefix to the local wiki
+ foreach ( $this->localInterwikis as $localIW ) {
+ if ( 0 == strcasecmp( $parts['interwiki'], $localIW ) ) {
+ if ( $dbkey == '' ) {
+ # Empty self-links should point to the Main Page, to ensure
+ # compatibility with cross-wiki transclusions and the like.
+ $mainPage = Title::newMainPage();
+ return array(
+ 'interwiki' => $mainPage->getInterwiki(),
+ 'local_interwiki' => true,
+ 'fragment' => $mainPage->getFragment(),
+ 'namespace' => $mainPage->getNamespace(),
+ 'dbkey' => $mainPage->getDBkey(),
+ 'user_case_dbkey' => $mainPage->getUserCaseDBKey()
+ );
+ }
+ $parts['interwiki'] = '';
+ # local interwikis should behave like initial-colon links
+ $parts['local_interwiki'] = true;
+
+ # Do another namespace split...
+ continue 2;
+ }
+ }
+
+ # If there's an initial colon after the interwiki, that also
+ # resets the default namespace
+ if ( $dbkey !== '' && $dbkey[0] == ':' ) {
+ $parts['namespace'] = NS_MAIN;
+ $dbkey = substr( $dbkey, 1 );
+ }
+ }
+ # If there's no recognized interwiki or namespace,
+ # then let the colon expression be part of the title.
+ }
+ break;
+ } while ( true );
+
+ $fragment = strstr( $dbkey, '#' );
+ if ( false !== $fragment ) {
+ $parts['fragment'] = str_replace( '_', ' ', substr( $fragment, 1 ) );
+ $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
+ # remove whitespace again: prevents "Foo_bar_#"
+ # becoming "Foo_bar_"
+ $dbkey = preg_replace( '/_*$/', '', $dbkey );
+ }
+
+ # Reject illegal characters.
+ $rxTc = Title::getTitleInvalidRegex();
+ if ( preg_match( $rxTc, $dbkey ) ) {
+ throw new MalformedTitleException( 'Illegal characters found in title: ' . $text );
+ }
+
+ # Pages with "/./" or "/../" appearing in the URLs will often be un-
+ # reachable due to the way web browsers deal with 'relative' URLs.
+ # Also, they conflict with subpage syntax. Forbid them explicitly.
+ if (
+ strpos( $dbkey, '.' ) !== false &&
+ (
+ $dbkey === '.' || $dbkey === '..' ||
+ strpos( $dbkey, './' ) === 0 ||
+ strpos( $dbkey, '../' ) === 0 ||
+ strpos( $dbkey, '/./' ) !== false ||
+ strpos( $dbkey, '/../' ) !== false ||
+ substr( $dbkey, -2 ) == '/.' ||
+ substr( $dbkey, -3 ) == '/..'
+ )
+ ) {
+ throw new MalformedTitleException( 'Bad title: ' . $text );
+ }
+
+ # Magic tilde sequences? Nu-uh!
+ if ( strpos( $dbkey, '~~~' ) !== false ) {
+ throw new MalformedTitleException( 'Bad title: ' . $text );
+ }
+
+ # Limit the size of titles to 255 bytes. This is typically the size of the
+ # underlying database field. We make an exception for special pages, which
+ # don't need to be stored in the database, and may edge over 255 bytes due
+ # to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
+ if (
+ ( $parts['namespace'] != NS_SPECIAL && strlen( $dbkey ) > 255 )
+ || strlen( $dbkey ) > 512
+ ) {
+ throw new MalformedTitleException( 'Title too long: ' . substr( $dbkey, 0, 255 ) . '...' );
+ }
+
+ # Normally, all wiki links are forced to have an initial capital letter so [[foo]]
+ # and [[Foo]] point to the same place. Don't force it for interwikis, since the
+ # other site might be case-sensitive.
+ $parts['user_case_dbkey'] = $dbkey;
+ if ( $parts['interwiki'] === '' ) {
+ $dbkey = Title::capitalize( $dbkey, $parts['namespace'] );
+ }
+
+ # Can't make a link to a namespace alone... "empty" local links can only be
+ # self-links with a fragment identifier.
+ if ( $dbkey == '' && $parts['interwiki'] === '' ) {
+ if ( $parts['namespace'] != NS_MAIN ) {
+ throw new MalformedTitleException( 'Empty title: ' . $text );
+ }
+ }
+
+ // Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles.
+ // IP names are not allowed for accounts, and can only be referring to
+ // edits from the IP. Given '::' abbreviations and caps/lowercaps,
+ // there are numerous ways to present the same IP. Having sp:contribs scan
+ // them all is silly and having some show the edits and others not is
+ // inconsistent. Same for talk/userpages. Keep them normalized instead.
+ if ( $parts['namespace'] == NS_USER || $parts['namespace'] == NS_USER_TALK ) {
+ $dbkey = IP::sanitizeIP( $dbkey );
+ }
+
+ // Any remaining initial :s are illegal.
+ if ( $dbkey !== '' && ':' == $dbkey[0] ) {
+ throw new MalformedTitleException( 'Title must not start with a colon: ' . $text );
+ }
+
+ # Fill fields
+ $parts['dbkey'] = $dbkey;
+
+ return $parts;
+ }
+}
diff --git a/includes/title/PageLinkRenderer.php b/includes/title/PageLinkRenderer.php
new file mode 100644
index 00000000..fb1096e0
--- /dev/null
+++ b/includes/title/PageLinkRenderer.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Represents a link rendering service for %MediaWiki.
+ *
+ * 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
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Represents a link rendering service for %MediaWiki.
+ *
+ * This is designed to encapsulate the knowledge about how page titles map to
+ * URLs, and how links are encoded in a given output format.
+ *
+ * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ */
+interface PageLinkRenderer {
+ /**
+ * Returns the URL for the given page.
+ *
+ * @todo expand this to cover the functionality of Linker::linkUrl
+ *
+ * @param TitleValue $page The link's target
+ * @param array $params Any additional URL parameters.
+ *
+ * @return string
+ */
+ public function getPageUrl( TitleValue $page, $params = array() );
+
+ /**
+ * Returns an HTML link to the given page, using the given surface text.
+ *
+ * @todo expand this to cover the functionality of Linker::link
+ *
+ * @param TitleValue $page The link's target
+ * @param string $text The link's surface text (will be derived from $page if not given).
+ *
+ * @return string
+ */
+ public function renderHtmlLink( TitleValue $page, $text = null );
+
+ /**
+ * Returns a wikitext link to the given page, using the given surface text.
+ *
+ * @param TitleValue $page The link's target
+ * @param string $text The link's surface text (will be derived from $page if not given).
+ *
+ * @return string
+ */
+ public function renderWikitextLink( TitleValue $page, $text = null );
+}
diff --git a/includes/title/TitleFormatter.php b/includes/title/TitleFormatter.php
new file mode 100644
index 00000000..7c71ef5e
--- /dev/null
+++ b/includes/title/TitleFormatter.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * A title formatter service for %MediaWiki.
+ *
+ * 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
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+
+/**
+ * A title formatter service for MediaWiki.
+ *
+ * This is designed to encapsulate knowledge about conventions for the title
+ * forms to be used in the database, in urls, in wikitext, etc.
+ *
+ * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ */
+interface TitleFormatter {
+ /**
+ * Returns the title formatted for display.
+ * Per default, this includes the namespace but not the fragment.
+ *
+ * @note Normalization is applied if $title is not in TitleValue::TITLE_FORM.
+ *
+ * @param int|bool $namespace The namespace ID (or false, if the namespace should be ignored)
+ * @param string $text The page title
+ * @param string $fragment The fragment name (may be empty).
+ *
+ * @return string
+ */
+ public function formatTitle( $namespace, $text, $fragment = '' );
+
+ /**
+ * Returns the title text formatted for display, without namespace of fragment.
+ *
+ * @note Only minimal normalization is applied. Consider using TitleValue::getText() directly.
+ *
+ * @param TitleValue $title The title to format
+ *
+ * @return string
+ */
+ public function getText( TitleValue $title );
+
+ /**
+ * Returns the title formatted for display, including the namespace name.
+ *
+ * @param TitleValue $title The title to format
+ *
+ * @return string
+ */
+ public function getPrefixedText( TitleValue $title );
+
+ /**
+ * Returns the title formatted for display, with namespace and fragment.
+ *
+ * @param TitleValue $title The title to format
+ *
+ * @return string
+ */
+ public function getFullText( TitleValue $title );
+
+ /**
+ * Returns the name of the namespace for the given title.
+ *
+ * @note This must take into account gender sensitive namespace names.
+ * @todo Move this to a separate interface
+ *
+ * @param int $namespace
+ * @param string $text
+ *
+ * @throws InvalidArgumentException
+ * @return string
+ */
+ public function getNamespaceName( $namespace, $text );
+}
diff --git a/includes/title/TitleParser.php b/includes/title/TitleParser.php
new file mode 100644
index 00000000..0635ee86
--- /dev/null
+++ b/includes/title/TitleParser.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * A title parser service for %MediaWiki.
+ *
+ * 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
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+
+/**
+ * A title parser service for %MediaWiki.
+ *
+ * This is designed to encapsulate knowledge about conventions for the title
+ * forms to be used in the database, in urls, in wikitext, etc.
+ *
+ * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ */
+interface TitleParser {
+ /**
+ * Parses the given text and constructs a TitleValue. Normalization
+ * is applied according to the rules appropriate for the form specified by $form.
+ *
+ * @note this only parses local page links, interwiki-prefixes etc. are not considered!
+ *
+ * @param string $text The text to parse
+ * @param int $defaultNamespace Namespace to assume per default (usually NS_MAIN)
+ *
+ * @throws MalformedTitleException If the text is not a valid representation of a page title.
+ * @return TitleValue
+ */
+ public function parseTitle( $text, $defaultNamespace );
+}
diff --git a/includes/title/TitleValue.php b/includes/title/TitleValue.php
new file mode 100644
index 00000000..402247c2
--- /dev/null
+++ b/includes/title/TitleValue.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Representation of a page title within %MediaWiki.
+ *
+ * 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
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Represents a page (or page fragment) title within %MediaWiki.
+ *
+ * @note In contrast to Title, this is designed to be a plain value object. That is,
+ * it is immutable, does not use global state, and causes no side effects.
+ *
+ * @note TitleValue represents the title of a local page (or fragment of a page).
+ * It does not represent a link, and does not support interwiki prefixes etc.
+ *
+ * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ */
+class TitleValue {
+ /**
+ * @var int
+ */
+ protected $namespace;
+
+ /**
+ * @var string
+ */
+ protected $dbkey;
+
+ /**
+ * @var string
+ */
+ protected $fragment;
+
+ /**
+ * Constructs a TitleValue.
+ *
+ * @note TitleValue expects a valid DB key; typically, a TitleValue is constructed either
+ * from a database entry, or by a TitleParser. We could apply "some" normalization here,
+ * such as substituting spaces by underscores, but that would encourage the use of
+ * un-normalized text when constructing TitleValues. For constructing a TitleValue from
+ * user input or external sources, use a TitleParser.
+ *
+ * @param int $namespace The namespace ID. This is not validated.
+ * @param string $dbkey The page title in valid DBkey form. No normalization is applied.
+ * @param string $fragment The fragment title. Use '' to represent the whole page.
+ * No validation or normalization is applied.
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct( $namespace, $dbkey, $fragment = '' ) {
+ if ( !is_int( $namespace ) ) {
+ throw new InvalidArgumentException( '$namespace must be an integer' );
+ }
+
+ if ( !is_string( $dbkey ) ) {
+ throw new InvalidArgumentException( '$dbkey must be a string' );
+ }
+
+ // Sanity check, no full validation or normalization applied here!
+ if ( preg_match( '/^_|[ \r\n\t]|_$/', $dbkey ) ) {
+ throw new InvalidArgumentException( '$dbkey must be a valid DB key: ' . $dbkey );
+ }
+
+ if ( !is_string( $fragment ) ) {
+ throw new InvalidArgumentException( '$fragment must be a string' );
+ }
+
+ if ( $dbkey === '' ) {
+ throw new InvalidArgumentException( '$dbkey must not be empty' );
+ }
+
+ $this->namespace = $namespace;
+ $this->dbkey = $dbkey;
+ $this->fragment = $fragment;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNamespace() {
+ return $this->namespace;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFragment() {
+ return $this->fragment;
+ }
+
+ /**
+ * Returns the title's DB key, as supplied to the constructor,
+ * without namespace prefix or fragment.
+ *
+ * @return string
+ */
+ public function getDBkey() {
+ return $this->dbkey;
+ }
+
+ /**
+ * Returns the title in text form,
+ * without namespace prefix or fragment.
+ *
+ * This is computed from the DB key by replacing any underscores with spaces.
+ *
+ * @note To get a title string that includes the namespace and/or fragment,
+ * use a TitleFormatter.
+ *
+ * @return string
+ */
+ public function getText() {
+ return str_replace( '_', ' ', $this->getDBkey() );
+ }
+
+ /**
+ * Creates a new TitleValue for a different fragment of the same page.
+ *
+ * @param string $fragment The fragment name, or "" for the entire page.
+ *
+ * @return TitleValue
+ */
+ public function createFragmentTitle( $fragment ) {
+ return new TitleValue( $this->namespace, $this->dbkey, $fragment );
+ }
+
+ /**
+ * Returns a string representation of the title, for logging. This is purely informative
+ * and must not be used programmatically. Use the appropriate TitleFormatter to generate
+ * the correct string representation for a given use.
+ *
+ * @return string
+ */
+ public function __toString() {
+ $name = $this->namespace . ':' . $this->dbkey;
+
+ if ( $this->fragment !== '' ) {
+ $name .= '#' . $this->fragment;
+ }
+
+ return $name;
+ }
+}
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index 8268f8e3..89ce2b3a 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -31,8 +31,6 @@
* UploadBase and subclasses are the backend of MediaWiki's file uploads.
* The frontends are formed by ApiUpload and SpecialUpload.
*
- * See also includes/docs/upload.txt
- *
* @author Brion Vibber
* @author Bryan Tong Minh
* @author Michael Dale
@@ -46,7 +44,13 @@ abstract class UploadBase {
protected $mBlackListedExtensions;
protected $mJavaDetected, $mSVGNSError;
- protected static $safeXmlEncodings = array( 'UTF-8', 'ISO-8859-1', 'ISO-8859-2', 'UTF-16', 'UTF-32' );
+ protected static $safeXmlEncodings = array(
+ 'UTF-8',
+ 'ISO-8859-1',
+ 'ISO-8859-2',
+ 'UTF-16',
+ 'UTF-32'
+ );
const SUCCESS = 0;
const OK = 0;
@@ -68,7 +72,7 @@ abstract class UploadBase {
const SESSION_STATUS_KEY = 'wsUploadStatusData';
/**
- * @param $error int
+ * @param int $error
* @return string
*/
public function getVerificationErrorCode( $error ) {
@@ -105,7 +109,7 @@ abstract class UploadBase {
}
# Check php's file_uploads setting
- return wfIsHipHop() || wfIniGetBool( 'file_uploads' );
+ return wfIsHHVM() || wfIniGetBool( 'file_uploads' );
}
/**
@@ -113,8 +117,8 @@ abstract class UploadBase {
* identifying the missing permission.
* Can be overridden by subclasses.
*
- * @param $user User
- * @return bool
+ * @param User $user
+ * @return bool|string
*/
public static function isAllowed( $user ) {
foreach ( array( 'upload', 'edit' ) as $permission ) {
@@ -122,18 +126,19 @@ abstract class UploadBase {
return $permission;
}
}
+
return true;
}
// Upload handlers. Should probably just be a global.
- static $uploadHandlers = array( 'Stash', 'File', 'Url' );
+ private static $uploadHandlers = array( 'Stash', 'File', 'Url' );
/**
* Create a form of UploadBase depending on wpSourceType and initializes it
*
- * @param $request WebRequest
- * @param $type
- * @return null
+ * @param WebRequest $request
+ * @param string|null $type
+ * @return null|UploadBase
*/
public static function createFromRequest( &$request, $type = null ) {
$type = $type ? $type : $request->getVal( 'wpSourceType', 'File' );
@@ -166,22 +171,25 @@ abstract class UploadBase {
return null;
}
+ /** @var UploadBase $handler */
$handler = new $className;
$handler->initializeFromRequest( $request );
+
return $handler;
}
/**
* Check whether a request if valid for this handler
- * @param $request
+ * @param WebRequest $request
* @return bool
*/
public static function isValidRequest( $request ) {
return false;
}
- public function __construct() {}
+ public function __construct() {
+ }
/**
* Returns the upload type. Should be overridden by child classes
@@ -195,9 +203,9 @@ abstract class UploadBase {
/**
* Initialize the path information
- * @param string $name the desired destination name
- * @param string $tempPath the temporary path
- * @param int $fileSize the file size
+ * @param string $name The desired destination name
+ * @param string $tempPath The temporary path
+ * @param int $fileSize The file size
* @param bool $removeTempFile (false) remove the temporary file?
* @throws MWException
*/
@@ -213,6 +221,8 @@ abstract class UploadBase {
/**
* Initialize from a WebRequest. Override this in a subclass.
+ *
+ * @param WebRequest $request
*/
abstract public function initializeFromRequest( &$request );
@@ -234,7 +244,7 @@ abstract class UploadBase {
/**
* Return the file size
- * @return integer
+ * @return int
*/
public function getFileSize() {
return $this->mFileSize;
@@ -249,15 +259,16 @@ abstract class UploadBase {
}
/**
- * @param string $srcPath the source path
- * @return string|bool the real path if it was a virtual URL Returns false on failure
+ * @param string $srcPath The source path
+ * @return string|bool The real path if it was a virtual URL Returns false on failure
*/
function getRealPath( $srcPath ) {
wfProfileIn( __METHOD__ );
$repo = RepoGroup::singleton()->getLocalRepo();
if ( $repo->isVirtualUrl( $srcPath ) ) {
- // @todo just make uploads work with storage paths
- // UploadFromStash loads files via virtual URLs
+ /** @todo Just make uploads work with storage paths UploadFromStash
+ * loads files via virtual URLs.
+ */
$tmpFile = $repo->getLocalCopy( $srcPath );
if ( $tmpFile ) {
$tmpFile->bind( $this ); // keep alive with $this
@@ -267,12 +278,13 @@ abstract class UploadBase {
$path = $srcPath;
}
wfProfileOut( __METHOD__ );
+
return $path;
}
/**
* Verify whether the upload is sane.
- * @return mixed self::OK or else an array with error information
+ * @return mixed Const self::OK or else an array with error information
*/
public function verifyUpload() {
wfProfileIn( __METHOD__ );
@@ -282,6 +294,7 @@ abstract class UploadBase {
*/
if ( $this->isEmptyFile() ) {
wfProfileOut( __METHOD__ );
+
return array( 'status' => self::EMPTY_FILE );
}
@@ -291,6 +304,7 @@ abstract class UploadBase {
$maxSize = self::getMaxUploadSize( $this->getSourceType() );
if ( $this->mFileSize > $maxSize ) {
wfProfileOut( __METHOD__ );
+
return array(
'status' => self::FILE_TOO_LARGE,
'max' => $maxSize,
@@ -305,6 +319,7 @@ abstract class UploadBase {
$verification = $this->verifyFile();
if ( $verification !== true ) {
wfProfileOut( __METHOD__ );
+
return array(
'status' => self::VERIFICATION_ERROR,
'details' => $verification
@@ -317,27 +332,30 @@ abstract class UploadBase {
$result = $this->validateName();
if ( $result !== true ) {
wfProfileOut( __METHOD__ );
+
return $result;
}
$error = '';
if ( !wfRunHooks( 'UploadVerification',
- array( $this->mDestName, $this->mTempPath, &$error ) ) )
- {
+ array( $this->mDestName, $this->mTempPath, &$error ) )
+ ) {
wfProfileOut( __METHOD__ );
+
return array( 'status' => self::HOOK_ABORTED, 'error' => $error );
}
wfProfileOut( __METHOD__ );
+
return array( 'status' => self::OK );
}
/**
* Verify that the name is valid and, if necessary, that we can overwrite
*
- * @return mixed true if valid, otherwise and array with 'status'
+ * @return mixed True if valid, otherwise and array with 'status'
* and other keys
- **/
+ */
public function validateName() {
$nt = $this->getTitle();
if ( is_null( $nt ) ) {
@@ -351,6 +369,7 @@ abstract class UploadBase {
$result['blacklistedExt'] = $this->mBlackListedExtensions;
}
}
+
return $result;
}
$this->mDestName = $this->getLocalFile()->getName();
@@ -359,25 +378,27 @@ abstract class UploadBase {
}
/**
- * Verify the mime type.
+ * Verify the MIME type.
*
- * @note Only checks that it is not an evil mime. The does it have
- * correct extension given its mime type check is in verifyFile.
- * @param string $mime representing the mime
- * @return mixed true if the file is verified, an array otherwise
+ * @note Only checks that it is not an evil MIME. The "does it have
+ * correct extension given its MIME type?" check is in verifyFile.
+ * in `verifyFile()` that MIME type and file extension correlate.
+ * @param string $mime Representing the MIME
+ * @return mixed True if the file is verified, an array otherwise
*/
protected function verifyMimeType( $mime ) {
global $wgVerifyMimeType;
wfProfileIn( __METHOD__ );
if ( $wgVerifyMimeType ) {
- wfDebug( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n" );
+ wfDebug( "mime: <$mime> extension: <{$this->mFinalExtension}>\n" );
global $wgMimeTypeBlacklist;
if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
wfProfileOut( __METHOD__ );
+
return array( 'filetype-badmime', $mime );
}
- # Check IE type
+ # Check what Internet Explorer would detect
$fp = fopen( $this->mTempPath, 'rb' );
$chunk = fread( $fp, 256 );
fclose( $fp );
@@ -388,20 +409,21 @@ abstract class UploadBase {
foreach ( $ieTypes as $ieType ) {
if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
wfProfileOut( __METHOD__ );
+
return array( 'filetype-bad-ie-mime', $ieType );
}
}
}
wfProfileOut( __METHOD__ );
+
return true;
}
-
/**
* Verifies that it's ok to include the uploaded file
*
- * @return mixed true of the file is verified, array otherwise.
+ * @return mixed True of the file is verified, array otherwise.
*/
protected function verifyFile() {
global $wgVerifyMimeType;
@@ -410,27 +432,29 @@ abstract class UploadBase {
$status = $this->verifyPartialFile();
if ( $status !== true ) {
wfProfileOut( __METHOD__ );
+
return $status;
}
$this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
- $mime = $this->mFileProps['file-mime'];
+ $mime = $this->mFileProps['mime'];
if ( $wgVerifyMimeType ) {
# XXX: Missing extension will be caught by validateName() via getTitle()
if ( $this->mFinalExtension != '' && !$this->verifyExtension( $mime, $this->mFinalExtension ) ) {
wfProfileOut( __METHOD__ );
+
return array( 'filetype-mime-mismatch', $this->mFinalExtension, $mime );
}
}
-
$handler = MediaHandler::getHandler( $mime );
if ( $handler ) {
$handlerStatus = $handler->verifyUpload( $this->mTempPath );
if ( !$handlerStatus->isOK() ) {
$errors = $handlerStatus->getErrorsArray();
wfProfileOut( __METHOD__ );
+
return reset( $errors );
}
}
@@ -438,11 +462,13 @@ abstract class UploadBase {
wfRunHooks( 'UploadVerifyFile', array( $this, $mime, &$status ) );
if ( $status !== true ) {
wfProfileOut( __METHOD__ );
+
return $status;
}
wfDebug( __METHOD__ . ": all clear; passing.\n" );
wfProfileOut( __METHOD__ );
+
return true;
}
@@ -452,7 +478,7 @@ abstract class UploadBase {
* Runs the blacklist checks, but not any checks that may
* assume the entire file is present.
*
- * @return Mixed true for valid or array with error message key.
+ * @return mixed True for valid or array with error message key.
*/
protected function verifyPartialFile() {
global $wgAllowJavaUploads, $wgDisableUploadScriptChecks;
@@ -463,11 +489,12 @@ abstract class UploadBase {
$this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
- # check mime type, if desired
+ # check MIME type, if desired
$mime = $this->mFileProps['file-mime'];
$status = $this->verifyMimeType( $mime );
if ( $status !== true ) {
wfProfileOut( __METHOD__ );
+
return $status;
}
@@ -475,12 +502,14 @@ abstract class UploadBase {
if ( !$wgDisableUploadScriptChecks ) {
if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
wfProfileOut( __METHOD__ );
+
return array( 'uploadscripted' );
}
if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
$svgStatus = $this->detectScriptInSvg( $this->mTempPath );
if ( $svgStatus !== false ) {
wfProfileOut( __METHOD__ );
+
return $svgStatus;
}
}
@@ -497,11 +526,13 @@ abstract class UploadBase {
$error = reset( $errors );
if ( $error[0] !== 'zip-wrong-format' ) {
wfProfileOut( __METHOD__ );
+
return $error;
}
}
if ( $this->mJavaDetected ) {
wfProfileOut( __METHOD__ );
+
return array( 'uploadjava' );
}
}
@@ -510,15 +541,19 @@ abstract class UploadBase {
$virus = $this->detectVirus( $this->mTempPath );
if ( $virus ) {
wfProfileOut( __METHOD__ );
+
return array( 'uploadvirus', $virus );
}
wfProfileOut( __METHOD__ );
+
return true;
}
/**
* Callback for ZipDirectoryReader to detect Java class files.
+ *
+ * @param array $entry
*/
function zipEntryCallback( $entry ) {
$names = array( $entry['name'] );
@@ -541,11 +576,13 @@ abstract class UploadBase {
}
/**
- * Alias for verifyTitlePermissions. The function was originally 'verifyPermissions'
- * but that suggests it's checking the user, when it's really checking the title + user combination.
- * @param $user User object to verify the permissions against
+ * Alias for verifyTitlePermissions. The function was originally
+ * 'verifyPermissions', but that suggests it's checking the user, when it's
+ * really checking the title + user combination.
+ *
+ * @param User $user User object to verify the permissions against
* @return mixed An array as returned by getUserPermissionsErrors or true
- * in case the user has proper permissions.
+ * in case the user has proper permissions.
*/
public function verifyPermissions( $user ) {
return $this->verifyTitlePermissions( $user );
@@ -558,9 +595,9 @@ abstract class UploadBase {
* isAllowed() should be called as well for generic is-user-blocked or
* can-user-upload checking.
*
- * @param $user User object to verify the permissions against
+ * @param User $user User object to verify the permissions against
* @return mixed An array as returned by getUserPermissionsErrors or true
- * in case the user has proper permissions.
+ * in case the user has proper permissions.
*/
public function verifyTitlePermissions( $user ) {
/**
@@ -581,6 +618,7 @@ abstract class UploadBase {
if ( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
+
return $permErrors;
}
@@ -597,7 +635,7 @@ abstract class UploadBase {
*
* This should not assume that mTempPath is set.
*
- * @return Array of warnings
+ * @return array Array of warnings
*/
public function checkWarnings() {
global $wgLang;
@@ -617,6 +655,9 @@ abstract class UploadBase {
if ( $this->mDesiredDestName != $filename && $comparableName != $filename ) {
$warnings['badfilename'] = $filename;
+ // Debugging for bug 62241
+ wfDebugLog( 'upload', "Filename: '$filename', mDesiredDestName: "
+ . "'$this->mDesiredDestName', comparableName: '$comparableName'" );
}
// Check whether the file extension is on the unwanted list
@@ -660,10 +701,15 @@ abstract class UploadBase {
// Check dupes against archives
$archivedImage = new ArchivedFile( null, 0, "{$hash}.{$this->mFinalExtension}" );
if ( $archivedImage->getID() > 0 ) {
- $warnings['duplicate-archive'] = $archivedImage->getName();
+ if ( $archivedImage->userCan( File::DELETED_FILE ) ) {
+ $warnings['duplicate-archive'] = $archivedImage->getName();
+ } else {
+ $warnings['duplicate-archive'] = '';
+ }
}
wfProfileOut( __METHOD__ );
+
return $warnings;
}
@@ -671,12 +717,12 @@ abstract class UploadBase {
* Really perform the upload. Stores the file in the local repo, watches
* if necessary and runs the UploadComplete hook.
*
- * @param $comment
- * @param $pageText
- * @param $watch
- * @param $user User
+ * @param string $comment
+ * @param string $pageText
+ * @param bool $watch
+ * @param User $user
*
- * @return Status indicating the whether the upload succeeded.
+ * @return Status Indicating the whether the upload succeeded.
*/
public function performUpload( $comment, $pageText, $watch, $user ) {
wfProfileIn( __METHOD__ );
@@ -693,12 +739,17 @@ abstract class UploadBase {
if ( $status->isGood() ) {
if ( $watch ) {
- WatchAction::doWatch( $this->getLocalFile()->getTitle(), $user, WatchedItem::IGNORE_USER_RIGHTS );
+ WatchAction::doWatch(
+ $this->getLocalFile()->getTitle(),
+ $user,
+ WatchedItem::IGNORE_USER_RIGHTS
+ );
}
wfRunHooks( 'UploadComplete', array( &$this ) );
}
wfProfileOut( __METHOD__ );
+
return $status;
}
@@ -726,7 +777,9 @@ abstract class UploadBase {
# exclamation mark, so restrict file name to 240 bytes.
if ( strlen( $this->mFilteredName ) > 240 ) {
$this->mTitleError = self::FILENAME_TOO_LONG;
- return $this->mTitle = null;
+ $this->mTitle = null;
+
+ return $this->mTitle;
}
/**
@@ -739,7 +792,9 @@ abstract class UploadBase {
$nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
if ( is_null( $nt ) ) {
$this->mTitleError = self::ILLEGAL_FILENAME;
- return $this->mTitle = null;
+ $this->mTitle = null;
+
+ return $this->mTitle;
}
$this->mFilteredName = $nt->getDBkey();
@@ -780,61 +835,79 @@ abstract class UploadBase {
if ( $this->mFinalExtension == '' ) {
$this->mTitleError = self::FILETYPE_MISSING;
- return $this->mTitle = null;
+ $this->mTitle = null;
+
+ return $this->mTitle;
} elseif ( $blackListedExtensions ||
- ( $wgCheckFileExtensions && $wgStrictFileExtensions &&
- !$this->checkFileExtensionList( $ext, $wgFileExtensions ) ) ) {
+ ( $wgCheckFileExtensions && $wgStrictFileExtensions &&
+ !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) )
+ ) {
$this->mBlackListedExtensions = $blackListedExtensions;
$this->mTitleError = self::FILETYPE_BADTYPE;
- return $this->mTitle = null;
+ $this->mTitle = null;
+
+ return $this->mTitle;
}
- // Windows may be broken with special characters, see bug XXX
- if ( wfIsWindows() && !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() ) ) {
+ // Windows may be broken with special characters, see bug 1780
+ if ( !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() )
+ && !RepoGroup::singleton()->getLocalRepo()->backendSupportsUnicodePaths()
+ ) {
$this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
- return $this->mTitle = null;
+ $this->mTitle = null;
+
+ return $this->mTitle;
}
# If there was more than one "extension", reassemble the base
# filename to prevent bogus complaints about length
if ( count( $ext ) > 1 ) {
- for ( $i = 0; $i < count( $ext ) - 1; $i++ ) {
+ $iterations = count( $ext ) - 1;
+ for ( $i = 0; $i < $iterations; $i++ ) {
$partname .= '.' . $ext[$i];
}
}
if ( strlen( $partname ) < 1 ) {
$this->mTitleError = self::MIN_LENGTH_PARTNAME;
- return $this->mTitle = null;
+ $this->mTitle = null;
+
+ return $this->mTitle;
}
- return $this->mTitle = $nt;
+ $this->mTitle = $nt;
+
+ return $this->mTitle;
}
/**
* Return the local file and initializes if necessary.
*
- * @return LocalFile|null
+ * @return LocalFile|UploadStashFile|null
*/
public function getLocalFile() {
if ( is_null( $this->mLocalFile ) ) {
$nt = $this->getTitle();
$this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt );
}
+
return $this->mLocalFile;
}
/**
- * If the user does not supply all necessary information in the first upload form submission (either by accident or
- * by design) then we may want to stash the file temporarily, get more information, and publish the file later.
+ * If the user does not supply all necessary information in the first upload
+ * form submission (either by accident or by design) then we may want to
+ * stash the file temporarily, get more information, and publish the file
+ * later.
*
- * This method will stash a file in a temporary directory for later processing, and save the necessary descriptive info
- * into the database.
- * This method returns the file object, which also has a 'fileKey' property which can be passed through a form or
- * API request to find this stashed file again.
+ * This method will stash a file in a temporary directory for later
+ * processing, and save the necessary descriptive info into the database.
+ * This method returns the file object, which also has a 'fileKey' property
+ * which can be passed through a form or API request to find this stashed
+ * file again.
*
- * @param $user User
- * @return UploadStashFile stashed file
+ * @param User $user
+ * @return UploadStashFile Stashed file
*/
public function stashFile( User $user = null ) {
// was stashSessionFile
@@ -845,13 +918,15 @@ abstract class UploadBase {
$this->mLocalFile = $file;
wfProfileOut( __METHOD__ );
+
return $file;
}
/**
- * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashFile().
+ * Stash a file in a temporary directory, returning a key which can be used
+ * to find the file again. See stashFile().
*
- * @return String: file key
+ * @return string File key
*/
public function stashFileGetKey() {
return $this->stashFile()->getFileKey();
@@ -860,7 +935,7 @@ abstract class UploadBase {
/**
* alias for stashFileGetKey, for backwards compatibility
*
- * @return String: file key
+ * @return string File key
*/
public function stashSession() {
return $this->stashFileGetKey();
@@ -887,12 +962,13 @@ abstract class UploadBase {
* earlier pseudo-'extensions' to determine type and execute
* scripts, so the blacklist needs to check them all.
*
- * @param $filename string
+ * @param string $filename
* @return array
*/
public static function splitExtensions( $filename ) {
$bits = explode( '.', $filename );
$basename = array_shift( $bits );
+
return array( $basename, $bits );
}
@@ -900,9 +976,9 @@ abstract class UploadBase {
* Perform case-insensitive match against a list of file extensions.
* Returns true if the extension is in the list.
*
- * @param $ext String
- * @param $list Array
- * @return Boolean
+ * @param string $ext
+ * @param array $list
+ * @return bool
*/
public static function checkFileExtension( $ext, $list ) {
return in_array( strtolower( $ext ), $list );
@@ -912,20 +988,20 @@ abstract class UploadBase {
* Perform case-insensitive match against a list of file extensions.
* Returns an array of matching extensions.
*
- * @param $ext Array
- * @param $list Array
- * @return Boolean
+ * @param array $ext
+ * @param array $list
+ * @return bool
*/
public static function checkFileExtensionList( $ext, $list ) {
return array_intersect( array_map( 'strtolower', $ext ), $list );
}
/**
- * Checks if the mime type of the uploaded file matches the file extension.
+ * Checks if the MIME type of the uploaded file matches the file extension.
*
- * @param string $mime the mime type of the uploaded file
- * @param string $extension the filename extension that the file is to be served with
- * @return Boolean
+ * @param string $mime The MIME type of the uploaded file
+ * @param string $extension The filename extension that the file is to be served with
+ * @return bool
*/
public static function verifyExtension( $mime, $extension ) {
$magic = MimeMagic::singleton();
@@ -934,10 +1010,12 @@ abstract class UploadBase {
if ( !$magic->isRecognizableExtension( $extension ) ) {
wfDebug( __METHOD__ . ": passing file with unknown detected mime type; " .
"unrecognized extension '$extension', can't verify\n" );
+
return true;
} else {
wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; " .
"recognized extension '$extension', so probably invalid file\n" );
+
return false;
}
}
@@ -947,19 +1025,22 @@ abstract class UploadBase {
if ( $match === null ) {
if ( $magic->getTypesForExtension( $extension ) !== null ) {
wfDebug( __METHOD__ . ": No extension known for $mime, but we know a mime for $extension\n" );
+
return false;
} else {
wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" );
+
return true;
}
} elseif ( $match === true ) {
wfDebug( __METHOD__ . ": mime type $mime matches extension $extension, passing file\n" );
- #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
+ /** @todo If it's a bitmap, make sure PHP or ImageMagick resp. can handle it! */
return true;
-
} else {
- wfDebug( __METHOD__ . ": mime type $mime mismatches file extension $extension, rejecting file\n" );
+ wfDebug( __METHOD__
+ . ": mime type $mime mismatches file extension $extension, rejecting file\n" );
+
return false;
}
}
@@ -970,10 +1051,10 @@ abstract class UploadBase {
* potentially harmful. The present implementation will produce false
* positives in some situations.
*
- * @param string $file pathname to the temporary upload file
- * @param string $mime the mime type of the file
- * @param string $extension the extension of the file
- * @return Boolean: true if the file contains something looking like embedded scripts
+ * @param string $file Pathname to the temporary upload file
+ * @param string $mime The MIME type of the file
+ * @param string $extension The extension of the file
+ * @return bool True if the file contains something looking like embedded scripts
*/
public static function detectScript( $file, $mime, $extension ) {
global $wgAllowTitlesInSVG;
@@ -994,6 +1075,7 @@ abstract class UploadBase {
if ( !$chunk ) {
wfProfileOut( __METHOD__ );
+
return false;
}
@@ -1012,12 +1094,13 @@ abstract class UploadBase {
$chunk = trim( $chunk );
- # @todo FIXME: Convert from UTF-16 if necessary!
+ /** @todo FIXME: Convert from UTF-16 if necessary! */
wfDebug( __METHOD__ . ": checking for embedded scripts and HTML stuff\n" );
# check for HTML doctype
if ( preg_match( "/<!DOCTYPE *X?HTML/i", $chunk ) ) {
wfProfileOut( __METHOD__ );
+
return true;
}
@@ -1026,6 +1109,7 @@ abstract class UploadBase {
if ( $extension == 'svg' || strpos( $mime, 'image/svg' ) === 0 ) {
if ( self::checkXMLEncodingMissmatch( $file ) ) {
wfProfileOut( __METHOD__ );
+
return true;
}
}
@@ -1049,7 +1133,7 @@ abstract class UploadBase {
'<a href',
'<body',
'<head',
- '<html', #also in safari
+ '<html', #also in safari
'<img',
'<pre',
'<script', #also in safari
@@ -1064,6 +1148,7 @@ abstract class UploadBase {
if ( false !== strpos( $chunk, $tag ) ) {
wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" );
wfProfileOut( __METHOD__ );
+
return true;
}
}
@@ -1079,6 +1164,7 @@ abstract class UploadBase {
if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
wfDebug( __METHOD__ . ": found script types\n" );
wfProfileOut( __METHOD__ );
+
return true;
}
@@ -1086,6 +1172,7 @@ abstract class UploadBase {
if ( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
wfDebug( __METHOD__ . ": found html-style script urls\n" );
wfProfileOut( __METHOD__ );
+
return true;
}
@@ -1093,21 +1180,22 @@ abstract class UploadBase {
if ( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
wfDebug( __METHOD__ . ": found css-style script urls\n" );
wfProfileOut( __METHOD__ );
+
return true;
}
wfDebug( __METHOD__ . ": no scripts found\n" );
wfProfileOut( __METHOD__ );
+
return false;
}
-
/**
* Check a whitelist of xml encodings that are known not to be interpreted differently
* by the server's xml parser (expat) and some common browsers.
*
- * @param string $file pathname to the temporary upload file
- * @return Boolean: true if the file contains an encoding that could be misinterpreted
+ * @param string $file Pathname to the temporary upload file
+ * @return bool True if the file contains an encoding that could be misinterpreted
*/
public static function checkXMLEncodingMissmatch( $file ) {
global $wgSVGMetadataCutoff;
@@ -1119,16 +1207,19 @@ abstract class UploadBase {
&& !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
) {
wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'\n" );
+
return true;
}
} elseif ( preg_match( "!<\?xml\b!si", $contents ) ) {
// Start of XML declaration without an end in the first $wgSVGMetadataCutoff
// bytes. There shouldn't be a legitimate reason for this to happen.
wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" );
+
return true;
} elseif ( substr( $contents, 0, 4 ) == "\x4C\x6F\xA7\x94" ) {
// EBCDIC encoded XML
wfDebug( __METHOD__ . ": EBCDIC Encoded XML\n" );
+
return true;
}
@@ -1139,17 +1230,19 @@ abstract class UploadBase {
wfSuppressWarnings();
$str = iconv( $encoding, 'UTF-8', $contents );
wfRestoreWarnings();
- if ( $str != '' && preg_match( "!<\?xml\b(.*?)\?>!si", $str, $matches ) ) {
+ if ( $str != '' && preg_match( "!<\?xml\b(.*?)\?>!si", $str, $matches ) ) {
if ( preg_match( $encodingRegex, $matches[1], $encMatch )
&& !in_array( strtoupper( $encMatch[1] ), self::$safeXmlEncodings )
) {
wfDebug( __METHOD__ . ": Found unsafe XML encoding '{$encMatch[1]}'\n" );
+
return true;
}
} elseif ( $str != '' && preg_match( "!<\?xml\b!si", $str ) ) {
// Start of XML declaration without an end in the first $wgSVGMetadataCutoff
// bytes. There shouldn't be a legitimate reason for this to happen.
wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" );
+
return true;
}
}
@@ -1158,8 +1251,8 @@ abstract class UploadBase {
}
/**
- * @param $filename string
- * @return bool
+ * @param string $filename
+ * @return mixed False of the file is verified (does not contain scripts), array otherwise.
*/
protected function detectScriptInSvg( $filename ) {
$this->mSVGNSError = false;
@@ -1176,35 +1269,40 @@ abstract class UploadBase {
if ( $this->mSVGNSError ) {
return array( 'uploadscriptednamespace', $this->mSVGNSError );
}
+
return array( 'uploadscripted' );
}
+
return false;
}
/**
* Callback to filter SVG Processing Instructions.
- * @param $target string processing instruction name
- * @param $data string processing instruction attribute and value
+ * @param string $target Processing instruction name
+ * @param string $data Processing instruction attribute and value
* @return bool (true if the filter identified something bad)
*/
public static function checkSvgPICallback( $target, $data ) {
// Don't allow external stylesheets (bug 57550)
- if ( preg_match( '/xml-stylesheet/i', $target) ) {
+ if ( preg_match( '/xml-stylesheet/i', $target ) ) {
return true;
}
+
return false;
}
/**
* @todo Replace this with a whitelist filter!
- * @param $element string
- * @param $attribs array
+ * @param string $element
+ * @param array $attribs
* @return bool
*/
public function checkSvgScriptCallback( $element, $attribs, $data = null ) {
list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element );
+ // We specifically don't include:
+ // http://www.w3.org/1999/xhtml (bug 60771)
static $validNamespaces = array(
'',
'adobe:ns:meta/',
@@ -1235,17 +1333,21 @@ abstract class UploadBase {
'http://purl.org/dc/elements/1.1',
'http://schemas.microsoft.com/visio/2003/svgextensions/',
'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
+ 'http://taptrix.com/inkpad/svg_extensions',
'http://web.resource.org/cc/',
'http://www.freesoftware.fsf.org/bkchem/cdml',
'http://www.inkscape.org/namespaces/inkscape',
+ 'http://www.opengis.net/gml',
'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
'http://www.w3.org/2000/svg',
+ 'http://www.w3.org/tr/rec-rdf-syntax/',
);
if ( !in_array( $namespace, $validNamespaces ) ) {
wfDebug( __METHOD__ . ": Non-svg namespace '$namespace' in uploaded file.\n" );
- // @TODO return a status object to a closure in XmlTypeCheck, for MW1.21+
+ /** @todo Return a status object to a closure in XmlTypeCheck, for MW1.21+ */
$this->mSVGNSError = $namespace;
+
return true;
}
@@ -1254,24 +1356,29 @@ abstract class UploadBase {
*/
if ( $strippedElement == 'script' ) {
wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
+
return true;
}
- # e.g., <svg xmlns="http://www.w3.org/2000/svg"> <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
+ # e.g., <svg xmlns="http://www.w3.org/2000/svg">
+ # <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
if ( $strippedElement == 'handler' ) {
wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
+
return true;
}
# SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
if ( $strippedElement == 'stylesheet' ) {
wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
+
return true;
}
# Block iframes, in case they pass the namespace check
if ( $strippedElement == 'iframe' ) {
wfDebug( __METHOD__ . ": iframe in uploaded file.\n" );
+
return true;
}
@@ -1288,7 +1395,9 @@ abstract class UploadBase {
$value = strtolower( $value );
if ( substr( $stripped, 0, 2 ) == 'on' ) {
- wfDebug( __METHOD__ . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
+ wfDebug( __METHOD__
+ . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
+
return true;
}
@@ -1309,13 +1418,17 @@ abstract class UploadBase {
# href with embedded svg as target
if ( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) {
- wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
+ wfDebug( __METHOD__ . ": Found href to embedded svg "
+ . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
+
return true;
}
# href with embedded (text/xml) svg as target
if ( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) {
- wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
+ wfDebug( __METHOD__ . ": Found href to embedded svg "
+ . "\"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" );
+
return true;
}
@@ -1332,26 +1445,41 @@ abstract class UploadBase {
}
# use set/animate to add event-handler attribute to parent
- if ( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) {
- wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
+ if ( ( $strippedElement == 'set' || $strippedElement == 'animate' )
+ && $stripped == 'attributename'
+ && substr( $value, 0, 2 ) == 'on'
+ ) {
+ wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with "
+ . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
+
return true;
}
# use set to add href attribute to parent element
- if ( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) {
+ if ( $strippedElement == 'set'
+ && $stripped == 'attributename'
+ && strpos( $value, 'href' ) !== false
+ ) {
wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file.\n" );
+
return true;
}
# use set to add a remote / data / script target to an element
- if ( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
+ if ( $strippedElement == 'set'
+ && $stripped == 'to'
+ && preg_match( '!(http|https|data|script):!sim', $value )
+ ) {
wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file.\n" );
+
return true;
}
# use handler attribute with remote / data / script
if ( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) {
- wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script '$attrib'='$value' in uploaded file.\n" );
+ wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script "
+ . "'$attrib'='$value' in uploaded file.\n" );
+
return true;
}
@@ -1376,11 +1504,15 @@ abstract class UploadBase {
}
# image filters can pull in url, which could be svg that executes scripts
- if ( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) {
- wfDebug( __METHOD__ . ": Found image filter with url: \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
+ if ( $strippedElement == 'image'
+ && $stripped == 'filter'
+ && preg_match( '!url\s*\(!sim', $value )
+ ) {
+ wfDebug( __METHOD__ . ": Found image filter with url: "
+ . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
+
return true;
}
-
}
return false; //No scripts detected
@@ -1440,24 +1572,26 @@ abstract class UploadBase {
/**
* Divide the element name passed by the xml parser to the callback into URI and prifix.
- * @param $name string
- * @return array containing the namespace URI and prefix
+ * @param string $element
+ * @return array Containing the namespace URI and prefix
*/
private static function splitXmlNamespace( $element ) {
// 'http://www.w3.org/2000/svg:script' -> array( 'http://www.w3.org/2000/svg', 'script' )
$parts = explode( ':', strtolower( $element ) );
$name = array_pop( $parts );
$ns = implode( ':', $parts );
+
return array( $ns, $name );
}
/**
- * @param $name string
+ * @param string $name
* @return string
*/
private function stripXmlNamespace( $name ) {
// 'http://www.w3.org/2000/svg:script' -> 'script'
$parts = explode( ':', strtolower( $name ) );
+
return array_pop( $parts );
}
@@ -1466,10 +1600,10 @@ abstract class UploadBase {
* This relies on the $wgAntivirus and $wgAntivirusSetup variables.
* $wgAntivirusRequired may be used to deny upload if the scan fails.
*
- * @param string $file pathname to the temporary upload file
- * @return mixed false if not virus is found, NULL if the scan fails or is disabled,
- * or a string containing feedback from the virus scanner if a virus was found.
- * If textual feedback is missing but a virus was found, this function returns true.
+ * @param string $file Pathname to the temporary upload file
+ * @return mixed False if not virus is found, null if the scan fails or is disabled,
+ * or a string containing feedback from the virus scanner if a virus was found.
+ * If textual feedback is missing but a virus was found, this function returns true.
*/
public static function detectVirus( $file ) {
global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut;
@@ -1478,6 +1612,7 @@ abstract class UploadBase {
if ( !$wgAntivirus ) {
wfDebug( __METHOD__ . ": virus scanner disabled\n" );
wfProfileOut( __METHOD__ );
+
return null;
}
@@ -1486,6 +1621,7 @@ abstract class UploadBase {
$wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>",
array( 'virus-badscanner', $wgAntivirus ) );
wfProfileOut( __METHOD__ );
+
return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus";
}
@@ -1530,7 +1666,9 @@ abstract class UploadBase {
# scan failed (code was mapped to false by $exitCodeMap)
wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode).\n" );
- $output = $wgAntivirusRequired ? wfMessage( 'virus-scanfailed', array( $exitCode ) )->text() : null;
+ $output = $wgAntivirusRequired
+ ? wfMessage( 'virus-scanfailed', array( $exitCode ) )->text()
+ : null;
} elseif ( $mappedCode === AV_SCAN_ABORTED ) {
# scan failed because filetype is unknown (probably imune)
wfDebug( __METHOD__ . ": unsupported file type $file (code $exitCode).\n" );
@@ -1557,6 +1695,7 @@ abstract class UploadBase {
}
wfProfileOut( __METHOD__ );
+
return $output;
}
@@ -1564,9 +1703,9 @@ abstract class UploadBase {
* Check if there's an overwrite conflict and, if so, if restrictions
* forbid this user from performing the upload.
*
- * @param $user User
+ * @param User $user
*
- * @return mixed true on success, array on failure
+ * @return mixed True on success, array on failure
*/
private function checkOverwrite( $user ) {
// First check whether the local file can be overwritten
@@ -1593,9 +1732,9 @@ abstract class UploadBase {
/**
* Check if a user is the last uploader
*
- * @param $user User object
- * @param string $img image name
- * @return Boolean
+ * @param User $user
+ * @param string $img Image name
+ * @return bool
*/
public static function userCanReUpload( User $user, $img ) {
if ( $user->isAllowed( 'reupload' ) ) {
@@ -1622,7 +1761,7 @@ abstract class UploadBase {
* - File exists with normalized extension
* - The file looks like a thumbnail and the original exists
*
- * @param $file File The File object to check
+ * @param File $file The File object to check
* @return mixed False if the file does not exists, else an array
*/
public static function getExistsWarning( $file ) {
@@ -1668,7 +1807,7 @@ abstract class UploadBase {
// Check for files with the same name but a different extension
$similarFiles = RepoGroup::singleton()->getLocalRepo()->findFilesByPrefix(
- "{$partname}.", 1 );
+ "{$partname}.", 1 );
if ( count( $similarFiles ) ) {
return array(
'warning' => 'exists-normalized',
@@ -1679,7 +1818,10 @@ abstract class UploadBase {
if ( self::isThumbName( $file->getName() ) ) {
# Check for filenames like 50px- or 180px-, these are mostly thumbnails
- $nt_thb = Title::newFromText( substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension, NS_FILE );
+ $nt_thb = Title::newFromText(
+ substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension,
+ NS_FILE
+ );
$file_thb = wfLocalFile( $nt_thb );
if ( $file_thb->exists() ) {
return array(
@@ -1712,23 +1854,24 @@ abstract class UploadBase {
/**
* Helper function that checks whether the filename looks like a thumbnail
- * @param $filename string
+ * @param string $filename
* @return bool
*/
public static function isThumbName( $filename ) {
$n = strrpos( $filename, '.' );
$partname = $n ? substr( $filename, 0, $n ) : $filename;
+
return (
- substr( $partname, 3, 3 ) == 'px-' ||
- substr( $partname, 2, 3 ) == 'px-'
- ) &&
- preg_match( "/[0-9]{2}/", substr( $partname, 0, 2 ) );
+ substr( $partname, 3, 3 ) == 'px-' ||
+ substr( $partname, 2, 3 ) == 'px-'
+ ) &&
+ preg_match( "/[0-9]{2}/", substr( $partname, 0, 2 ) );
}
/**
* Get a list of blacklisted filename prefixes from [[MediaWiki:Filename-prefix-blacklist]]
*
- * @return array list of prefixes
+ * @return array List of prefixes
*/
public static function getFilenamePrefixBlacklist() {
$blacklist = array();
@@ -1749,23 +1892,28 @@ abstract class UploadBase {
$blacklist[] = trim( $line );
}
}
+
return $blacklist;
}
/**
* Gets image info about the file just uploaded.
*
- * Also has the effect of setting metadata to be an 'indexed tag name' in returned API result if
- * 'metadata' was requested. Oddly, we have to pass the "result" object down just so it can do that
- * with the appropriate format, presumably.
+ * Also has the effect of setting metadata to be an 'indexed tag name' in
+ * returned API result if 'metadata' was requested. Oddly, we have to pass
+ * the "result" object down just so it can do that with the appropriate
+ * format, presumably.
*
- * @param $result ApiResult:
- * @return Array: image info
+ * @param ApiResult $result
+ * @return array Image info
*/
public function getImageInfo( $result ) {
$file = $this->getLocalFile();
- // TODO This cries out for refactoring. We really want to say $file->getAllInfo(); here.
- // Perhaps "info" methods should be moved into files, and the API should just wrap them in queries.
+ /** @todo This cries out for refactoring.
+ * We really want to say $file->getAllInfo(); here.
+ * Perhaps "info" methods should be moved into files, and the API should
+ * just wrap them in queries.
+ */
if ( $file instanceof UploadStashFile ) {
$imParam = ApiQueryStashImageInfo::getPropertyNames();
$info = ApiQueryStashImageInfo::getInfo( $file, array_flip( $imParam ), $result );
@@ -1773,21 +1921,23 @@ abstract class UploadBase {
$imParam = ApiQueryImageInfo::getPropertyNames();
$info = ApiQueryImageInfo::getInfo( $file, array_flip( $imParam ), $result );
}
+
return $info;
}
/**
- * @param $error array
+ * @param array $error
* @return Status
*/
public function convertVerifyErrorToStatus( $error ) {
$code = $error['status'];
unset( $code['status'] );
+
return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
}
/**
- * @param $forType null|string
+ * @param null|string $forType
* @return int
*/
public static function getMaxUploadSize( $forType = null ) {
@@ -1807,8 +1957,8 @@ abstract class UploadBase {
/**
* Get the current status of a chunked upload (used for polling).
* The status will be read from the *current* user session.
- * @param $statusKey string
- * @return Array|bool
+ * @param string $statusKey
+ * @return Status[]|bool
*/
public static function getSessionStatus( $statusKey ) {
return isset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] )
@@ -1819,8 +1969,8 @@ abstract class UploadBase {
/**
* Set the current status of a chunked upload (used for polling).
* The status will be stored in the *current* user session.
- * @param $statusKey string
- * @param $value array|false
+ * @param string $statusKey
+ * @param array|bool $value
* @return void
*/
public static function setSessionStatus( $statusKey, $value ) {
diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php
index 2e0b9444..14993023 100644
--- a/includes/upload/UploadFromChunks.php
+++ b/includes/upload/UploadFromChunks.php
@@ -28,14 +28,19 @@
* @author Michael Dale
*/
class UploadFromChunks extends UploadFromFile {
- protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath;
+ protected $mOffset;
+ protected $mChunkIndex;
+ protected $mFileKey;
+ protected $mVirtualTempPath;
+ /** @var LocalRepo */
+ private $repo;
/**
* Setup local pointers to stash, repo and user (similar to UploadFromStash)
*
- * @param $user User
- * @param $stash UploadStash
- * @param $repo FileRepo
+ * @param User|null $user Default: null
+ * @param UploadStash|bool $stash Default: false
+ * @param FileRepo|bool $repo Default: false
*/
public function __construct( $user = null, $stash = false, $repo = false ) {
// user object. sometimes this won't exist, as when running from cron.
@@ -57,14 +62,13 @@ class UploadFromChunks extends UploadFromFile {
}
$this->stash = new UploadStash( $this->repo, $this->user );
}
-
- return true;
}
/**
* Calls the parent stashFile and updates the uploadsession table to handle "chunks"
*
- * @return UploadStashFile stashed file
+ * @param User|null $user
+ * @return UploadStashFile Stashed file
*/
public function stashFile( User $user = null ) {
// Stash file is the called on creating a new chunk session:
@@ -83,11 +87,16 @@ class UploadFromChunks extends UploadFromFile {
// Update db table to reflect initial "chunk" state
$this->updateChunkStatus();
+
return $this->mLocalFile;
}
/**
* Continue chunk uploading
+ *
+ * @param string $name
+ * @param string $key
+ * @param WebRequestUpload $webRequestUpload
*/
public function continueChunks( $name, $key, $webRequestUpload ) {
$this->mFileKey = $key;
@@ -108,13 +117,14 @@ class UploadFromChunks extends UploadFromFile {
* @return FileRepoStatus
*/
public function concatenateChunks() {
+ $chunkIndex = $this->getChunkIndex();
wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" .
- $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
+ $this->getOffset() . ' inx:' . $chunkIndex . "\n" );
// Concatenate all the chunks to mVirtualTempPath
- $fileList = Array();
+ $fileList = array();
// The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1"
- for ( $i = 0; $i <= $this->getChunkIndex(); $i++ ) {
+ for ( $i = 0; $i <= $chunkIndex; $i++ ) {
$fileList[] = $this->getVirtualChunkLocation( $i );
}
@@ -122,9 +132,12 @@ class UploadFromChunks extends UploadFromFile {
$ext = FileBackend::extensionFromPath( $this->mVirtualTempPath );
// Get a 0-byte temp file to perform the concatenation at
$tmpFile = TempFSFile::factory( 'chunkedupload_', $ext );
- $tmpPath = $tmpFile
- ? $tmpFile->bind( $this )->getPath() // keep alive with $this
- : false; // fail in concatenate()
+ $tmpPath = false; // fail in concatenate()
+ if ( $tmpFile ) {
+ // keep alive with $this
+ $tmpPath = $tmpFile->bind( $this )->getPath();
+ }
+
// Concatenate the chunks at the temp file
$tStart = microtime( true );
$status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE );
@@ -132,14 +145,17 @@ class UploadFromChunks extends UploadFromFile {
if ( !$status->isOk() ) {
return $status;
}
- wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds.\n" );
+ wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds." );
- $this->mTempPath = $tmpPath; // file system path
- $this->mFileSize = filesize( $this->mTempPath ); //Since this was set for the last chunk previously
+ // File system path
+ $this->mTempPath = $tmpPath;
+ // Since this was set for the last chunk previously
+ $this->mFileSize = filesize( $this->mTempPath );
$ret = $this->verifyUpload();
if ( $ret['status'] !== UploadBase::OK ) {
wfDebugLog( 'fileconcatenate', "Verification failed for chunked upload" );
$status->fatal( $this->getVerificationErrorCode( $ret['status'] ) );
+
return $status;
}
@@ -149,44 +165,45 @@ class UploadFromChunks extends UploadFromFile {
$this->mLocalFile = parent::stashFile( $this->user );
$tAmount = microtime( true ) - $tStart;
$this->mLocalFile->setLocalReference( $tmpFile ); // reuse (e.g. for getImageInfo())
- wfDebugLog( 'fileconcatenate', "Stashed combined file ($i chunks) in $tAmount seconds.\n" );
+ wfDebugLog( 'fileconcatenate', "Stashed combined file ($i chunks) in $tAmount seconds." );
return $status;
}
/**
* Perform the upload, then remove the temp copy afterward
- * @param $comment string
- * @param $pageText string
- * @param $watch bool
- * @param $user User
+ * @param string $comment
+ * @param string $pageText
+ * @param bool $watch
+ * @param User $user
* @return Status
*/
public function performUpload( $comment, $pageText, $watch, $user ) {
$rv = parent::performUpload( $comment, $pageText, $watch, $user );
+
return $rv;
}
/**
* Returns the virtual chunk location:
- * @param $index
+ * @param int $index
* @return string
*/
function getVirtualChunkLocation( $index ) {
return $this->repo->getVirtualUrl( 'temp' ) .
- '/' .
- $this->repo->getHashPath(
- $this->getChunkFileKey( $index )
- ) .
- $this->getChunkFileKey( $index );
+ '/' .
+ $this->repo->getHashPath(
+ $this->getChunkFileKey( $index )
+ ) .
+ $this->getChunkFileKey( $index );
}
/**
* Add a chunk to the temporary directory
*
- * @param string $chunkPath path to temporary chunk file
- * @param int $chunkSize size of the current chunk
- * @param int $offset offset of current chunk ( mutch match database chunk offset )
+ * @param string $chunkPath Path to temporary chunk file
+ * @param int $chunkSize Size of the current chunk
+ * @param int $offset Offset of current chunk ( mutch match database chunk offset )
* @return Status
*/
public function addChunk( $chunkPath, $chunkSize, $offset ) {
@@ -220,6 +237,7 @@ class UploadFromChunks extends UploadFromFile {
$status = Status::newFatal( 'invalid-chunk-offset' );
}
}
+
return $status;
}
@@ -228,7 +246,7 @@ class UploadFromChunks extends UploadFromFile {
*/
private function updateChunkStatus() {
wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" .
- $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
+ $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
$dbw = $this->repo->getMasterDb();
// Use a quick transaction since we will upload the full temp file into shared
@@ -274,30 +292,32 @@ class UploadFromChunks extends UploadFromFile {
/**
* Get the current Chunk index
- * @return Integer index of the current chunk
+ * @return int Index of the current chunk
*/
private function getChunkIndex() {
if ( $this->mChunkIndex !== null ) {
return $this->mChunkIndex;
}
+
return 0;
}
/**
* Gets the current offset in fromt the stashedupload table
- * @return Integer current byte offset of the chunk file set
+ * @return int Current byte offset of the chunk file set
*/
private function getOffset() {
if ( $this->mOffset !== null ) {
return $this->mOffset;
}
+
return 0;
}
/**
* Output the chunk to disk
*
- * @param $chunkPath string
+ * @param string $chunkPath
* @throws UploadChunkFileException
* @return FileRepoStatus
*/
@@ -311,18 +331,20 @@ class UploadFromChunks extends UploadFromFile {
$this->repo->getZonePath( 'temp' ) . "/{$hashPath}{$fileKey}" );
// Check for error in stashing the chunk:
- if ( ! $storeStatus->isOK() ) {
+ if ( !$storeStatus->isOK() ) {
$error = $storeStatus->getErrorsArray();
$error = reset( $error );
- if ( ! count( $error ) ) {
+ if ( !count( $error ) ) {
$error = $storeStatus->getWarningsArray();
$error = reset( $error );
- if ( ! count( $error ) ) {
+ if ( !count( $error ) ) {
$error = array( 'unknown', 'no error recorded' );
}
}
- throw new UploadChunkFileException( "error storing file in '$chunkPath': " . implode( '; ', $error ) );
+ throw new UploadChunkFileException( "Error storing file in '$chunkPath': " .
+ implode( '; ', $error ) );
}
+
return $storeStatus;
}
@@ -330,6 +352,7 @@ class UploadFromChunks extends UploadFromFile {
if ( $index === null ) {
$index = $this->getChunkIndex();
}
+
return $this->mFileKey . '.' . $index;
}
@@ -352,6 +375,11 @@ class UploadFromChunks extends UploadFromFile {
}
}
-class UploadChunkZeroLengthFileException extends MWException {};
-class UploadChunkFileException extends MWException {};
-class UploadChunkVerificationException extends MWException {};
+class UploadChunkZeroLengthFileException extends MWException {
+}
+
+class UploadChunkFileException extends MWException {
+}
+
+class UploadChunkVerificationException extends MWException {
+}
diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php
index a00ed327..3a1e8bdc 100644
--- a/includes/upload/UploadFromFile.php
+++ b/includes/upload/UploadFromFile.php
@@ -28,14 +28,13 @@
* @author Bryan Tong Minh
*/
class UploadFromFile extends UploadBase {
-
/**
* @var WebRequestUpload
*/
protected $mUpload = null;
/**
- * @param $request WebRequest
+ * @param WebRequest $request
*/
function initializeFromRequest( &$request ) {
$upload = $request->getUpload( 'wpUploadFile' );
@@ -49,8 +48,8 @@ class UploadFromFile extends UploadBase {
/**
* Initialize from a filename and a WebRequestUpload
- * @param $name
- * @param $webRequestUpload
+ * @param string $name
+ * @param WebRequestUpload $webRequestUpload
*/
function initialize( $name, $webRequestUpload ) {
$this->mUpload = $webRequestUpload;
@@ -59,7 +58,7 @@ class UploadFromFile extends UploadBase {
}
/**
- * @param $request
+ * @param WebRequest $request
* @return bool
*/
static function isValidRequest( $request ) {
diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php
index cb85fc63..b4e815fc 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -28,7 +28,10 @@
* @author Bryan Tong Minh
*/
class UploadFromStash extends UploadBase {
- protected $mFileKey, $mVirtualTempPath, $mFileProps, $mSourceType;
+ protected $mFileKey;
+ protected $mVirtualTempPath;
+ protected $mFileProps;
+ protected $mSourceType;
// an instance of UploadStash
private $stash;
@@ -37,9 +40,9 @@ class UploadFromStash extends UploadBase {
private $repo;
/**
- * @param $user User
- * @param $stash UploadStash
- * @param $repo FileRepo
+ * @param User|bool $user Default: false
+ * @param UploadStash|bool $stash Default: false
+ * @param FileRepo|bool $repo Default: false
*/
public function __construct( $user = false, $stash = false, $repo = false ) {
// user object. sometimes this won't exist, as when running from cron.
@@ -65,7 +68,7 @@ class UploadFromStash extends UploadBase {
}
/**
- * @param $key string
+ * @param string $key
* @return bool
*/
public static function isValidKey( $key ) {
@@ -74,9 +77,8 @@ class UploadFromStash extends UploadBase {
}
/**
- * @param $request WebRequest
- *
- * @return Boolean
+ * @param WebRequest $request
+ * @return bool
*/
public static function isValidRequest( $request ) {
// this passes wpSessionKey to getText() as a default when wpFileKey isn't set.
@@ -86,8 +88,9 @@ class UploadFromStash extends UploadBase {
}
/**
- * @param $key string
- * @param $name string
+ * @param string $key
+ * @param string $name
+ * @param bool $initTempFile
*/
public function initialize( $key, $name = 'upload_file', $initTempFile = true ) {
/**
@@ -110,14 +113,17 @@ class UploadFromStash extends UploadBase {
}
/**
- * @param $request WebRequest
+ * @param WebRequest $request
*/
public function initializeFromRequest( &$request ) {
// sends wpSessionKey as a default when wpFileKey is missing
$fileKey = $request->getText( 'wpFileKey', $request->getText( 'wpSessionKey' ) );
// chooses one of wpDestFile, wpUploadFile, filename in that order.
- $desiredDestName = $request->getText( 'wpDestFile', $request->getText( 'wpUploadFile', $request->getText( 'filename' ) ) );
+ $desiredDestName = $request->getText(
+ 'wpDestFile',
+ $request->getText( 'wpUploadFile', $request->getText( 'filename' ) )
+ );
$this->initialize( $fileKey, $desiredDestName );
}
@@ -144,19 +150,20 @@ class UploadFromStash extends UploadBase {
/**
* Stash the file.
*
- * @param $user User
+ * @param User $user
* @return UploadStashFile
*/
public function stashFile( User $user = null ) {
// replace mLocalFile with an instance of UploadStashFile, which adds some methods
// that are useful for stashed files.
$this->mLocalFile = parent::stashFile( $user );
+
return $this->mLocalFile;
}
/**
* This should return the key instead of the UploadStashFile instance, for backward compatibility.
- * @return String
+ * @return string
*/
public function stashSession() {
return $this->stashFile()->getFileKey();
@@ -164,7 +171,7 @@ class UploadFromStash extends UploadBase {
/**
* Remove a temporarily kept file stashed by saveTempUploadedFile().
- * @return bool success
+ * @return bool Success
*/
public function unsaveUploadedFile() {
return $this->stash->removeFile( $this->mFileKey );
@@ -172,15 +179,16 @@ class UploadFromStash extends UploadBase {
/**
* Perform the upload, then remove the database record afterward.
- * @param $comment string
- * @param $pageText string
- * @param $watch bool
- * @param $user User
+ * @param string $comment
+ * @param string $pageText
+ * @param bool $watch
+ * @param User $user
* @return Status
*/
public function performUpload( $comment, $pageText, $watch, $user ) {
$rv = parent::performUpload( $comment, $pageText, $watch, $user );
$this->unsaveUploadedFile();
+
return $rv;
}
}
diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php
index 0201d5f4..b6056401 100644
--- a/includes/upload/UploadFromUrl.php
+++ b/includes/upload/UploadFromUrl.php
@@ -41,7 +41,7 @@ class UploadFromUrl extends UploadBase {
* user is not allowed, return the name of the user right as a string. If
* the user is allowed, have the parent do further permissions checking.
*
- * @param $user User
+ * @param User $user
*
* @return bool|string
*/
@@ -49,6 +49,7 @@ class UploadFromUrl extends UploadBase {
if ( !$user->isAllowed( 'upload_by_url' ) ) {
return 'upload_by_url';
}
+
return parent::isAllowed( $user );
}
@@ -58,6 +59,7 @@ class UploadFromUrl extends UploadBase {
*/
public static function isEnabled() {
global $wgAllowCopyUploads;
+
return $wgAllowCopyUploads && parent::isEnabled();
}
@@ -66,7 +68,7 @@ class UploadFromUrl extends UploadBase {
* The domains in the whitelist can include wildcard characters (*) in place
* of any of the domain levels, e.g. '*.flickr.com' or 'upload.*.gov.uk'.
*
- * @param $url string
+ * @param string $url
* @return bool
*/
public static function isAllowedHost( $url ) {
@@ -103,13 +105,14 @@ class UploadFromUrl extends UploadBase {
}
*/
}
+
return $valid;
}
/**
* Checks whether the URL is not allowed.
*
- * @param $url string
+ * @param string $url
* @return bool
*/
public static function isAllowedUrl( $url ) {
@@ -118,15 +121,16 @@ class UploadFromUrl extends UploadBase {
wfRunHooks( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) );
self::$allowedUrls[$url] = $allowed;
}
+
return self::$allowedUrls[$url];
}
/**
* Entry point for API upload
*
- * @param $name string
- * @param $url string
- * @param $async mixed Whether the download should be performed
+ * @param string $name
+ * @param string $url
+ * @param bool|string $async Whether the download should be performed
* asynchronous. False for synchronous, async or async-leavemessage for
* asynchronous download.
* @throws MWException
@@ -147,7 +151,7 @@ class UploadFromUrl extends UploadBase {
/**
* Entry point for SpecialUpload
- * @param $request WebRequest object
+ * @param WebRequest $request
*/
public function initializeFromRequest( &$request ) {
$desiredDestName = $request->getText( 'wpDestFile' );
@@ -162,13 +166,14 @@ class UploadFromUrl extends UploadBase {
}
/**
- * @param $request WebRequest object
+ * @param WebRequest $request
* @return bool
*/
public static function isValidRequest( $request ) {
global $wgUser;
$url = $request->getVal( 'wpUploadFileURL' );
+
return !empty( $url )
&& Http::isValidURI( $url )
&& $wgUser->isAllowed( 'upload_by_url' );
@@ -184,7 +189,7 @@ class UploadFromUrl extends UploadBase {
/**
* Download the file (if not async)
*
- * @param Array $httpOptions Array of options for MWHttpRequest. Ignored if async.
+ * @param array $httpOptions Array of options for MWHttpRequest. Ignored if async.
* This could be used to override the timeout on the http request.
* @return Status
*/
@@ -202,23 +207,28 @@ class UploadFromUrl extends UploadBase {
if ( !$this->mAsync ) {
return $this->reallyFetchFile( $httpOptions );
}
+
return Status::newGood();
}
+
/**
* Create a new temporary file in the URL subdirectory of wfTempDir().
*
* @return string Path to the file
*/
protected function makeTemporaryFile() {
- return tempnam( wfTempDir(), 'URL' );
+ $tmpFile = TempFSFile::factory( 'URL' );
+ $tmpFile->bind( $this );
+
+ return $tmpFile->getPath();
}
/**
* Callback: save a chunk of the result of a HTTP request to the temporary file
*
- * @param $req mixed
- * @param $buffer string
- * @return int number of bytes handled
+ * @param mixed $req
+ * @param string $buffer
+ * @return int Number of bytes handled
*/
public function saveTempFileChunk( $req, $buffer ) {
$nbytes = fwrite( $this->mTmpHandle, $buffer );
@@ -238,7 +248,7 @@ class UploadFromUrl extends UploadBase {
* Download the file, save it to the temporary file and update the file
* size and set $mRemoveTempFile to true.
*
- * @param Array $httpOptions Array of options for MWHttpRequest
+ * @param array $httpOptions Array of options for MWHttpRequest
* @return Status
*/
protected function reallyFetchFile( $httpOptions = array() ) {
@@ -256,12 +266,12 @@ class UploadFromUrl extends UploadBase {
$this->mRemoveTempFile = true;
$this->mFileSize = 0;
- $options = $httpOptions + array(
- 'followRedirects' => true,
- );
+ $options = $httpOptions + array( 'followRedirects' => true );
+
if ( $wgCopyUploadProxy !== false ) {
$options['proxy'] = $wgCopyUploadProxy;
}
+
if ( $wgCopyUploadTimeout && !isset( $options['timeout'] ) ) {
$options['timeout'] = $wgCopyUploadTimeout;
}
@@ -294,42 +304,46 @@ class UploadFromUrl extends UploadBase {
if ( $this->mAsync ) {
return array( 'status' => UploadBase::OK );
}
+
return parent::verifyUpload();
}
/**
* Wrapper around the parent function in order to defer checking warnings
* until the file really has been fetched.
- * @return Array
+ * @return array
*/
public function checkWarnings() {
if ( $this->mAsync ) {
$this->mIgnoreWarnings = false;
+
return array();
}
+
return parent::checkWarnings();
}
/**
* Wrapper around the parent function in order to defer checking protection
* until we are sure that the file can actually be uploaded
- * @param $user User
+ * @param User $user
* @return bool|mixed
*/
public function verifyTitlePermissions( $user ) {
if ( $this->mAsync ) {
return true;
}
+
return parent::verifyTitlePermissions( $user );
}
/**
* Wrapper around the parent function in order to defer uploading to the
* job queue for asynchronous uploads
- * @param $comment string
- * @param $pageText string
- * @param $watch bool
- * @param $user User
+ * @param string $comment
+ * @param string $pageText
+ * @param bool $watch
+ * @param User $user
* @return Status
*/
public function performUpload( $comment, $pageText, $watch, $user ) {
@@ -343,11 +357,11 @@ class UploadFromUrl extends UploadBase {
}
/**
- * @param $comment
- * @param $pageText
- * @param $watch
- * @param $user User
- * @return String
+ * @param string $comment
+ * @param string $pageText
+ * @param bool $watch
+ * @param User $user
+ * @return string
*/
protected function insertJob( $comment, $pageText, $watch, $user ) {
$sessionKey = $this->stashSession();
@@ -364,7 +378,7 @@ class UploadFromUrl extends UploadBase {
) );
$job->initializeSessionData();
JobQueueGroup::singleton()->push( $job );
+
return $sessionKey;
}
-
}
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index ea117378..7d80b448 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -23,26 +23,35 @@
/**
* UploadStash is intended to accomplish a few things:
- * - enable applications to temporarily stash files without publishing them to the wiki.
- * - Several parts of MediaWiki do this in similar ways: UploadBase, UploadWizard, and FirefoggChunkedExtension
- * And there are several that reimplement stashing from scratch, in idiosyncratic ways. The idea is to unify them all here.
- * Mostly all of them are the same except for storing some custom fields, which we subsume into the data array.
- * - enable applications to find said files later, as long as the db table or temp files haven't been purged.
- * - enable the uploading user (and *ONLY* the uploading user) to access said files, and thumbnails of said files, via a URL.
- * We accomplish this using a database table, with ownership checking as you might expect. See SpecialUploadStash, which
- * implements a web interface to some files stored this way.
+ * - Enable applications to temporarily stash files without publishing them to
+ * the wiki.
+ * - Several parts of MediaWiki do this in similar ways: UploadBase,
+ * UploadWizard, and FirefoggChunkedExtension.
+ * And there are several that reimplement stashing from scratch, in
+ * idiosyncratic ways. The idea is to unify them all here.
+ * Mostly all of them are the same except for storing some custom fields,
+ * which we subsume into the data array.
+ * - Enable applications to find said files later, as long as the db table or
+ * temp files haven't been purged.
+ * - Enable the uploading user (and *ONLY* the uploading user) to access said
+ * files, and thumbnails of said files, via a URL. We accomplish this using
+ * a database table, with ownership checking as you might expect. See
+ * SpecialUploadStash, which implements a web interface to some files stored
+ * this way.
*
- * UploadStash right now is *mostly* intended to show you one user's slice of the entire stash. The user parameter is only optional
- * because there are few cases where we clean out the stash from an automated script. In the future we might refactor this.
+ * UploadStash right now is *mostly* intended to show you one user's slice of
+ * the entire stash. The user parameter is only optional because there are few
+ * cases where we clean out the stash from an automated script. In the future we
+ * might refactor this.
*
* UploadStash represents the entire stash of temporary files.
* UploadStashFile is a filestore for the actual physical disk files.
- * UploadFromStash extends UploadBase, and represents a single stashed file as it is moved from the stash to the regular file repository
+ * UploadFromStash extends UploadBase, and represents a single stashed file as
+ * it is moved from the stash to the regular file repository
*
* @ingroup Upload
*/
class UploadStash {
-
// Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg)
const KEY_FORMAT_REGEX = '/^[\w-\.]+\.\w*$/';
@@ -71,8 +80,8 @@ class UploadStash {
* Designed to be compatible with the session stashing code in UploadBase
* (should replace it eventually).
*
- * @param $repo FileRepo
- * @param $user User (default null)
+ * @param FileRepo $repo
+ * @param User $user (default null)
*/
public function __construct( FileRepo $repo, $user = null ) {
// this might change based on wiki's configuration.
@@ -95,10 +104,11 @@ class UploadStash {
/**
* Get a file and its metadata from the stash.
- * The noAuth param is a bit janky but is required for automated scripts which clean out the stash.
+ * The noAuth param is a bit janky but is required for automated scripts
+ * which clean out the stash.
*
- * @param string $key key under which file information is stored
- * @param $noAuth Boolean (optional) Don't check authentication. Used by maintenance scripts.
+ * @param string $key Key under which file information is stored
+ * @param bool $noAuth (optional) Don't check authentication. Used by maintenance scripts.
* @throws UploadStashFileNotFoundException
* @throws UploadStashNotLoggedInException
* @throws UploadStashWrongOwnerException
@@ -106,7 +116,7 @@ class UploadStash {
* @return UploadStashFile
*/
public function getFile( $key, $noAuth = false ) {
- if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
+ if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
}
@@ -117,7 +127,8 @@ class UploadStash {
if ( !isset( $this->fileMetadata[$key] ) ) {
if ( !$this->fetchFileMetadata( $key ) ) {
- // If nothing was received, it's likely due to replication lag. Check the master to see if the record is there.
+ // If nothing was received, it's likely due to replication lag.
+ // Check the master to see if the record is there.
$this->fetchFileMetadata( $key, DB_MASTER );
}
@@ -138,14 +149,15 @@ class UploadStash {
}
}
- if ( ! $this->files[$key]->exists() ) {
+ if ( !$this->files[$key]->exists() ) {
wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist\n" );
throw new UploadStashBadPathException( "path doesn't exist" );
}
if ( !$noAuth ) {
if ( $this->fileMetadata[$key]['us_user'] != $this->userId ) {
- throw new UploadStashWrongOwnerException( "This file ($key) doesn't belong to the current user." );
+ throw new UploadStashWrongOwnerException( "This file ($key) doesn't "
+ . "belong to the current user." );
}
}
@@ -155,34 +167,38 @@ class UploadStash {
/**
* Getter for file metadata.
*
- * @param string $key key under which file information is stored
- * @return Array
+ * @param string $key Key under which file information is stored
+ * @return array
*/
public function getMetadata( $key ) {
$this->getFile( $key );
+
return $this->fileMetadata[$key];
}
/**
* Getter for fileProps
*
- * @param string $key key under which file information is stored
- * @return Array
+ * @param string $key Key under which file information is stored
+ * @return array
*/
public function getFileProps( $key ) {
$this->getFile( $key );
+
return $this->fileProps[$key];
}
/**
- * Stash a file in a temp directory and record that we did this in the database, along with other metadata.
+ * Stash a file in a temp directory and record that we did this in the
+ * database, along with other metadata.
*
- * @param string $path path to file you want stashed
- * @param string $sourceType the type of upload that generated this file (currently, I believe, 'file' or null)
+ * @param string $path Path to file you want stashed
+ * @param string $sourceType The type of upload that generated this file
+ * (currently, I believe, 'file' or null)
* @throws UploadStashBadPathException
* @throws UploadStashFileException
* @throws UploadStashNotLoggedInException
- * @return UploadStashFile: file, or null on failure
+ * @return UploadStashFile|null File, or null on failure
*/
public function stashFile( $path, $sourceType = null ) {
if ( !is_file( $path ) ) {
@@ -201,10 +217,11 @@ class UploadStash {
$pathWithGoodExtension = $path;
}
- // If no key was supplied, make one. a mysql insertid would be totally reasonable here, except
- // that for historical reasons, the key is this random thing instead. At least it's not guessable.
+ // If no key was supplied, make one. a mysql insertid would be totally
+ // reasonable here, except that for historical reasons, the key is this
+ // random thing instead. At least it's not guessable.
//
- // some things that when combined will make a suitably unique key.
+ // Some things that when combined will make a suitably unique key.
// see: http://www.jwz.org/doc/mid.html
list( $usec, $sec ) = explode( ' ', microtime() );
$usec = substr( $usec, 2 );
@@ -215,7 +232,7 @@ class UploadStash {
$this->fileProps[$key] = $fileProps;
- if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
+ if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
}
@@ -224,30 +241,36 @@ class UploadStash {
// if not already in a temporary area, put it there
$storeStatus = $this->repo->storeTemp( basename( $pathWithGoodExtension ), $path );
- if ( ! $storeStatus->isOK() ) {
- // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors
- // are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings.
- // This is a bit lame, as we may have more info in the $storeStatus and we're throwing it away, but to fix it means
+ if ( !$storeStatus->isOK() ) {
+ // It is a convention in MediaWiki to only return one error per API
+ // exception, even if multiple errors are available. We use reset()
+ // to pick the "first" thing that was wrong, preferring errors to
+ // warnings. This is a bit lame, as we may have more info in the
+ // $storeStatus and we're throwing it away, but to fix it means
// redesigning API errors significantly.
- // $storeStatus->value just contains the virtual URL (if anything) which is probably useless to the caller
+ // $storeStatus->value just contains the virtual URL (if anything)
+ // which is probably useless to the caller.
$error = $storeStatus->getErrorsArray();
$error = reset( $error );
- if ( ! count( $error ) ) {
+ if ( !count( $error ) ) {
$error = $storeStatus->getWarningsArray();
$error = reset( $error );
- if ( ! count( $error ) ) {
+ if ( !count( $error ) ) {
$error = array( 'unknown', 'no error recorded' );
}
}
- // at this point, $error should contain the single "most important" error, plus any parameters.
+ // At this point, $error should contain the single "most important"
+ // error, plus any parameters.
$errorMsg = array_shift( $error );
- throw new UploadStashFileException( "Error storing file in '$path': " . wfMessage( $errorMsg, $error )->text() );
+ throw new UploadStashFileException( "Error storing file in '$path': "
+ . wfMessage( $errorMsg, $error )->text() );
}
$stashPath = $storeStatus->value;
// fetch the current user ID
if ( !$this->isLoggedIn ) {
- throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ throw new UploadStashNotLoggedInException( __METHOD__
+ . ' No user is logged in, files must belong to users' );
}
// insert the file metadata into the db.
@@ -279,7 +302,8 @@ class UploadStash {
__METHOD__
);
- // store the insertid in the class variable so immediate retrieval (possibly laggy) isn't necesary.
+ // store the insertid in the class variable so immediate retrieval
+ // (possibly laggy) isn't necesary.
$this->fileMetadata[$key]['us_id'] = $dbw->insertId();
# create the UploadStashFile object for this file.
@@ -293,11 +317,12 @@ class UploadStash {
* Does not clean up files in the repo, just the record of them.
*
* @throws UploadStashNotLoggedInException
- * @return boolean: success
+ * @return bool Success
*/
public function clear() {
if ( !$this->isLoggedIn ) {
- throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ throw new UploadStashNotLoggedInException( __METHOD__
+ . ' No user is logged in, files must belong to users' );
}
wfDebug( __METHOD__ . ' clearing all rows for user ' . $this->userId . "\n" );
@@ -318,19 +343,21 @@ class UploadStash {
/**
* Remove a particular file from the stash. Also removes it from the repo.
*
- * @param $key
- * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException|UploadStashWrongOwnerException
- * @return boolean: success
+ * @param string $key
+ * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException
+ * @throws UploadStashWrongOwnerException
+ * @return bool Success
*/
public function removeFile( $key ) {
if ( !$this->isLoggedIn ) {
- throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ throw new UploadStashNotLoggedInException( __METHOD__
+ . ' No user is logged in, files must belong to users' );
}
$dbw = $this->repo->getMasterDb();
- // this is a cheap query. it runs on the master so that this function still works when there's lag.
- // it won't be called all that often.
+ // this is a cheap query. it runs on the master so that this function
+ // still works when there's lag. It won't be called all that often.
$row = $dbw->selectRow(
'uploadstash',
'us_user',
@@ -343,7 +370,8 @@ class UploadStash {
}
if ( $row->us_user != $this->userId ) {
- throw new UploadStashWrongOwnerException( "Can't delete: the file ($key) doesn't belong to this user." );
+ throw new UploadStashWrongOwnerException( "Can't delete: "
+ . "the file ($key) doesn't belong to this user." );
}
return $this->removeFileNoAuth( $key );
@@ -352,7 +380,8 @@ class UploadStash {
/**
* Remove a file (see removeFile), but doesn't check ownership first.
*
- * @return boolean: success
+ * @param string $key
+ * @return bool Success
*/
public function removeFileNoAuth( $key ) {
wfDebug( __METHOD__ . " clearing row $key\n" );
@@ -368,8 +397,9 @@ class UploadStash {
__METHOD__
);
- // TODO: look into UnregisteredLocalFile and find out why the rv here is sometimes wrong (false when file was removed)
- // for now, ignore.
+ /** @todo Look into UnregisteredLocalFile and find out why the rv here is
+ * sometimes wrong (false when file was removed). For now, ignore.
+ */
$this->files[$key]->remove();
unset( $this->files[$key] );
@@ -382,11 +412,12 @@ class UploadStash {
* List all files in the stash.
*
* @throws UploadStashNotLoggedInException
- * @return Array
+ * @return array
*/
public function listFiles() {
if ( !$this->isLoggedIn ) {
- throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ throw new UploadStashNotLoggedInException( __METHOD__
+ . ' No user is logged in, files must belong to users' );
}
$dbr = $this->repo->getSlaveDb();
@@ -412,12 +443,12 @@ class UploadStash {
}
/**
- * Find or guess extension -- ensuring that our extension matches our mime type.
+ * Find or guess extension -- ensuring that our extension matches our MIME type.
* Since these files are constructed from php tempnames they may not start off
* with an extension.
* XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
* uploads versus the desired filename. Maybe we can get that passed to us...
- * @param $path
+ * @param string $path
* @throws UploadStashFileException
* @return string
*/
@@ -429,7 +460,7 @@ class UploadStash {
if ( $n !== false ) {
$extension = $n ? substr( $path, $n + 1 ) : '';
} else {
- // If not, assume that it should be related to the mime type of the original file.
+ // If not, assume that it should be related to the MIME type of the original file.
$magic = MimeMagic::singleton();
$mimeType = $magic->guessMimeType( $path );
$extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) );
@@ -450,15 +481,16 @@ class UploadStash {
// put it in a web accesible directory.
return '';
}
+
return $extension;
}
/**
* Helper function: do the actual database query to fetch file metadata.
*
- * @param string $key key
- * @param $readFromDB: constant (default: DB_SLAVE)
- * @return boolean
+ * @param string $key
+ * @param int $readFromDB Constant (default: DB_SLAVE)
+ * @return bool
*/
protected function fetchFileMetadata( $key, $readFromDB = DB_SLAVE ) {
// populate $fileMetadata[$key]
@@ -483,6 +515,7 @@ class UploadStash {
}
$this->fileMetadata[$key] = (array)$row;
+ $this->fileMetadata[$key]['us_props'] = $dbr->decodeBlob( $row->us_props );
return true;
}
@@ -490,7 +523,7 @@ class UploadStash {
/**
* Helper function: Initialize the UploadStashFile for a given file.
*
- * @param string $key key under which to store the object
+ * @param string $key Key under which to store the object
* @throws UploadStashZeroLengthFileException
* @return bool
*/
@@ -500,6 +533,7 @@ class UploadStash {
throw new UploadStashZeroLengthFileException( "File is zero length" );
}
$this->files[$key] = $file;
+
return true;
}
}
@@ -510,12 +544,14 @@ class UploadStashFile extends UnregisteredLocalFile {
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
+ * 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 $repo FileRepo: 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
+ * @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
*/
@@ -526,18 +562,21 @@ class UploadStashFile extends UnregisteredLocalFile {
if ( $repo->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.
+ // 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" );
+ 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( 'path is not valid' );
}
// 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" );
+ 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( 'cannot find path, or not a plain file' );
}
}
@@ -550,10 +589,10 @@ class UploadStashFile extends UnregisteredLocalFile {
/**
* 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)
+ * 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
+ * @return string Dummy value
*/
public function getDescriptionUrl() {
return $this->getUrl();
@@ -564,14 +603,17 @@ class UploadStashFile extends UnregisteredLocalFile {
* 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
+ * @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;
}
@@ -580,18 +622,19 @@ class UploadStashFile extends UnregisteredLocalFile {
* 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 $flags integer Bitfield that supports THUMB_* constants
- * @return String: base name for URL, like '120px-12345.jpg', or null if there is no handler
+ * @param array $params Handler-specific parameters
+ * @param int $flags Bitfield that supports THUMB_* constants
+ * @return string 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 $subPage String
- * @return String: local URL for this subpage in the Special:UploadStash space.
+ * 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();
@@ -600,14 +643,16 @@ class UploadStashFile extends UnregisteredLocalFile {
/**
* 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)
+ * 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
+ * @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 );
}
@@ -615,12 +660,13 @@ class UploadStashFile extends UnregisteredLocalFile {
* 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'
+ * @return string Base url name, like '120px-123456.jpg'
*/
public function getUrlName() {
- if ( ! $this->urlName ) {
+ if ( !$this->urlName ) {
$this->urlName = $this->fileKey;
}
+
return $this->urlName;
}
@@ -628,29 +674,32 @@ class UploadStashFile extends UnregisteredLocalFile {
* 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
+ * @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.
+ * 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
+ * @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)
+ * Getter for file key (the unique id by which this file's location &
+ * metadata is stored in the db)
*
- * @return String: file key
+ * @return string File key
*/
public function getFileKey() {
return $this->fileKey;
@@ -658,7 +707,7 @@ class UploadStashFile extends UnregisteredLocalFile {
/**
* Remove the associated temporary file
- * @return Status: success
+ * @return status Success
*/
public function remove() {
if ( !$this->repo->fileExists( $this->path ) ) {
@@ -672,15 +721,32 @@ class UploadStashFile extends UnregisteredLocalFile {
public function exists() {
return $this->repo->fileExists( $this->path );
}
+}
+
+class UploadStashException extends MWException {
+}
+
+class UploadStashNotAvailableException extends UploadStashException {
+}
+
+class UploadStashFileNotFoundException extends UploadStashException {
+}
+
+class UploadStashBadPathException extends UploadStashException {
+}
+
+class UploadStashFileException extends UploadStashException {
+}
+
+class UploadStashZeroLengthFileException extends UploadStashException {
+}
+
+class UploadStashNotLoggedInException extends UploadStashException {
+}
+
+class UploadStashWrongOwnerException extends UploadStashException {
+}
+class UploadStashNoSuchKeyException extends UploadStashException {
}
-class UploadStashException extends MWException {};
-class UploadStashNotAvailableException extends UploadStashException {};
-class UploadStashFileNotFoundException extends UploadStashException {};
-class UploadStashBadPathException extends UploadStashException {};
-class UploadStashFileException extends UploadStashException {};
-class UploadStashZeroLengthFileException extends UploadStashException {};
-class UploadStashNotLoggedInException extends UploadStashException {};
-class UploadStashWrongOwnerException extends UploadStashException {};
-class UploadStashNoSuchKeyException extends UploadStashException {};
diff --git a/includes/utils/ArrayUtils.php b/includes/utils/ArrayUtils.php
new file mode 100644
index 00000000..1e521cb8
--- /dev/null
+++ b/includes/utils/ArrayUtils.php
@@ -0,0 +1,187 @@
+<?php
+/**
+ * Methods to play with arrays.
+ *
+ * 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
+ */
+
+/**
+ * A collection of static methods to play with arrays.
+ *
+ * @since 1.21
+ */
+class ArrayUtils {
+ /**
+ * Sort the given array in a pseudo-random order which depends only on the
+ * given key and each element value. This is typically used for load
+ * balancing between servers each with a local cache.
+ *
+ * Keys are preserved. The input array is modified in place.
+ *
+ * Note: Benchmarking on PHP 5.3 and 5.4 indicates that for small
+ * strings, md5() is only 10% slower than hash('joaat',...) etc.,
+ * since the function call overhead dominates. So there's not much
+ * justification for breaking compatibility with installations
+ * compiled with ./configure --disable-hash.
+ *
+ * @param array $array Array to sort
+ * @param string $key
+ * @param string $separator A separator used to delimit the array elements and the
+ * key. This can be chosen to provide backwards compatibility with
+ * various consistent hash implementations that existed before this
+ * function was introduced.
+ */
+ public static function consistentHashSort( &$array, $key, $separator = "\000" ) {
+ $hashes = array();
+ foreach ( $array as $elt ) {
+ $hashes[$elt] = md5( $elt . $separator . $key );
+ }
+ uasort( $array, function ( $a, $b ) use ( $hashes ) {
+ return strcmp( $hashes[$a], $hashes[$b] );
+ } );
+ }
+
+ /**
+ * Given an array of non-normalised probabilities, this function will select
+ * an element and return the appropriate key
+ *
+ * @param array $weights
+ * @return bool|int|string
+ */
+ public static function pickRandom( $weights ) {
+ if ( !is_array( $weights ) || count( $weights ) == 0 ) {
+ return false;
+ }
+
+ $sum = array_sum( $weights );
+ if ( $sum == 0 ) {
+ # No loads on any of them
+ # In previous versions, this triggered an unweighted random selection,
+ # but this feature has been removed as of April 2006 to allow for strict
+ # separation of query groups.
+ return false;
+ }
+ $max = mt_getrandmax();
+ $rand = mt_rand( 0, $max ) / $max * $sum;
+
+ $sum = 0;
+ foreach ( $weights as $i => $w ) {
+ $sum += $w;
+ # Do not return keys if they have 0 weight.
+ # Note that the "all 0 weight" case is handed above
+ if ( $w > 0 && $sum >= $rand ) {
+ break;
+ }
+ }
+
+ return $i;
+ }
+
+ /**
+ * Do a binary search, and return the index of the largest item that sorts
+ * less than or equal to the target value.
+ *
+ * @since 1.23
+ *
+ * @param array $valueCallback A function to call to get the value with
+ * a given array index.
+ * @param int $valueCount The number of items accessible via $valueCallback,
+ * indexed from 0 to $valueCount - 1
+ * @param array $comparisonCallback A callback to compare two values, returning
+ * -1, 0 or 1 in the style of strcmp().
+ * @param string $target The target value to find.
+ *
+ * @return int|bool The item index of the lower bound, or false if the target value
+ * sorts before all items.
+ */
+ public static function findLowerBound( $valueCallback, $valueCount,
+ $comparisonCallback, $target
+ ) {
+ if ( $valueCount === 0 ) {
+ return false;
+ }
+
+ $min = 0;
+ $max = $valueCount;
+ do {
+ $mid = $min + ( ( $max - $min ) >> 1 );
+ $item = call_user_func( $valueCallback, $mid );
+ $comparison = call_user_func( $comparisonCallback, $target, $item );
+ if ( $comparison > 0 ) {
+ $min = $mid;
+ } elseif ( $comparison == 0 ) {
+ $min = $mid;
+ break;
+ } else {
+ $max = $mid;
+ }
+ } while ( $min < $max - 1 );
+
+ if ( $min == 0 ) {
+ $item = call_user_func( $valueCallback, $min );
+ $comparison = call_user_func( $comparisonCallback, $target, $item );
+ if ( $comparison < 0 ) {
+ // Before the first item
+ return false;
+ }
+ }
+ return $min;
+ }
+
+ /**
+ * Do array_diff_assoc() on multi-dimensional arrays.
+ *
+ * Note: empty arrays are removed.
+ *
+ * @since 1.23
+ *
+ * @param array $array1 The array to compare from
+ * @param array $array2,... 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 );
+ $ret = array();
+
+ foreach ( $array1 as $key => $value ) {
+ if ( is_array( $value ) ) {
+ $args = array( $value );
+ foreach ( $arrays as $array ) {
+ if ( isset( $array[$key] ) ) {
+ $args[] = $array[$key];
+ }
+ }
+ $valueret = call_user_func_array( __METHOD__, $args );
+ if ( count( $valueret ) ) {
+ $ret[$key] = $valueret;
+ }
+ } else {
+ foreach ( $arrays as $array ) {
+ if ( isset( $array[$key] ) && $array[$key] === $value ) {
+ continue 2;
+ }
+ }
+ $ret[$key] = $value;
+ }
+ }
+
+ return $ret;
+ }
+}
diff --git a/includes/Cdb.php b/includes/utils/Cdb.php
index 81c0afe1..3ceb620f 100644
--- a/includes/Cdb.php
+++ b/includes/utils/Cdb.php
@@ -27,19 +27,21 @@
*/
abstract class CdbReader {
/**
+ * The file handle
+ */
+ protected $handle;
+
+ /**
* Open a file and return a subclass instance
*
- * @param $fileName string
+ * @param string $fileName
*
* @return CdbReader
*/
public static function open( $fileName ) {
- if ( self::haveExtension() ) {
- return new CdbReader_DBA( $fileName );
- } else {
- wfDebug( "Warning: no dba extension found, using emulation.\n" );
- return new CdbReader_PHP( $fileName );
- }
+ return self::haveExtension() ?
+ new CdbReaderDBA( $fileName ) :
+ new CdbReaderPHP( $fileName );
}
/**
@@ -55,23 +57,26 @@ abstract class CdbReader {
if ( !in_array( 'cdb', $handlers ) || !in_array( 'cdb_make', $handlers ) ) {
return false;
}
+
return true;
}
/**
- * Construct the object and open the file
+ * Create the object and open the file
+ *
+ * @param string $fileName
*/
- abstract function __construct( $fileName );
+ abstract public function __construct( $fileName );
/**
* Close the file. Optional, you can just let the variable go out of scope.
*/
- abstract function close();
+ abstract public function close();
/**
* Get a value with a given key. Only string values are supported.
*
- * @param $key string
+ * @param string $key
*/
abstract public function get( $key );
}
@@ -82,33 +87,47 @@ abstract class CdbReader {
*/
abstract class CdbWriter {
/**
+ * The file handle
+ */
+ protected $handle;
+
+ /**
+ * File we'll be writing to when we're done
+ * @var string
+ */
+ protected $realFileName;
+
+ /**
+ * File we write to temporarily until we're done
+ * @var string
+ */
+ protected $tmpFileName;
+
+ /**
* Open a writer and return a subclass instance.
* The user must have write access to the directory, for temporary file creation.
*
- * @param $fileName string
+ * @param string $fileName
*
- * @return CdbWriter_DBA|CdbWriter_PHP
+ * @return CdbWriterDBA|CdbWriterPHP
*/
public static function open( $fileName ) {
- if ( CdbReader::haveExtension() ) {
- return new CdbWriter_DBA( $fileName );
- } else {
- wfDebug( "Warning: no dba extension found, using emulation.\n" );
- return new CdbWriter_PHP( $fileName );
- }
+ return CdbReader::haveExtension() ?
+ new CdbWriterDBA( $fileName ) :
+ new CdbWriterPHP( $fileName );
}
/**
* Create the object and open the file
*
- * @param $fileName string
+ * @param string $fileName
*/
- abstract function __construct( $fileName );
+ abstract public function __construct( $fileName );
/**
* Set a key to a given value. The value will be converted to string.
- * @param $key string
- * @param $value string
+ * @param string $key
+ * @param string $value
*/
abstract public function set( $key, $value );
@@ -117,68 +136,28 @@ abstract class CdbWriter {
* goes out of scope, to write out the final hashtables.
*/
abstract public function close();
-}
-
-/**
- * Reader class which uses the DBA extension
- */
-class CdbReader_DBA {
- var $handle;
-
- function __construct( $fileName ) {
- $this->handle = dba_open( $fileName, 'r-', 'cdb' );
- if ( !$this->handle ) {
- throw new MWException( 'Unable to open CDB file "' . $fileName . '"' );
- }
- }
- function close() {
+ /**
+ * If the object goes out of scope, close it for sanity
+ */
+ public function __destruct() {
if ( isset( $this->handle ) ) {
- dba_close( $this->handle );
+ $this->close();
}
- unset( $this->handle );
}
- function get( $key ) {
- return dba_fetch( $key, $this->handle );
+ /**
+ * Are we running on Windows?
+ * @return bool
+ */
+ protected function isWindows() {
+ return substr( php_uname(), 0, 7 ) == 'Windows';
}
}
/**
- * Writer class which uses the DBA extension
+ * Exception for Cdb errors.
+ * This explicitly doesn't subclass MWException to encourage reuse.
*/
-class CdbWriter_DBA {
- var $handle, $realFileName, $tmpFileName;
-
- function __construct( $fileName ) {
- $this->realFileName = $fileName;
- $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
- $this->handle = dba_open( $this->tmpFileName, 'n', 'cdb_make' );
- if ( !$this->handle ) {
- throw new MWException( 'Unable to open CDB file for write "' . $fileName . '"' );
- }
- }
-
- function set( $key, $value ) {
- return dba_insert( $key, $value, $this->handle );
- }
-
- function close() {
- if ( isset( $this->handle ) ) {
- dba_close( $this->handle );
- }
- if ( wfIsWindows() ) {
- unlink( $this->realFileName );
- }
- if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
- throw new MWException( 'Unable to move the new CDB file into place.' );
- }
- unset( $this->handle );
- }
-
- function __destruct() {
- if ( isset( $this->handle ) ) {
- $this->close();
- }
- }
+class CdbException extends Exception {
}
diff --git a/includes/utils/CdbDBA.php b/includes/utils/CdbDBA.php
new file mode 100644
index 00000000..efcaf21f
--- /dev/null
+++ b/includes/utils/CdbDBA.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * DBA-based CDB reader/writer
+ *
+ * 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
+ */
+
+/**
+ * Reader class which uses the DBA extension
+ */
+class CdbReaderDBA extends CdbReader {
+ public function __construct( $fileName ) {
+ $this->handle = dba_open( $fileName, 'r-', 'cdb' );
+ if ( !$this->handle ) {
+ throw new CdbException( 'Unable to open CDB file "' . $fileName . '"' );
+ }
+ }
+
+ public function close() {
+ if ( isset( $this->handle ) ) {
+ dba_close( $this->handle );
+ }
+ unset( $this->handle );
+ }
+
+ public function get( $key ) {
+ return dba_fetch( $key, $this->handle );
+ }
+}
+
+/**
+ * Writer class which uses the DBA extension
+ */
+class CdbWriterDBA extends CdbWriter {
+ public function __construct( $fileName ) {
+ $this->realFileName = $fileName;
+ $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
+ $this->handle = dba_open( $this->tmpFileName, 'n', 'cdb_make' );
+ if ( !$this->handle ) {
+ throw new CdbException( 'Unable to open CDB file for write "' . $fileName . '"' );
+ }
+ }
+
+ public function set( $key, $value ) {
+ return dba_insert( $key, $value, $this->handle );
+ }
+
+ public function close() {
+ if ( isset( $this->handle ) ) {
+ dba_close( $this->handle );
+ }
+ if ( $this->isWindows() ) {
+ unlink( $this->realFileName );
+ }
+ if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
+ throw new CdbException( 'Unable to move the new CDB file into place.' );
+ }
+ unset( $this->handle );
+ }
+}
diff --git a/includes/Cdb_PHP.php b/includes/utils/CdbPHP.php
index a38b9a86..19d747a7 100644
--- a/includes/Cdb_PHP.php
+++ b/includes/utils/CdbPHP.php
@@ -32,14 +32,15 @@ class CdbFunctions {
* Take a modulo of a signed integer as if it were an unsigned integer.
* $b must be less than 0x40000000 and greater than 0
*
- * @param $a
- * @param $b
+ * @param int $a
+ * @param int $b
*
* @return int
*/
public static function unsignedMod( $a, $b ) {
if ( $a & 0x80000000 ) {
$m = ( $a & 0x7fffffff ) % $b + 2 * ( 0x40000000 % $b );
+
return $m % $b;
} else {
return $a % $b;
@@ -48,8 +49,8 @@ class CdbFunctions {
/**
* Shift a signed integer right as if it were unsigned
- * @param $a
- * @param $b
+ * @param int $a
+ * @param int $b
* @return int
*/
public static function unsignedShiftRight( $a, $b ) {
@@ -66,13 +67,14 @@ class CdbFunctions {
/**
* The CDB hash function.
*
- * @param $s string
+ * @param string $s
*
- * @return
+ * @return int
*/
public static function hash( $s ) {
$h = 5381;
- for ( $i = 0; $i < strlen( $s ); $i++ ) {
+ $len = strlen( $s );
+ for ( $i = 0; $i < $len; $i++ ) {
$h5 = ( $h << 5 ) & 0xffffffff;
// Do a 32-bit sum
// Inlined here for speed
@@ -89,6 +91,7 @@ class CdbFunctions {
$h ^= ord( $s[$i] );
$h &= 0xffffffff;
}
+
return $h;
}
}
@@ -96,48 +99,45 @@ class CdbFunctions {
/**
* CDB reader class
*/
-class CdbReader_PHP extends CdbReader {
+class CdbReaderPHP extends CdbReader {
/** The filename */
- var $fileName;
-
- /** The file handle */
- var $handle;
+ protected $fileName;
/* number of hash slots searched under this key */
- var $loop;
+ protected $loop;
/* initialized if loop is nonzero */
- var $khash;
+ protected $khash;
/* initialized if loop is nonzero */
- var $kpos;
+ protected $kpos;
/* initialized if loop is nonzero */
- var $hpos;
+ protected $hpos;
/* initialized if loop is nonzero */
- var $hslots;
+ protected $hslots;
/* initialized if findNext() returns true */
- var $dpos;
+ protected $dpos;
/* initialized if cdb_findnext() returns 1 */
- var $dlen;
+ protected $dlen;
/**
- * @param $fileName string
- * @throws MWException
+ * @param string $fileName
+ * @throws CdbException
*/
- function __construct( $fileName ) {
+ public function __construct( $fileName ) {
$this->fileName = $fileName;
$this->handle = fopen( $fileName, 'rb' );
if ( !$this->handle ) {
- throw new MWException( 'Unable to open CDB file "' . $this->fileName . '".' );
+ throw new CdbException( 'Unable to open CDB file "' . $this->fileName . '".' );
}
$this->findStart();
}
- function close() {
+ public function close() {
if ( isset( $this->handle ) ) {
fclose( $this->handle );
}
@@ -145,7 +145,7 @@ class CdbReader_PHP extends CdbReader {
}
/**
- * @param $key
+ * @param mixed $key
* @return bool|string
*/
public function get( $key ) {
@@ -158,12 +158,13 @@ class CdbReader_PHP extends CdbReader {
}
/**
- * @param $key
- * @param $pos
+ * @param string $key
+ * @param int $pos
* @return bool
*/
protected function match( $key, $pos ) {
$buf = $this->read( strlen( $key ), $pos );
+
return $buf === $key;
}
@@ -172,15 +173,15 @@ class CdbReader_PHP extends CdbReader {
}
/**
- * @throws MWException
- * @param $length
- * @param $pos
+ * @throws CdbException
+ * @param int $length
+ * @param int $pos
* @return string
*/
protected function read( $length, $pos ) {
if ( fseek( $this->handle, $pos ) == -1 ) {
// This can easily happen if the internal pointers are incorrect
- throw new MWException(
+ throw new CdbException(
'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
}
@@ -190,39 +191,42 @@ class CdbReader_PHP extends CdbReader {
$buf = fread( $this->handle, $length );
if ( $buf === false || strlen( $buf ) !== $length ) {
- throw new MWException(
+ throw new CdbException(
'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' );
}
+
return $buf;
}
/**
* Unpack an unsigned integer and throw an exception if it needs more than 31 bits
- * @param $s
- * @throws MWException
+ * @param string $s
+ * @throws CdbException
* @return mixed
*/
protected function unpack31( $s ) {
$data = unpack( 'V', $s );
if ( $data[1] > 0x7fffffff ) {
- throw new MWException(
+ throw new CdbException(
'Error in CDB file "' . $this->fileName . '", integer too big.' );
}
+
return $data[1];
}
/**
* Unpack a 32-bit signed integer
- * @param $s
+ * @param string $s
* @return int
*/
protected function unpackSigned( $s ) {
$data = unpack( 'va/vb', $s );
+
return $data['a'] | ( $data['b'] << 16 );
}
/**
- * @param $key
+ * @param string $key
* @return bool
*/
protected function findNext( $key ) {
@@ -260,19 +264,22 @@ class CdbReader_PHP extends CdbReader {
// Found
$this->dlen = $this->unpack31( substr( $buf, 4 ) );
$this->dpos = $pos + 8 + $keyLen;
+
return true;
}
}
}
+
return false;
}
/**
- * @param $key
+ * @param mixed $key
* @return bool
*/
protected function find( $key ) {
$this->findStart();
+
return $this->findNext( $key );
}
}
@@ -280,16 +287,17 @@ class CdbReader_PHP extends CdbReader {
/**
* CDB writer class
*/
-class CdbWriter_PHP extends CdbWriter {
- var $handle, $realFileName, $tmpFileName;
+class CdbWriterPHP extends CdbWriter {
+ protected $hplist;
+
+ protected $numentries;
- var $hplist;
- var $numentries, $pos;
+ protected $pos;
/**
- * @param $fileName string
+ * @param string $fileName
*/
- function __construct( $fileName ) {
+ public function __construct( $fileName ) {
$this->realFileName = $fileName;
$this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
$this->handle = fopen( $this->tmpFileName, 'wb' );
@@ -305,16 +313,9 @@ class CdbWriter_PHP extends CdbWriter {
}
}
- function __destruct() {
- if ( isset( $this->handle ) ) {
- $this->close();
- }
- }
-
/**
- * @param $key
- * @param $value
- * @return
+ * @param string $key
+ * @param string $value
*/
public function set( $key, $value ) {
if ( strval( $key ) === '' ) {
@@ -328,14 +329,14 @@ class CdbWriter_PHP extends CdbWriter {
}
/**
- * @throws MWException
+ * @throws CdbException
*/
public function close() {
$this->finish();
if ( isset( $this->handle ) ) {
fclose( $this->handle );
}
- if ( wfIsWindows() && file_exists( $this->realFileName ) ) {
+ if ( $this->isWindows() && file_exists( $this->realFileName ) ) {
unlink( $this->realFileName );
}
if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
@@ -345,8 +346,8 @@ class CdbWriter_PHP extends CdbWriter {
}
/**
- * @throws MWException
- * @param $buf
+ * @throws CdbException
+ * @param string $buf
*/
protected function write( $buf ) {
$len = fwrite( $this->handle, $buf );
@@ -356,8 +357,8 @@ class CdbWriter_PHP extends CdbWriter {
}
/**
- * @throws MWException
- * @param $len
+ * @throws CdbException
+ * @param int $len
*/
protected function posplus( $len ) {
$newpos = $this->pos + $len;
@@ -369,9 +370,9 @@ class CdbWriter_PHP extends CdbWriter {
}
/**
- * @param $keylen
- * @param $datalen
- * @param $h
+ * @param int $keylen
+ * @param int $datalen
+ * @param int $h
*/
protected function addend( $keylen, $datalen, $h ) {
$this->hplist[] = array(
@@ -386,9 +387,9 @@ class CdbWriter_PHP extends CdbWriter {
}
/**
- * @throws MWException
- * @param $keylen
- * @param $datalen
+ * @throws CdbException
+ * @param int $keylen
+ * @param int $datalen
*/
protected function addbegin( $keylen, $datalen ) {
if ( $keylen > 0x7fffffff ) {
@@ -402,7 +403,7 @@ class CdbWriter_PHP extends CdbWriter {
}
/**
- * @throws MWException
+ * @throws CdbException
*/
protected function finish() {
// Hack for DBA cross-check
@@ -411,7 +412,7 @@ class CdbWriter_PHP extends CdbWriter {
// Calculate the number of items that will be in each hashtable
$counts = array_fill( 0, 256, 0 );
foreach ( $this->hplist as $item ) {
- ++ $counts[255 & $item['h']];
+ ++$counts[255 & $item['h']];
}
// Fill in $starts with the *end* indexes
@@ -480,14 +481,14 @@ class CdbWriter_PHP extends CdbWriter {
/**
* Clean up the temp file and throw an exception
*
- * @param $msg string
- * @throws MWException
+ * @param string $msg
+ * @throws CdbException
*/
protected function throwException( $msg ) {
if ( $this->handle ) {
fclose( $this->handle );
unlink( $this->tmpFileName );
}
- throw new MWException( $msg );
+ throw new CdbException( $msg );
}
}
diff --git a/includes/IP.php b/includes/utils/IP.php
index 73834a59..0e2db8cc 100644
--- a/includes/IP.php
+++ b/includes/utils/IP.php
@@ -65,13 +65,16 @@ define( 'IP_ADDRESS_STRING',
* and IP blocks.
*/
class IP {
+ /** @var IPSet */
+ private static $proxyIpSet = null;
+
/**
* Determine if a string is as valid IP address or network (CIDR prefix).
* SIIT IPv4-translated addresses are rejected.
* Note: canonicalize() tries to convert translated addresses to IPv4.
*
- * @param string $ip possible IP address
- * @return Boolean
+ * @param string $ip Possible IP address
+ * @return bool
*/
public static function isIPAddress( $ip ) {
return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
@@ -81,8 +84,8 @@ class IP {
* Given a string, determine if it as valid IP in IPv6 only.
* Note: Unlike isValid(), this looks for networks too.
*
- * @param string $ip possible IP address
- * @return Boolean
+ * @param string $ip Possible IP address
+ * @return bool
*/
public static function isIPv6( $ip ) {
return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
@@ -92,8 +95,8 @@ class IP {
* Given a string, determine if it as valid IP in IPv4 only.
* Note: Unlike isValid(), this looks for networks too.
*
- * @param string $ip possible IP address
- * @return Boolean
+ * @param string $ip Possible IP address
+ * @return bool
*/
public static function isIPv4( $ip ) {
return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
@@ -104,8 +107,8 @@ class IP {
* SIIT IPv4-translated addresses are rejected.
* Note: canonicalize() tries to convert translated addresses to IPv4.
*
- * @param $ip String
- * @return Boolean: True if it is valid.
+ * @param string $ip
+ * @return bool True if it is valid
*/
public static function isValid( $ip ) {
return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
@@ -117,8 +120,8 @@ class IP {
* SIIT IPv4-translated addresses are rejected.
* Note: canonicalize() tries to convert translated addresses to IPv4.
*
- * @param $ipblock String
- * @return Boolean: True if it is valid.
+ * @param string $ipblock
+ * @return bool True if it is valid
*/
public static function isValidBlock( $ipblock ) {
return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
@@ -131,7 +134,7 @@ class IP {
* IPv4 addresses are just trimmed.
*
* @param string $ip IP address in quad or octet form (CIDR or not).
- * @return String
+ * @return string
*/
public static function sanitizeIP( $ip ) {
$ip = trim( $ip );
@@ -175,6 +178,7 @@ class IP {
}
// Remove leading zeros from each bloc as needed
$ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
+
return $ip;
}
@@ -182,7 +186,7 @@ class IP {
* Prettify an IP for display to end users.
* This will make it more compact and lower-case.
*
- * @param $ip string
+ * @param string $ip
* @return string
*/
public static function prettifyIP( $ip ) {
@@ -218,6 +222,7 @@ class IP {
// Convert to lower case to make it more readable
$ip = strtolower( $ip );
}
+
return $ip;
}
@@ -270,6 +275,7 @@ class IP {
return false;
}
}
+
// Plain hostname
return array( $both, false );
}
@@ -280,9 +286,9 @@ class IP {
* brackets like in RFC 2732. If the port matches the default port, omit
* the port specification
*
- * @param $host string
- * @param $port int
- * @param $defaultPort bool|int
+ * @param string $host
+ * @param int $port
+ * @param bool|int $defaultPort
* @return string
*/
public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
@@ -297,20 +303,10 @@ class IP {
}
/**
- * Given an unsigned integer, returns an IPv6 address in octet notation
- *
- * @param $ip_int String: IP address.
- * @return String
- */
- public static function toOctet( $ip_int ) {
- return self::hexToOctet( wfBaseConvert( $ip_int, 10, 16, 32, false ) );
- }
-
- /**
* Convert an IPv4 or IPv6 hexadecimal representation back to readable format
*
- * @param string $hex number, with "v6-" prefix if it is IPv6
- * @return String: quad-dotted (IPv4) or octet notation (IPv6)
+ * @param string $hex Number, with "v6-" prefix if it is IPv6
+ * @return string Quad-dotted (IPv4) or octet notation (IPv6)
*/
public static function formatHex( $hex ) {
if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
@@ -323,8 +319,8 @@ class IP {
/**
* Converts a hexadecimal number to an IPv6 address in octet notation
*
- * @param $ip_hex String: pure hex (no v6- prefix)
- * @return String (of format a:b:c:d:e:f:g:h)
+ * @param string $ip_hex Pure hex (no v6- prefix)
+ * @return string (of format a:b:c:d:e:f:g:h)
*/
public static function hexToOctet( $ip_hex ) {
// Pad hex to 32 chars (128 bits)
@@ -336,14 +332,15 @@ class IP {
}
// NO leading zeroes
$ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
+
return $ip_oct;
}
/**
* Converts a hexadecimal number to an IPv4 address in quad-dotted notation
*
- * @param $ip_hex String: pure hex
- * @return String (of format a.b.c.d)
+ * @param string $ip_hex Pure hex
+ * @return string (of format a.b.c.d)
*/
public static function hexToQuad( $ip_hex ) {
// Pad hex to 8 chars (32 bits)
@@ -356,77 +353,31 @@ class IP {
}
$s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
}
+
return $s;
}
/**
* Determine if an IP address really is an IP address, and if it is public,
* i.e. not RFC 1918 or similar
- * Comes from ProxyTools.php
*
- * @param $ip String
- * @return Boolean
+ * @param string $ip
+ * @return bool
*/
public static function isPublic( $ip ) {
- if ( self::isIPv6( $ip ) ) {
- return self::isPublic6( $ip );
- }
- $n = self::toUnsigned( $ip );
- if ( !$n ) {
- return false;
- }
-
- // ip2long accepts incomplete addresses, as well as some addresses
- // followed by garbage characters. Check that it's really valid.
- if ( $ip != long2ip( $n ) ) {
- return false;
- }
-
- static $privateRanges = false;
- if ( !$privateRanges ) {
- $privateRanges = array(
- array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
- array( '172.16.0.0', '172.31.255.255' ), # RFC 1918 (private)
- array( '192.168.0.0', '192.168.255.255' ), # RFC 1918 (private)
- array( '0.0.0.0', '0.255.255.255' ), # this network
- array( '127.0.0.0', '127.255.255.255' ), # loopback
- );
- }
-
- foreach ( $privateRanges as $r ) {
- $start = self::toUnsigned( $r[0] );
- $end = self::toUnsigned( $r[1] );
- if ( $n >= $start && $n <= $end ) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Determine if an IPv6 address really is an IP address, and if it is public,
- * i.e. not RFC 4193 or similar
- *
- * @param $ip String
- * @return Boolean
- */
- private static function isPublic6( $ip ) {
- static $privateRanges = false;
- if ( !$privateRanges ) {
- $privateRanges = array(
- array( 'fc00::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ), # RFC 4193 (local)
- array( '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1' ), # loopback
- );
- }
- $n = self::toHex( $ip );
- foreach ( $privateRanges as $r ) {
- $start = self::toHex( $r[0] );
- $end = self::toHex( $r[1] );
- if ( $n >= $start && $n <= $end ) {
- return false;
- }
- }
- return true;
+ static $privateSet = null;
+ if ( !$privateSet ) {
+ $privateSet = new IPSet( array(
+ '10.0.0.0/8', # RFC 1918 (private)
+ '172.16.0.0/12', # RFC 1918 (private)
+ '192.168.0.0/16', # RFC 1918 (private)
+ '0.0.0.0/8', # this network
+ '127.0.0.0/8', # loopback
+ 'fc00::/7', # RFC 4193 (local)
+ '0:0:0:0:0:0:0:1', # loopback
+ ) );
+ }
+ return !$privateSet->match( $ip );
}
/**
@@ -437,70 +388,52 @@ class IP {
* function for an IPv6 address will be prefixed with "v6-", a non-
* hexadecimal string which sorts after the IPv4 addresses.
*
- * @param string $ip quad dotted/octet IP address.
- * @return String
+ * @param string $ip Quad dotted/octet IP address.
+ * @return string|bool False on failure
*/
public static function toHex( $ip ) {
if ( self::isIPv6( $ip ) ) {
$n = 'v6-' . self::IPv6ToRawHex( $ip );
- } else {
- $n = self::toUnsigned( $ip );
+ } elseif ( self::isIPv4( $ip ) ) {
+ // Bug 60035: an IP with leading 0's fails in ip2long sometimes (e.g. *.08)
+ $ip = preg_replace( '/(?<=\.)0+(?=[1-9])/', '', $ip );
+ $n = ip2long( $ip );
+ if ( $n < 0 ) {
+ $n += pow( 2, 32 );
+ # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
+ # so $n becomes a float. We convert it to string instead.
+ if ( is_float( $n ) ) {
+ $n = (string)$n;
+ }
+ }
if ( $n !== false ) {
- $n = wfBaseConvert( $n, 10, 16, 8, false );
+ # Floating points can handle the conversion; faster than wfBaseConvert()
+ $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) );
}
+ } else {
+ $n = false;
}
+
return $n;
}
/**
* Given an IPv6 address in octet notation, returns a pure hex string.
*
- * @param string $ip octet ipv6 IP address.
- * @return String: pure hex (uppercase)
+ * @param string $ip Octet ipv6 IP address.
+ * @return string|bool Pure hex (uppercase); false on failure
*/
private static function IPv6ToRawHex( $ip ) {
$ip = self::sanitizeIP( $ip );
if ( !$ip ) {
- return null;
+ return false;
}
$r_ip = '';
foreach ( explode( ':', $ip ) as $v ) {
$r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
}
- return $r_ip;
- }
- /**
- * Given an IP address in dotted-quad/octet notation, returns an unsigned integer.
- * Like ip2long() except that it actually works and has a consistent error return value.
- * Comes from ProxyTools.php
- *
- * @param string $ip quad dotted IP address.
- * @return Mixed: string/int/false
- */
- public static function toUnsigned( $ip ) {
- if ( self::isIPv6( $ip ) ) {
- $n = self::toUnsigned6( $ip );
- } else {
- $n = ip2long( $ip );
- if ( $n < 0 ) {
- $n += pow( 2, 32 );
- # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
- # so $n becomes a float. We convert it to string instead.
- if ( is_float( $n ) ) {
- $n = (string)$n;
- }
- }
- }
- return $n;
- }
-
- /**
- * @param $ip
- * @return String
- */
- private static function toUnsigned6( $ip ) {
- return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
+ return $r_ip;
}
/**
@@ -534,6 +467,7 @@ class IP {
$network = false;
$bits = false;
}
+
return array( $network, $bits );
}
@@ -546,9 +480,9 @@ class IP {
* 1.2.3.4 - 1.2.3.5 Explicit range
* 1.2.3.4 Single IP
*
- * 2001:0db8:85a3::7344/96 CIDR
+ * 2001:0db8:85a3::7344/96 CIDR
* 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
- * 2001:0db8:85a3::7344 Single IP
+ * 2001:0db8:85a3::7344 Single IP
* @param string $range IP range
* @return array(string, string)
*/
@@ -572,13 +506,10 @@ class IP {
return self::parseRange6( $range );
}
if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
- $start = self::toUnsigned( $start );
- $end = self::toUnsigned( $end );
+ $start = self::toHex( $start );
+ $end = self::toHex( $end );
if ( $start > $end ) {
$start = $end = false;
- } else {
- $start = sprintf( '%08X', $start );
- $end = sprintf( '%08X', $end );
}
} else {
$start = $end = false;
@@ -598,7 +529,7 @@ class IP {
* Convert a network specification in IPv6 CIDR notation to an
* integer network and a number of bits
*
- * @param $range
+ * @param string $range
*
* @return array(string, int)
*/
@@ -626,6 +557,7 @@ class IP {
$network = false;
$bits = false;
}
+
return array( $network, (int)$bits );
}
@@ -634,11 +566,11 @@ class IP {
* start and end of the range in hexadecimal. For IPv6.
*
* Formats are:
- * 2001:0db8:85a3::7344/96 CIDR
+ * 2001:0db8:85a3::7344/96 CIDR
* 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
- * 2001:0db8:85a3::7344/96 Single IP
+ * 2001:0db8:85a3::7344/96 Single IP
*
- * @param $range
+ * @param string $range
*
* @return array(string, string)
*/
@@ -665,17 +597,11 @@ class IP {
// Explicit range notation...
} elseif ( strpos( $range, '-' ) !== false ) {
list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
- $start = self::toUnsigned6( $start );
- $end = self::toUnsigned6( $end );
+ $start = self::toHex( $start );
+ $end = self::toHex( $end );
if ( $start > $end ) {
$start = $end = false;
- } else {
- $start = wfBaseConvert( $start, 10, 16, 32, false );
- $end = wfBaseConvert( $end, 10, 16, 32, false );
}
- # see toHex() comment
- $start = "v6-$start";
- $end = "v6-$end";
} else {
# Single IP
$start = $end = self::toHex( $range );
@@ -690,13 +616,14 @@ class IP {
/**
* Determine if a given IPv4/IPv6 address is in a given CIDR network
*
- * @param string $addr the address to check against the given range.
- * @param string $range the range to check the given address against.
- * @return Boolean: whether or not the given address is in the given range.
+ * @param string $addr The address to check against the given range.
+ * @param string $range The range to check the given address against.
+ * @return bool Whether or not the given address is in the given range.
*/
public static function isInRange( $addr, $range ) {
$hexIP = self::toHex( $addr );
list( $start, $end ) = self::parseRange( $range );
+
return ( strcmp( $hexIP, $start ) >= 0 &&
strcmp( $hexIP, $end ) <= 0 );
}
@@ -708,8 +635,8 @@ class IP {
* This currently only checks a few IPV4-to-IPv6 related cases. More
* unusual representations may be added later.
*
- * @param string $addr something that might be an IP address
- * @return String: valid dotted quad IPv4 address or null
+ * @param string $addr Something that might be an IP address
+ * @return string Valid dotted quad IPv4 address or null
*/
public static function canonicalize( $addr ) {
// remove zone info (bug 35738)
@@ -735,8 +662,8 @@ class IP {
return $m[1];
}
if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
- ':' . RE_IPV6_WORD . '$/i', $addr, $m ) )
- {
+ ':' . RE_IPV6_WORD . '$/i', $addr, $m )
+ ) {
return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
}
@@ -756,6 +683,56 @@ class IP {
if ( $bits === false ) {
return $start; // wasn't actually a range
}
+
return "$start/$bits";
}
+
+ /**
+ * Checks if an IP is a trusted proxy provider.
+ * Useful to tell if X-Forwarded-For data is possibly bogus.
+ * Squid cache servers for the site are whitelisted.
+ * @since 1.24
+ *
+ * @param string $ip
+ * @return bool
+ */
+ public static function isTrustedProxy( $ip ) {
+ $trusted = self::isConfiguredProxy( $ip );
+ wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) );
+ return $trusted;
+ }
+
+ /**
+ * Checks if an IP matches a proxy we've configured
+ * @since 1.24
+ *
+ * @param string $ip
+ * @return bool
+ */
+ public static function isConfiguredProxy( $ip ) {
+ global $wgSquidServers, $wgSquidServersNoPurge;
+
+ wfProfileIn( __METHOD__ );
+ // Quick check of known singular proxy servers
+ $trusted = in_array( $ip, $wgSquidServers );
+
+ // Check against addresses and CIDR nets in the NoPurge list
+ if ( !$trusted ) {
+ if ( !self::$proxyIpSet ) {
+ self::$proxyIpSet = new IPSet( $wgSquidServersNoPurge );
+ }
+ $trusted = self::$proxyIpSet->match( $ip );
+ }
+ wfProfileOut( __METHOD__ );
+
+ return $trusted;
+ }
+
+ /**
+ * Clears precomputed data used for proxy support.
+ * Use this only for unit tests.
+ */
+ public static function clearCaches() {
+ self::$proxyIpSet = null;
+ }
}
diff --git a/includes/utils/MWCryptHKDF.php b/includes/utils/MWCryptHKDF.php
new file mode 100644
index 00000000..cc136793
--- /dev/null
+++ b/includes/utils/MWCryptHKDF.php
@@ -0,0 +1,332 @@
+<?php
+/**
+ * Extract-and-Expand Key Derivation Function (HKDF). A cryptographicly
+ * secure key expansion function based on RFC 5869.
+ *
+ * This relies on the secrecy of $wgSecretKey (by default), or $wgHKDFSecret.
+ * By default, sha256 is used as the underlying hashing algorithm, but any other
+ * algorithm can be used. Finding the secret key from the output would require
+ * an attacker to discover the input key (the PRK) to the hmac that generated
+ * the output, and discover the particular data, hmac'ed with an evolving key
+ * (salt), to produce the PRK. Even with md5, no publicly known attacks make
+ * this currently feasible.
+ *
+ * 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 Chris Steipp
+ * @file
+ */
+
+class MWCryptHKDF {
+
+ /**
+ * Singleton instance for public use
+ */
+ protected static $singleton = null;
+
+ /**
+ * The persistant cache
+ */
+ protected $cache = null;
+
+ /**
+ * Cache key we'll use for our salt
+ */
+ protected $cacheKey = null;
+
+ /**
+ * The hash algorithm being used
+ */
+ protected $algorithm = null;
+
+ /**
+ * binary string, the salt for the HKDF
+ */
+ protected $salt;
+
+ /**
+ * The pseudorandom key
+ */
+ private $prk;
+
+ /**
+ * The secret key material. This must be kept secret to preserve
+ * the security properties of this RNG.
+ */
+ private $skm;
+
+ /**
+ * The last block (K(i)) of the most recent expanded key
+ */
+ protected $lastK;
+
+ /**
+ * a "context information" string CTXinfo (which may be null)
+ * See http://eprint.iacr.org/2010/264.pdf Section 4.1
+ */
+ protected $context = array();
+
+ /**
+ * Round count is computed based on the hash'es output length,
+ * which neither php nor openssl seem to provide easily.
+ */
+ public static $hashLength = array(
+ 'md5' => 16,
+ 'sha1' => 20,
+ 'sha224' => 28,
+ 'sha256' => 32,
+ 'sha384' => 48,
+ 'sha512' => 64,
+ 'ripemd128' => 16,
+ 'ripemd160' => 20,
+ 'ripemd256' => 32,
+ 'ripemd320' => 40,
+ 'whirlpool' => 64,
+ );
+
+
+ /**
+ * @param string $secretKeyMaterial
+ * @param string $algorithm Name of hashing algorithm
+ * @param BagOStuff $cache
+ * @param string|array $context Context to mix into HKDF context
+ */
+ public function __construct( $secretKeyMaterial, $algorithm, $cache, $context ) {
+ if ( strlen( $secretKeyMaterial ) < 16 ) {
+ throw new MWException( "MWCryptHKDF secret was too short." );
+ }
+ $this->skm = $secretKeyMaterial;
+ $this->algorithm = $algorithm;
+ $this->cache = $cache;
+ $this->salt = ''; // Initialize a blank salt, see getSaltUsingCache()
+ $this->prk = '';
+ $this->context = is_array( $context ) ? $context : array( $context );
+
+ // To prevent every call from hitting the same memcache server, pick
+ // from a set of keys to use. mt_rand is only use to pick a random
+ // server, and does not affect the security of the process.
+ $this->cacheKey = wfMemcKey( 'HKDF', mt_rand( 0, 16 ) );
+ }
+
+ /**
+ * Save the last block generated, so the next user will compute a different PRK
+ * from the same SKM. This should keep things unpredictable even if an attacker
+ * is able to influence CTXinfo.
+ */
+ function __destruct() {
+ if ( $this->lastK ) {
+ $this->cache->set( $this->cacheKey, $this->lastK );
+ }
+ }
+
+ /**
+ * MW specific salt, cached from last run
+ * @return string Binary string
+ */
+ protected function getSaltUsingCache() {
+ if ( $this->salt == '' ) {
+ $lastSalt = $this->cache->get( $this->cacheKey );
+ if ( $lastSalt === false ) {
+ // If we don't have a previous value to use as our salt, we use
+ // 16 bytes from MWCryptRand, which will use a small amount of
+ // entropy from our pool. Note, "XTR may be deterministic or keyed
+ // via an optional “salt value” (i.e., a non-secret random
+ // value)..." - http://eprint.iacr.org/2010/264.pdf. However, we
+ // use a strongly random value since we can.
+ $lastSalt = MWCryptRand::generate( 16 );
+ }
+ // Get a binary string that is hashLen long
+ $this->salt = hash( $this->algorithm, $lastSalt, true );
+ }
+ return $this->salt;
+ }
+
+ /**
+ * Return a singleton instance, based on the global configs.
+ * @return HKDF
+ */
+ protected static function singleton() {
+ global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey;
+
+ $secret = $wgHKDFSecret ?: $wgSecretKey;
+ if ( !$secret ) {
+ throw new MWException( "Cannot use MWCryptHKDF without a secret." );
+ }
+
+ // In HKDF, the context can be known to the attacker, but this will
+ // keep simultaneous runs from producing the same output.
+ $context = array();
+ $context[] = microtime();
+ $context[] = getmypid();
+ $context[] = gethostname();
+
+ // Setup salt cache. Use APC, or fallback to the main cache if it isn't setup
+ try {
+ $cache = ObjectCache::newAccelerator( array() );
+ } catch ( Exception $e ) {
+ $cache = wfGetMainCache();
+ }
+
+ if ( is_null( self::$singleton ) ) {
+ self::$singleton = new self( $secret, $wgHKDFAlgorithm, $cache, $context );
+ }
+
+ return self::$singleton;
+ }
+
+ /**
+ * Produce $bytes of secure random data. As a side-effect,
+ * $this->lastK is set to the last hashLen block of key material.
+ * @param int $bytes Number of bytes of data
+ * @param string $context Context to mix into CTXinfo
+ * @return string Binary string of length $bytes
+ */
+ protected function realGenerate( $bytes, $context = '' ) {
+
+ if ( $this->prk === '' ) {
+ $salt = $this->getSaltUsingCache();
+ $this->prk = self::HKDFExtract(
+ $this->algorithm,
+ $salt,
+ $this->skm
+ );
+ }
+
+ $CTXinfo = implode( ':', array_merge( $this->context, array( $context ) ) );
+
+ return self::HKDFExpand(
+ $this->algorithm,
+ $this->prk,
+ $CTXinfo,
+ $bytes,
+ $this->lastK
+ );
+ }
+
+
+ /**
+ * RFC5869 defines HKDF in 2 steps, extraction and expansion.
+ * From http://eprint.iacr.org/2010/264.pdf:
+ *
+ * The scheme HKDF is specifed as:
+ * HKDF(XTS, SKM, CTXinfo, L) = K(1) || K(2) || ... || K(t)
+ * where the values K(i) are defined as follows:
+ * PRK = HMAC(XTS, SKM)
+ * K(1) = HMAC(PRK, CTXinfo || 0);
+ * K(i+1) = HMAC(PRK, K(i) || CTXinfo || i), 1 <= i < t;
+ * where t = [L/k] and the value K(t) is truncated to its first d = L mod k bits;
+ * the counter i is non-wrapping and of a given fixed size, e.g., a single byte.
+ * Note that the length of the HMAC output is the same as its key length and therefore
+ * the scheme is well defined.
+ *
+ * XTS is the "extractor salt"
+ * SKM is the "secret keying material"
+ *
+ * N.B. http://eprint.iacr.org/2010/264.pdf seems to differ from RFC 5869 in that the test
+ * vectors from RFC 5869 only work if K(0) = '' and K(1) = HMAC(PRK, K(0) || CTXinfo || 1)
+ *
+ * @param string $hash The hashing function to use (e.g., sha256)
+ * @param string $ikm The input keying material
+ * @param string $salt The salt to add to the ikm, to get the prk
+ * @param string $info Optional context (change the output without affecting
+ * the randomness properties of the output)
+ * @param int $L Number of bytes to return
+ * @return string Cryptographically secure pseudorandom binary string
+ */
+ public static function HKDF( $hash, $ikm, $salt, $info, $L ) {
+ $prk = self::HKDFExtract( $hash, $salt, $ikm );
+ $okm = self::HKDFExpand( $hash, $prk, $info, $L );
+ return $okm;
+ }
+
+ /**
+ * Extract the PRK, PRK = HMAC(XTS, SKM)
+ * Note that the hmac is keyed with XTS (the salt),
+ * and the SKM (source key material) is the "data".
+ *
+ * @param string $hash The hashing function to use (e.g., sha256)
+ * @param string $salt The salt to add to the ikm, to get the prk
+ * @param string $ikm The input keying material
+ * @return string Binary string (pseudorandm key) used as input to HKDFExpand
+ */
+ private static function HKDFExtract( $hash, $salt, $ikm ) {
+ return hash_hmac( $hash, $ikm, $salt, true );
+ }
+
+ /**
+ * Expand the key with the given context
+ *
+ * @param string $hash Hashing Algorithm
+ * @param string $prk A pseudorandom key of at least HashLen octets
+ * (usually, the output from the extract step)
+ * @param string $info Optional context and application specific information
+ * (can be a zero-length string)
+ * @param int $bytes Length of output keying material in bytes
+ * (<= 255*HashLen)
+ * @param string &$lastK Set by this function to the last block of the expansion.
+ * In MediaWiki, this is used to seed future Extractions.
+ * @return string Cryptographically secure random string $bytes long
+ */
+ private static function HKDFExpand( $hash, $prk, $info, $bytes, &$lastK = '' ) {
+ $hashLen = MWCryptHKDF::$hashLength[$hash];
+ $rounds = ceil( $bytes / $hashLen );
+ $output = '';
+
+ if ( $bytes > 255 * $hashLen ) {
+ throw new MWException( "Too many bytes requested from HDKFExpand" );
+ }
+
+ // K(1) = HMAC(PRK, CTXinfo || 1);
+ // K(i) = HMAC(PRK, K(i-1) || CTXinfo || i); 1 < i <= t;
+ for ( $counter = 1; $counter <= $rounds; ++$counter ) {
+ $lastK = hash_hmac(
+ $hash,
+ $lastK . $info . chr( $counter ),
+ $prk,
+ true
+ );
+ $output .= $lastK;
+ }
+
+ return substr( $output, 0, $bytes );
+ }
+
+ /**
+ * Generate cryptographically random data and return it in raw binary form.
+ *
+ * @param int $bytes The number of bytes of random data to generate
+ * @param string $context String to mix into HMAC context
+ * @return string Binary string of length $bytes
+ */
+ public static function generate( $bytes, $context ) {
+ return self::singleton()->realGenerate( $bytes, $context );
+ }
+
+ /**
+ * Generate cryptographically random data and return it in hexadecimal string format.
+ * See MWCryptRand::realGenerateHex for details of the char-to-byte conversion logic.
+ *
+ * @param int $chars The number of hex chars of random data to generate
+ * @param string $context String to mix into HMAC context
+ * @return string Random hex characters, $chars long
+ */
+ public static function generateHex( $chars, $context = '' ) {
+ $bytes = ceil( $chars / 2 );
+ $hex = bin2hex( self::singleton()->realGenerate( $bytes, $context ) );
+ return substr( $hex, 0, $chars );
+ }
+
+}
diff --git a/includes/MWCryptRand.php b/includes/utils/MWCryptRand.php
index bac018e8..b602f78e 100644
--- a/includes/MWCryptRand.php
+++ b/includes/utils/MWCryptRand.php
@@ -25,7 +25,6 @@
*/
class MWCryptRand {
-
/**
* Minimum number of iterations we want to make in our drift calculations.
*/
@@ -62,6 +61,7 @@ class MWCryptRand {
/**
* Initialize an initial random state based off of whatever we can find
+ * @return string
*/
protected function initialRandomState() {
// $_SERVER contains a variety of unstable user and system specific information
@@ -86,10 +86,11 @@ class MWCryptRand {
$files[] = __DIR__;
$files[] = dirname( __DIR__ );
- // The config file is likely the most often edited file we know should be around
- // so include its stat info into the state.
- // The constant with its location will almost always be defined, as WebStart.php defines
- // MW_CONFIG_FILE to $IP/LocalSettings.php unless being configured with MW_CONFIG_CALLBACK (eg. the installer)
+ // The config file is likely the most often edited file we know should
+ // be around so include its stat info into the state.
+ // The constant with its location will almost always be defined, as
+ // WebStart.php defines MW_CONFIG_FILE to $IP/LocalSettings.php unless
+ // being configured with MW_CONFIG_CALLBACK (e.g. the installer).
if ( defined( 'MW_CONFIG_FILE' ) ) {
$files[] = MW_CONFIG_FILE;
}
@@ -134,12 +135,10 @@ class MWCryptRand {
// It's mostly worthless but throw the wiki's id into the data for a little more variance
$state .= wfWikiID();
- // If we have a secret key or proxy key set then throw it into the state as well
- global $wgSecretKey, $wgProxyKey;
+ // If we have a secret key set then throw it into the state as well
+ global $wgSecretKey;
if ( $wgSecretKey ) {
$state .= $wgSecretKey;
- } elseif ( $wgProxyKey ) {
- $state .= $wgProxyKey;
}
return $state;
@@ -149,11 +148,12 @@ class MWCryptRand {
* Randomly hash data while mixing in clock drift data for randomness
*
* @param string $data The data to randomly hash.
- * @return String The hashed bytes
+ * @return string The hashed bytes
* @author Tim Starling
*/
protected function driftHash( $data ) {
- // Minimum number of iterations (to avoid slow operations causing the loop to gather little entropy)
+ // Minimum number of iterations (to avoid slow operations causing the
+ // loop to gather little entropy)
$minIterations = self::MIN_ITERATIONS;
// Duration of time to spend doing calculations (in seconds)
$duration = ( self::MSEC_PER_BYTE / 1000 ) * $this->hashLength();
@@ -189,6 +189,7 @@ class MWCryptRand {
"(time-taken=" . ( $timeTaken * 1000 ) . "ms, " .
"iterations=$iterations, " .
"time-per-iteration=" . ( $timeTaken / $iterations * 1e6 ) . "us)\n" );
+
return $data;
}
@@ -207,13 +208,14 @@ class MWCryptRand {
// Generate a new random state based on the initial random state or previous
// random state by combining it with clock drift
$state = $this->driftHash( $state );
+
return $state;
}
/**
* Decide on the best acceptable hash algorithm we have available for hash()
* @throws MWException
- * @return String A hash algorithm
+ * @return string A hash algorithm
*/
protected function hashAlgo() {
if ( !is_null( $this->algo ) ) {
@@ -227,6 +229,7 @@ class MWCryptRand {
if ( in_array( $algorithm, $algos ) ) {
$this->algo = $algorithm;
wfDebug( __METHOD__ . ": Using the {$this->algo} hash algorithm.\n" );
+
return $this->algo;
}
}
@@ -249,6 +252,7 @@ class MWCryptRand {
if ( is_null( $this->hashLength ) ) {
$this->hashLength = strlen( $this->hash( '' ) );
}
+
return $this->hashLength;
}
@@ -256,8 +260,8 @@ class MWCryptRand {
* Generate an acceptably unstable one-way-hash of some text
* making use of the best hash algorithm that we have available.
*
- * @param $data string
- * @return String A raw hash of the data
+ * @param string $data
+ * @return string A raw hash of the data
*/
protected function hash( $data ) {
return hash( $this->hashAlgo(), $data, true );
@@ -267,9 +271,9 @@ class MWCryptRand {
* Generate an acceptably unstable one-way-hmac of some text
* making use of the best hash algorithm that we have available.
*
- * @param $data string
- * @param $key string
- * @return String A raw hash of the data
+ * @param string $data
+ * @param string $key
+ * @return string A raw hash of the data
*/
protected function hmac( $data, $key ) {
return hash_hmac( $this->hashAlgo(), $data, $key, true );
@@ -282,6 +286,7 @@ class MWCryptRand {
if ( is_null( $this->strong ) ) {
throw new MWException( __METHOD__ . ' called before generation of random data' );
}
+
return $this->strong;
}
@@ -291,7 +296,8 @@ class MWCryptRand {
public function realGenerate( $bytes, $forceStrong = false ) {
wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " . wfGetAllCallers( 5 ) . "\n" );
+ wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " .
+ wfGetAllCallers( 5 ) . "\n" );
$bytes = floor( $bytes );
static $buffer = '';
@@ -315,15 +321,17 @@ class MWCryptRand {
wfDebug( __METHOD__ . ": mcrypt_create_iv returned false.\n" );
} else {
$buffer .= $iv;
- wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) . " bytes of randomness.\n" );
+ wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) .
+ " bytes of randomness.\n" );
}
wfProfileOut( __METHOD__ . '-mcrypt' );
}
}
if ( strlen( $buffer ) < $bytes ) {
- // If available make use of openssl's random_pseudo_bytes method to attempt to generate randomness.
- // However don't do this on Windows with PHP < 5.3.4 due to a bug:
+ // If available make use of openssl's random_pseudo_bytes method to
+ // attempt to generate randomness. However don't do this on Windows
+ // with PHP < 5.3.4 due to a bug:
// http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
// http://git.php.net/?p=php-src.git;a=commitdiff;h=cd62a70863c261b07f6dadedad9464f7e213cad5
if ( function_exists( 'openssl_random_pseudo_bytes' )
@@ -336,7 +344,9 @@ class MWCryptRand {
wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes returned false.\n" );
} else {
$buffer .= $openssl_bytes;
- wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes generated " . strlen( $openssl_bytes ) . " bytes of " . ( $openssl_strong ? "strong" : "weak" ) . " randomness.\n" );
+ wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes generated " .
+ strlen( $openssl_bytes ) . " bytes of " .
+ ( $openssl_strong ? "strong" : "weak" ) . " randomness.\n" );
}
if ( strlen( $buffer ) >= $bytes ) {
// openssl tells us if the random source was strong, if some of our data was generated
@@ -348,11 +358,14 @@ class MWCryptRand {
}
// Only read from urandom if we can control the buffer size or were passed forceStrong
- if ( strlen( $buffer ) < $bytes && ( function_exists( 'stream_set_read_buffer' ) || $forceStrong ) ) {
+ if ( strlen( $buffer ) < $bytes &&
+ ( function_exists( 'stream_set_read_buffer' ) || $forceStrong )
+ ) {
wfProfileIn( __METHOD__ . '-fopen-urandom' );
$rem = $bytes - strlen( $buffer );
if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) {
- wfDebug( __METHOD__ . ": Was forced to read from /dev/urandom without control over the buffer size.\n" );
+ wfDebug( __METHOD__ . ": Was forced to read from /dev/urandom " .
+ "without control over the buffer size.\n" );
}
// /dev/urandom is generally considered the best possible commonly
// available random source, and is available on most *nix systems.
@@ -377,7 +390,9 @@ class MWCryptRand {
$random_bytes = fread( $urandom, max( $chunk_size, $rem ) );
$buffer .= $random_bytes;
fclose( $urandom );
- wfDebug( __METHOD__ . ": /dev/urandom generated " . strlen( $random_bytes ) . " bytes of randomness.\n" );
+ wfDebug( __METHOD__ . ": /dev/urandom generated " . strlen( $random_bytes ) .
+ " bytes of randomness.\n" );
+
if ( strlen( $buffer ) >= $bytes ) {
// urandom is always strong, set to true if all our data was generated using it
$this->strong = true;
@@ -395,7 +410,8 @@ class MWCryptRand {
// We hash the random state with more salt to avoid the state from leaking
// out and being used to predict the /randomness/ that follows.
if ( strlen( $buffer ) < $bytes ) {
- wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" );
+ wfDebug( __METHOD__ .
+ ": Falling back to using a pseudo random state to generate randomness.\n" );
}
while ( strlen( $buffer ) < $bytes ) {
wfProfileIn( __METHOD__ . '-fallback' );
@@ -412,9 +428,11 @@ class MWCryptRand {
$generated = substr( $buffer, 0, $bytes );
$buffer = substr( $buffer, $bytes );
- wfDebug( __METHOD__ . ": " . strlen( $buffer ) . " bytes of randomness leftover in the buffer.\n" );
+ wfDebug( __METHOD__ . ": " . strlen( $buffer ) .
+ " bytes of randomness leftover in the buffer.\n" );
wfProfileOut( __METHOD__ );
+
return $generated;
}
@@ -428,6 +446,7 @@ class MWCryptRand {
$bytes = ceil( $chars / 2 );
// Generate the data and then convert it to a hex string
$hex = bin2hex( $this->generate( $bytes, $forceStrong ) );
+
// A bit of paranoia here, the caller asked for a specific length of string
// here, and it's possible (eg when given an odd number) that we may actually
// have at least 1 char more than they asked for. Just in case they made this
@@ -448,6 +467,7 @@ class MWCryptRand {
if ( is_null( self::$singleton ) ) {
self::$singleton = new self;
}
+
return self::$singleton;
}
@@ -468,11 +488,11 @@ class MWCryptRand {
* You can use MWCryptRand::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
+ * @param int $bytes The number of bytes of random data to generate
* @param bool $forceStrong Pass true if you want generate to prefer cryptographically
* strong sources of entropy even if reading from them may steal
* more entropy from the system than optimal.
- * @return String Raw binary random data
+ * @return string Raw binary random data
*/
public static function generate( $bytes, $forceStrong = false ) {
return self::singleton()->realGenerate( $bytes, $forceStrong );
@@ -484,14 +504,13 @@ class MWCryptRand {
* You can use MWCryptRand::wasStrong() if you wish to know if the source used
* was cryptographically strong.
*
- * @param int $chars the number of hex chars of random data to generate
+ * @param int $chars The number of hex chars of random data to generate
* @param bool $forceStrong Pass true if you want generate to prefer cryptographically
* strong sources of entropy even if reading from them may steal
* more entropy from the system than optimal.
- * @return String Hexadecimal random data
+ * @return string Hexadecimal random data
*/
public static function generateHex( $chars, $forceStrong = false ) {
return self::singleton()->realGenerateHex( $chars, $forceStrong );
}
-
}
diff --git a/includes/MWFunction.php b/includes/utils/MWFunction.php
index 6d11d178..3a0492dc 100644
--- a/includes/MWFunction.php
+++ b/includes/utils/MWFunction.php
@@ -24,29 +24,31 @@ class MWFunction {
/**
* @deprecated since 1.22; use call_user_func()
- * @param $callback
+ * @param callable $callback
* @return mixed
*/
public static function call( $callback ) {
wfDeprecated( __METHOD__, '1.22' );
$args = func_get_args();
+
return call_user_func_array( 'call_user_func', $args );
}
/**
* @deprecated since 1.22; use call_user_func_array()
- * @param $callback
- * @param $argsarams
+ * @param callable $callback
+ * @param array $argsarams
* @return mixed
*/
public static function callArray( $callback, $argsarams ) {
wfDeprecated( __METHOD__, '1.22' );
+
return call_user_func_array( $callback, $argsarams );
}
/**
- * @param $class
- * @param $args array
+ * @param string $class
+ * @param array $args
* @return object
*/
public static function newObj( $class, $args = array() ) {
@@ -55,7 +57,7 @@ class MWFunction {
}
$ref = new ReflectionClass( $class );
+
return $ref->newInstanceArgs( $args );
}
-
}
diff --git a/includes/utils/README b/includes/utils/README
new file mode 100644
index 00000000..b5b8ec88
--- /dev/null
+++ b/includes/utils/README
@@ -0,0 +1,9 @@
+The classes in this directory are general utilities for use by any part of
+MediaWiki. They do not favour any particular user interface and are not
+constrained to serve any particular feature. This is similar to includes/libs,
+except that some dependency on the MediaWiki framework (such as the use of
+MWException, Status or wfDebug()) disqualifies them from use outside of
+MediaWiki without modification.
+
+Utilities should not use global configuration variables, rather they should rely
+on the caller to configure their behaviour.
diff --git a/includes/StringUtils.php b/includes/utils/StringUtils.php
index c1545e6e..86f45122 100644
--- a/includes/StringUtils.php
+++ b/includes/utils/StringUtils.php
@@ -24,7 +24,6 @@
* A collection of static methods to play with strings.
*/
class StringUtils {
-
/**
* Test whether a string is valid UTF-8.
*
@@ -43,11 +42,11 @@ class StringUtils {
* Beware of this when backporting code to that version of MediaWiki.
*
* @param string $value String to check
- * @param boolean $disableMbstring Whether to use the pure PHP
+ * @param bool $disableMbstring Whether to use the pure PHP
* implementation instead of trying mb_check_encoding. Intended for unit
* testing. Default: false
*
- * @return boolean Whether the given $value is a valid UTF-8 encoded string
+ * @return bool Whether the given $value is a valid UTF-8 encoded string
*/
static function isUtf8( $value, $disableMbstring = false ) {
$value = (string)$value;
@@ -107,6 +106,7 @@ class StringUtils {
return false;
}
}
+
return true;
}
@@ -121,10 +121,10 @@ class StringUtils {
* hungry and inflexible. The memory requirements are such that I don't
* recommend using it on anything but guaranteed small chunks of text.
*
- * @param $startDelim
- * @param $endDelim
- * @param $replace
- * @param $subject
+ * @param string $startDelim
+ * @param string $endDelim
+ * @param string $replace
+ * @param string $subject
*
* @return string
*/
@@ -139,6 +139,7 @@ class StringUtils {
$output .= $replace . substr( $s, $endDelimPos + strlen( $endDelim ) );
}
}
+
return $output;
}
@@ -156,15 +157,17 @@ class StringUtils {
* start, so e.g. /*\/ is not considered to be both the start and end of a
* comment. /*\/xy/*\/ is considered to be a single comment with contents /xy/.
*
- * @param string $startDelim start delimiter
- * @param string $endDelim end delimiter
- * @param $callback Callback: function to call on each match
- * @param $subject String
- * @param string $flags regular expression flags
+ * @param string $startDelim Start delimiter
+ * @param string $endDelim End delimiter
+ * @param callable $callback Function to call on each match
+ * @param string $subject
+ * @param string $flags Regular expression flags
* @throws MWException
* @return string
*/
- static function delimiterReplaceCallback( $startDelim, $endDelim, $callback, $subject, $flags = '' ) {
+ static function delimiterReplaceCallback( $startDelim, $endDelim, $callback,
+ $subject, $flags = ''
+ ) {
$inputPos = 0;
$outputPos = 0;
$output = '';
@@ -176,13 +179,13 @@ class StringUtils {
$m = array();
while ( $inputPos < strlen( $subject ) &&
- preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) )
- {
+ preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos )
+ ) {
$tokenOffset = $m[0][1];
if ( $m[1][0] != '' ) {
if ( $foundStart &&
- $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0 )
- {
+ $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0
+ ) {
# An end match is present at the same location
$tokenType = 'end';
$tokenLength = $endLength;
@@ -219,7 +222,7 @@ class StringUtils {
$output .= call_user_func( $callback, array(
substr( $subject, $outputPos, $tokenOffset + $tokenLength - $outputPos ),
substr( $subject, $contentPos, $tokenOffset - $contentPos )
- ));
+ ) );
$foundStart = false;
} else {
# Non-matching end, write it out
@@ -233,6 +236,7 @@ class StringUtils {
if ( $outputPos < strlen( $subject ) ) {
$output .= substr( $subject, $outputPos );
}
+
return $output;
}
@@ -241,16 +245,17 @@ class StringUtils {
*
* preg_replace( "!$startDelim(.*)$endDelim!$flags", $replace, $subject )
*
- * @param string $startDelim start delimiter regular expression
- * @param string $endDelim end delimiter regular expression
- * @param string $replace replacement string. May contain $1, which will be
+ * @param string $startDelim Start delimiter regular expression
+ * @param string $endDelim End delimiter regular expression
+ * @param string $replace Replacement string. May contain $1, which will be
* replaced by the text between the delimiters
- * @param string $subject to search
- * @param string $flags regular expression flags
- * @return String: The string with the matches replaced
+ * @param string $subject String to search
+ * @param string $flags Regular expression flags
+ * @return string The string with the matches replaced
*/
static function delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags = '' ) {
$replacer = new RegexlikeReplacer( $replace );
+
return self::delimiterReplaceCallback( $startDelim, $endDelim,
$replacer->cb(), $subject, $flags );
}
@@ -291,6 +296,7 @@ class StringUtils {
static function escapeRegexReplacement( $string ) {
$string = str_replace( '\\', '\\\\', $string );
$string = str_replace( '$', '\\$', $string );
+
return $string;
}
@@ -315,7 +321,6 @@ class StringUtils {
* StringUtils::delimiterReplaceCallback()
*/
class Replacer {
-
/**
* @return array
*/
@@ -328,7 +333,7 @@ class Replacer {
* Class to replace regex matches with a string similar to that used in preg_replace()
*/
class RegexlikeReplacer extends Replacer {
- var $r;
+ private $r;
/**
* @param string $r
@@ -346,19 +351,18 @@ class RegexlikeReplacer extends Replacer {
foreach ( $matches as $i => $match ) {
$pairs["\$$i"] = $match;
}
+
return strtr( $this->r, $pairs );
}
-
}
/**
* Class to perform secondary replacement within each replacement string
*/
class DoubleReplacer extends Replacer {
-
/**
- * @param $from
- * @param $to
+ * @param mixed $from
+ * @param mixed $to
* @param int $index
*/
function __construct( $from, $to, $index = 0 ) {
@@ -380,10 +384,10 @@ class DoubleReplacer extends Replacer {
* Class to perform replacement based on a simple hashtable lookup
*/
class HashtableReplacer extends Replacer {
- var $table, $index;
+ private $table, $index;
/**
- * @param $table
+ * @param array $table
* @param int $index
*/
function __construct( $table, $index = 0 ) {
@@ -405,8 +409,8 @@ class HashtableReplacer extends Replacer {
* Supports lazy initialisation of FSS resource
*/
class ReplacementArray {
- /*mostly private*/ var $data = false;
- /*mostly private*/ var $fss = false;
+ private $data = false;
+ private $fss = false;
/**
* Create an object with the specified replacement array
@@ -505,6 +509,7 @@ class ReplacementArray {
$result = strtr( $subject, $this->data );
wfProfileOut( __METHOD__ . '-strtr' );
}
+
return $result;
}
}
@@ -520,19 +525,19 @@ class ReplacementArray {
*/
class ExplodeIterator implements Iterator {
// The subject string
- var $subject, $subjectLength;
+ private $subject, $subjectLength;
// The delimiter
- var $delim, $delimLength;
+ private $delim, $delimLength;
// The position of the start of the line
- var $curPos;
+ private $curPos;
// The position after the end of the next delimiter
- var $endPos;
+ private $endPos;
// The current token
- var $current;
+ private $current;
/**
* Construct a DelimIterator
@@ -594,6 +599,7 @@ class ExplodeIterator implements Iterator {
}
}
$this->refreshCurrent();
+
return $this->current;
}
diff --git a/includes/UIDGenerator.php b/includes/utils/UIDGenerator.php
index 963e51a4..5346afa6 100644
--- a/includes/UIDGenerator.php
+++ b/includes/utils/UIDGenerator.php
@@ -30,20 +30,25 @@ class UIDGenerator {
/** @var UIDGenerator */
protected static $instance = null;
+ protected $nodeIdFile; // string; local file path
protected $nodeId32; // string; node ID in binary (32 bits)
protected $nodeId48; // string; node ID in binary (48 bits)
protected $lockFile88; // string; local file path
protected $lockFile128; // string; local file path
- /** @var Array */
+ /** @var array */
protected $fileHandles = array(); // cache file handles
const QUICK_RAND = 1; // get randomness from fast and insecure sources
+ const QUICK_VOLATILE = 2; // use an APC like in-memory counter if available
protected function __construct() {
- $idFile = wfTempDir() . '/mw-' . __CLASS__ . '-UID-nodeid';
- $nodeId = is_file( $idFile ) ? file_get_contents( $idFile ) : '';
+ $this->nodeIdFile = wfTempDir() . '/mw-' . __CLASS__ . '-UID-nodeid';
+ $nodeId = '';
+ if ( is_file( $this->nodeIdFile ) ) {
+ $nodeId = file_get_contents( $this->nodeIdFile );
+ }
// Try to get some ID that uniquely identifies this machine (RFC 4122)...
if ( !preg_match( '/^[0-9a-f]{12}$/i', $nodeId ) ) {
wfSuppressWarnings();
@@ -65,7 +70,7 @@ class UIDGenerator {
$nodeId = MWCryptRand::generateHex( 12, true );
$nodeId[1] = dechex( hexdec( $nodeId[1] ) | 0x1 ); // set multicast bit
}
- file_put_contents( $idFile, $nodeId ); // cache
+ file_put_contents( $this->nodeIdFile, $nodeId ); // cache
}
$this->nodeId32 = wfBaseConvert( substr( sha1( $nodeId ), 0, 8 ), 16, 2, 32 );
$this->nodeId48 = wfBaseConvert( $nodeId, 16, 2, 48 );
@@ -82,6 +87,7 @@ class UIDGenerator {
if ( self::$instance === null ) {
self::$instance = new self();
}
+
return self::$instance;
}
@@ -96,7 +102,7 @@ class UIDGenerator {
*
* UID generation is serialized on each server (as the node ID is for the whole machine).
*
- * @param $base integer Specifies a base other than 10
+ * @param int $base Specifies a base other than 10
* @return string Number
* @throws MWException
*/
@@ -106,11 +112,12 @@ class UIDGenerator {
}
$gen = self::singleton();
$time = $gen->getTimestampAndDelay( 'lockFile88', 1, 1024 );
+
return wfBaseConvert( $gen->getTimestampedID88( $time ), 2, $base );
}
/**
- * @param array $time (UIDGenerator::millitime(), clock sequence)
+ * @param array $info (UIDGenerator::millitime(), counter, clock sequence)
* @return string 88 bits
*/
protected function getTimestampedID88( array $info ) {
@@ -125,6 +132,7 @@ class UIDGenerator {
if ( strlen( $id_bin ) !== 88 ) {
throw new MWException( "Detected overflow for millisecond timestamp." );
}
+
return $id_bin;
}
@@ -138,7 +146,7 @@ class UIDGenerator {
*
* UID generation is serialized on each server (as the node ID is for the whole machine).
*
- * @param $base integer Specifies a base other than 10
+ * @param int $base Specifies a base other than 10
* @return string Number
* @throws MWException
*/
@@ -148,6 +156,7 @@ class UIDGenerator {
}
$gen = self::singleton();
$time = $gen->getTimestampAndDelay( 'lockFile128', 16384, 1048576 );
+
return wfBaseConvert( $gen->getTimestampedID128( $time ), 2, $base );
}
@@ -169,13 +178,14 @@ class UIDGenerator {
if ( strlen( $id_bin ) !== 128 ) {
throw new MWException( "Detected overflow for millisecond timestamp." );
}
+
return $id_bin;
}
/**
* Return an RFC4122 compliant v4 UUID
*
- * @param $flags integer Bitfield (supports UIDGenerator::QUICK_RAND)
+ * @param int $flags Bitfield (supports UIDGenerator::QUICK_RAND)
* @return string
* @throws MWException
*/
@@ -201,7 +211,7 @@ class UIDGenerator {
/**
* Return an RFC4122 compliant v4 UUID
*
- * @param $flags integer Bitfield (supports UIDGenerator::QUICK_RAND)
+ * @param int $flags Bitfield (supports UIDGenerator::QUICK_RAND)
* @return string 32 hex characters with no hyphens
* @throws MWException
*/
@@ -210,28 +220,142 @@ class UIDGenerator {
}
/**
+ * Return an ID that is sequential *only* for this node and bucket
+ *
+ * These IDs are suitable for per-host sequence numbers, e.g. for some packet protocols.
+ * If UIDGenerator::QUICK_VOLATILE is used the counter might reset on server restart.
+ *
+ * @param string $bucket Arbitrary bucket name (should be ASCII)
+ * @param int $bits Bit size (<=48) of resulting numbers before wrap-around
+ * @param int $flags (supports UIDGenerator::QUICK_VOLATILE)
+ * @return float Integer value as float
+ * @since 1.23
+ */
+ public static function newSequentialPerNodeID( $bucket, $bits = 48, $flags = 0 ) {
+ return current( self::newSequentialPerNodeIDs( $bucket, $bits, 1, $flags ) );
+ }
+
+ /**
+ * Return IDs that are sequential *only* for this node and bucket
+ *
+ * @see UIDGenerator::newSequentialPerNodeID()
+ * @param string $bucket Arbitrary bucket name (should be ASCII)
+ * @param int $bits Bit size (16 to 48) of resulting numbers before wrap-around
+ * @param int $count Number of IDs to return (1 to 10000)
+ * @param int $flags (supports UIDGenerator::QUICK_VOLATILE)
+ * @return array Ordered list of float integer values
+ * @since 1.23
+ */
+ public static function newSequentialPerNodeIDs( $bucket, $bits, $count, $flags = 0 ) {
+ $gen = self::singleton();
+ return $gen->getSequentialPerNodeIDs( $bucket, $bits, $count, $flags );
+ }
+
+ /**
+ * Return IDs that are sequential *only* for this node and bucket
+ *
+ * @see UIDGenerator::newSequentialPerNodeID()
+ * @param string $bucket Arbitrary bucket name (should be ASCII)
+ * @param int $bits Bit size (16 to 48) of resulting numbers before wrap-around
+ * @param int $count Number of IDs to return (1 to 10000)
+ * @param int $flags (supports UIDGenerator::QUICK_VOLATILE)
+ * @return array Ordered list of float integer values
+ */
+ protected function getSequentialPerNodeIDs( $bucket, $bits, $count, $flags ) {
+ if ( $count <= 0 ) {
+ return array(); // nothing to do
+ } elseif ( $count > 10000 ) {
+ throw new MWException( "Number of requested IDs ($count) is too high." );
+ } elseif ( $bits < 16 || $bits > 48 ) {
+ throw new MWException( "Requested bit size ($bits) is out of range." );
+ }
+
+ $counter = null; // post-increment persistent counter value
+
+ // Use APC/eAccelerator/xcache if requested, available, and not in CLI mode;
+ // Counter values would not survive accross script instances in CLI mode.
+ $cache = null;
+ if ( ( $flags & self::QUICK_VOLATILE ) && PHP_SAPI !== 'cli' ) {
+ try {
+ $cache = ObjectCache::newAccelerator( array() );
+ } catch ( MWException $e ) {
+ // not supported
+ }
+ }
+ if ( $cache ) {
+ $counter = $cache->incr( $bucket, $count );
+ if ( $counter === false ) {
+ if ( !$cache->add( $bucket, (int)$count ) ) {
+ throw new MWException( 'Unable to set value to ' . get_class( $cache ) );
+ }
+ $counter = $count;
+ }
+ }
+
+ // Note: use of fmod() avoids "division by zero" on 32 bit machines
+ if ( $counter === null ) {
+ $path = wfTempDir() . '/mw-' . __CLASS__ . '-' . rawurlencode( $bucket ) . '-48';
+ // Get the UID lock file handle
+ if ( isset( $this->fileHandles[$path] ) ) {
+ $handle = $this->fileHandles[$path];
+ } else {
+ $handle = fopen( $path, 'cb+' );
+ $this->fileHandles[$path] = $handle ?: null; // cache
+ }
+ // Acquire the UID lock file
+ if ( $handle === false ) {
+ throw new MWException( "Could not open '{$path}'." );
+ } elseif ( !flock( $handle, LOCK_EX ) ) {
+ fclose( $handle );
+ throw new MWException( "Could not acquire '{$path}'." );
+ }
+ // Fetch the counter value and increment it...
+ rewind( $handle );
+ $counter = floor( trim( fgets( $handle ) ) ) + $count; // fetch as float
+ // Write back the new counter value
+ ftruncate( $handle, 0 );
+ rewind( $handle );
+ fwrite( $handle, fmod( $counter, pow( 2, 48 ) ) ); // warp-around as needed
+ fflush( $handle );
+ // Release the UID lock file
+ flock( $handle, LOCK_UN );
+ }
+
+ $ids = array();
+ $divisor = pow( 2, $bits );
+ $currentId = floor( $counter - $count ); // pre-increment counter value
+ for ( $i = 0; $i < $count; ++$i ) {
+ $ids[] = fmod( ++$currentId, $divisor );
+ }
+
+ return $ids;
+ }
+
+ /**
* Get a (time,counter,clock sequence) where (time,counter) is higher
* than any previous (time,counter) value for the given clock sequence.
* This is useful for making UIDs sequential on a per-node bases.
*
* @param string $lockFile Name of a local lock file
- * @param $clockSeqSize integer The number of possible clock sequence values
- * @param $counterSize integer The number of possible counter values
- * @return Array (result of UIDGenerator::millitime(), counter, clock sequence)
+ * @param int $clockSeqSize The number of possible clock sequence values
+ * @param int $counterSize The number of possible counter values
+ * @return array (result of UIDGenerator::millitime(), counter, clock sequence)
* @throws MWException
*/
protected function getTimestampAndDelay( $lockFile, $clockSeqSize, $counterSize ) {
// Get the UID lock file handle
- if ( isset( $this->fileHandles[$lockFile] ) ) {
- $handle = $this->fileHandles[$lockFile];
+ $path = $this->$lockFile;
+ if ( isset( $this->fileHandles[$path] ) ) {
+ $handle = $this->fileHandles[$path];
} else {
- $handle = fopen( $this->$lockFile, 'cb+' );
- $this->fileHandles[$lockFile] = $handle ?: null; // cache
+ $handle = fopen( $path, 'cb+' );
+ $this->fileHandles[$path] = $handle ?: null; // cache
}
// Acquire the UID lock file
if ( $handle === false ) {
throw new MWException( "Could not open '{$this->$lockFile}'." );
} elseif ( !flock( $handle, LOCK_EX ) ) {
+ fclose( $handle );
throw new MWException( "Could not acquire '{$this->$lockFile}'." );
}
// Get the current timestamp, clock sequence number, last time, and counter
@@ -296,7 +420,7 @@ class UIDGenerator {
* timestamp. This returns false if it would have to wait more than 10ms.
*
* @param array $time Result of UIDGenerator::millitime()
- * @return Array|bool UIDGenerator::millitime() result or false
+ * @return array|bool UIDGenerator::millitime() result or false
*/
protected function timeWaitUntil( array $time ) {
do {
@@ -320,18 +444,64 @@ class UIDGenerator {
throw new MWException( __METHOD__ .
': sorry, this function doesn\'t work after the year 144680' );
}
+
return substr( wfBaseConvert( $ts, 10, 2, 46 ), -46 );
}
/**
- * @return Array (current time in seconds, milliseconds since then)
+ * @return array (current time in seconds, milliseconds since then)
*/
protected static function millitime() {
list( $msec, $sec ) = explode( ' ', microtime() );
+
return array( (int)$sec, (int)( $msec * 1000 ) );
}
+ /**
+ * Delete all cache files that have been created.
+ *
+ * This is a cleanup method primarily meant to be used from unit tests to
+ * avoid poluting the local filesystem. If used outside of a unit test
+ * environment it should be used with caution as it may destroy state saved
+ * in the files.
+ *
+ * @see unitTestTearDown
+ * @since 1.23
+ */
+ protected function deleteCacheFiles() {
+ // Bug: 44850
+ foreach ( $this->fileHandles as $path => $handle ) {
+ if ( $handle !== null ) {
+ fclose( $handle );
+ }
+ if ( is_file( $path ) ) {
+ unlink( $path );
+ }
+ unset( $this->fileHandles[$path] );
+ }
+ if ( is_file( $this->nodeIdFile ) ) {
+ unlink( $this->nodeIdFile );
+ }
+ }
+
+ /**
+ * Cleanup resources when tearing down after a unit test.
+ *
+ * This is a cleanup method primarily meant to be used from unit tests to
+ * avoid poluting the local filesystem. If used outside of a unit test
+ * environment it should be used with caution as it may destroy state saved
+ * in the files.
+ *
+ * @see deleteCacheFiles
+ * @since 1.23
+ */
+ public static function unitTestTearDown() {
+ // Bug: 44850
+ $gen = self::singleton();
+ $gen->deleteCacheFiles();
+ }
+
function __destruct() {
- array_map( 'fclose', $this->fileHandles );
+ array_map( 'fclose', array_filter( $this->fileHandles ) );
}
}
diff --git a/includes/ZipDirectoryReader.php b/includes/utils/ZipDirectoryReader.php
index 307efcea..bc849766 100644
--- a/includes/ZipDirectoryReader.php
+++ b/includes/utils/ZipDirectoryReader.php
@@ -64,7 +64,7 @@ class ZipDirectoryReader {
* valid ZIP64 file, and working out what non-ZIP64 readers will make
* of such a file is not trivial.
*
- * @return Status object. The following fatal errors are defined:
+ * @return Status A Status object. The following fatal errors are defined:
*
* - zip-file-open-error: The file could not be opened.
*
@@ -88,31 +88,32 @@ class ZipDirectoryReader {
*/
public static function read( $fileName, $callback, $options = array() ) {
$zdr = new self( $fileName, $callback, $options );
+
return $zdr->execute();
}
/** The file name */
- var $fileName;
+ protected $fileName;
/** The opened file resource */
- var $file;
+ protected $file;
/** The cached length of the file, or null if it has not been loaded yet. */
- var $fileLength;
+ protected $fileLength;
/** A segmented cache of the file contents */
- var $buffer;
+ protected $buffer;
/** The file data callback */
- var $callback;
+ protected $callback;
/** The ZIP64 mode */
- var $zip64 = false;
+ protected $zip64 = false;
/** Stored headers */
- var $eocdr, $eocdr64, $eocdr64Locator;
+ protected $eocdr, $eocdr64, $eocdr64Locator;
- var $data;
+ protected $data;
/** The "extra field" ID for ZIP64 central directory entries */
const ZIP64_EXTRA_HEADER = 0x0001;
@@ -128,6 +129,9 @@ class ZipDirectoryReader {
/**
* Private constructor
+ * @param string $fileName
+ * @param callable $callback
+ * @param array $options
*/
protected function __construct( $fileName, $callback, $options ) {
$this->fileName = $fileName;
@@ -159,8 +163,8 @@ class ZipDirectoryReader {
} else {
if ( $this->eocdr['CD size'] == 0xffffffff
|| $this->eocdr['CD offset'] == 0xffffffff
- || $this->eocdr['CD entries total'] == 0xffff )
- {
+ || $this->eocdr['CD entries total'] == 0xffff
+ ) {
$this->error( 'zip-unsupported', 'Central directory header indicates ZIP64, ' .
'but we are in legacy mode. Rejecting this upload is necessary to avoid ' .
'opening vulnerabilities on clients using OpenJDK 7 or later.' );
@@ -174,11 +178,14 @@ class ZipDirectoryReader {
}
fclose( $this->file );
+
return $status;
}
/**
* Throw an error, and log a debug message
+ * @param mixed $code
+ * @param string $debugMessage
*/
function error( $code, $debugMessage ) {
wfDebug( __CLASS__ . ": Fatal error: $debugMessage\n" );
@@ -221,8 +228,8 @@ class ZipDirectoryReader {
$this->error( 'zip-bad', 'trailing bytes after the end of the file comment' );
}
if ( $this->eocdr['disk'] !== 0
- || $this->eocdr['CD start disk'] !== 0 )
- {
+ || $this->eocdr['CD start disk'] !== 0
+ ) {
$this->error( 'zip-unsupported', 'more than one disk (in EOCDR)' );
}
$this->eocdr += $this->unpack(
@@ -245,8 +252,8 @@ class ZipDirectoryReader {
);
$structSize = $this->getStructSize( $info );
- $block = $this->getBlock( $this->getFileLength() - $this->eocdr['EOCDR size']
- - $structSize, $structSize );
+ $start = $this->getFileLength() - $this->eocdr['EOCDR size'] - $structSize;
+ $block = $this->getBlock( $start, $structSize );
$this->eocdr64Locator = $data = $this->unpack( $block, $info );
if ( $data['signature'] !== "PK\x06\x07" ) {
@@ -263,8 +270,8 @@ class ZipDirectoryReader {
*/
function readZip64EndOfCentralDirectoryRecord() {
if ( $this->eocdr64Locator['eocdr64 start disk'] != 0
- || $this->eocdr64Locator['number of disks'] != 0 )
- {
+ || $this->eocdr64Locator['number of disks'] != 0
+ ) {
$this->error( 'zip-unsupported', 'more than one disk (in EOCDR64 locator)' );
}
@@ -287,8 +294,8 @@ class ZipDirectoryReader {
$this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory record' );
}
if ( $data['disk'] !== 0
- || $data['CD start disk'] !== 0 )
- {
+ || $data['CD start disk'] !== 0
+ ) {
$this->error( 'zip-unsupported', 'more than one disk (in EOCDR64)' );
}
}
@@ -297,7 +304,7 @@ class ZipDirectoryReader {
* Find the location of the central directory, as would be seen by a
* non-ZIP64 reader.
*
- * @return List containing offset, size and end position.
+ * @return array List containing offset, size and end position.
*/
function findOldCentralDirectory() {
$size = $this->eocdr['CD size'];
@@ -310,6 +317,7 @@ class ZipDirectoryReader {
$this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
'of central directory record' );
}
+
return array( $offset, $size );
}
@@ -329,8 +337,8 @@ class ZipDirectoryReader {
$endPos = $this->eocdr['position'];
if ( $size == 0xffffffff
|| $offset == 0xffffffff
- || $numEntries == 0xffff )
- {
+ || $numEntries == 0xffff
+ ) {
$this->readZip64EndOfCentralDirectoryLocator();
if ( isset( $this->eocdr64Locator['eocdr64 offset'] ) ) {
@@ -348,11 +356,14 @@ class ZipDirectoryReader {
$this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
'of central directory record' );
}
+
return array( $offset, $size );
}
/**
* Read the central directory at the given location
+ * @param int $offset
+ * @param int $size
*/
function readCentralDirectory( $offset, $size ) {
$block = $this->getBlock( $offset, $size );
@@ -396,10 +407,10 @@ class ZipDirectoryReader {
$pos += $this->getStructSize( $variableInfo );
if ( $this->zip64 && (
- $data['compressed size'] == 0xffffffff
- || $data['uncompressed size'] == 0xffffffff
- || $data['local header offset'] == 0xffffffff ) )
- {
+ $data['compressed size'] == 0xffffffff
+ || $data['uncompressed size'] == 0xffffffff
+ || $data['local header offset'] == 0xffffffff )
+ ) {
$zip64Data = $this->unpackZip64Extra( $data['extra field'] );
if ( $zip64Data ) {
$data = $zip64Data + $data;
@@ -426,9 +437,7 @@ class ZipDirectoryReader {
$year, $month, $day, $hour, $minute, $second );
// Convert the character set in the file name
- if ( !function_exists( 'iconv' )
- || $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) )
- {
+ if ( $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) ) {
$name = $data['name'];
} else {
$name = iconv( 'CP437', 'UTF-8', $data['name'] );
@@ -446,6 +455,7 @@ class ZipDirectoryReader {
/**
* Interpret ZIP64 "extra field" data and return an associative array.
+ * @param string $extraField
* @return array|bool
*/
function unpackZip64Extra( $extraField ) {
@@ -481,12 +491,14 @@ class ZipDirectoryReader {
/**
* Get the length of the file.
+ * @return int
*/
function getFileLength() {
if ( $this->fileLength === null ) {
$stat = fstat( $this->file );
$this->fileLength = $stat['size'];
}
+
return $this->fileLength;
}
@@ -541,6 +553,9 @@ class ZipDirectoryReader {
* If there are not enough bytes in the file to satisfy the request, the
* return value will be truncated. If a request is made for a segment beyond
* the end of the file, an empty string will be returned.
+ *
+ * @param int $segIndex
+ *
* @return string
*/
function getSegment( $segIndex ) {
@@ -548,6 +563,7 @@ class ZipDirectoryReader {
$bytePos = $segIndex * self::SEGSIZE;
if ( $bytePos >= $this->getFileLength() ) {
$this->buffer[$segIndex] = '';
+
return '';
}
if ( fseek( $this->file, $bytePos ) ) {
@@ -559,11 +575,13 @@ class ZipDirectoryReader {
}
$this->buffer[$segIndex] = $seg;
}
+
return $this->buffer[$segIndex];
}
/**
* Get the size of a structure in bytes. See unpack() for the format of $struct.
+ * @param array $struct
* @return int
*/
function getStructSize( $struct ) {
@@ -576,6 +594,7 @@ class ZipDirectoryReader {
$size += $type;
}
}
+
return $size;
}
@@ -613,12 +632,12 @@ class ZipDirectoryReader {
if ( is_array( $type ) ) {
list( $typeName, $fieldSize ) = $type;
switch ( $typeName ) {
- case 'string':
- $data[$key] = substr( $string, $pos, $fieldSize );
- $pos += $fieldSize;
- break;
- default:
- throw new MWException( __METHOD__ . ": invalid type \"$typeName\"" );
+ case 'string':
+ $data[$key] = substr( $string, $pos, $fieldSize );
+ $pos += $fieldSize;
+ break;
+ default:
+ throw new MWException( __METHOD__ . ": invalid type \"$typeName\"" );
}
} else {
// Unsigned little-endian integer
@@ -650,7 +669,7 @@ class ZipDirectoryReader {
* Returns a bit from a given position in an integer value, converted to
* boolean.
*
- * @param $value integer
+ * @param int $value
* @param int $bitIndex The index of the bit, where 0 is the LSB.
* @return bool
*/
@@ -660,6 +679,7 @@ class ZipDirectoryReader {
/**
* Debugging helper function which dumps a string in hexdump -C format.
+ * @param string $s
*/
function hexDump( $s ) {
$n = strlen( $s );
@@ -696,7 +716,7 @@ class ZipDirectoryReader {
* Internal exception class. Will be caught by private code.
*/
class ZipDirectoryReaderError extends Exception {
- var $errorCode;
+ protected $errorCode;
function __construct( $code ) {
$this->errorCode = $code;